2020-01-08 17:11:23 -05:00
// Copyright Epic Games, Inc. All Rights Reserved.
2019-12-19 18:07:47 -05:00
# include "DeformMeshPolygonsTool.h"
2020-06-23 18:40:00 -04:00
# include "InteractiveToolManager.h"
2021-06-13 00:36:02 -04:00
# include "DynamicMesh/MeshNormals.h"
2020-04-27 12:58:07 -04:00
# include "ModelingOperators/Public/ModelingTaskTypes.h"
2020-06-23 18:40:00 -04:00
# include "Solvers/ConstrainedMeshDeformer.h"
# include "ToolBuilderUtil.h"
2021-10-07 22:25:54 -04:00
# include "ToolSetupUtil.h"
2020-06-23 18:40:00 -04:00
# include "ToolSceneQueriesUtil.h"
2021-06-11 22:42:32 -04:00
# include "ModelingToolTargetUtil.h"
2021-10-28 19:47:45 -04:00
# include "Curves/RichCurve.h"
2021-03-18 17:20:32 -04:00
2021-03-09 19:33:56 -04:00
using namespace UE : : Geometry ;
2020-04-27 12:58:07 -04:00
2019-12-19 18:07:47 -05:00
# define LOCTEXT_NAMESPACE "UDeformMeshPolygonsTool"
2020-04-27 12:58:07 -04:00
class FDeformTask ;
//Stores per-vertex data needed by the laplacian deformer object
//TODO: May be a candidate for a subclass of the FGroupTopologyLaplacianDeformer
struct FDeformerVertexConstraintData
{
FDeformerVertexConstraintData & operator = ( const FDeformerVertexConstraintData & other )
{
Position = other . Position ;
2020-06-23 18:40:00 -04:00
Weight = other . Weight ;
2020-04-27 12:58:07 -04:00
bPostFix = other . bPostFix ;
return * this ;
}
FVector3d Position ;
2020-06-23 18:40:00 -04:00
double Weight { 0.0 } ;
bool bPostFix { false } ;
2020-04-27 12:58:07 -04:00
} ;
/**
* FDeformTask is an object which wraps an asynchronous task to be run multiple times on a separate thread .
* The Laplacian deformation process requires the use of potentially large sparse matrices and sparse multiplication .
*
* Expected usage :
*
*
* // define constraints. Need Constraints[VertID] to hold the constraints for the corresponding vertex.
* TArray < FDeformerVertexConstraintData > Constraints ;
* . . . .
*
* // populate with the VertexIDs of the vertices that are in the region you wish to deform.
* TArray < int32 > SrcVertIDs ; //Basically a mini-index buffer.
* . . .
*
* // Create or reuse a laplacian deformation task.
* FDeformTask * DeformTask = New FDeformTask ( WeightScheme ) ;
*
* // the deformer will have to build a new mesh that represents the regions in SrcVertIDs;
* // but set this to false on subsequent calls to UpdateDeformer if the SrcVertIDs array hasn't changed.
* bool bRequiresRegion = true ;
* DefTask - > UpdateDeformer ( WeightScheme , Mesh , Constraints , SrcVertIDs , bRequiresRegion ) ;
*
* DeformTask - > DoWork ( ) ; or DeformTask - > StartBackgroundTask ( ) ; //which calls DoWork on background thread.
*
* // wheh DeformTask->IsDone == true; you can copy the results back to the mesh
* DeformTask - > ExportResults ( Mesh ) ;
*
* Note : if only the positions in the Constraints change ( e . g . handle positions ) then subsequent calls
* to UpdateDeformer ( ) and DoWork ( ) will be much faster as the matrix system will not be rebuilt or re - factored
*/
class FConstrainedMeshDeformerTask : public FNonAbandonableTask
{
friend class FAsyncTask < FDeformTask > ;
2020-06-23 18:40:00 -04:00
public :
2020-04-27 12:58:07 -04:00
enum
{
INACTIVE_SUBSET_ID = - 1
} ;
FConstrainedMeshDeformerTask ( const ELaplacianWeightScheme SelectedWeightScheme )
2021-10-28 11:25:57 -04:00
: LaplacianWeightScheme ( SelectedWeightScheme )
2020-04-27 12:58:07 -04:00
{
}
2020-06-23 18:40:00 -04:00
virtual ~ FConstrainedMeshDeformerTask ( ) { } ;
2020-04-27 12:58:07 -04:00
//NO idea what this is meant to do. Performance analysis maybe? Scheduling data?
FORCEINLINE TStatId GetStatId ( ) const
{
RETURN_QUICK_DECLARE_CYCLE_STAT ( FConstrainedMeshDeformerTask , STATGROUP_ThreadPoolAsyncTasks ) ;
}
/** Called by the main thread in the tool, this copies the Constraint buffer right before the task begins on another thread.
* Ensures the FConstrainedMeshDeformer is using correct mesh subset and the selected settings , then updates on change in properties , i . e . weight scheme */
2020-06-23 18:40:00 -04:00
void UpdateDeformer ( const ELaplacianWeightScheme SelectedWeightScheme , const FDynamicMesh3 & Mesh ,
const TArray < FDeformerVertexConstraintData > & ConstraintArray ,
const TArray < int32 > & SrcIDBufferSubset , bool bNewTransaction , const FRichCurve * Curve ) ;
2020-04-27 12:58:07 -04:00
/** Required by the FAsyncTaskExecutor */
2020-06-23 18:40:00 -04:00
void SetAbortSource ( bool * bAbort )
{
bAbortSource = bAbort ;
} ;
2020-04-27 12:58:07 -04:00
/** Called by the FAsyncTask<FDeformTask> object for background computation. */
void DoWork ( ) ;
/** Updates the positions in the target mesh for regions that correspond to the subset mesh */
void ExportResults ( FDynamicMesh3 & TargetMesh ) const ;
private :
/** Creates the mesh (i.e. SubsetMesh) that corresponds to the region of the SrcMesh defined by the partial index buffer SrcIDBufferSubset */
void InitializeSubsetMesh ( const FDynamicMesh3 & SrcMesh , const TArray < int32 > & SrcIDBufferSubset ) ;
/** Attenuates the weights of the constraints using the selected curve */
void ApplyAttenuation ( ) ;
/** Denotes the weight scheme being used by the running background task. Changes when selected property changes in editor. */
ELaplacianWeightScheme LaplacianWeightScheme ;
/** positions for each vertex in the subset mesh - for use in the deformer */
TArray < FVector3d > SubsetPositionBuffer ;
/** constraint data for each vertex in subset mesh - for use by the deformer*/
TArray < FDeformerVertexConstraintData > SubsetConstraintBuffer ;
FRichCurve WeightAttenuationCurve ;
/** True only for the first update, and then false for the duration of the Input transaction
* It ' s passed in and copied in UpdateDeformer ( ) */
bool bIsNewTransaction = true ;
/** When true, the constraint weights will be attenuated based on distance using the provided curve object*/
bool bAttenuateWeights = false ;
/** The abort bool used by the Task Deleter */
bool * bAbortSource = nullptr ;
/** Used to initialize the array mapping, updated during the UpdateDeformer() function */
2021-10-28 11:25:57 -04:00
int SrcMeshMaxVertexID = 0 ;
2020-04-27 12:58:07 -04:00
/** A subset of the original mesh */
FDynamicMesh3 SubsetMesh ;
/** Maps Subset Mesh VertexID to Src Mesh VertexID */
TArray < int32 > SubsetVertexIDToSrcVertexIDMap ;
/** Laplacian deformer object gets rebuilt each new transaction */
2020-05-03 16:34:34 -04:00
TUniquePtr < UE : : Solvers : : IConstrainedMeshSolver > ConstrainedDeformer ;
2020-04-27 12:58:07 -04:00
} ;
class FGroupTopologyLaplacianDeformer : public FGroupTopologyDeformer
{
public :
FGroupTopologyLaplacianDeformer ( ) = default ;
virtual ~ FGroupTopologyLaplacianDeformer ( ) ;
/** Used to begin a procedural addition of modified vertices */
inline void ResetModifiedVertices ( )
{
ModifiedVertices . Empty ( ) ;
} ;
/** Change tracking */
2020-06-23 18:40:00 -04:00
template < typename ValidSetAppendContainerType >
2020-04-27 12:58:07 -04:00
void RecordModifiedVertices ( const ValidSetAppendContainerType & Container )
{
ModifiedVertices . Empty ( ) ;
ModifiedVertices . Append ( Container ) ;
}
/** Used to iteratively add to the active change set (TSet<>)*/
inline void RecordModifiedVertex ( int32 VertexID )
{
ModifiedVertices . Add ( VertexID ) ;
} ;
void SetActiveHandleFaces ( const TArray < int > & FaceGroupIDs ) override ;
void SetActiveHandleEdges ( const TArray < int > & TopologyEdgeIDs ) override ;
void SetActiveHandleCorners ( const TArray < int > & TopologyCornerIDs ) override ;
/** Allocates shared storage for use in task synchronization */
void InitBackgroundWorker ( const ELaplacianWeightScheme WeightScheme ) ;
/** Coordinates the background tasks. Returns false if the worker was already running */
bool UpdateAndLaunchdWorker ( const ELaplacianWeightScheme WeightScheme , const FRichCurve * Curve = nullptr ) ;
/** Capture data about background task state.*/
bool IsTaskInFlight ( ) const ;
/** Sets the SrcMeshConstraintBuffer to have a size of MaxVertexID, and initializes with the current mesh positions, but weight zero*/
void InitializeConstraintBuffer ( ) ;
/** Given an array of Group IDs, update the selection and record vertices */
void UpdateSelection ( const FDynamicMesh3 * TargetMesh , const TArray < int > & Groups , bool bLocalizeDeformation ) ;
/** Updates the mesh preview and/or solvers upon user input, provided a deformation strategy */
2020-06-23 18:40:00 -04:00
void UpdateSolution ( FDynamicMesh3 * TargetMesh ,
const TFunction < FVector3d ( FDynamicMesh3 * Mesh , int ) > & HandleVertexDeformFunc ) override ;
2020-04-27 12:58:07 -04:00
/** Updates the vertex positions of the mesh with the result from the last deformation solve. */
void ExportDeformedPositions ( FDynamicMesh3 * TargetMesh ) ;
/** Returns true if the asynchronous task has finished. */
2020-06-23 18:40:00 -04:00
inline bool IsDone ( )
{
return AsyncMeshDeformTask = = nullptr | | AsyncMeshDeformTask - > IsDone ( ) ;
} ;
2020-04-27 12:58:07 -04:00
/** Triggers abort on task and passes off ownership to deleter object */
inline void Shutdown ( ) ;
2020-06-23 18:40:00 -04:00
const TArray < FROIFace > & GetROIFaces ( ) const
{
return ROIFaces ;
}
2020-04-27 12:58:07 -04:00
/** Stores the position of the vertex constraints and corresponding weights for the entire mesh. This is used as a form of scratch space.*/
TArray < FDeformerVertexConstraintData > SrcMeshConstraintBuffer ;
/** Array of vertex indices organized in groups of three - basically an index buffer - that defines the subset of the mesh that the deformation task will work on.*/
TArray < int32 > SubsetIDBuffer ;
/** Need to update the task with the current submesh */
bool bTaskSubmeshIsDirty = true ;
/** Asynchronous task object. This object deals with expensive matrix functionality that computes the deformation of a local mesh. */
FAsyncTaskExecuterWithAbort < FConstrainedMeshDeformerTask > * AsyncMeshDeformTask = nullptr ;
/** The weight which will be applied to the constraints corresponding to the handle vertices. */
double HandleWeights = 1.0 ;
/** This is set to true whenever the user interacts with the tool under laplacian deformation mode.
* It is set to false immediately before beginning a background task and cannot be set to false again until the work is done . */
bool bDeformerNeedsToRun = false ;
/** When true, tells the solver to attempt to postfix the actual position of the handles to the constrained position */
bool bPostfixHandles = false ;
2020-06-23 18:40:00 -04:00
//This is set to false only after
2020-04-27 12:58:07 -04:00
// 1) the asynchronous deformation task is complete
// 2) the main thread has seen it complete, and
// 3) the main thread updates the vertex positions of the mesh one last time
bool bVertexPositionsNeedSync = false ;
bool bLocalize = true ;
} ;
2019-12-19 18:07:47 -05:00
//////////////////////////////
// DEBUG_SETTINGS
//Draw white triangles defining the selection subset
//#define DEBUG_ROI_TRIANGLES
//Draw pink circles around the handles
//#define DEBUG_ROI_HANDLES
//Draw points on the ROI vertices, White => Weight == 0, Black => Weight == 1
//#define DEBUG_ROI_WEIGHTS
//////////////////////////////
/*
* ToolBuilder
*/
UMeshSurfacePointTool * UDeformMeshPolygonsToolBuilder : : CreateNewTool ( const FToolBuilderState & SceneState ) const
{
UDeformMeshPolygonsTool * DeformTool = NewObject < UDeformMeshPolygonsTool > ( SceneState . ToolManager ) ;
2021-11-18 14:37:34 -05:00
DeformTool - > SetWorld ( SceneState . World ) ;
2019-12-19 18:07:47 -05:00
return DeformTool ;
}
/*
* Tool
*/
UDeformMeshPolygonsTransformProperties : : UDeformMeshPolygonsTransformProperties ( )
{
DeformationStrategy = EGroupTopologyDeformationStrategy : : Laplacian ;
2020-06-23 18:40:00 -04:00
TransformMode = EQuickTransformerMode : : AxisTranslation ;
bSelectVertices = true ;
bSelectFaces = true ;
bSelectEdges = true ;
bShowWireframe = false ;
2019-12-19 18:07:47 -05:00
}
/*
* Asynchronous Task
*/
2020-06-23 18:40:00 -04:00
void FConstrainedMeshDeformerTask : : UpdateDeformer ( const ELaplacianWeightScheme SelectedWeightScheme ,
const FDynamicMesh3 & SrcMesh ,
const TArray < FDeformerVertexConstraintData > & ConstraintArray ,
const TArray < int32 > & SrcIDBufferSubset , bool bNewTransaction ,
const FRichCurve * Curve )
2019-12-19 18:07:47 -05:00
{
2020-06-23 18:40:00 -04:00
bIsNewTransaction = bNewTransaction ;
2019-12-19 18:07:47 -05:00
SrcMeshMaxVertexID = SrcMesh . MaxVertexID ( ) ;
LaplacianWeightScheme = SelectedWeightScheme ;
2020-06-23 18:40:00 -04:00
2019-12-19 18:07:47 -05:00
bAttenuateWeights = ( Curve ! = nullptr ) ;
if ( bAttenuateWeights )
{
WeightAttenuationCurve = * Curve ;
}
// Set-up the subset mesh.
if ( bIsNewTransaction )
{
//Copy the part of the mesh we want to deform into the SubsetMesh and create map from Src Mesh to the SubsetMesh.
InitializeSubsetMesh ( SrcMesh , SrcIDBufferSubset ) ;
}
// only want the subset of constraints that correspond to our subset mesh.
{
const int32 NumSubsetVerts = SubsetVertexIDToSrcVertexIDMap . Num ( ) ;
SubsetConstraintBuffer . Empty ( NumSubsetVerts ) ;
SubsetConstraintBuffer . AddUninitialized ( NumSubsetVerts ) ;
for ( int32 SubVertexID = 0 ; SubVertexID < SubsetVertexIDToSrcVertexIDMap . Num ( ) ; + + SubVertexID )
{
2020-06-23 18:40:00 -04:00
int32 SrcVtxID = SubsetVertexIDToSrcVertexIDMap [ SubVertexID ] ;
2019-12-19 18:07:47 -05:00
SubsetConstraintBuffer [ SubVertexID ] = ConstraintArray [ SrcVtxID ] ;
}
}
check ( bIsNewTransaction | | ConstrainedDeformer . IsValid ( ) ) ;
}
void FConstrainedMeshDeformerTask : : DoWork ( )
{
//TODO: (simple optimization) -
// Instead of SrcVertexIDtoSubsetVertexIDMap, use SubsetVertexIDToSetVertexIDMap - then we can use the VertexIndicesItr()
// on the SubsetMesh to minimize the quantity of vertex indices we need to iterate at every following step.
if ( * bAbortSource = = true )
{
return ;
}
if ( bIsNewTransaction ) //Will only be true once per input transaction (click+drag)
{
// Create a new deformation solver.
2020-05-03 16:34:34 -04:00
ConstrainedDeformer = UE : : MeshDeformation : : ConstructConstrainedMeshDeformer ( LaplacianWeightScheme , SubsetMesh ) ;
2019-12-19 18:07:47 -05:00
if ( bAttenuateWeights )
{
ApplyAttenuation ( ) ;
}
//Update our deformer's constraints before deforming using the copy of the constraint buffer
for ( int32 SubsetVertexID = 0 ; SubsetVertexID < SubsetConstraintBuffer . Num ( ) ; + + SubsetVertexID )
{
2020-03-02 17:25:30 -05:00
FDeformerVertexConstraintData & CData = SubsetConstraintBuffer [ SubsetVertexID ] ;
2019-12-19 18:07:47 -05:00
ConstrainedDeformer - > AddConstraint ( SubsetVertexID , CData . Weight , CData . Position , CData . bPostFix ) ;
}
bIsNewTransaction = false ;
}
else
{
//This else block is run every consecutive frame after the start of the input transaction because UpdateConstraintPosition() is very cheap (no factorizing or rebuilding)
//Update only the positions of the constraints, as the weights cannot change mid-transaction
for ( int32 SubsetVertexID = 0 ; SubsetVertexID < SubsetConstraintBuffer . Num ( ) ; + + SubsetVertexID )
{
2020-03-02 17:25:30 -05:00
FDeformerVertexConstraintData & CData = SubsetConstraintBuffer [ SubsetVertexID ] ;
2019-12-19 18:07:47 -05:00
ConstrainedDeformer - > UpdateConstraintPosition ( SubsetVertexID , CData . Position , CData . bPostFix ) ;
}
}
if ( * bAbortSource = = true )
{
return ;
}
//Run the deformation process
const bool bSuccessfulSolve = ConstrainedDeformer - > Deform ( SubsetPositionBuffer ) ;
2020-06-23 18:40:00 -04:00
2019-12-19 18:07:47 -05:00
if ( bSuccessfulSolve )
{
if ( * bAbortSource = = true )
{
return ;
}
}
else
{
//UE_LOG(LogTemp, Warning, TEXT("Laplacian deformation failed"));
}
}
2020-06-23 18:40:00 -04:00
inline void FConstrainedMeshDeformerTask : : InitializeSubsetMesh ( const FDynamicMesh3 & SrcMesh ,
const TArray < int32 > & SrcIDBufferSubset )
2019-12-19 18:07:47 -05:00
{
//These can be re-used until the user stops dragging
SubsetMesh . Clear ( ) ;
SubsetPositionBuffer . Reset ( ) ;
//Initialize every element to -1, helps us keep track of vertices we've already added while iterating the triangles
TArray < int32 > SrcVertexIDToSubsetVertexIDMap ;
SrcVertexIDToSubsetVertexIDMap . Init ( INACTIVE_SUBSET_ID , SrcMeshMaxVertexID ) ;
//Iterate the triangle array to append vertices, and then triangles to the temporary subset mesh all at once
for ( int32 i = 0 ; i < SrcIDBufferSubset . Num ( ) ; i + = 3 )
{
// Build the triangle
FIndex3i Triangle ;
for ( int32 v = 0 ; v < 3 ; + + v )
{
//It's the SrcVertexID because every element in the SrcIDBufferSubset is the Vertex ID of a vertex in the original mesh.
const int32 SrcVertexID = SrcIDBufferSubset [ i + v ] ;
2020-06-23 18:40:00 -04:00
int32 & SubsetID = SrcVertexIDToSubsetVertexIDMap [ SrcVertexID ] ;
2019-12-19 18:07:47 -05:00
if ( SubsetID = = INACTIVE_SUBSET_ID ) // we haven't already visited this vertex
{
const FVector3d Vertex = SrcMesh . GetVertex ( SrcVertexID ) ;
2020-06-23 18:40:00 -04:00
SubsetID = SubsetMesh . AppendVertex ( Vertex ) ;
2019-12-19 18:07:47 -05:00
}
Triangle [ v ] = SubsetID ;
}
SubsetMesh . AppendTriangle ( Triangle ) ;
}
// create a mapping back to the original vertex IDs from the subset mesh
int32 MaxSubMeshVertexID = SubsetMesh . MaxVertexID ( ) ; // Really MaxID + 1
SubsetVertexIDToSrcVertexIDMap . Reset ( MaxSubMeshVertexID ) ;
SubsetVertexIDToSrcVertexIDMap . AddUninitialized ( MaxSubMeshVertexID ) ;
for ( int32 SrcID = 0 ; SrcID < SrcVertexIDToSubsetVertexIDMap . Num ( ) ; + + SrcID )
{
const int32 SubsetVertexID = SrcVertexIDToSubsetVertexIDMap [ SrcID ] ;
if ( SubsetVertexID ! = INACTIVE_SUBSET_ID )
{
SubsetVertexIDToSrcVertexIDMap [ SubsetVertexID ] = SrcID ;
}
}
}
void FConstrainedMeshDeformerTask : : ExportResults ( FDynamicMesh3 & TargetMesh ) const
{
//Update the position buffer result
for ( int32 SubsetVertexID = 0 ; SubsetVertexID < SubsetVertexIDToSrcVertexIDMap . Num ( ) ; + + SubsetVertexID )
{
2020-06-23 18:40:00 -04:00
const int32 SrcVertexID = SubsetVertexIDToSrcVertexIDMap [ SubsetVertexID ] ;
2019-12-19 18:07:47 -05:00
const FVector3d Position = SubsetPositionBuffer [ SubsetVertexID ] ;
2020-06-23 18:40:00 -04:00
2019-12-19 18:07:47 -05:00
TargetMesh . SetVertex ( SrcVertexID , Position ) ;
}
2020-01-27 20:11:15 -05:00
FMeshNormals : : QuickRecomputeOverlayNormals ( TargetMesh ) ;
2019-12-19 18:07:47 -05:00
}
void FConstrainedMeshDeformerTask : : ApplyAttenuation ( )
{
TSet < int > Handles ;
2020-06-23 18:40:00 -04:00
auto InPlaceMinMaxElements = [ ] ( FVector3d & Min , FVector3d & Max , const FVector3d Test ) {
2019-12-19 18:07:47 -05:00
for ( uint8 i = 0 ; i < 3 ; + + i )
{
Min [ i ] = Test [ i ] < Min [ i ] ? Test [ i ] : Min [ i ] ;
Max [ i ] = Test [ i ] > Max [ i ] ? Test [ i ] : Max [ i ] ;
}
} ;
//Experimental approach: Just going to try grabbing the bounding box of the entire mesh, then the bounding box of the handles as a point cloud.
// We need a T value to pass to the Weights curve, so let's try finding the distance of each vertex V from line segment formed by the min/max handles
2020-06-23 18:40:00 -04:00
// Divide the distance from the handles to vertex V by the length of the mesh's bounding box extent,
2019-12-19 18:07:47 -05:00
// and that will provide a **ROUGH** approximation of the time value for our curve.
//
// Distance( LineSegment(MaxHandle,MinHandle) , V )
// where T(V) is time value at V T(V) = -----------------------------------------------------
2020-06-23 18:40:00 -04:00
// and V is the position Length(MeshMin - MeshMax)
2019-12-19 18:07:47 -05:00
// of each vertex
2020-06-23 18:40:00 -04:00
FVector3d Min { std : : numeric_limits < double > : : max ( ) , std : : numeric_limits < double > : : max ( ) ,
std : : numeric_limits < double > : : max ( ) } ;
FVector3d Max { std : : numeric_limits < double > : : min ( ) , std : : numeric_limits < double > : : min ( ) ,
std : : numeric_limits < double > : : min ( ) } ;
2019-12-19 18:07:47 -05:00
FVector3d MinHandles = Min ;
FVector3d MaxHandles = Max ;
2020-06-23 18:40:00 -04:00
double LeastWeight = std : : numeric_limits < double > : : max ( ) ;
2019-12-19 18:07:47 -05:00
for ( int32 SubVertexID = 0 ; SubVertexID < SubsetConstraintBuffer . Num ( ) ; + + SubVertexID )
{
2020-03-02 17:25:30 -05:00
FDeformerVertexConstraintData & CData = SubsetConstraintBuffer [ SubVertexID ] ;
2019-12-19 18:07:47 -05:00
// Update bounding box
InPlaceMinMaxElements ( Min , Max , CData . Position ) ;
2020-06-23 18:40:00 -04:00
2019-12-19 18:07:47 -05:00
if ( CData . Weight > 0.0 )
{
LeastWeight = CData . Weight < LeastWeight ? CData . Weight : LeastWeight ;
// update bounding box
InPlaceMinMaxElements ( MinHandles , MaxHandles , CData . Position ) ;
Handles . Add ( SubVertexID ) ;
}
}
2021-03-30 21:25:22 -04:00
double ExtentLength = Distance ( Min , Max ) ;
2019-12-19 18:07:47 -05:00
// Is this why the system has memory?
for ( int32 SubVertexID = 0 ; SubVertexID < SubsetConstraintBuffer . Num ( ) ; + + SubVertexID )
{
if ( ! Handles . Contains ( SubVertexID ) )
{
2020-03-02 17:25:30 -05:00
FDeformerVertexConstraintData & CData = SubsetConstraintBuffer [ SubVertexID ] ;
2021-03-30 21:25:22 -04:00
FVector3d OtherPoint = ( FVector3d ) FMath : : ClosestPointOnSegment ( ( FVector ) CData . Position , ( FVector ) MinHandles , ( FVector ) MaxHandles ) ;
double T = Distance ( CData . Position , OtherPoint ) / ExtentLength ;
2019-12-19 18:07:47 -05:00
CData . Weight = WeightAttenuationCurve . Eval ( T ) * LeastWeight ;
}
}
}
/*
* FGroupTopologyLaplacianDeformer methods
*/
void FGroupTopologyLaplacianDeformer : : InitBackgroundWorker ( const ELaplacianWeightScheme WeightScheme )
{
//Initialize asynchronous deformation objects
if ( AsyncMeshDeformTask = = nullptr )
{
AsyncMeshDeformTask = new FAsyncTaskExecuterWithAbort < FConstrainedMeshDeformerTask > ( WeightScheme ) ;
}
}
void FGroupTopologyLaplacianDeformer : : InitializeConstraintBuffer ( )
{
//MaxVertexID is used because the array is potentially sparse.
int MaxVertexID = Mesh - > MaxVertexID ( ) ;
SrcMeshConstraintBuffer . SetNum ( MaxVertexID ) ;
2020-06-23 18:40:00 -04:00
2019-12-19 18:07:47 -05:00
for ( int32 VertexID : Mesh - > VertexIndicesItr ( ) )
{
2020-03-02 17:25:30 -05:00
FDeformerVertexConstraintData & CD = SrcMeshConstraintBuffer [ VertexID ] ;
2020-06-23 18:40:00 -04:00
CD . Position = Mesh - > GetVertex ( VertexID ) ;
CD . Weight = 0.0 ;
CD . bPostFix = false ;
2019-12-19 18:07:47 -05:00
}
}
bool FGroupTopologyLaplacianDeformer : : IsTaskInFlight ( ) const
{
2020-06-23 18:40:00 -04:00
return ( AsyncMeshDeformTask ! = nullptr & & ! AsyncMeshDeformTask - > IsDone ( ) ) ;
2019-12-19 18:07:47 -05:00
}
2020-06-23 18:40:00 -04:00
bool FGroupTopologyLaplacianDeformer : : UpdateAndLaunchdWorker ( const ELaplacianWeightScheme SelectedWeightScheme ,
const FRichCurve * Curve )
2019-12-19 18:07:47 -05:00
{
/* Deformer needs to run if we've modified the constraints since the last time it finished. */
if ( AsyncMeshDeformTask = = nullptr )
{
InitBackgroundWorker ( SelectedWeightScheme ) ;
}
if ( bDeformerNeedsToRun & & AsyncMeshDeformTask - > IsDone ( ) )
2020-06-23 18:40:00 -04:00
{
2019-12-19 18:07:47 -05:00
bool bRebuildSubsetMesh = bTaskSubmeshIsDirty ;
FConstrainedMeshDeformerTask & Task = AsyncMeshDeformTask - > GetTask ( ) ;
// Update the deformer's buffers and weight scheme
// this creates the subset mesh if needed.
2020-06-23 18:40:00 -04:00
Task . UpdateDeformer ( SelectedWeightScheme , * Mesh , SrcMeshConstraintBuffer , SubsetIDBuffer , bRebuildSubsetMesh ,
Curve ) ;
2019-12-19 18:07:47 -05:00
// task now has valid submesh
bTaskSubmeshIsDirty = false ;
//Launch second thread
AsyncMeshDeformTask - > StartBackgroundTask ( ) ;
2020-06-23 18:40:00 -04:00
bDeformerNeedsToRun = false ; // This was set to true above in UpdateSolution()
bVertexPositionsNeedSync = true ; // The task will generate new vertex positions.
2019-12-19 18:07:47 -05:00
return true ;
}
return false ;
}
void FGroupTopologyLaplacianDeformer : : SetActiveHandleFaces ( const TArray < int > & FaceGroupIDs )
{
Reset ( ) ;
2020-06-23 18:40:00 -04:00
check ( FaceGroupIDs . Num ( ) = = 1 ) ; // multi-face not supported yet
2019-12-19 18:07:47 -05:00
int GroupID = FaceGroupIDs [ 0 ] ;
2020-06-23 18:40:00 -04:00
// find set of vertices in handle
2019-12-19 18:07:47 -05:00
Topology - > CollectGroupVertices ( GroupID , HandleVertices ) ;
Topology - > CollectGroupBoundaryVertices ( GroupID , HandleBoundaryVertices ) ;
ModifiedVertices = HandleVertices ;
// list of adj groups. may contain duplicates.
TArray < int > AdjGroups ;
for ( int BoundaryVert : HandleBoundaryVertices )
{
Topology - > FindVertexNbrGroups ( BoundaryVert , AdjGroups ) ;
}
// Local neighborhood - Adjacent groups plus self
TArray < int > NeighborhoodGroups ;
// Collect the rest of the 1-ring groups that are adjacent to the selected one.
NeighborhoodGroups . Add ( GroupID ) ;
for ( int AdjGroup : AdjGroups )
{
NeighborhoodGroups . AddUnique ( AdjGroup ) ; // remove duplicates by add unique
}
CalculateROI ( FaceGroupIDs , NeighborhoodGroups ) ;
2020-06-23 18:40:00 -04:00
2019-12-19 18:07:47 -05:00
UpdateSelection ( Mesh , NeighborhoodGroups , bLocalize ) ;
// Save the positions of the selected region.
SaveInitialPositions ( ) ;
}
void FGroupTopologyLaplacianDeformer : : SetActiveHandleEdges ( const TArray < int > & TopologyEdgeIDs )
{
Reset ( ) ;
for ( int EdgeID : TopologyEdgeIDs )
{
const TArray < int > & EdgeVerts = Topology - > GetGroupEdgeVertices ( EdgeID ) ;
for ( int VertID : EdgeVerts )
{
HandleVertices . Add ( VertID ) ;
}
}
HandleBoundaryVertices = HandleVertices ;
2020-06-23 18:40:00 -04:00
ModifiedVertices = HandleVertices ;
2019-12-19 18:07:47 -05:00
TArray < int > HandleGroups ;
TArray < int > NbrGroups ;
Topology - > FindEdgeNbrGroups ( TopologyEdgeIDs , NbrGroups ) ;
CalculateROI ( HandleGroups , NbrGroups ) ;
UpdateSelection ( Mesh , NbrGroups , bLocalize ) ;
// Save the positions of the selected region.
SaveInitialPositions ( ) ;
}
void FGroupTopologyLaplacianDeformer : : SetActiveHandleCorners ( const TArray < int > & CornerIDs )
{
Reset ( ) ;
for ( int CornerID : CornerIDs )
{
int VertID = Topology - > GetCornerVertexID ( CornerID ) ;
if ( VertID > = 0 )
{
HandleVertices . Add ( VertID ) ;
}
}
HandleBoundaryVertices = HandleVertices ;
2020-06-23 18:40:00 -04:00
ModifiedVertices = HandleVertices ;
2019-12-19 18:07:47 -05:00
TArray < int > HandleGroups ;
TArray < int > NbrGroups ;
Topology - > FindCornerNbrGroups ( CornerIDs , NbrGroups ) ;
CalculateROI ( HandleGroups , NbrGroups ) ;
UpdateSelection ( Mesh , NbrGroups , bLocalize ) ;
// Save the positions of the selected region.
SaveInitialPositions ( ) ;
}
2020-06-23 18:40:00 -04:00
void FGroupTopologyLaplacianDeformer : : UpdateSelection ( const FDynamicMesh3 * TargetMesh , const TArray < int > & Groups ,
bool bLocalizeDeformation )
2019-12-19 18:07:47 -05:00
{
// Build an index buffer (SubsetIdBuffer) and a vertexId buffer (ModifidedVertices) for the region we want to change
if ( bLocalizeDeformation )
{
//For each group ID, retrieve the array of all TriangleIDs associated with that GroupID and append that array to the end of the TriSet to remove duplicates
TSet < int > TriSet ;
for ( const int32 & GroupID : Groups )
{
TriSet . Append ( Topology - > GetGroupFaces ( GroupID ) ) ;
2020-06-23 18:40:00 -04:00
} //Now we have every triangle ID involved in the transaction
2019-12-19 18:07:47 -05:00
//Since we are flattening the Face to a set of 3 indices, we do 3 * number of triangles though it is too many.
SubsetIDBuffer . Reset ( 3 * TriSet . Num ( ) ) ;
//Add each triangle's A,B, and C indices to the subset triangle array.
for ( const int & Tri : TriSet )
{
FIndex3i Triple = TargetMesh - > GetTriangle ( Tri ) ;
SubsetIDBuffer . Add ( Triple . A ) ;
SubsetIDBuffer . Add ( Triple . B ) ;
SubsetIDBuffer . Add ( Triple . C ) ;
}
}
else
{
// the entire mesh.
const int32 NumTris = TargetMesh - > TriangleCount ( ) ;
SubsetIDBuffer . Reset ( 3 * NumTris ) ;
2020-06-23 18:40:00 -04:00
for ( int TriId : TargetMesh - > TriangleIndicesItr ( ) )
{
2019-12-19 18:07:47 -05:00
FIndex3i Triple = TargetMesh - > GetTriangle ( TriId ) ;
SubsetIDBuffer . Add ( Triple . A ) ;
SubsetIDBuffer . Add ( Triple . B ) ;
SubsetIDBuffer . Add ( Triple . C ) ;
}
}
// Add the vertices to the set (eliminates duplicates.) Todo: don't use a set.
ResetModifiedVertices ( ) ;
for ( int32 VertexID : SubsetIDBuffer )
{
RecordModifiedVertex ( VertexID ) ;
}
}
// This actually updates constraints that correspond to the handle vertices.
2020-06-23 18:40:00 -04:00
void FGroupTopologyLaplacianDeformer : : UpdateSolution (
FDynamicMesh3 * TargetMesh , const TFunction < FVector3d ( FDynamicMesh3 * Mesh , int ) > & HandleVertexDeformFunc )
2019-12-19 18:07:47 -05:00
{
// copy the current positions.
FVertexPositionCache CurrentPositions ;
for ( int VertexID : InitialPositions . Vertices )
{
CurrentPositions . AddVertex ( TargetMesh , VertexID ) ;
}
// Set the target mesh to the initial positions.
// Note: this only updates the vertices in the selected region.
InitialPositions . SetPositions ( TargetMesh ) ;
//Reset the constraints
for ( int32 VertexID : ModifiedVertices )
{
//Get the vertex's data from the constraint buffer
2020-03-02 17:25:30 -05:00
FDeformerVertexConstraintData & CData = SrcMeshConstraintBuffer [ VertexID ] ;
2019-12-19 18:07:47 -05:00
CData . Position = TargetMesh - > GetVertex ( VertexID ) ;
CData . Weight = 0.0 ; //A weight of zero is used to allow this point to move freely when moving the handles
CData . bPostFix = false ;
}
//Actually deform the handles and add a constraint.
for ( int VertexID : HandleVertices )
{
const FVector3d DeformPos = HandleVertexDeformFunc ( TargetMesh , VertexID ) ;
//Get the vertex's data from the constraint buffer
2020-03-02 17:25:30 -05:00
FDeformerVertexConstraintData & CData = SrcMeshConstraintBuffer [ VertexID ] ;
2019-12-19 18:07:47 -05:00
//Set the new vertex data
CData . Position = DeformPos ;
CData . Weight = HandleWeights ;
CData . bPostFix = bPostfixHandles ;
}
// Restore Current Positions. This is done because the target mesh is being used to define the highlight region.
2020-06-23 18:40:00 -04:00
// if we don't reset the positions the highlight mesh will appear to reset momentarily until the first laplacian solver result is available
2019-12-19 18:07:47 -05:00
CurrentPositions . SetPositions ( TargetMesh ) ;
bDeformerNeedsToRun = true ;
}
void FGroupTopologyLaplacianDeformer : : ExportDeformedPositions ( FDynamicMesh3 * TargetMesh )
{
bool bIsWorking = IsTaskInFlight ( ) ;
if ( AsyncMeshDeformTask ! = nullptr & & ! bIsWorking )
{
const FConstrainedMeshDeformerTask & Task = AsyncMeshDeformTask - > GetTask ( ) ;
Task . ExportResults ( * TargetMesh ) ;
}
}
inline FGroupTopologyLaplacianDeformer : : ~ FGroupTopologyLaplacianDeformer ( )
{
Shutdown ( ) ;
}
inline void FGroupTopologyLaplacianDeformer : : Shutdown ( )
2020-06-23 18:40:00 -04:00
{
2019-12-19 18:07:47 -05:00
if ( AsyncMeshDeformTask ! = nullptr )
{
if ( AsyncMeshDeformTask - > IsDone ( ) )
{
delete AsyncMeshDeformTask ;
}
else
{
AsyncMeshDeformTask - > CancelAndDelete ( ) ;
}
AsyncMeshDeformTask = nullptr ;
}
}
/*
* Tool methods
*/
UDeformMeshPolygonsTool : : UDeformMeshPolygonsTool ( )
{
2021-10-28 11:25:57 -04:00
UInteractiveTool : : SetToolDisplayName ( LOCTEXT ( " DeformPolygroupsToolName " , " PolyGroup Deform " ) ) ;
2019-12-19 18:07:47 -05:00
}
void UDeformMeshPolygonsTool : : Setup ( )
{
UMeshSurfacePointTool : : Setup ( ) ;
2020-06-23 18:40:00 -04:00
LaplacianDeformer = MakePimpl < FGroupTopologyLaplacianDeformer > ( ) ;
2020-04-27 12:58:07 -04:00
2019-12-19 18:07:47 -05:00
// create dynamic mesh component to use for live preview
2022-03-30 11:06:49 -04:00
check ( TargetWorld . IsValid ( ) ) ;
2021-11-18 14:37:34 -05:00
FActorSpawnParameters SpawnInfo ;
PreviewMeshActor = TargetWorld - > SpawnActor < AInternalToolFrameworkActor > ( FVector : : ZeroVector , FRotator : : ZeroRotator , SpawnInfo ) ;
DynamicMeshComponent = NewObject < UDynamicMeshComponent > ( PreviewMeshActor ) ;
DynamicMeshComponent - > SetupAttachment ( PreviewMeshActor - > GetRootComponent ( ) ) ;
2019-12-19 18:07:47 -05:00
DynamicMeshComponent - > RegisterComponent ( ) ;
2021-06-11 22:42:32 -04:00
WorldTransform = UE : : ToolTarget : : GetLocalToWorldTransform ( Target ) ;
DynamicMeshComponent - > SetWorldTransform ( ( FTransform ) WorldTransform ) ;
2021-10-07 22:25:54 -04:00
ToolSetupUtil : : ApplyRenderingConfigurationToPreview ( DynamicMeshComponent , Target ) ;
2019-12-19 18:07:47 -05:00
// set materials
2021-06-11 22:42:32 -04:00
FComponentMaterialSet MaterialSet = UE : : ToolTarget : : GetMaterialSet ( Target ) ;
2019-12-19 18:07:47 -05:00
for ( int k = 0 ; k < MaterialSet . Materials . Num ( ) ; + + k )
{
DynamicMeshComponent - > SetMaterial ( k , MaterialSet . Materials [ k ] ) ;
}
// dynamic mesh configuration settings
2021-06-11 22:42:32 -04:00
DynamicMeshComponent - > SetTangentsType ( EDynamicMeshComponentTangentsMode : : AutoCalculated ) ;
DynamicMeshComponent - > SetMesh ( UE : : ToolTarget : : GetDynamicMeshCopy ( Target ) ) ;
2020-06-23 18:40:00 -04:00
OnDynamicMeshComponentChangedHandle =
DynamicMeshComponent - > OnMeshChanged . Add ( FSimpleMulticastDelegate : : FDelegate : : CreateUObject (
this , & UDeformMeshPolygonsTool : : OnDynamicMeshComponentChanged ) ) ;
2019-12-19 18:07:47 -05:00
// add properties
TransformProps = NewObject < UDeformMeshPolygonsTransformProperties > ( this ) ;
AddToolPropertySource ( TransformProps ) ;
// initialize AABBTree
MeshSpatial . SetMesh ( DynamicMeshComponent - > GetMesh ( ) ) ;
PrecomputeTopology ( ) ;
//initialize topology selector
TopoSelector . Initialize ( DynamicMeshComponent - > GetMesh ( ) , & Topology ) ;
2020-06-23 18:40:00 -04:00
TopoSelector . SetSpatialSource ( [ this ] ( ) { return & GetSpatial ( ) ; } ) ;
2021-06-11 22:42:32 -04:00
TopoSelector . PointsWithinToleranceTest = [ this ] ( const FVector3d & Position1 , const FVector3d & Position2 , double TolScale ) {
return ToolSceneQueriesUtil : : PointSnapQuery ( CameraState , WorldTransform . TransformPosition ( Position1 ) , WorldTransform . TransformPosition ( Position2 ) ,
2020-10-22 19:19:16 -04:00
ToolSceneQueriesUtil : : GetDefaultVisualAngleSnapThreshD ( ) * TolScale ) ;
2019-12-19 18:07:47 -05:00
} ;
// hide input StaticMeshComponent
2021-06-11 22:42:32 -04:00
UE : : ToolTarget : : HideSourceObject ( Target ) ;
2019-12-19 18:07:47 -05:00
// init state flags flags
bInDrag = false ;
// initialize snap solver
QuickAxisTranslater . Initialize ( ) ;
QuickAxisRotator . Initialize ( ) ;
// set up visualizers
2020-06-23 18:40:00 -04:00
PolyEdgesRenderer . LineColor = FLinearColor : : Red ;
2019-12-19 18:07:47 -05:00
PolyEdgesRenderer . LineThickness = 2.0 ;
2020-06-23 18:40:00 -04:00
HilightRenderer . LineColor = FLinearColor : : Green ;
HilightRenderer . LineThickness = 4.0f ;
2019-12-19 18:07:47 -05:00
// Allocates buffers, sets up the asynchronous task
// Copies the source mesh positions.
2020-06-23 18:40:00 -04:00
const ELaplacianWeightScheme LaplacianWeightScheme =
ConvertToLaplacianWeightScheme ( TransformProps - > SelectedWeightScheme ) ;
2020-04-27 12:58:07 -04:00
LaplacianDeformer - > InitBackgroundWorker ( LaplacianWeightScheme ) ;
2020-06-23 18:40:00 -04:00
2019-12-19 18:07:47 -05:00
/**
// How to add a curve for the weights.
//Add a default curve for falloff
FKeyHandle Keys [ 5 ] ;
Keys [ 0 ] = TransformProps - > DefaultFalloffCurve . UpdateOrAddKey ( 0.f , 1.f ) ;
Keys [ 1 ] = TransformProps - > DefaultFalloffCurve . UpdateOrAddKey ( 0.25f , 0.25f ) ;
Keys [ 2 ] = TransformProps - > DefaultFalloffCurve . UpdateOrAddKey ( 0.3333333f , 0.25f ) ;
Keys [ 3 ] = TransformProps - > DefaultFalloffCurve . UpdateOrAddKey ( 0.6666667f , 1.25f ) ;
Keys [ 4 ] = TransformProps - > DefaultFalloffCurve . UpdateOrAddKey ( 1.f , 1.4f ) ;
for ( uint8 i = 0 ; i < 5 ; + + i )
{
TransformProps - > DefaultFalloffCurve . SetKeyInterpMode ( Keys [ i ] , ERichCurveInterpMode : : RCIM_Cubic ) ;
}
TransformProps - > WeightAttenuationCurve . EditorCurveData = TransformProps - > DefaultFalloffCurve ;
*/
if ( Topology . Groups . Num ( ) < 2 )
{
2020-06-23 18:40:00 -04:00
GetToolManager ( ) - > DisplayMessage (
2021-10-15 11:20:27 -04:00
LOCTEXT ( " NoGroupsWarning " ,
2021-10-28 11:25:57 -04:00
" This object has only a single PolyGroup. Use the GrpGen, GrpPnt or TriSel (Create PolyGroup) tools to modify PolyGroups. " ) ,
2021-10-15 11:20:27 -04:00
EToolMessageLevel : : UserWarning ) ;
2019-12-19 18:07:47 -05:00
}
2020-09-24 00:43:27 -04:00
GetToolManager ( ) - > DisplayMessage (
2021-10-28 11:25:57 -04:00
LOCTEXT ( " DeformMeshPolygonsToolDescription " , " Deform the mesh by directly manipulating (i.e. click-and-drag) the PolyGroup edges, faces, and vertices. " ) ,
2020-09-24 00:43:27 -04:00
EToolMessageLevel : : UserNotification ) ;
2019-12-19 18:07:47 -05:00
}
void UDeformMeshPolygonsTool : : Shutdown ( EToolShutdownType ShutdownType )
{
//Tell the background thread to cancel the rest of its jobs before we close;
2020-06-23 18:40:00 -04:00
LaplacianDeformer . Reset ( ) ;
2019-12-19 18:07:47 -05:00
if ( DynamicMeshComponent ! = nullptr )
{
DynamicMeshComponent - > OnMeshChanged . Remove ( OnDynamicMeshComponentChangedHandle ) ;
2021-06-11 22:42:32 -04:00
UE : : ToolTarget : : ShowSourceObject ( Target ) ;
2019-12-19 18:07:47 -05:00
if ( ShutdownType = = EToolShutdownType : : Accept )
{
// this block bakes the modified DynamicMeshComponent back into the StaticMeshComponent inside an undo transaction
2021-10-28 11:25:57 -04:00
GetToolManager ( ) - > BeginUndoTransaction ( LOCTEXT ( " DeformMeshPolygonsToolTransactionName " , " PolyGroup Deform " ) ) ;
2021-06-11 22:42:32 -04:00
DynamicMeshComponent - > ProcessMesh ( [ & ] ( const FDynamicMesh3 & ReadMesh )
{
2019-12-19 18:07:47 -05:00
FConversionToMeshDescriptionOptions ConversionOptions ;
2020-06-23 18:40:00 -04:00
ConversionOptions . bSetPolyGroups =
2021-06-11 22:42:32 -04:00
false ; // don't save polygroups, as we may change these temporarily in this tool just to get a different edit effect
UE : : ToolTarget : : CommitDynamicMeshUpdate ( Target , ReadMesh , false , ConversionOptions ) ;
2019-12-19 18:07:47 -05:00
} ) ;
GetToolManager ( ) - > EndUndoTransaction ( ) ;
}
DynamicMeshComponent - > UnregisterComponent ( ) ;
DynamicMeshComponent - > DestroyComponent ( ) ;
DynamicMeshComponent = nullptr ;
}
2021-11-18 14:37:34 -05:00
if ( PreviewMeshActor ! = nullptr )
{
PreviewMeshActor - > Destroy ( ) ;
PreviewMeshActor = nullptr ;
}
2019-12-19 18:07:47 -05:00
}
void UDeformMeshPolygonsTool : : NextTransformTypeAction ( )
{
if ( bInDrag = = false )
{
if ( TransformProps - > TransformMode = = EQuickTransformerMode : : AxisRotation )
{
TransformProps - > TransformMode = EQuickTransformerMode : : AxisTranslation ;
}
else
{
TransformProps - > TransformMode = EQuickTransformerMode : : AxisRotation ;
}
UpdateQuickTransformer ( ) ;
}
}
void UDeformMeshPolygonsTool : : RegisterActions ( FInteractiveToolActionSet & ActionSet )
{
ActionSet . RegisterAction ( this , ( int32 ) EStandardToolActions : : BaseClientDefinedActionID + 2 ,
2020-06-23 18:40:00 -04:00
TEXT ( " DeformNextTransformType " ) , LOCTEXT ( " DeformNextTransformType " , " Next Transform Type " ) ,
LOCTEXT ( " DeformNextTransformTypeTooltip " , " Cycle to next transform type " ) ,
EModifierKey : : None , EKeys : : Q , [ this ] ( ) { NextTransformTypeAction ( ) ; } ) ;
2019-12-19 18:07:47 -05:00
}
void UDeformMeshPolygonsTool : : OnDynamicMeshComponentChanged ( )
{
bSpatialDirty = true ;
TopoSelector . Invalidate ( true , false ) ;
//Makes sure the constraint buffer and position buffers reflect Undo/Redo changes
FDynamicMesh3 * Mesh = DynamicMeshComponent - > GetMesh ( ) ;
2020-06-23 18:40:00 -04:00
2019-12-19 18:07:47 -05:00
//Apply Undo/redo
for ( int VertexID : Mesh - > VertexIndicesItr ( ) )
{
2020-06-23 18:40:00 -04:00
const FVector3d Position = Mesh - > GetVertex ( VertexID ) ;
2020-04-27 12:58:07 -04:00
LaplacianDeformer - > SrcMeshConstraintBuffer [ VertexID ] . Position = Position ;
2019-12-19 18:07:47 -05:00
}
// a deform task could still be in flight.
2020-04-27 12:58:07 -04:00
if ( LaplacianDeformer - > AsyncMeshDeformTask ! = nullptr )
2019-12-19 18:07:47 -05:00
{
2020-04-27 12:58:07 -04:00
LaplacianDeformer - > AsyncMeshDeformTask - > CancelAndDelete ( ) ;
LaplacianDeformer - > AsyncMeshDeformTask = nullptr ;
LaplacianDeformer - > bTaskSubmeshIsDirty = true ;
2019-12-19 18:07:47 -05:00
}
}
FDynamicMeshAABBTree3 & UDeformMeshPolygonsTool : : GetSpatial ( )
{
if ( bSpatialDirty )
{
MeshSpatial . Build ( ) ;
bSpatialDirty = false ;
}
return MeshSpatial ;
}
bool UDeformMeshPolygonsTool : : HitTest ( const FRay & WorldRay , FHitResult & OutHit )
{
2021-06-11 22:42:32 -04:00
FRay3d LocalRay ( WorldTransform . InverseTransformPosition ( ( FVector3d ) WorldRay . Origin ) ,
WorldTransform . InverseTransformVector ( ( FVector3d ) WorldRay . Direction ) ) ;
2021-03-17 19:32:44 -04:00
UE : : Geometry : : Normalize ( LocalRay . Direction ) ;
2019-12-19 18:07:47 -05:00
FGroupTopologySelection Selection ;
FVector3d LocalPosition , LocalNormal ;
2020-09-01 14:07:48 -04:00
FGroupTopologySelector : : FSelectionSettings TopoSelectorSettings = GetTopoSelectorSettings ( ) ;
if ( TopoSelector . FindSelectedElement ( TopoSelectorSettings , LocalRay , Selection , LocalPosition , LocalNormal ) = = false )
2019-12-19 18:07:47 -05:00
{
return false ;
}
if ( Selection . SelectedCornerIDs . Num ( ) > 0 )
{
2020-09-24 00:43:27 -04:00
OutHit . FaceIndex = Selection . GetASelectedCornerID ( ) ;
2021-11-17 21:06:46 -05:00
OutHit . Distance = LocalRay . GetParameter ( LocalPosition ) ;
2021-06-11 22:42:32 -04:00
OutHit . ImpactPoint = ( FVector ) WorldTransform . TransformPosition ( LocalRay . PointAt ( OutHit . Distance ) ) ;
2019-12-19 18:07:47 -05:00
}
else if ( Selection . SelectedEdgeIDs . Num ( ) > 0 )
{
2020-09-24 00:43:27 -04:00
OutHit . FaceIndex = Selection . GetASelectedEdgeID ( ) ;
2021-11-17 21:06:46 -05:00
OutHit . Distance = LocalRay . GetParameter ( LocalPosition ) ;
2021-06-11 22:42:32 -04:00
OutHit . ImpactPoint = ( FVector ) WorldTransform . TransformPosition ( LocalRay . PointAt ( OutHit . Distance ) ) ;
2019-12-19 18:07:47 -05:00
}
else
{
int HitTID = GetSpatial ( ) . FindNearestHitTriangle ( LocalRay ) ;
if ( HitTID ! = IndexConstants : : InvalidID )
{
FTriangle3d Triangle ;
GetSpatial ( ) . GetMesh ( ) - > GetTriVertices ( HitTID , Triangle . V [ 0 ] , Triangle . V [ 1 ] , Triangle . V [ 2 ] ) ;
FIntrRay3Triangle3d Query ( LocalRay , Triangle ) ;
Query . Find ( ) ;
OutHit . FaceIndex = HitTID ;
2020-06-23 18:40:00 -04:00
OutHit . Distance = Query . RayParameter ;
2021-06-11 22:42:32 -04:00
OutHit . Normal = ( FVector ) WorldTransform . TransformVectorNoScale ( GetSpatial ( ) . GetMesh ( ) - > GetTriNormal ( HitTID ) ) ;
OutHit . ImpactPoint = ( FVector ) WorldTransform . TransformPosition ( LocalRay . PointAt ( Query . RayParameter ) ) ;
2019-12-19 18:07:47 -05:00
}
}
return true ;
}
void UDeformMeshPolygonsTool : : OnBeginDrag ( const FRay & WorldRay )
{
2021-06-11 22:42:32 -04:00
FRay3d LocalRay ( WorldTransform . InverseTransformPosition ( ( FVector3d ) WorldRay . Origin ) ,
WorldTransform . InverseTransformVector ( ( FVector3d ) WorldRay . Direction ) ) ;
2021-03-17 19:32:44 -04:00
UE : : Geometry : : Normalize ( LocalRay . Direction ) ;
2019-12-19 18:07:47 -05:00
HilightSelection . Clear ( ) ;
FGroupTopologySelection Selection ;
FVector3d LocalPosition , LocalNormal ;
2020-09-01 14:07:48 -04:00
FGroupTopologySelector : : FSelectionSettings TopoSelectorSettings = GetTopoSelectorSettings ( ) ;
bool bHit = TopoSelector . FindSelectedElement ( TopoSelectorSettings , LocalRay , Selection , LocalPosition , LocalNormal ) ;
2019-12-19 18:07:47 -05:00
if ( bHit = = false )
{
bInDrag = false ;
return ;
}
HilightSelection = Selection ;
2021-06-11 22:42:32 -04:00
FVector3d WorldHitPos = WorldTransform . TransformPosition ( LocalPosition ) ;
FVector3d WorldHitNormal = WorldTransform . TransformVector ( LocalNormal ) ;
2019-12-19 18:07:47 -05:00
2020-06-23 18:40:00 -04:00
bInDrag = true ;
StartHitPosWorld = ( FVector ) WorldHitPos ;
LastHitPosWorld = StartHitPosWorld ;
2019-12-19 18:07:47 -05:00
StartHitNormalWorld = ( FVector ) WorldHitNormal ;
QuickAxisRotator . ClearAxisLock ( ) ;
UpdateActiveSurfaceFrame ( HilightSelection ) ;
UpdateQuickTransformer ( ) ;
2021-06-11 22:42:32 -04:00
LastBrushPosLocal = ( FVector ) WorldTransform . InverseTransformPosition ( ( FVector3d ) LastHitPosWorld ) ;
2019-12-19 18:07:47 -05:00
StartBrushPosLocal = LastBrushPosLocal ;
// Record the requested deformation strategy - NB: will be forced to linear if there aren't any free points to solve.
DeformationStrategy = TransformProps - > DeformationStrategy ;
2020-06-23 18:40:00 -04:00
2019-12-19 18:07:47 -05:00
// Capture the part of the mesh that will deform
if ( DeformationStrategy = = EGroupTopologyDeformationStrategy : : Laplacian )
{
2020-04-27 12:58:07 -04:00
LaplacianDeformer - > bLocalize = true ; // TransformProps->bLocalizeDeformation;
2019-12-19 18:07:47 -05:00
//Determine which of the following (corners, edges or faces) has been selected by counting the associated feature's IDs
if ( Selection . SelectedCornerIDs . Num ( ) > 0 )
{
//Add all the the Corner's adjacent poly-groups (NbrGroups) to the ongoing array of groups.
2020-09-24 00:43:27 -04:00
LaplacianDeformer - > SetActiveHandleCorners ( Selection . SelectedCornerIDs . Array ( ) ) ;
2019-12-19 18:07:47 -05:00
}
else if ( Selection . SelectedEdgeIDs . Num ( ) > 0 )
{
//Add all the the edge's adjacent poly-groups (NbrGroups) to the ongoing array of groups.
2020-09-24 00:43:27 -04:00
LaplacianDeformer - > SetActiveHandleEdges ( Selection . SelectedEdgeIDs . Array ( ) ) ;
2019-12-19 18:07:47 -05:00
}
else if ( Selection . SelectedGroupIDs . Num ( ) > 0 )
{
2020-09-24 00:43:27 -04:00
LaplacianDeformer - > SetActiveHandleFaces ( Selection . SelectedGroupIDs . Array ( ) ) ;
2019-12-19 18:07:47 -05:00
}
// If there are actually no interior points, then we can't actually use the laplacian deformer. Need to fall back to the linear.
bool bHasInteriorVerts = false ;
2020-06-23 18:40:00 -04:00
const auto & ROIFaces = LaplacianDeformer - > GetROIFaces ( ) ;
2019-12-19 18:07:47 -05:00
for ( const auto & Face : ROIFaces )
{
2020-06-23 18:40:00 -04:00
bHasInteriorVerts = bHasInteriorVerts | | ( Face . InteriorVerts . Num ( ) ! = 0 ) ;
2019-12-19 18:07:47 -05:00
}
if ( ! bHasInteriorVerts )
{
// Change to the linear strategy for this case.
DeformationStrategy = EGroupTopologyDeformationStrategy : : Linear ;
}
else
2020-06-23 18:40:00 -04:00
{
2019-12-19 18:07:47 -05:00
// finalize the laplacian deformer : the task will need a new mesh that corresponds to the selected region.
2020-06-23 18:40:00 -04:00
LaplacianDeformer - > bTaskSubmeshIsDirty = true ;
2019-12-19 18:07:47 -05:00
}
}
2020-06-23 18:40:00 -04:00
if ( DeformationStrategy = = EGroupTopologyDeformationStrategy : : Linear )
2019-12-19 18:07:47 -05:00
{
//Determine which of the following (corners, edges or faces) has been selected by counting the associated feature's IDs
if ( Selection . SelectedCornerIDs . Num ( ) > 0 )
{
//Add all the the Corner's adjacent poly-groups (NbrGroups) to the ongoing array of groups.
2020-09-24 00:43:27 -04:00
LinearDeformer . SetActiveHandleCorners ( Selection . SelectedCornerIDs . Array ( ) ) ;
2019-12-19 18:07:47 -05:00
}
else if ( Selection . SelectedEdgeIDs . Num ( ) > 0 )
{
//Add all the the edge's adjacent poly-groups (NbrGroups) to the ongoing array of groups.
2020-09-24 00:43:27 -04:00
LinearDeformer . SetActiveHandleEdges ( Selection . SelectedEdgeIDs . Array ( ) ) ;
2019-12-19 18:07:47 -05:00
}
else if ( Selection . SelectedGroupIDs . Num ( ) > 0 )
{
2020-09-24 00:43:27 -04:00
LinearDeformer . SetActiveHandleFaces ( Selection . SelectedGroupIDs . Array ( ) ) ;
2019-12-19 18:07:47 -05:00
}
}
2020-06-23 18:40:00 -04:00
2019-12-19 18:07:47 -05:00
BeginChange ( ) ;
}
void UDeformMeshPolygonsTool : : UpdateActiveSurfaceFrame ( FGroupTopologySelection & Selection )
{
// update surface frame
2021-03-30 21:25:22 -04:00
ActiveSurfaceFrame . Origin = ( FVector3d ) StartHitPosWorld ;
2019-12-19 18:07:47 -05:00
if ( HilightSelection . SelectedCornerIDs . Num ( ) = = 1 )
{
// just keeping existing axes...we don't have enough info to do something smarter
}
else
{
2021-03-30 21:25:22 -04:00
ActiveSurfaceFrame . AlignAxis ( 2 , ( FVector3d ) StartHitNormalWorld ) ;
2019-12-19 18:07:47 -05:00
if ( HilightSelection . SelectedEdgeIDs . Num ( ) = = 1 )
{
FVector3d Tangent ;
2020-09-24 00:43:27 -04:00
if ( Topology . GetGroupEdgeTangent ( HilightSelection . GetASelectedEdgeID ( ) , Tangent ) )
2019-12-19 18:07:47 -05:00
{
2021-06-11 22:42:32 -04:00
Tangent = WorldTransform . TransformVector ( Tangent ) ;
2019-12-19 18:07:47 -05:00
ActiveSurfaceFrame . ConstrainedAlignAxis ( 0 , Tangent , ActiveSurfaceFrame . Z ( ) ) ;
}
}
}
}
FQuickTransformer * UDeformMeshPolygonsTool : : GetActiveQuickTransformer ( )
{
if ( TransformProps - > TransformMode = = EQuickTransformerMode : : AxisRotation )
{
return & QuickAxisRotator ;
}
else
{
return & QuickAxisTranslater ;
}
}
void UDeformMeshPolygonsTool : : UpdateQuickTransformer ( )
{
bool bUseLocalAxes =
2020-06-23 18:40:00 -04:00
( GetToolManager ( ) - > GetContextQueriesAPI ( ) - > GetCurrentCoordinateSystem ( ) = = EToolContextCoordinateSystem : : Local ) ;
2019-12-19 18:07:47 -05:00
if ( bUseLocalAxes )
{
GetActiveQuickTransformer ( ) - > SetActiveWorldFrame ( ActiveSurfaceFrame ) ;
}
else
{
2021-03-30 21:25:22 -04:00
GetActiveQuickTransformer ( ) - > SetActiveFrameFromWorldAxes ( ( FVector3d ) StartHitPosWorld ) ;
2019-12-19 18:07:47 -05:00
}
}
void UDeformMeshPolygonsTool : : UpdateChangeFromROI ( bool bFinal )
{
if ( ActiveVertexChange = = nullptr )
{
return ;
}
const bool bIsLaplacian = ( DeformationStrategy = = EGroupTopologyDeformationStrategy : : Laplacian ) ;
FDynamicMesh3 * Mesh = DynamicMeshComponent - > GetMesh ( ) ;
2020-06-23 18:40:00 -04:00
const TSet < int > & ModifiedVertices =
( bIsLaplacian ) ? LaplacianDeformer - > GetModifiedVertices ( ) : LinearDeformer . GetModifiedVertices ( ) ;
2020-04-18 18:42:59 -04:00
ActiveVertexChange - > SaveVertices ( Mesh , ModifiedVertices , ! bFinal ) ;
2020-06-23 18:40:00 -04:00
const TSet < int > & ModifiedNormals =
( bIsLaplacian ) ? LaplacianDeformer - > GetModifiedOverlayNormals ( ) : LinearDeformer . GetModifiedOverlayNormals ( ) ;
2020-03-05 18:07:34 -05:00
ActiveVertexChange - > SaveOverlayNormals ( Mesh , ModifiedNormals , ! bFinal ) ;
2019-12-19 18:07:47 -05:00
}
void UDeformMeshPolygonsTool : : OnUpdateDrag ( const FRay & Ray )
{
if ( bInDrag )
{
bUpdatePending = true ;
2020-06-23 18:40:00 -04:00
UpdateRay = Ray ;
2019-12-19 18:07:47 -05:00
}
}
void UDeformMeshPolygonsTool : : OnEndDrag ( const FRay & Ray )
{
2020-06-23 18:40:00 -04:00
bInDrag = false ;
2019-12-19 18:07:47 -05:00
bUpdatePending = false ;
// update spatial
bSpatialDirty = true ;
2020-06-23 18:40:00 -04:00
HilightSelection . Clear ( ) ;
2019-12-19 18:07:47 -05:00
TopoSelector . Invalidate ( true , false ) ;
QuickAxisRotator . Reset ( ) ;
QuickAxisTranslater . Reset ( ) ;
//If it's linear, it's computed real time with no delay. This may need to be restructured for clarity by using the background task for this as well.
if ( DeformationStrategy = = EGroupTopologyDeformationStrategy : : Linear )
{
// close change record
EndChange ( ) ;
}
}
bool UDeformMeshPolygonsTool : : OnUpdateHover ( const FInputDeviceRay & DevicePos )
{
//if (!bNeedEmitEndChange)
if ( ActiveVertexChange = = nullptr )
{
2021-06-11 22:42:32 -04:00
FRay3d LocalRay ( WorldTransform . InverseTransformPosition ( ( FVector3d ) DevicePos . WorldRay . Origin ) ,
WorldTransform . InverseTransformVector ( ( FVector3d ) DevicePos . WorldRay . Direction ) ) ;
2021-03-17 19:32:44 -04:00
UE : : Geometry : : Normalize ( LocalRay . Direction ) ;
2019-12-19 18:07:47 -05:00
HilightSelection . Clear ( ) ;
FVector3d LocalPosition , LocalNormal ;
2020-09-01 14:07:48 -04:00
FGroupTopologySelector : : FSelectionSettings TopoSelectorSettings = GetTopoSelectorSettings ( ) ;
bool bHit = TopoSelector . FindSelectedElement ( TopoSelectorSettings , LocalRay , HilightSelection , LocalPosition , LocalNormal ) ;
2019-12-19 18:07:47 -05:00
if ( bHit )
{
2021-06-11 22:42:32 -04:00
StartHitPosWorld = ( FVector ) WorldTransform . TransformPosition ( LocalPosition ) ;
StartHitNormalWorld = ( FVector ) WorldTransform . TransformVector ( LocalNormal ) ;
2019-12-19 18:07:47 -05:00
UpdateActiveSurfaceFrame ( HilightSelection ) ;
UpdateQuickTransformer ( ) ;
}
}
return true ;
}
void UDeformMeshPolygonsTool : : ComputeUpdate ( )
{
if ( bUpdatePending = = true )
{
// Linear Deformer : Update the solution
// Laplacain Deformer : Update the constraints (positions and weights) - the region was identified in onBeginDrag
if ( TransformProps - > TransformMode = = EQuickTransformerMode : : AxisRotation )
{
ComputeUpdate_Rotate ( ) ;
}
else
{
ComputeUpdate_Translate ( ) ;
}
}
if ( DeformationStrategy = = EGroupTopologyDeformationStrategy : : Laplacian )
{
2020-04-27 12:58:07 -04:00
bool bIsWorking = LaplacianDeformer - > IsTaskInFlight ( ) ;
2019-12-19 18:07:47 -05:00
if ( ! bIsWorking )
{
// Sync update if we have new results.
2020-04-27 12:58:07 -04:00
if ( LaplacianDeformer - > bVertexPositionsNeedSync )
2019-12-19 18:07:47 -05:00
{
//Update the mesh with the provided solutions.
2020-04-27 12:58:07 -04:00
LaplacianDeformer - > ExportDeformedPositions ( DynamicMeshComponent - > GetMesh ( ) ) ;
2019-12-19 18:07:47 -05:00
2020-04-27 12:58:07 -04:00
LaplacianDeformer - > bVertexPositionsNeedSync = false ;
2019-12-19 18:07:47 -05:00
//Re-sync mesh, and flag the spatial data struct & topology for re-evaluation
2020-01-27 20:11:15 -05:00
DynamicMeshComponent - > FastNotifyPositionsUpdated ( true , false , false ) ;
2019-12-19 18:07:47 -05:00
GetToolManager ( ) - > PostInvalidation ( ) ;
bSpatialDirty = true ;
TopoSelector . Invalidate ( true , false ) ;
}
// emit end change if we are done with the drag
2020-04-27 12:58:07 -04:00
if ( ! LaplacianDeformer - > bDeformerNeedsToRun & & ! bInDrag )
2019-12-19 18:07:47 -05:00
{
EndChange ( ) ;
}
// Not working but we have more work for it to do..
2020-04-27 12:58:07 -04:00
if ( LaplacianDeformer - > bDeformerNeedsToRun )
2019-12-19 18:07:47 -05:00
{
FRichCurve * Curve = NULL ;
/**
// How to add a deformation curve
const bool bApplyAttenuationCurve = TransformProps - > bApplyAttenuationCurve
if ( bApplyAttenuationCurve )
{
Curve = TransformProps - > WeightAttenuationCurve . GetRichCurve ( ) ;
}
*/
2020-06-23 18:40:00 -04:00
const ELaplacianWeightScheme LaplacianWeightScheme =
ConvertToLaplacianWeightScheme ( TransformProps - > SelectedWeightScheme ) ;
2020-04-27 12:58:07 -04:00
LaplacianDeformer - > UpdateAndLaunchdWorker ( LaplacianWeightScheme , Curve ) ;
2019-12-19 18:07:47 -05:00
}
}
}
}
void UDeformMeshPolygonsTool : : ComputeUpdate_Rotate ( )
{
2020-06-23 18:40:00 -04:00
const bool bIsLaplacian = ( DeformationStrategy = = EGroupTopologyDeformationStrategy : : Laplacian ) ;
2020-04-27 12:58:07 -04:00
FGroupTopologyDeformer & SelectedDeformer = ( bIsLaplacian ) ? * LaplacianDeformer : LinearDeformer ;
2019-12-19 18:07:47 -05:00
2020-06-23 18:40:00 -04:00
FDynamicMesh3 * Mesh = DynamicMeshComponent - > GetMesh ( ) ;
2021-10-28 11:25:57 -04:00
FVector NewHitPosWorld ;
2019-12-19 18:07:47 -05:00
FVector3d SnappedPoint ;
if ( QuickAxisRotator . UpdateSnap ( FRay3d ( UpdateRay ) , SnappedPoint ) )
{
NewHitPosWorld = ( FVector ) SnappedPoint ;
}
else
{
return ;
}
// check if we are on back-facing part of rotation in which case we ignore...
FVector3d SphereCenter = QuickAxisRotator . GetActiveWorldFrame ( ) . Origin ;
if ( QuickAxisRotator . HaveActiveSnapRotation ( ) & & QuickAxisRotator . GetHaveLockedToAxis ( ) = = false )
{
FVector3d ToSnapPointVec = ( SnappedPoint - SphereCenter ) ;
2020-06-23 18:40:00 -04:00
FVector3d ToEyeVec = ( SnappedPoint - ( FVector3d ) CameraState . Position ) ;
2019-12-19 18:07:47 -05:00
if ( ToSnapPointVec . Dot ( ToEyeVec ) > 0 )
{
return ;
}
}
// if we haven't snapped to a rotation we can exit
if ( QuickAxisRotator . HaveActiveSnapRotation ( ) = = false )
{
QuickAxisRotator . ClearAxisLock ( ) ;
SelectedDeformer . ClearSolution ( Mesh ) ;
//TODO: This is unseemly here, need to potentially defer this so that it's handled the same way as laplacian. Placeholder for now.
if ( DeformationStrategy = = EGroupTopologyDeformationStrategy : : Linear )
{
2020-03-05 18:07:34 -05:00
DynamicMeshComponent - > FastNotifyPositionsUpdated ( true ) ;
2019-12-19 18:07:47 -05:00
GetToolManager ( ) - > PostInvalidation ( ) ;
}
bUpdatePending = false ;
return ;
}
// ok we have an axis...
if ( QuickAxisRotator . GetHaveLockedToAxis ( ) = = false )
{
QuickAxisRotator . SetAxisLock ( ) ;
RotationStartPointWorld = SnappedPoint ;
2020-06-23 18:40:00 -04:00
RotationStartFrame = QuickAxisRotator . GetActiveRotationFrame ( ) ;
2019-12-19 18:07:47 -05:00
}
FVector2d RotateStartVec = RotationStartFrame . ToPlaneUV ( RotationStartPointWorld , 2 ) ;
2021-03-17 19:32:44 -04:00
UE : : Geometry : : Normalize ( RotateStartVec ) ;
2021-03-30 21:25:22 -04:00
FVector2d RotateToVec = RotationStartFrame . ToPlaneUV ( ( FVector3d ) NewHitPosWorld , 2 ) ;
2021-03-17 19:32:44 -04:00
UE : : Geometry : : Normalize ( RotateToVec ) ;
double AngleRad = UE : : Geometry : : SignedAngleR ( RotateStartVec , RotateToVec ) ;
2021-06-11 22:42:32 -04:00
FQuaterniond Rotation ( WorldTransform . InverseTransformVectorNoScale ( RotationStartFrame . Z ( ) ) , AngleRad , false ) ;
FVector3d LocalOrigin = WorldTransform . InverseTransformPosition ( RotationStartFrame . Origin ) ;
2019-12-19 18:07:47 -05:00
// Linear Deformer: Update Mesh the rotation,
// Laplacian Deformer: Update handles constraints with the rotation and set bDeformerNeedsToRun = true;.
2020-06-23 18:40:00 -04:00
SelectedDeformer . UpdateSolution ( Mesh , [ this , LocalOrigin , Rotation ] ( FDynamicMesh3 * TargetMesh , int VertIdx ) {
2019-12-19 18:07:47 -05:00
FVector3d V = TargetMesh - > GetVertex ( VertIdx ) ;
V - = LocalOrigin ;
V = Rotation * V ;
V + = LocalOrigin ;
return V ;
} ) ;
//TODO: This is unseemly here, need to potentially defer this so that it's handled the same way as laplacian. Placeholder for now.
if ( ! bIsLaplacian )
{
2020-03-05 18:07:34 -05:00
DynamicMeshComponent - > FastNotifyPositionsUpdated ( true ) ;
2019-12-19 18:07:47 -05:00
GetToolManager ( ) - > PostInvalidation ( ) ;
}
bUpdatePending = false ;
}
void UDeformMeshPolygonsTool : : ComputeUpdate_Translate ( )
{
2020-06-23 18:40:00 -04:00
const bool bIsLaplacian = ( DeformationStrategy = = EGroupTopologyDeformationStrategy : : Laplacian ) ;
2020-04-27 12:58:07 -04:00
FGroupTopologyDeformer & SelectedDeformer = ( bIsLaplacian ) ? * LaplacianDeformer : LinearDeformer ;
2019-12-19 18:07:47 -05:00
TFunction < FVector3d ( const FVector3d & ) > PointConstraintFunc = nullptr ;
2021-07-29 18:40:19 -04:00
if ( GetToolManager ( ) - > GetContextQueriesAPI ( ) - > GetCurrentCoordinateSystem ( ) = = EToolContextCoordinateSystem : : World )
2019-12-19 18:07:47 -05:00
{
2021-07-29 18:40:19 -04:00
// We currently don't support grid snapping in local mode
2020-06-23 18:40:00 -04:00
PointConstraintFunc = [ & ] ( const FVector3d & Pos ) {
2019-12-19 18:07:47 -05:00
FVector3d GridSnapPos ;
return ToolSceneQueriesUtil : : FindWorldGridSnapPoint ( this , Pos , GridSnapPos ) ? GridSnapPos : Pos ;
} ;
}
2021-10-28 11:25:57 -04:00
FVector NewHitPosWorld ;
2019-12-19 18:07:47 -05:00
FVector3d SnappedPoint ;
if ( QuickAxisTranslater . UpdateSnap ( FRay3d ( UpdateRay ) , SnappedPoint , PointConstraintFunc ) )
{
NewHitPosWorld = ( FVector ) SnappedPoint ;
}
else
{
return ;
}
2021-06-11 22:42:32 -04:00
FVector3d NewBrushPosLocal = WorldTransform . InverseTransformPosition ( NewHitPosWorld ) ;
FVector3d NewMoveDelta = NewBrushPosLocal - ( FVector3d ) StartBrushPosLocal ;
2019-12-19 18:07:47 -05:00
FDynamicMesh3 * Mesh = DynamicMeshComponent - > GetMesh ( ) ;
if ( LastMoveDelta . SquaredLength ( ) > 0. )
{
if ( NewMoveDelta . SquaredLength ( ) > 0. )
{
// Linear Deformer: Update Mesh with the translation,
// Laplacian Deformer: Update handles constraints and set bDeformerNeedsToRun = true;.
2020-06-23 18:40:00 -04:00
SelectedDeformer . UpdateSolution ( Mesh , [ this , NewMoveDelta ] ( FDynamicMesh3 * TargetMesh , int VertIdx ) {
2019-12-19 18:07:47 -05:00
return TargetMesh - > GetVertex ( VertIdx ) + NewMoveDelta ;
} ) ;
}
else
{
// Reset mesh to initial positions.
SelectedDeformer . ClearSolution ( Mesh ) ;
}
//TODO: This is unseemly here, need to potentially defer this so that it's handled the same way as laplacian. Placeholder for now.
if ( ! bIsLaplacian )
{
2020-03-05 18:07:34 -05:00
DynamicMeshComponent - > FastNotifyPositionsUpdated ( true ) ;
2019-12-19 18:07:47 -05:00
GetToolManager ( ) - > PostInvalidation ( ) ;
}
}
2020-06-23 18:40:00 -04:00
LastMoveDelta = NewMoveDelta ;
2019-12-19 18:07:47 -05:00
LastBrushPosLocal = NewBrushPosLocal ;
bUpdatePending = false ;
}
2020-04-18 18:42:59 -04:00
void UDeformMeshPolygonsTool : : OnTick ( float DeltaTime )
2019-12-19 18:07:47 -05:00
{
2020-04-27 12:58:07 -04:00
LaplacianDeformer - > HandleWeights = TransformProps - > HandleWeight ;
LaplacianDeformer - > bPostfixHandles = TransformProps - > bPostFixHandles ;
2019-12-19 18:07:47 -05:00
}
void UDeformMeshPolygonsTool : : PrecomputeTopology ( )
{
FDynamicMesh3 * Mesh = DynamicMeshComponent - > GetMesh ( ) ;
2020-06-23 18:40:00 -04:00
Topology = FGroupTopology ( Mesh , true ) ;
2019-12-19 18:07:47 -05:00
LinearDeformer . Initialize ( Mesh , & Topology ) ;
2020-04-27 12:58:07 -04:00
LaplacianDeformer - > Initialize ( Mesh , & Topology ) ;
2019-12-19 18:07:47 -05:00
// Make the Constraint Buffer, zero weights, but current pos
2020-04-27 12:58:07 -04:00
LaplacianDeformer - > InitializeConstraintBuffer ( ) ;
2019-12-19 18:07:47 -05:00
}
void UDeformMeshPolygonsTool : : Render ( IToolsContextRenderAPI * RenderAPI )
{
ComputeUpdate ( ) ;
2020-06-23 18:40:00 -04:00
2019-12-19 18:07:47 -05:00
GetToolManager ( ) - > GetContextQueriesAPI ( ) - > GetCurrentViewState ( CameraState ) ;
GetActiveQuickTransformer ( ) - > UpdateCameraState ( CameraState ) ;
DynamicMeshComponent - > bExplicitShowWireframe = TransformProps - > bShowWireframe ;
2020-06-23 18:40:00 -04:00
FDynamicMesh3 * TargetMesh = DynamicMeshComponent - > GetMesh ( ) ;
2019-12-19 18:07:47 -05:00
PolyEdgesRenderer . BeginFrame ( RenderAPI , CameraState ) ;
2021-06-11 22:42:32 -04:00
PolyEdgesRenderer . SetTransform ( ( FTransform ) WorldTransform ) ;
2019-12-19 18:07:47 -05:00
for ( FGroupTopology : : FGroupEdge & Edge : Topology . Edges )
{
FVector3d A , B ;
for ( int eid : Edge . Span . Edges )
{
TargetMesh - > GetEdgeV ( eid , A , B ) ;
PolyEdgesRenderer . DrawLine ( A , B ) ;
}
}
PolyEdgesRenderer . EndFrame ( ) ;
HilightRenderer . BeginFrame ( RenderAPI , CameraState ) ;
2021-06-11 22:42:32 -04:00
HilightRenderer . SetTransform ( ( FTransform ) WorldTransform ) ;
2019-12-19 18:07:47 -05:00
# ifdef DEBUG_ROI_WEIGHTS
FDynamicMesh3 * MeshPtr = DynamicMeshComponent - > GetMesh ( ) ;
for ( int32 VertexID : DynamicMeshComponent - > GetMesh ( ) - > VertexIndicesItr ( ) )
{
float Color = 1.f - SrcMeshConstraintBuffer [ VertexID ] . Weight ;
HilightRenderer . DrawPoint ( MeshPtr - > GetVertex ( VertexID ) , FLinearColor ( Color , Color , Color , 1.f ) , 8 , true ) ;
}
# endif
# ifdef DEBUG_ROI_HANDLES
2020-06-23 18:40:00 -04:00
const FLinearColor FOOF { 1.f , 0.f , 1.f , 1.f } ;
2019-12-19 18:07:47 -05:00
for ( int VertIdx : HandleVertices )
{
HilightRenderer . DrawViewFacingCircle ( TargetMesh - > GetVertex ( VertIdx ) , 0.8f , 8 , FOOF , 3 , false ) ;
}
2020-06-23 18:40:00 -04:00
# endif
2019-12-19 18:07:47 -05:00
# ifdef DEBUG_ROI_TRIANGLES
2020-06-23 18:40:00 -04:00
const FLinearColor Whiteish { 0.67f , 0.67f , 0.67f , 1.f } ;
2019-12-19 18:07:47 -05:00
for ( int32 i = 0 ; i < SubsetIDBuffer . Num ( ) ; i + = 3 )
{
FVector3d A = TargetMesh - > GetVertex ( SubsetIDBuffer [ i ] ) ;
FVector3d B = TargetMesh - > GetVertex ( SubsetIDBuffer [ i + 1 ] ) ;
FVector3d C = TargetMesh - > GetVertex ( SubsetIDBuffer [ i + 2 ] ) ;
HilightRenderer . DrawLine ( A , B , Whiteish , 2.7f , true ) ;
HilightRenderer . DrawLine ( B , C , Whiteish , 2.7f , true ) ;
HilightRenderer . DrawLine ( C , A , Whiteish , 2.7f , true ) ;
}
# endif
TopoSelector . VisualAngleSnapThreshold = this - > VisualAngleSnapThreshold ;
TopoSelector . DrawSelection ( HilightSelection , & HilightRenderer , & CameraState ) ;
HilightRenderer . EndFrame ( ) ;
if ( bInDrag )
{
GetActiveQuickTransformer ( ) - > Render ( RenderAPI ) ;
}
else
{
GetActiveQuickTransformer ( ) - > PreviewRender ( RenderAPI ) ;
}
}
2020-06-23 18:40:00 -04:00
void UDeformMeshPolygonsTool : : OnPropertyModified ( UObject * PropertySet , FProperty * Property ) { }
2019-12-19 18:07:47 -05:00
2020-09-01 14:07:48 -04:00
FGroupTopologySelector : : FSelectionSettings UDeformMeshPolygonsTool : : GetTopoSelectorSettings ( )
{
FGroupTopologySelector : : FSelectionSettings TopoSelectorSettings ;
TopoSelectorSettings . bEnableFaceHits = TransformProps - > bSelectFaces ;
TopoSelectorSettings . bEnableEdgeHits = TransformProps - > bSelectEdges ;
TopoSelectorSettings . bEnableCornerHits = TransformProps - > bSelectVertices ;
return TopoSelectorSettings ;
}
2019-12-19 18:07:47 -05:00
//
// Change Tracking
//
void UDeformMeshPolygonsTool : : BeginChange ( )
{
const bool bIsLaplacian = ( DeformationStrategy = = EGroupTopologyDeformationStrategy : : Laplacian ) ;
2020-04-27 12:58:07 -04:00
if ( ! bIsLaplacian | | LaplacianDeformer - > IsDone ( ) )
2019-12-19 18:07:47 -05:00
{
if ( ActiveVertexChange = = nullptr )
{
2020-06-23 18:40:00 -04:00
ActiveVertexChange = new FMeshVertexChangeBuilder ( EMeshVertexChangeComponents : : VertexPositions |
EMeshVertexChangeComponents : : OverlayNormals ) ;
2019-12-19 18:07:47 -05:00
UpdateChangeFromROI ( false ) ;
}
}
}
void UDeformMeshPolygonsTool : : EndChange ( )
{
if ( ActiveVertexChange ! = nullptr )
{
UpdateChangeFromROI ( true ) ;
2020-06-23 18:40:00 -04:00
GetToolManager ( ) - > EmitObjectChange ( DynamicMeshComponent , MoveTemp ( ActiveVertexChange - > Change ) ,
LOCTEXT ( " PolyMeshDeformationChange " , " PolyMesh Edit " ) ) ;
2019-12-19 18:07:47 -05:00
}
delete ActiveVertexChange ;
ActiveVertexChange = nullptr ;
}
# undef LOCTEXT_NAMESPACE