Nimbin[12]?Games / Entropie / entropie/src/world/manor.c

Entropie git · main

Entropie festival game; first steps

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

C 88.5% Markdown 9.1% GLSL 2.3%
git clone https://git.christianimmanuel.de/games/Entropie.gitwget https://git.christianimmanuel.de/games/Entropie/archive/Entropie.tar.gz
entropie/src/world/manor.c 35.3 KB · 612 lines raw
#include "world/manor.h"
#include "world/terrain.h"

#include <stdio.h>
#include <string.h>

/* Heights (m from the ground) */
#define FL        0.70f  /* the ground floor: four steps up */
#define STEP_H    (FL / 4.0f)
#define CEIL      3.40f  /* its ceiling ... */
#define SLAB      0.20f  /* ... this thick */
#define WALL_TOP  6.20f  /* the eaves (two storeys over the basement) */
#define PORCH_TOP 3.00f
#define DOOR_H    2.15f

/* The halves */
#define LEFT_Z0  -5.0f
#define LEFT_Z1   5.0f
#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 */
#define P         0.15f  /* inner walls */
#define PORCH_Z1  7.3f   /* the porch: 1.5 m in front of the right half */
#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 };
static const Vec3 INSIDE    = { 0.93f, 0.91f, 0.85f };
static const Vec3 CEILING   = { 0.95f, 0.94f, 0.90f };
static const Vec3 FLOORING  = { 0.62f, 0.45f, 0.30f };
static const Vec3 TILES     = { 0.70f, 0.66f, 0.60f };
static const Vec3 STEPS     = { 0.58f, 0.56f, 0.53f };
static const Vec3 RAIL      = { 0.45f, 0.32f, 0.22f };
static const Vec3 FRAME     = { 0.93f, 0.91f, 0.86f };
static const Vec3 PORCELAIN = { 0.95f, 0.95f, 0.93f };
static const Vec3 PORCH_ROOF = { 0.38f, 0.38f, 0.43f };

enum { LOOK_ONLY, SOLID, EAVES_WALL };

#define MAX_SLABS    720
#define MAX_OPENINGS 96

typedef struct {
    ShapeSlab    slabs[MAX_SLABS];
    int          n_slabs;
    ShapeOpening openings[MAX_OPENINGS];
    int          n_openings;
    ShapeRoof    roofs[2];
    int          n_roofs;
    World       *world;
    Vec3         origin;
    float        yaw;
    bool         ok;
    bool         brick;  /* boxes made now are brick (mortar lines) */
    unsigned     rng;
} Plan;

/* Which way a door, window or piece of furniture faces (local) */
static const Vec3 FACE_PX = { 1.0f, 0.0f, 0.0f }, FACE_NX = { -1.0f, 0.0f, 0.0f };
static const Vec3 FACE_PZ = { 0.0f, 0.0f, 1.0f }, FACE_NZ = { 0.0f, 0.0f, -1.0f };

static float rnd(Plan *p) {
    p->rng = p->rng * 1664525u + 1013904223u;
    return (float)(p->rng >> 8) / 16777216.0f;
}

static Vec3 toWorld(const Plan *p, Vec3 local) {
    return vec3_add(p->origin, mat4_mul_dir(mat4_rotate_yxz(vec3(0.0f, p->yaw, 0.0f)), local));
}

/* A solid box of the building (it's walked on, leaned on, run into). */
static Object *solid(Plan *p, Vec3 min, Vec3 max, int kind, const char *name) {
    Vec3 size = vec3_sub(max, min);
    Vec3 foot = vec3((min.x + max.x) * 0.5f, min.y, (min.z + max.z) * 0.5f);
    Object *o = Object_create(OBJ_WALL, PALETTE_GROUND, toWorld(p, foot), size, 1000.0f);
    if (!o) { p->ok = false; return NULL; }
    Object_setRotation(o, vec3(0.0f, p->yaw, 0.0f));
    o->variant = kind == EAVES_WALL ? 2 : 0;
    if (name) snprintf(o->name, sizeof(o->name), "%s", name);
    if (!World_add(p->world, o)) { p->ok = false; return NULL; }
    return o;
}

/* A box: seen (colour), and solid unless LOOK_ONLY. */
static void box(Plan *p, Vec3 min, Vec3 max, Vec3 color, int kind) {
    if (max.x - min.x <= 1e-3f || max.y - min.y <= 1e-3f || max.z - min.z <= 1e-3f) return;
    if (p->n_slabs < MAX_SLABS) p->slabs[p->n_slabs++] = (ShapeSlab){ min, max, color, p->brick };
    else p->ok = false;
    if (kind != LOOK_ONLY) solid(p, min, max, kind, NULL);
}

/* A window, door or frame on a wall face at at, its front facing face (local). */
static void opening(Plan *p, OpeningKind kind, Vec3 at, Vec3 face, float w, float h) {
    if (p->n_openings < MAX_OPENINGS) p->openings[p->n_openings++] = (ShapeOpening){ kind, at, yaw_facing(face), w, h, FRAME };
    else p->ok = false;
}

/* A gap in a wall: from a to b along it, from y0 to y1 */
typedef struct { float a, b, y0, y1; } Gap;

/* A wall along x (from x0 to x1, between z0 and z1, y0 to y1) with gaps. */
static void wallX(Plan *p, float x0, float x1, float z0, float z1, float y0, float y1,
                  const Gap *gaps, int n, Vec3 color, int kind) {
    float at = x0;
    for (int i = 0; i < n; i++) { /* (gaps given left to right) */
        box(p, vec3(at, y0, z0), vec3(gaps[i].a, y1, z1), color, kind);
        box(p, vec3(gaps[i].a, y0, z0), vec3(gaps[i].b, gaps[i].y0, z1), color, kind); /* under it */
        box(p, vec3(gaps[i].a, gaps[i].y1, z0), vec3(gaps[i].b, y1, z1), color, kind); /* over it */
        at = gaps[i].b;
    }
    box(p, vec3(at, y0, z0), vec3(x1, y1, z1), color, kind);
}

/* The same along z. */
static void wallZ(Plan *p, float z0, float z1, float x0, float x1, float y0, float y1,
                  const Gap *gaps, int n, Vec3 color, int kind) {
    float at = z0;
    for (int i = 0; i < n; i++) {
        box(p, vec3(x0, y0, at), vec3(x1, y1, gaps[i].a), color, kind);
        box(p, vec3(x0, y0, gaps[i].a), vec3(x1, gaps[i].y0, gaps[i].b), color, kind);
        box(p, vec3(x0, gaps[i].y1, gaps[i].a), vec3(x1, y1, gaps[i].b), color, kind);
        at = gaps[i].b;
    }
    box(p, vec3(x0, y0, at), vec3(x1, y1, z1), color, kind);
}

/* A locked door drawn on a wall face, and a thin solid piece in front of it
   that says what it is (clicked: "locked"). face: the way it faces. */
static void lockedDoor(Plan *p, Vec3 foot, Vec3 face, float w, const char *name) {
    opening(p, OPENING_DOOR, foot, face, w, DOOR_H - 0.1f);
    Vec3 along = vec3(face.z, 0.0f, -face.x);
    Vec3 a = vec3_add(foot, vec3_scale(along, -w * 0.5f));
    Vec3 b = vec3_add(foot, vec3_add(vec3_scale(along, w * 0.5f), vec3_scale(face, 0.06f)));
    Vec3 min = vec3(fminf(a.x, b.x), foot.y, fminf(a.z, b.z)), max = vec3(fmaxf(a.x, b.x), foot.y + DOOR_H - 0.1f, fmaxf(a.z, b.z));
    Object *o = solid(p, min, max, SOLID, name);
    if (o) o->variant = 1;
}

/* Windows along a wall face at z (facing +z if side > 0), at these x, at height y. */
static void windowsX(Plan *p, float z, float side, const float *xs, int n, float y, float w, float h, bool inside_too, float inner_z) {
    for (int i = 0; i < n; i++) {
        opening(p, OPENING_WINDOW, vec3(xs[i], y, z), side > 0.0f ? FACE_PZ : FACE_NZ, w, h);
        if (inside_too) opening(p, OPENING_PANE, vec3(xs[i], y, inner_z), side > 0.0f ? FACE_NZ : FACE_PZ, w, h);
    }
}

/* ---------- The left half: entrance, corridor, toilet, kitchen, locked rooms ---------- */

#define DOOR_X   -7.6f   /* the front door (left half), by your garages */
#define VEST_X0  -9.6f   /* the entrance: ground level */
#define VEST_X1  -5.75f
#define STAIR_X1 -8.4f   /* the four steps up, on the left */
#define HALL_Z    1.4f   /* the ground floor starts here (towards the back) */
#define CORR_Z0  -0.75f  /* the corridor */
#define CORR_Z1   0.75f
#define TOILET_X0 -5.6f
#define KITCHEN_X0 -3.6f
#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 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;
    /* Outer walls: the front with the door; the back; the left end; the wall between the halves */
    Gap door = { DOOR_X - 0.5f, DOOR_X + 0.5f, 0.0f, DOOR_H };
    wallX(p, x0, -P, z1 - T, z1, 0.0f, WALL_TOP, &door, 1, PLASTER, EAVES_WALL);
    wallX(p, x0, -P, z0, z0 + T, 0.0f, WALL_TOP, NULL, 0, PLASTER, EAVES_WALL);
    wallZ(p, z0 + T, z1 - T, x0, x0 + T, 0.0f, WALL_TOP, NULL, 0, PLASTER, EAVES_WALL);
    Gap through = { CORR_Z0, CORR_Z1, FL, FL + DOOR_H };
    wallZ(p, z0, RIGHT_Z1, -P, P, 0.0f, WALL_TOP, &through, 1, PLASTER, SOLID);
    /* Plinth paint round the outside (the basement), and the front door */
    box(p, vec3(GARAGE_X1, 0.0f, z1), vec3(DOOR_X - 0.6f, FL - 0.05f, z1 + 0.02f), PLINTH, LOOK_ONLY);
    box(p, vec3(DOOR_X + 0.6f, 0.0f, z1), vec3(-P, FL - 0.05f, z1 + 0.02f), PLINTH, LOOK_ONLY);
    box(p, vec3(x0, 0.0f, z0 - 0.02f), vec3(-P, FL - 0.05f, z0), PLINTH, LOOK_ONLY);
    opening(p, OPENING_FRAME, vec3(DOOR_X, 0.0f, z1), FACE_PZ, 1.0f, DOOR_H);
    opening(p, OPENING_FRAME, vec3(DOOR_X, 0.0f, z1 - T), FACE_NZ, 1.0f, DOOR_H);

    /* Floors: the entrance on the ground, everything else four steps up */
    box(p, vec3(VEST_X0, -0.06f, HALL_Z), vec3(WELL_X0, 0.02f, z1 - T), TILES, SOLID);
    box(p, vec3(WELL_X0, -0.3f, WELL_TOP_Z), vec3(VEST_X1, 0.02f, z1 - T), TILES, SOLID); /* the landing at the top of the stairs down */
    box(p, vec3(x0 + T, 0.0f, z0 + T), vec3(-P, FL, HALL_Z), FLOORING, SOLID);         /* back: corridor and rooms */
    box(p, vec3(TOILET_X0, 0.0f, HALL_Z), vec3(-P, FL, z1 - T), FLOORING, SOLID);       /* toilet, kitchen */
    box(p, vec3(x0 + T, 0.0f, HALL_Z), vec3(GARAGE_X1, FL, z1 - T), FLOORING, SOLID);   /* (a closed store room) */
    for (int k = 0; k < 4; k++) /* the steps up, on the left */
        box(p, vec3(VEST_X0, 0.0f, HALL_Z + 1.2f - 0.3f * (float)(k + 1)), vec3(STAIR_X1, STEP_H * (float)(k + 1), HALL_Z + 1.2f - 0.3f * (float)k),
            STEPS, SOLID);
    box(p, vec3(STAIR_X1, FL, HALL_Z), vec3(VEST_X1, FL + 0.95f, HALL_Z + 0.08f), RAIL, SOLID); /* the rail along the edge */
    box(p, vec3(STAIR_X1, 0.0f, HALL_Z), vec3(VEST_X1, FL, HALL_Z + 0.02f), PLINTH, LOOK_ONLY);
    box(p, vec3(x0 + T, CEIL, z0 + T), vec3(-P, CEIL + SLAB, z1 - T), CEILING, SOLID);

    /* The entrance: its walls; on the right the stairs down to the basement (a rail along them) */
    wallZ(p, CORR_Z0, z1 - T, GARAGE_X1, VEST_X0, 0.0f, CEIL, NULL, 0, INSIDE, SOLID);
    wallZ(p, HALL_Z, z1 - T, VEST_X1, TOILET_X0, 0.0f, CEIL, NULL, 0, INSIDE, SOLID);
    box(p, vec3(WELL_X0 - 0.05f, 0.0f, HALL_Z), vec3(WELL_X0, 0.95f, WELL_TOP_Z), RAIL, SOLID);
    box(p, vec3(WELL_X0 - 0.06f, 0.0f, WELL_TOP_Z - 0.06f), vec3(WELL_X0 + 0.01f, 0.97f, WELL_TOP_Z), RAIL, LOOK_ONLY); /* (its post) */

    /* The corridor's walls: on the right toilet and kitchen (open), on the left two locked rooms */
    Gap rooms[2] = { { -5.0f, -4.2f, FL, FL + DOOR_H }, { -2.5f, -1.5f, FL, FL + DOOR_H } };
    wallX(p, TOILET_X0, -P, CORR_Z1, CORR_Z1 + P, FL, CEIL, rooms, 2, INSIDE, SOLID);
    opening(p, OPENING_FRAME, vec3(-4.6f, FL, CORR_Z1), FACE_NZ, 0.8f, DOOR_H);
    opening(p, OPENING_FRAME, vec3(-2.0f, FL, CORR_Z1), FACE_NZ, 1.0f, DOOR_H);
    wallZ(p, CORR_Z1 + P, z1 - T, KITCHEN_X0 - P, KITCHEN_X0, FL, CEIL, NULL, 0, INSIDE, SOLID); /* toilet | kitchen */
    wallX(p, x0 + T, GARAGE_X1, CORR_Z1, CORR_Z1 + P, FL, CEIL, NULL, 0, INSIDE, SOLID);          /* the store room */
    wallX(p, GARAGE_X1, -P, CORR_Z0 - P, CORR_Z0, FL, CEIL, NULL, 0, INSIDE, SOLID);
    wallZ(p, z0 + T, CORR_Z0 - P, LIVING_X0 - P, LIVING_X0, FL, CEIL, NULL, 0, INSIDE, SOLID);
    lockedDoor(p, vec3(-7.2f, FL, CORR_Z0), FACE_PZ, 0.9f, "A locked room");
    lockedDoor(p, vec3(-2.6f, FL, CORR_Z0), FACE_PZ, 0.9f, "Living room");

    /* The toilet: a little basin (the toilet itself: see furnish) */
    box(p, vec3(-4.2f, FL + 0.8f, 4.15f), vec3(-3.75f, FL + 0.95f, 4.6f), PORCELAIN, LOOK_ONLY);

    /* Windows: the front (behind your garages there are none), the back; basement windows low down */
    const float front_low[] = { -4.6f, -1.9f }, front_up[] = { -8.6f, -5.5f, -2.2f };
    windowsX(p, z1, 1.0f, &front_low[1], 1, FL + 1.45f, 1.0f, 1.2f, true, z1 - T);
    opening(p, OPENING_WINDOW, vec3(-4.6f, FL + 1.8f, z1), FACE_PZ, 0.6f, 0.6f);  /* the toilet's */
    opening(p, OPENING_PANE, vec3(-4.6f, FL + 1.8f, z1 - T), FACE_NZ, 0.6f, 0.6f);
    windowsX(p, z1, 1.0f, front_up, 3, CEIL + 1.5f, 1.0f, 1.2f, false, 0.0f);
    windowsX(p, z1, 1.0f, front_low, 2, 0.35f, 0.8f, 0.36f, false, 0.0f);
    float back[5];
    for (int i = 0; i < 5; i++) back[i] = x0 + 1.8f + 3.6f * (float)i;
    windowsX(p, z0, -1.0f, back, 5, FL + 1.45f, 1.0f, 1.2f, false, 0.0f);
    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);

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

static void rightHalf(Plan *p) {
    const float x1 = HALF_LEN, z0 = RIGHT_Z0, z1 = RIGHT_Z1;
    /* (the ridge runs front to back: gables at the front and back, the eaves over the right end) */
    Gap door = { RDOOR_X - 0.5f, RDOOR_X + 0.5f, FL, FL + DOOR_H };
    wallX(p, P, x1, z1 - T, z1, 0.0f, WALL_TOP, &door, 1, PLASTER, SOLID);
    wallX(p, P, x1, z0, z0 + T, 0.0f, WALL_TOP, NULL, 0, PLASTER, SOLID);
    wallZ(p, z0 + T, z1 - T, x1 - T, x1, 0.0f, WALL_TOP, NULL, 0, PLASTER, EAVES_WALL);
    box(p, vec3(P, 0.0f, z0 - 0.02f), vec3(x1, FL - 0.05f, z0), PLINTH, LOOK_ONLY);
    opening(p, OPENING_FRAME, vec3(RDOOR_X, FL, z1), FACE_PZ, 1.0f, DOOR_H);
    opening(p, OPENING_FRAME, vec3(RDOOR_X, FL, z1 - T), FACE_NZ, 1.0f, DOOR_H);

    box(p, vec3(P, 0.0f, z0 + T), vec3(x1 - T, FL, z1 - T), FLOORING, SOLID);
    box(p, vec3(P, CEIL, z0 + T), vec3(x1 - T, CEIL + SLAB, z1 - T), CEILING, SOLID);
    opening(p, OPENING_FRAME, vec3(P, FL, 0.0f), FACE_PX, CORR_Z1 - CORR_Z0, DOOR_H); /* from the corridor */
    opening(p, OPENING_FRAME, vec3(-P, FL, 0.0f), FACE_NX, CORR_Z1 - CORR_Z0, DOOR_H);

    /* Windows: the front (the right ones into the porch), the back, basement windows at the back */
    const float front[] = { 2.5f, 6.0f, 13.0f, 16.0f }, up[] = { 2.3f, 5.5f, 9.0f, 12.5f, 16.0f };
    windowsX(p, z1, 1.0f, front, 4, FL + 1.45f, 1.0f, 1.2f, true, z1 - T);
    windowsX(p, z1, 1.0f, up, 5, CEIL + 1.5f, 1.0f, 1.2f, false, 0.0f);
    float back[5];
    for (int i = 0; i < 5; i++) back[i] = 1.8f + 3.6f * (float)i;
    windowsX(p, z0, -1.0f, back, 5, FL + 1.45f, 1.0f, 1.2f, true, z0 + T);
    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);
    box(p, vec3(P, 0.0f, z1), vec3(PORCH_X0, FL - 0.05f, z1 + 0.02f), PLINTH, LOOK_ONLY); /* (left of the porch: the plinth shows) */

    /* The porch (the right half of the front): its front wall with windows, a closed left end, the way in at the right end */
    const float pz0 = z1, pz1 = PORCH_Z1;
    wallX(p, PORCH_X0, x1, pz1 - T, pz1, 0.0f, PORCH_TOP, NULL, 0, PLASTER, SOLID);
    wallZ(p, pz0, pz1 - T, PORCH_X0, PORCH_X0 + T, 0.0f, PORCH_TOP, NULL, 0, PLASTER, SOLID);
    Gap in = { pz0 + 0.15f, pz1 - T - 0.1f, 0.0f, DOOR_H };
    wallZ(p, pz0, pz1 - T, x1 - T, x1, 0.0f, PORCH_TOP, &in, 1, PLASTER, SOLID);
    opening(p, OPENING_FRAME, vec3(x1, 0.0f, (in.a + in.b) * 0.5f), FACE_PX, in.b - in.a, DOOR_H);
    box(p, vec3(PORCH_X0 - 0.1f, PORCH_TOP, pz0), vec3(x1 + 0.1f, PORCH_TOP + 0.18f, pz1 + 0.2f), PORCH_ROOF, SOLID);
    box(p, vec3(PORCH_X0, 0.0f, pz1), vec3(x1, 0.5f, pz1 + 0.02f), PLINTH, LOOK_ONLY);
    const float pw[] = { 11.2f, 15.2f };
    windowsX(p, pz1, 1.0f, pw, 2, 1.65f, 1.0f, 1.0f, true, pz1 - T);
    /* its floor: on the ground at the way in, four steps up to the landing at the door */
    box(p, vec3(PSTEP_X, -0.06f, pz0), vec3(x1 - T, 0.02f, pz1 - T), TILES, SOLID);
    for (int k = 0; k < 4; k++)
        box(p, vec3(PSTEP_X - 0.3f * (float)(k + 1), 0.0f, pz0), vec3(PSTEP_X - 0.3f * (float)k, STEP_H * (float)(k + 1), pz1 - T),
            STEPS, SOLID);
    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, 0.0f };
    p->roofs[p->n_roofs++] = r;
}

/* The roofs: a slab under each slope (you can get onto them from a ladder). */
static void roofSlabs(Plan *p, const ShapeRoof *r) {
    const float half_w = r->width * 0.5f, angle = atanf(r->roof_height / half_w);
    const float slope = sqrtf(half_w * half_w + r->roof_height * r->roof_height), thick = 0.25f;
    for (int side = -1; side <= 1; side += 2) {
        const float s = (float)side;
        /* ridge along x: the slopes to -z and +z; along z (across): to -x and +x */
        Vec3 normal = r->across ? vec3(s * sinf(angle), cosf(angle), 0.0f) : vec3(0.0f, cosf(angle), s * sinf(angle));
        Vec3 off = r->across ? vec3(s * half_w * 0.5f, 0.0f, 0.0f) : vec3(0.0f, 0.0f, s * half_w * 0.5f);
        Vec3 surface = vec3_add(vec3(r->center.x, r->wall_top + r->roof_height * 0.5f, r->center.z), off);
        Vec3 center = toWorld(p, vec3_sub(surface, vec3_scale(normal, thick * 0.5f)));
        Vec3 size = r->across ? vec3(slope, thick, r->length) : vec3(r->length, thick, slope);
        Object *o = Object_create(OBJ_WALL, PALETTE_GROUND, vec3(center.x, center.y - thick * 0.5f, center.z), size, 1000.0f);
        if (!o) { p->ok = false; return; }
        Object_setRotation(o, r->across ? vec3(0.0f, p->yaw, s * angle) : vec3(-s * angle, p->yaw, 0.0f));
        o->variant = 3; /* (a roof) */
        if (!World_add(p->world, o)) { p->ok = false; return; }
    }
}

/* ---------- The basement: under the left half, down from the entrance ---------- */

#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 14
#define STAIR_TREAD 0.23f
#define BW         0.25f /* its walls */

static const Vec3 OLD_BRICK = { 0.62f, 0.36f, 0.27f };
static const Vec3 EARTH_FLOOR = { 0.42f, 0.38f, 0.33f };
static const Vec3 CONCRETE = { 0.60f, 0.58f, 0.54f };

/* A few bricks loose on a wall face (sticking out a bit, darker or paler) and fallen to the floor. */
static void looseBricks(Plan *p, Vec3 min, Vec3 max, int n) {
    const bool along_x = max.x - min.x > max.z - min.z;
    for (int i = 0; i < n; i++) {
        float t = rnd(p), y = min.y + 0.3f + rnd(p) * (max.y - min.y - 0.5f), shade = 0.8f + 0.35f * rnd(p);
        Vec3 c = vec3_scale(OLD_BRICK, shade);
        float side = rnd(p) < 0.5f ? -1.0f : 1.0f;
        if (along_x) {
            float x = min.x + 0.2f + t * (max.x - min.x - 0.4f), z = side > 0.0f ? max.z : min.z;
            box(p, vec3(x - 0.11f, y, z - 0.02f), vec3(x + 0.11f, y + 0.07f, z + 0.02f), c, LOOK_ONLY);
        } else {
            float z = min.z + 0.2f + t * (max.z - min.z - 0.4f), x = side > 0.0f ? max.x : min.x;
            box(p, vec3(x - 0.02f, y, z - 0.11f), vec3(x + 0.02f, y + 0.07f, z + 0.11f), c, LOOK_ONLY);
        }
    }
}

static void basement(Plan *p) {
    const float rise = -BASE_Y / (float)STAIR_STEPS;
    /* Floor (packed earth, the ground itself goes down to it: see Terrain_addPit) and the ceiling seen from below */
    box(p, vec3(BASE_X0, BASE_Y - 0.04f, BASE_Z0), vec3(BASE_X1, BASE_Y, BASE_Z1), EARTH_FLOOR, LOOK_ONLY);
    box(p, vec3(WELL_X0, BASE_Y - 0.04f, BASE_Z1), vec3(BASE_X1, BASE_Y, UNDER_Z1), EARTH_FLOOR, LOOK_ONLY);
    box(p, vec3(BASE_X0, -0.03f, BASE_Z0), vec3(BASE_X1, 0.0f, BASE_Z1), CONCRETE, LOOK_ONLY);
    box(p, vec3(VEST_X1, -0.03f, BASE_Z1), vec3(BASE_X1, 0.0f, UNDER_Z1), CONCRETE, LOOK_ONLY);

    /* The walls round it: old brick, earth behind */
    p->brick = true;
    box(p, vec3(BASE_X0 - BW, BASE_Y, BASE_Z0 - BW), vec3(BASE_X1 + BW, 0.0f, BASE_Z0), OLD_BRICK, SOLID);            /* back */
    box(p, vec3(BASE_X0 - BW, BASE_Y, BASE_Z0), vec3(BASE_X0, 0.0f, BASE_Z1 + BW), OLD_BRICK, SOLID);                 /* left */
    box(p, vec3(BASE_X1, BASE_Y, BASE_Z0), vec3(BASE_X1 + BW, 0.0f, UNDER_Z1 + BW), OLD_BRICK, SOLID);               /* right */
    box(p, vec3(BASE_X0, BASE_Y, BASE_Z1), vec3(WELL_X0, 0.0f, BASE_Z1 + BW), OLD_BRICK, SOLID);                      /* under the entrance */
    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.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);
    wallZ(p, BASE_Z0, -1.4f, -5.0f, -4.82f, BASE_Y, 0.0f, NULL, 0, OLD_BRICK, SOLID);                                  /* two rooms at the back */
    wallX(p, BASE_X0, -7.6f, -1.6f, -1.42f, BASE_Y, -0.25f, NULL, 0, OLD_BRICK, SOLID);                               /* a nook on the left */
    p->brick = false;
    looseBricks(p, vec3(-5.0f, BASE_Y, BASE_Z0), vec3(-4.82f, 0.0f, -1.4f), 6);
    looseBricks(p, vec3(BASE_X0, BASE_Y, -1.6f), vec3(-7.6f, -0.25f, -1.42f), 4);
    looseBricks(p, vec3(TOILET_X0, BASE_Y, BASE_Z1), vec3(BASE_X1, 0.0f, BASE_Z1 + 0.15f), 5);
    for (int i = 0; i < 9; i++) { /* fallen out */
        float x = -8.8f + 8.0f * rnd(p), z = BASE_Z0 + 0.3f + 5.2f * rnd(p);
        box(p, vec3(x, BASE_Y, z), vec3(x + 0.22f, BASE_Y + 0.07f, z + 0.1f), vec3_scale(OLD_BRICK, 0.85f + 0.3f * rnd(p)), LOOK_ONLY);
    }

    /* The stairs: from the landing in the entrance down towards the back */
    for (int k = 0; k < STAIR_STEPS - 1; k++)
        box(p, vec3(WELL_X0, BASE_Y, WELL_TOP_Z - STAIR_TREAD * (float)(k + 1)),
            vec3(VEST_X1, -rise * (float)(k + 1), WELL_TOP_Z - STAIR_TREAD * (float)k), STEPS, SOLID);
}

/* What stands inside: the meeting table in the big room, the kitchen, shelves in the basement. */
static void furnish(Plan *p) {
    const float fl = FL, b = BASE_Y;
    const Vec3 chair = { 0.45f, 0.9f, 0.45f }, shelf = { 1.2f, 1.8f, 0.4f };
    struct { FurnitureKind kind; Vec3 at; Vec3 size; Vec3 face; } items[] = {
        { FURN_TABLE,   { 9.5f, fl, 0.8f }, { 4.2f, 0.78f, 1.6f }, FACE_PZ },  /* the big table */
        { FURN_CHAIR,   { 8.0f, fl, 2.15f }, chair, FACE_NZ },                  /* (all facing it) */
        { FURN_CHAIR,   { 9.5f, fl, 2.15f }, chair, FACE_NZ },
        { FURN_CHAIR,   { 11.0f, fl, 2.15f }, chair, FACE_NZ },
        { FURN_CHAIR,   { 8.0f, fl, -0.55f }, chair, FACE_PZ },
        { FURN_CHAIR,   { 9.5f, fl, -0.55f }, chair, FACE_PZ },
        { FURN_CHAIR,   { 11.0f, fl, -0.55f }, chair, FACE_PZ },
        { FURN_CHAIR,   { 6.85f, fl, 0.8f }, chair, FACE_PX },
        { FURN_CHAIR,   { 12.15f, fl, 0.8f }, chair, FACE_NX },
        { FURN_TOILET,  { -5.05f, fl, 4.05f }, { 0.42f, 0.8f, 0.8f }, FACE_NZ }, /* the toilet: its cistern against the front wall */
        { FURN_COUNTER, { -1.9f, fl, 4.4f }, { 3.0f, 0.9f, 0.6f }, FACE_NZ },   /* the kitchen: the counter along the front wall */
        { FURN_STOVE,   { -0.45f, fl, 2.6f }, { 0.6f, 0.9f, 0.6f }, FACE_NX },  /* ... the stove against the wall to the big room */
        { FURN_SHELF,   { -3.4f, fl, 2.3f }, shelf, FACE_PX },                  /* ... a shelf against the toilet's wall */
        { FURN_TABLE,   { -1.9f, fl, 2.4f }, { 1.2f, 0.75f, 0.8f }, FACE_PZ },
        { FURN_CHAIR,   { -1.9f, fl, 1.65f }, chair, FACE_PZ },
        { FURN_CHAIR,   { -1.9f, fl, 3.15f }, chair, FACE_NZ },
        { FURN_SHELF,   { -8.4f, b, BASE_Z0 + 0.2f }, shelf, FACE_PZ },        /* the basement */
        { FURN_SHELF,   { -6.4f, b, BASE_Z0 + 0.2f }, shelf, FACE_PZ },
        { FURN_SHELF,   { -3.6f, b, BASE_Z0 + 0.2f }, shelf, FACE_PZ },
        { FURN_SHELF,   { -1.3f, b, BASE_Z0 + 0.2f }, shelf, FACE_PZ },
        { FURN_SHELF,   { BASE_X0 + 0.2f, b, -3.2f }, shelf, FACE_PX },
        { FURN_SHELF,   { -0.35f, b, -1.0f }, shelf, FACE_NX },
        { FURN_SHELF,   { -2.0f, b, UNDER_Z1 - 0.2f }, shelf, FACE_NZ },
        { FURN_SHELF,   { -4.0f, b, UNDER_Z1 - 0.2f }, shelf, FACE_NZ },
    };
    for (size_t i = 0; i < sizeof(items) / sizeof(items[0]) && p->ok; i++) {
        Object *o = Object_createFurniture(items[i].kind, toWorld(p, items[i].at), items[i].size, p->yaw + yaw_facing(items[i].face));
        if (!o || !World_add(p->world, o)) p->ok = false;
    }
    /* Urinellas in a little carton on the toilet's floor, by the wall */
    Object *urinellas = p->ok ? Object_create(OBJ_SUPPLY, PALETTE_GROUND, toWorld(p, vec3(-3.85f, fl + 0.09f, 3.2f)), vec3(0.3f, 0.18f, 0.2f), 100.0f) : NULL;
    if (urinellas) {
        urinellas->dispenses = OBJ_URINELLA;
        Object_setRotation(urinellas, vec3(0.0f, p->yaw, 0.0f));
        if (!World_add(p->world, urinellas)) p->ok = false;
    } else p->ok = false;
}

/* The ground goes down to the basement floor under it, and isn't drawn inside the
   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[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 + rects[i][4]))
            p->ok = false;
    }
}

/* ---------- Your unfinished building: a brick shell under a roof ---------- */

#define SHELL_W     6.4f  /* along x */
#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   1.6f  /* the way in: on its -x side (where the woodpile is) */

static const Vec3 BRICK = { 0.78f, 0.42f, 0.30f };

bool Shell_build(World *world, Vec3 origin, float yaw) {
    static Plan plan;
    Plan *p = &plan;
    memset(p, 0, sizeof(*p));
    p->world = world;
    p->origin = origin;
    p->yaw = 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;
    /* (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_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);
    if (!mesh.vao) return false;
    Object *look = Object_createStructure(mesh, origin, yaw, vec3(SHELL_W, SHELL_WALL, SHELL_D));
    return look && World_add(world, look);
}

/* ---------- The shelter: a roof on posts in the yard, benches under it ---------- */
#define PAVILION_SIDE  3.0f  /* m: a party pavilion, square */
#define PAVILION_EAVES 2.3f  /* m: under the valance */
#define PAVILION_PEAK  3.1f  /* m: the top of its roof */
#define PAVILION_COVER 2.5f  /* m: the (unseen) roof that keeps the rain off, under the canvas */

bool Shelter_build(World *world, Vec3 origin, float yaw) {
    static Plan plan;
    Plan *p = &plan;
    memset(p, 0, sizeof(*p));
    p->world = world;
    p->origin = origin;
    p->yaw = yaw;
    p->ok = true;
    const Vec3 CANVAS = { 0.93f, 0.93f, 0.9f };
    const float h = PAVILION_SIDE * 0.5f, leg = 0.045f;
    for (int k = -1; k <= 1; k += 2) { /* two side by side along the yard */
        float x = (float)k * (h + 0.02f);
        for (int i = -1; i <= 1; i += 2) /* the legs */
            for (int j = -1; j <= 1; j += 2) {
                Vec3 at = vec3(x + (float)i * (h - leg), 0.0f, (float)j * (h - leg));
                solid(p, vec3(at.x - leg, 0.0f, at.z - leg), vec3(at.x + leg, PAVILION_EAVES, at.z + leg), SOLID, NULL);
            }
        solid(p, vec3(x - h + 0.2f, PAVILION_COVER, -h + 0.2f), vec3(x + h - 0.2f, PAVILION_COVER + 0.04f, h - 0.2f), SOLID, NULL);
        Mesh mesh = Shapes_pavilion(PAVILION_SIDE, PAVILION_EAVES, PAVILION_PEAK, CANVAS);
        if (!mesh.vao) return false;
        Object *look = Object_createStructure(mesh, toWorld(p, vec3(x, 0.0f, 0.0f)), yaw, vec3(PAVILION_SIDE, PAVILION_PEAK, PAVILION_SIDE));
        if (!look || !World_add(world, look)) return false;
        /* a beer table set under it: the table, a bench either side */
        Object *table = Object_createFurniture(FURN_TABLE, toWorld(p, vec3(x, 0.0f, 0.0f)), vec3(2.2f, 0.77f, 0.5f), yaw);
        if (!table || !World_add(world, table)) return false;
        for (int s = -1; s <= 1; s += 2) {
            Object *bench = Object_createFurniture(FURN_BENCH, toWorld(p, vec3(x, 0.0f, (float)s * 0.62f)), vec3(2.2f, SEAT_HEIGHT, 0.25f), yaw);
            if (!bench || !World_add(world, bench)) return false;
            bench->table = table;
        }
    }
    return p->ok;
}

bool Manor_build(World *world, Vec3 origin, float yaw) {
    static Plan plan; /* (big: not on the stack) */
    Plan *p = &plan;
    memset(p, 0, sizeof(*p));
    p->world = world;
    p->origin = origin;
    p->yaw = yaw;
    p->ok = true;
    p->rng = 12345u;
    leftHalf(p);
    rightHalf(p);
    basement(p);
    digBasement(p);
    for (int i = 0; i < p->n_roofs; i++) roofSlabs(p, &p->roofs[i]);
    furnish(p);
    if (!p->ok) return false;
    Mesh mesh = Shapes_structure(p->slabs, p->n_slabs, p->openings, p->n_openings, p->roofs, p->n_roofs);
    if (!mesh.vao) return false;
    Object *look = Object_createStructure(mesh, origin, yaw, vec3(2.0f * HALF_LEN, WALL_TOP, PORCH_Z1 - LEFT_Z0));
    return look && World_add(world, look);
}

/* ---------- Berlin: the flat in Grossbeerenstrasse and the Herberge ----------
   Two rooms off the map (BERLIN_X: the site is out of sight from there), closed
   in, windows drawn on the walls as in the farm's house. */

#define ROOM_H 3.2f /* (an old Berlin house: high ceilings) */

static Object *furniture(Plan *p, FurnitureKind kind, Vec3 at, Vec3 size, Vec3 face) {
    Object *o = Object_createFurniture(kind, toWorld(p, at), size, p->yaw + yaw_facing(face));
    if (!o || !World_add(p->world, o)) { p->ok = false; return NULL; }
    return o;
}

/* A closed room: floor, ceiling, four walls (x0..x1, z0..z1 inside), a locked door on its -z wall, windows on +z. */
static void room(Plan *p, float x0, float x1, float z0, float z1, Vec3 wall, Vec3 floor, const char *door) {
    box(p, vec3(x0 - T, -0.2f, z0 - T), vec3(x1 + T, 0.0f, z1 + T), floor, SOLID);
    box(p, vec3(x0, ROOM_H, z0), vec3(x1, ROOM_H + SLAB, z1), CEILING, SOLID); /* (inside the walls: touching their tops it wasn't drawn) */
    wallX(p, x0 - T, x1 + T, z1, z1 + T, 0.0f, ROOM_H + SLAB, NULL, 0, wall, SOLID);
    wallX(p, x0 - T, x1 + T, z0 - T, z0, 0.0f, ROOM_H + SLAB, NULL, 0, wall, SOLID);
    wallZ(p, z0, z1, x0 - T, x0, 0.0f, ROOM_H + SLAB, NULL, 0, wall, SOLID);
    wallZ(p, z0, z1, x1, x1 + T, 0.0f, ROOM_H + SLAB, NULL, 0, wall, SOLID);
    float xs[4];
    int n = (int)((x1 - x0) / 2.6f);
    if (n > 4) n = 4;
    for (int i = 0; i < n; i++) xs[i] = x0 + (x1 - x0) * ((float)i + 0.5f) / (float)n;
    for (int i = 0; i < n; i++) opening(p, OPENING_PANE, vec3(xs[i], 1.0f + 0.85f, z1), FACE_NZ, 1.1f, 1.7f);
    lockedDoor(p, vec3((x0 + x1) * 0.5f + 1.5f, 0.0f, z0), FACE_PZ, 1.0f, door);
}

bool Berlin_build(World *world) {
    static Plan plan;
    Plan *p = &plan;
    memset(p, 0, sizeof(*p));
    p->world = world;
    p->origin = vec3(BERLIN_X, 0.0f, 0.0f);
    p->yaw = 0.0f;
    p->ok = true;
    const Vec3 WALL_FLAT = { 0.90f, 0.88f, 0.80f }, WALL_BAR = { 0.55f, 0.32f, 0.22f }, PLANKS = { 0.55f, 0.38f, 0.24f }, BOARDS = { 0.32f, 0.22f, 0.15f };
    const Vec3 chair = { 0.45f, 0.9f, 0.45f }, shelf = { 1.2f, 1.9f, 0.4f };
    /* The flat, 3rd floor: a big kitchen with the table in the middle */
    room(p, -4.0f, 4.0f, -3.0f, 3.0f, WALL_FLAT, PLANKS, "The stairwell door - the meeting's here");
    furniture(p, FURN_COUNTER, vec3(-3.6f, 0.0f, 0.4f), vec3(2.4f, 0.9f, 0.6f), FACE_PX);
    furniture(p, FURN_STOVE, vec3(-3.6f, 0.0f, -1.5f), vec3(0.6f, 0.9f, 0.6f), FACE_PX);
    furniture(p, FURN_SHELF, vec3(3.7f, 0.0f, 1.2f), shelf, FACE_NX);
    furniture(p, FURN_BENCH, vec3(3.6f, 0.0f, -1.6f), vec3(1.8f, SEAT_HEIGHT, 0.5f), FACE_NX);
    furniture(p, FURN_TABLE, vec3(0.0f, 0.0f, 0.0f), vec3(2.2f, 0.75f, 0.9f), FACE_PZ);
    for (int k = -1; k <= 1; k++) {
        furniture(p, FURN_CHAIR, vec3((float)k * 0.7f, 0.0f, -0.85f), chair, FACE_PZ);
        furniture(p, FURN_CHAIR, vec3((float)k * 0.7f, 0.0f, 0.85f), chair, FACE_NZ);
    }
    /* The Herberge: the bar, a long table with two benches, two small tables */
    const float hx = BERLIN_BAR;
    room(p, hx - 6.0f, hx + 6.0f, -4.0f, 4.0f, WALL_BAR, BOARDS, "The door out to the street - the meeting's in here");
    furniture(p, FURN_BAR, vec3(hx - 5.2f, 0.0f, 0.0f), vec3(4.5f, 1.1f, 0.6f), FACE_PX);
    for (int k = -1; k <= 1; k += 2) furniture(p, FURN_SHELF, vec3(hx - 5.75f, 0.0f, (float)k * 1.3f), shelf, FACE_PX);
    Object *table = furniture(p, FURN_TABLE, vec3(hx + 0.6f, 0.0f, 0.0f), vec3(4.4f, 0.77f, 0.9f), FACE_PZ);
    for (int s = -1; s <= 1; s += 2) {
        Object *bench = furniture(p, FURN_BENCH, vec3(hx + 0.6f, 0.0f, (float)s * 0.85f), vec3(4.4f, SEAT_HEIGHT, 0.3f), FACE_PZ);
        if (bench) bench->table = table;
    }
    for (int k = -1; k <= 1; k += 2) {
        furniture(p, FURN_TABLE, vec3(hx + 4.6f, 0.0f, (float)k * 2.4f), vec3(0.8f, 0.75f, 0.8f), FACE_PZ);
        furniture(p, FURN_CHAIR, vec3(hx + 4.6f, 0.0f, (float)k * 2.4f - (float)k * 0.75f), chair, (Vec3){ 0.0f, 0.0f, (float)k });
    }
    if (!p->ok) return false;
    Mesh mesh = Shapes_structure(p->slabs, p->n_slabs, p->openings, p->n_openings, p->roofs, p->n_roofs);
    if (!mesh.vao) return false;
    Object *look = Object_createStructure(mesh, p->origin, 0.0f, vec3(BERLIN_BAR + 14.0f, ROOM_H, 10.0f));
    return look && World_add(world, look);
}