logo
Main

Forums

Downloads

Unreal-Netiquette

Donate for Oldunreal:
Donate

borderline

Links to our wiki:
Wiki

Walkthrough

Links

Tutorials

Unreal Reference

Usermaps

borderline

Contact us:
Submit News
Page Index Toggle Pages: 1 Send TopicPrint
Normal Topic [Snippet] James Mesh support code (FJamesMesh.h) (Read 804 times)
han
Global Moderator
Unreal Rendering Guru
Developer Team
*****
Offline


Oldunreal member

Posts: 564
Location: Germany
Joined: Dec 10th, 2014
Gender: Male
[Snippet] James Mesh support code (FJamesMesh.h)
Jan 26th, 2017 at 2:29am
Print Post  
I have a couple of James Mesh related commandlets in HTK end ended up doing a class representation of the actual James Meshes, which do have some interfaces for basic editing purposes and a constructor to sample from an UMesh/ULodMesh and load/save to file interfaces, etc.

Code
Select All
/*=============================================================================
	FJamesMesh.h: James Mesh Format Header.
	Copyright 2016 Sebastian Kaufel. All Rights Reserved.

	Derived of work copyrighted by Jochen Goernitz & 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.
-----------------------------------------------------------------------------*/
 



So something basic like exporting a mesh looks like:
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() ));
	}
 



A bit more complicated example is to this commandlet which takes a mesh and a plane as input, finds the triangles the plane intersects and dublicates all the vertices on one side of the plane so. Conceptionally this is basically like splitting up smoothing groups by a plane. I used so far to split up bottoms of the Monk statue and various Trees/Plants. It also works for animated meshes.

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=
 



  

HX on Mod DB. Revision on Steam.
Back to top
 
IP Logged
 
atrey
New Member
*
Offline


Oldunreal member

Posts: 4
Joined: Nov 7th, 2018
Re: [Snippet] James Mesh support code (FJamesMesh.h)
Reply #1 - Nov 7th, 2018 at 2:10pm
Print Post  
Really curious to see screenshots or video of this new Mesh class you worked on, and to see how it compares to traditional UMesh.
  
Back to top
 
IP Logged
 
Page Index Toggle Pages: 1
Send TopicPrint
Bookmarks: del.icio.us Digg Facebook Google Google+ Linked in reddit StumbleUpon Twitter Yahoo