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 2.3.0: hands, shoulder roll, strides, size
- Hands: wrist kept near straight; held bottles and cigarettes sit in the fist - Shoulder roll: dropping from about 2.5 m the sporty roll over the left or right shoulder in the direction they were moving; others go into the knees or crash - Sprint: everyone has their own stride (longer and fewer, or shorter and quicker), the foot reaches further ahead, arms swing freely against the legs - Size: body box, step height and speed follow each person's own height
entropie/Makefile | 2 +- entropie/README.md | 50 ++++++++++--------- entropie/STATE.md | 16 ++++++- entropie/commit_message.txt | 17 ++++--- entropie/src/math/linalg.h | 8 ++++ entropie/src/people/body.c | 40 ++++++++++++++-- entropie/src/people/body.h | 10 ++++ entropie/src/people/body_pose.c | 90 ++++++++++++++++++++++++++++++----- entropie/src/people/dog.c | 21 ++++++++ entropie/src/people/hands.c | 14 ++++-- entropie/src/render/shapes.c | 6 +-- entropie/src/render/shapes.h | 10 ++-- entropie/src/render/shapes_internal.h | 2 + entropie/src/render/shapes_props.c | 54 ++++++++++++++++++--- entropie/src/world/chips.c | 8 ---- entropie/src/world/manor.c | 12 +++-- entropie/src/world/object.c | 22 +++++++++ entropie/src/world/object.h | 4 ++ entropie/src/world/physics.c | 2 + entropie/src/world/scene.c | 62 +++++++++++++++--------- entropie/src/world/site.c | 31 ++++-------- entropie/src/world/world.c | 13 ++++- 22 files changed, 372 insertions(+), 122 deletions(-) diff --git a/entropie/Makefile b/entropie/Makefile index 8b828a8..3a4a82d 100644 --- a/entropie/Makefile +++ b/entropie/Makefile @@ -1,5 +1,5 @@ APP := entropie -VERSION := 2.1.0 +VERSION := 2.3.0 PREFIX ?= /usr BINDIR ?= $(PREFIX)/bin diff --git a/entropie/README.md b/entropie/README.md index 6a4e565..d8ffccc 100644 --- a/entropie/README.md +++ b/entropie/README.md @@ -28,10 +28,13 @@ Painted in the rotoscope style of *Waking Life* (2001): The map is Homburgshöhe at Geflügelhof Oderland, traced from the real map and satellite pictures (about 340 x 340 m). -- A plank ramp in the yard, its top end on a block: walk up it +- The courtyard is clear for the real game: the fire with its benches, + some firewood, branches and litter, a tree, a few bottles - An obstacle course in the chicken farm yard: three lanes of logs, crates, - benches, walls and blocks, spaced and close together. (Testing: set - `START_AT_COURSE` in `src/game/game.c` to 1 to start there) + benches, walls and blocks, spaced and close together. Beside it the things + to try out: crates to carry, heavy rollers to push together, a plank ramp + to walk up. (Testing: set `START_AT_COURSE` in `src/game/game.c` to 1 to + start there) - You start in the courtyard, a three-sided farmyard open to a gravel road: the main building, your garages (you can walk in) joined to it, the long brick barn opposite (ivy climbing high) and your unfinished building at the @@ -52,16 +55,18 @@ and satellite pictures (about 340 x 340 m). they swing right round and lie flat against the wall - Your garages have three rooms: tools next to the main building (workbench, tool wall, shelf, axe, shears, chainsaw and a ladder), a kitchen (counter, stove, - table and chairs, the coat rack; hold food over the stove to cook it) and a car repair + table and chairs; hold food over the stove to cook it) and a car repair place with an old car behind the wide door -- The woodpile by the barn, left of the toilet: everybody stacks firewood +- The woodpile by the barn: everybody stacks firewood there unless you show them another place -- The fireplace's tripod is of wooden poles standing well away from the - fire; you walk round them +- The fireplace is a ring of field stones; its tripod of long wooden poles + (3.9 m up, the grill on a long chain) stands well away from the fire; you + walk round them - Alice the dachshund lives here (painted like the people): she trots after you, sniffs around, begs for sausages, comes when called (R), lies by the fire and loves being petted (click). She runs after what you throw and brings it back (sometimes catching it in the air); - a sausage on the ground she eats, an apple she keeps and chews somewhere + a sausage on the ground she eats, an apple she keeps and chews somewhere. + She has a ball and a rope toy: now and then she brings you one to throw - A little path leads out between the garages and the barn - Across the gravel road: the orchard (two apple trees, bushes all round), then a small path and a row of other garages, some closed, some full of junk @@ -99,7 +104,7 @@ and satellite pictures (about 340 x 340 m). - Walk into the fire and you catch fire: C to drop and roll (down fast, stretched out, rolling side to side), or pour a drink over yourself (left click). Others run, then drop and roll; a drink, water or pee puts them out too -- Hold food close to the fire to grill it: sausages (from the cool box) are +- Hold food close to the fire to grill it: sausages are only really good grilled; apples and bread get better too - People come over to a burning fire, sit down round it and look into the flames - Now and then they sit down at a table (the meeting table, the kitchens) for @@ -207,7 +212,11 @@ and satellite pictures (about 340 x 340 m). room on top: you step up onto it the same way, one leg after the other - Dropping from high up: the knees come up to the belly, the legs reach down just before the ground. Over about 1.3 m you go deep into the knees; - over about 2.5 m the fit roll it off, the others crash (energy, dirt) + over about 2.5 m the sporty roll over a shoulder the way they were going, + the others crash (energy, dirt) +- Everyone moves at their own size: taller people step higher and run a + little faster; each has a stride of their own (long steps or quick ones) +- Bottles, cigarettes and the like sit in the fist, the wrist kept straight - The sporty pull up and climb ladders much quicker - Everyone walks their own way: feet wider or narrower, toes out a bit more or less, bouncier or flatter (livelier when lively), hips swaying more or less @@ -509,9 +518,7 @@ say no more often and don't come to you for a bit. the shy keep it all on), yours too; it goes back on when cooled down - Cold nights: away from the fire a jacket goes on (each their own colour), by the fire or in the day it comes off and gets tied round the waist; - warm again, they take it back to the rack sooner or later (the tidy sooner) -- Everybody's jacket starts on the coat rack in your kitchen: getting cold, - they walk over and put it on. Yours too: click the rack (on / back on it) + everybody's jacket is with them (the coat rack is gone) - Ask them what they're doing, send them back to helping you, or give them a break ## Smoking @@ -532,33 +539,30 @@ say no more often and don't come to you for a bit. - A drag now and then (the tip glows), smoke breathed out, the hand up in front between drags. Hands needed: it goes into the mouth - Done: flicked away or dropped as a butt (into the fire when close; the - tidy and you use the old tin on the cool box by the fire as ashtray; you see - it fill up) + tidy and you use the old tin on the end of a bench by the fire as ashtray; + you see it fill up) - A full ashtray gets emptied by somebody tidy (into a scrap of paper, off to the bin) - K: light one (yours, or somebody next to you gives you a light), K again puts it out. Talk: ask somebody for a cigarette, or offer one - Packs and tobacco run out; Tab (skills page) shows your pockets -- An old cigarette machine hangs on the main building, right of the door: - click it for a pack of 20 (there's no money in the game yet). Out of - smokes and nobody gives them one (you get asked first), the others walk - over and get a pack too ## Day and needs - Hunger, thirst, a full bladder and sleepiness build up over the day - Work, carrying and running wear you out; resting, food and drink bring you back - Worn out, hungry or thirsty: slower, weaker, slumped -- Eat apples and bread, drink water (beer counts a little); the apple crate - and the water barrel never run out +- Eat apples and bread, drink water (beer counts a little). (The apple + crate, the water barrel and the cool box are gone: food and drink come + with the car later) - Everyone's bladder is their own size (bigger people hold more, worn out it presses sooner); drinks fill it, and a full one takes a while to empty - Pee with P (or it happens anyway): you see the stream; being sick, too. Aim with the mouse, hold Shift to push harder (further, quicker done). - Next to the toilet (the blue cabin by the barn) P takes you inside; + Next to the toilet (in the main house, off the corridor) P takes you in; the others use it too when it's not too far, and wait if it's occupied - Women squat to pee, unless they have a Urinella: one-time use, up to 4 - in the pocket. Out of them, they fetch more from the box by the toilet + in the pocket. Out of them, they fetch more from the box in the toilet (you too: click it). Squatting, trousers come down to the knees, a skirt or dress is gathered up; standing, the fly is open or the skirt lifted - Women who never tried a Urinella may try one from the box: some like it, diff --git a/entropie/STATE.md b/entropie/STATE.md index 7686990..cc2a653 100644 --- a/entropie/STATE.md +++ b/entropie/STATE.md @@ -157,6 +157,18 @@ Round 2.1: - Rolling kit (`pockets.c`): `tobacco` (roll-ups' worth, a pouch POUCH_ROLLUPS 75), `papers` (PAPERS_PACK 50), `filters` (FILTERS_BAG 120), `filter_habit`; a roll-up takes tobacco, a paper and a filter if there is one. `Pockets_need`: papers, tobacco, or (filter habit) a filter missing. `ai.c` smokeBreak: asks whoever near has it to spare (6 m): NPCs give (3 papers, 5 filters, a pinch for 2) or say no; the player is asked (`asking_you`, a hint line until given, or they walk off). A filter is asked for once a smoke (`filter_asked`), then they roll without. - Your pockets (B, `game_actions.c` game_pockets / Game_pocketLines, drawn on the right in `main.c`): what's in them, how many pockets are used (`Pockets_slots`: work trousers 8 - you -, trousers and shorts 4, a skirt 2, a dress 1). Up / Down picks, Enter: looking at someone you give it (cigarette, pinch, papers, filters, lend the lighter, a Urinella; what they asked for: "Thanks, you're a star!"), else you roll one and light it. `keys` ("Car keys", Kurti's "House keys"). +Round 2.2: +- The courtyard cleared for the real game (`scene.c`): the carry crates, the heavy rollers (1200 / 100 kg), the 2 x 1 x 2 box and the plank ramp are beside the obstacle course now (`TESTING`, `testCorner`: 9.5 m left of the middle lane, 6 m down). Gone: the static box (and what stood on it), the apple crate, the water barrel, the cool box by the fire, the portable toilet and the Urinella carton by the barn, the cigarette machine (FURN_SMOKES stays in the code, placed nowhere: NPCs then don't go for one), the coat rack (jackets start with everybody; NPCs without a rack never go to one). +- A toilet in the house (`manor.c` furnish: FURN_TOILET 0.42 x 0.8 x 0.8, cistern against the front wall; `shapes_props.c`: under 1.2 m high it's a bowl and cistern, not a cabin); the Urinella carton on its floor. +- The fire: a ring of 15 field stones (`shapes_addRock`, shared from shapes.c), not blocks. Tripod GRILL_TOP 3.9 (was 1.95), TRIPOD_OUT 1.3, TRIPOD_PAST 0.6; the grill hangs GRILL_DROP 3.3 below (the grate stays 0.6 m over the fire; it swings slower, as far). The ashtray tin stands on the end of a bench. +- Alice's toys (OBJ_TOY, weight 0: a ball - rolls -, 1: a rope with knots): by her at the start. Thrown, she fetches them like anything; and wandering with you within PLAY_NEAR 20 m, PLAY_CHANCE 0.015/s she fetches one (TOY_SEES 15 m) and brings it to your feet. + +Round 2.3: +- Hands (`body_pose.c` reachArm, `hands.c`): the wrist bends at most WRIST_BEND 0.3 off the forearm (two passes), so the hand no longer hangs down; a held bottle, cigarette or anything one-handed sits in the fist (`fistOf`, FIST_AT 0.6 between wrist and fingertips), not floating in front of the fingers. +- Shoulder roll (`body.c` Body_land LAND_ROLL -> Body_shoulderRoll; `body_pose.c` solveShoulderRoll, TUCK pose): over about 2.5 m the sporty roll over the left or right shoulder the way they were going (sideways momentum, else their own side, `quirk[0]`), SHOULDER_ROLL_TIME 1.05 s, carried on at ROLL_ON 3 m/s (`world.c`), tucked about a 0.42 m ball, feet carried along, a fresh step after. The others still go into the knees or crash. +- Sprint: everyone's own stride (`Body.stride`, from their quirks: longer steps and fewer, or shorter and quicker; fitter: longer at a run), step length up to 1.8 m, the foot reaching further ahead at a run (MAX_LEAD x (1 + 0.45 run)). Arms swing against the other foot (they stayed forward when both feet were behind the hips), bent more (1.2 + 0.45 run), the forearm eased less. +- Size (`object.c` Object_setHeight, called in `scene.c`): the body box, its middle, the step height are from each person's own height (1.66 .. 1.86 m; it was 1.75 for all), top and jump speed x sqrt(height / 1.75). Audit NPCs: joint jumps 4516 -> 2418, knees back 76 -> 8; sprint 338 -> 225. + Unfinished building (Shell_build, at SHELL_AT in the frame's frame): brick shell 6.4 x 4.7, walls 2.9, open front (2 m) to the frame (now only the front half: pillars at z -13.8 / -9.2), window holes, bare floor, tiled roof with slabs. Barn: brick courses on BUILDING_BRICK walls (`addBrickCourses`: mortar a shade lighter, so the ink doesn't trace it), ivy (`Shapes_ivy`, OBJ_STRUCTUREs) on both long sides; the woodpile (OBJ_WOODPILE "Woodpile", WOODPILE_A/B) left of the toilet, everybody's woodpile from the start (`scene.c` Scene_finish). Fireplace tripod: wooden poles, feet TRIPOD_OUT 0.9 m outside the stones, TRIPOD_PAST 0.4 m past the top, a lashing; each pole an OBJ_WALL along it ("Tripod pole"). Gates (FURN_GATE, green boards): in your garages' doorways (GATES: hinge, end), `gate_open` / `gate_t`, `hinge`, `gate_shut` / `gate_out`; Object_swingGate (1.2 s), driven by showProps; a click within 1.2 m opens / shuts both leaves. Props that show (1.89, `world.c` showProps every frame): the tin's mesh is built again when its butts change (`Shapes_ashtray`: up to 12 show, the ash rises, a heap from 14; `shown` = what's built); the coat rack (FURN_COATRACK, in your kitchen by the doors, garage coords 20.6 / 2.4) shows the jackets hanging on it (`Shapes_coatRack`, up to 6, a bitmask of whose in `shown`). `jacket_hung` (characters, true at the start): cold (chill 0.6+) and the jacket with them: on it goes (as before); hanging: NPCs `ai.want_jacket` -> `ai.c` fetchJacket (idle / wander: goTo the rack, on with it, "Ah, better."; gives up after 90 s or warm again); you are told once ("your jacket hangs on the rack in your garage") and click the rack (`game_actions.c` coatRack: on, or back onto it). Warm again: it comes off and stays with them. @@ -202,7 +214,7 @@ Talk topics: offer, ask, suggest, where (new woodpile spot), ask for a hand, ask - **Falls**: straight over, knees first, twisting, diving forward; reeling back first; stumbles over the foot that caught - **Where a fall lands**: the way it was going if there's room, else turned aside or shorter (crumpling at the foot of a wall); lies across things up to 0.5 m high, never into or over higher ones; no part of the body goes into what it comes down on (lifted onto it, limbs refitted); sitting, the legs rest on what lies in front; the lift eases in and out (5 m/s), no jumps - **Pushing and pulling (F)**: look left / right past 12 deg and it turns that way about its middle (you and helpers walk round with it), as fast as strength against weight and size allow, never into walls or heavy things; Ctrl: free look, no turning -- **Moving**: walk, jog, sprint (a wave up and down with each stride), jumps off one foot, steps up and down, slopes, carrying, pushing, pulling, lifting together +- **Moving**: walk, jog, sprint (a wave up and down with each stride; everyone's own stride, longer and fewer or shorter and quicker), jumps off one foot, steps up and down, slopes, carrying, pushing, pulling, lifting together - **One end up**: a log (1.4 m and longer) too heavy for one person but not for half its weight: held within 0.8 m of an end, that end comes up to the hip (hands slide to it), the other drags on the ground and pivots there; you go at 75 %; a second person takes all of it (`hands.c`, endLift). Whoever can carry the whole weight lifts it all - **Arms out of things**: standing, an arm that would be in something solid (fixed or 40 kg and more; not ladders) moves back and out (eases at 2 m/s) and is fitted again; a hand holding an edge stays, the elbow swings back over the edge - **Speed limit**: no joint moves faster than it can (hands 14, feet 12, knees and elbows 9, trunk 5 m/s); bones keep their length; a limb folded right up (foot by the hip, cross-legged) keeps its bend instead of being fitted again (that flipped the knee); between folded and stretched (30-42 % of its reach) the two blend, so an arm folding up on an edge no longer jumps (elbows popped 31 cm); an elbow swung up over an edge stays solved that way @@ -309,4 +321,4 @@ Most important first. - Drawing many figures on a real graphics card: not measured (software GL: 300 people cost 3x the rest of the scene) --- -State of `entropie 2.1.0 (build 7cb9179)` +State of `entropie 2.3.0 (build 645f709)` diff --git a/entropie/commit_message.txt b/entropie/commit_message.txt index 0cfcc17..0d5253f 100644 --- a/entropie/commit_message.txt +++ b/entropie/commit_message.txt @@ -1,10 +1,9 @@ -entropie 2.1.0: new people, rolling kit, your pockets +entropie 2.3.0: hands, shoulder roll, strides, size -- New cast: you came with Volkram, Jana and Toni; the house belongs to - Kurti (74, slow on his feet) and his sons Martin (~30) and Jonas (~25). - Jonas, Ida, Mila and Ben are gone -- Most roll their own: tobacco (a pouch rolls about 75), papers (50 a - packet), filter tips (120 a bag). Short of something they ask whoever is - near for a paper, a filter or a bit of tobacco - you too -- B opens your pockets (work trousers: 8 of them): Up / Down, Enter takes - it out - looking at someone you give it to them, else you roll one +- Hands: wrist kept near straight; held bottles and cigarettes sit in the fist +- Shoulder roll: dropping from about 2.5 m the sporty roll over the left or + right shoulder in the direction they were moving; others go into the knees + or crash +- Sprint: everyone has their own stride (longer and fewer, or shorter and + quicker), the foot reaches further ahead, arms swing freely against the legs +- Size: body box, step height and speed follow each person's own height diff --git a/entropie/src/math/linalg.h b/entropie/src/math/linalg.h index 5a61d0f..785f6f8 100644 --- a/entropie/src/math/linalg.h +++ b/entropie/src/math/linalg.h @@ -102,6 +102,14 @@ static inline Vec3 mat4_mul_point(Mat4 m, Vec3 p) { return (Vec3){ r.x + m.m[12], r.y + m.m[13], r.z + m.m[14] }; } +static inline Mat4 mat4_scale(Vec3 v) { + Mat4 m = mat4_identity(); + m.m[0] = v.x; + m.m[5] = v.y; + m.m[10] = v.z; + return m; +} + static inline Mat4 mat4_translate(Vec3 v) { Mat4 m = mat4_identity(); m.m[12] = v.x; diff --git a/entropie/src/people/body.c b/entropie/src/people/body.c index 9792fca..ff58004 100644 --- a/entropie/src/people/body.c +++ b/entropie/src/people/body.c @@ -36,7 +36,7 @@ static Vec3 turnRight(Vec3 dir, float 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.2f * speed, 0.35f, 1.5f) * s; + return clampf(0.3f + 0.2f * speed, 0.35f, 1.8f) * s; /* (a sprint: long strides) */ } /* How far from the other foot a step over something may land: walking a @@ -61,7 +61,7 @@ static Vec3 footSpot(const Body *b, const BodyFrame *f, int side, float s) { 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 * s) / speed)); + 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 */ @@ -269,14 +269,26 @@ int Body_land(Body *b, Vec3 dir, float speed, float sure) { if (speed < LAND_DEEP_) return LAND_DEEP; float risk = (speed - LAND_DEEP_) / (LAND_BREAK - LAND_DEEP_); /* 0 .. 1 */ if (speed < LAND_BREAK && random01(b) < sure * (1.0f - 0.7f * risk)) { /* over the shoulder, and up again */ - Body_dropAndRoll(b, dir); - b->down_time = ROLL_REST * 0.5f; + 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); return LAND_ROLL; } 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]; } @@ -942,6 +954,8 @@ void Body_update(Body *b, const BodyFrame *f) { 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); @@ -983,13 +997,29 @@ void Body_update(Body *b, const BodyFrame *f) { 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.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 */ diff --git a/entropie/src/people/body.h b/entropie/src/people/body.h index 70d3311..96540ba 100644 --- a/entropie/src/people/body.h +++ b/entropie/src/people/body.h @@ -165,6 +165,11 @@ typedef struct { 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; @@ -201,6 +206,7 @@ typedef struct { 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; @@ -289,6 +295,10 @@ void Body_fall(Body *body, Vec3 dir); Soft: into the knees; harder: deep, a roll (the sure) or a crash. Returns LAND_*. */ enum { LAND_SOFT, LAND_DEEP, LAND_ROLL, LAND_CRASH }; int Body_land(Body *body, Vec3 dir, float speed, float sure); +/* 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); diff --git a/entropie/src/people/body_pose.c b/entropie/src/people/body_pose.c index 4b411ca..af3906b 100644 --- a/entropie/src/people/body_pose.c +++ b/entropie/src/people/body_pose.c @@ -87,10 +87,18 @@ static Vec3 refitLimb(Vec3 *j, int r, float l1, float l2, Vec3 pole) { } /* Shoulder -> elbow -> wrist -> hand reaching for a palm position. */ +#define WRIST_BEND 0.3f /* the hand goes on along the forearm, bent only this much towards where it reaches (no broken wrists) */ + static void reachArm(Vec3 *j, int shoulder, Vec3 palm, Vec3 pole) { Vec3 reach = vec3_normalize(vec3_sub(palm, j[shoulder])); - Vec3 wrist_goal = vec3_sub(palm, vec3_scale(reach, HAND)); - twoBone(j[shoulder], wrist_goal, UPPER_ARM, FOREARM, pole, &j[shoulder + 1], &j[shoulder + 2], true); + Vec3 dir = reach; + for (int pass = 0; pass < 2; pass++) { /* (the second time with the hand along the forearm the first one gave) */ + Vec3 wrist_goal = vec3_sub(palm, vec3_scale(dir, HAND)); + twoBone(j[shoulder], wrist_goal, UPPER_ARM, FOREARM, pole, &j[shoulder + 1], &j[shoulder + 2], true); + Vec3 fore = vec3_sub(j[shoulder + 2], j[shoulder + 1]); + if (vec3_length(fore) < 1e-4f) break; + dir = vec3_normalize(vec3_lerp(vec3_normalize(fore), reach, WRIST_BEND)); + } j[shoulder + 3] = vec3_add(j[shoulder + 2], vec3_scale(vec3_normalize(vec3_sub(palm, j[shoulder + 2])), HAND)); } @@ -690,7 +698,9 @@ static void solveStanding(Body *b, Vec3 position, Vec3 facing, float s, float dt /* Upper arm and forearm follow their swing with a little lag: the hand moves on a rounded path instead of a pendulum */ - float foot_ahead = ankle[side].z - j[JOINT_PELVIS].z; + /* (against the other foot, not the hips: running, both feet are mostly behind the hips - + measured from there both arms stayed forward, stiff in front of the chest) */ + float foot_ahead = lerpf(ankle[side].z - j[JOINT_PELVIS].z, 0.5f * (ankle[side].z - ankle[1 - side].z), smoothstepf(0.5f, 2.5f, speed)); float run = runAmount(speed); float amplitude = 0.85f * (1.0f + 0.25f * b->shown.energy + 0.2f * proud - 0.3f * shy) * (1.0f + 1.2f * run) * (1.0f + 0.2f * sign * b->quirk[0]); /* one arm swings more */ @@ -702,9 +712,12 @@ static void solveStanding(Body *b, Vec3 position, Vec3 facing, float s, float dt float lag = 1.0f - 0.15f * sign * b->quirk[0]; /* ... and a little later */ spring(&b->arm[side], &b->arm_vel[side], goal, ARM_STIFFNESS * quick * lag, ARM_DAMPING * sqrtf(quick * lag), dt); float a = b->arm[side]; - float bend_goal = 0.2f + clampf(speed * 0.06f, 0.0f, 0.3f) + 0.7f * runAmount(speed) - + 0.3f * fmaxf(a, 0.0f) + 0.15f * shy + 0.08f * sign * b->quirk[3]; - spring(&b->fore[side], &b->fore_vel[side], bend_goal, FORE_STIFFNESS * quick, FORE_DAMPING * sqrtf(quick), dt); + /* (walking: a little bend, more swinging forward; running: bent about square, closing as the + arm comes forward - the hand up by the chest - and opening on the way back past the hip) */ + float walk_bend = 0.2f + clampf(speed * 0.06f, 0.0f, 0.3f) + 0.3f * fmaxf(a, 0.0f); + float run_bend = 1.2f + 0.45f * a; + float bend_goal = lerpf(walk_bend, run_bend, run) + 0.15f * shy + 0.08f * sign * b->quirk[3]; + spring(&b->fore[side], &b->fore_vel[side], bend_goal, FORE_STIFFNESS * quick, FORE_DAMPING * 0.8f * sqrtf(quick), dt); /* (a little loose: no robot arms) */ float bend = b->fore[side]; Vec3 elbow = swing(j[sh], UPPER_ARM, a, sign * (0.1f - 0.04f * run + 0.04f * proud + 1.4f * flail - 0.07f * fmaxf(a, 0.0f) @@ -1540,6 +1553,57 @@ static void keepArmsOut(Body *b, Vec3 position, Vec3 facing, float s, float dt, } } +/* ---------- A shoulder roll (unscaled figure space) ---------- */ + +/* Curled up like a ball round (0, ROLL_BALL, 0): knees to the chest, chin down, the arms guiding. */ +#define ROLL_BALL 0.42f +#define ROLL_GROUND 0.06f /* the lowest joint this high (a limb's thickness): rolling on the ground */ +static const Vec3 TUCK[JOINT_COUNT] = { + [JOINT_PELVIS] = { 0.0f, 0.40f, -0.12f }, [JOINT_CHEST] = { 0.0f, 0.60f, 0.10f }, + [JOINT_NECK] = { 0.0f, 0.60f, 0.26f }, [JOINT_HEAD] = { 0.0f, 0.50f, 0.36f }, + [JOINT_HIP_L] = { -0.10f, 0.40f, -0.10f }, [JOINT_KNEE_L] = { -0.13f, 0.74f, 0.14f }, + [JOINT_ANKLE_L] = { -0.12f, 0.40f, -0.20f }, [JOINT_TOE_L] = { -0.12f, 0.32f, -0.10f }, + [JOINT_HIP_R] = { 0.10f, 0.40f, -0.10f }, [JOINT_KNEE_R] = { 0.13f, 0.74f, 0.14f }, + [JOINT_ANKLE_R] = { 0.12f, 0.40f, -0.20f }, [JOINT_TOE_R] = { 0.12f, 0.32f, -0.10f }, + [JOINT_SHOULDER_L] = { -0.18f, 0.60f, 0.12f }, [JOINT_ELBOW_L] = { -0.24f, 0.44f, 0.26f }, + [JOINT_WRIST_L] = { -0.19f, 0.36f, 0.44f }, [JOINT_HAND_L] = { -0.16f, 0.30f, 0.52f }, + [JOINT_SHOULDER_R] = { 0.18f, 0.60f, 0.12f }, [JOINT_ELBOW_R] = { 0.24f, 0.44f, 0.26f }, + [JOINT_WRIST_R] = { 0.19f, 0.36f, 0.44f }, [JOINT_HAND_R] = { 0.16f, 0.30f, 0.52f }, +}; + +static Vec3 yawPoint(Vec3 p, float a) { return vec3(p.x * cosf(a) + p.z * sinf(a), p.y, -p.x * sinf(a) + p.z * cosf(a)); } + +/* The ball turned over forward by angle (rad), along dir (figure space), the leading shoulder (side) first. */ +static void tuckAt(Skeleton *sk, float angle, Vec3 dir, float side) { + const float heading = atan2f(dir.x, dir.z), twist = -side * 0.5f, lean = side * 0.3f; + for (int i = 0; i < JOINT_COUNT; i++) { + Vec3 p = yawPoint(TUCK[i], twist); /* (one shoulder ahead) */ + Vec3 q = vec3(p.x, p.y - ROLL_BALL, p.z); + q = vec3(q.x * cosf(lean) - q.y * sinf(lean), q.x * sinf(lean) + q.y * cosf(lean), q.z); /* (over onto that side) */ + q = vec3(q.x, q.y * cosf(angle) - q.z * sinf(angle), q.y * sinf(angle) + q.z * cosf(angle)); /* forward over */ + sk->joints[i] = yawPoint(vec3(q.x, q.y + ROLL_BALL, q.z), heading); + } + float low = 1e9f; /* (the ball on the ground, not over it) */ + for (int i = 0; i < JOINT_COUNT; i++) low = fminf(low, sk->joints[i].y); + for (int i = 0; i < JOINT_COUNT; i++) sk->joints[i].y += ROLL_GROUND - low; + Vec3 c = sk->joints[JOINT_CHEST], pe = sk->joints[JOINT_PELVIS]; + sk->chest_forward = vec3_normalize(yawPoint(vec3(0.0f, -cosf(angle + 0.3f), cosf(angle) * 0.2f + 0.8f), heading)); + sk->waist_bend = vec3_scale(vec3_sub(c, pe), 0.0f); +} + +/* Down into the ball, over the shoulder, and up out of it (blended with the standing pose at both ends). */ +static void solveShoulderRoll(const Body *b, Vec3 facing, Skeleton *out) { + float t = b->shoulder_roll; + Vec3 dir = Figure_toLocal(vec3_zero(), facing, b->roll_dir); + dir.y = 0.0f; + dir = vec3_length(dir) > 1e-3f ? vec3_normalize(dir) : vec3(0.0f, 0.0f, 1.0f); + Skeleton tuck = *out; + float over = 2.0f * PI_F * smoothstepf(0.2f, 0.8f, t); + tuckAt(&tuck, over, dir, b->roll_shoulder); + float in = smoothstepf(0.0f, 0.22f, t) * (1.0f - smoothstepf(0.78f, 1.0f, t)); + blendSkeleton(out, &tuck, in); +} + void Body_solve(Body *b, Vec3 position, Vec3 facing, float height, float dt, Skeleton *out) { const float s = height / REF_HEIGHT; solveStanding(b, position, facing, s, dt, out); @@ -1611,6 +1675,8 @@ void Body_solve(Body *b, Vec3 position, Vec3 facing, float height, float dt, Ske blendSkeleton(out, &sitting, smoothstepf(0.0f, 1.0f, b->sit)); } keepAnatomy(b, out, b->pose == POSE_UP && b->sit > 0.05f && b->sit_style == SIT_GROUND, dt); + const bool rolling = b->shoulder_roll > 0.0f && b->pose == POSE_UP; + if (rolling) solveShoulderRoll(b, facing, out); out->face = faceFor(b); StreamArc arc = {0}; solveStream(b, facing, out, &arc); @@ -1623,11 +1689,13 @@ void Body_solve(Body *b, Vec3 position, Vec3 facing, float height, float dt, Ske out->uri_b = vec3_scale(out->uri_b, s); out->stream_width *= s; landStream(b, position, facing, s, &arc, out); - keepBodyOut(b, position, facing, s, dt, false, out); - clearLegs(b, position, facing, s, dt, out); - limitSpeeds(b, s, dt, out); - keepBodyOut(b, position, facing, s, dt, true, out); /* (contact wins over the speed limit) */ - keepArmsOut(b, position, facing, s, dt, out); + if (!rolling) { /* (a set move: as it is) */ + keepBodyOut(b, position, facing, s, dt, false, out); + clearLegs(b, position, facing, s, dt, out); + limitSpeeds(b, s, dt, out); + keepBodyOut(b, position, facing, s, dt, true, out); /* (contact wins over the speed limit) */ + keepArmsOut(b, position, facing, s, dt, out); + } placeCigarette(b, s, out); b->shown_valid = b->pose == POSE_UP && b->sit < 0.02f && !b->airborne; if (b->shown_valid) { diff --git a/entropie/src/people/dog.c b/entropie/src/people/dog.c index b4d605e..7f2cec0 100644 --- a/entropie/src/people/dog.c +++ b/entropie/src/people/dog.c @@ -47,6 +47,23 @@ static Object *sausageHolder(const Object *dog, Object *const *objects, int coun static void bark(Object *dog, const char *what); +#define PLAY_NEAR 20.0f /* m: you this near ... */ +#define PLAY_CHANCE 0.015f /* ... now and then (a second) she fetches one of her toys and brings it to you */ +#define TOY_SEES 15.0f + +/* One of her toys lying near. */ +static Object *toyNear(const Object *dog, Object *const *objects, int count) { + Object *best = NULL; + float best_d = TOY_SEES; + for (int i = 0; i < count; i++) { + Object *o = objects[i]; + if (o->type != OBJ_TOY || o->is_held) continue; + float d = flatDist(o->position, dog->position); + if (d < best_d) { best = o; best_d = d; } + } + return best; +} + /* Something for her: thrown (small enough to carry), or a sausage lying on the ground. */ static Object *somethingToFetch(const Object *dog, Object *const *objects, int count) { Object *best = NULL; @@ -154,6 +171,10 @@ void Dog_update(Object *dog, Object *const *objects, int count, float dt) { else if (to_player > FOLLOW_FAR) mode = DOG_FOLLOW; else if (mode == DOG_BEG) mode = DOG_WANDER; else if (mode == DOG_REST && !fire) mode = DOG_WANDER; + else if (mode == DOG_WANDER && to_player < PLAY_NEAR && rnd(dog) < PLAY_CHANCE * dt && + (dog->dog.fetch = toyNear(dog, objects, count))) { + mode = DOG_FETCH; /* (her toy, to bring you: throw it!) */ + } else if (mode == DOG_WANDER && fire && dog->dog.timer <= 0.0f && rnd(dog) < 0.5f) { mode = DOG_REST; float a = rnd(dog) * 2.0f * PI_F; diff --git a/entropie/src/people/hands.c b/entropie/src/people/hands.c index 8500a2a..2a37649 100644 --- a/entropie/src/people/hands.c +++ b/entropie/src/people/hands.c @@ -580,6 +580,8 @@ static float drinkGrip(float t) { /* Where along a bottle the hand holds it, from its middle towards the top (m): low around the body, or (rarely) at the neck. Sliding lower to drink. */ +#define FIST_AT 0.6f /* from the wrist to the knuckles: where a held thing sits in the hand */ + static float bottleGrip(const Object *who, int hand, const Object *bottle) { const Hands *h = &who->hands; float half = bottle->colliders[0].half_extents.y; @@ -1076,12 +1078,19 @@ void Hands_update(Object *who, float dt) { } /* Just taken: the item turns from the way it lay into the carry pose, around the palm. */ +/* The middle of the closed hand (between the wrist and the knuckles): what's held is in it. */ +static Vec3 fistOf(const Object *who, int hand) { + const Vec3 *j = who->skeleton.joints; + int w = hand == HAND_LEFT ? JOINT_WRIST_L : JOINT_WRIST_R; + return Figure_toWorld(who->position, Object_facing(who), vec3_lerp(j[w], j[w + 1], FIST_AT)); +} + static void settleInHand(const Object *who, int hand, Object *item) { const Hands *h = &who->hands; if (h->settle[hand] >= 1.0f) return; float k = smoothstepf(0.0f, 1.0f, h->settle[hand]); const Vec3 facing = Object_facing(who); - Vec3 palm = Figure_toWorld(who->position, facing, who->skeleton.joints[hand == HAND_LEFT ? JOINT_HAND_L : JOINT_HAND_R]); + Vec3 palm = fistOf(who, hand); Mat4 target = mat4_rotate_yxz(item->rotation); Vec3 y = vec3_normalize(vec3_lerp(Figure_toWorld(vec3_zero(), facing, h->settle_y[hand]), mat4_mul_dir(target, UP), k)); @@ -1168,8 +1177,7 @@ void Hands_placeHeld(Object *who) { float forward = extentAlong(item, facing) - CARRY_GRIP_DEPTH; center = vec3_add(vec3_scale(vec3_add(l, r), 0.5f), vec3_scale(facing, forward)); } else { - int joint = i == HAND_LEFT ? JOINT_HAND_L : JOINT_HAND_R; - Vec3 hand = Figure_toWorld(who->position, facing, who->skeleton.joints[joint]); + Vec3 hand = fistOf(who, i); /* (in the fist, not at the fingertips) */ if (h->drink_hand == i) { /* Tipped back towards the mouth */ item->rotation = vec3(-tiltOf(item, h->drink_time), who->rotation.y, 0.0f); diff --git a/entropie/src/render/shapes.c b/entropie/src/render/shapes.c index 2542832..71daad0 100644 --- a/entropie/src/render/shapes.c +++ b/entropie/src/render/shapes.c @@ -467,7 +467,7 @@ static const Vec3 STONE_COLORS[] = { }; /* A lumpy rock: a squashed sphere with a ragged radius. */ -static void addRock(MeshBuilder *b, Vec3 center, Vec3 half, unsigned *seed, Vec3 color) { +void shapes_addRock(MeshBuilder *b, Vec3 center, Vec3 half, unsigned *seed, Vec3 color) { enum { RINGS = 4, SEGS = 7 }; GLuint top = MeshBuilder_vertex(b, vec3_add(center, vec3(0.0f, half.y * (0.8f + 0.3f * scatter(seed)), 0.0f)), vec3(0, 1, 0), color); GLuint first = (GLuint)b->vertex_count; @@ -500,7 +500,7 @@ Mesh Shapes_stone(Vec3 size, unsigned seed) { MeshBuilder_init(&b); if (seed == 0) seed = 1; Vec3 color = STONE_COLORS[(seed >> 3) % (sizeof(STONE_COLORS) / sizeof(STONE_COLORS[0]))]; - addRock(&b, vec3_zero(), vec3_scale(size, 0.5f), &seed, color); + shapes_addRock(&b, vec3_zero(), vec3_scale(size, 0.5f), &seed, color); return Mesh_fromBuilder(&b); } @@ -527,7 +527,7 @@ Mesh Shapes_gravelStones(const Vec3 *points, int count, float width, float densi float yaw = scatter(&seed) * PI_F; Vec3 half = vec3(s * 0.5f, s * 0.22f, s * (0.3f + 0.15f * scatter(&seed))); size_t first = b.vertex_count; - addRock(&b, vec3_zero(), half, &seed, c); + shapes_addRock(&b, vec3_zero(), half, &seed, c); transformSince(&b, first, mat4_mul(mat4_translate(vec3(x, groundAt(g, x, z) + half.y * 0.4f, z)), mat4_rotate_y(yaw))); } } diff --git a/entropie/src/render/shapes.h b/entropie/src/render/shapes.h index 81b7df2..7562a58 100644 --- a/entropie/src/render/shapes.h +++ b/entropie/src/render/shapes.h @@ -72,6 +72,8 @@ Mesh Shapes_bottle(float diameter, float height, BottleStyle style); /* Food, centered on the origin. */ Mesh Shapes_apple(float diameter); +/* Alice's toys: variant 0 a ball (size.x across), 1 a rope with knots (size.x long, size.y thick). */ +Mesh Shapes_toy(int variant, Vec3 size); Mesh Shapes_bread(Vec3 size); /* Supplies, centered on the origin: a crate heaped with apples, a water barrel, @@ -89,8 +91,8 @@ Mesh Shapes_sausage(float length, bool grilled); Mesh Shapes_fireplace(float diameter); /* Where the tripod's pole k (0..2) stands, from the fireplace's middle. */ Vec3 Shapes_tripodFoot(float diameter, int k); -#define TRIPOD_OUT 0.9f /* its poles stand this far outside the stones (they don't catch fire) ... */ -#define TRIPOD_PAST 0.4f /* ... and reach this far past the top */ +#define TRIPOD_OUT 1.3f /* its poles stand this far outside the stones (they don't catch fire) ... */ +#define TRIPOD_PAST 0.6f /* ... and reach this far past the top */ /* Flames over the middle, base at y = 0. Their colors glow in the dark. */ Mesh Shapes_flames(void); @@ -108,8 +110,8 @@ Mesh Shapes_marker(float length, float depth); /* The grill: chain and grate hanging below the top of the tripod (origin). */ Mesh Shapes_grill(void); -#define GRILL_TOP 1.95f /* the tripod's rods meet this high over the fire */ -#define GRILL_DROP 1.35f /* the grate hangs this far below */ +#define GRILL_TOP 3.9f /* the tripod's rods meet this high over the fire */ +#define GRILL_DROP 3.3f /* the grate hangs this far below */ typedef enum { FURN_WORKBENCH, FURN_TOOLWALL, FURN_SHELF, FURN_STOVE, FURN_COUNTER, FURN_TABLE, diff --git a/entropie/src/render/shapes_internal.h b/entropie/src/render/shapes_internal.h index 7cfa164..7d1aa0c 100644 --- a/entropie/src/render/shapes_internal.h +++ b/entropie/src/render/shapes_internal.h @@ -39,6 +39,8 @@ extern const Vec3 COLOR_LEAVES; extern const Vec3 COLOR_STEEL; void shapes_addBox(MeshBuilder *b, Vec3 center, Vec3 size, const Vec3 colors[FACE_COUNT]); +/* A lumpy rock round center (half its size each way), its colour shaded a little. */ +void shapes_addRock(MeshBuilder *b, Vec3 center, Vec3 half, unsigned *seed, Vec3 color); void shapes_addTube(MeshBuilder *b, float base_y, float r_bottom, float r_top, float height, int segments, Vec3 color, bool caps); diff --git a/entropie/src/render/shapes_props.c b/entropie/src/render/shapes_props.c index de7d0f7..abdb1d9 100644 --- a/entropie/src/render/shapes_props.c +++ b/entropie/src/render/shapes_props.c @@ -52,6 +52,35 @@ Mesh Shapes_apple(float diameter) { return Mesh_fromBuilder(&b); } +Mesh Shapes_toy(int variant, Vec3 size) { + MeshBuilder b; + MeshBuilder_init(&b); + if (variant == 0) { /* a rubber ball: red, a yellow band round it */ + const Vec3 red = { 0.85f, 0.18f, 0.15f }, yellow = { 0.95f, 0.82f, 0.22f }; + const float r = size.x * 0.5f; + const int rings = 6; + for (int k = 0; k < rings; k++) { /* stacked bands, a sphere */ + float a0 = -PI_F * 0.5f + PI_F * (float)k / (float)rings, a1 = -PI_F * 0.5f + PI_F * (float)(k + 1) / (float)rings; + shapes_addTube(&b, r * sinf(a0), fmaxf(r * cosf(a0), 0.002f), fmaxf(r * cosf(a1), 0.002f), r * (sinf(a1) - sinf(a0)), 10, + k == rings / 2 || k == rings / 2 - 1 ? yellow : red, k == 0 || k == rings - 1); + } + } else { /* a rope toy: a short thick twisted rope (along x), a knot at each end */ + const Vec3 blue = { 0.28f, 0.40f, 0.70f }, white = { 0.90f, 0.88f, 0.82f }; + const float r = size.y * 0.5f, len = size.x; + size_t first = b.vertex_count; + shapes_addTube(&b, -len * 0.5f, r * 0.7f, r * 0.7f, len, 8, blue, true); + transformSince(&b, first, mat4_rotate_z(-PI_F * 0.5f)); + for (int e = -1; e <= 1; e += 2) { + first = b.vertex_count; + shapes_addTube(&b, -r * 1.2f, r * 1.1f, r * 1.1f, r * 2.4f, 8, white, true); + transformSince(&b, first, mat4_mul(mat4_translate(vec3((float)e * (len * 0.5f - r), 0.0f, 0.0f)), mat4_rotate_z(-PI_F * 0.5f))); + } + for (int k = -2; k <= 2; k++) /* the twist */ + addBlock(&b, vec3((float)k * len * 0.12f, 0.0f, 0.0f), vec3(0.015f, r * 1.5f, r * 1.5f), white); + } + return Mesh_fromBuilder(&b); +} + Mesh Shapes_bread(Vec3 size) { MeshBuilder b; MeshBuilder_init(&b); @@ -436,7 +465,7 @@ Vec3 Shapes_tripodFoot(float diameter, int k) { Mesh Shapes_fireplace(float diameter) { const float r = diameter * 0.5f; - const int stones = 11; + const int stones = 15; unsigned seed = 91u; MeshBuilder b; MeshBuilder_init(&b); @@ -453,10 +482,10 @@ Mesh Shapes_fireplace(float diameter) { shapes_addTube(&b, 0.0f, r * 0.8f, r * 0.75f, 0.04f, 14, COLOR_ASH, true); for (int i = 0; i < stones; i++) { float a = 2.0f * PI_F * ((float)i + 0.3f * scatter(&seed)) / (float)stones; - float s = 0.22f + 0.1f * scatter(&seed), h = 0.16f + 0.1f * scatter(&seed); - Vec3 c = vec3(cosf(a) * (r - s * 0.4f), h * 0.5f, sinf(a) * (r - s * 0.4f)); - Vec3 shade = vec3_scale(COLOR_STONE, 0.85f + 0.3f * scatter(&seed)); - addBlock(&b, c, vec3(s, h, s * 0.8f), shade); + float s = 0.2f + 0.1f * scatter(&seed), h = 0.15f + 0.09f * scatter(&seed); + Vec3 c = vec3(cosf(a) * (r - s * 0.4f), h * 0.4f, sinf(a) * (r - s * 0.4f)); + Vec3 shade = vec3_scale(COLOR_STONE, 0.8f + 0.35f * scatter(&seed)); + shapes_addRock(&b, c, vec3(s * 0.55f, h * 0.55f, s * 0.45f), &seed, shade); /* (field stones, not bricks) */ } return Mesh_fromBuilder(&b); } @@ -839,7 +868,20 @@ Mesh Shapes_furniture(FurnitureKind kind, Vec3 size) { shapes_addTube(&b, 0.22f * (float)k, size.x * 0.5f, size.x * 0.5f, 0.2f, 14, COLOR_TYRE, true); } break; - case FURN_TOILET: { /* a portable toilet: blue cabin, white roof, door at the front */ + case FURN_TOILET: + if (size.y < 1.2f) { /* a toilet in a house: the cistern at the back (z = -hz), the bowl and seat in front */ + const Vec3 white = { 0.93f, 0.93f, 0.90f }, seat = { 0.86f, 0.84f, 0.80f }; + addBlock(&b, vec3(0.0f, 0.62f, -hz + 0.09f), vec3(size.x * 0.95f, 0.36f, 0.17f), white); /* cistern */ + addBlock(&b, vec3(size.x * 0.25f, 0.82f, -hz + 0.09f), vec3(0.06f, 0.02f, 0.04f), COLOR_STEEL); /* flush button */ + size_t first = b.vertex_count; + shapes_addTube(&b, 0.0f, 0.13f, 0.17f, 0.38f, 12, white, true); /* bowl */ + transformSince(&b, first, mat4_mul(mat4_translate(vec3(0.0f, 0.0f, 0.06f)), mat4_scale(vec3(1.0f, 1.0f, 1.3f)))); + first = b.vertex_count; + shapes_addTube(&b, 0.38f, 0.18f, 0.18f, 0.025f, 12, seat, true); /* seat */ + transformSince(&b, first, mat4_mul(mat4_translate(vec3(0.0f, 0.0f, 0.06f)), mat4_scale(vec3(1.0f, 1.0f, 1.3f)))); + break; + } + { /* a portable toilet: blue cabin, white roof, door at the front */ const Vec3 blue = { 0.22f, 0.42f, 0.74f }, white = { 0.92f, 0.92f, 0.90f }; addBlock(&b, vec3(0.0f, size.y * 0.47f, 0.0f), vec3(size.x, size.y * 0.94f, size.z), blue); addBlock(&b, vec3(0.0f, size.y * 0.97f, 0.0f), vec3(size.x * 1.04f, size.y * 0.06f, size.z * 1.04f), white); diff --git a/entropie/src/world/chips.c b/entropie/src/world/chips.c index cb806e6..6053a54 100644 --- a/entropie/src/world/chips.c +++ b/entropie/src/world/chips.c @@ -8,14 +8,6 @@ #include "render/shapes.h" -static Mat4 mat4_scale(Vec3 s) { - Mat4 m = mat4_identity(); - m.m[0] = s.x; - m.m[5] = s.y; - m.m[10] = s.z; - return m; -} - #define CHIP_LIE 7.0f /* seconds a chip lies on the ground */ #define DUST_LIFE 1.3f /* seconds sawdust is in the air */ #define GRAVITY_C 9.81f diff --git a/entropie/src/world/manor.c b/entropie/src/world/manor.c index b868892..1282775 100644 --- a/entropie/src/world/manor.c +++ b/entropie/src/world/manor.c @@ -211,9 +211,7 @@ static void leftHalf(Plan *p) { lockedDoor(p, vec3(-7.2f, FL, CORR_Z0), FACE_PZ, 0.9f, "A locked room"); lockedDoor(p, vec3(-2.6f, FL, CORR_Z0), FACE_PZ, 0.9f, "Living room"); - /* The toilet: a bowl, a cistern, a little basin */ - box(p, vec3(-5.25f, FL, 3.6f), vec3(-4.85f, FL + 0.42f, 4.25f), PORCELAIN, SOLID); - box(p, vec3(-5.3f, FL + 0.42f, 4.25f), vec3(-4.8f, FL + 0.9f, 4.45f), PORCELAIN, LOOK_ONLY); + /* The toilet: a little basin (the toilet itself: see furnish) */ box(p, vec3(-4.2f, FL + 0.8f, 4.15f), vec3(-3.75f, FL + 0.95f, 4.6f), PORCELAIN, LOOK_ONLY); /* Windows: the front (behind your garages there are none), the back; basement windows low down */ @@ -396,6 +394,7 @@ static void furnish(Plan *p) { { FURN_CHAIR, { 11.0f, fl, -0.55f }, chair, FACE_PZ }, { FURN_CHAIR, { 6.85f, fl, 0.8f }, chair, FACE_PX }, { FURN_CHAIR, { 12.15f, fl, 0.8f }, chair, FACE_NX }, + { FURN_TOILET, { -5.05f, fl, 4.05f }, { 0.42f, 0.8f, 0.8f }, FACE_NZ }, /* the toilet: its cistern against the front wall */ { FURN_COUNTER, { -1.9f, fl, 4.4f }, { 3.0f, 0.9f, 0.6f }, FACE_NZ }, /* the kitchen: the counter along the front wall */ { FURN_STOVE, { -0.45f, fl, 2.6f }, { 0.6f, 0.9f, 0.6f }, FACE_NX }, /* ... the stove against the wall to the big room */ { FURN_SHELF, { -3.4f, fl, 2.3f }, shelf, FACE_PX }, /* ... a shelf against the toilet's wall */ @@ -415,6 +414,13 @@ static void furnish(Plan *p) { Object *o = Object_createFurniture(items[i].kind, toWorld(p, items[i].at), items[i].size, p->yaw + yaw_facing(items[i].face)); if (!o || !World_add(p->world, o)) p->ok = false; } + /* Urinellas in a little carton on the toilet's floor, by the wall */ + Object *urinellas = p->ok ? Object_create(OBJ_SUPPLY, PALETTE_GROUND, toWorld(p, vec3(-3.85f, fl + 0.09f, 3.2f)), vec3(0.3f, 0.18f, 0.2f), 100.0f) : NULL; + if (urinellas) { + urinellas->dispenses = OBJ_URINELLA; + Object_setRotation(urinellas, vec3(0.0f, p->yaw, 0.0f)); + if (!World_add(p->world, urinellas)) p->ok = false; + } else p->ok = false; } /* The ground goes down to the basement floor under it, and isn't drawn inside the diff --git a/entropie/src/world/object.c b/entropie/src/world/object.c index 1d2c750..f458518 100644 --- a/entropie/src/world/object.c +++ b/entropie/src/world/object.c @@ -163,6 +163,14 @@ Object *Object_create(ObjectType type, Palette palette, Vec3 position, Vec3 size addCollider(obj, vec3_zero(), vec3(size.x, size.y, size.x), vec3_zero()); break; + case OBJ_TOY: /* (weight picks which: see object.h) */ + obj->variant = (int)weight; + obj->weight = obj->variant == 0 ? 0.15f : 0.3f; + obj->mesh = Shapes_toy(obj->variant, size); + obj->pivot = centered; + addCollider(obj, vec3_zero(), obj->variant == 0 ? vec3(size.x, size.x, size.x) : vec3(size.x, size.y, size.y), vec3_zero()); + break; + case OBJ_APPLE: obj->mesh = Shapes_apple(size.x); obj->pivot = vec3(0.0f, size.x * 0.5f, 0.0f); @@ -584,6 +592,19 @@ float Object_vigor(const Object *obj) { return (0.5f + 0.5f * fminf(obj->needs.stamina / 0.3f, 1.0f)) * (1.0f - 0.25f * Object_tiredness(obj)); } +void Object_setHeight(Object *obj, float height) { + if (!obj->is_character || obj->collider_count == 0 || height < 0.5f) return; + float k = height / fmaxf(obj->size.y, 0.1f); + obj->size = vec3(obj->size.x * k, height, obj->size.z * k); + obj->colliders[0].half_extents = vec3_scale(obj->size, 0.5f); + obj->pivot = vec3(0.0f, height * 0.5f, 0.0f); + obj->step_height = height / 3.0f; + obj->look.height = height; + obj->top_speed *= sqrtf(k); + obj->jump_speed *= sqrtf(k); + Object_syncColliders(obj); +} + float Object_stepUpMost(const Object *obj) { const float s = obj->look.height / 1.75f; if (obj->type == OBJ_PLAYER) return 0.75f * s; /* (you decide yourself: as high as anyone, see physics.c STEP_UP_MOST) */ @@ -1195,6 +1216,7 @@ void Object_describe(const Object *obj, char *out, size_t size) { : "Ladder (slid together) - left click: slide it out, then lean it against something"); break; case OBJ_STONE: snprintf(out, size, kg >= 1 ? "A stone - %d kg" : "A pebble", kg); break; + case OBJ_TOY: snprintf(out, size, obj->variant == 0 ? "Alice's ball - throw it (T), she brings it back" : "Alice's rope toy - throw it (T), she loves to chew it"); break; case OBJ_CAN: if (obj->water > 0.05f) snprintf(out, size, "Watering can - %.0f l, left click (hold) to pour", obj->water); else snprintf(out, size, "Watering can - empty, fill it at the tap"); diff --git a/entropie/src/world/object.h b/entropie/src/world/object.h index 61b5a62..635551a 100644 --- a/entropie/src/world/object.h +++ b/entropie/src/world/object.h @@ -80,6 +80,7 @@ typedef enum { OBJ_WALL, /* static, invisible: a solid box of a building made of parts (walls, floors, steps); size = box, position = its bottom middle; variant 1: a locked door (name says which) */ OBJ_STRUCTURE, /* static: how a building made of parts looks (one mesh, no collision: see OBJ_WALL) */ + OBJ_TOY, /* Alice's toys: weight 0 = a ball (size.x across), 1 = a rope to bite (size.x long, size.y thick) */ } ObjectType; /* What a body needs over the day. All 0..1; 0 is fine (stamina: 1 is fine). */ @@ -378,6 +379,9 @@ float Object_vigor(const Object *obj); /* How high (m) someone walks up onto something solid (a block, a crate): the sporty and fresh higher, the unfit, worn out or drunk lower; higher than that they go round. */ float Object_stepUpMost(const Object *obj); +/* A person this tall (m): their body box, how high they step, and how fast their legs carry them + (longer legs: a little quicker) - everything else scales from look.height. */ +void Object_setHeight(Object *obj, float height); /* Worn down by the day (sleepiness, felt by personality, fitness and drink): 0 fresh .. 1 done in. */ float Object_tiredness(const Object *obj); /* Characters: how fast they can sprint and jump right now (tired or drunk: less). */ diff --git a/entropie/src/world/physics.c b/entropie/src/world/physics.c index b15effe..b901075 100644 --- a/entropie/src/world/physics.c +++ b/entropie/src/world/physics.c @@ -412,6 +412,8 @@ static bool rolls(const Object *o) { switch (o->type) { case OBJ_APPLE: return true; + case OBJ_TOY: + return o->variant == 0; /* (the ball) */ case OBJ_BOTTLE: case OBJ_VODKA: case OBJ_WATER: case OBJ_LOG: case OBJ_CYLINDER: /* (round: lying on its side, not standing) */ return o->collider_count > 0 && fabsf(o->colliders[0].axes[1].y) < 0.5f; default: diff --git a/entropie/src/world/scene.c b/entropie/src/world/scene.c index c8f1deb..83b5f2f 100644 --- a/entropie/src/world/scene.c +++ b/entropie/src/world/scene.c @@ -30,47 +30,44 @@ static const Spawn SCENE[] = { { OBJ_HUMAN, PALETTE_MODERN, { -3.0f, 1, -6 }, HUMAN_SIZE, 76, { 0, 150, 0 } }, /* Jonas */ /* Props (the ground itself is the terrain, see terrain.c) */ - { OBJ_BOX_STATIC, PALETTE_WARM, { -8, 0, 1 }, { 1, 1, 1 }, 20, NO_ROTATION }, { OBJ_TREE, PALETTE_WARM, { 3, 0, 2 }, { 3.0f, 3.0f, 0.0f }, 0, NO_ROTATION }, /* canopy, trunk */ - /* Things to carry: one hand (small) or both hands (medium crate) */ - { OBJ_BOX, PALETTE_WARM, { 0.0f, 0.5f, -2.0f }, { 0.40f, 0.40f, 0.40f }, 6, NO_ROTATION }, - { OBJ_BOX, PALETTE_MODERN, { 1.2f, 0.5f, -2.2f }, { 0.35f, 0.35f, 0.35f }, 5, { 0, 30, 0 } }, - { OBJ_CYLINDER, PALETTE_MODERN, { 2.0f, 0.5f, -1.8f }, { 0.30f, 0.45f, 0.30f }, 4, NO_ROTATION }, - { OBJ_BOX, PALETTE_STANDARD, { 0.5f, 0.5f, -5.5f }, { 0.80f, 0.60f, 0.60f }, 20, NO_ROTATION }, - /* A few bottles of beer (R drinks) */ { OBJ_BOTTLE, PALETTE_WARM, { -0.6f, 0.3f, -1.2f }, BOTTLE_SIZE, 0.6f, NO_ROTATION }, { OBJ_BOTTLE, PALETTE_WARM, { -0.4f, 0.3f, -1.3f }, BOTTLE_SIZE, 0.6f, NO_ROTATION }, { OBJ_BOTTLE, PALETTE_WARM, { -0.5f, 0.3f, -1.0f }, BOTTLE_SIZE, 0.6f, NO_ROTATION }, - { OBJ_BOTTLE, PALETTE_WARM, { -7.9f, 1.2f, 1.0f }, BOTTLE_SIZE, 0.6f, NO_ROTATION }, /* on the box */ { OBJ_BOTTLE, PALETTE_WARM, { 4.0f, 0.3f, -3.0f }, BOTTLE_SIZE, 0.6f, NO_ROTATION }, { OBJ_BOTTLE, PALETTE_WARM, { -2.0f, 0.3f, -6.0f }, BOTTLE_SIZE, 0.6f, NO_ROTATION }, /* ... and vodka, for getting there faster */ { OBJ_VODKA, PALETTE_WARM, { -0.8f, 0.3f, -1.5f }, VODKA_SIZE, 0.9f, NO_ROTATION }, - { OBJ_VODKA, PALETTE_WARM, { -7.7f, 1.2f, 1.2f }, VODKA_SIZE, 0.9f, NO_ROTATION }, /* on the box */ - /* Pushable objects */ - { OBJ_CYLINDER, PALETTE_WARM, { -3, 0, 1 }, { 1, 3, 1 }, 1200, { 90, 0, 0 } }, /* takes two or more */ - { OBJ_CYLINDER, PALETTE_MODERN, { -3, 0, 3 }, { 1, 3, 1 }, 100, NO_ROTATION }, - { OBJ_BOX, PALETTE_MODERN, { 1, 0, 2 }, { 2, 1, 2 }, 20, NO_ROTATION }, - /* (the tools are in the tool room of your garages, see site.c) */ /* Food and water: an apple crate and a water barrel that never run out, and a few to take along */ - { OBJ_SUPPLY, PALETTE_WARM, { -4.5f, 0.0f, -1.5f }, { 0.8f, 0.5f, 0.6f }, 0, { 0, 20, 0 } }, - { OBJ_SUPPLY, PALETTE_MODERN, { -5.6f, 0.0f, -0.4f }, { 0.6f, 0.9f, 0.0f }, 0, NO_ROTATION }, { OBJ_WATER, PALETTE_WARM, { 6.5f, 0.3f, -1.0f }, BOTTLE_SIZE, 0.6f, NO_ROTATION }, { OBJ_WATER, PALETTE_WARM, { 6.7f, 0.3f, -1.3f }, BOTTLE_SIZE, 0.6f, NO_ROTATION }, { OBJ_APPLE, PALETTE_WARM, { 2.2f, 0.2f, 1.2f }, { 0.08f, 0.08f, 0.08f }, 0.2f, NO_ROTATION }, { OBJ_APPLE, PALETTE_WARM, { 3.9f, 0.2f, 1.4f }, { 0.08f, 0.08f, 0.08f }, 0.2f, NO_ROTATION }, { OBJ_APPLE, PALETTE_WARM, { 2.6f, 0.2f, 3.1f }, { 0.08f, 0.08f, 0.08f }, 0.2f, NO_ROTATION }, - { OBJ_BREAD, PALETTE_WARM, { -8.2f, 1.2f, 0.8f }, { 0.26f, 0.12f, 0.12f }, 0.4f, NO_ROTATION }, /* on the box */ /* A log to cut and split */ { OBJ_LOG, PALETTE_WARM, { 5.8f, -0.25f, 0.2f }, { 0.36f, 1.4f, 0.0f }, 0, { 90, 20, 0 } }, }; +/* Things to try things out with (carrying, pushing, lifting together), out + of the way beside the obstacle course: positions from its test corner. */ +static const Spawn TESTING[] = { + /* Things to carry: one hand (small) or both hands (medium crate) */ + { OBJ_BOX, PALETTE_WARM, { 0.0f, 0.5f, -2.0f }, { 0.40f, 0.40f, 0.40f }, 6, NO_ROTATION }, + { OBJ_BOX, PALETTE_MODERN, { 1.2f, 0.5f, -2.2f }, { 0.35f, 0.35f, 0.35f }, 5, { 0, 30, 0 } }, + { OBJ_CYLINDER, PALETTE_MODERN, { 2.0f, 0.5f, -1.8f }, { 0.30f, 0.45f, 0.30f }, 4, NO_ROTATION }, + { OBJ_BOX, PALETTE_STANDARD, { 0.5f, 0.5f, -5.5f }, { 0.80f, 0.60f, 0.60f }, 20, NO_ROTATION }, + /* Pushable objects */ + { OBJ_CYLINDER, PALETTE_WARM, { -3, 0, 1 }, { 1, 3, 1 }, 1200, { 90, 0, 0 } }, /* takes two or more */ + { OBJ_CYLINDER, PALETTE_MODERN, { -3, 0, 4 }, { 1, 3, 1 }, 100, NO_ROTATION }, + { OBJ_BOX, PALETTE_MODERN, { 1, 0, 2 }, { 2, 1, 2 }, 20, NO_ROTATION }, +}; + /* Who's here, in spawn order: you; Volkram, Jana and Toni (your friends, here with you); Kurti (an old man, the house is his) and his sons Martin (about 30) and Jonas (about 25). */ @@ -154,15 +151,25 @@ static const Character CHARACTERS[] = { 1.15f, 0.4f, BREAK_CHAT, 80.0f, 30.0f }, /* keen, helpful, a chat in between */ }; +/* Where the things to try out stand: beside the obstacle course, left of its lanes. */ +#define TEST_ACROSS -9.5f /* m across from the middle lane (the lanes are 3.5 apart) */ +#define TEST_ALONG 6.0f /* m down from its start */ +static Vec3 testCorner(void) { + Vec3 start, along; + Site_courseFrame(&start, &along); + Vec3 across = vec3(-along.z, 0.0f, along.x); + return vec3_add(start, vec3_add(vec3_scale(across, TEST_ACROSS), vec3_scale(along, TEST_ALONG))); +} + /* A ramp to walk up: a plank 20 degrees up, its foot on the ground, its top resting on a block (a loose box left tilted would hang in the air: loose things don't turn in the physics). */ -#define RAMP_AT { 2.0f, 0.0f, 8.0f } +#define RAMP_AT { 4.0f, 0.0f, 8.0f } #define RAMP_SIZE { 1.2f, 0.15f, 3.2f } #define RAMP_SLOPE 20.0f -static bool addRamp(World *world, Vec3 yard) { - const Vec3 at = vec3_add(yard, (Vec3)RAMP_AT); +static bool addRamp(World *world, Vec3 corner) { + const Vec3 at = vec3_add(corner, (Vec3)RAMP_AT); Object *ramp = Object_create(OBJ_BOX_STATIC, PALETTE_MODERN, at, (Vec3)RAMP_SIZE, 100.0f); if (!ramp) return false; Object_setRotation(ramp, vec3(deg2rad(RAMP_SLOPE), 0.0f, 0.0f)); @@ -200,7 +207,7 @@ bool Scene_finish(World *world) { if (world->objects[i]->type == OBJ_WOODPILE) pile = world->objects[i]; for (int i = 0; i < world->count && pile; i++) if (world->objects[i]->type == OBJ_HUMAN) Ai_setWoodpile(world->objects[i], pile); - return addRamp(world, Site_yard()); + return addRamp(world, testCorner()); } Object *Scene_load(World *world) { @@ -240,7 +247,7 @@ Object *Scene_load(World *world) { obj->wits = WITS[characters % TEMPERAMENT_COUNT]; obj->height_fear = HEIGHT_FEAR[characters % TEMPERAMENT_COUNT]; obj->worn = YESTERDAY[characters % TEMPERAMENT_COUNT]; - obj->jacket_hung = true; /* (on the rack in your garage) */ + obj->jacket_hung = false; /* (with them: tied round the waist till it gets cold) */ obj->cigarettes = CIGARETTES[characters % TEMPERAMENT_COUNT]; obj->tobacco = TOBACCO[characters % TEMPERAMENT_COUNT]; obj->papers = PAPERS[characters % TEMPERAMENT_COUNT]; @@ -251,6 +258,7 @@ Object *Scene_load(World *world) { obj->craving = 0.3f + 0.15f * (float)(characters % 3); for (int k = 0; k < ORDER_COUNT; k++) obj->skill[k] = obj->ai.character.skill[k]; obj->tempo = obj->ai.character.tempo; + Object_setHeight(obj, obj->look.height); /* (the body box their own size: 1.66 .. 1.86 m) */ float years = clampf(1.0f - (float)(AGE[characters % TEMPERAMENT_COUNT] - 45) / 60.0f, 0.5f, 1.0f); /* (old legs) */ obj->top_speed *= years; obj->jump_speed *= years; @@ -260,6 +268,16 @@ Object *Scene_load(World *world) { if (!World_add(world, obj)) return NULL; if (s->type == OBJ_PLAYER && !player) player = obj; } + /* The things to try out, by the obstacle course */ + const Vec3 corner = testCorner(); + for (size_t i = 0; i < SDL_arraysize(TESTING); i++) { + const Spawn *s = &TESTING[i]; + Object *obj = Object_create(s->type, s->palette, vec3_add(s->position, corner), s->size, s->weight); + if (!obj) return NULL; + Vec3 r = s->rotation_deg; + if (r.x != 0.0f || r.y != 0.0f || r.z != 0.0f) Object_setRotation(obj, vec3(deg2rad(r.x), deg2rad(r.y), deg2rad(r.z))); + if (!World_add(world, obj)) return NULL; + } /* Everything else: the farm, your buildings, the forest */ return Site_build(world) ? player : NULL; } diff --git a/entropie/src/world/site.c b/entropie/src/world/site.c index 6837e5f..3b81b87 100644 --- a/entropie/src/world/site.c +++ b/entropie/src/world/site.c @@ -10,7 +10,6 @@ /* A point on the traced map, in pixels (x right, y down). */ typedef struct { float x, y; } Px; -#define SMOKES_FROM_TAP 2.65f /* m along the wall from the tap, past the door: the cigarette machine */ #define MAP_CENTER_X 400.0f #define MAP_CENTER_Y 385.0f #define COUNT(a) ((int)(sizeof(a) / sizeof((a)[0]))) @@ -715,7 +714,6 @@ static const RoomPiece ROOMS[] = { { FURN_TABLE, { 23.3f, 0, 4.0f }, { 1.6f, 0.75f, 0.9f }, 0.0f }, { FURN_CHAIR, { 22.5f, 0, 4.0f }, { 0.45f, 0.9f, 0.45f }, PI_F }, { FURN_CHAIR, { 24.1f, 0, 4.0f }, { 0.45f, 0.9f, 0.45f }, 0.0f }, - { FURN_COATRACK, { 20.6f, 0, 2.4f }, { 0.5f, 1.75f, 0.5f }, 0.0f }, /* everybody's jacket */ /* 3: car repairs (the wide door) */ { FURN_CAR, { 23.4f, 0, -11.2f }, { 1.8f, 1.5f, 4.2f }, 0.0f }, { FURN_WORKBENCH, { 26.6f, 0, -6.6f }, { 2.4f, 0.9f, 0.7f }, 0.0f }, @@ -840,7 +838,6 @@ static bool plantOrchard(World *world) { static bool furnishYard(World *world) { Vec3 fire = court(FIRE_A, FIRE_B); if (!addStatic(world, OBJ_FIREPLACE, PALETTE_WARM, fire, vec3(FIRE_SIZE, 0.3f, FIRE_SIZE), 0.0f) || - !addStatic(world, OBJ_SUPPLY, PALETTE_STANDARD, court(FIRE_A - 3.1f, FIRE_B + 2.6f), vec3(0.6f, 0.45f, 0.4f), 0.3f) || !addStatic(world, OBJ_BIN, PALETTE_STANDARD, court(15.0f, 20.0f), vec3(1.8f, 1.4f, 1.2f), yaw_along(mapDir(COURT_ANGLE))) || !addStatic(world, OBJ_SUPPLY, PALETTE_GROUND, court(17.2f, 20.0f), vec3(0.45f, 0.3f, 0.3f), 0.0f)) return false; @@ -871,11 +868,11 @@ static bool furnishYard(World *world) { Object *bench = Object_createFurniture(FURN_BENCH, court(FIRE_A + BENCH_RADIUS * cosf(a), FIRE_B + BENCH_RADIUS * sinf(a)), vec3(1.8f, SEAT_HEIGHT, 0.36f), yaw_along(tangent)); if (!bench || !World_add(world, bench)) return false; - } - { /* an old tin as ashtray, on the cool box by the fire (on the ground it was in everybody's way) */ - Vec3 on_box = vec3_add(court(FIRE_A - 3.1f, FIRE_B + 2.6f), vec3(0.0f, 0.45f, 0.0f)); - Object *tin = Object_createFurniture(FURN_ASHTRAY, on_box, vec3(0.1f, 0.12f, 0.1f), 0.0f); - if (!tin || !World_add(world, tin)) return false; + if (k == 0) { /* an old tin as ashtray, on the end of a bench (on the ground it was in everybody's way) */ + Vec3 on_end = vec3_add(vec3_add(bench->position, vec3_scale(tangent, 0.78f)), vec3(0.0f, SEAT_HEIGHT, 0.0f)); + Object *tin = Object_createFurniture(FURN_ASHTRAY, on_end, vec3(0.1f, 0.12f, 0.1f), 0.0f); + if (!tin || !World_add(world, tin)) return false; + } } for (int k = 0; k < 4; k++) { /* firewood quarters and dry branches */ Vec3 at_pos = court(FIRE_A + 2.3f, FIRE_B + 0.5f + 0.35f * (float)k); @@ -987,14 +984,10 @@ bool Site_build(World *world) { return false; if (!buildGarages(world) || !plantOrchard(world) || !furnishYard(world)) return false; - /* The tap left of the main door, the garden hose and a watering can; a toilet by the barn */ + /* The tap left of the main door, the garden hose and a watering can (the toilet is in the house) */ const float across_yaw = yaw_along(mapDir(COURT_ANGLE - 90.0f)); Object *tap = Object_createFurniture(FURN_TAP, court(6.15f, -9.75f), vec3(0.3f, 1.0f, 0.3f), across_yaw); if (!tap || !World_add(world, tap)) return false; - /* An old cigarette machine on the wall further along */ - Vec3 tap_front = tap->colliders[0].axes[2], along_wall = vec3(-tap_front.z, 0.0f, tap_front.x); - Object *smokes = Object_createFurniture(FURN_SMOKES, vec3_add(tap->position, vec3_scale(along_wall, -SMOKES_FROM_TAP)), vec3(0.5f, 1.75f, 0.25f), across_yaw); - if (!smokes || !World_add(world, smokes)) return false; Object *hose = Object_create(OBJ_HOSE, PALETTE_WARM, vec3_add(court(7.0f, -9.4f), vec3(0.0f, 0.1f, 0.0f)), vec3_zero(), 0.6f); if (!hose) return false; hose->hose_tap = tap; @@ -1002,20 +995,16 @@ bool Site_build(World *world) { if (!World_add(world, hose)) return false; Object *can = Object_create(OBJ_CAN, PALETTE_WARM, vec3_add(court(6.6f, -10.1f), vec3(0.0f, 0.2f, 0.0f)), vec3_zero(), 1.5f); if (!can || !World_add(world, can)) return false; - Object *toilet = Object_createFurniture(FURN_TOILET, court(36.8f, -4.3f), vec3(1.1f, 2.3f, 1.1f), - yaw_along(mapDir(COURT_ANGLE + 90.0f))); - if (!toilet || !World_add(world, toilet)) return false; - Object *urinellas = Object_create(OBJ_SUPPLY, PALETTE_GROUND, court(37.75f, -4.6f), vec3(0.3f, 0.18f, 0.2f), 100.0f); - if (!urinellas) return false; - urinellas->dispenses = OBJ_URINELLA; /* (a little carton beside the toilet) */ - Object_setRotation(urinellas, vec3(0.0f, yaw_along(mapDir(COURT_ANGLE + 90.0f)), 0.0f)); - if (!World_add(world, urinellas)) return false; /* Alice, the dachshund */ Object *alice = Object_create(OBJ_DOG, PALETTE_WARM, court(YARD_A - 2.0f, YARD_B - 4.0f), vec3(0.2f, 0.35f, 0.7f), 9.0f); if (!alice) return false; snprintf(alice->name, sizeof(alice->name), "Alice"); if (!World_add(world, alice)) return false; + /* ... and her toys: a ball and a rope to bite */ + Object *ball = Object_create(OBJ_TOY, PALETTE_WARM, vec3_add(court(YARD_A - 1.2f, YARD_B - 4.6f), vec3(0.0f, 0.1f, 0.0f)), vec3(0.07f, 0.07f, 0.07f), 0.0f); + Object *rope = Object_create(OBJ_TOY, PALETTE_WARM, vec3_add(court(YARD_A - 2.6f, YARD_B - 3.2f), vec3(0.0f, 0.1f, 0.0f)), vec3(0.3f, 0.06f, 0.06f), 1.0f); + if (!ball || !World_add(world, ball) || !rope || !World_add(world, rope)) return false; /* Family houses, and the farm */ for (int i = 0; i < COUNT(HOUSES); i++) { diff --git a/entropie/src/world/world.c b/entropie/src/world/world.c index e40209c..069d214 100644 --- a/entropie/src/world/world.c +++ b/entropie/src/world/world.c @@ -801,6 +801,8 @@ static void showProps(World *world) { } } +#define ROLL_ON 3.0f /* m/s on through a shoulder roll (scaled) */ + /* Down from high up: into the knees, deeper, a roll or a crash (see Body_land). */ static void landFromHigh(World *world, Object *o) { float speed = o->air_fall; @@ -1236,6 +1238,14 @@ void World_update(World *world, float dt, float game_dt) { } } + for (int i = 0; i < world->count; i++) { /* rolling over a shoulder: on the way it was going, whatever anyone wants */ + Object *o = world->objects[i]; + if (!o->is_character || !Body_isRolling(&o->body)) continue; + float t = o->body.shoulder_roll; + float v = ROLL_ON * (0.4f + 0.6f * smoothstepf(0.0f, 0.2f, t)) * (1.0f - 0.7f * smoothstepf(0.75f, 1.0f, t)); + o->movement = vec3_scale(o->body.roll_dir, v * o->look.height / 1.75f); + o->move_local = vec3(0.0f, 0.0f, 1.0f); + } Physics_useGrid(&world->grid); for (int i = 0; i < world->count; i++) { Object *o = world->objects[i]; @@ -1440,7 +1450,8 @@ void World_draw(World *world, Renderer *renderer, bool draw_player) { Renderer_drawMesh(renderer, &obj->mesh, Object_modelMatrix(obj)); if (obj->type == OBJ_FIREPLACE && obj->grill_mesh.vao) { /* the grate swings a little on its chain */ float t = renderer->boil_time; - Mat4 swing = mat4_mul(mat4_rotate_x(0.035f * sinf(t * 1.3f)), mat4_rotate_z(0.03f * sinf(t * 0.9f + 1.0f))); + const float sway = 1.35f / GRILL_DROP; /* (a long chain: slower, the grate goes as far) */ + Mat4 swing = mat4_mul(mat4_rotate_x(0.035f * sway * sinf(t * 1.3f * sqrtf(sway))), mat4_rotate_z(0.03f * sway * sinf(t * 0.9f * sqrtf(sway) + 1.0f))); Renderer_drawMesh(renderer, &obj->grill_mesh, mat4_mul(mat4_translate(vec3_add(obj->position, vec3(0.0f, GRILL_TOP, 0.0f))), swing)); }