Initial Comit: First commit.
[SauerEngine.git] / src / engine / physics.cpp
blobda3c5a77a93f9d45ebf8223c4fd8ee9aa9af2d16
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).
6 #include "pch.h"
7 #include "engine.h"
9 const int MAXCLIPPLANES = 1024;
11 clipplanes clipcache[MAXCLIPPLANES], *nextclip = clipcache;
13 static inline void setcubeclip(cube &c, int x, int y, int z, int size)
15 if(!c.ext || !c.ext->clip || c.ext->clip->owner!=&c)
17 if(nextclip >= &clipcache[MAXCLIPPLANES]) nextclip = clipcache;
18 ext(c).clip = nextclip;
19 nextclip->owner = &c;
20 genclipplanes(c, x, y, z, size, *nextclip);
21 nextclip++;
25 void freeclipplanes(cube &c)
27 if(!c.ext || !c.ext->clip) return;
28 if(c.ext->clip->owner==&c) c.ext->clip->owner = NULL;
29 c.ext->clip = NULL;
32 ///////////////////////// ray - cube collision ///////////////////////////////////////////////
34 static inline void pushvec(vec &o, const vec &ray, float dist)
36 vec d(ray);
37 d.mul(dist);
38 o.add(d);
41 static inline bool pointinbox(const vec &v, const vec &bo, const vec &br)
43 return v.x <= bo.x+br.x &&
44 v.x >= bo.x-br.x &&
45 v.y <= bo.y+br.y &&
46 v.y >= bo.y-br.y &&
47 v.z <= bo.z+br.z &&
48 v.z >= bo.z-br.z;
51 bool pointincube(const clipplanes &p, const vec &v)
53 if(!pointinbox(v, p.o, p.r)) return false;
54 loopi(p.size) if(p.p[i].dist(v)>1e-3f) return false;
55 return true;
58 #define INTERSECTPLANES(setentry) \
59 clipplanes &p = *c.ext->clip; \
60 float enterdist = -1e16f, exitdist = 1e16f; \
61 loopi(p.size) \
62 { \
63 float pdist = p.p[i].dist(o), facing = ray.dot(p.p[i]); \
64 if(facing < 0) \
65 { \
66 pdist /= -facing; \
67 if(pdist > enterdist) \
68 { \
69 if(pdist > exitdist) return false; \
70 enterdist = pdist; \
71 setentry; \
72 } \
73 } \
74 else if(facing > 0) \
75 { \
76 pdist /= -facing; \
77 if(pdist < exitdist) \
78 { \
79 if(pdist < enterdist) return false; \
80 exitdist = pdist; \
81 } \
82 } \
83 else if(pdist > 0) return false; \
86 // optimized shadow version
87 bool shadowcubeintersect(const cube &c, const vec &o, const vec &ray, float &dist)
89 INTERSECTPLANES({});
90 if(exitdist < 0) return false;
91 enterdist += 0.1f;
92 if(enterdist < 0) enterdist = 0;
93 if(!pointinbox(vec(ray).mul(enterdist).add(o), p.o, p.r)) return false;
94 dist = enterdist;
95 return true;
98 vec hitsurface;
100 bool raycubeintersect(const cube &c, const vec &o, const vec &ray, float &dist)
102 int entry = -1, bbentry = -1;
103 INTERSECTPLANES(entry = i);
104 loop(i, 3)
106 if(ray[i])
108 float pdist = ((ray[i] > 0 ? p.o[i]-p.r[i] : p.o[i]+p.r[i]) - o[i]) / ray[i];
109 if(pdist > enterdist)
111 if(pdist > exitdist) return false;
112 enterdist = pdist;
113 bbentry = i;
115 pdist += 2*p.r[i]/fabs(ray[i]);
116 if(pdist < exitdist)
118 if(pdist < enterdist) return false;
119 exitdist = pdist;
122 else if(o[i] < p.o[i]-p.r[i] || o[i] > p.o[i]+p.r[i]) return false;
124 if(exitdist < 0) return false;
125 dist = max(enterdist+0.1f, 0.0f);
126 if(bbentry>=0) { hitsurface = vec(0, 0, 0); hitsurface[bbentry] = ray[bbentry]>0 ? -1 : 1; }
127 else hitsurface = p.p[entry];
128 return true;
131 extern void entselectionbox(const entity &e, vec &eo, vec &es);
132 extern int entselradius;
133 float hitentdist;
134 int hitent, hitorient;
136 static float disttoent(octaentities *oc, octaentities *last, const vec &o, const vec &ray, float radius, int mode, extentity *t)
138 vec eo, es;
139 int orient;
140 float dist = 1e16f, f = 0.0f;
141 if(oc == last || oc == NULL) return dist;
142 const vector<extentity *> &ents = et->getents();
144 #define entintersect(mask, type, func) {\
145 if((mode&(mask))==(mask)) \
147 loopv(oc->type) \
148 if(!last || last->type.find(oc->type[i])<0) \
150 extentity &e = *ents[oc->type[i]]; \
151 if(!e.inoctanode || &e==t) continue; \
152 func; \
153 if(f<dist && f>0) { \
154 hitentdist = dist = f; \
155 hitent = oc->type[i]; \
156 hitorient = orient; \
162 entintersect(RAY_POLY, mapmodels,
163 if(e.attr3 && (e.triggerstate == TRIGGER_DISAPPEARED || !checktriggertype(e.attr3, TRIG_COLLIDE) || e.triggerstate == TRIGGERED) && (mode&RAY_ENTS)!=RAY_ENTS) continue;
164 orient = 0; // FIXME, not set
165 if(!mmintersect(e, o, ray, radius, mode, f)) continue;
168 entintersect(RAY_ENTS, other,
169 entselectionbox(e, eo, es);
170 if(!rayrectintersect(eo, es, o, ray, f, orient)) continue;
173 entintersect(RAY_ENTS, mapmodels,
174 entselectionbox(e, eo, es);
175 if(!rayrectintersect(eo, es, o, ray, f, orient)) continue;
178 return dist;
181 // optimized shadow version
182 static float shadowent(octaentities *oc, octaentities *last, const vec &o, const vec &ray, float radius, int mode, extentity *t)
184 float dist = 1e16f, f = 0.0f;
185 if(oc == last || oc == NULL) return dist;
186 const vector<extentity *> &ents = et->getents();
187 loopv(oc->mapmodels) if(!last || last->mapmodels.find(oc->mapmodels[i])<0)
189 extentity &e = *ents[oc->mapmodels[i]];
190 if(!e.inoctanode || &e==t) continue;
191 if(e.attr3 && (e.triggerstate == TRIGGER_DISAPPEARED || !checktriggertype(e.attr3, TRIG_COLLIDE) || e.triggerstate == TRIGGERED)) continue;
192 if(!mmintersect(e, o, ray, radius, mode, f)) continue;
193 if(f>0 && f<dist) dist = f;
195 return dist;
198 #define INITRAYCUBE \
199 octaentities *oclast = NULL; \
200 float dist = 0, dent = mode&RAY_BB ? 1e16f : 1e14f; \
201 vec v(o), invray(ray.x ? 1/ray.x : 1e16f, ray.y ? 1/ray.y : 1e16f, ray.z ? 1/ray.z : 1e16f); \
202 static cube *levels[32]; \
203 levels[worldscale] = worldroot; \
204 int lshift = worldscale; \
205 ivec lsizemask(invray.x>0 ? 1 : 0, invray.y>0 ? 1 : 0, invray.z>0 ? 1 : 0); \
207 #define CHECKINSIDEWORLD \
208 if(!insideworld(o)) \
210 float disttoworld = 0, exitworld = 1e16f; \
211 loopi(3) \
213 float c = v[i]; \
214 if(c<0 || c>=hdr.worldsize) \
216 float d = ((invray[i]>0?0:hdr.worldsize)-c)*invray[i]; \
217 if(d<0) return (radius>0?radius:-1); \
218 disttoworld = max(disttoworld, 0.1f + d); \
220 float e = ((invray[i]>0?hdr.worldsize:0)-c)*invray[i]; \
221 exitworld = min(exitworld, e); \
223 if(disttoworld > exitworld) return (radius>0?radius:-1); \
224 pushvec(v, ray, disttoworld); \
225 dist += disttoworld; \
228 #define DOWNOCTREE(disttoent, earlyexit) \
229 cube *lc = levels[lshift]; \
230 for(;;) \
232 lshift--; \
233 lc += octastep(x, y, z, lshift); \
234 if(lc->ext && lc->ext->ents && dent > 1e15f) \
236 dent = disttoent(lc->ext->ents, oclast, o, ray, radius, mode, t); \
237 if(dent < 1e15f earlyexit) return min(dent, dist); \
238 oclast = lc->ext->ents; \
240 if(lc->children==NULL) break; \
241 lc = lc->children; \
242 levels[lshift] = lc; \
245 #define FINDCLOSEST(xclosest, yclosest, zclosest) \
246 float dx = (lo.x+(lsizemask.x<<lshift)-v.x)*invray.x, \
247 dy = (lo.y+(lsizemask.y<<lshift)-v.y)*invray.y, \
248 dz = (lo.z+(lsizemask.z<<lshift)-v.z)*invray.z; \
249 float disttonext = dx; \
250 xclosest; \
251 if(dy < disttonext) { disttonext = dy; yclosest; } \
252 if(dz < disttonext) { disttonext = dz; zclosest; } \
253 disttonext += 0.1f; \
254 pushvec(v, ray, disttonext); \
255 dist += disttonext;
257 #define UPOCTREE(exitworld) \
258 x = int(v.x); \
259 y = int(v.y); \
260 z = int(v.z); \
261 uint diff = uint(lo.x^x)|uint(lo.y^y)|uint(lo.z^z); \
262 if(diff >= uint(hdr.worldsize)) exitworld; \
263 diff >>= lshift; \
264 if(!diff) exitworld; \
265 do \
267 lshift++; \
268 diff >>= 1; \
269 } while(diff);
271 float raycube(const vec &o, const vec &ray, float radius, int mode, int size, extentity *t)
273 if(ray.iszero()) return 0;
275 INITRAYCUBE;
276 CHECKINSIDEWORLD;
278 int closest = -1, x = int(v.x), y = int(v.y), z = int(v.z);
279 for(;;)
281 DOWNOCTREE(disttoent, && (mode&RAY_SHADOW));
283 int lsize = 1<<lshift;
285 cube &c = *lc;
286 if((dist>0 || !(mode&RAY_SKIPFIRST)) &&
287 (((mode&RAY_CLIPMAT) && c.ext && isclipped(c.ext->material&MATF_VOLUME)) ||
288 ((mode&RAY_EDITMAT) && c.ext && c.ext->material != MAT_AIR) ||
289 (!(mode&RAY_PASS) && lsize==size && !isempty(c)) ||
290 isentirelysolid(c) ||
291 dent < dist))
293 if(closest >= 0) { hitsurface = vec(0, 0, 0); hitsurface[closest] = ray[closest]>0 ? -1 : 1; }
294 return min(dent, dist);
297 ivec lo(x&(~0<<lshift), y&(~0<<lshift), z&(~0<<lshift));
299 if(!isempty(c))
301 float f = 0;
302 setcubeclip(c, lo.x, lo.y, lo.z, lsize);
303 if(raycubeintersect(c, v, ray, f) && (dist+f>0 || !(mode&RAY_SKIPFIRST)))
304 return min(dent, dist+f);
307 FINDCLOSEST(closest = 0, closest = 1, closest = 2);
309 if(radius>0 && dist>=radius) return min(dent, dist);
311 UPOCTREE(return min(dent, radius>0 ? radius : dist));
315 // optimized version for lightmap shadowing... every cycle here counts!!!
316 float shadowray(const vec &o, const vec &ray, float radius, int mode, extentity *t)
318 INITRAYCUBE;
319 CHECKINSIDEWORLD;
321 int x = int(v.x), y = int(v.y), z = int(v.z);
322 for(;;)
324 DOWNOCTREE(shadowent, );
326 cube &c = *lc;
327 if(isentirelysolid(c)) return dist;
329 ivec lo(x&(~0<<lshift), y&(~0<<lshift), z&(~0<<lshift));
331 if(!isempty(c))
333 float f = 0;
334 setcubeclip(c, lo.x, lo.y, lo.z, 1<<lshift);
335 if(shadowcubeintersect(c, v, ray, f)) return dist+f;
338 FINDCLOSEST( , , );
340 if(dist>=radius) return dist;
342 UPOCTREE(return radius);
346 float rayent(const vec &o, const vec &ray, vec &hitpos, float radius, int mode, int size, int &orient, int &ent)
348 hitent = -1;
349 float d = raycubepos(o, ray, hitpos, hitentdist = radius, mode, size);
350 orient = hitorient;
351 ent = (hitentdist == d) ? hitent : -1;
352 return d;
355 float raycubepos(const vec &o, const vec &ray, vec &hitpos, float radius, int mode, int size)
357 hitpos = ray;
358 float dist = raycube(o, ray, radius, mode, size);
359 hitpos.mul(dist);
360 hitpos.add(o);
361 return dist;
364 bool raycubelos(const vec &o, const vec &dest, vec &hitpos)
366 vec ray(dest);
367 ray.sub(o);
368 float mag = ray.magnitude();
369 ray.mul(1/mag);
370 float distance = raycubepos(o, ray, hitpos, mag, RAY_CLIPMAT|RAY_POLY);
371 return distance >= mag;
374 float rayfloor(const vec &o, vec &floor, int mode, float radius)
376 if(o.z<=0) return -1;
377 hitsurface = vec(0, 0, 1);
378 float dist = raycube(o, vec(0, 0, -1), radius, mode);
379 if(dist<0 || (radius>0 && dist>=radius)) return dist;
380 floor = hitsurface;
381 return dist;
384 ///////////////////////// entity collision ///////////////////////////////////////////////
386 // info about collisions
387 bool inside; // whether an internal collision happened
388 physent *hitplayer; // whether the collection hit a player
389 vec wall; // just the normal vectors.
390 float walldistance;
391 const float STAIRHEIGHT = 4.1f;
392 const float FLOORZ = 0.867f;
393 const float SLOPEZ = 0.5f;
394 const float WALLZ = 0.2f;
395 const float JUMPVEL = 125.0f;
396 const float GRAVITY = 200.0f;
397 const float STEPSPEED = 1.0f;
399 bool ellipsecollide(physent *d, const vec &dir, const vec &o, float yaw, float xr, float yr, float hi, float lo)
401 float below = (o.z-lo) - (d->o.z+d->aboveeye),
402 above = (d->o.z-d->eyeheight) - (o.z+hi);
403 if(below>=0 || above>=0) return true;
404 float x = o.x - d->o.x, y = o.y - d->o.y;
405 float angle = atan2f(y, x), dangle = angle-(d->yaw+90)*RAD, eangle = angle-(yaw+90)*RAD;
406 float dx = d->xradius*cosf(dangle), dy = d->yradius*sinf(dangle);
407 float ex = xr*cosf(eangle), ey = yr*sinf(eangle);
408 float dist = sqrtf(x*x + y*y) - sqrtf(dx*dx + dy*dy) - sqrtf(ex*ex + ey*ey);
409 if(dist < 0)
411 if(dist > (d->o.z < o.z ? below : above) && (dir.iszero() || x*dir.x + y*dir.y > 0))
413 wall = vec(-x, -y, 0);
414 if(!wall.iszero()) wall.normalize();
415 return false;
417 if(d->o.z < o.z)
419 if(dir.iszero() || (dir.z > 0 && (d->type>=ENT_INANIMATE || below >= d->zmargin-(d->eyeheight+d->aboveeye)/4.0f)))
421 wall = vec(0, 0, -1);
422 return false;
425 else if(dir.iszero() || (dir.z < 0 && (d->type>=ENT_INANIMATE || above >= d->zmargin-(d->eyeheight+d->aboveeye)/3.0f)))
427 wall = vec(0, 0, 1);
428 return false;
430 inside = true;
432 return true;
435 bool rectcollide(physent *d, const vec &dir, const vec &o, float xr, float yr, float hi, float lo, uchar visible = 0xFF, bool collideonly = true, float cutoff = 0)
437 if(collideonly && !visible) return true;
438 vec s(d->o);
439 s.sub(o);
440 float dxr = d->collidetype==COLLIDE_ELLIPSE ? d->radius : d->xradius, dyr = d->collidetype==COLLIDE_ELLIPSE ? d->radius : d->yradius;
441 xr += dxr;
442 yr += dyr;
443 walldistance = -1e10f;
444 float zr = s.z>0 ? d->eyeheight+hi : d->aboveeye+lo;
445 float ax = fabs(s.x)-xr;
446 float ay = fabs(s.y)-yr;
447 float az = fabs(s.z)-zr;
448 if(ax>0 || ay>0 || az>0) return true;
449 wall.x = wall.y = wall.z = 0;
450 #define TRYCOLLIDE(dim, ON, OP, N, P) \
452 if(s.dim<0) { if(visible&(1<<ON) && (dir.iszero() || (dir.dim>0 && (d->type>=ENT_INANIMATE || (N))))) { walldistance = a ## dim; wall.dim = -1; return false; } } \
453 else if(visible&(1<<OP) && (dir.iszero() || (dir.dim<0 && (d->type>=ENT_INANIMATE || (P))))) { walldistance = a ## dim; wall.dim = 1; return false; } \
455 if(ax>ay && ax>az) TRYCOLLIDE(x, O_LEFT, O_RIGHT, ax > -dxr, ax > -dxr);
456 if(ay>az) TRYCOLLIDE(y, O_BACK, O_FRONT, ay > -dyr, ay > -dyr);
457 TRYCOLLIDE(z, O_BOTTOM, O_TOP,
458 az >= d->zmargin-(d->eyeheight+d->aboveeye)/4.0f,
459 az >= d->zmargin-(d->eyeheight+d->aboveeye)/3.0f);
460 if(collideonly) inside = true;
461 return collideonly;
464 #define DYNENTCACHESIZE 1024
466 static uint dynentframe = 0;
468 static struct dynentcacheentry
470 int x, y;
471 uint frame;
472 vector<physent *> dynents;
473 } dynentcache[DYNENTCACHESIZE];
475 void cleardynentcache()
477 dynentframe++;
478 if(!dynentframe || dynentframe == 1) loopi(DYNENTCACHESIZE) dynentcache[i].frame = 0;
479 if(!dynentframe) dynentframe = 1;
482 VARF(dynentsize, 4, 7, 12, cleardynentcache());
484 #define DYNENTHASH(x, y) (((((x)^(y))<<5) + (((x)^(y))>>5)) & (DYNENTCACHESIZE - 1))
486 const vector<physent *> &checkdynentcache(int x, int y)
488 dynentcacheentry &dec = dynentcache[DYNENTHASH(x, y)];
489 if(dec.x == x && dec.y == y && dec.frame == dynentframe) return dec.dynents;
490 dec.x = x;
491 dec.y = y;
492 dec.frame = dynentframe;
493 dec.dynents.setsize(0);
494 int numdyns = cl->numdynents(), dsize = 1<<dynentsize, dx = x<<dynentsize, dy = y<<dynentsize;
495 loopi(numdyns)
497 dynent *d = cl->iterdynents(i);
498 if(!d || d->state != CS_ALIVE ||
499 d->o.x+d->radius <= dx || d->o.x-d->radius >= dx+dsize ||
500 d->o.y+d->radius <= dy || d->o.y-d->radius >= dy+dsize)
501 continue;
502 dec.dynents.add(d);
504 return dec.dynents;
507 #define loopdynentcache(curx, cury, o, radius) \
508 for(int curx = max(int(o.x-radius), 0)>>dynentsize, endx = min(int(o.x+radius), hdr.worldsize-1)>>dynentsize; curx <= endx; curx++) \
509 for(int cury = max(int(o.y-radius), 0)>>dynentsize, endy = min(int(o.y+radius), hdr.worldsize-1)>>dynentsize; cury <= endy; cury++)
511 void updatedynentcache(physent *d)
513 loopdynentcache(x, y, d->o, d->radius)
515 dynentcacheentry &dec = dynentcache[DYNENTHASH(x, y)];
516 if(dec.x != x || dec.y != y || dec.frame != dynentframe || dec.dynents.find(d) >= 0) continue;
517 dec.dynents.add(d);
521 bool overlapsdynent(const vec &o, float radius)
523 loopdynentcache(x, y, o, radius)
525 const vector<physent *> &dynents = checkdynentcache(x, y);
526 loopv(dynents)
528 physent *d = dynents[i];
529 if(o.dist(d->o)-d->radius < radius) return true;
532 return false;
535 bool plcollide(physent *d, const vec &dir) // collide with player or monster
537 if(d->type==ENT_CAMERA || d->state!=CS_ALIVE) return true;
538 loopdynentcache(x, y, d->o, d->radius)
540 const vector<physent *> &dynents = checkdynentcache(x, y);
541 loopv(dynents)
543 physent *o = dynents[i];
544 if(o==d || d->o.reject(o->o, d->radius+o->radius)) continue;
545 if(d->collidetype!=COLLIDE_ELLIPSE || o->collidetype!=COLLIDE_ELLIPSE)
547 if(!rectcollide(d, dir, o->o, o->collidetype==COLLIDE_ELLIPSE ? o->radius : o->xradius, o->collidetype==COLLIDE_ELLIPSE ? o->radius : o->yradius, o->aboveeye, o->eyeheight))
549 hitplayer = o;
550 if((d->type==ENT_AI || d->type==ENT_INANIMATE) && wall.z>0) d->onplayer = o;
551 return false;
554 else if(!ellipsecollide(d, dir, o->o, o->yaw, o->xradius, o->yradius, o->aboveeye, o->eyeheight))
556 hitplayer = o;
557 if((d->type==ENT_AI || d->type==ENT_INANIMATE) && wall.z>0) d->onplayer = o;
558 return false;
562 return true;
565 void rotatebb(vec &center, vec &radius, int yaw)
567 yaw = (yaw+7)-(yaw+7)%15;
568 yaw += 180;
569 if(yaw < 0) yaw = 360 + yaw%360;
570 else if(yaw >= 360) yaw %= 360;
571 switch(yaw)
573 case 0: break;
574 case 180:
575 center.x = -center.x;
576 center.y = -center.y;
577 break;
578 case 90:
579 swap(radius.x, radius.y);
580 swap(center.x, center.y);
581 center.x = -center.x;
582 break;
583 case 270:
584 swap(radius.x, radius.y);
585 swap(center.x, center.y);
586 center.y = -center.y;
587 break;
588 default:
589 radius.x = radius.y = max(radius.x, radius.y) + max(fabs(center.x), fabs(center.y));
590 center.x = center.y = 0.0f;
591 break;
595 bool mmcollide(physent *d, const vec &dir, octaentities &oc) // collide with a mapmodel
597 const vector<extentity *> &ents = et->getents();
598 loopv(oc.mapmodels)
600 extentity &e = *ents[oc.mapmodels[i]];
601 if(e.attr3 && e.attr3!=15 && (e.triggerstate == TRIGGER_DISAPPEARED || !checktriggertype(e.attr3, TRIG_COLLIDE) || e.triggerstate == TRIGGERED || (e.triggerstate == TRIGGERING && lastmillis-e.lasttrigger >= 500))) continue;
602 model *m = loadmodel(NULL, e.attr2);
603 if(!m || !m->collide) continue;
604 vec center, radius;
605 m->collisionbox(0, center, radius);
606 if(!m->ellipsecollide || d->collidetype!=COLLIDE_ELLIPSE)
608 rotatebb(center, radius, e.attr1);
609 if(!rectcollide(d, dir, center.add(e.o), radius.x, radius.y, radius.z, radius.z)) return false;
611 else if(!ellipsecollide(d, dir, center.add(e.o), float((e.attr1+7)-(e.attr1+7)%15), radius.x, radius.y, radius.z, radius.z)) return false;
613 return true;
616 bool cubecollide(physent *d, const vec &dir, float cutoff, cube &c, int x, int y, int z, int size, bool solid) // collide with cube geometry
618 if(solid || isentirelysolid(c))
620 int s2 = size>>1;
621 vec o = vec(x+s2, y+s2, z+s2);
622 vec r = vec(s2, s2, s2);
623 return rectcollide(d, dir, o, r.x, r.y, r.z, r.z, isentirelysolid(c) ? (c.ext ? c.ext->visible : 0) : 0xFF, true, cutoff);
626 setcubeclip(c, x, y, z, size);
627 clipplanes &p = *c.ext->clip;
629 float r = d->radius,
630 zr = (d->aboveeye+d->eyeheight)/2;
631 vec o(d->o), *w = &wall;
632 o.z += zr - d->eyeheight;
634 if(rectcollide(d, dir, p.o, p.r.x, p.r.y, p.r.z, p.r.z, c.ext->visible, !p.size, cutoff)) return true;
636 if(p.size)
638 if(!wall.iszero())
640 vec wo(o), wrad(r, r, zr);
641 loopi(3) if(wall[i]) { wo[i] = p.o[i]+wall[i]*p.r[i]; wrad[i] = 0; break; }
642 loopi(p.size)
644 plane &f = p.p[i];
645 if(!wall.dot(f)) continue;
646 if(f.dist(wo) >= vec(f.x*wrad.x, f.y*wrad.y, f.z*wrad.z).magnitude())
648 wall = vec(0, 0, 0);
649 walldistance = -1e10f;
650 break;
654 float m = walldistance;
655 loopi(p.size)
657 plane &f = p.p[i];
658 float dist = f.dist(o) - vec(f.x*r, f.y*r, f.z*zr).magnitude();
659 if(dist > 0) return true;
660 if(dist > m)
662 if(!dir.iszero())
664 if(f.dot(dir) >= -cutoff*dir.magnitude()) continue;
665 if(d->type<ENT_CAMERA &&
666 dist < (dir.z*f.z < 0 ?
667 d->zmargin-(d->eyeheight+d->aboveeye)/(dir.z < 0 ? 3.0f : 4.0f) :
668 ((dir.x*f.x < 0 || dir.y*f.y < 0) ? -r : 0)))
669 continue;
671 if(f.x && (f.x>0 ? o.x-p.o.x : p.o.x-o.x) + p.r.x - r < dist &&
672 f.dist(vec(p.o.x + (f.x>0 ? -p.r.x : p.r.x), o.y, o.z)) >= vec(0, f.y*r, f.z*zr).magnitude())
673 continue;
674 if(f.y && (f.y>0 ? o.y-p.o.y : p.o.y-o.y) + p.r.y - r < dist &&
675 f.dist(vec(o.x, p.o.y + (f.y>0 ? -p.r.y : p.r.y), o.z)) >= vec(f.x*r, 0, f.z*zr).magnitude())
676 continue;
677 if(f.z && (f.z>0 ? o.z-p.o.z : p.o.z-o.z) + p.r.z - zr < dist &&
678 f.dist(vec(o.x, o.y, p.o.z + (f.z>0 ? -p.r.z : p.r.z))) >= vec(f.x*r, f.y*r, 0).magnitude())
679 continue;
680 w = &f;
681 m = dist;
684 wall = *w;
685 if(wall.iszero())
687 inside = true;
688 return true;
691 return false;
694 static inline bool octacollide(physent *d, const vec &dir, float cutoff, const ivec &bo, const ivec &bs, cube *c, const ivec &cor, int size) // collide with octants
696 loopoctabox(cor, size, bo, bs)
698 if(c[i].ext && c[i].ext->ents) if(!mmcollide(d, dir, *c[i].ext->ents)) return false;
699 ivec o(i, cor.x, cor.y, cor.z, size);
700 if(c[i].children)
702 if(!octacollide(d, dir, cutoff, bo, bs, c[i].children, o, size>>1)) return false;
704 else
706 bool solid = false;
707 if(c[i].ext) switch(c[i].ext->material&MATF_CLIP)
709 case MAT_NOCLIP: continue;
710 case MAT_AICLIP: if(d->type==ENT_AI) solid = true; break;
711 case MAT_CLIP: if(isclipped(c[i].ext->material&MATF_VOLUME) || d->type<ENT_CAMERA) solid = true; break;
713 if(!solid && isempty(c[i])) continue;
714 if(!cubecollide(d, dir, cutoff, c[i], o.x, o.y, o.z, size, solid)) return false;
717 return true;
720 static inline bool octacollide(physent *d, const vec &dir, float cutoff, const ivec &bo, const ivec &bs)
722 int diff = (bo.x^(bo.x+bs.x)) | (bo.y^(bo.y+bs.y)) | (bo.z^(bo.z+bs.z)),
723 scale = worldscale-1;
724 if(diff&~((1<<scale)-1) || uint(bo.x|bo.y|bo.z|(bo.x+bs.x)|(bo.y+bs.y)|(bo.z+bs.z)) >= uint(hdr.worldsize))
725 return octacollide(d, dir, cutoff, bo, bs, worldroot, ivec(0, 0, 0), hdr.worldsize>>1);
726 cube *c = &worldroot[octastep(bo.x, bo.y, bo.z, scale)];
727 if(c->ext && c->ext->ents && !mmcollide(d, dir, *c->ext->ents)) return false;
728 scale--;
729 while(c->children && !(diff&(1<<scale)))
731 c = &c->children[octastep(bo.x, bo.y, bo.z, scale)];
732 if(c->ext && c->ext->ents && !mmcollide(d, dir, *c->ext->ents)) return false;
733 scale--;
735 if(c->children) return octacollide(d, dir, cutoff, bo, bs, c->children, ivec(bo).mask(~((2<<scale)-1)), 1<<scale);
736 bool solid = false;
737 if(c->ext) switch(c->ext->material&MATF_CLIP)
739 case MAT_NOCLIP: return true;
740 case MAT_AICLIP: if(d->type==ENT_AI) solid = true; break;
741 case MAT_CLIP: if(isclipped(c->ext->material&MATF_VOLUME) || d->type<ENT_CAMERA) solid = true; break;
743 if(!solid && isempty(*c)) return true;
744 int csize = 2<<scale, cmask = ~(csize-1);
745 return cubecollide(d, dir, cutoff, *c, bo.x&cmask, bo.y&cmask, bo.z&cmask, csize, solid);
748 // all collision happens here
749 bool collide(physent *d, const vec &dir, float cutoff, bool playercol)
751 inside = false;
752 hitplayer = NULL;
753 wall.x = wall.y = wall.z = 0;
754 ivec bo(int(d->o.x-d->radius), int(d->o.y-d->radius), int(d->o.z-d->eyeheight)),
755 bs(int(d->radius)*2, int(d->radius)*2, int(d->eyeheight+d->aboveeye));
756 bs.add(2); // guard space for rounding errors
757 if(!octacollide(d, dir, cutoff, bo, bs)) return false;//, worldroot, ivec(0, 0, 0), hdr.worldsize>>1)) return false; // collide with world
758 return !playercol || plcollide(d, dir);
761 void recalcdir(physent *d, const vec &oldvel, vec &dir)
763 float speed = oldvel.magnitude();
764 if(speed > 1e-6f)
766 float step = dir.magnitude();
767 dir = d->vel;
768 dir.add(d->falling);
769 dir.mul(step/speed);
773 void slideagainst(physent *d, vec &dir, const vec &obstacle, bool foundfloor)
775 vec wall(obstacle);
776 if(foundfloor && wall.z)
778 wall.z = 0;
779 if(!wall.iszero()) wall.normalize();
781 vec oldvel(d->vel);
782 oldvel.add(d->falling);
783 d->vel.project(wall);
784 d->falling.project(wall);
785 recalcdir(d, oldvel, dir);
788 void switchfloor(physent *d, vec &dir, const vec &floor)
790 if(floor.z >= FLOORZ) d->falling = vec(0, 0, 0);
792 vec oldvel(d->vel);
793 oldvel.add(d->falling);
794 if(dir.dot(floor) >= 0)
796 if(d->physstate < PHYS_SLIDE || fabs(dir.dot(d->floor)) > 0.01f*dir.magnitude()) return;
797 d->vel.projectxy(floor, 0.0f);
799 else d->vel.projectxy(floor);
800 d->falling.project(floor);
801 recalcdir(d, oldvel, dir);
804 bool trystepup(physent *d, vec &dir, float maxstep)
806 vec old(d->o);
807 /* check if there is space atop the stair to move to */
808 if(d->physstate != PHYS_STEP_UP)
810 d->o.add(dir);
811 d->o.z += maxstep + 0.1f;
812 if(!collide(d))
814 d->o = old;
815 return false;
818 /* try stepping up */
819 d->o = old;
820 d->o.z += dir.magnitude()*STEPSPEED;
821 if(collide(d, vec(0, 0, 1)))
823 if(d->physstate == PHYS_FALL)
825 d->timeinair = 0;
826 d->floor = vec(0, 0, 1);
827 switchfloor(d, dir, d->floor);
829 d->physstate = PHYS_STEP_UP;
830 return true;
832 d->o = old;
833 return false;
836 #if 0
837 bool trystepdown(physent *d, vec &dir, float step, float a, float b)
839 vec old(d->o);
840 vec dv(dir.x*a, dir.y*a, -step*b), v(dv);
841 v.mul(STAIRHEIGHT/(step*b));
842 d->o.add(v);
843 if(!collide(d, vec(0, 0, -1), SLOPEZ))
845 d->o = old;
846 d->o.add(dv);
847 if(collide(d, vec(0, 0, -1))) return true;
849 d->o = old;
850 return false;
852 #endif
854 void falling(physent *d, vec &dir, const vec &floor)
856 #if 0
857 if(d->physstate >= PHYS_FLOOR && (floor.z == 0.0f || floor.z == 1.0f))
859 vec moved(d->o);
860 d->o.z -= STAIRHEIGHT + 0.1f;
861 if(!collide(d, vec(0, 0, -1), SLOPEZ))
863 d->o = moved;
864 d->physstate = PHYS_STEP_DOWN;
865 return;
867 else d->o = moved;
869 #endif
870 if(floor.z > 0.0f && floor.z < SLOPEZ)
872 if(floor.z >= WALLZ) switchfloor(d, dir, floor);
873 d->timeinair = 0;
874 d->physstate = PHYS_SLIDE;
875 d->floor = floor;
877 else d->physstate = PHYS_FALL;
880 void landing(physent *d, vec &dir, const vec &floor)
882 #if 0
883 if(d->physstate == PHYS_FALL)
885 d->timeinair = 0;
886 if(dir.z < 0.0f) dir.z = d->vel.z = 0.0f;
888 #endif
889 switchfloor(d, dir, floor);
890 d->timeinair = 0;
891 if(floor.z >= FLOORZ) d->physstate = PHYS_FLOOR;
892 else d->physstate = PHYS_SLOPE;
893 d->floor = floor;
896 bool findfloor(physent *d, bool collided, const vec &obstacle, bool &slide, vec &floor)
898 bool found = false;
899 vec moved(d->o);
900 d->o.z -= 0.1f;
901 if(!collide(d, vec(0, 0, -1), d->physstate == PHYS_SLOPE ? SLOPEZ : FLOORZ))
903 floor = wall;
904 found = true;
906 else if(collided && obstacle.z >= SLOPEZ)
908 floor = obstacle;
909 found = true;
910 slide = false;
912 else if(d->physstate == PHYS_STEP_UP || d->physstate == PHYS_SLIDE)
914 if(!collide(d, vec(0, 0, -1)) && wall.z > 0.0f)
916 floor = wall;
917 if(floor.z >= SLOPEZ) found = true;
920 else if(d->physstate >= PHYS_SLOPE && d->floor.z < 1.0f)
922 d->o.z -= d->radius;
923 if(!collide(d, vec(d->floor).neg(), 0.95f) || !collide(d, vec(0, 0, -1)))
925 floor = wall;
926 if(floor.z >= SLOPEZ && floor.z < 1.0f) found = true;
929 if(collided && (!found || obstacle.z > floor.z))
931 floor = obstacle;
932 slide = !found && (floor.z < WALLZ || floor.z >= SLOPEZ);
934 d->o = moved;
935 return found;
938 bool move(physent *d, vec &dir)
940 vec old(d->o);
941 #if 0
942 if(d->physstate == PHYS_STEP_DOWN && dir.z <= 0.0f && cl->allowmove(pl) && (d->move || d->strafe))
944 float step = dir.magnitude()*STEPSPEED;
945 if(trystepdown(d, dir, step, 0.75f, 0.25f)) return true;
946 if(trystepdown(d, dir, step, 0.5f, 0.5f)) return true;
947 if(trystepdown(d, dir, step, 0.25f, 0.75f)) return true;
948 d->o.z -= step;
949 if(collide(d, vec(0, 0, -1))) return true;
950 d->o = old;
952 #endif
953 bool collided = false, slidecollide = false;
954 vec obstacle;
955 d->o.add(dir);
956 if(!collide(d, d->type!=ENT_CAMERA ? dir : vec(0, 0, 0)) || ((d->type==ENT_AI || d->type==ENT_INANIMATE) && !collide(d)))
958 obstacle = wall;
959 /* check to see if there is an obstacle that would prevent this one from being used as a floor (or ceiling bump) */
960 if(d->type==ENT_PLAYER && ((wall.z>=SLOPEZ && dir.z<0) || (wall.z<=-SLOPEZ && dir.z>0)) && (dir.x || dir.y) && !collide(d, vec(dir.x, dir.y, 0)))
962 if(wall.dot(dir) >= 0) slidecollide = true;
963 obstacle = wall;
965 d->o = old;
966 if(d->type == ENT_CAMERA) return false;
967 float stepdist = (d->physstate >= PHYS_SLOPE && d->floor.z < 1.0f ? d->radius+0.1f : STAIRHEIGHT);
968 d->o.z -= stepdist;
969 d->zmargin = -stepdist;
970 if(d->physstate == PHYS_SLOPE || d->physstate == PHYS_FLOOR || (!collide(d, vec(0, 0, -1), SLOPEZ) && (d->physstate==PHYS_STEP_UP || wall.z>=FLOORZ)))
972 d->o = old;
973 d->zmargin = 0;
974 float floorz = (d->physstate == PHYS_SLOPE || d->physstate == PHYS_FLOOR ? d->floor.z : wall.z);
975 if(trystepup(d, dir, floorz < 1.0f ? d->radius+0.1f : STAIRHEIGHT)) return true;
977 else
979 d->o = old;
980 d->zmargin = 0;
982 /* can't step over the obstacle, so just slide against it */
983 collided = true;
985 vec floor(0, 0, 0);
986 bool slide = collided,
987 found = findfloor(d, collided, obstacle, slide, floor);
988 if(slide || (!collided && floor.z > 0 && floor.z < WALLZ))
990 slideagainst(d, dir, slide ? obstacle : floor, found || slidecollide);
991 if(d->type == ENT_AI || d->type == ENT_INANIMATE) d->blocked = true;
993 if(found)
995 if(d->type == ENT_CAMERA) return false;
996 landing(d, dir, floor);
998 else falling(d, dir, floor);
999 return !collided;
1002 bool bounce(physent *d, float secs, float elasticity, float waterfric)
1004 int mat = lookupmaterial(vec(d->o.x, d->o.y, d->o.z + (d->aboveeye - d->eyeheight)/2));
1005 bool water = isliquid(mat);
1006 if(water)
1008 d->vel.z -= GRAVITY/16*secs;
1009 d->vel.mul(max(1.0f - secs/waterfric, 0.0f));
1011 else d->vel.z -= GRAVITY*secs;
1012 vec old(d->o);
1013 loopi(2)
1015 vec dir(d->vel);
1016 dir.mul(secs);
1017 d->o.add(dir);
1018 if(collide(d, dir))
1020 if(inside)
1022 d->o = old;
1023 d->vel.mul(-elasticity);
1025 break;
1027 else if(hitplayer) break;
1028 d->o = old;
1029 float c = wall.dot(d->vel),
1030 k = 1.0f + (1.0f-elasticity)*c/d->vel.magnitude();
1031 d->vel.mul(k);
1032 d->vel.sub(vec(wall).mul(elasticity*2.0f*c));
1034 if(d->physstate!=PHYS_BOUNCE)
1036 // make sure bouncers don't start inside geometry
1037 if(d->o == old) return !hitplayer;
1038 d->physstate = PHYS_BOUNCE;
1040 return hitplayer!=0;
1043 void avoidcollision(physent *d, const vec &dir, physent *obstacle, float space)
1045 float rad = obstacle->radius+d->radius;
1046 vec bbmin(obstacle->o);
1047 bbmin.x -= rad;
1048 bbmin.y -= rad;
1049 bbmin.z -= obstacle->eyeheight+d->aboveeye;
1050 bbmin.sub(space);
1051 vec bbmax(obstacle->o);
1052 bbmax.x += rad;
1053 bbmax.y += rad;
1054 bbmax.z += obstacle->aboveeye+d->eyeheight;
1055 bbmax.add(space);
1057 loopi(3) if(d->o[i] <= bbmin[i] || d->o[i] >= bbmax[i]) return;
1059 float mindist = 1e16f;
1060 loopi(3) if(dir[i] != 0)
1062 float dist = ((dir[i] > 0 ? bbmax[i] : bbmin[i]) - d->o[i]) / dir[i];
1063 mindist = min(mindist, dist);
1065 if(mindist >= 0.0f && mindist < 1e15f) d->o.add(vec(dir).mul(mindist));
1068 bool droptofloor(vec &o, float radius, float height)
1070 if(!insideworld(o)) return false;
1071 vec v(0.0001f, 0.0001f, -1);
1072 v.normalize();
1073 if(raycube(o, v, hdr.worldsize) >= hdr.worldsize) return false;
1074 physent d;
1075 d.type = ENT_CAMERA;
1076 d.o = o;
1077 d.vel = vec(0, 0, -1);
1078 d.radius = radius;
1079 d.eyeheight = height;
1080 d.aboveeye = radius;
1081 loopi(hdr.worldsize) if(!move(&d, v))
1083 o = d.o;
1084 return true;
1086 return false;
1089 void dropenttofloor(entity *e)
1091 droptofloor(e->o, 1.0f, et->dropheight(*e));
1094 void phystest()
1096 static const char *states[] = {"float", "fall", "slide", "slope", "floor", "step up", "step down", "bounce"};
1097 printf ("PHYS(pl): %s, air %d, floor: (%f, %f, %f), vel: (%f, %f, %f), g: (%f, %f, %f)\n", states[player->physstate], player->timeinair, player->floor.x, player->floor.y, player->floor.z, player->vel.x, player->vel.y, player->vel.z, player->falling.x, player->falling.y, player->falling.z);
1098 printf ("PHYS(cam): %s, air %d, floor: (%f, %f, %f), vel: (%f, %f, %f), g: (%f, %f, %f)\n", states[camera1->physstate], camera1->timeinair, camera1->floor.x, camera1->floor.y, camera1->floor.z, camera1->vel.x, camera1->vel.y, camera1->vel.z, camera1->falling.x, camera1->falling.y, camera1->falling.z);
1101 COMMAND(phystest, "");
1103 void vecfromyawpitch(float yaw, float pitch, int move, int strafe, vec &m)
1105 if(move)
1107 m.x = move*sinf(RAD*yaw);
1108 m.y = move*-cosf(RAD*yaw);
1110 else m.x = m.y = 0;
1112 if(pitch)
1114 m.x *= cosf(RAD*pitch);
1115 m.y *= cosf(RAD*pitch);
1116 m.z = move*sinf(RAD*pitch);
1118 else m.z = 0;
1120 if(strafe)
1122 m.x += strafe*-cosf(RAD*yaw);
1123 m.y += strafe*-sinf(RAD*yaw);
1127 void vectoyawpitch(const vec &v, float &yaw, float &pitch)
1129 yaw = -(float)atan2(v.x, v.y)/RAD + 180;
1130 pitch = asin(v.z/v.magnitude())/RAD;
1133 VARP(maxroll, 0, 3, 20);
1134 VAR(floatspeed, 10, 100, 1000);
1136 void modifyvelocity(physent *pl, bool local, bool water, bool floating, int curtime)
1138 if(floating)
1140 if(pl->jumpnext)
1142 pl->jumpnext = false;
1143 pl->vel.z = JUMPVEL;
1146 else if(pl->physstate >= PHYS_SLOPE || water)
1148 if(pl->type != ENT_CAMERA && water && !pl->inwater) pl->vel.div(8);
1149 if(pl->jumpnext)
1151 pl->jumpnext = false;
1153 pl->vel.z = JUMPVEL; // physics impulse upwards
1154 if(water) { pl->vel.x /= 8.0f; pl->vel.y /= 8.0f; } // dampen velocity change even harder, gives correct water feel
1156 cl->physicstrigger(pl, local, 1, 0);
1159 if(!floating && pl->physstate == PHYS_FALL) pl->timeinair += curtime;
1161 vec m(0.0f, 0.0f, 0.0f);
1162 if(pl->type==ENT_AI)
1164 dynent *d = (dynent *)pl;
1165 if(d->rotspeed && d->yaw!=d->targetyaw)
1167 float oldyaw = d->yaw, diff = d->rotspeed*curtime/1000.0f, maxdiff = fabs(d->targetyaw-d->yaw);
1168 if(diff >= maxdiff)
1170 d->yaw = d->targetyaw;
1171 d->rotspeed = 0;
1173 else d->yaw += (d->targetyaw>d->yaw ? 1 : -1) * min(diff, maxdiff);
1174 d->normalize_yaw(d->targetyaw);
1175 if(!plcollide(d, vec(0, 0, 0)))
1177 d->yaw = oldyaw;
1178 m.x = d->o.x - hitplayer->o.x;
1179 m.y = d->o.y - hitplayer->o.y;
1180 if(!m.iszero()) m.normalize();
1185 if(m.iszero() && cl->allowmove(pl) && (pl->move || pl->strafe))
1187 vecfromyawpitch(pl->yaw, floating || water || pl->type==ENT_CAMERA ? pl->pitch : 0, pl->move, pl->strafe, m);
1189 if(!floating && pl->physstate >= PHYS_SLIDE)
1191 /* move up or down slopes in air
1192 * but only move up slopes in water
1194 float dz = -(m.x*pl->floor.x + m.y*pl->floor.y)/pl->floor.z;
1195 if(water) m.z = max(m.z, dz);
1196 else if(pl->floor.z >= WALLZ) m.z = dz;
1199 m.normalize();
1202 vec d(m);
1203 d.mul(pl->maxspeed);
1204 if(floating)
1206 if(pl==player) d.mul(floatspeed/100.0f);
1208 //else if(!water && cl->allowmove(pl)) d.mul((pl->move && !pl->strafe ? 0.6f : 0.6f) * (pl->physstate < PHYS_SLOPE ? 1.3f : 1.0f)); // EXPERIMENTAL
1209 // Segfault: Fix the first value, 0.6f = forward, second = strafe, speed values independant depending on player gun etc.
1210 else if(!water && cl->allowmove(pl)) d.mul((pl->move && !pl->strafe ? 1.3f : 1.0f) * (pl->physstate < PHYS_SLOPE ? 1.3f : 1.0f)); // EXPERIMENTAL
1211 float friction = water && !floating ? 20.0f : (pl->physstate >= PHYS_SLOPE || floating ? 6.0f : 30.0f);
1212 float fpsfric = friction/curtime*20.0f;
1214 pl->vel.mul(fpsfric-1);
1215 pl->vel.add(d);
1216 pl->vel.div(fpsfric);
1219 void modifygravity(physent *pl, bool water, int curtime)
1221 float secs = curtime/1000.0f;
1222 vec g(0, 0, 0);
1223 if(pl->physstate == PHYS_FALL) g.z -= GRAVITY*secs;
1224 else if(pl->floor.z > 0 && pl->floor.z < FLOORZ)
1226 g.z = -1;
1227 g.project(pl->floor);
1228 g.normalize();
1229 g.mul(GRAVITY*secs);
1231 if(!water || !cl->allowmove(pl) || (!pl->move && !pl->strafe)) pl->falling.add(g);
1233 if(water || pl->physstate >= PHYS_SLOPE)
1235 float friction = water ? 2.0f : 6.0f,
1236 fpsfric = friction/curtime*20.0f,
1237 c = water ? 1.0f : clamp((pl->floor.z - SLOPEZ)/(FLOORZ-SLOPEZ), 0.0f, 1.0f);
1238 pl->falling.mul(1 - c/fpsfric);
1242 // main physics routine, moves a player/monster for a curtime step
1243 // moveres indicated the physics precision (which is lower for monsters and multiplayer prediction)
1244 // local is false for multiplayer prediction
1246 bool moveplayer(physent *pl, int moveres, bool local, int curtime)
1248 int material = lookupmaterial(vec(pl->o.x, pl->o.y, pl->o.z + (3*pl->aboveeye - pl->eyeheight)/4));
1249 bool water = isliquid(material&MATF_VOLUME);
1250 bool floating = pl->state==CS_EDITING || (pl->type!=ENT_CAMERA && pl->state==CS_SPECTATOR);
1251 float secs = curtime/1000.f;
1253 // apply gravity
1254 if(!floating && pl->type!=ENT_CAMERA) modifygravity(pl, water, curtime);
1255 // apply any player generated changes in velocity
1256 modifyvelocity(pl, local, water, floating, curtime);
1258 vec d(pl->vel), oldpos(pl->o);
1260 if(!floating && pl->type!=ENT_CAMERA && water) d.mul(0.5f);
1262 d.add(pl->falling);
1263 d.mul(secs);
1265 pl->blocked = false;
1266 pl->moving = true;
1267 pl->onplayer = NULL;
1269 if(floating) // just apply velocity
1271 if(pl->physstate != PHYS_FLOAT)
1273 pl->physstate = PHYS_FLOAT;
1274 pl->timeinair = 0;
1275 pl->falling = vec(0, 0, 0);
1277 pl->o.add(d);
1279 else // apply velocity with collision
1281 const float f = 1.0f/moveres; // Segfault: FIX: Adjust it so the player can adjust it to its own speed.
1282 const int timeinair = pl->timeinair;
1283 int collisions = 0;
1285 d.mul(f);
1286 loopi(moveres) if(!move(pl, d)) { if(pl->type==ENT_CAMERA) return false; if(++collisions<5) i--; } // discrete steps collision detection & sliding
1287 if(timeinair > 800 && !pl->timeinair && !water) // if we land after long time must have been a high jump, make thud sound
1289 cl->physicstrigger(pl, local, -1, 0);
1293 if(pl->type!=ENT_CAMERA && pl->state==CS_ALIVE) updatedynentcache(pl);
1295 if(!pl->timeinair && pl->physstate >= PHYS_FLOOR && pl->vel.squaredlen() < 1e-4f) pl->moving = false;
1297 pl->lastmoveattempt = lastmillis;
1298 if(pl->o!=oldpos) pl->lastmove = lastmillis;
1300 // automatically apply smooth roll when strafing
1302 if(pl->strafe==0)
1304 pl->roll = pl->roll/(1+(float)sqrtf((float)curtime)/25);
1306 else
1308 pl->roll += pl->strafe*curtime/-30.0f;
1309 if(pl->roll>maxroll) pl->roll = (float)maxroll;
1310 if(pl->roll<-maxroll) pl->roll = (float)-maxroll;
1313 // play sounds on water transitions
1315 if(pl->type!=ENT_CAMERA)
1317 if(pl->inwater && !water)
1319 material = lookupmaterial(vec(pl->o.x, pl->o.y, pl->o.z + (pl->aboveeye - pl->eyeheight)/2));
1320 water = isliquid(material&MATF_VOLUME);
1322 if(!pl->inwater && water) cl->physicstrigger(pl, local, 0, -1, material&MATF_VOLUME);
1323 else if(pl->inwater && !water) cl->physicstrigger(pl, local, 0, 1, pl->inwater);
1324 pl->inwater = water ? material&MATF_VOLUME : MAT_AIR;
1326 if(pl->state==CS_ALIVE && (pl->o.z < 0 || material&MAT_DEATH)) cl->suicide(pl);
1329 return true;
1332 #define PHYSFRAMETIME 5
1334 int physsteps = 0, physframetime = PHYSFRAMETIME, lastphysframe = 0;
1336 void physicsframe() // optimally schedule physics frames inside the graphics frames
1338 int diff = lastmillis + curtime - lastphysframe;
1339 if(diff <= 0) physsteps = 0;
1340 else
1342 extern int gamespeed;
1343 physframetime = clamp((PHYSFRAMETIME*gamespeed)/100, 1, PHYSFRAMETIME);
1344 physsteps = (diff + physframetime - 1)/physframetime;
1345 lastphysframe += physsteps * physframetime;
1347 cleardynentcache();
1350 VAR(physinterp, 0, 1, 1);
1352 void interppos(physent *pl)
1354 pl->o = pl->newpos;
1356 int diff = lastphysframe - (lastmillis + curtime);
1357 if(diff <= 0 || !physinterp) return;
1359 vec deltapos(pl->deltapos);
1360 deltapos.mul(min(diff, physframetime)/float(physframetime));
1361 pl->o.add(deltapos);
1364 void moveplayer(physent *pl, int moveres, bool local)
1366 if(physsteps <= 0)
1368 if(local) interppos(pl);
1369 return;
1372 if(local) pl->o = pl->newpos;
1373 loopi(physsteps-1) moveplayer(pl, moveres, local, physframetime);
1374 if(local) pl->deltapos = pl->o;
1375 moveplayer(pl, moveres, local, physframetime);
1376 if(local)
1378 pl->newpos = pl->o;
1379 pl->deltapos.sub(pl->newpos);
1380 interppos(pl);
1384 bool bounce(physent *d, float elasticity, float waterfric)
1386 if(physsteps <= 0)
1388 interppos(d);
1389 return false;
1392 d->o = d->newpos;
1393 bool hitplayer = false;
1394 loopi(physsteps-1)
1396 if(bounce(d, physframetime/1000.0f, elasticity, waterfric)) hitplayer = true;
1398 d->deltapos = d->o;
1399 if(bounce(d, physframetime/1000.0f, elasticity, waterfric)) hitplayer = true;
1400 d->newpos = d->o;
1401 d->deltapos.sub(d->newpos);
1402 interppos(d);
1403 return hitplayer;
1406 void updatephysstate(physent *d)
1408 if(d->physstate == PHYS_FALL) return;
1409 d->timeinair = 0;
1410 vec old(d->o);
1411 /* Attempt to reconstruct the floor state.
1412 * May be inaccurate since movement collisions are not considered.
1413 * If good floor is not found, just keep the old floor and hope it's correct enough.
1415 switch(d->physstate)
1417 case PHYS_SLOPE:
1418 case PHYS_FLOOR:
1419 d->o.z -= 0.1f;
1420 if(!collide(d, vec(0, 0, -1), d->physstate == PHYS_SLOPE ? SLOPEZ : FLOORZ))
1421 d->floor = wall;
1422 else if(d->physstate == PHYS_SLOPE)
1424 d->o.z -= d->radius;
1425 if(!collide(d, vec(0, 0, -1), SLOPEZ))
1426 d->floor = wall;
1428 break;
1430 case PHYS_STEP_UP:
1431 d->o.z -= STAIRHEIGHT+0.1f;
1432 if(!collide(d, vec(0, 0, -1), SLOPEZ))
1433 d->floor = wall;
1434 break;
1436 case PHYS_SLIDE:
1437 d->o.z -= d->radius+0.1f;
1438 if(!collide(d, vec(0, 0, -1)) && wall.z < SLOPEZ)
1439 d->floor = wall;
1440 break;
1442 if(d->physstate > PHYS_FALL && d->floor.z <= 0) d->floor = vec(0, 0, 1);
1443 d->o = old;
1446 bool intersect(physent *d, vec &from, vec &to) // if lineseg hits entity bounding box
1448 vec v = to, w = d->o, *p;
1449 v.sub(from);
1450 w.sub(from);
1451 float c1 = w.dot(v);
1453 if(c1<=0) p = &from;
1454 else
1456 float c2 = v.dot(v);
1457 if(c2<=c1) p = &to;
1458 else
1460 float f = c1/c2;
1461 v.mul(f);
1462 v.add(from);
1463 p = &v;
1467 return p->x <= d->o.x+d->radius
1468 && p->x >= d->o.x-d->radius
1469 && p->y <= d->o.y+d->radius
1470 && p->y >= d->o.y-d->radius
1471 && p->z <= d->o.z+d->aboveeye
1472 && p->z >= d->o.z-d->eyeheight;
1475 const float PLATFORMMARGIN = 0.2f;
1476 const float PLATFORMBORDER = 10.0f;
1478 struct platformcollision
1480 physent *d;
1481 int next;
1483 platformcollision() {}
1484 platformcollision(physent *d, int next) : d(d), next(next) {}
1487 bool platformcollide(physent *d, physent *o, const vec &dir, float margin = 0)
1489 if(d->collidetype!=COLLIDE_ELLIPSE || o->collidetype!=COLLIDE_ELLIPSE)
1491 if(rectcollide(d, dir, o->o,
1492 o->collidetype==COLLIDE_ELLIPSE ? o->radius : o->xradius,
1493 o->collidetype==COLLIDE_ELLIPSE ? o->radius : o->yradius, o->aboveeye,
1494 o->eyeheight + margin))
1495 return true;
1497 else if(ellipsecollide(d, dir, o->o, o->yaw, o->xradius, o->yradius, o->aboveeye, o->eyeheight + margin))
1498 return true;
1499 return false;
1502 bool moveplatform(physent *p, const vec &dir)
1504 if(!insideworld(p->newpos)) return false;
1506 vec oldpos(p->o);
1507 (p->o = p->newpos).add(dir);
1508 if(!collide(p, dir, 0, dir.z<=0))
1510 p->o = oldpos;
1511 return false;
1513 p->o = oldpos;
1515 static vector<physent *> candidates;
1516 candidates.setsizenodelete(0);
1517 for(int x = int(max(p->o.x-p->radius-PLATFORMBORDER, 0.0f))>>dynentsize, ex = int(min(p->o.x+p->radius+PLATFORMBORDER, hdr.worldsize-1.0f))>>dynentsize; x <= ex; x++)
1518 for(int y = int(max(p->o.y-p->radius-PLATFORMBORDER, 0.0f))>>dynentsize, ey = int(min(p->o.y+p->radius+PLATFORMBORDER, hdr.worldsize-1.0f))>>dynentsize; y <= ey; y++)
1520 const vector<physent *> &dynents = checkdynentcache(x, y);
1521 loopv(dynents)
1523 physent *d = dynents[i];
1524 if(p==d || d->o.z-d->eyeheight < p->o.z+p->aboveeye || p->o.reject(d->o, p->radius+PLATFORMBORDER+d->radius) || candidates.find(d) >= 0) continue;
1525 candidates.add(d);
1526 d->stacks = d->collisions = -1;
1529 static vector<physent *> passengers, colliders;
1530 passengers.setsizenodelete(0);
1531 colliders.setsizenodelete(0);
1532 static vector<platformcollision> collisions;
1533 collisions.setsizenodelete(0);
1534 // build up collision DAG of colliders to be pushed off, and DAG of stacked passengers
1535 loopv(candidates)
1537 physent *d = candidates[i];
1538 // check if the dynent is on top of the platform
1539 if(!platformcollide(p, d, vec(0, 0, 1), PLATFORMMARGIN)) passengers.add(d);
1540 vec doldpos(d->o);
1541 (d->o = d->newpos).add(dir);
1542 if(!collide(d, dir, 0, false)) colliders.add(d);
1543 d->o = doldpos;
1544 loopvj(candidates)
1546 physent *o = candidates[j];
1547 if(!platformcollide(d, o, dir))
1549 collisions.add(platformcollision(d, o->collisions));
1550 o->collisions = collisions.length() - 1;
1552 if(d->o.z < o->o.z && !platformcollide(d, o, vec(0, 0, 1), PLATFORMMARGIN))
1554 collisions.add(platformcollision(o, d->stacks));
1555 d->stacks = collisions.length() - 1;
1559 loopv(colliders) // propagate collisions
1561 physent *d = colliders[i];
1562 for(int n = d->collisions; n>=0; n = collisions[n].next)
1564 physent *o = collisions[n].d;
1565 if(colliders.find(o)<0) colliders.add(o);
1568 if(dir.z>0)
1570 loopv(passengers) // if any stacked passengers collide, stop the platform
1572 physent *d = passengers[i];
1573 if(colliders.find(d)>=0) return false;
1574 for(int n = d->stacks; n>=0; n = collisions[n].next)
1576 physent *o = collisions[n].d;
1577 if(passengers.find(o)<0) passengers.add(o);
1580 loopv(passengers)
1582 physent *d = passengers[i];
1583 d->o.add(dir);
1584 d->newpos.add(dir);
1585 d->lastmove = lastmillis;
1586 if(dir.x || dir.y) updatedynentcache(d);
1589 else loopv(passengers) // move any stacked passengers who aren't colliding with non-passengers
1591 physent *d = passengers[i];
1592 if(colliders.find(d)>=0) continue;
1594 d->o.add(dir);
1595 d->newpos.add(dir);
1596 d->lastmove = lastmillis;
1597 if(dir.x || dir.y) updatedynentcache(d);
1599 for(int n = d->stacks; n>=0; n = collisions[n].next)
1601 physent *o = collisions[n].d;
1602 if(passengers.find(o)<0) passengers.add(o);
1606 p->o.add(dir);
1607 p->newpos.add(dir);
1608 p->lastmove = lastmillis;
1609 if(dir.x || dir.y) updatedynentcache(p);
1611 return true;
1614 #define dir(name,v,d,s,os) ICOMMAND(name, "D", (int *down), { player->s = *down!=0; player->v = player->s ? d : (player->os ? -(d) : 0); });
1616 dir(backward, move, -1, k_down, k_up);
1617 dir(forward, move, 1, k_up, k_down);
1618 dir(left, strafe, 1, k_left, k_right);
1619 dir(right, strafe, -1, k_right, k_left);
1621 ICOMMAND(jump, "D", (int *down), { if(cl->canjump()) player->jumpnext = *down!=0; });
1622 ICOMMAND(attack, "D", (int *down), { cl->doattack(*down!=0); });
1624 bool entinmap(dynent *d, bool avoidplayers) // brute force but effective way to find a free spawn spot in the map
1626 d->o.z += d->eyeheight; // pos specified is at feet
1627 vec orig = d->o;
1628 loopi(100) // try max 100 times
1630 if(i)
1632 d->o = orig;
1633 d->o.x += (rnd(21)-10)*i/5; // increasing distance
1634 d->o.y += (rnd(21)-10)*i/5;
1635 d->o.z += (rnd(21)-10)*i/5;
1638 if(collide(d) && !inside)
1640 if(hitplayer)
1642 if(!avoidplayers) continue;
1643 d->o = orig;
1644 d->resetinterp();
1645 return false;
1648 d->resetinterp();
1649 return true;
1652 // leave ent at original pos, possibly stuck
1653 d->o = orig;
1654 d->resetinterp();
1655 conoutf(CON_WARN, "can't find entity spawn spot! (%.1f, %.1f, %.1f)", d->o.x, d->o.y, d->o.z);
1656 return false;