You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1833 lines
92 KiB
C++
1833 lines
92 KiB
C++
/*
|
|
* Copyright (c) Contributors to the Open 3D Engine Project.
|
|
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
*
|
|
*/
|
|
|
|
#include <AzCore/Component/TransformBus.h>
|
|
#include <AzCore/JSON/stringbuffer.h>
|
|
#include <AzCore/JSON/writer.h>
|
|
#include <AzCore/Utils/TypeHash.h>
|
|
|
|
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
|
|
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
|
|
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
|
|
#include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
|
|
#include <AzToolsFramework/Prefab/EditorPrefabComponent.h>
|
|
#include <AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h>
|
|
#include <AzToolsFramework/Prefab/Instance/Instance.h>
|
|
#include <AzToolsFramework/Prefab/Instance/InstanceEntityIdMapper.h>
|
|
#include <AzToolsFramework/Prefab/Instance/InstanceEntityMapperInterface.h>
|
|
#include <AzToolsFramework/Prefab/Instance/InstanceToTemplateInterface.h>
|
|
#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
|
|
#include <AzToolsFramework/Prefab/PrefabLoaderInterface.h>
|
|
#include <AzToolsFramework/Prefab/PrefabPublicHandler.h>
|
|
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
|
|
#include <AzToolsFramework/Prefab/PrefabUndo.h>
|
|
#include <AzToolsFramework/Prefab/PrefabUndoHelpers.h>
|
|
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
|
|
|
|
#include <QString>
|
|
|
|
namespace AzToolsFramework
|
|
{
|
|
namespace Prefab
|
|
{
|
|
void PrefabPublicHandler::RegisterPrefabPublicHandlerInterface()
|
|
{
|
|
m_instanceEntityMapperInterface = AZ::Interface<InstanceEntityMapperInterface>::Get();
|
|
AZ_Assert(m_instanceEntityMapperInterface, "PrefabPublicHandler - Could not retrieve instance of InstanceEntityMapperInterface");
|
|
|
|
m_instanceToTemplateInterface = AZ::Interface<InstanceToTemplateInterface>::Get();
|
|
AZ_Assert(m_instanceToTemplateInterface, "PrefabPublicHandler - Could not retrieve instance of InstanceToTemplateInterface");
|
|
|
|
m_prefabFocusInterface = AZ::Interface<PrefabFocusInterface>::Get();
|
|
AZ_Assert(m_prefabFocusInterface, "Could not get PrefabFocusInterface on PrefabPublicHandler construction.");
|
|
|
|
m_prefabFocusPublicInterface = AZ::Interface<PrefabFocusPublicInterface>::Get();
|
|
AZ_Assert(m_prefabFocusPublicInterface, "Could not get PrefabFocusPublicInterface on PrefabPublicHandler construction.");
|
|
|
|
m_prefabLoaderInterface = AZ::Interface<PrefabLoaderInterface>::Get();
|
|
AZ_Assert(m_prefabLoaderInterface, "Could not get PrefabLoaderInterface on PrefabPublicHandler construction.");
|
|
|
|
m_prefabSystemComponentInterface = AZ::Interface<PrefabSystemComponentInterface>::Get();
|
|
AZ_Assert(m_prefabSystemComponentInterface, "Could not get PrefabSystemComponentInterface on PrefabPublicHandler construction.");
|
|
|
|
m_prefabUndoCache.Initialize();
|
|
|
|
AZ::Interface<PrefabPublicInterface>::Register(this);
|
|
}
|
|
|
|
void PrefabPublicHandler::UnregisterPrefabPublicHandlerInterface()
|
|
{
|
|
AZ::Interface<PrefabPublicInterface>::Unregister(this);
|
|
|
|
m_prefabUndoCache.Destroy();
|
|
}
|
|
|
|
CreatePrefabResult PrefabPublicHandler::CreatePrefabInMemory(const EntityIdList& entityIds, AZ::IO::PathView filePath)
|
|
{
|
|
EntityList inputEntityList, topLevelEntities;
|
|
AZ::EntityId commonRootEntityId;
|
|
InstanceOptionalReference commonRootEntityOwningInstance;
|
|
PrefabOperationResult findCommonRootOutcome = FindCommonRootOwningInstance(
|
|
entityIds, inputEntityList, topLevelEntities, commonRootEntityId, commonRootEntityOwningInstance);
|
|
if (!findCommonRootOutcome.IsSuccess())
|
|
{
|
|
return AZ::Failure(findCommonRootOutcome.TakeError());
|
|
}
|
|
|
|
AZ::EntityId containerEntityId;
|
|
|
|
InstanceOptionalReference instanceToCreate;
|
|
{
|
|
// Initialize Undo Batch object
|
|
ScopedUndoBatch undoBatch("Create Prefab");
|
|
|
|
PrefabDom commonRootInstanceDomBeforeCreate;
|
|
m_instanceToTemplateInterface->GenerateDomForInstance(
|
|
commonRootInstanceDomBeforeCreate, commonRootEntityOwningInstance->get());
|
|
|
|
AZStd::vector<AZ::Entity*> entities;
|
|
AZStd::vector<AZStd::unique_ptr<Instance>> instancePtrs;
|
|
AZStd::vector<Instance*> instances;
|
|
AZStd::unordered_map<Instance*, PrefabDom> nestedInstanceLinkPatchesMap;
|
|
|
|
// Retrieve all entities affected and identify Instances
|
|
PrefabOperationResult retrieveEntitiesAndInstancesOutcome = RetrieveAndSortPrefabEntitiesAndInstances(
|
|
inputEntityList, commonRootEntityOwningInstance->get(), entities, instances);
|
|
if (!retrieveEntitiesAndInstancesOutcome.IsSuccess())
|
|
{
|
|
return AZ::Failure(retrieveEntitiesAndInstancesOutcome.TakeError());
|
|
}
|
|
|
|
AZStd::unordered_map<AZ::EntityId, AZStd::string> oldEntityAliases;
|
|
|
|
// Detach the retrieved entities
|
|
for (AZ::Entity* entity : entities)
|
|
{
|
|
AZ::EntityId entityId = entity->GetId();
|
|
oldEntityAliases.emplace(entityId, commonRootEntityOwningInstance->get().GetEntityAlias(entityId)->get());
|
|
commonRootEntityOwningInstance->get().DetachEntity(entity->GetId()).release();
|
|
}
|
|
|
|
// When we create a prefab with other prefab instances, we have to remove the existing links between the source and
|
|
// target templates of the other instances.
|
|
for (auto& nestedInstance : instances)
|
|
{
|
|
AZStd::unique_ptr<Instance> outInstance = commonRootEntityOwningInstance->get().DetachNestedInstance(nestedInstance->GetInstanceAlias());
|
|
|
|
LinkId detachingInstanceLinkId = nestedInstance->GetLinkId();
|
|
auto linkRef = m_prefabSystemComponentInterface->FindLink(detachingInstanceLinkId);
|
|
AZ_Assert(linkRef.has_value(), "Unable to find link with id '%llu' during prefab creation.", detachingInstanceLinkId);
|
|
|
|
PrefabDomValueReference linkPatches = linkRef->get().GetLinkPatches();
|
|
AZ_Assert(
|
|
linkPatches.has_value(), "Unable to get patches on link with id '%llu' during prefab creation.",
|
|
detachingInstanceLinkId);
|
|
|
|
PrefabDom linkPatchesCopy;
|
|
linkPatchesCopy.CopyFrom(linkPatches->get(), linkPatchesCopy.GetAllocator());
|
|
nestedInstanceLinkPatchesMap.emplace(nestedInstance, AZStd::move(linkPatchesCopy));
|
|
|
|
RemoveLink(outInstance, commonRootEntityOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch());
|
|
|
|
instancePtrs.emplace_back(AZStd::move(outInstance));
|
|
}
|
|
|
|
PrefabUndoHelpers::UpdatePrefabInstance(
|
|
commonRootEntityOwningInstance->get(), "Update prefab instance", commonRootInstanceDomBeforeCreate,
|
|
undoBatch.GetUndoBatch());
|
|
|
|
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)."));
|
|
}
|
|
|
|
// Create the Prefab
|
|
AZ_Assert(filePath.IsAbsolute(), "CreatePrefabInMemory requires an absolute file path.");
|
|
|
|
instanceToCreate = prefabEditorEntityOwnershipInterface->CreatePrefab(
|
|
entities, AZStd::move(instancePtrs), m_prefabLoaderInterface->GenerateRelativePath(filePath),
|
|
commonRootEntityOwningInstance);
|
|
|
|
if (!instanceToCreate)
|
|
{
|
|
return AZ::Failure(AZStd::string("Could not create a new prefab out of the entities provided - internal error "
|
|
"(A null instance is returned)."));
|
|
}
|
|
|
|
containerEntityId = instanceToCreate->get().GetContainerEntityId();
|
|
|
|
// Apply the correct transform to the container for the new instance, and store the patch for use when creating the link.
|
|
PrefabDom patch = ApplyContainerTransformAndGeneratePatch(containerEntityId, commonRootEntityId, topLevelEntities);
|
|
|
|
// Parent the non-container top level entities to the container entity.
|
|
// Parenting the top level container entities will be done during the creation of links.
|
|
for (AZ::Entity* topLevelEntity : topLevelEntities)
|
|
{
|
|
if (!IsInstanceContainerEntity(topLevelEntity->GetId()))
|
|
{
|
|
AZ::TransformBus::Event(topLevelEntity->GetId(), &AZ::TransformBus::Events::SetParent, containerEntityId);
|
|
}
|
|
}
|
|
|
|
// Update the template of the instance since the entities are modified since the template creation.
|
|
Prefab::PrefabDom serializedInstance;
|
|
if (Prefab::PrefabDomUtils::StoreInstanceInPrefabDom(instanceToCreate->get(), serializedInstance))
|
|
{
|
|
m_prefabSystemComponentInterface->UpdatePrefabTemplate(instanceToCreate->get().GetTemplateId(), serializedInstance);
|
|
}
|
|
|
|
instanceToCreate->get().GetNestedInstances([&](AZStd::unique_ptr<Instance>& nestedInstance) {
|
|
AZ_Assert(nestedInstance, "Invalid nested instance found in the new prefab created.");
|
|
|
|
EntityOptionalReference nestedInstanceContainerEntity = nestedInstance->GetContainerEntity();
|
|
AZ_Assert(
|
|
nestedInstanceContainerEntity, "Invalid container entity found for the nested instance used in prefab creation.");
|
|
|
|
AZ::EntityId nestedInstanceContainerEntityId = nestedInstanceContainerEntity->get().GetId();
|
|
PrefabDom previousPatch;
|
|
|
|
// Retrieve the previous patch if it exists
|
|
if (nestedInstanceLinkPatchesMap.contains(nestedInstance.get()))
|
|
{
|
|
previousPatch = AZStd::move(nestedInstanceLinkPatchesMap[nestedInstance.get()]);
|
|
UpdateLinkPatchesWithNewEntityAliases(previousPatch, oldEntityAliases, instanceToCreate->get());
|
|
}
|
|
|
|
// These link creations shouldn't be undone because that would put the template in a non-usable state if a user
|
|
// chooses to instantiate the template after undoing the creation.
|
|
CreateLink(*nestedInstance, instanceToCreate->get().GetTemplateId(), undoBatch.GetUndoBatch(), AZStd::move(previousPatch), false);
|
|
|
|
// If this nested instance's container is a top level entity in the new prefab, re-parent it and apply the change.
|
|
if (AZStd::find(topLevelEntities.begin(), topLevelEntities.end(), &nestedInstanceContainerEntity->get()) != topLevelEntities.end())
|
|
{
|
|
Prefab::PrefabDom containerEntityDomBefore;
|
|
m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomBefore, *nestedInstanceContainerEntity);
|
|
|
|
AZ::TransformBus::Event(nestedInstanceContainerEntityId, &AZ::TransformBus::Events::SetParent, containerEntityId);
|
|
|
|
PrefabDom containerEntityDomAfter;
|
|
m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomAfter, *nestedInstanceContainerEntity);
|
|
|
|
PrefabDom reparentPatch;
|
|
m_instanceToTemplateInterface->GeneratePatch(reparentPatch, containerEntityDomBefore, containerEntityDomAfter);
|
|
m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(reparentPatch, nestedInstanceContainerEntityId);
|
|
|
|
// We won't parent this undo node to the undo batch so that the newly created template and link will remain
|
|
// unaffected by undo actions. This is needed so that any future instantiations of the template will work.
|
|
PrefabUndoLinkUpdate linkUpdate = PrefabUndoLinkUpdate(AZStd::to_string(static_cast<AZ::u64>(nestedInstanceContainerEntityId)));
|
|
linkUpdate.Capture(reparentPatch, nestedInstance->GetLinkId());
|
|
linkUpdate.Redo();
|
|
}
|
|
});
|
|
|
|
// Create a link between the templates of the newly created instance and the instance it's being parented under.
|
|
CreateLink(
|
|
instanceToCreate->get(), commonRootEntityOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch(),
|
|
AZStd::move(patch));
|
|
|
|
// Reset the transform of the container entity so that the new values aren't saved in the new prefab's dom.
|
|
// The new values were saved in the link, so propagation will apply them correctly.
|
|
{
|
|
AZ::Entity* containerEntity = GetEntityById(containerEntityId);
|
|
|
|
PrefabDom containerBeforeReset;
|
|
m_instanceToTemplateInterface->GenerateDomForEntity(containerBeforeReset, *containerEntity);
|
|
|
|
AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetParent, AZ::EntityId());
|
|
AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetLocalTM, AZ::Transform::CreateIdentity());
|
|
|
|
PrefabDom containerAfterReset;
|
|
m_instanceToTemplateInterface->GenerateDomForEntity(containerAfterReset, *containerEntity);
|
|
|
|
// Update the state of the entity
|
|
auto templateId = instanceToCreate->get().GetTemplateId();
|
|
|
|
PrefabDom transformPatch;
|
|
m_instanceToTemplateInterface->GeneratePatch(transformPatch, containerBeforeReset, containerAfterReset);
|
|
m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(transformPatch, containerEntityId);
|
|
|
|
m_instanceToTemplateInterface->PatchTemplate(transformPatch, templateId);
|
|
}
|
|
|
|
// This clears any entities marked as dirty due to reparenting of entities during the process of creating a prefab.
|
|
// We are doing this so that the changes in those entities are not queued up twice for propagation.
|
|
AzToolsFramework::ToolsApplicationRequestBus::Broadcast(
|
|
&AzToolsFramework::ToolsApplicationRequestBus::Events::ClearDirtyEntities);
|
|
|
|
// Select Container Entity
|
|
{
|
|
const EntityIdList selectedEntities = EntityIdList{ containerEntityId };
|
|
auto selectionUndo = aznew SelectionCommand(selectedEntities, "Select Prefab Container Entity");
|
|
selectionUndo->SetParent(undoBatch.GetUndoBatch());
|
|
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::SetSelectedEntities, selectedEntities);
|
|
}
|
|
}
|
|
|
|
return AZ::Success(containerEntityId);
|
|
}
|
|
|
|
CreatePrefabResult PrefabPublicHandler::CreatePrefabInDisk(const EntityIdList& entityIds, AZ::IO::PathView filePath)
|
|
{
|
|
auto result = CreatePrefabInMemory(entityIds, filePath);
|
|
if (result.IsSuccess())
|
|
{
|
|
// Save Template to file
|
|
auto relativePath = m_prefabLoaderInterface->GenerateRelativePath(filePath);
|
|
Prefab::TemplateId templateId = m_prefabSystemComponentInterface->GetTemplateIdFromFilePath(relativePath);
|
|
if (!m_prefabLoaderInterface->SaveTemplateToFile(templateId, filePath))
|
|
{
|
|
AZStd::string_view filePathString(filePath);
|
|
return AZ::Failure(AZStd::string::format(
|
|
"Could not save the newly created prefab to file path %.*s - internal error ",
|
|
AZ_STRING_ARG(filePathString)));
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
PrefabDom PrefabPublicHandler::ApplyContainerTransformAndGeneratePatch(AZ::EntityId containerEntityId, AZ::EntityId parentEntityId, const EntityList& childEntities)
|
|
{
|
|
AZ::Entity* containerEntity = GetEntityById(containerEntityId);
|
|
AZ_Assert(containerEntity, "Invalid container entity passed to ApplyContainerTransformAndGeneratePatch.");
|
|
|
|
// Generate the transform for the container entity out of the top level entities, and set it
|
|
// This step needs to be done before anything is parented to the container, else children position will be wrong
|
|
Prefab::PrefabDom containerEntityDomBefore;
|
|
m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomBefore, *containerEntity);
|
|
|
|
AZ::Vector3 containerEntityTranslation(AZ::Vector3::CreateZero());
|
|
AZ::Quaternion containerEntityRotation(AZ::Quaternion::CreateZero());
|
|
|
|
// Set container entity to be child of common root
|
|
AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetParent, parentEntityId);
|
|
|
|
// Set the transform (translation, rotation) of the container entity
|
|
GenerateContainerEntityTransform(childEntities, containerEntityTranslation, containerEntityRotation);
|
|
AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetLocalTranslation, containerEntityTranslation);
|
|
AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetLocalRotationQuaternion, containerEntityRotation);
|
|
|
|
PrefabDom containerEntityDomAfter;
|
|
m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomAfter, *containerEntity);
|
|
|
|
PrefabDom patch;
|
|
m_instanceToTemplateInterface->GeneratePatch(patch, containerEntityDomBefore, containerEntityDomAfter);
|
|
m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(patch, containerEntityId);
|
|
|
|
// Update the cache - this prevents these changes from being stored in the regular undo/redo nodes
|
|
m_prefabUndoCache.Store(containerEntityId, AZStd::move(containerEntityDomAfter), parentEntityId);
|
|
|
|
return AZStd::move(patch);
|
|
}
|
|
|
|
InstantiatePrefabResult PrefabPublicHandler::InstantiatePrefab(
|
|
AZStd::string_view filePath, AZ::EntityId parent, const AZ::Vector3& position)
|
|
{
|
|
auto prefabEditorEntityOwnershipInterface = AZ::Interface<PrefabEditorEntityOwnershipInterface>::Get();
|
|
if (!prefabEditorEntityOwnershipInterface)
|
|
{
|
|
return AZ::Failure(AZStd::string("Could not instantiate prefab - internal error "
|
|
"(PrefabEditorEntityOwnershipInterface unavailable)."));
|
|
}
|
|
if (!prefabEditorEntityOwnershipInterface->IsRootPrefabAssigned())
|
|
{
|
|
return AZ::Failure(AZStd::string("Could not instantiate prefab - no root prefab assigned. "
|
|
"Currently, prefabs can only be instantiated inside a level"));
|
|
}
|
|
|
|
InstanceOptionalReference instanceToParentUnder;
|
|
|
|
// Get parent entity and owning instance
|
|
if (parent.IsValid())
|
|
{
|
|
instanceToParentUnder = m_instanceEntityMapperInterface->FindOwningInstance(parent);
|
|
}
|
|
|
|
if (!instanceToParentUnder.has_value())
|
|
{
|
|
instanceToParentUnder = prefabEditorEntityOwnershipInterface->GetRootPrefabInstance();
|
|
parent = instanceToParentUnder->get().GetContainerEntityId();
|
|
}
|
|
|
|
//Detect whether this instantiation would produce a cyclical dependency
|
|
auto relativePath = m_prefabLoaderInterface->GenerateRelativePath(filePath);
|
|
Prefab::TemplateId templateId = m_prefabSystemComponentInterface->GetTemplateIdFromFilePath(relativePath);
|
|
|
|
if (templateId == InvalidTemplateId)
|
|
{
|
|
// Load the template from the file
|
|
templateId = m_prefabLoaderInterface->LoadTemplateFromFile(filePath);
|
|
AZ_Assert(templateId != InvalidTemplateId, "Template with source path %.*s couldn't be loaded correctly.", AZ_STRING_ARG(filePath));
|
|
}
|
|
|
|
const PrefabDom& templateDom = m_prefabSystemComponentInterface->FindTemplateDom(templateId);
|
|
AZStd::unordered_set<AZ::IO::Path> templatePaths;
|
|
PrefabDomUtils::GetTemplateSourcePaths(templateDom, templatePaths);
|
|
|
|
if (IsCyclicalDependencyFound(instanceToParentUnder->get(), templatePaths))
|
|
{
|
|
return AZ::Failure(AZStd::string::format(
|
|
"Instantiate Prefab operation aborted - Cyclical dependency detected\n(%s depends on %s).",
|
|
relativePath.Native().c_str(), instanceToParentUnder->get().GetTemplateSourcePath().Native().c_str()));
|
|
}
|
|
|
|
AZ::EntityId containerEntityId;
|
|
{
|
|
// Initialize Undo Batch object
|
|
ScopedUndoBatch undoBatch("Instantiate Prefab");
|
|
|
|
// Instantiate the Prefab
|
|
PrefabDom instanceToParentUnderDomBeforeCreate;
|
|
m_instanceToTemplateInterface->GenerateDomForInstance(instanceToParentUnderDomBeforeCreate, instanceToParentUnder->get());
|
|
|
|
auto instanceToCreate = prefabEditorEntityOwnershipInterface->InstantiatePrefab(relativePath, instanceToParentUnder);
|
|
|
|
if (!instanceToCreate)
|
|
{
|
|
return AZ::Failure(AZStd::string("Could not instantiate the prefab provided - internal error "
|
|
"(A null instance is returned)."));
|
|
}
|
|
|
|
PrefabUndoHelpers::UpdatePrefabInstance(
|
|
instanceToParentUnder->get(), "Update prefab instance", instanceToParentUnderDomBeforeCreate, undoBatch.GetUndoBatch());
|
|
|
|
// Create Link with correct container patches
|
|
containerEntityId = instanceToCreate->get().GetContainerEntityId();
|
|
AZ::Entity* containerEntity = GetEntityById(containerEntityId);
|
|
AZ_Assert(containerEntity, "Invalid container entity detected in InstantiatePrefab.");
|
|
|
|
Prefab::PrefabDom containerEntityDomBefore;
|
|
m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomBefore, *containerEntity);
|
|
|
|
// Set container entity's parent
|
|
AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetParent, parent);
|
|
|
|
// Set the position of the container entity
|
|
AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetWorldTranslation, position);
|
|
|
|
PrefabDom containerEntityDomAfter;
|
|
m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomAfter, *containerEntity);
|
|
|
|
// Generate patch to be stored in the link
|
|
PrefabDom patch;
|
|
m_instanceToTemplateInterface->GeneratePatch(patch, containerEntityDomBefore, containerEntityDomAfter);
|
|
m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(patch, containerEntityId);
|
|
|
|
CreateLink(instanceToCreate->get(), instanceToParentUnder->get().GetTemplateId(), undoBatch.GetUndoBatch(), AZStd::move(patch));
|
|
|
|
AzToolsFramework::ToolsApplicationRequestBus::Broadcast(
|
|
&AzToolsFramework::ToolsApplicationRequestBus::Events::ClearDirtyEntities);
|
|
}
|
|
|
|
return AZ::Success(containerEntityId);
|
|
}
|
|
|
|
PrefabOperationResult PrefabPublicHandler::FindCommonRootOwningInstance(
|
|
const AZStd::vector<AZ::EntityId>& entityIds, EntityList& inputEntityList, EntityList& topLevelEntities,
|
|
AZ::EntityId& commonRootEntityId, InstanceOptionalReference& commonRootEntityOwningInstance)
|
|
{
|
|
// Retrieve entityList from entityIds
|
|
inputEntityList = EntityIdListToEntityList(entityIds);
|
|
|
|
// Remove Level Container Entity if it's part of the list
|
|
AZ::EntityId levelEntityId = GetLevelInstanceContainerEntityId();
|
|
if (levelEntityId.IsValid())
|
|
{
|
|
AZ::Entity* levelEntity = GetEntityById(levelEntityId);
|
|
if (levelEntity)
|
|
{
|
|
auto levelEntityIter = AZStd::find(inputEntityList.begin(), inputEntityList.end(), levelEntity);
|
|
if (levelEntityIter != inputEntityList.end())
|
|
{
|
|
inputEntityList.erase(levelEntityIter);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find common root and top level entities
|
|
bool entitiesHaveCommonRoot = false;
|
|
|
|
AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(
|
|
entitiesHaveCommonRoot, &AzToolsFramework::ToolsApplicationRequests::FindCommonRootInactive, inputEntityList,
|
|
commonRootEntityId, &topLevelEntities);
|
|
|
|
// Bail if entities don't share a common root
|
|
if (!entitiesHaveCommonRoot)
|
|
{
|
|
return AZ::Failure(AZStd::string("Failed to create a prefab: Provided entities do not share a common root."));
|
|
}
|
|
|
|
// Retrieve the owning instance of the common root entity, which will be our new instance's parent instance.
|
|
commonRootEntityOwningInstance = GetOwnerInstanceByEntityId(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");
|
|
return AZ::Success();
|
|
}
|
|
|
|
bool PrefabPublicHandler::IsCyclicalDependencyFound(
|
|
InstanceOptionalConstReference instance, const AZStd::unordered_set<AZ::IO::Path>& templateSourcePaths)
|
|
{
|
|
InstanceOptionalConstReference currentInstance = instance;
|
|
|
|
while (currentInstance.has_value())
|
|
{
|
|
if (templateSourcePaths.contains(currentInstance->get().GetTemplateSourcePath()))
|
|
{
|
|
return true;
|
|
}
|
|
currentInstance = currentInstance->get().GetParentInstance();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void PrefabPublicHandler::CreateLink(
|
|
Instance& sourceInstance, TemplateId targetTemplateId,
|
|
UndoSystem::URSequencePoint* undoBatch, PrefabDom patch, const bool isUndoRedoSupportNeeded)
|
|
{
|
|
LinkId linkId;
|
|
if (isUndoRedoSupportNeeded)
|
|
{
|
|
linkId = PrefabUndoHelpers::CreateLink(
|
|
sourceInstance.GetTemplateId(), targetTemplateId, AZStd::move(patch), sourceInstance.GetInstanceAlias(), undoBatch);
|
|
}
|
|
else
|
|
{
|
|
linkId = m_prefabSystemComponentInterface->CreateLink(
|
|
targetTemplateId, sourceInstance.GetTemplateId(), sourceInstance.GetInstanceAlias(), patch,
|
|
InvalidLinkId);
|
|
m_prefabSystemComponentInterface->PropagateTemplateChanges(targetTemplateId);
|
|
}
|
|
|
|
sourceInstance.SetLinkId(linkId);
|
|
}
|
|
|
|
void PrefabPublicHandler::RemoveLink(
|
|
AZStd::unique_ptr<Instance>& sourceInstance, TemplateId targetTemplateId, UndoSystem::URSequencePoint* undoBatch)
|
|
{
|
|
LinkReference nestedInstanceLink = m_prefabSystemComponentInterface->FindLink(sourceInstance->GetLinkId());
|
|
AZ_Assert(
|
|
nestedInstanceLink.has_value(),
|
|
"A valid link was not found for one of the instances provided as input for the CreatePrefab operation.");
|
|
|
|
PrefabDomReference nestedInstanceLinkDom = nestedInstanceLink->get().GetLinkDom();
|
|
AZ_Assert(
|
|
nestedInstanceLinkDom.has_value(),
|
|
"A valid DOM was not found for the link corresponding to one of the instances provided as input for the "
|
|
"CreatePrefab operation.");
|
|
|
|
PrefabDomValueReference nestedInstanceLinkPatches =
|
|
PrefabDomUtils::FindPrefabDomValue(nestedInstanceLinkDom->get(), PrefabDomUtils::PatchesName);
|
|
AZ_Assert(
|
|
nestedInstanceLinkPatches.has_value(),
|
|
"A valid DOM for patches was not found for the link corresponding to one of the instances provided as input for the "
|
|
"CreatePrefab operation.");
|
|
|
|
PrefabDom patchesCopyForUndoSupport;
|
|
patchesCopyForUndoSupport.CopyFrom(nestedInstanceLinkPatches->get(), patchesCopyForUndoSupport.GetAllocator());
|
|
PrefabUndoHelpers::RemoveLink(
|
|
sourceInstance->GetTemplateId(), targetTemplateId, sourceInstance->GetInstanceAlias(), sourceInstance->GetLinkId(),
|
|
AZStd::move(patchesCopyForUndoSupport), undoBatch);
|
|
}
|
|
|
|
PrefabOperationResult PrefabPublicHandler::SavePrefab(AZ::IO::Path filePath)
|
|
{
|
|
auto templateId = m_prefabSystemComponentInterface->GetTemplateIdFromFilePath(filePath.c_str());
|
|
|
|
if (templateId == InvalidTemplateId)
|
|
{
|
|
return AZ::Failure(
|
|
AZStd::string("SavePrefab - Path error. Path could be invalid, or the prefab may not be loaded in this level."));
|
|
}
|
|
|
|
if (!m_prefabLoaderInterface->SaveTemplate(templateId))
|
|
{
|
|
return AZ::Failure(AZStd::string("Could not save prefab - internal error (Json write operation failure)."));
|
|
}
|
|
|
|
return AZ::Success();
|
|
}
|
|
|
|
PrefabEntityResult PrefabPublicHandler::CreateEntity(AZ::EntityId parentId, const AZ::Vector3& position)
|
|
{
|
|
// If the parent is invalid, parent to the container of the currently focused prefab.
|
|
if (!parentId.IsValid())
|
|
{
|
|
AzFramework::EntityContextId editorEntityContextId = AzToolsFramework::GetEntityContextId();
|
|
parentId = m_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId);
|
|
}
|
|
|
|
InstanceOptionalReference owningInstanceOfParentEntity = GetOwnerInstanceByEntityId(parentId);
|
|
if (!owningInstanceOfParentEntity)
|
|
{
|
|
return AZ::Failure(AZStd::string::format(
|
|
"Cannot add entity because the owning instance of parent entity with id '%llu' could not be found.",
|
|
static_cast<AZ::u64>(parentId)));
|
|
}
|
|
|
|
EntityAlias entityAlias = Instance::GenerateEntityAlias();
|
|
|
|
AliasPath absoluteEntityPath = owningInstanceOfParentEntity->get().GetAbsoluteInstanceAliasPath();
|
|
absoluteEntityPath.Append(entityAlias);
|
|
|
|
AZ::EntityId entityId = InstanceEntityIdMapper::GenerateEntityIdForAliasPath(absoluteEntityPath);
|
|
AZStd::string entityName = AZStd::string::format("Entity%llu", static_cast<AZ::u64>(m_newEntityCounter++));
|
|
|
|
AZ::Entity* entity = aznew AZ::Entity(entityId, entityName.c_str());
|
|
|
|
Instance& entityOwningInstance = owningInstanceOfParentEntity->get();
|
|
|
|
PrefabDom instanceDomBeforeUpdate;
|
|
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBeforeUpdate, entityOwningInstance);
|
|
|
|
ScopedUndoBatch undoBatch("Add Entity");
|
|
|
|
entityOwningInstance.AddEntity(*entity, entityAlias);
|
|
|
|
EditorEntityContextRequestBus::Broadcast(&EditorEntityContextRequestBus::Events::HandleEntitiesAdded, EntityList{entity});
|
|
|
|
AZ::Transform transform = AZ::Transform::CreateIdentity();
|
|
transform.SetTranslation(position);
|
|
|
|
EntityOptionalReference owningInstanceContainerEntity = entityOwningInstance.GetContainerEntity();
|
|
if (owningInstanceContainerEntity && !parentId.IsValid())
|
|
{
|
|
parentId = owningInstanceContainerEntity->get().GetId();
|
|
}
|
|
|
|
if (parentId.IsValid())
|
|
{
|
|
AZ::TransformBus::Event(entityId, &AZ::TransformInterface::SetParent, parentId);
|
|
AZ::TransformBus::Event(entityId, &AZ::TransformInterface::SetLocalTM, transform);
|
|
}
|
|
else
|
|
{
|
|
AZ::TransformBus::Event(entityId, &AZ::TransformInterface::SetWorldTM, transform);
|
|
}
|
|
|
|
|
|
// Select the new entity (and deselect others).
|
|
AzToolsFramework::EntityIdList selection = {entityId};
|
|
|
|
SelectionCommand* selectionCommand = aznew SelectionCommand(selection, "");
|
|
selectionCommand->SetParent(undoBatch.GetUndoBatch());
|
|
|
|
ToolsApplicationRequests::Bus::Broadcast(&ToolsApplicationRequests::SetSelectedEntities, selection);
|
|
|
|
PrefabUndoHelpers::UpdatePrefabInstance(
|
|
entityOwningInstance, "Undo adding entity", instanceDomBeforeUpdate, undoBatch.GetUndoBatch());
|
|
|
|
return AZ::Success(entityId);
|
|
}
|
|
|
|
PrefabOperationResult PrefabPublicHandler::GenerateUndoNodesForEntityChangeAndUpdateCache(
|
|
AZ::EntityId entityId, UndoSystem::URSequencePoint* parentUndoBatch)
|
|
{
|
|
// Create Undo node on entities if they belong to an instance
|
|
InstanceOptionalReference owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(entityId);
|
|
if (!owningInstance.has_value())
|
|
{
|
|
return AZ::Success();
|
|
}
|
|
|
|
AZ::Entity* entity = GetEntityById(entityId);
|
|
if (!entity)
|
|
{
|
|
m_prefabUndoCache.PurgeCache(entityId);
|
|
return AZ::Success();
|
|
}
|
|
|
|
PrefabDom beforeState;
|
|
AZ::EntityId beforeParentId;
|
|
m_prefabUndoCache.Retrieve(entityId, beforeState, beforeParentId);
|
|
|
|
PrefabDom afterState;
|
|
AZ::EntityId afterParentId;
|
|
AZ::TransformBus::EventResult(afterParentId, entityId, &AZ::TransformBus::Events::GetParentId);
|
|
|
|
m_instanceToTemplateInterface->GenerateDomForEntity(afterState, *entity);
|
|
|
|
PrefabDom patch;
|
|
m_instanceToTemplateInterface->GeneratePatch(patch, beforeState, afterState);
|
|
m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(patch, entityId);
|
|
|
|
if (patch.IsArray() && !patch.Empty() && beforeState.IsObject())
|
|
{
|
|
bool isInstanceContainerEntity = IsInstanceContainerEntity(entityId) && !IsLevelInstanceContainerEntity(entityId);
|
|
bool isNewParentOwnedByDifferentInstance = false;
|
|
|
|
if (beforeParentId != afterParentId)
|
|
{
|
|
// If the entity parent changed, verify if the owning instance changed too
|
|
InstanceOptionalReference beforeOwningInstance = m_instanceEntityMapperInterface->FindOwningInstance(beforeParentId);
|
|
InstanceOptionalReference afterOwningInstance = m_instanceEntityMapperInterface->FindOwningInstance(afterParentId);
|
|
|
|
if (beforeOwningInstance.has_value() && afterOwningInstance.has_value() &&
|
|
(&beforeOwningInstance->get() != &afterOwningInstance->get()))
|
|
{
|
|
isNewParentOwnedByDifferentInstance = true;
|
|
|
|
// Detect loops. Assert if an instance has been reparented in such a way to generate circular dependencies.
|
|
AZStd::vector<Instance*> instancesInvolved;
|
|
|
|
if (isInstanceContainerEntity)
|
|
{
|
|
instancesInvolved.push_back(&owningInstance->get());
|
|
}
|
|
else
|
|
{
|
|
// Retrieve all nested instances that are part of the subtree under the current entity.
|
|
EntityList entities;
|
|
PrefabOperationResult retrieveEntitiesAndInstancesOutcome = RetrieveAndSortPrefabEntitiesAndInstances(
|
|
{ entity }, beforeOwningInstance->get(), entities, instancesInvolved);
|
|
if (!retrieveEntitiesAndInstancesOutcome.IsSuccess())
|
|
{
|
|
return retrieveEntitiesAndInstancesOutcome;
|
|
}
|
|
}
|
|
|
|
for (Instance* instance : instancesInvolved)
|
|
{
|
|
const PrefabDom& templateDom =
|
|
m_prefabSystemComponentInterface->FindTemplateDom(instance->GetTemplateId());
|
|
AZStd::unordered_set<AZ::IO::Path> templatePaths;
|
|
PrefabDomUtils::GetTemplateSourcePaths(templateDom, templatePaths);
|
|
|
|
if (IsCyclicalDependencyFound(afterOwningInstance->get(), templatePaths))
|
|
{
|
|
// Cancel the operation by restoring the previous parent
|
|
AZ::TransformBus::Event(entityId, &AZ::TransformBus::Events::SetParent, beforeParentId);
|
|
m_prefabUndoCache.UpdateCache(entityId);
|
|
|
|
// Skip the creation of an undo node
|
|
return AZ::Failure(AZStd::string::format(
|
|
"Reparent Prefab operation aborted - Cyclical dependency detected\n(%s depends on %s).",
|
|
instance->GetTemplateSourcePath().Native().c_str(),
|
|
afterOwningInstance->get().GetTemplateSourcePath().Native().c_str()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isInstanceContainerEntity)
|
|
{
|
|
if (isNewParentOwnedByDifferentInstance)
|
|
{
|
|
Internal_HandleInstanceChange(parentUndoBatch, entity, beforeParentId, afterParentId);
|
|
|
|
PrefabDom afterStateafterReparenting;
|
|
m_instanceToTemplateInterface->GenerateDomForEntity(afterStateafterReparenting, *entity);
|
|
|
|
PrefabDom newPatch;
|
|
m_instanceToTemplateInterface->GeneratePatch(newPatch, afterState, afterStateafterReparenting);
|
|
m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(newPatch, entityId);
|
|
|
|
InstanceOptionalReference owningInstanceAfterReparenting =
|
|
m_instanceEntityMapperInterface->FindOwningInstance(entityId);
|
|
|
|
Internal_HandleContainerOverride(
|
|
parentUndoBatch, entityId, newPatch, owningInstanceAfterReparenting->get().GetLinkId());
|
|
}
|
|
else
|
|
{
|
|
Internal_HandleContainerOverride(
|
|
parentUndoBatch, entityId, patch, owningInstance->get().GetLinkId(), owningInstance->get().GetParentInstance());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Internal_HandleEntityChange(parentUndoBatch, entityId, beforeState, afterState, owningInstance);
|
|
|
|
if (isNewParentOwnedByDifferentInstance)
|
|
{
|
|
Internal_HandleInstanceChange(parentUndoBatch, entity, beforeParentId, afterParentId);
|
|
}
|
|
}
|
|
}
|
|
|
|
m_prefabUndoCache.UpdateCache(entityId);
|
|
|
|
return AZ::Success();
|
|
}
|
|
|
|
void PrefabPublicHandler::Internal_HandleContainerOverride(
|
|
UndoSystem::URSequencePoint* undoBatch, AZ::EntityId entityId, const PrefabDom& patch,
|
|
const LinkId linkId, InstanceOptionalReference parentInstance)
|
|
{
|
|
// Save these changes as patches to the link
|
|
PrefabUndoLinkUpdate* linkUpdate = aznew PrefabUndoLinkUpdate(AZStd::to_string(static_cast<AZ::u64>(entityId)));
|
|
linkUpdate->SetParent(undoBatch);
|
|
linkUpdate->Capture(patch, linkId);
|
|
|
|
linkUpdate->Redo(parentInstance);
|
|
}
|
|
|
|
void PrefabPublicHandler::Internal_HandleEntityChange(
|
|
UndoSystem::URSequencePoint* undoBatch, AZ::EntityId entityId, PrefabDom& beforeState,
|
|
PrefabDom& afterState, InstanceOptionalReference instance)
|
|
{
|
|
// Update the state of the entity
|
|
PrefabUndoEntityUpdate* state = aznew PrefabUndoEntityUpdate(AZStd::to_string(static_cast<AZ::u64>(entityId)));
|
|
state->SetParent(undoBatch);
|
|
state->Capture(beforeState, afterState, entityId);
|
|
|
|
state->Redo(instance);
|
|
}
|
|
|
|
void PrefabPublicHandler::Internal_HandleInstanceChange(
|
|
UndoSystem::URSequencePoint* undoBatch, AZ::Entity* entity, AZ::EntityId beforeParentId, AZ::EntityId afterParentId)
|
|
{
|
|
// If the entity parent changed, verify if the owning instance changed too
|
|
InstanceOptionalReference beforeOwningInstance = m_instanceEntityMapperInterface->FindOwningInstance(beforeParentId);
|
|
InstanceOptionalReference afterOwningInstance = m_instanceEntityMapperInterface->FindOwningInstance(afterParentId);
|
|
|
|
EntityList entities;
|
|
AZStd::vector<Instance*> instances;
|
|
|
|
// Retrieve all descendant entities and instances of this entity that belonged to the same owning instance.
|
|
PrefabOperationResult retrieveEntitiesAndInstancesOutcome = RetrieveAndSortPrefabEntitiesAndInstances(
|
|
{ entity }, beforeOwningInstance->get(), entities, instances);
|
|
AZ_Error("Prefab", retrieveEntitiesAndInstancesOutcome.IsSuccess(), retrieveEntitiesAndInstancesOutcome.GetError().data());
|
|
|
|
AZStd::vector<AZStd::unique_ptr<Instance>> instanceUniquePtrs;
|
|
AZStd::vector<AZStd::pair<Instance*, PrefabDom>> instancePatches;
|
|
|
|
// Remove Entities and Instances from the prior instance
|
|
{
|
|
// Remove Instances
|
|
for (Instance* nestedInstance : instances)
|
|
{
|
|
auto linkRef = m_prefabSystemComponentInterface->FindLink(nestedInstance->GetLinkId());
|
|
|
|
PrefabDom oldLinkPatches;
|
|
|
|
if (linkRef.has_value())
|
|
{
|
|
auto patches = linkRef->get().GetLinkPatches();
|
|
if (patches.has_value())
|
|
{
|
|
oldLinkPatches.CopyFrom(patches->get(), oldLinkPatches.GetAllocator());
|
|
}
|
|
}
|
|
|
|
auto nestedInstanceUniquePtr = beforeOwningInstance->get().DetachNestedInstance(nestedInstance->GetInstanceAlias());
|
|
RemoveLink(nestedInstanceUniquePtr, beforeOwningInstance->get().GetTemplateId(), undoBatch);
|
|
|
|
instancePatches.emplace_back(AZStd::make_pair(nestedInstanceUniquePtr.get(), AZStd::move(oldLinkPatches)));
|
|
instanceUniquePtrs.emplace_back(AZStd::move(nestedInstanceUniquePtr));
|
|
}
|
|
|
|
// Get the previous state of the prior instance for undo/redo purposes
|
|
PrefabDom beforeInstanceDomBeforeRemoval;
|
|
m_instanceToTemplateInterface->GenerateDomForInstance(beforeInstanceDomBeforeRemoval, beforeOwningInstance->get());
|
|
|
|
// Remove Entities
|
|
for (AZ::Entity* nestedEntity : entities)
|
|
{
|
|
beforeOwningInstance->get().DetachEntity(nestedEntity->GetId()).release();
|
|
}
|
|
|
|
// Create the Update node for the prior owning instance
|
|
// Instance removal will be taken care of from the RemoveLink function for undo/redo purposes
|
|
PrefabUndoHelpers::UpdatePrefabInstance(
|
|
beforeOwningInstance->get(), "Update prior prefab instance", beforeInstanceDomBeforeRemoval, undoBatch);
|
|
}
|
|
|
|
// Add Entities and Instances to new instance
|
|
{
|
|
// Add Instances
|
|
for (auto& instanceUniquePtr : instanceUniquePtrs)
|
|
{
|
|
afterOwningInstance->get().AddInstance(AZStd::move(instanceUniquePtr));
|
|
}
|
|
|
|
// Create Links
|
|
for (auto& instanceInfo : instancePatches)
|
|
{
|
|
// Add a new link with the old dom
|
|
CreateLink(
|
|
*instanceInfo.first, afterOwningInstance->get().GetTemplateId(), undoBatch,
|
|
AZStd::move(instanceInfo.second));
|
|
}
|
|
|
|
// Get the previous state of the new instance for undo/redo purposes
|
|
PrefabDom afterInstanceDomBeforeAdd;
|
|
m_instanceToTemplateInterface->GenerateDomForInstance(afterInstanceDomBeforeAdd, afterOwningInstance->get());
|
|
|
|
// Add Entities
|
|
for (AZ::Entity* nestedEntity : entities)
|
|
{
|
|
afterOwningInstance->get().AddEntity(*nestedEntity);
|
|
}
|
|
|
|
// Create the Update node for the new owning instance
|
|
PrefabUndoHelpers::UpdatePrefabInstance(
|
|
afterOwningInstance->get(), "Update new prefab instance", afterInstanceDomBeforeAdd, undoBatch);
|
|
}
|
|
}
|
|
|
|
bool PrefabPublicHandler::IsInstanceContainerEntity(AZ::EntityId entityId) const
|
|
{
|
|
InstanceOptionalReference owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(entityId);
|
|
return owningInstance && (owningInstance->get().GetContainerEntityId() == entityId);
|
|
}
|
|
|
|
bool PrefabPublicHandler::IsLevelInstanceContainerEntity(AZ::EntityId entityId) const
|
|
{
|
|
// Get owning instance
|
|
InstanceOptionalReference owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(entityId);
|
|
|
|
// Get level root instance
|
|
auto prefabEditorEntityOwnershipInterface = AZ::Interface<PrefabEditorEntityOwnershipInterface>::Get();
|
|
if (!prefabEditorEntityOwnershipInterface)
|
|
{
|
|
AZ_Assert(
|
|
false,
|
|
"Could not get owning instance of common root entity :"
|
|
"PrefabEditorEntityOwnershipInterface unavailable.");
|
|
}
|
|
InstanceOptionalReference levelInstance = prefabEditorEntityOwnershipInterface->GetRootPrefabInstance();
|
|
|
|
return owningInstance
|
|
&& levelInstance
|
|
&& (&owningInstance->get() == &levelInstance->get())
|
|
&& (owningInstance->get().GetContainerEntityId() == entityId);
|
|
}
|
|
|
|
AZ::EntityId PrefabPublicHandler::GetInstanceContainerEntityId(AZ::EntityId entityId) const
|
|
{
|
|
AZ::Entity* entity = GetEntityById(entityId);
|
|
if (entity)
|
|
{
|
|
InstanceOptionalReference owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(entity->GetId());
|
|
if (owningInstance)
|
|
{
|
|
return owningInstance->get().GetContainerEntityId();
|
|
}
|
|
}
|
|
|
|
return AZ::EntityId();
|
|
}
|
|
|
|
AZ::EntityId PrefabPublicHandler::GetLevelInstanceContainerEntityId() const
|
|
{
|
|
auto prefabEditorEntityOwnershipInterface = AZ::Interface<PrefabEditorEntityOwnershipInterface>::Get();
|
|
if (!prefabEditorEntityOwnershipInterface)
|
|
{
|
|
AZ_Assert(
|
|
false,
|
|
"Could not get owning instance of common root entity :"
|
|
"PrefabEditorEntityOwnershipInterface unavailable.");
|
|
return AZ::EntityId();
|
|
}
|
|
|
|
auto rootInstance = prefabEditorEntityOwnershipInterface->GetRootPrefabInstance();
|
|
|
|
if (!rootInstance.has_value())
|
|
{
|
|
return AZ::EntityId();
|
|
}
|
|
|
|
return rootInstance->get().GetContainerEntityId();
|
|
}
|
|
|
|
AZ::IO::Path PrefabPublicHandler::GetOwningInstancePrefabPath(AZ::EntityId entityId) const
|
|
{
|
|
AZ::IO::Path path;
|
|
InstanceOptionalReference instance = GetOwnerInstanceByEntityId(entityId);
|
|
|
|
if (instance.has_value())
|
|
{
|
|
path = instance->get().GetTemplateSourcePath();
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
PrefabRequestResult PrefabPublicHandler::HasUnsavedChanges(AZ::IO::Path prefabFilePath) const
|
|
{
|
|
auto templateId = m_prefabSystemComponentInterface->GetTemplateIdFromFilePath(prefabFilePath.c_str());
|
|
|
|
if (templateId == InvalidTemplateId)
|
|
{
|
|
return AZ::Failure(AZStd::string("HasUnsavedChanges - Path error. Path could be invalid, or the prefab may not be loaded in this level."));
|
|
}
|
|
|
|
return AZ::Success(m_prefabSystemComponentInterface->IsTemplateDirty(templateId));
|
|
}
|
|
|
|
PrefabOperationResult PrefabPublicHandler::DeleteEntitiesInInstance(const EntityIdList& entityIds)
|
|
{
|
|
return DeleteFromInstance(entityIds, false);
|
|
}
|
|
|
|
PrefabOperationResult PrefabPublicHandler::DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds)
|
|
{
|
|
return DeleteFromInstance(entityIds, true);
|
|
}
|
|
|
|
PrefabOperationResult PrefabPublicHandler::DuplicateEntitiesInInstance(const EntityIdList& entityIds)
|
|
{
|
|
if (entityIds.empty())
|
|
{
|
|
return AZ::Failure(AZStd::string("No entities to duplicate."));
|
|
}
|
|
|
|
const EntityIdList entityIdsNoFocusContainer = GenerateEntityIdListWithoutFocusedInstanceContainer(entityIds);
|
|
if (entityIdsNoFocusContainer.empty())
|
|
{
|
|
return AZ::Failure(AZStd::string("No entities to duplicate because only instance selected is the container entity of the focused instance."));
|
|
}
|
|
|
|
if (!EntitiesBelongToSameInstance(entityIdsNoFocusContainer))
|
|
{
|
|
return AZ::Failure(AZStd::string("Cannot duplicate multiple entities belonging to different instances with one operation."
|
|
"Change your selection to contain entities in the same instance."));
|
|
}
|
|
|
|
// We've already verified the entities are all owned by the same instance,
|
|
// so we can just retrieve our instance from the first entity in the list.
|
|
AZ::EntityId firstEntityIdToDuplicate = entityIdsNoFocusContainer[0];
|
|
InstanceOptionalReference commonOwningInstance = GetOwnerInstanceByEntityId(firstEntityIdToDuplicate);
|
|
if (!commonOwningInstance.has_value())
|
|
{
|
|
return AZ::Failure(AZStd::string("Failed to duplicate : Couldn't get a valid owning instance for the common root entity of the entities provided."));
|
|
}
|
|
|
|
// If the first entity id is a container entity id, then we need to mark its parent as the common owning instance
|
|
// This is because containers, despite representing the nested instance in the parent, are owned by the child.
|
|
if (commonOwningInstance->get().GetContainerEntityId() == firstEntityIdToDuplicate)
|
|
{
|
|
commonOwningInstance = commonOwningInstance->get().GetParentInstance();
|
|
}
|
|
if (!commonOwningInstance.has_value())
|
|
{
|
|
return AZ::Failure(AZStd::string("Failed to duplicate : Couldn't get a valid owning instance for the common root entity of the entities provided."));
|
|
}
|
|
|
|
// This will cull out any entities that have ancestors in the list, since we will end up duplicating
|
|
// the full nested hierarchy with what is returned from RetrieveAndSortPrefabEntitiesAndInstances
|
|
AzToolsFramework::EntityIdSet duplicationSet = AzToolsFramework::GetCulledEntityHierarchy(entityIdsNoFocusContainer);
|
|
|
|
AZ_PROFILE_FUNCTION(AzToolsFramework);
|
|
|
|
ScopedUndoBatch undoBatch("Duplicate Entities");
|
|
|
|
{
|
|
AZ_PROFILE_SCOPE(AzToolsFramework, "DuplicateEntitiesInInstance::UndoCaptureAndDuplicateEntities");
|
|
|
|
AZStd::vector<AZ::Entity*> entities;
|
|
AZStd::vector<Instance*> instances;
|
|
|
|
EntityList inputEntityList = EntityIdSetToEntityList(duplicationSet);
|
|
PrefabOperationResult retrieveEntitiesAndInstancesOutcome =
|
|
RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonOwningInstance->get(), entities, instances);
|
|
|
|
if (!retrieveEntitiesAndInstancesOutcome.IsSuccess())
|
|
{
|
|
return AZStd::move(retrieveEntitiesAndInstancesOutcome);
|
|
}
|
|
|
|
// Take a snapshot of the instance DOM before we manipulate it
|
|
PrefabDom instanceDomBefore;
|
|
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, commonOwningInstance->get());
|
|
|
|
// Make a copy of our before instance DOM where we will add our duplicated entities and/or instances
|
|
PrefabDom instanceDomAfter;
|
|
instanceDomAfter.CopyFrom(instanceDomBefore, instanceDomAfter.GetAllocator());
|
|
|
|
EntityIdList duplicatedEntityAndInstanceIds;
|
|
|
|
// Duplicate any nested entities and instances as requested
|
|
AZStd::unordered_map<InstanceAlias, Instance*> newInstanceAliasToOldInstanceMap;
|
|
AZStd::unordered_map<EntityAlias, EntityAlias> duplicateEntityAliasMap;
|
|
DuplicateNestedEntitiesInInstance(commonOwningInstance->get(),
|
|
entities, instanceDomAfter, duplicatedEntityAndInstanceIds, duplicateEntityAliasMap);
|
|
|
|
PrefabUndoInstance* command = aznew PrefabUndoInstance("Entity/Instance duplication");
|
|
command->SetParent(undoBatch.GetUndoBatch());
|
|
command->Capture(instanceDomBefore, instanceDomAfter, commonOwningInstance->get().GetTemplateId());
|
|
command->RedoBatched();
|
|
|
|
DuplicateNestedInstancesInInstance(commonOwningInstance->get(),
|
|
instances, instanceDomAfter, duplicatedEntityAndInstanceIds, newInstanceAliasToOldInstanceMap);
|
|
|
|
// Create links for our duplicated instances (if any were duplicated)
|
|
for (auto [newInstanceAlias, oldInstance] : newInstanceAliasToOldInstanceMap)
|
|
{
|
|
LinkId oldLinkId = oldInstance->GetLinkId();
|
|
auto linkRef = m_prefabSystemComponentInterface->FindLink(oldLinkId);
|
|
AZ_Assert(
|
|
linkRef.has_value(), "Unable to find link with id '%llu' during instance duplication.",
|
|
oldLinkId);
|
|
|
|
PrefabDomValueReference linkPatches = linkRef->get().GetLinkPatches();
|
|
AZ_Assert(
|
|
linkPatches.has_value(), "Link with id '%llu' is missing patches.",
|
|
oldLinkId);
|
|
|
|
PrefabDom linkPatchesCopy;
|
|
linkPatchesCopy.CopyFrom(linkPatches->get(), linkPatchesCopy.GetAllocator());
|
|
|
|
// If the instance was duplicated as part of an ancestor's nested hierarchy, the container's parent patch
|
|
// will need to be refreshed to point to the new duplicated parent entity
|
|
auto oldInstanceContainerEntityId = oldInstance->GetContainerEntityId();
|
|
AZ_Assert(oldInstanceContainerEntityId.IsValid(), "Instance returned invalid Container Entity Id");
|
|
|
|
AZ::EntityId previousParentEntityId;
|
|
AZ::TransformBus::EventResult(previousParentEntityId, oldInstanceContainerEntityId, &AZ::TransformBus::Events::GetParentId);
|
|
|
|
if (previousParentEntityId.IsValid() && AZStd::find(duplicatedEntityAndInstanceIds.begin(), duplicatedEntityAndInstanceIds.end(), previousParentEntityId))
|
|
{
|
|
auto oldParentAlias = commonOwningInstance->get().GetEntityAlias(previousParentEntityId);
|
|
if (oldParentAlias.has_value() && duplicateEntityAliasMap.contains(oldParentAlias->get()))
|
|
{
|
|
// Get the dom into a QString for search/replace purposes
|
|
rapidjson::StringBuffer buffer;
|
|
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
|
|
linkPatchesCopy.Accept(writer);
|
|
|
|
QString linkPatchesString(buffer.GetString());
|
|
|
|
ReplaceOldAliases(linkPatchesString, oldParentAlias->get(), duplicateEntityAliasMap[oldParentAlias->get()]);
|
|
|
|
linkPatchesCopy.Parse(linkPatchesString.toUtf8().constData());
|
|
}
|
|
}
|
|
|
|
PrefabUndoHelpers::CreateLink(
|
|
oldInstance->GetTemplateId(), commonOwningInstance->get().GetTemplateId(),
|
|
AZStd::move(linkPatchesCopy), newInstanceAlias, undoBatch.GetUndoBatch());
|
|
}
|
|
|
|
// Select the duplicated entities/instances
|
|
auto selectionUndo = aznew SelectionCommand(duplicatedEntityAndInstanceIds, "Select Duplicated Entities/Instances");
|
|
selectionUndo->SetParent(undoBatch.GetUndoBatch());
|
|
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::SetSelectedEntities, duplicatedEntityAndInstanceIds);
|
|
}
|
|
|
|
return AZ::Success();
|
|
}
|
|
|
|
PrefabOperationResult PrefabPublicHandler::DeleteFromInstance(const EntityIdList& entityIds, bool deleteDescendants)
|
|
{
|
|
// Remove the container entity of the focused prefab from the list, if it is included.
|
|
const EntityIdList entityIdsNoFocusContainer = GenerateEntityIdListWithoutFocusedInstanceContainer(entityIds);
|
|
|
|
if (entityIdsNoFocusContainer.empty())
|
|
{
|
|
return AZ::Success();
|
|
}
|
|
|
|
// All entities in this list need to belong to the same prefab instance for the operation to be valid.
|
|
if (!EntitiesBelongToSameInstance(entityIdsNoFocusContainer))
|
|
{
|
|
return AZ::Failure(AZStd::string("Cannot delete multiple entities belonging to different instances with one operation."));
|
|
}
|
|
|
|
AZ::EntityId firstEntityIdToDelete = entityIdsNoFocusContainer[0];
|
|
InstanceOptionalReference commonOwningInstance = GetOwnerInstanceByEntityId(firstEntityIdToDelete);
|
|
|
|
// If the first entity id is a container entity id, then we need to mark its parent as the common owning instance because you
|
|
// cannot delete an instance from itself.
|
|
if (commonOwningInstance->get().GetContainerEntityId() == firstEntityIdToDelete)
|
|
{
|
|
commonOwningInstance = commonOwningInstance->get().GetParentInstance();
|
|
}
|
|
|
|
// We only allow explicit deletions for entities inside the currently focused prefab.
|
|
AzFramework::EntityContextId editorEntityContextId = AzToolsFramework::GetEntityContextId();
|
|
if (&m_prefabFocusInterface->GetFocusedPrefabInstance(editorEntityContextId)->get() != &commonOwningInstance->get())
|
|
{
|
|
return AZ::Failure(AZStd::string("Cannot delete entities belonging to an instance that is not being edited."));
|
|
}
|
|
|
|
// Retrieve entityList from entityIds
|
|
EntityList inputEntityList = EntityIdListToEntityList(entityIdsNoFocusContainer);
|
|
|
|
AZ_PROFILE_FUNCTION(AzToolsFramework);
|
|
|
|
ScopedUndoBatch undoBatch("Delete Selected");
|
|
|
|
// In order to undo DeleteSelected, we have to create a selection command which selects the current selection
|
|
// and then add the deletion as children.
|
|
// Commands always execute themselves first and then their children (when going forwards)
|
|
// and do the opposite when going backwards.
|
|
EntityIdList selectedEntities;
|
|
ToolsApplicationRequestBus::BroadcastResult(selectedEntities, &ToolsApplicationRequests::GetSelectedEntities);
|
|
SelectionCommand* selCommand = aznew SelectionCommand(selectedEntities, "Delete Entities");
|
|
|
|
// We insert a "deselect all" command before we delete the entities. This ensures the delete operations aren't changing
|
|
// selection state, which triggers expensive UI updates. By deselecting up front, we are able to do those expensive
|
|
// UI updates once at the start instead of once for each entity.
|
|
{
|
|
EntityIdList deselection;
|
|
SelectionCommand* deselectAllCommand = aznew SelectionCommand(deselection, "Deselect Entities");
|
|
deselectAllCommand->SetParent(selCommand);
|
|
}
|
|
|
|
{
|
|
AZ_PROFILE_SCOPE(AzToolsFramework, "Internal::DeleteEntities:UndoCaptureAndPurgeEntities");
|
|
|
|
Prefab::PrefabDom instanceDomBefore;
|
|
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, commonOwningInstance->get());
|
|
|
|
if (deleteDescendants)
|
|
{
|
|
AZStd::vector<AZ::Entity*> entities;
|
|
AZStd::vector<Instance*> instances;
|
|
|
|
PrefabOperationResult retrieveEntitiesAndInstancesOutcome =
|
|
RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonOwningInstance->get(), entities, instances);
|
|
|
|
if (!retrieveEntitiesAndInstancesOutcome.IsSuccess())
|
|
{
|
|
return AZStd::move(retrieveEntitiesAndInstancesOutcome);
|
|
}
|
|
|
|
for (AZ::Entity* entity : entities)
|
|
{
|
|
commonOwningInstance->get().DetachEntity(entity->GetId()).release();
|
|
AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationRequests::DeleteEntity, entity->GetId());
|
|
}
|
|
|
|
for (auto& nestedInstance : instances)
|
|
{
|
|
AZStd::unique_ptr<Instance> outInstance = commonOwningInstance->get().DetachNestedInstance(nestedInstance->GetInstanceAlias());
|
|
RemoveLink(outInstance, commonOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch());
|
|
outInstance.reset();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (AZ::EntityId entityId : entityIdsNoFocusContainer)
|
|
{
|
|
InstanceOptionalReference owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(entityId);
|
|
// If this is the container entity, it actually represents the instance so get its owner
|
|
if (owningInstance->get().GetContainerEntityId() == entityId)
|
|
{
|
|
auto instancePtr = commonOwningInstance->get().DetachNestedInstance(owningInstance->get().GetInstanceAlias());
|
|
RemoveLink(instancePtr, commonOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch());
|
|
}
|
|
else
|
|
{
|
|
commonOwningInstance->get().DetachEntity(entityId);
|
|
AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationRequests::DeleteEntity, entityId);
|
|
}
|
|
}
|
|
}
|
|
|
|
Prefab::PrefabDom instanceDomAfter;
|
|
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomAfter, commonOwningInstance->get());
|
|
|
|
PrefabUndoInstance* command = aznew PrefabUndoInstance("Instance deletion");
|
|
command->Capture(instanceDomBefore, instanceDomAfter, commonOwningInstance->get().GetTemplateId());
|
|
command->SetParent(selCommand);
|
|
}
|
|
|
|
selCommand->SetParent(undoBatch.GetUndoBatch());
|
|
{
|
|
AZ_PROFILE_SCOPE(AzToolsFramework, "Internal::DeleteEntities:RunRedo");
|
|
selCommand->RunRedo();
|
|
}
|
|
|
|
return AZ::Success();
|
|
}
|
|
|
|
PrefabOperationResult PrefabPublicHandler::DetachPrefab(const AZ::EntityId& containerEntityId)
|
|
{
|
|
if (!containerEntityId.IsValid())
|
|
{
|
|
return AZ::Failure(AZStd::string("Cannot detach Prefab Instance with invalid container entity."));
|
|
}
|
|
|
|
auto editorEntityContextId = AzFramework::EntityContextId::CreateNull();
|
|
EditorEntityContextRequestBus::BroadcastResult(editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId);
|
|
|
|
if (containerEntityId == m_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId))
|
|
{
|
|
return AZ::Failure(AZStd::string("Cannot detach focused Prefab Instance."));
|
|
}
|
|
|
|
InstanceOptionalReference owningInstance = GetOwnerInstanceByEntityId(containerEntityId);
|
|
if (owningInstance->get().GetContainerEntityId() != containerEntityId)
|
|
{
|
|
return AZ::Failure(AZStd::string("Input entity should be its owning Instance's container entity."));
|
|
}
|
|
|
|
AZ_PROFILE_FUNCTION(AzToolsFramework);
|
|
|
|
{
|
|
AZ_PROFILE_SCOPE(AzToolsFramework, "Internal::DetachPrefab:UndoCapture");
|
|
|
|
ScopedUndoBatch undoBatch("Detach Prefab");
|
|
|
|
InstanceOptionalReference getParentInstanceResult = owningInstance->get().GetParentInstance();
|
|
AZ_Assert(getParentInstanceResult.has_value(), "Can't get parent Instance from Instance of given container entity.");
|
|
|
|
auto& parentInstance = getParentInstanceResult->get();
|
|
const auto parentTemplateId = parentInstance.GetTemplateId();
|
|
|
|
{
|
|
auto instancePtr = parentInstance.DetachNestedInstance(owningInstance->get().GetInstanceAlias());
|
|
AZ_Assert(instancePtr, "Can't detach selected Instance from its parent Instance.");
|
|
|
|
RemoveLink(instancePtr, parentTemplateId, undoBatch.GetUndoBatch());
|
|
|
|
Prefab::PrefabDom instanceDomBefore;
|
|
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, parentInstance);
|
|
|
|
AZStd::unordered_map<AZ::EntityId, AZStd::string> oldEntityAliases;
|
|
oldEntityAliases.emplace(containerEntityId, instancePtr->GetEntityAlias(containerEntityId)->get());
|
|
|
|
auto containerEntityPtr = instancePtr->DetachContainerEntity();
|
|
auto& containerEntity = *containerEntityPtr.release();
|
|
auto editorPrefabComponent = containerEntity.FindComponent<EditorPrefabComponent>();
|
|
containerEntity.Deactivate();
|
|
[[maybe_unused]] const bool editorPrefabComponentRemoved = containerEntity.RemoveComponent(editorPrefabComponent);
|
|
AZ_Assert(editorPrefabComponentRemoved, "Remove EditorPrefabComponent failed.");
|
|
delete editorPrefabComponent;
|
|
containerEntity.Activate();
|
|
|
|
[[maybe_unused]] const bool containerEntityAdded = parentInstance.AddEntity(containerEntity);
|
|
AZ_Assert(containerEntityAdded, "Add target Instance's container entity to its parent Instance failed.");
|
|
|
|
EntityIdList entityIds;
|
|
entityIds.emplace_back(containerEntity.GetId());
|
|
|
|
instancePtr->GetEntities(
|
|
[&](AZStd::unique_ptr<AZ::Entity>& entityPtr)
|
|
{
|
|
oldEntityAliases.emplace(entityPtr->GetId(), instancePtr->GetEntityAlias(entityPtr->GetId())->get());
|
|
return true;
|
|
});
|
|
|
|
instancePtr->DetachEntities(
|
|
[&](AZStd::unique_ptr<AZ::Entity> entityPtr)
|
|
{
|
|
auto& entity = *entityPtr.release();
|
|
[[maybe_unused]] const bool entityAdded = parentInstance.AddEntity(entity);
|
|
AZ_Assert(entityAdded, "Add target Instance's entity to its parent Instance failed.");
|
|
|
|
entityIds.emplace_back(entity.GetId());
|
|
});
|
|
|
|
Prefab::PrefabDom instanceDomAfter;
|
|
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomAfter, parentInstance);
|
|
|
|
PrefabUndoInstance* command = aznew PrefabUndoInstance("Instance detachment");
|
|
command->Capture(instanceDomBefore, instanceDomAfter, parentTemplateId);
|
|
command->SetParent(undoBatch.GetUndoBatch());
|
|
{
|
|
AZ_PROFILE_SCOPE(AzToolsFramework, "Internal::DetachPrefab:RunRedo");
|
|
command->RunRedo();
|
|
}
|
|
|
|
instancePtr->DetachNestedInstances(
|
|
[&](AZStd::unique_ptr<Instance> detachedNestedInstance)
|
|
{
|
|
PrefabDom& nestedInstanceTemplateDom =
|
|
m_prefabSystemComponentInterface->FindTemplateDom(detachedNestedInstance->GetTemplateId());
|
|
|
|
Instance& nestedInstanceUnderNewParent = parentInstance.AddInstance(AZStd::move(detachedNestedInstance));
|
|
|
|
PrefabDom nestedInstanceDomUnderNewParent;
|
|
m_instanceToTemplateInterface->GenerateDomForInstance(
|
|
nestedInstanceDomUnderNewParent, nestedInstanceUnderNewParent);
|
|
PrefabDom reparentPatch;
|
|
m_instanceToTemplateInterface->GeneratePatch(
|
|
reparentPatch, nestedInstanceTemplateDom, nestedInstanceDomUnderNewParent);
|
|
|
|
CreateLink(nestedInstanceUnderNewParent, parentTemplateId, undoBatch.GetUndoBatch(), AZStd::move(reparentPatch), true);
|
|
});
|
|
}
|
|
|
|
AzToolsFramework::ToolsApplicationRequestBus::Broadcast(
|
|
&AzToolsFramework::ToolsApplicationRequestBus::Events::ClearDirtyEntities);
|
|
}
|
|
|
|
return AZ::Success();
|
|
}
|
|
|
|
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::GetOwnerInstanceByEntityId(AZ::EntityId entityId) const
|
|
{
|
|
if (entityId.IsValid())
|
|
{
|
|
return m_instanceEntityMapperInterface->FindOwningInstance(entityId);
|
|
}
|
|
|
|
// If the entityId 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 owning 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;
|
|
}
|
|
|
|
PrefabOperationResult PrefabPublicHandler::RetrieveAndSortPrefabEntitiesAndInstances(
|
|
const EntityList& inputEntities,
|
|
Instance& commonRootEntityOwningInstance,
|
|
EntityList& outEntities,
|
|
AZStd::vector<Instance*>& outInstances) const
|
|
{
|
|
if (inputEntities.size() == 0)
|
|
{
|
|
return AZ::Failure(
|
|
AZStd::string("An empty list of input entities is provided to retrieve the prefab entities and instances."));
|
|
}
|
|
|
|
AZStd::queue<AZ::Entity*> entityQueue;
|
|
|
|
auto editorEntityContextId = AzFramework::EntityContextId::CreateNull();
|
|
EditorEntityContextRequestBus::BroadcastResult(editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId);
|
|
|
|
AZ::EntityId focusedPrefabContainerEntityId =
|
|
m_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId);
|
|
for (auto inputEntity : inputEntities)
|
|
{
|
|
if (inputEntity && inputEntity->GetId() != focusedPrefabContainerEntityId)
|
|
{
|
|
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 occurred while retrieving entities and prefab instances : "
|
|
"Owning instance of entity with name '%s' and id '%llu' couldn't be found",
|
|
entity->GetName().c_str(), static_cast<AZ::u64>(entity->GetId()));
|
|
|
|
// Check if this entity is owned by the same instance owning the root.
|
|
if (&owningInstance->get() == &commonRootEntityOwningInstance)
|
|
{
|
|
// If it's the same instance, we can add this entity to the new instance entities.
|
|
size_t priorEntitiesSize = entities.size();
|
|
|
|
entities.insert(entity);
|
|
|
|
// 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 AZ::Failure(AZStd::string::format(
|
|
"Entity with name '%s' and id '%llu' has an owning instance that doesn't belong to the instance "
|
|
"hierarchy of the selected entities.",
|
|
entity->GetName().c_str(), static_cast<AZ::u64>(entity->GetId())));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Store results
|
|
outEntities.clear();
|
|
outEntities.reserve(entities.size());
|
|
|
|
for (AZ::Entity* entity : entities)
|
|
{
|
|
outEntities.emplace_back(entity);
|
|
}
|
|
|
|
outInstances.clear();
|
|
outInstances.reserve(instances.size());
|
|
for (Instance* instancePtr : instances)
|
|
{
|
|
outInstances.push_back(instancePtr);
|
|
}
|
|
|
|
if ((outEntities.size() + outInstances.size()) == 0)
|
|
{
|
|
return AZ::Failure(
|
|
AZStd::string("An empty list of entities and prefab instances were retrieved from the selected entities"));
|
|
}
|
|
return AZ::Success();
|
|
}
|
|
|
|
EntityIdList PrefabPublicHandler::GenerateEntityIdListWithoutFocusedInstanceContainer(
|
|
const EntityIdList& entityIds) const
|
|
{
|
|
EntityIdList outEntityIds(entityIds);
|
|
|
|
AzFramework::EntityContextId editorEntityContextId = AzToolsFramework::GetEntityContextId();
|
|
AZ::EntityId focusedInstanceContainerEntityId = m_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId);
|
|
|
|
if (auto iter = AZStd::find(outEntityIds.begin(), outEntityIds.end(), focusedInstanceContainerEntityId); iter != outEntityIds.end())
|
|
{
|
|
outEntityIds.erase(iter);
|
|
}
|
|
|
|
return outEntityIds;
|
|
}
|
|
|
|
bool PrefabPublicHandler::EntitiesBelongToSameInstance(const EntityIdList& entityIds) const
|
|
{
|
|
if (entityIds.size() <= 1)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
InstanceOptionalReference sharedInstance = AZStd::nullopt;
|
|
|
|
for (AZ::EntityId entityId : entityIds)
|
|
{
|
|
InstanceOptionalReference owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(entityId);
|
|
|
|
if (!owningInstance.has_value())
|
|
{
|
|
AZ_Assert(
|
|
false,
|
|
"An error occurred in function EntitiesBelongToSameInstance: "
|
|
"Owning instance of entity with id '%llu' couldn't be found",
|
|
entityId);
|
|
return false;
|
|
}
|
|
|
|
// If this is a container entity, it actually represents a child instance so get its owner.
|
|
// The only exception in the level root instance. We leave it as is to streamline operations.
|
|
if (owningInstance->get().GetContainerEntityId() == entityId && !IsLevelInstanceContainerEntity(entityId))
|
|
{
|
|
owningInstance = owningInstance->get().GetParentInstance();
|
|
}
|
|
|
|
if (!sharedInstance.has_value())
|
|
{
|
|
sharedInstance = owningInstance;
|
|
}
|
|
else
|
|
{
|
|
if (&sharedInstance->get() != &owningInstance->get())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void PrefabPublicHandler::DuplicateNestedEntitiesInInstance(Instance& commonOwningInstance,
|
|
const AZStd::vector<AZ::Entity*>& entities, PrefabDom& domToAddDuplicatedEntitiesUnder,
|
|
EntityIdList& duplicatedEntityIds, AZStd::unordered_map<EntityAlias, EntityAlias>& oldAliasToNewAliasMap)
|
|
{
|
|
if (entities.empty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
AZStd::unordered_map<EntityAlias, QString> aliasToEntityDomMap;
|
|
|
|
for (AZ::Entity* entity : entities)
|
|
{
|
|
EntityAliasOptionalReference oldAliasRef = commonOwningInstance.GetEntityAlias(entity->GetId());
|
|
AZ_Assert(oldAliasRef.has_value(), "No alias found for Entity in the DOM");
|
|
EntityAlias oldAlias = oldAliasRef.value();
|
|
|
|
// Give this the outer allocator so that the memory reference will be valid when
|
|
// it gets used for AddMember
|
|
PrefabDom entityDomBefore(&domToAddDuplicatedEntitiesUnder.GetAllocator());
|
|
m_instanceToTemplateInterface->GenerateDomForEntity(entityDomBefore, *entity);
|
|
|
|
// Keep track of the old alias <-> new alias mapping for this duplicated entity
|
|
// so we can fixup references later
|
|
EntityAlias newEntityAlias = Instance::GenerateEntityAlias();
|
|
oldAliasToNewAliasMap.emplace(oldAlias, newEntityAlias);
|
|
|
|
rapidjson::StringBuffer buffer;
|
|
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
|
|
entityDomBefore.Accept(writer);
|
|
|
|
// Store our duplicated Entity DOM with its new alias as a string
|
|
// so that we can fixup entity alias references before adding it
|
|
// to the Entities member of our instance DOM
|
|
QString entityDomString(buffer.GetString());
|
|
aliasToEntityDomMap.emplace(newEntityAlias, entityDomString);
|
|
}
|
|
|
|
auto entitiesIter = domToAddDuplicatedEntitiesUnder.FindMember(PrefabDomUtils::EntitiesName);
|
|
AZ_Assert(entitiesIter != domToAddDuplicatedEntitiesUnder.MemberEnd(), "Instance DOM missing the Entities member.");
|
|
|
|
// Now that all the duplicated Entity DOMs have been created, we need to iterate
|
|
// through them and replace any previous EntityAlias references with the new ones.
|
|
// These are more than just parent entity references for nested entities, this will
|
|
// also cover any EntityId references that were made in the components between them.
|
|
for (auto [newEntityAlias, newEntityDomString] : aliasToEntityDomMap)
|
|
{
|
|
// Replace all of the old alias references with the new ones
|
|
for (auto [oldAlias, newAlias] : oldAliasToNewAliasMap)
|
|
{
|
|
ReplaceOldAliases(newEntityDomString, oldAlias, newAlias);
|
|
}
|
|
|
|
// Create the new Entity DOM from parsing the JSON string
|
|
PrefabDom entityDomAfter(&domToAddDuplicatedEntitiesUnder.GetAllocator());
|
|
entityDomAfter.Parse(newEntityDomString.toUtf8().constData());
|
|
|
|
// Add the new Entity DOM to the Entities member of the instance
|
|
rapidjson::Value aliasName(newEntityAlias.c_str(), static_cast<rapidjson::SizeType>(newEntityAlias.length()), domToAddDuplicatedEntitiesUnder.GetAllocator());
|
|
entitiesIter->value.AddMember(AZStd::move(aliasName), entityDomAfter, domToAddDuplicatedEntitiesUnder.GetAllocator());
|
|
}
|
|
|
|
for (auto aliasMapIter : oldAliasToNewAliasMap)
|
|
{
|
|
EntityAlias newEntityAlias = aliasMapIter.second;
|
|
|
|
AliasPath absoluteEntityPath = commonOwningInstance.GetAbsoluteInstanceAliasPath();
|
|
absoluteEntityPath.Append(newEntityAlias);
|
|
|
|
AZ::EntityId newEntityId = InstanceEntityIdMapper::GenerateEntityIdForAliasPath(absoluteEntityPath);
|
|
duplicatedEntityIds.push_back(newEntityId);
|
|
}
|
|
}
|
|
|
|
void PrefabPublicHandler::DuplicateNestedInstancesInInstance(Instance& commonOwningInstance,
|
|
const AZStd::vector<Instance*>& instances, PrefabDom& domToAddDuplicatedInstancesUnder,
|
|
EntityIdList& duplicatedEntityIds, AZStd::unordered_map<InstanceAlias, Instance*>& newInstanceAliasToOldInstanceMap)
|
|
{
|
|
if (instances.empty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
AZStd::unordered_map<InstanceAlias, InstanceAlias> oldInstanceAliasToNewInstanceAliasMap;
|
|
AZStd::unordered_map<InstanceAlias, QString> aliasToInstanceDomMap;
|
|
|
|
for (auto instance : instances)
|
|
{
|
|
PrefabDom nestedInstanceDomBefore;
|
|
m_instanceToTemplateInterface->GenerateDomForInstance(nestedInstanceDomBefore, *instance);
|
|
|
|
// Keep track of the old alias <-> new alias mapping for this duplicated instance
|
|
// so we can fixup references later
|
|
InstanceAlias oldAlias = instance->GetInstanceAlias();
|
|
InstanceAlias newInstanceAlias = Instance::GenerateInstanceAlias();
|
|
oldInstanceAliasToNewInstanceAliasMap.emplace(oldAlias, newInstanceAlias);
|
|
|
|
// Keep track of our new instance alias with the Instance it was duplicated from,
|
|
// so that after all instances are duplicated, we can go back and create links for them
|
|
newInstanceAliasToOldInstanceMap.emplace(newInstanceAlias, instance);
|
|
|
|
rapidjson::StringBuffer buffer;
|
|
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
|
|
nestedInstanceDomBefore.Accept(writer);
|
|
|
|
// Store our duplicated Instance DOM with its new alias as a string
|
|
// so that we can fixup instance alias references before adding it
|
|
// to the Instances member of our instance DOM
|
|
QString instanceDomString(buffer.GetString());
|
|
aliasToInstanceDomMap.emplace(newInstanceAlias, instanceDomString);
|
|
}
|
|
|
|
auto instancesIter = domToAddDuplicatedInstancesUnder.FindMember(PrefabDomUtils::InstancesName);
|
|
AZ_Assert(instancesIter != domToAddDuplicatedInstancesUnder.MemberEnd(), "Instance DOM missing the Instances member.");
|
|
|
|
// Now that all the duplicated Instance DOMs have been created, we need to iterate
|
|
// through them and replace any previous InstanceAlias references with the new ones.
|
|
for (auto [newInstanceAlias, newInstanceDomString]: aliasToInstanceDomMap)
|
|
{
|
|
// Replace all of the old alias references with the new ones
|
|
for (auto [oldAlias, newAlias] : oldInstanceAliasToNewInstanceAliasMap)
|
|
{
|
|
ReplaceOldAliases(newInstanceDomString, oldAlias, newAlias);
|
|
}
|
|
|
|
// Create the new Instance DOM from parsing the JSON string
|
|
PrefabDom nestedInstanceDomAfter(&domToAddDuplicatedInstancesUnder.GetAllocator());
|
|
nestedInstanceDomAfter.Parse(newInstanceDomString.toUtf8().constData());
|
|
|
|
// Add the new Instance DOM to the Instances member of the instance
|
|
rapidjson::Value aliasName(newInstanceAlias.c_str(), static_cast<rapidjson::SizeType>(newInstanceAlias.length()), domToAddDuplicatedInstancesUnder.GetAllocator());
|
|
instancesIter->value.AddMember(AZStd::move(aliasName), nestedInstanceDomAfter, domToAddDuplicatedInstancesUnder.GetAllocator());
|
|
}
|
|
|
|
for (auto aliasMapIter : oldInstanceAliasToNewInstanceAliasMap)
|
|
{
|
|
InstanceAlias newInstanceAlias = aliasMapIter.second;
|
|
|
|
AliasPath absoluteInstancePath = commonOwningInstance.GetAbsoluteInstanceAliasPath();
|
|
absoluteInstancePath.Append(newInstanceAlias);
|
|
absoluteInstancePath.Append(PrefabDomUtils::ContainerEntityName);
|
|
|
|
AZ::EntityId newEntityId = InstanceEntityIdMapper::GenerateEntityIdForAliasPath(absoluteInstancePath);
|
|
duplicatedEntityIds.push_back(newEntityId);
|
|
}
|
|
}
|
|
|
|
void PrefabPublicHandler::ReplaceOldAliases(QString& stringToReplace, AZStd::string_view oldAlias, AZStd::string_view newAlias)
|
|
{
|
|
// Replace all of the old alias references with the new ones
|
|
// We bookend the aliases with \" and also with a / as an extra precaution to prevent
|
|
// inadvertently replacing a matching string vs. where an actual EntityId is expected
|
|
// This will cover both cases where an alias could be used in a normal entity vs. an instance
|
|
QString oldAliasQuotes = QString("\"%1\"").arg(oldAlias.data());
|
|
QString newAliasQuotes = QString("\"%1\"").arg(newAlias.data());
|
|
|
|
stringToReplace.replace(oldAliasQuotes, newAliasQuotes);
|
|
|
|
QString oldAliasPathRef = QString("/%1").arg(oldAlias.data());
|
|
QString newAliasPathRef = QString("/%1").arg(newAlias.data());
|
|
|
|
stringToReplace.replace(oldAliasPathRef, newAliasPathRef);
|
|
}
|
|
|
|
void PrefabPublicHandler::UpdateLinkPatchesWithNewEntityAliases(
|
|
PrefabDom& linkPatch,
|
|
const AZStd::unordered_map<AZ::EntityId, AZStd::string>& oldEntityAliases,
|
|
Instance& newParent)
|
|
{
|
|
rapidjson::StringBuffer buffer;
|
|
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
|
|
linkPatch.Accept(writer);
|
|
QString previousPatchString(buffer.GetString());
|
|
|
|
for (const auto& [entityId, oldEntityAlias] : oldEntityAliases)
|
|
{
|
|
EntityAliasOptionalReference newEntityAlias = newParent.GetEntityAlias(entityId);
|
|
AZ_Assert(
|
|
newEntityAlias.has_value(),
|
|
"Could not fetch entity alias for entity with id '%llu' during prefab creation.",
|
|
static_cast<AZ::u64>(entityId));
|
|
|
|
ReplaceOldAliases(previousPatchString, oldEntityAlias, newEntityAlias->get());
|
|
}
|
|
|
|
linkPatch.Parse(previousPatchString.toUtf8().constData());
|
|
}
|
|
|
|
} // namespace Prefab
|
|
} // namespace AzToolsFramework
|