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.
947 lines
43 KiB
C++
947 lines
43 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 <AzToolsFramework/Prefab/PrefabPublicHandler.h>
|
|
|
|
#include <AzCore/Component/TransformBus.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/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/PrefabSystemComponentInterface.h>
|
|
#include <AzToolsFramework/Prefab/PrefabUndo.h>
|
|
#include <AzToolsFramework/Prefab/PrefabUndoHelpers.h>
|
|
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
|
|
|
|
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 filePath)
|
|
{
|
|
EntityList inputEntityList, topLevelEntities;
|
|
AZ::EntityId commonRootEntityId;
|
|
InstanceOptionalReference commonRootEntityOwningInstance;
|
|
PrefabOperationResult findCommonRootOutcome = FindCommonRootOwningInstance(
|
|
entityIds, inputEntityList, topLevelEntities, commonRootEntityId, commonRootEntityOwningInstance);
|
|
if (!findCommonRootOutcome.IsSuccess())
|
|
{
|
|
return findCommonRootOutcome;
|
|
}
|
|
|
|
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>> instances;
|
|
|
|
// Retrieve all entities affected and identify Instances
|
|
if (!RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonRootEntityOwningInstance->get(), entities, instances))
|
|
{
|
|
return AZ::Failure(
|
|
AZStd::string("Could not create a new prefab out of the entities provided - entities do not share a common root."));
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
RemoveLink(nestedInstance, commonRootEntityOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch());
|
|
}
|
|
|
|
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(instances), 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)."));
|
|
}
|
|
|
|
AZ::EntityId containerEntityId = instanceToCreate->get().GetContainerEntityId();
|
|
|
|
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.");
|
|
CreateLink(
|
|
{&nestedInstanceContainerEntity->get()}, *nestedInstance, instanceToCreate->get().GetTemplateId(),
|
|
undoBatch.GetUndoBatch(), containerEntityId);
|
|
});
|
|
|
|
CreateLink(
|
|
topLevelEntities, instanceToCreate->get(), commonRootEntityOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch(),
|
|
commonRootEntityId);
|
|
|
|
// Change top level entities to be parented to the container entity
|
|
// Mark them as dirty so this change is correctly applied to the template
|
|
for (AZ::Entity* topLevelEntity : topLevelEntities)
|
|
{
|
|
m_prefabUndoCache.UpdateCache(topLevelEntity->GetId());
|
|
undoBatch.MarkEntityDirty(topLevelEntity->GetId());
|
|
AZ::TransformBus::Event(topLevelEntity->GetId(), &AZ::TransformBus::Events::SetParent, containerEntityId);
|
|
}
|
|
|
|
// 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->SaveTemplate(instanceToCreate->get().GetTemplateId());
|
|
|
|
return AZ::Success();
|
|
}
|
|
|
|
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->GetRelativePathToProject(filePath);
|
|
Prefab::TemplateId templateId = m_prefabSystemComponentInterface->GetTemplateIdFromFilePath(relativePath);
|
|
|
|
// If the template isn't currently loaded, there's no way for it to be in the hierarchy so we just skip the check.
|
|
if (templateId != Prefab::InvalidTemplateId && IsPrefabInInstanceAncestorHierarchy(templateId, instanceToParentUnder->get()))
|
|
{
|
|
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");
|
|
|
|
PrefabDom instanceToParentUnderDomBeforeCreate;
|
|
m_instanceToTemplateInterface->GenerateDomForInstance(
|
|
instanceToParentUnderDomBeforeCreate, instanceToParentUnder->get());
|
|
|
|
// Instantiate the Prefab
|
|
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());
|
|
|
|
CreateLink({}, instanceToCreate->get(), instanceToParentUnder->get().GetTemplateId(),
|
|
undoBatch.GetUndoBatch(), parent);
|
|
AZ::EntityId containerEntityId = instanceToCreate->get().GetContainerEntityId();
|
|
|
|
// Apply position
|
|
AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetWorldTranslation, position);
|
|
}
|
|
|
|
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);
|
|
|
|
// Find common root and top level entities
|
|
bool entitiesHaveCommonRoot = false;
|
|
|
|
AzToolsFramework::ToolsApplicationRequests::Bus::BroadcastResult(
|
|
entitiesHaveCommonRoot, &AzToolsFramework::ToolsApplicationRequests::FindCommonRootInactive, inputEntityList,
|
|
commonRootEntityId, &topLevelEntities);
|
|
|
|
// Bail if entities don't share a common root
|
|
if (!entitiesHaveCommonRoot)
|
|
{
|
|
return AZ::Failure(AZStd::string("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::IsPrefabInInstanceAncestorHierarchy(TemplateId prefabTemplateId, InstanceOptionalConstReference instance)
|
|
{
|
|
InstanceOptionalConstReference currentInstance = instance;
|
|
|
|
while (currentInstance.has_value())
|
|
{
|
|
if (currentInstance->get().GetTemplateId() == prefabTemplateId)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
currentInstance = currentInstance->get().GetParentInstance();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void PrefabPublicHandler::CreateLink(
|
|
const EntityList& topLevelEntities, Instance& sourceInstance, TemplateId targetTemplateId,
|
|
UndoSystem::URSequencePoint* undoBatch, AZ::EntityId commonRootEntityId)
|
|
{
|
|
AZ::EntityId containerEntityId = sourceInstance.GetContainerEntityId();
|
|
AZ::Entity* containerEntity = GetEntityById(containerEntityId);
|
|
Prefab::PrefabDom containerEntityDomBefore;
|
|
m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomBefore, *containerEntity);
|
|
|
|
AZ::Vector3 containerEntityTranslation(AZ::Vector3::CreateZero());
|
|
AZ::Quaternion containerEntityRotation(AZ::Quaternion::CreateZero());
|
|
|
|
// Set the transform (translation, rotation) of the container entity
|
|
GenerateContainerEntityTransform(topLevelEntities, containerEntityTranslation, containerEntityRotation);
|
|
|
|
// Set container entity to be child of common root
|
|
AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetParent, commonRootEntityId);
|
|
|
|
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);
|
|
|
|
LinkId linkId = PrefabUndoHelpers::CreateLink(
|
|
sourceInstance.GetTemplateId(), targetTemplateId, patch, sourceInstance.GetInstanceAlias(),
|
|
undoBatch);
|
|
|
|
sourceInstance.SetLinkId(linkId);
|
|
|
|
// Update the cache - this prevents these changes from being stored in the regular undo/redo nodes
|
|
m_prefabUndoCache.Store(containerEntityId, AZStd::move(containerEntityDomAfter));
|
|
}
|
|
|
|
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(),
|
|
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())
|
|
{
|
|
PrefabDom afterState;
|
|
AZ::Entity* entity = GetEntityById(entityId);
|
|
if (entity)
|
|
{
|
|
PrefabDom beforeState;
|
|
m_prefabUndoCache.Retrieve(entityId, beforeState);
|
|
|
|
m_instanceToTemplateInterface->GenerateDomForEntity(afterState, *entity);
|
|
|
|
PrefabDom patch;
|
|
m_instanceToTemplateInterface->GeneratePatch(patch, beforeState, afterState);
|
|
|
|
if (patch.IsArray() && !patch.Empty() && beforeState.IsObject())
|
|
{
|
|
if (IsInstanceContainerEntity(entityId) && !IsLevelInstanceContainerEntity(entityId))
|
|
{
|
|
m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(patch, entityId);
|
|
|
|
// Save these changes as patches to the link
|
|
PrefabUndoLinkUpdate* linkUpdate =
|
|
aznew PrefabUndoLinkUpdate(AZStd::to_string(static_cast<AZ::u64>(entityId)));
|
|
linkUpdate->SetParent(parentUndoBatch);
|
|
linkUpdate->Capture(patch, owningInstance->get().GetLinkId());
|
|
|
|
linkUpdate->Redo();
|
|
}
|
|
else
|
|
{
|
|
// Update the state of the entity
|
|
PrefabUndoEntityUpdate* state = aznew PrefabUndoEntityUpdate(AZStd::to_string(static_cast<AZ::u64>(entityId)));
|
|
state->SetParent(parentUndoBatch);
|
|
state->Capture(beforeState, afterState, entityId);
|
|
|
|
state->Redo();
|
|
}
|
|
}
|
|
|
|
// Update the cache
|
|
m_prefabUndoCache.Store(entityId, AZStd::move(afterState));
|
|
}
|
|
else
|
|
{
|
|
m_prefabUndoCache.PurgeCache(entityId);
|
|
}
|
|
}
|
|
}
|
|
|
|
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::DeleteFromInstance(const EntityIdList& entityIds, bool deleteDescendants)
|
|
{
|
|
if (entityIds.empty())
|
|
{
|
|
return AZ::Success();
|
|
}
|
|
|
|
if (!EntitiesBelongToSameInstance(entityIds))
|
|
{
|
|
return AZ::Failure(AZStd::string("DeleteEntitiesAndAllDescendantsInInstance - Deletion Error. Cannot delete multiple "
|
|
"entities belonging to different instances with one operation."));
|
|
}
|
|
|
|
InstanceOptionalReference instance = GetOwnerInstanceByEntityId(entityIds[0]);
|
|
|
|
// Retrieve entityList from entityIds
|
|
EntityList inputEntityList = EntityIdListToEntityList(entityIds);
|
|
|
|
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
|
|
|
|
UndoSystem::URSequencePoint* currentUndoBatch = nullptr;
|
|
ToolsApplicationRequests::Bus::BroadcastResult(currentUndoBatch, &ToolsApplicationRequests::Bus::Events::GetCurrentUndoBatch);
|
|
|
|
bool createdUndo = false;
|
|
if (!currentUndoBatch)
|
|
{
|
|
createdUndo = true;
|
|
ToolsApplicationRequests::Bus::BroadcastResult(
|
|
currentUndoBatch, &ToolsApplicationRequests::Bus::Events::BeginUndoBatch, "Delete Selected");
|
|
AZ_Assert(currentUndoBatch, "Failed to create new undo batch.");
|
|
}
|
|
|
|
// 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, instance->get());
|
|
|
|
if (deleteDescendants)
|
|
{
|
|
AZStd::vector<AZ::Entity*> entities;
|
|
AZStd::vector<AZStd::unique_ptr<Instance>> instances;
|
|
|
|
bool success = RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, instance->get(), entities, instances);
|
|
|
|
if (!success)
|
|
{
|
|
return AZ::Failure(AZStd::string("DeleteEntitiesAndAllDescendantsInInstance"));
|
|
}
|
|
|
|
for (AZ::Entity* entity : entities)
|
|
{
|
|
AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationRequests::DeleteEntity, entity->GetId());
|
|
}
|
|
|
|
for (auto& nestedInstance : instances)
|
|
{
|
|
nestedInstance.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 = instance->get().DetachNestedInstance(owningInstance->get().GetInstanceAlias());
|
|
instancePtr.reset();
|
|
}
|
|
else
|
|
{
|
|
instance->get().DetachEntity(entityId);
|
|
AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationRequests::DeleteEntity, entityId);
|
|
}
|
|
}
|
|
}
|
|
|
|
Prefab::PrefabDom instanceDomAfter;
|
|
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomAfter, instance->get());
|
|
|
|
PrefabUndoInstance* command = aznew PrefabUndoInstance("Instance deletion");
|
|
command->Capture(instanceDomBefore, instanceDomAfter, instance->get().GetTemplateId());
|
|
command->SetParent(selCommand);
|
|
}
|
|
|
|
selCommand->SetParent(currentUndoBatch);
|
|
{
|
|
AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::AzToolsFramework, "Internal::DeleteEntities:RunRedo");
|
|
selCommand->RunRedo();
|
|
}
|
|
|
|
if (createdUndo)
|
|
{
|
|
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequests::EndUndoBatch);
|
|
}
|
|
|
|
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<AZStd::unique_ptr<Instance>>& outInstances) const
|
|
{
|
|
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(commonRootEntityOwningInstance.DetachEntity(entity->GetId()).release());
|
|
}
|
|
|
|
outInstances.clear();
|
|
outInstances.reserve(instances.size());
|
|
for (Instance* instancePtr : instances)
|
|
{
|
|
outInstances.push_back(AZStd::move(commonRootEntityOwningInstance.DetachNestedInstance(instancePtr->GetInstanceAlias())));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|