struct smd; struct smdbone { string name; int parent; smdbone() : parent(-1) { name[0] = '\0'; } }; struct smd : skelloader { smd(const char *name) : skelloader(name) {} static const char *formatname() { return "smd"; } int type() const { return MDL_SMD; } struct smdmesh : skelmesh { }; struct smdmeshgroup : skelmeshgroup { smdmeshgroup() { } bool skipcomment(char *&curbuf) { while(*curbuf && isspace(*curbuf)) curbuf++; switch(*curbuf) { case '#': case ';': case '\r': case '\n': case '\0': return true; case '/': if(curbuf[1] == '/') return true; break; } return false; } void skipsection(stream *f, char *buf, size_t bufsize) { while(f->getline(buf, bufsize)) { char *curbuf = buf; if(skipcomment(curbuf)) continue; if(!strncmp(curbuf, "end", 3)) break; } } void readname(char *&curbuf, char *name, size_t namesize) { char *curname = name; while(*curbuf && isspace(*curbuf)) curbuf++; bool allowspace = false; if(*curbuf == '"') { curbuf++; allowspace = true; } while(*curbuf) { char c = *curbuf++; if(c == '"') break; if(isspace(c) && !allowspace) break; if(curname < &name[namesize-1]) *curname++ = c; } *curname = '\0'; } void readnodes(stream *f, char *buf, size_t bufsize, vector &bones) { while(f->getline(buf, bufsize)) { char *curbuf = buf; if(skipcomment(curbuf)) continue; if(!strncmp(curbuf, "end", 3)) break; int id = strtol(curbuf, &curbuf, 10); string name; readname(curbuf, name, sizeof(name)); int parent = strtol(curbuf, &curbuf, 10); if(id < 0 || id > 255 || parent > 255 || !name[0]) continue; while(!bones.inrange(id)) bones.add(); smdbone &bone = bones[id]; copystring(bone.name, name); bone.parent = parent; } } void readmaterial(char *&curbuf, char *name, size_t namesize) { char *curname = name; while(*curbuf && isspace(*curbuf)) curbuf++; while(*curbuf) { char c = *curbuf++; if(isspace(c)) break; if(c == '.') { while(*curbuf && !isspace(*curbuf)) curbuf++; break; } if(curname < &name[namesize-1]) *curname++ = c; } *curname = '\0'; } struct smdmeshdata { smdmesh *mesh; vector verts; vector tris; void finalize() { if(verts.empty() || tris.empty()) return; vert *mverts = new vert[mesh->numverts + verts.length()]; if(mesh->numverts) { memcpy(mverts, mesh->verts, mesh->numverts*sizeof(vert)); delete[] mesh->verts; } memcpy(&mverts[mesh->numverts], verts.getbuf(), verts.length()*sizeof(vert)); mesh->numverts += verts.length(); mesh->verts = mverts; tri *mtris = new tri[mesh->numtris + tris.length()]; if(mesh->numtris) { memcpy(mtris, mesh->tris, mesh->numtris*sizeof(tri)); delete[] mesh->tris; } memcpy(&mtris[mesh->numtris], tris.getbuf(), tris.length()*sizeof(tri)); mesh->numtris += tris.length(); mesh->tris = mtris; mesh->calctangents(); } }; struct smdvertkey : vert { smdmeshdata *mesh; smdvertkey(smdmeshdata *mesh) : mesh(mesh) {} }; void readtriangles(stream *f, char *buf, size_t bufsize) { smdmeshdata *curmesh = nullptr; hashtable materials(1<<6); hashset verts(1<<12); while(f->getline(buf, bufsize)) { char *curbuf = buf; if(skipcomment(curbuf)) continue; if(!strncmp(curbuf, "end", 3)) break; string material; readmaterial(curbuf, material, sizeof(material)); if(!curmesh || strcmp(curmesh->mesh->name, material)) { curmesh = materials.access(material); if(!curmesh) { smdmesh *m = new smdmesh; m->group = this; m->name = newstring(material); meshes.add(m); curmesh = &materials[m->name]; curmesh->mesh = m; } } tri curtri; loopi(3) { char *curbuf; do { if(!f->getline(buf, bufsize)) goto endsection; curbuf = buf; } while(skipcomment(curbuf)); smdvertkey key(curmesh); int parent = -1, numlinks = 0, len = 0; if(sscanf(curbuf, " %d %f %f %f %f %f %f %f %f %d%n", &parent, &key.pos.x, &key.pos.y, &key.pos.z, &key.norm.x, &key.norm.y, &key.norm.z, &key.tc.x, &key.tc.y, &numlinks, &len) < 9) goto endsection; curbuf += len; key.pos.y = -key.pos.y; key.norm.y = -key.norm.y; key.tc.y = 1 - key.tc.y; blendcombo c; int sorted = 0; float pweight = 0, tweight = 0; for(; numlinks > 0; numlinks--) { int bone = -1, len = 0; float weight = 0; if(sscanf(curbuf, " %d %f%n", &bone, &weight, &len) < 2) break; curbuf += len; tweight += weight; if(bone == parent) pweight += weight; else sorted = c.addweight(sorted, weight, bone); } if(tweight < 1) pweight += 1 - tweight; if(pweight > 0) sorted = c.addweight(sorted, pweight, parent); c.finalize(sorted); key.blend = curmesh->mesh->addblendcombo(c); int index = verts.access(key, curmesh->verts.length()); if(index == curmesh->verts.length()) curmesh->verts.add(key); curtri.vert[2-i] = index; } curmesh->tris.add(curtri); } endsection: enumerate(materials, smdmeshdata, data, data.finalize()); } void readskeleton(stream *f, char *buf, size_t bufsize) { int frame = -1; while(f->getline(buf, bufsize)) { char *curbuf = buf; if(skipcomment(curbuf)) continue; if(sscanf(curbuf, " time %d", &frame) == 1) continue; else if(!strncmp(curbuf, "end", 3)) break; else if(frame != 0) continue; int bone; vec pos, rot; if(sscanf(curbuf, " %d %f %f %f %f %f %f", &bone, &pos.x, &pos.y, &pos.z, &rot.x, &rot.y, &rot.z) != 7) continue; if(bone < 0 || bone >= skel->numbones) continue; rot.x = -rot.x; rot.z = -rot.z; float cx = cosf(rot.x/2), sx = sinf(rot.x/2), cy = cosf(rot.y/2), sy = sinf(rot.y/2), cz = cosf(rot.z/2), sz = sinf(rot.z/2); pos.y = -pos.y; dualquat dq(quat(sx*cy*cz - cx*sy*sz, cx*sy*cz + sx*cy*sz, cx*cy*sz - sx*sy*cz, cx*cy*cz + sx*sy*sz), pos); boneinfo &b = skel->bones[bone]; if(b.parent < 0) b.base = dq; else b.base.mul(skel->bones[b.parent].base, dq); (b.invbase = b.base).invert(); } } bool loadmesh(const char *filename) { stream *f = openfile(filename, "r"); if(!f) return false; char buf[512]; int version = -1; while(f->getline(buf, sizeof(buf))) { char *curbuf = buf; if(skipcomment(curbuf)) continue; if(sscanf(curbuf, " version %d", &version) == 1) { if(version != 1) { delete f; return false; } } else if(!strncmp(curbuf, "nodes", 5)) { if(skel->numbones > 0) { skipsection(f, buf, sizeof(buf)); continue; } vector bones; readnodes(f, buf, sizeof(buf), bones); if(bones.empty()) continue; skel->numbones = bones.length(); skel->bones = new boneinfo[skel->numbones]; loopv(bones) { boneinfo &dst = skel->bones[i]; smdbone &src = bones[i]; dst.name = newstring(src.name); dst.parent = src.parent; } skel->linkchildren(); } else if(!strncmp(curbuf, "triangles", 9)) readtriangles(f, buf, sizeof(buf)); else if(!strncmp(curbuf, "skeleton", 8)) { if(skel->shared > 1) skipsection(f, buf, sizeof(buf)); else readskeleton(f, buf, sizeof(buf)); } else if(!strncmp(curbuf, "vertexanimation", 15)) skipsection(f, buf, sizeof(buf)); } sortblendcombos(); delete f; return true; } int readframes(stream *f, char *buf, size_t bufsize, vector &bones, vector &animbones) { int frame = -1, numframes = 0, lastbone = skel->numbones; while(f->getline(buf, bufsize)) { char *curbuf = buf; if(skipcomment(curbuf)) continue; int nextframe = -1; if(sscanf(curbuf, " time %d", &nextframe) == 1) { for(; lastbone < skel->numbones; lastbone++) animbones[frame*skel->numbones + lastbone] = animbones[lastbone]; if(nextframe >= numframes) { databuf framebones = animbones.reserve(skel->numbones * (nextframe + 1 - numframes)); loopi(nextframe - numframes) framebones.put(animbones.getbuf(), skel->numbones); animbones.addbuf(framebones); animbones.advance(skel->numbones); numframes = nextframe + 1; } frame = nextframe; lastbone = 0; continue; } else if(!strncmp(curbuf, "end", 3)) break; int bone; vec pos, rot; if(sscanf(curbuf, " %d %f %f %f %f %f %f", &bone, &pos.x, &pos.y, &pos.z, &rot.x, &rot.y, &rot.z) != 7) continue; if(bone < 0 || bone >= skel->numbones) continue; for(; lastbone < bone; lastbone++) animbones[frame*skel->numbones + lastbone] = animbones[lastbone]; lastbone++; float cx = cosf(rot.x/2), sx = sinf(rot.x/2), cy = cosf(rot.y/2), sy = sinf(rot.y/2), cz = cosf(rot.z/2), sz = sinf(rot.z/2); pos.y = -pos.y; dualquat dq(quat(-(sx*cy*cz - cx*sy*sz), cx*sy*cz + sx*cy*sz, -(cx*cy*sz - sx*sy*cz), cx*cy*cz + sx*sy*sz), pos); if(adjustments.inrange(bone)) adjustments[bone].adjust(dq); smdbone &h = bones[bone]; boneinfo &b = skel->bones[bone]; dq.mul(b.invbase); dualquat &dst = animbones[frame*skel->numbones + bone]; if(h.parent < 0) dst = dq; else dst.mul(skel->bones[h.parent].base, dq); dst.fixantipodal(skel->numframes > 0 ? skel->framebones[bone] : animbones[bone]); } for(; lastbone < skel->numbones; lastbone++) animbones[frame*skel->numbones + lastbone] = animbones[lastbone]; return numframes; } skelanimspec *loadanim(const char *filename) { skelanimspec *sa = skel->findskelanim(filename); if(sa || skel->numbones <= 0) return sa; stream *f = openfile(filename, "r"); if(!f) return nullptr; char buf[512]; int version = -1; vector bones; vector animbones; while(f->getline(buf, sizeof(buf))) { char *curbuf = buf; if(skipcomment(curbuf)) continue; if(sscanf(curbuf, " version %d", &version) == 1) { if(version != 1) { delete f; return nullptr; } } else if(!strncmp(curbuf, "nodes", 5)) { readnodes(f, buf, sizeof(buf), bones); if(bones.length() != skel->numbones) { delete f; return nullptr; } } else if(!strncmp(curbuf, "triangles", 9)) skipsection(f, buf, sizeof(buf)); else if(!strncmp(curbuf, "skeleton", 8)) readframes(f, buf, sizeof(buf), bones, animbones); else if(!strncmp(curbuf, "vertexanimation", 15)) skipsection(f, buf, sizeof(buf)); } int numframes = animbones.length() / skel->numbones; dualquat *framebones = new dualquat[(skel->numframes+numframes)*skel->numbones]; if(skel->framebones) { memcpy(framebones, skel->framebones, skel->numframes*skel->numbones*sizeof(dualquat)); delete[] skel->framebones; } memcpy(&framebones[skel->numframes*skel->numbones], animbones.getbuf(), numframes*skel->numbones*sizeof(dualquat)); skel->framebones = framebones; sa = &skel->addskelanim(filename); sa->frame = skel->numframes; sa->range = numframes; skel->numframes += numframes; delete f; return sa; } bool load(const char *meshfile, float smooth) { name = newstring(meshfile); return loadmesh(meshfile); } }; skelmeshgroup *newmeshes() { return new smdmeshgroup; } 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.smd", name, fname); mdl.meshes = sharemeshes(path(meshname)); if(!mdl.meshes) return false; mdl.initanimparts(); mdl.initskins(); return true; } }; static inline uint hthash(const smd::smdmeshgroup::smdvertkey &k) { return hthash(k.pos); } static inline bool htcmp(const smd::smdmeshgroup::smdvertkey &k, int index) { if(!k.mesh->verts.inrange(index)) return false; const smd::vert &v = k.mesh->verts[index]; return k.pos == v.pos && k.norm == v.norm && k.tc == v.tc && k.blend == v.blend; } skelcommands smdcommands;