2020-01-08 17:11:23 -05:00
// Copyright Epic Games, Inc. All Rights Reserved.
2019-12-19 18:07:47 -05:00
# include "BakeTransformTool.h"
# include "InteractiveToolManager.h"
# include "ToolBuilderUtil.h"
# include "ToolSetupUtil.h"
2021-06-13 00:36:02 -04:00
# include "DynamicMesh/DynamicMesh3.h"
2019-12-19 18:07:47 -05:00
# include "BaseBehaviors/MultiClickSequenceInputBehavior.h"
# include "Selection/SelectClickedAction.h"
2020-01-27 20:11:15 -05:00
# include "MeshAdapterTransforms.h"
# include "MeshDescriptionAdapter.h"
2019-12-19 18:07:47 -05:00
# include "MeshDescriptionToDynamicMesh.h"
# include "DynamicMeshToMeshDescription.h"
2021-03-01 12:11:10 -04:00
# include "Physics/ComponentCollisionUtil.h"
2022-10-03 20:40:33 -04:00
# include "PhysicsEngine/BodySetup.h"
2019-12-19 18:07:47 -05:00
2021-02-23 18:03:26 -04:00
# include "TargetInterfaces/MeshDescriptionCommitter.h"
2021-03-01 12:11:10 -04:00
# include "TargetInterfaces/MeshDescriptionProvider.h"
2021-02-23 18:03:26 -04:00
# include "TargetInterfaces/PrimitiveComponentBackedTarget.h"
# include "ToolTargetManager.h"
2021-06-22 11:55:16 -04:00
# include "ModelingToolTargetUtil.h"
2021-02-23 18:03:26 -04:00
2022-09-28 01:06:15 -04:00
# include UE_INLINE_GENERATED_CPP_BY_NAME(BakeTransformTool)
2021-03-09 19:33:56 -04:00
using namespace UE : : Geometry ;
2019-12-19 18:07:47 -05:00
# define LOCTEXT_NAMESPACE "UBakeTransformTool"
/*
* ToolBuilder
*/
2021-11-23 09:42:40 -05:00
UMultiSelectionMeshEditingTool * UBakeTransformToolBuilder : : CreateNewTool ( const FToolBuilderState & SceneState ) const
2021-02-23 18:03:26 -04:00
{
2021-11-23 09:42:40 -05:00
return NewObject < UBakeTransformTool > ( SceneState . ToolManager ) ;
2019-12-19 18:07:47 -05:00
}
/*
* Tool
*/
UBakeTransformToolProperties : : UBakeTransformToolProperties ( )
{
}
UBakeTransformTool : : UBakeTransformTool ( )
{
}
void UBakeTransformTool : : Setup ( )
{
UInteractiveTool : : Setup ( ) ;
2020-01-27 20:11:15 -05:00
BasicProperties = NewObject < UBakeTransformToolProperties > ( this ) ;
2019-12-19 18:07:47 -05:00
AddToolPropertySource ( BasicProperties ) ;
2020-01-27 20:11:15 -05:00
FText AllTheWarnings = LOCTEXT ( " BakeTransformWarning " , " 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
2021-02-23 18:03:26 -04:00
bool bSharesSources = GetMapToSharedSourceData ( MapToFirstOccurrences ) ;
2020-01-27 20:11:15 -05:00
if ( bSharesSources )
{
AllTheWarnings = FText : : Format ( FTextFormat : : FromString ( " {0} \n \n {1} " ) , AllTheWarnings , LOCTEXT ( " BakeTransformSharedAssetsWarning " , " WARNING: Multiple meshes in your selection use the same source asset! This is not supported -- each asset can only have one baked transform. " ) ) ;
}
bool bHasZeroScales = false ;
2022-05-10 20:29:48 -04:00
BasicProperties - > bAllowNoScale = true ;
2021-03-11 11:40:03 -04:00
for ( int32 ComponentIdx = 0 ; ComponentIdx < Targets . Num ( ) ; ComponentIdx + + )
2020-01-27 20:11:15 -05:00
{
2021-06-22 11:55:16 -04:00
FTransform Transform = ( FTransform ) UE : : ToolTarget : : GetLocalToWorldTransform ( Targets [ ComponentIdx ] ) ;
2022-05-10 20:29:48 -04:00
FVector Scale = Transform . GetScale3D ( ) ;
// Set variable so a DetailCustomization can disable "DoNotBakeScale" if we have some targets w/ both rotation and non-uniform scale
// Note we could relax this to allow DoNotBakeScale if the rotation axis is aligned to the non-uniform scale axis, but that might make the enable/disable condition harder to understand.
BasicProperties - > bAllowNoScale = BasicProperties - > bAllowNoScale & & ( Transform . GetRotation ( ) . IsIdentity ( ) | | Transform . GetScale3D ( ) . AllComponentsEqual ( ) ) ;
if ( Scale . GetAbsMin ( ) < KINDA_SMALL_NUMBER )
2020-01-27 20:11:15 -05:00
{
bHasZeroScales = true ;
}
}
if ( bHasZeroScales )
{
AllTheWarnings = FText : : Format ( FTextFormat : : FromString ( " {0} \n \n {1} " ) , AllTheWarnings , LOCTEXT ( " BakeTransformWithZeroScale " , " WARNING: Baking a zero scale in any dimension will permanently flatten the asset. " ) ) ;
}
GetToolManager ( ) - > DisplayMessage ( AllTheWarnings , EToolMessageLevel : : UserWarning ) ;
2020-03-10 14:00:36 -04:00
2021-02-08 17:02:09 -04:00
SetToolDisplayName ( LOCTEXT ( " ToolName " , " Bake Transform " ) ) ;
2020-03-10 14:00:36 -04:00
GetToolManager ( ) - > DisplayMessage (
LOCTEXT ( " OnStartTool " , " This Tool applies the current Rotation and/or Scaling of the object's Transform to the underlying mesh Asset. " ) ,
EToolMessageLevel : : UserNotification ) ;
2019-12-19 18:07:47 -05:00
}
2022-01-28 18:40:54 -05:00
void UBakeTransformTool : : OnShutdown ( EToolShutdownType ShutdownType )
2019-12-19 18:07:47 -05:00
{
if ( ShutdownType = = EToolShutdownType : : Accept )
{
2020-01-27 20:11:15 -05:00
UpdateAssets ( ) ;
2019-12-19 18:07:47 -05:00
}
}
2020-01-27 20:11:15 -05:00
void UBakeTransformTool : : UpdateAssets ( )
2019-12-19 18:07:47 -05:00
{
2021-03-01 12:11:10 -04:00
// Make sure mesh descriptions are deserialized before we open transaction.
// This is to avoid potential stability issues related to creation/load of
// mesh descriptions inside a transaction.
2021-06-22 11:55:16 -04:00
// TODO: this may not be necessary anymore. Also may not be the most efficient
2022-10-03 20:40:33 -04:00
// Note: for the crash workaround below, this also now pre-computes the source mesh bounds
TArray < FBox > SourceMeshBounds ;
2021-03-01 12:11:10 -04:00
for ( int32 ComponentIdx = 0 ; ComponentIdx < Targets . Num ( ) ; ComponentIdx + + )
{
2022-10-03 20:40:33 -04:00
const FMeshDescription * MeshDescription = UE : : ToolTarget : : GetMeshDescription ( Targets [ ComponentIdx ] ) ;
if ( MapToFirstOccurrences [ ComponentIdx ] < ComponentIdx )
{
SourceMeshBounds . Add ( SourceMeshBounds [ MapToFirstOccurrences [ ComponentIdx ] ] ) ;
}
else
{
SourceMeshBounds . Add ( MeshDescription - > ComputeBoundingBox ( ) ) ;
}
2021-03-01 12:11:10 -04:00
}
2022-10-03 20:40:33 -04:00
constexpr bool bWorkaroundForCrashIfConvexAndMeshModifiedInSameTransaction = true ;
bool bNeedSeparateTransactionForSimpleCollision = false ;
if constexpr ( bWorkaroundForCrashIfConvexAndMeshModifiedInSameTransaction )
{
for ( int32 ComponentIdx = 0 ; ComponentIdx < Targets . Num ( ) ; ComponentIdx + + )
{
UToolTarget * Target = Targets [ ComponentIdx ] ;
UPrimitiveComponent * Component = UE : : ToolTarget : : GetTargetComponent ( Target ) ;
if ( UE : : Geometry : : ComponentTypeSupportsCollision ( Component ) )
{
if ( UBodySetup * BodySetup = UE : : Geometry : : GetBodySetup ( Component ) )
{
if ( BodySetup - > AggGeom . ConvexElems . Num ( ) > 0 )
{
bNeedSeparateTransactionForSimpleCollision = true ;
break ;
}
}
}
}
}
2019-12-19 18:07:47 -05:00
2022-10-03 20:40:33 -04:00
// Compute all the transforms that we should bake to the assets or apply to the components
TArray < FTransformSRT3d > BakedTransforms , ComponentTransforms ;
2021-02-23 18:03:26 -04:00
for ( int32 ComponentIdx = 0 ; ComponentIdx < Targets . Num ( ) ; ComponentIdx + + )
2019-12-19 18:07:47 -05:00
{
2021-06-22 11:55:16 -04:00
UToolTarget * Target = Targets [ ComponentIdx ] ;
2021-02-23 18:03:26 -04:00
2022-01-29 14:37:53 -05:00
FTransformSRT3d ComponentToWorld = UE : : ToolTarget : : GetLocalToWorldTransform ( Target ) ;
FTransformSRT3d ToBakePart = FTransformSRT3d : : Identity ( ) ;
FTransformSRT3d NewWorldPart = ComponentToWorld ;
2020-01-27 20:11:15 -05:00
if ( MapToFirstOccurrences [ ComponentIdx ] < ComponentIdx )
2019-12-19 18:07:47 -05:00
{
2020-01-27 20:11:15 -05:00
ToBakePart = BakedTransforms [ MapToFirstOccurrences [ ComponentIdx ] ] ;
BakedTransforms . Add ( ToBakePart ) ;
// try to invert baked transform
2022-01-29 14:37:53 -05:00
NewWorldPart = FTransformSRT3d (
2020-01-27 20:11:15 -05:00
NewWorldPart . GetRotation ( ) * ToBakePart . GetRotation ( ) . Inverse ( ) ,
NewWorldPart . GetTranslation ( ) ,
2022-01-29 14:37:53 -05:00
NewWorldPart . GetScale ( ) * FTransformSRT3d : : GetSafeScaleReciprocal ( ToBakePart . GetScale ( ) )
2020-01-27 20:11:15 -05:00
) ;
NewWorldPart . SetTranslation ( NewWorldPart . GetTranslation ( ) - NewWorldPart . TransformVector ( ToBakePart . GetTranslation ( ) ) ) ;
}
else
{
if ( BasicProperties - > bBakeRotation )
{
ToBakePart . SetRotation ( ComponentToWorld . GetRotation ( ) ) ;
NewWorldPart . SetRotation ( FQuaterniond : : Identity ( ) ) ;
}
FVector3d ScaleVec = ComponentToWorld . GetScale ( ) ;
2019-12-19 18:07:47 -05:00
2020-01-27 20:11:15 -05:00
// weird algo to choose what to keep around as uniform scale in the case where we want to bake out the non-uniform scaling
FVector3d AbsScales ( FMathd : : Abs ( ScaleVec . X ) , FMathd : : Abs ( ScaleVec . Y ) , FMathd : : Abs ( ScaleVec . Z ) ) ;
double RemainingUniformScale = AbsScales . X ;
{
FVector3d Dists ;
for ( int SubIdx = 0 ; SubIdx < 3 ; SubIdx + + )
{
int OtherA = ( SubIdx + 1 ) % 3 ;
int OtherB = ( SubIdx + 2 ) % 3 ;
Dists [ SubIdx ] = FMathd : : Abs ( AbsScales [ SubIdx ] - AbsScales [ OtherA ] ) + FMathd : : Abs ( AbsScales [ SubIdx ] - AbsScales [ OtherB ] ) ;
}
int BestSubIdx = 0 ;
for ( int CompareSubIdx = 1 ; CompareSubIdx < 3 ; CompareSubIdx + + )
{
if ( Dists [ CompareSubIdx ] < Dists [ BestSubIdx ] )
{
BestSubIdx = CompareSubIdx ;
}
}
RemainingUniformScale = AbsScales [ BestSubIdx ] ;
if ( RemainingUniformScale < = FLT_MIN )
{
2021-03-17 19:32:44 -04:00
RemainingUniformScale = MaxAbsElement ( AbsScales ) ;
2020-01-27 20:11:15 -05:00
}
}
switch ( BasicProperties - > BakeScale )
{
case EBakeScaleMethod : : BakeFullScale :
ToBakePart . SetScale ( ScaleVec ) ;
NewWorldPart . SetScale ( FVector3d : : One ( ) ) ;
break ;
case EBakeScaleMethod : : BakeNonuniformScale :
check ( RemainingUniformScale > FLT_MIN ) ; // avoid baking a ~zero scale
ToBakePart . SetScale ( ScaleVec / RemainingUniformScale ) ;
NewWorldPart . SetScale ( FVector3d ( RemainingUniformScale , RemainingUniformScale , RemainingUniformScale ) ) ;
break ;
case EBakeScaleMethod : : DoNotBakeScale :
break ;
default :
check ( false ) ; // must explicitly handle all cases
}
2019-12-19 18:07:47 -05:00
2021-06-22 11:55:16 -04:00
// do this part within the commit because we have the MeshDescription already computed
if ( BasicProperties - > bRecenterPivot )
2020-01-27 20:11:15 -05:00
{
2022-10-03 20:40:33 -04:00
FBox BBox = SourceMeshBounds [ ComponentIdx ] ;
2021-06-22 11:55:16 -04:00
FVector3d Center ( BBox . GetCenter ( ) ) ;
FFrame3d LocalFrame ( Center ) ;
ToBakePart . SetTranslation ( ToBakePart . GetTranslation ( ) - Center ) ;
NewWorldPart . SetTranslation ( NewWorldPart . GetTranslation ( ) + NewWorldPart . TransformVector ( Center ) ) ;
}
2020-01-27 20:11:15 -05:00
2022-10-03 20:40:33 -04:00
BakedTransforms . Add ( ToBakePart ) ;
}
ComponentTransforms . Add ( NewWorldPart ) ;
}
auto UpdateSimpleCollision = [ ] ( UPrimitiveComponent * Component , const FTransformSRT3d & ToBakePart )
{
// try to transform simple collision
if ( UE : : Geometry : : ComponentTypeSupportsCollision ( Component ) )
{
UE : : Geometry : : TransformSimpleCollision ( Component , ToBakePart ) ;
}
} ;
// If necessary, make a first transaction to bake simple collision updates separately (works around crash in undo/redo of transactions that update both hulls and static meshes together)
if ( bNeedSeparateTransactionForSimpleCollision )
{
GetToolManager ( ) - > BeginUndoTransaction ( LOCTEXT ( " BakeTransformToolSimpleCollisionTransactionName " , " Bake Transforms Part 1 (Simple Collision) " ) ) ;
for ( int32 ComponentIdx = 0 ; ComponentIdx < Targets . Num ( ) ; ComponentIdx + + )
{
UToolTarget * Target = Targets [ ComponentIdx ] ;
UPrimitiveComponent * Component = UE : : ToolTarget : : GetTargetComponent ( Target ) ;
Component - > Modify ( ) ;
UpdateSimpleCollision ( Component , BakedTransforms [ ComponentIdx ] ) ;
}
GetToolManager ( ) - > EndUndoTransaction ( ) ;
}
GetToolManager ( ) - > BeginUndoTransaction ( bNeedSeparateTransactionForSimpleCollision ?
LOCTEXT ( " BakeTransformToolGeometryTransactionName " , " Bake Transforms Part 2 (Visible Geometry) " ) :
LOCTEXT ( " BakeTransformToolTransactionName " , " Bake Transforms " ) ) ;
for ( int32 ComponentIdx = 0 ; ComponentIdx < Targets . Num ( ) ; ComponentIdx + + )
{
UToolTarget * Target = Targets [ ComponentIdx ] ;
UPrimitiveComponent * Component = UE : : ToolTarget : : GetTargetComponent ( Target ) ;
Component - > Modify ( ) ;
FTransformSRT3d ToBakePart = BakedTransforms [ ComponentIdx ] ;
if ( MapToFirstOccurrences [ ComponentIdx ] = = ComponentIdx )
{
// apply edit
FMeshDescription SourceMesh ( UE : : ToolTarget : : GetMeshDescriptionCopy ( Targets [ ComponentIdx ] ) ) ;
FMeshDescriptionEditableTriangleMeshAdapter EditableMeshDescAdapter ( & SourceMesh ) ;
2021-06-22 11:55:16 -04:00
MeshAdapterTransforms : : ApplyTransform ( EditableMeshDescAdapter , ToBakePart ) ;
2020-01-27 20:11:15 -05:00
2021-06-22 11:55:16 -04:00
FVector3d BakeScaleVec = ToBakePart . GetScale ( ) ;
if ( BakeScaleVec . X * BakeScaleVec . Y * BakeScaleVec . Z < 0 )
{
SourceMesh . ReverseAllPolygonFacing ( ) ;
}
2020-01-27 20:11:15 -05:00
2021-06-22 11:55:16 -04:00
// todo: support vertex-only update
UE : : ToolTarget : : CommitMeshDescriptionUpdate ( Target , & SourceMesh ) ;
2020-01-27 20:11:15 -05:00
2022-10-03 20:40:33 -04:00
if ( ! bNeedSeparateTransactionForSimpleCollision )
2021-06-22 11:55:16 -04:00
{
2022-10-03 20:40:33 -04:00
UpdateSimpleCollision ( Component , ToBakePart ) ;
2021-06-22 11:55:16 -04:00
}
2021-03-01 12:11:10 -04:00
2020-01-27 20:11:15 -05:00
BakedTransforms . Add ( ToBakePart ) ;
}
2019-12-19 18:07:47 -05:00
2022-10-03 20:40:33 -04:00
Component - > SetWorldTransform ( ( FTransform ) ComponentTransforms [ ComponentIdx ] ) ;
2021-06-22 11:55:16 -04:00
AActor * TargetActor = UE : : ToolTarget : : GetTargetActor ( Target ) ;
if ( TargetActor )
{
TargetActor - > MarkComponentsRenderStateDirty ( ) ;
}
2019-12-19 18:07:47 -05:00
}
2021-11-22 15:45:29 -05:00
if ( BasicProperties - > bRecenterPivot )
{
// 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 ) ;
}
2019-12-19 18:07:47 -05:00
GetToolManager ( ) - > EndUndoTransaction ( ) ;
}
# undef LOCTEXT_NAMESPACE
2022-09-28 01:06:15 -04:00