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.
o3de/Code/Editor/Plugins/ComponentEntityEditorPlugin/Objects/ComponentEntityObject.cpp

936 lines
32 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "ComponentEntityObject.h"
#include "Settings.h"
#include "IIconManager.h"
#include "Include/IObjectManager.h"
#include "Objects/SelectionGroup.h"
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Serialization/Utils.h>
#include <AzCore/std/string/string.h>
#include <AzCore/Math/Transform.h>
#include <AzFramework/Viewport/DisplayContextRequestBus.h>
#include <AzToolsFramework/API/ComponentEntitySelectionBus.h>
#include <AzToolsFramework/Commands/PreemptiveUndoCache.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <AzToolsFramework/ToolsComponents/EditorLayerComponentBus.h>
#include <AzToolsFramework/ToolsComponents/EditorLockComponent.h>
#include <AzToolsFramework/ToolsComponents/EditorVisibilityComponent.h>
#include <AzToolsFramework/ToolsComponents/SelectionComponent.h>
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
#include <AzToolsFramework/ToolsComponents/GenericComponentWrapper.h>
#include <AzToolsFramework/ToolsComponents/EditorSelectionAccentSystemComponent.h>
#include <AzToolsFramework/ToolsComponents/EditorEntityIconComponentBus.h>
#include <AzToolsFramework/Undo/UndoCacheInterface.h>
#include <IDisplayViewport.h>
#include <CryCommon/Cry_GeoIntersect.h>
#include <MathConversion.h>
#include <TrackView/TrackViewAnimNode.h>
#include <ViewManager.h>
#include <Viewport.h>
/**
* Scalars for icon drawing behavior.
*/
CComponentEntityObject::CComponentEntityObject()
: m_accentType(AzToolsFramework::EntityAccentType::None)
, m_hasIcon(false)
, m_entityIconVisible(false)
, m_iconOnlyHitTest(false)
, m_drawAccents(true)
, m_isIsolated(false)
, m_iconTexture(nullptr)
{
}
CComponentEntityObject::~CComponentEntityObject()
{
DeleteEntity();
}
bool CComponentEntityObject::Init(IEditor* ie, CBaseObject* copyFrom, const QString& file)
{
SetColor(RGB(0, 255, 0));
SetTextureIcon(GetClassDesc()->GetTextureIconId());
// Sandbox does not serialize this object with others in the layer.
SetFlags(OBJFLAG_DONT_SAVE);
const bool result = CEntityObject::Init(ie, copyFrom, file);
return result;
}
void CComponentEntityObject::UpdatePreemptiveUndoCache()
{
using namespace AzToolsFramework;
auto undoCacheInterface = AZ::Interface<UndoSystem::UndoCacheInterface>::Get();
if (undoCacheInterface)
{
undoCacheInterface->UpdateCache(m_entityId);
}
}
void CComponentEntityObject::AssignEntity(AZ::Entity* entity, bool destroyOld)
{
const AZ::EntityId newEntityId = entity ? entity->GetId() : AZ::EntityId();
if (m_entityId.IsValid())
{
AZ::TransformNotificationBus::Handler::BusDisconnect();
LmbrCentral::RenderBoundsNotificationBus::Handler::BusDisconnect();
AzToolsFramework::ComponentEntityEditorRequestBus::Handler::BusDisconnect();
AZ::EntityBus::Handler::BusDisconnect();
AzToolsFramework::ComponentEntityObjectRequestBus::Handler::BusDisconnect();
AzToolsFramework::EditorLockComponentNotificationBus::Handler::BusDisconnect();
AzToolsFramework::EditorVisibilityNotificationBus::Handler::BusDisconnect();
AzToolsFramework::EditorEntityIconComponentNotificationBus::Handler::BusDisconnect();
if (destroyOld && m_entityId != newEntityId)
{
EBUS_EVENT(AzToolsFramework::EditorEntityContextRequestBus, DestroyEditorEntity, m_entityId);
}
m_entityId.SetInvalid();
}
if (entity)
{
m_entityId = entity->GetId();
// note: GetObjectManager() will always be valid during normal operation but
// will not exist when running unit tests
if (IObjectManager* objectManager = GetObjectManager())
{
// Use the entity id to generate a GUID for this CEO because we need it to stay consistent for systems
// that register by GUID such as undo/redo since our own undo/redo system constantly recreates CEOs
GUID entityBasedGUID;
entityBasedGUID.Data1 = 0;
entityBasedGUID.Data2 = 0;
entityBasedGUID.Data3 = 0;
static_assert(sizeof(m_entityId) >= sizeof(entityBasedGUID.Data4), "The data contained in entity Id should fit inside Data4, if not switch to some other method of conversion to GUID");
memcpy(&entityBasedGUID.Data4, &m_entityId, sizeof(entityBasedGUID.Data4));
objectManager->ChangeObjectId(GetId(), entityBasedGUID);
// Synchronize sandbox name to new entity's name.
{
EditorActionScope nameChange(m_nameReentryGuard);
SetName(QString(entity->GetName().c_str()));
}
}
EBUS_EVENT(AzToolsFramework::EditorEntityContextRequestBus, AddRequiredComponents, *entity);
AZ::TransformNotificationBus::Handler::BusConnect(m_entityId);
LmbrCentral::RenderBoundsNotificationBus::Handler::BusConnect(m_entityId);
AzToolsFramework::ComponentEntityEditorRequestBus::Handler::BusConnect(m_entityId);
AZ::EntityBus::Handler::BusConnect(m_entityId);
AzToolsFramework::ComponentEntityObjectRequestBus::Handler::BusConnect(this);
AzToolsFramework::EditorLockComponentNotificationBus::Handler::BusConnect(m_entityId);
AzToolsFramework::EditorVisibilityNotificationBus::Handler::BusConnect(m_entityId);
AzToolsFramework::EditorEntityIconComponentNotificationBus::Handler::BusConnect(m_entityId);
// Synchronize transform to Sandbox.
AzToolsFramework::Components::TransformComponent* transformComponent =
entity->FindComponent<AzToolsFramework::Components::TransformComponent>();
if (transformComponent)
{
OnTransformChanged(transformComponent->GetLocalTM(), transformComponent->GetWorldTM());
}
}
RefreshVisibilityAndLock();
}
void CComponentEntityObject::RefreshVisibilityAndLock()
{
using AzToolsFramework::EditorEntityInfoRequestBus;
using AzToolsFramework::EditorEntityLockComponentNotificationBus;
using AzToolsFramework::EditorEntityVisibilityNotificationBus;
// Lock state is tracked in 3 places:
// EditorLockComponent, EditorEntityModel, and ComponentEntityObject.
// Entities in layers have additional behavior in relation to lock state, if the layer is locked it supersedes the entity's lock state.
// The viewport controls for manipulating entities are disabled during lock state here in ComponentEntityObject using the OBJFLAG_FROZEN.
// In this case, the lock behavior should include the layer hierarchy as well, if the layer is locked this entity can't move.
// EditorEntityModel can report this information.
bool locked = false;
EditorEntityInfoRequestBus::EventResult(
locked, m_entityId, &EditorEntityInfoRequestBus::Events::IsLocked);
EditorEntityLockComponentNotificationBus::Event(
m_entityId, &EditorEntityLockComponentNotificationBus::Events::OnEntityLockChanged, locked);
// OBJFLAG_HIDDEN should match EditorVisibilityComponent's VisibilityFlag.
// Visibility state is similar to lock state in the number of areas it can be set / tracked. See the comment about the lock state above.
bool visibility = true;
EditorEntityInfoRequestBus::EventResult(
visibility, m_entityId, &EditorEntityInfoRequestBus::Events::IsVisible);
EditorEntityVisibilityNotificationBus::Event(
m_entityId, &EditorEntityVisibilityNotificationBus::Events::OnEntityVisibilityChanged, visibility);
}
void CComponentEntityObject::SetName(const QString& name)
{
if (m_nameReentryGuard)
{
EditorActionScope nameChange(m_nameReentryGuard);
AZ::Entity* entity = nullptr;
EBUS_EVENT_RESULT(entity, AZ::ComponentApplicationBus, FindEntity, m_entityId);
if (entity)
{
entity->SetName(name.toUtf8().data());
}
}
CEntityObject::SetName(name);
}
void CComponentEntityObject::DeleteEntity()
{
AssignEntity(nullptr);
CEntityObject::DeleteEntity();
}
float CComponentEntityObject::GetRadius()
{
static const float s_defaultRadius = 0.5f;
return s_defaultRadius;
}
void CComponentEntityObject::SetSelected(bool bSelect)
{
CEntityObject::SetSelected(bSelect);
if (m_selectionReentryGuard)
{
// Ignore event when received from the tools app, since the action is originating in Sandbox.
EditorActionScope selectionChange(m_selectionReentryGuard);
// Pass the action to the tools application.
if (bSelect)
{
AzToolsFramework::ToolsApplicationRequestBus::Broadcast(&AzToolsFramework::ToolsApplicationRequests::MarkEntitySelected, m_entityId);
}
else
{
AzToolsFramework::ToolsApplicationRequestBus::Broadcast(&AzToolsFramework::ToolsApplicationRequests::MarkEntityDeselected, m_entityId);
}
}
bool anySelected = false;
AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(anySelected, &AzToolsFramework::ToolsApplicationRequests::AreAnyEntitiesSelected);
if (!anySelected)
{
GetIEditor()->Notify(eNotify_OnEntitiesDeselected);
}
else
{
GetIEditor()->Notify(eNotify_OnEntitiesSelected);
}
}
void CComponentEntityObject::SetHighlight(bool bHighlight)
{
CEntityObject::SetHighlight(bHighlight);
if (m_entityId.IsValid())
{
EBUS_EVENT(AzToolsFramework::ToolsApplicationRequests::Bus, SetEntityHighlighted, m_entityId, bHighlight);
}
}
void CComponentEntityObject::OnEntityNameChanged(const AZStd::string& name)
{
if (m_nameReentryGuard)
{
EditorActionScope selectionChange(m_nameReentryGuard);
SetName(QString(name.c_str()));
}
}
void CComponentEntityObject::AttachChild(CBaseObject* child, bool /*bKeepPos*/)
{
if (child->GetType() == OBJTYPE_AZENTITY)
{
CComponentEntityObject* childComponentEntity = static_cast<CComponentEntityObject*>(child);
AZ::EntityId childEntityId = childComponentEntity->GetAssociatedEntityId();
if (childEntityId.IsValid())
{
// The action is originating from Sandbox, so ignore the return event.
EditorActionScope parentChange(childComponentEntity->m_parentingReentryGuard);
{
AzToolsFramework::ScopedUndoBatch undoBatch("Editor Parent");
EBUS_EVENT_ID(childEntityId, AZ::TransformBus, SetParent, m_entityId);
undoBatch.MarkEntityDirty(childEntityId);
}
EBUS_EVENT(AzToolsFramework::ToolsApplicationEvents::Bus, InvalidatePropertyDisplay, AzToolsFramework::Refresh_Values);
}
}
}
void CComponentEntityObject::DetachAll(bool /*bKeepPos*/)
{
}
void CComponentEntityObject::DetachThis(bool /*bKeepPos*/)
{
if (m_parentingReentryGuard)
{
EditorActionScope parentChange(m_parentingReentryGuard);
if (m_entityId.IsValid())
{
AzToolsFramework::ScopedUndoBatch undoBatch("Editor Unparent");
EBUS_EVENT_ID(m_entityId, AZ::TransformBus, SetParent, AZ::EntityId());
undoBatch.MarkEntityDirty(m_entityId);
}
EBUS_EVENT(AzToolsFramework::ToolsApplicationEvents::Bus, InvalidatePropertyDisplay, AzToolsFramework::Refresh_Values);
}
}
bool CComponentEntityObject::IsFrozen() const
{
return CheckFlags(OBJFLAG_FROZEN);
}
void CComponentEntityObject::SetFrozen(bool frozen)
{
if (m_lockedReentryGuard)
{
EditorActionScope flagChange(m_lockedReentryGuard);
AzToolsFramework::SetEntityLockState(m_entityId, frozen);
}
}
void CComponentEntityObject::OnEntityLockChanged(bool locked)
{
CEntityObject::SetFrozen(locked);
}
void CComponentEntityObject::OnEntityVisibilityChanged(bool visible)
{
CEntityObject::SetHidden(!visible);
}
void CComponentEntityObject::OnEntityIconChanged(const AZ::Data::AssetId& entityIconAssetId)
{
(void)entityIconAssetId;
SetupEntityIcon();
}
void CComponentEntityObject::OnParentChanged([[maybe_unused]] AZ::EntityId oldParent, [[maybe_unused]] AZ::EntityId newParent)
{
}
void CComponentEntityObject::OnRenderBoundsReset()
{
CEntityObject::InvalidateTM(0);
}
void CComponentEntityObject::SetSandboxObjectAccent(AzToolsFramework::EntityAccentType accent)
{
m_accentType = accent;
AzToolsFramework::EditorComponentSelectionNotificationsBus::Event(m_entityId,
&AzToolsFramework::EditorComponentSelectionNotificationsBus::Events::OnAccentTypeChanged, m_accentType);
}
void CComponentEntityObject::SetSandBoxObjectIsolated(bool isIsolated)
{
m_isIsolated = isIsolated;
GetIEditor()->GetObjectManager()->InvalidateVisibleList();
}
bool CComponentEntityObject::IsSandBoxObjectIsolated()
{
return m_isIsolated;
}
bool CComponentEntityObject::SetPos(const Vec3& pos, int flags)
{
bool isAzEditorTransformLocked = false;
AzToolsFramework::Components::TransformComponentMessages::Bus::EventResult(isAzEditorTransformLocked, m_entityId,
&AzToolsFramework::Components::TransformComponentMessages::IsTransformLocked);
bool lockTransformOnUserInput = isAzEditorTransformLocked && (flags & eObjectUpdateFlags_UserInput);
if (IsLayer() || lockTransformOnUserInput)
{
return false;
}
if ((flags & eObjectUpdateFlags_MoveTool) || (flags & eObjectUpdateFlags_UserInput))
{
// If we have a parent also in the selection set, don't allow the move tool to manipulate our position.
if (IsNonLayerAncestorSelected())
{
return false;
}
}
return CEntityObject::SetPos(pos, flags);
}
bool CComponentEntityObject::SetRotation(const Quat& rotate, int flags)
{
bool isAzEditorTransformLocked = false;
AzToolsFramework::Components::TransformComponentMessages::Bus::EventResult(isAzEditorTransformLocked, m_entityId,
&AzToolsFramework::Components::TransformComponentMessages::IsTransformLocked);
bool lockTransformOnUserInput = isAzEditorTransformLocked && (flags & eObjectUpdateFlags_UserInput);
if (IsLayer() || lockTransformOnUserInput)
{
return false;
}
if (flags & eObjectUpdateFlags_UserInput)
{
// If we have a parent also in the selection set, don't allow the rotate tool to manipulate our position.
if (IsNonLayerAncestorSelected())
{
return false;
}
}
return CEntityObject::SetRotation(rotate, flags);
}
bool CComponentEntityObject::SetScale(const Vec3& scale, int flags)
{
bool isAzEditorTransformLocked = false;
AzToolsFramework::Components::TransformComponentMessages::Bus::EventResult(isAzEditorTransformLocked, m_entityId,
&AzToolsFramework::Components::TransformComponentMessages::IsTransformLocked);
bool lockTransformOnUserInput = isAzEditorTransformLocked && (flags & eObjectUpdateFlags_UserInput);
if (IsLayer() || lockTransformOnUserInput)
{
return false;
}
if ((flags & eObjectUpdateFlags_ScaleTool) || (flags & eObjectUpdateFlags_UserInput))
{
// If we have a parent also in the selection set, don't allow the scale tool to manipulate our position.
if (IsNonLayerAncestorSelected())
{
return false;
}
}
return CEntityObject::SetScale(scale, flags);
}
bool CComponentEntityObject::IsNonLayerAncestorSelected() const
{
AZ::EntityId parentId;
AZ::TransformBus::EventResult(parentId, m_entityId, &AZ::TransformBus::Events::GetParentId);
while (parentId.IsValid())
{
CComponentEntityObject* parentObject = CComponentEntityObject::FindObjectForEntity(parentId);
if (parentObject && parentObject->IsSelected())
{
bool isLayerEntity = false;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
isLayerEntity,
parentObject->GetAssociatedEntityId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::HasLayer);
if (!isLayerEntity)
{
return true;
}
}
AZ::EntityId currentParentId = parentId;
parentId.SetInvalid();
AZ::TransformBus::EventResult(parentId, currentParentId, &AZ::TransformBus::Events::GetParentId);
}
return false;
}
bool CComponentEntityObject::IsLayer() const
{
bool isLayerEntity = false;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
isLayerEntity,
m_entityId,
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::HasLayer);
return isLayerEntity;
}
bool CComponentEntityObject::IsAncestorIconDrawingAtSameLocation() const
{
if (m_entityId.IsValid())
{
AZ::EntityId parentId;
AZ::TransformBus::EventResult(parentId, m_entityId, &AZ::TransformBus::Events::GetParentId);
if (!parentId.IsValid())
{
return false;
}
AZ::Vector3 worldTranslation;
AZ::TransformBus::EventResult(worldTranslation, m_entityId, &AZ::TransformBus::Events::GetWorldTranslation);
while (parentId.IsValid())
{
AZ::Vector3 parentTranslation;
AZ::TransformBus::EventResult(parentTranslation, parentId, &AZ::TransformBus::Events::GetWorldTranslation);
if (parentTranslation.GetDistanceSq(worldTranslation) < 0.01f)
{
CComponentEntityObject* parentObject = CComponentEntityObject::FindObjectForEntity(parentId);
if (parentObject && !parentObject->IsSelected() && parentObject->IsEntityIconVisible())
{
// An ancestor in the same location that's not selected and has icon visible has been found
return true;
}
}
AZ::EntityId currentParentId = parentId;
parentId.SetInvalid();
AZ::TransformBus::EventResult(parentId, currentParentId, &AZ::TransformBus::Events::GetParentId);
}
}
return false;
}
bool CComponentEntityObject::IsDescendantSelectedAtSameLocation() const
{
if (m_entityId.IsValid())
{
if (GetObjectManager()->GetSelection() && GetObjectManager()->GetSelection()->GetCount() == 0)
{
return false;
}
AZ::Vector3 worldTranslation;
AZ::TransformBus::EventResult(worldTranslation, m_entityId, &AZ::TransformBus::Events::GetWorldTranslation);
// For each descendant, check if it's selected and if so if it's located at the same location as we are
AzToolsFramework::EntityIdList descendantIds;
AZ::TransformBus::EventResult(descendantIds, m_entityId, &AZ::TransformBus::Events::GetAllDescendants);
for (AZ::EntityId entityId : descendantIds)
{
CComponentEntityObject* descendantObject = CComponentEntityObject::FindObjectForEntity(entityId);
if (descendantObject && descendantObject->IsSelected())
{
// Check if this entity is at the exact location of us
AZ::Vector3 entityTranslation;
AZ::TransformBus::EventResult(entityTranslation, entityId, &AZ::TransformBus::Events::GetWorldTranslation);
if (entityTranslation.GetDistanceSq(worldTranslation) < 0.01f)
{
return true;
}
}
}
}
return false;
}
void CComponentEntityObject::InvalidateTM(int nWhyFlags)
{
CEntityObject::InvalidateTM(nWhyFlags);
if (m_transformReentryGuard)
{
EditorActionScope transformChange(m_transformReentryGuard);
if (m_entityId.IsValid())
{
Matrix34 worldTransform = GetWorldTM();
EBUS_EVENT_ID(m_entityId, AZ::TransformBus, SetWorldTM, LYTransformToAZTransform(worldTransform));
}
}
}
void CComponentEntityObject::OnTransformChanged([[maybe_unused]] const AZ::Transform& local, const AZ::Transform& world)
{
if (m_transformReentryGuard) // Ignore if action originated from Sandbox.
{
EditorActionScope transformChange(m_transformReentryGuard);
Matrix34 worlTM = AZTransformToLYTransform(world);
SetLocalTM(worlTM, eObjectUpdateFlags_Animated);
}
}
bool CComponentEntityObject::HitTest(HitContext& hc)
{
AZ_PROFILE_FUNCTION(Entity);
if (m_iconOnlyHitTest)
{
return false;
}
if (m_entityId.IsValid())
{
// Pick against bounding box/geometry
AABB bounds(AABB::RESET);
GetBoundBox(bounds);
if (!bounds.IsReset())
{
Vec3 hitPos;
if (Intersect::Ray_AABB(Ray(hc.raySrc, hc.rayDir), bounds, hitPos))
{
bool rayIntersection = false;
bool preciseSelectionRequired = false;
float closestDistance = std::numeric_limits<float>::max();
const int viewportId = GetIEditor()->GetViewManager()->GetGameViewport()->GetViewportId();
AzToolsFramework::EditorComponentSelectionRequestsBus::EnumerateHandlersId(m_entityId,
[&hc, &closestDistance, &rayIntersection, &preciseSelectionRequired, viewportId](
AzToolsFramework::EditorComponentSelectionRequests* handler) -> bool
{
AZ_PROFILE_FUNCTION(Entity);
if (handler->SupportsEditorRayIntersect())
{
float distance = std::numeric_limits<float>::max();
preciseSelectionRequired = true;
const bool intersection = handler->EditorSelectionIntersectRayViewport(
{ viewportId }, LYVec3ToAZVec3(hc.raySrc), LYVec3ToAZVec3(hc.rayDir), distance);
rayIntersection = rayIntersection || intersection;
if (intersection && distance < closestDistance)
{
closestDistance = distance;
}
}
return true; // iterate over all handlers
});
hc.object = this;
if (preciseSelectionRequired)
{
hc.dist = closestDistance;
return rayIntersection;
}
hc.dist = (hitPos - hc.raySrc).GetLength();
return true;
}
return false;
}
}
const Vec3 origin = GetWorldPos();
const float radius = GetRadius();
Vec3 w = origin - hc.raySrc;
Vec3 wcross = hc.rayDir.Cross(w);
const float d = wcross.GetLengthSquared();
if (d < square(radius) + hc.distanceTolerance &&
w.GetLengthSquared() > square(radius))
{
Vec3 i0;
hc.object = this;
if (Intersect::Ray_SphereFirst(Ray(hc.raySrc, hc.rayDir), ::Sphere(origin, radius), i0))
{
hc.dist = hc.raySrc.GetDistance(i0);
return true;
}
hc.dist = hc.raySrc.GetDistance(origin);
return true;
}
return false;
}
void CComponentEntityObject::GetBoundBox(AABB& box)
{
AZ_PROFILE_FUNCTION(Entity);
box.Reset();
const AZ::EntityId entityId = m_entityId;
if (entityId.IsValid())
{
CViewport* gameViewport = GetIEditor()->GetViewManager()->GetGameViewport();
const int viewportId = gameViewport ? gameViewport->GetViewportId() : -1;
if (const AZ::Aabb bound =
AzToolsFramework::CalculateEditorEntitySelectionBounds(entityId, AzFramework::ViewportInfo{viewportId});
bound.IsValid())
{
box.Add(AZVec3ToLYVec3(bound.GetMin()));
box.Add(AZVec3ToLYVec3(bound.GetMax()));
return;
}
}
CBaseObject::GetBoundBox(box);
}
void CComponentEntityObject::GetLocalBounds(AABB& box)
{
box.Reset();
float r = GetRadius();
box.min = -Vec3(r, r, r);
box.max = Vec3(r, r, r);
}
XmlNodeRef CComponentEntityObject::Export([[maybe_unused]] const QString& levelPath, [[maybe_unused]] XmlNodeRef& xmlNode)
{
// All component entities are serialized out in a separate pass, so they can be
// loaded en-masse rather than individually. As such, we don't export them
// alongside Cry Entities.
return XmlNodeRef();
}
CComponentEntityObject* CComponentEntityObject::FindObjectForEntity(AZ::EntityId id)
{
CEntityObject* object = nullptr;
EBUS_EVENT_ID_RESULT(object, id, AzToolsFramework::ComponentEntityEditorRequestBus, GetSandboxObject);
if (object && (object->GetType() == OBJTYPE_AZENTITY))
{
return static_cast<CComponentEntityObject*>(object);
}
return nullptr;
}
void CComponentEntityObject::Display(DisplayContext& dc)
{
if (!(dc.flags & DISPLAY_2D))
{
m_entityIconVisible = false;
}
bool displaySelectionHelper = false;
if (!CanBeDrawn(dc, displaySelectionHelper))
{
return;
}
DrawDefault(dc);
bool showIcons = m_hasIcon;
if (showIcons)
{
SEditorSettings* editorSettings = GetIEditor()->GetEditorSettings();
if (!editorSettings->viewports.bShowIcons && !editorSettings->viewports.bShowSizeBasedIcons)
{
showIcons = false;
}
}
if (m_entityId.IsValid())
{
// Draw link to parent if this or the parent object are selected.
{
AZ::EntityId parentId;
EBUS_EVENT_ID_RESULT(parentId, m_entityId, AZ::TransformBus, GetParentId);
if (parentId.IsValid())
{
bool isParentVisible = false;
AzToolsFramework::EditorEntityInfoRequestBus::EventResult(isParentVisible, parentId, &AzToolsFramework::EditorEntityInfoRequestBus::Events::IsVisible);
CComponentEntityObject* parentObject = CComponentEntityObject::FindObjectForEntity(parentId);
if (isParentVisible && (IsSelected() || (parentObject && parentObject->IsSelected())))
{
const QColor kLinkColorParent(0, 255, 255);
const QColor kLinkColorChild(0, 0, 255);
AZ::Vector3 parentTranslation;
EBUS_EVENT_ID_RESULT(parentTranslation, parentId, AZ::TransformBus, GetWorldTranslation);
dc.DrawLine(AZVec3ToLYVec3(parentTranslation), GetWorldTM().GetTranslation(), kLinkColorParent, kLinkColorChild);
}
}
}
// Don't draw icons if we have an ancestor in the same location that has an icon - makes sure
// ancestor icons draw on top and are able to be selected over children. Also check if a descendant
// is selected at the same location. In cases of entity hierarchies where numerous ancestors have
// no position offset, we need this so the ancestors don't draw over us when we're selected
if (showIcons)
{
if ((dc.flags & DISPLAY_2D) ||
IsSelected() ||
IsAncestorIconDrawingAtSameLocation() ||
IsDescendantSelectedAtSameLocation())
{
showIcons = false;
}
}
// Allow components to override in-editor visualization.
{
const AzFramework::DisplayContextRequestGuard displayContextGuard(dc);
AzFramework::DebugDisplayRequestBus::BusPtr debugDisplayBus;
AzFramework::DebugDisplayRequestBus::Bind(
debugDisplayBus, AzFramework::g_defaultSceneEntityDebugDisplayId);
AZ_Assert(debugDisplayBus, "Invalid DebugDisplayRequestBus.");
AzFramework::DebugDisplayRequests* debugDisplay =
AzFramework::DebugDisplayRequestBus::FindFirstHandler(debugDisplayBus);
AzFramework::EntityDebugDisplayEventBus::Event(
m_entityId, &AzFramework::EntityDebugDisplayEvents::DisplayEntityViewport,
AzFramework::ViewportInfo{ dc.GetView()->asCViewport()->GetViewportId() },
*debugDisplay);
}
}
}
void CComponentEntityObject::DrawDefault(DisplayContext& dc, const QColor& labelColor)
{
CEntityObject::DrawDefault(dc, labelColor);
DrawAccent(dc);
}
bool CComponentEntityObject::IsIsolated() const
{
return m_isIsolated;
}
bool CComponentEntityObject::IsSelected() const
{
return AzToolsFramework::IsSelected(m_entityId);
}
bool CComponentEntityObject::IsSelectable() const
{
return AzToolsFramework::IsSelectableInViewport(m_entityId);
}
void CComponentEntityObject::SetWorldPos(const Vec3& pos, int flags)
{
// Layers, by design, are not supposed to be moveable. Layers are intended to just be a grouping
// mechanism to allow teams to cleanly split their level into working zones, and a moveable position
// complicates that behavior more than it helps.
// Unfortunately component entity objects have a position under the hood, so prevent layers from moving here.
bool isLayerEntity = false;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
isLayerEntity,
m_entityId,
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::HasLayer);
bool isAzEditorTransformLocked = false;
AzToolsFramework::Components::TransformComponentMessages::Bus::EventResult(isAzEditorTransformLocked, m_entityId,
&AzToolsFramework::Components::TransformComponentMessages::IsTransformLocked);
bool lockTransformOnUserInput = isAzEditorTransformLocked && (flags & eObjectUpdateFlags_UserInput);
if (isLayerEntity || lockTransformOnUserInput)
{
return;
}
CEntityObject::SetWorldPos(pos, flags);
}
void CComponentEntityObject::SetupEntityIcon()
{
bool hideIconInViewport = false;
m_hasIcon = false;
AzToolsFramework::EditorEntityIconComponentRequestBus::EventResult(hideIconInViewport, m_entityId, &AzToolsFramework::EditorEntityIconComponentRequestBus::Events::IsEntityIconHiddenInViewport);
if (!hideIconInViewport)
{
AzToolsFramework::EditorEntityIconComponentRequestBus::EventResult(m_icon, m_entityId, &AzToolsFramework::EditorEntityIconComponentRequestBus::Events::GetEntityIconPath);
if (!m_icon.empty())
{
m_hasIcon = true;
// ToDo: Get from Atom?
// int textureId = GetIEditor()->GetIconManager()->GetIconTexture(m_icon.c_str());
m_iconTexture = nullptr;
}
}
}
void CComponentEntityObject::DrawAccent(DisplayContext& dc)
{
if (!m_drawAccents)
{
return;
}
switch (m_accentType)
{
case AzToolsFramework::EntityAccentType::None:
{
if (dc.flags & DISPLAY_2D)
{
dc.SetColor(0.941f, 0.764f, 0.176f); // Yellow
}
else
{
return;
}
break;
}
case AzToolsFramework::EntityAccentType::Hover:
{
dc.SetColor(0, 1, 0); // Green
break;
}
case AzToolsFramework::EntityAccentType::Selected:
{
dc.SetColor(1, 0, 0); // Red
break;
}
case AzToolsFramework::EntityAccentType::ParentSelected:
{
dc.SetColor(1, 0.549f, 0); // Orange
break;
}
case AzToolsFramework::EntityAccentType::SliceSelected:
{
dc.SetColor(0.117f, 0.565f, 1); // Blue
break;
}
default:
{
dc.SetColor(1, 0.0784f, 0.576f); // Pink
break;
}
}
// if there are no explicit handlers, default to show the aabb when the mouse is over or the entity is selected.
// this will be the case with newly added entities without explicit handlers attached (no components).
if (!AzToolsFramework::EditorComponentSelectionRequestsBus::HasHandlers(m_entityId))
{
AABB box;
GetBoundBox(box);
dc.DrawWireBox(box.min, box.max);
}
}