struct iqm; struct iqmheader { char magic[16]; uint version; uint filesize; uint flags; uint num_text, ofs_text; uint num_meshes, ofs_meshes; uint num_vertexarrays, num_vertexes, ofs_vertexarrays; uint num_triangles, ofs_triangles, ofs_adjacency; uint num_joints, ofs_joints; uint num_poses, ofs_poses; uint num_anims, ofs_anims; uint num_frames, num_framechannels, ofs_frames, ofs_bounds; uint num_comment, ofs_comment; uint num_extensions, ofs_extensions; }; struct iqmmesh { uint name; uint material; uint first_vertex, num_vertexes; uint first_triangle, num_triangles; }; enum { IQM_POSITION = 0, IQM_TEXCOORD = 1, IQM_NORMAL = 2, IQM_TANGENT = 3, IQM_BLENDINDEXES = 4, IQM_BLENDWEIGHTS = 5, IQM_COLOR = 6, IQM_CUSTOM = 0x10 }; enum { IQM_BYTE = 0, IQM_UBYTE = 1, IQM_SHORT = 2, IQM_USHORT = 3, IQM_INT = 4, IQM_UINT = 5, IQM_HALF = 6, IQM_FLOAT = 7, IQM_DOUBLE = 8, }; struct iqmtriangle { uint vertex[3]; }; struct iqmjoint { uint name; int parent; vec pos; quat orient; vec size; }; struct iqmpose { int parent; uint mask; vec offsetpos; vec4 offsetorient; vec offsetsize; vec scalepos; vec4 scaleorient; vec scalesize; }; struct iqmanim { uint name; uint first_frame, num_frames; float framerate; uint flags; }; struct iqmvertexarray { uint type; uint flags; uint format; uint size; uint offset; }; struct iqm : skelloader { iqm(const char *name) : skelloader(name) {} static const char *formatname() { return "iqm"; } int type() const { return MDL_IQM; } struct iqmmeshgroup : skelmeshgroup { iqmmeshgroup() { } bool loadiqmmeshes(const char *filename, const iqmheader &hdr, uchar *buf) { lilswap((uint *)&buf[hdr.ofs_vertexarrays], hdr.num_vertexarrays*sizeof(iqmvertexarray)/sizeof(uint)); lilswap((uint *)&buf[hdr.ofs_triangles], hdr.num_triangles*sizeof(iqmtriangle)/sizeof(uint)); lilswap((uint *)&buf[hdr.ofs_meshes], hdr.num_meshes*sizeof(iqmmesh)/sizeof(uint)); lilswap((uint *)&buf[hdr.ofs_joints], hdr.num_joints*sizeof(iqmjoint)/sizeof(uint)); const char *str = hdr.ofs_text ? (char *)&buf[hdr.ofs_text] : ""; float *vpos = nullptr, *vnorm = nullptr, *vtan = nullptr, *vtc = nullptr; uchar *vindex = nullptr, *vweight = nullptr; iqmvertexarray *vas = (iqmvertexarray *)&buf[hdr.ofs_vertexarrays]; loopi(hdr.num_vertexarrays) { iqmvertexarray &va = vas[i]; switch(va.type) { case IQM_POSITION: if(va.format != IQM_FLOAT || va.size != 3) return false; vpos = (float *)&buf[va.offset]; lilswap(vpos, 3*hdr.num_vertexes); break; case IQM_NORMAL: if(va.format != IQM_FLOAT || va.size != 3) return false; vnorm = (float *)&buf[va.offset]; lilswap(vnorm, 3*hdr.num_vertexes); break; case IQM_TANGENT: if(va.format != IQM_FLOAT || va.size != 4) return false; vtan = (float *)&buf[va.offset]; lilswap(vtan, 4*hdr.num_vertexes); break; case IQM_TEXCOORD: if(va.format != IQM_FLOAT || va.size != 2) return false; vtc = (float *)&buf[va.offset]; lilswap(vtc, 2*hdr.num_vertexes); break; case IQM_BLENDINDEXES: if(va.format != IQM_UBYTE || va.size != 4) return false; vindex = (uchar *)&buf[va.offset]; break; case IQM_BLENDWEIGHTS: if(va.format != IQM_UBYTE || va.size != 4) return false; vweight = (uchar *)&buf[va.offset]; break; } } if(!vpos) return false; iqmtriangle *tris = (iqmtriangle *)&buf[hdr.ofs_triangles]; iqmmesh *imeshes = (iqmmesh *)&buf[hdr.ofs_meshes]; iqmjoint *joints = (iqmjoint *)&buf[hdr.ofs_joints]; if(hdr.num_joints) { if(skel->numbones <= 0) { skel->numbones = hdr.num_joints; skel->bones = new boneinfo[skel->numbones]; loopi(hdr.num_joints) { iqmjoint &j = joints[i]; boneinfo &b = skel->bones[i]; if(!b.name) b.name = newstring(&str[j.name]); b.parent = j.parent; if(skel->shared <= 1) { j.pos.y = -j.pos.y; j.orient.x = -j.orient.x; j.orient.z = -j.orient.z; j.orient.normalize(); b.base = dualquat(j.orient, j.pos); if(b.parent >= 0) b.base.mul(skel->bones[b.parent].base, dualquat(b.base)); (b.invbase = b.base).invert(); } } } if(skel->shared <= 1) skel->linkchildren(); } loopi(hdr.num_meshes) { iqmmesh &im = imeshes[i]; skelmesh *m = new skelmesh; m->group = this; meshes.add(m); m->name = newstring(&str[im.name]); m->numverts = im.num_vertexes; int noblend = -1; if(m->numverts) { m->verts = new vert[m->numverts]; if(!vindex || !vweight) { blendcombo c; c.finalize(0); noblend = m->addblendcombo(c); } } int fv = im.first_vertex; float *mpos = vpos + 3*fv, *mnorm = vnorm ? vnorm + 3*fv : nullptr, *mtan = vtan ? vtan + 4*fv : nullptr, *mtc = vtc ? vtc + 2*fv : nullptr; uchar *mindex = vindex ? vindex + 4*fv : nullptr, *mweight = vweight ? vweight + 4*fv : nullptr; loopj(im.num_vertexes) { vert &v = m->verts[j]; v.pos = vec(mpos[0], -mpos[1], mpos[2]); mpos += 3; if(mtc) { v.tc = vec2(mtc[0], mtc[1]); mtc += 2; } else v.tc = vec2(0, 0); if(mnorm) { v.norm = vec(mnorm[0], -mnorm[1], mnorm[2]); mnorm += 3; if(mtan) { m->calctangent(v, v.norm, vec(mtan[0], -mtan[1], mtan[2]), mtan[3]); mtan += 4; } } else { v.norm = vec(0, 0, 0); v.tangent = quat(0, 0, 0, 1); } if(noblend < 0) { blendcombo c; int sorted = 0; loopk(4) sorted = c.addweight(sorted, mweight[k], mindex[k]); mweight += 4; mindex += 4; c.finalize(sorted); v.blend = m->addblendcombo(c); } else v.blend = noblend; } m->numtris = im.num_triangles; if(m->numtris) m->tris = new tri[m->numtris]; iqmtriangle *mtris = tris + im.first_triangle; loopj(im.num_triangles) { tri &t = m->tris[j]; t.vert[0] = mtris->vertex[0] - fv; t.vert[1] = mtris->vertex[1] - fv; t.vert[2] = mtris->vertex[2] - fv; ++mtris; } if(!m->numtris || !m->numverts) { conoutf(CON_WARN, "empty mesh in %s", filename); meshes.removeobj(m); delete m; continue; } if(vnorm && !vtan) m->calctangents(); } sortblendcombos(); return true; } bool loadiqmanims(const char *filename, const iqmheader &hdr, uchar *buf) { lilswap((uint *)&buf[hdr.ofs_poses], hdr.num_poses*sizeof(iqmpose)/sizeof(uint)); lilswap((uint *)&buf[hdr.ofs_anims], hdr.num_anims*sizeof(iqmanim)/sizeof(uint)); lilswap((ushort *)&buf[hdr.ofs_frames], hdr.num_frames*hdr.num_framechannels); const char *str = hdr.ofs_text ? (char *)&buf[hdr.ofs_text] : ""; iqmpose *poses = (iqmpose *)&buf[hdr.ofs_poses]; iqmanim *anims = (iqmanim *)&buf[hdr.ofs_anims]; ushort *frames = (ushort *)&buf[hdr.ofs_frames]; loopi(hdr.num_anims) { iqmanim &a = anims[i]; string name; copystring(name, filename); concatstring(name, ":"); concatstring(name, &str[a.name]); skelanimspec *sa = skel->findskelanim(name); if(sa) continue; sa = &skel->addskelanim(name); sa->frame = skel->numframes; sa->range = a.num_frames; dualquat *animbones = new dualquat[(skel->numframes+a.num_frames)*skel->numbones]; if(skel->bones) { memcpy(animbones, skel->framebones, skel->numframes*skel->numbones*sizeof(dualquat)); delete[] skel->framebones; } skel->framebones = animbones; animbones += skel->numframes*skel->numbones; skel->numframes += a.num_frames; ushort *animdata = &frames[a.first_frame*hdr.num_framechannels]; loopj(a.num_frames) { dualquat *frame = &animbones[j*skel->numbones]; loopk(skel->numbones) { iqmpose &p = poses[k]; vec pos; quat orient; pos.x = p.offsetpos.x; if(p.mask&0x01) pos.x += *animdata++ * p.scalepos.x; pos.y = -p.offsetpos.y; if(p.mask&0x02) pos.y -= *animdata++ * p.scalepos.y; pos.z = p.offsetpos.z; if(p.mask&0x04) pos.z += *animdata++ * p.scalepos.z; orient.x = -p.offsetorient.x; if(p.mask&0x08) orient.x -= *animdata++ * p.scaleorient.x; orient.y = p.offsetorient.y; if(p.mask&0x10) orient.y += *animdata++ * p.scaleorient.y; orient.z = -p.offsetorient.z; if(p.mask&0x20) orient.z -= *animdata++ * p.scaleorient.z; orient.w = p.offsetorient.w; if(p.mask&0x40) orient.w += *animdata++ * p.scaleorient.w; orient.normalize(); if(p.mask&0x380) { if(p.mask&0x80) animdata++; if(p.mask&0x100) animdata++; if(p.mask&0x200) animdata++; } dualquat dq(orient, pos); if(adjustments.inrange(k)) adjustments[k].adjust(dq); boneinfo &b = skel->bones[k]; dq.mul(b.invbase); dualquat &dst = frame[k]; if(p.parent < 0) dst = dq; else dst.mul(skel->bones[p.parent].base, dq); dst.fixantipodal(skel->framebones[k]); } } } return true; } bool loadiqm(const char *filename, bool doloadmesh, bool doloadanim) { stream *f = openfile(filename, "rb"); if(!f) return false; uchar *buf = nullptr; iqmheader hdr; if(f->read(&hdr, sizeof(hdr)) != sizeof(hdr) || memcmp(hdr.magic, "INTERQUAKEMODEL", sizeof(hdr.magic))) goto error; lilswap(&hdr.version, (sizeof(hdr) - sizeof(hdr.magic))/sizeof(uint)); if(hdr.version != 2) goto error; if(hdr.filesize > (16<<20)) goto error; // sanity check... don't load files bigger than 16 MB try { buf = new uchar[hdr.filesize]; } catch (...) { goto error; } if(!buf || f->read(buf + sizeof(hdr), hdr.filesize - sizeof(hdr)) != hdr.filesize - sizeof(hdr)) goto error; if(doloadmesh && !loadiqmmeshes(filename, hdr, buf)) goto error; if(doloadanim && !loadiqmanims(filename, hdr, buf)) goto error; delete[] buf; delete f; return true; error: if(buf) delete[] buf; delete f; return false; } bool load(const char *filename, float smooth) { name = newstring(filename); return loadiqm(filename, true, false); } skelanimspec *loadanim(const char *animname) { const char *sep = strchr(animname, ':'); skelanimspec *sa = skel->findskelanim(animname, sep ? '\0' : ':'); if(!sa) { string filename; copystring(filename, animname); if(sep) filename[sep - animname] = '\0'; if(loadiqm(filename, false, true)) sa = skel->findskelanim(animname, sep ? '\0' : ':'); } return sa; } }; skelmeshgroup *newmeshes() { return new iqmmeshgroup; } 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.iqm", name, fname); mdl.meshes = sharemeshes(path(meshname)); if(!mdl.meshes) return false; mdl.initanimparts(); mdl.initskins(); return true; } }; skelcommands iqmcommands;