Nimbin[12]?Games / Entropie / entropie/src/people/body.h

Entropie git · main

Entropie festival game; first steps

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

C 88.5% Markdown 9.1% GLSL 2.3%
git clone https://git.christianimmanuel.de/games/Entropie.gitwget https://git.christianimmanuel.de/games/Entropie/archive/Entropie.tar.gz
entropie/src/people/body.h 20.7 KB · 359 lines 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 */