2022-05-30 11:00:50 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
# ifdef NEW_DIRECTLINK_PLUGIN
# include "DatasmithMaxDirectLink.h"
# include "DatasmithSceneFactory.h"
# include "DatasmithMesh.h"
# include "DatasmithUtils.h"
# include "DatasmithMaxClassIDs.h"
# include "DatasmithMaxMeshExporter.h"
# include "DatasmithMaxSceneExporter.h"
# include "DatasmithMaxAttributes.h"
2024-03-27 03:26:43 -04:00
# include "DatasmithMaxSceneHelper.h"
2022-05-30 11:00:50 -04:00
# include "DatasmithMaxLogger.h"
# include "Logging/LogMacros.h"
# include "Windows/AllowWindowsPlatformTypes.h"
MAX_INCLUDES_START
# include "max.h"
# include "modstack.h"
# include "iparamb2.h"
# include "MeshNormalSpec.h"
MAX_INCLUDES_END
namespace DatasmithMaxDirectLink
{
namespace GeomUtils
{
class FNullView : public View
{
public :
FNullView ( )
{
worldToView . IdentityMatrix ( ) ; screenW = 640.0f ; screenH = 480.0f ;
}
virtual Point2 ViewToScreen ( Point3 p ) override
{
return Point2 ( p . x , p . y ) ;
}
} ;
2022-10-19 12:56:37 -04:00
Object * GetBaseObject ( INode * Node , TimeValue Time ) ;
int SetObjectParamValue ( TimeValue CurrentTime , Object * Obj , const FString & ParamName , int DesiredValue ) ;
2022-05-30 11:00:50 -04:00
FRenderMeshForConversion GetMeshForGeomObject ( TimeValue CurrentTime , INode * Node , const FTransform & Pivot )
{
// todo: baseline exporter uses GetBaseObject which takes result of EvalWorldState
// and searched down DerivedObject pipeline(by taking GetObjRef)
// This is STRANGE as EvalWorldState shouldn't return DerivedObject in the first place(it should return result of pipeline evaluation)
2022-10-19 12:56:37 -04:00
BOOL bNeedsDelete = false ;
2022-05-30 11:00:50 -04:00
Interval ValidityInterval ;
ValidityInterval . SetInfinite ( ) ;
2022-10-19 12:56:37 -04:00
TriObject * ObjectToDelete = nullptr ;
Mesh * RenderMesh = nullptr ;
{
Object * Obj = GetBaseObject ( Node , CurrentTime ) ;
// Read validity interval before changing display settings
// Else VRay will somehow react to a call to ObjectValidity and change RenderMesh contents to what it was originally.
// E.g. display was set 'Box', we change display to 'Mesh' here before calling GetRenderMesh to retrieve actual mesh later(rather than a simple box)
// but if ObjectValidity is called after GetRenderMesh that RenderMesh will become box again!
Interval ObjectValidity = Obj - > ObjectValidity ( CurrentTime ) ;
const Class_ID & ObjectClassID = Obj - > ClassID ( ) ;
const FString VRayProxyParamName ( TEXT ( " display " ) ) ;
const FString BodyObjectViewportMeshParamName ( TEXT ( " RenderViewportMeshRA " ) ) ;
int PreviousMeshDisplayValue = 0 ;
const int VRAY_PROXY_DISPLAY_AS_MESH = 4 ; // Value to set on a VRay Mesh Proxy to get the mesh
if ( ObjectClassID = = VRAYPROXY_CLASS_ID )
{
// Need the high resolution render mesh associated with the VRay Mesh Proxy for the export
PreviousMeshDisplayValue = SetObjectParamValue ( CurrentTime , Obj , VRayProxyParamName , VRAY_PROXY_DISPLAY_AS_MESH ) ;
}
else if ( ObjectClassID = = BODYOBJECT_CLASS_ID )
{
// Need to make sure we are using the viewport mesh on BodyObject, otherwise the RenderMesh gives a tessellated low resolution mesh.
PreviousMeshDisplayValue = SetObjectParamValue ( CurrentTime , Obj , BodyObjectViewportMeshParamName , 1 ) ;
}
GeomObject * GeomObj = static_cast < GeomObject * > ( Obj ) ;
if ( GeomObj )
{
if ( GeomObj - > CanConvertToType ( Class_ID ( TRIOBJ_CLASS_ID , 0 ) ) )
{
// GeomObj->GetRenderMesh for some objects types returns low quality mesh no matter settings
// Example: a body cutter object, its GetRenderMesh gives away a coarse mesh even when its viewport settings are set to Fine(and it's displayed Fine in viewport)
// Looks like TriObject should be used in the first place as a source of best quality mesh
TriObject * TriObj = static_cast < TriObject * > ( GeomObj - > ConvertToType ( CurrentTime , Class_ID ( TRIOBJ_CLASS_ID , 0 ) ) ) ;
if ( TriObj ! = GeomObj )
{
// In case tri object is a different object from node's GeomObj then use it to get mesh
RenderMesh = & ( TriObj - > mesh ) ;
bNeedsDelete = false ; // Mesh doesn't need to be deleted - TriObject does
check ( TriObj ! = GeomObj ) ; // Extra check that we won't delete live object
ObjectToDelete = TriObj ; // When converted tri object is different from th eobject itself it needs to be disposed
}
}
if ( ! RenderMesh ) // Fallback to GetRenderMesh if TriObject fails
{
FNullView View ;
RenderMesh = GeomObj - > GetRenderMesh ( CurrentTime , Node , View , bNeedsDelete ) ;
}
}
// Restore display state if different from mesh display
if ( ObjectClassID = = VRAYPROXY_CLASS_ID & & PreviousMeshDisplayValue ! = VRAY_PROXY_DISPLAY_AS_MESH )
{
SetObjectParamValue ( CurrentTime , Obj , VRayProxyParamName , PreviousMeshDisplayValue ) ;
}
else if ( ObjectClassID = = BODYOBJECT_CLASS_ID & & PreviousMeshDisplayValue ! = 1 )
{
SetObjectParamValue ( CurrentTime , Obj , BodyObjectViewportMeshParamName , PreviousMeshDisplayValue ) ;
}
if ( RenderMesh )
{
ValidityInterval & = ObjectValidity ; // Update validity only when actual mesh is returned
}
}
FRenderMeshForConversion Result ( Node , RenderMesh , bNeedsDelete , ObjectToDelete ) ;
2022-05-30 11:00:50 -04:00
Result . SetValidityInterval ( ValidityInterval ) ;
Result . SetPivot ( Pivot ) ;
return MoveTemp ( Result ) ;
}
/** Convert Max to UE coordinates, handle scene master unit
* @ param MaxTransform source transform
* @ param UnitMultiplier Master scene unit
*/
FTransform FTransformFromMatrix3 ( const Matrix3 & MaxTransform , float UnitMultiplier )
{
FVector Translation ;
FQuat Rotation ;
FVector Scale ;
FDatasmithMaxSceneExporter : : MaxToUnrealCoordinates ( MaxTransform , Translation , Rotation , Scale , UnitMultiplier ) ;
return FTransform ( Rotation , Translation , Scale ) ;
}
INode * GetCollisionNode ( ISceneTracker & SceneTracker , INode * Node , const FDatasmithMaxStaticMeshAttributes * DatasmithAttributes , bool & bOutFromDatasmithAttribute )
{
if ( DatasmithAttributes )
{
INode * ModifierSpecifiedCustomCollisionNode = DatasmithAttributes - > GetCustomCollisonNode ( ) ;
if ( ModifierSpecifiedCustomCollisionNode )
{
bOutFromDatasmithAttribute = true ;
return ModifierSpecifiedCustomCollisionNode ;
}
}
FString OriginalName = Node - > GetName ( ) ;
2024-03-27 03:26:43 -04:00
for ( const FString & CollisionNodePrefix : FDatasmithMaxSceneHelper : : CollisionNodesPrefixes )
2022-05-30 11:00:50 -04:00
{
if ( FNodeTracker * CollisionNode = SceneTracker . GetNodeTrackerByNodeName ( * ( CollisionNodePrefix + TEXT ( " _ " ) + OriginalName ) ) )
{
bOutFromDatasmithAttribute = false ;
return CollisionNode - > Node ;
}
}
return nullptr ;
}
FRenderMeshForConversion GetMeshForCollision ( TimeValue CurrentTime , ISceneTracker & SceneTracker , INode * Node , bool bBakePivot )
{
// source: FDatasmithMaxMeshExporter::ExportMesh
FDatasmithConverter Converter ;
bool bIsCollisionFromDatasmithAttributes ;
TOptional < FDatasmithMaxStaticMeshAttributes > DatasmithAttributes = FDatasmithMaxStaticMeshAttributes : : ExtractStaticMeshAttributes ( Node ) ;
INode * CollisionNode = GetCollisionNode ( SceneTracker , Node , DatasmithAttributes ? & DatasmithAttributes . GetValue ( ) : nullptr , bIsCollisionFromDatasmithAttributes ) ;
FTransform CollisionPivot ;
if ( CollisionNode )
{
FTransform ColliderPivot = FDatasmithMaxSceneExporter : : GetPivotTransform ( CollisionNode , Converter . UnitToCentimeter ) ;
if ( bIsCollisionFromDatasmithAttributes )
{
if ( ! bBakePivot )
{
FTransform RealPivot = FDatasmithMaxSceneExporter : : GetPivotTransform ( Node , Converter . UnitToCentimeter ) ;
ColliderPivot = ColliderPivot * RealPivot . Inverse ( ) ;
}
CollisionPivot = ColliderPivot ;
}
else
{
FTransform FTransformFromMatrix3 ( const Matrix3 & MaxTransform , float UnitMultiplier ) ; // todo: move to header and rename(F is for class!)
FTransform NodeWTM = FTransformFromMatrix3 ( Node - > GetNodeTM ( CurrentTime ) , Converter . UnitToCentimeter ) ;
FTransform ColliderNodeWTM = FTransformFromMatrix3 ( CollisionNode - > GetNodeTM ( CurrentTime ) , Converter . UnitToCentimeter ) ;
// if object-offset has been baked into the mesh data, we want collision mesh data in the mesh's node space
// MeshVert_Node = RealPivot * max_vert_data
// MeshVert_world = NodeWTM * MeshVert_Node
// Collision mesh vertices in world space:
// CollVert_node = ColliderPivot * CollVert_obj
// CollVert_world = ColliderNodeWTM * CollVert_node
// CollVert_mesh = NodeWTM-1 * CollVert_world
FTransform BakedTransform = ColliderPivot * ColliderNodeWTM * NodeWTM . Inverse ( ) ;
if ( ! bBakePivot )
{
// if object-offset has not been baked, we want collision mesh data in the mesh's object space
FTransform RealPivot = FDatasmithMaxSceneExporter : : GetPivotTransform ( Node , Converter . UnitToCentimeter ) ;
BakedTransform = BakedTransform * RealPivot . Inverse ( ) ;
}
CollisionPivot = BakedTransform ;
}
}
if ( ! CollisionNode )
{
return FRenderMeshForConversion ( ) ;
}
return GetMeshForGeomObject ( CurrentTime , CollisionNode , CollisionPivot ) ;
}
// todo: copied from baseline plugin(it has dependencies on converters that are not static in FDatasmithMaxMeshExporter)
void FillDatasmithMeshFromMaxMesh ( FDatasmithMesh & DatasmithMesh , Mesh & MaxMesh , INode * ExportedNode , bool bForceSingleMat , TSet < uint16 > & SupportedChannels , TMap < int32 , int32 > & UVChannelsMap , FTransform Pivot )
{
FDatasmithConverter Converter ;
const int NumFaces = MaxMesh . getNumFaces ( ) ;
const int NumVerts = MaxMesh . getNumVerts ( ) ;
DatasmithMesh . SetVerticesCount ( NumVerts ) ;
DatasmithMesh . SetFacesCount ( NumFaces ) ;
// Vertices
for ( int i = 0 ; i < NumVerts ; i + + )
{
Point3 Point = MaxMesh . getVert ( i ) ;
FVector Vertex = Converter . toDatasmithVector ( Point ) ;
Vertex = Pivot . TransformPosition ( Vertex ) ; // Bake object-offset in the mesh data when possible
DatasmithMesh . SetVertex ( i , Vertex . X , Vertex . Y , Vertex . Z ) ;
}
// Vertex Colors
if ( MaxMesh . curVCChan = = 0 & & MaxMesh . numCVerts > 0 )
{
// Default vertex color channel
for ( int32 i = 0 ; i < NumFaces ; i + + )
{
TVFace & Face = MaxMesh . vcFace [ i ] ;
DatasmithMesh . SetVertexColor ( i * 3 , Converter . toDatasmithColor ( MaxMesh . vertCol [ Face . t [ 0 ] ] ) ) ;
DatasmithMesh . SetVertexColor ( i * 3 + 1 , Converter . toDatasmithColor ( MaxMesh . vertCol [ Face . t [ 1 ] ] ) ) ;
DatasmithMesh . SetVertexColor ( i * 3 + 2 , Converter . toDatasmithColor ( MaxMesh . vertCol [ Face . t [ 2 ] ] ) ) ;
}
}
// UVs
TMap < uint32 , int32 > HashToChannel ;
bool bIsFirstUVChannelValid = true ;
for ( int32 i = 1 ; i < = MaxMesh . getNumMaps ( ) ; + + i )
{
if ( MaxMesh . mapSupport ( i ) = = BOOL ( true ) & & MaxMesh . getNumMapVerts ( i ) > 0 )
{
DatasmithMesh . AddUVChannel ( ) ;
const int32 UVChannelIndex = DatasmithMesh . GetUVChannelsCount ( ) - 1 ;
const int32 UVsCount = MaxMesh . getNumMapVerts ( i ) ;
DatasmithMesh . SetUVCount ( UVChannelIndex , UVsCount ) ;
UVVert * Vertex = MaxMesh . mapVerts ( i ) ;
for ( int32 j = 0 ; j < UVsCount ; + + j )
{
const UVVert & MaxUV = Vertex [ j ] ;
DatasmithMesh . SetUV ( UVChannelIndex , j , MaxUV . x , 1.f - MaxUV . y ) ;
}
TVFace * Faces = MaxMesh . mapFaces ( i ) ;
for ( int32 j = 0 ; j < MaxMesh . getNumFaces ( ) ; + + j )
{
DatasmithMesh . SetFaceUV ( j , UVChannelIndex , Faces [ j ] . t [ 0 ] , Faces [ j ] . t [ 1 ] , Faces [ j ] . t [ 2 ] ) ;
}
if ( UVChannelIndex = = 0 )
{
//Verifying that the UVs are properly unfolded, which is required to calculate the tangent in unreal.
bIsFirstUVChannelValid = FDatasmithMeshUtils : : IsUVChannelValid ( DatasmithMesh , UVChannelIndex ) ;
}
uint32 Hash = DatasmithMesh . GetHashForUVChannel ( UVChannelIndex ) ;
int32 * PointerToChannel = HashToChannel . Find ( Hash ) ;
if ( PointerToChannel )
{
// Remove the channel because there is another one that is identical
DatasmithMesh . RemoveUVChannel ( ) ;
// Map the user-specified UV Channel (in 3dsmax) to the actual UV channel that will be exported to Unreal
UVChannelsMap . Add ( i - 1 , * PointerToChannel ) ;
}
else
{
// Map the user-specified UV Channel (in 3dsmax) to the actual UV channel that will be exported to Unreal
UVChannelsMap . Add ( i - 1 , UVChannelIndex ) ;
HashToChannel . Add ( Hash , UVChannelIndex ) ;
}
}
}
if ( ! bIsFirstUVChannelValid )
{
2022-09-27 07:01:06 -04:00
LogWarning ( FString : : Printf ( TEXT ( " %s's UV channel #1 contains degenerated triangles, this can cause issues in Unreal. It is recommended to properly unfold and flatten exported UV data. " )
, static_cast < const TCHAR * > ( ExportedNode - > GetName ( ) ) ) ) ;
2022-05-30 11:00:50 -04:00
}
// Faces
for ( int i = 0 ; i < NumFaces ; i + + )
{
// Create polygons. Assign texture and texture UV indices.
// all faces of the cube have the same texture
Face & MaxFace = MaxMesh . faces [ i ] ;
int MaterialId = bForceSingleMat ? 0 : MaxFace . getMatID ( ) ;
SupportedChannels . Add ( MaterialId ) ;
//Max's channel UI is not zero-based, so we register an incremented ChannelID for better visual consistency after importing in Unreal.
DatasmithMesh . SetFace ( i , MaxFace . getVert ( 0 ) , MaxFace . getVert ( 1 ) , MaxFace . getVert ( 2 ) , MaterialId + 1 ) ;
DatasmithMesh . SetFaceSmoothingMask ( i , ( uint32 ) MaxFace . getSmGroup ( ) ) ;
}
//Normals
MaxMesh . SpecifyNormals ( ) ;
MeshNormalSpec * Normal = MaxMesh . GetSpecifiedNormals ( ) ;
Normal - > MakeNormalsExplicit ( false ) ;
Normal - > CheckNormals ( ) ;
Matrix3 RotationMatrix ;
RotationMatrix . IdentityMatrix ( ) ;
Quat ObjectOffsetRotation = ExportedNode - > GetObjOffsetRot ( ) ;
RotateMatrix ( RotationMatrix , ObjectOffsetRotation ) ;
Point3 Point ;
for ( int i = 0 ; i < NumFaces ; i + + )
{
Point = Normal - > GetNormal ( i , 0 ) . Normalize ( ) * RotationMatrix ;
FVector NormalVector = Converter . toDatasmithVector ( Point ) ;
DatasmithMesh . SetNormal ( i * 3 , NormalVector . X , NormalVector . Y , NormalVector . Z ) ;
Point = Normal - > GetNormal ( i , 1 ) . Normalize ( ) * RotationMatrix ;
NormalVector = Converter . toDatasmithVector ( Point ) ;
DatasmithMesh . SetNormal ( i * 3 + 1 , NormalVector . X , NormalVector . Y , NormalVector . Z ) ;
Point = Normal - > GetNormal ( i , 2 ) . Normalize ( ) * RotationMatrix ;
NormalVector = Converter . toDatasmithVector ( Point ) ;
DatasmithMesh . SetNormal ( i * 3 + 2 , NormalVector . X , NormalVector . Y , NormalVector . Z ) ;
}
}
void SetNormalForAFace ( FDatasmithMesh & DatasmithMesh , int32 Index , const FVector & NormalVector )
{
DatasmithMesh . SetNormal ( Index * 3 , NormalVector . X , NormalVector . Y , NormalVector . Z ) ;
DatasmithMesh . SetNormal ( Index * 3 + 1 , NormalVector . X , NormalVector . Y , NormalVector . Z ) ;
DatasmithMesh . SetNormal ( Index * 3 + 2 , NormalVector . X , NormalVector . Y , NormalVector . Z ) ;
}
bool FillDatasmithMeshFromBoundingBox ( TimeValue CurrentTime , FDatasmithMesh & DatasmithMesh , const GeomUtils : : FRenderMeshForConversion & MaxMesh )
{
if ( ! MaxMesh . GetNode ( ) )
{
return false ;
}
FDatasmithConverter Converter ;
if ( ! MaxMesh . IsValid ( ) )
{
return false ;
}
if ( MaxMesh . GetMesh ( ) - > getNumFaces ( ) = = 0 )
{
return false ;
}
if ( MaxMesh . GetMesh ( ) - > getBoundingBox ( ) . IsEmpty ( ) )
{
MaxMesh . GetMesh ( ) - > buildBoundingBox ( ) ;
}
Box3 BoundingBox = MaxMesh . GetMesh ( ) - > getBoundingBox ( ) ;
DatasmithMesh . SetVerticesCount ( 8 ) ;
DatasmithMesh . SetFacesCount ( 12 ) ;
for ( int32 i = 0 ; i < 8 ; i + + )
{
FVector Vertex = Converter . toDatasmithVector ( BoundingBox [ i ] ) ;
Vertex = MaxMesh . GetPivot ( ) . TransformPosition ( Vertex ) ;
DatasmithMesh . SetVertex ( i , Vertex . X , Vertex . Y , Vertex . Z ) ;
}
// Make a cube from the vertices
Matrix3 RotationMatrix ;
RotationMatrix . IdentityMatrix ( ) ;
Quat ObjectOffsetRotation = MaxMesh . GetNode ( ) - > GetObjOffsetRot ( ) ;
RotateMatrix ( RotationMatrix , ObjectOffsetRotation ) ;
Point3 Point ;
FVector NormalVector ;
// Points toward negative z (without a transform and in max coordinates)
DatasmithMesh . SetFace ( 0 , 1 , 0 , 2 ) ;
DatasmithMesh . SetFace ( 1 , 1 , 2 , 3 ) ;
Point = Point3 ( 0.f , 0.f , - 1.f ) * RotationMatrix ;
NormalVector = Converter . toDatasmithNormal ( Point ) ;
SetNormalForAFace ( DatasmithMesh , 0 , NormalVector ) ;
SetNormalForAFace ( DatasmithMesh , 1 , NormalVector ) ;
// Points toward positive y (without a transform and in max coordinates)
DatasmithMesh . SetFace ( 2 , 3 , 2 , 7 ) ;
DatasmithMesh . SetFace ( 3 , 2 , 6 , 7 ) ;
Point = Point3 ( 0.f , 1.f , 0.f ) * RotationMatrix ;
NormalVector = Converter . toDatasmithNormal ( Point ) ;
SetNormalForAFace ( DatasmithMesh , 2 , NormalVector ) ;
SetNormalForAFace ( DatasmithMesh , 3 , NormalVector ) ;
// Points toward positive x (without a transform and in max coordinates)
DatasmithMesh . SetFace ( 4 , 1 , 3 , 7 ) ;
DatasmithMesh . SetFace ( 5 , 5 , 1 , 7 ) ;
Point = Point3 ( 1.f , 0.f , 0.f ) * RotationMatrix ;
NormalVector = Converter . toDatasmithNormal ( Point ) ;
SetNormalForAFace ( DatasmithMesh , 4 , NormalVector ) ;
SetNormalForAFace ( DatasmithMesh , 5 , NormalVector ) ;
// Points toward negative x (without a transform and in max coordinates)
DatasmithMesh . SetFace ( 6 , 2 , 0 , 6 ) ;
DatasmithMesh . SetFace ( 7 , 0 , 4 , 6 ) ;
Point = Point3 ( - 1.f , 0.f , 0.f ) * RotationMatrix ;
NormalVector = Converter . toDatasmithNormal ( Point ) ;
SetNormalForAFace ( DatasmithMesh , 6 , NormalVector ) ;
SetNormalForAFace ( DatasmithMesh , 7 , NormalVector ) ;
// Points toward negative y (without a transform and in max coordinates)
DatasmithMesh . SetFace ( 8 , 0 , 1 , 5 ) ;
DatasmithMesh . SetFace ( 9 , 4 , 0 , 5 ) ;
Point = Point3 ( 0.f , - 1.f , 0.f ) * RotationMatrix ;
NormalVector = Converter . toDatasmithNormal ( Point ) ;
SetNormalForAFace ( DatasmithMesh , 8 , NormalVector ) ;
SetNormalForAFace ( DatasmithMesh , 9 , NormalVector ) ;
// Points toward positive z (without a transform and in max coordinates)
DatasmithMesh . SetFace ( 10 , 4 , 5 , 6 ) ;
DatasmithMesh . SetFace ( 11 , 6 , 5 , 7 ) ;
Point = Point3 ( 0.f , 0.f , 1.f ) * RotationMatrix ;
NormalVector = Converter . toDatasmithNormal ( Point ) ;
SetNormalForAFace ( DatasmithMesh , 10 , NormalVector ) ;
SetNormalForAFace ( DatasmithMesh , 11 , NormalVector ) ;
return true ;
}
bool CreateDatasmithMeshFromMaxMesh ( FDatasmithMesh & DatasmithMesh , const MeshConversionParams & Params , TSet < uint16 > & SupportedChannels , TMap < int32 , int32 > & UVChannelsMap )
{
bool bResult = false ;
if ( Params . RenderMesh . GetMesh ( ) - > getNumFaces ( ) )
{
// Copy mesh to clean it before filling Datasmith mesh from it
Mesh CachedMesh ;
CachedMesh . DeepCopy ( Params . RenderMesh . GetMesh ( ) , TOPO_CHANNEL | GEOM_CHANNEL | TEXMAP_CHANNEL | VERTCOLOR_CHANNEL ) ;
CachedMesh . DeleteIsoVerts ( ) ;
CachedMesh . RemoveDegenerateFaces ( ) ;
CachedMesh . RemoveIllegalFaces ( ) ;
// Need to invalidate/rebuild strips/edges after topology change(removing bad verts/faces)
CachedMesh . InvalidateStrips ( ) ;
CachedMesh . BuildStripsAndEdges ( ) ;
if ( CachedMesh . getNumFaces ( ) > 0 )
{
FillDatasmithMeshFromMaxMesh ( DatasmithMesh , CachedMesh , Params . Node , Params . bConsolidateMaterialIds , SupportedChannels , UVChannelsMap , Params . RenderMesh . GetPivot ( ) ) ;
bResult = true ; // Set to true, don't care what ExportToUObject does here - we need to move it to a thread anyway
}
CachedMesh . FreeAll ( ) ;
}
return bResult ;
}
Object * GetBaseObject ( INode * Node , TimeValue Time )
{
ObjectState ObjState = Node - > EvalWorldState ( Time ) ;
Object * Obj = ObjState . obj ;
if ( Obj )
{
SClass_ID SuperClassID ;
SuperClassID = Obj - > SuperClassID ( ) ;
while ( SuperClassID = = GEN_DERIVOB_CLASS_ID )
{
Obj = ( ( IDerivedObject * ) Obj ) - > GetObjRef ( ) ;
SuperClassID = Obj - > SuperClassID ( ) ;
}
}
return Obj ;
}
int SetObjectParamValue ( TimeValue CurrentTime , Object * Obj , const FString & ParamName , int DesiredValue )
{
bool bFoundDisplayValue = false ;
int PrevDisplayValue = DesiredValue ; // Display value to see mesh
int NumParamBlocks = Obj - > NumParamBlocks ( ) ;
for ( short BlockIndex = 0 ; BlockIndex < NumParamBlocks & & ! bFoundDisplayValue ; + + BlockIndex )
{
IParamBlock2 * ParamBlock2 = Obj - > GetParamBlockByID ( BlockIndex ) ;
ParamBlockDesc2 * ParamBlockDesc = ParamBlock2 - > GetDesc ( ) ;
for ( int ParamIndex = 0 ; ParamIndex < ParamBlockDesc - > count ; + + ParamIndex )
{
ParamDef & ParamDefinition = ParamBlockDesc - > paramdefs [ ParamIndex ] ;
if ( FCString : : Stricmp ( ParamDefinition . int_name , * ParamName ) = = 0 )
{
PrevDisplayValue = ParamBlock2 - > GetInt ( ParamDefinition . ID , CurrentTime ) ;
if ( PrevDisplayValue ! = DesiredValue )
{
ParamBlock2 - > SetValue ( ParamDefinition . ID , CurrentTime , DesiredValue ) ;
}
bFoundDisplayValue = true ;
break ;
}
}
ParamBlock2 - > ReleaseDesc ( ) ;
}
return PrevDisplayValue ;
}
}
}
namespace DatasmithMaxDirectLink
{
2023-04-24 01:16:02 -04:00
template < typename T >
static void UpdateMD5SimpleType ( FMD5 & MD5 , const T & Value )
{
static_assert ( TIsPODType < T > : : Value , " Simple type required " ) ;
MD5 . Update ( reinterpret_cast < const uint8 * > ( & Value ) , sizeof ( Value ) ) ;
}
template < typename T >
static void UpdateMD5Array ( FMD5 & MD5 , TArray < T > Value )
{
static_assert ( TIsPODType < T > : : Value , " This function requires POD array " ) ;
UpdateMD5SimpleType ( MD5 , Value . Num ( ) ) ;
if ( ! Value . IsEmpty ( ) )
{
MD5 . Update ( reinterpret_cast < const uint8 * > ( Value . GetData ( ) ) , Value . GetTypeSize ( ) * Value . Num ( ) ) ;
}
}
static void UpdateMD5 ( FMD5 & MD5 , const FDatasmithMesh & DatasmithMesh )
{
FMD5Hash MeshHash = DatasmithMesh . CalculateHash ( ) ;
MD5 . Update ( MeshHash . GetBytes ( ) , MeshHash . GetSize ( ) ) ;
}
FMD5Hash FDatasmithMeshConverter : : ComputeHash ( )
{
FMD5 MD5 ;
UpdateMD5 ( MD5 , RenderMesh ) ;
TArray < uint16 > SupportedChannelsArray = SupportedChannels . Array ( ) ;
SupportedChannelsArray . Sort ( ) ;
UpdateMD5Array ( MD5 , SupportedChannelsArray ) ;
UpdateMD5SimpleType ( MD5 , UVChannelsMap . Num ( ) ) ;
if ( ! UVChannelsMap . IsEmpty ( ) )
{
TArray < TPair < int32 , int32 > > UVChannelsMapArray = UVChannelsMap . Array ( ) ;
UVChannelsMapArray . Sort ( ) ;
for ( TPair < int32 , int32 > KVP : UVChannelsMapArray )
{
UpdateMD5SimpleType ( MD5 , KVP . Key ) ;
UpdateMD5SimpleType ( MD5 , KVP . Value ) ;
}
}
UpdateMD5SimpleType ( MD5 , SelectedLightmapUVChannel ) ;
UpdateMD5SimpleType ( MD5 , bHasCollision ) ;
if ( bHasCollision )
{
UpdateMD5 ( MD5 , CollisionMesh ) ;
}
FMD5Hash Hash ;
Hash . Set ( MD5 ) ;
return Hash ;
}
2022-05-30 11:00:50 -04:00
// todo: paralelize calls to ExportToUObject
2023-04-24 01:16:02 -04:00
bool ConvertMaxMeshToDatasmith ( TimeValue CurrentTime , FMeshConverterSource & MeshSource , FDatasmithMeshConverter & DatasmithMeshConverter )
2022-05-30 11:00:50 -04:00
{
TOptional < FDatasmithMaxStaticMeshAttributes > DatasmithAttributes = FDatasmithMaxStaticMeshAttributes : : ExtractStaticMeshAttributes ( MeshSource . Node ) ;
if ( DatasmithAttributes & & DatasmithAttributes - > GetExportMode ( ) = = EStaticMeshExportMode : : BoundingBox )
{
2023-04-24 01:16:02 -04:00
if ( GeomUtils : : FillDatasmithMeshFromBoundingBox ( CurrentTime , DatasmithMeshConverter . RenderMesh , MeshSource . RenderMesh ) )
2022-05-30 11:00:50 -04:00
{
return true ;
}
else
{
LogWarning ( FString ( TEXT ( " Invalid object: " ) ) + MeshSource . Node - > GetName ( ) ) ;
return false ;
}
}
MeshConversionParams RenderMeshParams = {
MeshSource . RenderMesh . GetNode ( ) ,
MeshSource . RenderMesh ,
MeshSource . bConsolidateMaterialIds
} ;
2023-04-24 01:16:02 -04:00
if ( ! GeomUtils : : CreateDatasmithMeshFromMaxMesh ( DatasmithMeshConverter . RenderMesh , RenderMeshParams , DatasmithMeshConverter . SupportedChannels , DatasmithMeshConverter . UVChannelsMap ) )
2022-05-30 11:00:50 -04:00
{
LogWarning ( FString ( TEXT ( " Invalid object: " ) ) + MeshSource . Node - > GetName ( ) ) ;
return false ;
}
// Mapping between the 3ds max channel and the exported mesh channel
if ( DatasmithAttributes )
{
2023-04-24 01:16:02 -04:00
DatasmithMeshConverter . SelectedLightmapUVChannel = DatasmithAttributes - > GetLightmapUVChannel ( ) ;
2022-05-30 11:00:50 -04:00
}
if ( MeshSource . CollisionMesh . IsValid ( ) )
{
TSet < uint16 > SupportedChannelsDummy ; // ignore map channels for collision mesh
TMap < int32 , int32 > UVChannelsMapDummy ;
MeshConversionParams CollisionParams = {
MeshSource . CollisionMesh . GetNode ( ) ,
MeshSource . CollisionMesh ,
true // Consolidate material ids into single mesh for collision
} ;
2023-04-24 01:16:02 -04:00
if ( GeomUtils : : CreateDatasmithMeshFromMaxMesh ( DatasmithMeshConverter . CollisionMesh , CollisionParams , SupportedChannelsDummy , UVChannelsMapDummy ) )
2022-05-30 11:00:50 -04:00
{
2023-04-24 01:16:02 -04:00
DatasmithMeshConverter . bHasCollision = true ;
2022-05-30 11:00:50 -04:00
}
}
return true ;
}
}
# include "Windows/HideWindowsPlatformTypes.h"
# endif // NEW_DIRECTLINK_PLUGIN