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 1.93.0: Alice painted like the people, one roof, chairs up to the table
- Alice is drawn in the same painted style as the people (smooth body, floppy ears, paws, wagging tail, her shadow too) - Chairs: once you sit, the chair is shuffled in to the table; getting up it goes out again first - E / Q next to a table, counter or crate sets the thing on top of it - Dropped bottles nearly always fall over; things on a slope slide or roll down it when steep enough, and keep going more easily once pushed - Felling: the cut at about hip height (lower if you look lower); the trunk slides off the stump and lies on the ground (it hung in the air) - Chopping: more hips, back and head in the swing - Main building: one merged roof (the left ridge runs into the right roof); the ground no longer shows through the floors; the basement landing is deeper, the stairs room wider, no lintel hanging in the side opening - Roofs are closed underneath: no looking through them from below - Your unfinished building: 2 m longer towards the frame, the roof turned, the door on the woodpile side - Garage gates swing right round and lie flat against the wall - No climbing through a wall onto something behind it
entropie/Makefile | 2 +- entropie/README.md | 26 ++++++++------ entropie/STATE.md | 20 +++++++++-- entropie/assets/shaders/figure.frag | 44 +++++++++++++++++++++-- entropie/assets/shaders/scene.frag | 4 +-- entropie/commit_message.txt | 38 ++++++++++---------- entropie/src/people/body_internal.h | 4 +++ entropie/src/people/body_pose.c | 13 +++++-- entropie/src/people/climb.c | 13 +++++++ entropie/src/people/dog.c | 69 +++++++++++++++++++++++++++---------- entropie/src/people/hands.c | 57 ++++++++++++++++++++++++++++++ entropie/src/render/figure.h | 4 +++ entropie/src/render/renderer.c | 6 ++++ entropie/src/render/renderer.h | 3 +- entropie/src/render/shapes.c | 35 ++++++++++++++++--- entropie/src/render/shapes.h | 8 ++--- entropie/src/render/shapes_props.c | 15 -------- entropie/src/world/forestry.c | 14 ++++---- entropie/src/world/manor.c | 58 ++++++++++++++++++------------- entropie/src/world/object.c | 21 +++++++---- entropie/src/world/object.h | 8 ++++- entropie/src/world/physics.c | 27 +++++++++++++-- entropie/src/world/site.c | 26 +++++++------- entropie/src/world/terrain.h | 2 +- entropie/src/world/tools.c | 3 +- entropie/src/world/world.c | 45 +++++++++++++++++++++++- 26 files changed, 428 insertions(+), 137 deletions(-) diff --git a/entropie/Makefile b/entropie/Makefile index 0e2ae0e..d612aa4 100644 --- a/entropie/Makefile +++ b/entropie/Makefile @@ -1,5 +1,5 @@ APP := entropie -VERSION := 1.92.0 +VERSION := 1.93.0 PREFIX ?= /usr BINDIR ?= $(PREFIX)/bin diff --git a/entropie/README.md b/entropie/README.md index 96e5945..e988ab3 100644 --- a/entropie/README.md +++ b/entropie/README.md @@ -36,9 +36,9 @@ and satellite pictures (about 340 x 340 m). 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 barn's right end -- The main building has two halves side by side, the right one a little - further out (its roof ridge runs front to back), with a porch along the - right part of its front. The ground floor sits four steps up. Walk in: +- The main building has two halves side by side under one roof (the left + ridge runs into the right half's, which runs front to back), the right one + a little further out at the front, with a porch along the right part of its front. The ground floor sits four steps up. Walk in: - Left door, by your garages: four steps up on the left, stairs down to the basement on the right. Along the corridor: the toilet and the kitchen on the right, two locked rooms on the left (the second the living room), and at @@ -46,8 +46,10 @@ and satellite pictures (about 340 x 340 m). - The basement: old loose brick walls, open rooms, shelves - Or in at the porch's far end, up its steps and right through the door - Your unfinished building: a bare brick shell under a roof (later: drinks), - and in front of it the frame of tree trunks where the bar will be built -- Your garages' doorways have green wooden gates: click to open or shut + its door on the side towards the woodpile, and in front of it the frame of + tree trunks where the bar will be built +- Your garages' doorways have green wooden gates: click to open or shut; open + 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 @@ -56,7 +58,7 @@ and satellite pictures (about 340 x 340 m). 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 -- Alice the dachshund lives here: she trots after you, sniffs around, begs +- 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) - A little path leads out between the garages and the barn - Across the gravel road: the orchard (two apple trees, bushes all round), @@ -99,7 +101,8 @@ and satellite pictures (about 340 x 340 m). 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 - C: sit down yourself (and rest faster): on a bench or chair (right on - the seat; a chair is pulled out first and pushed back in after) if one is next + the seat; a chair is pulled out first, shuffled in to the table once you sit, + and pushed back in after you leave) if one is next to you, else on a log lying there, else cross-legged on the ground; walking gets you up @@ -155,6 +158,8 @@ and satellite pictures (about 340 x 340 m). - Roofs are walkable; colliders are built from the same sizes as the meshes - Bottles lie around: water, beer (five sips) and much stronger vodka - Click a bottle: take it, click again to drink (hold to keep drinking). + Dropped, a bottle nearly always falls over; things on a slope slide or + roll down it when it's steep enough, and once going they keep going easier Empty, a click puts it away: into the container in front, onto a table or crate in reach, with other empties, down at your feet, or (drunk or wild) tossed away. Others do the same their way @@ -327,8 +332,9 @@ An axe, pruning shears and a chainsaw are in the tool room of your garages; the | Split a short log or half | 3 | – | – | – | | Chop out a stump | 14 | 7 | – | – | -A tree is cut where you started its notch; strokes elsewhere start a new -notch from scratch. +A tree is cut at about hip height (lower if you look lower), where you started +its notch; strokes elsewhere start a new notch from scratch. It falls beside its +stump and lies on the ground. Numbers are strokes for someone fresh. Tired or drunk takes more strokes; people who like the job need fewer, and practice makes everyone better at @@ -561,7 +567,7 @@ say no more often and don't come to you for a bit. | X | Talk to who's in front; else shout for someone | | 1 - 5 | Answer when talking / pick from the shout menu | | Right click | Put down / let go | -| E / Q | Take / drop with right / left hand | +| E / Q | Take / drop with right / left hand (by a table or crate: set it on top) | | F (hold) | Push / pull (walk forward / back); look left / right to turn it | | Ctrl (hold) | Look around; holding on: without turning it (the view swings back after) | | E / Q while holding on | Lift together / put down | diff --git a/entropie/STATE.md b/entropie/STATE.md index fa5b9de..5b64106 100644 --- a/entropie/STATE.md +++ b/entropie/STATE.md @@ -104,6 +104,19 @@ Round 1.92: - Worn from the days before (`worn`, condition.c): + WORN_PER_EFFORT 0.012 per stamina used, - WORN_SLEEP_HOUR 0.075 per game hour asleep; adds up to WORN_FEEL 0.6 to `Object_tiredness`. Tab shows it ("Worn (days)"). - Woodpile: "Find a good place for it" (instead of "Just pile it up here"): a woodpile within 40 m of the work, else the best spot 3-9 m round it (clear of things, level, off roads, by something big and fixed); they think it over first. +Round 1.93: +- Chairs: sat down (body.sit 0.85+), the chair shuffles in towards the table (`world.c` scootTo: a ray from just over the seat; the table's edge SEAT_TO_TABLE 0.3 m from the hips, at most 0.15 m past where it stood) and takes the sitter along (slideChair moves whoever has it as `seat`); getting up it goes out to CHAIR_PULL first (`seat_scooted`), then you step off. +- Putting down (E / Q, `hands.c` placeOnSurface): onto the highest level top 0.3-1.5 m over the feet, under the hand or 0.45-0.85 m ahead (static, or loose things 20 kg+; it must fit), upright (tools lie flat). Not on such a top: a bottle gets `drop_tip` and falls over when it lands (DROP_TIPS 0.85). +- Slopes: loose things slide down the ground too (resolveTerrain, terrain normal from 4 heights); round only when lying (`rolls`: bottles, logs on their side; apples always); moving, they grip GRIP_MOVING 0.6 as much (a nudge sets them going). Not ladders, nor leaning things. +- Felling (`tools.c`): the cut at the tool's height (axe 0.95 m: hip) unless you look lower. The trunk falls about the cut and, as it comes down, its butt slides off the stump (`fall_slide`: the stump's and its own radius) to the ground there (`fall_ground_y`, set in `forestry.c` fell); the old double drop in Forestry_update is gone. +- Swinging two-handed (`body_pose.c`): shoulders SWING_CHEST 0.8, hips SWING_HIPS 0.3, head SWING_HEAD 0.3 with the swing; the back arches winding up and bends into the hit (SWING_ARCH 0.15). +- Gates (`object.c` Object_swingGate): open right round (to 177 deg, GATE_SWING_TIME 1.8 s), hung on the outside of the wall (GATE_HINGE_X 18.94), lying flat against it. +- Climbing (`climb.c`): a ledge only if nothing fixed is between, from 0.5 m over it at the body down to 0.05 m over it at the hands (`onlyWalls`): no more climbing through a wall onto what's behind it. +- Main building: one roof (the left one runs on into the right one: `ShapeRoof.valley`, two valley triangles and the ridge cap, no eaves there; both dark); the right half's back in line with the left (RIGHT_Z0 -5). The ground isn't drawn under any floor (pits for both halves and the porch at -0.06, after the basement's: TERRAIN_PITS 8). Basement: floor at -2.45, 14 steps of 0.175 x 0.23 from a landing 0.8 m deep (WELL_TOP_Z 3.9), the side opening up to the ceiling (no lintel). +- Roofs (`shapes.c`): the whole underside of a tiled slope, and the gables from inside: no looking through a roof from below. +- The shell (`manor.c` Shell_build): 6.4 x 6.7 m (2 m longer towards the frame, which moved out 2 m), the ridge front to back, the door (1.6 m) on its -x side towards the woodpile, windows; the front closed but for a window. +- Alice is a painted figure (`figure.frag` species 1, buildDog: body, neck, skull, snout, nose, floppy ears, legs with paws, tail; her eyes painted; `dog.c` Dog_draw sets her joints: legs trot in diagonal pairs, lying stretched, nose down sniffing, tail wagging); her shadow the same way. Shapes_dogLeg / dogTail are gone. + 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. @@ -237,8 +250,9 @@ Most important first. **From the 15th of October (to do)** - Trees: push / pull fallen trees; branches break or snag on obstacles (with enough momentum); much easier to carry without branches -- Alice: doesn't fit the art style, and dumb; she tries to catch things thrown, eats a sausage dropped on the ground, may catch a thrown apple (and not bring it back). NPC pathfinding has problems -- NPCs sitting at the meeting table (chairs work for them, nothing sends them there yet) +- Alice: dumb; she tries to catch things thrown, eats a sausage dropped on the ground, may catch a thrown apple (and not bring it back). NPC pathfinding has problems +- NPCs sitting at the meeting table (chairs work for them, nothing sends them there yet); eating at a table +- Felling: aiming could set the cut height more freely **Body and moving** - Obstacle approach, still to do: NPCs stepping up onto higher things (now only what they'd stride over anyway); thin round logs (under 0.2 m) are still stepped over; a step in between on top of something unstable (if clumsy); NPCs: pull up on their own for their own reasons (now only following a leader), jumping onto things @@ -261,4 +275,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 1.92.0 (build 9eb26b9)` +State of `entropie 1.93.0 (build ac550b2)` diff --git a/entropie/assets/shaders/figure.frag b/entropie/assets/shaders/figure.frag index 8c543cd..57f49e4 100644 --- a/entropie/assets/shaders/figure.frag +++ b/entropie/assets/shaders/figure.frag @@ -44,6 +44,8 @@ uniform vec3 cigA, cigTip; // a cigarette (figure space): the end at the hand / uniform float cigGlow; uniform vec3 uriA, uriB; // a Urinella held to the body (figure space) uniform bool uri; // -1 none, 0 not lit .. 1 a drag +uniform int species; // 0 a person, 1 the dog (the joints mean her body, see dog.c) +uniform vec3 tailA, tailB; // the dog's tail uniform int top; // 0 a shirt, 1 bare-chested, 2 just a top (shirt off in the sun), 3 a jacket (cold night) uniform mat4 viewProj; @@ -60,6 +62,7 @@ uniform float boilTime; uniform sampler2D shadowMap; const int M_SKIN = 0, M_HAIR = 1, M_SHIRT = 2, M_PANTS = 3, M_SHOES = 4, M_PEE = 5, M_SICK = 6, M_WATER = 7, M_CIG = 8, M_JACKET = 9, M_URINELLA = 10; +const int M_FUR = 11, M_FUR_DARK = 12, M_NOSE = 13; const float FIGURE_LIGHT_WRAP = 0.6; // soft, flat faces instead of a hard light split const float SELF_SHADOW_SKIP = 0.10; // meters @@ -90,7 +93,35 @@ void addPart(vec3 a, vec3 b, float r1, float r2, float blend, float depth, int m partCount++; } +// The dog: a long low body, short legs, a long head with floppy ears, a thin tail. +// Joints: pelvis = her hindquarters, chest = her chest, neck, head; the hind legs in +// the leg slots (hip, knee, ankle, toe), the front legs in the arm slots. +void buildDog() { + vec3 hind = joints[J_PELVIS], fore = joints[J_CHEST], neck = joints[J_NECK]; + addPart(hind, fore, 0.075, 0.085, 0.04, 1.0, M_FUR); // the long body + addPart(fore + vec3(0.0, 0.01, 0.03), neck, 0.060, 0.048, 0.04, 1.0, M_FUR); // neck + addPart(onHead(vec3(0.0, 0.005, -0.01)), onHead(vec3(0.0, 0.0, 0.02)), 0.058, 0.055, 0.03, 1.0, M_FUR); // skull + addPart(onHead(vec3(0.0, -0.005, 0.04)), onHead(vec3(0.0, -0.022, 0.125)), 0.040, 0.024, 0.025, 1.0, M_FUR); // snout + addPart(onHead(vec3(0.0, -0.016, 0.142)), onHead(vec3(0.0, -0.016, 0.142)), 0.015, 0.015, 0.006, 1.0, M_NOSE); + for (int s = 0; s < 2; s++) { + float x = s == 0 ? -1.0 : 1.0; + addPart(onHead(vec3(x * 0.050, 0.025, -0.012)), onHead(vec3(x * 0.066, -0.070, -0.004)), 0.018, 0.027, 0.012, 0.6, M_FUR_DARK); // floppy ears + int o = s * 4; + // hind leg (thigh, shank, paw) and fore leg + addPart(joints[J_HIP_L + o], joints[J_KNEE_L + o], 0.040, 0.026, 0.03, 1.0, M_FUR); + addPart(joints[J_KNEE_L + o], joints[J_ANKLE_L + o], 0.024, 0.021, 0.015, 1.0, M_FUR); + addPart(joints[J_ANKLE_L + o], joints[J_TOE_L + o], 0.022, 0.020, 0.012, 1.0, M_FUR_DARK); + addPart(joints[J_SHOULDER_L + o], joints[J_ELBOW_L + o], 0.038, 0.026, 0.03, 1.0, M_FUR); + addPart(joints[J_ELBOW_L + o], joints[J_WRIST_L + o], 0.024, 0.021, 0.015, 1.0, M_FUR); + addPart(joints[J_WRIST_L + o], joints[J_HAND_L + o], 0.022, 0.020, 0.012, 1.0, M_FUR_DARK); + } + addPart(tailA, tailB, 0.020, 0.007, 0.02, 1.0, M_FUR); + if (streamKind > 0) // (she pees too) + for (int k = 0; k < 3; k++) addPart(stream[k], stream[k + 1], streamWidth, streamWidth * 0.8, 0.002, 1.0, M_PEE); +} + void buildBody() { + if (species == 1) { buildDog(); return; } vec3 pelvis = joints[J_PELVIS], chest = joints[J_CHEST], neck = joints[J_NECK]; vec3 waist = mix(pelvis, chest, 0.45) + waistBend; bool jacket = top == 3; @@ -318,6 +349,8 @@ vec3 materialColor(int mat) { if (mat == M_CIG) return vec3(0.96, 0.95, 0.92); if (mat == M_JACKET) return jacketColor; if (mat == M_URINELLA) return vec3(0.55, 0.78, 0.92); + if (mat == M_FUR_DARK) return hairColor; + if (mat == M_NOSE) return vec3(0.08, 0.06, 0.05); return skinColor; } @@ -366,7 +399,7 @@ void main() { if (length(cross(c - origin, ray)) < r) candidates |= 1 << i; } vec3 head = onHead(vec3(0.0, hairStyle == 1 ? -0.08 : 0.0, -0.02)); - hairCandidate = length(cross(head - origin, ray)) < (hairStyle == 1 ? 0.22 : 0.15); + hairCandidate = species == 0 && length(cross(head - origin, ray)) < (hairStyle == 1 ? 0.22 : 0.15); if (candidates == 0 && !hairCandidate) discard; // March the ray through the body @@ -391,7 +424,14 @@ void main() { vec3 n = normalize(R * nl.x + UP * nl.y + facing * nl.z); vec3 base = materialColor(mat); - if (mat == M_SKIN) base = faceDetail(base, p); + if (mat == M_SKIN && species == 0) base = faceDetail(base, p); + if (species == 1 && mat == M_FUR) { // her eyes, a darker back + for (int s = 0; s < 2; s++) { + vec3 e = toHead(p) - vec3(s == 0 ? -0.030 : 0.030, 0.022, 0.050); + if (dot(e, e) < 0.012 * 0.012) base = vec3(0.07, 0.05, 0.04); + } + base *= 1.0 - 0.18 * smoothstep(0.02, 0.07, p.y - joints[J_PELVIS].y); + } else if (mat == M_HAIR) base = hairDetail(base, p); else if (mat < M_PEE) base = clothDetail(mat, base, p); if (mat == M_JACKET) { // the zip down the front, a darker collar diff --git a/entropie/assets/shaders/scene.frag b/entropie/assets/shaders/scene.frag index 7f7eeef..3ba5a6d 100644 --- a/entropie/assets/shaders/scene.frag +++ b/entropie/assets/shaders/scene.frag @@ -24,8 +24,8 @@ uniform float grass; uniform sampler2D shadowMap; uniform sampler2D waterMap; uniform sampler2D surfaceMap; // asphalt, concrete, gravel, bare earth (Site_surface) -uniform vec4 holes[4]; // holes in the ground (basements): middle xz, x axis (cos, sin) ... -uniform vec2 holeSize[4]; // ... half sizes +uniform vec4 holes[8]; // holes in the ground (basements): middle xz, x axis (cos, sin) ... +uniform vec2 holeSize[8]; // ... half sizes uniform int holeCount; uniform vec4 tint; // rgb: darkens (charred wood), a: glows like embers diff --git a/entropie/commit_message.txt b/entropie/commit_message.txt index 8000583..eebe5bf 100644 --- a/entropie/commit_message.txt +++ b/entropie/commit_message.txt @@ -1,19 +1,21 @@ -entropie 1.92.0: careful on roofs, breaks of all kinds, out of the climber's way +entropie 1.93.0: Alice painted like the people, one roof, chairs up to the table -- Feet on the course: a step over or onto something lands the feet flat on - it (they stood 12-22 cm above logs); firewood lying on its side is walked - onto instead of being pushed along -- Holding a ladder: from the side, hands on one rail, a foot on the bottom - rung; the climber gets on and off in front -- Up on a roof: NPCs walk slowly and carefully, arms out; afraid of heights - (Ida) they don't follow up the ladder; drunk, the sharp ones are extra - careful, the dim ones stroll about and stumble. New trait: afraid of - heights (dev menu) -- Breaks: mostly their own kind, now and then another (a coffee at the - stove, a walk, a chat, a sit), short or long; the tool goes down first -- Called away from a job to help, they go back to it after; the forgetful - wait to be reminded ("Back to the firewood, please") -- Worn from the days before: hard work wears people down, only sleep takes - it off, a hard day leaves some for the next; shown on Tab -- Woodpile: "Find a good place for it" - they pick a spot themselves (a pile - nearby, else clear, level, off the roads, by a wall) +- Alice is drawn in the same painted style as the people (smooth body, + floppy ears, paws, wagging tail, her shadow too) +- Chairs: once you sit, the chair is shuffled in to the table; getting up + it goes out again first +- E / Q next to a table, counter or crate sets the thing on top of it +- Dropped bottles nearly always fall over; things on a slope slide or roll + down it when steep enough, and keep going more easily once pushed +- Felling: the cut at about hip height (lower if you look lower); the trunk + slides off the stump and lies on the ground (it hung in the air) +- Chopping: more hips, back and head in the swing +- Main building: one merged roof (the left ridge runs into the right + roof); the ground no longer shows through the floors; the basement + landing is deeper, the stairs room wider, no lintel hanging in the side + opening +- Roofs are closed underneath: no looking through them from below +- Your unfinished building: 2 m longer towards the frame, the roof turned, + the door on the woodpile side +- Garage gates swing right round and lie flat against the wall +- No climbing through a wall onto something behind it diff --git a/entropie/src/people/body_internal.h b/entropie/src/people/body_internal.h index 0f5ac50..8876e06 100644 --- a/entropie/src/people/body_internal.h +++ b/entropie/src/people/body_internal.h @@ -158,6 +158,10 @@ enum { #define LEG_RAISE_SPEED 2.5f /* m/s a foot comes up off the ground when its leg must */ #define OVER_LEAN 0.22f /* radians the body leans forward stepping over something high */ #define BOB_OMEGA 28.0f /* the running wave follows the strides this quickly */ +#define SWING_CHEST 0.8f /* a two-handed swing off to one side: the shoulders turn this much with it ... */ +#define SWING_HIPS 0.3f /* ... the hips ... */ +#define SWING_HEAD 0.3f /* ... the head ... */ +#define SWING_ARCH 0.15f /* ... and the back arches winding up, bends into the hit (rad) */ #define WALK_BOB 0.022f /* m walking: up mid-step, down as a foot lands (either way) */ #define BOB_FROM 1.8f /* m/s: from a brisk walk on ... */ #define BOB_FULL 3.6f /* ... to a run: the body rises and falls with each stride */ diff --git a/entropie/src/people/body_pose.c b/entropie/src/people/body_pose.c index 4666475..0c7661e 100644 --- a/entropie/src/people/body_pose.c +++ b/entropie/src/people/body_pose.c @@ -536,12 +536,18 @@ static void solveStanding(Body *b, Vec3 position, Vec3 facing, float s, float dt /* Hips turn a little with the forward leg, shoulders counter-turn */ float hip_turn = clampf(stride * 0.2f, -0.12f, 0.12f); float chest_turn = -0.9f * hip_turn + clampf(b->turn_rate * 0.06f, -0.15f, 0.15f); - { /* Both hands busy off to one side (a swing, a saw): the shoulders turn that way */ + float swing_yaw = 0.0f; /* (the head goes along a little: below) */ + { /* Both hands busy off to one side (a swing, a saw): the shoulders turn that way, the hips + a little too; up high (winding up) the back arches, down low (the hit) it bends into it */ float w0 = b->hand_ready[0] ? b->hand_weight[0] : 0.0f, w1 = b->hand_ready[1] ? b->hand_weight[1] : 0.0f; if (w0 + w1 > 0.01f) { Vec3 mid = vec3_scale(vec3_add(vec3_scale(b->hand_local[0], w0), vec3_scale(b->hand_local[1], w1)), 1.0f / (w0 + w1)); - float yaw = clampf(atan2f(mid.x, fmaxf(mid.z, 0.12f)), -1.0f, 1.0f); - chest_turn += 0.5f * yaw * fminf(w0, w1); + float yaw = clampf(atan2f(mid.x, fmaxf(mid.z, 0.12f)), -1.0f, 1.0f), both = fminf(w0, w1); + chest_turn += SWING_CHEST * yaw * both; + hip_turn = clampf(hip_turn + SWING_HIPS * yaw * both, -0.4f, 0.4f); + swing_yaw = yaw * both; + float up = clampf((mid.y - b->pelvis_y - 0.45f) / 0.5f, -1.0f, 1.0f); /* (over the shoulders .. at the hips) */ + pitch -= SWING_ARCH * up * both; } } /* Pulling up onto something: the back bends over the edge as far as it @@ -601,6 +607,7 @@ static void solveStanding(Body *b, Vec3 position, Vec3 facing, float s, float dt pitch_goal = pitch_goal + (up - pitch_goal) * b->look_weight; } yaw_goal += clampf(b->turn_rate * 0.3f, -0.5f, 0.5f); + yaw_goal += SWING_HEAD * swing_yaw; /* (with the swing, a little: the eyes stay on the work) */ /* The head stays fairly level while the back leans, except when bent over being sick */ float level = 0.4f * (pitch - 1.0f * bentOver(b)); pitch_goal += 0.12f * proud - 0.28f * shy - 0.1f * tired - 0.15f * u - level + b->chin diff --git a/entropie/src/people/climb.c b/entropie/src/people/climb.c index 424f66a..d234e08 100644 --- a/entropie/src/people/climb.c +++ b/entropie/src/people/climb.c @@ -5,6 +5,14 @@ /* What's in front at climbing distance and could be climbed from feet_y: its top (the edge), or -1e9 if nothing (too high, no room up there). */ +#define CLIMB_CLEAR 0.05f /* m over where the hands go ... */ +#define CLIMB_OVER 0.5f /* ... from this high over it at the body: the way there is clear */ + +static bool onlyWalls(const Object *o, const void *ctx) { + (void)ctx; + return !o->is_static || o->is_character || o->type == OBJ_LADDER; /* (only fixed things block it) */ +} + float Climb_ledgeAhead(const World *world, const Object *p, Vec3 *probe_out) { const float s = p->look.height / 1.75f; Vec3 f = Object_facing(p); @@ -20,6 +28,11 @@ float Climb_ledgeAhead(const World *world, const Object *p, Vec3 *probe_out) { if (PROBE[k][1] != 0.0f && rise > CLIMB_EASY * s) continue; /* (to one side: only something low enough to step up on) */ /* room to stand up there, and not a step beside us (the edge is ahead, not underfoot) */ if (World_groundHeight(world, probe.x, probe.z, top + 1.8f * s) > top + 0.05f) continue; + /* nothing solid in between, over the edge to it (a wall with something behind it: not + through the wall; down onto it, so a roof rising beyond its edge doesn't count) */ + Vec3 from = vec3(p->position.x, top + CLIMB_OVER, p->position.z), to = vec3_sub(vec3(probe.x, top + CLIMB_CLEAR, probe.z), from); + float d = vec3_length(to); + if (d > 1e-3f && World_rayCast(world, from, vec3_scale(to, 1.0f / d), d, onlyWalls, NULL, NULL, NULL) >= 0.0f) continue; *probe_out = probe; return top; } diff --git a/entropie/src/people/dog.c b/entropie/src/people/dog.c index a4011e3..00e98c5 100644 --- a/entropie/src/people/dog.c +++ b/entropie/src/people/dog.c @@ -1,5 +1,7 @@ #include "people/dog.h" +#include <string.h> + enum { DOG_WANDER, DOG_FOLLOW, DOG_COME, DOG_BEG, DOG_REST }; #define TROT_SPEED 1.5f @@ -9,8 +11,6 @@ enum { DOG_WANDER, DOG_FOLLOW, DOG_COME, DOG_BEG, DOG_REST }; #define FIRE_RANGE 25.0f #define TURN_RATE 6.0f -static Mesh leg_mesh, tail_mesh; - static float rnd(Object *dog) { unsigned *r = &dog->ai.rng; if (!*r) *r = 0x51ed270bu; @@ -153,30 +153,61 @@ void Dog_pet(Object *dog) { bark(dog, rnd(dog) < 0.5f ? "*happy whine*" : "*licks your hand*"); } -void Dog_draw(Renderer *r, const Object *dog) { - if (!leg_mesh.vao) leg_mesh = Shapes_dogLeg(); - if (!tail_mesh.vao) tail_mesh = Shapes_dogTail(); - const float lie = smoothstepf(0.0f, 1.0f, dog->dog.lie); - Mat4 base = mat4_mul(Object_modelMatrix(dog), mat4_translate(vec3(0.0f, -0.09f * lie, 0.0f))); - Renderer_drawMesh(r, &dog->mesh, base); +/* Drawn like the people (a painted figure, see figure.frag): her body as a skeleton. + Figure space: x right, y up, z forward, from her feet. */ +#define DOG_COAT ((Vec3){ 0.55f, 0.30f, 0.14f }) +#define DOG_DARK ((Vec3){ 0.33f, 0.17f, 0.08f }) +#define DOG_LEG (DOG_HIP_Y + 0.03f) - float moving = clampf(dog->ground_speed / TROT_SPEED, 0.0f, 1.0f); +void Dog_draw(Renderer *r, const Object *dog) { + FigurePose pose; + memset(&pose, 0, sizeof(pose)); + pose.position = dog->position; + pose.facing = Object_facing(dog); + pose.look.skin = DOG_COAT; + pose.look.hair = DOG_DARK; + pose.look.height = 0.4f; + Skeleton *sk = &pose.skeleton; + sk->species = FIGURE_DOG; + sk->cig_glow = -1.0f; + + const float lie = smoothstepf(0.0f, 1.0f, dog->dog.lie), dy = -0.09f * lie; + const float moving = clampf(dog->ground_speed / TROT_SPEED, 0.0f, 1.0f); + const float t = r->boil_time, breathe = 0.004f * sinf(t * 5.0f); + const float bob = 0.008f * moving * fabsf(sinf(dog->dog.phase)); + sk->joints[JOINT_PELVIS] = vec3(0.0f, 0.21f + dy + bob, -0.18f); + sk->joints[JOINT_CHEST] = vec3(0.0f, 0.22f + dy + bob + breathe, 0.17f); + sk->joints[JOINT_NECK] = vec3(0.0f, 0.29f + dy + bob, 0.27f); + const float sniff = dog->dog.mode == DOG_WANDER && moving < 0.6f && lie < 0.1f ? 0.5f : 0.0f; /* (nose down, sniffing about) */ + sk->joints[JOINT_HEAD] = vec3(0.0f, 0.33f + dy + bob - 0.06f * lie - 0.08f * sniff, 0.35f); + sk->head_forward = vec3_normalize(vec3(0.0f, -0.12f - sniff, 1.0f)); + sk->head_up = vec3_normalize(vec3(0.0f, 1.0f, 0.12f + sniff)); + sk->chest_forward = vec3(0.0f, 0.0f, 1.0f); + + /* Legs: trotting, diagonal pairs together; lying, the front legs forward, the hind ones back */ + static const int SLOT[4][4] = { + { JOINT_SHOULDER_L, JOINT_ELBOW_L, JOINT_WRIST_L, JOINT_HAND_L }, { JOINT_SHOULDER_R, JOINT_ELBOW_R, JOINT_WRIST_R, JOINT_HAND_R }, + { JOINT_HIP_L, JOINT_KNEE_L, JOINT_ANKLE_L, JOINT_TOE_L }, { JOINT_HIP_R, JOINT_KNEE_R, JOINT_ANKLE_R, JOINT_TOE_R }, + }; for (int k = 0; k < 4; k++) { bool front = k < 2; float side = k % 2 ? 1.0f : -1.0f; - Vec3 hip = vec3(side * DOG_HIP_X, DOG_HIP_Y, front ? -DOG_HIP_Z : DOG_HIP_Z); - /* Trot: diagonal legs together; lying: front legs forward, hind legs back */ + Vec3 hip = vec3(side * DOG_HIP_X, DOG_HIP_Y + 0.05f + dy + bob, front ? DOG_HIP_Z - 0.04f : -DOG_HIP_Z + 0.02f); float swing = sinf(dog->dog.phase + (front ? 0.0f : PI_F) + (side > 0.0f ? PI_F : 0.0f)) * 0.6f * moving; - swing = swing * (1.0f - lie) + (front ? 1.35f : -1.35f) * lie; - Mat4 m = mat4_mul(base, mat4_mul(mat4_translate(hip), mat4_rotate_x(swing))); - Renderer_drawMesh(r, &leg_mesh, m); + swing = swing * (1.0f - lie) + (front ? -1.35f : 1.35f) * lie; + Vec3 paw = vec3_add(hip, vec3(0.0f, -DOG_LEG * cosf(swing), -DOG_LEG * sinf(swing))); + if (lie < 0.5f) paw.y = fmaxf(paw.y, 0.022f); + Vec3 knee = vec3_add(vec3_scale(vec3_add(hip, paw), 0.5f), vec3(0.0f, 0.0f, front ? -0.012f : 0.022f)); + sk->joints[SLOT[k][0]] = hip; + sk->joints[SLOT[k][1]] = knee; + sk->joints[SLOT[k][2]] = paw; + sk->joints[SLOT[k][3]] = vec3_add(paw, vec3(0.0f, -0.004f, 0.035f)); } - float t = r->boil_time, wag = sinf(t * 20.0f) * (0.15f + 0.45f * fminf(dog->dog.wag, 1.0f)); - Mat4 tail = mat4_mul(base, mat4_mul(mat4_translate(vec3(0.0f, DOG_TAIL_Y, DOG_TAIL_Z)), mat4_rotate_y(wag))); - Renderer_drawMesh(r, &tail_mesh, tail); + float wag = sinf(t * 20.0f) * (0.15f + 0.45f * fminf(dog->dog.wag, 1.0f)); + sk->tail_a = vec3(0.0f, DOG_TAIL_Y + dy + bob, -DOG_TAIL_Z + 0.03f); + sk->tail_b = vec3_add(sk->tail_a, vec3(0.17f * sinf(wag), 0.07f, -0.17f * cosf(wag))); + Renderer_drawFigure(r, &pose); } void Dog_shutdown(void) { - Mesh_destroy(&leg_mesh); - Mesh_destroy(&tail_mesh); } diff --git a/entropie/src/people/hands.c b/entropie/src/people/hands.c index 6a13fdc..f2e9bdd 100644 --- a/entropie/src/people/hands.c +++ b/entropie/src/people/hands.c @@ -290,10 +290,67 @@ static bool startPickUp(Object *who, int hand, Object *item) { return false; } +/* Put down near a table, a counter, a crate (something solid at about hand + height in front): set on top of it, not dropped to the floor. */ +#define PLACE_LOWEST 0.3f /* m over the feet: a surface this high and up counts ... */ +#define PLACE_HIGHEST 1.5f /* ... up to this */ +#define PLACE_HEAVY 20.0f /* kg: loose things this heavy hold something put on them */ + +static float surfaceUnder(const Object *who, const Object *item, Object *const *objects, int count, Vec3 at, float from_y) { + float best = -1e9f; + for (int i = 0; i < count; i++) { + const Object *o = objects[i]; + if (o == who || o == item || o->is_character || o->is_held || o->collider_count == 0 || o->type == OBJ_DECAL || + o->type == OBJ_PUDDLE || o->type == OBJ_STINK || o->type == OBJ_STRUCTURE || (!o->is_static && o->weight < PLACE_HEAVY)) continue; + if (fabsf(o->position.x - at.x) > 4.0f || fabsf(o->position.z - at.z) > 4.0f) continue; + for (int c = 0; c < o->collider_count; c++) { + if (fabsf(o->colliders[c].axes[1].y) < 0.95f) continue; /* (a level top only) */ + float t = Object_rayToCollider(o, c, vec3(at.x, from_y, at.z), vec3(0.0f, -1.0f, 0.0f), NULL); + if (t >= 0.0f) best = fmaxf(best, from_y - t); + } + } + return best; +} + +static bool placeOnSurface(Object *who, Object *item, Object *const *objects, int count) { + if (item->type == OBJ_LADDER || item->weight > 15.0f || item->collider_count == 0) return false; + const Vec3 f = Object_facing(who); + const float feet = who->position.y, from = feet + PLACE_HIGHEST + 0.3f; + const float r = fmaxf(item->colliders[0].half_extents.x, item->colliders[0].half_extents.z); + Vec3 tries[4] = { Object_colliderCenter(item, 0) }; + for (int k = 1; k < 4; k++) tries[k] = vec3_add(who->position, vec3_scale(f, 0.25f + 0.2f * (float)k)); + int pick = -1; + float pick_top = -1e9f; + for (int k = 0; k < 4; k++) { /* (the highest: a table over the chair in front of it) */ + float top = surfaceUnder(who, item, objects, count, tries[k], from); + if (top < feet + PLACE_LOWEST || top > feet + PLACE_HIGHEST || top <= pick_top + 0.05f) continue; + /* room for all of it up there (its edges on the top too) */ + bool room = true; + Vec3 side = vec3(-f.z, 0.0f, f.x); + for (int e = 0; e < 4 && room; e++) { + Vec3 d = vec3_scale(e < 2 ? f : side, (e % 2 ? -1.0f : 1.0f) * r * 0.8f); + room = fabsf(surfaceUnder(who, item, objects, count, vec3_add(tries[k], d), from) - top) < 0.03f; + } + if (room) { pick = k; pick_top = top; } + } + if (pick >= 0) { + if (item->type != OBJ_AXE && item->type != OBJ_SHEARS) item->rotation = vec3(0.0f, who->rotation.y, 0.0f); /* (upright; tools lie flat) */ + Object_syncColliders(item); + float bottom = Object_bottom(item) - item->position.y; + item->position = vec3(tries[pick].x - item->colliders[0].offset.x, pick_top - bottom + 0.005f, tries[pick].z - item->colliders[0].offset.z); + item->velocity = vec3_zero(); + Object_syncColliders(item); + return true; + } + return false; +} + bool Hands_pickUpOrDrop(Object *who, int hand, Object *const *objects, int count) { Hands *h = &who->hands; if (h->state[hand] == HAND_HOLDING || h->state[hand] == HAND_REACHING) { + Object *item = h->state[hand] == HAND_HOLDING ? h->item[hand] : NULL; Hands_drop(who, hand); + if (item && !item->is_held && !placeOnSurface(who, item, objects, count) && Object_isBottle(item)) item->drop_tip = true; return true; } if (h->state[hand] == HAND_GRABBING) Hands_release(who); diff --git a/entropie/src/render/figure.h b/entropie/src/render/figure.h index dfc439f..2d7550d 100644 --- a/entropie/src/render/figure.h +++ b/entropie/src/render/figure.h @@ -75,8 +75,12 @@ typedef struct { Vec3 cig_a, cig_tip; /* a cigarette (figure space): the end at the hand / lips, the burning end ... */ float cig_glow; /* ... how it glows (-1: no cigarette, 0: not lit .. 1: a drag) */ float undress; /* to pee: squatting trousers down / skirt up (0..1); standing fly open / skirt lifted in front (-1..0) */ + int species; /* FIGURE_PERSON, or FIGURE_DOG: the joints are her body (see people/dog.c) */ + Vec3 tail_a, tail_b; /* the dog's tail */ } Skeleton; +enum { FIGURE_PERSON, FIGURE_DOG }; + enum { STREAM_NONE, STREAM_PEE, STREAM_VOMIT, STREAM_WATER }; typedef struct { diff --git a/entropie/src/render/renderer.c b/entropie/src/render/renderer.c index 61c25a4..9087168 100644 --- a/entropie/src/render/renderer.c +++ b/entropie/src/render/renderer.c @@ -108,6 +108,9 @@ static bool loadShaders(Renderer *r) { f->uri = glGetUniformLocation(f->program, "uri"); f->cig_tip = glGetUniformLocation(f->program, "cigTip"); f->cig_glow = glGetUniformLocation(f->program, "cigGlow"); + f->species = glGetUniformLocation(f->program, "species"); + f->tail_a = glGetUniformLocation(f->program, "tailA"); + f->tail_b = glGetUniformLocation(f->program, "tailB"); f->view_pos = glGetUniformLocation(f->program, "viewPos"); f->light_dir = glGetUniformLocation(f->program, "lightDirection"); f->light_color = glGetUniformLocation(f->program, "lightColor"); @@ -545,6 +548,9 @@ void Renderer_drawFigure(Renderer *r, const FigurePose *pose) { glUniform1i(f->uri, sk->uri ? 1 : 0); setVec3(f->cig_tip, sk->cig_tip); glUniform1f(f->cig_glow, sk->cig_glow); + glUniform1i(f->species, sk->species); + setVec3(f->tail_a, sk->tail_a); + setVec3(f->tail_b, sk->tail_b); glBindVertexArray(r->fullscreen_vao); /* no vertex data: the card is built from gl_VertexID */ glDrawArrays(GL_TRIANGLES, 0, 6); diff --git a/entropie/src/render/renderer.h b/entropie/src/render/renderer.h index acb5af3..bf5bbd4 100644 --- a/entropie/src/render/renderer.h +++ b/entropie/src/render/renderer.h @@ -70,6 +70,7 @@ typedef struct { GLint stream, stream_kind, stream_width; GLint garment, undress, top; GLint cig_a, cig_tip, cig_glow, jacket, uri_a, uri_b, uri; + GLint species, tail_a, tail_b; GLint view_pos, light_dir, light_color, shadow_color, sky_top, sky_horizon; GLint shadow_strength, shadow_map, fire_light; } FigureShader; @@ -149,7 +150,7 @@ void Renderer_drawDecal(Renderer *r, const Mesh *mesh, Mat4 model, float terrain */ /* Holes in the ground drawn next (up to RENDER_HOLES): each a rectangle, middle x, z, its x axis (cos, sin) and half sizes x, z. */ -#define RENDER_HOLES 4 +#define RENDER_HOLES 8 void Renderer_setHoles(Renderer *r, const float (*rects)[6], int count); void Renderer_drawTerrain(Renderer *r, const Mesh *mesh, GLuint wear_map, GLuint water_map, GLuint surface_map, float size, float grass_depth); diff --git a/entropie/src/render/shapes.c b/entropie/src/render/shapes.c index 26e840e..2542832 100644 --- a/entropie/src/render/shapes.c +++ b/entropie/src/render/shapes.c @@ -168,10 +168,11 @@ static void addTiledSlope(MeshBuilder *b, float lx, float wz, float wy, float ro MeshBuilder_gridQuad(b, vec3(x0, a.y, a.z), vec3(x1, a.y, a.z), vec3(x1, p0.y, p0.z), vec3(x0, p0.y, p0.z), n, vec3_scale(roof, 0.6f), CELL_SIZE * 2.0f); } - /* Underside of the overhang, and the board along the eaves */ - Vec3 under = vec3_scale(n, -1.0f); - MeshBuilder_gridQuad(b, vec3(x1, y0, z0), vec3(x0, y0, z0), vec3(x0, wy, side * wz), vec3(x1, wy, side * wz), - under, vec3_scale(roof, 0.55f), CELL_SIZE * 2.0f); + /* Underside, all of it (from inside under a roof without a ceiling, or below the + overhang, you don't look through it), and the board along the eaves */ + Vec3 under = vec3_scale(n, -1.0f), down = vec3_scale(n, -0.02f); + MeshBuilder_gridQuad(b, vec3_add(vec3(x1, y0, z0), down), vec3_add(vec3(x0, y0, z0), down), vec3_add(vec3(x0, y1, z1), down), + vec3_add(vec3(x1, y1, z1), down), under, vec3_scale(roof, 0.55f), CELL_SIZE * 2.0f); addBlock(b, vec3(0.0f, y0 - 0.06f, z0), vec3(x1 - x0, 0.16f, 0.04f), COLOR_FASCIA); /* ... and under the roof where it reaches past the gables (else seen from below you look through it) */ for (int e = -1; e <= 1; e += 2) { @@ -225,12 +226,16 @@ static void addRoof(MeshBuilder *b, float length, float width, float wall_height } } - /* Gables: walls up to the roof */ + /* Gables: walls up to the roof (inside too, a little in) */ const Vec3 gable = wall; MeshBuilder_gridTriangle(b, vec3(-lx, wy, -wz), vec3(-lx, wy, wz), vec3(-lx, ry, 0), vec3(-1, 0, 0), gable, CELL_SIZE); MeshBuilder_gridTriangle(b, vec3(lx, wy, -wz), vec3(lx, wy, wz), vec3(lx, ry, 0), vec3(1, 0, 0), gable, CELL_SIZE); + MeshBuilder_gridTriangle(b, vec3(-lx + 0.2f, wy, -wz), vec3(-lx + 0.2f, wy, wz), vec3(-lx + 0.2f, ry - 0.1f, 0), + vec3(1, 0, 0), vec3_scale(gable, 0.9f), CELL_SIZE); + MeshBuilder_gridTriangle(b, vec3(lx - 0.2f, wy, -wz), vec3(lx - 0.2f, wy, wz), vec3(lx - 0.2f, ry - 0.1f, 0), + vec3(-1, 0, 0), vec3_scale(gable, 0.9f), CELL_SIZE); } /* Brick walls: courses of mortar and the joints between the bricks, staggered, @@ -564,6 +569,11 @@ Mesh Shapes_building(BuildingSize size, bool with_details) { Vec3 Shapes_wallColor(BuildingLook look) { return wallColor(look); } +Vec3 Shapes_roofColor(BuildingLook look) { + return look == BUILDING_BRICK ? COLOR_ROOF : look == BUILDING_PLASTER ? COLOR_LIGHT_ROOF + : look == BUILDING_PLASTER_DARK ? COLOR_DARK_ROOF : look == BUILDING_HALL_RED ? COLOR_RED_ROOF : COLOR_HALL_ROOF; +} + Mesh Shapes_structure(const ShapeSlab *slabs, int n_slabs, const ShapeOpening *openings, int n_openings, const ShapeRoof *roofs, int n_roofs) { MeshBuilder b; @@ -611,6 +621,21 @@ Mesh Shapes_structure(const ShapeSlab *slabs, int n_slabs, const ShapeOpening *o const ShapeRoof *r = &roofs[i]; size_t first = b.vertex_count; addRoof(&b, r->length, r->width, 0.0f, r->roof_height, r->look); + if (r->valley > 0.0f) { /* its ridge runs on into the roof across it: a valley each side, no eaves */ + const float lx = r->length * 0.5f, wz = r->width * 0.5f, rh = r->roof_height; + const Vec3 color = Shapes_roofColor(r->look); + for (int side = -1; side <= 1; side += 2) { + const float sd = (float)side; + Vec3 n = vec3_normalize(vec3(0.0f, wz, sd * rh)), up = vec3_scale(n, 0.05f); /* (on a level with the tiles) */ + MeshBuilder_gridTriangle(&b, vec3_add(vec3(lx, 0.0f, sd * wz), up), vec3_add(vec3(lx, rh, 0.0f), up), + vec3_add(vec3(lx + r->valley, rh, 0.0f), up), n, color, CELL_SIZE * 2.0f); + MeshBuilder_gridTriangle(&b, vec3(lx, 0.0f, sd * wz), vec3(lx, rh, 0.0f), vec3(lx + r->valley, rh, 0.0f), + vec3_scale(n, -1.0f), vec3_scale(color, 0.55f), CELL_SIZE * 2.0f); + } + size_t cap = b.vertex_count; + shapes_addTube(&b, lx, 0.11f, 0.11f, r->valley, 8, vec3_scale(color, 0.75f), true); + transformSince(&b, cap, mat4_mul(mat4_translate(vec3(0.0f, rh + 0.03f, 0.0f)), mat4_rotate_z(-PI_F / 2.0f))); + } Mat4 turn = r->across ? mat4_rotate_y(PI_F * 0.5f) : mat4_identity(); /* (ridge along Z) */ transformSince(&b, first, mat4_mul(mat4_translate(vec3(r->center.x, r->wall_top, r->center.z)), turn)); } diff --git a/entropie/src/render/shapes.h b/entropie/src/render/shapes.h index cf35e31..fc8dfdc 100644 --- a/entropie/src/render/shapes.h +++ b/entropie/src/render/shapes.h @@ -127,11 +127,9 @@ Mesh Shapes_ivy(float width, float height, unsigned seed); /* The coat rack with these jackets hanging on it (n at most 6). */ Mesh Shapes_coatRack(Vec3 size, const Vec3 *jackets, int n); -/* A dachshund, its nose towards -Z, standing on y = 0: body (with head and - ears), one leg (hanging down from its top), the tail (from its root, along +Z). */ +/* A dachshund, its nose towards -Z, standing on y = 0: a plain body for picking it + out (she's drawn as a painted figure, see people/dog.c); her proportions. */ Mesh Shapes_dogBody(void); -Mesh Shapes_dogLeg(void); -Mesh Shapes_dogTail(void); #define DOG_HIP_Y 0.13f #define DOG_HIP_X 0.055f #define DOG_HIP_Z 0.20f @@ -255,9 +253,11 @@ typedef struct { float length, width, wall_top, roof_height; /* length along the ridge: X, or Z if across */ BuildingLook look; bool across; + float valley; /* > 0: the ridge runs on this far past its +X end into a roof across it (valleys, no eaves) */ } ShapeRoof; Mesh Shapes_structure(const ShapeSlab *slabs, int n_slabs, const ShapeOpening *openings, int n_openings, const ShapeRoof *roofs, int n_roofs); Vec3 Shapes_wallColor(BuildingLook look); +Vec3 Shapes_roofColor(BuildingLook look); #endif /* RENDER_SHAPES_H */ diff --git a/entropie/src/render/shapes_props.c b/entropie/src/render/shapes_props.c index 748505d..39086bd 100644 --- a/entropie/src/render/shapes_props.c +++ b/entropie/src/render/shapes_props.c @@ -997,21 +997,6 @@ Mesh Shapes_dogBody(void) { return Mesh_fromBuilder(&b); } -Mesh Shapes_dogLeg(void) { - MeshBuilder b; - MeshBuilder_init(&b); - addBlock(&b, vec3(0.0f, -DOG_HIP_Y * 0.5f, 0.0f), vec3(0.045f, DOG_HIP_Y, 0.05f), COLOR_COAT); - addBlock(&b, vec3(0.0f, -DOG_HIP_Y + 0.012f, -0.015f), vec3(0.05f, 0.025f, 0.075f), COLOR_EARS); /* paw */ - return Mesh_fromBuilder(&b); -} - -Mesh Shapes_dogTail(void) { - MeshBuilder b; - MeshBuilder_init(&b); - addRod(&b, vec3_zero(), vec3(0.0f, 0.07f, 0.17f), 0.022f, 0.007f, 6, COLOR_COAT); - return Mesh_fromBuilder(&b); -} - /* ---------- Telescopic ladder ---------- */ #define LADDER_PITCH_IN 0.066f /* rung spacing slid together ... */ diff --git a/entropie/src/world/forestry.c b/entropie/src/world/forestry.c index 36f830a..dd2e98e 100644 --- a/entropie/src/world/forestry.c +++ b/entropie/src/world/forestry.c @@ -38,6 +38,12 @@ static void fell(World *world, Object *tree, const Object *by, float cut_height) Object_topple(tree, away); float diameter = fmaxf(2.0f * tree->colliders[0].half_extents.x, STUMP_MIN); /* (as wide as the trunk: it looks the same as before) */ + { /* it comes down beside the stump, on the ground */ + Vec3 dir = tree->fall_dir; + tree->fall_slide = diameter * 0.5f + tree->colliders[0].half_extents.x + 0.05f; + Vec3 butt = vec3_add(base, vec3_scale(dir, tree->fall_slide)); + tree->fall_ground_y = Terrain_height(&world->terrain, butt.x, butt.z); + } Object *stump = Object_create(OBJ_STUMP, PALETTE_WARM, vec3_add(base, vec3(0.0f, stump_height * 0.5f, 0.0f)), vec3(diameter, stump_height, 0.0f), 0.0f); if (stump && World_add(world, stump)) tree->stump = stump; @@ -252,13 +258,7 @@ void Forestry_update(World *world, float dt) { if (tree->type != OBJ_TREE || !tree->toppled || tree->topple >= 1.0f) continue; Vec3 rotation = tree->rotation, position = tree->position; - Object_advanceTopple(tree, dt); - /* Cut above the ground: the trunk slides off the stump and comes down to the ground */ - if (tree->stump) { - float drop = tree->fall_base_y - tree->stump->position.y; - tree->position.y -= drop * smoothstepf(0.5f, 1.0f, tree->topple); - Object_syncColliders(tree); - } + Object_advanceTopple(tree, dt); /* (cut above the ground: it slides off the stump and comes down, see fell) */ Object *stopped = NULL; for (int k = 0; k < world->count; k++) { diff --git a/entropie/src/world/manor.c b/entropie/src/world/manor.c index 1e39d27..b868892 100644 --- a/entropie/src/world/manor.c +++ b/entropie/src/world/manor.c @@ -16,7 +16,7 @@ /* The halves */ #define LEFT_Z0 -5.0f #define LEFT_Z1 5.0f -#define RIGHT_Z0 -4.2f /* (the right half stands a little further out) */ +#define RIGHT_Z0 -5.0f /* (the right half stands a little further out at the front: its back in line) */ #define RIGHT_Z1 5.8f #define HALF_LEN 18.0f #define T 0.30f /* outer walls */ @@ -25,6 +25,7 @@ #define PORCH_X0 9.5f /* (the right half of the front) */ #define ROOF_PITCH (5.0f / 12.0f) +#define RIGHT_ROOF 4.5f /* the right half's roof: the ridge across (front to back), this high over the eaves */ static const Vec3 PLASTER = { 0.88f, 0.82f, 0.68f }; static const Vec3 PLINTH = { 0.62f, 0.60f, 0.56f }; @@ -161,7 +162,7 @@ static void windowsX(Plan *p, float z, float side, const float *xs, int n, float #define LIVING_X0 -5.0f #define GARAGE_X1 -9.75f /* your garages stand in front up to here */ #define WELL_X0 -6.95f /* the stairs down: from the landing at the front (WELL_TOP_Z), going back */ -#define WELL_TOP_Z 4.2f +#define WELL_TOP_Z 3.9f /* (a landing 0.8 m deep in front of it: room to step round the rail) */ static void leftHalf(Plan *p) { const float x0 = -HALF_LEN, z0 = LEFT_Z0, z1 = LEFT_Z1; @@ -228,15 +229,17 @@ static void leftHalf(Plan *p) { windowsX(p, z0, -1.0f, back, 5, CEIL + 1.5f, 1.0f, 1.2f, false, 0.0f); windowsX(p, z0, -1.0f, back, 5, 0.35f, 0.8f, 0.36f, false, 0.0f); - p->roofs[p->n_roofs++] = (ShapeRoof){ vec3(-HALF_LEN * 0.5f, 0.0f, (z0 + z1) * 0.5f), HALF_LEN, z1 - z0, WALL_TOP, - (z1 - z0) * ROOF_PITCH, BUILDING_PLASTER, false }; + /* One roof over both halves: the left one's ridge runs on into the right one's roof (turned) till they meet */ + const float rise = (z1 - z0) * ROOF_PITCH, into = HALF_LEN * 0.5f * rise / RIGHT_ROOF; + const float len = HALF_LEN - 0.35f; /* (its verge just reaches the middle wall: the valleys go on from there) */ + p->roofs[p->n_roofs++] = (ShapeRoof){ vec3(-HALF_LEN + len * 0.5f, 0.0f, (z0 + z1) * 0.5f), len, z1 - z0, WALL_TOP, + rise, BUILDING_PLASTER_DARK, false, 0.35f + into }; } /* ---------- The right half: the big room; the porch in front ---------- */ #define RDOOR_X 11.0f /* the door from the porch */ #define PSTEP_X 13.8f /* the porch steps: up from here, going left */ -#define RIGHT_ROOF 4.5f /* its roof: the ridge across (front to back), this high over the eaves */ static void rightHalf(Plan *p) { const float x1 = HALF_LEN, z0 = RIGHT_Z0, z1 = RIGHT_Z1; @@ -284,7 +287,7 @@ static void rightHalf(Plan *p) { box(p, vec3(PORCH_X0 + T, 0.0f, pz0), vec3(PSTEP_X - 1.2f, FL, pz1 - T), TILES, SOLID); box(p, vec3(PORCH_X0 + T, PORCH_TOP - 0.05f, pz0), vec3(x1 - T, PORCH_TOP, pz1 - T), CEILING, LOOK_ONLY); - ShapeRoof r = { vec3(HALF_LEN * 0.5f, 0.0f, (z0 + z1) * 0.5f), z1 - z0, HALF_LEN, WALL_TOP, RIGHT_ROOF, BUILDING_PLASTER_DARK, true }; + ShapeRoof r = { vec3(HALF_LEN * 0.5f, 0.0f, (z0 + z1) * 0.5f), z1 - z0, HALF_LEN, WALL_TOP, RIGHT_ROOF, BUILDING_PLASTER_DARK, true, 0.0f }; p->roofs[p->n_roofs++] = r; } @@ -310,14 +313,14 @@ static void roofSlabs(Plan *p, const ShapeRoof *r) { /* ---------- The basement: under the left half, down from the entrance ---------- */ -#define BASE_Y -2.3f /* its floor */ +#define BASE_Y -2.45f /* its floor */ #define BASE_X0 -9.6f /* the big part: under the corridor and the rooms at the back ... */ #define BASE_X1 -0.15f #define BASE_Z0 -4.7f #define BASE_Z1 1.4f /* (= HALL_Z) */ #define UNDER_Z1 4.7f /* ... and under the stairs, toilet and kitchen */ -#define STAIR_STEPS 13 -#define STAIR_TREAD 0.25f +#define STAIR_STEPS 14 +#define STAIR_TREAD 0.23f #define BW 0.25f /* its walls */ static const Vec3 OLD_BRICK = { 0.62f, 0.36f, 0.27f }; @@ -358,7 +361,7 @@ static void basement(Plan *p) { box(p, vec3(WELL_X0 - BW, BASE_Y, BASE_Z1 + BW), vec3(WELL_X0, 0.0f, UNDER_Z1 + BW), OLD_BRICK, SOLID); /* beside the stairs */ box(p, vec3(WELL_X0, BASE_Y, UNDER_Z1), vec3(BASE_X1, 0.0f, UNDER_Z1 + BW), OLD_BRICK, SOLID); /* front */ /* Inside: loose brick walls, open (no doors) */ - Gap side_way = { 2.5f, 3.4f, BASE_Y, -0.35f }; + Gap side_way = { 2.4f, 3.4f, BASE_Y, 0.0f }; /* (up to the ceiling: no lintel hanging there) */ wallZ(p, BASE_Z1 + 0.15f, UNDER_Z1, VEST_X1, TOILET_X0, BASE_Y, 0.0f, &side_way, 1, OLD_BRICK, SOLID); /* the stairs | under the kitchen */ Gap through = { -3.6f, -2.6f, BASE_Y, -0.35f }; wallX(p, TOILET_X0, BASE_X1, BASE_Z1, BASE_Z1 + 0.15f, BASE_Y, 0.0f, &through, 1, OLD_BRICK, SOLID); @@ -414,13 +417,20 @@ static void furnish(Plan *p) { } } -/* The ground goes down to the basement floor under it (rectangles: local min and max x, z). */ +/* The ground goes down to the basement floor under it, and isn't drawn inside the + walls at all (it showed through the floors here and there). Rectangles: local min + and max x, z, and the floor (the basement's first: the first one a point is in counts). */ static void digBasement(Plan *p) { - const float rects[2][4] = { { BASE_X0, BASE_Z0, BASE_X1, BASE_Z1 }, { WELL_X0, BASE_Z1, BASE_X1, UNDER_Z1 } }; - for (int i = 0; i < 2; i++) { + const float rects[5][5] = { + { BASE_X0, BASE_Z0, BASE_X1, BASE_Z1, BASE_Y }, { WELL_X0, BASE_Z1, BASE_X1, UNDER_Z1, BASE_Y }, + { -HALF_LEN + T, LEFT_Z0 + T, -P, LEFT_Z1 - T, -0.06f }, /* (under the floors) */ + { P, RIGHT_Z0 + T, HALF_LEN - T, RIGHT_Z1 - T, -0.06f }, + { PORCH_X0 + T, RIGHT_Z1, HALF_LEN - T, PORCH_Z1 - T, -0.06f }, + }; + for (int i = 0; i < 5; i++) { Vec3 mid = toWorld(p, vec3((rects[i][0] + rects[i][2]) * 0.5f, 0.0f, (rects[i][1] + rects[i][3]) * 0.5f)); if (!Terrain_addPit(&p->world->terrain, mid, p->yaw, (rects[i][2] - rects[i][0]) * 0.5f, (rects[i][3] - rects[i][1]) * 0.5f, - p->origin.y + BASE_Y)) + p->origin.y + rects[i][4])) p->ok = false; } } @@ -428,9 +438,9 @@ static void digBasement(Plan *p) { /* ---------- Your unfinished building: a brick shell under a roof ---------- */ #define SHELL_W 6.4f /* along x */ -#define SHELL_D 4.7f /* along z (+z: the back, against the barn) */ +#define SHELL_D 6.7f /* along z (+z: the back, against the barn; -z: the front, to the extension) */ #define SHELL_WALL 2.9f -#define SHELL_GAP 2.0f /* the way in, from the extension in front */ +#define SHELL_GAP 1.6f /* the way in: on its -x side (where the woodpile is) */ static const Vec3 BRICK = { 0.78f, 0.42f, 0.30f }; @@ -444,15 +454,17 @@ bool Shell_build(World *world, Vec3 origin, float yaw) { p->ok = true; const float x0 = -SHELL_W * 0.5f, x1 = SHELL_W * 0.5f, z0 = -SHELL_D * 0.5f, z1 = SHELL_D * 0.5f; p->brick = true; - Gap way_in = { -SHELL_GAP * 0.5f, SHELL_GAP * 0.5f, 0.0f, 2.2f }; - wallX(p, x0, x1, z0, z0 + T, 0.0f, SHELL_WALL, &way_in, 1, BRICK, EAVES_WALL); /* the front: open to the extension */ - wallX(p, x0, x1, z1 - T, z1, 0.0f, SHELL_WALL, NULL, 0, BRICK, EAVES_WALL); /* the back */ - Gap hole[1] = { { -0.5f, 0.5f, 1.0f, 2.0f } }; /* window holes, no glass yet */ - wallZ(p, z0 + T, z1 - T, x0, x0 + T, 0.0f, SHELL_WALL, hole, 1, BRICK, EAVES_WALL); - wallZ(p, z0 + T, z1 - T, x1 - T, x1, 0.0f, SHELL_WALL, hole, 1, BRICK, EAVES_WALL); + /* (the ridge runs front to back: gables front and back, the eaves over the sides) */ + Gap front_hole[1] = { { -0.6f, 0.6f, 1.0f, 2.0f } }; /* window holes, no glass yet */ + wallX(p, x0, x1, z0, z0 + T, 0.0f, SHELL_WALL, front_hole, 1, BRICK, SOLID); /* the front, to the extension */ + wallX(p, x0, x1, z1 - T, z1, 0.0f, SHELL_WALL, NULL, 0, BRICK, SOLID); /* the back */ + Gap door_side[2] = { { z0 + 0.9f, z0 + 0.9f + SHELL_GAP, 0.0f, 2.2f }, { 1.2f, 2.2f, 1.0f, 2.0f } }; + wallZ(p, z0 + T, z1 - T, x0, x0 + T, 0.0f, SHELL_WALL, door_side, 2, BRICK, EAVES_WALL); /* the way in, and a window */ + Gap holes[2] = { { -2.0f, -1.0f, 1.0f, 2.0f }, { 1.0f, 2.0f, 1.0f, 2.0f } }; + wallZ(p, z0 + T, z1 - T, x1 - T, x1, 0.0f, SHELL_WALL, holes, 2, BRICK, EAVES_WALL); p->brick = false; box(p, vec3(x0 + T, 0.0f, z0 + T), vec3(x1 - T, 0.02f, z1 - T), (Vec3){ 0.55f, 0.50f, 0.44f }, LOOK_ONLY); /* bare concrete */ - p->roofs[p->n_roofs++] = (ShapeRoof){ vec3(0.0f, 0.0f, 0.0f), SHELL_W, SHELL_D, SHELL_WALL, SHELL_D * ROOF_PITCH, BUILDING_BRICK, false }; + p->roofs[p->n_roofs++] = (ShapeRoof){ vec3(0.0f, 0.0f, 0.0f), SHELL_D, SHELL_W, SHELL_WALL, SHELL_W * ROOF_PITCH, BUILDING_BRICK, true, 0.0f }; roofSlabs(p, &p->roofs[0]); if (!p->ok) return false; Mesh mesh = Shapes_structure(p->slabs, p->n_slabs, p->openings, p->n_openings, p->roofs, p->n_roofs); diff --git a/entropie/src/world/object.c b/entropie/src/world/object.c index bb64bb9..9c9bf26 100644 --- a/entropie/src/world/object.c +++ b/entropie/src/world/object.c @@ -641,14 +641,16 @@ Object *Object_createFurniture(FurnitureKind kind, Vec3 position, Vec3 size, flo return o; } -#define GATE_SWING_TIME 1.2f /* s from shut to open */ +#define GATE_SWING_TIME 1.8f /* s from shut to open (right round) */ void Object_swingGate(Object *g, float dt) { float goal = g->gate_open ? 1.0f : 0.0f; g->gate_t += clampf(goal - g->gate_t, -dt / GATE_SWING_TIME, dt / GATE_SWING_TIME); - float a = smoothstepf(0.0f, 1.0f, g->gate_t) * PI_F * 0.5f * 0.97f; /* (open: nearly flat against the wall) */ + float u = smoothstepf(0.0f, 1.0f, g->gate_t); + float a = u * PI_F * 0.985f; /* (open: right round, flat against the wall outside) */ Vec3 dir = vec3_normalize(vec3_add(vec3_scale(g->gate_shut, cosf(a)), vec3_scale(g->gate_out, sinf(a)))); - Vec3 mid = vec3_add(g->hinge, vec3_scale(dir, g->size.x * 0.5f)); + /* (folding back it stands off the wall by its own thickness: it lies against it, not in it) */ + Vec3 mid = vec3_add(g->hinge, vec3_add(vec3_scale(dir, g->size.x * 0.5f), vec3_scale(g->gate_out, smoothstepf(0.6f, 1.0f, u) * (g->size.z + 0.02f)))); g->position = vec3(mid.x, g->hinge.y, mid.z); g->rotation.y = yaw_along(dir); /* (local x along the leaf) */ Object_syncColliders(g); @@ -666,7 +668,6 @@ void Object_useToilet(Object *who, Object *toilet) { } #define SEAT_BACK 0.2f /* the hips sit this far behind where the feet are (see body.c) */ -#define CHAIR_PULL 0.38f /* m a chair is pulled out to sit on it */ #define CHAIR_SPEED 0.9f /* m/s it slides */ #define CHAIR_GRAB 0.9f /* m: within this a free hand is on its backrest while it slides */ @@ -698,6 +699,7 @@ void Object_sit(Object *who, Object *seat, Vec3 toward) { who->sitting = true; who->seat = seat; who->body.crossing = false; /* (not stepping over it: onto it) */ + who->seat_scooted = false; who->sit_style = SIT_GROUND; who->body.seat_height = SEAT_HEIGHT; if (!seat) return; @@ -747,6 +749,7 @@ void Object_sit(Object *who, Object *seat, Vec3 toward) { void Object_leaveSeat(Object *who, float dt) { Object *seat = who->seat; if (!seat || who->sitting) return; + if (seat->type == OBJ_FURNITURE && seat->variant == FURN_CHAIR && seat->chair_out < seat->chair_goal - 0.01f) return; /* (out from the table first) */ /* got up: the seat is solid again once you're clear of it */ bool clear = true; for (int c = 0; c < seat->collider_count && clear; c++) { @@ -901,6 +904,9 @@ void Object_topple(Object *obj, Vec3 dir) { obj->topple = 0.0f; obj->fall_dir = dir; obj->fall_base_y = obj->position.y; + obj->fall_origin = obj->position; + obj->fall_ground_y = obj->position.y; /* (the world says where the ground is: see Forestry) */ + obj->fall_slide = 0.0f; Object_syncColliders(obj); } @@ -1221,8 +1227,11 @@ void Object_advanceTopple(Object *obj, float dt) { obj->topple = fminf(1.0f, obj->topple + dt / (tree ? TREE_FALL_TIME : BOTTLE_FALL_TIME)); float t = obj->topple; obj->rotation.x = -(PI_F / 2.0f) * t * t; /* speeds up as it falls */ - if (tree) /* the trunk comes to rest on the ground, not in it */ - obj->position.y = obj->fall_base_y + obj->colliders[0].half_extents.x * smoothstepf(0.8f, 1.0f, t); + if (tree) { /* the butt slides off the stump as it comes down, the trunk rests on the ground, not in it */ + float off = smoothstepf(0.55f, 1.0f, t); + Vec3 at = vec3_add(obj->fall_origin, vec3_scale(obj->fall_dir, obj->fall_slide * off)); + obj->position = vec3(at.x, lerpf(obj->fall_base_y, obj->fall_ground_y, off) + obj->colliders[0].half_extents.x * smoothstepf(0.8f, 1.0f, t), at.z); + } Object_syncColliders(obj); } diff --git a/entropie/src/world/object.h b/entropie/src/world/object.h index c1c8dca..0d4a6e1 100644 --- a/entropie/src/world/object.h +++ b/entropie/src/world/object.h @@ -155,7 +155,10 @@ typedef struct Object { Vec3 mark_at, mark_out; /* trees, logs, stumps: where they're being cut, and outwards there (object space) ... */ float mark; /* ... how far along (0 = no mark) */ Vec3 fall_dir; /* trees: direction the top falls */ - float fall_base_y; /* trees: ground height while falling over */ + float fall_base_y; /* trees: where it pivots falling over (the cut) ... */ + Vec3 fall_origin; /* ... from here ... */ + float fall_ground_y; /* ... and the ground it comes down on at the end (its butt slides off the stump) */ + float fall_slide; /* ... this far along fall_dir */ struct Object *stump; /* trees: the stump left behind (the trunk may touch it) */ struct Object *stink; /* puddles: the smell over it */ bool lifted; /* carried by a team: no gravity, moved by their hands */ @@ -264,6 +267,8 @@ typedef struct Object { bool tucked_set; bool trunk_cut; /* trees: felled (cut off its foot: a sawn end, no roots) */ float chair_out, chair_goal; /* ... pulled back from there this far (m), and going to this far */ + bool seat_scooted; + bool drop_tip; /* bottles: dropped (not set down): it falls over when it lands, mostly */ /* characters: sitting, the chair shuffled in to the table */ bool leaning; /* ladders: leaning against something (stands still) */ float air_fall; /* characters: in the air, falling this fast at most (m/s; for the landing) */ float ladder_skill; /* characters: practice climbing ladders one-handed (0..1) ... */ @@ -378,6 +383,7 @@ void Object_sit(Object *who, Object *seat, Vec3 toward); bool Object_settleOnSeat(Object *who, float dt); /* Up again: forgets the seat once clear of it (no bumping into it meanwhile). */ void Object_leaveSeat(Object *who, float dt); +#define CHAIR_PULL 0.38f /* m a chair is pulled out to sit on it */ /* A chair being pulled out or pushed in (towards chair_goal): slides along. */ void Object_slideChair(Object *chair, float dt); /* A seat (chair, bench) within reach of pos, or NULL. */ diff --git a/entropie/src/world/physics.c b/entropie/src/world/physics.c index f760c95..7fd3bc8 100644 --- a/entropie/src/world/physics.c +++ b/entropie/src/world/physics.c @@ -410,16 +410,22 @@ static float g_step_dt = 1.0f / 60.0f; static bool rolls(const Object *o) { switch (o->type) { - case OBJ_BOTTLE: case OBJ_VODKA: case OBJ_WATER: case OBJ_LOG: case OBJ_CYLINDER: case OBJ_APPLE: case OBJ_STONE: + case OBJ_APPLE: return true; + 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: return false; } } +#define DROP_TIPS 0.85f /* a dropped bottle falls over this often when it lands */ +#define GRIP_MOVING 0.6f /* sliding already: it grips this much less (a nudge on a slope can set it going) */ + static void slideDown(Object *obj, Vec3 normal) { float cos_a = clampf(normal.y, 0.0f, 1.0f), sin_a = sqrtf(fmaxf(0.0f, 1.0f - cos_a * cos_a)); - float pull = GRAVITY * (sin_a - (rolls(obj) ? GRIP_ROUND : GRIP_FLAT) * cos_a); + bool moving = obj->velocity.x * obj->velocity.x + obj->velocity.z * obj->velocity.z > 0.1f * 0.1f; + float pull = GRAVITY * (sin_a - (rolls(obj) ? GRIP_ROUND : GRIP_FLAT) * (moving ? GRIP_MOVING : 1.0f) * cos_a); if (pull <= 0.0f) return; Vec3 down = vec3(normal.x * normal.y, normal.y * normal.y - 1.0f, normal.z * normal.y); /* (down the slope) */ if (vec3_length(down) < 1e-4f) return; @@ -584,6 +590,13 @@ static void resolveTerrain(Object *obj, const Terrain *terrain) { obj->on_terrain = true; /* (resting right on it) */ obj->is_grounded = true; } + if (obj->on_terrain && !obj->lifted && obj->collider_count > 0 && obj->type != OBJ_LADDER && !obj->leaning) { /* on a slope: down it, if it's steep enough for it */ + Vec3 c = Object_colliderCenter(obj, 0); + const float d = 0.3f; + Vec3 n = vec3_normalize(vec3(Terrain_height(terrain, c.x - d, c.z) - Terrain_height(terrain, c.x + d, c.z), 2.0f * d, + Terrain_height(terrain, c.x, c.z - d) - Terrain_height(terrain, c.x, c.z + d))); + if (n.y < 0.995f) slideDown(obj, n); + } return; } for (int i = 0; i < obj->collider_count; i++) { @@ -1113,6 +1126,16 @@ void Physics_step(Object *obj, Object *const *objects, int count, const Terrain resolveObjects(obj, objects, count); if (obj->is_character) neverThrough(obj, start, objects, count); obj->impact = vec3_length(vec3_sub(before, obj->velocity)); + if (obj->drop_tip && obj->is_grounded) { /* a bottle dropped: it hardly ever lands standing */ + obj->drop_tip = false; + unsigned h = (unsigned)(int)(obj->position.x * 7919.0f) * 2654435761u ^ (unsigned)(int)(obj->position.z * 104729.0f); + float r = (float)(h >> 8 & 0xffffu) / 65535.0f; + if (r < DROP_TIPS) { + Vec3 dir = vec3(obj->velocity.x, 0.0f, obj->velocity.z); + if (vec3_length(dir) < 0.05f) dir = vec3(cosf(r * 40.0f), 0.0f, sinf(r * 40.0f)); + Object_topple(obj, dir); + } + } /* Walking down a slope or over a bump: stay on the ground instead of taking off for a frame (unless jumping). */ diff --git a/entropie/src/world/site.c b/entropie/src/world/site.c index bc47b16..4d44873 100644 --- a/entropie/src/world/site.c +++ b/entropie/src/world/site.c @@ -246,16 +246,16 @@ static const Vec3 WALLED_CENTER = { 23.25f, 0.0f, 0.0f }; end, see manor.c Shell_build, centered at SHELL_AT here), and in front of it the frame of tree trunks for its extension, where the bar gets built: four pillars and four beams. */ -static const Vec3 SHELL_AT = { -13.2f, 0.0f, -6.65f }; -static const Piece FRAME[] = { - { OBJ_CYLINDER_STATIC, { -10.2f, 0, -13.8f }, { 0.4f, 3.0f, 0.4f }, { 0, 0, 0 } }, - { OBJ_CYLINDER_STATIC, { -10.2f, 0, -9.2f }, { 0.4f, 3.0f, 0.4f }, { 0, 0, 0 } }, - { OBJ_CYLINDER_STATIC, { -16.2f, 0, -13.8f }, { 0.4f, 3.0f, 0.4f }, { 0, 0, 0 } }, - { OBJ_CYLINDER_STATIC, { -16.2f, 0, -9.2f }, { 0.4f, 3.0f, 0.4f }, { 0, 0, 0 } }, - { OBJ_CYLINDER_STATIC, { -10.2f, 0.7f, -11.5f }, { 0.4f, 4.6f, 0.4f }, { 90, 0, 0 } }, - { OBJ_CYLINDER_STATIC, { -16.2f, 0.7f, -11.5f }, { 0.4f, 4.6f, 0.4f }, { 90, 0, 0 } }, - { OBJ_CYLINDER_STATIC, { -13.2f, 0.0f, -13.8f }, { 0.4f, 6.0f, 0.4f }, { 90, 0, 90 } }, - { OBJ_CYLINDER_STATIC, { -13.2f, 0.0f, -9.2f }, { 0.4f, 6.0f, 0.4f }, { 90, 0, 90 } }, +static const Vec3 SHELL_AT = { -13.2f, 0.0f, -7.65f }; /* (6.7 m deep: its back where it was, longer to the front) */ +static const Piece FRAME[] = { /* (moved 2 m out when the shell got longer) */ + { OBJ_CYLINDER_STATIC, { -10.2f, 0, -15.8f }, { 0.4f, 3.0f, 0.4f }, { 0, 0, 0 } }, + { OBJ_CYLINDER_STATIC, { -10.2f, 0, -11.2f }, { 0.4f, 3.0f, 0.4f }, { 0, 0, 0 } }, + { OBJ_CYLINDER_STATIC, { -16.2f, 0, -15.8f }, { 0.4f, 3.0f, 0.4f }, { 0, 0, 0 } }, + { OBJ_CYLINDER_STATIC, { -16.2f, 0, -11.2f }, { 0.4f, 3.0f, 0.4f }, { 0, 0, 0 } }, + { OBJ_CYLINDER_STATIC, { -10.2f, 0.7f, -13.5f }, { 0.4f, 4.6f, 0.4f }, { 90, 0, 0 } }, + { OBJ_CYLINDER_STATIC, { -16.2f, 0.7f, -13.5f }, { 0.4f, 4.6f, 0.4f }, { 90, 0, 0 } }, + { OBJ_CYLINDER_STATIC, { -13.2f, 0.0f, -15.8f }, { 0.4f, 6.0f, 0.4f }, { 90, 0, 90 } }, + { OBJ_CYLINDER_STATIC, { -13.2f, 0.0f, -11.2f }, { 0.4f, 6.0f, 0.4f }, { 90, 0, 90 } }, }; static const Vec3 FRAME_CENTER = { -13.2f, 0.0f, -9.4f }; @@ -897,6 +897,7 @@ static bool furnishYard(World *world) { /* Your garages' doorways get green wooden gates (open at the start): from the hinge (z, in the front wall at x 19.25) this wide. */ +#define GATE_HINGE_X 18.94f /* the front wall's outside is at 19.0 */ static const struct { float hinge, end; } GATES[] = { { 12.5f, 11.0f }, /* the tool room door */ { 3.0f, 1.0f }, { -1.0f, 1.0f }, /* the kitchen */ @@ -906,8 +907,9 @@ static const struct { float hinge, end; } GATES[] = { static bool hangGates(World *world, Vec3 walled, float wyaw, float zsign) { for (int i = 0; i < COUNT(GATES); i++) { - Vec3 h = placed(vec3(19.25f, 0.0f, GATES[i].hinge), WALLED_CENTER, walled, wyaw, zsign); - Vec3 e = placed(vec3(19.25f, 0.0f, GATES[i].end), WALLED_CENTER, walled, wyaw, zsign); + /* (hung on the outside of the wall: they swing right round and lie flat against it) */ + Vec3 h = placed(vec3(GATE_HINGE_X, 0.0f, GATES[i].hinge), WALLED_CENTER, walled, wyaw, zsign); + Vec3 e = placed(vec3(GATE_HINGE_X, 0.0f, GATES[i].end), WALLED_CENTER, walled, wyaw, zsign); Vec3 o = placed(vec3(18.25f, 0.0f, GATES[i].hinge), WALLED_CENTER, walled, wyaw, zsign); Vec3 along = vec3_sub(e, h), out = vec3_sub(o, h); along.y = out.y = 0.0f; diff --git a/entropie/src/world/terrain.h b/entropie/src/world/terrain.h index 73c249d..21127c3 100644 --- a/entropie/src/world/terrain.h +++ b/entropie/src/world/terrain.h @@ -29,7 +29,7 @@ /* A hole in the ground (a basement under a building): no ground drawn there, and the ground's height in it is its floor. A rectangle turned by yaw. */ -#define TERRAIN_PITS 4 +#define TERRAIN_PITS 8 typedef struct { float x, z, cos_yaw, sin_yaw; /* its middle; local x is (cos, sin) */ float half_x, half_z; diff --git a/entropie/src/world/tools.c b/entropie/src/world/tools.c index c5c9bb9..cfff10f 100644 --- a/entropie/src/world/tools.c +++ b/entropie/src/world/tools.c @@ -214,7 +214,8 @@ static Plan planFor(const Object *who, ToolKind tool, Object *const *objects, in /* The cut goes in where the worker looks, within the tool's range */ Vec3 c = Object_colliderCenter(o, 0); float distance = vec3_length(flat(vec3_sub(c, who->position))); - float h = clampf(lookHeight(who, distance, spec->height * s), spec->low * s, spec->high * s); + /* (about hip high; lower only if they look lower: looking straight at the tree isn't aiming up the trunk) */ + float h = clampf(fminf(lookHeight(who, distance, spec->height * s), spec->height * s), spec->low * s, spec->high * s); float top = o->position.y + 2.0f * o->colliders[0].half_extents.y - 0.3f; float y = who->position.y + h; /* A cut already started: keep hitting the same notch, whoever swings, as long as it's in reach */ diff --git a/entropie/src/world/world.c b/entropie/src/world/world.c index 1073da4..98ab6e1 100644 --- a/entropie/src/world/world.c +++ b/entropie/src/world/world.c @@ -736,6 +736,38 @@ static bool chairWaits(const World *world, const Object *chair) { return false; } +/* A chair sliding takes whoever sits on it (or is getting up off it) along. */ +static void slideChair(World *world, Object *chair) { + Vec3 was = chair->position; + Object_slideChair(chair, world->dt_props); + Vec3 moved = vec3_sub(chair->position, was); + moved.y = 0.0f; + if (vec3_length(moved) < 1e-6f) return; + for (int i = 0; i < world->count; i++) { + Object *o = world->objects[i]; + if (!o->is_character || o->seat != chair || o->sit_moving) continue; + o->position = vec3_add(o->position, moved); + o->sit_at = vec3_add(o->sit_at, moved); + Object_syncColliders(o); + } +} + +/* Sat down on a chair: it's shuffled in towards the table (the table's edge + SEAT_TO_TABLE from the hips; none in front: just pushed in where it stood). */ +#define SEAT_TO_TABLE 0.30f +#define SEAT_LOOK 1.2f /* m: a table this near in front counts */ + +static float scootTo(const World *world, const Object *chair) { + Vec3 n = chair->colliders[0].axes[2]; + n = vec3_normalize(vec3(n.x, 0.0f, n.z)); + Vec3 from = vec3_add(chair->position, vec3(0.0f, SEAT_HEIGHT + 0.15f, 0.0f)); /* (just over the seat: a table top's edge) */ + float t = World_rayCast(world, from, n, SEAT_LOOK, NULL, NULL, NULL, NULL); + if (t < 0.0f) return 0.0f; + float hips = 0.16f; /* (the hips this far in front of the chair's middle, see Object_sit) */ + float closer = t - hips - SEAT_TO_TABLE; /* how much further in it can go from here */ + return clampf(chair->chair_out - closer, -0.15f, chair->chair_out); +} + /* The tin shows its butts, the coat rack the jackets on it: their meshes are built again when that changes. */ static void showProps(World *world) { @@ -743,7 +775,7 @@ static void showProps(World *world) { Object *f = world->objects[i]; if (f->type != OBJ_FURNITURE) continue; if (f->variant == FURN_GATE) { if (f->gate_t != (f->gate_open ? 1.0f : 0.0f)) Object_swingGate(f, world->dt_props); continue; } - if (f->variant == FURN_CHAIR) { if (f->chair_out != f->chair_goal && !chairWaits(world, f)) Object_slideChair(f, world->dt_props); continue; } + if (f->variant == FURN_CHAIR) { if (f->chair_out != f->chair_goal && !chairWaits(world, f)) slideChair(world, f); continue; } if (f->variant == FURN_ASHTRAY) { int butts = f->stored < 12 ? f->stored : 12 + (f->stored >= 14); if (butts == f->shown) continue; @@ -797,6 +829,15 @@ static void animateCharacter(World *world, Object *o, float dt) { Tools_update(o, dt); Ladder_poseHands(o, dt); /* on a ladder: hands on the rungs */ bool settling = Object_settleOnSeat(o, dt); /* over to the seat first */ + if (o->sitting && !settling && o->seat && o->seat->type == OBJ_FURNITURE && o->seat->variant == FURN_CHAIR && + o->body.sit > 0.85f && !o->seat_scooted) { /* down: shuffle the chair in to the table */ + o->seat_scooted = true; + o->seat->chair_goal = scootTo(world, o->seat); + } + if (!o->sitting && o->seat_scooted && o->seat) { /* getting up: out again first */ + o->seat_scooted = false; + if (o->seat->type == OBJ_FURNITURE && o->seat->variant == FURN_CHAIR) o->seat->chair_goal = CHAIR_PULL; + } Object_leaveSeat(o, dt); o->body.stand_ahead = o->sitting && o->seat ? 0.12f : 0.0f; /* (feet clear of it) */ o->body.on_ladder = o->on_ladder != NULL || o->climbing; /* (or pulling up over a ledge) */ @@ -1264,6 +1305,8 @@ void World_drawShadows(const World *world, Renderer *renderer) { if (obj->is_character) { FigurePose pose = figurePose(obj); /* the player casts a shadow too */ Renderer_drawFigure(renderer, &pose); + } else if (obj->type == OBJ_DOG) { + Dog_draw(renderer, obj); /* (painted like the people: the shadow pass draws her the same way) */ } else { Renderer_drawShadowCaster(renderer, &obj->mesh, Object_modelMatrix(obj)); }