// Copyright Epic Games, Inc. All Rights Reserved. #include "EditModes/AnimDynamicsEditMode.h" #include "AnimGraphNode_AnimDynamics.h" #include "AnimGraphNode_Base.h" #include "Animation/AnimPhysicsSolver.h" #include "Animation/DebugSkelMeshComponent.h" #include "BoneContainer.h" #include "BoneControllers/AnimNode_AnimDynamics.h" #include "Components/SkeletalMeshComponent.h" #include "EdMode.h" #include "EditorViewportClient.h" #include "Engine/Engine.h" #include "Engine/EngineTypes.h" #include "HAL/PlatformCrt.h" #include "HitProxies.h" #include "IPersonaPreviewScene.h" #include "InputCoreTypes.h" #include "Materials/MaterialInstanceDynamic.h" #include "Math/Axis.h" #include "Math/Color.h" #include "Math/IntVector.h" #include "Math/Quat.h" #include "Math/RotationMatrix.h" #include "Math/ScaleMatrix.h" #include "Math/TransformVectorized.h" #include "Math/Vector.h" #include "Math/Vector2D.h" #include "Math/Vector4.h" #include "Misc/AssertionMacros.h" #include "PrimitiveDrawingUtils.h" #include "Templates/Casts.h" #include "UObject/ObjectPtr.h" #include "UnrealClient.h" class FSceneView; // AnimDynamicsEditMode hit proxy // // Allow users to select objects in the viewport for editing with a TRS widget. // struct HAnimDynamicsEditModeHitProxy : public HHitProxy { DECLARE_HIT_PROXY() FAnimDynamicsViewportObjectReference ViewportObjectRef; HAnimDynamicsEditModeHitProxy(const uint32 InEditorNodeUniqueId, const FAnimDynamicsViewportObjectType InType, const uint32 InIndex) : HHitProxy(HPP_World), ViewportObjectRef(InEditorNodeUniqueId, InType, InIndex) {} HAnimDynamicsEditModeHitProxy(const FAnimDynamicsViewportObjectReference& InGeometricObjectRef) : HHitProxy(HPP_World), ViewportObjectRef(InGeometricObjectRef) {} }; IMPLEMENT_HIT_PROXY(HAnimDynamicsEditModeHitProxy, HHitProxy); // Class FAnimDynamicsViewportObjectReference FAnimDynamicsViewportObjectReference::FAnimDynamicsViewportObjectReference(const uint32 InEditorNodeUniqueId, const FAnimDynamicsViewportObjectType InType, const uint32 InIndex) : EditorNodeUniqueId(InEditorNodeUniqueId) , Type(InType) , Index(InIndex) {} const bool operator==(const FAnimDynamicsViewportObjectReference& Lhs, const FAnimDynamicsViewportObjectReference& Rhs) { return (Lhs.EditorNodeUniqueId == Rhs.EditorNodeUniqueId) && (Lhs.Type == Rhs.Type) && (Lhs.Index == Rhs.Index); } // Non-member utility functions void DrawAngularLimits(FPrimitiveDrawInterface* PDI, FTransform JointTransform, const FAnimPhysConstraintSetup& ConstraintSetup) { FVector XAxis = JointTransform.GetUnitAxis(EAxis::X); FVector YAxis = JointTransform.GetUnitAxis(EAxis::Y); FVector ZAxis = JointTransform.GetUnitAxis(EAxis::Z); const FVector& MinAngles = ConstraintSetup.AngularLimitsMin; const FVector& MaxAngles = ConstraintSetup.AngularLimitsMax; FVector AngleRange = MaxAngles - MinAngles; FVector Middle = MinAngles + AngleRange * 0.5f; if (AngleRange.X > 0.0f && AngleRange.X < 180.0f) { FTransform XAxisConeTM(YAxis, XAxis ^ YAxis, XAxis, JointTransform.GetTranslation()); XAxisConeTM.SetRotation(FQuat(XAxis, FMath::DegreesToRadians(-Middle.X)) * XAxisConeTM.GetRotation()); DrawCone(PDI, FScaleMatrix(30.0f) * XAxisConeTM.ToMatrixWithScale(), FMath::DegreesToRadians(static_cast(AngleRange.X) / 2.0f), 0.0f, 24, false, FLinearColor::White, GEngine->ConstraintLimitMaterialX->GetRenderProxy(), SDPG_World); } if (AngleRange.Y > 0.0f && AngleRange.Y < 180.0f) { FTransform YAxisConeTM(ZAxis, YAxis ^ ZAxis, YAxis, JointTransform.GetTranslation()); YAxisConeTM.SetRotation(FQuat(YAxis, FMath::DegreesToRadians(Middle.Y)) * YAxisConeTM.GetRotation()); DrawCone(PDI, FScaleMatrix(30.0f) * YAxisConeTM.ToMatrixWithScale(), FMath::DegreesToRadians(static_cast(AngleRange.Y) / 2.0f), 0.0f, 24, false, FLinearColor::White, GEngine->ConstraintLimitMaterialY->GetRenderProxy(), SDPG_World); } if (AngleRange.Z > 0.0f && AngleRange.Z < 180.0f) { FTransform ZAxisConeTM(XAxis, ZAxis ^ XAxis, ZAxis, JointTransform.GetTranslation()); ZAxisConeTM.SetRotation(FQuat(ZAxis, FMath::DegreesToRadians(Middle.Z)) * ZAxisConeTM.GetRotation()); DrawCone(PDI, FScaleMatrix(30.0f) * ZAxisConeTM.ToMatrixWithScale(), FMath::DegreesToRadians(static_cast(AngleRange.Z) / 2.0f), 0.0f, 24, false, FLinearColor::White, GEngine->ConstraintLimitMaterialZ->GetRenderProxy(), SDPG_World); } } void DrawLinearLimits(FPrimitiveDrawInterface* PDI, FTransform ShapeTransform, const FAnimPhysConstraintSetup& ConstraintSetup) { // Draw linear limits FVector LinearLimitHalfExtents(ConstraintSetup.LinearAxesMax - ConstraintSetup.LinearAxesMin); // Add a tiny bit so we can see collapsed axes LinearLimitHalfExtents += FVector(0.1f); LinearLimitHalfExtents /= 2.0f; FVector LinearLimitsCenter = ConstraintSetup.LinearAxesMin + LinearLimitHalfExtents; FTransform LinearLimitsTransform = ShapeTransform; LinearLimitsTransform.SetTranslation(LinearLimitsTransform.GetTranslation() + LinearLimitsTransform.TransformVector(LinearLimitsCenter)); DrawBox(PDI, LinearLimitsTransform.ToMatrixWithScale(), LinearLimitHalfExtents, GEngine->ConstraintLimitMaterialPrismatic->GetRenderProxy(), SDPG_Foreground); } template< typename TShape > void DrawShape(FPrimitiveDrawInterface* const PDI, const TShape& Shape, const FTransform& Transform, const FLinearColor& Color, const float LineWidth) { for (const FIntVector& Triangle : Shape.Triangles) { for (int32 Idx = 0; Idx < 3; ++Idx) { int32 Next = (Idx + 1) % 3; FVector FirstVertPosition = Transform.TransformPosition(Shape.Vertices[Triangle[Idx]]); FVector SecondVertPosition = Transform.TransformPosition(Shape.Vertices[Triangle[Next]]); PDI->DrawLine(FirstVertPosition, SecondVertPosition, Color, SDPG_Foreground, LineWidth); } } } void DrawBasis(FPrimitiveDrawInterface* const PDI, const FTransform& Transform) { const FVector Origin = Transform.GetTranslation(); PDI->DrawLine(Origin, Origin + Transform.TransformVector(FVector::XAxisVector) * AnimDynamicsNodeConstants::TransformBasisScale, FLinearColor::Red, SDPG_Foreground, AnimDynamicsNodeConstants::TransformLineWidth); PDI->DrawLine(Origin, Origin + Transform.TransformVector(FVector::YAxisVector) * AnimDynamicsNodeConstants::TransformBasisScale, FLinearColor::Green, SDPG_Foreground, AnimDynamicsNodeConstants::TransformLineWidth); PDI->DrawLine(Origin, Origin + Transform.TransformVector(FVector::ZAxisVector) * AnimDynamicsNodeConstants::TransformBasisScale, FLinearColor::Blue, SDPG_Foreground, AnimDynamicsNodeConstants::TransformLineWidth); } // Class FAnimDynamicsEditMode FAnimDynamicsEditMode::FAnimDynamicsEditMode() : CurWidgetMode(UE::Widget::WM_Translate) , bIsInteractingWithWidget(false) {} void FAnimDynamicsEditMode::ExitMode() { SelectedViewportObjects.Empty(); for (const EditorRuntimeNodePair& CurrentNodePair : SelectedAnimNodes) { if (const UAnimGraphNode_AnimDynamics* const EditorAnimDynamicsNode = CastChecked(CurrentNodePair.EditorAnimNode)) { if (FAnimNode_AnimDynamics* const ActivePreviewNode = EditorAnimDynamicsNode->GetPreviewDynamicsNode()) { ActivePreviewNode->bDoPhysicsUpdateInEditor = true; // Ensure physics update is enabled on premature exit. } } } FAnimNodeEditMode::ExitMode(); } void FAnimDynamicsEditMode::Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI) { USkeletalMeshComponent* const PreviewSkelMeshComp = GetAnimPreviewScene().GetPreviewMeshComponent(); check(View); check(Viewport); check(PDI); check(PreviewSkelMeshComp); for (const EditorRuntimeNodePair& CurrentNodePair : SelectedAnimNodes) { const UAnimGraphNode_AnimDynamics* const EditorAnimDynamicsNode = CastChecked(CurrentNodePair.EditorAnimNode); if (EditorAnimDynamicsNode) { const FAnimNode_AnimDynamics* const ActivePreviewNode = EditorAnimDynamicsNode->GetPreviewDynamicsNode(); if (!ActivePreviewNode) { return; } if (EditorAnimDynamicsNode->IsPreviewLiveActive()) { for (int32 BodyIndex = 0; BodyIndex < ActivePreviewNode->GetNumBodies(); ++BodyIndex) { const FAnimPhysRigidBody& Body = ActivePreviewNode->GetPhysBody(BodyIndex); FTransform BodyTransform(Body.Pose.Orientation, Body.Pose.Position); // Physics bodies are in Simulation Space. Transform into component space before rendering in the viewport. if (ActivePreviewNode->SimulationSpace == AnimPhysSimSpaceType::RootRelative) { const FTransform RelativeBoneTransform = PreviewSkelMeshComp->GetBoneTransform(0); BodyTransform = BodyTransform * RelativeBoneTransform; } else if (ActivePreviewNode->SimulationSpace == AnimPhysSimSpaceType::BoneRelative) { const FTransform RelativeBoneTransform = PreviewSkelMeshComp->GetBoneTransform(PreviewSkelMeshComp->GetBoneIndex(ActivePreviewNode->RelativeSpaceBone.BoneName)); BodyTransform = BodyTransform * RelativeBoneTransform; } for (const FAnimPhysShape& Shape : Body.Shapes) { for (const FIntVector& Triangle : Shape.Triangles) { for (int32 Idx = 0; Idx < 3; ++Idx) { int32 Next = (Idx + 1) % 3; FVector FirstVertPosition = BodyTransform.TransformPosition(Shape.Vertices[Triangle[Idx]]); FVector SecondVertPosition = BodyTransform.TransformPosition(Shape.Vertices[Triangle[Next]]); PDI->DrawLine(FirstVertPosition, SecondVertPosition, AnimDynamicsNodeConstants::ActiveBodyDrawColor, SDPG_Foreground, AnimDynamicsNodeConstants::ShapeLineWidth); } } } const int32 BoneIndex = PreviewSkelMeshComp->GetBoneIndex(ActivePreviewNode->BoundBone.BoneName); if (BoneIndex != INDEX_NONE) { FTransform BodyJointTransform = PreviewSkelMeshComp->GetBoneTransform(BoneIndex); FTransform ShapeOriginalTransform = BodyJointTransform; // Draw pin location const FVector LocalPinOffset = - BodyTransform.Rotator().RotateVector(ActivePreviewNode->GetBodyLocalJointOffset(BodyIndex)); // Position of joint relative to physics body in world space. PDI->DrawLine(Body.Pose.Position, Body.Pose.Position + LocalPinOffset, FLinearColor::Green, SDPG_Foreground, AnimDynamicsNodeConstants::ShapeLineWidth); // Draw basis at body location FVector Origin = BodyTransform.GetTranslation(); FVector XAxis(1.0f, 0.0f, 0.0f); FVector YAxis(0.0f, 1.0f, 0.0f); FVector ZAxis(0.0f, 0.0f, 1.0f); XAxis = BodyTransform.TransformVector(XAxis); YAxis = BodyTransform.TransformVector(YAxis); ZAxis = BodyTransform.TransformVector(ZAxis); PDI->DrawLine(Origin, Origin + XAxis * AnimDynamicsNodeConstants::TransformBasisScale, FLinearColor::Red, SDPG_Foreground, AnimDynamicsNodeConstants::TransformLineWidth); PDI->DrawLine(Origin, Origin + YAxis * AnimDynamicsNodeConstants::TransformBasisScale, FLinearColor::Green, SDPG_Foreground, AnimDynamicsNodeConstants::TransformLineWidth); PDI->DrawLine(Origin, Origin + ZAxis * AnimDynamicsNodeConstants::TransformBasisScale, FLinearColor::Blue, SDPG_Foreground, AnimDynamicsNodeConstants::TransformLineWidth); } } } uint32 PhysicsBodyIndex = 0; for (const FAnimPhysBodyDefinition& BodyDefinition : ActivePreviewNode->PhysicsBodyDefinitions) { const int32 BoneIndex = PreviewSkelMeshComp->GetBoneIndex(BodyDefinition.BoundBone.BoneName); if (BoneIndex != INDEX_NONE) { // World space transform const FTransform BoneTransform = PreviewSkelMeshComp->GetBoneTransform(BoneIndex); FTransform ShapeTransform = BoneTransform; ShapeTransform.SetTranslation(ShapeTransform.GetTranslation() + BoneTransform.GetRotation().RotateVector(BodyDefinition.LocalJointOffset)); // Transform of rendered shape in world space. const FAnimPhysShape EditPreviewShape = FAnimPhysShape::MakeBox(BodyDefinition.BoxExtents); { const FAnimDynamicsViewportObjectReference ViewportObjectRef(EditorAnimDynamicsNode->GetUniqueID(), FAnimDynamicsViewportObjectType::BoxExtents, PhysicsBodyIndex); const bool bIsSelected = SelectedViewportObjects.Contains(ViewportObjectRef); const float LineWidth = (bIsSelected) ? AnimDynamicsNodeConstants::SelectedShapeLineWidth : AnimDynamicsNodeConstants::ShapeLineWidth; // Draw Physics Body PDI->SetHitProxy(new HAnimDynamicsEditModeHitProxy(ViewportObjectRef)); DrawShape(PDI, EditPreviewShape, ShapeTransform, AnimDynamicsNodeConstants::ShapeDrawColor, LineWidth); PDI->SetHitProxy(nullptr); } DrawBasis(PDI, ShapeTransform); // Draw line connecting rendered shape to its associated bone. PDI->DrawLine(ShapeTransform.GetTranslation(), BoneTransform.GetTranslation(), FLinearColor::Green, SDPG_Foreground, AnimDynamicsNodeConstants::ShapeLineWidth); if (EditorAnimDynamicsNode->bShowLinearLimits) { DrawLinearLimits(PDI, ShapeTransform, BodyDefinition.ConstraintSetup); } if (EditorAnimDynamicsNode->bShowAngularLimits) { DrawAngularLimits(PDI, ShapeTransform, BodyDefinition.ConstraintSetup); } if (EditorAnimDynamicsNode->bShowCollisionSpheres && BodyDefinition.CollisionType != AnimPhysCollisionType::CoM) { const FAnimDynamicsViewportObjectReference ViewportObjectRef(EditorAnimDynamicsNode->GetUniqueID(), FAnimDynamicsViewportObjectType::SphericalColisionVolume, PhysicsBodyIndex); const bool bIsSelected = SelectedViewportObjects.Contains(ViewportObjectRef); const float LineWidth = (bIsSelected) ? AnimDynamicsNodeConstants::SelectedShapeLineWidth : AnimDynamicsNodeConstants::ShapeLineWidth; // Draw Collision Sphere. PDI->SetHitProxy(new HAnimDynamicsEditModeHitProxy(ViewportObjectRef)); DrawWireSphere(PDI, ShapeTransform, FLinearColor(FColor::Cyan), BodyDefinition.SphereCollisionRadius, 24, SDPG_Foreground, LineWidth); PDI->SetHitProxy(nullptr); } } ++PhysicsBodyIndex; } const float LimitPlaneSize = AnimDynamicsNodeConstants::LimitPlaneDrawSize; const FLinearColor LimitEdgeColor = AnimDynamicsNodeConstants::LimitLineDrawColor; // Draw the planar limits. if (EditorAnimDynamicsNode->bShowPlanarLimit && ActivePreviewNode->PlanarLimits.Num() > 0) { uint32 LimitIndex = 0; for (const FAnimPhysPlanarLimit& PlanarLimit : ActivePreviewNode->PlanarLimits) { FTransform LimitPlaneTransform = PlanarLimit.PlaneTransform; const int32 LimitDrivingBoneIdx = PreviewSkelMeshComp->GetBoneIndex(PlanarLimit.DrivingBone.BoneName); if (LimitDrivingBoneIdx != INDEX_NONE) { LimitPlaneTransform *= PreviewSkelMeshComp->GetComponentSpaceTransforms()[LimitDrivingBoneIdx]; } const FMatrix PlaneTransform = LimitPlaneTransform.ToMatrixNoScale(); const FAnimDynamicsViewportObjectReference ViewportObjectRef(EditorAnimDynamicsNode->GetUniqueID(), FAnimDynamicsViewportObjectType::PlaneLimit, LimitIndex); const bool bIsSelected = SelectedViewportObjects.Contains(ViewportObjectRef); const float LineWidth = (bIsSelected) ? AnimDynamicsNodeConstants::SelectedShapeLineWidth : AnimDynamicsNodeConstants::ShapeLineWidth; PDI->SetHitProxy(new HAnimDynamicsEditModeHitProxy(ViewportObjectRef)); DrawPlane10x10(PDI, PlaneTransform, LimitPlaneSize, FVector2D(0.0f, 0.0f), FVector2D(1.0f, 1.0f), GEngine->ConstraintLimitMaterialPrismatic->GetRenderProxy(), SDPG_World); const FVector PlaneVertexPositionA(PlaneTransform.TransformPosition(FVector(-1.0, -1.0, 0.0) * LimitPlaneSize)); const FVector PlaneVertexPositionB(PlaneTransform.TransformPosition(FVector(1.0, -1.0, 0.0) * LimitPlaneSize)); const FVector PlaneVertexPositionC(PlaneTransform.TransformPosition(FVector(1.0, 1.0, 0.0) * LimitPlaneSize)); const FVector PlaneVertexPositionD(PlaneTransform.TransformPosition(FVector(-1.0, 1.0, 0.0) * LimitPlaneSize)); PDI->DrawLine(PlaneVertexPositionA, PlaneVertexPositionB, LimitEdgeColor, SDPG_Foreground, LineWidth); PDI->DrawLine(PlaneVertexPositionB, PlaneVertexPositionC, LimitEdgeColor, SDPG_Foreground, LineWidth); PDI->DrawLine(PlaneVertexPositionC, PlaneVertexPositionD, LimitEdgeColor, SDPG_Foreground, LineWidth); PDI->DrawLine(PlaneVertexPositionD, PlaneVertexPositionA, LimitEdgeColor, SDPG_Foreground, LineWidth); PDI->DrawLine(PlaneVertexPositionA, PlaneVertexPositionC, LimitEdgeColor, SDPG_Foreground, LineWidth); PDI->DrawLine(PlaneVertexPositionD, PlaneVertexPositionB, LimitEdgeColor, SDPG_Foreground, LineWidth); DrawDirectionalArrow(PDI, FRotationMatrix(FRotator(90.0f, 0.0f, 0.0f)) * LimitPlaneTransform.ToMatrixNoScale(), LimitEdgeColor, 50.0f, 20.0f, SDPG_Foreground, LineWidth); PDI->SetHitProxy(nullptr); ++LimitIndex; } } // Draw Spherical Limits. if (EditorAnimDynamicsNode->bShowSphericalLimit && ActivePreviewNode->SphericalLimits.Num() > 0) { uint32 LimitIndex = 0; for (const FAnimPhysSphericalLimit& SphericalLimit : ActivePreviewNode->SphericalLimits) { FTransform SphereTransform = FTransform::Identity; SphereTransform.SetTranslation(SphericalLimit.SphereLocalOffset); const int32 DrivingBoneIdx = PreviewSkelMeshComp->GetBoneIndex(SphericalLimit.DrivingBone.BoneName); if (DrivingBoneIdx != INDEX_NONE) { SphereTransform *= PreviewSkelMeshComp->GetComponentSpaceTransforms()[DrivingBoneIdx]; } const FAnimDynamicsViewportObjectReference ViewportObjectRef(EditorAnimDynamicsNode->GetUniqueID(), FAnimDynamicsViewportObjectType::SphericalLimit, LimitIndex); const bool bIsSelected = SelectedViewportObjects.Contains(ViewportObjectRef); const float LineWidth = (bIsSelected) ? AnimDynamicsNodeConstants::SelectedShapeLineWidth : AnimDynamicsNodeConstants::ShapeLineWidth; PDI->SetHitProxy(new HAnimDynamicsEditModeHitProxy(ViewportObjectRef)); DrawSphere(PDI, SphereTransform.GetLocation(), FRotator::ZeroRotator, FVector(SphericalLimit.LimitRadius), 24, 6, GEngine->ConstraintLimitMaterialPrismatic->GetRenderProxy(), SDPG_World); DrawWireSphere(PDI, SphereTransform, LimitEdgeColor, SphericalLimit.LimitRadius, 24, SDPG_World, LineWidth); PDI->SetHitProxy(nullptr); ++LimitIndex; } } } } } bool FAnimDynamicsEditMode::HandleClick(FEditorViewportClient* InViewportClient, HHitProxy* HitProxy, const FViewportClick& Click) { const FViewport* const Viewport = InViewportClient->Viewport; const bool bIsCtrlKeyDown = Viewport->KeyState(EKeys::LeftControl) || Viewport->KeyState(EKeys::RightControl); const bool bIsShiftKeyDown = Viewport->KeyState(EKeys::LeftShift) || Viewport->KeyState(EKeys::RightShift); const bool bIsAltKeyDown = Viewport->KeyState(EKeys::LeftAlt) || Viewport->KeyState(EKeys::RightAlt); bool bHandled = false; const bool bModifySelection = bIsCtrlKeyDown || bIsShiftKeyDown; const HAnimDynamicsEditModeHitProxy* const AnimDynamicsProxy = HitProxyCast(HitProxy); if (AnimDynamicsProxy) { const FAnimDynamicsViewportObjectReference HitObjectRef = AnimDynamicsProxy->ViewportObjectRef; if (!bModifySelection) { SelectedViewportObjects.Reset(); } if (bModifySelection && SelectedViewportObjects.Contains(HitObjectRef)) { SelectedViewportObjects.Remove(HitObjectRef); } else { SelectedViewportObjects.Emplace(HitObjectRef); } bHandled = true; } if (!bHandled) { bHandled = FEdMode::HandleClick(InViewportClient, HitProxy, Click); } if (!bHandled && !bModifySelection) { SelectedViewportObjects.Reset(); // Clear selection if Click on empty space. bHandled = true; } return bHandled; } ECoordSystem FAnimDynamicsEditMode::GetWidgetCoordinateSystem() const { return COORD_Local; } FVector FAnimDynamicsEditMode::GetWidgetLocation() const { return GetActiveViewportObjectTransform().GetTranslation(); } bool FAnimDynamicsEditMode::GetCustomDrawingCoordinateSystem(FMatrix& InMatrix, void* InData) { InMatrix = GetActiveViewportObjectTransform().ToMatrixNoScale().RemoveTranslation(); return true; } const bool FAnimDynamicsEditMode::IsValidWidgetMode(UE::Widget::EWidgetMode InWidgetMode) const { return (InWidgetMode == UE::Widget::WM_Scale) || (InWidgetMode == UE::Widget::WM_Translate); } UE::Widget::EWidgetMode FAnimDynamicsEditMode::GetNextWidgetMode(UE::Widget::EWidgetMode InWidgetMode) const { UE::Widget::EWidgetMode NextMode = UE::Widget::WM_Translate; if (InWidgetMode == UE::Widget::WM_Translate) { NextMode = UE::Widget::WM_Scale; } check(IsValidWidgetMode(NextMode)); return NextMode; } UE::Widget::EWidgetMode FAnimDynamicsEditMode::FindValidWidgetMode(UE::Widget::EWidgetMode InWidgetMode) const { UE::Widget::EWidgetMode ValidMode = InWidgetMode; if (!IsValidWidgetMode(ValidMode)) { ValidMode = GetNextWidgetMode(ValidMode); } check(IsValidWidgetMode(ValidMode)); return ValidMode; } UE::Widget::EWidgetMode FAnimDynamicsEditMode::GetWidgetMode() const { const USkeletalMeshComponent* const SkelComp = GetAnimPreviewScene().GetPreviewMeshComponent(); const UAnimGraphNode_AnimDynamics* const EditorAnimDynamicsNode = CastChecked(GetActiveWidgetAnimNode()); if (SkelComp && EditorAnimDynamicsNode) { const int32 BoneIndex = SkelComp->GetBoneIndex(EditorAnimDynamicsNode->Node.BoundBone.BoneName); if (BoneIndex != INDEX_NONE) { CurWidgetMode = FindValidWidgetMode(CurWidgetMode); return CurWidgetMode; } } return UE::Widget::WM_None; } UE::Widget::EWidgetMode FAnimDynamicsEditMode::ChangeToNextWidgetMode(UE::Widget::EWidgetMode InCurWidgetMode) { UE::Widget::EWidgetMode NextWidgetMode = GetNextWidgetMode(InCurWidgetMode); CurWidgetMode = FindValidWidgetMode(NextWidgetMode); return CurWidgetMode; } bool FAnimDynamicsEditMode::SetWidgetMode(UE::Widget::EWidgetMode InWidgetMode) { // if InWidgetMode is available if (IsValidWidgetMode(InWidgetMode)) { CurWidgetMode = InWidgetMode; return true; } return false; } FName FAnimDynamicsEditMode::GetSelectedBone() const { FName SelectedBoneName; if (SelectedViewportObjects.Num() > 0) { const uint32 Index = SelectedViewportObjects.Num() - 1; const FAnimDynamicsViewportObjectReference& SelectedObjectRef = SelectedViewportObjects[Index]; const UAnimGraphNode_AnimDynamics* const EditorAnimDynamicsNode = FindSelectedEditorAnimNode(SelectedObjectRef.EditorNodeUniqueId); const FAnimNode_AnimDynamics* const ActivePreviewNode = EditorAnimDynamicsNode->GetPreviewDynamicsNode(); { if ((SelectedObjectRef.Type == FAnimDynamicsViewportObjectType::BoxExtents) || (SelectedObjectRef.Type == FAnimDynamicsViewportObjectType::SphericalColisionVolume)) { if (ActivePreviewNode->PhysicsBodyDefinitions.IsValidIndex(SelectedObjectRef.Index)) { SelectedBoneName = ActivePreviewNode->PhysicsBodyDefinitions[SelectedObjectRef.Index].BoundBone.BoneName; } } else if (SelectedObjectRef.Type == FAnimDynamicsViewportObjectType::PlaneLimit) { if (ActivePreviewNode->PlanarLimits.IsValidIndex(SelectedObjectRef.Index)) { SelectedBoneName = ActivePreviewNode->PlanarLimits[SelectedObjectRef.Index].DrivingBone.BoneName; } } else if (SelectedObjectRef.Type == FAnimDynamicsViewportObjectType::SphericalLimit) { if (ActivePreviewNode->SphericalLimits.IsValidIndex(SelectedObjectRef.Index)) { SelectedBoneName = ActivePreviewNode->SphericalLimits[SelectedObjectRef.Index].DrivingBone.BoneName; } } } } return SelectedBoneName; } const UAnimGraphNode_AnimDynamics* const FAnimDynamicsEditMode::FindSelectedEditorAnimNode(const int32 InEditorNodeId) const { const FAnimNodeEditMode::EditorRuntimeNodePair* const FoundNodePair = SelectedAnimNodes.FindByPredicate([InEditorNodeId](const FAnimNodeEditMode::EditorRuntimeNodePair& Element) { return Element.EditorAnimNode && Element.EditorAnimNode->GetUniqueID() == InEditorNodeId; }); if (FoundNodePair) { return CastChecked< UAnimGraphNode_AnimDynamics >(FoundNodePair->EditorAnimNode); } return nullptr; } UAnimGraphNode_AnimDynamics* const FAnimDynamicsEditMode::FindSelectedEditorAnimNode(const int32 InEditorNodeId) { const FAnimNodeEditMode::EditorRuntimeNodePair* const FoundNodePair = SelectedAnimNodes.FindByPredicate([InEditorNodeId](const FAnimNodeEditMode::EditorRuntimeNodePair& Element) { return Element.EditorAnimNode && Element.EditorAnimNode->GetUniqueID() == InEditorNodeId; }); if (FoundNodePair) { return CastChecked< UAnimGraphNode_AnimDynamics >(FoundNodePair->EditorAnimNode); } return nullptr; } const FTransform FAnimDynamicsEditMode::GetActiveViewportObjectTransform() const { return GetViewportObjectTransform(GetActiveViewportObject()); } const FTransform FAnimDynamicsEditMode::GetViewportObjectTransform(const FAnimDynamicsViewportObjectReference* const SelectedObjectRef) const { FTransform Transform; Transform.SetIdentity(); if (SelectedObjectRef) { Transform = GetViewportObjectLocalSpaceTransform(SelectedObjectRef); const UAnimGraphNode_AnimDynamics* const EditorAnimDynamicsNode = FindSelectedEditorAnimNode(SelectedObjectRef->EditorNodeUniqueId); FName BoneName; if (EditorAnimDynamicsNode) { const FAnimNode_AnimDynamics* const ActivePreviewNode = EditorAnimDynamicsNode->GetPreviewDynamicsNode(); if (ActivePreviewNode) { if ((SelectedObjectRef->Type == FAnimDynamicsViewportObjectType::BoxExtents) || (SelectedObjectRef->Type == FAnimDynamicsViewportObjectType::SphericalColisionVolume)) { if (ActivePreviewNode->PhysicsBodyDefinitions.IsValidIndex(SelectedObjectRef->Index)) { Transform.SetTranslation(Transform.GetTranslation() + Transform.GetRotation().RotateVector(ActivePreviewNode->PhysicsBodyDefinitions[SelectedObjectRef->Index].LocalJointOffset)); } } else if (SelectedObjectRef->Type == FAnimDynamicsViewportObjectType::PlaneLimit) { if (ActivePreviewNode->PlanarLimits.IsValidIndex(SelectedObjectRef->Index)) { Transform = ActivePreviewNode->PlanarLimits[SelectedObjectRef->Index].PlaneTransform * Transform; } } else if (SelectedObjectRef->Type == FAnimDynamicsViewportObjectType::SphericalLimit) { if (ActivePreviewNode->SphericalLimits.IsValidIndex(SelectedObjectRef->Index)) { Transform.SetTranslation(Transform.GetTranslation() + Transform.GetRotation().RotateVector(ActivePreviewNode->SphericalLimits[SelectedObjectRef->Index].SphereLocalOffset)); } } } } } return Transform; } const FTransform FAnimDynamicsEditMode::GetViewportObjectLocalSpaceTransform(const FAnimDynamicsViewportObjectReference* const SelectedObjectRef) const { FTransform Transform; Transform.SetIdentity(); const USkeletalMeshComponent* const SkelComp = GetAnimPreviewScene().GetPreviewMeshComponent(); if (SelectedObjectRef && SkelComp) { const FName BoneName = GetSelectedBone(); const int32 BoneIndex = SkelComp->GetBoneIndex(BoneName); if (BoneIndex != INDEX_NONE) { Transform = SkelComp->GetComponentSpaceTransforms()[BoneIndex]; } } return Transform; } void FAnimDynamicsEditMode::DoTranslation(FVector& InTranslation) { const USkeletalMeshComponent* const SkelComp = GetAnimPreviewScene().GetPreviewMeshComponent(); if (SkelComp) { // Find widget translation in the space of the selected objects associated bone. const FTransform LocalSpaceTransform = GetViewportObjectLocalSpaceTransform(GetActiveViewportObject()); const FVector LocalSpaceTranslation = LocalSpaceTransform.GetRotation().UnrotateVector(InTranslation); // Apply local space widget translation to all selected nodes. This means the collision volumes will each move in the same direction // in their own space, potentially different directions in world space. for (const FAnimDynamicsViewportObjectReference& SelectedObjectRef : SelectedViewportObjects) { UAnimGraphNode_AnimDynamics* const EditorAnimDynamicsNode = FindSelectedEditorAnimNode(SelectedObjectRef.EditorNodeUniqueId); if (EditorAnimDynamicsNode) { FAnimNode_AnimDynamics* const RuntimeAnimDynamicsNode = EditorAnimDynamicsNode->GetPreviewDynamicsNode(); if (RuntimeAnimDynamicsNode) { if ((SelectedObjectRef.Type == FAnimDynamicsViewportObjectType::BoxExtents) || (SelectedObjectRef.Type == FAnimDynamicsViewportObjectType::SphericalColisionVolume)) { if (RuntimeAnimDynamicsNode->PhysicsBodyDefinitions.IsValidIndex(SelectedObjectRef.Index)) { FVector& PhysicsBodyJointOffset = RuntimeAnimDynamicsNode->PhysicsBodyDefinitions[SelectedObjectRef.Index].LocalJointOffset; PhysicsBodyJointOffset += LocalSpaceTranslation; EditorAnimDynamicsNode->Node.PhysicsBodyDefinitions[SelectedObjectRef.Index].LocalJointOffset = PhysicsBodyJointOffset; } } else if (SelectedObjectRef.Type == FAnimDynamicsViewportObjectType::PlaneLimit) { if (RuntimeAnimDynamicsNode->PlanarLimits.IsValidIndex(SelectedObjectRef.Index)) { FTransform& LimitTransform = RuntimeAnimDynamicsNode->PlanarLimits[SelectedObjectRef.Index].PlaneTransform; LimitTransform.SetTranslation(LimitTransform.GetTranslation() + LocalSpaceTranslation); EditorAnimDynamicsNode->Node.PlanarLimits[SelectedObjectRef.Index].PlaneTransform = LimitTransform; } } else if (SelectedObjectRef.Type == FAnimDynamicsViewportObjectType::SphericalLimit) { if (RuntimeAnimDynamicsNode->SphericalLimits.IsValidIndex(SelectedObjectRef.Index)) { FVector& LimitOffset = RuntimeAnimDynamicsNode->SphericalLimits[SelectedObjectRef.Index].SphereLocalOffset; LimitOffset += LocalSpaceTranslation; EditorAnimDynamicsNode->Node.SphericalLimits[SelectedObjectRef.Index].SphereLocalOffset = LimitOffset; } } } } } } bIsInteractingWithWidget = true; } void FAnimDynamicsEditMode::DoRotation(FRotator& InRotation) { const USkeletalMeshComponent* const SkelComp = GetAnimPreviewScene().GetPreviewMeshComponent(); if (SkelComp) { // Transform rotation to the active object's local space. FQuat LocalSpaceRotation(FQuat::Identity); { FVector RotAxis; float RotAngle; InRotation.Quaternion().ToAxisAndAngle(RotAxis, RotAngle); const FTransform SelectedBoneTM = GetViewportObjectLocalSpaceTransform(GetActiveViewportObject()); RotAxis = SelectedBoneTM.Inverse().TransformVector(RotAxis); FQuat DeltaQuat(RotAxis, RotAngle); DeltaQuat.Normalize(); LocalSpaceRotation = DeltaQuat; } // Apply local rotation to all selected objects. for (const FAnimDynamicsViewportObjectReference& SelectedObjectRef : SelectedViewportObjects) { UAnimGraphNode_AnimDynamics* const EditorAnimDynamicsNode = FindSelectedEditorAnimNode(SelectedObjectRef.EditorNodeUniqueId); if (EditorAnimDynamicsNode) { FAnimNode_AnimDynamics* const RuntimeAnimDynamicsNode = EditorAnimDynamicsNode->GetPreviewDynamicsNode(); if (RuntimeAnimDynamicsNode) { if (SelectedObjectRef.Type == FAnimDynamicsViewportObjectType::PlaneLimit) { if (RuntimeAnimDynamicsNode->PlanarLimits.IsValidIndex(SelectedObjectRef.Index)) { FTransform& LimitTransform = RuntimeAnimDynamicsNode->PlanarLimits[SelectedObjectRef.Index].PlaneTransform; LimitTransform.SetRotation(LocalSpaceRotation * LimitTransform.GetRotation()); EditorAnimDynamicsNode->Node.PlanarLimits[SelectedObjectRef.Index].PlaneTransform = LimitTransform; } } } } } } } void FAnimDynamicsEditMode::DoScale(FVector& InScale) { const float Scale = static_cast(InScale.X + InScale.Y + InScale.Z); for (const FAnimDynamicsViewportObjectReference& SelectedObjectRef : SelectedViewportObjects) { UAnimGraphNode_AnimDynamics* const EditorAnimDynamicsNode = FindSelectedEditorAnimNode(SelectedObjectRef.EditorNodeUniqueId); if (EditorAnimDynamicsNode) { FAnimNode_AnimDynamics* const RuntimeAnimDynamicsNode = EditorAnimDynamicsNode->GetPreviewDynamicsNode(); if (RuntimeAnimDynamicsNode) { if (SelectedObjectRef.Type == FAnimDynamicsViewportObjectType::BoxExtents) { if (RuntimeAnimDynamicsNode->PhysicsBodyDefinitions.IsValidIndex(SelectedObjectRef.Index)) { FVector& PhysicsBodyBoxExtents = RuntimeAnimDynamicsNode->PhysicsBodyDefinitions[SelectedObjectRef.Index].BoxExtents; PhysicsBodyBoxExtents += InScale; EditorAnimDynamicsNode->Node.PhysicsBodyDefinitions[SelectedObjectRef.Index].BoxExtents = PhysicsBodyBoxExtents; } } else if (SelectedObjectRef.Type == FAnimDynamicsViewportObjectType::SphericalColisionVolume) { if (RuntimeAnimDynamicsNode->PhysicsBodyDefinitions.IsValidIndex(SelectedObjectRef.Index)) { float& PhysicsBodySphereCollisionRadius = RuntimeAnimDynamicsNode->PhysicsBodyDefinitions[SelectedObjectRef.Index].SphereCollisionRadius; PhysicsBodySphereCollisionRadius += Scale; EditorAnimDynamicsNode->Node.PhysicsBodyDefinitions[SelectedObjectRef.Index].SphereCollisionRadius = PhysicsBodySphereCollisionRadius; } } else if (SelectedObjectRef.Type == FAnimDynamicsViewportObjectType::SphericalLimit) { if (RuntimeAnimDynamicsNode->SphericalLimits.IsValidIndex(SelectedObjectRef.Index)) { const float LimitRadius = RuntimeAnimDynamicsNode->SphericalLimits[SelectedObjectRef.Index].LimitRadius + Scale; RuntimeAnimDynamicsNode->SphericalLimits[SelectedObjectRef.Index].LimitRadius = LimitRadius; EditorAnimDynamicsNode->Node.SphericalLimits[SelectedObjectRef.Index].LimitRadius = LimitRadius; } } } } } bIsInteractingWithWidget = true; } void FAnimDynamicsEditMode::Tick(FEditorViewportClient* ViewportClient, float DeltaTime) { FAnimNodeEditMode::Tick(ViewportClient, DeltaTime); // Keep this flag set until the mouse is released after moving the translation widget. DoTranslation / Scale fns are only called when // the widget actually moves but IsManipulatingWidget() rtns true whenever the input button is pressed. This logic prevents the physics // simulation from stating/stopping unexpectedly when the widget is being manipulated but hasn't moved and avoids stopping the simulation // every time a mouse button is pressed over the viewport. bIsInteractingWithWidget = IsManipulatingWidget() && bIsInteractingWithWidget; USkeletalMeshComponent* const PreviewSkelMeshComp = GetAnimPreviewScene().GetPreviewMeshComponent(); for (const EditorRuntimeNodePair& CurrentNodePair : SelectedAnimNodes) { const UAnimGraphNode_AnimDynamics* const EditorAnimDynamicsNode = CastChecked(CurrentNodePair.EditorAnimNode); if (EditorAnimDynamicsNode) { FAnimNode_AnimDynamics* const ActivePreviewNode = EditorAnimDynamicsNode->GetPreviewDynamicsNode(); if (ActivePreviewNode) { // Disable physics simulation when physics parameters are being edited via a widget, otherwise editing becomes // very difficult as the objects are moving and the simulation is liable to become unstable. if (bIsInteractingWithWidget) { ActivePreviewNode->bDoPhysicsUpdateInEditor = false; } else if (!ActivePreviewNode->bDoPhysicsUpdateInEditor) { ActivePreviewNode->RequestInitialise(ETeleportType::ResetPhysics); ActivePreviewNode->bDoPhysicsUpdateInEditor = true; } } } } } bool FAnimDynamicsEditMode::ShouldDrawWidget() const { return SelectedViewportObjects.Num() > 0; // Only draw a widget if at least one viewport object is selected. } const FAnimDynamicsViewportObjectReference* const FAnimDynamicsEditMode::GetActiveViewportObject() const { if (SelectedViewportObjects.Num() > 0) { return &SelectedViewportObjects[SelectedViewportObjects.Num() - 1]; } return nullptr; }