#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 #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 */