Code: Select all
/*=============================================================================
FJamesMesh.h: James Mesh Format Header.
Copyright 2016 Sebastian Kaufel. All Rights Reserved.
Derived of work copyrighted by Smirftsch & Dots.
Struct definitions found in 3ds2unr.
Revision history:
* Created by Sebastian Kaufel.
=============================================================================*/
/*-----------------------------------------------------------------------------
James mesh format definitions.
-----------------------------------------------------------------------------*/
enum EJSMeshTriType
{
// Triangle types. Mutually exclusive.
MTT_Normal = 0, // Normal one-sided.
MTT_NormalTwoSided = 1, // Normal but two-sided.
MTT_Translucent = 2, // Translucent two-sided.
MTT_Masked = 3, // Masked two-sided.
MTT_Modulate = 4, // Modulation blended two-sided.
#if ENGINE_VERSION==227
MTT_AlphaBlend = 5, // Alpha blended two-sided.
#endif
MTT_Placeholder = 8, // Placeholder triangle for positioning weapon. Invisible.
// Bit flags.
MTT_Unlit = 16, // Full brightness, no lighting.
MTT_Flat = 32, // Flat surface, don't do bMeshCurvy thing.
MTT_Environment = 64, // Environment mapped.
MTT_NoSmooth = 128, // No bilinear filtering on this poly's texture.
// Masks for more readable code.
MTT_TypeMask = 0x0f,
MTT_FlagMask = 0xf0,
};
/*-----------------------------------------------------------------------------
PolyFlags/JamesMeshTriType Conversation and Helper.
-----------------------------------------------------------------------------*/
inline DWORD FJamesMeshTriTypeToPolyFlags( BYTE Type, FOutputDevice* Error=GNull )
{
guard(FPolyFlagsToJamesTriType);
DWORD Result = 0;
// Base Type.
switch ( Type&MTT_TypeMask )
{
case MTT_Normal: Result=0; break;
case MTT_NormalTwoSided: Result=PF_TwoSided; break;
case MTT_Translucent: Result=PF_TwoSided|PF_Translucent; break;
case MTT_Masked: Result=PF_TwoSided|PF_Masked; break;
case MTT_Modulate: Result=PF_TwoSided|PF_Modulated; break;
case MTT_Placeholder: Result=PF_TwoSided|PF_Invisible; break;
#if ENGINE_VERSION==227
case MTT_AlphaBlend: Result=PF_TwoSided|PF_AlphaBlend; break;
#endif
default:
Error->Logf( TEXT("Unknown james mesh base tri type (%i)."), Type );
break;
}
// FX.
if ( Type&MTT_Unlit ) Result|=PF_Unlit;
if ( Type&MTT_Flat ) Result|=PF_Flat; // Code in ActorX indicates that this was used later for PF_AlphaTexture.
if ( Type&MTT_Environment ) Result|=PF_Environment;
if ( Type&MTT_NoSmooth ) Result|=PF_NoSmooth;
// No further checks requires, as the above code covers all bits.
return Result;
unguard;
}
inline BYTE FPolyFlagsToJamesMeshTriType( DWORD PolyFlags, FOutputDevice* Error=GNull )
{
guard(FPolyFlagsToJamesTriType);
BYTE Result = 0;
// Base Type.
DWORD TypeFlags = PolyFlags & (PF_TwoSided|PF_Modulated|PF_Translucent|PF_Masked|PF_Invisible);
if ( TypeFlags==0 )
Result = MTT_Normal;
else if ( TypeFlags==PF_TwoSided )
Result = MTT_NormalTwoSided;
else if ( TypeFlags==(PF_TwoSided|PF_Modulated) )
Result = MTT_Modulate;
else if ( TypeFlags==(PF_TwoSided|PF_Translucent) )
Result = MTT_Translucent;
else if ( TypeFlags==(PF_TwoSided|PF_Masked) )
Result = MTT_Masked;
else if ( TypeFlags==(PF_TwoSided|PF_Invisible) )
Result = MTT_Placeholder;
#if ENGINE_VERSION==227
else if ( TypeFlags==(PF_TwoSided|PF_AlphaBlend) )
Result = MTT_AlphaBlend;
#endif
else
Error->Logf( TEXT("Failed to determine base james mesh tri type (%i)."), PolyFlags );
// FX
if( PolyFlags & PF_Unlit )
Result |= MTT_Unlit;
if( PolyFlags & PF_Flat )
Result |= MTT_Flat;
if( PolyFlags & PF_Environment )
Result |= MTT_Environment;
if( PolyFlags & PF_NoSmooth )
Result |= MTT_NoSmooth;
if ( PolyFlags!=FJamesMeshTriTypeToPolyFlags(Result) )
Error->Logf( TEXT("Failed to match all PolyFlags to JamesMeshTriType (0x%08x:0x%08x)."), PolyFlags, FJamesMeshTriTypeToPolyFlags(Result) );
return Result;
unguard;
}
inline UBOOL FJSTriTypeTwoSided( BYTE Type, UBOOL InvisibleReturnValue=0, UBOOL UnknownReturnValue=1 )
{
guard(FJSTriTypeTwoSided);
switch ( Type&MTT_TypeMask )
{
case MTT_Normal:
return 0;
break;
case MTT_NormalTwoSided:
case MTT_Translucent:
case MTT_Masked:
case MTT_Modulate:
#if ENGINE_VERSION==227
case MTT_AlphaBlend:
#endif
return 1;
break;
case MTT_Placeholder:
return InvisibleReturnValue;
break;
default:
return UnknownReturnValue;
break;
}
unguard;
}
/*-----------------------------------------------------------------------------
Structures.
-----------------------------------------------------------------------------*/
// James MeshUV (Renamed FMeshUV).
struct FJSMeshUV
{
FJSMeshUV()
{}
FJSMeshUV( BYTE InU, BYTE InV )
: U(InU)
, V(InV)
{}
FJSMeshUV( FMeshUV InUV )
: U(InUV.U)
, V(InUV.V)
{}
// Operators.
UBOOL operator==( const FJSMeshUV& UV ) const
{
return U==UV.U && V==UV.V;
}
UBOOL operator!=( const FJSMeshUV& UV ) const
{
return U!=UV.U || V!=UV.V;
}
FJSMeshUV& operator=( const FMeshUV& Other )
{
this->U = Other.U;
this->V = Other.V;
return *this;
}
// Returns a string description.
FString String()
{
guard(FJSMeshUV::String);
return FString::Printf( TEXT("(U=%i,V=%i)"), U, V );
unguard;
}
friend FArchive &operator<<( FArchive& Ar, FJSMeshUV& M )
{
return Ar << M.U << M.V;
}
BYTE U;
BYTE V;
};
// Mesh triangle.
struct FJSMeshTri
{
FJSMeshTri()
{}
FJSMeshTri( _WORD iInVertex0, _WORD iInVertex1, _WORD iInVertex2, BYTE InType, FJSMeshUV InTex0, FJSMeshUV InTex1, FJSMeshUV InTex2, BYTE InTextureNum )
: Type(InType)
, TextureNum(InTextureNum)
, Color(0)
, Flags(0)
{
guard(FJSMeshTri::FJSMeshTri);
iVertex[0] = iInVertex0;
iVertex[1] = iInVertex1;
iVertex[2] = iInVertex2;
Tex[0] = InTex0;
Tex[1] = InTex1;
Tex[2] = InTex2;
unguard;
}
FJSMeshTri( _WORD iInVertex0, _WORD iInVertex1, _WORD iInVertex2, BYTE InType, BYTE InColor, FJSMeshUV InTex0, FJSMeshUV InTex1, FJSMeshUV InTex2, BYTE InTextureNum, BYTE InFlags )
: Type(InType)
, TextureNum(InTextureNum)
, Color(InColor)
, Flags(InFlags)
{
guard(FJSMeshTri::FJSMeshTri);
iVertex[0] = iInVertex0;
iVertex[1] = iInVertex1;
iVertex[2] = iInVertex2;
Tex[0] = InTex0;
Tex[1] = InTex1;
Tex[2] = InTex2;
unguard;
}
FJSMeshTri( FMeshTri& MeshTri, FOutputDevice* Error=GNull )
: Type(FPolyFlagsToJamesMeshTriType(MeshTri.PolyFlags,Error))
, TextureNum(MeshTri.TextureIndex)
, Color(0)
, Flags(0)
{
guard(FJSMeshTri::FJSMeshTri);
for ( INT i=0; i<3; i++ )
{
iVertex[i] = MeshTri.iVertex[i];
Tex[i] = MeshTri.Tex[i];
}
unguard;
}
// Operators.
UBOOL operator==( const FJSMeshTri& T ) const
{
return iVertex[0]==T.iVertex[0]
&& iVertex[1]==T.iVertex[1]
&& iVertex[2]==T.iVertex[2]
&& Type==T.Type
&& Color==T.Color
&& Tex[0]==T.Tex[0]
&& Tex[1]==T.Tex[1]
&& Tex[2]==T.Tex[2]
&& TextureNum==T.TextureNum
&& Flags== T.Flags;
}
UBOOL operator!=( const FJSMeshTri& T ) const
{
return iVertex[0]!=T.iVertex[0]
|| iVertex[1]!=T.iVertex[1]
|| iVertex[2]!=T.iVertex[2]
|| Type!=T.Type
|| Color!= T.Color
|| Tex[0]!=T.Tex[0]
|| Tex[1]!=T.Tex[1]
|| Tex[2]!=T.Tex[2]
|| TextureNum!=T.TextureNum
|| Flags!=T.Flags;
}
// Returns a string description.
FString String( UBOOL Verbose=0, UBOOL Unused=0 )
{
guard(FJSMeshTri::String);
FStringOutputDevice Out;
Out.Logf( TEXT("(Type=0x%02x,TextureNum=%i"), Type, TextureNum );
if ( Unused )
Out.Logf( TEXT(",Color=0x%02x,Flags=%0x%02x"), Color, Flags );
if ( Verbose )
Out.Logf( TEXT(",iVertex[0]=0x%04x,iVertex[1]=0x%04x,iVertex[2]=0x%04x,Tex[0]=%s,Tex[1]=%s,Tex[2]=%s"), iVertex[0], iVertex[1], iVertex[2], *Tex[0].String(), *Tex[1].String(), *Tex[2].String() );
Out.Log( TEXT(")") );
return Out;
unguard;
}
_WORD iVertex[3]; // Vertex indices.
BYTE Type; // James' mesh type.
BYTE Color; // Color for flat and Gouraud shaded.
FJSMeshUV Tex[3]; // Texture UV coordinates.
BYTE TextureNum; // Source texture offset.
BYTE Flags; // Unreal mesh flags (currently unused).
};
#if ENGINE_VERSION==1100
#define HIGH_PRECISION_MODELS // DEUS_EX CNN
#endif
// Packed mesh vertex point for skinned meshes.
#define GET_JSMESHVERT_DWORD(mv) (*(DWORD*)&(mv))
struct FJSMeshVert
{
// Variables.
#ifdef HIGH_PRECISION_MODELS
union
{
struct {INT X:16; INT Y:16; INT Z:16; INT PAD:16;}; // temp until this packing shit gets resolved - DEUS_EX CNN
struct {DWORD D1; DWORD D2;};
};
#else
#if __INTEL_BYTE_ORDER__
INT X:11; INT Y:11; INT Z:10;
#else
INT Z:10; INT Y:11; INT X:11;
#endif
#endif
// Constructors.
FJSMeshVert()
{}
FJSMeshVert( INT InX, INT InY, INT InZ )
: X(InX), Y(InY), Z(InZ)
{}
FJSMeshVert( const FVector& In )
: X((INT)In.X), Y((INT)In.Y), Z((INT)In.Z)
{}
#if EXTENDED_HEADER
FJSMeshVert( const FVectorI& In )
: X(In.X), Y(In.Y), Z(In.Z)
{}
#endif
// Functions.
FVector Vector() const
{
return FVector( X, Y, Z );
}
#if EXTENDED_HEADER
FVectorI VectorI() const
{
return FVectorI( X, Y, Z );
}
#endif
// Operators.
UBOOL operator==( const FMeshVert& V ) const
{
return X==V.X && Y==V.Y && Z==V.Z;
}
UBOOL operator!=( const FMeshVert& V ) const
{
return X!=V.X || Y!=V.Y || Z!=V.Z;
}
FJSMeshVert& operator=( const FMeshVert& Other )
{
this->X = Other.X;
this->Y = Other.Y;
this->Z = Other.Z;
return *this;
}
// Serializer.
friend FArchive& operator<<( FArchive& Ar, FJSMeshVert& V )
{
guard(FJSMeshVert<<);
#ifdef HIGH_PRECISION_MODELS
return Ar << V.D1 << V.D2;
#else
return Ar << GET_MESHVERT_DWORD(V);
#endif
unguard;
}
};
/*-----------------------------------------------------------------------------
Headers.
-----------------------------------------------------------------------------*/
// James mesh info.
struct FJSDataHeader
{
_WORD NumPolys;
_WORD NumVertices;
_WORD BogusRot;
_WORD BogusFrame;
DWORD BogusNormX,BogusNormY,BogusNormZ;
DWORD FixScale;
DWORD Unused1,Unused2,Unused3;
friend FArchive& operator<<( FArchive& Ar, FJSDataHeader& Header )
{
guard(FJSDataHeader<<);
DWORD MagicMushroom=0;
Ar << Header.NumPolys << Header.NumVertices << Header.BogusRot << Header.BogusFrame;
Ar << Header.BogusNormX << Header.BogusNormY << Header.BogusNormZ << Header.FixScale;
Ar << Header.Unused1 << Header.Unused2 << Header.Unused3;
Ar << MagicMushroom << MagicMushroom << MagicMushroom;
return Ar;
unguard;
}
};
// James animation info.
struct FJSAnivHeader
{
_WORD NumFrames; // Number of animation frames.
_WORD FrameSize; // Size of one frame of animation.
friend FArchive &operator<<( FArchive& Ar, FJSAnivHeader& Header )
{
guard(FJSAnivHeader<<);
return Ar << Header.NumFrames << Header.FrameSize;
unguard;
}
};
/*-----------------------------------------------------------------------------
File Handler.
-----------------------------------------------------------------------------*/
// See FJSMesh UMesh Constructor.
struct FRemapAnimVertsHackArchive : FArchive
{
FRemapAnimVertsHackArchive()
{
ArIsLoading = 1;
}
};
// A JamesMesh.
struct FJSMesh
{
// Header.
FJSDataHeader DataHeader;
FJSAnivHeader AnivHeader;
// Data.
TArray<FJSMeshVert> Verts;
TArray<FJSMeshTri> Tris;
// Constructors.
FJSMesh()
{
guard(FJSMesh::FJSMesh);
appMemzero( &DataHeader, sizeof(DataHeader) );
appMemzero( &AnivHeader, sizeof(AnivHeader) );
unguard;
}
FJSMesh( _WORD InNumPolys, _WORD InNumVertices, _WORD InNumFrames, _WORD InFrameSize, FJSMeshVert* InVerts, FJSMeshTri* InTris )
{
guard(FJSMesh::FJSMesh(_WORD,_WORD,_WORD,_WORD,FJSMeshVert*,FJSMeshTri*));
appMemzero( &DataHeader, sizeof(DataHeader) );
appMemzero( &AnivHeader, sizeof(AnivHeader) );
DataHeader.NumPolys = InNumPolys;
DataHeader.NumVertices = InNumVertices;
AnivHeader.NumFrames = InNumFrames;
AnivHeader.FrameSize = InFrameSize;
if ( (DataHeader.NumVertices*AnivHeader.NumFrames)>0 )
{
Verts.Add( DataHeader.NumVertices*AnivHeader.NumFrames );
if ( InVerts )
appMemcpy( &Verts(0), InVerts, DataHeader.NumVertices*AnivHeader.NumFrames*sizeof(FJSMeshVert) );
}
if ( DataHeader.NumPolys>0 )
{
Tris.Add( DataHeader.NumPolys );
if ( InTris )
appMemcpy( &Tris(0), InTris, DataHeader.NumPolys*sizeof(FJSMeshTri) );
}
unguardf(( TEXT("(InNumPolys=%i,InNumVertices=%i,InNumFrames=%i,InFrameSize=%i,InVerts=0x%08x,InTris=0x%08x)"), InNumPolys, InNumVertices, InNumFrames, InFrameSize, InVerts, InTris ));
}
FJSMesh( UMesh* Mesh, FOutputDevice* Error=GNull )
{
guard(FJSMesh::FJSMesh(UMesh*,FOutputDevice*));
appMemzero( &DataHeader, sizeof(DataHeader) );
appMemzero( &AnivHeader, sizeof(AnivHeader) );
if ( !Mesh )
{
Error->Logf( TEXT("No Mesh") );
return;
}
else if ( Mesh->GetClass()!=UMesh::StaticClass() && Mesh->GetClass()!=ULodMesh::StaticClass() )
{
Error->Logf( TEXT("Unsupported Mesh Format") );
return;
}
ULodMesh* LodMesh = Cast<ULodMesh>(Mesh);
if ( LodMesh )
{
// Make sure the necessary data is loaded.
Mesh->Verts.Load();
// This is some sort of a massive hack to trigger ULodMesh's anim verts remapping which happens at load time.
// However if the ULodMesh was just created this has not happened yet, and we might run into undesired behaviour.
guard(RemapAnimVertsHack);
if ( LodMesh->RemapAnimVerts.Num() )
{
FRemapAnimVertsHackArchive Ar;
LodMesh->Serialize( Ar );
check(!LodMesh->RemapAnimVerts.Num());
}
unguard; // RemapAnimVertsHack.
// Init Headers.
guard(InitHeaders);
AnivHeader.NumFrames = LodMesh->AnimFrames;
AnivHeader.FrameSize = LodMesh->FrameVerts*sizeof(FJSMeshVert);
DataHeader.NumVertices = LodMesh->FrameVerts;
DataHeader.NumPolys = LodMesh->SpecialFaces.Num()+LodMesh->Faces.Num();
unguard;
// Copy Triangles.
if ( (LodMesh->SpecialFaces.Num()+LodMesh->Faces.Num())>0 )
{
guard(AddTris);
Tris.Add( LodMesh->SpecialFaces.Num()+LodMesh->Faces.Num() );
unguard;
// SpecialFaces.
guard(SpecialFaces);
for ( INT iSpecial=0; iSpecial<LodMesh->SpecialFaces.Num(); iSpecial++ )
{
FMeshFace& SpecialFace = LodMesh->SpecialFaces(iSpecial);
Tris(iSpecial) = FJSMeshTri( SpecialFace.iWedge[0], SpecialFace.iWedge[1], SpecialFace.iWedge[2], MTT_Placeholder, FJSMeshUV(0,0), FJSMeshUV(0,0), FJSMeshUV(0,0), 0 );
}
unguard;
// Faces.
guard(Faces);
INT VertOffset = LodMesh->SpecialVerts;
INT FaceOffset = LodMesh->SpecialFaces.Num();
for( INT iFace=0; iFace<LodMesh->Faces.Num(); iFace++ )
{
FMeshFace& Face = LodMesh->Faces(iFace);
checkSlow(LodMesh->Faces.IsValidIndex(Face.MaterialIndex));
FMeshMaterial& Material = LodMesh->Materials(Face.MaterialIndex);
checkSlow(Tris.IsValidIndex(FaceOffset+iFace));
checkSlow(LodMesh->Wedges.IsValidIndex(Face.iWedge[0]));
checkSlow(LodMesh->Wedges.IsValidIndex(Face.iWedge[1]));
checkSlow(LodMesh->Wedges.IsValidIndex(Face.iWedge[2]));
Tris(FaceOffset+iFace) = FJSMeshTri( VertOffset+LodMesh->Wedges(Face.iWedge[0]).iVertex, VertOffset+LodMesh->Wedges(Face.iWedge[1]).iVertex, VertOffset+LodMesh->Wedges(Face.iWedge[2]).iVertex, FPolyFlagsToJamesMeshTriType(Material.PolyFlags,Error), LodMesh->Wedges(Face.iWedge[0]).TexUV, LodMesh->Wedges(Face.iWedge[1]).TexUV, LodMesh->Wedges(Face.iWedge[2]).TexUV, Material.TextureIndex );
}
unguard;
}
}
else
{
// Make sure the necessary data is loaded.
Mesh->Tris.Load();
Mesh->Verts.Load();
// Init Headers.
guard(InitHeaders);
AnivHeader.NumFrames = Mesh->AnimFrames;
AnivHeader.FrameSize = Mesh->FrameVerts*sizeof(FJSMeshVert);
DataHeader.NumVertices = Mesh->FrameVerts;
DataHeader.NumPolys = Mesh->Tris.Num();
unguard;
// Copy Triangles.
if ( Mesh->Tris.Num() )
{
Tris.Add( Mesh->Tris.Num() );
for ( INT iTri=0; iTri<Mesh->Tris.Num(); iTri++ )
Tris(iTri) = FJSMeshTri( Mesh->Tris(iTri), Error );
}
}
// Copy Vertices.
if ( Mesh->Verts.Num() )
{
check(sizeof(FMeshVert)==sizeof(FJSMeshVert));
Verts.Add( Mesh->Verts.Num() );
appMemcpy( &Verts(0), &Mesh->Verts(0), Mesh->Verts.Num()*sizeof(FJSMeshVert) );
}
unguardf(( TEXT("(Mesh=%s,Error=0x%08x)"), Mesh->GetFullName(), Error ));
}
// Conveniance functions.
_WORD NumPolys() { return DataHeader.NumPolys; }
_WORD NumFrames() { return AnivHeader.NumFrames; }
_WORD NumVertices() { return DataHeader.NumVertices; } // !! Per Frame.
// Some crude validity checks.
UBOOL Validate( FOutputDevice* Error=GNull )
{
guard(FJSMesh::Validate);
UBOOL Valid=1;
if ( DataHeader.NumPolys!=Tris.Num() )
Valid=0, Error->Logf( TEXT("NumPolys missmatch. Expected %i got %i."), DataHeader.NumPolys, Tris.Num() );
if ( (DataHeader.NumVertices*AnivHeader.NumFrames)!=Verts.Num() )
Valid=0, Error->Logf( TEXT("NumVertices*NumFrames missmatch. Expected %i got %i."), DataHeader.NumVertices*AnivHeader.NumFrames, Verts.Num() );
if ( AnivHeader.FrameSize!=(DataHeader.NumVertices*sizeof(FJSMeshVert)) ) // Maybe I should move towards honoring the FrameSize upon load. --han
Valid=0, Error->Logf( TEXT("FrameSize missmatch. Expected %i got %i."), DataHeader.NumVertices*sizeof(FJSMeshVert), AnivHeader.FrameSize );
//
// Further Ideas:
// * Check for invalid Vert indices.
// * Check for collapsed Tris.
//
return Valid;
unguard;
}
// AnivFile.
UBOOL ReadAnivFile( const TCHAR* AnivFileName, DWORD ReadFlags=0, FOutputDevice* Error=GNull )
{
guard(FJSMesh::ReadAnivFile);
FArchive* Ar = GFileManager->CreateFileReader( AnivFileName, ReadFlags, Error );
if ( !Ar )
return 0;
UBOOL Result = ReadAniv( *Ar, Error );
delete Ar;
return Result;
unguard;
}
UBOOL WriteAnivFile( const TCHAR* AnivFileName, DWORD WriteFlags=0, FOutputDevice* Error=GNull )
{
guard(FJSMesh::WriteAnivFile);
FArchive* Ar = GFileManager->CreateFileWriter( AnivFileName, WriteFlags, Error );
if ( !Ar )
return 0;
UBOOL Result = WriteAniv( *Ar, Error );
delete Ar;
if ( !Result )
Error->Logf( TEXT("Failed to write Aniv.") );
return Result;
unguard;
}
UBOOL ReadAniv( FArchive& Ar, FOutputDevice* Error=GNull )
{
guard(FJSMesh::ReadAniv);
Ar << AnivHeader;
if ( (AnivHeader.FrameSize*AnivHeader.NumFrames)<0 )
return 0;
Verts.Empty();
if ( (AnivHeader.FrameSize*AnivHeader.NumFrames)>0 )
{
Verts.AddZeroed( (AnivHeader.FrameSize*AnivHeader.NumFrames)/sizeof(FJSMeshVert) );
Ar.Serialize( &Verts(0), AnivHeader.FrameSize*AnivHeader.NumFrames );
}
return 1;
unguard;
}
UBOOL WriteAniv( FArchive& Ar, FOutputDevice* Error=GNull )
{
guard(FJSMesh::WriteAniv);
Ar << AnivHeader;;
Ar.Serialize( &Verts(0), Verts.Num()*sizeof(FJSMeshVert) );
return 1;
unguard;
}
// DataFile.
UBOOL ReadDataFile( const TCHAR* DataFileName, DWORD ReadFlags=0, FOutputDevice* Error=GNull )
{
guard(FJSMesh::ReadDataFile);
FArchive* Ar = GFileManager->CreateFileReader( DataFileName, ReadFlags, Error );
if ( !Ar )
return 0;
UBOOL Result = ReadData( *Ar, Error );
delete Ar;
return Result;
unguard;
}
UBOOL WriteDataFile( const TCHAR* DataFileName, DWORD WriteFlags=0, FOutputDevice* Error=GNull )
{
guard(FJSMesh::WriteDataFile);
FArchive* Ar = GFileManager->CreateFileWriter( DataFileName, WriteFlags, Error );
if ( !Ar )
return 0;
UBOOL Result = WriteData( *Ar, Error );
delete Ar;
if ( !Result )
Error->Logf( TEXT("Failed to write Data.") );
return Result;
unguard;
}
UBOOL ReadData( FArchive& Ar, FOutputDevice* Error=GNull )
{
guard(FJSMesh::ReadDataFile);
Ar << DataHeader;
if ( DataHeader.NumPolys<0 )
return 0;
Tris.Empty();
if ( DataHeader.NumPolys>0 )
{
Tris.AddZeroed( DataHeader.NumPolys );
Ar.Serialize( &Tris(0), DataHeader.NumPolys*sizeof(FJSMeshTri) );
}
return 1;
unguard;
}
UBOOL WriteData( FArchive& Ar, FOutputDevice* Error=GNull )
{
guard(FJSMesh::WriteData);
Ar << DataHeader;
Ar.Serialize( &Tris(0), Tris.Num()*sizeof(FJSMeshTri) );
return 1;
unguard;
}
// Slow search for Triangles on a vertex.
INT FindTrisOnVertex( INT iVertex, TArray<INT>* OutTris=NULL, INT iIgnoreTri=INDEX_NONE )
{
guard(FJSMesh::FindTrisOnVertex);
INT Count=0;
// Empty.
if ( OutTris )
OutTris->Empty();
// Walk over each Tri.
for ( INT iTri=0; iTri<Tris.Num(); iTri++ )
{
FJSMeshTri& Tri = Tris(iTri);
// Walk over each Tri's Verts.
for ( INT iVert=0; iVert<3; iVert++ )
{
if ( iVertex==Tri.iVertex[iVert] )
{
if ( iIgnoreTri==INDEX_NONE || iTri!=iIgnoreTri )
{
Count++;
if ( OutTris )
OutTris->AddItem( iTri );
}
break;
}
}
}
return Count;
unguard;
}
// Slow search for Triangles on a connect.
// Would probably be a better idea to create a Connects list first,
// which would be helpful for other commandlets, etc.
INT FindTrisOnConnect( INT iVertex0, INT iVertex1, TArray<INT>* OutTris=NULL, TArray<UBOOL>* OutTrisOrient=NULL, INT iIgnoreTri=INDEX_NONE )
{
guard(FJSMesh::FindTrisByConnect);
//check(iVertex0!=iVertex1);
INT Count=0;
// Empty.
if ( OutTris )
OutTris->Empty();
if ( OutTrisOrient )
OutTrisOrient->Empty();
// Walk over each Tri.
for ( INT iTri=0; iTri<Tris.Num(); iTri++ )
{
FJSMeshTri& Tri = Tris(iTri);
// Walk over each Tri's Verts.
for ( INT iVert=0; iVert<3; iVert++ )
{
INT iTriVert = Tri.iVertex[iVert];
if ( iTriVert==iVertex0 )
{
INT iNextTriVert = Tri.iVertex[(iVert+1)%3];
INT iPrevTriVert = Tri.iVertex[(iVert+2)%3];
if ( iNextTriVert==iVertex1 )
{
if ( iIgnoreTri==INDEX_NONE || iTri!=iIgnoreTri )
{
if ( OutTris )
OutTris->AddItem( iTri );
if ( OutTrisOrient )
OutTrisOrient->AddItem( 1 );
Count++;
}
break;
}
else if ( iPrevTriVert==iVertex1 )
{
if ( iIgnoreTri==INDEX_NONE || iTri!=iIgnoreTri )
{
if ( OutTris )
OutTris->AddItem( iTri );
if ( OutTrisOrient )
OutTrisOrient->AddItem( 0 );
Count++;
}
break;
}
}
}
}
return Count;
unguard;
}
// Slow search for Triangles on a given vertex set.
INT FindTrisOnSet( INT iVertex0, INT iVertex1, INT iVertex2, TArray<INT>* OutTris=NULL, TArray<UBOOL>* OutTrisOrient=NULL, INT iIgnoreTri=INDEX_NONE )
{
guard(FJSMesh::FindTrisOnSet);
//check(iVertex0!=iVertex1);
//check(iVertex1!=iVertex2);
//check(iVertex2!=iVertex0);
INT Count=0;
// Empty.
if ( OutTris )
OutTris->Empty();
if ( OutTrisOrient )
OutTrisOrient->Empty();
// Walk over each Tri.
for ( INT iTri=0; iTri<Tris.Num(); iTri++ )
{
FJSMeshTri& Tri = Tris(iTri);
// Walk over each Tri's Verts.
for ( INT iVert=0; iVert<3; iVert++ )
{
INT iTriVert = Tri.iVertex[iVert];
if ( iTriVert==iVertex0 )
{
INT iNextTriVert = Tri.iVertex[(iVert+1)%3];
INT iPrevTriVert = Tri.iVertex[(iVert+2)%3];
if ( iNextTriVert==iVertex1 && iPrevTriVert==iVertex2 )
{
if ( iIgnoreTri==INDEX_NONE || iTri!=iIgnoreTri )
{
if ( OutTris )
OutTris->AddItem( iTri );
if ( OutTrisOrient )
OutTrisOrient->AddItem( 1 );
Count++;
}
break;
}
else if ( iPrevTriVert==iVertex1 && iNextTriVert==iVertex2 )
{
if ( iIgnoreTri==INDEX_NONE || iTri!=iIgnoreTri )
{
if ( OutTris )
OutTris->AddItem( iTri );
if ( OutTrisOrient )
OutTrisOrient->AddItem( 0 );
Count++;
}
break;
}
}
}
}
return Count;
unguard;
}
// Removes triangle and updates DataHeader accordingly.
void RemoveTri( INT iTri )
{
guard(FJSMesh::RemoveTri);
checkSlow(iTri>=0);
checkSlow(iTri<Tris.Num());
Tris.Remove( iTri );
DataHeader.NumPolys--;
unguardf(( TEXT("(iTri=%i)"), iTri ));
}
// Flips the triangle's orientation.
void FlipTri( INT iTri )
{
guard(FJSMesh::FlipTri);
checkSlow(iTri>=0);
checkSlow(iTri<Tris.Num());
FJSMeshTri& Tri = Tris(iTri);
Exchange(Tri.iVertex[1],Tri.iVertex[2]);
Exchange(Tri.Tex[1],Tri.Tex[2]);
unguardf(( TEXT("(iTri=%i)"), iTri ));
}
// Removes dublicated triangles and optionally enforces the remained to be TwoSided.
void RemoveDublicatedTris( UBOOL ForceRemainedTwoSided )
{
guard(FJSMesh::RemoveDublicatedTris);
for ( INT iTri=0; iTri<Tris.Num(); iTri++ )
{
FJSMeshTri& MeshTri = Tris(iTri);
TArray<INT> DubList;
TArray<UBOOL> DubListOrient;
FindTrisOnSet( MeshTri.iVertex[0], MeshTri.iVertex[1], MeshTri.iVertex[2], &DubList, &DubListOrient, iTri );
if ( DubList.Num()>0 )
{
if ( ForceRemainedTwoSided )
{
// MTT_Normal is currently the only non TwoSided.
if ( (MeshTri.Type&MTT_TypeMask)==MTT_Normal )
{
MeshTri.Type = MTT_NormalTwoSided;
}
}
// Found list is sorted, so start removing from back.
//warnf( TEXT("Removing %i dublicates for Tri %i %s"), DubList.Num(), iTri, *MeshTri.String() );
for ( INT iDub=DubList.Num()-1; iDub>=0; iDub-- )
{
//warnf( TEXT(" Removing dublicate (%s) %i %s"), DubListOrient(iDub) ? TEXT("+") : TEXT("-"), DubList(iDub), *Tris(DubList(iDub)).String() );
RemoveTri( DubList(iDub) );
}
}
}
unguard;
}
// Error helper.
UBOOL IsVertValid( INT iVertex, UBOOL Check=0, FOutputDevice* Error=GNull )
{
guard(FJSMesh::IsVertValid);
if ( Check && (iVertex<0 || iVertex>=NumVertices()) )
{
Error->Logf( TEXT("Invalid Vertex %i."), iVertex );
return 0;
}
return 1;
unguard;
}
UBOOL IsFrameValid( INT iFrame, UBOOL Check=0, FOutputDevice* Error=GNull )
{
guard(FJSMesh::IsFrameValid);
if ( Check && (iFrame<0 || iFrame>=NumFrames()) )
{
Error->Logf( TEXT("Invalid Frame %i."), iFrame );
return 0;
}
return 1;
unguard;
}
UBOOL IsFrameVertValid( INT iFrameVert, UBOOL Check=0, FOutputDevice* Error=GNull )
{
guard(FJSMesh::IsFrameVertValid);
if ( Check && (iFrameVert<0 || iFrameVert>=Verts.Num()) )
{
Error->Logf( TEXT("FrameVert %i is not a valid index into Verts."), iFrameVert );
return 0;
}
return 1;
unguard;
}
// Translates a Vertex and a Frame Index into an Index into Verts.
INT GetFrameVertIndex( INT iVertex, INT iFrame, UBOOL Check=0, FOutputDevice* Error=GNull )
{
guard(FJSMesh::GetFrameVertIndex);
if ( !IsVertValid(iVertex,Check,Error) || !IsFrameValid(iFrame,Check,Error) )
return INDEX_NONE;
INT iFrameVert = iFrame*NumVertices()+iVertex;
if ( !IsFrameVertValid(iFrameVert,Check,Error) )
return INDEX_NONE;
return iFrameVert;
unguard;
}
// Translates a Vertex and a Frame Index into an Index into Verts.
FJSMeshVert& GetFrameVert( INT iVertex, INT iFrame, UBOOL Check=0, FOutputDevice* Error=GNull )
{
guard(FJSMesh::GetFrameVert);
static FJSMeshVert Invalid;
INT Index = GetFrameVertIndex( iVertex, iFrame,Check, Error );
if ( Check && Index==INDEX_NONE )
return Invalid;
return Verts(Index);
unguard;
}
// Find Tris on Plane.
INT FindTrisOnPlane( FPlane SplitPlane, INT SplitFrame, TArray<INT>* OutTris, TArray<INT>* UpVerts, TArray<INT>* DownVerts, UBOOL Check=0, FOutputDevice* Error=GNull )
{
guard(FJSMesh::FindTrisOnPlane);
INT Count=0;
// Empty.
if ( OutTris )
OutTris->Empty();
if ( UpVerts )
UpVerts->Empty();
if ( DownVerts )
DownVerts->Empty();
// Check each Tri.
for ( INT iTri=0; iTri<Tris.Num(); iTri++ )
{
FJSMeshTri& MeshTri = Tris(iTri);
INT Signs[3];
for ( INT i=0; i<3; i++ )
Signs[i] = SplitPlane.PlaneDot(GetFrameVert(MeshTri.iVertex[i],SplitFrame,Check,GError).Vector())>0.f ? 1 : -1;
if ( Abs(Signs[0]+Signs[1]+Signs[2])!=3 )
{
//warnf( TEXT("Tri on Plane %s"), *MeshTri.String() );
if ( OutTris )
OutTris->AddItem( iTri );
for ( INT i=0; i<3; i++ )
{
if ( Signs[i]>0 )
{
if ( UpVerts )
UpVerts->AddUniqueItem( MeshTri.iVertex[i] );
}
else
{
if ( DownVerts )
DownVerts->AddUniqueItem( MeshTri.iVertex[i] );
}
}
Count++;
}
}
return Count;
unguard;
}
// Duplicates the Vertex Data into a new Vertex.
#if EXTENDED_HEADER
INT DuplicateVert( INT iVertex, UBOOL Check=0, FOutputDevice* Error=GNull )
{
guard(FJSMesh::DuplicateVert);
if ( !IsVertValid(iVertex,Check,Error) )
return 0;
INT iNewVertex = DataHeader.NumVertices++;
for ( INT iFrame=0; iFrame<NumFrames(); iFrame++ )
{
FJSMeshVert& OrigVert = GetFrameVert( iVertex, iFrame, Check, Error );
Verts.InsertItem( iNewVertex+iFrame*DataHeader.NumVertices, OrigVert );
}
AnivHeader.FrameSize += sizeof(FJSMeshVert);
return iNewVertex;
unguard;
}
#endif
};
/*-----------------------------------------------------------------------------
The end.
-----------------------------------------------------------------------------*/
Code: Select all
UBOOL MeshExportJames( UMesh* Mesh, const TCHAR* BaseName )
{
guard(UBatchMeshExportCommandlet::meshExportJames);
if ( !Mesh || !BaseName || !*BaseName )
return 0;
FJSMesh JSMesh( Mesh, GWarn );
return JSMesh.WriteDataFile(*(US*BaseName+TEXT("_d.3d")),0,GWarn) && JSMesh.WriteAnivFile(*(US*BaseName+TEXT("_a.3d")),0,GWarn);
unguardf(( TEXT("(%s)"), Mesh->GetFullName() ));
}
Code: Select all
/*=============================================================================
UJamesPlaneSmoothSplitCommandlet.cpp.
Copyright 2016 Sebastian Kaufel. All Rights Reserved.
Revision history:
* Created by Sebastian Kaufel
=============================================================================*/
#include "HTK.h"
#include "FJamesMesh.h"
/*-----------------------------------------------------------------------------
UJamesPlaneSmoothSplitCommandlet.
-----------------------------------------------------------------------------*/
class HTK_API UJamesPlaneSmoothSplitCommandlet : public UCommandlet
{
DECLARE_HTK_CLASS(UJamesPlaneSmoothSplitCommandlet,UCommandlet,CLASS_Transient)
UJamesPlaneSmoothSplitCommandlet()
{
guard(UJamesPlaneSmoothSplitCommandlet::StaticConstructor);
unguard;
}
void StaticConstructor()
{
guard(UJamesPlaneSmoothSplitCommandlet::StaticConstructor);
LogToStdout = 0;
IsServer = 0;
IsClient = 0;
IsEditor = 1;
LazyLoad = 0;
ShowErrorCount = 0;
ShowBanner = 0;
unguard;
}
// Entry point.
INT Main( const TCHAR* Parms )
{
guard(UJamesPlaneSmoothSplitCommandlet::Main);
// Parse commandline.
FString InDataFileName, InAnivFileName, OutDataFileName, OutAnivFileName, Splits[4], Frame;
if ( !ParseToken(Parms,InDataFileName,0) )
appErrorf( TEXT("Input DataFile not specified.") );
if ( !ParseToken(Parms,InAnivFileName,0) )
appErrorf( TEXT("Input AnivFile not specified.") );
if ( !ParseToken(Parms,OutDataFileName,0) )
appErrorf( TEXT("Output DataFile not specified.") );
if ( !ParseToken(Parms,OutAnivFileName,0) )
appErrorf( TEXT("Output AnivFile not specified.") );
if ( !ParseToken(Parms,Splits[0],0) )
appErrorf( TEXT("SplitPlane X not specified.") );
if ( !ParseToken(Parms,Splits[1],0) )
appErrorf( TEXT("SplitPlane Y not specified.") );
if ( !ParseToken(Parms,Splits[2],0) )
appErrorf( TEXT("SplitPlane Z not specified.") );
if ( !ParseToken(Parms,Splits[3],0) )
appErrorf( TEXT("SplitPlane W not specified.") );
if ( !ParseToken(Parms,Frame,0) )
Frame = TEXT("0");
// Load Mesh.
FJSMesh JSMesh;
JSMesh.ReadDataFile( *InDataFileName, 0, GError );
JSMesh.ReadAnivFile( *InAnivFileName, 0, GError );
JSMesh.Validate( GError );
// Create Plane.
FPlane SplitPlane( appAtof(*Splits[0]), appAtof(*Splits[1]), appAtof(*Splits[2]), appAtof(*Splits[3]) );
warnf( TEXT("SplitPlane: %s"), *SplitPlane.String() );
// Create and Check SplitFrame.
INT SplitFrame = appAtoi( *Frame );
if ( SplitFrame<0 || SplitFrame>=JSMesh.NumFrames() )
appErrorf( TEXT("Invalid SplitFrame %i. Must be in [0,%i]."), SplitFrame, JSMesh.NumFrames()-1 );
warnf( TEXT("SplitFrame: %i"), SplitFrame );
// Find Tris on Plane.
TArray<INT> PlaneTris, UpVerts, DownVerts;
JSMesh.FindTrisOnPlane( SplitPlane, SplitFrame, &PlaneTris, &UpVerts, &DownVerts, 1, GError );
// We now have our list of Triangles on Plane and AdjVerts.
if ( PlaneTris.Num() )
{
// Make a pass to remove all DownVerts just used on one Tri.
for ( INT iTrim=DownVerts.Num()-1; iTrim>=0; iTrim-- )
{
if ( JSMesh.FindTrisOnVertex(DownVerts(iTrim))==1 )
{
//warnf( TEXT("Not duplicating Vert %i (only used once)"), DownVerts(iTrim) );
DownVerts.Remove( iTrim );
}
}
// Dublicate all DownVerts.
TArray<INT> DupVerts;
guard(Dub);
DupVerts.AddZeroed( DownVerts.Num() );
for ( INT iDownVert=0; iDownVert<DownVerts.Num(); iDownVert++ )
DupVerts(iDownVert) = JSMesh.DuplicateVert( DownVerts(iDownVert), 1, GError );
unguard;
// Remap all DownVerts on PlaneTris to DubVerts.
guard(Remap);
for ( INT iPlaneTri=0; iPlaneTri<PlaneTris.Num(); iPlaneTri++ )
{
FJSMeshTri& PlaneTri = JSMesh.Tris(PlaneTris(iPlaneTri));
for ( INT iEdge=0; iEdge<3; iEdge++ )
{
// Find in DownVerts.
INT iOrigVert = PlaneTri.iVertex[iEdge];
for ( INT i=0; i<DownVerts.Num(); i++ )
{
if ( iOrigVert==DownVerts(i) )
{
// Remap.
PlaneTri.iVertex[iEdge] = DupVerts(i);
break;
}
}
}
}
warnf( TEXT("Duplicated %i Verts for %i Tris."), DownVerts.Num(), PlaneTris.Num() );
unguard;
}
else
{
// Could have errored here, but opted against for minor reasons.
warnf( TEXT("No Triangles on SplitPlane.") );
}
// Save Mesh.
JSMesh.Validate( GError );
JSMesh.WriteDataFile( *OutDataFileName, 0, GError );
JSMesh.WriteAnivFile( *OutAnivFileName, 0, GError );
return 0;
unguard;
}
};
IMPLEMENT_CLASS(UJamesPlaneSmoothSplitCommandlet);
/*----------------------------------------------------------------------------
The End.
----------------------------------------------------------------------------*/
Code: Select all
[JamesPlaneSmoothSplitCommandlet]
HelpCmd=jamesplanesmoothsplit
HelpOneLiner=Split Smoothing by Plane.
HelpUsage=jamesplanesmoothsplit <in_d> <in_a> <out_d> <out_a> <x> <y> <z> <w> [<frame>]
HelpWebLink=