diff --git a/doomsday/client/include/resource/model.h b/doomsday/client/include/resource/model.h index a30a42f8e7..d610eafdee 100644 --- a/doomsday/client/include/resource/model.h +++ b/doomsday/client/include/resource/model.h @@ -145,6 +145,12 @@ class Model */ Model(Flags flags = 0); + /** + * Determines whether the specified @a file appears to be in a recognised + * model format. + */ + static bool recognise(de::FileHandle &file); + /** * Attempt to load a new model resource from the specified @a file. * diff --git a/doomsday/client/src/resource/model.cpp b/doomsday/client/src/resource/model.cpp index 20686dd776..35ae1322ed 100644 --- a/doomsday/client/src/resource/model.cpp +++ b/doomsday/client/src/resource/model.cpp @@ -49,179 +49,6 @@ float Model::Frame::horizontalRange(float *top, float *bottom) const return max.y - min.y; } -DENG2_PIMPL(Model) -{ - uint modelId; ///< Unique id of the model (in the repository). - Flags flags; - Skins skins; - Frames frames; - - Instance(Public *i) - : Base(i) - , modelId(0) - {} - - ~Instance() - { - self.clearAllFrames(); - } -}; - -Model::Model(Flags flags) - : _numVertices(0) - , d(new Instance(this)) -{ - setFlags(flags, de::ReplaceFlags); -} - -static bool recogniseDmd(de::FileHandle &file); -static bool recogniseMd2(de::FileHandle &file); -static Model *loadDmd(de::FileHandle &file, float aspectScale); -static Model *loadMd2(de::FileHandle &file, float aspectScale); - -#define NUMVERTEXNORMALS 162 -static float avertexnormals[NUMVERTEXNORMALS][3] = { -#include "tab_anorms.h" -}; - -/** - * Calculate vertex normals. Only with -renorm. - */ -#if 0 // unused atm. -static void rebuildNormals(model_t &mdl) -{ - // Renormalizing? - if(!CommandLine_Check("-renorm")) return; - - int const tris = mdl.lodInfo[0].numTriangles; - int const verts = mdl.info.numVertices; - - vector_t* normals = (vector_t*) Z_Malloc(sizeof(vector_t) * tris, PU_APPSTATIC, 0); - vector_t norm; - int cnt; - - // Calculate the normal for each vertex. - for(int i = 0; i < mdl.info.numFrames; ++i) - { - model_vertex_t* list = mdl.frames[i].vertices; - - for(int k = 0; k < tris; ++k) - { - dmd_triangle_t const& tri = mdl.lods[0].triangles[k]; - - // First calculate surface normals, combine them to vertex ones. - V3f_PointCrossProduct(normals[k].pos, - list[tri.vertexIndices[0]].vertex, - list[tri.vertexIndices[2]].vertex, - list[tri.vertexIndices[1]].vertex); - V3f_Normalize(normals[k].pos); - } - - for(int k = 0; k < verts; ++k) - { - memset(&norm, 0, sizeof(norm)); - cnt = 0; - - for(int j = 0; j < tris; ++j) - { - dmd_triangle_t const& tri = mdl.lods[0].triangles[j]; - - for(int n = 0; n < 3; ++n) - { - if(tri.vertexIndices[n] == k) - { - cnt++; - for(int n = 0; n < 3; ++n) - { - norm.pos[n] += normals[j].pos[n]; - } - break; - } - } - } - - if(!cnt) continue; // Impossible... - - // Calculate the average. - for(int n = 0; n < 3; ++n) - { - norm.pos[n] /= cnt; - } - - // Normalize it. - V3f_Normalize(norm.pos); - memcpy(list[k].normal, norm.pos, sizeof(norm.pos)); - } - } - - Z_Free(normals); -} -#endif - -static Model *interpretDmd(de::FileHandle &hndl, float scale) -{ - if(recogniseDmd(hndl)) - { - LOG_VERBOSE("Interpreted \"" + NativePath(hndl.file().composePath()).pretty() + "\" as a DMD model."); - return loadDmd(hndl, scale); - } - return 0; -} - -static Model *interpretMd2(de::FileHandle &hndl, float scale) -{ - if(recogniseMd2(hndl)) - { - LOG_VERBOSE("Interpreted \"" + NativePath(hndl.file().composePath()).pretty() + "\" as a MD2 model."); - return loadMd2(hndl, scale); - } - return 0; -} - -struct ModelFileType -{ - /// Symbolic name of the resource type. - String const name; - - /// Known file extension. - String const ext; - - Model *(*interpretFunc)(de::FileHandle &hndl, float scale); -}; - -// Model resource types. -static ModelFileType const modelTypes[] = { - { "DMD", ".dmd", interpretDmd }, - { "MD2", ".md2", interpretMd2 }, - { "", "", 0 } // Terminate. -}; - -static ModelFileType const *guessModelFileTypeFromFileName(String filePath) -{ - // An extension is required for this. - String ext = filePath.fileNameExtension(); - if(!ext.isEmpty()) - { - for(int i = 0; !modelTypes[i].name.isEmpty(); ++i) - { - ModelFileType const &type = modelTypes[i]; - if(!type.ext.compareWithoutCase(ext)) - { - return &type; - } - } - } - return 0; // Unknown. -} - -static void *allocAndLoad(de::FileHandle &file, int offset, int len) -{ - uint8_t *ptr = (uint8_t *) M_Malloc(len); - file.seek(offset, SeekSet); - file.read(ptr, len); - return ptr; -} - // #define MD2_MAGIC 0x32504449 @@ -286,124 +113,6 @@ static bool recogniseMd2(de::FileHandle &file) return result; } -#pragma pack(1) -struct md2_triangleVertex_t -{ - byte vertex[3]; - byte normalIndex; -}; - -struct md2_packedFrame_t -{ - float scale[3]; - float translate[3]; - char name[16]; - md2_triangleVertex_t vertices[1]; -}; - -struct md2_commandElement_t { - float s, t; - int index; -}; -#pragma pack() - -/** - * Note vertex Z/Y are swapped here (ordered XYZ in the serialized data). - */ -static Model *loadMd2(de::FileHandle &file, float aspectScale) -{ - // Read the header. - md2_header_t hdr; - bool readHeaderOk = readMd2Header(file, hdr); - DENG2_ASSERT(readHeaderOk); - DENG2_UNUSED(readHeaderOk); // should this be checked? - - Model *mdl = new Model; - - mdl->_numVertices = hdr.numVertices; - - // Load and convert to DMD. - uint8_t *frameData = (uint8_t *) allocAndLoad(file, hdr.offsetFrames, hdr.frameSize * hdr.numFrames); - for(int i = 0; i < hdr.numFrames; ++i) - { - md2_packedFrame_t const *pfr = (md2_packedFrame_t const *) (frameData + hdr.frameSize * i); - Vector3f const scale(FLOAT(pfr->scale[0]), FLOAT(pfr->scale[2]), FLOAT(pfr->scale[1])); - Vector3f const translation(FLOAT(pfr->translate[0]), FLOAT(pfr->translate[2]), FLOAT(pfr->translate[1])); - String const frameName = pfr->name; - - ModelFrame *frame = new ModelFrame(*mdl, frameName); - frame->vertices.reserve(hdr.numVertices); - - // Scale and translate each vertex. - md2_triangleVertex_t const *pVtx = pfr->vertices; - for(int k = 0; k < hdr.numVertices; ++k, pVtx++) - { - frame->vertices.append(ModelFrame::Vertex()); - ModelFrame::Vertex &vtx = frame->vertices.last(); - - vtx.pos = Vector3f(pVtx->vertex[0], pVtx->vertex[2], pVtx->vertex[1]) - * scale + translation; - vtx.pos.y *= aspectScale; // Aspect undoing. - - vtx.norm = Vector3f(avertexnormals[pVtx->normalIndex]); - - if(!k) - { - frame->min = frame->max = vtx.pos; - } - else - { - frame->min = vtx.pos.min(frame->min); - frame->max = vtx.pos.max(frame->max); - } - } - - mdl->addFrame(frame); // takes owernship - } - M_Free(frameData); - - mdl->_lods.append(new ModelDetailLevel(*mdl, 0)); - ModelDetailLevel &lod0 = *mdl->_lods.last(); - - uint8_t *commandData = (uint8_t *) allocAndLoad(file, hdr.offsetGlCommands, 4 * hdr.numGlCommands); - for(uint8_t const *pos = commandData; *pos;) - { - int count = LONG( *(int *) pos ); pos += 4; - - lod0.primitives.append(Model::Primitive()); - Model::Primitive &prim = lod0.primitives.last(); - - // The type of primitive depends on the sign. - prim.triFan = (count < 0); - - if(count < 0) - { - count = -count; - } - - while(count--) - { - md2_commandElement_t const *v = (md2_commandElement_t *) pos; pos += 12; - - prim.elements.append(Model::Primitive::Element()); - Model::Primitive::Element &elem = prim.elements.last(); - elem.texCoord = Vector2f(FLOAT(v->s), FLOAT(v->t)); - elem.index = LONG(v->index); - } - } - M_Free(commandData); - - // Load skins. (Note: numSkins may be zero.) - file.seek(hdr.offsetSkins, SeekSet); - for(int i = 0; i < hdr.numSkins; ++i) - { - char name[64]; file.read((uint8_t *)name, 64); - mdl->newSkin(name); - } - - return mdl; -} - // #define DMD_MAGIC 0x4D444D44 ///< "DMDM" = Doomsday/Detailed MoDel Magic @@ -439,201 +148,125 @@ static bool recogniseDmd(de::FileHandle &file) return result; } -// DMD chunk types. -enum { - DMC_END, /// Must be the last chunk. - DMC_INFO /// Required; will be expected to exist. -}; - -#pragma pack(1) -typedef struct { - int type; - int length; /// Next chunk follows... -} dmd_chunk_t; - -typedef struct { - int skinWidth; - int skinHeight; - int frameSize; - int numSkins; - int numVertices; - int numTexCoords; - int numFrames; - int numLODs; - int offsetSkins; - int offsetTexCoords; - int offsetFrames; - int offsetLODs; - int offsetEnd; -} dmd_info_t; - -typedef struct { - int numTriangles; - int numGlCommands; - int offsetTriangles; - int offsetGlCommands; -} dmd_levelOfDetail_t; - -typedef struct { - byte vertex[3]; - unsigned short normal; /// Yaw and pitch. -} dmd_packedVertex_t; - -typedef struct { - float scale[3]; - float translate[3]; - char name[16]; - dmd_packedVertex_t vertices[1]; // dmd_info_t::numVertices size -} dmd_packedFrame_t; - -typedef struct { - short vertexIndices[3]; - short textureIndices[3]; -} dmd_triangle_t; -#pragma pack() - -/** - * Packed: pppppppy yyyyyyyy. Yaw is on the XY plane. - */ -static Vector3f unpackVector(ushort packed) +static void *allocAndLoad(de::FileHandle &file, int offset, int len) { - float const yaw = (packed & 511) / 512.0f * 2 * PI; - float const pitch = ((packed >> 9) / 127.0f - 0.5f) * PI; - float const cosp = float(cos(pitch)); - return Vector3f(cos(yaw) * cosp, sin(yaw) * cosp, sin(pitch)); + uint8_t *ptr = (uint8_t *) M_Malloc(len); + file.seek(offset, SeekSet); + file.read(ptr, len); + return ptr; } -/** - * Note vertex Z/Y are swapped here (ordered XYZ in the serialized data). - */ -static Model *loadDmd(de::FileHandle &file, float aspectScale) +// Precalculated normal LUT for use when loading MD2/DMD format models. +#define NUMVERTEXNORMALS 162 +static float avertexnormals[NUMVERTEXNORMALS][3] = { +#include "tab_anorms.h" +}; + +DENG2_PIMPL(Model) { - // Read the header. - dmd_header_t hdr; - bool readHeaderOk = readHeaderDmd(file, hdr); - DENG2_ASSERT(readHeaderOk); - DENG2_UNUSED(readHeaderOk); // should this be checked? + uint modelId; ///< Unique id of the model (in the repository). + Flags flags; + Skins skins; + Frames frames; - // Read the chunks. - dmd_chunk_t chunk; - file.read((uint8_t *)&chunk, sizeof(chunk)); + Instance(Public *i) + : Base(i) + , modelId(0) + {} - dmd_info_t info; zap(info); - while(LONG(chunk.type) != DMC_END) + ~Instance() { - switch(LONG(chunk.type)) - { - case DMC_INFO: // Standard DMD information chunk. - file.read((uint8_t *)&info, LONG(chunk.length)); - - info.skinWidth = LONG(info.skinWidth); - info.skinHeight = LONG(info.skinHeight); - info.frameSize = LONG(info.frameSize); - info.numSkins = LONG(info.numSkins); - info.numVertices = LONG(info.numVertices); - info.numTexCoords = LONG(info.numTexCoords); - info.numFrames = LONG(info.numFrames); - info.numLODs = LONG(info.numLODs); - info.offsetSkins = LONG(info.offsetSkins); - info.offsetTexCoords = LONG(info.offsetTexCoords); - info.offsetFrames = LONG(info.offsetFrames); - info.offsetLODs = LONG(info.offsetLODs); - info.offsetEnd = LONG(info.offsetEnd); - break; - - default: - // Skip unknown chunks. - file.seek(LONG(chunk.length), SeekCur); - break; - } - // Read the next chunk header. - file.read((uint8_t *)&chunk, sizeof(chunk)); + self.clearAllFrames(); } - Model *mdl = new Model; - - mdl->_numVertices = info.numVertices; - - // Allocate and load in the data. (Note: numSkins may be zero.) - file.seek(info.offsetSkins, SeekSet); - for(int i = 0; i < info.numSkins; ++i) + #pragma pack(1) + struct md2_triangleVertex_t { - char name[64]; file.read((uint8_t *)name, 64); - mdl->newSkin(name); - } + byte vertex[3]; + byte normalIndex; + }; - uint8_t *frameData = (uint8_t *) allocAndLoad(file, info.offsetFrames, info.frameSize * info.numFrames); - for(int i = 0; i < info.numFrames; ++i) + struct md2_packedFrame_t { - dmd_packedFrame_t const *pfr = (dmd_packedFrame_t *) (frameData + info.frameSize * i); - Vector3f const scale(FLOAT(pfr->scale[0]), FLOAT(pfr->scale[2]), FLOAT(pfr->scale[1])); - Vector3f const translation(FLOAT(pfr->translate[0]), FLOAT(pfr->translate[2]), FLOAT(pfr->translate[1])); - String const frameName = pfr->name; + float scale[3]; + float translate[3]; + char name[16]; + md2_triangleVertex_t vertices[1]; + }; + + struct md2_commandElement_t { + float s, t; + int index; + }; + #pragma pack() + + /** + * Note vertex Z/Y are swapped here (ordered XYZ in the serialized data). + */ + static Model *loadMd2(de::FileHandle &file, float aspectScale) + { + // Read the header. + md2_header_t hdr; + bool readHeaderOk = readMd2Header(file, hdr); + DENG2_ASSERT(readHeaderOk); + DENG2_UNUSED(readHeaderOk); // should this be checked? - ModelFrame *frame = new ModelFrame(*mdl, frameName); - frame->vertices.reserve(info.numVertices); + Model *mdl = new Model; - // Scale and translate each vertex. - dmd_packedVertex_t const *pVtx = pfr->vertices; - for(int k = 0; k < info.numVertices; ++k, ++pVtx) - { - frame->vertices.append(ModelFrame::Vertex()); - ModelFrame::Vertex &vtx = frame->vertices.last(); + mdl->_numVertices = hdr.numVertices; - vtx.pos = Vector3f(pVtx->vertex[0], pVtx->vertex[2], pVtx->vertex[1]) - * scale + translation; - vtx.pos.y *= aspectScale; // Aspect undo. + // Load and convert to DMD. + uint8_t *frameData = (uint8_t *) allocAndLoad(file, hdr.offsetFrames, hdr.frameSize * hdr.numFrames); + for(int i = 0; i < hdr.numFrames; ++i) + { + md2_packedFrame_t const *pfr = (md2_packedFrame_t const *) (frameData + hdr.frameSize * i); + Vector3f const scale(FLOAT(pfr->scale[0]), FLOAT(pfr->scale[2]), FLOAT(pfr->scale[1])); + Vector3f const translation(FLOAT(pfr->translate[0]), FLOAT(pfr->translate[2]), FLOAT(pfr->translate[1])); + String const frameName = pfr->name; - vtx.norm = unpackVector(USHORT(pVtx->normal)); + ModelFrame *frame = new ModelFrame(*mdl, frameName); + frame->vertices.reserve(hdr.numVertices); - if(!k) - { - frame->min = frame->max = vtx.pos; - } - else + // Scale and translate each vertex. + md2_triangleVertex_t const *pVtx = pfr->vertices; + for(int k = 0; k < hdr.numVertices; ++k, pVtx++) { - frame->min = vtx.pos.min(frame->min); - frame->max = vtx.pos.max(frame->max); - } - } + frame->vertices.append(ModelFrame::Vertex()); + ModelFrame::Vertex &vtx = frame->vertices.last(); - mdl->addFrame(frame); - } - M_Free(frameData); + vtx.pos = Vector3f(pVtx->vertex[0], pVtx->vertex[2], pVtx->vertex[1]) + * scale + translation; + vtx.pos.y *= aspectScale; // Aspect undoing. - file.seek(info.offsetLODs, SeekSet); - dmd_levelOfDetail_t *lodInfo = new dmd_levelOfDetail_t[info.numLODs]; + vtx.norm = Vector3f(avertexnormals[pVtx->normalIndex]); - for(int i = 0; i < info.numLODs; ++i) - { - file.read((uint8_t *)&lodInfo[i], sizeof(dmd_levelOfDetail_t)); - - lodInfo[i].numTriangles = LONG(lodInfo[i].numTriangles); - lodInfo[i].numGlCommands = LONG(lodInfo[i].numGlCommands); - lodInfo[i].offsetTriangles = LONG(lodInfo[i].offsetTriangles); - lodInfo[i].offsetGlCommands = LONG(lodInfo[i].offsetGlCommands); - } - - dmd_triangle_t **triangles = new dmd_triangle_t*[info.numLODs]; + if(!k) + { + frame->min = frame->max = vtx.pos; + } + else + { + frame->min = vtx.pos.min(frame->min); + frame->max = vtx.pos.max(frame->max); + } + } - for(int i = 0; i < info.numLODs; ++i) - { - mdl->_lods.append(new ModelDetailLevel(*mdl, i)); - ModelDetailLevel &lod = *mdl->_lods.last(); + mdl->addFrame(frame); // takes owernship + } + M_Free(frameData); - triangles[i] = (dmd_triangle_t *) allocAndLoad(file, lodInfo[i].offsetTriangles, - sizeof(dmd_triangle_t) * lodInfo[i].numTriangles); + mdl->_lods.append(new ModelDetailLevel(*mdl, 0)); + ModelDetailLevel &lod0 = *mdl->_lods.last(); - uint8_t *commandData = (uint8_t *) allocAndLoad(file, lodInfo[i].offsetGlCommands, - 4 * lodInfo[i].numGlCommands); + uint8_t *commandData = (uint8_t *) allocAndLoad(file, hdr.offsetGlCommands, 4 * hdr.numGlCommands); for(uint8_t const *pos = commandData; *pos;) { int count = LONG( *(int *) pos ); pos += 4; - lod.primitives.append(Model::Primitive()); - Model::Primitive &prim = lod.primitives.last(); + lod0.primitives.append(Model::Primitive()); + Model::Primitive &prim = lod0.primitives.last(); - // The type of primitive depends on the sign of the element count. + // The type of primitive depends on the sign. prim.triFan = (count < 0); if(count < 0) @@ -647,45 +280,406 @@ static Model *loadDmd(de::FileHandle &file, float aspectScale) prim.elements.append(Model::Primitive::Element()); Model::Primitive::Element &elem = prim.elements.last(); - elem.texCoord = Vector2f(FLOAT(v->s), FLOAT(v->t)); elem.index = LONG(v->index); } } M_Free(commandData); + + // Load skins. (Note: numSkins may be zero.) + file.seek(hdr.offsetSkins, SeekSet); + for(int i = 0; i < hdr.numSkins; ++i) + { + char name[64]; file.read((uint8_t *)name, 64); + mdl->newSkin(name); + } + + return mdl; } - // Determine vertex usage at each LOD level. - mdl->_vertexUsage.resize(info.numVertices * info.numLODs); - mdl->_vertexUsage.fill(false); + // DMD chunk types. + enum { + DMC_END, /// Must be the last chunk. + DMC_INFO /// Required; will be expected to exist. + }; + + #pragma pack(1) + typedef struct { + int type; + int length; /// Next chunk follows... + } dmd_chunk_t; + + typedef struct { + int skinWidth; + int skinHeight; + int frameSize; + int numSkins; + int numVertices; + int numTexCoords; + int numFrames; + int numLODs; + int offsetSkins; + int offsetTexCoords; + int offsetFrames; + int offsetLODs; + int offsetEnd; + } dmd_info_t; + + typedef struct { + int numTriangles; + int numGlCommands; + int offsetTriangles; + int offsetGlCommands; + } dmd_levelOfDetail_t; + + typedef struct { + byte vertex[3]; + unsigned short normal; /// Yaw and pitch. + } dmd_packedVertex_t; + + typedef struct { + float scale[3]; + float translate[3]; + char name[16]; + dmd_packedVertex_t vertices[1]; // dmd_info_t::numVertices size + } dmd_packedFrame_t; + + typedef struct { + short vertexIndices[3]; + short textureIndices[3]; + } dmd_triangle_t; + #pragma pack() + + /** + * Packed: pppppppy yyyyyyyy. Yaw is on the XY plane. + */ + static Vector3f unpackVector(ushort packed) + { + float const yaw = (packed & 511) / 512.0f * 2 * PI; + float const pitch = ((packed >> 9) / 127.0f - 0.5f) * PI; + float const cosp = float(cos(pitch)); + return Vector3f(cos(yaw) * cosp, sin(yaw) * cosp, sin(pitch)); + } - for(int i = 0; i < info.numLODs; ++i) - for(int k = 0; k < lodInfo[i].numTriangles; ++k) - for(int m = 0; m < 3; ++m) + /** + * Note vertex Z/Y are swapped here (ordered XYZ in the serialized data). + */ + static Model *loadDmd(de::FileHandle &file, float aspectScale) { - int vertexIndex = SHORT(triangles[i][k].vertexIndices[m]); - mdl->_vertexUsage.setBit(vertexIndex * info.numLODs + i); + // Read the header. + dmd_header_t hdr; + bool readHeaderOk = readHeaderDmd(file, hdr); + DENG2_ASSERT(readHeaderOk); + DENG2_UNUSED(readHeaderOk); // should this be checked? + + // Read the chunks. + dmd_chunk_t chunk; + file.read((uint8_t *)&chunk, sizeof(chunk)); + + dmd_info_t info; zap(info); + while(LONG(chunk.type) != DMC_END) + { + switch(LONG(chunk.type)) + { + case DMC_INFO: // Standard DMD information chunk. + file.read((uint8_t *)&info, LONG(chunk.length)); + + info.skinWidth = LONG(info.skinWidth); + info.skinHeight = LONG(info.skinHeight); + info.frameSize = LONG(info.frameSize); + info.numSkins = LONG(info.numSkins); + info.numVertices = LONG(info.numVertices); + info.numTexCoords = LONG(info.numTexCoords); + info.numFrames = LONG(info.numFrames); + info.numLODs = LONG(info.numLODs); + info.offsetSkins = LONG(info.offsetSkins); + info.offsetTexCoords = LONG(info.offsetTexCoords); + info.offsetFrames = LONG(info.offsetFrames); + info.offsetLODs = LONG(info.offsetLODs); + info.offsetEnd = LONG(info.offsetEnd); + break; + + default: + // Skip unknown chunks. + file.seek(LONG(chunk.length), SeekCur); + break; + } + // Read the next chunk header. + file.read((uint8_t *)&chunk, sizeof(chunk)); + } + + Model *mdl = new Model; + + mdl->_numVertices = info.numVertices; + + // Allocate and load in the data. (Note: numSkins may be zero.) + file.seek(info.offsetSkins, SeekSet); + for(int i = 0; i < info.numSkins; ++i) + { + char name[64]; file.read((uint8_t *)name, 64); + mdl->newSkin(name); + } + + uint8_t *frameData = (uint8_t *) allocAndLoad(file, info.offsetFrames, info.frameSize * info.numFrames); + for(int i = 0; i < info.numFrames; ++i) + { + dmd_packedFrame_t const *pfr = (dmd_packedFrame_t *) (frameData + info.frameSize * i); + Vector3f const scale(FLOAT(pfr->scale[0]), FLOAT(pfr->scale[2]), FLOAT(pfr->scale[1])); + Vector3f const translation(FLOAT(pfr->translate[0]), FLOAT(pfr->translate[2]), FLOAT(pfr->translate[1])); + String const frameName = pfr->name; + + ModelFrame *frame = new ModelFrame(*mdl, frameName); + frame->vertices.reserve(info.numVertices); + + // Scale and translate each vertex. + dmd_packedVertex_t const *pVtx = pfr->vertices; + for(int k = 0; k < info.numVertices; ++k, ++pVtx) + { + frame->vertices.append(ModelFrame::Vertex()); + ModelFrame::Vertex &vtx = frame->vertices.last(); + + vtx.pos = Vector3f(pVtx->vertex[0], pVtx->vertex[2], pVtx->vertex[1]) + * scale + translation; + vtx.pos.y *= aspectScale; // Aspect undo. + + vtx.norm = unpackVector(USHORT(pVtx->normal)); + + if(!k) + { + frame->min = frame->max = vtx.pos; + } + else + { + frame->min = vtx.pos.min(frame->min); + frame->max = vtx.pos.max(frame->max); + } + } + + mdl->addFrame(frame); + } + M_Free(frameData); + + file.seek(info.offsetLODs, SeekSet); + dmd_levelOfDetail_t *lodInfo = new dmd_levelOfDetail_t[info.numLODs]; + + for(int i = 0; i < info.numLODs; ++i) + { + file.read((uint8_t *)&lodInfo[i], sizeof(dmd_levelOfDetail_t)); + + lodInfo[i].numTriangles = LONG(lodInfo[i].numTriangles); + lodInfo[i].numGlCommands = LONG(lodInfo[i].numGlCommands); + lodInfo[i].offsetTriangles = LONG(lodInfo[i].offsetTriangles); + lodInfo[i].offsetGlCommands = LONG(lodInfo[i].offsetGlCommands); + } + + dmd_triangle_t **triangles = new dmd_triangle_t*[info.numLODs]; + + for(int i = 0; i < info.numLODs; ++i) + { + mdl->_lods.append(new ModelDetailLevel(*mdl, i)); + ModelDetailLevel &lod = *mdl->_lods.last(); + + triangles[i] = (dmd_triangle_t *) allocAndLoad(file, lodInfo[i].offsetTriangles, + sizeof(dmd_triangle_t) * lodInfo[i].numTriangles); + + uint8_t *commandData = (uint8_t *) allocAndLoad(file, lodInfo[i].offsetGlCommands, + 4 * lodInfo[i].numGlCommands); + for(uint8_t const *pos = commandData; *pos;) + { + int count = LONG( *(int *) pos ); pos += 4; + + lod.primitives.append(Model::Primitive()); + Model::Primitive &prim = lod.primitives.last(); + + // The type of primitive depends on the sign of the element count. + prim.triFan = (count < 0); + + if(count < 0) + { + count = -count; + } + + while(count--) + { + md2_commandElement_t const *v = (md2_commandElement_t *) pos; pos += 12; + + prim.elements.append(Model::Primitive::Element()); + Model::Primitive::Element &elem = prim.elements.last(); + + elem.texCoord = Vector2f(FLOAT(v->s), FLOAT(v->t)); + elem.index = LONG(v->index); + } + } + M_Free(commandData); + } + + // Determine vertex usage at each LOD level. + mdl->_vertexUsage.resize(info.numVertices * info.numLODs); + mdl->_vertexUsage.fill(false); + + for(int i = 0; i < info.numLODs; ++i) + for(int k = 0; k < lodInfo[i].numTriangles; ++k) + for(int m = 0; m < 3; ++m) + { + int vertexIndex = SHORT(triangles[i][k].vertexIndices[m]); + mdl->_vertexUsage.setBit(vertexIndex * info.numLODs + i); + } + + delete [] lodInfo; + for(int i = 0; i < info.numLODs; ++i) + { + M_Free(triangles[i]); + } + delete [] triangles; + + return mdl; } - delete [] lodInfo; - for(int i = 0; i < info.numLODs; ++i) + static Model *interpretDmd(de::FileHandle &hndl, float aspectScale) { - M_Free(triangles[i]); + if(recogniseDmd(hndl)) + { + LOG_VERBOSE("Interpreted \"" + NativePath(hndl.file().composePath()).pretty() + "\" as a DMD model."); + return loadDmd(hndl, aspectScale); + } + return 0; + } + + static Model *interpretMd2(de::FileHandle &hndl, float aspectScale) + { + if(recogniseMd2(hndl)) + { + LOG_VERBOSE("Interpreted \"" + NativePath(hndl.file().composePath()).pretty() + "\" as a MD2 model."); + return loadMd2(hndl, aspectScale); + } + return 0; } - delete [] triangles; - return mdl; +#if 0 + /** + * Calculate vertex normals. Only with -renorm. + */ + static void rebuildNormals(model_t &mdl) + { + // Renormalizing? + if(!CommandLine_Check("-renorm")) return; + + int const tris = mdl.lodInfo[0].numTriangles; + int const verts = mdl.info.numVertices; + + vector_t* normals = (vector_t*) Z_Malloc(sizeof(vector_t) * tris, PU_APPSTATIC, 0); + vector_t norm; + int cnt; + + // Calculate the normal for each vertex. + for(int i = 0; i < mdl.info.numFrames; ++i) + { + model_vertex_t* list = mdl.frames[i].vertices; + + for(int k = 0; k < tris; ++k) + { + dmd_triangle_t const& tri = mdl.lods[0].triangles[k]; + + // First calculate surface normals, combine them to vertex ones. + V3f_PointCrossProduct(normals[k].pos, + list[tri.vertexIndices[0]].vertex, + list[tri.vertexIndices[2]].vertex, + list[tri.vertexIndices[1]].vertex); + V3f_Normalize(normals[k].pos); + } + + for(int k = 0; k < verts; ++k) + { + memset(&norm, 0, sizeof(norm)); + cnt = 0; + + for(int j = 0; j < tris; ++j) + { + dmd_triangle_t const& tri = mdl.lods[0].triangles[j]; + + for(int n = 0; n < 3; ++n) + { + if(tri.vertexIndices[n] == k) + { + cnt++; + for(int n = 0; n < 3; ++n) + { + norm.pos[n] += normals[j].pos[n]; + } + break; + } + } + } + + if(!cnt) continue; // Impossible... + + // Calculate the average. + for(int n = 0; n < 3; ++n) + { + norm.pos[n] /= cnt; + } + + // Normalize it. + V3f_Normalize(norm.pos); + memcpy(list[k].normal, norm.pos, sizeof(norm.pos)); + } + } + + Z_Free(normals); + } +#endif +}; + +Model::Model(Flags flags) + : _numVertices(0) + , d(new Instance(this)) +{ + setFlags(flags, de::ReplaceFlags); +} + +bool Model::recognise(de::FileHandle &hndl) //static +{ + if(recogniseDmd(hndl)) return true; + if(recogniseMd2(hndl)) return true; + return false; } -Model *Model::loadFromFile(de::FileHandle &hndl, float scale) //static +struct ModelFileType +{ + String name; ///< Symbolic name of the resource type. + String ext; ///< Known file extension. + + Model *(*interpretFunc)(de::FileHandle &hndl, float aspectScale); +}; + +Model *Model::loadFromFile(de::FileHandle &hndl, float aspectScale) //static { - // Firstly try the interpreter for the guessed resource types. - ModelFileType const *rtypeGuess = guessModelFileTypeFromFileName(hndl.file().composePath()); - if(rtypeGuess) + // Recognised file types. + static ModelFileType modelTypes[] = { + { "DMD", ".dmd", Instance::interpretDmd }, + { "MD2", ".md2", Instance::interpretMd2 }, + { "", "", 0 } // Terminate. + }; + + // Firstly, attempt to guess the resource type from the file extension. + ModelFileType *rtypeGuess = 0; + // An extension is required for this. + String filePath = hndl.file().composePath(); + String ext = filePath.fileNameExtension(); + if(!ext.isEmpty()) { - if(Model *mdl = rtypeGuess->interpretFunc(hndl, scale)) + for(int i = 0; !modelTypes[i].name.isEmpty(); ++i) { - return mdl; + ModelFileType &type = modelTypes[i]; + if(!type.ext.compareWithoutCase(ext)) + { + rtypeGuess = &type; + if(Model *mdl = type.interpretFunc(hndl, aspectScale)) + { + return mdl; + } + break; + } } } @@ -698,7 +692,7 @@ Model *Model::loadFromFile(de::FileHandle &hndl, float scale) //static // Already tried this? if(&modelType == rtypeGuess) continue; - if(Model *mdl = modelType.interpretFunc(hndl, scale)) + if(Model *mdl = modelType.interpretFunc(hndl, aspectScale)) { return mdl; }