@adatechnology/game-floorplan-three 0.3.0 → 0.4.0

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
  }
@@ -14705,13 +14707,14 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
14705
14707
  const crackLen = clamp2(0.03 + ratio * 0.2, 0.03, 0.8);
14706
14708
  let exitPoint;
14707
14709
  if (penetrated) {
14708
- exitPoint = vec.madd(entry2, d, Math.min(pathLen, effM + layersThickness(p.layers, face) / slant + layersThickness(p.layers, -face) / slant));
14710
+ const exitDist = p.spec.contains || p.spec.fill !== void 0 && p.spec.fill < 0.5 || p.thickness < pathLen * 0.5 ? pathLen : Math.min(pathLen, effM + layersThickness(p.layers, face) / slant + layersThickness(p.layers, -face) / slant);
14711
+ exitPoint = vec.madd(entry2, d, exitDist);
14709
14712
  const hole = addHole(o, p, entry2, n, caliber / 1e3 * (mat.ductility >= 0.3 ? 1 : 1.5), true, exitPoint, src);
14710
14713
  if (p.layers.length > 0) spallOuter(o, p, exitPoint, -face, vec.scale(n, -1), Math.max(0.03, caliber / 1e3 * 4), src);
14711
14714
  addBreach(o, p, entry2, Math.PI * (hole.diameter / 2) * (hole.diameter / 2), E, d, src);
14712
14715
  if (mat.ductility >= 0.5 && p.thickness <= THIN_SHEET_DISHING) addDent(o, p, entry2, n, Math.max(caliber * 3 / 1e3, 0.012), clamp2(E * 2e-5, 1e-3, 0.03), src);
14713
14716
  if (brittle && mat.fracture === "shatter") addCrack(o, p, entry2, n, crackLen, src);
14714
- if (p.spec.contains && (p.st.liters ?? 0) > 0) {
14717
+ if (p.spec.contains && (p.st.liters ?? 0) > 1e-4) {
14715
14718
  emit({
14716
14719
  ...base(o, src),
14717
14720
  type: "leak",
@@ -16156,6 +16159,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
16156
16159
  };
16157
16160
 
16158
16161
  // ../game-floorplan-structure/dist/index.js
16162
+ var __defProp2 = Object.defineProperty;
16163
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
16164
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
16159
16165
  var uniform = (joint) => ({ wall: joint, slab: joint, beam: joint, column: joint, truss: joint });
16160
16166
  var CONSTRUCTION_SYSTEMS = /* @__PURE__ */ new Map([
16161
16167
  ["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 }],
@@ -16164,10 +16170,59 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
16164
16170
  ["improvised", { walls: "sheet_metal_shack", joints: uniform("nail"), slabThickness: 0.2, plateThickness: 0.1, timber: true, wallRole: "partition", wallFill: 1 }],
16165
16171
  ["canvas", { walls: "", joints: uniform("tie"), slabThickness: 0, plateThickness: 0, timber: false, wallRole: "partition", wallFill: 1 }]
16166
16172
  ]);
16173
+ var ROOF_COVERINGS = /* @__PURE__ */ new Map([
16174
+ ["colonial", { material: "ceramic", joint: "resting" }],
16175
+ ["fiber_cement", { material: "concrete", joint: "bolt" }],
16176
+ ["slab", { material: "concrete", joint: "rebar" }],
16177
+ ["asphalt", { material: "plastic", joint: "nail" }],
16178
+ ["zinc", { material: "steel", joint: "nail" }],
16179
+ ["wood", { material: "wood_soft", joint: "nail" }],
16180
+ ["canvas", { material: "fabric", joint: "tie" }],
16181
+ ["thatch", { material: "foliage", joint: "tie" }]
16182
+ ]);
16183
+ var WALL_THICKNESS = 0.15;
16167
16184
  var round2 = (value) => Math.round(value * 100) / 100;
16168
16185
  function touches(a, b, tolerance) {
16169
16186
  return a[0] < b[2] + tolerance && a[2] > b[0] - tolerance && a[1] < b[3] + tolerance && a[3] > b[1] - tolerance;
16170
16187
  }
16188
+ function distanceBetween(a, b) {
16189
+ 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));
16190
+ }
16191
+ function unionOf(rects) {
16192
+ const out = [1e9, 1e9, -1e9, -1e9];
16193
+ for (const rect of rects) {
16194
+ out[0] = Math.min(out[0], rect[0]);
16195
+ out[1] = Math.min(out[1], rect[1]);
16196
+ out[2] = Math.max(out[2], rect[2]);
16197
+ out[3] = Math.max(out[3], rect[3]);
16198
+ }
16199
+ return out;
16200
+ }
16201
+ function wallBox(wall) {
16202
+ const half = (wall.thickness ?? WALL_THICKNESS) / 2;
16203
+ 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];
16204
+ }
16205
+ var SLAB_COVERING = "slab";
16206
+ var PLASTERED_FINISHES = ["plaster", "partial_plaster"];
16207
+ function masonryConstruction(source) {
16208
+ const unit = source.floorNumber !== void 0;
16209
+ const covering = unit ? SLAB_COVERING : source.facade.roof ?? "colonial";
16210
+ const finish = source.facade.finish ?? "roughcast";
16211
+ const budget = source.style === "budget";
16212
+ const flat = unit || covering === SLAB_COVERING;
16213
+ return {
16214
+ system: "masonry",
16215
+ walls: { material: "brick", cladding: PLASTERED_FINISHES.includes(finish) ? "plaster" : null, finish, thickness: WALL_THICKNESS, ...finish === "exposed_block" ? { assembly: "concrete_block_wall" } : {} },
16216
+ floor: { material: budget ? "concrete" : "ceramic", finish: budget ? "burnt_cement" : "ceramic_tile" },
16217
+ roof: { material: ROOF_COVERINGS.get(covering)?.material ?? "ceramic", covering, shape: flat ? "slab" : "gable", pitch: flat ? 0 : covering === "colonial" ? 30 : 15, timber: "wood_soft" },
16218
+ frame: { material: "concrete", pillars: "concrete", beams: "concrete", slabs: "concrete", timber: "wood_soft", rebar: "steel" },
16219
+ foundation: { type: unit ? "building" : "strip_footing", material: "concrete", elevation: 0 }
16220
+ };
16221
+ }
16222
+ function resolveConstruction(source) {
16223
+ const extras = source.structureExtras ?? {};
16224
+ return { construction: source.construction ?? masonryConstruction(source), extras };
16225
+ }
16171
16226
  var en_default = {
16172
16227
  ui: {
16173
16228
  structure_layer: "Structure",
@@ -16238,8 +16293,950 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
16238
16293
  };
16239
16294
  var DEFAULT_DENSITY = 500;
16240
16295
  var densityOf = (material) => DENSITY[material] ?? DEFAULT_DENSITY;
16296
+ var StructureBuilder = class {
16297
+ constructor() {
16298
+ __publicField(this, "parts", []);
16299
+ __publicField(this, "supports", []);
16300
+ __publicField(this, "ids", /* @__PURE__ */ new Set());
16301
+ }
16302
+ add(draft, options = {}) {
16303
+ const { id, role, material, aabb, y0, y1 } = draft;
16304
+ if (this.ids.has(id)) throw new Error(`Part repeated in the structure: ${id}`);
16305
+ this.ids.add(id);
16306
+ const { floor, fill: fill2, ...extras } = options;
16307
+ const volume = Math.max(0, (aabb[2] - aabb[0]) * (aabb[3] - aabb[1]) * (y1 - y0)) * (fill2 ?? 1);
16308
+ const part = {
16309
+ id,
16310
+ role,
16311
+ material,
16312
+ aabb: aabb.map(round2),
16313
+ y0: round2(y0),
16314
+ y1: round2(y1),
16315
+ floor: floor ?? 0,
16316
+ mass: Math.max(1, Math.round(volume * densityOf(material)))
16317
+ };
16318
+ for (const [key, value] of Object.entries(extras)) if (value !== void 0) part[key] = value;
16319
+ this.parts.push(part);
16320
+ return part;
16321
+ }
16322
+ rest(part, on, joint) {
16323
+ const id = typeof part === "string" ? part : part.id;
16324
+ const base = typeof on === "string" ? on : on.id;
16325
+ this.supports.push({ part: id, on: base === GROUND ? GROUND : base, joint });
16326
+ }
16327
+ has(id) {
16328
+ return this.ids.has(id);
16329
+ }
16330
+ find(id) {
16331
+ return this.parts.find((part) => part.id === id);
16332
+ }
16333
+ };
16334
+ function hashLegacyKey(key) {
16335
+ let hash = 1779033703 ^ key.length;
16336
+ for (let i = 0; i < key.length; i++) {
16337
+ hash = Math.imul(hash ^ key.charCodeAt(i), 3432918353);
16338
+ hash = hash << 13 | hash >>> 19;
16339
+ }
16340
+ hash = Math.imul(hash ^ hash >>> 16, 2246822507);
16341
+ hash = Math.imul(hash ^ hash >>> 13, 3266489909);
16342
+ return (hash ^ hash >>> 16) >>> 0;
16343
+ }
16241
16344
  var OPEN_ROOM_TYPES2 = ["balcony", "terrace", "porch"];
16345
+ var GUARD_HEIGHT = 1.1;
16346
+ function createContext(source, construction, extras, tank, options) {
16347
+ const system = CONSTRUCTION_SYSTEMS.get(construction.system);
16348
+ if (!system) throw new Error(`Unknown construction system "${construction.system}"`);
16349
+ const floorCount2 = source.floors.length;
16350
+ const isUnit = source.floorNumber !== void 0;
16351
+ const elevation = construction.foundation.elevation || 0;
16352
+ const level = (floor) => round2(elevation + (source.floors[floor]?.y0 ?? 0));
16353
+ const ceilingOf = (floor) => source.floors[floor]?.ceiling ?? 0;
16354
+ const topOf = (floor) => floor < floorCount2 - 1 && !isUnit ? level(floor + 1) - system.slabThickness : level(floor) + ceilingOf(floor);
16355
+ const rectsOf2 = (floor, exclude = []) => source.rooms.filter((room) => room.floor === floor && !exclude.includes(room.type)).flatMap((room) => room.parts);
16356
+ const builder = new StructureBuilder();
16357
+ const context = {
16358
+ source,
16359
+ construction,
16360
+ system,
16361
+ extras,
16362
+ tank,
16363
+ builder,
16364
+ porchRoofOnPostsOnly: options.porchRoofOnPostsOnly,
16365
+ rafterRoof: options.rafterRoof,
16366
+ floorCount: floorCount2,
16367
+ isUnit,
16368
+ elevation,
16369
+ isTimber: system.timber,
16370
+ walls: [],
16371
+ wallParts: /* @__PURE__ */ new Map(),
16372
+ carriers: /* @__PURE__ */ new Map(),
16373
+ plates: /* @__PURE__ */ new Map(),
16374
+ pillars: /* @__PURE__ */ new Map(),
16375
+ porches: /* @__PURE__ */ new Map(),
16376
+ base0: GROUND,
16377
+ houseBox: [0, 0, 0, 0],
16378
+ topBox: [0, 0, 0, 0],
16379
+ hasBasement: false,
16380
+ roof: { shape: "slab", covering: "", material: "", pitch: 0, bearingFloor: 0, panes: [], trusses: 0 },
16381
+ level,
16382
+ ceilingOf,
16383
+ topOf,
16384
+ rectsOf: rectsOf2,
16385
+ baseOf: (floor) => floor === 0 ? context.base0 : builder.find(`slab_${floor}`)
16386
+ };
16387
+ const closed = rectsOf2(0, ["balcony", "porch"]);
16388
+ context.houseBox = unionOf(closed.length ? closed : rectsOf2(0));
16389
+ context.walls = classifyWalls(context);
16390
+ return context;
16391
+ }
16392
+ var isBearingOnTop = (wall, walls) => {
16393
+ const along = (w, pick2) => w.axis === "u" ? pick2(w.u0, w.u1) : pick2(w.v0, w.v1);
16394
+ const across = (w) => w.axis === "u" ? w.v0 : w.u0;
16395
+ return walls.some(
16396
+ (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
16397
+ );
16398
+ };
16399
+ function roleOf(context, wall, walls, porchIds) {
16400
+ if (wall.type === "railing") return "partition";
16401
+ const { wallRole } = context.system;
16402
+ if (wallRole === "bearing") return "wall";
16403
+ if (wallRole === "partition") return "partition";
16404
+ const bearingFloor = context.construction.roof.bearingFloor ?? context.floorCount - 1;
16405
+ if (wall.floor > bearingFloor) return "partition";
16406
+ if (wall.exterior || wall.sides.some((id) => porchIds.has(id))) return "wall";
16407
+ return wall.floor < context.floorCount - 1 && isBearingOnTop(wall, walls) ? "wall" : "partition";
16408
+ }
16409
+ function materialOf(context, wall) {
16410
+ const { walls, guard } = context.construction;
16411
+ if (wall.type === "railing") return guard ?? (context.isTimber ? "wood_soft" : "steel");
16412
+ if (walls.mixed?.length) return walls.mixed[hashLegacyKey(wall.legacyKey) % walls.mixed.length];
16413
+ return wall.exterior ? walls.material : walls.interior ?? walls.material;
16414
+ }
16415
+ function classifyWalls(context) {
16416
+ const countByFloor = /* @__PURE__ */ new Map();
16417
+ const all = context.source.walls.map((wall) => {
16418
+ const index = countByFloor.get(wall.floor) ?? 0;
16419
+ countByFloor.set(wall.floor, index + 1);
16420
+ return { ...wall, id: `wall_${wall.floor}_${index}`, legacyKey: `w${wall.floor}_${index}`, role: "wall", material: "", box: wallBox(wall) };
16421
+ });
16422
+ const built = all.filter((wall) => wall.type === "wall" || wall.type === "railing");
16423
+ const porchIds = new Set(context.source.rooms.filter((room) => room.type === "porch").map((room) => room.id));
16424
+ for (const wall of built) {
16425
+ wall.role = roleOf(context, wall, built, porchIds);
16426
+ wall.material = materialOf(context, wall);
16427
+ }
16428
+ return built;
16429
+ }
16430
+ var guardHeightOf = (wall) => wall.h || GUARD_HEIGHT;
16431
+ function addBalconies(context) {
16432
+ const { builder, construction, system } = context;
16433
+ for (const room of context.source.rooms.filter((r) => r.type === "balcony" && r.floor >= 0)) {
16434
+ const floor = room.floor;
16435
+ const box22 = unionOf(room.parts);
16436
+ const base = floor === 0 ? void 0 : context.baseOf(floor);
16437
+ 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 });
16438
+ if (base) {
16439
+ builder.rest(deck, base, system.joints.slab);
16440
+ continue;
16441
+ }
16442
+ 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 });
16443
+ builder.rest(pillar, GROUND, "rebar");
16444
+ builder.rest(deck, pillar, "rebar");
16445
+ }
16446
+ }
16447
+ var GROUNDSHEET = 0.02;
16448
+ function buildCanvasStructure(construction, extras) {
16449
+ const builder = new StructureBuilder();
16450
+ const panes = [];
16451
+ for (const tent of extras.tents ?? []) {
16452
+ const [u0, v0, u1, v1] = tent.aabb;
16453
+ const centreU = (u0 + u1) / 2;
16454
+ const centreV = (v0 + v1) / 2;
16455
+ const sheet = builder.add({ id: `groundsheet_${tent.id}`, role: "slab", material: "plastic", aabb: tent.aabb, y0: 0, y1: GROUNDSHEET }, { floor: 0, tent: tent.id });
16456
+ builder.rest(sheet, GROUND, "resting");
16457
+ 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 });
16458
+ panes.push(canvas.id);
16459
+ 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]];
16460
+ for (const [aabb, k] of poles) {
16461
+ 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 });
16462
+ builder.rest(pole, GROUND, "resting");
16463
+ builder.rest(canvas, pole, "tie");
16464
+ }
16465
+ 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]];
16466
+ corners.forEach(([u, v], k) => {
16467
+ 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 });
16468
+ builder.rest(peg, GROUND, "nail");
16469
+ builder.rest(canvas, peg, "tie");
16470
+ });
16471
+ }
16472
+ const roof = { shape: "tent", covering: "canvas", material: "fabric", pitch: 0, bearingFloor: 0, panes, trusses: 0 };
16473
+ return { parts: builder.parts, supports: builder.supports, levels: [0], elevation: 0, roof, construction, framed: false, openings: [] };
16474
+ }
16475
+ var CHIMNEY_CLEARANCE_ABOVE_RIDGE = 0.6;
16476
+ function addChimney(context) {
16477
+ const chimney = context.extras.chimney;
16478
+ if (!chimney) return;
16479
+ const { builder } = context;
16480
+ const top = context.floorCount - 1;
16481
+ const ridge = context.roof.ridge || context.level(top) + context.ceilingOf(top);
16482
+ const height = Math.max(ridge, chimney.top || 0) + CHIMNEY_CLEARANCE_ABOVE_RIDGE;
16483
+ 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 });
16484
+ if (chimney.flue) builder.rest(part, context.base0 ?? GROUND, "resting");
16485
+ else builder.rest(part, GROUND, "mortar");
16486
+ const walls = builder.parts.filter((wall) => wall.role === "wall" && wall.floor === 0 && !wall.id.startsWith("basement") && touches(wall.aabb, part.aabb, 0.1));
16487
+ for (const wall of walls) builder.rest(part, wall, "bolt");
16488
+ chimney.height = round2(height);
16489
+ }
16490
+ function addWell(context) {
16491
+ const well = context.extras.well;
16492
+ if (!well) return;
16493
+ 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 });
16494
+ context.builder.rest(part, GROUND, "mortar");
16495
+ }
16496
+ var JOIST_PITCH = 0.6;
16497
+ function addJoists(context, options) {
16498
+ const { floor, box: box22, thickness, carriers, material, joint, suffix = "" } = options;
16499
+ const length = box22[2] - box22[0];
16500
+ const depth = box22[3] - box22[1];
16501
+ const alongU = length >= depth;
16502
+ const span = alongU ? length : depth;
16503
+ const count = Math.max(2, Math.round(span / JOIST_PITCH) + 1);
16504
+ const top = context.level(floor) - thickness * 0.15;
16505
+ const out = [];
16506
+ for (let k = 0; k < count; k++) {
16507
+ const centre = (alongU ? box22[0] : box22[1]) + 0.05 + k * (span - 0.1) / (count - 1);
16508
+ const aabb = alongU ? [centre - 0.025, box22[1], centre + 0.025, box22[3]] : [box22[0], centre - 0.025, box22[2], centre + 0.025];
16509
+ 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 });
16510
+ out.push(joist);
16511
+ const below = carriers.filter((carrier) => carrier.y0 <= top + 0.05);
16512
+ const touching = below.filter((carrier) => touches(aabb, carrier.aabb, 0.2));
16513
+ const pool = below.length ? below : carriers;
16514
+ const chosen = touching.length ? touching : pool.slice().sort((a, b) => distanceBetween(aabb, a.aabb) - distanceBetween(aabb, b.aabb)).slice(0, 1);
16515
+ for (const carrier of chosen) context.builder.rest(joist, carrier, joint);
16516
+ }
16517
+ return out;
16518
+ }
16519
+ function addDeck(context, id, material, options, joists) {
16520
+ const { floor, box: box22, thickness, joint } = options;
16521
+ const deck = context.builder.add({ id, role: "slab", material, aabb: box22, y0: context.level(floor) - thickness * 0.15, y1: context.level(floor) }, { floor });
16522
+ for (const joist of joists) context.builder.rest(deck, joist, joint);
16523
+ return deck;
16524
+ }
16525
+ var raft = (context, thickness, material) => {
16526
+ const [u0, v0, u1, v1] = context.houseBox;
16527
+ 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 });
16528
+ context.builder.rest(part, GROUND, "resting");
16529
+ return part;
16530
+ };
16531
+ function addBuildingBase(context) {
16532
+ const { builder, construction, houseBox: box22 } = context;
16533
+ const corners = [[box22[0], box22[1]], [box22[2], box22[1]], [box22[0], box22[3]], [box22[2], box22[3]]];
16534
+ const top = context.level(0) - context.system.slabThickness;
16535
+ const pillars = corners.map(([u, v], k) => {
16536
+ 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 });
16537
+ builder.rest(pillar, GROUND, "rebar");
16538
+ return pillar;
16539
+ });
16540
+ 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] });
16541
+ for (const pillar of pillars) builder.rest(slab, pillar, "rebar");
16542
+ context.base0 = slab;
16543
+ }
16544
+ function addBasement(context) {
16545
+ const { builder, houseBox: box22, elevation } = context;
16546
+ const depth = context.construction.foundation.depth || 2.4;
16547
+ const floor = builder.add({ id: "basement_slab", role: "slab", material: "concrete", aabb: box22, y0: -depth - 0.15, y1: -depth }, { floor: -1 });
16548
+ builder.rest(floor, GROUND, "resting");
16549
+ const sides = [
16550
+ ["front", [box22[0], box22[1] - 0.1, box22[2], box22[1] + 0.1]],
16551
+ ["back", [box22[0], box22[3] - 0.1, box22[2], box22[3] + 0.1]],
16552
+ ["left", [box22[0] - 0.1, box22[1], box22[0] + 0.1, box22[3]]],
16553
+ ["right", [box22[2] - 0.1, box22[1], box22[2] + 0.1, box22[3]]]
16554
+ ];
16555
+ const carried = [];
16556
+ for (const [name, aabb] of sides) {
16557
+ const wall = builder.add({ id: `basement_wall_${name}`, role: "wall", material: "concrete", aabb, y0: -depth, y1: elevation }, { floor: -1 });
16558
+ builder.rest(wall, GROUND, "rebar");
16559
+ builder.rest(wall, floor, "rebar");
16560
+ carried.push(wall);
16561
+ }
16562
+ const count = Math.max(1, Math.floor((box22[2] - box22[0]) / 3));
16563
+ for (let k = 0; k < count; k++) {
16564
+ const u = box22[0] + (k + 0.5) * (box22[2] - box22[0]) / count;
16565
+ const v = (box22[1] + box22[3]) / 2;
16566
+ 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 });
16567
+ builder.rest(column, floor, "rebar");
16568
+ builder.rest(column, GROUND, "rebar");
16569
+ carried.push(column);
16570
+ }
16571
+ context.hasBasement = true;
16572
+ return carried;
16573
+ }
16574
+ function addPiers(context) {
16575
+ const { builder, houseBox: box22, elevation, construction } = context;
16576
+ const columns = Math.max(2, Math.ceil((box22[2] - box22[0]) / 2) + 1);
16577
+ const rows = Math.max(2, Math.ceil((box22[3] - box22[1]) / 2) + 1);
16578
+ const height = Math.max(0.1, elevation - context.system.slabThickness - 0.15);
16579
+ const piers = [];
16580
+ for (let j = 0; j < rows; j++) {
16581
+ for (let i = 0; i < columns; i++) {
16582
+ const u = box22[0] + i * (box22[2] - box22[0]) / (columns - 1);
16583
+ const v = box22[1] + j * (box22[3] - box22[1]) / (rows - 1);
16584
+ 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 });
16585
+ builder.rest(pier, GROUND, "resting");
16586
+ piers.push(pier);
16587
+ }
16588
+ }
16589
+ const alongU = box22[2] - box22[0] >= box22[3] - box22[1];
16590
+ const beams = [];
16591
+ const lines = alongU ? rows : columns;
16592
+ for (let line = 0; line < lines; line++) {
16593
+ const at2 = alongU ? box22[1] + line * (box22[3] - box22[1]) / (rows - 1) : box22[0] + line * (box22[2] - box22[0]) / (columns - 1);
16594
+ const aabb = alongU ? [box22[0], at2 - 0.07, box22[2], at2 + 0.07] : [at2 - 0.07, box22[1], at2 + 0.07, box22[3]];
16595
+ const beam = builder.add({ id: `base_beam_${line}`, role: "beam", material: construction.frame.timber, aabb, y0: height, y1: elevation - context.system.slabThickness }, { floor: 0 });
16596
+ const centre = (q) => alongU ? (q.aabb[1] + q.aabb[3]) / 2 : (q.aabb[0] + q.aabb[2]) / 2;
16597
+ for (const pier of piers.filter((q) => Math.abs(centre(q) - at2) < 0.05)) builder.rest(beam, pier, "bolt");
16598
+ beams.push(beam);
16599
+ }
16600
+ return beams;
16601
+ }
16602
+ function addRaisedFloor(context) {
16603
+ const { construction, system, houseBox: box22 } = context;
16604
+ const carriers = construction.foundation.type === "basement" ? addBasement(context) : addPiers(context);
16605
+ const thickness = system.slabThickness;
16606
+ const joists = addJoists(context, { floor: 0, box: box22, thickness, carriers, material: construction.frame.timber, joint: construction.foundation.type === "basement" ? "bolt" : "nail" });
16607
+ const deckMaterial = context.isTimber ? construction.floor.material === "wood_hard" ? "wood_hard" : "plywood" : construction.frame.slabs;
16608
+ context.base0 = addDeck(context, "slab_0", deckMaterial, { floor: 0, box: box22, thickness, joint: "nail" }, joists);
16609
+ }
16610
+ function addFoundation(context) {
16611
+ const { type, material } = context.construction.foundation;
16612
+ if (context.isUnit) return addBuildingBase(context);
16613
+ if (type === "raft") context.base0 = raft(context, Math.max(0.1, context.elevation), material || "concrete");
16614
+ else if (type === "crawl_space" || type === "piles" || type === "basement") addRaisedFloor(context);
16615
+ else if (context.isTimber && type !== "ground") context.base0 = raft(context, Math.max(0.12, context.elevation), "concrete");
16616
+ }
16617
+ var MAX_GAP_WITHIN_RUN = 3.2;
16618
+ var POST_SPACING = 2.4;
16619
+ var POST_SIDE = 0.05;
16620
+ var alongOf = (wall, pick2) => wall.axis === "u" ? pick2(wall.u0, wall.u1) : pick2(wall.v0, wall.v1);
16621
+ var acrossOf = (wall) => wall.axis === "u" ? wall.v0 : wall.u0;
16622
+ function runsOf(context, walls, floor) {
16623
+ const lines = /* @__PURE__ */ new Map();
16624
+ for (const wall of walls) {
16625
+ if (!wall.exterior || wall.type !== "wall") continue;
16626
+ const top = round2(context.wallParts.get(wall.id).y1);
16627
+ const key = `${floor}|${wall.axis}|${acrossOf(wall).toFixed(2)}|${top.toFixed(2)}`;
16628
+ lines.set(key, [...lines.get(key) ?? [], wall]);
16629
+ }
16630
+ return [...lines.values()].map((line) => {
16631
+ const sorted = line.slice().sort((a, b) => alongOf(a, Math.min) - alongOf(b, Math.min));
16632
+ const runs = [];
16633
+ for (const wall of sorted) {
16634
+ const from = alongOf(wall, Math.min);
16635
+ const to = alongOf(wall, Math.max);
16636
+ const last = runs[runs.length - 1];
16637
+ if (last && from - last.to <= MAX_GAP_WITHIN_RUN) {
16638
+ last.to = Math.max(last.to, to);
16639
+ last.walls.push(wall);
16640
+ } else runs.push({ from, to, walls: [wall] });
16641
+ }
16642
+ return runs;
16643
+ });
16644
+ }
16645
+ function addShackPosts(context, run, plate, floor, index) {
16646
+ const first = run.walls[0];
16647
+ const across = acrossOf(first);
16648
+ const top = context.wallParts.get(first.id).y1;
16649
+ const count = Math.max(1, Math.ceil((run.to - run.from) / POST_SPACING));
16650
+ const posts = [];
16651
+ for (let k = 0; k <= count; k++) {
16652
+ const along = run.from + (run.to - run.from) * k / count;
16653
+ const [u, v] = first.axis === "u" ? [along, across] : [across, along];
16654
+ const half = 0.05 + POST_SIDE / 2;
16655
+ 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 });
16656
+ const base = context.baseOf(floor);
16657
+ if (base === GROUND || !base) context.builder.rest(post, GROUND, "tie");
16658
+ else context.builder.rest(post, base, "nail");
16659
+ context.builder.rest(plate, post, "nail");
16660
+ posts.push(post);
16661
+ }
16662
+ return posts;
16663
+ }
16664
+ function addPlates(context, walls, floor) {
16665
+ const { builder, system, construction } = context;
16666
+ const plates = [];
16667
+ const pillars = [];
16668
+ let counter = 0;
16669
+ for (const runs of runsOf(context, walls, floor)) {
16670
+ for (const run of runs) {
16671
+ const first = run.walls[0];
16672
+ const half = (first.thickness ?? 0.15) / 2;
16673
+ const across = acrossOf(first);
16674
+ const top = context.wallParts.get(first.id).y1;
16675
+ 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];
16676
+ const material = context.isTimber ? construction.frame.timber : construction.frame.beams;
16677
+ 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" });
16678
+ if (construction.system === "improvised") pillars.push(...addShackPosts(context, run, plate, floor, counter));
16679
+ else for (const wall of run.walls) builder.rest(plate, context.wallParts.get(wall.id), system.joints.beam);
16680
+ plates.push(plate);
16681
+ }
16682
+ }
16683
+ return { plates, pillars };
16684
+ }
16685
+ function cornerPoints(walls) {
16686
+ const ends = walls.filter((wall) => wall.exterior && wall.type === "wall").flatMap(
16687
+ (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"]]
16688
+ );
16689
+ const points = [];
16690
+ for (const a of ends) {
16691
+ const joinsOtherAxis = ends.some((b) => b[2] !== a[2] && Math.hypot(a[0] - b[0], a[1] - b[1]) < 0.12);
16692
+ if (joinsOtherAxis && !points.some((q) => Math.hypot(q[0] - a[0], q[1] - a[1]) < 0.12)) points.push([a[0], a[1]]);
16693
+ }
16694
+ return points;
16695
+ }
16696
+ function addCornerPillars(context, walls, floor, plates) {
16697
+ const { builder } = context;
16698
+ const topOfFloor = context.topOf(floor);
16699
+ return cornerPoints(walls).map(([u, v], k) => {
16700
+ const box22 = [u - 0.1, v - 0.1, u + 0.1, v + 0.1];
16701
+ const top = Math.max(topOfFloor, ...plates.filter((plate) => touches(plate.aabb, box22, 0.05)).map((plate) => plate.y0));
16702
+ const pillar = builder.add({ id: `column_${floor}_${k}`, role: "column", material: context.construction.frame.pillars, aabb: box22, y0: context.level(floor), y1: top }, { floor });
16703
+ const base = context.baseOf(floor);
16704
+ builder.rest(pillar, base === GROUND || !base ? GROUND : base, "rebar");
16705
+ for (const plate of plates.filter((p) => touches(p.aabb, pillar.aabb, 0.05))) builder.rest(plate, pillar, "rebar");
16706
+ return pillar;
16707
+ });
16708
+ }
16709
+ function addPlatesAndPillars(context, floor) {
16710
+ const walls = context.walls.filter((wall) => wall.floor === floor);
16711
+ const { plates, pillars } = addPlates(context, walls, floor);
16712
+ if (context.construction.system === "masonry") pillars.push(...addCornerPillars(context, walls, floor, plates));
16713
+ context.pillars.set(floor, pillars);
16714
+ context.plates.set(floor, plates);
16715
+ const innerBearing = walls.filter((wall) => wall.role === "wall" && !wall.exterior).map((wall) => context.wallParts.get(wall.id));
16716
+ context.carriers.set(floor, [...plates, ...pillars, ...innerBearing]);
16717
+ }
16718
+ var POST_HALF = 0.08;
16719
+ var FOOTING_HALF = 0.12;
16720
+ var BEAM_DEPTH = 0.2;
16721
+ var BEAM_WIDTH = 0.16;
16722
+ function addPosts(context, porchId, rect, deck, floor) {
16723
+ const { builder, construction } = context;
16724
+ const hasUpperFloor = floor < context.floorCount - 1;
16725
+ const ceilingY = hasUpperFloor ? context.level(floor + 1) - context.system.slabThickness : context.level(floor) + context.ceilingOf(floor);
16726
+ const deckDepth = Math.min(0.2, Math.max(0.06, context.elevation * 0.5));
16727
+ const spots = [[rect[0] + 0.1, rect[1] + 0.1], [rect[2] - 0.1, rect[1] + 0.1]];
16728
+ if (rect[2] - rect[0] > 4.5) spots.push([(rect[0] + rect[2]) / 2, rect[1] + 0.1]);
16729
+ const posts = spots.map(([u, v], k) => {
16730
+ 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 });
16731
+ builder.rest(footing, GROUND, "resting");
16732
+ builder.rest(deck, footing, "resting");
16733
+ const material = context.isTimber ? construction.frame.timber : construction.frame.pillars;
16734
+ 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 });
16735
+ builder.rest(post, deck, context.isTimber ? "nail" : "rebar");
16736
+ return post;
16737
+ });
16738
+ context.porches.set(porchId, { posts, ceilingY });
16739
+ return posts;
16740
+ }
16741
+ function addUpperBeams(context, porchId, rect, posts, floor) {
16742
+ const { builder, construction } = context;
16743
+ const material = context.isTimber ? construction.frame.timber : construction.frame.beams;
16744
+ const joint = context.isTimber ? "nail" : "rebar";
16745
+ const ceilingY = context.porches.get(porchId).ceilingY;
16746
+ const y0 = ceilingY - BEAM_DEPTH;
16747
+ const carriers = context.carriers.get(floor);
16748
+ 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 });
16749
+ for (const post of posts) builder.rest(head, post, joint);
16750
+ carriers.push(head);
16751
+ 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]]];
16752
+ for (const [name, aabb, post] of sides) {
16753
+ const side = builder.add({ id: `porch_beam_${porchId}_${name}`, role: "beam", material, aabb, y0, y1: ceilingY }, { floor });
16754
+ builder.rest(side, post, joint);
16755
+ builder.rest(side, head, joint);
16756
+ 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);
16757
+ carriers.push(side);
16758
+ }
16759
+ }
16760
+ function addPorches(context, floor) {
16761
+ const { builder, construction } = context;
16762
+ for (const room of context.source.rooms.filter((r) => r.type === "porch" && r.floor === floor)) {
16763
+ const rect = room.parts[0];
16764
+ const base = context.baseOf(floor);
16765
+ const deckDepth = Math.min(0.2, Math.max(0.06, context.elevation * 0.5));
16766
+ 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 });
16767
+ if (base && base !== GROUND) builder.rest(deck, base, context.isTimber ? "nail" : context.system.joints.slab);
16768
+ const posts = addPosts(context, room.id, rect, deck, floor);
16769
+ if (floor < context.floorCount - 1) addUpperBeams(context, room.id, rect, posts, floor);
16770
+ }
16771
+ }
16772
+ var ROOF_RISE = 0.6;
16773
+ function addPorchRoofs(context) {
16774
+ const { builder, construction } = context;
16775
+ for (const room of context.source.rooms.filter((r) => r.type === "porch")) {
16776
+ const floor = room.floor;
16777
+ const rect = room.parts[0];
16778
+ const posts = context.porches.get(room.id);
16779
+ if (floor !== context.floorCount - 1 || !posts) continue;
16780
+ const material = ROOF_COVERINGS.get(construction.roof.covering)?.material ?? "ceramic";
16781
+ 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 });
16782
+ for (const post of posts.posts) builder.rest(roof, post, context.isTimber ? "nail" : "bolt");
16783
+ const housewalls = context.walls.filter((w) => w.floor === floor && !w.exterior && w.sides.includes(room.id) && w.type === "wall");
16784
+ for (const wall of housewalls) {
16785
+ if (context.porchRoofOnPostsOnly) builder.supports.push({ part: roof.id, on: wall.id, joint: "nail", bears: false });
16786
+ else builder.rest(roof, context.wallParts.get(wall.id), "nail");
16787
+ }
16788
+ }
16789
+ }
16790
+ var TRUSS_PITCH = 1.2;
16791
+ var DEFAULT_PITCH = 20;
16792
+ var DEFAULT_EAVE = 0.4;
16242
16793
  var CLOSED_ONLY = [...OPEN_ROOM_TYPES2, "void"];
16794
+ function roofUnits(context, bearing) {
16795
+ const units = [{ suffix: "", box: context.topBox, floor: bearing, annex: false }];
16796
+ if (context.floorCount <= 1 || context.isUnit || bearing !== context.floorCount - 1) return units;
16797
+ for (let floor = 0; floor < context.floorCount - 1; floor++) {
16798
+ const above = context.source.rooms.filter((room) => room.floor === floor + 1).flatMap((room) => room.parts);
16799
+ const rooms = context.source.rooms.filter((room) => room.floor === floor && !OPEN_ROOM_TYPES2.includes(room.type) && room.type !== "void" && room.type !== "elevator");
16800
+ for (const room of rooms) {
16801
+ const area3 = room.parts.reduce((total, part) => total + (part[2] - part[0]) * (part[3] - part[1]), 0);
16802
+ 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);
16803
+ if (covered < area3 * 0.7) units.push({ suffix: `_${room.id}`, box: unionOf(room.parts), floor, annex: true });
16804
+ }
16805
+ }
16806
+ return units;
16807
+ }
16808
+ function roofCarriers(context, unit) {
16809
+ const platesHere = context.plates.get(unit.floor) ?? [];
16810
+ const allPlates = [...context.plates.values()].flat();
16811
+ const eaveHeight = platesHere.length ? Math.max(...platesHere.map((plate) => plate.y1)) : 0;
16812
+ const plates = allPlates.filter((plate) => Math.abs(plate.y1 - eaveHeight) < 0.4 && touches(plate.aabb, unit.box, 0.3));
16813
+ const pillars = (context.pillars.get(unit.floor) ?? []).filter((pillar) => touches(pillar.aabb, unit.box, 0.3));
16814
+ 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));
16815
+ return [...plates, ...pillars, ...walls];
16816
+ }
16817
+ function addFlatRoof(context, unit, carriers) {
16818
+ const { builder, construction, system } = context;
16819
+ const top = context.level(unit.floor) + context.ceilingOf(unit.floor);
16820
+ const [u0, v0, u1, v1] = unit.box;
16821
+ 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] });
16822
+ for (const carrier of carriers.length ? carriers : context.plates.get(unit.floor) ?? []) builder.rest(slab, carrier, system.joints.slab);
16823
+ if (unit.suffix === "") context.topSlab = slab;
16824
+ return { id: slab.id, aabb: slab.aabb, y0: slab.y0, y1: slab.y1, flat: true };
16825
+ }
16826
+ function addTrusses(context, unit, carriers, geometry) {
16827
+ const { builder, construction, system } = context;
16828
+ const { box: box22 } = unit;
16829
+ const { axis, length, eave, rise, eaveY } = geometry;
16830
+ const count = Math.max(2, Math.ceil(length / TRUSS_PITCH) + 1);
16831
+ const trusses = [];
16832
+ for (let k = 0; k < count; k++) {
16833
+ const at2 = (axis === "u" ? box22[0] : box22[1]) + k * length / (count - 1);
16834
+ 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];
16835
+ 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 });
16836
+ const touching = carriers.filter((carrier) => touches(aabb, carrier.aabb, 0.2) && carrier.role !== "slab");
16837
+ const chosen = touching.length ? touching : carriers.slice().sort((a, b) => distanceBetween(aabb, a.aabb) - distanceBetween(aabb, b.aabb)).slice(0, 2);
16838
+ for (const carrier of chosen) builder.rest(truss, carrier, system.joints.truss);
16839
+ trusses.push(truss);
16840
+ }
16841
+ return trusses;
16842
+ }
16843
+ function geometryOf(context, unit) {
16844
+ const { box: box22 } = unit;
16845
+ const roof = context.construction.roof;
16846
+ const platesHere = context.plates.get(unit.floor) ?? [];
16847
+ const width = box22[2] - box22[0];
16848
+ const depth = box22[3] - box22[1];
16849
+ const axis = roof.axis ?? (width >= depth ? "u" : "v");
16850
+ const span = axis === "u" ? depth : width;
16851
+ const pitch = unit.annex ? Math.min(roof.pitch || DEFAULT_PITCH, 20) : roof.pitch || DEFAULT_PITCH;
16852
+ const eaveY = platesHere.length ? Math.max(...platesHere.map((plate) => plate.y1)) : context.level(unit.floor) + context.ceilingOf(unit.floor);
16853
+ return { axis, length: axis === "u" ? width : depth, eave: roof.eave ?? DEFAULT_EAVE, rise: round2(span / 2 * Math.tan(pitch * Math.PI / 180)), eaveY };
16854
+ }
16855
+ function addRafters(context, unit, carriers, geometry) {
16856
+ const { builder, construction, system } = context;
16857
+ const { box: box22 } = unit;
16858
+ const { axis, length, eave, rise, eaveY } = geometry;
16859
+ const count = Math.max(2, Math.ceil(length / TRUSS_PITCH) + 1);
16860
+ const middle = axis === "u" ? (box22[1] + box22[3]) / 2 : (box22[0] + box22[2]) / 2;
16861
+ const frames = { all: [], sideA: [], sideB: [] };
16862
+ for (let k = 0; k < count; k++) {
16863
+ const at2 = (axis === "u" ? box22[0] : box22[1]) + k * length / (count - 1);
16864
+ 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]]];
16865
+ for (const [side, aabb] of halves) {
16866
+ 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 });
16867
+ const touching = carriers.filter((carrier) => touches(aabb, carrier.aabb, 0.2) && carrier.role !== "slab");
16868
+ const chosen = touching.length ? touching : carriers.slice().sort((x, y) => distanceBetween(aabb, x.aabb) - distanceBetween(aabb, y.aabb)).slice(0, 2);
16869
+ for (const carrier of chosen) builder.rest(rafter, carrier, system.joints.truss);
16870
+ frames.all.push(rafter);
16871
+ if (side === "a") frames.sideA.push(rafter);
16872
+ else frames.sideB.push(rafter);
16873
+ }
16874
+ }
16875
+ if (!unit.annex) {
16876
+ 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];
16877
+ 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 });
16878
+ for (const rafter of frames.all) builder.rest(ridge, rafter, system.joints.truss);
16879
+ }
16880
+ context.roof.trusses += count;
16881
+ return frames;
16882
+ }
16883
+ function panesOf(unit, geometry) {
16884
+ const [u0, v0, u1, v1] = unit.box;
16885
+ const { eave, axis } = geometry;
16886
+ if (unit.annex) return [[u0 - eave, v0 - eave, u1 + eave, v1 + eave]];
16887
+ const midV = (v0 + v1) / 2;
16888
+ const midU = (u0 + u1) / 2;
16889
+ 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]];
16890
+ }
16891
+ function coarseFrames(context, unit, carriers, geometry) {
16892
+ const trusses = addTrusses(context, unit, carriers, geometry);
16893
+ context.roof.trusses += trusses.length;
16894
+ return { all: trusses, sideA: trusses, sideB: trusses };
16895
+ }
16896
+ function addGableRoof(context, unit, carriers, covering) {
16897
+ const { builder, construction } = context;
16898
+ const geometry = geometryOf(context, unit);
16899
+ const frames = context.rafterRoof ? addRafters(context, unit, carriers, geometry) : coarseFrames(context, unit, carriers, geometry);
16900
+ const panes = [];
16901
+ panesOf(unit, geometry).forEach((aabb, k) => {
16902
+ const name = unit.annex ? "annex" : k === 0 ? "a" : "b";
16903
+ const options = { floor: unit.floor, fill: construction.roof.covering === "colonial" ? 0.12 : 0.05, ...construction.roof.pitch === void 0 ? {} : { pitch: construction.roof.pitch } };
16904
+ 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);
16905
+ const carried = unit.annex || !context.rafterRoof ? frames.all : k === 0 ? frames.sideA : frames.sideB;
16906
+ for (const frame2 of carried) builder.rest(pane, frame2, covering);
16907
+ panes.push({ id: pane.id, aabb: pane.aabb, y0: pane.y0, y1: pane.y1 });
16908
+ });
16909
+ if (!unit.annex) {
16910
+ const roof = context.roof;
16911
+ roof.axis = geometry.axis;
16912
+ roof.ridge = round2(geometry.eaveY + geometry.rise);
16913
+ roof.eave = geometry.eave;
16914
+ roof.y0 = round2(geometry.eaveY);
16915
+ roof.aabb = [unit.box[0] - geometry.eave, unit.box[1] - geometry.eave, unit.box[2] + geometry.eave, unit.box[3] + geometry.eave].map(round2);
16916
+ }
16917
+ return panes;
16918
+ }
16919
+ function addCeilingJoists(context) {
16920
+ const { builder, construction } = context;
16921
+ const bearing = context.roof.bearingFloor;
16922
+ const rects = context.rectsOf(bearing, CLOSED_ONLY);
16923
+ const box22 = unionOf(rects.length ? rects : [context.topBox]);
16924
+ 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))];
16925
+ const length = box22[2] - box22[0];
16926
+ const depth = box22[3] - box22[1];
16927
+ const alongU = length >= depth;
16928
+ const span = alongU ? length : depth;
16929
+ const count = Math.max(2, Math.round(span / 0.6) + 1);
16930
+ const beamTops = carriers.filter((carrier) => carrier.role === "beam").map((carrier) => carrier.y0);
16931
+ const y0 = carriers.length ? Math.max(...beamTops, context.level(bearing) + 1) : context.topOf(bearing);
16932
+ const joists = [];
16933
+ for (let k = 0; k < count; k++) {
16934
+ const at2 = (alongU ? box22[0] : box22[1]) + 0.05 + k * (span - 0.1) / (count - 1);
16935
+ const aabb = alongU ? [at2 - 0.025, box22[1], at2 + 0.025, box22[3]] : [box22[0], at2 - 0.025, box22[2], at2 + 0.025];
16936
+ 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 });
16937
+ const beams = carriers.filter((carrier) => carrier.role === "beam" && touches(aabb, carrier.aabb, 0.2));
16938
+ for (const carrier of beams.length ? beams : carriers.slice(0, 2)) builder.rest(joist, carrier, "nail");
16939
+ joists.push(joist);
16940
+ }
16941
+ context.ceilingJoists = joists;
16942
+ }
16943
+ function addRoof(context) {
16944
+ const { construction } = context;
16945
+ const covering = ROOF_COVERINGS.get(construction.roof.covering);
16946
+ const bearing = construction.roof.bearingFloor ?? context.floorCount - 1;
16947
+ const open = context.rectsOf(context.floorCount - 1, OPEN_ROOM_TYPES2);
16948
+ const topRects = context.rectsOf(context.floorCount - 1, CLOSED_ONLY);
16949
+ const body = unionOf(open.length ? open : topRects);
16950
+ context.topBox = context.floorCount - 1 === bearing ? body : unionOf(context.rectsOf(bearing, OPEN_ROOM_TYPES2));
16951
+ context.roof = { shape: construction.roof.shape, covering: construction.roof.covering, material: construction.roof.material, pitch: construction.roof.pitch || 0, bearingFloor: bearing, panes: [], trusses: 0 };
16952
+ for (const unit of roofUnits(context, bearing)) {
16953
+ const carriers = roofCarriers(context, unit);
16954
+ if (construction.roof.shape === "slab") context.roof.panes.push(addFlatRoof(context, unit, carriers));
16955
+ else context.roof.panes.push(...addGableRoof(context, unit, carriers, covering?.joint ?? "nail"));
16956
+ }
16957
+ if (context.isTimber && construction.roof.shape !== "slab") addCeilingJoists(context);
16958
+ }
16959
+ function flightsOf(items) {
16960
+ const flights = /* @__PURE__ */ new Map();
16961
+ for (const item of items.filter((it) => it.category === "stairs" && it.link)) {
16962
+ const key = `${item.link.from}_${item.link.to}`;
16963
+ if (!flights.has(key) || item.floor === item.link.from) flights.set(key, item);
16964
+ }
16965
+ return flights;
16966
+ }
16967
+ function addStairs(context) {
16968
+ const { builder, construction } = context;
16969
+ const material = context.isTimber ? construction.frame.timber : construction.stairs ?? "concrete";
16970
+ for (const [key, item2] of flightsOf(context.source.items)) {
16971
+ const { from, to } = item2.link;
16972
+ 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) });
16973
+ const base = context.baseOf(from);
16974
+ builder.rest(flight2, !base || base === GROUND ? GROUND : base, !base || base === GROUND ? "resting" : context.isTimber ? "nail" : "mortar");
16975
+ const upper = context.baseOf(to);
16976
+ if (upper && upper !== GROUND) builder.rest(flight2, upper, context.isTimber ? "nail" : "bolt");
16977
+ }
16978
+ if (!context.hasBasement) return;
16979
+ const item = context.source.items.find((it) => it.type === "basement_stairs");
16980
+ if (!item?.aabb) return;
16981
+ const depth = construction.foundation.depth || 2.4;
16982
+ 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) });
16983
+ builder.rest(flight, builder.find("basement_slab"), "resting");
16984
+ if (context.base0 && context.base0 !== GROUND) builder.rest(flight, context.base0, "nail");
16985
+ }
16986
+ var TOLERANCE = 0.02;
16987
+ var DOOR_HEIGHT = 2.1;
16988
+ var LINTEL_DEPTH = 0.2;
16989
+ var LINTEL_BEARING = 0.2;
16990
+ var crossOf = (wall) => wall.axis === "u" ? wall.v0 : wall.u0;
16991
+ 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)];
16992
+ function findOpenings(context) {
16993
+ const openings = [];
16994
+ for (const window of context.source.windows ?? []) {
16995
+ const from = window.at - window.width / 2;
16996
+ const to = window.at + window.width / 2;
16997
+ const wall = context.walls.find((w) => {
16998
+ if (w.floor !== window.floor || w.axis !== window.axis || w.type !== "wall" || Math.abs(crossOf(w) - window.pos) > TOLERANCE) return false;
16999
+ const [low, high] = alongRange(w);
17000
+ return from >= low - TOLERANCE && to <= high + TOLERANCE;
17001
+ });
17002
+ if (!wall) continue;
17003
+ const base = context.level(window.floor);
17004
+ openings.push({ id: window.id, kind: "window", wall: wall.id, from, to, y0: base + window.y0, y1: base + window.y1 });
17005
+ }
17006
+ return openings;
17007
+ }
17008
+ function addDoorLintels(context) {
17009
+ const { builder, construction, system } = context;
17010
+ for (const door of context.source.doors.filter((d) => d.a !== void 0 && d.type !== "zipper")) {
17011
+ const along = door.axis === "u" ? door.u : door.v;
17012
+ const cross = door.axis === "u" ? door.v : door.u;
17013
+ const from = along - door.width / 2;
17014
+ const to = along + door.width / 2;
17015
+ 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));
17016
+ if (!beside.length) continue;
17017
+ const half = (beside[0].thickness ?? 0.15) / 2;
17018
+ const lo = from - LINTEL_BEARING;
17019
+ const hi = to + LINTEL_BEARING;
17020
+ const box22 = door.axis === "u" ? [lo, cross - half, hi, cross + half] : [cross - half, lo, cross + half, hi];
17021
+ const y0 = context.level(door.floor) + DOOR_HEIGHT;
17022
+ 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" } });
17023
+ for (const wall of beside) builder.rest(lintel, context.wallParts.get(wall.id), system.joints.beam);
17024
+ for (const plate of context.plates.get(door.floor) ?? []) if (overlapsInPlan(plate.aabb, box22)) builder.rest(plate, lintel, system.joints.beam);
17025
+ }
17026
+ }
17027
+ var overlapsInPlan = (a, b) => a[0] < b[2] && a[2] > b[0] && a[1] < b[3] && a[3] > b[1];
17028
+ var RISER = 0.18;
17029
+ var TREAD = 0.28;
17030
+ var stepCount = (elevation) => Math.max(1, Math.ceil(elevation / RISER));
17031
+ function addPorchSteps(context) {
17032
+ const { builder } = context;
17033
+ for (const room of context.source.rooms.filter((r) => r.type === "porch")) {
17034
+ const deck = builder.find(`porch_floor_${room.id}`);
17035
+ for (const door of context.source.doors.filter((d) => d.floor === room.floor && d.b === "outside" && d.a === room.id)) {
17036
+ const count = stepCount(context.elevation);
17037
+ const depth = count * TREAD;
17038
+ const aabb = [door.u - door.width / 2, door.v - depth, door.u + door.width / 2, door.v];
17039
+ 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 });
17040
+ builder.rest(flight, GROUND, "resting");
17041
+ if (deck) builder.rest(flight, deck, context.isTimber ? "nail" : "mortar");
17042
+ }
17043
+ }
17044
+ }
17045
+ function boxOfSteps(door, depth) {
17046
+ const direction = door.axis === "u" ? door.side === "front" ? -1 : 1 : door.side === "left" ? -1 : 1;
17047
+ 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)];
17048
+ 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];
17049
+ }
17050
+ function addDoorSteps(context) {
17051
+ if (context.elevation <= 0.1) return;
17052
+ const { builder } = context;
17053
+ const doors = context.source.doors.filter((d) => d.floor === 0 && d.exterior && d.model !== "steel" && d.type !== "steps" && d.type !== "zipper");
17054
+ for (const door of doors) {
17055
+ const count = stepCount(context.elevation);
17056
+ const depth = Math.min(TREAD * count, 0.9);
17057
+ 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 });
17058
+ builder.rest(flight, GROUND, "resting");
17059
+ const base = context.base0;
17060
+ if (base && base !== GROUND) builder.rest(flight, base, context.isTimber ? "nail" : "mortar");
17061
+ }
17062
+ }
17063
+ function addExteriorSteps(context) {
17064
+ addPorchSteps(context);
17065
+ addDoorSteps(context);
17066
+ }
17067
+ var TANK_FILL = 0.08;
17068
+ var squareAround = (u, v, side) => [u - side / 2, v - side / 2, u + side / 2, v + side / 2];
17069
+ function addTank(context, tank, floor, y0) {
17070
+ 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 });
17071
+ }
17072
+ function ensureCeilingSlab(context, y) {
17073
+ const { builder, construction, system } = context;
17074
+ const existing = builder.find("slab_ceiling");
17075
+ if (existing) return existing;
17076
+ const top = context.floorCount - 1;
17077
+ const box22 = context.topBox;
17078
+ const carriers = builder.parts.filter(
17079
+ (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)
17080
+ );
17081
+ 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] });
17082
+ for (const carrier of carriers) builder.rest(slab, carrier, system.joints.slab);
17083
+ return slab;
17084
+ }
17085
+ function addRaisedPlatform(context, tank) {
17086
+ const { builder, construction } = context;
17087
+ const platform = tank.platform;
17088
+ const top = context.floorCount - 1;
17089
+ const ceiling = ensureCeilingSlab(context, platform.baseY);
17090
+ const pillars = platform.pillars.map((q, k) => {
17091
+ 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 });
17092
+ builder.rest(pillar, ceiling, "rebar");
17093
+ return pillar;
17094
+ });
17095
+ 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 });
17096
+ for (const pillar of pillars) builder.rest(slab, pillar, "rebar");
17097
+ builder.rest(addTank(context, tank, top, tank.y), slab, "resting");
17098
+ }
17099
+ function addAtticTank(context, tank) {
17100
+ const { builder } = context;
17101
+ const top = context.floorCount - 1;
17102
+ if (!context.isTimber) {
17103
+ const ceiling = ensureCeilingSlab(context, tank.y);
17104
+ builder.rest(addTank(context, tank, top, tank.y), ceiling, "resting");
17105
+ return;
17106
+ }
17107
+ const supports = context.topSlab ? [context.topSlab] : context.ceilingJoists ?? builder.parts.filter((part) => part.role === "beam" && part.id.startsWith(`plate_${top}_`));
17108
+ const y0 = supports[0] ? supports[0].y1 : tank.y - 0.03;
17109
+ const floor = builder.add({ id: "attic_floor", role: "slab", material: "plywood", aabb: context.topBox, y0, y1: y0 + 0.03 }, { floor: top });
17110
+ for (const support of supports) if (touches(floor.aabb, support.aabb, 0.1)) builder.rest(floor, support, "nail");
17111
+ builder.rest(addTank(context, tank, top, tank.y), floor, "resting");
17112
+ }
17113
+ function addSlabTank(context, tank) {
17114
+ const { builder } = context;
17115
+ const supports = builder.parts.filter((part2) => part2.role === "slab" && Math.abs(part2.y1 - tank.y) < 0.3 && touches(part2.aabb, tank.aabb, 0.1));
17116
+ const carrier = supports.sort((a, b) => Math.abs(a.y1 - tank.y) - Math.abs(b.y1 - tank.y))[0] ?? context.topSlab;
17117
+ const part = addTank(context, tank, tank.floor, tank.y);
17118
+ builder.rest(part, carrier ?? GROUND, "resting");
17119
+ }
17120
+ function addTrestleTank(context, tank) {
17121
+ const { builder } = context;
17122
+ const stand = tank.stand;
17123
+ const columns = stand.pillars.map((q, k) => {
17124
+ 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 });
17125
+ builder.rest(column, GROUND, stand.baseJoint ?? "resting");
17126
+ return column;
17127
+ });
17128
+ const [u0, v0, u1, v1] = tank.aabb;
17129
+ 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) => {
17130
+ const beam = builder.add({ id: `trestle_beam_${k}`, role: "beam", material: stand.material, aabb, y0: stand.height - 0.12, y1: stand.height }, { floor: 0 });
17131
+ for (const column of columns.filter((c) => touches(beam.aabb, c.aabb, 0.15))) builder.rest(beam, column, stand.joint ?? "nail");
17132
+ return beam;
17133
+ });
17134
+ const part = addTank(context, tank, 0, stand.height);
17135
+ for (const beam of beams) builder.rest(part, beam, stand.tie ?? "resting");
17136
+ }
17137
+ function addWaterTank(context) {
17138
+ const tank = context.tank;
17139
+ if (!tank) return;
17140
+ switch (tank.place) {
17141
+ case "slab":
17142
+ return addSlabTank(context, tank);
17143
+ case "raised_slab":
17144
+ return addRaisedPlatform(context, tank);
17145
+ case "attic":
17146
+ return addAtticTank(context, tank);
17147
+ case "trestle":
17148
+ return addTrestleTank(context, tank);
17149
+ }
17150
+ }
17151
+ function addUpperFloor(context, floor) {
17152
+ if (floor >= context.floorCount - 1 || context.isUnit) return;
17153
+ const next = floor + 1;
17154
+ const rects = context.rectsOf(next, ["balcony", "porch", "void"]);
17155
+ const box22 = rects.length ? unionOf(rects) : context.houseBox;
17156
+ const carriers = context.carriers.get(floor);
17157
+ const { system, construction, builder } = context;
17158
+ const thickness = system.slabThickness;
17159
+ if (context.isTimber) {
17160
+ const joists = addJoists(context, { floor: next, box: box22, thickness, carriers, material: construction.frame.timber, joint: system.joints.beam });
17161
+ addDeck(context, `slab_${next}`, construction.frame.slabs, { floor: next, box: box22, thickness, joint: system.joints.slab }, joists);
17162
+ return;
17163
+ }
17164
+ 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] });
17165
+ const below = carriers.filter((carrier) => carrier.y0 <= slab.y0 + 0.05);
17166
+ const touching = below.filter((carrier) => touches(slab.aabb, carrier.aabb, 0.2));
17167
+ for (const carrier of touching.length ? touching : below) builder.rest(slab, carrier, system.joints.slab);
17168
+ }
17169
+ function wallCeiling(context, wall) {
17170
+ const rooms = wall.sides.map((id) => context.source.rooms.find((room) => room.id === id && room.floor === wall.floor)).filter((room) => room !== void 0);
17171
+ return Math.max(context.ceilingOf(wall.floor), ...rooms.map((room) => room.ceiling || 0));
17172
+ }
17173
+ function wallTop(context, wall, floor) {
17174
+ const y0 = context.level(floor);
17175
+ if (wall.type === "railing") return y0 + guardHeightOf(wall);
17176
+ const ceiling = wallCeiling(context, wall);
17177
+ const height = wall.structureHeight || wall.h || context.ceilingOf(floor);
17178
+ if (wall.exterior && floor < context.floorCount - 1 && !context.isUnit && ceiling > context.ceilingOf(floor) + 0.01) return y0 + ceiling;
17179
+ if (floor === context.floorCount - 1 || context.isUnit) return y0 + Math.max(height, ceiling);
17180
+ const top = context.topOf(floor);
17181
+ return Math.max(y0 + height, y0 + ceiling > top ? top : y0 + ceiling);
17182
+ }
17183
+ function railingBase(context, wall, floor) {
17184
+ 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"));
17185
+ if (!open) return context.baseOf(floor);
17186
+ return open.type === "porch" ? `porch_floor_${open.id}` : `slab_${floor}_${open.id}`;
17187
+ }
17188
+ var assemblyOf = (context) => context.construction.walls.assembly ?? (context.system.walls || void 0);
17189
+ function addWallPart(context, wall, floor) {
17190
+ const { system, builder } = context;
17191
+ const isRailing = wall.type === "railing";
17192
+ const y0 = context.level(floor);
17193
+ const top = wallTop(context, wall, floor);
17194
+ const y1 = isRailing ? top : wall.exterior && !wall.gable ? top - system.plateThickness : top;
17195
+ const framed = system.studs !== void 0 && !isRailing;
17196
+ const part = builder.add(
17197
+ { id: wall.id, role: wall.role, material: wall.material, aabb: wall.box, y0, y1: Math.max(y1, y0 + 0.2) },
17198
+ { floor, fill: context.isTimber && wall.role === "wall" ? system.wallFill : 1, ...framed ? { studs: system.studs } : {}, ...isRailing ? {} : { assembly: assemblyOf(context) } }
17199
+ );
17200
+ context.wallParts.set(wall.id, part);
17201
+ const base = isRailing ? railingBase(context, wall, floor) : context.baseOf(floor);
17202
+ const joint = isRailing ? "nail" : system.joints.wall;
17203
+ if (base === GROUND || floor === 0 && !base) builder.rest(part, GROUND, joint);
17204
+ else if (base) builder.rest(part, base, joint);
17205
+ }
17206
+ function addWalls(context, floor) {
17207
+ for (const wall of context.walls.filter((w) => w.floor === floor)) addWallPart(context, wall, floor);
17208
+ }
17209
+ function buildFloors(context) {
17210
+ for (let floor = 0; floor < context.floorCount; floor++) {
17211
+ addWalls(context, floor);
17212
+ addPlatesAndPillars(context, floor);
17213
+ addPorches(context, floor);
17214
+ addUpperFloor(context, floor);
17215
+ }
17216
+ }
17217
+ function buildStructure(source, options = {}) {
17218
+ const refined = options.wallDetail !== "coarse";
17219
+ const built = options.construction ? { construction: options.construction, extras: options.extras ?? {} } : resolveConstruction(source);
17220
+ const construction = built.construction;
17221
+ const extras = options.extras ?? built.extras ?? {};
17222
+ if (construction.system === "canvas") return buildCanvasStructure(construction, extras);
17223
+ const tank = options.tank !== void 0 ? options.tank : source.waterTank ?? null;
17224
+ const context = createContext(source, construction, extras, tank, { porchRoofOnPostsOnly: options.porchRoofOnPostsOnly ?? refined, rafterRoof: options.rafterRoof ?? refined });
17225
+ addFoundation(context);
17226
+ buildFloors(context);
17227
+ addBalconies(context);
17228
+ addPorchRoofs(context);
17229
+ addExteriorSteps(context);
17230
+ addStairs(context);
17231
+ if (options.roof !== false) addRoof(context);
17232
+ if (refined) addDoorLintels(context);
17233
+ addWaterTank(context);
17234
+ addChimney(context);
17235
+ addWell(context);
17236
+ const levels = source.floors.map((_, floor) => context.level(floor));
17237
+ const framed = construction.system === "masonry" && (context.isUnit || source.style === "commercial" || context.floorCount >= 3);
17238
+ return { parts: context.builder.parts, supports: context.builder.supports, levels, elevation: context.elevation, roof: context.roof, construction, framed, openings: findOpenings(context) };
17239
+ }
16243
17240
  var entry = (itemId, min, max, group, pickable, weight) => ({ itemId, min, max, group, pickable, weight });
16244
17241
  var FRIDGE = [
16245
17242
  entry("water_bottle", 1, 3, "water", true, 3),
@@ -20372,10 +21369,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20372
21369
  }
20373
21370
 
20374
21371
  // ../game-floorplan-residential/dist/index.js
20375
- var __defProp2 = Object.defineProperty;
21372
+ var __defProp3 = Object.defineProperty;
20376
21373
  var __export2 = (target, all) => {
20377
21374
  for (var name in all)
20378
- __defProp2(target, name, { get: all[name], enumerable: true });
21375
+ __defProp3(target, name, { get: all[name], enumerable: true });
20379
21376
  };
20380
21377
  var residentialItems = [
20381
21378
  { type: "sofa_3seat", category: "sofa", w: 2.1, d: 0.9, h: 0.85, color: { role: "fabric_main" } },
@@ -21576,7 +22573,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21576
22573
  }
21577
22574
  function recordSpec(THREE, name, root, run, opts) {
21578
22575
  const o = { chairs: opts.chairs };
21579
- const materialOf = { ...BASE_MATERIAL, ...opts.materials };
22576
+ const materialOf2 = { ...BASE_MATERIAL, ...opts.materials };
21580
22577
  const drawn = [];
21581
22578
  const counters = /* @__PURE__ */ new Map();
21582
22579
  const q = new THREE.Quaternion();
@@ -21632,7 +22629,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21632
22629
  if (!drawn.length) throw new Error(`game-floorplan-three: the builder of "${name}" drew no box or cylinder`);
21633
22630
  const parts = drawn.map((p) => {
21634
22631
  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]);
21635
- let material = materialOf[p.key] ?? "plastic";
22632
+ let material = materialOf2[p.key] ?? "plastic";
21636
22633
  if (p.key === "wood" && material === "wood_hard" && thin < WOOD_THIN) material = "wood_soft";
21637
22634
  const part = { id: p.id, material, shape: p.shape, position: p.position };
21638
22635
  if (p.rotation) part.rotation = p.rotation;
@@ -21758,6 +22755,66 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21758
22755
  K.PARTS.length = before;
21759
22756
  return { spec: made.spec, build: made.build, spout: [water.pos[0] - root.x, water.pos[1], water.pos[2] - root.z] };
21760
22757
  }
22758
+
22759
+ // src/wall.ts
22760
+ var PLASTER = 15262420;
22761
+ var WALL_ID = "wall_0_0";
22762
+ function wallPlan(options) {
22763
+ const { w, h, thickness = 0.15, finish = "plaster", color: color2 = PLASTER } = options;
22764
+ const facade = { finish, roof: "none", floors: 1 };
22765
+ 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 };
22766
+ const plan = {
22767
+ template: "wall",
22768
+ name: "wall",
22769
+ style: "mid",
22770
+ state: "normal",
22771
+ w,
22772
+ d: thickness,
22773
+ floors: [{ index: 0, y0: 0, ceiling: h }],
22774
+ rooms: [],
22775
+ walls: [wall],
22776
+ doors: [],
22777
+ windows: [],
22778
+ items: [],
22779
+ stateItems: [],
22780
+ openPairs: [],
22781
+ partyWalls: [],
22782
+ missing: [],
22783
+ facade,
22784
+ colors: { facade: color2, wall: color2 },
22785
+ lot: null,
22786
+ building: null,
22787
+ waterTank: null,
22788
+ outbuildings: []
22789
+ };
22790
+ plan.construction = masonryConstruction({ ...plan, facade: { ...facade, roof: "colonial" } });
22791
+ plan.structure = buildStructure(plan, { wallDetail: "coarse", construction: plan.construction, roof: false });
22792
+ return plan;
22793
+ }
22794
+ function draw(THREE, plan, options) {
22795
+ return create3(THREE, { ...options, fleet: null }).build(plan, { quality: "game", terrain: false, noPerimeter: true, pieces: "meshes" });
22796
+ }
22797
+ function label(house) {
22798
+ for (const [id, items] of Object.entries(house.destruction.ranges())) {
22799
+ for (const item of items) {
22800
+ const object2 = item.isObject3D ? item : item.mesh;
22801
+ object2.userData.destructionPart = id;
22802
+ }
22803
+ }
22804
+ return house.group;
22805
+ }
22806
+ function wallPiece(THREE, options) {
22807
+ const plan = wallPlan(options);
22808
+ const houseOptions = options.house ?? {};
22809
+ const probe = draw(THREE, plan, { ...houseOptions, destruction: void 0, destructionRenderer: void 0 });
22810
+ const spec = probe.structure.spec({ wallDetail: "coarse" });
22811
+ probe.dispose();
22812
+ return {
22813
+ plan,
22814
+ spec,
22815
+ build: (T2) => label(draw(T2, plan, houseOptions))
22816
+ };
22817
+ }
21761
22818
  return __toCommonJS(index_exports);
21762
22819
  })();
21763
22820
  //# sourceMappingURL=index.global.js.map