Entropie git · main
Entropie festival game; first steps
C 88.5% Markdown 9.1% GLSL 2.3%git clone https://git.christianimmanuel.de/games/Entropie.gitwget https://git.christianimmanuel.de/games/Entropie/archive/Entropie.tar.gzentropie/src/people/body.c raw
/*
* The body over time: feet that step, falls, moods and fidgets.
* Body_update moves everything along; body_pose.c turns it into a skeleton.
*/
#include "people/body_internal.h"
static const float FIDGET_LENGTH[FIDGET_COUNT] = { 0.0f, 1.6f, 2.4f, 2.6f, 2.0f, 3.0f, 6.0f, 2.8f, 2.4f, 5.0f, 5.0f, 5.5f, 5.0f };
static Vec3 clearSpot(const BodyFrame *f, Foot *foot, Vec3 spot, Vec3 other, float reach, bool sloppy, float s);
static Vec3 leapPast(const BodyFrame *f, Foot *foot, Vec3 aim, Vec3 other, float reach, float s);
static void crossOver(Body *b, const BodyFrame *f, float s);
#define KNEE_ROOM 0.35f /* a foot this far back from anything higher than ... */
#define KNEE_HIGH 0.5f /* ... this (scaled): room for the knee */
#define CROSS_OVER 0.12f /* a foot lifted this much over something ... */
#define CROSS_FROM 0.15f /* ... and this far into its step: half way over it */
#define CROSS_PAST 0.2f /* the body goes no further than this past where that foot came down */
#define CROSS_DONE 0.06f /* over: the body this close behind where that foot came down (or past it) */
#define CROSS_AWAY 0.75f /* the body this far back from it: not going over after all */
static float random01(Body *b) {
if (b->rng == 0) b->rng = 0x9E3779B9u ^ (unsigned)(b->clock * 7919.0f);
b->rng ^= b->rng << 13;
b->rng ^= b->rng >> 17;
b->rng ^= b->rng << 5;
return (float)(b->rng & 0xffffffu) / 16777215.0f;
}
static Vec3 rightOf(Vec3 facing) { return vec3_normalize(vec3_cross(facing, UP)); }
/* Turns a horizontal direction towards its right by angle (radians). */
static Vec3 turnRight(Vec3 dir, float angle) {
return vec3_add(vec3_scale(dir, cosf(angle)), vec3_scale(rightOf(dir), sinf(angle)));
}
/* Step length grows with speed: short steps when slow, long strides when
running (about 2.9 steps/s at 3 m/s, 4 steps/s at 6.5 m/s). */
static float stepLength(float speed, float s) {
return clampf(0.3f + 0.27f * speed, 0.35f, 1.8f) * s; /* (a brisk walk: long steps, not a shuffle; a sprint: long strides) */
}
/* How far from the other foot a step over something may land: walking a
careful step, running a leap. */
static float overReach(const Body *b, float s) {
return fmaxf(OVER_STRIDE * s, OVER_LEAP * b->step_length);
}
/* Where a foot should stand: beside the body, half a step ahead when moving. */
/* The ground a foot can stand on around the body: no further down than a
leg reaches (over an edge the foot stays up; the body falls if it must). */
static float groundUnder(const BodyFrame *f, float x, float z, float s) {
return fmaxf(f->ground(f->ground_ctx, x, z, f->position.y + STEP_UP * s), f->position.y - MAX_DROP * s);
}
static Vec3 footSpot(const Body *b, const BodyFrame *f, int side, float s) {
float sign = side == 0 ? -1.0f : 1.0f;
float width = FOOT_X * (1.0f + 0.12f * proudness(b) - 0.2f * shyness(b) + 0.15f * b->style[0]); /* (their own way) */
Vec3 spot = vec3_add(f->position, vec3_scale(rightOf(f->facing), sign * width * s));
spot = vec3_add(spot, vec3_scale(f->facing, b->stand_ahead * s)); /* (in front of a seat) */
Vec3 v = flat(b->velocity);
float speed = vec3_length(v);
if (speed > 0.05f) /* half a step ahead, but never further than a leg can land */
spot = vec3_add(spot, vec3_scale(v, fminf(0.5f * b->step_length, MAX_LEAD * (1.0f + 0.45f * runAmount(speed)) * s) / speed)); /* (running: reaching out further) */
if (b->unsteadiness > 0.0f) {
/* Drunk: steps land a bit off */
float n = sinf(b->clock * 2.3f + (float)side * 1.7f) * cosf(b->clock * 1.1f);
spot = vec3_add(spot, vec3_scale(rightOf(f->facing), n * 0.15f * b->unsteadiness * s));
}
/* Standing about: one foot a little ahead, or the feet wider apart, now and then another way */
float still = 1.0f - smoothstepf(0.05f, 0.3f, speed);
if (still > 0.0f) {
Vec3 st = b->stance[side];
spot = vec3_add(spot, vec3_scale(vec3_add(vec3_scale(rightOf(f->facing), st.x), vec3_scale(f->facing, st.z)), still * s));
}
spot.y = groundUnder(f, spot.x, spot.z, s);
/* Something higher than a knee just ahead (a wall, a ledge): the foot stays back
far enough for the knee (it bends forward) */
for (float d = 0.04f * s; d <= KNEE_ROOM * s; d += 0.04f * s) {
Vec3 q = vec3_add(spot, vec3_scale(f->facing, d));
if (f->ground(f->ground_ctx, q.x, q.z, spot.y + 1.2f * s) - spot.y > KNEE_HIGH * s) {
spot = vec3_sub(spot, vec3_scale(f->facing, KNEE_ROOM * s - d + 0.04f * s));
spot.y = groundUnder(f, spot.x, spot.z, s);
break;
}
}
return spot;
}
/* Direction a planted foot points: a little outwards, inwards when shy. */
static Vec3 footDir(const Body *b, Vec3 facing, int side) {
float sign = side == 0 ? -1.0f : 1.0f;
return turnRight(facing, sign * (TOE_OUT + 0.04f * b->style[1] - 0.42f * shyness(b)));
}
/* A foot's spot may not cross over to the other foot's side. */
static Vec3 uncrossed(Vec3 spot, const BodyFrame *f, int side, Vec3 other, float s) {
Vec3 right = rightOf(f->facing);
float sign = side == 0 ? -1.0f : 1.0f;
float across = vec3_dot(vec3_sub(spot, other), right) * sign; /* > 0: on its own side */
if (across < FOOT_GAP * s) {
spot = vec3_add(spot, vec3_scale(right, sign * (FOOT_GAP * s - across)));
spot.y = groundUnder(f, spot.x, spot.z, s);
}
return spot;
}
static Vec3 ankleAt(Vec3 ground_spot, float s) {
return vec3_add(ground_spot, vec3(0.0f, ANKLE_Y * s, 0.0f));
}
/* Toe on the ground in front of the ankle, so feet follow slopes. */
static Vec3 toePlanted(const BodyFrame *f, Vec3 ankle, Vec3 dir, float s) {
Vec3 tip = vec3_add(ankle, vec3_scale(dir, FOOT_LENGTH * s));
tip.y = f->ground(f->ground_ctx, tip.x, tip.z, ankle.y + 0.1f * s) + 0.02f * s;
tip.y = fmaxf(tip.y, ankle.y - 0.14f * s); /* toes over an edge stay level */
return tip;
}
/* "Flat" is the way a planted foot points: the ankle sits higher than the toes. */
#define FLAT_PITCH (-atan2f(ANKLE_Y - 0.02f, FOOT_LENGTH))
/* How high a stepping foot is (0..1) through the step: highest early on
(sooner the smaller early is), and it leaves and meets the ground gently. */
static float stepArc(float step, float early) {
return sinf(PI_F * powf(step, early)) * smoothstepf(0.0f, 0.3f, step) * (1.0f - smoothstepf(0.8f, 1.0f, step));
}
/* How far the toes are up when the heel lands (walking; running lands flatter). */
static float heelStrike(float run) { return 0.36f * (1.0f - run); }
/* Swinging foot: it leaves the ground tilted as it was (on the toes), hangs,
and the toes come up before the heel lands. */
static Vec3 toeSwinging(const BodyFrame *f, Vec3 ankle, Vec3 dir, float step, float pitch0, float strike, float s) {
float swing = FLAT_PITCH - 0.25f * (1.0f - smoothstepf(0.2f, 0.6f, step)) + strike * smoothstepf(0.45f, 0.95f, step);
float pitch = lerpf(pitch0, swing, smoothstepf(0.0f, 0.4f, step));
Vec3 along = vec3_add(vec3_scale(dir, cosf(pitch)), vec3_scale(UP, sinf(pitch)));
Vec3 tip = vec3_add(ankle, vec3_scale(along, FOOT_LENGTH * s));
float ground = f->ground(f->ground_ctx, tip.x, tip.z, ankle.y + 0.1f * s) + 0.02f * s;
if (ankle.y > ground) tip.y = fmaxf(tip.y, ground); /* (coming up from below an edge: no clamping) */
return tip;
}
static void plantAll(Body *b, const BodyFrame *f, float s) {
b->root_ok = false; /* (moved elsewhere: the hips are drawn right there) */
for (int i = 0; i < 2; i++) {
Foot *foot = &b->feet[i];
foot->ankle = ankleAt(footSpot(b, f, i, s), s);
foot->dir = footDir(b, f->facing, i);
foot->toe = toePlanted(f, foot->ankle, foot->dir, s);
foot->stepping = false;
}
}
/* ---------- Falling down, lying, getting up ---------- */
#define LIE_DRAPE 0.5f /* a falling body can come to lie across things this high (m above the feet) */
#define LIE_TURN_COST 1.2f /* (per radian off the way the fall wanted to go) */
/* How bad it would be to come to lie this way: across something too high is
out of the question, lumps under the body are uncomfortable. */
static float lieCost(const Body *b, Vec3 dir, float reach) {
const float s = b->at_scale;
const Vec3 feet = b->at, side = rightOf(dir);
float cost = 0.0f, last = 0.0f;
for (int k = 0; k <= 12; k++) {
float along = reach - 0.9f + 0.15f * (float)k; /* from the feet end to the head end (unscaled) */
for (int w = -1; w <= 1; w++) {
if (w && (k < 5 || k > 10)) continue; /* (the shoulders and hips are wide) */
Vec3 p = vec3_add(feet, vec3_add(vec3_scale(dir, along * s), vec3_scale(side, 0.2f * s * (float)w)));
float h = b->ground(b->clutter_ctx, p.x, p.z, feet.y + 2.5f * s) - feet.y;
if (h > LIE_DRAPE * s) cost += 20.0f + 20.0f * h;
if (!w) {
if (k) cost += 2.0f * fabsf(h - last);
last = h;
}
}
}
return cost;
}
/* Where the body comes down: the way it was going if there's room, else a bit
to the side, or shorter (crumpling at the foot of a wall), never into or
over something high. */
static void chooseLanding(Body *b, float most_turn) {
if (!b->ground || b->at_scale <= 0.0f) return;
static const float TURNS[] = { 0.0f, 0.35f, -0.35f, 0.7f, -0.7f, 1.1f, -1.1f, 1.6f, -1.6f, 2.3f, -2.3f, 3.14f };
static const float REACHES[] = { 1.0f, 0.7f, 0.45f };
float best = 1e9f, best_turn = 0.0f, best_reach = 1.0f;
for (unsigned t = 0; t < sizeof TURNS / sizeof TURNS[0]; t++) {
if (fabsf(TURNS[t]) > most_turn) continue;
for (unsigned r = 0; r < sizeof REACHES / sizeof REACHES[0]; r++) {
float reach = b->lie_offset * REACHES[r];
float cost = lieCost(b, turnRight(b->fall_dir, TURNS[t]), reach) + LIE_TURN_COST * fabsf(TURNS[t]) +
2.0f * (1.0f - REACHES[r]);
if (cost < best) {
best = cost;
best_turn = TURNS[t];
best_reach = reach;
}
}
if (best < 0.3f) break; /* (good enough: the way it was going) */
}
b->fall_dir = turnRight(b->fall_dir, best_turn);
b->lie_offset = best_reach;
}
void Body_fall(Body *b, Vec3 dir) {
if (b->pose != POSE_UP) return;
dir = flat(dir);
if (vec3_length(dir) < 1e-4f) dir = b->last_facing;
/* No two falls alike: straight like a plank, knees giving way first, or
turning on the way down; a bit off the push, a bit faster or slower */
float r = random01(b);
b->fall_style = b->unsteadiness > 0.3f && r < 0.5f ? FALL_CRUMPLE : (r < 0.35f ? FALL_PLANK : (r < 0.7f ? FALL_TWIST : FALL_CRUMPLE));
b->fall_twist = b->fall_style == FALL_TWIST ? (random01(b) - 0.5f) * 1.8f : (random01(b) - 0.5f) * 0.4f;
b->fall_time = FALL_TIME * (0.8f + 0.5f * random01(b)) * (b->fall_style == FALL_CRUMPLE ? 1.3f : 1.0f);
dir = flat(dir);
if (vec3_length(dir) < 1e-3f) dir = vec3_length(b->last_facing) > 1e-3f ? b->last_facing : vec3(0.0f, 0.0f, 1.0f); /* (no way given: forward) */
dir = turnRight(vec3_normalize(dir), (random01(b) - 0.5f) * 0.6f);
b->fall_delay = 0.0f;
b->pose = POSE_FALLING;
b->pose_time = 0.0f;
b->fall_dir = vec3_normalize(dir);
b->lie_offset = FALL_REACH;
b->face_down = vec3_dot(b->fall_dir, b->last_facing) > -0.3f; /* backwards: on the back */
b->asleep = false;
b->down_time = 1.8f + 1.4f * random01(b);
b->from_knees = false;
chooseLanding(b, 3.2f);
b->face_down = vec3_dot(b->fall_dir, b->last_facing) > -0.3f;
}
void Body_dropAndRoll(Body *b, Vec3 dir) {
if (b->pose == POSE_DOWN && !b->rolling) { /* already down: roll right away */
b->rolling = true;
b->pose_time = 0.0f;
b->face_down = true;
b->down_time = fmaxf(b->down_time, ROLL_REST);
return;
}
if (b->pose != POSE_UP) return;
Body_fall(b, dir);
b->fall_style = FALL_DIVE;
b->fall_twist = 0.0f;
b->fall_time = DIVE_TIME;
b->fall_dir = vec3_normalize(flat(dir)); /* straight ahead, no wobble */
b->face_down = true;
b->lie_offset = FALL_REACH * 1.05f;
chooseLanding(b, 1.2f);
b->down_time = ROLL_REST;
b->rolling = true;
b->roll = 0.0f;
b->roll_side = 0.0f;
}
void Body_rollOnto(Body *b, Vec3 along, float side) {
if (b->pose != POSE_UP) return;
along = flat(along);
b->pose = POSE_FALLING;
b->pose_time = 0.0f;
b->fall_dir = vec3_length(along) > 1e-3f ? vec3_normalize(along) : b->last_facing;
b->lie_offset = 0.0f; /* (the hips where it is: on the edge) */
b->face_down = true;
b->asleep = false;
b->from_knees = true;
b->fall_style = FALL_PLANK;
b->fall_twist = 0.0f;
b->fall_time = 0.45f; /* (down onto it, not a fall) */
b->down_time = ROLL_OVER_TIME + 0.5f;
b->rolling = false;
b->roll = b->roll_side = 0.0f;
b->roll_over = side >= 0.0f ? 1.0f : -1.0f;
}
#define LAND_EASY 5.0f /* m/s down (about 1.3 m): the legs take it as they are */
#define LAND_DEEP_ 7.0f /* (2.5 m) deep into the knees, bounced off ... */
#define LAND_KNEES 8.2f /* (3.4 m) ... the knees alone take it up to here (the sure), harder: a roll or a crash */
#define LAND_ROLL_MOST 8.0f /* (3.3 m) a roll takes it up to this ... */
#define LAND_SPORT 1.0f /* ... the very sporty this much more (4.1 m) */
#define LAND_BREAK 10.5f /* (5.6 m) landing this hard, something gives (the worst) */
#define ROLL_FORWARD 1.2f /* m/s along: a roll needs going somewhere */
int Body_land(Body *b, Vec3 dir, float speed, float forward, float sure, float sporty, float *bad) {
*bad = 0.0f;
if (b->pose != POSE_UP || speed < LAND_EASY) return LAND_SOFT;
/* first always into the knees: bounce it off */
float hard = (speed - LAND_EASY) / (LAND_DEEP_ - LAND_EASY); /* 0 .. 1 at a deep landing */
b->land_squat = clampf(0.35f + 0.5f * hard, 0.0f, 1.1f);
b->crouch_v += 3.0f + 2.0f * hard; /* (down at once, not eased) */
/* going somewhere, not too high, and knowing how: over the shoulder, and up again */
float limit = LAND_ROLL_MOST + LAND_SPORT * sporty;
if (speed >= LAND_DEEP_ - 0.5f && forward >= ROLL_FORWARD && speed < limit && random01(b) < sure) {
float lateral = vec3_dot(flat(dir), rightOf(b->last_facing)); /* (the shoulder on the side the way goes, else their own) */
float side = fabsf(lateral) > 0.2f ? (lateral > 0.0f ? 1.0f : -1.0f) : (b->quirk[0] > 0.0f ? 1.0f : -1.0f);
Body_shoulderRoll(b, dir, side);
*bad = 0.05f * clampf((speed - LAND_DEEP_) / (limit - LAND_DEEP_), 0.0f, 1.0f);
return LAND_ROLL;
}
if (speed < LAND_DEEP_) { *bad = 0.12f * hard * (1.0f - sure); return LAND_DEEP; } /* (the knees take it - an unsure one may twist an ankle) */
float over = clampf((speed - LAND_DEEP_) / (LAND_BREAK - LAND_DEEP_), 0.0f, 1.0f);
*bad = clampf(0.2f + 0.8f * over - 0.25f * sure, 0.05f, 1.0f);
if (speed < LAND_KNEES && random01(b) < 0.4f + 0.5f * sure) { *bad *= 0.6f; return LAND_DEEP; } /* (deep, and still on the feet) */
Body_fall(b, dir);
return LAND_CRASH;
}
void Body_shoulderRoll(Body *b, Vec3 dir, float side) {
if (b->pose != POSE_UP) return;
Vec3 d = flat(dir);
b->roll_dir = vec3_length(d) > 1e-3f ? vec3_normalize(d) : b->last_facing;
b->roll_shoulder = side >= 0.0f ? 1.0f : -1.0f;
b->shoulder_roll = 1e-4f;
b->land_squat = 0.0f;
}
bool Body_isRolling(const Body *b) { return b->shoulder_roll > 0.0f; }
float Body_clumsiness(const Body *b) {
return 0.5f + 0.5f * b->quirk[3];
}
/* Staggers backwards a few steps, then goes down that way. */
static void fallLater(Body *b, Vec3 dir, float delay) {
if (b->pose != POSE_UP || b->fall_delay > 0.0f) return;
b->fall_delay = delay;
b->fall_pending = vec3_normalize(flat(dir));
b->stumble = STUMBLE_TIME;
b->landing = CATCH_STEP_TIME;
b->stumble_push = vec3_add(b->stumble_push, vec3_scale(b->fall_pending, 1.6f));
}
void Body_trip(Body *b, int foot, Vec3 facing, float severity) {
if (b->pose != POSE_UP || b->stumble > 0.0f || b->fall_delay > 0.0f) return;
float side = foot == 0 ? -1.0f : 1.0f;
Vec3 right = rightOf(facing);
Vec3 lurch = vec3_add(vec3_scale(facing, 0.9f), vec3_scale(right, side * 0.5f));
b->stumble = STUMBLE_TIME;
b->landing = CATCH_STEP_TIME;
b->stumble_push = vec3_add(b->stumble_push, vec3_scale(lurch, 0.6f + 1.2f * severity));
/* Bad enough: down, over the foot that caught */
if (random01(b) < (severity - 0.5f) * 0.6f)
Body_fall(b, vec3_add(facing, vec3_scale(right, side * 0.7f)));
}
/* ---------- Smoking ---------- */
#define CIG_TIME 200.0f /* seconds it burns (a bit quicker with each drag) */
#define EXHALE_TIME 1.4f
bool Body_smoke(Body *b, bool rolled, int hand) {
if (b->smoke_phase != SMOKE_NONE || b->pose != POSE_UP) return false;
b->smoke_phase = rolled ? SMOKE_ROLL : SMOKE_LIGHT;
b->smoke_t = 0.0f;
b->cig_left = 1.0f;
b->cig_hand = hand;
b->drag = b->exhale = 0.0f;
b->drag_timer = 4.0f;
return true;
}
bool Body_isSmoking(const Body *b) {
return b->smoke_phase != SMOKE_NONE;
}
void Body_stopSmoking(Body *b) {
if (b->smoke_phase == SMOKE_NONE) return;
b->butt = b->smoke_phase == SMOKE_ON; /* (half rolled: just put away) */
b->smoke_phase = SMOKE_NONE;
b->drag = b->exhale = 0.0f;
}
bool Body_takeButt(Body *b) {
bool butt = b->butt;
b->butt = false;
return butt;
}
static void updateSmoking(Body *b, float speed, float dt) {
if (b->smoke_phase == SMOKE_NONE) return;
if (b->pose != POSE_UP) { Body_stopSmoking(b); return; } /* (down: out of the hand) */
b->smoke_t += dt;
b->exhale = fmaxf(0.0f, b->exhale - dt);
if (b->smoke_phase == SMOKE_ROLL) {
if (b->hand_weight[0] > 0.5f || b->hand_weight[1] > 0.5f || speed > 1.0f) { b->smoke_t = fminf(b->smoke_t, 0.5f); return; } /* (hands needed: rolling waits) */
if (b->smoke_t >= ROLL_CIG_TIME) { b->smoke_phase = SMOKE_LIGHT; b->smoke_t = 0.0f; }
return;
}
if (b->smoke_phase == SMOKE_LIGHT) {
if (b->smoke_t >= LIGHT_TIME) { b->smoke_phase = SMOKE_ON; b->smoke_t = 0.0f; b->exhale = EXHALE_TIME; }
return;
}
float hold = speed < 0.5f && b->cig_hand < 2 ? 1.0f : 0.0f; /* (standing about: up in front; walking: it swings along) */
b->cig_hold += clampf(hold - b->cig_hold, -2.5f * dt, 2.5f * dt);
/* In a hand that's needed for something: into the mouth; back to a free hand after a drag */
if (b->cig_hand < 2 && b->hand_weight[b->cig_hand] > 0.3f) b->cig_hand = 2;
b->cig_left -= dt / CIG_TIME * (b->drag > 0.0f ? 4.0f : 1.0f);
if (b->drag > 0.0f && (b->drag -= dt) <= 0.0f) {
b->drag = 0.0f;
b->exhale = EXHALE_TIME;
if (b->cig_hand == 2)
for (int h = 1; h >= 0; h--) if (b->hand_weight[h] < 0.05f) { b->cig_hand = h; break; }
}
if (b->drag <= 0.0f && (b->drag_timer -= dt) <= 0.0f) {
b->drag = DRAG_TIME;
b->drag_timer = 8.0f + 12.0f * random01(b) - 4.0f * b->shown.nerves; /* (nervous: more often) */
}
if (b->cig_left <= 0.0f) { b->butt = true; b->smoke_phase = SMOKE_NONE; b->drag = 0.0f; }
}
bool Body_takeLanding(Body *b, int *foot, Vec3 *at) {
if (!b->landed) return false;
*foot = b->landed - 1;
*at = b->landed_at;
b->landed = 0;
return true;
}
void Body_stagger(Body *b, Vec3 push) {
if (b->pose != POSE_UP) return;
b->stumble = STUMBLE_TIME;
b->landing = CATCH_STEP_TIME;
b->stumble_push = vec3_add(b->stumble_push, push);
}
void Body_lieDown(Body *b) {
b->asleep = true;
b->down_time = -1.0f;
if (b->pose != POSE_UP) return; /* already on the ground: just stay there */
b->pose = POSE_LYING_DOWN;
b->pose_time = 0.0f;
b->fall_dir = vec3_scale(b->last_facing, -1.0f); /* sits down, then lies back */
if (vec3_length(b->fall_dir) < 1e-3f) b->fall_dir = vec3(0.0f, 0.0f, -1.0f); /* (never faced anywhere yet) */
b->lie_offset = 0.0f;
b->face_down = false;
}
void Body_stretch(Body *b) {
b->fidget = FIDGET_STRETCH;
b->fidget_side = 0;
b->fidget_len = b->fidget_time = FIDGET_LENGTH[FIDGET_STRETCH] * 1.3f;
b->fidget_fade = 1.0f;
b->fidget_timer = b->fidget_len + 2.0f;
}
void Body_getUp(Body *b) {
b->asleep = false;
if (b->pose != POSE_UP) b->down_time = 0.0f; /* still going down: up again right after */
}
bool Body_isDown(const Body *b) {
return b->pose != POSE_UP;
}
void Body_burp(Body *b) {
if (b->burp <= 0.0f && b->pose == POSE_UP) b->burp = BURP_TIME;
}
void Body_vomit(Body *b) {
if (b->vomit <= 0.0f && b->pose == POSE_UP) b->vomit = VOMIT_TIME;
}
bool Body_takeVomit(Body *b) {
bool splash = b->vomit_splash;
b->vomit_splash = false;
return splash;
}
bool Body_isBusy(const Body *b) {
return b->pose != POSE_UP || b->vomit > 0.0f || b->pee > 0.0f;
}
void Body_pee(Body *b, float seconds) {
if (b->pee <= 0.0f && b->pose == POSE_UP) b->pee = b->pee_total = fmaxf(seconds, 1.0f);
}
bool Body_takePee(Body *b) {
bool done = b->pee_done;
b->pee_done = false;
return done;
}
Vec3 Body_takeShift(Body *b) {
Vec3 shift = b->shift;
b->shift = vec3_zero();
return shift;
}
/* Advances falling / lying / getting up. Returns false once standing again. */
static bool updateDown(Body *b, const BodyFrame *f, float s) {
if (b->pose == POSE_UP) return false;
b->pose_time += f->dt;
b->velocity = vec3_zero();
for (int i = 0; i < 2; i++) b->feet[i].stepping = false;
/* Feel the ground under the body, so it lies along the bumps */
Vec3 hips = vec3_add(f->position, vec3_scale(b->fall_dir, b->lie_offset * s));
for (int k = 0; k < LIE_SAMPLES; k++) {
Vec3 p = vec3_add(hips, vec3_scale(b->fall_dir, ((float)k - 4.0f) * LIE_STEP));
b->lie_ground[k] = f->ground(f->ground_ctx, p.x, p.z, f->position.y + 1.0f) - f->position.y;
}
switch (b->pose) {
case POSE_FALLING:
b->stumble = STUMBLE_TIME * fmaxf(0.0f, 1.0f - b->pose_time / b->fall_time); /* arms out */
if (b->pose_time >= b->fall_time) { b->pose = POSE_DOWN; b->pose_time = 0.0f; b->stumble = 0.0f; }
break;
case POSE_LYING_DOWN:
if (b->pose_time >= LIE_DOWN_TIME) { b->pose = POSE_DOWN; b->pose_time = 0.0f; }
break;
case POSE_DOWN:
if (b->roll_over != 0.0f) { /* over once, face down onto the back, moving sideways onto it like a log */
float k = fminf(1.0f, b->pose_time / ROLL_OVER_TIME);
b->rolling = true;
b->roll = b->roll_over * PI_F * smoothstepf(0.0f, 1.0f, k);
b->roll_side = b->roll * ROLL_RADIUS;
if (k >= 1.0f) { /* on the back now: lying where the roll ended */
b->shift = vec3_add(b->shift, vec3_scale(vec3_normalize(vec3_cross(b->fall_dir, UP)), b->roll_side * s));
b->face_down = false;
b->rolling = false;
b->roll = b->roll_side = b->roll_over = 0.0f;
b->down_time = 0.35f;
}
break;
}
if (b->rolling && b->pose_time < ROLL_TIME) { /* side to side, the whole body turning about its length */
b->roll = ROLL_REACH * sinf(b->pose_time * ROLL_PACE);
b->roll_side = b->roll * ROLL_RADIUS;
break;
}
if (b->rolling) b->roll = b->roll_side = 0.0f;
/* Wanting to move on gets you up sooner */
if (b->down_time >= 0.0f && (b->down_time -= f->dt * (1.0f + 2.5f * b->urge)) <= 0.0f) {
/* Get up where the hips lie */
b->pose = POSE_GETTING_UP;
b->pose_time = 0.0f;
b->shift = vec3_scale(b->fall_dir, b->lie_offset * s);
if (b->rolling) /* up where the roll ended */
b->shift = vec3_add(b->shift, vec3_scale(vec3_normalize(vec3_cross(b->fall_dir, UP)), b->roll_side * s));
b->lie_offset = 0.0f;
b->replant = true;
/* Drunk or clumsy: getting up may go wrong */
float u = b->unsteadiness, clumsy = 0.5f + 0.5f * b->quirk[3]; /* 0..1 */
float fail = clampf((u - 0.55f) * 0.6f, 0.0f, 0.4f);
float wobble = clampf(0.9f * u + 0.12f * clumsy, 0.0f, 0.9f);
float r = random01(b);
b->trouble = r < fail ? 2 : (r < fail + (1.0f - fail) * wobble ? 1 : 0);
}
break;
case POSE_GETTING_UP:
b->pose_time += f->dt * 0.8f * b->urge; /* quicker when moving on */
if (b->trouble == 2 && b->pose_time >= 0.55f * GET_UP_TIME) {
/* Halfway up, the legs give way: back down for a moment */
float side = random01(b) < 0.5f ? -1.0f : 1.0f;
Vec3 dir = vec3_add(vec3_scale(rightOf(b->last_facing), side), vec3_scale(b->last_facing, -0.4f));
b->pose = POSE_FALLING;
b->pose_time = 0.0f;
b->fall_dir = vec3_normalize(dir);
b->lie_offset = FALL_REACH * 0.6f;
b->face_down = false;
b->from_knees = true;
b->fall_style = FALL_PLANK;
b->fall_twist = 0.0f;
b->fall_time = FALL_TIME;
chooseLanding(b, 3.2f);
b->down_time = 0.8f + 0.8f * random01(b);
b->trouble = 0;
break;
}
if (b->pose_time >= GET_UP_TIME) {
b->pose = POSE_UP;
b->from_knees = false;
b->rolling = false;
b->roll = b->roll_side = 0.0f;
if (b->trouble == 1) {
/* Up, but not steady yet: a few catching steps */
float side = random01(b) < 0.5f ? -1.0f : 1.0f;
Vec3 lurch = vec3_add(vec3_scale(rightOf(b->last_facing), side), vec3_scale(b->last_facing, 0.5f));
Body_stagger(b, vec3_scale(vec3_normalize(lurch), 0.8f + 1.4f * b->unsteadiness));
}
b->trouble = 0;
}
break;
case POSE_UP:
break;
}
return true;
}
/* Room to stand here: nothing under the heel, and room for the toes (which the foot rolls onto). */
static bool spotFree(const BodyFrame *f, Vec3 spot, float s) {
Vec3 toes = vec3_add(spot, vec3_scale(f->facing, 0.13f * s));
return f->clutter(f->ground_ctx, spot.x, spot.z, 0.12f * s, f->position.y) <= 0.0f && f->clutter(f->ground_ctx, toes.x, toes.z, 0.08f * s, f->position.y) <= 0.0f;
}
/* The highest thing lying along the way from a to b, a foot's width either side (0 = nothing).
(The first bit is skipped: whatever the foot is just leaving.) */
static float clutterAlong(const BodyFrame *f, Vec3 a, Vec3 b, float r, float skip) {
float len = vec3_length(flat(vec3_sub(b, a))), most = 0.0f;
int n = (int)(len / r) + 1;
if (n > 8) n = 8;
for (int k = 0; k <= n; k++) {
float t = (float)k / (float)n;
if (t * len < skip) continue;
Vec3 p = vec3_lerp(a, b, t);
most = fmaxf(most, f->clutter(f->ground_ctx, p.x, p.z, r, f->position.y));
}
return most;
}
/* How deep a leg standing on this ankle would reach into what lies about (0:
clear): the sole (heel to toes), the shin up to the knee (bent forward, as
the pose bends it) and the thigh up to the hip, a limb's thickness round
them. lead: the body moved on this far; hip_y: the hips' height. */
static float legInto(const BodyFrame *f, Vec3 ankle, Vec3 dir, int side, Vec3 lead, float hip_y, float s) {
Vec3 hip = vec3_add(vec3_add(f->position, lead), vec3_add(vec3(0.0f, hip_y, 0.0f),
vec3_scale(rightOf(f->facing), (side == 0 ? -HIP_X : HIP_X) * s)));
/* the knee: two bones from ankle to hip, bending forward */
Vec3 line = vec3_sub(hip, ankle);
float d = fmaxf(vec3_length(line), 1e-4f), shin = SHIN * s, thigh = THIGH * s;
line = vec3_scale(line, 1.0f / d);
Vec3 knee;
if (d >= shin + thigh) {
knee = vec3_add(ankle, vec3_scale(line, shin));
} else {
float a = (shin * shin - thigh * thigh + d * d) / (2.0f * d);
float h = sqrtf(fmaxf(shin * shin - a * a, 0.0f));
Vec3 fwd = vec3_sub(f->facing, vec3_scale(line, vec3_dot(f->facing, line)));
fwd = vec3_length(fwd) > 1e-3f ? vec3_normalize(fwd) : UP;
knee = vec3_add(ankle, vec3_add(vec3_scale(line, a), vec3_scale(fwd, h)));
}
const float sole = ankle.y - ANKLE_Y * s;
float most = 0.0f;
for (int k = 0; k < 9; k++) {
Vec3 p;
float r, bottom;
if (k < 3) { /* heel, ankle, toes */
p = vec3_add(ankle, vec3_scale(dir, (k == 0 ? -0.05f : k == 1 ? 0.05f : FOOT_LENGTH) * s));
r = 0.05f * s;
bottom = sole;
} else { /* up the shin, and the lower thigh */
p = k < 7 ? vec3_lerp(ankle, knee, 0.25f * (float)(k - 2)) : vec3_lerp(knee, hip, 0.2f * (float)(k - 6));
r = LIMB_R * s;
bottom = p.y - LIMB_R * s;
}
float q = f->clutter(f->ground_ctx, p.x, p.z, r, bottom - 0.05f); /* (whatever reaches above it here) */
if (q > 0.0f) most = fmaxf(most, q - 0.05f);
}
return most;
}
/* Where a stepping foot aims, carried along as the body moves and turns; but
a new idea of where to stand (another stance, done standing about) only
glides over, never jumps (the foot would flick there and back). */
static Vec3 gliding(Foot *foot, Vec3 aim, const BodyFrame *f, float speed, float s, float dt) {
Vec3 right = rightOf(f->facing);
Vec3 rel = flat(vec3_sub(aim, f->position));
Vec3 local = vec3(vec3_dot(rel, right), 0.0f, vec3_dot(rel, f->facing));
Vec3 change = vec3_sub(local, foot->aim_local);
float most = (AIM_GLIDE + 1.5f * speed) * s * dt, len = vec3_length(change);
if (len > most) local = vec3_add(foot->aim_local, vec3_scale(change, most / len));
foot->aim_local = local;
Vec3 out = vec3_add(f->position, vec3_add(vec3_scale(right, local.x), vec3_scale(f->facing, local.z)));
out.y = aim.y;
return out;
}
/*
* Drunk: a step may go wrong. Uneven ground and slopes make it more likely.
* The foot lands off target, the body lurches and the next step is a quick
* catch. Very drunk, the catch may fail and down you go.
*/
static void maybeStumble(Body *b, const BodyFrame *f, Foot *foot, float speed, float s) {
float u = b->unsteadiness;
if (u < 0.05f || b->stumble > 0.0f) return;
Vec3 spot = foot->step_to;
Vec3 ahead = vec3_add(spot, vec3_scale(f->facing, 0.3f * s));
float ground_here = spot.y - ANKLE_Y * s;
float ground_ahead = f->ground(f->ground_ctx, ahead.x, ahead.z, spot.y + STEP_UP * s);
float rough = fabsf(spot.y - foot->ankle.y) / s; /* step up or down */
float slope = fabsf(ground_ahead - ground_here) / (0.3f * s);
/* Tipsy is mostly fine; really drunk is where it gets wobbly */
float chance = u * u * (0.03f + 1.2f * rough + 0.5f * slope) * (speed > 0.4f ? 1.0f : 0.25f);
if (random01(b) >= chance) return;
/* The foot that goes wrong takes the body its way */
float side = foot == &b->feet[0] ? -1.0f : 1.0f;
Vec3 right = rightOf(f->facing);
Vec3 miss = vec3_add(vec3_scale(right, side * (0.12f + 0.2f * u) * s),
vec3_scale(f->facing, (random01(b) - 0.4f) * 0.25f * s));
spot = vec3_add(spot, miss);
spot.y = groundUnder(f, spot.x, spot.z, s) + ANKLE_Y * s;
foot->step_to = spot;
b->stumble = STUMBLE_TIME;
b->landing = CATCH_STEP_TIME;
b->stumble_push = vec3_add(vec3_scale(right, side * (0.8f + 1.2f * u)),
vec3_scale(f->facing, 0.4f * u));
if (u > 0.6f && random01(b) < (u - 0.6f) * 0.6f) {
if (random01(b) < 0.4f) /* reeling backwards a few steps first */
fallLater(b, vec3_add(vec3_scale(f->facing, -1.0f), vec3_scale(right, side * 0.4f)), 0.5f + 0.6f * random01(b));
else
Body_fall(b, vec3_add(vec3_scale(right, side), vec3_scale(f->facing, 0.6f)));
}
}
/* Head glances, blinking, burps, idle weight shifts. */
static void updateExpression(Body *b, const BodyFrame *f, float speed) {
const float dt = f->dt;
b->shown.pride = ease(b->shown.pride, b->mood.pride, EASE_MOOD, dt);
b->shown.energy = ease(b->shown.energy, b->mood.energy, EASE_MOOD, dt);
b->shown.nerves = ease(b->shown.nerves, b->mood.nerves, EASE_MOOD, dt);
b->chin = ease(b->chin, b->chin_goal, EASE_POSE, dt);
b->disgust = fmaxf(0.0f, b->disgust - 0.5f * dt);
b->burp = fmaxf(0.0f, b->burp - dt);
if (b->pee > 0.0f && (b->pee -= dt * (1.0f + 0.6f * b->pee_power)) <= 0.0f) { /* pushing: done sooner */
b->pee = 0.0f;
b->pee_done = true;
}
if (b->pose != POSE_UP) b->pee = 0.0f;
if (b->vomit > 0.0f) {
float before = VOMIT_TIME - b->vomit;
b->vomit = fmaxf(0.0f, b->vomit - dt);
if (before < VOMIT_SPLASH && VOMIT_TIME - b->vomit >= VOMIT_SPLASH) b->vomit_splash = true;
if (b->pose != POSE_UP) b->vomit = 0.0f;
}
/* Turning: the head leads */
Vec3 last = vec3_length(b->last_facing) > 0.5f ? b->last_facing : f->facing;
float turned = atan2f(vec3_dot(f->facing, rightOf(last)), vec3_dot(f->facing, last));
b->turn_rate = ease(b->turn_rate, dt > 0.0f ? turned / dt : 0.0f, 8.0f, dt);
b->last_facing = f->facing;
/* Looking around now and then, more often when nervous */
if ((b->glance_timer -= dt) <= 0.0f) {
bool ahead = random01(b) < 0.45f;
float range = speed > 1.5f ? 0.3f : 0.75f;
b->glance_yaw = ahead ? 0.0f : (random01(b) * 2.0f - 1.0f) * range;
b->glance_pitch = ahead ? 0.0f : (random01(b) * 0.4f - 0.25f) * range;
b->glance_timer = (1.2f + 3.0f * random01(b)) * (1.0f - 0.6f * b->shown.nerves);
}
/* Blinking */
b->blink = fmaxf(0.0f, b->blink - dt);
if ((b->blink_timer -= dt) <= 0.0f) {
b->blink = BLINK_TIME;
b->blink_timer = (2.0f + 4.0f * random01(b)) * (1.0f - 0.5f * b->shown.nerves);
}
/* Standing around: weight moves from one leg to the other */
if ((b->weight_timer -= dt) <= 0.0f) {
b->weight_goal = random01(b) < 0.3f ? 0.0f : (random01(b) < 0.5f ? -1.0f : 1.0f);
b->weight_timer = (4.0f + 6.0f * random01(b)) * (1.0f - 0.6f * b->shown.nerves);
}
float idle = 1.0f - smoothstepf(0.1f, 0.5f, speed);
b->weight_shift = ease(b->weight_shift, b->weight_goal * idle, 1.2f, dt);
/* Now and then one hand does something: on the hip, scratching the head,
at the chin, in the pocket. Moving or busy hands end it. */
b->gesture_time = fmaxf(0.0f, b->gesture_time - dt);
bool talking = b->gesture >= GESTURE_WAVE; /* (said in passing: walking on, it goes on) */
if (speed > (talking ? 2.5f : 0.3f) || b->pose != POSE_UP || b->hand_active[b->gesture_side]) { /* fades out from where it is */
if ((b->gesture_fade -= dt / GESTURE_FADE) <= 0.0f) b->gesture_time = 0.0f;
}
if ((b->gesture_timer -= dt) <= 0.0f && b->gesture_time <= 0.0f) {
b->gesture_timer = (5.0f + 9.0f * random01(b)) * (1.0f - 0.5f * b->shown.nerves);
int side = random01(b) < 0.5f + 0.3f * b->quirk[0] ? 1 : 0;
if (speed < 0.15f && b->pose == POSE_UP && !b->hand_active[side]) {
/* which one: how they feel, and their own habits (each person has two they make most) */
const float nerves = b->shown.nerves, tired = fmaxf(-b->shown.energy, 0.0f), lively = fmaxf(b->shown.energy, 0.0f);
const float proud = proudness(b), shy = shyness(b);
float weight[GESTURE_COUNT] = {
[GESTURE_HIP] = 0.6f + 1.5f * proud,
[GESTURE_HEAD] = 0.6f + 0.8f * nerves,
[GESTURE_CHIN] = 0.6f + 0.6f * nerves,
[GESTURE_POCKET] = 0.8f + 0.6f * shy,
[GESTURE_NECK] = 0.3f + 1.2f * tired + 0.6f * shy,
[GESTURE_NOSE] = 0.3f + 1.2f * nerves,
[GESTURE_EAR] = 0.2f + 1.0f * nerves + 0.5f * shy,
[GESTURE_HAIR] = 0.3f + 1.0f * proud + 0.4f * lively,
[GESTURE_THUMB] = 0.3f + 1.0f * proud,
[GESTURE_CHEST] = 0.2f + 0.8f * shy,
[GESTURE_SHADE] = 0.2f + 0.4f * lively,
};
weight[b->habit[0]] *= 3.0f;
weight[b->habit[1]] *= 2.0f;
float total = 0.0f;
for (int k = 1; k < GESTURE_COUNT; k++) total += fmaxf(weight[k], 0.0f);
float r = random01(b) * total;
int g = GESTURE_POCKET;
for (int k = 1; k < GESTURE_COUNT; k++) {
r -= fmaxf(weight[k], 0.0f);
if (r <= 0.0f) { g = k; break; }
}
b->gesture = g;
b->gesture_side = side;
b->gesture_len = b->gesture_time = 2.5f + 3.0f * random01(b);
b->gesture_fade = 1.0f;
}
}
/* The whole body fidgets now and then: shakes a leg loose, rocks on the
toes, stretches, rolls the shoulders, folds the arms. Who you are
decides what (nervous: tapping, shaking; tired: stretching; proud:
arms folded). Walking, working or sitting ends it. */
bool at_ease = idle > 0.9f && b->pose == POSE_UP && b->sit < 0.05f && b->pee <= 0.0f && b->vomit <= 0.0f &&
!b->hand_active[0] && !b->hand_active[1] && b->crouch_goal < 0.1f && b->stumble <= 0.0f;
b->fidget_time = fmaxf(0.0f, b->fidget_time - dt);
if (!at_ease && (b->fidget_fade -= dt / FIDGET_FADE) <= 0.0f) b->fidget_time = 0.0f; /* (fades out from where it is) */
if ((b->fidget_timer -= dt) <= 0.0f && b->fidget_time <= 0.0f) {
/* (out of breath: soon after stopping, one after the other - catching it) */
const float puffed = smoothstepf(0.35f, 0.75f, b->exertion);
b->fidget_timer = (3.0f + 8.0f * random01(b)) * (1.0f - 0.45f * b->shown.nerves) * (1.0f - 0.75f * puffed);
if (at_ease && b->gesture_time <= 0.0f) {
const float nerves = b->shown.nerves, tired = fmaxf(-b->shown.energy, 0.0f), proud = proudness(b);
const float fit = b->fitness;
const float weight[FIDGET_COUNT] = {
[FIDGET_SHAKE_LEG] = 0.8f + 1.5f * nerves,
[FIDGET_ROCK] = 0.7f + 0.8f * nerves - 0.4f * tired,
[FIDGET_STRETCH] = 0.4f + 1.6f * tired + 1.2f * puffed * fit,
[FIDGET_SHOULDERS] = 0.6f + 0.3f * puffed,
[FIDGET_NECK] = 0.6f + 0.8f * tired,
[FIDGET_CROSS_ARMS] = 0.5f + 1.5f * proud - 0.3f * shyness(b),
[FIDGET_TAP_FOOT] = 0.3f + 2.0f * nerves,
[FIDGET_RUB_HANDS] = 0.2f + 1.2f * nerves,
[FIDGET_HEAD_BEHIND] = 0.2f + 0.6f * proud + 0.5f * fmaxf(b->shown.energy, 0.0f) - 0.3f * shyness(b),
[FIDGET_AKIMBO] = 0.3f + 1.4f * proud - 0.3f * shyness(b) + 2.0f * puffed,
/* catching breath, by who they are: the unfit bent over, the sporty hands on the head */
[FIDGET_KNEES] = 4.0f * puffed * (1.15f - fit) - 1.0f * proud,
[FIDGET_ON_HEAD] = 3.5f * puffed * (0.2f + fit),
};
float total = 0.0f;
for (int k = 1; k < FIDGET_COUNT; k++) total += fmaxf(weight[k], 0.0f);
float r = random01(b) * total;
int pick = FIDGET_SHAKE_LEG;
for (int k = 1; k < FIDGET_COUNT; k++) {
r -= fmaxf(weight[k], 0.0f);
if (r <= 0.0f) { pick = k; break; }
}
b->fidget = pick;
b->fidget_side = random01(b) < 0.5f ? 0 : 1;
b->fidget_len = b->fidget_time = FIDGET_LENGTH[pick] * (0.8f + 0.4f * random01(b));
b->fidget_fade = 1.0f;
}
}
/* Standing about, the feet settle into another stance now and then: one
a little ahead, wider apart, back together (each change is a step) */
if (idle < 0.5f) {
b->stance[0] = b->stance[1] = vec3_zero();
b->stance_timer = 4.0f + 6.0f * random01(b);
} else if (at_ease && (b->stance_timer -= dt) <= 0.0f) { /* (not while busy: hands on the knees, ...) */
b->stance_timer = (5.0f + 10.0f * random01(b)) * (1.0f - 0.4f * b->shown.nerves);
int side = random01(b) < 0.5f ? 0 : 1;
float r = random01(b);
Vec3 old = b->stance[side];
if (r < 0.35f) b->stance[side] = vec3(0.0f, 0.0f, STANCE_STEP * (random01(b) < 0.7f ? 1.0f : -0.8f)); /* one foot ahead (or back) */
else if (r < 0.55f) b->stance[side] = vec3((side ? 1.0f : -1.0f) * STANCE_STEP * 0.9f, 0.0f, 0.0f); /* wider */
else b->stance[side] = vec3_zero(); /* back together */
if (vec3_length(vec3_sub(old, b->stance[side])) < 0.05f) b->stance[side] = vec3(0.0f, 0.0f, STANCE_STEP);
b->stance[1 - side] = vec3_scale(b->stance[1 - side], 0.5f);
}
}
void Body_talkGesture(Body *b, int kind) {
int side = !b->hand_active[1] ? 1 : !b->hand_active[0] ? 0 : -1;
if (side < 0 || b->pose != POSE_UP) return;
b->gesture = kind == TALK_WAVE ? GESTURE_WAVE : kind == TALK_SHRUG ? GESTURE_SHRUG : GESTURE_POINT;
b->gesture_side = side;
b->gesture_len = b->gesture_time = kind == TALK_WAVE ? 1.8f : 1.4f;
b->gesture_fade = 1.0f;
b->gesture_timer = b->gesture_len + 4.0f;
}
void Body_think(Body *b, float seconds) {
int side = !b->hand_active[1] ? 1 : !b->hand_active[0] ? 0 : -1;
if (side < 0 || b->pose != POSE_UP || seconds < 0.5f) return;
b->gesture = random01(b) < 0.65f ? GESTURE_CHIN : GESTURE_HEAD;
b->gesture_side = side;
b->gesture_len = b->gesture_time = seconds;
b->gesture_fade = 1.0f;
b->gesture_timer = seconds + 3.0f;
}
/* Leaving the ground: which foot pushes off last, and (running) which one leads. */
static void takeOff(Body *b, const BodyFrame *f, float speed) {
b->jumping = true;
b->lead_foot = -1;
Vec3 fwd = speed > 0.6f ? vec3_normalize(flat(b->velocity)) : f->facing;
float ahead0 = vec3_dot(vec3_sub(b->feet[0].ankle, f->position), fwd);
float ahead1 = vec3_dot(vec3_sub(b->feet[1].ankle, f->position), fwd);
if (speed > 0.8f) {
b->push_foot = ahead0 < ahead1 ? 0 : 1; /* the rear foot pushes off ... */
b->lead_foot = 1 - b->push_foot; /* ... the front one goes for the landing */
b->push_time = PUSH_OFF_TIME * 1.2f;
} else {
/* Standing: nearly always one foot a little before the other */
float r = random01(b);
b->push_foot = r < 0.12f ? -1 : (r < 0.56f + 0.2f * b->quirk[0] ? 1 : 0);
b->push_time = PUSH_OFF_TIME * (0.6f + 0.6f * random01(b));
}
for (int i = 0; i < 2; i++) b->feet[i].stepping = false;
}
/* Feet off the ground: tucked under the body; running, the leading leg
reaches forward and the trailing one hangs back, heel up. */
static void inTheAir(Body *b, const BodyFrame *f, float s, float speed) {
const float dt = f->dt;
Vec3 fwd = speed > 0.6f ? vec3_normalize(flat(b->velocity)) : f->facing;
bool rising = b->vy > 0.0f;
/* Dropping from high up: knees pulled up to the belly, then (timed) the legs
reach down for the ground */
float tuck = 0.0f, reach = 0.0f;
if (b->vy < -2.0f && f->ground) {
float h = f->position.y - f->ground(f->ground_ctx, f->position.x, f->position.z, f->position.y + 0.1f);
float v = -b->vy, t = (-v + sqrtf(v * v + 2.0f * 9.81f * fmaxf(h, 0.0f))) / 9.81f; /* (till the feet are down) */
float high = smoothstepf(0.5f * s, 1.2f * s, h + 0.1f * v * v);
reach = high * (1.0f - smoothstepf(0.12f, 0.28f, t));
tuck = high * smoothstepf(2.0f, 4.0f, v) * (1.0f - reach);
}
for (int i = 0; i < 2; i++) {
Foot *foot = &b->feet[i];
foot->stepping = false;
if (i == b->push_foot && b->air_time < b->push_time && tuck <= 0.0f) continue; /* still pushing off */
float sign = i == 0 ? -1.0f : 1.0f;
float ahead, lift, pitch;
if (b->lead_foot >= 0) {
bool lead = i == b->lead_foot;
ahead = lead ? 0.34f : -0.30f;
lift = lead ? (rising ? 0.30f : 0.10f) : 0.26f;
pitch = lead ? 0.25f : -0.9f; /* toes up to land on the heel / pointing down behind */
} else {
ahead = 0.03f;
/* the foot that left first tucks up higher, the push-off leg stays longer */
lift = (rising ? 0.20f : 0.11f) + (b->push_foot < 0 ? 0.0f : (i == b->push_foot ? -0.04f : 0.04f));
pitch = -0.35f;
}
if (tuck + reach > 0.0f) { /* (knees up, then legs down, feet flat to land) */
ahead = ahead + (0.12f - ahead) * tuck + (0.05f - ahead) * reach;
lift = lift + (0.40f - lift) * tuck - lift * reach;
pitch = pitch + (-0.15f - pitch) * fmaxf(tuck, reach);
}
Vec3 hang = vec3_add(f->position, vec3_add(vec3_scale(rightOf(f->facing), sign * FOOT_X * s), vec3_scale(fwd, ahead * s)));
hang.y = f->position.y + lift * s;
/* on a spring, from the way it was moving when it left the ground */
float omega = reach > 0.3f ? 22.0f : i == b->push_foot ? 10.0f : 14.0f; /* (reaching for the ground: quick) */
if (!b->airborne) foot->air_v = foot->vel;
Vec3 was = vec3_sub(foot->toe, foot->ankle); /* (how the foot pointed, before it moves on) */
Vec3 carried = vec3(b->velocity.x, b->vy, b->velocity.z); /* (it goes along with the body) */
springStep3(&foot->ankle, &foot->air_v, hang, carried, omega, dt);
foot->dir = f->facing;
Vec3 along = vec3_add(vec3_scale(f->facing, cosf(pitch)), vec3_scale(UP, sinf(pitch))); /* (toes the way the body faces) */
if (vec3_length(was) > 1e-4f) along = vec3_normalize(vec3_lerp(vec3_normalize(was), along, fminf(1.0f, 10.0f * dt)));
foot->toe = vec3_add(foot->ankle, vec3_scale(along, FOOT_LENGTH * s));
if (vec3_length(flat(along)) > 0.05f) foot->dir = vec3_normalize(flat(along)); /* (lands pointing that way) */
}
b->airborne = true;
}
/* On something narrow (a log): the arms go out and the body teeters from side
to side - a wobble that grows with clumsiness, drink and hurry and that the
sporty keep small. Past the edge of it: a step off sideways. */
#define TEETER_OMEGA 3.2f /* rad/s: how quickly a wobble swings back */
#define TEETER_DAMP 0.35f /* (some is caught, the rest swings on) */
#define TEETER_KICK 40.0f /* how hard the wobbles come */
static void updateBalance(Body *b, const BodyFrame *f, float speed, float dt) {
b->balance += (b->balance_goal - b->balance) * fminf(1.0f, (b->balance_goal > b->balance ? 4.0f : 2.0f) * dt);
b->dance += (b->dance_goal - b->dance) * fminf(1.0f, 2.0f * dt);
if (b->dance < 0.002f) b->dance = 0.0f;
if (b->balance < 0.02f || b->airborne || b->on_ladder || dt <= 0.0f) {
b->teeter *= fmaxf(0.0f, 1.0f - 3.0f * dt);
b->teeter_v = 0.0f;
return;
}
float shaky = (0.25f + 0.6f * Body_clumsiness(b) + 1.6f * b->unsteadiness + 0.35f * fminf(speed, 2.0f) + 0.4f * b->shown.nerves)
* (1.25f - 0.75f * b->fitness) * b->balance;
float kick = (random01(b) * 2.0f - 1.0f) * TEETER_KICK * shaky;
b->teeter_v += (kick - TEETER_OMEGA * TEETER_OMEGA * b->teeter - 2.0f * TEETER_DAMP * TEETER_OMEGA * b->teeter_v) * dt;
b->teeter += b->teeter_v * dt;
if (fabsf(b->teeter) > 1.0f && b->stumble <= 0.0f) { /* too far over: a step off, that side */
float side = b->teeter > 0.0f ? 1.0f : -1.0f;
Body_stagger(b, vec3_add(vec3_scale(rightOf(f->facing), side * 1.3f), vec3_scale(f->facing, 0.2f)));
b->teeter = b->teeter_v = 0.0f;
b->stepped_off = true;
}
}
void Body_update(Body *b, const BodyFrame *f) {
const float s = f->height / REF_HEIGHT;
const float dt = f->dt;
b->clock += dt;
b->clutter = f->clutter; /* (the pose keeps the legs clear of it) */
b->clutter_ctx = f->ground_ctx;
b->ground = f->ground;
b->push_out = f->push_out;
b->at = f->position;
b->at_scale = s;
for (int i = 0; i < 2; i++) { /* how the feet moved last frame */
Foot *foot = &b->feet[i];
foot->vel = dt > 0.0f ? vec3_scale(vec3_sub(foot->ankle, foot->prev), 1.0f / dt) : vec3_zero();
if (vec3_length(foot->vel) > 15.0f) foot->vel = vec3_zero(); /* (put somewhere else) */
foot->prev = foot->ankle;
}
/* Velocity from actual motion, whatever caused it (walking, pushing, being pushed) */
if (!b->ready) {
b->ready = true;
/* Everybody gets their own dice, and their own little lopsided ways */
b->rng = 0x9E3779B9u ^ (unsigned)(int)(f->position.x * 7919.0f) ^ ((unsigned)(int)(f->position.z * 104729.0f) << 7);
if (b->rng == 0) b->rng = 1;
for (int k = 0; k < 4; k++) b->quirk[k] = random01(b) * 2.0f - 1.0f;
for (int k = 0; k < 4; k++) { /* (from the quirks, not the dice: everything else stays as it was) */
float h = sinf(b->quirk[k] * 127.1f + b->quirk[(k + 1) % 4] * 311.7f + (float)k * 74.7f) * 43758.5453f;
b->style[k] = (h - floorf(h)) * 2.0f - 1.0f;
}
{ float h = sinf(b->quirk[0] * 91.3f + b->quirk[2] * 47.1f + 13.7f) * 43758.5453f;
b->stride = (h - floorf(h)) * 2.0f - 1.0f; } /* (long strides, or short quick ones: the same speed) */
b->habit[0] = (unsigned char)(1 + (int)(random01(b) * (float)(GESTURE_COUNT - 1)) % (GESTURE_COUNT - 1));
b->habit[1] = (unsigned char)(1 + ((int)b->habit[0] + (int)(random01(b) * (float)(GESTURE_COUNT - 2))) % (GESTURE_COUNT - 1));
b->gesture_timer = 3.0f + 6.0f * random01(b);
b->last_position = f->position;
b->last_facing = f->facing;
b->velocity = vec3_zero();
b->step_length = stepLength(0.0f, s);
b->pelvis_y = PELVIS_Y;
b->last_y = f->position.y;
b->push_foot = b->lead_foot = -1;
plantAll(b, f, s);
}
if (b->replant) {
b->replant = false;
b->last_position = f->position;
plantAll(b, f, s);
}
Vec3 moved = flat(vec3_sub(f->position, b->last_position));
b->last_position = f->position;
Vec3 v = dt > 0.0f ? vec3_scale(moved, 1.0f / dt) : vec3_zero();
if (vec3_length(v) > TELEPORT_SPEED) {
b->velocity = vec3_zero();
plantAll(b, f, s);
}
b->velocity = vec3_lerp(b->velocity, v, fminf(1.0f, EASE_VELOCITY * dt));
/* The body itself carries some weight: when the character jitters (pushed
about, wedged against something) the hips don't shake with it */
if (!b->root_ok || vec3_length(v) > TELEPORT_SPEED) {
b->root_vis = flat(f->position);
b->root_vel = flat(b->velocity);
b->root_ok = true;
} else if (dt > 0.0f) {
springStep3(&b->root_vis, &b->root_vel, flat(f->position), flat(b->velocity), ROOT_OMEGA, dt);
Vec3 off = vec3_sub(b->root_vis, flat(f->position));
float far = vec3_length(off), most = ROOT_LAG * s;
if (far > most) b->root_vis = vec3_add(flat(f->position), vec3_scale(off, most / far));
}
float speed = vec3_length(b->velocity);
updateExpression(b, f, speed);
updateSmoking(b, speed, dt);
if (updateDown(b, f, s)) return;
if (b->shoulder_roll > 0.0f) { /* over the shoulder: a set move, the feet go where the body is */
b->shoulder_roll += dt / SHOULDER_ROLL_TIME;
Vec3 r = rightOf(f->facing);
for (int i = 0; i < 2; i++) { /* (the feet come along with the body: under it when up again) */
Foot *foot = &b->feet[i];
foot->stepping = false;
foot->ankle = vec3_add(f->position, vec3_add(vec3_scale(r, (i == 0 ? -0.1f : 0.1f) * s), vec3(0.0f, 0.075f * s, 0.0f)));
foot->dir = f->facing;
foot->toe = vec3_add(foot->ankle, vec3_add(vec3_scale(f->facing, 0.14f * s), vec3(0.0f, -0.05f * s, 0.0f)));
}
if (b->shoulder_roll < 1.0f) return;
b->shoulder_roll = 0.0f;
b->replant = true; /* (up again: the feet under you) */
}
updateBalance(b, f, speed, dt);
/* Side steps and backward steps are shorter than steps forward; shy people take small steps */
float sideways = speed > 0.05f ? fabsf(vec3_dot(b->velocity, rightOf(f->facing))) / speed : 0.0f;
float backward = speed > 0.05f ? fmaxf(0.0f, -vec3_dot(b->velocity, f->facing) / speed) : 0.0f;
b->step_length = stepLength(speed, s) * (1.0f - 0.45f * sideways) * (1.0f - 0.35f * backward)
* (1.0f - 0.12f * shyness(b))
* (1.0f + 0.14f * b->stride) /* their own: long and slow, or short and quick */
* (1.0f + 0.3f * (b->fitness - 0.5f) * runAmount(speed)); /* (sprinting, the sporty bound along) */
/* Something to step over coming up: the sporty fit their steps to it, so a
foot comes down at a good take-off spot just before it, no stutter */
if (f->clutter && speed > 0.6f && b->fitness > PLAN_FIT && backward < 0.3f) {
Vec3 dir = vec3_scale(flat(b->velocity), 1.0f / speed);
float edge = -1.0f;
for (float d = 0.3f * s; d < PLAN_AHEAD * s && edge < 0.0f; d += 0.1f * s) {
Vec3 p = vec3_add(f->position, vec3_scale(dir, d));
if (f->clutter(f->ground_ctx, p.x, p.z, 0.1f * s, f->position.y) > 0.04f * s) edge = d;
}
if (edge > 0.0f) {
float take_off = (PLAN_TAKE_OFF + 0.08f * speed) * s;
float room = edge - take_off - 0.5f * b->step_length; /* (the next foot lands half a step ahead) */
if (room > 0.0f) {
float n = fmaxf(1.0f, roundf(room / b->step_length));
float fit = clampf(room / n, 0.75f * b->step_length, 1.25f * b->step_length);
b->step_length = lerpf(b->step_length, fit, smoothstepf(PLAN_FIT, PLAN_FIT + 0.4f, b->fitness));
}
}
}
/* Uphill: shorter steps; downhill: longer ones */
float grade = 0.0f;
if (speed > 0.2f) {
Vec3 ahead = vec3_add(f->position, vec3_scale(flat(b->velocity), 0.5f * s / speed));
float from_y = f->position.y + STEP_UP * s;
grade = (f->ground(f->ground_ctx, ahead.x, ahead.z, from_y)
- f->ground(f->ground_ctx, f->position.x, f->position.z, from_y)) / (0.5f * s);
}
b->grade = ease(b->grade, clampf(grade, -1.0f, 1.0f), 5.0f, dt);
b->step_length *= b->grade > 0.0f ? 1.0f - clampf(b->grade * 1.5f, 0.0f, 0.45f)
: 1.0f + clampf(-b->grade * 0.9f, 0.0f, 0.3f);
/* Action goals */
if (b->squat_pee && b->pee > 0.0f) b->crouch_goal = fmaxf(b->crouch_goal, 1.0f); /* squatting to pee */
if (b->land_squat > 0.0f) { /* landed from high up: into the knees, up again */
b->crouch_goal = fmaxf(b->crouch_goal, b->land_squat);
b->land_squat = fmaxf(0.0f, b->land_squat - 1.6f * dt);
}
springCritical(&b->crouch, &b->crouch_v, b->crouch_goal, POSE_OMEGA, dt); /* bending down speeds up, slows down */
b->crouch = clampf(b->crouch, 0.0f, 1.2f);
b->sit = ease(b->sit, b->pose == POSE_UP ? b->sit_goal : 0.0f, 2.5f, dt);
springCritical(&b->lean, &b->lean_v, b->lean_goal, POSE_OMEGA, dt);
for (int h = 0; h < 2; h++)
{ /* eases in and out (a spring): a hand never jerks off towards something */
springCritical(&b->hand_weight[h], &b->hand_wvel[h], b->hand_active[h] ? 1.0f : 0.0f, HAND_WEIGHT_OMEGA, dt);
b->hand_weight[h] = clampf(b->hand_weight[h], 0.0f, 1.0f);
}
/* Jumping: off one foot (rarely both at once); running, the rear foot
pushes off and the front one reaches ahead to land first */
float rise = dt > 0.0f ? (f->position.y - b->last_y) / dt : 0.0f;
if (f->grounded && fabsf(f->position.y - b->last_y) > 0.08f * s && fabsf(f->position.y - b->last_y) < 0.7f * s) {
/* hopped up (or dropped) onto something in one go: the hips stay where they were and follow */
b->pelvis_y -= (f->position.y - b->last_y) / s;
rise = 0.0f;
}
b->vy = ease(b->vy, rise, 20.0f, dt);
b->last_y = f->position.y;
if (!f->grounded && b->air_time <= 0.0f && rise > TAKE_OFF_SPEED && !b->jumping) takeOff(b, f, speed);
b->air_time = f->grounded ? 0.0f : b->air_time + dt;
if (b->air_time > AIR_GRACE || (b->jumping && !f->grounded)) {
inTheAir(b, f, s, speed);
return;
}
if (b->airborne && f->grounded) {
/* Landing: both feet reach for the ground quickly, one a moment before the other */
b->airborne = false;
b->landing = LANDING_STEP_TIME;
for (int i = 0; i < 2; i++) {
b->feet[i].stepping = true;
b->feet[i].step = 0.0f;
b->feet[i].step_from = b->feet[i].ankle;
b->feet[i].step_v0 = b->feet[i].vel; /* (on from how it was moving) */
if (vec3_length(b->feet[i].step_v0) > 4.0f * s) b->feet[i].step_v0 = vec3_scale(vec3_normalize(b->feet[i].step_v0), 4.0f * s);
b->feet[i].spot_was = b->feet[i].step_to = ankleAt(footSpot(b, f, i, s), s);
b->feet[i].pitch0 = asinf(clampf((b->feet[i].toe.y - b->feet[i].ankle.y) / fmaxf(FOOT_LENGTH * s, 1e-4f), -1.0f, 1.0f));
b->feet[i].delay = 0.0f;
b->feet[i].clear_shift = vec3_zero();
b->feet[i].over = 0.0f;
}
if (b->lead_foot >= 0) b->feet[1 - b->lead_foot].delay = TRAIL_LAND_DELAY;
else if (b->push_foot >= 0) b->feet[b->push_foot].delay = 0.04f;
}
if (f->grounded) {
b->jumping = false;
b->lead_foot = b->push_foot = -1;
}
b->landing = fmaxf(0.0f, b->landing - dt);
b->stumble = fmaxf(0.0f, b->stumble - dt);
if (b->fall_delay > 0.0f) { /* reeling: keeps going, then down */
b->stumble = fmaxf(b->stumble, 0.3f);
if ((b->fall_delay -= dt) <= 0.0f) Body_fall(b, b->fall_pending);
else if (b->fall_delay < 0.3f && b->fall_delay + dt >= 0.3f)
b->stumble_push = vec3_add(b->stumble_push, vec3_scale(b->fall_pending, 0.9f));
}
/* Swing time: walking ~ one step, running longer (flight phase) */
/* (setting off, the body is already going: quick first steps keep up with it) */
const float pace = fmaxf(speed, fminf(vec3_length(v), 2.0f * speed + 1.0f));
const float swing = b->step_length / fmaxf(pace, 0.01f) * (1.0f + 0.4f * runAmount(speed));
const float step_time = b->landing > 0.0f ? LANDING_STEP_TIME
: clampf(swing, MIN_STEP_TIME, MAX_STEP_TIME);
const float hip_y = ((b->pelvis_y > 0.3f ? b->pelvis_y : PELVIS_Y) - HIP_DROP) * s; /* (the legs hang from there) */
/* Feet that are stepping keep aiming at their (moving) spot */
bool any_stepping = false;
for (int i = 0; i < 2; i++) {
Foot *foot = &b->feet[i];
if (!foot->stepping) continue;
if (foot->delay > 0.0f) { /* still in the air a moment: stays where it is */
foot->delay -= dt;
any_stepping = true;
continue;
}
/* the spot moves with the body; the way round what lies there was chosen when the step began */
Vec3 spot = uncrossed(footSpot(b, f, i, s), f, i, b->feet[1 - i].ankle, s);
if (vec3_length(foot->clear_shift) > 1e-4f) {
spot = vec3_add(spot, foot->clear_shift);
spot.y = groundUnder(f, spot.x, spot.z, s);
}
/* The aim moves with the body, less and less towards the end: the
foot slows down onto the ground instead of being dragged along */
Vec3 aim = gliding(foot, ankleAt(spot, s), f, vec3_length(flat(b->velocity)), s, dt);
float follow = 1.0f - smoothstepf(0.55f, 0.95f, foot->step);
Vec3 next = vec3_add(foot->step_to, vec3_scale(flat(vec3_sub(aim, foot->spot_was)), follow));
/* (but not onto something lying there: then it lands where it was going) */
bool lunge = foot->over > 0.0f && vec3_length(flat(vec3_sub(next, b->feet[1 - i].ankle))) > overReach(b, s) &&
vec3_length(flat(vec3_sub(next, b->feet[1 - i].ankle))) > vec3_length(flat(vec3_sub(foot->step_to, b->feet[1 - i].ankle)));
if (!lunge && (!f->clutter || spotFree(f, next, s) || !spotFree(f, foot->step_to, s)))
foot->step_to = next;
else if (foot->step < OVER_LEAP_LATE && vec3_length(flat(vec3_sub(next, foot->step_to))) > OVER_LEFT * s)
foot->step_to = leapPast(f, foot, next, b->feet[1 - i].stepping ? b->feet[1 - i].step_to : b->feet[1 - i].ankle, overReach(b, s), s);
foot->spot_was = aim;
/* (over an edge the ground below the aim jumps: the foot goes there smoothly) */
float to_y = groundUnder(f, foot->step_to.x, foot->step_to.z, s) + ANKLE_Y * s;
foot->step_to.y = ease(foot->step_to.y, to_y, foot->step > 0.85f ? 40.0f : 18.0f, dt);
/* (over something: slower; running: a leap, and a step held back behind the body comes down quick) */
float late = speed > LEAP_SPEED ? fmaxf(0.0f, -vec3_dot(flat(vec3_sub(foot->step_to, f->position)), f->facing)) / s : 0.0f;
const float step_was = foot->step;
foot->step = fminf(1.0f, foot->step + dt * (1.0f + LATE_QUICK * late) / (step_time * (1.0f + OVER_SLOW * (1.0f - runAmount(speed)) * foot->over / s)));
/* The foot rises early in the swing and reaches forward late. Running:
the heel kicks up behind first, then the knee drives forward. */
float run = runAmount(speed);
float t = smoothstepf(0.0f, 1.0f, powf(foot->step, 1.0f + 0.8f * run)); /* (running: stays behind a while, then comes through) */
Vec3 pos = vec3_lerp(foot->step_from, foot->step_to, t);
{ /* a foot already on the move (coming down from a jump) carries on smoothly */
float u = foot->step;
pos = vec3_add(pos, vec3_scale(foot->step_v0, u * (1.0f - u) * (1.0f - u) * step_time));
}
if (f->clutter && foot->step < 0.9f) {
/* something turned up in the way (the aim moved on with the body): lift higher, quickly but smoothly */
float need = clutterAlong(f, pos, foot->step_to, 0.09f * s, 0.0f);
float want = need > 0.0f ? need + OVER_CLEAR * s : 0.0f;
if (want > foot->over) foot->over = fminf(want, foot->over + OVER_RISE * s * dt);
}
pos.y += fmaxf((STEP_LIFT + 0.3f * run) * s * stepArc(foot->step, 0.75f - 0.35f * run) /* (running: the heel up behind, early) */
* clampf(0.4f + speed * 0.3f, 0.4f, 1.2f) * (1.0f + 0.25f * b->shown.energy),
foot->over * stepArc(foot->step, 1.0f)); /* a big step over something (no higher than that) */
foot->swing_run = fmaxf(run, foot->swing_run - SWING_RUN_EASE * dt); /* (slowing down mid-swing: the curve goes over gently) */
if (foot->swing_run > 0.0f && foot->over <= 0.0f) {
/* Running: off the toes the heel folds up under the seat, the knee drives through in
front, the foot reaches out and comes down (a curve that goes with the body) */
Vec3 side = vec3_scale(rightOf(f->facing), (i == 0 ? -1.0f : 1.0f) * 0.07f * s); /* (running: the feet nearly on one line) */
Vec3 tuck = vec3_add(vec3_add(f->position, side), vec3_add(vec3_scale(f->facing, RUN_TUCK_BACK * s), vec3(0.0f, RUN_TUCK_UP * s, 0.0f)));
Vec3 drive = vec3_add(vec3_add(f->position, side), vec3_add(vec3_scale(f->facing, RUN_DRIVE_AHEAD * s), vec3(0.0f, RUN_DRIVE_UP * s, 0.0f)));
float u = foot->step, m = 1.0f - u;
Vec3 curve = vec3_add(vec3_add(vec3_scale(foot->step_from, m * m * m), vec3_scale(tuck, 3.0f * m * m * u)),
vec3_add(vec3_scale(drive, 3.0f * m * u * u), vec3_scale(foot->step_to, u * u * u)));
pos = vec3_lerp(pos, curve, foot->swing_run);
}
if (foot->over > 0.0f) {
/* Over something the leg swings round a little, out to the side and back in to land,
while the body's momentum carries it on: the shin clears without climbing so high */
float out = OVER_SWING_OUT * s * smoothstepf(0.0f, OVER_SWING_FULL * s, foot->over) * stepArc(foot->step, 1.0f);
pos = vec3_add(pos, vec3_scale(rightOf(f->facing), i == 0 ? -out : out));
}
if (f->clutter) {
/* never into what lies there: lifted over it, and over what it's about to reach (smoothly) */
Vec3 ahead = vec3_add(pos, vec3_scale(flat(vec3_sub(foot->step_to, foot->step_from)), 0.25f));
float floor = fmaxf(groundUnder(f, pos.x, pos.z, s), groundUnder(f, ahead.x, ahead.z, s)) + (ANKLE_Y + 0.02f) * s;
float want = foot->step < 0.97f ? fmaxf(0.0f, floor - pos.y) : 0.0f;
/* the whole leg too: the shin and the knee come over it, not through it */
/* (a foot far off is just catching up; standing about, feet just shift, they don't climb anything) */
bool near = vec3_length(flat(vec3_sub(pos, f->position))) < LEG_NEAR * s && speed > LEG_MOVING;
float into = foot->step < 0.97f && near ? legInto(f, vec3_add(pos, vec3(0.0f, foot->lift, 0.0f)), foot->dir, i, vec3_zero(), hip_y, s) : 0.0f;
want = fminf(fmaxf(want, foot->lift + into), LIFT_MOST * s);
float up = (into > 0.0f ? LEG_RISE : LIFT_RISE) * s * dt;
foot->lift = want > foot->lift ? fminf(want, foot->lift + up) : fmaxf(want, foot->lift - LIFT_RISE * s * dt);
/* down onto the ground with the step: never faster than a foot comes down, and the step
isn't over before the foot is down */
float cap = LIFT_MOST * s * (1.0f - smoothstepf(LEG_HOLD - 0.05f, 0.97f, foot->step));
if (foot->lift > cap) foot->lift = fmaxf(cap, foot->lift - LIFT_DROP * s * dt);
if (foot->lift > 0.03f * s && foot->step > 0.95f) foot->step = 0.95f;
pos.y += foot->lift;
/* not clear yet: the foot goes up first and over after (unless it can't get that high) */
/* (running there's no waiting: the knee drives through, high, and it may scrape) */
if (near && speed < LEAP_SPEED && foot->step < LEG_HOLD && foot->lift < LIFT_MOST * s - 0.01f &&
legInto(f, pos, foot->dir, i, vec3_zero(), hip_y, s) > LEG_TOUCH * s) {
pos.x = foot->ankle.x;
pos.z = foot->ankle.z;
foot->step = step_was;
}
}
foot->ankle = pos;
foot->dir = vec3_normalize(vec3_lerp(foot->dir, footDir(b, f->facing, i), fminf(1.0f, dt * 12.0f)));
float strike = heelStrike(run) * (b->landing > 0.0f ? 0.3f : 1.0f);
foot->toe = toeSwinging(f, pos, foot->dir, foot->step, foot->pitch0, strike, s);
if (foot->step >= 1.0f) {
foot->stepping = false;
foot->ankle = foot->step_to;
b->landed = i + 1;
b->landed_at = foot->ankle;
foot->toe = toePlanted(f, foot->ankle, foot->dir, s);
foot->slap = strike; /* on the heel: the toes come down next */
foot->slap_v = 0.0f;
b->last_step = i;
} else {
any_stepping = true;
}
}
crossOver(b, f, s);
/* Start a new step with the foot that drifted furthest from its spot (or
that the body turned away from). Walking: one foot at a time.
Running: the next foot may leave before the other lands. */
float overlap = 0.45f * runAmount(speed);
float limit = (speed > 0.2f ? STEP_THRESHOLD : IDLE_THRESHOLD) * s;
int best = -1;
float best_need = 0.0f;
Vec3 best_spot = vec3_zero();
for (int i = 0; i < 2; i++) b->feet[i].planted = b->feet[i].stepping ? 0.0f : b->feet[i].planted + dt;
for (int i = 0; i < 2; i++) {
Foot *foot = &b->feet[i], *other = &b->feet[1 - i];
if (foot->stepping) continue;
/* (running, the foot that came down stays down a moment: the leg bends under the body and pushes off) */
if (foot->planted < STANCE_RUN * runAmount(speed) && foot->into <= LEG_TOUCH * s) continue;
/* (running, a foot left far behind while the other is still on its way over something: off it goes, a leap) */
float behind = -vec3_dot(flat(vec3_sub(foot->ankle, f->position)), f->facing);
bool leap = speed > LEAP_SPEED && ((other->over > 0.0f && behind > LEAP_BEHIND * s) || foot->into > LEG_TOUCH * s);
if (other->stepping && other->step < 1.0f - overlap && !leap) continue;
if (any_stepping && !other->stepping) continue;
/* half over something: the foot already over there stays; the other one comes after it */
if (b->crossing && !other->stepping && vec3_dot(flat(vec3_sub(foot->ankle, b->cross_to)), b->cross_dir) > -0.12f * s &&
vec3_dot(flat(vec3_sub(other->ankle, b->cross_to)), b->cross_dir) < -0.25f * s) continue;
Vec3 spot = ankleAt(uncrossed(footSpot(b, f, i, s), f, i, other->ankle, s), s);
float drift = vec3_length(flat(vec3_sub(spot, foot->ankle)));
float aligned = vec3_dot(foot->dir, footDir(b, f->facing, i));
bool turned = aligned < TURN_THRESHOLD || (speed < 0.2f && aligned < 0.95f); /* standing: settle the feet */
bool climb = fabsf(spot.y - foot->ankle.y) > 0.15f * s;
float need = drift / limit + (turned ? 1.0f : 0.0f) + (climb ? 1.0f : 0.0f);
if (i != b->last_step) need += 0.05f; /* tie: alternate feet */
if (foot->into > LEG_TOUCH * s)
need += 1.5f; /* the body moved on over something: this leg would go through it, up it comes */
if (need > 1.0f && need > best_need) {
best = i;
best_need = need;
best_spot = spot;
}
}
if (best >= 0) {
Foot *foot = &b->feet[best];
foot->stepping = true;
foot->step = 0.0f;
foot->swing_run = runAmount(speed);
/* off from where it was drawn (heel up, rolled over the toes): no jump */
bool shown = b->shown_valid && vec3_length(vec3_sub(b->ankle_shown[best], foot->ankle)) < 0.25f * s;
foot->step_from = shown ? b->ankle_shown[best] : foot->ankle;
if (shown) {
Vec3 d = vec3_sub(b->toe_shown[best], b->ankle_shown[best]);
foot->pitch0 = asinf(clampf(d.y / fmaxf(vec3_length(d), 1e-4f), -1.0f, 1.0f));
foot->ankle = b->ankle_shown[best]; /* (drawn as it was, this frame too) */
foot->toe = b->toe_shown[best];
} else {
foot->pitch0 = FLAT_PITCH - 0.75f * foot->roll;
}
foot->roll = 0.0f;
foot->slap = foot->slap_v = 0.0f;
foot->step_v0 = vec3_zero();
/* (how much they bother with what lies about: the sporty and the careful step round it) */
float careless = clampf(0.08f + 0.6f * b->unsteadiness + 0.3f * (Body_clumsiness(b) - 0.5f)
+ 0.35f * fmaxf(-b->shown.energy, 0.0f) + 0.3f * smoothstepf(2.0f, 5.0f, speed)
- 0.3f * (b->fitness - 0.5f), 0.0f, 0.85f);
foot->step_to = clearSpot(f, foot, best_spot, b->feet[1 - best].ankle, overReach(b, s), random01(b) < careless, s);
maybeStumble(b, f, foot, speed, s); /* (drunk: may land off) */
foot->clear_shift = vec3(foot->step_to.x - best_spot.x, 0.0f, foot->step_to.z - best_spot.z);
foot->spot_was = vec3_add(best_spot, foot->clear_shift);
Vec3 rel = flat(vec3_sub(best_spot, f->position));
foot->aim_local = vec3(vec3_dot(rel, rightOf(f->facing)), 0.0f, vec3_dot(rel, f->facing));
}
/* Planted feet behind the body roll onto their toes, smoothly; a foot
that just landed on its heel puts the toes down */
for (int i = 0; i < 2; i++) {
Foot *foot = &b->feet[i];
float into_was = foot->into;
foot->into = 0.0f;
if (foot->stepping) continue;
float back = -vec3_dot(flat(vec3_sub(foot->ankle, f->position)), f->facing) / s;
if (f->clutter && vec3_length(flat(vec3_sub(foot->ankle, f->position))) < LEG_NEAR * s) {
/* now, or where the body is going (and it doesn't flicker off at once) */
foot->into = fmaxf(legInto(f, foot->ankle, foot->dir, i, vec3_zero(), hip_y, s),
legInto(f, foot->ankle, foot->dir, i, vec3_scale(flat(b->velocity), LEG_AHEAD), hip_y, s));
foot->into = fmaxf(foot->into, into_was - LEG_FORGET * dt);
}
/* (the leg over something behind: up on the toes, the knee goes up with the heel) */
foot->roll = ease(foot->roll, fmaxf(smoothstepf(0.03f, 0.2f, back), foot->into > 0.0f ? 1.0f : 0.0f), 10.0f, dt);
springCritical(&foot->slap, &foot->slap_v, 0.0f, 30.0f, dt);
}
}
/* Half way over something: from when a foot is well on its way over it until
the body is over there too (above where that foot came down). */
static void crossOver(Body *b, const BodyFrame *f, float s) {
for (int i = 0; i < 2 && !b->crossing; i++) { /* (where the first foot goes over: the body goes that far, no further) */
const Foot *ft = &b->feet[i];
if (!ft->stepping || ft->over < CROSS_OVER * s || ft->step < CROSS_FROM) continue;
Vec3 way = flat(vec3_sub(ft->step_to, ft->step_from));
if (vec3_length(way) < 0.08f * s) continue; /* (lifted on the spot: not going over anything) */
b->crossing = true;
b->cross_foot = i;
b->cross_to = ft->step_to;
b->cross_dir = vec3_normalize(way);
b->cross_left = vec3_dot(flat(vec3_sub(b->cross_to, f->position)), b->cross_dir);
return;
}
if (!b->crossing) return;
/* (a foot set a little further on, past it: the body goes after that one, no further) */
float to = 0.0f;
for (int i = 0; i < 2; i++) {
const Foot *ft = &b->feet[i];
if (!ft->stepping) to = fmaxf(to, fminf(vec3_dot(flat(vec3_sub(ft->ankle, b->cross_to)), b->cross_dir), CROSS_PAST * s));
}
float left = vec3_dot(flat(vec3_sub(b->cross_to, f->position)), b->cross_dir) + to;
b->cross_left = left;
float back = vec3_dot(flat(vec3_sub(b->feet[b->cross_foot].ankle, b->cross_to)), b->cross_dir);
bool returned = back < -0.25f * s; /* (that foot came back: not going over after all) */
bool away = left > CROSS_AWAY * s; /* (the body went off the other way: not going over either - one foot was left stepping on its own) */
if (left < CROSS_DONE * s || returned || away || b->pose != POSE_UP || b->airborne) {
b->crossing = b->cross_force = false;
b->cross_held = 0.0f;
}
}
/* Where a foot lands: not on something lying there. A longer (or shorter)
step past it, and the foot lifted over whatever is in the way. Too high to
stand on and nowhere free: the foot stays where it is. */
static Vec3 clearSpot(const BodyFrame *f, Foot *foot, Vec3 spot, Vec3 other, float reach, bool sloppy, float s) {
foot->over = 0.0f;
if (!f->clutter) return spot;
const float foot_r = 0.12f * s;
float h = clutterAlong(f, foot->step_from, spot, 0.1f * s, 0.12f * s);
if (spotFree(f, spot, s)) {
foot->over = h > 0.0f ? h + OVER_CLEAR * s : 0.0f;
return spot;
}
/* Not bothering (drunk, worn out, clumsy, in a hurry): onto the bottle or branch it goes */
if (sloppy && f->clutter(f->ground_ctx, spot.x, spot.z, foot_r, f->position.y) < STEP_ON_SMALL * s) return spot;
Vec3 right = rightOf(f->facing);
static const float TRY[][2] = { { 0.22f, 0.0f }, { -0.16f, 0.0f }, { 0.34f, 0.0f }, { -0.3f, 0.0f },
{ 0.45f, 0.0f }, { 0.0f, 0.12f }, { 0.0f, -0.12f }, { -0.45f, 0.0f } };
for (int k = 0; k < (int)(sizeof(TRY) / sizeof(TRY[0])); k++) {
Vec3 p = vec3_add(spot, vec3_add(vec3_scale(f->facing, TRY[k][0] * s), vec3_scale(right, TRY[k][1] * s)));
if (TRY[k][0] > 0.0f && vec3_length(flat(vec3_sub(p, other))) > reach) continue; /* (no lunge) */
if (spotFree(f, p, s)) {
p.y = f->ground(f->ground_ctx, p.x, p.z, spot.y + 0.4f * s) + (spot.y - f->ground(f->ground_ctx, spot.x, spot.z, spot.y + 0.4f * s));
float hp = clutterAlong(f, foot->step_from, p, 0.1f * s, 0.12f * s);
foot->over = hp > 0.0f ? hp + OVER_CLEAR * s : 0.0f; /* (the spot it goes to, not the one it didn't) */
return p;
}
}
float under = f->clutter(f->ground_ctx, spot.x, spot.z, foot_r, f->position.y);
if (under > STAND_ON_MOST * s) return foot->step_from; /* nowhere free, and too high to stand on: stays */
foot->over = h + OVER_CLEAR * s;
return spot; /* low enough: on it then */
}
/* The body ran on while the foot waited in front of something: rather than
landing far behind, the foot goes past it (if a leg reaches); else it stays put. */
static Vec3 leapPast(const BodyFrame *f, Foot *foot, Vec3 aim, Vec3 other, float reach, float s) {
static const float PAST[] = { 0.22f, 0.34f, 0.45f, 0.6f };
for (int k = 0; k < (int)(sizeof(PAST) / sizeof(PAST[0])); k++) {
Vec3 p = vec3_add(aim, vec3_scale(f->facing, PAST[k] * s));
if (vec3_length(flat(vec3_sub(p, other))) > reach) break;
if (!spotFree(f, p, s)) continue;
p.y = foot->step_to.y;
float h = clutterAlong(f, foot->step_from, p, 0.1f * s, 0.12f * s);
if (h > 0.0f) foot->over = fmaxf(foot->over, h + OVER_CLEAR * s);
foot->clear_shift = vec3_add(foot->clear_shift, flat(vec3_sub(p, aim)));
return p;
}
return foot->step_to;
}
Vec3 Body_takeStumblePush(Body *b) {
Vec3 push = b->stumble_push;
b->stumble_push = vec3_zero();
return push;
}