Code:
#define CHUNKID_PRIMARY 0x4D4D
#define CHUNKID_MATERIAL 0xAFFF
#define CHUNKID_MATNAME 0xA000
#define CHUNKID_MATSHININESS 0xA040
#define CHUNKID_MATSHINSTRENGTH 0xA041
#define CHUNKID_MATDIFFUSE 0xA020
#define CHUNKID_MATRADIANCE 0xA084
#define CHUNKID_MATMAP 0xA200
#define CHUNKID_MATMAPNAME 0xA300
#define CHUNKID_OBJECTINFO 0x3D3D
#define CHUNKID_OBJECT 0x4000
#define CHUNKID_OBJECT_MESH 0x4100
#define CHUNKID_OBJECT_VERTICES 0x4110
#define CHUNKID_OBJECT_FACES 0x4120
#define CHUNKID_OBJECT_FACEMAT 0x4130
#define CHUNKID_OBJECT_UV 0x4140
void meshres::loadMesh (compiler &comp, const string &sFile)
{
filehandle fh;
if ((fh = file_open (sFile)) == 0)
throwException (ERRCODE_NOT_FOUND, "unable to open sound file %s", sFile.str());
size_t cbFile = file_getsize (fh);
uchar *abFile = (uchar*)malloc (cbFile);
file_read (fh, abFile, cbFile);
file_close (fh);
bTry {
datastream data ((char*)abFile, cbFile);
loadMesh (comp, data);
free (abFile);
} bCatch (err) {
free (abFile);
throwException (ERRCODE_ILLEGAL_INPUT, "%s\nerror %d processing %s", getLastException().str(), err, sFile.str());
} bEndCatch
}
void meshres::loadMesh (compiler &comp, datastream &data)
{
datachunk primary (data);
if (primary.getID() != CHUNKID_PRIMARY)
throwException (ERRCODE_ILLEGAL_INPUT, "unrecognized mesh file format (export as .3DS)");
loadNextChunk (comp, primary);
}
void meshres::loadNextChunk (compiler &comp, datachunk &dcParent)
{
for (datachunk dc (dcParent); !dc.isEOF(); dc.next())
{
if (dc.getID() == CHUNKID_OBJECTINFO)
{
loadNextChunk (comp, dc);
}
else if (dc.getID() == CHUNKID_MATERIAL)
{
loadMaterial (comp, dc);
}
else if (dc.getID() == CHUNKID_OBJECT)
{
loadObject (dc);
}
}
}
void meshres::loadMaterial (compiler &comp, datachunk &dcParent)
{
textureres tr;
loadMaterialChunk (comp, dcParent, tr);
tr.idbImage = 0;
if (!!tr.sImageName)
{
if ((tr.idbImage = comp.findImage (tr.sImageName)) == 0)
printf ("warning: no image provided for texture %s; surface will be solid color\n", tr.sImageName.c_str());
else
comp.warnAboutTextureSize (tr.idbImage, _sName);
}
_aTextures.push_back (tr);
}
void meshres::loadMaterialChunk (compiler &comp, datachunk &dcParent, textureres &tr)
{
for (datachunk dc (dcParent); !dc.isEOF(); dc.next())
{
if (dc.getID() == CHUNKID_MATNAME)
tr.sTextureName = dc.readString();
else if (dc.getID() == CHUNKID_MATDIFFUSE)
tr.clr = dc.readColor();
else if (dc.getID() == CHUNKID_MATSHININESS)
tr.polish = dc.readPercentage();
else if (dc.getID() == CHUNKID_MATSHINSTRENGTH)
tr.reflectance = dc.readPercentage();
else if (dc.getID() == CHUNKID_MATRADIANCE)
tr.radiance = dc.readPercentage();
else if (dc.getID() == CHUNKID_MATMAPNAME)
tr.sImageName = dc.readString();
else if (dc.getID() == CHUNKID_MATMAP)
loadMaterialChunk (comp, dc, tr);
}
}
void meshres::loadObject (datachunk &dcParent)
{
string sObjName = dcParent.readString();
for (datachunk dc (dcParent); !dc.isEOF(); dc.next())
{
if (dc.getID() == CHUNKID_OBJECT_MESH)
loadObjectMesh (dc, _aVertices.size(), _aFaces.size());
}
}
void meshres::loadObjectMesh (datachunk &dcParent, size_t iVertexBase, size_t iFaceBase)
{
datastream &ds = dcParent.getStream();
for (datachunk dc (dcParent); !dc.isEOF(); dc.next())
{
if (dc.getID() == CHUNKID_OBJECT_VERTICES)
{
size_t cVertices = ds.pop16();
for (size_t ii = 0; ii < cVertices; ++ii)
{
float right = *(float*)(ds.read (sizeof (float)));
float out = *(float*)(ds.read (sizeof (float)));
float up = *(float*)(ds.read (sizeof (float)));
vertexres vr;
vr.vWorld.dx = right;
vr.vWorld.dy = up;
vr.vWorld.dz = -out; // meshlib uses right-handed coords, z toward your nose
vr.duS16 = 0;
vr.dvS16 = 0;
_aVertices.push_back (vr);
}
}
else if (dc.getID() == CHUNKID_OBJECT_FACES)
{
if (!_aTextures.size())
throwException (ERRCODE_ILLEGAL_INPUT, "face defined with no texture");
size_t iTex = _aTextures.size()-1;
size_t cFaces = ds.pop16();
for (size_t ii = 0; ii < cFaces; ++ii)
{
faceres fr;
fr.iTexture = iTex;
fr.iVertex1 = ds.pop16() + iVertexBase;
fr.iVertex2 = ds.pop16() + iVertexBase;
fr.iVertex3 = ds.pop16() + iVertexBase;
fr.flags = 0;
size_t flags = ds.pop16();
// 3DS writes as left-handed coords, and therefore winds its
// vertices clockwise. Meshlib uses right-handed coords--that's
// why we flipped the sign on the Z axis above. And when that
// axis flip happened, the incoming winding suddenly became
// counter-clockwise--so we don't need to swap vertices for
// most models. That's why the default winding (CCW) doesn't
// need a vertex swap.
if (_wind == WINDING_CW)
{
fr.iVertex2 ^= fr.iVertex3 ^= fr.iVertex2 ^= fr.iVertex3;
}
else if (_wind == WINDING_DOUBLE)
{
fr.flags |= MESHLIB_FACE_FLAGS_DOUBLESIDED;
}
if ((fr.iVertex1 >= _aVertices.size()) ||
(fr.iVertex2 >= _aVertices.size()) ||
(fr.iVertex3 >= _aVertices.size()))
{
throwException (ERRCODE_ILLEGAL_INPUT, "illegal vertex index used in mesh face definition");
}
_aFaces.push_back (fr);
}
loadObjectMesh (dc, iVertexBase, iFaceBase);
}
else if (dc.getID() == CHUNKID_OBJECT_FACEMAT)
{
string sTextureName = dc.readString();
size_t iTex;
for (iTex = 0; iTex < _aTextures.size(); ++iTex)
{
if (sTextureName == _aTextures[iTex].sTextureName)
break;
}
if (iTex == _aTextures.size())
throwException (ERRCODE_ILLEGAL_INPUT, "illegal texture name used in mesh face definition");
size_t cFaces = ds.pop16();
for (size_t ii = 0; ii < cFaces; ++ii)
{
size_t iFace = iFaceBase + ds.pop16();
faceres &fr = _aFaces[ iFace ];
fr.iTexture = iTex;
}
}
else if (dc.getID() == CHUNKID_OBJECT_UV)
{
size_t cVertices = ds.pop16();
for (size_t ii = 0; ii < cVertices; ++ii)
{
vertexres &vr = _aVertices[ iVertexBase + ii ];
float uu = *(float*)(ds.read (sizeof (float)));
float vv = *(float*)(ds.read (sizeof (float)));
vr.duS16 = (long)(uu * 65536);
vr.dvS16 = (long)(vv * 65536);
}
}
}
}
class datachunk
{
public:
datachunk (datastream &ds);
datachunk (datachunk &dcParent);
~datachunk (void);
string readString (void);
color readColor (void);
uint8 readPercentage (void);
size_t getID (void);
size_t getLength (void);
datastream & getStream (void);
bool isEOF (void);
bool next (void);
protected:
datastream &_ds;
size_t _ibParentStops;
size_t _idChunk;
size_t _cbChunk;
size_t _ibStart;
size_t _ibAfter;
bool _isEOF;
};
#define CHUNKHEADERSIZE 6
datachunk::datachunk (datastream &ds) : _ds(ds)
{
_idChunk = 0;
_cbChunk = 0;
_ibStart = 0;
_ibAfter = 0;
_ibParentStops = _ds.getPos() + _ds.getRemaining();
_isEOF = false;
(void)next();
}
datachunk::datachunk (datachunk &dcParent) : _ds(dcParent._ds)
{
_idChunk = 0;
_cbChunk = 0;
_ibStart = _ds.getPos();
_ibParentStops = dcParent._ibAfter;
_ibAfter = _ibStart;
_isEOF = dcParent._isEOF;
(void)next();
}
datachunk::~datachunk (void)
{
if (_ds.getPos() < _ibAfter)
_ds.setPos (_ibAfter);
}
size_t datachunk::getID (void)
{
if (_isEOF)
throwException (ERRCODE_ILLEGAL_INPUT, "unexpected EOF");
return _idChunk;
}
size_t datachunk::getLength (void)
{
if (_isEOF)
throwException (ERRCODE_ILLEGAL_INPUT, "unexpected EOF");
return _cbChunk;
}
datastream & datachunk::getStream (void)
{
return _ds;
}
string datachunk::readString (void)
{
string rc;
char *psz = _ds.peek();
while (*psz)
{
rc += *psz++;
_ds.advance(1);
}
_ds.advance(1);
return rc;
}
color datachunk::readColor (void)
{
datachunk dc (*this);
if (dc.getID() == 0x0011) // 3DS COLOR_24 chunk
{
unsigned char *psz = (unsigned char *)_ds.read(3);
return color (psz[0], psz[1], psz[2]);
}
else if (dc.getID() == 0x0010) // 3DS COLOR_F chunk
{
float *pf = (float *)_ds.read(12);
return color ((uchar)(pf[0] * 255), (uchar)(pf[1] * 255), (uchar)(pf[2] * 255));
}
throwException (ERRCODE_ILLEGAL_INPUT, "Unrecognized color format %X", dc.getID());
return color (0, 0, 0);
}
uint8 datachunk::readPercentage (void)
{
datachunk dc (*this);
if (dc.getID() != 0x0030) // 3DS INT_PERCENTAGE chunk
throwException (ERRCODE_ILLEGAL_INPUT, "Unrecognized color format %X", dc.getID());
uint16 val = _ds.pop16();
return (val > 100) ? 255 : (uint8)((size_t)val * 255 / 100);
}
bool datachunk::isEOF (void)
{
return _isEOF;
}
bool datachunk::next (void)
{
if (!_isEOF)
{
_ds.setPos (_ibAfter);
_ibStart = _ds.getPos();
if (_ds.getRemaining() < CHUNKHEADERSIZE)
{
_isEOF = true;
}
else if (_ds.getPos() >= _ibParentStops)
{
_isEOF = true;
}
else
{
_idChunk = _ds.pop16();
_cbChunk = _ds.pop32();
_ibAfter = _ibStart + _cbChunk;
if (_ds.getRemaining() < _ibAfter - _ds.getPos())
throwException (ERRCODE_ILLEGAL_INPUT, "incomplete 3DS data file chunk");
}
}
return !_isEOF;
}