struct md3; md3 *loadingmd3 = NULL; string md3dir; struct md3tag { char name[64]; vec pos; float rotation[3][3]; }; struct md3vertex { short vertex[3]; short normal; }; struct md3triangle { int vertexindices[3]; }; struct md3header { char id[4]; int version; char name[64]; int flags; int numframes, numtags, nummeshes, numskins; int ofs_frames, ofs_tags, ofs_meshes, ofs_eof; // offsets }; struct md3meshheader { char id[4]; char name[64]; int flags; int numframes, numshaders, numvertices, numtriangles; int ofs_triangles, ofs_shaders, ofs_uv, ofs_vertices, meshsize; // offsets }; struct md3 : vertmodel { md3(const char *name) : vertmodel(name) {} int type() { return MDL_MD3; } struct md3part : part { bool load(char *path) { if(filename) return true; stream *f = openfile(path, "rb"); if(!f) return false; md3header header; f->read(&header, sizeof(md3header)); lilswap(&header.version, 1); lilswap(&header.flags, 9); if(strncmp(header.id, "IDP3", 4) != 0 || header.version != 15) // header check { delete f; conoutf("md3: corrupted header"); return false; } numframes = header.numframes; numtags = header.numtags; if(numtags) { tags = new tag[numframes*numtags]; f->seek(header.ofs_tags, SEEK_SET); md3tag tag; loopi(header.numframes*header.numtags) { f->read(&tag, sizeof(md3tag)); lilswap((float *)&tag.pos, 12); if(tag.name[0] && i<header.numtags) tags[i].name = newstring(tag.name); tags[i].pos = vec(tag.pos.y, tag.pos.x, tag.pos.z); memcpy(tags[i].transform, tag.rotation, sizeof(tag.rotation)); // undo the x/y swap loopj(3) swap(tags[i].transform[0][j], tags[i].transform[1][j]); // then restore it loopj(3) swap(tags[i].transform[j][0], tags[i].transform[j][1]); } links = new linkedpart[numtags]; } int mesh_offset = header.ofs_meshes; loopi(header.nummeshes) { md3meshheader mheader; f->seek(mesh_offset, SEEK_SET); f->read(&mheader, sizeof(md3meshheader)); lilswap(&mheader.flags, 10); if(mheader.numtriangles <= 0) { mesh_offset += mheader.meshsize; continue; } mesh &m = *meshes.add(new mesh); m.owner = this; m.name = newstring(mheader.name); m.numtris = mheader.numtriangles; m.tris = new tri[m.numtris]; f->seek(mesh_offset + mheader.ofs_triangles, SEEK_SET); loopj(mheader.numtriangles) { md3triangle tri; f->read(&tri, sizeof(md3triangle)); // read the triangles lilswap((int *)&tri, 3); loopk(3) m.tris[j].vert[k] = (ushort)tri.vertexindices[k]; } m.numverts = mheader.numvertices; m.tcverts = new tcvert[m.numverts]; f->seek(mesh_offset + mheader.ofs_uv , SEEK_SET); f->read(m.tcverts, 2*sizeof(float)*m.numverts); // read the UV data lilswap(&m.tcverts[0].u, 2*m.numverts); m.verts = new vec[numframes*m.numverts + 1]; f->seek(mesh_offset + mheader.ofs_vertices, SEEK_SET); loopj(numframes*mheader.numvertices) { md3vertex v; f->read(&v, sizeof(md3vertex)); // read the vertices lilswap((short *)&v, 4); m.verts[j].x = v.vertex[1]/64.0f; m.verts[j].y = v.vertex[0]/64.0f; m.verts[j].z = v.vertex[2]/64.0f; } mesh_offset += mheader.meshsize; } delete f; filename = newstring(path); return true; } void begingenshadow() { matrixpos = 0; matrixstack[0].identity(); matrixstack[0].rotate_around_z(180*RAD); } }; void render(int anim, int varseed, float speed, int basetime, const vec &o, float yaw, float pitch, dynent *d, modelattach *a, float scale, float zoomed) { if(!loaded) return; if(a) for(int i = 0; a[i].tag; i++) { vertmodel *m = (vertmodel *)a[i].m; if(!m) { if(a[i].pos) link(NULL, a[i].tag, a[i].pos); continue; } part *p = m->parts[0]; if(link(p, a[i].tag, a[i].pos)) p->index = parts.length()+i; } if(!cullface) glDisable(GL_CULL_FACE); else if(anim&ANIM_MIRROR) glCullFace(GL_BACK); if(stenciling) { shadowdir = vec(0, 1/SQRT2, -1/SQRT2); shadowdir.rotate_around_z((-shadowyaw-yaw-180.0f)*RAD); shadowdir.rotate_around_y(-pitch*RAD); (shadowpos = shadowdir).mul(shadowdist); } modelpos = o; modelyaw = yaw; modelpitch = pitch; matrixpos = 0; matrixstack[0].identity(); matrixstack[0].translate(o); matrixstack[0].rotate_around_z((yaw+180)*RAD); matrixstack[0].rotate_around_y(-pitch*RAD); if(anim&ANIM_MIRROR || scale!=1) matrixstack[0].scale(scale, anim&ANIM_MIRROR ? -scale : scale, scale); parts[0]->render(anim, varseed, speed, basetime, d, zoomed); if(!cullface) glEnable(GL_CULL_FACE); else if(anim&ANIM_MIRROR) glCullFace(GL_FRONT); if(a) for(int i = 0; a[i].tag; i++) link(NULL, a[i].tag); if(d) d->lastrendered = lastmillis; } void rendershadow(int anim, int varseed, float speed, int basetime, const vec &o, float yaw, modelattach *a) { if(parts.length()>1) return; parts[0]->rendershadow(anim, varseed, speed, basetime, o, yaw); } bool load() { if(loaded) return true; formatstring(md3dir)("packages/models/%s", loadname); const char *pname = parentdir(loadname); defformatstring(cfgname)("packages/models/%s/md3.cfg", loadname); loadingmd3 = this; per_idents = false; neverpersist = true; if(execfile(cfgname) && parts.length()) // configured md3, will call the md3* commands below { neverpersist = false; per_idents = true; loadingmd3 = NULL; if(parts.empty()) return false; loopv(parts) if(!parts[i]->filename) return false; } else // md3 without configuration, try default tris and skin { per_idents = false; loadingmd3 = NULL; md3part &mdl = *new md3part; parts.add(&mdl); mdl.model = this; mdl.index = 0; defformatstring(name1)("packages/models/%s/tris.md3", loadname); if(!mdl.load(path(name1))) { formatstring(name1)("packages/models/%s/tris.md3", pname); // try md3 in parent folder (vert sharing) if(!mdl.load(path(name1))) return false; }; Texture *skin; loadskin(loadname, pname, skin); loopv(mdl.meshes) mdl.meshes[i]->skin = skin; if(skin==notexture) conoutf("could not load model skin for %s", name1); } loopv(parts) parts[i]->scaleverts(scale/16.0f, vec(translate.x, -translate.y, translate.z)); radius = calcradius(); if(shadowdist) calcneighbors(); calcbbs(); return (loaded = true); } }; void md3load(char *model) { if(!loadingmd3) { conoutf("not loading an md3"); return; }; defformatstring(filename)("%s/%s", md3dir, model); md3::md3part &mdl = *new md3::md3part; loadingmd3->parts.add(&mdl); mdl.model = loadingmd3; mdl.index = loadingmd3->parts.length()-1; if(!mdl.load(path(filename))) conoutf("could not load %s", filename); // ignore failure } void md3skin(char *objname, char *skin) { if(!objname || !skin) return; if(!loadingmd3 || loadingmd3->parts.empty()) { conoutf("not loading an md3"); return; }; md3::part &mdl = *loadingmd3->parts.last(); loopv(mdl.meshes) { md3::mesh &m = *mdl.meshes[i]; if(!strcmp(objname, "*") || !strcmp(m.name, objname)) { defformatstring(spath)("%s/%s", md3dir, skin); m.skin = textureload(spath); } } } void md3anim(char *anim, int *startframe, int *range, float *speed) { if(!loadingmd3 || loadingmd3->parts.empty()) { conoutf("not loading an md3"); return; }; int num = findanim(anim); if(num<0) { conoutf("could not find animation %s", anim); return; }; loadingmd3->parts.last()->setanim(num, *startframe, *range, *speed); } void md3link(int *parent, int *child, char *tagname) { if(!loadingmd3) { conoutf("not loading an md3"); return; }; if(!loadingmd3->parts.inrange(*parent) || !loadingmd3->parts.inrange(*child)) { conoutf("no models loaded to link"); return; } if(!loadingmd3->parts[*parent]->link(loadingmd3->parts[*child], tagname)) conoutf("could not link model %s", loadingmd3->loadname); } void md3emit(char *tag, int *type, int *arg1, int *arg2) { if(!loadingmd3 || loadingmd3->parts.empty()) { conoutf("not loading an md3"); return; }; md3::part &mdl = *loadingmd3->parts.last(); if(!mdl.addemitter(tag, *type, *arg1, *arg2)) { conoutf("could not find tag %s", tag); return; } } COMMAND(md3load, "s"); COMMAND(md3skin, "ss"); COMMAND(md3anim, "siif"); COMMAND(md3link, "iis"); COMMAND(md3emit, "siii");