From 98f69b002d16653494d5ed7479fd58005f23d64d Mon Sep 17 00:00:00 2001 From: danij Date: Sun, 1 Dec 2013 13:13:55 +0000 Subject: [PATCH] Resources: Cleanup --- doomsday/client/include/gl/gl_model.h | 2 +- doomsday/client/src/gl/gl_model.cpp | 5 +- doomsday/client/src/render/rend_model.cpp | 2 +- doomsday/client/src/resource/models.cpp | 2373 ++++++++++----------- 4 files changed, 1190 insertions(+), 1192 deletions(-) diff --git a/doomsday/client/include/gl/gl_model.h b/doomsday/client/include/gl/gl_model.h index 903637de7e..cd163fd2b3 100644 --- a/doomsday/client/include/gl/gl_model.h +++ b/doomsday/client/include/gl/gl_model.h @@ -252,7 +252,7 @@ class Model DetailLevels const &lods() const; /// @todo Remove me. - int numVertices() const; + int vertexCount() const; private: DENG2_PRIVATE(d) diff --git a/doomsday/client/src/gl/gl_model.cpp b/doomsday/client/src/gl/gl_model.cpp index e7a2ffb0a0..347cf66d7d 100644 --- a/doomsday/client/src/gl/gl_model.cpp +++ b/doomsday/client/src/gl/gl_model.cpp @@ -25,7 +25,6 @@ #include "Texture" #include "TextureManifest" #include -#include #include using namespace de; @@ -181,7 +180,7 @@ Model::DetailLevel &Model::lod(int level) const { return *_lods.at(level); } - throw MissingDetailLevelError("Model::lod", "Invalid detail leve " + String::number(level) + " Valid range is " + Rangei(0, _lods.count()).asText()); + throw MissingDetailLevelError("Model::lod", "Invalid detail level " + String::number(level) + " Valid range is " + Rangei(0, _lods.count()).asText()); } Model::DetailLevels const &Model::lods() const @@ -189,7 +188,7 @@ Model::DetailLevels const &Model::lods() const return _lods; } -int Model::numVertices() const +int Model::vertexCount() const { return _numVertices; } diff --git a/doomsday/client/src/render/rend_model.cpp b/doomsday/client/src/render/rend_model.cpp index 66a90500d7..025af99572 100644 --- a/doomsday/client/src/render/rend_model.cpp +++ b/doomsday/client/src/render/rend_model.cpp @@ -881,7 +881,7 @@ static void Mod_RenderSubModel(uint number, rendmodelparams_t const *parm) } // Determine the total number of vertices we have. - numVerts = mdl->numVertices(); + numVerts = mdl->vertexCount(); // Ensure our vertex render buffers can accommodate this. if(!Mod_ExpandVertexBuffer(numVerts)) diff --git a/doomsday/client/src/resource/models.cpp b/doomsday/client/src/resource/models.cpp index 829ebbfa1c..529ec9d567 100644 --- a/doomsday/client/src/resource/models.cpp +++ b/doomsday/client/src/resource/models.cpp @@ -51,138 +51,27 @@ using namespace de; -// -#define MD2_MAGIC 0x32504449 - -#pragma pack(1) -struct md2_header_t -{ - int magic; - int version; - int skinWidth; - int skinHeight; - int frameSize; - int numSkins; - int numVertices; - int numTexCoords; - int numTriangles; - int numGlCommands; - int numFrames; - int offsetSkins; - int offsetTexCoords; - int offsetTriangles; - int offsetFrames; - int offsetGlCommands; - int offsetEnd; -}; -#pragma pack() - -// -#define DMD_MAGIC 0x4D444D44 ///< "DMDM" = Doomsday/Detailed MoDel Magic - -#pragma pack(1) -struct dmd_header_t -{ - int magic; - int version; - int flags; -}; -#pragma pack() - ModelDefs modefs; byte useModels = true; float rModelAspectMod = 1 / 1.2f; //.833334f; -static StringPool *modelRepository; // Owns model_t instances. +static StringPool *modelRepository; // Owns Model instances. static std::vector stateModefs; // Index to the modefs array. -#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 *loadModel(String path); +static bool recogniseDmd(de::FileHandle &file); +static bool recogniseMd2(de::FileHandle &file); +static void loadDmd(de::FileHandle &file, Model &mdl); +static void loadMd2(de::FileHandle &file, Model &mdl); static int indexOfModelDef(ModelDef const *mf) { return mf - &modefs[0]; } -static ModelDef *modelDefForState(int stateIndex) +static ModelDef *modelDefForState(int stateIndex, int select = 0) { DENG2_ASSERT(stateIndex >= 0); DENG2_ASSERT(stateIndex < int(stateModefs.size())); @@ -192,455 +81,314 @@ static ModelDef *modelDefForState(int stateIndex) DENG2_ASSERT(stateModefs[stateIndex] >= 0); DENG2_ASSERT(stateModefs[stateIndex] < int(modefs.size())); - return &modefs[stateModefs[stateIndex]]; -} + ModelDef *def = &modefs[stateModefs[stateIndex]]; + if(select) + { + // Choose the correct selector, or selector zero if the given one not available. + int const mosel = select & DDMOBJ_SELECTOR_MASK; + for(ModelDef *it = def; it; it = it->selectNext) + { + if(it->select == mosel) + { + return it; + } + } + } -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; + return def; } -static bool readMd2Header(de::FileHandle &file, md2_header_t &hdr) +static Model *modelForId(modelid_t modelId, bool canCreate = false) { - size_t readBytes = file.read((uint8_t *)&hdr, sizeof(md2_header_t)); - if(readBytes < sizeof(md2_header_t)) return false; - - hdr.magic = littleEndianByteOrder.toNative(hdr.magic); - hdr.version = littleEndianByteOrder.toNative(hdr.version); - hdr.skinWidth = littleEndianByteOrder.toNative(hdr.skinWidth); - hdr.skinHeight = littleEndianByteOrder.toNative(hdr.skinHeight); - hdr.frameSize = littleEndianByteOrder.toNative(hdr.frameSize); - hdr.numSkins = littleEndianByteOrder.toNative(hdr.numSkins); - hdr.numVertices = littleEndianByteOrder.toNative(hdr.numVertices); - hdr.numTexCoords = littleEndianByteOrder.toNative(hdr.numTexCoords); - hdr.numTriangles = littleEndianByteOrder.toNative(hdr.numTriangles); - hdr.numGlCommands = littleEndianByteOrder.toNative(hdr.numGlCommands); - hdr.numFrames = littleEndianByteOrder.toNative(hdr.numFrames); - hdr.offsetSkins = littleEndianByteOrder.toNative(hdr.offsetSkins); - hdr.offsetTexCoords = littleEndianByteOrder.toNative(hdr.offsetTexCoords); - hdr.offsetTriangles = littleEndianByteOrder.toNative(hdr.offsetTriangles); - hdr.offsetFrames = littleEndianByteOrder.toNative(hdr.offsetFrames); - hdr.offsetGlCommands = littleEndianByteOrder.toNative(hdr.offsetGlCommands); - hdr.offsetEnd = littleEndianByteOrder.toNative(hdr.offsetEnd); - return true; + DENG2_ASSERT(modelRepository); + Model *mdl = reinterpret_cast(modelRepository->userPointer(modelId)); + if(!mdl && canCreate) + { + // Allocate a new model_t. + mdl = new Model(modelId); + modelRepository->setUserPointer(modelId, mdl); + } + return mdl; } -/// @todo We only really need to read the magic bytes and the version here. -static bool recogniseMd2(de::FileHandle &file) +static inline String const &findModelPath(modelid_t id) { - md2_header_t hdr; - size_t initPos = file.tell(); - // Seek to the start of the header. - file.seek(0, SeekSet); - bool result = (readMd2Header(file, hdr) && LONG(hdr.magic) == MD2_MAGIC); - // Return the stream to its original position. - file.seek(initPos, SeekSet); - return result; + return modelRepository->stringRef(id); } -#pragma pack(1) -struct md2_triangleVertex_t +Model *Models_ToModel(modelid_t id) { - byte vertex[3]; - byte normalIndex; -}; + return modelForId(id); +} -struct md2_packedFrame_t +modeldef_t *Models_Definition(char const *id) { - float scale[3]; - float translate[3]; - char name[16]; - md2_triangleVertex_t vertices[1]; -}; + if(!id || !id[0]) return 0; -struct md2_commandElement_t { - float s, t; - int index; -}; -#pragma pack() + for(uint i = 0; i < modefs.size(); ++i) + { + if(!strcmp(modefs[i].id, id)) + { + return &modefs[i]; + } + } + return 0; +} -/** - * Note vertex Z/Y are swapped here (ordered XYZ in the serialized data). - */ -static void loadMd2(de::FileHandle &file, Model &mdl) +float Models_ModelForMobj(mobj_t const *mo, modeldef_t **modef, modeldef_t **nextmodef) { - // Read the header. - md2_header_t hdr; - bool readHeaderOk = readMd2Header(file, hdr); - DENG2_ASSERT(readHeaderOk); - DENG2_UNUSED(readHeaderOk); // should this be checked? + // On the client it is possible that we don't know the mobj's state. + if(!mo->state) return -1; - mdl._numVertices = hdr.numVertices; + state_t &st = *mo->state; - mdl.clearAllFrames(); + // By default there are no models. + *nextmodef = NULL; + *modef = modelDefForState(&st - states, mo->selector); + if(!*modef) return -1; // No model available. - // 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) + float interp = -1; + + // World time animation? + bool worldTime = false; + if((*modef)->flags & MFF_WORLD_TIME_ANIM) { - 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; + float duration = (*modef)->interRange[0]; + float offset = (*modef)->interRange[1]; - ModelFrame *frame = new ModelFrame(mdl, frameName); - frame->vertices.reserve(hdr.numVertices); + // Validate/modify the values. + if(duration == 0) duration = 1; - // Scale and translate each vertex. - md2_triangleVertex_t const *pVtx = pfr->vertices; - for(int k = 0; k < hdr.numVertices; ++k, pVtx++) + if(offset == -1) { - 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 *= rModelAspectMod; // Aspect undoing. + offset = M_CycleIntoRange(MOBJ_TO_ID(mo), duration); + } - vtx.norm = Vector3f(avertexnormals[pVtx->normalIndex]); + interp = M_CycleIntoRange(App_World().time() / duration + offset, 1); + worldTime = true; + } + else + { + // Calculate the currently applicable intermark. + interp = 1.0f - (mo->tics - frameTimePos) / float( st.tics ); + } - if(!k) - { - frame->min = frame->max = vtx.pos; - } - else - { - frame->min = vtx.pos.min(frame->min); - frame->max = vtx.pos.max(frame->max); - } - } +/*#if _DEBUG + if(mo->dPlayer) + { + qDebug() << "itp:" << interp << " mot:" << mo->tics << " stt:" << st.tics; + } +#endif*/ - mdl.addFrame(frame); // takes owernship + // First find the modef for the interpoint. Intermark is 'stronger' than interrange. + + // Scan interlinks. + while((*modef)->interNext && (*modef)->interNext->interMark <= interp) + { + *modef = (*modef)->interNext; } - M_Free(frameData); - mdl._lods.append(new ModelDetailLevel); - ModelDetailLevel &lod0 = *mdl._lods.last(); + if(!worldTime) + { + // Scale to the modeldef's interpolation range. + interp = (*modef)->interRange[0] + interp * ((*modef)->interRange[1] - + (*modef)->interRange[0]); + } - uint8_t *commandData = (uint8_t *) allocAndLoad(file, hdr.offsetGlCommands, 4 * hdr.numGlCommands); - for(uint8_t const *pos = commandData; *pos;) + // What would be the next model? Check interlinks first. + if((*modef)->interNext) { - int count = LONG( *(int *) pos ); pos += 4; + *nextmodef = (*modef)->interNext; + } + else if(worldTime) + { + *nextmodef = modelDefForState(&st - states, mo->selector); + } + else if(st.nextState > 0) // Check next state. + { + // Find the appropriate state based on interrange. + state_t *it = states + st.nextState; + bool foundNext = false; + if((*modef)->interRange[1] < 1) + { + // Current modef doesn't interpolate to the end, find the proper destination + // modef (it isn't just the next one). Scan the states that follow (and + // interlinks of each). + bool stopScan = false; + int max = 20; // Let's not be here forever... + while(!stopScan) + { + if(!((!modelDefForState(it - states) || + modelDefForState(it - states, mo->selector)->interRange[0] > 0) && + it->nextState > 0)) + { + stopScan = true; + } + else + { + // Scan interlinks, then go to the next state. + modeldef_t *mdit; + if((mdit = modelDefForState(it - states, mo->selector)) && mdit->interNext) + { + forever + { + mdit = mdit->interNext; + if(mdit) + { + if(mdit->interRange[0] <= 0) // A new beginning? + { + *nextmodef = mdit; + foundNext = true; + } + } - lod0.primitives.append(ModelDetailLevel::Primitive()); - ModelDetailLevel::Primitive &prim = lod0.primitives.last(); + if(!mdit || foundNext) + { + break; + } + } + } - // The type of primitive depends on the sign. - prim.triFan = (count < 0); + if(foundNext) + { + stopScan = true; + } + else + { + it = states + it->nextState; + } + } - if(count < 0) - { - count = -count; + if(max-- <= 0) + stopScan = true; + } + // @todo What about max == -1? What should 'it' be then? } - while(count--) + if(!foundNext) { - md2_commandElement_t const *v = (md2_commandElement_t *) pos; pos += 12; - - prim.elements.append(ModelDetailLevel::Primitive::Element()); - ModelDetailLevel::Primitive::Element &elem = prim.elements.last(); - elem.texCoord = Vector2f(FLOAT(v->s), FLOAT(v->t)); - elem.index = LONG(v->index); + *nextmodef = modelDefForState(it - states, mo->selector); } } - M_Free(commandData); - - mdl.clearAllSkins(); - // Load skins. (Note: numSkins may be zero.) - file.seek(hdr.offsetSkins, SeekSet); - for(int i = 0; i < hdr.numSkins; ++i) + // Is this group disabled? + if(useModels >= 2 && (*modef)->group & useModels) { - char name[64]; file.read((uint8_t *)name, 64); - mdl.newSkin(name); + *modef = *nextmodef = 0; + return -1; } + + return interp; } -static bool readHeaderDmd(de::FileHandle &file, dmd_header_t &hdr) +/** + * Scales the given model so that it'll be 'destHeight' units tall. Measurements + * are based on submodel zero. Scale is applied uniformly. + */ +static void scaleModel(modeldef_t &mf, float destHeight, float offset) { - size_t readBytes = file.read((uint8_t *)&hdr, sizeof(dmd_header_t)); - if(readBytes < sizeof(dmd_header_t)) return false; + if(!mf.subCount()) return; - hdr.magic = littleEndianByteOrder.toNative(hdr.magic); - hdr.version = littleEndianByteOrder.toNative(hdr.version); - hdr.flags = littleEndianByteOrder.toNative(hdr.flags); - return true; + submodeldef_t &smf = mf.subModelDef(0); + + // No model to scale? + if(!smf.modelId) return; + + // Find the top and bottom heights. + float top, bottom; + float height = Models_ToModel(smf.modelId)->frame(smf.frame).horizontalRange(&top, &bottom); + if(!height) height = 1; + + float scale = destHeight / height; + + mf.scale = Vector3f(scale, scale, scale); + mf.offset.y = -bottom * scale + offset; } -static bool recogniseDmd(de::FileHandle &file) +static void scaleModelToSprite(modeldef_t &mf, Sprite *sprite) { - dmd_header_t hdr; - size_t initPos = file.tell(); - // Seek to the start of the header. - file.seek(0, SeekSet); - bool result = (readHeaderDmd(file, hdr) && LONG(hdr.magic) == DMD_MAGIC); - // Return the stream to its original position. - file.seek(initPos, SeekSet); - return result; -} + if(!sprite) return; + if(!sprite->hasViewAngle(0)) return; -// DMD chunk types. -enum { - DMC_END, /// Must be the last chunk. - DMC_INFO /// Required; will be expected to exist. -}; + MaterialSnapshot const &ms = sprite->viewAngle(0).material->prepare(Rend_SpriteMaterialSpec()); + Texture const &tex = ms.texture(MTU_PRIMARY).generalCase(); + int off = de::max(0, -tex.origin().y - ms.height()); + scaleModel(mf, ms.height(), off); +} -#pragma pack(1) -typedef struct { - int type; - int length; /// Next chunk follows... -} dmd_chunk_t; +static float calcModelVisualRadius(modeldef_t *def) +{ + if(!def || !def->subModelId(0)) return 0; -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; + // Use the first frame bounds. + Vector3f min, max; + float maxRadius = 0; + for(uint i = 0; i < def->subCount(); ++i) + { + if(!def->subModelId(i)) break; -typedef struct { - int numTriangles; - int numGlCommands; - int offsetTriangles; - int offsetGlCommands; -} dmd_levelOfDetail_t; + SubmodelDef &sub = def->subModelDef(i); -typedef struct { - byte vertex[3]; - unsigned short normal; /// Yaw and pitch. -} dmd_packedVertex_t; + Models_ToModel(sub.modelId)->frame(sub.frame).bounds(min, max); -typedef struct { - float scale[3]; - float translate[3]; - char name[16]; - dmd_packedVertex_t vertices[1]; // dmd_info_t::numVertices size -} dmd_packedFrame_t; + // Half the distance from bottom left to top right. + float radius = (def->scale.x * (max.x - min.x) + + def->scale.z * (max.z - min.z)) / 3.5f; + if(radius > maxRadius) + { + maxRadius = radius; + } + } -typedef struct { - short vertexIndices[3]; - short textureIndices[3]; -} dmd_triangle_t; -#pragma pack() + return maxRadius; +} /** - * Packed: pppppppy yyyyyyyy. Yaw is on the XY plane. + * Create a new modeldef or find an existing one. This is for ID'd models. */ -static Vector3f unpackVector(ushort packed) +static modeldef_t *getModelDefWithId(char const *id) { - 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)); -} + // ID defined? + if(!id || !id[0]) return 0; + + // First try to find an existing modef. + modeldef_t *md = Models_Definition(id); + if(md) return md; + + // Get a new entry. + modefs.push_back(ModelDef(id)); + return &modefs.back(); +} /** - * Note vertex Z/Y are swapped here (ordered XYZ in the serialized data). + * Create a new modeldef or find an existing one. There can be only one model + * definition associated with a state/intermark pair. */ -static void loadDmd(de::FileHandle &file, Model &mdl) +static modeldef_t *getModelDef(int state, float interMark, int select) { - // 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)); - } - mdl._numVertices = info.numVertices; - - mdl.clearAllSkins(); - - // 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); - } - - mdl.clearAllFrames(); - - 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 *= rModelAspectMod; // 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]; + // Is this a valid state? + if(state < 0 || state >= countStates.num) return 0; - for(int i = 0; i < info.numLODs; ++i) + // First try to find an existing modef. + for(uint i = 0; i < modefs.size(); ++i) { - mdl._lods.append(new ModelDetailLevel); - 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;) + if(modefs[i].state == &states[state] && + modefs[i].interMark == interMark && modefs[i].select == select) { - int count = LONG( *(int *) pos ); pos += 4; - - lod.primitives.append(ModelDetailLevel::Primitive()); - ModelDetailLevel::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(ModelDetailLevel::Primitive::Element()); - ModelDetailLevel::Primitive::Element &elem = prim.elements.last(); - - elem.texCoord = Vector2f(FLOAT(v->s), FLOAT(v->t)); - elem.index = LONG(v->index); - } + // Models are loaded in reverse order; this one already has + // a model. + return NULL; } - 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 c = 0; c < 3; ++c) - { - int vertexIndex = SHORT(triangles[i][k].vertexIndices[c]); - mdl._vertexUsage.setBit(vertexIndex * info.numLODs + i); - } - - delete [] lodInfo; - for(int i = 0; i < info.numLODs; ++i) - { - M_Free(triangles[i]); - } - delete [] triangles; -} - -static Model *modelForId(modelid_t modelId, bool canCreate = false) -{ - DENG2_ASSERT(modelRepository); - Model *mdl = reinterpret_cast(modelRepository->userPointer(modelId)); - if(!mdl && canCreate) - { - // Allocate a new model_t. - mdl = new Model(modelId); - modelRepository->setUserPointer(modelId, mdl); } - return mdl; -} -Model *Models_ToModel(modelid_t id) -{ - return modelForId(id); -} + modefs.push_back(ModelDef()); -static inline String const &findModelPath(modelid_t id) -{ - return modelRepository->stringRef(id); + // Set initial data. + modeldef_t *md = &modefs.back(); + md->state = &states[state]; + md->interMark = interMark; + md->select = select; + return md; } static String findSkinPath(Path const &skinPath, Path const &modelFilePath) @@ -653,8 +401,8 @@ static String findSkinPath(Path const &skinPath, Path const &modelFilePath) // The "first choice" directory is that in which the model file resides. try { - de::Uri searchPath("Models", modelFilePath.toString().fileNamePath() / skinPath.fileName()); - return App_FileSystem().findPath(searchPath, RLF_DEFAULT, App_ResourceClass(RC_GRAPHIC)); + return App_FileSystem().findPath(de::Uri("Models", modelFilePath.toString().fileNamePath() / skinPath.fileName()), + RLF_DEFAULT, App_ResourceClass(RC_GRAPHIC)); } catch(FS1::NotFoundError const &) {} // Ignore this error. @@ -687,911 +435,1162 @@ static short defineSkinAndAddToModelIndex(Model &mdl, Path const &skinPath) return -1; } -static void defineAllSkins(Model &mdl) +/** + * Creates a modeldef based on the given DED info. A pretty straightforward + * operation. No interlinks are set yet. Autoscaling is done and the scale + * factors set appropriately. After this has been called for all available + * Model DEDs, each State that has a model will have a pointer to the one + * with the smallest intermark (start of a chain). + */ +static void setupModel(ded_model_t &def) { - String const &modelFilePath = findModelPath(mdl.modelId()); - - int numFoundSkins = 0; - for(int i = 0; i < mdl.skinCount(); ++i) - { - ModelSkin &skin = mdl.skin(i); - - if(skin.name.isEmpty()) - continue; + LOG_AS("setupModel"); - try - { - de::Uri foundResourceUri(Path(findSkinPath(skin.name, modelFilePath))); + int const modelScopeFlags = def.flags | defs.modelFlags; + int const statenum = Def_GetStateNum(def.state); - skin.texture = App_ResourceSystem().defineTexture("ModelSkins", foundResourceUri); + // Is this an ID'd model? + modeldef_t *modef = getModelDefWithId(def.id); + if(!modef) + { + // No, normal State-model. + if(statenum < 0) return; - // We have found one more skin for this model. - numFoundSkins += 1; - } - catch(FS1::NotFoundError const&) - { - LOG_WARNING("Failed to locate \"%s\" (#%i) for model \"%s\", ignoring.") - << skin.name << i << NativePath(modelFilePath).pretty(); - } + modef = getModelDef(statenum + def.off, def.interMark, def.selector); + if(!modef) return; // Can't get a modef, quit! } - if(!numFoundSkins) - { - // Lastly try a skin named similarly to the model in the same directory. - de::Uri searchPath(modelFilePath.fileNamePath() / modelFilePath.fileNameWithoutExtension(), RC_GRAPHIC); + // Init modef info (state & intermark already set). + modef->def = &def; + modef->group = def.group; + modef->flags = modelScopeFlags; + modef->offset = def.offset; + modef->offset.y += defs.modelOffset; // Common Y axis offset. + modef->scale = def.scale; + modef->scale.y *= defs.modelScale; // Common Y axis scaling. + modef->resize = def.resize; + modef->skinTics = de::max(def.skinTics, 1); + for(int i = 0; i < 2; ++i) + { + modef->interRange[i] = def.interRange[i]; + } + + // Submodels. + modef->clearSubs(); + for(uint i = 0; i < def.subCount(); ++i) + { + ded_submodel_t const *subdef = &def.sub(i); + submodeldef_t *sub = modef->addSub(); + + sub->modelId = 0; + + if(!subdef->filename) continue; + de::Uri const &searchPath = reinterpret_cast(*subdef->filename); + if(searchPath.isEmpty()) continue; try { String foundPath = App_FileSystem().findPath(searchPath, RLF_DEFAULT, - App_ResourceClass(RC_GRAPHIC)); + App_ResourceClass(RC_MODEL)); // Ensure the found path is absolute. foundPath = App_BasePath() / foundPath; - defineSkinAndAddToModelIndex(mdl, foundPath); - // We have found one more skin for this model. - numFoundSkins = 1; + Model *mdl = loadModel(foundPath); + if(!mdl) continue; - LOG_INFO("Assigned fallback skin \"%s\" to index #0 for model \"%s\".") - << NativePath(foundPath).pretty() - << NativePath(modelFilePath).pretty(); - } - catch(FS1::NotFoundError const&) - {} // Ignore this error. - } + sub->modelId = mdl->modelId(); + sub->frame = mdl->toFrameNumber(subdef->frame); + if(sub->frame < 0) sub->frame = 0; + sub->frameRange = de::max(1, subdef->frameRange); // Frame range must always be greater than zero. - if(!numFoundSkins) - { - LOG_WARNING("Failed to locate a skin for model \"%s\". This model will be rendered without a skin.") - << NativePath(modelFilePath).pretty(); - } -} + sub->alpha = byte(255 - subdef->alpha * 255); + sub->blendMode = subdef->blendMode; -static Model *interpretDmd(de::FileHandle &hndl, String path, modelid_t modelId) -{ - if(recogniseDmd(hndl)) - { - LOG_VERBOSE("Interpreted \"" + NativePath(path).pretty() + "\" as a DMD model."); - Model *mdl = modelForId(modelId, true/*create*/); - loadDmd(hndl, *mdl); - return mdl; - } - return 0; -} + // Submodel-specific flags cancel out model-scope flags! + sub->setFlags(modelScopeFlags ^ subdef->flags); -static Model *interpretMd2(de::FileHandle &hndl, String path, modelid_t modelId) -{ - if(recogniseMd2(hndl)) - { - LOG_VERBOSE("Interpreted \"" + NativePath(path).pretty() + "\" as a MD2 model."); - Model *mdl = modelForId(modelId, true/*create*/); - loadMd2(hndl, *mdl); - return mdl; - } - return 0; -} + // Flags may override alpha and/or blendmode. + if(sub->testFlag(MFF_BRIGHTSHADOW)) + { + sub->alpha = byte(256 * .80f); + sub->blendMode = BM_ADD; + } + else if(sub->testFlag(MFF_BRIGHTSHADOW2)) + { + sub->blendMode = BM_ADD; + } + else if(sub->testFlag(MFF_DARKSHADOW)) + { + sub->blendMode = BM_DARK; + } + else if(sub->testFlag(MFF_SHADOW2)) + { + sub->alpha = byte(256 * .2f); + } + else if(sub->testFlag(MFF_SHADOW1)) + { + sub->alpha = byte(256 * .62f); + } -struct ModelFileType -{ - /// Symbolic name of the resource type. - String const name; + // Extra blendmodes: + if(sub->testFlag(MFF_REVERSE_SUBTRACT)) + { + sub->blendMode = BM_REVERSE_SUBTRACT; + } + else if(sub->testFlag(MFF_SUBTRACT)) + { + sub->blendMode = BM_SUBTRACT; + } - /// Known file extension. - String const ext; + if(subdef->skinFilename && !Uri_IsEmpty(subdef->skinFilename)) + { + // A specific file name has been given for the skin. + String const &skinFilePath = reinterpret_cast(*subdef->skinFilename).path(); + String const &modelFilePath = findModelPath(sub->modelId); + try + { + Path foundResourcePath(findSkinPath(skinFilePath, modelFilePath)); - Model *(*interpretFunc)(de::FileHandle &hndl, String path, modelid_t modelId); -}; + sub->skin = defineSkinAndAddToModelIndex(*mdl, foundResourcePath); + } + catch(FS1::NotFoundError const&) + { + LOG_WARNING("Failed to locate skin \"%s\" for model \"%s\", ignoring.") + << reinterpret_cast(*subdef->skinFilename) << NativePath(modelFilePath).pretty(); + } + } + else + { + sub->skin = subdef->skin; + } -// Model resource types. -static ModelFileType const modelTypes[] = { - { "DMD", ".dmd", interpretDmd }, - { "MD2", ".md2", interpretMd2 }, - { "", "", 0 } // Terminate. -}; + // Skin range must always be greater than zero. + sub->skinRange = de::max(subdef->skinRange, 1); -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)) + // Offset within the model. + sub->offset = subdef->offset; + + if(subdef->shinySkin && !Uri_IsEmpty(subdef->shinySkin)) { - return &type; + String const &skinFilePath = reinterpret_cast(*subdef->shinySkin).path(); + String const &modelFilePath = findModelPath(sub->modelId); + try + { + de::Uri foundResourceUri(Path(findSkinPath(skinFilePath, modelFilePath))); + + sub->shinySkin = App_ResourceSystem().defineTexture("ModelReflectionSkins", foundResourceUri); + } + catch(FS1::NotFoundError const &) + { + LOG_WARNING("Failed to locate skin \"%s\" for model \"%s\", ignoring.") + << skinFilePath << NativePath(modelFilePath).pretty(); + } + } + else + { + sub->shinySkin = 0; } + + // Should we allow texture compression with this model? + if(sub->testFlag(MFF_NO_TEXCOMP)) + { + // All skins of this model will no longer use compression. + mdl->setFlags(Model::NoTextureCompression); + } + } + catch(FS1::NotFoundError const &) + { + LOG_WARNING("Failed to locate \"%s\", ignoring.") << searchPath; } } - return 0; // Unknown. -} -static Model *interpretModel(de::FileHandle &hndl, String path, modelid_t modelId) -{ - // Firstly try the interpreter for the guessed resource types. - ModelFileType const *rtypeGuess = guessModelFileTypeFromFileName(path); - if(rtypeGuess) + // Do scaling, if necessary. + if(modef->resize) { - if(Model *mdl = rtypeGuess->interpretFunc(hndl, path, modelId)) - return mdl; + scaleModel(*modef, modef->resize, modef->offset.y); } - - // Not yet interpreted - try each recognisable format in order. - // Try each recognisable format instead. - for(int i = 0; !modelTypes[i].name.isEmpty(); ++i) + else if(modef->state && modef->testSubFlag(0, MFF_AUTOSCALE)) { - ModelFileType const &modelType = modelTypes[i]; + spritenum_t sprNum = Def_GetSpriteNum(def.sprite.id); + int sprFrame = def.spriteFrame; - // Already tried this? - if(&modelType == rtypeGuess) continue; + if(sprNum < 0) + { + // No sprite ID given. + sprNum = modef->state->sprite; + sprFrame = modef->state->frame; + } - if(Model *mdl = modelType.interpretFunc(hndl, path, modelId)) - return mdl; + if(Sprite *sprite = App_ResourceSystem().spritePtr(sprNum, sprFrame)) + { + scaleModelToSprite(*modef, sprite); + } } - return 0; -} - -/** - * Finds the existing model or loads in a new one. - */ -static Model *loadModel(String path) -{ - // Have we already loaded this? - modelid_t modelId = modelRepository->intern(path); - Model *mdl = Models_ToModel(modelId); - if(mdl) return mdl; // Yes. - - try + if(modef->state) { - // Attempt to interpret and load this model file. - de::FileHandle &hndl = App_FileSystem().openFile(path, "rb"); - - mdl = interpretModel(hndl, path, modelId); - - // We're done with the file. - App_FileSystem().releaseFile(hndl.file()); - delete &hndl; + int stateNum = modef->state - states; - // Loaded? - if(mdl) + // Associate this modeldef with its state. + if(stateModefs[stateNum] < 0) { - defineAllSkins(*mdl); - - // Enlarge the vertex buffers in preparation for drawing of this model. - if(!Rend_ModelExpandVertexBuffers(mdl->numVertices())) - { - LOG_WARNING("Model \"%s\" contains more than %u max vertices (%i), it will not be rendered.") - << NativePath(path).pretty() - << uint(RENDER_MAX_MODEL_VERTS) << mdl->numVertices(); - } + // No modef; use this. + stateModefs[stateNum] = indexOfModelDef(modef); + } + else + { + // Must check intermark; smallest wins! + modeldef_t *other = modelDefForState(stateNum); - return mdl; + if((modef->interMark <= other->interMark && // Should never be == + modef->select == other->select) || modef->select < other->select) // Smallest selector? + { + stateModefs[stateNum] = indexOfModelDef(modef); + } } } - catch(FS1::NotFoundError const &er) - { - // Huh?? Should never happen. - LOG_WARNING(er.asText() + ", ignoring."); - } - - return 0; -} - -/** - * Returns the appropriate modeldef for the given state. - */ -static modeldef_t *getStateModel(state_t &st, int select) -{ - modeldef_t *modef = modelDefForState(&st - states); - if(!modef) return 0; - if(select) + // Calculate the particle offset for each submodel. + Vector3f min, max; + for(uint i = 0; i < modef->subCount(); ++i) { - // Choose the correct selector, or selector zero if the given one not available. - int const mosel = select & DDMOBJ_SELECTOR_MASK; - for(modeldef_t *it = modef; it; it = it->selectNext) + SubmodelDef *sub = &modef->subModelDef(i); + if(sub->modelId && sub->frame >= 0) { - if(it->select == mosel) - { - return it; - } + Models_ToModel(sub->modelId)->frame(sub->frame).bounds(min, max); + modef->setParticleOffset(i, ((max + min) / 2 + sub->offset) * modef->scale + modef->offset); } } - return modef; + // Calculate visual radius for shadows. + /// @todo fixme: use a separate property. + /*if(def.shadowRadius) + { + modef->visualRadius = def.shadowRadius; + } + else*/ + { + modef->visualRadius = calcModelVisualRadius(modef); + } } -modeldef_t *Models_Definition(char const *id) +static int destroyModelInRepository(StringPool::Id id, void * /*context*/) { - if(!id || !id[0]) return 0; - - for(uint i = 0; i < modefs.size(); ++i) + if(Model *model = reinterpret_cast(modelRepository->userPointer(id))) { - if(!strcmp(modefs[i].id, id)) - { - return &modefs[i]; - } + modelRepository->setUserPointer(id, 0); + delete model; } return 0; } -float Models_ModelForMobj(mobj_t const *mo, modeldef_t **modef, modeldef_t **nextmodef) +static void clearModelList() { - // On the client it is possible that we don't know the mobj's state. - if(!mo->state) return -1; - - state_t &st = *mo->state; + if(!modelRepository) return; - // By default there are no models. - *nextmodef = NULL; - *modef = getStateModel(st, mo->selector); - if(!*modef) return -1; // No model available. + modelRepository->iterate(destroyModelInRepository, 0); +} - float interp = -1; +void Models_Init() +{ + // Dedicated servers do nothing with models. + if(isDedicated) return; + if(CommandLine_Check("-nomd2")) return; - // World time animation? - bool worldTime = false; - if((*modef)->flags & MFF_WORLD_TIME_ANIM) - { - float duration = (*modef)->interRange[0]; - float offset = (*modef)->interRange[1]; + LOG_VERBOSE("Initializing Models..."); + Time begunAt; - // Validate/modify the values. - if(duration == 0) duration = 1; + modelRepository = new StringPool(); - if(offset == -1) - { - offset = M_CycleIntoRange(MOBJ_TO_ID(mo), duration); - } + clearModelList(); + modefs.clear(); - interp = M_CycleIntoRange(App_World().time() / duration + offset, 1); - worldTime = true; - } - else + // There can't be more modeldefs than there are DED Models. + for(uint i = 0; i < defs.models.size(); ++i) { - // Calculate the currently applicable intermark. - interp = 1.0f - (mo->tics - frameTimePos) / float( st.tics ); + modefs.push_back(ModelDef()); } -/*#if _DEBUG - if(mo->dPlayer) + // Clear the modef pointers of all States. + stateModefs.clear(); + for(int i = 0; i < countStates.num; ++i) { - qDebug() << "itp:" << interp << " mot:" << mo->tics << " stt:" << st.tics; + stateModefs.push_back(-1); } -#endif*/ - - // First find the modef for the interpoint. Intermark is 'stronger' than interrange. - // Scan interlinks. - while((*modef)->interNext && (*modef)->interNext->interMark <= interp) + // Read in the model files and their data. + // Use the latest definition available for each sprite ID. + for(int i = int(defs.models.size()) - 1; i >= 0; --i) { - *modef = (*modef)->interNext; - } + if(!(i % 100)) + { + // This may take a while, so keep updating the progress. + Con_SetProgress(130 + 70*(defs.models.size() - i)/defs.models.size()); + } - if(!worldTime) - { - // Scale to the modeldef's interpolation range. - interp = (*modef)->interRange[0] + interp * ((*modef)->interRange[1] - - (*modef)->interRange[0]); + setupModel(defs.models[i]); } - // What would be the next model? Check interlinks first. - if((*modef)->interNext) - { - *nextmodef = (*modef)->interNext; - } - else if(worldTime) - { - *nextmodef = getStateModel(st, mo->selector); - } - else if(st.nextState > 0) // Check next state. + // Create interlinks. Note that the order in which the defs were loaded + // is important. We want to allow "patch" definitions, right? + + // For each modeldef we will find the "next" def. + for(int i = int(modefs.size()) - 1; i >= 0; --i) { - // Find the appropriate state based on interrange. - state_t *it = states + st.nextState; - bool foundNext = false; - if((*modef)->interRange[1] < 1) - { - // Current modef doesn't interpolate to the end, find the proper destination - // modef (it isn't just the next one). Scan the states that follow (and - // interlinks of each). - bool stopScan = false; - int max = 20; // Let's not be here forever... - while(!stopScan) - { - if(!((!modelDefForState(it - states) || - getStateModel(*it, mo->selector)->interRange[0] > 0) && - it->nextState > 0)) - { - stopScan = true; - } - else - { - // Scan interlinks, then go to the next state. - modeldef_t *mdit; - if((mdit = getStateModel(*it, mo->selector)) && mdit->interNext) - { - forever - { - mdit = mdit->interNext; - if(mdit) - { - if(mdit->interRange[0] <= 0) // A new beginning? - { - *nextmodef = mdit; - foundNext = true; - } - } + modeldef_t *me = &modefs[i]; - if(!mdit || foundNext) - { - break; - } - } - } + float minmark = 2; // max = 1, so this is "out of bounds". - if(foundNext) - { - stopScan = true; - } - else - { - it = states + it->nextState; - } - } + modeldef_t *closest = 0; + for(int k = int(modefs.size()) - 1; k >= 0; --k) + { + modeldef_t *other = &modefs[k]; - if(max-- <= 0) - stopScan = true; + // Same state and a bigger order are the requirements. + if(other->state == me->state && other->def > me->def && // Defined after me. + other->interMark > me->interMark && + other->interMark < minmark) + { + minmark = other->interMark; + closest = other; } - // @todo What about max == -1? What should 'it' be then? } - if(!foundNext) - *nextmodef = getStateModel(*it, mo->selector); + me->interNext = closest; } - // Is this group disabled? - if(useModels >= 2 && (*modef)->group & useModels) + // Create selectlinks. + for(int i = int(modefs.size()) - 1; i >= 0; --i) { - *modef = *nextmodef = NULL; - return -1; - } + modeldef_t *me = &modefs[i]; - return interp; -} + int minsel = DDMAXINT; -/** - * Scales the given model so that it'll be 'destHeight' units tall. Measurements - * are based on submodel zero. Scale is applied uniformly. - */ -static void scaleModel(modeldef_t &mf, float destHeight, float offset) -{ - if(!mf.subCount()) return; + modeldef_t *closest = 0; - submodeldef_t &smf = mf.subModelDef(0); + // Start scanning from the next definition. + for(int k = int(modefs.size()) - 1; k >= 0; --k) + { + modeldef_t *other = &modefs[k]; - // No model to scale? - if(!smf.modelId) return; + // Same state and a bigger order are the requirements. + if(other->state == me->state && other->def > me->def && // Defined after me. + other->select > me->select && other->select < minsel && + other->interMark >= me->interMark) + { + minsel = other->select; + closest = other; + } + } - // Find the top and bottom heights. - float top, bottom; - float height = Models_ToModel(smf.modelId)->frame(smf.frame).horizontalRange(&top, &bottom); - if(!height) height = 1; + me->selectNext = closest; + } - float scale = destHeight / height; + LOG_INFO(String("Models_Init: Completed in %1 seconds.").arg(begunAt.since(), 0, 'g', 2)); +} - mf.scale = Vector3f(scale, scale, scale); - mf.offset.y = -bottom * scale + offset; -} - -static void scaleModelToSprite(modeldef_t &mf, Sprite *sprite) +void Models_Shutdown() { - if(!sprite) return; - if(!sprite->hasViewAngle(0)) return; + /// @todo Why only centralized memory deallocation? Bad (lazy) design... + modefs.clear(); + stateModefs.clear(); - MaterialSnapshot const &ms = sprite->viewAngle(0).material->prepare(Rend_SpriteMaterialSpec()); - Texture const &tex = ms.texture(MTU_PRIMARY).generalCase(); - int off = de::max(0, -tex.origin().y - ms.height()); - scaleModel(mf, ms.height(), off); + clearModelList(); + + if(modelRepository) + { + delete modelRepository; modelRepository = 0; + } } -static float calcModelVisualRadius(modeldef_t *def) +void Models_Cache(modeldef_t *modef) { - if(!def || !def->subModelId(0)) return 0; + if(!modef) return; - // Use the first frame bounds. - Vector3f min, max; - float maxRadius = 0; - for(uint i = 0; i < def->subCount(); ++i) + for(uint sub = 0; sub < modef->subCount(); ++sub) { - if(!def->subModelId(i)) break; - - SubmodelDef &sub = def->subModelDef(i); + submodeldef_t &subdef = modef->subModelDef(sub); + Model *mdl = Models_ToModel(subdef.modelId); + if(!mdl) continue; - Models_ToModel(sub.modelId)->frame(sub.frame).bounds(min, max); + // Load all skins. + foreach(ModelSkin const &skin, mdl->skins()) + { + if(Texture *tex = skin.texture) + { + tex->prepareVariant(Rend_ModelDiffuseTextureSpec(mdl->flags().testFlag(Model::NoTextureCompression))); + } + } - // Half the distance from bottom left to top right. - float radius = (def->scale.x * (max.x - min.x) + - def->scale.z * (max.z - min.z)) / 3.5f; - if(radius > maxRadius) + // Load the shiny skin too. + if(Texture *tex = subdef.shinySkin) { - maxRadius = radius; + tex->prepareVariant(Rend_ModelShinyTextureSpec()); } } - - return maxRadius; } -/** - * Create a new modeldef or find an existing one. This is for ID'd models. - */ -static modeldef_t *getModelDefWithId(char const *id) +int Models_CacheForMobj(thinker_t *th, void * /*context*/) { - // ID defined? - if(!id || !id[0]) return 0; + if(!(useModels && precacheSkins)) return true; - // First try to find an existing modef. - modeldef_t *md = Models_Definition(id); - if(md) return md; + mobj_t *mo = (mobj_t *) th; - // Get a new entry. - modefs.push_back(ModelDef(id)); - return &modefs.back(); + // Check through all the model definitions. + for(uint i = 0; i < modefs.size(); ++i) + { + modeldef_t *modef = &modefs[i]; + + if(!modef->state) continue; + if(mo->type < 0 || mo->type >= defs.count.mobjs.num) continue; // Hmm? + if(stateOwners[modef->state - states] != &mobjInfo[mo->type]) continue; + + Models_Cache(modef); + } + + return false; // Used as iterator. +} + +#undef Models_CacheForState +DENG_EXTERN_C void Models_CacheForState(int stateIndex) +{ + if(!useModels) return; + if(stateIndex <= 0 || stateIndex >= defs.count.states.num) return; + if(stateModefs[stateIndex] < 0) return; + + Models_Cache(modelDefForState(stateIndex)); } +// ----------------------------------------------------------------------------- + +#define NUMVERTEXNORMALS 162 +static float avertexnormals[NUMVERTEXNORMALS][3] = { +#include "tab_anorms.h" +}; + /** - * Create a new modeldef or find an existing one. There can be only one model - * definition associated with a state/intermark pair. + * Calculate vertex normals. Only with -renorm. */ -static modeldef_t *getModelDef(int state, float interMark, int select) +#if 0 // unused atm. +static void rebuildNormals(model_t &mdl) { - // Is this a valid state? - if(state < 0 || state >= countStates.num) return 0; + // Renormalizing? + if(!CommandLine_Check("-renorm")) return; - // First try to find an existing modef. - for(uint i = 0; i < modefs.size(); ++i) + 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) { - if(modefs[i].state == &states[state] && - modefs[i].interMark == interMark && modefs[i].select == select) + model_vertex_t* list = mdl.frames[i].vertices; + + for(int k = 0; k < tris; ++k) { - // Models are loaded in reverse order; this one already has - // a model. - return NULL; + 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); } - } - modefs.push_back(ModelDef()); + for(int k = 0; k < verts; ++k) + { + memset(&norm, 0, sizeof(norm)); + cnt = 0; - // Set initial data. - modeldef_t *md = &modefs.back(); - md->state = &states[state]; - md->interMark = interMark; - md->select = select; - return md; -} + for(int j = 0; j < tris; ++j) + { + dmd_triangle_t const& tri = mdl.lods[0].triangles[j]; -/** - * Creates a modeldef based on the given DED info. A pretty straightforward - * operation. No interlinks are set yet. Autoscaling is done and the scale - * factors set appropriately. After this has been called for all available - * Model DEDs, each State that has a model will have a pointer to the one - * with the smallest intermark (start of a chain). - */ -static void setupModel(ded_model_t &def) -{ - LOG_AS("setupModel"); + 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; + } + } + } - int const modelScopeFlags = def.flags | defs.modelFlags; - int const statenum = Def_GetStateNum(def.state); + if(!cnt) continue; // Impossible... - // Is this an ID'd model? - modeldef_t *modef = getModelDefWithId(def.id); - if(!modef) - { - // No, normal State-model. - if(statenum < 0) return; + // Calculate the average. + for(int n = 0; n < 3; ++n) + { + norm.pos[n] /= cnt; + } - modef = getModelDef(statenum + def.off, def.interMark, def.selector); - if(!modef) return; // Can't get a modef, quit! + // Normalize it. + V3f_Normalize(norm.pos); + memcpy(list[k].normal, norm.pos, sizeof(norm.pos)); + } } - // Init modef info (state & intermark already set). - modef->def = &def; - modef->group = def.group; - modef->flags = modelScopeFlags; - modef->offset = def.offset; - modef->offset.y += defs.modelOffset; // Common Y axis offset. - modef->scale = def.scale; - modef->scale.y *= defs.modelScale; // Common Y axis scaling. - modef->resize = def.resize; - modef->skinTics = de::max(def.skinTics, 1); - for(int i = 0; i < 2; ++i) - { - modef->interRange[i] = def.interRange[i]; - } + Z_Free(normals); +} +#endif - // Submodels. - modef->clearSubs(); - for(uint i = 0; i < def.subCount(); ++i) +static void defineAllSkins(Model &mdl) +{ + String const &modelFilePath = findModelPath(mdl.modelId()); + + int numFoundSkins = 0; + for(int i = 0; i < mdl.skinCount(); ++i) { - ded_submodel_t const *subdef = &def.sub(i); - submodeldef_t *sub = modef->addSub(); + ModelSkin &skin = mdl.skin(i); - sub->modelId = 0; + if(skin.name.isEmpty()) + continue; - if(!subdef->filename) continue; - de::Uri const &searchPath = reinterpret_cast(*subdef->filename); - if(searchPath.isEmpty()) continue; + try + { + de::Uri foundResourceUri(Path(findSkinPath(skin.name, modelFilePath))); + + skin.texture = App_ResourceSystem().defineTexture("ModelSkins", foundResourceUri); + + // We have found one more skin for this model. + numFoundSkins += 1; + } + catch(FS1::NotFoundError const&) + { + LOG_WARNING("Failed to locate \"%s\" (#%i) for model \"%s\", ignoring.") + << skin.name << i << NativePath(modelFilePath).pretty(); + } + } + + if(!numFoundSkins) + { + // Lastly try a skin named similarly to the model in the same directory. + de::Uri searchPath(modelFilePath.fileNamePath() / modelFilePath.fileNameWithoutExtension(), RC_GRAPHIC); try { String foundPath = App_FileSystem().findPath(searchPath, RLF_DEFAULT, - App_ResourceClass(RC_MODEL)); + App_ResourceClass(RC_GRAPHIC)); // Ensure the found path is absolute. foundPath = App_BasePath() / foundPath; - Model *mdl = loadModel(foundPath); - if(!mdl) continue; - - sub->modelId = mdl->modelId(); - sub->frame = mdl->toFrameNumber(subdef->frame); - if(sub->frame < 0) sub->frame = 0; - sub->frameRange = de::max(1, subdef->frameRange); // Frame range must always be greater than zero. + defineSkinAndAddToModelIndex(mdl, foundPath); + // We have found one more skin for this model. + numFoundSkins = 1; - sub->alpha = byte(255 - subdef->alpha * 255); - sub->blendMode = subdef->blendMode; + LOG_INFO("Assigned fallback skin \"%s\" to index #0 for model \"%s\".") + << NativePath(foundPath).pretty() + << NativePath(modelFilePath).pretty(); + } + catch(FS1::NotFoundError const&) + {} // Ignore this error. + } - // Submodel-specific flags cancel out model-scope flags! - sub->setFlags(modelScopeFlags ^ subdef->flags); + if(!numFoundSkins) + { + LOG_WARNING("Failed to locate a skin for model \"%s\". This model will be rendered without a skin.") + << NativePath(modelFilePath).pretty(); + } +} - // Flags may override alpha and/or blendmode. - if(sub->testFlag(MFF_BRIGHTSHADOW)) - { - sub->alpha = byte(256 * .80f); - sub->blendMode = BM_ADD; - } - else if(sub->testFlag(MFF_BRIGHTSHADOW2)) - { - sub->blendMode = BM_ADD; - } - else if(sub->testFlag(MFF_DARKSHADOW)) - { - sub->blendMode = BM_DARK; - } - else if(sub->testFlag(MFF_SHADOW2)) - { - sub->alpha = byte(256 * .2f); - } - else if(sub->testFlag(MFF_SHADOW1)) - { - sub->alpha = byte(256 * .62f); - } +static Model *interpretDmd(de::FileHandle &hndl, String path, modelid_t modelId) +{ + if(recogniseDmd(hndl)) + { + LOG_VERBOSE("Interpreted \"" + NativePath(path).pretty() + "\" as a DMD model."); + Model *mdl = modelForId(modelId, true/*create*/); + loadDmd(hndl, *mdl); + return mdl; + } + return 0; +} - // Extra blendmodes: - if(sub->testFlag(MFF_REVERSE_SUBTRACT)) +static Model *interpretMd2(de::FileHandle &hndl, String path, modelid_t modelId) +{ + if(recogniseMd2(hndl)) + { + LOG_VERBOSE("Interpreted \"" + NativePath(path).pretty() + "\" as a MD2 model."); + Model *mdl = modelForId(modelId, true/*create*/); + loadMd2(hndl, *mdl); + return mdl; + } + return 0; +} + +struct ModelFileType +{ + /// Symbolic name of the resource type. + String const name; + + /// Known file extension. + String const ext; + + Model *(*interpretFunc)(de::FileHandle &hndl, String path, modelid_t modelId); +}; + +// 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)) { - sub->blendMode = BM_REVERSE_SUBTRACT; + return &type; } - else if(sub->testFlag(MFF_SUBTRACT)) + } + } + return 0; // Unknown. +} + +static Model *interpretModel(de::FileHandle &hndl, String path, modelid_t modelId) +{ + // Firstly try the interpreter for the guessed resource types. + ModelFileType const *rtypeGuess = guessModelFileTypeFromFileName(path); + if(rtypeGuess) + { + if(Model *mdl = rtypeGuess->interpretFunc(hndl, path, modelId)) + return mdl; + } + + // Not yet interpreted - try each recognisable format in order. + // Try each recognisable format instead. + for(int i = 0; !modelTypes[i].name.isEmpty(); ++i) + { + ModelFileType const &modelType = modelTypes[i]; + + // Already tried this? + if(&modelType == rtypeGuess) continue; + + if(Model *mdl = modelType.interpretFunc(hndl, path, modelId)) + return mdl; + } + + return 0; +} + +/** + * Finds the existing model or loads in a new one. + */ +static Model *loadModel(String path) +{ + // Have we already loaded this? + modelid_t modelId = modelRepository->intern(path); + Model *mdl = Models_ToModel(modelId); + if(mdl) return mdl; // Yes. + + try + { + // Attempt to interpret and load this model file. + de::FileHandle &hndl = App_FileSystem().openFile(path, "rb"); + + mdl = interpretModel(hndl, path, modelId); + + // We're done with the file. + App_FileSystem().releaseFile(hndl.file()); + delete &hndl; + + // Loaded? + if(mdl) + { + defineAllSkins(*mdl); + + // Enlarge the vertex buffers in preparation for drawing of this model. + if(!Rend_ModelExpandVertexBuffers(mdl->vertexCount())) { - sub->blendMode = BM_SUBTRACT; + LOG_WARNING("Model \"%s\" contains more than %u max vertices (%i), it will not be rendered.") + << NativePath(path).pretty() + << uint(RENDER_MAX_MODEL_VERTS) << mdl->vertexCount(); } - if(subdef->skinFilename && !Uri_IsEmpty(subdef->skinFilename)) - { - // A specific file name has been given for the skin. - String const &skinFilePath = reinterpret_cast(*subdef->skinFilename).path(); - String const &modelFilePath = findModelPath(sub->modelId); - try - { - Path foundResourcePath(findSkinPath(skinFilePath, modelFilePath)); + return mdl; + } + } + catch(FS1::NotFoundError const &er) + { + // Huh?? Should never happen. + LOG_WARNING(er.asText() + ", ignoring."); + } + + return 0; +} + +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 + +#pragma pack(1) +struct md2_header_t +{ + int magic; + int version; + int skinWidth; + int skinHeight; + int frameSize; + int numSkins; + int numVertices; + int numTexCoords; + int numTriangles; + int numGlCommands; + int numFrames; + int offsetSkins; + int offsetTexCoords; + int offsetTriangles; + int offsetFrames; + int offsetGlCommands; + int offsetEnd; +}; +#pragma pack() + +static bool readMd2Header(de::FileHandle &file, md2_header_t &hdr) +{ + size_t readBytes = file.read((uint8_t *)&hdr, sizeof(md2_header_t)); + if(readBytes < sizeof(md2_header_t)) return false; + + hdr.magic = littleEndianByteOrder.toNative(hdr.magic); + hdr.version = littleEndianByteOrder.toNative(hdr.version); + hdr.skinWidth = littleEndianByteOrder.toNative(hdr.skinWidth); + hdr.skinHeight = littleEndianByteOrder.toNative(hdr.skinHeight); + hdr.frameSize = littleEndianByteOrder.toNative(hdr.frameSize); + hdr.numSkins = littleEndianByteOrder.toNative(hdr.numSkins); + hdr.numVertices = littleEndianByteOrder.toNative(hdr.numVertices); + hdr.numTexCoords = littleEndianByteOrder.toNative(hdr.numTexCoords); + hdr.numTriangles = littleEndianByteOrder.toNative(hdr.numTriangles); + hdr.numGlCommands = littleEndianByteOrder.toNative(hdr.numGlCommands); + hdr.numFrames = littleEndianByteOrder.toNative(hdr.numFrames); + hdr.offsetSkins = littleEndianByteOrder.toNative(hdr.offsetSkins); + hdr.offsetTexCoords = littleEndianByteOrder.toNative(hdr.offsetTexCoords); + hdr.offsetTriangles = littleEndianByteOrder.toNative(hdr.offsetTriangles); + hdr.offsetFrames = littleEndianByteOrder.toNative(hdr.offsetFrames); + hdr.offsetGlCommands = littleEndianByteOrder.toNative(hdr.offsetGlCommands); + hdr.offsetEnd = littleEndianByteOrder.toNative(hdr.offsetEnd); + return true; +} + +/// @todo We only really need to read the magic bytes and the version here. +static bool recogniseMd2(de::FileHandle &file) +{ + md2_header_t hdr; + size_t initPos = file.tell(); + // Seek to the start of the header. + file.seek(0, SeekSet); + bool result = (readMd2Header(file, hdr) && LONG(hdr.magic) == MD2_MAGIC); + // Return the stream to its original position. + file.seek(initPos, SeekSet); + 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 void loadMd2(de::FileHandle &file, Model &mdl) +{ + // Read the header. + md2_header_t hdr; + bool readHeaderOk = readMd2Header(file, hdr); + DENG2_ASSERT(readHeaderOk); + DENG2_UNUSED(readHeaderOk); // should this be checked? + + mdl._numVertices = hdr.numVertices; + + mdl.clearAllFrames(); + + // 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; - sub->skin = defineSkinAndAddToModelIndex(*mdl, foundResourcePath); - } - catch(FS1::NotFoundError const&) - { - LOG_WARNING("Failed to locate skin \"%s\" for model \"%s\", ignoring.") - << reinterpret_cast(*subdef->skinFilename) << NativePath(modelFilePath).pretty(); - } - } - else - { - sub->skin = subdef->skin; - } + ModelFrame *frame = new ModelFrame(mdl, frameName); + frame->vertices.reserve(hdr.numVertices); - // Skin range must always be greater than zero. - sub->skinRange = de::max(subdef->skinRange, 1); + // 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(); - // Offset within the model. - sub->offset = subdef->offset; + vtx.pos = Vector3f(pVtx->vertex[0], pVtx->vertex[2], pVtx->vertex[1]) + * scale + translation; + vtx.pos.y *= rModelAspectMod; // Aspect undoing. - if(subdef->shinySkin && !Uri_IsEmpty(subdef->shinySkin)) - { - String const &skinFilePath = reinterpret_cast(*subdef->shinySkin).path(); - String const &modelFilePath = findModelPath(sub->modelId); - try - { - de::Uri foundResourceUri(Path(findSkinPath(skinFilePath, modelFilePath))); + vtx.norm = Vector3f(avertexnormals[pVtx->normalIndex]); - sub->shinySkin = App_ResourceSystem().defineTexture("ModelReflectionSkins", foundResourceUri); - } - catch(FS1::NotFoundError const &) - { - LOG_WARNING("Failed to locate skin \"%s\" for model \"%s\", ignoring.") - << skinFilePath << NativePath(modelFilePath).pretty(); - } - } - else + if(!k) { - sub->shinySkin = 0; + frame->min = frame->max = vtx.pos; } - - // Should we allow texture compression with this model? - if(sub->testFlag(MFF_NO_TEXCOMP)) + else { - // All skins of this model will no longer use compression. - mdl->setFlags(Model::NoTextureCompression); + frame->min = vtx.pos.min(frame->min); + frame->max = vtx.pos.max(frame->max); } } - catch(FS1::NotFoundError const &) - { - LOG_WARNING("Failed to locate \"%s\", ignoring.") << searchPath; - } - } - // Do scaling, if necessary. - if(modef->resize) - { - scaleModel(*modef, modef->resize, modef->offset.y); + mdl.addFrame(frame); // takes owernship } - else if(modef->state && modef->testSubFlag(0, MFF_AUTOSCALE)) - { - spritenum_t sprNum = Def_GetSpriteNum(def.sprite.id); - int sprFrame = def.spriteFrame; - - if(sprNum < 0) - { - // No sprite ID given. - sprNum = modef->state->sprite; - sprFrame = modef->state->frame; - } + M_Free(frameData); - if(Sprite *sprite = App_ResourceSystem().spritePtr(sprNum, sprFrame)) - { - scaleModelToSprite(*modef, sprite); - } - } + mdl._lods.append(new ModelDetailLevel); + ModelDetailLevel &lod0 = *mdl._lods.last(); - if(modef->state) + uint8_t *commandData = (uint8_t *) allocAndLoad(file, hdr.offsetGlCommands, 4 * hdr.numGlCommands); + for(uint8_t const *pos = commandData; *pos;) { - int stateNum = modef->state - states; + int count = LONG( *(int *) pos ); pos += 4; - // Associate this modeldef with its state. - if(stateModefs[stateNum] < 0) + lod0.primitives.append(ModelDetailLevel::Primitive()); + ModelDetailLevel::Primitive &prim = lod0.primitives.last(); + + // The type of primitive depends on the sign. + prim.triFan = (count < 0); + + if(count < 0) { - // No modef; use this. - stateModefs[stateNum] = indexOfModelDef(modef); + count = -count; } - else + + while(count--) { - // Must check intermark; smallest wins! - modeldef_t *other = modelDefForState(stateNum); + md2_commandElement_t const *v = (md2_commandElement_t *) pos; pos += 12; - if((modef->interMark <= other->interMark && // Should never be == - modef->select == other->select) || modef->select < other->select) // Smallest selector? - { - stateModefs[stateNum] = indexOfModelDef(modef); - } + prim.elements.append(ModelDetailLevel::Primitive::Element()); + ModelDetailLevel::Primitive::Element &elem = prim.elements.last(); + elem.texCoord = Vector2f(FLOAT(v->s), FLOAT(v->t)); + elem.index = LONG(v->index); } } + M_Free(commandData); - // Calculate the particle offset for each submodel. - Vector3f min, max; - for(uint i = 0; i < modef->subCount(); ++i) - { - SubmodelDef *sub = &modef->subModelDef(i); - if(sub->modelId && sub->frame >= 0) - { - Models_ToModel(sub->modelId)->frame(sub->frame).bounds(min, max); - modef->setParticleOffset(i, ((max + min) / 2 + sub->offset) * modef->scale + modef->offset); - } - } + mdl.clearAllSkins(); - // Calculate visual radius for shadows. - /// @todo fixme: use a separate property. - /*if(def.shadowRadius) - { - modef->visualRadius = def.shadowRadius; - } - else*/ + // Load skins. (Note: numSkins may be zero.) + file.seek(hdr.offsetSkins, SeekSet); + for(int i = 0; i < hdr.numSkins; ++i) { - modef->visualRadius = calcModelVisualRadius(modef); + char name[64]; file.read((uint8_t *)name, 64); + mdl.newSkin(name); } } -static int destroyModelInRepository(StringPool::Id id, void * /*context*/) +// +#define DMD_MAGIC 0x4D444D44 ///< "DMDM" = Doomsday/Detailed MoDel Magic + +#pragma pack(1) +struct dmd_header_t { - if(Model *model = reinterpret_cast(modelRepository->userPointer(id))) - { - modelRepository->setUserPointer(id, 0); - delete model; - } - return 0; + int magic; + int version; + int flags; +}; +#pragma pack() + +static bool readHeaderDmd(de::FileHandle &file, dmd_header_t &hdr) +{ + size_t readBytes = file.read((uint8_t *)&hdr, sizeof(dmd_header_t)); + if(readBytes < sizeof(dmd_header_t)) return false; + + hdr.magic = littleEndianByteOrder.toNative(hdr.magic); + hdr.version = littleEndianByteOrder.toNative(hdr.version); + hdr.flags = littleEndianByteOrder.toNative(hdr.flags); + return true; } -static void clearModelList() +static bool recogniseDmd(de::FileHandle &file) { - if(!modelRepository) return; + dmd_header_t hdr; + size_t initPos = file.tell(); + // Seek to the start of the header. + file.seek(0, SeekSet); + bool result = (readHeaderDmd(file, hdr) && LONG(hdr.magic) == DMD_MAGIC); + // Return the stream to its original position. + file.seek(initPos, SeekSet); + return result; +} - modelRepository->iterate(destroyModelInRepository, 0); +// 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)); } -void Models_Init() +/** + * Note vertex Z/Y are swapped here (ordered XYZ in the serialized data). + */ +static void loadDmd(de::FileHandle &file, Model &mdl) { - // Dedicated servers do nothing with models. - if(isDedicated) return; - if(CommandLine_Check("-nomd2")) return; + // Read the header. + dmd_header_t hdr; + bool readHeaderOk = readHeaderDmd(file, hdr); + DENG2_ASSERT(readHeaderOk); + DENG2_UNUSED(readHeaderOk); // should this be checked? - LOG_VERBOSE("Initializing Models..."); - Time begunAt; + // Read the chunks. + dmd_chunk_t chunk; + file.read((uint8_t *)&chunk, sizeof(chunk)); - modelRepository = new StringPool(); + 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)); - clearModelList(); - modefs.clear(); + 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; - // There can't be more modeldefs than there are DED Models. - for(uint i = 0; i < defs.models.size(); ++i) - { - modefs.push_back(ModelDef()); + default: + // Skip unknown chunks. + file.seek(LONG(chunk.length), SeekCur); + break; + } + // Read the next chunk header. + file.read((uint8_t *)&chunk, sizeof(chunk)); } + mdl._numVertices = info.numVertices; - // Clear the modef pointers of all States. - stateModefs.clear(); - for(int i = 0; i < countStates.num; ++i) - { - stateModefs.push_back(-1); - } + mdl.clearAllSkins(); - // Read in the model files and their data. - // Use the latest definition available for each sprite ID. - for(int i = int(defs.models.size()) - 1; i >= 0; --i) + // Allocate and load in the data. (Note: numSkins may be zero.) + file.seek(info.offsetSkins, SeekSet); + for(int i = 0; i < info.numSkins; ++i) { - if(!(i % 100)) - { - // This may take a while, so keep updating the progress. - Con_SetProgress(130 + 70*(defs.models.size() - i)/defs.models.size()); - } - - setupModel(defs.models[i]); + char name[64]; file.read((uint8_t *)name, 64); + mdl.newSkin(name); } - // Create interlinks. Note that the order in which the defs were loaded - // is important. We want to allow "patch" definitions, right? + mdl.clearAllFrames(); - // For each modeldef we will find the "next" def. - for(int i = int(modefs.size()) - 1; i >= 0; --i) + uint8_t *frameData = (uint8_t *) allocAndLoad(file, info.offsetFrames, info.frameSize * info.numFrames); + for(int i = 0; i < info.numFrames; ++i) { - modeldef_t *me = &modefs[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; - float minmark = 2; // max = 1, so this is "out of bounds". + ModelFrame *frame = new ModelFrame(mdl, frameName); + frame->vertices.reserve(info.numVertices); - modeldef_t *closest = 0; - for(int k = int(modefs.size()) - 1; k >= 0; --k) + // Scale and translate each vertex. + dmd_packedVertex_t const *pVtx = pfr->vertices; + for(int k = 0; k < info.numVertices; ++k, ++pVtx) { - modeldef_t *other = &modefs[k]; - - // Same state and a bigger order are the requirements. - if(other->state == me->state && other->def > me->def && // Defined after me. - other->interMark > me->interMark && - other->interMark < minmark) - { - minmark = other->interMark; - closest = other; - } - } - - me->interNext = closest; - } - - // Create selectlinks. - for(int i = int(modefs.size()) - 1; i >= 0; --i) - { - modeldef_t *me = &modefs[i]; - - int minsel = DDMAXINT; + frame->vertices.append(ModelFrame::Vertex()); + ModelFrame::Vertex &vtx = frame->vertices.last(); - modeldef_t *closest = 0; + vtx.pos = Vector3f(pVtx->vertex[0], pVtx->vertex[2], pVtx->vertex[1]) + * scale + translation; + vtx.pos.y *= rModelAspectMod; // Aspect undo. - // Start scanning from the next definition. - for(int k = int(modefs.size()) - 1; k >= 0; --k) - { - modeldef_t *other = &modefs[k]; + vtx.norm = unpackVector(USHORT(pVtx->normal)); - // Same state and a bigger order are the requirements. - if(other->state == me->state && other->def > me->def && // Defined after me. - other->select > me->select && other->select < minsel && - other->interMark >= me->interMark) + if(!k) { - minsel = other->select; - closest = other; + frame->min = frame->max = vtx.pos; + } + else + { + frame->min = vtx.pos.min(frame->min); + frame->max = vtx.pos.max(frame->max); } } - me->selectNext = closest; + mdl.addFrame(frame); } + M_Free(frameData); - LOG_INFO(String("Models_Init: Completed in %1 seconds.").arg(begunAt.since(), 0, 'g', 2)); -} - -void Models_Shutdown() -{ - /// @todo Why only centralized memory deallocation? Bad (lazy) design... - modefs.clear(); - stateModefs.clear(); - - clearModelList(); + file.seek(info.offsetLODs, SeekSet); + dmd_levelOfDetail_t *lodInfo = new dmd_levelOfDetail_t[info.numLODs]; - if(modelRepository) + for(int i = 0; i < info.numLODs; ++i) { - delete modelRepository; modelRepository = 0; + 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); } -} -void Models_Cache(modeldef_t *modef) -{ - if(!modef) return; + dmd_triangle_t **triangles = new dmd_triangle_t*[info.numLODs]; - for(uint sub = 0; sub < modef->subCount(); ++sub) + for(int i = 0; i < info.numLODs; ++i) { - submodeldef_t &subdef = modef->subModelDef(sub); - Model *mdl = Models_ToModel(subdef.modelId); - if(!mdl) continue; + mdl._lods.append(new ModelDetailLevel); + ModelDetailLevel &lod = *mdl._lods.last(); - // Load all skins. - foreach(ModelSkin const &skin, mdl->skins()) - { - if(Texture *tex = skin.texture) - { - tex->prepareVariant(Rend_ModelDiffuseTextureSpec(mdl->flags().testFlag(Model::NoTextureCompression))); - } - } + triangles[i] = (dmd_triangle_t *) allocAndLoad(file, lodInfo[i].offsetTriangles, + sizeof(dmd_triangle_t) * lodInfo[i].numTriangles); - // Load the shiny skin too. - if(Texture *tex = subdef.shinySkin) + uint8_t *commandData = (uint8_t *) allocAndLoad(file, lodInfo[i].offsetGlCommands, + 4 * lodInfo[i].numGlCommands); + for(uint8_t const *pos = commandData; *pos;) { - tex->prepareVariant(Rend_ModelShinyTextureSpec()); - } - } -} + int count = LONG( *(int *) pos ); pos += 4; -#undef Models_CacheForState -DENG_EXTERN_C void Models_CacheForState(int stateIndex) -{ - if(!useModels) return; - if(stateIndex <= 0 || stateIndex >= defs.count.states.num) return; - if(stateModefs[stateIndex] < 0) return; + lod.primitives.append(ModelDetailLevel::Primitive()); + ModelDetailLevel::Primitive &prim = lod.primitives.last(); - Models_Cache(modelDefForState(stateIndex)); -} + // The type of primitive depends on the sign of the element count. + prim.triFan = (count < 0); -int Models_CacheForMobj(thinker_t *th, void * /*context*/) -{ - if(!(useModels && precacheSkins)) return true; + if(count < 0) + { + count = -count; + } - mobj_t *mo = (mobj_t *) th; + while(count--) + { + md2_commandElement_t const *v = (md2_commandElement_t *) pos; pos += 12; - // Check through all the model definitions. - for(uint i = 0; i < modefs.size(); ++i) - { - modeldef_t *modef = &modefs[i]; + prim.elements.append(ModelDetailLevel::Primitive::Element()); + ModelDetailLevel::Primitive::Element &elem = prim.elements.last(); - if(!modef->state) continue; - if(mo->type < 0 || mo->type >= defs.count.mobjs.num) continue; // Hmm? - if(stateOwners[modef->state - states] != &mobjInfo[mo->type]) continue; + elem.texCoord = Vector2f(FLOAT(v->s), FLOAT(v->t)); + elem.index = LONG(v->index); + } + } + M_Free(commandData); + } - Models_Cache(modef); + // 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); } - return false; // Used as iterator. + delete [] lodInfo; + for(int i = 0; i < info.numLODs; ++i) + { + M_Free(triangles[i]); + } + delete [] triangles; }