3ds2mesh_cpp        
// Converts a 3D Studio file into an Ogre3D mesh and material

#include <OgrePrerequisites.h>
#include <OgrePlatform.h>
#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE
#include <Carbon/Carbon.h>
#endif

#include <Ogre.h>
#include <OgreStringConverter.h>
#include <OgreDefaultHardwareBufferManager.h>
#include <OgreHardwareVertexBuffer.h>
#include <OgreVertexIndexData.h>
#include <OgreResourceGroupManager.h>
#include <memory>
#include <set>
#include <string>
#include <vector>

#include <iostream>

#define VERSION "1.1"

#ifdef WIN32
#include "getopt.h"
#else
#include <getopt.h>
#endif

using namespace std;

extern "C" {
#include <3dsftk.h>
}

class Face {
public:
    Face() {}
    Face(ushort3ds i1, ushort3ds i2, ushort3ds i3) : v1(i1), v2(i2), v3(i3) {}
    ushort3ds v1, v2, v3;
};

Ogre::String ReplaceSpaces(const Ogre::String& s)
{
    Ogre::String res(s);
    replace(res.begin(), res.end(), ' ', '_');
   
    return res;
}

bool convert3dsToMesh(const char * sz3dsFileName,
                      const char * szMeshPrefix,
                      bool bCreateSkeleton = false,
                      bool bTextureVerticalFlip = false);

int main(int argc, char **argv)
{
    std::cerr << "3DS to Ogre mesh converter " << VERSION << std::endl;
    std::cerr << "by David Geldreich" << std::endl;
    std::cerr << "using the 3D Studio File Toolkit (C) Copyright 1995 by Autodesk, Inc." << std::endl;
    std::cerr << std::endl;

    bool bCreateSkeleton = false;
    bool bTextureVerticalFlip = false;

    int ch;
    while ((ch = getopt(argc, argv, "fs")) != -1) {
      switch (ch) {
         case 'f':
                bTextureVerticalFlip = true;
            break;
         case 's':
            bCreateSkeleton = true;
            break;
         default:
            break;
      }
   }
   argc -= (optind-1);
   argv += (optind-1);

    if (argc < 3)
    {
        std::cerr << "Usage : 3ds2mesh  [-s] [-f] myfile.3ds prefix" << std::endl;
        std::cerr << "      [-s]          export skeleton" << std::endl;
        std::cerr << "      [-f]          flip texture vertically" << std::endl;
        std::cerr << "      myfile.3ds    the name of your input file" << std::endl;
        std::cerr << "      prefix        will output prefix.mesh and prefix.material" << std::endl;
        return -1;
    }

    // Construct Ogre singletons needed by the exporter
    Ogre::Root *r = new Ogre::Root();
    Ogre::DefaultHardwareBufferManager defHWBufMgr;

    convert3dsToMesh(argv[1], argv[2], bCreateSkeleton, bTextureVerticalFlip);
    delete r;
}

#define PRINT_ERRORS_RETURN(file) {if(ftkerr3ds){DumpErrList3ds(file); if (!ignoreftkerr3ds) return (false);}}

bool convert3dsToMesh(const char * sz3dsFileName, const char * szMeshPrefix,
                      bool bCreateSkeleton,
                      bool bTextureVerticalFlip)
{
    file3ds *ifile = NULL;
    database3ds *db = NULL;

    Ogre::String sFullPrefix(szMeshPrefix);
    Ogre::String sMatPrefix;
    size_t lastSlash = sFullPrefix.find_last_of("\\");
    if (lastSlash != Ogre::String::npos)
        sMatPrefix = sFullPrefix.substr(lastSlash+1)+"/";
    else
        sMatPrefix = sFullPrefix+"/";

    //ignoreftkerr3ds = 1;

    ifile = OpenFile3ds(sz3dsFileName, "r");
    PRINT_ERRORS_RETURN(stderr);

    InitDatabase3ds(&db);
    CreateDatabase3ds(ifile, db);
    PRINT_ERRORS_RETURN(stderr);

    meshset3ds* mset = NULL;
    GetMeshSet3ds(db, &mset);
    ReleaseMeshSet3ds(&mset);

    assert(GetDatabaseType3ds(db) == MeshFile);

    // If we are called in an Ogre application, Manager are already there
    Ogre::LogManager *pLogMgr = Ogre::LogManager::getSingletonPtr();
    assert(pLogMgr != NULL);
   pLogMgr->logMessage("OGRE 3DS Exporter Log");
   pLogMgr->logMessage("---------------------------");

    ulong3ds i;

    Ogre::MeshManager* pMeshMgr = Ogre::MeshManager::getSingletonPtr();
    assert(pMeshMgr != NULL);

   pMeshMgr->unload(szMeshPrefix);
   pMeshMgr->remove(szMeshPrefix);
    Ogre::MeshPtr ogreMesh = pMeshMgr->createManual(Ogre::String(szMeshPrefix),
        Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);

    Ogre::SkeletonPtr ogreSkeleton;

    if (bCreateSkeleton)
    {
        // create Skeleton
        Ogre::String skeletonName = Ogre::String(szMeshPrefix) + ".skeleton";
        Ogre::SkeletonManager::getSingleton().unload(skeletonName);
        Ogre::SkeletonManager::getSingleton().remove(skeletonName);
        ogreSkeleton = Ogre::SkeletonManager::getSingleton().create(skeletonName, 
            Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);

        ogreMesh->_notifySkeleton(ogreSkeleton);
    }

    bool bBonesExist = false;


   namelist3ds *objectlist = NULL;
    namelist3ds *meshlist = NULL;

    mesh3ds *mesh = NULL;
    kfmesh3ds *kfmesh = NULL;

    size_t j;
    Ogre::Vector3 min, max, currpos;
    Ogre::Real maxSquaredRadius = 0;
    bool bFirst = true;

    /* Get the list of meshes */
    GetMeshNameList3ds(db, &meshlist);
    PRINT_ERRORS_RETURN(stderr);
   GetObjectNodeNameList3ds(db, &objectlist);
    PRINT_ERRORS_RETURN(stderr);

    /* Store name of found material */
    set<string> foundMaterial;

    for (i = 0; i < meshlist->count; i++)
    {
        GetMeshByName3ds(db, meshlist->list[i].name, &mesh);
        PRINT_ERRORS_RETURN(stderr);

      // loop materials in sub mesh and create a sub mesh for each material 
      for (int k = 0; k < mesh->nmats; k++)
        {
            // get current material
            objmat3ds * mat = mesh->matarray+k;
         
         // if the material doen't have faces - continue
         if (mat->nfaces == 0)
            continue;

         // not all the vertexes in the mesh are using this material
         // we want to find the vertexes of this material from the
         // material faces.
          
         vector<bool> isVertexUsedInMaterial(mesh->nvertices, false);
            vector<ushort3ds> origVertexToMaterialVertex(mesh->nvertices);

         //  find the material vertexes from the faces of the material
         for (j = 0; j < mat->nfaces; ++j)
         {
            ushort3ds faceindex = mat->faceindex[j];

            isVertexUsedInMaterial[mesh->facearray[faceindex].v1] = true;
            isVertexUsedInMaterial[mesh->facearray[faceindex].v2] = true;
            isVertexUsedInMaterial[mesh->facearray[faceindex].v3] = true;
         }

         ushort3ds materialVertexCount = 0;
         // build a map that will be our index guide from the 3ds vertexes indexes to
         // the material vertexes indexes
         for (j = 0; j < mesh->nvertices; ++j)
         {
            if (isVertexUsedInMaterial[j])
            {
               origVertexToMaterialVertex[j] = materialVertexCount;
               materialVertexCount++;
            }
         }

            Ogre::String subMeshName = Ogre::String(meshlist->list[i].name) + "__" + Ogre::String(mat->name);
            pLogMgr->logMessage("Creating SubMesh object..." + subMeshName);
         Ogre::SubMesh* ogreSubMesh = ogreMesh->createSubMesh(subMeshName);
         pLogMgr->logMessage("SubMesh object created.");

            ogreSubMesh->vertexData = new Ogre::VertexData();
            ogreSubMesh->vertexData->vertexCount = materialVertexCount;
            ogreSubMesh->vertexData->vertexStart = 0;
            Ogre::VertexBufferBinding* bind = ogreSubMesh->vertexData->vertexBufferBinding;
            Ogre::VertexDeclaration* decl = ogreSubMesh->vertexData->vertexDeclaration;

            // Always 2D coords and normals
            // Texture coords only if there is texture coordinates in the 3ds mesh
#define POSITION_BINDING 0
#define NORMAL_BINDING 1
#define TEXCOORD_BINDING 2
            decl->addElement(POSITION_BINDING, 0, Ogre::VET_FLOAT3, Ogre::VES_POSITION);
            decl->addElement(NORMAL_BINDING, 0, Ogre::VET_FLOAT3, Ogre::VES_NORMAL);
            if (mesh->ntextverts != 0) 
                decl->addElement(TEXCOORD_BINDING, 0, Ogre::VET_FLOAT2, Ogre::VES_TEXTURE_COORDINATES);
            // Create buffers
            Ogre::HardwareVertexBufferSharedPtr pbuf = Ogre::HardwareBufferManager::getSingleton().
                createVertexBuffer(decl->getVertexSize(POSITION_BINDING), ogreSubMesh->vertexData->vertexCount,
                Ogre::HardwareBuffer::HBU_DYNAMIC, false);
            Ogre::HardwareVertexBufferSharedPtr nbuf = Ogre::HardwareBufferManager::getSingleton().
                createVertexBuffer(decl->getVertexSize(NORMAL_BINDING), ogreSubMesh->vertexData->vertexCount,
                Ogre::HardwareBuffer::HBU_DYNAMIC, false);
            Ogre::HardwareVertexBufferSharedPtr tbuf = Ogre::HardwareBufferManager::getSingleton().
                createVertexBuffer(decl->getVertexSize(TEXCOORD_BINDING), ogreSubMesh->vertexData->vertexCount,
                Ogre::HardwareBuffer::HBU_DYNAMIC, false);
            bind->setBinding(POSITION_BINDING, pbuf);
            bind->setBinding(NORMAL_BINDING, nbuf);
            if (mesh->ntextverts != 0) 
                bind->setBinding(TEXCOORD_BINDING, tbuf);

            ogreSubMesh->useSharedVertices = false;

            float* pPos = static_cast<float*>(
                pbuf->lock(Ogre::HardwareBuffer::HBL_DISCARD));
            float* pTex;

            pLogMgr->logMessage("Doing positions ...");
            assert(mesh->ntextverts == 0 || mesh->ntextverts == mesh->nvertices);
            if (mesh->ntextverts != 0)
            {
                pLogMgr->logMessage("and texture coordinates ...");
                pTex = static_cast<float*>(
                    tbuf->lock(Ogre::HardwareBuffer::HBL_DISCARD));
            }

            std::vector<Ogre::Vector3> vertices;
            std::vector<Face> faces;
            // For each vertex a list of faces containing it
            std::vector<std::vector<ushort3ds> > vertexFaces; 
            vertexFaces.resize(ogreSubMesh->vertexData->vertexCount);

            for (j = 0; j < mesh->nvertices; ++j)
            {
            if (isVertexUsedInMaterial[j] == false)
               continue;

                // Do some coordsys change
                currpos = Ogre::Vector3(mesh->vertexarray[j].x,
                    mesh->vertexarray[j].z,
                    -mesh->vertexarray[j].y);
                *pPos = currpos.x; pPos++;
                *pPos = currpos.y; pPos++;
                *pPos = currpos.z; pPos++;

                vertices.push_back(currpos);

                if (mesh->ntextverts != 0) 
                {
                    *pTex = mesh->textarray[j].u; ++pTex;
                    if (bTextureVerticalFlip)
                        *pTex = mesh->textarray[j].v;
                    else
                        *pTex = 1.0 - mesh->textarray[j].v;
                    ++pTex;
                }
                // Deal with bounds
                if (bFirst)
                {
                    min = max = currpos;
                    maxSquaredRadius = currpos.squaredLength();
                    bFirst = false;
                }
                else
                {
                    min.makeFloor(currpos);
                    max.makeCeil(currpos);
                    maxSquaredRadius = std::max(maxSquaredRadius, currpos.squaredLength());
                }
            }
            if (mesh->ntextverts != 0)
                tbuf->unlock();
            pbuf->unlock();

            ogreSubMesh->indexData->indexStart = 0;
            ogreSubMesh->indexData->indexCount = mat->nfaces*3;
        
            Ogre::HardwareIndexBufferSharedPtr ibuf;
            ibuf = Ogre::HardwareBufferManager::getSingleton().createIndexBuffer(
                Ogre::HardwareIndexBuffer::IT_16BIT,
                ogreSubMesh->indexData->indexCount,
                Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY);
            ogreSubMesh->indexData->indexBuffer = ibuf;

            pLogMgr->logMessage("Doing indices ...");
            unsigned short *pShort = static_cast<unsigned short*>(
                ibuf->lock(Ogre::HardwareBuffer::HBL_DISCARD));
            assert(mat->nfaces < 65536);
            for (j = 0; j < mat->nfaces; ++j)
            {
                ushort3ds faceindex = mat->faceindex[j];

                Face f(origVertexToMaterialVertex[mesh->facearray[faceindex].v1],
                       origVertexToMaterialVertex[mesh->facearray[faceindex].v2],
                       origVertexToMaterialVertex[mesh->facearray[faceindex].v3]);
                faces.push_back(f);

                *pShort = f.v1; ++pShort;
                *pShort = f.v2; ++pShort;
                *pShort = f.v3; ++pShort;

                vertexFaces[f.v1].push_back((ushort3ds)j);
                vertexFaces[f.v2].push_back((ushort3ds)j);
                vertexFaces[f.v3].push_back((ushort3ds)j);
            }
            ibuf->unlock();

            // Compute the normal of each face
            // TODO: take into account smoothing group
            pLogMgr->logMessage("Computing face normals ...");
            std::vector<Ogre::Vector3> faceNormals;
            std::vector<Face>::const_iterator itF;
            for (itF = faces.begin(); itF != faces.end(); ++itF)
            {
                Ogre::Vector3 normal;
                Ogre::Vector3 ab = vertices[(*itF).v2] - vertices[(*itF).v1];
                Ogre::Vector3 ac = vertices[(*itF).v3] - vertices[(*itF).v1];

                normal = ab.crossProduct(ac);
                faceNormals.push_back(normal);
            }
            assert(faceNormals.size() == mat->nfaces);

            // Compute the normal for each vertices
            assert(vertices.size() < 65536);
            ushort3ds iv;
            float* pNorm = static_cast<float*>(nbuf->lock(Ogre::HardwareBuffer::HBL_DISCARD));
            for (iv = 0; iv < vertices.size(); ++iv)
            {
                // Sum up the normals
                Ogre::Vector3 normal(0,0,0);
                std::vector<ushort3ds>::const_iterator itF;
                //assert(!vertexFaces[iv].empty());
                for (itF = vertexFaces[iv].begin(); itF != vertexFaces[iv].end(); ++itF)
                    normal += faceNormals[*itF];

                normal.normalise();
                //assert(Ogre::Math::Abs(normal.length() - 1.0) < 0.1);

                *pNorm = normal.x; ++pNorm; 
                *pNorm = normal.y; ++pNorm; 
                *pNorm = normal.z; ++pNorm; 
            }
            nbuf->unlock();

            // Now use Ogre's ability to reorganise the vertex buffers the best way
            Ogre::VertexDeclaration* newDecl = 
                ogreSubMesh->vertexData->vertexDeclaration->getAutoOrganisedDeclaration(
                false,
                false);
            Ogre::BufferUsageList bufferUsages;
            for (size_t u = 0; u <= newDecl->getMaxSource(); ++u)
                bufferUsages.push_back(Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY);
            ogreSubMesh->vertexData->reorganiseBuffers(newDecl, bufferUsages);

            if (mesh->matarray != NULL)
            {
                ogreSubMesh->setMaterialName(ReplaceSpaces(sMatPrefix+mat->name));
                foundMaterial.insert(mat->name);
            }
        }
        RelMeshObj3ds (&mesh);
    }

    // Handle only the materials found on the object
    namelist3ds *matlist = NULL;
    material3ds *mat = NULL;

    Ogre::MaterialManager* pMatMgr = Ogre::MaterialManager::getSingletonPtr();
    assert(pMatMgr != NULL);

    Ogre::MaterialSerializer matSer;
    bool bExportMat = false;

    GetMaterialNameList3ds(db, &matlist);
    PRINT_ERRORS_RETURN(stderr);
    for (i = 0; i < matlist->count; i++)
    {
        if (foundMaterial.find(matlist->list[i].name) == foundMaterial.end())
            continue;

        pLogMgr->logMessage("Creating material " + ReplaceSpaces(matlist->list[i].name));
        GetMaterialByName3ds(db, matlist->list[i].name, &mat);
        PRINT_ERRORS_RETURN(stderr);

        // When used in Ogre application, the material could already exist
      pMatMgr->unload(ReplaceSpaces(sMatPrefix+matlist->list[i].name));
      pMatMgr->remove(ReplaceSpaces(sMatPrefix+matlist->list[i].name));

        Ogre::MaterialPtr ogremat = pMatMgr->create(ReplaceSpaces(sMatPrefix+matlist->list[i].name), 
            Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
        pLogMgr->logMessage("Created.");

        ogremat->setAmbient(mat->ambient.r, mat->ambient.g, mat->ambient.b);
        ogremat->setDiffuse(mat->diffuse.r, mat->diffuse.g, mat->diffuse.b, 1 + mat->transparency);
        // TODO : check why we should ignore these
        //ogremat->setSpecular(mat->specular.r, mat->specular.g, mat->specular.b, 1);
        //ogremat->setShininess(mat->shininess);

        // Handle transparency
        if (1.0f + mat->transparency < 1.0f)
        {
            ogremat->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA); 
            ogremat->setDepthWriteEnabled(false); 
        }

        if (mat->twosided)
            ogremat->setCullingMode(Ogre::CULL_NONE);

        if (strlen(mat->texture.map.name) > 0)
        {
            Ogre::TextureUnitState *tu;
       if(ogremat->getNumTechniques() == 0){
          Ogre::Technique *t = ogremat->createTechnique();
          t->createPass();
       }
            tu = ogremat->getTechnique(0)->getPass(0)->createTextureUnitState(mat->texture.map.name);
        }

        matSer.queueForExport(ogremat);
        bExportMat = true;
    }
    if (bExportMat)
        matSer.exportQueued(Ogre::String(szMeshPrefix)+".material");
    ReleaseNameList3ds(&matlist);

    // Set bounds
    ogreMesh->_setBoundingSphereRadius(Ogre::Math::Sqrt(maxSquaredRadius));
    ogreMesh->_setBounds(Ogre::AxisAlignedBox(min, max), false);

    ReleaseNameList3ds(&meshlist);
   ReleaseNameList3ds(&objectlist);

    // Write the mesh file
    Ogre::MeshSerializer meshSer;
    meshSer.exportMesh(ogreMesh.getPointer(), Ogre::String(szMeshPrefix)+".mesh");
    Ogre::MeshManager::getSingleton().remove(ogreMesh->getHandle());

    ReleaseDatabase3ds(&db);
    CloseFile3ds(ifile);

    return true;
}