• Home
  • Games
    • Darksiders III
    • From Other Suns
    • Dead & Buried
    • Chronos
    • Darksiders II: Deathinitive Ed.
    • Herobound: Spirit Champion
    • Battlecry
    • HUNT
    • Kraven Manor
    • Modern Toyfare
    • Quantum Chef
    • Dead Men Walking
    • Castle Woffordstein
  • Individual
    • Portal Rendering
    • 3dsMax Exporter
    • Artificial Intelligence
    • Particle System
    • Quake III BSP Loader
    • Modifiable Voxel Terrain
    • Miscellaneous
  • About
  • Résumé
  • Contact
Picture

Overview

  • Created custom file format for exporting 3dsMax scenes
  • Implemented exporter plugin using 3dsMax 2012 SDK
  • Imported custom file format into engine for rendering and animation playback
  • Handled animation frames, bones, skinning, cameras, textures, and vertex data
Picture

Interesting Bits

  • Initially had issues incorporating skinning data, ultimately solving it by drawing debug bones to the screen
  • Modeled custom, binary file format after the one used in Quake III BSP map files
  • Beginning of file format serves as a table of contents, where each chapter holds a cluster of similar data (vertices, bones, etc.)
  • Put vertices with the same materials together in triangle batches
Picture

Source Code

  • FileFormat.hpp
  • Exporter.cpp
//------------------------------------------------------------------------
// FileFormat.hpp
// 
// Author:      Benjamin Klingler
// Email:       bhklingler@gmail.com
// Website:     http://www.benjaminklingler.com/
// 
// Copyright (C) 2013 Benjamin Klingler. All rights reserved.
//------------------------------------------------------------------------
 
#ifndef BHKLINGLER_GUARD_FILEFORMAT_HPP
#define BHKLINGLER_GUARD_FILEFORMAT_HPP
 
#include "Vector2.hpp"
#include "Vector3.hpp"
#include "Matrix44.hpp"
 
namespace bhklingler
{
    namespace BLM
    {
        // Type definitions.
        typedef unsigned int Index;
        typedef unsigned int Size;
 
        // Constants.
        const unsigned int  VERSION_NUMBER      = 2;
        const Index         INVALID_INDEX       = static_cast< Index >( -1 );
        const Size          MAX_NUM_BONES       = 4;
        const Size          MAX_STRING_SIZE     = 64;
 
        // Overall information about the scene, including compatibility and frames per second.
        struct Header
        {
            unsigned int    version;
            unsigned int    framesPerSecond;
        };
 
        // The lumps serve as a table of contents, telling code where key pieces of information
        // are in the scene's binary file.
        enum LumpType
        {
            kSceneNodes,
            kTriangleBatches,
            kDiffuseMaterials,
            kAnimationFrames,
            kVertices,
            kIndices,
            kBones,
            kCameras,
            kMaxLumps
        };
 
        // Lump data telling where information is in a file and how many bytes long it is.
        struct Lump
        {
            unsigned int    offset;
            unsigned int    length;
        };
 
        // The vertex data of a scene node.
        struct Vertex
        {
            Vector3f        position;
            Vector2f        texCoord;
            Vector3f        normal;
            float           tangent[ 4 ];
            float           boneWeights[ MAX_NUM_BONES ];
            Index           boneIndices[ MAX_NUM_BONES ];
        };      
 
        // Diffuse material textures.
        struct DiffuseMaterial
        {
            char            name[ MAX_STRING_SIZE ];
            char            fileName[ MAX_STRING_SIZE ];
        };
 
        // Holds all information about a scene node.
        struct SceneNode
        {
            char            name[ MAX_STRING_SIZE ];
            Index           parentIndex;
            Size            numChildren;
            Index           startTriangleBatchIndex;
            Size            numTriangleBatches;
            Index           startAnimationFrameIndex;
            Size            numAnimationFrames;
            Index           startBoneIndex;
            Size            numBones;
            Matrix44f       inverseWorldTransformation;
        };
 
        // A batch of vertices for a particular material.
        struct TriangleBatch
        {
            Index           materialIndex;
            Index           startIndex;
            Size            numIndices;
        };
 
        // A frame of animation--for now, just a transformation.
        struct AnimationFrame
        {
            Matrix44f       transformation;
        };
 
        // A bone, which for now just references a scene node index.
        struct Bone
        {
            Index           sceneNodeIndex;
        };
 
        // A camera, referencing a scene node index.
        struct Camera
        {
            Index           sceneNodeIndex;
        };
    }
}
 
#endif // BHKLINGLER_GUARD_FILEFORMAT_HPP
  
//------------------------------------------------------------------------
// BlackMagicExporter.hpp
// 
// Author:      Benjamin Klingler
// Email:       bhklingler@gmail.com
// Website:     http://www.benjaminklingler.com/
// 
// Copyright (C) 2013 Benjamin Klingler. All rights reserved.
//------------------------------------------------------------------------

namespace bhklingler
{
//------------------------------------------------------------------------
    BlackMagicExporter::BlackMagicExporter()
        : m_pIGame( nullptr )
        , m_nStartBoneIndexForCurrentSceneNode( BLM::INVALID_INDEX )
    {

    }

//------------------------------------------------------------------------
    BlackMagicExporter::~BlackMagicExporter()
    {
        _destroy();
    }

//------------------------------------------------------------------------
    int BlackMagicExporter::DoExport(
        const TCHAR*   name,
        ExpInterface*  ei,
        Interface*     i,
        BOOL           suppressprompts,
        DWORD          options )
    {
        try
        {
            // Redirect all output to the console window when in debug mode.
            RedirectIOToConsole();

            // Begin exporting.
            const bool exportselected = 0 != (options & SCENE_EXPORT_SELECTED);
            _log( "Starting export process...\n" );

            // Initialize IGame.
            m_pIGame = GetIGameInterface();

            // Throw an exception if unable to initialize the IGame interface.
            if ( !m_pIGame ) throw std::exception( "Failed to initialize IGame interface" );

            // Set the coordinate system to that of OpenGLs.
            IGameConversionManager * cm = GetConversionManager();
            cm->SetCoordSystem( IGameConversionManager::IGAME_OGL );

            // Initialize the IGame to ready for exporting.
            m_pIGame->InitialiseIGame( exportselected );
            m_pIGame->SetStaticFrame( 0 );

            // Map all nodes in the tree to their future index into scene nodes vector.
            _mapIGameNodesToSceneNodeIndices();

            // Export animation frames first.
            _exportAnimationFrames();

            // Export the nodes themselves.
            _exportNodes();

            // Write everything to file.
            _writeToFile( name );

            // Release initialized stuff.
            _destroy();
        }
        catch ( std::exception& e )
        {
            TSTR tstr( e.what() );
            MessageBox( i->GetMAXHWnd(), tstr, _T( "Export Error" ), MB_OK | MB_ICONERROR );
            _destroy();
        }
        return TRUE;
    }

//------------------------------------------------------------------------
    void BlackMagicExporter::ShowAbout( HWND hwnd )
    {

    }

//------------------------------------------------------------------------
    int BlackMagicExporter::ExtCount()
    {
        return 1;
    }

//------------------------------------------------------------------------
    const TCHAR* BlackMagicExporter::Ext( int /*n*/ )
    {
        return _T("blm");
    }

//------------------------------------------------------------------------
    const TCHAR* BlackMagicExporter::LongDesc()
    {
        return _T("Black Magic");
    }

//------------------------------------------------------------------------
    const TCHAR* BlackMagicExporter::ShortDesc()
    {
        return _T("Black Magic");
    }

//------------------------------------------------------------------------
    const TCHAR* BlackMagicExporter::AuthorName()
    {
        return _T("Benjamin Klingler");
    }

//------------------------------------------------------------------------
    const TCHAR* BlackMagicExporter::CopyrightMessage()
    {
        return _T("Copyright (C) Benjamin Klingler 2012");
    }

//------------------------------------------------------------------------
    const TCHAR* BlackMagicExporter::OtherMessage1()
    {
        return _T("");
    }

//------------------------------------------------------------------------
    const TCHAR* BlackMagicExporter::OtherMessage2()
    {
        return _T("");
    }

//------------------------------------------------------------------------
    unsigned int BlackMagicExporter::Version()
    {
        return 1;
    }

//------------------------------------------------------------------------
    BOOL BlackMagicExporter::SupportsOptions( int ext, DWORD options )
    {
        return TRUE;
    }

//------------------------------------------------------------------------
    void BlackMagicExporter::_arrangeFacesByMaterial(
        IGameMesh* mesh,
        std::unordered_map< IGameMaterial*, std::vector< int > >& outMaterialToFaces,
        std::vector< int >& outPlainFaceIds )
    {
        int nNumFaces = mesh->GetNumberOfFaces();
        FaceEx* pFace;
        IGameMaterial* pMaterial;

        // Iterate through all of the faces again to see if there were any without materials.
        for( int i = 0; i < nNumFaces; ++i )
        {
            pFace = mesh->GetFace( i );

            if( pFace )
            {
                pMaterial = mesh->GetMaterialFromFace( pFace );

                if( pMaterial ) outMaterialToFaces[ pMaterial ].push_back( i );
                else outPlainFaceIds.push_back( i );
            }
        }
    }

//------------------------------------------------------------------------
    void BlackMagicExporter::_destroy()
    {
        if( m_fileOut.is_open() )
            m_fileOut.close();

        if ( m_pIGame )
        {
            m_pIGame->ReleaseIGame();
            m_pIGame = nullptr;
        }
    }

//------------------------------------------------------------------------
    void BlackMagicExporter::_mapIGameNodesToSceneNodeIndices()
    {
        // Acquire the amount of root nodes.
        const int nTopLevelNodeCount = m_pIGame->GetTopLevelNodeCount();
        BLM::Index nIndexIntoSceneNodes = 0;

        // Iterate through all top level nodes and export each of their subtrees.
        for( int i = 0; i < nTopLevelNodeCount; ++i )
            _mapIGameNodesToSceneNodeIndices( m_pIGame->GetTopLevelNode( i ), nIndexIntoSceneNodes );
    }

//------------------------------------------------------------------------
    void BlackMagicExporter::_mapIGameNodesToSceneNodeIndices( IGameNode* root, BLM::Index& index )
    {
        // If it's nonexistent, bail.
        if( !root )
            return;

        // Determine how many children are dependent upon this particular node.
        const int nNumChildren = root->GetChildCount();

        // Record the mapping, and increment global index counter.
        m_mapIGameNodesToSceneNodeIndex[ root ] = index;
        index += 1;

        // Iterate through each of the children nodes, if there are any.
        for( int i = 0; i < nNumChildren; ++i )
            _mapIGameNodesToSceneNodeIndices( root->GetNodeChild( i ), index );
    }

//------------------------------------------------------------------------
    void BlackMagicExporter::_exportAnimationFrames()
    {
        // Acquire the amount of root nodes.
        const int nTopLevelNodeCount = m_pIGame->GetTopLevelNodeCount();
        const TimeValue nSceneStartTime = m_pIGame->GetSceneStartTime();
        const TimeValue nSceneEndTime = m_pIGame->GetSceneEndTime();
        const TimeValue nTicksPerFrame = m_pIGame->GetSceneTicks();
        const TimeValue nFramesPerSecond = 4800 / nTicksPerFrame;

        // Determine the fps and total number of animation frames a node could have (so we can figure
        // out how big the animation vectors should be.
        m_cHeader.framesPerSecond = nFramesPerSecond;
        m_nMaxNumberAnimationFrames = abs( nSceneEndTime - nSceneStartTime ) / nTicksPerFrame;
        
        // Acquire all transformations in any sort of animation for this node.
        for( TimeValue time = nSceneStartTime; time < nSceneEndTime; time += nTicksPerFrame )
        {
            // Iterate through all top level nodes and export each of their subtrees.
            for( int index = 0; index < nTopLevelNodeCount; ++index )
                _exportAnimationFrame( m_pIGame->GetTopLevelNode( index ), time );
        }
    }

//------------------------------------------------------------------------
    void BlackMagicExporter::_exportAnimationFrame( IGameNode* node, TimeValue time )
    {
        // Bail if the node is invalid.
        if( !node )
            return;

        // Data variables.
        BLM::AnimationFrame frame;
        const int nNumChildren = node->GetChildCount();
        GMatrix matToParent;
        
        // If we have a parent, get it's inverse so that we can compute our local transformation.
        if( node->GetNodeParent() )
            matToParent = node->GetNodeParent()->GetWorldTM( time ).Inverse();

        // Copy over the local-space transformation matrix for this particular frame.
        memcpy(
            frame.transformation,
            reinterpret_cast< float* >( ( node->GetWorldTM( time ) * matToParent ).GetAddr() ),
            16 * sizeof( float ) );

        // Put the data into the map of nodes to animations (later to be
        // condensed into a single large vector).
        std::vector< BLM::AnimationFrame >& vecAnimationFrames =
            m_mapIGameNodesToAnimationFrames[ node ];

        // If the vector is empty, reserve enough room for the amount of
        // matrices we'll be adding.
        if( vecAnimationFrames.empty() )
            vecAnimationFrames.reserve( m_nMaxNumberAnimationFrames );

        // Put the frame into the vector.
        vecAnimationFrames.emplace_back( frame );

        // Iterate through each of the children nodes, if there are any.
        for( int i = 0; i < nNumChildren; ++i )
            _exportAnimationFrame( node->GetNodeChild( i ), time );
    }

//------------------------------------------------------------------------
    bool BlackMagicExporter::_isAnimationChanging(
        const std::vector< BLM::AnimationFrame >& vecAnimations ) const
    {
        auto iterBegin = vecAnimations.cbegin();
        auto iterEnd = vecAnimations.cend();
        auto iterOld = vecAnimations.cbegin();

        // Iterate through each transformation in the animation vector.
        for( auto iter = iterBegin; iter != iterEnd; ++iter )
        {
            // If the current transformation and the last are not the same,
            // the animation is changing, so return true.
            if( std::memcmp(
                reinterpret_cast< const void* >( ( *iter ).transformation ),
                reinterpret_cast< const void* >( ( *iterOld ).transformation ),
                16 * sizeof( float ) ) != 0 )
                return true;

            iterOld = iter;
        }

        // If we reach here, the animation is not changing.
        return false;
    }

//------------------------------------------------------------------------
    void BlackMagicExporter::_exportNodes()
    {
        // Acquire the amount of root nodes.
        const int nTopLevelNodeCount = m_pIGame->GetTopLevelNodeCount();

        // Iterate through all top level nodes and export each of their subtrees.
        for( int i = 0; i < nTopLevelNodeCount; ++i )
            _exportNode( m_pIGame->GetTopLevelNode( i ) );
    }

//------------------------------------------------------------------------
    void BlackMagicExporter::_exportNode(
        IGameNode* node,
        BLM::Index parentIndex )
    {
        // Bail if the node is non-existent.
        if( !node )
            return;

        Box3 box;
        node->GetIGameObject()->GetBoundingBox( box );

        if( box.Min() < m_boundingBox.Min() )
            m_boundingBox.pmin = box.Min();
        if( box.Max() > m_boundingBox.Max() )
            m_boundingBox.pmax = box.Max();

        // Data variables.
        Matrix44		matrixToLocalSpace;
        BLM::SceneNode	sceneNode;
        IGameObject*	pIGameObject = node->GetIGameObject();

        // Bail if the game object is non-existent.
        if( !pIGameObject )
            return;

        const int nNumChildren = node->GetChildCount();

        const BLM::Index nCurrentSceneNodeIndex =
            static_cast< BLM::Index >( m_vecSceneNodes.size() );

        // Debug message notifying exporting for this particular scene node has begun.
        _log( "Exporting scene node '%s'...\n", node->GetName() );

        // Acquire the name of the node, padding it to fill up 64 bytes.
        strcpy_s( sceneNode.name, BLM::MAX_STRING_SIZE, node->GetName() );

        // Set up who the parent is (-1 if top-most node) and record index start.
        sceneNode.parentIndex = parentIndex;
        sceneNode.startTriangleBatchIndex = static_cast< BLM::Index >( m_vecTriangleBatches.size() );
        sceneNode.startBoneIndex = static_cast< BLM::Index >( m_vecBones.size() );
        m_nStartBoneIndexForCurrentSceneNode = sceneNode.startBoneIndex;

        // Acquire the transformation matrices needed.
        matrixToLocalSpace.setData(
            reinterpret_cast< float* >( node->GetWorldTM( 0 ).Inverse().GetAddr() ) );

        memcpy(
            sceneNode.inverseWorldTransformation,
            matrixToLocalSpace.getData(),
            16 * sizeof( float ) );

        // Figure out what type of object this is.
        if( pIGameObject->GetIGameType() == IGameObject::IGAME_MESH )
        {
            // Gather any new materials beforehand, if any.
            _log( "\tExporting materials...\n" );
            _exportMaterials( node->GetNodeMaterial() );
                
            // Gather all the vertices and indices needed for this mesh.
            _log( "\tExporting faces...\n" );
            _exportMesh( reinterpret_cast< IGameMesh* >( pIGameObject ), matrixToLocalSpace );
        }
        else if( pIGameObject->GetIGameType() == IGameObject::IGAME_CAMERA )
        {
            // If we found a camera, add it to our list of cameras.
            BLM::Camera camera;
            camera.sceneNodeIndex = nCurrentSceneNodeIndex;
            m_vecCameras.emplace_back( camera );
        }

        // Acquire the animation data for this node.
        std::vector< BLM::AnimationFrame >& vecAnimationFramesForNode =
            m_mapIGameNodesToAnimationFrames[ node ];

        // If the animation is constant, get rid of everything but the first frame.
        if( !_isAnimationChanging( vecAnimationFramesForNode ) &&
            vecAnimationFramesForNode.size() > 1 )
            vecAnimationFramesForNode.erase(
                vecAnimationFramesForNode.begin() + 1,
                vecAnimationFramesForNode.end() );

        // Set the number of indices and stick the mesh into the vector.
        sceneNode.numTriangleBatches =
            static_cast< BLM::Size >( m_vecTriangleBatches.size() ) - sceneNode.startTriangleBatchIndex;

        sceneNode.numChildren =
            static_cast< BLM::Size >( nNumChildren );

        sceneNode.startAnimationFrameIndex =
            static_cast< BLM::Index >( m_vecAnimationFrames.size() );

        sceneNode.numAnimationFrames =
            static_cast< BLM::Size >( vecAnimationFramesForNode.size() );

        sceneNode.numBones =
            static_cast< BLM::Size >( m_vecBones.size() ) - sceneNode.startBoneIndex;

        // Add the mesh and its animation frames to their respected containers.
        m_vecSceneNodes.emplace_back( sceneNode );

        m_vecAnimationFrames.insert(
            m_vecAnimationFrames.end(),
            vecAnimationFramesForNode.begin(),
            vecAnimationFramesForNode.end() );

        // Iterate through each of the children nodes, if there are any.
        for( int i = 0; i < nNumChildren; ++i )
            _exportNode( node->GetNodeChild( i ), nCurrentSceneNodeIndex );

        // Free memory.
        node->ReleaseIGameObject();
    }

//------------------------------------------------------------------------
    void BlackMagicExporter::_exportMaterials( IGameMaterial* material )
    {
        // Bail if the given material was non-existent.
        if( !material )
            return;

        // Data variables.
        bool                    bIsDiffuseTexture;
        int                     nNumSubMaterials = material->GetSubMaterialCount();
        int                     nNumTextureMaps = material->GetNumberOfTextureMaps();
        BLM::DiffuseMaterial    newMaterial;
        IGameTextureMap*        pIGameTextureMap;

        // Iterate through each of the texture maps in the material.
        for( int i = 0; i < nNumTextureMaps; ++i )
        {
            pIGameTextureMap = material->GetIGameTextureMap( i );

            // Skip over this one if the texture map is non-existent.
            if( !pIGameTextureMap )
                continue;

            // Determine if the node is a diffuse texture.
            bIsDiffuseTexture = pIGameTextureMap->GetStdMapSlot() == ID_DI;

            // If it is, export the material.
            if( bIsDiffuseTexture )
            {
                // If it's a diffuse texture and has not already been stored, store it.
                if( m_mapDiffuseMaterialsToIndex.find( material ) ==
                    m_mapDiffuseMaterialsToIndex.end() )
                {
                    // Set the material information to be written to the file.
                    strcpy_s( newMaterial.name, BLM::MAX_STRING_SIZE, material->GetMaterialName() );
                    _extractFileName( newMaterial.fileName, pIGameTextureMap->GetBitmapFileName() );

                    // Record the new material so we may reference it later (map for telling the
                    // exporter which material id corresponds to which index in the
                    // vector-to-be-written-to-file).
                    m_mapDiffuseMaterialsToIndex[ material ] =
                        static_cast< BLM::Index >( m_vecDiffuseMaterials.size() );

                    m_vecDiffuseMaterials.emplace_back( newMaterial );
                }
            }
        }
        
        // Recurse through each of the sub-materials.
        for( int i = 0; i < nNumSubMaterials; ++i )
            _exportMaterials( material->GetSubMaterial( i ) );
    }

//------------------------------------------------------------------------
    void BlackMagicExporter::_exportMesh(
        IGameMesh* mesh,
        const Matrix44& matrixToLocalSpace )
    {
        std::unordered_map< IGameMaterial*, std::vector< int > > mapMaterialsToFaces;
        std::vector< int > vecPlainFaces;
        
        // Bail if the mesh cannot initialize.
        if( !mesh || !mesh->InitializeData() )
            return;

        // Arrange the faces by material for nice outputting.
        _log( "\t\tArranging faces by material (because I'm nice)...\n" );
        _arrangeFacesByMaterial( mesh, mapMaterialsToFaces, vecPlainFaces );

        // For each material used in this mesh, export all of the faces associated.
        _log( "\t\tExporting faces with materials...\n" );
        for( auto iter = mapMaterialsToFaces.begin(); iter != mapMaterialsToFaces.end(); ++iter )
            _exportFacesInVector( mesh, iter->first, matrixToLocalSpace, iter->second );

        // Finally, export any faces that did not have any materials associated with it.
        _log( "\t\tExporting faces without materials...\n" );
        _exportFacesInVector( mesh, nullptr, matrixToLocalSpace, vecPlainFaces );
    }

//------------------------------------------------------------------------
    void BlackMagicExporter::_exportFace(
        IGameMesh* mesh,
        FaceEx* face,
        const Matrix44& matrixToLocalSpace )
    {
        BLM::Vertex vertex;
        BLM::Index nIndexToUse = 0;
        Point3 tangent, bitangent;
        int nTangentBiTangentIndex = 0;

        // Bail if there's no face to export.
        if( !face )
            return;

        // Iterate over each corner of the current face.
        for( int k = 0; k < 3; ++k )
        {
            // Acquire the tangent / bitangent from the mesh.
            nTangentBiTangentIndex = mesh->GetFaceVertexTangentBinormal( face->meshFaceIndex, k );
            tangent = mesh->GetTangent( nTangentBiTangentIndex );
            bitangent = mesh->GetBinormal( nTangentBiTangentIndex );

            // Acquire vertex data.
            vertex.position = mesh->GetVertex( face->vert[ k ] );
            vertex.texCoord = mesh->GetTexVertex( face->texCoord[ k ] );
            vertex.texCoord.y *= -1.0f;
            vertex.normal = mesh->GetNormal( face->norm[ k ] );
            vertex.tangent = tangent;

            // Calculate the handedness so that we do not have to store the bitangent.
            vertex.tangent.w =
                ( DotProd( CrossProd( vertex.normal, tangent ), bitangent ) < 0.0f ) ? -1.0f : 1.0f;

            // If the mesh we are on is skinned, export the bone data, too.
            if( mesh->IsObjectSkinned() )
                _exportSkinningDataToVertex( mesh->GetIGameSkin(), face->vert[ k ], vertex );
            else
            {
                // Put the data into local space.
                vertex.position = matrixToLocalSpace.calcTransformPoint3D( vertex.position );
                vertex.normal = matrixToLocalSpace.calcTransformVector3D( vertex.normal );
                vertex.normal = vertex.normal.Normalize();
            }

            // Check if this vertex has already been added. If it hasn't, add it in; otherwise,
            // use the existing index already placed.
            if( m_mapVerticesToIndices.find( vertex ) == m_mapVerticesToIndices.end() )
            {
                nIndexToUse = static_cast< BLM::Index >( m_vecVertices.size() );
                m_mapVerticesToIndices[ vertex ] = nIndexToUse;
                m_vecVertices.emplace_back( vertex );
            }
            else nIndexToUse = m_mapVerticesToIndices[ vertex ];

            // Add index to list of indices.
            m_vecIndices.emplace_back( nIndexToUse );
        }
    }

//------------------------------------------------------------------------
    void BlackMagicExporter::_exportFacesInVector(
        IGameMesh* mesh,
        IGameMaterial* material,
        const Matrix44& matrixToLocalSpace,
        const std::vector< int >& vecFaceIds )
    {
        BLM::TriangleBatch triangleBatch;

        // Start filling in triangle batch information.
        triangleBatch.materialIndex =
            ( material ? m_mapDiffuseMaterialsToIndex[ material ] : BLM::INVALID_INDEX );

        triangleBatch.startIndex = static_cast< BLM::Index >( m_vecIndices.size() );

        // Iterate through each face and export it.
        for( auto faceIter = vecFaceIds.begin(); faceIter != vecFaceIds.end(); ++faceIter )
            _exportFace( mesh, mesh->GetFace( *faceIter ), matrixToLocalSpace );

        // Finish up filling out the triangle batch information, then record it for file output.
        triangleBatch.numIndices =
            static_cast< BLM::Size >( m_vecIndices.size() ) - triangleBatch.startIndex;

        m_vecTriangleBatches.emplace_back( triangleBatch );
    }

//------------------------------------------------------------------------
    void BlackMagicExporter::_exportSkinningDataToVertex(
        IGameSkin* skinModifier,
        int vertexIndexIntoFace,
        BLM::Vertex& outVertex )
    {
        float fTotalWeight = 0;

        // Clear out any previously gathered BoneWeights.
        m_vecBoneWeights.clear();

        // Go through each of the bones connected to this particular vertex.
        for( int i = 0; i < skinModifier->GetNumberOfBones( vertexIndexIntoFace ); ++i )
        {
            BoneWeight boneWeight;

            // Acquire the necessary data from each bone.
            boneWeight.bone = skinModifier->GetIGameBone( vertexIndexIntoFace, i );
            boneWeight.weight = skinModifier->GetWeight( vertexIndexIntoFace, i );

            // Stick it in a vector to-be-reassessed later.
            m_vecBoneWeights.emplace_back( boneWeight );
        }

        // Sort the bone weights according to their individual weights.
        std::sort( m_vecBoneWeights.begin(), m_vecBoneWeights.end() );

        // Restrict the number of bones we allow per vertex (kill off any extras).
        m_vecBoneWeights.resize( BLM::MAX_NUM_BONES );

        // Iterate through each of the picked bones, and calculate the total weight.
        for( auto iter = m_vecBoneWeights.begin(); iter != m_vecBoneWeights.end(); ++iter )
        {
            BoneWeight& boneWeight = *iter;

            // Only total up the known weights (prevent from adding undefined weight
            // from nonexistent bone).
            if( boneWeight.bone )
                fTotalWeight += boneWeight.weight;
        }

        // Compute one-over-total-weight so that we can multiply it by each weight to get a percentage.
        const float fOneOverTotalWeight = 1.0f / fTotalWeight;
        size_t i = 0;

        // Iterate through each weight and update their value to be a percentage of the total
        // weight sum calculated.
        for( auto iter = m_vecBoneWeights.begin(); iter != m_vecBoneWeights.end(); ++iter, ++i )
        {
            BLM::Bone	bone;
            BoneWeight&	boneWeight = *iter;

            // If we reach a bone that doesn't exist, set it's index to 0, since we can guarantee
            // it will exist, and set the weight to 0 so that the shader inside the importer will
            // essentially disregard this.
            if( !boneWeight.bone )
            {
                outVertex.boneIndices[ i ] = 0;
                outVertex.boneWeights[ i ] = 0.0f;
                continue;
            }

            // Find the scene node index of the bone, and adjust it's weight to be a percentage.
            const BLM::Index nBoneSceneNodeIndex = m_mapIGameNodesToSceneNodeIndex[ boneWeight.bone ];
            bone.sceneNodeIndex = nBoneSceneNodeIndex;
            boneWeight.weight *= fOneOverTotalWeight;

            // See if the bone is already there and find a local index for it if it is / isn't.
            auto iterLocationOfBone
                = std::find( m_vecBones.begin() +
                    m_nStartBoneIndexForCurrentSceneNode, m_vecBones.end(), bone );

            BLM::Index nLocalIndexOfBoneReference =
                static_cast< BLM::Index >( iterLocationOfBone - ( m_vecBones.begin() + 
                m_nStartBoneIndexForCurrentSceneNode) );

            const bool bIsReferencingNewBone = ( iterLocationOfBone == m_vecBones.end() );

            // Add the bone into this triangle batch's used bone list if it is not already in there.
            if( bIsReferencingNewBone )
            {
                nLocalIndexOfBoneReference =
                    static_cast< BLM::Index >( m_vecBones.size() ) -
                        m_nStartBoneIndexForCurrentSceneNode;

                m_vecBones.emplace_back( bone );
            }

            // Set the vertex data.
            outVertex.boneWeights[ i ] = boneWeight.weight;
            outVertex.boneIndices[ i ] = nLocalIndexOfBoneReference;
        }
    }

//------------------------------------------------------------------------
    void BlackMagicExporter::_extractFileName( char* dest, const char* src )
    {
        std::string sInput( src );
        std::string sDestination;
        int nLocationOfSlash = -1;
        size_t nLocationOfExtension = sInput.find_last_of( '.' ) + 1;

        // If the texture file name is incompatible, rewrite it out as .PNG.
        if( strcmp( &sInput[ nLocationOfExtension ], "dds" ) == 0 )
            sInput.replace( nLocationOfExtension, 3, "png" );

        // Find the location of a slash to get rid of any file paths.
        nLocationOfSlash = static_cast< int >( sInput.find_last_of( '\\' ) );
        if( nLocationOfSlash == std::string::npos )
            nLocationOfSlash = static_cast< int >( sInput.find_last_of( '/' ) );

        // Output the new string.
        strcpy_s( dest, BLM::MAX_STRING_SIZE, sInput.substr( nLocationOfSlash + 1 ).c_str() );
    }

//------------------------------------------------------------------------
    void BlackMagicExporter::_log( const char* format, ... )
    {
#ifdef BLM_ENABLE_MESSAGE_LOGGING
        va_list args;
        va_start( args, format );
        vprintf( format, args );
        va_end( args );
#endif
    }

//------------------------------------------------------------------------
    void BlackMagicExporter::_printDebugInformation()
    {
        _log( "\n\n============================================\n" );
        _log( "Number of Meshes:			%d\n", m_vecSceneNodes.size() );
        _log( "Number of Triangle Batches:	%d\n", m_vecTriangleBatches.size() );
        _log( "Number of Diffuse Materials: %d\n", m_vecDiffuseMaterials.size() );
        _log( "Number of Vertices:			%d\n", m_vecVertices.size() );
        _log( "Number of Indices:			%d\n", m_vecIndices.size() );
        _log( "Number of Cameras:			%d\n", m_vecCameras.size() );
        _log( "============================================\n\n" );
    }

//------------------------------------------------------------------------
    void BlackMagicExporter::_writeToFile( const TCHAR* fileName )
    {
        // Open file for writing.
        m_fileOut.open( fileName, std::ios_base::binary );

        // Bail if the file could not open.
        if( !m_fileOut.is_open() )
        {
            _log( "Unable to open file '%s'!\n", fileName );
            return;
        }

        // Output the final results of all the exporting.
        _printDebugInformation();

        // Write out the file.
        _log( "Writing scene to file: '%s'\n", fileName );
        _writeHeader();
        _writeLumps();
        _writeData( m_rgLumps[ BLM::kSceneNodes ], m_vecSceneNodes );
        _writeData( m_rgLumps[ BLM::kTriangleBatches ], m_vecTriangleBatches );
        _writeData( m_rgLumps[ BLM::kDiffuseMaterials ], m_vecDiffuseMaterials );
        _writeData( m_rgLumps[ BLM::kAnimationFrames ], m_vecAnimationFrames );
        _writeData( m_rgLumps[ BLM::kVertices ], m_vecVertices );
        _writeData( m_rgLumps[ BLM::kIndices ], m_vecIndices );
        _writeData( m_rgLumps[ BLM::kBones ], m_vecBones );
        _writeData( m_rgLumps[ BLM::kCameras ], m_vecCameras );
        _log( "Done!\n" );
    }

    /*
     * Note: Other source code removed.
     */
}
    
Picture
Home | Games | Individual | About | Résumé | Contact
Copyright © 2017 Benjamin Klingler
✕