Files
UnrealEngineUWP/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/EditPivotTool.cpp

523 lines
16 KiB
C++
Raw Normal View History

// Copyright Epic Games, Inc. All Rights Reserved.
#include "EditPivotTool.h"
#include "InteractiveToolManager.h"
#include "InteractiveGizmoManager.h"
#include "ToolBuilderUtil.h"
#include "ToolSetupUtil.h"
#include "DynamicMesh/DynamicMesh3.h"
#include "Mechanics/DragAlignmentMechanic.h"
#include "MeshAdapterTransforms.h"
#include "MeshDescriptionAdapter.h"
#include "DynamicMesh/MeshTransforms.h"
#include "BaseBehaviors/ClickDragBehavior.h"
#include "ToolSceneQueriesUtil.h"
#include "Physics/ComponentCollisionUtil.h"
#include "BaseGizmos/GizmoComponents.h"
Gizmos: refactor Modeling Mode gizmo creation out of InteractiveGizmoManager. Editor will use other "default" transform gizmo implementations, and so the UTransformGizmo creation helper functions do not belong in GizmoManager. Instead a UTransformGizmoContextObject now provides this functionality. ModelingToolsEditorMode (and any other modes/systems that want to use these gizmo convenience functions) creates an instance of UTransformGizmoContextObject and registers it with the ContextObjectStore. Calling code can spawn a new UTransformGizmo by looking this object up in the ContextStore and calling it's helper functions. Static versions of the helper functions in the UE::TransformGizmoUtil:: namespace provide a single-line interface that replaces the previous GizmoManager call sites in the MeshModelingTools library. IntervalGizmo is now just registered and unregistered as needed by the MeshSpaceDeformerTool, as this is the only place it is currently used. Previous implementation in InteractiveGizmoManager is left intact as there are a few uses outside of MeshModelingTools that need to be cleaned up before it can be deleted. UTransformGizmo now requires it's Builder to tell it which sub-gizmo identifier strings to pass to the GizmoManager to create axis/plane/rotation sub-gizmos (and the code that registers the Builder must then provide these strings). This cleans up previous explicit references to UInteractiveGizmoManager static strings from UTransformGizmo. #rb Christina.TempelaarL, david.hill #rnx #jira none [CL 16409673 by Ryan Schmidt in ue5-main branch]
2021-05-20 16:39:39 -04:00
#include "BaseGizmos/TransformGizmoUtil.h"
#include "Components/PrimitiveComponent.h"
#include "Components/InstancedStaticMeshComponent.h"
#include "Engine/World.h"
#include "TargetInterfaces/MeshDescriptionCommitter.h"
#include "TargetInterfaces/MeshDescriptionProvider.h"
#include "TargetInterfaces/PrimitiveComponentBackedTarget.h"
#include "ToolTargetManager.h"
#include "ModelingToolTargetUtil.h"
#include "ExplicitUseGeometryMathTypes.h" // using UE::Geometry::(math types)
using namespace UE::Geometry;
#define LOCTEXT_NAMESPACE "UEditPivotTool"
/*
* ToolBuilder
*/
const FToolTargetTypeRequirements& UEditPivotToolBuilder::GetTargetRequirements() const
{
static FToolTargetTypeRequirements TypeRequirements({
UMeshDescriptionCommitter::StaticClass(),
UMeshDescriptionProvider::StaticClass(),
UPrimitiveComponentBackedTarget::StaticClass()
});
return TypeRequirements;
}
bool UEditPivotToolBuilder::CanBuildTool(const FToolBuilderState& SceneState) const
{
return SceneState.TargetManager->CountSelectedAndTargetable(SceneState, GetTargetRequirements()) >= 1;
}
UInteractiveTool* UEditPivotToolBuilder::BuildTool(const FToolBuilderState& SceneState) const
{
UEditPivotTool* NewTool = NewObject<UEditPivotTool>(SceneState.ToolManager);
TArray<TObjectPtr<UToolTarget>> Targets = SceneState.TargetManager->BuildAllSelectedTargetable(SceneState, GetTargetRequirements());
NewTool->SetTargets(MoveTemp(Targets));
NewTool->SetWorld(SceneState.World, SceneState.GizmoManager);
return NewTool;
}
void UEditPivotToolActionPropertySet::PostAction(EEditPivotToolActions Action)
{
if (ParentTool.IsValid())
{
ParentTool->RequestAction(Action);
}
}
/*
* Tool
*/
UEditPivotTool::UEditPivotTool()
{
}
void UEditPivotTool::SetWorld(UWorld* World, UInteractiveGizmoManager* GizmoManagerIn)
{
this->TargetWorld = World;
this->GizmoManager = GizmoManagerIn;
}
void UEditPivotTool::Setup()
{
UInteractiveTool::Setup();
UClickDragInputBehavior* ClickDragBehavior = NewObject<UClickDragInputBehavior>(this);
ClickDragBehavior->Initialize(this);
AddInputBehavior(ClickDragBehavior);
TransformProps = NewObject<UEditPivotToolProperties>();
AddToolPropertySource(TransformProps);
EditPivotActions = NewObject<UEditPivotToolActionPropertySet>(this);
EditPivotActions->Initialize(this);
AddToolPropertySource(EditPivotActions);
ResetActiveGizmos();
SetActiveGizmos_Single(false);
UpdateSetPivotModes(true);
DragAlignmentMechanic = NewObject<UDragAlignmentMechanic>(this);
DragAlignmentMechanic->Setup(this);
DragAlignmentMechanic->AddToGizmo(ActiveGizmos[0].TransformGizmo);
Precompute();
FText AllTheWarnings = LOCTEXT("EditPivotWarning", "WARNING: This Tool will Modify the selected StaticMesh Assets! If you do not wish to modify the original Assets, please make copies in the Content Browser first!");
// detect and warn about any meshes in selection that correspond to same source data
bool bSharesSources = GetMapToSharedSourceData(MapToFirstOccurrences);
if (bSharesSources)
{
AllTheWarnings = FText::Format(FTextFormat::FromString("{0}\n\n{1}"), AllTheWarnings, LOCTEXT("EditPivotSharedAssetsWarning", "WARNING: Multiple selected meshes share the same source Asset! Each Asset can only have one baked pivot, some results will be incorrect."));
}
bool bHasISMCs = false;
for (int32 k = 0; k < Targets.Num(); ++k)
{
if (Cast<UInstancedStaticMeshComponent>(UE::ToolTarget::GetTargetComponent(Targets[k])) != nullptr)
{
bHasISMCs = true;
}
}
if (bHasISMCs)
{
AllTheWarnings = FText::Format(FTextFormat::FromString("{0}\n\n{1}"), AllTheWarnings, LOCTEXT("EditPivotISMCWarning", "WARNING: Some selected objects are Instanced Components. Pivot of Instances will be modified, instead of Asset."));
}
GetToolManager()->DisplayMessage(AllTheWarnings, EToolMessageLevel::UserWarning);
SetToolDisplayName(LOCTEXT("ToolName", "Edit Pivot"));
GetToolManager()->DisplayMessage(
LOCTEXT("OnStartTool", "This tool edits the Pivot (Origin) of the input assets. Hold Ctrl while using the gizmo to align to scene. Enable Snap Dragging and click+drag to place gizmo directly into clicked position."),
EToolMessageLevel::UserNotification);
}
void UEditPivotTool::Shutdown(EToolShutdownType ShutdownType)
{
DragAlignmentMechanic->Shutdown();
FFrame3d CurPivotFrame(ActiveGizmos[0].TransformProxy->GetTransform());
GizmoManager->DestroyAllGizmosByOwner(this);
if (ShutdownType == EToolShutdownType::Accept)
{
UpdateAssets(CurPivotFrame);
}
}
void VertexIteration(const FMeshDescription* Mesh, TFunctionRef<void(int32, const FVector&)> ApplyFunc)
{
TArrayView<const FVector3f> VertexPositions = Mesh->GetVertexPositions().GetRawArray();
for (const FVertexID VertexID : Mesh->Vertices().GetElementIDs())
{
const FVector Position = VertexPositions[VertexID];
ApplyFunc(VertexID.GetValue(), Position);
}
}
void UEditPivotTool::Precompute()
{
ObjectBounds = FAxisAlignedBox3d::Empty();
WorldBounds = FAxisAlignedBox3d::Empty();
int NumComponents = Targets.Num();
if (NumComponents == 1)
{
Transform = UE::ToolTarget::GetLocalToWorldTransform(Targets[0]);
const FMeshDescription* Mesh = UE::ToolTarget::GetMeshDescription(Targets[0]);
VertexIteration(Mesh, [&](int32 VertexID, const FVector& Position) {
ObjectBounds.Contain((FVector3d)Position);
WorldBounds.Contain(Transform.TransformPosition((FVector3d)Position));
});
}
else
{
Transform = UE::Geometry::FTransform3d::Identity();
for (int k = 0; k < NumComponents; ++k)
{
UE::Geometry::FTransform3d CurTransform = UE::ToolTarget::GetLocalToWorldTransform(Targets[k]);
const FMeshDescription* Mesh = UE::ToolTarget::GetMeshDescription(Targets[k]);
VertexIteration(Mesh, [&](int32 VertexID, const FVector& Position) {
ObjectBounds.Contain(CurTransform.TransformPosition((FVector3d)Position));
WorldBounds.Contain(CurTransform.TransformPosition((FVector3d)Position));
});
}
}
}
void UEditPivotTool::RequestAction(EEditPivotToolActions ActionType)
{
if (PendingAction == EEditPivotToolActions::NoAction)
{
PendingAction = ActionType;
}
}
void UEditPivotTool::OnTick(float DeltaTime)
{
if (PendingAction != EEditPivotToolActions::NoAction)
{
ApplyAction(PendingAction);
PendingAction = EEditPivotToolActions::NoAction;
}
}
void UEditPivotTool::Render(IToolsContextRenderAPI* RenderAPI)
{
DragAlignmentMechanic->Render(RenderAPI);
}
void UEditPivotTool::OnPropertyModified(UObject* PropertySet, FProperty* Property)
{
}
void UEditPivotTool::UpdateSetPivotModes(bool bEnableSetPivot)
{
for (FEditPivotTarget& Target : ActiveGizmos)
{
Target.TransformProxy->bSetPivotMode = bEnableSetPivot;
}
}
void UEditPivotTool::RegisterActions(FInteractiveToolActionSet& ActionSet)
{
}
void UEditPivotTool::ApplyAction(EEditPivotToolActions ActionType)
{
switch (ActionType)
{
case EEditPivotToolActions::Center:
case EEditPivotToolActions::Bottom:
case EEditPivotToolActions::Top:
case EEditPivotToolActions::Left:
case EEditPivotToolActions::Right:
case EEditPivotToolActions::Front:
case EEditPivotToolActions::Back:
SetPivotToBoxPoint(ActionType);
break;
case EEditPivotToolActions::WorldOrigin:
SetPivotToWorldOrigin();
break;
}
}
void UEditPivotTool::SetPivotToBoxPoint(EEditPivotToolActions ActionPoint)
{
FAxisAlignedBox3d UseBox = (EditPivotActions->bUseWorldBox) ? WorldBounds : ObjectBounds;
FVector3d Point = UseBox.Center();
if (ActionPoint == EEditPivotToolActions::Bottom || ActionPoint == EEditPivotToolActions::Top)
{
Point.Z = (ActionPoint == EEditPivotToolActions::Bottom) ? UseBox.Min.Z : UseBox.Max.Z;
}
else if (ActionPoint == EEditPivotToolActions::Left || ActionPoint == EEditPivotToolActions::Right)
{
Point.Y = (ActionPoint == EEditPivotToolActions::Left) ? UseBox.Min.Y : UseBox.Max.Y;
}
else if (ActionPoint == EEditPivotToolActions::Back || ActionPoint == EEditPivotToolActions::Front)
{
Point.X = (ActionPoint == EEditPivotToolActions::Front) ? UseBox.Min.X : UseBox.Max.X;
}
FTransform NewTransform;
if (EditPivotActions->bUseWorldBox == false)
{
FFrame3d LocalFrame(Point);
LocalFrame.Transform(Transform);
NewTransform = LocalFrame.ToFTransform();
}
else
{
NewTransform = FTransform((FVector)Point);
}
ActiveGizmos[0].TransformGizmo->SetNewGizmoTransform(NewTransform);
}
void UEditPivotTool::SetPivotToWorldOrigin()
{
ActiveGizmos[0].TransformGizmo->SetNewGizmoTransform(FTransform());
}
void UEditPivotTool::SetActiveGizmos_Single(bool bLocalRotations)
{
check(ActiveGizmos.Num() == 0);
FEditPivotTarget Transformable;
Transformable.TransformProxy = NewObject<UTransformProxy>(this);
Transformable.TransformProxy->bRotatePerObject = bLocalRotations;
for (int32 ComponentIdx = 0; ComponentIdx < Targets.Num(); ComponentIdx++)
{
Transformable.TransformProxy->AddComponent(UE::ToolTarget::GetTargetComponent(Targets[ComponentIdx]));
}
Gizmos: refactor Modeling Mode gizmo creation out of InteractiveGizmoManager. Editor will use other "default" transform gizmo implementations, and so the UTransformGizmo creation helper functions do not belong in GizmoManager. Instead a UTransformGizmoContextObject now provides this functionality. ModelingToolsEditorMode (and any other modes/systems that want to use these gizmo convenience functions) creates an instance of UTransformGizmoContextObject and registers it with the ContextObjectStore. Calling code can spawn a new UTransformGizmo by looking this object up in the ContextStore and calling it's helper functions. Static versions of the helper functions in the UE::TransformGizmoUtil:: namespace provide a single-line interface that replaces the previous GizmoManager call sites in the MeshModelingTools library. IntervalGizmo is now just registered and unregistered as needed by the MeshSpaceDeformerTool, as this is the only place it is currently used. Previous implementation in InteractiveGizmoManager is left intact as there are a few uses outside of MeshModelingTools that need to be cleaned up before it can be deleted. UTransformGizmo now requires it's Builder to tell it which sub-gizmo identifier strings to pass to the GizmoManager to create axis/plane/rotation sub-gizmos (and the code that registers the Builder must then provide these strings). This cleans up previous explicit references to UInteractiveGizmoManager static strings from UTransformGizmo. #rb Christina.TempelaarL, david.hill #rnx #jira none [CL 16409673 by Ryan Schmidt in ue5-main branch]
2021-05-20 16:39:39 -04:00
Transformable.TransformGizmo = UE::TransformGizmoUtil::CreateCustomTransformGizmo(GizmoManager,
ETransformGizmoSubElements::StandardTranslateRotate, this
);
Transformable.TransformGizmo->SetActiveTarget(Transformable.TransformProxy, GetToolManager());
Transformable.TransformGizmo->bUseContextCoordinateSystem = false;
Transformable.TransformGizmo->CurrentCoordinateSystem = EToolContextCoordinateSystem::Local;
ActiveGizmos.Add(Transformable);
}
void UEditPivotTool::ResetActiveGizmos()
{
GizmoManager->DestroyAllGizmosByOwner(this);
ActiveGizmos.Reset();
}
// does not make sense that CanBeginClickDragSequence() returns a RayHit? Needs to be an out-argument...
FInputRayHit UEditPivotTool::CanBeginClickDragSequence(const FInputDeviceRay& PressPos)
{
if (TransformProps->bEnableSnapDragging == false || ActiveGizmos.Num() == 0)
{
return FInputRayHit();
}
FHitResult Result;
bool bWorldHit = ToolSceneQueriesUtil::FindNearestVisibleObjectHit(TargetWorld, Result, PressPos.WorldRay);
if (!bWorldHit)
{
return FInputRayHit();
}
return FInputRayHit(Result.Distance, Result.ImpactNormal);
}
void UEditPivotTool::OnClickPress(const FInputDeviceRay& PressPos)
{
FInputRayHit HitPos = CanBeginClickDragSequence(PressPos);
check(HitPos.bHit);
GetToolManager()->BeginUndoTransaction(LOCTEXT("TransformToolTransformTxnName", "SnapDrag"));
FEditPivotTarget& ActiveTarget = ActiveGizmos[0];
USceneComponent* GizmoComponent = ActiveTarget.TransformGizmo->GetGizmoActor()->GetRootComponent();
StartDragTransform = GizmoComponent->GetComponentToWorld();
}
void UEditPivotTool::OnClickDrag(const FInputDeviceRay& DragPos)
{
bool bRotate = (TransformProps->RotationMode != EEditPivotSnapDragRotationMode::Ignore);
float NormalSign = (TransformProps->RotationMode == EEditPivotSnapDragRotationMode::AlignFlipped) ? -1.0f : 1.0f;
FHitResult Result;
bool bWorldHit = ToolSceneQueriesUtil::FindNearestVisibleObjectHit(TargetWorld, Result, DragPos.WorldRay);
if (bWorldHit == false)
{
return;
}
FVector HitPos = Result.ImpactPoint;
FVector TargetNormal = (-NormalSign) * Result.Normal;
FQuaterniond AlignRotation = (bRotate) ?
FQuaterniond(FVector3d::UnitZ(), (FVector3d)TargetNormal) : FQuaterniond::Identity();
FTransform NewTransform = StartDragTransform;
NewTransform.SetRotation((FQuat)AlignRotation);
NewTransform.SetTranslation(HitPos);
FEditPivotTarget& ActiveTarget = ActiveGizmos[0];
ActiveTarget.TransformGizmo->SetNewGizmoTransform(NewTransform);
}
void UEditPivotTool::OnClickRelease(const FInputDeviceRay& ReleasePos)
{
OnTerminateDragSequence();
}
void UEditPivotTool::OnTerminateDragSequence()
{
FEditPivotTarget& ActiveTarget = ActiveGizmos[0];
USceneComponent* GizmoComponent = ActiveTarget.TransformGizmo->GetGizmoActor()->GetRootComponent();
FTransform EndDragtransform = GizmoComponent->GetComponentToWorld();
TUniquePtr<FComponentWorldTransformChange> Change = MakeUnique<FComponentWorldTransformChange>(StartDragTransform, EndDragtransform);
GetToolManager()->EmitObjectChange(GizmoComponent, MoveTemp(Change),
LOCTEXT("TransformToolTransformTxnName", "SnapDrag"));
GetToolManager()->EndUndoTransaction();
}
void UEditPivotTool::UpdateAssets(const FFrame3d& NewPivotWorldFrame)
{
GetToolManager()->BeginUndoTransaction(LOCTEXT("EditPivotToolTransactionName", "Edit Pivot"));
FTransform NewWorldTransform = NewPivotWorldFrame.ToFTransform();
FTransform NewWorldInverse = NewWorldTransform.Inverse();
TArray<FTransform> OriginalTransforms;
for (int32 ComponentIdx = 0; ComponentIdx < Targets.Num(); ComponentIdx++)
{
OriginalTransforms.Add((FTransform)UE::ToolTarget::GetLocalToWorldTransform(Targets[ComponentIdx]));
}
for (int32 ComponentIdx = 0; ComponentIdx < Targets.Num(); ComponentIdx++)
{
UPrimitiveComponent* Component = UE::ToolTarget::GetTargetComponent(Targets[ComponentIdx]);
Component->Modify();
UInstancedStaticMeshComponent* InstancedComponent = Cast<UInstancedStaticMeshComponent>(Component);
if (InstancedComponent != nullptr)
{
// For ISMC, we will not bake in a mesh transform, instead we will update the instance transforms relative to the new pivot
// TODO: this could be optional, and another alternative would be to bake the pivot to the mesh and then update
// all the instance transforms so they stay in the same position?
// save world transforms
int32 NumInstances = InstancedComponent->GetInstanceCount();
TArray<FTransform> WorldTransforms;
WorldTransforms.SetNum(NumInstances);
for (int32 k = 0; k < NumInstances; ++k)
{
InstancedComponent->GetInstanceTransform(k, WorldTransforms[k], true);
}
// update position to new pivot
InstancedComponent->SetWorldTransform(NewWorldTransform);
// restore world transforms, which will compute new local transforms such that the instances do not move in the world
for (int32 k = 0; k < NumInstances; ++k)
{
InstancedComponent->UpdateInstanceTransform(k, WorldTransforms[k], true, true, false);
}
}
else if (MapToFirstOccurrences[ComponentIdx] == ComponentIdx)
{
UE::Geometry::FTransform3d ToBake(OriginalTransforms[ComponentIdx] * NewWorldInverse);
// transform simple collision geometry
if (UE::Geometry::ComponentTypeSupportsCollision(Component))
{
UE::Geometry::TransformSimpleCollision(Component, ToBake);
}
FMeshDescription SourceMesh(UE::ToolTarget::GetMeshDescriptionCopy(Targets[ComponentIdx], false));
FMeshDescriptionEditableTriangleMeshAdapter EditableMeshDescAdapter(&SourceMesh);
MeshAdapterTransforms::ApplyTransform(EditableMeshDescAdapter, ToBake);
// todo: support vertex-only update
UE::ToolTarget::CommitMeshDescriptionUpdate(Targets[ComponentIdx], &SourceMesh);
Component->SetWorldTransform(NewWorldTransform);
}
else
{
// try to invert baked transform
FTransform Baked = OriginalTransforms[MapToFirstOccurrences[ComponentIdx]] * NewWorldInverse;
Component->SetWorldTransform(Baked.Inverse() * OriginalTransforms[ComponentIdx]);
}
AActor* OwnerActor = UE::ToolTarget::GetTargetActor(Targets[ComponentIdx]);
if (OwnerActor)
{
OwnerActor->MarkComponentsRenderStateDirty();
OwnerActor->UpdateComponentTransforms();
}
}
// hack to ensure user sees the updated pivot immediately: request re-select of the original selection
FSelectedOjectsChangeList NewSelection;
NewSelection.ModificationType = ESelectedObjectsModificationType::Replace;
for (int OrigMeshIdx = 0; OrigMeshIdx < Targets.Num(); OrigMeshIdx++)
{
AActor* OwnerActor = UE::ToolTarget::GetTargetActor(Targets[OrigMeshIdx]);
if (OwnerActor)
{
NewSelection.Actors.Add(OwnerActor);
}
}
GetToolManager()->RequestSelectionChange(NewSelection);
GetToolManager()->EndUndoTransaction();
}
#undef LOCTEXT_NAMESPACE