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