// Copyright Epic Games, Inc. All Rights Reserved. #include "Mechanics/CollisionPrimitivesMechanic.h" #include "BaseBehaviors/SingleClickOrDragBehavior.h" #include "BaseBehaviors/MouseHoverBehavior.h" #include "BaseGizmos/IntervalGizmo.h" #include "BaseGizmos/TransformGizmoUtil.h" #include "BaseGizmos/TransformProxy.h" #include "Drawing/PreviewGeometryActor.h" #include "Drawing/LineSetComponent.h" #include "Engine/World.h" #include "InteractiveToolManager.h" #include "ToolSceneQueriesUtil.h" #include "ToolSetupUtil.h" #include "Transforms/MultiTransformer.h" #include "Physics/PhysicsDataCollection.h" #include "Util/ColorConstants.h" #include "Generators/LineSegmentGenerators.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(CollisionPrimitivesMechanic) #define LOCTEXT_NAMESPACE "UCollisionPrimitivesMechanic" namespace CollisionPrimitivesMechanicLocals { static const FText CollisionPrimitiveDeselectionTransactionText = LOCTEXT("CollisionPrimitiveDeselection", "Collision Primitive Deselection"); static const FText CollisionPrimitiveSelectionTransactionText = LOCTEXT("CollisionPrimitiveSelection", "Collision Primitive Selection"); static const FText CollisionPrimitiveMovementTransactionText = LOCTEXT("CollisionPrimitiveGeometryChange", "Collision Primitive Geometry Change"); void Toggle(TSet& Selection, const TSet& NewSelection) { for (const int& NewElement : NewSelection) { if (Selection.Remove(NewElement) == 0) { Selection.Add(NewElement); } } } // TODO Move to SetUtil namespace and call it in FGroupTopologySelection::operator== bool IsEqual(const TSet& A, const TSet& B) { if (A.Num() != B.Num()) { return false; } // No need to also check B.Includes(A) here since counts match and sets contain unique elements return A.Includes(B); } } void UCollisionPrimitivesMechanic::Setup(UInteractiveTool* ParentToolIn) { UInteractionMechanic::Setup(ParentToolIn); MarqueeMechanic = NewObject(this); MarqueeMechanic->bUseExternalClickDragBehavior = true; MarqueeMechanic->Setup(ParentToolIn); MarqueeMechanic->OnDragRectangleStarted.AddUObject(this, &UCollisionPrimitivesMechanic::OnDragRectangleStarted); MarqueeMechanic->OnDragRectangleChanged.AddUObject(this, &UCollisionPrimitivesMechanic::OnDragRectangleChanged); MarqueeMechanic->OnDragRectangleFinished.AddUObject(this, &UCollisionPrimitivesMechanic::OnDragRectangleFinished); USingleClickOrDragInputBehavior* ClickOrDragBehavior = NewObject(); ClickOrDragBehavior->Initialize(this, MarqueeMechanic); ClickOrDragBehavior->Modifiers.RegisterModifier(ShiftModifierID, FInputDeviceState::IsShiftKeyDown); ClickOrDragBehavior->Modifiers.RegisterModifier(CtrlModifierID, FInputDeviceState::IsCtrlKeyDown); ParentTool->AddInputBehavior(ClickOrDragBehavior); UMouseHoverBehavior* HoverBehavior = NewObject(); HoverBehavior->Initialize(this); ParentTool->AddInputBehavior(HoverBehavior); NormalSegmentColor = FColor::Red; HoverColor = FColor::Green; SelectedColor = FColor::Yellow; SegmentsThickness = 1.0f; float UseSegmentRadius = 5.0f; GeometrySetToleranceTest = [UseSegmentRadius](const FVector3d& A, const FVector3d& B) { return UE::Geometry::DistanceSquared(A, B) < UseSegmentRadius * UseSegmentRadius; }; UInteractiveGizmoManager* GizmoManager = GetParentTool()->GetToolManager()->GetPairedGizmoManager(); TranslateTransformProxy = NewObject(this); SphereTransformProxy = NewObject(this); BoxTransformProxy = NewObject(this); CapsuleTransformProxy = NewObject(this); FullTransformProxy = NewObject(this); // TODO: Maybe don't have the gizmo's axes flip around when it crosses the origin, if possible? TranslateTransformGizmo = UE::TransformGizmoUtil::CreateCustomTransformGizmo(GizmoManager, ETransformGizmoSubElements::TranslateAllAxes | ETransformGizmoSubElements::TranslateAllPlanes, this); SphereTransformGizmo = UE::TransformGizmoUtil::CreateCustomTransformGizmo(GizmoManager, ETransformGizmoSubElements::TranslateAllAxes | ETransformGizmoSubElements::TranslateAllPlanes | ETransformGizmoSubElements::ScaleUniform, this); BoxTransformGizmo = UE::TransformGizmoUtil::CreateCustomTransformGizmo(GizmoManager, ETransformGizmoSubElements::TranslateAllAxes | ETransformGizmoSubElements::TranslateAllPlanes | ETransformGizmoSubElements::RotateAllAxes, this); // create sources for the interval parameters BoxXIntervalSource = NewObject< UGizmoLocalFloatParameterSource >(this); BoxYIntervalSource = NewObject< UGizmoLocalFloatParameterSource >(this); BoxZIntervalSource = NewObject< UGizmoLocalFloatParameterSource >(this); CapsuleRadiusIntervalSource = NewObject< UGizmoLocalFloatParameterSource >(this); CapsuleLengthIntervalSource = NewObject< UGizmoLocalFloatParameterSource >(this); // add the interval gizmo GizmoManager->RegisterGizmoType(UIntervalGizmo::GizmoName, NewObject()); BoxIntervalGizmo = GizmoManager->CreateGizmo(UIntervalGizmo::GizmoName, TEXT("BoxSizeInterval"), this); UIntervalGizmo::FParameterSources BoxIntervalSources; BoxIntervalSources.RightInterval = BoxXIntervalSource; BoxIntervalSources.ForwardInterval = BoxYIntervalSource; BoxIntervalSources.UpInterval = BoxZIntervalSource; BoxIntervalGizmo->SetActiveTarget(BoxTransformProxy, BoxIntervalSources); CapsuleTransformGizmo = UE::TransformGizmoUtil::CreateCustomTransformGizmo(GizmoManager, ETransformGizmoSubElements::TranslateAllAxes | ETransformGizmoSubElements::TranslateAllPlanes | ETransformGizmoSubElements::RotateAllAxes, this); CapsuleIntervalGizmo = GizmoManager->CreateGizmo(UIntervalGizmo::GizmoName, TEXT("CapsuleSizeInterval"), this); UIntervalGizmo::FParameterSources CapsuleIntervalSources; CapsuleIntervalSources.RightInterval = CapsuleRadiusIntervalSource; CapsuleIntervalSources.UpInterval = CapsuleLengthIntervalSource; CapsuleIntervalGizmo->SetActiveTarget(CapsuleTransformProxy, CapsuleIntervalSources); FullTransformGizmo = UE::TransformGizmoUtil::CreateCustomTransformGizmo(GizmoManager, ETransformGizmoSubElements::FullTranslateRotateScale, this); const bool bShouldUseContextCoordinateSystem = true; TranslateTransformProxy->OnTransformChanged.AddUObject(this, &UCollisionPrimitivesMechanic::GizmoTransformChanged); TranslateTransformProxy->OnBeginTransformEdit.AddUObject(this, &UCollisionPrimitivesMechanic::GizmoTransformStarted); TranslateTransformProxy->OnEndTransformEdit.AddUObject(this, &UCollisionPrimitivesMechanic::GizmoTransformEnded); TranslateTransformGizmo->SetActiveTarget(TranslateTransformProxy); TranslateTransformGizmo->SetVisibility(false); TranslateTransformGizmo->bUseContextCoordinateSystem = bShouldUseContextCoordinateSystem; TranslateTransformGizmo->CurrentCoordinateSystem = EToolContextCoordinateSystem::Local; SphereTransformProxy->OnTransformChanged.AddUObject(this, &UCollisionPrimitivesMechanic::GizmoTransformChanged); SphereTransformProxy->OnBeginTransformEdit.AddUObject(this, &UCollisionPrimitivesMechanic::GizmoTransformStarted); SphereTransformProxy->OnEndTransformEdit.AddUObject(this, &UCollisionPrimitivesMechanic::GizmoTransformEnded); SphereTransformGizmo->SetActiveTarget(SphereTransformProxy); SphereTransformGizmo->SetVisibility(false); SphereTransformGizmo->bUseContextCoordinateSystem = bShouldUseContextCoordinateSystem; SphereTransformGizmo->CurrentCoordinateSystem = EToolContextCoordinateSystem::Local; BoxTransformProxy->OnTransformChanged.AddUObject(this, &UCollisionPrimitivesMechanic::GizmoTransformChanged); BoxTransformProxy->OnBeginTransformEdit.AddUObject(this, &UCollisionPrimitivesMechanic::GizmoTransformStarted); BoxTransformProxy->OnEndTransformEdit.AddUObject(this, &UCollisionPrimitivesMechanic::GizmoTransformEnded); BoxIntervalGizmo->OnIntervalChanged.AddUObject(this, &UCollisionPrimitivesMechanic::IntervalGizmoValueChanged); BoxTransformGizmo->SetActiveTarget(BoxTransformProxy); BoxTransformGizmo->SetVisibility(false); BoxTransformGizmo->bUseContextCoordinateSystem = bShouldUseContextCoordinateSystem; BoxTransformGizmo->CurrentCoordinateSystem = EToolContextCoordinateSystem::Local; BoxIntervalGizmo->SetVisibility(false); CapsuleTransformProxy->OnTransformChanged.AddUObject(this, &UCollisionPrimitivesMechanic::GizmoTransformChanged); CapsuleTransformProxy->OnBeginTransformEdit.AddUObject(this, &UCollisionPrimitivesMechanic::GizmoTransformStarted); CapsuleTransformProxy->OnEndTransformEdit.AddUObject(this, &UCollisionPrimitivesMechanic::GizmoTransformEnded); CapsuleIntervalGizmo->OnIntervalChanged.AddUObject(this, &UCollisionPrimitivesMechanic::IntervalGizmoValueChanged); CapsuleTransformGizmo->SetActiveTarget(CapsuleTransformProxy); CapsuleTransformGizmo->SetVisibility(false); CapsuleTransformGizmo->bUseContextCoordinateSystem = bShouldUseContextCoordinateSystem; CapsuleTransformGizmo->CurrentCoordinateSystem = EToolContextCoordinateSystem::Local; CapsuleIntervalGizmo->SetVisibility(false); FullTransformProxy->OnTransformChanged.AddUObject(this, &UCollisionPrimitivesMechanic::GizmoTransformChanged); FullTransformProxy->OnBeginTransformEdit.AddUObject(this, &UCollisionPrimitivesMechanic::GizmoTransformStarted); FullTransformProxy->OnEndTransformEdit.AddUObject(this, &UCollisionPrimitivesMechanic::GizmoTransformEnded); FullTransformGizmo->SetActiveTarget(FullTransformProxy); FullTransformGizmo->SetVisibility(false); FullTransformGizmo->bUseContextCoordinateSystem = bShouldUseContextCoordinateSystem; FullTransformGizmo->CurrentCoordinateSystem = EToolContextCoordinateSystem::Local; // Hold the active proxy, so we can capture undo information from whatever gizmo got moved by the user. CurrentActiveProxy = TranslateTransformProxy; } void UCollisionPrimitivesMechanic::Initialize(TSharedPtr InPhysicsData, const UE::Geometry::FAxisAlignedBox3d& MeshBoundsIn, const FTransform3d& InLocalToWorldTransform) { LocalToWorldTransform = InLocalToWorldTransform; PhysicsData = InPhysicsData; MeshBounds = MeshBoundsIn; SelectedPrimitiveIDs.Empty(); UpdateGizmoLocation(); RebuildDrawables(); ++CurrentChangeStamp; } void UCollisionPrimitivesMechanic::SetWorld(UWorld* World) { // It may be unreasonable to worry about SetWorld being called more than once, but let's be safe anyway if (PreviewGeometry) { PreviewGeometry->Disconnect(); PreviewGeometry = nullptr; } // We need the world so we can create the geometry actor in the right place FRotator Rotation(0.0f, 0.0f, 0.0f); FActorSpawnParameters SpawnInfo; PreviewGeometry = NewObject(this); PreviewGeometry->CreateInWorld(World, FTransform()); DrawnPrimitiveEdges = PreviewGeometry->AddLineSet("CollisionPrimitives"); DrawnPrimitiveEdges->SetLineMaterial(ToolSetupUtil::GetDefaultLineComponentMaterial(GetParentTool()->GetToolManager(), false)); } void UCollisionPrimitivesMechanic::Shutdown() { LongTransactions.CloseAll(GetParentTool()->GetToolManager()); if (PreviewGeometry) { PreviewGeometry->Disconnect(); PreviewGeometry = nullptr; } // Calls shutdown on gizmo and destroys it. UInteractiveGizmoManager* GizmoManager = GetParentTool()->GetToolManager()->GetPairedGizmoManager(); GizmoManager->DestroyAllGizmosByOwner(this); GizmoManager->DeregisterGizmoType(UIntervalGizmo::GizmoName); } void UCollisionPrimitivesMechanic::Render(IToolsContextRenderAPI* RenderAPI) { // Cache the camera state GetParentTool()->GetToolManager()->GetContextQueriesAPI()->GetCurrentViewState(CachedCameraState); MarqueeMechanic->Render(RenderAPI); } void UCollisionPrimitivesMechanic::DrawHUD(FCanvas* Canvas, IToolsContextRenderAPI* RenderAPI) { MarqueeMechanic->DrawHUD(Canvas, RenderAPI); } void UCollisionPrimitivesMechanic::RebuildDrawables(bool bRegenerateCurveLists) { // TODO: This code replicates a lot of the functionality found in CollisionGeometryVisualization.cpp, but with additional behavior needed for this class. Unification in the future would be good to explore. int32 CircleSteps = FMath::Max(24, 24); typedef TFunction& LinesOut, int32 PrimIndex, int32 CurveIndex, const FTransform& Transform, const FVector& A, const FVector& B, const FColor& Color)> WriteCurveFunc; WriteCurveFunc StoreCurve; if (bRegenerateCurveLists) { GeometrySet.Reset(); DrawnPrimitiveEdges->Clear(); PrimitiveRenderData.Empty(); PrimitiveToCurveLookup.Empty(); CurveToPrimitiveLookup.Empty(); StoreCurve = [this](FPrimitiveRenderData& RenderData, TArray& LinesOut, int32 PrimIndex, int32 CurveIndex, const FTransform& Transform, const FVector& A, const FVector& B, const FColor& Color) -> void { FVector A_World, B_World; A_World = Transform.TransformPosition(A); B_World = Transform.TransformPosition(B); LinesOut.Add(FRenderableLine(A_World, B_World, Color, SegmentsThickness, DepthBias)); GeometrySet.AddCurve(CurveIndex, UE::Geometry::FPolyline3d(A_World, B_World)); PrimitiveToCurveLookup.FindOrAdd(PrimIndex).Add(CurveIndex); CurveToPrimitiveLookup.FindOrAdd(CurveIndex) = PrimIndex; }; } else { StoreCurve = [this](FPrimitiveRenderData& RenderData, TArray& LinesOut, int32 PrimIndex, int32 CurveIndex, const FTransform& Transform, const FVector& A, const FVector& B, const FColor& Color) -> void { FVector A_World, B_World; A_World = Transform.TransformPosition(A); B_World = Transform.TransformPosition(B); DrawnPrimitiveEdges->SetLineStart(CurveIndex, A_World); DrawnPrimitiveEdges->SetLineEnd(CurveIndex, B_World); GeometrySet.UpdateCurve(CurveIndex, UE::Geometry::FPolyline3d(A_World, B_World)); ensure(PrimitiveToCurveLookup[PrimIndex].Contains(CurveIndex)); ensure(CurveToPrimitiveLookup[CurveIndex] == PrimIndex); }; } const FKAggregateGeom& AggGeom = PhysicsData->AggGeom; int32 PrimIndex = 0; int32 GobalCurveIndex = 0; // spheres are draw as 3 orthogonal circles for (int32 Index = 0; Index < AggGeom.SphereElems.Num(); Index++) { FPrimitiveRenderData RenderData; RenderData.PrimIndex = PrimIndex; RenderData.LineIndexStart = GobalCurveIndex; FColor Color = SelectedPrimitiveIDs.Contains(PrimIndex) ? SelectedColor : NormalSegmentColor; RenderData.RenderColor = Color; RenderData.ShapeType = EAggCollisionShape::Sphere; int32 CurveIndex = 0; DrawnPrimitiveEdges->AddLines(1, [this, bRegenerateCurveLists, &StoreCurve, &RenderData, &AggGeom, &Color, CircleSteps, PrimIndex, &GobalCurveIndex, Index](int32 UnusedIndex, TArray& LinesOut) { const FKSphereElem& Sphere = AggGeom.SphereElems[Index]; FTransform ElemTransform = Sphere.GetTransform(); ElemTransform.ScaleTranslation(PhysicsData->ExternalScale3D); if (bRegenerateCurveLists) { RenderData.Transform = ElemTransform; } float Radius = PhysicsData->ExternalScale3D.GetAbsMin() * Sphere.Radius; UE::Geometry::GenerateCircleSegments(CircleSteps, Radius, FVector3f::Zero(), FVector3f::UnitX(), FVector3f::UnitY(), UE::Geometry::FTransformSRT3f(), [&](const FVector3f& A, const FVector3f& B) { StoreCurve(RenderData, LinesOut, PrimIndex, GobalCurveIndex++, ElemTransform * LocalToWorldTransform, (FVector)A, (FVector)B, Color); }); UE::Geometry::GenerateCircleSegments(CircleSteps, Radius, FVector3f::Zero(), FVector3f::UnitX(), FVector3f::UnitZ(), UE::Geometry::FTransformSRT3f(), [&](const FVector3f& A, const FVector3f& B) { StoreCurve(RenderData, LinesOut, PrimIndex, GobalCurveIndex++, ElemTransform * LocalToWorldTransform, (FVector)A, (FVector)B, Color); }); UE::Geometry::GenerateCircleSegments(CircleSteps, Radius, FVector3f::Zero(), FVector3f::UnitY(), FVector3f::UnitZ(), UE::Geometry::FTransformSRT3f(), [&](const FVector3f& A, const FVector3f& B) { StoreCurve(RenderData, LinesOut, PrimIndex, GobalCurveIndex++, ElemTransform * LocalToWorldTransform, (FVector)A, (FVector)B, Color); }); }); RenderData.LineIndexEnd = GobalCurveIndex; if (bRegenerateCurveLists) { PrimitiveRenderData.Add(RenderData); } PrimIndex++; } // boxes are drawn as boxes for (int32 Index = 0; Index < AggGeom.BoxElems.Num(); Index++) { FPrimitiveRenderData RenderData; RenderData.PrimIndex = PrimIndex; RenderData.LineIndexStart = GobalCurveIndex; FColor Color = SelectedPrimitiveIDs.Contains(PrimIndex) ? SelectedColor : NormalSegmentColor; RenderData.RenderColor = Color; RenderData.ShapeType = EAggCollisionShape::Box; int32 CurveIndex = 0; DrawnPrimitiveEdges->AddLines(1, [this, bRegenerateCurveLists, &StoreCurve, &RenderData, &AggGeom, &Color, CircleSteps, PrimIndex, &GobalCurveIndex, Index](int32 UnusedIndex, TArray& LinesOut) { const FKBoxElem& Box = AggGeom.BoxElems[Index]; FTransform ElemTransform = Box.GetTransform(); ElemTransform.ScaleTranslation(PhysicsData->ExternalScale3D); if (bRegenerateCurveLists) { RenderData.Transform = ElemTransform; } FVector3f HalfDimensions( PhysicsData->ExternalScale3D.X * Box.X * 0.5f, PhysicsData->ExternalScale3D.Y * Box.Y * 0.5f, PhysicsData->ExternalScale3D.Z * Box.Z * 0.5f); UE::Geometry::GenerateBoxSegments(HalfDimensions, FVector3f::Zero(), FVector3f::UnitX(), FVector3f::UnitY(), FVector3f::UnitZ(), UE::Geometry::FTransformSRT3f(), [&](const FVector3f& A, const FVector3f& B) { StoreCurve(RenderData, LinesOut, PrimIndex, GobalCurveIndex++, ElemTransform* LocalToWorldTransform, (FVector)A, (FVector)B, Color); }); }); RenderData.LineIndexEnd = GobalCurveIndex; if (bRegenerateCurveLists) { PrimitiveRenderData.Add(RenderData); } PrimIndex++; } // capsules are draw as two hemispheres (with 3 intersecting arcs/circles) and connecting lines for (int32 Index = 0; Index < AggGeom.SphylElems.Num(); Index++) { FPrimitiveRenderData RenderData; RenderData.PrimIndex = PrimIndex; RenderData.LineIndexStart = GobalCurveIndex; FColor Color = SelectedPrimitiveIDs.Contains(PrimIndex) ? SelectedColor : NormalSegmentColor; RenderData.RenderColor = Color; RenderData.ShapeType = EAggCollisionShape::Sphyl; int32 CurveIndex = 0; DrawnPrimitiveEdges->AddLines(1, [this, bRegenerateCurveLists, &StoreCurve, &RenderData, &AggGeom, &Color, CircleSteps, PrimIndex, &GobalCurveIndex, Index](int32 UnusedIndex, TArray& LinesOut) { const FKSphylElem& Capsule = AggGeom.SphylElems[Index]; FTransform ElemTransform = Capsule.GetTransform(); ElemTransform.ScaleTranslation(PhysicsData->ExternalScale3D); if (bRegenerateCurveLists) { RenderData.Transform = ElemTransform; } const float HalfLength = Capsule.GetScaledCylinderLength(PhysicsData->ExternalScale3D) * .5f; const float Radius = Capsule.GetScaledRadius(PhysicsData->ExternalScale3D); FVector3f Top(0, 0, HalfLength), Bottom(0, 0, -HalfLength); // top and bottom circles UE::Geometry::GenerateCircleSegments(CircleSteps, Radius, Top, FVector3f::UnitX(), FVector3f::UnitY(), UE::Geometry::FTransformSRT3f(), [&](const FVector3f& A, const FVector3f& B) { StoreCurve(RenderData, LinesOut, PrimIndex, GobalCurveIndex++, ElemTransform* LocalToWorldTransform, (FVector)A, (FVector)B, Color); }); UE::Geometry::GenerateCircleSegments(CircleSteps, Radius, Bottom, FVector3f::UnitX(), FVector3f::UnitY(), UE::Geometry::FTransformSRT3f(), [&](const FVector3f& A, const FVector3f& B) { StoreCurve(RenderData, LinesOut, PrimIndex, GobalCurveIndex++, ElemTransform* LocalToWorldTransform, (FVector)A, (FVector)B, Color); }); // top dome UE::Geometry::GenerateArcSegments(CircleSteps, Radius, 0.0, PI, Top, FVector3f::UnitY(), FVector3f::UnitZ(), UE::Geometry::FTransformSRT3f(), [&](const FVector3f& A, const FVector3f& B) { StoreCurve(RenderData, LinesOut, PrimIndex, GobalCurveIndex++, ElemTransform* LocalToWorldTransform, (FVector)A, (FVector)B, Color); }); UE::Geometry::GenerateArcSegments(CircleSteps, Radius, 0.0, PI, Top, FVector3f::UnitX(), FVector3f::UnitZ(), UE::Geometry::FTransformSRT3f(), [&](const FVector3f& A, const FVector3f& B) { StoreCurve(RenderData, LinesOut, PrimIndex, GobalCurveIndex++, ElemTransform* LocalToWorldTransform, (FVector)A, (FVector)B, Color); }); // bottom dome UE::Geometry::GenerateArcSegments(CircleSteps, Radius, 0.0, -PI, Bottom, FVector3f::UnitY(), FVector3f::UnitZ(), UE::Geometry::FTransformSRT3f(), [&](const FVector3f& A, const FVector3f& B) { StoreCurve(RenderData, LinesOut, PrimIndex, GobalCurveIndex++, ElemTransform* LocalToWorldTransform, (FVector)A, (FVector)B, Color); }); UE::Geometry::GenerateArcSegments(CircleSteps, Radius, 0.0, -PI, Bottom, FVector3f::UnitX(), FVector3f::UnitZ(), UE::Geometry::FTransformSRT3f(), [&](const FVector3f& A, const FVector3f& B) { StoreCurve(RenderData, LinesOut, PrimIndex, GobalCurveIndex++, ElemTransform* LocalToWorldTransform, (FVector)A, (FVector)B, Color); }); // connecting lines for (int k = 0; k < 2; ++k) { FVector A, B; FVector DX = (k < 1) ? FVector(-Radius, 0, 0) : FVector(Radius, 0, 0); A = (FVector)Top + DX; B = (FVector)Bottom + DX; StoreCurve(RenderData, LinesOut, PrimIndex, GobalCurveIndex++, ElemTransform* LocalToWorldTransform, (FVector)A, (FVector)B, Color); FVector DY = (k < 1) ? FVector(0, -Radius, 0) : FVector(0, Radius, 0); A = (FVector)Top + DY; B = (FVector)Bottom + DY; StoreCurve(RenderData, LinesOut, PrimIndex, GobalCurveIndex++, ElemTransform* LocalToWorldTransform, (FVector)A, (FVector)B, Color); } }); RenderData.LineIndexEnd = GobalCurveIndex; if (bRegenerateCurveLists) { PrimitiveRenderData.Add(RenderData); } PrimIndex++; } // convexes are drawn as mesh edges for (int32 Index = 0; Index < AggGeom.ConvexElems.Num(); Index++) { FPrimitiveRenderData RenderData; RenderData.PrimIndex = PrimIndex; RenderData.LineIndexStart = GobalCurveIndex; FColor Color = SelectedPrimitiveIDs.Contains(PrimIndex) ? SelectedColor : NormalSegmentColor; RenderData.RenderColor = Color; RenderData.ShapeType = EAggCollisionShape::Convex; int32 CurveIndex = 0; DrawnPrimitiveEdges->AddLines(1, [this, bRegenerateCurveLists, &StoreCurve, &RenderData, &AggGeom, &Color, CircleSteps, PrimIndex, &GobalCurveIndex, Index](int32 UnusedIndex, TArray& LinesOut) { const FKConvexElem& Convex = AggGeom.ConvexElems[Index]; FTransform ElemTransform = Convex.GetTransform(); ElemTransform.ScaleTranslation(PhysicsData->ExternalScale3D); ElemTransform.MultiplyScale3D(PhysicsData->ExternalScale3D); if (bRegenerateCurveLists) { RenderData.Transform = ElemTransform; } int32 NumTriangles = Convex.IndexData.Num() / 3; for (int32 k = 0; k < NumTriangles; ++k) { FVector A = Convex.VertexData[Convex.IndexData[3 * k]]; FVector B = Convex.VertexData[Convex.IndexData[3 * k + 1]]; FVector C = Convex.VertexData[Convex.IndexData[3 * k + 2]]; StoreCurve(RenderData, LinesOut, PrimIndex, GobalCurveIndex++, ElemTransform* LocalToWorldTransform, (FVector)A, (FVector)B, Color); StoreCurve(RenderData, LinesOut, PrimIndex, GobalCurveIndex++, ElemTransform* LocalToWorldTransform, (FVector)B, (FVector)C, Color); StoreCurve(RenderData, LinesOut, PrimIndex, GobalCurveIndex++, ElemTransform* LocalToWorldTransform, (FVector)C, (FVector)A, Color); } }); RenderData.LineIndexEnd = GobalCurveIndex; if (bRegenerateCurveLists) { PrimitiveRenderData.Add(RenderData); } PrimIndex++; } // for Level Sets draw the grid cells where phi < 0 for (int32 Index = 0; Index < AggGeom.LevelSetElems.Num(); Index++) { FPrimitiveRenderData RenderData; RenderData.PrimIndex = PrimIndex; RenderData.LineIndexStart = GobalCurveIndex; FColor Color = SelectedPrimitiveIDs.Contains(PrimIndex) ? SelectedColor : NormalSegmentColor; RenderData.RenderColor = Color; RenderData.ShapeType = EAggCollisionShape::LevelSet; int32 CurveIndex = 0; DrawnPrimitiveEdges->AddLines(1, [this, bRegenerateCurveLists, &StoreCurve, &RenderData, &AggGeom, &Color, CircleSteps, PrimIndex, &GobalCurveIndex, Index](int32 UnusedIndex, TArray& LinesOut) { const FKLevelSetElem& LevelSet = AggGeom.LevelSetElems[Index]; FTransform ElemTransform = LevelSet.GetTransform(); ElemTransform.ScaleTranslation(PhysicsData->ExternalScale3D); ElemTransform.MultiplyScale3D(PhysicsData->ExternalScale3D); if (bRegenerateCurveLists) { RenderData.Transform = ElemTransform; } auto GenerateBoxSegmentsFromFBox = [&](const FBox& Box) { const FVector3d Center = Box.GetCenter(); const FVector3d HalfDimensions = 0.5 * (Box.Max - Box.Min); UE::Geometry::GenerateBoxSegments(HalfDimensions, Center, FVector3d::UnitX(), FVector3d::UnitY(), FVector3d::UnitZ(), UE::Geometry::FTransformSRT3d(), [&](const FVector3d& A, const FVector3d& B) { StoreCurve(RenderData, LinesOut, PrimIndex, GobalCurveIndex++, ElemTransform* LocalToWorldTransform, (FVector)A, (FVector)B, Color); }); }; const FBox TotalGridBox = LevelSet.UntransformedAABB(); GenerateBoxSegmentsFromFBox(TotalGridBox); TArray CellBoxes; const double Threshold = UE_KINDA_SMALL_NUMBER; // allow slightly greater than zero for visualization purposes LevelSet.GetInteriorGridCells(CellBoxes, Threshold); for (const FBox& CellBox : CellBoxes) { GenerateBoxSegmentsFromFBox(CellBox); } }); RenderData.LineIndexEnd = GobalCurveIndex; if (bRegenerateCurveLists) { PrimitiveRenderData.Add(RenderData); } PrimIndex++; } // TODO: Unclear whether we actually use these in the Engine, for UBodySetup? Does not appear to be supported by UxX import system, // and online documentation suggests they may only be supported for cloth? ensure(AggGeom.TaperedCapsuleElems.Num() == 0); } void UCollisionPrimitivesMechanic::UpdateDrawables() { UpdateGizmoLocation(); UpdateGizmoVisibility(); for (int32 PrimIndex = 0; PrimIndex < PrimitiveRenderData.Num(); ++PrimIndex) { PrimitiveRenderData[PrimIndex].RenderColor = SelectedPrimitiveIDs.Contains(PrimIndex) ? SelectedColor : NormalSegmentColor; for (int32 CurveId : PrimitiveToCurveLookup[PrimIndex]) { DrawnPrimitiveEdges->SetLineColor(CurveId, PrimitiveRenderData[PrimIndex].RenderColor); } } } void UCollisionPrimitivesMechanic::IntervalGizmoValueChanged(UIntervalGizmo* IntervalGizmo, const FVector& Direction, float Value) { if (SelectedPrimitiveIDs.Num() == 0 || !bGizmoBeingDragged) { return; } FKAggregateGeom& AggGeom = PhysicsData->AggGeom; for (int32 PrimitiveID : SelectedPrimitiveIDs) { FKShapeElem* Prim = AggGeom.GetElement(PrimitiveID); switch (Prim->GetShapeType()) { case EAggCollisionShape::Sphere: break; case EAggCollisionShape::Box: SetBoxShapeFromIntervals(StaticCast(Prim)); break; case EAggCollisionShape::Sphyl: SetCapsuleShapeFromIntervals(StaticCast(Prim)); break; case EAggCollisionShape::Convex: break; case EAggCollisionShape::LevelSet: break; default: ensure(false); // If we encounter a non-supported primitive type, // we should catch this and figure out how to handle it when needed. }; } RebuildDrawables(false); OnCollisionGeometryChanged.Broadcast(); } void UCollisionPrimitivesMechanic::GizmoTransformStarted(UTransformProxy* Proxy) { ParentTool->GetToolManager()->BeginUndoTransaction(CollisionPrimitivesMechanicLocals::CollisionPrimitiveMovementTransactionText); PrimitivePreTransform = MakeShared(PhysicsData->AggGeom); GizmoStartPosition = Proxy->GetTransform().GetTranslation(); GizmoStartRotation = Proxy->GetTransform().GetRotation(); GizmoStartScale = Proxy->GetTransform().GetScale3D(); bGizmoBeingDragged = true; } void UCollisionPrimitivesMechanic::GizmoTransformChanged(UTransformProxy* Proxy, FTransform Transform) { if (SelectedPrimitiveIDs.Num() == 0 || !bGizmoBeingDragged) { return; } if (Proxy->bSetPivotMode) { return; } FTransform LocalTransform = Transform * LocalToWorldTransform.Inverse(); UE::Geometry::FQuaterniond DeltaRotation = (UE::Geometry::FQuaterniond) ( LocalToWorldTransform.InverseTransformRotation( Transform.GetRotation() ) * LocalToWorldTransform.InverseTransformRotation( GizmoStartRotation) .Inverse() ); FVector DeltaScale = Transform.GetScale3D() / GizmoStartScale; UE::Geometry::FTransformSRT3d DeltaTransform; DeltaTransform.SetScale((FVector3d)DeltaScale); DeltaTransform.SetRotation((UE::Geometry::FQuaterniond)DeltaRotation); DeltaTransform.SetTranslation( (FVector3d)LocalToWorldTransform.InverseTransformPosition(Transform.GetLocation()) - (FVector3d)LocalToWorldTransform.InverseTransformPosition(GizmoStartPosition)); FKAggregateGeom& AggGeom = PhysicsData->AggGeom; FKAggregateGeom& PreTransformAggGeom = *PrimitivePreTransform; for (int32 PrimitiveID : SelectedPrimitiveIDs) { FKShapeElem* Prim = AggGeom.GetElement(PrimitiveID); FKShapeElem* PreTransformPrim = PreTransformAggGeom.GetElement(PrimitiveID); FTransform& ElemTM = PrimitiveRenderData[PrimitiveID].Transform; switch (Prim->GetShapeType()) { case EAggCollisionShape::Sphere: ElemTM = StaticCast(PreTransformPrim)->GetTransform(); ElemTM.Accumulate(DeltaTransform, ScalarRegister(1.0)); ElemTM.NormalizeRotation(); StaticCast(Prim)->Radius = StaticCast(PreTransformPrim)->Radius * ElemTM.GetScale3D().GetAbsMax(); ElemTM.SetScale3D(FVector::One()); StaticCast(Prim)->SetTransform(ElemTM); break; case EAggCollisionShape::Box: ElemTM = StaticCast(PreTransformPrim)->GetTransform(); ElemTM.Accumulate(DeltaTransform, ScalarRegister(1.0)); ElemTM.NormalizeRotation(); SetBoxShapeFromIntervals(StaticCast(Prim)); ElemTM.SetScale3D(FVector::One()); StaticCast(Prim)->SetTransform(ElemTM); break; case EAggCollisionShape::Sphyl: ElemTM = StaticCast(PreTransformPrim)->GetTransform(); ElemTM.Accumulate(DeltaTransform, ScalarRegister(1.0)); ElemTM.NormalizeRotation(); SetCapsuleShapeFromIntervals(StaticCast(Prim)); ElemTM.SetScale3D(FVector::One()); StaticCast(Prim)->SetTransform(ElemTM); break; case EAggCollisionShape::Convex: ElemTM = StaticCast(PreTransformPrim)->GetTransform(); ElemTM.Accumulate(DeltaTransform, ScalarRegister(1.0)); ElemTM.NormalizeRotation(); StaticCast(Prim)->SetTransform(ElemTM); break; case EAggCollisionShape::LevelSet: ElemTM = StaticCast(PreTransformPrim)->GetTransform(); ElemTM.Accumulate(DeltaTransform, ScalarRegister(1.0)); ElemTM.NormalizeRotation(); StaticCast(Prim)->SetTransform(ElemTM); break; default: ensure(false); // If we encounter a non-supported primitive type, // we should catch this and figure out how to handle it when needed. }; } RebuildDrawables(false); OnCollisionGeometryChanged.Broadcast(); } void UCollisionPrimitivesMechanic::SetBoxShapeFromIntervals(FKBoxElem* BoxElem) const { FVector ExternalScale = GetSafeAbsScale(LocalToWorldTransform.GetScale3D()); BoxElem->X = FMath::Abs(BoxXIntervalSource->GetParameter() * 2 / ExternalScale.X); BoxElem->Y = FMath::Abs(BoxYIntervalSource->GetParameter() * 2 / ExternalScale.Y); BoxElem->Z = FMath::Abs(BoxZIntervalSource->GetParameter() * 2 / ExternalScale.Z); } void UCollisionPrimitivesMechanic::SetCapsuleShapeFromIntervals(FKSphylElem* CapsuleElem) const { FVector ExternalScale = GetSafeAbsScale(LocalToWorldTransform.GetScale3D()); CapsuleElem->Radius = FMath::Abs(CapsuleRadiusIntervalSource->GetParameter() / FMath::Max(ExternalScale.X, ExternalScale.Y)); CapsuleElem->Length = FMath::Abs(CapsuleLengthIntervalSource->GetParameter() * 2 / ExternalScale.Z); } FVector3d UCollisionPrimitivesMechanic::GetSafeAbsScale(FVector3d Scale3D) const { Scale3D.X = FMath::Max(FMathd::ZeroTolerance, FMath::Abs(Scale3D.X)); Scale3D.Y = FMath::Max(FMathd::ZeroTolerance, FMath::Abs(Scale3D.Y)); Scale3D.Z = FMath::Max(FMathd::ZeroTolerance, FMath::Abs(Scale3D.Z)); return Scale3D; } void UCollisionPrimitivesMechanic::GizmoTransformEnded(UTransformProxy* Proxy) { FKAggregateGeom& AggGeom = PhysicsData->AggGeom; for (int32 PrimitiveID : SelectedPrimitiveIDs) { FKShapeElem* Prim = AggGeom.GetElement(PrimitiveID); FTransform& ElemTM = PrimitiveRenderData[PrimitiveID].Transform; switch (Prim->GetShapeType()) { case EAggCollisionShape::Sphere: StaticCast(Prim)->SetTransform(ElemTM); StaticCast(Prim)->Radius *= ElemTM.GetScale3D().GetAbsMax(); break; case EAggCollisionShape::Box: StaticCast(Prim)->SetTransform(ElemTM); SetBoxShapeFromIntervals(StaticCast(Prim)); break; case EAggCollisionShape::Sphyl: StaticCast(Prim)->SetTransform(ElemTM); SetCapsuleShapeFromIntervals(StaticCast(Prim)); break; case EAggCollisionShape::Convex: StaticCast(Prim)->SetTransform(ElemTM); break; case EAggCollisionShape::LevelSet: StaticCast(Prim)->SetTransform(ElemTM); break; default: ensure(false); // If we encounter a non-supported primitive type, // we should catch this and figure out how to handle it when needed. }; } ParentTool->GetToolManager()->EmitObjectChange(this, MakeUnique( PrimitivePreTransform, MakeShared(PhysicsData->AggGeom), CurrentChangeStamp), CollisionPrimitivesMechanicLocals::CollisionPrimitiveMovementTransactionText); // This just lets the tool know that the gizmo has finished moving and we've added it to the undo stack. // TODO: Add a different callback? "OnGizmoTransformChanged"? OnCollisionGeometryChanged.Broadcast(); // TODO: When we implement snapping // We may need to reset the gizmo if our snapping caused the final Primitive position to differ from the gizmo position // UpdateGizmoLocation(); ParentTool->GetToolManager()->EndUndoTransaction(); // was started in GizmoTransformStarted above RebuildDrawables(true); PrimitivePreTransform.Reset(); bGizmoBeingDragged = false; } bool UCollisionPrimitivesMechanic::HitTest(const FInputDeviceRay& ClickPos, FInputRayHit& ResultOut) { FGeometrySet3::FNearest Nearest; // See if we hit a curve for selection if (GeometrySet.FindNearestCurveToRay((FRay3d)ClickPos.WorldRay, Nearest, GeometrySetToleranceTest)) { ResultOut = FInputRayHit(Nearest.RayParam); return true; } return false; } FInputRayHit UCollisionPrimitivesMechanic::IsHitByClick(const FInputDeviceRay& ClickPos) { FInputRayHit Result; if (HitTest(ClickPos, Result)) { return Result; } // Return a hit so we always capture and can clear the selection return FInputRayHit(TNumericLimits::Max()); } void UCollisionPrimitivesMechanic::OnClicked(const FInputDeviceRay& ClickPos) { using namespace CollisionPrimitivesMechanicLocals; ParentTool->GetToolManager()->BeginUndoTransaction(CollisionPrimitiveSelectionTransactionText); const TSet PreClickSelection = SelectedPrimitiveIDs; FGeometrySet3::FNearest Nearest; if (GeometrySet.FindNearestCurveToRay((FRay3d)ClickPos.WorldRay, Nearest, GeometrySetToleranceTest)) { if (ShouldAddToSelectionFunc()) { if (ShouldRemoveFromSelectionFunc()) { Toggle(SelectedPrimitiveIDs, TSet{CurveToPrimitiveLookup[Nearest.ID]}); } else { SelectedPrimitiveIDs.Add(CurveToPrimitiveLookup[Nearest.ID]); } } else if (ShouldRemoveFromSelectionFunc()) { SelectedPrimitiveIDs.Remove(CurveToPrimitiveLookup[Nearest.ID]); } else { // Neither key pressed. SelectedPrimitiveIDs = TSet{ CurveToPrimitiveLookup[Nearest.ID] }; } } else { SelectedPrimitiveIDs.Empty(); } UpdateDrawables(); if (!IsEqual(PreClickSelection, SelectedPrimitiveIDs)) { checkSlow(CurrentActiveProxy); const FTransform Transform = CurrentActiveProxy->GetTransform(); ParentTool->GetToolManager()->EmitObjectChange(this, MakeUnique( PreClickSelection, false, Transform, Transform, CurrentChangeStamp), CollisionPrimitiveDeselectionTransactionText); ParentTool->GetToolManager()->EmitObjectChange(this, MakeUnique( SelectedPrimitiveIDs, true, Transform, Transform, CurrentChangeStamp), CollisionPrimitiveSelectionTransactionText); UpdateGizmoLocation(); OnSelectionChanged.Broadcast(); } ParentTool->GetToolManager()->EndUndoTransaction(); } void UCollisionPrimitivesMechanic::UpdateGizmoLocation() { if (!TranslateTransformGizmo || !SphereTransformGizmo || !BoxTransformGizmo || !CapsuleTransformGizmo || !FullTransformGizmo) { return; } FVector3d NewGizmoLocation(0, 0, 0); auto ForAllGizmos = [this](TFunction GizmoFunc) { GizmoFunc(*TranslateTransformGizmo); GizmoFunc(*SphereTransformGizmo); GizmoFunc(*BoxTransformGizmo); GizmoFunc(*CapsuleTransformGizmo); GizmoFunc(*FullTransformGizmo); }; if (SelectedPrimitiveIDs.Num() == 0) { ForAllGizmos([](UCombinedTransformGizmo& Gizmo) { Gizmo.ReinitializeGizmoTransform(FTransform()); }); } else { for (int32 PrimitiveID : SelectedPrimitiveIDs) { NewGizmoLocation += PrimitiveRenderData[PrimitiveID].Transform.GetLocation(); } NewGizmoLocation /= (double)SelectedPrimitiveIDs.Num(); } FQuat GizmoRotation = LocalToWorldTransform.GetRotation(); // We are handling one selected primitive specially because multiple selected primitives don't get rotation or interval controls if (SelectedPrimitiveIDs.Num() == 1) { const int32 SelectedPrimitiveID = *SelectedPrimitiveIDs.CreateConstIterator(); GizmoRotation = (PrimitiveRenderData[SelectedPrimitiveID].Transform * LocalToWorldTransform).GetRotation(); FKShapeElem* Shape = PhysicsData->AggGeom.GetElement(SelectedPrimitiveID); if (Shape->GetShapeType() == EAggCollisionShape::Box) { FKBoxElem* BoxElem = StaticCast(Shape); FVector ExternalScale = GetSafeAbsScale(LocalToWorldTransform.GetScale3D()); BoxXIntervalSource->SetParameter(BoxElem->X * .5 * ExternalScale.X); BoxYIntervalSource->SetParameter(BoxElem->Y * .5 * ExternalScale.Y); BoxZIntervalSource->SetParameter(BoxElem->Z * .5 * ExternalScale.Z); } if (Shape->GetShapeType() == EAggCollisionShape::Sphyl) { FKSphylElem* CapsuleElem = StaticCast(Shape); FVector ExternalScaleAbs = GetSafeAbsScale(LocalToWorldTransform.GetScale3D()); const float RadiusScale = FMath::Max(ExternalScaleAbs.X, ExternalScaleAbs.Y); CapsuleRadiusIntervalSource->SetParameter(CapsuleElem->Radius * RadiusScale); CapsuleLengthIntervalSource->SetParameter(CapsuleElem->Length * .5 * ExternalScaleAbs.Z); } } UpdateGizmoVisibility(); FTransform NewTransform(GizmoRotation, LocalToWorldTransform.TransformPosition((FVector)NewGizmoLocation)); ForAllGizmos([&NewTransform](UCombinedTransformGizmo& Gizmo) { Gizmo.ReinitializeGizmoTransform(NewTransform); }); // Clear the child scale ForAllGizmos([](UCombinedTransformGizmo& Gizmo) { FVector GizmoScale{ 1.0, 1.0, 1.0 }; Gizmo.SetNewChildScale(GizmoScale); }); } void UCollisionPrimitivesMechanic::UpdateGizmoVisibility() { if (!TranslateTransformGizmo || !SphereTransformGizmo || !BoxTransformGizmo || !CapsuleTransformGizmo || !FullTransformGizmo) { return; } auto ForAllGizmos = [this](TFunction GizmoFunc) { GizmoFunc(*TranslateTransformGizmo); GizmoFunc(*SphereTransformGizmo); GizmoFunc(*BoxTransformGizmo); GizmoFunc(*CapsuleTransformGizmo); GizmoFunc(*FullTransformGizmo); }; ForAllGizmos([](UCombinedTransformGizmo& Gizmo) { Gizmo.SetVisibility(false); }); // Note: Interval gizmos are not transform gizmos so we handle them separately from the above 'forall' helper BoxIntervalGizmo->SetVisibility(false); CapsuleIntervalGizmo->SetVisibility(false); CurrentActiveProxy = TranslateTransformProxy; if(!(bIsDraggingRectangle || SelectedPrimitiveIDs.IsEmpty() || (ShouldHideGizmo.IsBound() && ShouldHideGizmo.Execute()))) { // We are handling one selected primitive specially because each primitive type gets its own defined gizmo if (SelectedPrimitiveIDs.Num() == 1) { const int32 PrimitiveID = *SelectedPrimitiveIDs.CreateConstIterator(); switch (PrimitiveRenderData[PrimitiveID].ShapeType) { case EAggCollisionShape::Sphere: SphereTransformGizmo->SetVisibility(true); CurrentActiveProxy = SphereTransformProxy; break; case EAggCollisionShape::Box: BoxTransformGizmo->SetVisibility(true); BoxIntervalGizmo->SetVisibility(true); CurrentActiveProxy = BoxTransformProxy; break; case EAggCollisionShape::Sphyl: CapsuleTransformGizmo->SetVisibility(true); CapsuleIntervalGizmo->SetVisibility(true); CurrentActiveProxy = CapsuleTransformProxy; break; case EAggCollisionShape::Convex: FullTransformGizmo->SetVisibility(true); CurrentActiveProxy = FullTransformProxy; break; case EAggCollisionShape::LevelSet: FullTransformGizmo->SetVisibility(true); CurrentActiveProxy = FullTransformProxy; break; default: ensure(false); } } else { TranslateTransformGizmo->SetVisibility(true); } } } bool UCollisionPrimitivesMechanic::DeselectPrimitive(int32 PrimitiveID) { return SelectedPrimitiveIDs.Remove(PrimitiveID) != 0; // true if a primitive was removed } void UCollisionPrimitivesMechanic::SelectPrimitive(int32 PrimitiveID) { SelectedPrimitiveIDs.Add(PrimitiveID); } FInputRayHit UCollisionPrimitivesMechanic::BeginHoverSequenceHitTest(const FInputDeviceRay& PressPos) { FInputRayHit Result; HitTest(PressPos, Result); return Result; } void UCollisionPrimitivesMechanic::OnBeginHover(const FInputDeviceRay& DevicePos) { OnUpdateHover(DevicePos); } void UCollisionPrimitivesMechanic::ClearHover() { if (HoveredPrimitiveID >= 0) { for (int32 CurveId = PrimitiveRenderData[HoveredPrimitiveID].LineIndexStart; CurveId < PrimitiveRenderData[HoveredPrimitiveID].LineIndexEnd; ++CurveId) { DrawnPrimitiveEdges->SetLineColor(CurveId, PreHoverPrimitiveColor); } HoveredPrimitiveID = -1; } } bool UCollisionPrimitivesMechanic::OnUpdateHover(const FInputDeviceRay& DevicePos) { FGeometrySet3::FNearest Nearest; // see if we're hovering a curve for selection if (GeometrySet.FindNearestCurveToRay((FRay3d)DevicePos.WorldRay, Nearest, GeometrySetToleranceTest)) { // Only need to update the hover if we changed the curve if (Nearest.ID != HoveredPrimitiveID) { ClearHover(); HoveredPrimitiveID = CurveToPrimitiveLookup[Nearest.ID]; if (SelectedPrimitiveIDs.Contains(HoveredPrimitiveID)) { PreHoverPrimitiveColor = SelectedColor; } else { PreHoverPrimitiveColor = PrimitiveRenderData[HoveredPrimitiveID].RenderColor; } for (int32 CurveId = PrimitiveRenderData[HoveredPrimitiveID].LineIndexStart; CurveId < PrimitiveRenderData[HoveredPrimitiveID].LineIndexEnd; ++CurveId) { DrawnPrimitiveEdges->SetLineColor(CurveId, HoverColor); } } } else { // Not hovering anything, so done hovering return false; } return true; } void UCollisionPrimitivesMechanic::OnEndHover() { ClearHover(); } // Detects Ctrl key state void UCollisionPrimitivesMechanic::OnUpdateModifierState(int ModifierID, bool bIsOn) { switch (ModifierID) { case ShiftModifierID: bShiftToggle = bIsOn; break; case CtrlModifierID: bCtrlToggle = bIsOn; break; default: break; } } // These get bound to the delegates on the marquee mechanic. void UCollisionPrimitivesMechanic::OnDragRectangleStarted() { using namespace CollisionPrimitivesMechanicLocals; PreDragSelection = SelectedPrimitiveIDs; bIsDraggingRectangle = true; UpdateGizmoVisibility(); LongTransactions.Open(CollisionPrimitiveSelectionTransactionText, GetParentTool()->GetToolManager()); } void UCollisionPrimitivesMechanic::OnDragRectangleChanged(const FCameraRectangle& Rectangle) { using namespace CollisionPrimitivesMechanicLocals; TSet CurveSelection, PrimitiveSelection; GeometrySet.ParallelFindAllCurvesSatisfying([&](const UE::Geometry::FPolyline3d& Segment) { return Rectangle.IsProjectedSegmentIntersectingRectangle(Segment.Start(), Segment.End()); }, CurveSelection); for (int& Entry : CurveSelection) { PrimitiveSelection.Add(CurveToPrimitiveLookup[Entry]); } if (ShouldAddToSelectionFunc()) { SelectedPrimitiveIDs = PreDragSelection; if (ShouldRemoveFromSelectionFunc()) { Toggle(SelectedPrimitiveIDs, PrimitiveSelection); } else { SelectedPrimitiveIDs.Append(PrimitiveSelection); } } else if (ShouldRemoveFromSelectionFunc()) { SelectedPrimitiveIDs = PreDragSelection.Difference(PrimitiveSelection); } else { // Neither key pressed. SelectedPrimitiveIDs = PrimitiveSelection; } UpdateDrawables(); } void UCollisionPrimitivesMechanic::OnDragRectangleFinished(const FCameraRectangle& Rectangle, bool bCancelled) { using namespace CollisionPrimitivesMechanicLocals; bIsDraggingRectangle = false; if (!IsEqual(PreDragSelection, SelectedPrimitiveIDs)) { checkSlow(CurrentActiveProxy); const FTransform Transform = CurrentActiveProxy->GetTransform(); ParentTool->GetToolManager()->EmitObjectChange(this, MakeUnique( PreDragSelection, false, Transform, Transform, CurrentChangeStamp), CollisionPrimitiveDeselectionTransactionText); ParentTool->GetToolManager()->EmitObjectChange(this, MakeUnique( SelectedPrimitiveIDs, true, Transform, Transform, CurrentChangeStamp), CollisionPrimitiveSelectionTransactionText); OnSelectionChanged.Broadcast(); } // We hid the gizmo at rectangle start, so it needs updating now. UpdateGizmoLocation(); LongTransactions.Close(GetParentTool()->GetToolManager()); UpdateDrawables(); } void UCollisionPrimitivesMechanic::AddSphere() { // TOOD: Refactor these Add methods into a single templated solution FKAggregateGeom& AggGeom = PhysicsData->AggGeom; TSharedPtr PreAddGeometry = MakeShared(PhysicsData->AggGeom); AggGeom.SphereElems.Add(FKSphereElem(MeshBounds.MaxDim()/2.0)); TSet PreAddSelection = SelectedPrimitiveIDs; SelectedPrimitiveIDs.Empty(); SelectedPrimitiveIDs.Add(AggGeom.SphereElems.Num() - 1); ParentTool->GetToolManager()->BeginUndoTransaction(LOCTEXT("AddSphereUndo", "Add Sphere Collision Primitive")); ParentTool->GetToolManager()->EmitObjectChange(this, MakeUnique( PreAddGeometry, MakeShared(PhysicsData->AggGeom), CurrentChangeStamp), CollisionPrimitivesMechanicLocals::CollisionPrimitiveMovementTransactionText); OnCollisionGeometryChanged.Broadcast(); checkSlow(CurrentActiveProxy); const FTransform Transform = CurrentActiveProxy->GetTransform(); ParentTool->GetToolManager()->EmitObjectChange(this, MakeUnique( PreAddSelection, false, Transform, Transform, CurrentChangeStamp), CollisionPrimitivesMechanicLocals::CollisionPrimitiveDeselectionTransactionText); ParentTool->GetToolManager()->EmitObjectChange(this, MakeUnique( SelectedPrimitiveIDs, true, Transform, Transform, CurrentChangeStamp), CollisionPrimitivesMechanicLocals::CollisionPrimitiveSelectionTransactionText); OnSelectionChanged.Broadcast(); ParentTool->GetToolManager()->EndUndoTransaction(); RebuildDrawables(true); UpdateGizmoLocation(); UpdateGizmoVisibility(); } void UCollisionPrimitivesMechanic::AddBox() { // TOOD: Refactor these Add methods into a single templated solution FKAggregateGeom& AggGeom = PhysicsData->AggGeom; TSharedPtr PreAddGeometry = MakeShared(PhysicsData->AggGeom); AggGeom.BoxElems.Add(FKBoxElem(MeshBounds.Dimension(0), MeshBounds.Dimension(1), MeshBounds.Dimension(2))); TSet PreAddSelection = SelectedPrimitiveIDs; SelectedPrimitiveIDs.Empty(); SelectedPrimitiveIDs.Add(AggGeom.SphereElems.Num() + AggGeom.BoxElems.Num() - 1); ParentTool->GetToolManager()->BeginUndoTransaction(LOCTEXT("AddBoxUndo", "Add Box Collision Primitive")); ParentTool->GetToolManager()->EmitObjectChange(this, MakeUnique( PreAddGeometry, MakeShared(PhysicsData->AggGeom), CurrentChangeStamp), CollisionPrimitivesMechanicLocals::CollisionPrimitiveMovementTransactionText); OnCollisionGeometryChanged.Broadcast(); checkSlow(CurrentActiveProxy); const FTransform Transform = CurrentActiveProxy->GetTransform(); ParentTool->GetToolManager()->EmitObjectChange(this, MakeUnique( PreAddSelection, false, Transform, Transform, CurrentChangeStamp), CollisionPrimitivesMechanicLocals::CollisionPrimitiveDeselectionTransactionText); ParentTool->GetToolManager()->EmitObjectChange(this, MakeUnique( SelectedPrimitiveIDs, true, Transform, Transform, CurrentChangeStamp), CollisionPrimitivesMechanicLocals::CollisionPrimitiveSelectionTransactionText); OnSelectionChanged.Broadcast(); ParentTool->GetToolManager()->EndUndoTransaction(); RebuildDrawables(true); UpdateGizmoLocation(); UpdateGizmoVisibility(); } void UCollisionPrimitivesMechanic::AddCapsule() { // TOOD: Refactor these Add methods into a single templated solution FKAggregateGeom& AggGeom = PhysicsData->AggGeom; TSharedPtr PreAddGeometry = MakeShared(PhysicsData->AggGeom); AggGeom.SphylElems.Add(FKSphylElem(FMath::Max(MeshBounds.Dimension(0), MeshBounds.Dimension(1)) / 2.0, MeshBounds.Dimension(2))); TSet PreAddSelection = SelectedPrimitiveIDs; SelectedPrimitiveIDs.Empty(); SelectedPrimitiveIDs.Add(AggGeom.SphereElems.Num() + AggGeom.BoxElems.Num() + AggGeom.SphylElems.Num() - 1); ParentTool->GetToolManager()->BeginUndoTransaction(LOCTEXT("AddCapsuleUndo", "Add Capsule Collision Primitive")); ParentTool->GetToolManager()->EmitObjectChange(this, MakeUnique( PreAddGeometry, MakeShared(PhysicsData->AggGeom), CurrentChangeStamp), CollisionPrimitivesMechanicLocals::CollisionPrimitiveMovementTransactionText); OnCollisionGeometryChanged.Broadcast(); checkSlow(CurrentActiveProxy); const FTransform Transform = CurrentActiveProxy->GetTransform(); ParentTool->GetToolManager()->EmitObjectChange(this, MakeUnique( PreAddSelection, false, Transform, Transform, CurrentChangeStamp), CollisionPrimitivesMechanicLocals::CollisionPrimitiveDeselectionTransactionText); ParentTool->GetToolManager()->EmitObjectChange(this, MakeUnique( SelectedPrimitiveIDs, true, Transform, Transform, CurrentChangeStamp), CollisionPrimitivesMechanicLocals::CollisionPrimitiveSelectionTransactionText); OnSelectionChanged.Broadcast(); ParentTool->GetToolManager()->EndUndoTransaction(); RebuildDrawables(true); UpdateGizmoLocation(); UpdateGizmoVisibility(); } void UCollisionPrimitivesMechanic::DuplicateSelectedPrimitive() { using namespace CollisionPrimitivesMechanicLocals; TArray NewSphereElems; TArray NewBoxElems; TArray NewSphylElems; TArray NewConvexElems; TArray NewLevelSetElems; FKAggregateGeom& AggGeom = PhysicsData->AggGeom; TSharedPtr PreAddGeometry = MakeShared(PhysicsData->AggGeom); if (SelectedPrimitiveIDs.IsEmpty()) { return; } for (int32 PrimIndex : SelectedPrimitiveIDs) { const FKShapeElem* Prim = AggGeom.GetElement(PrimIndex); switch (Prim->GetShapeType()) { case EAggCollisionShape::Sphere: NewSphereElems.Add(FKSphereElem(*StaticCast(Prim))); break; case EAggCollisionShape::Box: NewBoxElems.Add(FKBoxElem(*StaticCast(Prim))); break; case EAggCollisionShape::Sphyl: NewSphylElems.Add(FKSphylElem(*StaticCast(Prim))); break; case EAggCollisionShape::Convex: NewConvexElems.Add(FKConvexElem(*StaticCast(Prim))); break; case EAggCollisionShape::LevelSet: NewLevelSetElems.Add(FKLevelSetElem(*StaticCast(Prim))); break; default: ensure(false); // If we encounter a non-supported primitive type, // we should catch this and figure out how to handle it when needed. }; } TSet NewSelectedIDs; TSet PreDuplicateSelection = SelectedPrimitiveIDs; int32 TotalIDs = 0; for (int32 NewID = AggGeom.SphereElems.Num(); NewID < AggGeom.SphereElems.Num() + NewSphereElems.Num(); ++NewID) { NewSelectedIDs.Add(NewID); } AggGeom.SphereElems.Append(NewSphereElems); TotalIDs += AggGeom.SphereElems.Num(); for (int32 NewID = AggGeom.BoxElems.Num()+TotalIDs; NewID < AggGeom.BoxElems.Num() + NewBoxElems.Num() + TotalIDs; ++NewID) { NewSelectedIDs.Add(NewID); } AggGeom.BoxElems.Append(NewBoxElems); TotalIDs += AggGeom.BoxElems.Num(); for (int32 NewID = AggGeom.SphylElems.Num() + TotalIDs; NewID < AggGeom.SphylElems.Num() + NewSphylElems.Num() + TotalIDs; ++NewID) { NewSelectedIDs.Add(NewID); } AggGeom.SphylElems.Append(NewSphylElems); TotalIDs += AggGeom.SphylElems.Num(); for (int32 NewID = AggGeom.ConvexElems.Num() + TotalIDs; NewID < AggGeom.ConvexElems.Num() + NewConvexElems.Num() + TotalIDs; ++NewID) { NewSelectedIDs.Add(NewID); } AggGeom.ConvexElems.Append(NewConvexElems); TotalIDs += AggGeom.ConvexElems.Num(); for (int32 NewID = AggGeom.LevelSetElems.Num() + TotalIDs; NewID < AggGeom.LevelSetElems.Num() + NewLevelSetElems.Num() + TotalIDs; ++NewID) { NewSelectedIDs.Add(NewID); } AggGeom.LevelSetElems.Append(NewLevelSetElems); TotalIDs += AggGeom.LevelSetElems.Num(); ParentTool->GetToolManager()->BeginUndoTransaction(LOCTEXT("DuplicateSelectionUndo", "Duplicate Selected Collision Primitives")); ParentTool->GetToolManager()->EmitObjectChange(this, MakeUnique( PreAddGeometry, MakeShared(PhysicsData->AggGeom), CurrentChangeStamp), CollisionPrimitiveMovementTransactionText); OnCollisionGeometryChanged.Broadcast(); SelectedPrimitiveIDs = NewSelectedIDs; checkSlow(CurrentActiveProxy); const FTransform Transform = CurrentActiveProxy->GetTransform(); ParentTool->GetToolManager()->EmitObjectChange(this, MakeUnique( PreDuplicateSelection, false, Transform, Transform, CurrentChangeStamp), CollisionPrimitiveDeselectionTransactionText); ParentTool->GetToolManager()->EmitObjectChange(this, MakeUnique( SelectedPrimitiveIDs, true, Transform, Transform, CurrentChangeStamp), CollisionPrimitiveSelectionTransactionText); OnSelectionChanged.Broadcast(); ParentTool->GetToolManager()->EndUndoTransaction(); RebuildDrawables(true); UpdateGizmoLocation(); UpdateGizmoVisibility(); } void UCollisionPrimitivesMechanic::DeleteSelectedPrimitive() { int32 PrimsDeleted = 0; FKAggregateGeom& AggGeom = PhysicsData->AggGeom; TSharedPtr PreAddGeometry = MakeShared(PhysicsData->AggGeom); TArray SortedSelectedIDs = SelectedPrimitiveIDs.Array(); SortedSelectedIDs.Sort(); for (int32 PrimIndex : SortedSelectedIDs) { int Index = PrimIndex - PrimsDeleted; ensure(Index < AggGeom.GetElementCount()); PrimsDeleted++; if (Index < AggGeom.SphereElems.Num()) { AggGeom.SphereElems.RemoveAt(Index); continue; } Index -= AggGeom.SphereElems.Num(); if (Index < AggGeom.BoxElems.Num()) { AggGeom.BoxElems.RemoveAt(Index); continue; } Index -= AggGeom.BoxElems.Num(); if (Index < AggGeom.SphylElems.Num()) { AggGeom.SphylElems.RemoveAt(Index); continue; } Index -= AggGeom.SphylElems.Num(); if (Index < AggGeom.ConvexElems.Num()) { AggGeom.ConvexElems.RemoveAt(Index); continue; } Index -= AggGeom.ConvexElems.Num(); if (Index < AggGeom.TaperedCapsuleElems.Num()) { AggGeom.TaperedCapsuleElems.RemoveAt(Index); continue; } Index -= AggGeom.TaperedCapsuleElems.Num(); if (Index < AggGeom.LevelSetElems.Num()) { AggGeom.LevelSetElems.RemoveAt(Index); continue; } Index -= AggGeom.LevelSetElems.Num(); if (Index < AggGeom.SkinnedLevelSetElems.Num()) { AggGeom.SkinnedLevelSetElems.RemoveAt(Index); continue; } } ParentTool->GetToolManager()->BeginUndoTransaction(LOCTEXT("DeleteSelectionUndo", "Delete Selected Collision Primitives")); TSet PreDeleteSelection = SelectedPrimitiveIDs; SelectedPrimitiveIDs.Empty(); checkSlow(CurrentActiveProxy); const FTransform Transform = CurrentActiveProxy->GetTransform(); ParentTool->GetToolManager()->EmitObjectChange(this, MakeUnique( PreDeleteSelection, false, Transform, Transform, CurrentChangeStamp), CollisionPrimitivesMechanicLocals::CollisionPrimitiveDeselectionTransactionText); ParentTool->GetToolManager()->EmitObjectChange(this, MakeUnique( SelectedPrimitiveIDs, true, Transform, Transform, CurrentChangeStamp), CollisionPrimitivesMechanicLocals::CollisionPrimitiveSelectionTransactionText); OnSelectionChanged.Broadcast(); ParentTool->GetToolManager()->EmitObjectChange(this, MakeUnique( PreAddGeometry, MakeShared(PhysicsData->AggGeom), CurrentChangeStamp), CollisionPrimitivesMechanicLocals::CollisionPrimitiveMovementTransactionText); OnCollisionGeometryChanged.Broadcast(); ParentTool->GetToolManager()->EndUndoTransaction(); RebuildDrawables(true); UpdateGizmoLocation(); UpdateGizmoVisibility(); } void UCollisionPrimitivesMechanic::DeleteAllPrimitives() { FKAggregateGeom& AggGeom = PhysicsData->AggGeom; TSharedPtr PreAddGeometry = MakeShared(PhysicsData->AggGeom); AggGeom.EmptyElements(); ParentTool->GetToolManager()->BeginUndoTransaction(LOCTEXT("DeleteAllUndo", "Delete All Collision Primitives")); TSet PreDeleteSelection = SelectedPrimitiveIDs; SelectedPrimitiveIDs.Empty(); checkSlow(CurrentActiveProxy); const FTransform Transform = CurrentActiveProxy->GetTransform(); ParentTool->GetToolManager()->EmitObjectChange(this, MakeUnique( PreDeleteSelection, false, Transform, Transform, CurrentChangeStamp), CollisionPrimitivesMechanicLocals::CollisionPrimitiveDeselectionTransactionText); ParentTool->GetToolManager()->EmitObjectChange(this, MakeUnique( SelectedPrimitiveIDs, true, Transform, Transform, CurrentChangeStamp), CollisionPrimitivesMechanicLocals::CollisionPrimitiveSelectionTransactionText); OnSelectionChanged.Broadcast(); ParentTool->GetToolManager()->EmitObjectChange(this, MakeUnique( PreAddGeometry, MakeShared(PhysicsData->AggGeom), CurrentChangeStamp), CollisionPrimitivesMechanicLocals::CollisionPrimitiveMovementTransactionText); OnCollisionGeometryChanged.Broadcast(); ParentTool->GetToolManager()->EndUndoTransaction(); RebuildDrawables(true); UpdateGizmoLocation(); UpdateGizmoVisibility(); } void UCollisionPrimitivesMechanic::UpdateCollisionGeometry(const FKAggregateGeom& NewGeometryIn) { PhysicsData->AggGeom = NewGeometryIn; RebuildDrawables(true); UpdateGizmoLocation(); UpdateGizmoVisibility(); } // ==================== Undo/redo object functions ==================== UCollisionPrimitivesMechanic::FCollisionPrimitivesMechanicSelectionChange::FCollisionPrimitivesMechanicSelectionChange(int32 PrimitiveIDIn, bool bAddedIn, const FTransform& PreviousTransformIn, const FTransform& NewTransformIn, int32 ChangeStampIn) : PrimitiveIDs{ PrimitiveIDIn } , bAdded(bAddedIn) , PreviousTransform(PreviousTransformIn) , NewTransform(NewTransformIn) , ChangeStamp(ChangeStampIn) {} UCollisionPrimitivesMechanic::FCollisionPrimitivesMechanicSelectionChange::FCollisionPrimitivesMechanicSelectionChange(const TSet& PrimitiveIDsIn, bool bAddedIn, const FTransform& PreviousTransformIn, const FTransform& NewTransformIn, int32 ChangeStampIn) : PrimitiveIDs(PrimitiveIDsIn) , bAdded(bAddedIn) , PreviousTransform(PreviousTransformIn) , NewTransform(NewTransformIn) , ChangeStamp(ChangeStampIn) {} void UCollisionPrimitivesMechanic::FCollisionPrimitivesMechanicSelectionChange::Apply(UObject* Object) { UCollisionPrimitivesMechanic* Mechanic = Cast(Object); for (int32 PrimitiveID : PrimitiveIDs) { if (bAdded) { Mechanic->SelectPrimitive(PrimitiveID); } else { Mechanic->DeselectPrimitive(PrimitiveID); } } Mechanic->UpdateDrawables(); Mechanic->OnSelectionChanged.Broadcast(); } void UCollisionPrimitivesMechanic::FCollisionPrimitivesMechanicSelectionChange::Revert(UObject* Object) { UCollisionPrimitivesMechanic* Mechanic = Cast(Object); for (int32 PrimitiveID : PrimitiveIDs) { if (bAdded) { Mechanic->DeselectPrimitive(PrimitiveID); } else { Mechanic->SelectPrimitive(PrimitiveID); } } Mechanic->UpdateDrawables(); Mechanic->OnSelectionChanged.Broadcast(); } FString UCollisionPrimitivesMechanic::FCollisionPrimitivesMechanicSelectionChange::ToString() const { return TEXT("FCollisionPrimitivesMechanicSelectionChange"); } UCollisionPrimitivesMechanic::FCollisionPrimitivesMechanicGeometryChange::FCollisionPrimitivesMechanicGeometryChange( TSharedPtr GeometryPreviousIn, TSharedPtr GeometryNewIn, int32 ChangeStampIn) : GeometryPrevious{ GeometryPreviousIn } , GeometryNew{ GeometryNewIn } , ChangeStamp(ChangeStampIn) { } void UCollisionPrimitivesMechanic::FCollisionPrimitivesMechanicGeometryChange::Apply(UObject* Object) { UCollisionPrimitivesMechanic* Mechanic = Cast(Object); Mechanic->UpdateCollisionGeometry(*GeometryNew); Mechanic->OnCollisionGeometryChanged.Broadcast(); } void UCollisionPrimitivesMechanic::FCollisionPrimitivesMechanicGeometryChange::Revert(UObject* Object) { UCollisionPrimitivesMechanic* Mechanic = Cast(Object); Mechanic->UpdateCollisionGeometry(*GeometryPrevious); Mechanic->OnCollisionGeometryChanged.Broadcast(); } FString UCollisionPrimitivesMechanic::FCollisionPrimitivesMechanicGeometryChange::ToString() const { return TEXT("FCollisionPrimitivesMechanicGeometryChange"); } #undef LOCTEXT_NAMESPACE