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).
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
;
20 genclipplanes(c
, x
, y
, z
, size
, *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
;
32 ///////////////////////// ray - cube collision ///////////////////////////////////////////////
34 static inline void pushvec(vec
&o
, const vec
&ray
, float dist
)
41 static inline bool pointinbox(const vec
&v
, const vec
&bo
, const vec
&br
)
43 return v
.x
<= bo
.x
+br
.x
&&
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;
58 #define INTERSECTPLANES(setentry) \
59 clipplanes &p = *c.ext->clip; \
60 float enterdist = -1e16f, exitdist = 1e16f; \
63 float pdist = p.p[i].dist(o), facing = ray.dot(p.p[i]); \
67 if(pdist > enterdist) \
69 if(pdist > exitdist) return false; \
77 if(pdist < exitdist) \
79 if(pdist < enterdist) return false; \
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
)
90 if(exitdist
< 0) return false;
92 if(enterdist
< 0) enterdist
= 0;
93 if(!pointinbox(vec(ray
).mul(enterdist
).add(o
), p
.o
, p
.r
)) return false;
100 bool raycubeintersect(const cube
&c
, const vec
&o
, const vec
&ray
, float &dist
)
102 int entry
= -1, bbentry
= -1;
103 INTERSECTPLANES(entry
= 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;
115 pdist
+= 2*p
.r
[i
]/fabs(ray
[i
]);
118 if(pdist
< enterdist
) return false;
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
];
131 extern void entselectionbox(const entity
&e
, vec
&eo
, vec
&es
);
132 extern int entselradius
;
134 int hitent
, hitorient
;
136 static float disttoent(octaentities
*oc
, octaentities
*last
, const vec
&o
, const vec
&ray
, float radius
, int mode
, extentity
*t
)
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)) \
148 if(!last || last->type.find(oc->type[i])<0) \
150 extentity &e = *ents[oc->type[i]]; \
151 if(!e.inoctanode || &e==t) continue; \
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;
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
;
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; \
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]; \
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; \
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; \
251 if(dy < disttonext) { disttonext = dy; yclosest; } \
252 if(dz < disttonext) { disttonext = dz; zclosest; } \
253 disttonext += 0.1f; \
254 pushvec(v, ray, disttonext); \
257 #define UPOCTREE(exitworld) \
261 uint diff = uint(lo.x^x)|uint(lo.y^y)|uint(lo.z^z); \
262 if(diff >= uint(hdr.worldsize)) exitworld; \
264 if(!diff) exitworld; \
271 float raycube(const vec
&o
, const vec
&ray
, float radius
, int mode
, int size
, extentity
*t
)
273 if(ray
.iszero()) return 0;
278 int closest
= -1, x
= int(v
.x
), y
= int(v
.y
), z
= int(v
.z
);
281 DOWNOCTREE(disttoent
, && (mode
&RAY_SHADOW
));
283 int lsize
= 1<<lshift
;
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
) ||
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
));
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
)
321 int x
= int(v
.x
), y
= int(v
.y
), z
= int(v
.z
);
324 DOWNOCTREE(shadowent
, );
327 if(isentirelysolid(c
)) return dist
;
329 ivec
lo(x
&(~0<<lshift
), y
&(~0<<lshift
), z
&(~0<<lshift
));
334 setcubeclip(c
, lo
.x
, lo
.y
, lo
.z
, 1<<lshift
);
335 if(shadowcubeintersect(c
, v
, ray
, f
)) return dist
+f
;
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
)
349 float d
= raycubepos(o
, ray
, hitpos
, hitentdist
= radius
, mode
, size
);
351 ent
= (hitentdist
== d
) ? hitent
: -1;
355 float raycubepos(const vec
&o
, const vec
&ray
, vec
&hitpos
, float radius
, int mode
, int size
)
358 float dist
= raycube(o
, ray
, radius
, mode
, size
);
364 bool raycubelos(const vec
&o
, const vec
&dest
, vec
&hitpos
)
368 float mag
= ray
.magnitude();
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
;
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.
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
);
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();
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);
425 else if(dir
.iszero() || (dir
.z
< 0 && (d
->type
>=ENT_INANIMATE
|| above
>= d
->zmargin
-(d
->eyeheight
+d
->aboveeye
)/3.0f
)))
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;
440 float dxr
= d
->collidetype
==COLLIDE_ELLIPSE
? d
->radius
: d
->xradius
, dyr
= d
->collidetype
==COLLIDE_ELLIPSE
? d
->radius
: d
->yradius
;
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;
464 #define DYNENTCACHESIZE 1024
466 static uint dynentframe
= 0;
468 static struct dynentcacheentry
472 vector
<physent
*> dynents
;
473 } dynentcache
[DYNENTCACHESIZE
];
475 void cleardynentcache()
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
;
492 dec
.frame
= dynentframe
;
493 dec
.dynents
.setsize(0);
494 int numdyns
= cl
->numdynents(), dsize
= 1<<dynentsize
, dx
= x
<<dynentsize
, dy
= y
<<dynentsize
;
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
)
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;
521 bool overlapsdynent(const vec
&o
, float radius
)
523 loopdynentcache(x
, y
, o
, radius
)
525 const vector
<physent
*> &dynents
= checkdynentcache(x
, y
);
528 physent
*d
= dynents
[i
];
529 if(o
.dist(d
->o
)-d
->radius
< radius
) return true;
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
);
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
))
550 if((d
->type
==ENT_AI
|| d
->type
==ENT_INANIMATE
) && wall
.z
>0) d
->onplayer
= o
;
554 else if(!ellipsecollide(d
, dir
, o
->o
, o
->yaw
, o
->xradius
, o
->yradius
, o
->aboveeye
, o
->eyeheight
))
557 if((d
->type
==ENT_AI
|| d
->type
==ENT_INANIMATE
) && wall
.z
>0) d
->onplayer
= o
;
565 void rotatebb(vec
¢er
, vec
&radius
, int yaw
)
567 yaw
= (yaw
+7)-(yaw
+7)%15;
569 if(yaw
< 0) yaw
= 360 + yaw
%360;
570 else if(yaw
>= 360) yaw
%= 360;
575 center
.x
= -center
.x
;
576 center
.y
= -center
.y
;
579 swap(radius
.x
, radius
.y
);
580 swap(center
.x
, center
.y
);
581 center
.x
= -center
.x
;
584 swap(radius
.x
, radius
.y
);
585 swap(center
.x
, center
.y
);
586 center
.y
= -center
.y
;
589 radius
.x
= radius
.y
= max(radius
.x
, radius
.y
) + max(fabs(center
.x
), fabs(center
.y
));
590 center
.x
= center
.y
= 0.0f
;
595 bool mmcollide(physent
*d
, const vec
&dir
, octaentities
&oc
) // collide with a mapmodel
597 const vector
<extentity
*> &ents
= et
->getents();
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;
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;
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
))
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
;
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;
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; }
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())
649 walldistance
= -1e10f
;
654 float m
= walldistance
;
658 float dist
= f
.dist(o
) - vec(f
.x
*r
, f
.y
*r
, f
.z
*zr
).magnitude();
659 if(dist
> 0) return true;
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)))
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())
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())
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())
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
);
702 if(!octacollide(d
, dir
, cutoff
, bo
, bs
, c
[i
].children
, o
, size
>>1)) return 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;
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;
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;
735 if(c
->children
) return octacollide(d
, dir
, cutoff
, bo
, bs
, c
->children
, ivec(bo
).mask(~((2<<scale
)-1)), 1<<scale
);
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
)
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();
766 float step
= dir
.magnitude();
773 void slideagainst(physent
*d
, vec
&dir
, const vec
&obstacle
, bool foundfloor
)
776 if(foundfloor
&& wall
.z
)
779 if(!wall
.iszero()) wall
.normalize();
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);
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
)
807 /* check if there is space atop the stair to move to */
808 if(d
->physstate
!= PHYS_STEP_UP
)
811 d
->o
.z
+= maxstep
+ 0.1f
;
818 /* try stepping up */
820 d
->o
.z
+= dir
.magnitude()*STEPSPEED
;
821 if(collide(d
, vec(0, 0, 1)))
823 if(d
->physstate
== PHYS_FALL
)
826 d
->floor
= vec(0, 0, 1);
827 switchfloor(d
, dir
, d
->floor
);
829 d
->physstate
= PHYS_STEP_UP
;
837 bool trystepdown(physent
*d
, vec
&dir
, float step
, float a
, float b
)
840 vec
dv(dir
.x
*a
, dir
.y
*a
, -step
*b
), v(dv
);
841 v
.mul(STAIRHEIGHT
/(step
*b
));
843 if(!collide(d
, vec(0, 0, -1), SLOPEZ
))
847 if(collide(d
, vec(0, 0, -1))) return true;
854 void falling(physent
*d
, vec
&dir
, const vec
&floor
)
857 if(d
->physstate
>= PHYS_FLOOR
&& (floor
.z
== 0.0f
|| floor
.z
== 1.0f
))
860 d
->o
.z
-= STAIRHEIGHT
+ 0.1f
;
861 if(!collide(d
, vec(0, 0, -1), SLOPEZ
))
864 d
->physstate
= PHYS_STEP_DOWN
;
870 if(floor
.z
> 0.0f
&& floor
.z
< SLOPEZ
)
872 if(floor
.z
>= WALLZ
) switchfloor(d
, dir
, floor
);
874 d
->physstate
= PHYS_SLIDE
;
877 else d
->physstate
= PHYS_FALL
;
880 void landing(physent
*d
, vec
&dir
, const vec
&floor
)
883 if(d
->physstate
== PHYS_FALL
)
886 if(dir
.z
< 0.0f
) dir
.z
= d
->vel
.z
= 0.0f
;
889 switchfloor(d
, dir
, floor
);
891 if(floor
.z
>= FLOORZ
) d
->physstate
= PHYS_FLOOR
;
892 else d
->physstate
= PHYS_SLOPE
;
896 bool findfloor(physent
*d
, bool collided
, const vec
&obstacle
, bool &slide
, vec
&floor
)
901 if(!collide(d
, vec(0, 0, -1), d
->physstate
== PHYS_SLOPE
? SLOPEZ
: FLOORZ
))
906 else if(collided
&& obstacle
.z
>= SLOPEZ
)
912 else if(d
->physstate
== PHYS_STEP_UP
|| d
->physstate
== PHYS_SLIDE
)
914 if(!collide(d
, vec(0, 0, -1)) && wall
.z
> 0.0f
)
917 if(floor
.z
>= SLOPEZ
) found
= true;
920 else if(d
->physstate
>= PHYS_SLOPE
&& d
->floor
.z
< 1.0f
)
923 if(!collide(d
, vec(d
->floor
).neg(), 0.95f
) || !collide(d
, vec(0, 0, -1)))
926 if(floor
.z
>= SLOPEZ
&& floor
.z
< 1.0f
) found
= true;
929 if(collided
&& (!found
|| obstacle
.z
> floor
.z
))
932 slide
= !found
&& (floor
.z
< WALLZ
|| floor
.z
>= SLOPEZ
);
938 bool move(physent
*d
, vec
&dir
)
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;
949 if(collide(d
, vec(0, 0, -1))) return true;
953 bool collided
= false, slidecollide
= false;
956 if(!collide(d
, d
->type
!=ENT_CAMERA
? dir
: vec(0, 0, 0)) || ((d
->type
==ENT_AI
|| d
->type
==ENT_INANIMATE
) && !collide(d
)))
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;
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
);
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
)))
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;
982 /* can't step over the obstacle, so just slide against it */
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;
995 if(d
->type
== ENT_CAMERA
) return false;
996 landing(d
, dir
, floor
);
998 else falling(d
, dir
, floor
);
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
);
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
;
1023 d
->vel
.mul(-elasticity
);
1027 else if(hitplayer
) break;
1029 float c
= wall
.dot(d
->vel
),
1030 k
= 1.0f
+ (1.0f
-elasticity
)*c
/d
->vel
.magnitude();
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
);
1049 bbmin
.z
-= obstacle
->eyeheight
+d
->aboveeye
;
1051 vec
bbmax(obstacle
->o
);
1054 bbmax
.z
+= obstacle
->aboveeye
+d
->eyeheight
;
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);
1073 if(raycube(o
, v
, hdr
.worldsize
) >= hdr
.worldsize
) return false;
1075 d
.type
= ENT_CAMERA
;
1077 d
.vel
= vec(0, 0, -1);
1079 d
.eyeheight
= height
;
1080 d
.aboveeye
= radius
;
1081 loopi(hdr
.worldsize
) if(!move(&d
, v
))
1089 void dropenttofloor(entity
*e
)
1091 droptofloor(e
->o
, 1.0f
, et
->dropheight(*e
));
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
)
1107 m
.x
= move
*sinf(RAD
*yaw
);
1108 m
.y
= move
*-cosf(RAD
*yaw
);
1114 m
.x
*= cosf(RAD
*pitch
);
1115 m
.y
*= cosf(RAD
*pitch
);
1116 m
.z
= move
*sinf(RAD
*pitch
);
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
)
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);
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
);
1170 d
->yaw
= d
->targetyaw
;
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)))
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
;
1203 d
.mul(pl
->maxspeed
);
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);
1216 pl
->vel
.div(fpsfric
);
1219 void modifygravity(physent
*pl
, bool water
, int curtime
)
1221 float secs
= curtime
/1000.0f
;
1223 if(pl
->physstate
== PHYS_FALL
) g
.z
-= GRAVITY
*secs
;
1224 else if(pl
->floor
.z
> 0 && pl
->floor
.z
< FLOORZ
)
1227 g
.project(pl
->floor
);
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
;
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
);
1265 pl
->blocked
= false;
1267 pl
->onplayer
= NULL
;
1269 if(floating
) // just apply velocity
1271 if(pl
->physstate
!= PHYS_FLOAT
)
1273 pl
->physstate
= PHYS_FLOAT
;
1275 pl
->falling
= vec(0, 0, 0);
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
;
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
1304 pl
->roll
= pl
->roll
/(1+(float)sqrtf((float)curtime
)/25);
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
);
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;
1342 extern int gamespeed
;
1343 physframetime
= clamp((PHYSFRAMETIME
*gamespeed
)/100, 1, PHYSFRAMETIME
);
1344 physsteps
= (diff
+ physframetime
- 1)/physframetime
;
1345 lastphysframe
+= physsteps
* physframetime
;
1350 VAR(physinterp
, 0, 1, 1);
1352 void interppos(physent
*pl
)
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
)
1368 if(local
) interppos(pl
);
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
);
1379 pl
->deltapos
.sub(pl
->newpos
);
1384 bool bounce(physent
*d
, float elasticity
, float waterfric
)
1393 bool hitplayer
= false;
1396 if(bounce(d
, physframetime
/1000.0f
, elasticity
, waterfric
)) hitplayer
= true;
1399 if(bounce(d
, physframetime
/1000.0f
, elasticity
, waterfric
)) hitplayer
= true;
1401 d
->deltapos
.sub(d
->newpos
);
1406 void updatephysstate(physent
*d
)
1408 if(d
->physstate
== PHYS_FALL
) return;
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
)
1420 if(!collide(d
, vec(0, 0, -1), d
->physstate
== PHYS_SLOPE
? SLOPEZ
: FLOORZ
))
1422 else if(d
->physstate
== PHYS_SLOPE
)
1424 d
->o
.z
-= d
->radius
;
1425 if(!collide(d
, vec(0, 0, -1), SLOPEZ
))
1431 d
->o
.z
-= STAIRHEIGHT
+0.1f
;
1432 if(!collide(d
, vec(0, 0, -1), SLOPEZ
))
1437 d
->o
.z
-= d
->radius
+0.1f
;
1438 if(!collide(d
, vec(0, 0, -1)) && wall
.z
< SLOPEZ
)
1442 if(d
->physstate
> PHYS_FALL
&& d
->floor
.z
<= 0) d
->floor
= vec(0, 0, 1);
1446 bool intersect(physent
*d
, vec
&from
, vec
&to
) // if lineseg hits entity bounding box
1448 vec v
= to
, w
= d
->o
, *p
;
1451 float c1
= w
.dot(v
);
1453 if(c1
<=0) p
= &from
;
1456 float c2
= v
.dot(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
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
))
1497 else if(ellipsecollide(d
, dir
, o
->o
, o
->yaw
, o
->xradius
, o
->yradius
, o
->aboveeye
, o
->eyeheight
+ margin
))
1502 bool moveplatform(physent
*p
, const vec
&dir
)
1504 if(!insideworld(p
->newpos
)) return false;
1507 (p
->o
= p
->newpos
).add(dir
);
1508 if(!collide(p
, dir
, 0, dir
.z
<=0))
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
);
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;
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
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
);
1541 (d
->o
= d
->newpos
).add(dir
);
1542 if(!collide(d
, dir
, 0, false)) colliders
.add(d
);
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
);
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
);
1582 physent
*d
= passengers
[i
];
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;
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
);
1608 p
->lastmove
= lastmillis
;
1609 if(dir
.x
|| dir
.y
) updatedynentcache(p
);
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
1628 loopi(100) // try max 100 times
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
)
1642 if(!avoidplayers
) continue;
1652 // leave ent at original pos, possibly stuck
1655 conoutf(CON_WARN
, "can't find entity spawn spot! (%.1f, %.1f, %.1f)", d
->o
.x
, d
->o
.y
, d
->o
.z
);