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