For direct access use https://forums.oldunreal.com
It's been quite a while since oldunreal had an overhaul, but we are moving to another server which require some updates and changes. The biggest change is the migration of our old reliable YaBB forum to phpBB. This system expects you to login with your username and old password known from YaBB.
If you experience any problems there is also the usual "password forgotten" function. Don't forget to clear your browser cache!
If you have any further concerns feel free to contact me: Smirftsch@oldunreal.com

[Snippet] James Mesh support code (FJamesMesh.h)

Post Reply
User avatar
han
Global Moderator
Posts: 686
Joined: Wed Dec 10, 2014 12:38 am

[Snippet] James Mesh support code (FJamesMesh.h)

Post by han »

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 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.
-----------------------------------------------------------------------------*/
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. Löffels on Patreon.
User avatar
atrey
OldUnreal Member
Posts: 5
Joined: Wed Nov 07, 2018 12:47 pm

Re: [Snippet] James Mesh support code (FJamesMesh.h)

Post by atrey »

Really curious to see screenshots or video of this new Mesh class you worked on, and to see how it compares to traditional UMesh.
Post Reply

Return to “C++ Native Mods for UE1”