@adatechnology/game-floorplan-structure 1.0.5 → 1.1.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.
package/dist/index.js CHANGED
@@ -116,14 +116,14 @@ function logCabinConstruction(options = {}) {
116
116
  var MIXED_SCRAP = ["wood_soft", "steel", "plywood", "steel", "wood_soft"];
117
117
  function shackConstruction(options = {}) {
118
118
  const material = options.material === "zinc" ? "steel" : "wood_soft";
119
- const onSoil = options.floor === "soil";
119
+ const onSoil2 = options.floor === "soil";
120
120
  return {
121
121
  system: "improvised",
122
122
  walls: { material, interior: options.material === "zinc" ? "plywood" : material, cladding: null, finish: "scrap", thickness: WALL_THICKNESS, ...options.material === "mixed" ? { mixed: MIXED_SCRAP } : {} },
123
- floor: onSoil ? { material: "soil", finish: "packed_earth" } : { material: "concrete", finish: "burnt_cement" },
123
+ floor: onSoil2 ? { material: "soil", finish: "packed_earth" } : { material: "concrete", finish: "burnt_cement" },
124
124
  roof: { material: "steel", covering: "zinc", shape: "gable", pitch: 12, timber: "wood_soft", eave: 0.3, fastening: "nails_and_stones" },
125
125
  frame: { material: "wood_soft", pillars: "wood_soft", beams: "wood_soft", slabs: "concrete", timber: "wood_soft", rebar: null },
126
- foundation: onSoil ? { type: "ground", material: "soil", elevation: 0 } : { type: "raft", material: "concrete", elevation: 0.08 }
126
+ foundation: onSoil2 ? { type: "ground", material: "soil", elevation: 0 } : { type: "raft", material: "concrete", elevation: 0.08 }
127
127
  };
128
128
  }
129
129
  function campConstruction() {
@@ -192,6 +192,13 @@ var en_default = {
192
192
  structure_beam_concrete: "Bond beam / beam (concrete)",
193
193
  structure_beam_wood: "Plate / beam (wood)",
194
194
  structure_roof_frame: "Trusses, rafters and ridge (roof view)"
195
+ },
196
+ labels: {
197
+ elevator_lobby: "Elevator lobby",
198
+ service_shaft: "Service shaft",
199
+ plant_room: "Plant room",
200
+ mechanical_floor: "Mechanical floor",
201
+ typical_floor: "Typical floor"
195
202
  }
196
203
  };
197
204
 
@@ -206,6 +213,13 @@ var pt_BR_default = {
206
213
  structure_beam_concrete: "Cinta / viga (concreto)",
207
214
  structure_beam_wood: "Prato / viga (madeira)",
208
215
  structure_roof_frame: "Tesouras, caibros e cumeeira (vista do telhado)"
216
+ },
217
+ labels: {
218
+ elevator_lobby: "Antessala dos elevadores",
219
+ service_shaft: "Shaft de servi\xE7o",
220
+ plant_room: "Casa de m\xE1quinas",
221
+ mechanical_floor: "Andar t\xE9cnico",
222
+ typical_floor: "Andar tipo"
209
223
  }
210
224
  };
211
225
 
@@ -391,14 +405,14 @@ function addBalconies(context) {
391
405
  const { builder, construction, system } = context;
392
406
  for (const room of context.source.rooms.filter((r) => r.type === "balcony" && r.floor >= 0)) {
393
407
  const floor = room.floor;
394
- const box = unionOf(room.parts);
408
+ const box2 = unionOf(room.parts);
395
409
  const base = floor === 0 ? void 0 : context.baseOf(floor);
396
- const deck = builder.add({ id: `slab_${floor}_${room.id}`, role: "slab", material: construction.frame.slabs, aabb: box, y0: context.level(floor) - system.slabThickness, y1: context.level(floor) }, { floor });
410
+ const deck = builder.add({ id: `slab_${floor}_${room.id}`, role: "slab", material: construction.frame.slabs, aabb: box2, y0: context.level(floor) - system.slabThickness, y1: context.level(floor) }, { floor });
397
411
  if (base) {
398
412
  builder.rest(deck, base, system.joints.slab);
399
413
  continue;
400
414
  }
401
- const pillar = builder.add({ id: `balcony_column_${room.id}`, role: "column", material: construction.frame.pillars, aabb: [box[0] - 0.08, box[1] - 0.08, box[0] + 0.08, box[1] + 0.08], y0: 0, y1: context.level(floor) - system.slabThickness }, { floor: 0 });
415
+ const pillar = builder.add({ id: `balcony_column_${room.id}`, role: "column", material: construction.frame.pillars, aabb: [box2[0] - 0.08, box2[1] - 0.08, box2[0] + 0.08, box2[1] + 0.08], y0: 0, y1: context.level(floor) - system.slabThickness }, { floor: 0 });
402
416
  builder.rest(pillar, GROUND, "rebar");
403
417
  builder.rest(deck, pillar, "rebar");
404
418
  }
@@ -460,17 +474,17 @@ function addWell(context) {
460
474
  // src/structure/floor-deck.ts
461
475
  var JOIST_PITCH = 0.6;
462
476
  function addJoists(context, options) {
463
- const { floor, box, thickness, carriers, material, joint, suffix = "" } = options;
464
- const length = box[2] - box[0];
465
- const depth = box[3] - box[1];
477
+ const { floor, box: box2, thickness, carriers, material, joint, suffix = "" } = options;
478
+ const length = box2[2] - box2[0];
479
+ const depth = box2[3] - box2[1];
466
480
  const alongU = length >= depth;
467
481
  const span = alongU ? length : depth;
468
482
  const count = Math.max(2, Math.round(span / JOIST_PITCH) + 1);
469
483
  const top = context.level(floor) - thickness * 0.15;
470
484
  const out = [];
471
485
  for (let k = 0; k < count; k++) {
472
- const centre = (alongU ? box[0] : box[1]) + 0.05 + k * (span - 0.1) / (count - 1);
473
- const aabb = alongU ? [centre - 0.025, box[1], centre + 0.025, box[3]] : [box[0], centre - 0.025, box[2], centre + 0.025];
486
+ const centre = (alongU ? box2[0] : box2[1]) + 0.05 + k * (span - 0.1) / (count - 1);
487
+ const aabb = alongU ? [centre - 0.025, box2[1], centre + 0.025, box2[3]] : [box2[0], centre - 0.025, box2[2], centre + 0.025];
474
488
  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 });
475
489
  out.push(joist);
476
490
  const below = carriers.filter((carrier) => carrier.y0 <= top + 0.05);
@@ -482,8 +496,8 @@ function addJoists(context, options) {
482
496
  return out;
483
497
  }
484
498
  function addDeck(context, id, material, options, joists) {
485
- const { floor, box, thickness, joint } = options;
486
- const deck = context.builder.add({ id, role: "slab", material, aabb: box, y0: context.level(floor) - thickness * 0.15, y1: context.level(floor) }, { floor });
499
+ const { floor, box: box2, thickness, joint } = options;
500
+ const deck = context.builder.add({ id, role: "slab", material, aabb: box2, y0: context.level(floor) - thickness * 0.15, y1: context.level(floor) }, { floor });
487
501
  for (const joist of joists) context.builder.rest(deck, joist, joint);
488
502
  return deck;
489
503
  }
@@ -496,28 +510,28 @@ var raft = (context, thickness, material) => {
496
510
  return part;
497
511
  };
498
512
  function addBuildingBase(context) {
499
- const { builder, construction, houseBox: box } = context;
500
- const corners = [[box[0], box[1]], [box[2], box[1]], [box[0], box[3]], [box[2], box[3]]];
513
+ const { builder, construction, houseBox: box2 } = context;
514
+ const corners = [[box2[0], box2[1]], [box2[2], box2[1]], [box2[0], box2[3]], [box2[2], box2[3]]];
501
515
  const top = context.level(0) - context.system.slabThickness;
502
516
  const pillars = corners.map(([u, v], k) => {
503
517
  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 });
504
518
  builder.rest(pillar, GROUND, "rebar");
505
519
  return pillar;
506
520
  });
507
- const slab = builder.add({ id: "slab_base", role: "slab", material: construction.frame.slabs, aabb: box, y0: top, y1: context.level(0) }, { floor: 0 });
521
+ const slab = builder.add({ id: "slab_base", role: "slab", material: construction.frame.slabs, aabb: box2, y0: top, y1: context.level(0) }, { floor: 0, footprint: [box2] });
508
522
  for (const pillar of pillars) builder.rest(slab, pillar, "rebar");
509
523
  context.base0 = slab;
510
524
  }
511
525
  function addBasement(context) {
512
- const { builder, houseBox: box, elevation } = context;
526
+ const { builder, houseBox: box2, elevation } = context;
513
527
  const depth = context.construction.foundation.depth || 2.4;
514
- const floor = builder.add({ id: "basement_slab", role: "slab", material: "concrete", aabb: box, y0: -depth - 0.15, y1: -depth }, { floor: -1 });
528
+ const floor = builder.add({ id: "basement_slab", role: "slab", material: "concrete", aabb: box2, y0: -depth - 0.15, y1: -depth }, { floor: -1 });
515
529
  builder.rest(floor, GROUND, "resting");
516
530
  const sides = [
517
- ["front", [box[0], box[1] - 0.1, box[2], box[1] + 0.1]],
518
- ["back", [box[0], box[3] - 0.1, box[2], box[3] + 0.1]],
519
- ["left", [box[0] - 0.1, box[1], box[0] + 0.1, box[3]]],
520
- ["right", [box[2] - 0.1, box[1], box[2] + 0.1, box[3]]]
531
+ ["front", [box2[0], box2[1] - 0.1, box2[2], box2[1] + 0.1]],
532
+ ["back", [box2[0], box2[3] - 0.1, box2[2], box2[3] + 0.1]],
533
+ ["left", [box2[0] - 0.1, box2[1], box2[0] + 0.1, box2[3]]],
534
+ ["right", [box2[2] - 0.1, box2[1], box2[2] + 0.1, box2[3]]]
521
535
  ];
522
536
  const carried = [];
523
537
  for (const [name, aabb] of sides) {
@@ -526,10 +540,10 @@ function addBasement(context) {
526
540
  builder.rest(wall, floor, "rebar");
527
541
  carried.push(wall);
528
542
  }
529
- const count = Math.max(1, Math.floor((box[2] - box[0]) / 3));
543
+ const count = Math.max(1, Math.floor((box2[2] - box2[0]) / 3));
530
544
  for (let k = 0; k < count; k++) {
531
- const u = box[0] + (k + 0.5) * (box[2] - box[0]) / count;
532
- const v = (box[1] + box[3]) / 2;
545
+ const u = box2[0] + (k + 0.5) * (box2[2] - box2[0]) / count;
546
+ const v = (box2[1] + box2[3]) / 2;
533
547
  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 });
534
548
  builder.rest(column, floor, "rebar");
535
549
  builder.rest(column, GROUND, "rebar");
@@ -539,26 +553,26 @@ function addBasement(context) {
539
553
  return carried;
540
554
  }
541
555
  function addPiers(context) {
542
- const { builder, houseBox: box, elevation, construction } = context;
543
- const columns = Math.max(2, Math.ceil((box[2] - box[0]) / 2) + 1);
544
- const rows = Math.max(2, Math.ceil((box[3] - box[1]) / 2) + 1);
556
+ const { builder, houseBox: box2, elevation, construction } = context;
557
+ const columns = Math.max(2, Math.ceil((box2[2] - box2[0]) / 2) + 1);
558
+ const rows = Math.max(2, Math.ceil((box2[3] - box2[1]) / 2) + 1);
545
559
  const height = Math.max(0.1, elevation - context.system.slabThickness - 0.15);
546
560
  const piers = [];
547
561
  for (let j = 0; j < rows; j++) {
548
562
  for (let i = 0; i < columns; i++) {
549
- const u = box[0] + i * (box[2] - box[0]) / (columns - 1);
550
- const v = box[1] + j * (box[3] - box[1]) / (rows - 1);
563
+ const u = box2[0] + i * (box2[2] - box2[0]) / (columns - 1);
564
+ const v = box2[1] + j * (box2[3] - box2[1]) / (rows - 1);
551
565
  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 });
552
566
  builder.rest(pier, GROUND, "resting");
553
567
  piers.push(pier);
554
568
  }
555
569
  }
556
- const alongU = box[2] - box[0] >= box[3] - box[1];
570
+ const alongU = box2[2] - box2[0] >= box2[3] - box2[1];
557
571
  const beams = [];
558
572
  const lines = alongU ? rows : columns;
559
573
  for (let line = 0; line < lines; line++) {
560
- const at2 = alongU ? box[1] + line * (box[3] - box[1]) / (rows - 1) : box[0] + line * (box[2] - box[0]) / (columns - 1);
561
- const aabb = alongU ? [box[0], at2 - 0.07, box[2], at2 + 0.07] : [at2 - 0.07, box[1], at2 + 0.07, box[3]];
574
+ const at2 = alongU ? box2[1] + line * (box2[3] - box2[1]) / (rows - 1) : box2[0] + line * (box2[2] - box2[0]) / (columns - 1);
575
+ const aabb = alongU ? [box2[0], at2 - 0.07, box2[2], at2 + 0.07] : [at2 - 0.07, box2[1], at2 + 0.07, box2[3]];
562
576
  const beam = builder.add({ id: `base_beam_${line}`, role: "beam", material: construction.frame.timber, aabb, y0: height, y1: elevation - context.system.slabThickness }, { floor: 0 });
563
577
  const centre = (q) => alongU ? (q.aabb[1] + q.aabb[3]) / 2 : (q.aabb[0] + q.aabb[2]) / 2;
564
578
  for (const pier of piers.filter((q) => Math.abs(centre(q) - at2) < 0.05)) builder.rest(beam, pier, "bolt");
@@ -567,12 +581,12 @@ function addPiers(context) {
567
581
  return beams;
568
582
  }
569
583
  function addRaisedFloor(context) {
570
- const { construction, system, houseBox: box } = context;
584
+ const { construction, system, houseBox: box2 } = context;
571
585
  const carriers = construction.foundation.type === "basement" ? addBasement(context) : addPiers(context);
572
586
  const thickness = system.slabThickness;
573
- const joists = addJoists(context, { floor: 0, box, thickness, carriers, material: construction.frame.timber, joint: construction.foundation.type === "basement" ? "bolt" : "nail" });
587
+ const joists = addJoists(context, { floor: 0, box: box2, thickness, carriers, material: construction.frame.timber, joint: construction.foundation.type === "basement" ? "bolt" : "nail" });
574
588
  const deckMaterial = context.isTimber ? construction.floor.material === "wood_hard" ? "wood_hard" : "plywood" : construction.frame.slabs;
575
- context.base0 = addDeck(context, "slab_0", deckMaterial, { floor: 0, box, thickness, joint: "nail" }, joists);
589
+ context.base0 = addDeck(context, "slab_0", deckMaterial, { floor: 0, box: box2, thickness, joint: "nail" }, joists);
576
590
  }
577
591
  function addFoundation(context) {
578
592
  const { type, material } = context.construction.foundation;
@@ -666,9 +680,9 @@ function addCornerPillars(context, walls, floor, plates) {
666
680
  const { builder } = context;
667
681
  const topOfFloor = context.topOf(floor);
668
682
  return cornerPoints(walls).map(([u, v], k) => {
669
- const box = [u - 0.1, v - 0.1, u + 0.1, v + 0.1];
670
- const top = Math.max(topOfFloor, ...plates.filter((plate) => touches(plate.aabb, box, 0.05)).map((plate) => plate.y0));
671
- const pillar = builder.add({ id: `column_${floor}_${k}`, role: "column", material: context.construction.frame.pillars, aabb: box, y0: context.level(floor), y1: top }, { floor });
683
+ const box2 = [u - 0.1, v - 0.1, u + 0.1, v + 0.1];
684
+ const top = Math.max(topOfFloor, ...plates.filter((plate) => touches(plate.aabb, box2, 0.05)).map((plate) => plate.y0));
685
+ const pillar = builder.add({ id: `column_${floor}_${k}`, role: "column", material: context.construction.frame.pillars, aabb: box2, y0: context.level(floor), y1: top }, { floor });
672
686
  const base = context.baseOf(floor);
673
687
  builder.rest(pillar, base === GROUND || !base ? GROUND : base, "rebar");
674
688
  for (const plate of plates.filter((p) => touches(p.aabb, pillar.aabb, 0.05))) builder.rest(plate, pillar, "rebar");
@@ -793,20 +807,20 @@ function addFlatRoof(context, unit, carriers) {
793
807
  const { builder, construction, system } = context;
794
808
  const top = context.level(unit.floor) + context.ceilingOf(unit.floor);
795
809
  const [u0, v0, u1, v1] = unit.box;
796
- 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 });
810
+ 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] });
797
811
  for (const carrier of carriers.length ? carriers : context.plates.get(unit.floor) ?? []) builder.rest(slab, carrier, system.joints.slab);
798
812
  if (unit.suffix === "") context.topSlab = slab;
799
813
  return { id: slab.id, aabb: slab.aabb, y0: slab.y0, y1: slab.y1, flat: true };
800
814
  }
801
815
  function addTrusses(context, unit, carriers, geometry) {
802
816
  const { builder, construction, system } = context;
803
- const { box } = unit;
817
+ const { box: box2 } = unit;
804
818
  const { axis, length, eave, rise, eaveY } = geometry;
805
819
  const count = Math.max(2, Math.ceil(length / TRUSS_PITCH) + 1);
806
820
  const trusses = [];
807
821
  for (let k = 0; k < count; k++) {
808
- const at2 = (axis === "u" ? box[0] : box[1]) + k * length / (count - 1);
809
- const aabb = axis === "u" ? [at2 - 0.04, box[1] - eave * 0.5, at2 + 0.04, box[3] + eave * 0.5] : [box[0] - eave * 0.5, at2 - 0.04, box[2] + eave * 0.5, at2 + 0.04];
822
+ const at2 = (axis === "u" ? box2[0] : box2[1]) + k * length / (count - 1);
823
+ const aabb = axis === "u" ? [at2 - 0.04, box2[1] - eave * 0.5, at2 + 0.04, box2[3] + eave * 0.5] : [box2[0] - eave * 0.5, at2 - 0.04, box2[2] + eave * 0.5, at2 + 0.04];
810
824
  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 });
811
825
  const touching = carriers.filter((carrier) => touches(aabb, carrier.aabb, 0.2) && carrier.role !== "slab");
812
826
  const chosen = touching.length ? touching : carriers.slice().sort((a, b) => distanceBetween(aabb, a.aabb) - distanceBetween(aabb, b.aabb)).slice(0, 2);
@@ -816,11 +830,11 @@ function addTrusses(context, unit, carriers, geometry) {
816
830
  return trusses;
817
831
  }
818
832
  function geometryOf(context, unit) {
819
- const { box } = unit;
833
+ const { box: box2 } = unit;
820
834
  const roof = context.construction.roof;
821
835
  const platesHere = context.plates.get(unit.floor) ?? [];
822
- const width = box[2] - box[0];
823
- const depth = box[3] - box[1];
836
+ const width = box2[2] - box2[0];
837
+ const depth = box2[3] - box2[1];
824
838
  const axis = roof.axis ?? (width >= depth ? "u" : "v");
825
839
  const span = axis === "u" ? depth : width;
826
840
  const pitch = unit.annex ? Math.min(roof.pitch || DEFAULT_PITCH, 20) : roof.pitch || DEFAULT_PITCH;
@@ -829,14 +843,14 @@ function geometryOf(context, unit) {
829
843
  }
830
844
  function addRafters(context, unit, carriers, geometry) {
831
845
  const { builder, construction, system } = context;
832
- const { box } = unit;
846
+ const { box: box2 } = unit;
833
847
  const { axis, length, eave, rise, eaveY } = geometry;
834
848
  const count = Math.max(2, Math.ceil(length / TRUSS_PITCH) + 1);
835
- const middle = axis === "u" ? (box[1] + box[3]) / 2 : (box[0] + box[2]) / 2;
849
+ const middle = axis === "u" ? (box2[1] + box2[3]) / 2 : (box2[0] + box2[2]) / 2;
836
850
  const frames = { all: [], sideA: [], sideB: [] };
837
851
  for (let k = 0; k < count; k++) {
838
- const at2 = (axis === "u" ? box[0] : box[1]) + k * length / (count - 1);
839
- const halves = axis === "u" ? [["a", [at2 - 0.04, box[1] - eave * 0.5, at2 + 0.04, middle]], ["b", [at2 - 0.04, middle, at2 + 0.04, box[3] + eave * 0.5]]] : [["a", [box[0] - eave * 0.5, at2 - 0.04, middle, at2 + 0.04]], ["b", [middle, at2 - 0.04, box[2] + eave * 0.5, at2 + 0.04]]];
852
+ const at2 = (axis === "u" ? box2[0] : box2[1]) + k * length / (count - 1);
853
+ const halves = axis === "u" ? [["a", [at2 - 0.04, box2[1] - eave * 0.5, at2 + 0.04, middle]], ["b", [at2 - 0.04, middle, at2 + 0.04, box2[3] + eave * 0.5]]] : [["a", [box2[0] - eave * 0.5, at2 - 0.04, middle, at2 + 0.04]], ["b", [middle, at2 - 0.04, box2[2] + eave * 0.5, at2 + 0.04]]];
840
854
  for (const [side, aabb] of halves) {
841
855
  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 });
842
856
  const touching = carriers.filter((carrier) => touches(aabb, carrier.aabb, 0.2) && carrier.role !== "slab");
@@ -848,7 +862,7 @@ function addRafters(context, unit, carriers, geometry) {
848
862
  }
849
863
  }
850
864
  if (!unit.annex) {
851
- const ridgeBox = axis === "u" ? [box[0] - eave, middle - 0.05, box[2] + eave, middle + 0.05] : [middle - 0.05, box[1] - eave, middle + 0.05, box[3] + eave];
865
+ const ridgeBox = axis === "u" ? [box2[0] - eave, middle - 0.05, box2[2] + eave, middle + 0.05] : [middle - 0.05, box2[1] - eave, middle + 0.05, box2[3] + eave];
852
866
  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 });
853
867
  for (const rafter of frames.all) builder.rest(ridge, rafter, system.joints.truss);
854
868
  }
@@ -895,10 +909,10 @@ function addCeilingJoists(context) {
895
909
  const { builder, construction } = context;
896
910
  const bearing = context.roof.bearingFloor;
897
911
  const rects = context.rectsOf(bearing, CLOSED_ONLY);
898
- const box = unionOf(rects.length ? rects : [context.topBox]);
912
+ const box2 = unionOf(rects.length ? rects : [context.topBox]);
899
913
  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))];
900
- const length = box[2] - box[0];
901
- const depth = box[3] - box[1];
914
+ const length = box2[2] - box2[0];
915
+ const depth = box2[3] - box2[1];
902
916
  const alongU = length >= depth;
903
917
  const span = alongU ? length : depth;
904
918
  const count = Math.max(2, Math.round(span / 0.6) + 1);
@@ -906,8 +920,8 @@ function addCeilingJoists(context) {
906
920
  const y0 = carriers.length ? Math.max(...beamTops, context.level(bearing) + 1) : context.topOf(bearing);
907
921
  const joists = [];
908
922
  for (let k = 0; k < count; k++) {
909
- const at2 = (alongU ? box[0] : box[1]) + 0.05 + k * (span - 0.1) / (count - 1);
910
- const aabb = alongU ? [at2 - 0.025, box[1], at2 + 0.025, box[3]] : [box[0], at2 - 0.025, box[2], at2 + 0.025];
923
+ const at2 = (alongU ? box2[0] : box2[1]) + 0.05 + k * (span - 0.1) / (count - 1);
924
+ const aabb = alongU ? [at2 - 0.025, box2[1], at2 + 0.025, box2[3]] : [box2[0], at2 - 0.025, box2[2], at2 + 0.025];
911
925
  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 });
912
926
  const beams = carriers.filter((carrier) => carrier.role === "beam" && touches(aabb, carrier.aabb, 0.2));
913
927
  for (const carrier of beams.length ? beams : carriers.slice(0, 2)) builder.rest(joist, carrier, "nail");
@@ -996,11 +1010,11 @@ function addDoorLintels(context) {
996
1010
  const half = (beside[0].thickness ?? 0.15) / 2;
997
1011
  const lo = from - LINTEL_BEARING;
998
1012
  const hi = to + LINTEL_BEARING;
999
- const box = door.axis === "u" ? [lo, cross - half, hi, cross + half] : [cross - half, lo, cross + half, hi];
1013
+ const box2 = door.axis === "u" ? [lo, cross - half, hi, cross + half] : [cross - half, lo, cross + half, hi];
1000
1014
  const y0 = context.level(door.floor) + DOOR_HEIGHT;
1001
- const lintel = builder.add({ id: `lintel_${door.id}`, role: "beam", material: system.timber ? construction.frame.timber : "concrete", aabb: box, y0, y1: y0 + LINTEL_DEPTH }, { floor: door.floor, ...system.timber ? {} : { assembly: "reinforced_concrete" } });
1015
+ const lintel = builder.add({ id: `lintel_${door.id}`, role: "beam", material: system.timber ? construction.frame.timber : "concrete", aabb: box2, y0, y1: y0 + LINTEL_DEPTH }, { floor: door.floor, ...system.timber ? {} : { assembly: "reinforced_concrete" } });
1002
1016
  for (const wall of beside) builder.rest(lintel, context.wallParts.get(wall.id), system.joints.beam);
1003
- for (const plate of context.plates.get(door.floor) ?? []) if (overlapsInPlan(plate.aabb, box)) builder.rest(plate, lintel, system.joints.beam);
1017
+ for (const plate of context.plates.get(door.floor) ?? []) if (overlapsInPlan(plate.aabb, box2)) builder.rest(plate, lintel, system.joints.beam);
1004
1018
  }
1005
1019
  }
1006
1020
  var overlapsInPlan = (a, b) => a[0] < b[2] && a[2] > b[0] && a[1] < b[3] && a[3] > b[1];
@@ -1057,11 +1071,11 @@ function ensureCeilingSlab(context, y) {
1057
1071
  const existing = builder.find("slab_ceiling");
1058
1072
  if (existing) return existing;
1059
1073
  const top = context.floorCount - 1;
1060
- const box = context.topBox;
1074
+ const box2 = context.topBox;
1061
1075
  const carriers = builder.parts.filter(
1062
- (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(box, part.aabb, 0.1)
1076
+ (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(box2, part.aabb, 0.1)
1063
1077
  );
1064
- const slab = builder.add({ id: "slab_ceiling", role: "slab", material: construction.frame.slabs, aabb: box, y0: y - 0.12, y1: y }, { floor: top });
1078
+ const slab = builder.add({ id: "slab_ceiling", role: "slab", material: construction.frame.slabs, aabb: box2, y0: y - 0.12, y1: y }, { floor: top, footprint: [box2] });
1065
1079
  for (const carrier of carriers) builder.rest(slab, carrier, system.joints.slab);
1066
1080
  return slab;
1067
1081
  }
@@ -1137,16 +1151,16 @@ function addUpperFloor(context, floor) {
1137
1151
  if (floor >= context.floorCount - 1 || context.isUnit) return;
1138
1152
  const next = floor + 1;
1139
1153
  const rects = context.rectsOf(next, ["balcony", "porch", "void"]);
1140
- const box = rects.length ? unionOf(rects) : context.houseBox;
1154
+ const box2 = rects.length ? unionOf(rects) : context.houseBox;
1141
1155
  const carriers = context.carriers.get(floor);
1142
1156
  const { system, construction, builder } = context;
1143
1157
  const thickness = system.slabThickness;
1144
1158
  if (context.isTimber) {
1145
- const joists = addJoists(context, { floor: next, box, thickness, carriers, material: construction.frame.timber, joint: system.joints.beam });
1146
- addDeck(context, `slab_${next}`, construction.frame.slabs, { floor: next, box, thickness, joint: system.joints.slab }, joists);
1159
+ const joists = addJoists(context, { floor: next, box: box2, thickness, carriers, material: construction.frame.timber, joint: system.joints.beam });
1160
+ addDeck(context, `slab_${next}`, construction.frame.slabs, { floor: next, box: box2, thickness, joint: system.joints.slab }, joists);
1147
1161
  return;
1148
1162
  }
1149
- const slab = builder.add({ id: `slab_${next}`, role: "slab", material: construction.frame.slabs, aabb: box, y0: context.level(next) - thickness, y1: context.level(next) }, { floor: next });
1163
+ const slab = builder.add({ id: `slab_${next}`, role: "slab", material: construction.frame.slabs, aabb: box2, y0: context.level(next) - thickness, y1: context.level(next) }, { floor: next, footprint: rects.length ? rects : [context.houseBox] });
1150
1164
  const below = carriers.filter((carrier) => carrier.y0 <= slab.y0 + 0.05);
1151
1165
  const touching = below.filter((carrier) => touches(slab.aabb, carrier.aabb, 0.2));
1152
1166
  for (const carrier of touching.length ? touching : below) builder.rest(slab, carrier, system.joints.slab);
@@ -1704,19 +1718,19 @@ function inNiche(context, stove, room, doorZones) {
1704
1718
  return { mode: "niche", x: cx, z: cz, rect, outdoor: true };
1705
1719
  }
1706
1720
  function underCounter(context, stove) {
1707
- const distance = (m) => Math.hypot(m.x - stove.x, m.z - stove.z);
1721
+ const distance2 = (m) => Math.hypot(m.x - stove.x, m.z - stove.z);
1708
1722
  const cabinets = context.items.filter(
1709
1723
  (m) => m.floor === stove.floor && m.room === stove.room && !m.outdoor && !m.on && !m.flat && ["kitchen_cabinet", "workbench"].includes(m.category) && m.aabb && m.id !== stove.id && Math.min(m.aabb[2] - m.aabb[0], m.aabb[3] - m.aabb[1]) >= CYLINDER.d - 0.011 && Math.max(m.aabb[2] - m.aabb[0], m.aabb[3] - m.aabb[1]) >= CYLINDER.w + 0.02
1710
- ).sort((a, b) => (a.category === "kitchen_cabinet" ? 0 : 1) - (b.category === "kitchen_cabinet" ? 0 : 1) || distance(a) - distance(b));
1724
+ ).sort((a, b) => (a.category === "kitchen_cabinet" ? 0 : 1) - (b.category === "kitchen_cabinet" ? 0 : 1) || distance2(a) - distance2(b));
1711
1725
  for (const cabinet of cabinets) {
1712
- if (distance(cabinet) > 3) continue;
1713
- const box = cabinet.aabb;
1726
+ if (distance2(cabinet) > 3) continue;
1727
+ const box2 = cabinet.aabb;
1714
1728
  const hw = CYLINDER.w / 2;
1715
1729
  const hd = CYLINDER.d / 2;
1716
- const cx = clamp(stove.x, box[0] + hw + 0.01, box[2] - hw - 0.01);
1717
- const cz = clamp(stove.z, box[1] + hd + 0.01, box[3] - hd - 0.01);
1730
+ const cx = clamp(stove.x, box2[0] + hw + 0.01, box2[2] - hw - 0.01);
1731
+ const cz = clamp(stove.z, box2[1] + hd + 0.01, box2[3] - hd - 0.01);
1718
1732
  const rect = [cx - hw, cz - hd, cx + hw, cz + hd].map(round22);
1719
- if (rect[0] < box[0] || rect[2] > box[2] || rect[1] < box[1] || rect[3] > box[3] || overlaps(rect, stove.aabb, 0.011)) continue;
1733
+ if (rect[0] < box2[0] || rect[2] > box2[2] || rect[1] < box2[1] || rect[3] > box2[3] || overlaps(rect, stove.aabb, 0.011)) continue;
1720
1734
  return { mode: "under", x: cx, z: cz, rect, cabinet: cabinet.id };
1721
1735
  }
1722
1736
  return void 0;
@@ -1873,16 +1887,16 @@ function planSolar(context) {
1873
1887
  const tops = plan.rooms.filter((c) => c.floor === floor && !NO_PANELS_UNDER.includes(c.type));
1874
1888
  if (!tops.length) return null;
1875
1889
  const parts = tops.flatMap((c) => c.parts);
1876
- const box = [Math.min(...parts.map((q) => q[0])), Math.min(...parts.map((q) => q[1])), Math.max(...parts.map((q) => q[2])), Math.max(...parts.map((q) => q[3]))];
1890
+ const box2 = [Math.min(...parts.map((q) => q[0])), Math.min(...parts.map((q) => q[1])), Math.max(...parts.map((q) => q[2])), Math.max(...parts.map((q) => q[3]))];
1877
1891
  const north = rng() < 0.5 ? "front" : "back";
1878
1892
  const angle = flat ? 15 : 25;
1879
1893
  const projection = round22(PANEL_LENGTH * Math.cos(angle * Math.PI / 180));
1880
1894
  const margin = 0.6;
1881
- const uStart = box[0] + margin;
1882
- const uEnd = box[2] - margin;
1895
+ const uStart = box2[0] + margin;
1896
+ const uEnd = box2[2] - margin;
1883
1897
  const columns = Math.max(1, Math.floor((uEnd - uStart) / (PANEL_WIDTH + 0.05)));
1884
- const vMiddle = (box[1] + box[3]) / 2;
1885
- const vArea = flat ? [box[1] + margin, box[3] - margin] : north === "front" ? [box[1] + 0.4, vMiddle - 0.2] : [vMiddle + 0.2, box[3] - 0.4];
1898
+ const vMiddle = (box2[1] + box2[3]) / 2;
1899
+ const vArea = flat ? [box2[1] + margin, box2[3] - margin] : north === "front" ? [box2[1] + 0.4, vMiddle - 0.2] : [vMiddle + 0.2, box2[3] - 0.4];
1886
1900
  const step = flat ? projection + 0.5 : projection + 0.05;
1887
1901
  const rowsMax = Math.max(1, Math.floor((vArea[1] - vArea[0]) / step));
1888
1902
  const wanted = forced !== null ? 1 + Math.floor(rng() * 2) : 3 + Math.floor(rng() * (plan.style === "high" ? 12 : 8));
@@ -1954,13 +1968,13 @@ function opening(frame, id, a, b, vector, width, extra) {
1954
1968
  const alongU = Math.abs(vector[0]) !== 1;
1955
1969
  return { id, axis: alongU ? "u" : "v", pos: round22(alongU ? v : u), at: round22(alongU ? u : v), u: round22(u), v: round22(v), width: round22(width), side: sideOf(vector[0], vector[1]), ...extra };
1956
1970
  }
1957
- function wallsOf(box, door, height) {
1971
+ function wallsOf(box2, door, height) {
1958
1972
  const walls = [];
1959
1973
  const sides = [
1960
- { axis: "u", pos: box[1], i0: box[0], i1: box[2], side: "front" },
1961
- { axis: "u", pos: box[3], i0: box[0], i1: box[2], side: "back" },
1962
- { axis: "v", pos: box[0], i0: box[1], i1: box[3], side: "left" },
1963
- { axis: "v", pos: box[2], i0: box[1], i1: box[3], side: "right" }
1974
+ { axis: "u", pos: box2[1], i0: box2[0], i1: box2[2], side: "front" },
1975
+ { axis: "u", pos: box2[3], i0: box2[0], i1: box2[2], side: "back" },
1976
+ { axis: "v", pos: box2[0], i0: box2[1], i1: box2[3], side: "left" },
1977
+ { axis: "v", pos: box2[2], i0: box2[1], i1: box2[3], side: "right" }
1964
1978
  ];
1965
1979
  for (const w of sides) {
1966
1980
  const cut = w.axis === door.axis && Math.abs(w.pos - door.pos) < 0.02 ? [door.at - door.width / 2, door.at + door.width / 2] : null;
@@ -1981,9 +1995,9 @@ function furnish(context, frame, use, interior, insideDoor) {
1981
1995
  const [u, v] = at(frame, a, b);
1982
1996
  const eu = Math.abs(facing[0]) === 1 ? def.d : def.w;
1983
1997
  const ev = Math.abs(facing[0]) === 1 ? def.w : def.d;
1984
- const box = [u - eu / 2, v - ev / 2, u + eu / 2, v + ev / 2].map(round22);
1985
- if (box[0] < interior[0] - 0.011 || box[2] > interior[2] + 0.011 || box[1] < interior[1] - 0.011 || box[3] > interior[3] + 0.011 || overlaps(box, insideDoor) || placed.some((o) => overlaps(box, o.aabb))) return;
1986
- placed.push({ type, category: def.category, u: round22(u), v: round22(v), w: def.w, d: def.d, h: def.h, ry: Math.atan2(facing[0], facing[1]), frontU: facing[0], frontV: facing[1], aabb: box });
1998
+ const box2 = [u - eu / 2, v - ev / 2, u + eu / 2, v + ev / 2].map(round22);
1999
+ if (box2[0] < interior[0] - 0.011 || box2[2] > interior[2] + 0.011 || box2[1] < interior[1] - 0.011 || box2[3] > interior[3] + 0.011 || overlaps(box2, insideDoor) || placed.some((o) => overlaps(box2, o.aabb))) return;
2000
+ placed.push({ type, category: def.category, u: round22(u), v: round22(v), w: def.w, d: def.d, h: def.h, ry: Math.atan2(facing[0], facing[1]), frontU: facing[0], frontV: facing[1], aabb: box2 });
1987
2001
  });
1988
2002
  return placed;
1989
2003
  }
@@ -2050,8 +2064,8 @@ function addOutbuildingPaths(lot, outbuildings, items) {
2050
2064
  if (Math.abs(T[0] - S[0]) < 1e-6 && Math.abs(T[1] - S[1]) < 1e-6) candidates.push({ rects: [stub], points: [A, S] });
2051
2065
  else for (const via of [[T[0], S[1]], [S[0], T[1]]]) candidates.push({ rects: [stub, strip(S, via), strip(via, T)].filter((q) => q[2] - q[0] > 1e-6), points: [A, S, via, T] });
2052
2066
  for (const k of candidates) {
2053
- const distance = k.points.slice(1).reduce((sum, q, i) => sum + Math.abs(q[0] - k.points[i][0]) + Math.abs(q[1] - k.points[i][1]), 0);
2054
- const value = cost(k.rects.slice(1)) * 100 + distance;
2067
+ const distance2 = k.points.slice(1).reduce((sum, q, i) => sum + Math.abs(q[0] - k.points[i][0]) + Math.abs(q[1] - k.points[i][1]), 0);
2068
+ const value = cost(k.rects.slice(1)) * 100 + distance2;
2055
2069
  if (!best || value < best.v) best = { v: value, k, from: c.from };
2056
2070
  }
2057
2071
  }
@@ -2279,7 +2293,7 @@ function register(engine, options = {}) {
2279
2293
  }
2280
2294
 
2281
2295
  // src/to-destructible-spec.ts
2282
- import { assemblyPart } from "@adatechnology/game-destruction";
2296
+ import { GRAVITY, MATERIALS, SOILS, assemblyPart, partMass, pileLength, profileOf, sizeRebar } from "@adatechnology/game-destruction";
2283
2297
 
2284
2298
  // src/loads.ts
2285
2299
  function loadsAtRest(parts, supports, without) {
@@ -2299,14 +2313,14 @@ function loadsAtRest(parts, supports, without) {
2299
2313
  }
2300
2314
  const partition = (id) => byId.get(id)?.role === "partition";
2301
2315
  const structural = (id) => byId.has(id) && byId.get(id).role !== "none";
2302
- const distance = /* @__PURE__ */ new Map([[GROUND, 0]]);
2316
+ const distance2 = /* @__PURE__ */ new Map([[GROUND, 0]]);
2303
2317
  const queue = [GROUND];
2304
2318
  for (let head = 0; head < queue.length; head++) {
2305
2319
  const current = queue[head];
2306
2320
  if (current !== GROUND && partition(current)) continue;
2307
2321
  for (const above of carrying.get(current) ?? []) {
2308
- if (distance.has(above) || !structural(above)) continue;
2309
- distance.set(above, distance.get(current) + 1);
2322
+ if (distance2.has(above) || !structural(above)) continue;
2323
+ distance2.set(above, distance2.get(current) + 1);
2310
2324
  queue.push(above);
2311
2325
  }
2312
2326
  }
@@ -2316,7 +2330,7 @@ function loadsAtRest(parts, supports, without) {
2316
2330
  const id = queue[head];
2317
2331
  const total = (byId.get(id).mass || 1) + (incoming.get(id) ?? 0);
2318
2332
  loads.set(id, total);
2319
- const below = (resting.get(id) ?? []).filter((other) => distance.has(other) && distance.get(other) < distance.get(id) && !(other !== GROUND && partition(other)));
2333
+ const below = (resting.get(id) ?? []).filter((other) => distance2.has(other) && distance2.get(other) < distance2.get(id) && !(other !== GROUND && partition(other)));
2320
2334
  for (const other of below) incoming.set(other, (incoming.get(other) ?? 0) + total / below.length);
2321
2335
  }
2322
2336
  return loads;
@@ -2341,6 +2355,533 @@ function sizeCapacities(parts, supports, options) {
2341
2355
  return capacities;
2342
2356
  }
2343
2357
 
2358
+ // src/slab-panels/layout.ts
2359
+ var EPS = 1e-6;
2360
+ function linesOf(boxes) {
2361
+ return boxes.map(([u0, v0, u1, v1]) => u1 - u0 >= v1 - v0 ? { dir: "u", at: (v0 + v1) / 2, from: u0, to: u1 } : { dir: "v", at: (u0 + u1) / 2, from: v0, to: v1 });
2362
+ }
2363
+ function coverage(lines, dir, at2, from, to, options) {
2364
+ const spans = lines.filter((line) => line.dir === dir && Math.abs(line.at - at2) <= options.lineTolerance).map((line) => [Math.max(from, line.from), Math.min(to, line.to)]).filter(([a, b]) => b > a).sort((x, y) => x[0] - y[0]);
2365
+ let covered = 0;
2366
+ let end = from;
2367
+ let start = NaN;
2368
+ for (const [a, b] of spans) {
2369
+ if (Number.isNaN(start)) {
2370
+ start = a;
2371
+ end = b;
2372
+ } else if (a - end <= options.doorGap) end = Math.max(end, b);
2373
+ else {
2374
+ covered += end - start;
2375
+ start = a;
2376
+ end = b;
2377
+ }
2378
+ }
2379
+ if (!Number.isNaN(start)) covered += end - start;
2380
+ return to > from ? covered / (to - from) : 0;
2381
+ }
2382
+ function positions(lines, dir, tolerance) {
2383
+ const sorted = lines.filter((line) => line.dir === dir).map((line) => line.at).sort((a, b) => a - b);
2384
+ const out = [];
2385
+ let group = [];
2386
+ for (const at2 of sorted) {
2387
+ if (group.length && at2 - group[group.length - 1] > tolerance) {
2388
+ out.push(group.reduce((s, x) => s + x, 0) / group.length);
2389
+ group = [];
2390
+ }
2391
+ group.push(at2);
2392
+ }
2393
+ if (group.length) out.push(group.reduce((s, x) => s + x, 0) / group.length);
2394
+ return out;
2395
+ }
2396
+ var round = (x) => Math.round(x * 100) / 100;
2397
+ function choose(rect, lines, options) {
2398
+ const [u0, v0, u1, v1] = rect;
2399
+ const width = u1 - u0;
2400
+ const depth = v1 - v0;
2401
+ const tooWide = width > options.maxSpan + EPS;
2402
+ const tooDeep = depth > options.maxSpan + EPS;
2403
+ const candidates = [];
2404
+ for (const at2 of positions(lines, "v", options.lineTolerance)) {
2405
+ if (at2 < u0 + options.minPanel - EPS || at2 > u1 - options.minPanel + EPS) continue;
2406
+ candidates.push({ dir: "v", at: at2, coverage: coverage(lines, "v", at2, v0, v1, options), skew: Math.abs(at2 - (u0 + u1) / 2) / (width / 2) });
2407
+ }
2408
+ for (const at2 of positions(lines, "u", options.lineTolerance)) {
2409
+ if (at2 < v0 + options.minPanel - EPS || at2 > v1 - options.minPanel + EPS) continue;
2410
+ candidates.push({ dir: "u", at: at2, coverage: coverage(lines, "u", at2, u0, u1, options), skew: Math.abs(at2 - (v0 + v1) / 2) / (depth / 2) });
2411
+ }
2412
+ const boundary = candidates.filter((c) => c.coverage >= options.lineCoverage);
2413
+ if (tooWide || tooDeep) {
2414
+ const across = tooWide && (!tooDeep || width >= depth) ? "v" : "u";
2415
+ const pool = boundary.filter((c) => c.dir === across);
2416
+ if (pool.length) {
2417
+ const best2 = pool.reduce((a, b) => b.skew - 0.25 * b.coverage < a.skew - 0.25 * a.coverage ? b : a);
2418
+ return { dir: best2.dir, at: round(best2.at) };
2419
+ }
2420
+ const length = across === "v" ? width : depth;
2421
+ const origin = across === "v" ? u0 : v0;
2422
+ return { dir: across, at: round(origin + length / Math.ceil(length / options.maxSpan - EPS)) };
2423
+ }
2424
+ if (!boundary.length) return null;
2425
+ const best = boundary.reduce((a, b) => b.coverage - 0.25 * b.skew > a.coverage - 0.25 * a.skew ? b : a);
2426
+ return { dir: best.dir, at: round(best.at) };
2427
+ }
2428
+ function layoutPanels(rect, lines, options) {
2429
+ const leaves = [];
2430
+ const cuts = [];
2431
+ const walk = (r, depth) => {
2432
+ const cut = depth < 16 ? choose(r, lines, options) : null;
2433
+ if (!cut) {
2434
+ leaves.push(r);
2435
+ return;
2436
+ }
2437
+ if (cut.dir === "v") {
2438
+ cuts.push({ dir: "v", at: cut.at, from: r[1], to: r[3] });
2439
+ walk([r[0], r[1], cut.at, r[3]], depth + 1);
2440
+ walk([cut.at, r[1], r[2], r[3]], depth + 1);
2441
+ } else {
2442
+ cuts.push({ dir: "u", at: cut.at, from: r[0], to: r[2] });
2443
+ walk([r[0], r[1], r[2], cut.at], depth + 1);
2444
+ walk([r[0], cut.at, r[2], r[3]], depth + 1);
2445
+ }
2446
+ };
2447
+ walk(rect, 0);
2448
+ return { leaves, cuts };
2449
+ }
2450
+ var areaOf = (rect) => Math.max(0, rect[2] - rect[0]) * Math.max(0, rect[3] - rect[1]);
2451
+ function overlapArea(a, b) {
2452
+ return Math.max(0, Math.min(a[2], b[2]) - Math.max(a[0], b[0])) * Math.max(0, Math.min(a[3], b[3]) - Math.max(a[1], b[1]));
2453
+ }
2454
+ function shareInside(rect, footprint) {
2455
+ const area = areaOf(rect);
2456
+ return area > 0 ? Math.min(1, footprint.reduce((sum, other) => sum + overlapArea(rect, other), 0) / area) : 0;
2457
+ }
2458
+ function runsOf2(cut, panels, tolerance = 0.02) {
2459
+ const spans = [];
2460
+ for (const [u0, v0, u1, v1] of panels) {
2461
+ if (cut.dir === "v") {
2462
+ if (Math.abs(u0 - cut.at) < tolerance || Math.abs(u1 - cut.at) < tolerance) spans.push([Math.max(v0, cut.from), Math.min(v1, cut.to)]);
2463
+ } else if (Math.abs(v0 - cut.at) < tolerance || Math.abs(v1 - cut.at) < tolerance) spans.push([Math.max(u0, cut.from), Math.min(u1, cut.to)]);
2464
+ }
2465
+ const sorted = spans.filter(([a, b]) => b - a > tolerance).sort((x, y) => x[0] - y[0]);
2466
+ const runs = [];
2467
+ for (const [a, b] of sorted) {
2468
+ const last = runs[runs.length - 1];
2469
+ if (last && a <= last[1] + tolerance) last[1] = Math.max(last[1], b);
2470
+ else runs.push([a, b]);
2471
+ }
2472
+ return runs;
2473
+ }
2474
+
2475
+ // src/slab-panels/apply.ts
2476
+ var SLAB_ID = /^slab_(\d+|top.*|ceiling|base)$/;
2477
+ var PLAN_TOLERANCE = 0.05;
2478
+ var BELOW = 0.35;
2479
+ var UP = 0.15;
2480
+ var END_REACH = 0.35;
2481
+ var EPS2 = 1e-6;
2482
+ var ceilTo = (value, step) => Math.ceil(value / step - 1e-9) * step;
2483
+ var isConcreteSlab = (part, material) => part.role === "slab" && part.material === material && SLAB_ID.test(part.id);
2484
+ var isBondBeam = (part, material) => part.role === "beam" && part.beamKind === "bond_beam" && part.material === material;
2485
+ function merged(intervals) {
2486
+ const out = [];
2487
+ for (const [a, b] of intervals.slice().sort((x, y) => x[0] - y[0])) {
2488
+ const last = out[out.length - 1];
2489
+ if (last && a <= last[1] + EPS2) last[1] = Math.max(last[1], b);
2490
+ else out.push([a, b]);
2491
+ }
2492
+ return out;
2493
+ }
2494
+ function beamSpan(length, supports) {
2495
+ const held = merged(supports.map(([a, b]) => [Math.max(0, a), Math.min(length, b)]).filter(([a, b]) => b > a));
2496
+ if (!held.length) return { span: Infinity, at: length / 2, lo: 0, hi: length };
2497
+ let span = 0;
2498
+ let at2 = length / 2;
2499
+ let lo = 0;
2500
+ let hi = length;
2501
+ const lead = held[0][0];
2502
+ if (2 * lead > span) {
2503
+ span = 2 * lead;
2504
+ at2 = 0;
2505
+ lo = 0;
2506
+ hi = lead;
2507
+ }
2508
+ const trail = length - held[held.length - 1][1];
2509
+ if (2 * trail > span) {
2510
+ span = 2 * trail;
2511
+ at2 = length;
2512
+ lo = length - trail;
2513
+ hi = length;
2514
+ }
2515
+ for (let k = 1; k < held.length; k++) {
2516
+ const gap = held[k][0] - held[k - 1][1];
2517
+ if (gap > span) {
2518
+ span = gap;
2519
+ at2 = (held[k][0] + held[k - 1][1]) / 2;
2520
+ lo = held[k - 1][1];
2521
+ hi = held[k][0];
2522
+ }
2523
+ }
2524
+ return { span, at: at2, lo, hi };
2525
+ }
2526
+ var lineOf = (aabb, dir) => dir === "u" ? { dir, at: (aabb[1] + aabb[3]) / 2, from: aabb[0], to: aabb[2] } : { dir, at: (aabb[0] + aabb[2]) / 2, from: aabb[1], to: aabb[3] };
2527
+ var pointOf = (line, s) => line.dir === "u" ? [line.from + s, line.at] : [line.at, line.from + s];
2528
+ function holds(line, width, box2) {
2529
+ const [across0, across1, along0, along1] = line.dir === "u" ? [box2[1], box2[3], box2[0], box2[2]] : [box2[0], box2[2], box2[1], box2[3]];
2530
+ if (across0 >= line.at + width / 2 + PLAN_TOLERANCE || across1 <= line.at - width / 2 - PLAN_TOLERANCE) return void 0;
2531
+ const a = Math.max(along0 - PLAN_TOLERANCE, line.from);
2532
+ const b = Math.min(along1 + PLAN_TOLERANCE, line.to);
2533
+ return b > a ? [a - line.from, b - line.from] : void 0;
2534
+ }
2535
+ var boxOfSpot = (spot, side) => [spot.u - side / 2, spot.v - side / 2, spot.u + side / 2, spot.v + side / 2];
2536
+ function distancesOf(context) {
2537
+ const cache = context.distCache;
2538
+ if (cache && cache.parts === context.parts.length && cache.supports === context.supports.length) return cache.map;
2539
+ const map = distances(context.parts, context.supports);
2540
+ context.distCache = { parts: context.parts.length, supports: context.supports.length, map };
2541
+ return map;
2542
+ }
2543
+ function distances(parts, supports) {
2544
+ const byId = new Map(parts.map((p) => [p.id, p]));
2545
+ const carrying = /* @__PURE__ */ new Map();
2546
+ for (const s of supports) {
2547
+ if (s.bears === false || !byId.has(s.part) || s.on !== GROUND && !byId.has(s.on)) continue;
2548
+ const list = carrying.get(s.on);
2549
+ if (list) list.push(s.part);
2550
+ else carrying.set(s.on, [s.part]);
2551
+ }
2552
+ const dist = /* @__PURE__ */ new Map([[GROUND, 0]]);
2553
+ const queue = [GROUND];
2554
+ for (let head = 0; head < queue.length; head++) {
2555
+ const current = queue[head];
2556
+ if (current !== GROUND && byId.get(current)?.role === "partition") continue;
2557
+ for (const above of carrying.get(current) ?? []) {
2558
+ if (dist.has(above) || byId.get(above)?.role === "none") continue;
2559
+ dist.set(above, dist.get(current) + 1);
2560
+ queue.push(above);
2561
+ }
2562
+ }
2563
+ return dist;
2564
+ }
2565
+ function propsUnder(context, dist, y0, y1, floor) {
2566
+ return context.parts.filter((q) => (q.role === "column" || q.role === "wall") && q.floor === floor && dist.has(q.id) && q.y0 < y0 - PLAN_TOLERANCE && q.y1 >= y0 - BELOW && q.y1 <= y1 + UP).map((q) => ({ box: q.aabb, dist: dist.get(q.id), part: q }));
2567
+ }
2568
+ function heldBy(line, width, props) {
2569
+ const touching = props.map((prop) => ({ prop, interval: holds(line, width, prop.box) })).filter((x) => x.interval !== void 0);
2570
+ if (!touching.length) return { intervals: [], used: [] };
2571
+ const nearest = Math.min(...touching.map((x) => x.prop.dist));
2572
+ const used = touching.filter((x) => x.prop.dist === nearest);
2573
+ return { intervals: used.map((x) => x.interval), used: used.map((x) => x.prop) };
2574
+ }
2575
+ function pillarsFor(line, width, props, spots, dist, options, ends, ground = []) {
2576
+ const length = line.to - line.from;
2577
+ const side = options.columnSide;
2578
+ const all = () => [...props, ...spots.map((spot) => ({ box: boxOfSpot(spot, side), dist: spot.dist }))];
2579
+ const allowed = merged(ground.map((box2) => holds(line, 0.02, box2)).filter((x) => x !== void 0));
2580
+ const place = (at2, lo, hi) => {
2581
+ if (!allowed.length) return at2;
2582
+ let best;
2583
+ let gap = Infinity;
2584
+ for (const [a, b] of allowed) {
2585
+ const from = Math.max(a + side / 2, lo + 0.2);
2586
+ const to = Math.min(b - side / 2, hi - 0.2);
2587
+ if (to < from) continue;
2588
+ const clamped = Math.min(to, Math.max(from, at2));
2589
+ if (Math.abs(clamped - at2) < gap) {
2590
+ gap = Math.abs(clamped - at2);
2591
+ best = clamped;
2592
+ }
2593
+ }
2594
+ return best ?? at2;
2595
+ };
2596
+ let held = heldBy(line, width, all()).intervals;
2597
+ for (let guard = 0; guard < 64; guard++) {
2598
+ let at2;
2599
+ let lo = 0;
2600
+ let hi = length;
2601
+ if (ends && !held.some(([a]) => a <= END_REACH)) at2 = 0;
2602
+ else if (ends && !held.some(([, b]) => b >= length - END_REACH)) at2 = length;
2603
+ else {
2604
+ const found = beamSpan(length, held);
2605
+ if (found.span <= options.maxSpan + 1e-3) break;
2606
+ at2 = found.at;
2607
+ lo = found.lo;
2608
+ hi = found.hi;
2609
+ }
2610
+ const [u, v] = pointOf(line, at2 === 0 || at2 === length ? at2 : place(at2, lo, hi));
2611
+ const spot = { u: round2(u), v: round2(v), dist };
2612
+ if (spots.some((other) => Math.hypot(other.u - spot.u, other.v - spot.v) < 0.15)) break;
2613
+ spots.push(spot);
2614
+ held = heldBy(line, width, all()).intervals;
2615
+ }
2616
+ return held;
2617
+ }
2618
+ var make = (draft) => {
2619
+ const [u0, v0, u1, v1] = draft.aabb;
2620
+ const volume = Math.max(0, (u1 - u0) * (v1 - v0) * (draft.y1 - draft.y0));
2621
+ return { ...draft, aabb: draft.aabb.map(round2), y0: round2(draft.y0), y1: round2(draft.y1), mass: Math.max(1, Math.round(volume * densityOf(draft.material))) };
2622
+ };
2623
+ var rest = (context, on, below, joint = context.joint) => {
2624
+ context.supports.push({ part: below.id, on: typeof on === "string" ? on : on.id, joint });
2625
+ };
2626
+ var distance = (a, b) => Math.hypot(Math.max(a[0] - b[2], b[0] - a[2], 0), Math.max(a[1] - b[3], b[1] - a[3], 0));
2627
+ function panelsUnder(context, slab, box2) {
2628
+ const panels = context.panelsOf.get(slab) ?? [];
2629
+ const touching = panels.filter((panel) => touches(panel.aabb, box2, PLAN_TOLERANCE));
2630
+ return touching.length ? touching : panels.slice().sort((a, b) => distance(a.aabb, box2) - distance(b.aabb, box2)).slice(0, 1);
2631
+ }
2632
+ function addPillar(context, spot, bottom, top, floor, below, slab) {
2633
+ const pillar = make({
2634
+ id: `pillar_${floor}_${context.pillars++}`,
2635
+ role: "column",
2636
+ material: context.pillarMaterial,
2637
+ floor,
2638
+ slab,
2639
+ aabb: boxOfSpot(spot, context.options.columnSide),
2640
+ y0: bottom,
2641
+ y1: top
2642
+ });
2643
+ context.parts.push(pillar);
2644
+ if (below && context.panelsOf.has(below)) for (const panel of panelsUnder(context, below, pillar.aabb)) rest(context, panel, pillar, "rebar");
2645
+ else rest(context, context.groundBase, pillar, "rebar");
2646
+ return pillar;
2647
+ }
2648
+ function storeyUnder(context, slab) {
2649
+ const levels = context.structure.levels;
2650
+ const match = /^slab_(\d+)$/.exec(slab.id);
2651
+ const floor = match && Number(match[1]) >= 1 ? Number(match[1]) - 1 : slab.id === "slab_base" ? 0 : slab.floor;
2652
+ return { floor, below: slab.id === "slab_base" || floor < 1 ? void 0 : `slab_${floor}`, bottom: slab.id === "slab_base" ? 0 : levels[floor] ?? 0 };
2653
+ }
2654
+ function pillarDist(context, dist, storey) {
2655
+ if (storey.below && context.panelsOf.has(storey.below)) return Math.min(...context.panelsOf.get(storey.below).map((p) => dist.get(p.id) ?? 99)) + 1;
2656
+ return (dist.get(context.groundBase) ?? 0) + 1;
2657
+ }
2658
+ function groundOf(context, below) {
2659
+ const panels = context.panelsOf.get(below) ?? [];
2660
+ const level = panels[0]?.y1;
2661
+ if (level === void 0) return [];
2662
+ return context.parts.filter((q) => q.role === "beam" && (q.slab === below || q.beamKind === "bond_beam" && Math.abs(q.y1 - level) < 0.4)).map((q) => q.aabb);
2663
+ }
2664
+ function thicknessOf(leaves, roof, options) {
2665
+ const shortest = Math.max(...leaves.map((leaf) => Math.min(leaf[2] - leaf[0], leaf[3] - leaf[1])));
2666
+ return Math.max(roof ? options.roofMinThickness : options.floorMinThickness, ceilTo(shortest / options.thicknessRatio, 0.01));
2667
+ }
2668
+ var restThickness = (slab, thickness, roof) => {
2669
+ if (roof) slab.y1 = round2(slab.y0 + thickness);
2670
+ else slab.y0 = round2(slab.y1 - thickness);
2671
+ slab.mass = Math.max(1, Math.round((slab.aabb[2] - slab.aabb[0]) * (slab.aabb[3] - slab.aabb[1]) * (slab.y1 - slab.y0) * densityOf(slab.material)));
2672
+ };
2673
+ function cutSlab(context, slab, wallBoxes) {
2674
+ const { options } = context;
2675
+ const roof = slab.cover === true;
2676
+ const layout = layoutPanels(slab.aabb, linesOf(wallBoxes), options);
2677
+ const footprint = slab.footprint ?? [slab.aabb];
2678
+ const kept = layout.leaves.filter((leaf) => shareInside(leaf, footprint) >= options.footprintShare);
2679
+ const leaves = kept.length ? kept : [slab.aabb];
2680
+ const thickness = thicknessOf(leaves, roof, options);
2681
+ if (leaves.length === 1 && layout.cuts.length === 0) {
2682
+ restThickness(slab, thickness, roof);
2683
+ context.panelsOf.set(slab.id, [slab]);
2684
+ return;
2685
+ }
2686
+ const own = context.supports.filter((s) => s.part === slab.id && s.bears !== false);
2687
+ const resting = context.supports.filter((s) => s.on === slab.id);
2688
+ context.supports = context.supports.filter((s) => s.part !== slab.id && s.on !== slab.id);
2689
+ const [y0, y1] = roof ? [slab.y0, slab.y0 + thickness] : [slab.y1 - thickness, slab.y1];
2690
+ const panels = leaves.map(
2691
+ (leaf, k) => make({
2692
+ id: `${slab.id}_p${k}`,
2693
+ role: "slab",
2694
+ material: slab.material,
2695
+ aabb: leaf,
2696
+ y0,
2697
+ y1,
2698
+ floor: slab.floor,
2699
+ ...slab.cover ? { cover: true } : {},
2700
+ slab: slab.id,
2701
+ ...slab.footprint ? { footprint: slab.footprint } : {}
2702
+ })
2703
+ );
2704
+ context.parts.splice(context.parts.indexOf(slab), 1, ...panels);
2705
+ context.panelsOf.set(slab.id, panels);
2706
+ const top = roof ? slab.y0 : y0;
2707
+ const dist = distancesOf(context);
2708
+ const storey = storeyUnder(context, slab);
2709
+ const props = propsUnder(context, dist, top, slab.y1, storey.floor);
2710
+ const near = props.length ? Math.min(...props.map((p) => p.dist)) : pillarDist(context, dist, storey);
2711
+ const spots = [];
2712
+ const bonds = context.parts.filter((q) => q.role === "beam" && q.beamKind === "bond_beam" && q.y1 >= top - BELOW && q.y0 < slab.y1);
2713
+ const ground = storey.below ? groundOf(context, storey.below) : [];
2714
+ const covered = (line) => {
2715
+ const width = context.wallThickness;
2716
+ const spans = merged(bonds.map((bond) => holds(line, width + 0.5, bond.aabb)).filter((x) => x !== void 0));
2717
+ return spans.reduce((sum, [a, b]) => sum + Math.max(0, Math.min(b, line.to - line.from) - Math.max(a, 0)), 0) >= (line.to - line.from) * 0.9;
2718
+ };
2719
+ const [su0, sv0, su1, sv1] = slab.aabb;
2720
+ const borders = [
2721
+ { dir: "u", at: sv0, from: su0, to: su1 },
2722
+ { dir: "u", at: sv1, from: su0, to: su1 },
2723
+ { dir: "v", at: su0, from: sv0, to: sv1 },
2724
+ { dir: "v", at: su1, from: sv0, to: sv1 }
2725
+ ];
2726
+ const beams = [];
2727
+ let count = 0;
2728
+ for (const cut of [...layout.cuts, ...borders]) {
2729
+ for (const [from, to] of runsOf2(cut, leaves)) {
2730
+ const line = { dir: cut.dir === "v" ? "v" : "u", at: cut.at, from, to };
2731
+ if (covered(line)) continue;
2732
+ const held = pillarsFor(line, context.wallThickness, props, spots, near, options, true, ground);
2733
+ const span = Math.min(beamSpan(to - from, held).span, options.maxSpan);
2734
+ const depth = beamDepthFor(span, options);
2735
+ const width = Math.max(context.wallThickness, round2(depth / options.beamWidthRatio));
2736
+ const aabb = line.dir === "u" ? [from, cut.at - width / 2, to, cut.at + width / 2] : [cut.at - width / 2, from, cut.at + width / 2, to];
2737
+ beams.push({ part: make({ id: `${slab.id}_b${count++}`, role: "beam", material: context.beamMaterial, aabb, y0: top - depth, y1: top, floor: slab.floor, slab: slab.id }), line, width });
2738
+ }
2739
+ }
2740
+ const pillars = spots.map((spot) => addPillar(context, spot, storey.bottom, top, storey.floor, storey.below, slab.id));
2741
+ context.parts.splice(context.parts.indexOf(panels[panels.length - 1]) + 1, 0, ...beams.map((b) => b.part));
2742
+ const all = [...props, ...pillars.map((p, k) => ({ box: p.aabb, dist: spots[k].dist, part: p }))];
2743
+ for (const { part: beam, line, width } of beams) for (const used of heldBy(line, width, all).used) rest(context, used.part, beam);
2744
+ for (const panel of panels) {
2745
+ for (const { part: beam } of beams) if (touches(panel.aabb, beam.aabb, PLAN_TOLERANCE)) rest(context, beam, panel);
2746
+ for (const bond of bonds) if (touches(panel.aabb, bond.aabb, PLAN_TOLERANCE)) rest(context, bond, panel, own.find((s) => s.on === bond.id)?.joint ?? context.joint);
2747
+ }
2748
+ for (const panel of panels.filter((p) => !context.supports.some((s) => s.part === p.id))) {
2749
+ const nearest = [...beams.map((b) => b.part), ...bonds].sort((a, b) => distance(a.aabb, panel.aabb) - distance(b.aabb, panel.aabb))[0];
2750
+ if (nearest) rest(context, nearest, panel);
2751
+ }
2752
+ for (const support of resting) {
2753
+ const upper = context.parts.find((q) => q.id === support.part);
2754
+ if (!upper) continue;
2755
+ for (const panel of panelsUnder(context, slab.id, upper.aabb)) context.supports.push({ ...support, on: panel.id });
2756
+ }
2757
+ }
2758
+ var beamDepthFor = (span, options, minimum = options.beamMinDepth) => Math.max(minimum, ceilTo(span / options.beamDepthRatio, options.beamStep));
2759
+ function deepenBondBeams(input, structure, options) {
2760
+ const frame = structure.construction.frame;
2761
+ if (!frame.rebar) return input;
2762
+ const dist = distances(input.parts, input.supports);
2763
+ const context = { parts: input.parts };
2764
+ let changed = false;
2765
+ const parts = input.parts.map((part) => {
2766
+ if (!isBondBeam(part, frame.beams) || !dist.has(part.id)) return part;
2767
+ const dir = part.aabb[2] - part.aabb[0] >= part.aabb[3] - part.aabb[1] ? "u" : "v";
2768
+ const line = lineOf(part.aabb, dir);
2769
+ const width = dir === "u" ? part.aabb[3] - part.aabb[1] : part.aabb[2] - part.aabb[0];
2770
+ const span = beamSpan(line.to - line.from, heldBy(line, width, propsUnder(context, dist, part.y0, part.y1, part.floor)).intervals).span;
2771
+ if (!(span > 1.5) || !Number.isFinite(span)) return part;
2772
+ const depth = Math.max(part.y1 - part.y0, beamDepthFor(span, options));
2773
+ const wide = Math.max(width, round2(depth / options.beamWidthRatio));
2774
+ const grow = (wide - width) / 2;
2775
+ changed = true;
2776
+ const aabb = dir === "u" ? [part.aabb[0], part.aabb[1] - grow, part.aabb[2], part.aabb[3] + grow] : [part.aabb[0] - grow, part.aabb[1], part.aabb[2] + grow, part.aabb[3]];
2777
+ return make({ ...part, aabb, y0: part.y1 - depth });
2778
+ });
2779
+ return changed ? { parts, supports: input.supports } : input;
2780
+ }
2781
+ function supportBondBeam(context, plate) {
2782
+ const { options } = context;
2783
+ const dir = plate.aabb[2] - plate.aabb[0] >= plate.aabb[3] - plate.aabb[1] ? "u" : "v";
2784
+ const line = lineOf(plate.aabb, dir);
2785
+ const width = dir === "u" ? plate.aabb[3] - plate.aabb[1] : plate.aabb[2] - plate.aabb[0];
2786
+ const dist = distancesOf(context);
2787
+ const floor = plate.floor;
2788
+ const props = propsUnder(context, dist, plate.y0, plate.y1, floor);
2789
+ const storey = { floor, below: floor >= 1 ? `slab_${floor}` : void 0, bottom: context.structure.levels[floor] ?? 0 };
2790
+ const spots = [];
2791
+ const near = props.length ? Math.min(...props.map((p) => p.dist)) : pillarDist(context, dist, storey);
2792
+ const held = pillarsFor(line, width, props, spots, near, options, false);
2793
+ const span = beamSpan(line.to - line.from, held).span;
2794
+ if (span > 1.5) {
2795
+ const depth = Math.max(plate.y1 - plate.y0, beamDepthFor(Math.min(span, options.maxSpan), options));
2796
+ plate.y0 = round2(plate.y1 - depth);
2797
+ plate.mass = Math.max(1, Math.round((plate.aabb[2] - plate.aabb[0]) * (plate.aabb[3] - plate.aabb[1]) * depth * densityOf(plate.material)));
2798
+ }
2799
+ for (const spot of spots) {
2800
+ const pillar = addPillar(context, spot, storey.bottom, plate.y0 + PLAN_TOLERANCE, floor, storey.below, plate.id);
2801
+ rest(context, pillar, plate, "rebar");
2802
+ }
2803
+ }
2804
+ function applySlabPanels(input, structure, options) {
2805
+ const frame = structure.construction.frame;
2806
+ const system = CONSTRUCTION_SYSTEMS.get(structure.construction.system);
2807
+ if (!frame.rebar || !system || system.timber || structure.levels.length > options.maxStoreys) return input;
2808
+ const parts = input.parts.map((p) => ({ ...p }));
2809
+ const slabs = parts.filter((p) => isConcreteSlab(p, frame.slabs));
2810
+ const bonds = parts.filter((p) => isBondBeam(p, frame.beams));
2811
+ if (!slabs.length && !bonds.length) return input;
2812
+ const firstColumn = input.supports.find((s) => /^(column_0_|building_column_)/.test(s.part) && !/^(column_0_|building_column_)/.test(s.on));
2813
+ const context = {
2814
+ structure,
2815
+ options,
2816
+ beamMaterial: frame.beams,
2817
+ pillarMaterial: frame.pillars,
2818
+ joint: system.joints.slab,
2819
+ wallThickness: structure.construction.walls.thickness ?? 0.15,
2820
+ parts,
2821
+ supports: input.supports.slice(),
2822
+ panelsOf: /* @__PURE__ */ new Map(),
2823
+ pillars: 0,
2824
+ groundBase: firstColumn?.on ?? GROUND
2825
+ };
2826
+ const walls = parts.filter((p) => /^wall_/.test(p.id) && (p.role === "wall" || p.role === "partition"));
2827
+ const jobs = [...bonds.map((part) => ({ part, bond: true })), ...slabs.map((part) => ({ part, bond: false }))].sort((a, b) => a.part.y0 - b.part.y0 || Number(b.bond) - Number(a.bond));
2828
+ for (const job of jobs) {
2829
+ if (job.bond) {
2830
+ supportBondBeam(context, job.part);
2831
+ continue;
2832
+ }
2833
+ const slab = job.part;
2834
+ const boxes = walls.filter((w) => w.y0 >= slab.y1 - 0.1 && w.y0 <= slab.y1 + 0.4 || w.y1 >= slab.y0 - 0.4 && w.y1 <= slab.y0 + 0.1 && w.y0 < slab.y0).map((w) => w.aabb);
2835
+ cutSlab(context, slab, boxes);
2836
+ }
2837
+ if (structure.construction.foundation.type === "building") anchorBalconies(context);
2838
+ return { parts: context.parts, supports: context.supports };
2839
+ }
2840
+ function anchorBalconies(context) {
2841
+ const base = context.panelsOf.get("slab_base");
2842
+ if (!base) return;
2843
+ const decks = context.parts.filter((p) => p.role === "slab" && /^slab_\d+_/.test(p.id) && context.supports.some((s) => s.part === p.id && /^balcony_column_/.test(s.on)));
2844
+ for (const deck of decks) {
2845
+ const pillars = new Set(context.supports.filter((s) => s.part === deck.id && /^balcony_column_/.test(s.on)).map((s) => s.on));
2846
+ context.supports = context.supports.filter((s) => s.part !== deck.id && !pillars.has(s.part) && !pillars.has(s.on));
2847
+ context.parts = context.parts.filter((p) => !pillars.has(p.id));
2848
+ for (const panel of panelsUnder(context, "slab_base", deck.aabb)) rest(context, panel, deck);
2849
+ }
2850
+ }
2851
+ function slabPanelView(structure, options) {
2852
+ const out = applySlabPanels({ parts: structure.parts, supports: structure.supports }, structure, options);
2853
+ const panels = out.parts.filter((p) => p.slab !== void 0 && p.role === "slab");
2854
+ const ids = new Set(panels.map((p) => p.id));
2855
+ return { panels, replaced: [...new Set(panels.map((p) => p.slab))], supports: out.supports.filter((s) => ids.has(s.part) || ids.has(s.on)) };
2856
+ }
2857
+ var slabPanelsOf = (structure, options) => slabPanelView(structure, options).panels;
2858
+
2859
+ // src/slab-panels/options.ts
2860
+ var SLAB_PANEL_DEFAULTS = {
2861
+ maxSpan: 5,
2862
+ minPanel: 1.5,
2863
+ lineCoverage: 0.6,
2864
+ doorGap: 1.3,
2865
+ lineTolerance: 0.2,
2866
+ footprintShare: 0.5,
2867
+ thicknessRatio: 40,
2868
+ floorMinThickness: 0.1,
2869
+ roofMinThickness: 0.08,
2870
+ beamDepthRatio: 10,
2871
+ beamMinDepth: 0.25,
2872
+ beamStep: 0.05,
2873
+ beamWidthRatio: 3,
2874
+ columnSide: 0.2,
2875
+ maxStoreys: 8
2876
+ };
2877
+ var resolveSlabPanels = (options) => {
2878
+ if (options === false) return void 0;
2879
+ const given = options === true || options === void 0 ? {} : options;
2880
+ const out = { ...SLAB_PANEL_DEFAULTS };
2881
+ for (const key of Object.keys(given)) if (given[key] !== void 0) out[key] = given[key];
2882
+ return out;
2883
+ };
2884
+
2344
2885
  // src/wall-detail/index.ts
2345
2886
  import { getAssembly } from "@adatechnology/game-destruction";
2346
2887
 
@@ -2399,12 +2940,12 @@ function splitIntoCourses(wall, openings, spec) {
2399
2940
  for (const [a, b] of cutAt(from, to, openings.filter((o) => o.wall === wall.id), y0, y1)) for (const [x0, x1] of unitsOf(a, b, row, spec)) cells.push({ from: x0, to: x1, row, y0, y1 });
2400
2941
  }
2401
2942
  const total = cells.reduce((sum, cell) => sum + (cell.to - cell.from) * (cell.y1 - cell.y0), 0);
2402
- const box = (cell) => alongU ? [cell.from, cross[0], cell.to, cross[1]] : [cross[0], cell.from, cross[1], cell.to];
2943
+ const box2 = (cell) => alongU ? [cell.from, cross[0], cell.to, cross[1]] : [cross[0], cell.from, cross[1], cell.to];
2403
2944
  const pieces = cells.map((cell, index) => ({
2404
2945
  id: `${wall.id}_${spec.unitLength ? "u" : "log"}${cell.row}_${index}`,
2405
2946
  role: wall.role,
2406
2947
  material: wall.material,
2407
- aabb: box(cell),
2948
+ aabb: box2(cell),
2408
2949
  y0: cell.y0,
2409
2950
  y1: cell.y1,
2410
2951
  floor: wall.floor,
@@ -2448,7 +2989,7 @@ function splitIntoPanels(wall, openings, style) {
2448
2989
  };
2449
2990
  own.forEach((opening2, k) => {
2450
2991
  addPier(Math.max(cursor, opening2.from));
2451
- const lintelTop = Math.min(wall.y1, opening2.y1 + LINTEL_DEPTH2);
2992
+ const lintelTop = Math.min(wall.y1, opening2.y1 + Math.max(LINTEL_DEPTH2, style.lintelDepth?.(opening2.to - opening2.from) ?? 0));
2452
2993
  const lintelFrom = Math.max(frame.from, opening2.from - LINTEL_BEARING2);
2453
2994
  const lintelTo = Math.min(frame.to, opening2.to + LINTEL_BEARING2);
2454
2995
  const lintelHeight = lintelTop - opening2.y1;
@@ -2496,7 +3037,7 @@ function logSpec(construction, part) {
2496
3037
  const diameter = construction.walls.logDiameter ?? LOG_DIAMETER;
2497
3038
  return { rowHeight: diameter, bond: "stack", joint: CONSTRUCTION_SYSTEMS.get(construction.system).joints.wall, assembly: part.assembly };
2498
3039
  }
2499
- function panelSplits(input, structure) {
3040
+ function panelSplits(input, structure, lintelDepth) {
2500
3041
  const { construction } = structure;
2501
3042
  const system = CONSTRUCTION_SYSTEMS.get(construction.system);
2502
3043
  const splits = /* @__PURE__ */ new Map();
@@ -2509,15 +3050,16 @@ function panelSplits(input, structure) {
2509
3050
  lintelMaterial: system.timber ? construction.frame.timber : "concrete",
2510
3051
  ...system.timber ? {} : { lintelAssembly: "reinforced_concrete" },
2511
3052
  lintelJoint: system.joints.beam,
2512
- wallJoint: system.joints.wall
3053
+ wallJoint: system.joints.wall,
3054
+ ...lintelDepth && !system.timber ? { lintelDepth } : {}
2513
3055
  });
2514
3056
  if (split.pieces.length > 1) splits.set(part.id, split);
2515
3057
  }
2516
3058
  return splits;
2517
3059
  }
2518
- function applyPanels(input, structure, detail) {
3060
+ function applyPanels(input, structure, detail, lintelDepth) {
2519
3061
  if (detail === "coarse") return input;
2520
- return applySplits(input, panelSplits(input, structure));
3062
+ return applySplits(input, panelSplits(input, structure, lintelDepth));
2521
3063
  }
2522
3064
  function applyUnits(input, structure) {
2523
3065
  const splits = /* @__PURE__ */ new Map();
@@ -2534,7 +3076,7 @@ function applyUnits(input, structure) {
2534
3076
  const capacity = input.capacities.get(id);
2535
3077
  capacities.delete(id);
2536
3078
  if (capacity === void 0) continue;
2537
- const along = (box) => Math.max(box[2] - box[0], box[3] - box[1]);
3079
+ const along = (box2) => Math.max(box2[2] - box2[0], box2[3] - box2[1]);
2538
3080
  const length = along(input.parts.find((part) => part.id === id).aabb);
2539
3081
  for (const piece of split.pieces) capacities.set(piece.id, Math.max(1, capacity * along(piece.aabb) / length));
2540
3082
  }
@@ -2542,6 +3084,11 @@ function applyUnits(input, structure) {
2542
3084
  }
2543
3085
 
2544
3086
  // src/to-destructible-spec.ts
3087
+ var RAFT_THICKNESS = 0.25;
3088
+ var PILE_AREA = 9;
3089
+ var RAFT_MARGIN = 0.15;
3090
+ var RAFT_FOUNDATIONS = /* @__PURE__ */ new Set(["raft", "basement", "building"]);
3091
+ var REBAR_RATIOS = { beam: 8e-3, slab: 25e-4, roof: 25e-4, stair: 4e-3 };
2545
3092
  var CONTRACT_ROLE = { column: "column", beam: "beam", slab: "slab", wall: "wall", partition: "partition", roof: "roof", stair: "stair", none: "none" };
2546
3093
  var TERRAIN_MARGIN = 6;
2547
3094
  var TERRAIN_THICKNESS = 0.5;
@@ -2583,7 +3130,11 @@ function toDestructibleSpec(structure, options = {}) {
2583
3130
  const ids = new Set(roled.map((part) => part.id));
2584
3131
  const filtered = structure.supports.filter((support) => !options.filter || ids.has(support.part) && (support.on === GROUND || ids.has(support.on)));
2585
3132
  const detail = options.wallDetail ?? "panels";
2586
- const panels = applyPanels({ parts: roled, supports: filtered }, structure, detail);
3133
+ const resolved = resolveSlabPanels(options.slabPanels ?? true);
3134
+ const slabOptions = resolved && structure.levels.length <= resolved.maxStoreys ? resolved : void 0;
3135
+ const slabbed = slabOptions ? applySlabPanels({ parts: roled, supports: filtered }, structure, slabOptions) : { parts: roled, supports: filtered };
3136
+ const walled = applyPanels(slabbed, structure, detail, slabOptions && structure.construction.frame.rebar ? (span) => beamDepthFor(span, slabOptions) : void 0);
3137
+ const panels = slabOptions && detail !== "coarse" ? deepenBondBeams(walled, structure, slabOptions) : walled;
2587
3138
  const sizing = { safety, redundancy, safetyOthers };
2588
3139
  const sized = { ...panels, capacities: options.sized === false ? /* @__PURE__ */ new Map() : sizeCapacities(panels.parts, panels.supports, sizing) };
2589
3140
  const { parts, supports, capacities } = detail === "units" ? applyUnits(sized, structure) : sized;
@@ -2595,8 +3146,60 @@ function toDestructibleSpec(structure, options = {}) {
2595
3146
  const z1 = Math.max(...parts.map((part) => part.aabb[3]));
2596
3147
  if (parts.length) specs.push({ id: GROUND, material: "concrete", shape: { kind: "box", size: [x1 - x0 + margin, TERRAIN_THICKNESS, z1 - z0 + margin] }, position: [(x0 + x1) / 2, -TERRAIN_THICKNESS / 2, (z0 + z1) / 2], hp: 1e12, structure: { role: "foundation" }, anchored: true, tags: ["terrain"] });
2597
3148
  const present = /* @__PURE__ */ new Set([...parts.map((part) => part.id), GROUND]);
2598
- const joints = supports.filter((s) => present.has(s.part) && present.has(s.on)).map((s) => ({ id: `${s.part}~${s.on}`, a: s.part, b: s.on, kind: s.joint, bears: s.bears === false ? false : "a_on_b" }));
2599
- return { name: options.name ?? "house", parts: specs, joints, tags: ["structure", "house"] };
3149
+ const infill = new Set(portico && slabOptions ? kept.filter((part) => part.role === "wall").map((part) => part.id) : []);
3150
+ const beams = new Set(parts.filter((part) => part.role === "beam" && (part.beamKind === "bond_beam" || part.slab !== void 0)).map((part) => part.id));
3151
+ const joints = supports.filter((s) => present.has(s.part) && present.has(s.on)).map((s) => ({ id: `${s.part}~${s.on}`, a: s.part, b: s.on, kind: s.joint, bears: s.bears === false || beams.has(s.part) && infill.has(s.on) ? false : "a_on_b" }));
3152
+ const spec = { name: options.name ?? "house", parts: specs, joints, tags: ["structure", "house"] };
3153
+ return onSoil(withRebar(spec, structure, options, portico, slabOptions !== void 0), structure, options);
3154
+ }
3155
+ function withRebar(spec, structure, options, portico, panelled) {
3156
+ const { redundancy = 0.97, safetyOthers = 0.45 } = options;
3157
+ const rebar = options.rebar ?? panelled;
3158
+ if (!rebar || !structure.construction.frame.rebar) return spec;
3159
+ const ratios = { ...REBAR_RATIOS, ...rebar === true ? {} : rebar };
3160
+ for (const part of spec.parts) {
3161
+ const role = part.structure?.role;
3162
+ if (!role || !(role in ratios) || part.structure.rebar || !MATERIALS.find((m) => m.id === part.material)?.strength?.tensileMPa) continue;
3163
+ part.structure = { ...part.structure, rebar: { ratio: ratios[role] } };
3164
+ }
3165
+ if (options.sized === false) return spec;
3166
+ return sizeRebar(spec, { safety: safetyOthers, redundancy, redundancyRoles: portico ? ["column"] : ["column", "wall"] });
3167
+ }
3168
+ function onSoil(spec, structure, options) {
3169
+ if (options.soil === void 0) return spec;
3170
+ const out = { ...spec, soil: options.soil };
3171
+ const type = options.foundation ?? structure.construction.foundation.type;
3172
+ const ground = out.parts.findIndex((p) => p.id === GROUND);
3173
+ if (ground < 0 || !RAFT_FOUNDATIONS.has(type) && !options.piles) return out;
3174
+ const house = out.parts.filter((p) => p.id !== GROUND);
3175
+ const onGround = new Set((out.joints ?? []).filter((j) => j.b === GROUND).map((j) => j.a));
3176
+ const footprint = house.filter((p) => onGround.has(p.id));
3177
+ const half = (p, k) => p.shape.kind === "box" ? p.shape.size[k] / 2 : p.shape.kind === "cylinder" ? k === 1 ? p.shape.height / 2 : p.shape.radius : p.shape.radius;
3178
+ const under = footprint.length ? footprint : house;
3179
+ const x0 = Math.min(...under.map((p) => p.position[0] - half(p, 0))) - RAFT_MARGIN, x1 = Math.max(...under.map((p) => p.position[0] + half(p, 0))) + RAFT_MARGIN;
3180
+ const z0 = Math.min(...under.map((p) => p.position[2] - half(p, 2))) - RAFT_MARGIN, z1 = Math.max(...under.map((p) => p.position[2] + half(p, 2))) + RAFT_MARGIN;
3181
+ const raft2 = {
3182
+ id: GROUND,
3183
+ material: "concrete",
3184
+ shape: { kind: "box", size: [x1 - x0, RAFT_THICKNESS, z1 - z0] },
3185
+ position: [(x0 + x1) / 2, -RAFT_THICKNESS / 2, (z0 + z1) / 2],
3186
+ structure: { role: "foundation" },
3187
+ tags: ["raft"]
3188
+ };
3189
+ if (options.piles) {
3190
+ const given = options.piles === true ? {} : options.piles;
3191
+ const count = given.count ?? Math.max(4, Math.ceil((x1 - x0) * (z1 - z0) / PILE_AREA));
3192
+ const diameter = given.diameter ?? 0.4;
3193
+ const method = given.method ?? "bored";
3194
+ const materials = new Map(MATERIALS.map((m) => [m.id, m]));
3195
+ const mass = [...house, raft2].reduce((sum, p) => sum + (p.mass ?? partMass(p, materials.get(p.material))), 0);
3196
+ const soils = new Map(SOILS.map((d) => [d.id, d]));
3197
+ for (const d of options.soils ?? []) soils.set(d.id, d);
3198
+ const length = given.length ?? pileLength(profileOf(options.soil), { load: mass * GRAVITY / count, diameter, top: RAFT_THICKNESS, method, soils });
3199
+ raft2.structure = { role: "foundation", pile: { length, diameter, count, method } };
3200
+ }
3201
+ out.parts = out.parts.map((p, i) => i === ground ? raft2 : p);
3202
+ return out;
2600
3203
  }
2601
3204
 
2602
3205
  // src/wall-specimen.ts
@@ -2629,30 +3232,767 @@ function buildWallSpecimen(options) {
2629
3232
  return toDestructibleSpec(structure, { wallDetail: detail, sized: false, terrainMargin: 0.6, name: `wall_${assembly}` });
2630
3233
  }
2631
3234
 
3235
+ // src/tower.ts
3236
+ import { MATERIALS as MATERIALS2, buildFrame, partMass as partMass2, sizeStructure } from "@adatechnology/game-destruction";
3237
+ var slabId = (g, f) => f === g.floors - 1 && g.roof ? "roof" : `slab_${f}`;
3238
+ var storeyBase = (g, f) => f * (g.height + g.slab);
3239
+ var slabHalf = (g) => [(g.spanX + g.columnSize + 0.2) / 2, (g.spanZ + g.columnSize + 0.2) / 2];
3240
+ var box = (size) => ({ kind: "box", size });
3241
+ var rests = (joints, a, b, kind, bears = "a_on_b") => joints.push({ id: `${a}~${b}`, a, b, kind, bears });
3242
+ var CORE_DEFAULTS = {
3243
+ wallThickness: 0.35,
3244
+ innerThickness: 0.2,
3245
+ elevators: 2,
3246
+ shaft: 2.2,
3247
+ shaftDepth: 2.4,
3248
+ stairWidth: 2.6,
3249
+ stairLength: 4.8,
3250
+ material: "concrete",
3251
+ rebar: { spacing: 0.15, diameter: 0.0125 },
3252
+ matThickness: 1.2
3253
+ };
3254
+ var coreSize = (o = {}) => {
3255
+ const c = { ...CORE_DEFAULTS, ...o };
3256
+ const shafts = c.elevators * c.shaft + Math.max(0, c.elevators - 1) * c.innerThickness;
3257
+ return [2 * c.wallThickness + c.stairWidth + c.innerThickness + c.shaftDepth, 2 * c.wallThickness + Math.max(c.stairLength, shafts)];
3258
+ };
3259
+ function addCore(spec, g, options = {}) {
3260
+ const c = { ...CORE_DEFAULTS, ...options, rebar: { ...CORE_DEFAULTS.rebar, ...options.rebar } };
3261
+ const [cx, cz] = options.center ?? [0, 0];
3262
+ const [W, L] = coreSize(options);
3263
+ const t = c.wallThickness, ti = c.innerThickness;
3264
+ const x0 = cx - W / 2, z0 = cz - L / 2;
3265
+ const parts = [...spec.parts];
3266
+ const joints = [...spec.joints ?? []];
3267
+ const reinforcement = { material: "steel", spacing: c.rebar.spacing, diameter: c.rebar.diameter };
3268
+ const raft2 = parts.find((p) => p.id === "raft");
3269
+ let base = raft2?.id;
3270
+ if (!base) {
3271
+ base = "core_mat";
3272
+ parts.push({ id: base, material: c.material, shape: box([W + 1, c.matThickness, L + 1]), position: [cx, -c.matThickness / 2, cz], structure: { role: "foundation" }, anchored: true });
3273
+ }
3274
+ const innerX = x0 + t + c.stairWidth + ti / 2;
3275
+ for (let f = 0; f < g.floors; f++) {
3276
+ const yb = storeyBase(g, f), H = g.height;
3277
+ const wall = (name, size, pos) => {
3278
+ const id = `core_${f}_${name}`;
3279
+ parts.push({ id, material: c.material, shape: box(size), position: [pos[0], yb + H / 2, pos[1]], structure: { role: "wall" }, reinforcement, tags: ["core"] });
3280
+ rests(joints, id, f === 0 ? base : `core_${f - 1}_${name}`, "rebar");
3281
+ return id;
3282
+ };
3283
+ const tube = [
3284
+ wall("w", [t, H, L], [x0 + t / 2, cz]),
3285
+ wall("e", [t, H, L], [x0 + W - t / 2, cz]),
3286
+ wall("n", [W - 2 * t, H, t], [cx, z0 + L - t / 2]),
3287
+ wall("s", [W - 2 * t, H, t], [cx, z0 + t / 2])
3288
+ ];
3289
+ wall("stair", [ti, H, L - 2 * t], [innerX, cz]);
3290
+ for (let k = 1; k < c.elevators; k++) {
3291
+ wall(`shaft_${k}`, [c.shaftDepth, H, ti], [innerX + ti / 2 + c.shaftDepth / 2, z0 + t + k * c.shaft + (k - 1) * ti + ti / 2]);
3292
+ }
3293
+ for (const w of tube) rests(joints, slabId(g, f), w, "rebar");
3294
+ const rise = (H + g.slab) / 2;
3295
+ const flightWidth = (c.stairWidth - 0.2) / 2;
3296
+ const landing = 1.2;
3297
+ const run = c.stairLength - 2 * landing;
3298
+ const angle = Math.atan2(rise, run);
3299
+ const length = Math.hypot(rise, run);
3300
+ const sx = x0 + t;
3301
+ const landingId = `landing_${f}`;
3302
+ parts.push({ id: landingId, material: c.material, shape: box([c.stairWidth, 0.15, landing]), position: [sx + c.stairWidth / 2, yb + rise - 0.075, z0 + t + c.stairLength - landing / 2], structure: { role: "stair" }, tags: ["stair"] });
3303
+ rests(joints, landingId, `core_${f}_w`, "rebar");
3304
+ rests(joints, landingId, `core_${f}_stair`, "rebar");
3305
+ const zMid = z0 + t + landing + run / 2;
3306
+ for (const [k, side] of [["a", 0], ["b", 1]]) {
3307
+ const id = `stair_${f}_${k}`;
3308
+ const up = side === 0 ? 1 : -1;
3309
+ parts.push({
3310
+ id,
3311
+ material: c.material,
3312
+ shape: box([flightWidth, 0.15, length]),
3313
+ position: [sx + flightWidth / 2 + side * (flightWidth + 0.2), yb + rise / 2 + side * rise, zMid],
3314
+ rotation: [up * angle * -1, 0, 0],
3315
+ structure: { role: "stair" },
3316
+ tags: ["stair"]
3317
+ });
3318
+ rests(joints, id, landingId, "rebar");
3319
+ rests(joints, id, side === 0 ? `core_${f}_w` : `core_${f}_stair`, "rebar");
3320
+ }
3321
+ }
3322
+ for (let k = 0; k < c.elevators; k++) {
3323
+ const id = `elevator_${k}`;
3324
+ parts.push({
3325
+ id,
3326
+ material: "steel",
3327
+ shape: box([c.shaftDepth - 0.6, 2.3, c.shaft - 0.6]),
3328
+ position: [innerX + ti / 2 + c.shaftDepth / 2, 1.15 + 0.05, z0 + t + k * (c.shaft + ti) + c.shaft / 2],
3329
+ fill: 0.03,
3330
+ mass: 1500,
3331
+ tags: ["elevator_car"]
3332
+ });
3333
+ rests(joints, id, base, "resting", false);
3334
+ }
3335
+ return { ...spec, parts, joints };
3336
+ }
3337
+ var CURTAIN_DEFAULTS = { panel: 1.5, material: "glass", thickness: 0.012, frameKgM2: 10, shard: 0.6, offset: 0.1 };
3338
+ function addCurtainWall(spec, g, options = {}) {
3339
+ const c = { ...CURTAIN_DEFAULTS, ...options };
3340
+ const parts = [...spec.parts];
3341
+ const joints = [...spec.joints ?? []];
3342
+ const [hx, hz] = slabHalf(g);
3343
+ const h = g.height + g.slab;
3344
+ const sides = [
3345
+ { name: "n", along: 0, length: 2 * hx, fixed: hz + c.offset, axis: 2 },
3346
+ { name: "s", along: 0, length: 2 * hx, fixed: -hz - c.offset, axis: 2 },
3347
+ { name: "e", along: 2, length: 2 * hz, fixed: hx + c.offset, axis: 0 },
3348
+ { name: "w", along: 2, length: 2 * hz, fixed: -hx - c.offset, axis: 0 }
3349
+ ];
3350
+ for (let f = 0; f < g.floors; f++) {
3351
+ if (c.skip?.(f)) continue;
3352
+ const yb = storeyBase(g, f);
3353
+ for (const s of sides) {
3354
+ const n = Math.max(1, Math.round(s.length / c.panel));
3355
+ const w = s.length / n;
3356
+ for (let k = 0; k < n; k++) {
3357
+ const id = `glass_${f}_${s.name}_${k}`;
3358
+ const at2 = -s.length / 2 + (k + 0.5) * w;
3359
+ const size = s.axis === 2 ? [w - 0.01, h - 0.01, c.thickness] : [c.thickness, h - 0.01, w - 0.01];
3360
+ const position = s.axis === 2 ? [at2, yb + h / 2, s.fixed] : [s.fixed, yb + h / 2, at2];
3361
+ parts.push({
3362
+ id,
3363
+ material: c.material,
3364
+ shape: box(size),
3365
+ position,
3366
+ mass: (2500 * c.thickness + c.frameKgM2) * w * h,
3367
+ thickness: c.thickness,
3368
+ units: { size: [c.shard, c.shard, c.thickness], bond: "stack", mortar: "plastic" },
3369
+ tags: ["curtain_wall", "glass"]
3370
+ });
3371
+ rests(joints, id, slabId(g, f), "bolt", false);
3372
+ }
3373
+ }
3374
+ }
3375
+ return { ...spec, parts, joints };
3376
+ }
3377
+ var PUNCHED_DEFAULTS = { material: "brick", thickness: 0.2, window: 0.6, sill: 0.9, head: 2.4, glass: "glass", glassThickness: 0.012 };
3378
+ function addPunchedFacade(spec, g, options = {}) {
3379
+ const c = { ...PUNCHED_DEFAULTS, ...options };
3380
+ const parts = [...spec.parts];
3381
+ const joints = [...spec.joints ?? []];
3382
+ const nx = Math.max(2, g.columnsX ?? 3), nz = Math.max(2, g.columnsZ ?? 4);
3383
+ const xs = Array.from({ length: nx }, (_, i) => -g.spanX / 2 + i * g.spanX / (nx - 1));
3384
+ const zs = Array.from({ length: nz }, (_, j) => -g.spanZ / 2 + j * g.spanZ / (nz - 1));
3385
+ const beamUnder = g.beamHeight ?? TOWER_DEFAULTS.beamHeight;
3386
+ for (let f = 0; f < g.floors; f++) {
3387
+ if (c.skip?.(f)) continue;
3388
+ const yb = storeyBase(g, f);
3389
+ const room = g.height - beamUnder;
3390
+ const head = Math.min(c.head, room - 0.3), sill = Math.min(c.sill, head - 0.6);
3391
+ const wall = (id, size, position, on) => {
3392
+ parts.push({ id, material: c.material, shape: box(size), position, structure: { role: "partition" }, tags: ["infill"] });
3393
+ for (const o of on) rests(joints, id, o, "mortar");
3394
+ };
3395
+ const bay = (side, k, along, centre, fixed, length, c1, c2) => {
3396
+ const clear = length - g.columnSize;
3397
+ const win = clear * c.window;
3398
+ const pier = (clear - win) / 2;
3399
+ const at2 = (u) => along === 0 ? [u, 0, fixed] : [fixed, 0, u];
3400
+ const size = (u, h) => along === 0 ? [u, h, c.thickness] : [c.thickness, h, u];
3401
+ const put = (name, u, h, y, centreU, on) => {
3402
+ const p = at2(centreU);
3403
+ wall(`infill_${f}_${side}_${k}_${name}`, size(u, h), [p[0], y, p[2]], on);
3404
+ };
3405
+ const id = (name) => `infill_${f}_${side}_${k}_${name}`;
3406
+ put("sill", clear, sill, yb + sill / 2, centre, [c1, c2]);
3407
+ put("pl", pier, head - sill, yb + sill + (head - sill) / 2, centre - clear / 2 + pier / 2, [c1, id("sill")]);
3408
+ put("pr", pier, head - sill, yb + sill + (head - sill) / 2, centre + clear / 2 - pier / 2, [c2, id("sill")]);
3409
+ put("head", clear, room - head, yb + head + (room - head) / 2, centre, [c1, c2]);
3410
+ const gp = at2(centre);
3411
+ const gid = `glass_${f}_${side}_${k}`;
3412
+ parts.push({
3413
+ id: gid,
3414
+ material: c.glass,
3415
+ shape: box(size(win - 0.01, head - sill)),
3416
+ position: [gp[0], yb + sill + (head - sill) / 2, gp[2]],
3417
+ thickness: c.glassThickness,
3418
+ units: { size: [0.6, 0.6, c.glassThickness], bond: "stack", mortar: "plastic" },
3419
+ tags: ["window", "glass"]
3420
+ });
3421
+ rests(joints, gid, id("sill"), "glue", false);
3422
+ };
3423
+ for (const [side, j] of [["s", 0], ["n", nz - 1]]) {
3424
+ for (let i = 0; i < nx - 1; i++) bay(side, i, 0, (xs[i] + xs[i + 1]) / 2, zs[j], xs[i + 1] - xs[i], `column_${f}_${i}_${j}`, `column_${f}_${i + 1}_${j}`);
3425
+ }
3426
+ for (const [side, i] of [["w", 0], ["e", nx - 1]]) {
3427
+ for (let j = 0; j < nz - 1; j++) bay(side, j, 2, (zs[j] + zs[j + 1]) / 2, xs[i], zs[j + 1] - zs[j], `column_${f}_${i}_${j}`, `column_${f}_${i}_${j + 1}`);
3428
+ }
3429
+ }
3430
+ return { ...spec, parts, joints };
3431
+ }
3432
+ var FLAT_ROOF_DEFAULTS = { slope: 0.01, screedMin: 0.02, screedDensity: 2e3, membrane: "rubber", membraneThickness: 15e-4, water: 5e-3, areaPerDrain: 150 };
3433
+ function addFlatRoof2(spec, g, options = {}) {
3434
+ const c = { ...FLAT_ROOF_DEFAULTS, ...options };
3435
+ const parts = [...spec.parts];
3436
+ const joints = [...spec.joints ?? []];
3437
+ const top = slabId(g, g.floors - 1);
3438
+ const slab = parts.find((p) => p.id === top);
3439
+ if (!slab || slab.shape.kind !== "box") return spec;
3440
+ const [sx, sy, sz] = slab.shape.size;
3441
+ const area = sx * sz;
3442
+ const longX = sx >= sz;
3443
+ const count = c.drains ?? Math.max(2, Math.ceil(area / c.areaPerDrain));
3444
+ const long = longX ? sx : sz;
3445
+ const spacing = long / count;
3446
+ const run = Math.hypot(spacing / 2, (longX ? sz : sx) / 2);
3447
+ const screed = c.screedMin + c.slope * run / 2;
3448
+ const y = slab.position[1] + sy / 2;
3449
+ parts.push({
3450
+ id: "roof_screed",
3451
+ material: "concrete",
3452
+ shape: box([sx, screed, sz]),
3453
+ position: [slab.position[0], y + screed / 2, slab.position[2]],
3454
+ mass: c.screedDensity * screed * area,
3455
+ structure: { role: "partition" },
3456
+ tags: ["roof_screed"]
3457
+ });
3458
+ rests(joints, "roof_screed", top, "mortar");
3459
+ const my = y + screed + c.membraneThickness / 2;
3460
+ parts.push({
3461
+ id: "roof_membrane",
3462
+ material: c.membrane,
3463
+ shape: box([sx, c.membraneThickness, sz]),
3464
+ position: [slab.position[0], my, slab.position[2]],
3465
+ thickness: c.membraneThickness,
3466
+ structure: { role: "partition" },
3467
+ contains: { fluid: "water", liters: c.water * area * 1e3, outflow: [0, -1, 0] },
3468
+ tags: ["roof_membrane", "waterproofing"]
3469
+ });
3470
+ rests(joints, "roof_membrane", top, "glue");
3471
+ for (let k = 0; k < count; k++) {
3472
+ const at2 = -long / 2 + (k + 0.5) * spacing;
3473
+ const id = `roof_drain_${k}`;
3474
+ parts.push({
3475
+ id,
3476
+ material: "plastic",
3477
+ shape: { kind: "cylinder", radius: 0.05, height: 0.1 },
3478
+ position: longX ? [slab.position[0] + at2, my, slab.position[2]] : [slab.position[0], my, slab.position[2] + at2],
3479
+ fill: 0.2,
3480
+ tags: ["drain"]
3481
+ });
3482
+ rests(joints, id, top, "glue", false);
3483
+ }
3484
+ return { ...spec, parts, joints };
3485
+ }
3486
+ var MECHANICAL_DEFAULTS = { tankMaterial: "plastic", tankWall: 0.01, machineMass: 2500, suspendedMass: 4500, occupants: 16, litersPerPerson: 200, storedFraction: 0.4, fireReserve: 6e3, chillers: 2, chillerMass: 3500 };
3487
+ var towerWater = (storeys, o = {}) => Math.round((o.occupants ?? MECHANICAL_DEFAULTS.occupants) * MECHANICAL_DEFAULTS.litersPerPerson * MECHANICAL_DEFAULTS.storedFraction * storeys + (o.fireReserve ?? MECHANICAL_DEFAULTS.fireReserve));
3488
+ function addMechanicalFloor(spec, g, options = {}) {
3489
+ const c = { ...MECHANICAL_DEFAULTS, ...options };
3490
+ if (g.floors < 2) return spec;
3491
+ const level = options.floor ?? g.floors - 1;
3492
+ if (level < 1 || level > g.floors - 1) return spec;
3493
+ const sfx = level === g.floors - 1 ? "" : `_${level}`;
3494
+ const parts = [...spec.parts];
3495
+ const joints = [...spec.joints ?? []];
3496
+ const floor = `slab_${level - 1}`;
3497
+ const y = storeyBase(g, level);
3498
+ const shafts = options.shafts ?? [];
3499
+ const put = (id, material, size, at2, mass, extra = {}) => {
3500
+ parts.push({ id: id + sfx, material, shape: box(size), position: [at2[0], y + size[1] / 2, at2[1]], mass, fill: 0.2, structure: { role: "partition" }, tags: ["mechanical"], ...extra });
3501
+ rests(joints, id + sfx, floor, "bolt");
3502
+ };
3503
+ shafts.forEach((at2, k) => put(`machine_${k}`, "steel", [1.2, 1, 0.8], at2, c.machineMass + c.suspendedMass, { tags: ["mechanical", "elevator_machine"] }));
3504
+ const liters = options.waterLiters ?? towerWater(level, options);
3505
+ const depth = 2;
3506
+ const side = Math.sqrt(liters / 1e3 / depth);
3507
+ const [hx] = slabHalf(g);
3508
+ const tankAt = options.tankAt ?? [-hx / 2, 0];
3509
+ const shell = 4 * side * depth + 2 * side * side;
3510
+ put("water_tank", c.tankMaterial, [side, depth, side], tankAt, liters + 1100 * c.tankWall * shell, {
3511
+ fill: shell * c.tankWall / (side * side * depth),
3512
+ thickness: c.tankWall,
3513
+ contains: { fluid: "water", liters },
3514
+ tags: ["mechanical", "water_tank"]
3515
+ });
3516
+ for (let k = 0; k < c.chillers; k++) put(`chiller_${k}`, "steel", [3, 2, 1.5], [-hx + 2 + k * 3.5, -(slabHalf(g)[1] - 2)], c.chillerMass, { tags: ["mechanical", "chiller"] });
3517
+ return { ...spec, parts, joints };
3518
+ }
3519
+ var DAMPER_DEFAULTS = { ratio: 0.01, modalShare: 0.25, material: "steel", maxHeight: 2.5 };
3520
+ var damperMass = (spec, o = {}) => {
3521
+ const byId = new Map(MATERIALS2.map((m) => [m.id, m]));
3522
+ let total = 0;
3523
+ for (const p of spec.parts) {
3524
+ const m = byId.get(p.material);
3525
+ if (!m || p.structure?.role === "foundation" || p.damper) continue;
3526
+ total += partMass2(p, m);
3527
+ }
3528
+ return total * (o.modalShare ?? DAMPER_DEFAULTS.modalShare) * (o.ratio ?? DAMPER_DEFAULTS.ratio);
3529
+ };
3530
+ function addTunedMassDamper(spec, g, options = {}) {
3531
+ const c = { ...DAMPER_DEFAULTS, ...options };
3532
+ if (g.floors < 2) return spec;
3533
+ const mat = MATERIALS2.find((m) => m.id === c.material);
3534
+ if (!mat) throw new Error(`unknown material "${c.material}" for the damper`);
3535
+ const mass = options.mass ?? damperMass(spec, c);
3536
+ const volume = mass / mat.density;
3537
+ const height = Math.min(c.maxHeight, Math.cbrt(volume));
3538
+ const side = Math.sqrt(volume / height);
3539
+ const column = (span, n, want) => {
3540
+ const k = Math.max(2, n ?? 0) - 1;
3541
+ return n === void 0 ? want : Math.round((want + span / 2) / (span / k)) * (span / k) - span / 2;
3542
+ };
3543
+ const colZ = column(g.spanZ, g.columnsZ, g.spanZ * 0.35);
3544
+ const at2 = options.at ?? [column(g.spanX, g.columnsX, 0), colZ - Math.sign(colZ || 1) * (g.columnSize / 2 + 0.1)];
3545
+ const y = storeyBase(g, g.floors - 1);
3546
+ const floor = `slab_${g.floors - 2}`;
3547
+ const part = {
3548
+ id: "damper",
3549
+ material: c.material,
3550
+ shape: box([side, height, side]),
3551
+ position: [at2[0], y + height / 2, at2[1]],
3552
+ mass,
3553
+ structure: { role: "partition" },
3554
+ tags: ["mechanical", "tuned_mass_damper"],
3555
+ damper: { ...c.frequency !== void 0 ? { frequency: c.frequency } : {}, ...c.damping !== void 0 ? { damping: c.damping } : {} }
3556
+ };
3557
+ const joints = [...spec.joints ?? []];
3558
+ rests(joints, part.id, floor, "bolt");
3559
+ return { ...spec, parts: [...spec.parts, part], joints };
3560
+ }
3561
+ var mechanicalStoreys = (floors, every) => {
3562
+ if (!every || every < 2) return [];
3563
+ const out = [];
3564
+ for (let k = every; k < floors - 1; k += every) if (floors - 1 - k >= every / 2) out.push(k);
3565
+ return out;
3566
+ };
3567
+ var windExposureOf = (w) => {
3568
+ const out = {};
3569
+ if (w.terrain !== void 0) out.terrain = w.terrain;
3570
+ if (w.class !== void 0) out.class = w.class;
3571
+ if (w.topography !== void 0) out.topography = w.topography;
3572
+ if (w.statistical !== void 0) out.statistical = w.statistical;
3573
+ if (w.drag !== void 0) out.drag = w.drag;
3574
+ if (w.damping !== void 0) out.damping = w.damping;
3575
+ return out;
3576
+ };
3577
+ var TOWER_DEFAULTS = {
3578
+ spanX: 16,
3579
+ spanZ: 24,
3580
+ columnsX: 3,
3581
+ columnsZ: 4,
3582
+ floors: 12,
3583
+ height: 3,
3584
+ columnSize: 0.6,
3585
+ beamHeight: 0.6,
3586
+ slabThickness: 0.2,
3587
+ columnCage: { material: "steel", spacing: 0.15, diameter: 0.02 }
3588
+ };
3589
+ function buildTower(options = {}) {
3590
+ const { core, facade, punched, flatRoof, mechanical, damper, wind, sizing, ...frame } = options;
3591
+ const f = { ...TOWER_DEFAULTS, ...frame, columnCage: { ...TOWER_DEFAULTS.columnCage, ...frame.columnCage } };
3592
+ const g = { floors: f.floors, height: f.height, slab: f.slabThickness, spanX: f.spanX, spanZ: f.spanZ, columnSize: f.columnSize, columnsX: f.columnsX, columnsZ: f.columnsZ, beamHeight: f.beamHeight, roof: true };
3593
+ const { every, ...mech } = mechanical || {};
3594
+ const relays = mechanical === false ? [] : mechanicalStoreys(f.floors, every);
3595
+ const noGlass = (fl) => mechanical !== false && (fl === g.floors - 1 || relays.includes(fl));
3596
+ let spec = buildFrame({ roofThickness: f.slabThickness, ...f, roof: true, continuousColumns: true });
3597
+ spec = { ...spec, name: "tower", tags: ["structure", "tower"] };
3598
+ let shafts = [];
3599
+ if (core !== false) {
3600
+ const bayX = f.spanX / (Math.max(2, f.columnsX) - 1);
3601
+ const center = core?.center ?? [bayX / 2, 0];
3602
+ spec = addCore(spec, g, { ...core, center });
3603
+ const c = { ...CORE_DEFAULTS, ...core };
3604
+ const [W, L] = coreSize(core || {});
3605
+ const sx = center[0] - W / 2 + c.wallThickness + c.stairWidth + c.innerThickness + c.shaftDepth / 2;
3606
+ shafts = Array.from({ length: c.elevators }, (_, k) => [sx, center[1] - L / 2 + c.wallThickness + k * (c.shaft + c.innerThickness) + c.shaft / 2]);
3607
+ }
3608
+ if (facade !== false && !punched) spec = addCurtainWall(spec, g, { skip: noGlass, ...facade });
3609
+ if (punched) spec = addPunchedFacade(spec, g, { skip: noGlass, ...punched });
3610
+ if (flatRoof !== false) spec = addFlatRoof2(spec, g, flatRoof || {});
3611
+ if (mechanical !== false) {
3612
+ spec = addMechanicalFloor(spec, g, { shafts, ...mech });
3613
+ for (const floor of relays) spec = addMechanicalFloor(spec, g, { ...mech, shafts: [], floor, waterLiters: mech.waterLiters ?? towerWater(every ?? floor, mech) });
3614
+ }
3615
+ if (damper) spec = addTunedMassDamper(spec, g, damper);
3616
+ if (wind) {
3617
+ const exposure = windExposureOf(wind);
3618
+ if (Object.keys(exposure).length) spec = { ...spec, wind: exposure };
3619
+ }
3620
+ if (sizing === false) return spec;
3621
+ return sizeStructure(spec, wind && sizing?.wind === void 0 ? { ...sizing, wind } : sizing ?? {});
3622
+ }
3623
+
3624
+ // src/skyscraper.ts
3625
+ import { GRAVITY as GRAVITY2, MATERIALS as MATERIALS3, SOILS as SOILS2, createDestruction, partMass as partMass3, pileLength as pileLength2, profileOf as profileOf2 } from "@adatechnology/game-destruction";
3626
+ var PILE_DEFAULTS = { diameter: 1.2, pitch: 3.5, method: "bored", maxLength: 60 };
3627
+ var SKYSCRAPER_MAX_STOREYS = 60;
3628
+ var SKYSCRAPER_DEFAULTS = {
3629
+ storeys: 40,
3630
+ range: [20, SKYSCRAPER_MAX_STOREYS],
3631
+ height: 3.6,
3632
+ bay: [12, 12],
3633
+ bays: [3, 3],
3634
+ slenderness: 9,
3635
+ coreRatio: 0.08,
3636
+ facade: "curtain",
3637
+ mechanicalEvery: 20,
3638
+ damperAbove: 150,
3639
+ wind: { speed: 40, terrain: "IV" },
3640
+ soil: { layers: [{ soil: "clay_soft", thickness: 10 }, { soil: "sand_dense" }] },
3641
+ piles: {},
3642
+ superimposed: 450
3643
+ };
3644
+ var ceilTo2 = (v, step) => Math.ceil(v / step - 1e-9) * step;
3645
+ var round23 = (v) => Math.round(v * 100) / 100;
3646
+ var COLUMN_KG_M2 = 0.85 * 25e6 / GRAVITY2 * 0.65;
3647
+ var COLUMN_SHARE = 0.5;
3648
+ var CORE_CLEARANCE = 0.4;
3649
+ var WALL_SLENDER = 200;
3650
+ var WIND_SIZING_DEFAULTS = { above: 180, maxBay: 20 };
3651
+ var sizedCache = /* @__PURE__ */ new Map();
3652
+ var driftOf = (layout, wind) => {
3653
+ const w = createDestruction({ wind });
3654
+ return w.state(w.add(buildTower(layout.tower)))?.wind?.drift ?? 0;
3655
+ };
3656
+ function skyscraperLayout(options = {}, preset = SKYSCRAPER_DEFAULTS) {
3657
+ const raw = rawLayout(options, preset);
3658
+ const o = { ...preset, ...Object.fromEntries(Object.entries(options).filter(([, v]) => v !== void 0)) };
3659
+ const cfg = { ...WIND_SIZING_DEFAULTS, ...typeof o.sizeForWind === "object" ? o.sizeForWind : {} };
3660
+ if (o.sizeForWind === false || !o.wind || o.core === false || raw.total < cfg.above) return raw;
3661
+ const key = JSON.stringify([options, preset]);
3662
+ const hit = sizedCache.get(key);
3663
+ if (hit) return hit;
3664
+ const limit = o.wind.driftLimit ?? 1 / 500;
3665
+ let layout = raw;
3666
+ for (let i = 0; i < 6; i++) {
3667
+ const drift = driftOf(layout, o.wind);
3668
+ const widest = Math.max(...layout.bay);
3669
+ if (drift <= limit || widest >= cfg.maxBay) break;
3670
+ const k = Math.max(1.02, Math.pow(drift / limit, 1 / 3) * 1.02);
3671
+ const bay = layout.bay.map((b) => Math.min(cfg.maxBay, round23(ceilTo2(b * k, 0.5))));
3672
+ layout = rawLayout({ ...options, bay }, preset);
3673
+ }
3674
+ sizedCache.set(key, layout);
3675
+ return layout;
3676
+ }
3677
+ function rawLayout(options, preset) {
3678
+ const o = { ...preset, ...Object.fromEntries(Object.entries(options).filter(([, v]) => v !== void 0)) };
3679
+ const range = o.range;
3680
+ const storeys = Math.max(range[0], Math.min(range[1], Math.round(o.storeys)));
3681
+ const given = typeof o.bay === "number" ? [o.bay, o.bay] : [o.bay[0], o.bay[1]];
3682
+ const oddZ = (n) => Math.round(n) % 2 === 1 ? Math.round(n) : Math.round(n) + 1;
3683
+ const bays = [Math.max(1, Math.round(o.bays[0])), oddZ(Math.max(1, o.bays[1]))];
3684
+ const slabOf = (b) => Math.max(TOWER_DEFAULTS.slabThickness, round23(ceilTo2(b / 32, 0.05)));
3685
+ let bay = given;
3686
+ for (let i = 0; i < 4 && o.slenderness !== false; i++) {
3687
+ const total2 = storeys * (o.height + (o.slabThickness ?? slabOf(Math.max(...bay))));
3688
+ const narrow = Math.min(bays[0] * bay[0], bays[1] * bay[1]);
3689
+ if (narrow >= total2 / o.slenderness) break;
3690
+ const k = total2 / o.slenderness / narrow;
3691
+ bay = [round23(ceilTo2(bay[0] * k, 0.5)), round23(ceilTo2(bay[1] * k, 0.5))];
3692
+ }
3693
+ const span = [bays[0] * bay[0], bays[1] * bay[1]];
3694
+ const slab = o.slabThickness ?? slabOf(Math.max(...bay));
3695
+ const beamHeight = o.beamHeight ?? Math.max(TOWER_DEFAULTS.beamHeight, round23(ceilTo2(Math.max(...bay) / 14, 0.1)));
3696
+ const pitch = o.height + slab;
3697
+ const total = storeys * pitch;
3698
+ const tributary = bay[0] * bay[1];
3699
+ const load = COLUMN_SHARE * tributary * storeys * (o.superimposed + slab * 2500);
3700
+ const columnSize = o.columnSize ?? Math.max(TOWER_DEFAULTS.columnSize, round23(ceilTo2(Math.sqrt(load / COLUMN_KG_M2), 0.1)));
3701
+ const plate = [round23(span[0] + columnSize + 0.2), round23(span[1] + columnSize + 0.2)];
3702
+ const mech = o.mechanicalEvery === false ? void 0 : o.mechanicalEvery;
3703
+ const relays = o.mechanical === false ? [] : mechanicalStoreys(storeys, o.mechanical === void 0 ? mech : o.mechanical.every ?? mech);
3704
+ const mechanical = o.mechanical === false ? [] : [...relays, storeys - 1];
3705
+ let coreLayout = null;
3706
+ let coreOptions = false;
3707
+ if (o.core !== false) {
3708
+ const given2 = o.core ?? {};
3709
+ const maxWall = Math.floor((o.height / 3 - 0.05) * 20) / 20;
3710
+ const wallThickness = given2.wallThickness ?? Math.max(CORE_DEFAULTS.wallThickness, Math.min(maxWall, round23(ceilTo2(total / WALL_SLENDER, 0.05))));
3711
+ const ti = given2.innerThickness ?? CORE_DEFAULTS.innerThickness;
3712
+ const shaft = given2.shaft ?? CORE_DEFAULTS.shaft;
3713
+ const fit = Math.max(1, Math.floor((bay[1] - 2 * CORE_CLEARANCE - 0.2 - 2 * wallThickness + ti) / (shaft + ti)));
3714
+ const probe = coreSize({ ...given2, wallThickness, elevators: 2 });
3715
+ const wanted = given2.elevators ?? Math.max(2, Math.ceil((o.coreRatio * plate[0] * plate[1] / probe[0] - 2 * wallThickness + ti) / (shaft + ti)));
3716
+ const elevators = Math.max(1, Math.min(wanted, fit));
3717
+ const narrow = coreSize({ ...given2, wallThickness, elevators });
3718
+ const room = bay[0] - columnSize - 2 * CORE_CLEARANCE - narrow[0];
3719
+ const extra = given2.shaftDepth !== void 0 ? 0 : Math.max(0, Math.min(room, o.coreRatio * plate[0] * plate[1] / narrow[1] - narrow[0]));
3720
+ const xs = Array.from({ length: bays[0] + 1 }, (_, i) => -span[0] / 2 + i * bay[0]);
3721
+ const middle = Math.floor(bays[0] / 2);
3722
+ const center = given2.center ?? [bays[0] % 2 === 1 ? (xs[middle] + xs[middle + 1]) / 2 : xs[middle] + bay[0] / 2, 0];
3723
+ const rebar = { spacing: storeys > 50 ? 0.075 : 0.1, ...given2.rebar };
3724
+ coreOptions = { ...given2, wallThickness, elevators, center, rebar, shaftDepth: (given2.shaftDepth ?? CORE_DEFAULTS.shaftDepth) + round23(extra) };
3725
+ const size = coreSize(coreOptions);
3726
+ coreLayout = { center, size, wallThickness, elevators, stairWidth: given2.stairWidth ?? CORE_DEFAULTS.stairWidth, shaftDepth: coreOptions.shaftDepth };
3727
+ }
3728
+ const damperOn = o.damperAbove !== false && total >= o.damperAbove && o.mechanical !== false;
3729
+ const tower = {
3730
+ floors: storeys,
3731
+ height: o.height,
3732
+ spanX: span[0],
3733
+ spanZ: span[1],
3734
+ columnsX: bays[0] + 1,
3735
+ columnsZ: bays[1] + 1,
3736
+ columnSize,
3737
+ beamHeight,
3738
+ slabThickness: slab,
3739
+ roofThickness: round23(slab + 0.1),
3740
+ columnCage: { ...TOWER_DEFAULTS.columnCage },
3741
+ core: coreOptions,
3742
+ facade: o.facade === "curtain" ? o.curtain ?? {} : false,
3743
+ punched: o.facade === "punched" ? o.punchedFacade ?? {} : false,
3744
+ flatRoof: o.flatRoof ?? {},
3745
+ mechanical: o.mechanical === false ? false : { every: o.mechanicalEvery === false ? void 0 : mech, ...o.mechanical },
3746
+ damper: damperOn ? o.damper ?? {} : false,
3747
+ wind: o.wind === false ? false : o.wind,
3748
+ sizing: o.sizing ?? {},
3749
+ raft: true
3750
+ };
3751
+ return {
3752
+ storeys,
3753
+ height: o.height,
3754
+ slab,
3755
+ pitch,
3756
+ total,
3757
+ bay,
3758
+ span,
3759
+ columnsX: bays[0] + 1,
3760
+ columnsZ: bays[1] + 1,
3761
+ columnSize,
3762
+ beamHeight,
3763
+ plate,
3764
+ core: coreLayout,
3765
+ coreRatio: coreLayout ? coreLayout.size[0] * coreLayout.size[1] / (plate[0] * plate[1]) : 0,
3766
+ facade: o.facade,
3767
+ mechanical,
3768
+ damper: damperOn,
3769
+ tower
3770
+ };
3771
+ }
3772
+ var totalMass = (spec) => {
3773
+ const byId = new Map(MATERIALS3.map((m) => [m.id, m]));
3774
+ return spec.parts.reduce((sum, part) => sum + (part.mass ?? partMass3(part, byId.get(part.material))), 0);
3775
+ };
3776
+ function pilesFor(spec, soil, design = {}) {
3777
+ const raft2 = spec.parts.find((p) => p.id === "raft");
3778
+ if (!raft2 || raft2.shape.kind !== "box") throw new Error("Piles go under a raft: the spec has none");
3779
+ const d = { ...PILE_DEFAULTS, ...design };
3780
+ const [sx, sy, sz] = raft2.shape.size;
3781
+ const profile = profileOf2(soil);
3782
+ const soils = new Map(SOILS2.map((s) => [s.id, s]));
3783
+ const weight = totalMass(spec) * GRAVITY2;
3784
+ if (design.length !== void 0 || design.count !== void 0) {
3785
+ const count = design.count ?? Math.max(4, Math.ceil(sx * sz / (d.pitch * d.diameter) ** 2));
3786
+ const length = design.length ?? pileLength2(profile, { load: weight / count, diameter: d.diameter, top: sy, method: d.method, maxLength: d.maxLength, soils });
3787
+ return { length, diameter: d.diameter, count, method: d.method };
3788
+ }
3789
+ let pitch = d.pitch;
3790
+ for (; ; ) {
3791
+ const count = Math.max(4, Math.ceil(sx / (pitch * d.diameter)) * Math.ceil(sz / (pitch * d.diameter)));
3792
+ const length = pileLength2(profile, { load: weight / count, diameter: d.diameter, top: sy, method: d.method, maxLength: d.maxLength, soils });
3793
+ if (length < d.maxLength || pitch <= 2.5) return { length, diameter: d.diameter, count, method: d.method };
3794
+ pitch = Math.max(2.5, pitch - 0.25);
3795
+ }
3796
+ }
3797
+ function buildSkyscraper(options = {}, preset = SKYSCRAPER_DEFAULTS) {
3798
+ const layout = skyscraperLayout(options, preset);
3799
+ const o = { ...SKYSCRAPER_DEFAULTS, ...preset, ...Object.fromEntries(Object.entries(options).filter(([, v]) => v !== void 0)) };
3800
+ const built = buildTower(layout.tower);
3801
+ const spec = { ...built, name: "skyscraper", tags: [...built.tags ?? [], "skyscraper"] };
3802
+ if (o.soil === false) return spec;
3803
+ const parts = spec.parts.map((part) => {
3804
+ if (part.id !== "raft") return part;
3805
+ return o.piles === false ? { ...part, structure: { ...part.structure, role: "foundation" } } : { ...part, structure: { ...part.structure, role: "foundation", pile: pilesFor(spec, o.soil, o.piles) } };
3806
+ });
3807
+ return { ...spec, parts, soil: o.soil };
3808
+ }
3809
+
3810
+ // src/skyscraper-plan.ts
3811
+ var PLATE_DEFAULTS = { corridor: 1.8, lobby: 3, stairWidth: 3.4 };
3812
+ var r2 = (v) => Math.round(v * 100) / 100;
3813
+ function towerPlate(layout, options = {}) {
3814
+ const o = { ...PLATE_DEFAULTS, ...options };
3815
+ const [w, d] = layout.plate;
3816
+ const core = layout.core;
3817
+ if (!core) throw new Error("A tower plan needs a core (turn the core on)");
3818
+ const t = core.wallThickness;
3819
+ const [W, L] = core.size;
3820
+ const u = w / 2 + core.center[0], v = d / 2 + core.center[1];
3821
+ const rect = [r2(u - W / 2), r2(v - L / 2), r2(u + W / 2), r2(v + L / 2)];
3822
+ const [x0, z0, x1, z1] = rect;
3823
+ const split = r2(Math.min(x1 - 1.8, x0 + Math.max(o.stairWidth, t + core.stairWidth + 0.1)));
3824
+ const stairEnd = r2(z0 + Math.min(L, Math.max(5.4, 2 * t + 4.8)));
3825
+ const merged2 = z1 - stairEnd < 1.6;
3826
+ const stairs = [x0, z0, split, merged2 ? z1 : stairEnd];
3827
+ const service = merged2 ? null : [x0, stairEnd, split, z1];
3828
+ const n = core.elevators;
3829
+ const shafts = Array.from({ length: n }, (_, k) => [split, r2(z0 + k * L / n), x1, r2(z0 + (k + 1) * L / n)]);
3830
+ const lobby = [x1, z0, r2(x1 + o.lobby), z1];
3831
+ const west = [r2(x0 - o.corridor), z0, x0, z1];
3832
+ const south = [0, r2(z0 - o.corridor), w, z0];
3833
+ const north = [0, z1, w, r2(z1 + o.corridor)];
3834
+ const strip = (id, side, r, alongU, circulation) => ({
3835
+ id,
3836
+ side,
3837
+ rect: r,
3838
+ alongU,
3839
+ length: r2(alongU ? r[2] - r[0] : r[3] - r[1]),
3840
+ depth: r2(alongU ? r[3] - r[1] : r[2] - r[0]),
3841
+ circulation
3842
+ });
3843
+ return {
3844
+ w,
3845
+ d,
3846
+ core: rect,
3847
+ stairs,
3848
+ service,
3849
+ shafts,
3850
+ lobby,
3851
+ corridors: { s: south, n: north, w: west },
3852
+ strips: [
3853
+ strip("south", "front", [0, 0, w, south[1]], true, "corridor_s"),
3854
+ strip("north", "back", [0, north[3], w, d], true, "corridor_n"),
3855
+ strip("west", "left", [0, z0, west[0], z1], false, "corridor_w"),
3856
+ strip("east", "right", [lobby[2], z0, w, z1], false, "lobby")
3857
+ ]
3858
+ };
3859
+ }
3860
+ var planToSpace = (layout, [u, v]) => [r2(u - layout.plate[0] / 2), r2(v - layout.plate[1] / 2)];
3861
+ var storeyKinds = (layout) => Array.from({ length: layout.storeys }, (_, f) => f === 0 ? "ground" : layout.mechanical.includes(f) ? "mechanical" : "typical");
3862
+ var splitStrip = (strip, count) => {
3863
+ const [u0, v0, u1, v1] = strip.rect;
3864
+ return Array.from({ length: Math.max(1, count) }, (_, k) => {
3865
+ const a = k / Math.max(1, count), b = (k + 1) / Math.max(1, count);
3866
+ return strip.alongU ? [r2(u0 + (u1 - u0) * a), v0, r2(u0 + (u1 - u0) * b), v1] : [u0, r2(v0 + (v1 - v0) * a), u1, r2(v0 + (v1 - v0) * b)];
3867
+ });
3868
+ };
3869
+ var EDGE = 1e-6;
3870
+ function exteriorWindows(id, rect, plate, ceiling, o = {}) {
3871
+ const [u0, v0, u1, v1] = rect;
3872
+ const spacing = o.spacing ?? 3;
3873
+ const out = [];
3874
+ const add = (side, length) => {
3875
+ const n = Math.max(1, Math.round(length / spacing));
3876
+ out.push({ room: id, side, n, width: Math.min(o.width ?? 2.4, length / n * 0.8), y0: o.y0 ?? 0.3, y1: o.y1 ?? r2(ceiling - 0.5) });
3877
+ };
3878
+ if (Math.abs(v0) < EDGE) add("front", u1 - u0);
3879
+ if (Math.abs(v1 - plate.d) < EDGE) add("back", u1 - u0);
3880
+ if (Math.abs(u0) < EDGE) add("left", v1 - v0);
3881
+ if (Math.abs(u1 - plate.w) < EDGE) add("right", v1 - v0);
3882
+ return out;
3883
+ }
3884
+ var stripDoor = (id, strip, floor, width = 1) => ({ a: id, b: `${strip.circulation}_${floor}`, width, opensTo: id });
3885
+ var plantRooms = (strip, { floor }) => {
3886
+ const id = `plant_${floor}_${strip.id}`;
3887
+ return { rooms: [{ id, type: "storage", label: "plant_room", rect: [...strip.rect] }], doors: [stripDoor(id, strip, floor, 1.2)], windows: [] };
3888
+ };
3889
+ function coreRooms(plate, floor, storeys) {
3890
+ const id = (name) => `${name}_${floor}`;
3891
+ const link = floor < storeys - 1 ? [floor, floor + 1] : [floor - 1, floor];
3892
+ const rooms = [
3893
+ { id: id("stairs"), type: "stairs", label: "stairs", rect: [...plate.stairs], link },
3894
+ ...plate.service ? [{ id: id("service"), type: "storage", label: "service_shaft", rect: [...plate.service] }] : [],
3895
+ ...plate.shafts.map((rect, k) => ({ id: id(`elevator_${k}`), type: "elevator", label: "elevator", rect: [...rect] })),
3896
+ { id: id("lobby"), type: "hall", label: "elevator_lobby", rect: [...plate.lobby] },
3897
+ { id: id("corridor_w"), type: "corridor", label: "corridor", rect: [...plate.corridors.w] },
3898
+ { id: id("corridor_s"), type: "corridor", label: "corridor", rect: [...plate.corridors.s] },
3899
+ { id: id("corridor_n"), type: "corridor", label: "corridor", rect: [...plate.corridors.n] }
3900
+ ];
3901
+ const doors = [
3902
+ { a: id("stairs"), b: id("corridor_w"), width: 1, opensTo: id("corridor_w") },
3903
+ ...plate.service ? [{ a: id("service"), b: id("corridor_w"), width: 0.8, opensTo: id("service") }] : [],
3904
+ ...plate.shafts.map((_, k) => ({ a: id(`elevator_${k}`), b: id("lobby"), width: 1.2, opensTo: id("lobby") }))
3905
+ ];
3906
+ const openPairs = [[id("lobby"), id("corridor_s")], [id("lobby"), id("corridor_n")], [id("corridor_w"), id("corridor_s")], [id("corridor_w"), id("corridor_n")]];
3907
+ return { rooms, doors, openPairs };
3908
+ }
3909
+ function towerFloors(layout, plan) {
3910
+ const plate = towerPlate(layout, plan);
3911
+ const fillers = { ground: plan.ground ?? plan.typical, typical: plan.typical, mechanical: plan.mechanical ?? plantRooms };
3912
+ return storeyKinds(layout).map((kind, floor) => {
3913
+ const context = { floor, kind, layout, plate, ceiling: layout.pitch };
3914
+ const fills = plate.strips.map((strip) => fillers[kind](strip, context));
3915
+ const core = coreRooms(plate, floor, layout.storeys);
3916
+ return {
3917
+ label: kind === "ground" ? "ground_floor" : kind === "mechanical" ? "mechanical_floor" : "typical_floor",
3918
+ ceiling: layout.pitch,
3919
+ openPairs: core.openPairs,
3920
+ rooms: () => [...core.rooms, ...fills.flatMap((f) => f.rooms)],
3921
+ doors: () => [...core.doors, ...fills.flatMap((f) => f.doors)],
3922
+ windows: () => fills.flatMap((f) => f.windows)
3923
+ };
3924
+ });
3925
+ }
3926
+ var towerLocales = { en: { labels: en_default.labels }, "pt-BR": { labels: pt_BR_default.labels } };
3927
+
2632
3928
  // src/index.ts
2633
3929
  var version = "0.1.0";
2634
3930
  export {
2635
3931
  CONSTRUCTIONS,
2636
3932
  CONSTRUCTION_SYSTEMS,
3933
+ CORE_DEFAULTS,
3934
+ CURTAIN_DEFAULTS,
3935
+ DAMPER_DEFAULTS,
2637
3936
  DENSITY,
3937
+ FLAT_ROOF_DEFAULTS,
2638
3938
  GROUND,
3939
+ MECHANICAL_DEFAULTS,
3940
+ PILE_AREA,
3941
+ PILE_DEFAULTS,
3942
+ PLATE_DEFAULTS,
3943
+ PUNCHED_DEFAULTS,
3944
+ RAFT_THICKNESS,
3945
+ REBAR_RATIOS,
2639
3946
  ROOF_COVERINGS,
3947
+ SKYSCRAPER_DEFAULTS,
3948
+ SKYSCRAPER_MAX_STOREYS,
3949
+ SLAB_PANEL_DEFAULTS,
3950
+ TOWER_DEFAULTS,
3951
+ addCore,
3952
+ addCurtainWall,
3953
+ addFlatRoof2 as addFlatRoof,
3954
+ addMechanicalFloor,
3955
+ addPunchedFacade,
3956
+ addTunedMassDamper,
3957
+ applySlabPanels,
3958
+ beamDepthFor,
3959
+ beamSpan,
3960
+ buildSkyscraper,
2640
3961
  buildStructure,
3962
+ buildTower,
2641
3963
  buildWallSpecimen,
2642
3964
  campConstruction,
2643
3965
  chaletConstruction,
2644
3966
  constructionOfSystem,
3967
+ coreSize,
3968
+ damperMass,
3969
+ exteriorWindows,
2645
3970
  logCabinConstruction,
2646
3971
  masonryConstruction,
3972
+ mechanicalStoreys,
3973
+ pilesFor,
3974
+ planToSpace,
2647
3975
  planWaterTank,
2648
3976
  register,
2649
3977
  registerConstruction,
2650
3978
  registerConstructionSystem,
2651
3979
  registerRoofCovering,
3980
+ resolveSlabPanels,
2652
3981
  shackConstruction,
3982
+ skyscraperLayout,
3983
+ slabPanelView,
3984
+ slabPanelsOf,
3985
+ splitStrip,
3986
+ storeyKinds,
3987
+ stripDoor,
2653
3988
  structureLayer,
2654
3989
  toDestructibleSpec,
3990
+ towerFloors,
3991
+ towerLocales,
3992
+ towerPlate,
3993
+ towerWater,
2655
3994
  version,
3995
+ windExposureOf,
2656
3996
  woodFrameConstruction
2657
3997
  };
2658
3998
  //# sourceMappingURL=index.js.map