@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/README.md +7 -0
- package/dist/index.d.ts +679 -3
- package/dist/index.global.js +5807 -183
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +1444 -104
- package/dist/index.js.map +1 -1
- package/package.json +7 -10
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
|
|
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:
|
|
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:
|
|
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
|
|
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:
|
|
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: [
|
|
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 =
|
|
465
|
-
const depth =
|
|
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 ?
|
|
473
|
-
const aabb = alongU ? [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:
|
|
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:
|
|
500
|
-
const corners = [[
|
|
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:
|
|
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:
|
|
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:
|
|
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", [
|
|
518
|
-
["back", [
|
|
519
|
-
["left", [
|
|
520
|
-
["right", [
|
|
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((
|
|
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 =
|
|
532
|
-
const v = (
|
|
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:
|
|
543
|
-
const columns = Math.max(2, Math.ceil((
|
|
544
|
-
const rows = Math.max(2, Math.ceil((
|
|
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 =
|
|
550
|
-
const v =
|
|
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 =
|
|
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 ?
|
|
561
|
-
const aabb = alongU ? [
|
|
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:
|
|
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
|
|
670
|
-
const top = Math.max(topOfFloor, ...plates.filter((plate) => touches(plate.aabb,
|
|
671
|
-
const pillar = builder.add({ id: `column_${floor}_${k}`, role: "column", material: context.construction.frame.pillars, aabb:
|
|
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" ?
|
|
809
|
-
const aabb = axis === "u" ? [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 =
|
|
823
|
-
const depth =
|
|
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" ? (
|
|
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" ?
|
|
839
|
-
const halves = axis === "u" ? [["a", [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" ? [
|
|
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
|
|
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 =
|
|
901
|
-
const depth =
|
|
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 ?
|
|
910
|
-
const aabb = alongU ? [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
|
|
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:
|
|
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,
|
|
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
|
|
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(
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
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) ||
|
|
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 (
|
|
1713
|
-
const
|
|
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,
|
|
1717
|
-
const cz = clamp(stove.z,
|
|
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] <
|
|
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
|
|
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 =
|
|
1882
|
-
const uEnd =
|
|
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 = (
|
|
1885
|
-
const vArea = flat ? [
|
|
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(
|
|
1971
|
+
function wallsOf(box2, door, height) {
|
|
1958
1972
|
const walls = [];
|
|
1959
1973
|
const sides = [
|
|
1960
|
-
{ axis: "u", pos:
|
|
1961
|
-
{ axis: "u", pos:
|
|
1962
|
-
{ axis: "v", pos:
|
|
1963
|
-
{ axis: "v", pos:
|
|
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
|
|
1985
|
-
if (
|
|
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:
|
|
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
|
|
2054
|
-
const value = cost(k.rects.slice(1)) * 100 +
|
|
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
|
|
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 (
|
|
2309
|
-
|
|
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) =>
|
|
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
|
|
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:
|
|
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 = (
|
|
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
|
|
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
|
|
2599
|
-
|
|
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
|