415 lines
15 KiB
C++
415 lines
15 KiB
C++
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>
|
|
{
|
|
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<md5joint> &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<numverts) vertinfo[index] = v;
|
|
}
|
|
else if(sscanf(buf, " tri %d %hu %hu %hu", &index, &t.vert[0], &t.vert[1], &t.vert[2])==4)
|
|
{
|
|
if(index>=0 && index<numtris) tris[index] = t;
|
|
}
|
|
else if(sscanf(buf, " weight %d %d %f ( %f %f %f ) ", &index, &w.joint, &w.bias, &w.pos.x, &w.pos.y, &w.pos.z)==6)
|
|
{
|
|
w.pos.y = -w.pos.y;
|
|
if(index>=0 && index<numweights) weightinfo[index] = w;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
struct md5meshgroup : skelmeshgroup
|
|
{
|
|
md5meshgroup()
|
|
{
|
|
}
|
|
|
|
bool loadmesh(const char *filename, float smooth)
|
|
{
|
|
stream *f = openfile(filename, "r");
|
|
if(!f) return false;
|
|
|
|
char buf[512];
|
|
vector<md5joint> 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()<skel->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<md5hierarchy> hierarchy;
|
|
vector<md5joint> 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<md5> md5commands;
|
|
|