Merge branch 'main' into LYN-1767-AB
This commit is contained in:
@@ -433,6 +433,11 @@ namespace AzToolsFramework
|
||||
return m_manipulatorSpaceWithLocalTransform.GetSpace();
|
||||
}
|
||||
|
||||
const AZ::Vector3& Manipulators::GetNonUniformScale() const
|
||||
{
|
||||
return m_manipulatorSpaceWithLocalTransform.GetNonUniformScale();
|
||||
}
|
||||
|
||||
void Manipulators::SetSpace(const AZ::Transform& worldFromLocal)
|
||||
{
|
||||
m_manipulatorSpaceWithLocalTransform.SetSpace(worldFromLocal);
|
||||
|
||||
+13
-14
@@ -192,8 +192,7 @@ namespace AzToolsFramework
|
||||
/// for each vertex associated with the translation manipulator to use with offset calculations when updating.
|
||||
template<typename Vertex>
|
||||
void InitializeVertexLookup(
|
||||
IndexedTranslationManipulator<Vertex>& translationManipulator,
|
||||
const AZ::EntityId entityId, const AZ::Vector3& snapOffset)
|
||||
IndexedTranslationManipulator<Vertex>& translationManipulator, const AZ::EntityId entityId)
|
||||
{
|
||||
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
|
||||
|
||||
@@ -202,7 +201,7 @@ namespace AzToolsFramework
|
||||
AZ::FixedVerticesRequestBus<Vertex>::Bind(fixedVertices, entityId);
|
||||
|
||||
translationManipulator.Process(
|
||||
[snapOffset, fixedVertices]
|
||||
[fixedVertices]
|
||||
(typename IndexedTranslationManipulator<Vertex>::VertexLookup& vertexLookup)
|
||||
{
|
||||
Vertex vertex;
|
||||
@@ -213,7 +212,7 @@ namespace AzToolsFramework
|
||||
|
||||
if (found)
|
||||
{
|
||||
vertexLookup.m_start = vertex + AZ::AdaptVertexIn<Vertex>(snapOffset);
|
||||
vertexLookup.m_start = vertex;
|
||||
vertexLookup.m_offset = Vertex::CreateZero();
|
||||
}
|
||||
});
|
||||
@@ -250,10 +249,10 @@ namespace AzToolsFramework
|
||||
|
||||
// linear manipulator callbacks
|
||||
m_translationManipulator->m_manipulator.InstallLinearManipulatorMouseDownCallback(
|
||||
[this](const LinearManipulator::Action& action)
|
||||
[this]([[maybe_unused]] const LinearManipulator::Action& action)
|
||||
{
|
||||
BeginBatchMovement();
|
||||
InitializeVertexLookup(*m_translationManipulator, GetEntityId(), action.m_start.m_positionSnapOffset);
|
||||
InitializeVertexLookup(*m_translationManipulator, GetEntityId());
|
||||
});
|
||||
|
||||
m_translationManipulator->m_manipulator.InstallLinearManipulatorMouseMoveCallback(
|
||||
@@ -264,17 +263,17 @@ namespace AzToolsFramework
|
||||
});
|
||||
|
||||
m_translationManipulator->m_manipulator.InstallLinearManipulatorMouseUpCallback(
|
||||
[this](const LinearManipulator::Action& /*action*/)
|
||||
[this]([[maybe_unused]] const LinearManipulator::Action& action)
|
||||
{
|
||||
EndBatchMovement();
|
||||
});
|
||||
|
||||
// planar manipulator callbacks
|
||||
m_translationManipulator->m_manipulator.InstallPlanarManipulatorMouseDownCallback(
|
||||
[this](const PlanarManipulator::Action& action)
|
||||
[this]([[maybe_unused]] const PlanarManipulator::Action& action)
|
||||
{
|
||||
BeginBatchMovement();
|
||||
InitializeVertexLookup(*m_translationManipulator, GetEntityId(), action.m_start.m_snapOffset);
|
||||
InitializeVertexLookup(*m_translationManipulator, GetEntityId());
|
||||
});
|
||||
|
||||
m_translationManipulator->m_manipulator.InstallPlanarManipulatorMouseMoveCallback(
|
||||
@@ -285,17 +284,17 @@ namespace AzToolsFramework
|
||||
});
|
||||
|
||||
m_translationManipulator->m_manipulator.InstallPlanarManipulatorMouseUpCallback(
|
||||
[this](const PlanarManipulator::Action& /*action*/)
|
||||
[this]([[maybe_unused]] const PlanarManipulator::Action& action)
|
||||
{
|
||||
EndBatchMovement();
|
||||
});
|
||||
|
||||
// surface manipulator callbacks
|
||||
m_translationManipulator->m_manipulator.InstallSurfaceManipulatorMouseDownCallback(
|
||||
[this](const SurfaceManipulator::Action& action)
|
||||
[this]([[maybe_unused]] const SurfaceManipulator::Action& action)
|
||||
{
|
||||
BeginBatchMovement();
|
||||
InitializeVertexLookup(*m_translationManipulator, GetEntityId(), action.m_start.m_snapOffset);
|
||||
InitializeVertexLookup(*m_translationManipulator, GetEntityId());
|
||||
});
|
||||
|
||||
m_translationManipulator->m_manipulator.InstallSurfaceManipulatorMouseMoveCallback(
|
||||
@@ -306,7 +305,7 @@ namespace AzToolsFramework
|
||||
});
|
||||
|
||||
m_translationManipulator->m_manipulator.InstallSurfaceManipulatorMouseUpCallback(
|
||||
[this](const SurfaceManipulator::Action& /*action*/)
|
||||
[this]([[maybe_unused]] const SurfaceManipulator::Action& action)
|
||||
{
|
||||
EndBatchMovement();
|
||||
});
|
||||
@@ -893,7 +892,7 @@ namespace AzToolsFramework
|
||||
{
|
||||
BeginBatchMovement();
|
||||
|
||||
InitializeVertexLookup(*m_translationManipulator, GetEntityId(), AZ::Vector3::CreateZero());
|
||||
InitializeVertexLookup(*m_translationManipulator, GetEntityId());
|
||||
// note: AdaptVertexIn/Out is to ensure we clamp the vertex local Z position to 0 if
|
||||
// dealing with Vector2s when setting the position of the manipulator.
|
||||
const AZ::Vector3 localOffset =
|
||||
|
||||
+26
-44
@@ -23,8 +23,8 @@ namespace AzToolsFramework
|
||||
{
|
||||
LinearManipulator::Starter CalculateLinearManipulationDataStart(
|
||||
const LinearManipulator::Fixed& fixed, const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale,
|
||||
const AZ::Transform& localTransform, const GridSnapAction& gridSnapAction, const ViewportInteraction::MouseInteraction& interaction,
|
||||
const float intersectionDistance, const AzFramework::CameraState& cameraState)
|
||||
const AZ::Transform& localTransform, const ViewportInteraction::MouseInteraction& interaction, const float intersectionDistance,
|
||||
const AzFramework::CameraState& cameraState)
|
||||
{
|
||||
const ManipulatorInteraction manipulatorInteraction =
|
||||
BuildManipulatorInteraction(
|
||||
@@ -50,28 +50,9 @@ namespace AzToolsFramework
|
||||
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 = AZ::Vector3(localTransform.GetUniformScale());
|
||||
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_localPosition = localTransform.GetTranslation();
|
||||
start.m_localScale = AZ::Vector3(localTransform.GetUniformScale());;
|
||||
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)
|
||||
@@ -87,7 +68,7 @@ namespace AzToolsFramework
|
||||
LinearManipulator::Action CalculateLinearManipulationDataAction(
|
||||
const LinearManipulator::Fixed& fixed, const LinearManipulator::Starter& starter,
|
||||
const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale, const AZ::Transform& localTransform,
|
||||
const GridSnapAction& gridSnapAction, const ViewportInteraction::MouseInteraction& interaction)
|
||||
const GridSnapParameters& gridSnapParams, const ViewportInteraction::MouseInteraction& interaction)
|
||||
{
|
||||
const ManipulatorInteraction manipulatorInteraction =
|
||||
BuildManipulatorInteraction(
|
||||
@@ -108,31 +89,34 @@ namespace AzToolsFramework
|
||||
GetCameraState(interaction.m_interactionId.m_viewportId));
|
||||
|
||||
const AZ::Vector3 axis = TransformDirectionNoScaling(localTransform, fixed.m_axis);
|
||||
// The local positions have been transformed to the reference frame of the object being manipulated. But they appear in the world
|
||||
// with non-uniform scale applied, and the object being manipulated will want to work with unscaled deltas, so we need to divide by
|
||||
// the non-uniform scale here.
|
||||
// the local positions have been transformed to the reference frame of the object being manipulated, but they appear in the world
|
||||
// with non-uniform scale applied, the object being manipulated will want to work with unscaled deltas, so we need to divide by
|
||||
// the non-uniform scale here
|
||||
const AZ::Vector3 hitDelta = (localHitPosition - start.m_localHitPosition) / nonUniformScale;
|
||||
const AZ::Vector3 unsnappedOffset = axis * axis.Dot(hitDelta);
|
||||
|
||||
const float scaleRecip = manipulatorInteraction.m_scaleReciprocal * axis.Dot(manipulatorInteraction.m_nonUniformScaleReciprocal);
|
||||
const float gridSize = gridSnapAction.m_gridSnapParams.m_gridSize;
|
||||
const bool snapping = gridSnapAction.m_gridSnapParams.m_gridSnap;
|
||||
const float scaleRecip =
|
||||
manipulatorInteraction.m_scaleReciprocal * fixed.m_axis.Dot(manipulatorInteraction.m_nonUniformScaleReciprocal);
|
||||
const float gridSize = gridSnapParams.m_gridSize;
|
||||
const bool snapping = 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)
|
||||
? CalculateSnappedAmount(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 * NonUniformScaleReciprocal(nonUniformScale);
|
||||
const AZ::Vector3 scaledUnsnappedOffset =
|
||||
unsnappedOffset * startTransition.m_screenToWorldScale * NonUniformScaleReciprocal(nonUniformScale);
|
||||
|
||||
// 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(CalculateSnappedAmount(scaledUnsnappedOffset, axis, gridSize * scaleRecip))
|
||||
: invLocalRotation.TransformVector(scaledUnsnappedOffset);
|
||||
|
||||
// record what modifier keys are held during this action
|
||||
@@ -171,19 +155,18 @@ namespace AzToolsFramework
|
||||
const ViewportInteraction::MouseInteraction& interaction, const float rayIntersectionDistance)
|
||||
{
|
||||
const AZ::Transform worldFromLocalUniformScale = TransformUniformScale(GetSpace());
|
||||
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, GetNonUniformScale(), GetLocalTransform(),
|
||||
GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt()), interaction, rayIntersectionDistance,
|
||||
m_fixed, worldFromLocalUniformScale, GetNonUniformScale(), GetLocalTransform(), interaction, rayIntersectionDistance,
|
||||
GetCameraState(interaction.m_interactionId.m_viewportId));
|
||||
|
||||
if (m_onLeftMouseDownCallback)
|
||||
{
|
||||
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
|
||||
|
||||
m_onLeftMouseDownCallback(CalculateLinearManipulationDataAction(
|
||||
m_fixed, m_starter, worldFromLocalUniformScale, GetNonUniformScale(), GetLocalTransform(),
|
||||
GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt()), interaction));
|
||||
m_fixed, m_starter, worldFromLocalUniformScale, GetNonUniformScale(), GetLocalTransform(), gridSnapParams, interaction));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,8 +178,8 @@ namespace AzToolsFramework
|
||||
|
||||
// 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(GetSpace()), GetNonUniformScale(), GetLocalTransform(),
|
||||
GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt()), interaction));
|
||||
m_fixed, m_starter, TransformUniformScale(GetSpace()), GetNonUniformScale(), GetLocalTransform(), gridSnapParams,
|
||||
interaction));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,8 +191,7 @@ namespace AzToolsFramework
|
||||
|
||||
// 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(GetSpace()), GetNonUniformScale(), GetLocalTransform(),
|
||||
GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt()), interaction));
|
||||
m_fixed, m_starter, TransformUniformScale(GetSpace()), GetNonUniformScale(), GetLocalTransform(), gridSnapParams, interaction));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,8 +214,8 @@ namespace AzToolsFramework
|
||||
GridSnapSettings(mouseInteraction.m_interactionId.m_viewportId);
|
||||
|
||||
const auto action = CalculateLinearManipulationDataAction(
|
||||
m_fixed, m_starter, TransformUniformScale(GetSpace()), GetNonUniformScale(), GetLocalTransform(),
|
||||
GridSnapAction(gridSnapParams, mouseInteraction.m_keyboardModifiers.Alt()), mouseInteraction);
|
||||
m_fixed, m_starter, TransformUniformScale(GetSpace()), GetNonUniformScale(), GetLocalTransform(), gridSnapParams,
|
||||
mouseInteraction);
|
||||
|
||||
// display the exact hit (ray intersection) of the mouse pick on the manipulator
|
||||
DrawTransformAxes(
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
struct GridSnapAction;
|
||||
struct GridSnapParameters;
|
||||
|
||||
/// LinearManipulator serves as a visual tool for users to modify values
|
||||
/// in one dimension on an axis defined in 3D space.
|
||||
@@ -68,8 +68,6 @@ namespace AzToolsFramework
|
||||
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.
|
||||
};
|
||||
@@ -91,7 +89,7 @@ namespace AzToolsFramework
|
||||
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 LocalScaleOffset() const { return 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
|
||||
@@ -162,11 +160,11 @@ namespace AzToolsFramework
|
||||
|
||||
LinearManipulator::Starter CalculateLinearManipulationDataStart(
|
||||
const LinearManipulator::Fixed& fixed, const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale,
|
||||
const AZ::Transform& localTransform, const GridSnapAction& gridSnapAction, const ViewportInteraction::MouseInteraction& interaction,
|
||||
float intersectionDistance, const AzFramework::CameraState& cameraState);
|
||||
const AZ::Transform& localTransform, 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::Vector3& nonUniformScale, const AZ::Transform& localTransform,
|
||||
const GridSnapAction& gridSnapAction, const ViewportInteraction::MouseInteraction& interaction);
|
||||
const GridSnapParameters& gridSnapParams, const ViewportInteraction::MouseInteraction& interaction);
|
||||
} // namespace AzToolsFramework
|
||||
|
||||
+25
-11
@@ -42,12 +42,6 @@ namespace AzToolsFramework
|
||||
{
|
||||
}
|
||||
|
||||
GridSnapAction::GridSnapAction(const GridSnapParameters& gridSnapParameters, const bool localSnapping)
|
||||
: m_gridSnapParams(gridSnapParameters)
|
||||
, m_localSnapping(localSnapping)
|
||||
{
|
||||
}
|
||||
|
||||
ManipulatorInteraction BuildManipulatorInteraction(
|
||||
const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale,
|
||||
const AZ::Vector3& worldRayOrigin, const AZ::Vector3& worldRayDirection)
|
||||
@@ -57,19 +51,39 @@ namespace AzToolsFramework
|
||||
|
||||
return {localFromWorldUniform.TransformPoint(worldRayOrigin),
|
||||
TransformDirectionNoScaling(localFromWorldUniform, worldRayDirection),
|
||||
ScaleReciprocal(worldFromLocalUniform),
|
||||
NonUniformScaleReciprocal(nonUniformScale)};
|
||||
NonUniformScaleReciprocal(nonUniformScale),
|
||||
ScaleReciprocal(worldFromLocalUniform)};
|
||||
}
|
||||
|
||||
AZ::Vector3 CalculateSnappedOffset(
|
||||
const AZ::Vector3& unsnappedPosition, const AZ::Vector3& axis, const float size)
|
||||
struct SnapAdjustment
|
||||
{
|
||||
float m_existingSnapDistance; //!< How far to snap up or down to align to the grid.
|
||||
float m_nextSnapDistance; //!< The snap increment (will return full signed value (grid size) when distance
|
||||
//!< moved is greater than half of the grid size in either direction).
|
||||
};
|
||||
|
||||
static SnapAdjustment CalculateSnapDistance(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 { axisDistance, snappedAxisDistance };
|
||||
}
|
||||
|
||||
AZ::Vector3 CalculateSnappedOffset(const AZ::Vector3& unsnappedPosition, const AZ::Vector3& axis, const float size)
|
||||
{
|
||||
const auto snapAdjustment = CalculateSnapDistance(unsnappedPosition, axis, size);
|
||||
// return offset along axis to snap to step size
|
||||
return axis * (snappedAxisDistance - axisDistance);
|
||||
return axis * (snapAdjustment.m_nextSnapDistance - snapAdjustment.m_existingSnapDistance);
|
||||
}
|
||||
|
||||
AZ::Vector3 CalculateSnappedAmount(const AZ::Vector3& unsnappedPosition, const AZ::Vector3& axis, const float size)
|
||||
{
|
||||
const auto snapAdjustment = CalculateSnapDistance(unsnappedPosition, axis, size);
|
||||
// return offset along axis to snap to step size
|
||||
return axis * snapAdjustment.m_nextSnapDistance;
|
||||
}
|
||||
|
||||
AZ::Vector3 CalculateSnappedTerrainPosition(
|
||||
|
||||
+9
-13
@@ -31,24 +31,15 @@ namespace AzToolsFramework
|
||||
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.
|
||||
AZ::Vector3 m_nonUniformScaleReciprocal; ///< Handles inverting any non-uniform scale which was applied
|
||||
///< separately from the transform.
|
||||
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.
|
||||
@@ -56,11 +47,16 @@ namespace AzToolsFramework
|
||||
const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale,
|
||||
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.
|
||||
/// Calculate the offset along an axis to adjust a position to stay snapped to a given grid size.
|
||||
/// @note This is snap up or down to the nearest grid segment (e.g. 0.2 snaps to 0.0 -> delta 0.2,
|
||||
/// 0.7 snaps to 1.0 -> delta 0.3).
|
||||
AZ::Vector3 CalculateSnappedOffset(
|
||||
const AZ::Vector3& unsnappedPosition, const AZ::Vector3& axis, float size);
|
||||
|
||||
/// Return the amount to snap from the starting position given the current grid size.
|
||||
/// @note A movement of more than half size (in either direction) will cause a snap by size.
|
||||
AZ::Vector3 CalculateSnappedAmount(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(
|
||||
|
||||
+10
-18
@@ -59,17 +59,16 @@ namespace AzToolsFramework
|
||||
const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale, const AZ::Transform& localTransform,
|
||||
const ViewportInteraction::MouseInteraction& interaction,
|
||||
const AZStd::vector<LinearManipulator::Fixed>& fixedAxes,
|
||||
const AZStd::vector<LinearManipulator::Starter>& starterStates, const GridSnapAction& gridSnapAction)
|
||||
const AZStd::vector<LinearManipulator::Starter>& starterStates, const GridSnapParameters& gridSnapParams)
|
||||
{
|
||||
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, nonUniformScale, localTransform,
|
||||
gridSnapAction, interaction));
|
||||
action.m_actions.push_back(CalculateLinearManipulationDataAction(
|
||||
fixedAxes[fixedIndex], starterStates[fixedIndex], worldFromLocal, nonUniformScale, localTransform, gridSnapParams,
|
||||
interaction));
|
||||
}
|
||||
|
||||
return action;
|
||||
@@ -79,8 +78,6 @@ namespace AzToolsFramework
|
||||
const ViewportInteraction::MouseInteraction& interaction, const float rayIntersectionDistance)
|
||||
{
|
||||
const AZ::Transform worldFromLocalUniformScale = TransformUniformScale(GetSpace());
|
||||
|
||||
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
|
||||
@@ -88,20 +85,19 @@ namespace AzToolsFramework
|
||||
{
|
||||
// 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, GetNonUniformScale(), GetLocalTransform(),
|
||||
GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt()), interaction,
|
||||
rayIntersectionDistance, cameraState);
|
||||
fixed, worldFromLocalUniformScale, GetNonUniformScale(), GetLocalTransform(), interaction, rayIntersectionDistance,
|
||||
cameraState);
|
||||
|
||||
m_starters.push_back(linearStart);
|
||||
}
|
||||
|
||||
if (m_onLeftMouseDownCallback)
|
||||
{
|
||||
const GridSnapAction gridSnapAction = GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt());
|
||||
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
|
||||
// pass action containing all linear actions for each axis to handler
|
||||
m_onLeftMouseDownCallback(BuildMultiLinearManipulatorAction(
|
||||
worldFromLocalUniformScale, GetNonUniformScale(), GetLocalTransform(),
|
||||
interaction, m_fixedAxes, m_starters, gridSnapAction));
|
||||
interaction, m_fixedAxes, m_starters, gridSnapParams));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,11 +107,9 @@ namespace AzToolsFramework
|
||||
{
|
||||
const AZ::Transform worldFromLocalUniformScale = TransformUniformScale(GetSpace());
|
||||
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
|
||||
const GridSnapAction gridSnapAction = GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt());
|
||||
|
||||
m_onMouseMoveCallback(BuildMultiLinearManipulatorAction(
|
||||
worldFromLocalUniformScale, GetNonUniformScale(), GetLocalTransform(),
|
||||
interaction, m_fixedAxes, m_starters, gridSnapAction));
|
||||
interaction, m_fixedAxes, m_starters, gridSnapParams));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,11 +119,9 @@ namespace AzToolsFramework
|
||||
{
|
||||
const AZ::Transform worldFromLocalUniformScale = TransformUniformScale(GetSpace());
|
||||
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
|
||||
const GridSnapAction gridSnapAction = GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt());
|
||||
|
||||
m_onLeftMouseUpCallback(BuildMultiLinearManipulatorAction(
|
||||
worldFromLocalUniformScale, GetNonUniformScale(), GetLocalTransform(),
|
||||
interaction, m_fixedAxes, m_starters, gridSnapAction));
|
||||
interaction, m_fixedAxes, m_starters, gridSnapParams));
|
||||
|
||||
m_starters.clear();
|
||||
}
|
||||
|
||||
@@ -20,8 +20,6 @@
|
||||
|
||||
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
|
||||
|
||||
+15
-35
@@ -22,8 +22,7 @@
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
PlanarManipulator::StartInternal PlanarManipulator::CalculateManipulationDataStart(
|
||||
const Fixed& fixed, const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale,
|
||||
const AZ::Transform& localTransform, const GridSnapAction& gridSnapAction,
|
||||
const Fixed& fixed, const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale, const AZ::Transform& localTransform,
|
||||
const ViewportInteraction::MouseInteraction& interaction, const float intersectionDistance)
|
||||
{
|
||||
const ManipulatorInteraction manipulatorInteraction =
|
||||
@@ -31,8 +30,6 @@ namespace AzToolsFramework
|
||||
worldFromLocal, nonUniformScale, 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 =
|
||||
@@ -43,25 +40,14 @@ namespace AzToolsFramework
|
||||
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;
|
||||
startInternal.m_localPosition = localTransform.GetTranslation();
|
||||
|
||||
return startInternal;
|
||||
}
|
||||
|
||||
PlanarManipulator::Action PlanarManipulator::CalculateManipulationDataAction(
|
||||
const Fixed& fixed, const StartInternal& startInternal, const AZ::Transform& worldFromLocal,
|
||||
const AZ::Vector3& nonUniformScale, const AZ::Transform& localTransform, const GridSnapAction& gridSnapAction,
|
||||
const Fixed& fixed, const StartInternal& startInternal, const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale,
|
||||
const AZ::Transform& localTransform, const GridSnapParameters& gridSnapParams,
|
||||
const ViewportInteraction::MouseInteraction& interaction)
|
||||
{
|
||||
const ManipulatorInteraction manipulatorInteraction =
|
||||
@@ -88,20 +74,18 @@ namespace AzToolsFramework
|
||||
const AZ::Vector3 hitDelta = (localHitPosition - startInternal.m_localHitPosition) / nonUniformScale;
|
||||
const AZ::Vector3 unsnappedOffset = axis1.Dot(hitDelta) * axis1 + axis2.Dot(hitDelta) * axis2;
|
||||
|
||||
const float scaleRecip = manipulatorInteraction.m_scaleReciprocal;
|
||||
const AZ::Vector3 nonUniformScaleRecip = manipulatorInteraction.m_nonUniformScaleReciprocal;
|
||||
const float gridSize = gridSnapAction.m_gridSnapParams.m_gridSize;
|
||||
const bool snapping = gridSnapAction.m_gridSnapParams.m_gridSnap;
|
||||
const float scaleRecip = manipulatorInteraction.m_scaleReciprocal;
|
||||
const float gridSize = gridSnapParams.m_gridSize;
|
||||
const bool snapping = 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 * nonUniformScaleRecip.Dot(axis1)) +
|
||||
CalculateSnappedOffset(unsnappedOffset, axis2, gridSize * scaleRecip * nonUniformScaleRecip.Dot(axis2))
|
||||
? CalculateSnappedAmount(unsnappedOffset, axis1, gridSize * scaleRecip * nonUniformScaleRecip.Dot(fixed.m_axis1)) +
|
||||
CalculateSnappedAmount(unsnappedOffset, axis2, gridSize * scaleRecip * nonUniformScaleRecip.Dot(fixed.m_axis2))
|
||||
: unsnappedOffset;
|
||||
|
||||
// record what modifier keys are held during this action
|
||||
@@ -141,18 +125,17 @@ namespace AzToolsFramework
|
||||
{
|
||||
const AZ::Transform worldFromLocalUniformScale = TransformUniformScale(GetSpace());
|
||||
|
||||
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
|
||||
|
||||
m_startInternal = CalculateManipulationDataStart(
|
||||
m_fixed, worldFromLocalUniformScale, GetNonUniformScale(), TransformNormalizedScale(GetLocalTransform()),
|
||||
GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt()),
|
||||
interaction, rayIntersectionDistance);
|
||||
|
||||
if (m_onLeftMouseDownCallback)
|
||||
{
|
||||
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
|
||||
|
||||
m_onLeftMouseDownCallback(CalculateManipulationDataAction(
|
||||
m_fixed, m_startInternal, worldFromLocalUniformScale, GetNonUniformScale(), TransformNormalizedScale(GetLocalTransform()),
|
||||
GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt()), interaction));
|
||||
gridSnapParams, interaction));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,8 +147,7 @@ namespace AzToolsFramework
|
||||
|
||||
m_onMouseMoveCallback(CalculateManipulationDataAction(
|
||||
m_fixed, m_startInternal, TransformUniformScale(GetSpace()), GetNonUniformScale(),
|
||||
TransformNormalizedScale(GetLocalTransform()),
|
||||
GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt()), interaction));
|
||||
TransformNormalizedScale(GetLocalTransform()), gridSnapParams, interaction));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,8 +159,7 @@ namespace AzToolsFramework
|
||||
|
||||
m_onLeftMouseUpCallback(CalculateManipulationDataAction(
|
||||
m_fixed, m_startInternal, TransformUniformScale(GetSpace()), GetNonUniformScale(),
|
||||
TransformNormalizedScale(GetLocalTransform()),
|
||||
GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt()), interaction));
|
||||
TransformNormalizedScale(GetLocalTransform()), gridSnapParams, interaction));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,8 +176,7 @@ namespace AzToolsFramework
|
||||
const GridSnapParameters gridSnapParams = GridSnapSettings(mouseInteraction.m_interactionId.m_viewportId);
|
||||
const auto action = CalculateManipulationDataAction(
|
||||
m_fixed, m_startInternal, TransformUniformScale(GetSpace()), GetNonUniformScale(),
|
||||
TransformNormalizedScale(GetLocalTransform()),
|
||||
GridSnapAction(gridSnapParams, mouseInteraction.m_keyboardModifiers.Alt()), mouseInteraction);
|
||||
TransformNormalizedScale(GetLocalTransform()), gridSnapParams, mouseInteraction);
|
||||
|
||||
// display the exact hit (ray intersection) of the mouse pick on the manipulator
|
||||
DrawTransformAxes(
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
class ManipulatorView;
|
||||
struct GridSnapAction;
|
||||
struct GridSnapParameters;
|
||||
|
||||
/// 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.
|
||||
@@ -58,7 +58,6 @@ namespace AzToolsFramework
|
||||
{
|
||||
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.
|
||||
@@ -120,7 +119,6 @@ namespace AzToolsFramework
|
||||
{
|
||||
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.
|
||||
};
|
||||
|
||||
Fixed m_fixed;
|
||||
@@ -134,12 +132,11 @@ namespace AzToolsFramework
|
||||
|
||||
static StartInternal CalculateManipulationDataStart(
|
||||
const Fixed& fixed, const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale,
|
||||
const AZ::Transform& localTransform, const GridSnapAction& gridSnapAction,
|
||||
const ViewportInteraction::MouseInteraction& interaction, float intersectionDistance);
|
||||
const AZ::Transform& localTransform, const ViewportInteraction::MouseInteraction& interaction, float intersectionDistance);
|
||||
|
||||
static Action CalculateManipulationDataAction(
|
||||
const Fixed& fixed, const StartInternal& startInternal, const AZ::Transform& worldFromLocal,
|
||||
const AZ::Vector3& nonUniformScale, const AZ::Transform& localTransform,
|
||||
const GridSnapAction& gridSnapAction, const ViewportInteraction::MouseInteraction& interaction);
|
||||
const Fixed& fixed, const StartInternal& startInternal, const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale,
|
||||
const AZ::Transform& localTransform, const GridSnapParameters& gridSnapParams,
|
||||
const ViewportInteraction::MouseInteraction& interaction);
|
||||
};
|
||||
} // namespace AzToolsFramework
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
|
||||
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
|
||||
#include <AzToolsFramework/Prefab/Instance/Instance.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
|
||||
#include <AzToolsFramework/Prefab/Instance/InstanceEntityMapperInterface.h>
|
||||
#include <AzToolsFramework/Prefab/Instance/TemplateInstanceMapperInterface.h>
|
||||
|
||||
@@ -49,8 +50,7 @@ namespace AzToolsFramework
|
||||
m_alias = GenerateInstanceAlias();
|
||||
m_containerEntity = containerEntity ? AZStd::move(containerEntity)
|
||||
: AZStd::make_unique<AZ::Entity>();
|
||||
EntityAlias containerEntityAlias = GenerateEntityAlias();
|
||||
RegisterEntity(m_containerEntity->GetId(), containerEntityAlias);
|
||||
RegisterEntity(m_containerEntity->GetId(), PrefabDomUtils::ContainerEntityName);
|
||||
}
|
||||
|
||||
Instance::~Instance()
|
||||
@@ -311,8 +311,15 @@ namespace AzToolsFramework
|
||||
Instance& Instance::AddInstance(AZStd::unique_ptr<Instance> instance)
|
||||
{
|
||||
InstanceAlias newInstanceAlias = GenerateInstanceAlias();
|
||||
return AddInstance(AZStd::move(instance), newInstanceAlias);
|
||||
}
|
||||
|
||||
Instance& Instance::AddInstance(AZStd::unique_ptr<Instance> instance, InstanceAlias newInstanceAlias)
|
||||
{
|
||||
AZ_Assert(instance.get(), "instance argument is nullptr");
|
||||
AZ_Assert(m_nestedInstances.find(newInstanceAlias) == m_nestedInstances.end(), "InstanceAlias' unique id collision, this should never happen.");
|
||||
AZ_Assert(
|
||||
m_nestedInstances.find(newInstanceAlias) == m_nestedInstances.end(),
|
||||
"InstanceAlias' unique id collision, this should never happen.");
|
||||
instance->m_parent = this;
|
||||
instance->m_alias = newInstanceAlias;
|
||||
return *(m_nestedInstances[newInstanceAlias] = std::move(instance));
|
||||
@@ -613,6 +620,7 @@ namespace AzToolsFramework
|
||||
|
||||
AZStd::unique_ptr<AZ::Entity> Instance::DetachContainerEntity()
|
||||
{
|
||||
m_instanceEntityMapper->UnregisterEntity(m_containerEntity->GetId());
|
||||
return AZStd::move(m_containerEntity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ namespace AzToolsFramework
|
||||
using EntityAliasOptionalReference = AZStd::optional<AZStd::reference_wrapper<EntityAlias>>;
|
||||
using InstanceOptionalReference = AZStd::optional<AZStd::reference_wrapper<Instance>>;
|
||||
using InstanceOptionalConstReference = AZStd::optional<AZStd::reference_wrapper<const Instance>>;
|
||||
|
||||
using InstanceSet = AZStd::unordered_set<Instance*>;
|
||||
using InstanceSetConstReference = AZStd::optional<AZStd::reference_wrapper<const InstanceSet>>;
|
||||
using EntityOptionalReference = AZStd::optional<AZStd::reference_wrapper<AZ::Entity>>;
|
||||
@@ -85,12 +86,14 @@ namespace AzToolsFramework
|
||||
bool AddEntity(AZ::Entity& entity);
|
||||
bool AddEntity(AZ::Entity& entity, EntityAlias entityAlias);
|
||||
AZStd::unique_ptr<AZ::Entity> DetachEntity(const AZ::EntityId& entityId);
|
||||
void DetachEntities(const AZStd::function<void(AZStd::unique_ptr<AZ::Entity>)>& callback);
|
||||
void DetachNestedEntities(const AZStd::function<void(AZStd::unique_ptr<AZ::Entity>)>& callback);
|
||||
void RemoveNestedEntities(const AZStd::function<bool(const AZStd::unique_ptr<AZ::Entity>&)>& filter);
|
||||
|
||||
void Reset();
|
||||
|
||||
Instance& AddInstance(AZStd::unique_ptr<Instance> instance);
|
||||
Instance& AddInstance(AZStd::unique_ptr<Instance> instance, InstanceAlias instanceAlias);
|
||||
AZStd::unique_ptr<Instance> DetachNestedInstance(const InstanceAlias& instanceAlias);
|
||||
|
||||
/**
|
||||
@@ -171,6 +174,8 @@ namespace AzToolsFramework
|
||||
static EntityAlias GenerateEntityAlias();
|
||||
AliasPath GetAbsoluteInstanceAliasPath() const;
|
||||
|
||||
static InstanceAlias GenerateInstanceAlias();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Gets the entities owned by this instance
|
||||
@@ -182,14 +187,11 @@ namespace AzToolsFramework
|
||||
|
||||
void ClearEntities();
|
||||
|
||||
void DetachEntities(const AZStd::function<void(AZStd::unique_ptr<AZ::Entity>)>& callback);
|
||||
void RemoveEntities(const AZStd::function<bool(const AZStd::unique_ptr<AZ::Entity>&)>& filter);
|
||||
|
||||
bool RegisterEntity(const AZ::EntityId& entityId, const EntityAlias& entityAlias);
|
||||
AZStd::unique_ptr<AZ::Entity> DetachEntity(const EntityAlias& entityAlias);
|
||||
|
||||
static InstanceAlias GenerateInstanceAlias();
|
||||
|
||||
// Provide access to private data members in the serializer
|
||||
friend class JsonInstanceSerializer;
|
||||
friend class InstanceEntityIdMapper;
|
||||
|
||||
+24
@@ -152,6 +152,30 @@ namespace AzToolsFramework
|
||||
Instance::EntityList newEntities;
|
||||
if (PrefabDomUtils::LoadInstanceFromPrefabDom(*instanceToUpdate, newEntities, currentTemplate.GetPrefabDom()))
|
||||
{
|
||||
// If a link was created for a nested instance before the changes were propagated,
|
||||
// then we associate it correctly here
|
||||
instanceToUpdate->GetNestedInstances([&](AZStd::unique_ptr<Instance>& nestedInstance) {
|
||||
if (nestedInstance->GetLinkId() != InvalidLinkId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto linkId : currentTemplate.GetLinks())
|
||||
{
|
||||
LinkReference nestedLink = m_prefabSystemComponentInterface->FindLink(linkId);
|
||||
if (!nestedLink.has_value())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nestedLink->get().GetInstanceName() == nestedInstance->GetInstanceAlias())
|
||||
{
|
||||
nestedInstance->SetLinkId(linkId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
AzToolsFramework::EditorEntityContextRequestBus::Broadcast(
|
||||
&AzToolsFramework::EditorEntityContextRequests::HandleEntitiesAdded, newEntities);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
|
||||
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
|
||||
#include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
|
||||
#include <AzToolsFramework/Prefab/EditorPrefabComponent.h>
|
||||
#include <AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h>
|
||||
#include <AzToolsFramework/Prefab/Instance/Instance.h>
|
||||
#include <AzToolsFramework/Prefab/Instance/InstanceEntityIdMapper.h>
|
||||
@@ -191,24 +192,7 @@ namespace AzToolsFramework
|
||||
if (nestedInstanceLinkPatchesMap.contains(nestedInstance.get()))
|
||||
{
|
||||
previousPatch = AZStd::move(nestedInstanceLinkPatchesMap[nestedInstance.get()]);
|
||||
rapidjson::StringBuffer buffer;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
|
||||
previousPatch.Accept(writer);
|
||||
QString previousPatchString(buffer.GetString());
|
||||
|
||||
for (AZ::Entity* entity : entities)
|
||||
{
|
||||
AZ::EntityId entityId = entity->GetId();
|
||||
AZStd::string oldEntityAlias = oldEntityAliases[entityId];
|
||||
EntityAliasOptionalReference newEntityAlias = instanceToCreate->get().GetEntityAlias(entityId);
|
||||
AZ_Assert(
|
||||
newEntityAlias.has_value(),
|
||||
"Could not fetch entity alias for entity with id '%llu' during prefab creation.",
|
||||
static_cast<AZ::u64>(entityId));
|
||||
ReplaceOldAliases(previousPatchString, oldEntityAlias, newEntityAlias->get());
|
||||
}
|
||||
|
||||
previousPatch.Parse(previousPatchString.toUtf8().constData());
|
||||
UpdateLinkPatchesWithNewEntityAliases(previousPatch, oldEntityAliases, instanceToCreate->get());
|
||||
}
|
||||
|
||||
// These link creations shouldn't be undone because that would put the template in a non-usable state if a user
|
||||
@@ -243,11 +227,33 @@ namespace AzToolsFramework
|
||||
instanceToCreate->get(), commonRootEntityOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch(),
|
||||
AZStd::move(patch));
|
||||
|
||||
// Reset the transform of the container entity so that the new values aren't saved in the new prefab's dom.
|
||||
// The new values were saved in the link, so propagation will apply them correctly.
|
||||
{
|
||||
AZ::Entity* containerEntity = GetEntityById(containerEntityId);
|
||||
|
||||
PrefabDom containerBeforeReset;
|
||||
m_instanceToTemplateInterface->GenerateDomForEntity(containerBeforeReset, *containerEntity);
|
||||
|
||||
AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetParent, AZ::EntityId());
|
||||
AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetLocalTM, AZ::Transform::CreateIdentity());
|
||||
|
||||
PrefabDom containerAfterReset;
|
||||
m_instanceToTemplateInterface->GenerateDomForEntity(containerAfterReset, *containerEntity);
|
||||
|
||||
// Update the state of the entity
|
||||
PrefabUndoEntityUpdate* state = aznew PrefabUndoEntityUpdate(AZStd::to_string(static_cast<AZ::u64>(containerEntityId)));
|
||||
state->SetParent(undoBatch.GetUndoBatch());
|
||||
state->Capture(containerBeforeReset, containerAfterReset, containerEntityId);
|
||||
|
||||
state->Redo();
|
||||
}
|
||||
|
||||
// This clears any entities marked as dirty due to reparenting of entities during the process of creating a prefab.
|
||||
// We are doing this so that the changes in those enities are not queued up twice for propagation.
|
||||
// We are doing this so that the changes in those entities are not queued up twice for propagation.
|
||||
AzToolsFramework::ToolsApplicationRequestBus::Broadcast(
|
||||
&AzToolsFramework::ToolsApplicationRequestBus::Events::ClearDirtyEntities);
|
||||
|
||||
|
||||
// Select Container Entity
|
||||
{
|
||||
auto selectionUndo = aznew SelectionCommand({containerEntityId}, "Select Prefab Container Entity");
|
||||
@@ -291,7 +297,7 @@ namespace AzToolsFramework
|
||||
m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(patch, containerEntityId);
|
||||
|
||||
// Update the cache - this prevents these changes from being stored in the regular undo/redo nodes
|
||||
m_prefabUndoCache.Store(containerEntityId, AZStd::move(containerEntityDomAfter));
|
||||
m_prefabUndoCache.Store(containerEntityId, AZStd::move(containerEntityDomAfter), parentEntityId);
|
||||
|
||||
return AZStd::move(patch);
|
||||
}
|
||||
@@ -385,8 +391,8 @@ namespace AzToolsFramework
|
||||
|
||||
CreateLink(instanceToCreate->get(), instanceToParentUnder->get().GetTemplateId(), undoBatch.GetUndoBatch(), AZStd::move(patch));
|
||||
|
||||
// Update the cache - this prevents these changes from being stored in the regular undo/redo nodes
|
||||
m_prefabUndoCache.Store(containerEntityId, AZStd::move(containerEntityDomAfter));
|
||||
AzToolsFramework::ToolsApplicationRequestBus::Broadcast(
|
||||
&AzToolsFramework::ToolsApplicationRequestBus::Events::ClearDirtyEntities);
|
||||
}
|
||||
|
||||
return AZ::Success();
|
||||
@@ -589,54 +595,199 @@ namespace AzToolsFramework
|
||||
{
|
||||
// Create Undo node on entities if they belong to an instance
|
||||
InstanceOptionalReference owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(entityId);
|
||||
|
||||
if (owningInstance.has_value())
|
||||
if (!owningInstance.has_value())
|
||||
{
|
||||
PrefabDom afterState;
|
||||
AZ::Entity* entity = GetEntityById(entityId);
|
||||
if (entity)
|
||||
return;
|
||||
}
|
||||
|
||||
AZ::Entity* entity = GetEntityById(entityId);
|
||||
if (!entity)
|
||||
{
|
||||
m_prefabUndoCache.PurgeCache(entityId);
|
||||
return;
|
||||
}
|
||||
|
||||
PrefabDom beforeState;
|
||||
AZ::EntityId beforeParentId;
|
||||
m_prefabUndoCache.Retrieve(entityId, beforeState, beforeParentId);
|
||||
|
||||
PrefabDom afterState;
|
||||
AZ::EntityId afterParentId;
|
||||
AZ::TransformBus::EventResult(afterParentId, entityId, &AZ::TransformBus::Events::GetParentId);
|
||||
|
||||
m_instanceToTemplateInterface->GenerateDomForEntity(afterState, *entity);
|
||||
|
||||
PrefabDom patch;
|
||||
m_instanceToTemplateInterface->GeneratePatch(patch, beforeState, afterState);
|
||||
m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(patch, entityId);
|
||||
|
||||
if (patch.IsArray() && !patch.Empty() && beforeState.IsObject())
|
||||
{
|
||||
bool isInstanceContainerEntity = IsInstanceContainerEntity(entityId) && !IsLevelInstanceContainerEntity(entityId);
|
||||
bool isNewParentOwnedByDifferentInstance = false;
|
||||
|
||||
if (beforeParentId != afterParentId)
|
||||
{
|
||||
PrefabDom beforeState;
|
||||
m_prefabUndoCache.Retrieve(entityId, beforeState);
|
||||
// If the entity parent changed, verify if the owning instance changed too
|
||||
InstanceOptionalReference beforeOwningInstance = m_instanceEntityMapperInterface->FindOwningInstance(beforeParentId);
|
||||
InstanceOptionalReference afterOwningInstance = m_instanceEntityMapperInterface->FindOwningInstance(afterParentId);
|
||||
|
||||
m_instanceToTemplateInterface->GenerateDomForEntity(afterState, *entity);
|
||||
|
||||
PrefabDom patch;
|
||||
m_instanceToTemplateInterface->GeneratePatch(patch, beforeState, afterState);
|
||||
|
||||
if (patch.IsArray() && !patch.Empty() && beforeState.IsObject())
|
||||
if (beforeOwningInstance.has_value() && afterOwningInstance.has_value() &&
|
||||
(&beforeOwningInstance->get() != &afterOwningInstance->get()))
|
||||
{
|
||||
if (IsInstanceContainerEntity(entityId) && !IsLevelInstanceContainerEntity(entityId))
|
||||
{
|
||||
m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(patch, entityId);
|
||||
|
||||
// Save these changes as patches to the link
|
||||
PrefabUndoLinkUpdate* linkUpdate =
|
||||
aznew PrefabUndoLinkUpdate(AZStd::to_string(static_cast<AZ::u64>(entityId)));
|
||||
linkUpdate->SetParent(parentUndoBatch);
|
||||
linkUpdate->Capture(patch, owningInstance->get().GetLinkId());
|
||||
|
||||
linkUpdate->Redo();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update the state of the entity
|
||||
PrefabUndoEntityUpdate* state = aznew PrefabUndoEntityUpdate(AZStd::to_string(static_cast<AZ::u64>(entityId)));
|
||||
state->SetParent(parentUndoBatch);
|
||||
state->Capture(beforeState, afterState, entityId);
|
||||
|
||||
state->Redo();
|
||||
}
|
||||
isNewParentOwnedByDifferentInstance = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the cache
|
||||
m_prefabUndoCache.Store(entityId, AZStd::move(afterState));
|
||||
if (isInstanceContainerEntity)
|
||||
{
|
||||
if (isNewParentOwnedByDifferentInstance)
|
||||
{
|
||||
Internal_HandleInstanceChange(parentUndoBatch, entity, beforeParentId, afterParentId);
|
||||
|
||||
PrefabDom afterStateafterReparenting;
|
||||
m_instanceToTemplateInterface->GenerateDomForEntity(afterStateafterReparenting, *entity);
|
||||
|
||||
PrefabDom newPatch;
|
||||
m_instanceToTemplateInterface->GeneratePatch(newPatch, afterState, afterStateafterReparenting);
|
||||
m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(newPatch, entityId);
|
||||
|
||||
InstanceOptionalReference owningInstanceAfterReparenting =
|
||||
m_instanceEntityMapperInterface->FindOwningInstance(entityId);
|
||||
|
||||
Internal_HandleContainerOverride(
|
||||
parentUndoBatch, entityId, newPatch, owningInstanceAfterReparenting->get().GetLinkId());
|
||||
}
|
||||
else
|
||||
{
|
||||
Internal_HandleContainerOverride(
|
||||
parentUndoBatch, entityId, patch, owningInstance->get().GetLinkId());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_prefabUndoCache.PurgeCache(entityId);
|
||||
Internal_HandleEntityChange(parentUndoBatch, entityId, beforeState, afterState);
|
||||
|
||||
if (isNewParentOwnedByDifferentInstance)
|
||||
{
|
||||
Internal_HandleInstanceChange(parentUndoBatch, entity, beforeParentId, afterParentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_prefabUndoCache.UpdateCache(entityId);
|
||||
}
|
||||
|
||||
void PrefabPublicHandler::Internal_HandleContainerOverride(
|
||||
UndoSystem::URSequencePoint* undoBatch, AZ::EntityId entityId, const PrefabDom& patch, const LinkId linkId)
|
||||
{
|
||||
// Save these changes as patches to the link
|
||||
PrefabUndoLinkUpdate* linkUpdate = aznew PrefabUndoLinkUpdate(AZStd::to_string(static_cast<AZ::u64>(entityId)));
|
||||
linkUpdate->SetParent(undoBatch);
|
||||
linkUpdate->Capture(patch, linkId);
|
||||
|
||||
linkUpdate->Redo();
|
||||
}
|
||||
|
||||
void PrefabPublicHandler::Internal_HandleEntityChange(
|
||||
UndoSystem::URSequencePoint* undoBatch, AZ::EntityId entityId, PrefabDom& beforeState, PrefabDom& afterState)
|
||||
{
|
||||
// Update the state of the entity
|
||||
PrefabUndoEntityUpdate* state = aznew PrefabUndoEntityUpdate(AZStd::to_string(static_cast<AZ::u64>(entityId)));
|
||||
state->SetParent(undoBatch);
|
||||
state->Capture(beforeState, afterState, entityId);
|
||||
|
||||
state->Redo();
|
||||
}
|
||||
|
||||
void PrefabPublicHandler::Internal_HandleInstanceChange(
|
||||
UndoSystem::URSequencePoint* undoBatch, AZ::Entity* entity, AZ::EntityId beforeParentId, AZ::EntityId afterParentId)
|
||||
{
|
||||
// If the entity parent changed, verify if the owning instance changed too
|
||||
InstanceOptionalReference beforeOwningInstance = m_instanceEntityMapperInterface->FindOwningInstance(beforeParentId);
|
||||
InstanceOptionalReference afterOwningInstance = m_instanceEntityMapperInterface->FindOwningInstance(afterParentId);
|
||||
|
||||
EntityList entities;
|
||||
AZStd::vector<Instance*> instances;
|
||||
|
||||
// Retrieve all descendant entities and instances of this entity that belonged to the same owning instance.
|
||||
RetrieveAndSortPrefabEntitiesAndInstances({ entity }, beforeOwningInstance->get(), entities, instances);
|
||||
|
||||
AZStd::vector<AZStd::unique_ptr<Instance>> instanceUniquePtrs;
|
||||
AZStd::vector<AZStd::pair<Instance*, PrefabDom>> instancePatches;
|
||||
|
||||
// Remove Entities and Instances from the prior instance
|
||||
{
|
||||
// Remove Instances
|
||||
for (Instance* nestedInstance : instances)
|
||||
{
|
||||
auto linkRef = m_prefabSystemComponentInterface->FindLink(nestedInstance->GetLinkId());
|
||||
|
||||
PrefabDom oldLinkPatches;
|
||||
|
||||
if (linkRef.has_value())
|
||||
{
|
||||
auto patches = linkRef->get().GetLinkPatches();
|
||||
if (patches.has_value())
|
||||
{
|
||||
oldLinkPatches.CopyFrom(patches->get(), oldLinkPatches.GetAllocator());
|
||||
}
|
||||
}
|
||||
|
||||
auto nestedInstanceUniquePtr = beforeOwningInstance->get().DetachNestedInstance(nestedInstance->GetInstanceAlias());
|
||||
RemoveLink(nestedInstanceUniquePtr, beforeOwningInstance->get().GetTemplateId(), undoBatch);
|
||||
|
||||
instancePatches.emplace_back(AZStd::make_pair(nestedInstanceUniquePtr.get(), AZStd::move(oldLinkPatches)));
|
||||
instanceUniquePtrs.emplace_back(AZStd::move(nestedInstanceUniquePtr));
|
||||
}
|
||||
|
||||
// Get the previous state of the prior instance for undo/redo purposes
|
||||
PrefabDom beforeInstanceDomBeforeRemoval;
|
||||
m_instanceToTemplateInterface->GenerateDomForInstance(beforeInstanceDomBeforeRemoval, beforeOwningInstance->get());
|
||||
|
||||
// Remove Entities
|
||||
for (AZ::Entity* nestedEntity : entities)
|
||||
{
|
||||
beforeOwningInstance->get().DetachEntity(nestedEntity->GetId()).release();
|
||||
}
|
||||
|
||||
// Create the Update node for the prior owning instance
|
||||
// Instance removal will be taken care of from the RemoveLink function for undo/redo purposes
|
||||
PrefabUndoHelpers::UpdatePrefabInstance(
|
||||
beforeOwningInstance->get(), "Update prior prefab instance", beforeInstanceDomBeforeRemoval, undoBatch);
|
||||
}
|
||||
|
||||
// Add Entities and Instances to new instance
|
||||
{
|
||||
// Add Instances
|
||||
for (auto& instanceUniquePtr : instanceUniquePtrs)
|
||||
{
|
||||
afterOwningInstance->get().AddInstance(AZStd::move(instanceUniquePtr));
|
||||
}
|
||||
|
||||
// Create Links
|
||||
for (auto& instanceInfo : instancePatches)
|
||||
{
|
||||
// Add a new link with the old dom
|
||||
CreateLink(
|
||||
*instanceInfo.first, afterOwningInstance->get().GetTemplateId(), undoBatch,
|
||||
AZStd::move(instanceInfo.second));
|
||||
}
|
||||
|
||||
// Get the previous state of the new instance for undo/redo purposes
|
||||
PrefabDom afterInstanceDomBeforeAdd;
|
||||
m_instanceToTemplateInterface->GenerateDomForInstance(afterInstanceDomBeforeAdd, afterOwningInstance->get());
|
||||
|
||||
// Add Entities
|
||||
for (AZ::Entity* nestedEntity : entities)
|
||||
{
|
||||
afterOwningInstance->get().AddEntity(*nestedEntity);
|
||||
}
|
||||
|
||||
// Create the Update node for the new owning instance
|
||||
PrefabUndoHelpers::UpdatePrefabInstance(
|
||||
afterOwningInstance->get(), "Update new prefab instance", afterInstanceDomBeforeAdd, undoBatch);
|
||||
}
|
||||
}
|
||||
|
||||
bool PrefabPublicHandler::IsInstanceContainerEntity(AZ::EntityId entityId) const
|
||||
@@ -748,16 +899,29 @@ namespace AzToolsFramework
|
||||
|
||||
if (!EntitiesBelongToSameInstance(entityIds))
|
||||
{
|
||||
return AZ::Failure(AZStd::string("Cannot duplicate multiple "
|
||||
"entities belonging to different instances with one operation."));
|
||||
return AZ::Failure(AZStd::string("Cannot duplicate multiple entities belonging to different instances with one operation."
|
||||
"Change your selection to contain entities in the same instance."));
|
||||
}
|
||||
|
||||
// We've already verified the entities are all owned by the same instance,
|
||||
// so we can just retrieve our instance from the first entity in the list.
|
||||
InstanceOptionalReference commonEntityOwningInstance = GetOwnerInstanceByEntityId(entityIds[0]);
|
||||
AZ_Assert(
|
||||
commonEntityOwningInstance.has_value(),
|
||||
"Failed to duplicate : Couldn't get a valid owning instance for the common root entity of the entities provided");
|
||||
AZ::EntityId firstEntityIdToDuplicate = entityIds[0];
|
||||
InstanceOptionalReference commonOwningInstance = GetOwnerInstanceByEntityId(firstEntityIdToDuplicate);
|
||||
if (!commonOwningInstance.has_value())
|
||||
{
|
||||
return AZ::Failure(AZStd::string("Failed to duplicate : Couldn't get a valid owning instance for the common root entity of the entities provided."));
|
||||
}
|
||||
|
||||
// If the first entity id is a container entity id, then we need to mark its parent as the common owning instance because you
|
||||
// cannot duplicate an instance from itself.
|
||||
if (commonOwningInstance->get().GetContainerEntityId() == firstEntityIdToDuplicate)
|
||||
{
|
||||
commonOwningInstance = commonOwningInstance->get().GetParentInstance();
|
||||
}
|
||||
if (!commonOwningInstance.has_value())
|
||||
{
|
||||
return AZ::Failure(AZStd::string("Failed to duplicate : Couldn't get a valid owning instance for the common root entity of the entities provided."));
|
||||
}
|
||||
|
||||
// This will cull out any entities that have ancestors in the list, since we will end up duplicating
|
||||
// the full nested hierarchy with what is returned from RetrieveAndSortPrefabEntitiesAndInstances
|
||||
@@ -770,105 +934,63 @@ namespace AzToolsFramework
|
||||
{
|
||||
AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::AzToolsFramework, "DuplicateEntitiesInInstance::UndoCaptureAndDuplicateEntities");
|
||||
|
||||
// Take a snapshot of the instance DOM before we manipulate it
|
||||
Prefab::PrefabDom instanceDomBefore;
|
||||
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, commonEntityOwningInstance->get());
|
||||
|
||||
AZStd::vector<AZ::Entity*> entities;
|
||||
AZStd::vector<Instance*> instances;
|
||||
|
||||
// Gather all entities/instances in the hierarchy, but don't detach them because we are duplicating not deleting.
|
||||
EntityList inputEntityList = EntityIdSetToEntityList(duplicationSet);
|
||||
bool success = RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonEntityOwningInstance->get(), entities, instances);
|
||||
bool success = RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonOwningInstance->get(), entities, instances);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
return AZ::Failure(AZStd::string("Failed to retrieve entities and instances from the given list of entity ids for duplication"));
|
||||
}
|
||||
|
||||
// Make a copy of our before instance DOM where we will add our duplicated entities
|
||||
Prefab::PrefabDom instanceDomAfter;
|
||||
// Take a snapshot of the instance DOM before we manipulate it
|
||||
PrefabDom instanceDomBefore;
|
||||
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, commonOwningInstance->get());
|
||||
|
||||
// Make a copy of our before instance DOM where we will add our duplicated entities and/or instances
|
||||
PrefabDom instanceDomAfter;
|
||||
instanceDomAfter.CopyFrom(instanceDomBefore, instanceDomAfter.GetAllocator());
|
||||
|
||||
AZStd::unordered_map<EntityAlias, EntityAlias> oldAliasToNewAliasMap;
|
||||
AZStd::unordered_map<EntityAlias, QString> aliasToEntityDomMap;
|
||||
EntityIdList duplicatedEntityAndInstanceIds;
|
||||
|
||||
for (AZ::Entity* entity : entities)
|
||||
{
|
||||
EntityAliasOptionalReference oldAliasRef = commonEntityOwningInstance->get().GetEntityAlias(entity->GetId());
|
||||
AZ_Assert(oldAliasRef.has_value(), "No alias found for Entity in the DOM");
|
||||
EntityAlias oldAlias = oldAliasRef.value();
|
||||
// Duplicate any nested entities and instances as requested
|
||||
AZStd::unordered_map<InstanceAlias, Instance*> newInstanceAliasToOldInstanceMap;
|
||||
DuplicateNestedEntitiesInInstance(commonOwningInstance->get(),
|
||||
entities, instanceDomAfter, duplicatedEntityAndInstanceIds);
|
||||
DuplicateNestedInstancesInInstance(commonOwningInstance->get(),
|
||||
instances, instanceDomAfter, duplicatedEntityAndInstanceIds,
|
||||
newInstanceAliasToOldInstanceMap);
|
||||
|
||||
// Give this the outer allocator so that the memory reference will be valid when
|
||||
// it gets used for AddMember
|
||||
Prefab::PrefabDom entityDomBefore(&instanceDomAfter.GetAllocator());
|
||||
m_instanceToTemplateInterface->GenerateDomForEntity(entityDomBefore, *entity);
|
||||
|
||||
// Keep track of the old alias <-> new alias mapping for this duplicated entity
|
||||
// so we can fixup references later
|
||||
EntityAlias newEntityAlias = Instance::GenerateEntityAlias();
|
||||
oldAliasToNewAliasMap.insert(AZStd::make_pair(oldAlias, newEntityAlias));
|
||||
|
||||
rapidjson::StringBuffer buffer;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
|
||||
entityDomBefore.Accept(writer);
|
||||
|
||||
// Store our duplicated Entity DOM with its new alias as a string
|
||||
// so that we can fixup entity alias references before adding it
|
||||
// to the Entities member of our instance DOM
|
||||
QString entityDomString(buffer.GetString());
|
||||
aliasToEntityDomMap.insert(AZStd::make_pair(newEntityAlias, entityDomString));
|
||||
}
|
||||
|
||||
auto entitiesIter = instanceDomAfter.FindMember(PrefabDomUtils::EntitiesName);
|
||||
AZ_Assert(entitiesIter != instanceDomAfter.MemberEnd(), "Instance DOM missing the Entities member.");
|
||||
|
||||
// Now that all the duplicated Entity DOMs have been created, we need to iterate
|
||||
// through them and replace any previous EntityAlias references with the new ones.
|
||||
// These are more than just parent entity references for nested entities, this will
|
||||
// also cover any EntityId references that were made in the components between them.
|
||||
for (auto aliasEntityPair : aliasToEntityDomMap)
|
||||
{
|
||||
EntityAlias newEntityAlias = aliasEntityPair.first;
|
||||
QString newEntityDomString = aliasEntityPair.second;
|
||||
|
||||
// Replace all of the old alias references with the new ones
|
||||
// We bookend the aliases with \" and also with a / as an extra precaution to prevent
|
||||
// inadvertently replacing a matching string vs. where an actual EntityId is expected
|
||||
// This will cover both cases where an alias could be used in a normal entity vs. an instance
|
||||
for (auto aliasMapIter : oldAliasToNewAliasMap)
|
||||
{
|
||||
ReplaceOldAliases(newEntityDomString, aliasMapIter.first, aliasMapIter.second);
|
||||
}
|
||||
|
||||
// Create the new Entity DOM from parsing the JSON string
|
||||
Prefab::PrefabDom entityDomAfter(&instanceDomAfter.GetAllocator());
|
||||
entityDomAfter.Parse(newEntityDomString.toUtf8().constData());
|
||||
|
||||
// Add the new Entity DOM to the Entities member of the instance
|
||||
rapidjson::Value aliasName(newEntityAlias.c_str(), newEntityAlias.length(), instanceDomAfter.GetAllocator());
|
||||
entitiesIter->value.AddMember(AZStd::move(aliasName), entityDomAfter, instanceDomAfter.GetAllocator());
|
||||
}
|
||||
|
||||
PrefabUndoInstance* command = aznew PrefabUndoInstance("Entity duplication");
|
||||
PrefabUndoInstance* command = aznew PrefabUndoInstance("Entity/Instance duplication");
|
||||
command->SetParent(undoBatch.GetUndoBatch());
|
||||
command->Capture(instanceDomBefore, instanceDomAfter, commonEntityOwningInstance->get().GetTemplateId());
|
||||
command->RunRedo();
|
||||
command->Capture(instanceDomBefore, instanceDomAfter, commonOwningInstance->get().GetTemplateId());
|
||||
command->Redo();
|
||||
|
||||
EntityIdList duplicatedEntityIds;
|
||||
for (auto aliasMapIter : oldAliasToNewAliasMap)
|
||||
// Create links for our duplicated instances (if any were duplicated)
|
||||
for (auto [newInstanceAlias, oldInstance] : newInstanceAliasToOldInstanceMap)
|
||||
{
|
||||
EntityAlias newEntityAlias = aliasMapIter.second;
|
||||
LinkId oldLinkId = oldInstance->GetLinkId();
|
||||
auto linkRef = m_prefabSystemComponentInterface->FindLink(oldLinkId);
|
||||
AZ_Assert(
|
||||
linkRef.has_value(), "Unable to find link with id '%llu' during instance duplication.",
|
||||
oldLinkId);
|
||||
|
||||
AliasPath absoluteEntityPath = commonEntityOwningInstance->get().GetAbsoluteInstanceAliasPath();
|
||||
absoluteEntityPath.Append(newEntityAlias);
|
||||
PrefabDomValueReference linkPatches = linkRef->get().GetLinkPatches();
|
||||
AZ_Assert(
|
||||
linkPatches.has_value(), "Link with id '%llu' is missing patches.",
|
||||
oldLinkId);
|
||||
|
||||
AZ::EntityId newEntityId = InstanceEntityIdMapper::GenerateEntityIdForAliasPath(absoluteEntityPath);
|
||||
duplicatedEntityIds.push_back(newEntityId);
|
||||
PrefabDom linkPatchesCopy;
|
||||
linkPatchesCopy.CopyFrom(linkPatches->get(), linkPatchesCopy.GetAllocator());
|
||||
|
||||
m_prefabSystemComponentInterface->CreateLink(
|
||||
commonOwningInstance->get().GetTemplateId(), oldInstance->GetTemplateId(), newInstanceAlias, linkPatchesCopy);
|
||||
}
|
||||
|
||||
// Select the duplicated entities
|
||||
auto selectionUndo = aznew SelectionCommand(duplicatedEntityIds, "Select Duplicated Entities");
|
||||
// Select the duplicated entities/instances
|
||||
auto selectionUndo = aznew SelectionCommand(duplicatedEntityAndInstanceIds, "Select Duplicated Entities/Instances");
|
||||
selectionUndo->SetParent(undoBatch.GetUndoBatch());
|
||||
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::RunRedoSeparately, selectionUndo);
|
||||
}
|
||||
@@ -989,6 +1111,123 @@ namespace AzToolsFramework
|
||||
return AZ::Success();
|
||||
}
|
||||
|
||||
PrefabOperationResult PrefabPublicHandler::DetachPrefab(const AZ::EntityId& containerEntityId)
|
||||
{
|
||||
if (!containerEntityId.IsValid())
|
||||
{
|
||||
return AZ::Failure(AZStd::string("Cannot detach Prefab Instance with invalid container entity."));
|
||||
}
|
||||
|
||||
if (IsLevelInstanceContainerEntity(containerEntityId))
|
||||
{
|
||||
return AZ::Failure(AZStd::string("Cannot detach level Prefab Instance."));
|
||||
}
|
||||
|
||||
InstanceOptionalReference owningInstance = GetOwnerInstanceByEntityId(containerEntityId);
|
||||
if (owningInstance->get().GetContainerEntityId() != containerEntityId)
|
||||
{
|
||||
return AZ::Failure(AZStd::string("Input entity should be its owning Instance's container entity."));
|
||||
}
|
||||
|
||||
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
|
||||
|
||||
{
|
||||
AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::AzToolsFramework, "Internal::DetachPrefab:UndoCapture");
|
||||
|
||||
ScopedUndoBatch undoBatch("Detach Prefab");
|
||||
|
||||
InstanceOptionalReference getParentInstanceResult = owningInstance->get().GetParentInstance();
|
||||
AZ_Assert(getParentInstanceResult.has_value(), "Can't get parent Instance from Instance of given container entity.");
|
||||
|
||||
auto& parentInstance = getParentInstanceResult->get();
|
||||
const auto parentTemplateId = parentInstance.GetTemplateId();
|
||||
|
||||
{
|
||||
auto instancePtr = parentInstance.DetachNestedInstance(owningInstance->get().GetInstanceAlias());
|
||||
AZ_Assert(instancePtr, "Can't detach selected Instance from its parent Instance.");
|
||||
|
||||
RemoveLink(instancePtr, parentTemplateId, undoBatch.GetUndoBatch());
|
||||
|
||||
Prefab::PrefabDom instanceDomBefore;
|
||||
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, parentInstance);
|
||||
|
||||
AZStd::unordered_map<AZ::EntityId, AZStd::string> oldEntityAliases;
|
||||
oldEntityAliases.emplace(containerEntityId, instancePtr->GetEntityAlias(containerEntityId)->get());
|
||||
|
||||
auto containerEntityPtr = instancePtr->DetachContainerEntity();
|
||||
auto& containerEntity = *containerEntityPtr.release();
|
||||
auto editorPrefabComponent = containerEntity.FindComponent<EditorPrefabComponent>();
|
||||
containerEntity.Deactivate();
|
||||
const bool editorPrefabComponentRemoved = containerEntity.RemoveComponent(editorPrefabComponent);
|
||||
AZ_Assert(editorPrefabComponentRemoved, "Remove EditorPrefabComponent failed.");
|
||||
delete editorPrefabComponent;
|
||||
containerEntity.Activate();
|
||||
|
||||
const bool containerEntityAdded = parentInstance.AddEntity(containerEntity);
|
||||
AZ_Assert(containerEntityAdded, "Add target Instance's container entity to its parent Instance failed.");
|
||||
|
||||
EntityIdList entityIds;
|
||||
entityIds.emplace_back(containerEntity.GetId());
|
||||
|
||||
instancePtr->GetEntities(
|
||||
[&](AZStd::unique_ptr<AZ::Entity>& entityPtr)
|
||||
{
|
||||
oldEntityAliases.emplace(entityPtr->GetId(), instancePtr->GetEntityAlias(entityPtr->GetId())->get());
|
||||
return true;
|
||||
});
|
||||
|
||||
instancePtr->DetachEntities(
|
||||
[&](AZStd::unique_ptr<AZ::Entity> entityPtr)
|
||||
{
|
||||
auto& entity = *entityPtr.release();
|
||||
const bool entityAdded = parentInstance.AddEntity(entity);
|
||||
AZ_Assert(entityAdded, "Add target Instance's entity to its parent Instance failed.");
|
||||
|
||||
entityIds.emplace_back(entity.GetId());
|
||||
});
|
||||
|
||||
Prefab::PrefabDom instanceDomAfter;
|
||||
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomAfter, parentInstance);
|
||||
|
||||
PrefabUndoInstance* command = aznew PrefabUndoInstance("Instance detachment");
|
||||
command->Capture(instanceDomBefore, instanceDomAfter, parentTemplateId);
|
||||
command->SetParent(undoBatch.GetUndoBatch());
|
||||
{
|
||||
AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::AzToolsFramework, "Internal::DetachPrefab:RunRedo");
|
||||
command->RunRedo();
|
||||
}
|
||||
|
||||
const auto instanceTemplateId = instancePtr->GetTemplateId();
|
||||
auto parentContainerEntityId = parentInstance.GetContainerEntityId();
|
||||
instancePtr->GetNestedInstances(
|
||||
[&](AZStd::unique_ptr<Instance>& nestedInstancePtr)
|
||||
{
|
||||
//get previous link patch
|
||||
auto linkRef = m_prefabSystemComponentInterface->FindLink(nestedInstancePtr->GetLinkId());
|
||||
PrefabDomValueReference linkPatches = linkRef->get().GetLinkPatches();
|
||||
AZ_Assert(
|
||||
linkPatches.has_value(), "Unable to get patches on link with id '%llu' during prefab creation.",
|
||||
nestedInstancePtr->GetLinkId());
|
||||
|
||||
PrefabDom linkPatchesCopy;
|
||||
linkPatchesCopy.CopyFrom(linkPatches->get(), linkPatchesCopy.GetAllocator());
|
||||
|
||||
RemoveLink(nestedInstancePtr, instanceTemplateId, undoBatch.GetUndoBatch());
|
||||
|
||||
UpdateLinkPatchesWithNewEntityAliases(linkPatchesCopy, oldEntityAliases, parentInstance);
|
||||
|
||||
CreateLink(*nestedInstancePtr, parentTemplateId, undoBatch.GetUndoBatch(),
|
||||
AZStd::move(linkPatchesCopy), true);
|
||||
});
|
||||
}
|
||||
|
||||
AzToolsFramework::ToolsApplicationRequestBus::Broadcast(
|
||||
&AzToolsFramework::ToolsApplicationRequestBus::Events::ClearDirtyEntities);
|
||||
}
|
||||
|
||||
return AZ::Success();
|
||||
}
|
||||
|
||||
void PrefabPublicHandler::GenerateContainerEntityTransform(const EntityList& topLevelEntities,
|
||||
AZ::Vector3& translation, AZ::Quaternion& rotation)
|
||||
{
|
||||
@@ -1240,8 +1479,159 @@ namespace AzToolsFramework
|
||||
return true;
|
||||
}
|
||||
|
||||
void PrefabPublicHandler::DuplicateNestedEntitiesInInstance(Instance& commonOwningInstance,
|
||||
const AZStd::vector<AZ::Entity*>& entities, PrefabDom& domToAddDuplicatedEntitiesUnder,
|
||||
EntityIdList& duplicatedEntityIds)
|
||||
{
|
||||
if (entities.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AZStd::unordered_map<EntityAlias, EntityAlias> oldAliasToNewAliasMap;
|
||||
AZStd::unordered_map<EntityAlias, QString> aliasToEntityDomMap;
|
||||
|
||||
for (AZ::Entity* entity : entities)
|
||||
{
|
||||
EntityAliasOptionalReference oldAliasRef = commonOwningInstance.GetEntityAlias(entity->GetId());
|
||||
AZ_Assert(oldAliasRef.has_value(), "No alias found for Entity in the DOM");
|
||||
EntityAlias oldAlias = oldAliasRef.value();
|
||||
|
||||
// Give this the outer allocator so that the memory reference will be valid when
|
||||
// it gets used for AddMember
|
||||
PrefabDom entityDomBefore(&domToAddDuplicatedEntitiesUnder.GetAllocator());
|
||||
m_instanceToTemplateInterface->GenerateDomForEntity(entityDomBefore, *entity);
|
||||
|
||||
// Keep track of the old alias <-> new alias mapping for this duplicated entity
|
||||
// so we can fixup references later
|
||||
EntityAlias newEntityAlias = Instance::GenerateEntityAlias();
|
||||
oldAliasToNewAliasMap.emplace(oldAlias, newEntityAlias);
|
||||
|
||||
rapidjson::StringBuffer buffer;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
|
||||
entityDomBefore.Accept(writer);
|
||||
|
||||
// Store our duplicated Entity DOM with its new alias as a string
|
||||
// so that we can fixup entity alias references before adding it
|
||||
// to the Entities member of our instance DOM
|
||||
QString entityDomString(buffer.GetString());
|
||||
aliasToEntityDomMap.emplace(newEntityAlias, entityDomString);
|
||||
}
|
||||
|
||||
auto entitiesIter = domToAddDuplicatedEntitiesUnder.FindMember(PrefabDomUtils::EntitiesName);
|
||||
AZ_Assert(entitiesIter != domToAddDuplicatedEntitiesUnder.MemberEnd(), "Instance DOM missing the Entities member.");
|
||||
|
||||
// Now that all the duplicated Entity DOMs have been created, we need to iterate
|
||||
// through them and replace any previous EntityAlias references with the new ones.
|
||||
// These are more than just parent entity references for nested entities, this will
|
||||
// also cover any EntityId references that were made in the components between them.
|
||||
for (auto [newEntityAlias, newEntityDomString] : aliasToEntityDomMap)
|
||||
{
|
||||
// Replace all of the old alias references with the new ones
|
||||
for (auto [oldAlias, newAlias] : oldAliasToNewAliasMap)
|
||||
{
|
||||
ReplaceOldAliases(newEntityDomString, oldAlias, newAlias);
|
||||
}
|
||||
|
||||
// Create the new Entity DOM from parsing the JSON string
|
||||
PrefabDom entityDomAfter(&domToAddDuplicatedEntitiesUnder.GetAllocator());
|
||||
entityDomAfter.Parse(newEntityDomString.toUtf8().constData());
|
||||
|
||||
// Add the new Entity DOM to the Entities member of the instance
|
||||
rapidjson::Value aliasName(newEntityAlias.c_str(), newEntityAlias.length(), domToAddDuplicatedEntitiesUnder.GetAllocator());
|
||||
entitiesIter->value.AddMember(AZStd::move(aliasName), entityDomAfter, domToAddDuplicatedEntitiesUnder.GetAllocator());
|
||||
}
|
||||
|
||||
for (auto aliasMapIter : oldAliasToNewAliasMap)
|
||||
{
|
||||
EntityAlias newEntityAlias = aliasMapIter.second;
|
||||
|
||||
AliasPath absoluteEntityPath = commonOwningInstance.GetAbsoluteInstanceAliasPath();
|
||||
absoluteEntityPath.Append(newEntityAlias);
|
||||
|
||||
AZ::EntityId newEntityId = InstanceEntityIdMapper::GenerateEntityIdForAliasPath(absoluteEntityPath);
|
||||
duplicatedEntityIds.push_back(newEntityId);
|
||||
}
|
||||
}
|
||||
|
||||
void PrefabPublicHandler::DuplicateNestedInstancesInInstance(Instance& commonOwningInstance,
|
||||
const AZStd::vector<Instance*>& instances, PrefabDom& domToAddDuplicatedInstancesUnder,
|
||||
EntityIdList& duplicatedEntityIds, AZStd::unordered_map<InstanceAlias, Instance*>& newInstanceAliasToOldInstanceMap)
|
||||
{
|
||||
if (instances.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AZStd::unordered_map<InstanceAlias, InstanceAlias> oldInstanceAliasToNewInstanceAliasMap;
|
||||
AZStd::unordered_map<InstanceAlias, QString> aliasToInstanceDomMap;
|
||||
|
||||
for (auto instance : instances)
|
||||
{
|
||||
PrefabDom nestedInstanceDomBefore;
|
||||
m_instanceToTemplateInterface->GenerateDomForInstance(nestedInstanceDomBefore, *instance);
|
||||
|
||||
// Keep track of the old alias <-> new alias mapping for this duplicated instance
|
||||
// so we can fixup references later
|
||||
InstanceAlias oldAlias = instance->GetInstanceAlias();
|
||||
InstanceAlias newInstanceAlias = Instance::GenerateInstanceAlias();
|
||||
oldInstanceAliasToNewInstanceAliasMap.emplace(oldAlias, newInstanceAlias);
|
||||
|
||||
// Keep track of our new instance alias with the Instance it was duplicated from,
|
||||
// so that after all instances are duplicated, we can go back and create links for them
|
||||
newInstanceAliasToOldInstanceMap.emplace(newInstanceAlias, instance);
|
||||
|
||||
rapidjson::StringBuffer buffer;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
|
||||
nestedInstanceDomBefore.Accept(writer);
|
||||
|
||||
// Store our duplicated Instance DOM with its new alias as a string
|
||||
// so that we can fixup instance alias references before adding it
|
||||
// to the Instances member of our instance DOM
|
||||
QString instanceDomString(buffer.GetString());
|
||||
aliasToInstanceDomMap.emplace(newInstanceAlias, instanceDomString);
|
||||
}
|
||||
|
||||
auto instancesIter = domToAddDuplicatedInstancesUnder.FindMember(PrefabDomUtils::InstancesName);
|
||||
AZ_Assert(instancesIter != domToAddDuplicatedInstancesUnder.MemberEnd(), "Instance DOM missing the Instances member.");
|
||||
|
||||
// Now that all the duplicated Instance DOMs have been created, we need to iterate
|
||||
// through them and replace any previous InstanceAlias references with the new ones.
|
||||
for (auto [newInstanceAlias, newInstanceDomString]: aliasToInstanceDomMap)
|
||||
{
|
||||
// Replace all of the old alias references with the new ones
|
||||
for (auto [oldAlias, newAlias] : oldInstanceAliasToNewInstanceAliasMap)
|
||||
{
|
||||
ReplaceOldAliases(newInstanceDomString, oldAlias, newAlias);
|
||||
}
|
||||
|
||||
// Create the new Instance DOM from parsing the JSON string
|
||||
PrefabDom nestedInstanceDomAfter(&domToAddDuplicatedInstancesUnder.GetAllocator());
|
||||
nestedInstanceDomAfter.Parse(newInstanceDomString.toUtf8().constData());
|
||||
|
||||
// Add the new Instance DOM to the Instances member of the instance
|
||||
rapidjson::Value aliasName(newInstanceAlias.c_str(), newInstanceAlias.length(), domToAddDuplicatedInstancesUnder.GetAllocator());
|
||||
instancesIter->value.AddMember(AZStd::move(aliasName), nestedInstanceDomAfter, domToAddDuplicatedInstancesUnder.GetAllocator());
|
||||
}
|
||||
|
||||
for (auto aliasMapIter : oldInstanceAliasToNewInstanceAliasMap)
|
||||
{
|
||||
InstanceAlias newInstanceAlias = aliasMapIter.second;
|
||||
|
||||
AliasPath absoluteInstancePath = commonOwningInstance.GetAbsoluteInstanceAliasPath();
|
||||
absoluteInstancePath.Append(newInstanceAlias);
|
||||
|
||||
AZ::EntityId newEntityId = InstanceEntityIdMapper::GenerateEntityIdForAliasPath(absoluteInstancePath);
|
||||
duplicatedEntityIds.push_back(newEntityId);
|
||||
}
|
||||
}
|
||||
|
||||
void PrefabPublicHandler::ReplaceOldAliases(QString& stringToReplace, AZStd::string_view oldAlias, AZStd::string_view newAlias)
|
||||
{
|
||||
// Replace all of the old alias references with the new ones
|
||||
// We bookend the aliases with \" and also with a / as an extra precaution to prevent
|
||||
// inadvertently replacing a matching string vs. where an actual EntityId is expected
|
||||
// This will cover both cases where an alias could be used in a normal entity vs. an instance
|
||||
QString oldAliasQuotes = QString("\"%1\"").arg(oldAlias.data());
|
||||
QString newAliasQuotes = QString("\"%1\"").arg(newAlias.data());
|
||||
|
||||
@@ -1252,5 +1642,30 @@ namespace AzToolsFramework
|
||||
|
||||
stringToReplace.replace(oldAliasPathRef, newAliasPathRef);
|
||||
}
|
||||
|
||||
void PrefabPublicHandler::UpdateLinkPatchesWithNewEntityAliases(
|
||||
PrefabDom& linkPatch,
|
||||
const AZStd::unordered_map<AZ::EntityId, AZStd::string>& oldEntityAliases,
|
||||
Instance& newParent)
|
||||
{
|
||||
rapidjson::StringBuffer buffer;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
|
||||
linkPatch.Accept(writer);
|
||||
QString previousPatchString(buffer.GetString());
|
||||
|
||||
for (const auto& [entityId, oldEntityAlias] : oldEntityAliases)
|
||||
{
|
||||
EntityAliasOptionalReference newEntityAlias = newParent.GetEntityAlias(entityId);
|
||||
AZ_Assert(
|
||||
newEntityAlias.has_value(),
|
||||
"Could not fetch entity alias for entity with id '%llu' during prefab creation.",
|
||||
static_cast<AZ::u64>(entityId));
|
||||
|
||||
ReplaceOldAliases(previousPatchString, oldEntityAlias, newEntityAlias->get());
|
||||
}
|
||||
|
||||
linkPatch.Parse(previousPatchString.toUtf8().constData());
|
||||
}
|
||||
|
||||
} // namespace Prefab
|
||||
} // namespace AzToolsFramework
|
||||
|
||||
@@ -64,6 +64,8 @@ namespace AzToolsFramework
|
||||
PrefabOperationResult DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds) override;
|
||||
PrefabOperationResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) override;
|
||||
|
||||
PrefabOperationResult DetachPrefab(const AZ::EntityId& containerEntityId) override;
|
||||
|
||||
private:
|
||||
PrefabOperationResult DeleteFromInstance(const EntityIdList& entityIds, bool deleteDescendants);
|
||||
bool RetrieveAndSortPrefabEntitiesAndInstances(const EntityList& inputEntities, Instance& commonRootEntityOwningInstance,
|
||||
@@ -71,6 +73,33 @@ namespace AzToolsFramework
|
||||
|
||||
InstanceOptionalReference GetOwnerInstanceByEntityId(AZ::EntityId entityId) const;
|
||||
bool EntitiesBelongToSameInstance(const EntityIdList& entityIds) const;
|
||||
|
||||
/**
|
||||
* Duplicate a list of entities owned by a common owning instance by directly
|
||||
* copying/modifying their entries in the instance DOM
|
||||
*
|
||||
* \param commonOwningInstance The common owning instance of all the entities being duplicated.
|
||||
* \param entities The list of Entities that will be duplicated.
|
||||
* \param domToAddDuplicatedEntitiesUnder The DOM of the common owning instance where the duplicated
|
||||
* entity DOM values will be added to.
|
||||
* \param duplicatedEntityIds A list of EntityIds corresponding to the entities that were duplicated.
|
||||
*/
|
||||
void DuplicateNestedEntitiesInInstance(Instance& commonOwningInstance,
|
||||
const AZStd::vector<AZ::Entity*>& entities, PrefabDom& domToAddDuplicatedEntitiesUnder,
|
||||
EntityIdList& duplicatedEntityIds);
|
||||
/**
|
||||
* Duplicate a list of instances owned by a common owning instance by directly
|
||||
* copying/modifying their entries in the instance DOM
|
||||
*
|
||||
* \param commonOwningInstance The common owning instance of all the instances being duplicated.
|
||||
* \param entities The list of Instances that will be duplicated.
|
||||
* \param domToAddDuplicatedInstancesUnder The DOM of the common owning instance where the duplicated
|
||||
* instance DOM values will be added to.
|
||||
* \param duplicatedEntityIds A list of EntityIds corresponding to the instances that were duplicated.
|
||||
*/
|
||||
void DuplicateNestedInstancesInInstance(Instance& commonOwningInstance,
|
||||
const AZStd::vector<Instance*>& instances, PrefabDom& domToAddDuplicatedInstancesUnder,
|
||||
EntityIdList& duplicatedEntityIds, AZStd::unordered_map<InstanceAlias, Instance*>& newInstanceAliasToOldInstanceMap);
|
||||
|
||||
/**
|
||||
* Applies the correct transform changes to the container entity based on the parent and child entities provided, and returns an appropriate patch.
|
||||
@@ -88,8 +117,8 @@ namespace AzToolsFramework
|
||||
/**
|
||||
* Creates a link between the templates of an instance and its parent.
|
||||
*
|
||||
* \param sourceInstance The instance that corresponds to the source template of the link.
|
||||
* \param targetInstance The id of the target template.
|
||||
* \param sourceInstance The instance that corresponds to the source template of the link (child).
|
||||
* \param targetInstance The id of the target template (parent).
|
||||
* \param undoBatch The undo batch to set as parent for this create link action.
|
||||
* \param patch The patch to store in the newly created link dom.
|
||||
* \param isUndoRedoSupportNeeded The flag indicating whether the link should be created with undo/redo support or not.
|
||||
@@ -132,7 +161,18 @@ namespace AzToolsFramework
|
||||
bool IsCyclicalDependencyFound(
|
||||
InstanceOptionalConstReference instance, const AZStd::unordered_set<AZ::IO::Path>& templateSourcePaths);
|
||||
|
||||
void ReplaceOldAliases(QString& stringToReplace, AZStd::string_view oldAlias, AZStd::string_view newAlias);
|
||||
static void Internal_HandleContainerOverride(
|
||||
UndoSystem::URSequencePoint* undoBatch, AZ::EntityId entityId, const PrefabDom& patch, const LinkId linkId);
|
||||
static void Internal_HandleEntityChange(
|
||||
UndoSystem::URSequencePoint* undoBatch, AZ::EntityId entityId, PrefabDom& beforeState, PrefabDom& afterState);
|
||||
void Internal_HandleInstanceChange(UndoSystem::URSequencePoint* undoBatch, AZ::Entity* entity, AZ::EntityId beforeParentId, AZ::EntityId afterParentId);
|
||||
|
||||
void UpdateLinkPatchesWithNewEntityAliases(
|
||||
PrefabDom& linkPatch,
|
||||
const AZStd::unordered_map<AZ::EntityId, AZStd::string>& oldEntityAliases,
|
||||
Instance& newParent);
|
||||
|
||||
static void ReplaceOldAliases(QString& stringToReplace, AZStd::string_view oldAlias, AZStd::string_view newAlias);
|
||||
|
||||
static Instance* GetParentInstance(Instance* instance);
|
||||
static Instance* GetAncestorOfInstanceThatIsChildOfRoot(const Instance* ancestor, Instance* descendant);
|
||||
|
||||
@@ -150,6 +150,17 @@ namespace AzToolsFramework
|
||||
* @return An outcome object; on failure, it comes with an error message detailing the cause of the error.
|
||||
*/
|
||||
virtual PrefabOperationResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) = 0;
|
||||
|
||||
/**
|
||||
* If the entity id is a container entity id, detaches the prefab instance corresponding to it. This includes converting
|
||||
* the container entity into a regular entity and putting it under the parent prefab, removing the link between this
|
||||
* instance and the parent, removing links between this instance and it's nested instances, adding entities directly
|
||||
* owned by this instance under the parent instance.
|
||||
* Bails if the entity is not a container entity or belongs to the level prefab instance.
|
||||
* @param containerEntityId The container entity id of the instance to detach.
|
||||
* @return An outcome object; on failure, it comes with an error message detailing the cause of the error.
|
||||
*/
|
||||
virtual PrefabOperationResult DetachPrefab(const AZ::EntityId& containerEntityId) = 0;
|
||||
};
|
||||
|
||||
} // namespace Prefab
|
||||
|
||||
@@ -652,7 +652,8 @@ namespace AzToolsFramework
|
||||
if (instancesValue->get().FindMember(rapidjson::StringRef(instanceAlias.c_str())) == instancesValue->get().MemberEnd())
|
||||
{
|
||||
instancesValue->get().AddMember(
|
||||
rapidjson::StringRef(instanceAlias.c_str()), PrefabDomValue(), targetTemplateDom.GetAllocator());
|
||||
rapidjson::Value(instanceAlias.c_str(), targetTemplateDom.GetAllocator()), PrefabDomValue(),
|
||||
targetTemplateDom.GetAllocator());
|
||||
}
|
||||
|
||||
Template& sourceTemplate = sourceTemplateRef->get();
|
||||
@@ -705,14 +706,14 @@ namespace AzToolsFramework
|
||||
"Prefab - PrefabSystemComponent::RemoveLink - "
|
||||
"Failed to remove Link with Id '%llu' for Instance '%s' of source Template with Id '%llu' "
|
||||
"from TemplateToLinkIdsMap.",
|
||||
linkId, link.GetSourceTemplateId(), link.GetInstanceName().c_str());
|
||||
linkId, link.GetInstanceName().c_str(), link.GetSourceTemplateId());
|
||||
|
||||
result = RemoveLinkFromTargetTemplate(linkId, link);
|
||||
AZ_Assert(result,
|
||||
"Prefab - PrefabSystemComponent::RemoveLink - "
|
||||
"Failed to remove Link with Id '%llu' for Instance '%s' of source Template with Id '%llu' "
|
||||
"from target Template with Id '%llu'.",
|
||||
linkId, link.GetSourceTemplateId(), link.GetInstanceName().c_str(), link.GetTargetTemplateId());
|
||||
linkId, link.GetInstanceName().c_str(), link.GetSourceTemplateId(), link.GetTargetTemplateId());
|
||||
|
||||
m_linkIdMap.erase(linkId);
|
||||
|
||||
|
||||
@@ -73,14 +73,16 @@ namespace AzToolsFramework
|
||||
}
|
||||
|
||||
PrefabDom oldData;
|
||||
Retrieve(entityId, oldData);
|
||||
AZ::EntityId oldParentId;
|
||||
Retrieve(entityId, oldData, oldParentId);
|
||||
|
||||
UpdateCache(entityId);
|
||||
|
||||
PrefabDom newData;
|
||||
Retrieve(entityId, newData);
|
||||
AZ::EntityId newParentId;
|
||||
Retrieve(entityId, newData, newParentId);
|
||||
|
||||
if (newData != oldData)
|
||||
if (newData != oldData || oldParentId != newParentId)
|
||||
{
|
||||
// display a useful message
|
||||
AZ::Entity* entity = nullptr;
|
||||
@@ -106,7 +108,7 @@ namespace AzToolsFramework
|
||||
// Clear out newly generated data and
|
||||
// replace with original data to ensure debug mode has the same data as profile/release
|
||||
// in the event of the consistency check failing.
|
||||
m_entitySavedStates[entityId] = AZStd::move(oldData);
|
||||
m_entitySavedStates[entityId] = {AZStd::move(oldData), oldParentId};
|
||||
|
||||
#endif // ENABLE_UNDOCACHE_CONSISTENCY_CHECKS
|
||||
}
|
||||
@@ -140,10 +142,13 @@ namespace AzToolsFramework
|
||||
return;
|
||||
}
|
||||
|
||||
AZ::EntityId parentId;
|
||||
AZ::TransformBus::EventResult(parentId, entityId, &AZ::TransformBus::Events::GetParentId);
|
||||
|
||||
// Capture it
|
||||
PrefabDom entityDom;
|
||||
m_instanceToTemplateInterface->GenerateDomForEntity(entityDom, *entity);
|
||||
m_entitySavedStates.emplace(AZStd::make_pair(entityId, AZStd::move(entityDom)));
|
||||
m_entitySavedStates[entityId] = {AZStd::move(entityDom), parentId};
|
||||
|
||||
AZLOG("Prefab Undo", "Correctly updated cache for entity of id %llu (%s)", static_cast<AZ::u64>(entityId), entity->GetName().c_str());
|
||||
|
||||
@@ -155,7 +160,7 @@ namespace AzToolsFramework
|
||||
m_entitySavedStates.erase(entityId);
|
||||
}
|
||||
|
||||
bool PrefabUndoCache::Retrieve(const AZ::EntityId& entityId, PrefabDom& outDom)
|
||||
bool PrefabUndoCache::Retrieve(const AZ::EntityId& entityId, PrefabDom& outDom, AZ::EntityId& parentId)
|
||||
{
|
||||
auto it = m_entitySavedStates.find(entityId);
|
||||
|
||||
@@ -164,14 +169,15 @@ namespace AzToolsFramework
|
||||
return false;
|
||||
}
|
||||
|
||||
outDom = AZStd::move(m_entitySavedStates[entityId]);
|
||||
outDom = AZStd::move(m_entitySavedStates[entityId].dom);
|
||||
parentId = m_entitySavedStates[entityId].parentId;
|
||||
m_entitySavedStates.erase(entityId);
|
||||
return true;
|
||||
}
|
||||
|
||||
void PrefabUndoCache::Store(const AZ::EntityId& entityId, PrefabDom&& dom)
|
||||
void PrefabUndoCache::Store(const AZ::EntityId& entityId, PrefabDom&& dom, const AZ::EntityId& parentId)
|
||||
{
|
||||
m_entitySavedStates.emplace(AZStd::make_pair(entityId, AZStd::move(dom)));
|
||||
m_entitySavedStates[entityId] = {AZStd::move(dom), parentId};
|
||||
}
|
||||
|
||||
void PrefabUndoCache::Clear()
|
||||
|
||||
@@ -46,14 +46,19 @@ namespace AzToolsFramework
|
||||
void Validate(const AZ::EntityId& entityId) override;
|
||||
|
||||
// Retrieve the last known state for an entity
|
||||
bool Retrieve(const AZ::EntityId& entityId, PrefabDom& outDom);
|
||||
bool Retrieve(const AZ::EntityId& entityId, PrefabDom& outDom, AZ::EntityId& parentId);
|
||||
|
||||
// Store dom as the cached state of entityId
|
||||
void Store(const AZ::EntityId& entityId, PrefabDom&& dom);
|
||||
void Store(const AZ::EntityId& entityId, PrefabDom&& dom, const AZ::EntityId& parentId);
|
||||
|
||||
private:
|
||||
typedef AZStd::unordered_map<AZ::EntityId, PrefabDom> EntityDomMap;
|
||||
EntityDomMap m_entitySavedStates;
|
||||
struct PrefabUndoCacheItem
|
||||
{
|
||||
PrefabDom dom;
|
||||
AZ::EntityId parentId;
|
||||
};
|
||||
typedef AZStd::unordered_map<AZ::EntityId, PrefabUndoCacheItem> EntityCache;
|
||||
EntityCache m_entitySavedStates;
|
||||
|
||||
InstanceEntityMapperInterface* m_instanceEntityMapperInterface = nullptr;
|
||||
InstanceToTemplateInterface* m_instanceToTemplateInterface = nullptr;
|
||||
|
||||
+28
-20
@@ -37,13 +37,13 @@ namespace AzToolsFramework
|
||||
axisLength, AzFramework::ViewportColors::XAxisColor, AzFramework::ViewportColors::YAxisColor,
|
||||
AzFramework::ViewportColors::ZAxisColor);
|
||||
|
||||
auto mouseDownCallback = [this](const LinearManipulator::Action& action) {
|
||||
auto mouseDownCallback = [this]([[maybe_unused]] const LinearManipulator::Action& action)
|
||||
{
|
||||
AZ::Vector3 nonUniformScale = AZ::Vector3::CreateOne();
|
||||
|
||||
AZ::NonUniformScaleRequestBus::EventResult(
|
||||
nonUniformScale, m_entityComponentIdPair.GetEntityId(), &AZ::NonUniformScaleRequests::GetScale);
|
||||
|
||||
m_initialScale = nonUniformScale + action.m_start.m_scaleSnapOffset;
|
||||
m_initialScale = nonUniformScale;
|
||||
|
||||
AZ::NonUniformScaleRequestBus::Event(
|
||||
m_entityComponentIdPair.GetEntityId(), &AZ::NonUniformScaleRequests::SetScale, m_initialScale);
|
||||
@@ -51,29 +51,37 @@ namespace AzToolsFramework
|
||||
|
||||
m_manipulators->InstallAxisLeftMouseDownCallback(mouseDownCallback);
|
||||
|
||||
m_manipulators->InstallAxisMouseMoveCallback([this](const LinearManipulator::Action& action) {
|
||||
const AZ::Vector3 scaleMultiplier =
|
||||
(AZ::Vector3::CreateOne() + ((action.LocalScaleOffset() * action.m_start.m_sign) / m_initialScale));
|
||||
m_manipulators->InstallAxisMouseMoveCallback(
|
||||
[this](const LinearManipulator::Action& action)
|
||||
{
|
||||
const AZ::Vector3 scaleMultiplier =
|
||||
(AZ::Vector3::CreateOne() + ((action.LocalScaleOffset() * action.m_start.m_sign) / m_initialScale));
|
||||
|
||||
AZ::NonUniformScaleRequestBus::Event(
|
||||
m_entityComponentIdPair.GetEntityId(), &AZ::NonUniformScaleRequests::SetScale,
|
||||
(scaleMultiplier * m_initialScale).GetClamp(AZ::Vector3(AZ::MinTransformScale), AZ::Vector3(AZ::MaxTransformScale)));
|
||||
});
|
||||
AZ::NonUniformScaleRequestBus::Event(
|
||||
m_entityComponentIdPair.GetEntityId(), &AZ::NonUniformScaleRequests::SetScale,
|
||||
(scaleMultiplier * m_initialScale)
|
||||
.GetClamp(AZ::Vector3(AZ::MinTransformScale), AZ::Vector3(AZ::MaxTransformScale)));
|
||||
});
|
||||
|
||||
m_manipulators->InstallUniformLeftMouseDownCallback(mouseDownCallback);
|
||||
|
||||
m_manipulators->InstallUniformMouseMoveCallback([this](const LinearManipulator::Action& action) {
|
||||
const auto sumVectorElements = [](const AZ::Vector3& vec) { return vec.GetX() + vec.GetY() + vec.GetZ(); };
|
||||
m_manipulators->InstallUniformMouseMoveCallback(
|
||||
[this](const LinearManipulator::Action& action)
|
||||
{
|
||||
const auto sumVectorElements = [](const AZ::Vector3& vec)
|
||||
{
|
||||
return vec.GetX() + vec.GetY() + vec.GetZ();
|
||||
};
|
||||
|
||||
const float minScaleMultiplier = AZ::MinTransformScale / m_initialScale.GetMinElement();
|
||||
const float maxScaleMultiplier = AZ::MaxTransformScale / m_initialScale.GetMaxElement();
|
||||
const float scaleMultiplier = AZ::GetClamp(
|
||||
1.0f + sumVectorElements(action.m_start.m_sign * action.LocalScaleOffset() / m_initialScale), minScaleMultiplier,
|
||||
maxScaleMultiplier);
|
||||
const float minScaleMultiplier = AZ::MinTransformScale / m_initialScale.GetMinElement();
|
||||
const float maxScaleMultiplier = AZ::MaxTransformScale / m_initialScale.GetMaxElement();
|
||||
const float scaleMultiplier = AZ::GetClamp(
|
||||
1.0f + sumVectorElements(action.m_start.m_sign * action.LocalScaleOffset() / m_initialScale), minScaleMultiplier,
|
||||
maxScaleMultiplier);
|
||||
|
||||
AZ::NonUniformScaleRequestBus::Event(
|
||||
m_entityComponentIdPair.GetEntityId(), &AZ::NonUniformScaleRequests::SetScale, scaleMultiplier * m_initialScale);
|
||||
});
|
||||
AZ::NonUniformScaleRequestBus::Event(
|
||||
m_entityComponentIdPair.GetEntityId(), &AZ::NonUniformScaleRequests::SetScale, scaleMultiplier * m_initialScale);
|
||||
});
|
||||
}
|
||||
|
||||
NonUniformScaleComponentMode::~NonUniformScaleComponentMode()
|
||||
|
||||
+29
@@ -237,6 +237,24 @@ namespace AzToolsFramework
|
||||
{
|
||||
deleteAction->setDisabled(true);
|
||||
}
|
||||
|
||||
// Detach Prefab
|
||||
if (selectedEntities.size() == 1)
|
||||
{
|
||||
AZ::EntityId selectedEntity = selectedEntities[0];
|
||||
|
||||
if (s_prefabPublicInterface->IsInstanceContainerEntity(selectedEntity) &&
|
||||
!s_prefabPublicInterface->IsLevelInstanceContainerEntity(selectedEntity))
|
||||
{
|
||||
QAction* detachPrefabAction = menu->addAction(QObject::tr("Detach Prefab..."));
|
||||
QObject::connect(
|
||||
detachPrefabAction, &QAction::triggered, detachPrefabAction,
|
||||
[this, selectedEntity]
|
||||
{
|
||||
ContextMenu_DetachPrefab(selectedEntity);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PrefabIntegrationManager::HandleSourceFileType(AZStd::string_view sourceFilePath, AZ::EntityId parentId, AZ::Vector3 position) const
|
||||
@@ -392,6 +410,17 @@ namespace AzToolsFramework
|
||||
}
|
||||
}
|
||||
|
||||
void PrefabIntegrationManager::ContextMenu_DetachPrefab(AZ::EntityId containerEntity)
|
||||
{
|
||||
PrefabOperationResult detachPrefabResult =
|
||||
s_prefabPublicInterface->DetachPrefab(containerEntity);
|
||||
|
||||
if (!detachPrefabResult.IsSuccess())
|
||||
{
|
||||
WarnUserOfError("Detach Prefab error", detachPrefabResult.GetError());
|
||||
}
|
||||
}
|
||||
|
||||
void PrefabIntegrationManager::GenerateSuggestedFilenameFromEntities(const EntityIdList& entityIds, AZStd::string& outName)
|
||||
{
|
||||
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
|
||||
|
||||
@@ -93,6 +93,7 @@ namespace AzToolsFramework
|
||||
static void ContextMenu_EditPrefab(AZ::EntityId containerEntity);
|
||||
static void ContextMenu_SavePrefab(AZ::EntityId containerEntity);
|
||||
static void ContextMenu_DeleteSelected();
|
||||
static void ContextMenu_DetachPrefab(AZ::EntityId containerEntity);
|
||||
|
||||
// Prompt and resolve dialogs
|
||||
static bool QueryUserForPrefabSaveLocation(
|
||||
|
||||
+2
-1
@@ -969,7 +969,8 @@ namespace AzToolsFramework
|
||||
{
|
||||
// Build up components to display
|
||||
SharedComponentArray sharedComponentArray;
|
||||
BuildSharedComponentArray(sharedComponentArray, selectionEntityTypeInfo != SelectionEntityTypeInfo::OnlyStandardEntities);
|
||||
BuildSharedComponentArray(sharedComponentArray,
|
||||
!(selectionEntityTypeInfo == SelectionEntityTypeInfo::OnlyStandardEntities || selectionEntityTypeInfo == SelectionEntityTypeInfo::OnlyPrefabEntities));
|
||||
|
||||
if (sharedComponentArray.size() == 0)
|
||||
{
|
||||
|
||||
+11
@@ -124,4 +124,15 @@ namespace AzToolsFramework
|
||||
|
||||
return cameraState;
|
||||
}
|
||||
|
||||
float GetScreenDisplayScaling(const int viewportId)
|
||||
{
|
||||
float scaling = 1.0f;
|
||||
ViewportInteraction::ViewportInteractionRequestBus::EventResult(
|
||||
scaling, viewportId,
|
||||
&ViewportInteraction::ViewportInteractionRequestBus::Events::DeviceScalingFactor);
|
||||
|
||||
return scaling;
|
||||
}
|
||||
|
||||
} // namespace AzToolsFramework
|
||||
|
||||
+3
@@ -60,6 +60,9 @@ namespace AzToolsFramework
|
||||
/// Wrapper for EBus call to return the CameraState for a given viewport.
|
||||
AzFramework::CameraState GetCameraState(int viewportId);
|
||||
|
||||
/// Wrapper for EBus call to return the DPI scaling for a given viewport.
|
||||
float GetScreenDisplayScaling(const int viewportId);
|
||||
|
||||
/// A utility to return the center of several points.
|
||||
/// Take several positions and store the min and max of each in
|
||||
/// turn - when all points have been added return the center/midpoint.
|
||||
|
||||
+29
-29
@@ -423,15 +423,14 @@ namespace AzToolsFramework
|
||||
}
|
||||
}
|
||||
|
||||
static void InitializeTranslationLookup(
|
||||
EntityIdManipulators& entityIdManipulators, const AZ::Vector3& snapOffset)
|
||||
static void InitializeTranslationLookup(EntityIdManipulators& entityIdManipulators)
|
||||
{
|
||||
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
|
||||
|
||||
for (auto& entityIdLookup : entityIdManipulators.m_lookups)
|
||||
{
|
||||
entityIdLookup.second.m_initial =
|
||||
AZ::Transform::CreateTranslation(GetWorldTranslation(entityIdLookup.first) + snapOffset);
|
||||
AZ::Transform::CreateTranslation(GetWorldTranslation(entityIdLookup.first));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -820,7 +819,7 @@ namespace AzToolsFramework
|
||||
// moving with ctrl - setting override
|
||||
pivotOverrideFrame.m_translationOverride =
|
||||
entityIdManipulators.m_manipulators->GetLocalTransform().GetTranslation();
|
||||
InitializeTranslationLookup(entityIdManipulators, -action.LocalPositionOffset());
|
||||
InitializeTranslationLookup(entityIdManipulators);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1277,12 +1276,12 @@ namespace AzToolsFramework
|
||||
|
||||
// linear
|
||||
translationManipulators->InstallLinearManipulatorMouseDownCallback(
|
||||
[this, manipulatorEntityIds](const LinearManipulator::Action& action) mutable
|
||||
[this, manipulatorEntityIds]([[maybe_unused]] const LinearManipulator::Action& action) mutable
|
||||
{
|
||||
// important to sort entityIds based on hierarchy order when updating transforms
|
||||
BuildSortedEntityIdVectorFromEntityIdMap(m_entityIdManipulators.m_lookups, manipulatorEntityIds->m_entityIds);
|
||||
|
||||
InitializeTranslationLookup(m_entityIdManipulators, action.m_start.m_positionSnapOffset);
|
||||
InitializeTranslationLookup(m_entityIdManipulators);
|
||||
|
||||
m_axisPreview.m_translation = m_entityIdManipulators.m_manipulators->GetLocalTransform().GetTranslation();
|
||||
m_axisPreview.m_orientation = QuaternionFromTransformNoScaling(
|
||||
@@ -1302,19 +1301,19 @@ namespace AzToolsFramework
|
||||
});
|
||||
|
||||
translationManipulators->InstallLinearManipulatorMouseUpCallback(
|
||||
[this](const LinearManipulator::Action& /*action*/) mutable
|
||||
[this]([[maybe_unused]] const LinearManipulator::Action& action) mutable
|
||||
{
|
||||
EndRecordManipulatorCommand();
|
||||
});
|
||||
|
||||
// planar
|
||||
translationManipulators->InstallPlanarManipulatorMouseDownCallback(
|
||||
[this, manipulatorEntityIds](const PlanarManipulator::Action& action)
|
||||
[this, manipulatorEntityIds]([[maybe_unused]] const PlanarManipulator::Action& action)
|
||||
{
|
||||
// important to sort entityIds based on hierarchy order when updating transforms
|
||||
BuildSortedEntityIdVectorFromEntityIdMap(m_entityIdManipulators.m_lookups, manipulatorEntityIds->m_entityIds);
|
||||
|
||||
InitializeTranslationLookup(m_entityIdManipulators, action.m_start.m_snapOffset);
|
||||
InitializeTranslationLookup(m_entityIdManipulators);
|
||||
|
||||
m_axisPreview.m_translation = m_entityIdManipulators.m_manipulators->GetLocalTransform().GetTranslation();
|
||||
m_axisPreview.m_orientation = QuaternionFromTransformNoScaling(
|
||||
@@ -1340,11 +1339,11 @@ namespace AzToolsFramework
|
||||
|
||||
// surface
|
||||
translationManipulators->InstallSurfaceManipulatorMouseDownCallback(
|
||||
[this, manipulatorEntityIds](const SurfaceManipulator::Action& action)
|
||||
[this, manipulatorEntityIds]([[maybe_unused]] const SurfaceManipulator::Action& action)
|
||||
{
|
||||
BuildSortedEntityIdVectorFromEntityIdMap(m_entityIdManipulators.m_lookups, manipulatorEntityIds->m_entityIds);
|
||||
|
||||
InitializeTranslationLookup(m_entityIdManipulators, action.m_start.m_snapOffset);
|
||||
InitializeTranslationLookup(m_entityIdManipulators);
|
||||
|
||||
m_axisPreview.m_translation = m_entityIdManipulators.m_manipulators->GetLocalTransform().GetTranslation();
|
||||
m_axisPreview.m_orientation = QuaternionFromTransformNoScaling(
|
||||
@@ -2471,7 +2470,17 @@ namespace AzToolsFramework
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
AddAction(
|
||||
m_actions, { QKeySequence(Qt::Key_U) },
|
||||
/*ID_VIEWPORTUI_VISIBLE=*/50040, "Toggle ViewportUI", "Hide/Unhide Viewport UI",
|
||||
[this]()
|
||||
{
|
||||
SetViewportUiClusterVisible(m_transformModeClusterId, !m_viewportUiVisible);
|
||||
SetViewportUiClusterVisible(m_spaceCluster.m_spaceClusterId, !m_viewportUiVisible);
|
||||
m_viewportUiVisible = !m_viewportUiVisible;
|
||||
});
|
||||
|
||||
EditorMenuRequestBus::Broadcast(&EditorMenuRequests::RestoreEditMenuToDefault);
|
||||
}
|
||||
|
||||
@@ -3316,26 +3325,16 @@ namespace AzToolsFramework
|
||||
}
|
||||
|
||||
static void DrawManipulatorGrid(
|
||||
AzFramework::DebugDisplayRequests& debugDisplay, const EntityIdManipulators& entityIdManipulators,
|
||||
const float gridSize, const float localSnapping)
|
||||
AzFramework::DebugDisplayRequests& debugDisplay, const EntityIdManipulators& entityIdManipulators, const float gridSize)
|
||||
{
|
||||
const AZ::Matrix3x3 orientation =
|
||||
AZ::Matrix3x3::CreateFromTransform(entityIdManipulators.m_manipulators->GetLocalTransform());
|
||||
|
||||
const AZ::Vector3 unsnappedTranslation =
|
||||
const AZ::Vector3 translation =
|
||||
entityIdManipulators.m_manipulators->GetLocalTransform().GetTranslation();
|
||||
|
||||
// calculate the offset to snap by to align the manipulator to the grid
|
||||
// note: only perform this if we are not snapping in local space
|
||||
const AZ::Vector3 snappedOffset = !localSnapping
|
||||
? CalculateSnappedOffset(unsnappedTranslation, orientation.GetBasisX(), gridSize) +
|
||||
CalculateSnappedOffset(unsnappedTranslation, orientation.GetBasisY(), gridSize)
|
||||
: AZ::Vector3::CreateZero();
|
||||
|
||||
const AZ::Vector3 snappedTranslation = unsnappedTranslation + snappedOffset;
|
||||
|
||||
DrawSnappingGrid(
|
||||
debugDisplay, AZ::Transform::CreateFromMatrix3x3AndTranslation(orientation, snappedTranslation),
|
||||
debugDisplay, AZ::Transform::CreateFromMatrix3x3AndTranslation(orientation, translation),
|
||||
gridSize);
|
||||
}
|
||||
|
||||
@@ -3474,7 +3473,7 @@ namespace AzToolsFramework
|
||||
const GridSnapParameters gridSnapParams = GridSnapSettings(viewportInfo.m_viewportId);
|
||||
if (gridSnapParams.m_gridSnap && m_entityIdManipulators.m_manipulators)
|
||||
{
|
||||
DrawManipulatorGrid(debugDisplay, m_entityIdManipulators, gridSnapParams.m_gridSize, modifiers.Alt());
|
||||
DrawManipulatorGrid(debugDisplay, m_entityIdManipulators, gridSnapParams.m_gridSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3563,9 +3562,10 @@ namespace AzToolsFramework
|
||||
debugDisplay.SetLineWidth(1.0f);
|
||||
|
||||
const float labelOffset = cl_viewportGizmoAxisLabelOffset;
|
||||
const auto labelXScreenPosition = (gizmoStart + (gizmoAxisX * labelOffset)) * editorCameraState.m_viewportSize;
|
||||
const auto labelYScreenPosition = (gizmoStart + (gizmoAxisY * labelOffset)) * editorCameraState.m_viewportSize;
|
||||
const auto labelZScreenPosition = (gizmoStart + (gizmoAxisZ * labelOffset)) * editorCameraState.m_viewportSize;
|
||||
const float screenScale = GetScreenDisplayScaling(viewportId);
|
||||
const auto labelXScreenPosition = (gizmoStart + (gizmoAxisX * labelOffset)) * editorCameraState.m_viewportSize * screenScale;
|
||||
const auto labelYScreenPosition = (gizmoStart + (gizmoAxisY * labelOffset)) * editorCameraState.m_viewportSize * screenScale;
|
||||
const auto labelZScreenPosition = (gizmoStart + (gizmoAxisZ * labelOffset)) * editorCameraState.m_viewportSize * screenScale;
|
||||
|
||||
// draw the label of of each axis for the gizmo
|
||||
const float labelSize = cl_viewportGizmoAxisLabelSize;
|
||||
|
||||
+1
@@ -306,6 +306,7 @@ namespace AzToolsFramework
|
||||
AzFramework::ClickDetector m_clickDetector; //!< Detect different types of mouse click.
|
||||
AzFramework::CursorState m_cursorState; //!< Track the mouse position and delta movement each frame.
|
||||
SpaceCluster m_spaceCluster; //!< Related viewport ui state for controlling the current reference space.
|
||||
bool m_viewportUiVisible = true; //!< Used to hide/show the viewport ui elements.
|
||||
};
|
||||
|
||||
//! The ETCS (EntityTransformComponentSelection) namespace contains functions and data used exclusively by
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <AzCore/Math/Matrix3x3.h>
|
||||
#include <AzCore/Math/Matrix4x4.h>
|
||||
#include <AzCore/Math/Transform.h>
|
||||
#include <AzCore/Math/VectorConversions.h>
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <AzFramework/Viewport/CameraState.h>
|
||||
#include <AzFramework/Viewport/ViewportScreen.h>
|
||||
@@ -23,6 +24,15 @@
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
// transform a point from normalized device coordinates to world space, and then from world space back to normalized device coordinates
|
||||
AZ::Vector2 ScreenNDCToWorldToScreenNDC(
|
||||
const AZ::Vector2& ndcPoint, const AzFramework::CameraState& cameraState)
|
||||
{
|
||||
const auto worldResult = AzFramework::ScreenNDCToWorld(ndcPoint, InverseCameraView(cameraState), InverseCameraProjection(cameraState));
|
||||
const auto ndcResult = AzFramework::WorldToScreenNDC(worldResult, CameraView(cameraState), CameraProjection(cameraState));
|
||||
return AZ::Vector3ToVector2(ndcResult);
|
||||
}
|
||||
|
||||
// transform a point from screen space to world space, and then from world space back to screen space
|
||||
AzFramework::ScreenPoint ScreenToWorldToScreen(
|
||||
const AzFramework::ScreenPoint& screenPoint, const AzFramework::CameraState& cameraState)
|
||||
@@ -30,7 +40,8 @@ namespace UnitTest
|
||||
const auto worldResult = AzFramework::ScreenToWorld(screenPoint, cameraState);
|
||||
return AzFramework::WorldToScreen(worldResult, cameraState);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// ScreenPoint tests
|
||||
TEST(ViewportScreen, WorldToScreenAndScreenToWorldReturnsTheSameValueIdentityCameraOffsetFromOrigin)
|
||||
{
|
||||
using AzFramework::ScreenPoint;
|
||||
@@ -38,8 +49,6 @@ namespace UnitTest
|
||||
const auto screenDimensions = AZ::Vector2(800.0f, 600.0f);
|
||||
const auto cameraPosition = AZ::Vector3::CreateAxisY(-10.0f);
|
||||
|
||||
// note: nearClip is 0.1 - the world space value returned will be aligned to the near clip
|
||||
// plane of the camera so use that to confirm the mapping to/from is correct
|
||||
const auto cameraState = AzFramework::CreateIdentityDefaultCamera(cameraPosition, screenDimensions);
|
||||
{
|
||||
const auto expectedScreenPoint = ScreenPoint{600, 450};
|
||||
@@ -81,6 +90,8 @@ namespace UnitTest
|
||||
EXPECT_EQ(resultScreenPoint, expectedScreenPoint);
|
||||
}
|
||||
|
||||
// note: nearClip is 0.1 - the world space value returned will be aligned to the near clip
|
||||
// plane of the camera so use that to confirm the mapping to/from is correct
|
||||
TEST(ViewportScreen, ScreenToWorldReturnsPositionOnNearClipPlaneInWorldSpace)
|
||||
{
|
||||
using AzFramework::ScreenPoint;
|
||||
@@ -94,7 +105,75 @@ namespace UnitTest
|
||||
const auto worldResult = AzFramework::ScreenToWorld(ScreenPoint{400, 300}, cameraState);
|
||||
EXPECT_THAT(worldResult, IsClose(AZ::Vector3(10.1f, 0.0f, 0.0f)));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// NDC tests
|
||||
TEST(ViewportScreen, WorldToScreenNDCAndScreenNDCToWorldReturnsTheSameValueIdentityCameraOffsetFromOrigin)
|
||||
{
|
||||
using NdcPoint = AZ::Vector2;
|
||||
|
||||
const auto screenDimensions = AZ::Vector2(800.0f, 600.0f);
|
||||
const auto cameraPosition = AZ::Vector3::CreateAxisY(-10.0f);
|
||||
|
||||
const auto cameraState = AzFramework::CreateIdentityDefaultCamera(cameraPosition, screenDimensions);
|
||||
{
|
||||
const auto expectedNdcPoint = NdcPoint{0.75f, 0.75f};
|
||||
const auto resultNdcPoint = ScreenNDCToWorldToScreenNDC(expectedNdcPoint, cameraState);
|
||||
EXPECT_THAT(resultNdcPoint, IsClose(expectedNdcPoint));
|
||||
}
|
||||
|
||||
{
|
||||
const auto expectedNdcPoint = NdcPoint{0.5f, 0.5f};
|
||||
const auto resultNdcPoint = ScreenNDCToWorldToScreenNDC(expectedNdcPoint, cameraState);
|
||||
EXPECT_THAT(resultNdcPoint, IsClose(expectedNdcPoint));
|
||||
}
|
||||
|
||||
{
|
||||
const auto expectedNdcPoint = NdcPoint{0.0f, 0.0f};
|
||||
const auto resultNdcPoint = ScreenNDCToWorldToScreenNDC(expectedNdcPoint, cameraState);
|
||||
EXPECT_THAT(resultNdcPoint, IsClose(expectedNdcPoint));
|
||||
}
|
||||
|
||||
{
|
||||
const auto expectedNdcPoint = NdcPoint{1.0f, 1.0f};
|
||||
const auto resultNdcPoint = ScreenNDCToWorldToScreenNDC(expectedNdcPoint, cameraState);
|
||||
EXPECT_THAT(resultNdcPoint, IsClose(expectedNdcPoint));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ViewportScreen, WorldToScreenNDCAndScreenNDCToWorldReturnsTheSameValueOrientatedCamera)
|
||||
{
|
||||
using NdcPoint = AZ::Vector2;
|
||||
|
||||
const auto screenDimensions = AZ::Vector2(800.0f, 600.0f);
|
||||
const auto cameraTransform =
|
||||
AZ::Transform::CreateRotationX(AZ::DegToRad(45.0f)) * AZ::Transform::CreateRotationZ(AZ::DegToRad(90.0f));
|
||||
|
||||
const auto cameraState = AzFramework::CreateDefaultCamera(cameraTransform, screenDimensions);
|
||||
|
||||
const auto expectedNdcPoint = NdcPoint{0.25f, 0.5f};
|
||||
const auto resultNdcPoint = ScreenNDCToWorldToScreenNDC(expectedNdcPoint, cameraState);
|
||||
EXPECT_THAT(resultNdcPoint, IsClose(expectedNdcPoint));
|
||||
}
|
||||
|
||||
// note: nearClip is 0.1 - the world space value returned will be aligned to the near clip
|
||||
// plane of the camera so use that to confirm the mapping to/from is correct
|
||||
TEST(ViewportScreen, ScreenNDCToWorldReturnsPositionOnNearClipPlaneInWorldSpace)
|
||||
{
|
||||
using NdcPoint = AZ::Vector2;
|
||||
|
||||
const auto screenDimensions = AZ::Vector2(800.0f, 600.0f);
|
||||
const auto cameraTransform = AZ::Transform::CreateTranslation(AZ::Vector3(10.0f, 0.0f, 0.0f)) *
|
||||
AZ::Transform::CreateRotationZ(AZ::DegToRad(-90.0f));
|
||||
|
||||
const auto cameraState = AzFramework::CreateDefaultCamera(cameraTransform, screenDimensions);
|
||||
|
||||
const auto worldResult = AzFramework::ScreenNDCToWorld(NdcPoint{0.5f, 0.5f}, InverseCameraView(cameraState), InverseCameraProjection(cameraState));
|
||||
EXPECT_THAT(worldResult, IsClose(AZ::Vector3(10.1f, 0.0f, 0.0f)));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// ScreenVector tests
|
||||
TEST(ViewportScreen, SubstractingScreenPointGivesScreenVector)
|
||||
{
|
||||
using AzFramework::ScreenPoint;
|
||||
@@ -220,6 +299,8 @@ namespace UnitTest
|
||||
EXPECT_NEAR(AzFramework::ScreenVectorLength(ScreenVector(12, 15)), 19.20937f, 0.001f);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Other tests
|
||||
TEST(ViewportScreen, CanGetCameraTransformFromCameraViewAndBack)
|
||||
{
|
||||
const auto screenDimensions = AZ::Vector2(1024.0f, 768.0f);
|
||||
|
||||
Reference in New Issue
Block a user