#include "world/physics.h" #include "world/grid.h" #include #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(); }