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