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.
476 lines
19 KiB
C++
476 lines
19 KiB
C++
/*
|
|
* Copyright (c) Contributors to the Open 3D Engine Project
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
*
|
|
*/
|
|
|
|
#include "LmbrCentral_precompiled.h"
|
|
#include "EditorSplineComponent.h"
|
|
#include "EditorSplineComponentMode.h"
|
|
|
|
#include <AzCore/Serialization/EditContext.h>
|
|
#include <AzCore/Serialization/SerializeContext.h>
|
|
#include <AzFramework/Viewport/CameraState.h>
|
|
#include <AzFramework/Viewport/ViewportColors.h>
|
|
#include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
|
|
#include <AzToolsFramework/Viewport/VertexContainerDisplay.h>
|
|
#include <AzToolsFramework/ViewportSelection/EditorSelectionUtil.h>
|
|
|
|
#include "MathConversion.h"
|
|
|
|
namespace LmbrCentral
|
|
{
|
|
static const float s_lineWidth = 0.1f; ///< The 'virtual' width of the line (for selection)
|
|
|
|
void EditorSplineComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
|
|
{
|
|
required.push_back(AZ_CRC("TransformService", 0x8ee22c50));
|
|
}
|
|
|
|
void EditorSplineComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
|
|
{
|
|
provided.push_back(AZ_CRC("SplineService", 0x2b674d3c));
|
|
provided.push_back(AZ_CRC("VariableVertexContainerService", 0x70c58740));
|
|
provided.push_back(AZ_CRC("FixedVertexContainerService", 0x83f1bbf2));
|
|
}
|
|
|
|
void EditorSplineComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
|
|
{
|
|
incompatible.push_back(AZ_CRC("VariableVertexContainerService", 0x70c58740));
|
|
incompatible.push_back(AZ_CRC("FixedVertexContainerService", 0x83f1bbf2));
|
|
incompatible.push_back(AZ_CRC_CE("NonUniformScaleService"));
|
|
}
|
|
|
|
void EditorSplineComponent::Reflect(AZ::ReflectContext* context)
|
|
{
|
|
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
|
|
{
|
|
serializeContext->Class<EditorSplineComponent, EditorComponentBase>()
|
|
->Version(2)
|
|
->Field("Visible", &EditorSplineComponent::m_visibleInEditor)
|
|
->Field("Configuration", &EditorSplineComponent::m_splineCommon)
|
|
->Field("ComponentMode", &EditorSplineComponent::m_componentModeDelegate)
|
|
;
|
|
|
|
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
|
|
{
|
|
editContext->Class<EditorSplineComponent>(
|
|
"Spline", "Defines a sequence of points that can be interpolated.")
|
|
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
|
|
->Attribute(AZ::Edit::Attributes::Category, "Shape")
|
|
->Attribute(AZ::Edit::Attributes::Icon, "Icons/Components/Spline.svg")
|
|
->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Spline.png")
|
|
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c))
|
|
->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://docs.o3de.org/docs/user-guide/components/reference/shape/spline/")
|
|
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
|
|
->DataElement(AZ::Edit::UIHandlers::Default, &EditorSplineComponent::m_visibleInEditor, "Visible", "Always display this shape in the editor viewport")
|
|
->DataElement(AZ::Edit::UIHandlers::Default, &EditorSplineComponent::m_splineCommon, "Configuration", "Spline Configuration")
|
|
//->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) // disabled - prevents ChangeNotify attribute firing correctly
|
|
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorSplineComponent::SplineChanged)
|
|
->DataElement(AZ::Edit::UIHandlers::Default, &EditorSplineComponent::m_componentModeDelegate, "Component Mode", "Spline Component Mode")
|
|
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
|
|
;
|
|
}
|
|
}
|
|
}
|
|
|
|
void EditorSplineComponent::Activate()
|
|
{
|
|
EditorComponentBase::Activate();
|
|
|
|
const AZ::EntityId entityId = GetEntityId();
|
|
|
|
AzToolsFramework::EditorComponentSelectionRequestsBus::Handler::BusConnect(entityId);
|
|
AzToolsFramework::EditorComponentSelectionNotificationsBus::Handler::BusConnect(entityId);
|
|
AzFramework::EntityDebugDisplayEventBus::Handler::BusConnect(entityId);
|
|
SplineComponentRequestBus::Handler::BusConnect(entityId);
|
|
AZ::VariableVerticesRequestBus<AZ::Vector3>::Handler::BusConnect(entityId);
|
|
AZ::FixedVerticesRequestBus<AZ::Vector3>::Handler::BusConnect(entityId);
|
|
AZ::TransformNotificationBus::Handler::BusConnect(entityId);
|
|
AzFramework::BoundsRequestBus::Handler::BusConnect(entityId);
|
|
|
|
m_cachedUniformScaleTransform = AZ::Transform::CreateIdentity();
|
|
AZ::TransformBus::EventResult(m_cachedUniformScaleTransform, entityId, &AZ::TransformBus::Events::GetWorldTM);
|
|
m_cachedUniformScaleTransform = AzToolsFramework::TransformUniformScale(m_cachedUniformScaleTransform);
|
|
|
|
// placeholder - create initial spline if empty
|
|
AZ::VertexContainer<AZ::Vector3>& vertexContainer = m_splineCommon.m_spline->m_vertexContainer;
|
|
if (vertexContainer.Empty())
|
|
{
|
|
vertexContainer.AddVertex(AZ::Vector3(-3.0f, 0.0f, 0.0f));
|
|
vertexContainer.AddVertex(AZ::Vector3(-1.0f, 0.0f, 0.0f));
|
|
vertexContainer.AddVertex(AZ::Vector3(1.0f, 0.0f, 0.0f));
|
|
vertexContainer.AddVertex(AZ::Vector3(3.0f, 0.0f, 0.0f));
|
|
}
|
|
|
|
const auto vertexAdded = [this](size_t vertIndex)
|
|
{
|
|
SplineComponentNotificationBus::Event(
|
|
GetEntityId(), &SplineComponentNotificationBus::Events::OnVertexAdded, vertIndex);
|
|
|
|
SplineChanged();
|
|
};
|
|
|
|
const auto vertexRemoved = [this](size_t vertIndex)
|
|
{
|
|
SplineComponentNotificationBus::Event(
|
|
GetEntityId(), &SplineComponentNotificationBus::Events::OnVertexRemoved, vertIndex);
|
|
|
|
SplineChanged();
|
|
};
|
|
|
|
const auto vertexUpdated = [this](size_t vertIndex)
|
|
{
|
|
SplineComponentNotificationBus::Event(
|
|
GetEntityId(), &SplineComponentNotificationBus::Events::OnVertexUpdated, vertIndex);
|
|
|
|
SplineChanged();
|
|
};
|
|
|
|
const auto verticesSet = [this]()
|
|
{
|
|
SplineComponentNotificationBus::Event(
|
|
GetEntityId(), &SplineComponentNotificationBus::Events::OnVerticesSet,
|
|
m_splineCommon.m_spline->GetVertices());
|
|
|
|
SplineChanged();
|
|
};
|
|
|
|
const auto verticesCleared = [this]()
|
|
{
|
|
SplineComponentNotificationBus::Event(
|
|
GetEntityId(), &SplineComponentNotificationBus::Events::OnVerticesCleared);
|
|
|
|
SplineChanged();
|
|
};
|
|
|
|
const auto openCloseChanged = [this](const bool closed)
|
|
{
|
|
SplineComponentNotificationBus::Event(
|
|
GetEntityId(), &SplineComponentNotificationBus::Events::OnOpenCloseChanged, closed);
|
|
|
|
SplineChanged();
|
|
};
|
|
|
|
m_splineCommon.SetCallbacks(
|
|
vertexAdded,
|
|
vertexRemoved,
|
|
vertexUpdated,
|
|
verticesSet,
|
|
verticesCleared,
|
|
[this]() { OnChangeSplineType(); },
|
|
openCloseChanged);
|
|
|
|
m_componentModeDelegate.ConnectWithSingleComponentMode<
|
|
EditorSplineComponent, EditorSplineComponentMode>(
|
|
AZ::EntityComponentIdPair(GetEntityId(), GetId()), this);
|
|
}
|
|
|
|
void EditorSplineComponent::Deactivate()
|
|
{
|
|
EditorComponentBase::Deactivate();
|
|
|
|
m_splineCommon.SetCallbacks(
|
|
nullptr, nullptr, nullptr, nullptr,
|
|
nullptr, nullptr, nullptr);
|
|
|
|
m_componentModeDelegate.Disconnect();
|
|
|
|
AzFramework::BoundsRequestBus::Handler::BusDisconnect();
|
|
AZ::TransformNotificationBus::Handler::BusDisconnect();
|
|
AZ::FixedVerticesRequestBus<AZ::Vector3>::Handler::BusDisconnect();
|
|
AZ::VariableVerticesRequestBus<AZ::Vector3>::Handler::BusDisconnect();
|
|
SplineComponentRequestBus::Handler::BusDisconnect();
|
|
AzFramework::EntityDebugDisplayEventBus::Handler::BusDisconnect();
|
|
AzToolsFramework::EditorComponentSelectionNotificationsBus::Handler::BusDisconnect();
|
|
AzToolsFramework::EditorComponentSelectionRequestsBus::Handler::BusDisconnect();
|
|
}
|
|
|
|
void EditorSplineComponent::BuildGameEntity(AZ::Entity* gameEntity)
|
|
{
|
|
if (auto component = gameEntity->CreateComponent<SplineComponent>())
|
|
{
|
|
component->m_splineCommon = m_splineCommon;
|
|
}
|
|
}
|
|
|
|
static void DrawSpline(
|
|
const AZ::Spline& spline, const size_t begin, const size_t end,
|
|
const AZ::Transform& worldFromLocal, AzFramework::DebugDisplayRequests& debugDisplay)
|
|
{
|
|
const size_t granularity = spline.GetSegmentGranularity();
|
|
|
|
for (size_t vertIndex = begin; vertIndex < end; ++vertIndex)
|
|
{
|
|
AZ::Vector3 p1 = worldFromLocal.TransformPoint(spline.GetVertex(vertIndex - 1));
|
|
for (size_t granularityStep = 1; granularityStep <= granularity; ++granularityStep)
|
|
{
|
|
const AZ::Vector3 p2 = worldFromLocal.TransformPoint(spline.GetPosition(
|
|
AZ::SplineAddress(vertIndex - 1, granularityStep / static_cast<float>(granularity))));
|
|
debugDisplay.DrawLine(p1, p2);
|
|
p1 = p2;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void DrawVertices(
|
|
const AZ::Spline& spline, const AZ::Transform& worldFromLocal,
|
|
const AzFramework::CameraState& cameraState,
|
|
const size_t begin, const size_t end, AzFramework::DebugDisplayRequests& debugDisplay)
|
|
{
|
|
for (size_t vertIndex = begin; vertIndex < end; ++vertIndex)
|
|
{
|
|
const AZ::Vector3& worldPosition = worldFromLocal.TransformPoint(spline.GetVertex(vertIndex));
|
|
debugDisplay.DrawBall(
|
|
worldPosition, 0.075f * AzToolsFramework::CalculateScreenToWorldMultiplier(worldPosition, cameraState));
|
|
}
|
|
}
|
|
|
|
void EditorSplineComponent::DisplayEntityViewport(
|
|
const AzFramework::ViewportInfo& viewportInfo,
|
|
AzFramework::DebugDisplayRequests& debugDisplay)
|
|
{
|
|
const bool mouseHovered = m_accentType == AzToolsFramework::EntityAccentType::Hover;
|
|
if (!IsSelected() && !m_visibleInEditor && !mouseHovered)
|
|
{
|
|
return;
|
|
}
|
|
|
|
AzFramework::CameraState cameraState;
|
|
AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::EventResult(
|
|
cameraState, viewportInfo.m_viewportId,
|
|
&AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::Events::GetCameraState);
|
|
|
|
const AZ::Spline* spline = m_splineCommon.m_spline.get();
|
|
const size_t vertexCount = spline->GetVertexCount();
|
|
if (vertexCount == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Set color
|
|
if (IsSelected())
|
|
{
|
|
debugDisplay.SetColor(AzFramework::ViewportColors::SelectedColor);
|
|
}
|
|
else if(mouseHovered)
|
|
{
|
|
debugDisplay.SetColor(AzFramework::ViewportColors::HoverColor);
|
|
}
|
|
else
|
|
{
|
|
debugDisplay.SetColor(AzFramework::ViewportColors::DeselectedColor);
|
|
}
|
|
|
|
const bool inComponentMode = m_componentModeDelegate.AddedToComponentMode();
|
|
|
|
// render spline
|
|
if (spline->RTTI_IsTypeOf(AZ::LinearSpline::RTTI_Type()) || spline->RTTI_IsTypeOf(AZ::BezierSpline::RTTI_Type()))
|
|
{
|
|
DrawSpline(
|
|
*spline, 1, spline->IsClosed() ? vertexCount + 1 : vertexCount,
|
|
m_cachedUniformScaleTransform, debugDisplay);
|
|
|
|
if (!inComponentMode)
|
|
{
|
|
DrawVertices(*spline, m_cachedUniformScaleTransform, cameraState, 0, vertexCount, debugDisplay);
|
|
}
|
|
}
|
|
else if (spline->RTTI_IsTypeOf(AZ::CatmullRomSpline::RTTI_Type()))
|
|
{
|
|
// the minimum number of control points for a Catmull-Rom spline is 4; do not attempt to render the spline
|
|
// unless this condition is met
|
|
if (spline->GetVertexCount() >= 4)
|
|
{
|
|
// a Catmull-Rom spline uses the first and last points as control points only, omit them from display
|
|
DrawSpline(
|
|
*spline, spline->IsClosed() ? 1 : 2, spline->IsClosed() ? vertexCount + 1 : vertexCount - 1,
|
|
m_cachedUniformScaleTransform, debugDisplay);
|
|
}
|
|
|
|
if (!inComponentMode)
|
|
{
|
|
DrawVertices(*spline, m_cachedUniformScaleTransform, cameraState, 1, vertexCount - 1, debugDisplay);
|
|
}
|
|
}
|
|
|
|
if (inComponentMode)
|
|
{
|
|
AzToolsFramework::VertexContainerDisplay::DisplayVertexContainerIndices(
|
|
debugDisplay, AzToolsFramework::VariableVerticesVertexContainer<AZ::Vector3>(
|
|
m_splineCommon.m_spline->m_vertexContainer), m_cachedUniformScaleTransform, AZ::Vector3::CreateOne(), IsSelected());
|
|
}
|
|
}
|
|
|
|
AZ::Aabb EditorSplineComponent::GetEditorSelectionBoundsViewport(
|
|
const AzFramework::ViewportInfo& viewportInfo)
|
|
{
|
|
AzFramework::CameraState cameraState;
|
|
AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::EventResult(
|
|
cameraState, viewportInfo.m_viewportId,
|
|
&AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::Events::GetCameraState);
|
|
|
|
const float screenToWorldScale = AzToolsFramework::CalculateScreenToWorldMultiplier(
|
|
m_cachedUniformScaleTransform.GetTranslation(), cameraState);
|
|
|
|
const float lineWidth = s_lineWidth * screenToWorldScale;
|
|
AZ::Aabb aabb = GetWorldBounds();
|
|
const AZ::Vector3 extents = aabb.GetExtents();
|
|
|
|
AZ::Vector3 expandExtents = AZ::Vector3::CreateZero();
|
|
if (extents.GetX() < lineWidth)
|
|
{
|
|
expandExtents.SetX(lineWidth);
|
|
}
|
|
|
|
if (extents.GetY() < lineWidth)
|
|
{
|
|
expandExtents.SetY(lineWidth);
|
|
}
|
|
|
|
if (extents.GetZ() < lineWidth)
|
|
{
|
|
expandExtents.SetZ(lineWidth);
|
|
}
|
|
|
|
aabb.Expand(expandExtents);
|
|
|
|
return aabb;
|
|
}
|
|
|
|
bool EditorSplineComponent::EditorSelectionIntersectRayViewport(
|
|
const AzFramework::ViewportInfo& viewportInfo,
|
|
const AZ::Vector3& src, const AZ::Vector3& dir, float& distance)
|
|
{
|
|
const auto rayIntersectData = IntersectSpline(m_cachedUniformScaleTransform, src, dir, *m_splineCommon.m_spline);
|
|
distance = rayIntersectData.m_rayDistance * m_cachedUniformScaleTransform.GetUniformScale();
|
|
|
|
AzFramework::CameraState cameraState;
|
|
AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::EventResult(
|
|
cameraState, viewportInfo.m_viewportId,
|
|
&AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::Events::GetCameraState);
|
|
|
|
const float screenToWorldScale = AzToolsFramework::CalculateScreenToWorldMultiplier(
|
|
m_cachedUniformScaleTransform.GetTranslation(), cameraState);
|
|
|
|
return (static_cast<float>(rayIntersectData.m_distanceSq) < powf(s_lineWidth * screenToWorldScale, 2.0f));
|
|
}
|
|
|
|
void EditorSplineComponent::OnTransformChanged(const AZ::Transform& /*local*/, const AZ::Transform& world)
|
|
{
|
|
m_cachedUniformScaleTransform = AzToolsFramework::TransformUniformScale(world);
|
|
}
|
|
|
|
void EditorSplineComponent::OnChangeSplineType()
|
|
{
|
|
EditorSplineComponentNotificationBus::Event(
|
|
GetEntityId(), &EditorSplineComponentNotifications::OnSplineTypeChanged);
|
|
|
|
SplineChanged();
|
|
}
|
|
|
|
void EditorSplineComponent::SplineChanged() const
|
|
{
|
|
SplineComponentNotificationBus::Event(
|
|
GetEntityId(), &SplineComponentNotificationBus::Events::OnSplineChanged);
|
|
AZ::Interface<AzFramework::IEntityBoundsUnion>::Get()->RefreshEntityLocalBoundsUnion(GetEntityId());
|
|
}
|
|
|
|
AZ::SplinePtr EditorSplineComponent::GetSpline()
|
|
{
|
|
return m_splineCommon.m_spline;
|
|
}
|
|
|
|
void EditorSplineComponent::ChangeSplineType(const AZ::u64 splineType)
|
|
{
|
|
m_splineCommon.ChangeSplineType(splineType);
|
|
}
|
|
|
|
void EditorSplineComponent::SetClosed(const bool closed)
|
|
{
|
|
m_splineCommon.m_spline->SetClosed(closed);
|
|
SplineChanged();
|
|
}
|
|
|
|
bool EditorSplineComponent::GetVertex(const size_t vertIndex, AZ::Vector3& vertex) const
|
|
{
|
|
return m_splineCommon.m_spline->m_vertexContainer.GetVertex(vertIndex, vertex);
|
|
}
|
|
|
|
bool EditorSplineComponent::UpdateVertex(const size_t vertIndex, const AZ::Vector3& vertex)
|
|
{
|
|
if (m_splineCommon.m_spline->m_vertexContainer.UpdateVertex(vertIndex, vertex))
|
|
{
|
|
SplineChanged();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void EditorSplineComponent::AddVertex(const AZ::Vector3& vertex)
|
|
{
|
|
m_splineCommon.m_spline->m_vertexContainer.AddVertex(vertex);
|
|
SplineChanged();
|
|
}
|
|
|
|
bool EditorSplineComponent::InsertVertex(const size_t vertIndex, const AZ::Vector3& vertex)
|
|
{
|
|
if (m_splineCommon.m_spline->m_vertexContainer.InsertVertex(vertIndex, vertex))
|
|
{
|
|
SplineChanged();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool EditorSplineComponent::RemoveVertex(const size_t vertIndex)
|
|
{
|
|
if (m_splineCommon.m_spline->m_vertexContainer.RemoveVertex(vertIndex))
|
|
{
|
|
SplineChanged();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void EditorSplineComponent::SetVertices(const AZStd::vector<AZ::Vector3>& vertices)
|
|
{
|
|
m_splineCommon.m_spline->m_vertexContainer.SetVertices(vertices);
|
|
SplineChanged();
|
|
}
|
|
|
|
void EditorSplineComponent::ClearVertices()
|
|
{
|
|
m_splineCommon.m_spline->m_vertexContainer.Clear();
|
|
SplineChanged();
|
|
}
|
|
|
|
size_t EditorSplineComponent::Size() const
|
|
{
|
|
return m_splineCommon.m_spline->m_vertexContainer.Size();
|
|
}
|
|
|
|
bool EditorSplineComponent::Empty() const
|
|
{
|
|
return m_splineCommon.m_spline->m_vertexContainer.Empty();
|
|
}
|
|
|
|
AZ::Aabb EditorSplineComponent::GetWorldBounds()
|
|
{
|
|
AZ::Aabb aabb = AZ::Aabb::CreateNull();
|
|
m_splineCommon.m_spline->GetAabb(aabb, m_cachedUniformScaleTransform);
|
|
return aabb;
|
|
}
|
|
|
|
AZ::Aabb EditorSplineComponent::GetLocalBounds()
|
|
{
|
|
AZ::Aabb aabb = AZ::Aabb::CreateNull();
|
|
m_splineCommon.m_spline->GetAabb(aabb, AZ::Transform::CreateIdentity());
|
|
return aabb;
|
|
}
|
|
} // namespace LmbrCentral
|