Integrating latest 47acbe8

This commit is contained in:
alexpete
2021-03-25 13:57:57 -07:00
parent 448c549698
commit 75dc720198
10312 changed files with 2711566 additions and 671451 deletions
@@ -15,6 +15,11 @@
#include <AzCore/Math/Vector3.h>
#include <AzCore/Component/Component.h>
namespace AzFramework
{
struct CameraState;
}
namespace Camera
{
/**
@@ -56,6 +61,16 @@ namespace Camera
* @return True if the camera position was successfully retrieved, false if not.
*/
virtual bool GetActiveCameraPosition(AZ::Vector3& /*cameraPos*/) { return false; }
/**
* Gets the position of the currently active Editor camera.
* The Editor can have multiple viewports displayed, though at most only one is active at any point in time.
* (Active is not the same as "has focus" - a different editor pane can have focus, but there's still one
* active viewport that's updating every frame, and the others are not).
* @param cameraView The current camera view in the one active Editor viewport.
* @return True if the camera view was successfully retrieved, false if not.
*/
virtual bool GetActiveCameraState([[maybe_unused]] AzFramework::CameraState& cameraState) { return false; }
};
using EditorCameraRequestBus = AZ::EBus<EditorCameraRequests>;
@@ -42,6 +42,7 @@ class QMenu;
class QWidget;
class QApplication;
class QDockWidget;
class QMainWindow;
struct IEditor;
namespace AzToolsFramework
@@ -587,6 +588,13 @@ namespace AzToolsFramework
*/
virtual const char* GetEngineVersion() const = 0;
/**
* Retrieves if Legacy Slice System is enabled
*/
virtual bool IsLegacySliceSystemEnabled() const = 0;
virtual bool ShouldAssertForLegacySlicesUsage() const = 0;
/**
* Creates and adds a new entity to the tools application from components which match at least one of the requiredTags
* The tag matching occurs on AZ::Edit::SystemComponentTags attribute from the reflected class data in the serialization context
@@ -948,6 +956,9 @@ namespace AzToolsFramework
/// Notify that the IEditor is ready
virtual void NotifyIEditorAvailable(IEditor* /*editor*/) {}
/// Notify that the MainWindow has been fully initialized
virtual void NotifyMainWindowInitialized(QMainWindow* /*mainWindow*/) {}
/// Signal that an asset should be highlighted / selected
virtual void SelectAsset(const QString& /* assetPath */) {}
};
@@ -35,6 +35,7 @@
#include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
#include <AzToolsFramework/Slice/SliceMetadataEntityContextComponent.h>
#include <AzToolsFramework/Prefab/PrefabSystemComponent.h>
#include <AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h>
#include <AzToolsFramework/Component/EditorComponentAPIComponent.h>
#include <AzToolsFramework/Component/EditorLevelComponentAPIComponent.h>
#include <AzToolsFramework/Entity/EditorEntityActionComponent.h>
@@ -66,6 +67,7 @@
#include <AzToolsFramework/ViewportSelection/EditorTransformComponentSelectionRequestBus.h>
#include <AzToolsFramework/AssetEditor/AssetEditorBus.h>
#include <AzToolsFramework/Render/EditorIntersectorComponent.h>
#include <AzToolsFramework/UI/EditorEntityUi/EditorEntityUiSystemComponent.h>
#include <QtWidgets/QMessageBox>
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // 4251: 'QFileInfo::d_ptr': class 'QSharedDataPointer<QFileInfoPrivate>' needs to have dll-interface to be used by clients of class 'QFileInfo'
@@ -93,6 +95,9 @@ namespace AzToolsFramework
static const char* s_startupLogWindow = "Startup";
static const char* s_prefabSystemKey = "/Amazon/Editor/Preferences/EnablePrefabSystem";
static const char* s_legacySlicesAssertKey = "/Amazon/Editor/Preferences/ShouldAssertForLegacySlicesUsage";
template<typename IdContainerType>
void DeleteEntities(const IdContainerType& entityIds)
{
@@ -206,6 +211,19 @@ namespace AzToolsFramework
}
};
struct EditorEventsBusHandler final
: public EditorEventsBus::Handler
, public AZ::BehaviorEBusHandler
{
AZ_EBUS_BEHAVIOR_BINDER(EditorEventsBusHandler, "{352F80BB-469A-40B6-B322-FE57AB51E4DA}", AZ::SystemAllocator,
NotifyRegisterViews);
void NotifyRegisterViews() override
{
Call(FN_NotifyRegisterViews);
}
};
} // Internal
#define AZ_MAX_ENGINE_VERSION_LEN 64
@@ -346,6 +364,7 @@ namespace AzToolsFramework
components.insert(components.end(), {
azrtti_typeid<EditorEntityContextComponent>(),
azrtti_typeid<Components::EditorEntityUiSystemComponent>(),
azrtti_typeid<SliceMetadataEntityContextComponent>(),
azrtti_typeid<Prefab::PrefabSystemComponent>(),
azrtti_typeid<EditorEntityFixupComponent>(),
@@ -440,6 +459,7 @@ namespace AzToolsFramework
QTreeViewWithStateSaving::Reflect(context);
QWidgetSavedState::Reflect(context);
SliceUtilities::Reflect(context);
Prefab::PrefabIntegrationManager::Reflect(context);
ComponentModeFramework::ComponentModeDelegate::Reflect(context);
@@ -499,6 +519,14 @@ namespace AzToolsFramework
->Event("UnregisterViewPane", &EditorRequests::UnregisterViewPane)
;
behaviorContext->EBus<EditorEventsBus>("EditorEventBus")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Category, "Editor")
->Attribute(AZ::Script::Attributes::Module, "editor")
->Handler<Internal::EditorEventsBusHandler>()
->Event("NotifyRegisterViews", &EditorEvents::NotifyRegisterViews)
;
behaviorContext->EBus<ViewPaneCallbackBus>("ViewPaneCallbackBus")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Category, "Editor")
@@ -1741,6 +1769,26 @@ namespace AzToolsFramework
return m_engineConfigImpl->GetEngineVersion();
}
bool ToolsApplication::IsLegacySliceSystemEnabled() const
{
bool value = false;
if (auto* registry = AZ::SettingsRegistry::Get())
{
registry->Get(value, Internal::s_prefabSystemKey);
}
return !value;
}
bool ToolsApplication::ShouldAssertForLegacySlicesUsage() const
{
bool value = false;
if (auto* registry = AZ::SettingsRegistry::Get())
{
registry->Get(value, Internal::s_legacySlicesAssertKey);
}
return value;
}
void ToolsApplication::CreateAndAddEntityFromComponentTags(const AZStd::vector<AZ::Crc32>& requiredTags, const char* entityName)
{
if (!entityName || !entityName[0])
@@ -145,6 +145,8 @@ namespace AzToolsFramework
bool IsEditorInIsolationMode() override;
const char* GetEngineRootPath() const override;
const char* GetEngineVersion() const override;
bool IsLegacySliceSystemEnabled() const override;
bool ShouldAssertForLegacySlicesUsage() const override;
void CreateAndAddEntityFromComponentTags(const AZStd::vector<AZ::Crc32>& requiredTags, const char* entityName) override;
@@ -0,0 +1,42 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Component/EntityId.h>
#include <AzCore/EBus/EBus.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/std/string/string.h>
namespace AzToolsFramework
{
namespace AssetBrowser
{
class AssetBrowserSourceDropEvents
: public AZ::EBusTraits
{
public:
virtual ~AssetBrowserSourceDropEvents() = default;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
// File extension, includes dot.
using BusIdType = AZStd::string;
//! Handles source files dropped from the AssetBrowser into the scene.
virtual void HandleSourceFileType(AZStd::string_view sourceFilePath, AZ::EntityId parentId, AZ::Vector3 position) const = 0;
};
using AssetBrowserSourceDropBus = AZ::EBus<AssetBrowserSourceDropEvents>;
} // namespace AssetBrowser
} // namespace AzToolsFramework
@@ -194,7 +194,8 @@ namespace AzToolsFramework
->Field("m_name", &AssetBrowserEntry::m_name)
->Field("m_children", &AssetBrowserEntry::m_children)
->Field("m_row", &AssetBrowserEntry::m_row)
->Version(1);
->Field("m_fullPath", &AssetBrowserEntry::m_fullPath)
->Version(2);
}
}
@@ -282,7 +282,7 @@ namespace AzToolsFramework
{
m_dirty = false;
AZ::Data::AssetBus::Handler::BusDisconnect(asset.GetId());
AZ::Data::AssetBus::MultiHandler::BusDisconnect(asset.GetId());
// Clone the asset
AZ::Data::AssetId newAssetId = AZ::Data::AssetId(AZ::Uuid::CreateRandom());
@@ -333,9 +333,9 @@ namespace AzToolsFramework
m_saveAssetAction->setEnabled(false);
m_propertyEditor->ClearInstances();
if (AZ::Data::AssetBus::Handler::BusIsConnectedId(asset.GetId()))
if (AZ::Data::AssetBus::MultiHandler::BusIsConnectedId(asset.GetId()))
{
AZ::Data::AssetBus::Handler::BusDisconnect(asset.GetId());
AZ::Data::AssetBus::MultiHandler::BusDisconnect(asset.GetId());
}
QString errString = tr("Failed to load %1!").arg(asset.GetHint().c_str());
AZ_Error("Asset Editor", false, errString.toUtf8());
@@ -591,6 +591,8 @@ namespace AzToolsFramework
{
auto asset = AZ::Data::AssetManager::Instance().GetAsset(assetId, assetType, AZ::Data::AssetLoadBehavior::Default);
asset.BlockUntilLoadComplete();
if (asset.IsReady())
{
OnAssetReady(asset);
@@ -599,10 +601,10 @@ namespace AzToolsFramework
{
if (m_inMemoryAsset)
{
AZ::Data::AssetBus::Handler::BusDisconnect(m_inMemoryAsset.GetId());
AZ::Data::AssetBus::MultiHandler::BusDisconnect(m_inMemoryAsset.GetId());
}
AZ::Data::AssetBus::Handler::BusConnect(asset.GetId());
AZ::Data::AssetBus::MultiHandler::BusConnect(asset.GetId());
// Need to disable editing until OnAssetReady.
m_propertyEditor->setEnabled(false);
@@ -619,7 +621,9 @@ namespace AzToolsFramework
AssetEditorValidationRequestBus::Event(m_sourceAssetId, &AssetEditorValidationRequests::PreAssetSave, m_inMemoryAsset);
if (AZ::Utils::SaveObjectToStream(dstByteStream, AZ::DataStream::ST_XML, m_inMemoryAsset.Get(), m_inMemoryAsset.Get()->RTTI_GetType(), m_serializeContext))
if (AZ::Utils::SaveObjectToStream(
dstByteStream, AZ::DataStream::ST_XML, m_inMemoryAsset.Get(), m_inMemoryAsset.Get()->RTTI_GetType(),
m_serializeContext))
{
AZStd::swap(newSaveData, m_saveData);
}
@@ -800,7 +804,7 @@ namespace AzToolsFramework
if (m_inMemoryAsset)
{
AZ::Data::AssetBus::Handler::BusDisconnect(m_inMemoryAsset.GetId());
AZ::Data::AssetBus::MultiHandler::BusDisconnect(m_inMemoryAsset.GetId());
m_inMemoryAsset.Release();
}
@@ -64,7 +64,7 @@ namespace AzToolsFramework
*/
class AssetEditorWidget
: public QWidget
, private AZ::Data::AssetBus::Handler
, private AZ::Data::AssetBus::MultiHandler
, private AzFramework::AssetCatalogEventBus::Handler
, private AzToolsFramework::IPropertyEditorNotify
, private AZ::SystemTickBus::Handler
@@ -49,6 +49,7 @@
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
#include <AzToolsFramework/ToolsComponents/EditorNonUniformScaleComponent.h>
#include <AzToolsFramework/UI/PropertyEditor/PropertyManagerComponent.h>
#include <AzToolsFramework/UI/EditorEntityUi/EditorEntityUiSystemComponent.h>
#include <AzToolsFramework/Thumbnails/ThumbnailerComponent.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserComponent.h>
#include <AzToolsFramework/MaterialBrowser/MaterialBrowserComponent.h>
@@ -98,6 +99,7 @@ namespace AzToolsFramework
AzToolsFramework::Components::EditorEntitySearchComponent::CreateDescriptor(),
AzToolsFramework::Components::EditorIntersectorComponent::CreateDescriptor(),
AzToolsFramework::AzToolsFrameworkConfigurationSystemComponent::CreateDescriptor(),
AzToolsFramework::Components::EditorEntityUiSystemComponent::CreateDescriptor(),
});
}
}
@@ -0,0 +1,86 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/EBus/EBus.h>
#include <AzCore/Outcome/Outcome.h>
#include <AzCore/std/any.h>
#include <AzCore/std/string/string.h>
class QMenu;
namespace AzToolsFramework
{
enum class EditorContextMenuOrdering
{
TOP = 0,
MIDDLE = 500,
BOTTOM = 1000
};
//! Editor Settings API Bus
class EditorContextMenuEvents
: public AZ::EBusTraits
{
public:
virtual ~EditorContextMenuEvents() = default;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::MultipleAndOrdered;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
/**
* Determines the order in which handlers populate the context menus.
*/
struct BusHandlerOrderCompare
{
AZ_FORCE_INLINE bool operator()(EditorContextMenuEvents* left, EditorContextMenuEvents* right) const
{
if (left->GetMenuPosition() == right->GetMenuPosition())
{
return left->GetMenuIdentifier() < right->GetMenuIdentifier();
}
return left->GetMenuPosition() < right->GetMenuPosition();
}
};
/**
* Specifies the order in which a handler receives context menu events relative to other handlers.
* This value should not be changed while the handler is connected.
* Use the EditorContextMenuOrdering enum values as a baseline.
* @return A value specifying this handler's relative order.
*/
virtual int GetMenuPosition() const
{
return aznumeric_cast<int>(EditorContextMenuOrdering::BOTTOM);
}
/**
* Returns the identifier for this handler.
* Used to break ties in the order comparison.
* @return A string containing the identifier for this handler.
*/
virtual AZStd::string GetMenuIdentifier() const
{
return "";
}
/**
* Appends menu items to the global editor context menu.
* This is the menu that appears when right clicking the main editor window,
* including the Entity Outliner and the Viewport.
*/
virtual void PopulateEditorGlobalContextMenu(QMenu* menu) const = 0;
};
using EditorContextMenuBus = AZ::EBus<EditorContextMenuEvents>;
}
@@ -12,6 +12,8 @@
#include "AzToolsFramework_precompiled.h"
#include <AzToolsFramework/Entity/EditorEntityContextComponent.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/EntityUtils.h>
#include <AzCore/Component/ComponentApplicationBus.h>
@@ -51,8 +53,9 @@
#include <AzToolsFramework/ToolsComponents/EditorOnlyEntityComponent.h>
#include <AzToolsFramework/Entity/EditorEntitySortComponent.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include "EditorEntityContextComponent.h"
namespace AzToolsFramework
{
@@ -160,7 +163,19 @@ namespace AzToolsFramework
//=========================================================================
void EditorEntityContextComponent::Activate()
{
m_entityOwnershipService = AZStd::make_unique<SliceEditorEntityOwnershipService>(GetContextId(), GetSerializeContext());
m_isLegacySliceService = true;
AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(m_isLegacySliceService,
&AzToolsFramework::ToolsApplicationRequests::IsLegacySliceSystemEnabled);
if (m_isLegacySliceService)
{
m_entityOwnershipService = AZStd::make_unique<SliceEditorEntityOwnershipService>(GetContextId(), GetSerializeContext());
}
else
{
m_entityOwnershipService = AZStd::make_unique<PrefabEditorEntityOwnershipService>(GetContextId(), GetSerializeContext());
}
InitContext();
@@ -359,9 +374,18 @@ namespace AzToolsFramework
AZ::SliceComponent::SliceReferenceToInstancePtrs& instancesInLayers)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
SliceEditorEntityOwnershipService* editorEntityOwnershipService =
static_cast<SliceEditorEntityOwnershipService*>(m_entityOwnershipService.get());
return editorEntityOwnershipService->SaveToStreamForEditor(stream, entitiesInLayers, instancesInLayers);
if (m_isLegacySliceService)
{
SliceEditorEntityOwnershipService* editorEntityOwnershipService =
static_cast<SliceEditorEntityOwnershipService*>(m_entityOwnershipService.get());
return editorEntityOwnershipService->SaveToStreamForEditor(stream, entitiesInLayers, instancesInLayers);
}
else
{
AZ_Assert(!m_entityOwnershipService->m_shouldAssertForLegacySlicesUsage, "Not implemented");
return true;
}
}
void EditorEntityContextComponent::GetLooseEditorEntities(EntityList& entityList)
@@ -375,9 +399,17 @@ namespace AzToolsFramework
bool EditorEntityContextComponent::SaveToStreamForGame(AZ::IO::GenericStream& stream, AZ::DataStream::StreamType streamType)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
SliceEditorEntityOwnershipService* editorEntityOwnershipService =
static_cast<SliceEditorEntityOwnershipService*>(m_entityOwnershipService.get());
return editorEntityOwnershipService->SaveToStreamForGame(stream, streamType);
if (m_isLegacySliceService)
{
SliceEditorEntityOwnershipService* editorEntityOwnershipService =
static_cast<SliceEditorEntityOwnershipService*>(m_entityOwnershipService.get());
return editorEntityOwnershipService->SaveToStreamForGame(stream, streamType);
}
else
{
AZ_Assert(!m_entityOwnershipService->m_shouldAssertForLegacySlicesUsage, "Not implemented");
return true;
}
}
//=========================================================================
@@ -410,11 +442,16 @@ namespace AzToolsFramework
EditorEntityContextNotificationBus::Broadcast(&EditorEntityContextNotification::OnEntityStreamLoadBegin);
SliceEditorEntityOwnershipService* editorEntityOwnershipService =
static_cast<SliceEditorEntityOwnershipService*>(m_entityOwnershipService.get());
const bool loadedSuccessfully = editorEntityOwnershipService->LoadFromStreamWithLayers(stream, levelPakFile);
bool loadedSuccessfully = true;
if (m_isLegacySliceService)
{
loadedSuccessfully = static_cast<SliceEditorEntityOwnershipService*>(m_entityOwnershipService.get())->LoadFromStreamWithLayers(stream, levelPakFile);
}
else
{
AZ_Assert(!m_entityOwnershipService->m_shouldAssertForLegacySlicesUsage, "Not implemented");
}
LoadFromStreamComplete(loadedSuccessfully);
return loadedSuccessfully;
@@ -450,10 +487,16 @@ namespace AzToolsFramework
EditorEntityContextNotificationBus::Broadcast(&EditorEntityContextNotification::OnStartPlayInEditorBegin);
SliceEditorEntityOwnershipService* editorEntityOwnershipService =
static_cast<SliceEditorEntityOwnershipService*>(m_entityOwnershipService.get());
editorEntityOwnershipService->StartPlayInEditor(m_editorToRuntimeIdMap, m_runtimeToEditorIdMap);
if (m_isLegacySliceService)
{
SliceEditorEntityOwnershipService* editorEntityOwnershipService =
static_cast<SliceEditorEntityOwnershipService*>(m_entityOwnershipService.get());
editorEntityOwnershipService->StartPlayInEditor(m_editorToRuntimeIdMap, m_runtimeToEditorIdMap);
}
else
{
AZ_Assert(!m_entityOwnershipService->m_shouldAssertForLegacySlicesUsage, "Not implemented");
}
m_isRunningGame = true;
@@ -471,10 +514,17 @@ namespace AzToolsFramework
m_isRunningGame = false;
SliceEditorEntityOwnershipService* editorEntityOwnershipService =
static_cast<SliceEditorEntityOwnershipService*>(m_entityOwnershipService.get());
if (m_isLegacySliceService)
{
SliceEditorEntityOwnershipService* editorEntityOwnershipService =
static_cast<SliceEditorEntityOwnershipService*>(m_entityOwnershipService.get());
editorEntityOwnershipService->StopPlayInEditor(m_editorToRuntimeIdMap, m_runtimeToEditorIdMap);
editorEntityOwnershipService->StopPlayInEditor(m_editorToRuntimeIdMap, m_runtimeToEditorIdMap);
}
else
{
AZ_Assert(!m_entityOwnershipService->m_shouldAssertForLegacySlicesUsage, "Not implemented");
}
ToolsApplicationRequests::Bus::Broadcast(&ToolsApplicationRequests::SetSelectedEntities, m_selectedBeforeStartingGame);
m_selectedBeforeStartingGame.clear();
@@ -603,13 +653,20 @@ namespace AzToolsFramework
void EditorEntityContextComponent::OnContextEntitiesAdded(const EntityList& entities)
{
EntityContext::OnContextEntitiesAdded(entities);
SliceEditorEntityOwnershipService* editorEntityOwnershipService =
static_cast<SliceEditorEntityOwnershipService*>(m_entityOwnershipService.get());
// Any entities being added to the context that don't belong to another slice
// need to be associated with the root metadata info component.
editorEntityOwnershipService->AssociateToRootMetadataEntity(entities);
if (m_isLegacySliceService)
{
SliceEditorEntityOwnershipService* editorEntityOwnershipService =
static_cast<SliceEditorEntityOwnershipService*>(m_entityOwnershipService.get());
// Any entities being added to the context that don't belong to another slice
// need to be associated with the root metadata info component.
editorEntityOwnershipService->AssociateToRootMetadataEntity(entities);
}
else
{
AZ_Assert(!m_entityOwnershipService->m_shouldAssertForLegacySlicesUsage, "Not implemented");
}
SetupEditorEntities(entities);
}
@@ -124,18 +124,19 @@ namespace AzToolsFramework
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("EditorEntityContextService", 0x28d93a43));
provided.push_back(AZ_CRC_CE("EditorEntityContextService"));
}
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("EditorEntityContextService", 0x28d93a43));
incompatible.push_back(AZ_CRC_CE("EditorEntityContextService"));
}
static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent)
{
dependent.push_back(AZ_CRC("AssetDatabaseService", 0x3abf5601));
dependent.push_back(AZ_CRC("SliceSystemService", 0x1a5b7aad));
dependent.push_back(AZ_CRC_CE("AssetDatabaseService"));
dependent.push_back(AZ_CRC_CE("SliceSystemService"));
dependent.push_back(AZ_CRC_CE("PrefabSystem"));
}
protected:
@@ -183,7 +184,8 @@ namespace AzToolsFramework
AZ::ComponentTypeList m_requiredEditorComponentTypes;
//! Edit time visibility management integrating entities with the IVisibilitySystem.
AzFramework::EntityVisibilityBoundsUnionSystem m_entityVisibilityBoundsUnionSystem;
AzFramework::EntityVisibilityBoundsUnionSystem m_entityVisibilityBoundsUnionSystem;
bool m_isLegacySliceService;
};
} // namespace AzToolsFramework
@@ -0,0 +1,39 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Component/Entity.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/string/string.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
namespace AzToolsFramework
{
class PrefabEditorEntityOwnershipInterface
{
public:
AZ_RTTI(PrefabEditorEntityOwnershipInterface,"{38E764BA-A089-49F3-848F-46018822CE2E}");
//! Creates a prefab instance with the provided entities and nestedPrefabInstances.
//! /param entities The entities to put under the new prefab.
//! /param nestedPrefabInstances The nested prefab instances to put under the new prefab.
//! /param filePath The filepath corresponding to the prefab file to be created.
//! /param instanceToParentUnder The instance under which the newly created prefab instance is parented under.
//! /return The optional reference to the prefab created.
virtual Prefab::InstanceOptionalReference CreatePrefab(
const AZStd::vector<AZ::Entity*>& entities, AZStd::vector<AZStd::unique_ptr<Prefab::Instance>>&& nestedPrefabInstances,
const AZStd::string& filePath, Prefab::Instance& instanceToParentUnder) = 0;
virtual Prefab::InstanceOptionalReference GetRootPrefabInstance() = 0;
};
}
@@ -10,21 +10,48 @@
*
*/
#include <AzCore/Component/Entity.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
#include <AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.h>
#include <AzToolsFramework/Prefab/PrefabLoader.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzCore/Component/Entity.h>
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
namespace AzToolsFramework
{
PrefabEditorEntityOwnershipService::PrefabEditorEntityOwnershipService(const AzFramework::EntityContextId& entityContextId,
AZ::SerializeContext* serializeContext)
: m_entityContextId(entityContextId)
, m_serializeContext(serializeContext)
{
AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(
m_shouldAssertForLegacySlicesUsage, &AzToolsFramework::ToolsApplicationRequests::ShouldAssertForLegacySlicesUsage);
AZ::Interface<PrefabEditorEntityOwnershipInterface>::Register(this);
}
PrefabEditorEntityOwnershipService::~PrefabEditorEntityOwnershipService()
{
AZ::Interface<PrefabEditorEntityOwnershipInterface>::Unregister(this);
}
void PrefabEditorEntityOwnershipService::Initialize()
{
m_prefabSystemComponent = AZ::Interface<Prefab::PrefabSystemComponentInterface>::Get();
AZ_Assert(m_prefabSystemComponent != nullptr, "Couldn't get prefab system component, it's a requirement for PrefabEntityOwnership system to work");
AZ_Assert(m_prefabSystemComponent != nullptr,
"Couldn't get prefab system component, it's a requirement for PrefabEntityOwnership system to work");
m_loaderInterface = AZ::Interface<Prefab::PrefabLoaderInterface>::Get();
AZ_Assert(m_loaderInterface != nullptr,
"Couldn't get prefab loader interface, it's a requirement for PrefabEntityOwnership system to work");
// This path has to be updated with a proper path based where the level lives
// Potentially, a level should be a prefab itself
m_rootInstance = AZStd::unique_ptr<Prefab::Instance>(m_prefabSystemComponent->CreatePrefab({}, {}, "/"));
m_sliceOwnershipService.BusConnect(m_entityContextId);
m_sliceOwnershipService.m_shouldAssertForLegacySlicesUsage = m_shouldAssertForLegacySlicesUsage;
m_editorSliceOwnershipService.BusConnect();
m_editorSliceOwnershipService.m_shouldAssertForLegacySlicesUsage = m_shouldAssertForLegacySlicesUsage;
}
bool PrefabEditorEntityOwnershipService::IsInitialized()
@@ -34,6 +61,8 @@ namespace AzToolsFramework
void PrefabEditorEntityOwnershipService::Destroy()
{
m_editorSliceOwnershipService.BusDisconnect();
m_sliceOwnershipService.BusDisconnect();
m_rootInstance.reset();
}
@@ -46,7 +75,7 @@ namespace AzToolsFramework
{
AZ_Assert(IsInitialized(), "Tried to add an entity without initializing the Entity Ownership Service");
m_rootInstance->AddEntity(*entity);
m_entitiesAddedCallback({ entity });
HandleEntitiesAdded({ entity });
}
void PrefabEditorEntityOwnershipService::AddEntities(const EntityList& entities)
@@ -56,7 +85,7 @@ namespace AzToolsFramework
{
m_rootInstance->AddEntity(*entity);
}
m_entitiesAddedCallback(entities);
HandleEntitiesAdded(entities);
}
bool PrefabEditorEntityOwnershipService::DestroyEntity(AZ::Entity* entity)
@@ -67,9 +96,11 @@ namespace AzToolsFramework
bool PrefabEditorEntityOwnershipService::DestroyEntityById(AZ::EntityId entityId)
{
AZ_Assert(IsInitialized(), "Tried to destroy an entity without initializing the Entity Ownership Service");
AZ_Assert(m_entitiesRemovedCallback, "Callback function for DestroyEntityById has not been set.");
AZStd::unique_ptr<AZ::Entity> detachedEntity = m_rootInstance->DetachEntity(entityId);
if (detachedEntity)
{
AzFramework::SliceEntityRequestBus::MultiHandler::BusDisconnect(detachedEntity->GetId());
m_entitiesRemovedCallback({ entityId });
return true;
}
@@ -92,8 +123,14 @@ namespace AzToolsFramework
{
}
void PrefabEditorEntityOwnershipService::HandleEntitiesAdded(const EntityList& /*entities*/)
void PrefabEditorEntityOwnershipService::HandleEntitiesAdded(const EntityList& entities)
{
AZ_Assert(m_entitiesAddedCallback, "Callback function for AddEntity has not been set.");
for (const AZ::Entity* entity : entities)
{
AzFramework::SliceEntityRequestBus::MultiHandler::BusConnect(entity->GetId());
}
m_entitiesAddedCallback(entities);
}
bool PrefabEditorEntityOwnershipService::LoadFromStream(AZ::IO::GenericStream& /*stream*/, bool /*remapIds*/,
@@ -102,6 +139,28 @@ namespace AzToolsFramework
return true;
}
Prefab::InstanceOptionalReference PrefabEditorEntityOwnershipService::CreatePrefab(
const AZStd::vector<AZ::Entity*>& entities, AZStd::vector<AZStd::unique_ptr<Prefab::Instance>>&& nestedPrefabInstances,
const AZStd::string& filePath, Prefab::Instance& instanceToParentUnder)
{
AZStd::unique_ptr<Prefab::Instance> createdPrefabInstance =
m_prefabSystemComponent->CreatePrefab(entities, AZStd::move(nestedPrefabInstances), filePath);
if (createdPrefabInstance)
{
Prefab::Instance& addedInstance = instanceToParentUnder.AddInstance(AZStd::move(createdPrefabInstance));
HandleEntitiesAdded({addedInstance.m_containerEntity.get()});
return addedInstance;
}
return AZStd::nullopt;
}
Prefab::InstanceOptionalReference PrefabEditorEntityOwnershipService::GetRootPrefabInstance()
{
AZ_Assert(m_rootInstance, "A valid root prefab instance couldn't be found in PrefabEditorEntityOwnershipService.");
return *m_rootInstance;
}
void PrefabEditorEntityOwnershipService::SetEntitiesAddedCallback(OnEntitiesAddedCallback onEntitiesAddedCallback)
{
m_entitiesAddedCallback = AZStd::move(onEntitiesAddedCallback);
@@ -116,4 +175,149 @@ namespace AzToolsFramework
{
m_validateEntitiesCallback = AZStd::move(validateEntitiesCallback);
}
//////////////////////////////////////////////////////////////////////////
// Slice Buses implementation with Assert(false), this will exist only during Slice->Prefab
// development to pinpoint and replace specific calls to Slice system
AZ::SliceComponent::SliceInstanceAddress PrefabEditorEntityOwnershipService::GetOwningSlice()
{
AZ_Assert(!m_shouldAssertForLegacySlicesUsage, "Slice usage with Prefab code enabled");
return AZ::SliceComponent::SliceInstanceAddress();
}
AZ::Data::AssetId UnimplementedSliceEntityOwnershipService::CurrentlyInstantiatingSlice()
{
AZ_Assert(!m_shouldAssertForLegacySlicesUsage, "Slice usage with Prefab code enabled");
return {};
}
bool UnimplementedSliceEntityOwnershipService::HandleRootEntityReloadedFromStream(
AZ::Entity*, bool, AZ::SliceComponent::EntityIdToEntityIdMap*)
{
AZ_Assert(!m_shouldAssertForLegacySlicesUsage, "Slice usage with Prefab code enabled");
return false;
}
AZ::SliceComponent* UnimplementedSliceEntityOwnershipService::GetRootSlice()
{
AZ_Assert(!m_shouldAssertForLegacySlicesUsage, "Slice usage with Prefab code enabled");
return nullptr;
}
const AZ::SliceComponent::EntityIdToEntityIdMap& UnimplementedSliceEntityOwnershipService::GetLoadedEntityIdMap()
{
AZ_Assert(!m_shouldAssertForLegacySlicesUsage, "Slice usage with Prefab code enabled");
static AZ::SliceComponent::EntityIdToEntityIdMap dummy;
return dummy;
}
AZ::EntityId UnimplementedSliceEntityOwnershipService::FindLoadedEntityIdMapping(const AZ::EntityId&) const
{
AZ_Assert(!m_shouldAssertForLegacySlicesUsage, "Slice usage with Prefab code enabled");
return AZ::EntityId();
}
AzFramework::SliceInstantiationTicket UnimplementedSliceEntityOwnershipService::InstantiateSlice(
const AZ::Data::Asset<AZ::Data::AssetData>&,
const AZ::IdUtils::Remapper<AZ::EntityId>::IdMapper&,
const AZ::Data::AssetFilterCB&)
{
AZ_Assert(!m_shouldAssertForLegacySlicesUsage, "Slice usage with Prefab code enabled");
return AzFramework::SliceInstantiationTicket();
}
AZ::SliceComponent::SliceInstanceAddress UnimplementedSliceEntityOwnershipService::CloneSliceInstance(
AZ::SliceComponent::SliceInstanceAddress, AZ::SliceComponent::EntityIdToEntityIdMap&)
{
AZ_Assert(!m_shouldAssertForLegacySlicesUsage, "Slice usage with Prefab code enabled");
return {};
}
void UnimplementedSliceEntityOwnershipService::CancelSliceInstantiation(const AzFramework::SliceInstantiationTicket&)
{
AZ_Assert(!m_shouldAssertForLegacySlicesUsage, "Slice usage with Prefab code enabled");
}
AzFramework::SliceInstantiationTicket UnimplementedSliceEntityOwnershipService::GenerateSliceInstantiationTicket()
{
AZ_Assert(!m_shouldAssertForLegacySlicesUsage, "Slice usage with Prefab code enabled");
return AzFramework::SliceInstantiationTicket();
}
void UnimplementedSliceEntityOwnershipService::SetIsDynamic(bool)
{
AZ_Assert(!m_shouldAssertForLegacySlicesUsage, "Slice usage with Prefab code enabled");
}
const AzFramework::RootSliceAsset& UnimplementedSliceEntityOwnershipService::GetRootAsset() const
{
static AzFramework::RootSliceAsset dummy;
AZ_Assert(!m_shouldAssertForLegacySlicesUsage, "Slice usage with Prefab code enabled");
return dummy;
}
//////////////////////////////////////////////////////////////////////////
AzFramework::SliceInstantiationTicket UnimplementedSliceEditorEntityOwnershipService::InstantiateEditorSlice(
const AZ::Data::Asset<AZ::Data::AssetData>&, const AZ::Transform&)
{
AZ_Assert(!m_shouldAssertForLegacySlicesUsage, "Slice usage with Prefab code enabled");
return AzFramework::SliceInstantiationTicket();
}
AZ::SliceComponent::SliceInstanceAddress UnimplementedSliceEditorEntityOwnershipService::CloneEditorSliceInstance(
AZ::SliceComponent::SliceInstanceAddress, AZ::SliceComponent::EntityIdToEntityIdMap&)
{
AZ_Assert(!m_shouldAssertForLegacySlicesUsage, "Slice usage with Prefab code enabled");
return {};
}
AZ::SliceComponent::SliceInstanceAddress UnimplementedSliceEditorEntityOwnershipService::CloneSubSliceInstance(
const AZ::SliceComponent::SliceInstanceAddress&, const AZStd::vector<AZ::SliceComponent::SliceInstanceAddress>&,
const AZ::SliceComponent::SliceInstanceAddress&, AZ::SliceComponent::EntityIdToEntityIdMap*)
{
AZ_Assert(!m_shouldAssertForLegacySlicesUsage, "Slice usage with Prefab code enabled");
return {};
}
AZ::SliceComponent::SliceInstanceAddress UnimplementedSliceEditorEntityOwnershipService::PromoteEditorEntitiesIntoSlice(
const AZ::Data::Asset<AZ::SliceAsset>&, const AZ::SliceComponent::EntityIdToEntityIdMap&)
{
AZ_Assert(!m_shouldAssertForLegacySlicesUsage, "Slice usage with Prefab code enabled");
return {};
}
void UnimplementedSliceEditorEntityOwnershipService::DetachSliceEntities(const EntityIdList&)
{
AZ_Assert(!m_shouldAssertForLegacySlicesUsage, "Slice usage with Prefab code enabled");
}
void UnimplementedSliceEditorEntityOwnershipService::DetachSliceInstances(const AZ::SliceComponent::SliceInstanceAddressSet&)
{
AZ_Assert(!m_shouldAssertForLegacySlicesUsage, "Slice usage with Prefab code enabled");
}
void UnimplementedSliceEditorEntityOwnershipService::DetachSubsliceInstances(const AZ::SliceComponent::SliceInstanceEntityIdRemapList&)
{
AZ_Assert(!m_shouldAssertForLegacySlicesUsage, "Slice usage with Prefab code enabled");
}
void UnimplementedSliceEditorEntityOwnershipService::RestoreSliceEntity(
AZ::Entity*, const AZ::SliceComponent::EntityRestoreInfo&, SliceEntityRestoreType)
{
AZ_Assert(!m_shouldAssertForLegacySlicesUsage, "Slice usage with Prefab code enabled");
}
void UnimplementedSliceEditorEntityOwnershipService::ResetEntitiesToSliceDefaults(EntityIdList)
{
AZ_Assert(!m_shouldAssertForLegacySlicesUsage, "Slice usage with Prefab code enabled");
}
AZ::SliceComponent* UnimplementedSliceEditorEntityOwnershipService::GetEditorRootSlice()
{
AZ_Assert(!m_shouldAssertForLegacySlicesUsage, "Slice usage with Prefab code enabled");
return nullptr;
}
}
@@ -13,6 +13,10 @@
#pragma once
#include <AzFramework/Entity/PrefabEntityOwnershipService.h>
#include <AzFramework/Entity/SliceEntityOwnershipServiceBus.h>
#include <AzFramework/Slice/SliceEntityBus.h>
#include <AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h>
#include <AzToolsFramework/Entity/SliceEditorEntityOwnershipServiceBus.h>
namespace AzToolsFramework
{
@@ -20,10 +24,79 @@ namespace AzToolsFramework
{
class Instance;
class PrefabSystemComponentInterface;
class PrefabLoaderInterface;
}
//////////////////////////////////////////////////////////////////////////
// Implementation with Assert(false), this will exist only during Slice->Prefab
// development to pinpoint and replace specific calls to Slice system
class UnimplementedSliceEntityOwnershipService
: public AzFramework::SliceEntityOwnershipServiceRequestBus::Handler
{
public:
bool m_shouldAssertForLegacySlicesUsage = false;
private:
AZ::Data::AssetId CurrentlyInstantiatingSlice() override;
bool HandleRootEntityReloadedFromStream(AZ::Entity* rootEntity, bool remapIds,
AZ::SliceComponent::EntityIdToEntityIdMap* idRemapTable = nullptr) override;
AZ::SliceComponent* GetRootSlice() override;
const AZ::SliceComponent::EntityIdToEntityIdMap& GetLoadedEntityIdMap() override;
AZ::EntityId FindLoadedEntityIdMapping(const AZ::EntityId& staticId) const override;
AzFramework::SliceInstantiationTicket InstantiateSlice(const AZ::Data::Asset<AZ::Data::AssetData>& asset,
const AZ::IdUtils::Remapper<AZ::EntityId>::IdMapper& customIdMapper = nullptr,
const AZ::Data::AssetFilterCB& assetLoadFilter = nullptr) override;
AZ::SliceComponent::SliceInstanceAddress CloneSliceInstance(AZ::SliceComponent::SliceInstanceAddress sourceInstance,
AZ::SliceComponent::EntityIdToEntityIdMap& sourceToCloneEntityIdMap) override;
void CancelSliceInstantiation(const AzFramework::SliceInstantiationTicket& ticket) override;
AzFramework::SliceInstantiationTicket GenerateSliceInstantiationTicket() override;
void SetIsDynamic(bool isDynamic) override;
const AzFramework::RootSliceAsset& GetRootAsset() const override;
};
class UnimplementedSliceEditorEntityOwnershipService
: public AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Handler
{
public:
bool m_shouldAssertForLegacySlicesUsage = false;
private:
AzFramework::SliceInstantiationTicket InstantiateEditorSlice(
const AZ::Data::Asset<AZ::Data::AssetData>& sliceAsset, const AZ::Transform& worldTransform) override;
AZ::SliceComponent::SliceInstanceAddress CloneEditorSliceInstance(
AZ::SliceComponent::SliceInstanceAddress sourceInstance,
AZ::SliceComponent::EntityIdToEntityIdMap& sourceToCloneEntityIdMap) override;
AZ::SliceComponent::SliceInstanceAddress CloneSubSliceInstance(
const AZ::SliceComponent::SliceInstanceAddress& sourceSliceInstanceAddress,
const AZStd::vector<AZ::SliceComponent::SliceInstanceAddress>& sourceSubSliceInstanceAncestry,
const AZ::SliceComponent::SliceInstanceAddress& sourceSubSliceInstanceAddress,
AZ::SliceComponent::EntityIdToEntityIdMap* out_sourceToCloneEntityIdMap) override;
AZ::SliceComponent::SliceInstanceAddress PromoteEditorEntitiesIntoSlice(
const AZ::Data::Asset<AZ::SliceAsset>& sliceAsset, const AZ::SliceComponent::EntityIdToEntityIdMap& liveToAssetMap) override;
void DetachSliceEntities(const EntityIdList& entities) override;
void DetachSliceInstances(const AZ::SliceComponent::SliceInstanceAddressSet& instances) override;
void DetachSubsliceInstances(const AZ::SliceComponent::SliceInstanceEntityIdRemapList& subsliceRootList) override;
void RestoreSliceEntity(
AZ::Entity* entity, const AZ::SliceComponent::EntityRestoreInfo& info, SliceEntityRestoreType restoreType) override;
void ResetEntitiesToSliceDefaults(EntityIdList entities) override;
AZ::SliceComponent* GetEditorRootSlice() override;
};
//////////////////////////////////////////////////////////////////////////
class PrefabEditorEntityOwnershipService
: public AzFramework::PrefabEntityOwnershipService
, private PrefabEditorEntityOwnershipInterface
, private AzFramework::SliceEntityRequestBus::MultiHandler
{
public:
using EntityList = AzFramework::EntityList;
@@ -31,6 +104,11 @@ namespace AzToolsFramework
using OnEntitiesRemovedCallback = AzFramework::OnEntitiesRemovedCallback;
using ValidateEntitiesCallback = AzFramework::ValidateEntitiesCallback;
explicit PrefabEditorEntityOwnershipService(
const AzFramework::EntityContextId& entityContextId, AZ::SerializeContext* serializeContext);
~PrefabEditorEntityOwnershipService();
//! Initializes all assets/entities/components required for managing entities.
void Initialize() override;
@@ -83,11 +161,33 @@ namespace AzToolsFramework
void SetValidateEntitiesCallback(ValidateEntitiesCallback validateEntitiesCallback) override;
protected:
AZ::SliceComponent::SliceInstanceAddress GetOwningSlice() override;
private:
//////////////////////////////////////////////////////////////////////////
// PrefabSystemComponentInterface interface implementation
Prefab::InstanceOptionalReference CreatePrefab(
const AZStd::vector<AZ::Entity*>& entities, AZStd::vector<AZStd::unique_ptr<Prefab::Instance>>&& nestedPrefabInstances,
const AZStd::string& filePath, Prefab::Instance& instanceToParentUnder) override;
Prefab::InstanceOptionalReference GetRootPrefabInstance() override;
//////////////////////////////////////////////////////////////////////////
OnEntitiesAddedCallback m_entitiesAddedCallback;
OnEntitiesRemovedCallback m_entitiesRemovedCallback;
ValidateEntitiesCallback m_validateEntitiesCallback;
UnimplementedSliceEntityOwnershipService m_sliceOwnershipService;
UnimplementedSliceEditorEntityOwnershipService m_editorSliceOwnershipService;
AZStd::string m_rootPath;
AZStd::unique_ptr<Prefab::Instance> m_rootInstance;
Prefab::PrefabSystemComponentInterface* m_prefabSystemComponent;
Prefab::PrefabLoaderInterface* m_loaderInterface;
AzFramework::EntityContextId m_entityContextId;
AZ::SerializeContext m_serializeContext;
};
}
@@ -119,8 +119,8 @@ namespace AzToolsFramework
}
AngularManipulator::AngularManipulator(const AZ::Transform& worldFromLocal)
: m_worldFromLocal(worldFromLocal)
{
SetSpace(worldFromLocal);
AttachLeftMouseDownImpl();
}
@@ -147,7 +147,7 @@ namespace AzToolsFramework
// calculate initial state when mouse press first happens
m_actionInternal = CalculateManipulationDataStart(
m_fixed, TransformNormalizedScale(m_worldFromLocal), TransformNormalizedScale(m_localTransform),
m_fixed, TransformNormalizedScale(GetSpace()), TransformNormalizedScale(GetLocalTransform()),
interaction.m_mousePick.m_rayOrigin, interaction.m_mousePick.m_rayDirection,
rayIntersectionDistance);
@@ -199,7 +199,7 @@ namespace AzToolsFramework
m_manipulatorView->Draw(
GetManipulatorManagerId(), managerState,
GetManipulatorId(), {
m_worldFromLocal * m_localTransform,
ApplySpace(GetLocalTransform()), GetNonUniformScale(),
AZ::Vector3::CreateZero(), MouseOver()
},
debugDisplay, cameraState, mouseInteraction);
@@ -210,27 +210,6 @@ namespace AzToolsFramework
m_fixed.m_axis = axis;
}
void AngularManipulator::SetSpace(const AZ::Transform& worldFromLocal)
{
m_worldFromLocal = worldFromLocal;
}
void AngularManipulator::SetLocalTransform(const AZ::Transform& localTransform)
{
m_localTransform = localTransform;
}
void AngularManipulator::SetLocalPosition(const AZ::Vector3& localPosition)
{
m_localTransform.SetTranslation(localPosition);
}
void AngularManipulator::SetLocalOrientation(const AZ::Quaternion& localOrientation)
{
m_localTransform = AZ::Transform::CreateFromQuaternionAndTranslation(
localOrientation, m_localTransform.GetTranslation());
}
void AngularManipulator::SetView(AZStd::unique_ptr<ManipulatorView>&& view)
{
m_manipulatorView = AZStd::move(view);
@@ -27,6 +27,7 @@ namespace AzToolsFramework
/// in the opposite direction the rotation axis points to.
class AngularManipulator
: public BaseManipulator
, public ManipulatorSpaceWithLocalTransform
{
/// Private constructor.
explicit AngularManipulator(const AZ::Transform& worldFromLocal);
@@ -81,12 +82,6 @@ namespace AzToolsFramework
const ViewportInteraction::MouseInteraction& mouseInteraction) override;
void SetAxis(const AZ::Vector3& axis);
void SetSpace(const AZ::Transform& worldFromLocal);
void SetLocalTransform(const AZ::Transform& localTransform);
void SetLocalPosition(const AZ::Vector3& localPosition);
void SetLocalOrientation(const AZ::Quaternion& localOrientation);
AZ::Vector3 GetPosition() const { return m_localTransform.GetTranslation(); }
const AZ::Vector3& GetAxis() const { return m_fixed.m_axis; }
void SetView(AZStd::unique_ptr<ManipulatorView>&& view);
@@ -133,9 +128,6 @@ namespace AzToolsFramework
CurrentInternal m_current;
};
AZ::Transform m_localTransform = AZ::Transform::CreateIdentity(); ///< Local transform of the manipulator.
AZ::Transform m_worldFromLocal = AZ::Transform::CreateIdentity(); ///< Space the manipulator is in (identity is world space).
Fixed m_fixed;
ActionInternal m_actionInternal;
@@ -155,4 +147,4 @@ namespace AzToolsFramework
const AZ::Vector3& rayOrigin, const AZ::Vector3& rayDirection,
ViewportInteraction::KeyboardModifiers keyboardModifiers);
};
} // namespace AzToolsFramework
} // namespace AzToolsFramework
@@ -26,8 +26,8 @@ namespace AzToolsFramework
AZ_CLASS_ALLOCATOR_IMPL(BaseManipulator, AZ::SystemAllocator, 0)
static bool EntityIdAndEntityComponentIdComparison(
const AZ::EntityId entityId, const AZ::EntityComponentIdPair& entityComponentId)
static bool EntityIdAndEntityComponentIdComparison(
const AZ::EntityId entityId, const AZ::EntityComponentIdPair& entityComponentId)
{
return entityId == entityComponentId.GetEntityId();
}
@@ -347,64 +347,64 @@ namespace AzToolsFramework
void Manipulators::Register(const ManipulatorManagerId manipulatorManagerId)
{
ProcessManipulators([manipulatorManagerId](BaseManipulator* manipulator)
{
manipulator->Register(manipulatorManagerId);
});
{
manipulator->Register(manipulatorManagerId);
});
}
void Manipulators::Unregister()
{
ProcessManipulators([](BaseManipulator* manipulator)
{
if (manipulator->Registered())
{
manipulator->Unregister();
}
});
if (manipulator->Registered())
{
manipulator->Unregister();
}
});
}
void Manipulators::SetBoundsDirty()
{
ProcessManipulators([](BaseManipulator* manipulator)
{
manipulator->SetBoundsDirty();
});
{
manipulator->SetBoundsDirty();
});
}
void Manipulators::AddEntityComponentIdPair(const AZ::EntityComponentIdPair& entityComponentIdPair)
{
ProcessManipulators([&entityComponentIdPair](BaseManipulator* manipulator)
{
manipulator->AddEntityComponentIdPair(entityComponentIdPair);
});
{
manipulator->AddEntityComponentIdPair(entityComponentIdPair);
});
}
void Manipulators::RemoveEntityComponentIdPair(const AZ::EntityComponentIdPair& entityComponentIdPair)
{
ProcessManipulators([&entityComponentIdPair](BaseManipulator* manipulator)
{
manipulator->RemoveEntityComponentIdPair(entityComponentIdPair);
});
{
manipulator->RemoveEntityComponentIdPair(entityComponentIdPair);
});
}
void Manipulators::RemoveEntityId(const AZ::EntityId entityId)
{
ProcessManipulators([entityId](BaseManipulator* manipulator)
{
manipulator->RemoveEntityId(entityId);
});
{
manipulator->RemoveEntityId(entityId);
});
}
bool Manipulators::PerformingAction()
{
bool performingAction = false;
ProcessManipulators([&performingAction](BaseManipulator* manipulator)
{
if (manipulator->PerformingAction())
{
performingAction = true;
}
});
if (manipulator->PerformingAction())
{
performingAction = true;
}
});
return performingAction;
}
@@ -413,16 +413,56 @@ namespace AzToolsFramework
{
bool registered = false;
ProcessManipulators([&registered](BaseManipulator* manipulator)
{
if (manipulator->Registered())
{
registered = true;
}
});
if (manipulator->Registered())
{
registered = true;
}
});
return registered;
}
const AZ::Transform& Manipulators::GetLocalTransform() const
{
return m_manipulatorSpaceWithLocalTransform.GetLocalTransform();
}
const AZ::Transform& Manipulators::GetSpace() const
{
return m_manipulatorSpaceWithLocalTransform.GetSpace();
}
void Manipulators::SetSpace(const AZ::Transform& worldFromLocal)
{
m_manipulatorSpaceWithLocalTransform.SetSpace(worldFromLocal);
SetSpaceImpl(worldFromLocal);
}
void Manipulators::SetLocalTransform(const AZ::Transform& localTransform)
{
m_manipulatorSpaceWithLocalTransform.SetLocalTransform(localTransform);
SetLocalTransformImpl(localTransform);
}
void Manipulators::SetLocalPosition(const AZ::Vector3& localPosition)
{
m_manipulatorSpaceWithLocalTransform.SetLocalPosition(localPosition);
SetLocalPositionImpl(localPosition);
}
void Manipulators::SetLocalOrientation(const AZ::Quaternion& localOrientation)
{
m_manipulatorSpaceWithLocalTransform.SetLocalOrientation(localOrientation);
SetLocalOrientationImpl(localOrientation);
}
void Manipulators::SetNonUniformScale(const AZ::Vector3& nonUniformScale)
{
m_manipulatorSpaceWithLocalTransform.SetNonUniformScale(nonUniformScale);
SetNonUniformScaleImpl(nonUniformScale);
}
namespace Internal
{
bool CalculateRayPlaneIntersectingPoint(const AZ::Vector3& rayOrigin, const AZ::Vector3& rayDirection,
@@ -22,6 +22,7 @@
#include <AzCore/std/containers/set.h>
#include <AzCore/std/smart_ptr/enable_shared_from_this.h>
#include <AzToolsFramework/Manipulators/ManipulatorBus.h>
#include "ManipulatorSpace.h"
namespace AzFramework
{
@@ -248,24 +249,33 @@ namespace AzToolsFramework
bool PerformingAction();
bool Registered();
const AZ::Transform& GetLocalTransform() const { return m_localTransform; }
const AZ::Transform& GetSpace() const { return m_space; }
virtual void SetSpace(const AZ::Transform& worldFromLocal) = 0;
virtual void SetLocalTransform(const AZ::Transform& localTransform) = 0;
virtual void SetLocalPosition(const AZ::Vector3& localPosition) = 0;
virtual void SetLocalOrientation(const AZ::Quaternion& localOrientation) = 0;
/// Refresh the Manipulator and/or View based on the current view position.
virtual void RefreshView(const AZ::Vector3& /*worldViewPosition*/) {}
const AZ::Transform& GetLocalTransform() const;
const AZ::Transform& GetSpace() const;
const AZ::Vector3& GetNonUniformScale() const;
void SetSpace(const AZ::Transform& worldFromLocal);
void SetLocalTransform(const AZ::Transform& localTransform);
void SetLocalPosition(const AZ::Vector3& localPosition);
void SetLocalOrientation(const AZ::Quaternion& localOrientation);
void SetNonUniformScale(const AZ::Vector3& nonUniformScale);
protected:
/// Common processing for base manipulator type - Implement for all
/// individual manipulators used in an aggregate manipulator.
virtual void ProcessManipulators(const AZStd::function<void(BaseManipulator*)>&) = 0;
AZ::Transform m_localTransform = AZ::Transform::CreateIdentity(); ///< Local space transform of Manipulators.
AZ::Transform m_space = AZ::Transform::CreateIdentity(); ///< Space the Manipulators are in.
///@{
/// Allows implementers to perform additional logic when updating the location of the manipulator group.
virtual void SetSpaceImpl([[maybe_unused]] const AZ::Transform& worldFromLocal) {}
virtual void SetLocalTransformImpl([[maybe_unused]] const AZ::Transform& localTransform) {}
virtual void SetLocalPositionImpl([[maybe_unused]] const AZ::Vector3& localPosition) {}
virtual void SetLocalOrientationImpl([[maybe_unused]] const AZ::Quaternion& localOrientation) {}
virtual void SetNonUniformScaleImpl([[maybe_unused]] const AZ::Vector3& nonUniformScale) {}
///@}
ManipulatorSpaceWithLocalTransform m_manipulatorSpaceWithLocalTransform; ///< The space and local transform for the manipulators.
};
namespace Internal
@@ -176,7 +176,7 @@ namespace AzToolsFramework
updated, fixedVertices, &AZ::FixedVerticesRequestBus<Vertex>::Handler::UpdateVertex,
vertex.m_index, vertexPosition);
m_selectionManipulators[vertex.m_index]->SetPosition(AZ::AdaptVertexOut(vertexPosition));
m_selectionManipulators[vertex.m_index]->SetLocalPosition(AZ::AdaptVertexOut(vertexPosition));
});
m_translationManipulator->m_manipulator.SetLocalPosition(
@@ -241,7 +241,8 @@ namespace AzToolsFramework
// create a new translation manipulator bound for the selected vertexIndex
m_translationManipulator = AZStd::make_shared<IndexedTranslationManipulator<Vertex>>(
Dimensions(), vertexIndex, vertex, WorldFromLocalWithUniformScale(entityComponentIdPair.GetEntityId()));
Dimensions(), vertexIndex, vertex, WorldFromLocalWithUniformScale(entityComponentIdPair.GetEntityId()),
GetNonUniformScale(entityComponentIdPair.GetEntityId()));
// setup how the manipulator should look
m_manipulatorConfiguratorFn(&m_translationManipulator->m_manipulator);
@@ -524,12 +525,13 @@ namespace AzToolsFramework
vertexIndex, vertex);
m_selectionManipulators.push_back(SelectionManipulator::MakeShared(
WorldFromLocalWithUniformScale(GetEntityId())));
WorldFromLocalWithUniformScale(GetEntityId()),
GetNonUniformScale(GetEntityId())));
const auto& selectionManipulator = m_selectionManipulators.back();
selectionManipulator->Register(managerId);
selectionManipulator->AddEntityComponentIdPair(entityComponentIdPair);
selectionManipulator->SetPosition(AdaptVertexOut(vertex));
selectionManipulator->SetLocalPosition(AdaptVertexOut(vertex));
SetupSelectionManipulator(selectionManipulator, entityComponentIdPair, managerId, vertexIndex);
}
@@ -975,7 +977,7 @@ namespace AzToolsFramework
if (found)
{
m_selectionManipulators[manipulatorIndex]->SetPosition(AZ::AdaptVertexOut(vertex));
m_selectionManipulators[manipulatorIndex]->SetLocalPosition(AZ::AdaptVertexOut(vertex));
}
}
@@ -990,23 +992,26 @@ namespace AzToolsFramework
}
template<typename Vertex>
void EditorVertexSelectionBase<Vertex>::RefreshSpace(const AZ::Transform& worldFromLocal)
void EditorVertexSelectionBase<Vertex>::RefreshSpace(const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
for (auto& manipulator : m_selectionManipulators)
{
manipulator->SetSpace(TransformUniformScale(worldFromLocal));
manipulator->SetNonUniformScale(nonUniformScale);
}
if (m_translationManipulator)
{
m_translationManipulator->m_manipulator.SetSpace(TransformUniformScale(worldFromLocal));
m_translationManipulator->m_manipulator.SetNonUniformScale(nonUniformScale);
}
if (m_hoverSelection)
{
m_hoverSelection->SetSpace(TransformUniformScale(worldFromLocal));
m_hoverSelection->SetNonUniformScale(nonUniformScale);
m_hoverSelection->Refresh();
}
@@ -122,8 +122,8 @@ namespace AzToolsFramework
/// Update the translation manipulator to be correctly positioned based
/// on the current selection (recenter it).
void RefreshTranslationManipulator();
/// Update manipulators based on changes to the entities transform.
void RefreshSpace(const AZ::Transform& worldFromLocal);
/// Update manipulators based on changes to the entity's transform and non-uniform scale.
void RefreshSpace(const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale = AZ::Vector3::CreateOne());
/// Set bounds dirty (need recalculating) for all owned manipulators (selection, translation, hover).
void SetBoundsDirty();
@@ -30,6 +30,7 @@ namespace AzToolsFramework
virtual void SetBoundsDirty() = 0;
virtual void Refresh() = 0;
virtual void SetSpace(const AZ::Transform& worldFromLocal) = 0;
virtual void SetNonUniformScale(const AZ::Vector3& nonUniformScale) = 0;
};
/// NullHoverSelection is used when vertices cannot be inserted. This serves as a no-op
@@ -47,5 +48,6 @@ namespace AzToolsFramework
void SetBoundsDirty() override {}
void Refresh() override {}
void SetSpace(const AZ::Transform& /*worldFromLocal*/) override {}
void SetNonUniformScale([[maybe_unused]] const AZ::Vector3& nonUniformScale) override {}
};
} // namespace AzToolsFramework
} // namespace AzToolsFramework
@@ -75,6 +75,7 @@ namespace AzToolsFramework
lineSegmentManipulator->Register(managerId);
lineSegmentManipulator->AddEntityComponentIdPair(entityComponentIdPair);
lineSegmentManipulator->SetSpace(WorldFromLocalWithUniformScale(entityComponentIdPair.GetEntityId()));
lineSegmentManipulator->SetNonUniformScale(GetNonUniformScale(entityComponentIdPair.GetEntityId()));
UpdateLineSegmentPosition<Vertex>(vertIndex, entityComponentIdPair.GetEntityId(), *lineSegmentManipulator);
@@ -174,6 +175,15 @@ namespace AzToolsFramework
}
}
template<typename Vertex>
void LineSegmentHoverSelection<Vertex>::SetNonUniformScale(const AZ::Vector3& nonUniformScale)
{
for (auto& lineSegmentManipulator : m_lineSegmentManipulators)
{
lineSegmentManipulator->SetNonUniformScale(nonUniformScale);
}
}
template class LineSegmentHoverSelection<AZ::Vector2>;
template class LineSegmentHoverSelection<AZ::Vector3>;
} // namespace AzToolsFramework
@@ -45,9 +45,10 @@ namespace AzToolsFramework
void SetBoundsDirty() override;
void Refresh() override;
void SetSpace(const AZ::Transform& worldFromLocal) override;
void SetNonUniformScale(const AZ::Vector3& nonUniformScale) override;
private:
AZ::EntityId m_entityId;
AZStd::vector<AZStd::shared_ptr<LineSegmentSelectionManipulator>> m_lineSegmentManipulators; ///< Manipulators for each line.
};
} // namespace AzToolsFramework
} // namespace AzToolsFramework
@@ -20,18 +20,18 @@
namespace AzToolsFramework
{
LineSegmentSelectionManipulator::Action CalculateManipulationDataAction(
const AZ::Transform& worldFromLocal, const AZ::Vector3& rayOrigin, const AZ::Vector3& rayDirection,
const float rayLength, const AZ::Vector3& localStart, const AZ::Vector3& localEnd)
const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale, const AZ::Vector3& rayOrigin,
const AZ::Vector3& rayDirection, const float rayLength, const AZ::Vector3& localStart, const AZ::Vector3& localEnd)
{
AZ::Vector3 worldClosestPositionRay, worldClosestPositionLineSegment;
float rayProportion, lineSegmentProportion;
AZ::Intersect::ClosestSegmentSegment(
rayOrigin, rayOrigin + rayDirection * rayLength,
worldFromLocal.TransformPoint(localStart), worldFromLocal.TransformPoint(localEnd),
worldFromLocal.TransformPoint(nonUniformScale * localStart), worldFromLocal.TransformPoint(nonUniformScale * localEnd),
rayProportion, lineSegmentProportion, worldClosestPositionRay, worldClosestPositionLineSegment);
AZ::Transform worldFromLocalNormalized = worldFromLocal;
const AZ::Vector3 scale = worldFromLocalNormalized.ExtractScale();
const AZ::Vector3 scale = worldFromLocalNormalized.ExtractScale() * nonUniformScale;
const AZ::Transform localFromWorldNormalized = worldFromLocalNormalized.GetInverse();
return { (localFromWorldNormalized.TransformPoint(worldClosestPositionLineSegment)) / scale };
@@ -75,7 +75,7 @@ namespace AzToolsFramework
&ViewportInteraction::ViewportInteractionRequestBus::Events::GetCameraState);
m_onLeftMouseDownCallback(CalculateManipulationDataAction(
TransformUniformScale(m_worldFromLocal), interaction.m_mousePick.m_rayOrigin,
TransformUniformScale(GetSpace()), GetNonUniformScale(), interaction.m_mousePick.m_rayOrigin,
interaction.m_mousePick.m_rayDirection, cameraState.m_farClip, m_localStart, m_localEnd));
}
}
@@ -90,8 +90,8 @@ namespace AzToolsFramework
&ViewportInteraction::ViewportInteractionRequestBus::Events::GetCameraState);
m_onLeftMouseUpCallback(CalculateManipulationDataAction(
TransformUniformScale(m_worldFromLocal), interaction.m_mousePick.m_rayOrigin, interaction.m_mousePick.m_rayDirection,
cameraState.m_farClip, m_localStart, m_localEnd));
TransformUniformScale(GetSpace()), GetNonUniformScale(), interaction.m_mousePick.m_rayOrigin,
interaction.m_mousePick.m_rayDirection, cameraState.m_farClip, m_localStart, m_localEnd));
}
}
@@ -114,7 +114,7 @@ namespace AzToolsFramework
m_manipulatorView->Draw(
GetManipulatorManagerId(), managerState,
GetManipulatorId(), {
TransformUniformScale(m_worldFromLocal),
TransformUniformScale(GetSpace()), GetNonUniformScale(),
m_localStart, MouseOver()
},
debugDisplay, cameraState, mouseInteraction);
@@ -23,6 +23,7 @@ namespace AzToolsFramework
/// A manipulator to expose where on a line a user is moving their mouse.
class LineSegmentSelectionManipulator
: public BaseManipulator
, public ManipulatorSpace
{
/// Private constructor.
LineSegmentSelectionManipulator();
@@ -56,7 +57,6 @@ namespace AzToolsFramework
const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction) override;
void SetSpace(const AZ::Transform& worldFromLocal) { m_worldFromLocal = worldFromLocal; }
void SetStart(const AZ::Vector3& startLocal) { m_localStart = startLocal; }
void SetEnd(const AZ::Vector3& endLocal) { m_localEnd = endLocal; }
const AZ::Vector3& GetStart() const { return m_localStart; }
@@ -73,7 +73,6 @@ namespace AzToolsFramework
void InvalidateImpl() override;
void SetBoundsDirtyImpl() override;
AZ::Transform m_worldFromLocal = AZ::Transform::CreateIdentity(); ///< Space the manipulator is in (identity is world space).
AZ::Vector3 m_localStart = AZ::Vector3::CreateZero();
AZ::Vector3 m_localEnd = AZ::Vector3::CreateZero();
@@ -86,6 +85,6 @@ namespace AzToolsFramework
};
LineSegmentSelectionManipulator::Action CalculateManipulationDataAction(
const AZ::Transform& worldFromLocal, const AZ::Vector3& rayOrigin, const AZ::Vector3& rayDirection,
float rayLength, const AZ::Vector3& localStart, const AZ::Vector3& localEnd);
const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale, const AZ::Vector3& rayOrigin,
const AZ::Vector3& rayDirection, float rayLength, const AZ::Vector3& localStart, const AZ::Vector3& localEnd);
} // namespace AzToolsFramework
@@ -22,13 +22,13 @@
namespace AzToolsFramework
{
LinearManipulator::Starter CalculateLinearManipulationDataStart(
const LinearManipulator::Fixed& fixed, const AZ::Transform& worldFromLocal, const AZ::Transform& localTransform,
const GridSnapAction& gridSnapAction, const ViewportInteraction::MouseInteraction& interaction,
const LinearManipulator::Fixed& fixed, const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale,
const AZ::Transform& localTransform, const GridSnapAction& gridSnapAction, const ViewportInteraction::MouseInteraction& interaction,
const float intersectionDistance, const AzFramework::CameraState& cameraState)
{
const ManipulatorInteraction manipulatorInteraction =
BuildManipulatorInteraction(
worldFromLocal, interaction.m_mousePick.m_rayOrigin, interaction.m_mousePick.m_rayDirection);
worldFromLocal, nonUniformScale, interaction.m_mousePick.m_rayOrigin, interaction.m_mousePick.m_rayDirection);
const AZ::Vector3 axis = TransformDirectionNoScaling(localTransform, fixed.m_axis);
const AZ::Vector3 rayCrossAxis = manipulatorInteraction.m_localRayDirection.Cross(axis);
@@ -37,7 +37,9 @@ namespace AzToolsFramework
LinearManipulator::StartTransition startTransition;
// initialize m_localHitPosition to handle edge case where CalculateRayPlaneIntersectingPoint
// fails because ray is parallel to the plane
start.m_localHitPosition = localTransform.GetTranslation();
// localTransform is in the reference frame of the object being manipulated (i.e. the world rotation, translation and uniform scale
// from the world transform have been extracted), but non-uniform scale has to be accounted for separately
start.m_localHitPosition = nonUniformScale * localTransform.GetTranslation();
startTransition.m_localNormal = rayCrossAxis.Cross(axis).GetNormalizedSafe();
// initial intersect point
@@ -84,12 +86,12 @@ namespace AzToolsFramework
LinearManipulator::Action CalculateLinearManipulationDataAction(
const LinearManipulator::Fixed& fixed, const LinearManipulator::Starter& starter,
const AZ::Transform& worldFromLocal, const AZ::Transform& localTransform, const GridSnapAction& gridSnapAction,
const ViewportInteraction::MouseInteraction& interaction)
const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale, const AZ::Transform& localTransform,
const GridSnapAction& gridSnapAction, const ViewportInteraction::MouseInteraction& interaction)
{
const ManipulatorInteraction manipulatorInteraction =
BuildManipulatorInteraction(
worldFromLocal, interaction.m_mousePick.m_rayOrigin, interaction.m_mousePick.m_rayDirection);
worldFromLocal, nonUniformScale, interaction.m_mousePick.m_rayOrigin, interaction.m_mousePick.m_rayDirection);
const auto& [startTransition, start] = starter;
@@ -106,10 +108,13 @@ namespace AzToolsFramework
GetCameraState(interaction.m_interactionId.m_viewportId));
const AZ::Vector3 axis = TransformDirectionNoScaling(localTransform, fixed.m_axis);
const AZ::Vector3 hitDelta = (localHitPosition - start.m_localHitPosition);
// The local positions have been transformed to the reference frame of the object being manipulated. But they appear in the world
// with non-uniform scale applied, and the object being manipulated will want to work with unscaled deltas, so we need to divide by
// the non-uniform scale here.
const AZ::Vector3 hitDelta = (localHitPosition - start.m_localHitPosition) / nonUniformScale;
const AZ::Vector3 unsnappedOffset = axis * axis.Dot(hitDelta);
const float scaleRecip = manipulatorInteraction.m_scaleReciprocal;
const float scaleRecip = manipulatorInteraction.m_scaleReciprocal * axis.Dot(manipulatorInteraction.m_nonUniformScaleReciprocal);
const float gridSize = gridSnapAction.m_gridSnapParams.m_gridSize;
const bool snapping = gridSnapAction.m_gridSnapParams.m_gridSnap;
@@ -123,7 +128,7 @@ namespace AzToolsFramework
action.m_viewportId = interaction.m_interactionId.m_viewportId;
const AZ::Quaternion localRotation = QuaternionFromTransformNoScaling(localTransform);
const AZ::Vector3 scaledUnsnappedOffset = unsnappedOffset * startTransition.m_screenToWorldScale;
const AZ::Vector3 scaledUnsnappedOffset = unsnappedOffset * startTransition.m_screenToWorldScale * NonUniformScaleReciprocal(nonUniformScale);
// how much to adjust the scale based on movement
const AZ::Quaternion invLocalRotation = localRotation.GetInverseFull();
action.m_current.m_localScaleOffset = snapping
@@ -142,8 +147,8 @@ namespace AzToolsFramework
}
LinearManipulator::LinearManipulator(const AZ::Transform& worldFromLocal)
: m_worldFromLocal(worldFromLocal)
{
SetSpace(worldFromLocal);
AttachLeftMouseDownImpl();
}
@@ -165,19 +170,19 @@ namespace AzToolsFramework
void LinearManipulator::OnLeftMouseDownImpl(
const ViewportInteraction::MouseInteraction& interaction, const float rayIntersectionDistance)
{
const AZ::Transform worldFromLocalUniformScale = TransformUniformScale(m_worldFromLocal);
const AZ::Transform worldFromLocalUniformScale = TransformUniformScale(GetSpace());
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
// note: m_localTransform must not be made uniform as it may contain a local scale we want to snap
m_starter = CalculateLinearManipulationDataStart(
m_fixed, worldFromLocalUniformScale, m_localTransform,
m_fixed, worldFromLocalUniformScale, GetNonUniformScale(), GetLocalTransform(),
GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt()), interaction, rayIntersectionDistance,
GetCameraState(interaction.m_interactionId.m_viewportId));
if (m_onLeftMouseDownCallback)
{
m_onLeftMouseDownCallback(CalculateLinearManipulationDataAction(
m_fixed, m_starter, worldFromLocalUniformScale, m_localTransform,
m_fixed, m_starter, worldFromLocalUniformScale, GetNonUniformScale(), GetLocalTransform(),
GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt()), interaction));
}
}
@@ -190,7 +195,7 @@ namespace AzToolsFramework
// note: m_localTransform must not be made uniform as it may contain a local scale we want to snap
m_onMouseMoveCallback(CalculateLinearManipulationDataAction(
m_fixed, m_starter, TransformUniformScale(m_worldFromLocal), m_localTransform,
m_fixed, m_starter, TransformUniformScale(GetSpace()), GetNonUniformScale(), GetLocalTransform(),
GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt()), interaction));
}
}
@@ -203,7 +208,7 @@ namespace AzToolsFramework
// note: m_localTransform must not be made uniform as it may contain a local scale we want to snap
m_onLeftMouseUpCallback(CalculateLinearManipulationDataAction(
m_fixed, m_starter, TransformUniformScale(m_worldFromLocal), m_localTransform,
m_fixed, m_starter, TransformUniformScale(GetSpace()), GetNonUniformScale(), GetLocalTransform(),
GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt()), interaction));
}
}
@@ -216,8 +221,8 @@ namespace AzToolsFramework
{
const AZ::Transform localTransform = m_useVisualsOverride
? AZ::Transform::CreateFromQuaternionAndTranslation(
m_visualOrientationOverride, m_localTransform.GetTranslation())
: m_localTransform;
m_visualOrientationOverride, GetLocalPosition())
: GetLocalTransform();
if (cl_manipulatorDrawDebug)
{
@@ -227,17 +232,19 @@ namespace AzToolsFramework
GridSnapSettings(mouseInteraction.m_interactionId.m_viewportId);
const auto action = CalculateLinearManipulationDataAction(
m_fixed, m_starter, TransformUniformScale(m_worldFromLocal), m_localTransform,
m_fixed, m_starter, TransformUniformScale(GetSpace()), GetNonUniformScale(), GetLocalTransform(),
GridSnapAction(gridSnapParams, mouseInteraction.m_keyboardModifiers.Alt()), mouseInteraction);
// display the exact hit (ray intersection) of the mouse pick on the manipulator
DrawTransformAxes(
debugDisplay, TransformUniformScale(m_worldFromLocal) *
debugDisplay, TransformUniformScale(GetSpace()) *
AZ::Transform::CreateTranslation(
action.m_start.m_localHitPosition + action.m_current.m_localPositionOffset));
action.m_start.m_localHitPosition + GetNonUniformScale() * action.m_current.m_localPositionOffset));
}
const AZ::Transform combined = TransformUniformScale(m_worldFromLocal) * localTransform;
AZ::Transform combined = GetLocalTransform();
combined.SetTranslation(GetNonUniformScale() * combined.GetTranslation());
combined = GetSpace() * combined;
DrawTransformAxes(debugDisplay, combined);
DrawAxis(
@@ -246,10 +253,12 @@ namespace AzToolsFramework
for (auto& view : m_manipulatorViews)
{
auto nonUniformScale = GetNonUniformScale();
view->Draw(
GetManipulatorManagerId(), managerState,
GetManipulatorId(), {
m_worldFromLocal * localTransform,
ApplySpace(localTransform), GetNonUniformScale(),
AZ::Vector3::CreateZero(), MouseOver()
},
debugDisplay, cameraState, mouseInteraction);
@@ -261,27 +270,6 @@ namespace AzToolsFramework
m_fixed.m_axis = axis;
}
void LinearManipulator::SetSpace(const AZ::Transform& worldFromLocal)
{
m_worldFromLocal = worldFromLocal;
}
void LinearManipulator::SetLocalTransform(const AZ::Transform& localTransform)
{
m_localTransform = localTransform;
}
void LinearManipulator::SetLocalPosition(const AZ::Vector3& localPosition)
{
m_localTransform.SetTranslation(localPosition);
}
void LinearManipulator::SetLocalOrientation(const AZ::Quaternion& localOrientation)
{
m_localTransform = AZ::Transform::CreateFromQuaternionAndTranslation(
localOrientation, m_localTransform.GetTranslation());
}
void LinearManipulator::InvalidateImpl()
{
for (auto& view : m_manipulatorViews)
@@ -26,6 +26,7 @@ namespace AzToolsFramework
/// in one dimension on an axis defined in 3D space.
class LinearManipulator
: public BaseManipulator
, public ManipulatorSpaceWithLocalTransform
{
/// Private constructor.
explicit LinearManipulator(const AZ::Transform& worldFromLocal);
@@ -116,12 +117,6 @@ namespace AzToolsFramework
const ViewportInteraction::MouseInteraction& mouseInteraction) override;
void SetAxis(const AZ::Vector3& axis);
void SetSpace(const AZ::Transform& worldFromLocal);
void SetLocalTransform(const AZ::Transform& localTransform);
void SetLocalPosition(const AZ::Vector3& localPosition);
void SetLocalOrientation(const AZ::Quaternion& localOrientation);
AZ::Vector3 GetPosition() const { return m_localTransform.GetTranslation(); }
const AZ::Vector3& GetAxis() const { return m_fixed.m_axis; }
template<typename Views>
@@ -151,9 +146,6 @@ namespace AzToolsFramework
void InvalidateImpl() override;
void SetBoundsDirtyImpl() override;
AZ::Transform m_localTransform = AZ::Transform::CreateIdentity(); ///< Local transform of the manipulator.
AZ::Transform m_worldFromLocal = AZ::Transform::CreateIdentity(); ///< Space the manipulator is in (identity is world space).
bool m_useVisualsOverride = false; // Set this to true to use the Visual Quaternion Override (decoupled from logical axis).
AZ::Quaternion m_visualOrientationOverride = AZ::Quaternion::CreateIdentity(); // Quaternion to use only for visuals.
@@ -168,12 +160,12 @@ namespace AzToolsFramework
};
LinearManipulator::Starter CalculateLinearManipulationDataStart(
const LinearManipulator::Fixed& fixed, const AZ::Transform& worldFromLocal, const AZ::Transform& localTransform,
const GridSnapAction& gridSnapAction, const ViewportInteraction::MouseInteraction& interaction,
const LinearManipulator::Fixed& fixed, const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale,
const AZ::Transform& localTransform, const GridSnapAction& gridSnapAction, const ViewportInteraction::MouseInteraction& interaction,
float intersectionDistance, const AzFramework::CameraState& cameraState);
LinearManipulator::Action CalculateLinearManipulationDataAction(
const LinearManipulator::Fixed& fixed, const LinearManipulator::Starter& starter,
const AZ::Transform& worldFromLocal, const AZ::Transform& localTransform, const GridSnapAction& gridSnapAction,
const ViewportInteraction::MouseInteraction& interaction);
const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale, const AZ::Transform& localTransform,
const GridSnapAction& gridSnapAction, const ViewportInteraction::MouseInteraction& interaction);
} // namespace AzToolsFramework
@@ -49,14 +49,16 @@ namespace AzToolsFramework
}
ManipulatorInteraction BuildManipulatorInteraction(
const AZ::Transform& worldFromLocal, const AZ::Vector3& worldRayOrigin, const AZ::Vector3& worldRayDirection)
const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale,
const AZ::Vector3& worldRayOrigin, const AZ::Vector3& worldRayDirection)
{
const AZ::Transform worldFromLocalUniform = AzToolsFramework::TransformUniformScale(worldFromLocal);
const AZ::Transform localFromWorldUniform = worldFromLocalUniform.GetInverse();
return {localFromWorldUniform.TransformPoint(worldRayOrigin),
TransformDirectionNoScaling(localFromWorldUniform, worldRayDirection),
ScaleReciprocal(worldFromLocalUniform)};
ScaleReciprocal(worldFromLocalUniform),
NonUniformScaleReciprocal(nonUniformScale)};
}
AZ::Vector3 CalculateSnappedOffset(
@@ -47,11 +47,14 @@ namespace AzToolsFramework
AZ::Vector3 m_localRayDirection; ///< The ray direction in the reference from of the manipulator.
float m_scaleReciprocal; ///< The scale reciprocal (1.0 / scale) of the transform used to move the
///< ray from world space to local space.
AZ::Vector3 m_nonUniformScaleReciprocal; ///< Handles inverting any non-uniform scale which was applied
///< separately from the transform.
};
/// Build a ManipulatorInteraction structure from the incoming viewport interaction.
ManipulatorInteraction BuildManipulatorInteraction(
const AZ::Transform& worldFromLocal, const AZ::Vector3& worldRayOrigin, const AZ::Vector3& worldRayDirection);
const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale,
const AZ::Vector3& worldRayOrigin, const AZ::Vector3& worldRayDirection);
/// Calculate the offset along an axis to adjust a position
/// to stay snapped to a given grid size.
@@ -112,4 +115,16 @@ namespace AzToolsFramework
{
return Round3(transform.GetScale().GetReciprocal().GetMinElement());
}
/// Find the reciprocal of the non-uniform scale.
/// Each element will be rounded to three significant digits to eliminate noise
/// when dealing with values far from the origin.
inline AZ::Vector3 NonUniformScaleReciprocal(const AZ::Vector3& nonUniformScale)
{
AZ::Vector3 scaleReciprocal = nonUniformScale.GetReciprocal();
return AZ::Vector3(
Round3(scaleReciprocal.GetX()),
Round3(scaleReciprocal.GetY()),
Round3(scaleReciprocal.GetZ()));
}
} // namespace AzToolsFramework
@@ -0,0 +1,85 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "ManipulatorSpace.h"
namespace AzToolsFramework
{
const AZ::Transform& ManipulatorSpace::GetSpace() const
{
return m_space;
}
void ManipulatorSpace::SetSpace(const AZ::Transform& space)
{
m_space = space;
}
const AZ::Vector3& ManipulatorSpace::GetNonUniformScale() const
{
return m_nonUniformScale;
}
void ManipulatorSpace::SetNonUniformScale(const AZ::Vector3& nonUniformScale)
{
m_nonUniformScale = nonUniformScale;
}
AZ::Transform ManipulatorSpace::ApplySpace(const AZ::Transform& localTransform) const
{
AZ::Transform result;
result.SetRotation(m_space.GetRotation() * localTransform.GetRotation());
result.SetTranslation(m_space.TransformPoint(m_nonUniformScale * localTransform.GetTranslation()));
result.SetScale(m_space.GetScale() * localTransform.GetScale());
return result;
}
const AZ::Vector3& ManipulatorSpaceWithLocalPosition::GetLocalPosition() const
{
return m_localPosition;
}
void ManipulatorSpaceWithLocalPosition::SetLocalPosition(const AZ::Vector3& localPosition)
{
m_localPosition = localPosition;
}
const AZ::Vector3& ManipulatorSpaceWithLocalTransform::GetLocalPosition() const
{
return m_localTransform.GetTranslation();
}
void ManipulatorSpaceWithLocalTransform::SetLocalPosition(const AZ::Vector3& localPosition)
{
m_localTransform.SetTranslation(localPosition);
}
const AZ::Transform& ManipulatorSpaceWithLocalTransform::GetLocalTransform() const
{
return m_localTransform;
}
const AZ::Quaternion& ManipulatorSpaceWithLocalTransform::GetLocalOrientation() const
{
return m_localTransform.GetRotation();
}
void ManipulatorSpaceWithLocalTransform::SetLocalTransform(const AZ::Transform& localTransform)
{
m_localTransform = localTransform;
}
void ManipulatorSpaceWithLocalTransform::SetLocalOrientation(const AZ::Quaternion& localOrientation)
{
m_localTransform.SetRotation(localOrientation);
}
} // namespace AzToolsFramework
@@ -0,0 +1,77 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Math/Transform.h>
namespace AZ
{
class Quaternion;
} // namespace AZ
namespace AzToolsFramework
{
/// Handles location for manipulators which have a global space but no local transformation.
class ManipulatorSpace
{
public:
AZ_TYPE_INFO(ManipulatorSpace, "{5D4B8974-8F98-4268-8D6B-3214A77C6382}")
AZ_CLASS_ALLOCATOR(ManipulatorSpace, AZ::SystemAllocator, 0)
const AZ::Transform& GetSpace() const;
void SetSpace(const AZ::Transform& space);
const AZ::Vector3& GetNonUniformScale() const;
void SetNonUniformScale(const AZ::Vector3& nonUniformScale);
/// Calculates a transform combining the space and local transform, taking non-uniform scale into account.
AZ::Transform ApplySpace(const AZ::Transform& localTransform) const;
private:
AZ::Transform m_space = AZ::Transform::CreateIdentity(); ///< Space the manipulator is in.
AZ::Vector3 m_nonUniformScale = AZ::Vector3::CreateOne(); ///< Handles non-uniform scale for the space the manipulator is in.
};
/// Handles location for manipulators which have a global space and a local position, but no local rotation.
class ManipulatorSpaceWithLocalPosition
: public ManipulatorSpace
{
public:
AZ_TYPE_INFO(ManipulatorSpaceWithLocalPosition, "{47BE15AF-60A8-436B-8F3F-7DDFB97220E6}")
AZ_CLASS_ALLOCATOR(ManipulatorSpaceWithLocalPosition, AZ::SystemAllocator, 0)
const AZ::Vector3& GetLocalPosition() const;
void SetLocalPosition(const AZ::Vector3& localPosition);
private:
AZ::Vector3 m_localPosition = AZ::Vector3::CreateZero(); ///< Position in local space.
};
/// Handles location for manipulators which have a global space and a local transform (position and rotation).
class ManipulatorSpaceWithLocalTransform
: public ManipulatorSpace
{
public:
AZ_TYPE_INFO(ManipulatorSpaceWithLocalTransform, "{6D100797-1DD8-45B0-A21C-8893B770C0BC}")
AZ_CLASS_ALLOCATOR(ManipulatorSpaceWithLocalTransform, AZ::SystemAllocator, 0)
const AZ::Vector3& GetLocalPosition() const;
void SetLocalPosition(const AZ::Vector3& localPosition);
const AZ::Transform& GetLocalTransform() const;
const AZ::Quaternion& GetLocalOrientation() const;
void SetLocalTransform(const AZ::Transform& localTransform);
void SetLocalOrientation(const AZ::Quaternion& localOrientation);
private:
AZ::Transform m_localTransform = AZ::Transform::CreateIdentity(); ///< Local transform.
};
} // namespace AzToolsFramework
@@ -13,6 +13,7 @@
#include "ManipulatorView.h"
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Component/NonUniformScaleBus.h>
#include <AzCore/Math/VectorConversions.h>
#include <AzCore/std/containers/array.h>
#include <AzFramework/Entity/EntityDebugDisplayBus.h>
@@ -38,6 +39,23 @@ namespace AzToolsFramework
return TransformUniformScale(worldFromLocal);
}
AZ::Vector3 GetNonUniformScale(AZ::EntityId entityId)
{
AZ::Vector3 nonUniformScale = AZ::Vector3::CreateOne();
AZ::NonUniformScaleRequestBus::EventResult(nonUniformScale, entityId, &AZ::NonUniformScaleRequests::GetScale);
return nonUniformScale;
}
AZ::Vector3 ManipulatorState::TransformPoint(const AZ::Vector3& point) const
{
return m_worldFromLocal.TransformPoint(m_nonUniformScale * point);
}
AZ::Vector3 ManipulatorState::TransformDirectionNoScaling(const AZ::Vector3& direction) const
{
return AzToolsFramework::TransformDirectionNoScaling(m_worldFromLocal, direction);
}
/// Take into account the location of the camera and orientate the axis so it faces the camera.
/// if we did correct the camera (shouldCorrect is true) then we know the axis facing us it negative.
/// we can use this to change the rendering for a flipped axis if we wish.
@@ -70,14 +88,12 @@ namespace AzToolsFramework
/// Calculate quad bound in world space.
static Picking::BoundShapeQuad CalculateQuadBound(
const AZ::Vector3& localPosition, const AZ::Transform& worldFromLocal,
const AZ::Vector3& localPosition, const ManipulatorState& manipulatorState,
const AZ::Vector3& axis1, const AZ::Vector3& axis2, const float size)
{
const AZ::Vector3 worldPosition = worldFromLocal.TransformPoint(localPosition);
const AZ::Vector3 endAxis1World = localPosition +
TransformDirectionNoScaling(worldFromLocal, axis1) * size;
const AZ::Vector3 endAxis2World = localPosition +
TransformDirectionNoScaling(worldFromLocal, axis2) * size;
const AZ::Vector3 worldPosition = manipulatorState.TransformPoint(localPosition);
const AZ::Vector3 endAxis1World = manipulatorState.TransformDirectionNoScaling(axis1) * size;
const AZ::Vector3 endAxis2World = manipulatorState.TransformDirectionNoScaling(axis2) * size;
Picking::BoundShapeQuad quadBound;
quadBound.m_corner1 = worldPosition;
@@ -117,11 +133,12 @@ namespace AzToolsFramework
static Picking::BoundShapeLineSegment CalculateLineBound(
const AZ::Vector3& localStartPosition,
const AZ::Vector3& localEndPosition,
const AZ::Transform& worldFromLocal, const float width)
const ManipulatorState& manipulatorState,
const float width)
{
Picking::BoundShapeLineSegment lineBound;
lineBound.m_start = worldFromLocal.TransformPoint(localStartPosition);
lineBound.m_end = worldFromLocal.TransformPoint(localEndPosition);
lineBound.m_start = manipulatorState.TransformPoint(localStartPosition);
lineBound.m_end = manipulatorState.TransformPoint(localEndPosition);
lineBound.m_width = width;
return lineBound;
}
@@ -166,11 +183,11 @@ namespace AzToolsFramework
/// Calculate sphere bound in world space.
static Picking::BoundShapeSphere CalculateSphereBound(
const AZ::Vector3& localPosition, const AZ::Transform& worldFromLocal,
const AZ::Vector3& localPosition, const ManipulatorState& manipulatorState,
const float radius)
{
Picking::BoundShapeSphere sphereBound;
sphereBound.m_center = worldFromLocal.TransformPoint(localPosition);
sphereBound.m_center = manipulatorState.TransformPoint(localPosition);
sphereBound.m_radius = radius;
return sphereBound;
}
@@ -305,7 +322,7 @@ namespace AzToolsFramework
const Picking::BoundShapeQuad quadBound =
CalculateQuadBound(
manipulatorState.m_localPosition, manipulatorState.m_worldFromLocal, m_cameraCorrectedAxis1, m_cameraCorrectedAxis2,
manipulatorState.m_localPosition, manipulatorState, m_cameraCorrectedAxis1, m_cameraCorrectedAxis2,
m_size * ManipulatorViewScaleMultiplier(
manipulatorState.m_worldFromLocal.TransformPoint(manipulatorState.m_localPosition), cameraState));
@@ -385,16 +402,16 @@ namespace AzToolsFramework
manipulatorState.m_worldFromLocal.TransformPoint(manipulatorState.m_localPosition), cameraState);
const Picking::BoundShapeLineSegment lineBound =
CalculateLineBound(m_localStart, m_localEnd, manipulatorState.m_worldFromLocal, m_width * viewScale);
CalculateLineBound(m_localStart, m_localEnd, manipulatorState, m_width * viewScale);
if (manipulatorState.m_mouseOver)
{
const LineSegmentSelectionManipulator::Action action = CalculateManipulationDataAction(
manipulatorState.m_worldFromLocal, mouseInteraction.m_mousePick.m_rayOrigin,
mouseInteraction.m_mousePick.m_rayDirection,
manipulatorState.m_worldFromLocal, manipulatorState.m_nonUniformScale,
mouseInteraction.m_mousePick.m_rayOrigin, mouseInteraction.m_mousePick.m_rayDirection,
cameraState.m_farClip, m_localStart, m_localEnd);
const AZ::Vector3 worldLineHitPosition = manipulatorState.m_worldFromLocal.TransformPoint(action.m_localLineHitPosition);
const AZ::Vector3 worldLineHitPosition = manipulatorState.TransformPoint(action.m_localLineHitPosition);
debugDisplay.SetColor(AZ::Vector4(0.0f, 1.0f, 0.0f, 1.0f));
debugDisplay.DrawBall(
worldLineHitPosition, ManipulatorViewScaleMultiplier(worldLineHitPosition, cameraState)
@@ -510,9 +527,9 @@ namespace AzToolsFramework
const ViewportInteraction::MouseInteraction& mouseInteraction)
{
const Picking::BoundShapeSphere sphereBound =
CalculateSphereBound(manipulatorState.m_localPosition, manipulatorState.m_worldFromLocal,
CalculateSphereBound(manipulatorState.m_localPosition, manipulatorState,
m_radius * ManipulatorViewScaleMultiplier(
manipulatorState.m_worldFromLocal.TransformPoint(manipulatorState.m_localPosition), cameraState));
manipulatorState.TransformPoint(manipulatorState.m_localPosition), cameraState));
if (m_depthTest)
{
@@ -36,8 +36,16 @@ namespace AzToolsFramework
struct ManipulatorState
{
AZ::Transform m_worldFromLocal;
AZ::Vector3 m_nonUniformScale;
AZ::Vector3 m_localPosition;
bool m_mouseOver;
/// Transforms a point, taking non-uniform scale into account.
AZ::Vector3 TransformPoint(const AZ::Vector3& point) const;
/// Rotates a direction into the space of the manipulator and normalizes it.
/// Non-uniform scaling and translation are not applied.
AZ::Vector3 TransformDirectionNoScaling(const AZ::Vector3& direction) const;
};
/// The base interface for the visual representation of manipulators.
@@ -348,6 +356,9 @@ namespace AzToolsFramework
/// the largest element.
AZ::Transform WorldFromLocalWithUniformScale(AZ::EntityId entityId);
/// Get the non-uniform scale for this entity id.
AZ::Vector3 GetNonUniformScale(AZ::EntityId entityId);
// Helpers to create various manipulator views.
AZStd::unique_ptr<ManipulatorViewQuad> CreateManipulatorViewQuad(
@@ -30,8 +30,8 @@ namespace AzToolsFramework
}
MultiLinearManipulator::MultiLinearManipulator(const AZ::Transform& worldFromLocal)
: m_worldFromLocal(worldFromLocal)
{
SetSpace(worldFromLocal);
AttachLeftMouseDownImpl();
}
@@ -56,7 +56,7 @@ namespace AzToolsFramework
}
static MultiLinearManipulator::Action BuildMultiLinearManipulatorAction(
const AZ::Transform& worldFromLocal, const AZ::Transform& localTransform,
const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale, const AZ::Transform& localTransform,
const ViewportInteraction::MouseInteraction& interaction,
const AZStd::vector<LinearManipulator::Fixed>& fixedAxes,
const AZStd::vector<LinearManipulator::Starter>& starterStates, const GridSnapAction& gridSnapAction)
@@ -68,8 +68,8 @@ namespace AzToolsFramework
{
action.m_actions.push_back(
CalculateLinearManipulationDataAction(
fixedAxes[fixedIndex], starterStates[fixedIndex], worldFromLocal, localTransform, gridSnapAction,
interaction));
fixedAxes[fixedIndex], starterStates[fixedIndex], worldFromLocal, nonUniformScale, localTransform,
gridSnapAction, interaction));
}
return action;
@@ -78,7 +78,7 @@ namespace AzToolsFramework
void MultiLinearManipulator::OnLeftMouseDownImpl(
const ViewportInteraction::MouseInteraction& interaction, const float rayIntersectionDistance)
{
const AZ::Transform worldFromLocalUniformScale = TransformUniformScale(m_worldFromLocal);
const AZ::Transform worldFromLocalUniformScale = TransformUniformScale(GetSpace());
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
const AzFramework::CameraState cameraState = GetCameraState(interaction.m_interactionId.m_viewportId);
@@ -88,7 +88,7 @@ namespace AzToolsFramework
{
// note: m_localTransform must not be made uniform as it may contain a local scale we want to snap
const auto linearStart = CalculateLinearManipulationDataStart(
fixed, worldFromLocalUniformScale, m_localTransform,
fixed, worldFromLocalUniformScale, GetNonUniformScale(), GetLocalTransform(),
GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt()), interaction,
rayIntersectionDistance, cameraState);
@@ -100,7 +100,8 @@ namespace AzToolsFramework
const GridSnapAction gridSnapAction = GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt());
// pass action containing all linear actions for each axis to handler
m_onLeftMouseDownCallback(BuildMultiLinearManipulatorAction(
worldFromLocalUniformScale, m_localTransform, interaction, m_fixedAxes, m_starters, gridSnapAction));
worldFromLocalUniformScale, GetNonUniformScale(), GetLocalTransform(),
interaction, m_fixedAxes, m_starters, gridSnapAction));
}
}
@@ -108,12 +109,13 @@ namespace AzToolsFramework
{
if (m_onMouseMoveCallback)
{
const AZ::Transform worldFromLocalUniformScale = TransformUniformScale(m_worldFromLocal);
const AZ::Transform worldFromLocalUniformScale = TransformUniformScale(GetSpace());
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
const GridSnapAction gridSnapAction = GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt());
m_onMouseMoveCallback(BuildMultiLinearManipulatorAction(
worldFromLocalUniformScale, m_localTransform, interaction, m_fixedAxes, m_starters, gridSnapAction));
worldFromLocalUniformScale, GetNonUniformScale(), GetLocalTransform(),
interaction, m_fixedAxes, m_starters, gridSnapAction));
}
}
@@ -121,12 +123,13 @@ namespace AzToolsFramework
{
if (m_onLeftMouseUpCallback)
{
const AZ::Transform worldFromLocalUniformScale = TransformUniformScale(m_worldFromLocal);
const AZ::Transform worldFromLocalUniformScale = TransformUniformScale(GetSpace());
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
const GridSnapAction gridSnapAction = GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt());
m_onLeftMouseUpCallback(BuildMultiLinearManipulatorAction(
worldFromLocalUniformScale, m_localTransform, interaction, m_fixedAxes, m_starters, gridSnapAction));
worldFromLocalUniformScale, GetNonUniformScale(), GetLocalTransform(),
interaction, m_fixedAxes, m_starters, gridSnapAction));
m_starters.clear();
}
@@ -140,7 +143,7 @@ namespace AzToolsFramework
{
if (cl_manipulatorDrawDebug)
{
const AZ::Transform combined = TransformUniformScale(m_worldFromLocal) * m_localTransform;
const AZ::Transform combined = TransformUniformScale(GetSpace()) * GetLocalTransform();
for (const auto& fixed : m_fixedAxes)
{
DrawAxis(
@@ -153,7 +156,7 @@ namespace AzToolsFramework
view->Draw(
GetManipulatorManagerId(), managerState,
GetManipulatorId(), {
m_worldFromLocal * m_localTransform,
ApplySpace(GetLocalTransform()), GetNonUniformScale(),
AZ::Vector3::CreateZero(), MouseOver()
},
debugDisplay, cameraState, mouseInteraction);
@@ -176,27 +179,6 @@ namespace AzToolsFramework
});
}
void MultiLinearManipulator::SetSpace(const AZ::Transform& worldFromLocal)
{
m_worldFromLocal = worldFromLocal;
}
void MultiLinearManipulator::SetLocalTransform(const AZ::Transform& localTransform)
{
m_localTransform = localTransform;
}
void MultiLinearManipulator::SetLocalPosition(const AZ::Vector3& localPosition)
{
m_localTransform.SetTranslation(localPosition);
}
void MultiLinearManipulator::SetLocalOrientation(const AZ::Quaternion& localOrientation)
{
m_localTransform = AZ::Transform::CreateFromQuaternionAndTranslation(
localOrientation, m_localTransform.GetTranslation());
}
void MultiLinearManipulator::ClearAxes()
{
m_fixedAxes.clear();
@@ -26,6 +26,7 @@ namespace AzToolsFramework
//! in one or more dimensions on axes defined in 3D space.
class MultiLinearManipulator
: public BaseManipulator
, public ManipulatorSpaceWithLocalTransform
{
//! Private constructor.
explicit MultiLinearManipulator(const AZ::Transform& worldFromLocal);
@@ -70,15 +71,6 @@ namespace AzToolsFramework
void AddAxes(const AZStd::vector<AZ::Vector3>& axes);
void ClearAxes();
void SetSpace(const AZ::Transform& worldFromLocal);
void SetLocalTransform(const AZ::Transform& localTransform);
void SetLocalPosition(const AZ::Vector3& localPosition);
void SetLocalOrientation(const AZ::Quaternion& localOrientation);
AZ::Vector3 GetLocalPosition() const;
const AZ::Transform& GetSpace() const;
const AZ::Transform& GetLocalTransform() const;
using ConstFixedIterator = AZStd::vector<LinearManipulator::Fixed>::const_iterator;
ConstFixedIterator FixedBegin() const;
ConstFixedIterator FixedEnd() const;
@@ -100,9 +92,6 @@ namespace AzToolsFramework
void InvalidateImpl() override;
void SetBoundsDirtyImpl() override;
AZ::Transform m_localTransform = AZ::Transform::CreateIdentity(); //!< Local transform of the manipulator.
AZ::Transform m_worldFromLocal = AZ::Transform::CreateIdentity(); //!< Space the manipulator is in (identity is world space).
AZStd::vector<LinearManipulator::Fixed> m_fixedAxes; //!< A collection of LinearManipulator fixed states.
AZStd::vector<LinearManipulator::Starter> m_starters; //!< A collection of LinearManipulator starter states.
@@ -113,21 +102,6 @@ namespace AzToolsFramework
ManipulatorViews m_manipulatorViews; //!< Look of manipulator.
};
inline AZ::Vector3 MultiLinearManipulator::GetLocalPosition() const
{
return m_localTransform.GetTranslation();
}
inline const AZ::Transform& MultiLinearManipulator::GetSpace() const
{
return m_localTransform;
}
inline const AZ::Transform& MultiLinearManipulator::GetLocalTransform() const
{
return m_localTransform;
}
inline MultiLinearManipulator::ConstFixedIterator MultiLinearManipulator::FixedBegin() const
{
return m_fixedAxes.cbegin();
@@ -22,13 +22,13 @@
namespace AzToolsFramework
{
PlanarManipulator::StartInternal PlanarManipulator::CalculateManipulationDataStart(
const Fixed& fixed, const AZ::Transform& worldFromLocal, const AZ::Transform& localTransform,
const GridSnapAction& gridSnapAction, const ViewportInteraction::MouseInteraction& interaction,
const float intersectionDistance)
const Fixed& fixed, const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale,
const AZ::Transform& localTransform, const GridSnapAction& gridSnapAction,
const ViewportInteraction::MouseInteraction& interaction, const float intersectionDistance)
{
const ManipulatorInteraction manipulatorInteraction =
BuildManipulatorInteraction(
worldFromLocal, interaction.m_mousePick.m_rayOrigin, interaction.m_mousePick.m_rayDirection);
worldFromLocal, nonUniformScale, interaction.m_mousePick.m_rayOrigin, interaction.m_mousePick.m_rayDirection);
const AZ::Vector3 normal = TransformDirectionNoScaling(localTransform, fixed.m_normal);
const AZ::Vector3 axis1 = TransformDirectionNoScaling(localTransform, fixed.m_axis1);
@@ -61,12 +61,12 @@ namespace AzToolsFramework
PlanarManipulator::Action PlanarManipulator::CalculateManipulationDataAction(
const Fixed& fixed, const StartInternal& startInternal, const AZ::Transform& worldFromLocal,
const AZ::Transform& localTransform, const GridSnapAction& gridSnapAction,
const AZ::Vector3& nonUniformScale, const AZ::Transform& localTransform, const GridSnapAction& gridSnapAction,
const ViewportInteraction::MouseInteraction& interaction)
{
const ManipulatorInteraction manipulatorInteraction =
BuildManipulatorInteraction(
worldFromLocal, interaction.m_mousePick.m_rayOrigin, interaction.m_mousePick.m_rayDirection);
worldFromLocal, nonUniformScale, interaction.m_mousePick.m_rayOrigin, interaction.m_mousePick.m_rayDirection);
const AZ::Vector3 normal = TransformDirectionNoScaling(localTransform, fixed.m_normal);
@@ -85,10 +85,11 @@ namespace AzToolsFramework
const AZ::Vector3 axis1 = TransformDirectionNoScaling(localTransform, fixed.m_axis1);
const AZ::Vector3 axis2 = TransformDirectionNoScaling(localTransform, fixed.m_axis2);
const AZ::Vector3 hitDelta = (localHitPosition - startInternal.m_localHitPosition);
const AZ::Vector3 hitDelta = (localHitPosition - startInternal.m_localHitPosition) / nonUniformScale;
const AZ::Vector3 unsnappedOffset = axis1.Dot(hitDelta) * axis1 + axis2.Dot(hitDelta) * axis2;
const float scaleRecip = manipulatorInteraction.m_scaleReciprocal;
const AZ::Vector3 nonUniformScaleRecip = manipulatorInteraction.m_nonUniformScaleReciprocal;
const float gridSize = gridSnapAction.m_gridSnapParams.m_gridSize;
const bool snapping = gridSnapAction.m_gridSnapParams.m_gridSnap;
@@ -99,8 +100,8 @@ namespace AzToolsFramework
action.m_start.m_localHitPosition = startInternal.m_localHitPosition;
action.m_current.m_localOffset = snapping
? unsnappedOffset +
CalculateSnappedOffset(unsnappedOffset, axis1, gridSize * scaleRecip) +
CalculateSnappedOffset(unsnappedOffset, axis2, gridSize * scaleRecip)
CalculateSnappedOffset(unsnappedOffset, axis1, gridSize * scaleRecip * nonUniformScaleRecip.Dot(axis1)) +
CalculateSnappedOffset(unsnappedOffset, axis2, gridSize * scaleRecip * nonUniformScaleRecip.Dot(axis2))
: unsnappedOffset;
// record what modifier keys are held during this action
@@ -115,8 +116,8 @@ namespace AzToolsFramework
}
PlanarManipulator::PlanarManipulator(const AZ::Transform& worldFromLocal)
: m_worldFromLocal(worldFromLocal)
{
SetSpace(worldFromLocal);
AttachLeftMouseDownImpl();
}
@@ -138,19 +139,19 @@ namespace AzToolsFramework
void PlanarManipulator::OnLeftMouseDownImpl(
const ViewportInteraction::MouseInteraction& interaction, const float rayIntersectionDistance)
{
const AZ::Transform worldFromLocalUniformScale = TransformUniformScale(m_worldFromLocal);
const AZ::Transform worldFromLocalUniformScale = TransformUniformScale(GetSpace());
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
m_startInternal = CalculateManipulationDataStart(
m_fixed, worldFromLocalUniformScale, TransformNormalizedScale(m_localTransform),
m_fixed, worldFromLocalUniformScale, GetNonUniformScale(), TransformNormalizedScale(GetLocalTransform()),
GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt()),
interaction, rayIntersectionDistance);
if (m_onLeftMouseDownCallback)
{
m_onLeftMouseDownCallback(CalculateManipulationDataAction(
m_fixed, m_startInternal, worldFromLocalUniformScale, TransformNormalizedScale(m_localTransform),
m_fixed, m_startInternal, worldFromLocalUniformScale, GetNonUniformScale(), TransformNormalizedScale(GetLocalTransform()),
GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt()), interaction));
}
}
@@ -162,8 +163,8 @@ namespace AzToolsFramework
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
m_onMouseMoveCallback(CalculateManipulationDataAction(
m_fixed, m_startInternal, TransformUniformScale(m_worldFromLocal),
TransformNormalizedScale(m_localTransform),
m_fixed, m_startInternal, TransformUniformScale(GetSpace()), GetNonUniformScale(),
TransformNormalizedScale(GetLocalTransform()),
GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt()), interaction));
}
}
@@ -175,8 +176,8 @@ namespace AzToolsFramework
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
m_onLeftMouseUpCallback(CalculateManipulationDataAction(
m_fixed, m_startInternal, TransformUniformScale(m_worldFromLocal),
TransformNormalizedScale(m_localTransform),
m_fixed, m_startInternal, TransformUniformScale(GetSpace()), GetNonUniformScale(),
TransformNormalizedScale(GetLocalTransform()),
GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt()), interaction));
}
}
@@ -193,27 +194,29 @@ namespace AzToolsFramework
{
const GridSnapParameters gridSnapParams = GridSnapSettings(mouseInteraction.m_interactionId.m_viewportId);
const auto action = CalculateManipulationDataAction(
m_fixed, m_startInternal, TransformUniformScale(m_worldFromLocal),
TransformNormalizedScale(m_localTransform),
m_fixed, m_startInternal, TransformUniformScale(GetSpace()), GetNonUniformScale(),
TransformNormalizedScale(GetLocalTransform()),
GridSnapAction(gridSnapParams, mouseInteraction.m_keyboardModifiers.Alt()), mouseInteraction);
// display the exact hit (ray intersection) of the mouse pick on the manipulator
DrawTransformAxes(
debugDisplay, TransformUniformScale(m_worldFromLocal) *
debugDisplay, TransformUniformScale(GetSpace()) *
AZ::Transform::CreateTranslation(
action.m_start.m_localHitPosition + action.m_current.m_localOffset));
action.m_start.m_localHitPosition + GetNonUniformScale() * action.m_current.m_localOffset));
}
const AZ::Transform combined = m_worldFromLocal * m_localTransform;
AZ::Transform combined = GetLocalTransform();
combined.SetTranslation(GetNonUniformScale() * combined.GetTranslation());
combined = GetSpace() * combined;
DrawTransformAxes(debugDisplay, combined);
DrawAxis(
debugDisplay, combined.GetTranslation(),
TransformDirectionNoScaling(m_localTransform, m_fixed.m_axis1));
TransformDirectionNoScaling(GetLocalTransform(), m_fixed.m_axis1));
DrawAxis(
debugDisplay, combined.GetTranslation(),
TransformDirectionNoScaling(m_localTransform, m_fixed.m_axis2));
TransformDirectionNoScaling(GetLocalTransform(), m_fixed.m_axis2));
}
for (auto& view : m_manipulatorViews)
@@ -221,7 +224,7 @@ namespace AzToolsFramework
view->Draw(
GetManipulatorManagerId(), managerState,
GetManipulatorId(), {
m_worldFromLocal * m_localTransform,
ApplySpace(GetLocalTransform()), GetNonUniformScale(),
AZ::Vector3::CreateZero(), MouseOver()
},
debugDisplay, cameraState, mouseInteraction);
@@ -235,27 +238,6 @@ namespace AzToolsFramework
m_fixed.m_normal = axis1.Cross(axis2);
}
void PlanarManipulator::SetSpace(const AZ::Transform& worldFromLocal)
{
m_worldFromLocal = worldFromLocal;
}
void PlanarManipulator::SetLocalTransform(const AZ::Transform& localTransform)
{
m_localTransform = localTransform;
}
void PlanarManipulator::SetLocalPosition(const AZ::Vector3& localPosition)
{
m_localTransform.SetTranslation(localPosition);
}
void PlanarManipulator::SetLocalOrientation(const AZ::Quaternion& localOrientation)
{
m_localTransform = AZ::Transform::CreateFromQuaternionAndTranslation(
localOrientation, m_localTransform.GetTranslation());
}
void PlanarManipulator::InvalidateImpl()
{
for (auto& view : m_manipulatorViews)
@@ -27,6 +27,7 @@ namespace AzToolsFramework
/// in two dimension in a plane defined two non-collinear axes in 3D space.
class PlanarManipulator
: public BaseManipulator
, public ManipulatorSpaceWithLocalTransform
{
/// Private constructor.
explicit PlanarManipulator(const AZ::Transform& worldFromLocal);
@@ -93,14 +94,9 @@ namespace AzToolsFramework
/// Ensure @param axis1 and @param axis2 are not collinear.
void SetAxes(const AZ::Vector3& axis1, const AZ::Vector3& axis2);
void SetSpace(const AZ::Transform& worldFromLocal);
void SetLocalTransform(const AZ::Transform& localTransform);
void SetLocalPosition(const AZ::Vector3& localPosition);
void SetLocalOrientation(const AZ::Quaternion& localOrientation);
const AZ::Vector3& GetAxis1() const { return m_fixed.m_axis1; }
const AZ::Vector3& GetAxis2() const { return m_fixed.m_axis2; }
AZ::Vector3 GetPosition() const { return m_localTransform.GetTranslation(); }
template<typename Views>
void SetViews(Views&& views)
@@ -127,9 +123,6 @@ namespace AzToolsFramework
AZ::Vector3 m_snapOffset; ///< The snap offset amount to ensure manipulator is aligned to the grid.
};
AZ::Transform m_localTransform = AZ::Transform::CreateIdentity(); ///< Local transform of the manipulator.
AZ::Transform m_worldFromLocal = AZ::Transform::CreateIdentity(); ///< Space the manipulator is in (identity is world space).
Fixed m_fixed;
StartInternal m_startInternal;
@@ -140,13 +133,13 @@ namespace AzToolsFramework
ManipulatorViews m_manipulatorViews; ///< Look of manipulator.
static StartInternal CalculateManipulationDataStart(
const Fixed& fixed, const AZ::Transform& worldFromLocal, const AZ::Transform& localTransform,
const GridSnapAction& gridSnapAction, const ViewportInteraction::MouseInteraction& interaction,
float intersectionDistance);
const Fixed& fixed, const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale,
const AZ::Transform& localTransform, const GridSnapAction& gridSnapAction,
const ViewportInteraction::MouseInteraction& interaction, float intersectionDistance);
static Action CalculateManipulationDataAction(
const Fixed& fixed, const StartInternal& startInternal, const AZ::Transform& worldFromLocal,
const AZ::Transform& localTransform, const GridSnapAction& gridSnapAction,
const ViewportInteraction::MouseInteraction& interaction);
const AZ::Vector3& nonUniformScale, const AZ::Transform& localTransform,
const GridSnapAction& gridSnapAction, const ViewportInteraction::MouseInteraction& interaction);
};
} // namespace AzToolsFramework
@@ -25,7 +25,7 @@ namespace AzToolsFramework
m_viewAngularManipulator = AngularManipulator::MakeShared(worldFromLocal);
m_space = worldFromLocal;
m_manipulatorSpaceWithLocalTransform.SetSpace(worldFromLocal);
}
void RotationManipulators::InstallLeftMouseDownCallback(
@@ -61,7 +61,7 @@ namespace AzToolsFramework
m_viewAngularManipulator->InstallLeftMouseUpCallback(onMouseUpCallback);
}
void RotationManipulators::SetLocalTransform(const AZ::Transform& localTransform)
void RotationManipulators::SetLocalTransformImpl(const AZ::Transform& localTransform)
{
for (AZStd::shared_ptr<AngularManipulator>& manipulator : m_localAngularManipulators)
{
@@ -69,11 +69,9 @@ namespace AzToolsFramework
}
m_viewAngularManipulator->SetLocalTransform(localTransform);
m_localTransform = localTransform;
}
void RotationManipulators::SetLocalPosition(const AZ::Vector3& localPosition)
void RotationManipulators::SetLocalPositionImpl(const AZ::Vector3& localPosition)
{
for (AZStd::shared_ptr<AngularManipulator>& manipulator : m_localAngularManipulators)
{
@@ -81,11 +79,9 @@ namespace AzToolsFramework
}
m_viewAngularManipulator->SetLocalPosition(localPosition);
m_localTransform.SetTranslation(localPosition);
}
void RotationManipulators::SetLocalOrientation(const AZ::Quaternion& localOrientation)
void RotationManipulators::SetLocalOrientationImpl(const AZ::Quaternion& localOrientation)
{
for (AZStd::shared_ptr<AngularManipulator>& manipulator : m_localAngularManipulators)
{
@@ -93,9 +89,6 @@ namespace AzToolsFramework
}
m_viewAngularManipulator->SetLocalOrientation(localOrientation);
m_localTransform = AZ::Transform::CreateFromQuaternionAndTranslation(
localOrientation, m_localTransform.GetTranslation());
}
void RotationManipulators::RefreshView(const AZ::Vector3& worldViewPosition)
@@ -106,7 +99,7 @@ namespace AzToolsFramework
}
}
void RotationManipulators::SetSpace(const AZ::Transform& worldFromLocal)
void RotationManipulators::SetSpaceImpl(const AZ::Transform& worldFromLocal)
{
for (AZStd::shared_ptr<AngularManipulator>& manipulator : m_localAngularManipulators)
{
@@ -114,8 +107,6 @@ namespace AzToolsFramework
}
m_viewAngularManipulator->SetSpace(worldFromLocal);
m_space = worldFromLocal;
}
void RotationManipulators::SetLocalAxes(
@@ -177,4 +168,4 @@ namespace AzToolsFramework
manipulatorFn(m_viewAngularManipulator.get());
}
} // namespace AzToolsFramework
} // namespace AzToolsFramework
@@ -34,10 +34,10 @@ namespace AzToolsFramework
void InstallLeftMouseUpCallback(const AngularManipulator::MouseActionCallback& onMouseUpCallback);
void InstallMouseMoveCallback(const AngularManipulator::MouseActionCallback& onMouseMoveCallback);
void SetSpace(const AZ::Transform& worldFromLocal) override;
void SetLocalTransform(const AZ::Transform& localTransform) override;
void SetLocalPosition(const AZ::Vector3& localPosition) override;
void SetLocalOrientation(const AZ::Quaternion& localOrientation) override;
void SetSpaceImpl(const AZ::Transform& worldFromLocal) override;
void SetLocalTransformImpl(const AZ::Transform& localTransform) override;
void SetLocalPositionImpl(const AZ::Vector3& localPosition) override;
void SetLocalOrientationImpl(const AZ::Quaternion& localOrientation) override;
void RefreshView(const AZ::Vector3& worldViewPosition) override;
void SetLocalAxes(
@@ -57,4 +57,4 @@ namespace AzToolsFramework
AZStd::array<AZStd::shared_ptr<AngularManipulator>, 3> m_localAngularManipulators;
AZStd::shared_ptr<AngularManipulator> m_viewAngularManipulator;
};
} // namespace AzToolsFramework
} // namespace AzToolsFramework
@@ -25,7 +25,7 @@ namespace AzToolsFramework
m_uniformScaleManipulator = LinearManipulator::MakeShared(worldFromLocal);
m_space = worldFromLocal;
m_manipulatorSpaceWithLocalTransform.SetSpace(worldFromLocal);
}
void ScaleManipulators::InstallAxisLeftMouseDownCallback(
@@ -73,7 +73,7 @@ namespace AzToolsFramework
m_uniformScaleManipulator->InstallLeftMouseUpCallback(onMouseUpCallback);
}
void ScaleManipulators::SetLocalTransform(const AZ::Transform& localTransform)
void ScaleManipulators::SetLocalTransformImpl(const AZ::Transform& localTransform)
{
for (AZStd::shared_ptr<LinearManipulator>& manipulator : m_axisScaleManipulators)
{
@@ -85,11 +85,9 @@ namespace AzToolsFramework
m_uniformScaleManipulator->SetLocalTransform(
AZ::Transform::CreateTranslation(localTransform.GetTranslation()) *
AZ::Transform::CreateScale(localTransform.GetScale()));
m_localTransform = localTransform;
}
void ScaleManipulators::SetLocalPosition(const AZ::Vector3& localPosition)
void ScaleManipulators::SetLocalPositionImpl(const AZ::Vector3& localPosition)
{
for (AZStd::shared_ptr<LinearManipulator>& manipulator : m_axisScaleManipulators)
{
@@ -97,22 +95,17 @@ namespace AzToolsFramework
}
m_uniformScaleManipulator->SetLocalPosition(localPosition);
m_localTransform.SetTranslation(localPosition);
}
void ScaleManipulators::SetLocalOrientation(const AZ::Quaternion& localOrientation)
void ScaleManipulators::SetLocalOrientationImpl(const AZ::Quaternion& localOrientation)
{
for (AZStd::shared_ptr<LinearManipulator>& manipulator : m_axisScaleManipulators)
{
manipulator->SetLocalOrientation(localOrientation);
}
m_localTransform = AZ::Transform::CreateFromQuaternionAndTranslation(
localOrientation, m_localTransform.GetTranslation());
}
void ScaleManipulators::SetSpace(const AZ::Transform& worldFromLocal)
void ScaleManipulators::SetSpaceImpl(const AZ::Transform& worldFromLocal)
{
for (AZStd::shared_ptr<LinearManipulator>& manipulator : m_axisScaleManipulators)
{
@@ -120,8 +113,6 @@ namespace AzToolsFramework
}
m_uniformScaleManipulator->SetSpace(worldFromLocal);
m_space = worldFromLocal;
}
void ScaleManipulators::SetAxes(
@@ -37,10 +37,10 @@ namespace AzToolsFramework
void InstallUniformMouseMoveCallback(const LinearManipulator::MouseActionCallback& onMouseMoveCallback);
void InstallUniformLeftMouseUpCallback(const LinearManipulator::MouseActionCallback& onMouseUpCallback);
void SetSpace(const AZ::Transform& worldFromLocal) override;
void SetLocalTransform(const AZ::Transform& localTransform) override;
void SetLocalPosition(const AZ::Vector3& localPosition) override;
void SetLocalOrientation(const AZ::Quaternion& localOrientation) override;
void SetSpaceImpl(const AZ::Transform& worldFromLocal) override;
void SetLocalTransformImpl(const AZ::Transform& localTransform) override;
void SetLocalPositionImpl(const AZ::Vector3& localPosition) override;
void SetLocalOrientationImpl(const AZ::Quaternion& localOrientation) override;
void SetAxes(
const AZ::Vector3& axis1,
@@ -62,4 +62,4 @@ namespace AzToolsFramework
AZStd::array<AZStd::shared_ptr<LinearManipulator>, 3> m_axisScaleManipulators;
AZStd::shared_ptr<LinearManipulator> m_uniformScaleManipulator;
};
} // namespace AzToolsFramework
} // namespace AzToolsFramework
@@ -16,14 +16,16 @@
namespace AzToolsFramework
{
AZStd::shared_ptr<SelectionManipulator> SelectionManipulator::MakeShared(const AZ::Transform& worldFromLocal)
AZStd::shared_ptr<SelectionManipulator> SelectionManipulator::MakeShared(const AZ::Transform& worldFromLocal,
const AZ::Vector3& nonUniformScale)
{
return AZStd::shared_ptr<SelectionManipulator>(aznew SelectionManipulator(worldFromLocal));
return AZStd::shared_ptr<SelectionManipulator>(aznew SelectionManipulator(worldFromLocal, nonUniformScale));
}
SelectionManipulator::SelectionManipulator(const AZ::Transform& worldFromLocal)
: m_worldFromLocal(worldFromLocal)
SelectionManipulator::SelectionManipulator(const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale)
{
SetSpace(worldFromLocal);
SetNonUniformScale(nonUniformScale);
AttachLeftMouseDownImpl();
AttachRightMouseDownImpl();
}
@@ -93,8 +95,8 @@ namespace AzToolsFramework
view->Draw(
GetManipulatorManagerId(), managerState,
GetManipulatorId(), {
TransformUniformScale(m_worldFromLocal),
m_position, MouseOver()
TransformUniformScale(GetSpace()), GetNonUniformScale(),
GetLocalPosition(), MouseOver()
},
debugDisplay, cameraState, mouseInteraction);
}
@@ -24,9 +24,10 @@ namespace AzToolsFramework
/// For example clicking a preview point to create a translation manipulator.
class SelectionManipulator
: public BaseManipulator
, public ManipulatorSpaceWithLocalPosition
{
/// Private constructor.
explicit SelectionManipulator(const AZ::Transform& worldFromLocal);
SelectionManipulator(const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale = AZ::Vector3::CreateOne());
public:
AZ_RTTI(SelectionManipulator, "{966F44B7-E287-4C28-9734-5958F1A13A1D}", BaseManipulator);
@@ -39,7 +40,8 @@ namespace AzToolsFramework
~SelectionManipulator() = default;
/// A Manipulator must only be created and managed through a shared_ptr.
static AZStd::shared_ptr<SelectionManipulator> MakeShared(const AZ::Transform& worldFromLocal);
static AZStd::shared_ptr<SelectionManipulator> MakeShared(const AZ::Transform& worldFromLocal,
const AZ::Vector3& nonUniformScale = AZ::Vector3::CreateOne());
/// This is the function signature of callbacks that will be invoked
/// whenever a selection manipulator is clicked on.
@@ -56,11 +58,6 @@ namespace AzToolsFramework
const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction) override;
void SetPosition(const AZ::Vector3& position) { m_position = position; }
void SetSpace(const AZ::Transform& worldFromLocal) { m_worldFromLocal = worldFromLocal; }
const AZ::Vector3& GetPosition() const { return m_position; }
bool Selected() const { return m_selected; }
void Select() { m_selected = true; }
void Deselect() { m_selected = false; }
@@ -85,9 +82,6 @@ namespace AzToolsFramework
void InvalidateImpl() override;
void SetBoundsDirtyImpl() override;
AZ::Vector3 m_position = AZ::Vector3::CreateZero(); ///< Position in local space.
AZ::Transform m_worldFromLocal = AZ::Transform::CreateIdentity(); ///< Space the manipulator is in (identity is world space).
bool m_selected = false;
MouseActionCallback m_onLeftMouseDownCallback = nullptr;
@@ -86,4 +86,12 @@ namespace AzToolsFramework
m_splineSelectionManipulator->SetSpace(worldFromLocal);
}
}
void SplineHoverSelection::SetNonUniformScale(const AZ::Vector3& nonUniformScale)
{
if (m_splineSelectionManipulator)
{
m_splineSelectionManipulator->SetNonUniformScale(nonUniformScale);
}
}
} // namespace AzToolsFramework
@@ -43,8 +43,9 @@ namespace AzToolsFramework
void SetBoundsDirty() override;
void Refresh() override;
void SetSpace(const AZ::Transform& worldFromLocal) override;
void SetNonUniformScale(const AZ::Vector3& nonUniformScale) override;
private:
AZStd::shared_ptr<SplineSelectionManipulator> m_splineSelectionManipulator; ///< Manipulator for adding points to spline.
};
} // namespace AzToolsFramework
} // namespace AzToolsFramework
@@ -65,7 +65,7 @@ namespace AzToolsFramework
if (m_onLeftMouseDownCallback)
{
m_onLeftMouseDownCallback(CalculateManipulationDataAction(
TransformUniformScale(m_worldFromLocal),
TransformUniformScale(GetSpace()),
interaction.m_mousePick.m_rayOrigin,
interaction.m_mousePick.m_rayDirection, m_spline));
}
@@ -76,7 +76,7 @@ namespace AzToolsFramework
if (MouseOver() && m_onLeftMouseUpCallback)
{
m_onLeftMouseUpCallback(CalculateManipulationDataAction(
TransformUniformScale(m_worldFromLocal),
TransformUniformScale(GetSpace()),
interaction.m_mousePick.m_rayOrigin,
interaction.m_mousePick.m_rayDirection, m_spline));
}
@@ -101,7 +101,7 @@ namespace AzToolsFramework
m_manipulatorView->Draw(
GetManipulatorManagerId(), managerState,
GetManipulatorId(), {
TransformUniformScale(m_worldFromLocal),
TransformUniformScale(GetSpace()), GetNonUniformScale(),
AZ::Vector3::CreateZero(), MouseOver()
},
debugDisplay, cameraState, mouseInteraction);
@@ -27,6 +27,7 @@ namespace AzToolsFramework
/// used to test mouse picking ray against to preview closest point on spline.
class SplineSelectionManipulator
: public BaseManipulator
, public ManipulatorSpace
{
/// Private constructor.
SplineSelectionManipulator();
@@ -61,7 +62,6 @@ namespace AzToolsFramework
const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction) override;
void SetSpace(const AZ::Transform& worldFromLocal) { m_worldFromLocal = worldFromLocal; }
void SetSpline(AZStd::shared_ptr<const AZ::Spline> spline) { m_spline = AZStd::move(spline); }
AZStd::weak_ptr<const AZ::Spline> GetSpline() const { return m_spline; }
@@ -76,7 +76,6 @@ namespace AzToolsFramework
void InvalidateImpl() override;
void SetBoundsDirtyImpl() override;
AZ::Transform m_worldFromLocal = AZ::Transform::CreateIdentity(); ///< Space the manipulator is in (identity is world space).
AZStd::weak_ptr<const AZ::Spline> m_spline;
AZStd::unique_ptr<ManipulatorView> m_manipulatorView = nullptr; ///< Look of manipulator and bounds for interaction.
MouseActionCallback m_onLeftMouseDownCallback = nullptr;
@@ -73,8 +73,8 @@ namespace AzToolsFramework
}
SurfaceManipulator::SurfaceManipulator(const AZ::Transform& worldFromLocal)
: m_worldFromLocal(worldFromLocal)
{
SetSpace(worldFromLocal);
AttachLeftMouseDownImpl();
}
@@ -95,7 +95,7 @@ namespace AzToolsFramework
void SurfaceManipulator::OnLeftMouseDownImpl(
const ViewportInteraction::MouseInteraction& interaction, float /*rayIntersectionDistance*/)
{
const AZ::Transform worldFromLocalUniformScale = TransformUniformScale(m_worldFromLocal);
const AZ::Transform worldFromLocalUniformScale = TransformUniformScale(GetSpace());
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
@@ -107,7 +107,7 @@ namespace AzToolsFramework
interaction.m_mousePick.m_screenCoordinates));
m_startInternal = CalculateManipulationDataStart(
worldFromLocalUniformScale, worldSurfacePosition, m_position,
worldFromLocalUniformScale, worldSurfacePosition, GetLocalPosition(),
gridSnapParams.m_gridSnap, gridSnapParams.m_gridSize,
interaction.m_interactionId.m_viewportId);
@@ -135,7 +135,7 @@ namespace AzToolsFramework
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
m_onLeftMouseUpCallback(CalculateManipulationDataAction(
m_startInternal, TransformUniformScale(m_worldFromLocal), worldSurfacePosition,
m_startInternal, TransformUniformScale(GetSpace()), worldSurfacePosition,
gridSnapParams.m_gridSnap,
gridSnapParams.m_gridSize,
interaction.m_keyboardModifiers, interaction.m_interactionId.m_viewportId));
@@ -156,7 +156,7 @@ namespace AzToolsFramework
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
m_onMouseMoveCallback(CalculateManipulationDataAction(
m_startInternal, TransformUniformScale(m_worldFromLocal), worldSurfacePosition,
m_startInternal, TransformUniformScale(GetSpace()), worldSurfacePosition,
gridSnapParams.m_gridSnap,
gridSnapParams.m_gridSize,
interaction.m_keyboardModifiers, interaction.m_interactionId.m_viewportId));
@@ -177,8 +177,8 @@ namespace AzToolsFramework
m_manipulatorView->Draw(
GetManipulatorManagerId(), managerState,
GetManipulatorId(), {
TransformUniformScale(m_worldFromLocal),
m_position, MouseOver()
TransformUniformScale(GetSpace()), GetNonUniformScale(),
GetLocalPosition(), MouseOver()
},
debugDisplay, cameraState, mouseInteraction);
}
@@ -25,6 +25,7 @@ namespace AzToolsFramework
/// while also staying aligned exactly to the height of the terrain.
class SurfaceManipulator
: public BaseManipulator
, public ManipulatorSpaceWithLocalPosition
{
/// Private constructor.
explicit SurfaceManipulator(const AZ::Transform& worldFromLocal);
@@ -77,11 +78,6 @@ namespace AzToolsFramework
const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction) override;
void SetPosition(const AZ::Vector3& position) { m_position = position; }
void SetSpace(const AZ::Transform& worldFromLocal) { m_worldFromLocal = worldFromLocal; }
const AZ::Vector3& GetPosition() const { return m_position; }
void SetView(AZStd::unique_ptr<ManipulatorView>&& view);
private:
@@ -103,9 +99,6 @@ namespace AzToolsFramework
AZ::Vector3 m_snapOffset; ///< The snap offset amount to ensure manipulator is aligned to the grid.
};
AZ::Vector3 m_position = AZ::Vector3::CreateZero(); ///< Position in local space.
AZ::Transform m_worldFromLocal = AZ::Transform::CreateIdentity(); ///< Space the manipulator is in (identity is world space).
StartInternal m_startInternal; ///< Internal initial state recorded/created in OnMouseDown.
AZStd::unique_ptr<ManipulatorView> m_manipulatorView = nullptr; ///< Look of manipulator.
@@ -27,7 +27,7 @@ namespace AzToolsFramework
static const AZ::Color s_surfaceManipulatorColor = AZ::Color(1.0f, 1.0f, 0.0f, 0.5f);
TranslationManipulators::TranslationManipulators(
const Dimensions dimensions, const AZ::Transform& worldFromLocal)
const Dimensions dimensions, const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale)
: m_dimensions(dimensions)
{
switch (dimensions)
@@ -55,7 +55,8 @@ namespace AzToolsFramework
break;
}
m_space = worldFromLocal;
m_manipulatorSpaceWithLocalTransform.SetSpace(worldFromLocal);
SetNonUniformScale(nonUniformScale);
}
void TranslationManipulators::InstallLinearManipulatorMouseDownCallback(
@@ -139,7 +140,7 @@ namespace AzToolsFramework
}
}
void TranslationManipulators::SetLocalTransform(const AZ::Transform& localTransform)
void TranslationManipulators::SetLocalTransformImpl(const AZ::Transform& localTransform)
{
for (AZStd::shared_ptr<LinearManipulator>& manipulator : m_linearManipulators)
{
@@ -153,13 +154,11 @@ namespace AzToolsFramework
if (m_surfaceManipulator)
{
m_surfaceManipulator->SetPosition(localTransform.GetTranslation());
m_surfaceManipulator->SetLocalPosition(localTransform.GetTranslation());
}
m_localTransform = localTransform;
}
void TranslationManipulators::SetLocalPosition(const AZ::Vector3& localPosition)
void TranslationManipulators::SetLocalPositionImpl(const AZ::Vector3& localPosition)
{
for (AZStd::shared_ptr<LinearManipulator>& manipulator : m_linearManipulators)
{
@@ -173,13 +172,11 @@ namespace AzToolsFramework
if (m_surfaceManipulator)
{
m_surfaceManipulator->SetPosition(localPosition);
m_surfaceManipulator->SetLocalPosition(localPosition);
}
m_localTransform.SetTranslation(localPosition);
}
void TranslationManipulators::SetLocalOrientation(const AZ::Quaternion& localOrientation)
void TranslationManipulators::SetLocalOrientationImpl(const AZ::Quaternion& localOrientation)
{
for (AZStd::shared_ptr<LinearManipulator>& manipulator : m_linearManipulators)
{
@@ -190,12 +187,9 @@ namespace AzToolsFramework
{
manipulator->SetLocalOrientation(localOrientation);
}
m_localTransform = AZ::Transform::CreateFromQuaternionAndTranslation(
localOrientation, m_localTransform.GetTranslation());
}
void TranslationManipulators::SetSpace(const AZ::Transform& worldFromLocal)
void TranslationManipulators::SetSpaceImpl(const AZ::Transform& worldFromLocal)
{
for (AZStd::shared_ptr<LinearManipulator>& manipulator : m_linearManipulators)
{
@@ -211,8 +205,24 @@ namespace AzToolsFramework
{
m_surfaceManipulator->SetSpace(worldFromLocal);
}
}
m_space = worldFromLocal;
void TranslationManipulators::SetNonUniformScaleImpl(const AZ::Vector3& nonUniformScale)
{
for (AZStd::shared_ptr<LinearManipulator>& manipulator : m_linearManipulators)
{
manipulator->SetNonUniformScale(nonUniformScale);
}
for (AZStd::shared_ptr<PlanarManipulator>& manipulator : m_planarManipulators)
{
manipulator->SetNonUniformScale(nonUniformScale);
}
if (m_surfaceManipulator)
{
m_surfaceManipulator->SetNonUniformScale(nonUniformScale);
}
}
void TranslationManipulators::SetAxes(
@@ -35,7 +35,7 @@ namespace AzToolsFramework
Three
};
TranslationManipulators(Dimensions dimensions, const AZ::Transform& worldFromLocal);
TranslationManipulators(Dimensions dimensions, const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale);
void InstallLinearManipulatorMouseDownCallback(const LinearManipulator::MouseActionCallback& onMouseDownCallback);
void InstallLinearManipulatorMouseMoveCallback(const LinearManipulator::MouseActionCallback& onMouseMoveCallback);
@@ -49,10 +49,11 @@ namespace AzToolsFramework
void InstallSurfaceManipulatorMouseMoveCallback(const SurfaceManipulator::MouseActionCallback& onMouseMoveCallback);
void InstallSurfaceManipulatorMouseUpCallback(const SurfaceManipulator::MouseActionCallback& onMouseUpCallback);
void SetSpace(const AZ::Transform& worldFromLocal) override;
void SetLocalTransform(const AZ::Transform& localTransform) override;
void SetLocalPosition(const AZ::Vector3& localPosition) override;
void SetLocalOrientation(const AZ::Quaternion& localOrientation) override;
void SetSpaceImpl(const AZ::Transform& worldFromLocal) override;
void SetLocalTransformImpl(const AZ::Transform& localTransform) override;
void SetLocalPositionImpl(const AZ::Vector3& localPosition) override;
void SetLocalOrientationImpl(const AZ::Quaternion& localOrientation) override;
void SetNonUniformScaleImpl(const AZ::Vector3& nonUniformScale) override;
void SetAxes(
const AZ::Vector3& axis1, const AZ::Vector3& axis2,
@@ -91,8 +92,8 @@ namespace AzToolsFramework
{
explicit IndexedTranslationManipulator(
TranslationManipulators::Dimensions dimensions, AZ::u64 vertIndex,
const Vertex& position, const AZ::Transform& worldFromLocal)
: m_manipulator(dimensions, worldFromLocal)
const Vertex& position, const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale)
: m_manipulator(dimensions, worldFromLocal, nonUniformScale)
{
m_vertices.push_back({ position, Vertex::CreateZero(), vertIndex });
}
@@ -128,4 +129,4 @@ namespace AzToolsFramework
void ConfigureTranslationManipulatorAppearance2d(
TranslationManipulators* translationManipulators);
} // namespace AzToolsFramework
} // namespace AzToolsFramework
@@ -12,7 +12,11 @@
#include <AzCore/Component/Entity.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/Instance/InstanceEntityMapperInterface.h>
#include <AzToolsFramework/Prefab/Instance/TemplateInstanceMapperInterface.h>
@@ -36,6 +40,10 @@ namespace AzToolsFramework
"Template Instance Mapper Interface could not be found. "
"It is a requirement for the Prefab Instance class. "
"Check that it is being correctly initialized.");
m_containerEntity = AZStd::make_unique<AZ::Entity>();
EntityAlias containerEntityAlias = GenerateEntityAlias();
RegisterEntity(m_containerEntity->GetId(), containerEntityAlias);
}
Instance::~Instance()
@@ -63,6 +71,7 @@ namespace AzToolsFramework
if (serialize)
{
serialize->Class<Instance>()
->Field("m_containerEntity", &Instance::m_containerEntity)
->Field("m_entities", &Instance::m_entities)
->Field("m_nestedInstances", &Instance::m_nestedInstances)
->Field("m_templateSourcePath", &Instance::m_templateSourcePath)
@@ -120,6 +129,12 @@ namespace AzToolsFramework
void Instance::SetTemplateSourcePath(AZStd::string sourcePath)
{
m_templateSourcePath = AZStd::move(sourcePath);
AZStd::string filename;
if (AZ::StringFunc::Path::GetFileName(m_templateSourcePath.c_str(), filename))
{
m_containerEntity->SetName(filename);
}
}
bool Instance::AddEntity(AZ::Entity& entity)
@@ -167,6 +182,60 @@ namespace AzToolsFramework
return removedEntity;
}
void Instance::DetachNestedEntities(const AZStd::function<void(AZStd::unique_ptr<AZ::Entity>)>& callback)
{
DetachEntities(callback);
for (const auto& [instanceAlias, instance] : m_nestedInstances)
{
instance->DetachNestedEntities(callback);
}
}
void Instance::DetachEntities(const AZStd::function<void(AZStd::unique_ptr<AZ::Entity>)>& callback)
{
for (auto&& [entityAlias, entity] : m_entities)
{
m_instanceEntityMapper->UnregisterEntity(entity->GetId());
m_templateToInstanceEntityIdMap.erase(entityAlias);
m_instanceToTemplateEntityIdMap.erase(entity->GetId());
callback(AZStd::move(entity));
}
m_entities.clear();
}
void Instance::RemoveNestedEntities(
const AZStd::function<bool(const AZStd::unique_ptr<AZ::Entity>&)>& filter)
{
RemoveEntities(filter);
for (const auto& [instanceAlias, instance] : m_nestedInstances)
{
instance->RemoveNestedEntities(filter);
}
}
void Instance::RemoveEntities(
const AZStd::function<bool(const AZStd::unique_ptr<AZ::Entity>&)>& filter)
{
AZStd::erase_if(m_entities,
[this, &filter](const auto& item)
{
const auto& [entityAlias, entity] = item;
const bool shouldRemove = filter(entity);
if (shouldRemove)
{
m_instanceEntityMapper->UnregisterEntity(entity->GetId());
m_templateToInstanceEntityIdMap.erase(entityAlias);
m_instanceToTemplateEntityIdMap.erase(entity->GetId());
}
return shouldRemove;
}
);
}
void Instance::ClearEntities()
{
for (const auto&[entityAlias, entity] : m_entities)
@@ -209,20 +278,14 @@ namespace AzToolsFramework
return true;
}
InstanceOptionalReference Instance::AddInstance(AZStd::unique_ptr<Instance> instance)
Instance& Instance::AddInstance(AZStd::unique_ptr<Instance> instance)
{
InstanceAlias newInstanceAlias = GenerateInstanceAlias();
const auto [it, added] = m_nestedInstances.emplace(AZStd::make_pair(newInstanceAlias, AZStd::move(instance)));
if (added)
{
it->second->m_parent = this;
it->second->m_alias = newInstanceAlias;
return *it->second;
}
return AZStd::nullopt;
AZ_Assert(instance.get(), "instance argument is nullptr");
AZ_Assert(m_nestedInstances.find(newInstanceAlias) == m_nestedInstances.end(), "InstanceAlias' unique id collision, this should never happen.");
instance->m_parent = this;
instance->m_alias = newInstanceAlias;
return *(m_nestedInstances[newInstanceAlias] = std::move(instance));
}
AZStd::unique_ptr<Instance> Instance::DetachNestedInstance(const InstanceAlias& instanceAlias)
@@ -252,19 +315,19 @@ namespace AzToolsFramework
return entityAliases;
}
void Instance::GetNestedEntityIds(const AZStd::function<bool(const AZ::EntityId&)>& callback)
void Instance::GetNestedEntityIds(const AZStd::function<bool(AZ::EntityId)>& callback)
{
GetEntityIds(callback);
for (const auto&[instanceAlias, instance] : m_nestedInstances)
for (auto&&[instanceAlias, instance] : m_nestedInstances)
{
instance->GetNestedEntityIds(callback);
}
}
void Instance::GetEntityIds(const AZStd::function<bool(const AZ::EntityId&)>& callback)
void Instance::GetEntityIds(const AZStd::function<bool(AZ::EntityId)>& callback)
{
for (const auto&[entityAlias, entityId] : m_templateToInstanceEntityIdMap)
for (auto&&[entityAlias, entityId] : m_templateToInstanceEntityIdMap)
{
if (!callback(entityId))
{
@@ -273,6 +336,53 @@ namespace AzToolsFramework
}
}
void Instance::GetConstNestedEntities(const AZStd::function<bool(const AZ::Entity&)>& callback)
{
GetConstEntities(callback);
for (const auto& [instanceAlias, instance] : m_nestedInstances)
{
instance->GetConstNestedEntities(callback);
}
}
void Instance::GetConstEntities(const AZStd::function<bool(const AZ::Entity&)>& callback)
{
for (const auto& [entityAlias, entity] : m_entities)
{
if (!entity)
{
continue;
}
if (!callback(*entity))
{
break;
}
}
}
void Instance::GetNestedEntities(const AZStd::function<bool(AZStd::unique_ptr<AZ::Entity>&)>& callback)
{
GetEntities(callback);
for (auto& [instanceAlias, instance] : m_nestedInstances)
{
instance->GetNestedEntities(callback);
}
}
void Instance::GetEntities(const AZStd::function<bool(AZStd::unique_ptr<AZ::Entity>&)>& callback)
{
for (auto& [entityAlias, entity] : m_entities)
{
if (!callback(entity))
{
break;
}
}
}
void Instance::GetEntities(EntityList& entities, bool includeNestedEntities)
{
// Non-recursive traversal of instances
@@ -308,14 +418,14 @@ namespace AzToolsFramework
return AZStd::nullopt;
}
AZStd::optional<AZ::EntityId> Instance::GetEntityId(const EntityAlias& alias)
AZ::EntityId Instance::GetEntityId(const EntityAlias& alias)
{
if (m_templateToInstanceEntityIdMap.count(alias))
{
return m_templateToInstanceEntityIdMap[alias];
}
return AZStd::nullopt;
return AZ::EntityId();
}
AZStd::vector<InstanceAlias> Instance::GetNestedInstanceAliases(TemplateId templateId) const
@@ -430,5 +540,10 @@ namespace AzToolsFramework
{
return &instance == m_parent;
}
AZ::EntityId Instance::GetContainerEntityId() const
{
return m_containerEntity->GetId();
}
}
}
@@ -77,8 +77,10 @@ namespace AzToolsFramework
bool AddEntity(AZ::Entity& entity);
AZStd::unique_ptr<AZ::Entity> DetachEntity(const AZ::EntityId& entityId);
void DetachNestedEntities(const AZStd::function<void(AZStd::unique_ptr<AZ::Entity>)>& callback);
void RemoveNestedEntities(const AZStd::function<bool(const AZStd::unique_ptr<AZ::Entity>&)>& filter);
InstanceOptionalReference AddInstance(AZStd::unique_ptr<Instance> instance);
Instance& AddInstance(AZStd::unique_ptr<Instance> instance);
AZStd::unique_ptr<Instance> DetachNestedInstance(const InstanceAlias& instanceAlias);
/**
@@ -91,9 +93,17 @@ namespace AzToolsFramework
/**
* Gets the ids for the entities in the Instance DOM. Can recursively trace all nested instances.
*/
void GetNestedEntityIds(const AZStd::function<bool(const AZ::EntityId&)>& callback);
void GetNestedEntityIds(const AZStd::function<bool(AZ::EntityId)>& callback);
void GetEntityIds(const AZStd::function<bool(const AZ::EntityId&)>& callback);
void GetEntityIds(const AZStd::function<bool(AZ::EntityId)>& callback);
/**
* Gets the entities in the Instance DOM. Can recursively trace all nested instances.
*/
void GetConstNestedEntities(const AZStd::function<bool(const AZ::Entity&)>& callback);
void GetConstEntities(const AZStd::function<bool(const AZ::Entity&)>& callback);
void GetNestedEntities(const AZStd::function<bool(AZStd::unique_ptr<AZ::Entity>&)>& callback);
void GetEntities(const AZStd::function<bool(AZStd::unique_ptr<AZ::Entity>&)>& callback);
/**
* Gets the alias for a given EnitityId in the Instance DOM.
@@ -105,9 +115,9 @@ namespace AzToolsFramework
/**
* Gets the id for a given EnitityAlias in the Instance DOM.
*
* @return entityId via optional
* @return entityId, invalid ID if not found
*/
AZStd::optional<AZ::EntityId> GetEntityId(const EntityAlias& alias);
AZ::EntityId GetEntityId(const EntityAlias& alias);
/**
@@ -146,6 +156,8 @@ namespace AzToolsFramework
bool IsParentInstance(const Instance& instance) const;
AZ::EntityId GetContainerEntityId() const;
protected:
/**
* Gets the entities owned by this instance
@@ -156,6 +168,9 @@ namespace AzToolsFramework
void ClearEntities();
void DetachEntities(const AZStd::function<void(AZStd::unique_ptr<AZ::Entity>)>& callback);
void RemoveEntities(const AZStd::function<bool(const AZStd::unique_ptr<AZ::Entity>&)>& filter);
bool RegisterEntity(const AZ::EntityId& entityId, const EntityAlias& entityAlias);
AZStd::unique_ptr<AZ::Entity> DetachEntity(const EntityAlias& entityAlias);
@@ -172,8 +187,10 @@ namespace AzToolsFramework
// A map of prefab instance pointers that this prefab instance owns.
AliasToInstanceMap m_nestedInstances;
// The entity representing this Instance as a container in the entity hierarchy.
AZStd::unique_ptr<AZ::Entity> m_containerEntity;
// The id of the link that connects the template of this instance to it's source template.
// The id of the link that connects the template of this instance to its source template.
// This is not unique per instance. It's unique per link. It is invalid for instances that aren't nested under other instances.
LinkId m_linkId = InvalidLinkId;
@@ -42,26 +42,21 @@ namespace AzToolsFramework
inputAlias = EntityAlias(inputValue.GetString(), inputValue.GetStringLength());
}
if(!inputAlias.empty())
if (!inputAlias.empty())
{
auto entityIdMapIter = m_loadingInstance->m_templateToInstanceEntityIdMap.find(inputAlias);
if (entityIdMapIter != m_loadingInstance->m_templateToInstanceEntityIdMap.end())
if (m_isEntityReference)
{
mappedValue = entityIdMapIter->second;
m_unresolvedEntityAliases[m_loadingInstance].emplace_back(inputAlias, &outputValue);
}
else
{
if (inputAlias[0] != ReferencePathDelimiter)
mappedValue = AZ::Entity::MakeId();
if (m_loadingInstance->RegisterEntity(mappedValue, inputAlias))
{
mappedValue = AZ::Entity::MakeId();
m_resolvedEntityAliases[m_loadingInstance].emplace_back(inputAlias, mappedValue);
}
else
{
mappedValue = ResolveEntityReferencePath(inputAlias);
}
if (!m_loadingInstance->RegisterEntity(mappedValue, inputAlias))
{
mappedValue = AZ::EntityId(AZ::EntityId::InvalidEntityId);
context.Report(JSR::Tasks::ReadField, JSR::Outcomes::DefaultsUsed,
@@ -92,15 +87,27 @@ namespace AzToolsFramework
EntityAlias mappedValue;
if (inputValue.IsValid())
{
auto idMapIter = m_storingInstance->m_instanceToTemplateEntityIdMap.find(inputValue);
if (idMapIter != m_storingInstance->m_instanceToTemplateEntityIdMap.end())
if (m_isEntityReference)
{
mappedValue = idMapIter->second;
mappedValue = ResolveReferenceId(inputValue);
}
else
{
mappedValue = ResolveEntityId(inputValue);
auto idMapIter = m_storingInstance->m_instanceToTemplateEntityIdMap.find(inputValue);
if (idMapIter != m_storingInstance->m_instanceToTemplateEntityIdMap.end())
{
mappedValue = idMapIter->second;
}
else
{
AZStd::string defaultErrorMessage =
"Entity with Id " + inputValue.ToString() +
" could not be found within its owning instance. Defaulting to invalid Id for Store.";
AZ_Assert(false, defaultErrorMessage.c_str());
context.Report(JSR::Tasks::WriteValue, JSR::Outcomes::DefaultsUsed, defaultErrorMessage);
}
}
}
@@ -112,6 +119,7 @@ namespace AzToolsFramework
void InstanceEntityIdMapper::SetStoringInstance(const Instance& storingInstance)
{
m_storingInstance = &storingInstance;
GetAbsoluteInstanceAliasPath(m_storingInstance, m_instanceAbsolutePath);
}
void InstanceEntityIdMapper::SetLoadingInstance(Instance& loadingInstance)
@@ -119,77 +127,87 @@ namespace AzToolsFramework
m_loadingInstance = &loadingInstance;
}
AZ::EntityId InstanceEntityIdMapper::ResolveEntityReferencePath(const AZStd::string& entityIdReferencePath)
void InstanceEntityIdMapper::FixUpUnresolvedEntityReferences()
{
// If we can't resolve the path treat the entire path as one alias.
// Assigning a random id instead of an invalid id will save off a unique mapping.
// Allowing the ability to store the instance back to this full alias again.
// This prevents the replacement of the path with an empty alias and avoids data loss.
AZ::EntityId resolvedId = AZ::Entity::MakeId();
AZStd::string_view referencePathView = entityIdReferencePath;
// Trim the starting '/' from the path view as tokenizing it would result in an empty token
referencePathView.remove_prefix(1);
AZStd::optional<AZStd::string_view> currentPathToken = AZ::StringFunc::TokenizeNext(referencePathView, ReferencePathDelimiter);
const Instance* currentInstance = m_loadingInstance;
// Walk down the instance hierarchy using the Alias path
while(!referencePathView.empty() && currentPathToken.has_value())
// Nothing to resolve
if (m_unresolvedEntityAliases.empty())
{
auto aliasedInstanceIter = currentInstance->m_nestedInstances.find(currentPathToken.value());
if (aliasedInstanceIter != currentInstance->m_nestedInstances.end())
{
AZ_Assert(aliasedInstanceIter->second,
"Prefab - EntityIdMapper: An instance of %s contained a null instance using alias %.*s",
currentInstance->GetTemplateSourcePath().c_str(),
aznumeric_cast<int>(currentPathToken.value().size()), currentPathToken.value().data());
return;
}
currentInstance = aliasedInstanceIter->second.get();
// Calculate the absolute path to each instance based on its position in the hierarchy
// We'll use this to generate the absolute paths of resolved and unresolved aliases
AZStd::unordered_map<Instance*, AliasPath> absoluteInstanceAliasPaths;
absoluteInstanceAliasPaths.reserve(m_resolvedEntityAliases.bucket_count());
// Also calculate the absolute path to each resolved entity based on its position in the hierarchy
AZStd::unordered_map<AliasPath, AZ::EntityId> resolvedAbsoluteEntityAliasPaths;
for (auto& [instance, resolvedAliasIdList] : m_resolvedEntityAliases)
{
AliasPath absoluteInstancePath;
GetAbsoluteInstanceAliasPath(instance, absoluteInstancePath);
absoluteInstanceAliasPaths.emplace(instance, absoluteInstancePath);
for (auto& [entityAlias, entityId] : resolvedAliasIdList)
{
resolvedAbsoluteEntityAliasPaths.emplace(absoluteInstancePath / entityAlias, entityId);
}
}
// Using the absolute paths of the instances containing the unresolved aliases
// Attempt to find a match within the resolved paths
// A match will allow us to update the unresolved reference id to match the id it's referencing
for (auto& [instance, unresolvedAliasIdList] : m_unresolvedEntityAliases)
{
auto findResolvedInstance = absoluteInstanceAliasPaths.find(instance);
if (findResolvedInstance != absoluteInstanceAliasPaths.end())
{
AliasPath& absoluteInstancePath = findResolvedInstance->second;
for (auto& [entityAlias, entityPointer] : unresolvedAliasIdList)
{
AliasPath absoluteEntityReferencePath = (absoluteInstancePath / entityAlias).LexicallyNormal();
auto foundResolvedPath =
resolvedAbsoluteEntityAliasPaths.find(absoluteEntityReferencePath);
if (foundResolvedPath != resolvedAbsoluteEntityAliasPaths.end())
{
*entityPointer = foundResolvedPath->second;
}
else
{
AZ_Warning("Prefabs", false,
"Unable to resolve entity reference alias path [%s] while loading Prefab instance. "
"The reference was likely made on a parent or sibling prefab without the use of an override. "
"Defaulting the reference to an invalid EntityId.",
absoluteEntityReferencePath.String().c_str());
}
}
}
else
{
// If there's ever a path mismatch then there's no valid entity to reference
return resolvedId;
AZ_Assert(false, "Prefabs - "
"Attempted to resolve entity alias path(s) but the instance owning the unresolved reference has no entities. "
"An Entity Reference can only come from an entity/component property.");
}
currentPathToken = AZ::StringFunc::TokenizeNext(referencePathView, ReferencePathDelimiter);
}
// If we ever recieved an invalid token then our path was empty or we walked too far
if (!currentPathToken.has_value())
{
return resolvedId;
}
// Our current alias should be an entity in our current instance
auto aliasedEntityIter = currentInstance->m_entities.find(currentPathToken.value());
if (aliasedEntityIter != currentInstance->m_entities.end())
{
const AZStd::unique_ptr<AZ::Entity>& aliasedEntity = aliasedEntityIter->second;
AZ_Assert(aliasedEntity,
"Prefab - EntityIdMapper: An instance of %s contained a null entity using alias %s",
currentInstance->GetTemplateSourcePath().c_str(),
aznumeric_cast<int>(currentPathToken.value().size()), currentPathToken.value().data());
resolvedId = aliasedEntity->GetId();
}
return resolvedId;
return;
}
EntityAlias InstanceEntityIdMapper::ResolveEntityId(const AZ::EntityId& entityId)
EntityAlias InstanceEntityIdMapper::ResolveReferenceId(const AZ::EntityId& entityId)
{
// Acquire the owning instance of our entity
InstanceOptionalReference currentInstanceOptionalReference = m_storingInstance->m_instanceEntityMapper->FindOwningInstance(entityId);
InstanceOptionalReference owningInstanceReference = m_storingInstance->m_instanceEntityMapper->FindOwningInstance(entityId);
// Start with an empty alias to build out our reference path
// If we can't resolve this id we'll return a random new alias instead of a reference path
EntityAlias resolvedAlias;
if (!currentInstanceOptionalReference)
AliasPath relativeEntityAliasPath;
if (!owningInstanceReference)
{
AZ_Assert(false,
"Prefab - EntityIdMapper: Entity with Id %s has no registered owning instance",
@@ -197,41 +215,29 @@ namespace AzToolsFramework
return Instance::GenerateEntityAlias();
}
Instance* currentInstance = &(currentInstanceOptionalReference->get());
// This entity should have an alias in its owning instance
// If not
auto entityAliasIter = currentInstance->m_instanceToTemplateEntityIdMap.find(entityId);
if (entityAliasIter == currentInstance->m_instanceToTemplateEntityIdMap.end())
Instance* owningInstance = &(owningInstanceReference->get());
// Build out the absolute path of this alias
// so we can compare it to the absolute path of our currently scoped instance
GetAbsoluteInstanceAliasPath(owningInstance, relativeEntityAliasPath);
relativeEntityAliasPath.Append(owningInstance->GetEntityAlias(entityId)->get());
return relativeEntityAliasPath.LexicallyRelative(m_instanceAbsolutePath).String();
}
void InstanceEntityIdMapper::GetAbsoluteInstanceAliasPath(const Instance* instance, AliasPath& aliasPathResult)
{
// Reset the path using our preferred seperator
aliasPathResult = AliasPath(m_aliasPathSeperator);
const Instance* currentInstance = instance;
// If no parent instance we are a root instance and our absolute path is empty
while (currentInstance->m_parent)
{
AZ_Assert(false,
"Prefab - EntityIdMapper: Instance of Prefab %s was registered to own entity with Id %s. "
"However the entity has no alias within the instance",
currentInstance->GetTemplateSourcePath().c_str(), entityId.ToString().c_str());
return Instance::GenerateEntityAlias();
}
// Start off the reference path with /EntityAlias
resolvedAlias = ReferencePathDelimiter + entityAliasIter->second;
// Walk up the instance hierarchy
while (currentInstance)
{
if (currentInstance == m_storingInstance)
{
// Path is resolved if we intersect with the instance resolving this id
return resolvedAlias;
}
// Continue building out the reference path
// /InstanceAlias/InstanceAlias/EntityAlias
resolvedAlias = ReferencePathDelimiter + currentInstance->m_alias + resolvedAlias;
aliasPathResult.Append(currentInstance->m_alias);
currentInstance = currentInstance->m_parent;
}
// If we hit a null instance we failed to resolve the id
return Instance::GenerateEntityAlias();
}
}
}
@@ -13,13 +13,15 @@
#pragma once
#include <AzCore/Component/EntityIdSerializer.h>
#include <AzCore/IO/Path/Path.h>
namespace AzToolsFramework
{
namespace Prefab
{
class Instance;
class InstanceEntityIdMapper
class InstanceEntityIdMapper final
: public AZ::JsonEntityIdSerializer::JsonEntityIdMapper
{
public:
@@ -31,14 +33,33 @@ namespace AzToolsFramework
void SetStoringInstance(const Instance& storingInstance);
void SetLoadingInstance(Instance& loadingInstance);
/**
* Fixes up any unresolved entity references by assigning them to the id value of the entity it references.
* This is done by computing the absolute alias paths of all EntityIds and EntityId references
* then matching the references to the id values they reference.
* During a load instance and alias information is stored to build out these paths,
* but will be incomplete until the whole Load call is finished.
* Calling this after Load and the mapper have finished will give complete information
* on all entities and references discovered in the load
*/
void FixUpUnresolvedEntityReferences();
private:
AZ::EntityId ResolveEntityReferencePath(const AZStd::string& entityIdReferencePath);
EntityAlias ResolveEntityId(const AZ::EntityId& entityId);
using AliasPath = AZ::IO::Path;
EntityAlias ResolveReferenceId(const AZ::EntityId& entityId);
void GetAbsoluteInstanceAliasPath(const Instance* instance, AliasPath& aliasPathResult);
AliasPath m_instanceAbsolutePath;
const Instance* m_storingInstance = nullptr;
Instance* m_loadingInstance = nullptr;
inline static constexpr char ReferencePathDelimiter = '/';
static constexpr const char m_aliasPathSeperator = '/';
AZStd::unordered_map<Instance*, AZStd::vector<AZStd::pair<EntityAlias, AZ::EntityId>>> m_resolvedEntityAliases;
AZStd::unordered_map<Instance*, AZStd::vector<AZStd::pair<EntityAlias, AZ::EntityId*>>> m_unresolvedEntityAliases;
};
}
}
@@ -35,6 +35,7 @@ namespace AzToolsFramework
protected:
// Only the Instance class is allowed to register and unregister entities
friend class Instance;
friend class JsonInstanceSerializer;
virtual bool RegisterEntityToInstance(const AZ::EntityId& entityId, Prefab::Instance& instance) = 0;
virtual bool UnregisterEntity(const AZ::EntityId& entityId) = 0;
@@ -15,6 +15,7 @@
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/Instance/InstanceSerializer.h>
#include <AzToolsFramework/Prefab/Instance/InstanceEntityIdMapper.h>
#include <AzToolsFramework/Prefab/Instance/InstanceEntityMapperInterface.h>
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
namespace AzToolsFramework
@@ -50,6 +51,14 @@ namespace AzToolsFramework
result = ContinueStoringToJsonObjectField(outputValue, "Source", sourcePath, defaultSourcePath, azrtti_typeid<AZStd::string>(), context);
}
{
AZ::ScopedContextPath subPathContainerEntity(context, "m_containerEntity");
JSR::ResultCode resultContainerEntity = ContinueStoringToJsonObjectField(outputValue, "ContainerEntity",
&instance->m_containerEntity, nullptr, azrtti_typeid<decltype(instance->m_containerEntity)>(), context);
result.Combine(resultContainerEntity);
}
{
AZ::ScopedContextPath subPathEntities(context, "m_entities");
@@ -94,15 +103,8 @@ namespace AzToolsFramework
InstanceEntityIdMapper** idMapper = context.GetMetadata().Find<InstanceEntityIdMapper*>();
if (idMapper && *idMapper)
{
(*idMapper)->SetLoadingInstance(*instance);
}
JSR::ResultCode result(JSR::Tasks::ReadField);
{
AZ::ScopedContextPath subPathSource(context, "Source");
JSR::ResultCode sourceLoadResult =
ContinueLoadingFromJsonObjectField(&instance->m_templateSourcePath, azrtti_typeid<AZStd::string>(), inputValue, "Source", context);
@@ -123,8 +125,6 @@ namespace AzToolsFramework
}
{
AZ::ScopedContextPath subPathInstances(context, "Instances");
// An already filled instance should be cleared if inputValue's Instances member is empty
// The Json serializer will not do this by default as it will not attempt to load a missing member
if (!instance->m_nestedInstances.empty() && !inputValue.HasMember("Instances"))
@@ -146,19 +146,32 @@ namespace AzToolsFramework
result.Combine(instanceResult);
}
// An already filled instance should be cleared if inputValue's Entities member is empty
// The Json serializer will not do this by default as it will not attempt to load a missing member
instance->ClearEntities();
if (instance->m_containerEntity)
{
AZ::ScopedContextPath subPathEntities(context, "Entities");
instance->m_instanceEntityMapper->UnregisterEntity(instance->m_containerEntity->GetId());
}
// An already filled instance should be cleared if inputValue's Entities member is empty
// The Json serializer will not do this by default as it will not attempt to load a missing member
instance->ClearEntities();
if (idMapper && *idMapper)
{
(*idMapper)->SetLoadingInstance(*instance);
}
{
JSR::ResultCode containerEntityResult = ContinueLoadingFromJsonObjectField(
&instance->m_containerEntity, azrtti_typeid<decltype(instance->m_containerEntity)>(), inputValue, "ContainerEntity", context);
result.Combine(containerEntityResult);
}
{
result.Combine(ContinueLoadingFromJsonObjectField(&instance->m_entities, azrtti_typeid<Instance::AliasToEntityMap>(), inputValue, "Entities", context));
}
{
AZ::ScopedContextPath subPathEntities(context, "LinkId");
result.Combine(ContinueLoadingFromJsonObjectField(&instance->m_linkId, azrtti_typeid<LinkId>(), inputValue, "LinkId", context));
}
@@ -30,7 +30,7 @@ namespace AzToolsFramework
virtual bool GenerateDomForEntity(PrefabDom& generatedEntityDom, const AZ::Entity& entity) = 0;
//! Generates a prefabdom for the instance in its current state and places the result in generatedDom
virtual bool GenerateDomForInstance(PrefabDom& generatedInstanceDom, const Prefab::Instance& instance) = 0;
virtual bool GenerateDomForInstance(PrefabDom& generatedInstanceDom, const Instance& instance) = 0;
//! Generates a patch using serialization system and places the result in generatedPatch
virtual bool GeneratePatch(PrefabDom& generatedPatch, const PrefabDom& initialState, const PrefabDom& modifiedState) = 0;
@@ -42,6 +42,8 @@ namespace AzToolsFramework
//! Updates the affected template for a given entityId using the providedPatch
virtual void PatchEntityInTemplate(PrefabDomValue& providedPatch, const AZ::EntityId& entityId) = 0;
virtual void PatchEntityInTemplate(PrefabDomValue& providedPatch, const EntityAlias& entityAlias, const TemplateId& templateId) = 0;
//! Updates the template links (updating instances) for the given templateId using the providedPatch
virtual void PatchTemplate(PrefabDomValue& providedPatch, const AzToolsFramework::Prefab::TemplateId& templateId) = 0;
@@ -124,44 +124,45 @@ namespace AzToolsFramework
PrefabDom& templateDomReference = m_prefabSystemComponentInterface->FindTemplateDom(templateId);
PrefabDom templateDom;
templateDom.CopyFrom(templateDomReference, templateDom.GetAllocator());
PatchEntityInTemplate(providedPatch, entityAlias.value(), templateId);
}
void InstanceToTemplatePropagator::PatchEntityInTemplate(PrefabDomValue& providedPatch, const EntityAlias& entityAlias, const TemplateId& templateId)
{
PrefabDom& templateDomReference = m_prefabSystemComponentInterface->FindTemplateDom(templateId);
//query into the template dom for the alias
PrefabDomValueReference entityList = PrefabDomUtils::FindPrefabDomValue(templateDom, "Entities");
PrefabDomValueReference entityList = PrefabDomUtils::FindPrefabDomValue(templateDomReference, PrefabDomUtils::EntitiesName);
PrefabDomValueReference entity = PrefabDomUtils::FindPrefabDomValue(entityList->get(), entityAlias->c_str());
PrefabDomValueReference entity = PrefabDomUtils::FindPrefabDomValue(entityList->get(), entityAlias.c_str());
AZ_Error("Prefab", entity != AZStd::nullopt, "Failed to aquire entity value reference")
//apply patch to section
AZ::JsonSerializationResult::ResultCode result = AZ::JsonSerialization::ApplyPatch(entity->get(),
templateDom.GetAllocator(), providedPatch, AZ::JsonMergeApproach::JsonPatch);
templateDomReference.GetAllocator(), providedPatch, AZ::JsonMergeApproach::JsonPatch);
AZ_Error("Prefab", result.GetOutcome() == AZ::JsonSerializationResult::Outcomes::Success,
"Patch was not successfully applied")
//update the Dom and trigger propogation
m_prefabSystemComponentInterface->UpdatePrefabTemplate(templateId, templateDom);
m_prefabSystemComponentInterface->PropagateTemplateChanges(templateId);
}
void InstanceToTemplatePropagator::PatchTemplate(PrefabDomValue& providedPatch, const TemplateId& templateId)
{
const PrefabDom& templateDomReference = m_prefabSystemComponentInterface->FindTemplateDom(templateId);
PrefabDom templateDom;
templateDom.CopyFrom(templateDomReference, templateDom.GetAllocator());
PrefabDom& templateDomReference = m_prefabSystemComponentInterface->FindTemplateDom(templateId);
//apply patch to template
AZ::JsonSerializationResult::ResultCode result = AZ::JsonSerialization::ApplyPatch(templateDom,
templateDom.GetAllocator(), providedPatch, AZ::JsonMergeApproach::JsonPatch);
AZ::JsonSerializationResult::ResultCode result = AZ::JsonSerialization::ApplyPatch(templateDomReference,
templateDomReference.GetAllocator(), providedPatch, AZ::JsonMergeApproach::JsonPatch);
AZ_Error("Prefab", result.GetOutcome() == AZ::JsonSerializationResult::Outcomes::Success,
"Patch was not successfully applied")
"Patch was not successfully applied");
//update the Dom and trigger propogation
if (result.GetOutcome() == AZ::JsonSerializationResult::Outcomes::Success)
{
m_prefabSystemComponentInterface->UpdatePrefabTemplate(templateId, templateDom);
m_prefabSystemComponentInterface->PropagateTemplateChanges(templateId);
}
}
@@ -32,6 +32,7 @@ namespace AzToolsFramework
bool GeneratePatchForLink(PrefabDom& generatedPatch, const PrefabDom& initialState,
const PrefabDom& modifiedState, LinkId linkId) override;
void PatchEntityInTemplate(PrefabDomValue& providedPatch, const AZ::EntityId& entityId) override;
void PatchEntityInTemplate(PrefabDomValue& providedPatch, const EntityAlias& entityAlias, const TemplateId& templateId) override;
InstanceOptionalReference GetTopMostInstanceInHierarchy(AZ::EntityId entityId);
@@ -14,6 +14,7 @@
#include <AzCore/JSON/document.h>
#include <AzCore/JSON/pointer.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/optional.h>
namespace AzToolsFramework
@@ -23,6 +24,7 @@ namespace AzToolsFramework
using PrefabDom = rapidjson::Document;
using PrefabDomValue = rapidjson::Value;
using PrefabDomPath = rapidjson::Pointer;
using PrefabDomList = AZStd::vector<PrefabDom>;
using PrefabDomValueReference = AZStd::optional<AZStd::reference_wrapper<PrefabDomValue>>;
using PrefabDomValueConstReference = AZStd::optional<AZStd::reference_wrapper<const PrefabDomValue>>;
@@ -93,6 +93,8 @@ namespace AzToolsFramework
return false;
}
entityIdMapper.FixUpUnresolvedEntityReferences();
return true;
}
@@ -26,6 +26,7 @@ namespace AzToolsFramework
inline static const char* PatchesName = "Patches";
inline static const char* SourceName = "Source";
inline static const char* LinkIdName = "LinkId";
inline static const char* EntitiesName = "Entities";
/**
* Find Prefab value from given parent value and target value's name.
@@ -55,7 +55,7 @@ namespace AzToolsFramework
{
AZ_Error("Prefab", false,
"PrefabLoader::LoadTemplate - "
"Prefab fie %s has been detected to directly or indirectly depend on itself."
"Prefab file %s has been detected to directly or indirectly depend on itself."
"Terminating any further loading of this branch of its prefab hierarchy.",
filePath.c_str());
return InvalidTemplateId;
@@ -0,0 +1,372 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzToolsFramework/Prefab/PrefabPublicHandler.h>
#include <AzCore/Component/TransformBus.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
#include <AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/Instance/InstanceEntityMapperInterface.h>
#include <AzToolsFramework/Prefab/PrefabLoaderInterface.h>
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
#include <AzToolsFramework/UI/EditorEntityUi/EditorEntityUiInterface.h>
namespace AzToolsFramework
{
namespace Prefab
{
PrefabPublicHandler::PrefabPublicHandler()
{
m_instanceEntityMapperInterface = AZ::Interface<InstanceEntityMapperInterface>::Get();
AZ_Assert(m_instanceEntityMapperInterface, "PrefabPublicHandler - Could not retrieve instance of InstanceEntityMapperInterface");
AZ::Interface<PrefabPublicInterface>::Register(this);
}
PrefabPublicHandler::~PrefabPublicHandler()
{
AZ::Interface<PrefabPublicInterface>::Unregister(this);
}
PrefabOperationResult PrefabPublicHandler::CreatePrefab(const AZStd::vector<AZ::EntityId>& entityIds, AZStd::string_view filePath)
{
// Retrieve entityList from entityIds
EntityList inputEntityList;
inputEntityList.reserve(entityIds.size());
for (AZ::EntityId entityId : entityIds)
{
if (entityId.IsValid())
{
inputEntityList.emplace_back(GetEntityById(entityId));
}
}
// Find common root and top level entities
bool entitiesHaveCommonRoot = false;
AZ::EntityId commonRootEntityId;
EntityList topLevelEntities;
AzToolsFramework::ToolsApplicationRequests::Bus::BroadcastResult(
entitiesHaveCommonRoot,
&AzToolsFramework::ToolsApplicationRequests::FindCommonRootInactive,
inputEntityList,
commonRootEntityId,
&topLevelEntities
);
// Bail if entities don't share a common root
if (!entitiesHaveCommonRoot)
{
return AZ::Failure(AZStd::string("Could not create a new prefab out of the entities provided - entities do not share a common root."));
}
AZ::Entity* commonRootEntity = nullptr;
if (commonRootEntityId.IsValid())
{
commonRootEntity = GetEntityById(commonRootEntityId);
}
// Retrieve the owning instance of the common root entity, which will be our new instance's parent instance.
InstanceOptionalReference commonRootEntityOwningInstance = GetCommonRootEntityOwningInstance(commonRootEntityId);
AZ_Assert(commonRootEntityOwningInstance.has_value(), "Failed to create prefab : "
"Couldn't get a valid owning instance for the common root entity of the enities provided");
AZStd::vector<AZ::Entity*> entities;
AZStd::vector<AZStd::unique_ptr<Instance>> instances;
// Retrieve all entities affected and identify Instances
if (!RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonRootEntityOwningInstance->get(), entities, instances))
{
return AZ::Failure(AZStd::string("Could not create a new prefab out of the entities provided - entities do not share a common root."));
}
auto prefabEditorEntityOwnershipInterface = AZ::Interface<PrefabEditorEntityOwnershipInterface>::Get();
if (!prefabEditorEntityOwnershipInterface)
{
return AZ::Failure(AZStd::string("Could not create a new prefab out of the entities provided - internal error "
"(PrefabEditorEntityOwnershipInterface unavailable)."));
}
InstanceOptionalReference instance = prefabEditorEntityOwnershipInterface->CreatePrefab(
entities, AZStd::move(instances), filePath, commonRootEntityOwningInstance->get());
if (!instance)
{
return AZ::Failure(AZStd::string("Could not create a new prefab out of the entities provided - internal error "
"(A null instance is returned)."));
}
auto prefabLoaderInterface = AZ::Interface<PrefabLoaderInterface>::Get();
if (prefabLoaderInterface == nullptr)
{
return AZ::Failure(AZStd::string("Could not create a new prefab out of the entities provided - internal error (PrefabLoaderInterface unavailable)."));
}
AZ::EntityId containerEntityId = instance->get().GetContainerEntityId();
AZ::Vector3 containerEntityTranslation(AZ::Vector3::CreateZero());
AZ::Quaternion containerEntityRotation(AZ::Quaternion::CreateZero());
// Set the transform (translation, rotation) of the container entity
GenerateContainerEntityTransform(topLevelEntities, containerEntityTranslation, containerEntityRotation);
AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetLocalTranslation, containerEntityTranslation);
AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetLocalRotationQuaternion, containerEntityRotation);
// Set container entity to be child of common root
AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetParent, commonRootEntityId);
// Change top level entities to be parented to the container entity
for (AZ::Entity* topLevelEntity : topLevelEntities)
{
AZ::TransformBus::Event(topLevelEntity->GetId(), &AZ::TransformBus::Events::SetParent, containerEntityId);
}
// Register container entity to PrefabUiHandler
auto editorEntityUiInterface = AZ::Interface<EditorEntityUiInterface>::Get();
if (editorEntityUiInterface != nullptr)
{
editorEntityUiInterface->RegisterEntity(containerEntityId, 1);
}
// Save Template
prefabLoaderInterface->SaveTemplate(instance->get().GetTemplateId());
return AZ::Success();
}
PrefabOperationResult PrefabPublicHandler::InstantiatePrefab(AZStd::string_view /*filePath*/, AZ::EntityId /*parent*/, AZ::Vector3 /*position*/)
{
return AZ::Failure(AZStd::string("Prefab - InstantiatePrefab is yet to be implemented."));
}
bool PrefabPublicHandler::IsInstanceContainerEntity(AZ::EntityId entityId)
{
AZ::Entity* entity = GetEntityById(entityId);
InstanceOptionalReference owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(entity->GetId());
return owningInstance && (owningInstance->get().GetContainerEntityId() == entityId);
}
AZ::EntityId PrefabPublicHandler::GetInstanceContainerEntityId(AZ::EntityId entityId)
{
AZ::Entity* entity = GetEntityById(entityId);
InstanceOptionalReference owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(entity->GetId());
if (owningInstance)
{
return owningInstance->get().GetContainerEntityId();
}
return AZ::EntityId();
}
void PrefabPublicHandler::GenerateContainerEntityTransform(const EntityList& topLevelEntities,
AZ::Vector3& translation, AZ::Quaternion& rotation)
{
// --- Multiple top level entities
// Translation is the average of all translations, with the minimum Z value.
// Rotation is set to zero.
if (topLevelEntities.size() > 1)
{
AZ::Vector3 translationSum = AZ::Vector3::CreateZero();
float minZ = AZStd::numeric_limits<float>::max();
int transformCount = 0;
for (AZ::Entity* topLevelEntity : topLevelEntities)
{
if (topLevelEntity != nullptr)
{
AzToolsFramework::Components::TransformComponent* transformComponent =
topLevelEntity->FindComponent<AzToolsFramework::Components::TransformComponent>();
if (transformComponent != nullptr)
{
++transformCount;
auto currentTranslation = transformComponent->GetLocalTranslation();
translationSum += currentTranslation;
minZ = AZ::GetMin<float>(minZ, currentTranslation.GetZ());
}
}
}
if (transformCount > 0)
{
translation = translationSum / aznumeric_cast<float>(transformCount);
translation.SetZ(minZ);
rotation = AZ::Quaternion::CreateZero();
}
}
// --- Single top level entity
// World Translation and Rotation are inherited, unchanged.
else if (topLevelEntities.size() == 1)
{
AZ::Entity* topLevelEntity = topLevelEntities[0];
if (topLevelEntity)
{
AzToolsFramework::Components::TransformComponent* transformComponent =
topLevelEntity->FindComponent<AzToolsFramework::Components::TransformComponent>();
if (transformComponent)
{
translation = transformComponent->GetLocalTranslation();
rotation = transformComponent->GetLocalRotationQuaternion();
}
}
}
}
InstanceOptionalReference PrefabPublicHandler::GetCommonRootEntityOwningInstance(AZ::EntityId entityId)
{
if (entityId.IsValid())
{
return m_instanceEntityMapperInterface->FindOwningInstance(entityId);
}
// If the commonRootEntity is invalid, then the owning instance would be the root prefab instance of the
// PrefabEditorEntityOwnershipService.
auto prefabEditorEntityOwnershipInterface = AZ::Interface<PrefabEditorEntityOwnershipInterface>::Get();
if (!prefabEditorEntityOwnershipInterface)
{
AZ_Assert(false, "Could not get owining instance of common root entity :"
"PrefabEditorEntityOwnershipInterface unavailable.");
}
return prefabEditorEntityOwnershipInterface->GetRootPrefabInstance();
}
Instance* PrefabPublicHandler::GetParentInstance(Instance* instance)
{
auto instanceRef = instance->GetParentInstance();
if (instanceRef != AZStd::nullopt)
{
return &instanceRef->get();
}
return nullptr;
}
Instance* PrefabPublicHandler::GetAncestorOfInstanceThatIsChildOfRoot(const Instance* root, Instance* instance)
{
while (instance != nullptr)
{
Instance* parent = GetParentInstance(instance);
if (parent == root)
{
return instance;
}
instance = parent;
}
return nullptr;
}
bool PrefabPublicHandler::RetrieveAndSortPrefabEntitiesAndInstances(
const EntityList& inputEntities, const Instance& commonRootEntityOwningInstance,
EntityList& outEntities, AZStd::vector<AZStd::unique_ptr<Instance>>& outInstances) const
{
AZStd::queue<AZ::Entity*> entityQueue;
for (auto inputEntity : inputEntities)
{
entityQueue.push(inputEntity);
}
// Support sets to easily identify if we're processing the same entity multiple times.
AZStd::unordered_set<AZ::Entity*> entities;
AZStd::unordered_set<Instance*> instances;
while (!entityQueue.empty())
{
AZ::Entity* entity = entityQueue.front();
entityQueue.pop();
// Get this entity's owning instance.
InstanceOptionalReference owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(entity->GetId());
AZ_Assert(owningInstance.has_value(), "An error occored while retrieving entities and prefab instances : "
"Owning instance of entity with id '%llu' couldn't be found", entity->GetId());
// Check if this entity is owned by the same instance owning the root.
if (&owningInstance->get() == &commonRootEntityOwningInstance)
{
AZStd::unique_ptr<AZ::Entity> detachedEntity = owningInstance->get().DetachEntity(entity->GetId());
// If it's the same instance, we can add this entity to the new instance entities.
int priorEntitiesSize = entities.size();
entities.insert(detachedEntity.release());
// If the size of entities increased, then it wasn't added before.
// In that case, add the children of this entity to the queue.
if (entities.size() > priorEntitiesSize)
{
EntityIdList childrenIds;
EditorEntityInfoRequestBus::EventResult(
childrenIds,
entity->GetId(),
&EditorEntityInfoRequests::GetChildren
);
for (AZ::EntityId childId : childrenIds)
{
AZ::Entity* child = GetEntityById(childId);
entityQueue.push(child);
}
}
}
else
{
// The instances differ, so we should add the instance to the instances set,
// but only if it's a direct descendant of the root instance!
Instance* childInstance = GetAncestorOfInstanceThatIsChildOfRoot(&commonRootEntityOwningInstance, &owningInstance->get());
if (childInstance != nullptr)
{
instances.insert(childInstance);
}
else
{
// This can only happen if one entity does not share the common root!
return false;
}
}
}
// Store results
outEntities.clear();
outEntities.resize(entities.size());
AZStd::copy(entities.begin(), entities.end(), outEntities.begin());
outInstances.clear();
outInstances.reserve(instances.size());
for (Instance* instancePtr : instances)
{
auto parentInstance = instancePtr->GetParentInstance();
if (parentInstance.has_value())
{
auto uniquePtr = parentInstance->get().DetachNestedInstance(instancePtr->GetInstanceAlias());
outInstances.push_back(AZStd::move(uniquePtr));
}
}
return true;
}
}
}
@@ -0,0 +1,60 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Math/Vector3.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
using EntityList = AZStd::vector<AZ::Entity*>;
namespace AzToolsFramework
{
namespace Prefab
{
class Instance;
class InstanceEntityMapperInterface;
class PrefabPublicHandler final
: public PrefabPublicInterface
{
public:
AZ_CLASS_ALLOCATOR(PrefabPublicHandler, AZ::SystemAllocator, 0);
AZ_RTTI(PrefabPublicHandler, "{35802943-6B60-430F-9DED-075E3A576A25}", PrefabPublicInterface);
PrefabPublicHandler();
~PrefabPublicHandler();
PrefabOperationResult CreatePrefab(const AZStd::vector<AZ::EntityId>& entityIds, AZStd::string_view filePath) override;
PrefabOperationResult InstantiatePrefab(AZStd::string_view filePath, AZ::EntityId parent, AZ::Vector3 position) override;
bool IsInstanceContainerEntity(AZ::EntityId entityId) override;
AZ::EntityId GetInstanceContainerEntityId(AZ::EntityId entityId) override;
private:
bool RetrieveAndSortPrefabEntitiesAndInstances(const EntityList& inputEntities, const Instance& commonRootEntityOwningInstance,
EntityList& outEntities, AZStd::vector<AZStd::unique_ptr<Instance>>& outInstances) const;
//! Gets the owning instance of a valid commonRootEntity and the root prefab instance for an invalid commonRootEntity.
InstanceOptionalReference GetCommonRootEntityOwningInstance(AZ::EntityId entityId);
static Instance* GetParentInstance(Instance* instance);
static Instance* GetAncestorOfInstanceThatIsChildOfRoot(const Instance* ancestor, Instance* descendant);
static void GenerateContainerEntityTransform(const EntityList& topLevelEntities, AZ::Vector3& translation, AZ::Quaternion& rotation);
InstanceEntityMapperInterface* m_instanceEntityMapperInterface;
};
}
}
@@ -0,0 +1,71 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Interface/Interface.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Serialization/SerializeContext.h>
namespace AzToolsFramework
{
namespace Prefab
{
typedef AZ::Outcome<void, AZStd::string> PrefabOperationResult;
/*!
* PrefabPublicInterface
* Interface to expose Prefab functionality directly to UI and Scripting.
* Functions will correctly call the Undo/Redo system under the hood.
*/
class PrefabPublicInterface
{
public:
AZ_RTTI(PrefabPublicInterface, "{931AAE9D-C775-4818-9070-A2DA69489CBE}");
/**
* Create a prefab out of the entities provided, at the path provided.
* Automatically detects descendants of entities, and discerns between entities and child instances.
* @param entityIds The entities that should form the new prefab (along with their descendants).
* @param filePath The path for the new prefab file.
* @return An outcome object; on failure, it comes with an error message detailing the cause of the error.
*/
virtual PrefabOperationResult CreatePrefab(const AZStd::vector<AZ::EntityId>& entityIds, AZStd::string_view filePath) = 0;
/**
* Instantiate a prefab from a prefab file.
* @param filePath The path to the prefab file to instantiate.
* @param parent The entity the prefab should be a child of in the transform hierarchy.
* @param position The position in world space the prefab should be instantiated in.
* @return An outcome object; on failure, it comes with an error message detailing the cause of the error.
*/
virtual PrefabOperationResult InstantiatePrefab(AZStd::string_view filePath, AZ::EntityId parent, AZ::Vector3 position) = 0;
/**
* Detects if an entity is the container entity for its owning prefab instance.
* @param entityId The entity to query.
* @return True if the entity is the container entity for its owning prefab instance, false otherwise.
*/
virtual bool IsInstanceContainerEntity(AZ::EntityId entityId) = 0;
/**
* Gets the entity id for the instance container of the owning instance.
* @param entityId The id of the entity to query.
* @return The entity id of the instance container owning the queried entity.
*/
virtual AZ::EntityId GetInstanceContainerEntityId(AZ::EntityId entityId) = 0;
};
} // namespace Prefab
} // namespace AzToolsFramework
@@ -17,6 +17,9 @@
#include <AzCore/Serialization/Json/RegistrationContext.h>
#include <AzToolsFramework/Prefab/Instance/InstanceEntityIdMapper.h>
#include <AzToolsFramework/Prefab/Instance/InstanceSerializer.h>
#include <AzToolsFramework/Prefab/Spawnable/EditorInfoRemover.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabCatchmentProcessor.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabConversionPipeline.h>
#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
namespace AzToolsFramework
@@ -46,12 +49,14 @@ namespace AzToolsFramework
void PrefabSystemComponent::Reflect(AZ::ReflectContext* context)
{
Instance::Reflect(context);
AzToolsFramework::Prefab::PrefabConversionUtils::PrefabConversionPipeline::Reflect(context);
AzToolsFramework::Prefab::PrefabConversionUtils::PrefabCatchmentProcessor::Reflect(context);
AzToolsFramework::Prefab::PrefabConversionUtils::EditorInfoRemover::Reflect(context);
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<PrefabSystemComponent, AZ::Component>()
;
serialize->Class<PrefabSystemComponent, AZ::Component>()->Version(1);
}
AZ::JsonRegistrationContext* jsonRegistration = azrtti_cast<AZ::JsonRegistrationContext*>(context);
@@ -62,8 +67,9 @@ namespace AzToolsFramework
}
}
void PrefabSystemComponent::GetProvidedServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& provided)
void PrefabSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC_CE("PrefabSystem"));
}
void PrefabSystemComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required)
@@ -273,7 +279,6 @@ namespace AzToolsFramework
"PrefabSystemComponent::CreateTemplateFromInstance - "
"Attempted to create a prefab template from an instance without a source file path. "
"Unable to proceed.");
return InvalidTemplateId;
}
@@ -411,7 +416,7 @@ namespace AzToolsFramework
{
for (auto linkId : templateToLinkIterator->second)
{
result = RemoveLinkIdFromTargetTemplate(linkId);
result = RemoveLinkFromTargetTemplate(linkId);
AZ_Assert(result,
"Prefab - PrefabSystemComponent::RemoveTemplate - "
"Failed to remove Link with Id '%llu' that depend on the source Template with Id '%llu' on file path '%s'.",
@@ -509,6 +514,101 @@ namespace AzToolsFramework
return newLinkId;
}
LinkId PrefabSystemComponent::CreateLink(
const TemplateId& linkTargetId,
const TemplateId& linkSourceId,
const InstanceAlias& instanceAlias,
const LinkId& linkId)
{
if (linkTargetId == InvalidTemplateId)
{
AZ_Error("Prefab", false, "Invalid Link Target Template Id");
}
TemplateReference targetTemplateRef = FindTemplate(linkTargetId);
if (targetTemplateRef == AZStd::nullopt)
{
AZ_Error("Prefab", false, "Link Target Template not found");
}
if (linkSourceId == InvalidTemplateId)
{
AZ_Error("Prefab", false, "Invalid Link Source Template Id");
}
TemplateReference sourceTemplateRef = FindTemplate(linkSourceId);
if (sourceTemplateRef == AZStd::nullopt)
{
AZ_Error("Prefab", false, "Link Source Template not found");
}
//use an existing link id if provided
LinkId newLinkId = linkId;
if (newLinkId == InvalidLinkId)
{
newLinkId = CreateUniqueLinkId();
}
//setup initial link values
Link newLink(newLinkId);
newLink.SetTargetTemplateId(linkTargetId);
newLink.SetSourceTemplateId(linkSourceId);
newLink.SetInstanceName(instanceAlias.c_str());
//get owner template and add the link
Template& targetTemplate = targetTemplateRef->get();
if (!targetTemplate.AddLink(newLinkId))
{
AZ_Error("Prefab", false, "Failed to add link id '%llu' to '%s'", newLinkId, targetTemplate.GetFilePath().c_str());
}
//insert nested instance alias into the template owner dom
PrefabDom& targetTemplateDom = targetTemplate.GetPrefabDom();
auto memberFound = targetTemplateDom.FindMember(PrefabDomUtils::InstancesName);
PrefabDomValueReference instancesValue;
if (memberFound == targetTemplateDom.MemberEnd())
{
//add the instance alias to the template dom
instancesValue = targetTemplateDom.AddMember(rapidjson::StringRef(PrefabDomUtils::InstancesName),
PrefabDomValue(),
targetTemplateDom.GetAllocator());
//when AddMember returns, it returns the object that the member was added to, not the added
//member itself, so we need to move instancesValue to the correct position for the next insert
memberFound = instancesValue->get().FindMember(PrefabDomUtils::InstancesName);
instancesValue = memberFound->value;
instancesValue->get().SetObject();
}
else
{
instancesValue = memberFound->value;
}
instancesValue->get().AddMember(rapidjson::StringRef(instanceAlias.c_str()), PrefabDomValue(), targetTemplateDom.GetAllocator());
//setup the link dom
Template& sourceTemplate = sourceTemplateRef->get();
newLink.GetLinkDom().SetObject();
newLink.GetLinkDom().AddMember(rapidjson::StringRef(PrefabDomUtils::SourceName),
rapidjson::StringRef(sourceTemplate.GetFilePath().c_str()), newLink.GetLinkDom().GetAllocator());
//update the target template dom to have the proper values for the source template dom
if (!newLink.UpdateTarget())
{
AZ_Error("Prefab", false, "Failed to update link with template information");
}
//add the link to the link maps
m_linkIdMap.emplace(AZStd::make_pair(newLinkId, AZStd::move(newLink)));
m_templateToLinkIdsMap[linkSourceId].emplace(newLinkId);
return newLinkId;
}
void PrefabSystemComponent::RemoveLink(const LinkId& linkId)
{
auto findLinkResult = FindLink(linkId);
@@ -531,13 +631,15 @@ namespace AzToolsFramework
"from TemplateToLinkIdsMap.",
linkId, link.GetSourceTemplateId(), link.GetInstanceName().c_str());
result = RemoveLinkIdFromTargetTemplate(linkId, link);
result = RemoveLinkFromTargetTemplate(linkId, link);
AZ_Assert(result,
"Prefab - PrefabSystemComponent::RemoveLink - "
"Failed to remove Link with Id '%llu' for Instance '%s' of source Template with Id '%llu' "
"from target Template with Id '%llu'.",
linkId, link.GetSourceTemplateId(), link.GetInstanceName().c_str(), link.GetTargetTemplateId());
m_linkIdMap.erase(linkId);
return;
}
@@ -724,7 +826,7 @@ namespace AzToolsFramework
return removed;
}
bool PrefabSystemComponent::RemoveLinkIdFromTargetTemplate(const LinkId& linkId)
bool PrefabSystemComponent::RemoveLinkFromTargetTemplate(const LinkId& linkId)
{
auto findLinkResult = FindLink(linkId);
if (!findLinkResult.has_value())
@@ -733,10 +835,10 @@ namespace AzToolsFramework
}
Link& link = findLinkResult->get();
return RemoveLinkIdFromTargetTemplate(linkId, link);
return RemoveLinkFromTargetTemplate(linkId, link);
}
bool PrefabSystemComponent::RemoveLinkIdFromTargetTemplate(const LinkId& linkId, const Link& link)
bool PrefabSystemComponent::RemoveLinkFromTargetTemplate(const LinkId& linkId, const Link& link)
{
TemplateId targetTemplateId = link.GetTargetTemplateId();
@@ -745,6 +847,17 @@ namespace AzToolsFramework
if (templateIterator != m_templateIdMap.end())
{
removed = templateIterator->second.RemoveLink(linkId);
//remove link
PrefabDomValueReference templateInstancesRef = templateIterator->second.GetInstancesValue();
if (templateInstancesRef == AZStd::nullopt)
{
AZ_Error("Prefab", false, "Failed to get template reference");
return false;
}
removed = templateInstancesRef->get().RemoveMember(link.GetInstanceName().c_str())
? removed : false;
}
return removed;
@@ -26,10 +26,10 @@
#include <AzToolsFramework/Prefab/Link/Link.h>
#include <AzToolsFramework/Prefab/PrefabIdTypes.h>
#include <AzToolsFramework/Prefab/PrefabLoader.h>
#include <AzToolsFramework/Prefab/PrefabPublicHandler.h>
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
#include <AzToolsFramework/Prefab/Template/Template.h>
namespace AzToolsFramework
{
class Entity;
@@ -123,6 +123,21 @@ namespace AzToolsFramework
PrefabDomValue::MemberIterator& instanceIterator,
InstanceOptionalReference instance) override;
/**
* Create a new Link with Prefab System Component and create a unique id for it.
* @param linkTargetId The Id of Template which owns this Link.
* @param linkSourceId The Id of Template whose instance is referred by targetTemplate.
* @param instanceAlias The alias of the instance that should be included in link.
* @param linkId The id of the link. If invalid, create a new link id. If valid, use to recreate
* a prior link.
* @return A unique id for the new Link.
*/
LinkId CreateLink(
const TemplateId& linkTargetId,
const TemplateId& linkSourceId,
const InstanceAlias& instanceAlias,
const LinkId& linkId = InvalidLinkId) override;
/**
* Remove the Link associated with the given id from Prefab System Component.
* @param linkId A unique id of a Link.
@@ -272,13 +287,13 @@ namespace AzToolsFramework
* Remove given Link Id from the Link's target Template.
* @return bool on whether the operation succeeded.
*/
bool RemoveLinkIdFromTargetTemplate(const LinkId& linkId);
bool RemoveLinkFromTargetTemplate(const LinkId& linkId);
/**
* Given Link and its Id, remove the Id from the Link's target Template..
* Given Link and its Id, remove the link from the target Template..
* @return bool on whether the operation succeeded.
*/
bool RemoveLinkIdFromTargetTemplate(const LinkId& linkId, const Link& link);
bool RemoveLinkFromTargetTemplate(const LinkId& linkId, const Link& link);
// A container for mapping Templates to the Links they may propagate changes to.
AZStd::unordered_map<TemplateId, AZStd::unordered_set<LinkId>> m_templateToLinkIdsMap;
@@ -307,6 +322,9 @@ namespace AzToolsFramework
// Used for loading/saving Prefab Template files.
PrefabLoader m_prefabLoader;
// Handler the public Prefab API used by UI and scripting
PrefabPublicHandler m_prefabPublicHandler;
// Used for updating Instances of Prefab Template.
InstanceUpdateExecutor m_instanceUpdateExecutor;
@@ -40,6 +40,11 @@ namespace AzToolsFramework
virtual LinkId AddLink(const TemplateId& sourceTemplateId, const TemplateId& targetTemplateId,
PrefabDomValue::MemberIterator& instanceIterator, InstanceOptionalReference instance) = 0;
//creates a new Link
virtual LinkId CreateLink(const TemplateId& linkTargetId, const TemplateId& linkSourceId,
const InstanceAlias& instanceAlias, const LinkId& linkId = InvalidLinkId) = 0;
virtual void RemoveLink(const LinkId& linkId) = 0;
virtual TemplateId GetTemplateIdFromFilePath(AZStd::string_view filePath) const = 0;
@@ -0,0 +1,189 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Interface/Interface.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <Prefab/PrefabUndo.h>
namespace AzToolsFramework
{
namespace Prefab
{
PrefabUndoBase::PrefabUndoBase(const AZStd::string& undoOperationName)
: UndoSystem::URSequencePoint(undoOperationName)
, m_changed(true)
, m_templateId(InvalidTemplateId)
{
m_instanceToTemplateInterface = AZ::Interface<InstanceToTemplateInterface>::Get();
AZ_Assert(m_instanceToTemplateInterface, "Failed to grab instance to template interface");
}
//PrefabInstanceUndo
PrefabUndoInstance::PrefabUndoInstance(const AZStd::string& undoOperationName)
: PrefabUndoBase(undoOperationName)
{
}
void PrefabUndoInstance::Capture(
PrefabDom& initialState,
PrefabDom& endState,
const TemplateId& templateId)
{
m_templateId = templateId;
m_instanceToTemplateInterface->GeneratePatch(m_redoPatch, initialState, endState);
m_instanceToTemplateInterface->GeneratePatch(m_undoPatch, endState, initialState);
}
void PrefabUndoInstance::Undo()
{
m_instanceToTemplateInterface->PatchTemplate(m_undoPatch, m_templateId);
}
void PrefabUndoInstance::Redo()
{
m_instanceToTemplateInterface->PatchTemplate(m_redoPatch, m_templateId);
}
//PrefabEntityUpdateUndo
PrefabUndoEntityUpdate::PrefabUndoEntityUpdate(const AZStd::string& undoOperationName)
: PrefabUndoBase(undoOperationName)
{
m_instanceEntityMapperInterface = AZ::Interface<InstanceEntityMapperInterface>::Get();
AZ_Assert(m_instanceEntityMapperInterface, "Failed to grab instance entity mapper interface");
}
void PrefabUndoEntityUpdate::Capture(
PrefabDom& initialState,
PrefabDom& endState,
const AZ::EntityId& entityId)
{
//get the entity alias for future undo/redo
InstanceOptionalReference instanceOptionalReference = m_instanceEntityMapperInterface->FindOwningInstance(entityId);
AZ_Error("Prefab", instanceOptionalReference,
"Failed to find an owning instance for the entity with id %llu.", static_cast<AZ::u64>(entityId));
Instance& instance = instanceOptionalReference->get();
m_templateId = instance.GetTemplateId();
m_entityAlias = (instance.GetEntityAlias(entityId)).value();
//generate undo/redo patches
m_instanceToTemplateInterface->GeneratePatch(m_redoPatch, initialState, endState);
m_instanceToTemplateInterface->GeneratePatch(m_undoPatch, endState, initialState);
}
void PrefabUndoEntityUpdate::Undo()
{
m_instanceToTemplateInterface->PatchEntityInTemplate(m_undoPatch, m_entityAlias, m_templateId);
}
void PrefabUndoEntityUpdate::Redo()
{
m_instanceToTemplateInterface->PatchEntityInTemplate(m_redoPatch, m_entityAlias, m_templateId);
}
//PrefabInstanceLinkUndo
PrefabUndoInstanceLink::PrefabUndoInstanceLink(const AZStd::string& undoOperationName)
: PrefabUndoBase(undoOperationName)
, m_targetId(InvalidTemplateId)
, m_sourceId(InvalidTemplateId)
, m_instanceAlias("")
, m_linkId(InvalidLinkId)
, m_link(Link())
, m_linkStatus(LinkStatus::LINKSTATUS)
{
m_prefabSystemComponentInterface = AZ::Interface<PrefabSystemComponentInterface>::Get();
AZ_Assert(m_instanceToTemplateInterface, "Failed to grab interface");
}
void PrefabUndoInstanceLink::Capture(
const TemplateId& targetId,
const TemplateId& sourceId,
const InstanceAlias& instanceAlias,
const LinkId& linkId,
const Link& link)
{
m_targetId = targetId;
m_sourceId = sourceId;
m_instanceAlias = instanceAlias;
m_linkId = linkId;
m_link = link;
//if linkId is invalid, set as ADD
if (m_linkId == InvalidLinkId)
{
m_linkStatus = LinkStatus::ADD;
}
else
{
m_linkStatus = LinkStatus::REMOVE;
}
}
void PrefabUndoInstanceLink::Undo()
{
switch (m_linkStatus)
{
case LinkStatus::ADD:
RemoveLink();
break;
case LinkStatus::REMOVE:
AddLink();
break;
default:
break;
}
m_prefabSystemComponentInterface->PropagateTemplateChanges(m_targetId);
}
void PrefabUndoInstanceLink::Redo()
{
switch (m_linkStatus)
{
case LinkStatus::ADD:
AddLink();
break;
case LinkStatus::REMOVE:
RemoveLink();
break;
default:
break;
}
m_prefabSystemComponentInterface->PropagateTemplateChanges(m_targetId);
}
void PrefabUndoInstanceLink::AddLink()
{
m_linkId = m_prefabSystemComponentInterface->CreateLink(m_targetId, m_sourceId, m_instanceAlias, m_linkId);
//if data already exists, repopulate
if (m_linkStatus == LinkStatus::REMOVE)
{
LinkReference link = m_prefabSystemComponentInterface->FindLink(m_linkId);
link = m_link;
}
}
void PrefabUndoInstanceLink::RemoveLink()
{
m_prefabSystemComponentInterface->RemoveLink(m_linkId);
}
}
}
@@ -0,0 +1,125 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzToolsFramework/Undo/UndoSystem.h>
#include <AzToolsFramework/Prefab/PrefabIdTypes.h>
#include <AzToolsFramework/Prefab/Instance/InstanceToTemplateInterface.h>
#include <AzToolsFramework/Prefab/Instance/InstanceEntityMapperInterface.h>
//for link undo
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
#include <AzToolsFramework/Prefab/Link/Link.h>
namespace AzToolsFramework
{
namespace Prefab
{
class PrefabUndoBase
: public UndoSystem::URSequencePoint
{
public:
explicit PrefabUndoBase(const AZStd::string& undoOperationName);
bool Changed() const override { return m_changed; }
protected:
TemplateId m_templateId;
PrefabDom m_redoPatch;
PrefabDom m_undoPatch;
InstanceToTemplateInterface* m_instanceToTemplateInterface = nullptr;
bool m_changed;
};
//! handles the addition and removal of entities from instances
class PrefabUndoInstance
: public PrefabUndoBase
{
public:
explicit PrefabUndoInstance(const AZStd::string& undoOperationName);
void Capture(
PrefabDom& initialState,
PrefabDom& endState,
const TemplateId& templateId);
void Undo() override;
void Redo() override;
};
//! handles entity updates, such as when the values on an entity change
class PrefabUndoEntityUpdate
: public PrefabUndoBase
{
public:
explicit PrefabUndoEntityUpdate(const AZStd::string& undoOperationName);
void Capture(
PrefabDom& initialState,
PrefabDom& endState,
const AZ::EntityId& entity);
void Undo() override;
void Redo() override;
private:
InstanceEntityMapperInterface* m_instanceEntityMapperInterface = nullptr;
EntityAlias m_entityAlias;
};
//! handles link changes on instances
class PrefabUndoInstanceLink
: public PrefabUndoBase
{
public:
enum class LinkStatus
{
ADD,
REMOVE,
UPDATE,
LINKSTATUS
};
explicit PrefabUndoInstanceLink(const AZStd::string& undoOperationName);
//capture for add/remove
void Capture(
const TemplateId& targetId,
const TemplateId& sourceId,
const InstanceAlias& instanceAlias,
const LinkId& linkId = InvalidLinkId,
const Link& link = Link());
void Undo() override;
void Redo() override;
private:
//used for special cases of add/delete
void AddLink();
void RemoveLink();
TemplateId m_targetId;
TemplateId m_sourceId;
InstanceAlias m_instanceAlias;
LinkId m_linkId;
Link m_link; //data for delete/update
LinkStatus m_linkStatus;
PrefabSystemComponentInterface* m_prefabSystemComponentInterface = nullptr;
};
}
}
@@ -0,0 +1,58 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzToolsFramework/Prefab/Spawnable/ComponentRequirementsValidator.h>
#include <AzCore/Outcome/Outcome.h>
#include <AzCore/std/algorithm.h>
#include <AzToolsFramework/ToolsComponents/GenericComponentWrapper.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
void ComponentRequirementsValidator::SetPlatformTags(AZ::PlatformTagSet platformTags)
{
m_platformTags = AZStd::move(platformTags);
}
void ComponentRequirementsValidator::SetEntities(const AZStd::vector<AZ::Entity*>& entities)
{
m_immutableEntities.clear();
m_immutableEntities.assign(entities.cbegin(), entities.cend());
}
ComponentRequirementsValidator::ValidationResult ComponentRequirementsValidator::Validate(
const AZ::Component* component)
{
AZ::ComponentValidationResult result = component->ValidateComponentRequirements(m_immutableEntities, m_platformTags);
if (!result.IsSuccess())
{
// Try to cast to GenericComponentWrapper, and if we can, get the internal template.
const char* componentName = component->RTTI_GetTypeName();
const auto* asEditorComponent = azrtti_cast<const Components::EditorComponentBase*>(component);
const Components::GenericComponentWrapper* wrapper = azrtti_cast<const Components::GenericComponentWrapper*>(asEditorComponent);
if (wrapper && wrapper->GetTemplate())
{
componentName = wrapper->GetTemplate()->RTTI_GetTypeName();
}
return AZ::Failure(AZStd::string::format(
"Editor Component '%s' could not pass validation due to the error - %s",
componentName,
result.GetError().c_str())
);
}
return AZ::Success();
}
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,45 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Component/Component.h>
#include <AzCore/Component/ComponentExport.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/optional.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
class ComponentRequirementsValidator
{
public:
AZ_CLASS_ALLOCATOR(ComponentRequirementsValidator, AZ::SystemAllocator, 0);
AZ_RTTI(AzToolsFramework::Prefab::PrefabConversionUtils::ComponentRequirementsValidator, "{1E9CD55D-FFEA-4E71-A316-731E25E6C981}");
virtual ~ComponentRequirementsValidator() = default;
void SetPlatformTags(AZ::PlatformTagSet platformTags);
void SetEntities(const AZStd::vector<AZ::Entity*>& entities);
using ValidationResult = AZ::Outcome<void, AZStd::string>;
ValidationResult Validate(const AZ::Component* component);
private:
AZ::ImmutableEntityVector m_immutableEntities;
AZ::PlatformTagSet m_platformTags;
};
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,632 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Component/ComponentExport.h>
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/string/string_view.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
#include <AzToolsFramework/Prefab/Spawnable/EditorInfoRemover.h>
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
#include <AzToolsFramework/ToolsComponents/EditorOnlyEntityComponentBus.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
EditorInfoRemover::~EditorInfoRemover()
{
for (auto* handler : m_editorOnlyEntityHandlerCandidates)
{
delete handler;
}
}
void EditorInfoRemover::Process(PrefabProcessorContext& prefabProcessorContext)
{
AZ::SerializeContext* serializeContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
if (!serializeContext)
{
AZ_Assert(serializeContext, "Failed to retrieve serialize context.");
return;
}
prefabProcessorContext.ListPrefabs([this, &serializeContext, &prefabProcessorContext](AZStd::string_view prefabName, PrefabDom& prefab)
{
auto result = RemoveEditorInfo(prefab, serializeContext, prefabProcessorContext);
if (!result)
{
AZ_Assert(false,
"Converting to runtime Prefab '%.*s' failed, Error: %s .",
AZ_STRING_ARG(prefabName),
result.GetError().c_str());
return;
}
});
}
void EditorInfoRemover::Reflect(AZ::ReflectContext* context)
{
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context); serializeContext != nullptr)
{
serializeContext->Class<EditorInfoRemover, PrefabProcessor>()->Version(1);
}
}
AZStd::vector<AZ::Entity*> EditorInfoRemover::GetEntitiesFromInstance(AZStd::unique_ptr<Instance>& instance)
{
AZStd::vector<AZ::Entity*> result;
instance->GetNestedEntities(
[&result](const AZStd::unique_ptr<AZ::Entity>& entity)
{
result.emplace_back(entity.get());
return true;
}
);
return result;
}
void EditorInfoRemover::SetEditorOnlyEntityHandlerFromCandidates(const EntityList& entities)
{
ClearEditorOnlyEntityIds();
m_editorOnlyEntityHandler = nullptr;
for (auto& handlerCandidate : m_editorOnlyEntityHandlerCandidates)
{
// See if this handler can handle at least one of the entities.
for (auto entity : entities)
{
if (handlerCandidate->IsEntityUniquelyForThisHandler(entity))
{
m_editorOnlyEntityHandler = handlerCandidate;
break;
}
}
if (HasValidEditorOnlyHandler())
{
break;
}
}
}
bool EditorInfoRemover::HasValidEditorOnlyHandler() const
{
return m_editorOnlyEntityHandler != nullptr;
}
void EditorInfoRemover::ClearEditorOnlyEntityIds()
{
m_editorOnlyEntityIds.clear();
}
void EditorInfoRemover::AddEntityIdIfEditorOnly(AZ::Entity* entity)
{
bool isEditorOnly = false;
EditorOnlyEntityComponentRequestBus::EventResult(isEditorOnly, entity->GetId(), &EditorOnlyEntityComponentRequests::IsEditorOnlyEntity);
if (isEditorOnly && HasValidEditorOnlyHandler())
{
m_editorOnlyEntityHandler->AddEditorOnlyEntity(entity, m_editorOnlyEntityIds);
}
}
/**
* Identify and remove any entities marked as editor-only.
* If any are discovered, adjust descendants' transforms to retain spatial relationships.
* Note we cannot use EBuses for this purpose, since we're crunching data, and can't assume any entities are active.
*/
EditorInfoRemover::RemoveEditorOnlyEntitiesResult EditorInfoRemover::RemoveEditorOnlyEntities(EntityList& entities)
{
if (HasValidEditorOnlyHandler())
{
const auto handlerResult =
m_editorOnlyEntityHandler->HandleEditorOnlyEntities(entities, m_editorOnlyEntityIds, *m_serializeContext);
if (!handlerResult)
{
return AZ::Failure(AZStd::string::format(
"Error occurred when handle editor-only entities. Error: %s",
handlerResult.GetError().c_str())
);
}
}
// Remove editor-only entities from the given entity list.
AZStd::erase_if(
entities,
[this](auto entity)
{
return m_editorOnlyEntityIds.find(entity->GetId()) != m_editorOnlyEntityIds.end();
}
);
return AZ::Success();
}
EditorInfoRemover::ExportEntityResult EditorInfoRemover::ExportEntity(AZ::Entity* sourceEntity, PrefabProcessorContext& context)
{
// For export, components can assume they're initialized, but not activated.
if (sourceEntity->GetState() == AZ::Entity::State::Constructed)
{
sourceEntity->Init();
}
AZ::Entity* exportEntity = aznew AZ::Entity(sourceEntity->GetId(), sourceEntity->GetName().c_str());
exportEntity->SetRuntimeActiveByDefault(sourceEntity->IsRuntimeActiveByDefault());
AddEntityIdIfEditorOnly(sourceEntity);
const AZ::Entity::ComponentArrayType& editorComponents = sourceEntity->GetComponents();
EntityList exportedEntities;
for (AZ::Component* component : editorComponents)
{
auto result = ExportComponent(component, context, sourceEntity, exportEntity);
if (!result)
{
return AZ::Failure(AZStd::string::format(
"Entity '%s' %s - export component '%s' failed. Error: %s",
exportEntity->GetName().c_str(),
exportEntity->GetId().ToString().c_str(),
component->RTTI_GetTypeName(),
result.GetError().c_str())
);
}
}
// Pre-sort prior to exporting so it isn't required at instantiation time.
const auto sortResult = exportEntity->EvaluateDependenciesGetDetails();
/* :CBR_TODO: verify AZ::Entity::DependencySortResult::HasIncompatibleServices and
AZ::Entity::DependencySortResult::DescriptorNotRegistered are still covered here*/
if (!sortResult.IsSuccess())
{
return AZ::Failure(AZStd::string::format(
"Entity '%s' %s - dependency evaluation failed. Error: %s",
exportEntity->GetName().c_str(),
exportEntity->GetId().ToString().c_str(),
sortResult.GetError().m_message.c_str()));
}
return AZ::Success(exportEntity);
}
bool EditorInfoRemover::ReadComponentAttribute(
AZ::Component* component,
AZ::Edit::Attribute* attribute,
AZStd::vector<AZ::Crc32>& attributeTags)
{
attributeTags.clear();
PropertyAttributeReader reader(component, attribute);
return reader.Read<AZStd::vector<AZ::Crc32>>(attributeTags);
}
EditorInfoRemover::ShouldExportResult EditorInfoRemover::ShouldExportComponent(
AZ::Component* component,
PrefabProcessorContext& context) const
{
const AZ::SerializeContext::ClassData* classData = m_serializeContext->FindClassData(component->RTTI_GetType());
if (!classData || !classData->m_editData)
{
return AZ::Success(true);
}
const AZ::Edit::ElementData* editorDataElement = classData->m_editData->FindElementData(AZ::Edit::ClassElements::EditorData);
if (!editorDataElement)
{
return AZ::Success(true);
}
AZStd::vector<AZ::Crc32> attributeTags;
const auto& platformTags = context.GetPlatformTags();
// If the component has declared the 'ExportIfAllPlatforms' attribute, skip export if any of the flags are not present.
AZ::Edit::Attribute* allTagsAttribute = editorDataElement->FindAttribute(AZ::Edit::Attributes::ExportIfAllPlatformTags);
if (allTagsAttribute)
{
if (!ReadComponentAttribute(component, allTagsAttribute, attributeTags))
{
return AZ::Failure(
AZStd::string("'ExportIfAllPlatforms' attribute is not bound to the correct return type. Expects AZStd::vector<AZ::Crc32>.")
);
}
for (AZ::Crc32 tag : attributeTags)
{
if (platformTags.find(tag) == platformTags.end())
{
// Export platform tags does not contain all tags specified in 'ExportIfAllPlatforms' attribute.
return AZ::Success(false);
}
}
}
// If the component has declared the 'ExportIfAnyPlatforms' attribute, skip export if none of the flags are present.
AZ::Edit::Attribute* anyTagsAttribute = editorDataElement->FindAttribute(AZ::Edit::Attributes::ExportIfAnyPlatformTags);
if (anyTagsAttribute)
{
if (!ReadComponentAttribute(component, anyTagsAttribute, attributeTags))
{
return AZ::Failure(
AZStd::string("'ExportIfAnyPlatforms' attribute is not bound to the correct return type. Expects AZStd::vector<AZ::Crc32>.")
);
}
bool anyFlagSet = false;
for (AZ::Crc32 tag : attributeTags)
{
if (platformTags.find(tag) != platformTags.end())
{
anyFlagSet = true;
break;
}
}
if (!anyFlagSet)
{
// None of the flags in 'ExportIfAnyPlatforms' was present in the export platform tags.
return AZ::Success(false);
}
}
return AZ::Success(true);
}
EditorInfoRemover::ResolveExportedComponentResult EditorInfoRemover::ResolveExportedComponent(
AZ::ExportedComponent& component,
PrefabProcessorContext& prefabProcessorContext)
{
AZ::Component* inputComponent = component.m_component;
if (!inputComponent)
{
return AZ::Success(component);
}
// Don't export the component if it has unmet platform tag requirements.
ShouldExportResult shouldExportResult = ShouldExportComponent(inputComponent, prefabProcessorContext);
if (!shouldExportResult)
{
return AZ::Failure(shouldExportResult.TakeError());
}
if (!shouldExportResult.GetValue())
{
// If the platform tag requirements aren't met, return a null component that's been flagged as exported,
// so that we know not to try and process it any further.
return AZ::Success(AZ::ExportedComponent());
}
// Determine if the component has a custom export callback, and invoke it if so.
// If there's no custom export callback, just return what we were given.
const AZ::SerializeContext::ClassData* classData = m_serializeContext->FindClassData(inputComponent->RTTI_GetType());
if (!classData || !classData->m_editData)
{
return AZ::Success(component);
}
const AZ::Edit::ElementData* editorDataElement = classData->m_editData->FindElementData(AZ::Edit::ClassElements::EditorData);
if (!editorDataElement)
{
return AZ::Success(component);
}
AZ::Edit::Attribute* exportCallbackAttribute = editorDataElement->FindAttribute(AZ::Edit::Attributes::RuntimeExportCallback);
if (!exportCallbackAttribute)
{
return AZ::Success(component);
}
PropertyAttributeReader reader(inputComponent, exportCallbackAttribute);
AZ::ExportedComponent exportedComponent;
if (reader.Read<AZ::ExportedComponent>(exportedComponent, inputComponent, prefabProcessorContext.GetPlatformTags()))
{
// If the callback handled the export and provided a different component instance, continue to resolve recursively.
if (exportedComponent.m_componentExportHandled && (exportedComponent.m_component != inputComponent))
{
return ResolveExportedComponent(exportedComponent, prefabProcessorContext);
}
else
{
// It provided the *same* component back (or didn't handle the export at all), so we're done.
return AZ::Success(exportedComponent);
}
}
else
{
return AZ::Failure(AZStd::string("Bound 'CustomExportCallback' does not have the required return type/signature."));
}
}
EditorInfoRemover::BuildGameEntityResult EditorInfoRemover::BuildGameEntity(
AzToolsFramework::Components::EditorComponentBase* editorComponent,
AZ::Entity* sourceEntity,
AZ::Entity* exportEntity)
{
const size_t oldComponentCount = exportEntity->GetComponents().size();
editorComponent->BuildGameEntity(exportEntity);
AZ::ComponentId newID = editorComponent->GetId();
for (auto i = oldComponentCount; i < exportEntity->GetComponents().size(); ++i)
{
AZ::Component* exportComponent = exportEntity->GetComponents()[i];
// Verify that the result of BuildGameEntity() wasn't an editor component.
auto* exportAsEditorComponent = azrtti_cast<Components::EditorComponentBase*>(exportComponent);
if (exportAsEditorComponent)
{
return AZ::Failure(AZStd::string::format(
"Entity '%s' %s - component '%s' exported an editor component from BuildGameEntity() for runtime use.",
sourceEntity->GetName().c_str(),
sourceEntity->GetId().ToString().c_str(),
editorComponent->RTTI_GetType().ToString<AZStd::string>().c_str()));
}
else if (editorComponent->GetId() == AZ::InvalidComponentId)
{
return AZ::Failure(AZStd::string::format(
"Entity '%s' %s - component '%s' doesn't have a valid component Id.",
sourceEntity->GetName().c_str(),
sourceEntity->GetId().ToString().c_str(),
editorComponent->RTTI_GetType().ToString<AZStd::string>().c_str()));
}
exportComponent->SetId(newID++);
// The first time round set the new component the same as the editor one. This will change in a separate ticket
// when 8 bit runtime Ids are implemented.
// Make sure the newID isn't already on the source Entity. If it is increment the ID and try again.
while (sourceEntity->FindComponent(newID))
{
++newID;
}
}
return AZ::Success();
}
EditorInfoRemover::ExportComponentResult EditorInfoRemover::ExportComponent(
AZ::Component* component,
PrefabProcessorContext& prefabProcessorContext,
AZ::Entity* sourceEntity,
AZ::Entity* exportEntity)
{
auto validationResult = m_componentRequirementsValidator.Validate(component);
if (!validationResult.IsSuccess())
{
return AZ::Failure(AZStd::string::format(
"Entity '%s' %s - validation of component '%s' failed: %s",
sourceEntity->GetName().c_str(),
sourceEntity->GetId().ToString().c_str(),
component->RTTI_GetTypeName(),
validationResult.GetError().c_str())
);
}
AZ::ExportedComponent exportComponent(component, false, false);
auto exportResult = ResolveExportedComponent(
exportComponent, prefabProcessorContext);
if (!exportResult)
{
return AZ::Failure(AZStd::string::format(
"Entity '%s' %s - component '%s' could not be exported due to export attributes: %s.",
sourceEntity->GetName().c_str(),
sourceEntity->GetId().ToString().c_str(),
component->RTTI_GetTypeName(),
exportResult.GetError().c_str()));
}
AZ::ExportedComponent& exportedComponent = exportResult.GetValue();
// If ResolveExportedComponent didn't handle the component export, then we'll do the following:
// - For editor components, fall back on the legacy BuildGameEntity() path for handling component exports.
// - For runtime components, provide a default behavior of "clone / add" to export the component.
if (!exportedComponent.m_componentExportHandled)
{
auto* asEditorComponent = azrtti_cast<Components::EditorComponentBase*>(component);
// Editor components: Try to use BuildGameEntity()
if (asEditorComponent) // BEGIN BuildGameEntity compatibility path for editor components not using the newer RuntimeExportCallback functionality.
{
auto buildGameEntityResult = BuildGameEntity(asEditorComponent, sourceEntity, exportEntity);
if (!buildGameEntityResult.IsSuccess())
{
return AZ::Failure(AZStd::string::format(
"Entity '%s' %s - component '%s' to build game entity failed. Error: %s.",
sourceEntity->GetName().c_str(),
sourceEntity->GetId().ToString().c_str(),
component->RTTI_GetTypeName(),
buildGameEntityResult.GetError().c_str()));
}
// Since this is an editor component, we very specifically do *not* want to clone and add it as a runtime
// component by default, so regardless of whether or not the BuildGameEntity() call did anything,
// null out the editor component and mark it handled.
return AZ::Success();
} // END BuildGameEntity compatibility path for editor components not using the newer RuntimeExportCallback functionality.
else
{
// Nothing else has handled the component export, so fall back on the default behavior
// for runtime components: clone and add the runtime component that already exists.
exportedComponent = AZ::ExportedComponent(component, false);
}
}
// At this point, either ResolveExportedComponent or the default logic above should have set the component export
// as being handled. If not, there is likely a new code path that requires a default export behavior.
AZ_Assert(exportedComponent.m_componentExportHandled,
"Entity '%s' %s - component '%s' had no export handlers and could not be added to the entity.",
exportEntity->GetName().c_str(),
exportEntity->GetId().ToString().c_str(),
component->RTTI_GetTypeName());
// If we have an exported component, we add it to the exported entity.
// If we don't (m_component == nullptr), this component chose not to be exported, so we skip it.
if (exportedComponent.m_componentExportHandled && exportedComponent.m_component)
{
AZ::Component* runtimeComponent = exportedComponent.m_component;
// Verify that we aren't trying to export an editor component.
auto* exportAsEditorComponent = azrtti_cast<Components::EditorComponentBase*>(runtimeComponent);
if (exportAsEditorComponent)
{
auto* asEditorComponent =
azrtti_cast<Components::EditorComponentBase*>(component);
return AZ::Failure(AZStd::string::format(
"Entity '%s' %s - component '%s' is trying to export an Editor component for runtime use.",
sourceEntity->GetName().c_str(),
sourceEntity->GetId().ToString().c_str(),
asEditorComponent->RTTI_GetType().ToString<AZStd::string>().c_str()));
}
// If the final component is not owned by us, make our own copy.
if (!exportedComponent.m_deleteAfterExport)
{
runtimeComponent = m_serializeContext->CloneObject(runtimeComponent);
}
// Synchronize to source component Id, and add to the export entity.
runtimeComponent->SetId(component->GetId());
if (!exportEntity->AddComponent(runtimeComponent))
{
return AZ::Failure(AZStd::string::format(
"Entity '%s' %s - component '%s' could not be added to this entity.",
exportEntity->GetName().c_str(),
exportEntity->GetId().ToString().c_str(),
runtimeComponent->RTTI_GetTypeName())
);
}
}
return AZ::Success();
}
EditorInfoRemover::RemoveEditorInfoResult EditorInfoRemover::RemoveEditorInfo(
PrefabDom& prefab,
AZ::SerializeContext* serializeContext,
PrefabProcessorContext& prefabProcessorContext)
{
if (!serializeContext)
{
return AZ::Failure(AZStd::string("Invalid Serialize Context used."));
}
m_serializeContext = serializeContext;
m_componentRequirementsValidator.SetPlatformTags(prefabProcessorContext.GetPlatformTags());
// convert Prefab DOM into Prefab Instance.
AZStd::unique_ptr<Instance> instance(aznew Instance());
if (!Prefab::PrefabDomUtils::LoadInstanceFromPrefabDom(*instance, prefab, false))
{
PrefabDomValueReference sourceReference = PrefabDomUtils::FindPrefabDomValue(prefab, PrefabDomUtils::SourceName);
AZStd::string errorMessage("Failed to Load Prefab Instance from given Prefab Dom during Removal of Editor Info.");
if (sourceReference.has_value() &&
sourceReference->get().IsString() &&
sourceReference->get().GetStringLength() != 0)
{
AZStd::string_view source(sourceReference->get().GetString(), sourceReference->get().GetStringLength());
errorMessage += AZStd::string::format("Prefab Source: %.*s", AZ_STRING_ARG(source));
}
return AZ::Failure(errorMessage);
}
// grab all nested entities from the Instance as source entities.
EntityList sourceEntities = GetEntitiesFromInstance(instance);
EntityList exportEntities;
// prepare for validation of component requirements.
m_componentRequirementsValidator.SetEntities(sourceEntities);
// find valid editor-only entity handler for removing editor-only entities later.
SetEditorOnlyEntityHandlerFromCandidates(sourceEntities);
// export entities.
for (AZ::Entity* entity : sourceEntities)
{
const auto result = ExportEntity(entity, prefabProcessorContext);
if (!result)
{
return AZ::Failure(AZStd::string::format(
"Entity '%s' %s - export entity failed. Error: %s",
entity->GetName().c_str(),
entity->GetId().ToString().c_str(),
result.GetError().c_str())
);
}
exportEntities.emplace_back(result.GetValue());
}
// remove editor-only entities with valid editor-only entity handler.
const auto removeEditorOnlyEntitiesResult = RemoveEditorOnlyEntities(exportEntities);
if (!removeEditorOnlyEntitiesResult)
{
return AZ::Failure(AZStd::string::format(
"Remove Editor-Only Entities failed. Error: '%s'",
removeEditorOnlyEntitiesResult.GetError().c_str())
);
}
// validate component requirements for exported entities.
m_componentRequirementsValidator.SetEntities(exportEntities);
for (AZ::Entity* exportEntity : exportEntities)
{
const AZ::Entity::ComponentArrayType& gameComponents = exportEntity->GetComponents();
for (const AZ::Component* component : gameComponents)
{
const auto result = m_componentRequirementsValidator.Validate(component);
if (!result)
{
return AZ::Failure(AZStd::string::format(
"Entity '%s' %s - validation of export component '%s' failed: %s",
exportEntity->GetName().c_str(),
exportEntity->GetId().ToString().c_str(),
component->RTTI_GetTypeName(),
result.GetError().c_str())
);
}
}
}
// remove editor-only entities from instance.
AZStd::unordered_map<AZ::EntityId, AZ::Entity*> exportEntitiesMap;
AZStd::for_each(exportEntities.begin(), exportEntities.end(),
[&exportEntitiesMap](auto& entity)
{
exportEntitiesMap.emplace(entity->GetId(), entity);
}
);
instance->RemoveNestedEntities(
[&exportEntitiesMap](const AZStd::unique_ptr<AZ::Entity>& entity)
{
return exportEntitiesMap.find(entity->GetId()) == exportEntitiesMap.end();
}
);
// replace entities of instance with exported ones.
instance->GetNestedEntities(
[&exportEntitiesMap](AZStd::unique_ptr<AZ::Entity>& entity)
{
auto entityId = entity->GetId();
entity.release();
entity.reset(exportEntitiesMap[entityId]);
return true;
}
);
// save the final result in the target Prefab DOM.
if (!PrefabDomUtils::StoreInstanceInPrefabDom(*instance, prefab))
{
return AZ::Failure(AZStd::string::format(
"Saving exported Prefab Instance within a Prefab Dom failed.")
);
}
return AZ::Success();
}
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,111 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Outcome/Outcome.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/Spawnable/ComponentRequirementsValidator.h>
#include <AzToolsFramework/Prefab/Spawnable/EditorOnlyEntityHandler/EditorOnlyEntityHandler.h>
#include <AzToolsFramework/Prefab/Spawnable/EditorOnlyEntityHandler/UiEditorOnlyEntityHandler.h>
#include <AzToolsFramework/Prefab/Spawnable/EditorOnlyEntityHandler/WorldEditorOnlyEntityHandler.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabProcessor.h>
namespace AZ
{
class ReflectContext;
}
namespace AzToolsFramework::Components
{
class EditorComponentBase;
}
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
class EditorInfoRemover
: public PrefabProcessor
{
public:
AZ_CLASS_ALLOCATOR(EditorInfoRemover, AZ::SystemAllocator, 0);
AZ_RTTI(AzToolsFramework::Prefab::PrefabConversionUtils::EditorInfoRemover,
"{50B48C7E-C9DE-48DE-8438-1A186A8EEAC8}", PrefabProcessor);
~EditorInfoRemover() override;
void Process(PrefabProcessorContext& prefabProcessorContext) override;
using RemoveEditorInfoResult = AZ::Outcome<void, AZStd::string>;
RemoveEditorInfoResult RemoveEditorInfo(
PrefabDom& prefab,
AZ::SerializeContext* serializeContext,
PrefabProcessorContext& prefabProcessorContext);
static void Reflect(AZ::ReflectContext* context);
protected:
using EntityList = AZStd::vector<AZ::Entity*>;
static EntityList GetEntitiesFromInstance(
AZStd::unique_ptr<AzToolsFramework::Prefab::Instance>& instance);
static bool ReadComponentAttribute(
AZ::Component* component,
AZ::Edit::Attribute* attribute,
AZStd::vector<AZ::Crc32>& attributeTags);
void SetEditorOnlyEntityHandlerFromCandidates(const EntityList& entities);
bool HasValidEditorOnlyHandler() const;
void ClearEditorOnlyEntityIds();
void AddEntityIdIfEditorOnly(AZ::Entity* entity);
using RemoveEditorOnlyEntitiesResult = AZ::Outcome<void, AZStd::string>;
RemoveEditorOnlyEntitiesResult RemoveEditorOnlyEntities(EntityList& entities);
using ExportEntityResult = AZ::Outcome<AZ::Entity*, AZStd::string>;
ExportEntityResult ExportEntity(AZ::Entity* sourceEntity, PrefabProcessorContext& context);
using ResolveExportedComponentResult = AZ::Outcome<AZ::ExportedComponent, AZStd::string>;
ResolveExportedComponentResult ResolveExportedComponent(
AZ::ExportedComponent& component, PrefabProcessorContext& prefabProcessorContext);
using ShouldExportResult = AZ::Outcome<bool, AZStd::string>;
ShouldExportResult ShouldExportComponent(
AZ::Component* component,
PrefabProcessorContext& prefabProcessorContext) const;
using BuildGameEntityResult = AZ::Outcome<void, AZStd::string>;
BuildGameEntityResult BuildGameEntity(
AzToolsFramework::Components::EditorComponentBase* editorComponent,
AZ::Entity* sourceEntity,
AZ::Entity* exportEntity
);
using ExportComponentResult = AZ::Outcome<void, AZStd::string>;
ExportComponentResult ExportComponent(
AZ::Component* component,
PrefabProcessorContext& prefabProcessorContext,
AZ::Entity* sourceEntity,
AZ::Entity* exportEntity);
AZ::SerializeContext* m_serializeContext{ nullptr };
EditorOnlyEntityHandler* m_editorOnlyEntityHandler{ nullptr };
EditorOnlyEntityHandlers m_editorOnlyEntityHandlerCandidates{
aznew WorldEditorOnlyEntityHandler(),
aznew UiEditorOnlyEntityHandler() };
ComponentRequirementsValidator m_componentRequirementsValidator;
EntityIdSet m_editorOnlyEntityIds;
};
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,76 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzToolsFramework/Prefab/Spawnable/EditorOnlyEntityHandler/EditorOnlyEntityHandler.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
void EditorOnlyEntityHandler::AddEditorOnlyEntity(
AZ::Entity* editorOnlyEntity,
AZStd::unordered_set<AZ::EntityId>& editorOnlyEntities)
{
editorOnlyEntities.insert(editorOnlyEntity->GetId());
}
EditorOnlyEntityHandler::Result EditorOnlyEntityHandler::HandleEditorOnlyEntities(
const EntityList& /*entities*/,
const EntityIdSet& /*editorOnlyEntityIds*/,
AZ::SerializeContext& /*serializeContext*/)
{
return AZ::Success();
}
EditorOnlyEntityHandler::Result EditorOnlyEntityHandler::ValidateReferences(
const EntityList& entities,
const EntityIdSet& editorOnlyEntityIds,
AZ::SerializeContext& serializeContext)
{
EditorOnlyEntityHandler::Result result = AZ::Success();
// Inspect all runtime entities via the serialize context and identify any references to editor-only entity Ids.
for (AZ::Entity* runtimeEntity : entities)
{
if (editorOnlyEntityIds.end() != editorOnlyEntityIds.find(runtimeEntity->GetId()))
{
continue; // This is not a runtime entity, so no need to validate its references as it's going away.
}
AZ::EntityUtils::EnumerateEntityIds<AZ::Entity>(
runtimeEntity,
[&editorOnlyEntityIds, &result, runtimeEntity](const AZ::EntityId& id, bool /*isEntityId*/, const AZ::SerializeContext::ClassElement* /*elementData*/)
{
if (editorOnlyEntityIds.end() != editorOnlyEntityIds.find(id))
{
result = AZ::Failure(
AZStd::string::format(
"A runtime entity (%s) contains references to an entity marked as editor-only.",
runtimeEntity->GetName().c_str()
)
);
return false;
}
return true;
},
&serializeContext
);
if (!result)
{
break;
}
}
return result;
}
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,68 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Component/Entity.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
/**
* Callback handler interface for processing prefab prior to stripping of editor-only entities.
*/
class EditorOnlyEntityHandler
{
public:
AZ_CLASS_ALLOCATOR(EditorOnlyEntityHandler, AZ::SystemAllocator, 0);
AZ_RTTI(AzToolsFramework::Prefab::PrefabConversionUtils::EditorOnlyEntityHandler, "{C420F65D-18AE-4CAF-BB18-70FA4FE73243}");
virtual ~EditorOnlyEntityHandler() = default;
virtual bool IsEntityUniquelyForThisHandler(AZ::Entity* entity) const = 0;
/**
* Adds the given entity ID to the set of editor only entities.
*
* Handlers can customize this behavior, such as additionally adding child entities
* when a parent is marked as editor-only.
*/
virtual void AddEditorOnlyEntity(
AZ::Entity* editorOnlyEntity,
AZStd::unordered_set<AZ::EntityId>& editorOnlyEntities);
using Result = AZ::Outcome<void, AZStd::string>;
/**
* This handler is responsible for making any necessary modifications to other entities in the Prefab prior to the removal
* of all editor-only entities.
* After this callback returns, editor-only entities will be removed from the Prefab.
* See \ref WorldEditorOnlyEntityHandler below for an example of processing and validation that occurs for standard world entities.
* @param entities a list of all entities in the Prefab, including those marked as editor-only.
* @param editorOnlyEntityIds a precomputed set containing Ids for all entities within the 'entities' list that were marked as editor-only.
* @param serializeContext useful to inspect entity data for validation purposes.
*/
virtual Result HandleEditorOnlyEntities(
const EntityList& /*entities*/,
const EntityIdSet& /*editorOnlyEntityIds*/,
AZ::SerializeContext& /*serializeContext*/);
// Verify that none of the runtime entities reference editor-only entities. Fail w/ details if so.
static Result ValidateReferences(
const EntityList& entities,
const EntityIdSet& editorOnlyEntityIds,
AZ::SerializeContext& serializeContext);
};
using EditorOnlyEntityHandlers = AZStd::vector<EditorOnlyEntityHandler*>;
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,45 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzToolsFramework/Prefab/Spawnable/EditorOnlyEntityHandler/UiEditorOnlyEntityHandler.h>
#include <AzFramework/InGameUI/UiFrameworkBus.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
bool UiEditorOnlyEntityHandler::IsEntityUniquelyForThisHandler(AZ::Entity* entity) const
{
// Assume that an entity is a UI element if it has a UI element component.
bool uniqueForThisHandler = false;
UiFrameworkBus::BroadcastResult(uniqueForThisHandler, &UiFrameworkInterface::HasUiElementComponent, entity);
return uniqueForThisHandler;
}
void UiEditorOnlyEntityHandler::AddEditorOnlyEntity(AZ::Entity* editorOnlyEntity, EntityIdSet& editorOnlyEntities)
{
UiFrameworkBus::Broadcast(&UiFrameworkInterface::AddEditorOnlyEntity, editorOnlyEntity, editorOnlyEntities);
}
EditorOnlyEntityHandler::Result UiEditorOnlyEntityHandler::HandleEditorOnlyEntities(
const AzToolsFramework::EntityList& exportEntities,
const AzToolsFramework::EntityIdSet& editorOnlyEntityIds,
AZ::SerializeContext& serializeContext)
{
UiFrameworkBus::Broadcast(&UiFrameworkInterface::HandleEditorOnlyEntities, exportEntities, editorOnlyEntityIds);
// Perform a final check to verify that all editor-only entities have been removed
auto result = ValidateReferences(exportEntities, editorOnlyEntityIds, serializeContext);
return result;
}
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,43 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzToolsFramework/Prefab/Spawnable/EditorOnlyEntityHandler/EditorOnlyEntityHandler.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
/**
* EditorOnlyEntity handler for UI entities.
* - Removes editor-only entities and their descedent hierarchy entirely.
* -- This differs from the world-entity handler where editor-only entities
* are removed "in-place".
* - Validates that no editor entities are referenced by non-editor entities.
*/
class UiEditorOnlyEntityHandler
: public EditorOnlyEntityHandler
{
public:
AZ_CLASS_ALLOCATOR(UiEditorOnlyEntityHandler, AZ::SystemAllocator, 0);
AZ_RTTI(AzToolsFramework::Prefab::PrefabConversionUtils::UiEditorOnlyEntityHandler, "{949CF813-4A8E-4D55-B323-0ED2A967CDCC}", EditorOnlyEntityHandler);
bool IsEntityUniquelyForThisHandler(AZ::Entity* entity) const override;
void AddEditorOnlyEntity(AZ::Entity* editorOnlyEntity, EntityIdSet& editorOnlyEntities) override;
Result HandleEditorOnlyEntities(
const AzToolsFramework::EntityList& entities,
const AzToolsFramework::EntityIdSet& editorOnlyEntityIds,
AZ::SerializeContext& serializeContext) override;
};
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,83 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzToolsFramework/Prefab/Spawnable/EditorOnlyEntityHandler/WorldEditorOnlyEntityHandler.h>
#include <AzCore/Component/TransformBus.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
bool WorldEditorOnlyEntityHandler::IsEntityUniquelyForThisHandler(AZ::Entity* entity) const
{
return AZ::EntityUtils::FindFirstDerivedComponent<AZ::TransformInterface>(entity) != nullptr;
}
EditorOnlyEntityHandler::Result WorldEditorOnlyEntityHandler::HandleEditorOnlyEntities(const AzToolsFramework::EntityList& entities, const AzToolsFramework::EntityIdSet& editorOnlyEntityIds, AZ::SerializeContext& serializeContext)
{
FixTransformRelationships(entities, editorOnlyEntityIds);
return ValidateReferences(entities, editorOnlyEntityIds, serializeContext);
}
void WorldEditorOnlyEntityHandler::FixTransformRelationships(const AzToolsFramework::EntityList& entities, const AzToolsFramework::EntityIdSet& editorOnlyEntityIds)
{
AZStd::unordered_map<AZ::EntityId, AZStd::vector<AZ::Entity*>> parentToChildren;
// Build a map of entity Ids to their parent Ids, for faster lookup during processing.
for (AZ::Entity* entity : entities)
{
AZ::TransformInterface* transformComponent = AZ::EntityUtils::FindFirstDerivedComponent<AZ::TransformInterface>(entity);
if (transformComponent)
{
const AZ::EntityId parentId = transformComponent->GetParentId();
if (parentId.IsValid())
{
parentToChildren[parentId].push_back(entity);
}
}
}
// Identify any editor-only entities. If we encounter one, adjust transform relationships
// for all of its children to ensure relative transforms are maintained and respected at
// runtime.
// This works regardless of entity ordering in the Prefab because we add reassigned children to
// parentToChildren cache during the operation.
for (AZ::Entity* entity : entities)
{
if (editorOnlyEntityIds.end() == editorOnlyEntityIds.find(entity->GetId()))
{
continue; // This is not an editor-only entity.
}
AZ::TransformInterface* transformComponent = AZ::EntityUtils::FindFirstDerivedComponent<AZ::TransformInterface>(entity);
if (transformComponent)
{
const AZ::Transform& parentLocalTm = transformComponent->GetLocalTM();
// Identify all transform children and adjust them to be children of the removed entity's parent.
for (AZ::Entity* childEntity : parentToChildren[entity->GetId()])
{
AZ::TransformInterface* childTransformComponent = AZ::EntityUtils::FindFirstDerivedComponent<AZ::TransformInterface>(childEntity);
if (childTransformComponent && childTransformComponent->GetParentId() == entity->GetId())
{
const AZ::Transform localTm = childTransformComponent->GetLocalTM();
childTransformComponent->SetParent(transformComponent->GetParentId());
childTransformComponent->SetLocalTM(parentLocalTm * localTm);
parentToChildren[transformComponent->GetParentId()].push_back(childEntity);
}
}
}
}
}
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,46 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzToolsFramework/Prefab/Spawnable/EditorOnlyEntityHandler/EditorOnlyEntityHandler.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
/**
* EditorOnlyEntity handler for world entities.
* - Fixes up transform relationships so entities removed mid-hierarchy still result in valid runtime transform relationships
* and correct relative transforms.
* - Validates that no editor entities are referenced by non-editor entities.
*/
class WorldEditorOnlyEntityHandler
: public EditorOnlyEntityHandler
{
public:
AZ_CLASS_ALLOCATOR(WorldEditorOnlyEntityHandler, AZ::SystemAllocator, 0);
AZ_RTTI(AzToolsFramework::Prefab::PrefabConversionUtils::WorldEditorOnlyEntityHandler, "{55587AE2-B583-48E4-9634-6BFACF6CBF04}", EditorOnlyEntityHandler);
bool IsEntityUniquelyForThisHandler(AZ::Entity* entity) const override;
Result HandleEditorOnlyEntities(
const AzToolsFramework::EntityList& entities,
const AzToolsFramework::EntityIdSet& editorOnlyEntityIds,
AZ::SerializeContext& serializeContext) override;
// Adjust transform relationships to maintain integrity of the transform hierarchy at runtime, even if editor-only
// entities were positioned within the transform hierarchy.
static void FixTransformRelationships(
const AzToolsFramework::EntityList& entities,
const AzToolsFramework::EntityIdSet& editorOnlyEntityIds);
};
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,63 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/IO/ByteContainerStream.h>
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/RTTI/TypeInfo.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/Utils.h>
#include <AzFramework/Spawnable/Spawnable.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabCatchmentProcessor.h>
#include <AzToolsFramework/Prefab/Spawnable/SpawnableUtils.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
void PrefabCatchmentProcessor::Process(PrefabProcessorContext& context)
{
context.ListPrefabs([&context](AZStd::string_view prefabName, PrefabDom& prefab)
{
ProcessPrefab(context, prefabName, prefab);
});
}
void PrefabCatchmentProcessor::Reflect(AZ::ReflectContext* context)
{
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context); serializeContext != nullptr)
{
serializeContext->Class<PrefabCatchmentProcessor, PrefabProcessor>()->Version(1);
}
}
void PrefabCatchmentProcessor::ProcessPrefab(PrefabProcessorContext& context, AZStd::string_view prefabName, PrefabDom& prefab)
{
AZStd::string uniqueName = prefabName;
uniqueName += '.';
uniqueName += AzFramework::Spawnable::FileExtension;
auto serializer = [](AZStd::vector<uint8_t>& output, const ProcessedObjectStore& object) -> bool
{
AZ::IO::ByteContainerStream stream(&output);
return AZ::Utils::SaveObjectToStream(stream, AZ::DataStream::StreamType::ST_BINARY,
AZStd::any_cast<void>(&object.GetObject()), object.GetObject().type());
};
auto spawnable = SpawnableUtils::CreateSpawnable(prefab);
SpawnableUtils::SortEntitiesByTransformHierarchy(spawnable);
AZStd::any spawnableAny(AZStd::move(spawnable));
context.GetProcessedObjects().emplace_back(AZStd::move(uniqueName), AZStd::move(spawnableAny),
AZStd::move(serializer), AZ::AzTypeInfo<AzFramework::Spawnable>::Uuid());
context.RemovePrefab(prefabName);
}
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,42 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Memory/SystemAllocator.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabProcessor.h>
namespace AZ
{
class ReflectContext;
}
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
class PrefabCatchmentProcessor
: public PrefabProcessor
{
public:
AZ_CLASS_ALLOCATOR(PrefabCatchmentProcessor, AZ::SystemAllocator, 0);
AZ_RTTI(AzToolsFramework::Prefab::PrefabConversionUtils::PrefabCatchmentProcessor,
"{F71E2FBA-22ED-44C7-B4C8-D2CF4B2C7B97}", PrefabProcessor);
~PrefabCatchmentProcessor() override = default;
void Process(PrefabProcessorContext& context) override;
static void Reflect(AZ::ReflectContext* context);
protected:
static void ProcessPrefab(PrefabProcessorContext& context, AZStd::string_view prefabName, PrefabDom& prefab);
};
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,48 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Settings/SettingsRegistry.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabConversionPipeline.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
bool PrefabConversionPipeline::LoadStackProfile(AZStd::string_view stackProfile)
{
m_processors.clear();
AZStd::string registryKey = "/Amazon/Tools/Prefab/Processing/Stack/";
registryKey += stackProfile;
auto registry = AZ::SettingsRegistry::Get();
AZ_Assert(registry, "PrefabConversionPipeline is created before the Settings Registry is available.");
return registry->GetObject(m_processors, registryKey);
}
void PrefabConversionPipeline::ProcessPrefab(PrefabProcessorContext& context)
{
for (auto& processor : m_processors)
{
processor->Process(context);
}
}
void PrefabConversionPipeline::Reflect(AZ::ReflectContext* context)
{
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context); serializeContext != nullptr)
{
serializeContext->Class<PrefabProcessor>()->Version(1);
serializeContext->RegisterGenericType<PrefabProcessorList>();
serializeContext->RegisterGenericType<PrefabProcessorListEntry>();
}
}
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,42 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/string/string_view.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabProcessor.h>
#include <AzToolsFramework/Prefab/Spawnable/SpawnableUtils.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
class PrefabConversionPipeline final
{
public:
AZ_CLASS_ALLOCATOR(PrefabConversionPipeline, AZ::SystemAllocator, 0);
using PrefabProcessorListEntry = AZStd::unique_ptr<PrefabProcessor>;
using PrefabProcessorList = AZStd::vector<PrefabProcessorListEntry>;
bool LoadStackProfile(AZStd::string_view stackProfile);
void ProcessPrefab(PrefabProcessorContext& context);
static void Reflect(AZ::ReflectContext* context);
private:
PrefabProcessorList m_processors;
};
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,31 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabProcessorContext.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
class PrefabProcessor
{
public:
AZ_CLASS_ALLOCATOR(PrefabProcessor, AZ::SystemAllocator, 0);
AZ_RTTI(PrefabProcessor, "{393C95DF-C0DA-4EF0-A081-9CA899649DDD}");
virtual ~PrefabProcessor() = default;
virtual void Process(PrefabProcessorContext& context) = 0;
};
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,84 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzToolsFramework/Prefab/Spawnable/PrefabProcessorContext.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
bool PrefabProcessorContext::AddPrefab(AZStd::string prefabName, PrefabDom prefab)
{
auto result = m_prefabs.emplace(AZStd::move(prefabName), AZStd::move(prefab));
return result.second;
}
bool PrefabProcessorContext::RemovePrefab(AZStd::string_view prefabName)
{
if (!m_isIterating)
{
return m_prefabs.erase(prefabName) > 0;
}
else
{
m_delayedDelete.emplace_back(prefabName);
}
return false;
}
void PrefabProcessorContext::ListPrefabs(const AZStd::function<void(AZStd::string_view, PrefabDom&)>& callback)
{
m_isIterating = true;
for (auto& it : m_prefabs)
{
if (AZStd::find(m_delayedDelete.begin(), m_delayedDelete.end(), it.first) == m_delayedDelete.end())
{
callback(it.first, it.second);
}
}
m_isIterating = false;
// Clear out any prefabs that have been deleted.
for (AZStd::string& deleted : m_delayedDelete)
{
m_prefabs.erase(deleted);
}
m_delayedDelete.clear();
}
void PrefabProcessorContext::ListPrefabs(const AZStd::function<void(AZStd::string_view, const PrefabDom&)>& callback) const
{
for (const auto& it : m_prefabs)
{
callback(it.first, it.second);
}
}
bool PrefabProcessorContext::HasPrefabs() const
{
return !m_prefabs.empty();
}
PrefabProcessorContext::ProcessedObjectStoreContainer& PrefabProcessorContext::GetProcessedObjects()
{
return m_products;
}
const PrefabProcessorContext::ProcessedObjectStoreContainer& PrefabProcessorContext::GetProcessedObjects() const
{
return m_products;
}
const AZ::PlatformTagSet& PrefabProcessorContext::GetPlatformTags() const
{
return m_platformTags;
}
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,58 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Component/ComponentExport.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/functional.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/string/string_view.h>
#include <AzToolsFramework/Prefab/PrefabDomTypes.h>
#include <AzToolsFramework/Prefab/Spawnable/ProcesedObjectStore.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
class PrefabProcessorContext
{
public:
using ProcessedObjectStoreContainer = AZStd::vector<ProcessedObjectStore>;
AZ_CLASS_ALLOCATOR(PrefabProcessorContext, AZ::SystemAllocator, 0);
AZ_RTTI(PrefabProcessorContext, "{C7D77E3A-C544-486B-B774-7C82C38FE22F}");
virtual ~PrefabProcessorContext() = default;
virtual bool AddPrefab(AZStd::string prefabName, PrefabDom prefab);
virtual bool RemovePrefab(AZStd::string_view prefabName);
virtual void ListPrefabs(const AZStd::function<void(AZStd::string_view, PrefabDom&)>& callback);
virtual void ListPrefabs(const AZStd::function<void(AZStd::string_view, const PrefabDom&)>& callback) const;
virtual bool HasPrefabs() const;
virtual ProcessedObjectStoreContainer& GetProcessedObjects();
virtual const ProcessedObjectStoreContainer& GetProcessedObjects() const;
virtual const AZ::PlatformTagSet& GetPlatformTags() const;
protected:
using NamedPrefabContainer = AZStd::unordered_map<AZStd::string, PrefabDom>;
NamedPrefabContainer m_prefabs;
ProcessedObjectStoreContainer m_products;
AZStd::vector<AZStd::string> m_delayedDelete;
AZ::PlatformTagSet m_platformTags;
bool m_isIterating{ false };
};
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,65 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Casting/lossy_cast.h>
#include <AzCore/Math/Uuid.h>
#include <AzToolsFramework/Prefab/Spawnable/ProcesedObjectStore.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
ProcessedObjectStore::ProcessedObjectStore(AZStd::string uniqueId, AZStd::any object, SerializerFunction objectSerializer,
AZ::Data::AssetType assetType)
: m_uniqueId(AZStd::move(uniqueId))
, m_object(AZStd::move(object))
, m_objectSerializer(AZStd::move(objectSerializer))
, m_assetType(AZStd::move(assetType))
{
}
bool ProcessedObjectStore::Serialize(AZStd::vector<uint8_t>& output) const
{
if (m_objectSerializer)
{
return m_objectSerializer(output, *this);
}
else
{
return false;
}
}
const AZStd::any& ProcessedObjectStore::GetObject() const
{
return m_object;
}
AZStd::any ProcessedObjectStore::ReleaseObject()
{
return AZStd::move(m_object);
}
uint32_t ProcessedObjectStore::BuildSubId() const
{
AZ::Uuid subIdHash = AZ::Uuid::CreateData(m_uniqueId.data(), m_uniqueId.size());
return azlossy_caster(subIdHash.GetHash());
}
const AZ::Data::AssetType& ProcessedObjectStore::GetAssetType() const
{
return m_assetType;
}
const AZStd::string& ProcessedObjectStore::GetId() const
{
return m_uniqueId;
}
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,56 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/std/any.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/functional.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
//! Storage for objects created through the Prefab processing pipeline.
//! These typically store the created object for immediate use in the editor plus additional information
//! to allow the Prefab Builder to convert the object into a serialized form and register it with the
//! Asset Database.
class ProcessedObjectStore
{
public:
using SerializerFunction = AZStd::function<bool(AZStd::vector<uint8_t>&, const ProcessedObjectStore&)>;
//! Constructs a new instance.
//! @param uniqueId A name for the object that's unique within the scope of the Prefab. This name will be used to generate a sub id for the product
//! which requires that the name is stable between runs.
//! @param object The object that generated during processing of a Prefab.
//! @param objectSerializer The callback used to convert the provided object into a binary stream.
//! @param assetType The asset type of the asset.
//! @param storagePath The relative path where the asset will be stored if/when committed to disk.
ProcessedObjectStore(AZStd::string uniqueId, AZStd::any object, SerializerFunction objectSerializer, AZ::Data::AssetType assetType);
bool Serialize(AZStd::vector<uint8_t>& output) const;
uint32_t BuildSubId() const;
const AZStd::any& GetObject() const;
AZStd::any ReleaseObject();
const AZ::Data::AssetType& GetAssetType() const;
const AZStd::string& GetId() const;
private:
AZStd::any m_object;
SerializerFunction m_objectSerializer;
AZ::Data::AssetType m_assetType;
AZStd::string m_uniqueId;
};
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,212 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/std/algorithm.h>
#include <AzCore/std/sort.h>
#include <AzToolsFramework/Prefab/Spawnable/SpawnableMetaDataBuilder.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
SpawnableMetaDataBuilder& SpawnableMetaDataBuilder::Add(AZStd::string_view key, bool value)
{
return AddGeneric(key, value);
}
SpawnableMetaDataBuilder& SpawnableMetaDataBuilder::Add(AZStd::string_view key, uint64_t value)
{
return AddGeneric(key, value);
}
SpawnableMetaDataBuilder& SpawnableMetaDataBuilder::Add(AZStd::string_view key, int64_t value)
{
return AddGeneric(key, value);
}
SpawnableMetaDataBuilder& SpawnableMetaDataBuilder::Add(AZStd::string_view key, double value)
{
return AddGeneric(key, value);
}
SpawnableMetaDataBuilder& SpawnableMetaDataBuilder::Add(AZStd::string_view key, AZStd::string value)
{
return AddGeneric(key, AZStd::move(value));
}
SpawnableMetaDataBuilder& SpawnableMetaDataBuilder::AppendArray(AZStd::string_view arrayKey, bool value)
{
return AppendArrayGeneric(arrayKey, value);
}
SpawnableMetaDataBuilder& SpawnableMetaDataBuilder::AppendArray(AZStd::string_view arrayKey, uint64_t value)
{
return AppendArrayGeneric(arrayKey, value);
}
SpawnableMetaDataBuilder& SpawnableMetaDataBuilder::AppendArray(AZStd::string_view arrayKey, int64_t value)
{
return AppendArrayGeneric(arrayKey, value);
}
SpawnableMetaDataBuilder& SpawnableMetaDataBuilder::AppendArray(AZStd::string_view arrayKey, double value)
{
return AppendArrayGeneric(arrayKey, value);
}
SpawnableMetaDataBuilder& SpawnableMetaDataBuilder::AppendArray(AZStd::string_view arrayKey, AZStd::string value)
{
return AppendArrayGeneric(arrayKey, AZStd::move(value));
}
bool SpawnableMetaDataBuilder::Remove(AZStd::string_view key)
{
auto it = m_table.find(HashKey(key));
if (it != m_table.end())
{
RemoveAllEntriesIfArray(key, it);
m_table.erase(it);
return true;
}
return false;
}
bool SpawnableMetaDataBuilder::RemoveArrayEntry(AZStd::string_view arrayKey, uint64_t index)
{
return RemoveArrayEntry(arrayKey, aznumeric_cast<AzFramework::SpawnableMetaDataArrayIndex>(index));
}
bool SpawnableMetaDataBuilder::RemoveArrayEntry(AZStd::string_view arrayKey, AzFramework::SpawnableMetaDataArrayIndex index)
{
auto it = m_table.find(HashKey(arrayKey));
if (it != m_table.end())
{
if (AzFramework::SpawnableMetaDataArraySize* size =
AZStd::get_if<AzFramework::SpawnableMetaDataArraySize>(&it->second); size != nullptr)
{
if (index < *size)
{
AZ::HashValue64 indexHash = HashArrayKey(arrayKey, index);
index++;
for (; index < *size; ++index)
{
AZ::HashValue64 nextIndexHash = HashArrayKey(arrayKey, index);
m_table[indexHash] = AZStd::move(m_table[nextIndexHash]);
indexHash = nextIndexHash;
}
[[maybe_unused]] size_t removedCount = m_table.erase(indexHash);
AZ_Assert(removedCount == 1, "RemoveArrayEntry did not correctly detect an edge case.");
(*size)--;
return true;
}
}
}
return false;
}
size_t SpawnableMetaDataBuilder::GetEntryCount() const
{
return m_table.size();
}
AzFramework::SpawnableMetaData SpawnableMetaDataBuilder::BuildMetaData() const
{
AzFramework::SpawnableMetaData::Table readOnlyTable;
readOnlyTable.reserve(m_table.size());
AZStd::transform(m_table.begin(), m_table.end(), AZStd::back_inserter(readOnlyTable),
[](const auto& entry)
{
return AzFramework::SpawnableMetaData::TableEntry(entry.first, entry.second);
});
AZStd::sort(readOnlyTable.begin(), readOnlyTable.end(),
[](const auto& lhs, const auto& rhs)
{
return lhs.first < rhs.first;
});
return AzFramework::SpawnableMetaData(AZStd::move(readOnlyTable));
}
AZ::HashValue64 SpawnableMetaDataBuilder::HashKey(AZStd::string_view key) const
{
return AZ::TypeHash64(reinterpret_cast<const uint8_t*>(key.data()), aznumeric_cast<uint64_t>(key.length()));
}
AZ::HashValue64 SpawnableMetaDataBuilder::HashArrayKey(AZStd::string_view arrayKey, uint64_t index) const
{
return AZ::TypeHash64(reinterpret_cast<const uint8_t*>(arrayKey.data()), aznumeric_cast<uint64_t>(arrayKey.length()),
aznumeric_caster(AzFramework::SpawnableMetaData::ArrayKeyRoot + index));
}
AZ::HashValue64 SpawnableMetaDataBuilder::HashArrayKey(AZStd::string_view arrayKey, AzFramework::SpawnableMetaDataArrayIndex index) const
{
return HashArrayKey(arrayKey, aznumeric_cast<uint64_t>(index));
}
template<typename T>
SpawnableMetaDataBuilder& SpawnableMetaDataBuilder::AddGeneric(AZStd::string_view key, T&& value)
{
auto keyHash = HashKey(key);
auto it = m_table.find(keyHash);
if (it != m_table.end())
{
RemoveAllEntriesIfArray(key, it);
it->second = AZStd::forward<T>(value);
}
else
{
m_table.emplace(keyHash, AZStd::forward<T>(value));
}
return *this;
}
template<typename T>
SpawnableMetaDataBuilder& SpawnableMetaDataBuilder::AppendArrayGeneric(AZStd::string_view arrayKey, T&& value)
{
auto arrayKeyHash = HashKey(arrayKey);
auto it = m_table.find(arrayKeyHash);
if (it != m_table.end())
{
if (AzFramework::SpawnableMetaDataArraySize* storedValue =
AZStd::get_if<AzFramework::SpawnableMetaDataArraySize>(&it->second); storedValue != nullptr)
{
m_table[HashArrayKey(arrayKey, (*storedValue)++)] = AZStd::forward<T>(value);
}
else
{
it->second = AzFramework::SpawnableMetaDataArraySize{ 1 };
m_table[HashArrayKey(arrayKey, 0)] = AZStd::forward<T>(value);
}
}
else
{
m_table.emplace(arrayKeyHash, AzFramework::SpawnableMetaDataArraySize{ 1 });
m_table[HashArrayKey(arrayKey, 0)] = AZStd::forward<T>(value);
}
return *this;
}
void SpawnableMetaDataBuilder::RemoveAllEntriesIfArray(AZStd::string_view arrayKey, Table::iterator sizeEntry)
{
if (AzFramework::SpawnableMetaDataArraySize* size =
AZStd::get_if<AzFramework::SpawnableMetaDataArraySize>(&sizeEntry->second); size != nullptr)
{
for (AzFramework::SpawnableMetaDataArrayIndex i{ 0 }; i < *size; ++i)
{
[[maybe_unused]] size_t removedCount = m_table.erase(HashArrayKey(arrayKey, i));
AZ_Assert(removedCount == 1, "RemoveArrayEntry did not correctly detect an edge case.");
}
}
}
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,61 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Utils/TypeHash.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzFramework/Spawnable/SpawnableMetaData.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
class SpawnableMetaDataBuilder final
{
public:
SpawnableMetaDataBuilder& Add(AZStd::string_view key, bool value);
SpawnableMetaDataBuilder& Add(AZStd::string_view key, uint64_t value);
SpawnableMetaDataBuilder& Add(AZStd::string_view key, int64_t value);
SpawnableMetaDataBuilder& Add(AZStd::string_view key, double value);
SpawnableMetaDataBuilder& Add(AZStd::string_view key, AZStd::string value);
SpawnableMetaDataBuilder& AppendArray(AZStd::string_view arrayKey, bool value);
SpawnableMetaDataBuilder& AppendArray(AZStd::string_view arrayKey, uint64_t value);
SpawnableMetaDataBuilder& AppendArray(AZStd::string_view arrayKey, int64_t value);
SpawnableMetaDataBuilder& AppendArray(AZStd::string_view arrayKey, double value);
SpawnableMetaDataBuilder& AppendArray(AZStd::string_view arrayKey, AZStd::string value);
bool Remove(AZStd::string_view key);
bool RemoveArrayEntry(AZStd::string_view arrayKey, uint64_t index);
bool RemoveArrayEntry(AZStd::string_view arrayKey, AzFramework::SpawnableMetaDataArrayIndex index);
size_t GetEntryCount() const;
AzFramework::SpawnableMetaData BuildMetaData() const;
private:
using Table = AZStd::unordered_map<AZ::HashValue64, AzFramework::SpawnableMetaData::TableValue>;
AZ::HashValue64 HashKey(AZStd::string_view key) const;
AZ::HashValue64 HashArrayKey(AZStd::string_view arrayKey, uint64_t index) const;
AZ::HashValue64 HashArrayKey(AZStd::string_view arrayKey, AzFramework::SpawnableMetaDataArrayIndex index) const;
template<typename T>
SpawnableMetaDataBuilder& AddGeneric(AZStd::string_view key, T&& value);
template<typename T>
SpawnableMetaDataBuilder& AppendArrayGeneric(AZStd::string_view arrayKey, T&& value);
void RemoveAllEntriesIfArray(AZStd::string_view arrayKey, Table::iterator sizeEntry);
Table m_table;
};
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,219 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzToolsFramework/Prefab/Spawnable/SpawnableUtils.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/EntityUtils.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/std/algorithm.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
#include <AzFramework/Spawnable/Spawnable.h>
namespace AzToolsFramework::Prefab::SpawnableUtils
{
AzFramework::Spawnable CreateSpawnable(const PrefabDom& prefabDom)
{
AzFramework::Spawnable spawnable;
AZStd::unique_ptr<Instance> instance(aznew Instance());
if (!Prefab::PrefabDomUtils::LoadInstanceFromPrefabDom(*instance, prefabDom, false))
{
AZ_Assert(false,
"Failed to Load Prefab Instance from given Prefab DOM while Spawnable creation.");
}
else
{
AzFramework::Spawnable::EntityList& entities = spawnable.GetEntities();
instance->DetachNestedEntities([&entities](AZStd::unique_ptr<AZ::Entity> entity)
{
entities.emplace_back(AZStd::move(entity));
});
}
return spawnable;
}
void OrganizeEntitiesForSorting(
AzFramework::Spawnable::EntityList& entities,
AZStd::unordered_set<AZ::EntityId>& existingEntityIds,
AZStd::unordered_map<AZ::EntityId, AzFramework::Spawnable::EntityList>& parentIdToChildren,
AZStd::vector<AZ::EntityId>& candidateIds,
size_t& removedEntitiesCount)
{
existingEntityIds.clear();
parentIdToChildren.clear();
candidateIds.clear();
removedEntitiesCount = 0;
for (auto& entity : entities)
{
if (!entity)
{
++removedEntitiesCount;
continue;
}
AZ::EntityId entityId = entity->GetId();
if (!entityId.IsValid())
{
AZ_Warning("Entity", false, "Hierarchy sort found entity '%s' with invalid ID", entity->GetName().c_str());
++removedEntitiesCount;
continue;
}
if (!existingEntityIds.insert(entityId).second)
{
AZ_Warning("Entity", false, "Hierarchy sort found multiple entities using same ID as entity '%s' %s",
entity->GetName().c_str(),
entityId.ToString().c_str());
++removedEntitiesCount;
continue;
}
// search for any component that implements the TransformInterface.
// don't use EBus because we support sorting entities that haven't been initialized or activated.
// entities with no transform component will be treated like entities with no parent.
AZ::EntityId parentId;
if (AZ::TransformInterface* transformInterface =
AZ::EntityUtils::FindFirstDerivedComponent<AZ::TransformInterface>(entity.get()))
{
parentId = transformInterface->GetParentId();
if (parentId == entityId)
{
AZ_Warning("Entity", false, "Hierarchy sort found entity parented to itself '%s' %s",
entity->GetName().c_str(),
entityId.ToString().c_str());
parentId.SetInvalid();
}
}
auto& children = parentIdToChildren[parentId];
children.emplace_back(nullptr);
children.back().swap(entity);
}
// clear 'entities', we'll refill it in sorted order.
entities.clear();
// the first candidates should be the parents of the roots.
for (auto& parentChildrenPair : parentIdToChildren)
{
const AZ::EntityId& parentId = parentChildrenPair.first;
// we found a root if parent ID doesn't correspond to any entity in the list
if (existingEntityIds.find(parentId) == existingEntityIds.end())
{
candidateIds.push_back(parentId);
}
}
}
void TraceParentingLoop(
const AZ::EntityId& parentFromLoopId,
const AZStd::unordered_map<AZ::EntityId, AzFramework::Spawnable::EntityList>& parentIdToChildren)
{
// Find name to use in warning message
AZStd::string_view parentFromLoopName;
for (const auto& parentIdChildrenPair : parentIdToChildren)
{
for (const auto& entity : parentIdChildrenPair.second)
{
if (entity->GetId() == parentFromLoopId)
{
parentFromLoopName = entity->GetName();
break;
}
if (!parentFromLoopName.empty())
{
break;
}
}
}
AZ_Warning("Entity", false, "Hierarchy sort found parenting loop involving entity '%.*s' %s",
AZ_STRING_ARG(parentFromLoopName),
parentFromLoopId.ToString().c_str());
}
void SortEntitiesByTransformHierarchy(AzFramework::Spawnable& spawnable)
{
auto& entities = spawnable.GetEntities();
const size_t originalEntityCount = entities.size();
// IDs of those present in 'entities'. Does not include parent ID if parent not found in 'entities'
AZStd::unordered_set<AZ::EntityId> existingEntityIds;
// map children by their parent ID (even if parent not found in 'entities')
AZStd::unordered_map<AZ::EntityId, AzFramework::Spawnable::EntityList> parentIdToChildren;
// use 'candidateIds' to track the parent IDs we're going to process next.
AZStd::vector<AZ::EntityId> candidateIds;
candidateIds.reserve(originalEntityCount + 1);
size_t removedCount = 0;
OrganizeEntitiesForSorting(entities, existingEntityIds, parentIdToChildren, candidateIds, removedCount);
// process candidates until everything is sorted:
// - add candidate's children to the final sorted order
// - add candidate's children to list of candidates, so we can process *their* children in a future loop
// - erase parent/children entry from parentToChildrenIds
// - continue until nothing is left in parentToChildrenIds
for (size_t candidateIndex = 0; !parentIdToChildren.empty(); ++candidateIndex)
{
// if there are no more candidates, but there are still unsorted children, then we have an infinite loop.
// pick an arbitrary parent from the loop to be the next candidate.
if (candidateIndex == candidateIds.size())
{
const AZ::EntityId& parentFromLoopId = parentIdToChildren.begin()->first;
#ifdef AZ_ENABLE_TRACING
TraceParentingLoop(parentFromLoopId, parentIdToChildren);
#endif // AZ_ENABLE_TRACING
candidateIds.push_back(parentFromLoopId);
}
const AZ::EntityId& parentId = candidateIds[candidateIndex];
auto foundChildren = parentIdToChildren.find(parentId);
if (foundChildren != parentIdToChildren.end())
{
for (auto& child : foundChildren->second)
{
candidateIds.push_back(child->GetId());
entities.emplace_back(nullptr);
entities.back().swap(child);
}
parentIdToChildren.erase(foundChildren);
}
}
AZ_Assert(entities.size() + removedCount == originalEntityCount,
"Wrong number of entities after sort. Original entity count = %zu, Sorted entity count = %zu, Removed entity count = %zu",
originalEntityCount,
entities.size(),
removedCount
);
}
} // namespace AzToolsFramework::Prefab::SpawnableUtils
@@ -0,0 +1,23 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzFramework/Spawnable/Spawnable.h>
#include <AzToolsFramework/Prefab/PrefabDomTypes.h>
namespace AzToolsFramework::Prefab::SpawnableUtils
{
AzFramework::Spawnable CreateSpawnable(const PrefabDom& prefabDom);
void SortEntitiesByTransformHierarchy(AzFramework::Spawnable& spawnable);
} // namespace AzToolsFramework::Prefab::SpawnableUtils
@@ -217,10 +217,16 @@ namespace AzToolsFramework
ComponentEntityEditorRequestBus::Event(descendant, &ComponentEntityEditorRequestBus::Events::RefreshVisibilityAndLock);
}
}
AzToolsFramework::Layers::EditorLayerComponentNotificationBus::Broadcast(
&AzToolsFramework::Layers::EditorLayerComponentNotifications::OnLayerComponentActivated, GetEntityId());
}
void EditorLayerComponent::Deactivate()
{
AzToolsFramework::Layers::EditorLayerComponentNotificationBus::Broadcast(
&AzToolsFramework::Layers::EditorLayerComponentNotifications::OnLayerComponentDeactivated, GetEntityId());
AZ::TransformNotificationBus::Handler::BusDisconnect();
}
@@ -207,5 +207,28 @@ namespace AzToolsFramework
virtual void OnNewLayerEntity(const AZ::EntityId& entityId, AZStd::vector<AZ::Component*>& componentsToAdd) = 0;
};
using EditorLayerCreationBus = AZ::EBus<EditorLayerCreationNotification>;
/**
* This is a single bus with multiple listeners, for allowing systems to listen when specific layer component events occur.
*/
class EditorLayerComponentNotifications
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; // multi listener
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; //single bus
/**
* Called when a layer component is activated on a layer entity
* \param entityId The id of the entity whose layer component has been activated.
*/
virtual void OnLayerComponentActivated(AZ::EntityId entityId) = 0;
/**
* Called when a layer component is deactivated on a layer entity
* \param entityId The id of the entity whose layer component has been deactivated.
*/
virtual void OnLayerComponentDeactivated(AZ::EntityId entityId) = 0;
};
using EditorLayerComponentNotificationBus = AZ::EBus<EditorLayerComponentNotifications>;
}
}
@@ -13,6 +13,7 @@
#include <AzToolsFramework/ToolsComponents/EditorNonUniformScaleComponent.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzFramework/Components/NonUniformScaleComponent.h>
#include <AzCore/Math/ToString.h>
namespace AzToolsFramework
{
@@ -43,6 +44,7 @@ namespace AzToolsFramework
->DataElement(
AZ::Edit::UIHandlers::Default, &EditorNonUniformScaleComponent::m_scale, "Non-uniform Scale",
"Non-uniform scale for this entity only (does not propagate through hierarchy)")
->Attribute(AZ::Edit::Attributes::Min, AZ::MinNonUniformScale)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorNonUniformScaleComponent::OnScaleChanged)
;
}
@@ -56,7 +58,6 @@ namespace AzToolsFramework
void EditorNonUniformScaleComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC_CE("SkyCloudService"));
incompatible.push_back(AZ_CRC_CE("DebugDrawObbService"));
incompatible.push_back(AZ_CRC_CE("DebugDrawService"));
incompatible.push_back(AZ_CRC_CE("EMotionFXActorService"));
@@ -72,8 +73,6 @@ namespace AzToolsFramework
incompatible.push_back(AZ_CRC_CE("PhysXShapeColliderService"));
incompatible.push_back(AZ_CRC_CE("PhysXCharacterControllerService"));
incompatible.push_back(AZ_CRC_CE("PhysXRagdollService"));
incompatible.push_back(AZ_CRC_CE("TouchBendingPhysicsService"));
incompatible.push_back(AZ_CRC_CE("WaterVolumeService"));
incompatible.push_back(AZ_CRC_CE("WhiteBoxService"));
incompatible.push_back(AZ_CRC_CE("NavigationAreaService"));
incompatible.push_back(AZ_CRC_CE("GeometryService"));
@@ -81,12 +80,9 @@ namespace AzToolsFramework
incompatible.push_back(AZ_CRC_CE("CompoundShapeService"));
incompatible.push_back(AZ_CRC_CE("CylinderShapeService"));
incompatible.push_back(AZ_CRC_CE("DiskShapeService"));
incompatible.push_back(AZ_CRC_CE("FixedVertexContainerService"));
incompatible.push_back(AZ_CRC_CE("PolygonPrismShapeService"));
incompatible.push_back(AZ_CRC_CE("SphereShapeService"));
incompatible.push_back(AZ_CRC_CE("SplineService"));
incompatible.push_back(AZ_CRC_CE("TubeShapeService"));
incompatible.push_back(AZ_CRC_CE("VariableVertexContainerService"));
}
void EditorNonUniformScaleComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
@@ -113,7 +109,18 @@ namespace AzToolsFramework
void EditorNonUniformScaleComponent::SetScale(const AZ::Vector3& scale)
{
m_scale = scale;
if (scale.GetMinElement() >= AZ::MinNonUniformScale)
{
m_scale = scale;
}
else
{
AZ::Vector3 clampedScale = scale.GetMax(AZ::Vector3(AZ::MinNonUniformScale));
AZ_Warning("Editor Non-uniform Scale Component", false, "SetScale value was clamped from %s to %s for entity %s",
AZ::ToString(scale).c_str(), AZ::ToString(clampedScale).c_str(), GetEntity()->GetName().c_str());
m_scale = clampedScale;
}
m_scaleChangedEvent.Signal(m_scale);
}
void EditorNonUniformScaleComponent::RegisterScaleChangedEvent(AZ::NonUniformScaleChangedEvent::Handler& handler)
@@ -27,6 +27,7 @@
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/ToolsComponents/TransformComponentBus.h>
#include <AzToolsFramework/ToolsComponents/TransformScalePropertyHandler.h>
#include <AzToolsFramework/ViewportSelection/EditorSelectionUtil.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
@@ -163,7 +164,6 @@ namespace AzToolsFramework
, m_parentActivationTransformMode(AZ::TransformConfig::ParentActivationTransformMode::MaintainOriginalRelativeTransform)
, m_cachedWorldTransform(AZ::Transform::Identity())
, m_suppressTransformChangedEvent(false)
, m_netSyncEnabled(false)
, m_interpolatePosition(AZ::InterpolationMode::NoInterpolation)
, m_interpolateRotation(AZ::InterpolationMode::NoInterpolation)
{
@@ -221,6 +221,26 @@ namespace AzToolsFramework
EditorComponentBase::Deactivate();
}
void TransformComponent::BindTransformChangedEventHandler(AZ::TransformChangedEvent::Handler& handler)
{
handler.Connect(m_transformChangedEvent);
}
void TransformComponent::BindParentChangedEventHandler(AZ::ParentChangedEvent::Handler& handler)
{
handler.Connect(m_parentChangedEvent);
}
void TransformComponent::BindChildChangedEventHandler(AZ::ChildChangedEvent::Handler& handler)
{
handler.Connect(m_childChangedEvent);
}
void TransformComponent::NotifyChildChangedEvent(AZ::ChildChangeType changeType, AZ::EntityId entityId)
{
m_childChangedEvent.Signal(changeType, entityId);
}
// This is called when our transform changes directly, or our parent's has changed.
void TransformComponent::OnTransformChanged(const AZ::Transform& /*local*/, const AZ::Transform& world)
{
@@ -278,16 +298,6 @@ namespace AzToolsFramework
m_cachedWorldTransformParent = AZ::EntityId();
}
bool TransformComponent::IsPositionInterpolated()
{
return m_interpolatePosition != AZ::InterpolationMode::NoInterpolation;
}
bool TransformComponent::IsRotationInterpolated()
{
return m_interpolateRotation != AZ::InterpolationMode::NoInterpolation;
}
void TransformComponent::CheckApplyCachedWorldTransform(const AZ::Transform& parentWorld)
{
if (m_parentEntityId != m_cachedWorldTransformParent)
@@ -835,6 +845,7 @@ namespace AzToolsFramework
// This is for Create Entity as child / Drag+drop parent update / add component
EBUS_EVENT(AzToolsFramework::ToolsApplicationEvents::Bus, EntityParentChanged, GetEntityId(), parentId, oldParentId);
EBUS_EVENT_ID(GetEntityId(), AZ::TransformNotificationBus, OnParentChanged, oldParentId, parentId);
m_parentChangedEvent.Signal(oldParentId, parentId);
TransformChanged();
}
@@ -1136,7 +1147,6 @@ namespace AzToolsFramework
{
AZ::TransformConfig configuration;
configuration.m_parentId = m_parentEntityId;
configuration.m_netSyncEnabled = m_netSyncEnabled;
configuration.m_worldTransform = GetWorldTM();
configuration.m_localTransform = GetLocalTM();
configuration.m_parentActivationTransformMode = m_parentActivationTransformMode;
@@ -1241,7 +1251,7 @@ namespace AzToolsFramework
Attribute(AZ::Edit::Attributes::Suffix, " deg")->
Attribute(AZ::Edit::Attributes::ReadOnly, &EditorTransform::m_locked)->
Attribute(AZ::Edit::Attributes::SliceFlags, AZ::Edit::SliceFlags::NotPushableOnSliceRoot)->
DataElement(AZ::Edit::UIHandlers::Default, &EditorTransform::m_scale, "Scale", "Local Scale")->
DataElement(TransformScaleHandler, &EditorTransform::m_scale, "Scale", "Local Scale")->
Attribute(AZ::Edit::Attributes::Step, 0.1f)->
Attribute(AZ::Edit::Attributes::Min, 0.01f)->
Attribute(AZ::Edit::Attributes::ReadOnly, &EditorTransform::m_locked)

Some files were not shown because too many files have changed in this diff Show More