3 md5
*loadingmd5
= NULL
;
29 int parent
, flags
, start
;
32 struct md5
: skelmodel
34 md5(const char *name
) : skelmodel(name
) {}
36 int type() const { return MDL_MD5
; }
38 struct md5mesh
: skelmesh
40 md5weight
*weightinfo
;
44 md5mesh() : weightinfo(NULL
), numweights(0), vertinfo(NULL
)
59 void buildverts(vector
<md5joint
> &joints
)
63 md5vert
&v
= vertinfo
[i
];
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
));
80 md5weight
&w
= weightinfo
[v
.start
+j
];
81 sorted
= c
.addweight(sorted
, w
.bias
, w
.joint
);
84 vv
.blend
= addblendcombo(c
);
88 void buildnorms(bool areaweight
= true)
90 loopi(numverts
) verts
[i
].norm
= vec(0, 0, 0);
94 vert
&v1
= verts
[t
.vert
[0]], &v2
= verts
[t
.vert
[1]], &v3
= verts
[t
.vert
[2]];
96 norm
.cross(vec(v2
.pos
).sub(v1
.pos
), vec(v3
.pos
).sub(v1
.pos
));
97 if(!areaweight
) norm
.normalize();
102 loopi(numverts
) verts
[i
].norm
.normalize();
105 void load(FILE *f
, char *buf
, size_t bufsize
)
114 fgets(buf
, bufsize
, f
);
115 if(buf
[0]=='}' || feof(f
)) break;
116 if(strstr(buf
, "// meshes:"))
118 char *start
= strchr(buf
, ':')+1;
119 if(*start
==' ') start
++;
120 char *end
= start
+ strlen(start
)-1;
121 while(end
>= start
&& isspace(*end
)) end
--;
122 name
= newstring(start
, end
+1-start
);
124 else if(strstr(buf
, "shader"))
126 char *start
= strchr(buf
, '"'), *end
= start
? strchr(start
+1, '"') : NULL
;
129 char *texname
= newstring(start
+1, end
-(start
+1));
130 part
*p
= loadingmd5
->parts
.last();
131 p
->initskins(notexture
, notexture
, group
->meshes
.length());
132 skin
&s
= p
->skins
.last();
133 s
.tex
= textureload(makerelpath(md5dir
, texname
), 0, true, false);
137 else if(sscanf(buf
, " numverts %d", &numverts
)==1)
139 numverts
= max(numverts
, 0);
142 vertinfo
= new md5vert
[numverts
];
143 verts
= new vert
[numverts
];
146 else if(sscanf(buf
, " numtris %d", &numtris
)==1)
148 numtris
= max(numtris
, 0);
149 if(numtris
) tris
= new tri
[numtris
];
151 else if(sscanf(buf
, " numweights %d", &numweights
)==1)
153 numweights
= max(numweights
, 0);
154 if(numweights
) weightinfo
= new md5weight
[numweights
];
156 else if(sscanf(buf
, " vert %d ( %f %f ) %hu %hu", &index
, &v
.u
, &v
.v
, &v
.start
, &v
.count
)==5)
158 if(index
>=0 && index
<numverts
) vertinfo
[index
] = v
;
160 else if(sscanf(buf
, " tri %d %hu %hu %hu", &index
, &t
.vert
[0], &t
.vert
[1], &t
.vert
[2])==4)
162 if(index
>=0 && index
<numtris
) tris
[index
] = t
;
164 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)
167 if(index
>=0 && index
<numweights
) weightinfo
[index
] = w
;
173 struct md5meshgroup
: skelmeshgroup
179 bool loadmd5mesh(const char *filename
)
181 FILE *f
= openfile(filename
, "r");
185 vector
<md5joint
> basejoints
;
188 fgets(buf
, sizeof(buf
), f
);
191 if(sscanf(buf
, " MD5Version %d", &tmp
)==1)
193 if(tmp
!=10) { fclose(f
); return false; }
195 else if(sscanf(buf
, " numJoints %d", &tmp
)==1)
197 if(tmp
<1) { fclose(f
); return false; }
198 if(skel
->numbones
>0) continue;
199 skel
->numbones
= tmp
;
200 skel
->bones
= new boneinfo
[skel
->numbones
];
202 else if(sscanf(buf
, " numMeshes %d", &tmp
)==1)
204 if(tmp
<1) { fclose(f
); return false; }
206 else if(strstr(buf
, "joints {"))
213 fgets(buf
, sizeof(buf
), f
);
214 if(buf
[0]=='}' || feof(f
)) break;
215 if(sscanf(buf
, " %s %d ( %f %f %f ) ( %f %f %f )",
216 name
, &parent
, &j
.pos
.x
, &j
.pos
.y
, &j
.pos
.z
,
217 &j
.orient
.x
, &j
.orient
.y
, &j
.orient
.z
)==8)
220 j
.orient
.x
= -j
.orient
.x
;
221 j
.orient
.z
= -j
.orient
.z
;
222 if(basejoints
.length()<skel
->numbones
)
224 if(!skel
->bones
[basejoints
.length()].name
)
226 char *start
= strchr(name
, '"'), *end
= start
? strchr(start
+1, '"') : NULL
;
227 skel
->bones
[basejoints
.length()].name
= start
&& end
? newstring(start
+1, end
-(start
+1)) : newstring(name
);
229 skel
->bones
[basejoints
.length()].parent
= parent
;
235 if(basejoints
.length()!=skel
->numbones
) { fclose(f
); return false; }
237 else if(strstr(buf
, "mesh {"))
239 md5mesh
*m
= new md5mesh
;
242 m
->load(f
, buf
, sizeof(buf
));
243 if(!m
->numtris
|| !m
->numverts
)
245 conoutf("empty mesh in %s", filename
);
252 if(skel
->shared
<= 1)
254 skel
->linkchildren();
255 loopv(basejoints
) skel
->bones
[i
].base
= dualquat(basejoints
[i
].orient
, basejoints
[i
].pos
);
260 md5mesh
&m
= *(md5mesh
*)meshes
[i
];
261 m
.buildverts(basejoints
);
272 skelanimspec
*loadmd5anim(const char *filename
)
274 skelanimspec
*sa
= skel
->findskelanim(filename
);
277 FILE *f
= openfile(filename
, "r");
280 vector
<md5hierarchy
> hierarchy
;
281 vector
<md5joint
> basejoints
;
282 int animdatalen
= 0, animframes
= 0;
283 float *animdata
= NULL
;
284 dualquat
*animbones
= NULL
;
288 fgets(buf
, sizeof(buf
), f
);
291 if(sscanf(buf
, " MD5Version %d", &tmp
)==1)
293 if(tmp
!=10) { fclose(f
); return NULL
; }
295 else if(sscanf(buf
, " numJoints %d", &tmp
)==1)
297 if(tmp
!=skel
->numbones
) { fclose(f
); return NULL
; }
299 else if(sscanf(buf
, " numFrames %d", &animframes
)==1)
301 if(animframes
<1) { fclose(f
); return NULL
; }
303 else if(sscanf(buf
, " frameRate %d", &tmp
)==1);
304 else if(sscanf(buf
, " numAnimatedComponents %d", &animdatalen
)==1)
306 if(animdatalen
<1) { fclose(f
); return NULL
; }
307 animdata
= new float[animdatalen
];
309 else if(strstr(buf
, "bounds {"))
313 fgets(buf
, sizeof(buf
), f
);
314 if(buf
[0]=='}' || feof(f
)) break;
317 else if(strstr(buf
, "hierarchy {"))
321 fgets(buf
, sizeof(buf
), f
);
322 if(buf
[0]=='}' || feof(f
)) break;
324 if(sscanf(buf
, " %s %d %d %d", h
.name
, &h
.parent
, &h
.flags
, &h
.start
)==4)
328 else if(strstr(buf
, "baseframe {"))
332 fgets(buf
, sizeof(buf
), f
);
333 if(buf
[0]=='}' || feof(f
)) break;
335 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)
338 j
.orient
.x
= -j
.orient
.x
;
339 j
.orient
.z
= -j
.orient
.z
;
344 if(basejoints
.length()!=skel
->numbones
) { fclose(f
); return NULL
; }
345 animbones
= new dualquat
[(skel
->numframes
+animframes
)*skel
->numbones
];
348 memcpy(animbones
, skel
->framebones
, skel
->numframes
*skel
->numbones
*sizeof(dualquat
));
349 delete[] skel
->framebones
;
351 skel
->framebones
= animbones
;
352 animbones
+= skel
->numframes
*skel
->numbones
;
354 sa
= &skel
->addskelanim(filename
);
355 sa
->frame
= skel
->numframes
;
356 sa
->range
= animframes
;
358 skel
->numframes
+= animframes
;
360 else if(sscanf(buf
, " frame %d", &tmp
)==1)
362 loopi(animdatalen
) fscanf(f
, "%f", &animdata
[i
]);
363 dualquat
*frame
= &animbones
[tmp
*skel
->numbones
];
366 md5hierarchy
&h
= hierarchy
[i
];
367 md5joint j
= basejoints
[i
];
368 float *jdata
= &animdata
[h
.start
];
371 if(h
.flags
&1) j
.pos
.x
= *jdata
++;
372 if(h
.flags
&2) j
.pos
.y
= -*jdata
++;
373 if(h
.flags
&4) j
.pos
.z
= *jdata
++;
374 if(h
.flags
&8) j
.orient
.x
= -*jdata
++;
375 if(h
.flags
&16) j
.orient
.y
= *jdata
++;
376 if(h
.flags
&32) j
.orient
.z
= -*jdata
++;
378 /*if(memcmp(&j, &basejoints[i], sizeof(j))) usedjoints[i] = 1; */
380 frame
[i
] = dualquat(j
.orient
, j
.pos
);
382 if(h
.parent
<0) frame
[i
] = dualquat(j
.orient
, j
.pos
);
383 else (frame
[i
] = frame
[h
.parent
]).mul(dualquat(j
.orient
, j
.pos
));
393 vector
<dualquat
> invbase
;
396 dualquat
&d
= invbase
.add(basebones
[i
]);
398 d
.translate(vec(translate
).neg());
405 dualquat
*frame
= &animbones
[i
*numbones
];
408 dualquat
&d
= frame
[j
];
413 d
.translate(translate
);
424 bool load(const char *meshfile
)
426 name
= newstring(meshfile
);
428 if(!loadmd5mesh(meshfile
)) return false;
434 void extendbb(int frame
, vec
¢er
, vec
&radius
, modelattach
&a
)
436 vec acenter
, aradius
;
437 a
.m
->boundbox(frame
, acenter
, aradius
);
438 float margin
= 2*max(aradius
.x
, max(aradius
.y
, aradius
.z
));
443 meshgroup
*loadmeshes(char *name
, va_list args
)
445 md5meshgroup
*group
= new md5meshgroup
;
446 group
->shareskeleton(va_arg(args
, char *));
447 if(!group
->load(name
)) { delete group
; return NULL
; }
451 bool loaddefaultparts()
453 skelpart
&mdl
= *new skelpart
;
457 mdl
.pitchscale
= mdl
.pitchoffset
= mdl
.pitchmin
= mdl
.pitchmax
= 0;
458 const char *fname
= loadname
+ strlen(loadname
);
459 do --fname
; while(fname
>= loadname
&& *fname
!='/' && *fname
!='\\');
462 s_sprintfd(meshname
)("packages/plexus/models/%s/%s.md5mesh", loadname
, fname
);
463 if (!fileexists(meshname
, "r")) {
464 s_sprintf(meshname
)("packages/models/%s/%s.md5mesh", loadname
, fname
);
467 s_sprintfd(meshname
)("packages/models/%s/%s.md5mesh", loadname
, fname
);
469 mdl
.meshes
= sharemeshes(path(meshname
));
470 if(!mdl
.meshes
) return false;
474 s_sprintfd(animname
)("packages/plexus/models/%s/%s.md5anim", loadname
, fname
);
475 if (!fileexists(animname
, "r")) {
476 s_sprintf(animname
)("packages/plexus/models/%s/%s.md5anim", loadname
, fname
);
479 s_sprintfd(animname
)("packages/models/%s/%s.md5anim", loadname
, fname
);
481 ((md5meshgroup
*)mdl
.meshes
)->loadmd5anim(path(animname
));
487 if(loaded
) return true;
489 s_sprintf(md5dir
)("packages/plexus/models/%s", loadname
);
490 s_sprintfd(cfgname
)("packages/plexus/models/%s/md5.cfg", loadname
);
491 if (!fileexists(cfgname
, "r")) {
492 s_sprintf(md5dir
)("packages/models/%s", loadname
);
493 s_sprintf(cfgname
)("packages/models/%s/md5.cfg", loadname
);
494 if (!fileexists(cfgname
, "r")) {
499 s_sprintf(md5dir
)("packages/models/%s", loadname
);
500 s_sprintfd(cfgname
)("packages/models/%s/md5.cfg", loadname
);
503 persistidents
= false;
504 if(execfile(cfgname
) && parts
.length()) // configured md5, will call the md5* commands below
506 persistidents
= true;
508 loopv(parts
) if(!parts
[i
]->meshes
) return false;
510 else if(!loaddefaultparts()) // md5 without configuration, try default tris and skin
512 persistidents
= true;
519 skelpart
*p
= (skelpart
*)parts
[i
];
521 p
->meshes
= p
->meshes
->scaleverts(scale
/4.0f
, i
? vec(0, 0, 0) : vec(translate
.x
, translate
.y
, translate
.z
));
523 return loaded
= true;
527 void md5load(char *meshfile
, char *skelname
)
529 if(!loadingmd5
) { conoutf("not loading an md5"); return; }
530 s_sprintfd(filename
)("%s/%s", md5dir
, meshfile
);
531 md5::skelpart
&mdl
= *new md5::skelpart
;
532 loadingmd5
->parts
.add(&mdl
);
533 mdl
.model
= loadingmd5
;
534 mdl
.index
= loadingmd5
->parts
.length()-1;
535 mdl
.pitchscale
= mdl
.pitchoffset
= mdl
.pitchmin
= mdl
.pitchmax
= 0;
536 mdl
.meshes
= loadingmd5
->sharemeshes(path(filename
), skelname
[0] ? skelname
: NULL
);
537 if(!mdl
.meshes
) conoutf("could not load %s", filename
); // ignore failure
545 void md5tag(char *name
, char *tagname
)
547 if(!loadingmd5
|| loadingmd5
->parts
.empty()) { conoutf("not loading an md5"); return; }
548 md5::part
&mdl
= *loadingmd5
->parts
.last();
549 int i
= mdl
.meshes
? ((md5::skelmeshgroup
*)mdl
.meshes
)->skel
->findbone(name
) : -1;
552 ((md5::skelmeshgroup
*)mdl
.meshes
)->skel
->addtag(tagname
, i
);
555 conoutf("could not find bone %s for tag %s", name
, tagname
);
558 void md5pitch(char *name
, float *pitchscale
, float *pitchoffset
, float *pitchmin
, float *pitchmax
)
560 if(!loadingmd5
|| loadingmd5
->parts
.empty()) { conoutf("not loading an md5"); return; }
561 md5::part
&mdl
= *loadingmd5
->parts
.last();
565 int i
= mdl
.meshes
? ((md5::skelmeshgroup
*)mdl
.meshes
)->skel
->findbone(name
) : -1;
568 md5::boneinfo
&b
= ((md5::skelmeshgroup
*)mdl
.meshes
)->skel
->bones
[i
];
569 b
.pitchscale
= *pitchscale
;
570 b
.pitchoffset
= *pitchoffset
;
571 if(*pitchmin
|| *pitchmax
)
573 b
.pitchmin
= *pitchmin
;
574 b
.pitchmax
= *pitchmax
;
578 b
.pitchmin
= -360*b
.pitchscale
;
579 b
.pitchmax
= 360*b
.pitchscale
;
583 conoutf("could not find bone %s to pitch", name
);
587 mdl
.pitchscale
= *pitchscale
;
588 mdl
.pitchoffset
= *pitchoffset
;
589 if(*pitchmin
|| *pitchmax
)
591 mdl
.pitchmin
= *pitchmin
;
592 mdl
.pitchmax
= *pitchmax
;
596 mdl
.pitchmin
= -360*mdl
.pitchscale
;
597 mdl
.pitchmax
= 360*mdl
.pitchscale
;
601 #define loopmd5meshes(meshname, m, body) \
602 if(!loadingmd5 || loadingmd5->parts.empty()) { conoutf("not loading an md5"); return; } \
603 md5::part &mdl = *loadingmd5->parts.last(); \
604 if(!mdl.meshes) return; \
605 loopv(mdl.meshes->meshes) \
607 md5::skelmesh &m = *(md5::skelmesh *)mdl.meshes->meshes[i]; \
608 if(!strcmp(meshname, "*") || (m.name && !strcmp(m.name, meshname))) \
614 #define loopmd5skins(meshname, s, body) loopmd5meshes(meshname, m, { md5::skin &s = mdl.skins[i]; body; })
616 void md5skin(char *meshname
, char *tex
, char *masks
, float *envmapmax
, float *envmapmin
)
618 loopmd5skins(meshname
, s
,
619 s
.tex
= textureload(makerelpath(md5dir
, tex
), 0, true, false);
622 s
.masks
= textureload(makerelpath(md5dir
, masks
, "<ffmask:25>"), 0, true, false);
623 s
.envmapmax
= *envmapmax
;
624 s
.envmapmin
= *envmapmin
;
629 void md5spec(char *meshname
, int *percent
)
632 if(*percent
>0) spec
= *percent
/100.0f
;
633 else if(*percent
<0) spec
= 0.0f
;
634 loopmd5skins(meshname
, s
, s
.spec
= spec
);
637 void md5ambient(char *meshname
, int *percent
)
639 float ambient
= 0.3f
;
640 if(*percent
>0) ambient
= *percent
/100.0f
;
641 else if(*percent
<0) ambient
= 0.0f
;
642 loopmd5skins(meshname
, s
, s
.ambient
= ambient
);
645 void md5glow(char *meshname
, int *percent
)
648 if(*percent
>0) glow
= *percent
/100.0f
;
649 else if(*percent
<0) glow
= 0.0f
;
650 loopmd5skins(meshname
, s
, s
.glow
= glow
);
653 void md5glare(char *meshname
, float *specglare
, float *glowglare
)
655 loopmd5skins(meshname
, s
, { s
.specglare
= *specglare
; s
.glowglare
= *glowglare
; });
658 void md5alphatest(char *meshname
, float *cutoff
)
660 loopmd5skins(meshname
, s
, s
.alphatest
= max(0.0f
, min(1.0f
, *cutoff
)));
663 void md5alphablend(char *meshname
, int *blend
)
665 loopmd5skins(meshname
, s
, s
.alphablend
= *blend
!=0);
668 void md5envmap(char *meshname
, char *envmap
)
670 Texture
*tex
= cubemapload(envmap
);
671 loopmd5skins(meshname
, s
, s
.envmap
= tex
);
674 void md5bumpmap(char *meshname
, char *normalmap
, char *skin
)
676 Texture
*normalmaptex
= NULL
, *skintex
= NULL
;
677 normalmaptex
= textureload(makerelpath(md5dir
, normalmap
, "<noff>"), 0, true, false);
678 if(skin
[0]) skintex
= textureload(makerelpath(md5dir
, skin
, "<noff>"), 0, true, false);
679 loopmd5skins(meshname
, s
, { s
.unlittex
= skintex
; s
.normalmap
= normalmaptex
; m
.calctangents(); });
682 void md5translucent(char *meshname
, float *translucency
)
684 loopmd5skins(meshname
, s
, s
.translucency
= *translucency
);
687 void md5fullbright(char *meshname
, float *fullbright
)
689 loopmd5skins(meshname
, s
, s
.fullbright
= *fullbright
);
692 void md5shader(char *meshname
, char *shader
)
694 loopmd5skins(meshname
, s
, s
.shader
= lookupshaderbyname(shader
));
697 void md5scroll(char *meshname
, float *scrollu
, float *scrollv
)
699 loopmd5skins(meshname
, s
, { s
.scrollu
= *scrollu
; s
.scrollv
= *scrollv
; });
702 void md5anim(char *anim
, char *animfile
, float *speed
, int *priority
)
704 if(!loadingmd5
|| loadingmd5
->parts
.empty()) { conoutf("not loading an md5"); return; }
707 findanims(anim
, anims
);
708 if(anims
.empty()) conoutf("could not find animation %s", anim
);
711 s_sprintfd(filename
)("%s/%s", md5dir
, animfile
);
712 md5::part
*p
= loadingmd5
->parts
.last();
713 md5::skelanimspec
*sa
= ((md5::md5meshgroup
*)p
->meshes
)->loadmd5anim(path(filename
));
714 if(!sa
) conoutf("could not load md5anim file %s", filename
);
717 loadingmd5
->parts
.last()->setanim(p
->numanimparts
-1, anims
[i
], sa
->frame
, sa
->range
, *speed
, *priority
);
722 void md5animpart(char *maskstr
)
724 if(!loadingmd5
|| loadingmd5
->parts
.empty()) { conoutf("not loading an md5"); return; }
726 md5::skelpart
*p
= (md5::skelpart
*)loadingmd5
->parts
.last();
728 vector
<char *> bonestrs
;
729 explodelist(maskstr
, bonestrs
);
730 vector
<ushort
> bonemask
;
733 char *bonestr
= bonestrs
[i
];
734 int bone
= p
->meshes
? ((md5::skelmeshgroup
*)p
->meshes
)->skel
->findbone(bonestr
[0]=='!' ? bonestr
+1 : bonestr
) : -1;
735 if(bone
<0) { conoutf("could not find bone %s for anim part mask [%s]", bonestr
, maskstr
); bonestrs
.deletecontentsa(); return; }
736 bonemask
.add(bone
| (bonestr
[0]=='!' ? BONEMASK_NOT
: 0));
738 bonestrs
.deletecontentsa();
739 bonemask
.sort(bonemaskcmp
);
740 if(bonemask
.length()) bonemask
.add(BONEMASK_END
);
742 if(!p
->addanimpart(bonemask
.getbuf())) conoutf("too many animation parts");
745 void md5link(int *parent
, int *child
, char *tagname
)
747 if(!loadingmd5
) { conoutf("not loading an md5"); return; }
748 if(!loadingmd5
->parts
.inrange(*parent
) || !loadingmd5
->parts
.inrange(*child
)) { conoutf("no models loaded to link"); return; }
749 if(!loadingmd5
->parts
[*parent
]->link(loadingmd5
->parts
[*child
], tagname
)) conoutf("could not link model %s", loadingmd5
->loadname
);
752 void md5noclip(char *meshname
, int *noclip
)
754 loopmd5meshes(meshname
, m
, m
.noclip
= *noclip
!=0);
757 COMMAND(md5load
, "ss");
758 COMMAND(md5tag
, "ss");
759 COMMAND(md5pitch
, "sffff");
760 COMMAND(md5skin
, "sssff");
761 COMMAND(md5spec
, "si");
762 COMMAND(md5ambient
, "si");
763 COMMAND(md5glow
, "si");
764 COMMAND(md5glare
, "sff");
765 COMMAND(md5alphatest
, "sf");
766 COMMAND(md5alphablend
, "si");
767 COMMAND(md5envmap
, "ss");
768 COMMAND(md5bumpmap
, "sss");
769 COMMAND(md5translucent
, "sf");
770 COMMAND(md5fullbright
, "sf");
771 COMMAND(md5shader
, "ss");
772 COMMAND(md5scroll
, "sff");
773 COMMAND(md5animpart
, "s");
774 COMMAND(md5anim
, "ssfi");
775 COMMAND(md5link
, "iis");
776 COMMAND(md5noclip
, "si");