#include "world/site.h" #include "world/stage.h" #include "world/field.h" #include "world/manor.h" #include "world/ladder.h" #include #include /* ---------- Map ---------- */ /* A point on the traced map, in pixels (x right, y down). */ typedef struct { float x, y; } Px; #define MAP_CENTER_X 400.0f #define MAP_CENTER_Y 385.0f #define COUNT(a) ((int)(sizeof(a) / sizeof((a)[0]))) static Vec3 at(Px p) { return vec3((p.x - MAP_CENTER_X) * SITE_SCALE, 0.0f, (p.y - MAP_CENTER_Y) * SITE_SCALE); } static Px toMap(float x, float z) { return (Px){ x / SITE_SCALE + MAP_CENTER_X, z / SITE_SCALE + MAP_CENTER_Y }; } /* Map direction (degrees, as drawn: 0 = right, 90 = down) as a world direction. */ static Vec3 mapDir(float degrees) { float a = deg2rad(degrees); return vec3(cosf(a), 0.0f, sinf(a)); } /* Turn about Y that brings an object's local +X onto dir (objects turn +X towards +Z). */ static bool insidePolygon(const Px *poly, int n, Px p) { bool in = false; for (int i = 0, j = n - 1; i < n; j = i++) { if ((poly[i].y > p.y) != (poly[j].y > p.y) && p.x < (poly[j].x - poly[i].x) * (p.y - poly[i].y) / (poly[j].y - poly[i].y) + poly[i].x) in = !in; } return in; } /* ---------- Roads ---------- */ typedef enum { ASPHALT, GRAVEL, TRACK, CONCRETE } Surface; typedef struct { const Px *points; int count; float width; /* meters */ Surface surface; const RoadLine *lines; /* painted lines, if any */ int line_count; } Road; static const Px L33[] = { { -20, 420 }, { 20, 372 }, { 100, 272 }, { 160, 195 }, { 230, 103 }, { 300, 10 }, { 320, -20 }, }; static const Px HOMBURGSHOEHE[] = { { 250, 88 }, { 300, 80 }, { 345, 95 }, { 385, 125 }, { 410, 160 }, { 440, 215 }, { 470, 268 }, { 500, 320 }, { 515, 368 }, { 525, 410 }, { 538, 460 }, { 552, 520 }, { 565, 575 }, { 585, 660 }, { 605, 740 }, { 612, 790 }, }; /* The gravel loop: from the farm past the family houses, along the top of the orchard, round the corner by the courtyard, down its open side and round the pond back to the farm. */ static const Px LOOP[] = { { 440, 440 }, { 400, 443 }, { 330, 443 }, { 290, 452 }, { 260.9f, 435.4f }, { 216.9f, 430.0f }, { 191.2f, 439.4f }, { 144.0f, 497.7f }, { 145.0f, 507.6f }, { 227.1f, 574.1f }, { 245.4f, 573.9f }, { 264.5f, 565.2f }, { 270, 566 }, { 334.9f, 592.5f }, { 345, 582 }, { 365, 545 }, { 395, 490 }, { 425, 455 }, { 470, 428 }, { 510, 395 }, { 520, 380 }, }; static const Px LOOP_WEST[] = { /* from the L33 down to the loop */ { 25, 375 }, { 80, 420 }, { 130, 462 }, { 155.8f, 483.1f }, }; static const Px COURT_PATH[] = { /* the little path between the garages and the barn */ { 134.9f, 602.2f }, { 147.7f, 612.5f }, { 165.9f, 627.3f }, { 186.3f, 646.8f }, { 202.3f, 671.9f }, }; static const Px ORCHARD_PATH[] = { /* between the orchard and the other garages */ { 173.9f, 464.6f }, { 252.3f, 528.1f }, { 256.5f, 552.7f }, }; static const Px TRACK_EAST[] = { { 322, 88 }, { 350, 68 }, { 400, 52 }, { 445, 43 }, { 470, 46 }, { 520, 63 }, { 600, 88 }, { 680, 122 }, { 720, 100 }, { 800, 60 }, }; static const Px TRACK_SOUTHEAST[] = { { 680, 122 }, { 730, 155 }, { 800, 195 } }; static const Px TRACK_WEST[] = { { -5, 570 }, { 50, 670 }, { 105, 775 } }; static const Px FARM_YARD[] = { { 538, 465 }, { 552, 530 }, { 572, 610 } }; static const Px FARM_LANE[] = { { 440, 615 }, { 500, 600 }, { 560, 590 } }; /* The L33: four lanes of 3.25 m, a double line in the middle, dashed lane lines (6 m paint, 12 m gap) and solid edge lines. */ #define L33_WIDTH 14.0f static const RoadLine L33_LINES[] = { { -0.15f, 0.12f, 0, 0 }, { 0.15f, 0.12f, 0, 0 }, { -3.25f, 0.12f, 6, 12 }, { 3.25f, 0.12f, 6, 12 }, { -6.6f, 0.2f, 0, 0 }, { 6.6f, 0.2f, 0, 0 }, }; #define ROAD(points, width, surface) { points, COUNT(points), width, surface, NULL, 0 } /* Lowest first: where roads meet, the later one lies on top. */ static const Road ROADS[] = { ROAD(COURT_PATH, 2.5f, TRACK), ROAD(ORCHARD_PATH, 2.0f, TRACK), ROAD(TRACK_EAST, 3.5f, TRACK), ROAD(TRACK_SOUTHEAST, 3.5f, TRACK), ROAD(TRACK_WEST, 3.5f, TRACK), ROAD(LOOP_WEST, 4.5f, GRAVEL), ROAD(LOOP, 5.0f, GRAVEL), ROAD(FARM_LANE, 6.0f, CONCRETE), ROAD(FARM_YARD, 16.0f, CONCRETE), ROAD(HOMBURGSHOEHE, 6.0f, ASPHALT), { L33, COUNT(L33), L33_WIDTH, ASPHALT, L33_LINES, COUNT(L33_LINES) }, }; /* Distance from (x, z) to the middle line of a road (meters). */ static float roadDistance(const Road *r, float x, float z) { float best = 1e9f; for (int i = 0; i + 1 < r->count; i++) { Vec3 a = at(r->points[i]), b = at(r->points[i + 1]); float dx = b.x - a.x, dz = b.z - a.z, len2 = dx * dx + dz * dz; float t = len2 > 0.0f ? clampf(((x - a.x) * dx + (z - a.z) * dz) / len2, 0.0f, 1.0f) : 0.0f; float ex = a.x + dx * t - x, ez = a.z + dz * t - z; best = fminf(best, sqrtf(ex * ex + ez * ez)); } return best; } /* Bounds of each road, for skipping the far ones (meters). */ static float road_box[COUNT(ROADS)][4]; static bool road_box_ready; static bool nearRoad(int i, float x, float z, float margin) { if (!road_box_ready) { for (int r = 0; r < COUNT(ROADS); r++) { float *box = road_box[r]; box[0] = box[2] = 1e9f; box[1] = box[3] = -1e9f; for (int k = 0; k < ROADS[r].count; k++) { Vec3 p = at(ROADS[r].points[k]); box[0] = fminf(box[0], p.x); box[1] = fmaxf(box[1], p.x); box[2] = fminf(box[2], p.z); box[3] = fmaxf(box[3], p.z); } } road_box_ready = true; } margin += ROADS[i].width * 0.5f; return x > road_box[i][0] - margin && x < road_box[i][1] + margin && z > road_box[i][2] - margin && z < road_box[i][3] + margin; } /* 1 on the road, fading to 0 between `from` and `to` meters past its edge. */ static float roadAmount(float x, float z, bool paved_only, float from, float to) { float most = 0.0f; for (int i = 0; i < COUNT(ROADS); i++) { if (paved_only && ROADS[i].surface != ASPHALT && ROADS[i].surface != CONCRETE) continue; if (!nearRoad(i, x, z, to)) continue; float beyond = roadDistance(&ROADS[i], x, z) - ROADS[i].width * 0.5f; most = fmaxf(most, 1.0f - smoothstepf(from, to, beyond)); } return most; } float Site_road(float x, float z) { return roadAmount(x, z, false, 1.0f, 4.0f); } float Site_paved(float x, float z) { return roadAmount(x, z, true, 2.0f, 10.0f); } /* Gravelled patches in the courtyard (a / b, see court()): from, to, width */ static const struct { float a0, b0, a1, b1, width; } GRAVEL_PATCHES[] = { { 7.0f, -8.6f, 34.0f, -8.6f, 4.0f }, /* in front of your garages, by the main door */ }; static Vec3 court(float a, float b); bool Site_hard(float x, float z) { for (int i = 0; i < COUNT(ROADS); i++) { if (ROADS[i].surface == TRACK || !nearRoad(i, x, z, 0.5f)) continue; if (roadDistance(&ROADS[i], x, z) < ROADS[i].width * 0.5f) return true; } for (int i = 0; i < COUNT(GRAVEL_PATCHES); i++) { Vec3 a = court(GRAVEL_PATCHES[i].a0, GRAVEL_PATCHES[i].b0), b = court(GRAVEL_PATCHES[i].a1, GRAVEL_PATCHES[i].b1); float dx = b.x - a.x, dz = b.z - a.z, len2 = dx * dx + dz * dz; float t = clampf(((x - a.x) * dx + (z - a.z) * dz) / len2, 0.0f, 1.0f); float ex = a.x + dx * t - x, ez = a.z + dz * t - z; if (sqrtf(ex * ex + ez * ez) < GRAVEL_PATCHES[i].width * 0.5f) return true; } return false; } /* ---------- Farm halls ---------- */ typedef struct { Px center; float length, width; /* meters */ float angle; /* direction of the ridge on the map (degrees) */ Palette look; /* see OBJ_BARN */ } Hall; #define HALL_WALL 4.0f static const Hall HALLS[] = { { { 478, 552 }, 51.0f, 15.0f, -13.0f, PALETTE_MODERN }, /* solar, left of the road */ { { 662, 510 }, 65.0f, 13.5f, -14.0f, PALETTE_GROUND }, /* right of the road */ { { 520, 507 }, 15.0f, 8.0f, 74.0f, PALETTE_GROUND }, /* by the gate */ { { 506, 618 }, 21.5f, 15.5f, 109.0f, PALETTE_GROUND }, { { 460, 660 }, 45.0f, 9.5f, 27.0f, PALETTE_STANDARD }, /* red roof */ { { 385, 712 }, 57.0f, 17.0f, 118.0f, PALETTE_MODERN }, { { 472, 728 }, 43.0f, 15.5f, 110.0f, PALETTE_MODERN }, { { 632, 643 }, 57.0f, 13.0f, 76.0f, PALETTE_MODERN }, { { 707, 632 }, 42.0f, 14.0f, 76.0f, PALETTE_GROUND }, { { 694, 728 }, 24.0f, 14.5f, 77.0f, PALETTE_MODERN }, }; /* ---------- Your buildings (traced in the old scene, moved here as a whole) ---------- */ typedef struct { ObjectType type; Vec3 position; /* where it stood in the old scene */ Vec3 size; Vec3 rotation; /* degrees */ } Piece; /* The walled building you can walk into: every piece ends on a 0.5 m grid and pieces only touch, never overlap, so no seams or flickering faces show. */ static const Piece WALLED[] = { { OBJ_BOX_STATIC, { 27.25f, 0, 0.0f }, { 0.5f, 5, 29.0f }, { 0, 0, 0 } }, /* back */ { OBJ_BOX_STATIC, { 23.25f, 0, 14.25f }, { 7.5f, 5, 0.5f }, { 0, 0, 0 } }, /* ends */ { OBJ_BOX_STATIC, { 23.25f, 0, -14.25f }, { 7.5f, 5, 0.5f }, { 0, 0, 0 } }, { OBJ_BOX_STATIC, { 23.25f, 0, 9.25f }, { 7.5f, 5, 0.5f }, { 0, 0, 0 } }, /* rooms */ { OBJ_BOX_STATIC, { 23.25f, 0, -4.25f }, { 7.5f, 5, 0.5f }, { 0, 0, 0 } }, { OBJ_BOX_STATIC, { 19.25f, 3, 0.0f }, { 0.5f, 2, 29.0f }, { 0, 0, 0 } }, /* front, above the doors */ { OBJ_BOX_STATIC, { 19.25f, 0, 13.5f }, { 0.5f, 3, 2.0f }, { 0, 0, 0 } }, /* front, between doors */ { OBJ_BOX_STATIC, { 19.25f, 0, 7.0f }, { 0.5f, 3, 8.0f }, { 0, 0, 0 } }, { OBJ_BOX_STATIC, { 19.25f, 0, -4.25f }, { 0.5f, 3, 6.5f }, { 0, 0, 0 } }, /* (the car room's doorway nearer its middle: */ { OBJ_BOX_STATIC, { 19.25f, 0, -13.25f }, { 0.5f, 3, 2.5f }, { 0, 0, 0 } }, /* open, its gates lie on the wall, not the path) */ { OBJ_BOX_STATIC, { 23.25f, 5, 0.0f }, { 8.5f, 0.5f, 29.0f }, { 0, 0, 0 } }, /* roof */ }; static const Vec3 WALLED_CENTER = { 23.25f, 0.0f, 0.0f }; /* Your unfinished building: the brick shell at the back (against the barn's 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, -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 }; /* ---------- The courtyard ---------- * A three-sided farmyard, open to the gravel road: * * main building (two roofs) * g +-----------------------+ : gravel road * a | +--+ : * r | fireplace :::::::::::::::::::::: * a | : orchard . other * g | # : . garages * e | . barn ----------- (#: your unfinished building) * s . the little path * * Measured along the yard's own axes, in meters from the middle of the main * building: a runs along the garages (down in the sketch, south-east on the * map), b across them (right in the sketch, north-east). Floors are 2.8 m. */ static const Px COURT_AT = { 88, 537 }; #define COURT_ANGLE 39.0f /* the garages' long side on the map */ #define FLOOR 2.8f static Vec3 court(float a, float b) { Vec3 along = mapDir(COURT_ANGLE), across = mapDir(COURT_ANGLE - 90.0f); return vec3_add(at(COURT_AT), vec3_add(vec3_scale(along, a), vec3_scale(across, b))); } /* door: where along the ridge the front door is (meters from the middle, on the side facing the yard); NO_DOOR keeps the usual one */ #define NO_DOOR 1e9f typedef struct { ObjectType type; Palette look; float a, b; Vec3 size; bool across; float door; } CourtDoorBuilding; #define BARN_A 45.0f /* the brick barn */ #define BARN_B 7.0f #define BARN_LEN 28.0f #define BARN_WIDTH 10.0f static const CourtDoorBuilding COURT[] = { /* (the main building is made of parts you can walk into: see manor.c, MANOR_A / MANOR_B) */ { OBJ_BARN, PALETTE_WARM, BARN_A, BARN_B, { BARN_LEN, 2.0f * FLOOR, BARN_WIDTH }, true, NO_DOOR }, /* the barn (brick) */ { OBJ_BARN, PALETTE_GROUND, 46.5f, -16.3f, { 11.0f, 3.0f, 8.4f }, false, NO_DOOR }, /* end of the garage row */ }; #define MANOR_A 1.0f /* the main building: where its two halves meet, in the middle of its depth */ #define MANOR_B -1.0f #define WALLED_A 20.6f /* your garages: along the side, right from the main building */ #define WALLED_B -14.75f #define FRAME_A 34.8f /* your unfinished building: at the right end of the barn */ #define FRAME_B 16.5f #define YARD_A 20.0f /* where you start, and the things to play with */ #define YARD_B 12.0f #define TAP_A 3.0f /* the tap: on the right end of the main building */ #define TAP_B 17.25f #define SHELTER_A 10.2f /* the pavilions: two side by side, beer tables under them, in front of the main building's */ #define SHELTER_B 3.8f /* right half, left of its porch (in a row along the front) */ #define FIRE_A 16.0f /* the fireplace: near the main building and your garages */ #define FIRE_B -4.0f #define BENCH_RADIUS 2.8f /* log benches round it */ static const float BENCH_ANGLES[] = { 90.0f, 190.0f, 290.0f }; /* in the yard's a/b plane */ #define FIRE_SIZE 2.0f /* The other garages, right of the orchard: some closed, some full of trash */ #define GARAGES_A 11.5f /* first one starts here ... */ #define GARAGES_B 48.5f /* ... doors on this side */ #define GARAGE_WIDTH 4.5f #define GARAGE_DEPTH 6.0f #define GARAGE_HEIGHT 2.6f #define GARAGE_COUNT 8 static const bool GARAGE_CLOSED[GARAGE_COUNT] = { false, true, false, true, true, false, true, false }; /* The orchard ("Obstwiese") between the gravel road and the little path: two apple trees in a meadow, bushes all round (a gap by the road) */ #define ORCHARD_A0 14.0f #define ORCHARD_A1 48.0f #define ORCHARD_B0 29.0f #define ORCHARD_B1 45.0f #define ORCHARD_GAP 21.5f /* the way in, from the road */ static const float APPLE_TREES[][2] = { { 25.0f, 37.0f }, { 37.0f, 36.0f } }; /* No forest round the courtyard, the orchard and the garages */ #define CLEAR_A0 -10.0f #define CLEAR_A1 58.0f #define CLEAR_B0 -40.0f #define CLEAR_B1 58.0f /* ---------- Other buildings ---------- */ typedef struct { ObjectType type; Palette look; Px center; Vec3 size; /* length, wall height, width (meters) */ float angle; /* ridge direction on the map (degrees) */ } Building; /* Family houses along the loop road, a bit away from the courtyard */ static const Building HOUSES[] = { { OBJ_HOUSE, PALETTE_WARM, { 209.3f, 398.4f }, { 12.0f, 2.0f * FLOOR, 9.0f }, 5.0f }, { OBJ_HOUSE, PALETTE_STANDARD, { 255.3f, 397.6f }, { 13.0f, 2.0f * FLOOR, 10.0f }, 8.0f }, { OBJ_HOUSE, PALETTE_STANDARD, { 313.4f, 411.9f }, { 18.4f, 3.4f, 9.7f }, -15.0f }, { OBJ_HOUSE, PALETTE_MODERN, { 364.5f, 404.5f }, { 11.0f, 2.0f * FLOOR, 9.0f }, 0.0f }, { OBJ_BARN, PALETTE_GROUND, { 255.3f, 491.6f }, { 7.7f, 2.6f, 5.0f }, 70.0f }, /* white shed */ }; static const Px POND_AT = { 259.7f, 611.6f }; #define POND_SIDE 13.0f static const Px BUS_STOP_AT = { 90.4f, 317.6f }; /* on the south-east side of the L33 */ /* Where a point given in a building's own (old scene) coordinates ends up: turned by yaw about its center, standing at origin, mirrored along z if zsign < 0. */ static Vec3 placed(Vec3 local, Vec3 center, Vec3 origin, float yaw, float zsign) { Vec3 rel = vec3_sub(local, center); rel.z *= zsign; return vec3_add(origin, mat4_mul_dir(mat4_rotate_yxz(vec3(0.0f, yaw, 0.0f)), rel)); } /* Places the pieces of a building as one (see placed). */ static bool placePieces(World *world, const Piece *pieces, int n, Vec3 center, Vec3 origin, float yaw, float zsign) { for (int i = 0; i < n; i++) { const Piece *p = &pieces[i]; Vec3 pos = placed(p->position, center, origin, yaw, zsign); Object *o = Object_create(p->type, PALETTE_WARM, pos, p->size, 100.0f); if (!o) return false; Object_setRotation(o, vec3(deg2rad(p->rotation.x), deg2rad(p->rotation.y) + yaw, deg2rad(p->rotation.z))); if (!World_add(world, o)) return false; } return true; } /* ---------- Forest ---------- */ typedef struct { const Px *points; int count; float spacing; /* meters between trees */ } Forest; static const Px FOREST_NW[] = { { -30, -30 }, { 310, -30 }, { 230, 95 }, { 100, 262 }, { 20, 360 }, { -30, 400 }, }; static const Px FOREST_MIDDLE[] = { { 60, 345 }, { 150, 230 }, { 230, 140 }, { 270, 130 }, { 300, 190 }, { 330, 300 }, { 380, 400 }, { 330, 425 }, { 230, 430 }, { 150, 465 }, { 110, 430 }, }; static const Px FOREST_NE[] = { { 430, -30 }, { 830, -30 }, { 830, 440 }, { 700, 450 }, { 620, 445 }, { 570, 460 }, { 545, 420 }, { 520, 330 }, { 480, 250 }, { 430, 170 }, { 400, 110 }, { 380, 40 }, }; static const Px FOREST_SOUTHWEST[] = { { -30, 400 }, { 110, 450 }, { 160, 500 }, { 230, 560 }, { 300, 600 }, { 335, 640 }, { 330, 700 }, { 250, 800 }, { 110, 800 }, { 60, 690 }, { 30, 600 }, { -30, 560 }, }; static const Px FOREST_SOUTHEAST[] = { { 760, 520 }, { 830, 500 }, { 830, 800 }, { 745, 800 }, { 745, 560 } }; /* The strip of wood right of the Kartoffelacker (about 10 m through), the guests' camping beyond it, above the chicken farm */ static const Px FOREST_STRIP[] = { { 388, 588 }, { 412, 588 }, { 412, 648 }, { 388, 648 } }; static const Forest FORESTS[] = { { FOREST_NW, COUNT(FOREST_NW), 10.0f }, { FOREST_MIDDLE, COUNT(FOREST_MIDDLE), 10.0f }, { FOREST_NE, COUNT(FOREST_NE), 11.0f }, { FOREST_SOUTHWEST, COUNT(FOREST_SOUTHWEST), 13.0f }, { FOREST_SOUTHEAST, COUNT(FOREST_SOUTHEAST), 12.0f }, { FOREST_STRIP, COUNT(FOREST_STRIP), 4.0f }, }; #define YARD_CLEARING 24.0f /* no forest this close to the yard */ #define BUILDING_CLEAR 4.0f /* ... or to a building */ #define ROAD_CLEAR 3.0f /* ... or to a road's edge */ static float hash(int x, int z, int salt) { unsigned h = (unsigned)x * 374761393u + (unsigned)z * 668265263u + (unsigned)salt * 2246822519u; h = (h ^ (h >> 13)) * 1274126177u; h ^= h >> 16; return (float)(h & 0xffffu) / 65535.0f; } /* Something built stands here (with room around it)? */ static bool nearBuilding(const World *world, Vec3 p) { for (int i = 0; i < world->count; i++) { const Object *o = world->objects[i]; if (!o->is_static || o->type == OBJ_TREE || o->type == OBJ_BUSH) continue; for (int c = 0; c < o->collider_count; c++) { const Collider *col = &o->colliders[c]; Vec3 rel = vec3_sub(p, Object_colliderCenter(o, c)); float dx = fabsf(vec3_dot(rel, col->axes[0])) - col->half_extents.x; float dz = fabsf(vec3_dot(rel, col->axes[2])) - col->half_extents.z; if (dx < BUILDING_CLEAR && dz < BUILDING_CLEAR) return true; } } return false; } static bool roadAt(Vec3 p, float clear) { for (int i = 0; i < COUNT(ROADS); i++) if (roadDistance(&ROADS[i], p.x, p.z) < ROADS[i].width * 0.5f + clear) return true; return false; } static bool inClearing(Vec3 p); /* Pines on a loose grid over each forest, leaving roads, buildings and the yard free. */ static bool plantForests(World *world) { const Vec3 yard = Site_yard(), pond = at(POND_AT); const float half = TERRAIN_SIZE * 0.5f - 2.0f; int planted = 0; for (int f = 0; f < COUNT(FORESTS); f++) { const Forest *forest = &FORESTS[f]; const float step = forest->spacing; for (int iz = (int)(-half / step); iz <= (int)(half / step); iz++) { for (int ix = (int)(-half / step); ix <= (int)(half / step); ix++) { Vec3 p = vec3(((float)ix + (hash(ix, iz, f) - 0.5f) * 0.8f) * step, 0.0f, ((float)iz + (hash(ix, iz, f + 7) - 0.5f) * 0.8f) * step); if (fabsf(p.x) > half || fabsf(p.z) > half) continue; if (!insidePolygon(forest->points, forest->count, toMap(p.x, p.z))) continue; Vec3 to_yard = vec3_sub(p, yard), to_pond = vec3_sub(p, pond); if (vec3_length(vec3(to_yard.x, 0.0f, to_yard.z)) < YARD_CLEARING) continue; if (vec3_length(vec3(to_pond.x, 0.0f, to_pond.z)) < POND_SIDE) continue; if (inClearing(p) || roadAt(p, ROAD_CLEAR) || nearBuilding(world, p)) continue; float canopy = 3.0f + 1.8f * hash(ix, iz, f + 13); float trunk = 4.5f + 3.0f * hash(ix, iz, f + 21); Object *tree = Object_create(OBJ_TREE, PALETTE_WARM, p, vec3(canopy, trunk, 0.0f), 0.0f); if (!tree || !World_add(world, tree)) return false; planted++; } } } SDL_Log("Site: %d trees", planted); return true; } /* ---------- Ground colors ---------- */ typedef struct { const Px *points; int count; Vec3 color; float amount; } Zone; static const Px DRY_MEADOW[] = { { 270, 110 }, { 340, 100 }, { 400, 140 }, { 500, 330 }, { 520, 390 }, { 470, 420 }, { 360, 300 }, { 300, 200 }, }; static const Px FIELD[] = { { -30, 630 }, { 42, 640 }, { 102, 800 }, { -30, 800 } }; static const Px FARM[] = { { 400, 460 }, { 830, 460 }, { 830, 800 }, { 330, 800 }, { 400, 560 } }; static const Px MEADOW_EAST[] = { { 570, 440 }, { 830, 430 }, { 830, 500 }, { 600, 470 } }; static const Zone ZONES[] = { { FIELD, COUNT(FIELD), { 0.56f, 0.43f, 0.28f }, 0.85f }, /* ploughed field */ { DRY_MEADOW, COUNT(DRY_MEADOW), { 0.72f, 0.66f, 0.38f }, 0.70f }, { MEADOW_EAST, COUNT(MEADOW_EAST), { 0.70f, 0.66f, 0.40f }, 0.55f }, { FARM, COUNT(FARM), { 0.68f, 0.62f, 0.48f }, 0.45f }, /* trodden farm ground */ }; #define YARD_EARTH 18.0f /* bare trodden earth this far around the middle of the yard */ Vec3 Site_groundColor(float x, float z, Vec3 meadow) { Vec3 yard = Site_yard(); float dy = sqrtf((x - yard.x) * (x - yard.x) + (z - yard.z) * (z - yard.z)); if (dy < YARD_EARTH) meadow = vec3_lerp(meadow, vec3(0.62f, 0.49f, 0.33f), 0.75f * (1.0f - smoothstepf(YARD_EARTH * 0.6f, YARD_EARTH, dy))); Px p = toMap(x, z); for (int i = 0; i < COUNT(ZONES); i++) if (insidePolygon(ZONES[i].points, ZONES[i].count, p)) return vec3_lerp(meadow, ZONES[i].color, ZONES[i].amount); for (int f = 0; f < COUNT(FORESTS); f++) /* needles and shade under the pines */ if (insidePolygon(FORESTS[f].points, FORESTS[f].count, p)) return vec3_lerp(meadow, vec3(0.38f, 0.42f, 0.24f), 0.55f); return meadow; } /* ---------- Building it ---------- */ /* ---------- Higher ground ---------- * Behind your garages the ground is 1.5 m higher: a steep bank right behind * their back wall, a gentler way up by the little path left of them, a small * meadow up there (the tents) with bushes and trees round it. Right of the * main building (towards the gravel road) a meadow 0.6 m up, bushes behind it * and on its right. */ #define CAMP_RISE 1.5f #define CAMP_A0 6.0f /* the meadow behind the garages, along them ... */ #define CAMP_A1 40.0f #define CAMP_B_WALL -19.2f /* ... from just behind their back wall ... */ #define CAMP_B1 -34.0f /* ... to its bushes at the back */ #define SIDE_RISE 0.6f #define SIDE_A0 -6.5f /* the meadow right of the main building */ #define SIDE_A1 3.0f #define SIDE_B0 18.0f #define SIDE_B1 29.0f static void courtOf(float x, float z, float *a, float *b) { Vec3 rel = vec3_sub(vec3(x, 0.0f, z), at(COURT_AT)), along = mapDir(COURT_ANGLE), across = mapDir(COURT_ANGLE - 90.0f); *a = rel.x * along.x + rel.z * along.z; *b = rel.x * across.x + rel.z * across.z; } float Site_rise(float x, float z) { float a, b; courtOf(x, z, &a, &b); /* behind the garages: the bank (steep behind them, the path's slope at their left end) */ float path = smoothstepf(34.0f, 37.0f, a); float edge = lerpf(CAMP_B_WALL, -13.5f, path), ramp = lerpf(1.6f, 9.0f, path); float camp = CAMP_RISE * smoothstepf(edge, edge - ramp, b) * smoothstepf(CAMP_A0 - 4.0f, CAMP_A0, a) * (1.0f - smoothstepf(CAMP_A1 + 1.0f, CAMP_A1 + 6.0f, a)) * (1.0f - smoothstepf(CAMP_B1 - 1.0f, CAMP_B1 - 6.0f, b)); float side = SIDE_RISE * smoothstepf(SIDE_A1 + 1.5f, SIDE_A1, a) * smoothstepf(SIDE_A0 - 2.0f, SIDE_A0, a) * smoothstepf(SIDE_B0, SIDE_B0 + 1.5f, b) * (1.0f - smoothstepf(SIDE_B1, SIDE_B1 + 2.0f, b)); return fmaxf(camp, side); } /* Your garages as placed (see Site_build): for spots inside them later. */ static struct { Vec3 origin; float yaw, zsign; bool set; } g_garages; Vec3 Site_drinksSpot(void) { /* in the kitchen of your garages, along its front wall right of the door */ if (!g_garages.set) return court(FIRE_A + 4.5f, FIRE_B + 3.0f); return placed(vec3(20.1f, 0.0f, 6.0f), WALLED_CENTER, g_garages.origin, g_garages.yaw, g_garages.zsign); } Vec3 Site_materialSpot(void) { /* out in front of the car room of your garages (its doorway): the store */ if (!g_garages.set) return court(FIRE_A + 6.0f, FIRE_B + 6.0f); return placed(vec3(16.6f, 0.0f, -9.75f), WALLED_CENTER, g_garages.origin, g_garages.yaw, g_garages.zsign); } Vec3 Site_rainRoom(void) { /* the middle of the kitchen in your garages: where the guests wait out the rain */ if (!g_garages.set) return Site_drinksSpot(); return placed(vec3(23.6f, 0.0f, 1.2f), WALLED_CENTER, g_garages.origin, g_garages.yaw, g_garages.zsign); } int Site_rainPlaces(Vec3 *out, int max) { /* under a roof together: the kitchen, the pavilions in the yard */ int n = 0; if (n < max) out[n++] = Site_rainRoom(); if (n < max) out[n++] = court(SHELTER_A, SHELTER_B); return n; } Vec3 Site_houseDoor(void) { return court(MANOR_A + 6.0f, MANOR_B - 7.6f); } /* (out in front of the main building's left door) */ bool Site_inHouse(float x, float z) { float a, b; courtOf(x, z, &a, &b); return a > MANOR_A - 5.5f && a < MANOR_A + 7.5f && b > MANOR_B - 18.5f && b < MANOR_B + 18.5f; } Vec3 Site_campSpot(int k) { static const float AT[3][2] = { { 13.0f, -27.5f }, { 22.0f, -28.0f }, { 31.0f, -27.5f } }; /* (up on the meadow behind your garages) */ k = k < 0 ? 0 : k > 2 ? 2 : k; return court(AT[k][0], AT[k][1]); } Vec3 Site_campAlong(void) { return mapDir(COURT_ANGLE); } Vec3 Site_campDoor(void) { return mapDir(COURT_ANGLE - 90.0f); } /* (towards the garages' back wall) */ void Site_arrivalPath(Vec3 *points, int *n, float *park_yaw) { /* off the street onto the gravel road beside the main building, up along it, into the yard */ static const float AT[][2] = { { -65.0f, 52.0f }, { -64.0f, 41.0f }, { -56.0f, 36.0f }, { -20.0f, 36.0f }, { 2.0f, 36.0f }, { 8.0f, 32.0f }, { 9.5f, 26.0f }, { 14.0f, 21.5f }, { 20.0f, 19.8f }, { 25.0f, 19.5f } }; *n = COUNT(AT); for (int i = 0; i < *n; i++) points[i] = court(AT[i][0], AT[i][1]); *park_yaw = yaw_facing(mapDir(COURT_ANGLE)); /* (nose to the barn, the boot to the main building) */ } Vec3 Site_yard(void) { return court(YARD_A, YARD_B); } /* Inside the cleared ground round the courtyard? */ static bool inClearing(Vec3 p) { Vec3 rel = vec3_sub(p, at(COURT_AT)), along = mapDir(COURT_ANGLE), across = mapDir(COURT_ANGLE - 90.0f); float a = rel.x * along.x + rel.z * along.z, b = rel.x * across.x + rel.z * across.z; return a > CLEAR_A0 && a < CLEAR_A1 && b > CLEAR_B0 && b < CLEAR_B1; } float Site_grass(float x, float z) { float road = Site_road(x, z); if (road > 0.6f) return 0.0f; Vec3 yard = Site_yard(); float dy = sqrtf((x - yard.x) * (x - yard.x) + (z - yard.z) * (z - yard.z)); float amount = 1.0f - 0.75f * (1.0f - smoothstepf(YARD_EARTH * 0.6f, YARD_EARTH, dy)); /* trodden yard */ Px p = toMap(x, z); if (insidePolygon(FIELD, COUNT(FIELD), p)) amount = 0.1f; else for (int f = 0; f < COUNT(FORESTS); f++) if (insidePolygon(FORESTS[f].points, FORESTS[f].count, p) && !inClearing(vec3(x, 0.0f, z))) { amount *= 0.4f; break; } return amount * (1.0f - road); } Vec3 Site_landmark(void) { return court(0.0f, 0.0f); } #define YARD_LEVEL 20.0f /* the yard is level this far out, then blends into the hills */ float Site_flat(float x, float z) { Vec3 yard = Site_yard(), pond = at(POND_AT); float dy = sqrtf((x - yard.x) * (x - yard.x) + (z - yard.z) * (z - yard.z)); float dp = sqrtf((x - pond.x) * (x - pond.x) + (z - pond.z) * (z - pond.z)); return fmaxf(1.0f - smoothstepf(YARD_LEVEL, YARD_LEVEL + 14.0f, dy), 1.0f - smoothstepf(POND_SIDE * 0.75f, POND_SIDE * 0.75f + 8.0f, dp)); } Soil Site_soil(float x, float z) { Soil soil = SOIL_COUNT; for (int i = 0; i < COUNT(ROADS); i++) { /* the topmost road here */ if (!nearRoad(i, x, z, 0.5f) || roadDistance(&ROADS[i], x, z) >= ROADS[i].width * 0.5f) continue; soil = ROADS[i].surface == TRACK ? SOIL_TRACK : ROADS[i].surface == GRAVEL ? SOIL_GRAVEL : SOIL_PAVED; } if (soil != SOIL_COUNT) return soil; if (Site_hard(x, z)) return SOIL_GRAVEL; /* the gravel patches */ Px p = toMap(x, z); if (insidePolygon(FIELD, COUNT(FIELD), p)) return SOIL_FIELD; Vec3 yard = Site_yard(); float dy = sqrtf((x - yard.x) * (x - yard.x) + (z - yard.z) * (z - yard.z)); if (dy < YARD_EARTH * 0.8f || insidePolygon(FARM, COUNT(FARM), p)) return SOIL_YARD; for (int f = 0; f < COUNT(FORESTS); f++) if (insidePolygon(FORESTS[f].points, FORESTS[f].count, p) && !inClearing(vec3(x, 0.0f, z))) return SOIL_FOREST; return SOIL_MEADOW; } /* ---------- Surfaces ---------- */ #define TAR_TOP 0.06f /* tar and its gravel bed above the soil ... */ #define TAR_CAMBER 0.02f /* ... higher in the middle, so rain runs off */ #define TAR_SHOULDER 0.5f /* the gravel shoulder beside it (m) */ #define SLAB_TOP 0.05f /* concrete */ #define SLAB_SHOULDER 0.3f #define GRAVEL_TOP 0.02f /* stones on the soil */ #define RUT_DEPTH 0.025f /* wheels press ruts in */ #define TRACK_DEPTH 0.04f /* a track is worn into the ground ... */ #define TRACK_RUT 0.03f /* ... the ruts deeper still */ /* Smooth noise 0..1 at about one meter (for ragged edges). */ static float noise2(float x, float z, int salt) { float fx = floorf(x), fz = floorf(z); int ix = (int)fx, iz = (int)fz; float u = x - fx, v = z - fz; u = u * u * (3.0f - 2.0f * u); v = v * v * (3.0f - 2.0f * v); return lerpf(lerpf(hash(ix, iz, salt), hash(ix + 1, iz, salt), u), lerpf(hash(ix, iz + 1, salt), hash(ix + 1, iz + 1, salt), u), v); } /* Lays something over what's there, by c (0..1). */ static void cover(SiteSurface *s, float c, float asphalt, float concrete, float gravel, float earth, float lift) { if (c <= 0.0f) return; s->asphalt = lerpf(s->asphalt, asphalt, c); s->concrete = lerpf(s->concrete, concrete, c); s->gravel = lerpf(s->gravel, gravel, c); s->earth = lerpf(s->earth, earth, c); s->lift = lerpf(s->lift, lift, c); } /* A paved strip: the slab (tar or concrete) with a sharp edge, on a shoulder of gravel and soil that slopes down to the meadow. e: meters inside the edge. */ static void paved(SiteSurface *s, float e, float across, bool tar) { const float shoulder = tar ? TAR_SHOULDER : SLAB_SHOULDER; const float top = tar ? TAR_TOP + TAR_CAMBER * (1.0f - across * across) : SLAB_TOP; /* (the shoulder only lies on soft ground: next to another slab, tar meets it directly) */ float side = (1.0f - smoothstepf(shoulder * 0.5f, shoulder, -e)) * (1.0f - fminf(s->asphalt + s->concrete, 1.0f)); float bed = clampf(1.0f + e / shoulder, 0.0f, 1.0f); cover(s, side, 0.0f, 0.0f, tar ? 0.8f : 0.4f, tar ? 0.2f : 0.6f, 0.6f * top * bed); cover(s, smoothstepf(-0.02f, 0.03f, e), tar ? 1.0f : 0.0f, tar ? 0.0f : 1.0f, 0.0f, 0.0f, top); } /* Gravel or a soil track: ruts where the wheels go, grass in the middle and creeping in from the edges. a: 0 in the middle .. 0.5 at the edge. */ static void unpaved(SiteSurface *s, float e, float a, bool gravel, bool ruts, float rag) { float c = smoothstepf(-0.15f, 0.35f, e + rag); float rut = ruts ? 1.0f - smoothstepf(0.0f, 0.08f, fabsf(a - 0.24f)) : 0.0f; float grass = (ruts ? (gravel ? 0.45f : 0.6f) * (1.0f - smoothstepf(0.04f, 0.12f, a)) : 0.0f) + 0.5f * smoothstepf(0.38f, 0.5f, a); float keep = 1.0f - fminf(grass, 1.0f); if (gravel) cover(s, c, 0.0f, 0.0f, (1.0f - 0.55f * rut) * keep, 0.55f * rut * keep, GRAVEL_TOP - RUT_DEPTH * rut); else cover(s, c, 0.0f, 0.0f, 0.0f, keep, -TRACK_DEPTH * (1.0f - smoothstepf(0.3f, 0.5f, a)) - TRACK_RUT * rut); } bool Site_surfaceNear(float x, float z, float radius) { const float reach = radius + TAR_SHOULDER + 0.5f; for (int i = 0; i < COUNT(ROADS); i++) if (nearRoad(i, x, z, reach) && roadDistance(&ROADS[i], x, z) < ROADS[i].width * 0.5f + reach) return true; for (int i = 0; i < COUNT(GRAVEL_PATCHES); i++) { Vec3 a = court(GRAVEL_PATCHES[i].a0, GRAVEL_PATCHES[i].b0), b = court(GRAVEL_PATCHES[i].a1, GRAVEL_PATCHES[i].b1); float dx = b.x - a.x, dz = b.z - a.z, len2 = dx * dx + dz * dz; float t = clampf(((x - a.x) * dx + (z - a.z) * dz) / len2, 0.0f, 1.0f); float ex = a.x + dx * t - x, ez = a.z + dz * t - z; if (sqrtf(ex * ex + ez * ez) < GRAVEL_PATCHES[i].width * 0.5f + 1.0f + radius) return true; } return false; } SiteSurface Site_surface(float x, float z) { SiteSurface s = { 0 }; float rag = (noise2(x * 1.3f, z * 1.3f, 71) - 0.5f) * 0.3f; for (int i = 0; i < COUNT(ROADS); i++) { /* lowest first: later roads lie on top */ const Road *r = &ROADS[i]; if (!nearRoad(i, x, z, TAR_SHOULDER + 0.5f)) continue; float half = r->width * 0.5f, d = roadDistance(r, x, z), e = half - d; if (e < -(TAR_SHOULDER + 0.4f)) continue; switch (r->surface) { case ASPHALT: paved(&s, e + rag * 0.25f, clampf(d / half, 0.0f, 1.0f), true); break; case CONCRETE: paved(&s, e + rag * 0.25f, clampf(d / half, 0.0f, 1.0f), false); break; case GRAVEL: unpaved(&s, e, d / r->width, true, true, rag); break; case TRACK: unpaved(&s, e, d / r->width, false, true, rag); break; } } for (int i = 0; i < COUNT(GRAVEL_PATCHES); i++) { Vec3 a = court(GRAVEL_PATCHES[i].a0, GRAVEL_PATCHES[i].b0), b = court(GRAVEL_PATCHES[i].a1, GRAVEL_PATCHES[i].b1); float dx = b.x - a.x, dz = b.z - a.z, len2 = dx * dx + dz * dz; float t = clampf(((x - a.x) * dx + (z - a.z) * dz) / len2, 0.0f, 1.0f); float ex = a.x + dx * t - x, ez = a.z + dz * t - z, d = sqrtf(ex * ex + ez * ez); float half = GRAVEL_PATCHES[i].width * 0.5f; if (d < half + 1.0f) unpaved(&s, half - d, 0.5f * d / half, true, false, rag); } return s; } static bool addStatic(World *world, ObjectType type, Palette look, Vec3 at_pos, Vec3 size, float yaw) { Object *o = Object_create(type, look, at_pos, size, 100.0f); if (!o) return false; Object_setRotation(o, vec3(0.0f, yaw, 0.0f)); return World_add(world, o); } /* Position along the yard (a), for telling which end of something is nearer the main building. */ static float courtA(Vec3 p) { Vec3 rel = vec3_sub(p, at(COURT_AT)), along = mapDir(COURT_ANGLE); return rel.x * along.x + rel.z * along.z; } /* Inside your garages: in their own coordinates (x = across, 19.5 at the doors .. 27 at the back wall; z = along, the tool room at +z). Furniture fronts face the doors. */ typedef struct { FurnitureKind kind; Vec3 at; /* y unused */ Vec3 size; float turn; /* extra turn (radians); 0 = front towards the doors */ } RoomPiece; static const RoomPiece ROOMS[] = { /* 1: tools */ { FURN_WORKBENCH, { 26.6f, 0, 12.5f }, { 2.4f, 0.9f, 0.7f }, 0.0f }, { FURN_TOOLWALL, { 26.92f, 0, 12.5f }, { 2.4f, 2.2f, 0.06f }, 0.0f }, { FURN_SHELF, { 26.75f, 0, 10.3f }, { 1.2f, 1.8f, 0.4f }, 0.0f }, /* 2: kitchen */ { FURN_COUNTER, { 26.65f, 0, 5.2f }, { 3.0f, 0.9f, 0.6f }, 0.0f }, { FURN_STOVE, { 26.65f, 0, 3.2f }, { 0.6f, 0.9f, 0.6f }, 0.0f }, { FURN_SHELF, { 26.75f, 0, 7.8f }, { 1.2f, 1.8f, 0.4f }, 0.0f }, { FURN_TABLE, { 23.3f, 0, 4.0f }, { 1.6f, 0.75f, 0.9f }, 0.0f }, { FURN_BENCH, { 26.7f, 0, -1.6f }, { 2.2f, SEAT_HEIGHT, 0.36f }, 0.0f }, /* (along the back wall: waiting out the rain) */ { 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 }, /* 3: car repairs (the wide door) */ { FURN_CAR, { 23.4f, 0, -9.8f }, { 1.8f, 1.5f, 4.2f }, 0.0f }, { FURN_WORKBENCH, { 26.6f, 0, -6.6f }, { 2.4f, 0.9f, 0.7f }, 0.0f }, { FURN_TIRES, { 25.0f, 0, -5.3f }, { 0.65f, 0.9f, 0.65f }, 0.0f }, }; static bool furnishGarages(World *world, Vec3 origin, float yaw, float zsign) { for (int i = 0; i < COUNT(ROOMS); i++) { const RoomPiece *r = &ROOMS[i]; Vec3 pos = placed(r->at, WALLED_CENTER, origin, yaw, zsign); pos.y = 0.0f; Object *o = Object_createFurniture(r->kind, pos, r->size, yaw + PI_F / 2.0f + r->turn); if (!o || !World_add(world, o)) return false; } /* The tools: on and by the workbench in the tool room */ struct { ObjectType type; Vec3 at, size, rot; float kg; int variant; } TOOLS[] = { { OBJ_AXE, { 26.5f, 1.0f, 12.0f }, { 0.0f, 0.8f, 0.0f }, { 90, 0, 0 }, 2.2f, 0 }, { OBJ_SHEARS, { 26.5f, 1.0f, 13.2f }, { 0.0f, 0.3f, 0.0f }, { 90, 20, 0 }, 0.4f, 0 }, { OBJ_TAPE, { 26.5f, 1.0f, 12.6f }, { 0.1f, 0.05f, 0.1f }, { 0, 0, 0 }, 0.2f, 0 }, { OBJ_CHAINSAW, { 25.6f, 0.3f, 12.0f }, { 0.0f, 0.0f, 0.0f }, { 0, 60, 0 }, 6.0f, 0 }, { OBJ_LADDER, { 20.9f, 0.1f, 13.55f }, { 0.0f, 0.0f, 0.0f }, { 90, 90, 0 }, 9.5f, 0 }, /* slid together, along the end wall */ { OBJ_LADDER, { 23.2f, 0.1f, 13.55f }, { 0.0f, 0.0f, 0.0f }, { 90, 90, 0 }, 9.8f, LADDER_FOLDING }, /* folded, beside it */ { OBJ_LADDER, { 21.0f, 0.1f, 12.6f }, { 0.0f, 0.0f, 0.0f }, { 90, 90, 0 }, 7.0f, LADDER_STEP }, /* folded, in front of them */ { OBJ_SCYTHE, { 24.6f, 0.1f, 11.0f }, { 0.0f, 0.0f, 0.0f }, { 90, 90, 0 }, 2.5f, 0 }, /* on the floor, along the room */ }; for (unsigned i = 0; i < sizeof(TOOLS) / sizeof(TOOLS[0]); i++) { Vec3 pos = placed(TOOLS[i].at, WALLED_CENTER, origin, yaw, zsign); Object *o = Object_create(TOOLS[i].type, PALETTE_WARM, pos, TOOLS[i].size, TOOLS[i].kg); if (!o) return false; Object_setRotation(o, vec3(deg2rad(TOOLS[i].rot.x), deg2rad(TOOLS[i].rot.y) + yaw, deg2rad(TOOLS[i].rot.z))); if (TOOLS[i].variant) { o->variant = TOOLS[i].variant; Ladder_setExtend(o, 0.0f); } if (!World_add(world, o)) return false; } return true; } /* A static box standing square to the courtyard: size along a, height, along b. */ static bool courtBox(World *world, Palette look, float a, float b, float y, Vec3 size) { Vec3 p = court(a, b); p.y = y; return addStatic(world, OBJ_BOX_STATIC, look, p, size, yaw_along(mapDir(COURT_ANGLE))); } /* Something loose, dropped a little above the ground (it falls into place). */ static bool addLoose(World *world, ObjectType type, Palette look, Vec3 at_pos, Vec3 size, float weight, Vec3 rotation) { Object *o = Object_create(type, look, vec3_add(at_pos, vec3(0.0f, 0.15f, 0.0f)), size, weight); if (!o) return false; Object_setRotation(o, rotation); return World_add(world, o); } static const Palette TRASH_KINDS[] = { PALETTE_WARM, PALETTE_MODERN, PALETTE_STANDARD, PALETTE_WARM, PALETTE_MODERN, PALETTE_GROUND }; static const float TRASH_WEIGHT[] = { [PALETTE_WARM] = 0.05f, [PALETTE_MODERN] = 0.03f, [PALETTE_STANDARD] = 0.04f, [PALETTE_GROUND] = 1.2f }; static bool addTrash(World *world, Vec3 where, int n) { Palette kind = TRASH_KINDS[n % (int)(sizeof(TRASH_KINDS) / sizeof(TRASH_KINDS[0]))]; return addLoose(world, OBJ_TRASH, kind, where, vec3_zero(), TRASH_WEIGHT[kind], vec3(0.0f, (float)n * 2.3f, 0.0f)); } /* A guest's litter: paper, a can, a plastic cup, there (by: whose it was). */ Object *Site_litter(World *world, Vec3 at, int n, struct Object *by) { static const Palette KINDS[] = { PALETTE_WARM, PALETTE_MODERN, PALETTE_STANDARD }; Palette kind = KINDS[(unsigned)n % 3u]; Object *o = Object_create(OBJ_TRASH, kind, vec3_add(at, vec3(0.0f, 0.15f, 0.0f)), vec3_zero(), TRASH_WEIGHT[kind]); if (!o) return NULL; Object_setRotation(o, vec3(0.0f, (float)n * 2.3f, 0.0f)); o->owner = by; return World_add(world, o) ? o : NULL; } /* The other garages: a row of eight under one roof, doors towards the orchard. The open ones are full of junk. */ static bool buildGarages(World *world) { const float length = GARAGE_WIDTH * GARAGE_COUNT, mid = GARAGES_A + length * 0.5f; const float back = GARAGES_B + GARAGE_DEPTH; if (!courtBox(world, PALETTE_STANDARD, mid, back - 0.15f, 0.0f, vec3(length, GARAGE_HEIGHT, 0.3f)) || !courtBox(world, PALETTE_GROUND, mid, GARAGES_B + GARAGE_DEPTH * 0.5f, GARAGE_HEIGHT, vec3(length + 0.6f, 0.2f, GARAGE_DEPTH + 0.4f))) return false; for (int k = 0; k <= GARAGE_COUNT; k++) if (!courtBox(world, PALETTE_STANDARD, GARAGES_A + GARAGE_WIDTH * (float)k, GARAGES_B + GARAGE_DEPTH * 0.5f - 0.15f, 0.0f, vec3(0.25f, GARAGE_HEIGHT, GARAGE_DEPTH - 0.3f))) return false; if (!courtBox(world, PALETTE_STANDARD, mid, GARAGES_B + 0.1f, GARAGE_HEIGHT - 0.3f, vec3(length, 0.3f, 0.2f))) return false; /* over the doors */ int n = 0; for (int k = 0; k < GARAGE_COUNT; k++) { float a = GARAGES_A + GARAGE_WIDTH * ((float)k + 0.5f); if (GARAGE_CLOSED[k]) { if (!courtBox(world, PALETTE_WARM, a, GARAGES_B + 0.1f, 0.0f, vec3(GARAGE_WIDTH - 0.3f, GARAGE_HEIGHT - 0.3f, 0.08f))) return false; continue; } for (int t = 0; t < 2; t++, n++) { /* (a bit of junk) */ float u = hash(k, t, 31) - 0.5f, v = hash(k, t, 37); Vec3 at_pos = court(a + u * (GARAGE_WIDTH - 1.0f), GARAGES_B + 0.8f + v * (GARAGE_DEPTH - 1.6f)); at_pos.y = 0.1f + 0.3f * hash(k, t, 41); if (!addTrash(world, at_pos, n)) return false; } } return true; } /* The orchard: two apple trees, bushes all round */ static bool plantOrchard(World *world) { for (int i = 0; i < 2; i++) { Object *tree = Object_create(OBJ_TREE, PALETTE_STANDARD, court(APPLE_TREES[i][0], APPLE_TREES[i][1]), vec3(5.2f + 0.8f * (float)i, 1.7f, 0.0f), 0.0f); if (!tree || !World_add(world, tree)) return false; } const float step = 1.5f; const float side_a = ORCHARD_A1 - ORCHARD_A0, side_b = ORCHARD_B1 - ORCHARD_B0; const int along = (int)(side_a / step), across = (int)(side_b / step); int n = 0; for (int k = 0; k < 2 * (along + across); k++, n++) { float a, b; if (k < along) { a = ORCHARD_A0 + step * (float)k; b = ORCHARD_B0; } else if (k < along + across) { a = ORCHARD_A1; b = ORCHARD_B0 + step * (float)(k - along); } else if (k < 2 * along + across) { a = ORCHARD_A1 - step * (float)(k - along - across); b = ORCHARD_B1; } else { a = ORCHARD_A0; b = ORCHARD_B1 - step * (float)(k - 2 * along - across); } if (b == ORCHARD_B0 && fabsf(a - ORCHARD_GAP) < 2.0f) continue; /* the way in */ a += (hash(k, 1, 71) - 0.5f) * 0.6f; b += (hash(k, 2, 73) - 0.5f) * 0.6f; Vec3 size = vec3(1.5f + 0.8f * hash(k, 3, 79), 1.3f + 0.7f * hash(k, 4, 83), 0.0f); Object *bush = Object_create(OBJ_BUSH, PALETTE_WARM, court(a, b), size, 0.0f); if (!bush || !World_add(world, bush)) return false; } return true; } /* The meadows up the banks: behind your garages (bushes round it, trees that stay) and right of the main building (bushes behind it and on its right). */ static bool addBush(World *world, float a, float b, int k) { a += (hash(k, 5, 91) - 0.5f) * 0.6f; b += (hash(k, 6, 93) - 0.5f) * 0.6f; Vec3 size = vec3(1.6f + 0.9f * hash(k, 7, 97), 1.4f + 0.8f * hash(k, 8, 101), 0.0f); Object *bush = Object_create(OBJ_BUSH, PALETTE_WARM, court(a, b), size, 0.0f); return bush && World_add(world, bush); } static bool plantMeadows(World *world) { int k = 0; for (float a = CAMP_A0; a <= CAMP_A1; a += 1.7f) if (!addBush(world, a, CAMP_B1, k++)) return false; /* the back */ for (float b = -21.5f; b > CAMP_B1; b -= 1.7f) if (!addBush(world, CAMP_A0 - 0.8f, b, k++)) return false; /* the right */ for (float b = -25.0f; b > CAMP_B1; b -= 1.7f) if (!addBush(world, CAMP_A1 + 0.8f, b, k++)) return false; /* the left, past the way up */ static const float TREES[][2] = { { 8.0f, -36.0f }, { 17.5f, -37.0f }, { 27.0f, -36.5f }, { 36.5f, -35.5f }, { 43.0f, -29.0f }, { 3.5f, -27.0f } }; for (int i = 0; i < COUNT(TREES); i++) { Object *tree = Object_create(OBJ_TREE, PALETTE_WARM, court(TREES[i][0], TREES[i][1]), vec3(4.0f + 1.2f * hash(i, 9, 103), 3.5f + 2.0f * hash(i, 10, 107), 0.0f), 0.0f); if (!tree) return false; tree->keep = true; if (!World_add(world, tree)) return false; } for (float b = SIDE_B0 + 0.5f; b <= SIDE_B1; b += 1.7f) if (!addBush(world, SIDE_A0 - 0.8f, b, k++)) return false; /* behind */ for (float a = SIDE_A0; a <= SIDE_A1; a += 1.7f) if (!addBush(world, a, SIDE_B1 + 0.8f, k++)) return false; /* on its right */ return true; } /* The fireplace with some wood beside it, sausages and garbage bags, the container, and the yard's litter. */ 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_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; for (int k = 0; k < 3; k++) { /* the tripod's poles: solid (you walk round them, not through) */ Vec3 foot = vec3_add(fire, Shapes_tripodFoot(FIRE_SIZE, k)), top = vec3_add(fire, vec3(0.0f, GRILL_TOP, 0.0f)); foot.y = fire.y; Vec3 d = vec3_sub(top, foot); float len = vec3_length(d); d = vec3_scale(d, 1.0f / len); Vec3 mid = vec3_scale(vec3_add(foot, top), 0.5f); Object *pole = Object_create(OBJ_WALL, PALETTE_WARM, vec3(mid.x, mid.y - len * 0.5f, mid.z), vec3(0.1f, len, 0.1f), 1000.0f); if (!pole) return false; float tilt = acosf(clampf(d.y, -1.0f, 1.0f)), best = -1.0f; Vec3 pick = vec3_zero(); for (int s = 0; s < 4; s++) { /* (the turn that lays its length along the pole) */ Vec3 r = vec3((s & 1 ? -1.0f : 1.0f) * tilt, atan2f((s & 2 ? -1.0f : 1.0f) * d.x, d.z), 0.0f); Object_setRotation(pole, r); float fit = fabsf(vec3_dot(pole->colliders[0].axes[1], d)); if (fit > best) { best = fit; pick = r; } } Object_setRotation(pole, pick); snprintf(pole->name, sizeof(pole->name), "Tripod pole"); if (!World_add(world, pole)) return false; } for (int k = 0; k < 3; k++) { /* log benches round the fire */ float a = deg2rad(BENCH_ANGLES[k]); Vec3 tangent = vec3_add(vec3_scale(mapDir(COURT_ANGLE), -sinf(a)), vec3_scale(mapDir(COURT_ANGLE - 90.0f), cosf(a))); 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; 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); if (!addLoose(world, OBJ_FIREWOOD, PALETTE_WARM, at_pos, vec3(0.3f, 0.45f, 4.0f), 0.0f, vec3(PI_F / 2.0f, 0.3f * (float)k, 0.0f))) return false; } for (int k = 0; k < 3; k++) if (!addLoose(world, OBJ_BRANCH, PALETTE_WARM, court(FIRE_A - 1.0f - 0.35f * (float)k, FIRE_B - 2.4f), vec3(0.0f, 1.1f, 0.0f), 1.2f, vec3(PI_F / 2.0f, 1.0f + 0.4f * (float)k, 0.0f))) return false; for (int t = 0; t < 4; t++) { /* a little litter in the yard */ float a = 8.0f + 30.0f * hash(t, 3, 61), b = -9.0f + 29.0f * hash(t, 5, 67); Vec3 at_pos = court(a, b); if (vec3_length(vec3_sub(at_pos, fire)) < 2.0f) continue; if (!addTrash(world, at_pos, t)) return false; } return true; } /* 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 */ { -7.5f, -9.75f }, { -12.0f, -9.75f }, /* the car repairs (opened, the left leaf ends at the corner) */ }; #define GATE_HEIGHT 2.9f static bool hangGates(World *world, Vec3 walled, float wyaw, float zsign) { for (int i = 0; i < COUNT(GATES); i++) { /* (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; float w = vec3_length(along) - 0.04f; Object *g = Object_createFurniture(FURN_GATE, h, vec3(w, GATE_HEIGHT, 0.06f), 0.0f); if (!g) return false; g->hinge = h; g->gate_shut = vec3_normalize(along); g->gate_out = vec3_normalize(out); g->gate_open = true; g->gate_t = 1.0f; Object_swingGate(g, 0.0f); if (!World_add(world, g)) return false; } return true; } /* The barn: ivy climbing high up its walls; the woodpile against it, left of the toilet. */ #define WOODPILE_A 39.1f #define WOODPILE_B -1.5f static bool dressBarn(World *world, Vec3 center, float yaw) { const Mat4 r = mat4_rotate_yxz(vec3(0.0f, yaw, 0.0f)); Vec3 out = mat4_mul_dir(r, vec3(0.0f, 0.0f, 1.0f)); /* (which long side faces the yard) */ float front = vec3_dot(out, vec3_sub(Site_yard(), center)) > 0.0f ? 1.0f : -1.0f; static const struct { float x, width, height; bool yard_side; } IVY[] = { { -11.0f, 2.2f, 5.2f, true }, { -4.5f, 1.6f, 4.4f, true }, { 4.0f, 2.6f, 5.5f, true }, { 10.5f, 1.8f, 4.8f, true }, { -8.0f, 2.4f, 5.3f, false }, { 7.0f, 2.0f, 4.6f, false }, }; for (int i = 0; i < COUNT(IVY); i++) { float side = IVY[i].yard_side ? front : -front; Vec3 at = vec3_add(center, mat4_mul_dir(r, vec3(IVY[i].x, 0.0f, side * (BARN_WIDTH * 0.5f + 0.01f)))); Mesh mesh = Shapes_ivy(IVY[i].width, IVY[i].height, 101u + (unsigned)i * 37u); if (!mesh.vao) return false; Object *ivy = Object_createStructure(mesh, at, yaw + (side > 0.0f ? 0.0f : PI_F), vec3(IVY[i].width, IVY[i].height, 0.2f)); if (!ivy || !World_add(world, ivy)) return false; } const Vec3 size = { 1.6f, 0.0f, 0.6f }; Object *pile = Object_create(OBJ_WOODPILE, PALETTE_WARM, court(WOODPILE_A, WOODPILE_B), size, 0.0f); if (!pile) return false; Object_setRotation(pile, vec3(0.0f, yaw, 0.0f)); snprintf(pile->name, sizeof(pile->name), "Woodpile"); return World_add(world, pile); } bool Site_build(World *world) { /* The courtyard */ const float along = yaw_along(mapDir(COURT_ANGLE)), across = yaw_along(mapDir(COURT_ANGLE - 90.0f)); for (int i = 0; i < COUNT(COURT); i++) { const CourtDoorBuilding *c = &COURT[i]; Object *o = Object_create(c->type, c->look, court(c->a, c->b), c->size, 100.0f); if (!o) return false; Object_setRotation(o, vec3(0.0f, c->across ? across : along, 0.0f)); if (c->door != NO_DOOR) Object_setDoor(o, c->door); if (!World_add(world, o)) return false; } if (!Manor_build(world, court(MANOR_A, MANOR_B), across)) return false; if (!dressBarn(world, court(BARN_A, BARN_B), across)) return false; /* Your garages turn their doors (local -X) to the yard; your unfinished building stands square to the barn, its long side along the yard. */ Vec3 walled = court(WALLED_A, WALLED_B), dir = mapDir(COURT_ANGLE); Vec3 front = vec3(dir.z, 0.0f, -dir.x); if (vec3_dot(front, vec3_sub(Site_yard(), walled)) < 0.0f) front = vec3_scale(front, -1.0f); /* The small tool room goes next to the main building: mirror the walls if needed */ const float wyaw = yaw_along(vec3_scale(front, -1.0f)); float zsign = courtA(placed(vec3(23.25f, 0.0f, 12.0f), WALLED_CENTER, walled, wyaw, 1.0f)) < courtA(placed(vec3(23.25f, 0.0f, -12.0f), WALLED_CENTER, walled, wyaw, 1.0f)) ? 1.0f : -1.0f; g_garages.origin = walled; g_garages.yaw = wyaw; g_garages.zsign = zsign; g_garages.set = true; if (!placePieces(world, WALLED, COUNT(WALLED), WALLED_CENTER, walled, wyaw, zsign) || !placePieces(world, FRAME, COUNT(FRAME), FRAME_CENTER, court(FRAME_A, FRAME_B), across, 1.0f) || !Shell_build(world, placed(SHELL_AT, FRAME_CENTER, court(FRAME_A, FRAME_B), across, 1.0f), across) || !Shelter_build(world, court(SHELTER_A, SHELTER_B), yaw_along(mapDir(COURT_ANGLE - 90.0f))) || !furnishGarages(world, walled, wyaw, zsign) || !hangGates(world, walled, wyaw, zsign)) return false; if (!buildGarages(world) || !plantOrchard(world) || !plantMeadows(world) || !furnishYard(world)) return false; /* The tap on the right end of the main building, 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(TAP_A, TAP_B), vec3(0.3f, 1.0f, 0.3f), yaw_facing(mapDir(COURT_ANGLE - 90.0f))); if (!tap || !World_add(world, tap)) return false; Object *hose = Object_create(OBJ_HOSE, PALETTE_WARM, vec3_add(court(TAP_A + 0.6f, TAP_B + 0.8f), vec3(0.0f, 0.1f, 0.0f)), vec3_zero(), 0.6f); if (!hose) return false; hose->hose_tap = tap; Object_setRotation(hose, vec3(PI_F / 2.0f, across_yaw, 0.0f)); if (!World_add(world, hose)) return false; Object *can = Object_create(OBJ_CAN, PALETTE_WARM, vec3_add(court(TAP_A - 0.5f, TAP_B + 0.5f), vec3(0.0f, 0.2f, 0.0f)), vec3_zero(), 1.5f); if (!can || !World_add(world, can)) 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++) { const Building *h = &HOUSES[i]; if (!addStatic(world, h->type, h->look, at(h->center), h->size, yaw_along(mapDir(h->angle)))) return false; } for (int i = 0; i < COUNT(HALLS); i++) { const Hall *h = &HALLS[i]; if (!addStatic(world, OBJ_BARN, h->look, at(h->center), vec3(h->length, HALL_WALL, h->width), yaw_along(mapDir(h->angle)))) return false; } /* The pond: a low wall round a square of water (the water comes when the ground is there) */ Vec3 pond = at(POND_AT); const float yaw = deg2rad(45.0f); const Mat4 turn = mat4_rotate_yxz(vec3(0.0f, yaw, 0.0f)); for (int k = 0; k < 4; k++) { bool along_x = k < 2; float sign = k % 2 ? 1.0f : -1.0f; Vec3 offset = along_x ? vec3(0.0f, 0.0f, sign * (POND_SIDE * 0.5f + 0.15f)) : vec3(sign * (POND_SIDE * 0.5f + 0.15f), 0.0f, 0.0f); Vec3 size = along_x ? vec3(POND_SIDE + 0.6f, 0.35f, 0.3f) : vec3(0.3f, 0.35f, POND_SIDE); if (!addStatic(world, OBJ_BOX_STATIC, PALETTE_STANDARD, vec3_add(pond, mat4_mul_dir(turn, offset)), size, yaw)) return false; } /* The bus stop by the L33: a pole with a sign */ Vec3 stop = at(BUS_STOP_AT); if (!addStatic(world, OBJ_CYLINDER_STATIC, PALETTE_STANDARD, stop, vec3(0.08f, 2.4f, 0.08f), 0.0f) || !addStatic(world, OBJ_BOX_STATIC, PALETTE_MODERN, vec3_add(stop, vec3(0.0f, 2.0f, 0.0f)), vec3(0.05f, 0.45f, 0.45f), yaw_along(mapDir(-52.0f)))) return false; return plantForests(world); } #define EDGE (TERRAIN_SIZE * 0.5f - 0.05f) /* Ground height; just past the edge of the world, the height at the edge. */ static float terrainAt(const void *ctx, float x, float z) { return Terrain_height(&((const World *)ctx)->terrain, clampf(x, -EDGE, EDGE), clampf(z, -EDGE, EDGE)); } /* Cuts a segment to the square |x|, |z| <= l. False if it lies outside. */ static bool clipSegment(Vec3 *a, Vec3 *b, float l) { float t0 = 0.0f, t1 = 1.0f, dx = b->x - a->x, dz = b->z - a->z; const float p[4] = { -dx, dx, -dz, dz }, q[4] = { a->x + l, l - a->x, a->z + l, l - a->z }; for (int k = 0; k < 4; k++) { if (fabsf(p[k]) < 1e-6f) { if (q[k] < 0.0f) return false; continue; } float t = q[k] / p[k]; if (p[k] < 0.0f) t0 = fmaxf(t0, t); else t1 = fminf(t1, t); } if (t0 > t1) return false; Vec3 from = *a; *a = vec3_lerp(from, *b, t0); *b = vec3_lerp(from, *b, t1); return true; } #define ROAD_POINTS 40 #define GRAVEL_STONES 3.0f /* loose stones per square meter of gravel */ /* A loose stone lying on the ground (pick it up, throw it). */ static bool addStone(World *world, float x, float z, float size, unsigned seed) { Vec3 dims = vec3(size, size * (0.45f + 0.25f * hash((int)seed, 1, 5)), size * (0.6f + 0.35f * hash((int)seed, 2, 7))); float ground = Terrain_height(&world->terrain, x, z); if (World_groundHeight(world, x, z, ground + 4.0f) > ground + 0.05f) return true; /* something stands there */ float kg = 2600.0f * 0.52f * dims.x * dims.y * dims.z; Object *o = Object_create(OBJ_STONE, PALETTE_GROUND, vec3(x, ground + dims.y * 0.35f, z), dims, kg); if (!o) return false; o->variant = (int)seed; Object_setRotation(o, vec3(0.0f, hash((int)seed, 3, 11) * 6.28f, 0.0f)); return World_add(world, o); } /* Stones: along the gravel roads, a few in the yard, field stones in the meadow. */ static bool scatterStones(World *world) { unsigned n = 0; for (int r = 0; r < COUNT(ROADS); r++) { if (ROADS[r].surface != GRAVEL) continue; for (int k = 0; k + 1 < ROADS[r].count; k++) { Vec3 a = at(ROADS[r].points[k]), b = at(ROADS[r].points[k + 1]); Vec3 d = vec3_sub(b, a); float len = sqrtf(d.x * d.x + d.z * d.z); Vec3 side = vec3(-d.z / len, 0.0f, d.x / len); for (float t = 0.0f; t < len; t += 7.0f, n++) { if (hash((int)n, 4, 13) < 0.45f) continue; float s = (hash((int)n, 5, 17) < 0.5f ? -1.0f : 1.0f) * (ROADS[r].width * 0.5f + 0.2f + 0.8f * hash((int)n, 6, 19)); Vec3 p = vec3_add(vec3_add(a, vec3_scale(d, t / len)), vec3_scale(side, s)); if (fabsf(p.x) > EDGE - 2.0f || fabsf(p.z) > EDGE - 2.0f) continue; if (!addStone(world, p.x, p.z, 0.08f + 0.18f * hash((int)n, 7, 23), n)) return false; } } } for (int k = 0; k < 14; k++, n++) { /* the yard */ Vec3 p = court(9.0f + 28.0f * hash(k, 8, 29), -8.0f + 26.0f * hash(k, 9, 31)); if (!addStone(world, p.x, p.z, 0.06f + 0.12f * hash(k, 10, 37), n)) return false; } Vec3 yard = Site_yard(); for (int k = 0; k < 60; k++, n++) { /* field stones in the meadow, some big */ float ang = 6.28f * hash(k, 11, 41), dist = 25.0f + 95.0f * hash(k, 12, 43); float x = yard.x + cosf(ang) * dist, z = yard.z + sinf(ang) * dist; if (fabsf(x) > EDGE - 2.0f || fabsf(z) > EDGE - 2.0f || Site_road(x, z) > 0.2f) continue; float size = hash(k, 13, 47) < 0.15f ? 0.35f + 0.25f * hash(k, 14, 53) : 0.1f + 0.2f * hash(k, 15, 59); if (!addStone(world, x, z, size, n)) return false; } return true; } /* A road's points in the world, ending where the world ends. */ static int roadPoints(const Road *road, Vec3 *out) { const float l = EDGE - road->width * 0.35f; int n = 0; for (int k = 0; k + 1 < road->count && n + 2 <= ROAD_POINTS; k++) { Vec3 a = at(road->points[k]), b = at(road->points[k + 1]); if (!clipSegment(&a, &b, l)) continue; if (n == 0 || vec3_length(vec3_sub(out[n - 1], a)) > 0.01f) out[n++] = a; out[n++] = b; } return n; } void Site_courseFrame(Vec3 *start, Vec3 *along) { /* The farm yard between the chicken halls: the stretch from the gate down to the halls */ Vec3 a = at(FARM_YARD[1]), b = at(FARM_YARD[2]); Vec3 d = vec3_sub(b, a); d.y = 0.0f; *along = vec3_normalize(d); *start = vec3_add(a, vec3_scale(*along, 2.0f)); } /* The floors' stages, marked out where they're going to be built (a, b; which way their front looks along a). */ static const struct { Project project; float a, b, front; } FLOORS[] = { { PROJECT_HOFBUEHNE, 36.0f, 5.0f, -1.0f }, /* in the yard, its back to the barn, facing the main building */ { PROJECT_OBSTWIESE, 42.0f, 38.0f, -1.0f }, /* at the far end of the orchard, facing the apple trees */ { PROJECT_KARTOFFELACKER, 107.5f, 45.9f, -1.0f }, /* behind the road to the chicken farm, far past the orchard and the barn, facing the road */ { PROJECT_HIDDEN, -20.0f, -2.0f, 1.0f }, /* behind the main building, facing its back */ }; Vec3 Site_campPath(void) { return court(36.0f, -11.0f); } /* (the foot of the path up behind your garages: their end by the barn) */ Vec3 Site_snailRefuge(void) { return at((Px){ 325.0f, 612.0f }); } /* (between the Kartoffelacker and the wood left of it: long grass, bushes) */ Vec3 Site_barnDoor(void) { return court(BARN_A - BARN_WIDTH * 0.5f - 0.9f, BARN_B - 10.0f); } /* (its side to the yard, left of the Hofbuehne's stage) */ Vec3 Site_artSpot(void) { return court(14.0f, 12.0f); } /* (in the yard, between the pavilions and where the cars stop) */ Vec3 Site_floorSpot(int project) { for (int k = 0; k < COUNT(FLOORS); k++) if ((int)FLOORS[k].project == project) return court(FLOORS[k].a, FLOORS[k].b); return Site_yard(); } /* ---------- The big old tree and the phone line ---------- */ /* The tree south of the gravel loop between the Obstwiese and the Kartoffelacker, leaning towards the road; the phone line along the road's other side, on poles, the tall mast nearest the tree. */ static const Vec3 BIG_TREE_AT = { -45.0f, 0.0f, 73.5f }; #define BIG_TREE_TRUNK 5.0f /* m of bare trunk ... */ #define BIG_TREE_LAYERS 26 /* ... then the crown (about 21 m in all) */ #define BIG_TREE_CROWN 10.0f /* m across at the bottom of the crown */ static const Vec3 POLES[] = { { -68.0f, 0.0f, 84.8f }, { -51.3f, 0.0f, 83.7f }, { -29.4f, 0.0f, 92.6f } }; #define MAST 1 /* (the middle one: the tall mast) */ #define POLE_H 8.5f #define MAST_H 11.0f static Object *g_wires; /* (the wires: torn when the tree takes them down) */ Vec3 Site_bigTree(void) { return BIG_TREE_AT; } /* Which way it should go (away from the road, into the meadow) and which way it leans (the road, the line). */ Vec3 Site_bigTreeSafe(void) { return vec3_normalize(vec3(0.42f, 0.0f, -0.91f)); } Vec3 Site_bigTreeLean(void) { return vec3_normalize(vec3(-0.30f, 0.0f, 0.95f)); } int Site_phoneLine(Vec3 *out, int max) { int n = 0; for (int k = 0; k < COUNT(POLES) && n < max; k++) out[n++] = vec3(POLES[k].x, (k == MAST ? MAST_H : POLE_H) - 0.4f, POLES[k].z); return n; } static Mesh wiresMesh(const World *world, bool down) { Vec3 pts[COUNT(POLES)]; float ground[COUNT(POLES)]; Vec3 origin = POLES[MAST]; origin.y = Terrain_height(&world->terrain, origin.x, origin.z); for (int k = 0; k < COUNT(POLES); k++) { float y = Terrain_height(&world->terrain, POLES[k].x, POLES[k].z); pts[k] = vec3(POLES[k].x - origin.x, y + (k == MAST ? MAST_H : POLE_H) - 0.4f - origin.y, POLES[k].z - origin.z); ground[k] = y - origin.y; } /* (torn: only the span the tree came down on - which one, the game tells: both spans at the mast hang) */ return Shapes_wires(pts, COUNT(POLES), down, ground); } void Site_phoneWiresDown(World *world, bool down) { if (!g_wires) return; Mesh m = wiresMesh(world, down); if (!m.vao) return; Mesh_destroy(&g_wires->mesh); g_wires->mesh = m; } static bool phoneLine(World *world) { for (int k = 0; k < COUNT(POLES); k++) { Vec3 at = POLES[k]; at.y = Terrain_height(&world->terrain, at.x, at.z) - 0.3f; float h = (k == MAST ? MAST_H : POLE_H) + 0.3f; Vec3 d = k + 1 < COUNT(POLES) ? vec3_sub(POLES[k + 1], POLES[k]) : vec3_sub(POLES[k], POLES[k - 1]); float yaw = atan2f(d.x, d.z); /* (the cross arms across the line) */ Object *look = Object_createStructure(Shapes_phonePole(h, k == MAST), at, yaw, vec3(1.4f, h, 1.4f)); if (!look || !World_add(world, look)) return false; Object *solid = Object_create(OBJ_WALL, PALETTE_STANDARD, at, vec3(0.3f, h, 0.3f), 0.0f); if (!solid || !World_add(world, solid)) return false; } Vec3 origin = POLES[MAST]; origin.y = Terrain_height(&world->terrain, origin.x, origin.z); g_wires = Object_createStructure(wiresMesh(world, false), origin, 0.0f, vec3(40.0f, 12.0f, 40.0f)); return g_wires && World_add(world, g_wires); } static bool plantBigTree(World *world) { Vec3 at = BIG_TREE_AT; at.y = Terrain_height(&world->terrain, at.x, at.z) - 0.15f; for (int i = 0; i < world->count; i++) { /* (room round it: no forest tree, no bush in its crown) */ Object *o = world->objects[i]; if ((o->type == OBJ_TREE || o->type == OBJ_BUSH) && hypotf(o->position.x - at.x, o->position.z - at.z) < 7.0f) Object_useUp(o); } Object *tree = Object_create(OBJ_TREE, PALETTE_WARM, at, vec3(BIG_TREE_CROWN, BIG_TREE_TRUNK, 0.0f), 0.0f); if (!tree) return false; tree->variant = 2; /* (a metre through) */ tree->keep = true; /* (nobody fells this one on a whim: Enrico's job) */ tree->branches = 0; Object_setBranches(tree, BIG_TREE_LAYERS); return World_add(world, tree) && phoneLine(world); } bool Site_finish(World *world) { g_wires = NULL; if (!plantBigTree(world)) return false; for (int k = 0; k < COUNT(FLOORS); k++) { /* the stages, marked out on the ground */ Vec3 at = court(FLOORS[k].a, FLOORS[k].b); at.y = Terrain_height(&world->terrain, at.x, at.z); Vec3 front = FLOORS[k].front != 0.0f ? vec3_scale(mapDir(COURT_ANGLE), FLOORS[k].front) : mapDir(COURT_ANGLE - 90.0f); Object *s = Stage_create(FLOORS[k].project, at, yaw_facing(front)); if (!s || !World_add(world, s)) return false; } if (!Field_prepare(world)) return false; /* (the Kartoffelacker's grass round its stage, the snails) */ if (!Berlin_build(world)) return false; /* (the meeting places, off the map) */ /* Trees stand a little into the ground, wherever it is */ for (int i = 0; i < world->count; i++) { Object *o = world->objects[i]; if (o->type != OBJ_TREE && o->type != OBJ_BUSH) continue; o->position.y = Terrain_height(&world->terrain, o->position.x, o->position.z) - 0.15f; Object_syncColliders(o); } /* Paint on the tar, loose stones on the gravel (the roads themselves are part of the ground) */ for (int r = 0; r < COUNT(ROADS); r++) { Vec3 points[ROAD_POINTS]; int n = roadPoints(&ROADS[r], points); if (n < 2) continue; Vec3 origin = points[0]; origin.y = Terrain_height(&world->terrain, origin.x, origin.z); ShapeGround ground = { .height = terrainAt, .ctx = world, .origin = origin }; const Road *road = &ROADS[r]; if (road->surface != GRAVEL && road->line_count == 0) continue; /* (the surface is the ground's) */ Mesh mesh = road->surface == GRAVEL ? Shapes_gravelStones(points, n, road->width, GRAVEL_STONES, true, ground) : Shapes_roadLines(points, n, road->lines, road->line_count, ground); if (!mesh.vao || !World_add(world, Object_createDecal(mesh, origin))) return false; } /* Loose stones on the gravel in the courtyard */ for (int i = 0; i < COUNT(GRAVEL_PATCHES); i++) { Vec3 points[2] = { court(GRAVEL_PATCHES[i].a0, GRAVEL_PATCHES[i].b0), court(GRAVEL_PATCHES[i].a1, GRAVEL_PATCHES[i].b1) }; Vec3 origin = points[0]; origin.y = Terrain_height(&world->terrain, origin.x, origin.z); ShapeGround ground = { .height = terrainAt, .ctx = world, .origin = origin }; Mesh mesh = Shapes_gravelStones(points, 2, GRAVEL_PATCHES[i].width, GRAVEL_STONES * 1.5f, false, ground); if (!mesh.vao || !World_add(world, Object_createDecal(mesh, origin))) return false; } if (!scatterStones(world)) return false; /* Water in the pond, on the leveled ground inside its wall */ Vec3 pond = at(POND_AT); pond.y = Terrain_height(&world->terrain, pond.x, pond.z) + 0.15f; Mesh water = Shapes_water(POND_SIDE); if (!water.vao) return false; Object *w = Object_createDecal(water, pond); if (!w) return false; Object_setRotation(w, vec3(0.0f, deg2rad(45.0f), 0.0f)); return World_add(world, w); }