Line data Source code
1 : /* rendermodel.cpp: world static and dynamic models
2 : *
3 : * Libprimis can handle static ("mapmodel") type models which are placed in levels
4 : * as well as dynamic, animated models such as players or other actors. For animated
5 : * models, the md5 model format is supported; simpler static models can use the
6 : * common Wavefront (obj) model format.
7 : *
8 : */
9 : #include "../libprimis-headers/cube.h"
10 : #include "../../shared/geomexts.h"
11 : #include "../../shared/glemu.h"
12 : #include "../../shared/glexts.h"
13 : #include "../../shared/stream.h"
14 :
15 : #include <optional>
16 : #include <memory>
17 : #include <format>
18 :
19 : #include "aa.h"
20 : #include "csm.h"
21 : #include "radiancehints.h"
22 : #include "rendergl.h"
23 : #include "renderlights.h"
24 : #include "rendermodel.h"
25 : #include "renderva.h"
26 : #include "renderwindow.h"
27 : #include "shader.h"
28 : #include "shaderparam.h"
29 : #include "texture.h"
30 :
31 : #include "interface/console.h"
32 : #include "interface/control.h"
33 : #include "interface/cs.h"
34 :
35 : #include "world/entities.h"
36 : #include "world/octaedit.h"
37 : #include "world/octaworld.h"
38 : #include "world/bih.h"
39 : #include "world/world.h"
40 :
41 : VAR(oqdynent, 0, 1, 1); //occlusion query dynamic ents
42 :
43 : std::vector<std::string> animnames; //set by game at runtime
44 :
45 : //need the above vars inited before these headers will load properly
46 :
47 : #include "model/model.h"
48 : #include "model/ragdoll.h"
49 : #include "model/animmodel.h"
50 : #include "model/vertmodel.h"
51 : #include "model/skelmodel.h"
52 :
53 0 : model *loadmapmodel(int n)
54 : {
55 0 : if(static_cast<int>(mapmodel::mapmodels.size()) > n)
56 : {
57 0 : model *m = mapmodel::mapmodels[n].m;
58 0 : return m ? m : loadmodel("", n);
59 : }
60 0 : return nullptr;
61 : }
62 :
63 : //need the above macros & fxns inited before these headers will load properly
64 : #include "model/md5.h"
65 : #include "model/obj.h"
66 : #include "model/gltf.h"
67 :
68 : // mapmodels
69 :
70 : namespace mapmodel
71 : {
72 : std::vector<mapmodelinfo> mapmodels;
73 : static const std::string mmprefix = "mapmodel/";
74 : static const size_t mmprefixlen = mmprefix.size();
75 :
76 1 : void open(const char *name)
77 : {
78 1 : mapmodelinfo mmi;
79 1 : if(name[0])
80 : {
81 0 : mmi.name = std::string().append(mmprefix).append(name);
82 : }
83 : else
84 : {
85 1 : mmi.name.clear();
86 : }
87 1 : mmi.m = mmi.collide = nullptr;
88 1 : mapmodels.push_back(mmi);
89 1 : }
90 :
91 1 : void reset(const int *n)
92 : {
93 1 : if(!(identflags&Idf_Overridden) && !allowediting)
94 : {
95 1 : return;
96 : }
97 0 : mapmodels.resize(std::clamp(*n, 0, static_cast<int>(mapmodels.size())));
98 : }
99 :
100 0 : const char *name(int i)
101 : {
102 0 : return (static_cast<int>(mapmodels.size()) > i) ? mapmodels[i].name.c_str() : nullptr;
103 : }
104 :
105 1 : void namecmd(const int *index, const int *prefix)
106 : {
107 1 : if(static_cast<int>(mapmodels.size()) > *index)
108 : {
109 0 : result(mapmodels[*index].name.empty() ? mapmodels[*index].name.c_str() + (*prefix ? 0 : mmprefixlen) : "");
110 : }
111 1 : }
112 :
113 1 : void loaded(const int *index)
114 : {
115 1 : intret(static_cast<int>(mapmodels.size()) > *index && mapmodels[*index].m ? 1 : 0);
116 1 : }
117 :
118 1 : void num()
119 : {
120 1 : intret(mapmodels.size());
121 1 : }
122 : }
123 :
124 0 : static bool modeloccluded(const vec ¢er, float radius)
125 : {
126 0 : ivec bbmin(vec(center).sub(radius)),
127 0 : bbmax(vec(center).add(radius+1));
128 0 : return rootworld.bboccluded(bbmin, bbmax);
129 : }
130 :
131 : //ratio between model size and distance at which to cull: at 200, model must be 200 times smaller than distance to model
132 : VAR(maxmodelradiusdistance, 10, 200, 1000);
133 :
134 : /**
135 : * @brief Returns whether the model should be culled.
136 : *
137 : * Attempts to cull by distance from camera1, then by view frustum from `vfc` view,
138 : * then by occlusion query.
139 : *
140 : * If no reason can be found to occlude the model, returns 0. Otherwise, returns
141 : * the Model enum flag for the cull reason.
142 : */
143 0 : static int cullmodel(const vec ¢er, float radius, int flags, const dynent *d = nullptr)
144 : {
145 0 : if(flags&Model_CullDist && (center.dist(camera1->o) / radius) > maxmodelradiusdistance)
146 : {
147 0 : return Model_CullDist;
148 : }
149 0 : if(flags&Model_CullVFC && view.isfoggedsphere(radius, center))
150 : {
151 0 : return Model_CullVFC;
152 : }
153 0 : if(flags&Model_CullOccluded && modeloccluded(center, radius))
154 : {
155 0 : return Model_CullOccluded;
156 : }
157 0 : else if(flags&Model_CullQuery && d->query && d->query->owner==d && occlusionengine.checkquery(d->query))
158 : {
159 0 : return Model_CullQuery;
160 : }
161 0 : return 0;
162 : }
163 :
164 0 : static int shadowmaskmodel(const vec ¢er, float radius)
165 : {
166 0 : switch(shadowmapping)
167 : {
168 0 : case ShadowMap_Reflect:
169 0 : return calcspherersmsplits(center, radius);
170 0 : case ShadowMap_CubeMap:
171 : {
172 0 : vec scenter = vec(center).sub(shadoworigin);
173 0 : float sradius = radius + shadowradius;
174 0 : if(scenter.squaredlen() >= sradius*sradius)
175 : {
176 0 : return 0;
177 : }
178 0 : return calcspheresidemask(scenter, radius, shadowbias);
179 : }
180 0 : case ShadowMap_Cascade:
181 : {
182 0 : return csm.calcspheresplits(center, radius);
183 : }
184 0 : case ShadowMap_Spot:
185 : {
186 0 : vec scenter = vec(center).sub(shadoworigin);
187 0 : float sradius = radius + shadowradius;
188 0 : return scenter.squaredlen() < sradius*sradius && sphereinsidespot(shadowdir, shadowspot, scenter, radius) ? 1 : 0;
189 : }
190 : }
191 0 : return 0;
192 : }
193 :
194 : namespace batching
195 : {
196 : struct modelbatch
197 : {
198 : const model *m;
199 : int flags, batched;
200 : };
201 :
202 : struct batchedmodel
203 : {
204 : //orient = yaw, pitch, roll
205 : vec pos, orient, center;
206 : float radius, sizescale;
207 : vec4<float> colorscale;
208 : int anim, basetime, basetime2, flags, attached;
209 : union
210 : {
211 : int visible;
212 : int culled;
213 : };
214 : dynent *d;
215 : int next;
216 :
217 : void renderbatchedmodel(const model *m) const;
218 : //sets bbmin and bbmax to the min/max of itself and the batchedmodel's bb
219 : void applybb(vec &bbmin, vec &bbmax) const;
220 : bool shadowmask(bool dynshadow);
221 :
222 : int rendertransparentmodel(const modelbatch &b, bool &rendered);
223 : };
224 :
225 : static std::vector<batchedmodel> batchedmodels;
226 : static std::vector<modelbatch> batches;
227 : static std::vector<modelattach> modelattached;
228 :
229 0 : void resetmodelbatches()
230 : {
231 0 : batchedmodels.clear();
232 0 : batches.clear();
233 0 : modelattached.clear();
234 0 : }
235 :
236 : /**
237 : * @brief Adds or modifies a batchedmodel with the specified model
238 : *
239 : * @param m the model to potentially assign to the batched model
240 : * @param bm the batched model to modify
241 : * @param idx the index value to set, if model `m` is already the model assigned to `bm`
242 : */
243 0 : static void addbatchedmodel(model *m, batchedmodel &bm, int idx)
244 : {
245 0 : modelbatch *b = nullptr;
246 0 : if(batches.size() > static_cast<uint>(m->batch))
247 : {
248 0 : b = &batches[m->batch];
249 0 : if(b->m == m && (b->flags & Model_Mapmodel) == (bm.flags & Model_Mapmodel))
250 : {
251 0 : goto foundbatch; //skip setup
252 : }
253 : }
254 0 : m->batch = batches.size();
255 0 : batches.emplace_back();
256 0 : b = &batches.back();
257 0 : b->m = m;
258 0 : b->flags = 0;
259 0 : b->batched = -1;
260 :
261 0 : foundbatch:
262 0 : b->flags |= bm.flags;
263 0 : bm.next = b->batched;
264 0 : b->batched = idx;
265 0 : }
266 :
267 0 : void batchedmodel::renderbatchedmodel(const model *m) const
268 : {
269 0 : modelattach *a = nullptr;
270 0 : if(attached>=0)
271 : {
272 0 : a = &modelattached[attached];
273 : }
274 0 : int tempanim = anim;
275 0 : if(shadowmapping > ShadowMap_Reflect)
276 : {
277 0 : tempanim |= Anim_NoSkin;
278 : }
279 : else
280 : {
281 0 : if(flags&Model_FullBright)
282 : {
283 0 : tempanim |= Anim_FullBright;
284 : }
285 : }
286 :
287 0 : m->render(tempanim, basetime, basetime2, pos, orient.x, orient.y, orient.z, d, a, sizescale, colorscale);
288 0 : }
289 :
290 0 : bool batchedmodel::shadowmask(bool dynshadow)
291 : {
292 0 : if(flags&(Model_Mapmodel | Model_NoShadow)) //mapmodels are not dynamic models by definition
293 : {
294 0 : return false;
295 : }
296 0 : visible = dynshadow && (colorscale.a() >= 1 || flags&(Model_OnlyShadow | Model_ForceShadow)) ? shadowmaskmodel(center, radius) : 0;
297 0 : return true;
298 : }
299 :
300 0 : void shadowmaskbatchedmodels(bool dynshadow)
301 : {
302 0 : for(batchedmodel &b : batchedmodels)
303 : {
304 0 : if(!b.shadowmask(dynshadow))
305 : {
306 0 : break;
307 : }
308 : }
309 0 : }
310 :
311 0 : int batcheddynamicmodels()
312 : {
313 0 : int visible = 0;
314 0 : for(const batchedmodel &b : batchedmodels)
315 : {
316 0 : if(b.flags&Model_Mapmodel) //mapmodels are not dynamic models by definition
317 : {
318 0 : break;
319 : }
320 0 : visible |= b.visible;
321 : }
322 0 : for(const modelbatch &b : batches)
323 : {
324 0 : if(!(b.flags&Model_Mapmodel) || !b.m->animated())
325 : {
326 0 : continue;
327 : }
328 0 : for(int j = b.batched; j >= 0;)
329 : {
330 0 : const batchedmodel &bm = batchedmodels[j];
331 0 : j = bm.next;
332 0 : visible |= bm.visible;
333 : }
334 : }
335 0 : return visible;
336 : }
337 :
338 0 : void batchedmodel::applybb(vec &bbmin, vec &bbmax) const
339 : {
340 0 : bbmin.min(vec(center).sub(radius));
341 0 : bbmax.max(vec(center).add(radius));
342 0 : }
343 :
344 0 : int batcheddynamicmodelbounds(int mask, vec &bbmin, vec &bbmax)
345 : {
346 0 : int vis = 0;
347 0 : for(const batchedmodel &b : batchedmodels)
348 : {
349 0 : if(b.flags&Model_Mapmodel) //mapmodels are not dynamic models by definition
350 : {
351 0 : break;
352 : }
353 0 : if(b.visible&mask)
354 : {
355 0 : b.applybb(bbmin, bbmax);
356 0 : ++vis;
357 : }
358 : }
359 0 : for(const modelbatch &b : batches)
360 : {
361 0 : if(!(b.flags&Model_Mapmodel) || !b.m->animated())
362 : {
363 0 : continue;
364 : }
365 0 : for(int j = b.batched; j >= 0;)
366 : {
367 0 : const batchedmodel &bm = batchedmodels[j];
368 0 : j = bm.next;
369 0 : if(bm.visible&mask)
370 : {
371 0 : bm.applybb(bbmin, bbmax);
372 0 : ++vis;
373 : }
374 : }
375 : }
376 0 : return vis;
377 : }
378 :
379 0 : void rendershadowmodelbatches(bool dynmodel)
380 : {
381 0 : for(const modelbatch &b : batches)
382 : {
383 0 : if(!b.m->shadow || (!dynmodel && (!(b.flags&Model_Mapmodel) || b.m->animated())))
384 : {
385 0 : continue;
386 : }
387 0 : bool rendered = false;
388 0 : for(int j = b.batched; j >= 0;)
389 : {
390 0 : const batchedmodel &bm = batchedmodels[j];
391 0 : j = bm.next;
392 0 : if(!(bm.visible&(1<<shadowside)))
393 : {
394 0 : continue;
395 : }
396 0 : if(!rendered)
397 : {
398 0 : b.m->startrender();
399 0 : rendered = true;
400 : }
401 0 : bm.renderbatchedmodel(b.m);
402 : }
403 0 : if(rendered)
404 : {
405 0 : b.m->endrender();
406 : }
407 : }
408 0 : }
409 :
410 0 : void rendermapmodelbatches()
411 : {
412 0 : aamask::enable();
413 0 : for(const modelbatch &b : batches)
414 : {
415 0 : if(!(b.flags&Model_Mapmodel))
416 : {
417 0 : continue;
418 : }
419 0 : b.m->startrender();
420 0 : aamask::set(b.m->animated());
421 0 : for(int j = b.batched; j >= 0;)
422 : {
423 0 : const batchedmodel &bm = batchedmodels[j];
424 0 : bm.renderbatchedmodel(b.m);
425 0 : j = bm.next;
426 : }
427 0 : b.m->endrender();
428 : }
429 0 : aamask::disable();
430 0 : }
431 :
432 0 : int batchedmodel::rendertransparentmodel(const modelbatch &b, bool &rendered)
433 : {
434 0 : int j = next;
435 0 : culled = cullmodel(center, radius, flags, d);
436 0 : if(culled || !(colorscale.a() < 1 || flags&Model_ForceTransparent) || flags&Model_OnlyShadow)
437 : {
438 0 : return j;
439 : }
440 0 : if(!rendered)
441 : {
442 0 : b.m->startrender();
443 0 : rendered = true;
444 0 : aamask::set(true);
445 : }
446 0 : if(flags&Model_CullQuery)
447 : {
448 0 : d->query = occlusionengine.newquery(d);
449 0 : if(d->query)
450 : {
451 0 : d->query->startquery();
452 0 : renderbatchedmodel(b.m);
453 0 : occlusionengine.endquery();
454 0 : return j;
455 : }
456 : }
457 0 : renderbatchedmodel(b.m);
458 0 : return j;
459 : }
460 :
461 0 : void rendertransparentmodelbatches(int stencil)
462 : {
463 0 : aamask::enable(stencil);
464 0 : for(modelbatch &b : batches)
465 : {
466 0 : if(b.flags&Model_Mapmodel)
467 : {
468 0 : continue;
469 : }
470 0 : bool rendered = false;
471 0 : for(int j = b.batched; j >= 0;)
472 : {
473 0 : batchedmodel &bm = batchedmodels[j];
474 0 : bm.rendertransparentmodel(b, rendered);
475 : }
476 0 : if(rendered)
477 : {
478 0 : b.m->endrender();
479 : }
480 : }
481 0 : aamask::disable();
482 0 : }
483 :
484 0 : void clearbatchedmapmodels()
485 : {
486 0 : for(uint i = 0; i < batches.size(); i++)
487 : {
488 0 : const modelbatch &b = batches[i];
489 0 : if(b.flags&Model_Mapmodel)
490 : {
491 0 : batchedmodels.resize(b.batched);
492 0 : batches.resize(i);
493 0 : break;
494 : }
495 : }
496 0 : }
497 : }
498 :
499 : // model registry
500 :
501 : std::unordered_map<std::string, model *> models;
502 : std::vector<std::string> preloadmodels;
503 :
504 : //used in iengine
505 0 : void preloadmodel(std::string name)
506 : {
507 0 : if(name.empty() || models.find(name) != models.end() || std::find(preloadmodels.begin(), preloadmodels.end(), name) != preloadmodels.end() )
508 : {
509 0 : return;
510 : }
511 0 : preloadmodels.push_back(name);
512 : }
513 :
514 0 : void flushpreloadedmodels(bool msg)
515 : {
516 0 : for(uint i = 0; i < preloadmodels.size(); i++)
517 : {
518 0 : loadprogress = static_cast<float>(i+1)/preloadmodels.size();
519 0 : model *m = loadmodel(preloadmodels[i].c_str(), -1, msg);
520 0 : if(!m)
521 : {
522 0 : if(msg)
523 : {
524 0 : conoutf(Console_Warn, "could not load model: %s", preloadmodels[i].c_str());
525 : }
526 : }
527 : else
528 : {
529 0 : m->preloadmeshes();
530 0 : m->preloadshaders();
531 : }
532 : }
533 0 : preloadmodels.clear();
534 :
535 0 : loadprogress = 0;
536 0 : }
537 :
538 0 : void preloadusedmapmodels(bool msg, bool bih)
539 : {
540 0 : std::vector<extentity *> &ents = entities::getents();
541 0 : std::vector<int> used;
542 0 : for(extentity *&e : ents)
543 : {
544 0 : if(e->type==EngineEnt_Mapmodel && e->attr1 >= 0 && std::find(used.begin(), used.end(), e->attr1) != used.end() )
545 : {
546 0 : used.push_back(e->attr1);
547 : }
548 : }
549 :
550 0 : std::vector<std::string> col;
551 0 : for(uint i = 0; i < used.size(); i++)
552 : {
553 0 : loadprogress = static_cast<float>(i+1)/used.size();
554 0 : int mmindex = used[i];
555 0 : if(!(static_cast<int>(mapmodel::mapmodels.size()) > (mmindex)))
556 : {
557 0 : if(msg)
558 : {
559 0 : conoutf(Console_Warn, "could not find map model: %d", mmindex);
560 : }
561 0 : continue;
562 : }
563 0 : const mapmodelinfo &mmi = mapmodel::mapmodels[mmindex];
564 0 : if(mmi.name.empty())
565 : {
566 0 : continue;
567 : }
568 0 : model *m = loadmodel("", mmindex, msg);
569 0 : if(!m)
570 : {
571 0 : if(msg)
572 : {
573 0 : conoutf(Console_Warn, "could not load map model: %s", mmi.name.c_str());
574 : }
575 : }
576 : else
577 : {
578 0 : if(bih)
579 : {
580 0 : m->preloadBIH();
581 : }
582 0 : else if(m->collide == Collide_TRI && m->collidemodel.empty() && m->bih)
583 : {
584 0 : m->setBIH();
585 : }
586 0 : m->preloadmeshes();
587 0 : m->preloadshaders();
588 0 : if(!m->collidemodel.empty() && std::find(col.begin(), col.end(), m->collidemodel) == col.end())
589 : {
590 0 : col.push_back(m->collidemodel);
591 : }
592 : }
593 : }
594 :
595 0 : for(uint i = 0; i < col.size(); i++)
596 : {
597 0 : loadprogress = static_cast<float>(i+1)/col.size();
598 0 : model *m = loadmodel(col[i].c_str(), -1, msg);
599 0 : if(!m)
600 : {
601 0 : if(msg)
602 : {
603 0 : conoutf(Console_Warn, "could not load collide model: %s", col[i].c_str());
604 : }
605 : }
606 0 : else if(!m->bih)
607 : {
608 0 : m->setBIH();
609 : }
610 : }
611 :
612 0 : loadprogress = 0;
613 0 : }
614 :
615 0 : model *loadmodel(std::string_view name, int i, bool msg)
616 : {
617 0 : model *(__cdecl *md5loader)(const std::string &filename) = +[] (const std::string &filename) -> model* { return new md5(filename); };
618 0 : model *(__cdecl *objloader)(const std::string &filename) = +[] (const std::string &filename) -> model* { return new obj(filename); };
619 0 : model *(__cdecl *gltfloader)(const std::string &filename) = +[] (const std::string &filename) -> model* { return new gltf(filename); };
620 :
621 0 : std::vector<model *(__cdecl *)(const std::string &)> loaders;
622 0 : loaders.push_back(md5loader);
623 0 : loaders.push_back(objloader);
624 0 : loaders.push_back(gltfloader);
625 0 : std::unordered_set<std::string> failedmodels;
626 :
627 0 : if(!name.size())
628 : {
629 0 : if(!(static_cast<int>(mapmodel::mapmodels.size()) > i))
630 : {
631 0 : return nullptr;
632 : }
633 0 : const mapmodelinfo &mmi = mapmodel::mapmodels[i];
634 0 : if(mmi.m)
635 : {
636 0 : return mmi.m;
637 : }
638 0 : name = mmi.name.c_str();
639 : }
640 0 : auto itr = models.find(std::string(name));
641 : model *m;
642 0 : if(itr != models.end())
643 : {
644 0 : m = (*itr).second;
645 : }
646 : else
647 : {
648 0 : if(!name[0] || failedmodels.find(std::string(name)) != failedmodels.end())
649 : {
650 0 : return nullptr;
651 : }
652 0 : if(msg)
653 : {
654 0 : std::string filename;
655 0 : filename.append(modelpath).append(name);
656 0 : renderprogress(loadprogress, filename.c_str());
657 0 : }
658 0 : for(model *(__cdecl *i)(const std::string &) : loaders)
659 : {
660 0 : m = i(std::string(name)); //call model ctor
661 0 : if(!m)
662 : {
663 0 : continue;
664 : }
665 0 : if(m->load()) //now load the model
666 : {
667 0 : break;
668 : }
669 : //delete model if not successful
670 0 : delete m;
671 0 : m = nullptr;
672 : }
673 0 : if(!m)
674 : {
675 0 : failedmodels.insert(std::string(name));
676 0 : return nullptr;
677 : }
678 0 : if(models.find(m->modelname()) == models.end())
679 : {
680 0 : models[m->modelname()] = m;
681 : }
682 : }
683 0 : if((mapmodel::mapmodels.size() > static_cast<uint>(i)) && !mapmodel::mapmodels[i].m)
684 : {
685 0 : mapmodel::mapmodels[i].m = m;
686 : }
687 0 : return m;
688 0 : }
689 :
690 : //used in iengine.h
691 0 : void clear_models()
692 : {
693 0 : for(auto [k, i] : models)
694 : {
695 0 : delete i;
696 0 : }
697 0 : }
698 :
699 0 : void cleanupmodels()
700 : {
701 0 : for(auto [k, i] : models)
702 : {
703 0 : i->cleanup();
704 0 : }
705 0 : }
706 :
707 1 : static void clearmodel(const char *name)
708 : {
709 1 : model *m = nullptr;
710 1 : const auto it = models.find(name);
711 1 : if(it != models.end())
712 : {
713 0 : m = (*it).second;
714 : }
715 1 : if(!m)
716 : {
717 1 : conoutf("model %s is not loaded", name);
718 1 : return;
719 : }
720 0 : for(mapmodelinfo &mmi : mapmodel::mapmodels)
721 : {
722 0 : if(mmi.m == m)
723 : {
724 0 : mmi.m = nullptr;
725 : }
726 0 : if(mmi.collide == m)
727 : {
728 0 : mmi.collide = nullptr;
729 : }
730 : }
731 0 : models.erase(name);
732 0 : m->cleanup();
733 0 : delete m;
734 0 : conoutf("cleared model %s", name);
735 : }
736 :
737 0 : static void rendercullmodelquery(dynent *d, const vec ¢er, float radius)
738 : {
739 0 : if(std::fabs(camera1->o.x-center.x) < radius+1 &&
740 0 : std::fabs(camera1->o.y-center.y) < radius+1 &&
741 0 : std::fabs(camera1->o.z-center.z) < radius+1)
742 : {
743 0 : d->query = nullptr;
744 0 : return;
745 : }
746 0 : d->query = occlusionengine.newquery(d);
747 0 : if(!d->query)
748 : {
749 0 : return;
750 : }
751 0 : d->query->startquery();
752 0 : int br = static_cast<int>(radius*2)+1;
753 0 : drawbb(ivec(static_cast<float>(center.x-radius), static_cast<float>(center.y-radius), static_cast<float>(center.z-radius)), ivec(br, br, br));
754 0 : occlusionengine.endquery();
755 : }
756 :
757 0 : void GBuffer::rendermodelbatches()
758 : {
759 0 : tmodelinfo.mdlsx1 = tmodelinfo.mdlsy1 = 1;
760 0 : tmodelinfo.mdlsx2 = tmodelinfo.mdlsy2 = -1;
761 0 : tmodelinfo.mdltiles.fill(0);
762 :
763 0 : aamask::enable();
764 0 : for(const batching::modelbatch &b : batching::batches)
765 : {
766 0 : if(b.flags&Model_Mapmodel)
767 : {
768 0 : continue;
769 : }
770 0 : bool rendered = false;
771 0 : for(int j = b.batched; j >= 0;)
772 : {
773 0 : batching::batchedmodel &bm = batching::batchedmodels[j];
774 0 : j = bm.next;
775 0 : bm.culled = cullmodel(bm.center, bm.radius, bm.flags, bm.d);
776 0 : if(bm.culled || bm.flags&Model_OnlyShadow)
777 : {
778 0 : continue;
779 : }
780 0 : if(bm.colorscale.a() < 1 || bm.flags&Model_ForceTransparent)
781 : {
782 : float sx1, sy1, sx2, sy2;
783 0 : ivec bbmin(vec(bm.center).sub(bm.radius)), bbmax(vec(bm.center).add(bm.radius+1));
784 0 : if(calcbbscissor(bbmin, bbmax, sx1, sy1, sx2, sy2))
785 : {
786 0 : tmodelinfo.mdlsx1 = std::min(tmodelinfo.mdlsx1, sx1);
787 0 : tmodelinfo.mdlsy1 = std::min(tmodelinfo.mdlsy1, sy1);
788 0 : tmodelinfo.mdlsx2 = std::max(tmodelinfo.mdlsx2, sx2);
789 0 : tmodelinfo.mdlsy2 = std::max(tmodelinfo.mdlsy2, sy2);
790 0 : masktiles(tmodelinfo.mdltiles.data(), sx1, sy1, sx2, sy2);
791 : }
792 0 : continue;
793 0 : }
794 0 : if(!rendered)
795 : {
796 0 : b.m->startrender();
797 0 : rendered = true;
798 0 : aamask::set(true);
799 : }
800 0 : if(bm.flags&Model_CullQuery)
801 : {
802 0 : bm.d->query = occlusionengine.newquery(bm.d);
803 0 : if(bm.d->query)
804 : {
805 0 : bm.d->query->startquery();
806 0 : bm.renderbatchedmodel(b.m);
807 0 : occlusionengine.endquery();
808 0 : continue;
809 : }
810 : }
811 0 : bm.renderbatchedmodel(b.m);
812 : }
813 0 : if(rendered)
814 : {
815 0 : b.m->endrender();
816 : }
817 0 : if(b.flags&Model_CullQuery)
818 : {
819 0 : bool queried = false;
820 0 : for(int j = b.batched; j >= 0;)
821 : {
822 0 : batching::batchedmodel &bm = batching::batchedmodels[j];
823 0 : j = bm.next;
824 0 : if(bm.culled&(Model_CullOccluded|Model_CullQuery) && bm.flags&Model_CullQuery)
825 : {
826 0 : if(!queried)
827 : {
828 0 : if(rendered)
829 : {
830 0 : aamask::set(false);
831 : }
832 0 : startbb();
833 0 : queried = true;
834 : }
835 0 : rendercullmodelquery(bm.d, bm.center, bm.radius);
836 : }
837 : }
838 0 : if(queried)
839 : {
840 0 : endbb();
841 : }
842 : }
843 : }
844 0 : aamask::disable();
845 0 : }
846 :
847 0 : void Occluder::setupmodelquery(occludequery *q)
848 : {
849 0 : modelquery = q;
850 0 : modelquerybatches = batching::batches.size();
851 0 : modelquerymodels = batching::batchedmodels.size();
852 0 : modelqueryattached = batching::modelattached.size();
853 0 : }
854 :
855 0 : void Occluder::endmodelquery()
856 : {
857 0 : if(static_cast<int>(batching::batchedmodels.size()) == modelquerymodels)
858 : {
859 0 : modelquery->fragments = 0;
860 0 : modelquery = nullptr;
861 0 : return;
862 : }
863 0 : aamask::enable();
864 0 : modelquery->startquery();
865 0 : for(batching::modelbatch &b : batching::batches)
866 : {
867 0 : int j = b.batched;
868 0 : if(j < modelquerymodels)
869 : {
870 0 : continue;
871 : }
872 0 : b.m->startrender();
873 0 : aamask::set(!(b.flags&Model_Mapmodel) || b.m->animated());
874 : do
875 : {
876 0 : const batching::batchedmodel &bm = batching::batchedmodels[j];
877 0 : bm.renderbatchedmodel(b.m);
878 0 : j = bm.next;
879 0 : } while(j >= modelquerymodels);
880 0 : b.batched = j;
881 0 : b.m->endrender();
882 : }
883 0 : occlusionengine.endquery();
884 0 : modelquery = nullptr;
885 0 : batching::batches.resize(modelquerybatches);
886 0 : batching::batchedmodels.resize(modelquerymodels);
887 0 : batching::modelattached.resize(modelqueryattached);
888 0 : aamask::disable();
889 : }
890 :
891 0 : void rendermapmodel(int idx, int anim, const vec &o, float yaw, float pitch, float roll, int flags, int basetime, float size)
892 : {
893 0 : if(!(static_cast<int>(mapmodel::mapmodels.size()) > idx))
894 : {
895 0 : return;
896 : }
897 0 : const mapmodelinfo &mmi = mapmodel::mapmodels[idx];
898 0 : model *m = mmi.m ? mmi.m : loadmodel(mmi.name);
899 0 : if(!m)
900 : {
901 0 : return;
902 : }
903 0 : vec center, bbradius;
904 0 : m->boundbox(center, bbradius);
905 0 : float radius = bbradius.magnitude();
906 0 : center.mul(size);
907 0 : if(roll)
908 : {
909 0 : center.rotate_around_y(roll/RAD);
910 : }
911 0 : if(pitch && m->pitched())
912 : {
913 0 : center.rotate_around_x(pitch/RAD);
914 : }
915 0 : center.rotate_around_z(yaw/RAD);
916 0 : center.add(o);
917 0 : radius *= size;
918 :
919 0 : int visible = 0;
920 0 : if(shadowmapping)
921 : {
922 0 : if(!m->shadow)
923 : {
924 0 : return;
925 : }
926 0 : visible = shadowmaskmodel(center, radius);
927 0 : if(!visible)
928 : {
929 0 : return;
930 : }
931 : }
932 0 : else if(flags&(Model_CullVFC|Model_CullDist|Model_CullOccluded) && cullmodel(center, radius, flags))
933 : {
934 0 : return;
935 : }
936 0 : batching::batchedmodels.emplace_back();
937 0 : batching::batchedmodel &b = batching::batchedmodels.back();
938 0 : b.pos = o;
939 0 : b.center = center;
940 0 : b.radius = radius;
941 0 : b.anim = anim;
942 0 : b.orient = {yaw, pitch, roll};
943 0 : b.basetime = basetime;
944 0 : b.basetime2 = 0;
945 0 : b.sizescale = size;
946 0 : b.colorscale = vec4<float>(1, 1, 1, 1);
947 0 : b.flags = flags | Model_Mapmodel;
948 0 : b.visible = visible;
949 0 : b.d = nullptr;
950 0 : b.attached = -1;
951 0 : batching::addbatchedmodel(m, b, batching::batchedmodels.size()-1);
952 : }
953 :
954 0 : void rendermodel(std::string_view mdl, int anim, const vec &o, float yaw, float pitch, float roll, int flags, dynent *d, modelattach *a, int basetime, int basetime2, float size, const vec4<float> &color)
955 : {
956 0 : model *m = loadmodel(mdl);
957 0 : if(!m)
958 : {
959 0 : return;
960 : }
961 :
962 0 : vec center, bbradius;
963 0 : m->boundbox(center, bbradius);
964 0 : float radius = bbradius.magnitude();
965 0 : if(d)
966 : {
967 0 : if(d->ragdoll)
968 : {
969 0 : if(anim & Anim_Ragdoll && d->ragdoll->millis >= basetime)
970 : {
971 0 : radius = std::max(radius, d->ragdoll->radius);
972 0 : center = d->ragdoll->center;
973 0 : goto hasboundbox; //skip roll and pitch stuff
974 : }
975 0 : if(d->ragdoll)
976 : {
977 0 : delete d->ragdoll;
978 0 : d->ragdoll = nullptr;
979 : }
980 : }
981 0 : if(anim & Anim_Ragdoll)
982 : {
983 0 : flags &= ~(Model_CullVFC | Model_CullOccluded | Model_CullQuery);
984 : }
985 : }
986 0 : center.mul(size);
987 0 : if(roll)
988 : {
989 0 : center.rotate_around_y(roll/RAD);
990 : }
991 0 : if(pitch && m->pitched())
992 : {
993 0 : center.rotate_around_x(pitch/RAD);
994 : }
995 0 : center.rotate_around_z(yaw/RAD);
996 0 : center.add(o);
997 0 : hasboundbox:
998 0 : radius *= size;
999 :
1000 0 : if(flags&Model_NoRender)
1001 : {
1002 0 : anim |= Anim_NoRender;
1003 : }
1004 :
1005 0 : if(a)
1006 : {
1007 0 : for(int i = 0; a[i].tag; i++)
1008 : {
1009 0 : if(a[i].name)
1010 : {
1011 0 : a[i].m = loadmodel(a[i].name);
1012 : }
1013 : }
1014 : }
1015 :
1016 0 : if(flags&Model_CullQuery)
1017 : {
1018 0 : if(!oqfrags || !oqdynent || !d)
1019 : {
1020 0 : flags &= ~Model_CullQuery;
1021 : }
1022 : }
1023 :
1024 0 : if(flags&Model_NoBatch)
1025 : {
1026 0 : const int culled = cullmodel(center, radius, flags, d);
1027 0 : if(culled)
1028 : {
1029 0 : if(culled&(Model_CullOccluded|Model_CullQuery) && flags&Model_CullQuery)
1030 : {
1031 0 : startbb();
1032 0 : rendercullmodelquery(d, center, radius);
1033 0 : endbb();
1034 : }
1035 0 : return;
1036 : }
1037 0 : aamask::enable();
1038 0 : if(flags&Model_CullQuery)
1039 : {
1040 0 : d->query = occlusionengine.newquery(d);
1041 0 : if(d->query)
1042 : {
1043 0 : d->query->startquery();
1044 : }
1045 : }
1046 0 : m->startrender();
1047 0 : aamask::set(true);
1048 0 : if(flags&Model_FullBright)
1049 : {
1050 0 : anim |= Anim_FullBright;
1051 : }
1052 0 : m->render(anim, basetime, basetime2, o, yaw, pitch, roll, d, a, size, color);
1053 0 : m->endrender();
1054 0 : if(flags&Model_CullQuery && d->query)
1055 : {
1056 0 : occlusionengine.endquery();
1057 : }
1058 0 : aamask::disable();
1059 0 : return;
1060 : }
1061 :
1062 0 : batching::batchedmodels.emplace_back();
1063 0 : batching::batchedmodel &b = batching::batchedmodels.back();
1064 0 : b.pos = o;
1065 0 : b.center = center;
1066 0 : b.radius = radius;
1067 0 : b.anim = anim;
1068 0 : b.orient = {yaw, pitch, roll};
1069 0 : b.basetime = basetime;
1070 0 : b.basetime2 = basetime2;
1071 0 : b.sizescale = size;
1072 0 : b.colorscale = color;
1073 0 : b.flags = flags;
1074 0 : b.visible = 0;
1075 0 : b.d = d;
1076 0 : b.attached = a ? batching::modelattached.size() : -1;
1077 0 : if(a)
1078 : {
1079 0 : for(int i = 0;; i++)
1080 : {
1081 0 : batching::modelattached.push_back(a[i]);
1082 0 : if(!a[i].tag)
1083 : {
1084 0 : break;
1085 : }
1086 : }
1087 : }
1088 0 : batching::addbatchedmodel(m, b, batching::batchedmodels.size()-1);
1089 : }
1090 :
1091 0 : int intersectmodel(std::string_view mdl, int anim, const vec &pos, float yaw, float pitch, float roll, const vec &o, const vec &ray, dynent *d, modelattach *a, int basetime, int basetime2, float size)
1092 : {
1093 0 : const model *m = loadmodel(mdl);
1094 0 : if(!m)
1095 : {
1096 0 : return -1;
1097 : }
1098 0 : if(d && d->ragdoll && (!(anim & Anim_Ragdoll) || d->ragdoll->millis < basetime))
1099 : {
1100 0 : if(d->ragdoll)
1101 : {
1102 0 : delete d->ragdoll;
1103 0 : d->ragdoll = nullptr;
1104 : }
1105 : }
1106 0 : if(a)
1107 : {
1108 0 : for(int i = 0; a[i].tag; i++)
1109 : {
1110 0 : if(a[i].name)
1111 : {
1112 0 : a[i].m = loadmodel(a[i].name);
1113 : }
1114 : }
1115 : }
1116 0 : return m->intersect(anim, basetime, basetime2, pos, yaw, pitch, roll, d, a, size, o, ray);
1117 : }
1118 :
1119 0 : void abovemodel(vec &o, const char *mdl)
1120 : {
1121 0 : const model *m = loadmodel(mdl);
1122 0 : if(!m)
1123 : {
1124 0 : return;
1125 : }
1126 0 : vec center, radius;
1127 0 : m->calcbb(center, radius);
1128 0 : o.z += center.z + radius.z;
1129 : }
1130 :
1131 3 : std::vector<size_t> findanims(std::string_view pattern)
1132 : {
1133 3 : std::vector<size_t> anims;
1134 5 : for(size_t i = 0; i < animnames.size(); ++i)
1135 : {
1136 2 : if(!animnames.at(i).compare(pattern))
1137 : {
1138 2 : anims.push_back(i);
1139 : }
1140 : }
1141 3 : return anims;
1142 0 : }
1143 :
1144 1 : void findanimscmd(const char *name)
1145 : {
1146 1 : std::vector<size_t> anims = findanims(name);
1147 1 : std::vector<char> buf;
1148 : string num;
1149 1 : for(size_t i = 0; i < anims.size(); i++)
1150 : {
1151 0 : formatstring(num, "%lu", anims[i]);
1152 0 : if(i > 0)
1153 : {
1154 0 : buf.push_back(' ');
1155 : }
1156 0 : for(size_t i = 0; i < std::strlen(num); ++i)
1157 : {
1158 0 : buf.push_back(num[i]);
1159 : }
1160 : }
1161 1 : buf.push_back('\0');
1162 1 : result(buf.data());
1163 1 : }
1164 :
1165 0 : void loadskin(const std::string &dir, const std::string &altdir, Texture *&skin, Texture *&masks) // model skin sharing
1166 : {
1167 : //goes and attempts a textureload for png, jpg four times using the cascading if statements, first for default then for alt directory
1168 0 : static auto tryload = [] (Texture *tex, std::string name, const std::string &mdir, const std::string &maltdir) -> bool
1169 : {
1170 0 : if((tex = textureload(makerelpath(mdir.c_str(), name.append(".jpg").c_str(), nullptr, nullptr), 0, true, false))==notexture)
1171 : {
1172 0 : if((tex = textureload(makerelpath(mdir.c_str(), name.append(".png").c_str(), nullptr, nullptr), 0, true, false))==notexture)
1173 : {
1174 0 : if((tex = textureload(makerelpath(maltdir.c_str(), name.append(".jpg").c_str(), nullptr, nullptr), 0, true, false))==notexture)
1175 : {
1176 0 : if((tex = textureload(makerelpath(maltdir.c_str(), name.append(".png").c_str(), nullptr, nullptr), 0, true, false))==notexture)
1177 : {
1178 0 : return true;
1179 : }
1180 : }
1181 : }
1182 : }
1183 0 : return false;
1184 : };
1185 :
1186 0 : std::string mdir,
1187 0 : maltdir;
1188 0 : mdir.append(modelpath).append(dir);
1189 0 : mdir.append(modelpath).append(altdir);
1190 0 : masks = notexture;
1191 0 : if(tryload(skin, "skin", mdir, maltdir))
1192 : {
1193 0 : return;
1194 : }
1195 0 : if(tryload(masks, "masks", mdir, maltdir))
1196 : {
1197 0 : return;
1198 : }
1199 0 : }
1200 :
1201 0 : void setbbfrommodel(dynent *d, std::string_view mdl)
1202 : {
1203 0 : model *m = loadmodel(mdl);
1204 0 : if(!m)
1205 : {
1206 0 : return;
1207 : }
1208 0 : vec center, radius;
1209 0 : m->collisionbox(center, radius);
1210 0 : if(m->collide != Collide_Ellipse)
1211 : {
1212 0 : d->collidetype = Collide_OrientedBoundingBox;
1213 : }
1214 0 : d->xradius = radius.x + std::fabs(center.x);
1215 0 : d->yradius = radius.y + std::fabs(center.y);
1216 0 : d->radius = d->collidetype==Collide_OrientedBoundingBox ? sqrtf(d->xradius*d->xradius + d->yradius*d->yradius) : std::max(d->xradius, d->yradius);
1217 0 : d->eyeheight = (center.z-radius.z) + radius.z*2*m->eyeheight;
1218 0 : d->aboveeye = radius.z*2*(1.0f-m->eyeheight);
1219 0 : if (d->aboveeye + d->eyeheight <= 0.5f)
1220 : {
1221 0 : float zrad = (0.5f - (d->aboveeye + d->eyeheight)) / 2;
1222 0 : d->aboveeye += zrad;
1223 0 : d->eyeheight += zrad;
1224 : }
1225 : }
1226 :
1227 1 : void initrendermodelcmds()
1228 : {
1229 1 : addcommand("mapmodelreset", reinterpret_cast<identfun>(mapmodel::reset), "i", Id_Command);
1230 1 : addcommand("mapmodel", reinterpret_cast<identfun>(mapmodel::open), "s", Id_Command);
1231 1 : addcommand("mapmodelname", reinterpret_cast<identfun>(mapmodel::namecmd), "ii", Id_Command);
1232 1 : addcommand("mapmodelloaded", reinterpret_cast<identfun>(mapmodel::loaded), "i", Id_Command);
1233 1 : addcommand("nummapmodels", reinterpret_cast<identfun>(mapmodel::num), "", Id_Command);
1234 1 : addcommand("clearmodel", reinterpret_cast<identfun>(clearmodel), "s", Id_Command);
1235 1 : addcommand("findanims", reinterpret_cast<identfun>(findanimscmd), "s", Id_Command);
1236 1 : }
|