struct md5; struct md5joint { vec pos; quat orient; }; struct md5weight { int joint; float bias; vec pos; }; struct md5vert { vec2 tc; ushort start, count; }; struct md5hierarchy { string name; int parent, flags, start; }; struct md5 : skelloader { md5(const char *name) : skelloader(name) {} static const char *formatname() { return "md5"; } int type() const { return MDL_MD5; } struct md5mesh : skelmesh { md5weight *weightinfo; int numweights; md5vert *vertinfo; md5mesh() : weightinfo(nullptr), numweights(0), vertinfo(nullptr) { } ~md5mesh() { cleanup(); } void cleanup() { DELETEA(weightinfo); DELETEA(vertinfo); } void buildverts(vector &joints) { loopi(numverts) { md5vert &v = vertinfo[i]; vec pos(0, 0, 0); loopk(v.count) { md5weight &w = weightinfo[v.start+k]; md5joint &j = joints[w.joint]; vec wpos = j.orient.rotate(w.pos); wpos.add(j.pos); wpos.mul(w.bias); pos.add(wpos); } vert &vv = verts[i]; vv.pos = pos; vv.tc = v.tc; blendcombo c; int sorted = 0; loopj(v.count) { md5weight &w = weightinfo[v.start+j]; sorted = c.addweight(sorted, w.bias, w.joint); } c.finalize(sorted); vv.blend = addblendcombo(c); } } void load(stream *f, char *buf, size_t bufsize) { md5weight w; md5vert v; tri t; int index; while(f->getline(buf, bufsize) && buf[0]!='}') { if(strstr(buf, "// meshes:")) { char *start = strchr(buf, ':')+1; if(*start==' ') start++; char *end = start + strlen(start)-1; while(end >= start && isspace(*end)) end--; name = newstring(start, end+1-start); } else if(strstr(buf, "shader")) { char *start = strchr(buf, '"'), *end = start ? strchr(start+1, '"') : nullptr; if(start && end) { char *texname = newstring(start+1, end-(start+1)); part *p = loading->parts.last(); p->initskins(notexture, notexture, group->meshes.length()); skin &s = p->skins.last(); s.tex = textureload(makerelpath(dir, texname), 0, true, false); delete[] texname; } } else if(sscanf(buf, " numverts %d", &numverts)==1) { numverts = max(numverts, 0); if(numverts) { vertinfo = new md5vert[numverts]; verts = new vert[numverts]; } } else if(sscanf(buf, " numtris %d", &numtris)==1) { numtris = max(numtris, 0); if(numtris) tris = new tri[numtris]; } else if(sscanf(buf, " numweights %d", &numweights)==1) { numweights = max(numweights, 0); if(numweights) weightinfo = new md5weight[numweights]; } else if(sscanf(buf, " vert %d ( %f %f ) %hu %hu", &index, &v.tc.x, &v.tc.y, &v.start, &v.count)==5) { if(index>=0 && index=0 && index=0 && index basejoints; while(f->getline(buf, sizeof(buf))) { int tmp; if(sscanf(buf, " MD5Version %d", &tmp)==1) { if(tmp!=10) { delete f; return false; } } else if(sscanf(buf, " numJoints %d", &tmp)==1) { if(tmp<1) { delete f; return false; } if(skel->numbones>0) continue; skel->numbones = tmp; skel->bones = new boneinfo[skel->numbones]; } else if(sscanf(buf, " numMeshes %d", &tmp)==1) { if(tmp<1) { delete f; return false; } } else if(strstr(buf, "joints {")) { string name; int parent; md5joint j; while(f->getline(buf, sizeof(buf)) && buf[0]!='}') { char *curbuf = buf, *curname = name; bool allowspace = false; while(*curbuf && isspace(*curbuf)) curbuf++; if(*curbuf == '"') { curbuf++; allowspace = true; } while(*curbuf && curname < &name[sizeof(name)-1]) { char c = *curbuf++; if(c == '"') break; if(isspace(c) && !allowspace) break; *curname++ = c; } *curname = '\0'; if(sscanf(curbuf, " %d ( %f %f %f ) ( %f %f %f )", &parent, &j.pos.x, &j.pos.y, &j.pos.z, &j.orient.x, &j.orient.y, &j.orient.z)==7) { j.pos.y = -j.pos.y; j.orient.x = -j.orient.x; j.orient.z = -j.orient.z; if(basejoints.length()numbones) { if(!skel->bones[basejoints.length()].name) skel->bones[basejoints.length()].name = newstring(name); skel->bones[basejoints.length()].parent = parent; } j.orient.restorew(); basejoints.add(j); } } if(basejoints.length()!=skel->numbones) { delete f; return false; } } else if(strstr(buf, "mesh {")) { md5mesh *m = new md5mesh; m->group = this; meshes.add(m); m->load(f, buf, sizeof(buf)); if(!m->numtris || !m->numverts) { conoutf(CON_WARN, "empty mesh in %s", filename); meshes.removeobj(m); delete m; } } } if(skel->shared <= 1) { skel->linkchildren(); loopv(basejoints) { boneinfo &b = skel->bones[i]; b.base = dualquat(basejoints[i].orient, basejoints[i].pos); (b.invbase = b.base).invert(); } } loopv(meshes) { md5mesh &m = *(md5mesh *)meshes[i]; m.buildverts(basejoints); if(smooth <= 1) m.smoothnorms(smooth); else m.buildnorms(); m.calctangents(); m.cleanup(); } sortblendcombos(); delete f; return true; } skelanimspec *loadanim(const char *filename) { skelanimspec *sa = skel->findskelanim(filename); if(sa) return sa; stream *f = openfile(filename, "r"); if(!f) return nullptr; vector hierarchy; vector basejoints; int animdatalen = 0, animframes = 0; float *animdata = nullptr; dualquat *animbones = nullptr; char buf[512]; while(f->getline(buf, sizeof(buf))) { int tmp; if(sscanf(buf, " MD5Version %d", &tmp)==1) { if(tmp!=10) { delete f; return nullptr; } } else if(sscanf(buf, " numJoints %d", &tmp)==1) { if(tmp!=skel->numbones) { delete f; return nullptr; } } else if(sscanf(buf, " numFrames %d", &animframes)==1) { if(animframes<1) { delete f; return nullptr; } } else if(sscanf(buf, " frameRate %d", &tmp)==1); else if(sscanf(buf, " numAnimatedComponents %d", &animdatalen)==1) { if(animdatalen>0) animdata = new float[animdatalen]; } else if(strstr(buf, "bounds {")) { while(f->getline(buf, sizeof(buf)) && buf[0]!='}'); } else if(strstr(buf, "hierarchy {")) { while(f->getline(buf, sizeof(buf)) && buf[0]!='}') { md5hierarchy h; if(sscanf(buf, " %100s %d %d %d", h.name, &h.parent, &h.flags, &h.start)==4) hierarchy.add(h); } } else if(strstr(buf, "baseframe {")) { while(f->getline(buf, sizeof(buf)) && buf[0]!='}') { md5joint j; if(sscanf(buf, " ( %f %f %f ) ( %f %f %f )", &j.pos.x, &j.pos.y, &j.pos.z, &j.orient.x, &j.orient.y, &j.orient.z)==6) { j.pos.y = -j.pos.y; j.orient.x = -j.orient.x; j.orient.z = -j.orient.z; j.orient.restorew(); basejoints.add(j); } } if(basejoints.length()!=skel->numbones) { delete f; if(animdata) delete[] animdata; return nullptr; } animbones = new dualquat[(skel->numframes+animframes)*skel->numbones]; if(skel->framebones) { memcpy(animbones, skel->framebones, skel->numframes*skel->numbones*sizeof(dualquat)); delete[] skel->framebones; } skel->framebones = animbones; animbones += skel->numframes*skel->numbones; sa = &skel->addskelanim(filename); sa->frame = skel->numframes; sa->range = animframes; skel->numframes += animframes; } else if(sscanf(buf, " frame %d", &tmp)==1) { for(int numdata = 0; f->getline(buf, sizeof(buf)) && buf[0]!='}';) { for(char *src = buf, *next = src; numdata < animdatalen; numdata++, src = next) { animdata[numdata] = strtod(src, &next); if(next <= src) break; } } dualquat *frame = &animbones[tmp*skel->numbones]; loopv(basejoints) { md5hierarchy &h = hierarchy[i]; md5joint j = basejoints[i]; if(h.start < animdatalen && h.flags) { float *jdata = &animdata[h.start]; if(h.flags&1) j.pos.x = *jdata++; if(h.flags&2) j.pos.y = -*jdata++; if(h.flags&4) j.pos.z = *jdata++; if(h.flags&8) j.orient.x = -*jdata++; if(h.flags&16) j.orient.y = *jdata++; if(h.flags&32) j.orient.z = -*jdata++; j.orient.restorew(); } dualquat dq(j.orient, j.pos); if(adjustments.inrange(i)) adjustments[i].adjust(dq); boneinfo &b = skel->bones[i]; dq.mul(b.invbase); dualquat &dst = frame[i]; if(h.parent < 0) dst = dq; else dst.mul(skel->bones[h.parent].base, dq); dst.fixantipodal(skel->framebones[i]); } } } if(animdata) delete[] animdata; delete f; return sa; } bool load(const char *meshfile, float smooth) { name = newstring(meshfile); if(!loadmesh(meshfile, smooth)) return false; return true; } }; skelmeshgroup *newmeshes() { return new md5meshgroup; } bool loaddefaultparts() { skelpart &mdl = addpart(); const char *fname = name + strlen(name); do --fname; while(fname >= name && *fname!='/' && *fname!='\\'); fname++; defformatstring(meshname, "media/model/%s/%s.md5mesh", name, fname); mdl.meshes = sharemeshes(path(meshname)); if(!mdl.meshes) return false; mdl.initanimparts(); mdl.initskins(); defformatstring(animname, "media/model/%s/%s.md5anim", name, fname); ((md5meshgroup *)mdl.meshes)->loadanim(path(animname)); return true; } }; skelcommands md5commands;