#include "world/ladder.h" #include #include "world/physics.h" #include "world/wetness.h" #define SLIDE_SPEED 0.7f /* of the way out per second */ #define LEAN_ANGLE 75.0f /* degrees from the ground: the 1-in-4 rule */ #define OVERHANG 0.9f /* slid out to fit: this much over the edge (m) */ #define LEAN_REACH 2.0f /* looks this far ahead for something to lean on */ #define HOLDS_WEIGHT 60.0f /* loose things lighter than this would tip over */ #define EAVES_OUT 0.42f /* roofs reach past the wall: the top rests on the gutter */ #define MIN_HEIGHT 0.6f #define LEAN_ROOM 0.03f /* leaned there it may touch other things this much, no more */ #define STAND_OUT 0.08f /* a climber's feet this far out from the rungs */ #define CLIMB_SPEED 0.8f /* meters along the ladder per second */ #define HANDS_ABOVE 1.05f /* hands this far above the feet (along the ladder) */ #define MOUNT_REACH 1.2f #define LADDER_ONTO 1.6f /* m/s the body moves over onto it, getting on */ #define STEP_OFF_TIME 0.6f /* seconds to step off at the bottom */ #define RAISE_ANGLE 1.36f /* a raised ladder: nearly upright (rad) looking straight ahead (any flatter is too heavy to hold) ... */ #define RAISE_FOLLOW 0.45f /* ... a bit steeper looking up, flatter looking down ... */ #define RAISE_LOW 1.15f #define RAISE_HIGH 1.52f #define RAISE_SPEED 1.6f /* rad/s it tips up or down */ #define RAISE_DIP 0.12f /* tipped down to get under something: nearly flat */ #define RAISE_GRIP 0.45f /* held this far up from its foot */ #define FLATTEST 55.0f /* degrees: no flatter than this (it would slide out) */ #define AIM_VIEW 1.5f /* looking at it: this much further than the ladder is long */ #define LOOK_DOWN -0.6f /* looking down this steeply (view y), a click slides a held-up ladder together */ #define AIM_FOOT 1.8f /* its foot no further than this from you (else: step closer) */ #define AIM_PAST 0.8f /* held up, it leans on what its top points at up to this much past it */ #define AIM_EDGE 0.45f /* ... aimed this close under the top of it: the top rests on the edge */ #define STEP_FOLD_SPEED 2.5f /* step ladder: folds up this fast (of the way per second) */ #define STEP_IN_FRONT 0.45f /* ... stood up: its front feet this far in front of you */ #define STEP_AT_WALL 0.6f /* ... a wall this close past its back feet: it stands with its back to it */ #define STEP_WALL_GAP 0.08f /* ... its back feet this far from that wall */ #define STEP_OFF_UP 0.9f /* ... from its top you get onto that if it's no higher than this over the top */ #define FREE_AHEAD 0.6f /* stood up on its own (nothing to lean on): this far in front of you */ #define HOLD_OUT 0.3f /* somebody holding it stands this far out from its foot ... */ #define HOLD_BESIDE 0.48f /* ... and this far to the side of its middle (the climber gets on and off in front) */ static const Vec3 UP = { 0.0f, 1.0f, 0.0f }; static Vec3 flatv(Vec3 v) { return vec3(v.x, 0.0f, v.z); } static float flatDist(Vec3 a, Vec3 b) { return vec3_length(flatv(vec3_sub(a, b))); } /* Three kinds: telescopic (variant 0: slides out), folding (two sections on a hinge, unfolds) and the step ladder (stands on its own: extend is how far its legs are open). */ static bool folding(const Object *l) { return l->variant == LADDER_FOLDING; } static bool stepLadder(const Object *l) { return l->variant == LADDER_STEP; } static float lengthFor(const Object *l, float extend) { return stepLadder(l) ? Shapes_stepLadderLength() : folding(l) ? Shapes_foldLadderLength(extend) : Shapes_ladderLength(extend); } static float lengthOf(const Object *l) { return lengthFor(l, l->extend); } static Vec3 upAlong(const Object *l) { return l->colliders[0].axes[1]; } static Vec3 pointAt(const Object *l, float along) { return vec3_add(l->position, vec3_scale(upAlong(l), along)); } /* Colliders for that length (the step ladder: one per leg). */ static void fitColliders(Object *l, float extend) { float len = lengthFor(l, extend); l->colliders[0].center = vec3(0.0f, len * 0.5f, stepLadder(l) ? -0.015f : 0.0f); l->colliders[0].half_extents = vec3(LADDER_WIDTH * 0.5f + 0.03f, len * 0.5f, stepLadder(l) ? 0.06f : 0.04f); if (stepLadder(l)) { float a = Shapes_stepLadderSpread(extend); Vec3 hinge = vec3(0.0f, len - 0.04f, STEP_HINGE_Z), down = vec3(0.0f, -cosf(a), -sinf(a)); l->colliders[1] = (Collider){ .center = vec3_add(hinge, vec3_scale(down, len * 0.5f)), .half_extents = vec3(LADDER_WIDTH * 0.5f + 0.01f, len * 0.5f, 0.03f), .tilt = vec3(-a, 0.0f, 0.0f) }; l->collider_count = 2; } Object_syncColliders(l); } /* New mesh and collider for the current length. */ static void reshape(Object *l) { Mesh mesh = stepLadder(l) ? Shapes_stepLadder(l->extend) : folding(l) ? Shapes_foldLadder(l->extend) : Shapes_ladder(l->extend); if (mesh.vao) { Mesh_destroy(&l->mesh); l->mesh = mesh; } fitColliders(l, l->extend); } void Ladder_slide(Object *l) { if (l->leaning || l->placing > 0.0f || stepLadder(l)) return; l->extend_goal = l->extend_goal > 0.5f ? 0.0f : 1.0f; } void Ladder_setExtend(Object *l, float extend) { l->extend = l->extend_goal = extend; reshape(l); } #define PLACE_TIME 1.1f /* seconds to put it up: stand it up, slide it out, tip it onto the wall */ /* Putting it up: upright in front of you, slid out, then tipped onto the wall. */ static void putUp(Object *l, float dt) { l->placing = fminf(1.0f, l->placing + dt / PLACE_TIME); float t = l->placing; float up = smoothstepf(0.0f, 0.35f, t), slide = smoothstepf(0.3f, 0.7f, t), tip = smoothstepf(0.65f, 1.0f, t); Vec3 upright_face = vec3_normalize(flatv(l->place_face[1])); Vec3 axis = vec3_lerp(vec3_lerp(l->place_axis[0], UP, up), l->place_axis[1], tip); Vec3 face = vec3_lerp(vec3_lerp(l->place_face[0], upright_face, up), l->place_face[1], tip); axis = vec3_normalize(axis); face = vec3_normalize(vec3_sub(face, vec3_scale(axis, vec3_dot(face, axis)))); l->extend = l->extend_goal = lerpf(l->place_extend[0], l->place_extend[1], slide); l->rotation = euler_yxz_from_axes(axis, face); l->position = vec3_lerp(l->place_foot[0], l->place_foot[1], up); reshape(l); if (t >= 1.0f) { /* up: it leans there now */ l->placing = 0.0f; l->leaning = true; l->lean_top = fmaxf(l->place_top, 0.0f); l->rotation = euler_yxz_from_axes(l->place_axis[1], l->place_face[1]); l->position = l->place_foot[1]; Object_syncColliders(l); if (l->place_top < 0.0f) { /* (stood up on its own: nothing holds it - over it goes) */ l->leaning = false; l->tip = 0.0f; l->tip_v = 0.25f; l->tip_from = upAlong(l); l->place_face[0] = l->colliders[0].axes[2]; } } } static float topOf(const Object *o, float x, float z, float from_y); /* How deep the ladder is in solid things (not people, the dog, what's carried), and the deepest one. */ static float solidIn(const World *world, const Object *l, Object **deepest) { float most = 0.0f; if (deepest) *deepest = NULL; for (int i = 0; world && i < world->count; i++) { Object *o = world->objects[i]; if (o == l || o->is_character || o->is_held || o->collider_count == 0 || o->type == OBJ_DOG || o->type == OBJ_DECAL || o->type == OBJ_PUDDLE || o->type == OBJ_STINK || o->type == OBJ_BUSH) continue; if (!o->is_static && o->weight < HOLDS_WEIGHT) continue; /* (light things it just knocks aside) */ if (flatDist(o->position, l->position) > 30.0f && !(o->is_static && vec3_length(o->colliders[0].half_extents) > 4.0f)) continue; float d = Physics_penetration(l, o); if (d > most) { most = d; if (deepest) *deepest = o; } } return most; } /* Falling against something on the way down (a beam, a wall, a tree): it stops there and leans on it - not as steady as one put up properly. */ #define CAUGHT_STEADY 0.45f static bool caught(World *world, Object *l, Vec3 axis_was, Vec3 face_was, float before) { Object *thing; if (solidIn(world, l, &thing) <= before + 0.01f || !thing) return false; l->rotation = euler_yxz_from_axes(axis_was, face_was); Object_syncColliders(l); l->tip = l->tip_v = 0.0f; l->leaning = true; l->lean_normal = vec3_normalize(vec3_scale(l->tip_dir, -1.0f)); l->place_axis[1] = axis_was; l->place_face[1] = face_was; /* its top over what caught it (far enough past it to get off there)? */ float top = -1.0f; for (float along = lengthOf(l) - 0.1f; along > 0.5f; along -= 0.1f) { Vec3 p = pointAt(l, along); float t = topOf(thing, p.x, p.z, p.y + 0.5f); if (t > -1e8f && t > p.y - 0.25f) { top = along; break; } } l->lean_top = top > 0.0f && lengthOf(l) - top > 0.3f ? top : 0.0f; l->stability = CAUGHT_STEADY; l->just_caught = true; return true; } /* Falling over about its foot, faster and faster, then it lies there. */ static void fallOver(World *world, Object *l, float dt) { Vec3 axis_was = upAlong(l), face_was = l->colliders[0].axes[2]; float before = world ? solidIn(world, l, NULL) : 0.0f; float len = fmaxf(lengthOf(l), 1.0f); float from = acosf(clampf(vec3_dot(l->tip_from, UP), -1.0f, 1.0f)); /* (how far it leaned already) */ float theta = from + l->tip; l->tip_v += 1.5f * 9.81f / len * sinf(fmaxf(theta, 0.05f)) * dt; /* (a rod tipping over its end) */ l->tip += l->tip_v * dt; theta = from + l->tip; Vec3 target = vec3_add(vec3_scale(UP, cosf(theta)), vec3_scale(l->tip_dir, sinf(theta))); Vec3 axis = vec3_normalize(vec3_lerp(l->tip_from, target, smoothstepf(0.0f, 0.25f, l->tip))); if (theta >= PI_F * 0.5f - 0.04f) { /* down: lying flat, loose again */ l->tip = l->tip_v = 0.0f; l->rotation = euler_yxz_from_axes(l->tip_dir, UP); l->position.y += 0.06f; l->is_static = false; l->velocity = l->angular_velocity = vec3_zero(); if (l->held_by) l->held_by->holding_ladder = NULL; l->held_by = NULL; Object_syncColliders(l); return; } Vec3 face = vec3_sub(l->place_face[0], vec3_scale(axis, vec3_dot(l->place_face[0], axis))); face = vec3_length(face) > 1e-3f ? vec3_normalize(face) : vec3_normalize(vec3_cross(axis, vec3(axis.z, 0.0f, -axis.x))); l->rotation = euler_yxz_from_axes(axis, face); Object_syncColliders(l); if (world) caught(world, l, axis_was, face_was, before); } void Ladder_topple(World *world, Object *l, Vec3 dir) { if (l->tip_v > 0.0f || l->placing > 0.0f) return; for (int i = 0; i < world->count; i++) { /* anybody on it comes off */ Object *o = world->objects[i]; if (o->on_ladder == l) Ladder_getOff(o); } dir = flatv(dir); l->tip_dir = vec3_length(dir) > 1e-3f ? vec3_normalize(dir) : vec3(1.0f, 0.0f, 0.0f); l->tip_from = upAlong(l); l->place_face[0] = l->colliders[0].axes[2]; l->tip = 0.0f; l->tip_v = 0.3f; l->leaning = false; l->lean_top = 0.0f; } float Ladder_steadiness(const Object *l) { const Object *h = l->held_by; if (h && h->holding_ladder == l && !Body_isDown(&h->body)) { Vec3 facing; if (flatDist(h->position, Ladder_holdSpot(l, &facing)) < 0.5f) return 1.0f; } return l->stability; } void Ladder_hold(Object *who, Object *l) { if (who->holding_ladder && who->holding_ladder->held_by == who) who->holding_ladder->held_by = NULL; who->holding_ladder = l; if (!l) return; l->held_by = who; Vec3 across = vec3_normalize(vec3_cross(l->lean_normal, UP)); l->hold_side = vec3_dot(vec3_sub(who->position, l->position), across) >= 0.0f ? 1.0f : -1.0f; /* (the side they come from) */ } Vec3 Ladder_holdSpot(const Object *l, Vec3 *facing) { Vec3 across = vec3_normalize(vec3_cross(l->lean_normal, UP)); float side = l->hold_side != 0.0f ? l->hold_side : 1.0f; /* beside its foot, a little out, turned to it (half facing the wall) */ *facing = vec3_normalize(vec3_add(vec3_scale(across, -side), vec3_scale(l->lean_normal, -0.6f))); Vec3 at = vec3_add(l->position, vec3_add(vec3_scale(l->lean_normal, HOLD_OUT), vec3_scale(across, side * HOLD_BESIDE))); at.y = l->position.y; return at; } void Ladder_update(World *world, Object *l, float dt) { if (l->placing > 0.0f) { putUp(l, dt); return; } if (l->tip_v > 0.0f) { fallOver(world, l, dt); return; } if (l->shake > 0.0f && l->leaning) { /* wobbling: the top rocks from side to side, dying away */ l->shake = fmaxf(0.0f, l->shake - dt); Vec3 axis = l->place_axis[1], across = vec3_normalize(vec3_cross(l->lean_normal, UP)); float a = 0.05f * sinf(l->shake * 38.0f) * l->shake; Vec3 rocked = vec3_normalize(vec3_add(vec3_scale(axis, cosf(a)), vec3_scale(across, sinf(a)))); l->rotation = euler_yxz_from_axes(rocked, l->place_face[1]); Object_syncColliders(l); } if (l->raised > 0.0f) l->raised += clampf(l->raise_goal - l->raised, -RAISE_SPEED * dt, RAISE_SPEED * dt); if (l->extend == l->extend_goal) return; float step = (stepLadder(l) ? STEP_FOLD_SPEED : SLIDE_SPEED) * dt; l->extend = l->extend < l->extend_goal ? fminf(l->extend + step, l->extend_goal) : fmaxf(l->extend - step, l->extend_goal); reshape(l); } /* Highest top of o's colliders straight below (x, from_y, z), or -1e9. */ static float topOf(const Object *o, float x, float z, float from_y) { float best = -1e9f; for (int c = 0; c < o->collider_count; c++) { Vec3 n; float t = Object_rayToCollider(o, c, vec3(x, from_y, z), vec3(0.0f, -1.0f, 0.0f), &n); if (t >= 0.0f) best = fmaxf(best, from_y - t); } return best; } /* Something solid straight ahead of who: the face it shows (normal towards who). */ static Object *faceAhead(const World *world, const Object *who, const Object *ladder, Vec3 *point, Vec3 *normal) { Vec3 f = Object_facing(who); static const float HEIGHTS[] = { 0.45f, 1.0f, 1.5f }; Object *hit = NULL; float best = LEAN_REACH; for (int i = 0; i < world->count; i++) { Object *o = world->objects[i]; if (o == ladder || o == who || o->is_character || o->is_held || o->collider_count == 0 || o->type == OBJ_DOG) continue; if (flatDist(o->position, who->position) > 60.0f) continue; for (int h = 0; h < 3; h++) { Vec3 origin = vec3_add(who->position, vec3(0.0f, HEIGHTS[h], 0.0f)); for (int c = 0; c < o->collider_count; c++) { Vec3 n; float t = Object_rayToCollider(o, c, origin, f, &n); if (t >= 0.0f && t < best) { best = t; hit = o; *normal = n; *point = vec3_add(origin, vec3_scale(f, t)); } } } } return hit; } /* The first solid thing along a ray (not the ladder, nor who carries it, nor people). */ static Object *rayHit(const World *world, const Object *who, const Object *ladder, Vec3 origin, Vec3 dir, float max, Vec3 *point, Vec3 *normal) { Object *hit = NULL; float best = max; for (int i = 0; i < world->count; i++) { Object *o = world->objects[i]; if (o == ladder || o == who || o->is_character || o->is_held || o->collider_count == 0 || o->type == OBJ_DOG || o->type == OBJ_DECAL || o->type == OBJ_PUDDLE || o->type == OBJ_STINK) continue; if (flatDist(o->position, origin) > 60.0f) continue; for (int c = 0; c < o->collider_count; c++) { Vec3 nn; float t = Object_rayToCollider(o, c, origin, dir, &nn); if (t >= 0.0f && t < best) { best = t; hit = o; *normal = nn; *point = vec3_add(origin, vec3_scale(dir, t)); } } } return hit; } /* Held up in the air: what its top points at (up to a bit past its length). Its top face: the side of it towards you, just under that edge. */ static Object *aimedAt(const World *world, const Object *who, const Object *l, Vec3 *face, Vec3 *n, float *aim_y) { Object *thing = NULL; if (who->type == OBJ_PLAYER && vec3_length(who->body.look_dir) > 0.5f) { /* where you look (a thin beam: a little above or below it does too) */ Vec3 eye = vec3_add(who->position, vec3(0.0f, 0.93f * who->look.height, 0.0f)); Vec3 look = vec3_normalize(who->body.look_dir), level = flatv(look); float pitch = asinf(clampf(look.y, -1.0f, 1.0f)); static const float NEAR_MISS[] = { 0.0f, 0.05f, -0.05f, 0.1f, -0.1f }; for (int k = 0; k < 5 && !thing && vec3_length(level) > 1e-3f; k++) { float a = pitch + NEAR_MISS[k]; Vec3 dir = vec3_add(vec3_scale(vec3_normalize(level), cosf(a)), vec3_scale(UP, sinf(a))); thing = rayHit(world, who, l, eye, dir, lengthOf(l) + AIM_VIEW, face, n); } } if (!thing) { /* else where its top points */ Vec3 axis = upAlong(l), from = pointAt(l, RAISE_GRIP); thing = rayHit(world, who, l, from, axis, lengthOf(l) - RAISE_GRIP + AIM_PAST, face, n); } if (!thing) return NULL; *aim_y = face->y; if (fabsf(n->y) > 0.5f) { /* on top of it (or under it: a beam from below): the side towards you */ Vec3 to = flatv(vec3_sub(*face, who->position)); if (vec3_length(to) < 0.05f) return NULL; to = vec3_normalize(to); Vec3 o = vec3(who->position.x, face->y + (n->y > 0.0f ? -0.08f : 0.08f), who->position.z); Vec3 p, nn; if (rayHit(world, who, l, o, to, LEAN_REACH + 4.0f, &p, &nn) != thing) return NULL; *face = p; *n = nn; } return thing; } /* Lets go of it and puts it up there (stood up, slid out or opened, tipped: see putUp). n: out towards where you stand at it. */ static void startPutUp(Object *who, Object *l, Vec3 foot, Vec3 up, Vec3 z, Vec3 n, float extend_to, float top) { Vec3 from_foot = l->position, from_axis = upAlong(l), from_face = l->colliders[0].axes[2]; for (int h2 = 0; h2 < 2; h2++) if (who->hands.item[h2] == l) Hands_drop(who, h2); l->velocity = l->angular_velocity = vec3_zero(); l->is_static = true; l->leaning = false; l->lean_normal = n; l->placing = 1e-4f; l->place_foot[0] = from_foot; l->place_foot[1] = foot; l->place_axis[0] = from_axis; l->place_axis[1] = up; l->place_face[0] = from_face; l->place_face[1] = z; l->place_extend[0] = l->extend; l->place_extend[1] = extend_to; l->place_top = top; l->lean_top = l->place_top; /* (known now: the game says whether it reaches) */ /* stepping back out of its way, to stand at its foot */ float behind = vec3_dot(flatv(vec3_sub(who->position, foot)), n); if (behind < 0.5f) { who->position = vec3_add(who->position, vec3_scale(n, 0.5f - behind)); who->body.replant = true; Object_syncColliders(who); } } /* Would it stand there (opened, at extend_to) without going into anything? */ static bool roomFor(const World *world, const Object *who, const Object *l, const Object *thing, Vec3 foot, Vec3 up, Vec3 z, float extend_to) { Object stood = *l; stood.extend = extend_to; stood.rotation = euler_yxz_from_axes(up, z); stood.position = foot; stood.is_held = false; fitColliders(&stood, extend_to); for (int i = 0; i < world->count; i++) { const Object *o = world->objects[i]; if (o == l || o == thing || o == who || o->is_held || o->collider_count == 0 || o->type == OBJ_DECAL || o->type == OBJ_PUDDLE || o->type == OBJ_STINK || flatDist(o->position, foot) > 30.0f) continue; if (Physics_penetration(&stood, o) > LEAN_ROOM) return false; } return true; } /* The step ladder: opened and stood in front of you - or, a wall just past it, with its back to the wall (from its top you get up there if it's not too high). */ static int standStep(World *world, Object *who, Object *l, const char **why) { const float len = Shapes_stepLadderLength(), lean = 0.5f * Shapes_stepLadderSpread(1.0f); const float out = len * sinf(lean), height = len * cosf(lean); /* (each leg's feet this far out from under the top) */ Vec3 f = vec3_normalize(flatv(Object_facing(who))); Vec3 foot = vec3_add(who->position, vec3_scale(f, STEP_IN_FRONT)); float top = 0.0f; Vec3 face = vec3_zero(), n = vec3_zero(); Object *thing = faceAhead(world, who, l, &face, &n); if (thing && fabsf(n.y) < 0.35f) { n = vec3_normalize(flatv(n)); float d = vec3_dot(flatv(vec3_sub(face, who->position)), f); if (d < STEP_IN_FRONT + 2.0f * out + STEP_AT_WALL) { /* at it: its back to it */ f = vec3_scale(n, -1.0f); foot = vec3_add(face, vec3_scale(n, STEP_WALL_GAP + 2.0f * out)); Vec3 inside = vec3_sub(face, vec3_scale(n, 0.06f)); float ground = World_groundHeight(world, foot.x, foot.z, who->position.y + 0.8f); float h = topOf(thing, inside.x, inside.z, face.y + 40.0f) - ground; if (h > height - 0.6f && h < height + STEP_OFF_UP) top = len; } else thing = NULL; } else thing = NULL; foot.y = World_groundHeight(world, foot.x, foot.z, who->position.y + 0.8f); if (fabsf(foot.y - who->position.y) > 0.5f) { *why = "No level ground there for the step ladder"; return LEAN_NO; } Vec3 across = vec3_cross(f, UP); for (int leg = 0; leg < 2; leg++) { Vec3 c = vec3_add(foot, vec3_scale(f, 2.0f * out * (float)leg)); for (int s = -1; s <= 1; s += 2) { Vec3 p = vec3_add(c, vec3_scale(across, (float)s * LADDER_WIDTH * 0.5f)); float g = World_groundHeight(world, p.x, p.z, foot.y + 1.0f); if (g > foot.y + 0.12f || g < foot.y - 0.15f) { *why = "Not level enough there for the step ladder"; return LEAN_NO; } } } Vec3 back = vec3_scale(f, -1.0f); /* (you climb it from this side) */ Vec3 up = vec3_normalize(vec3_add(vec3_scale(UP, cosf(lean)), vec3_scale(f, sinf(lean)))); Vec3 z = vec3_normalize(vec3_sub(back, vec3_scale(up, vec3_dot(back, up)))); if (!roomFor(world, who, l, NULL, foot, up, z, 1.0f)) { *why = "No room for the step ladder there"; return LEAN_NO; } startPutUp(who, l, foot, up, z, back, 1.0f, top); l->stability = 1.0f; /* (it stands on its own legs) */ return LEAN_DONE; } /* Nothing to lean it on: stood up on its own in front of you (out all the way), and gravity does the rest - it falls over (see putUp, fallOver). */ static int standFree(World *world, Object *who, Object *l, const char **why) { Vec3 f = Object_facing(who); Vec3 foot = vec3_add(who->position, vec3_scale(f, FREE_AHEAD)); foot.y = World_groundHeight(world, foot.x, foot.z, who->position.y + 0.8f); if (fabsf(foot.y - who->position.y) > 0.4f) { *why = "No ground to stand it on there"; return LEAN_NO; } Vec3 tilt = vec3_normalize(vec3_add(UP, vec3_scale(f, 0.07f))); /* (never quite upright) */ Vec3 z = vec3_normalize(vec3_sub(vec3_scale(f, -1.0f), vec3_scale(tilt, vec3_dot(vec3_scale(f, -1.0f), tilt)))); startPutUp(who, l, foot, tilt, z, vec3_scale(f, -1.0f), folding(l) ? 1.0f : l->extend, 0.0f); l->place_top = -1.0f; /* (free: it falls when it's up) */ l->tip_dir = f; l->stability = 0.0f; return LEAN_DONE; } int Ladder_lean(World *world, Object *who, Object *l, bool fit, const char **why) { Vec3 face = vec3_zero(), n = vec3_zero(); float aim_y = -1e9f; /* (held up: where its top should go) */ if (stepLadder(l)) return standStep(world, who, l, why); const bool raised = Ladder_isRaised(l); /* (held up and looking down at the ground: slide it together) */ if (raised && who->type == OBJ_PLAYER && who->body.look_dir.y < LOOK_DOWN) return LEAN_NOTHING; Object *thing = raised ? aimedAt(world, who, l, &face, &n, &aim_y) : NULL; if (!thing) thing = faceAhead(world, who, l, &face, &n); if (!thing && raised) return standFree(world, who, l, why); if (!thing) return LEAN_NOTHING; if (fabsf(n.y) > 0.35f) { *why = "No upright side to lean it on there"; return LEAN_NO; } n = vec3_normalize(flatv(n)); /* How high it goes just behind that face, and the ground in front of it */ Vec3 inside = vec3_sub(face, vec3_scale(n, 0.06f)); float top = topOf(thing, inside.x, inside.z, face.y + 40.0f); if (top < -1e8f) top = face.y; Vec3 out = vec3_add(face, vec3_scale(n, 1.0f)); float ground = World_groundHeight(world, out.x, out.z, who->position.y + 0.8f); bool eaves = thing->type == OBJ_HOUSE || thing->type == OBJ_BARN || (thing->type == OBJ_WALL && thing->variant == 2); float h = top - (eaves ? 0.12f : 0.0f) - ground; if (h < MIN_HEIGHT) { *why = "Too low to need a ladder"; return LEAN_NO; } /* Rest the top on the edge if it reaches; else against the face */ const float angle = LEAN_ANGLE * PI_F / 180.0f; float extend_to = l->extend; if (fit) { /* slid out just so far: resting on the edge, a bit over it to get off at the top */ float want = h / sinf(angle) + OVERHANG, lo = 0.0f, hi = 1.0f; for (int k = 0; k < 20; k++) { /* (the sections slide out one after another: search for it) */ float mid = 0.5f * (lo + hi); if (Shapes_ladderLength(mid) < want) lo = mid; else hi = mid; } extend_to = folding(l) ? 1.0f : hi; /* (folding: all the way, it has no in between) */ } const float len = lengthFor(l, extend_to); bool reaches = h <= len * sinf(angle) - 0.15f; /* aimed lower down the face (not at its top): against the face there, as high as it reaches */ float against = len * sinf(angle) - 0.1f; float lean = angle; if (aim_y > -1e8f && aim_y - ground < h - AIM_EDGE && aim_y - ground < against) { /* (its top there: flatter, the foot further out - a ladder only leans so flat) */ reaches = false; against = fmaxf(aim_y - ground, MIN_HEIGHT); lean = asinf(clampf((against + 0.1f) / len, 0.0f, 1.0f)); if (lean < FLATTEST * PI_F / 180.0f) { /* (that low it can't go: as usual then, on the edge or as high as it reaches) */ lean = angle; reaches = h <= len * sinf(angle) - 0.15f; against = len * sinf(angle) - 0.1f; } } Vec3 rest = reaches && eaves ? vec3_add(face, vec3_scale(n, EAVES_OUT)) : face; rest.y = ground + (reaches ? h : against); Vec3 foot = vec3_add(rest, vec3_scale(n, (rest.y - ground) / tanf(lean) + 0.04f)); foot.y = World_groundHeight(world, foot.x, foot.z, ground + 0.8f); if (fabsf(foot.y - ground) > 0.3f) { *why = "No level ground for its feet there"; return LEAN_NO; } if (raised && flatDist(foot, who->position) > AIM_FOOT) { *why = "Too far away - step closer to put it up there"; return LEAN_NO; } Vec3 across = vec3_cross(n, UP); for (int s = -1; s <= 1; s += 2) { Vec3 p = vec3_add(foot, vec3_scale(across, (float)s * LADDER_WIDTH * 0.5f)); if (World_groundHeight(world, p.x, p.z, foot.y + 2.0f) > foot.y + 0.25f) { *why = "No room for its feet there"; return LEAN_NO; } } Vec3 up = vec3_normalize(vec3_sub(rest, foot)); Vec3 z = vec3_normalize(vec3_sub(n, vec3_scale(up, vec3_dot(n, up)))); /* the ladder's face, out from the wall */ /* Room for all of it there (not through a wall beside it, or anything else) */ if (!roomFor(world, who, l, thing, foot, up, z, extend_to)) { *why = "No room for it there - something's in the way"; return LEAN_NO; } startPutUp(who, l, foot, up, z, n, extend_to, reaches ? vec3_length(vec3_sub(rest, foot)) : 0.0f); /* How steady it will stand: the angle (75 deg is best: flatter its foot slides, steeper it tips back), soft or muddy ground, its feet not level, what holds it (a bush, something light that gives), resting on an edge or only against a face */ float deg = lean * 180.0f / PI_F, off = deg < LEAN_ANGLE ? (LEAN_ANGLE - deg) / 15.0f : (deg - LEAN_ANGLE) / 9.0f; float st = 1.0f - 0.7f * clampf(off, 0.0f, 1.0f); st *= 1.0f - 0.6f * clampf(Wetness_mud(&world->terrain.wet, foot.x, foot.z), 0.0f, 1.0f); float feet_off = 0.0f; for (int s2 = -1; s2 <= 1; s2 += 2) { Vec3 p = vec3_add(foot, vec3_scale(across, (float)s2 * LADDER_WIDTH * 0.5f)); feet_off = fmaxf(feet_off, fabsf(World_groundHeight(world, p.x, p.z, foot.y + 0.3f) - foot.y)); } st *= 1.0f - 0.6f * clampf(feet_off / 0.12f, 0.0f, 1.0f); if (thing->type == OBJ_BUSH) st *= 0.2f; else if (!thing->is_static && thing->weight < HOLDS_WEIGHT) st *= clampf(thing->weight / HOLDS_WEIGHT, 0.1f, 1.0f); if (!reaches) st *= 0.85f; l->stability = clampf(st, 0.0f, 1.0f); return LEAN_DONE; } Vec3 Ladder_edge(const Object *l) { return pointAt(l, l->lean_top > 0.0f ? l->lean_top : lengthOf(l)); } Object *Ladder_near(const World *world, const Object *who, bool *at_top) { Vec3 f = Object_facing(who); for (int i = 0; i < world->count; i++) { Object *l = world->objects[i]; if (l->type != OBJ_LADDER || !l->leaning) continue; /* at its foot, facing it */ Vec3 to = flatv(vec3_sub(l->position, who->position)); float d = vec3_length(to); if (d < MOUNT_REACH && fabsf(l->position.y - who->position.y) < 0.6f && (d < 0.6f || vec3_dot(vec3_scale(to, 1.0f / d), f) > 0.3f)) { *at_top = false; return l; } /* up there, at its top */ if (l->lean_top > 0.0f) { Vec3 edge = Ladder_edge(l); if (flatDist(edge, who->position) < MOUNT_REACH && fabsf(edge.y - who->position.y) < (stepLadder(l) ? STEP_OFF_UP + 0.1f : 0.8f)) { *at_top = true; return l; } } } return NULL; } bool Ladder_take(Object *who, Object *l) { int hand = Hands_freeHand(who); if (hand < 0 || !l->leaning) return false; if (l->held_by) l->held_by->holding_ladder = NULL; l->held_by = NULL; l->leaning = false; l->is_static = false; l->lean_top = 0.0f; l->rest_time = 0.0f; l->dipped = l->steep = false; l->grip_shift = 0.0f; if (stepLadder(l)) l->extend_goal = 0.0f; /* (folds up as you take it) */ else l->raised = clampf(asinf(clampf(upAlong(l).y, -1.0f, 1.0f)), 0.15f, RAISE_HIGH); /* (held up as steep as it leaned: no swing down and up again) */ return Hands_pickUpObject(who, hand, l); } /* Highest the feet go: the hands still find rungs above them. */ static float highest(const Object *l) { if (stepLadder(l)) return Shapes_stepLadderRung(STEP_TREADS - 1) - 0.02f; /* (on the top tread, not the platform) */ float top = l->lean_top > 0.0f ? l->lean_top : lengthOf(l); return fmaxf(0.0f, top - HANDS_ABOVE - 0.15f); } /* Stands the climber on the ladder at ladder_at, facing it. */ static void place(Object *who) { const Object *l = who->on_ladder; Vec3 n = l->lean_normal; who->position = vec3_add(vec3_add(pointAt(l, who->ladder_at), vec3_scale(n, STAND_OUT)), who->ladder_shift); who->rotation.y = atan2f(-n.z, -n.x) + PI_F / 2.0f; who->velocity = who->movement = vec3_zero(); who->is_grounded = false; Object_syncColliders(who); } void Ladder_mount(Object *who, Object *l, bool at_top) { /* hands and feet go onto the rungs from where they are now (eased, see limbTo) */ const Vec3 f = Object_facing(who); for (int i = 0; i < 2; i++) who->ladder_from[i] = who->body.feet[i].ankle; who->ladder_from[2] = Figure_toWorld(who->position, f, who->skeleton.joints[JOINT_HAND_L]); who->ladder_from[3] = Figure_toWorld(who->position, f, who->skeleton.joints[JOINT_HAND_R]); for (int k = 0; k < 4; k++) { who->ladder_to[k] = who->ladder_from[k]; who->ladder_t[k] = 1.0f; } who->ladder_limbs_ok = true; who->on_ladder = l; who->ladder_at = at_top ? highest(l) : 0.0f; who->climbing = true; who->sitting = false; const Vec3 was = who->position; who->ladder_shift = vec3_zero(); place(who); who->ladder_shift = vec3_sub(was, who->position); /* (from where you stood onto it: eased, see Ladder_climb) */ who->position = was; Object_syncColliders(who); } void Ladder_mountAt(Object *who, Object *l, float at) { who->on_ladder = l; who->ladder_at = clampf(at, 0.0f, highest(l)); who->ladder_shift = vec3_zero(); who->ladder_off = 0.0f; who->ladder_limbs_ok = false; /* (no easing from where they were: straight onto the rungs) */ who->climbing = true; who->sitting = false; place(who); } /* Where you stand once off it at the bottom. */ static Vec3 offSpot(const Object *l) { return vec3_add(l->position, vec3_scale(l->lean_normal, 0.45f)); } void Ladder_getOff(Object *who) { who->ladder_shift = vec3_zero(); who->ladder_off = 0.0f; who->on_ladder = NULL; who->climbing = false; } int Ladder_climb(Object *who, float input, float dt) { Object *l = who->on_ladder; if (!l) return CLIMB_OFF_BOTTOM; if (who->ladder_off > 0.0f) { /* stepping off backwards onto the ground, one foot after the other */ who->ladder_off = fminf(1.0f, who->ladder_off + dt / STEP_OFF_TIME); Vec3 off = offSpot(l); who->position = vec3_lerp(who->ladder_off_from, off, smoothstepf(0.0f, 1.0f, who->ladder_off)); Object_syncColliders(who); if (who->ladder_off < 1.0f) return CLIMB_ON; Ladder_getOff(who); who->is_grounded = true; who->body.velocity = vec3_zero(); /* (the feet are down there already: they stay) */ return CLIMB_OFF_BOTTOM; } who->ladder_at += input * CLIMB_SPEED * Object_vigor(who) * (0.7f + 0.6f * who->fitness) * dt; /* (the sporty quicker) */ { /* just got on: the body eases over onto it */ float left = vec3_length(who->ladder_shift), step = LADDER_ONTO * dt; who->ladder_shift = left > step ? vec3_scale(who->ladder_shift, (left - step) / left) : vec3_zero(); } if (who->ladder_at < 0.0f) { /* down: step off backwards onto the ground (see above) */ who->ladder_at = 0.0f; who->ladder_off = 1e-4f; who->ladder_off_from = who->position; for (int i = 0; i < 2; i++) who->ladder_from[i] = who->body.feet[i].ankle; /* (the feet go down from there) */ return CLIMB_ON; } float top = highest(l); bool at_edge = who->ladder_at >= top && input > 0.0f && l->lean_top > 0.0f; who->ladder_at = fminf(who->ladder_at, top); place(who); return at_edge ? CLIMB_AT_EDGE : CLIMB_ON; } /* Rung k's height along the ladder. */ static float rung(const Object *l, int k) { return stepLadder(l) ? Shapes_stepLadderRung(k) : folding(l) ? Shapes_foldLadderRung(k) : Shapes_ladderRung(l->extend, k); } static int rungCount(const Object *l) { return stepLadder(l) ? STEP_TREADS + 1 : folding(l) ? (l->extend > 0.99f ? 2 * FOLD_RUNGS : FOLD_RUNGS) : LADDER_RUNGS; } /* The rung at or just above `along`. */ static int rungAbove(const Object *l, float along) { for (int k = 0; k < rungCount(l); k++) if (rung(l, k) >= along) return k; return rungCount(l) - 1; } /* A hand or foot going from rung to rung: it speeds up and slows down, and comes away from the ladder on the way (clear of the rungs). Returns where it is; *out = how far out (0..1). Limb k: 0, 1 feet, 2, 3 hands. */ static Vec3 limbTo(Object *who, int k, Vec3 goal, float time, float dt, float *out) { Vec3 *from = &who->ladder_from[k], *to = &who->ladder_to[k]; float *t = &who->ladder_t[k]; float e = *t * *t * *t * (*t * (*t * 6.0f - 15.0f) + 10.0f); if (!who->ladder_limbs_ok) { *from = *to = goal; *t = 1.0f; e = 1.0f; } if (vec3_length(vec3_sub(goal, *to)) > 1e-3f) { /* off to another rung, from wherever it is now */ *from = vec3_lerp(*from, *to, e); *to = goal; *t = 0.0f; } *t = fminf(1.0f, *t + dt / time); e = *t * *t * *t * (*t * (*t * 6.0f - 15.0f) + 10.0f); float arc = sinf(PI_F * *t); *out = arc * arc; return vec3_lerp(*from, *to, e); } #define HAND_TO_RUNG 0.3f /* seconds a hand takes to its next rung */ #define FOOT_TO_RUNG 0.34f /* ... and a foot */ /* Holding it: at the spot behind its foot, facing it. */ static bool atHold(const Object *who) { const Object *l = who->holding_ladder; Vec3 facing; return l && l->held_by == who && !l->placing && l->tip_v <= 0.0f && flatDist(who->position, Ladder_holdSpot(l, &facing)) < 0.5f; } void Ladder_poseHands(Object *who, float dt) { const Object *l = who->on_ladder; if (!l && atHold(who)) { /* holding it steady: both hands on the rails at chest height */ const Object *hl = who->holding_ladder; Vec3 across = vec3_normalize(vec3_cross(hl->lean_normal, UP)); float s = who->look.height / 1.75f, side = hl->hold_side != 0.0f ? hl->hold_side : 1.0f; for (int h = 0; h < 2; h++) { /* both on the rail on their side, one higher (the hand nearer the wall) */ bool high = (h == HAND_LEFT) == (side > 0.0f); Vec3 at = pointAt(hl, (high ? 1.3f : 0.95f) * s); who->body.hand_active[h] = true; who->body.hand_target[h] = vec3_add(at, vec3_add(vec3_scale(across, side * (LADDER_WIDTH * 0.5f + 0.03f)), vec3_scale(hl->lean_normal, 0.02f))); } return; } if (!l) return; const float s = who->look.height / 1.75f; Vec3 n = l->lean_normal, across = vec3_normalize(vec3_cross(n, UP)); if (who->ladder_off > 0.5f) return; /* (stepping off: let go) */ int k = rungAbove(l, who->ladder_at + HANDS_ABOVE * s); for (int h = 0; h < 2; h++) { if (who->hands.state[h] == HAND_HOLDING) continue; /* (one-handed: the other holds something) */ /* hand over hand: one a rung above the other, swapping as you go */ int kh = k + ((k + h) % 2 == 0 ? 0 : -1); if (kh < 0) kh = 0; float side = h == HAND_LEFT ? 1.0f : -1.0f; /* facing the wall, its left is along +across */ Vec3 goal = vec3_add(pointAt(l, rung(l, kh)), vec3_add(vec3_scale(across, side * 0.17f), vec3_scale(n, 0.04f))); float away; Vec3 at = limbTo(who, 2 + h, goal, HAND_TO_RUNG / fmaxf(Object_vigor(who) * (0.7f + 0.6f * who->fitness), 0.5f), dt, &away); who->body.hand_active[h] = true; who->body.hand_target[h] = vec3_add(at, vec3_scale(n, 0.08f * away * s)); } who->body.lean_goal = 0.35f; who->body.crouch_goal = 0.0f; } void Ladder_poseFeet(Object *who, float dt) { const Object *l = who->on_ladder; if (!l && atHold(who) && who->body.pose == POSE_UP) { /* holding it: a foot on the bottom rung */ const Object *hl = who->holding_ladder; float s = who->look.height / 1.75f; Vec3 across = vec3_normalize(vec3_cross(hl->lean_normal, UP)); float side = hl->hold_side != 0.0f ? hl->hold_side : 1.0f; Foot *foot = &who->body.feet[side > 0.0f ? HAND_RIGHT : HAND_LEFT]; /* (the foot nearer it: on the end of the bottom rung) */ Vec3 goal = vec3_add(pointAt(hl, rung(hl, 0)), vec3_add(vec3_scale(hl->lean_normal, 0.06f * s), vec3_scale(across, side * (LADDER_WIDTH * 0.5f - 0.08f)))); goal.y += 0.06f * s; /* (ankle over the rung) */ float k = fminf(1.0f, 6.0f * dt); foot->ankle = vec3_lerp(foot->ankle, goal, k); foot->dir = vec3_scale(hl->lean_normal, -1.0f); foot->toe = vec3_add(foot->ankle, vec3_add(vec3_scale(foot->dir, 0.14f * s), vec3(0.0f, -0.05f * s, 0.0f))); return; } if (!l) return; const float s = who->look.height / 1.75f; Vec3 n = l->lean_normal, across = vec3_normalize(vec3_cross(n, UP)); if (who->ladder_off > 0.0f) { /* stepping off: one foot down onto the ground, then the other */ for (int i = 0; i < 2; i++) { Foot *foot = &who->body.feet[i]; float side = i == 0 ? 1.0f : -1.0f; float u = smoothstepf(i == 0 ? 0.0f : 0.4f, i == 0 ? 0.6f : 1.0f, who->ladder_off); Vec3 down = vec3_add(offSpot(l), vec3_add(vec3_scale(across, side * 0.11f * s), vec3_scale(n, -0.05f * s))); down.y = l->position.y + 0.075f * s; /* (ankle height over the ground) */ Vec3 at = vec3_lerp(who->ladder_from[i], down, u); at.y += 0.08f * s * sinf(PI_F * u); /* (lifted off the rung, set down) */ foot->ankle = at; foot->dir = vec3_scale(n, -1.0f); foot->toe = vec3_add(at, vec3_add(vec3_scale(n, -0.14f * s), vec3(0.0f, -lerpf(0.05f, 0.055f, u) * s, 0.0f))); foot->stepping = false; } return; } int k = rungAbove(l, who->ladder_at - 0.02f); for (int i = 0; i < 2; i++) { /* one foot a rung higher than the other, swapping as you climb */ int kf = k + ((k + i) % 2 == 0 ? 0 : 1), last = rungCount(l) - (stepLadder(l) ? 2 : 1); /* (no feet on the platform) */ if (kf > last) kf = last; float side = i == 0 ? 1.0f : -1.0f; Foot *foot = &who->body.feet[i]; Vec3 goal = vec3_add(pointAt(l, rung(l, kf)), vec3_add(vec3_scale(across, side * 0.11f), vec3_add(vec3_scale(n, 0.07f), vec3(0.0f, 0.075f * s, 0.0f)))); /* from rung to rung: lifted off, out a little (the toes clear the rungs) and set down */ float away; Vec3 at = limbTo(who, i, goal, FOOT_TO_RUNG / fmaxf(Object_vigor(who) * (0.7f + 0.6f * who->fitness), 0.5f), dt, &away); foot->ankle = vec3_add(at, vec3_scale(n, 0.1f * away * s)); foot->dir = vec3_scale(n, -1.0f); foot->toe = vec3_add(foot->ankle, vec3_add(vec3_scale(n, -0.14f * s), vec3(0.0f, -0.05f * (1.0f + away) * s, 0.0f))); foot->stepping = false; } who->ladder_limbs_ok = true; } bool Ladder_isRaised(const Object *l) { return l->type == OBJ_LADDER && !stepLadder(l) && l->is_held && l->extend_goal > 0.5f && l->placing <= 0.0f; } /* How steep a raised ladder is held: up with the view (more upright looking up). */ static float raiseGoal(const Object *who) { float pitch = asinf(clampf(who->body.look_dir.y, -1.0f, 1.0f)); if (who->type != OBJ_PLAYER) pitch = 0.0f; return clampf(RAISE_ANGLE + RAISE_FOLLOW * pitch, RAISE_LOW, RAISE_HIGH); } /* Up the rail from the foot to where a raised ladder is held, and to the other hand. */ Vec3 Ladder_raisedGrip(const Object *l, float along) { return pointAt(l, along); } void Ladder_placeHeld(Object *who, int hand, Object *l) { Vec3 facing = Object_facing(who); Vec3 right = vec3_normalize(vec3_cross(facing, UP)); int jh = hand == HAND_LEFT ? JOINT_HAND_L : JOINT_HAND_R; if (Ladder_isRaised(l)) { /* slid out: held near its foot, reaching up into the air in front */ l->raise_goal = l->dipped ? RAISE_DIP : l->steep ? RAISE_HIGH : raiseGoal(who); /* (eased in Ladder_update) */ if (l->raised <= 0.0f) l->raised = 0.15f; Vec3 axis = vec3_normalize(vec3_add(vec3_scale(facing, cosf(l->raised)), vec3_scale(UP, sinf(l->raised)))); Vec3 z = vec3_normalize(vec3_cross(right, axis)); /* (rungs across, the rails side by side) */ l->rotation = euler_yxz_from_axes(axis, z); Object_syncColliders(l); Vec3 palm = Figure_toWorld(who->position, facing, who->skeleton.joints[jh]); Vec3 foot = vec3_sub(palm, vec3_add(vec3_scale(axis, RAISE_GRIP), vec3_scale(right, (hand == HAND_LEFT ? -1.0f : 1.0f) * LADDER_WIDTH * 0.5f))); l->position = foot; Object_syncColliders(l); l->velocity = vec3_zero(); return; } l->raised = 0.0f; l->dipped = l->steep = false; /* on edge, along the way you walk: local Y forward, the rungs upright */ l->rotation = euler_yxz_from_axes(facing, vec3_scale(right, -1.0f)); Object_syncColliders(l); Vec3 palm = Figure_toWorld(who->position, facing, who->skeleton.joints[jh]); Vec3 center = vec3_sub(palm, vec3(0.0f, LADDER_WIDTH * 0.5f - 0.02f, 0.0f)); /* holding the upper rail */ center = vec3_add(center, vec3_scale(right, hand == HAND_LEFT ? -0.05f : 0.05f)); center = vec3_add(center, vec3_scale(facing, l->grip_shift)); /* (slid along in the hand: off a wall) */ l->position = vec3_sub(center, l->colliders[0].offset); l->velocity = vec3_zero(); }