Entropie git · main
Entropie festival game; first steps
C 88.5% Markdown 9.1% GLSL 2.3%git clone https://git.christianimmanuel.de/games/Entropie.gitwget https://git.christianimmanuel.de/games/Entropie/archive/Entropie.tar.gzentropie/src/world/physics.c raw
#include "world/physics.h"
#include "world/grid.h"
#include <float.h>
#define GRAVITY 9.81f
#define WALKABLE_SLOPE 0.7f /* cos of steepest walkable angle (~45 deg) */
#define CEILING_SLOPE -0.7f
#define SINK_MOST 0.45f /* m: a person's feet this far into something solid's top: up onto it (not pushed down) */
#define MAX_PUSH_CORRECTION 0.5f /* max push-out per contact between dynamic objects */
#define PUSH_STRENGTH 2.0f
#define PUSH_RECOIL 0.2f
#define SPIN_FACTOR 50.5f
#define YAW_DAMPING 0.9f
#define FRICTION 0.5f
#define FRICTION_MAX_WEIGHT 10.0f
#define GROUND_FRICTION 5.0f /* m/s^2 of braking for anything resting on the ground */
#define REST_VELOCITY 0.01f
#define GROUND_SNAP 0.3f /* walking characters stick to ground this far below */
#define TOPPLE_SPEED 0.5f /* knocked into this fast, a bottle falls over */
#define KICK_SPEED 3.0f /* run into this fast, it's kicked away like a throw */
#define THROW_IGNORE_TIME 0.3f /* a thrown item doesn't hit its thrower right away */
#define LADDER_GRIP 0.15f /* walked into, a ladder lying there moves this much of what a loose thing would ... */
#define LADDER_SLIDE 0.25f /* ... and slides at most this fast (m/s) */
#define WALK_PUSH_WEIGHT 15.0f /* walking into it shoves it aside; heavier things stop you (F pushes them) */
#define STEP_OVER_HEIGHT 0.48f /* low things up to this (scaled, about the knee) are stepped over ... */
#define STEP_OVER_WIDTH 0.75f /* ... if they are no wider than a stride across */
#define STEP_OVER_LIGHT 1.0f /* (lighter ones, bottles and cans, are just kicked aside) */
#define STEP_OVER_SPEED 1.2f /* m/s at most stepping over something knee high ... */
#define STEP_OVER_RUN 6.0f /* ... low things you can sprint over */
#define STEP_OVER_RUNNING 3.2f /* m/s: faster than this, things are taken in the stride (more risk) */
#define LEFT_BEHIND 0.5f /* near obstacles a standing foot is left at most about this far behind (scaled) */
#define OVER_STEP_SPEED 0.55f /* m/s while a foot goes over something knee high (the body waits for it) */
#define STEP_OVER_SLOWING 2.5f /* ... and this much more per meter before it */
#define STEP_OVER_LOOK 1.2f /* how far ahead people look for it */
#define LEG_WAIT 0.0f /* m a standing leg may reach into something ... */
#define LEG_WAIT_SPEED 0.05f /* ... before the body waits (m/s) */
#define JUMP_OVER_SPEED 2.9f /* m/s: jogging or faster, something low ahead ... */
#define HURDLE_LOWEST 0.15f /* ... at least this high (scaled; lower: run over) ... */
#define HURDLE_DEPTH 0.7f /* ... and no deeper than this: jumped */
#define JUMP_LOOK 2.5f /* (looked for this far ahead: no slowing down for it) */
#define JUMP_CLEAR 0.14f /* the feet this much over it ... */
#define JUMP_TUCK 0.18f /* ... tucked up this much in the air */
#define JUMP_PAST 0.25f /* landing this far past it */
#define JUMP_MOST 1.05f /* of a full jump at most */
#define JUMP_EARLY 0.04f /* m: take-off a touch early rather than late */
#define OVER_MOVE_LOWEST 0.2f /* walking over something at least this high (scaled) is a set move ... */
#define OVER_MOVE_FROM 0.42f /* ... starting this close to it ... */
#define OVER_MOVE_SLOWEST 0.3f /* ... (walking at it, not just standing there) ... */
#define OVER_MOVE_TIME 0.9f /* ... taking this long ... */
#define OVER_MOVE_HIGH 1.6f /* ... and this much more per meter high */
#define OVER_LAND 0.16f /* the first foot comes down this far past it */
#define STEP_UP_MOST 0.75f /* walking up onto something this high at most (scaled; higher: climb) ... */
#define STEP_UP_ROOM 0.45f /* ... with this much room on top ... */
#define STEP_UP_IN 0.2f /* ... the first foot this far in from its edge */
#define STAND_LOWEST 0.18f /* solid things this high (scaled) you step onto rather than over (lower: you just walk onto them) ... */
#define STAND_HOLDS 4.0f /* ... kg at least (lying on the ground) ... */
#define STAND_ROOM 0.2f /* ... a foot's width on top will do (a log: balancing) ... */
#define BALANCE_SLOW 0.55f /* balancing all the way: this much slower */
#define STOOD_ON 0.08f /* m: feet no lower than this under its top: standing on it, not stepping over */
#define STAND_UP_TIME 0.4f /* ... one step up taking this long ... */
#define STAND_UP_HIGH 0.9f /* ... and this much more per meter high */
#define ONTO_HIGHEST 0.95f /* up onto things this high at most (scaled) ... */
#define ONTO_ROOM 0.4f /* ... with this much room on top to land ... */
#define ONTO_HOLDS 40.0f /* ... (loose: this heavy, else it would go over) ... */
#define ONTO_CLEAR 0.1f /* ... the feet this much over its edge ... */
#define ONTO_MOST 1.25f /* ... a full jump and the run-up at most ... */
#define ONTO_TIMING 1.1f /* ... over the edge just after the top of the jump */
static bool jumpOver(const Object *obj, const Object *o, Vec3 dir, float speed, float *v_up, float *center);
static bool startStepOver(Object *obj, Object *const *objects, int count, const Terrain *terrain);
static void stepOverMove(Object *obj, const Terrain *terrain, float dt);
static bool canJumpOver(const Object *obj, float speed);
#define PERSON_EASE 0.008f /* m per step people standing into each other move apart at most */
#define LAND_ON_MOST 0.12f /* characters get onto loose things only from about this close above */
static const Vec3 WORLD_UP = { 0.0f, 1.0f, 0.0f };
/* ---------- Geometry ---------- */
/* Half length of a collider projected onto an axis. */
static float projectRadius(const Collider *c, Vec3 axis) {
return fabsf(c->half_extents.x * vec3_dot(axis, c->axes[0])) +
fabsf(c->half_extents.y * vec3_dot(axis, c->axes[1])) +
fabsf(c->half_extents.z * vec3_dot(axis, c->axes[2]));
}
/* Narrow enough one way to stride across? (half that width) */
static float halfAcross(const Collider *c) {
float across = fminf(projectRadius(c, vec3(1.0f, 0.0f, 0.0f)), projectRadius(c, vec3(0.0f, 0.0f, 1.0f)));
for (int k = 0; k < 3; k++) {
Vec3 a = vec3(c->axes[k].x, 0.0f, c->axes[k].z);
if (vec3_length(a) > 0.3f) across = fminf(across, projectRadius(c, vec3_normalize(a)));
}
return across;
}
/* Kinds that are never stepped over (however low): people, buildings, trees,
the fire; a ladder only lying on the ground. */
static bool neverOver(const Object *o) {
switch (o->type) {
case OBJ_HOUSE: case OBJ_BARN: case OBJ_BUSH: case OBJ_FIREPLACE: case OBJ_DOG:
case OBJ_HUMAN: case OBJ_PLAYER: case OBJ_DECAL: case OBJ_PUDDLE: case OBJ_STINK: case OBJ_WALL: case OBJ_STRUCTURE: case OBJ_TENT:
return true;
case OBJ_TREE:
return !(o->toppled && o->topple >= 1.0f); /* (one lying on the ground: over it, or onto it) */
case OBJ_LADDER:
return o->leaning;
default:
return false;
}
}
/* Solid enough to stand on, and won't break under you: heavy or fixed, its
top flat (a box or a log lying: one of its sides faces straight up), not a
bottle, a table, a ladder or a bin. */
static bool standsOn(const Object *o) {
if (o->collider_count == 0 || neverOver(o) || o->is_held || o->lifted || o->broken) return false;
if (!o->is_static && o->weight < STAND_HOLDS) return false; /* (lying on the ground it holds you; too light, it slips away) */
switch (o->type) {
case OBJ_BOTTLE: case OBJ_VODKA: case OBJ_FURNITURE: case OBJ_BIN: case OBJ_SUPPLY: case OBJ_LADDER: case OBJ_FIREPLACE:
case OBJ_BRANCH: case OBJ_BAG: case OBJ_TRASH: case OBJ_AXE: case OBJ_SHEARS: case OBJ_CHAINSAW:
return false;
default:
break;
}
const Collider *c = &o->colliders[0];
return fabsf(c->axes[0].y) > 0.95f || fabsf(c->axes[1].y) > 0.95f || fabsf(c->axes[2].y) > 0.95f;
}
bool Physics_standsOn(const Object *o) { return standsOn(o); }
float Physics_footing(const Object *o) {
if (o->collider_count == 0) return 1e9f;
const Collider *c = &o->colliders[0];
if (fabsf(c->axes[0].y) < 0.95f && fabsf(c->axes[1].y) < 0.95f && fabsf(c->axes[2].y) < 0.95f) return 2.0f * halfAcross(c) * 0.6f; /* (rounded or tilted: less of it) */
return 2.0f * halfAcross(c);
}
float Physics_lowObstacle(const Object *o) {
if (neverOver(o) || o->is_held || o->lifted || o->collider_count == 0 || o->broken || o->thrown_by) return 0.0f;
const Collider *c = &o->colliders[0];
if (2.0f * halfAcross(c) > STEP_OVER_WIDTH) return 0.0f;
float height = 2.0f * projectRadius(c, WORLD_UP);
return height <= STEP_OVER_HEIGHT * 1.15f ? height : 0.0f;
}
/* Something low lying in a character's way that they step over instead of
walking into (logs, branches, firewood, a small crate, a bench): no
collision with the body, the feet go over it (see the world's clutter). */
bool Physics_stepsOver(const Object *who, const Object *o) {
if (!who->is_character || (o->weight < STEP_OVER_LIGHT && !o->is_static)) return false;
if (Physics_lowObstacle(o) <= 0.0f) return false;
const float s = who->look.height / 1.75f;
const Collider *c = &o->colliders[0];
float top = Object_colliderCenter(o, 0).y + projectRadius(c, WORLD_UP);
float feet = Object_colliderCenter(who, 0).y - projectRadius(&who->colliders[0], WORLD_UP);
if (top - feet < STOOD_ON) return false; /* (up on it: it holds you) */
if ((who->type == OBJ_PLAYER || who->type == OBJ_HUMAN) && standsOn(o) && top - feet < STAND_LOWEST * s) return false; /* (flat, a plank: you just walk onto it) */
return top - feet <= STEP_OVER_HEIGHT * s && 2.0f * halfAcross(c) <= STEP_OVER_WIDTH * s;
}
/*
* Separating axis test between collider ia of a and collider ib of b.
* On overlap returns the contact normal (pointing from b towards a)
* and the penetration depth along it.
*/
static bool collidersOverlap(const Object *a, int ia, const Object *b, int ib,
Vec3 *normal, float *depth) {
const Collider *ca = &a->colliders[ia];
const Collider *cb = &b->colliders[ib];
Vec3 between = vec3_sub(Object_colliderCenter(a, ia), Object_colliderCenter(b, ib));
/* Cheap bounding-sphere rejection */
float reach = vec3_length(ca->half_extents) + vec3_length(cb->half_extents);
if (vec3_dot(between, between) > reach * reach) return false;
Vec3 axes[15];
int n = 0;
for (int k = 0; k < 3; k++) axes[n++] = ca->axes[k];
for (int k = 0; k < 3; k++) axes[n++] = cb->axes[k];
for (int k = 0; k < 3; k++)
for (int l = 0; l < 3; l++)
axes[n++] = vec3_cross(ca->axes[k], cb->axes[l]);
float best = FLT_MAX;
Vec3 best_axis = WORLD_UP;
for (int k = 0; k < n; k++) {
if (vec3_length(axes[k]) < 1e-6f) continue; /* parallel edges */
Vec3 axis = vec3_normalize(axes[k]);
float d = projectRadius(ca, axis) + projectRadius(cb, axis) - fabsf(vec3_dot(between, axis));
if (d < 0.0f) return false;
if (d < best) { best = d; best_axis = axis; }
}
if (vec3_dot(between, best_axis) < 0.0f) best_axis = vec3_scale(best_axis, -1.0f);
*normal = best_axis;
*depth = best;
return true;
}
/* ---------- Contacts ---------- */
static void land(Object *obj, float lift) {
obj->position.y += lift;
if (obj->velocity.y < 0.0f) obj->velocity.y = 0.0f;
obj->is_grounded = true;
}
/* Pushes obj out along the normal and removes velocity into the surface. */
static void slideAlong(Object *obj, Vec3 normal, float depth) {
obj->position = vec3_add(obj->position, vec3_scale(normal, depth));
float into = vec3_dot(obj->velocity, normal);
if (into < 0.0f) obj->velocity = vec3_sub(obj->velocity, vec3_scale(normal, into));
}
/*
* Characters walking into a low, flat-topped obstacle step onto it.
* Returns true if the step was taken.
*/
/* Straight down from origin into the collider: how far to its surface (-1: misses it). */
#define STEP_PROBE 0.3f /* m ahead where a step lands */
#define SNAP_UP_MOST 0.22f /* m: a step up to this high is just taken (stairs); higher, the body steps up onto it */
static void beginStepOver(Object *obj, const Terrain *terrain, Vec3 dir, float near, float far, float top, float land_at, bool up);
static float rayDownInto(Vec3 origin, const Object *o, int ic) {
const Collider *c = &o->colliders[ic];
Vec3 rel = vec3_sub(origin, Object_colliderCenter(o, ic));
const float half[3] = { c->half_extents.x, c->half_extents.y, c->half_extents.z };
float t0 = 0.0f, t1 = 1e9f;
for (int k = 0; k < 3; k++) {
float p = vec3_dot(rel, c->axes[k]), d = -c->axes[k].y; /* (the ray goes down) */
if (fabsf(d) < 1e-6f) {
if (fabsf(p) > half[k]) return -1.0f;
continue;
}
float a = (-half[k] - p) / d, b = (half[k] - p) / d;
if (a > b) { float x = a; a = b; b = x; }
t0 = fmaxf(t0, a);
t1 = fminf(t1, b);
}
return t0 <= t1 ? t0 : -1.0f;
}
static bool tryStepUp(Object *obj, int io, const Object *other, int ic, Vec3 normal, float most) {
if (!obj->is_character) return false;
/* Only walking into it, not brushing past along its side */
Vec3 mv = vec3(obj->movement.x, 0.0f, obj->movement.z), out = vec3(normal.x, 0.0f, normal.z);
if (vec3_length(mv) < 0.1f || vec3_length(out) < 1e-4f) return false;
if (vec3_dot(vec3_normalize(mv), vec3_normalize(out)) > -0.4f) return false;
const Collider *c = &other->colliders[ic];
if (fmaxf(fabsf(c->axes[0].y), fmaxf(fabsf(c->axes[1].y), fabsf(c->axes[2].y))) < WALKABLE_SLOPE) return false; /* no face up flat enough (whichever axis: firewood lies on its side) */
float top = Object_colliderCenter(other, ic).y + projectRadius(c, WORLD_UP);
if (fmaxf(fabsf(c->axes[0].y), fmaxf(fabsf(c->axes[1].y), fabsf(c->axes[2].y))) < 0.98f) {
/* tilted (a ramp): its surface just in front of you, not its highest corner */
Vec3 ahead = vec3_add(obj->position, vec3_scale(vec3_normalize(mv), STEP_PROBE));
float t = rayDownInto(vec3(ahead.x, top + 0.5f, ahead.z), other, ic);
if (t < 0.0f) return false;
top = top + 0.5f - t;
}
float bottom = Object_colliderCenter(obj, io).y - projectRadius(&obj->colliders[io], WORLD_UP);
float rise = top - bottom;
if (rise <= 0.0f || rise > most) return false;
/* Higher than a stair (the side of a ramp, a crate): one step up onto it, not a jolt */
const Body *b = &obj->body;
const float s = obj->look.height / 1.75f;
if (rise > SNAP_UP_MOST * s && (obj->type == OBJ_PLAYER || obj->type == OBJ_HUMAN) && obj->over_t <= 0.0f &&
b->pose == POSE_UP && !b->airborne && b->stumble <= 0.0f && !obj->sitting && !obj->climbing && !obj->on_ladder &&
!obj->hands.grabbed) {
beginStepOver(obj, NULL, vec3_normalize(mv), 0.1f * s, STEP_PROBE + 0.3f, rise, STEP_PROBE, true);
return true;
}
land(obj, rise);
return true;
}
/* How much of a push obj can transfer to something of other_weight (0..1). */
static float pushFactor(const Object *obj, float other_weight) {
if (obj->strength <= 0.0f || obj->weight <= 0.0f) return 0.0f;
float max_weight = obj->weight * (obj->strength / 50.0f);
if (other_weight <= 0.0f) return 1.0f;
if (other_weight >= max_weight) return 0.0f;
return 1.0f - other_weight / max_weight;
}
/* obj (moving) pushes a dynamic object sideways. normal points from other to obj. */
static void push(Object *obj, Object *other, Vec3 normal, float depth) {
float share = obj->is_character && other->weight > WALK_PUSH_WEIGHT ? 0.0f : pushFactor(obj, other->weight);
bool grips = obj->is_character && other->type == OBJ_LADDER; /* (rungs and feet catch on the ground: it hardly slides) */
if (grips) share *= LADDER_GRIP;
float d = fminf(depth, MAX_PUSH_CORRECTION);
if (obj->is_character && other->is_character) d = fminf(d, PERSON_EASE); /* (people standing close ease apart, no jolt) */
other->position = vec3_sub(other->position, vec3_scale(normal, d * share));
slideAlong(obj, normal, d * (1.0f - share));
if (share <= 0.0f) return;
Vec3 away = vec3(-normal.x, 0.0f, -normal.z); /* horizontal, towards other */
if (vec3_length(away) < 1e-6f) return;
away = vec3_normalize(away);
/* The other object gets going at most as fast as we walk into it:
something just touching it (like a dropped bottle) doesn't shove it away. */
float pusher = vec3_dot(vec3_add(obj->movement, obj->velocity), away);
float along = vec3_dot(other->velocity, away);
float target = fminf(pusher * share, grips ? LADDER_SLIDE : PUSH_STRENGTH);
if (pusher > TOPPLE_SPEED) Object_topple(other, away);
/* Kicked at a run: a bottle flies off and may break where it lands */
if (obj->is_character && Object_isBottle(other) && pusher > KICK_SPEED) {
other->thrown_by = obj;
other->thrown_time = 0.0f;
}
if (target <= along) return;
other->velocity = vec3_add(other->velocity, vec3_scale(away, target - along));
obj->velocity = vec3_sub(obj->velocity, vec3_scale(away, (target - along) * PUSH_RECOIL));
/* Off-center pushes spin the other object around Y */
Vec3 lever = vec3_sub(obj->position, other->position);
Vec3 impulse = vec3_scale(away, target - along);
float torque = (lever.x * impulse.z - lever.z * impulse.x) * SPIN_FACTOR;
other->angular_velocity.y += torque / (other->weight > 0.0f ? other->weight : 1.0f);
}
bool Physics_overlap(const Object *a, int ia, const Object *b, int ib) {
Vec3 normal;
float depth;
return collidersOverlap(a, ia, b, ib, &normal, &depth);
}
float Physics_penetration(const Object *a, const Object *b) {
float most = 0.0f;
for (int ia = 0; ia < a->collider_count; ia++)
for (int ib = 0; ib < b->collider_count; ib++) {
Vec3 normal;
float depth;
if (collidersOverlap(a, ia, b, ib, &normal, &depth)) most = fmaxf(most, depth);
}
return most;
}
/* Something (not a person) touches a person. normal points from the person to it. */
static bool touchPerson(Object *obj, Object *person, Vec3 normal, float depth) {
float into = -vec3_dot(obj->velocity, normal);
if (into > 1.0f) person->knock = vec3_add(person->knock, vec3_scale(normal, -into * obj->weight));
if (normal.y < WALKABLE_SLOPE) return false;
/* On top of someone: slide off sideways instead of resting there */
Vec3 off = vec3_sub(obj->position, person->position);
off.y = 0.0f;
if (vec3_length(off) < 1e-3f) off = vec3(1.0f, 0.0f, 0.0f);
off = vec3_normalize(off);
slideAlong(obj, off, fminf(depth, 0.05f) + 0.01f);
obj->velocity = vec3_add(obj->velocity, vec3_scale(off, 0.5f));
return true;
}
#define BUMP_WEIGHT 15.0f /* lighter things just get kicked aside (not a ladder) */
/* A walking person runs into something solid: how hard (see the world: drunk people fall). */
static void noteBump(Object *obj, const Object *other, Vec3 normal) {
if (!obj->is_character || (!other->is_static && !other->is_character && other->weight < BUMP_WEIGHT && other->type != OBJ_LADDER)) return;
Vec3 n = vec3(normal.x, 0.0f, normal.z);
if (vec3_length(n) < 1e-4f) return;
n = vec3_normalize(n);
float into = -vec3_dot(vec3_add(obj->movement, obj->velocity), n);
if (into > obj->bump) {
obj->bump = into;
obj->bump_normal = n;
float top = -1e9f;
for (int c = 0; c < other->collider_count; c++)
top = fmaxf(top, Object_colliderCenter(other, c).y + projectRadius(&other->colliders[c], WORLD_UP));
obj->bump_top = top - obj->position.y;
obj->bump_toe = other->type == OBJ_LADDER && !other->leaning;
}
}
/* Is p (a body's middle) over the collider's top, give or take margin? */
#define OVER_EDGE 0.12f /* m: the middle this far past an edge still stands (the feet are wider) */
static bool middleOver(const Object *o, int ic, Vec3 p, float margin) {
const Collider *c = &o->colliders[ic];
Vec3 rel = vec3_sub(p, Object_colliderCenter(o, ic));
rel.y = 0.0f;
const float half[3] = { c->half_extents.x, c->half_extents.y, c->half_extents.z };
for (int a = 0; a < 3; a++) {
Vec3 axis = vec3(c->axes[a].x, 0.0f, c->axes[a].z);
float len = vec3_length(axis);
if (len < 0.3f) continue; /* (an upright axis) */
Vec3 dir = vec3_scale(axis, 1.0f / len);
if (fabsf(vec3_dot(rel, dir)) > projectRadius(c, dir) + margin) return false; /* (its whole outline seen from above) */
}
(void)half;
return true;
}
/* ... over any part of it (a house is walls and roofs; standing on one, touching another is fine) */
static bool middleOverAny(const Object *o, Vec3 p, float margin) {
for (int c = 0; c < o->collider_count; c++)
if (middleOver(o, c, p, margin)) return true;
return false;
}
/* Lying on a slope (a ramp, a roof): down it it goes once the slope beats the
grip - round things roll off early, the rest only on steeper ones, faster
the steeper. */
#define GRIP_ROUND 0.12f
#define GRIP_FLAT 0.5f
static float g_step_dt = 1.0f / 60.0f;
static bool rolls(const Object *o) {
switch (o->type) {
case OBJ_APPLE:
return true;
case OBJ_TOY:
return o->variant == 0; /* (the ball) */
case OBJ_BOTTLE: case OBJ_VODKA: case OBJ_WATER: case OBJ_LOG: case OBJ_CYLINDER: /* (round: lying on its side, not standing) */
return o->collider_count > 0 && fabsf(o->colliders[0].axes[1].y) < 0.5f;
default:
return false;
}
}
#define DROP_TIPS 0.85f /* a dropped bottle falls over this often when it lands */
#define GRIP_MOVING 0.6f /* sliding already: it grips this much less (a nudge on a slope can set it going) */
static void slideDown(Object *obj, Vec3 normal) {
float cos_a = clampf(normal.y, 0.0f, 1.0f), sin_a = sqrtf(fmaxf(0.0f, 1.0f - cos_a * cos_a));
bool moving = obj->velocity.x * obj->velocity.x + obj->velocity.z * obj->velocity.z > 0.1f * 0.1f;
float pull = GRAVITY * (sin_a - (rolls(obj) ? GRIP_ROUND : GRIP_FLAT) * (moving ? GRIP_MOVING : 1.0f) * cos_a);
if (pull <= 0.0f) return;
Vec3 down = vec3(normal.x * normal.y, normal.y * normal.y - 1.0f, normal.z * normal.y); /* (down the slope) */
if (vec3_length(down) < 1e-4f) return;
down = vec3_normalize(down);
float along = vec3_dot(obj->velocity, down);
float goal = fmaxf(along + pull * g_step_dt, 0.06f); /* (no resting on it: faster than "lying still") */
obj->velocity = vec3_add(obj->velocity, vec3_scale(down, goal - along));
}
static void resolveContact(Object *obj, int io, Object *other, int ic, Vec3 normal, float depth) {
obj->touched = other;
if (other->is_character && !obj->is_character && touchPerson(obj, other, normal, depth)) return;
/* Standing on something: lift straight up, so walkable slopes don't slide.
(Walking into something, a person doesn't pop up onto it: it's in the way.) */
if (normal.y >= WALKABLE_SLOPE && obj->is_character && !other->is_character && !middleOverAny(other, obj->position, OVER_EDGE)) {
/* only a corner of the body on it (the middle's off its edge): no standing on air - off it you slide */
Vec3 away = vec3_sub(obj->position, Object_colliderCenter(other, ic));
away.y = 0.0f;
float len = vec3_length(away);
obj->position = vec3_add(obj->position, len > 1e-4f ? vec3_scale(away, (fminf(depth, 0.03f) + 0.01f) / len) : vec3(0.01f, 0.0f, 0.0f));
Object_syncColliders(obj);
return;
}
if (normal.y >= WALKABLE_SLOPE && !(obj->is_character && depth / normal.y > LAND_ON_MOST)) {
land(obj, depth / normal.y);
if (obj->is_character && !other->is_character) obj->standing_on = other;
if (!obj->is_character && !obj->lifted && normal.y < 0.995f) slideDown(obj, normal);
return;
}
if (normal.y >= WALKABLE_SLOPE) {
Vec3 away = vec3_sub(Object_colliderCenter(obj, io), Object_colliderCenter(other, ic));
away.y = 0.0f;
normal = vec3_length(away) > 1e-4f ? vec3_normalize(away) : vec3(1.0f, 0.0f, 0.0f);
depth = fminf(depth, 0.05f);
}
if (normal.y <= CEILING_SLOPE) {
/* Dynamic objects below are resolved when they rest on us. */
if (!other->is_static) return;
if (obj->is_character) { /* (the feet at its top - a roof met at a valley, a floor's edge: up onto it, never pushed down through) */
float t = rayDownInto(vec3(obj->position.x, obj->position.y + 2.0f, obj->position.z), other, ic);
float rise = t >= 0.0f ? 2.0f - t : -1.0f;
if (rise >= -0.05f && rise < SINK_MOST) { land(obj, fmaxf(rise, 0.0f)); obj->standing_on = other; return; }
}
if (obj->on_terrain) { /* lying on the ground under it (a shelf): squeezed out sideways, never into the ground */
Vec3 away = vec3_sub(Object_colliderCenter(obj, io), Object_colliderCenter(other, ic));
away.y = 0.0f;
float len = vec3_length(away);
obj->position = vec3_add(obj->position, len > 1e-4f ? vec3_scale(away, fminf(depth, 0.02f) / len) : vec3(0.02f, 0.0f, 0.0f));
return;
}
slideAlong(obj, normal, depth);
return;
}
/* Wall or steep slope */
if (other->is_static) {
if (tryStepUp(obj, io, other, ic, normal, obj->step_height)) return;
noteBump(obj, other, normal);
slideAlong(obj, normal, depth);
} else {
if ((obj->type == OBJ_PLAYER || obj->type == OBJ_HUMAN) && standsOn(other) && tryStepUp(obj, io, other, ic, normal, STAND_LOWEST)) return; /* (a plank: onto it) */
noteBump(obj, other, normal);
push(obj, other, normal, depth);
}
}
static void resolveObjects(Object *obj, Object *const *objects, int count) {
for (int i = 0; i < count; i++) {
Object *other = objects[i];
if (other == obj || other->is_held || other->collider_count == 0) continue;
if ((obj->thrown_by == other || other->thrown_by == obj) &&
fmaxf(obj->thrown_time, other->thrown_time) < THROW_IGNORE_TIME) continue;
/* A pushed/pulled object is kept at arm's length by the hands instead */
if (obj->hands.grabbed == other || other->hands.grabbed == obj) continue;
/* Sitting on it */
if (obj->seat == other || other->seat == obj) continue; /* (sitting, or just getting up off it) */
if (obj->in_tent == other || other->in_tent == obj) continue; /* (lying in it) */
/* Stepping over it */
if (Physics_stepsOver(obj, other) || Physics_stepsOver(other, obj)) continue;
for (int io = 0; io < obj->collider_count; io++) {
for (int ic = 0; ic < other->collider_count; ic++) {
Vec3 normal;
float depth;
if (collidersOverlap(obj, io, other, ic, &normal, &depth))
resolveContact(obj, io, other, ic, normal, depth);
}
}
}
}
/* Does the segment a-b go into this box collider (more than a skin), from outside? */
static bool segmentThrough(Vec3 a, Vec3 b, const Object *o, int ic) {
const Collider *c = &o->colliders[ic];
Vec3 center = Object_colliderCenter(o, ic), d = vec3_sub(b, a), rel = vec3_sub(a, center);
const float half[3] = { c->half_extents.x - 0.02f, c->half_extents.y - 0.02f, c->half_extents.z - 0.02f };
bool a_in = true; /* (already in it: getting out is fine) */
for (int k = 0; k < 3; k++) a_in = a_in && fabsf(vec3_dot(rel, c->axes[k])) < c->half_extents.x * (k == 0) + c->half_extents.y * (k == 1) + c->half_extents.z * (k == 2);
if (a_in) return false;
float t0 = 0.0f, t1 = 1.0f;
for (int k = 0; k < 3; k++) {
if (half[k] <= 0.0f) return false;
float o_ = vec3_dot(rel, c->axes[k]), v = vec3_dot(d, c->axes[k]);
if (fabsf(v) < 1e-7f) {
if (fabsf(o_) > half[k]) return false;
continue;
}
float ta = (-half[k] - o_) / v, tb = (half[k] - o_) / v;
if (ta > tb) { float x = ta; ta = tb; tb = x; }
t0 = fmaxf(t0, ta);
t1 = fminf(t1, tb);
if (t0 > t1) return false;
}
return true;
}
/* A person never ends up on the other side of a wall: if this step (with
all its pushing about) took the hips through something solid and fixed,
they stay where they were. */
static void neverThrough(Object *obj, Vec3 start, Object *const *objects, int count) {
const float s = obj->look.height / 1.75f;
Vec3 a = vec3_add(start, vec3(0.0f, 0.9f * s, 0.0f)), b = vec3_add(obj->position, vec3(0.0f, 0.9f * s, 0.0f));
if (vec3_length(vec3_sub(b, a)) < 1e-4f) return;
for (int i = 0; i < count; i++) {
const Object *o = objects[i];
if (!o->is_static || o == obj || o->collider_count == 0 || o->type == OBJ_DECAL) continue;
for (int c = 0; c < o->collider_count; c++) {
if (!segmentThrough(a, b, o, c)) continue;
obj->position.x = start.x;
obj->position.z = start.z;
obj->velocity.x = obj->velocity.z = 0.0f;
Object_syncColliders(obj);
return;
}
}
}
/* Nobody goes down through a floor or a roof: the feet went in at its top this step - back up onto it. */
static void neverThroughFloor(Object *obj, Vec3 start, Object *const *objects, int count) {
if (obj->position.y >= start.y) return;
Vec3 a = vec3_add(start, vec3(0.0f, 0.05f, 0.0f)), b = vec3_add(obj->position, vec3(0.0f, 0.05f, 0.0f));
float best = -1e9f;
for (int i = 0; i < count; i++) {
const Object *o = objects[i];
if (!o->is_static || o == obj || o->collider_count == 0 || o->type == OBJ_DECAL) continue;
for (int c = 0; c < o->collider_count; c++) {
if (!segmentThrough(a, b, o, c)) continue;
float t = rayDownInto(vec3(b.x, a.y, b.z), o, c);
if (t < 0.0f) continue;
float top = a.y - t;
if (top > b.y && top > best) best = top;
}
}
if (best < -1e8f) return;
obj->position.y = best - 0.05f;
if (obj->velocity.y < 0.0f) obj->velocity.y = 0.0f;
obj->is_grounded = true;
Object_syncColliders(obj);
}
/* The eight corners of a collider. */
static void corners(const Object *obj, int i, Vec3 out[8]) {
const Collider *c = &obj->colliders[i];
Vec3 center = Object_colliderCenter(obj, i);
for (int k = 0; k < 8; k++) {
Vec3 p = center;
p = vec3_add(p, vec3_scale(c->axes[0], (k & 1 ? 1.0f : -1.0f) * c->half_extents.x));
p = vec3_add(p, vec3_scale(c->axes[1], (k & 2 ? 1.0f : -1.0f) * c->half_extents.y));
p = vec3_add(p, vec3_scale(c->axes[2], (k & 4 ? 1.0f : -1.0f) * c->half_extents.z));
out[k] = p;
}
}
/* People: the highest ground under the middle and the corners of their footprint
(also where they're kept down to walking down a slope: the same ground, or on
a steep one they'd be pushed up onto the uphill corner one frame and snapped
down to the middle the next - the view shook). */
static float footprintGround(const Collider *c, Vec3 center, const Terrain *terrain) {
float ex = projectRadius(c, vec3(1, 0, 0)) * 0.7f;
float ez = projectRadius(c, vec3(0, 0, 1)) * 0.7f;
float ground = Terrain_height(terrain, center.x, center.z);
ground = fmaxf(ground, Terrain_height(terrain, center.x - ex, center.z - ez));
ground = fmaxf(ground, Terrain_height(terrain, center.x + ex, center.z - ez));
ground = fmaxf(ground, Terrain_height(terrain, center.x - ex, center.z + ez));
return fmaxf(ground, Terrain_height(terrain, center.x + ex, center.z + ez));
}
/* Keeps every collider above the terrain surface. Things: each corner over
the ground right under it (they rest on their real contact points, not on
the highest ground anywhere under them). People: their feet do the rest. */
static void resolveTerrain(Object *obj, const Terrain *terrain) {
if (!obj->is_character) {
float lift = -1e9f;
const int parts = obj->type == OBJ_TREE ? 1 : obj->collider_count; /* (a tree lies on its trunk: the crown gives) */
for (int i = 0; i < parts; i++) {
Vec3 p[8];
corners(obj, i, p);
for (int k = 0; k < 8; k++) lift = fmaxf(lift, Terrain_height(terrain, p[k].x, p[k].z) - p[k].y);
}
if (lift > 0.0f) {
land(obj, lift);
obj->on_terrain = true;
} else if (lift > -0.004f && obj->velocity.y <= 0.0f) {
obj->on_terrain = true; /* (resting right on it) */
obj->is_grounded = true;
}
if (obj->on_terrain && !obj->lifted && obj->collider_count > 0 && obj->type != OBJ_LADDER && !obj->leaning) { /* on a slope: down it, if it's steep enough for it */
Vec3 c = Object_colliderCenter(obj, 0);
const float d = 0.3f;
Vec3 n = vec3_normalize(vec3(Terrain_height(terrain, c.x - d, c.z) - Terrain_height(terrain, c.x + d, c.z), 2.0f * d,
Terrain_height(terrain, c.x, c.z - d) - Terrain_height(terrain, c.x, c.z + d)));
if (n.y < 0.995f) slideDown(obj, n);
}
return;
}
for (int i = 0; i < obj->collider_count; i++) {
const Collider *c = &obj->colliders[i];
Vec3 center = Object_colliderCenter(obj, i);
float bottom = center.y - projectRadius(c, WORLD_UP);
float ground = footprintGround(c, center, terrain);
if (bottom < ground) {
land(obj, ground - bottom);
obj->on_terrain = true;
}
}
}
/* Lying on the ground, come to rest: tilted to the ground under it - its
bottom face along the slope there, down onto it - instead of lying level on
its highest point (one end in the air). */
#define SETTLE_AFTER 0.3f /* s at rest before it settles */
#define SETTLE_STEEPEST 0.5f /* rad: no steeper (it would slide or roll off) */
#define SETTLE_SMALL 0.12f /* m: smaller things just lie as they are */
static Vec3 rotateAbout(Vec3 v, Vec3 axis, float angle) {
float c = cosf(angle), s = sinf(angle);
return vec3_add(vec3_add(vec3_scale(v, c), vec3_scale(vec3_cross(axis, v), s)), vec3_scale(axis, vec3_dot(axis, v) * (1.0f - c)));
}
static void settleOnGround(Object *obj, const Terrain *terrain) {
if (obj->is_character || obj->type == OBJ_TREE || obj->type == OBJ_DOG || obj->collider_count == 0 || obj->lifted ||
obj->is_held || obj->thrown_by || (obj->topple > 0.0f && obj->topple < 1.0f) || obj->touched) return;
if ((obj->type == OBJ_AXE || obj->type == OBJ_SHEARS) && fabsf(obj->colliders[0].axes[1].y) < 0.7f &&
fabsf(obj->colliders[0].axes[0].y) < 0.7f) { /* (an axe lying with its blade up or down: it tips onto its side) */
Vec3 handle = obj->colliders[0].axes[1];
obj->rotation = vec3(0.0f, atan2f(handle.z, handle.x) + PI_F, PI_F / 2.0f);
Object_syncColliders(obj);
}
const Collider *c = &obj->colliders[0];
const float half[3] = { c->half_extents.x, c->half_extents.y, c->half_extents.z };
int k = 0;
for (int a = 1; a < 3; a++) if (fabsf(c->axes[a].y) > fabsf(c->axes[k].y)) k = a;
if (fabsf(c->axes[k].y) < 0.8f) return; /* (on an edge: as it is) */
int a1 = (k + 1) % 3, a2 = (k + 2) % 3;
if (half[a1] < SETTLE_SMALL && half[a2] < SETTLE_SMALL) return;
Vec3 up = vec3_scale(c->axes[k], c->axes[k].y > 0.0f ? 1.0f : -1.0f);
Vec3 base = vec3_sub(Object_colliderCenter(obj, 0), vec3_scale(up, half[k]));
Vec3 u = vec3_scale(c->axes[a1], half[a1]), v = vec3_scale(c->axes[a2], half[a2]), q[4];
q[0] = vec3_sub(vec3_sub(base, u), v);
q[1] = vec3_sub(vec3_add(base, u), v);
q[2] = vec3_add(vec3_add(base, u), v);
q[3] = vec3_add(vec3_sub(base, u), v);
for (int i = 0; i < 4; i++) q[i].y = Terrain_height(terrain, q[i].x, q[i].z);
Vec3 n = vec3_cross(vec3_sub(q[2], q[0]), vec3_sub(q[3], q[1]));
if (vec3_length(n) < 1e-6f) return;
n = vec3_normalize(n);
if (n.y < 0.0f) n = vec3_scale(n, -1.0f);
float angle = acosf(clampf(vec3_dot(up, n), -1.0f, 1.0f));
if (angle > 1e-3f) {
Vec3 axis = vec3_cross(up, n);
if (vec3_length(axis) < 1e-6f) return;
axis = vec3_normalize(axis);
float tilt = fminf(angle, SETTLE_STEEPEST);
Mat4 r = mat4_rotate_yxz(obj->rotation);
Vec3 y = rotateAbout(mat4_mul_dir(r, vec3(0, 1, 0)), axis, tilt), z = rotateAbout(mat4_mul_dir(r, vec3(0, 0, 1)), axis, tilt);
obj->rotation = euler_yxz_from_axes(vec3_normalize(y), vec3_normalize(z));
Object_syncColliders(obj);
}
/* down (or up) onto it: the lowest gap closed */
float lift = -1e9f;
for (int i = 0; i < obj->collider_count; i++) {
Vec3 p[8];
corners(obj, i, p);
for (int j = 0; j < 8; j++) lift = fmaxf(lift, Terrain_height(terrain, p[j].x, p[j].z) - p[j].y);
}
obj->position.y += lift;
Object_syncColliders(obj);
}
static void snapToTerrain(Object *obj, const Terrain *terrain) {
const Collider *c = &obj->colliders[0];
Vec3 center = Object_colliderCenter(obj, 0);
float bottom = center.y - projectRadius(c, WORLD_UP);
float gap = bottom - footprintGround(c, center, terrain);
if (gap > 0.0f && gap < GROUND_SNAP) {
land(obj, -gap);
obj->on_terrain = true;
}
}
static void applyFriction(Object *obj, float dt) {
float friction = FRICTION * fminf(obj->weight, FRICTION_MAX_WEIGHT);
obj->velocity.x -= obj->velocity.x * friction * dt;
obj->velocity.z -= obj->velocity.z * friction * dt;
/* Resting on something: sliding stops soon, however light the object */
if (obj->is_grounded) {
float speed = sqrtf(obj->velocity.x * obj->velocity.x + obj->velocity.z * obj->velocity.z);
float keep = speed > 1e-6f ? fmaxf(0.0f, speed - GROUND_FRICTION * dt) / speed : 0.0f;
obj->velocity.x *= keep;
obj->velocity.z *= keep;
}
if (fabsf(obj->velocity.x) < REST_VELOCITY) obj->velocity.x = 0.0f;
if (fabsf(obj->velocity.z) < REST_VELOCITY) obj->velocity.z = 0.0f;
}
/* Walking uphill is slower, downhill a little faster. */
static Vec3 slopeAdjusted(const Object *obj, const Terrain *terrain) {
Vec3 m = obj->movement;
float speed = sqrtf(m.x * m.x + m.z * m.z);
if (speed < 0.05f) return m;
const float probe = 0.5f;
float ahead = Terrain_height(terrain, obj->position.x + m.x / speed * probe, obj->position.z + m.z / speed * probe);
float grade = (ahead - Terrain_height(terrain, obj->position.x, obj->position.z)) / probe;
float k = grade > 0.0f ? 1.0f - clampf(grade * 1.2f, 0.0f, 0.4f) : 1.0f + clampf(-grade * 0.5f, 0.0f, 0.15f);
k *= 1.0f - 0.3f * Wetness_mud(&terrain->wet, obj->position.x, obj->position.z); /* mud holds the feet */
return vec3(m.x * k, m.y, m.z * k);
}
#define CROSS_SPEED 0.6f /* m/s the body carries on over something when you stop half way */
/* Half way over something and stopping (or made to): the body carries on
over it, after the foot that went over. Walking back takes you back. */
static Vec3 carryOver(const Object *obj, Vec3 m) {
const Body *b = &obj->body;
if (!b->crossing || Body_isBusy(b) || obj->sitting) return m; /* (sitting down: over the seat on purpose) */
float speed = sqrtf(m.x * m.x + m.z * m.z);
if (!b->cross_force && speed > 0.3f) return m; /* (walking on, or back: as you like) */
const float s = obj->look.height / 1.75f;
float go = CROSS_SPEED * s * smoothstepf(0.0f, 0.15f * s, b->cross_left); /* (easing in at the end) */
return vec3(b->cross_dir.x * fmaxf(go, 0.15f * s), m.y, b->cross_dir.z * fmaxf(go, 0.15f * s));
}
/* Something to step over ahead (or underfoot): slow down for it, the more the higher it is. */
static Vec3 steppingOver(const Object *obj, Vec3 m, Object *const *objects, int count) {
float speed = sqrtf(m.x * m.x + m.z * m.z);
/* A standing leg the body would drag through what it stands behind: the
body waits there for the other foot to come down, then that leg comes up */
for (int k = 0; k < 2 && vec3_length(obj->shoved) < 0.5f && !obj->standing_on; k++) { /* (a load shoving into us: the legs get out of its way anyhow) */
const Foot *ft = &obj->body.feet[k];
if (!ft->stepping && ft->into > LEG_WAIT && obj->body.feet[1 - k].stepping && speed > LEG_WAIT_SPEED &&
speed <= STEP_OVER_RUNNING) { /* (running, the foot leaps off instead) */
m.x *= LEG_WAIT_SPEED / speed;
m.z *= LEG_WAIT_SPEED / speed;
return m;
}
}
if (speed < STEP_OVER_SPEED) return m;
Vec3 dir = vec3(m.x / speed, 0.0f, m.z / speed);
const float s = obj->look.height / 1.75f;
float most = 1e9f;
bool near = false;
const bool jumper = canJumpOver(obj, speed);
for (int i = 0; i < count; i++) {
const Object *o = objects[i];
if (o == obj->standing_on) continue; /* (walking along on top of it) */
if ((o->weight < STEP_OVER_LIGHT && !o->is_static) || Physics_lowObstacle(o) <= 0.0f) continue; /* (low for its own sake) */
float jv, jc;
if (jumper && jumpOver(obj, o, dir, speed, &jv, &jc)) continue; /* (going to jump it: no slowing down, the run-up counts) */
const Collider *c = &o->colliders[0];
Vec3 rel = vec3_sub(Object_colliderCenter(o, 0), obj->position);
rel.y = 0.0f;
float reach = projectRadius(c, dir), side = projectRadius(c, vec3(-dir.z, 0.0f, dir.x));
float ahead = vec3_dot(rel, dir), beside = fabsf(rel.x * -dir.z + rel.z * dir.x);
if (beside > side + 0.3f || ahead + reach < -0.2f || ahead - reach > STEP_OVER_LOOK) continue;
/* the higher, the more careful: low things are run over, knee high is a careful step */
float high = smoothstepf(0.04f, 0.3f, 2.0f * projectRadius(c, WORLD_UP) / s);
float careful = (STEP_OVER_SPEED + (STEP_OVER_RUN - STEP_OVER_SPEED) * (1.0f - high)) * (0.8f + 0.4f * obj->fitness); /* (sporty: quicker over it) */
if (speed > STEP_OVER_RUNNING) careful = fmaxf(careful, speed * (1.0f - 0.35f * high)); /* running: taken in a long stride */
most = fminf(most, careful + STEP_OVER_SLOWING * fmaxf(0.0f, ahead - reach));
near = true;
}
/* a foot on its way over something: the body doesn't walk on without it,
nor leave a standing foot further behind than the leg reaches */
bool over = false;
for (int k = 0; k < 2; k++) {
const Foot *ft = &obj->body.feet[k];
if (ft->stepping && ft->over > 0.15f * s && speed <= STEP_OVER_RUNNING) {
most = fminf(most, OVER_STEP_SPEED + 4.0f * fmaxf(0.0f, 0.45f * s - ft->over));
over = true;
}
}
if ((over || near) && speed <= STEP_OVER_RUNNING) { /* (running: the feet leap, see Body) */
const float left = LEFT_BEHIND * s + 0.12f * fminf(speed, obj->ground_speed); /* (strides are longer at a run) */
for (int k = 0; k < 2; k++) {
const Foot *ft = &obj->body.feet[k];
float behind = -vec3_dot(vec3_sub(ft->ankle, obj->position), dir);
if (!ft->stepping && behind > left)
most = fminf(most, fmaxf(0.3f, speed * (1.0f - smoothstepf(left, left + 0.25f * s, behind))));
}
}
if (speed <= most) return m;
return vec3(m.x * most / speed, m.y, m.z * most / speed);
}
/* Jogging or faster at something low and narrow: over it in a jump, timed
so the top of the jump is over it and as high as it takes (the feet tuck
up a little), the speed kept. Too high or too deep for that jump: no jump
(stepped over, slower). */
static bool jumpOver(const Object *obj, const Object *o, Vec3 dir, float speed, float *v_up, float *center) {
/* (low for its own sake - uphill it may be a bit higher above your feet: the jump is higher) */
if (!obj->is_character || (o->weight < STEP_OVER_LIGHT && !o->is_static) || Physics_lowObstacle(o) <= 0.0f) return false;
const float s = obj->look.height / 1.75f;
const Collider *c = &o->colliders[0];
Vec3 rel = vec3_sub(Object_colliderCenter(o, 0), obj->position);
rel.y = 0.0f;
float reach = projectRadius(c, dir), side = projectRadius(c, vec3(-dir.z, 0.0f, dir.x));
float ahead = vec3_dot(rel, dir), beside = fabsf(rel.x * -dir.z + rel.z * dir.x);
float height = Object_colliderCenter(o, 0).y + projectRadius(c, WORLD_UP) - obj->position.y;
float bottom = Object_colliderCenter(o, 0).y - projectRadius(c, WORLD_UP) - obj->position.y;
if (bottom > (0.2f + 0.15f * fmaxf(ahead, 0.0f)) * s || bottom < -0.5f * s - 0.15f * fmaxf(ahead, 0.0f)) return false; /* (not on the ground in front of you) */
if (beside > side + 0.2f || ahead - reach < 0.05f * s || ahead - reach > JUMP_LOOK * s) return false;
if (height < HURDLE_LOWEST * s || 2.0f * reach > HURDLE_DEPTH * s) return false;
/* high enough to clear it, long enough to land past it */
float lift = fmaxf(height + (JUMP_CLEAR - JUMP_TUCK) * s, 0.1f * s);
float v = fmaxf(sqrtf(2.0f * GRAVITY * lift), GRAVITY * (reach + JUMP_PAST * s) / fmaxf(speed, 0.1f));
if (v > Object_jumpSpeed(obj) * JUMP_MOST) return false; /* (that jump isn't in them) */
*v_up = v;
*center = ahead;
return true;
}
static bool canJumpOver(const Object *obj, float speed) {
return obj->type != OBJ_DOG && speed >= JUMP_OVER_SPEED && obj->needs.stamina >= 0.15f && obj->drunk <= 0.6f &&
obj->hands.state[HAND_LEFT] != HAND_GRABBING && obj->hands.state[HAND_RIGHT] != HAND_GRABBING &&
!obj->body.airborne && obj->body.pose == POSE_UP && !obj->sitting && !obj->climbing && !obj->on_ladder;
}
/* Jogging or faster at something higher than a step over (a block, a crate)
with room on top: up onto it in a jump, timed to come down onto it. */
static bool jumpOnto(const Object *obj, const Object *o, Vec3 dir, float *v_up, float *edge_out) {
if (o->collider_count == 0 || o->is_character || o->is_held || o->lifted || o->type == OBJ_DOG ||
o->type == OBJ_TREE || o->type == OBJ_BUSH || o->type == OBJ_LADDER || o->type == OBJ_FIREPLACE ||
o->type == OBJ_DECAL || o->type == OBJ_PUDDLE || o->type == OBJ_STINK) return false;
if (!o->is_static && o->weight < ONTO_HOLDS) return false; /* (would tip or slide away) */
const float s = obj->look.height / 1.75f;
const Collider *c = &o->colliders[0];
if (c->axes[1].y < 0.95f) return false; /* (a flat top only) */
Vec3 rel = vec3_sub(Object_colliderCenter(o, 0), obj->position);
rel.y = 0.0f;
float reach = projectRadius(c, dir), side = projectRadius(c, vec3(-dir.z, 0.0f, dir.x));
float ahead = vec3_dot(rel, dir), beside = fabsf(rel.x * -dir.z + rel.z * dir.x);
float top = Object_colliderCenter(o, 0).y + projectRadius(c, WORLD_UP) - obj->position.y;
float bottom = Object_colliderCenter(o, 0).y - projectRadius(c, WORLD_UP) - obj->position.y;
if (bottom > (0.2f + 0.15f * fmaxf(ahead, 0.0f)) * s || bottom < -0.5f * s - 0.15f * fmaxf(ahead, 0.0f)) return false;
if (beside > side - 0.15f || ahead - reach < 0.05f * s || ahead - reach > JUMP_LOOK * s) return false;
if (top <= STEP_OVER_HEIGHT * s || top > ONTO_HIGHEST * s || 2.0f * reach < ONTO_ROOM * s) return false;
float v = sqrtf(2.0f * GRAVITY * fmaxf(top + (ONTO_CLEAR - JUMP_TUCK) * s, 0.1f * s));
if (v > Object_jumpSpeed(obj) * ONTO_MOST) return false; /* (that's a climb) */
*v_up = v;
*edge_out = ahead - reach;
return true;
}
/* ---------- Stepping over something knee high: a set move ---------- */
/* p (feet height) inside o, with a margin? */
static bool pointIn(const Object *o, Vec3 p, float margin) {
for (int c = 0; c < o->collider_count; c++) {
const Collider *col = &o->colliders[c];
Vec3 rel = vec3_sub(p, Object_colliderCenter(o, c));
if (fabsf(vec3_dot(rel, col->axes[0])) < col->half_extents.x + margin &&
fabsf(vec3_dot(rel, col->axes[1])) < col->half_extents.y + margin &&
fabsf(vec3_dot(rel, col->axes[2])) < col->half_extents.z + margin) return true;
}
return false;
}
/* Walking (not running) up to something knee high to step over, a step away:
the set move starts. Not if where the feet would land is taken. */
/* Starts the set move over something (up: onto it) along dir: near and far
its edges, top its height (from the feet), land_at where the leading foot comes down. */
static void beginStepOver(Object *obj, const Terrain *terrain, Vec3 dir, float near, float far, float top, float land_at, bool up) {
const Body *b = &obj->body;
const float s = obj->look.height / 1.75f;
const Vec3 right = vec3(-dir.z, 0.0f, dir.x);
obj->over_t = 1e-4f;
obj->over_up = up;
obj->over_time = (up ? STAND_UP_TIME + STAND_UP_HIGH * top / s /* (onto something low: one step up) */
: OVER_MOVE_TIME + OVER_MOVE_HIGH * top / s) * (1.25f - 0.5f * obj->fitness) / fmaxf(Object_vigor(obj), 0.6f);
obj->over_from = obj->position;
obj->over_dir = dir;
obj->over_near = near;
obj->over_far = far;
obj->over_top = top;
/* the foot further back goes first (the other stands) */
float a0 = vec3_dot(vec3_sub(b->feet[0].ankle, obj->position), dir), a1 = vec3_dot(vec3_sub(b->feet[1].ankle, obj->position), dir);
obj->over_lead = a0 <= a1 ? 0 : 1;
for (int k = 0; k < 2; k++) {
obj->over_foot0[k] = b->feet[k].ankle;
float lateral = clampf(vec3_dot(vec3_sub(b->feet[k].ankle, obj->position), right), -0.14f * s, 0.14f * s);
if (fabsf(lateral) < 0.08f * s) lateral = (k == 0 ? -0.1f : 0.1f) * s; /* (feet apart) */
float at = k == obj->over_lead ? land_at : land_at + 0.08f * s;
Vec3 land = vec3_add(obj->position, vec3_add(vec3_scale(dir, at), vec3_scale(right, lateral)));
/* (standing flat there: the ankle its own height over the surface - not lifted as it may be now, on the toes) */
land.y = (up || !terrain ? obj->position.y + top : Terrain_height(terrain, land.x, land.z)) + BODY_ANKLE_Y * s;
obj->over_land[k] = land;
}
obj->movement = vec3_zero();
}
static bool startStepOver(Object *obj, Object *const *objects, int count, const Terrain *terrain) {
float speed = sqrtf(obj->movement.x * obj->movement.x + obj->movement.z * obj->movement.z);
const Body *b = &obj->body;
if (speed < OVER_MOVE_SLOWEST || canJumpOver(obj, speed) || b->pose != POSE_UP || b->airborne || b->stumble > 0.0f ||
obj->sitting || obj->climbing || obj->on_ladder || b->sit > 0.05f || obj->hands.grabbed || obj->type == OBJ_DOG) return false;
const float s = obj->look.height / 1.75f;
Vec3 dir = vec3(obj->movement.x / speed, 0.0f, obj->movement.z / speed);
if (vec3_dot(dir, Object_facing(obj)) < 0.7f) return false; /* (backing or sidling: as it comes) */
for (int i = 0; i < count; i++) {
const Object *o = objects[i];
if (o->collider_count == 0 || o->is_character || o->is_held || o->lifted || o == obj->standing_on) continue;
/* over it (low for its own sake), or up onto it (higher, room on top, holds you) */
bool over = (o->weight >= STEP_OVER_LIGHT || o->is_static) && Physics_lowObstacle(o) >= OVER_MOVE_LOWEST * s;
const Collider *c = &o->colliders[0];
Vec3 rel = vec3_sub(Object_colliderCenter(o, 0), obj->position);
rel.y = 0.0f;
float reach = projectRadius(c, dir), side = projectRadius(c, vec3(-dir.z, 0.0f, dir.x));
float ahead = vec3_dot(rel, dir), beside = fabsf(rel.x * -dir.z + rel.z * dir.x);
float top = Object_colliderCenter(o, 0).y + projectRadius(c, WORLD_UP) - obj->position.y;
float near = ahead - reach, far = ahead + reach;
{ /* (straight at a long thing at a slant: its faces where the way goes in and out, not its corners) */
Vec3 from = vec3(obj->position.x, obj->position.y + fminf(0.5f * top, 0.2f * s), obj->position.z), center = Object_colliderCenter(o, 0);
float in = Collider_ray(c, center, from, dir, NULL);
if (in >= 0.0f) {
float back = Collider_ray(c, center, vec3_add(from, vec3_scale(dir, in + 20.0f)), vec3_scale(dir, -1.0f), NULL);
if (back >= 0.0f) {
near = in;
far = in + 20.0f - back;
reach = 0.5f * (far - near);
}
}
}
/* up onto it: higher than a stride over, or something solid you can just as well stand on (a log, a crate) -
no straddling then, you stand up there and walk on (nobody walks up onto tables) */
const bool you = obj->type == OBJ_PLAYER, person = you || obj->type == OBJ_HUMAN;
bool up = person && standsOn(o) && top <= STEP_UP_MOST * s && side >= 0.12f * s && /* (a log end-on: a narrow step) */
(((you || top <= Object_stepUpMost(obj)) && !over && top > STEP_OVER_HEIGHT * s && 2.0f * reach >= STEP_UP_ROOM * s) || /* (higher: as high as they go up; else they walk round) */
(top >= STAND_LOWEST * s && 2.0f * reach >= STAND_ROOM * s && (you || top <= STEP_OVER_HEIGHT * 1.15f * s)));
if (up) over = false;
if (!over && !up) continue;
float bottom = Object_colliderCenter(o, 0).y - projectRadius(c, WORLD_UP) - obj->position.y;
if (bottom > (0.2f + 0.15f * fmaxf(ahead, 0.0f)) * s || bottom < -0.5f * s - 0.15f * fmaxf(ahead, 0.0f)) continue; /* (up on something, or down in a hole: not in your way) */
if (beside > side + (up ? -0.1f : 0.1f) * s || near > OVER_MOVE_FROM * s || near < 0.08f * s) continue;
top = fmaxf(top, 0.05f * s); /* (downhill it may be lower than your feet) */
/* where the feet come down: free? (on top of it, going up) */
Vec3 right = vec3(-dir.z, 0.0f, dir.x);
float land_at = up ? near + fminf(STEP_UP_IN * s, reach) : far + OVER_LAND * s; /* (a log: its middle) */
bool free = true;
for (int k = -1; k <= 1 && free; k += 2) {
Vec3 p = vec3_add(obj->position, vec3_add(vec3_scale(dir, land_at), vec3_scale(right, (float)k * 0.11f * s)));
p.y = up ? obj->position.y + top + 0.12f * s : Terrain_height(terrain, p.x, p.z) + 0.05f;
for (int j = 0; j < count && free; j++)
if (objects[j] != obj && (!up || objects[j] != o) && !objects[j]->is_character && objects[j]->collider_count > 0 && objects[j]->type != OBJ_DECAL &&
objects[j]->type != OBJ_PUDDLE && objects[j]->type != OBJ_STINK && pointIn(objects[j], p, 0.08f * s)) free = false;
}
if (!free) continue;
beginStepOver(obj, terrain, dir, near, far, top, land_at, up);
obj->movement = vec3_zero();
return true;
}
return false;
}
/* The body over it: up to above its middle while the first leg goes over,
then on past it while the other one follows. */
static void stepOverMove(Object *obj, const Terrain *terrain, float dt) {
obj->over_t = fminf(1.0f, obj->over_t + dt / fmaxf(obj->over_time, 0.2f));
float t = obj->over_t;
const float s = obj->look.height / 1.75f;
/* (straddling it: the hips between where the first foot came down and the other one, still back there) */
const int lead = obj->over_lead;
float landed = vec3_dot(vec3_sub(obj->over_land[lead], obj->over_from), obj->over_dir);
float behind = vec3_dot(vec3_sub(obj->over_foot0[1 - lead], obj->over_from), obj->over_dir);
float mid = fmaxf(0.5f * (landed + behind), 0.05f * s), end = obj->over_up ? landed + 0.02f * s : obj->over_far + OVER_LAND * s + 0.04f * s;
float along = t < 0.5f ? mid * smoothstepf(0.08f, 0.5f, t) : lerpf(mid, end, smoothstepf(0.5f, 0.95f, t));
Vec3 p = vec3_add(obj->over_from, vec3_scale(obj->over_dir, along));
if (obj->over_up) /* up onto it: the body rises as the leading leg pushes up, over its foot */
p.y = obj->over_from.y + obj->over_top * smoothstepf(0.3f, 0.78f, t);
else
p.y = Terrain_height(terrain, p.x, p.z) + (obj->over_from.y - Terrain_height(terrain, obj->over_from.x, obj->over_from.z));
/* (something high: up on the toes of the leading foot while the other one comes over, its knee clear of it) */
if (!obj->over_up) p.y += clampf(obj->over_top - 0.3f * s, 0.0f, 0.1f * s) * smoothstepf(0.45f, 0.6f, t) * (1.0f - smoothstepf(0.85f, 1.0f, t));
obj->position = p;
obj->velocity = obj->movement = vec3_zero();
obj->is_grounded = true;
obj->ground_speed = 0.0f;
Object_syncColliders(obj);
if (t >= 1.0f) obj->over_t = 0.0f; /* over */
}
static void hurdle(Object *obj, Object *const *objects, int count) {
float speed = sqrtf(obj->movement.x * obj->movement.x + obj->movement.z * obj->movement.z);
if (!canJumpOver(obj, speed)) return;
Vec3 dir = vec3(obj->movement.x / speed, 0.0f, obj->movement.z / speed);
for (int i = 0; i < count; i++) {
float v, center;
if (!jumpOver(obj, objects[i], dir, speed, &v, ¢er)) continue;
/* off now: the top of the jump comes over its middle */
if (center > speed * v / GRAVITY + JUMP_EARLY) continue;
obj->velocity.y = v;
obj->is_grounded = false;
return;
}
for (int i = 0; i < count; i++) {
float v, edge;
if (!jumpOnto(obj, objects[i], dir, &v, &edge)) continue;
/* off now: over the edge a little after the top of the jump, coming down onto it */
if (edge > speed * v / GRAVITY * ONTO_TIMING + JUMP_EARLY) continue;
obj->velocity.y = v;
obj->is_grounded = false;
return;
}
}
/* Walking at something you can't step over or onto (a tree, a high crate, the
corner of a wall, the fire) that you'd only just catch: the body drifts a
little to the side, past it, instead of running into it. */
#define END_CLIP 0.7f /* m of you that would still catch it at most (wider: you walk round it yourself) */
#define END_PUSH 0.45f /* sideways at most this share of the walking speed ... */
#define END_EASE 2.5f /* ... coming and going this quickly (per second) */
#define END_LOOK 1.6f /* m ahead */
#define END_BODY 0.4f /* (your half width, and the legs swinging past) */
static Vec3 pastTheEnd(Object *obj, Vec3 m, Object *const *objects, int count, float dt) {
float speed = sqrtf(m.x * m.x + m.z * m.z), want = 0.0f;
if (speed < 0.3f || obj->sitting || obj->climbing || obj->on_ladder || obj->hands.grabbed || obj->type != OBJ_PLAYER) {
obj->veer_side = 0.0f;
return m;
}
Vec3 dir = vec3(m.x / speed, 0.0f, m.z / speed), right = vec3(-dir.z, 0.0f, dir.x);
for (int i = 0; i < count && want == 0.0f; i++) {
const Object *o = objects[i];
if (o->collider_count == 0 || o->is_character || o->is_held || o == obj->standing_on || o->type == OBJ_DECAL ||
o->type == OBJ_PUDDLE || o->type == OBJ_STINK || (!o->is_static && o->weight < STEP_OVER_LIGHT)) continue;
const Collider *c = &o->colliders[0];
float side = projectRadius(c, right), reach = projectRadius(c, dir);
float top = Object_colliderCenter(o, 0).y + projectRadius(c, WORLD_UP) - obj->position.y;
float bottom = top - 2.0f * projectRadius(c, WORLD_UP);
if (top < 0.1f || bottom > 1.2f) continue; /* (flat on the ground, or overhead) */
/* only what you can't step over or onto (a tree, a high crate, a wall's corner, the fire) */
bool steps = !neverOver(o) && ((Physics_lowObstacle(o) > 0.0f && top <= STEP_OVER_HEIGHT * 1.15f) || (standsOn(o) && top <= STEP_UP_MOST));
if (steps) continue;
Vec3 rel = vec3_sub(Object_colliderCenter(o, 0), obj->position);
float ahead = vec3_dot(vec3(rel.x, 0.0f, rel.z), dir) - reach, across = vec3_dot(vec3(rel.x, 0.0f, rel.z), right);
float clip = side + END_BODY - fabsf(across);
if (ahead < -0.05f || ahead > END_LOOK || clip <= 0.0f || clip > END_CLIP) continue;
/* away from its middle, more the closer it gets */
want = fminf(END_PUSH, clip / fmaxf(ahead, 0.4f)) * (across > 0.0f ? -1.0f : 1.0f);
}
/* the body drifts over to it smoothly (and back) */
obj->veer_side += clampf(want - obj->veer_side, -END_EASE * dt, END_EASE * dt);
return vec3_add(m, vec3_scale(right, obj->veer_side * speed));
}
/* ---------- Public ---------- */
#define REST_SPEED 0.03f /* things lying this still ... */
#define REST_AFTER 1.0f /* ... for this long only get a look now and then */
#define REST_CHECK 0.25f /* (whether what they lie on is still there) */
static Grid *g_grid;
void Physics_useGrid(struct Grid *grid) { g_grid = grid; }
#define PHYS_NEAR 512
#define PHYS_LOOK 3.5f /* what a step can touch or look ahead to (m, beyond the thing's own size) */
void Physics_step(Object *obj, Object *const *objects, int count, const Terrain *terrain, float dt) {
if (obj->is_static || obj->is_held) return;
g_step_dt = dt;
if (obj->over_t > 0.0f) { stepOverMove(obj, terrain, dt); return; } /* (a set move) */
/* At rest: skip most steps (a push or a fall wakes it) */
if (obj->climbing || obj->on_grill || obj->toilet || obj->riding || obj->indoors > 0.0f) return; /* moved by hand, or out of sight */
if (obj->sitting && obj->sit_moving && obj->seat) return; /* (stepping over to a seat, turning, back onto it: a set move) */
bool still = !obj->is_character && !obj->lifted && !obj->thrown_by && obj->is_grounded &&
vec3_length(obj->movement) < 0.01f &&
vec3_length(obj->velocity) < REST_SPEED && fabsf(obj->angular_velocity.y) < 0.05f &&
(obj->topple <= 0.0f || obj->topple >= 1.0f);
if (!still) {
obj->rest_time = 0.0f;
obj->settled = false;
} else {
float before = obj->rest_time;
obj->rest_time += dt;
if (!obj->settled && obj->rest_time > SETTLE_AFTER && obj->on_terrain) {
obj->settled = true;
settleOnGround(obj, terrain);
}
if (before > REST_AFTER && floorf(before / REST_CHECK) == floorf(obj->rest_time / REST_CHECK)) return;
}
if (obj->lifted) obj->velocity = vec3_zero(); /* carried: the hands move it */
else obj->velocity.y -= GRAVITY * dt;
if (obj->thrown_by) obj->thrown_time += dt;
/* Physics only changes yaw; X/Z rotation is fixed placement (or a topple). */
Object_advanceTopple(obj, dt);
obj->rotation.y += obj->angular_velocity.y * dt;
obj->angular_velocity.y *= YAW_DAMPING;
Object_syncColliders(obj);
/* Only what's near can be touched (from the cell grid) */
Object *near[PHYS_NEAR];
if (g_grid) {
float size = 0.0f;
for (int c = 0; c < obj->collider_count; c++)
size = fmaxf(size, vec3_length(vec3_sub(Object_colliderCenter(obj, c), obj->position)) + vec3_length(obj->colliders[c].half_extents));
float travel = vec3_length(vec3_add(obj->movement, obj->velocity)) * dt;
int n = Grid_nearObjects(g_grid, objects, count, obj->position.x, obj->position.z, size + travel + PHYS_LOOK, near, PHYS_NEAR);
if (n >= 0) { objects = near; count = n; }
}
if (obj->is_character && obj->on_terrain) obj->movement = slopeAdjusted(obj, terrain);
if (obj->is_character && obj->is_grounded && obj->body.balance > 0.0f) /* (balancing on a log: careful steps) */
obj->movement = vec3_scale(obj->movement, 1.0f - BALANCE_SLOW * obj->body.balance);
if (obj->is_character && obj->is_grounded) obj->movement = pastTheEnd(obj, obj->movement, objects, count, dt);
if (obj->is_character && obj->is_grounded) hurdle(obj, objects, count);
if (obj->is_character && obj->is_grounded && obj->velocity.y <= 0.0f && startStepOver(obj, objects, count, terrain)) {
stepOverMove(obj, terrain, dt);
return;
}
if (obj->is_character && obj->is_grounded) obj->movement = carryOver(obj, obj->movement);
if (obj->is_character && obj->is_grounded) obj->movement = steppingOver(obj, obj->movement, objects, count);
Vec3 motion = vec3_add(obj->movement, obj->velocity);
const Vec3 start = obj->position;
obj->position = vec3_add(obj->position, vec3_scale(motion, dt));
obj->ground_speed = sqrtf(motion.x * motion.x + motion.z * motion.z);
bool was_grounded = obj->is_grounded;
obj->is_grounded = false;
obj->standing_on = NULL;
obj->touched = NULL;
obj->on_terrain = false;
Vec3 before = obj->velocity;
resolveTerrain(obj, terrain);
resolveObjects(obj, objects, count);
if (obj->is_character) neverThrough(obj, start, objects, count);
if (obj->is_character) neverThroughFloor(obj, start, objects, count);
obj->impact = vec3_length(vec3_sub(before, obj->velocity));
if (obj->drop_tip && obj->is_grounded) { /* a bottle dropped: it hardly ever lands standing */
obj->drop_tip = false;
unsigned h = (unsigned)(int)(obj->position.x * 7919.0f) * 2654435761u ^ (unsigned)(int)(obj->position.z * 104729.0f);
float r = (float)(h >> 8 & 0xffffu) / 65535.0f;
if (r < DROP_TIPS) {
Vec3 dir = vec3(obj->velocity.x, 0.0f, obj->velocity.z);
if (vec3_length(dir) < 0.05f) dir = vec3(cosf(r * 40.0f), 0.0f, sinf(r * 40.0f));
Object_topple(obj, dir);
}
}
/* Walking down a slope or over a bump: stay on the ground instead of
taking off for a frame (unless jumping). */
if (obj->is_character && was_grounded && !obj->is_grounded && obj->velocity.y <= 0.0f)
snapToTerrain(obj, terrain);
applyFriction(obj, dt);
obj->movement = vec3_zero();
}