History: 3ds2mesh_cpp
Source of version: 1 (current)
Copy to clipboard
{CODE(wrap="1", colors="c++")}// 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;
}{CODE}