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/Gems/LmbrCentral/Code/Source/Shape/EditorSplineComponent.cpp

481 lines
19 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 "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, "http://docs.aws.amazon.com/console/lumberyard/userguide/spline-component")
->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.GetScale().GetMaxElement();
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