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