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