440 lines
16 KiB
C++
440 lines
16 KiB
C++
struct smd;
|
|
|
|
struct smdbone
|
|
{
|
|
string name;
|
|
int parent;
|
|
smdbone() : parent(-1) { name[0] = '\0'; }
|
|
};
|
|
|
|
struct smd : skelloader<smd>
|
|
{
|
|
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<smdbone> &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<vert> verts;
|
|
vector<tri> 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 = NULL;
|
|
hashtable<const char *, smdmeshdata> materials(1<<6);
|
|
hashset<int> 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<smdbone> 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<smdbone> &bones, vector<dualquat> &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<dualquat> 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 NULL;
|
|
|
|
char buf[512];
|
|
int version = -1;
|
|
vector<smdbone> bones;
|
|
vector<dualquat> 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 NULL; }
|
|
}
|
|
else if(!strncmp(curbuf, "nodes", 5))
|
|
{
|
|
readnodes(f, buf, sizeof(buf), bones);
|
|
if(bones.length() != skel->numbones) { delete f; return NULL; }
|
|
}
|
|
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<smd> smdcommands;
|
|
|