Line data Source code
1 : /* physics.cpp: no physics books were hurt nor consulted in the construction of this code.
2 : * All physics computations and constants were invented on the fly and simply tweaked until
3 : * they "felt right", and have no basis in reality. Collision detection is simplistic but
4 : * very robust (uses discrete steps at fixed fps).
5 : */
6 : #include "../libprimis-headers/cube.h"
7 : #include "../../shared/geomexts.h"
8 :
9 : #include <memory>
10 : #include <optional>
11 :
12 : #include "bih.h"
13 : #include "entities.h"
14 : #include "mpr.h"
15 : #include "octaworld.h"
16 : #include "physics.h"
17 : #include "raycube.h"
18 : #include "world.h"
19 :
20 : #include "interface/console.h"
21 :
22 : #include "model/model.h"
23 :
24 : #include "render/rendermodel.h"
25 :
26 : int numdynents; //updated by engine, visible through iengine.h
27 : std::vector<dynent *> dynents;
28 :
29 : static constexpr int maxclipoffset = 4;
30 : static constexpr int maxclipplanes = 1024;
31 : static std::array<clipplanes, maxclipplanes> clipcache;
32 : static int clipcacheversion = -maxclipoffset;
33 :
34 0 : clipplanes &cubeworld::getclipbounds(const cube &c, const ivec &o, int size, int offset)
35 : {
36 : //index is naive hash of difference between addresses (not necessarily contiguous) modulo cache size
37 0 : clipplanes &p = clipcache[static_cast<int>(&c - &((*worldroot)[0])) & (maxclipplanes-1)];
38 0 : if(p.owner != &c || p.version != clipcacheversion+offset)
39 : {
40 0 : p.owner = &c;
41 0 : p.version = clipcacheversion+offset;
42 0 : genclipbounds(c, o, size, p);
43 : }
44 0 : return p;
45 : }
46 :
47 0 : static clipplanes &getclipbounds(const cube &c, const ivec &o, int size, const physent &d)
48 : {
49 0 : int offset = !(c.visible&0x80) || d.type==physent::PhysEnt_Player ? 0 : 1;
50 0 : return rootworld.getclipbounds(c, o, size, offset);
51 : }
52 :
53 0 : static int forceclipplanes(const cube &c, const ivec &o, int size, clipplanes &p)
54 : {
55 0 : if(p.visible&0x80)
56 : {
57 0 : bool collide = true,
58 0 : noclip = false;
59 0 : if(p.version&1)
60 : {
61 0 : collide = false;
62 0 : noclip = true;
63 : }
64 0 : genclipplanes(c, o, size, p, collide, noclip);
65 : }
66 0 : return p.visible;
67 : }
68 :
69 0 : void cubeworld::resetclipplanes()
70 : {
71 0 : clipcacheversion += maxclipoffset;
72 0 : if(!clipcacheversion)
73 : {
74 0 : for(clipplanes &i : clipcache)
75 : {
76 0 : i.clear();
77 : }
78 0 : clipcacheversion = maxclipoffset;
79 : }
80 0 : }
81 :
82 : ///////////////////////// entity collision ///////////////////////////////////////////////
83 :
84 : // info about collisions
85 : int collideinside; // whether an internal collision happened
86 : const physent *collideplayer; // whether the collection hit a player
87 :
88 0 : static CollisionInfo ellipseboxcollide(const physent *d, const vec &dir, const vec &origin, const vec ¢er, float yaw, float xr, float yr, float hi, float lo)
89 : {
90 0 : vec cwall(0,0,0);
91 0 : float below = (origin.z+center.z-lo) - (d->o.z+d->aboveeye),
92 0 : above = (d->o.z-d->eyeheight) - (origin.z+center.z+hi);
93 0 : if(below>=0 || above>=0)
94 : {
95 0 : return {false, cwall};
96 : }
97 0 : vec yo(d->o);
98 0 : yo.sub(origin);
99 0 : yo.rotate_around_z(-yaw/RAD);
100 0 : yo.sub(center);
101 :
102 0 : float dx = std::clamp(yo.x, -xr, xr) - yo.x,
103 0 : dy = std::clamp(yo.y, -yr, yr) - yo.y,
104 0 : dist = sqrtf(dx*dx + dy*dy) - d->radius;
105 0 : if(dist < 0)
106 : {
107 0 : int sx = yo.x <= -xr ? -1 : (yo.x >= xr ? 1 : 0),
108 0 : sy = yo.y <= -yr ? -1 : (yo.y >= yr ? 1 : 0);
109 0 : if(dist > (yo.z < 0 ? below : above) && (sx || sy))
110 : {
111 0 : vec ydir(dir);
112 0 : ydir.rotate_around_z(-yaw/RAD);
113 0 : if(sx*yo.x - xr > sy*yo.y - yr)
114 : {
115 0 : if(dir.iszero() || sx*ydir.x < -1e-6f)
116 : {
117 0 : cwall = vec(sx, 0, 0);
118 0 : cwall.rotate_around_z(yaw/RAD);
119 0 : return {true, cwall};
120 : }
121 : }
122 0 : else if(dir.iszero() || sy*ydir.y < -1e-6f)
123 : {
124 0 : cwall = vec(0, sy, 0);
125 0 : cwall.rotate_around_z(yaw/RAD);
126 0 : return {true, cwall};
127 : }
128 : }
129 0 : if(yo.z < 0)
130 : {
131 0 : if(dir.iszero() || (dir.z > 0 && (d->type!=physent::PhysEnt_Player || below >= d->zmargin-(d->eyeheight+d->aboveeye)/4.0f)))
132 : {
133 0 : cwall = vec(0, 0, -1);
134 0 : return {true, cwall};
135 : }
136 : }
137 0 : else if(dir.iszero() || (dir.z < 0 && (d->type!=physent::PhysEnt_Player || above >= d->zmargin-(d->eyeheight+d->aboveeye)/3.0f)))
138 : {
139 0 : cwall = vec(0, 0, 1);
140 0 : return {true, cwall};
141 : }
142 0 : collideinside++;
143 : }
144 0 : return {false, vec(0,0,0)};
145 : }
146 :
147 0 : static CollisionInfo ellipsecollide(const physent *d, const vec &dir, const vec &o, const vec ¢er, float yaw, float xr, float yr, float hi, float lo)
148 : {
149 0 : vec cwall(0,0,0);
150 0 : float below = (o.z+center.z-lo) - (d->o.z+d->aboveeye),
151 0 : above = (d->o.z-d->eyeheight) - (o.z+center.z+hi);
152 0 : if(below>=0 || above>=0)
153 : {
154 0 : return {false, cwall};
155 : }
156 0 : vec yo(center);
157 0 : yo.rotate_around_z(yaw/RAD);
158 0 : yo.add(o);
159 0 : float x = yo.x - d->o.x,
160 0 : y = yo.y - d->o.y,
161 0 : angle = atan2f(y, x),
162 0 : dangle = angle-d->yaw/RAD,
163 0 : eangle = angle-yaw/RAD,
164 0 : dx = d->xradius*std::cos(dangle),
165 0 : dy = d->yradius*std::sin(dangle),
166 0 : ex = xr*std::cos(eangle),
167 0 : ey = yr*std::sin(eangle),
168 0 : dist = sqrtf(x*x + y*y) - sqrtf(dx*dx + dy*dy) - sqrtf(ex*ex + ey*ey);
169 0 : if(dist < 0)
170 : {
171 0 : if(dist > (d->o.z < yo.z ? below : above) && (dir.iszero() || x*dir.x + y*dir.y > 0))
172 : {
173 0 : cwall = vec(-x, -y, 0).rescale(1);
174 0 : return {true, cwall};
175 : }
176 0 : if(d->o.z < yo.z)
177 : {
178 0 : if(dir.iszero() || (dir.z > 0 && (d->type!=physent::PhysEnt_Player || below >= d->zmargin-(d->eyeheight+d->aboveeye)/4.0f)))
179 : {
180 0 : cwall = vec(0, 0, -1);
181 0 : return {true, cwall};
182 : }
183 : }
184 0 : else if(dir.iszero() || (dir.z < 0 && (d->type!=physent::PhysEnt_Player || above >= d->zmargin-(d->eyeheight+d->aboveeye)/3.0f)))
185 : {
186 0 : cwall = vec(0, 0, 1);
187 0 : return {true, cwall};
188 : }
189 0 : collideinside++;
190 : }
191 0 : return {false, vec(0,0,0)};
192 : }
193 :
194 : static constexpr int dynentcachesize = 1024;
195 :
196 : static size_t dynentframe = 0;
197 :
198 : struct dynentcacheentry final
199 : {
200 : int x, y;
201 : size_t frame;
202 : std::vector<const physent *> dynents;
203 : };
204 :
205 : static std::array<dynentcacheentry, dynentcachesize> dynentcache;
206 :
207 : //resets the dynentcache[] array entries
208 : //used in iengine
209 0 : void cleardynentcache()
210 : {
211 0 : dynentframe++;
212 0 : if(!dynentframe || dynentframe == 1)
213 : {
214 0 : for(int i = 0; i < dynentcachesize; ++i)
215 : {
216 0 : dynentcache[i].frame = 0;
217 : }
218 : }
219 0 : if(!dynentframe)
220 : {
221 0 : dynentframe = 1;
222 : }
223 0 : }
224 :
225 : //returns the dynent at location i in the dynents vector
226 : //used in iengine
227 0 : dynent *iterdynents(int i)
228 : {
229 0 : if(i < static_cast<int>(dynents.size()))
230 : {
231 0 : return dynents[i];
232 : }
233 0 : return nullptr;
234 : }
235 :
236 0 : static VARF(dynentsize, 4, 7, 12, cleardynentcache());
237 :
238 0 : static int dynenthash(int x, int y)
239 : {
240 0 : return (((((x)^(y))<<5) + (((x)^(y))>>5)) & (dynentcachesize - 1));
241 : }
242 :
243 0 : static const std::vector<const physent *> &checkdynentcache(int x, int y)
244 : {
245 0 : dynentcacheentry &dec = dynentcache[dynenthash(x, y)];
246 0 : if(dec.x == x && dec.y == y && dec.frame == dynentframe)
247 : {
248 0 : return dec.dynents;
249 : }
250 0 : dec.x = x;
251 0 : dec.y = y;
252 0 : dec.frame = dynentframe;
253 0 : dec.dynents.clear();
254 0 : int numdyns = numdynents,
255 0 : dsize = 1<<dynentsize,
256 0 : dx = x<<dynentsize,
257 0 : dy = y<<dynentsize;
258 0 : for(int i = 0; i < numdyns; ++i)
259 : {
260 0 : dynent *d = iterdynents(i);
261 0 : if(d->ragdoll ||
262 0 : d->o.x+d->radius <= dx || d->o.x-d->radius >= dx+dsize ||
263 0 : d->o.y+d->radius <= dy || d->o.y-d->radius >= dy+dsize)
264 : {
265 0 : continue;
266 : }
267 0 : dec.dynents.push_back(d);
268 : }
269 0 : return dec.dynents;
270 : }
271 :
272 : //============================================================== LOOPDYNENTCACHE
273 : #define LOOPDYNENTCACHE(curx, cury, o, radius) \
274 : for(int curx = std::max(static_cast<int>(o.x-radius), 0)>>dynentsize, endx = std::min(static_cast<int>(o.x+radius), rootworld.mapsize()-1)>>dynentsize; curx <= endx; curx++) \
275 : for(int cury = std::max(static_cast<int>(o.y-radius), 0)>>dynentsize, endy = std::min(static_cast<int>(o.y+radius), rootworld.mapsize()-1)>>dynentsize; cury <= endy; cury++)
276 :
277 : //used in iengine
278 0 : void updatedynentcache(physent *d)
279 : {
280 0 : LOOPDYNENTCACHE(x, y, d->o, d->radius)
281 : {
282 0 : dynentcacheentry &dec = dynentcache[dynenthash(x, y)];
283 0 : if(dec.x != x || dec.y != y || dec.frame != dynentframe || (std::find(dec.dynents.begin(), dec.dynents.end(), d) != dec.dynents.end()))
284 : {
285 0 : continue;
286 : }
287 0 : dec.dynents.push_back(d);
288 : }
289 0 : }
290 :
291 : template<class O>
292 0 : static CollisionInfo plcollide(const physent *d, const vec &dir, const physent *o)
293 : {
294 0 : mpr::EntOBB entvol(d);
295 0 : O obvol(o);
296 0 : vec cp;
297 0 : if(mpr::collide(entvol, obvol, nullptr, nullptr, &cp))
298 : {
299 0 : vec wn = cp.sub(obvol.center());
300 0 : vec cwall = obvol.contactface(wn, dir.iszero() ? wn.neg() : dir);
301 0 : if(!cwall.iszero())
302 : {
303 0 : return {true, cwall};
304 : }
305 0 : collideinside++;
306 : }
307 0 : return {false, vec(0,0,0)};
308 : }
309 :
310 0 : static CollisionInfo plcollide(const physent *d, const vec &dir, const physent *o)
311 : {
312 0 : switch(d->collidetype)
313 : {
314 0 : case Collide_Ellipse:
315 : {
316 0 : if(o->collidetype == Collide_Ellipse)
317 : {
318 0 : return ellipsecollide(d, dir, o->o, vec(0, 0, 0), o->yaw, o->xradius, o->yradius, o->aboveeye, o->eyeheight);
319 : }
320 : else
321 : {
322 0 : return ellipseboxcollide(d, dir, o->o, vec(0, 0, 0), o->yaw, o->xradius, o->yradius, o->aboveeye, o->eyeheight);
323 : }
324 : }
325 0 : case Collide_OrientedBoundingBox:
326 : {
327 0 : if(o->collidetype == Collide_Ellipse)
328 : {
329 0 : return plcollide<mpr::EntCylinder>(d, dir, o);
330 : }
331 : else
332 : {
333 0 : return plcollide<mpr::EntOBB>(d, dir, o);
334 : }
335 : }
336 0 : default:
337 : {
338 0 : return {false, vec(0,0,0)};
339 : }
340 : }
341 : }
342 :
343 0 : CollisionInfo plcollide(const physent *d, const vec &dir, bool insideplayercol) // collide with player
344 : {
345 0 : if(d->type==physent::PhysEnt_Camera)
346 : {
347 0 : return {false, vec(0,0,0)};
348 : }
349 0 : int lastinside = collideinside;
350 0 : const physent *insideplayer = nullptr;
351 0 : LOOPDYNENTCACHE(x, y, d->o, d->radius)
352 : {
353 0 : const std::vector<const physent *> &dynentlist = checkdynentcache(x, y);
354 0 : for(const physent* const& o: dynentlist)
355 : {
356 0 : if(o==d || d->o.reject(o->o, d->radius+o->radius))
357 : {
358 0 : continue;
359 : }
360 0 : if(CollisionInfo ci = plcollide(d, dir, o); ci.collided)
361 : {
362 0 : collideplayer = o;
363 0 : return ci;
364 : }
365 0 : if(collideinside > lastinside)
366 : {
367 0 : lastinside = collideinside;
368 0 : insideplayer = o;
369 : }
370 : }
371 : }
372 0 : if(insideplayer && insideplayercol)
373 : {
374 0 : collideplayer = insideplayer;
375 0 : return {true, vec(0,0,0)};
376 : }
377 0 : return {false, vec(0,0,0)};
378 : }
379 :
380 : #undef LOOPDYNENTCACHE
381 : //==============================================================================
382 :
383 : //orient consists of {yaw, pitch, roll}
384 : //cwall -> collide wall
385 : template<class M>
386 0 : static CollisionInfo mmcollide(const physent *d, const vec &dir, const extentity &e, const vec ¢er, const vec &radius, const ivec &orient)
387 : {
388 0 : mpr::EntOBB entvol(d);
389 0 : M mdlvol(e.o, center, radius, orient.x, orient.y, orient.z);
390 0 : vec cp;
391 0 : if(mpr::collide(entvol, mdlvol, nullptr, nullptr, &cp))
392 : {
393 0 : vec wn = cp.sub(mdlvol.center());
394 0 : vec cwall = mdlvol.contactface(wn, dir.iszero() ? wn.neg() : dir);
395 0 : if(!cwall.iszero())
396 : {
397 0 : return {true, cwall};
398 : }
399 0 : collideinside++;
400 : }
401 0 : return {false, vec(0,0,0)};
402 0 : }
403 :
404 : /**
405 : * @brief Preliminary check for fuzzycollide functions.
406 : *
407 : * @tparam E the mpr object type to evaluate
408 : * @param d the physent to get object location and other information from
409 : * @param mdlvol the model volume bounding values to check
410 : * @param bbradius the bounding box radius to check against
411 : *
412 : * @return true if it is possible for these models to collide
413 : * @return false if there is no way for these models to collide
414 : */
415 : template<class E>
416 0 : static bool checkfuzzycollidebounds(const physent *d, const E &mdlvol, const vec &bbradius)
417 : {
418 0 : if(std::fabs(d->o.x - mdlvol.o.x) > bbradius.x + d->radius ||
419 0 : std::fabs(d->o.y - mdlvol.o.y) > bbradius.y + d->radius ||
420 0 : d->o.z + d->aboveeye < mdlvol.o.z - bbradius.z ||
421 0 : d->o.z - d->eyeheight > mdlvol.o.z + bbradius.z)
422 : {
423 0 : return false;
424 : }
425 0 : return true;
426 : }
427 :
428 : //cwall -> collide wall
429 : //orient {yaw, pitch, roll}
430 0 : static CollisionInfo fuzzycollidebox(const physent *d, const vec &dir, float cutoff, const vec &o, const vec ¢er, const vec &radius, const ivec &orient)
431 : {
432 0 : mpr::ModelOBB mdlvol(o, center, radius, orient.x, orient.y, orient.z);
433 0 : vec bbradius = mdlvol.orient.abstransposedtransform(radius);
434 0 : if(!checkfuzzycollidebounds(d, mdlvol, bbradius))
435 : {
436 0 : return {false, vec(0,0,0)};
437 : }
438 0 : mpr::EntCapsule entvol(d);
439 0 : vec cwall(0, 0, 0);
440 0 : float bestdist = -1e10f;
441 0 : for(int i = 0; i < 6; ++i)
442 : {
443 0 : vec w;
444 : float dist;
445 0 : switch(i)
446 : {
447 0 : default:
448 : case 0:
449 : {
450 0 : w = mdlvol.orient.rowx().neg();
451 0 : dist = -radius.x;
452 0 : break;
453 : }
454 0 : case 1:
455 : {
456 0 : w = mdlvol.orient.rowx();
457 0 : dist = -radius.x;
458 0 : break;
459 : }
460 0 : case 2:
461 : {
462 0 : w = mdlvol.orient.rowy().neg();
463 0 : dist = -radius.y;
464 0 : break;
465 : }
466 0 : case 3:
467 : {
468 0 : w = mdlvol.orient.rowy();
469 0 : dist = -radius.y;
470 0 : break;
471 : }
472 0 : case 4:
473 : {
474 0 : w = mdlvol.orient.rowz().neg();
475 0 : dist = -radius.z;
476 0 : break;
477 : }
478 0 : case 5:
479 : {
480 0 : w = mdlvol.orient.rowz();
481 0 : dist = -radius.z;
482 0 : break;
483 : }
484 : }
485 0 : vec pw = entvol.supportpoint(w.neg());
486 0 : dist += w.dot(pw.sub(mdlvol.o));
487 0 : if(dist >= 0)
488 : {
489 0 : return {false, cwall};
490 : }
491 0 : if(dist <= bestdist)
492 : {
493 0 : continue;
494 : }
495 0 : cwall = vec(0, 0, 0);
496 0 : bestdist = dist;
497 0 : if(!dir.iszero())
498 : {
499 0 : if(w.dot(dir) >= -cutoff*dir.magnitude())
500 : {
501 0 : continue;
502 : }
503 : //nasty ternary in the indented part
504 0 : if(d->type==physent::PhysEnt_Player &&
505 0 : dist < (dir.z*w.z < 0 ?
506 0 : d->zmargin-(d->eyeheight+d->aboveeye)/(dir.z < 0 ? 3.0f : 4.0f) :
507 0 : (dir.x*w.x < 0 || dir.y*w.y < 0 ? -d->radius : 0)))
508 : {
509 0 : continue;
510 : }
511 : }
512 0 : cwall = w;
513 : }
514 0 : if(cwall.iszero())
515 : {
516 0 : collideinside++;
517 0 : return {false, cwall};
518 : }
519 0 : return {true, cwall};
520 0 : }
521 :
522 : //orient consists of {yaw, pitch, roll}
523 : //cwall -> collide wall
524 : template<class E>
525 0 : static CollisionInfo fuzzycollideellipse(const physent *d, const vec &dir, float cutoff, const vec &o, const vec ¢er, const vec &radius, const ivec &orient)
526 : {
527 0 : mpr::ModelEllipse mdlvol(o, center, radius, orient.x, orient.y, orient.z);
528 0 : vec bbradius = mdlvol.orient.abstransposedtransform(radius);
529 :
530 0 : if(!checkfuzzycollidebounds(d, mdlvol, bbradius))
531 : {
532 0 : return {false, vec(0,0,0)};
533 : }
534 0 : E entvol(d);
535 0 : vec cwall(0, 0, 0);
536 0 : float bestdist = -1e10f;
537 0 : for(int i = 0; i < 3; ++i)
538 : {
539 0 : vec w;
540 : float dist;
541 0 : switch(i)
542 : {
543 0 : default:
544 : case 0:
545 : {
546 0 : w = mdlvol.orient.rowz();
547 0 : dist = -radius.z;
548 0 : break;
549 : }
550 0 : case 1:
551 : {
552 0 : w = mdlvol.orient.rowz().neg();
553 0 : dist = -radius.z;
554 0 : break;
555 : }
556 0 : case 2:
557 : {
558 0 : vec2 ln(mdlvol.orient.transform(entvol.center().sub(mdlvol.o)));
559 0 : float r = ln.magnitude();
560 0 : if(r < 1e-6f)
561 : {
562 0 : continue;
563 : }
564 0 : vec2 lw = vec2(ln.x*radius.y, ln.y*radius.x).normalize();
565 0 : w = mdlvol.orient.transposedtransform(lw);
566 0 : dist = -vec2(ln.x*radius.x, ln.y*radius.y).dot(lw)/r;
567 0 : break;
568 : }
569 : }
570 0 : vec pw = entvol.supportpoint(vec(w).neg());
571 0 : dist += w.dot(vec(pw).sub(mdlvol.o));
572 0 : if(dist >= 0)
573 : {
574 0 : return {false, vec(0,0,0)};
575 : }
576 0 : if(dist <= bestdist)
577 : {
578 0 : continue;
579 : }
580 0 : cwall = vec(0, 0, 0);
581 0 : bestdist = dist;
582 0 : if(!dir.iszero())
583 : {
584 0 : if(w.dot(dir) >= -cutoff*dir.magnitude())
585 : {
586 0 : continue;
587 : }
588 0 : if(d->type==physent::PhysEnt_Player &&
589 0 : dist < (dir.z*w.z < 0 ?
590 0 : d->zmargin-(d->eyeheight+d->aboveeye)/(dir.z < 0 ? 3.0f : 4.0f) :
591 0 : (dir.x*w.x < 0 || dir.y*w.y < 0 ? -d->radius : 0)))
592 : {
593 0 : continue;
594 : }
595 : }
596 0 : cwall = w;
597 : }
598 0 : if(cwall.iszero())
599 : {
600 0 : collideinside++;
601 0 : return {false, cwall};
602 : }
603 0 : return {true, cwall};
604 0 : }
605 :
606 : //force a collision type:
607 : // 0: do not force
608 : // 1: Collide_Ellipse
609 : // 2: Collide_OrientedBoundingBox
610 : static VAR(testtricol, 0, 0, 2);
611 :
612 0 : static CollisionInfo mmcollide(const physent *d, const vec &dir, float cutoff, const octaentities &oc) // collide with a mapmodel
613 : {
614 0 : const std::vector<extentity *> &ents = entities::getents();
615 0 : for(const int &i : oc.mapmodels)
616 : {
617 0 : extentity &e = *ents[i];
618 0 : if(e.flags&EntFlag_NoCollide || !(static_cast<int>(mapmodel::mapmodels.size()) > e.attr1))
619 : {
620 0 : continue;
621 : }
622 0 : MapModelInfo &mmi = mapmodel::mapmodels[e.attr1];
623 0 : model *m = mmi.collide;
624 0 : if(!m)
625 : {
626 0 : if(!mmi.m && !loadmodel("", e.attr1))
627 : {
628 0 : continue;
629 : }
630 0 : if(!mmi.m->collidemodel.empty())
631 : {
632 0 : m = loadmodel(mmi.m->collidemodel);
633 : }
634 0 : if(!m)
635 : {
636 0 : m = mmi.m;
637 : }
638 0 : mmi.collide = m;
639 : }
640 0 : int mcol = mmi.m->collide;
641 0 : if(!mcol)
642 : {
643 0 : continue;
644 : }
645 0 : vec center, radius;
646 0 : float rejectradius = m->collisionbox(center, radius),
647 0 : scale = e.attr5 > 0 ? e.attr5/100.0f : 1;
648 0 : center.mul(scale);
649 0 : if(d->o.reject(vec(e.o).add(center), d->radius + rejectradius*scale))
650 : {
651 0 : continue;
652 : }
653 0 : int yaw = e.attr2,
654 0 : pitch = e.attr3,
655 0 : roll = e.attr4;
656 0 : if(mcol == Collide_TRI || testtricol)
657 : {
658 0 : m->setBIH();
659 0 : switch(testtricol ? testtricol : d->collidetype)
660 : {
661 0 : case Collide_Ellipse:
662 : {
663 0 : if(CollisionInfo ci = m->bih->ellipsecollide(d, dir, cutoff, e.o, yaw, pitch, roll, scale); ci.collided)
664 : {
665 0 : return ci;
666 : }
667 0 : break;
668 : }
669 0 : case Collide_OrientedBoundingBox:
670 : {
671 0 : if(CollisionInfo ci = m->bih->boxcollide(d, dir, cutoff, e.o, yaw, pitch, roll, scale); ci.collided)
672 : {
673 0 : return ci;
674 : }
675 0 : break;
676 : }
677 0 : default:
678 : {
679 0 : continue;
680 : }
681 0 : }
682 0 : }
683 : else
684 : {
685 0 : radius.mul(scale);
686 0 : switch(d->collidetype)
687 : {
688 0 : case Collide_Ellipse:
689 : {
690 0 : if(mcol == Collide_Ellipse)
691 : {
692 0 : if(pitch || roll)
693 : {
694 0 : CollisionInfo ci = fuzzycollideellipse<mpr::EntCapsule>(d, dir, cutoff, e.o, center, radius, {yaw, pitch, roll});
695 0 : if(ci.collided)
696 : {
697 0 : return ci;
698 : }
699 0 : }
700 0 : else if(CollisionInfo ci = ellipsecollide(d, dir, e.o, center, yaw, radius.x, radius.y, radius.z, radius.z); ci.collided)
701 : {
702 0 : return ci;
703 : }
704 : }
705 0 : else if(pitch || roll)
706 : {
707 0 : if(CollisionInfo ci = fuzzycollidebox(d, dir, cutoff, e.o, center, radius, {yaw, pitch, roll}); ci.collided)
708 : {
709 0 : return ci;
710 : }
711 0 : }
712 0 : else if(CollisionInfo ci = ellipseboxcollide(d, dir, e.o, center, yaw, radius.x, radius.y, radius.z, radius.z); ci.collided)
713 : {
714 0 : return ci;
715 : }
716 0 : break;
717 : }
718 0 : case Collide_OrientedBoundingBox:
719 : {
720 0 : if(mcol == Collide_Ellipse)
721 : {
722 0 : if(CollisionInfo ci = mmcollide<mpr::ModelEllipse>(d, dir, e, center, radius, {yaw, pitch, roll}); ci.collided)
723 : {
724 0 : return ci;
725 : }
726 : }
727 0 : else if(CollisionInfo ci = mmcollide<mpr::ModelOBB>(d, dir, e, center, radius, {yaw, pitch, roll}); ci.collided)
728 : {
729 0 : return ci;
730 : }
731 0 : break;
732 : }
733 0 : default:
734 : {
735 0 : continue;
736 : }
737 0 : }
738 : }
739 : }
740 0 : return {false, vec(0,0,0)};
741 : }
742 :
743 0 : static bool checkside(const physent &d, int side, const vec &dir, const int visible, const float cutoff, float distval, float dotval, float margin, vec normal, vec &collidewall, float &bestdist)
744 : {
745 0 : if(visible&(1<<side))
746 : {
747 0 : float dist = distval;
748 0 : if(dist > 0)
749 : {
750 0 : return false;
751 : }
752 0 : if(dist <= bestdist)
753 : {
754 0 : return true;
755 : }
756 0 : if(!dir.iszero())
757 : {
758 0 : if(dotval >= -cutoff*dir.magnitude())
759 : {
760 0 : return true;
761 : }
762 0 : if(d.type==physent::PhysEnt_Player && dotval < 0 && dist < margin)
763 : {
764 0 : return true;
765 : }
766 : }
767 0 : collidewall = normal;
768 0 : bestdist = dist;
769 : }
770 0 : return true;
771 : }
772 :
773 : //cwall -> collide wall
774 0 : static CollisionInfo fuzzycollidesolid(const physent *d, const vec &dir, float cutoff, const cube &c, const ivec &co, int size) // collide with solid cube geometry
775 : {
776 0 : vec cwall(0, 0, 0);
777 0 : int crad = size/2;
778 0 : if(std::fabs(d->o.x - co.x - crad) > d->radius + crad || std::fabs(d->o.y - co.y - crad) > d->radius + crad ||
779 0 : d->o.z + d->aboveeye < co.z || d->o.z - d->eyeheight > co.z + size)
780 : {
781 0 : return {false, cwall};
782 : }
783 0 : float bestdist = -1e10f;
784 0 : int visible = !(c.visible&0x80) || d->type==physent::PhysEnt_Player ? c.visible : 0xFF;
785 :
786 : //if any of these checks are false (NAND of all of these checks)
787 0 : if(!( checkside(*d, Orient_Left, dir, visible, cutoff, co.x - (d->o.x + d->radius), -dir.x, -d->radius, vec(-1, 0, 0), cwall, bestdist)
788 0 : && checkside(*d, Orient_Right, dir, visible, cutoff, d->o.x - d->radius - (co.x + size), dir.x, -d->radius, vec(1, 0, 0), cwall, bestdist)
789 0 : && checkside(*d, Orient_Back, dir, visible, cutoff, co.y - (d->o.y + d->radius), -dir.y, -d->radius, vec(0, -1, 0), cwall, bestdist)
790 0 : && checkside(*d, Orient_Front, dir, visible, cutoff, d->o.y - d->radius - (co.y + size), dir.y, -d->radius, vec(0, 1, 0), cwall, bestdist)
791 0 : && checkside(*d, Orient_Bottom, dir, visible, cutoff, co.z - (d->o.z + d->aboveeye), -dir.z, d->zmargin-(d->eyeheight+d->aboveeye)/4.0f, vec(0, 0, -1), cwall, bestdist)
792 0 : && checkside(*d, Orient_Top, dir, visible, cutoff, d->o.z - d->eyeheight - (co.z + size), dir.z, d->zmargin-(d->eyeheight+d->aboveeye)/3.0f, vec(0, 0, 1), cwall, bestdist))
793 : )
794 : {
795 0 : return {false, cwall};
796 : }
797 0 : if(cwall.iszero())
798 : {
799 0 : collideinside++;
800 0 : return {false, cwall};
801 : }
802 0 : return {true, cwall};
803 : }
804 :
805 : template<class E>
806 0 : static bool clampcollide(const clipplanes &p, const E &entvol, const plane &w, const vec &pw)
807 : {
808 0 : if(w.x && (w.y || w.z) && std::fabs(pw.x - p.o.x) > p.r.x)
809 : {
810 0 : vec c = entvol.center();
811 0 : float fv = pw.x < p.o.x ? p.o.x-p.r.x : p.o.x+p.r.x,
812 0 : fdist = (w.x*fv + w.y*c.y + w.z*c.z + w.offset) / (w.y*w.y + w.z*w.z);
813 0 : vec fdir(fv - c.x, -w.y*fdist, -w.z*fdist);
814 0 : if((pw.y-c.y-fdir.y)*w.y + (pw.z-c.z-fdir.z)*w.z >= 0 && entvol.supportpoint(fdir).squaredist(c) < fdir.squaredlen())
815 : {
816 0 : return true;
817 : }
818 : }
819 0 : if(w.y && (w.x || w.z) && std::fabs(pw.y - p.o.y) > p.r.y)
820 : {
821 0 : vec c = entvol.center();
822 0 : float fv = pw.y < p.o.y ? p.o.y-p.r.y : p.o.y+p.r.y,
823 0 : fdist = (w.x*c.x + w.y*fv + w.z*c.z + w.offset) / (w.x*w.x + w.z*w.z);
824 0 : vec fdir(-w.x*fdist, fv - c.y, -w.z*fdist);
825 0 : if((pw.x-c.x-fdir.x)*w.x + (pw.z-c.z-fdir.z)*w.z >= 0 && entvol.supportpoint(fdir).squaredist(c) < fdir.squaredlen())
826 : {
827 0 : return true;
828 : }
829 : }
830 0 : if(w.z && (w.x || w.y) && std::fabs(pw.z - p.o.z) > p.r.z)
831 : {
832 0 : vec c = entvol.center();
833 0 : float fv = pw.z < p.o.z ? p.o.z-p.r.z : p.o.z+p.r.z,
834 0 : fdist = (w.x*c.x + w.y*c.y + w.z*fv + w.offset) / (w.x*w.x + w.y*w.y);
835 0 : vec fdir(-w.x*fdist, -w.y*fdist, fv - c.z);
836 0 : if((pw.x-c.x-fdir.x)*w.x + (pw.y-c.y-fdir.y)*w.y >= 0 && entvol.supportpoint(fdir).squaredist(c) < fdir.squaredlen())
837 : {
838 0 : return true;
839 : }
840 : }
841 0 : return false;
842 : }
843 :
844 0 : static CollisionInfo fuzzycollideplanes(const physent *d, const vec &dir, float cutoff, const cube &c, const ivec &co, int size) // collide with deformed cube geometry
845 : {
846 0 : vec cwall(0, 0, 0);
847 :
848 0 : clipplanes &p = getclipbounds(c, co, size, *d);
849 :
850 0 : if(std::fabs(d->o.x - p.o.x) > p.r.x + d->radius || std::fabs(d->o.y - p.o.y) > p.r.y + d->radius ||
851 0 : d->o.z + d->aboveeye < p.o.z - p.r.z || d->o.z - d->eyeheight > p.o.z + p.r.z)
852 : {
853 0 : return {false, cwall};
854 : }
855 0 : float bestdist = -1e10f;
856 0 : int visible = forceclipplanes(c, co, size, p);
857 :
858 0 : if(!( checkside(*d, Orient_Left, dir, visible, cutoff, p.o.x - p.r.x - (d->o.x + d->radius), -dir.x, -d->radius, vec(-1, 0, 0), cwall, bestdist)
859 0 : && checkside(*d, Orient_Right, dir, visible, cutoff, d->o.x - d->radius - (p.o.x + p.r.x), dir.x, -d->radius, vec(1, 0, 0), cwall, bestdist)
860 0 : && checkside(*d, Orient_Back, dir, visible, cutoff, p.o.y - p.r.y - (d->o.y + d->radius), -dir.y, -d->radius, vec(0, -1, 0), cwall, bestdist)
861 0 : && checkside(*d, Orient_Front, dir, visible, cutoff, d->o.y - d->radius - (p.o.y + p.r.y), dir.y, -d->radius, vec(0, 1, 0), cwall, bestdist)
862 0 : && checkside(*d, Orient_Bottom, dir, visible, cutoff, p.o.z - p.r.z - (d->o.z + d->aboveeye), -dir.z, d->zmargin-(d->eyeheight+d->aboveeye)/4.0f, vec(0, 0, -1), cwall, bestdist)
863 0 : && checkside(*d, Orient_Top, dir, visible, cutoff, d->o.z - d->eyeheight - (p.o.z + p.r.z), dir.z, d->zmargin-(d->eyeheight+d->aboveeye)/3.0f, vec(0, 0, 1), cwall, bestdist))
864 : )
865 : {
866 0 : return {false, cwall};
867 : }
868 :
869 0 : mpr::EntCapsule entvol(d);
870 0 : int bestplane = -1;
871 0 : for(int i = 0; i < p.size; ++i)
872 : {
873 0 : const plane &w = p.p[i];
874 0 : vec pw = entvol.supportpoint(vec(w).neg());
875 0 : float dist = w.dist(pw);
876 0 : if(dist >= 0)
877 : {
878 0 : return {false, cwall};
879 : }
880 0 : if(dist <= bestdist)
881 : {
882 0 : continue;
883 : }
884 0 : bestplane = -1;
885 0 : bestdist = dist;
886 0 : if(!dir.iszero())
887 : {
888 0 : if(w.dot(dir) >= -cutoff*dir.magnitude())
889 : {
890 0 : continue;
891 : }
892 : //nasty ternary
893 0 : if(d->type==physent::PhysEnt_Player &&
894 0 : dist < (dir.z*w.z < 0 ?
895 0 : d->zmargin-(d->eyeheight+d->aboveeye)/(dir.z < 0 ? 3.0f : 4.0f) :
896 0 : (dir.x*w.x < 0 || dir.y*w.y < 0 ? -d->radius : 0)))
897 : {
898 0 : continue;
899 : }
900 : }
901 0 : if(clampcollide(p, entvol, w, pw))
902 : {
903 0 : continue;
904 : }
905 0 : bestplane = i;
906 : }
907 :
908 0 : if(bestplane >= 0)
909 : {
910 0 : cwall = p.p[bestplane];
911 : }
912 0 : else if(cwall.iszero())
913 : {
914 0 : collideinside++;
915 0 : return {false, cwall};
916 : }
917 0 : return {true, cwall};
918 : }
919 :
920 : //cwall -> collide wall
921 0 : static CollisionInfo cubecollidesolid(const physent *d, const vec &dir, float cutoff, const cube &c, const ivec &co, int size) // collide with solid cube geometry
922 : {
923 0 : vec cwall(0, 0, 0);
924 0 : int crad = size/2;
925 0 : if(std::fabs(d->o.x - co.x - crad) > d->radius + crad || std::fabs(d->o.y - co.y - crad) > d->radius + crad ||
926 0 : d->o.z + d->aboveeye < co.z || d->o.z - d->eyeheight > co.z + size)
927 : {
928 0 : return {false, cwall};
929 : }
930 0 : mpr::EntOBB entvol(d);
931 0 : bool collided = mpr::collide(mpr::SolidCube(co, size), entvol);
932 0 : if(!collided)
933 : {
934 0 : return {false, cwall};
935 : }
936 0 : float bestdist = -1e10f;
937 0 : int visible = !(c.visible&0x80) || d->type==physent::PhysEnt_Player ? c.visible : 0xFF;
938 :
939 0 : if(!( checkside(*d, Orient_Left, dir, visible, cutoff, co.x - entvol.right(), -dir.x, -d->radius, vec(-1, 0, 0), cwall, bestdist)
940 0 : && checkside(*d, Orient_Right, dir, visible, cutoff, entvol.left() - (co.x + size), dir.x, -d->radius, vec(1, 0, 0), cwall, bestdist)
941 0 : && checkside(*d, Orient_Back, dir, visible, cutoff, co.y - entvol.front(), -dir.y, -d->radius, vec(0, -1, 0), cwall, bestdist)
942 0 : && checkside(*d, Orient_Front, dir, visible, cutoff, entvol.back() - (co.y + size), dir.y, -d->radius, vec(0, 1, 0), cwall, bestdist)
943 0 : && checkside(*d, Orient_Bottom, dir, visible, cutoff, co.z - entvol.top(), -dir.z, d->zmargin-(d->eyeheight+d->aboveeye)/4.0f, vec(0, 0, -1), cwall, bestdist)
944 0 : && checkside(*d, Orient_Top, dir, visible, cutoff, entvol.bottom() - (co.z + size), dir.z, d->zmargin-(d->eyeheight+d->aboveeye)/3.0f, vec(0, 0, 1), cwall, bestdist))
945 : )
946 : {
947 0 : return {false, cwall};
948 : }
949 :
950 0 : if(cwall.iszero())
951 : {
952 0 : collideinside++;
953 0 : return {false, cwall};
954 : }
955 0 : return {true, cwall};
956 : }
957 :
958 : //cwall -> collide wall
959 0 : static CollisionInfo cubecollideplanes(const physent *d, const vec &dir, float cutoff, const cube &c, const ivec &co, int size) // collide with deformed cube geometry
960 : {
961 0 : vec cwall(0, 0, 0);
962 0 : clipplanes &p = getclipbounds(c, co, size, *d);
963 0 : if(std::fabs(d->o.x - p.o.x) > p.r.x + d->radius || std::fabs(d->o.y - p.o.y) > p.r.y + d->radius ||
964 0 : d->o.z + d->aboveeye < p.o.z - p.r.z || d->o.z - d->eyeheight > p.o.z + p.r.z)
965 : {
966 0 : return {false, cwall};
967 : }
968 0 : mpr::EntOBB entvol(d);
969 0 : bool collided = mpr::collide(mpr::CubePlanes(p), entvol);
970 0 : if(!collided)
971 : {
972 0 : return {false, cwall};
973 : }
974 0 : float bestdist = -1e10f;
975 0 : int visible = forceclipplanes(c, co, size, p);
976 0 : if(!( checkside(*d, Orient_Left, dir, visible, cutoff, p.o.x - p.r.x - entvol.right(), -dir.x, -d->radius, vec(-1, 0, 0), cwall, bestdist)
977 0 : && checkside(*d, Orient_Right, dir, visible, cutoff, entvol.left() - (p.o.x + p.r.x), dir.x, -d->radius, vec(1, 0, 0), cwall, bestdist)
978 0 : && checkside(*d, Orient_Back, dir, visible, cutoff, p.o.y - p.r.y - entvol.front(), -dir.y, -d->radius, vec(0, -1, 0), cwall, bestdist)
979 0 : && checkside(*d, Orient_Front, dir, visible, cutoff, entvol.back() - (p.o.y + p.r.y), dir.y, -d->radius, vec(0, 1, 0), cwall, bestdist)
980 0 : && checkside(*d, Orient_Bottom, dir, visible, cutoff, p.o.z - p.r.z - entvol.top(), -dir.z, d->zmargin-(d->eyeheight+d->aboveeye)/4.0f, vec(0, 0, -1), cwall, bestdist)
981 0 : && checkside(*d, Orient_Top, dir, visible, cutoff, entvol.bottom() - (p.o.z + p.r.z), dir.z, d->zmargin-(d->eyeheight+d->aboveeye)/3.0f, vec(0, 0, 1), cwall, bestdist))
982 : )
983 : {
984 0 : return {false, cwall};
985 : }
986 :
987 0 : int bestplane = -1;
988 0 : for(int i = 0; i < p.size; ++i)
989 : {
990 0 : const plane &w = p.p[i];
991 0 : vec pw = entvol.supportpoint(vec(w).neg());
992 0 : float dist = w.dist(pw);
993 0 : if(dist <= bestdist)
994 : {
995 0 : continue;
996 : }
997 0 : bestplane = -1;
998 0 : bestdist = dist;
999 0 : if(!dir.iszero())
1000 : {
1001 0 : if(w.dot(dir) >= -cutoff*dir.magnitude())
1002 : {
1003 0 : continue;
1004 : }
1005 0 : if(d->type==physent::PhysEnt_Player &&
1006 0 : dist < (dir.z*w.z < 0 ?
1007 0 : d->zmargin-(d->eyeheight+d->aboveeye)/(dir.z < 0 ? 3.0f : 4.0f) :
1008 0 : (dir.x*w.x < 0 || dir.y*w.y < 0 ? -d->radius : 0)))
1009 : {
1010 0 : continue;
1011 : }
1012 : }
1013 0 : if(clampcollide(p, entvol, w, pw))
1014 : {
1015 0 : continue;
1016 : }
1017 0 : bestplane = i;
1018 : }
1019 :
1020 0 : if(bestplane >= 0)
1021 : {
1022 0 : cwall = p.p[bestplane];
1023 : }
1024 0 : else if(cwall.iszero())
1025 : {
1026 0 : collideinside++;
1027 0 : return {false, cwall};
1028 : }
1029 0 : return {true, cwall};
1030 : }
1031 :
1032 0 : static CollisionInfo cubecollide(const physent *d, const vec &dir, float cutoff, const cube &c, const ivec &co, int size, bool solid)
1033 : {
1034 0 : switch(d->collidetype)
1035 : {
1036 0 : case Collide_OrientedBoundingBox:
1037 : {
1038 0 : if(c.issolid() || solid)
1039 : {
1040 0 : return cubecollidesolid(d, dir, cutoff, c, co, size);
1041 : }
1042 : else
1043 : {
1044 0 : return cubecollideplanes(d, dir, cutoff, c, co, size);
1045 : }
1046 : }
1047 0 : case Collide_Ellipse:
1048 : {
1049 0 : if(c.issolid() || solid)
1050 : {
1051 0 : return fuzzycollidesolid(d, dir, cutoff, c, co, size);
1052 : }
1053 : else
1054 : {
1055 0 : return fuzzycollideplanes(d, dir, cutoff, c, co, size);
1056 : }
1057 : }
1058 0 : default:
1059 : {
1060 0 : return {false, vec(0,0,0)};
1061 : }
1062 : }
1063 : }
1064 :
1065 0 : static CollisionInfo octacollide(const physent *d, const vec &dir, float cutoff, const ivec &bo, const ivec &bs, const std::array<cube, 8> &c, const ivec &cor, int size) // collide with octants
1066 : {
1067 0 : LOOP_OCTA_BOX(cor, size, bo, bs)
1068 : {
1069 0 : if(c[i].ext && c[i].ext->ents)
1070 : {
1071 0 : CollisionInfo ci = mmcollide(d, dir, cutoff, *c[i].ext->ents);
1072 0 : if(ci.collided)
1073 : {
1074 0 : return ci;
1075 : }
1076 : }
1077 0 : ivec o(i, cor, size);
1078 0 : if(c[i].children)
1079 : {
1080 0 : CollisionInfo ci = octacollide(d, dir, cutoff, bo, bs, *(c[i].children), o, size>>1);
1081 0 : if(ci.collided)
1082 : {
1083 0 : return ci;
1084 : }
1085 : }
1086 : else
1087 : {
1088 0 : bool solid = false;
1089 0 : switch(c[i].material&MatFlag_Clip)
1090 : {
1091 0 : case Mat_NoClip:
1092 : {
1093 0 : continue;
1094 : }
1095 0 : case Mat_Clip:
1096 : {
1097 0 : if(IS_CLIPPED(c[i].material&MatFlag_Volume) || d->type==physent::PhysEnt_Player)
1098 : {
1099 0 : solid = true;
1100 : }
1101 0 : break;
1102 : }
1103 : }
1104 0 : if(!solid && c[i].isempty())
1105 : {
1106 0 : continue;
1107 : }
1108 0 : CollisionInfo ci = cubecollide(d, dir, cutoff, c[i], o, size, solid);
1109 0 : if(ci.collided)
1110 : {
1111 0 : return ci;
1112 : }
1113 : }
1114 : }
1115 0 : return {false, vec(0,0,0)};
1116 : }
1117 :
1118 : /**
1119 : * @brief Returns whether the entity passed has collided with this octaworld.
1120 : *
1121 : * @param d the physent to check
1122 : * @param dir the direction at which to check for a collision
1123 : * @param cutoff the model cutoff factor
1124 : * @param bo the vector for the minimum position of the model
1125 : * @param bs the vector for the maximum position of the model
1126 : * @param cw the cube octree world to check collision against
1127 : */
1128 0 : static CollisionInfo octacollide(const physent *d, const vec &dir, float cutoff, const ivec &bo, const ivec &bs, const cubeworld &cw)
1129 : {
1130 0 : int diff = (bo.x^bs.x) | (bo.y^bs.y) | (bo.z^bs.z),
1131 0 : scale = cw.mapscale()-1;
1132 0 : if(diff&~((1<<scale)-1) || static_cast<uint>(bo.x|bo.y|bo.z|bs.x|bs.y|bs.z) >= static_cast<uint>(cw.mapsize()))
1133 : {
1134 0 : return octacollide(d, dir, cutoff, bo, bs, *cw.worldroot, ivec(0, 0, 0), cw.mapsize()>>1);
1135 : }
1136 0 : const cube *c = &((*cw.worldroot)[OCTA_STEP(bo.x, bo.y, bo.z, scale)]);
1137 0 : if(c->ext && c->ext->ents)
1138 : {
1139 0 : if(CollisionInfo ci = mmcollide(d, dir, cutoff, *c->ext->ents); ci.collided)
1140 : {
1141 0 : return ci;
1142 : }
1143 : }
1144 0 : scale--;
1145 0 : while(c->children && !(diff&(1<<scale)))
1146 : {
1147 0 : c = &((*c->children)[OCTA_STEP(bo.x, bo.y, bo.z, scale)]);
1148 0 : if(c->ext && c->ext->ents)
1149 : {
1150 0 : if(CollisionInfo ci = mmcollide(d, dir, cutoff, *c->ext->ents); ci.collided)
1151 : {
1152 0 : return ci;
1153 : }
1154 : }
1155 0 : scale--;
1156 : }
1157 0 : if(c->children)
1158 : {
1159 0 : return ::octacollide(d, dir, cutoff, bo, bs, *c->children, ivec(bo).mask(~((2<<scale)-1)), 1<<scale);
1160 : }
1161 0 : bool solid = false;
1162 0 : switch(c->material&MatFlag_Clip)
1163 : {
1164 0 : case Mat_NoClip:
1165 : {
1166 0 : return {false, vec(0,0,0)};
1167 : }
1168 0 : case Mat_Clip:
1169 : {
1170 0 : if(IS_CLIPPED(c->material&MatFlag_Volume) || d->type==physent::PhysEnt_Player)
1171 : {
1172 0 : solid = true;
1173 : }
1174 0 : break;
1175 : }
1176 : }
1177 0 : if(!solid && c->isempty())
1178 : {
1179 0 : return {false, vec(0,0,0)};
1180 : }
1181 0 : int csize = 2<<scale,
1182 0 : cmask = ~(csize-1);
1183 0 : return cubecollide(d, dir, cutoff, *c, ivec(bo).mask(cmask), csize, solid);
1184 : }
1185 :
1186 : // all collision happens here
1187 : //
1188 :
1189 : //used in iengine
1190 0 : bool collide(const physent *d, vec *cwall, const vec &dir, float cutoff, bool insideplayercol)
1191 : {
1192 0 : collideinside = 0;
1193 0 : collideplayer = nullptr;
1194 0 : ivec bo(static_cast<int>(d->o.x-d->radius), static_cast<int>(d->o.y-d->radius), static_cast<int>(d->o.z-d->eyeheight)),
1195 0 : bs(static_cast<int>(d->o.x+d->radius), static_cast<int>(d->o.y+d->radius), static_cast<int>(d->o.z+d->aboveeye));
1196 0 : bo.sub(1);
1197 0 : bs.add(1); // guard space for rounding errors
1198 :
1199 0 : if(CollisionInfo ci = octacollide(d, dir, cutoff, bo, bs, rootworld); ci.collided)
1200 : {
1201 0 : if(cwall)
1202 : {
1203 0 : *cwall = ci.collidewall;
1204 : }
1205 0 : return ci.collided;
1206 : }
1207 : else
1208 : {
1209 0 : ci = plcollide(d, dir, insideplayercol);
1210 0 : if(cwall)
1211 : {
1212 0 : *cwall = ci.collidewall;
1213 : }
1214 0 : return ci.collided;
1215 : }
1216 : }
1217 :
1218 : //used in iengine
1219 0 : void recalcdir(const physent *d, const vec &oldvel, vec &dir)
1220 : {
1221 0 : float speed = oldvel.magnitude();
1222 0 : if(speed > 1e-6f)
1223 : {
1224 0 : float step = dir.magnitude();
1225 0 : dir = d->vel;
1226 0 : dir.add(d->falling);
1227 0 : dir.mul(step/speed);
1228 : }
1229 0 : }
1230 :
1231 : //used in iengine
1232 0 : void slideagainst(physent *d, vec &dir, const vec &obstacle, bool foundfloor, bool slidecollide)
1233 : {
1234 0 : vec wall(obstacle);
1235 0 : if(foundfloor ? wall.z > 0 : slidecollide)
1236 : {
1237 0 : wall.z = 0;
1238 0 : if(!wall.iszero())
1239 : {
1240 0 : wall.normalize();
1241 : }
1242 : }
1243 0 : vec oldvel(d->vel);
1244 0 : oldvel.add(d->falling);
1245 0 : d->vel.project(wall);
1246 0 : d->falling.project(wall);
1247 0 : recalcdir(d, oldvel, dir);
1248 0 : }
1249 :
1250 : //used in iengine
1251 0 : void avoidcollision(physent *d, const vec &dir, const physent *obstacle, float space)
1252 : {
1253 0 : float rad = obstacle->radius+d->radius;
1254 0 : vec bbmin(obstacle->o);
1255 0 : bbmin.x -= rad;
1256 0 : bbmin.y -= rad;
1257 0 : bbmin.z -= obstacle->eyeheight+d->aboveeye;
1258 0 : bbmin.sub(space);
1259 0 : vec bbmax(obstacle->o);
1260 0 : bbmax.x += rad;
1261 0 : bbmax.y += rad;
1262 0 : bbmax.z += obstacle->aboveeye+d->eyeheight;
1263 0 : bbmax.add(space);
1264 :
1265 0 : for(int i = 0; i < 3; ++i)
1266 : {
1267 0 : if(d->o[i] <= bbmin[i] || d->o[i] >= bbmax[i])
1268 : {
1269 0 : return;
1270 : }
1271 : }
1272 :
1273 0 : float mindist = 1e16f;
1274 0 : for(int i = 0; i < 3; ++i)
1275 : {
1276 0 : if(dir[i] != 0)
1277 : {
1278 0 : float dist = ((dir[i] > 0 ? bbmax[i] : bbmin[i]) - d->o[i]) / dir[i];
1279 0 : mindist = std::min(mindist, dist);
1280 : }
1281 : }
1282 0 : if(mindist >= 0.0f && mindist < 1e15f)
1283 : {
1284 0 : d->o.add(static_cast<vec>(dir).mul(mindist));
1285 : }
1286 : }
1287 :
1288 : //used in iengine
1289 0 : bool movecamera(physent *pl, const vec &dir, float dist, float stepdist)
1290 : {
1291 0 : int steps = static_cast<int>(ceil(dist/stepdist));
1292 0 : if(steps <= 0)
1293 : {
1294 0 : return true;
1295 : }
1296 0 : vec d(dir);
1297 0 : d.mul(dist/steps);
1298 0 : for(int i = 0; i < steps; ++i)
1299 : {
1300 0 : vec oldpos(pl->o);
1301 0 : pl->o.add(d);
1302 0 : if(collide(pl, nullptr, vec(0, 0, 0), 0, false))
1303 : {
1304 0 : pl->o = oldpos;
1305 0 : return false;
1306 : }
1307 : }
1308 0 : return true;
1309 : }
1310 :
1311 0 : static bool droptofloor(vec &o, float radius, float height)
1312 : {
1313 : struct dropent final : physent
1314 : {
1315 0 : dropent()
1316 0 : {
1317 0 : type = PhysEnt_Bounce;
1318 0 : vel = vec(0, 0, -1);
1319 0 : }
1320 : };
1321 0 : static dropent d;
1322 0 : d.o = o;
1323 0 : if(!insideworld(d.o))
1324 : {
1325 0 : if(d.o.z < rootworld.mapsize())
1326 : {
1327 0 : return false;
1328 : }
1329 0 : d.o.z = rootworld.mapsize() - 1e-3f;
1330 0 : if(!insideworld(d.o))
1331 : {
1332 0 : return false;
1333 : }
1334 : }
1335 0 : vec v(0.0001f, 0.0001f, -1);
1336 0 : v.normalize();
1337 0 : if(rootworld.raycube(d.o, v, rootworld.mapsize()) >= rootworld.mapsize())
1338 : {
1339 0 : return false;
1340 : }
1341 0 : d.radius = d.xradius = d.yradius = radius;
1342 0 : d.eyeheight = height;
1343 0 : d.aboveeye = radius;
1344 0 : if(!movecamera(&d, d.vel, rootworld.mapsize(), 1))
1345 : {
1346 0 : o = d.o;
1347 0 : return true;
1348 : }
1349 0 : return false;
1350 : }
1351 :
1352 0 : static float dropheight(const entity &e)
1353 : {
1354 0 : switch(e.type)
1355 : {
1356 0 : case EngineEnt_Particles:
1357 : case EngineEnt_Mapmodel:
1358 : {
1359 0 : return 0.0f;
1360 : }
1361 0 : default:
1362 : {
1363 0 : return 4.0f;
1364 : }
1365 : }
1366 : }
1367 :
1368 : //used in iengine
1369 0 : void dropenttofloor(entity *e)
1370 : {
1371 0 : droptofloor(e->o, 1.0f, dropheight(*e));
1372 0 : }
1373 :
1374 : //used in iengine
1375 0 : void vecfromyawpitch(float yaw, float pitch, int move, int strafe, vec &m)
1376 : {
1377 0 : if(move)
1378 : {
1379 0 : m.x = move*-std::sin(yaw/RAD);
1380 0 : m.y = move*std::cos(yaw/RAD);
1381 : }
1382 : else
1383 : {
1384 0 : m.x = m.y = 0;
1385 : }
1386 :
1387 0 : if(pitch)
1388 : {
1389 0 : m.x *= std::cos(pitch/RAD);
1390 0 : m.y *= std::cos(pitch/RAD);
1391 0 : m.z = move*std::sin(pitch/RAD);
1392 : }
1393 : else
1394 : {
1395 0 : m.z = 0;
1396 : }
1397 :
1398 0 : if(strafe)
1399 : {
1400 0 : m.x += strafe*std::cos(yaw/RAD);
1401 0 : m.y += strafe*std::sin(yaw/RAD);
1402 : }
1403 0 : }
1404 :
1405 : //used in iengine
1406 0 : bool entinmap(dynent *d, bool avoidplayers) // brute force but effective way to find a free spawn spot in the map
1407 : {
1408 0 : d->o.z += d->eyeheight; // pos specified is at feet
1409 0 : vec orig = d->o;
1410 : // try max 100 times
1411 0 : for(int i = 0; i < 100; ++i)
1412 : {
1413 0 : if(i)
1414 : {
1415 0 : d->o = orig;
1416 0 : d->o.x += (randomint(21)-10)*i/5; // increasing distance
1417 0 : d->o.y += (randomint(21)-10)*i/5;
1418 0 : d->o.z += (randomint(21)-10)*i/5;
1419 : }
1420 0 : if(!collide(d) && !collideinside)
1421 : {
1422 0 : if(collideplayer)
1423 : {
1424 0 : if(!avoidplayers)
1425 : {
1426 0 : continue;
1427 : }
1428 0 : d->o = orig;
1429 0 : d->resetinterp();
1430 0 : return false;
1431 : }
1432 :
1433 0 : d->resetinterp();
1434 0 : return true;
1435 : }
1436 : }
1437 : // leave ent at original pos, possibly stuck
1438 0 : d->o = orig;
1439 0 : d->resetinterp();
1440 0 : conoutf(Console_Warn, "can't find entity spawn spot! (%.1f, %.1f, %.1f)", d->o.x, d->o.y, d->o.z);
1441 0 : return false;
1442 : }
|