1 struct md5;
2 
3 md5 *loadingmd5 = NULL;
4 
5 string md5dir;
6 
7 struct md5joint
8 {
9     vec pos;
10     quat orient;
11 };
12 
13 struct md5weight
14 {
15     int joint;
16     float bias;
17     vec pos;
18 };
19 
20 struct md5vert
21 {
22     float u, v;
23     ushort start, count;
24 };
25 
26 struct md5hierarchy
27 {
28     string name;
29     int parent, flags, start;
30 };
31 
32 struct md5 : skelmodel
33 {
md5md534     md5(const char *name) : skelmodel(name) {}
35 
typemd536     int type() const { return MDL_MD5; }
37 
38     struct md5mesh : skelmesh
39     {
40         md5weight *weightinfo;
41         int numweights;
42         md5vert *vertinfo;
43 
md5meshmd5::md5mesh44         md5mesh() : weightinfo(NULL), numweights(0), vertinfo(NULL)
45         {
46         }
47 
~md5meshmd5::md5mesh48         ~md5mesh()
49         {
50             cleanup();
51         }
52 
cleanupmd5::md5mesh53         void cleanup()
54         {
55             DELETEA(weightinfo);
56             DELETEA(vertinfo);
57         }
58 
buildvertsmd5::md5mesh59         void buildverts(vector<md5joint> &joints)
60         {
61             loopi(numverts)
62             {
63                 md5vert &v = vertinfo[i];
64                 vec pos(0, 0, 0);
65                 loopk(v.count)
66                 {
67                     md5weight &w = weightinfo[v.start+k];
68                     md5joint &j = joints[w.joint];
69                     pos.add(j.orient.rotate(w.pos).add(j.pos).mul(w.bias));
70                 }
71                 vert &vv = verts[i];
72                 vv.pos = pos;
73                 vv.u = v.u;
74                 vv.v = v.v;
75 
76                 blendcombo c;
77                 int sorted = 0;
78                 loopj(v.count)
79                 {
80                     md5weight &w = weightinfo[v.start+j];
81                     sorted = c.addweight(sorted, w.bias, w.joint);
82                 }
83                 c.finalize(sorted);
84                 vv.blend = addblendcombo(c);
85             }
86         }
87 
loadmd5::md5mesh88         void load(stream *f, char *buf, size_t bufsize)
89         {
90             md5weight w;
91             md5vert v;
92             tri t;
93             int index;
94 
95             while(f->getline(buf, bufsize) && buf[0]!='}')
96             {
97                 if(strstr(buf, "// meshes:"))
98                 {
99                     char *start = strchr(buf, ':')+1;
100                     if(*start==' ') start++;
101                     char *end = start + strlen(start)-1;
102                     while(end >= start && isspace(*end)) end--;
103                     name = newstring(start, end+1-start);
104                 }
105                 else if(strstr(buf, "shader"))
106                 {
107                     char *start = strchr(buf, '"'), *end = start ? strchr(start+1, '"') : NULL;
108                     if(start && end)
109                     {
110                         char *texname = newstring(start+1, end-(start+1));
111                         part *p = loadingmd5->parts.last();
112                         p->initskins(notexture, notexture, group->meshes.length());
113                         skin &s = p->skins.last();
114                         s.tex = textureload(makerelpath(md5dir, texname), 0, true, false);
115                         delete[] texname;
116                     }
117                 }
118                 else if(sscanf(buf, " numverts %d", &numverts)==1)
119                 {
120                     numverts = max(numverts, 0);
121                     if(numverts)
122                     {
123                         vertinfo = new md5vert[numverts];
124                         verts = new vert[numverts];
125                     }
126                 }
127                 else if(sscanf(buf, " numtris %d", &numtris)==1)
128                 {
129                     numtris = max(numtris, 0);
130                     if(numtris) tris = new tri[numtris];
131                 }
132                 else if(sscanf(buf, " numweights %d", &numweights)==1)
133                 {
134                     numweights = max(numweights, 0);
135                     if(numweights) weightinfo = new md5weight[numweights];
136                 }
137                 else if(sscanf(buf, " vert %d ( %f %f ) %hu %hu", &index, &v.u, &v.v, &v.start, &v.count)==5)
138                 {
139                     if(index>=0 && index<numverts) vertinfo[index] = v;
140                 }
141                 else if(sscanf(buf, " tri %d %hu %hu %hu", &index, &t.vert[0], &t.vert[1], &t.vert[2])==4)
142                 {
143                     if(index>=0 && index<numtris) tris[index] = t;
144                 }
145                 else if(sscanf(buf, " weight %d %d %f ( %f %f %f ) ", &index, &w.joint, &w.bias, &w.pos.x, &w.pos.y, &w.pos.z)==6)
146                 {
147                     w.pos.y = -w.pos.y;
148                     if(index>=0 && index<numweights) weightinfo[index] = w;
149                 }
150             }
151         }
152     };
153 
154     struct md5meshgroup : skelmeshgroup
155     {
md5meshgroupmd5::md5meshgroup156         md5meshgroup()
157         {
158         }
159 
loadmd5meshmd5::md5meshgroup160         bool loadmd5mesh(const char *filename)
161         {
162             stream *f = openfile(filename, "r");
163             if(!f) return false;
164 
165             char buf[512];
166             vector<md5joint> basejoints;
167             while(f->getline(buf, sizeof(buf)))
168             {
169                 int tmp;
170                 if(sscanf(buf, " MD5Version %d", &tmp)==1)
171                 {
172                     if(tmp!=10) { delete f; return false; }
173                 }
174                 else if(sscanf(buf, " numJoints %d", &tmp)==1)
175                 {
176                     if(tmp<1) { delete f; return false; }
177                     if(skel->numbones>0) continue;
178                     skel->numbones = tmp;
179                     skel->bones = new boneinfo[skel->numbones];
180                 }
181                 else if(sscanf(buf, " numMeshes %d", &tmp)==1)
182                 {
183                     if(tmp<1) { delete f; return false; }
184                 }
185                 else if(strstr(buf, "joints {"))
186                 {
187                     string name;
188                     int parent;
189                     md5joint j;
190                     while(f->getline(buf, sizeof(buf)) && buf[0]!='}')
191                     {
192                         if(sscanf(buf, " %s %d ( %f %f %f ) ( %f %f %f )",
193                             name, &parent, &j.pos.x, &j.pos.y, &j.pos.z,
194                             &j.orient.x, &j.orient.y, &j.orient.z)==8)
195                         {
196                             j.pos.y = -j.pos.y;
197                             j.orient.x = -j.orient.x;
198                             j.orient.z = -j.orient.z;
199                             if(basejoints.length()<skel->numbones)
200                             {
201                                 if(!skel->bones[basejoints.length()].name)
202                                 {
203                                     char *start = strchr(name, '"'), *end = start ? strchr(start+1, '"') : NULL;
204                                     skel->bones[basejoints.length()].name = start && end ? newstring(start+1, end-(start+1)) : newstring(name);
205                                 }
206                                 skel->bones[basejoints.length()].parent = parent;
207                             }
208                             j.orient.restorew();
209                             basejoints.add(j);
210                         }
211                     }
212                     if(basejoints.length()!=skel->numbones) { delete f; return false; }
213                 }
214                 else if(strstr(buf, "mesh {"))
215                 {
216                     md5mesh *m = new md5mesh;
217                     m->group = this;
218                     meshes.add(m);
219                     m->load(f, buf, sizeof(buf));
220                     if(!m->numtris || !m->numverts)
221                     {
222                         conoutf("empty mesh in %s", filename);
223                         meshes.removeobj(m);
224                         delete m;
225                     }
226                 }
227             }
228 
229             if(skel->shared <= 1)
230             {
231                 skel->linkchildren();
232                 loopv(basejoints)
233                 {
234                     boneinfo &b = skel->bones[i];
235                     b.base = dualquat(basejoints[i].orient, basejoints[i].pos);
236                     (b.invbase = b.base).invert();
237                 }
238             }
239 
240             loopv(meshes)
241             {
242                 md5mesh &m = *(md5mesh *)meshes[i];
243                 m.buildverts(basejoints);
244                 m.buildnorms();
245                 m.cleanup();
246             }
247 
248             sortblendcombos();
249 
250             delete f;
251             return true;
252         }
253 
loadmd5animmd5::md5meshgroup254         skelanimspec *loadmd5anim(const char *filename)
255         {
256             skelanimspec *sa = skel->findskelanim(filename);
257             if(sa) return sa;
258 
259             stream *f = openfile(filename, "r");
260             if(!f) return NULL;
261 
262             vector<md5hierarchy> hierarchy;
263             vector<md5joint> basejoints;
264             int animdatalen = 0, animframes = 0;
265             float *animdata = NULL;
266             dualquat *animbones = NULL;
267             char buf[512];
268             while(f->getline(buf, sizeof(buf)))
269             {
270                 int tmp;
271                 if(sscanf(buf, " MD5Version %d", &tmp)==1)
272                 {
273                     if(tmp!=10) { delete f; return NULL; }
274                 }
275                 else if(sscanf(buf, " numJoints %d", &tmp)==1)
276                 {
277                     if(tmp!=skel->numbones) { delete f; return NULL; }
278                 }
279                 else if(sscanf(buf, " numFrames %d", &animframes)==1)
280                 {
281                     if(animframes<1) { delete f; return NULL; }
282                 }
283                 else if(sscanf(buf, " frameRate %d", &tmp)==1);
284                 else if(sscanf(buf, " numAnimatedComponents %d", &animdatalen)==1)
285                 {
286                     if(animdatalen>0) animdata = new float[animdatalen];
287                 }
288                 else if(strstr(buf, "bounds {"))
289                 {
290                     while(f->getline(buf, sizeof(buf)) && buf[0]!='}');
291                 }
292                 else if(strstr(buf, "hierarchy {"))
293                 {
294                     while(f->getline(buf, sizeof(buf)) && buf[0]!='}')
295                     {
296                         md5hierarchy h;
297                         if(sscanf(buf, " %s %d %d %d", h.name, &h.parent, &h.flags, &h.start)==4)
298                             hierarchy.add(h);
299                     }
300                 }
301                 else if(strstr(buf, "baseframe {"))
302                 {
303                     while(f->getline(buf, sizeof(buf)) && buf[0]!='}')
304                     {
305                         md5joint j;
306                         if(sscanf(buf, " ( %f %f %f ) ( %f %f %f )", &j.pos.x, &j.pos.y, &j.pos.z, &j.orient.x, &j.orient.y, &j.orient.z)==6)
307                         {
308                             j.pos.y = -j.pos.y;
309                             j.orient.x = -j.orient.x;
310                             j.orient.z = -j.orient.z;
311                             j.orient.restorew();
312                             basejoints.add(j);
313                         }
314                     }
315                     if(basejoints.length()!=skel->numbones) { delete f; return NULL; }
316                     animbones = new dualquat[(skel->numframes+animframes)*skel->numbones];
317                     if(skel->bones)
318                     {
319                         memcpy(animbones, skel->framebones, skel->numframes*skel->numbones*sizeof(dualquat));
320                         delete[] skel->framebones;
321                     }
322                     skel->framebones = animbones;
323                     animbones += skel->numframes*skel->numbones;
324 
325                     sa = &skel->addskelanim(filename);
326                     sa->frame = skel->numframes;
327                     sa->range = animframes;
328 
329                     skel->numframes += animframes;
330                 }
331                 else if(sscanf(buf, " frame %d", &tmp)==1)
332                 {
333                     for(int numdata = 0; f->getline(buf, sizeof(buf)) && buf[0]!='}';)
334                     {
335                         for(char *src = buf, *next = src; numdata < animdatalen; numdata++, src = next)
336                         {
337                             animdata[numdata] = strtod(src, &next);
338                             if(next <= src) break;
339                         }
340                     }
341                     dualquat *frame = &animbones[tmp*skel->numbones];
342                     loopv(basejoints)
343                     {
344                         md5hierarchy &h = hierarchy[i];
345                         md5joint j = basejoints[i];
346                         if(h.start < animdatalen && h.flags)
347                         {
348                             float *jdata = &animdata[h.start];
349                             if(h.flags&1) j.pos.x = *jdata++;
350                             if(h.flags&2) j.pos.y = -*jdata++;
351                             if(h.flags&4) j.pos.z = *jdata++;
352                             if(h.flags&8) j.orient.x = -*jdata++;
353                             if(h.flags&16) j.orient.y = *jdata++;
354                             if(h.flags&32) j.orient.z = -*jdata++;
355                             j.orient.restorew();
356                         }
357                         frame[i] = dualquat(j.orient, j.pos);
358                         frame[i].mul(skel->bones[i].invbase);
359                         if(h.parent >= 0) frame[i].mul(skel->bones[h.parent].base, dualquat(frame[i]));
360                         frame[i].fixantipodal(skel->framebones[i]);
361                     }
362                 }
363             }
364 
365             DELETEA(animdata);
366             delete f;
367 
368             return sa;
369         }
370 
loadmd5::md5meshgroup371         bool load(const char *meshfile)
372         {
373             name = newstring(meshfile);
374 
375             if(!loadmd5mesh(meshfile)) return false;
376 
377             return true;
378         }
379     };
380 
loadmeshesmd5381     meshgroup *loadmeshes(char *name, va_list args)
382     {
383         md5meshgroup *group = new md5meshgroup;
384         group->shareskeleton(va_arg(args, char *));
385         if(!group->load(name)) { delete group; return NULL; }
386         return group;
387     }
388 
loaddefaultpartsmd5389     bool loaddefaultparts()
390     {
391         skelpart &mdl = *new skelpart;
392         parts.add(&mdl);
393         mdl.model = this;
394         mdl.index = 0;
395         mdl.pitchscale = mdl.pitchoffset = mdl.pitchmin = mdl.pitchmax = 0;
396         const char *fname = loadname + strlen(loadname);
397         do --fname; while(fname >= loadname && *fname!='/' && *fname!='\\');
398         fname++;
399         defformatstring(meshname)("models/%s/%s.md5mesh", loadname, fname);
400         mdl.meshes = sharemeshes(path(meshname), NULL);
401         if(!mdl.meshes) return false;
402         mdl.initanimparts();
403         mdl.initskins();
404         defformatstring(animname)("models/%s/%s.md5anim", loadname, fname);
405         ((md5meshgroup *)mdl.meshes)->loadmd5anim(path(animname));
406         return true;
407     }
408 
loadmd5409     bool load()
410     {
411         if(loaded) return true;
412         formatstring(md5dir)("models/%s", loadname);
413         defformatstring(cfgname)("models/%s/md5.cfg", loadname);
414 
415         loadingmd5 = this;
416         persistidents = false;
417         if(execfile(cfgname, false) && parts.length()) // configured md5, will call the md5* commands below
418         {
419             persistidents = true;
420             loadingmd5 = NULL;
421             loopv(parts) if(!parts[i]->meshes) return false;
422         }
423         else // md5 without configuration, try default tris and skin
424         {
425             persistidents = true;
426             if(!loaddefaultparts())
427             {
428                 loadingmd5 = NULL;
429                 return false;
430             }
431             loadingmd5 = NULL;
432         }
433         scale /= 4;
434         parts[0]->translate = translate;
435         loopv(parts)
436         {
437             skelpart *p = (skelpart *)parts[i];
438             p->endanimparts();
439             p->meshes->shared++;
440         }
441         preloadshaders();
442         return loaded = true;
443     }
444 };
445 
setmd5dir(char * name)446 void setmd5dir(char *name)
447 {
448     if(!loadingmd5) { conoutf("\frnot loading an md5"); return; }
449     formatstring(md5dir)("models/%s", name);
450 }
451 
md5load(char * meshfile,char * skelname)452 void md5load(char *meshfile, char *skelname)
453 {
454     if(!loadingmd5) { conoutf("\frnot loading an md5"); return; }
455     defformatstring(filename)("%s/%s", md5dir, meshfile);
456     md5::skelpart &mdl = *new md5::skelpart;
457     loadingmd5->parts.add(&mdl);
458     mdl.model = loadingmd5;
459     mdl.index = loadingmd5->parts.length()-1;
460     mdl.pitchscale = mdl.pitchoffset = mdl.pitchmin = mdl.pitchmax = 0;
461     mdl.meshes = loadingmd5->sharemeshes(path(filename), skelname[0] ? skelname : NULL);
462     if(!mdl.meshes) conoutf("\frcould not load %s", filename); // ignore failure
463     else
464     {
465         mdl.initanimparts();
466         mdl.initskins();
467     }
468 }
469 
md5tag(char * name,char * tagname)470 void md5tag(char *name, char *tagname)
471 {
472     if(!loadingmd5 || loadingmd5->parts.empty()) { conoutf("\frnot loading an md5"); return; }
473     md5::part &mdl = *loadingmd5->parts.last();
474     int i = mdl.meshes ? ((md5::skelmeshgroup *)mdl.meshes)->skel->findbone(name) : -1;
475     if(i >= 0)
476     {
477         ((md5::skelmeshgroup *)mdl.meshes)->skel->addtag(tagname, i);
478         return;
479     }
480     conoutf("\frcould not find bone %s for tag %s", name, tagname);
481 }
482 
md5pitch(char * name,float * pitchscale,float * pitchoffset,float * pitchmin,float * pitchmax)483 void md5pitch(char *name, float *pitchscale, float *pitchoffset, float *pitchmin, float *pitchmax)
484 {
485     if(!loadingmd5 || loadingmd5->parts.empty()) { conoutf("\frnot loading an md5"); return; }
486     md5::part &mdl = *loadingmd5->parts.last();
487 
488     if(name[0])
489     {
490         int i = mdl.meshes ? ((md5::skelmeshgroup *)mdl.meshes)->skel->findbone(name) : -1;
491         if(i>=0)
492         {
493             md5::boneinfo &b = ((md5::skelmeshgroup *)mdl.meshes)->skel->bones[i];
494             b.pitchscale = *pitchscale;
495             b.pitchoffset = *pitchoffset;
496             if(*pitchmin || *pitchmax)
497             {
498                 b.pitchmin = *pitchmin;
499                 b.pitchmax = *pitchmax;
500             }
501             else
502             {
503                 b.pitchmin = -360*b.pitchscale;
504                 b.pitchmax = 360*b.pitchscale;
505             }
506             return;
507         }
508         conoutf("\frcould not find bone %s to pitch", name);
509         return;
510     }
511 
512     mdl.pitchscale = *pitchscale;
513     mdl.pitchoffset = *pitchoffset;
514     if(*pitchmin || *pitchmax)
515     {
516         mdl.pitchmin = *pitchmin;
517         mdl.pitchmax = *pitchmax;
518     }
519     else
520     {
521         mdl.pitchmin = -360*mdl.pitchscale;
522         mdl.pitchmax = 360*mdl.pitchscale;
523     }
524 }
525 
526 #define loopmd5meshes(meshname, m, body) \
527     if(!loadingmd5 || loadingmd5->parts.empty()) { conoutf("\frnot loading an md5"); return; } \
528     md5::part &mdl = *loadingmd5->parts.last(); \
529     if(!mdl.meshes) return; \
530     loopv(mdl.meshes->meshes) \
531     { \
532         md5::skelmesh &m = *(md5::skelmesh *)mdl.meshes->meshes[i]; \
533         if(!strcmp(meshname, "*") || (m.name && !strcmp(m.name, meshname))) \
534         { \
535             body; \
536         } \
537     }
538 
539 #define loopmd5skins(meshname, s, body) loopmd5meshes(meshname, m, { md5::skin &s = mdl.skins[i]; body; })
540 
md5skin(char * meshname,char * tex,char * masks,float * envmapmax,float * envmapmin)541 void md5skin(char *meshname, char *tex, char *masks, float *envmapmax, float *envmapmin)
542 {
543     loopmd5skins(meshname, s,
544         s.tex = textureload(makerelpath(md5dir, tex), 0, true, false);
545         if(*masks)
546         {
547             s.masks = textureload(makerelpath(md5dir, masks, NULL, "<ffmask:25>"), 0, true, false);
548             s.envmapmax = *envmapmax;
549             s.envmapmin = *envmapmin;
550         }
551     );
552 }
553 
md5spec(char * meshname,int * percent)554 void md5spec(char *meshname, int *percent)
555 {
556     float spec = 1.0f;
557     if(*percent>0) spec = *percent/100.0f;
558     else if(*percent<0) spec = 0.0f;
559     loopmd5skins(meshname, s, s.spec = spec);
560 }
561 
md5ambient(char * meshname,int * percent)562 void md5ambient(char *meshname, int *percent)
563 {
564     float ambient = 0.3f;
565     if(*percent>0) ambient = *percent/100.0f;
566     else if(*percent<0) ambient = 0.0f;
567     loopmd5skins(meshname, s, s.ambient = ambient);
568 }
569 
md5glow(char * meshname,int * percent)570 void md5glow(char *meshname, int *percent)
571 {
572     float glow = 3.0f;
573     if(*percent>0) glow = *percent/100.0f;
574     else if(*percent<0) glow = 0.0f;
575     loopmd5skins(meshname, s, s.glow = glow);
576 }
577 
md5glare(char * meshname,float * specglare,float * glowglare)578 void md5glare(char *meshname, float *specglare, float *glowglare)
579 {
580     loopmd5skins(meshname, s, { s.specglare = *specglare; s.glowglare = *glowglare; });
581 }
582 
md5alphatest(char * meshname,float * cutoff)583 void md5alphatest(char *meshname, float *cutoff)
584 {
585     loopmd5skins(meshname, s, s.alphatest = max(0.0f, min(1.0f, *cutoff)));
586 }
587 
md5alphablend(char * meshname,int * blend)588 void md5alphablend(char *meshname, int *blend)
589 {
590     loopmd5skins(meshname, s, s.alphablend = *blend!=0);
591 }
592 
md5cullface(char * meshname,int * cullface)593 void md5cullface(char *meshname, int *cullface)
594 {
595     loopmd5skins(meshname, s, s.cullface = *cullface!=0);
596 }
597 
md5envmap(char * meshname,char * envmap)598 void md5envmap(char *meshname, char *envmap)
599 {
600     Texture *tex = cubemapload(envmap);
601     loopmd5skins(meshname, s, s.envmap = tex);
602 }
603 
md5bumpmap(char * meshname,char * normalmap,char * skin)604 void md5bumpmap(char *meshname, char *normalmap, char *skin)
605 {
606     Texture *normalmaptex = NULL, *skintex = NULL;
607     normalmaptex = textureload(makerelpath(md5dir, normalmap, "<noff>"), 0, true, false);
608     if(skin[0]) skintex = textureload(makerelpath(md5dir, skin, "<noff>"), 0, true, false);
609     loopmd5skins(meshname, s, { s.unlittex = skintex; s.normalmap = normalmaptex; m.calctangents(); });
610 }
611 
md5fullbright(char * meshname,float * fullbright)612 void md5fullbright(char *meshname, float *fullbright)
613 {
614     loopmd5skins(meshname, s, s.fullbright = *fullbright);
615 }
616 
md5shader(char * meshname,char * shader)617 void md5shader(char *meshname, char *shader)
618 {
619     loopmd5skins(meshname, s, s.shader = lookupshaderbyname(shader));
620 }
621 
md5scroll(char * meshname,float * scrollu,float * scrollv)622 void md5scroll(char *meshname, float *scrollu, float *scrollv)
623 {
624     loopmd5skins(meshname, s, { s.scrollu = *scrollu; s.scrollv = *scrollv; });
625 }
626 
md5anim(char * anim,char * animfile,float * speed,int * priority)627 void md5anim(char *anim, char *animfile, float *speed, int *priority)
628 {
629     if(!loadingmd5 || loadingmd5->parts.empty()) { conoutf("\frnot loading an md5"); return; }
630 
631     vector<int> anims;
632     game::findanims(anim, anims);
633     if(anims.empty()) conoutf("\frcould not find animation %s in %s", anim, loadingmd5->loadname);
634     else
635     {
636         md5::part *p = loadingmd5->parts.last();
637         if(!p->meshes) return;
638         defformatstring(filename)("%s/%s", md5dir, animfile);
639         md5::skelanimspec *sa = ((md5::md5meshgroup *)p->meshes)->loadmd5anim(path(filename));
640         if(!sa) conoutf("\frcould not load md5anim file %s", filename);
641         else loopv(anims)
642         {
643             loadingmd5->parts.last()->setanim(p->numanimparts-1, anims[i], sa->frame, sa->range, *speed, *priority);
644         }
645     }
646 }
647 
md5animpart(char * maskstr)648 void md5animpart(char *maskstr)
649 {
650     if(!loadingmd5 || loadingmd5->parts.empty()) { conoutf("\frnot loading an md5"); return; }
651 
652     md5::skelpart *p = (md5::skelpart *)loadingmd5->parts.last();
653 
654     vector<char *> bonestrs;
655     explodelist(maskstr, bonestrs);
656     vector<ushort> bonemask;
657     loopv(bonestrs)
658     {
659         char *bonestr = bonestrs[i];
660         int bone = p->meshes ? ((md5::skelmeshgroup *)p->meshes)->skel->findbone(bonestr[0]=='!' ? bonestr+1 : bonestr) : -1;
661         if(bone<0) { conoutf("\frcould not find bone %s for anim part mask [%s]", bonestr, maskstr); bonestrs.deletecontentsa(); return; }
662         bonemask.add(bone | (bonestr[0]=='!' ? BONEMASK_NOT : 0));
663     }
664     bonestrs.deletecontentsa();
665     bonemask.sort(bonemaskcmp);
666     if(bonemask.length()) bonemask.add(BONEMASK_END);
667 
668     if(!p->addanimpart(bonemask.getbuf())) conoutf("\frtoo many animation parts");
669 }
670 
md5link(int * parent,int * child,char * tagname,float * x,float * y,float * z)671 void md5link(int *parent, int *child, char *tagname, float *x, float *y, float *z)
672 {
673     if(!loadingmd5) { conoutf("\frnot loading an md5"); return; }
674     if(!loadingmd5->parts.inrange(*parent) || !loadingmd5->parts.inrange(*child)) { conoutf("\frno models loaded to link"); return; }
675     if(!loadingmd5->parts[*parent]->link(loadingmd5->parts[*child], tagname, vec(*x, *y, *z))) conoutf("\frcould not link model %s", loadingmd5->loadname);
676 }
677 
md5noclip(char * meshname,int * noclip)678 void md5noclip(char *meshname, int *noclip)
679 {
680     loopmd5meshes(meshname, m, m.noclip = *noclip!=0);
681 }
682 
683 COMMANDN(md5dir, setmd5dir, "s");
684 COMMAND(md5load, "ss");
685 COMMAND(md5tag, "ss");
686 COMMAND(md5pitch, "sffff");
687 COMMAND(md5skin, "sssff");
688 COMMAND(md5spec, "si");
689 COMMAND(md5ambient, "si");
690 COMMAND(md5glow, "si");
691 COMMAND(md5glare, "sff");
692 COMMAND(md5alphatest, "sf");
693 COMMAND(md5alphablend, "si");
694 COMMAND(md5cullface, "si");
695 COMMAND(md5envmap, "ss");
696 COMMAND(md5bumpmap, "sss");
697 COMMAND(md5fullbright, "sf");
698 COMMAND(md5shader, "ss");
699 COMMAND(md5scroll, "sff");
700 COMMAND(md5animpart, "s");
701 COMMAND(md5anim, "ssfi");
702 COMMAND(md5link, "iisfff");
703 COMMAND(md5noclip, "si");
704 
705