Line data Source code
1 :
2 : /* md5.cpp: md5 (id tech 4) model support
3 : *
4 : * Libprimis supports the md5 (id Tech 4) skeletal model format for animated
5 : * models. Ragdolls and animated models (such as players) use this model format.
6 : * The implmentation for the md5 class is in this file, while the object is defined
7 : * inside md5.h.
8 : */
9 :
10 : //includes copied from obj.cpp
11 : #include "../libprimis-headers/cube.h"
12 : #include "../../shared/geomexts.h"
13 : #include "../../shared/glemu.h"
14 : #include "../../shared/glexts.h"
15 : #include "../../shared/stream.h"
16 :
17 : #include <optional>
18 : #include <memory>
19 :
20 : #include "render/rendergl.h"
21 : #include "render/rendermodel.h"
22 : #include "render/renderwindow.h"
23 : #include "render/shader.h"
24 : #include "render/shaderparam.h"
25 : #include "render/texture.h"
26 :
27 : #include "interface/console.h"
28 : #include "interface/control.h"
29 : #include "interface/cs.h"
30 :
31 : #include "world/entities.h"
32 : #include "world/octaworld.h"
33 : #include "world/bih.h"
34 :
35 : #include "model.h"
36 : #include "ragdoll.h"
37 : #include "animmodel.h"
38 : #include "skelmodel.h"
39 :
40 : #include "md5.h"
41 :
42 : static constexpr int md5version = 10;
43 :
44 : skelcommands<md5> md5::md5commands;
45 :
46 19 : md5::md5(std::string name) : skelloader(name) {}
47 :
48 86 : const char *md5::formatname()
49 : {
50 86 : return "md5";
51 : }
52 :
53 7 : bool md5::flipy() const
54 : {
55 7 : return false;
56 : }
57 :
58 1 : int md5::type() const
59 : {
60 1 : return MDL_MD5;
61 : }
62 :
63 4 : md5::skelmeshgroup *md5::newmeshes()
64 : {
65 4 : return new md5meshgroup;
66 : }
67 :
68 : /**
69 : * @brief Attempts to load an md5mesh with default parameters.
70 : *
71 : * - adds a skelpart to the current model
72 : * - attempts to add the md5mesh file at the path indicated by the model's name
73 : * - the path and md5mesh file name should be the same (e.g. foo/foo.md5mesh)
74 : *
75 : * @return true if the mesh was successfully added
76 : * @return false if no mesh was added (such as if no file found)
77 : */
78 8 : bool md5::loaddefaultparts()
79 : {
80 8 : skelpart &mdl = addpart();
81 8 : const char *fname = modelname().c_str() + std::strlen(modelname().c_str());
82 : do
83 : {
84 90 : --fname;
85 90 : } while(fname >= modelname().c_str() && *fname!='/' && *fname!='\\');
86 8 : fname++;
87 8 : std::string meshname = modelpath;
88 8 : meshname.append(modelname()).append("/").append(fname).append(".md5mesh");
89 8 : mdl.meshes = sharemeshes(path(meshname).c_str());
90 8 : if(!mdl.meshes)
91 : {
92 2 : return false;
93 : }
94 6 : mdl.initanimparts();
95 6 : mdl.initskins();
96 6 : std::string animname = modelpath;
97 6 : animname.append(modelname()).append("/").append(fname).append(".md5anim");
98 6 : static_cast<md5meshgroup *>(mdl.meshes)->loadanim(path(animname));
99 6 : return true;
100 8 : }
101 :
102 :
103 4 : md5::md5meshgroup::md5meshgroup()
104 : {
105 4 : }
106 :
107 : //main anim loading functionality
108 9 : const md5::skelanimspec *md5::md5meshgroup::loadanim(const std::string &filename)
109 : {
110 : {
111 9 : const skelanimspec *sa = skel->findskelanim(filename);
112 9 : if(sa)
113 : {
114 8 : return sa;
115 : }
116 : }
117 1 : stream *f = openfile(filename.c_str(), "r");
118 1 : if(!f)
119 : {
120 0 : return nullptr;
121 : }
122 : //hierarchy, basejoints vectors are to have correlating indices with
123 : // skel->bones, this->adjustments, this->frame, skel->framebones
124 : struct md5hierarchy
125 : {
126 : string name;
127 : int parent, flags, start;
128 : };
129 1 : std::vector<md5hierarchy> hierarchy; //metadata used only within this function
130 1 : std::vector<md5joint> basejoints;
131 1 : int animdatalen = 0,
132 1 : animframes = 0;
133 1 : float *animdata = nullptr;
134 1 : dualquat *animbones = nullptr;
135 : char buf[512]; //presumably lines over 512 char long will break this loader
136 : //for each line in the opened file
137 1 : skelanimspec *sas = nullptr;
138 17 : while(f->getline(buf, sizeof(buf)))
139 : {
140 : int tmp;
141 16 : if(std::sscanf(buf, " MD5Version %d", &tmp) == 1)
142 : {
143 1 : if(tmp != md5version)
144 : {
145 0 : delete f; //bail out if md5version is not what we want
146 0 : return nullptr;
147 : }
148 : }
149 15 : else if(std::sscanf(buf, " numJoints %d", &tmp) == 1)
150 : {
151 1 : if(tmp != static_cast<int>(skel->numbones))
152 : {
153 0 : delete f; //bail out if numbones is not consistent
154 0 : return nullptr;
155 : }
156 : }
157 14 : else if(std::sscanf(buf, " numFrames %d", &animframes) == 1)
158 : {
159 1 : if(animframes < 1) //if there are no animated frames, don't do animated frame stuff
160 : {
161 0 : delete f;
162 0 : return nullptr;
163 : }
164 : }
165 : //apparently, do nothing with respect to framerate
166 13 : else if(std::sscanf(buf, " frameRate %d", &tmp) == 1)
167 : {
168 : //(empty body)
169 : }
170 : //create animdata if there is some relevant info in file
171 12 : else if(std::sscanf(buf, " numAnimatedComponents %d", &animdatalen)==1)
172 : {
173 1 : if(animdatalen > 0)
174 : {
175 1 : animdata = new float[animdatalen];
176 : }
177 : }
178 11 : else if(std::strstr(buf, "bounds {"))
179 : {
180 2 : while(f->getline(buf, sizeof(buf)) && buf[0]!='}') //loop until end of {} block
181 : {
182 : //(empty body)
183 : }
184 : }
185 10 : else if(std::strstr(buf, "hierarchy {"))
186 : {
187 5 : while(f->getline(buf, sizeof(buf)) && buf[0]!='}') //loop until end of {} block
188 : {
189 : md5hierarchy h;
190 4 : if(std::sscanf(buf, " %100s %d %d %d", h.name, &h.parent, &h.flags, &h.start)==4)
191 : {
192 4 : hierarchy.push_back(h);
193 : }
194 : }
195 : }
196 9 : else if(std::strstr(buf, "baseframe {"))
197 : {
198 5 : while(f->getline(buf, sizeof(buf)) && buf[0]!='}') //loop until end of {} block
199 : {
200 4 : md5joint j;
201 : //pick up pos/orient 3-vectors within
202 4 : if(std::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)
203 : {
204 4 : j.pos.y = -j.pos.y;
205 4 : j.orient.x = -j.orient.x;
206 4 : j.orient.z = -j.orient.z;
207 4 : j.orient.restorew();
208 4 : basejoints.push_back(j);
209 : }
210 : }
211 1 : if(basejoints.size() != skel->numbones)
212 : {
213 0 : delete f;
214 0 : if(animdata)
215 : {
216 0 : delete[] animdata;
217 : }
218 0 : return nullptr;
219 : }
220 5 : animbones = new dualquat[(skel->numframes+animframes)*skel->numbones];
221 1 : if(skel->framebones)
222 : {
223 0 : std::memcpy(animbones, skel->framebones, skel->numframes*skel->numbones*sizeof(dualquat));
224 0 : delete[] skel->framebones;
225 : }
226 1 : skel->framebones = animbones;
227 1 : animbones += skel->numframes*skel->numbones;
228 :
229 1 : sas = &skel->addskelanim(filename, skel->numframes, animframes);
230 :
231 1 : skel->numframes += animframes;
232 : }
233 8 : else if(std::sscanf(buf, " frame %d", &tmp)==1)
234 : {
235 5 : for(int numdata = 0; f->getline(buf, sizeof(buf)) && buf[0]!='}';)
236 : {
237 28 : for(char *src = buf, *next = src; numdata < animdatalen; numdata++, src = next)
238 : {
239 27 : animdata[numdata] = std::strtod(src, &next);
240 27 : if(next <= src)
241 : {
242 3 : break;
243 : }
244 : }
245 : }
246 1 : dualquat *frame = &animbones[tmp*skel->numbones];
247 1 : if(basejoints.size() != hierarchy.size())
248 : {
249 0 : conoutf("Invalid model data: hierarchy (%lu) and baseframe (%lu) size mismatch", hierarchy.size(), basejoints.size());
250 0 : return nullptr;
251 : }
252 5 : for(uint i = 0; i < basejoints.size(); i++)
253 : {
254 4 : const md5hierarchy &h = hierarchy[i];
255 4 : md5joint j = basejoints[i]; //intentionally getting by value to modify temp copy
256 4 : if(h.start < animdatalen && h.flags)
257 : {
258 4 : const float *jdata = &animdata[h.start];
259 : //bitwise AND against bits 0...5
260 4 : if(h.flags & 1)
261 : {
262 4 : j.pos.x = *jdata++;
263 : }
264 4 : if(h.flags & 2)
265 : {
266 4 : j.pos.y = -*jdata++;
267 : }
268 4 : if(h.flags & 4)
269 : {
270 4 : j.pos.z = *jdata++;
271 : }
272 4 : if(h.flags & 8)
273 : {
274 4 : j.orient.x = -*jdata++;
275 : }
276 4 : if(h.flags & 16)
277 : {
278 4 : j.orient.y = *jdata++;
279 : }
280 4 : if(h.flags & 32)
281 : {
282 4 : j.orient.z = -*jdata++;
283 : }
284 4 : j.orient.restorew();
285 : }
286 4 : dualquat dq(j.orient, j.pos);
287 4 : if(adjustments.size() > i)
288 : {
289 0 : adjustments[i].adjust(dq);
290 : }
291 : //assume nullopt cannot happen
292 4 : dq.mul(skel->getbonebase(i).value().invert());
293 4 : dualquat &dst = frame[i];
294 4 : if(h.parent < 0)
295 : {
296 1 : dst = dq;
297 : }
298 : else
299 : {
300 3 : dst.mul(skel->getbonebase(h.parent).value(), dq);
301 : }
302 4 : dst.fixantipodal(skel->framebones[i]);
303 : }
304 : }
305 : }
306 :
307 1 : if(animdata)
308 : {
309 1 : delete[] animdata;
310 : }
311 1 : delete f;
312 :
313 1 : return sas;
314 1 : }
315 :
316 3 : bool md5::md5meshgroup::loadmesh(std::string_view filename, float smooth, part &p)
317 : {
318 3 : stream *f = openfile(filename.data(), "r");
319 3 : if(!f) //immediately bail if no file present
320 : {
321 2 : return false;
322 : }
323 : char buf[512]; //presumably this will fail with lines over 512 char long
324 1 : std::vector<md5joint> basejoints;
325 10 : while(f->getline(buf, sizeof(buf)))
326 : {
327 : int tmp;
328 9 : if(std::sscanf(buf, " MD5Version %d", &tmp)==1)
329 : {
330 1 : if(tmp!=10)
331 : {
332 0 : delete f; //bail out if md5version is not what we want
333 0 : return false;
334 : }
335 : }
336 8 : else if(std::sscanf(buf, " numJoints %d", &tmp)==1)
337 : {
338 1 : if(tmp<1)
339 : {
340 0 : delete f; //bail out if no joints found
341 0 : return false;
342 : }
343 1 : if(skel->numbones>0) //if we have bones, keep going
344 : {
345 0 : continue;
346 : }
347 1 : skel->createbones(tmp);
348 : }
349 7 : else if(std::sscanf(buf, " numMeshes %d", &tmp)==1)
350 : {
351 1 : if(tmp<1)
352 : {
353 0 : delete f; //if there's no meshes, nothing to be done
354 0 : return false;
355 : }
356 : }
357 6 : else if(std::strstr(buf, "joints {"))
358 : {
359 : string name;
360 : int parent;
361 1 : md5joint j;
362 5 : while(f->getline(buf, sizeof(buf)) && buf[0]!='}')
363 : {
364 4 : const char *curbuf = buf;
365 4 : char *curname = name;
366 4 : bool allowspace = false;
367 8 : while(*curbuf && std::isspace(*curbuf))
368 : {
369 4 : curbuf++;
370 : }
371 4 : if(*curbuf == '"')
372 : {
373 4 : curbuf++;
374 4 : allowspace = true;
375 : }
376 51 : while(*curbuf && curname < &name[sizeof(name)-1])
377 : {
378 51 : char c = *curbuf++;
379 51 : if(c == '"')
380 : {
381 4 : break;
382 : }
383 47 : if(std::isspace(c) && !allowspace)
384 : {
385 0 : break;
386 : }
387 47 : *curname++ = c;
388 : }
389 4 : *curname = '\0';
390 : //pickup parent, pos/orient 3-vectors
391 4 : if(std::sscanf(curbuf, " %d ( %f %f %f ) ( %f %f %f )",
392 : &parent, &j.pos.x, &j.pos.y, &j.pos.z,
393 4 : &j.orient.x, &j.orient.y, &j.orient.z)==7)
394 : {
395 4 : j.pos.y = -j.pos.y;
396 4 : j.orient.x = -j.orient.x;
397 4 : j.orient.z = -j.orient.z;
398 4 : if(basejoints.size() < skel->numbones)
399 : {
400 4 : skel->setbonename(basejoints.size(), name);
401 4 : skel->setboneparent(basejoints.size(), parent);
402 : }
403 4 : j.orient.restorew();
404 4 : basejoints.push_back(j);
405 : }
406 : }
407 1 : if(basejoints.size() != skel->numbones)
408 : {
409 0 : delete f;
410 0 : return false;
411 : }
412 : }
413 : //load up meshes
414 5 : else if(std::strstr(buf, "mesh {"))
415 : {
416 1 : md5mesh *m = new md5mesh("", this); //we will set its name later
417 1 : meshes.push_back(m);
418 :
419 1 : std::string modeldir(filename);
420 1 : modeldir.resize(modeldir.rfind("/")); //truncate to file's directory
421 1 : m->load(f, buf, sizeof(buf), p, modeldir);
422 1 : if(!m->tricount() || !m->vertcount()) //if no content in the mesh
423 : {
424 0 : conoutf("empty mesh in %s", filename.data());
425 0 : auto itr = std::find(meshes.begin(), meshes.end(), m);
426 0 : if(itr != meshes.end())
427 : {
428 0 : meshes.erase(itr);
429 : }
430 0 : delete m;
431 : }
432 1 : }
433 : }
434 :
435 1 : skel->linkchildren();
436 : //assign basejoints accumulated above
437 : {
438 1 : std::vector<dualquat> bases;
439 5 : for(const md5joint &m : basejoints)
440 : {
441 4 : bases.emplace_back(m.orient, m.pos);
442 : }
443 1 : skel->setbonebases(bases);
444 1 : }
445 2 : for(uint i = 0; i < meshes.size(); i++)
446 : {
447 1 : md5mesh &m = *static_cast<md5mesh *>(meshes[i]);
448 1 : m.buildverts(basejoints);
449 1 : if(smooth <= 1)
450 : {
451 0 : m.smoothnorms(smooth);
452 : }
453 : else
454 : {
455 1 : m.buildnorms();
456 : }
457 1 : m.calctangents();
458 1 : m.cleanup();
459 : }
460 :
461 1 : sortblendcombos();
462 :
463 1 : delete f;
464 1 : return true;
465 1 : }
466 :
467 3 : bool md5::md5meshgroup::load(std::string_view meshfile, float smooth, part &p)
468 : {
469 3 : name = meshfile;
470 :
471 3 : if(!loadmesh(meshfile, smooth, p))
472 : {
473 2 : return false;
474 : }
475 1 : return true;
476 : }
477 :
478 1 : md5::md5mesh::md5mesh(std::string_view name, meshgroup *m) :
479 : skelmesh(name, nullptr, 0, nullptr, 0, m),
480 1 : weightinfo(nullptr),
481 1 : numweights(0),
482 1 : vertinfo(nullptr)
483 : {
484 1 : }
485 :
486 0 : md5::md5mesh::~md5mesh()
487 : {
488 0 : cleanup();
489 0 : }
490 :
491 1 : void md5::md5mesh::cleanup()
492 : {
493 1 : delete[] weightinfo;
494 1 : delete[] vertinfo;
495 1 : vertinfo = nullptr;
496 1 : weightinfo = nullptr;
497 1 : }
498 :
499 1 : void md5::md5mesh::buildverts(const std::vector<md5joint> &joints)
500 : {
501 439 : for(int i = 0; i < numverts; ++i)
502 : {
503 438 : md5vert &v = vertinfo[i];
504 438 : vec pos(0, 0, 0);
505 935 : for(uint k = 0; k < v.count; ++k)
506 : {
507 497 : const md5weight &w = weightinfo[v.start+k];
508 497 : const md5joint &j = joints[w.joint];
509 497 : vec wpos = j.orient.rotate(w.pos);
510 497 : wpos.add(j.pos);
511 497 : wpos.mul(w.bias);
512 497 : pos.add(wpos);
513 : }
514 438 : vert &vv = verts[i];
515 438 : vv.pos = pos;
516 438 : vv.tc = v.tc;
517 :
518 438 : blendcombo c;
519 438 : int sorted = 0;
520 935 : for(uint j = 0; j < v.count; ++j)
521 : {
522 497 : const md5weight &w = weightinfo[v.start+j];
523 497 : sorted = c.addweight(sorted, w.bias, w.joint);
524 : }
525 438 : c.finalize(sorted);
526 438 : vv.blend = addblendcombo(c);
527 : }
528 1 : }
529 :
530 : //md5 model loader
531 1 : void md5::md5mesh::load(stream *f, char *buf, size_t bufsize, part &p, const std::string &modeldir)
532 : {
533 1 : md5weight w;
534 1 : md5vert v;
535 : tri t;
536 : int index;
537 :
538 1405 : while(f->getline(buf, bufsize) && buf[0]!='}')
539 : {
540 1404 : if(std::strstr(buf, "// meshes:"))
541 : {
542 0 : char *start = std::strchr(buf, ':')+1;
543 0 : if(*start==' ')
544 : {
545 0 : start++;
546 : }
547 0 : char *end = start + std::strlen(start)-1;
548 0 : while(end >= start && std::isspace(*end))
549 : {
550 0 : end--;
551 : }
552 0 : name = std::string(start, end+1-start);
553 : }
554 1404 : else if(std::strstr(buf, "shader"))
555 : {
556 1 : char *start = std::strchr(buf, '"'),
557 1 : *end = start ? std::strchr(start+1, '"') : nullptr;
558 1 : if(start && end)
559 : {
560 1 : char *texname = newstring(start+1, end-(start+1));
561 1 : p.initskins(notexture, notexture, group->meshes.size());
562 1 : skin &s = p.skins.back();
563 1 : s.tex = textureload(makerelpath(modeldir.c_str(), texname), 0, true, false);
564 1 : delete[] texname;
565 : }
566 : }
567 : //create the vert arrays
568 1403 : else if(std::sscanf(buf, " numverts %d", &numverts)==1)
569 : {
570 1 : numverts = std::max(numverts, 0);
571 1 : if(numverts)
572 : {
573 439 : vertinfo = new md5vert[numverts];
574 439 : verts = new vert[numverts];
575 : }
576 : }
577 : //create tri array
578 1402 : else if(std::sscanf(buf, " numtris %d", &numtris)==1)
579 : {
580 1 : numtris = std::max(numtris, 0);
581 1 : if(numtris)
582 : {
583 1 : tris = new tri[numtris];
584 : }
585 : }
586 : //create md5weight array
587 1401 : else if(std::sscanf(buf, " numweights %d", &numweights)==1)
588 : {
589 1 : numweights = std::max(numweights, 0);
590 1 : if(numweights)
591 : {
592 498 : weightinfo = new md5weight[numweights];
593 : }
594 : }
595 : //assign md5verts to vertinfo array
596 1400 : else if(std::sscanf(buf, " vert %d ( %f %f ) %u %u", &index, &v.tc.x, &v.tc.y, &v.start, &v.count)==5)
597 : {
598 438 : if(index>=0 && index<numverts)
599 : {
600 438 : vertinfo[index] = v;
601 : }
602 : }
603 : // assign tris to tri array
604 962 : else if(std::sscanf(buf, " tri %d %u %u %u", &index, &t.vert[0], &t.vert[1], &t.vert[2])==4)
605 : {
606 462 : if(index>=0 && index<numtris)
607 : {
608 462 : tris[index] = t;
609 : }
610 : }
611 : //assign md5weights to weights array
612 500 : else if(std::sscanf(buf, " weight %d %d %f ( %f %f %f ) ", &index, &w.joint, &w.bias, &w.pos.x, &w.pos.y, &w.pos.z)==6)
613 : {
614 497 : w.pos.y = -w.pos.y;
615 497 : if(index>=0 && index<numweights)
616 : {
617 497 : weightinfo[index] = w;
618 : }
619 : }
620 : }
621 1 : }
|