mirror of
https://github.com/scratchfoundation/bgfx.git
synced 2024-12-01 11:56:58 -05:00
432 lines
12 KiB
C++
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);
|
|
}
|
|
}
|