summaryrefslogtreecommitdiff
path: root/src/engine/skelmodel.h
diff options
context:
space:
mode:
authorxolatile2025-08-04 22:53:42 +0200
committerxolatile2025-08-04 22:53:42 +0200
commitd309df4ce4d8ad0ed995a8e1c4267412a7782021 (patch)
tree999ca8d785ecc1681e5eb7538ce2e6a18d244fa5 /src/engine/skelmodel.h
parent29d613d9cb65a0faa7e3f80e75bea0b6d910cb9a (diff)
downloadxolatile-badassbug-d309df4ce4d8ad0ed995a8e1c4267412a7782021.tar.xz
xolatile-badassbug-d309df4ce4d8ad0ed995a8e1c4267412a7782021.tar.zst
Bunch of small changes...
Diffstat (limited to 'src/engine/skelmodel.h')
-rw-r--r--src/engine/skelmodel.h3664
1 files changed, 1832 insertions, 1832 deletions
diff --git a/src/engine/skelmodel.h b/src/engine/skelmodel.h
index 6d36b35..53d586d 100644
--- a/src/engine/skelmodel.h
+++ b/src/engine/skelmodel.h
@@ -9,1853 +9,1853 @@ VAR(testtags, 0, 0, 1);
struct skelmodel : animmodel
{
- struct vert { vec pos, norm; vec2 tc; int blend, interpindex; };
- struct vvert { vec pos; vec2 tc; };
- struct vvertn : vvert { vec norm; };
- struct vvertbump : vvert { squat tangent; };
- struct vvertw { uchar weights[4]; uchar bones[4]; };
- struct vvertnw : vvertn, vvertw {};
- struct vvertbumpw : vvertbump, vvertw {};
- struct bumpvert { quat tangent; };
- struct tri { ushort vert[3]; };
-
- struct blendcombo
- {
- int uses, interpindex;
- float weights[4];
- uchar bones[4], interpbones[4];
-
- blendcombo() : uses(1)
- {
- }
-
- bool operator==(const blendcombo &c) const
- {
- loopk(4) if(bones[k] != c.bones[k]) return false;
- loopk(4) if(weights[k] != c.weights[k]) return false;
- return true;
- }
-
- int size() const
- {
- int i = 1;
- while(i < 4 && weights[i]) i++;
- return i;
- }
-
- static bool sortcmp(const blendcombo &x, const blendcombo &y)
- {
- loopi(4)
- {
- if(x.weights[i])
- {
- if(!y.weights[i]) return true;
- }
- else if(y.weights[i]) return false;
- else break;
- }
- return false;
- }
-
- int addweight(int sorted, float weight, int bone)
- {
- if(weight <= 1e-3f) return sorted;
- loopk(sorted) if(weight > weights[k])
- {
- for(int l = min(sorted-1, 2); l >= k; l--)
- {
- weights[l+1] = weights[l];
- bones[l+1] = bones[l];
- }
- weights[k] = weight;
- bones[k] = bone;
- return sorted<4 ? sorted+1 : sorted;
- }
- if(sorted>=4) return sorted;
- weights[sorted] = weight;
- bones[sorted] = bone;
- return sorted+1;
- }
-
- void finalize(int sorted)
- {
- loopj(4-sorted) { weights[sorted+j] = 0; bones[sorted+j] = 0; }
- if(sorted <= 0) return;
- float total = 0;
- loopj(sorted) total += weights[j];
- total = 1.0f/total;
- loopj(sorted) weights[j] *= total;
- }
-
- void serialize(vvertw &v)
- {
- if(interpindex >= 0)
- {
- v.weights[0] = 255;
- loopk(3) v.weights[k+1] = 0;
- v.bones[0] = 2*interpindex;
- loopk(3) v.bones[k+1] = v.bones[0];
- }
- else
- {
- int total = 0;
- loopk(4) total += (v.weights[k] = uchar(0.5f + weights[k]*255));
- while(total > 255)
- {
- loopk(4) if(v.weights[k] > 0 && total > 255) { v.weights[k]--; total--; }
- }
- while(total < 255)
- {
- loopk(4) if(v.weights[k] < 255 && total < 255) { v.weights[k]++; total++; }
- }
- loopk(4) v.bones[k] = 2*interpbones[k];
- }
- }
- };
-
-
- struct animcacheentry
- {
- animstate as[MAXANIMPARTS];
- float pitch;
- int millis;
- uchar *partmask;
- ragdolldata *ragdoll;
-
- animcacheentry() : ragdoll(NULL)
- {
- loopk(MAXANIMPARTS) as[k].cur.fr1 = as[k].prev.fr1 = -1;
- }
-
- bool operator==(const animcacheentry &c) const
- {
- loopi(MAXANIMPARTS) if(as[i]!=c.as[i]) return false;
- return pitch==c.pitch && partmask==c.partmask && ragdoll==c.ragdoll && (!ragdoll || min(millis, c.millis) >= ragdoll->lastmove);
- }
- };
-
- struct vbocacheentry : animcacheentry
- {
- GLuint vbuf;
- int owner;
-
- vbocacheentry() : vbuf(0), owner(-1) {}
- };
-
- struct skelcacheentry : animcacheentry
- {
- dualquat *bdata;
- int version;
- bool dirty;
-
- skelcacheentry() : bdata(NULL), version(-1), dirty(false) {}
-
- void nextversion()
- {
- version = Shader::uniformlocversion();
- dirty = true;
- }
- };
-
- struct blendcacheentry : skelcacheentry
- {
- int owner;
-
- blendcacheentry() : owner(-1) {}
- };
-
- struct skelmeshgroup;
-
- struct skelmesh : mesh
- {
- vert *verts;
- bumpvert *bumpverts;
- tri *tris;
- int numverts, numtris, maxweights;
-
- int voffset, eoffset, elen;
- ushort minvert, maxvert;
-
- skelmesh() : verts(NULL), bumpverts(NULL), tris(NULL), numverts(0), numtris(0), maxweights(0)
- {
- }
-
- virtual ~skelmesh()
- {
- DELETEA(verts);
- DELETEA(bumpverts);
- DELETEA(tris);
- }
-
- int addblendcombo(const blendcombo &c)
- {
- maxweights = max(maxweights, c.size());
- return ((skelmeshgroup *)group)->addblendcombo(c);
- }
-
- void smoothnorms(float limit = 0, bool areaweight = true)
- {
- mesh::smoothnorms(verts, numverts, tris, numtris, limit, areaweight);
- }
-
- void buildnorms(bool areaweight = true)
- {
- mesh::buildnorms(verts, numverts, tris, numtris, areaweight);
- }
-
- void calctangents(bool areaweight = true)
- {
- if(bumpverts) return;
- bumpverts = new bumpvert[numverts];
- mesh::calctangents(bumpverts, verts, verts, numverts, tris, numtris, areaweight);
- }
-
- void calcbb(vec &bbmin, vec &bbmax, const matrix4x3 &m)
- {
- loopj(numverts)
- {
- vec v = m.transform(verts[j].pos);
- loopi(3)
- {
- bbmin[i] = min(bbmin[i], v[i]);
- bbmax[i] = max(bbmax[i], v[i]);
- }
- }
- }
-
- void genBIH(BIH::mesh &m)
- {
- m.tris = (const BIH::tri *)tris;
- m.numtris = numtris;
- m.pos = (const uchar *)&verts->pos;
- m.posstride = sizeof(vert);
- m.tc = (const uchar *)&verts->tc;
- m.tcstride = sizeof(vert);
- }
-
- static inline void assignvert(vvertn &vv, int j, vert &v, blendcombo &c)
- {
- vv.pos = v.pos;
- vv.norm = v.norm;
- vv.tc = v.tc;
- }
-
- inline void assignvert(vvertbump &vv, int j, vert &v, blendcombo &c)
- {
- vv.pos = v.pos;
- vv.tc = v.tc;
- vv.tangent = bumpverts[j].tangent;
- }
-
- static inline void assignvert(vvertnw &vv, int j, vert &v, blendcombo &c)
- {
- vv.pos = v.pos;
- vv.norm = v.norm;
- vv.tc = v.tc;
- c.serialize(vv);
- }
-
- inline void assignvert(vvertbumpw &vv, int j, vert &v, blendcombo &c)
- {
- vv.pos = v.pos;
- vv.tc = v.tc;
- vv.tangent = bumpverts[j].tangent;
- c.serialize(vv);
- }
-
- template<class T>
- int genvbo(vector<ushort> &idxs, int offset, vector<T> &vverts)
- {
- voffset = offset;
- eoffset = idxs.length();
- loopi(numverts)
- {
- vert &v = verts[i];
- assignvert(vverts.add(), i, v, ((skelmeshgroup *)group)->blendcombos[v.blend]);
- }
- loopi(numtris) loopj(3) idxs.add(voffset + tris[i].vert[j]);
- elen = idxs.length()-eoffset;
- minvert = voffset;
- maxvert = voffset + numverts-1;
- return numverts;
- }
-
- template<class T>
- int genvbo(vector<ushort> &idxs, int offset, vector<T> &vverts, int *htdata, int htlen)
- {
- voffset = offset;
- eoffset = idxs.length();
- minvert = 0xFFFF;
- loopi(numtris)
- {
- tri &t = tris[i];
- loopj(3)
- {
- int index = t.vert[j];
- vert &v = verts[index];
- T vv;
- assignvert(vv, index, v, ((skelmeshgroup *)group)->blendcombos[v.blend]);
- int htidx = hthash(v.pos)&(htlen-1);
- loopk(htlen)
- {
- int &vidx = htdata[(htidx+k)&(htlen-1)];
- if(vidx < 0) { vidx = idxs.add(ushort(vverts.length())); vverts.add(vv); break; }
- else if(!memcmp(&vverts[vidx], &vv, sizeof(vv))) { minvert = min(minvert, idxs.add(ushort(vidx))); break; }
- }
- }
- }
- elen = idxs.length()-eoffset;
- minvert = min(minvert, ushort(voffset));
- maxvert = max(minvert, ushort(vverts.length()-1));
- return vverts.length()-voffset;
- }
-
- int genvbo(vector<ushort> &idxs, int offset)
- {
- loopi(numverts) verts[i].interpindex = ((skelmeshgroup *)group)->remapblend(verts[i].blend);
-
- voffset = offset;
- eoffset = idxs.length();
- loopi(numtris)
- {
- tri &t = tris[i];
- loopj(3) idxs.add(voffset+t.vert[j]);
- }
- minvert = voffset;
- maxvert = voffset + numverts-1;
- elen = idxs.length()-eoffset;
- return numverts;
- }
-
- template<class T>
- static inline void fillvert(T &vv, int j, vert &v)
- {
- vv.tc = v.tc;
- }
-
- template<class T>
- void fillverts(T *vdata)
- {
- vdata += voffset;
- loopi(numverts) fillvert(vdata[i], i, verts[i]);
- }
-
- void interpverts(const dualquat * RESTRICT bdata1, const dualquat * RESTRICT bdata2, bool tangents, void * RESTRICT vdata, skin &s)
- {
- const int blendoffset = ((skelmeshgroup *)group)->skel->numgpubones;
- bdata2 -= blendoffset;
-
- #define IPLOOP(type, dosetup, dotransform) \
- loopi(numverts) \
- { \
- const vert &src = verts[i]; \
- type &dst = ((type * RESTRICT)vdata)[i]; \
- dosetup; \
- const dualquat &b = (src.interpindex < blendoffset ? bdata1 : bdata2)[src.interpindex]; \
- dst.pos = b.transform(src.pos); \
- dotransform; \
- }
-
- if(tangents)
- {
- IPLOOP(vvertbump, bumpvert &bsrc = bumpverts[i],
- {
- quat q = b.transform(bsrc.tangent);
- fixqtangent(q, bsrc.tangent.w);
- dst.tangent = q;
- });
- }
- else
- {
- IPLOOP(vvertn, ,
- {
- dst.norm = b.transformnormal(src.norm);
- });
- }
-
- #undef IPLOOP
- }
-
- void setshader(Shader *s)
- {
- skelmeshgroup *g = (skelmeshgroup *)group;
- if(glaring)
- {
- if(!g->skel->usegpuskel) s->setvariant(0, 1);
- else s->setvariant(min(maxweights, g->vweights), 1);
- }
- else if(!g->skel->usegpuskel) s->set();
- else s->setvariant(min(maxweights, g->vweights)-1, 0);
- }
-
- void render(const animstate *as, skin &s, vbocacheentry &vc)
- {
- if(!Shader::lastshader) return;
- glDrawRangeElements_(GL_TRIANGLES, minvert, maxvert, elen, GL_UNSIGNED_SHORT, &((skelmeshgroup *)group)->edata[eoffset]);
- glde++;
- xtravertsva += numverts;
- }
- };
-
-
- struct tag
- {
- char *name;
- int bone;
- matrix4x3 matrix;
-
- tag() : name(NULL) {}
- ~tag() { DELETEA(name); }
- };
-
- struct skelanimspec
- {
- char *name;
- int frame, range;
-
- skelanimspec() : name(NULL), frame(0), range(0) {}
- ~skelanimspec()
- {
- DELETEA(name);
- }
- };
-
- struct boneinfo
- {
- const char *name;
- int parent, children, next, group, scheduled, interpindex, interpparent, ragdollindex, correctindex;
- float pitchscale, pitchoffset, pitchmin, pitchmax;
- dualquat base, invbase;
-
- boneinfo() : name(NULL), parent(-1), children(-1), next(-1), group(INT_MAX), scheduled(-1), interpindex(-1), interpparent(-1), ragdollindex(-1), correctindex(-1), pitchscale(0), pitchoffset(0), pitchmin(0), pitchmax(0) {}
- ~boneinfo()
- {
- DELETEA(name);
- }
- };
-
- struct antipode
- {
- int parent, child;
-
- antipode(int parent, int child) : parent(parent), child(child) {}
- };
-
- struct pitchdep
- {
- int bone, parent;
- dualquat pose;
- };
-
- struct pitchtarget
- {
- int bone, frame, corrects, deps;
- float pitchmin, pitchmax, deviated;
- dualquat pose;
- };
-
- struct pitchcorrect
- {
- int bone, target, parent;
- float pitchmin, pitchmax, pitchscale, pitchangle, pitchtotal;
-
- pitchcorrect() : parent(-1), pitchangle(0), pitchtotal(0) {}
- };
-
- struct skeleton
- {
- char *name;
- int shared;
- vector<skelmeshgroup *> users;
- boneinfo *bones;
- int numbones, numinterpbones, numgpubones, numframes;
- dualquat *framebones;
- vector<skelanimspec> skelanims;
- vector<tag> tags;
- vector<antipode> antipodes;
- ragdollskel *ragdoll;
- vector<pitchdep> pitchdeps;
- vector<pitchtarget> pitchtargets;
- vector<pitchcorrect> pitchcorrects;
-
- bool usegpuskel;
- vector<skelcacheentry> skelcache;
- hashtable<GLuint, int> blendoffsets;
-
- skeleton() : name(NULL), shared(0), bones(NULL), numbones(0), numinterpbones(0), numgpubones(0), numframes(0), framebones(NULL), ragdoll(NULL), usegpuskel(false), blendoffsets(32)
- {
- }
-
- ~skeleton()
- {
- DELETEA(name);
- DELETEA(bones);
- DELETEA(framebones);
- DELETEP(ragdoll);
- loopv(skelcache)
- {
- DELETEA(skelcache[i].bdata);
- }
- }
-
- skelanimspec *findskelanim(const char *name, char sep = '\0')
- {
- int len = sep ? strlen(name) : 0;
- loopv(skelanims)
- {
- if(skelanims[i].name)
- {
- if(sep)
- {
- const char *end = strchr(skelanims[i].name, ':');
- if(end && end - skelanims[i].name == len && !memcmp(name, skelanims[i].name, len)) return &skelanims[i];
- }
- if(!strcmp(name, skelanims[i].name)) return &skelanims[i];
- }
- }
- return NULL;
- }
-
- skelanimspec &addskelanim(const char *name)
- {
- skelanimspec &sa = skelanims.add();
- sa.name = name ? newstring(name) : NULL;
- return sa;
- }
-
- int findbone(const char *name)
- {
- loopi(numbones) if(bones[i].name && !strcmp(bones[i].name, name)) return i;
- return -1;
- }
-
- int findtag(const char *name)
- {
- loopv(tags) if(!strcmp(tags[i].name, name)) return i;
- return -1;
- }
-
- bool addtag(const char *name, int bone, const matrix4x3 &matrix)
- {
- int idx = findtag(name);
- if(idx >= 0)
- {
- if(!testtags) return false;
- tag &t = tags[idx];
- t.bone = bone;
- t.matrix = matrix;
- }
- else
- {
- tag &t = tags.add();
- t.name = newstring(name);
- t.bone = bone;
- t.matrix = matrix;
- }
- return true;
- }
-
- void calcantipodes()
- {
- antipodes.shrink(0);
- vector<int> schedule;
- loopi(numbones)
- {
- if(bones[i].group >= numbones)
- {
- bones[i].scheduled = schedule.length();
- schedule.add(i);
- }
- else bones[i].scheduled = -1;
- }
- loopv(schedule)
- {
- int bone = schedule[i];
- const boneinfo &info = bones[bone];
- loopj(numbones) if(abs(bones[j].group) == bone && bones[j].scheduled < 0)
- {
- antipodes.add(antipode(info.interpindex, bones[j].interpindex));
- bones[j].scheduled = schedule.length();
- schedule.add(j);
- }
- if(i + 1 == schedule.length())
- {
- int conflict = INT_MAX;
- loopj(numbones) if(bones[j].group < numbones && bones[j].scheduled < 0) conflict = min(conflict, abs(bones[j].group));
- if(conflict < numbones)
- {
- bones[conflict].scheduled = schedule.length();
- schedule.add(conflict);
- }
- }
- }
- }
-
- void remapbones()
- {
- loopi(numbones)
- {
- boneinfo &info = bones[i];
- info.interpindex = -1;
- info.ragdollindex = -1;
- }
- numgpubones = 0;
- loopv(users)
- {
- skelmeshgroup *group = users[i];
- loopvj(group->blendcombos)
- {
- blendcombo &c = group->blendcombos[j];
- loopk(4)
- {
- if(!c.weights[k]) { c.interpbones[k] = k > 0 ? c.interpbones[k-1] : 0; continue; }
- boneinfo &info = bones[c.bones[k]];
- if(info.interpindex < 0) info.interpindex = numgpubones++;
- c.interpbones[k] = info.interpindex;
- if(info.group < 0) continue;
- loopl(4)
- {
- if(!c.weights[l]) break;
- if(l == k) continue;
- int parent = c.bones[l];
- if(info.parent == parent || (info.parent >= 0 && info.parent == bones[parent].parent)) { info.group = -info.parent; break; }
- if(info.group <= parent) continue;
- int child = c.bones[k];
- while(parent > child) parent = bones[parent].parent;
- if(parent != child) info.group = c.bones[l];
- }
- }
- }
- }
- numinterpbones = numgpubones;
- loopv(tags)
- {
- boneinfo &info = bones[tags[i].bone];
- if(info.interpindex < 0) info.interpindex = numinterpbones++;
- }
- if(ragdoll)
- {
- loopv(ragdoll->joints)
- {
- boneinfo &info = bones[ragdoll->joints[i].bone];
- if(info.interpindex < 0) info.interpindex = numinterpbones++;
- info.ragdollindex = i;
- }
- }
- loopi(numbones)
- {
- boneinfo &info = bones[i];
- if(info.interpindex < 0) continue;
- for(int parent = info.parent; parent >= 0 && bones[parent].interpindex < 0; parent = bones[parent].parent)
- bones[parent].interpindex = numinterpbones++;
- }
- loopi(numbones)
- {
- boneinfo &info = bones[i];
- if(info.interpindex < 0) continue;
- info.interpparent = info.parent >= 0 ? bones[info.parent].interpindex : -1;
- }
- if(ragdoll)
- {
- loopi(numbones)
- {
- boneinfo &info = bones[i];
- if(info.interpindex < 0 || info.ragdollindex >= 0) continue;
- for(int parent = info.parent; parent >= 0; parent = bones[parent].parent)
- {
- if(bones[parent].ragdollindex >= 0) { ragdoll->addreljoint(i, bones[parent].ragdollindex); break; }
- }
- }
- }
- calcantipodes();
- }
-
-
- void addpitchdep(int bone, int frame)
- {
- for(; bone >= 0; bone = bones[bone].parent)
- {
- int pos = pitchdeps.length();
- loopvj(pitchdeps) if(bone <= pitchdeps[j].bone)
- {
- if(bone == pitchdeps[j].bone) goto nextbone;
- pos = j;
- break;
- }
- {
- pitchdep d;
- d.bone = bone;
- d.parent = -1;
- d.pose = framebones[frame*numbones + bone];
- pitchdeps.insert(pos, d);
- }
- nextbone:;
- }
- }
-
- int findpitchdep(int bone)
- {
- loopv(pitchdeps) if(bone <= pitchdeps[i].bone) return bone == pitchdeps[i].bone ? i : -1;
- return -1;
- }
-
- int findpitchcorrect(int bone)
- {
- loopv(pitchcorrects) if(bone <= pitchcorrects[i].bone) return bone == pitchcorrects[i].bone ? i : -1;
- return -1;
- }
-
- void initpitchdeps()
- {
- pitchdeps.setsize(0);
- if(pitchtargets.empty()) return;
- loopv(pitchtargets)
- {
- pitchtarget &t = pitchtargets[i];
- t.deps = -1;
- addpitchdep(t.bone, t.frame);
- }
- loopv(pitchdeps)
- {
- pitchdep &d = pitchdeps[i];
- int parent = bones[d.bone].parent;
- if(parent >= 0)
- {
- int j = findpitchdep(parent);
- if(j >= 0)
- {
- d.parent = j;
- d.pose.mul(pitchdeps[j].pose, dualquat(d.pose));
- }
- }
- }
- loopv(pitchtargets)
- {
- pitchtarget &t = pitchtargets[i];
- int j = findpitchdep(t.bone);
- if(j >= 0)
- {
- t.deps = j;
- t.pose = pitchdeps[j].pose;
- }
- t.corrects = -1;
- for(int parent = t.bone; parent >= 0; parent = bones[parent].parent)
- {
- t.corrects = findpitchcorrect(parent);
- if(t.corrects >= 0) break;
- }
- }
- loopv(pitchcorrects)
- {
- pitchcorrect &c = pitchcorrects[i];
- bones[c.bone].correctindex = i;
- c.parent = -1;
- for(int parent = c.bone;;)
- {
- parent = bones[parent].parent;
- if(parent < 0) break;
- c.parent = findpitchcorrect(parent);
- if(c.parent >= 0) break;
- }
- }
- }
-
- void optimize()
- {
- cleanup();
- if(ragdoll) ragdoll->setup();
- remapbones();
- initpitchdeps();
- }
-
- void expandbonemask(uchar *expansion, int bone, int val)
- {
- expansion[bone] = val;
- bone = bones[bone].children;
- while(bone>=0) { expandbonemask(expansion, bone, val); bone = bones[bone].next; }
- }
-
- void applybonemask(ushort *mask, uchar *partmask, int partindex)
- {
- if(!mask || *mask==BONEMASK_END) return;
- uchar *expansion = new uchar[numbones];
- memset(expansion, *mask&BONEMASK_NOT ? 1 : 0, numbones);
- while(*mask!=BONEMASK_END)
- {
- expandbonemask(expansion, *mask&BONEMASK_BONE, *mask&BONEMASK_NOT ? 0 : 1);
- mask++;
- }
- loopi(numbones) if(expansion[i]) partmask[i] = partindex;
- delete[] expansion;
- }
-
- void linkchildren()
- {
- loopi(numbones)
- {
- boneinfo &b = bones[i];
- b.children = -1;
- if(b.parent<0) b.next = -1;
- else
- {
- b.next = bones[b.parent].children;
- bones[b.parent].children = i;
- }
- }
- }
-
- int availgpubones() const { return min(maxvsuniforms - reservevpparams - 10, maxskelanimdata) / 2; }
- bool gpuaccelerate() const { return numframes && gpuskel && numgpubones<=availgpubones(); }
-
- float calcdeviation(const vec &axis, const vec &forward, const dualquat &pose1, const dualquat &pose2)
- {
- vec forward1 = pose1.transformnormal(forward).project(axis).normalize(),
- forward2 = pose2.transformnormal(forward).project(axis).normalize(),
- daxis = vec().cross(forward1, forward2);
- float dx = clamp(forward1.dot(forward2), -1.0f, 1.0f), dy = clamp(daxis.magnitude(), -1.0f, 1.0f);
- if(daxis.dot(axis) < 0) dy = -dy;
- return atan2f(dy, dx)/RAD;
- }
-
- void calcpitchcorrects(float pitch, const vec &axis, const vec &forward)
- {
- loopv(pitchtargets)
- {
- pitchtarget &t = pitchtargets[i];
- t.deviated = calcdeviation(axis, forward, t.pose, pitchdeps[t.deps].pose);
- }
- loopv(pitchcorrects)
- {
- pitchcorrect &c = pitchcorrects[i];
- c.pitchangle = c.pitchtotal = 0;
- }
- loopvj(pitchtargets)
- {
- pitchtarget &t = pitchtargets[j];
- float tpitch = pitch - t.deviated;
- for(int parent = t.corrects; parent >= 0; parent = pitchcorrects[parent].parent)
- tpitch -= pitchcorrects[parent].pitchangle;
- if(t.pitchmin || t.pitchmax) tpitch = clamp(tpitch, t.pitchmin, t.pitchmax);
- loopv(pitchcorrects)
- {
- pitchcorrect &c = pitchcorrects[i];
- if(c.target != j) continue;
- float total = c.parent >= 0 ? pitchcorrects[c.parent].pitchtotal : 0,
- avail = tpitch - total,
- used = tpitch*c.pitchscale;
- if(c.pitchmin || c.pitchmax)
- {
- if(used < 0) used = clamp(c.pitchmin, used, 0.0f);
- else used = clamp(c.pitchmax, 0.0f, used);
- }
- if(used < 0) used = clamp(avail, used, 0.0f);
- else used = clamp(avail, 0.0f, used);
- c.pitchangle = used;
- c.pitchtotal = used + total;
- }
- }
- }
-
- #define INTERPBONE(bone) \
- const animstate &s = as[partmask[bone]]; \
- const framedata &f = partframes[partmask[bone]]; \
- dualquat d; \
- (d = f.fr1[bone]).mul((1-s.cur.t)*s.interp); \
- d.accumulate(f.fr2[bone], s.cur.t*s.interp); \
- if(s.interp<1) \
- { \
- d.accumulate(f.pfr1[bone], (1-s.prev.t)*(1-s.interp)); \
- d.accumulate(f.pfr2[bone], s.prev.t*(1-s.interp)); \
- }
-
- void interpbones(const animstate *as, float pitch, const vec &axis, const vec &forward, int numanimparts, const uchar *partmask, skelcacheentry &sc)
- {
- if(!sc.bdata) sc.bdata = new dualquat[numinterpbones];
- sc.nextversion();
- struct framedata
- {
- const dualquat *fr1, *fr2, *pfr1, *pfr2;
- } partframes[MAXANIMPARTS];
- loopi(numanimparts)
- {
- partframes[i].fr1 = &framebones[as[i].cur.fr1*numbones];
- partframes[i].fr2 = &framebones[as[i].cur.fr2*numbones];
- if(as[i].interp<1)
- {
- partframes[i].pfr1 = &framebones[as[i].prev.fr1*numbones];
- partframes[i].pfr2 = &framebones[as[i].prev.fr2*numbones];
- }
- }
- loopv(pitchdeps)
- {
- pitchdep &p = pitchdeps[i];
- INTERPBONE(p.bone);
- d.normalize();
- if(p.parent >= 0) p.pose.mul(pitchdeps[p.parent].pose, d);
- else p.pose = d;
- }
- calcpitchcorrects(pitch, axis, forward);
- loopi(numbones) if(bones[i].interpindex>=0)
- {
- INTERPBONE(i);
- const boneinfo &b = bones[i];
- d.normalize();
- if(b.interpparent<0) sc.bdata[b.interpindex] = d;
- else sc.bdata[b.interpindex].mul(sc.bdata[b.interpparent], d);
- float angle;
- if(b.pitchscale) { angle = b.pitchscale*pitch + b.pitchoffset; if(b.pitchmin || b.pitchmax) angle = clamp(angle, b.pitchmin, b.pitchmax); }
- else if(b.correctindex >= 0) angle = pitchcorrects[b.correctindex].pitchangle;
- else continue;
- if(as->cur.anim&ANIM_NOPITCH || (as->interp < 1 && as->prev.anim&ANIM_NOPITCH))
- angle *= (as->cur.anim&ANIM_NOPITCH ? 0 : as->interp) + (as->interp < 1 && as->prev.anim&ANIM_NOPITCH ? 0 : 1-as->interp);
- sc.bdata[b.interpindex].mulorient(quat(axis, angle*RAD), b.base);
- }
- loopv(antipodes) sc.bdata[antipodes[i].child].fixantipodal(sc.bdata[antipodes[i].parent]);
- }
-
- void initragdoll(ragdolldata &d, skelcacheentry &sc, part *p)
- {
- const dualquat *bdata = sc.bdata;
- loopv(ragdoll->joints)
- {
- const ragdollskel::joint &j = ragdoll->joints[i];
- const boneinfo &b = bones[j.bone];
- const dualquat &q = bdata[b.interpindex];
- loopk(3) if(j.vert[k] >= 0)
- {
- ragdollskel::vert &v = ragdoll->verts[j.vert[k]];
- ragdolldata::vert &dv = d.verts[j.vert[k]];
- dv.pos.add(q.transform(v.pos).mul(v.weight));
- }
- }
- if(ragdoll->animjoints) loopv(ragdoll->joints)
- {
- const ragdollskel::joint &j = ragdoll->joints[i];
- const boneinfo &b = bones[j.bone];
- const dualquat &q = bdata[b.interpindex];
- d.calcanimjoint(i, matrix4x3(q));
- }
- loopv(ragdoll->verts)
- {
- ragdolldata::vert &dv = d.verts[i];
- matrixstack[matrixpos].transform(vec(dv.pos).add(p->translate).mul(p->model->scale), dv.pos);
- }
- loopv(ragdoll->reljoints)
- {
- const ragdollskel::reljoint &r = ragdoll->reljoints[i];
- const ragdollskel::joint &j = ragdoll->joints[r.parent];
- const boneinfo &br = bones[r.bone], &bj = bones[j.bone];
- d.reljoints[i].mul(dualquat(bdata[bj.interpindex]).invert(), bdata[br.interpindex]);
- }
- }
-
- void genragdollbones(ragdolldata &d, skelcacheentry &sc, part *p)
- {
- if(!sc.bdata) sc.bdata = new dualquat[numinterpbones];
- sc.nextversion();
- loopv(ragdoll->joints)
- {
- const ragdollskel::joint &j = ragdoll->joints[i];
- const boneinfo &b = bones[j.bone];
- vec pos(0, 0, 0);
- loopk(3) if(j.vert[k]>=0) pos.add(d.verts[j.vert[k]].pos);
- pos.mul(j.weight/p->model->scale).sub(p->translate);
- matrix4x3 m;
- m.mul(d.tris[j.tri], pos, d.animjoints ? d.animjoints[i] : j.orient);
- sc.bdata[b.interpindex] = dualquat(m);
- }
- loopv(ragdoll->reljoints)
- {
- const ragdollskel::reljoint &r = ragdoll->reljoints[i];
- const ragdollskel::joint &j = ragdoll->joints[r.parent];
- const boneinfo &br = bones[r.bone], &bj = bones[j.bone];
- sc.bdata[br.interpindex].mul(sc.bdata[bj.interpindex], d.reljoints[i]);
- }
- loopv(antipodes) sc.bdata[antipodes[i].child].fixantipodal(sc.bdata[antipodes[i].parent]);
- }
-
- void concattagtransform(part *p, int i, const matrix4x3 &m, matrix4x3 &n)
- {
- matrix4x3 t;
- t.mul(bones[tags[i].bone].base, tags[i].matrix);
- t.posttranslate(p->translate, p->model->scale);
- n.mul(m, t);
- }
-
- void calctags(part *p, skelcacheentry *sc = NULL)
- {
- loopv(p->links)
- {
- linkedpart &l = p->links[i];
- tag &t = tags[l.tag];
- dualquat q;
- if(sc) q.mul(sc->bdata[bones[t.bone].interpindex], bones[t.bone].base);
- else q = bones[t.bone].base;
- matrix4x3 m;
- m.mul(q, t.matrix);
- m.d.add(p->translate).mul(p->model->scale);
- l.matrix = m;
- }
- }
-
- void cleanup(bool full = true)
- {
- loopv(skelcache)
- {
- skelcacheentry &sc = skelcache[i];
- loopj(MAXANIMPARTS) sc.as[j].cur.fr1 = -1;
- DELETEA(sc.bdata);
- }
- skelcache.setsize(0);
- blendoffsets.clear();
- if(full) loopv(users) users[i]->cleanup();
- }
-
- bool canpreload() { return !numframes || gpuaccelerate(); }
-
- void preload()
- {
- if(!numframes) return;
- if(skelcache.empty())
- {
- usegpuskel = gpuaccelerate();
- }
- }
-
- skelcacheentry &checkskelcache(part *p, const animstate *as, float pitch, const vec &axis, const vec &forward, ragdolldata *rdata)
- {
- if(skelcache.empty())
- {
- usegpuskel = gpuaccelerate();
- }
-
- int numanimparts = ((skelpart *)as->owner)->numanimparts;
- uchar *partmask = ((skelpart *)as->owner)->partmask;
- skelcacheentry *sc = NULL;
- bool match = false;
- loopv(skelcache)
- {
- skelcacheentry &c = skelcache[i];
- loopj(numanimparts) if(c.as[j]!=as[j]) goto mismatch;
- if(c.pitch != pitch || c.partmask != partmask || c.ragdoll != rdata || (rdata && c.millis < rdata->lastmove)) goto mismatch;
- match = true;
- sc = &c;
- break;
- mismatch:
- if(c.millis < lastmillis) { sc = &c; break; }
- }
- if(!sc) sc = &skelcache.add();
- if(!match)
- {
- loopi(numanimparts) sc->as[i] = as[i];
- sc->pitch = pitch;
- sc->partmask = partmask;
- sc->ragdoll = rdata;
- if(rdata) genragdollbones(*rdata, *sc, p);
- else interpbones(as, pitch, axis, forward, numanimparts, partmask, *sc);
- }
- sc->millis = lastmillis;
- return *sc;
- }
-
- int getblendoffset(UniformLoc &u)
- {
- int &offset = blendoffsets.access(Shader::lastshader->program, -1);
- if(offset < 0)
- {
- defformatstring(offsetname, "%s[%d]", u.name, 2*numgpubones);
- offset = glGetUniformLocation_(Shader::lastshader->program, offsetname);
- }
- return offset;
- }
-
- void setglslbones(UniformLoc &u, skelcacheentry &sc, skelcacheentry &bc, int count)
- {
- if(u.version == bc.version && u.data == bc.bdata) return;
- glUniform4fv_(u.loc, 2*numgpubones, sc.bdata[0].real.v);
- if(count > 0)
- {
- int offset = getblendoffset(u);
- if(offset >= 0) glUniform4fv_(offset, 2*count, bc.bdata[0].real.v);
- }
- u.version = bc.version;
- u.data = bc.bdata;
- }
-
- void setgpubones(skelcacheentry &sc, blendcacheentry *bc, int count)
- {
- if(!Shader::lastshader) return;
- if(Shader::lastshader->uniformlocs.length() < 1) return;
- UniformLoc &u = Shader::lastshader->uniformlocs[0];
- setglslbones(u, sc, bc ? *bc : sc, count);
- }
-
- bool shouldcleanup() const
- {
- return numframes && (skelcache.empty() || gpuaccelerate()!=usegpuskel);
- }
- };
-
- struct skelmeshgroup : meshgroup
- {
- skeleton *skel;
-
- vector<blendcombo> blendcombos;
- int numblends[4];
-
- static const int MAXBLENDCACHE = 16;
- blendcacheentry blendcache[MAXBLENDCACHE];
-
- static const int MAXVBOCACHE = 16;
- vbocacheentry vbocache[MAXVBOCACHE];
-
- ushort *edata;
- GLuint ebuf;
- bool vtangents;
- int vlen, vertsize, vblends, vweights;
- uchar *vdata;
-
- skelmeshgroup() : skel(NULL), edata(NULL), ebuf(0), vtangents(false), vlen(0), vertsize(0), vblends(0), vweights(0), vdata(NULL)
- {
- memset(numblends, 0, sizeof(numblends));
- }
-
- virtual ~skelmeshgroup()
- {
- if(skel)
- {
- if(skel->shared) skel->users.removeobj(this);
- else DELETEP(skel);
- }
- if(ebuf) glDeleteBuffers_(1, &ebuf);
- loopi(MAXBLENDCACHE)
- {
- DELETEA(blendcache[i].bdata);
- }
- loopi(MAXVBOCACHE)
- {
- if(vbocache[i].vbuf) glDeleteBuffers_(1, &vbocache[i].vbuf);
- }
- DELETEA(vdata);
- }
-
- void shareskeleton(char *name)
- {
- if(!name)
- {
- skel = new skeleton;
- skel->users.add(this);
- return;
- }
-
- static hashnameset<skeleton *> skeletons;
- if(skeletons.access(name)) skel = skeletons[name];
- else
- {
- skel = new skeleton;
- skel->name = newstring(name);
- skeletons.add(skel);
- }
- skel->users.add(this);
- skel->shared++;
- }
-
- int findtag(const char *name)
- {
- return skel->findtag(name);
- }
-
- void *animkey() { return skel; }
- int totalframes() const { return max(skel->numframes, 1); }
-
- virtual skelanimspec *loadanim(const char *filename) { return NULL; }
-
- void genvbo(bool tangents, vbocacheentry &vc)
- {
- if(!vc.vbuf) glGenBuffers_(1, &vc.vbuf);
- if(ebuf) return;
-
- vector<ushort> idxs;
-
- if(tangents) loopv(meshes) ((skelmesh *)meshes[i])->calctangents();
-
- vtangents = tangents;
- vlen = 0;
- vblends = 0;
- if(skel->numframes && !skel->usegpuskel)
- {
- vweights = 1;
- loopv(blendcombos)
- {
- blendcombo &c = blendcombos[i];
- c.interpindex = c.weights[1] ? skel->numgpubones + vblends++ : -1;
- }
-
- vertsize = tangents ? sizeof(vvertbump) : sizeof(vvertn);
- loopv(meshes) vlen += ((skelmesh *)meshes[i])->genvbo(idxs, vlen);
- DELETEA(vdata);
- vdata = new uchar[vlen*vertsize];
- #define FILLVDATA(type) do { \
- loopv(meshes) ((skelmesh *)meshes[i])->fillverts((type *)vdata); \
- } while(0)
- if(tangents) FILLVDATA(vvertbump);
- else FILLVDATA(vvertn);
- #undef FILLVDATA
- }
- else
- {
- if(skel->numframes)
- {
- vweights = 4;
- int availbones = skel->availgpubones() - skel->numgpubones;
- while(vweights > 1 && availbones >= numblends[vweights-1]) availbones -= numblends[--vweights];
- loopv(blendcombos)
- {
- blendcombo &c = blendcombos[i];
- c.interpindex = c.size() > vweights ? skel->numgpubones + vblends++ : -1;
- }
- }
- else
- {
- vweights = 0;
- loopv(blendcombos) blendcombos[i].interpindex = -1;
- }
-
- gle::bindvbo(vc.vbuf);
- #define GENVBO(type, args) do { \
- vertsize = sizeof(type); \
- vector<type> vverts; \
- loopv(meshes) vlen += ((skelmesh *)meshes[i])->genvbo args; \
- glBufferData_(GL_ARRAY_BUFFER, vverts.length()*sizeof(type), vverts.getbuf(), GL_STATIC_DRAW); \
- } while(0)
- #define GENVBOANIM(type) GENVBO(type, (idxs, vlen, vverts))
- #define GENVBOSTAT(type) GENVBO(type, (idxs, vlen, vverts, htdata, htlen))
- if(skel->numframes)
- {
- if(tangents) GENVBOANIM(vvertbumpw);
- else GENVBOANIM(vvertnw);
- }
- else
- {
- int numverts = 0, htlen = 128;
- loopv(meshes) numverts += ((skelmesh *)meshes[i])->numverts;
- while(htlen < numverts) htlen *= 2;
- if(numverts*4 > htlen*3) htlen *= 2;
- int *htdata = new int[htlen];
- memset(htdata, -1, htlen*sizeof(int));
- if(tangents) GENVBOSTAT(vvertbump);
- else GENVBOSTAT(vvertn);
- delete[] htdata;
- }
- #undef GENVBO
- #undef GENVBOANIM
- #undef GENVBOSTAT
- gle::clearvbo();
- }
-
- glGenBuffers_(1, &ebuf);
- gle::bindebo(ebuf);
- glBufferData_(GL_ELEMENT_ARRAY_BUFFER, idxs.length()*sizeof(ushort), idxs.getbuf(), GL_STATIC_DRAW);
- gle::clearebo();
- }
-
- void bindvbo(const animstate *as, vbocacheentry &vc, skelcacheentry *sc = NULL, blendcacheentry *bc = NULL)
- {
- vvert *vverts = 0;
- bindpos(ebuf, vc.vbuf, &vverts->pos, vertsize);
- if(as->cur.anim&ANIM_NOSKIN)
- {
- if(enabletc) disabletc();
- if(enablenormals) disablenormals();
- if(enabletangents) disabletangents();
- }
- else
- {
- if(vtangents)
- {
- if(enablenormals) disablenormals();
- vvertbump *vvertbumps = 0;
- bindtangents(&vvertbumps->tangent, vertsize);
- }
- else
- {
- if(enabletangents) disabletangents();
- vvertn *vvertns = 0;
- bindnormals(&vvertns->norm, vertsize);
- }
-
- bindtc(&vverts->tc, vertsize);
- }
- if(!sc || !skel->usegpuskel)
- {
- if(enablebones) disablebones();
- }
- else
- {
- if(vtangents)
- {
- vvertbumpw *vvertbumpws = 0;
- bindbones(vvertbumpws->weights, vvertbumpws->bones, vertsize);
- }
- else
- {
- vvertnw *vvertnws = 0;
- bindbones(vvertnws->weights, vvertnws->bones, vertsize);
- }
- }
- }
-
- void concattagtransform(part *p, int i, const matrix4x3 &m, matrix4x3 &n)
- {
- skel->concattagtransform(p, i, m, n);
- }
-
- int addblendcombo(const blendcombo &c)
- {
- loopv(blendcombos) if(blendcombos[i]==c)
- {
- blendcombos[i].uses += c.uses;
- return i;
- }
- numblends[c.size()-1]++;
- blendcombo &a = blendcombos.add(c);
- return a.interpindex = blendcombos.length()-1;
- }
-
- void sortblendcombos()
- {
- blendcombos.sort(blendcombo::sortcmp);
- int *remap = new int[blendcombos.length()];
- loopv(blendcombos) remap[blendcombos[i].interpindex] = i;
- loopv(meshes)
- {
- skelmesh *m = (skelmesh *)meshes[i];
- loopj(m->numverts)
- {
- vert &v = m->verts[j];
- v.blend = remap[v.blend];
- }
- }
- delete[] remap;
- }
-
- int remapblend(int blend)
- {
- const blendcombo &c = blendcombos[blend];
- return c.weights[1] ? c.interpindex : c.interpbones[0];
- }
-
- static inline void blendbones(dualquat &d, const dualquat *bdata, const blendcombo &c)
- {
- d = bdata[c.interpbones[0]];
- d.mul(c.weights[0]);
- d.accumulate(bdata[c.interpbones[1]], c.weights[1]);
- if(c.weights[2])
- {
- d.accumulate(bdata[c.interpbones[2]], c.weights[2]);
- if(c.weights[3]) d.accumulate(bdata[c.interpbones[3]], c.weights[3]);
- }
- }
-
- void blendbones(const skelcacheentry &sc, blendcacheentry &bc)
- {
- bc.nextversion();
- if(!bc.bdata) bc.bdata = new dualquat[vblends];
- dualquat *dst = bc.bdata - skel->numgpubones;
- bool normalize = !skel->usegpuskel || vweights<=1;
- loopv(blendcombos)
- {
- const blendcombo &c = blendcombos[i];
- if(c.interpindex<0) break;
- dualquat &d = dst[c.interpindex];
- blendbones(d, sc.bdata, c);
- if(normalize) d.normalize();
- }
- }
-
- void cleanup()
- {
- loopi(MAXBLENDCACHE)
- {
- blendcacheentry &c = blendcache[i];
- DELETEA(c.bdata);
- c.owner = -1;
- }
- loopi(MAXVBOCACHE)
- {
- vbocacheentry &c = vbocache[i];
- if(c.vbuf) { glDeleteBuffers_(1, &c.vbuf); c.vbuf = 0; }
- c.owner = -1;
- }
- if(ebuf) { glDeleteBuffers_(1, &ebuf); ebuf = 0; }
- if(skel) skel->cleanup(false);
- }
-
- #define SEARCHCACHE(cachesize, cacheentry, cache, reusecheck) \
- loopi(cachesize) \
- { \
- cacheentry &c = cache[i]; \
- if(c.owner==owner) \
- { \
- if(c==sc) return c; \
- else c.owner = -1; \
- break; \
- } \
- } \
- loopi(cachesize-1) \
- { \
- cacheentry &c = cache[i]; \
- if(reusecheck c.owner < 0 || c.millis < lastmillis) \
- return c; \
- } \
- return cache[cachesize-1];
-
- vbocacheentry &checkvbocache(skelcacheentry &sc, int owner)
- {
- SEARCHCACHE(MAXVBOCACHE, vbocacheentry, vbocache, !c.vbuf || );
- }
-
- blendcacheentry &checkblendcache(skelcacheentry &sc, int owner)
- {
- SEARCHCACHE(MAXBLENDCACHE, blendcacheentry, blendcache, )
- }
-
- void preload(part *p)
- {
- if(!skel->canpreload()) return;
- bool tangents = false;
- loopv(p->skins) if(p->skins[i].tangents()) tangents = true;
- if(skel->shouldcleanup()) skel->cleanup();
- else if(tangents!=vtangents) cleanup();
- skel->preload();
- if(!vbocache->vbuf) genvbo(tangents, *vbocache);
- }
-
- void render(const animstate *as, float pitch, const vec &axis, const vec &forward, dynent *d, part *p)
- {
- bool tangents = false;
- loopv(p->skins) if(p->skins[i].tangents()) tangents = true;
- if(skel->shouldcleanup()) { skel->cleanup(); disablevbo(); }
- else if(tangents!=vtangents) { cleanup(); disablevbo(); }
-
- if(!skel->numframes)
- {
- if(!(as->cur.anim&ANIM_NORENDER))
- {
- if(!vbocache->vbuf) genvbo(tangents, *vbocache);
- bindvbo(as, *vbocache);
- loopv(meshes)
- {
- skelmesh *m = (skelmesh *)meshes[i];
- p->skins[i].bind(m, as);
- m->render(as, p->skins[i], *vbocache);
- }
- }
- skel->calctags(p);
- return;
- }
-
- skelcacheentry &sc = skel->checkskelcache(p, as, pitch, axis, forward, as->cur.anim&ANIM_RAGDOLL || !d || !d->ragdoll || d->ragdoll->skel != skel->ragdoll ? NULL : d->ragdoll);
- if(!(as->cur.anim&ANIM_NORENDER))
- {
- int owner = &sc-&skel->skelcache[0];
- vbocacheentry &vc = skel->usegpuskel ? *vbocache : checkvbocache(sc, owner);
- vc.millis = lastmillis;
- if(!vc.vbuf) genvbo(tangents, vc);
- blendcacheentry *bc = NULL;
- if(vblends)
- {
- bc = &checkblendcache(sc, owner);
- bc->millis = lastmillis;
- if(bc->owner!=owner)
- {
- bc->owner = owner;
- *(animcacheentry *)bc = sc;
- blendbones(sc, *bc);
- }
- }
- if(!skel->usegpuskel && vc.owner!=owner)
- {
- vc.owner = owner;
- (animcacheentry &)vc = sc;
- loopv(meshes)
- {
- skelmesh &m = *(skelmesh *)meshes[i];
- m.interpverts(sc.bdata, bc ? bc->bdata : NULL, tangents, vdata + m.voffset*vertsize, p->skins[i]);
- }
- gle::bindvbo(vc.vbuf);
- glBufferData_(GL_ARRAY_BUFFER, vlen*vertsize, vdata, GL_STREAM_DRAW);
- }
-
- bindvbo(as, vc, &sc, bc);
- loopv(meshes)
- {
- skelmesh *m = (skelmesh *)meshes[i];
- p->skins[i].bind(m, as);
- if(skel->usegpuskel) skel->setgpubones(sc, bc, vblends);
- m->render(as, p->skins[i], vc);
- }
- }
-
- skel->calctags(p, &sc);
-
- if(as->cur.anim&ANIM_RAGDOLL && skel->ragdoll && !d->ragdoll)
- {
- d->ragdoll = new ragdolldata(skel->ragdoll, p->model->scale);
- skel->initragdoll(*d->ragdoll, sc, p);
- d->ragdoll->init(d);
- }
- }
- };
-
- struct animpartmask
- {
- animpartmask *next;
- int numbones;
- uchar bones[1];
- };
-
- struct skelpart : part
- {
- animpartmask *buildingpartmask;
-
- uchar *partmask;
-
- skelpart(animmodel *model, int index = 0) : part(model, index), buildingpartmask(NULL), partmask(NULL)
- {
- }
-
- virtual ~skelpart()
- {
- DELETEA(buildingpartmask);
- }
-
- uchar *sharepartmask(animpartmask *o)
- {
- static animpartmask *partmasks = NULL;
- animpartmask *p = partmasks;
- for(; p; p = p->next) if(p->numbones==o->numbones && !memcmp(p->bones, o->bones, p->numbones))
- {
- delete[] (uchar *)o;
- return p->bones;
- }
-
- o->next = p;
- partmasks = o;
- return o->bones;
- }
-
- animpartmask *newpartmask()
- {
- animpartmask *p = (animpartmask *)new uchar[sizeof(animpartmask) + ((skelmeshgroup *)meshes)->skel->numbones-1];
- p->numbones = ((skelmeshgroup *)meshes)->skel->numbones;
- memset(p->bones, 0, p->numbones);
- return p;
- }
-
- void initanimparts()
- {
- DELETEA(buildingpartmask);
- buildingpartmask = newpartmask();
- }
-
- bool addanimpart(ushort *bonemask)
- {
- if(!buildingpartmask || numanimparts>=MAXANIMPARTS) return false;
- ((skelmeshgroup *)meshes)->skel->applybonemask(bonemask, buildingpartmask->bones, numanimparts);
- numanimparts++;
- return true;
- }
-
- void endanimparts()
- {
- if(buildingpartmask)
- {
- partmask = sharepartmask(buildingpartmask);
- buildingpartmask = NULL;
- }
-
- ((skelmeshgroup *)meshes)->skel->optimize();
- }
-
- void loaded()
- {
- endanimparts();
- part::loaded();
- }
- };
-
- skelmodel(const char *name) : animmodel(name)
- {
- }
-
- int linktype(animmodel *m) const
- {
- return type()==m->type() &&
- ((skelmeshgroup *)parts[0]->meshes)->skel == ((skelmeshgroup *)m->parts[0]->meshes)->skel ?
- LINK_REUSE :
- LINK_TAG;
- }
-
- bool skeletal() const { return true; }
-
- skelpart &addpart()
- {
- flushpart();
- skelpart *p = new skelpart(this, parts.length());
- parts.add(p);
- return *p;
- }
+ struct vert { vec pos, norm; vec2 tc; int blend, interpindex; };
+ struct vvert { vec pos; vec2 tc; };
+ struct vvertn : vvert { vec norm; };
+ struct vvertbump : vvert { squat tangent; };
+ struct vvertw { uchar weights[4]; uchar bones[4]; };
+ struct vvertnw : vvertn, vvertw {};
+ struct vvertbumpw : vvertbump, vvertw {};
+ struct bumpvert { quat tangent; };
+ struct tri { ushort vert[3]; };
+
+ struct blendcombo
+ {
+ int uses, interpindex;
+ float weights[4];
+ uchar bones[4], interpbones[4];
+
+ blendcombo() : uses(1)
+ {
+ }
+
+ bool operator==(const blendcombo &c) const
+ {
+ loopk(4) if(bones[k] != c.bones[k]) return false;
+ loopk(4) if(weights[k] != c.weights[k]) return false;
+ return true;
+ }
+
+ int size() const
+ {
+ int i = 1;
+ while(i < 4 && weights[i]) i++;
+ return i;
+ }
+
+ static bool sortcmp(const blendcombo &x, const blendcombo &y)
+ {
+ loopi(4)
+ {
+ if(x.weights[i])
+ {
+ if(!y.weights[i]) return true;
+ }
+ else if(y.weights[i]) return false;
+ else break;
+ }
+ return false;
+ }
+
+ int addweight(int sorted, float weight, int bone)
+ {
+ if(weight <= 1e-3f) return sorted;
+ loopk(sorted) if(weight > weights[k])
+ {
+ for(int l = min(sorted-1, 2); l >= k; l--)
+ {
+ weights[l+1] = weights[l];
+ bones[l+1] = bones[l];
+ }
+ weights[k] = weight;
+ bones[k] = bone;
+ return sorted<4 ? sorted+1 : sorted;
+ }
+ if(sorted>=4) return sorted;
+ weights[sorted] = weight;
+ bones[sorted] = bone;
+ return sorted+1;
+ }
+
+ void finalize(int sorted)
+ {
+ loopj(4-sorted) { weights[sorted+j] = 0; bones[sorted+j] = 0; }
+ if(sorted <= 0) return;
+ float total = 0;
+ loopj(sorted) total += weights[j];
+ total = 1.0f/total;
+ loopj(sorted) weights[j] *= total;
+ }
+
+ void serialize(vvertw &v)
+ {
+ if(interpindex >= 0)
+ {
+ v.weights[0] = 255;
+ loopk(3) v.weights[k+1] = 0;
+ v.bones[0] = 2*interpindex;
+ loopk(3) v.bones[k+1] = v.bones[0];
+ }
+ else
+ {
+ int total = 0;
+ loopk(4) total += (v.weights[k] = uchar(0.5f + weights[k]*255));
+ while(total > 255)
+ {
+ loopk(4) if(v.weights[k] > 0 && total > 255) { v.weights[k]--; total--; }
+ }
+ while(total < 255)
+ {
+ loopk(4) if(v.weights[k] < 255 && total < 255) { v.weights[k]++; total++; }
+ }
+ loopk(4) v.bones[k] = 2*interpbones[k];
+ }
+ }
+ };
+
+
+ struct animcacheentry
+ {
+ animstate as[MAXANIMPARTS];
+ float pitch;
+ int millis;
+ uchar *partmask;
+ ragdolldata *ragdoll;
+
+ animcacheentry() : ragdoll(NULL)
+ {
+ loopk(MAXANIMPARTS) as[k].cur.fr1 = as[k].prev.fr1 = -1;
+ }
+
+ bool operator==(const animcacheentry &c) const
+ {
+ loopi(MAXANIMPARTS) if(as[i]!=c.as[i]) return false;
+ return pitch==c.pitch && partmask==c.partmask && ragdoll==c.ragdoll && (!ragdoll || min(millis, c.millis) >= ragdoll->lastmove);
+ }
+ };
+
+ struct vbocacheentry : animcacheentry
+ {
+ GLuint vbuf;
+ int owner;
+
+ vbocacheentry() : vbuf(0), owner(-1) {}
+ };
+
+ struct skelcacheentry : animcacheentry
+ {
+ dualquat *bdata;
+ int version;
+ bool dirty;
+
+ skelcacheentry() : bdata(NULL), version(-1), dirty(false) {}
+
+ void nextversion()
+ {
+ version = Shader::uniformlocversion();
+ dirty = true;
+ }
+ };
+
+ struct blendcacheentry : skelcacheentry
+ {
+ int owner;
+
+ blendcacheentry() : owner(-1) {}
+ };
+
+ struct skelmeshgroup;
+
+ struct skelmesh : mesh
+ {
+ vert *verts;
+ bumpvert *bumpverts;
+ tri *tris;
+ int numverts, numtris, maxweights;
+
+ int voffset, eoffset, elen;
+ ushort minvert, maxvert;
+
+ skelmesh() : verts(NULL), bumpverts(NULL), tris(NULL), numverts(0), numtris(0), maxweights(0)
+ {
+ }
+
+ virtual ~skelmesh()
+ {
+ DELETEA(verts);
+ DELETEA(bumpverts);
+ DELETEA(tris);
+ }
+
+ int addblendcombo(const blendcombo &c)
+ {
+ maxweights = max(maxweights, c.size());
+ return ((skelmeshgroup *)group)->addblendcombo(c);
+ }
+
+ void smoothnorms(float limit = 0, bool areaweight = true)
+ {
+ mesh::smoothnorms(verts, numverts, tris, numtris, limit, areaweight);
+ }
+
+ void buildnorms(bool areaweight = true)
+ {
+ mesh::buildnorms(verts, numverts, tris, numtris, areaweight);
+ }
+
+ void calctangents(bool areaweight = true)
+ {
+ if(bumpverts) return;
+ bumpverts = new bumpvert[numverts];
+ mesh::calctangents(bumpverts, verts, verts, numverts, tris, numtris, areaweight);
+ }
+
+ void calcbb(vec &bbmin, vec &bbmax, const matrix4x3 &m)
+ {
+ loopj(numverts)
+ {
+ vec v = m.transform(verts[j].pos);
+ loopi(3)
+ {
+ bbmin[i] = min(bbmin[i], v[i]);
+ bbmax[i] = max(bbmax[i], v[i]);
+ }
+ }
+ }
+
+ void genBIH(BIH::mesh &m)
+ {
+ m.tris = (const BIH::tri *)tris;
+ m.numtris = numtris;
+ m.pos = (const uchar *)&verts->pos;
+ m.posstride = sizeof(vert);
+ m.tc = (const uchar *)&verts->tc;
+ m.tcstride = sizeof(vert);
+ }
+
+ static inline void assignvert(vvertn &vv, int j, vert &v, blendcombo &c)
+ {
+ vv.pos = v.pos;
+ vv.norm = v.norm;
+ vv.tc = v.tc;
+ }
+
+ inline void assignvert(vvertbump &vv, int j, vert &v, blendcombo &c)
+ {
+ vv.pos = v.pos;
+ vv.tc = v.tc;
+ vv.tangent = bumpverts[j].tangent;
+ }
+
+ static inline void assignvert(vvertnw &vv, int j, vert &v, blendcombo &c)
+ {
+ vv.pos = v.pos;
+ vv.norm = v.norm;
+ vv.tc = v.tc;
+ c.serialize(vv);
+ }
+
+ inline void assignvert(vvertbumpw &vv, int j, vert &v, blendcombo &c)
+ {
+ vv.pos = v.pos;
+ vv.tc = v.tc;
+ vv.tangent = bumpverts[j].tangent;
+ c.serialize(vv);
+ }
+
+ template<class T>
+ int genvbo(vector<ushort> &idxs, int offset, vector<T> &vverts)
+ {
+ voffset = offset;
+ eoffset = idxs.length();
+ loopi(numverts)
+ {
+ vert &v = verts[i];
+ assignvert(vverts.add(), i, v, ((skelmeshgroup *)group)->blendcombos[v.blend]);
+ }
+ loopi(numtris) loopj(3) idxs.add(voffset + tris[i].vert[j]);
+ elen = idxs.length()-eoffset;
+ minvert = voffset;
+ maxvert = voffset + numverts-1;
+ return numverts;
+ }
+
+ template<class T>
+ int genvbo(vector<ushort> &idxs, int offset, vector<T> &vverts, int *htdata, int htlen)
+ {
+ voffset = offset;
+ eoffset = idxs.length();
+ minvert = 0xFFFF;
+ loopi(numtris)
+ {
+ tri &t = tris[i];
+ loopj(3)
+ {
+ int index = t.vert[j];
+ vert &v = verts[index];
+ T vv;
+ assignvert(vv, index, v, ((skelmeshgroup *)group)->blendcombos[v.blend]);
+ int htidx = hthash(v.pos)&(htlen-1);
+ loopk(htlen)
+ {
+ int &vidx = htdata[(htidx+k)&(htlen-1)];
+ if(vidx < 0) { vidx = idxs.add(ushort(vverts.length())); vverts.add(vv); break; }
+ else if(!memcmp(&vverts[vidx], &vv, sizeof(vv))) { minvert = min(minvert, idxs.add(ushort(vidx))); break; }
+ }
+ }
+ }
+ elen = idxs.length()-eoffset;
+ minvert = min(minvert, ushort(voffset));
+ maxvert = max(minvert, ushort(vverts.length()-1));
+ return vverts.length()-voffset;
+ }
+
+ int genvbo(vector<ushort> &idxs, int offset)
+ {
+ loopi(numverts) verts[i].interpindex = ((skelmeshgroup *)group)->remapblend(verts[i].blend);
+
+ voffset = offset;
+ eoffset = idxs.length();
+ loopi(numtris)
+ {
+ tri &t = tris[i];
+ loopj(3) idxs.add(voffset+t.vert[j]);
+ }
+ minvert = voffset;
+ maxvert = voffset + numverts-1;
+ elen = idxs.length()-eoffset;
+ return numverts;
+ }
+
+ template<class T>
+ static inline void fillvert(T &vv, int j, vert &v)
+ {
+ vv.tc = v.tc;
+ }
+
+ template<class T>
+ void fillverts(T *vdata)
+ {
+ vdata += voffset;
+ loopi(numverts) fillvert(vdata[i], i, verts[i]);
+ }
+
+ void interpverts(const dualquat * RESTRICT bdata1, const dualquat * RESTRICT bdata2, bool tangents, void * RESTRICT vdata, skin &s)
+ {
+ const int blendoffset = ((skelmeshgroup *)group)->skel->numgpubones;
+ bdata2 -= blendoffset;
+
+ #define IPLOOP(type, dosetup, dotransform) \
+ loopi(numverts) \
+ { \
+ const vert &src = verts[i]; \
+ type &dst = ((type * RESTRICT)vdata)[i]; \
+ dosetup; \
+ const dualquat &b = (src.interpindex < blendoffset ? bdata1 : bdata2)[src.interpindex]; \
+ dst.pos = b.transform(src.pos); \
+ dotransform; \
+ }
+
+ if(tangents)
+ {
+ IPLOOP(vvertbump, bumpvert &bsrc = bumpverts[i],
+ {
+ quat q = b.transform(bsrc.tangent);
+ fixqtangent(q, bsrc.tangent.w);
+ dst.tangent = q;
+ });
+ }
+ else
+ {
+ IPLOOP(vvertn, ,
+ {
+ dst.norm = b.transformnormal(src.norm);
+ });
+ }
+
+ #undef IPLOOP
+ }
+
+ void setshader(Shader *s)
+ {
+ skelmeshgroup *g = (skelmeshgroup *)group;
+ if(glaring)
+ {
+ if(!g->skel->usegpuskel) s->setvariant(0, 1);
+ else s->setvariant(min(maxweights, g->vweights), 1);
+ }
+ else if(!g->skel->usegpuskel) s->set();
+ else s->setvariant(min(maxweights, g->vweights)-1, 0);
+ }
+
+ void render(const animstate *as, skin &s, vbocacheentry &vc)
+ {
+ if(!Shader::lastshader) return;
+ glDrawRangeElements_(GL_TRIANGLES, minvert, maxvert, elen, GL_UNSIGNED_SHORT, &((skelmeshgroup *)group)->edata[eoffset]);
+ glde++;
+ xtravertsva += numverts;
+ }
+ };
+
+
+ struct tag
+ {
+ char *name;
+ int bone;
+ matrix4x3 matrix;
+
+ tag() : name(NULL) {}
+ ~tag() { DELETEA(name); }
+ };
+
+ struct skelanimspec
+ {
+ char *name;
+ int frame, range;
+
+ skelanimspec() : name(NULL), frame(0), range(0) {}
+ ~skelanimspec()
+ {
+ DELETEA(name);
+ }
+ };
+
+ struct boneinfo
+ {
+ const char *name;
+ int parent, children, next, group, scheduled, interpindex, interpparent, ragdollindex, correctindex;
+ float pitchscale, pitchoffset, pitchmin, pitchmax;
+ dualquat base, invbase;
+
+ boneinfo() : name(NULL), parent(-1), children(-1), next(-1), group(INT_MAX), scheduled(-1), interpindex(-1), interpparent(-1), ragdollindex(-1), correctindex(-1), pitchscale(0), pitchoffset(0), pitchmin(0), pitchmax(0) {}
+ ~boneinfo()
+ {
+ DELETEA(name);
+ }
+ };
+
+ struct antipode
+ {
+ int parent, child;
+
+ antipode(int parent, int child) : parent(parent), child(child) {}
+ };
+
+ struct pitchdep
+ {
+ int bone, parent;
+ dualquat pose;
+ };
+
+ struct pitchtarget
+ {
+ int bone, frame, corrects, deps;
+ float pitchmin, pitchmax, deviated;
+ dualquat pose;
+ };
+
+ struct pitchcorrect
+ {
+ int bone, target, parent;
+ float pitchmin, pitchmax, pitchscale, pitchangle, pitchtotal;
+
+ pitchcorrect() : parent(-1), pitchangle(0), pitchtotal(0) {}
+ };
+
+ struct skeleton
+ {
+ char *name;
+ int shared;
+ vector<skelmeshgroup *> users;
+ boneinfo *bones;
+ int numbones, numinterpbones, numgpubones, numframes;
+ dualquat *framebones;
+ vector<skelanimspec> skelanims;
+ vector<tag> tags;
+ vector<antipode> antipodes;
+ ragdollskel *ragdoll;
+ vector<pitchdep> pitchdeps;
+ vector<pitchtarget> pitchtargets;
+ vector<pitchcorrect> pitchcorrects;
+
+ bool usegpuskel;
+ vector<skelcacheentry> skelcache;
+ hashtable<GLuint, int> blendoffsets;
+
+ skeleton() : name(NULL), shared(0), bones(NULL), numbones(0), numinterpbones(0), numgpubones(0), numframes(0), framebones(NULL), ragdoll(NULL), usegpuskel(false), blendoffsets(32)
+ {
+ }
+
+ ~skeleton()
+ {
+ DELETEA(name);
+ DELETEA(bones);
+ DELETEA(framebones);
+ DELETEP(ragdoll);
+ loopv(skelcache)
+ {
+ DELETEA(skelcache[i].bdata);
+ }
+ }
+
+ skelanimspec *findskelanim(const char *name, char sep = '\0')
+ {
+ int len = sep ? strlen(name) : 0;
+ loopv(skelanims)
+ {
+ if(skelanims[i].name)
+ {
+ if(sep)
+ {
+ const char *end = strchr(skelanims[i].name, ':');
+ if(end && end - skelanims[i].name == len && !memcmp(name, skelanims[i].name, len)) return &skelanims[i];
+ }
+ if(!strcmp(name, skelanims[i].name)) return &skelanims[i];
+ }
+ }
+ return NULL;
+ }
+
+ skelanimspec &addskelanim(const char *name)
+ {
+ skelanimspec &sa = skelanims.add();
+ sa.name = name ? newstring(name) : NULL;
+ return sa;
+ }
+
+ int findbone(const char *name)
+ {
+ loopi(numbones) if(bones[i].name && !strcmp(bones[i].name, name)) return i;
+ return -1;
+ }
+
+ int findtag(const char *name)
+ {
+ loopv(tags) if(!strcmp(tags[i].name, name)) return i;
+ return -1;
+ }
+
+ bool addtag(const char *name, int bone, const matrix4x3 &matrix)
+ {
+ int idx = findtag(name);
+ if(idx >= 0)
+ {
+ if(!testtags) return false;
+ tag &t = tags[idx];
+ t.bone = bone;
+ t.matrix = matrix;
+ }
+ else
+ {
+ tag &t = tags.add();
+ t.name = newstring(name);
+ t.bone = bone;
+ t.matrix = matrix;
+ }
+ return true;
+ }
+
+ void calcantipodes()
+ {
+ antipodes.shrink(0);
+ vector<int> schedule;
+ loopi(numbones)
+ {
+ if(bones[i].group >= numbones)
+ {
+ bones[i].scheduled = schedule.length();
+ schedule.add(i);
+ }
+ else bones[i].scheduled = -1;
+ }
+ loopv(schedule)
+ {
+ int bone = schedule[i];
+ const boneinfo &info = bones[bone];
+ loopj(numbones) if(abs(bones[j].group) == bone && bones[j].scheduled < 0)
+ {
+ antipodes.add(antipode(info.interpindex, bones[j].interpindex));
+ bones[j].scheduled = schedule.length();
+ schedule.add(j);
+ }
+ if(i + 1 == schedule.length())
+ {
+ int conflict = INT_MAX;
+ loopj(numbones) if(bones[j].group < numbones && bones[j].scheduled < 0) conflict = min(conflict, abs(bones[j].group));
+ if(conflict < numbones)
+ {
+ bones[conflict].scheduled = schedule.length();
+ schedule.add(conflict);
+ }
+ }
+ }
+ }
+
+ void remapbones()
+ {
+ loopi(numbones)
+ {
+ boneinfo &info = bones[i];
+ info.interpindex = -1;
+ info.ragdollindex = -1;
+ }
+ numgpubones = 0;
+ loopv(users)
+ {
+ skelmeshgroup *group = users[i];
+ loopvj(group->blendcombos)
+ {
+ blendcombo &c = group->blendcombos[j];
+ loopk(4)
+ {
+ if(!c.weights[k]) { c.interpbones[k] = k > 0 ? c.interpbones[k-1] : 0; continue; }
+ boneinfo &info = bones[c.bones[k]];
+ if(info.interpindex < 0) info.interpindex = numgpubones++;
+ c.interpbones[k] = info.interpindex;
+ if(info.group < 0) continue;
+ loopl(4)
+ {
+ if(!c.weights[l]) break;
+ if(l == k) continue;
+ int parent = c.bones[l];
+ if(info.parent == parent || (info.parent >= 0 && info.parent == bones[parent].parent)) { info.group = -info.parent; break; }
+ if(info.group <= parent) continue;
+ int child = c.bones[k];
+ while(parent > child) parent = bones[parent].parent;
+ if(parent != child) info.group = c.bones[l];
+ }
+ }
+ }
+ }
+ numinterpbones = numgpubones;
+ loopv(tags)
+ {
+ boneinfo &info = bones[tags[i].bone];
+ if(info.interpindex < 0) info.interpindex = numinterpbones++;
+ }
+ if(ragdoll)
+ {
+ loopv(ragdoll->joints)
+ {
+ boneinfo &info = bones[ragdoll->joints[i].bone];
+ if(info.interpindex < 0) info.interpindex = numinterpbones++;
+ info.ragdollindex = i;
+ }
+ }
+ loopi(numbones)
+ {
+ boneinfo &info = bones[i];
+ if(info.interpindex < 0) continue;
+ for(int parent = info.parent; parent >= 0 && bones[parent].interpindex < 0; parent = bones[parent].parent)
+ bones[parent].interpindex = numinterpbones++;
+ }
+ loopi(numbones)
+ {
+ boneinfo &info = bones[i];
+ if(info.interpindex < 0) continue;
+ info.interpparent = info.parent >= 0 ? bones[info.parent].interpindex : -1;
+ }
+ if(ragdoll)
+ {
+ loopi(numbones)
+ {
+ boneinfo &info = bones[i];
+ if(info.interpindex < 0 || info.ragdollindex >= 0) continue;
+ for(int parent = info.parent; parent >= 0; parent = bones[parent].parent)
+ {
+ if(bones[parent].ragdollindex >= 0) { ragdoll->addreljoint(i, bones[parent].ragdollindex); break; }
+ }
+ }
+ }
+ calcantipodes();
+ }
+
+
+ void addpitchdep(int bone, int frame)
+ {
+ for(; bone >= 0; bone = bones[bone].parent)
+ {
+ int pos = pitchdeps.length();
+ loopvj(pitchdeps) if(bone <= pitchdeps[j].bone)
+ {
+ if(bone == pitchdeps[j].bone) goto nextbone;
+ pos = j;
+ break;
+ }
+ {
+ pitchdep d;
+ d.bone = bone;
+ d.parent = -1;
+ d.pose = framebones[frame*numbones + bone];
+ pitchdeps.insert(pos, d);
+ }
+ nextbone:;
+ }
+ }
+
+ int findpitchdep(int bone)
+ {
+ loopv(pitchdeps) if(bone <= pitchdeps[i].bone) return bone == pitchdeps[i].bone ? i : -1;
+ return -1;
+ }
+
+ int findpitchcorrect(int bone)
+ {
+ loopv(pitchcorrects) if(bone <= pitchcorrects[i].bone) return bone == pitchcorrects[i].bone ? i : -1;
+ return -1;
+ }
+
+ void initpitchdeps()
+ {
+ pitchdeps.setsize(0);
+ if(pitchtargets.empty()) return;
+ loopv(pitchtargets)
+ {
+ pitchtarget &t = pitchtargets[i];
+ t.deps = -1;
+ addpitchdep(t.bone, t.frame);
+ }
+ loopv(pitchdeps)
+ {
+ pitchdep &d = pitchdeps[i];
+ int parent = bones[d.bone].parent;
+ if(parent >= 0)
+ {
+ int j = findpitchdep(parent);
+ if(j >= 0)
+ {
+ d.parent = j;
+ d.pose.mul(pitchdeps[j].pose, dualquat(d.pose));
+ }
+ }
+ }
+ loopv(pitchtargets)
+ {
+ pitchtarget &t = pitchtargets[i];
+ int j = findpitchdep(t.bone);
+ if(j >= 0)
+ {
+ t.deps = j;
+ t.pose = pitchdeps[j].pose;
+ }
+ t.corrects = -1;
+ for(int parent = t.bone; parent >= 0; parent = bones[parent].parent)
+ {
+ t.corrects = findpitchcorrect(parent);
+ if(t.corrects >= 0) break;
+ }
+ }
+ loopv(pitchcorrects)
+ {
+ pitchcorrect &c = pitchcorrects[i];
+ bones[c.bone].correctindex = i;
+ c.parent = -1;
+ for(int parent = c.bone;;)
+ {
+ parent = bones[parent].parent;
+ if(parent < 0) break;
+ c.parent = findpitchcorrect(parent);
+ if(c.parent >= 0) break;
+ }
+ }
+ }
+
+ void optimize()
+ {
+ cleanup();
+ if(ragdoll) ragdoll->setup();
+ remapbones();
+ initpitchdeps();
+ }
+
+ void expandbonemask(uchar *expansion, int bone, int val)
+ {
+ expansion[bone] = val;
+ bone = bones[bone].children;
+ while(bone>=0) { expandbonemask(expansion, bone, val); bone = bones[bone].next; }
+ }
+
+ void applybonemask(ushort *mask, uchar *partmask, int partindex)
+ {
+ if(!mask || *mask==BONEMASK_END) return;
+ uchar *expansion = new uchar[numbones];
+ memset(expansion, *mask&BONEMASK_NOT ? 1 : 0, numbones);
+ while(*mask!=BONEMASK_END)
+ {
+ expandbonemask(expansion, *mask&BONEMASK_BONE, *mask&BONEMASK_NOT ? 0 : 1);
+ mask++;
+ }
+ loopi(numbones) if(expansion[i]) partmask[i] = partindex;
+ delete[] expansion;
+ }
+
+ void linkchildren()
+ {
+ loopi(numbones)
+ {
+ boneinfo &b = bones[i];
+ b.children = -1;
+ if(b.parent<0) b.next = -1;
+ else
+ {
+ b.next = bones[b.parent].children;
+ bones[b.parent].children = i;
+ }
+ }
+ }
+
+ int availgpubones() const { return min(maxvsuniforms - reservevpparams - 10, maxskelanimdata) / 2; }
+ bool gpuaccelerate() const { return numframes && gpuskel && numgpubones<=availgpubones(); }
+
+ float calcdeviation(const vec &axis, const vec &forward, const dualquat &pose1, const dualquat &pose2)
+ {
+ vec forward1 = pose1.transformnormal(forward).project(axis).normalize(),
+ forward2 = pose2.transformnormal(forward).project(axis).normalize(),
+ daxis = vec().cross(forward1, forward2);
+ float dx = clamp(forward1.dot(forward2), -1.0f, 1.0f), dy = clamp(daxis.magnitude(), -1.0f, 1.0f);
+ if(daxis.dot(axis) < 0) dy = -dy;
+ return atan2f(dy, dx)/RAD;
+ }
+
+ void calcpitchcorrects(float pitch, const vec &axis, const vec &forward)
+ {
+ loopv(pitchtargets)
+ {
+ pitchtarget &t = pitchtargets[i];
+ t.deviated = calcdeviation(axis, forward, t.pose, pitchdeps[t.deps].pose);
+ }
+ loopv(pitchcorrects)
+ {
+ pitchcorrect &c = pitchcorrects[i];
+ c.pitchangle = c.pitchtotal = 0;
+ }
+ loopvj(pitchtargets)
+ {
+ pitchtarget &t = pitchtargets[j];
+ float tpitch = pitch - t.deviated;
+ for(int parent = t.corrects; parent >= 0; parent = pitchcorrects[parent].parent)
+ tpitch -= pitchcorrects[parent].pitchangle;
+ if(t.pitchmin || t.pitchmax) tpitch = clamp(tpitch, t.pitchmin, t.pitchmax);
+ loopv(pitchcorrects)
+ {
+ pitchcorrect &c = pitchcorrects[i];
+ if(c.target != j) continue;
+ float total = c.parent >= 0 ? pitchcorrects[c.parent].pitchtotal : 0,
+ avail = tpitch - total,
+ used = tpitch*c.pitchscale;
+ if(c.pitchmin || c.pitchmax)
+ {
+ if(used < 0) used = clamp(c.pitchmin, used, 0.0f);
+ else used = clamp(c.pitchmax, 0.0f, used);
+ }
+ if(used < 0) used = clamp(avail, used, 0.0f);
+ else used = clamp(avail, 0.0f, used);
+ c.pitchangle = used;
+ c.pitchtotal = used + total;
+ }
+ }
+ }
+
+ #define INTERPBONE(bone) \
+ const animstate &s = as[partmask[bone]]; \
+ const framedata &f = partframes[partmask[bone]]; \
+ dualquat d; \
+ (d = f.fr1[bone]).mul((1-s.cur.t)*s.interp); \
+ d.accumulate(f.fr2[bone], s.cur.t*s.interp); \
+ if(s.interp<1) \
+ { \
+ d.accumulate(f.pfr1[bone], (1-s.prev.t)*(1-s.interp)); \
+ d.accumulate(f.pfr2[bone], s.prev.t*(1-s.interp)); \
+ }
+
+ void interpbones(const animstate *as, float pitch, const vec &axis, const vec &forward, int numanimparts, const uchar *partmask, skelcacheentry &sc)
+ {
+ if(!sc.bdata) sc.bdata = new dualquat[numinterpbones];
+ sc.nextversion();
+ struct framedata
+ {
+ const dualquat *fr1, *fr2, *pfr1, *pfr2;
+ } partframes[MAXANIMPARTS];
+ loopi(numanimparts)
+ {
+ partframes[i].fr1 = &framebones[as[i].cur.fr1*numbones];
+ partframes[i].fr2 = &framebones[as[i].cur.fr2*numbones];
+ if(as[i].interp<1)
+ {
+ partframes[i].pfr1 = &framebones[as[i].prev.fr1*numbones];
+ partframes[i].pfr2 = &framebones[as[i].prev.fr2*numbones];
+ }
+ }
+ loopv(pitchdeps)
+ {
+ pitchdep &p = pitchdeps[i];
+ INTERPBONE(p.bone);
+ d.normalize();
+ if(p.parent >= 0) p.pose.mul(pitchdeps[p.parent].pose, d);
+ else p.pose = d;
+ }
+ calcpitchcorrects(pitch, axis, forward);
+ loopi(numbones) if(bones[i].interpindex>=0)
+ {
+ INTERPBONE(i);
+ const boneinfo &b = bones[i];
+ d.normalize();
+ if(b.interpparent<0) sc.bdata[b.interpindex] = d;
+ else sc.bdata[b.interpindex].mul(sc.bdata[b.interpparent], d);
+ float angle;
+ if(b.pitchscale) { angle = b.pitchscale*pitch + b.pitchoffset; if(b.pitchmin || b.pitchmax) angle = clamp(angle, b.pitchmin, b.pitchmax); }
+ else if(b.correctindex >= 0) angle = pitchcorrects[b.correctindex].pitchangle;
+ else continue;
+ if(as->cur.anim&ANIM_NOPITCH || (as->interp < 1 && as->prev.anim&ANIM_NOPITCH))
+ angle *= (as->cur.anim&ANIM_NOPITCH ? 0 : as->interp) + (as->interp < 1 && as->prev.anim&ANIM_NOPITCH ? 0 : 1-as->interp);
+ sc.bdata[b.interpindex].mulorient(quat(axis, angle*RAD), b.base);
+ }
+ loopv(antipodes) sc.bdata[antipodes[i].child].fixantipodal(sc.bdata[antipodes[i].parent]);
+ }
+
+ void initragdoll(ragdolldata &d, skelcacheentry &sc, part *p)
+ {
+ const dualquat *bdata = sc.bdata;
+ loopv(ragdoll->joints)
+ {
+ const ragdollskel::joint &j = ragdoll->joints[i];
+ const boneinfo &b = bones[j.bone];
+ const dualquat &q = bdata[b.interpindex];
+ loopk(3) if(j.vert[k] >= 0)
+ {
+ ragdollskel::vert &v = ragdoll->verts[j.vert[k]];
+ ragdolldata::vert &dv = d.verts[j.vert[k]];
+ dv.pos.add(q.transform(v.pos).mul(v.weight));
+ }
+ }
+ if(ragdoll->animjoints) loopv(ragdoll->joints)
+ {
+ const ragdollskel::joint &j = ragdoll->joints[i];
+ const boneinfo &b = bones[j.bone];
+ const dualquat &q = bdata[b.interpindex];
+ d.calcanimjoint(i, matrix4x3(q));
+ }
+ loopv(ragdoll->verts)
+ {
+ ragdolldata::vert &dv = d.verts[i];
+ matrixstack[matrixpos].transform(vec(dv.pos).add(p->translate).mul(p->model->scale), dv.pos);
+ }
+ loopv(ragdoll->reljoints)
+ {
+ const ragdollskel::reljoint &r = ragdoll->reljoints[i];
+ const ragdollskel::joint &j = ragdoll->joints[r.parent];
+ const boneinfo &br = bones[r.bone], &bj = bones[j.bone];
+ d.reljoints[i].mul(dualquat(bdata[bj.interpindex]).invert(), bdata[br.interpindex]);
+ }
+ }
+
+ void genragdollbones(ragdolldata &d, skelcacheentry &sc, part *p)
+ {
+ if(!sc.bdata) sc.bdata = new dualquat[numinterpbones];
+ sc.nextversion();
+ loopv(ragdoll->joints)
+ {
+ const ragdollskel::joint &j = ragdoll->joints[i];
+ const boneinfo &b = bones[j.bone];
+ vec pos(0, 0, 0);
+ loopk(3) if(j.vert[k]>=0) pos.add(d.verts[j.vert[k]].pos);
+ pos.mul(j.weight/p->model->scale).sub(p->translate);
+ matrix4x3 m;
+ m.mul(d.tris[j.tri], pos, d.animjoints ? d.animjoints[i] : j.orient);
+ sc.bdata[b.interpindex] = dualquat(m);
+ }
+ loopv(ragdoll->reljoints)
+ {
+ const ragdollskel::reljoint &r = ragdoll->reljoints[i];
+ const ragdollskel::joint &j = ragdoll->joints[r.parent];
+ const boneinfo &br = bones[r.bone], &bj = bones[j.bone];
+ sc.bdata[br.interpindex].mul(sc.bdata[bj.interpindex], d.reljoints[i]);
+ }
+ loopv(antipodes) sc.bdata[antipodes[i].child].fixantipodal(sc.bdata[antipodes[i].parent]);
+ }
+
+ void concattagtransform(part *p, int i, const matrix4x3 &m, matrix4x3 &n)
+ {
+ matrix4x3 t;
+ t.mul(bones[tags[i].bone].base, tags[i].matrix);
+ t.posttranslate(p->translate, p->model->scale);
+ n.mul(m, t);
+ }
+
+ void calctags(part *p, skelcacheentry *sc = NULL)
+ {
+ loopv(p->links)
+ {
+ linkedpart &l = p->links[i];
+ tag &t = tags[l.tag];
+ dualquat q;
+ if(sc) q.mul(sc->bdata[bones[t.bone].interpindex], bones[t.bone].base);
+ else q = bones[t.bone].base;
+ matrix4x3 m;
+ m.mul(q, t.matrix);
+ m.d.add(p->translate).mul(p->model->scale);
+ l.matrix = m;
+ }
+ }
+
+ void cleanup(bool full = true)
+ {
+ loopv(skelcache)
+ {
+ skelcacheentry &sc = skelcache[i];
+ loopj(MAXANIMPARTS) sc.as[j].cur.fr1 = -1;
+ DELETEA(sc.bdata);
+ }
+ skelcache.setsize(0);
+ blendoffsets.clear();
+ if(full) loopv(users) users[i]->cleanup();
+ }
+
+ bool canpreload() { return !numframes || gpuaccelerate(); }
+
+ void preload()
+ {
+ if(!numframes) return;
+ if(skelcache.empty())
+ {
+ usegpuskel = gpuaccelerate();
+ }
+ }
+
+ skelcacheentry &checkskelcache(part *p, const animstate *as, float pitch, const vec &axis, const vec &forward, ragdolldata *rdata)
+ {
+ if(skelcache.empty())
+ {
+ usegpuskel = gpuaccelerate();
+ }
+
+ int numanimparts = ((skelpart *)as->owner)->numanimparts;
+ uchar *partmask = ((skelpart *)as->owner)->partmask;
+ skelcacheentry *sc = NULL;
+ bool match = false;
+ loopv(skelcache)
+ {
+ skelcacheentry &c = skelcache[i];
+ loopj(numanimparts) if(c.as[j]!=as[j]) goto mismatch;
+ if(c.pitch != pitch || c.partmask != partmask || c.ragdoll != rdata || (rdata && c.millis < rdata->lastmove)) goto mismatch;
+ match = true;
+ sc = &c;
+ break;
+ mismatch:
+ if(c.millis < lastmillis) { sc = &c; break; }
+ }
+ if(!sc) sc = &skelcache.add();
+ if(!match)
+ {
+ loopi(numanimparts) sc->as[i] = as[i];
+ sc->pitch = pitch;
+ sc->partmask = partmask;
+ sc->ragdoll = rdata;
+ if(rdata) genragdollbones(*rdata, *sc, p);
+ else interpbones(as, pitch, axis, forward, numanimparts, partmask, *sc);
+ }
+ sc->millis = lastmillis;
+ return *sc;
+ }
+
+ int getblendoffset(UniformLoc &u)
+ {
+ int &offset = blendoffsets.access(Shader::lastshader->program, -1);
+ if(offset < 0)
+ {
+ defformatstring(offsetname, "%s[%d]", u.name, 2*numgpubones);
+ offset = glGetUniformLocation_(Shader::lastshader->program, offsetname);
+ }
+ return offset;
+ }
+
+ void setglslbones(UniformLoc &u, skelcacheentry &sc, skelcacheentry &bc, int count)
+ {
+ if(u.version == bc.version && u.data == bc.bdata) return;
+ glUniform4fv_(u.loc, 2*numgpubones, sc.bdata[0].real.v);
+ if(count > 0)
+ {
+ int offset = getblendoffset(u);
+ if(offset >= 0) glUniform4fv_(offset, 2*count, bc.bdata[0].real.v);
+ }
+ u.version = bc.version;
+ u.data = bc.bdata;
+ }
+
+ void setgpubones(skelcacheentry &sc, blendcacheentry *bc, int count)
+ {
+ if(!Shader::lastshader) return;
+ if(Shader::lastshader->uniformlocs.length() < 1) return;
+ UniformLoc &u = Shader::lastshader->uniformlocs[0];
+ setglslbones(u, sc, bc ? *bc : sc, count);
+ }
+
+ bool shouldcleanup() const
+ {
+ return numframes && (skelcache.empty() || gpuaccelerate()!=usegpuskel);
+ }
+ };
+
+ struct skelmeshgroup : meshgroup
+ {
+ skeleton *skel;
+
+ vector<blendcombo> blendcombos;
+ int numblends[4];
+
+ static const int MAXBLENDCACHE = 16;
+ blendcacheentry blendcache[MAXBLENDCACHE];
+
+ static const int MAXVBOCACHE = 16;
+ vbocacheentry vbocache[MAXVBOCACHE];
+
+ ushort *edata;
+ GLuint ebuf;
+ bool vtangents;
+ int vlen, vertsize, vblends, vweights;
+ uchar *vdata;
+
+ skelmeshgroup() : skel(NULL), edata(NULL), ebuf(0), vtangents(false), vlen(0), vertsize(0), vblends(0), vweights(0), vdata(NULL)
+ {
+ memset(numblends, 0, sizeof(numblends));
+ }
+
+ virtual ~skelmeshgroup()
+ {
+ if(skel)
+ {
+ if(skel->shared) skel->users.removeobj(this);
+ else DELETEP(skel);
+ }
+ if(ebuf) glDeleteBuffers_(1, &ebuf);
+ loopi(MAXBLENDCACHE)
+ {
+ DELETEA(blendcache[i].bdata);
+ }
+ loopi(MAXVBOCACHE)
+ {
+ if(vbocache[i].vbuf) glDeleteBuffers_(1, &vbocache[i].vbuf);
+ }
+ DELETEA(vdata);
+ }
+
+ void shareskeleton(char *name)
+ {
+ if(!name)
+ {
+ skel = new skeleton;
+ skel->users.add(this);
+ return;
+ }
+
+ static hashnameset<skeleton *> skeletons;
+ if(skeletons.access(name)) skel = skeletons[name];
+ else
+ {
+ skel = new skeleton;
+ skel->name = newstring(name);
+ skeletons.add(skel);
+ }
+ skel->users.add(this);
+ skel->shared++;
+ }
+
+ int findtag(const char *name)
+ {
+ return skel->findtag(name);
+ }
+
+ void *animkey() { return skel; }
+ int totalframes() const { return max(skel->numframes, 1); }
+
+ virtual skelanimspec *loadanim(const char *filename) { (void) filename; return NULL; }
+
+ void genvbo(bool tangents, vbocacheentry &vc)
+ {
+ if(!vc.vbuf) glGenBuffers_(1, &vc.vbuf);
+ if(ebuf) return;
+
+ vector<ushort> idxs;
+
+ if(tangents) loopv(meshes) ((skelmesh *)meshes[i])->calctangents();
+
+ vtangents = tangents;
+ vlen = 0;
+ vblends = 0;
+ if(skel->numframes && !skel->usegpuskel)
+ {
+ vweights = 1;
+ loopv(blendcombos)
+ {
+ blendcombo &c = blendcombos[i];
+ c.interpindex = c.weights[1] ? skel->numgpubones + vblends++ : -1;
+ }
+
+ vertsize = tangents ? sizeof(vvertbump) : sizeof(vvertn);
+ loopv(meshes) vlen += ((skelmesh *)meshes[i])->genvbo(idxs, vlen);
+ DELETEA(vdata);
+ vdata = new uchar[vlen*vertsize];
+ #define FILLVDATA(type) do { \
+ loopv(meshes) ((skelmesh *)meshes[i])->fillverts((type *)vdata); \
+ } while(0)
+ if(tangents) FILLVDATA(vvertbump);
+ else FILLVDATA(vvertn);
+ #undef FILLVDATA
+ }
+ else
+ {
+ if(skel->numframes)
+ {
+ vweights = 4;
+ int availbones = skel->availgpubones() - skel->numgpubones;
+ while(vweights > 1 && availbones >= numblends[vweights-1]) availbones -= numblends[--vweights];
+ loopv(blendcombos)
+ {
+ blendcombo &c = blendcombos[i];
+ c.interpindex = c.size() > vweights ? skel->numgpubones + vblends++ : -1;
+ }
+ }
+ else
+ {
+ vweights = 0;
+ loopv(blendcombos) blendcombos[i].interpindex = -1;
+ }
+
+ gle::bindvbo(vc.vbuf);
+ #define GENVBO(type, args) do { \
+ vertsize = sizeof(type); \
+ vector<type> vverts; \
+ loopv(meshes) vlen += ((skelmesh *)meshes[i])->genvbo args; \
+ glBufferData_(GL_ARRAY_BUFFER, vverts.length()*sizeof(type), vverts.getbuf(), GL_STATIC_DRAW); \
+ } while(0)
+ #define GENVBOANIM(type) GENVBO(type, (idxs, vlen, vverts))
+ #define GENVBOSTAT(type) GENVBO(type, (idxs, vlen, vverts, htdata, htlen))
+ if(skel->numframes)
+ {
+ if(tangents) GENVBOANIM(vvertbumpw);
+ else GENVBOANIM(vvertnw);
+ }
+ else
+ {
+ int numverts = 0, htlen = 128;
+ loopv(meshes) numverts += ((skelmesh *)meshes[i])->numverts;
+ while(htlen < numverts) htlen *= 2;
+ if(numverts*4 > htlen*3) htlen *= 2;
+ int *htdata = new int[htlen];
+ memset(htdata, -1, htlen*sizeof(int));
+ if(tangents) GENVBOSTAT(vvertbump);
+ else GENVBOSTAT(vvertn);
+ delete[] htdata;
+ }
+ #undef GENVBO
+ #undef GENVBOANIM
+ #undef GENVBOSTAT
+ gle::clearvbo();
+ }
+
+ glGenBuffers_(1, &ebuf);
+ gle::bindebo(ebuf);
+ glBufferData_(GL_ELEMENT_ARRAY_BUFFER, idxs.length()*sizeof(ushort), idxs.getbuf(), GL_STATIC_DRAW);
+ gle::clearebo();
+ }
+
+ void bindvbo(const animstate *as, vbocacheentry &vc, skelcacheentry *sc = NULL, blendcacheentry *bc = NULL)
+ {
+ vvert *vverts = 0;
+ bindpos(ebuf, vc.vbuf, &vverts->pos, vertsize);
+ if(as->cur.anim&ANIM_NOSKIN)
+ {
+ if(enabletc) disabletc();
+ if(enablenormals) disablenormals();
+ if(enabletangents) disabletangents();
+ }
+ else
+ {
+ if(vtangents)
+ {
+ if(enablenormals) disablenormals();
+ vvertbump *vvertbumps = 0;
+ bindtangents(&vvertbumps->tangent, vertsize);
+ }
+ else
+ {
+ if(enabletangents) disabletangents();
+ vvertn *vvertns = 0;
+ bindnormals(&vvertns->norm, vertsize);
+ }
+
+ bindtc(&vverts->tc, vertsize);
+ }
+ if(!sc || !skel->usegpuskel)
+ {
+ if(enablebones) disablebones();
+ }
+ else
+ {
+ if(vtangents)
+ {
+ vvertbumpw *vvertbumpws = 0;
+ bindbones(vvertbumpws->weights, vvertbumpws->bones, vertsize);
+ }
+ else
+ {
+ vvertnw *vvertnws = 0;
+ bindbones(vvertnws->weights, vvertnws->bones, vertsize);
+ }
+ }
+ }
+
+ void concattagtransform(part *p, int i, const matrix4x3 &m, matrix4x3 &n)
+ {
+ skel->concattagtransform(p, i, m, n);
+ }
+
+ int addblendcombo(const blendcombo &c)
+ {
+ loopv(blendcombos) if(blendcombos[i]==c)
+ {
+ blendcombos[i].uses += c.uses;
+ return i;
+ }
+ numblends[c.size()-1]++;
+ blendcombo &a = blendcombos.add(c);
+ return a.interpindex = blendcombos.length()-1;
+ }
+
+ void sortblendcombos()
+ {
+ blendcombos.sort(blendcombo::sortcmp);
+ int *remap = new int[blendcombos.length()];
+ loopv(blendcombos) remap[blendcombos[i].interpindex] = i;
+ loopv(meshes)
+ {
+ skelmesh *m = (skelmesh *)meshes[i];
+ loopj(m->numverts)
+ {
+ vert &v = m->verts[j];
+ v.blend = remap[v.blend];
+ }
+ }
+ delete[] remap;
+ }
+
+ int remapblend(int blend)
+ {
+ const blendcombo &c = blendcombos[blend];
+ return c.weights[1] ? c.interpindex : c.interpbones[0];
+ }
+
+ static inline void blendbones(dualquat &d, const dualquat *bdata, const blendcombo &c)
+ {
+ d = bdata[c.interpbones[0]];
+ d.mul(c.weights[0]);
+ d.accumulate(bdata[c.interpbones[1]], c.weights[1]);
+ if(c.weights[2])
+ {
+ d.accumulate(bdata[c.interpbones[2]], c.weights[2]);
+ if(c.weights[3]) d.accumulate(bdata[c.interpbones[3]], c.weights[3]);
+ }
+ }
+
+ void blendbones(const skelcacheentry &sc, blendcacheentry &bc)
+ {
+ bc.nextversion();
+ if(!bc.bdata) bc.bdata = new dualquat[vblends];
+ dualquat *dst = bc.bdata - skel->numgpubones;
+ bool normalize = !skel->usegpuskel || vweights<=1;
+ loopv(blendcombos)
+ {
+ const blendcombo &c = blendcombos[i];
+ if(c.interpindex<0) break;
+ dualquat &d = dst[c.interpindex];
+ blendbones(d, sc.bdata, c);
+ if(normalize) d.normalize();
+ }
+ }
+
+ void cleanup()
+ {
+ loopi(MAXBLENDCACHE)
+ {
+ blendcacheentry &c = blendcache[i];
+ DELETEA(c.bdata);
+ c.owner = -1;
+ }
+ loopi(MAXVBOCACHE)
+ {
+ vbocacheentry &c = vbocache[i];
+ if(c.vbuf) { glDeleteBuffers_(1, &c.vbuf); c.vbuf = 0; }
+ c.owner = -1;
+ }
+ if(ebuf) { glDeleteBuffers_(1, &ebuf); ebuf = 0; }
+ if(skel) skel->cleanup(false);
+ }
+
+ #define SEARCHCACHE(cachesize, cacheentry, cache, reusecheck) \
+ loopi(cachesize) \
+ { \
+ cacheentry &c = cache[i]; \
+ if(c.owner==owner) \
+ { \
+ if(c==sc) return c; \
+ else c.owner = -1; \
+ break; \
+ } \
+ } \
+ loopi(cachesize-1) \
+ { \
+ cacheentry &c = cache[i]; \
+ if(reusecheck c.owner < 0 || c.millis < lastmillis) \
+ return c; \
+ } \
+ return cache[cachesize-1];
+
+ vbocacheentry &checkvbocache(skelcacheentry &sc, int owner)
+ {
+ SEARCHCACHE(MAXVBOCACHE, vbocacheentry, vbocache, !c.vbuf || );
+ }
+
+ blendcacheentry &checkblendcache(skelcacheentry &sc, int owner)
+ {
+ SEARCHCACHE(MAXBLENDCACHE, blendcacheentry, blendcache, )
+ }
+
+ void preload(part *p)
+ {
+ if(!skel->canpreload()) return;
+ bool tangents = false;
+ loopv(p->skins) if(p->skins[i].tangents()) tangents = true;
+ if(skel->shouldcleanup()) skel->cleanup();
+ else if(tangents!=vtangents) cleanup();
+ skel->preload();
+ if(!vbocache->vbuf) genvbo(tangents, *vbocache);
+ }
+
+ void render(const animstate *as, float pitch, const vec &axis, const vec &forward, dynent *d, part *p)
+ {
+ bool tangents = false;
+ loopv(p->skins) if(p->skins[i].tangents()) tangents = true;
+ if(skel->shouldcleanup()) { skel->cleanup(); disablevbo(); }
+ else if(tangents!=vtangents) { cleanup(); disablevbo(); }
+
+ if(!skel->numframes)
+ {
+ if(!(as->cur.anim&ANIM_NORENDER))
+ {
+ if(!vbocache->vbuf) genvbo(tangents, *vbocache);
+ bindvbo(as, *vbocache);
+ loopv(meshes)
+ {
+ skelmesh *m = (skelmesh *)meshes[i];
+ p->skins[i].bind(m, as);
+ m->render(as, p->skins[i], *vbocache);
+ }
+ }
+ skel->calctags(p);
+ return;
+ }
+
+ skelcacheentry &sc = skel->checkskelcache(p, as, pitch, axis, forward, as->cur.anim&ANIM_RAGDOLL || !d || !d->ragdoll || d->ragdoll->skel != skel->ragdoll ? NULL : d->ragdoll);
+ if(!(as->cur.anim&ANIM_NORENDER))
+ {
+ int owner = &sc-&skel->skelcache[0];
+ vbocacheentry &vc = skel->usegpuskel ? *vbocache : checkvbocache(sc, owner);
+ vc.millis = lastmillis;
+ if(!vc.vbuf) genvbo(tangents, vc);
+ blendcacheentry *bc = NULL;
+ if(vblends)
+ {
+ bc = &checkblendcache(sc, owner);
+ bc->millis = lastmillis;
+ if(bc->owner!=owner)
+ {
+ bc->owner = owner;
+ *(animcacheentry *)bc = sc;
+ blendbones(sc, *bc);
+ }
+ }
+ if(!skel->usegpuskel && vc.owner!=owner)
+ {
+ vc.owner = owner;
+ (animcacheentry &)vc = sc;
+ loopv(meshes)
+ {
+ skelmesh &m = *(skelmesh *)meshes[i];
+ m.interpverts(sc.bdata, bc ? bc->bdata : NULL, tangents, vdata + m.voffset*vertsize, p->skins[i]);
+ }
+ gle::bindvbo(vc.vbuf);
+ glBufferData_(GL_ARRAY_BUFFER, vlen*vertsize, vdata, GL_STREAM_DRAW);
+ }
+
+ bindvbo(as, vc, &sc, bc);
+ loopv(meshes)
+ {
+ skelmesh *m = (skelmesh *)meshes[i];
+ p->skins[i].bind(m, as);
+ if(skel->usegpuskel) skel->setgpubones(sc, bc, vblends);
+ m->render(as, p->skins[i], vc);
+ }
+ }
+
+ skel->calctags(p, &sc);
+
+ if(as->cur.anim&ANIM_RAGDOLL && skel->ragdoll && !d->ragdoll)
+ {
+ d->ragdoll = new ragdolldata(skel->ragdoll, p->model->scale);
+ skel->initragdoll(*d->ragdoll, sc, p);
+ d->ragdoll->init(d);
+ }
+ }
+ };
+
+ struct animpartmask
+ {
+ animpartmask *next;
+ int numbones;
+ uchar bones[1];
+ };
+
+ struct skelpart : part
+ {
+ animpartmask *buildingpartmask;
+
+ uchar *partmask;
+
+ skelpart(animmodel *model, int index = 0) : part(model, index), buildingpartmask(NULL), partmask(NULL)
+ {
+ }
+
+ virtual ~skelpart()
+ {
+ DELETEA(buildingpartmask);
+ }
+
+ uchar *sharepartmask(animpartmask *o)
+ {
+ static animpartmask *partmasks = NULL;
+ animpartmask *p = partmasks;
+ for(; p; p = p->next) if(p->numbones==o->numbones && !memcmp(p->bones, o->bones, p->numbones))
+ {
+ delete[] (uchar *)o;
+ return p->bones;
+ }
+
+ o->next = p;
+ partmasks = o;
+ return o->bones;
+ }
+
+ animpartmask *newpartmask()
+ {
+ animpartmask *p = (animpartmask *)new uchar[sizeof(animpartmask) + ((skelmeshgroup *)meshes)->skel->numbones-1];
+ p->numbones = ((skelmeshgroup *)meshes)->skel->numbones;
+ memset(p->bones, 0, p->numbones);
+ return p;
+ }
+
+ void initanimparts()
+ {
+ DELETEA(buildingpartmask);
+ buildingpartmask = newpartmask();
+ }
+
+ bool addanimpart(ushort *bonemask)
+ {
+ if(!buildingpartmask || numanimparts>=MAXANIMPARTS) return false;
+ ((skelmeshgroup *)meshes)->skel->applybonemask(bonemask, buildingpartmask->bones, numanimparts);
+ numanimparts++;
+ return true;
+ }
+
+ void endanimparts()
+ {
+ if(buildingpartmask)
+ {
+ partmask = sharepartmask(buildingpartmask);
+ buildingpartmask = NULL;
+ }
+
+ ((skelmeshgroup *)meshes)->skel->optimize();
+ }
+
+ void loaded()
+ {
+ endanimparts();
+ part::loaded();
+ }
+ };
+
+ skelmodel(const char *name) : animmodel(name)
+ {
+ }
+
+ int linktype(animmodel *m) const
+ {
+ return type()==m->type() &&
+ ((skelmeshgroup *)parts[0]->meshes)->skel == ((skelmeshgroup *)m->parts[0]->meshes)->skel ?
+ LINK_REUSE :
+ LINK_TAG;
+ }
+
+ bool skeletal() const { return true; }
+
+ skelpart &addpart()
+ {
+ flushpart();
+ skelpart *p = new skelpart(this, parts.length());
+ parts.add(p);
+ return *p;
+ }
};
struct skeladjustment
{
- float yaw, pitch, roll;
- vec translate;
-
- skeladjustment(float yaw, float pitch, float roll, const vec &translate) : yaw(yaw), pitch(pitch), roll(roll), translate(translate) {}
-
- void adjust(dualquat &dq)
- {
- if(yaw) dq.mulorient(quat(vec(0, 0, 1), yaw*RAD));
- if(pitch) dq.mulorient(quat(vec(0, -1, 0), pitch*RAD));
- if(roll) dq.mulorient(quat(vec(-1, 0, 0), roll*RAD));
- if(!translate.iszero()) dq.translate(translate);
- }
+ float yaw, pitch, roll;
+ vec translate;
+
+ skeladjustment(float yaw, float pitch, float roll, const vec &translate) : yaw(yaw), pitch(pitch), roll(roll), translate(translate) {}
+
+ void adjust(dualquat &dq)
+ {
+ if(yaw) dq.mulorient(quat(vec(0, 0, 1), yaw*RAD));
+ if(pitch) dq.mulorient(quat(vec(0, -1, 0), pitch*RAD));
+ if(roll) dq.mulorient(quat(vec(-1, 0, 0), roll*RAD));
+ if(!translate.iszero()) dq.translate(translate);
+ }
};
template<class MDL> struct skelloader : modelloader<MDL, skelmodel>
{
- static vector<skeladjustment> adjustments;
+ static vector<skeladjustment> adjustments;
- skelloader(const char *name) : modelloader<MDL, skelmodel>(name) {}
+ skelloader(const char *name) : modelloader<MDL, skelmodel>(name) {}
- void flushpart()
- {
- adjustments.setsize(0);
- }
+ void flushpart()
+ {
+ adjustments.setsize(0);
+ }
};
template<class MDL> vector<skeladjustment> skelloader<MDL>::adjustments;
template<class MDL> struct skelcommands : modelcommands<MDL, struct MDL::skelmesh>
{
- typedef modelcommands<MDL, struct MDL::skelmesh> commands;
- typedef struct MDL::skeleton skeleton;
- typedef struct MDL::skelmeshgroup meshgroup;
- typedef struct MDL::skelpart part;
- typedef struct MDL::skin skin;
- typedef struct MDL::boneinfo boneinfo;
- typedef struct MDL::skelanimspec animspec;
- typedef struct MDL::pitchdep pitchdep;
- typedef struct MDL::pitchtarget pitchtarget;
- typedef struct MDL::pitchcorrect pitchcorrect;
-
- static void loadpart(char *meshfile, char *skelname, float *smooth)
- {
- if(!MDL::loading) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; }
- defformatstring(filename, "%s/%s", MDL::dir, meshfile);
- part &mdl = MDL::loading->addpart();
- mdl.pitchscale = mdl.pitchoffset = mdl.pitchmin = mdl.pitchmax = 0;
- mdl.meshes = MDL::loading->sharemeshes(path(filename), skelname[0] ? skelname : NULL, double(*smooth > 0 ? cos(clamp(*smooth, 0.0f, 180.0f)*RAD) : 2));
- if(!mdl.meshes) conoutf(CON_ERROR, "could not load %s", filename);
- else
- {
- mdl.initanimparts();
- mdl.initskins();
- }
- }
-
- static void settag(char *name, char *tagname, float *tx, float *ty, float *tz, float *rx, float *ry, float *rz)
- {
- if(!MDL::loading || MDL::loading->parts.empty()) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; }
- part &mdl = *(part *)MDL::loading->parts.last();
- int i = mdl.meshes ? ((meshgroup *)mdl.meshes)->skel->findbone(name) : -1;
- if(i >= 0)
- {
- float cx = *rx ? cosf(*rx/2*RAD) : 1, sx = *rx ? sinf(*rx/2*RAD) : 0,
- cy = *ry ? cosf(*ry/2*RAD) : 1, sy = *ry ? sinf(*ry/2*RAD) : 0,
- cz = *rz ? cosf(*rz/2*RAD) : 1, sz = *rz ? sinf(*rz/2*RAD) : 0;
- matrix4x3 m(matrix3(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)),
- vec(*tx, *ty, *tz));
- ((meshgroup *)mdl.meshes)->skel->addtag(tagname, i, m);
- return;
- }
- conoutf(CON_ERROR, "could not find bone %s for tag %s", name, tagname);
- }
-
- static void setpitch(char *name, float *pitchscale, float *pitchoffset, float *pitchmin, float *pitchmax)
- {
- if(!MDL::loading || MDL::loading->parts.empty()) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; }
- part &mdl = *(part *)MDL::loading->parts.last();
-
- if(name[0])
- {
- int i = mdl.meshes ? ((meshgroup *)mdl.meshes)->skel->findbone(name) : -1;
- if(i>=0)
- {
- boneinfo &b = ((meshgroup *)mdl.meshes)->skel->bones[i];
- b.pitchscale = *pitchscale;
- b.pitchoffset = *pitchoffset;
- if(*pitchmin || *pitchmax)
- {
- b.pitchmin = *pitchmin;
- b.pitchmax = *pitchmax;
- }
- else
- {
- b.pitchmin = -360*fabs(b.pitchscale) + b.pitchoffset;
- b.pitchmax = 360*fabs(b.pitchscale) + b.pitchoffset;
- }
- return;
- }
- conoutf(CON_ERROR, "could not find bone %s to pitch", name);
- return;
- }
-
- mdl.pitchscale = *pitchscale;
- mdl.pitchoffset = *pitchoffset;
- if(*pitchmin || *pitchmax)
- {
- mdl.pitchmin = *pitchmin;
- mdl.pitchmax = *pitchmax;
- }
- else
- {
- mdl.pitchmin = -360*fabs(mdl.pitchscale) + mdl.pitchoffset;
- mdl.pitchmax = 360*fabs(mdl.pitchscale) + mdl.pitchoffset;
- }
- }
-
- static void setpitchtarget(char *name, char *animfile, int *frameoffset, float *pitchmin, float *pitchmax)
- {
- if(!MDL::loading || MDL::loading->parts.empty()) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; }
- part &mdl = *(part *)MDL::loading->parts.last();
- if(!mdl.meshes) return;
- defformatstring(filename, "%s/%s", MDL::dir, animfile);
- animspec *sa = ((meshgroup *)mdl.meshes)->loadanim(path(filename));
- if(!sa) { conoutf(CON_ERROR, "could not load %s anim file %s", MDL::formatname(), filename); return; }
- skeleton *skel = ((meshgroup *)mdl.meshes)->skel;
- int bone = skel ? skel->findbone(name) : -1;
- if(bone < 0)
- {
- conoutf(CON_ERROR, "could not find bone %s to pitch target", name);
- return;
- }
- loopv(skel->pitchtargets) if(skel->pitchtargets[i].bone == bone) return;
- pitchtarget &t = skel->pitchtargets.add();
- t.bone = bone;
- t.frame = sa->frame + clamp(*frameoffset, 0, sa->range-1);
- t.pitchmin = *pitchmin;
- t.pitchmax = *pitchmax;
- }
-
- static void setpitchcorrect(char *name, char *targetname, float *scale, float *pitchmin, float *pitchmax)
- {
- if(!MDL::loading || MDL::loading->parts.empty()) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; }
- part &mdl = *(part *)MDL::loading->parts.last();
- if(!mdl.meshes) return;
- skeleton *skel = ((meshgroup *)mdl.meshes)->skel;
- int bone = skel ? skel->findbone(name) : -1;
- if(bone < 0)
- {
- conoutf(CON_ERROR, "could not find bone %s to pitch correct", name);
- return;
- }
- if(skel->findpitchcorrect(bone) >= 0) return;
- int targetbone = skel->findbone(targetname), target = -1;
- if(targetbone >= 0) loopv(skel->pitchtargets) if(skel->pitchtargets[i].bone == targetbone) { target = i; break; }
- if(target < 0)
- {
- conoutf(CON_ERROR, "could not find pitch target %s to pitch correct %s", targetname, name);
- return;
- }
- pitchcorrect c;
- c.bone = bone;
- c.target = target;
- c.pitchmin = *pitchmin;
- c.pitchmax = *pitchmax;
- c.pitchscale = *scale;
- int pos = skel->pitchcorrects.length();
- loopv(skel->pitchcorrects) if(bone <= skel->pitchcorrects[i].bone) { pos = i; break; }
- skel->pitchcorrects.insert(pos, c);
- }
-
- static void setanim(char *anim, char *animfile, float *speed, int *priority, int *startoffset, int *endoffset)
- {
- if(!MDL::loading || MDL::loading->parts.empty()) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; }
-
- vector<int> anims;
- findanims(anim, anims);
- if(anims.empty()) conoutf(CON_ERROR, "could not find animation %s", anim);
- else
- {
- part *p = (part *)MDL::loading->parts.last();
- if(!p->meshes) return;
- defformatstring(filename, "%s/%s", MDL::dir, animfile);
- animspec *sa = ((meshgroup *)p->meshes)->loadanim(path(filename));
- if(!sa) conoutf(CON_ERROR, "could not load %s anim file %s", MDL::formatname(), filename);
- else loopv(anims)
- {
- int start = sa->frame, end = sa->range;
- if(*startoffset > 0) start += min(*startoffset, end-1);
- else if(*startoffset < 0) start += max(end + *startoffset, 0);
- end -= start - sa->frame;
- if(*endoffset > 0) end = min(end, *endoffset);
- else if(*endoffset < 0) end = max(end + *endoffset, 1);
- MDL::loading->parts.last()->setanim(p->numanimparts-1, anims[i], start, end, *speed, *priority);
- }
- }
- }
-
- static void setanimpart(char *maskstr)
- {
- if(!MDL::loading || MDL::loading->parts.empty()) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; }
-
- part *p = (part *)MDL::loading->parts.last();
-
- vector<char *> bonestrs;
- explodelist(maskstr, bonestrs);
- vector<ushort> bonemask;
- loopv(bonestrs)
- {
- char *bonestr = bonestrs[i];
- int bone = p->meshes ? ((meshgroup *)p->meshes)->skel->findbone(bonestr[0]=='!' ? bonestr+1 : bonestr) : -1;
- if(bone<0) { conoutf(CON_ERROR, "could not find bone %s for anim part mask [%s]", bonestr, maskstr); bonestrs.deletearrays(); return; }
- bonemask.add(bone | (bonestr[0]=='!' ? BONEMASK_NOT : 0));
- }
- bonestrs.deletearrays();
- bonemask.sort();
- if(bonemask.length()) bonemask.add(BONEMASK_END);
-
- if(!p->addanimpart(bonemask.getbuf())) conoutf(CON_ERROR, "too many animation parts");
- }
-
- static void setadjust(char *name, float *yaw, float *pitch, float *roll, float *tx, float *ty, float *tz)
- {
- if(!MDL::loading || MDL::loading->parts.empty()) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; }
- part &mdl = *(part *)MDL::loading->parts.last();
-
- if(!name[0]) return;
- int i = mdl.meshes ? ((meshgroup *)mdl.meshes)->skel->findbone(name) : -1;
- if(i < 0) { conoutf(CON_ERROR, "could not find bone %s to adjust", name); return; }
- while(!MDL::adjustments.inrange(i)) MDL::adjustments.add(skeladjustment(0, 0, 0, vec(0, 0, 0)));
- MDL::adjustments[i] = skeladjustment(*yaw, *pitch, *roll, vec(*tx/4, *ty/4, *tz/4));
- }
-
- skelcommands()
- {
- if(MDL::multiparted()) this->modelcommand(loadpart, "load", "ssf");
- this->modelcommand(settag, "tag", "ssffffff");
- this->modelcommand(setpitch, "pitch", "sffff");
- this->modelcommand(setpitchtarget, "pitchtarget", "ssiff");
- this->modelcommand(setpitchcorrect, "pitchcorrect", "ssfff");
- if(MDL::animated())
- {
- this->modelcommand(setanim, "anim", "ssfiii");
- this->modelcommand(setanimpart, "animpart", "s");
- this->modelcommand(setadjust, "adjust", "sffffff");
- }
- }
+ typedef modelcommands<MDL, struct MDL::skelmesh> commands;
+ typedef struct MDL::skeleton skeleton;
+ typedef struct MDL::skelmeshgroup meshgroup;
+ typedef struct MDL::skelpart part;
+ typedef struct MDL::skin skin;
+ typedef struct MDL::boneinfo boneinfo;
+ typedef struct MDL::skelanimspec animspec;
+ typedef struct MDL::pitchdep pitchdep;
+ typedef struct MDL::pitchtarget pitchtarget;
+ typedef struct MDL::pitchcorrect pitchcorrect;
+
+ static void loadpart(char *meshfile, char *skelname, float *smooth)
+ {
+ if(!MDL::loading) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; }
+ defformatstring(filename, "%s/%s", MDL::dir, meshfile);
+ part &mdl = MDL::loading->addpart();
+ mdl.pitchscale = mdl.pitchoffset = mdl.pitchmin = mdl.pitchmax = 0;
+ mdl.meshes = MDL::loading->sharemeshes(path(filename), skelname[0] ? skelname : NULL, double(*smooth > 0 ? cos(clamp(*smooth, 0.0f, 180.0f)*RAD) : 2));
+ if(!mdl.meshes) conoutf(CON_ERROR, "could not load %s", filename);
+ else
+ {
+ mdl.initanimparts();
+ mdl.initskins();
+ }
+ }
+
+ static void settag(char *name, char *tagname, float *tx, float *ty, float *tz, float *rx, float *ry, float *rz)
+ {
+ if(!MDL::loading || MDL::loading->parts.empty()) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; }
+ part &mdl = *(part *)MDL::loading->parts.last();
+ int i = mdl.meshes ? ((meshgroup *)mdl.meshes)->skel->findbone(name) : -1;
+ if(i >= 0)
+ {
+ float cx = *rx ? cosf(*rx/2*RAD) : 1, sx = *rx ? sinf(*rx/2*RAD) : 0,
+ cy = *ry ? cosf(*ry/2*RAD) : 1, sy = *ry ? sinf(*ry/2*RAD) : 0,
+ cz = *rz ? cosf(*rz/2*RAD) : 1, sz = *rz ? sinf(*rz/2*RAD) : 0;
+ matrix4x3 m(matrix3(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)),
+ vec(*tx, *ty, *tz));
+ ((meshgroup *)mdl.meshes)->skel->addtag(tagname, i, m);
+ return;
+ }
+ conoutf(CON_ERROR, "could not find bone %s for tag %s", name, tagname);
+ }
+
+ static void setpitch(char *name, float *pitchscale, float *pitchoffset, float *pitchmin, float *pitchmax)
+ {
+ if(!MDL::loading || MDL::loading->parts.empty()) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; }
+ part &mdl = *(part *)MDL::loading->parts.last();
+
+ if(name[0])
+ {
+ int i = mdl.meshes ? ((meshgroup *)mdl.meshes)->skel->findbone(name) : -1;
+ if(i>=0)
+ {
+ boneinfo &b = ((meshgroup *)mdl.meshes)->skel->bones[i];
+ b.pitchscale = *pitchscale;
+ b.pitchoffset = *pitchoffset;
+ if(*pitchmin || *pitchmax)
+ {
+ b.pitchmin = *pitchmin;
+ b.pitchmax = *pitchmax;
+ }
+ else
+ {
+ b.pitchmin = -360*fabs(b.pitchscale) + b.pitchoffset;
+ b.pitchmax = 360*fabs(b.pitchscale) + b.pitchoffset;
+ }
+ return;
+ }
+ conoutf(CON_ERROR, "could not find bone %s to pitch", name);
+ return;
+ }
+
+ mdl.pitchscale = *pitchscale;
+ mdl.pitchoffset = *pitchoffset;
+ if(*pitchmin || *pitchmax)
+ {
+ mdl.pitchmin = *pitchmin;
+ mdl.pitchmax = *pitchmax;
+ }
+ else
+ {
+ mdl.pitchmin = -360*fabs(mdl.pitchscale) + mdl.pitchoffset;
+ mdl.pitchmax = 360*fabs(mdl.pitchscale) + mdl.pitchoffset;
+ }
+ }
+
+ static void setpitchtarget(char *name, char *animfile, int *frameoffset, float *pitchmin, float *pitchmax)
+ {
+ if(!MDL::loading || MDL::loading->parts.empty()) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; }
+ part &mdl = *(part *)MDL::loading->parts.last();
+ if(!mdl.meshes) return;
+ defformatstring(filename, "%s/%s", MDL::dir, animfile);
+ animspec *sa = ((meshgroup *)mdl.meshes)->loadanim(path(filename));
+ if(!sa) { conoutf(CON_ERROR, "could not load %s anim file %s", MDL::formatname(), filename); return; }
+ skeleton *skel = ((meshgroup *)mdl.meshes)->skel;
+ int bone = skel ? skel->findbone(name) : -1;
+ if(bone < 0)
+ {
+ conoutf(CON_ERROR, "could not find bone %s to pitch target", name);
+ return;
+ }
+ loopv(skel->pitchtargets) if(skel->pitchtargets[i].bone == bone) return;
+ pitchtarget &t = skel->pitchtargets.add();
+ t.bone = bone;
+ t.frame = sa->frame + clamp(*frameoffset, 0, sa->range-1);
+ t.pitchmin = *pitchmin;
+ t.pitchmax = *pitchmax;
+ }
+
+ static void setpitchcorrect(char *name, char *targetname, float *scale, float *pitchmin, float *pitchmax)
+ {
+ if(!MDL::loading || MDL::loading->parts.empty()) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; }
+ part &mdl = *(part *)MDL::loading->parts.last();
+ if(!mdl.meshes) return;
+ skeleton *skel = ((meshgroup *)mdl.meshes)->skel;
+ int bone = skel ? skel->findbone(name) : -1;
+ if(bone < 0)
+ {
+ conoutf(CON_ERROR, "could not find bone %s to pitch correct", name);
+ return;
+ }
+ if(skel->findpitchcorrect(bone) >= 0) return;
+ int targetbone = skel->findbone(targetname), target = -1;
+ if(targetbone >= 0) loopv(skel->pitchtargets) if(skel->pitchtargets[i].bone == targetbone) { target = i; break; }
+ if(target < 0)
+ {
+ conoutf(CON_ERROR, "could not find pitch target %s to pitch correct %s", targetname, name);
+ return;
+ }
+ pitchcorrect c;
+ c.bone = bone;
+ c.target = target;
+ c.pitchmin = *pitchmin;
+ c.pitchmax = *pitchmax;
+ c.pitchscale = *scale;
+ int pos = skel->pitchcorrects.length();
+ loopv(skel->pitchcorrects) if(bone <= skel->pitchcorrects[i].bone) { pos = i; break; }
+ skel->pitchcorrects.insert(pos, c);
+ }
+
+ static void setanim(char *anim, char *animfile, float *speed, int *priority, int *startoffset, int *endoffset)
+ {
+ if(!MDL::loading || MDL::loading->parts.empty()) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; }
+
+ vector<int> anims;
+ findanims(anim, anims);
+ if(anims.empty()) conoutf(CON_ERROR, "could not find animation %s", anim);
+ else
+ {
+ part *p = (part *)MDL::loading->parts.last();
+ if(!p->meshes) return;
+ defformatstring(filename, "%s/%s", MDL::dir, animfile);
+ animspec *sa = ((meshgroup *)p->meshes)->loadanim(path(filename));
+ if(!sa) conoutf(CON_ERROR, "could not load %s anim file %s", MDL::formatname(), filename);
+ else loopv(anims)
+ {
+ int start = sa->frame, end = sa->range;
+ if(*startoffset > 0) start += min(*startoffset, end-1);
+ else if(*startoffset < 0) start += max(end + *startoffset, 0);
+ end -= start - sa->frame;
+ if(*endoffset > 0) end = min(end, *endoffset);
+ else if(*endoffset < 0) end = max(end + *endoffset, 1);
+ MDL::loading->parts.last()->setanim(p->numanimparts-1, anims[i], start, end, *speed, *priority);
+ }
+ }
+ }
+
+ static void setanimpart(char *maskstr)
+ {
+ if(!MDL::loading || MDL::loading->parts.empty()) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; }
+
+ part *p = (part *)MDL::loading->parts.last();
+
+ vector<char *> bonestrs;
+ explodelist(maskstr, bonestrs);
+ vector<ushort> bonemask;
+ loopv(bonestrs)
+ {
+ char *bonestr = bonestrs[i];
+ int bone = p->meshes ? ((meshgroup *)p->meshes)->skel->findbone(bonestr[0]=='!' ? bonestr+1 : bonestr) : -1;
+ if(bone<0) { conoutf(CON_ERROR, "could not find bone %s for anim part mask [%s]", bonestr, maskstr); bonestrs.deletearrays(); return; }
+ bonemask.add(bone | (bonestr[0]=='!' ? BONEMASK_NOT : 0));
+ }
+ bonestrs.deletearrays();
+ bonemask.sort();
+ if(bonemask.length()) bonemask.add(BONEMASK_END);
+
+ if(!p->addanimpart(bonemask.getbuf())) conoutf(CON_ERROR, "too many animation parts");
+ }
+
+ static void setadjust(char *name, float *yaw, float *pitch, float *roll, float *tx, float *ty, float *tz)
+ {
+ if(!MDL::loading || MDL::loading->parts.empty()) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; }
+ part &mdl = *(part *)MDL::loading->parts.last();
+
+ if(!name[0]) return;
+ int i = mdl.meshes ? ((meshgroup *)mdl.meshes)->skel->findbone(name) : -1;
+ if(i < 0) { conoutf(CON_ERROR, "could not find bone %s to adjust", name); return; }
+ while(!MDL::adjustments.inrange(i)) MDL::adjustments.add(skeladjustment(0, 0, 0, vec(0, 0, 0)));
+ MDL::adjustments[i] = skeladjustment(*yaw, *pitch, *roll, vec(*tx/4, *ty/4, *tz/4));
+ }
+
+ skelcommands()
+ {
+ if(MDL::multiparted()) this->modelcommand(loadpart, "load", "ssf");
+ this->modelcommand(settag, "tag", "ssffffff");
+ this->modelcommand(setpitch, "pitch", "sffff");
+ this->modelcommand(setpitchtarget, "pitchtarget", "ssiff");
+ this->modelcommand(setpitchcorrect, "pitchcorrect", "ssfff");
+ if(MDL::animated())
+ {
+ this->modelcommand(setanim, "anim", "ssfiii");
+ this->modelcommand(setanimpart, "animpart", "s");
+ this->modelcommand(setadjust, "adjust", "sffffff");
+ }
+ }
};