bgfx/3rdparty/openctm/tools/3ds.cpp
2012-10-07 20:41:18 -07:00

432 lines
12 KiB
C++

//-----------------------------------------------------------------------------
// Product: OpenCTM tools
// File: 3ds.cpp
// Description: Implementation of the 3DS file format importer/exporter.
//-----------------------------------------------------------------------------
// Copyright (c) 2009-2010 Marcus Geelnard
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would be
// appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not
// be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source
// distribution.
//-----------------------------------------------------------------------------
#include <stdexcept>
#include <fstream>
#include <vector>
#include <list>
#include "3ds.h"
#ifdef _MSC_VER
typedef unsigned short uint16;
typedef unsigned int uint32;
#else
#include <stdint.h>
typedef uint16_t uint16;
typedef uint32_t uint32;
#endif
using namespace std;
// Known 3DS chunks
#define CHUNK_MAIN 0x4d4d
#define CHUNK_M3D_VERSION 0x0002
#define CHUNK_3DEDIT 0x3d3d
#define CHUNK_MESH_VERSION 0x3d3e
#define CHUNK_OBJECT 0x4000
#define CHUNK_TRIMESH 0x4100
#define CHUNK_VERTEXLIST 0x4110
#define CHUNK_MAPPINGCOORDS 0x4140
#define CHUNK_FACES 0x4120
#define CHUNK_MSH_MAT_GROUP 0x4130
#define CHUNK_MAT_ENTRY 0xafff
#define CHUNK_MAT_NAME 0xa000
#define CHUNK_MAT_TEXMAP 0xa200
#define CHUNK_MAT_MAPNAME 0xa300
// 3DS object class
class Obj3DS {
public:
vector<uint16> mIndices;
vector<Vector3> mVertices;
vector<Vector2> mUVCoords;
};
/// Read a 16-bit integer, endian independent.
static uint16 ReadInt16(istream &aStream)
{
unsigned char buf[2];
aStream.read((char *) buf, 2);
return ((uint16) buf[0]) | (((uint16) buf[1]) << 8);
}
/// Write a 16-bit integer, endian independent.
static void WriteInt16(ostream &aStream, uint16 aValue)
{
unsigned char buf[2];
buf[0] = aValue & 255;
buf[1] = (aValue >> 8) & 255;
aStream.write((char *) buf, 2);
}
/// Read a 32-bit integer, endian independent.
static uint32 ReadInt32(istream &aStream)
{
unsigned char buf[4];
aStream.read((char *) buf, 4);
return ((uint32) buf[0]) | (((uint32) buf[1]) << 8) |
(((uint32) buf[2]) << 16) | (((uint32) buf[3]) << 24);
}
/// Write a 32-bit integer, endian independent.
static void WriteInt32(ostream &aStream, uint32 aValue)
{
unsigned char buf[4];
buf[0] = aValue & 255;
buf[1] = (aValue >> 8) & 255;
buf[2] = (aValue >> 16) & 255;
buf[3] = (aValue >> 24) & 255;
aStream.write((char *) buf, 4);
}
/// Read a Vector2, endian independent.
static Vector2 ReadVector2(istream &aStream)
{
union {
uint32 i;
float f;
} val;
Vector2 result;
val.i = ReadInt32(aStream);
result.u = val.f;
val.i = ReadInt32(aStream);
result.v = val.f;
return result;
}
/// Write a Vector2, endian independent.
static void WriteVector2(ostream &aStream, Vector2 aValue)
{
union {
uint32 i;
float f;
} val;
val.f = aValue.u;
WriteInt32(aStream, val.i);
val.f = aValue.v;
WriteInt32(aStream, val.i);
}
/// Read a Vector3, endian independent.
static Vector3 ReadVector3(istream &aStream)
{
union {
uint32 i;
float f;
} val;
Vector3 result;
val.i = ReadInt32(aStream);
result.x = val.f;
val.i = ReadInt32(aStream);
result.y = val.f;
val.i = ReadInt32(aStream);
result.z = val.f;
return result;
}
/// Write a Vector3, endian independent.
static void WriteVector3(ostream &aStream, Vector3 aValue)
{
union {
uint32 i;
float f;
} val;
val.f = aValue.x;
WriteInt32(aStream, val.i);
val.f = aValue.y;
WriteInt32(aStream, val.i);
val.f = aValue.z;
WriteInt32(aStream, val.i);
}
/// Import a 3DS file from a file.
void Import_3DS(const char * aFileName, Mesh * aMesh)
{
// Clear the mesh
aMesh->Clear();
// Open the input file
ifstream f(aFileName, ios::in | ios::binary);
if(f.fail())
throw runtime_error("Could not open input file.");
// Get file size
f.seekg(0, ios::end);
uint32 fileSize = f.tellg();
f.seekg(0, ios::beg);
// Check file size (rough initial check)
if(fileSize < 6)
throw runtime_error("Invalid 3DS file format.");
uint16 chunk, count;
uint32 chunkLen;
// Read & check file header identifier
chunk = ReadInt16(f);
chunkLen = ReadInt32(f);
if((chunk != CHUNK_MAIN) || (chunkLen != fileSize))
throw runtime_error("Invalid 3DS file format.");
// Parse chunks, and store the data in a temporary list, objList...
Obj3DS * obj = 0;
list<Obj3DS> objList;
bool hasUVCoords = false;
while(uint32(f.tellg()) < fileSize)
{
// Read next chunk
chunk = ReadInt16(f);
chunkLen = ReadInt32(f);
// What chunk did we get?
switch(chunk)
{
// 3D Edit -> Step into
case CHUNK_3DEDIT:
break;
// Object -> Step into
case CHUNK_OBJECT:
// Skip object name (null terminated string)
while((uint32(f.tellg()) < fileSize) && f.get()) {};
// Create a new object
objList.push_back(Obj3DS());
obj = &objList.back();
break;
// Triangle mesh -> Step into
case CHUNK_TRIMESH:
break;
// Vertex list (point coordinates)
case CHUNK_VERTEXLIST:
count = ReadInt16(f);
if((!obj) || ((obj->mVertices.size() > 0) && (obj->mVertices.size() != count)))
{
f.seekg(count * 12, ios::cur);
break;
}
if(obj->mVertices.size() == 0)
obj->mVertices.resize(count);
for(uint16 i = 0; i < count; ++ i)
obj->mVertices[i] = ReadVector3(f);
break;
// Texture map coordinates (UV coordinates)
case CHUNK_MAPPINGCOORDS:
count = ReadInt16(f);
if((!obj) || ((obj->mUVCoords.size() > 0) && (obj->mUVCoords.size() != count)))
{
f.seekg(count * 8, ios::cur);
break;
}
if(obj->mUVCoords.size() == 0)
obj->mUVCoords.resize(count);
for(uint16 i = 0; i < count; ++ i)
obj->mUVCoords[i] = ReadVector2(f);
if(count > 0)
hasUVCoords = true;
break;
// Face description (triangle indices)
case CHUNK_FACES:
count = ReadInt16(f);
if(!obj)
{
f.seekg(count * 8, ios::cur);
break;
}
if(obj->mIndices.size() == 0)
obj->mIndices.resize(3 * count);
for(uint32 i = 0; i < count; ++ i)
{
obj->mIndices[i * 3] = ReadInt16(f);
obj->mIndices[i * 3 + 1] = ReadInt16(f);
obj->mIndices[i * 3 + 2] = ReadInt16(f);
ReadInt16(f); // Skip face flag
}
break;
default: // Unknown/ignored - skip past this one
f.seekg(chunkLen - 6, ios::cur);
}
}
// Close the input file
f.close();
// Convert the loaded object list to the mesh structore (merge all geometries)
aMesh->Clear();
for(list<Obj3DS>::iterator o = objList.begin(); o != objList.end(); ++ o)
{
// Append...
uint32 idxOffset = aMesh->mIndices.size();
uint32 vertOffset = aMesh->mVertices.size();
aMesh->mIndices.resize(idxOffset + (*o).mIndices.size());
aMesh->mVertices.resize(vertOffset + (*o).mVertices.size());
if(hasUVCoords)
aMesh->mTexCoords.resize(vertOffset + (*o).mVertices.size());
// Transcode the data
for(uint32 i = 0; i < (*o).mIndices.size(); ++ i)
aMesh->mIndices[idxOffset + i] = vertOffset + uint32((*o).mIndices[i]);
for(uint32 i = 0; i < (*o).mVertices.size(); ++ i)
aMesh->mVertices[vertOffset + i] = (*o).mVertices[i];
if(hasUVCoords)
{
if((*o).mUVCoords.size() == (*o).mVertices.size())
for(uint32 i = 0; i < (*o).mVertices.size(); ++ i)
aMesh->mTexCoords[vertOffset + i] = (*o).mUVCoords[i];
else
for(uint32 i = 0; i < (*o).mVertices.size(); ++ i)
aMesh->mTexCoords[vertOffset + i] = Vector2(0.0f, 0.0f);
}
}
}
/// Export a 3DS file to a file.
void Export_3DS(const char * aFileName, Mesh * aMesh, Options &aOptions)
{
// First, check that the mesh fits in a 3DS file (at most 65535 triangles
// and 65535 vertices are supported).
if((aMesh->mIndices.size() > (3*65535)) || (aMesh->mVertices.size() > 65535))
throw runtime_error("The mesh is too large to fit in a 3DS file.");
// What should we export?
bool exportTexCoords = aMesh->HasTexCoords() && !aOptions.mNoTexCoords;
// Predefined names / strings
string objName("Object1");
string matName("Material0");
// Get mesh properties
uint32 triCount = aMesh->mIndices.size() / 3;
uint32 vertCount = aMesh->mVertices.size();
// Calculate the material chunk size
uint32 materialSize = 0;
uint32 matGroupSize = 0;
if(exportTexCoords && aMesh->mTexFileName.size() > 0)
{
materialSize += 24 + matName.size() + 1 + aMesh->mTexFileName.size() + 1;
matGroupSize += 8 + matName.size() + 1 + 2 * triCount;
}
// Calculate the mesh chunk size
uint32 triMeshSize = 22 + 8 * triCount + 12 * vertCount + matGroupSize;
if(exportTexCoords)
triMeshSize += 8 + 8 * vertCount;
// Calculate the total file size
uint32 fileSize = 38 + objName.size() + 1 + materialSize + triMeshSize;
// Open the output file
ofstream f(aFileName, ios::out | ios::binary);
if(f.fail())
throw runtime_error("Could not open output file.");
// Write file header
WriteInt16(f, CHUNK_MAIN);
WriteInt32(f, fileSize);
WriteInt16(f, CHUNK_M3D_VERSION);
WriteInt32(f, 6 + 4);
WriteInt32(f, 0x00000003);
// 3D Edit chunk
WriteInt16(f, CHUNK_3DEDIT);
WriteInt32(f, 16 + materialSize + objName.size() + 1 + triMeshSize);
WriteInt16(f, CHUNK_MESH_VERSION);
WriteInt32(f, 6 + 4);
WriteInt32(f, 0x00000003);
// Material chunk
if(materialSize > 0)
{
WriteInt16(f, CHUNK_MAT_ENTRY);
WriteInt32(f, materialSize);
WriteInt16(f, CHUNK_MAT_NAME);
WriteInt32(f, 6 + matName.size() + 1);
f.write(matName.c_str(), matName.size() + 1);
WriteInt16(f, CHUNK_MAT_TEXMAP);
WriteInt32(f, 12 + aMesh->mTexFileName.size() + 1);
WriteInt16(f, CHUNK_MAT_MAPNAME);
WriteInt32(f, 6 + aMesh->mTexFileName.size() + 1);
f.write(aMesh->mTexFileName.c_str(), aMesh->mTexFileName.size() + 1);
}
// Object chunk
WriteInt16(f, CHUNK_OBJECT);
WriteInt32(f, 6 + objName.size() + 1 + triMeshSize);
f.write(objName.c_str(), objName.size() + 1);
// Triangle Mesh chunk
WriteInt16(f, CHUNK_TRIMESH);
WriteInt32(f, triMeshSize);
// Vertex List chunk
WriteInt16(f, CHUNK_VERTEXLIST);
WriteInt32(f, 8 + 12 * vertCount);
WriteInt16(f, vertCount);
for(uint32 i = 0; i < vertCount; ++ i)
WriteVector3(f, aMesh->mVertices[i]);
// Mapping Coordinates chunk
if(exportTexCoords)
{
WriteInt16(f, CHUNK_MAPPINGCOORDS);
WriteInt32(f, 8 + 8 * vertCount);
WriteInt16(f, vertCount);
for(uint32 i = 0; i < vertCount; ++ i)
WriteVector2(f, aMesh->mTexCoords[i]);
}
// Faces chunk
WriteInt16(f, CHUNK_FACES);
WriteInt32(f, 8 + 8 * triCount);
WriteInt16(f, triCount);
for(uint32 i = 0; i < triCount; ++ i)
{
WriteInt16(f, uint16(aMesh->mIndices[i * 3]));
WriteInt16(f, uint16(aMesh->mIndices[i * 3 + 1]));
WriteInt16(f, uint16(aMesh->mIndices[i * 3 + 2]));
WriteInt16(f, 0);
}
// Material Group chunk
if(matGroupSize > 0)
{
WriteInt16(f, CHUNK_MSH_MAT_GROUP);
WriteInt32(f, matGroupSize);
f.write(matName.c_str(), matName.size() + 1);
WriteInt16(f, triCount);
for(uint16 i = 0; i < triCount; ++ i)
WriteInt16(f, i);
}
}