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