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