@adatechnology/game-floorplan-three 0.3.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -42,7 +42,9 @@ var GameFloorplanThree = (() => {
42
42
  ptBR: () => ptBR,
43
43
  rectsOf: () => rectsOf,
44
44
  specimenSpec: () => specimenSpec,
45
- tapSpec: () => tapSpec
45
+ tapSpec: () => tapSpec,
46
+ wallPiece: () => wallPiece,
47
+ wallPlan: () => wallPlan
46
48
  });
47
49
 
48
50
  // ../game-props3d/dist/index.js
@@ -3461,7 +3463,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
3461
3463
  light(M, x, y, z, color2, range2, kind) {
3462
3464
  into(M).push({ kind: "point", position: [x, y, z], data: { light: true, color: color2, range: range2, kind } });
3463
3465
  },
3464
- part(id, pivot, ry, draw) {
3466
+ part(id, pivot, ry, draw2) {
3465
3467
  let children = partChildren.get(id);
3466
3468
  if (!children) {
3467
3469
  children = [];
@@ -3472,7 +3474,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
3472
3474
  const outer = current;
3473
3475
  current = children;
3474
3476
  try {
3475
- draw();
3477
+ draw2();
3476
3478
  } finally {
3477
3479
  current = outer;
3478
3480
  }
@@ -4257,7 +4259,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
4257
4259
  // src/house/walls.ts
4258
4260
  function installHouseWalls(K, shared) {
4259
4261
  const { EPS: EPS3, Y0, boxC, bx, cursor, effectiveHeight, pieceRecord, structurePiece, wallColor } = K;
4260
- function wallBox(alongU, L, a, b, ya, yb, th, cL, cH, mL, mH) {
4262
+ function wallBox2(alongU, L, a, b, ya, yb, th, cL, cH, mL, mH) {
4261
4263
  if (yb - ya < 1e-4 || b - a < 1e-4) return;
4262
4264
  if (alongU) {
4263
4265
  bx(mL, a, ya, L - th / 2, b, yb, L, cL);
@@ -4324,7 +4326,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
4324
4326
  continue;
4325
4327
  }
4326
4328
  const seg = (a, b, ya, yb) => {
4327
- wallBox(alongU, L, a, b, y0 + ya, y0 + yb, th, sideL.color, sideH.color, sideL.mat, sideH.mat);
4329
+ wallBox2(alongU, L, a, b, y0 + ya, y0 + yb, th, sideL.color, sideH.color, sideL.mat, sideH.mat);
4328
4330
  };
4329
4331
  const win = R.windows.filter((j) => j.floor === f && j.axis === w.axis && Math.abs(j.pos - L) < EPS3 && j.at + j.width / 2 > s0 + EPS3 && j.at - j.width / 2 < s1 - EPS3).sort((p, q) => p.at - q.at);
4330
4332
  let cur = s0;
@@ -4356,13 +4358,13 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
4356
4358
  bx(e.color.mat, e.pu - e.th / 2, e.y0, e.pv - e.th / 2, e.pu + e.th / 2, e.y0 + e.hh, e.pv + e.th / 2, e.color.color);
4357
4359
  }
4358
4360
  }
4359
- Object.assign(K, { wallBox, sideOf, pieceOf, nearestWall, walls });
4361
+ Object.assign(K, { wallBox: wallBox2, sideOf, pieceOf, nearestWall, walls });
4360
4362
  }
4361
4363
 
4362
4364
  // src/house/doors.ts
4363
4365
  function installHouseDoors(K, shared) {
4364
4366
  const seedKey = (id) => K.seedKey(id);
4365
- const { EPS: EPS3, Y0, boxC, bx, cursor, effectiveHeight, nearestWall, part, pieceOf, pieceRecord, roomAt, sideOf, wallBox } = K;
4367
+ const { EPS: EPS3, Y0, boxC, bx, cursor, effectiveHeight, nearestWall, part, pieceOf, pieceRecord, roomAt, sideOf, wallBox: wallBox2 } = K;
4366
4368
  const SEED = (...a) => K.SEED(...a);
4367
4369
  const definePiece = (...a) => K.definePiece(...a);
4368
4370
  const rngString = (...a) => K.rngString(...a);
@@ -4387,7 +4389,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
4387
4389
  if (!cut && wallHeight > dH + 1e-3) {
4388
4390
  const wv = nearestWall(f, p.axis, L, p.at);
4389
4391
  cursor.pc = wv ? pieceOf(wv) : null;
4390
- wallBox(alongU, L, a, b, y0 + dH, y0 + effectiveHeight(f, wallHeight), th, lL.color, lH.color, lL.mat, lH.mat);
4392
+ wallBox2(alongU, L, a, b, y0 + dH, y0 + effectiveHeight(f, wallHeight), th, lL.color, lH.color, lL.mat, lH.mat);
4391
4393
  cursor.pc = null;
4392
4394
  }
4393
4395
  const fb = (u0, y_0, u1, y_1, tt = th + 0.02, color2 = frame2) => alongU ? bx("wood", u0, y0 + y_0, L - tt / 2, u1, y0 + y_1, L + tt / 2, color2) : bx("wood", L - tt / 2, y0 + y_0, u0, L + tt / 2, y0 + y_1, u1, color2);
@@ -5335,8 +5337,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
5335
5337
  t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
5336
5338
  return ((t ^ t >>> 14) >>> 0) / 4294967296;
5337
5339
  };
5338
- const draw = rnd() < p, light = rnd() < 0.6;
5339
- void draw;
5340
+ const draw2 = rnd() < p, light = rnd() < 0.6;
5341
+ void draw2;
5340
5342
  const on = true;
5341
5343
  return { on, rnd, color: high ? 3816770 : light ? 15527921 : 10857392, bad: (shared.ctx.colors.meta && shared.ctx.colors.meta.state) === "abandoned" || R.state === "abandoned", fallback: false };
5342
5344
  }
@@ -10893,6 +10895,13 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10893
10895
  const flame = flameBase * (1 - rules.flameFillInfluence * (1 - k));
10894
10896
  return 1 - (1 - Math.min(1, Math.max(0, spark))) * (1 - Math.min(1, Math.max(0, flame)));
10895
10897
  };
10898
+ var KIT_ORIGIN_KEY = "kitOrigin";
10899
+ function markKitOrigin(object2, origin) {
10900
+ const node = object2;
10901
+ node.userData = node.userData ?? {};
10902
+ node.userData[KIT_ORIGIN_KEY] = { ...origin };
10903
+ return object2;
10904
+ }
10896
10905
 
10897
10906
  // ../game-destruction/dist/index.js
10898
10907
  var FIRE_PRIME_C = 300;
@@ -16157,6 +16166,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
16157
16166
  };
16158
16167
 
16159
16168
  // ../game-floorplan-structure/dist/index.js
16169
+ var __defProp2 = Object.defineProperty;
16170
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
16171
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
16160
16172
  var uniform = (joint) => ({ wall: joint, slab: joint, beam: joint, column: joint, truss: joint });
16161
16173
  var CONSTRUCTION_SYSTEMS = /* @__PURE__ */ new Map([
16162
16174
  ["masonry", { walls: "brick_wall_plastered", joints: { wall: "mortar", slab: "rebar", beam: "mortar", column: "rebar", truss: "bolt" }, slabThickness: 0.12, plateThickness: 0.2, timber: false, wallRole: "rules", wallFill: 1 }],
@@ -16165,10 +16177,59 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
16165
16177
  ["improvised", { walls: "sheet_metal_shack", joints: uniform("nail"), slabThickness: 0.2, plateThickness: 0.1, timber: true, wallRole: "partition", wallFill: 1 }],
16166
16178
  ["canvas", { walls: "", joints: uniform("tie"), slabThickness: 0, plateThickness: 0, timber: false, wallRole: "partition", wallFill: 1 }]
16167
16179
  ]);
16180
+ var ROOF_COVERINGS = /* @__PURE__ */ new Map([
16181
+ ["colonial", { material: "ceramic", joint: "resting" }],
16182
+ ["fiber_cement", { material: "concrete", joint: "bolt" }],
16183
+ ["slab", { material: "concrete", joint: "rebar" }],
16184
+ ["asphalt", { material: "plastic", joint: "nail" }],
16185
+ ["zinc", { material: "steel", joint: "nail" }],
16186
+ ["wood", { material: "wood_soft", joint: "nail" }],
16187
+ ["canvas", { material: "fabric", joint: "tie" }],
16188
+ ["thatch", { material: "foliage", joint: "tie" }]
16189
+ ]);
16190
+ var WALL_THICKNESS = 0.15;
16168
16191
  var round2 = (value) => Math.round(value * 100) / 100;
16169
16192
  function touches(a, b, tolerance) {
16170
16193
  return a[0] < b[2] + tolerance && a[2] > b[0] - tolerance && a[1] < b[3] + tolerance && a[3] > b[1] - tolerance;
16171
16194
  }
16195
+ function distanceBetween(a, b) {
16196
+ return Math.hypot(Math.max(a[0] - b[2], b[0] - a[2], 0), Math.max(a[1] - b[3], b[1] - a[3], 0));
16197
+ }
16198
+ function unionOf(rects) {
16199
+ const out = [1e9, 1e9, -1e9, -1e9];
16200
+ for (const rect of rects) {
16201
+ out[0] = Math.min(out[0], rect[0]);
16202
+ out[1] = Math.min(out[1], rect[1]);
16203
+ out[2] = Math.max(out[2], rect[2]);
16204
+ out[3] = Math.max(out[3], rect[3]);
16205
+ }
16206
+ return out;
16207
+ }
16208
+ function wallBox(wall) {
16209
+ const half = (wall.thickness ?? WALL_THICKNESS) / 2;
16210
+ return [Math.min(wall.u0, wall.u1) - half, Math.min(wall.v0, wall.v1) - half, Math.max(wall.u0, wall.u1) + half, Math.max(wall.v0, wall.v1) + half];
16211
+ }
16212
+ var SLAB_COVERING = "slab";
16213
+ var PLASTERED_FINISHES = ["plaster", "partial_plaster"];
16214
+ function masonryConstruction(source) {
16215
+ const unit = source.floorNumber !== void 0;
16216
+ const covering = unit ? SLAB_COVERING : source.facade.roof ?? "colonial";
16217
+ const finish = source.facade.finish ?? "roughcast";
16218
+ const budget = source.style === "budget";
16219
+ const flat = unit || covering === SLAB_COVERING;
16220
+ return {
16221
+ system: "masonry",
16222
+ walls: { material: "brick", cladding: PLASTERED_FINISHES.includes(finish) ? "plaster" : null, finish, thickness: WALL_THICKNESS, ...finish === "exposed_block" ? { assembly: "concrete_block_wall" } : {} },
16223
+ floor: { material: budget ? "concrete" : "ceramic", finish: budget ? "burnt_cement" : "ceramic_tile" },
16224
+ roof: { material: ROOF_COVERINGS.get(covering)?.material ?? "ceramic", covering, shape: flat ? "slab" : "gable", pitch: flat ? 0 : covering === "colonial" ? 30 : 15, timber: "wood_soft" },
16225
+ frame: { material: "concrete", pillars: "concrete", beams: "concrete", slabs: "concrete", timber: "wood_soft", rebar: "steel" },
16226
+ foundation: { type: unit ? "building" : "strip_footing", material: "concrete", elevation: 0 }
16227
+ };
16228
+ }
16229
+ function resolveConstruction(source) {
16230
+ const extras = source.structureExtras ?? {};
16231
+ return { construction: source.construction ?? masonryConstruction(source), extras };
16232
+ }
16172
16233
  var en_default = {
16173
16234
  ui: {
16174
16235
  structure_layer: "Structure",
@@ -16239,8 +16300,950 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
16239
16300
  };
16240
16301
  var DEFAULT_DENSITY = 500;
16241
16302
  var densityOf = (material) => DENSITY[material] ?? DEFAULT_DENSITY;
16303
+ var StructureBuilder = class {
16304
+ constructor() {
16305
+ __publicField(this, "parts", []);
16306
+ __publicField(this, "supports", []);
16307
+ __publicField(this, "ids", /* @__PURE__ */ new Set());
16308
+ }
16309
+ add(draft, options = {}) {
16310
+ const { id, role, material, aabb, y0, y1 } = draft;
16311
+ if (this.ids.has(id)) throw new Error(`Part repeated in the structure: ${id}`);
16312
+ this.ids.add(id);
16313
+ const { floor, fill: fill2, ...extras } = options;
16314
+ const volume = Math.max(0, (aabb[2] - aabb[0]) * (aabb[3] - aabb[1]) * (y1 - y0)) * (fill2 ?? 1);
16315
+ const part = {
16316
+ id,
16317
+ role,
16318
+ material,
16319
+ aabb: aabb.map(round2),
16320
+ y0: round2(y0),
16321
+ y1: round2(y1),
16322
+ floor: floor ?? 0,
16323
+ mass: Math.max(1, Math.round(volume * densityOf(material)))
16324
+ };
16325
+ for (const [key, value] of Object.entries(extras)) if (value !== void 0) part[key] = value;
16326
+ this.parts.push(part);
16327
+ return part;
16328
+ }
16329
+ rest(part, on, joint) {
16330
+ const id = typeof part === "string" ? part : part.id;
16331
+ const base = typeof on === "string" ? on : on.id;
16332
+ this.supports.push({ part: id, on: base === GROUND ? GROUND : base, joint });
16333
+ }
16334
+ has(id) {
16335
+ return this.ids.has(id);
16336
+ }
16337
+ find(id) {
16338
+ return this.parts.find((part) => part.id === id);
16339
+ }
16340
+ };
16341
+ function hashLegacyKey(key) {
16342
+ let hash = 1779033703 ^ key.length;
16343
+ for (let i = 0; i < key.length; i++) {
16344
+ hash = Math.imul(hash ^ key.charCodeAt(i), 3432918353);
16345
+ hash = hash << 13 | hash >>> 19;
16346
+ }
16347
+ hash = Math.imul(hash ^ hash >>> 16, 2246822507);
16348
+ hash = Math.imul(hash ^ hash >>> 13, 3266489909);
16349
+ return (hash ^ hash >>> 16) >>> 0;
16350
+ }
16242
16351
  var OPEN_ROOM_TYPES2 = ["balcony", "terrace", "porch"];
16352
+ var GUARD_HEIGHT = 1.1;
16353
+ function createContext(source, construction, extras, tank, options) {
16354
+ const system = CONSTRUCTION_SYSTEMS.get(construction.system);
16355
+ if (!system) throw new Error(`Unknown construction system "${construction.system}"`);
16356
+ const floorCount2 = source.floors.length;
16357
+ const isUnit = source.floorNumber !== void 0;
16358
+ const elevation = construction.foundation.elevation || 0;
16359
+ const level = (floor) => round2(elevation + (source.floors[floor]?.y0 ?? 0));
16360
+ const ceilingOf = (floor) => source.floors[floor]?.ceiling ?? 0;
16361
+ const topOf = (floor) => floor < floorCount2 - 1 && !isUnit ? level(floor + 1) - system.slabThickness : level(floor) + ceilingOf(floor);
16362
+ const rectsOf2 = (floor, exclude = []) => source.rooms.filter((room) => room.floor === floor && !exclude.includes(room.type)).flatMap((room) => room.parts);
16363
+ const builder = new StructureBuilder();
16364
+ const context = {
16365
+ source,
16366
+ construction,
16367
+ system,
16368
+ extras,
16369
+ tank,
16370
+ builder,
16371
+ porchRoofOnPostsOnly: options.porchRoofOnPostsOnly,
16372
+ rafterRoof: options.rafterRoof,
16373
+ floorCount: floorCount2,
16374
+ isUnit,
16375
+ elevation,
16376
+ isTimber: system.timber,
16377
+ walls: [],
16378
+ wallParts: /* @__PURE__ */ new Map(),
16379
+ carriers: /* @__PURE__ */ new Map(),
16380
+ plates: /* @__PURE__ */ new Map(),
16381
+ pillars: /* @__PURE__ */ new Map(),
16382
+ porches: /* @__PURE__ */ new Map(),
16383
+ base0: GROUND,
16384
+ houseBox: [0, 0, 0, 0],
16385
+ topBox: [0, 0, 0, 0],
16386
+ hasBasement: false,
16387
+ roof: { shape: "slab", covering: "", material: "", pitch: 0, bearingFloor: 0, panes: [], trusses: 0 },
16388
+ level,
16389
+ ceilingOf,
16390
+ topOf,
16391
+ rectsOf: rectsOf2,
16392
+ baseOf: (floor) => floor === 0 ? context.base0 : builder.find(`slab_${floor}`)
16393
+ };
16394
+ const closed = rectsOf2(0, ["balcony", "porch"]);
16395
+ context.houseBox = unionOf(closed.length ? closed : rectsOf2(0));
16396
+ context.walls = classifyWalls(context);
16397
+ return context;
16398
+ }
16399
+ var isBearingOnTop = (wall, walls) => {
16400
+ const along = (w, pick2) => w.axis === "u" ? pick2(w.u0, w.u1) : pick2(w.v0, w.v1);
16401
+ const across = (w) => w.axis === "u" ? w.v0 : w.u0;
16402
+ return walls.some(
16403
+ (other) => other.floor === wall.floor + 1 && other.type === "wall" && other.axis === wall.axis && Math.abs(across(other) - across(wall)) < 0.2 && Math.min(along(wall, Math.max), along(other, Math.max)) - Math.max(along(wall, Math.min), along(other, Math.min)) > 0.3
16404
+ );
16405
+ };
16406
+ function roleOf(context, wall, walls, porchIds) {
16407
+ if (wall.type === "railing") return "partition";
16408
+ const { wallRole } = context.system;
16409
+ if (wallRole === "bearing") return "wall";
16410
+ if (wallRole === "partition") return "partition";
16411
+ const bearingFloor = context.construction.roof.bearingFloor ?? context.floorCount - 1;
16412
+ if (wall.floor > bearingFloor) return "partition";
16413
+ if (wall.exterior || wall.sides.some((id) => porchIds.has(id))) return "wall";
16414
+ return wall.floor < context.floorCount - 1 && isBearingOnTop(wall, walls) ? "wall" : "partition";
16415
+ }
16416
+ function materialOf(context, wall) {
16417
+ const { walls, guard } = context.construction;
16418
+ if (wall.type === "railing") return guard ?? (context.isTimber ? "wood_soft" : "steel");
16419
+ if (walls.mixed?.length) return walls.mixed[hashLegacyKey(wall.legacyKey) % walls.mixed.length];
16420
+ return wall.exterior ? walls.material : walls.interior ?? walls.material;
16421
+ }
16422
+ function classifyWalls(context) {
16423
+ const countByFloor = /* @__PURE__ */ new Map();
16424
+ const all = context.source.walls.map((wall) => {
16425
+ const index = countByFloor.get(wall.floor) ?? 0;
16426
+ countByFloor.set(wall.floor, index + 1);
16427
+ return { ...wall, id: `wall_${wall.floor}_${index}`, legacyKey: `w${wall.floor}_${index}`, role: "wall", material: "", box: wallBox(wall) };
16428
+ });
16429
+ const built = all.filter((wall) => wall.type === "wall" || wall.type === "railing");
16430
+ const porchIds = new Set(context.source.rooms.filter((room) => room.type === "porch").map((room) => room.id));
16431
+ for (const wall of built) {
16432
+ wall.role = roleOf(context, wall, built, porchIds);
16433
+ wall.material = materialOf(context, wall);
16434
+ }
16435
+ return built;
16436
+ }
16437
+ var guardHeightOf = (wall) => wall.h || GUARD_HEIGHT;
16438
+ function addBalconies(context) {
16439
+ const { builder, construction, system } = context;
16440
+ for (const room of context.source.rooms.filter((r) => r.type === "balcony" && r.floor >= 0)) {
16441
+ const floor = room.floor;
16442
+ const box22 = unionOf(room.parts);
16443
+ const base = floor === 0 ? void 0 : context.baseOf(floor);
16444
+ const deck = builder.add({ id: `slab_${floor}_${room.id}`, role: "slab", material: construction.frame.slabs, aabb: box22, y0: context.level(floor) - system.slabThickness, y1: context.level(floor) }, { floor });
16445
+ if (base) {
16446
+ builder.rest(deck, base, system.joints.slab);
16447
+ continue;
16448
+ }
16449
+ const pillar = builder.add({ id: `balcony_column_${room.id}`, role: "column", material: construction.frame.pillars, aabb: [box22[0] - 0.08, box22[1] - 0.08, box22[0] + 0.08, box22[1] + 0.08], y0: 0, y1: context.level(floor) - system.slabThickness }, { floor: 0 });
16450
+ builder.rest(pillar, GROUND, "rebar");
16451
+ builder.rest(deck, pillar, "rebar");
16452
+ }
16453
+ }
16454
+ var GROUNDSHEET = 0.02;
16455
+ function buildCanvasStructure(construction, extras) {
16456
+ const builder = new StructureBuilder();
16457
+ const panes = [];
16458
+ for (const tent of extras.tents ?? []) {
16459
+ const [u0, v0, u1, v1] = tent.aabb;
16460
+ const centreU = (u0 + u1) / 2;
16461
+ const centreV = (v0 + v1) / 2;
16462
+ const sheet = builder.add({ id: `groundsheet_${tent.id}`, role: "slab", material: "plastic", aabb: tent.aabb, y0: 0, y1: GROUNDSHEET }, { floor: 0, tent: tent.id });
16463
+ builder.rest(sheet, GROUND, "resting");
16464
+ const canvas = builder.add({ id: `canvas_${tent.id}`, role: "roof", material: "fabric", aabb: tent.aabb, y0: GROUNDSHEET, y1: tent.height }, { floor: 0, fill: 0.04, tent: tent.id, form: tent.kind });
16465
+ panes.push(canvas.id);
16466
+ const poles = tent.kind === "tunnel" ? [0.25, 0.75].map((fraction, k) => [[u0 + (u1 - u0) * fraction - 0.015, v0 + 0.05, u0 + (u1 - u0) * fraction + 0.015, v1 - 0.05], k]) : [[[u0 + 0.05, centreV - 0.015, u1 - 0.05, centreV + 0.015], 0], [[centreU - 0.015, v0 + 0.05, centreU + 0.015, v1 - 0.05], 1]];
16467
+ for (const [aabb, k] of poles) {
16468
+ const pole = builder.add({ id: `pole_${tent.id}_${k}`, role: "column", material: construction.frame.pillars, aabb, y0: GROUNDSHEET, y1: tent.height - GROUNDSHEET }, { floor: 0, fill: 0.05, tent: tent.id });
16469
+ builder.rest(pole, GROUND, "resting");
16470
+ builder.rest(canvas, pole, "tie");
16471
+ }
16472
+ const corners = [[u0 - 0.2, v0 - 0.2], [u1 + 0.2, v0 - 0.2], [u0 - 0.2, v1 + 0.2], [u1 + 0.2, v1 + 0.2]];
16473
+ corners.forEach(([u, v], k) => {
16474
+ const peg = builder.add({ id: `peg_${tent.id}_${k}`, role: "column", material: "wood_soft", aabb: [u - 0.02, v - 0.02, u + 0.02, v + 0.02], y0: 0, y1: 0.2 }, { floor: 0 });
16475
+ builder.rest(peg, GROUND, "nail");
16476
+ builder.rest(canvas, peg, "tie");
16477
+ });
16478
+ }
16479
+ const roof = { shape: "tent", covering: "canvas", material: "fabric", pitch: 0, bearingFloor: 0, panes, trusses: 0 };
16480
+ return { parts: builder.parts, supports: builder.supports, levels: [0], elevation: 0, roof, construction, framed: false, openings: [] };
16481
+ }
16482
+ var CHIMNEY_CLEARANCE_ABOVE_RIDGE = 0.6;
16483
+ function addChimney(context) {
16484
+ const chimney = context.extras.chimney;
16485
+ if (!chimney) return;
16486
+ const { builder } = context;
16487
+ const top = context.floorCount - 1;
16488
+ const ridge = context.roof.ridge || context.level(top) + context.ceilingOf(top);
16489
+ const height = Math.max(ridge, chimney.top || 0) + CHIMNEY_CLEARANCE_ABOVE_RIDGE;
16490
+ const part = builder.add({ id: "chimney", role: "column", material: chimney.material || "brick", aabb: chimney.aabb, y0: chimney.flue ? context.level(0) : 0, y1: height }, { floor: 0, fill: chimney.flue ? 0.15 : 0.7 });
16491
+ if (chimney.flue) builder.rest(part, context.base0 ?? GROUND, "resting");
16492
+ else builder.rest(part, GROUND, "mortar");
16493
+ const walls = builder.parts.filter((wall) => wall.role === "wall" && wall.floor === 0 && !wall.id.startsWith("basement") && touches(wall.aabb, part.aabb, 0.1));
16494
+ for (const wall of walls) builder.rest(part, wall, "bolt");
16495
+ chimney.height = round2(height);
16496
+ }
16497
+ function addWell(context) {
16498
+ const well = context.extras.well;
16499
+ if (!well) return;
16500
+ const part = context.builder.add({ id: "well", role: "column", material: "stone", aabb: well.aabb, y0: -0.5, y1: well.height || 0.9 }, { floor: 0, fill: 0.5 });
16501
+ context.builder.rest(part, GROUND, "mortar");
16502
+ }
16503
+ var JOIST_PITCH = 0.6;
16504
+ function addJoists(context, options) {
16505
+ const { floor, box: box22, thickness, carriers, material, joint, suffix = "" } = options;
16506
+ const length = box22[2] - box22[0];
16507
+ const depth = box22[3] - box22[1];
16508
+ const alongU = length >= depth;
16509
+ const span = alongU ? length : depth;
16510
+ const count = Math.max(2, Math.round(span / JOIST_PITCH) + 1);
16511
+ const top = context.level(floor) - thickness * 0.15;
16512
+ const out = [];
16513
+ for (let k = 0; k < count; k++) {
16514
+ const centre = (alongU ? box22[0] : box22[1]) + 0.05 + k * (span - 0.1) / (count - 1);
16515
+ const aabb = alongU ? [centre - 0.025, box22[1], centre + 0.025, box22[3]] : [box22[0], centre - 0.025, box22[2], centre + 0.025];
16516
+ const joist = context.builder.add({ id: `joist_${floor}${suffix}_${k}`, role: "beam", material, aabb, y0: context.level(floor) - thickness, y1: top }, { floor, fill: 1, spacing: JOIST_PITCH });
16517
+ out.push(joist);
16518
+ const below = carriers.filter((carrier) => carrier.y0 <= top + 0.05);
16519
+ const touching = below.filter((carrier) => touches(aabb, carrier.aabb, 0.2));
16520
+ const pool = below.length ? below : carriers;
16521
+ const chosen = touching.length ? touching : pool.slice().sort((a, b) => distanceBetween(aabb, a.aabb) - distanceBetween(aabb, b.aabb)).slice(0, 1);
16522
+ for (const carrier of chosen) context.builder.rest(joist, carrier, joint);
16523
+ }
16524
+ return out;
16525
+ }
16526
+ function addDeck(context, id, material, options, joists) {
16527
+ const { floor, box: box22, thickness, joint } = options;
16528
+ const deck = context.builder.add({ id, role: "slab", material, aabb: box22, y0: context.level(floor) - thickness * 0.15, y1: context.level(floor) }, { floor });
16529
+ for (const joist of joists) context.builder.rest(deck, joist, joint);
16530
+ return deck;
16531
+ }
16532
+ var raft = (context, thickness, material) => {
16533
+ const [u0, v0, u1, v1] = context.houseBox;
16534
+ const part = context.builder.add({ id: "foundation_raft", role: "slab", material, aabb: [u0 - 0.1, v0 - 0.1, u1 + 0.1, v1 + 0.1], y0: 0, y1: thickness }, { floor: 0 });
16535
+ context.builder.rest(part, GROUND, "resting");
16536
+ return part;
16537
+ };
16538
+ function addBuildingBase(context) {
16539
+ const { builder, construction, houseBox: box22 } = context;
16540
+ const corners = [[box22[0], box22[1]], [box22[2], box22[1]], [box22[0], box22[3]], [box22[2], box22[3]]];
16541
+ const top = context.level(0) - context.system.slabThickness;
16542
+ const pillars = corners.map(([u, v], k) => {
16543
+ const pillar = builder.add({ id: `building_column_${k}`, role: "column", material: construction.frame.pillars, aabb: [u - 0.15, v - 0.15, u + 0.15, v + 0.15], y0: 0, y1: top }, { floor: 0 });
16544
+ builder.rest(pillar, GROUND, "rebar");
16545
+ return pillar;
16546
+ });
16547
+ const slab = builder.add({ id: "slab_base", role: "slab", material: construction.frame.slabs, aabb: box22, y0: top, y1: context.level(0) }, { floor: 0, footprint: [box22] });
16548
+ for (const pillar of pillars) builder.rest(slab, pillar, "rebar");
16549
+ context.base0 = slab;
16550
+ }
16551
+ function addBasement(context) {
16552
+ const { builder, houseBox: box22, elevation } = context;
16553
+ const depth = context.construction.foundation.depth || 2.4;
16554
+ const floor = builder.add({ id: "basement_slab", role: "slab", material: "concrete", aabb: box22, y0: -depth - 0.15, y1: -depth }, { floor: -1 });
16555
+ builder.rest(floor, GROUND, "resting");
16556
+ const sides = [
16557
+ ["front", [box22[0], box22[1] - 0.1, box22[2], box22[1] + 0.1]],
16558
+ ["back", [box22[0], box22[3] - 0.1, box22[2], box22[3] + 0.1]],
16559
+ ["left", [box22[0] - 0.1, box22[1], box22[0] + 0.1, box22[3]]],
16560
+ ["right", [box22[2] - 0.1, box22[1], box22[2] + 0.1, box22[3]]]
16561
+ ];
16562
+ const carried = [];
16563
+ for (const [name, aabb] of sides) {
16564
+ const wall = builder.add({ id: `basement_wall_${name}`, role: "wall", material: "concrete", aabb, y0: -depth, y1: elevation }, { floor: -1 });
16565
+ builder.rest(wall, GROUND, "rebar");
16566
+ builder.rest(wall, floor, "rebar");
16567
+ carried.push(wall);
16568
+ }
16569
+ const count = Math.max(1, Math.floor((box22[2] - box22[0]) / 3));
16570
+ for (let k = 0; k < count; k++) {
16571
+ const u = box22[0] + (k + 0.5) * (box22[2] - box22[0]) / count;
16572
+ const v = (box22[1] + box22[3]) / 2;
16573
+ const column = builder.add({ id: `basement_column_${k}`, role: "column", material: "concrete", aabb: [u - 0.15, v - 0.15, u + 0.15, v + 0.15], y0: -depth, y1: elevation - 0.3 }, { floor: -1 });
16574
+ builder.rest(column, floor, "rebar");
16575
+ builder.rest(column, GROUND, "rebar");
16576
+ carried.push(column);
16577
+ }
16578
+ context.hasBasement = true;
16579
+ return carried;
16580
+ }
16581
+ function addPiers(context) {
16582
+ const { builder, houseBox: box22, elevation, construction } = context;
16583
+ const columns = Math.max(2, Math.ceil((box22[2] - box22[0]) / 2) + 1);
16584
+ const rows = Math.max(2, Math.ceil((box22[3] - box22[1]) / 2) + 1);
16585
+ const height = Math.max(0.1, elevation - context.system.slabThickness - 0.15);
16586
+ const piers = [];
16587
+ for (let j = 0; j < rows; j++) {
16588
+ for (let i = 0; i < columns; i++) {
16589
+ const u = box22[0] + i * (box22[2] - box22[0]) / (columns - 1);
16590
+ const v = box22[1] + j * (box22[3] - box22[1]) / (rows - 1);
16591
+ const pier = builder.add({ id: `footing_pier_${j}_${i}`, role: "column", material: construction.foundation.material || "concrete", aabb: [u - 0.12, v - 0.12, u + 0.12, v + 0.12], y0: 0, y1: height }, { floor: 0 });
16592
+ builder.rest(pier, GROUND, "resting");
16593
+ piers.push(pier);
16594
+ }
16595
+ }
16596
+ const alongU = box22[2] - box22[0] >= box22[3] - box22[1];
16597
+ const beams = [];
16598
+ const lines = alongU ? rows : columns;
16599
+ for (let line = 0; line < lines; line++) {
16600
+ const at2 = alongU ? box22[1] + line * (box22[3] - box22[1]) / (rows - 1) : box22[0] + line * (box22[2] - box22[0]) / (columns - 1);
16601
+ const aabb = alongU ? [box22[0], at2 - 0.07, box22[2], at2 + 0.07] : [at2 - 0.07, box22[1], at2 + 0.07, box22[3]];
16602
+ const beam = builder.add({ id: `base_beam_${line}`, role: "beam", material: construction.frame.timber, aabb, y0: height, y1: elevation - context.system.slabThickness }, { floor: 0 });
16603
+ const centre = (q) => alongU ? (q.aabb[1] + q.aabb[3]) / 2 : (q.aabb[0] + q.aabb[2]) / 2;
16604
+ for (const pier of piers.filter((q) => Math.abs(centre(q) - at2) < 0.05)) builder.rest(beam, pier, "bolt");
16605
+ beams.push(beam);
16606
+ }
16607
+ return beams;
16608
+ }
16609
+ function addRaisedFloor(context) {
16610
+ const { construction, system, houseBox: box22 } = context;
16611
+ const carriers = construction.foundation.type === "basement" ? addBasement(context) : addPiers(context);
16612
+ const thickness = system.slabThickness;
16613
+ const joists = addJoists(context, { floor: 0, box: box22, thickness, carriers, material: construction.frame.timber, joint: construction.foundation.type === "basement" ? "bolt" : "nail" });
16614
+ const deckMaterial = context.isTimber ? construction.floor.material === "wood_hard" ? "wood_hard" : "plywood" : construction.frame.slabs;
16615
+ context.base0 = addDeck(context, "slab_0", deckMaterial, { floor: 0, box: box22, thickness, joint: "nail" }, joists);
16616
+ }
16617
+ function addFoundation(context) {
16618
+ const { type, material } = context.construction.foundation;
16619
+ if (context.isUnit) return addBuildingBase(context);
16620
+ if (type === "raft") context.base0 = raft(context, Math.max(0.1, context.elevation), material || "concrete");
16621
+ else if (type === "crawl_space" || type === "piles" || type === "basement") addRaisedFloor(context);
16622
+ else if (context.isTimber && type !== "ground") context.base0 = raft(context, Math.max(0.12, context.elevation), "concrete");
16623
+ }
16624
+ var MAX_GAP_WITHIN_RUN = 3.2;
16625
+ var POST_SPACING = 2.4;
16626
+ var POST_SIDE = 0.05;
16627
+ var alongOf = (wall, pick2) => wall.axis === "u" ? pick2(wall.u0, wall.u1) : pick2(wall.v0, wall.v1);
16628
+ var acrossOf = (wall) => wall.axis === "u" ? wall.v0 : wall.u0;
16629
+ function runsOf(context, walls, floor) {
16630
+ const lines = /* @__PURE__ */ new Map();
16631
+ for (const wall of walls) {
16632
+ if (!wall.exterior || wall.type !== "wall") continue;
16633
+ const top = round2(context.wallParts.get(wall.id).y1);
16634
+ const key = `${floor}|${wall.axis}|${acrossOf(wall).toFixed(2)}|${top.toFixed(2)}`;
16635
+ lines.set(key, [...lines.get(key) ?? [], wall]);
16636
+ }
16637
+ return [...lines.values()].map((line) => {
16638
+ const sorted = line.slice().sort((a, b) => alongOf(a, Math.min) - alongOf(b, Math.min));
16639
+ const runs = [];
16640
+ for (const wall of sorted) {
16641
+ const from = alongOf(wall, Math.min);
16642
+ const to = alongOf(wall, Math.max);
16643
+ const last = runs[runs.length - 1];
16644
+ if (last && from - last.to <= MAX_GAP_WITHIN_RUN) {
16645
+ last.to = Math.max(last.to, to);
16646
+ last.walls.push(wall);
16647
+ } else runs.push({ from, to, walls: [wall] });
16648
+ }
16649
+ return runs;
16650
+ });
16651
+ }
16652
+ function addShackPosts(context, run, plate, floor, index) {
16653
+ const first = run.walls[0];
16654
+ const across = acrossOf(first);
16655
+ const top = context.wallParts.get(first.id).y1;
16656
+ const count = Math.max(1, Math.ceil((run.to - run.from) / POST_SPACING));
16657
+ const posts = [];
16658
+ for (let k = 0; k <= count; k++) {
16659
+ const along = run.from + (run.to - run.from) * k / count;
16660
+ const [u, v] = first.axis === "u" ? [along, across] : [across, along];
16661
+ const half = 0.05 + POST_SIDE / 2;
16662
+ const post = context.builder.add({ id: `post_${floor}_${index}_${k}`, role: "column", material: "wood_soft", aabb: [u - half, v - half, u + half, v + half], y0: context.level(floor), y1: top }, { floor });
16663
+ const base = context.baseOf(floor);
16664
+ if (base === GROUND || !base) context.builder.rest(post, GROUND, "tie");
16665
+ else context.builder.rest(post, base, "nail");
16666
+ context.builder.rest(plate, post, "nail");
16667
+ posts.push(post);
16668
+ }
16669
+ return posts;
16670
+ }
16671
+ function addPlates(context, walls, floor) {
16672
+ const { builder, system, construction } = context;
16673
+ const plates = [];
16674
+ const pillars = [];
16675
+ let counter = 0;
16676
+ for (const runs of runsOf(context, walls, floor)) {
16677
+ for (const run of runs) {
16678
+ const first = run.walls[0];
16679
+ const half = (first.thickness ?? 0.15) / 2;
16680
+ const across = acrossOf(first);
16681
+ const top = context.wallParts.get(first.id).y1;
16682
+ const aabb = first.axis === "u" ? [run.from - half, across - half, run.to + half, across + half] : [across - half, run.from - half, across + half, run.to + half];
16683
+ const material = context.isTimber ? construction.frame.timber : construction.frame.beams;
16684
+ const plate = builder.add({ id: `plate_${floor}_${counter++}`, role: "beam", material, aabb, y0: top, y1: top + system.plateThickness }, { floor, beamKind: context.isTimber ? "plate" : "bond_beam" });
16685
+ if (construction.system === "improvised") pillars.push(...addShackPosts(context, run, plate, floor, counter));
16686
+ else for (const wall of run.walls) builder.rest(plate, context.wallParts.get(wall.id), system.joints.beam);
16687
+ plates.push(plate);
16688
+ }
16689
+ }
16690
+ return { plates, pillars };
16691
+ }
16692
+ function cornerPoints(walls) {
16693
+ const ends = walls.filter((wall) => wall.exterior && wall.type === "wall").flatMap(
16694
+ (wall) => wall.axis === "u" ? [[Math.min(wall.u0, wall.u1), wall.v0, "u"], [Math.max(wall.u0, wall.u1), wall.v0, "u"]] : [[wall.u0, Math.min(wall.v0, wall.v1), "v"], [wall.u0, Math.max(wall.v0, wall.v1), "v"]]
16695
+ );
16696
+ const points = [];
16697
+ for (const a of ends) {
16698
+ const joinsOtherAxis = ends.some((b) => b[2] !== a[2] && Math.hypot(a[0] - b[0], a[1] - b[1]) < 0.12);
16699
+ if (joinsOtherAxis && !points.some((q) => Math.hypot(q[0] - a[0], q[1] - a[1]) < 0.12)) points.push([a[0], a[1]]);
16700
+ }
16701
+ return points;
16702
+ }
16703
+ function addCornerPillars(context, walls, floor, plates) {
16704
+ const { builder } = context;
16705
+ const topOfFloor = context.topOf(floor);
16706
+ return cornerPoints(walls).map(([u, v], k) => {
16707
+ const box22 = [u - 0.1, v - 0.1, u + 0.1, v + 0.1];
16708
+ const top = Math.max(topOfFloor, ...plates.filter((plate) => touches(plate.aabb, box22, 0.05)).map((plate) => plate.y0));
16709
+ const pillar = builder.add({ id: `column_${floor}_${k}`, role: "column", material: context.construction.frame.pillars, aabb: box22, y0: context.level(floor), y1: top }, { floor });
16710
+ const base = context.baseOf(floor);
16711
+ builder.rest(pillar, base === GROUND || !base ? GROUND : base, "rebar");
16712
+ for (const plate of plates.filter((p) => touches(p.aabb, pillar.aabb, 0.05))) builder.rest(plate, pillar, "rebar");
16713
+ return pillar;
16714
+ });
16715
+ }
16716
+ function addPlatesAndPillars(context, floor) {
16717
+ const walls = context.walls.filter((wall) => wall.floor === floor);
16718
+ const { plates, pillars } = addPlates(context, walls, floor);
16719
+ if (context.construction.system === "masonry") pillars.push(...addCornerPillars(context, walls, floor, plates));
16720
+ context.pillars.set(floor, pillars);
16721
+ context.plates.set(floor, plates);
16722
+ const innerBearing = walls.filter((wall) => wall.role === "wall" && !wall.exterior).map((wall) => context.wallParts.get(wall.id));
16723
+ context.carriers.set(floor, [...plates, ...pillars, ...innerBearing]);
16724
+ }
16725
+ var POST_HALF = 0.08;
16726
+ var FOOTING_HALF = 0.12;
16727
+ var BEAM_DEPTH = 0.2;
16728
+ var BEAM_WIDTH = 0.16;
16729
+ function addPosts(context, porchId, rect, deck, floor) {
16730
+ const { builder, construction } = context;
16731
+ const hasUpperFloor = floor < context.floorCount - 1;
16732
+ const ceilingY = hasUpperFloor ? context.level(floor + 1) - context.system.slabThickness : context.level(floor) + context.ceilingOf(floor);
16733
+ const deckDepth = Math.min(0.2, Math.max(0.06, context.elevation * 0.5));
16734
+ const spots = [[rect[0] + 0.1, rect[1] + 0.1], [rect[2] - 0.1, rect[1] + 0.1]];
16735
+ if (rect[2] - rect[0] > 4.5) spots.push([(rect[0] + rect[2]) / 2, rect[1] + 0.1]);
16736
+ const posts = spots.map(([u, v], k) => {
16737
+ const footing = builder.add({ id: `porch_footing_${porchId}_${k}`, role: "column", material: "concrete", aabb: [u - FOOTING_HALF, v - FOOTING_HALF, u + FOOTING_HALF, v + FOOTING_HALF], y0: 0, y1: context.level(floor) - deckDepth }, { floor: 0 });
16738
+ builder.rest(footing, GROUND, "resting");
16739
+ builder.rest(deck, footing, "resting");
16740
+ const material = context.isTimber ? construction.frame.timber : construction.frame.pillars;
16741
+ const post = builder.add({ id: `porch_post_${porchId}_${k}`, role: "column", material, aabb: [u - POST_HALF, v - POST_HALF, u + POST_HALF, v + POST_HALF], y0: context.level(floor), y1: hasUpperFloor ? ceilingY - BEAM_DEPTH : ceilingY }, { floor });
16742
+ builder.rest(post, deck, context.isTimber ? "nail" : "rebar");
16743
+ return post;
16744
+ });
16745
+ context.porches.set(porchId, { posts, ceilingY });
16746
+ return posts;
16747
+ }
16748
+ function addUpperBeams(context, porchId, rect, posts, floor) {
16749
+ const { builder, construction } = context;
16750
+ const material = context.isTimber ? construction.frame.timber : construction.frame.beams;
16751
+ const joint = context.isTimber ? "nail" : "rebar";
16752
+ const ceilingY = context.porches.get(porchId).ceilingY;
16753
+ const y0 = ceilingY - BEAM_DEPTH;
16754
+ const carriers = context.carriers.get(floor);
16755
+ const head = builder.add({ id: `porch_beam_${porchId}`, role: "beam", material, aabb: [rect[0], rect[1], rect[2], rect[1] + BEAM_WIDTH], y0, y1: ceilingY }, { floor });
16756
+ for (const post of posts) builder.rest(head, post, joint);
16757
+ carriers.push(head);
16758
+ const sides = [["e", [rect[0], rect[1], rect[0] + BEAM_WIDTH, rect[3]], posts[0]], ["d", [rect[2] - BEAM_WIDTH, rect[1], rect[2], rect[3]], posts[1]]];
16759
+ for (const [name, aabb, post] of sides) {
16760
+ const side = builder.add({ id: `porch_beam_${porchId}_${name}`, role: "beam", material, aabb, y0, y1: ceilingY }, { floor });
16761
+ builder.rest(side, post, joint);
16762
+ builder.rest(side, head, joint);
16763
+ for (const wall of context.walls.filter((w) => w.floor === floor && w.role === "wall" && touches(w.box, side.aabb, 0.3))) builder.rest(side, context.wallParts.get(wall.id), joint);
16764
+ carriers.push(side);
16765
+ }
16766
+ }
16767
+ function addPorches(context, floor) {
16768
+ const { builder, construction } = context;
16769
+ for (const room of context.source.rooms.filter((r) => r.type === "porch" && r.floor === floor)) {
16770
+ const rect = room.parts[0];
16771
+ const base = context.baseOf(floor);
16772
+ const deckDepth = Math.min(0.2, Math.max(0.06, context.elevation * 0.5));
16773
+ const deck = builder.add({ id: `porch_floor_${room.id}`, role: "slab", material: context.isTimber ? "wood_soft" : construction.frame.slabs, aabb: rect, y0: context.level(floor) - deckDepth, y1: context.level(floor) }, { floor });
16774
+ if (base && base !== GROUND) builder.rest(deck, base, context.isTimber ? "nail" : context.system.joints.slab);
16775
+ const posts = addPosts(context, room.id, rect, deck, floor);
16776
+ if (floor < context.floorCount - 1) addUpperBeams(context, room.id, rect, posts, floor);
16777
+ }
16778
+ }
16779
+ var ROOF_RISE = 0.6;
16780
+ function addPorchRoofs(context) {
16781
+ const { builder, construction } = context;
16782
+ for (const room of context.source.rooms.filter((r) => r.type === "porch")) {
16783
+ const floor = room.floor;
16784
+ const rect = room.parts[0];
16785
+ const posts = context.porches.get(room.id);
16786
+ if (floor !== context.floorCount - 1 || !posts) continue;
16787
+ const material = ROOF_COVERINGS.get(construction.roof.covering)?.material ?? "ceramic";
16788
+ const roof = builder.add({ id: `porch_roof_${room.id}`, role: "roof", material, aabb: [rect[0] - 0.3, rect[1] - 0.5, rect[2] + 0.3, rect[3]], y0: posts.ceilingY, y1: posts.ceilingY + ROOF_RISE }, { floor });
16789
+ for (const post of posts.posts) builder.rest(roof, post, context.isTimber ? "nail" : "bolt");
16790
+ const housewalls = context.walls.filter((w) => w.floor === floor && !w.exterior && w.sides.includes(room.id) && w.type === "wall");
16791
+ for (const wall of housewalls) {
16792
+ if (context.porchRoofOnPostsOnly) builder.supports.push({ part: roof.id, on: wall.id, joint: "nail", bears: false });
16793
+ else builder.rest(roof, context.wallParts.get(wall.id), "nail");
16794
+ }
16795
+ }
16796
+ }
16797
+ var TRUSS_PITCH = 1.2;
16798
+ var DEFAULT_PITCH = 20;
16799
+ var DEFAULT_EAVE = 0.4;
16243
16800
  var CLOSED_ONLY = [...OPEN_ROOM_TYPES2, "void"];
16801
+ function roofUnits(context, bearing) {
16802
+ const units = [{ suffix: "", box: context.topBox, floor: bearing, annex: false }];
16803
+ if (context.floorCount <= 1 || context.isUnit || bearing !== context.floorCount - 1) return units;
16804
+ for (let floor = 0; floor < context.floorCount - 1; floor++) {
16805
+ const above = context.source.rooms.filter((room) => room.floor === floor + 1).flatMap((room) => room.parts);
16806
+ const rooms = context.source.rooms.filter((room) => room.floor === floor && !OPEN_ROOM_TYPES2.includes(room.type) && room.type !== "void" && room.type !== "elevator");
16807
+ for (const room of rooms) {
16808
+ const area3 = room.parts.reduce((total, part) => total + (part[2] - part[0]) * (part[3] - part[1]), 0);
16809
+ const covered = room.parts.reduce((total, part) => total + above.reduce((sum, other) => sum + Math.max(0, Math.min(part[2], other[2]) - Math.max(part[0], other[0])) * Math.max(0, Math.min(part[3], other[3]) - Math.max(part[1], other[1])), 0), 0);
16810
+ if (covered < area3 * 0.7) units.push({ suffix: `_${room.id}`, box: unionOf(room.parts), floor, annex: true });
16811
+ }
16812
+ }
16813
+ return units;
16814
+ }
16815
+ function roofCarriers(context, unit) {
16816
+ const platesHere = context.plates.get(unit.floor) ?? [];
16817
+ const allPlates = [...context.plates.values()].flat();
16818
+ const eaveHeight = platesHere.length ? Math.max(...platesHere.map((plate) => plate.y1)) : 0;
16819
+ const plates = allPlates.filter((plate) => Math.abs(plate.y1 - eaveHeight) < 0.4 && touches(plate.aabb, unit.box, 0.3));
16820
+ const pillars = (context.pillars.get(unit.floor) ?? []).filter((pillar) => touches(pillar.aabb, unit.box, 0.3));
16821
+ const walls = context.walls.filter((wall) => wall.floor === unit.floor && wall.role === "wall" && !wall.exterior && touches(wall.box, unit.box, 0.2)).map((wall) => context.wallParts.get(wall.id));
16822
+ return [...plates, ...pillars, ...walls];
16823
+ }
16824
+ function addFlatRoof(context, unit, carriers) {
16825
+ const { builder, construction, system } = context;
16826
+ const top = context.level(unit.floor) + context.ceilingOf(unit.floor);
16827
+ const [u0, v0, u1, v1] = unit.box;
16828
+ const slab = builder.add({ id: `slab_top${unit.suffix}`, role: "slab", material: construction.frame.slabs, aabb: [u0 - 0.15, v0 - 0.15, u1 + 0.15, v1 + 0.15], y0: top, y1: top + 0.12 }, { floor: unit.floor, cover: true, footprint: [unit.box] });
16829
+ for (const carrier of carriers.length ? carriers : context.plates.get(unit.floor) ?? []) builder.rest(slab, carrier, system.joints.slab);
16830
+ if (unit.suffix === "") context.topSlab = slab;
16831
+ return { id: slab.id, aabb: slab.aabb, y0: slab.y0, y1: slab.y1, flat: true };
16832
+ }
16833
+ function addTrusses(context, unit, carriers, geometry) {
16834
+ const { builder, construction, system } = context;
16835
+ const { box: box22 } = unit;
16836
+ const { axis, length, eave, rise, eaveY } = geometry;
16837
+ const count = Math.max(2, Math.ceil(length / TRUSS_PITCH) + 1);
16838
+ const trusses = [];
16839
+ for (let k = 0; k < count; k++) {
16840
+ const at2 = (axis === "u" ? box22[0] : box22[1]) + k * length / (count - 1);
16841
+ const aabb = axis === "u" ? [at2 - 0.04, box22[1] - eave * 0.5, at2 + 0.04, box22[3] + eave * 0.5] : [box22[0] - eave * 0.5, at2 - 0.04, box22[2] + eave * 0.5, at2 + 0.04];
16842
+ const truss = builder.add({ id: `truss${unit.suffix}_${k}`, role: "beam", material: construction.frame.timber, aabb, y0: eaveY, y1: eaveY + rise }, { floor: unit.floor, fill: 0.25 });
16843
+ const touching = carriers.filter((carrier) => touches(aabb, carrier.aabb, 0.2) && carrier.role !== "slab");
16844
+ const chosen = touching.length ? touching : carriers.slice().sort((a, b) => distanceBetween(aabb, a.aabb) - distanceBetween(aabb, b.aabb)).slice(0, 2);
16845
+ for (const carrier of chosen) builder.rest(truss, carrier, system.joints.truss);
16846
+ trusses.push(truss);
16847
+ }
16848
+ return trusses;
16849
+ }
16850
+ function geometryOf(context, unit) {
16851
+ const { box: box22 } = unit;
16852
+ const roof = context.construction.roof;
16853
+ const platesHere = context.plates.get(unit.floor) ?? [];
16854
+ const width = box22[2] - box22[0];
16855
+ const depth = box22[3] - box22[1];
16856
+ const axis = roof.axis ?? (width >= depth ? "u" : "v");
16857
+ const span = axis === "u" ? depth : width;
16858
+ const pitch = unit.annex ? Math.min(roof.pitch || DEFAULT_PITCH, 20) : roof.pitch || DEFAULT_PITCH;
16859
+ const eaveY = platesHere.length ? Math.max(...platesHere.map((plate) => plate.y1)) : context.level(unit.floor) + context.ceilingOf(unit.floor);
16860
+ return { axis, length: axis === "u" ? width : depth, eave: roof.eave ?? DEFAULT_EAVE, rise: round2(span / 2 * Math.tan(pitch * Math.PI / 180)), eaveY };
16861
+ }
16862
+ function addRafters(context, unit, carriers, geometry) {
16863
+ const { builder, construction, system } = context;
16864
+ const { box: box22 } = unit;
16865
+ const { axis, length, eave, rise, eaveY } = geometry;
16866
+ const count = Math.max(2, Math.ceil(length / TRUSS_PITCH) + 1);
16867
+ const middle = axis === "u" ? (box22[1] + box22[3]) / 2 : (box22[0] + box22[2]) / 2;
16868
+ const frames = { all: [], sideA: [], sideB: [] };
16869
+ for (let k = 0; k < count; k++) {
16870
+ const at2 = (axis === "u" ? box22[0] : box22[1]) + k * length / (count - 1);
16871
+ const halves = axis === "u" ? [["a", [at2 - 0.04, box22[1] - eave * 0.5, at2 + 0.04, middle]], ["b", [at2 - 0.04, middle, at2 + 0.04, box22[3] + eave * 0.5]]] : [["a", [box22[0] - eave * 0.5, at2 - 0.04, middle, at2 + 0.04]], ["b", [middle, at2 - 0.04, box22[2] + eave * 0.5, at2 + 0.04]]];
16872
+ for (const [side, aabb] of halves) {
16873
+ const rafter = builder.add({ id: `rafter${unit.suffix}_${side}_${k}`, role: "beam", material: construction.frame.timber, aabb, y0: eaveY, y1: eaveY + rise }, { floor: unit.floor, fill: 0.25 });
16874
+ const touching = carriers.filter((carrier) => touches(aabb, carrier.aabb, 0.2) && carrier.role !== "slab");
16875
+ const chosen = touching.length ? touching : carriers.slice().sort((x, y) => distanceBetween(aabb, x.aabb) - distanceBetween(aabb, y.aabb)).slice(0, 2);
16876
+ for (const carrier of chosen) builder.rest(rafter, carrier, system.joints.truss);
16877
+ frames.all.push(rafter);
16878
+ if (side === "a") frames.sideA.push(rafter);
16879
+ else frames.sideB.push(rafter);
16880
+ }
16881
+ }
16882
+ if (!unit.annex) {
16883
+ const ridgeBox = axis === "u" ? [box22[0] - eave, middle - 0.05, box22[2] + eave, middle + 0.05] : [middle - 0.05, box22[1] - eave, middle + 0.05, box22[3] + eave];
16884
+ const ridge = builder.add({ id: `ridge${unit.suffix}`, role: "beam", material: construction.frame.timber, aabb: ridgeBox, y0: eaveY + rise - 0.12, y1: eaveY + rise }, { floor: unit.floor });
16885
+ for (const rafter of frames.all) builder.rest(ridge, rafter, system.joints.truss);
16886
+ }
16887
+ context.roof.trusses += count;
16888
+ return frames;
16889
+ }
16890
+ function panesOf(unit, geometry) {
16891
+ const [u0, v0, u1, v1] = unit.box;
16892
+ const { eave, axis } = geometry;
16893
+ if (unit.annex) return [[u0 - eave, v0 - eave, u1 + eave, v1 + eave]];
16894
+ const midV = (v0 + v1) / 2;
16895
+ const midU = (u0 + u1) / 2;
16896
+ return axis === "u" ? [[u0 - eave, v0 - eave, u1 + eave, midV], [u0 - eave, midV, u1 + eave, v1 + eave]] : [[u0 - eave, v0 - eave, midU, v1 + eave], [midU, v0 - eave, u1 + eave, v1 + eave]];
16897
+ }
16898
+ function coarseFrames(context, unit, carriers, geometry) {
16899
+ const trusses = addTrusses(context, unit, carriers, geometry);
16900
+ context.roof.trusses += trusses.length;
16901
+ return { all: trusses, sideA: trusses, sideB: trusses };
16902
+ }
16903
+ function addGableRoof(context, unit, carriers, covering) {
16904
+ const { builder, construction } = context;
16905
+ const geometry = geometryOf(context, unit);
16906
+ const frames = context.rafterRoof ? addRafters(context, unit, carriers, geometry) : coarseFrames(context, unit, carriers, geometry);
16907
+ const panes = [];
16908
+ panesOf(unit, geometry).forEach((aabb, k) => {
16909
+ const name = unit.annex ? "annex" : k === 0 ? "a" : "b";
16910
+ const options = { floor: unit.floor, fill: construction.roof.covering === "colonial" ? 0.12 : 0.05, ...construction.roof.pitch === void 0 ? {} : { pitch: construction.roof.pitch } };
16911
+ const pane = builder.add({ id: `roof${unit.suffix}_${name}`, role: "roof", material: construction.roof.material, aabb, y0: geometry.eaveY, y1: geometry.eaveY + geometry.rise + 0.05 }, options);
16912
+ const carried = unit.annex || !context.rafterRoof ? frames.all : k === 0 ? frames.sideA : frames.sideB;
16913
+ for (const frame2 of carried) builder.rest(pane, frame2, covering);
16914
+ panes.push({ id: pane.id, aabb: pane.aabb, y0: pane.y0, y1: pane.y1 });
16915
+ });
16916
+ if (!unit.annex) {
16917
+ const roof = context.roof;
16918
+ roof.axis = geometry.axis;
16919
+ roof.ridge = round2(geometry.eaveY + geometry.rise);
16920
+ roof.eave = geometry.eave;
16921
+ roof.y0 = round2(geometry.eaveY);
16922
+ roof.aabb = [unit.box[0] - geometry.eave, unit.box[1] - geometry.eave, unit.box[2] + geometry.eave, unit.box[3] + geometry.eave].map(round2);
16923
+ }
16924
+ return panes;
16925
+ }
16926
+ function addCeilingJoists(context) {
16927
+ const { builder, construction } = context;
16928
+ const bearing = context.roof.bearingFloor;
16929
+ const rects = context.rectsOf(bearing, CLOSED_ONLY);
16930
+ const box22 = unionOf(rects.length ? rects : [context.topBox]);
16931
+ const carriers = [...context.plates.get(bearing) ?? [], ...context.walls.filter((w) => w.floor === bearing && w.role === "wall" && !w.exterior).map((w) => context.wallParts.get(w.id))];
16932
+ const length = box22[2] - box22[0];
16933
+ const depth = box22[3] - box22[1];
16934
+ const alongU = length >= depth;
16935
+ const span = alongU ? length : depth;
16936
+ const count = Math.max(2, Math.round(span / 0.6) + 1);
16937
+ const beamTops = carriers.filter((carrier) => carrier.role === "beam").map((carrier) => carrier.y0);
16938
+ const y0 = carriers.length ? Math.max(...beamTops, context.level(bearing) + 1) : context.topOf(bearing);
16939
+ const joists = [];
16940
+ for (let k = 0; k < count; k++) {
16941
+ const at2 = (alongU ? box22[0] : box22[1]) + 0.05 + k * (span - 0.1) / (count - 1);
16942
+ const aabb = alongU ? [at2 - 0.025, box22[1], at2 + 0.025, box22[3]] : [box22[0], at2 - 0.025, box22[2], at2 + 0.025];
16943
+ const joist = builder.add({ id: `ceiling_joist_${k}`, role: "beam", material: construction.frame.timber, aabb, y0: y0 - 0.02, y1: y0 + context.system.plateThickness - 0.02 }, { floor: bearing, spacing: 0.6 });
16944
+ const beams = carriers.filter((carrier) => carrier.role === "beam" && touches(aabb, carrier.aabb, 0.2));
16945
+ for (const carrier of beams.length ? beams : carriers.slice(0, 2)) builder.rest(joist, carrier, "nail");
16946
+ joists.push(joist);
16947
+ }
16948
+ context.ceilingJoists = joists;
16949
+ }
16950
+ function addRoof(context) {
16951
+ const { construction } = context;
16952
+ const covering = ROOF_COVERINGS.get(construction.roof.covering);
16953
+ const bearing = construction.roof.bearingFloor ?? context.floorCount - 1;
16954
+ const open = context.rectsOf(context.floorCount - 1, OPEN_ROOM_TYPES2);
16955
+ const topRects = context.rectsOf(context.floorCount - 1, CLOSED_ONLY);
16956
+ const body = unionOf(open.length ? open : topRects);
16957
+ context.topBox = context.floorCount - 1 === bearing ? body : unionOf(context.rectsOf(bearing, OPEN_ROOM_TYPES2));
16958
+ context.roof = { shape: construction.roof.shape, covering: construction.roof.covering, material: construction.roof.material, pitch: construction.roof.pitch || 0, bearingFloor: bearing, panes: [], trusses: 0 };
16959
+ for (const unit of roofUnits(context, bearing)) {
16960
+ const carriers = roofCarriers(context, unit);
16961
+ if (construction.roof.shape === "slab") context.roof.panes.push(addFlatRoof(context, unit, carriers));
16962
+ else context.roof.panes.push(...addGableRoof(context, unit, carriers, covering?.joint ?? "nail"));
16963
+ }
16964
+ if (context.isTimber && construction.roof.shape !== "slab") addCeilingJoists(context);
16965
+ }
16966
+ function flightsOf(items) {
16967
+ const flights = /* @__PURE__ */ new Map();
16968
+ for (const item of items.filter((it) => it.category === "stairs" && it.link)) {
16969
+ const key = `${item.link.from}_${item.link.to}`;
16970
+ if (!flights.has(key) || item.floor === item.link.from) flights.set(key, item);
16971
+ }
16972
+ return flights;
16973
+ }
16974
+ function addStairs(context) {
16975
+ const { builder, construction } = context;
16976
+ const material = context.isTimber ? construction.frame.timber : construction.stairs ?? "concrete";
16977
+ for (const [key, item2] of flightsOf(context.source.items)) {
16978
+ const { from, to } = item2.link;
16979
+ const flight2 = builder.add({ id: `stairs_${key}`, role: "stair", material, aabb: item2.aabb, y0: context.level(from), y1: context.level(to) }, { floor: from, steps: Math.round((context.level(to) - context.level(from)) / 0.18) });
16980
+ const base = context.baseOf(from);
16981
+ builder.rest(flight2, !base || base === GROUND ? GROUND : base, !base || base === GROUND ? "resting" : context.isTimber ? "nail" : "mortar");
16982
+ const upper = context.baseOf(to);
16983
+ if (upper && upper !== GROUND) builder.rest(flight2, upper, context.isTimber ? "nail" : "bolt");
16984
+ }
16985
+ if (!context.hasBasement) return;
16986
+ const item = context.source.items.find((it) => it.type === "basement_stairs");
16987
+ if (!item?.aabb) return;
16988
+ const depth = construction.foundation.depth || 2.4;
16989
+ const flight = builder.add({ id: "basement_stairs", role: "stair", material: "wood_soft", aabb: item.aabb, y0: -depth, y1: context.level(0) }, { floor: -1, steps: Math.round((context.level(0) + depth) / 0.18) });
16990
+ builder.rest(flight, builder.find("basement_slab"), "resting");
16991
+ if (context.base0 && context.base0 !== GROUND) builder.rest(flight, context.base0, "nail");
16992
+ }
16993
+ var TOLERANCE = 0.02;
16994
+ var DOOR_HEIGHT = 2.1;
16995
+ var LINTEL_DEPTH = 0.2;
16996
+ var LINTEL_BEARING = 0.2;
16997
+ var crossOf = (wall) => wall.axis === "u" ? wall.v0 : wall.u0;
16998
+ var alongRange = (wall) => wall.axis === "u" ? [Math.min(wall.u0, wall.u1), Math.max(wall.u0, wall.u1)] : [Math.min(wall.v0, wall.v1), Math.max(wall.v0, wall.v1)];
16999
+ function findOpenings(context) {
17000
+ const openings = [];
17001
+ for (const window of context.source.windows ?? []) {
17002
+ const from = window.at - window.width / 2;
17003
+ const to = window.at + window.width / 2;
17004
+ const wall = context.walls.find((w) => {
17005
+ if (w.floor !== window.floor || w.axis !== window.axis || w.type !== "wall" || Math.abs(crossOf(w) - window.pos) > TOLERANCE) return false;
17006
+ const [low, high] = alongRange(w);
17007
+ return from >= low - TOLERANCE && to <= high + TOLERANCE;
17008
+ });
17009
+ if (!wall) continue;
17010
+ const base = context.level(window.floor);
17011
+ openings.push({ id: window.id, kind: "window", wall: wall.id, from, to, y0: base + window.y0, y1: base + window.y1 });
17012
+ }
17013
+ return openings;
17014
+ }
17015
+ function addDoorLintels(context) {
17016
+ const { builder, construction, system } = context;
17017
+ for (const door of context.source.doors.filter((d) => d.a !== void 0 && d.type !== "zipper")) {
17018
+ const along = door.axis === "u" ? door.u : door.v;
17019
+ const cross = door.axis === "u" ? door.v : door.u;
17020
+ const from = along - door.width / 2;
17021
+ const to = along + door.width / 2;
17022
+ const beside = context.walls.filter((w) => w.floor === door.floor && w.axis === door.axis && w.type === "wall" && Math.abs(crossOf(w) - cross) <= TOLERANCE && (Math.abs(alongRange(w)[1] - from) <= 0.05 || Math.abs(alongRange(w)[0] - to) <= 0.05));
17023
+ if (!beside.length) continue;
17024
+ const half = (beside[0].thickness ?? 0.15) / 2;
17025
+ const lo = from - LINTEL_BEARING;
17026
+ const hi = to + LINTEL_BEARING;
17027
+ const box22 = door.axis === "u" ? [lo, cross - half, hi, cross + half] : [cross - half, lo, cross + half, hi];
17028
+ const y0 = context.level(door.floor) + DOOR_HEIGHT;
17029
+ const lintel = builder.add({ id: `lintel_${door.id}`, role: "beam", material: system.timber ? construction.frame.timber : "concrete", aabb: box22, y0, y1: y0 + LINTEL_DEPTH }, { floor: door.floor, ...system.timber ? {} : { assembly: "reinforced_concrete" } });
17030
+ for (const wall of beside) builder.rest(lintel, context.wallParts.get(wall.id), system.joints.beam);
17031
+ for (const plate of context.plates.get(door.floor) ?? []) if (overlapsInPlan(plate.aabb, box22)) builder.rest(plate, lintel, system.joints.beam);
17032
+ }
17033
+ }
17034
+ var overlapsInPlan = (a, b) => a[0] < b[2] && a[2] > b[0] && a[1] < b[3] && a[3] > b[1];
17035
+ var RISER = 0.18;
17036
+ var TREAD = 0.28;
17037
+ var stepCount = (elevation) => Math.max(1, Math.ceil(elevation / RISER));
17038
+ function addPorchSteps(context) {
17039
+ const { builder } = context;
17040
+ for (const room of context.source.rooms.filter((r) => r.type === "porch")) {
17041
+ const deck = builder.find(`porch_floor_${room.id}`);
17042
+ for (const door of context.source.doors.filter((d) => d.floor === room.floor && d.b === "outside" && d.a === room.id)) {
17043
+ const count = stepCount(context.elevation);
17044
+ const depth = count * TREAD;
17045
+ const aabb = [door.u - door.width / 2, door.v - depth, door.u + door.width / 2, door.v];
17046
+ const flight = builder.add({ id: `steps_${door.id}`, role: "stair", material: context.isTimber ? "wood_soft" : "concrete", aabb, y0: 0, y1: context.level(room.floor) }, { floor: 0, steps: count, door: door.id });
17047
+ builder.rest(flight, GROUND, "resting");
17048
+ if (deck) builder.rest(flight, deck, context.isTimber ? "nail" : "mortar");
17049
+ }
17050
+ }
17051
+ }
17052
+ function boxOfSteps(door, depth) {
17053
+ const direction = door.axis === "u" ? door.side === "front" ? -1 : 1 : door.side === "left" ? -1 : 1;
17054
+ if (door.axis === "u") return [door.u - door.width / 2, Math.min(door.v, door.v + direction * depth), door.u + door.width / 2, Math.max(door.v, door.v + direction * depth)];
17055
+ return [Math.min(door.u, door.u + direction * depth), door.v - door.width / 2, Math.max(door.u, door.u + direction * depth), door.v + door.width / 2];
17056
+ }
17057
+ function addDoorSteps(context) {
17058
+ if (context.elevation <= 0.1) return;
17059
+ const { builder } = context;
17060
+ const doors = context.source.doors.filter((d) => d.floor === 0 && d.exterior && d.model !== "steel" && d.type !== "steps" && d.type !== "zipper");
17061
+ for (const door of doors) {
17062
+ const count = stepCount(context.elevation);
17063
+ const depth = Math.min(TREAD * count, 0.9);
17064
+ const flight = builder.add({ id: `steps_${door.id}`, role: "stair", material: context.isTimber ? "wood_soft" : "concrete", aabb: boxOfSteps(door, depth), y0: 0, y1: context.level(0) }, { floor: 0, steps: count, door: door.id });
17065
+ builder.rest(flight, GROUND, "resting");
17066
+ const base = context.base0;
17067
+ if (base && base !== GROUND) builder.rest(flight, base, context.isTimber ? "nail" : "mortar");
17068
+ }
17069
+ }
17070
+ function addExteriorSteps(context) {
17071
+ addPorchSteps(context);
17072
+ addDoorSteps(context);
17073
+ }
17074
+ var TANK_FILL = 0.08;
17075
+ var squareAround = (u, v, side) => [u - side / 2, v - side / 2, u + side / 2, v + side / 2];
17076
+ function addTank(context, tank, floor, y0) {
17077
+ return context.builder.add({ id: "water_tank", role: "none", material: tank.material ?? "plastic", aabb: tank.aabb, y0, y1: y0 + tank.h }, { floor, fill: TANK_FILL, liters: tank.capacity });
17078
+ }
17079
+ function ensureCeilingSlab(context, y) {
17080
+ const { builder, construction, system } = context;
17081
+ const existing = builder.find("slab_ceiling");
17082
+ if (existing) return existing;
17083
+ const top = context.floorCount - 1;
17084
+ const box22 = context.topBox;
17085
+ const carriers = builder.parts.filter(
17086
+ (part) => (part.role === "beam" && part.id.startsWith(`plate_${top}_`) || part.role === "column" && part.floor === top || part.role === "wall" && part.floor === top && !part.id.startsWith("basement")) && touches(box22, part.aabb, 0.1)
17087
+ );
17088
+ const slab = builder.add({ id: "slab_ceiling", role: "slab", material: construction.frame.slabs, aabb: box22, y0: y - 0.12, y1: y }, { floor: top, footprint: [box22] });
17089
+ for (const carrier of carriers) builder.rest(slab, carrier, system.joints.slab);
17090
+ return slab;
17091
+ }
17092
+ function addRaisedPlatform(context, tank) {
17093
+ const { builder, construction } = context;
17094
+ const platform = tank.platform;
17095
+ const top = context.floorCount - 1;
17096
+ const ceiling = ensureCeilingSlab(context, platform.baseY);
17097
+ const pillars = platform.pillars.map((q, k) => {
17098
+ const pillar = builder.add({ id: `tank_column_${k}`, role: "column", material: construction.frame.pillars, aabb: squareAround(q.u, q.v, q.side), y0: platform.baseY, y1: platform.topY - platform.thickness }, { floor: top });
17099
+ builder.rest(pillar, ceiling, "rebar");
17100
+ return pillar;
17101
+ });
17102
+ const slab = builder.add({ id: "tank_slab", role: "slab", material: construction.frame.slabs, aabb: platform.aabb, y0: platform.topY - platform.thickness, y1: platform.topY }, { floor: top });
17103
+ for (const pillar of pillars) builder.rest(slab, pillar, "rebar");
17104
+ builder.rest(addTank(context, tank, top, tank.y), slab, "resting");
17105
+ }
17106
+ function addAtticTank(context, tank) {
17107
+ const { builder } = context;
17108
+ const top = context.floorCount - 1;
17109
+ if (!context.isTimber) {
17110
+ const ceiling = ensureCeilingSlab(context, tank.y);
17111
+ builder.rest(addTank(context, tank, top, tank.y), ceiling, "resting");
17112
+ return;
17113
+ }
17114
+ const supports = context.topSlab ? [context.topSlab] : context.ceilingJoists ?? builder.parts.filter((part) => part.role === "beam" && part.id.startsWith(`plate_${top}_`));
17115
+ const y0 = supports[0] ? supports[0].y1 : tank.y - 0.03;
17116
+ const floor = builder.add({ id: "attic_floor", role: "slab", material: "plywood", aabb: context.topBox, y0, y1: y0 + 0.03 }, { floor: top });
17117
+ for (const support of supports) if (touches(floor.aabb, support.aabb, 0.1)) builder.rest(floor, support, "nail");
17118
+ builder.rest(addTank(context, tank, top, tank.y), floor, "resting");
17119
+ }
17120
+ function addSlabTank(context, tank) {
17121
+ const { builder } = context;
17122
+ const supports = builder.parts.filter((part2) => part2.role === "slab" && Math.abs(part2.y1 - tank.y) < 0.3 && touches(part2.aabb, tank.aabb, 0.1));
17123
+ const carrier = supports.sort((a, b) => Math.abs(a.y1 - tank.y) - Math.abs(b.y1 - tank.y))[0] ?? context.topSlab;
17124
+ const part = addTank(context, tank, tank.floor, tank.y);
17125
+ builder.rest(part, carrier ?? GROUND, "resting");
17126
+ }
17127
+ function addTrestleTank(context, tank) {
17128
+ const { builder } = context;
17129
+ const stand = tank.stand;
17130
+ const columns = stand.pillars.map((q, k) => {
17131
+ const column = builder.add({ id: `trestle_column_${k}`, role: "column", material: stand.material, aabb: squareAround(q.u, q.v, q.side), y0: 0, y1: stand.height }, { floor: 0 });
17132
+ builder.rest(column, GROUND, stand.baseJoint ?? "resting");
17133
+ return column;
17134
+ });
17135
+ const [u0, v0, u1, v1] = tank.aabb;
17136
+ const beams = [[u0 - 0.1, v0 - 0.1, u1 + 0.1, v0 + 0.05], [u0 - 0.1, v1 - 0.05, u1 + 0.1, v1 + 0.1]].map((aabb, k) => {
17137
+ const beam = builder.add({ id: `trestle_beam_${k}`, role: "beam", material: stand.material, aabb, y0: stand.height - 0.12, y1: stand.height }, { floor: 0 });
17138
+ for (const column of columns.filter((c) => touches(beam.aabb, c.aabb, 0.15))) builder.rest(beam, column, stand.joint ?? "nail");
17139
+ return beam;
17140
+ });
17141
+ const part = addTank(context, tank, 0, stand.height);
17142
+ for (const beam of beams) builder.rest(part, beam, stand.tie ?? "resting");
17143
+ }
17144
+ function addWaterTank(context) {
17145
+ const tank = context.tank;
17146
+ if (!tank) return;
17147
+ switch (tank.place) {
17148
+ case "slab":
17149
+ return addSlabTank(context, tank);
17150
+ case "raised_slab":
17151
+ return addRaisedPlatform(context, tank);
17152
+ case "attic":
17153
+ return addAtticTank(context, tank);
17154
+ case "trestle":
17155
+ return addTrestleTank(context, tank);
17156
+ }
17157
+ }
17158
+ function addUpperFloor(context, floor) {
17159
+ if (floor >= context.floorCount - 1 || context.isUnit) return;
17160
+ const next = floor + 1;
17161
+ const rects = context.rectsOf(next, ["balcony", "porch", "void"]);
17162
+ const box22 = rects.length ? unionOf(rects) : context.houseBox;
17163
+ const carriers = context.carriers.get(floor);
17164
+ const { system, construction, builder } = context;
17165
+ const thickness = system.slabThickness;
17166
+ if (context.isTimber) {
17167
+ const joists = addJoists(context, { floor: next, box: box22, thickness, carriers, material: construction.frame.timber, joint: system.joints.beam });
17168
+ addDeck(context, `slab_${next}`, construction.frame.slabs, { floor: next, box: box22, thickness, joint: system.joints.slab }, joists);
17169
+ return;
17170
+ }
17171
+ const slab = builder.add({ id: `slab_${next}`, role: "slab", material: construction.frame.slabs, aabb: box22, y0: context.level(next) - thickness, y1: context.level(next) }, { floor: next, footprint: rects.length ? rects : [context.houseBox] });
17172
+ const below = carriers.filter((carrier) => carrier.y0 <= slab.y0 + 0.05);
17173
+ const touching = below.filter((carrier) => touches(slab.aabb, carrier.aabb, 0.2));
17174
+ for (const carrier of touching.length ? touching : below) builder.rest(slab, carrier, system.joints.slab);
17175
+ }
17176
+ function wallCeiling(context, wall) {
17177
+ const rooms = wall.sides.map((id) => context.source.rooms.find((room) => room.id === id && room.floor === wall.floor)).filter((room) => room !== void 0);
17178
+ return Math.max(context.ceilingOf(wall.floor), ...rooms.map((room) => room.ceiling || 0));
17179
+ }
17180
+ function wallTop(context, wall, floor) {
17181
+ const y0 = context.level(floor);
17182
+ if (wall.type === "railing") return y0 + guardHeightOf(wall);
17183
+ const ceiling = wallCeiling(context, wall);
17184
+ const height = wall.structureHeight || wall.h || context.ceilingOf(floor);
17185
+ if (wall.exterior && floor < context.floorCount - 1 && !context.isUnit && ceiling > context.ceilingOf(floor) + 0.01) return y0 + ceiling;
17186
+ if (floor === context.floorCount - 1 || context.isUnit) return y0 + Math.max(height, ceiling);
17187
+ const top = context.topOf(floor);
17188
+ return Math.max(y0 + height, y0 + ceiling > top ? top : y0 + ceiling);
17189
+ }
17190
+ function railingBase(context, wall, floor) {
17191
+ const open = wall.sides.map((id) => context.source.rooms.find((room) => room.id === id && room.floor === floor)).find((room) => room && (room.type === "balcony" || room.type === "porch"));
17192
+ if (!open) return context.baseOf(floor);
17193
+ return open.type === "porch" ? `porch_floor_${open.id}` : `slab_${floor}_${open.id}`;
17194
+ }
17195
+ var assemblyOf = (context) => context.construction.walls.assembly ?? (context.system.walls || void 0);
17196
+ function addWallPart(context, wall, floor) {
17197
+ const { system, builder } = context;
17198
+ const isRailing = wall.type === "railing";
17199
+ const y0 = context.level(floor);
17200
+ const top = wallTop(context, wall, floor);
17201
+ const y1 = isRailing ? top : wall.exterior && !wall.gable ? top - system.plateThickness : top;
17202
+ const framed = system.studs !== void 0 && !isRailing;
17203
+ const part = builder.add(
17204
+ { id: wall.id, role: wall.role, material: wall.material, aabb: wall.box, y0, y1: Math.max(y1, y0 + 0.2) },
17205
+ { floor, fill: context.isTimber && wall.role === "wall" ? system.wallFill : 1, ...framed ? { studs: system.studs } : {}, ...isRailing ? {} : { assembly: assemblyOf(context) } }
17206
+ );
17207
+ context.wallParts.set(wall.id, part);
17208
+ const base = isRailing ? railingBase(context, wall, floor) : context.baseOf(floor);
17209
+ const joint = isRailing ? "nail" : system.joints.wall;
17210
+ if (base === GROUND || floor === 0 && !base) builder.rest(part, GROUND, joint);
17211
+ else if (base) builder.rest(part, base, joint);
17212
+ }
17213
+ function addWalls(context, floor) {
17214
+ for (const wall of context.walls.filter((w) => w.floor === floor)) addWallPart(context, wall, floor);
17215
+ }
17216
+ function buildFloors(context) {
17217
+ for (let floor = 0; floor < context.floorCount; floor++) {
17218
+ addWalls(context, floor);
17219
+ addPlatesAndPillars(context, floor);
17220
+ addPorches(context, floor);
17221
+ addUpperFloor(context, floor);
17222
+ }
17223
+ }
17224
+ function buildStructure(source, options = {}) {
17225
+ const refined = options.wallDetail !== "coarse";
17226
+ const built = options.construction ? { construction: options.construction, extras: options.extras ?? {} } : resolveConstruction(source);
17227
+ const construction = built.construction;
17228
+ const extras = options.extras ?? built.extras ?? {};
17229
+ if (construction.system === "canvas") return buildCanvasStructure(construction, extras);
17230
+ const tank = options.tank !== void 0 ? options.tank : source.waterTank ?? null;
17231
+ const context = createContext(source, construction, extras, tank, { porchRoofOnPostsOnly: options.porchRoofOnPostsOnly ?? refined, rafterRoof: options.rafterRoof ?? refined });
17232
+ addFoundation(context);
17233
+ buildFloors(context);
17234
+ addBalconies(context);
17235
+ addPorchRoofs(context);
17236
+ addExteriorSteps(context);
17237
+ addStairs(context);
17238
+ if (options.roof !== false) addRoof(context);
17239
+ if (refined) addDoorLintels(context);
17240
+ addWaterTank(context);
17241
+ addChimney(context);
17242
+ addWell(context);
17243
+ const levels = source.floors.map((_, floor) => context.level(floor));
17244
+ const framed = construction.system === "masonry" && (context.isUnit || source.style === "commercial" || context.floorCount >= 3);
17245
+ return { parts: context.builder.parts, supports: context.builder.supports, levels, elevation: context.elevation, roof: context.roof, construction, framed, openings: findOpenings(context) };
17246
+ }
16244
17247
  var entry = (itemId, min, max, group, pickable, weight) => ({ itemId, min, max, group, pickable, weight });
16245
17248
  var FRIDGE = [
16246
17249
  entry("water_bottle", 1, 3, "water", true, 3),
@@ -18634,7 +19637,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
18634
19637
  assembleInteractive(gs);
18635
19638
  linkDoorLeaves();
18636
19639
  mailboxInteractive();
18637
- const root = new THREE.Group(), house = new THREE.Group();
19640
+ const root = markKitOrigin(new THREE.Group(), { owner: "game-floorplan-three", kind: "structure", id: r.template }), house = new THREE.Group();
18638
19641
  root.name = "house3d";
18639
19642
  root.add(house);
18640
19643
  shared.mirrors.house = house;
@@ -20373,10 +21376,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20373
21376
  }
20374
21377
 
20375
21378
  // ../game-floorplan-residential/dist/index.js
20376
- var __defProp2 = Object.defineProperty;
21379
+ var __defProp3 = Object.defineProperty;
20377
21380
  var __export2 = (target, all) => {
20378
21381
  for (var name in all)
20379
- __defProp2(target, name, { get: all[name], enumerable: true });
21382
+ __defProp3(target, name, { get: all[name], enumerable: true });
20380
21383
  };
20381
21384
  var residentialItems = [
20382
21385
  { type: "sofa_3seat", category: "sofa", w: 2.1, d: 0.9, h: 0.85, color: { role: "fabric_main" } },
@@ -21577,7 +22580,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21577
22580
  }
21578
22581
  function recordSpec(THREE, name, root, run, opts) {
21579
22582
  const o = { chairs: opts.chairs };
21580
- const materialOf = { ...BASE_MATERIAL, ...opts.materials };
22583
+ const materialOf2 = { ...BASE_MATERIAL, ...opts.materials };
21581
22584
  const drawn = [];
21582
22585
  const counters = /* @__PURE__ */ new Map();
21583
22586
  const q = new THREE.Quaternion();
@@ -21633,7 +22636,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21633
22636
  if (!drawn.length) throw new Error(`game-floorplan-three: the builder of "${name}" drew no box or cylinder`);
21634
22637
  const parts = drawn.map((p) => {
21635
22638
  const thin = Math.min(...p.shape.kind === "box" ? p.shape.size : p.shape.kind === "cylinder" ? [p.shape.radius * 2, p.shape.height] : [p.shape.radius * 2]);
21636
- let material = materialOf[p.key] ?? "plastic";
22639
+ let material = materialOf2[p.key] ?? "plastic";
21637
22640
  if (p.key === "wood" && material === "wood_hard" && thin < WOOD_THIN) material = "wood_soft";
21638
22641
  const part = { id: p.id, material, shape: p.shape, position: p.position };
21639
22642
  if (p.rotation) part.rotation = p.rotation;
@@ -21672,7 +22675,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21672
22675
  const spec = { name, parts, joints, tags: ["furniture"] };
21673
22676
  const colors = new Map(drawn.map((p) => [p.id, p.color]));
21674
22677
  const build = (T3) => {
21675
- const group = new T3.Group();
22678
+ const group = markKitOrigin(new T3.Group(), { owner: "game-floorplan-three", kind: "furniture", id: name });
21676
22679
  group.name = name;
21677
22680
  const mats = /* @__PURE__ */ new Map();
21678
22681
  for (const p of parts) {
@@ -21759,6 +22762,66 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21759
22762
  K.PARTS.length = before;
21760
22763
  return { spec: made.spec, build: made.build, spout: [water.pos[0] - root.x, water.pos[1], water.pos[2] - root.z] };
21761
22764
  }
22765
+
22766
+ // src/wall.ts
22767
+ var PLASTER = 15262420;
22768
+ var WALL_ID = "wall_0_0";
22769
+ function wallPlan(options) {
22770
+ const { w, h, thickness = 0.15, finish = "plaster", color: color2 = PLASTER } = options;
22771
+ const facade = { finish, roof: "none", floors: 1 };
22772
+ const wall = { id: WALL_ID, floor: 0, axis: "u", u0: -w / 2, v0: 0, u1: w / 2, v1: 0, thickness, sides: ["outside", "outside"], exterior: true, type: "wall", h };
22773
+ const plan = {
22774
+ template: "wall",
22775
+ name: "wall",
22776
+ style: "mid",
22777
+ state: "normal",
22778
+ w,
22779
+ d: thickness,
22780
+ floors: [{ index: 0, y0: 0, ceiling: h }],
22781
+ rooms: [],
22782
+ walls: [wall],
22783
+ doors: [],
22784
+ windows: [],
22785
+ items: [],
22786
+ stateItems: [],
22787
+ openPairs: [],
22788
+ partyWalls: [],
22789
+ missing: [],
22790
+ facade,
22791
+ colors: { facade: color2, wall: color2 },
22792
+ lot: null,
22793
+ building: null,
22794
+ waterTank: null,
22795
+ outbuildings: []
22796
+ };
22797
+ plan.construction = masonryConstruction({ ...plan, facade: { ...facade, roof: "colonial" } });
22798
+ plan.structure = buildStructure(plan, { wallDetail: "coarse", construction: plan.construction, roof: false });
22799
+ return plan;
22800
+ }
22801
+ function draw(THREE, plan, options) {
22802
+ return create3(THREE, { ...options, fleet: null }).build(plan, { quality: "game", terrain: false, noPerimeter: true, pieces: "meshes" });
22803
+ }
22804
+ function label(house) {
22805
+ for (const [id, items] of Object.entries(house.destruction.ranges())) {
22806
+ for (const item of items) {
22807
+ const object2 = item.isObject3D ? item : item.mesh;
22808
+ object2.userData.destructionPart = id;
22809
+ }
22810
+ }
22811
+ return house.group;
22812
+ }
22813
+ function wallPiece(THREE, options) {
22814
+ const plan = wallPlan(options);
22815
+ const houseOptions = options.house ?? {};
22816
+ const probe = draw(THREE, plan, { ...houseOptions, destruction: void 0, destructionRenderer: void 0 });
22817
+ const spec = probe.structure.spec({ wallDetail: "coarse" });
22818
+ probe.dispose();
22819
+ return {
22820
+ plan,
22821
+ spec,
22822
+ build: (T2) => label(draw(T2, plan, houseOptions))
22823
+ };
22824
+ }
21762
22825
  return __toCommonJS(index_exports);
21763
22826
  })();
21764
22827
  //# sourceMappingURL=index.global.js.map