1 VAR(IDF_PERSIST, gpuskel, 0, 1, 1);
2 
3 VAR(0, maxskelanimdata, 1, 192, 0);
4 
5 #define BONEMASK_NOT  0x8000
6 #define BONEMASK_END  0xFFFF
7 #define BONEMASK_BONE 0x7FFF
8 
9 struct skelhitdata;
10 
11 struct skelmodel : animmodel
12 {
13     struct vert { vec pos, norm; vec2 tc; quat tangent; int blend, interpindex; };
14     struct vvert { vec pos; hvec2 tc; squat tangent; };
15     struct vvertg { hvec4 pos; hvec2 tc; squat tangent; };
16     struct vvertgw : vvertg { uchar weights[4]; uchar bones[4]; };
17     struct vvertgc : vvertg { bvec4 col; };
18     struct vvertgwc : vvertgw { bvec4 col; };
19     struct tri { ushort vert[3]; };
20 
21     struct blendcombo
22     {
23         int uses, interpindex;
24         float weights[4];
25         uchar bones[4], interpbones[4];
26 
blendcomboskelmodel::blendcombo27         blendcombo() : uses(1)
28         {
29         }
30 
31         bool operator==(const blendcombo &c) const
32         {
33             loopk(4) if(bones[k] != c.bones[k]) return false;
34             loopk(4) if(weights[k] != c.weights[k]) return false;
35             return true;
36         }
37 
sizeskelmodel::blendcombo38         int size() const
39         {
40             int i = 1;
41             while(i < 4 && weights[i]) i++;
42             return i;
43         }
44 
sortcmpskelmodel::blendcombo45         static bool sortcmp(const blendcombo &x, const blendcombo &y)
46         {
47             loopi(4)
48             {
49                 if(x.weights[i])
50                 {
51                     if(!y.weights[i]) return true;
52                 }
53                 else if(y.weights[i]) return false;
54                 else break;
55             }
56             return false;
57         }
58 
addweightskelmodel::blendcombo59         int addweight(int sorted, float weight, int bone)
60         {
61             if(weight <= 1e-3f) return sorted;
62             loopk(sorted) if(weight > weights[k])
63             {
64                 for(int l = min(sorted-1, 2); l >= k; l--)
65                 {
66                     weights[l+1] = weights[l];
67                     bones[l+1] = bones[l];
68                 }
69                 weights[k] = weight;
70                 bones[k] = bone;
71                 return sorted<4 ? sorted+1 : sorted;
72             }
73             if(sorted>=4) return sorted;
74             weights[sorted] = weight;
75             bones[sorted] = bone;
76             return sorted+1;
77         }
78 
finalizeskelmodel::blendcombo79         void finalize(int sorted)
80         {
81             loopj(4-sorted) { weights[sorted+j] = 0; bones[sorted+j] = 0; }
82             if(sorted <= 0) return;
83             float total = 0;
84             loopj(sorted) total += weights[j];
85             total = 1.0f/total;
86             loopj(sorted) weights[j] *= total;
87         }
88 
89         template<class T>
serializeskelmodel::blendcombo90         void serialize(T &v)
91         {
92             if(interpindex >= 0)
93             {
94                 v.weights[0] = 255;
95                 loopk(3) v.weights[k+1] = 0;
96                 v.bones[0] = 2*interpindex;
97                 loopk(3) v.bones[k+1] = v.bones[0];
98             }
99             else
100             {
101                 int total = 0;
102                 loopk(4) total += (v.weights[k] = uchar(0.5f + weights[k]*255));
103                 while(total > 255)
104                 {
105                     loopk(4) if(v.weights[k] > 0 && total > 255) { v.weights[k]--; total--; }
106                 }
107                 while(total < 255)
108                 {
109                     loopk(4) if(v.weights[k] < 255 && total < 255) { v.weights[k]++; total++; }
110                 }
111                 loopk(4) v.bones[k] = 2*interpbones[k];
112             }
113         }
114     };
115 
116 
117     struct animcacheentry
118     {
119         animstate as[MAXANIMPARTS];
120         float pitch;
121         int millis;
122         uchar *partmask;
123         ragdolldata *ragdoll;
124 
animcacheentryskelmodel::animcacheentry125         animcacheentry() : ragdoll(NULL)
126         {
127             loopk(MAXANIMPARTS) as[k].cur.fr1 = as[k].prev.fr1 = -1;
128         }
129 
130         bool operator==(const animcacheentry &c) const
131         {
132             loopi(MAXANIMPARTS) if(as[i]!=c.as[i]) return false;
133             return pitch==c.pitch && partmask==c.partmask && ragdoll==c.ragdoll && (!ragdoll || min(millis, c.millis) >= ragdoll->lastmove);
134         }
135 
136         bool operator!=(const animcacheentry &c) const
137         {
138             return !operator==(c);
139         }
140     };
141 
142     struct vbocacheentry : animcacheentry
143     {
144         GLuint vbuf;
145         int owner;
146 
vbocacheentryskelmodel::vbocacheentry147         vbocacheentry() : vbuf(0), owner(-1) {}
148     };
149 
150     struct skelcacheentry : animcacheentry
151     {
152         dualquat *bdata;
153         int version;
154 
skelcacheentryskelmodel::skelcacheentry155         skelcacheentry() : bdata(NULL), version(-1) {}
156 
nextversionskelmodel::skelcacheentry157         void nextversion()
158         {
159             version = Shader::uniformlocversion();
160         }
161     };
162 
163     struct blendcacheentry : skelcacheentry
164     {
165         int owner;
166 
blendcacheentryskelmodel::blendcacheentry167         blendcacheentry() : owner(-1) {}
168     };
169 
170     struct skelmeshgroup;
171 
172     struct skelmesh : mesh
173     {
174         vert *verts;
175         tri *tris;
176         bvec4 *vcolors;
177         int numverts, numtris, maxweights;
178 
179         int voffset, eoffset, elen;
180         ushort minvert, maxvert;
181 
skelmeshskelmodel::skelmesh182         skelmesh() : verts(NULL), tris(NULL), vcolors(NULL), numverts(0), numtris(0), maxweights(0)
183         {
184         }
185 
~skelmeshskelmodel::skelmesh186         virtual ~skelmesh()
187         {
188             DELETEA(verts);
189             DELETEA(tris);
190             DELETEA(vcolors);
191         }
192 
addblendcomboskelmodel::skelmesh193         int addblendcombo(const blendcombo &c)
194         {
195             maxweights = max(maxweights, c.size());
196             return ((skelmeshgroup *)group)->addblendcombo(c);
197         }
198 
199         void smoothnorms(float limit = 0, bool areaweight = true)
200         {
201             mesh::smoothnorms(verts, numverts, tris, numtris, limit, areaweight);
202         }
203 
204         void buildnorms(bool areaweight = true)
205         {
206             mesh::buildnorms(verts, numverts, tris, numtris, areaweight);
207         }
208 
209         void calctangents(bool areaweight = true)
210         {
211             mesh::calctangents(verts, verts, numverts, tris, numtris, areaweight);
212         }
213 
calcbbskelmodel::skelmesh214         void calcbb(vec &bbmin, vec &bbmax, const matrix4x3 &m)
215         {
216             loopj(numverts)
217             {
218                 vec v = m.transform(verts[j].pos);
219                 bbmin.min(v);
220                 bbmax.max(v);
221             }
222         }
223 
genBIHskelmodel::skelmesh224         void genBIH(BIH::mesh &m)
225         {
226             m.tris = (const BIH::tri *)tris;
227             m.numtris = numtris;
228             m.pos = (const uchar *)&verts->pos;
229             m.posstride = sizeof(vert);
230             m.tc = (const uchar *)&verts->tc;
231             m.tcstride = sizeof(vert);
232         }
233 
genshadowmeshskelmodel::skelmesh234         void genshadowmesh(vector<triangle> &out, const matrix4x3 &m)
235         {
236             loopj(numtris)
237             {
238                 triangle &t = out.add();
239                 t.a = m.transform(verts[tris[j].vert[0]].pos);
240                 t.b = m.transform(verts[tris[j].vert[1]].pos);
241                 t.c = m.transform(verts[tris[j].vert[2]].pos);
242             }
243         }
244 
assignvertskelmodel::skelmesh245         inline void assignvert(vvertg &vv, int j, vert &v, blendcombo &c)
246         {
247             vv.pos = hvec4(v.pos, 1);
248             vv.tc = v.tc;
249             vv.tangent = v.tangent;
250         }
251 
assignvertskelmodel::skelmesh252         inline void assignvert(vvertgc &vv, int j, vert &v, blendcombo &c)
253         {
254             vv.pos = hvec4(v.pos, 1);
255             vv.tc = v.tc;
256             vv.tangent = v.tangent;
257             vv.col = vcolors ? vcolors[j] : bvec4(255, 255, 255, 255);
258         }
259 
assignvertskelmodel::skelmesh260         inline void assignvert(vvertgw &vv, int j, vert &v, blendcombo &c)
261         {
262             vv.pos = hvec4(v.pos, 1);
263             vv.tc = v.tc;
264             vv.tangent = v.tangent;
265             c.serialize(vv);
266         }
267 
assignvertskelmodel::skelmesh268         inline void assignvert(vvertgwc &vv, int j, vert &v, blendcombo &c)
269         {
270             vv.pos = hvec4(v.pos, 1);
271             vv.tc = v.tc;
272             vv.tangent = v.tangent;
273             c.serialize(vv);
274             vv.col = vcolors ? vcolors[j] : bvec4(255, 255, 255, 255);
275         }
276 
277         template<class T>
genvboskelmodel::skelmesh278         int genvbo(vector<ushort> &idxs, int offset, vector<T> &vverts)
279         {
280             voffset = offset;
281             eoffset = idxs.length();
282             loopi(numverts)
283             {
284                 vert &v = verts[i];
285                 assignvert(vverts.add(), i, v, ((skelmeshgroup *)group)->blendcombos[v.blend]);
286             }
287             loopi(numtris) loopj(3) idxs.add(voffset + tris[i].vert[j]);
288             elen = idxs.length()-eoffset;
289             minvert = voffset;
290             maxvert = voffset + numverts-1;
291             return numverts;
292         }
293 
294         template<class T>
genvboskelmodel::skelmesh295         int genvbo(vector<ushort> &idxs, int offset, vector<T> &vverts, int *htdata, int htlen)
296         {
297             voffset = offset;
298             eoffset = idxs.length();
299             minvert = 0xFFFF;
300             loopi(numtris)
301             {
302                 tri &t = tris[i];
303                 loopj(3)
304                 {
305                     int index = t.vert[j];
306                     vert &v = verts[index];
307                     T vv;
308                     assignvert(vv, index, v, ((skelmeshgroup *)group)->blendcombos[v.blend]);
309                     int htidx = hthash(v.pos)&(htlen-1);
310                     loopk(htlen)
311                     {
312                         int &vidx = htdata[(htidx+k)&(htlen-1)];
313                         if(vidx < 0) { vidx = idxs.add(ushort(vverts.length())); vverts.add(vv); break; }
314                         else if(!memcmp(&vverts[vidx], &vv, sizeof(vv))) { minvert = min(minvert, idxs.add(ushort(vidx))); break; }
315                     }
316                 }
317             }
318             elen = idxs.length()-eoffset;
319             minvert = min(minvert, ushort(voffset));
320             maxvert = max(minvert, ushort(vverts.length()-1));
321             return vverts.length()-voffset;
322         }
323 
genvboskelmodel::skelmesh324         int genvbo(vector<ushort> &idxs, int offset)
325         {
326             loopi(numverts) verts[i].interpindex = ((skelmeshgroup *)group)->remapblend(verts[i].blend);
327 
328             voffset = offset;
329             eoffset = idxs.length();
330             loopi(numtris)
331             {
332                 tri &t = tris[i];
333                 loopj(3) idxs.add(voffset+t.vert[j]);
334             }
335             minvert = voffset;
336             maxvert = voffset + numverts-1;
337             elen = idxs.length()-eoffset;
338             return numverts;
339         }
340 
341         template<class T>
fillvertskelmodel::skelmesh342         static inline void fillvert(T &vv, int j, vert &v)
343         {
344             vv.tc = v.tc;
345         }
346 
347         template<class T>
fillvertsskelmodel::skelmesh348         void fillverts(T *vdata)
349         {
350             vdata += voffset;
351             loopi(numverts) fillvert(vdata[i], i, verts[i]);
352         }
353 
354         template<class T>
interpvertsskelmodel::skelmesh355         void interpverts(const dualquat * RESTRICT bdata1, const dualquat * RESTRICT bdata2, T * RESTRICT vdata, skin &s)
356         {
357             const int blendoffset = ((skelmeshgroup *)group)->skel->numgpubones;
358             bdata2 -= blendoffset;
359             vdata += voffset;
360             loopi(numverts)
361             {
362                 const vert &src = verts[i];
363                 T &dst = vdata[i];
364                 const dualquat &b = (src.interpindex < blendoffset ? bdata1 : bdata2)[src.interpindex];
365                 dst.pos = b.transform(src.pos);
366                 quat q = b.transform(src.tangent);
367                 fixqtangent(q, src.tangent.w);
368                 dst.tangent = q;
369             }
370         }
371 
setshaderskelmodel::skelmesh372         void setshader(Shader *s, int row)
373         {
374             skelmeshgroup *g = (skelmeshgroup *)group;
375             if(row) s->setvariant(g->skel->usegpuskel ? min(maxweights, g->vweights) : 0, row);
376             else if(g->skel->usegpuskel) s->setvariant(min(maxweights, g->vweights)-1, 0);
377             else s->set();
378         }
379 
renderskelmodel::skelmesh380         void render(const animstate *as, skin &s, vbocacheentry &vc)
381         {
382             if(!Shader::lastshader) return;
383             glDrawRangeElements_(GL_TRIANGLES, minvert, maxvert, elen, GL_UNSIGNED_SHORT, &((skelmeshgroup *)group)->edata[eoffset]);
384             glde++;
385             xtravertsva += numverts;
386         }
387     };
388 
389 
390     struct tag
391     {
392         char *name;
393         int bone;
394         matrix4x3 matrix;
395 
tagskelmodel::tag396         tag() : name(NULL) {}
~tagskelmodel::tag397         ~tag() { DELETEA(name); }
398     };
399 
400     struct skelanimspec
401     {
402         char *name;
403         int frame, range;
404 
skelanimspecskelmodel::skelanimspec405         skelanimspec() : name(NULL), frame(0), range(0) {}
~skelanimspecskelmodel::skelanimspec406         ~skelanimspec()
407         {
408             DELETEA(name);
409         }
410     };
411 
412     struct boneinfo
413     {
414         const char *name;
415         int parent, children, next, group, scheduled, interpindex, interpparent, ragdollindex, correctindex;
416         float pitchscale, pitchoffset, pitchmin, pitchmax;
417         dualquat base, invbase;
418 
boneinfoskelmodel::boneinfo419         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) {}
~boneinfoskelmodel::boneinfo420         ~boneinfo()
421         {
422             DELETEA(name);
423         }
424     };
425 
426     struct antipode
427     {
428         int parent, child;
429 
antipodeskelmodel::antipode430         antipode(int parent, int child) : parent(parent), child(child) {}
431     };
432 
433     struct pitchdep
434     {
435         int bone, parent;
436         dualquat pose;
437     };
438 
439     struct pitchtarget
440     {
441         int bone, frame, corrects, deps;
442         float pitchmin, pitchmax, deviated;
443         dualquat pose;
444     };
445 
446     struct pitchcorrect
447     {
448         int bone, target, parent;
449         float pitchmin, pitchmax, pitchscale, pitchangle, pitchtotal;
450 
pitchcorrectskelmodel::pitchcorrect451         pitchcorrect() : parent(-1), pitchangle(0), pitchtotal(0) {}
452     };
453 
454     struct skeleton
455     {
456         char *name;
457         int shared;
458         vector<skelmeshgroup *> users;
459         boneinfo *bones;
460         int numbones, numinterpbones, numgpubones, numframes;
461         dualquat *framebones;
462         vector<skelanimspec> skelanims;
463         vector<tag> tags;
464         vector<antipode> antipodes;
465         ragdollskel *ragdoll;
466         vector<pitchdep> pitchdeps;
467         vector<pitchtarget> pitchtargets;
468         vector<pitchcorrect> pitchcorrects;
469 
470         bool usegpuskel;
471         vector<skelcacheentry> skelcache;
472         hashtable<GLuint, int> blendoffsets;
473 
skeletonskelmodel::skeleton474         skeleton() : name(NULL), shared(0), bones(NULL), numbones(0), numinterpbones(0), numgpubones(0), numframes(0), framebones(NULL), ragdoll(NULL), usegpuskel(false), blendoffsets(32)
475         {
476         }
477 
~skeletonskelmodel::skeleton478         ~skeleton()
479         {
480             DELETEA(name);
481             DELETEA(bones);
482             DELETEA(framebones);
483             DELETEP(ragdoll);
484             loopv(skelcache)
485             {
486                 DELETEA(skelcache[i].bdata);
487             }
488         }
489 
490         skelanimspec *findskelanim(const char *name, char sep = '\0')
491         {
492             int len = sep ? strlen(name) : 0;
loopvskelmodel::skeleton493             loopv(skelanims)
494             {
495                 if(skelanims[i].name)
496                 {
497                     if(sep)
498                     {
499                         const char *end = strchr(skelanims[i].name, ':');
500                         if(end && end - skelanims[i].name == len && !memcmp(name, skelanims[i].name, len)) return &skelanims[i];
501                     }
502                     if(!strcmp(name, skelanims[i].name)) return &skelanims[i];
503                 }
504             }
505             return NULL;
506         }
507 
addskelanimskelmodel::skeleton508         skelanimspec &addskelanim(const char *name)
509         {
510             skelanimspec &sa = skelanims.add();
511             sa.name = name ? newstring(name) : NULL;
512             return sa;
513         }
514 
findboneskelmodel::skeleton515         int findbone(const char *name)
516         {
517             loopi(numbones) if(cubematchstr(bones[i].name, name)) return i;
518             return -1;
519         }
520 
findbonesskelmodel::skeleton521         int findbones(const char *name, vector<int> &elems)
522         {
523             int num = 0;
524             loopi(numbones) if(cubepattern(bones[i].name, name) >= 0)
525             {
526                 elems.add(i);
527                 num++;
528             }
529             return num;
530         }
531 
findtagskelmodel::skeleton532         int findtag(const char *name)
533         {
534             loopv(tags) if(cubematchstr(tags[i].name, name)) return i;
535             return -1;
536         }
537 
findtagsskelmodel::skeleton538         int findtags(const char *name, vector<int> &elems)
539         {
540             int num = 0;
541             loopv(tags) if(cubepattern(tags[i].name, name) >= 0)
542             {
543                 elems.add(i);
544                 num++;
545             }
546             return num;
547         }
548 
addtagskelmodel::skeleton549         bool addtag(const char *name, int bone, const matrix4x3 &matrix)
550         {
551             int idx = findtag(name);
552             if(idx >= 0)
553             {
554                 if(!testtags) return false;
555                 tag &t = tags[idx];
556                 t.bone = bone;
557                 t.matrix = matrix;
558             }
559             else
560             {
561                 tag &t = tags.add();
562                 t.name = newstring(name);
563                 t.bone = bone;
564                 t.matrix = matrix;
565             }
566             return true;
567         }
568 
calcantipodesskelmodel::skeleton569         void calcantipodes()
570         {
571             antipodes.shrink(0);
572             vector<int> schedule;
573             loopi(numbones)
574             {
575                 if(bones[i].group >= numbones)
576                 {
577                     bones[i].scheduled = schedule.length();
578                     schedule.add(i);
579                 }
580                 else bones[i].scheduled = -1;
581             }
582             loopv(schedule)
583             {
584                 int bone = schedule[i];
585                 const boneinfo &info = bones[bone];
586                 loopj(numbones) if(abs(bones[j].group) == bone && bones[j].scheduled < 0)
587                 {
588                     antipodes.add(antipode(info.interpindex, bones[j].interpindex));
589                     bones[j].scheduled = schedule.length();
590                     schedule.add(j);
591                 }
592                 if(i + 1 == schedule.length())
593                 {
594                     int conflict = INT_MAX;
595                     loopj(numbones) if(bones[j].group < numbones && bones[j].scheduled < 0) conflict = min(conflict, abs(bones[j].group));
596                     if(conflict < numbones)
597                     {
598                         bones[conflict].scheduled = schedule.length();
599                         schedule.add(conflict);
600                     }
601                 }
602             }
603         }
604 
remapbonesskelmodel::skeleton605         void remapbones()
606         {
607             loopi(numbones)
608             {
609                 boneinfo &info = bones[i];
610                 info.interpindex = -1;
611                 info.ragdollindex = -1;
612             }
613             numgpubones = 0;
614             loopv(users)
615             {
616                 skelmeshgroup *group = users[i];
617                 loopvj(group->blendcombos)
618                 {
619                     blendcombo &c = group->blendcombos[j];
620                     loopk(4)
621                     {
622                         if(!c.weights[k]) { c.interpbones[k] = k > 0 ? c.interpbones[k-1] : 0; continue; }
623                         boneinfo &info = bones[c.bones[k]];
624                         if(info.interpindex < 0) info.interpindex = numgpubones++;
625                         c.interpbones[k] = info.interpindex;
626                         if(info.group < 0) continue;
627                         loopl(4)
628                         {
629                             if(!c.weights[l]) break;
630                             if(l == k) continue;
631                             int parent = c.bones[l];
632                             if(info.parent == parent || (info.parent >= 0 && info.parent == bones[parent].parent)) { info.group = -info.parent; break; }
633                             if(info.group <= parent) continue;
634                             int child = c.bones[k];
635                             while(parent > child) parent = bones[parent].parent;
636                             if(parent != child) info.group = c.bones[l];
637                         }
638                     }
639                 }
640             }
641             numinterpbones = numgpubones;
642             loopv(tags)
643             {
644                 boneinfo &info = bones[tags[i].bone];
645                 if(info.interpindex < 0) info.interpindex = numinterpbones++;
646             }
647             if(ragdoll)
648             {
649                 loopv(ragdoll->joints)
650                 {
651                     boneinfo &info = bones[ragdoll->joints[i].bone];
652                     if(info.interpindex < 0) info.interpindex = numinterpbones++;
653                     info.ragdollindex = i;
654                 }
655             }
656             loopi(numbones)
657             {
658                 boneinfo &info = bones[i];
659                 if(info.interpindex < 0) continue;
660                 for(int parent = info.parent; parent >= 0 && bones[parent].interpindex < 0; parent = bones[parent].parent)
661                     bones[parent].interpindex = numinterpbones++;
662             }
663             loopi(numbones)
664             {
665                 boneinfo &info = bones[i];
666                 if(info.interpindex < 0) continue;
667                 info.interpparent = info.parent >= 0 ? bones[info.parent].interpindex : -1;
668             }
669             if(ragdoll)
670             {
671                 loopi(numbones)
672                 {
673                     boneinfo &info = bones[i];
674                     if(info.interpindex < 0 || info.ragdollindex >= 0) continue;
675                     for(int parent = info.parent; parent >= 0; parent = bones[parent].parent)
676                     {
677                         if(bones[parent].ragdollindex >= 0) { ragdoll->addreljoint(i, bones[parent].ragdollindex); break; }
678                     }
679                 }
680             }
681             calcantipodes();
682         }
683 
684 
addpitchdepskelmodel::skeleton685         void addpitchdep(int bone, int frame)
686         {
687             for(; bone >= 0; bone = bones[bone].parent)
688             {
689                 int pos = pitchdeps.length();
690                 loopvj(pitchdeps) if(bone <= pitchdeps[j].bone)
691                 {
692                     if(bone == pitchdeps[j].bone) goto nextbone;
693                     pos = j;
694                     break;
695                 }
696                 {
697                     pitchdep d;
698                     d.bone = bone;
699                     d.parent = -1;
700                     d.pose = framebones[frame*numbones + bone];
701                     pitchdeps.insert(pos, d);
702                 }
703             nextbone:;
704             }
705         }
706 
findpitchdepskelmodel::skeleton707         int findpitchdep(int bone)
708         {
709             loopv(pitchdeps) if(bone <= pitchdeps[i].bone) return bone == pitchdeps[i].bone ? i : -1;
710             return -1;
711         }
712 
findpitchcorrectskelmodel::skeleton713         int findpitchcorrect(int bone)
714         {
715             loopv(pitchcorrects) if(bone <= pitchcorrects[i].bone) return bone == pitchcorrects[i].bone ? i : -1;
716             return -1;
717         }
718 
initpitchdepsskelmodel::skeleton719         void initpitchdeps()
720         {
721             pitchdeps.setsize(0);
722             if(pitchtargets.empty()) return;
723             loopv(pitchtargets)
724             {
725                 pitchtarget &t = pitchtargets[i];
726                 t.deps = -1;
727                 addpitchdep(t.bone, t.frame);
728             }
729             loopv(pitchdeps)
730             {
731                 pitchdep &d = pitchdeps[i];
732                 int parent = bones[d.bone].parent;
733                 if(parent >= 0)
734                 {
735                     int j = findpitchdep(parent);
736                     if(j >= 0)
737                     {
738                         d.parent = j;
739                         d.pose.mul(pitchdeps[j].pose, dualquat(d.pose));
740                     }
741                 }
742             }
743             loopv(pitchtargets)
744             {
745                 pitchtarget &t = pitchtargets[i];
746                 int j = findpitchdep(t.bone);
747                 if(j >= 0)
748                 {
749                     t.deps = j;
750                     t.pose = pitchdeps[j].pose;
751                 }
752                 t.corrects = -1;
753                 for(int parent = t.bone; parent >= 0; parent = bones[parent].parent)
754                 {
755                     t.corrects = findpitchcorrect(parent);
756                     if(t.corrects >= 0) break;
757                 }
758             }
759             loopv(pitchcorrects)
760             {
761                 pitchcorrect &c = pitchcorrects[i];
762                 bones[c.bone].correctindex = i;
763                 c.parent = -1;
764                 for(int parent = c.bone;;)
765                 {
766                     parent = bones[parent].parent;
767                     if(parent < 0) break;
768                     c.parent = findpitchcorrect(parent);
769                     if(c.parent >= 0) break;
770                 }
771             }
772         }
773 
optimizeskelmodel::skeleton774         void optimize()
775         {
776             cleanup();
777             if(ragdoll) ragdoll->setup();
778             remapbones();
779             initpitchdeps();
780         }
781 
expandbonemaskskelmodel::skeleton782         void expandbonemask(uchar *expansion, int bone, int val)
783         {
784             expansion[bone] = val;
785             bone = bones[bone].children;
786             while(bone>=0) { expandbonemask(expansion, bone, val); bone = bones[bone].next; }
787         }
788 
applybonemaskskelmodel::skeleton789         void applybonemask(ushort *mask, uchar *partmask, int partindex)
790         {
791             if(!mask || *mask==BONEMASK_END) return;
792             uchar *expansion = new uchar[numbones];
793             memset(expansion, *mask&BONEMASK_NOT ? 1 : 0, numbones);
794             while(*mask!=BONEMASK_END)
795             {
796                 expandbonemask(expansion, *mask&BONEMASK_BONE, *mask&BONEMASK_NOT ? 0 : 1);
797                 mask++;
798             }
799             loopi(numbones) if(expansion[i]) partmask[i] = partindex;
800             delete[] expansion;
801         }
802 
linkchildrenskelmodel::skeleton803         void linkchildren()
804         {
805             loopi(numbones)
806             {
807                 boneinfo &b = bones[i];
808                 b.children = -1;
809                 if(b.parent<0) b.next = -1;
810                 else
811                 {
812                     b.next = bones[b.parent].children;
813                     bones[b.parent].children = i;
814                 }
815             }
816         }
817 
availgpubonesskelmodel::skeleton818         int availgpubones() const { return min(maxvsuniforms, maxskelanimdata) / 2; }
gpuaccelerateskelmodel::skeleton819         bool gpuaccelerate() const { return numframes && gpuskel && numgpubones<=availgpubones(); }
820 
calcdeviationskelmodel::skeleton821         float calcdeviation(const vec &axis, const vec &forward, const dualquat &pose1, const dualquat &pose2)
822         {
823             vec forward1 = pose1.transformnormal(forward).project(axis).normalize(),
824                 forward2 = pose2.transformnormal(forward).project(axis).normalize(),
825                 daxis = vec().cross(forward1, forward2);
826             float dx = clamp(forward1.dot(forward2), -1.0f, 1.0f), dy = clamp(daxis.magnitude(), -1.0f, 1.0f);
827             if(daxis.dot(axis) < 0) dy = -dy;
828             return atan2f(dy, dx)/RAD;
829         }
830 
calcpitchcorrectsskelmodel::skeleton831         void calcpitchcorrects(float pitch, const vec &axis, const vec &forward)
832         {
833             loopv(pitchtargets)
834             {
835                 pitchtarget &t = pitchtargets[i];
836                 t.deviated = calcdeviation(axis, forward, t.pose, pitchdeps[t.deps].pose);
837             }
838             loopv(pitchcorrects)
839             {
840                 pitchcorrect &c = pitchcorrects[i];
841                 c.pitchangle = c.pitchtotal = 0;
842             }
843             loopvj(pitchtargets)
844             {
845                 pitchtarget &t = pitchtargets[j];
846                 float tpitch = pitch - t.deviated;
847                 for(int parent = t.corrects; parent >= 0; parent = pitchcorrects[parent].parent)
848                     tpitch -= pitchcorrects[parent].pitchangle;
849                 if(t.pitchmin || t.pitchmax) tpitch = clamp(tpitch, t.pitchmin, t.pitchmax);
850                 loopv(pitchcorrects)
851                 {
852                     pitchcorrect &c = pitchcorrects[i];
853                     if(c.target != j) continue;
854                     float total = c.parent >= 0 ? pitchcorrects[c.parent].pitchtotal : 0,
855                           avail = tpitch - total,
856                           used = tpitch*c.pitchscale;
857                     if(c.pitchmin || c.pitchmax)
858                     {
859                         if(used < 0) used = clamp(c.pitchmin, used, 0.0f);
860                         else used = clamp(c.pitchmax, 0.0f, used);
861                     }
862                     if(used < 0) used = clamp(avail, used, 0.0f);
863                     else used = clamp(avail, 0.0f, used);
864                     c.pitchangle = used;
865                     c.pitchtotal = used + total;
866                 }
867             }
868         }
869 
870         #define INTERPBONE(bone) \
871             const animstate &s = as[partmask[bone]]; \
872             const framedata &f = partframes[partmask[bone]]; \
873             dualquat d; \
874             (d = f.fr1[bone]).mul((1-s.cur.t)*s.interp); \
875             d.accumulate(f.fr2[bone], s.cur.t*s.interp); \
876             if(s.interp<1) \
877             { \
878                 d.accumulate(f.pfr1[bone], (1-s.prev.t)*(1-s.interp)); \
879                 d.accumulate(f.pfr2[bone], s.prev.t*(1-s.interp)); \
880             }
881 
interpbonesskelmodel::skeleton882         void interpbones(const animstate *as, float pitch, const vec &axis, const vec &forward, int numanimparts, const uchar *partmask, skelcacheentry &sc)
883         {
884             if(!sc.bdata) sc.bdata = new dualquat[numinterpbones];
885             sc.nextversion();
886             struct framedata
887             {
888                 const dualquat *fr1, *fr2, *pfr1, *pfr2;
889             } partframes[MAXANIMPARTS];
890             loopi(numanimparts)
891             {
892                 partframes[i].fr1 = &framebones[as[i].cur.fr1*numbones];
893                 partframes[i].fr2 = &framebones[as[i].cur.fr2*numbones];
894                 if(as[i].interp<1)
895                 {
896                     partframes[i].pfr1 = &framebones[as[i].prev.fr1*numbones];
897                     partframes[i].pfr2 = &framebones[as[i].prev.fr2*numbones];
898                 }
899             }
900             loopv(pitchdeps)
901             {
902                 pitchdep &p = pitchdeps[i];
903                 INTERPBONE(p.bone);
904                 d.normalize();
905                 if(p.parent >= 0) p.pose.mul(pitchdeps[p.parent].pose, d);
906                 else p.pose = d;
907             }
908             calcpitchcorrects(pitch, axis, forward);
909             loopi(numbones) if(bones[i].interpindex>=0)
910             {
911                 INTERPBONE(i);
912                 d.normalize();
913                 const boneinfo &b = bones[i];
914                 if(b.interpparent<0) sc.bdata[b.interpindex] = d;
915                 else sc.bdata[b.interpindex].mul(sc.bdata[b.interpparent], d);
916                 float angle;
917                 if(b.pitchscale) { angle = b.pitchscale*pitch + b.pitchoffset; if(b.pitchmin || b.pitchmax) angle = clamp(angle, b.pitchmin, b.pitchmax); }
918                 else if(b.correctindex >= 0) angle = pitchcorrects[b.correctindex].pitchangle;
919                 else continue;
920                 if(as->cur.anim&ANIM_NOPITCH || (as->interp < 1 && as->prev.anim&ANIM_NOPITCH))
921                     angle *= (as->cur.anim&ANIM_NOPITCH ? 0 : as->interp) + (as->interp < 1 && as->prev.anim&ANIM_NOPITCH ? 0 : 1-as->interp);
922                 sc.bdata[b.interpindex].mulorient(quat(axis, angle*RAD), b.base);
923             }
924             loopv(antipodes) sc.bdata[antipodes[i].child].fixantipodal(sc.bdata[antipodes[i].parent]);
925         }
926 
initragdollskelmodel::skeleton927         void initragdoll(ragdolldata &d, skelcacheentry &sc, part *p)
928         {
929             const dualquat *bdata = sc.bdata;
930             loopv(ragdoll->joints)
931             {
932                 const ragdollskel::joint &j = ragdoll->joints[i];
933                 const boneinfo &b = bones[j.bone];
934                 const dualquat &q = bdata[b.interpindex];
935                 loopk(3) if(j.vert[k] >= 0)
936                 {
937                     ragdollskel::vert &v = ragdoll->verts[j.vert[k]];
938                     ragdolldata::vert &dv = d.verts[j.vert[k]];
939                     dv.pos.add(q.transform(v.pos).mul(v.weight));
940                 }
941             }
942             if(ragdoll->animjoints) loopv(ragdoll->joints)
943             {
944                 const ragdollskel::joint &j = ragdoll->joints[i];
945                 const boneinfo &b = bones[j.bone];
946                 const dualquat &q = bdata[b.interpindex];
947                 d.calcanimjoint(i, matrix4x3(q));
948             }
949             loopv(ragdoll->verts)
950             {
951                 ragdolldata::vert &dv = d.verts[i];
952                 matrixstack[matrixpos].transform(vec(dv.pos).mul(p->model->scale), dv.pos);
953             }
954             loopv(ragdoll->reljoints)
955             {
956                 const ragdollskel::reljoint &r = ragdoll->reljoints[i];
957                 const ragdollskel::joint &j = ragdoll->joints[r.parent];
958                 const boneinfo &br = bones[r.bone], &bj = bones[j.bone];
959                 d.reljoints[i].mul(dualquat(bdata[bj.interpindex]).invert(), bdata[br.interpindex]);
960             }
961         }
962 
genragdollbonesskelmodel::skeleton963         void genragdollbones(ragdolldata &d, skelcacheentry &sc, part *p)
964         {
965             if(!sc.bdata) sc.bdata = new dualquat[numinterpbones];
966             sc.nextversion();
967             vec trans = vec(d.center).div(p->model->scale).add(p->model->translate);
968             loopv(ragdoll->joints)
969             {
970                 const ragdollskel::joint &j = ragdoll->joints[i];
971                 const boneinfo &b = bones[j.bone];
972                 vec pos(0, 0, 0);
973                 loopk(3) if(j.vert[k]>=0) pos.add(d.verts[j.vert[k]].pos);
974                 pos.mul(j.weight/p->model->scale).sub(trans);
975                 matrix4x3 m;
976                 m.mul(d.tris[j.tri], pos, d.animjoints ? d.animjoints[i] : j.orient);
977                 sc.bdata[b.interpindex] = dualquat(m);
978             }
979             loopv(ragdoll->reljoints)
980             {
981                 const ragdollskel::reljoint &r = ragdoll->reljoints[i];
982                 const ragdollskel::joint &j = ragdoll->joints[r.parent];
983                 const boneinfo &br = bones[r.bone], &bj = bones[j.bone];
984                 sc.bdata[br.interpindex].mul(sc.bdata[bj.interpindex], d.reljoints[i]);
985             }
986             loopv(antipodes) sc.bdata[antipodes[i].child].fixantipodal(sc.bdata[antipodes[i].parent]);
987         }
988 
concattagtransformskelmodel::skeleton989         void concattagtransform(part *p, int i, const matrix4x3 &m, matrix4x3 &n)
990         {
991             matrix4x3 t;
992             t.mul(bones[tags[i].bone].base, tags[i].matrix);
993             n.mul(m, t);
994         }
995 
996         void calctags(part *p, skelcacheentry *sc = NULL)
997         {
998             loopv(p->links)
999             {
1000                 linkedpart &l = p->links[i];
1001                 tag &t = tags[l.tag];
1002                 dualquat q;
1003                 if(sc) q.mul(sc->bdata[bones[t.bone].interpindex], bones[t.bone].base);
1004                 else q = bones[t.bone].base;
1005                 matrix4x3 m;
1006                 m.mul(q, t.matrix);
1007                 m.d.mul(p->model->scale * sizescale);
1008                 l.matrix = m;
1009             }
1010         }
1011 
1012         void cleanup(bool full = true)
1013         {
loopvskelmodel::skeleton1014             loopv(skelcache)
1015             {
1016                 skelcacheentry &sc = skelcache[i];
1017                 loopj(MAXANIMPARTS) sc.as[j].cur.fr1 = -1;
1018                 DELETEA(sc.bdata);
1019             }
1020             skelcache.setsize(0);
1021             blendoffsets.clear();
1022             if(full) loopv(users) users[i]->cleanup();
1023         }
1024 
canpreloadskelmodel::skeleton1025         bool canpreload() { return !numframes || gpuaccelerate(); }
1026 
preloadskelmodel::skeleton1027         void preload()
1028         {
1029             if(!numframes) return;
1030             if(skelcache.empty())
1031             {
1032                 usegpuskel = gpuaccelerate();
1033             }
1034         }
1035 
checkskelcacheskelmodel::skeleton1036         skelcacheentry &checkskelcache(part *p, const animstate *as, float pitch, const vec &axis, const vec &forward, ragdolldata *rdata)
1037         {
1038             if(skelcache.empty())
1039             {
1040                 usegpuskel = gpuaccelerate();
1041             }
1042 
1043             int numanimparts = ((skelpart *)as->owner)->numanimparts;
1044             uchar *partmask = ((skelpart *)as->owner)->partmask;
1045             skelcacheentry *sc = NULL;
1046             bool match = false;
1047             loopv(skelcache)
1048             {
1049                 skelcacheentry &c = skelcache[i];
1050                 loopj(numanimparts) if(c.as[j]!=as[j]) goto mismatch;
1051                 if(c.pitch != pitch || c.partmask != partmask || c.ragdoll != rdata || (rdata && c.millis < rdata->lastmove)) goto mismatch;
1052                 match = true;
1053                 sc = &c;
1054                 break;
1055             mismatch:
1056                 if(c.millis < lastmillis) { sc = &c; break; }
1057             }
1058             if(!sc) sc = &skelcache.add();
1059             if(!match)
1060             {
1061                 loopi(numanimparts) sc->as[i] = as[i];
1062                 sc->pitch = pitch;
1063                 sc->partmask = partmask;
1064                 sc->ragdoll = rdata;
1065                 if(rdata) genragdollbones(*rdata, *sc, p);
1066                 else interpbones(as, pitch, axis, forward, numanimparts, partmask, *sc);
1067             }
1068             sc->millis = lastmillis;
1069             return *sc;
1070         }
1071 
getblendoffsetskelmodel::skeleton1072         int getblendoffset(UniformLoc &u)
1073         {
1074             int &offset = blendoffsets.access(Shader::lastshader->program, -1);
1075             if(offset < 0)
1076             {
1077                 defformatstring(offsetname, "%s[%d]", u.name, 2*numgpubones);
1078                 offset = glGetUniformLocation_(Shader::lastshader->program, offsetname);
1079             }
1080             return offset;
1081         }
1082 
setglslbonesskelmodel::skeleton1083         void setglslbones(UniformLoc &u, skelcacheentry &sc, skelcacheentry &bc, int count)
1084         {
1085             if(u.version == bc.version && u.data == bc.bdata) return;
1086             glUniform4fv_(u.loc, 2*numgpubones, sc.bdata[0].real.v);
1087             if(count > 0)
1088             {
1089                 int offset = getblendoffset(u);
1090                 if(offset >= 0) glUniform4fv_(offset, 2*count, bc.bdata[0].real.v);
1091             }
1092             u.version = bc.version;
1093             u.data = bc.bdata;
1094         }
1095 
setgpubonesskelmodel::skeleton1096         void setgpubones(skelcacheentry &sc, blendcacheentry *bc, int count)
1097         {
1098             if(!Shader::lastshader) return;
1099             if(Shader::lastshader->uniformlocs.length() < 1) return;
1100             UniformLoc &u = Shader::lastshader->uniformlocs[0];
1101             setglslbones(u, sc, bc ? *bc : sc, count);
1102         }
1103 
shouldcleanupskelmodel::skeleton1104         bool shouldcleanup() const
1105         {
1106             return numframes && (skelcache.empty() || gpuaccelerate()!=usegpuskel);
1107         }
1108     };
1109 
1110     static hashnameset<skeleton *> skeletons;
1111 
1112     struct skelmeshgroup : meshgroup
1113     {
1114         skeleton *skel;
1115 
1116         vector<blendcombo> blendcombos;
1117         int numblends[4];
1118 
1119         static const int MAXBLENDCACHE = 16;
1120         blendcacheentry blendcache[MAXBLENDCACHE];
1121 
1122         static const int MAXVBOCACHE = 16;
1123         vbocacheentry vbocache[MAXVBOCACHE];
1124 
1125         ushort *edata;
1126         GLuint ebuf;
1127         int vlen, vertsize, vblends, vweights;
1128         uchar *vdata;
1129         bool usecolor;
1130 
1131         skelhitdata *hitdata;
1132 
skelmeshgroupskelmodel::skelmeshgroup1133         skelmeshgroup() : skel(NULL), edata(NULL), ebuf(0), vlen(0), vertsize(0), vblends(0), vweights(0), vdata(NULL), usecolor(false), hitdata(NULL)
1134         {
1135             memset(numblends, 0, sizeof(numblends));
1136         }
1137 
~skelmeshgroupskelmodel::skelmeshgroup1138         virtual ~skelmeshgroup()
1139         {
1140             if(skel)
1141             {
1142                 if(skel->shared) skel->users.removeobj(this);
1143                 else DELETEP(skel);
1144             }
1145             if(ebuf) glDeleteBuffers_(1, &ebuf);
1146             loopi(MAXBLENDCACHE)
1147             {
1148                 DELETEA(blendcache[i].bdata);
1149             }
1150             loopi(MAXVBOCACHE)
1151             {
1152                 if(vbocache[i].vbuf) glDeleteBuffers_(1, &vbocache[i].vbuf);
1153             }
1154             DELETEA(vdata);
1155             deletehitdata();
1156         }
1157 
shareskeletonskelmodel::skelmeshgroup1158         void shareskeleton(const char *name)
1159         {
1160             if(!name)
1161             {
1162                 skel = new skeleton;
1163                 skel->users.add(this);
1164                 return;
1165             }
1166 
1167             if(skeletons.access(name)) skel = skeletons[name];
1168             else
1169             {
1170                 skel = new skeleton;
1171                 skel->name = newstring(name);
1172                 skeletons.add(skel);
1173             }
1174             skel->users.add(this);
1175             skel->shared++;
1176         }
1177 
findtagskelmodel::skelmeshgroup1178         int findtag(const char *name)
1179         {
1180             return skel->findtag(name);
1181         }
1182 
animkeyskelmodel::skelmeshgroup1183         void *animkey() { return skel; }
totalframesskelmodel::skelmeshgroup1184         int totalframes() const { return max(skel->numframes, 1); }
1185 
loadanimskelmodel::skelmeshgroup1186         virtual skelanimspec *loadanim(const char *filename) { return NULL; }
1187 
genvboskelmodel::skelmeshgroup1188         void genvbo(vbocacheentry &vc)
1189         {
1190             if(!vc.vbuf) glGenBuffers_(1, &vc.vbuf);
1191             if(ebuf) return;
1192 
1193             vector<ushort> idxs;
1194 
1195             vlen = 0;
1196             vblends = 0;
1197             if(skel->numframes && !skel->usegpuskel)
1198             {
1199                 vweights = 1;
1200                 loopv(blendcombos)
1201                 {
1202                     blendcombo &c = blendcombos[i];
1203                     c.interpindex = c.weights[1] ? skel->numgpubones + vblends++ : -1;
1204                 }
1205 
1206                 vertsize = sizeof(vvert);
1207                 looprendermeshes(skelmesh, m, vlen += m.genvbo(idxs, vlen));
1208                 DELETEA(vdata);
1209                 vdata = new uchar[vlen*vertsize];
1210                 looprendermeshes(skelmesh, m,
1211                 {
1212                     m.fillverts((vvert *)vdata);
1213                 });
1214             }
1215             else
1216             {
1217                 if(skel->numframes)
1218                 {
1219                     vweights = 4;
1220                     int availbones = skel->availgpubones() - skel->numgpubones;
1221                     while(vweights > 1 && availbones >= numblends[vweights-1]) availbones -= numblends[--vweights];
1222                     loopv(blendcombos)
1223                     {
1224                         blendcombo &c = blendcombos[i];
1225                         c.interpindex = c.size() > vweights ? skel->numgpubones + vblends++ : -1;
1226                     }
1227                 }
1228                 else
1229                 {
1230                     vweights = 0;
1231                     loopv(blendcombos) blendcombos[i].interpindex = -1;
1232                 }
1233 
1234                 looprendermeshes(skelmesh, m, { if(m.vcolors) { usecolor = true; break; }});
1235                 gle::bindvbo(vc.vbuf);
1236                 #define GENVBO(type, args) \
1237                     do \
1238                     { \
1239                         vertsize = sizeof(type); \
1240                         vector<type> vverts; \
1241                         looprendermeshes(skelmesh, m, vlen += m.genvbo args); \
1242                         glBufferData_(GL_ARRAY_BUFFER, vverts.length()*sizeof(type), vverts.getbuf(), GL_STATIC_DRAW); \
1243                     } while(0)
1244                 #define GENVBOANIM(type) GENVBO(type, (idxs, vlen, vverts))
1245                 #define GENVBOSTAT(type) GENVBO(type, (idxs, vlen, vverts, htdata, htlen))
1246                 if(skel->numframes)
1247                 {
1248                     if(usecolor) GENVBOANIM(vvertgwc);
1249                     else GENVBOANIM(vvertgw);
1250                 }
1251                 else
1252                 {
1253                     int numverts = 0, htlen = 128;
1254                     looprendermeshes(skelmesh, m, numverts += m.numverts);
1255                     while(htlen < numverts) htlen *= 2;
1256                     if(numverts*4 > htlen*3) htlen *= 2;
1257                     int *htdata = new int[htlen];
1258                     memset(htdata, -1, htlen*sizeof(int));
1259                     if(usecolor) GENVBOSTAT(vvertgc);
1260                     else GENVBOSTAT(vvertg);
1261                     delete[] htdata;
1262                 }
1263                 #undef GENVBO
1264                 #undef GENVBOANIM
1265                 #undef GENVBOSTAT
1266                 gle::clearvbo();
1267             }
1268 
1269             glGenBuffers_(1, &ebuf);
1270             gle::bindebo(ebuf);
1271             glBufferData_(GL_ELEMENT_ARRAY_BUFFER, idxs.length()*sizeof(ushort), idxs.getbuf(), GL_STATIC_DRAW);
1272             gle::clearebo();
1273         }
1274 
1275         template<class T>
bindbonesskelmodel::skelmeshgroup1276         void bindbones(T *vverts) { if(enablebones) disablebones(); }
bindbonesskelmodel::skelmeshgroup1277         void bindbones(vvertgw *vverts) { meshgroup::bindbones(vverts->weights, vverts->bones, vertsize); }
bindbonesskelmodel::skelmeshgroup1278         void bindbones(vvertgwc *vverts) { meshgroup::bindbones(vverts->weights, vverts->bones, vertsize); }
1279 
1280         template<class T>
bindcolorskelmodel::skelmeshgroup1281         void bindcolor(T *vverts) { if(enablecolor) disablecolor(); }
bindcolorskelmodel::skelmeshgroup1282         void bindcolor(vvertgc *vverts) { meshgroup::bindcolor(&vverts->col, vertsize); }
bindcolorskelmodel::skelmeshgroup1283         void bindcolor(vvertgwc *vverts) { meshgroup::bindcolor(&vverts->col, vertsize); }
1284 
1285         template<class T>
bindvboskelmodel::skelmeshgroup1286         void bindvbo(const animstate *as, part *p, vbocacheentry &vc)
1287         {
1288             T *vverts = 0;
1289             bindpos(ebuf, vc.vbuf, &vverts->pos, vertsize);
1290             if(as->cur.anim&ANIM_NOSKIN)
1291             {
1292                 if(enabletangents) disabletangents();
1293 
1294                 if(p->alphatested()) bindtc(&vverts->tc, vertsize);
1295                 else if(enabletc) disabletc();
1296             }
1297             else
1298             {
1299                 bindtangents(&vverts->tangent, vertsize);
1300 
1301                 bindtc(&vverts->tc, vertsize);
1302             }
1303             bindbones(vverts);
1304             bindcolor(vverts);
1305         }
1306 
1307         void bindvbo(const animstate *as, part *p, vbocacheentry &vc, skelcacheentry *sc = NULL, blendcacheentry *bc = NULL)
1308         {
1309             if(!skel->numframes)
1310             {
1311                 if(usecolor) bindvbo<vvertgc>(as, p, vc);
1312                 else bindvbo<vvertg>(as, p, vc);
1313             }
1314             else if(skel->usegpuskel)
1315             {
1316                 if(usecolor) bindvbo<vvertgwc>(as, p, vc);
1317                 else bindvbo<vvertgw>(as, p, vc);
1318             }
1319             else bindvbo<vvert>(as, p, vc);
1320         }
1321 
concattagtransformskelmodel::skelmeshgroup1322         void concattagtransform(part *p, int i, const matrix4x3 &m, matrix4x3 &n)
1323         {
1324             skel->concattagtransform(p, i, m, n);
1325         }
1326 
addblendcomboskelmodel::skelmeshgroup1327         int addblendcombo(const blendcombo &c)
1328         {
1329             loopv(blendcombos) if(blendcombos[i]==c)
1330             {
1331                 blendcombos[i].uses += c.uses;
1332                 return i;
1333             }
1334             numblends[c.size()-1]++;
1335             blendcombo &a = blendcombos.add(c);
1336             return a.interpindex = blendcombos.length()-1;
1337         }
1338 
sortblendcombosskelmodel::skelmeshgroup1339         void sortblendcombos()
1340         {
1341             blendcombos.sort(blendcombo::sortcmp);
1342             int *remap = new int[blendcombos.length()];
1343             loopv(blendcombos) remap[blendcombos[i].interpindex] = i;
1344             looprendermeshes(skelmesh, m,
1345             {
1346                 loopj(m.numverts)
1347                 {
1348                     vert &v = m.verts[j];
1349                     v.blend = remap[v.blend];
1350                 }
1351             });
1352             delete[] remap;
1353         }
1354 
remapblendskelmodel::skelmeshgroup1355         int remapblend(int blend)
1356         {
1357             const blendcombo &c = blendcombos[blend];
1358             return c.weights[1] ? c.interpindex : c.interpbones[0];
1359         }
1360 
blendbonesskelmodel::skelmeshgroup1361         static inline void blendbones(dualquat &d, const dualquat *bdata, const blendcombo &c)
1362         {
1363             d = bdata[c.interpbones[0]];
1364             d.mul(c.weights[0]);
1365             d.accumulate(bdata[c.interpbones[1]], c.weights[1]);
1366             if(c.weights[2])
1367             {
1368                 d.accumulate(bdata[c.interpbones[2]], c.weights[2]);
1369                 if(c.weights[3]) d.accumulate(bdata[c.interpbones[3]], c.weights[3]);
1370             }
1371         }
1372 
blendbonesskelmodel::skelmeshgroup1373         void blendbones(const skelcacheentry &sc, blendcacheentry &bc)
1374         {
1375             bc.nextversion();
1376             if(!bc.bdata) bc.bdata = new dualquat[vblends];
1377             dualquat *dst = bc.bdata - skel->numgpubones;
1378             bool normalize = !skel->usegpuskel || vweights<=1;
1379             loopv(blendcombos)
1380             {
1381                 const blendcombo &c = blendcombos[i];
1382                 if(c.interpindex<0) break;
1383                 dualquat &d = dst[c.interpindex];
1384                 blendbones(d, sc.bdata, c);
1385                 if(normalize) d.normalize();
1386             }
1387         }
1388 
blendbonesskelmodel::skelmeshgroup1389         static inline void blendbones(const dualquat *bdata, dualquat *dst, const blendcombo *c, int numblends)
1390         {
1391             loopi(numblends)
1392             {
1393                 dualquat &d = dst[i];
1394                 blendbones(d, bdata, c[i]);
1395                 d.normalize();
1396             }
1397         }
1398 
cleanupskelmodel::skelmeshgroup1399         void cleanup()
1400         {
1401             loopi(MAXBLENDCACHE)
1402             {
1403                 blendcacheentry &c = blendcache[i];
1404                 DELETEA(c.bdata);
1405                 c.owner = -1;
1406             }
1407             loopi(MAXVBOCACHE)
1408             {
1409                 vbocacheentry &c = vbocache[i];
1410                 if(c.vbuf) { glDeleteBuffers_(1, &c.vbuf); c.vbuf = 0; }
1411                 c.owner = -1;
1412             }
1413             if(ebuf) { glDeleteBuffers_(1, &ebuf); ebuf = 0; }
1414             if(skel) skel->cleanup(false);
1415             cleanuphitdata();
1416         }
1417 
1418         #define SEARCHCACHE(cachesize, cacheentry, cache, reusecheck) \
1419             loopi(cachesize) \
1420             { \
1421                 cacheentry &c = cache[i]; \
1422                 if(c.owner==owner) \
1423                 { \
1424                      if(c==sc) return c; \
1425                      else c.owner = -1; \
1426                      break; \
1427                 } \
1428             } \
1429             loopi(cachesize-1) \
1430             { \
1431                 cacheentry &c = cache[i]; \
1432                 if(reusecheck c.owner < 0 || c.millis < lastmillis) \
1433                     return c; \
1434             } \
1435             return cache[cachesize-1];
1436 
checkvbocacheskelmodel::skelmeshgroup1437         vbocacheentry &checkvbocache(skelcacheentry &sc, int owner)
1438         {
1439             SEARCHCACHE(MAXVBOCACHE, vbocacheentry, vbocache, !c.vbuf || );
1440         }
1441 
checkblendcacheskelmodel::skelmeshgroup1442         blendcacheentry &checkblendcache(skelcacheentry &sc, int owner)
1443         {
1444             SEARCHCACHE(MAXBLENDCACHE, blendcacheentry, blendcache, )
1445         }
1446 
1447         void cleanuphitdata();
1448         void deletehitdata();
1449         void buildhitdata(const uchar *hitzones);
1450         void intersect(skelhitdata *z, part *p, const skelmodel::skelcacheentry &sc, const vec &o, const vec &ray);
1451 
intersectskelmodel::skelmeshgroup1452         void intersect(const animstate *as, float pitch, const vec &axis, const vec &forward, modelstate *state, dynent *d, part *p, const vec &o, const vec &ray)
1453         {
1454             if(!hitdata) return;
1455 
1456             if(skel->shouldcleanup()) skel->cleanup();
1457 
1458             skelcacheentry &sc = skel->checkskelcache(p, as, pitch, axis, forward, !d || !d->ragdoll || d->ragdoll->skel != skel->ragdoll || d->ragdoll->millis == lastmillis ? NULL : d->ragdoll);
1459 
1460             intersect(hitdata, p, sc, o, ray);
1461 
1462             skel->calctags(p, &sc);
1463         }
1464 
preloadskelmodel::skelmeshgroup1465         void preload(part *p)
1466         {
1467             if(!skel->canpreload()) return;
1468             if(skel->shouldcleanup()) skel->cleanup();
1469             skel->preload();
1470             if(!vbocache->vbuf) genvbo(*vbocache);
1471         }
1472 
renderskelmodel::skelmeshgroup1473         void render(const animstate *as, float pitch, const vec &axis, const vec &forward, modelstate *state, dynent *d, part *p)
1474         {
1475             if(skel->shouldcleanup()) { skel->cleanup(); disablevbo(); }
1476 
1477             if(!skel->numframes)
1478             {
1479                 if(!(as->cur.anim&ANIM_NORENDER))
1480                 {
1481                     if(!vbocache->vbuf) genvbo(*vbocache);
1482                     bindvbo(as, p, *vbocache);
1483                     looprendermeshes(skelmesh, m,
1484                     {
1485                         p->skins[i].bind(m, as, state);
1486                         m.render(as, p->skins[i], *vbocache);
1487                     });
1488                 }
1489                 skel->calctags(p);
1490                 return;
1491             }
1492 
1493             skelcacheentry &sc = skel->checkskelcache(p, as, pitch, axis, forward, !d || !d->ragdoll || d->ragdoll->skel != skel->ragdoll || d->ragdoll->millis == lastmillis ? NULL : d->ragdoll);
1494             if(!(as->cur.anim&ANIM_NORENDER))
1495             {
1496                 int owner = &sc-&skel->skelcache[0];
1497                 vbocacheentry &vc = skel->usegpuskel ? *vbocache : checkvbocache(sc, owner);
1498                 vc.millis = lastmillis;
1499                 if(!vc.vbuf) genvbo(vc);
1500                 blendcacheentry *bc = NULL;
1501                 if(vblends)
1502                 {
1503                     bc = &checkblendcache(sc, owner);
1504                     bc->millis = lastmillis;
1505                     if(bc->owner!=owner)
1506                     {
1507                         bc->owner = owner;
1508                         *(animcacheentry *)bc = sc;
1509                         blendbones(sc, *bc);
1510                     }
1511                 }
1512                 if(!skel->usegpuskel && vc.owner != owner)
1513                 {
1514                     vc.owner = owner;
1515                     (animcacheentry &)vc = sc;
1516                     looprendermeshes(skelmesh, m,
1517                     {
1518                         m.interpverts(sc.bdata, bc ? bc->bdata : NULL, (vvert *)vdata, p->skins[i]);
1519                     });
1520                     gle::bindvbo(vc.vbuf);
1521                     glBufferData_(GL_ARRAY_BUFFER, vlen*vertsize, vdata, GL_STREAM_DRAW);
1522                 }
1523 
1524                 bindvbo(as, p, vc, &sc, bc);
1525 
1526                 looprendermeshes(skelmesh, m,
1527                 {
1528                     p->skins[i].bind(m, as, state);
1529                     if(skel->usegpuskel) skel->setgpubones(sc, bc, vblends);
1530                     m.render(as, p->skins[i], vc);
1531                 });
1532             }
1533 
1534             skel->calctags(p, &sc);
1535 
1536             if(as->cur.anim&ANIM_RAGDOLL && skel->ragdoll && !d->ragdoll)
1537             {
1538                 d->ragdoll = new ragdolldata(skel->ragdoll, p->model->scale);
1539                 skel->initragdoll(*d->ragdoll, sc, p);
1540                 d->ragdoll->init(d);
1541             }
1542         }
1543 
1544         virtual bool load(const char *name, float smooth) = 0;
1545     };
1546 
1547     virtual skelmeshgroup *newmeshes() = 0;
1548 
1549     meshgroup *loadmeshes(const char *name, const char *skelname = NULL, float smooth = 2)
1550     {
1551         skelmeshgroup *group = newmeshes();
1552         group->shareskeleton(skelname);
1553         if(!group->load(name, smooth)) { delete group; return NULL; }
1554         return group;
1555     }
1556 
1557     meshgroup *sharemeshes(const char *name, const char *skelname = NULL, float smooth = 2)
1558     {
1559         if(!meshgroups.access(name))
1560         {
1561             meshgroup *group = loadmeshes(name, skelname, smooth);
1562             if(!group) return NULL;
1563             meshgroups.add(group);
1564         }
1565         return meshgroups[name];
1566     }
1567 
1568     struct animpartmask
1569     {
1570         animpartmask *next;
1571         int numbones;
1572         uchar bones[1];
1573     };
1574 
1575     struct skelpart : part
1576     {
1577         animpartmask *buildingpartmask;
1578 
1579         uchar *partmask;
1580 
partskelmodel::skelpart1581         skelpart(animmodel *model, int index = 0) : part(model, index), buildingpartmask(NULL), partmask(NULL)
1582         {
1583         }
1584 
~skelpartskelmodel::skelpart1585         virtual ~skelpart()
1586         {
1587             DELETEA(buildingpartmask);
1588         }
1589 
sharepartmaskskelmodel::skelpart1590         uchar *sharepartmask(animpartmask *o)
1591         {
1592             static animpartmask *partmasks = NULL;
1593             animpartmask *p = partmasks;
1594             for(; p; p = p->next) if(p->numbones==o->numbones && !memcmp(p->bones, o->bones, p->numbones))
1595             {
1596                 delete[] (uchar *)o;
1597                 return p->bones;
1598             }
1599 
1600             o->next = p;
1601             partmasks = o;
1602             return o->bones;
1603         }
1604 
newpartmaskskelmodel::skelpart1605         animpartmask *newpartmask()
1606         {
1607             animpartmask *p = (animpartmask *)new uchar[sizeof(animpartmask) + ((skelmeshgroup *)meshes)->skel->numbones-1];
1608             p->numbones = ((skelmeshgroup *)meshes)->skel->numbones;
1609             memset(p->bones, 0, p->numbones);
1610             return p;
1611         }
1612 
initanimpartsskelmodel::skelpart1613         void initanimparts()
1614         {
1615             DELETEA(buildingpartmask);
1616             buildingpartmask = newpartmask();
1617         }
1618 
addanimpartskelmodel::skelpart1619         bool addanimpart(ushort *bonemask)
1620         {
1621             if(!buildingpartmask || numanimparts>=MAXANIMPARTS) return false;
1622             ((skelmeshgroup *)meshes)->skel->applybonemask(bonemask, buildingpartmask->bones, numanimparts);
1623             numanimparts++;
1624             return true;
1625         }
1626 
endanimpartsskelmodel::skelpart1627         void endanimparts()
1628         {
1629             if(buildingpartmask)
1630             {
1631                 partmask = sharepartmask(buildingpartmask);
1632                 buildingpartmask = NULL;
1633             }
1634 
1635             ((skelmeshgroup *)meshes)->skel->optimize();
1636         }
1637 
loadedskelmodel::skelpart1638         void loaded()
1639         {
1640             endanimparts();
1641             part::loaded();
1642         }
1643     };
1644 
skelmodelskelmodel1645     skelmodel(const char *name) : animmodel(name)
1646     {
1647     }
1648 
linktypeskelmodel1649     int linktype(animmodel *m, part *p) const
1650     {
1651         return type()==m->type() &&
1652             ((skelmeshgroup *)parts[0]->meshes)->skel == ((skelmeshgroup *)p->meshes)->skel ?
1653                 LINK_REUSE :
1654                 LINK_TAG;
1655     }
1656 
skeletalskelmodel1657     bool skeletal() const { return true; }
1658 
addpartskelmodel1659     skelpart &addpart()
1660     {
1661         flushpart();
1662         skelpart *p = new skelpart(this, parts.length());
1663         parts.add(p);
1664         return *p;
1665     }
1666 };
1667 
1668 hashnameset<skelmodel::skeleton *> skelmodel::skeletons;
1669 
1670 struct skeladjustment
1671 {
1672     float yaw, pitch, roll;
1673     vec translate;
1674 
skeladjustmentskeladjustment1675     skeladjustment(float yaw, float pitch, float roll, const vec &translate) : yaw(yaw), pitch(pitch), roll(roll), translate(translate) {}
1676 
adjustskeladjustment1677     void adjust(dualquat &dq)
1678     {
1679         if(yaw) dq.mulorient(quat(vec(0, 0, 1), yaw*RAD));
1680         if(pitch) dq.mulorient(quat(vec(0, -1, 0), pitch*RAD));
1681         if(roll) dq.mulorient(quat(vec(-1, 0, 0), roll*RAD));
1682         if(!translate.iszero()) dq.translate(translate);
1683     }
1684 };
1685 
1686 template<class MDL> struct skelloader : modelloader<MDL, skelmodel>
1687 {
1688     static vector<skeladjustment> adjustments;
1689     static vector<uchar> hitzones;
1690 
skelloaderskelloader1691     skelloader(const char *name) : modelloader<MDL, skelmodel>(name) {}
1692 
flushpartskelloader1693     void flushpart()
1694     {
1695         if(hitzones.length() && skelmodel::parts.length())
1696         {
1697             skelmodel::skelpart *p = (skelmodel::skelpart *)skelmodel::parts.last();
1698             skelmodel::skelmeshgroup *m = (skelmodel::skelmeshgroup *)p->meshes;
1699             if(m) m->buildhitdata(hitzones.getbuf());
1700         }
1701 
1702         adjustments.setsize(0);
1703         hitzones.setsize(0);
1704     }
1705 };
1706 
1707 template<class MDL> vector<skeladjustment> skelloader<MDL>::adjustments;
1708 template<class MDL> vector<uchar> skelloader<MDL>::hitzones;
1709 
1710 template<class MDL> struct skelcommands : modelcommands<MDL, struct MDL::skelmesh>
1711 {
1712     typedef modelcommands<MDL, struct MDL::skelmesh> commands;
1713     typedef struct MDL::skeleton skeleton;
1714     typedef struct MDL::skelmeshgroup meshgroup;
1715     typedef struct MDL::skelpart part;
1716     typedef struct MDL::skin skin;
1717     typedef struct MDL::boneinfo boneinfo;
1718     typedef struct MDL::skelanimspec animspec;
1719     typedef struct MDL::pitchdep pitchdep;
1720     typedef struct MDL::pitchtarget pitchtarget;
1721     typedef struct MDL::pitchcorrect pitchcorrect;
1722 
loadpartskelcommands1723     static void loadpart(char *meshfile, char *skelname, float *smooth)
1724     {
1725         if(!MDL::loading) { conoutf("\frNot loading an %s", MDL::formatname()); return; }
1726         defformatstring(filename, "%s/%s", MDL::dir, meshfile);
1727         part &mdl = MDL::loading->addpart();
1728         mdl.meshes = MDL::loading->sharemeshes(path(filename), skelname[0] ? skelname : NULL, *smooth > 0 ? cosf(clamp(*smooth, 0.0f, 180.0f)*RAD) : 2);
1729         if(!mdl.meshes) conoutf("\frCould not load %s", filename);
1730         else
1731         {
1732             if(mdl.meshes && ((meshgroup *)mdl.meshes)->skel->numbones > 0) mdl.disablepitch();
1733             mdl.initanimparts();
1734             mdl.initskins();
1735         }
1736     }
1737 
settagskelcommands1738     static void settag(char *name, char *tagname, float *tx, float *ty, float *tz, float *rx, float *ry, float *rz)
1739     {
1740         if(!MDL::loading || MDL::loading->parts.empty()) { conoutf("\frNot loading an %s", MDL::formatname()); return; }
1741         part &mdl = *(part *)MDL::loading->parts.last();
1742         int i = mdl.meshes ? ((meshgroup *)mdl.meshes)->skel->findbone(name) : -1;
1743         if(i >= 0)
1744         {
1745             float cx = *rx ? cosf(*rx/2*RAD) : 1, sx = *rx ? sinf(*rx/2*RAD) : 0,
1746                   cy = *ry ? cosf(*ry/2*RAD) : 1, sy = *ry ? sinf(*ry/2*RAD) : 0,
1747                   cz = *rz ? cosf(*rz/2*RAD) : 1, sz = *rz ? sinf(*rz/2*RAD) : 0;
1748             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)),
1749                         vec(*tx, *ty, *tz));
1750             ((meshgroup *)mdl.meshes)->skel->addtag(tagname, i, m);
1751             return;
1752         }
1753         conoutf("\frCould not find bone %s for tag %s", name, tagname);
1754     }
1755 
setpitchskelcommands1756     static void setpitch(char *name, float *pitchscale, float *pitchoffset, float *pitchmin, float *pitchmax)
1757     {
1758         if(!MDL::loading || MDL::loading->parts.empty()) { conoutf("\frNot loading an %s", MDL::formatname()); return; }
1759         part &mdl = *(part *)MDL::loading->parts.last();
1760 
1761         if(!*name)
1762         {
1763             mdl.pitchscale = *pitchscale;
1764             mdl.pitchoffset = *pitchoffset;
1765             if(*pitchmin || *pitchmax)
1766             {
1767                 mdl.pitchmin = *pitchmin;
1768                 mdl.pitchmax = *pitchmax;
1769             }
1770             else
1771             {
1772                 mdl.pitchmin = -360*fabs(mdl.pitchscale) + mdl.pitchoffset;
1773                 mdl.pitchmax = 360*fabs(mdl.pitchscale) + mdl.pitchoffset;
1774             }
1775             return;
1776         }
1777         if(mdl.meshes)
1778         {
1779             vector<int> elems;
1780             if(((meshgroup *)mdl.meshes)->skel->findbones(name, elems))
1781             {
1782                 loopv(elems)
1783                 {
1784                     boneinfo &b = ((meshgroup *)mdl.meshes)->skel->bones[elems[i]];
1785                     b.pitchscale = *pitchscale;
1786                     b.pitchoffset = *pitchoffset;
1787                     if(*pitchmin || *pitchmax)
1788                     {
1789                         b.pitchmin = *pitchmin;
1790                         b.pitchmax = *pitchmax;
1791                     }
1792                     else
1793                     {
1794                         b.pitchmin = -360*fabs(b.pitchscale) + b.pitchoffset;
1795                         b.pitchmax = 360*fabs(b.pitchscale) + b.pitchoffset;
1796                     }
1797                 }
1798                 return;
1799             }
1800         }
1801         conoutf("\frCould not find bone matching %s to pitch", name);
1802     }
1803 
setpitchtargetskelcommands1804     static void setpitchtarget(char *name, char *animfile, int *frameoffset, float *pitchmin, float *pitchmax)
1805     {
1806         if(!MDL::loading || MDL::loading->parts.empty()) { conoutf("\frNot loading an %s", MDL::formatname()); return; }
1807         part &mdl = *(part *)MDL::loading->parts.last();
1808         if(!mdl.meshes) return;
1809         defformatstring(filename, "%s/%s", MDL::dir, animfile);
1810         animspec *sa = ((meshgroup *)mdl.meshes)->loadanim(path(filename));
1811         if(!sa) { conoutf("\frCould not load %s anim file %s", MDL::formatname(), filename); return; }
1812         skeleton *skel = ((meshgroup *)mdl.meshes)->skel;
1813         if(skel)
1814         {
1815             vector<int> elems;
1816             if(skel->findbones(name, elems))
1817             {
1818                 loopv(elems)
1819                 {
1820                     bool skip = false;
1821                     loopvj(skel->pitchtargets) if(skel->pitchtargets[j].bone == elems[i]) { skip = true; break; }
1822                     if(skip) continue;
1823                     pitchtarget &t = skel->pitchtargets.add();
1824                     t.bone = elems[i];
1825                     t.frame = sa->frame + clamp(*frameoffset, 0, sa->range-1);
1826                     t.pitchmin = *pitchmin;
1827                     t.pitchmax = *pitchmax;
1828                 }
1829                 return;
1830             }
1831         }
1832         conoutf("\frCould not find bones matching %s to pitch target", name);
1833     }
1834 
setpitchcorrectskelcommands1835     static void setpitchcorrect(char *name, char *targetname, float *scale, float *pitchmin, float *pitchmax)
1836     {
1837         if(!MDL::loading || MDL::loading->parts.empty()) { conoutf("\frNot loading an %s", MDL::formatname()); return; }
1838         part &mdl = *(part *)MDL::loading->parts.last();
1839         if(!mdl.meshes) return;
1840         skeleton *skel = ((meshgroup *)mdl.meshes)->skel;
1841         int bone = skel ? skel->findbone(name) : -1;
1842         if(bone < 0)
1843         {
1844             conoutf("\frCould not find bone %s to pitch correct", name);
1845             return;
1846         }
1847         if(skel->findpitchcorrect(bone) >= 0) return;
1848         int targetbone = skel->findbone(targetname), target = -1;
1849         if(targetbone >= 0) loopv(skel->pitchtargets) if(skel->pitchtargets[i].bone == targetbone) { target = i; break; }
1850         if(target < 0)
1851         {
1852             conoutf("\frCould not find pitch target %s to pitch correct %s", targetname, name);
1853             return;
1854         }
1855         pitchcorrect c;
1856         c.bone = bone;
1857         c.target = target;
1858         c.pitchmin = *pitchmin;
1859         c.pitchmax = *pitchmax;
1860         c.pitchscale = *scale;
1861         int pos = skel->pitchcorrects.length();
1862         loopv(skel->pitchcorrects) if(bone <= skel->pitchcorrects[i].bone) { pos = i; break; }
1863         skel->pitchcorrects.insert(pos, c);
1864     }
1865 
setanimskelcommands1866     static void setanim(char *anim, char *animfile, float *speed, int *priority, int *startoffset, int *endoffset)
1867     {
1868         if(!MDL::loading || MDL::loading->parts.empty()) { conoutf("\frNot loading an %s", MDL::formatname()); return; }
1869 
1870         vector<int> anims;
1871         game::findanims(anim, anims);
1872         if(anims.empty()) conoutf("\frCould not find animation %s", anim);
1873         else
1874         {
1875             part *p = (part *)MDL::loading->parts.last();
1876             if(!p->meshes) return;
1877             defformatstring(filename, "%s/%s", MDL::dir, animfile);
1878             animspec *sa = ((meshgroup *)p->meshes)->loadanim(path(filename));
1879             if(!sa) conoutf("\frCould not load %s anim file %s", MDL::formatname(), filename);
1880             else loopv(anims)
1881             {
1882                 int start = sa->frame, end = sa->range;
1883                 if(*startoffset > 0) start += min(*startoffset, end-1);
1884                 else if(*startoffset < 0) start += max(end + *startoffset, 0);
1885                 end -= start - sa->frame;
1886                 if(*endoffset > 0) end = min(end, *endoffset);
1887                 else if(*endoffset < 0) end = max(end + *endoffset, 1);
1888                 MDL::loading->parts.last()->setanim(p->numanimparts-1, anims[i], start, end, *speed, *priority);
1889             }
1890         }
1891     }
1892 
setanimpartskelcommands1893     static void setanimpart(char *maskstr)
1894     {
1895         if(!MDL::loading || MDL::loading->parts.empty()) { conoutf("\frNot loading an %s", MDL::formatname()); return; }
1896 
1897         part *p = (part *)MDL::loading->parts.last();
1898 
1899         vector<char *> bonestrs;
1900         explodelist(maskstr, bonestrs);
1901         vector<ushort> bonemask;
1902         loopv(bonestrs)
1903         {
1904             char *bonestr = bonestrs[i];
1905             int num = 0;
1906             if(p->meshes)
1907             {
1908                 vector<int> elems;
1909                 if(((meshgroup *)p->meshes)->skel->findbones(bonestr[0]=='!' ? bonestr+1 : bonestr, elems)) loopv(elems)
1910                 {
1911                     bonemask.add(elems[i] | (bonestr[0]=='!' ? BONEMASK_NOT : 0));
1912                     num++;
1913                 }
1914             }
1915             if(!num) conoutf("\frCould not find bone %s for anim part mask [%s]", bonestr, maskstr);
1916         }
1917         bonestrs.deletearrays();
1918         if(bonemask.empty()) return;
1919         bonemask.sort();
1920         if(bonemask.length()) bonemask.add(BONEMASK_END);
1921         if(!p->addanimpart(bonemask.getbuf())) conoutf("\frToo many animation parts");
1922     }
1923 
setadjustskelcommands1924     static void setadjust(char *name, float *yaw, float *pitch, float *roll, float *tx, float *ty, float *tz)
1925     {
1926         if(!MDL::loading || MDL::loading->parts.empty()) { conoutf("\frNot loading an %s", MDL::formatname()); return; }
1927         part &mdl = *(part *)MDL::loading->parts.last();
1928 
1929         if(!name[0]) return;
1930         if(mdl.meshes)
1931         {
1932             vector<int> elems;
1933             if(((meshgroup *)mdl.meshes)->skel->findbones(name, elems))
1934             {
1935                 loopv(elems)
1936                 {
1937                     while(!MDL::adjustments.inrange(elems[i])) MDL::adjustments.add(skeladjustment(0, 0, 0, vec(0, 0, 0)));
1938                     MDL::adjustments[elems[i]] = skeladjustment(*yaw, *pitch, *roll, vec(*tx/4, *ty/4, *tz/4));
1939                 }
1940                 return;
1941             }
1942         }
1943         conoutf("\frCould not find bone %s to adjust", name);
1944     }
1945 
sethitzoneskelcommands1946     static void sethitzone(int *id, char *maskstr)
1947     {
1948         if(!MDL::loading || MDL::loading->parts.empty()) { conoutf("\frNot loading an %s", MDL::formatname()); return; }
1949         if(*id >= 0x80) { conoutf("\frInvalid hit zone id %d", *id); return; }
1950 
1951         part *p = (part *)MDL::loading->parts.last();
1952         meshgroup *m = (meshgroup *)p->meshes;
1953         if(!m || m->hitdata) return;
1954 
1955         vector<char *> bonestrs;
1956         explodelist(maskstr, bonestrs);
1957         vector<ushort> bonemask;
1958         loopv(bonestrs)
1959         {
1960             char *bonestr = bonestrs[i];
1961             int num = 0;
1962             if(p->meshes)
1963             {
1964                 vector<int> elems;
1965                 if(((meshgroup *)p->meshes)->skel->findbones(bonestr[0]=='!' ? bonestr+1 : bonestr, elems)) loopv(elems)
1966                 {
1967                     bonemask.add(elems[i] | (bonestr[0]=='!' ? BONEMASK_NOT : 0));
1968                     num++;
1969                 }
1970             }
1971             if(!num) conoutf("\frCould not find bone %s for hit zone mask [%s]", bonestr, maskstr);
1972         }
1973         bonestrs.deletearrays();
1974         if(bonemask.empty()) return;
1975         bonemask.sort();
1976         bonemask.add(BONEMASK_END);
1977 
1978         while(MDL::hitzones.length() < m->skel->numbones) MDL::hitzones.add(0xFF);
1979         m->skel->applybonemask(bonemask.getbuf(), MDL::hitzones.getbuf(), *id < 0 ? 0xFF : *id);
1980     }
1981 
skelcommandsskelcommands1982     skelcommands()
1983     {
1984         if(MDL::multiparted()) this->modelcommand(loadpart, "load", "ssf");
1985         this->modelcommand(settag, "tag", "ssffffff");
1986         this->modelcommand(setpitch, "pitch", "sffff");
1987         this->modelcommand(setpitchtarget, "pitchtarget", "ssiff");
1988         this->modelcommand(setpitchcorrect, "pitchcorrect", "ssfff");
1989         this->modelcommand(sethitzone, "hitzone", "is");
1990         if(MDL::cananimate())
1991         {
1992             this->modelcommand(setanim, "anim", "ssfiii");
1993             this->modelcommand(setanimpart, "animpart", "s");
1994             this->modelcommand(setadjust, "adjust", "sffffff");
1995         }
1996     }
1997 };
1998 
1999