Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
@@ -0,0 +1,249 @@
/*
* 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 "AngularManipulator.h"
#include <AzFramework/Entity/EntityDebugDisplayBus.h>
#include <AzToolsFramework/Manipulators/ManipulatorView.h>
#include <AzToolsFramework/Manipulators/ManipulatorSnapping.h>
namespace AzToolsFramework
{
static const float s_circularRotateThresholdDegrees = 80.0f;
AngularManipulator::ActionInternal AngularManipulator::CalculateManipulationDataStart(
const Fixed& fixed, const AZ::Transform& worldFromLocal, const AZ::Transform& localTransform,
const AZ::Vector3& rayOrigin, const AZ::Vector3& rayDirection, const float rayDistance)
{
const AZ::Transform worldFromLocalWithTransform = worldFromLocal * localTransform;
const AZ::Vector3 worldAxis = TransformDirectionNoScaling(worldFromLocalWithTransform, fixed.m_axis);
ActionInternal actionInternal;
// default plane normal and point used for rotation
actionInternal.m_start.m_planeNormal = worldAxis;
actionInternal.m_start.m_planePoint = worldFromLocalWithTransform.GetTranslation();
// if angular manipulator axis is at right angles to us, use initial ray direction
// as plane normal and use hit position on manipulator as plane point
const float pickAngle = AZ::RadToDeg(AZ::Acos(AZ::Abs(rayDirection.Dot(worldAxis))));
if (pickAngle > s_circularRotateThresholdDegrees)
{
actionInternal.m_start.m_planeNormal = -rayDirection;
actionInternal.m_start.m_planePoint = rayOrigin + rayDirection * rayDistance;
}
// store initial world hit position
Internal::CalculateRayPlaneIntersectingPoint(
rayOrigin, rayDirection, actionInternal.m_start.m_planePoint,
actionInternal.m_start.m_planeNormal, actionInternal.m_current.m_worldHitPosition);
// store entity transform (to go from local to world space)
// and store our own starting local transform
actionInternal.m_start.m_worldFromLocal = worldFromLocal;
actionInternal.m_start.m_localTransform = localTransform;
actionInternal.m_current.m_radians = 0.0f;
return actionInternal;
}
AngularManipulator::Action AngularManipulator::CalculateManipulationDataAction(
const Fixed& fixed, ActionInternal& actionInternal, const AZ::Transform& worldFromLocal,
const AZ::Transform& localTransform, const bool snapping, const float angleStepDegrees,
const AZ::Vector3& rayOrigin, const AZ::Vector3& rayDirection,
const ViewportInteraction::KeyboardModifiers keyboardModifiers)
{
const AZ::Transform worldFromLocalWithTransform = worldFromLocal * localTransform;
const AZ::Vector3 worldAxis = TransformDirectionNoScaling(worldFromLocalWithTransform, fixed.m_axis);
AZ::Vector3 worldHitPosition = AZ::Vector3::CreateZero();
Internal::CalculateRayPlaneIntersectingPoint(rayOrigin, rayDirection,
actionInternal.m_start.m_planePoint, actionInternal.m_start.m_planeNormal,
worldHitPosition);
// get vector from center of rotation for current and previous frame
const AZ::Vector3 center = worldFromLocalWithTransform.GetTranslation();
const AZ::Vector3 currentWorldHitVector = (worldHitPosition - center).GetNormalizedSafe();
const AZ::Vector3 previousWorldHitVector =
(actionInternal.m_current.m_worldHitPosition - center).GetNormalizedSafe();
// calculate which direction we rotated
const AZ::Vector3 worldAxisRight = worldAxis.Cross(previousWorldHitVector);
const float rotateSign = Sign(currentWorldHitVector.Dot(worldAxisRight));
// how far did we rotate this frame
const float rotationAngleRad = AZ::Acos(AZ::GetMin<float>(
1.0f, currentWorldHitVector.Dot(previousWorldHitVector)));
actionInternal.m_current.m_worldHitPosition = worldHitPosition;
// if we're snapping, only increment current radians when we know
// preSnapRadians is greater than the angleStep
if (snapping)
{
actionInternal.m_current.m_preSnapRadians += rotationAngleRad * rotateSign;
const float angleStepRad = AZ::DegToRad(angleStepDegrees);
const float preSnapRotateSign = Sign(actionInternal.m_current.m_preSnapRadians);
// if we move more than angleStep in a frame, make sure we catch up
while (fabsf(actionInternal.m_current.m_preSnapRadians) >= angleStepRad)
{
actionInternal.m_current.m_radians += angleStepRad * preSnapRotateSign;
actionInternal.m_current.m_preSnapRadians -= angleStepRad * preSnapRotateSign;
}
}
else
{
// no snapping, just update current radius immediately
actionInternal.m_current.m_radians += rotationAngleRad * rotateSign;
}
Action action;
action.m_start.m_space = actionInternal.m_start.m_worldFromLocal.GetRotation().GetNormalized();
action.m_start.m_rotation = actionInternal.m_start.m_localTransform.GetRotation().GetNormalized();
action.m_current.m_delta = AZ::Quaternion::CreateFromAxisAngle(fixed.m_axis, actionInternal.m_current.m_radians).GetNormalized();
action.m_modifiers = keyboardModifiers;
return action;
}
AZStd::shared_ptr<AngularManipulator> AngularManipulator::MakeShared(const AZ::Transform& worldFromLocal)
{
return AZStd::shared_ptr<AngularManipulator>(aznew AngularManipulator(worldFromLocal));
}
AngularManipulator::AngularManipulator(const AZ::Transform& worldFromLocal)
: m_worldFromLocal(worldFromLocal)
{
AttachLeftMouseDownImpl();
}
void AngularManipulator::InstallLeftMouseDownCallback(const MouseActionCallback& onMouseDownCallback)
{
m_onLeftMouseDownCallback = onMouseDownCallback;
}
void AngularManipulator::InstallLeftMouseUpCallback(const MouseActionCallback& onMouseUpCallback)
{
m_onLeftMouseUpCallback = onMouseUpCallback;
}
void AngularManipulator::InstallMouseMoveCallback(const MouseActionCallback& onMouseMoveCallback)
{
m_onMouseMoveCallback = onMouseMoveCallback;
}
void AngularManipulator::OnLeftMouseDownImpl(
const ViewportInteraction::MouseInteraction& interaction, const float rayIntersectionDistance)
{
const bool snapping = AngleSnapping(interaction.m_interactionId.m_viewportId);
const float angleStep = AngleStep(interaction.m_interactionId.m_viewportId);
// calculate initial state when mouse press first happens
m_actionInternal = CalculateManipulationDataStart(
m_fixed, TransformNormalizedScale(m_worldFromLocal), TransformNormalizedScale(m_localTransform),
interaction.m_mousePick.m_rayOrigin, interaction.m_mousePick.m_rayDirection,
rayIntersectionDistance);
if (m_onLeftMouseDownCallback)
{
m_onLeftMouseDownCallback(CalculateManipulationDataAction(
m_fixed, m_actionInternal, m_actionInternal.m_start.m_worldFromLocal,
m_actionInternal.m_start.m_localTransform, snapping, angleStep,
interaction.m_mousePick.m_rayOrigin, interaction.m_mousePick.m_rayDirection,
interaction.m_keyboardModifiers));
}
}
void AngularManipulator::OnMouseMoveImpl(const ViewportInteraction::MouseInteraction& interaction)
{
if (m_onMouseMoveCallback)
{
// calculate delta rotation
m_onMouseMoveCallback(CalculateManipulationDataAction(
m_fixed, m_actionInternal, m_actionInternal.m_start.m_worldFromLocal,
m_actionInternal.m_start.m_localTransform,
AngleSnapping(interaction.m_interactionId.m_viewportId),
AngleStep(interaction.m_interactionId.m_viewportId),
interaction.m_mousePick.m_rayOrigin, interaction.m_mousePick.m_rayDirection,
interaction.m_keyboardModifiers));
}
}
void AngularManipulator::OnLeftMouseUpImpl(const ViewportInteraction::MouseInteraction& interaction)
{
if (m_onLeftMouseUpCallback)
{
m_onLeftMouseUpCallback(CalculateManipulationDataAction(
m_fixed, m_actionInternal, m_actionInternal.m_start.m_worldFromLocal,
m_actionInternal.m_start.m_localTransform,
AngleSnapping(interaction.m_interactionId.m_viewportId),
AngleStep(interaction.m_interactionId.m_viewportId),
interaction.m_mousePick.m_rayOrigin, interaction.m_mousePick.m_rayDirection,
interaction.m_keyboardModifiers));
}
}
void AngularManipulator::Draw(
const ManipulatorManagerState& managerState,
AzFramework::DebugDisplayRequests& debugDisplay,
const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction)
{
m_manipulatorView->Draw(
GetManipulatorManagerId(), managerState,
GetManipulatorId(), {
m_worldFromLocal * m_localTransform,
AZ::Vector3::CreateZero(), MouseOver()
},
debugDisplay, cameraState, mouseInteraction);
}
void AngularManipulator::SetAxis(const AZ::Vector3& axis)
{
m_fixed.m_axis = axis;
}
void AngularManipulator::SetSpace(const AZ::Transform& worldFromLocal)
{
m_worldFromLocal = worldFromLocal;
}
void AngularManipulator::SetLocalTransform(const AZ::Transform& localTransform)
{
m_localTransform = localTransform;
}
void AngularManipulator::SetLocalPosition(const AZ::Vector3& localPosition)
{
m_localTransform.SetTranslation(localPosition);
}
void AngularManipulator::SetLocalOrientation(const AZ::Quaternion& localOrientation)
{
m_localTransform = AZ::Transform::CreateFromQuaternionAndTranslation(
localOrientation, m_localTransform.GetTranslation());
}
void AngularManipulator::SetView(AZStd::unique_ptr<ManipulatorView>&& view)
{
m_manipulatorView = AZStd::move(view);
}
void AngularManipulator::SetBoundsDirtyImpl()
{
m_manipulatorView->SetBoundDirty(GetManipulatorManagerId());
}
void AngularManipulator::InvalidateImpl()
{
m_manipulatorView->Invalidate(GetManipulatorManagerId());
}
} // namespace AzToolsFramework
@@ -0,0 +1,158 @@
/*
* 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.
*
*/
#pragma once
#include "BaseManipulator.h"
#include <AzCore/Math/Quaternion.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
namespace AzToolsFramework
{
class ManipulatorView;
/// AngularManipulator serves as a visual tool for users to change a component's property based on rotation
/// around an axis. The rotation angle increases if the rotation goes counter clock-wise when looking
/// in the opposite direction the rotation axis points to.
class AngularManipulator
: public BaseManipulator
{
/// Private constructor.
explicit AngularManipulator(const AZ::Transform& worldFromLocal);
public:
AZ_RTTI(AngularManipulator, "{01CB40F9-4537-4187-A8A6-1A12356D3FD1}", BaseManipulator);
AZ_CLASS_ALLOCATOR(AngularManipulator, AZ::SystemAllocator, 0);
AngularManipulator() = delete;
AngularManipulator(const AngularManipulator&) = delete;
AngularManipulator& operator=(const AngularManipulator&) = delete;
~AngularManipulator() = default;
/// A Manipulator must only be created and managed through a shared_ptr.
static AZStd::shared_ptr<AngularManipulator> MakeShared(const AZ::Transform& worldFromLocal);
/// The state of the manipulator at the start of an interaction.
struct Start
{
AZ::Quaternion m_space; ///< Starting orientation space of manipulator.
AZ::Quaternion m_rotation; ///< Starting local rotation of the manipulator.
};
/// The state of the manipulator during an interaction.
struct Current
{
AZ::Quaternion m_delta; ///< Amount of rotation to apply to manipulator during action.
};
/// Mouse action data used by MouseActionCallback (wraps Start and Current manipulator state).
struct Action
{
Start m_start;
Current m_current;
ViewportInteraction::KeyboardModifiers m_modifiers;
AZ::Quaternion LocalOrientation() const { return m_start.m_rotation * m_current.m_delta; }
};
/// This is the function signature of callbacks that will be invoked whenever a manipulator
/// is clicked on or dragged.
using MouseActionCallback = AZStd::function<void(const Action&)>;
void InstallLeftMouseDownCallback(const MouseActionCallback& onMouseDownCallback);
void InstallLeftMouseUpCallback(const MouseActionCallback& onMouseUpCallback);
void InstallMouseMoveCallback(const MouseActionCallback& onMouseMoveCallback);
void Draw(
const ManipulatorManagerState& managerState,
AzFramework::DebugDisplayRequests& debugDisplay,
const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction) override;
void SetAxis(const AZ::Vector3& axis);
void SetSpace(const AZ::Transform& worldFromLocal);
void SetLocalTransform(const AZ::Transform& localTransform);
void SetLocalPosition(const AZ::Vector3& localPosition);
void SetLocalOrientation(const AZ::Quaternion& localOrientation);
AZ::Vector3 GetPosition() const { return m_localTransform.GetTranslation(); }
const AZ::Vector3& GetAxis() const { return m_fixed.m_axis; }
void SetView(AZStd::unique_ptr<ManipulatorView>&& view);
ManipulatorView* GetView() const { return m_manipulatorView.get(); }
private:
void OnLeftMouseDownImpl(
const ViewportInteraction::MouseInteraction& interaction, float rayIntersectionDistance) override;
void OnLeftMouseUpImpl(
const ViewportInteraction::MouseInteraction& interaction) override;
void OnMouseMoveImpl(
const ViewportInteraction::MouseInteraction& interaction) override;
void SetBoundsDirtyImpl() override;
void InvalidateImpl() override;
/// Unchanging data set once for the angular manipulator.
struct Fixed
{
AZ::Vector3 m_axis = AZ::Vector3::CreateAxisX(); ///< Axis for this angular manipulator to rotate around.
};
/// Initial data recorded when a press first happens with an angular manipulator.
struct StartInternal
{
AZ::Transform m_worldFromLocal; ///< Initial transform when pressed.
AZ::Transform m_localTransform; ///< Additional transform (offset) to apply to manipulator.
AZ::Vector3 m_planePoint; ///< Position on plane to use for ray intersection.
AZ::Vector3 m_planeNormal; ///< Normal of plane to use for ray intersection.
};
/// Current data recorded each frame during an interaction with an angular manipulator.
struct CurrentInternal
{
float m_preSnapRadians = 0.0f; ///< Amount of rotation before a snap (snap increment accumulator).
float m_radians = 0.0f; ///< Amount of rotation about the axis for this action.
AZ::Vector3 m_worldHitPosition; ///< Initial world space hit position.
};
/// Wrap start and current internal data during an interaction with an angular manipulator.
struct ActionInternal
{
StartInternal m_start;
CurrentInternal m_current;
};
AZ::Transform m_localTransform = AZ::Transform::CreateIdentity(); ///< Local transform of the manipulator.
AZ::Transform m_worldFromLocal = AZ::Transform::CreateIdentity(); ///< Space the manipulator is in (identity is world space).
Fixed m_fixed;
ActionInternal m_actionInternal;
MouseActionCallback m_onLeftMouseDownCallback = nullptr;
MouseActionCallback m_onLeftMouseUpCallback = nullptr;
MouseActionCallback m_onMouseMoveCallback = nullptr;
AZStd::unique_ptr<ManipulatorView> m_manipulatorView; ///< Look of manipulator.
static ActionInternal CalculateManipulationDataStart(
const Fixed& fixed, const AZ::Transform& worldFromLocal, const AZ::Transform& localTransform,
const AZ::Vector3& rayOrigin, const AZ::Vector3& rayDirection, float rayDistance);
static Action CalculateManipulationDataAction(
const Fixed& fixed, ActionInternal& actionInternal, const AZ::Transform& worldFromLocal,
const AZ::Transform& localTransform, bool snapping, float angleStepDegrees,
const AZ::Vector3& rayOrigin, const AZ::Vector3& rayDirection,
ViewportInteraction::KeyboardModifiers keyboardModifiers);
};
} // namespace AzToolsFramework
@@ -0,0 +1,454 @@
/*
* 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 "BaseManipulator.h"
#include <AzCore/Math/IntersectSegment.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
namespace AzToolsFramework
{
AZ_CVAR(
bool, cl_manipulatorDrawDebug, false, nullptr, AZ::ConsoleFunctorFlags::Null,
"Enable debug drawing for Manipulators");
const AZ::Color BaseManipulator::s_defaultMouseOverColor = AZ::Color(1.0f, 1.0f, 0.0f, 1.0f); // yellow
AZ_CLASS_ALLOCATOR_IMPL(BaseManipulator, AZ::SystemAllocator, 0)
static bool EntityIdAndEntityComponentIdComparison(
const AZ::EntityId entityId, const AZ::EntityComponentIdPair& entityComponentId)
{
return entityId == entityComponentId.GetEntityId();
}
BaseManipulator::~BaseManipulator()
{
AZ_Assert(!Registered(), "Manipulator must be unregistered before it is destroyed");
EndUndoBatch();
}
bool BaseManipulator::OnLeftMouseDown(
const ViewportInteraction::MouseInteraction& interaction, const float rayIntersectionDistance)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
if (m_onLeftMouseDownImpl)
{
BeginAction();
ToolsApplicationRequests::Bus::BroadcastResult(
m_undoBatch, &ToolsApplicationRequests::Bus::Events::BeginUndoBatch, "ManipulatorLeftMouseDown");
for (const AZ::EntityComponentIdPair& entityComponentId : m_entityComponentIdPairs)
{
ToolsApplicationRequests::Bus::Broadcast(
&ToolsApplicationRequests::Bus::Events::AddDirtyEntity, entityComponentId.GetEntityId());
}
(*this.*m_onLeftMouseDownImpl)(interaction, rayIntersectionDistance);
ToolsApplicationNotificationBus::Broadcast(
&ToolsApplicationNotificationBus::Events::InvalidatePropertyDisplay, Refresh_Values);
return true;
}
return false;
}
bool BaseManipulator::OnRightMouseDown(
const ViewportInteraction::MouseInteraction& interaction, const float rayIntersectionDistance)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
if (m_onRightMouseDownImpl)
{
BeginAction();
ToolsApplicationRequests::Bus::BroadcastResult(
m_undoBatch, &ToolsApplicationRequests::Bus::Events::BeginUndoBatch, "ManipulatorRightMouseDown");
for (const AZ::EntityComponentIdPair& entityComponentId : m_entityComponentIdPairs)
{
ToolsApplicationRequests::Bus::Broadcast(
&ToolsApplicationRequests::Bus::Events::AddDirtyEntity, entityComponentId.GetEntityId());
}
(*this.*m_onRightMouseDownImpl)(interaction, rayIntersectionDistance);
ToolsApplicationNotificationBus::Broadcast(
&ToolsApplicationNotificationBus::Events::InvalidatePropertyDisplay, Refresh_Values);
return true;
}
return false;
}
// note: OnLeft/RightMouseUp will not be called if OnLeft/RightMouseDownImpl have not been
// attached as no active manipulator will have been set in ManipulatorManager.
void BaseManipulator::OnLeftMouseUp(const ViewportInteraction::MouseInteraction& interaction)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
SetBoundsDirty();
EndAction();
OnLeftMouseUpImpl(interaction);
EndUndoBatch();
}
void BaseManipulator::OnRightMouseUp(const ViewportInteraction::MouseInteraction& interaction)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
SetBoundsDirty();
EndAction();
OnRightMouseUpImpl(interaction);
EndUndoBatch();
}
bool BaseManipulator::OnMouseOver(
const ManipulatorId manipulatorId, const ViewportInteraction::MouseInteraction& interaction)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
UpdateMouseOver(manipulatorId);
OnMouseOverImpl(manipulatorId, interaction);
return m_mouseOver;
}
void BaseManipulator::OnMouseWheel(const ViewportInteraction::MouseInteraction& interaction)
{
OnMouseWheelImpl(interaction);
ToolsApplicationNotificationBus::Broadcast(
&ToolsApplicationNotificationBus::Events::InvalidatePropertyDisplay, Refresh_Values);
}
void BaseManipulator::OnMouseMove(const ViewportInteraction::MouseInteraction& interaction)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
if (!m_performingAction)
{
AZ_Warning(
"Manipulators", false,
"MouseMove action received, but this manipulator is not performing an action");
return;
}
// ensure property grid (entity inspector) values are refreshed
ToolsApplicationNotificationBus::Broadcast(
&ToolsApplicationNotificationBus::Events::InvalidatePropertyDisplay, Refresh_Values);
OnMouseMoveImpl(interaction);
}
void BaseManipulator::SetBoundsDirty()
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
SetBoundsDirtyImpl();
}
void BaseManipulator::Register(const ManipulatorManagerId managerId)
{
if (Registered())
{
Unregister();
}
ManipulatorManagerRequestBus::Event(managerId,
&ManipulatorManagerRequestBus::Events::RegisterManipulator, shared_from_this());
}
void BaseManipulator::Unregister()
{
// if the manipulator has already been unregistered, the m_manipulatorManagerId
// should be invalid which makes the call below a no-op.
ManipulatorManagerRequestBus::Event(m_manipulatorManagerId,
&ManipulatorManagerRequestBus::Events::UnregisterManipulator, this);
}
void BaseManipulator::Invalidate()
{
SetBoundsDirty();
InvalidateImpl();
m_mouseOver = false;
m_performingAction = false;
m_manipulatorId = InvalidManipulatorId;
m_manipulatorManagerId = InvalidManipulatorManagerId;
}
void BaseManipulator::BeginAction()
{
if (m_performingAction)
{
AZ_Warning(
"Manipulators", false,
"MouseDown action received, but the manipulator (id: %d) is still performing an action",
GetManipulatorId());
return;
}
m_performingAction = true;
}
void BaseManipulator::EndAction()
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
if (!m_performingAction)
{
AZ_Warning(
"Manipulators", false,
"MouseUp action received, but this manipulator (id: %d) didn't receive MouseDown action before",
GetManipulatorId());
return;
}
m_performingAction = false;
// let other systems know that a manipulator has modified an entity component property
NotifyEntityComponentPropertyChanged();
}
void BaseManipulator::ForwardMouseOverEvent(const ViewportInteraction::MouseInteraction& interaction)
{
OnMouseOver(m_manipulatorId, interaction);
}
void BaseManipulator::UpdateMouseOver(const ManipulatorId manipulatorId)
{
if (!PerformingAction())
{
m_mouseOver = (m_manipulatorId == manipulatorId);
}
}
void BaseManipulator::EndUndoBatch()
{
if (m_undoBatch != nullptr)
{
ToolsApplicationRequests::Bus::Broadcast(&ToolsApplicationRequests::Bus::Events::EndUndoBatch);
m_undoBatch = nullptr;
}
}
void BaseManipulator::AddEntityComponentIdPair(const AZ::EntityComponentIdPair& entityIdComponentPair)
{
m_entityComponentIdPairs.insert(entityIdComponentPair);
}
void BaseManipulator::NotifyEntityComponentPropertyChanged()
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
for (const AZ::EntityComponentIdPair& entityComponentIdPair : m_entityComponentIdPairs)
{
// if we have a valid component, send a single message for that component id
if (entityComponentIdPair.GetComponentId() != AZ::InvalidComponentId)
{
PropertyEditorEntityChangeNotificationBus::Event(
entityComponentIdPair.GetEntityId(),
&PropertyEditorEntityChangeNotifications::OnEntityComponentPropertyChanged,
entityComponentIdPair.GetComponentId());
}
else
{
AZ_Warning("Manipulators", false,
"This Manipulator was only registered with an EntityId and not an EntityComponentIdPair. "
"Please use AddEntityComponentIdPair() instead of AddEntityId() when registering what this "
"Manipulator is changing.");
// if we do not have a valid component id, send the message to all components on the entity,
// this is inefficient but will guarantee the component that changed does get the message.
const AZ::Entity* entity = GetEntityById(entityComponentIdPair.GetEntityId());
for (const AZ::Component* component : entity->GetComponents())
{
PropertyEditorEntityChangeNotificationBus::Event(
entity->GetId(), &PropertyEditorEntityChangeNotifications::OnEntityComponentPropertyChanged,
component->GetId());
}
}
}
}
AZStd::unordered_set<AZ::EntityComponentIdPair>::iterator BaseManipulator::RemoveEntityId(const AZ::EntityId entityId)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
auto afterErased = m_entityComponentIdPairs.end();
bool allErased = false;
while (!allErased)
{
// look for a match (keep looking in case we have several entity ids with different component ids)
const auto entityComponentPairId =
m_entityComponentIdPairs.find_as(
entityId, AZStd::hash<AZ::EntityId>(),
&EntityIdAndEntityComponentIdComparison);
// update the afterErased variable so we can return an iterator
// to the correct position in the container.
if (entityComponentPairId != m_entityComponentIdPairs.end())
{
afterErased = m_entityComponentIdPairs.erase(entityComponentPairId);
}
else
{
allErased = true;
}
}
return afterErased;
}
AZStd::unordered_set<AZ::EntityComponentIdPair>::iterator BaseManipulator::RemoveEntityComponentIdPair(
const AZ::EntityComponentIdPair& entityComponentIdPair)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
const auto entityIdIt = m_entityComponentIdPairs.find(entityComponentIdPair);
if (entityIdIt != m_entityComponentIdPairs.end())
{
return m_entityComponentIdPairs.erase(entityIdIt);
}
return entityIdIt;
}
bool BaseManipulator::HasEntityId(const AZ::EntityId entityId) const
{
return m_entityComponentIdPairs.find_as(
entityId, AZStd::hash<AZ::EntityId>(),
&EntityIdAndEntityComponentIdComparison) != m_entityComponentIdPairs.end();
}
bool BaseManipulator::HasEntityComponentIdPair(const AZ::EntityComponentIdPair& entityComponentIdPair) const
{
return m_entityComponentIdPairs.find(entityComponentIdPair) != m_entityComponentIdPairs.end();
}
void Manipulators::Register(const ManipulatorManagerId manipulatorManagerId)
{
ProcessManipulators([manipulatorManagerId](BaseManipulator* manipulator)
{
manipulator->Register(manipulatorManagerId);
});
}
void Manipulators::Unregister()
{
ProcessManipulators([](BaseManipulator* manipulator)
{
if (manipulator->Registered())
{
manipulator->Unregister();
}
});
}
void Manipulators::SetBoundsDirty()
{
ProcessManipulators([](BaseManipulator* manipulator)
{
manipulator->SetBoundsDirty();
});
}
void Manipulators::AddEntityComponentIdPair(const AZ::EntityComponentIdPair& entityComponentIdPair)
{
ProcessManipulators([&entityComponentIdPair](BaseManipulator* manipulator)
{
manipulator->AddEntityComponentIdPair(entityComponentIdPair);
});
}
void Manipulators::RemoveEntityComponentIdPair(const AZ::EntityComponentIdPair& entityComponentIdPair)
{
ProcessManipulators([&entityComponentIdPair](BaseManipulator* manipulator)
{
manipulator->RemoveEntityComponentIdPair(entityComponentIdPair);
});
}
void Manipulators::RemoveEntityId(const AZ::EntityId entityId)
{
ProcessManipulators([entityId](BaseManipulator* manipulator)
{
manipulator->RemoveEntityId(entityId);
});
}
bool Manipulators::PerformingAction()
{
bool performingAction = false;
ProcessManipulators([&performingAction](BaseManipulator* manipulator)
{
if (manipulator->PerformingAction())
{
performingAction = true;
}
});
return performingAction;
}
bool Manipulators::Registered()
{
bool registered = false;
ProcessManipulators([&registered](BaseManipulator* manipulator)
{
if (manipulator->Registered())
{
registered = true;
}
});
return registered;
}
namespace Internal
{
bool CalculateRayPlaneIntersectingPoint(const AZ::Vector3& rayOrigin, const AZ::Vector3& rayDirection,
const AZ::Vector3& pointOnPlane, const AZ::Vector3& planeNormal, AZ::Vector3& resultIntersectingPoint)
{
float t = 0.0f;
if (AZ::Intersect::IntersectRayPlane(rayOrigin, rayDirection, pointOnPlane, planeNormal, t) > 0)
{
resultIntersectingPoint = rayOrigin + t * rayDirection;
return true;
}
return false;
}
AZ::Vector3 TryConstrainHitPositionToView(
const AZ::Vector3& currentLocalHitPosition, const AZ::Vector3& startLocalHitPosition,
const AZ::Transform& localFromWorld, const AzFramework::CameraState& cameraState)
{
if (currentLocalHitPosition.GetDistance(localFromWorld.TransformPoint(cameraState.m_position))
> cameraState.m_farClip)
{
return startLocalHitPosition;
}
return currentLocalHitPosition;
}
} // namespace Internal
} // namespace AzToolsFramework
@@ -0,0 +1,290 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Component/ComponentBus.h>
#include <AzCore/Component/EntityId.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Math/Color.h>
#include <AzCore/Math/Quaternion.h>
#include <AzCore/Math/Transform.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/std/containers/set.h>
#include <AzCore/std/smart_ptr/enable_shared_from_this.h>
#include <AzToolsFramework/Manipulators/ManipulatorBus.h>
namespace AzFramework
{
struct CameraState;
class DebugDisplayRequests;
}
namespace AzToolsFramework
{
AZ_CVAR_EXTERNED(bool, cl_manipulatorDrawDebug);
namespace UndoSystem
{
class URSequencePoint;
}
namespace ViewportInteraction
{
struct MouseInteraction;
}
struct ManipulatorManagerState;
/// The base class for manipulators, providing interfaces for users of manipulators to talk to.
class BaseManipulator
: public AZStd::enable_shared_from_this<BaseManipulator>
{
public:
AZ_CLASS_ALLOCATOR_DECL
AZ_RTTI(BaseManipulator, "{3D1CD58D-C589-464C-BC9A-480D59341AB4}")
BaseManipulator(const BaseManipulator&) = delete;
BaseManipulator& operator=(const BaseManipulator&) = delete;
virtual ~BaseManipulator();
using EntityComponentIds = AZStd::unordered_set<AZ::EntityComponentIdPair>;
/// Callback for the event when the mouse pointer is over this manipulator and the left mouse button is pressed.
/// @param interaction It contains various mouse states when the event happens, as well as a ray shooting from the viewing camera through the mouse pointer.
/// @param rayIntersectionDistance The parameter value in the ray's explicit equation that represents the intersecting point on the target manipulator in world space.
/// @return Return true if OnLeftMouseDownImpl was attached and will be used.
bool OnLeftMouseDown(const ViewportInteraction::MouseInteraction& interaction, float rayIntersectionDistance);
/// Callback for the event when this manipulator is active and the left mouse button is released.
/// @param interaction It contains various mouse states when the event happens, as well as a ray shooting from the viewing camera through the mouse pointer.
void OnLeftMouseUp(const ViewportInteraction::MouseInteraction& interaction);
/// Callback for the event when the mouse pointer is over this manipulator and the right mouse button is pressed .
/// @param interaction It contains various mouse states when the event happens, as well as a ray shooting from the viewing camera through the mouse pointer.
/// @param rayIntersectionDistance The parameter value in the ray's explicit equation that represents the intersecting point on the target manipulator in world space.
/// @return Return true if OnRightMouseDownImpl was attached and will be used.
bool OnRightMouseDown(const ViewportInteraction::MouseInteraction& interaction, float rayIntersectionDistance);
/// Callback for the event when this manipulator is active and the right mouse button is released.
/// @param interaction It contains various mouse states when the event happens, as well as a ray shooting from the viewing camera through the mouse pointer.
void OnRightMouseUp(const ViewportInteraction::MouseInteraction& interaction);
/// Callback for the event when this manipulator is active and the mouse is moved.
/// @param interaction It contains various mouse states when the event happens, as well as a ray shooting from the viewing camera through the mouse pointer.
void OnMouseMove(const ViewportInteraction::MouseInteraction& interaction);
/// Callback for the event when this manipulator is active and the mouse wheel is scrolled.
/// @param interaction It contains various mouse states when the event happens, as well as a ray shooting from the viewing camera through the mouse pointer.
void OnMouseWheel(const ViewportInteraction::MouseInteraction& interaction);
/// This function changes the state indicating whether the manipulator is under the mouse pointer.
/// It is called in the event of OnMouseMove and OnMouseWheel only when there is no manipulator currently performing actions.
bool OnMouseOver(ManipulatorId manipulatorId, const ViewportInteraction::MouseInteraction& interaction);
/// Register itself to a manipulator manager so that it can receive various mouse events and perform manipulations.
/// @param managerId The id identifying a unique manipulator manager.
void Register(ManipulatorManagerId managerId);
/// Unregister itself from the manipulator manager it was registered with.
void Unregister();
/// Bounds will need to be recalculated next time we render.
void SetBoundsDirty();
/// Is this manipulator currently registered with a manipulator manager.
bool Registered() const
{
return m_manipulatorId != InvalidManipulatorId &&
m_manipulatorManagerId != InvalidManipulatorManagerId;
}
/// Is the manipulator in the middle of an action (between mouse down and mouse up).
bool PerformingAction() const { return m_performingAction; }
/// Is the mouse currently over the manipulator (intersecting manipulator bound).
bool MouseOver() const { return m_mouseOver; }
/// The unique id of this manipulator.
ManipulatorId GetManipulatorId() const { return m_manipulatorId; }
/// The unique id of the manager this manipulator was registered with.
ManipulatorManagerId GetManipulatorManagerId() const { return m_manipulatorManagerId; }
/// Returns all EntityComponentIdPairs associated with this manipulator.
const EntityComponentIds& EntityComponentIdPairs() const
{
return m_entityComponentIdPairs;
}
/// Add an entity and component the manipulator is responsible for.
void AddEntityComponentIdPair(const AZ::EntityComponentIdPair& entityComponentIdPair);
/// Remove an entity from being affected by this manipulator.
/// @note All components on this entity registered with the manipulator will be removed.
EntityComponentIds::iterator RemoveEntityId(AZ::EntityId entityId);
/// Remove a specific component (via a EntityComponentIdPair) being affected by this manipulator.
EntityComponentIds::iterator RemoveEntityComponentIdPair(const AZ::EntityComponentIdPair& entityComponentIdPair);
/// Is this entity currently being tracked by this manipulator.
bool HasEntityId(AZ::EntityId entityId) const;
/// Is this entity component pair currently being tracked by this manipulator.
bool HasEntityComponentIdPair(const AZ::EntityComponentIdPair& entityComponentIdPair) const;
/// Forward a mouse over event in a case where we need the manipulator to immediately refresh.
/// @note Only call this when a mouse over event has just happened.
void ForwardMouseOverEvent(const ViewportInteraction::MouseInteraction& interaction);
static const AZ::Color s_defaultMouseOverColor;
protected:
/// Protected constructor.
BaseManipulator() = default;
/// Called when unregistering - users of manipulators should not call it directly.
void Invalidate();
/// The implementation to override in a derived class for Invalidate.
virtual void InvalidateImpl() {}
/// The implementation to override in a derived class for OnLeftMouseDown.
/// Note: When implementing this function you must also call AttachLeftMouseDownImpl to ensure
/// m_onLeftMouseDownImpl is set to OnLeftMouseDownImpl, otherwise it will not be called
virtual void OnLeftMouseDownImpl(
const ViewportInteraction::MouseInteraction& /*interaction*/, float /*rayIntersectionDistance*/) {}
void AttachLeftMouseDownImpl() { m_onLeftMouseDownImpl = &BaseManipulator::OnLeftMouseDownImpl; }
/// The implementation to override in a derived class for OnRightMouseDown.
/// Note: When implementing this function you must also call AttachRightMouseDownImpl to ensure
/// m_onRightMouseDownImpl is set to OnRightMouseDownImpl, otherwise it will not be called
virtual void OnRightMouseDownImpl(
const ViewportInteraction::MouseInteraction& /*interaction*/, float /*rayIntersectionDistance*/) {}
void AttachRightMouseDownImpl() { m_onRightMouseDownImpl = &BaseManipulator::OnRightMouseDownImpl; }
/// The implementation to override in a derived class for OnLeftMouseUp.
virtual void OnLeftMouseUpImpl(const ViewportInteraction::MouseInteraction& /*interaction*/) {}
/// The implementation to override in a derived class for OnRightMouseUp.
virtual void OnRightMouseUpImpl(const ViewportInteraction::MouseInteraction& /*interaction*/) {}
/// The implementation to override in a derived class for OnMouseMove.
virtual void OnMouseMoveImpl(const ViewportInteraction::MouseInteraction& /*interaction*/) {}
/// The implementation to override in a derived class for OnMouseOver.
virtual void OnMouseOverImpl(
ManipulatorId /*manipulatorId*/, const ViewportInteraction::MouseInteraction& /*interaction*/) {}
/// The implementation to override in a derived class for OnMouseWheel.
virtual void OnMouseWheelImpl(const ViewportInteraction::MouseInteraction& /*interaction*/) {}
/// The implementation to override in a derived class for SetBoundsDirty.
virtual void SetBoundsDirtyImpl() {}
/// Rendering for the manipulator - it is recommended drawing be delegated to a ManipulatorView.
virtual void Draw(
const ManipulatorManagerState& managerState,
AzFramework::DebugDisplayRequests& debugDisplay,
const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction) = 0;
private:
friend class ManipulatorManager;
AZStd::unordered_set<AZ::EntityComponentIdPair> m_entityComponentIdPairs; ///< The entities this manipulator is associated with.
ManipulatorId m_manipulatorId = InvalidManipulatorId; ///< The unique id of this manipulator.
ManipulatorManagerId m_manipulatorManagerId = InvalidManipulatorManagerId; ///< The manager this manipulator was registered with.
UndoSystem::URSequencePoint* m_undoBatch = nullptr; ///< Undo active while mouse is pressed.
bool m_performingAction = false; ///< After mouse down and before mouse up.
bool m_mouseOver = false; ///< Is the mouse pointer over the manipulator bound.
/// Member function pointers to OnLeftMouseDownImpl and OnRightMouseDownImpl.
/// Set in AttachLeft/RightMouseDownImpl.
void (BaseManipulator::*m_onLeftMouseDownImpl)(
const ViewportInteraction::MouseInteraction& interaction, float rayIntersectionDistance) = nullptr;
void (BaseManipulator::*m_onRightMouseDownImpl)(
const ViewportInteraction::MouseInteraction& interaction, float rayIntersectionDistance) = nullptr;
/// Update the mouseOver state for this manipulator.
void UpdateMouseOver(ManipulatorId manipulatorId);
/// Manage correctly ending the undo batch.
void EndUndoBatch();
/// Record an action as having started.
void BeginAction();
/// Record an action as having stopped.
void EndAction();
/// Let other systems (UI) know that a component property has been modified by a manipulator.
void NotifyEntityComponentPropertyChanged();
};
/// Base class to be used when composing aggregate manipulator types - wraps some
/// common functionality all manipulators need.
class Manipulators
{
public:
virtual ~Manipulators() = default;
void Register(ManipulatorManagerId manipulatorManagerId);
void Unregister();
void SetBoundsDirty();
void AddEntityComponentIdPair(const AZ::EntityComponentIdPair& entityComponentIdPair);
void RemoveEntityId(AZ::EntityId entityId);
void RemoveEntityComponentIdPair(const AZ::EntityComponentIdPair& entityComponentIdPair);
bool PerformingAction();
bool Registered();
const AZ::Transform& GetLocalTransform() const { return m_localTransform; }
const AZ::Transform& GetSpace() const { return m_space; }
virtual void SetSpace(const AZ::Transform& worldFromLocal) = 0;
virtual void SetLocalTransform(const AZ::Transform& localTransform) = 0;
virtual void SetLocalPosition(const AZ::Vector3& localPosition) = 0;
virtual void SetLocalOrientation(const AZ::Quaternion& localOrientation) = 0;
/// Refresh the Manipulator and/or View based on the current view position.
virtual void RefreshView(const AZ::Vector3& /*worldViewPosition*/) {}
protected:
/// Common processing for base manipulator type - Implement for all
/// individual manipulators used in an aggregate manipulator.
virtual void ProcessManipulators(const AZStd::function<void(BaseManipulator*)>&) = 0;
AZ::Transform m_localTransform = AZ::Transform::CreateIdentity(); ///< Local space transform of Manipulators.
AZ::Transform m_space = AZ::Transform::CreateIdentity(); ///< Space the Manipulators are in.
};
namespace Internal
{
/// This helper function calculates the intersecting point between a ray and a plane.
/// @param rayOrigin The origin of the ray to test.
/// @param rayDirection The direction of the ray to test.
/// @param maxRayLength
/// @param pointOnPlane A point on the plane.
/// @param planeNormal The normal vector of the plane.
/// @param[out] resultIntersectingPoint This stores the result intersecting point. It will be left unchanged
/// if there is no intersection between the ray and the plane.
/// @return Was there an intersection
bool CalculateRayPlaneIntersectingPoint(const AZ::Vector3& rayOrigin, const AZ::Vector3& rayDirection,
const AZ::Vector3& pointOnPlane, const AZ::Vector3& planeNormal, AZ::Vector3& resultIntersectingPoint);
/// Returns startLocalHitPosition if currentLocalHitPosition is further away than the camera's far clip plane.
AZ::Vector3 TryConstrainHitPositionToView(
const AZ::Vector3& currentLocalHitPosition, const AZ::Vector3& startLocalHitPosition,
const AZ::Transform& localFromWorld, const AzFramework::CameraState& cameraState);
}
} // namespace AzToolsFramework
@@ -0,0 +1,51 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Component/ComponentBus.h>
namespace AZ
{
class Vector3;
}
namespace AzToolsFramework
{
/// Interface for handling box manipulator requests.
/// Used by \ref BoxComponentMode.
class BoxManipulatorRequests
: public AZ::EntityComponentBus
{
public:
/// Get the X/Y/Z dimensions of the box shape/collider.
virtual AZ::Vector3 GetDimensions() = 0;
/// Set the X/Y/Z dimensions of the box shape/collider.
virtual void SetDimensions(const AZ::Vector3& dimensions) = 0;
/// Get the transform of the box shape/collider.
/// This is used by \ref BoxComponentMode instead of the \ref \AZ::TransformBus
/// because a collider may have an additional translation/orientation offset from
/// the Entity transform.
virtual AZ::Transform GetCurrentTransform() = 0;
/// Get the scale currently applied to the box.
/// With the Box Shape, the largest x/y/z component is taken
/// so scale is always uniform, with colliders the scale may
/// be different per component.
virtual AZ::Vector3 GetBoxScale() = 0;
protected:
~BoxManipulatorRequests() = default;
};
/// Type to inherit to implement BoxManipulatorRequests
using BoxManipulatorRequestBus = AZ::EBus<BoxManipulatorRequests>;
} // namespace AzToolsFramework
@@ -0,0 +1,301 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Math/VertexContainer.h>
#include <AzCore/Math/VertexContainerInterface.h>
#include <AzFramework/Entity/EntityDebugDisplayBus.h>
#include <AzToolsFramework/Manipulators/ManipulatorBus.h>
#include <AzToolsFramework/Manipulators/HoverSelection.h>
#include <AzToolsFramework/Manipulators/SelectionManipulator.h>
#include <AzToolsFramework/Manipulators/TranslationManipulators.h>
#include <AzToolsFramework/Viewport/ActionBus.h>
#include <AzToolsFramework/ViewportSelection/EditorBoxSelect.h>
namespace AzToolsFramework
{
/// Concrete implementation of AZ::VariableVertices backed by an AZ::VertexContainer.
template<typename Vertex>
class VariableVerticesVertexContainer
: public AZ::VariableVertices<Vertex>
{
public:
explicit VariableVerticesVertexContainer(AZ::VertexContainer<Vertex>& vertexContainer)
: m_vertexContainer(vertexContainer) {}
bool GetVertex(size_t index, Vertex& vertex) const override { return m_vertexContainer.GetVertex(index, vertex); }
bool UpdateVertex(size_t index, const Vertex& vertex) override { return m_vertexContainer.UpdateVertex(index, vertex); };
void AddVertex(const Vertex& vertex) override { m_vertexContainer.AddVertex(vertex); }
bool InsertVertex(size_t index, const Vertex& vertex) override { return m_vertexContainer.InsertVertex(index, vertex); }
bool RemoveVertex(size_t index) override { return m_vertexContainer.RemoveVertex(index); }
void SetVertices(const AZStd::vector<Vertex>& vertices) override { m_vertexContainer.SetVertices(vertices); };
void ClearVertices() override { m_vertexContainer.Clear(); }
size_t Size() const override { return m_vertexContainer.Size(); }
bool Empty() const override { return m_vertexContainer.Empty(); }
private:
AZ::VertexContainer<Vertex>& m_vertexContainer;
};
/// Concrete implementation of AZ::FixedVertices backed by an AZStd::array.
template<typename Vertex, size_t Count>
class FixedVerticesArray
: public AZ::FixedVertices<Vertex>
{
public:
explicit FixedVerticesArray(AZStd::array<Vertex, Count>& array)
: m_array(array) {}
bool GetVertex(size_t index, Vertex& vertex) const override
{
if (index < m_array.size())
{
vertex = m_array[index];
return true;
}
return false;
}
bool UpdateVertex(size_t index, const Vertex& vertex) override
{
if (index < m_array.size())
{
m_array[index] = vertex;
return true;;
}
return false;
}
size_t Size() const override { return m_array.size(); }
private:
AZStd::array<Vertex, Count>& m_array;
};
/// EditorVertexSelection provides an interface for a collection of manipulators to expose
/// editing of vertices in a container/collection. EditorVertexSelection is templated on the
/// type of Vertex (Vector2/Vector3) stored in the container.
/// EditorVertexSelectionBase provides common behavior shared across Fixed and Variable selections.
template<typename Vertex>
class EditorVertexSelectionBase
: private AzFramework::EntityDebugDisplayEventBus::Handler
, private AzFramework::ViewportDebugDisplayEventBus::Handler
{
public:
EditorVertexSelectionBase();
EditorVertexSelectionBase(EditorVertexSelectionBase&&) = default;
EditorVertexSelectionBase& operator=(EditorVertexSelectionBase&&) = default;
virtual ~EditorVertexSelectionBase() = default;
/// Setup and configure the EditorVertexSelection for operation.
void Create(
const AZ::EntityComponentIdPair& entityComponentIdPair, ManipulatorManagerId managerId,
AZStd::unique_ptr<HoverSelection> hoverSelection,
TranslationManipulators::Dimensions dimensions,
TranslationManipulatorConfiguratorFn translationManipulatorConfigurator);
/// Create a translation manipulator for a given vertex.
void CreateTranslationManipulator(
const AZ::EntityComponentIdPair& entityComponentIdPair,
ManipulatorManagerId managerId, const Vertex& vertex, size_t index);
/// Destroy all manipulators associated with the vertex selection.
void Destroy();
/// Set custom callback for when vertex positions are updated.
void SetVertexPositionsUpdatedCallback(const AZStd::function<void()>& callback);
/// Update manipulators based on local changes to vertex positions.
void RefreshLocal();
/// Update the translation manipulator to be correctly positioned based
/// on the current selection (recenter it).
void RefreshTranslationManipulator();
/// Update manipulators based on changes to the entities transform.
void RefreshSpace(const AZ::Transform& worldFromLocal);
/// Set bounds dirty (need recalculating) for all owned manipulators (selection, translation, hover).
void SetBoundsDirty();
/// How should the EditorVertexSelection respond to mouse input.
virtual bool HandleMouse(
const ViewportInteraction::MouseInteractionEvent& mouseInteraction);
/// Snap the selected vertices to the terrain.
/// Note: With a multi-selection the manipulator will be translated to the picked
/// terrain position with all verts moved relative to it.
void SnapVerticesToTerrain(
const ViewportInteraction::MouseInteractionEvent& mouseInteraction);
/// The Actions provided by the EditorVertexSelection while it is active.
/// e.g. Vertex deletion, duplication etc.
AZStd::vector<ActionOverride> ActionOverrides() const;
/// Let the EditorVertexSelection know a batch movement is about to begin so it
/// can avoid certain unnecessary updates.
void BeginBatchMovement();
/// Let the EditorVertexSelection know a batch movement has ended so it can return
/// to its normal state.
void EndBatchMovement();
/// Set the position of the TranslationManipulators (if active).
void SetSelectedPosition(const AZ::Vector3& localPosition);
AZ::EntityId GetEntityId() const { return m_entityComponentIdPair.GetEntityId(); }
protected:
/// Internal interface for EditorVertexSelection.
virtual void SetupSelectionManipulator(
const AZStd::shared_ptr<SelectionManipulator>& selectionManipulator,
const AZ::EntityComponentIdPair& entityComponentIdPair,
ManipulatorManagerId managerId, size_t index) = 0;
virtual void PrepareActions() = 0;
/// Default behavior when clicking on a selection manipulator (representing a vertex).
void SelectionManipulatorSelectCallback(
size_t index, const ViewportInteraction::MouseInteraction& interaction,
const AZ::EntityComponentIdPair& entityComponentIdPair, ManipulatorManagerId managerId);
/// Destroy the translation manipulator and deselect all vertices.
void ClearSelected();
AZ::ComponentId GetComponentId() const { return m_entityComponentIdPair.GetComponentId(); }
const AZ::EntityComponentIdPair& GetEntityComponentIdPair() const { return m_entityComponentIdPair; }
ManipulatorManagerId GetManipulatorManagerId() const { return m_manipulatorManagerId; }
/// Is the translation vertex manipulator in 2D or 3D.
TranslationManipulators::Dimensions Dimensions() const { return m_dimensions; }
/// How to configure the translation manipulator (view and axes).
TranslationManipulatorConfiguratorFn ConfiguratorFn() const { return m_manipulatorConfiguratorFn; }
/// The state we are in when editing vertices.
enum class State
{
Selecting,
Translating,
};
void SetState(State state);
AZStd::unique_ptr<HoverSelection> m_hoverSelection = nullptr; ///< Interface to hover selection, representing bounds that can be selected.
AZStd::shared_ptr<IndexedTranslationManipulator<Vertex>> m_translationManipulator = nullptr; ///< Manipulator when vertex is selected to translate it.
AZStd::vector<AZStd::shared_ptr<SelectionManipulator>> m_selectionManipulators; ///< Manipulators for each vertex when entity is selected.
AZStd::array<AZStd::vector<ActionOverride>, 2> m_actionOverrides; ///< Available actions corresponding to each mode.
private:
// AzFramework::EntityDebugDisplayEventBus
void DisplayEntityViewport(
const AzFramework::ViewportInfo& viewportInfo,
AzFramework::DebugDisplayRequests& debugDisplay) override;
// AzFramework::ViewportDebugDisplayEventBus
void DisplayViewport2d(
const AzFramework::ViewportInfo& viewportInfo,
AzFramework::DebugDisplayRequests& debugDisplay) override;
/// Set selected manipulator and vertices position from offset from starting position when pressed.
void UpdateManipulatorsAndVerticesFromOffset(
IndexedTranslationManipulator<Vertex>& translationManipulator,
const AZ::Vector3& localManipulatorStartPosition,
const AZ::Vector3& localManipulatorOffset);
template<typename V, typename AZStd::enable_if<AZStd::is_same<V, AZ::Vector3>::value>::type* = nullptr>
void UpdateManipulatorSpace(const AzFramework::ViewportInfo& viewportInfo);
template<typename V, typename AZStd::enable_if<AZStd::is_same<V, AZ::Vector2>::value>::type* = nullptr>
void UpdateManipulatorSpace(const AzFramework::ViewportInfo& viewportInfo) const;
EditorBoxSelect m_editorBoxSelect; ///< Provide box select support for vertex selection.
AZ::EntityComponentIdPair m_entityComponentIdPair; ///< Id of the Entity and Component this editor vertex selection was created on.
ManipulatorManagerId m_manipulatorManagerId; ///< Id of the manager manipulators created from this type will be associated with.
TranslationManipulators::Dimensions m_dimensions = TranslationManipulators::Dimensions::Three; ///< The dimensions this vertex selection was created with.
TranslationManipulatorConfiguratorFn m_manipulatorConfiguratorFn = nullptr; ///< Function pointer set on Create to decide look and functionality of translation manipulator.
AZStd::function<void()> m_onVertexPositionsUpdated = nullptr; ///< Callback for when vertex positions are changed.
State m_state = State::Selecting; ///< Different states VertexSelection can be in.
bool m_worldSpace = false; ///< Are the manipulators being used in local or world space.
bool m_batchMovementInProgress = false; ///< If a batch movement operation is in progress we do not want to
///< refresh the VertexSelection during it for performance reasons.
};
/// EditorVertexSelectionFixed provides selection and editing for a fixed length number of
/// vertices. New vertices cannot be inserted/added or removed.
template<typename Vertex>
class EditorVertexSelectionFixed
: public EditorVertexSelectionBase<Vertex>
{
public:
AZ_CLASS_ALLOCATOR_DECL
EditorVertexSelectionFixed() = default;
EditorVertexSelectionFixed(EditorVertexSelectionFixed&&) = default;
EditorVertexSelectionFixed& operator=(EditorVertexSelectionFixed&&) = default;
private:
// EditorVertexSelectionBase
void SetupSelectionManipulator(
const AZStd::shared_ptr<SelectionManipulator>& selectionManipulator,
const AZ::EntityComponentIdPair& entityComponentIdPair,
ManipulatorManagerId managerId, size_t index) override;
void PrepareActions() override;
};
/// EditorVertexSelectionVariable provides selection and editing for a variable length number of
/// vertices. New vertices can be inserted/added or removed from the collection.
template<typename Vertex>
class EditorVertexSelectionVariable
: public EditorVertexSelectionBase<Vertex>
{
public:
AZ_CLASS_ALLOCATOR_DECL
EditorVertexSelectionVariable() = default;
EditorVertexSelectionVariable(EditorVertexSelectionVariable&&) = default;
EditorVertexSelectionVariable& operator=(EditorVertexSelectionVariable&&) = default;
void DuplicateSelected();
void DestroySelected();
protected:
// EditorVertexSelectionBase
void SetupSelectionManipulator(
const AZStd::shared_ptr<SelectionManipulator>& selectionManipulator,
const AZ::EntityComponentIdPair& entityComponentIdPair,
ManipulatorManagerId managerId, size_t vertIndex) override;
//! Presents a warning to the user that vertices will not be deleted.
//! @note Allow overriding by derived classes to make this a noop if required.
virtual void ShowVertexDeletionWarning();
private:
void PrepareActions() override;
/// @return The center point of the selected vertices.
Vertex InsertSelectedInPlace(
AZStd::vector<typename IndexedTranslationManipulator<Vertex>::VertexLookup>& manipulators);
};
/// Helper for inserting a vertex in a variable vertices container.
template<typename Vertex>
void InsertVertexAfter(
const AZ::EntityComponentIdPair& entityComponentIdPair, size_t vertIndex, const Vertex& localPosition);
/// Helper for removing a vertex in a variable vertices container.
/// Remove a vertex from the container and ensure the associated manipulator is unset and
/// property display values are refreshed.
template<typename Vertex>
void SafeRemoveVertex(
const AZ::EntityComponentIdPair& entityComponentIdPair, size_t vertexIndex);
} // namespace AzToolsFramework
@@ -0,0 +1,51 @@
/*
* 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.
*
*/
#pragma once
#include <AzToolsFramework/Manipulators/ManipulatorBus.h>
namespace AzToolsFramework
{
/// HoverSelection provides an interface for manipulator/s offering selection when
/// the mouse is hovered over a particular bound. This interface is used to represent
/// a Spline manipulator bound, and a series of LineSegment manipulator bounds.
/// This generic interface allows EditorVertexSelection to use either Spline or LineSegment selection.
class HoverSelection
{
public:
virtual ~HoverSelection() = default;
virtual void Register(ManipulatorManagerId managerId) = 0;
virtual void Unregister() = 0;
virtual void SetBoundsDirty() = 0;
virtual void Refresh() = 0;
virtual void SetSpace(const AZ::Transform& worldFromLocal) = 0;
};
/// NullHoverSelection is used when vertices cannot be inserted. This serves as a no-op
/// and is used to prevent the need for additional null checks in EditorVertexSelection.
class NullHoverSelection
: public HoverSelection
{
public:
NullHoverSelection() = default;
NullHoverSelection(const NullHoverSelection&) = delete;
NullHoverSelection& operator=(const NullHoverSelection&) = delete;
void Register(ManipulatorManagerId /*managerId*/) override {}
void Unregister() override {}
void SetBoundsDirty() override {}
void Refresh() override {}
void SetSpace(const AZ::Transform& /*worldFromLocal*/) override {}
};
} // namespace AzToolsFramework
@@ -0,0 +1,179 @@
/*
* 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 "LineHoverSelection.h"
#include <AzCore/Component/ComponentBus.h>
#include <AzCore/Math/Vector2.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Math/VertexContainerInterface.h>
#include <AzToolsFramework/Manipulators/EditorVertexSelection.h>
#include <AzToolsFramework/Manipulators/LineSegmentSelectionManipulator.h>
#include <AzToolsFramework/Manipulators/ManipulatorView.h>
namespace AzToolsFramework
{
static const AZ::Color s_lineSelectManipulatorColor = AZ::Color(0.0f, 1.0f, 0.0f, 1.0f);
template<typename Vertex>
static void UpdateLineSegmentPosition(
const size_t vertIndex, const AZ::EntityId entityId, LineSegmentSelectionManipulator& lineSegment)
{
Vertex start;
bool foundStart = false;
AZ::FixedVerticesRequestBus<Vertex>::EventResult(
foundStart, entityId, &AZ::FixedVerticesRequestBus<Vertex>::Handler::GetVertex,
vertIndex, start);
if (foundStart)
{
lineSegment.SetStart(AdaptVertexOut(start));
}
size_t size = 0;
AZ::FixedVerticesRequestBus<Vertex>::EventResult(
size, entityId, &AZ::FixedVerticesRequestBus<Vertex>::Handler::Size);
Vertex end;
bool foundEnd = false;
AZ::FixedVerticesRequestBus<Vertex>::EventResult(
foundEnd, entityId, &AZ::FixedVerticesRequestBus<Vertex>::Handler::GetVertex,
(vertIndex + 1) % size, end);
if (foundEnd)
{
lineSegment.SetEnd(AdaptVertexOut(end));
}
// update the view
const float lineWidth = 0.05f;
lineSegment.SetView(
CreateManipulatorViewLineSelect(lineSegment, s_lineSelectManipulatorColor, lineWidth));
}
template<typename Vertex>
LineSegmentHoverSelection<Vertex>::LineSegmentHoverSelection(
const AZ::EntityComponentIdPair& entityComponentIdPair, const ManipulatorManagerId managerId)
: m_entityId(entityComponentIdPair.GetEntityId())
{
// create a line segment manipulator from vertex positions and setup its callback
auto setupLineSegment = [this] (
const AZ::EntityComponentIdPair& entityComponentIdPair,
const ManipulatorManagerId managerId, const size_t vertIndex)
{
m_lineSegmentManipulators.push_back(LineSegmentSelectionManipulator::MakeShared());
AZStd::shared_ptr<LineSegmentSelectionManipulator>& lineSegmentManipulator = m_lineSegmentManipulators.back();
lineSegmentManipulator->Register(managerId);
lineSegmentManipulator->AddEntityComponentIdPair(entityComponentIdPair);
lineSegmentManipulator->SetSpace(WorldFromLocalWithUniformScale(entityComponentIdPair.GetEntityId()));
UpdateLineSegmentPosition<Vertex>(vertIndex, entityComponentIdPair.GetEntityId(), *lineSegmentManipulator);
lineSegmentManipulator->InstallLeftMouseUpCallback(
[vertIndex, entityComponentIdPair](const LineSegmentSelectionManipulator::Action& action)
{
InsertVertexAfter<Vertex>(
entityComponentIdPair, vertIndex,
AZ::AdaptVertexIn<Vertex>(action.m_localLineHitPosition));
});
};
// create all line segment manipulators for the polygon prism (used for selection bounds)
size_t vertexCount = 0;
AZ::FixedVerticesRequestBus<Vertex>::EventResult(
vertexCount, entityComponentIdPair.GetEntityId(), &AZ::FixedVerticesRequestBus<Vertex>::Handler::Size);
if (vertexCount > 1)
{
// special case when there are only two vertices
if (vertexCount == 2)
{
m_lineSegmentManipulators.reserve(vertexCount - 1);
setupLineSegment(entityComponentIdPair, managerId, 0);
}
else
{
m_lineSegmentManipulators.reserve(vertexCount);
for (size_t vertIndex = 0; vertIndex < vertexCount; ++vertIndex)
{
setupLineSegment(entityComponentIdPair, managerId, vertIndex);
}
}
}
}
template<typename Vertex>
LineSegmentHoverSelection<Vertex>::~LineSegmentHoverSelection()
{
LineSegmentHoverSelection::Unregister();
m_lineSegmentManipulators.clear();
}
template<typename Vertex>
void LineSegmentHoverSelection<Vertex>::Register(const ManipulatorManagerId managerId)
{
for (auto& manipulator : m_lineSegmentManipulators)
{
manipulator->Register(managerId);
}
}
template<typename Vertex>
void LineSegmentHoverSelection<Vertex>::Unregister()
{
for (auto& manipulator : m_lineSegmentManipulators)
{
manipulator->Unregister();
}
}
template<typename Vertex>
void LineSegmentHoverSelection<Vertex>::SetBoundsDirty()
{
for (auto& manipulator : m_lineSegmentManipulators)
{
manipulator->SetBoundsDirty();
}
}
template<typename Vertex>
void LineSegmentHoverSelection<Vertex>::Refresh()
{
size_t vertexCount = 0;
AZ::FixedVerticesRequestBus<Vertex>::EventResult(
vertexCount, m_entityId, &AZ::FixedVerticesRequestBus<Vertex>::Handler::Size);
// update the start/end positions of all the line segment manipulators to ensure
// they stay consistent with the polygon prism shape
if (vertexCount > 1)
{
for (size_t manipulatorIndex = 0; manipulatorIndex < m_lineSegmentManipulators.size(); ++manipulatorIndex)
{
LineSegmentSelectionManipulator& lineSegmentManipulator = *m_lineSegmentManipulators[manipulatorIndex];
UpdateLineSegmentPosition<Vertex>(manipulatorIndex, m_entityId, lineSegmentManipulator);
lineSegmentManipulator.SetBoundsDirty();
}
}
}
template<typename Vertex>
void LineSegmentHoverSelection<Vertex>::SetSpace(const AZ::Transform& worldFromLocal)
{
for (auto& lineSegmentManipulator : m_lineSegmentManipulators)
{
lineSegmentManipulator->SetSpace(worldFromLocal);
}
}
template class LineSegmentHoverSelection<AZ::Vector2>;
template class LineSegmentHoverSelection<AZ::Vector3>;
} // namespace AzToolsFramework
@@ -0,0 +1,53 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Component/EntityId.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzToolsFramework/Manipulators/HoverSelection.h>
namespace AZ
{
class EntityComponentIdPair;
}
namespace AzToolsFramework
{
class LineSegmentSelectionManipulator;
/// LineSegmentHoverSelection is a concrete implementation of HoverSelection wrapping a collection/container
/// of vertices and a list of LineSegmentManipulators. The underlying manipulators are used to control selection
/// by highlighting where on the line a new vertex will be inserted.
template<typename Vertex>
class LineSegmentHoverSelection
: public HoverSelection
{
public:
explicit LineSegmentHoverSelection(
const AZ::EntityComponentIdPair& entityComponentIdPair,
ManipulatorManagerId managerId);
LineSegmentHoverSelection(const LineSegmentHoverSelection&) = delete;
LineSegmentHoverSelection& operator=(const LineSegmentHoverSelection&) = delete;
~LineSegmentHoverSelection();
void Register(ManipulatorManagerId managerId) override;
void Unregister() override;
void SetBoundsDirty() override;
void Refresh() override;
void SetSpace(const AZ::Transform& worldFromLocal) override;
private:
AZ::EntityId m_entityId;
AZStd::vector<AZStd::shared_ptr<LineSegmentSelectionManipulator>> m_lineSegmentManipulators; ///< Manipulators for each line.
};
} // namespace AzToolsFramework
@@ -0,0 +1,138 @@
/*
* 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 "LineSegmentSelectionManipulator.h"
#include <AzCore/Math/IntersectSegment.h>
#include <AzFramework/Entity/EntityDebugDisplayBus.h>
#include <AzToolsFramework/Manipulators/ManipulatorView.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
namespace AzToolsFramework
{
LineSegmentSelectionManipulator::Action CalculateManipulationDataAction(
const AZ::Transform& worldFromLocal, const AZ::Vector3& rayOrigin, const AZ::Vector3& rayDirection,
const float rayLength, const AZ::Vector3& localStart, const AZ::Vector3& localEnd)
{
AZ::Vector3 worldClosestPositionRay, worldClosestPositionLineSegment;
float rayProportion, lineSegmentProportion;
AZ::Intersect::ClosestSegmentSegment(
rayOrigin, rayOrigin + rayDirection * rayLength,
worldFromLocal.TransformPoint(localStart), worldFromLocal.TransformPoint(localEnd),
rayProportion, lineSegmentProportion, worldClosestPositionRay, worldClosestPositionLineSegment);
AZ::Transform worldFromLocalNormalized = worldFromLocal;
const AZ::Vector3 scale = worldFromLocalNormalized.ExtractScale();
const AZ::Transform localFromWorldNormalized = worldFromLocalNormalized.GetInverse();
return { (localFromWorldNormalized.TransformPoint(worldClosestPositionLineSegment)) / scale };
}
AZStd::shared_ptr<LineSegmentSelectionManipulator> LineSegmentSelectionManipulator::MakeShared()
{
return AZStd::shared_ptr<LineSegmentSelectionManipulator>(aznew LineSegmentSelectionManipulator());
}
LineSegmentSelectionManipulator::LineSegmentSelectionManipulator()
{
AttachLeftMouseDownImpl();
}
LineSegmentSelectionManipulator::~LineSegmentSelectionManipulator() {}
void LineSegmentSelectionManipulator::InstallLeftMouseDownCallback(const MouseActionCallback& onMouseDownCallback)
{
m_onLeftMouseDownCallback = onMouseDownCallback;
}
void LineSegmentSelectionManipulator::InstallLeftMouseUpCallback(const MouseActionCallback& onMouseUpCallback)
{
m_onLeftMouseUpCallback = onMouseUpCallback;
}
void LineSegmentSelectionManipulator::OnLeftMouseDownImpl(
const ViewportInteraction::MouseInteraction& interaction, float /*rayIntersectionDistance*/)
{
if (!interaction.m_keyboardModifiers.Ctrl())
{
return;
}
if (m_onLeftMouseDownCallback)
{
AzFramework::CameraState cameraState;
ViewportInteraction::ViewportInteractionRequestBus::EventResult(
cameraState, interaction.m_interactionId.m_viewportId,
&ViewportInteraction::ViewportInteractionRequestBus::Events::GetCameraState);
m_onLeftMouseDownCallback(CalculateManipulationDataAction(
TransformUniformScale(m_worldFromLocal), interaction.m_mousePick.m_rayOrigin,
interaction.m_mousePick.m_rayDirection, cameraState.m_farClip, m_localStart, m_localEnd));
}
}
void LineSegmentSelectionManipulator::OnLeftMouseUpImpl(const ViewportInteraction::MouseInteraction& interaction)
{
if (MouseOver() && m_onLeftMouseUpCallback)
{
AzFramework::CameraState cameraState;
ViewportInteraction::ViewportInteractionRequestBus::EventResult(
cameraState, interaction.m_interactionId.m_viewportId,
&ViewportInteraction::ViewportInteractionRequestBus::Events::GetCameraState);
m_onLeftMouseUpCallback(CalculateManipulationDataAction(
TransformUniformScale(m_worldFromLocal), interaction.m_mousePick.m_rayOrigin, interaction.m_mousePick.m_rayDirection,
cameraState.m_farClip, m_localStart, m_localEnd));
}
}
void LineSegmentSelectionManipulator::Draw(
const ManipulatorManagerState& managerState,
AzFramework::DebugDisplayRequests& debugDisplay,
const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction)
{
// if the ctrl modifier key state has changed - set out bounds to dirty and
// update the active state.
if (m_keyboardModifiers != mouseInteraction.m_keyboardModifiers)
{
SetBoundsDirty();
m_keyboardModifiers = mouseInteraction.m_keyboardModifiers;
}
if (mouseInteraction.m_keyboardModifiers.Ctrl() && !mouseInteraction.m_keyboardModifiers.Shift())
{
m_manipulatorView->Draw(
GetManipulatorManagerId(), managerState,
GetManipulatorId(), {
TransformUniformScale(m_worldFromLocal),
m_localStart, MouseOver()
},
debugDisplay, cameraState, mouseInteraction);
}
}
void LineSegmentSelectionManipulator::SetView(AZStd::unique_ptr<ManipulatorView>&& view)
{
m_manipulatorView = AZStd::move(view);
}
void LineSegmentSelectionManipulator::SetBoundsDirtyImpl()
{
m_manipulatorView->SetBoundDirty(GetManipulatorManagerId());
}
void LineSegmentSelectionManipulator::InvalidateImpl()
{
m_manipulatorView->Invalidate(GetManipulatorManagerId());
}
}
@@ -0,0 +1,91 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Memory/SystemAllocator.h>
#include <AzToolsFramework/Manipulators/BaseManipulator.h>
#include <AzToolsFramework/Viewport/ViewportTypes.h>
namespace AzToolsFramework
{
class ManipulatorView;
/// A manipulator to expose where on a line a user is moving their mouse.
class LineSegmentSelectionManipulator
: public BaseManipulator
{
/// Private constructor.
LineSegmentSelectionManipulator();
public:
AZ_RTTI(LineSegmentSelectionManipulator, "{8BA5A9E4-72B4-4B48-BD54-D9DB58EDDA72}", BaseManipulator);
AZ_CLASS_ALLOCATOR(LineSegmentSelectionManipulator, AZ::SystemAllocator, 0);
LineSegmentSelectionManipulator(const LineSegmentSelectionManipulator&) = delete;
LineSegmentSelectionManipulator& operator=(const LineSegmentSelectionManipulator&) = delete;
~LineSegmentSelectionManipulator();
/// A Manipulator must only be created and managed through a shared_ptr.
static AZStd::shared_ptr<LineSegmentSelectionManipulator> MakeShared();
/// Mouse action data used by MouseActionCallback.
struct Action
{
AZ::Vector3 m_localLineHitPosition;
};
using MouseActionCallback = AZStd::function<void(const Action&)>;
void InstallLeftMouseDownCallback(const MouseActionCallback& onMouseDownCallback);
void InstallLeftMouseUpCallback(const MouseActionCallback& onMouseUpCallback);
void Draw(
const ManipulatorManagerState& managerState,
AzFramework::DebugDisplayRequests& debugDisplay,
const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction) override;
void SetSpace(const AZ::Transform& worldFromLocal) { m_worldFromLocal = worldFromLocal; }
void SetStart(const AZ::Vector3& startLocal) { m_localStart = startLocal; }
void SetEnd(const AZ::Vector3& endLocal) { m_localEnd = endLocal; }
const AZ::Vector3& GetStart() const { return m_localStart; }
const AZ::Vector3& GetEnd() const { return m_localEnd; }
void SetView(AZStd::unique_ptr<ManipulatorView>&& view);
private:
void OnLeftMouseDownImpl(
const ViewportInteraction::MouseInteraction& interaction, float rayIntersectionDistance) override;
void OnLeftMouseUpImpl(
const ViewportInteraction::MouseInteraction& interaction) override;
void InvalidateImpl() override;
void SetBoundsDirtyImpl() override;
AZ::Transform m_worldFromLocal = AZ::Transform::CreateIdentity(); ///< Space the manipulator is in (identity is world space).
AZ::Vector3 m_localStart = AZ::Vector3::CreateZero();
AZ::Vector3 m_localEnd = AZ::Vector3::CreateZero();
MouseActionCallback m_onLeftMouseDownCallback = nullptr;
MouseActionCallback m_onLeftMouseUpCallback = nullptr;
ViewportInteraction::KeyboardModifiers m_keyboardModifiers; ///< What modifier keys are pressed when interacting with this manipulator.
AZStd::unique_ptr<ManipulatorView> m_manipulatorView = nullptr; ///< Look of manipulator.
};
LineSegmentSelectionManipulator::Action CalculateManipulationDataAction(
const AZ::Transform& worldFromLocal, const AZ::Vector3& rayOrigin, const AZ::Vector3& rayDirection,
float rayLength, const AZ::Vector3& localStart, const AZ::Vector3& localEnd);
} // namespace AzToolsFramework
@@ -0,0 +1,300 @@
/*
* 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 "LinearManipulator.h"
#include <AzFramework/Entity/EntityDebugDisplayBus.h>
#include <AzToolsFramework/Manipulators/ManipulatorDebug.h>
#include <AzToolsFramework/Manipulators/ManipulatorSnapping.h>
#include <AzToolsFramework/Maths/TransformUtils.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
#include <AzToolsFramework/ViewportSelection/EditorSelectionUtil.h>
namespace AzToolsFramework
{
LinearManipulator::Starter CalculateLinearManipulationDataStart(
const LinearManipulator::Fixed& fixed, const AZ::Transform& worldFromLocal, const AZ::Transform& localTransform,
const GridSnapAction& gridSnapAction, const ViewportInteraction::MouseInteraction& interaction,
const float intersectionDistance, const AzFramework::CameraState& cameraState)
{
const ManipulatorInteraction manipulatorInteraction =
BuildManipulatorInteraction(
worldFromLocal, interaction.m_mousePick.m_rayOrigin, interaction.m_mousePick.m_rayDirection);
const AZ::Vector3 axis = TransformDirectionNoScaling(localTransform, fixed.m_axis);
const AZ::Vector3 rayCrossAxis = manipulatorInteraction.m_localRayDirection.Cross(axis);
LinearManipulator::Start start;
LinearManipulator::StartTransition startTransition;
// initialize m_localHitPosition to handle edge case where CalculateRayPlaneIntersectingPoint
// fails because ray is parallel to the plane
start.m_localHitPosition = localTransform.GetTranslation();
startTransition.m_localNormal = rayCrossAxis.Cross(axis).GetNormalizedSafe();
// initial intersect point
const AZ::Vector3 localIntersectionPoint =
manipulatorInteraction.m_localRayOrigin + manipulatorInteraction.m_localRayDirection * intersectionDistance;
Internal::CalculateRayPlaneIntersectingPoint(
manipulatorInteraction.m_localRayOrigin, manipulatorInteraction.m_localRayDirection,
localIntersectionPoint, startTransition.m_localNormal, start.m_localHitPosition);
const float gridSize = gridSnapAction.m_gridSnapParams.m_gridSize;
const bool snapping = gridSnapAction.m_gridSnapParams.m_gridSnap;
const float scaleRecip = manipulatorInteraction.m_scaleReciprocal;
// calculate position amount to snap, to align with grid
const AZ::Vector3 positionSnapOffset = snapping && !gridSnapAction.m_localSnapping
? CalculateSnappedOffset(localTransform.GetTranslation(), axis, gridSize * scaleRecip)
: AZ::Vector3::CreateZero();
const AZ::Vector3 localScale = localTransform.GetScale();
const AZ::Quaternion localRotation = QuaternionFromTransformNoScaling(localTransform);
// calculate scale amount to snap, to align to round scale value
const AZ::Vector3 scaleSnapOffset = snapping && !gridSnapAction.m_localSnapping
? localRotation.GetInverseFull().TransformVector(CalculateSnappedOffset(
localRotation.TransformVector(localScale), axis, gridSize * scaleRecip))
: AZ::Vector3::CreateZero();
start.m_screenPosition = interaction.m_mousePick.m_screenCoordinates;
start.m_positionSnapOffset = positionSnapOffset;
start.m_scaleSnapOffset = scaleSnapOffset;
start.m_localPosition = localTransform.GetTranslation() + positionSnapOffset;
start.m_localScale = localScale + scaleSnapOffset;
start.m_localAxis = axis;
// sign to determine which side of the linear axis we pressed
// (useful to know when the visual axis flips to face the camera)
start.m_sign =
AZ::GetSign((start.m_localHitPosition - localTransform.GetTranslation()).Dot(axis));
startTransition.m_screenToWorldScale =
1.0f / CalculateScreenToWorldMultiplier((worldFromLocal * localTransform).GetTranslation(), cameraState);
return {startTransition, start};
}
LinearManipulator::Action CalculateLinearManipulationDataAction(
const LinearManipulator::Fixed& fixed, const LinearManipulator::Starter& starter,
const AZ::Transform& worldFromLocal, const AZ::Transform& localTransform, const GridSnapAction& gridSnapAction,
const ViewportInteraction::MouseInteraction& interaction)
{
const ManipulatorInteraction manipulatorInteraction =
BuildManipulatorInteraction(
worldFromLocal, interaction.m_mousePick.m_rayOrigin, interaction.m_mousePick.m_rayDirection);
const auto& [startTransition, start] = starter;
// as CalculateRayPlaneIntersectingPoint may fail, ensure localHitPosition is initialized with
// the starting hit position so the manipulator returns to the original location it was pressed
// if an invalid ray intersection is attempted
AZ::Vector3 localHitPosition = start.m_localHitPosition;
Internal::CalculateRayPlaneIntersectingPoint(
manipulatorInteraction.m_localRayOrigin, manipulatorInteraction.m_localRayDirection,
start.m_localHitPosition, startTransition.m_localNormal, localHitPosition);
localHitPosition = Internal::TryConstrainHitPositionToView(
localHitPosition, start.m_localHitPosition, worldFromLocal.GetInverse(),
GetCameraState(interaction.m_interactionId.m_viewportId));
const AZ::Vector3 axis = TransformDirectionNoScaling(localTransform, fixed.m_axis);
const AZ::Vector3 hitDelta = (localHitPosition - start.m_localHitPosition);
const AZ::Vector3 unsnappedOffset = axis * axis.Dot(hitDelta);
const float scaleRecip = manipulatorInteraction.m_scaleReciprocal;
const float gridSize = gridSnapAction.m_gridSnapParams.m_gridSize;
const bool snapping = gridSnapAction.m_gridSnapParams.m_gridSnap;
LinearManipulator::Action action;
action.m_fixed = fixed;
action.m_start = start;
action.m_current.m_localPositionOffset = snapping
? unsnappedOffset + CalculateSnappedOffset(unsnappedOffset, axis, gridSize * scaleRecip)
: unsnappedOffset;
action.m_current.m_screenPosition = interaction.m_mousePick.m_screenCoordinates;
action.m_viewportId = interaction.m_interactionId.m_viewportId;
const AZ::Quaternion localRotation = QuaternionFromTransformNoScaling(localTransform);
const AZ::Vector3 scaledUnsnappedOffset = unsnappedOffset * startTransition.m_screenToWorldScale;
// how much to adjust the scale based on movement
const AZ::Quaternion invLocalRotation = localRotation.GetInverseFull();
action.m_current.m_localScaleOffset = snapping
? invLocalRotation.TransformVector((scaledUnsnappedOffset + CalculateSnappedOffset(scaledUnsnappedOffset, axis, gridSize * scaleRecip)))
: invLocalRotation.TransformVector(scaledUnsnappedOffset);
// record what modifier keys are held during this action
action.m_modifiers = interaction.m_keyboardModifiers;
return action;
}
AZStd::shared_ptr<LinearManipulator> LinearManipulator::MakeShared(const AZ::Transform& worldFromLocal)
{
return AZStd::shared_ptr<LinearManipulator>(aznew LinearManipulator(worldFromLocal));
}
LinearManipulator::LinearManipulator(const AZ::Transform& worldFromLocal)
: m_worldFromLocal(worldFromLocal)
{
AttachLeftMouseDownImpl();
}
void LinearManipulator::InstallLeftMouseDownCallback(const MouseActionCallback& onMouseDownCallback)
{
m_onLeftMouseDownCallback = onMouseDownCallback;
}
void LinearManipulator::InstallLeftMouseUpCallback(const MouseActionCallback& onMouseUpCallback)
{
m_onLeftMouseUpCallback = onMouseUpCallback;
}
void LinearManipulator::InstallMouseMoveCallback(const MouseActionCallback& onMouseMoveCallback)
{
m_onMouseMoveCallback = onMouseMoveCallback;
}
void LinearManipulator::OnLeftMouseDownImpl(
const ViewportInteraction::MouseInteraction& interaction, const float rayIntersectionDistance)
{
const AZ::Transform worldFromLocalUniformScale = TransformUniformScale(m_worldFromLocal);
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
// note: m_localTransform must not be made uniform as it may contain a local scale we want to snap
m_starter = CalculateLinearManipulationDataStart(
m_fixed, worldFromLocalUniformScale, m_localTransform,
GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt()), interaction, rayIntersectionDistance,
GetCameraState(interaction.m_interactionId.m_viewportId));
if (m_onLeftMouseDownCallback)
{
m_onLeftMouseDownCallback(CalculateLinearManipulationDataAction(
m_fixed, m_starter, worldFromLocalUniformScale, m_localTransform,
GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt()), interaction));
}
}
void LinearManipulator::OnMouseMoveImpl(const ViewportInteraction::MouseInteraction& interaction)
{
if (m_onMouseMoveCallback)
{
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
// note: m_localTransform must not be made uniform as it may contain a local scale we want to snap
m_onMouseMoveCallback(CalculateLinearManipulationDataAction(
m_fixed, m_starter, TransformUniformScale(m_worldFromLocal), m_localTransform,
GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt()), interaction));
}
}
void LinearManipulator::OnLeftMouseUpImpl(const ViewportInteraction::MouseInteraction& interaction)
{
if (m_onLeftMouseUpCallback)
{
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
// note: m_localTransform must not be made uniform as it may contain a local scale we want to snap
m_onLeftMouseUpCallback(CalculateLinearManipulationDataAction(
m_fixed, m_starter, TransformUniformScale(m_worldFromLocal), m_localTransform,
GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt()), interaction));
}
}
void LinearManipulator::Draw(
const ManipulatorManagerState& managerState,
AzFramework::DebugDisplayRequests& debugDisplay,
const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction)
{
const AZ::Transform localTransform = m_useVisualsOverride
? AZ::Transform::CreateFromQuaternionAndTranslation(
m_visualOrientationOverride, m_localTransform.GetTranslation())
: m_localTransform;
if (cl_manipulatorDrawDebug)
{
if (PerformingAction())
{
const GridSnapParameters gridSnapParams =
GridSnapSettings(mouseInteraction.m_interactionId.m_viewportId);
const auto action = CalculateLinearManipulationDataAction(
m_fixed, m_starter, TransformUniformScale(m_worldFromLocal), m_localTransform,
GridSnapAction(gridSnapParams, mouseInteraction.m_keyboardModifiers.Alt()), mouseInteraction);
// display the exact hit (ray intersection) of the mouse pick on the manipulator
DrawTransformAxes(
debugDisplay, TransformUniformScale(m_worldFromLocal) *
AZ::Transform::CreateTranslation(
action.m_start.m_localHitPosition + action.m_current.m_localPositionOffset));
}
const AZ::Transform combined = TransformUniformScale(m_worldFromLocal) * localTransform;
DrawTransformAxes(debugDisplay, combined);
DrawAxis(
debugDisplay, combined.GetTranslation(), TransformDirectionNoScaling(combined, m_fixed.m_axis));
}
for (auto& view : m_manipulatorViews)
{
view->Draw(
GetManipulatorManagerId(), managerState,
GetManipulatorId(), {
m_worldFromLocal * localTransform,
AZ::Vector3::CreateZero(), MouseOver()
},
debugDisplay, cameraState, mouseInteraction);
}
}
void LinearManipulator::SetAxis(const AZ::Vector3& axis)
{
m_fixed.m_axis = axis;
}
void LinearManipulator::SetSpace(const AZ::Transform& worldFromLocal)
{
m_worldFromLocal = worldFromLocal;
}
void LinearManipulator::SetLocalTransform(const AZ::Transform& localTransform)
{
m_localTransform = localTransform;
}
void LinearManipulator::SetLocalPosition(const AZ::Vector3& localPosition)
{
m_localTransform.SetTranslation(localPosition);
}
void LinearManipulator::SetLocalOrientation(const AZ::Quaternion& localOrientation)
{
m_localTransform = AZ::Transform::CreateFromQuaternionAndTranslation(
localOrientation, m_localTransform.GetTranslation());
}
void LinearManipulator::InvalidateImpl()
{
for (auto& view : m_manipulatorViews)
{
view->Invalidate(GetManipulatorManagerId());
}
}
void LinearManipulator::SetBoundsDirtyImpl()
{
for (auto& view : m_manipulatorViews)
{
view->SetBoundDirty(GetManipulatorManagerId());
}
}
} // namespace AzToolsFramework
@@ -0,0 +1,179 @@
/*
* 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.
*
*/
#pragma once
#include "BaseManipulator.h"
#include <AzCore/Memory/SystemAllocator.h>
#include <AzToolsFramework/Manipulators/ManipulatorView.h>
#include <AzToolsFramework/Viewport/ViewportTypes.h>
namespace AzToolsFramework
{
struct GridSnapAction;
/// LinearManipulator serves as a visual tool for users to modify values
/// in one dimension on an axis defined in 3D space.
class LinearManipulator
: public BaseManipulator
{
/// Private constructor.
explicit LinearManipulator(const AZ::Transform& worldFromLocal);
public:
AZ_RTTI(LinearManipulator, "{4AA805DA-7D3C-4AFA-8110-EECF32B8F530}", BaseManipulator)
AZ_CLASS_ALLOCATOR(LinearManipulator, AZ::SystemAllocator, 0)
LinearManipulator() = delete;
LinearManipulator(const LinearManipulator&) = delete;
LinearManipulator& operator=(const LinearManipulator&) = delete;
~LinearManipulator() = default;
/// A Manipulator must only be created and managed through a shared_ptr.
static AZStd::shared_ptr<LinearManipulator> MakeShared(const AZ::Transform& worldFromLocal);
/// Unchanging data set once for the linear manipulator.
struct Fixed
{
AZ::Vector3 m_axis = AZ::Vector3::CreateAxisX(); ///< The axis the manipulator will move along.
};
/// Data passed between the initial press and first movement of the linear manipulator.
struct StartTransition
{
/// The normal in local space of the manipulator when the mouse down event happens.
AZ::Vector3 m_localNormal;
/// Used to scale movement based on camera distance if we want screen space instead
/// of world space displacement.
float m_screenToWorldScale;
};
/// The state of the manipulator at the start of an interaction.
struct Start
{
AZ::Vector3 m_localPosition; ///< The current position of the manipulator in local space.
AZ::Vector3 m_localScale; ///< The current scale of the manipulator in local space.
AZ::Vector3 m_localHitPosition; ///< The intersection point in local space between the ray and the manipulator when the mouse down event happens.
AZ::Vector3 m_localAxis; ///< The axis in the local space of the manipulator itself.
AZ::Vector3 m_positionSnapOffset; ///< The snap offset amount to ensure manipulator is aligned to the grid.
AZ::Vector3 m_scaleSnapOffset; ///< The snap offset amount to ensure manipulator is aligned to round scale increments.
float m_sign; ///< Used to determine which side of the axis we clicked on in case it's flipped to face the camera.
AzFramework::ScreenPoint m_screenPosition; ///< The initial position in screen space of the manipulator.
};
/// The state of the manipulator during an interaction.
struct Current
{
AZ::Vector3 m_localPositionOffset; ///< The current offset of the manipulator from its starting position in local space.
AZ::Vector3 m_localScaleOffset; ///< The current offset of the manipulator from its starting scale in local space.
AzFramework::ScreenPoint m_screenPosition; ///< The current position in screen space of the manipulator.
};
/// Mouse action data used by MouseActionCallback (wraps Fixed, Start and Current manipulator state).
struct Action
{
Fixed m_fixed;
Start m_start;
Current m_current;
ViewportInteraction::KeyboardModifiers m_modifiers;
int m_viewportId; ///< The id of the viewport this manipulator is being used in.
AZ::Vector3 LocalScale() const { return m_start.m_localScale + m_current.m_localScaleOffset; }
AZ::Vector3 LocalScaleOffset() const { return m_start.m_scaleSnapOffset + m_current.m_localScaleOffset; }
AZ::Vector3 LocalPosition() const { return m_start.m_localPosition + m_current.m_localPositionOffset; }
AZ::Vector3 LocalPositionOffset() const { return m_current.m_localPositionOffset; }
AZ::Vector2 ScreenOffset() const
{
return AzFramework::Vector2FromScreenVector(
m_current.m_screenPosition - m_start.m_screenPosition);
}
};
/// This is the function signature of callbacks that will be invoked whenever a manipulator
/// is clicked on or dragged.
using MouseActionCallback = AZStd::function<void(const Action&)>;
/// Tuple of StartTransition (initial mouse down to mouse move) and Start state.
using Starter = AZStd::tuple<StartTransition, Start>;
void InstallLeftMouseDownCallback(const MouseActionCallback& onMouseDownCallback);
void InstallMouseMoveCallback(const MouseActionCallback& onMouseMoveCallback);
void InstallLeftMouseUpCallback(const MouseActionCallback& onMouseUpCallback);
void Draw(
const ManipulatorManagerState& managerState,
AzFramework::DebugDisplayRequests& debugDisplay,
const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction) override;
void SetAxis(const AZ::Vector3& axis);
void SetSpace(const AZ::Transform& worldFromLocal);
void SetLocalTransform(const AZ::Transform& localTransform);
void SetLocalPosition(const AZ::Vector3& localPosition);
void SetLocalOrientation(const AZ::Quaternion& localOrientation);
AZ::Vector3 GetPosition() const { return m_localTransform.GetTranslation(); }
const AZ::Vector3& GetAxis() const { return m_fixed.m_axis; }
template<typename Views>
void SetViews(Views&& views)
{
m_manipulatorViews = AZStd::forward<Views>(views);
}
void UseVisualOrientationOverride(const bool useVisualOverride)
{
m_useVisualsOverride = useVisualOverride;
}
void SetVisualOrientationOverride(const AZ::Quaternion& visualOrientation)
{
m_visualOrientationOverride = visualOrientation;
}
private:
void OnLeftMouseDownImpl(
const ViewportInteraction::MouseInteraction& interaction, float rayIntersectionDistance) override;
void OnLeftMouseUpImpl(
const ViewportInteraction::MouseInteraction& interaction) override;
void OnMouseMoveImpl(
const ViewportInteraction::MouseInteraction& interaction) override;
void InvalidateImpl() override;
void SetBoundsDirtyImpl() override;
AZ::Transform m_localTransform = AZ::Transform::CreateIdentity(); ///< Local transform of the manipulator.
AZ::Transform m_worldFromLocal = AZ::Transform::CreateIdentity(); ///< Space the manipulator is in (identity is world space).
bool m_useVisualsOverride = false; // Set this to true to use the Visual Quaternion Override (decoupled from logical axis).
AZ::Quaternion m_visualOrientationOverride = AZ::Quaternion::CreateIdentity(); // Quaternion to use only for visuals.
Fixed m_fixed;
Starter m_starter;
MouseActionCallback m_onLeftMouseDownCallback = nullptr;
MouseActionCallback m_onLeftMouseUpCallback = nullptr;
MouseActionCallback m_onMouseMoveCallback = nullptr;
ManipulatorViews m_manipulatorViews; ///< Look of manipulator.
};
LinearManipulator::Starter CalculateLinearManipulationDataStart(
const LinearManipulator::Fixed& fixed, const AZ::Transform& worldFromLocal, const AZ::Transform& localTransform,
const GridSnapAction& gridSnapAction, const ViewportInteraction::MouseInteraction& interaction,
float intersectionDistance, const AzFramework::CameraState& cameraState);
LinearManipulator::Action CalculateLinearManipulationDataAction(
const LinearManipulator::Fixed& fixed, const LinearManipulator::Starter& starter,
const AZ::Transform& worldFromLocal, const AZ::Transform& localTransform, const GridSnapAction& gridSnapAction,
const ViewportInteraction::MouseInteraction& interaction);
} // namespace AzToolsFramework
@@ -0,0 +1,81 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/EBus/EBus.h>
#include <AzToolsFramework/Picking/BoundInterface.h>
namespace AzToolsFramework
{
class ManipulatorManager;
class BaseManipulator;
using ManipulatorId = IdType<struct ManipulatorType>;
static const ManipulatorId InvalidManipulatorId = ManipulatorId(0);
using ManipulatorManagerId = IdType<struct ManipulatorManagerType>;
static const ManipulatorManagerId InvalidManipulatorManagerId = ManipulatorManagerId(0);
/// EBus interface used to send requests to ManipulatorManager.
class ManipulatorManagerRequests
: public AZ::EBusTraits
{
public:
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById; /**< We can have multiple manipulator managers.
In the case where there are multiple viewports, each displaying
a different set of entities, a different manipulator manager is required
to provide a different collision space for each viewport so that mouse
hit detection can be handled properly. */
using BusIdType = ManipulatorManagerId;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
virtual ~ManipulatorManagerRequests() = default;
/// Register a manipulator with the Manipulator Manager.
/// @param manipulator The manipulator parameter is passed as a shared_ptr so
/// that the system responsible for managing manipulators can maintain ownership
/// of the manipulator even if is destroyed while in use.
virtual void RegisterManipulator(AZStd::shared_ptr<BaseManipulator> manipulator) = 0;
/// Unregister a manipulator from the Manipulator Manager.
/// After unregistering the manipulator, it will be excluded from mouse hit detection
/// and will not receive any mouse action events. The Manipulator Manager will also
/// relinquish ownership of the manipulator.
virtual void UnregisterManipulator(BaseManipulator* manipulator) = 0;
/// Delete a manipulator bound.
virtual void DeleteManipulatorBound(Picking::RegisteredBoundId boundId) = 0;
/// Mark the bound of a manipulator dirty so it's excluded from mouse hit detection.
/// This should be called whenever a manipulator is moved.
virtual void SetBoundDirty(Picking::RegisteredBoundId boundId) = 0;
/// Returns true if the manipulator manager is currently interacting, otherwise false.
virtual bool Interacting() const = 0;
/// Update the bound for a manipulator.
/// If \ref boundId hasn't been registered before or it's invalid, a new bound is created and set using \ref boundShapeData
/// @param manipulatorId The id of the manipulator whose bound needs to update.
/// @param boundId The id of the bound that needs to update.
/// @param boundShapeData The pointer to the new bound shape data.
/// @return If \ref boundId has been registered return the same id, otherwise create a new bound and return its id.
virtual Picking::RegisteredBoundId UpdateBound(
ManipulatorId manipulatorId, Picking::RegisteredBoundId boundId,
const Picking::BoundRequestShapeBase& boundShapeData) = 0;
};
/// Type to inherit to implement ManipulatorManagerRequests.
using ManipulatorManagerRequestBus = AZ::EBus<ManipulatorManagerRequests>;
}//namespace AzToolsFramework
@@ -0,0 +1,48 @@
/*
* 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 "ManipulatorDebug.h"
#include <AzFramework/Entity/EntityDebugDisplayBus.h>
namespace AzToolsFramework
{
void DrawAxis(
AzFramework::DebugDisplayRequests& display, const AZ::Vector3& position, const AZ::Vector3& direction)
{
display.SetLineWidth(4.0f);
display.SetColor(AZ::Color{ 1.0f, 1.0f, 0.0f, 1.0f });
display.DrawLine(position, position + direction);
display.SetLineWidth(1.0f);
}
void DrawTransformAxes(
AzFramework::DebugDisplayRequests& display, const AZ::Transform& transform)
{
const float AxisLength = 0.5f;
display.SetLineWidth(4.0f);
display.SetColor(AZ::Color{ 1.0f, 0.0f, 0.0f, 1.0f });
display.DrawLine(
transform.GetTranslation(),
transform.GetTranslation() + transform.GetBasisX().GetNormalizedSafe() * AxisLength);
display.SetColor(AZ::Color{ 0.0f, 1.0f, 0.0f, 1.0f });
display.DrawLine(
transform.GetTranslation(),
transform.GetTranslation() + transform.GetBasisY().GetNormalizedSafe() * AxisLength);
display.SetColor(AZ::Color{ 0.0f, 0.0f, 1.0f, 1.0f });
display.DrawLine(
transform.GetTranslation(),
transform.GetTranslation() + transform.GetBasisZ().GetNormalizedSafe() * AxisLength);
display.SetLineWidth(1.0f);
}
} // namespace AzToolsFramework
@@ -0,0 +1,33 @@
/*
* 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.
*
*/
#pragma once
namespace AZ
{
class Vector3;
class Transform;
} // namespace AZ
namespace AzFramework
{
class DebugDisplayRequests;
} // namespace AzFramework
namespace AzToolsFramework
{
void DrawAxis(
AzFramework::DebugDisplayRequests& display, const AZ::Vector3& position, const AZ::Vector3& direction);
void DrawTransformAxes(
AzFramework::DebugDisplayRequests& display, const AZ::Transform& transform);
} // namespace AzToolsFramework
@@ -0,0 +1,315 @@
/*
* 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 "BaseManipulator.h"
#include "ManipulatorManager.h"
#include <AzCore/std/tuple.h>
#include <AzToolsFramework/Picking/BoundInterface.h>
namespace AzToolsFramework
{
const ManipulatorManagerId g_mainManipulatorManagerId = ManipulatorManagerId(AZ::Crc32("MainManipulatorManagerId"));
ManipulatorManager::ManipulatorManager(const ManipulatorManagerId managerId)
: m_manipulatorManagerId(managerId)
, m_nextManipulatorIdToGenerate(ManipulatorId(1))
{
ManipulatorManagerRequestBus::Handler::BusConnect(m_manipulatorManagerId);
EditorEntityInfoNotificationBus::Handler::BusConnect();
}
ManipulatorManager::~ManipulatorManager()
{
for (auto& pair : m_manipulatorIdToPtrMap)
{
pair.second->Invalidate();
}
m_manipulatorIdToPtrMap.clear();
ManipulatorManagerRequestBus::Handler::BusDisconnect();
EditorEntityInfoNotificationBus::Handler::BusDisconnect();
}
void ManipulatorManager::RegisterManipulator(AZStd::shared_ptr<BaseManipulator> manipulator)
{
if (!manipulator)
{
AZ_Error("Manipulators", false, "Attempting to register a null Manipulator");
return;
}
if (manipulator->Registered())
{
AZ_Assert(manipulator->GetManipulatorManagerId() == m_manipulatorManagerId,
"This manipulator was registered with a different manipulator manager!");
return;
}
const ManipulatorId manipulatorId = m_nextManipulatorIdToGenerate++;
manipulator->m_manipulatorId = manipulatorId;
manipulator->m_manipulatorManagerId = m_manipulatorManagerId;
m_manipulatorIdToPtrMap[manipulatorId] = AZStd::move(manipulator);
}
void ManipulatorManager::UnregisterManipulator(BaseManipulator* manipulator)
{
if (!manipulator)
{
AZ_Error("Manipulators", false, "Attempting to unregister a null Manipulator");
return;
}
m_manipulatorIdToPtrMap.erase(manipulator->GetManipulatorId());
manipulator->Invalidate();
}
Picking::RegisteredBoundId ManipulatorManager::UpdateBound(
const ManipulatorId manipulatorId, const Picking::RegisteredBoundId boundId,
const Picking::BoundRequestShapeBase& boundShapeData)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
if (manipulatorId == InvalidManipulatorId)
{
return Picking::InvalidBoundId;
}
const auto manipulatorItr = m_manipulatorIdToPtrMap.find(manipulatorId);
if (manipulatorItr == m_manipulatorIdToPtrMap.end())
{
return Picking::InvalidBoundId;
}
if (boundId != Picking::InvalidBoundId)
{
auto boundItr = m_boundIdToManipulatorIdMap.find(boundId);
AZ_UNUSED(boundItr);
AZ_Assert(boundItr != m_boundIdToManipulatorIdMap.end(), "Manipulator and its bounds are out of synchronization!");
AZ_Assert(boundItr->second == manipulatorId, "Manipulator and its bounds are out of synchronization!");
}
const Picking::RegisteredBoundId newBoundId =
m_boundManager.UpdateOrRegisterBound(boundShapeData, boundId);
if (newBoundId != boundId)
{
m_boundIdToManipulatorIdMap[newBoundId] = manipulatorId;
}
return newBoundId;
}
void ManipulatorManager::SetBoundDirty(const Picking::RegisteredBoundId boundId)
{
if (boundId != Picking::InvalidBoundId)
{
m_boundManager.SetBoundValidity(boundId, false);
}
}
void ManipulatorManager::DeleteManipulatorBound(const Picking::RegisteredBoundId boundId)
{
if (boundId != Picking::InvalidBoundId)
{
m_boundManager.UnregisterBound(boundId);
m_boundIdToManipulatorIdMap.erase(boundId);
}
}
void ManipulatorManager::RefreshMouseOverState(const ViewportInteraction::MousePick& mousePick)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
if (!Interacting())
{
auto [pickedManipulatorId, _] = PickManipulatorId(mousePick);
for (auto& pair : m_manipulatorIdToPtrMap)
{
pair.second->UpdateMouseOver(pickedManipulatorId);
}
}
}
void ManipulatorManager::CheckModifierKeysChanged(
[[maybe_unused]] const ViewportInteraction::KeyboardModifiers keyboardModifiers,
const ViewportInteraction::MousePick& mousePick)
{
RefreshMouseOverState(mousePick);
}
void ManipulatorManager::DrawManipulators(
AzFramework::DebugDisplayRequests& debugDisplay,
const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
for (const auto& pair : m_manipulatorIdToPtrMap)
{
pair.second->Draw({ Interacting() }, debugDisplay, cameraState, mouseInteraction);
}
RefreshMouseOverState(mouseInteraction.m_mousePick);
}
AZStd::shared_ptr<BaseManipulator> ManipulatorManager::PerformRaycast(
const AZ::Vector3& rayOrigin, const AZ::Vector3& rayDirection, float& rayIntersectionDistance)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
Picking::RaySelectInfo raySelection;
raySelection.m_origin = rayOrigin;
raySelection.m_direction = rayDirection;
m_boundManager.RaySelect(raySelection);
for (const auto& hitItr : raySelection.m_boundIdsHit)
{
const auto found = m_boundIdToManipulatorIdMap.find(hitItr.first);
if (found != m_boundIdToManipulatorIdMap.end())
{
const auto manipulatorFound = m_manipulatorIdToPtrMap.find(found->second);
AZ_Assert(manipulatorFound != m_manipulatorIdToPtrMap.end(),
"Found a bound without a corresponding Manipulator, "
"it's likely a bound was not cleaned up correctly");
rayIntersectionDistance = hitItr.second;
return manipulatorFound != m_manipulatorIdToPtrMap.end() ? manipulatorFound->second : nullptr;
}
}
return nullptr;
}
bool ManipulatorManager::ConsumeViewportMousePress(const ViewportInteraction::MouseInteraction& interaction)
{
if (auto pickedManipulator = PickManipulator(interaction.m_mousePick);
pickedManipulator.has_value())
{
auto[manipulator, intersectionDistance] = pickedManipulator.value();
if (interaction.m_mouseButtons.Left())
{
if (manipulator->OnLeftMouseDown(interaction, intersectionDistance))
{
m_activeManipulator = manipulator;
return true;
}
}
if (interaction.m_mouseButtons.Right())
{
if (manipulator->OnRightMouseDown(interaction, intersectionDistance))
{
m_activeManipulator = manipulator;
return true;
}
}
}
return false;
}
bool ManipulatorManager::ConsumeViewportMouseRelease(const ViewportInteraction::MouseInteraction& interaction)
{
// must have had a meaningful interaction in mouse down to have assigned an
// active manipulator - only notify mouse up if this was the case
if (m_activeManipulator)
{
if (interaction.m_mouseButtons.Left())
{
m_activeManipulator->OnLeftMouseUp(interaction);
m_activeManipulator = nullptr;
return true;
}
if (interaction.m_mouseButtons.Right())
{
m_activeManipulator->OnRightMouseUp(interaction);
m_activeManipulator = nullptr;
return true;
}
}
return false;
}
AZStd::optional<ManipulatorManager::PickedManipulator> ManipulatorManager::PickManipulator(
const ViewportInteraction::MousePick& mousePick)
{
float intersectionDistance = 0.0f;
const AZStd::shared_ptr<BaseManipulator> pickedManipulator = PerformRaycast(
mousePick.m_rayOrigin, mousePick.m_rayDirection, intersectionDistance);
return pickedManipulator.get() != nullptr
? AZStd::make_optional(AZStd::make_tuple(pickedManipulator, intersectionDistance))
: AZStd::nullopt;
}
ManipulatorManager::PickedManipulatorId ManipulatorManager::PickManipulatorId(
const ViewportInteraction::MousePick& mousePick)
{
auto [manipulator, intersectionDistance] =
PickManipulator(mousePick).value_or(PickedManipulator(nullptr, 0.0f));
const ManipulatorId pickedManipulatorId = manipulator
? manipulator->GetManipulatorId()
: InvalidManipulatorId;
return PickedManipulatorId{pickedManipulatorId, intersectionDistance};
}
ManipulatorManager::ConsumeMouseMoveResult ManipulatorManager::ConsumeViewportMouseMove(
const ViewportInteraction::MouseInteraction& interaction)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
if (m_activeManipulator)
{
m_activeManipulator->OnMouseMove(interaction);
return ConsumeMouseMoveResult::Interacting;
}
return ConsumeMouseMoveResult::None;
}
bool ManipulatorManager::ConsumeViewportMouseWheel(const ViewportInteraction::MouseInteraction& interaction)
{
if (m_activeManipulator)
{
m_activeManipulator->OnMouseWheel(interaction);
return true;
}
return false;
}
void ManipulatorManager::OnEntityInfoUpdatedVisibility(const AZ::EntityId entityId, const bool visible)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
for (auto& pair : m_manipulatorIdToPtrMap)
{
// set all manipulator bounds on this entity to dirty so we cannot
// interact with them (bounds will be refreshed when they are redrawn)
for (const AZ::EntityComponentIdPair& id : pair.second->EntityComponentIdPairs())
{
if (id.GetEntityId() == entityId && !visible)
{
pair.second->SetBoundsDirty();
break;
}
}
}
}
} // namespace AzToolsFramework
@@ -0,0 +1,140 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
#include <AzToolsFramework/Manipulators/ManipulatorBus.h>
#include <AzToolsFramework/Picking/Manipulators/ManipulatorBoundManager.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
namespace AzFramework
{
struct CameraState;
class DebugDisplayRequests;
}
namespace AzToolsFramework
{
namespace Picking
{
class DefaultContextBoundManager;
}
namespace ViewportInteraction
{
struct MouseInteraction;
}
class BaseManipulator;
class LinearManipulator;
/// State of overall manipulator manager.
struct ManipulatorManagerState
{
bool m_interacting;
};
/// This class serves to manage all relevant mouse events and coordinate all registered manipulators to function properly.
/// ManipulatorManager does not manage the life cycle of specific manipulators. The users of manipulators are responsible
/// for creating and deleting them at right time, as well as registering and unregistering accordingly.
class ManipulatorManager
: private ManipulatorManagerRequestBus::Handler
, private EditorEntityInfoNotificationBus::Handler
{
public:
AZ_CLASS_ALLOCATOR(ManipulatorManager, AZ::SystemAllocator, 0)
explicit ManipulatorManager(ManipulatorManagerId managerId);
~ManipulatorManager();
/// The result of consuming a mouse move.
enum class ConsumeMouseMoveResult
{
None,
Hovering, // Note: unused
Interacting,
};
// Note: These are not EBus messages, they are called by the owner of the manipulator manager and they will
// return true if they have handled the interaction - if this is the case you should not process it yourself.
bool ConsumeViewportMousePress(const ViewportInteraction::MouseInteraction&);
ConsumeMouseMoveResult ConsumeViewportMouseMove(const ViewportInteraction::MouseInteraction&);
bool ConsumeViewportMouseRelease(const ViewportInteraction::MouseInteraction&);
bool ConsumeViewportMouseWheel(const ViewportInteraction::MouseInteraction&);
// ManipulatorManagerRequestBus ...
void RegisterManipulator(AZStd::shared_ptr<BaseManipulator> manipulator) override;
void UnregisterManipulator(BaseManipulator* manipulator) override;
void DeleteManipulatorBound(Picking::RegisteredBoundId boundId) override;
void SetBoundDirty(Picking::RegisteredBoundId boundId) override;
Picking::RegisteredBoundId UpdateBound(
ManipulatorId manipulatorId, Picking::RegisteredBoundId boundId,
const Picking::BoundRequestShapeBase& boundShapeData) override;
bool Interacting() const override { return m_activeManipulator != nullptr; }
void DrawManipulators(
AzFramework::DebugDisplayRequests& debugDisplay,
const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction);
// LUMBERYARD_DEPRECATED(LY-117150)
/// Check if the modifier key state has changed - if so we may need to refresh
/// certain manipulator bounds.
AZ_DEPRECATED(
void CheckModifierKeysChanged(
ViewportInteraction::KeyboardModifiers keyboardModifiers,
const ViewportInteraction::MousePick& mousePick),
"CheckModifierKeysChanged is deprecated and will be removed in a future release");
protected:
/// @param rayOrigin The origin of the ray to test intersection with.
/// @param rayDirection The direction of the ray to test intersection with.
/// @param[out] rayIntersectionDistance The result intersecting point equals "rayOrigin + rayIntersectionDistance * rayDirection".
/// @return A pointer to a manipulator that the ray intersects. Null pointer if no intersection is detected.
AZStd::shared_ptr<BaseManipulator> PerformRaycast(
const AZ::Vector3& rayOrigin, const AZ::Vector3& rayDirection, float& rayIntersectionDistance);
// EditorEntityInfoNotifications ...
void OnEntityInfoUpdatedVisibility(AZ::EntityId entityId, bool visible) override;
/// Alias for a Manipulator and intersection distance.
using PickedManipulator = AZStd::tuple<AZStd::shared_ptr<BaseManipulator>, float>;
/// Alias for a ManipulatorId and intersection distance.
using PickedManipulatorId = AZStd::tuple<ManipulatorId, float>;
/// Return the picked manipulator and intersection distance if a manipulator was intersected.
AZStd::optional<PickedManipulator> PickManipulator(const ViewportInteraction::MousePick& mousePick);
/// Wrapper for PickManipulator to return the ManipulatorId directly.
PickedManipulatorId PickManipulatorId(const ViewportInteraction::MousePick& mousePick);
/// Called once per frame after all manipulators have been drawn (and their
/// bounds updated if required).
void RefreshMouseOverState(const ViewportInteraction::MousePick& mousePick);
ManipulatorManagerId m_manipulatorManagerId; ///< This manipulator manager's id.
ManipulatorId m_nextManipulatorIdToGenerate; ///< Id to use for the next manipulator that is registered with this manager.
AZStd::unordered_map<ManipulatorId, AZStd::shared_ptr<BaseManipulator>> m_manipulatorIdToPtrMap; ///< Mapping from a manipulatorId to the corresponding manipulator.
AZStd::unordered_map<Picking::RegisteredBoundId, ManipulatorId> m_boundIdToManipulatorIdMap; ///< Mapping from a boundId to the corresponding manipulatorId.
AZStd::shared_ptr<BaseManipulator> m_activeManipulator; ///< The manipulator we are currently interacting with.
Picking::ManipulatorBoundManager m_boundManager; ///< All active manipulator bounds that could be interacted with.
};
// The main/default ManipulatorManagerId to be used for
// registering manipulators used by components in the level
extern const ManipulatorManagerId g_mainManipulatorManagerId;
} // namespace AzToolsFramework
@@ -0,0 +1,214 @@
/*
* 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 "ManipulatorSnapping.h"
#include <AzCore/Console/Console.h>
#include <AzCore/Math/Internal/VectorConversions.inl>
#include <AzFramework/Entity/EntityDebugDisplayBus.h>
#include <AzToolsFramework/Maths/TransformUtils.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
AZ_CVAR(
AZ::Color, cl_viewportGridMainColor, AZ::Color::CreateFromRgba(26, 26, 26, 127), nullptr,
AZ::ConsoleFunctorFlags::Null, "Main color for snapping grid");
AZ_CVAR(
AZ::Color, cl_viewportGridFadeColor, AZ::Color::CreateFromRgba(127, 127, 127, 0), nullptr,
AZ::ConsoleFunctorFlags::Null, "Fade color for snapping grid");
AZ_CVAR(
int, cl_viewportGridSquareCount, 20, nullptr, AZ::ConsoleFunctorFlags::Null,
"Number of grid squares for snapping grid");
AZ_CVAR(
float, cl_viewportGridLineWidth, 1.0f, nullptr, AZ::ConsoleFunctorFlags::Null,
"Width of grid lines for snapping grid");
AZ_CVAR(
float, cl_viewportFadeLineDistanceScale, 1.0f, nullptr, AZ::ConsoleFunctorFlags::Null,
"The scale to be applied to the line that fades out (scales the current gridSize)");
namespace AzToolsFramework
{
GridSnapParameters::GridSnapParameters(const bool gridSnap, const float gridSize)
: m_gridSnap(gridSnap)
, m_gridSize(gridSize)
{
}
GridSnapAction::GridSnapAction(const GridSnapParameters& gridSnapParameters, const bool localSnapping)
: m_gridSnapParams(gridSnapParameters)
, m_localSnapping(localSnapping)
{
}
ManipulatorInteraction BuildManipulatorInteraction(
const AZ::Transform& worldFromLocal, const AZ::Vector3& worldRayOrigin, const AZ::Vector3& worldRayDirection)
{
const AZ::Transform worldFromLocalUniform = AzToolsFramework::TransformUniformScale(worldFromLocal);
const AZ::Transform localFromWorldUniform = worldFromLocalUniform.GetInverse();
return {localFromWorldUniform.TransformPoint(worldRayOrigin),
TransformDirectionNoScaling(localFromWorldUniform, worldRayDirection),
ScaleReciprocal(worldFromLocalUniform)};
}
AZ::Vector3 CalculateSnappedOffset(
const AZ::Vector3& unsnappedPosition, const AZ::Vector3& axis, const float size)
{
// calculate total distance along axis
const float axisDistance = axis.Dot(unsnappedPosition);
// round to nearest step size
const float snappedAxisDistance = floorf((axisDistance / size) + 0.5f) * size;
// return offset along axis to snap to step size
return axis * (snappedAxisDistance - axisDistance);
}
AZ::Vector3 CalculateSnappedTerrainPosition(
const AZ::Vector3& worldSurfacePosition, const AZ::Transform& worldFromLocal,
const int viewportId, const float gridSize)
{
const AZ::Transform localFromWorld = worldFromLocal.GetInverse();
const AZ::Vector3 localSurfacePosition = localFromWorld.TransformPoint(worldSurfacePosition);
// snap in xy plane
AZ::Vector3 localSnappedSurfacePosition = localSurfacePosition +
CalculateSnappedOffset(localSurfacePosition, AZ::Vector3::CreateAxisX(), gridSize) +
CalculateSnappedOffset(localSurfacePosition, AZ::Vector3::CreateAxisY(), gridSize);
// find terrain height at xy snapped location
float terrainHeight = 0.0f;
ViewportInteraction::MainEditorViewportInteractionRequestBus::EventResult(
terrainHeight, viewportId,
&ViewportInteraction::MainEditorViewportInteractionRequestBus::Events::TerrainHeight,
Vector3ToVector2(worldFromLocal.TransformPoint(localSnappedSurfacePosition)));
// set snapped z value to terrain height
AZ::Vector3 localTerrainHeight = localFromWorld.TransformPoint(AZ::Vector3(0.0f, 0.0f, terrainHeight));
localSnappedSurfacePosition.SetZ(localTerrainHeight.GetZ());
return localSnappedSurfacePosition;
}
bool GridSnapping(const int viewportId)
{
bool snapping = false;
ViewportInteraction::ViewportInteractionRequestBus::EventResult(
snapping, viewportId,
&ViewportInteraction::ViewportInteractionRequestBus::Events::GridSnappingEnabled);
return snapping;
}
float GridSize(const int viewportId)
{
float gridSize = 0.0f;
ViewportInteraction::ViewportInteractionRequestBus::EventResult(
gridSize, viewportId,
&ViewportInteraction::ViewportInteractionRequestBus::Events::GridSize);
return gridSize;
}
GridSnapParameters GridSnapSettings(const int viewportId)
{
bool snapping = GridSnapping(viewportId);
const float gridSize = GridSize(viewportId);
if (AZ::IsClose(gridSize, 0.0f, 1e-2f)) // Same threshold value as min value for m_spinBox in SnapToWidget constructor in MainWindow.cpp
{
snapping = false;
}
return GridSnapParameters(snapping, gridSize);
}
bool AngleSnapping(const int viewportId)
{
bool snapping = false;
ViewportInteraction::ViewportInteractionRequestBus::EventResult(
snapping, viewportId,
&ViewportInteraction::ViewportInteractionRequestBus::Events::AngleSnappingEnabled);
return snapping;
}
float AngleStep(const int viewportId)
{
float angle = 0.0f;
ViewportInteraction::ViewportInteractionRequestBus::EventResult(
angle, viewportId,
&ViewportInteraction::ViewportInteractionRequestBus::Events::AngleStep);
return angle;
}
bool ShowingGrid(const int viewportId)
{
bool show = false;
ViewportInteraction::ViewportInteractionRequestBus::EventResult(
show, viewportId,
&ViewportInteraction::ViewportInteractionRequestBus::Events::ShowGrid);
return show;
}
void DrawSnappingGrid(
AzFramework::DebugDisplayRequests& debugDisplay, const AZ::Transform& worldFromLocal, const float squareSize)
{
debugDisplay.PushMatrix(worldFromLocal);
debugDisplay.SetLineWidth(cl_viewportGridLineWidth);
const int gridSquareCount = cl_viewportGridSquareCount;
AZStd::vector<AZ::Vector3> lines;
lines.reserve((gridSquareCount + 1) * 2);
const AZ::Vector4 gridMainColor = static_cast<AZ::Color>(cl_viewportGridMainColor).GetAsVector4();
const AZ::Vector4 gridFadeColor = static_cast<AZ::Color>(cl_viewportGridFadeColor).GetAsVector4();
const float halfGridSquareCount = float(gridSquareCount) * 0.5f;
const float halfGridSize = halfGridSquareCount * squareSize;
const float fadeLineLength = cl_viewportFadeLineDistanceScale * squareSize;
for (size_t lineIndex = 0; lineIndex <= gridSquareCount; ++lineIndex)
{
const float lineOffset = -halfGridSize + (lineIndex * squareSize);
// draw the faded end parts of the grid lines
debugDisplay.DrawLine(
AZ::Vector3(lineOffset, -halfGridSize, 0.0f),
AZ::Vector3(lineOffset, -(halfGridSize + fadeLineLength), 0.0f),
gridMainColor, gridFadeColor);
debugDisplay.DrawLine(
AZ::Vector3(lineOffset, halfGridSize, 0.0f),
AZ::Vector3(lineOffset, (halfGridSize + fadeLineLength), 0.0f),
gridMainColor, gridFadeColor);
debugDisplay.DrawLine(
AZ::Vector3(-halfGridSize, lineOffset, 0.0f),
AZ::Vector3(-(halfGridSize + fadeLineLength), lineOffset, 0.0f),
gridMainColor, gridFadeColor);
debugDisplay.DrawLine(
AZ::Vector3(halfGridSize, lineOffset, 0.0f),
AZ::Vector3((halfGridSize + fadeLineLength), lineOffset, 0.0f),
gridMainColor, gridFadeColor);
// build a vector of the main grid lines to draw (start and end positions)
lines.push_back(AZ::Vector3(lineOffset, -halfGridSize, 0.0f));
lines.push_back(AZ::Vector3(lineOffset, halfGridSize, 0.0f));
lines.push_back(AZ::Vector3(-halfGridSize, lineOffset, 0.0f));
lines.push_back(AZ::Vector3(halfGridSize, lineOffset, 0.0f));
}
debugDisplay.DrawLines(lines, cl_viewportGridMainColor);
// restore original width
debugDisplay.SetLineWidth(1.0f);
debugDisplay.PopMatrix();
}
} // namespace AzToolsFramework
@@ -0,0 +1,115 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Math/Vector3.h>
#include <AzCore/Math/Transform.h>
namespace AzFramework
{
class DebugDisplayRequests;
}
namespace AzToolsFramework
{
/// Structure to encapsulate grid snapping properties.
struct GridSnapParameters
{
GridSnapParameters(bool gridSnap, float gridSize);
bool m_gridSnap;
float m_gridSize;
};
/// Structure to encapsulate the current grid snapping state.
struct GridSnapAction
{
GridSnapAction(const GridSnapParameters& gridSnapParameters, bool localSnapping);
GridSnapParameters m_gridSnapParams;
bool m_localSnapping;
};
/// Structure to hold transformed incoming viewport interaction from world space to manipulator space.
struct ManipulatorInteraction
{
AZ::Vector3 m_localRayOrigin; ///< The ray origin (start) in the reference from of the manipulator.
AZ::Vector3 m_localRayDirection; ///< The ray direction in the reference from of the manipulator.
float m_scaleReciprocal; ///< The scale reciprocal (1.0 / scale) of the transform used to move the
///< ray from world space to local space.
};
/// Build a ManipulatorInteraction structure from the incoming viewport interaction.
ManipulatorInteraction BuildManipulatorInteraction(
const AZ::Transform& worldFromLocal, const AZ::Vector3& worldRayOrigin, const AZ::Vector3& worldRayDirection);
/// Calculate the offset along an axis to adjust a position
/// to stay snapped to a given grid size.
AZ::Vector3 CalculateSnappedOffset(
const AZ::Vector3& unsnappedPosition, const AZ::Vector3& axis, float size);
/// For a given point on the terrain, calculate the closest xy position snapped to the grid
/// (z position is aligned to terrain height, not snapped to z grid)
AZ::Vector3 CalculateSnappedTerrainPosition(
const AZ::Vector3& worldSurfacePosition, const AZ::Transform& worldFromLocal,
int viewportId, float gridSize);
/// Wrapper for grid snapping and grid size bus calls.
GridSnapParameters GridSnapSettings(int viewportId);
/// Wrapper for angle snapping enabled bus call.
bool AngleSnapping(int viewportId);
/// Wrapper for angle snapping increment bus call.
/// @return Angle in degrees
float AngleStep(int viewportId);
/// Wrapper for grid rendering check call.
bool ShowingGrid(int viewportId);
/// Render the grid used for snapping.
void DrawSnappingGrid(
AzFramework::DebugDisplayRequests& debugDisplay, const AZ::Transform& worldFromLocal, float squareSize);
/// Round to x number of significant digits.
/// @param value Number to round.
/// @param exponent Precision to use when rounding.
inline float Round(const float value, const float exponent)
{
const float precision = std::pow(10.0f, exponent);
return roundf(value * precision) / precision;
}
/// Round to 3 significant digits (3 digits common usage).
inline float Round3(const float value)
{
return Round(value, 3.0f);
}
/// Util to return sign of floating point number.
/// value > 0 return 1.0
/// value < 0 return -1.0
/// value == 0 return 0.0
inline float Sign(const float value)
{
return static_cast<float>((0.0f < value) - (value < 0.0f));
}
/// Find the max scale element and return the reciprocal of it.
/// Note: The reciprocal will be rounded to three significant digits to eliminate
/// noise in the value returned when dealing with values far from the origin.
inline float ScaleReciprocal(const AZ::Transform& transform)
{
return Round3(transform.GetScale().GetReciprocal().GetMinElement());
}
} // namespace AzToolsFramework
@@ -0,0 +1,742 @@
/*
* 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 "ManipulatorView.h"
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Math/VectorConversions.h>
#include <AzCore/std/containers/array.h>
#include <AzFramework/Entity/EntityDebugDisplayBus.h>
#include <AzToolsFramework/Manipulators/ManipulatorManager.h>
#include <AzToolsFramework/Manipulators/AngularManipulator.h>
#include <AzToolsFramework/Manipulators/LinearManipulator.h>
#include <AzToolsFramework/Manipulators/LineSegmentSelectionManipulator.h>
#include <AzToolsFramework/Manipulators/PlanarManipulator.h>
#include <AzToolsFramework/Manipulators/SplineSelectionManipulator.h>
#include <AzToolsFramework/Maths/TransformUtils.h>
#include <AzToolsFramework/ViewportSelection/EditorSelectionUtil.h>
namespace AzToolsFramework
{
const float g_defaultManipulatorSphereRadius = 0.1f;
AZ::Transform WorldFromLocalWithUniformScale(const AZ::EntityId entityId)
{
AZ::Transform worldFromLocal = AZ::Transform::CreateIdentity();
AZ::TransformBus::EventResult(
worldFromLocal, entityId, &AZ::TransformBus::Events::GetWorldTM);
return TransformUniformScale(worldFromLocal);
}
/// Take into account the location of the camera and orientate the axis so it faces the camera.
/// if we did correct the camera (shouldCorrect is true) then we know the axis facing us it negative.
/// we can use this to change the rendering for a flipped axis if we wish.
static void CameraCorrectAxis(
const AZ::Vector3& axis, AZ::Vector3& correctedAxis, const ManipulatorManagerState& managerState,
const ViewportInteraction::MouseInteraction& mouseInteraction,
const AZ::Transform& worldFromLocal, const AZ::Vector3& localPosition,
const AzFramework::CameraState& cameraState,
bool* shouldCorrect = nullptr)
{
// do not update (flip) the axis while the manipulator is being interacted with (mouse button is held)
if (!(mouseInteraction.m_mouseButtons.Any() && managerState.m_interacting))
{
// check if we actually needed to flip the axis, if so, write to shouldCorrect
// so we know and are able to draw it differently if we wish (e.g. hollow if flipped)
const bool correcting = ShouldFlipCameraAxis(worldFromLocal, localPosition, axis, cameraState);
// the corrected axis, if no flip was required, output == input
correctedAxis = correcting
? -axis
: axis;
// optional out ref to use if we care about the result
if (shouldCorrect)
{
*shouldCorrect = correcting;
}
}
}
/// Calculate quad bound in world space.
static Picking::BoundShapeQuad CalculateQuadBound(
const AZ::Vector3& localPosition, const AZ::Transform& worldFromLocal,
const AZ::Vector3& axis1, const AZ::Vector3& axis2, const float size)
{
const AZ::Vector3 worldPosition = worldFromLocal.TransformPoint(localPosition);
const AZ::Vector3 endAxis1World = localPosition +
TransformDirectionNoScaling(worldFromLocal, axis1) * size;
const AZ::Vector3 endAxis2World = localPosition +
TransformDirectionNoScaling(worldFromLocal, axis2) * size;
Picking::BoundShapeQuad quadBound;
quadBound.m_corner1 = worldPosition;
quadBound.m_corner2 = worldPosition + endAxis1World;
quadBound.m_corner3 = worldPosition + endAxis1World + endAxis2World;
quadBound.m_corner4 = worldPosition + endAxis2World;
return quadBound;
}
static Picking::BoundShapeQuad CalculateQuadBoundBillboard(
const AZ::Vector3& localPosition, const AZ::Transform& worldFromLocal,
const float size, const AzFramework::CameraState& cameraState)
{
const AZ::Vector3 worldPosition = worldFromLocal.TransformPoint(localPosition);
Picking::BoundShapeQuad quadBound;
quadBound.m_corner1 = worldPosition - size * cameraState.m_up - size * cameraState.m_side;
quadBound.m_corner2 = worldPosition - size * cameraState.m_up + size * cameraState.m_side;
quadBound.m_corner3 = worldPosition + size * cameraState.m_up + size * cameraState.m_side;
quadBound.m_corner4 = worldPosition + size * cameraState.m_up - size * cameraState.m_side;
return quadBound;
}
/// Calculate line bound in world space (axis and length).
static Picking::BoundShapeLineSegment CalculateLineBound(
const AZ::Vector3& localPosition, const AZ::Transform& worldFromLocal,
const AZ::Vector3& axis, const float length, const float width)
{
Picking::BoundShapeLineSegment lineBound;
lineBound.m_start = worldFromLocal.TransformPoint(localPosition);
lineBound.m_end = TransformPositionNoScaling(worldFromLocal, localPosition + (axis * length));
lineBound.m_width = width;
return lineBound;
}
/// Calculate line bound in world space (start and end point).
static Picking::BoundShapeLineSegment CalculateLineBound(
const AZ::Vector3& localStartPosition,
const AZ::Vector3& localEndPosition,
const AZ::Transform& worldFromLocal, const float width)
{
Picking::BoundShapeLineSegment lineBound;
lineBound.m_start = worldFromLocal.TransformPoint(localStartPosition);
lineBound.m_end = worldFromLocal.TransformPoint(localEndPosition);
lineBound.m_width = width;
return lineBound;
}
/// Calculate cone bound in world space.
static Picking::BoundShapeCone CalculateConeBound(
const AZ::Vector3& localPosition, const AZ::Transform& worldFromLocal,
const AZ::Vector3& axis, const AZ::Vector3& offset, const float length, const float radius)
{
Picking::BoundShapeCone coneBound;
coneBound.m_radius = radius;
coneBound.m_height = length;
coneBound.m_axis = TransformDirectionNoScaling(worldFromLocal, axis);
coneBound.m_base = TransformPositionNoScaling(worldFromLocal, localPosition + offset);
return coneBound;
}
/// Calculate box bound in world space.
static Picking::BoundShapeBox CalculateBoxBound(
const AZ::Vector3& localPosition, const AZ::Transform& worldFromLocal,
const AZ::Quaternion& orientation, const AZ::Vector3& offset, const AZ::Vector3& halfExtents)
{
Picking::BoundShapeBox boxBound;
boxBound.m_halfExtents = halfExtents;
boxBound.m_orientation = (worldFromLocal.GetRotation() * orientation).GetNormalized();
boxBound.m_center = TransformPositionNoScaling(worldFromLocal, localPosition + offset);
return boxBound;
}
/// Calculate cylinder bound in world space.
static Picking::BoundShapeCylinder CalculateCylinderBound(
const AZ::Vector3& localPosition, const AZ::Transform& worldFromLocal,
const AZ::Vector3& axis, const float length, const float radius)
{
Picking::BoundShapeCylinder boxBound;
boxBound.m_base = worldFromLocal.TransformPoint(localPosition);
boxBound.m_axis = TransformDirectionNoScaling(worldFromLocal, axis);
boxBound.m_height = length;
boxBound.m_radius = radius;
return boxBound;
}
/// Calculate sphere bound in world space.
static Picking::BoundShapeSphere CalculateSphereBound(
const AZ::Vector3& localPosition, const AZ::Transform& worldFromLocal,
const float radius)
{
Picking::BoundShapeSphere sphereBound;
sphereBound.m_center = worldFromLocal.TransformPoint(localPosition);
sphereBound.m_radius = radius;
return sphereBound;
}
/// Calculate torus bound in world space.
static Picking::BoundShapeTorus CalculateTorusBound(
const AZ::Vector3& localPosition, const AZ::Transform& worldFromLocal,
const AZ::Vector3& axis, const float radius, const float width)
{
Picking::BoundShapeTorus torusBound;
torusBound.m_center = worldFromLocal.TransformPoint(localPosition);
torusBound.m_minorRadius = width;
torusBound.m_majorRadius = radius;
torusBound.m_axis = TransformDirectionNoScaling(worldFromLocal, axis);
return torusBound;
}
/// Calculate spline bound in world space.
static Picking::BoundShapeSpline CalculateSplineBound(
const AZStd::weak_ptr<const AZ::Spline>& spline, const AZ::Transform& worldFromLocal, const float width)
{
Picking::BoundShapeSpline splineBound;
splineBound.m_spline = spline;
splineBound.m_width = width;
splineBound.m_transform = worldFromLocal;
return splineBound;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static float LineWidth(const bool mouseOver, const float defaultWidth, const float mouseOverWidth)
{
const AZStd::array<float, 2> lineWidth = { { defaultWidth, mouseOverWidth } };
return lineWidth[mouseOver];
}
static AZ::Color ViewColor(
const bool mouseOver, const AZ::Color& defaultColor, const AZ::Color& mouseOverColor)
{
const AZStd::array<AZ::Color, 2> viewColor = { { defaultColor, mouseOverColor } };
return viewColor[mouseOver].GetAsVector4();
}
auto defaultLineWidth = [](const bool mouseOver)
{
return LineWidth(mouseOver, 0.0f, 4.0f);
};
ManipulatorView::ManipulatorView() = default;
ManipulatorView::ManipulatorView(const bool screenSizeFixed)
: m_screenSizeFixed(screenSizeFixed)
{
}
ManipulatorView::~ManipulatorView()
{
Invalidate(m_managerId);
}
void ManipulatorView::SetBoundDirty(const ManipulatorManagerId managerId)
{
ManipulatorManagerRequestBus::Event(
managerId, &ManipulatorManagerRequestBus::Events::SetBoundDirty, m_boundId);
m_boundDirty = true;
}
void ManipulatorView::RefreshBound(
const ManipulatorManagerId managerId, const ManipulatorId manipulatorId,
const Picking::BoundRequestShapeBase& bound)
{
ManipulatorManagerRequestBus::EventResult(
m_boundId, managerId, &ManipulatorManagerRequestBus::Events::UpdateBound,
manipulatorId, m_boundId, bound);
// store the manager id if we know the bound has been registered
m_managerId = managerId;
// the bound will now be up to date
m_boundDirty = false;
}
void ManipulatorView::RefreshBoundInternal(
const ManipulatorManagerId managerId, const ManipulatorId manipulatorId,
const Picking::BoundRequestShapeBase& bound)
{
// update the manipulator's bounds if necessary
// if m_screenSizeFixed is true, any camera movement can potentially change the size
// of the manipulator, so we update bounds every frame regardless until we have performance issue
if (m_screenSizeFixed || m_boundDirty)
{
RefreshBound(managerId, manipulatorId, bound);
}
}
void ManipulatorView::Invalidate(const ManipulatorManagerId managerId)
{
if (m_boundId != Picking::InvalidBoundId)
{
ManipulatorManagerRequestBus::Event(
managerId, &ManipulatorManagerRequestBus::Events::DeleteManipulatorBound, m_boundId);
m_boundId = Picking::InvalidBoundId;
}
}
float ManipulatorView::ManipulatorViewScaleMultiplier(
const AZ::Vector3& worldPosition, const AzFramework::CameraState& cameraState) const
{
return ScreenSizeFixed()
? CalculateScreenToWorldMultiplier(worldPosition, cameraState)
: 1.0f;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ManipulatorViewQuad::Draw(
const ManipulatorManagerId managerId, const ManipulatorManagerState& managerState,
const ManipulatorId manipulatorId, const ManipulatorState& manipulatorState,
AzFramework::DebugDisplayRequests& debugDisplay, const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction)
{
const AZ::Vector3 axis1 = m_axis1;
const AZ::Vector3 axis2 = m_axis2;
CameraCorrectAxis(
axis1, m_cameraCorrectedAxis1, managerState, mouseInteraction,
manipulatorState.m_worldFromLocal, manipulatorState.m_localPosition, cameraState);
CameraCorrectAxis(
axis2, m_cameraCorrectedAxis2, managerState, mouseInteraction,
manipulatorState.m_worldFromLocal, manipulatorState.m_localPosition, cameraState);
const Picking::BoundShapeQuad quadBound =
CalculateQuadBound(
manipulatorState.m_localPosition, manipulatorState.m_worldFromLocal, m_cameraCorrectedAxis1, m_cameraCorrectedAxis2,
m_size * ManipulatorViewScaleMultiplier(
manipulatorState.m_worldFromLocal.TransformPoint(manipulatorState.m_localPosition), cameraState));
debugDisplay.SetLineWidth(defaultLineWidth(manipulatorState.m_mouseOver));
debugDisplay.SetColor(ViewColor(manipulatorState.m_mouseOver, m_axis1Color, m_mouseOverColor).GetAsVector4());
debugDisplay.DrawLine(quadBound.m_corner4, quadBound.m_corner3);
debugDisplay.SetColor(ViewColor(manipulatorState.m_mouseOver, m_axis2Color, m_mouseOverColor).GetAsVector4());
debugDisplay.DrawLine(quadBound.m_corner2, quadBound.m_corner3);
if (manipulatorState.m_mouseOver)
{
debugDisplay.SetColor(Vector3ToVector4(m_mouseOverColor.GetAsVector3(), 0.5f));
debugDisplay.CullOff();
debugDisplay.DrawQuad(
quadBound.m_corner1, quadBound.m_corner2,
quadBound.m_corner3, quadBound.m_corner4);
debugDisplay.CullOn();
}
RefreshBoundInternal(managerId, manipulatorId, quadBound);
}
void ManipulatorViewQuadBillboard::Draw(
const ManipulatorManagerId managerId, const ManipulatorManagerState& /*managerState*/,
const ManipulatorId manipulatorId, const ManipulatorState& manipulatorState,
AzFramework::DebugDisplayRequests& debugDisplay, const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& /*mouseInteraction*/)
{
const Picking::BoundShapeQuad quadBound =
CalculateQuadBoundBillboard(manipulatorState.m_localPosition, manipulatorState.m_worldFromLocal,
m_size * ManipulatorViewScaleMultiplier(
manipulatorState.m_worldFromLocal.TransformPoint(manipulatorState.m_localPosition), cameraState), cameraState);
debugDisplay.SetColor(ViewColor(manipulatorState.m_mouseOver, m_color, m_mouseOverColor).GetAsVector4());
debugDisplay.DrawQuad(
quadBound.m_corner1, quadBound.m_corner2,
quadBound.m_corner3, quadBound.m_corner4);
RefreshBoundInternal(managerId, manipulatorId, quadBound);
}
void ManipulatorViewLine::Draw(
const ManipulatorManagerId managerId, const ManipulatorManagerState& managerState,
const ManipulatorId manipulatorId, const ManipulatorState& manipulatorState,
AzFramework::DebugDisplayRequests& debugDisplay, const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction)
{
const float viewScale = ManipulatorViewScaleMultiplier(
manipulatorState.m_worldFromLocal.TransformPoint(manipulatorState.m_localPosition), cameraState);
CameraCorrectAxis(
m_axis, m_cameraCorrectedAxis, managerState, mouseInteraction,
manipulatorState.m_worldFromLocal, manipulatorState.m_localPosition, cameraState);
const Picking::BoundShapeLineSegment lineBound =
CalculateLineBound(
manipulatorState.m_localPosition, manipulatorState.m_worldFromLocal,
m_cameraCorrectedAxis, m_length * viewScale, m_width * viewScale);
debugDisplay.SetColor(ViewColor(manipulatorState.m_mouseOver, m_color, m_mouseOverColor).GetAsVector4());
debugDisplay.SetLineWidth(defaultLineWidth(manipulatorState.m_mouseOver));
debugDisplay.DrawLine(lineBound.m_start, lineBound.m_end);
RefreshBoundInternal(managerId, manipulatorId, lineBound);
}
void ManipulatorViewLineSelect::Draw(
const ManipulatorManagerId managerId, const ManipulatorManagerState& /*managerState*/,
const ManipulatorId manipulatorId, const ManipulatorState& manipulatorState,
AzFramework::DebugDisplayRequests& debugDisplay, const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction)
{
const float viewScale = ManipulatorViewScaleMultiplier(
manipulatorState.m_worldFromLocal.TransformPoint(manipulatorState.m_localPosition), cameraState);
const Picking::BoundShapeLineSegment lineBound =
CalculateLineBound(m_localStart, m_localEnd, manipulatorState.m_worldFromLocal, m_width * viewScale);
if (manipulatorState.m_mouseOver)
{
const LineSegmentSelectionManipulator::Action action = CalculateManipulationDataAction(
manipulatorState.m_worldFromLocal, mouseInteraction.m_mousePick.m_rayOrigin,
mouseInteraction.m_mousePick.m_rayDirection,
cameraState.m_farClip, m_localStart, m_localEnd);
const AZ::Vector3 worldLineHitPosition = manipulatorState.m_worldFromLocal.TransformPoint(action.m_localLineHitPosition);
debugDisplay.SetColor(AZ::Vector4(0.0f, 1.0f, 0.0f, 1.0f));
debugDisplay.DrawBall(
worldLineHitPosition, ManipulatorViewScaleMultiplier(worldLineHitPosition, cameraState)
* g_defaultManipulatorSphereRadius, false);
}
RefreshBoundInternal(managerId, manipulatorId, lineBound);
}
void ManipulatorViewCone::Draw(
const ManipulatorManagerId managerId, const ManipulatorManagerState& managerState,
const ManipulatorId manipulatorId, const ManipulatorState& manipulatorState,
AzFramework::DebugDisplayRequests& debugDisplay, const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction)
{
const float viewScale = ManipulatorViewScaleMultiplier(
manipulatorState.m_worldFromLocal.TransformPoint(manipulatorState.m_localPosition), cameraState);
CameraCorrectAxis(
m_axis, m_cameraCorrectedAxis, managerState, mouseInteraction,
manipulatorState.m_worldFromLocal, manipulatorState.m_localPosition,
cameraState, &m_shouldCorrect);
CameraCorrectAxis(
m_offset, m_cameraCorrectedOffset, managerState, mouseInteraction,
manipulatorState.m_worldFromLocal, manipulatorState.m_localPosition, cameraState);
const Picking::BoundShapeCone coneBound =
CalculateConeBound(
manipulatorState.m_localPosition, manipulatorState.m_worldFromLocal, m_cameraCorrectedAxis,
m_cameraCorrectedOffset * viewScale,
m_length * viewScale,
m_radius * viewScale);
debugDisplay.SetColor(ViewColor(manipulatorState.m_mouseOver, m_color, m_mouseOverColor).GetAsVector4());
// show wireframe if the axis has been corrected/flipped
// note: please see IRenderAuxGeom.h for the definition of e_FillModeWireframe and e_FillModeSolid.
// it is not possible to include IRenderAuxGeom from here and we also don't want to introduce that dependency.
// these legacy enums should be wrapped so set SetFillMode can be used in a type safe way, until then,
// use the values directly until the API has been updated.
const AZ::u32 prevFillMode = debugDisplay.SetFillMode(
m_shouldCorrect ? /*e_FillModeWireframe =*/ 0x1 << 26 : /*e_FillModeSolid =*/ 0);
debugDisplay.DrawCone(coneBound.m_base, coneBound.m_axis, coneBound.m_radius, coneBound.m_height, false);
debugDisplay.SetFillMode(prevFillMode);
RefreshBoundInternal(managerId, manipulatorId, coneBound);
}
void ManipulatorViewBox::Draw(
const ManipulatorManagerId managerId, const ManipulatorManagerState& managerState,
const ManipulatorId manipulatorId, const ManipulatorState& manipulatorState,
AzFramework::DebugDisplayRequests& debugDisplay, const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction)
{
const float viewScale = ManipulatorViewScaleMultiplier(
manipulatorState.m_worldFromLocal.TransformPoint(manipulatorState.m_localPosition), cameraState);
const AZ::Quaternion orientation = m_orientation;
CameraCorrectAxis(
m_offset, m_cameraCorrectedOffset, managerState, mouseInteraction,
manipulatorState.m_worldFromLocal, manipulatorState.m_localPosition,
cameraState);
const Picking::BoundShapeBox boxBound =
CalculateBoxBound(manipulatorState.m_localPosition, manipulatorState.m_worldFromLocal, orientation,
m_cameraCorrectedOffset * viewScale,
m_halfExtents * viewScale);
const AZ::Vector3 xAxis = boxBound.m_orientation.TransformVector(AZ::Vector3::CreateAxisX());
const AZ::Vector3 yAxis = boxBound.m_orientation.TransformVector(AZ::Vector3::CreateAxisY());
const AZ::Vector3 zAxis = boxBound.m_orientation.TransformVector(AZ::Vector3::CreateAxisZ());
debugDisplay.SetColor(ViewColor(manipulatorState.m_mouseOver, m_color, m_mouseOverColor).GetAsVector4());
debugDisplay.DrawSolidOBB(boxBound.m_center,
xAxis, yAxis, zAxis, boxBound.m_halfExtents);
RefreshBoundInternal(managerId, manipulatorId, boxBound);
}
void ManipulatorViewCylinder::Draw(
const ManipulatorManagerId managerId, const ManipulatorManagerState& managerState,
const ManipulatorId manipulatorId, const ManipulatorState& manipulatorState,
AzFramework::DebugDisplayRequests& debugDisplay, const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction)
{
const float viewScale = ManipulatorViewScaleMultiplier(
manipulatorState.m_worldFromLocal.TransformPoint(manipulatorState.m_localPosition), cameraState);
CameraCorrectAxis(
m_axis, m_cameraCorrectedAxis, managerState, mouseInteraction,
manipulatorState.m_worldFromLocal, manipulatorState.m_localPosition, cameraState);
const Picking::BoundShapeCylinder cylinderBound =
CalculateCylinderBound(
manipulatorState.m_localPosition, manipulatorState.m_worldFromLocal, m_cameraCorrectedAxis,
m_length * viewScale,
m_radius * viewScale);
debugDisplay.SetColor(ViewColor(manipulatorState.m_mouseOver, m_color, m_mouseOverColor).GetAsVector4());
debugDisplay.DrawSolidCylinder(cylinderBound.m_base + cylinderBound.m_axis * cylinderBound.m_height * 0.5f,
cylinderBound.m_axis, cylinderBound.m_radius, cylinderBound.m_height, false);
RefreshBoundInternal(managerId, manipulatorId, cylinderBound);
}
void ManipulatorViewSphere::Draw(
const ManipulatorManagerId managerId, const ManipulatorManagerState& /*managerState*/,
const ManipulatorId manipulatorId, const ManipulatorState& manipulatorState,
AzFramework::DebugDisplayRequests& debugDisplay, const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction)
{
const Picking::BoundShapeSphere sphereBound =
CalculateSphereBound(manipulatorState.m_localPosition, manipulatorState.m_worldFromLocal,
m_radius * ManipulatorViewScaleMultiplier(
manipulatorState.m_worldFromLocal.TransformPoint(manipulatorState.m_localPosition), cameraState));
if (m_depthTest)
{
debugDisplay.DepthTestOn();
}
debugDisplay.SetColor(m_decideColorFn(mouseInteraction, manipulatorState.m_mouseOver, m_color).GetAsVector4());
debugDisplay.DrawBall(sphereBound.m_center, sphereBound.m_radius, false);
if (m_depthTest)
{
debugDisplay.DepthTestOff();
}
RefreshBoundInternal(managerId, manipulatorId, sphereBound);
}
void ManipulatorViewCircle::Draw(
const ManipulatorManagerId managerId, const ManipulatorManagerState& /*managerState*/,
const ManipulatorId manipulatorId, const ManipulatorState& manipulatorState,
AzFramework::DebugDisplayRequests& debugDisplay, const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& /*mouseInteraction*/)
{
const float viewScale = ManipulatorViewScaleMultiplier(
manipulatorState.m_worldFromLocal.TransformPoint(manipulatorState.m_localPosition), cameraState);
const Picking::BoundShapeTorus torusBound =
CalculateTorusBound(
manipulatorState.m_localPosition, manipulatorState.m_worldFromLocal, m_axis,
m_radius * viewScale,
m_width * viewScale);
// transform circle based on delta between default z up axis and other axes
const AZ::Transform worldFromLocalWithOrientation =
AZ::Transform::CreateTranslation(manipulatorState.m_worldFromLocal.GetTranslation()) *
AZ::Transform::CreateFromQuaternion(
(QuaternionFromTransformNoScaling(manipulatorState.m_worldFromLocal) *
AZ::Quaternion::CreateShortestArc(AZ::Vector3::CreateAxisZ(), m_axis)).GetNormalized());
debugDisplay.CullOn();
debugDisplay.PushMatrix(worldFromLocalWithOrientation);
debugDisplay.SetColor(ViewColor(manipulatorState.m_mouseOver, m_color, m_mouseOverColor).GetAsVector4());
m_drawCircleFunc(debugDisplay, manipulatorState.m_localPosition, torusBound.m_majorRadius,
worldFromLocalWithOrientation.GetInverse().TransformPoint(cameraState.m_position));
debugDisplay.PopMatrix();
debugDisplay.CullOff();
RefreshBoundInternal(managerId, manipulatorId, torusBound);
}
void DrawHalfDottedCircle(
AzFramework::DebugDisplayRequests& debugDisplay, const AZ::Vector3& position,
const float radius, const AZ::Vector3& viewPos)
{
debugDisplay.DrawHalfDottedCircle(position, radius, viewPos);
}
void DrawFullCircle(
AzFramework::DebugDisplayRequests& debugDisplay, const AZ::Vector3& position,
const float radius, const AZ::Vector3& /*viewPos*/)
{
debugDisplay.DrawCircle(position, radius);
}
void ManipulatorViewSplineSelect::Draw(
const ManipulatorManagerId managerId, const ManipulatorManagerState& /*managerState*/,
const ManipulatorId manipulatorId, const ManipulatorState& manipulatorState,
AzFramework::DebugDisplayRequests& debugDisplay, const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction)
{
const float viewScale = ManipulatorViewScaleMultiplier(
manipulatorState.m_worldFromLocal.TransformPoint(manipulatorState.m_localPosition), cameraState);
const Picking::BoundShapeSpline splineBound =
CalculateSplineBound(m_spline, manipulatorState.m_worldFromLocal, m_width * viewScale);
if (manipulatorState.m_mouseOver)
{
const SplineSelectionManipulator::Action action = CalculateManipulationDataAction(
manipulatorState.m_worldFromLocal, mouseInteraction.m_mousePick.m_rayOrigin,
mouseInteraction.m_mousePick.m_rayDirection, m_spline);
const AZ::Vector3 worldSplineHitPosition =
manipulatorState.m_worldFromLocal.TransformPoint(action.m_localSplineHitPosition);
debugDisplay.SetColor(m_color.GetAsVector4());
debugDisplay.DrawBall(
worldSplineHitPosition, ManipulatorViewScaleMultiplier(worldSplineHitPosition, cameraState)
* g_defaultManipulatorSphereRadius, false);
}
RefreshBoundInternal(managerId, manipulatorId, splineBound);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
AZStd::unique_ptr<ManipulatorViewQuad> CreateManipulatorViewQuad(
const PlanarManipulator& planarManipulator, const AZ::Color& axis1Color,
const AZ::Color& axis2Color, const float size)
{
AZStd::unique_ptr<ManipulatorViewQuad> viewQuad = AZStd::make_unique<ManipulatorViewQuad>();
viewQuad->m_axis1 = planarManipulator.GetAxis1();
viewQuad->m_axis2 = planarManipulator.GetAxis2();
viewQuad->m_size = size;
viewQuad->m_axis1Color = axis1Color;
viewQuad->m_axis2Color = axis2Color;
return viewQuad;
}
AZStd::unique_ptr<ManipulatorViewQuadBillboard> CreateManipulatorViewQuadBillboard(
const AZ::Color& color, const float size)
{
AZStd::unique_ptr<ManipulatorViewQuadBillboard> viewQuad = AZStd::make_unique<ManipulatorViewQuadBillboard>();
viewQuad->m_size = size;
viewQuad->m_color = color;
return viewQuad;
}
AZStd::unique_ptr<ManipulatorViewLine> CreateManipulatorViewLine(
const LinearManipulator& linearManipulator, const AZ::Color& color,
const float length, const float width)
{
AZStd::unique_ptr<ManipulatorViewLine> viewLine = AZStd::make_unique<ManipulatorViewLine>();
viewLine->m_axis = linearManipulator.GetAxis();
viewLine->m_length = length;
viewLine->m_width = width;
viewLine->m_color = color;
return viewLine;
}
AZStd::unique_ptr<ManipulatorViewLineSelect> CreateManipulatorViewLineSelect(
const LineSegmentSelectionManipulator& lineSegmentManipulator,
const AZ::Color& color, const float width)
{
AZStd::unique_ptr<ManipulatorViewLineSelect> viewLineSelect = AZStd::make_unique<ManipulatorViewLineSelect>();
viewLineSelect->m_localStart = lineSegmentManipulator.GetStart();
viewLineSelect->m_localEnd = lineSegmentManipulator.GetEnd();
viewLineSelect->m_width = width;
viewLineSelect->m_color = color;
return viewLineSelect;
}
AZStd::unique_ptr<ManipulatorViewCone> CreateManipulatorViewCone(
const LinearManipulator& linearManipulator, const AZ::Color& color,
const AZ::Vector3& offset, const float length, const float radius)
{
AZStd::unique_ptr<ManipulatorViewCone> viewCone = AZStd::make_unique<ManipulatorViewCone>();
viewCone->m_axis = linearManipulator.GetAxis();
viewCone->m_length = length;
viewCone->m_radius = radius;
viewCone->m_offset = offset;
viewCone->m_color = color;
return viewCone;
}
AZStd::unique_ptr<ManipulatorViewBox> CreateManipulatorViewBox(
const AZ::Transform& transform, const AZ::Color& color,
const AZ::Vector3& offset, const AZ::Vector3& halfExtents)
{
AZStd::unique_ptr<ManipulatorViewBox> viewBox = AZStd::make_unique<ManipulatorViewBox>();
viewBox->m_orientation = transform.GetRotation();
viewBox->m_halfExtents = halfExtents;
viewBox->m_offset = offset;
viewBox->m_color = color;
return viewBox;
}
AZStd::unique_ptr<ManipulatorViewCylinder> CreateManipulatorViewCylinder(
const LinearManipulator& linearManipulator, const AZ::Color& color,
const float length, const float radius)
{
AZStd::unique_ptr<ManipulatorViewCylinder> viewCylinder = AZStd::make_unique<ManipulatorViewCylinder>();
viewCylinder->m_axis = linearManipulator.GetAxis();
viewCylinder->m_radius = radius;
viewCylinder->m_length = length;
viewCylinder->m_color = color;
return viewCylinder;
}
AZStd::unique_ptr<ManipulatorViewSphere> CreateManipulatorViewSphere(
const AZ::Color& color, const float radius, const DecideColorFn& decideColor, bool enableDepthTest)
{
AZStd::unique_ptr<ManipulatorViewSphere> viewSphere = AZStd::make_unique<ManipulatorViewSphere>();
viewSphere->m_radius = radius;
viewSphere->m_color = color;
viewSphere->m_decideColorFn = decideColor;
viewSphere->m_depthTest = enableDepthTest;
return viewSphere;
}
AZStd::unique_ptr<ManipulatorViewCircle> CreateManipulatorViewCircle(
const AngularManipulator& angularManipulator, const AZ::Color& color,
const float radius, const float width, const ManipulatorViewCircle::DrawCircleFunc drawFunc)
{
AZStd::unique_ptr<ManipulatorViewCircle> viewCircle = AZStd::make_unique<ManipulatorViewCircle>();
viewCircle->m_axis = angularManipulator.GetAxis();
viewCircle->m_color = color;
viewCircle->m_radius = radius;
viewCircle->m_width = width;
viewCircle->m_drawCircleFunc = drawFunc;
return viewCircle;
}
AZStd::unique_ptr<ManipulatorViewSplineSelect> CreateManipulatorViewSplineSelect(
const SplineSelectionManipulator& splineManipulator,
const AZ::Color& color, const float width)
{
AZStd::unique_ptr<ManipulatorViewSplineSelect> viewSplineSelect = AZStd::make_unique<ManipulatorViewSplineSelect>();
viewSplineSelect->m_spline = splineManipulator.GetSpline();
viewSplineSelect->m_color = color;
viewSplineSelect->m_width = width;
return viewSplineSelect;
}
AZ::Vector3 CalculateViewDirection(
const Manipulators& manipulators, const AZ::Vector3& worldViewPosition)
{
const AZ::Transform worldFromLocalWithTransform =
manipulators.GetSpace() * manipulators.GetLocalTransform();
AZ::Vector3 lookDirection =
(worldFromLocalWithTransform.GetTranslation() - worldViewPosition).GetNormalized();
return TransformDirectionNoScaling(
worldFromLocalWithTransform.GetInverse(), lookDirection);
}
} // namespace AzToolsFramework
@@ -0,0 +1,396 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Math/Vector3.h>
#include <AzFramework/Viewport/CameraState.h>
#include <AzToolsFramework/Manipulators/BaseManipulator.h>
#include <AzToolsFramework/Maths/TransformUtils.h>
#include <AzToolsFramework/Picking/ContextBoundAPI.h>
namespace AzToolsFramework
{
class PlanarManipulator;
class LinearManipulator;
class AngularManipulator;
class LineSegmentSelectionManipulator;
class SplineSelectionManipulator;
using DecideColorFn = AZStd::function<AZ::Color(
const ViewportInteraction::MouseInteraction&,
bool mouseOver, const AZ::Color& defaultColor)>;
extern const float g_defaultManipulatorSphereRadius;
/// State of an individual manipulator.
struct ManipulatorState
{
AZ::Transform m_worldFromLocal;
AZ::Vector3 m_localPosition;
bool m_mouseOver;
};
/// The base interface for the visual representation of manipulators.
/// The View represents the appearance and bounds of the manipulator for
/// the user to interact with. Any manipulator can have any view (some may
/// be more appropriate than others in certain cases).
class ManipulatorView
{
public:
AZ_CLASS_ALLOCATOR(ManipulatorView, AZ::SystemAllocator, 0)
AZ_RTTI(ManipulatorView, "{7529E3E9-39B3-4D15-899A-FA13770113B2}")
ManipulatorView();
ManipulatorView(bool screenSizeFixed);
virtual ~ManipulatorView();
ManipulatorView(ManipulatorView&&) = default;
ManipulatorView& operator=(ManipulatorView&&) = default;
void SetBoundDirty(ManipulatorManagerId managerId);
void RefreshBound(
ManipulatorManagerId managerId, ManipulatorId manipulatorId, const Picking::BoundRequestShapeBase& bound);
void Invalidate(ManipulatorManagerId managerId);
virtual void Draw(
ManipulatorManagerId managerId, const ManipulatorManagerState& managerState,
ManipulatorId manipulatorId, const ManipulatorState& manipulatorState,
AzFramework::DebugDisplayRequests& debugDisplay, const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction) = 0;
bool ScreenSizeFixed() const { return m_screenSizeFixed; }
protected:
AZ::Color m_mouseOverColor = BaseManipulator::s_defaultMouseOverColor; ///< What color should the manipulator
///< be when the mouse is hovering over it.
/// Scale the manipulator based on the distance
/// from the camera if m_screenSizeFixed is true.
float ManipulatorViewScaleMultiplier(
const AZ::Vector3& worldPosition, const AzFramework::CameraState& cameraState) const;
/// Wrap the logic for updating a bound.
/// Should be called at the end of the Draw function once a concrete BoundRequestShape has
/// been created to use for dimensions for rendering.
void RefreshBoundInternal(
ManipulatorManagerId managerId, ManipulatorId manipulatorId, const Picking::BoundRequestShapeBase& bound);
private:
Picking::RegisteredBoundId m_boundId = Picking::InvalidBoundId; ///< Used for hit detection.
ManipulatorManagerId m_managerId = InvalidManipulatorManagerId; /// The manipulator manager this view has been registered with.
bool m_screenSizeFixed = true; ///< Should manipulator size be adjusted based on camera distance.
bool m_boundDirty = true; ///< Do the bounds need to be recalculated.
};
// A collection of views (a manipulator may have 1 - * views)
using ManipulatorViews = AZStd::vector<AZStd::shared_ptr<ManipulatorView>>;
/// Display a quad representing part of a plane, rendered as 4 lines.
class ManipulatorViewQuad
: public ManipulatorView
{
public:
AZ_CLASS_ALLOCATOR(ManipulatorViewQuad, AZ::SystemAllocator, 0)
AZ_RTTI(ManipulatorViewQuad, "{D85E1B45-495E-4755-BCF2-6AE45F8BB2B0}", ManipulatorView)
void Draw(
ManipulatorManagerId managerId, const ManipulatorManagerState& managerState,
ManipulatorId manipulatorId, const ManipulatorState& manipulatorState,
AzFramework::DebugDisplayRequests& debugDisplay, const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction) override;
AZ::Vector3 m_axis1 = AZ::Vector3(1.0f, 0.0f, 0.0f);
AZ::Vector3 m_axis2 = AZ::Vector3(0.0f, 1.0f, 0.0f);
AZ::Color m_axis1Color = AZ::Color(1.0f, 0.0f, 0.0f, 1.0f);
AZ::Color m_axis2Color = AZ::Color(1.0f, 0.0f, 0.0f, 1.0f);
float m_size = 0.06f; ///< size to render and do mouse ray intersection tests against.
private:
AZ::Vector3 m_cameraCorrectedAxis1;
AZ::Vector3 m_cameraCorrectedAxis2;
};
/// A screen aligned quad, centered at the position of the manipulator, display filled.
class ManipulatorViewQuadBillboard
: public ManipulatorView
{
public:
AZ_CLASS_ALLOCATOR(ManipulatorViewQuadBillboard, AZ::SystemAllocator, 0)
AZ_RTTI(ManipulatorViewQuadBillboard, "{C205E967-E8C6-4A73-A31B-41EE5529B15B}", ManipulatorView)
void Draw(
ManipulatorManagerId managerId, const ManipulatorManagerState& managerState,
ManipulatorId manipulatorId, const ManipulatorState& manipulatorState,
AzFramework::DebugDisplayRequests& debugDisplay, const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction) override;
AZ::Color m_color = AZ::Color(1.0f, 0.0f, 0.0f, 1.0f);
float m_size = 0.005f; ///< size to render and do mouse ray intersection tests against.
};
/// Displays a debug style line starting from the manipulator's transform,
/// width determines the click area.
class ManipulatorViewLine
: public ManipulatorView
{
public:
AZ_CLASS_ALLOCATOR(ManipulatorViewLine, AZ::SystemAllocator, 0)
AZ_RTTI(ManipulatorViewLine, "{831EEF66-4A5C-450C-B152-EA4A0BC8A272}", ManipulatorView)
void Draw(
ManipulatorManagerId managerId, const ManipulatorManagerState& managerState,
ManipulatorId manipulatorId, const ManipulatorState& manipulatorState,
AzFramework::DebugDisplayRequests& debugDisplay, const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction) override;
AZ::Vector3 m_axis;
AZ::Color m_color = AZ::Color(1.0f, 0.0f, 0.0f, 1.0f);
float m_length = 0.0f;
float m_width = 0.0f;
private:
AZ::Vector3 m_cameraCorrectedAxis;
};
/// Variant of ManipulatorViewLine which instead of using an axis, provides begin and end
/// points for the line. Used for selection when inserting points along a line.
class ManipulatorViewLineSelect
: public ManipulatorView
{
public:
AZ_CLASS_ALLOCATOR(ManipulatorViewLineSelect, AZ::SystemAllocator, 0)
AZ_RTTI(ManipulatorViewLineSelect, "{BF26A947-91F8-4595-9A5B-481876EB2C48}", ManipulatorView)
void Draw(
ManipulatorManagerId managerId, const ManipulatorManagerState& managerState,
ManipulatorId manipulatorId, const ManipulatorState& manipulatorState,
AzFramework::DebugDisplayRequests& debugDisplay, const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction) override;
AZ::Vector3 m_localStart;
AZ::Vector3 m_localEnd;
AZ::Color m_color = AZ::Color(1.0f, 0.0f, 0.0f, 1.0f);
float m_width = 0.0f;
};
/// Displays a filled cone along the specified axis, offset is local translation from
/// the manipulator transform (often used in conjunction with other views to build
/// aggregate views such as arrows - e.g. a line and cone).
class ManipulatorViewCone
: public ManipulatorView
{
public:
AZ_CLASS_ALLOCATOR(ManipulatorViewCone, AZ::SystemAllocator, 0)
AZ_RTTI(ManipulatorViewCone, "{BF042887-1F51-4FD8-8CA5-4A649B4AF356}", ManipulatorView)
void Draw(
ManipulatorManagerId managerId, const ManipulatorManagerState& managerState,
ManipulatorId manipulatorId, const ManipulatorState& manipulatorState,
AzFramework::DebugDisplayRequests& debugDisplay, const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction) override;
AZ::Vector3 m_offset;
AZ::Vector3 m_axis;
AZ::Color m_color = AZ::Color(1.0f, 0.0f, 0.0f, 1.0f);
float m_length = 0.0f;
float m_radius = 0.0f;
private:
AZ::Vector3 m_cameraCorrectedAxis;
AZ::Vector3 m_cameraCorrectedOffset;
bool m_shouldCorrect = false;
};
/// Displays a filled box, offset is local translation from the manipulator
/// transform, box is often used in conjunction with other views, orientation allows
/// the box to be orientated separately from the manipulator transform.
class ManipulatorViewBox
: public ManipulatorView
{
public:
AZ_CLASS_ALLOCATOR(ManipulatorViewBox, AZ::SystemAllocator, 0)
AZ_RTTI(ManipulatorViewBox, "{2D082201-7878-4C1B-A3DD-7A629E5AD598}", ManipulatorView)
void Draw(
ManipulatorManagerId managerId, const ManipulatorManagerState& managerState,
ManipulatorId manipulatorId, const ManipulatorState& manipulatorState,
AzFramework::DebugDisplayRequests& debugDisplay, const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction) override;
AZ::Vector3 m_offset;
AZ::Quaternion m_orientation;
AZ::Vector3 m_halfExtents;
AZ::Color m_color = AZ::Color(1.0f, 0.0f, 0.0f, 1.0f);
private:
AZ::Vector3 m_cameraCorrectedOffset;
};
/// Displays a filled cylinder along the axis provided.
class ManipulatorViewCylinder
: public ManipulatorView
{
public:
AZ_CLASS_ALLOCATOR(ManipulatorViewCylinder, AZ::SystemAllocator, 0)
AZ_RTTI(ManipulatorViewCylinder, "{9B8E5EF4-0F85-4CD0-A5FF-3C7097DF58AC}", ManipulatorView)
void Draw(
ManipulatorManagerId managerId, const ManipulatorManagerState& managerState,
ManipulatorId manipulatorId, const ManipulatorState& manipulatorState,
AzFramework::DebugDisplayRequests& debugDisplay, const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction) override;
AZ::Vector3 m_axis;
float m_length = 0.0f;
float m_radius = 0.0f;
AZ::Color m_color = AZ::Color(1.0f, 0.0f, 0.0f, 1.0f);
private:
AZ::Vector3 m_cameraCorrectedAxis;
};
/// Displays a filled sphere at the transform of the manipulator, often used as
/// a selection manipulator. DecideColorFn allows more complex logic to be used
/// to decide the color of the manipulator (based on hover state etc.)
class ManipulatorViewSphere
: public ManipulatorView
{
public:
AZ_CLASS_ALLOCATOR(ManipulatorViewSphere, AZ::SystemAllocator, 0)
AZ_RTTI(ManipulatorViewSphere, "{324D8329-6E7B-4A5D-AC8A-8C0E1C984E38}", ManipulatorView)
void Draw(
ManipulatorManagerId managerId, const ManipulatorManagerState& managerState,
ManipulatorId manipulatorId, const ManipulatorState& manipulatorState,
AzFramework::DebugDisplayRequests& debugDisplay, const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction) override;
float m_radius = 0.0f;
DecideColorFn m_decideColorFn;
AZ::Color m_color = AZ::Color(1.0f, 0.0f, 0.0f, 1.0f);
bool m_depthTest = false;
};
/// Displays a wire circle. DrawCircleFunc can be used to either draw a full
/// circle or a half dotted circle where the part of the circle facing away
/// from the camera is dotted (useful for angular/rotation manipulators).
class ManipulatorViewCircle
: public ManipulatorView
{
public:
AZ_CLASS_ALLOCATOR(ManipulatorViewCircle, AZ::SystemAllocator, 0)
AZ_RTTI(ManipulatorViewCircle, "{26563A03-3E48-49EB-9DCF-30EE4F567FCD}", ManipulatorView)
using DrawCircleFunc =
void(*)(AzFramework::DebugDisplayRequests&, const AZ::Vector3&, float, const AZ::Vector3&);
void Draw(
ManipulatorManagerId managerId, const ManipulatorManagerState& managerState,
ManipulatorId manipulatorId, const ManipulatorState& manipulatorState,
AzFramework::DebugDisplayRequests& debugDisplay, const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction) override;
AZ::Vector3 m_axis;
float m_width = 0.0f;
float m_radius = 0.0f;
AZ::Color m_color = AZ::Color(1.0f, 0.0f, 0.0f, 1.0f);
DrawCircleFunc m_drawCircleFunc = nullptr;
};
// helpers to provide consistent function pointer interface for deciding
// on type of circle to draw (see DrawCircleFunc in ManipulatorViewCircle above)
void DrawHalfDottedCircle(
AzFramework::DebugDisplayRequests& debugDisplay, const AZ::Vector3& position,
float radius, const AZ::Vector3& viewPos);
void DrawFullCircle(
AzFramework::DebugDisplayRequests& debugDisplay, const AZ::Vector3& position,
float radius, const AZ::Vector3& viewPos);
/// Used for interaction with spline primitive - it will generate a spline bound
/// to be interacted with and will display the intersection point on the spline
/// where a user may wish to insert a point.
class ManipulatorViewSplineSelect
: public ManipulatorView
{
public:
AZ_CLASS_ALLOCATOR(ManipulatorViewSplineSelect, AZ::SystemAllocator, 0)
AZ_RTTI(ManipulatorViewSplineSelect, "{60996E49-D6BF-4817-BAA3-D27A407DD21A}", ManipulatorView)
void Draw(
ManipulatorManagerId managerId, const ManipulatorManagerState& managerState,
ManipulatorId manipulatorId, const ManipulatorState& manipulatorState,
AzFramework::DebugDisplayRequests& debugDisplay, const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction) override;
AZStd::weak_ptr<const AZ::Spline> m_spline;
float m_width = 0.0f;
AZ::Color m_color = AZ::Color(0.0f, 1.0f, 0.0f, 1.0f);
};
/// Returns true if axis is pointing away from us (we should flip it).
inline bool ShouldFlipCameraAxis(
const AZ::Transform& worldFromLocal, const AZ::Vector3& localPosition,
const AZ::Vector3& axis, const AzFramework::CameraState& cameraState)
{
return (worldFromLocal.TransformPoint(localPosition) - cameraState.m_position).Dot(
TransformDirectionNoScaling(worldFromLocal, axis)) > 0.0f;
}
/// @brief Return the world transform of the entity with uniform scale - choose
/// the largest element.
AZ::Transform WorldFromLocalWithUniformScale(AZ::EntityId entityId);
// Helpers to create various manipulator views.
AZStd::unique_ptr<ManipulatorViewQuad> CreateManipulatorViewQuad(
const PlanarManipulator& planarManipulator, const AZ::Color& axis1Color,
const AZ::Color& axis2Color, float size);
AZStd::unique_ptr<ManipulatorViewQuadBillboard> CreateManipulatorViewQuadBillboard(
const AZ::Color& color, float size);
AZStd::unique_ptr<ManipulatorViewLine> CreateManipulatorViewLine(
const LinearManipulator& linearManipulator, const AZ::Color& color,
float length, float width);
AZStd::unique_ptr<ManipulatorViewLineSelect> CreateManipulatorViewLineSelect(
const LineSegmentSelectionManipulator& lineSegmentManipulator, const AZ::Color& color,
float width);
AZStd::unique_ptr<ManipulatorViewCone> CreateManipulatorViewCone(
const LinearManipulator& linearManipulator, const AZ::Color& color,
const AZ::Vector3& offset, float length, float radius);
AZStd::unique_ptr<ManipulatorViewBox> CreateManipulatorViewBox(
const AZ::Transform& transform, const AZ::Color& color,
const AZ::Vector3& offset, const AZ::Vector3& halfExtents);
AZStd::unique_ptr<ManipulatorViewCylinder> CreateManipulatorViewCylinder(
const LinearManipulator& linearManipulator, const AZ::Color& color,
float length, float radius);
AZStd::unique_ptr<ManipulatorViewSphere> CreateManipulatorViewSphere(
const AZ::Color& color, float radius, const DecideColorFn& decideColor, bool enableDepthTest = false);
AZStd::unique_ptr<ManipulatorViewCircle> CreateManipulatorViewCircle(
const AngularManipulator& angularManipulator, const AZ::Color& color,
float radius, float width, ManipulatorViewCircle::DrawCircleFunc drawFunc);
AZStd::unique_ptr<ManipulatorViewSplineSelect> CreateManipulatorViewSplineSelect(
const SplineSelectionManipulator& splineManipulator, const AZ::Color& color,
float width);
/// Returns the vector between the view (camera) and the manipulator in the space
/// of the Manipulator (manipulator space + local transform).
AZ::Vector3 CalculateViewDirection(
const Manipulators& manipulators, const AZ::Vector3& worldViewPosition);
} // namespace AzToolsFramework
@@ -0,0 +1,220 @@
/*
* 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 "MultiLinearManipulator.h"
#include <AzCore/Memory/SystemAllocator.h>
#include <AzFramework/Entity/EntityDebugDisplayBus.h>
#include <AzToolsFramework/Manipulators/ManipulatorDebug.h>
#include <AzToolsFramework/Manipulators/ManipulatorSnapping.h>
#include <AzToolsFramework/Maths/TransformUtils.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
#include <AzToolsFramework/ViewportSelection/EditorSelectionUtil.h>
namespace AzToolsFramework
{
AZ_CLASS_ALLOCATOR_IMPL(MultiLinearManipulator, AZ::SystemAllocator, 0)
AZStd::shared_ptr<MultiLinearManipulator> MultiLinearManipulator::MakeShared(const AZ::Transform& worldFromLocal)
{
return AZStd::shared_ptr<MultiLinearManipulator>(aznew MultiLinearManipulator(worldFromLocal));
}
MultiLinearManipulator::MultiLinearManipulator(const AZ::Transform& worldFromLocal)
: m_worldFromLocal(worldFromLocal)
{
AttachLeftMouseDownImpl();
}
MultiLinearManipulator::~MultiLinearManipulator()
{
ClearAxes();
}
void MultiLinearManipulator::InstallLeftMouseDownCallback(const MouseActionCallback& onMouseDownCallback)
{
m_onLeftMouseDownCallback = onMouseDownCallback;
}
void MultiLinearManipulator::InstallLeftMouseUpCallback(const MouseActionCallback& onMouseUpCallback)
{
m_onLeftMouseUpCallback = onMouseUpCallback;
}
void MultiLinearManipulator::InstallMouseMoveCallback(const MouseActionCallback& onMouseMoveCallback)
{
m_onMouseMoveCallback = onMouseMoveCallback;
}
static MultiLinearManipulator::Action BuildMultiLinearManipulatorAction(
const AZ::Transform& worldFromLocal, const AZ::Transform& localTransform,
const ViewportInteraction::MouseInteraction& interaction,
const AZStd::vector<LinearManipulator::Fixed>& fixedAxes,
const AZStd::vector<LinearManipulator::Starter>& starterStates, const GridSnapAction& gridSnapAction)
{
MultiLinearManipulator::Action action;
action.m_viewportId = interaction.m_interactionId.m_viewportId;
// build up action state for each axis
for (size_t fixedIndex = 0; fixedIndex < fixedAxes.size(); ++fixedIndex)
{
action.m_actions.push_back(
CalculateLinearManipulationDataAction(
fixedAxes[fixedIndex], starterStates[fixedIndex], worldFromLocal, localTransform, gridSnapAction,
interaction));
}
return action;
}
void MultiLinearManipulator::OnLeftMouseDownImpl(
const ViewportInteraction::MouseInteraction& interaction, const float rayIntersectionDistance)
{
const AZ::Transform worldFromLocalUniformScale = TransformUniformScale(m_worldFromLocal);
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
const AzFramework::CameraState cameraState = GetCameraState(interaction.m_interactionId.m_viewportId);
// build up initial start state for each axis
for (const auto& fixed : m_fixedAxes)
{
// note: m_localTransform must not be made uniform as it may contain a local scale we want to snap
const auto linearStart = CalculateLinearManipulationDataStart(
fixed, worldFromLocalUniformScale, m_localTransform,
GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt()), interaction,
rayIntersectionDistance, cameraState);
m_starters.push_back(linearStart);
}
if (m_onLeftMouseDownCallback)
{
const GridSnapAction gridSnapAction = GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt());
// pass action containing all linear actions for each axis to handler
m_onLeftMouseDownCallback(BuildMultiLinearManipulatorAction(
worldFromLocalUniformScale, m_localTransform, interaction, m_fixedAxes, m_starters, gridSnapAction));
}
}
void MultiLinearManipulator::OnMouseMoveImpl(const ViewportInteraction::MouseInteraction& interaction)
{
if (m_onMouseMoveCallback)
{
const AZ::Transform worldFromLocalUniformScale = TransformUniformScale(m_worldFromLocal);
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
const GridSnapAction gridSnapAction = GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt());
m_onMouseMoveCallback(BuildMultiLinearManipulatorAction(
worldFromLocalUniformScale, m_localTransform, interaction, m_fixedAxes, m_starters, gridSnapAction));
}
}
void MultiLinearManipulator::OnLeftMouseUpImpl(const ViewportInteraction::MouseInteraction& interaction)
{
if (m_onLeftMouseUpCallback)
{
const AZ::Transform worldFromLocalUniformScale = TransformUniformScale(m_worldFromLocal);
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
const GridSnapAction gridSnapAction = GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt());
m_onLeftMouseUpCallback(BuildMultiLinearManipulatorAction(
worldFromLocalUniformScale, m_localTransform, interaction, m_fixedAxes, m_starters, gridSnapAction));
m_starters.clear();
}
}
void MultiLinearManipulator::Draw(
const ManipulatorManagerState& managerState,
AzFramework::DebugDisplayRequests& debugDisplay,
const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction)
{
if (cl_manipulatorDrawDebug)
{
const AZ::Transform combined = TransformUniformScale(m_worldFromLocal) * m_localTransform;
for (const auto& fixed : m_fixedAxes)
{
DrawAxis(
debugDisplay, combined.GetTranslation(), TransformDirectionNoScaling(combined, fixed.m_axis));
}
}
for (auto& view : m_manipulatorViews)
{
view->Draw(
GetManipulatorManagerId(), managerState,
GetManipulatorId(), {
m_worldFromLocal * m_localTransform,
AZ::Vector3::CreateZero(), MouseOver()
},
debugDisplay, cameraState, mouseInteraction);
}
}
void MultiLinearManipulator::AddAxis(const AZ::Vector3& axis)
{
m_fixedAxes.push_back(LinearManipulator::Fixed{axis});
}
void MultiLinearManipulator::AddAxes(const AZStd::vector<AZ::Vector3>& axes)
{
AZStd::transform(
axes.begin(), axes.end(),
AZStd::back_inserter(m_fixedAxes),
[](const AZ::Vector3& axis)
{
return LinearManipulator::Fixed{axis};
});
}
void MultiLinearManipulator::SetSpace(const AZ::Transform& worldFromLocal)
{
m_worldFromLocal = worldFromLocal;
}
void MultiLinearManipulator::SetLocalTransform(const AZ::Transform& localTransform)
{
m_localTransform = localTransform;
}
void MultiLinearManipulator::SetLocalPosition(const AZ::Vector3& localPosition)
{
m_localTransform.SetTranslation(localPosition);
}
void MultiLinearManipulator::SetLocalOrientation(const AZ::Quaternion& localOrientation)
{
m_localTransform = AZ::Transform::CreateFromQuaternionAndTranslation(
localOrientation, m_localTransform.GetTranslation());
}
void MultiLinearManipulator::ClearAxes()
{
m_fixedAxes.clear();
}
void MultiLinearManipulator::InvalidateImpl()
{
for (auto& view : m_manipulatorViews)
{
view->Invalidate(GetManipulatorManagerId());
}
}
void MultiLinearManipulator::SetBoundsDirtyImpl()
{
for (auto& view : m_manipulatorViews)
{
view->SetBoundDirty(GetManipulatorManagerId());
}
}
} // namespace AzToolsFramework
@@ -0,0 +1,140 @@
/*
* 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.
*
*/
#pragma once
#include "BaseManipulator.h"
#include "LinearManipulator.h"
#include <AzToolsFramework/Manipulators/ManipulatorView.h>
#include <AzToolsFramework/Viewport/ViewportTypes.h>
namespace AzToolsFramework
{
struct GridSnapAction;
//! MultiLinearManipulator serves as a visual tool for users to modify values
//! in one or more dimensions on axes defined in 3D space.
class MultiLinearManipulator
: public BaseManipulator
{
//! Private constructor.
explicit MultiLinearManipulator(const AZ::Transform& worldFromLocal);
public:
AZ_CLASS_ALLOCATOR_DECL
AZ_RTTI(MultiLinearManipulator, "{8490E883-8CC6-44C7-B2FE-AF9C9AF38AA7}", BaseManipulator)
MultiLinearManipulator() = delete;
MultiLinearManipulator(const MultiLinearManipulator&) = delete;
MultiLinearManipulator& operator=(const MultiLinearManipulator&) = delete;
~MultiLinearManipulator();
//! A Manipulator must only be created and managed through a shared_ptr.
static AZStd::shared_ptr<MultiLinearManipulator> MakeShared(const AZ::Transform& worldFromLocal);
//! Mouse action data used by MouseActionCallback
//! Provides a collection of LinearManipulator actions for each axis.
struct Action
{
AZStd::vector<LinearManipulator::Action> m_actions;
int m_viewportId; //!< The id of the viewport this manipulator is being used in.
};
//! This is the function signature of callbacks that will be invoked whenever a MultiLinearManipulator
//! is clicked on or dragged.
using MouseActionCallback = AZStd::function<void(const Action&)>;
void InstallLeftMouseDownCallback(const MouseActionCallback& onMouseDownCallback);
void InstallMouseMoveCallback(const MouseActionCallback& onMouseMoveCallback);
void InstallLeftMouseUpCallback(const MouseActionCallback& onMouseUpCallback);
// BaseManipulator ...
void Draw(
const ManipulatorManagerState& managerState,
AzFramework::DebugDisplayRequests& debugDisplay,
const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction) override;
void AddAxis(const AZ::Vector3& axis);
void AddAxes(const AZStd::vector<AZ::Vector3>& axes);
void ClearAxes();
void SetSpace(const AZ::Transform& worldFromLocal);
void SetLocalTransform(const AZ::Transform& localTransform);
void SetLocalPosition(const AZ::Vector3& localPosition);
void SetLocalOrientation(const AZ::Quaternion& localOrientation);
AZ::Vector3 GetLocalPosition() const;
const AZ::Transform& GetSpace() const;
const AZ::Transform& GetLocalTransform() const;
using ConstFixedIterator = AZStd::vector<LinearManipulator::Fixed>::const_iterator;
ConstFixedIterator FixedBegin() const;
ConstFixedIterator FixedEnd() const;
template<typename Views>
void SetViews(Views&& views)
{
m_manipulatorViews = AZStd::forward<Views>(views);
}
private:
void OnLeftMouseDownImpl(
const ViewportInteraction::MouseInteraction& interaction, float rayIntersectionDistance) override;
void OnLeftMouseUpImpl(
const ViewportInteraction::MouseInteraction& interaction) override;
void OnMouseMoveImpl(
const ViewportInteraction::MouseInteraction& interaction) override;
void InvalidateImpl() override;
void SetBoundsDirtyImpl() override;
AZ::Transform m_localTransform = AZ::Transform::CreateIdentity(); //!< Local transform of the manipulator.
AZ::Transform m_worldFromLocal = AZ::Transform::CreateIdentity(); //!< Space the manipulator is in (identity is world space).
AZStd::vector<LinearManipulator::Fixed> m_fixedAxes; //!< A collection of LinearManipulator fixed states.
AZStd::vector<LinearManipulator::Starter> m_starters; //!< A collection of LinearManipulator starter states.
MouseActionCallback m_onLeftMouseDownCallback = nullptr;
MouseActionCallback m_onLeftMouseUpCallback = nullptr;
MouseActionCallback m_onMouseMoveCallback = nullptr;
ManipulatorViews m_manipulatorViews; //!< Look of manipulator.
};
inline AZ::Vector3 MultiLinearManipulator::GetLocalPosition() const
{
return m_localTransform.GetTranslation();
}
inline const AZ::Transform& MultiLinearManipulator::GetSpace() const
{
return m_localTransform;
}
inline const AZ::Transform& MultiLinearManipulator::GetLocalTransform() const
{
return m_localTransform;
}
inline MultiLinearManipulator::ConstFixedIterator MultiLinearManipulator::FixedBegin() const
{
return m_fixedAxes.cbegin();
}
inline MultiLinearManipulator::ConstFixedIterator MultiLinearManipulator::FixedEnd() const
{
return m_fixedAxes.cend();
}
} // namespace AzToolsFramework
@@ -0,0 +1,274 @@
/*
* 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 "PlanarManipulator.h"
#include <AzFramework/Entity/EntityDebugDisplayBus.h>
#include <AzToolsFramework/Manipulators/ManipulatorDebug.h>
#include <AzToolsFramework/Manipulators/ManipulatorSnapping.h>
#include <AzToolsFramework/Manipulators/ManipulatorView.h>
#include <AzToolsFramework/Maths/TransformUtils.h>
#include <AzToolsFramework/ViewportSelection/EditorSelectionUtil.h>
namespace AzToolsFramework
{
PlanarManipulator::StartInternal PlanarManipulator::CalculateManipulationDataStart(
const Fixed& fixed, const AZ::Transform& worldFromLocal, const AZ::Transform& localTransform,
const GridSnapAction& gridSnapAction, const ViewportInteraction::MouseInteraction& interaction,
const float intersectionDistance)
{
const ManipulatorInteraction manipulatorInteraction =
BuildManipulatorInteraction(
worldFromLocal, interaction.m_mousePick.m_rayOrigin, interaction.m_mousePick.m_rayDirection);
const AZ::Vector3 normal = TransformDirectionNoScaling(localTransform, fixed.m_normal);
const AZ::Vector3 axis1 = TransformDirectionNoScaling(localTransform, fixed.m_axis1);
const AZ::Vector3 axis2 = TransformDirectionNoScaling(localTransform, fixed.m_axis2);
// initial intersect point
const AZ::Vector3 localIntersectionPoint =
manipulatorInteraction.m_localRayOrigin + manipulatorInteraction.m_localRayDirection * intersectionDistance;
StartInternal startInternal;
Internal::CalculateRayPlaneIntersectingPoint(
manipulatorInteraction.m_localRayOrigin, manipulatorInteraction.m_localRayDirection,
localIntersectionPoint, normal, startInternal.m_localHitPosition);
const float scaleRecip = manipulatorInteraction.m_scaleReciprocal;
const float gridSize = gridSnapAction.m_gridSnapParams.m_gridSize;
const bool snapping = gridSnapAction.m_gridSnapParams.m_gridSnap;
// calculate amount to snap to align with grid
const AZ::Vector3 snapOffset = snapping && !gridSnapAction.m_localSnapping
? CalculateSnappedOffset(localTransform.GetTranslation(), axis1, gridSize * scaleRecip) +
CalculateSnappedOffset(localTransform.GetTranslation(), axis2, gridSize * scaleRecip)
: AZ::Vector3::CreateZero();
startInternal.m_snapOffset = snapOffset;
startInternal.m_localPosition = localTransform.GetTranslation() + snapOffset;
return startInternal;
}
PlanarManipulator::Action PlanarManipulator::CalculateManipulationDataAction(
const Fixed& fixed, const StartInternal& startInternal, const AZ::Transform& worldFromLocal,
const AZ::Transform& localTransform, const GridSnapAction& gridSnapAction,
const ViewportInteraction::MouseInteraction& interaction)
{
const ManipulatorInteraction manipulatorInteraction =
BuildManipulatorInteraction(
worldFromLocal, interaction.m_mousePick.m_rayOrigin, interaction.m_mousePick.m_rayDirection);
const AZ::Vector3 normal = TransformDirectionNoScaling(localTransform, fixed.m_normal);
// as CalculateRayPlaneIntersectingPoint may fail, ensure localHitPosition is initialized with
// the starting hit position so the manipulator returns to the original location it was pressed
// if an invalid ray intersection is attempted
AZ::Vector3 localHitPosition = startInternal.m_localHitPosition;
Internal::CalculateRayPlaneIntersectingPoint(
manipulatorInteraction.m_localRayOrigin, manipulatorInteraction.m_localRayDirection,
startInternal.m_localHitPosition, normal, localHitPosition);
localHitPosition = Internal::TryConstrainHitPositionToView(
localHitPosition, startInternal.m_localHitPosition, worldFromLocal.GetInverse(),
GetCameraState(interaction.m_interactionId.m_viewportId));
const AZ::Vector3 axis1 = TransformDirectionNoScaling(localTransform, fixed.m_axis1);
const AZ::Vector3 axis2 = TransformDirectionNoScaling(localTransform, fixed.m_axis2);
const AZ::Vector3 hitDelta = (localHitPosition - startInternal.m_localHitPosition);
const AZ::Vector3 unsnappedOffset = axis1.Dot(hitDelta) * axis1 + axis2.Dot(hitDelta) * axis2;
const float scaleRecip = manipulatorInteraction.m_scaleReciprocal;
const float gridSize = gridSnapAction.m_gridSnapParams.m_gridSize;
const bool snapping = gridSnapAction.m_gridSnapParams.m_gridSnap;
Action action;
action.m_fixed = fixed;
action.m_start.m_localPosition = startInternal.m_localPosition;
action.m_start.m_snapOffset = startInternal.m_snapOffset;
action.m_start.m_localHitPosition = startInternal.m_localHitPosition;
action.m_current.m_localOffset = snapping
? unsnappedOffset +
CalculateSnappedOffset(unsnappedOffset, axis1, gridSize * scaleRecip) +
CalculateSnappedOffset(unsnappedOffset, axis2, gridSize * scaleRecip)
: unsnappedOffset;
// record what modifier keys are held during this action
action.m_modifiers = interaction.m_keyboardModifiers;
return action;
}
AZStd::shared_ptr<PlanarManipulator> PlanarManipulator::MakeShared(const AZ::Transform& worldFromLocal)
{
return AZStd::shared_ptr<PlanarManipulator>(aznew PlanarManipulator(worldFromLocal));
}
PlanarManipulator::PlanarManipulator(const AZ::Transform& worldFromLocal)
: m_worldFromLocal(worldFromLocal)
{
AttachLeftMouseDownImpl();
}
void PlanarManipulator::InstallLeftMouseDownCallback(const MouseActionCallback& onMouseDownCallback)
{
m_onLeftMouseDownCallback = onMouseDownCallback;
}
void PlanarManipulator::InstallLeftMouseUpCallback(const MouseActionCallback& onMouseUpCallback)
{
m_onLeftMouseUpCallback = onMouseUpCallback;
}
void PlanarManipulator::InstallMouseMoveCallback(const MouseActionCallback& onMouseMoveCallback)
{
m_onMouseMoveCallback = onMouseMoveCallback;
}
void PlanarManipulator::OnLeftMouseDownImpl(
const ViewportInteraction::MouseInteraction& interaction, const float rayIntersectionDistance)
{
const AZ::Transform worldFromLocalUniformScale = TransformUniformScale(m_worldFromLocal);
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
m_startInternal = CalculateManipulationDataStart(
m_fixed, worldFromLocalUniformScale, TransformNormalizedScale(m_localTransform),
GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt()),
interaction, rayIntersectionDistance);
if (m_onLeftMouseDownCallback)
{
m_onLeftMouseDownCallback(CalculateManipulationDataAction(
m_fixed, m_startInternal, worldFromLocalUniformScale, TransformNormalizedScale(m_localTransform),
GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt()), interaction));
}
}
void PlanarManipulator::OnMouseMoveImpl(const ViewportInteraction::MouseInteraction& interaction)
{
if (m_onMouseMoveCallback)
{
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
m_onMouseMoveCallback(CalculateManipulationDataAction(
m_fixed, m_startInternal, TransformUniformScale(m_worldFromLocal),
TransformNormalizedScale(m_localTransform),
GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt()), interaction));
}
}
void PlanarManipulator::OnLeftMouseUpImpl(const ViewportInteraction::MouseInteraction& interaction)
{
if (m_onLeftMouseUpCallback)
{
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
m_onLeftMouseUpCallback(CalculateManipulationDataAction(
m_fixed, m_startInternal, TransformUniformScale(m_worldFromLocal),
TransformNormalizedScale(m_localTransform),
GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt()), interaction));
}
}
void PlanarManipulator::Draw(
const ManipulatorManagerState& managerState,
AzFramework::DebugDisplayRequests& debugDisplay,
const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction)
{
if (cl_manipulatorDrawDebug)
{
if (PerformingAction())
{
const GridSnapParameters gridSnapParams = GridSnapSettings(mouseInteraction.m_interactionId.m_viewportId);
const auto action = CalculateManipulationDataAction(
m_fixed, m_startInternal, TransformUniformScale(m_worldFromLocal),
TransformNormalizedScale(m_localTransform),
GridSnapAction(gridSnapParams, mouseInteraction.m_keyboardModifiers.Alt()), mouseInteraction);
// display the exact hit (ray intersection) of the mouse pick on the manipulator
DrawTransformAxes(
debugDisplay, TransformUniformScale(m_worldFromLocal) *
AZ::Transform::CreateTranslation(
action.m_start.m_localHitPosition + action.m_current.m_localOffset));
}
const AZ::Transform combined = m_worldFromLocal * m_localTransform;
DrawTransformAxes(debugDisplay, combined);
DrawAxis(
debugDisplay, combined.GetTranslation(),
TransformDirectionNoScaling(m_localTransform, m_fixed.m_axis1));
DrawAxis(
debugDisplay, combined.GetTranslation(),
TransformDirectionNoScaling(m_localTransform, m_fixed.m_axis2));
}
for (auto& view : m_manipulatorViews)
{
view->Draw(
GetManipulatorManagerId(), managerState,
GetManipulatorId(), {
m_worldFromLocal * m_localTransform,
AZ::Vector3::CreateZero(), MouseOver()
},
debugDisplay, cameraState, mouseInteraction);
}
}
void PlanarManipulator::SetAxes(const AZ::Vector3& axis1, const AZ::Vector3& axis2)
{
m_fixed.m_axis1 = axis1;
m_fixed.m_axis2 = axis2;
m_fixed.m_normal = axis1.Cross(axis2);
}
void PlanarManipulator::SetSpace(const AZ::Transform& worldFromLocal)
{
m_worldFromLocal = worldFromLocal;
}
void PlanarManipulator::SetLocalTransform(const AZ::Transform& localTransform)
{
m_localTransform = localTransform;
}
void PlanarManipulator::SetLocalPosition(const AZ::Vector3& localPosition)
{
m_localTransform.SetTranslation(localPosition);
}
void PlanarManipulator::SetLocalOrientation(const AZ::Quaternion& localOrientation)
{
m_localTransform = AZ::Transform::CreateFromQuaternionAndTranslation(
localOrientation, m_localTransform.GetTranslation());
}
void PlanarManipulator::InvalidateImpl()
{
for (auto& view : m_manipulatorViews)
{
view->Invalidate(GetManipulatorManagerId());
}
}
void PlanarManipulator::SetBoundsDirtyImpl()
{
for (auto& view : m_manipulatorViews)
{
view->SetBoundDirty(GetManipulatorManagerId());
}
}
} // namespace AzToolsFramework
@@ -0,0 +1,152 @@
/*
* 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.
*
*/
#pragma once
#include "BaseManipulator.h"
#include <AzCore/Memory/SystemAllocator.h>
#include <AzToolsFramework/Manipulators/ManipulatorView.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
namespace AzToolsFramework
{
class ManipulatorView;
struct GridSnapAction;
/// PlanarManipulator serves as a visual tool for users to modify values
/// in two dimension in a plane defined two non-collinear axes in 3D space.
class PlanarManipulator
: public BaseManipulator
{
/// Private constructor.
explicit PlanarManipulator(const AZ::Transform& worldFromLocal);
public:
AZ_RTTI(PlanarManipulator, "{2B1C2140-F3B1-4DB2-B066-156B67B57B97}", BaseManipulator)
AZ_CLASS_ALLOCATOR(PlanarManipulator, AZ::SystemAllocator, 0)
PlanarManipulator() = delete;
PlanarManipulator(const PlanarManipulator&) = delete;
PlanarManipulator& operator=(const PlanarManipulator&) = delete;
~PlanarManipulator() = default;
/// A Manipulator must only be created and managed through a shared_ptr.
static AZStd::shared_ptr<PlanarManipulator> MakeShared(const AZ::Transform& worldFromLocal);
/// Unchanging data set once for the planar manipulator.
struct Fixed
{
AZ::Vector3 m_axis1 = AZ::Vector3::CreateAxisX(); ///< m_axis1 and m_axis2 have to be orthogonal, they together define a plane in 3d space.
AZ::Vector3 m_axis2 = AZ::Vector3::CreateAxisY();
AZ::Vector3 m_normal = AZ::Vector3::CreateAxisZ(); ///< m_normal is calculated automatically when setting the axes.
};
/// The state of the manipulator at the start of an interaction.
struct Start
{
AZ::Vector3 m_localPosition; ///< The current position of the manipulator in local space.
AZ::Vector3 m_localHitPosition; ///< The intersection point in local space between the ray and the manipulator when the mouse down event happens.
AZ::Vector3 m_snapOffset; ///< The snap offset amount to ensure manipulator is aligned to the grid.
};
/// The state of the manipulator during an interaction.
struct Current
{
AZ::Vector3 m_localOffset; ///< The current position of the manipulator in local space.
};
/// Mouse action data used by MouseActionCallback (wraps Start and Current manipulator state).
struct Action
{
Fixed m_fixed;
Start m_start;
Current m_current;
ViewportInteraction::KeyboardModifiers m_modifiers;
AZ::Vector3 LocalPosition() const { return m_start.m_localPosition + m_current.m_localOffset; }
AZ::Vector3 LocalPositionOffset() const { return m_current.m_localOffset; }
};
/// This is the function signature of callbacks that will be invoked whenever a manipulator
/// is being clicked on or dragged.
using MouseActionCallback = AZStd::function<void(const Action&)>;
void InstallLeftMouseDownCallback(const MouseActionCallback& onMouseDownCallback);
void InstallMouseMoveCallback(const MouseActionCallback& onMouseMoveCallback);
void InstallLeftMouseUpCallback(const MouseActionCallback& onMouseUpCallback);
void Draw(
const ManipulatorManagerState& managerState,
AzFramework::DebugDisplayRequests& debugDisplay,
const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction) override;
/// Ensure @param axis1 and @param axis2 are not collinear.
void SetAxes(const AZ::Vector3& axis1, const AZ::Vector3& axis2);
void SetSpace(const AZ::Transform& worldFromLocal);
void SetLocalTransform(const AZ::Transform& localTransform);
void SetLocalPosition(const AZ::Vector3& localPosition);
void SetLocalOrientation(const AZ::Quaternion& localOrientation);
const AZ::Vector3& GetAxis1() const { return m_fixed.m_axis1; }
const AZ::Vector3& GetAxis2() const { return m_fixed.m_axis2; }
AZ::Vector3 GetPosition() const { return m_localTransform.GetTranslation(); }
template<typename Views>
void SetViews(Views&& views)
{
m_manipulatorViews = AZStd::forward<Views>(views);
}
private:
void OnLeftMouseDownImpl(
const ViewportInteraction::MouseInteraction& interaction, float rayIntersectionDistance) override;
void OnLeftMouseUpImpl(
const ViewportInteraction::MouseInteraction& interaction) override;
void OnMouseMoveImpl(
const ViewportInteraction::MouseInteraction& interaction) override;
void InvalidateImpl() override;
void SetBoundsDirtyImpl() override;
/// Initial data recorded when a press first happens with a planar manipulator.
struct StartInternal
{
AZ::Vector3 m_localPosition; ///< The starting position of the manipulator in local space.
AZ::Vector3 m_localHitPosition; ///< The intersection point in world space between the ray and the manipulator when the mouse down event happens.
AZ::Vector3 m_snapOffset; ///< The snap offset amount to ensure manipulator is aligned to the grid.
};
AZ::Transform m_localTransform = AZ::Transform::CreateIdentity(); ///< Local transform of the manipulator.
AZ::Transform m_worldFromLocal = AZ::Transform::CreateIdentity(); ///< Space the manipulator is in (identity is world space).
Fixed m_fixed;
StartInternal m_startInternal;
MouseActionCallback m_onLeftMouseDownCallback = nullptr;
MouseActionCallback m_onLeftMouseUpCallback = nullptr;
MouseActionCallback m_onMouseMoveCallback = nullptr;
ManipulatorViews m_manipulatorViews; ///< Look of manipulator.
static StartInternal CalculateManipulationDataStart(
const Fixed& fixed, const AZ::Transform& worldFromLocal, const AZ::Transform& localTransform,
const GridSnapAction& gridSnapAction, const ViewportInteraction::MouseInteraction& interaction,
float intersectionDistance);
static Action CalculateManipulationDataAction(
const Fixed& fixed, const StartInternal& startInternal, const AZ::Transform& worldFromLocal,
const AZ::Transform& localTransform, const GridSnapAction& gridSnapAction,
const ViewportInteraction::MouseInteraction& interaction);
};
} // namespace AzToolsFramework
@@ -0,0 +1,180 @@
/*
* 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 "RotationManipulators.h"
#include <AzToolsFramework/Manipulators/ManipulatorView.h>
namespace AzToolsFramework
{
RotationManipulators::RotationManipulators(const AZ::Transform& worldFromLocal)
{
for (size_t manipulatorIndex = 0; manipulatorIndex < m_localAngularManipulators.size(); ++manipulatorIndex)
{
m_localAngularManipulators[manipulatorIndex] = AngularManipulator::MakeShared(worldFromLocal);
}
m_viewAngularManipulator = AngularManipulator::MakeShared(worldFromLocal);
m_space = worldFromLocal;
}
void RotationManipulators::InstallLeftMouseDownCallback(
const AngularManipulator::MouseActionCallback& onMouseDownCallback)
{
for (AZStd::shared_ptr<AngularManipulator>& manipulator : m_localAngularManipulators)
{
manipulator->InstallLeftMouseDownCallback(onMouseDownCallback);
}
m_viewAngularManipulator->InstallLeftMouseDownCallback(onMouseDownCallback);
}
void RotationManipulators::InstallMouseMoveCallback(
const AngularManipulator::MouseActionCallback& onMouseMoveCallback)
{
for (AZStd::shared_ptr<AngularManipulator>& manipulator : m_localAngularManipulators)
{
manipulator->InstallMouseMoveCallback(onMouseMoveCallback);
}
m_viewAngularManipulator->InstallMouseMoveCallback(onMouseMoveCallback);
}
void RotationManipulators::InstallLeftMouseUpCallback(
const AngularManipulator::MouseActionCallback& onMouseUpCallback)
{
for (AZStd::shared_ptr<AngularManipulator>& manipulator : m_localAngularManipulators)
{
manipulator->InstallLeftMouseUpCallback(onMouseUpCallback);
}
m_viewAngularManipulator->InstallLeftMouseUpCallback(onMouseUpCallback);
}
void RotationManipulators::SetLocalTransform(const AZ::Transform& localTransform)
{
for (AZStd::shared_ptr<AngularManipulator>& manipulator : m_localAngularManipulators)
{
manipulator->SetLocalTransform(localTransform);
}
m_viewAngularManipulator->SetLocalTransform(localTransform);
m_localTransform = localTransform;
}
void RotationManipulators::SetLocalPosition(const AZ::Vector3& localPosition)
{
for (AZStd::shared_ptr<AngularManipulator>& manipulator : m_localAngularManipulators)
{
manipulator->SetLocalPosition(localPosition);
}
m_viewAngularManipulator->SetLocalPosition(localPosition);
m_localTransform.SetTranslation(localPosition);
}
void RotationManipulators::SetLocalOrientation(const AZ::Quaternion& localOrientation)
{
for (AZStd::shared_ptr<AngularManipulator>& manipulator : m_localAngularManipulators)
{
manipulator->SetLocalOrientation(localOrientation);
}
m_viewAngularManipulator->SetLocalOrientation(localOrientation);
m_localTransform = AZ::Transform::CreateFromQuaternionAndTranslation(
localOrientation, m_localTransform.GetTranslation());
}
void RotationManipulators::RefreshView(const AZ::Vector3& worldViewPosition)
{
if (!PerformingActionViewAxis())
{
SetViewAxis(CalculateViewDirection(*this, worldViewPosition));
}
}
void RotationManipulators::SetSpace(const AZ::Transform& worldFromLocal)
{
for (AZStd::shared_ptr<AngularManipulator>& manipulator : m_localAngularManipulators)
{
manipulator->SetSpace(worldFromLocal);
}
m_viewAngularManipulator->SetSpace(worldFromLocal);
m_space = worldFromLocal;
}
void RotationManipulators::SetLocalAxes(
const AZ::Vector3& axis1, const AZ::Vector3& axis2, const AZ::Vector3& axis3)
{
const AZ::Vector3 axes[] = { axis1, axis2, axis3 };
for (size_t manipulatorIndex = 0; manipulatorIndex < m_localAngularManipulators.size(); ++manipulatorIndex)
{
m_localAngularManipulators[manipulatorIndex]->SetAxis(axes[manipulatorIndex]);
}
}
void RotationManipulators::SetViewAxis(const AZ::Vector3& axis)
{
m_viewAngularManipulator->SetAxis(axis);
if (auto circleView = azrtti_cast<ManipulatorViewCircle*>(
m_viewAngularManipulator->GetView()))
{
circleView->m_axis = axis;
}
}
void RotationManipulators::ConfigureView(
const float radius, const AZ::Color& axis1Color,
const AZ::Color& axis2Color, const AZ::Color& axis3Color)
{
const AZ::Color colors[] = {
axis1Color, axis2Color, axis3Color
};
for (size_t manipulatorIndex = 0; manipulatorIndex < m_localAngularManipulators.size(); ++manipulatorIndex)
{
m_localAngularManipulators[manipulatorIndex]->SetView(
CreateManipulatorViewCircle(
*m_localAngularManipulators[manipulatorIndex], colors[manipulatorIndex],
radius, 0.05f, DrawHalfDottedCircle));
}
m_viewAngularManipulator->SetView(
CreateManipulatorViewCircle(
*m_viewAngularManipulator,
AZ::Color(1.0f, 1.0f, 1.0f, 1.0f),
radius + (radius * 0.12f), 0.05f, DrawFullCircle));
}
bool RotationManipulators::PerformingActionViewAxis() const
{
return m_viewAngularManipulator->PerformingAction();
}
void RotationManipulators::ProcessManipulators(const AZStd::function<void(BaseManipulator*)>& manipulatorFn)
{
for (AZStd::shared_ptr<AngularManipulator>& manipulator : m_localAngularManipulators)
{
manipulatorFn(manipulator.get());
}
manipulatorFn(m_viewAngularManipulator.get());
}
} // namespace AzToolsFramework
@@ -0,0 +1,60 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Memory/SystemAllocator.h>
#include <AzToolsFramework/Manipulators/AngularManipulator.h>
#include <AzToolsFramework/API/EditorCameraBus.h>
namespace AzToolsFramework
{
/// RotationManipulators is an aggregation of 3 angular manipulators who share the same origin
/// in addition to a view aligned angular manipulator (facing the camera).
class RotationManipulators
: public Manipulators
{
public:
AZ_RTTI(RotationManipulators, "{5D1F1D47-1D5B-4E42-B47E-23F108F8BF7D}")
AZ_CLASS_ALLOCATOR(RotationManipulators, AZ::SystemAllocator, 0)
explicit RotationManipulators(const AZ::Transform& worldFromLocal);
~RotationManipulators() = default;
void InstallLeftMouseDownCallback(const AngularManipulator::MouseActionCallback& onMouseDownCallback);
void InstallLeftMouseUpCallback(const AngularManipulator::MouseActionCallback& onMouseUpCallback);
void InstallMouseMoveCallback(const AngularManipulator::MouseActionCallback& onMouseMoveCallback);
void SetSpace(const AZ::Transform& worldFromLocal) override;
void SetLocalTransform(const AZ::Transform& localTransform) override;
void SetLocalPosition(const AZ::Vector3& localPosition) override;
void SetLocalOrientation(const AZ::Quaternion& localOrientation) override;
void RefreshView(const AZ::Vector3& worldViewPosition) override;
void SetLocalAxes(
const AZ::Vector3& axis1, const AZ::Vector3& axis2, const AZ::Vector3& axis3);
void SetViewAxis(const AZ::Vector3& axis);
void ConfigureView(
float radius, const AZ::Color& axis1Color, const AZ::Color& axis2Color, const AZ::Color& axis3Color);
bool PerformingActionViewAxis() const;
private:
AZ_DISABLE_COPY_MOVE(RotationManipulators)
void ProcessManipulators(const AZStd::function<void(BaseManipulator*)>&) override;
AZStd::array<AZStd::shared_ptr<AngularManipulator>, 3> m_localAngularManipulators;
AZStd::shared_ptr<AngularManipulator> m_viewAngularManipulator;
};
} // namespace AzToolsFramework
@@ -0,0 +1,181 @@
/*
* 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 "ScaleManipulators.h"
#include <AzToolsFramework/Maths/TransformUtils.h>
namespace AzToolsFramework
{
ScaleManipulators::ScaleManipulators(const AZ::Transform& worldFromLocal)
{
for (size_t manipulatorIndex = 0; manipulatorIndex < m_axisScaleManipulators.size(); ++manipulatorIndex)
{
m_axisScaleManipulators[manipulatorIndex] = LinearManipulator::MakeShared(worldFromLocal);
}
m_uniformScaleManipulator = LinearManipulator::MakeShared(worldFromLocal);
m_space = worldFromLocal;
}
void ScaleManipulators::InstallAxisLeftMouseDownCallback(
const LinearManipulator::MouseActionCallback& onMouseDownCallback)
{
for (AZStd::shared_ptr<LinearManipulator>& manipulator : m_axisScaleManipulators)
{
manipulator->InstallLeftMouseDownCallback(onMouseDownCallback);
}
}
void ScaleManipulators::InstallAxisMouseMoveCallback(
const LinearManipulator::MouseActionCallback& onMouseMoveCallback)
{
for (AZStd::shared_ptr<LinearManipulator>& manipulator : m_axisScaleManipulators)
{
manipulator->InstallMouseMoveCallback(onMouseMoveCallback);
}
}
void ScaleManipulators::InstallAxisLeftMouseUpCallback(
const LinearManipulator::MouseActionCallback& onMouseUpCallback)
{
for (AZStd::shared_ptr<LinearManipulator>& manipulator : m_axisScaleManipulators)
{
manipulator->InstallLeftMouseUpCallback(onMouseUpCallback);
}
}
void ScaleManipulators::InstallUniformLeftMouseDownCallback(
const LinearManipulator::MouseActionCallback& onMouseDownCallback)
{
m_uniformScaleManipulator->InstallLeftMouseDownCallback(onMouseDownCallback);
}
void ScaleManipulators::InstallUniformMouseMoveCallback(
const LinearManipulator::MouseActionCallback& onMouseMoveCallback)
{
m_uniformScaleManipulator->InstallMouseMoveCallback(onMouseMoveCallback);
}
void ScaleManipulators::InstallUniformLeftMouseUpCallback(
const LinearManipulator::MouseActionCallback& onMouseUpCallback)
{
m_uniformScaleManipulator->InstallLeftMouseUpCallback(onMouseUpCallback);
}
void ScaleManipulators::SetLocalTransform(const AZ::Transform& localTransform)
{
for (AZStd::shared_ptr<LinearManipulator>& manipulator : m_axisScaleManipulators)
{
manipulator->SetLocalTransform(localTransform);
}
m_uniformScaleManipulator->SetVisualOrientationOverride(
QuaternionFromTransformNoScaling(localTransform));
m_uniformScaleManipulator->SetLocalTransform(
AZ::Transform::CreateTranslation(localTransform.GetTranslation()) *
AZ::Transform::CreateScale(localTransform.GetScale()));
m_localTransform = localTransform;
}
void ScaleManipulators::SetLocalPosition(const AZ::Vector3& localPosition)
{
for (AZStd::shared_ptr<LinearManipulator>& manipulator : m_axisScaleManipulators)
{
manipulator->SetLocalPosition(localPosition);
}
m_uniformScaleManipulator->SetLocalPosition(localPosition);
m_localTransform.SetTranslation(localPosition);
}
void ScaleManipulators::SetLocalOrientation(const AZ::Quaternion& localOrientation)
{
for (AZStd::shared_ptr<LinearManipulator>& manipulator : m_axisScaleManipulators)
{
manipulator->SetLocalOrientation(localOrientation);
}
m_localTransform = AZ::Transform::CreateFromQuaternionAndTranslation(
localOrientation, m_localTransform.GetTranslation());
}
void ScaleManipulators::SetSpace(const AZ::Transform& worldFromLocal)
{
for (AZStd::shared_ptr<LinearManipulator>& manipulator : m_axisScaleManipulators)
{
manipulator->SetSpace(worldFromLocal);
}
m_uniformScaleManipulator->SetSpace(worldFromLocal);
m_space = worldFromLocal;
}
void ScaleManipulators::SetAxes(
const AZ::Vector3& axis1, const AZ::Vector3& axis2, const AZ::Vector3& axis3)
{
AZ::Vector3 axes[] = { axis1, axis2, axis3 };
for (size_t manipulatorIndex = 0; manipulatorIndex < m_axisScaleManipulators.size(); ++manipulatorIndex)
{
m_axisScaleManipulators[manipulatorIndex]->SetAxis(axes[manipulatorIndex]);
}
// uniform scale manipulator uses Z axis for scaling (always in world space)
m_uniformScaleManipulator->SetAxis(AZ::Vector3::CreateAxisZ());
m_uniformScaleManipulator->UseVisualOrientationOverride(true);
}
void ScaleManipulators::ConfigureView(
const float axisLength, const AZ::Color& axis1Color,
const AZ::Color& axis2Color, const AZ::Color& axis3Color)
{
const float boxSize = 0.1f;
const float lineWidth = 0.05f;
const AZ::Color colors[] = {
axis1Color, axis2Color, axis3Color
};
for (size_t manipulatorIndex = 0; manipulatorIndex < m_axisScaleManipulators.size(); ++manipulatorIndex)
{
ManipulatorViews views;
views.emplace_back(CreateManipulatorViewLine(
*m_axisScaleManipulators[manipulatorIndex], colors[manipulatorIndex], axisLength, lineWidth));
views.emplace_back(CreateManipulatorViewBox(
AZ::Transform::CreateIdentity(), colors[manipulatorIndex],
m_axisScaleManipulators[manipulatorIndex]->GetAxis() * (axisLength - boxSize),
AZ::Vector3(boxSize)));
m_axisScaleManipulators[manipulatorIndex]->SetViews(AZStd::move(views));
}
ManipulatorViews views;
views.emplace_back(CreateManipulatorViewBox(
AZ::Transform::CreateIdentity(), AZ::Color::CreateOne(),
AZ::Vector3::CreateZero(), AZ::Vector3(boxSize)));
m_uniformScaleManipulator->SetViews(AZStd::move(views));
}
void ScaleManipulators::ProcessManipulators(const AZStd::function<void(BaseManipulator*)>& manipulatorFn)
{
for (AZStd::shared_ptr<LinearManipulator>& manipulator : m_axisScaleManipulators)
{
manipulatorFn(manipulator.get());
}
manipulatorFn(m_uniformScaleManipulator.get());
}
}
@@ -0,0 +1,65 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Memory/SystemAllocator.h>
#include <AzToolsFramework/Manipulators/LinearManipulator.h>
namespace AzToolsFramework
{
/// ScaleManipulators is an aggregation of 3 linear manipulators for each basis axis who share
/// the same transform, and a single linear manipulator at the center of the transform whose
/// axis is world up (z).
class ScaleManipulators
: public Manipulators
{
public:
AZ_RTTI(ScaleManipulators, "{C6350CE0-7B7A-46F8-B65F-D4A54DD9A7D9}")
AZ_CLASS_ALLOCATOR(ScaleManipulators, AZ::SystemAllocator, 0)
explicit ScaleManipulators(const AZ::Transform& worldFromLocal);
void InstallAxisLeftMouseDownCallback(const LinearManipulator::MouseActionCallback& onMouseDownCallback);
void InstallAxisMouseMoveCallback(const LinearManipulator::MouseActionCallback& onMouseMoveCallback);
void InstallAxisLeftMouseUpCallback(const LinearManipulator::MouseActionCallback& onMouseUpCallback);
void InstallUniformLeftMouseDownCallback(const LinearManipulator::MouseActionCallback& onMouseDownCallback);
void InstallUniformMouseMoveCallback(const LinearManipulator::MouseActionCallback& onMouseMoveCallback);
void InstallUniformLeftMouseUpCallback(const LinearManipulator::MouseActionCallback& onMouseUpCallback);
void SetSpace(const AZ::Transform& worldFromLocal) override;
void SetLocalTransform(const AZ::Transform& localTransform) override;
void SetLocalPosition(const AZ::Vector3& localPosition) override;
void SetLocalOrientation(const AZ::Quaternion& localOrientation) override;
void SetAxes(
const AZ::Vector3& axis1,
const AZ::Vector3& axis2,
const AZ::Vector3& axis3);
void ConfigureView(
float axisLength,
const AZ::Color& axis1Color,
const AZ::Color& axis2Color,
const AZ::Color& axis3Color);
private:
AZ_DISABLE_COPY_MOVE(ScaleManipulators)
// Manipulators
void ProcessManipulators(const AZStd::function<void(BaseManipulator*)>&) override;
AZStd::array<AZStd::shared_ptr<LinearManipulator>, 3> m_axisScaleManipulators;
AZStd::shared_ptr<LinearManipulator> m_uniformScaleManipulator;
};
} // namespace AzToolsFramework
@@ -0,0 +1,118 @@
/*
* 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 "SelectionManipulator.h"
#include <AzFramework/Entity/EntityDebugDisplayBus.h>
namespace AzToolsFramework
{
AZStd::shared_ptr<SelectionManipulator> SelectionManipulator::MakeShared(const AZ::Transform& worldFromLocal)
{
return AZStd::shared_ptr<SelectionManipulator>(aznew SelectionManipulator(worldFromLocal));
}
SelectionManipulator::SelectionManipulator(const AZ::Transform& worldFromLocal)
: m_worldFromLocal(worldFromLocal)
{
AttachLeftMouseDownImpl();
AttachRightMouseDownImpl();
}
void SelectionManipulator::InstallLeftMouseDownCallback(const MouseActionCallback& onMouseDownCallback)
{
m_onLeftMouseDownCallback = onMouseDownCallback;
}
void SelectionManipulator::InstallLeftMouseUpCallback(const MouseActionCallback& onMouseUpCallback)
{
m_onLeftMouseUpCallback = onMouseUpCallback;
}
void SelectionManipulator::InstallRightMouseDownCallback(const MouseActionCallback& onMouseDownCallback)
{
m_onRightMouseDownCallback = onMouseDownCallback;
}
void SelectionManipulator::InstallRightMouseUpCallback(const MouseActionCallback& onMouseUpCallback)
{
m_onRightMouseUpCallback = onMouseUpCallback;
}
void SelectionManipulator::OnLeftMouseDownImpl(
const ViewportInteraction::MouseInteraction& interaction, float /*rayIntersectionDistance*/)
{
if (m_onLeftMouseDownCallback)
{
m_onLeftMouseDownCallback(interaction);
}
}
void SelectionManipulator::OnLeftMouseUpImpl(const ViewportInteraction::MouseInteraction& interaction)
{
if (MouseOver() && m_onLeftMouseUpCallback)
{
m_onLeftMouseUpCallback(interaction);
}
}
void SelectionManipulator::OnRightMouseDownImpl(
const ViewportInteraction::MouseInteraction& interaction, float /*rayIntersectionDistance*/)
{
if (m_onRightMouseDownCallback)
{
m_onRightMouseDownCallback(interaction);
}
}
void SelectionManipulator::OnRightMouseUpImpl(const ViewportInteraction::MouseInteraction& interaction)
{
if (m_onRightMouseUpCallback)
{
m_onRightMouseUpCallback(interaction);
}
}
void SelectionManipulator::Draw(
const ManipulatorManagerState& managerState,
AzFramework::DebugDisplayRequests& debugDisplay,
const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction)
{
for (auto& view : m_manipulatorViews)
{
view->Draw(
GetManipulatorManagerId(), managerState,
GetManipulatorId(), {
TransformUniformScale(m_worldFromLocal),
m_position, MouseOver()
},
debugDisplay, cameraState, mouseInteraction);
}
}
void SelectionManipulator::SetBoundsDirtyImpl()
{
for (auto& view : m_manipulatorViews)
{
view->SetBoundDirty(GetManipulatorManagerId());
}
}
void SelectionManipulator::InvalidateImpl()
{
for (auto& view : m_manipulatorViews)
{
view->Invalidate(GetManipulatorManagerId());
}
}
}
@@ -0,0 +1,100 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Memory/SystemAllocator.h>
#include <AzToolsFramework/Manipulators/BaseManipulator.h>
#include <AzToolsFramework/Manipulators/ManipulatorView.h>
namespace AzToolsFramework
{
class ManipulatorView;
/// Represents a sphere that can be clicked on to trigger a particular behavior
/// For example clicking a preview point to create a translation manipulator.
class SelectionManipulator
: public BaseManipulator
{
/// Private constructor.
explicit SelectionManipulator(const AZ::Transform& worldFromLocal);
public:
AZ_RTTI(SelectionManipulator, "{966F44B7-E287-4C28-9734-5958F1A13A1D}", BaseManipulator);
AZ_CLASS_ALLOCATOR(SelectionManipulator, AZ::SystemAllocator, 0);
SelectionManipulator() = delete;
SelectionManipulator(const SelectionManipulator&) = delete;
SelectionManipulator& operator=(const SelectionManipulator&) = delete;
~SelectionManipulator() = default;
/// A Manipulator must only be created and managed through a shared_ptr.
static AZStd::shared_ptr<SelectionManipulator> MakeShared(const AZ::Transform& worldFromLocal);
/// This is the function signature of callbacks that will be invoked
/// whenever a selection manipulator is clicked on.
using MouseActionCallback = AZStd::function<void(const ViewportInteraction::MouseInteraction&)>;
void InstallLeftMouseDownCallback(const MouseActionCallback& onMouseDownCallback);
void InstallLeftMouseUpCallback(const MouseActionCallback& onMouseUpCallback);
void InstallRightMouseDownCallback(const MouseActionCallback& onMouseDownCallback);
void InstallRightMouseUpCallback(const MouseActionCallback& onMouseUpCallback);
void Draw(
const ManipulatorManagerState& managerState,
AzFramework::DebugDisplayRequests& debugDisplay,
const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction) override;
void SetPosition(const AZ::Vector3& position) { m_position = position; }
void SetSpace(const AZ::Transform& worldFromLocal) { m_worldFromLocal = worldFromLocal; }
const AZ::Vector3& GetPosition() const { return m_position; }
bool Selected() const { return m_selected; }
void Select() { m_selected = true; }
void Deselect() { m_selected = false; }
void ToggleSelected() { m_selected = !m_selected; }
template<typename Views>
void SetViews(Views&& views)
{
m_manipulatorViews = AZStd::forward<Views>(views);
}
private:
void OnLeftMouseDownImpl(
const ViewportInteraction::MouseInteraction& interaction,
float rayIntersectionDistance) override;
void OnLeftMouseUpImpl(const ViewportInteraction::MouseInteraction& interaction) override;
void OnRightMouseDownImpl(
const ViewportInteraction::MouseInteraction& interaction,
float rayIntersectionDistance) override;
void OnRightMouseUpImpl(const ViewportInteraction::MouseInteraction& interaction) override;
void InvalidateImpl() override;
void SetBoundsDirtyImpl() override;
AZ::Vector3 m_position = AZ::Vector3::CreateZero(); ///< Position in local space.
AZ::Transform m_worldFromLocal = AZ::Transform::CreateIdentity(); ///< Space the manipulator is in (identity is world space).
bool m_selected = false;
MouseActionCallback m_onLeftMouseDownCallback = nullptr;
MouseActionCallback m_onLeftMouseUpCallback = nullptr;
MouseActionCallback m_onRightMouseDownCallback = nullptr;
MouseActionCallback m_onRightMouseUpCallback = nullptr;
ManipulatorViews m_manipulatorViews; ///< Look of manipulator.
};
} // namespace AzToolsFramework
@@ -0,0 +1,89 @@
/*
* 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 "SplineHoverSelection.h"
#include <AzCore/Component/ComponentBus.h>
#include <AzCore/Math/Spline.h>
#include <AzToolsFramework/Manipulators/EditorVertexSelection.h>
#include <AzToolsFramework/Manipulators/ManipulatorView.h>
#include <AzToolsFramework/Manipulators/SplineSelectionManipulator.h>
namespace AzToolsFramework
{
static const AZ::Color s_splineSelectManipulatorColor = AZ::Color(0.0f, 1.0f, 0.0f, 1.0f);
SplineHoverSelection::SplineHoverSelection(
const AZ::EntityComponentIdPair& entityComponentIdPair,
const ManipulatorManagerId managerId, const AZStd::shared_ptr<AZ::Spline>& spline)
{
m_splineSelectionManipulator = SplineSelectionManipulator::MakeShared();
m_splineSelectionManipulator->Register(managerId);
m_splineSelectionManipulator->AddEntityComponentIdPair(entityComponentIdPair);
m_splineSelectionManipulator->SetSpace(WorldFromLocalWithUniformScale(entityComponentIdPair.GetEntityId()));
const float splineWidth = 0.05f;
m_splineSelectionManipulator->SetSpline(spline);
m_splineSelectionManipulator->SetView(CreateManipulatorViewSplineSelect(
*m_splineSelectionManipulator, s_splineSelectManipulatorColor, splineWidth));
m_splineSelectionManipulator->InstallLeftMouseUpCallback(
[entityComponentIdPair](const SplineSelectionManipulator::Action& action)
{
InsertVertexAfter(
entityComponentIdPair, action.m_splineAddress.m_segmentIndex,
action.m_localSplineHitPosition);
});
}
SplineHoverSelection::~SplineHoverSelection()
{
m_splineSelectionManipulator->Unregister();
}
void SplineHoverSelection::Register(ManipulatorManagerId managerId)
{
if (m_splineSelectionManipulator)
{
m_splineSelectionManipulator->Register(managerId);
}
}
void SplineHoverSelection::Unregister()
{
if (m_splineSelectionManipulator)
{
m_splineSelectionManipulator->Unregister();
}
}
void SplineHoverSelection::SetBoundsDirty()
{
if (m_splineSelectionManipulator)
{
m_splineSelectionManipulator->SetBoundsDirty();
}
}
void SplineHoverSelection::Refresh()
{
// do nothing
}
void SplineHoverSelection::SetSpace(const AZ::Transform& worldFromLocal)
{
if (m_splineSelectionManipulator)
{
m_splineSelectionManipulator->SetSpace(worldFromLocal);
}
}
} // namespace AzToolsFramework
@@ -0,0 +1,50 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/std/smart_ptr/weak_ptr.h>
#include <AzToolsFramework/Manipulators/HoverSelection.h>
namespace AZ
{
class Spline;
class EntityComponentIdPair;
}
namespace AzToolsFramework
{
class SplineSelectionManipulator;
/// SplineHoverSelection is a concrete implementation of HoverSelection wrapping a Spline and
/// SplineManipulator. The underlying manipulators are used to control selection.
class SplineHoverSelection
: public HoverSelection
{
public:
explicit SplineHoverSelection(
const AZ::EntityComponentIdPair& entityComponentIdPair, ManipulatorManagerId managerId,
const AZStd::shared_ptr<AZ::Spline>& spline);
SplineHoverSelection(const SplineHoverSelection&) = delete;
SplineHoverSelection& operator=(const SplineHoverSelection&) = delete;
~SplineHoverSelection();
void Register(ManipulatorManagerId managerId) override;
void Unregister() override;
void SetBoundsDirty() override;
void Refresh() override;
void SetSpace(const AZ::Transform& worldFromLocal) override;
private:
AZStd::shared_ptr<SplineSelectionManipulator> m_splineSelectionManipulator; ///< Manipulator for adding points to spline.
};
} // namespace AzToolsFramework
@@ -0,0 +1,125 @@
/*
* 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 "SplineSelectionManipulator.h"
#include <AzFramework/Entity/EntityDebugDisplayBus.h>
#include <AzToolsFramework/Manipulators/ManipulatorView.h>
namespace AzToolsFramework
{
SplineSelectionManipulator::Action CalculateManipulationDataAction(
const AZ::Transform& worldFromLocal, const AZ::Vector3& rayOrigin,
const AZ::Vector3& rayDirection, const AZStd::weak_ptr<const AZ::Spline>& spline)
{
SplineSelectionManipulator::Action action;
if (const AZStd::shared_ptr<const AZ::Spline> splinePtr = spline.lock())
{
const auto splineQueryResult = IntersectSpline(worldFromLocal, rayOrigin, rayDirection, *splinePtr);
action.m_localSplineHitPosition = splinePtr->GetPosition(splineQueryResult.m_splineAddress);
action.m_splineAddress = splineQueryResult.m_splineAddress;
}
return action;
}
AZStd::shared_ptr<SplineSelectionManipulator> SplineSelectionManipulator::MakeShared()
{
return AZStd::shared_ptr<SplineSelectionManipulator>(aznew SplineSelectionManipulator());
}
SplineSelectionManipulator::SplineSelectionManipulator()
{
AttachLeftMouseDownImpl();
}
SplineSelectionManipulator::~SplineSelectionManipulator() = default;
void SplineSelectionManipulator::InstallLeftMouseDownCallback(const MouseActionCallback& onMouseDownCallback)
{
m_onLeftMouseDownCallback = onMouseDownCallback;
}
void SplineSelectionManipulator::InstallLeftMouseUpCallback(const MouseActionCallback& onMouseUpCallback)
{
m_onLeftMouseUpCallback = onMouseUpCallback;
}
void SplineSelectionManipulator::OnLeftMouseDownImpl(
const ViewportInteraction::MouseInteraction& interaction, float /*rayIntersectionDistance*/)
{
if (!interaction.m_keyboardModifiers.Ctrl())
{
return;
}
if (m_onLeftMouseDownCallback)
{
m_onLeftMouseDownCallback(CalculateManipulationDataAction(
TransformUniformScale(m_worldFromLocal),
interaction.m_mousePick.m_rayOrigin,
interaction.m_mousePick.m_rayDirection, m_spline));
}
}
void SplineSelectionManipulator::OnLeftMouseUpImpl(const ViewportInteraction::MouseInteraction& interaction)
{
if (MouseOver() && m_onLeftMouseUpCallback)
{
m_onLeftMouseUpCallback(CalculateManipulationDataAction(
TransformUniformScale(m_worldFromLocal),
interaction.m_mousePick.m_rayOrigin,
interaction.m_mousePick.m_rayDirection, m_spline));
}
}
void SplineSelectionManipulator::Draw(
const ManipulatorManagerState& managerState,
AzFramework::DebugDisplayRequests& debugDisplay,
const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction)
{
// if the ctrl modifier key state has changed - set out bounds to dirty and
// update the active state.
if (m_keyboardModifiers != mouseInteraction.m_keyboardModifiers)
{
SetBoundsDirty();
m_keyboardModifiers = mouseInteraction.m_keyboardModifiers;
}
if (mouseInteraction.m_keyboardModifiers.Ctrl() && !mouseInteraction.m_keyboardModifiers.Shift())
{
m_manipulatorView->Draw(
GetManipulatorManagerId(), managerState,
GetManipulatorId(), {
TransformUniformScale(m_worldFromLocal),
AZ::Vector3::CreateZero(), MouseOver()
},
debugDisplay, cameraState, mouseInteraction);
}
}
void SplineSelectionManipulator::SetView(AZStd::unique_ptr<ManipulatorView>&& view)
{
m_manipulatorView = AZStd::move(view);
}
void SplineSelectionManipulator::SetBoundsDirtyImpl()
{
m_manipulatorView->SetBoundDirty(GetManipulatorManagerId());
}
void SplineSelectionManipulator::InvalidateImpl()
{
m_manipulatorView->Invalidate(GetManipulatorManagerId());
}
}
@@ -0,0 +1,91 @@
/*
* 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.
*
*/
#pragma once
#include "BaseManipulator.h"
#include <AzCore/Math/Spline.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/std/smart_ptr/weak_ptr.h>
#include <AzToolsFramework/Viewport/ViewportTypes.h>
namespace AzToolsFramework
{
class ManipulatorView;
/// A manipulator to represent selection of a spline. Underlying spline data is
/// used to test mouse picking ray against to preview closest point on spline.
class SplineSelectionManipulator
: public BaseManipulator
{
/// Private constructor.
SplineSelectionManipulator();
public:
AZ_RTTI(SplineSelectionManipulator, "{3E6B2206-E910-48C9-BDB6-F45B539C00F4}", BaseManipulator);
AZ_CLASS_ALLOCATOR(SplineSelectionManipulator, AZ::SystemAllocator, 0);
SplineSelectionManipulator(const SplineSelectionManipulator&) = delete;
SplineSelectionManipulator& operator=(const SplineSelectionManipulator&) = delete;
~SplineSelectionManipulator();
/// A Manipulator must only be created and managed through a shared_ptr.
static AZStd::shared_ptr<SplineSelectionManipulator> MakeShared();
/// Mouse action data used by MouseActionCallback.
struct Action
{
AZ::Vector3 m_localSplineHitPosition;
AZ::SplineAddress m_splineAddress;
};
using MouseActionCallback = AZStd::function<void(const Action&)>;
void InstallLeftMouseDownCallback(const MouseActionCallback& onMouseDownCallback);
void InstallLeftMouseUpCallback(const MouseActionCallback& onMouseUpCallback);
void Draw(
const ManipulatorManagerState& managerState,
AzFramework::DebugDisplayRequests& debugDisplay,
const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction) override;
void SetSpace(const AZ::Transform& worldFromLocal) { m_worldFromLocal = worldFromLocal; }
void SetSpline(AZStd::shared_ptr<const AZ::Spline> spline) { m_spline = AZStd::move(spline); }
AZStd::weak_ptr<const AZ::Spline> GetSpline() const { return m_spline; }
void SetView(AZStd::unique_ptr<ManipulatorView>&& view);
private:
void OnLeftMouseDownImpl(
const ViewportInteraction::MouseInteraction& interaction, float rayIntersectionDistance) override;
void OnLeftMouseUpImpl(
const ViewportInteraction::MouseInteraction& interaction) override;
void InvalidateImpl() override;
void SetBoundsDirtyImpl() override;
AZ::Transform m_worldFromLocal = AZ::Transform::CreateIdentity(); ///< Space the manipulator is in (identity is world space).
AZStd::weak_ptr<const AZ::Spline> m_spline;
AZStd::unique_ptr<ManipulatorView> m_manipulatorView = nullptr; ///< Look of manipulator and bounds for interaction.
MouseActionCallback m_onLeftMouseDownCallback = nullptr;
MouseActionCallback m_onLeftMouseUpCallback = nullptr;
ViewportInteraction::KeyboardModifiers m_keyboardModifiers; ///< What modifier keys are pressed when interacting with this manipulator.
};
SplineSelectionManipulator::Action CalculateManipulationDataAction(
const AZ::Transform& worldFromLocal, const AZ::Vector3& rayOrigin,
const AZ::Vector3& rayDirection, const AZStd::weak_ptr<const AZ::Spline>& spline);
} // namespace AzToolsFramework
@@ -0,0 +1,195 @@
/*
* 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 "SurfaceManipulator.h"
#include <AzToolsFramework/Manipulators/ManipulatorSnapping.h>
#include <AzToolsFramework/Manipulators/ManipulatorView.h>
namespace AzToolsFramework
{
SurfaceManipulator::StartInternal SurfaceManipulator::CalculateManipulationDataStart(
const AZ::Transform& worldFromLocal, const AZ::Vector3& worldSurfacePosition,
const AZ::Vector3& localStartPosition, const bool snapping, const float gridSize, const int viewportId)
{
const AZ::Transform worldFromLocalUniform = AzToolsFramework::TransformUniformScale(worldFromLocal);
const AZ::Transform localFromWorldUniform = worldFromLocalUniform.GetInverse();
const AZ::Vector3 localFinalSurfacePosition = snapping
? CalculateSnappedTerrainPosition(
// note: gridSize is not scaled by scaleRecip here as localStartPosition is
// unscaled itself so the position returned by CalculateSnappedTerrainPosition
// must be in the same space (if localStartPosition were also scaled, gridSize
// would need to be multiplied by scaleRecip)
worldSurfacePosition, worldFromLocalUniform, viewportId, gridSize)
: localFromWorldUniform.TransformPoint(worldSurfacePosition);
// delta/offset between initial vertex position and terrain pick position
const AZ::Vector3 localSurfaceOffset = localFinalSurfacePosition - localStartPosition;
StartInternal startInternal;
startInternal.m_snapOffset = localSurfaceOffset;
startInternal.m_localPosition = localStartPosition + localSurfaceOffset;
startInternal.m_localHitPosition = localFromWorldUniform.TransformVector(worldSurfacePosition);
return startInternal;
}
SurfaceManipulator::Action SurfaceManipulator::CalculateManipulationDataAction(
const StartInternal& startInternal, const AZ::Transform& worldFromLocal,
const AZ::Vector3& worldSurfacePosition, const bool snapping, const float gridSize,
const ViewportInteraction::KeyboardModifiers keyboardModifiers, const int viewportId)
{
const AZ::Transform worldFromLocalUniform = AzToolsFramework::TransformUniformScale(worldFromLocal);
const AZ::Transform localFromWorldUniform = worldFromLocalUniform.GetInverse();
const float scaleRecip = ScaleReciprocal(worldFromLocalUniform);
const AZ::Vector3 localFinalSurfacePosition = snapping
? CalculateSnappedTerrainPosition(
worldSurfacePosition, worldFromLocalUniform, viewportId, gridSize * scaleRecip)
: localFromWorldUniform.TransformPoint(worldSurfacePosition);
Action action;
action.m_start.m_localPosition = startInternal.m_localPosition;
action.m_start.m_snapOffset = startInternal.m_snapOffset;
action.m_current.m_localOffset = localFinalSurfacePosition - startInternal.m_localPosition;
// record what modifier keys are held during this action
action.m_modifiers = keyboardModifiers;
return action;
}
AZStd::shared_ptr<SurfaceManipulator> SurfaceManipulator::MakeShared(const AZ::Transform& worldFromLocal)
{
return AZStd::shared_ptr<SurfaceManipulator>(aznew SurfaceManipulator(worldFromLocal));
}
SurfaceManipulator::SurfaceManipulator(const AZ::Transform& worldFromLocal)
: m_worldFromLocal(worldFromLocal)
{
AttachLeftMouseDownImpl();
}
void SurfaceManipulator::InstallLeftMouseDownCallback(const MouseActionCallback& onMouseDownCallback)
{
m_onLeftMouseDownCallback = onMouseDownCallback;
}
void SurfaceManipulator::InstallLeftMouseUpCallback(const MouseActionCallback& onMouseUpCallback)
{
m_onLeftMouseUpCallback = onMouseUpCallback;
}
void SurfaceManipulator::InstallMouseMoveCallback(const MouseActionCallback& onMouseMoveCallback)
{
m_onMouseMoveCallback = onMouseMoveCallback;
}
void SurfaceManipulator::OnLeftMouseDownImpl(
const ViewportInteraction::MouseInteraction& interaction, float /*rayIntersectionDistance*/)
{
const AZ::Transform worldFromLocalUniformScale = TransformUniformScale(m_worldFromLocal);
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
AZ::Vector3 worldSurfacePosition;
ViewportInteraction::MainEditorViewportInteractionRequestBus::EventResult(
worldSurfacePosition, interaction.m_interactionId.m_viewportId,
&ViewportInteraction::MainEditorViewportInteractionRequestBus::Events::PickTerrain,
ViewportInteraction::QPointFromScreenPoint(
interaction.m_mousePick.m_screenCoordinates));
m_startInternal = CalculateManipulationDataStart(
worldFromLocalUniformScale, worldSurfacePosition, m_position,
gridSnapParams.m_gridSnap, gridSnapParams.m_gridSize,
interaction.m_interactionId.m_viewportId);
if (m_onLeftMouseDownCallback)
{
m_onLeftMouseDownCallback(CalculateManipulationDataAction(
m_startInternal, worldFromLocalUniformScale, worldSurfacePosition,
gridSnapParams.m_gridSnap, gridSnapParams.m_gridSize,
interaction.m_keyboardModifiers,
interaction.m_interactionId.m_viewportId));
}
}
void SurfaceManipulator::OnLeftMouseUpImpl(const ViewportInteraction::MouseInteraction& interaction)
{
if (m_onLeftMouseUpCallback)
{
AZ::Vector3 worldSurfacePosition = AZ::Vector3::CreateZero();
ViewportInteraction::MainEditorViewportInteractionRequestBus::EventResult(
worldSurfacePosition, interaction.m_interactionId.m_viewportId,
&ViewportInteraction::MainEditorViewportInteractionRequestBus::Events::PickTerrain,
ViewportInteraction::QPointFromScreenPoint(
interaction.m_mousePick.m_screenCoordinates));
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
m_onLeftMouseUpCallback(CalculateManipulationDataAction(
m_startInternal, TransformUniformScale(m_worldFromLocal), worldSurfacePosition,
gridSnapParams.m_gridSnap,
gridSnapParams.m_gridSize,
interaction.m_keyboardModifiers, interaction.m_interactionId.m_viewportId));
}
}
void SurfaceManipulator::OnMouseMoveImpl(const ViewportInteraction::MouseInteraction& interaction)
{
if (m_onMouseMoveCallback)
{
AZ::Vector3 worldSurfacePosition = AZ::Vector3::CreateZero();
ViewportInteraction::MainEditorViewportInteractionRequestBus::EventResult(
worldSurfacePosition, interaction.m_interactionId.m_viewportId,
&ViewportInteraction::MainEditorViewportInteractionRequestBus::Events::PickTerrain,
ViewportInteraction::QPointFromScreenPoint(
interaction.m_mousePick.m_screenCoordinates));
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
m_onMouseMoveCallback(CalculateManipulationDataAction(
m_startInternal, TransformUniformScale(m_worldFromLocal), worldSurfacePosition,
gridSnapParams.m_gridSnap,
gridSnapParams.m_gridSize,
interaction.m_keyboardModifiers, interaction.m_interactionId.m_viewportId));
}
}
void SurfaceManipulator::SetBoundsDirtyImpl()
{
m_manipulatorView->SetBoundDirty(GetManipulatorManagerId());
}
void SurfaceManipulator::Draw(
const ManipulatorManagerState& managerState,
AzFramework::DebugDisplayRequests& debugDisplay,
const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction)
{
m_manipulatorView->Draw(
GetManipulatorManagerId(), managerState,
GetManipulatorId(), {
TransformUniformScale(m_worldFromLocal),
m_position, MouseOver()
},
debugDisplay, cameraState, mouseInteraction);
}
void SurfaceManipulator::InvalidateImpl()
{
m_manipulatorView->Invalidate(GetManipulatorManagerId());
}
void SurfaceManipulator::SetView(AZStd::unique_ptr<ManipulatorView>&& view)
{
m_manipulatorView = AZStd::move(view);
}
}
@@ -0,0 +1,126 @@
/*
* 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.
*
*/
#pragma once
#include "BaseManipulator.h"
#include <AzCore/Memory/SystemAllocator.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
namespace AzToolsFramework
{
class ManipulatorView;
/// Surface manipulator will ensure the point(s) it controls snap precisely to the xy grid
/// while also staying aligned exactly to the height of the terrain.
class SurfaceManipulator
: public BaseManipulator
{
/// Private constructor.
explicit SurfaceManipulator(const AZ::Transform& worldFromLocal);
public:
AZ_RTTI(SurfaceManipulator, "{75B8EF42-A5F0-48EB-893E-84BED1BC8BAF}", BaseManipulator)
AZ_CLASS_ALLOCATOR(SurfaceManipulator, AZ::SystemAllocator, 0)
SurfaceManipulator() = delete;
SurfaceManipulator(const SurfaceManipulator&) = delete;
SurfaceManipulator& operator=(const SurfaceManipulator&) = delete;
~SurfaceManipulator() = default;
/// A Manipulator must only be created and managed through a shared_ptr.
static AZStd::shared_ptr<SurfaceManipulator> MakeShared(const AZ::Transform& worldFromLocal);
/// The state of the manipulator at the start of an interaction.
struct Start
{
AZ::Vector3 m_localPosition; ///< The current position of the manipulator in local space.
AZ::Vector3 m_snapOffset; ///< The snap offset amount to ensure manipulator is aligned to the grid.
};
/// The state of the manipulator during an interaction.
struct Current
{
AZ::Vector3 m_localOffset; ///< The current offset of the manipulator from its starting position in local space.
};
/// Mouse action data used by MouseActionCallback (wraps Start and Current manipulator state).
struct Action
{
Start m_start;
Current m_current;
ViewportInteraction::KeyboardModifiers m_modifiers;
AZ::Vector3 LocalPosition() const { return m_start.m_localPosition + m_current.m_localOffset; }
AZ::Vector3 LocalPositionOffset() const { return m_current.m_localOffset; }
};
using MouseActionCallback = AZStd::function<void(const Action&)>;
void InstallLeftMouseDownCallback(const MouseActionCallback& onMouseDownCallback);
void InstallLeftMouseUpCallback(const MouseActionCallback& onMouseUpCallback);
void InstallMouseMoveCallback(const MouseActionCallback& onMouseMoveCallback);
void Draw(
const ManipulatorManagerState& managerState,
AzFramework::DebugDisplayRequests& debugDisplay,
const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction) override;
void SetPosition(const AZ::Vector3& position) { m_position = position; }
void SetSpace(const AZ::Transform& worldFromLocal) { m_worldFromLocal = worldFromLocal; }
const AZ::Vector3& GetPosition() const { return m_position; }
void SetView(AZStd::unique_ptr<ManipulatorView>&& view);
private:
void OnLeftMouseDownImpl(
const ViewportInteraction::MouseInteraction& interaction, float rayIntersectionDistance) override;
void OnLeftMouseUpImpl(
const ViewportInteraction::MouseInteraction& interaction) override;
void OnMouseMoveImpl(
const ViewportInteraction::MouseInteraction& interaction) override;
void InvalidateImpl() override;
void SetBoundsDirtyImpl() override;
/// Initial data recorded when a press first happens with a surface manipulator.
struct StartInternal
{
AZ::Vector3 m_localPosition; ///< The current position of the manipulator in local space.
AZ::Vector3 m_localHitPosition; ///< The hit position with the terrain in local space.
AZ::Vector3 m_snapOffset; ///< The snap offset amount to ensure manipulator is aligned to the grid.
};
AZ::Vector3 m_position = AZ::Vector3::CreateZero(); ///< Position in local space.
AZ::Transform m_worldFromLocal = AZ::Transform::CreateIdentity(); ///< Space the manipulator is in (identity is world space).
StartInternal m_startInternal; ///< Internal initial state recorded/created in OnMouseDown.
AZStd::unique_ptr<ManipulatorView> m_manipulatorView = nullptr; ///< Look of manipulator.
MouseActionCallback m_onLeftMouseDownCallback = nullptr;
MouseActionCallback m_onLeftMouseUpCallback = nullptr;
MouseActionCallback m_onMouseMoveCallback = nullptr;
static StartInternal CalculateManipulationDataStart(
const AZ::Transform& worldFromLocal, const AZ::Vector3& worldSurfacePosition,
const AZ::Vector3& localPosition, bool snapping, float gridSize, int viewportId);
static Action CalculateManipulationDataAction(
const StartInternal& startInternal, const AZ::Transform& worldFromLocal,
const AZ::Vector3& worldSurfacePosition, bool snapping, float gridSize,
ViewportInteraction::KeyboardModifiers keyboardModifiers, int viewportId);
};
} // namespace AzToolsFramework
@@ -0,0 +1,344 @@
/*
* 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 "TranslationManipulators.h"
#include <AzCore/Math/VectorConversions.h>
#include <AzToolsFramework/Manipulators/ManipulatorView.h>
namespace AzToolsFramework
{
static const float s_surfaceManipulatorTransparency = 0.75f;
static const float s_axisLength = 2.0f;
static const float s_surfaceManipulatorRadius = 0.1f;
static const AZ::Color s_xAxisColor = AZ::Color(1.0f, 0.0f, 0.0f, 1.0f);
static const AZ::Color s_yAxisColor = AZ::Color(0.0f, 1.0f, 0.0f, 1.0f);
static const AZ::Color s_zAxisColor = AZ::Color(0.0f, 0.0f, 1.0f, 1.0f);
static const AZ::Color s_surfaceManipulatorColor = AZ::Color(1.0f, 1.0f, 0.0f, 0.5f);
TranslationManipulators::TranslationManipulators(
const Dimensions dimensions, const AZ::Transform& worldFromLocal)
: m_dimensions(dimensions)
{
switch (dimensions)
{
case Dimensions::Two:
m_linearManipulators.reserve(2);
for (size_t manipulatorIndex = 0; manipulatorIndex < 2; ++manipulatorIndex)
{
m_linearManipulators.emplace_back(LinearManipulator::MakeShared(worldFromLocal));
}
m_planarManipulators.emplace_back(PlanarManipulator::MakeShared(worldFromLocal));
break;
case Dimensions::Three:
m_linearManipulators.reserve(3);
m_planarManipulators.reserve(3);
for (size_t manipulatorIndex = 0; manipulatorIndex < 3; ++manipulatorIndex)
{
m_linearManipulators.emplace_back(LinearManipulator::MakeShared(worldFromLocal));
m_planarManipulators.emplace_back(PlanarManipulator::MakeShared(worldFromLocal));
}
m_surfaceManipulator = SurfaceManipulator::MakeShared(worldFromLocal);
break;
default:
AZ_Assert(false, "Invalid dimensions provided");
break;
}
m_space = worldFromLocal;
}
void TranslationManipulators::InstallLinearManipulatorMouseDownCallback(
const LinearManipulator::MouseActionCallback& onMouseDownCallback)
{
for (AZStd::shared_ptr<LinearManipulator>& manipulator : m_linearManipulators)
{
manipulator->InstallLeftMouseDownCallback(onMouseDownCallback);
}
}
void TranslationManipulators::InstallLinearManipulatorMouseMoveCallback(
const LinearManipulator::MouseActionCallback& onMouseMoveCallback)
{
for (AZStd::shared_ptr<LinearManipulator>& manipulator : m_linearManipulators)
{
manipulator->InstallMouseMoveCallback(onMouseMoveCallback);
}
}
void TranslationManipulators::InstallLinearManipulatorMouseUpCallback(
const LinearManipulator::MouseActionCallback& onMouseUpCallback)
{
for (AZStd::shared_ptr<LinearManipulator>& manipulator : m_linearManipulators)
{
manipulator->InstallLeftMouseUpCallback(onMouseUpCallback);
}
}
void TranslationManipulators::InstallPlanarManipulatorMouseDownCallback(
const PlanarManipulator::MouseActionCallback& onMouseDownCallback)
{
for (AZStd::shared_ptr<PlanarManipulator>& manipulator : m_planarManipulators)
{
manipulator->InstallLeftMouseDownCallback(onMouseDownCallback);
}
}
void TranslationManipulators::InstallPlanarManipulatorMouseMoveCallback(
const PlanarManipulator::MouseActionCallback& onMouseMoveCallback)
{
for (AZStd::shared_ptr<PlanarManipulator>& manipulator : m_planarManipulators)
{
manipulator->InstallMouseMoveCallback(onMouseMoveCallback);
}
}
void TranslationManipulators::InstallPlanarManipulatorMouseUpCallback(
const PlanarManipulator::MouseActionCallback& onMouseUpCallback)
{
for (AZStd::shared_ptr<PlanarManipulator>& manipulator : m_planarManipulators)
{
manipulator->InstallLeftMouseUpCallback(onMouseUpCallback);
}
}
void TranslationManipulators::InstallSurfaceManipulatorMouseDownCallback(
const SurfaceManipulator::MouseActionCallback& onMouseDownCallback)
{
if (m_surfaceManipulator)
{
m_surfaceManipulator->InstallLeftMouseDownCallback(onMouseDownCallback);
}
}
void TranslationManipulators::InstallSurfaceManipulatorMouseUpCallback(
const SurfaceManipulator::MouseActionCallback& onMouseUpCallback)
{
if (m_surfaceManipulator)
{
m_surfaceManipulator->InstallLeftMouseUpCallback(onMouseUpCallback);
}
}
void TranslationManipulators::InstallSurfaceManipulatorMouseMoveCallback(
const SurfaceManipulator::MouseActionCallback& onMouseMoveCallback)
{
if (m_surfaceManipulator)
{
m_surfaceManipulator->InstallMouseMoveCallback(onMouseMoveCallback);
}
}
void TranslationManipulators::SetLocalTransform(const AZ::Transform& localTransform)
{
for (AZStd::shared_ptr<LinearManipulator>& manipulator : m_linearManipulators)
{
manipulator->SetLocalTransform(localTransform);
}
for (AZStd::shared_ptr<PlanarManipulator>& manipulator : m_planarManipulators)
{
manipulator->SetLocalTransform(localTransform);
}
if (m_surfaceManipulator)
{
m_surfaceManipulator->SetPosition(localTransform.GetTranslation());
}
m_localTransform = localTransform;
}
void TranslationManipulators::SetLocalPosition(const AZ::Vector3& localPosition)
{
for (AZStd::shared_ptr<LinearManipulator>& manipulator : m_linearManipulators)
{
manipulator->SetLocalPosition(localPosition);
}
for (AZStd::shared_ptr<PlanarManipulator>& manipulator : m_planarManipulators)
{
manipulator->SetLocalPosition(localPosition);
}
if (m_surfaceManipulator)
{
m_surfaceManipulator->SetPosition(localPosition);
}
m_localTransform.SetTranslation(localPosition);
}
void TranslationManipulators::SetLocalOrientation(const AZ::Quaternion& localOrientation)
{
for (AZStd::shared_ptr<LinearManipulator>& manipulator : m_linearManipulators)
{
manipulator->SetLocalOrientation(localOrientation);
}
for (AZStd::shared_ptr<PlanarManipulator>& manipulator : m_planarManipulators)
{
manipulator->SetLocalOrientation(localOrientation);
}
m_localTransform = AZ::Transform::CreateFromQuaternionAndTranslation(
localOrientation, m_localTransform.GetTranslation());
}
void TranslationManipulators::SetSpace(const AZ::Transform& worldFromLocal)
{
for (AZStd::shared_ptr<LinearManipulator>& manipulator : m_linearManipulators)
{
manipulator->SetSpace(worldFromLocal);
}
for (AZStd::shared_ptr<PlanarManipulator>& manipulator : m_planarManipulators)
{
manipulator->SetSpace(worldFromLocal);
}
if (m_surfaceManipulator)
{
m_surfaceManipulator->SetSpace(worldFromLocal);
}
m_space = worldFromLocal;
}
void TranslationManipulators::SetAxes(
const AZ::Vector3& axis1, const AZ::Vector3& axis2, const AZ::Vector3& axis3 /*= AZ::Vector3::CreateAxisZ()*/)
{
AZ::Vector3 axes[] = { axis1, axis2, axis3 };
for (size_t manipulatorIndex = 0; manipulatorIndex < m_linearManipulators.size(); ++manipulatorIndex)
{
m_linearManipulators[manipulatorIndex]->SetAxis(axes[manipulatorIndex]);
}
for (size_t manipulatorIndex = 0; manipulatorIndex < m_planarManipulators.size(); ++manipulatorIndex)
{
m_planarManipulators[manipulatorIndex]->SetAxes(axes[manipulatorIndex], axes[(manipulatorIndex + 1) % 3]);
}
}
void TranslationManipulators::ConfigureLinearView(
float axisLength, const AZ::Color& axis1Color, const AZ::Color& axis2Color,
const AZ::Color& axis3Color /*= AZ::Color(0.0f, 0.0f, 1.0f, 0.5f)*/)
{
const float coneLength = 0.28f;
const float coneRadius = 0.07f;
const float lineWidth = 0.05f;
const AZ::Color axesColor[] = { axis1Color, axis2Color, axis3Color };
const auto configureLinearView = [lineWidth, coneLength, axisLength, coneRadius](
LinearManipulator* linearManipulator, const AZ::Color& color)
{
ManipulatorViews views;
views.emplace_back(CreateManipulatorViewLine(
*linearManipulator, color, axisLength, lineWidth));
views.emplace_back(CreateManipulatorViewCone(
*linearManipulator, color, linearManipulator->GetAxis() * (axisLength - coneLength),
coneLength, coneRadius));
linearManipulator->SetViews(AZStd::move(views));
};
for (size_t manipulatorIndex = 0; manipulatorIndex < m_linearManipulators.size(); ++manipulatorIndex)
{
configureLinearView(m_linearManipulators[manipulatorIndex].get(), axesColor[manipulatorIndex]);
}
}
void TranslationManipulators::ConfigurePlanarView(
const AZ::Color& plane1Color, const AZ::Color& plane2Color /*= AZ::Color(0.0f, 1.0f, 0.0f, 0.5f)*/,
const AZ::Color& plane3Color /*= AZ::Color(0.0f, 0.0f, 1.0f, 0.5f)*/)
{
const float planeSize = 0.6f;
const AZ::Color planesColor[] = { plane1Color, plane2Color, plane3Color };
for (size_t manipulatorIndex = 0; manipulatorIndex < m_planarManipulators.size(); ++manipulatorIndex)
{
const AZStd::shared_ptr<ManipulatorViewQuad> manipulatorView =
CreateManipulatorViewQuad(
*m_planarManipulators[manipulatorIndex], planesColor[manipulatorIndex],
planesColor[(manipulatorIndex + 1) % 3],
planeSize);
m_planarManipulators[manipulatorIndex]->SetViews(ManipulatorViews{manipulatorView});
}
}
void TranslationManipulators::ConfigureSurfaceView(
const float radius, const AZ::Color& color)
{
if (m_surfaceManipulator)
{
m_surfaceManipulator->SetView(CreateManipulatorViewSphere(color, radius,
[](const ViewportInteraction::MouseInteraction& /*mouseInteraction*/,
bool mouseOver, const AZ::Color& defaultColor) -> AZ::Color
{
const AZ::Color color[2] =
{
defaultColor,
Vector3ToVector4(
BaseManipulator::s_defaultMouseOverColor.GetAsVector3(), s_surfaceManipulatorTransparency)
};
return color[mouseOver];
}));
}
}
void TranslationManipulators::ProcessManipulators(const AZStd::function<void(BaseManipulator*)>& manipulatorFn)
{
for (AZStd::shared_ptr<LinearManipulator>& manipulator : m_linearManipulators)
{
manipulatorFn(manipulator.get());
}
for (AZStd::shared_ptr<PlanarManipulator>& manipulator : m_planarManipulators)
{
manipulatorFn(manipulator.get());
}
if (m_surfaceManipulator)
{
manipulatorFn(m_surfaceManipulator.get());
}
}
void ConfigureTranslationManipulatorAppearance3d(
TranslationManipulators* translationManipulators)
{
translationManipulators->SetAxes(
AZ::Vector3::CreateAxisX(),
AZ::Vector3::CreateAxisY(),
AZ::Vector3::CreateAxisZ());
translationManipulators->ConfigurePlanarView(
s_xAxisColor, s_yAxisColor, s_zAxisColor);
translationManipulators->ConfigureLinearView(
s_axisLength, s_xAxisColor, s_yAxisColor, s_zAxisColor);
translationManipulators->ConfigureSurfaceView(
s_surfaceManipulatorRadius, s_surfaceManipulatorColor);
}
void ConfigureTranslationManipulatorAppearance2d(
TranslationManipulators* translationManipulators)
{
translationManipulators->SetAxes(
AZ::Vector3::CreateAxisX(),
AZ::Vector3::CreateAxisY());
translationManipulators->ConfigurePlanarView(s_xAxisColor);
translationManipulators->ConfigureLinearView(s_axisLength, s_xAxisColor, s_yAxisColor);
}
} // namespace AzToolsFramework
@@ -0,0 +1,131 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Memory/SystemAllocator.h>
#include <AzToolsFramework/Manipulators/LinearManipulator.h>
#include <AzToolsFramework/Manipulators/PlanarManipulator.h>
#include <AzToolsFramework/Manipulators/SurfaceManipulator.h>
namespace AzToolsFramework
{
/// TranslationManipulators is an aggregation of 3 linear manipulators, 3 planar manipulators
/// and one surface manipulator who share the same transform.
class TranslationManipulators
: public Manipulators
{
public:
AZ_RTTI(TranslationManipulators, "{D5E49EA2-30E0-42BC-A51D-6A7F87818260}")
AZ_CLASS_ALLOCATOR(TranslationManipulators, AZ::SystemAllocator, 0)
/// How many dimensions does this translation manipulator have
enum class Dimensions
{
Two,
Three
};
TranslationManipulators(Dimensions dimensions, const AZ::Transform& worldFromLocal);
void InstallLinearManipulatorMouseDownCallback(const LinearManipulator::MouseActionCallback& onMouseDownCallback);
void InstallLinearManipulatorMouseMoveCallback(const LinearManipulator::MouseActionCallback& onMouseMoveCallback);
void InstallLinearManipulatorMouseUpCallback(const LinearManipulator::MouseActionCallback& onMouseUpCallback);
void InstallPlanarManipulatorMouseDownCallback(const PlanarManipulator::MouseActionCallback& onMouseDownCallback);
void InstallPlanarManipulatorMouseMoveCallback(const PlanarManipulator::MouseActionCallback& onMouseMoveCallback);
void InstallPlanarManipulatorMouseUpCallback(const PlanarManipulator::MouseActionCallback& onMouseUpCallback);
void InstallSurfaceManipulatorMouseDownCallback(const SurfaceManipulator::MouseActionCallback& onMouseDownCallback);
void InstallSurfaceManipulatorMouseMoveCallback(const SurfaceManipulator::MouseActionCallback& onMouseMoveCallback);
void InstallSurfaceManipulatorMouseUpCallback(const SurfaceManipulator::MouseActionCallback& onMouseUpCallback);
void SetSpace(const AZ::Transform& worldFromLocal) override;
void SetLocalTransform(const AZ::Transform& localTransform) override;
void SetLocalPosition(const AZ::Vector3& localPosition) override;
void SetLocalOrientation(const AZ::Quaternion& localOrientation) override;
void SetAxes(
const AZ::Vector3& axis1, const AZ::Vector3& axis2,
const AZ::Vector3& axis3 = AZ::Vector3::CreateAxisZ());
void ConfigurePlanarView(
const AZ::Color& plane1Color,
const AZ::Color& plane2Color = AZ::Color(0.0f, 1.0f, 0.0f, 0.5f),
const AZ::Color& plane3Color = AZ::Color(0.0f, 0.0f, 1.0f, 0.5f));
void ConfigureLinearView(
float axisLength,
const AZ::Color& axis1Color, const AZ::Color& axis2Color,
const AZ::Color& axis3Color = AZ::Color(0.0f, 0.0f, 1.0f, 0.5f));
void ConfigureSurfaceView(
float radius, const AZ::Color& color);
private:
AZ_DISABLE_COPY_MOVE(TranslationManipulators)
// Manipulators
void ProcessManipulators(const AZStd::function<void(BaseManipulator*)>&) override;
const Dimensions m_dimensions; ///< How many dimensions of freedom does this manipulator have.
AZStd::vector<AZStd::shared_ptr<LinearManipulator>> m_linearManipulators;
AZStd::vector<AZStd::shared_ptr<PlanarManipulator>> m_planarManipulators;
AZStd::shared_ptr<SurfaceManipulator> m_surfaceManipulator = nullptr;
};
/// IndexedTranslationManipulator wraps a standard TranslationManipulators and allows it to be linked
/// to a particular index in a list of vertices/points.
template<typename Vertex>
struct IndexedTranslationManipulator
{
explicit IndexedTranslationManipulator(
TranslationManipulators::Dimensions dimensions, AZ::u64 vertIndex,
const Vertex& position, const AZ::Transform& worldFromLocal)
: m_manipulator(dimensions, worldFromLocal)
{
m_vertices.push_back({ position, Vertex::CreateZero(), vertIndex });
}
/// Store vertex start position as manipulator event occurs, index refers to location in container.
struct VertexLookup
{
Vertex m_start;
Vertex m_offset;
AZ::u64 m_index;
Vertex CurrentPosition() const { return m_start + m_offset; }
};
/// Helper to iterate over all vertices stored by the manipulator.
void Process(AZStd::function<void(VertexLookup&)> fn)
{
for (VertexLookup& vertex : m_vertices)
{
fn(vertex);
}
}
AZStd::vector<VertexLookup> m_vertices; ///< List of vertices currently associated with this translation manipulator.
TranslationManipulators m_manipulator;
};
/// Function pointer to configure how a translation manipulator should look and behave (dimensions/axes/views).
using TranslationManipulatorConfiguratorFn = void(*)(TranslationManipulators*);
void ConfigureTranslationManipulatorAppearance3d(
TranslationManipulators* translationManipulators);
void ConfigureTranslationManipulatorAppearance2d(
TranslationManipulators* translationManipulators);
} // namespace AzToolsFramework