Nimbin[12]?Games / Entropie / entropie/src/world/physics.c

Entropie git · main

Entropie festival game; first steps

sdl c game entropie cmake · first commit 2026-10-10 · last commit 2026-11-02 (18 hours ago) · synced 10 hours ago

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.gz
entropie/src/world/physics.c 64.8 KB · 1204 lines 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, &center)) 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();
}