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.h raw
#ifndef PEOPLE_BODY_H
#define PEOPLE_BODY_H
/*
* Character body animation.
*
* Feet are planted in the world and only move when they step: a foot steps
* when it drifts too far from where it should stand under the body. That
* gives walking, running, side steps, turning on the spot and standing on
* uneven ground without feet sliding. The rest of the skeleton (pelvis,
* spine, arms) is solved around the feet with two-bone IK.
*
* Actions (people/hands.c) set the goals each frame; Body_update eases towards
* them and moves the feet; Body_solve builds the skeleton (render/figure.h).
*
* On top of that: a mood shapes the posture (proud: chest out, chin up;
* shy: shoulders in, toes in, eyes down), the head looks around and leads
* turns, arms swing with follow-through, and a body can fall down, lie
* asleep and get up again.
*/
#include <stdbool.h>
#include "math/linalg.h"
#include "render/figure.h"
enum { HAND_LEFT = 0, HAND_RIGHT = 1 };
#define BODY_ANKLE_Y 0.075f /* the ankle over the sole, standing flat (a 1.75 m person; scale with height) */
/* How a person carries themselves. All -1..1 (nerves 0..1); 0 is neutral. */
typedef struct {
float pride; /* -1 = shy, closed up .. 1 = proud, chest out */
float energy; /* -1 = tired, slumped .. 1 = lively */
float nerves; /* 0 = calm .. 1 = restless, glancing around */
} Mood;
typedef enum {
POSE_UP,
POSE_FALLING, /* toppling over after a bad stumble */
POSE_LYING_DOWN, /* going down slowly to sleep */
POSE_DOWN, /* on the ground */
POSE_GETTING_UP,
} PoseState;
enum { SIT_GROUND, SIT_SEAT }; /* cross-legged on the ground, or on a chair / bench */
/* Height of the walkable surface at (x, z), searching down from from_y. */
typedef float (*GroundQuery)(const void *ctx, float x, float z, float from_y);
/* How high something stands above feet_y within radius of (x, z) (0 = nothing; what you stand on
doesn't count): feet keep off it, and step over it if it's low. */
typedef float (*ClutterQuery)(const void *ctx, float x, float z, float radius, float feet_y);
/* How far p (a ball of radius r) has to go along dir (unit) to be out of anything solid (0 = it's out). */
typedef float (*PushOutQuery)(const void *ctx, Vec3 p, Vec3 dir, float radius);
typedef struct {
Vec3 ankle; /* world position of the ankle */
Vec3 toe; /* world position of the toe tip */
Vec3 dir; /* direction the foot points (planted feet keep it) */
Vec3 step_from;
Vec3 step_to;
float step; /* 0..1 progress while stepping */
bool stepping;
float over; /* stepping over something this high: the foot goes up higher (and slower) */
float lift; /* ... and this much more, kept clear of what it passes over (sprung) */
float into; /* planted: how far its leg would reach into what lies there as the body moves on (m) */
Vec3 clear_shift; /* this step lands this much further on (or short), off what lies there */
float roll; /* planted: 0 = flat .. 1 = heel up, on the toes (behind the body) */
float pitch0; /* how the foot was tilted when this step began (it leaves the ground like that) */
Vec3 spot_was; /* where the step was aimed last frame (the aim follows the body, less so near the end) */
Vec3 aim_local; /* ... in the body's own frame (x right, z ahead): changes there glide */
float slap, slap_v;/* just landed on the heel: the toes still up, coming down (radians) */
Vec3 vel, prev; /* how fast the ankle moved last frame (world) */
Vec3 air_v; /* in the air: its speed (sprung) */
Vec3 step_v0; /* this step started moving this fast (landing from the air) */
float delay; /* landing: this foot comes down a moment after the other */
float planted; /* s on the ground since it last came down (running: it stays a moment, pushing off) */
float swing_run; /* how much this swing is a running one (set as it leaves, easing off slowly: stopping, no jump) */
} Foot;
typedef struct {
/* Goals, set every frame by the actions */
float crouch_goal; /* 0 = standing .. 1 = deep squat */
float sit_goal; /* 1 = sitting down (sit_style) */
bool squat_pee; /* squats down to pee (women, without a Urinella) */
bool urinella_use; /* pees standing with a Urinella (it's drawn) */
float seat_height; /* SIT_SEAT: how high the seat is (unscaled... near enough) */
float stand_ahead; /* the feet stand this much further forward (unscaled): sitting down on a seat */
bool on_ladder; /* feet on the rungs (placed by world/ladder.c), not in the air */
int sit_style;
float lean_goal; /* -1 = lean back (pull) .. 1 = lean forward (push) */
bool hand_active[2]; /* hand reaches for hand_target */
Vec3 hand_target[2]; /* world space palm position */
float unsteadiness; /* 0 = sober .. 1 = drunk: sway and misplaced steps */
float balance_goal; /* 0 .. 1: standing on something narrow (a log) - set by the world */
float dance_goal; /* 0 .. 1: dancing (a floor playing: knees on the beat, arms going) ... */
float dance_beat; /* ... where in the music (beats, runs on) ... */
int dance_style; /* ... how they dance (0 .. 3: bouncing, swaying, arms up, stomping) */
float balance; /* ... eased: arms out, teetering */
float dance; /* ... eased: how much they're dancing */
float teeter, teeter_v; /* -1 .. 1 tipping to the left / right (past 1: a step off) */
bool stepped_off; /* lost the balance just now (the world says so; taken) */
float exertion; /* 0 = fresh .. 1 = exhausted: heavy breathing */
float breath_phase; /* where in a breath (radians, runs at the breathing rate) */
float chin_goal; /* head tipped back (radians), e.g. drinking */
float urge; /* 0..1: wants to move on (gets up faster after a fall) */
float disgust; /* 0..1: something stinks (face scrunches up); eases off by itself */
Vec3 look_dir; /* world direction to look at ... */
float look_weight; /* ... this much (0 = look around on your own) */
Mood mood;
/* Animated state */
bool ready;
bool airborne;
float air_time; /* seconds off the ground */
float landing; /* > 0 while the feet reach for the ground after a jump */
Foot feet[2];
int last_step; /* foot that stepped last */
float step_length; /* current stride, drives step timing */
Vec3 last_position;
float sit; /* 0 = up .. 1 = sitting */
Vec3 spine_lag, spine_lag_vel; /* the upper body lags behind starts, stops and turns */
Vec3 last_velocity;
Vec3 velocity; /* smoothed world velocity */
float crouch, crouch_v;
float land_squat; /* just landed from high up: down into the knees this far (eases off) */
float lean, lean_v;
float hand_weight[2]; /* 0 = free swing .. 1 = at target */
float hand_wvel[2]; /* ... how fast that changes (it eases in and out) */
float pelvis_y; /* smoothed pelvis height (figure space, unscaled) */
float pelvis_vel; /* ... and how fast it moves (a spring: no jolts) */
float sway_s, sway_v; /* hips over the standing foot, sprung */
float hip_s[2], hip_v[2];
Vec3 hand_local[2], hand_vel[2]; /* hand goals, sprung (figure space): reaching never snaps */
Vec3 hand_goal_was[2];
bool hand_ready[2];
Vec3 limb_pole[4]; /* which way each knee and elbow pointed last frame (they never flip over) */
Vec3 joints_was[JOINT_COUNT]; /* the skeleton shown last frame (figure space, scaled): nothing moves faster than it can */
bool joints_was_ok;
Vec3 knee_off[2]; /* a knee swung out round the leg, over what it would go through: this far
from where the pose put it (figure space, gliding) */
float leg_raise[2]; /* ... or the foot raised off the ground, knee bent (m) */
float scrape; /* how deep a leg went into something it couldn't keep clear of (m, this frame) */
ClutterQuery clutter; /* what lies about (from the last frame), for keeping the legs clear of it */
GroundQuery ground; /* ... and the ground (a stream comes down on it) */
PushOutQuery push_out; /* ... and solid things (arms keep out of them) */
const void *clutter_ctx;
float sulk; /* 0..1: annoyed (shows in the face; set by the world) */
float lifted[JOINT_COUNT]; /* down or sitting: how far each joint is lifted onto what it lies on (see Body_solve) */
Vec3 arm_out[2]; /* standing: how far each arm is moved out of what it would be in (figure space, eases) ... */
float elbow_up[2]; /* ... how far its elbow swings back over an edge its hand holds (0..1, eases) ... */
float arm_hold[2]; /* ... and whether its hand holds on there (0..1, eases: the hand isn't moved out with the arm) */
Vec3 at; /* where the body stands (last frame, world) ... */
float at_scale; /* ... and its size (height / reference height) */
bool limb_pole_ok[4];
float clock;
float stumble; /* > 0 while recovering from a misstep (seconds left) */
/* Half way over something (a foot stepping over it, or landed past it with
the body still behind): the body carries on over it (see Physics) */
bool crossing;
bool cross_force; /* ... and now it must (worn out, too long, legs too wide apart) */
int cross_foot; /* the foot that went over */
Vec3 cross_to, cross_dir; /* where it lands, and the way over */
float cross_left; /* how much further the body has to go */
float cross_held; /* seconds standing still half over (costs energy) */
float flail, flail_v; /* arms out for balance (sprung) */
float idle_s, idle_v; /* standing about (0..1, sprung): hands into their habits */
Vec3 root_vis, root_vel;/* where the hips are drawn over (world, horizontal): sprung, no jitter */
bool root_ok;
Vec3 stumble_push; /* lurch to apply to the character (taken by the world) */
unsigned rng;
/* A shoulder roll out of a hard landing (see Body_shoulderRoll) */
float shoulder_roll; /* 0 = none, else how far through it (0..1) */
float roll_shoulder; /* -1 over the left shoulder, 1 the right */
Vec3 roll_dir; /* which way (world, horizontal) */
/* Falling down and getting up */
PoseState pose;
float pose_time;
float down_time; /* seconds left on the ground, < 0 = until woken */
Vec3 fall_dir; /* world direction the head points while lying */
float lie_offset; /* hips this far along fall_dir (unscaled) */
bool face_down;
bool asleep;
bool replant; /* plant the feet again next update */
bool pulling_up; /* climbing up over an edge (on_ladder too): the legs still keep clear of it */
float pull_knees; /* ... its knees up and out, eased (0..1) */
float reach_bend, reach_bend_v; /* ... and the back bends over as far as it takes to keep the hands on it ... */
float belly_over; /* ... or lies flat over the edge on the belly (0..1) */
float fitness; /* how sporty (0..1): runs smoother, steps over things surer */
float bob, bob_v; /* running: the body's rise and fall with each stride (sprung, -1..1) */
Vec3 shift; /* move the character here when getting up (taken by the world) */
/* Expression and secondary motion */
Mood shown; /* mood eased in slowly */
Vec3 last_facing;
float turn_rate; /* radians per second, > 0 = turning right */
float head_yaw, head_pitch, head_roll, chin;
float glance_yaw, glance_pitch, glance_timer;
float blink, blink_timer;
float burp; /* > 0 while burping (seconds left) */
float vomit; /* > 0 while being sick (seconds left) */
bool vomit_splash; /* it just came out (taken by the world) */
float pee; /* > 0 while peeing (seconds left) */
float pee_total; /* ... of this many */
bool pee_done; /* just finished (taken by the world) */
float lie_ground[9]; /* ground under a lying body, from 1.2 m behind the hips to
1.2 m ahead along fall_dir (meters, relative to the feet) */
float arm[2], arm_vel[2]; /* upper arm swing angle and its speed */
float fore[2], fore_vel[2]; /* elbow bend */
float weight_shift, weight_goal, weight_timer; /* idle: standing on one leg more */
float quirk[4]; /* per person: lopsided arm swing, shoulders, head tilt, elbows (-1..1) */
float style[4]; /* per person, how they walk (-1..1): feet wide or narrow, toes out or in, bouncy or flat, hips swaying or still */
float stride; /* per person (-1..1): short quick steps .. long ones (the same speed) */
unsigned char habit[2]; /* per person: two gestures they make more than others */
int gesture; /* idle: one hand on the hip, at the head, at the chin, ... */
int gesture_side;
float gesture_time; /* > 0 while gesturing (seconds left) */
float gesture_len;
float gesture_timer; /* until the next one */
float gesture_fade; /* 1 .. 0: cut short (walking off, hand needed), it fades out from where it was */
float grade; /* slope along the walk: > 0 uphill */
int trouble; /* getting up: 1 = stumbles at the end, 2 = falls back */
bool from_knees; /* falling back while getting up */
int fall_style; /* how this fall looks (see body.c) */
float fall_twist; /* radians the body turns while going down */
float reach_turn; /* the shoulders turned this far (reaching up with one hand first: that shoulder ahead) */
float fall_time; /* seconds this fall takes */
float fall_delay; /* > 0: staggering backwards before going down */
Vec3 fall_pending;
/* Standing about: never quite still */
int fidget; /* FIDGET_* (see body.c): shaking a leg, rocking, stretching, ... */
int fidget_side;
float fidget_time; /* > 0 while doing it (seconds left) */
float fidget_len;
float fidget_timer; /* until the next one */
float fidget_fade;
Vec3 stance[2]; /* how each foot likes to stand right now, off its usual spot (unscaled) */
float stance_timer;
Vec3 ankle_shown[2]; /* where the ankles were drawn last (world): steps start from there */
Vec3 toe_shown[2];
bool shown_valid;
int landed; /* a foot just came down: 1 left, 2 right (taken by the world) */
Vec3 landed_at;
/* Jumping: one foot pushes off last, running the other leads and lands first */
bool jumping;
int push_foot; /* -1 = both feet together (rare) */
int lead_foot; /* running jump: reaches ahead for the landing (-1 = none) */
float push_time; /* seconds the push-off foot stays on the ground */
float last_y, vy; /* vertical speed of the body */
/* Drop and roll (on fire): body stretched, rolling from side to side */
bool rolling;
float roll; /* radians about the long axis */
float roll_side; /* meters rolled sideways */
float roll_over; /* rolling over onto something (+1 / -1 the way round, 0: not): face down to on the back, once */
/* Peeing */
Vec3 pee_aim; /* world direction to aim at (zero: just ahead and down) */
float pee_power; /* 0 = normal .. 1 = pushing hard: further, done sooner */
/* Smoking (see Body_smoke) */
int smoke_phase; /* SMOKE_NONE / _ROLL (rolling one) / _LIGHT / _ON */
float smoke_t; /* seconds in this phase */
float cig_left; /* 1 = fresh .. 0 = down to the filter */
int cig_hand; /* HAND_LEFT / HAND_RIGHT, or 2: in the mouth */
float drag; /* > 0 while taking a drag (seconds left) */
float drag_timer; /* until the next one */
float exhale; /* > 0: smoke coming out (seconds left) */
float cig_hold; /* 0..1 eased: the cigarette hand up in front (standing about) */
bool butt; /* finished or dropped: the world drops a butt (taken) */
} Body;
enum { SMOKE_NONE, SMOKE_ROLL, SMOKE_LIGHT, SMOKE_ON };
typedef struct {
Vec3 position; /* character base point */
Vec3 facing;
float height;
bool grounded;
float dt;
GroundQuery ground;
const void *ground_ctx;
ClutterQuery clutter; /* optional, same ctx */
PushOutQuery push_out; /* optional, same ctx */
} BodyFrame;
/* Eases towards the goals and moves the feet. */
void Body_update(Body *body, const BodyFrame *frame);
/* Returns and clears the lurch of a stumble that just happened (world velocity). */
Vec3 Body_takeStumblePush(Body *body);
/* Returns and clears where the character moves when getting up (world offset). */
Vec3 Body_takeShift(Body *body);
/* Hit by something: a lurch and a few unsteady steps (push = world velocity). */
void Body_stagger(Body *body, Vec3 push);
/* Falls over towards dir (world), lies a moment and gets up again. */
void Body_fall(Body *body, Vec3 dir);
/* Landed from high up at this speed (m/s down), going this fast along (m/s); sure 0..1 (fit, sober,
fresh, sure-footed), sporty 0..1. Always into the knees first (bounced off); going along and not
too high (about 3.3 m, the very sporty 4 m): a roll over the shoulder (the sure); higher, or nowhere
to roll to: deep in the knees, or a crash. Returns LAND_*; bad: how hard it was on the body (0..1:
a twisted ankle, a broken bone - world.c decides). */
enum { LAND_SOFT, LAND_DEEP, LAND_ROLL, LAND_CRASH };
int Body_land(Body *body, Vec3 dir, float speed, float forward, float sure, float sporty, float *bad);
/* Over one shoulder (side -1 left, 1 right) along dir (world) and up on the feet again: the sporty landing from high up. */
void Body_shoulderRoll(Body *body, Vec3 dir, float side);
bool Body_isRolling(const Body *body);
#define SHOULDER_ROLL_TIME 1.05f /* s: down, over, up */
/* Lights a cigarette (rolled: rolls it first, both hands) into hand (or 2: the mouth).
It burns down over a few minutes, a drag now and then, smoke breathed out. */
bool Body_smoke(Body *body, bool rolled, int hand);
bool Body_isSmoking(const Body *body);
/* Done with it early: dropped (a butt for the world). */
void Body_stopSmoking(Body *body);
/* A cigarette just finished or dropped (the world drops the butt). */
bool Body_takeButt(Body *body);
/* On fire: quickly down to the ground, stretched out, rolling from side to side. */
void Body_dropAndRoll(Body *body, Vec3 dir);
/* Up over an edge (the roll way): down flat on the top, along `along`, rolled
over once onto the back sideways (side +1: the way cross(along, up) points), then up. */
void Body_rollOnto(Body *body, Vec3 along, float side);
/* Tripped over something with this foot (0 left, 1 right): a lurch that way,
maybe a fall. severity 0..1 (drunk, running, clumsy). */
void Body_trip(Body *body, int foot, Vec3 facing, float severity);
/* 0..1: how clumsy this person is by nature. */
float Body_clumsiness(const Body *body);
/* Returns and clears the foot that just came down and where (world ankle). */
bool Body_takeLanding(Body *body, int *foot, Vec3 *at);
/* Lies down to sleep until Body_getUp. */
void Body_lieDown(Body *body);
void Body_getUp(Body *body);
/* A good stretch now (arms up over the head: out of the car, up from a long sit). */
void Body_stretch(Body *body);
/* Not standing: falling, lying or getting up (no walking, no hands). */
bool Body_isDown(const Body *body);
void Body_burp(Body *body);
/* Bends over and is sick; Body_takeVomit reports the moment it comes out. */
void Body_vomit(Body *body);
bool Body_takeVomit(Body *body);
/* Thinking it over for a moment: a hand to the chin or scratching the head (if one is free). */
void Body_think(Body *body, float seconds);
/* Something said: a hand goes with it (a free hand, standing): waving, a shrug, pointing. */
enum { TALK_WAVE, TALK_SHRUG, TALK_POINT };
void Body_talkGesture(Body *body, int kind);
/* Stands still for a pee (this many seconds); Body_takePee reports when it's done. */
void Body_pee(Body *body, float seconds);
bool Body_takePee(Body *body);
/* Can't walk or use the hands right now: down, being sick or peeing. */
bool Body_isBusy(const Body *body);
/* Builds the skeleton (figure space) for the current state. */
void Body_solve(Body *body, Vec3 position, Vec3 facing, float height, float dt, Skeleton *out);
#endif /* PEOPLE_BODY_H */