80f62d0523
* Add instanceToIgnore to calls leading to instances being added to the queue for propagation. * Change PrefabUndoEntityUpdate to make it so that the instance triggering the prefab template change is not reloaded on propagation, since it will already be up to date due to the way we generated the patch to begin with. * Add FindPrefabDomValue utility function for paths * Expose the level root prefab template id in the Prefab EOS Interface * Fix Instance Alias Path generation to work with the new FindValueInPrefabDom function * Stop reloading ancestors on propagation, and fix instance reloading so that the level dom is used (and overrides are preserved) * Remove commented out code, refactor FindPrefabDomValue for paths (was handling an edge case incorrectly, and it's not even triggered) * Fix issue with PathView reference - with PathView already being a reference, this resulted in a copy and triggered a warning during automated review builds. * Additional fix to the build warning, remove redundant error message * Revert changes to Instance::GetAbsoluteInstanceAliasPath(), as they were impacting serialization. * Remove the dependency to the level root prefab template in the propagation code, climb up the hierarchy instead. This allows tests to work despite not using the EOS properly. Also use PrefabDomPaths to retrieve the instance dom from the root dom instead of iterating. * Remove now unused PrefabDomUtils function, extend optimization to link updates. * Trigger a full instance propagation to correctly refresh alias references. This is an issue in the test because some operations are called from the backend API and will not trigger propagation properly. Tests will soon be rewritten to more properly represent frontend workflows. * Fixes lingering issues with propagation: - Restores code that fixes the selection if entityIds have changed; - Fixes Do() function on link update. Prefab containers will propagate correctly while still being stable during editing. * Remove GetRootPrefabInstanceTemplateId (no longer necessary after the code has been rewritten) * Fix optimization code to account for instances being removed and propagation being run out of order in Create Prefab undo. * Renamed variable, added comments for clarity. * Restore asserts on instance not being found; Rename Do to Redo for clarity; Add comments. * Fixed incomplete comment.
1676 lines
83 KiB
C++
1676 lines
83 KiB
C++
/*
|
|
* 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/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_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();
|
|
}
|
|
|
|
PrefabOperationResult PrefabPublicHandler::CreatePrefab(const AZStd::vector<AZ::EntityId>& entityIds, AZ::IO::PathView absolutePath)
|
|
{
|
|
EntityList inputEntityList, topLevelEntities;
|
|
AZ::EntityId commonRootEntityId;
|
|
InstanceOptionalReference commonRootEntityOwningInstance;
|
|
PrefabOperationResult findCommonRootOutcome = FindCommonRootOwningInstance(
|
|
entityIds, inputEntityList, topLevelEntities, commonRootEntityId, commonRootEntityOwningInstance);
|
|
if (!findCommonRootOutcome.IsSuccess())
|
|
{
|
|
return findCommonRootOutcome;
|
|
}
|
|
|
|
AZ_Assert(absolutePath.IsAbsolute(), "CreatePrefab requires an absolute path for saving the initial prefab file.");
|
|
|
|
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
|
|
if (!RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonRootEntityOwningInstance->get(), entities, instances))
|
|
{
|
|
return AZ::Failure(
|
|
AZStd::string("Could not create a new prefab out of the entities provided - invalid selection."));
|
|
}
|
|
|
|
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
|
|
instanceToCreate = prefabEditorEntityOwnershipInterface->CreatePrefab(
|
|
entities, AZStd::move(instancePtrs), m_prefabLoaderInterface->GenerateRelativePath(absolutePath),
|
|
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)."));
|
|
}
|
|
|
|
AZ::EntityId 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
|
|
{
|
|
auto selectionUndo = aznew SelectionCommand({containerEntityId}, "Select Prefab Container Entity");
|
|
selectionUndo->SetParent(undoBatch.GetUndoBatch());
|
|
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::RunRedoSeparately, selectionUndo);
|
|
}
|
|
}
|
|
|
|
// Save Template to file
|
|
m_prefabLoaderInterface->SaveTemplateToFile(instanceToCreate->get().GetTemplateId(), absolutePath);
|
|
|
|
return AZ::Success();
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
PrefabOperationResult 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)."));
|
|
}
|
|
|
|
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.", 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()));
|
|
}
|
|
|
|
{
|
|
// 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
|
|
AZ::EntityId 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();
|
|
}
|
|
|
|
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)
|
|
{
|
|
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);
|
|
}
|
|
|
|
void 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::Entity* entity = GetEntityById(entityId);
|
|
if (!entity)
|
|
{
|
|
m_prefabUndoCache.PurgeCache(entityId);
|
|
return;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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.
|
|
RetrieveAndSortPrefabEntitiesAndInstances({ entity }, beforeOwningInstance->get(), entities, instances);
|
|
|
|
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."));
|
|
}
|
|
|
|
if (!EntitiesBelongToSameInstance(entityIds))
|
|
{
|
|
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 = entityIds[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 because you
|
|
// cannot duplicate an instance from itself.
|
|
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(entityIds);
|
|
|
|
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
|
|
|
|
ScopedUndoBatch undoBatch("Duplicate Entities");
|
|
|
|
{
|
|
AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::AzToolsFramework, "DuplicateEntitiesInInstance::UndoCaptureAndDuplicateEntities");
|
|
|
|
AZStd::vector<AZ::Entity*> entities;
|
|
AZStd::vector<Instance*> instances;
|
|
|
|
EntityList inputEntityList = EntityIdSetToEntityList(duplicationSet);
|
|
bool success = RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonOwningInstance->get(), entities, instances);
|
|
|
|
if (!success)
|
|
{
|
|
return AZ::Failure(AZStd::string("Failed to retrieve entities and instances from the given list of entity ids for duplication"));
|
|
}
|
|
|
|
// 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;
|
|
DuplicateNestedEntitiesInInstance(commonOwningInstance->get(),
|
|
entities, instanceDomAfter, duplicatedEntityAndInstanceIds);
|
|
DuplicateNestedInstancesInInstance(commonOwningInstance->get(),
|
|
instances, instanceDomAfter, duplicatedEntityAndInstanceIds,
|
|
newInstanceAliasToOldInstanceMap);
|
|
|
|
PrefabUndoInstance* command = aznew PrefabUndoInstance("Entity/Instance duplication");
|
|
command->SetParent(undoBatch.GetUndoBatch());
|
|
command->Capture(instanceDomBefore, instanceDomAfter, commonOwningInstance->get().GetTemplateId());
|
|
command->Redo();
|
|
|
|
// 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());
|
|
|
|
m_prefabSystemComponentInterface->CreateLink(
|
|
commonOwningInstance->get().GetTemplateId(), oldInstance->GetTemplateId(), newInstanceAlias, linkPatchesCopy);
|
|
}
|
|
|
|
// Select the duplicated entities/instances
|
|
auto selectionUndo = aznew SelectionCommand(duplicatedEntityAndInstanceIds, "Select Duplicated Entities/Instances");
|
|
selectionUndo->SetParent(undoBatch.GetUndoBatch());
|
|
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::RunRedoSeparately, selectionUndo);
|
|
}
|
|
|
|
return AZ::Success();
|
|
}
|
|
|
|
PrefabOperationResult PrefabPublicHandler::DeleteFromInstance(const EntityIdList& entityIds, bool deleteDescendants)
|
|
{
|
|
if (entityIds.empty())
|
|
{
|
|
return AZ::Success();
|
|
}
|
|
|
|
if (!EntitiesBelongToSameInstance(entityIds))
|
|
{
|
|
return AZ::Failure(AZStd::string("Cannot delete multiple entities belonging to different instances with one operation."));
|
|
}
|
|
|
|
AZ::EntityId firstEntityIdToDelete = entityIds[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();
|
|
}
|
|
|
|
// Retrieve entityList from entityIds
|
|
EntityList inputEntityList = EntityIdListToEntityList(entityIds);
|
|
|
|
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::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(AZ::Debug::ProfileCategory::AzToolsFramework, "Internal::DeleteEntities:UndoCaptureAndPurgeEntities");
|
|
|
|
Prefab::PrefabDom instanceDomBefore;
|
|
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, commonOwningInstance->get());
|
|
|
|
if (deleteDescendants)
|
|
{
|
|
AZStd::vector<AZ::Entity*> entities;
|
|
AZStd::vector<Instance*> instances;
|
|
|
|
bool success = RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonOwningInstance->get(), entities, instances);
|
|
|
|
if (!success)
|
|
{
|
|
return AZ::Failure(AZStd::string("DeleteEntitiesAndAllDescendantsInInstance"));
|
|
}
|
|
|
|
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 : entityIds)
|
|
{
|
|
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(AZ::Debug::ProfileCategory::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."));
|
|
}
|
|
|
|
if (IsLevelInstanceContainerEntity(containerEntityId))
|
|
{
|
|
return AZ::Failure(AZStd::string("Cannot detach level 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(AZ::Debug::ProfileCategory::AzToolsFramework);
|
|
|
|
{
|
|
AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::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();
|
|
const bool editorPrefabComponentRemoved = containerEntity.RemoveComponent(editorPrefabComponent);
|
|
AZ_Assert(editorPrefabComponentRemoved, "Remove EditorPrefabComponent failed.");
|
|
delete editorPrefabComponent;
|
|
containerEntity.Activate();
|
|
|
|
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();
|
|
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(AZ::Debug::ProfileCategory::AzToolsFramework, "Internal::DetachPrefab:RunRedo");
|
|
command->RunRedo();
|
|
}
|
|
|
|
const auto instanceTemplateId = instancePtr->GetTemplateId();
|
|
auto parentContainerEntityId = parentInstance.GetContainerEntityId();
|
|
instancePtr->GetNestedInstances(
|
|
[&](AZStd::unique_ptr<Instance>& nestedInstancePtr)
|
|
{
|
|
//get previous link patch
|
|
auto linkRef = m_prefabSystemComponentInterface->FindLink(nestedInstancePtr->GetLinkId());
|
|
PrefabDomValueReference linkPatches = linkRef->get().GetLinkPatches();
|
|
AZ_Assert(
|
|
linkPatches.has_value(), "Unable to get patches on link with id '%llu' during prefab creation.",
|
|
nestedInstancePtr->GetLinkId());
|
|
|
|
PrefabDom linkPatchesCopy;
|
|
linkPatchesCopy.CopyFrom(linkPatches->get(), linkPatchesCopy.GetAllocator());
|
|
|
|
RemoveLink(nestedInstancePtr, instanceTemplateId, undoBatch.GetUndoBatch());
|
|
|
|
UpdateLinkPatchesWithNewEntityAliases(linkPatchesCopy, oldEntityAliases, parentInstance);
|
|
|
|
CreateLink(*nestedInstancePtr, parentTemplateId, undoBatch.GetUndoBatch(),
|
|
AZStd::move(linkPatchesCopy), 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;
|
|
}
|
|
|
|
bool PrefabPublicHandler::RetrieveAndSortPrefabEntitiesAndInstances(
|
|
const EntityList& inputEntities, Instance& commonRootEntityOwningInstance,
|
|
EntityList& outEntities, AZStd::vector<Instance*>& outInstances) const
|
|
{
|
|
if (inputEntities.size() == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
AZStd::queue<AZ::Entity*> entityQueue;
|
|
|
|
for (auto inputEntity : inputEntities)
|
|
{
|
|
if (inputEntity && !IsLevelInstanceContainerEntity(inputEntity->GetId()))
|
|
{
|
|
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 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)
|
|
{
|
|
// If it's the same instance, we can add this entity to the new instance entities.
|
|
int 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 false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
return (outEntities.size() + outInstances.size()) > 0;
|
|
}
|
|
|
|
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)
|
|
{
|
|
if (entities.empty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
AZStd::unordered_map<EntityAlias, EntityAlias> oldAliasToNewAliasMap;
|
|
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(), 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(), 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);
|
|
|
|
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
|