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