Custom C++ 3DS Max Exporter and Importer


Project Features                                                     Custom C++ Engine and dll / OpenGL / Development Time - 12 Weeks

  • Custom export binary file format (.CBE)

  • C++ 3DS Max export plugin dll

  • Importer integrated into Custom C++ Engine

  • Supports Skeletal Animation and Vertex Animation

  • Redundant transform data minimized by animation frame sampling

  • Supports exporting of diffuse textures, normal maps, specular color and level, and emissive

  • Option for Multi-threaded loading of exported scenes

  • Preserves scene graph hierarchy of exported scene  

  • Supports translation, rotation, and scaling transformations

  • Triangle batches grouped by materials and as sub meshes

  • Supports multiple materials for a mesh

  • Loaded meshes can be duplicated efficiently from a blueprint registry


Project Overview

The goal of this project was to export 3DS Max 3D scenes into my custom C++ Engine. Scenes and meshes are extracted by a custom dll plugin which integrates with 3DS Max. Data is exported into a custom binary file format, which is recognized and parsed by my importer that is integrated into my custom C++ Engine. The exporting process preserves the scene graph hierarchy and exports each scene as a collection of triangle batches, which are grouped according to their materials. For materials, diffuse textures, emissive textures, specular color and level textures, and normal map textures are supported. Exporting of skinned meshes and older vertex animation is supported, and redundant animation transforms are detected and removed during the export process to minimize file sizes. Skinned mesh animation is supported by custom a vertex shader in my C++ Engine.


Project Post Mortem

What Went Well

  • This project helped ease my discomfort with using dll's. 3DS Max has great documentation on creating plugin dll's and a very handy plugin wizard. It was not very difficult to get the initial software configuration and setup going and allowed me to focus on design and coding much earlier than anticipated. 
  • I dedicated a lot of effort early on to debugging output of the export file. I created a separate text file option where I could view the output of the exported meshes in text rather than binary. This helped dissect any issues with my data layout and extraction early on in the project.

What Went Wrong

  • This was one of my earlier projects at Guildhall and at this point I had still not grasped the full importance of visual debugging options. When implementing skeletal animation, I attempted to do so without any visual debugging assistance other than the visual appearance of the mesh. This led to a lot of lost development time with trial and error tweaks to my vertex shader and skinned mesh classes. The development time lost would likely had been much less then the development time of implementing visual debugging of skinned mesh bone structures.
  • On the exporter side, early on I made bad assumptions with how 3DS Max managed memory of IGame objects. This led to some frustrating memory corruption issues that would arise if I needed to export more than one mesh per instance of 3DS Max. I was not able to pinpoint this issue until much later on in the project, which in turn led to lost development time. 

What Was Learned

  • Always invest in debugging tools (especially visual ones with rendering projects) for every project. It will almost always pay off with mitigating lost development time in the future.
  • Never make assumptions about a 3rd party program's or library's management and handling of memory. I should always read the documentation thoroughly and if I am still unsure, I should create small test projects to better grasp it. Many of my exporter design implementations had to be refactored much later in the project  and could have been avoided if I spent more time upfront understanding how 3DS Max manages memory.

Code Samples

Importer Code Samples

  • CB3DSMaxImporter.hpp

    					
    #ifndef included_CB3DSMaxImporter
    #define included_CB3DSMaxImporter
    #pragma once
    
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #include "EngineMacros.hpp"
    
    #include "Vertex.hpp"
    #include "Entity.hpp"
    #include "Matrix44.hpp"
    
    #include "CBMesh.hpp"
    #include "CBNode.hpp"
    #include "CBSkinnedMesh.hpp"
    #include "EntityMesh.hpp"
    #include "CBTriangleBatch.hpp"
    #include "CBSkinnedTriangleBatch.hpp"
    
    const std::string MESH_BINARY_FILE_EXTENSION	= ".CBE";
    const std::string MESH_DATA_FILE_LOCATION		= "DataFiles/";
    
    const std::string TEXTURE_DIRECTORY = "Art\\";
    const std::string UNIFORM_BONE_TM_NAME = "u_boneTM"; 
    const int MAX_NUM_BONES_TO_EXTRACT	= 4;
    const int BONE_INDEX_NOT_USED		= -1;
    
    const std::string DEFAULT_NO_DIFFUSE = "Art/DefaultDiffuse.png";
    const std::string DEFAULT_NO_NORMAL_MAP = "Art/DefaultNormalMap.png";
    const std::string DEFAULT_NO_SPECULAR = "Art/DefaultSpecular.png";
    const std::string DEFAULT_NO_SPECULAR_COLOR = "Art/DefaultSpecularColor.png";
    const std::string DEFAULT_NO_EMISSIVE = "Art/DefaultEmissive.png";
    const std::string NO_TEXTURE_FOUND = "None";
    
    struct MaterialData {
    public:
    	MaterialData() {
    		indexNum = 0;
    	}
    
    	int indexNum;
    	std::string materialName;
    	std::string diffuseTextureFilePath;
    	std::string normalMapTextureFilePath;
    	std::string specularLevelTextureFilePath;
    	std::string specularColorTextureFilePath;
    	std::string emissiveTextureFilePath;
    };
    
    
    struct TriBatchData {
    public:
    	TriBatchData() {
    		materialIndex = 0;
    		numVerts = 0;
    	}
    
    	int materialIndex;
    	int numVerts;
    	std::vector verts;
    	std::string	diffuseTextureFilePath;
    };
    
    
    struct BoneData {
    public:
    	BoneData() {
    		boneIndex = -1;
    		boneWeight = 0.0f;
    	}
    
    	int boneIndex;
    	float boneWeight;
    
    };
    
    
    class CB3DSMaxImporter {
    public:
    	static CB3DSMaxImporter& getSharedCB3DSMaxImporter() {
    		static CB3DSMaxImporter maxImporter;
    		return maxImporter;
    	}
    
    	~CB3DSMaxImporter();
    
    	bool importMaxCBEFileAndGetEntity( const std::string& cbeFilePath, std::vector& maxMeshEntityVec );
    	bool importMaxCBEBinaryFileAndGetRootCBNode( const std::string& cbeFilePath, EntityMesh*& meshToCreate );
    	bool importMaxCBEBinaryFileAndGetRootCBNode( std::ifstream* importBinaryStream, EntityMesh*& meshToCreate );
    
    protected:
    	CB3DSMaxImporter() {}
    
    	void extractEntityMeshFromCBEImportBinaryFile( std::ifstream* importBinaryStream, EntityMesh*& meshToCreate );
    
    	void extractMaterialDataFromCBEImportBinaryFile( std::ifstream* binaryInputStream, std::map& materialMap );
    
    	void extractAllNodeDataFromCBEImportBinaryFile( std::ifstream* binaryInputStream,
    		std::map& importCBNodes,
    		std::map& materialMap, 
    		EntityMesh* entityMesh );
    
    	void extractNodeDataFromCBEImportBinaryFile( std::ifstream* binaryInputStream,
    		CBNode* importCBNode,
    		std::map& materialMap );
    
    	void extractMeshDataFromCBEImportBinaryFile( std::ifstream* binaryInputStream,
    		CBMesh* importCBMesh,
    		std::map& materialMap );
    
    	void extractTriBatchDataFromMeshNode( std::ifstream* binaryInputStream,
    		CBMesh* meshToExtractFrom,
    		std::map& materialMap );
    
    	void extractBoneNodeDataFromCBEImportBinaryFile( std::ifstream* binaryInputStream,
    		CBNode* importCBNode,
    		std::map& materialMap,
    		EntityMesh* entityMesh );
    
    	void extractSkinnedMeshDataFromCBEImportBinaryFile( std::ifstream* binaryInputStream,
    		CBSkinnedMesh* importCBMesh,
    		std::map& materialMap );
    
    	void extractSkinnedTriBatchDataFromMeshNode( std::ifstream* binaryInputStream,
    		CBSkinnedMesh* meshToExtractFrom,
    		std::map& materialMap );
    	
    	void cleanUpTextureNames( std::string& textureName );
    	
    private:
    	PREVENT_COPY_AND_ASSIGN( CB3DSMaxImporter );
    
    };
    
    #endif
    							
  • CB3DSMaxImporter.cpp

    #include "CB3DSMaxImporter.hpp"
    
    #include 
    #include 
    
    #include "CBStringHelper.hpp"
    
    #include "Vector3D.hpp"
    #include "Vector2.hpp"
    #include "Texture.hpp"
    
    #include "../../CBEngine/EngineCode/MemoryMacros.hpp"
    
    CB3DSMaxImporter::~CB3DSMaxImporter() 
    {
    
    }
    
    
    bool CB3DSMaxImporter::importMaxCBEBinaryFileAndGetRootCBNode( const std::string& cbeFilePath, EntityMesh*& meshToCreate ) 
    {
    	bool didImport = false;
    
    	std::ifstream* importBinaryStream = new std::ifstream( cbeFilePath.c_str(), std::ios::binary | std::ios::in );
    
    	if ( !importBinaryStream || !( importBinaryStream->is_open() ) )
    	{
    		return didImport;
    	}
    
    	extractEntityMeshFromCBEImportBinaryFile( importBinaryStream, meshToCreate );
    
    	importBinaryStream->close();
    	delete importBinaryStream;
    
    	didImport = true;
    
    	return didImport;
    }
    
    
    bool CB3DSMaxImporter::importMaxCBEBinaryFileAndGetRootCBNode( std::ifstream* importBinaryStream, EntityMesh*& meshToCreate )
    {
    	bool didImport = false;
    
    	if ( !importBinaryStream || !( importBinaryStream->is_open() ) ) 
    	{
    		return didImport;
    	}
    
    	extractEntityMeshFromCBEImportBinaryFile( importBinaryStream, meshToCreate );
    
    	importBinaryStream->close();
    	delete importBinaryStream;
    
    	didImport = true;
    
    	return didImport;
    }
    
    
    void CB3DSMaxImporter::extractEntityMeshFromCBEImportBinaryFile( std::ifstream* importBinaryStream, EntityMesh*& meshToCreate )
    {
    	const int rootNodeIndex = -1;
    	const int defaultParentIndexForRoot = -2;
    	std::map importCBNodes;
    	CBNode* nodeToCreate = new CBNode;
    	nodeToCreate->setNodeIndex( rootNodeIndex );
    	nodeToCreate->setParentIndex( defaultParentIndexForRoot );
    
    	meshToCreate = new EntityMesh( nodeToCreate );
    	nodeToCreate->setEntityMesh( meshToCreate );
    
    	importCBNodes.insert( std::pair( rootNodeIndex, nodeToCreate ) );
    
    	std::map materialMap;
    
    	extractMaterialDataFromCBEImportBinaryFile( importBinaryStream, materialMap );
    
    	extractAllNodeDataFromCBEImportBinaryFile( importBinaryStream, importCBNodes, materialMap, meshToCreate );
    
    	std::map::iterator itOut;
    	for ( itOut = importCBNodes.begin(); itOut != importCBNodes.end(); ++itOut ) 
    	{
    		int outerIndex = itOut->first;
    		CBNode* nodeOuter = itOut->second;
    		std::map::iterator itInner;
    
    		for ( itInner = importCBNodes.begin(); itInner != importCBNodes.end(); ++itInner ) 
    		{
    			CBNode* nodeInner = itInner->second;
    			int innerParentIndex = nodeInner->getParentIndex();
    
    			if ( innerParentIndex == outerIndex ) 
    			{
    				nodeOuter->addChildToNode( nodeInner );
    				nodeInner->setParentNode( nodeOuter );
    			}
    		}
    	}
    }
    
    
    void CB3DSMaxImporter::extractMaterialDataFromCBEImportBinaryFile( std::ifstream* binaryInputStream, std::map& materialMap ) {
    
    	int totalNumMaterials = 0;
    	binaryInputStream->read( (char*) &totalNumMaterials, sizeof( int ) );
    
    	int sizeOfMaterialNameString = 0;
    	std::string materialNameString;
    
    	int sizeOfDiffuseTextureString = 0;
    	std::string diffuseNameString;
    
    	int sizeOfNormalMapTextureString = 0;
    	std::string normalMapNameString;
    
    	int sizeOfSpecularLevelTextureString = 0;
    	std::string specularLevelTextureNameString;
    
    	int sizeOfSpecularColorTextureString = 0;
    	std::string specularColorTextureNameString;
    
    	int sizeOfEmissiveTextureString = 0;
    	std::string emissiveTextureNameString;
    
    	for ( size_t i = 0; i < static_cast( totalNumMaterials ); ++i ) 
    	{
    		MaterialData materialData;
    
    		// Material Index
    		binaryInputStream->read( (char*) &materialData.indexNum, sizeof( materialData.indexNum ) );
    		
    		// Material Name
    		binaryInputStream->read( (char*) &sizeOfMaterialNameString, sizeof( sizeOfMaterialNameString ) );
    		materialNameString.resize( sizeOfMaterialNameString );
    		binaryInputStream->read( &materialNameString[0], sizeOfMaterialNameString );
    		materialData.materialName = materialNameString;
    
    		// Diffuse Name
    		binaryInputStream->read( (char*) &sizeOfDiffuseTextureString, sizeof( sizeOfDiffuseTextureString ) );
    		diffuseNameString.resize( sizeOfDiffuseTextureString );
    		binaryInputStream->read( &diffuseNameString[0], sizeOfDiffuseTextureString );
    
    		cleanUpTextureNames( diffuseNameString );
    
    		if ( diffuseNameString == NO_TEXTURE_FOUND ) {
    			diffuseNameString = DEFAULT_NO_DIFFUSE;
    		}
    
    		// Normal Name
    		binaryInputStream->read( (char*) &sizeOfNormalMapTextureString, sizeof( sizeOfNormalMapTextureString ) );
    		normalMapNameString.resize( sizeOfNormalMapTextureString );
    		binaryInputStream->read( (char*) &normalMapNameString[0], sizeOfNormalMapTextureString );
    
    		cleanUpTextureNames( normalMapNameString );
    
    		if ( normalMapNameString == NO_TEXTURE_FOUND ) {
    			normalMapNameString = DEFAULT_NO_NORMAL_MAP;
    		} 
    
    		// Specular Level Name
    		binaryInputStream->read( (char*) &sizeOfSpecularLevelTextureString, sizeof( sizeOfSpecularLevelTextureString ) );
    		specularLevelTextureNameString.resize( sizeOfSpecularLevelTextureString );
    		binaryInputStream->read( (char*) &specularLevelTextureNameString[0], sizeOfSpecularLevelTextureString );
    
    		cleanUpTextureNames( specularLevelTextureNameString );
    
    		if ( specularLevelTextureNameString == NO_TEXTURE_FOUND ) {
    			specularLevelTextureNameString = DEFAULT_NO_SPECULAR;
    		} 
    		
    		// Specular Color Name
    		binaryInputStream->read( (char*) &sizeOfSpecularColorTextureString, sizeof( sizeOfSpecularColorTextureString ) );
    		specularColorTextureNameString.resize( sizeOfSpecularColorTextureString );
    		binaryInputStream->read( (char*) &specularColorTextureNameString[0], sizeOfSpecularColorTextureString );
    
    		cleanUpTextureNames( specularColorTextureNameString );
    
    		if ( specularColorTextureNameString == NO_TEXTURE_FOUND ) {
    			specularColorTextureNameString = DEFAULT_NO_SPECULAR_COLOR;
    		}
    
    		// Emissive Name
    		binaryInputStream->read( (char*) &sizeOfEmissiveTextureString, sizeof( sizeOfEmissiveTextureString ) );
    		emissiveTextureNameString.resize( sizeOfEmissiveTextureString );
    		binaryInputStream->read( (char*) &emissiveTextureNameString[0], sizeOfEmissiveTextureString ); 
    
    		cleanUpTextureNames( emissiveTextureNameString );
    
    		if ( emissiveTextureNameString == NO_TEXTURE_FOUND ) {
    			emissiveTextureNameString = DEFAULT_NO_EMISSIVE;
    		}
    
    		materialData.diffuseTextureFilePath = diffuseNameString;
    		materialData.normalMapTextureFilePath = normalMapNameString;
    		materialData.specularLevelTextureFilePath = specularLevelTextureNameString;
    		materialData.specularColorTextureFilePath = specularColorTextureNameString;
    		materialData.emissiveTextureFilePath = emissiveTextureNameString;
    
    		materialMap.insert( std::pair( materialData.indexNum, materialData ) );
    	}
    }
    
    
    void CB3DSMaxImporter::extractAllNodeDataFromCBEImportBinaryFile( std::ifstream* binaryInputStream,
    	std::map&  importCBNodes,
    	std::map& materialMap,
    	EntityMesh* entityMesh ) 
    {
    	
    	int totalNumberOfNodes = 0;
    	binaryInputStream->read( (char*) &totalNumberOfNodes, sizeof( totalNumberOfNodes ) );
    
    	for ( int i = 0; i < totalNumberOfNodes; ++i ) 
    	{
    		// Read in the type of Node
    		int sizeOfNodeType = 0;
    		binaryInputStream->read( (char*) &sizeOfNodeType, sizeof( sizeOfNodeType ) );
    
    		std::string nodeType;
    		nodeType.resize( sizeOfNodeType );
    		binaryInputStream->read( &nodeType[0], sizeOfNodeType );
    
    		if ( nodeType == "Node" ) {
    
    			CBNode* newNode = new CBNode( entityMesh );
    			extractNodeDataFromCBEImportBinaryFile( binaryInputStream, newNode, materialMap );
    			importCBNodes.insert( std::pair( newNode->getNodeIndex(), newNode ) );
    
    		} else if ( nodeType == "Mesh" ) {
    
    			CBMesh* newMesh = new CBMesh( entityMesh );
    			extractMeshDataFromCBEImportBinaryFile( binaryInputStream, newMesh, materialMap );
    			importCBNodes.insert( std::pair( newMesh->getNodeIndex(), newMesh ) );
    
    		} else if ( nodeType == "SkinnedMesh" ) {
    			
    			CBSkinnedMesh* newSkinnedMesh = new CBSkinnedMesh( entityMesh );
    			extractSkinnedMeshDataFromCBEImportBinaryFile( binaryInputStream, newSkinnedMesh, materialMap );
    			//newSkinnedMesh->generateBoneTransformationMatrixUniformLocationValues();
    			importCBNodes.insert( std::pair( newSkinnedMesh->getNodeIndex(), newSkinnedMesh ) );
    
    		} else if ( nodeType == "Bone" ) {
    			
    			CBNode* newBoneNode = new CBNode( entityMesh );
    			extractBoneNodeDataFromCBEImportBinaryFile( binaryInputStream, newBoneNode, materialMap, entityMesh );
    			importCBNodes.insert( std::pair( newBoneNode->getNodeIndex(), newBoneNode ) );
    
    		} else {
    			// Shit
    			assert( true == true );
    		}
    	}
    }
    
    
    void CB3DSMaxImporter::extractNodeDataFromCBEImportBinaryFile( std::ifstream* binaryInputStream,
    	CBNode* importCBNode,
    	std::map& materialMap ) 
    {
    
    	UNUSED( materialMap );
    	int sizeOfNodeNameString = 0;
    	std::string nodeNameString;
    	binaryInputStream->read( (char*) &sizeOfNodeNameString, sizeof( sizeOfNodeNameString ) );
    	nodeNameString.resize( sizeOfNodeNameString );
    	binaryInputStream->read( (char*) &nodeNameString[0], sizeOfNodeNameString );
    	importCBNode->setNodeName( nodeNameString );
    
    	// Node Index
    	int nodeIndex = 0;
    	binaryInputStream->read( (char*) &nodeIndex, sizeof( nodeIndex ) );
    	importCBNode->setNodeIndex( nodeIndex );
    
    	// Parent Node Index
    	int parentNodeIndex = -1;
    	binaryInputStream->read( (char*) &parentNodeIndex, sizeof( parentNodeIndex ) );
    	importCBNode->setParentIndex( parentNodeIndex );
    
    	Matrix44 localToWorldTransform;
    	binaryInputStream->read( (char*) &localToWorldTransform.matrixData[0], sizeof( Matrix44 ) );
    	importCBNode->setInitialLocalToWorldTransform( localToWorldTransform );
    
    	Matrix44 worldToLocalTransform;
    	binaryInputStream->read( (char*) &worldToLocalTransform.matrixData[0], sizeof( Matrix44 ) ); 
    	importCBNode->setInitialWorldToLocalTransform( worldToLocalTransform );
    
    	int numberOfAnimTransforms = 0;
    	binaryInputStream->read( (char*) &numberOfAnimTransforms, sizeof( numberOfAnimTransforms ) );
    
    	for ( int i = 0; i < numberOfAnimTransforms; ++i ) {
    
    		Matrix44 animTransform;
    		binaryInputStream->read( (char*) &animTransform.matrixData[0], sizeof( Matrix44 ) );
    		importCBNode->addAnimationTransform( animTransform );
    	}
    	
    }
    
    
    void CB3DSMaxImporter::extractMeshDataFromCBEImportBinaryFile( std::ifstream* binaryInputStream,
    	CBMesh* importCBMesh,
    	std::map& materialMap )
    {
    
    	int sizeOfNodeNameString = 0;
    	std::string nodeNameString;
    	binaryInputStream->read( (char*) &sizeOfNodeNameString, sizeof( sizeOfNodeNameString ) );
    	nodeNameString.resize( sizeOfNodeNameString );
    	binaryInputStream->read( (char*) &nodeNameString[0], sizeOfNodeNameString );
    	importCBMesh->setNodeName( nodeNameString );
    
    	// Node Index
    	int nodeIndex = 0;
    	binaryInputStream->read( (char*) &nodeIndex, sizeof( nodeIndex ) );
    	importCBMesh->setNodeIndex( nodeIndex );
    
    	// Parent Node Index
    	int parentNodeIndex = -1;
    	binaryInputStream->read( (char*) &parentNodeIndex, sizeof( parentNodeIndex ) );
    	importCBMesh->setParentIndex( parentNodeIndex );
    
    	Matrix44 localToWorldTransform;
    	binaryInputStream->read( (char*) &localToWorldTransform.matrixData, sizeof( Matrix44 ) );
    	importCBMesh->setInitialLocalToWorldTransform( localToWorldTransform );
    
    	Matrix44 worldToLocalTransform;
    	binaryInputStream->read( (char*) &worldToLocalTransform.matrixData[0], sizeof( Matrix44 ) ); 
    	importCBMesh->setInitialWorldToLocalTransform( worldToLocalTransform );
    
    	int numberOfAnimTransforms = 0;
    	binaryInputStream->read( (char*) &numberOfAnimTransforms, sizeof( numberOfAnimTransforms ) );
    
    
    	for ( int i = 0; i < numberOfAnimTransforms; ++i ) {
    
    		Matrix44 animTransform;
    		binaryInputStream->read( (char*) &animTransform.matrixData[0], sizeof( Matrix44 ) );
    		importCBMesh->addAnimationTransform( animTransform );
    	}
    
    	extractTriBatchDataFromMeshNode( binaryInputStream, importCBMesh, materialMap );
    }
    
    
    void CB3DSMaxImporter::extractSkinnedMeshDataFromCBEImportBinaryFile( std::ifstream* binaryInputStream,
    	CBSkinnedMesh* importCBMesh,
    	std::map& materialMap )
    {
    	int sizeOfNodeNameString = 0;
    	std::string nodeNameString;
    	binaryInputStream->read( (char*) &sizeOfNodeNameString, sizeof( sizeOfNodeNameString ) );
    	nodeNameString.resize( sizeOfNodeNameString );
    	binaryInputStream->read( (char*) &nodeNameString[0], sizeOfNodeNameString );
    	importCBMesh->setNodeName( nodeNameString );
    
    	// Node Index
    	int nodeIndex = 0;
    	binaryInputStream->read( (char*) &nodeIndex, sizeof( nodeIndex ) );
    	importCBMesh->setNodeIndex( nodeIndex );
    
    	// Parent Node Index
    	int parentNodeIndex = -1;
    	binaryInputStream->read( (char*) &parentNodeIndex, sizeof( parentNodeIndex ) );
    	importCBMesh->setParentIndex( parentNodeIndex );
    
    	Matrix44 localToWorldTransform;
    	binaryInputStream->read( (char*) &localToWorldTransform.matrixData, sizeof( Matrix44 ) );
    	importCBMesh->setInitialLocalToWorldTransform( localToWorldTransform );
    
    	Matrix44 worldToLocalTransform;
    	binaryInputStream->read( (char*) &worldToLocalTransform.matrixData[0], sizeof( Matrix44 ) ); 
    	importCBMesh->setInitialWorldToLocalTransform( worldToLocalTransform );
    
    	int numberOfAnimTransforms = 0;
    	binaryInputStream->read( (char*) &numberOfAnimTransforms, sizeof( numberOfAnimTransforms ) );
    
    
    	for ( int i = 0; i < numberOfAnimTransforms; ++i ) {
    
    		Matrix44 animTransform;
    		binaryInputStream->read( (char*) &animTransform.matrixData[0], sizeof( Matrix44 ) );
    		importCBMesh->addAnimationTransform( animTransform );
    	}
    
    	extractSkinnedTriBatchDataFromMeshNode( binaryInputStream, importCBMesh, materialMap );
    }
    
    
    void CB3DSMaxImporter::extractBoneNodeDataFromCBEImportBinaryFile( std::ifstream* binaryInputStream,
    	CBNode* importCBNode,
    	std::map& materialMap,
    	EntityMesh* entityMesh ) 
    {
    	UNUSED( materialMap );
    	int sizeOfNodeNameString = 0;
    	std::string nodeNameString;
    	binaryInputStream->read( (char*) &sizeOfNodeNameString, sizeof( sizeOfNodeNameString ) );
    	nodeNameString.resize( sizeOfNodeNameString );
    	binaryInputStream->read( (char*) &nodeNameString[0], sizeOfNodeNameString );
    	importCBNode->setNodeName( nodeNameString );
    
    	// Node Index
    	int nodeIndex = 0;
    	binaryInputStream->read( (char*) &nodeIndex, sizeof( nodeIndex ) );
    	importCBNode->setNodeIndex( nodeIndex );
    
    	// Bone Index
    	int boneIndex = 0;
    	binaryInputStream->read( (char*) &boneIndex, sizeof( boneIndex ) );
    	importCBNode->setBoneIndex( boneIndex );
    
    	// Parent Node Index
    	int parentNodeIndex = -1;
    	binaryInputStream->read( (char*) &parentNodeIndex, sizeof( parentNodeIndex ) );
    	importCBNode->setParentIndex( parentNodeIndex );
    
    	Matrix44 localToWorldTransform;
    	binaryInputStream->read( (char*) &localToWorldTransform.matrixData[0], sizeof( Matrix44 ) );
    	importCBNode->setInitialLocalToWorldTransform( localToWorldTransform );
    
    	Matrix44 worldToLocalTransform;
    	binaryInputStream->read( (char*) &worldToLocalTransform.matrixData[0], sizeof( Matrix44 ) ); 
    	importCBNode->setInitialWorldToLocalTransform( worldToLocalTransform );
    
    	int numberOfAnimTransforms = 0;
    	binaryInputStream->read( (char*) &numberOfAnimTransforms, sizeof( numberOfAnimTransforms ) );
    
    	for ( int i = 0; i < numberOfAnimTransforms; ++i ) 
    	{
    		Matrix44 animTransform;
    		binaryInputStream->read( (char*) &animTransform.matrixData[0], sizeof( Matrix44 ) );
    		importCBNode->addAnimationTransform( animTransform );
    	}
    
    	entityMesh->addBoneNodeToBoneHashMap( boneIndex, importCBNode );
    }
    
    
    void CB3DSMaxImporter::extractTriBatchDataFromMeshNode( std::ifstream* binaryInputStream,
    	CBMesh* meshToExtractFrom,
    	std::map& materialMap )
    {
    	int numTriBatchesInMesh = 0;
    	binaryInputStream->read( (char*) &numTriBatchesInMesh, sizeof( numTriBatchesInMesh ) );
    
    	for ( int i = 0; i < numTriBatchesInMesh; ++i ) {
    
    		TriBatchData triBatchData;
    
    		int materialIndex = 0;
    		binaryInputStream->read( (char*) &materialIndex, sizeof( materialIndex ) );
    		triBatchData.materialIndex = materialIndex;
    
    		int numVertsInTriBatch = 0;
    		binaryInputStream->read( (char*) &numVertsInTriBatch, sizeof( numVertsInTriBatch ) );
    		triBatchData.numVerts = numVertsInTriBatch;
    
    		for ( int vertIndex = 0; vertIndex < numVertsInTriBatch; ++vertIndex ) {
    
    			cbengine::Vertex triBatchVert;
    			binaryInputStream->read( (char*) &triBatchVert, sizeof( cbengine::Vertex ) );
    			// Adjust for lack of importing colors in exporter
    			triBatchVert.vertexColor.x = 1.0f;
    			triBatchVert.vertexColor.y = 1.0f;
    			triBatchVert.vertexColor.z = 1.0f;
    			triBatchVert.vertexColor.w = 1.0f;
    
    			// Adjust for our inverted tex coord importer
    			triBatchVert.vertexTextureCoords.y = 1.0f - triBatchVert.vertexTextureCoords.y;
    
    			triBatchData.verts.push_back( triBatchVert );
    		}
    
    		std::map::iterator it;
    		it = materialMap.find( triBatchData.materialIndex );
    		if ( it != materialMap.end() ) {
    
    			const MaterialData& matdata = it->second;
    			CBTriangleBatch* triBatchToAdd = new CBTriangleBatch( meshToExtractFrom, triBatchData.verts );
    
    			triBatchToAdd->setDiffuseTexture( matdata.diffuseTextureFilePath );
    			triBatchToAdd->setNormalMapTexture( matdata.normalMapTextureFilePath );
    			triBatchToAdd->setSpecularLevelTexture( matdata.specularLevelTextureFilePath );
    			triBatchToAdd->setSpecularColorTexture( matdata.specularColorTextureFilePath );
    			triBatchToAdd->setEmissiveTexture( matdata.emissiveTextureFilePath );
    
    			//triBatchToAdd->createVBOAndMaterial();
    			
    			// Add trasnform matrix
    			assert( triBatchToAdd != nullptr );
    			meshToExtractFrom->addTriangleBatchToMesh( triBatchToAdd );
    
    		} else {
    			assert( true == true );
    		}
    	}
    }
    
    
    void CB3DSMaxImporter::extractSkinnedTriBatchDataFromMeshNode( std::ifstream* binaryInputStream,
    	CBSkinnedMesh* meshToExtractFrom,
    	std::map& materialMap ) 
    {
    	int numTriBatchesInMesh = 0;
    	binaryInputStream->read( (char*) &numTriBatchesInMesh, sizeof( numTriBatchesInMesh ) );
    
    	for ( int i = 0; i < numTriBatchesInMesh; ++i ) {
    
    		TriBatchData triBatchData;
    
    		int materialIndex = 0;
    		binaryInputStream->read( (char*) &materialIndex, sizeof( materialIndex ) );
    		triBatchData.materialIndex = materialIndex;
    
    		int numVertsInTriBatch = 0;
    		binaryInputStream->read( (char*) &numVertsInTriBatch, sizeof( numVertsInTriBatch ) );
    		triBatchData.numVerts = numVertsInTriBatch;
    
    		for ( int vertIndex = 0; vertIndex < numVertsInTriBatch; ++vertIndex ) {
    
    			cbengine::Vertex triBatchVert;
    			binaryInputStream->read( (char*) &triBatchVert, sizeof( cbengine::Vertex ) );
    			// Adjust for lack of importing colors in exporter
    			triBatchVert.vertexColor.x = 1.0f;
    			triBatchVert.vertexColor.y = 1.0f;
    			triBatchVert.vertexColor.z = 1.0f;
    			triBatchVert.vertexColor.w = 1.0f;
    
    			triBatchVert.vertexBoneIndexes.x = BONE_INDEX_NOT_USED;
    			triBatchVert.vertexBoneIndexes.y = BONE_INDEX_NOT_USED;
    			triBatchVert.vertexBoneIndexes.z = BONE_INDEX_NOT_USED;
    			triBatchVert.vertexBoneIndexes.w = BONE_INDEX_NOT_USED;
    
    			// Adjust for our inverted tex coord importer
    			triBatchVert.vertexTextureCoords.y = 1.0f - triBatchVert.vertexTextureCoords.y;
    
    			std::vector boneDataVector;
    			int numBonesForVert = 0;
    			binaryInputStream->read( (char*) &numBonesForVert, sizeof( numBonesForVert ) );
    
    			for ( int boneIndex = 0; boneIndex < numBonesForVert; ++boneIndex ) {
    
    				BoneData boneData;
    				binaryInputStream->read( (char*) &boneData.boneIndex, sizeof( boneData.boneIndex ) );
    				binaryInputStream->read( (char*) &boneData.boneWeight, sizeof( boneData.boneWeight ) );
    				boneDataVector.push_back( boneData );
    			}
    
    			float totalExtractedWeight = 0.0f;
    			for ( int boneDataIndex = 0; boneDataIndex < static_cast( boneDataVector.size() ); ++boneDataIndex ) {
    
    				BoneData& boneData = boneDataVector[ boneDataIndex ];
    				
    				if ( boneDataIndex == ( MAX_NUM_BONES_TO_EXTRACT - 1 ) ) {
    
    					boneData.boneWeight = 1.0f - totalExtractedWeight;
    				}
    
    				totalExtractedWeight += boneData.boneWeight;
    
    				switch( boneDataIndex ) {
    				case 0:
    					triBatchVert.vertexBoneIndexes.x = boneData.boneIndex;
    					triBatchVert.vertexBoneWeights.x = boneData.boneWeight;
    					break;
    				case 1:
    					triBatchVert.vertexBoneIndexes.y = boneData.boneIndex;
    					triBatchVert.vertexBoneWeights.y = boneData.boneWeight;
    					break;
    				case 2:
    					triBatchVert.vertexBoneIndexes.z = boneData.boneIndex;
    					triBatchVert.vertexBoneWeights.z = boneData.boneWeight;
    					break;
    				case 3:
    					triBatchVert.vertexBoneIndexes.w = boneData.boneIndex;
    					triBatchVert.vertexBoneWeights.w = boneData.boneWeight;
    					break;
    				}
    			}
    
    			triBatchData.verts.push_back( triBatchVert );
    		}
    
    		std::map::iterator it;
    		it = materialMap.find( triBatchData.materialIndex );
    		if ( it != materialMap.end() ) {
    
    			const MaterialData& matdata = it->second;
    			CBSkinnedTriangleBatch* triBatchToAdd = new CBSkinnedTriangleBatch( meshToExtractFrom, triBatchData.verts );
    			
    			triBatchToAdd->setDiffuseTexture(  matdata.diffuseTextureFilePath );
    			triBatchToAdd->setNormalMapTexture( matdata.normalMapTextureFilePath );
    			triBatchToAdd->setSpecularLevelTexture( matdata.specularLevelTextureFilePath );
    			triBatchToAdd->setSpecularColorTexture( matdata.specularColorTextureFilePath );
    			triBatchToAdd->setEmissiveTexture( matdata.emissiveTextureFilePath );
    			
    			// Can't do this here with SkinnedTriBatches
    			//triBatchToAdd->createVBOAndMaterial();
    			
    			// Add trasnform matrix
    			assert( triBatchToAdd != nullptr );
    			meshToExtractFrom->addSkinnedTriangleBatchToMesh( triBatchToAdd );
    
    		} else {
    			assert( true == true );
    		}
    	}
    
    }
    
    void CB3DSMaxImporter::cleanUpTextureNames( std::string& textureName ) {
    
    	const std::string NoTextureString( "None" );
    	if ( textureName == NoTextureString || textureName.empty() ) {
    		return;
    	}
    
    	int index = textureName.size() - 1;
    	std::string textureNameCleaned;
    
    	while ( ( index >= 0 ) && textureName[index] != '\\' ) {
    
    		textureNameCleaned += textureName[index];
    		--index;
    	}
    
    	std::reverse( textureNameCleaned.begin(), textureNameCleaned.end() );
    
    	if ( textureNameCleaned.size() > 3 ) {
    
    		std::string replaceString( "png" );
    		std::string checkForDDS( "sdd" );
    		std::string currentString;
    		for ( int i = ( textureNameCleaned.size() - 1 ); i >= 0; --i ) {
    
    			currentString += textureNameCleaned[i];
    			if ( currentString == checkForDDS ) {
    				textureNameCleaned[ textureNameCleaned.size() - 1 ] = replaceString[2];
    				textureNameCleaned[ textureNameCleaned.size() - 2 ] = replaceString[1];
    				textureNameCleaned[ textureNameCleaned.size() - 3 ] = replaceString[0];
    			}
    		}
    	}
    
    	if ( index == -1 ) {
    		textureName = "None";
    	}
    	
    	textureName.clear();
    	textureName += TEXTURE_DIRECTORY;
    	textureName += textureNameCleaned;
    }
    							
  • EntityMesh.hpp

    #ifndef included_EntityMesh
    #define included_EntityMesh
    #pragma once
    
    #include 
    #include 
    #include 
    
    #include "EngineCommon.hpp"
    
    #include "Texture.hpp"
    #include "Material.hpp"
    
    #include "Matrix44.hpp"
    #include "Light.hpp"
    #include "Vector3D.hpp"
    #include "EulerAngles.hpp"
    
    /*
    	EntityMesh is always the top level node of the scene graph. It encapsulates all exported mesh data.
    	It houses root transform data, bone node data, and other convenience functionality such as duplication
    */
    
    
    typedef enum
    {
    	EM_TYPE_FR,
    	EM_TYPE_MRT,
    	EM_TYPE_FBO_DEBUG,
    	EM_TYPE_DR,
    
    } EntityMeshShaderType;
    
    const float ENTITY_MESH_DEFAULT_SCALE_VALUE = 1.0f;
    
    class CBNode;
    class GameObject;
    
    class EntityMesh 
    {
    public:
    	friend class CBNode;
    	friend class CBMesh;
    	friend class CBSkinnedMesh;
    
    	static EntityMesh* CreateEntityMeshAs2DQuad( float quadWidth, float quadHeight, EntityMeshShaderType shaderType );
    
    	~EntityMesh();
    	explicit EntityMesh( CBNode* rootNode );
    	EntityMesh( const EntityMesh& meshToCopy );
    
    	EntityMesh* clone( const EntityMesh& meshToClone );
    
    	void update( float deltaSeconds );
    	void render( float deltaSeconds ) const;
    
    	// Inline Convenience Functions
    	void addBoneNodeToBoneHashMap( int boneNodeIndex, CBNode* boneNode );
    	void buildBoneTransformMatrixVector();
    	const CBNode* getBoneNodeWithIndex( int boneIndex ) const;
    
    	void createVBOAndMaterialForEntityMesh();
    	void bindDiffuseTextureForAllTriBatches( cbengine::Texture* diffuseTexture );
    	void bindTextureForAllTriBatches( cbengine::Texture* textureToBind, Material::Texture_Slot materialTextureSlot );
    
    	// Inline Mutators
    	const CBNode* getRootNode() const;
    	bool isBoneTransformMatrixVectorDirty() const;
    
    	void setObjectOwner( GameObject* objectOwner );
    	void setPosition( const cbengine::Vector3& entityMeshPos );
    	void setOrientationDegrees( const EulerAngles& orientationDegrees );
    
    	cbengine::Vector3			m_position;
    	cbengine::Vector3			m_scale;
    	EulerAngles							m_orientationDegrees;
    
    protected:
    
    	void applyMatrixTransforms() const;
    	void popMatrixTransforms() const;
    
    	void setEntityMeshDefaults();
    private:
    	// Copy Ctor Functions
    	void copyNonSceneGraphDataAndSetDefaults( const EntityMesh& meshToCopy );
    	void copyAndReconstructSceneGraph( const EntityMesh& meshToCopy );
    	void traverseSceneGraphAndRegisterBoneNodes();
    
    	GameObject*							m_objectOwner;
    	CBNode*								m_rootEntityMeshNode;
    	std::unordered_map		m_boneNodes;
    	mutable Matrix44				m_currentTransformMatrix;
    
    	bool								m_isSkeletalMesh;
    };
    
    
    inline void EntityMesh::setPosition( const cbengine::Vector3& entityMeshPos )
    {
    	m_position = entityMeshPos;
    }
    
    
    inline void EntityMesh::setOrientationDegrees( const EulerAngles& orientationDegrees )
    {
    	m_orientationDegrees = orientationDegrees;
    }
    
    
    inline void EntityMesh::addBoneNodeToBoneHashMap( int boneNodeIndex, CBNode* boneNode ) 
    {
    	assert( boneNode != nullptr );
    	m_boneNodes.insert( std::pair( boneNodeIndex, boneNode ) );
    	m_isSkeletalMesh = true;
    }
    
    
    inline const CBNode* EntityMesh::getRootNode() const 
    {
    	return m_rootEntityMeshNode;
    }
    
    
    inline const CBNode* EntityMesh::getBoneNodeWithIndex( int boneIndex ) const 
    {
    	CBNode* boneNodeToReturn = nullptr;
    	std::unordered_map::const_iterator itBone = m_boneNodes.find( boneIndex );
    
    	if ( itBone != m_boneNodes.end() ) {
    
    		boneNodeToReturn = itBone->second;
    	}
    
    	return boneNodeToReturn;
    }
    
    
    inline void EntityMesh::setObjectOwner( GameObject* objectOwner )
    {
    	m_objectOwner = objectOwner;
    }
    
    #endif
    							
  • EntityMesh.cpp

    #include "EntityMesh.hpp"
    
    #include 
    
    #include "CBNode.hpp"
    #include "CBMesh.hpp"
    #include "CBTriangleBatch.hpp"
    #include "Material.hpp"
    
    #include "Vertex.hpp"
    #include "MatrixStack.hpp"
    #include "MathUtil.hpp"
    #include "GameObject.hpp"
    
    #include "../../CBEngine/EngineCode/MemoryMacros.hpp"
    
    
    
    EntityMesh::~EntityMesh() 
    {
    	if ( m_rootEntityMeshNode != nullptr ) 
    	{
    		m_rootEntityMeshNode->freeMemoryAndCleanUpBeforeDeletion();
    		delete m_rootEntityMeshNode;
    	}
    }
    
    
    EntityMesh::EntityMesh( CBNode* rootNode ) 
    {
    	setEntityMeshDefaults();
    
    	assert( rootNode != nullptr );
    	m_rootEntityMeshNode = rootNode;
    }
    
    
    EntityMesh::EntityMesh( const EntityMesh& meshToCopy )
    {
    	copyNonSceneGraphDataAndSetDefaults( meshToCopy );
    	copyAndReconstructSceneGraph( meshToCopy );
    }
    
    
    EntityMesh* EntityMesh::clone( const EntityMesh& meshToClone )
    {
    	return new EntityMesh( meshToClone );
    }
    
    
    void EntityMesh::update( float deltaSeconds ) 
    {
    	//m_orientationDegrees.yawDegreesAboutZ += ( 0.016f * 10.0f );
    
    	m_rootEntityMeshNode->update( deltaSeconds );	
    }
    
    
    void EntityMesh::render( float deltaSeconds ) const 
    {
    	applyMatrixTransforms();
    
    	m_rootEntityMeshNode->render( deltaSeconds );
    
    	popMatrixTransforms();
    }
    
    
    void EntityMesh::applyMatrixTransforms() const
    {
    	MatrixStack* sharedMatrixStack = MatrixStack::getSharedMatrixStack();
    
    	// Translate === Rotate === Scale //
    
    	// Translate
    	Matrix44 translationMatrix = Matrix44::CreateTranslateMatrixFloat( m_position );
    
    	// Rotate
    	float rollRadiansAboutX = cbengine::DegreesToRadians( m_orientationDegrees.rollDegreesAboutX );
    	float pitchRadiansAboutY = cbengine::DegreesToRadians( m_orientationDegrees.pitchDegreesAboutY );
    	float yawRadiansAboutZ = cbengine::DegreesToRadians( m_orientationDegrees.yawDegreesAboutZ );
    
    	Matrix44 rotationAboutXAxis = Matrix44::CreateCannonicalRotationAboutXMatrixFloat( rollRadiansAboutX );
    	Matrix44 rotationAboutYAxis = Matrix44::CreateCannonicalRotationAboutYMatrixFloat( pitchRadiansAboutY );
    	Matrix44 rotationAboutZAxis = Matrix44::CreateCannonicalRotationAboutZMatrixFloat( yawRadiansAboutZ );
    
    	Matrix44 rotXMultRotY;
    	Matrix44::MatrixMultiply( rotationAboutXAxis, rotationAboutYAxis, rotXMultRotY );
    	Matrix44 rotZMultRotXY;
    	Matrix44::MatrixMultiply( rotationAboutZAxis, rotXMultRotY, rotZMultRotXY );
    	
    	Matrix44 transformMatrix;
    	Matrix44::MatrixMultiply( rotZMultRotXY, translationMatrix, transformMatrix );
    
    	// Scale
    	Matrix44::applyNonUniformScalingToFloatMatrix( m_scale, transformMatrix );
    	sharedMatrixStack->applyTransformAndPushToStack( transformMatrix );
    
    	// If Mesh has has owner, incorporate owner's transforms into model matrix
    	if ( m_objectOwner != nullptr )
    	{
    		const Matrix44& ownerTransform = m_objectOwner->m_currentTransformMatrix;
    		Matrix44 meshTransformMultOwnerTransform;
    		Matrix44::MatrixMultiply( transformMatrix, ownerTransform, meshTransformMultOwnerTransform );
    		Matrix44::CopyFloatMatrix( meshTransformMultOwnerTransform, m_currentTransformMatrix );
    	}
    	else
    	{
    		Matrix44::CopyFloatMatrix( transformMatrix, m_currentTransformMatrix );
    	}
    }
    
    
    void EntityMesh::popMatrixTransforms() const
    {
    	MatrixStack* sharedMatrixStack = MatrixStack::getSharedMatrixStack();
    	sharedMatrixStack->popFromTopOfStack();
    }
    
    
    void EntityMesh::setEntityMeshDefaults() 
    {
    	m_objectOwner = nullptr;
    	m_rootEntityMeshNode = nullptr;
    	m_isSkeletalMesh = false;
    
    	m_scale.x = ENTITY_MESH_DEFAULT_SCALE_VALUE;
    	m_scale.y = ENTITY_MESH_DEFAULT_SCALE_VALUE;
    	m_scale.z = ENTITY_MESH_DEFAULT_SCALE_VALUE;
    }
    
    
    void EntityMesh::createVBOAndMaterialForEntityMesh()
    {
    	m_rootEntityMeshNode->createVBOAndMaterial();
    }
    
    
    void EntityMesh::bindDiffuseTextureForAllTriBatches( cbengine::Texture* diffuseTexture )
    {
    	if ( diffuseTexture == nullptr || m_rootEntityMeshNode == nullptr )
    	{
    		return;
    	}
    
    	m_rootEntityMeshNode->bindDiffuseTextureForAllTriBatches( diffuseTexture );
    }
    
    
    void EntityMesh::bindTextureForAllTriBatches( cbengine::Texture* textureToBind, Material::Texture_Slot materialTextureSlot )
    {
    	if ( textureToBind == nullptr || m_rootEntityMeshNode == nullptr )
    	{
    		return;
    	}
    
    	m_rootEntityMeshNode->bindTextureForAllTriBatches( textureToBind, materialTextureSlot );
    }
    
    
    void EntityMesh::copyNonSceneGraphDataAndSetDefaults( const EntityMesh& meshToCopy )
    {
    	m_currentTransformMatrix = Matrix44::CreateIdentityMatrixFloat();
    	m_position = cbengine::Vector3( 0.0f, 0.0f, 0.0f ); // Position will not be copied as the copy blueprint could be positioned relative to an owner
    	m_scale = meshToCopy.m_scale;
    	m_orientationDegrees = meshToCopy.m_orientationDegrees;
    	m_isSkeletalMesh = meshToCopy.m_isSkeletalMesh;
    	m_objectOwner = nullptr;
    	m_rootEntityMeshNode = nullptr;
    }
    
    
    void EntityMesh::copyAndReconstructSceneGraph( const EntityMesh& meshToCopy )
    {
    	const CBNode* rootNodeToCopy = meshToCopy.m_rootEntityMeshNode;
    	m_rootEntityMeshNode = new CBNode( *(rootNodeToCopy) );
    	m_rootEntityMeshNode->setEntityMeshParentForSelfAndAllChildren( this );
    
    	traverseSceneGraphAndRegisterBoneNodes();
    }
    
    
    void EntityMesh::traverseSceneGraphAndRegisterBoneNodes()
    {
    	if ( m_isSkeletalMesh && m_rootEntityMeshNode != nullptr )
    	{
    		m_rootEntityMeshNode->traverseSceneGraphForBoneRegistration();
    	}
    }
    
    
    // STATIC FACTORY FUNCTIONS
    STATIC EntityMesh* EntityMesh::CreateEntityMeshAs2DQuad( float quadWidth, float quadHeight, EntityMeshShaderType shaderType )
    {
    	std::vector verts;
    
    	cbengine::Vertex quadVerts;
    	quadVerts.vertexColor.x = 1.0f;
    	quadVerts.vertexColor.y = 1.0f;
    	quadVerts.vertexColor.z = 1.0f;
    	quadVerts.vertexColor.w = 1.0f;
    
    	// South West
    	quadVerts.vertexTextureCoords.x = 0.0f;
    	quadVerts.vertexTextureCoords.y = 0.0f;
    	quadVerts.vertexPosition.x = 0.0f;
    	quadVerts.vertexPosition.y = 0.0f;
    	quadVerts.vertexPosition.z = 0.0f;
    	quadVerts.vertexNormal.x = -1.0f;
    	quadVerts.vertexNormal.y = 0.0f;
    	quadVerts.vertexNormal.z = 0.0f;
    	verts.push_back( quadVerts );
    
    	// SouthEast
    	quadVerts.vertexTextureCoords.x = 1.0f;
    	quadVerts.vertexTextureCoords.y = 0.0f;
    	quadVerts.vertexPosition.x = quadWidth;
    	quadVerts.vertexPosition.y = 0.0f;
    	quadVerts.vertexPosition.z = 0.0f;
    	quadVerts.vertexNormal.x = -1.0f;
    	quadVerts.vertexNormal.y = 0.0f;
    	quadVerts.vertexNormal.z = 0.0f;
    	verts.push_back( quadVerts );
    
    	// NorthEast
    	quadVerts.vertexTextureCoords.x = 1.0f;
    	quadVerts.vertexTextureCoords.y = 1.0f;
    	quadVerts.vertexPosition.x = quadWidth;
    	quadVerts.vertexPosition.y = quadHeight;
    	quadVerts.vertexPosition.z = 0.0f;
    	quadVerts.vertexNormal.x = -1.0f;
    	quadVerts.vertexNormal.y = 0.0f;
    	quadVerts.vertexNormal.z = 0.0f;
    	verts.push_back( quadVerts );
    
    	// NorthWest
    	quadVerts.vertexTextureCoords.x = 0.0f;
    	quadVerts.vertexTextureCoords.y = 1.0f;
    	quadVerts.vertexPosition.x = 0.0f;
    	quadVerts.vertexPosition.y = quadHeight;
    	quadVerts.vertexPosition.z = 0.0f;
    	quadVerts.vertexNormal.x = -1.0f;
    	quadVerts.vertexNormal.y = 0.0f;
    	quadVerts.vertexNormal.z = 0.0f;
    	verts.push_back( quadVerts );
    	
    	CBNode*				rootNode	= new CBNode;
    	EntityMesh*			quadEntity	= new EntityMesh( rootNode );
    	CBMesh*				quadMesh	= new CBMesh( quadEntity );
    	CBTriangleBatch*	quadBatch	= new CBTriangleBatch( quadMesh, verts );
    
    	rootNode->setParentNode( nullptr );
    	rootNode->setEntityMesh( quadEntity );
    	quadMesh->setParentNode( rootNode );
    	quadMesh->setEntityMesh( quadEntity );
    	rootNode->addChildToNode( quadMesh );
    	quadBatch->setDrawPrimitive( cbengine::kQUADS ); // PR TODO:: Refactor to triangles
    	quadMesh->addTriangleBatchToMesh( quadBatch );
    
    	std::string vertexFilePath;
    	std::string vertexFileName;
    	std::string fragmentFilePath;
    	std::string fragmentFileName;
    
    	if ( shaderType == EM_TYPE_FBO_DEBUG )
    	{
    		// PR TODO:: Remove this temporary hard coding on next refactor
    		vertexFilePath		= "Shaders/FBOShader_330.vertex.glsl";
    		vertexFileName		= "FBOShader_330.vertex.glsl";
    		fragmentFilePath	= "Shaders/FBOShader_330.fragment.glsl";
    		fragmentFileName	= "FBOShader_330.fragment.glsl";
    
    		quadBatch->createVBOAndMaterial( vertexFilePath, vertexFileName, fragmentFilePath, fragmentFileName );
    	}
    	else if ( shaderType == EM_TYPE_FR )
    	{
    		quadBatch->createVBOAndMaterial();
    	}
    	else if ( shaderType == EM_TYPE_MRT )
    	{
    		quadBatch->createVBOAndMaterial();
    	}
    	else if ( shaderType == EM_TYPE_DR )
    	{
    		vertexFilePath		= "Shaders/DR_FinalPass_330.vertex.glsl";
    		vertexFileName		= "DR_FinalPass_330.vertex.glsl";
    		fragmentFilePath	= "Shaders/DR_FinalPass_330.fragment.glsl";
    		fragmentFileName	= "DR_FinalPass_330.fragment.glsl";
    
    		quadBatch->createVBOAndMaterial( vertexFilePath, vertexFileName, fragmentFilePath, fragmentFileName );
    	}
    
    	return quadEntity;
    }
    							
  • CBNode.hpp

    #ifndef included_CBNode
    #define included_CBNode
    #pragma once
    
    #include 
    #include 
    #include 
    
    #include "EngineCommon.hpp"
    
    #include "Texture.hpp"
    #include "Material.hpp"
    #include "Matrix44.hpp"
    
    const int BONE_INDEX_NONE = -1;
    const int PARENT_INDEX_NONE = -1;
    
    class EntityMesh;
    
    class CBNode 
    {
    public:
    	friend class EntityMesh;
    
    	virtual ~CBNode();
    	explicit CBNode();
    	explicit CBNode( EntityMesh* entityMesh );
    
    	virtual CBNode* clone(); // Virtual Copy Constructor Idiom
    
    	virtual void update( float deltaSeconds );
    	virtual void render( float deltaSeconds ) const;
    
    	virtual void addChildToNode( CBNode* childToAdd );
    	virtual void addAnimationTransform( const Matrix44& animTransform );
    	virtual const Matrix44& getAnimationAtTime( float animationDuration ) const;
    
    	virtual void traverseSceneGraphForBoneRegistration();
    
    	virtual void freeMemoryAndCleanUpBeforeDeletion();
    
    	// Inline Mutators
    	virtual void setInitialLocalToWorldTransform( const Matrix44& localToWorldTransform	);
    	virtual void setInitialWorldToLocalTransform( const Matrix44& worldToLocalTransform );
    	virtual const Matrix44& getInitialLocalToWorldTransform() const;
    	virtual const Matrix44& getInitialWorldToLocalTransform() const;
    
    	virtual void createVBOAndMaterial();
    	virtual void bindDiffuseTextureForAllTriBatches( cbengine::Texture* diffuseTexture );
    	virtual void bindTextureForAllTriBatches( cbengine::Texture* textureToBind, Material::Texture_Slot materialTextureSlot );
    
    	virtual void setEntityMesh( EntityMesh* entityMesh );
    	virtual const EntityMesh* getEntityMesh() const;
    	virtual void setEntityMeshParentForSelfAndAllChildren( EntityMesh* entityMesh );
    
    	virtual void setParentNode( CBNode* parentNode );
    	virtual void setNodeIndex( int index );
    	virtual int getNodeIndex() const;
    	virtual void setBoneIndex( int boneIndex );
    	virtual int getBoneIndex() const;
    	virtual void setParentIndex( int parentIndex );
    	virtual int getParentIndex() const;
    
    	const std::string& getNodeName();
    	void setNodeName( const std::string& nodeName );
    
    protected:
    	// Copy Ctor Functions
    	explicit CBNode( const CBNode& nodeToCopy );
    	virtual void createCopyOfNode( const CBNode& nodeToCopy );
    	virtual void setCopyDefaults();
    
    	virtual void applyMatrixTransforms() const;
    
    	std::string						m_nodeName;
    	EntityMesh*						m_entityMesh;
    	CBNode*							m_parentNode;
    	std::vector			m_childrenNodes;
    	int								m_parentIndex;
    	int								m_nodeIndex;
    	int								m_boneIndex;
    
    	Matrix44					m_initialLocalToWorldTransform;
    	Matrix44					m_initialWorldToLocalTransform;
    	std::vector>	m_animationTransforms;
    	float							m_animationDuration;
    
    	bool							m_isVisible;
    private:
    	void setNodeDefaults();
    
    };
    
    // Inline Mutators
    inline void CBNode::addChildToNode( CBNode* childToAdd ) {
    
    	assert( childToAdd != nullptr );
    	m_childrenNodes.push_back( childToAdd );
    }
    
    
    inline void CBNode::addAnimationTransform( const Matrix44& animTransform ) {
    
    	m_animationTransforms.push_back( animTransform );
    }
    
    
    inline void CBNode::setInitialLocalToWorldTransform( const Matrix44& localToWorldTransform ) {
    
    	m_initialLocalToWorldTransform = localToWorldTransform;
    }
    
    
    inline void CBNode::setInitialWorldToLocalTransform( const Matrix44& worldToLocalTransform ) {
    
    	m_initialWorldToLocalTransform = worldToLocalTransform;
    }
    
    inline const Matrix44& CBNode::getInitialLocalToWorldTransform() const {
    
    	return m_initialLocalToWorldTransform;
    }
    
    
    inline const Matrix44& CBNode::getInitialWorldToLocalTransform() const {
    
    	return m_initialWorldToLocalTransform;
    }
    
    
    inline void CBNode::setParentNode( CBNode* parentNode ) {
    
    	m_parentNode = parentNode;
    }
    
    
    inline void CBNode::setNodeIndex( int index ) {
    	m_nodeIndex = index;
    }
    
    
    inline int CBNode::getNodeIndex() const {
    	return m_nodeIndex;
    }
    
    
    inline void CBNode::setBoneIndex( int boneIndex ) {
    
    	m_boneIndex = boneIndex;
    }
    
    
    
    inline int CBNode::getBoneIndex() const {
    
    	return m_boneIndex;
    }
    
    
    inline void CBNode::setParentIndex( int parentIndex ) {
    
    	m_parentIndex = parentIndex;
    }
    
    
    inline int CBNode::getParentIndex() const {
    
    	return m_parentIndex;
    }
    
    
    inline const std::string& CBNode::getNodeName() {
    
    	return m_nodeName;
    }
    
    
    inline void CBNode::setNodeName( const std::string& nodeName ) {
    
    	if ( nodeName == "" ) {
    		m_nodeName = "Unknown";
    	} else {
    		m_nodeName = nodeName;
    	}
    }
    
    #endif
    							
  • CBNode.cpp

    #include "CBNode.hpp"
    
    #include "MatrixStack.hpp"
    
    #include "EntityMesh.hpp"
    
    #include "../../CBEngine/EngineCode/MemoryMacros.hpp"
    
    CBNode::~CBNode() 
    {
    	
    }
    
    
    CBNode::CBNode() 
    {
    	setNodeDefaults();
    }
    
    
    CBNode::CBNode( EntityMesh* entityMesh ) 
    {
    	setNodeDefaults();
    	setEntityMesh( entityMesh );
    }
    
    
    CBNode::CBNode( const CBNode& nodeToCopy )
    {
    	setCopyDefaults();
    	createCopyOfNode( nodeToCopy );
    }
    
    // Virtual Copy Constructor Idiom
    CBNode* CBNode::clone()
    {
    	return new CBNode( *(this) );
    }
    
    
    void CBNode::update( float deltaSeconds ) 
    {	
    	m_animationDuration = m_animationDuration + deltaSeconds;
    
    	for ( size_t i = 0; i < m_childrenNodes.size(); ++i ) {
    
    		CBNode* childNode = m_childrenNodes[i];
    		childNode->update( deltaSeconds );
    	}
    }
    
    
    void CBNode::render( float deltaSeconds ) const 
    {
    	UNUSED(deltaSeconds);
    
    	applyMatrixTransforms();
    
    	for ( size_t i = 0; i < m_childrenNodes.size(); ++i ) 
    	{
    		CBNode* childNode = m_childrenNodes[i];
    		childNode->render( deltaSeconds );
    	}
    }
    
    
    void CBNode::applyMatrixTransforms() const
    {
    	MatrixStack* matrixStack = MatrixStack::getSharedMatrixStack();
    
    	if ( !m_animationTransforms.empty() ) 
    	{
    		int numberOfAnimationsTransforms = m_animationTransforms.size();
    		int currentAnimation = static_cast( m_animationDuration * 30 ) % numberOfAnimationsTransforms;
    		const Matrix44& animMatrix = m_animationTransforms[currentAnimation];
    		matrixStack->applyTransformAndPushToStack( animMatrix );
    		
    		Matrix44 localToWorldTransformWithEntityMesh;
    		Matrix44::MatrixMultiply( animMatrix, m_entityMesh->m_currentTransformMatrix, localToWorldTransformWithEntityMesh );
    		matrixStack->setCurrentModelTransformMatrix( localToWorldTransformWithEntityMesh );
    
    	}
    	else 
    	{
    		matrixStack->applyTransformAndPushToStack( m_initialLocalToWorldTransform );
    
    		Matrix44 localToWorldTransformWithEntityMesh;
    		Matrix44::MatrixMultiply( m_initialLocalToWorldTransform, m_entityMesh->m_currentTransformMatrix, localToWorldTransformWithEntityMesh );
    		matrixStack->setCurrentModelTransformMatrix( localToWorldTransformWithEntityMesh );
    	}
    
    	matrixStack->popFromTopOfStack();
    }
    
    // TODO:: Incorporate frames per second variable
    const Matrix44& CBNode::getAnimationAtTime( float animationDuration ) const 
    {
    
    	UNUSED( animationDuration );
    	int numberOfAnimationsTransforms = m_animationTransforms.size();
    
    	if ( numberOfAnimationsTransforms > 0 ) {
    
    		int currentAnimation = static_cast( m_animationDuration * 30 ) % numberOfAnimationsTransforms;
    		const Matrix44& animMatrix = m_animationTransforms[currentAnimation];
    		return animMatrix;
    
    	} else {
    
    		return m_initialWorldToLocalTransform;
    	}
    }
    
    
    void CBNode::createVBOAndMaterial()
    {
    	for ( size_t i = 0; i < m_childrenNodes.size(); ++i )
    	{
    		CBNode* childNode = m_childrenNodes[i];
    		childNode->createVBOAndMaterial();
    	}
    }
    
    
    void CBNode::bindDiffuseTextureForAllTriBatches( cbengine::Texture* diffuseTexture )
    {
    	for ( size_t i = 0; i < m_childrenNodes.size(); ++i )
    	{
    		CBNode* childNode = m_childrenNodes[i];
    		childNode->bindDiffuseTextureForAllTriBatches( diffuseTexture );
    	}
    }
    
    
    void CBNode::bindTextureForAllTriBatches( cbengine::Texture* textureToBind, Material::Texture_Slot materialTextureSlot )
    {
    	for ( size_t i = 0; i < m_childrenNodes.size(); ++i )
    	{
    		CBNode* childNode = m_childrenNodes[i];
    		childNode->bindTextureForAllTriBatches( textureToBind, materialTextureSlot );
    	}
    }
    
    
    void CBNode::setEntityMeshParentForSelfAndAllChildren( EntityMesh* entityMesh )
    {
    	setEntityMesh( entityMesh );
    
    	for ( size_t i = 0; i < m_childrenNodes.size(); ++i )
    	{
    		CBNode* childNode = m_childrenNodes[i];
    		childNode->setEntityMeshParentForSelfAndAllChildren( entityMesh );
    	}
    }
    
    
    void CBNode::setEntityMesh( EntityMesh* entityMesh ) {
    
    	assert( entityMesh != nullptr );
    	m_entityMesh = entityMesh;
    }
    
    
    const EntityMesh* CBNode::getEntityMesh() const 
    {
    	return m_entityMesh;
    }
    
    
    void CBNode::freeMemoryAndCleanUpBeforeDeletion()
    {
    	for ( size_t i = 0; i < m_childrenNodes.size(); ++i )
    	{
    		CBNode* childNode = m_childrenNodes[i];
    		childNode->freeMemoryAndCleanUpBeforeDeletion();
    	}
    
    	for ( size_t i = 0; i < m_childrenNodes.size(); ++i )
    	{
    		CBNode* childNode = m_childrenNodes[i];
    		delete childNode;
    	}
    
    	m_parentNode = nullptr;
    }
    
    
    void CBNode::traverseSceneGraphForBoneRegistration()
    {
    	if ( m_boneIndex != BONE_INDEX_NONE )
    	{
    		m_entityMesh->addBoneNodeToBoneHashMap( m_boneIndex, this );
    	}
    
    	for ( size_t i = 0; i < m_childrenNodes.size(); ++i )
    	{
    		CBNode* childNode = m_childrenNodes[i];
    		childNode->traverseSceneGraphForBoneRegistration();
    	}
    }
    
    
    void CBNode::setCopyDefaults()
    {
    	m_animationDuration				= 0.0f;
    	m_entityMesh					= nullptr;
    	m_parentNode					= nullptr;
    }
    
    
    void CBNode::createCopyOfNode( const CBNode& nodeToCopy )
    {
    	m_nodeName							= nodeToCopy.m_nodeName;
    	m_isVisible							= nodeToCopy.m_isVisible;
    	m_nodeIndex							= nodeToCopy.m_nodeIndex;
    	m_boneIndex							= nodeToCopy.m_boneIndex;
    	m_parentIndex						= nodeToCopy.m_parentIndex;
    
    	m_initialLocalToWorldTransform		= nodeToCopy.m_initialLocalToWorldTransform;
    	m_initialWorldToLocalTransform		= nodeToCopy.m_initialWorldToLocalTransform;
    
    	m_animationTransforms.assign( nodeToCopy.m_animationTransforms.begin(), nodeToCopy.m_animationTransforms.end() );
    
    	// Create Copies Of All Child Nodes
    	for ( size_t i = 0; i < nodeToCopy.m_childrenNodes.size(); ++i )
    	{
    		CBNode* childNodeToCopy = nodeToCopy.m_childrenNodes[i];
    		CBNode* childCopy = childNodeToCopy->clone();
    		childCopy->setParentNode( this );
    		m_childrenNodes.push_back( childCopy );
    	}
    }
    
    
    void CBNode::setNodeDefaults() {
    
    	m_initialLocalToWorldTransform = Matrix44::CreateIdentityMatrixFloat();
    	m_initialWorldToLocalTransform = Matrix44::CreateIdentityMatrixFloat();
    	m_nodeName = "Unknown";
    	m_parentNode = nullptr;
    	m_isVisible = true;
    	m_nodeIndex = 0;
    	m_boneIndex = BONE_INDEX_NONE;
    	m_parentIndex = PARENT_INDEX_NONE;
    	m_animationDuration = 0.0f;
    	m_entityMesh = nullptr;
    }
    							
  • CBSkinnedMesh.hpp

    #ifndef included_CBSkinnedMesh
    #define included_CBSkinnedMesh
    #pragma once
    
    #include "CBNode.hpp"
    
    #include 
    #include 
    #include 
    
    #include "EngineCommon.hpp"
    
    #include "Texture.hpp"
    #include "Vertex.hpp"
    #include "Vector3D.hpp"
    #include "Matrix44.hpp"
    #include "Material.hpp"
    
    class CBSkinnedTriangleBatch;
    class EntityMesh;
    
    class CBSkinnedMesh : public CBNode 
    {
    public:
    	friend class CBSkinnedTriangleBatch;
    	friend class EntityMesh;
    
    	virtual ~CBSkinnedMesh();
    	explicit CBSkinnedMesh( EntityMesh* entityMesh );
    
    	virtual CBSkinnedMesh* clone();
    
    	virtual void update( float deltaSeconds );
    	virtual void render( float deltaSeconds ) const;
    
    	virtual void createVBOAndMaterial();
    
    	virtual void freeMemoryAndCleanUpBeforeDeletion();
    
    	void addSkinnedTriangleBatchToMesh( CBSkinnedTriangleBatch* triBatchToAdd );	
    	void generateBoneTransformationMatrixUniformLocationValues();
    
    protected:
    	// Copy CTOR Functions
    	CBSkinnedMesh( const CBSkinnedMesh& meshToCopy );
    	virtual void createCopyOfNode( const CBSkinnedMesh& nodeToCopy );
    
    	void createMaterial();
    
    	void updateBoneIndexSetWithNewTriangleBatchBones( CBSkinnedTriangleBatch* triBatchToAdd );
    	void rebuildBoneTransformationMatrixVector() const;
    
    	void setSkinnedMeshDefaults();
    	void initializeBoneTMMatricesVector();
    private:
    
    	std::vector				m_skinnedTriangleBatches;
    	std::set										m_boneIndexesUsedByMesh;
    	mutable std::vector>				m_boneTransformationMatrices;
    
    };
    
    #endif
    							
  • CBSkinnedMesh.cpp

    #include "CBSkinnedMesh.hpp"
    
    #include "OpenGLShader.hpp"
    #include "OpenGLShaderProgram.hpp"
    #include "OpenGLShaderError.hpp"
    
    #include "CBSkinnedTriangleBatch.hpp"
    #include "CB3DSMaxImporter.hpp"
    #include "EntityMesh.hpp"
    #include "CB3DSMaxImporter.hpp"
    #include "Uniform.hpp"
    
    #include "Vector2.hpp"
    
    #include "MatrixStack.hpp"
    #include "TextureManager.hpp"
    
    
    #include "../../CBEngine/EngineCode/MemoryMacros.hpp"
    
    CBSkinnedMesh::~CBSkinnedMesh() 
    {
    	// Memory and Clean up handled by scene graph traversals. Do not delete memory here
    }
    
    
    CBSkinnedMesh::CBSkinnedMesh( EntityMesh* entityMesh ) 
    {
    	setSkinnedMeshDefaults();
    	setEntityMesh( entityMesh );
    	initializeBoneTMMatricesVector();
    }
    
    
    CBSkinnedMesh::CBSkinnedMesh( const CBSkinnedMesh& meshToCopy )
    {
    	setCopyDefaults();
    	createCopyOfNode( meshToCopy );
    }
    
    
    CBSkinnedMesh* CBSkinnedMesh::clone()
    {
    	return new CBSkinnedMesh( *(this) );
    }
    
    
    void CBSkinnedMesh::update( float deltaSeconds ) 
    {
    	m_animationDuration = m_animationDuration + deltaSeconds;
    
    	for ( size_t i = 0; i < m_childrenNodes.size(); ++i ) {
    
    		CBNode* childNode = m_childrenNodes[i];
    		childNode->update( deltaSeconds );
    
    	}
    }
    
    
    void CBSkinnedMesh::render( float deltaSeconds ) const 
    {
    	UNUSED(deltaSeconds);
    
    	MatrixStack* matrixStack = MatrixStack::getSharedMatrixStack();
    	Matrix44 localToWorldTransformWithEntityMesh;
    	matrixStack->setCurrentModelTransformMatrix( m_entityMesh->m_currentTransformMatrix );
    
    	rebuildBoneTransformationMatrixVector();
    
    	for ( size_t i = 0; i < m_skinnedTriangleBatches.size(); ++i ) 
    	{
    		CBSkinnedTriangleBatch* triBatch = m_skinnedTriangleBatches[i];
    		triBatch->render( deltaSeconds, m_boneTransformationMatrices );
    	}
    
    	for ( size_t i = 0; i < m_childrenNodes.size(); ++i ) 
    	{
    		CBNode* childNode = m_childrenNodes[i];
    		childNode->render( deltaSeconds );
    	}
    }
    
    
    void CBSkinnedMesh::rebuildBoneTransformationMatrixVector() const 
    {
    	assert( m_entityMesh != nullptr );
    
    	std::set::iterator itBoneIndex;
    	for ( itBoneIndex = m_boneIndexesUsedByMesh.begin(); itBoneIndex != m_boneIndexesUsedByMesh.end(); ++itBoneIndex ) 
    	{
    		int boneIndex = *(itBoneIndex);
    		const CBNode* boneNode = m_entityMesh->getBoneNodeWithIndex( boneIndex );
    
    		const Matrix44& meshWorldTM			= this->getInitialLocalToWorldTransform();
    		const Matrix44& boneWorldToLocalTM	= boneNode->getInitialWorldToLocalTransform();
    		const Matrix44& boneAnimTMForTime	= boneNode->getAnimationAtTime( m_animationDuration );
    		
    		Matrix44 meshWorldTMMultboneWorldToLocal;
    		Matrix44::MatrixMultiply( meshWorldTM, boneWorldToLocalTM, meshWorldTMMultboneWorldToLocal );
    
    		Matrix44& matrixAtIndex = m_boneTransformationMatrices[ boneIndex ];
    		Matrix44::MatrixMultiply( meshWorldTMMultboneWorldToLocal, boneAnimTMForTime, matrixAtIndex );
    	}
    }
    
    
    void CBSkinnedMesh::generateBoneTransformationMatrixUniformLocationValues() 
    {
    	for ( size_t i = 0; i < m_skinnedTriangleBatches.size(); ++i ) 
    	{
    		CBSkinnedTriangleBatch* triBatch = m_skinnedTriangleBatches[i];
    		Material* triBatchMaterial = triBatch->m_material;
    
    		int boneTMUniformLocation = triBatchMaterial->addUniformToMaterial( UNIFORM_BONE_TM_NAME, m_boneTransformationMatrices );
    		triBatch->m_boneTMUniformLocation = boneTMUniformLocation;
    	}
    }
    
    
    void CBSkinnedMesh::addSkinnedTriangleBatchToMesh( CBSkinnedTriangleBatch* triBatchToAdd ) 
    {
    	assert( triBatchToAdd != nullptr );
    	m_skinnedTriangleBatches.push_back( triBatchToAdd );
    
    	updateBoneIndexSetWithNewTriangleBatchBones( triBatchToAdd );
    }
    
    
    void CBSkinnedMesh::updateBoneIndexSetWithNewTriangleBatchBones( CBSkinnedTriangleBatch* triBatchToAdd ) 
    {
    	assert( triBatchToAdd != nullptr );
    
    	std::vector& triBatchVerts = triBatchToAdd->getVertsVectorToModify();
    
    	for ( size_t i = 0; i < triBatchVerts.size(); ++i ) {
    
    		cbengine::Vertex& vert = triBatchVerts[i];
    		if ( vert.vertexBoneIndexes.x != BONE_INDEX_NOT_USED ) 
    		{
    			m_boneIndexesUsedByMesh.insert( vert.vertexBoneIndexes.x );
    		} 
    
    		if ( vert.vertexBoneIndexes.y != BONE_INDEX_NOT_USED ) 
    		{
    			m_boneIndexesUsedByMesh.insert( vert.vertexBoneIndexes.y );
    		}
    
    		if ( vert.vertexBoneIndexes.z != BONE_INDEX_NOT_USED ) 
    		{
    			m_boneIndexesUsedByMesh.insert( vert.vertexBoneIndexes.z );
    		}
    
    		if ( vert.vertexBoneIndexes.w != BONE_INDEX_NOT_USED ) 
    		{
    			m_boneIndexesUsedByMesh.insert( vert.vertexBoneIndexes.w );
    		}
    	}
    }
    
    
    void CBSkinnedMesh::createVBOAndMaterial()
    {
    	for ( size_t i = 0; i < m_skinnedTriangleBatches.size(); ++i )
    	{
    		CBSkinnedTriangleBatch* triBatch = m_skinnedTriangleBatches[i];
    		triBatch->createVBOAndMaterial();
    	}
    
    	generateBoneTransformationMatrixUniformLocationValues();
    
    	for ( size_t i = 0; i < m_childrenNodes.size(); ++i )
    	{
    		CBNode* childNode = m_childrenNodes[i];
    		childNode->createVBOAndMaterial();
    	}
    }
    
    
    void CBSkinnedMesh::freeMemoryAndCleanUpBeforeDeletion()
    {
    	for ( size_t i = 0; i < m_childrenNodes.size(); ++i )
    	{
    		CBNode* childNode = m_childrenNodes[i];
    		childNode->freeMemoryAndCleanUpBeforeDeletion();
    	}
    
    	for ( size_t i = 0; i < m_skinnedTriangleBatches.size(); ++i )
    	{
    		CBSkinnedTriangleBatch* skinnedTriBatch = m_skinnedTriangleBatches[i];
    		delete skinnedTriBatch;
    	}
    
    	for ( size_t i = 0; i < m_childrenNodes.size(); ++i )
    	{
    		CBNode* childNode = m_childrenNodes[i];
    		delete childNode;
    	}
    
    	m_parentNode = nullptr;
    }
    
    
    void CBSkinnedMesh::setSkinnedMeshDefaults() 
    {
    
    }
    
    
    void CBSkinnedMesh::initializeBoneTMMatricesVector() 
    {
    	m_boneTransformationMatrices.resize( NUM_BONE_MATRIX_PASSED_TO_SHADER );
    
    	for ( size_t i = 0; i < m_boneTransformationMatrices.size(); ++i ) {
    
    		Matrix44& boneTM = m_boneTransformationMatrices[i];
    		boneTM = Matrix44::CreateIdentityMatrixFloat();
    	}
    
    }
    
    
    void CBSkinnedMesh::createCopyOfNode( const CBSkinnedMesh& nodeToCopy )
    {
    	m_nodeName							= nodeToCopy.m_nodeName;
    	m_isVisible							= nodeToCopy.m_isVisible;
    	m_nodeIndex							= nodeToCopy.m_nodeIndex;
    	m_boneIndex							= nodeToCopy.m_boneIndex;
    	m_parentIndex						= nodeToCopy.m_parentIndex;
    
    	m_initialLocalToWorldTransform		= nodeToCopy.m_initialLocalToWorldTransform;
    	m_initialWorldToLocalTransform		= nodeToCopy.m_initialWorldToLocalTransform;
    	m_boneIndexesUsedByMesh				= nodeToCopy.m_boneIndexesUsedByMesh;
    
    	m_animationTransforms.assign( nodeToCopy.m_animationTransforms.begin(), nodeToCopy.m_animationTransforms.end() );
    	m_boneTransformationMatrices.assign( nodeToCopy.m_boneTransformationMatrices.begin(), nodeToCopy.m_boneTransformationMatrices.end() );
    
    	// Create Copies Of All Triangle Batches
    	for ( size_t i = 0; i < nodeToCopy.m_skinnedTriangleBatches.size(); ++i )
    	{
    		const CBSkinnedTriangleBatch* triBatch = nodeToCopy.m_skinnedTriangleBatches[i];
    		CBSkinnedTriangleBatch* triBatchCopy = new CBSkinnedTriangleBatch( *(triBatch) );
    		triBatchCopy->m_parentMesh = this;
    		m_skinnedTriangleBatches.push_back( triBatchCopy );
    	}
    
    	// Create Copies Of All Child Nodes
    	for ( size_t i = 0; i < nodeToCopy.m_childrenNodes.size(); ++i )
    	{
    		CBNode* childNodeToCopy = nodeToCopy.m_childrenNodes[i];
    		CBNode* childCopy = childNodeToCopy->clone();
    		childCopy->setParentNode( this );
    		m_childrenNodes.push_back( childCopy );
    	}
    }
    							
  • SkeletalMeshShader.vertex.glsl

    #version 410
    // Vertex Shader, GLSL 4.10
    
    uniform   vec3 u_cameraPosition;
    uniform   mat4 u_boneTM[ 200 ];
    uniform   mat4 u_mvpMatrix;
    uniform	  mat4 u_cameraSpaceToWorldSpaceTransform;
    uniform   mat4 u_worldSpaceToCameraSpaceTransform;
    uniform   mat4 u_modelSpaceTransform;
    uniform   mat4 u_projectionMatrix;
    uniform   int  u_useTextures;
    in		  vec4 u_vertex;
    in		  vec4 u_color;
    
    in		  vec3 a_tangent;
    in        vec3 a_bitangent;
    in		  vec3 a_normal;
    
    in		  vec2 u_diffuseTextureCoords;
    in		  vec2 u_normalMapTextureCoords;
    in        vec2 u_bumpMapTextureCoords;
    
    in		  ivec4 a_boneIndexes;
    in		   vec4 a_boneWeights;  
    
    out vec4		v_worldPosition;
    out vec4		v_surfaceColor;
    out vec2		v_textureCoords;
    out vec3		v_cameraPosition;
    
    out vec3		v_tangent;
    out vec3		v_bitangent;
    out vec3		v_normal;
    
    void main()
    {
    	vec4 vertPosition = vec4( 0.0, 0.0, 0.0, 0.0 );
    	vec4 skinnedNormal = vec4( 0.0, 0.0, 0.0, 0.0 );
    	vec4 skinnedTangent = vec4( 0.0, 0.0, 0.0, 0.0 );
    	vec4 skinnedBitangent = vec4( 0.0, 0.0, 0.0, 0.0 );
    	vec4 normalVec4 = vec4( a_normal.x, a_normal.y, a_normal.z, 0.0 );
    	vec4 tangentVec4 = vec4( a_tangent.x, a_tangent.y, a_tangent.z, 0.0 );
    	vec4 bitangentVec4 = vec4( a_bitangent.x, a_bitangent.y, a_bitangent.z, 0.0 );
    
    	int boneIndex = 0;
    	mat4 localSpaceToWorldModel = mat4( 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 );
    
    	for ( boneIndex; boneIndex < 4; ++boneIndex ) 
    	{
    		float boneWeight = a_boneWeights[boneIndex];
    		int matrixIndex = a_boneIndexes[boneIndex];
    		mat4 currentBoneTM = u_boneTM[ matrixIndex ];
    
    		localSpaceToWorldModel += ( currentBoneTM * boneWeight );
    		vertPosition += ( ( currentBoneTM * u_vertex ) * boneWeight );
    		skinnedNormal += ( ( currentBoneTM * normalVec4 ) * boneWeight );
    		skinnedTangent += ( ( currentBoneTM * tangentVec4 ) * boneWeight );
    		skinnedBitangent += ( ( currentBoneTM * bitangentVec4 ) * boneWeight );
    	}
    
    	vertPosition.w = 1.0;
    
    	gl_Position = u_mvpMatrix * vertPosition;
    	v_worldPosition = u_modelSpaceTransform * vertPosition;
    	v_surfaceColor = u_color;
    	v_textureCoords = u_diffuseTextureCoords.xy;
    
    	// Make sure skinned TBN vectors incorporate model space rotation
    	skinnedNormal = u_modelSpaceTransform * skinnedNormal;
    	skinnedTangent = u_modelSpaceTransform * skinnedTangent;
    	skinnedBitangent = u_modelSpaceTransform * skinnedBitangent;
    	
    	v_normal = skinnedNormal.xyz;
    	v_tangent = skinnedTangent.xyz;
    	v_bitangent = skinnedBitangent.xyz;
    
    	v_cameraPosition = u_cameraPosition;
    }