@adatechnology/game-floorplan-three 0.9.2 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-QUVGWVEC.js → chunk-7XVCZF6H.js} +128 -48
- package/dist/chunk-7XVCZF6H.js.map +1 -0
- package/dist/index.d.ts +10 -4
- package/dist/index.global.js +787 -96
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +38 -2
- package/dist/index.js.map +1 -1
- package/dist/{testing-ikPy2Nxl.d.ts → testing-CcAh3Eg1.d.ts} +7 -0
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +1 -1
- package/package.json +9 -9
- package/dist/chunk-QUVGWVEC.js.map +0 -1
package/dist/index.global.js
CHANGED
|
@@ -4361,6 +4361,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
4361
4361
|
// src/house/walls.ts
|
|
4362
4362
|
function installHouseWalls(K, shared) {
|
|
4363
4363
|
const { EPS: EPS3, Y0, boxC, bx, cursor, effectiveHeight, pieceRecord, structurePiece, wallColor } = K;
|
|
4364
|
+
const sh = (color2, factor) => K.sh(color2, factor);
|
|
4364
4365
|
function wallBox2(alongU, L, a, b, ya, yb, th, cL, cH, mL, mH) {
|
|
4365
4366
|
if (yb - ya < 1e-4 || b - a < 1e-4) return;
|
|
4366
4367
|
if (alongU) {
|
|
@@ -4404,6 +4405,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
4404
4405
|
function walls() {
|
|
4405
4406
|
const R = shared.ctx.r, cut = shared.ctx.cut, nA = R.floors.length, vMin = Math.min(...R.walls.map((w) => w.axis === "u" ? w.v0 : Math.min(w.v0, w.v1)));
|
|
4406
4407
|
const corners = /* @__PURE__ */ new Map();
|
|
4408
|
+
const looks = /* @__PURE__ */ new Map();
|
|
4407
4409
|
for (const w of R.walls) {
|
|
4408
4410
|
const f = w.floor;
|
|
4409
4411
|
cursor.group = "f" + f;
|
|
@@ -4413,6 +4415,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
4413
4415
|
const hh = cut ? Math.min(h, 1.2) : h, L = alongU ? w.v0 : w.u0, s0 = Math.min(alongU ? w.u0 : w.v0, alongU ? w.u1 : w.v1), s1 = Math.max(alongU ? w.u0 : w.v0, alongU ? w.u1 : w.v1);
|
|
4414
4416
|
const front = alongU && Math.abs(L - vMin) < 0.05;
|
|
4415
4417
|
const sideL = sideOf(w.sides[0], front), sideH = sideOf(w.sides[1], front);
|
|
4418
|
+
if (w.id) {
|
|
4419
|
+
const face = sideL.exterior ? sideL : sideH;
|
|
4420
|
+
looks.set(w.id, { mat: face.mat, color: face.color, top: y0 + hh });
|
|
4421
|
+
}
|
|
4416
4422
|
shared.ctx.colliders.push({ type: "wall", piece: w.id, f, x0: alongU ? s0 : L - th / 2, z0: alongU ? L - th / 2 : s0, x1: alongU ? s1 : L + th / 2, z1: alongU ? L + th / 2 : s1 });
|
|
4417
4423
|
if (w.type === "railing") {
|
|
4418
4424
|
const c0 = shared.ctx.colors.metal != null ? shared.ctx.colors.metal : 9080466;
|
|
@@ -4459,8 +4465,27 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
4459
4465
|
cursor.group = "f" + e.f;
|
|
4460
4466
|
bx(e.color.mat, e.pu - e.th / 2, e.y0, e.pv - e.th / 2, e.pu + e.th / 2, e.y0 + e.hh, e.pv + e.th / 2, e.color.color);
|
|
4461
4467
|
}
|
|
4468
|
+
if (shared.ctx.showPillars) wallPillars(looks);
|
|
4469
|
+
}
|
|
4470
|
+
const PILLAR_IN_WALL = /^(wall_column_|column_\d|jamb_column_)/;
|
|
4471
|
+
const wallOf = (pillar, parts) => {
|
|
4472
|
+
const u = (pillar.aabb[0] + pillar.aabb[2]) / 2, v = (pillar.aabb[1] + pillar.aabb[3]) / 2;
|
|
4473
|
+
return parts.find((q) => (q.role === "wall" || q.role === "partition") && q.assembly !== void 0 && q.floor === pillar.floor && u >= q.aabb[0] - 1e-3 && u <= q.aabb[2] + 1e-3 && v >= q.aabb[1] - 1e-3 && v <= q.aabb[3] + 1e-3);
|
|
4474
|
+
};
|
|
4475
|
+
function wallPillars(looks) {
|
|
4476
|
+
const parts = Object.values(shared.ctx.structureIndex);
|
|
4477
|
+
for (const e of parts) {
|
|
4478
|
+
if (e.role !== "column" || !PILLAR_IN_WALL.test(e.id)) continue;
|
|
4479
|
+
const host = wallOf(e, parts);
|
|
4480
|
+
const look = host && looks.get(host.id);
|
|
4481
|
+
if (!host || !look) continue;
|
|
4482
|
+
cursor.group = "f" + e.floor;
|
|
4483
|
+
cursor.pc = structurePiece(host.id);
|
|
4484
|
+
bx(look.mat, e.aabb[0], e.y0, e.aabb[1], e.aabb[2], Math.min(e.y1, look.top), e.aabb[3], sh(look.color != null ? look.color : 15262420, 0.93));
|
|
4485
|
+
}
|
|
4486
|
+
cursor.pc = null;
|
|
4462
4487
|
}
|
|
4463
|
-
Object.assign(K, { wallBox: wallBox2, sideOf, pieceOf, nearestWall, walls });
|
|
4488
|
+
Object.assign(K, { wallBox: wallBox2, sideOf, pieceOf, nearestWall, walls, wallPillars });
|
|
4464
4489
|
}
|
|
4465
4490
|
|
|
4466
4491
|
// src/house/doors.ts
|
|
@@ -11433,6 +11458,22 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
11433
11458
|
return angle === 0 ? q : quat.mul(q, quat.fromAxisAngle([0, 0, 1], angle));
|
|
11434
11459
|
};
|
|
11435
11460
|
var clamp = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
11461
|
+
var splitAlong = (box3, count, axis, rnd) => {
|
|
11462
|
+
const shares = Array.from({ length: count }, () => 0.25 + 1.5 * rnd());
|
|
11463
|
+
const total = shares.reduce((sum, share) => sum + share, 0);
|
|
11464
|
+
const out = [];
|
|
11465
|
+
let from = box3.c[axis] - box3.s[axis] / 2;
|
|
11466
|
+
for (const share of shares) {
|
|
11467
|
+
const length = box3.s[axis] * share / total;
|
|
11468
|
+
const c = [box3.c[0], box3.c[1], box3.c[2]];
|
|
11469
|
+
const size = [box3.s[0], box3.s[1], box3.s[2]];
|
|
11470
|
+
c[axis] = from + length / 2;
|
|
11471
|
+
size[axis] = length;
|
|
11472
|
+
out.push({ c, s: size });
|
|
11473
|
+
from += length;
|
|
11474
|
+
}
|
|
11475
|
+
return out;
|
|
11476
|
+
};
|
|
11436
11477
|
var splinterBox = (inp, box3, limit = inp.maxFragments) => {
|
|
11437
11478
|
const { rnd } = inp;
|
|
11438
11479
|
const dims = box3.s;
|
|
@@ -11442,35 +11483,81 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
11442
11483
|
const w = Math.max(fmin * 1.5, 0.01);
|
|
11443
11484
|
const c = clamp(Math.round(dims[a] * dims[b] / (w * w)), 3, Math.max(3, Math.floor(limit / 2)));
|
|
11444
11485
|
const maxLen = fmax * 4;
|
|
11445
|
-
const cells =
|
|
11486
|
+
const cells = [];
|
|
11487
|
+
for (const cell of bsp(box3, c, [a, b], rnd)) {
|
|
11488
|
+
const wide = cell.s[a] >= cell.s[b] ? a : b;
|
|
11489
|
+
if (rnd() < 0.4 && cell.s[wide] > fmin * 0.5) for (const part of splitAlong(cell, 2, wide, rnd)) cells.push(part);
|
|
11490
|
+
else cells.push(cell);
|
|
11491
|
+
}
|
|
11446
11492
|
const pieces = [];
|
|
11447
11493
|
for (const cell of cells) {
|
|
11448
|
-
const extra = rnd()
|
|
11494
|
+
const extra = Math.floor(rnd() * 3);
|
|
11449
11495
|
const k = Math.max(1, Math.ceil(dims[g] / maxLen)) + extra;
|
|
11450
|
-
for (const p of
|
|
11496
|
+
for (const p of splitAlong(cell, k, g, rnd)) pieces.push(p);
|
|
11451
11497
|
}
|
|
11452
11498
|
return pieces.slice(0, limit).map((p) => ({ center: p.c, size: [p.s[g], p.s[a], p.s[b]], q: frame(g, a, b), shape: "splinter", weight: p.s[0] * p.s[1] * p.s[2] }));
|
|
11453
11499
|
};
|
|
11500
|
+
var PLY = 3e-3;
|
|
11501
|
+
var PLY_GAP = 0.6;
|
|
11502
|
+
var PLY_SHEET = 0.012;
|
|
11503
|
+
var plyBreak = (inp, root, limit) => {
|
|
11504
|
+
const { rnd } = inp;
|
|
11505
|
+
const dims = root.s;
|
|
11506
|
+
const sorted = [0, 1, 2].sort((x, y) => dims[x] - dims[y]);
|
|
11507
|
+
const t = sorted[0];
|
|
11508
|
+
const [u, v] = [sorted[1], sorted[2]].sort((x, y) => x - y);
|
|
11509
|
+
const along = dims[u] >= dims[v] ? u : v;
|
|
11510
|
+
const across = along === u ? v : u;
|
|
11511
|
+
const layers = clamp(Math.round(dims[t] / PLY / 2), 2, 3);
|
|
11512
|
+
const pitch = dims[t] / layers;
|
|
11513
|
+
const th = Math.min(pitch, PLY_SHEET);
|
|
11514
|
+
const per = Math.max(2, Math.floor(limit / layers));
|
|
11515
|
+
const out = [];
|
|
11516
|
+
for (let k = 0; k < layers; k++) {
|
|
11517
|
+
const layer = { c: [root.c[0], root.c[1], root.c[2]], s: [dims[0], dims[1], dims[2]] };
|
|
11518
|
+
layer.c[t] = root.c[t] + (-dims[t] / 2 + (k + 0.5) * pitch) * (pitch > PLY_SHEET ? 1 : 1 + PLY_GAP);
|
|
11519
|
+
layer.s[t] = th;
|
|
11520
|
+
for (const cell of bsp(layer, per, [u, v], rnd)) {
|
|
11521
|
+
if (out.length >= limit) return out;
|
|
11522
|
+
if (rnd() < 0.35) {
|
|
11523
|
+
const strand = cell.s[across] * (0.15 + 0.2 * rnd());
|
|
11524
|
+
out.push({ center: cell.c, size: [cell.s[along], strand, th * 0.7], q: frame(along, across, t), shape: "splinter", weight: cell.s[along] * strand * th });
|
|
11525
|
+
} else {
|
|
11526
|
+
out.push({ center: cell.c, size: [cell.s[u], cell.s[v], th], q: frame(u, v, t, (rnd() - 0.5) * 0.5), shape: "scrap", weight: cell.s[u] * cell.s[v] * th });
|
|
11527
|
+
}
|
|
11528
|
+
}
|
|
11529
|
+
}
|
|
11530
|
+
return out;
|
|
11531
|
+
};
|
|
11532
|
+
var granules = (inp, box3, count) => bsp(box3, count, [0, 1, 2], inp.rnd).map((b) => ({ center: b.c, size: b.s, q: quat.identity(), shape: "chunk", weight: b.s[0] * b.s[1] * b.s[2] }));
|
|
11533
|
+
var CRUMBS = 6;
|
|
11454
11534
|
var severAt = (inp, root, at2, gap) => {
|
|
11455
11535
|
const dims = root.s;
|
|
11456
11536
|
const L = [0, 1, 2].sort((x, y) => dims[y] - dims[x])[0];
|
|
11457
11537
|
const half = dims[L] / 2;
|
|
11458
11538
|
const lo = clamp(at2 - gap / 2, -half, half), hi = clamp(at2 + gap / 2, -half, half);
|
|
11459
11539
|
const out = [];
|
|
11460
|
-
const
|
|
11540
|
+
const fibrous = inp.form === "slivers" || inp.form === "plies";
|
|
11541
|
+
const [a, b] = [0, 1, 2].filter((i) => i !== L);
|
|
11542
|
+
const stub = (from, to, breakAtEnd) => {
|
|
11461
11543
|
if (to - from < 0.02) return;
|
|
11462
11544
|
const c = [root.c[0], root.c[1], root.c[2]], sz = [dims[0], dims[1], dims[2]];
|
|
11463
11545
|
c[L] = (from + to) / 2;
|
|
11464
11546
|
sz[L] = to - from;
|
|
11465
|
-
|
|
11547
|
+
const weight = sz[0] * sz[1] * sz[2];
|
|
11548
|
+
if (fibrous) out.push({ center: c, size: [sz[L], sz[a], sz[b]], q: frame(L, a, b, breakAtEnd ? 0 : Math.PI), shape: "slab", weight });
|
|
11549
|
+
else out.push({ center: c, size: sz, q: quat.identity(), shape: "slab", weight });
|
|
11466
11550
|
};
|
|
11467
|
-
stub(-half, lo);
|
|
11468
|
-
stub(hi, half);
|
|
11551
|
+
stub(-half, lo, true);
|
|
11552
|
+
stub(hi, half, false);
|
|
11469
11553
|
const band = { c: [root.c[0], root.c[1], root.c[2]], s: [dims[0], dims[1], dims[2]] };
|
|
11470
11554
|
band.c[L] = (lo + hi) / 2;
|
|
11471
11555
|
band.s[L] = Math.max(hi - lo, 0.01);
|
|
11472
|
-
|
|
11473
|
-
|
|
11556
|
+
const room = inp.maxFragments - out.length;
|
|
11557
|
+
if (inp.style === "snap" && inp.form === "plies") for (const p of plyBreak(inp, band, Math.max(3, room))) out.push(p);
|
|
11558
|
+
else if (inp.style === "snap" && inp.form === "granules") for (const p of granules(inp, band, clamp(room, 3, 12))) out.push(p);
|
|
11559
|
+
else if (inp.style === "snap") {
|
|
11560
|
+
for (const b2 of bsp(band, Math.max(3, Math.min(6, room)), [0, 1, 2], inp.rnd)) out.push({ center: b2.c, size: b2.s, q: quat.identity(), shape: "chunk", weight: b2.s[0] * b2.s[1] * b2.s[2] });
|
|
11474
11561
|
} else for (const p of splinterBox(inp, band, Math.max(3, inp.maxFragments - out.length))) out.push(p);
|
|
11475
11562
|
return out;
|
|
11476
11563
|
};
|
|
@@ -11503,8 +11590,18 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
11503
11590
|
const [u, v] = [sorted[1], sorted[2]].sort((a, b) => a - b);
|
|
11504
11591
|
if (style === "snap") {
|
|
11505
11592
|
if (inp.cut) return severAt(inp, root, inp.cut.at, inp.cut.gap);
|
|
11593
|
+
if (inp.form === "plies") return plyBreak(inp, root, maxFragments);
|
|
11506
11594
|
const n2 = Math.min(maxFragments, 2 + Math.floor(rnd() * 3));
|
|
11507
11595
|
for (const b of bsp(root, n2, [0, 1, 2], rnd)) out.push({ center: b.c, size: b.s, q: idq, shape: "slab", weight: b.s[0] * b.s[1] * b.s[2] });
|
|
11596
|
+
if (inp.form !== "granules") return out;
|
|
11597
|
+
const crumbs = Math.min(CRUMBS, maxFragments - out.length);
|
|
11598
|
+
if (crumbs <= 0) return out;
|
|
11599
|
+
const band = { c: [0, 0, 0], s: [0, 0, 0] };
|
|
11600
|
+
for (let i = 0; i < 3; i++) {
|
|
11601
|
+
band.s[i] = Math.min(dims[i], Math.max(fmin, dims[i] * 0.2));
|
|
11602
|
+
band.c[i] = clamp(inp.hit[i], -dims[i] / 2 + band.s[i] / 2, dims[i] / 2 - band.s[i] / 2);
|
|
11603
|
+
}
|
|
11604
|
+
for (const p of granules(inp, band, crumbs)) out.push({ ...p, weight: p.weight * 0.5 });
|
|
11508
11605
|
return out;
|
|
11509
11606
|
}
|
|
11510
11607
|
if (style === "crumble" || style === "none") {
|
|
@@ -11611,6 +11708,88 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
11611
11708
|
}
|
|
11612
11709
|
return marks;
|
|
11613
11710
|
};
|
|
11711
|
+
var JOINT_GAP = 1e-4;
|
|
11712
|
+
var MIN_BEARING_SHARE = 0.25;
|
|
11713
|
+
var unsupportedCells = (cells, thin, dims, standing) => {
|
|
11714
|
+
if (thin === 1) return [];
|
|
11715
|
+
const [u, v] = planeAxes(thin);
|
|
11716
|
+
const bottom = -dims[v] / 2;
|
|
11717
|
+
const rowOf = (y) => Math.round((y - bottom) * 1e4);
|
|
11718
|
+
const rows = /* @__PURE__ */ new Map();
|
|
11719
|
+
for (let i = 0; i < cells.length; i++) {
|
|
11720
|
+
if (!standing(i)) continue;
|
|
11721
|
+
const cell = cells[i];
|
|
11722
|
+
const key = rowOf(cell.center[v] - cell.size[v] / 2);
|
|
11723
|
+
const row = rows.get(key);
|
|
11724
|
+
if (row) row.push(i);
|
|
11725
|
+
else rows.set(key, [i]);
|
|
11726
|
+
}
|
|
11727
|
+
const held = /* @__PURE__ */ new Set();
|
|
11728
|
+
const queue = [];
|
|
11729
|
+
for (const i of rows.get(0) ?? []) {
|
|
11730
|
+
held.add(i);
|
|
11731
|
+
queue.push(i);
|
|
11732
|
+
}
|
|
11733
|
+
const overlap = (a, b) => Math.min(a.center[u] + a.size[u] / 2, b.center[u] + b.size[u] / 2) - Math.max(a.center[u] - a.size[u] / 2, b.center[u] - b.size[u] / 2);
|
|
11734
|
+
const climb = () => {
|
|
11735
|
+
while (queue.length > 0) {
|
|
11736
|
+
const cell = cells[queue.pop()];
|
|
11737
|
+
for (const j of rows.get(rowOf(cell.center[v] + cell.size[v] / 2)) ?? []) {
|
|
11738
|
+
const other = cells[j];
|
|
11739
|
+
if (held.has(j) || overlap(cell, other) <= Math.max(JOINT_GAP, MIN_BEARING_SHARE * Math.min(cell.size[u], other.size[u]))) continue;
|
|
11740
|
+
held.add(j);
|
|
11741
|
+
queue.push(j);
|
|
11742
|
+
}
|
|
11743
|
+
}
|
|
11744
|
+
};
|
|
11745
|
+
for (const row of rows.values()) row.sort((a, b) => cells[a].center[u] - cells[b].center[u]);
|
|
11746
|
+
const touching = (a, b) => Math.abs(a.center[u] + a.size[u] / 2 - (b.center[u] - b.size[u] / 2)) <= 1e-3;
|
|
11747
|
+
for (let wedged = true; wedged; ) {
|
|
11748
|
+
climb();
|
|
11749
|
+
wedged = false;
|
|
11750
|
+
for (const row of rows.values()) {
|
|
11751
|
+
for (let k = 1; k < row.length - 1; k++) {
|
|
11752
|
+
const i = row[k];
|
|
11753
|
+
if (held.has(i) || !held.has(row[k - 1]) || !held.has(row[k + 1])) continue;
|
|
11754
|
+
if (!touching(cells[row[k - 1]], cells[i]) || !touching(cells[i], cells[row[k + 1]])) continue;
|
|
11755
|
+
held.add(i);
|
|
11756
|
+
queue.push(i);
|
|
11757
|
+
wedged = true;
|
|
11758
|
+
}
|
|
11759
|
+
}
|
|
11760
|
+
}
|
|
11761
|
+
const loose = [];
|
|
11762
|
+
for (const row of rows.values()) for (const i of row) if (!held.has(i)) loose.push(i);
|
|
11763
|
+
return loose;
|
|
11764
|
+
};
|
|
11765
|
+
var clumpCells = (cells, thin, cap) => {
|
|
11766
|
+
if (cells.length <= cap) return [...cells];
|
|
11767
|
+
const [u, v] = planeAxes(thin);
|
|
11768
|
+
let lo0 = Infinity, lo1 = Infinity, hi0 = -Infinity, hi1 = -Infinity;
|
|
11769
|
+
for (const cell of cells) {
|
|
11770
|
+
lo0 = Math.min(lo0, cell.center[u] - cell.size[u] / 2);
|
|
11771
|
+
hi0 = Math.max(hi0, cell.center[u] + cell.size[u] / 2);
|
|
11772
|
+
lo1 = Math.min(lo1, cell.center[v] - cell.size[v] / 2);
|
|
11773
|
+
hi1 = Math.max(hi1, cell.center[v] + cell.size[v] / 2);
|
|
11774
|
+
}
|
|
11775
|
+
let side = Math.sqrt((hi0 - lo0) * (hi1 - lo1) / cap);
|
|
11776
|
+
for (; ; ) {
|
|
11777
|
+
const bins = /* @__PURE__ */ new Map();
|
|
11778
|
+
for (const cell of cells) {
|
|
11779
|
+
const key = `${Math.floor((cell.center[u] - lo0) / side)}:${Math.floor((cell.center[v] - lo1) / side)}`;
|
|
11780
|
+
const bin = bins.get(key) ?? { lo: [Infinity, Infinity, Infinity], hi: [-Infinity, -Infinity, -Infinity] };
|
|
11781
|
+
for (let k = 0; k < 3; k++) {
|
|
11782
|
+
bin.lo[k] = Math.min(bin.lo[k], cell.center[k] - cell.size[k] / 2);
|
|
11783
|
+
bin.hi[k] = Math.max(bin.hi[k], cell.center[k] + cell.size[k] / 2);
|
|
11784
|
+
}
|
|
11785
|
+
bins.set(key, bin);
|
|
11786
|
+
}
|
|
11787
|
+
if (bins.size <= cap) {
|
|
11788
|
+
return [...bins.values()].map(({ lo, hi }) => ({ center: [(lo[0] + hi[0]) / 2, (lo[1] + hi[1]) / 2, (lo[2] + hi[2]) / 2], size: [hi[0] - lo[0], hi[1] - lo[1], hi[2] - lo[2]] }));
|
|
11789
|
+
}
|
|
11790
|
+
side *= 1.25;
|
|
11791
|
+
}
|
|
11792
|
+
};
|
|
11614
11793
|
var MAX_SPALLS = 64;
|
|
11615
11794
|
var thinAxisOf = (dims) => dims[0] <= dims[1] && dims[0] <= dims[2] ? 0 : dims[1] <= dims[2] ? 1 : 2;
|
|
11616
11795
|
var resolveLayer = (def, index, mat) => ({ index, def, mat, energy: mat.penetration * def.thickness * 1e3 });
|
|
@@ -12590,6 +12769,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
12590
12769
|
},
|
|
12591
12770
|
{
|
|
12592
12771
|
id: "wood_soft",
|
|
12772
|
+
splitForm: "slivers",
|
|
12593
12773
|
density: 500,
|
|
12594
12774
|
hpPerKg: 90,
|
|
12595
12775
|
penetration: 9,
|
|
@@ -12635,6 +12815,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
12635
12815
|
},
|
|
12636
12816
|
{
|
|
12637
12817
|
id: "wood_hard",
|
|
12818
|
+
splitForm: "slivers",
|
|
12638
12819
|
density: 750,
|
|
12639
12820
|
hpPerKg: 140,
|
|
12640
12821
|
penetration: 14,
|
|
@@ -12679,6 +12860,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
12679
12860
|
},
|
|
12680
12861
|
{
|
|
12681
12862
|
id: "plywood",
|
|
12863
|
+
splitForm: "plies",
|
|
12682
12864
|
density: 600,
|
|
12683
12865
|
hpPerKg: 70,
|
|
12684
12866
|
penetration: 7,
|
|
@@ -12726,6 +12908,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
12726
12908
|
},
|
|
12727
12909
|
{
|
|
12728
12910
|
id: "mdf",
|
|
12911
|
+
splitForm: "granules",
|
|
12729
12912
|
density: 750,
|
|
12730
12913
|
hpPerKg: 45,
|
|
12731
12914
|
penetration: 6,
|
|
@@ -13237,6 +13420,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
13237
13420
|
},
|
|
13238
13421
|
{
|
|
13239
13422
|
id: "green_wood",
|
|
13423
|
+
splitForm: "slivers",
|
|
13240
13424
|
density: 650,
|
|
13241
13425
|
hpPerKg: 5,
|
|
13242
13426
|
penetration: 10,
|
|
@@ -13517,6 +13701,125 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
13517
13701
|
"brittle"
|
|
13518
13702
|
],
|
|
13519
13703
|
compressive: 1e5
|
|
13704
|
+
},
|
|
13705
|
+
{
|
|
13706
|
+
id: "mud",
|
|
13707
|
+
density: 1700,
|
|
13708
|
+
hpPerKg: 1,
|
|
13709
|
+
penetration: 5,
|
|
13710
|
+
ductility: 0.8,
|
|
13711
|
+
fracture: "crumble",
|
|
13712
|
+
fragmentSize: [
|
|
13713
|
+
0.02,
|
|
13714
|
+
0.08
|
|
13715
|
+
],
|
|
13716
|
+
hitDebris: "dust",
|
|
13717
|
+
breakDebris: "dust",
|
|
13718
|
+
thermal: {
|
|
13719
|
+
conductivity: 0.5,
|
|
13720
|
+
heatCapacity: 3.2
|
|
13721
|
+
},
|
|
13722
|
+
tags: [
|
|
13723
|
+
"soil",
|
|
13724
|
+
"wet"
|
|
13725
|
+
],
|
|
13726
|
+
compressive: 5e3
|
|
13727
|
+
},
|
|
13728
|
+
{
|
|
13729
|
+
id: "clay",
|
|
13730
|
+
density: 1800,
|
|
13731
|
+
hpPerKg: 5,
|
|
13732
|
+
penetration: 7,
|
|
13733
|
+
ductility: 0.05,
|
|
13734
|
+
fracture: "crumble",
|
|
13735
|
+
fragmentSize: [
|
|
13736
|
+
0.02,
|
|
13737
|
+
0.1
|
|
13738
|
+
],
|
|
13739
|
+
hitDebris: "chips",
|
|
13740
|
+
breakDebris: "dust",
|
|
13741
|
+
thermal: {
|
|
13742
|
+
conductivity: 0.15,
|
|
13743
|
+
heatCapacity: 2.2
|
|
13744
|
+
},
|
|
13745
|
+
tags: [
|
|
13746
|
+
"soil",
|
|
13747
|
+
"brittle"
|
|
13748
|
+
],
|
|
13749
|
+
compressive: 6e4
|
|
13750
|
+
},
|
|
13751
|
+
{
|
|
13752
|
+
id: "snow",
|
|
13753
|
+
density: 300,
|
|
13754
|
+
hpPerKg: 1,
|
|
13755
|
+
penetration: 3,
|
|
13756
|
+
ductility: 0.6,
|
|
13757
|
+
fracture: "crumble",
|
|
13758
|
+
fragmentSize: [
|
|
13759
|
+
0.01,
|
|
13760
|
+
0.05
|
|
13761
|
+
],
|
|
13762
|
+
hitDebris: "dust",
|
|
13763
|
+
breakDebris: "dust",
|
|
13764
|
+
thermal: {
|
|
13765
|
+
conductivity: 0.1,
|
|
13766
|
+
heatCapacity: 2.1
|
|
13767
|
+
},
|
|
13768
|
+
tags: [
|
|
13769
|
+
"soil",
|
|
13770
|
+
"frozen"
|
|
13771
|
+
],
|
|
13772
|
+
compressive: 1e3
|
|
13773
|
+
},
|
|
13774
|
+
{
|
|
13775
|
+
id: "cobble",
|
|
13776
|
+
density: 2650,
|
|
13777
|
+
hpPerKg: 25,
|
|
13778
|
+
penetration: 25,
|
|
13779
|
+
penetrationClass: "concrete",
|
|
13780
|
+
penetrationScale: 0.9,
|
|
13781
|
+
ductility: 0.03,
|
|
13782
|
+
fracture: "crumble",
|
|
13783
|
+
fragmentSize: [
|
|
13784
|
+
0.05,
|
|
13785
|
+
0.15
|
|
13786
|
+
],
|
|
13787
|
+
hitDebris: "chips",
|
|
13788
|
+
breakDebris: "chips",
|
|
13789
|
+
thermal: {
|
|
13790
|
+
conductivity: 0.2,
|
|
13791
|
+
heatCapacity: 2.5
|
|
13792
|
+
},
|
|
13793
|
+
tags: [
|
|
13794
|
+
"masonry",
|
|
13795
|
+
"brittle"
|
|
13796
|
+
],
|
|
13797
|
+
compressive: 3e5
|
|
13798
|
+
},
|
|
13799
|
+
{
|
|
13800
|
+
id: "paver",
|
|
13801
|
+
density: 2300,
|
|
13802
|
+
hpPerKg: 14,
|
|
13803
|
+
penetration: 18,
|
|
13804
|
+
penetrationClass: "concrete",
|
|
13805
|
+
penetrationScale: 1,
|
|
13806
|
+
ductility: 0.03,
|
|
13807
|
+
fracture: "crumble",
|
|
13808
|
+
fragmentSize: [
|
|
13809
|
+
0.04,
|
|
13810
|
+
0.12
|
|
13811
|
+
],
|
|
13812
|
+
hitDebris: "chips",
|
|
13813
|
+
breakDebris: "chips",
|
|
13814
|
+
thermal: {
|
|
13815
|
+
conductivity: 0.15,
|
|
13816
|
+
heatCapacity: 2.5
|
|
13817
|
+
},
|
|
13818
|
+
tags: [
|
|
13819
|
+
"masonry",
|
|
13820
|
+
"brittle"
|
|
13821
|
+
],
|
|
13822
|
+
compressive: 35e4
|
|
13520
13823
|
}
|
|
13521
13824
|
];
|
|
13522
13825
|
var material_hints_default = {
|
|
@@ -13729,6 +14032,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
13729
14032
|
var BREACH_BLAST_FACTOR = 6;
|
|
13730
14033
|
var BREACH_AREA = 0.25;
|
|
13731
14034
|
var BREACH_BLAST_REACH = 0.12;
|
|
14035
|
+
var MEMBER_CUT_REACH = 0.5;
|
|
14036
|
+
var BLAST_REFERENCE_RESISTANCE = 200 * 1900 * 0.13;
|
|
14037
|
+
var BLAST_EDGE_RAGGED = 0.3;
|
|
14038
|
+
var BLAST_EDGE_LOOSE = 1.35;
|
|
14039
|
+
var BLAST_EDGE_WEAR = 0.6;
|
|
13732
14040
|
var RESIDUAL_CAPACITY = 0.15;
|
|
13733
14041
|
var SEVER_BAND = 0.1;
|
|
13734
14042
|
var SEVER_ASPECT = 3;
|
|
@@ -14981,7 +15289,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14981
15289
|
if (j.st.open) continue;
|
|
14982
15290
|
if (j.spec.kind === "rebar" && (isReinforced(o.parts[j.ia].spec) || isReinforced(o.parts[j.ib].spec))) continue;
|
|
14983
15291
|
if (near && jointReach(o, j, near) > Math.max(radius, jointReachLimit(o, j))) continue;
|
|
14984
|
-
j.st.hp -= span === void 0 ? amount : amount * Math.min(1, span / Math.max(jointLength(o, j), 1e-6));
|
|
15292
|
+
j.st.hp -= span === void 0 ? amount : Math.min(amount, j.st.maxHp) * Math.min(1, span / Math.max(jointLength(o, j), 1e-6));
|
|
14985
15293
|
if (j.st.hp <= 0) {
|
|
14986
15294
|
j.st.hp = 0;
|
|
14987
15295
|
j.st.open = true;
|
|
@@ -15109,6 +15417,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
15109
15417
|
const worn = (_a = p.rt).chips ?? (_a.chips = {});
|
|
15110
15418
|
const cut = (_b = p.rt).craters ?? (_b.craters = {});
|
|
15111
15419
|
const out = [];
|
|
15420
|
+
const wholeBefore = Object.values(worn).filter((w) => w === -1).length;
|
|
15112
15421
|
const shot = kind === "ballistic";
|
|
15113
15422
|
for (let i = 0; i < grid.length; i++) {
|
|
15114
15423
|
const cell = grid[i];
|
|
@@ -15167,6 +15476,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
15167
15476
|
emit({ ...base(o, src), type: "chip", part: p.spec.id, center, size: [cell.size[0], cell.size[1], cell.size[2]], rotation: q, fragments });
|
|
15168
15477
|
debris(o, p, p.mat.breakDebris, center, vec.scale(dir ?? [0, 1, 0], -1), 6, 2, src);
|
|
15169
15478
|
}
|
|
15479
|
+
if (Object.values(worn).filter((w) => w === -1).length > wholeBefore) dropUnsupported(o, p, src);
|
|
15170
15480
|
if (p.spec.reinforcement) exposeRebar(o, p, src);
|
|
15171
15481
|
for (const cell of out) addBreach(o, p, toWorld(c, q, cell.center), cell.size[u] * cell.size[v], energy, dir, src);
|
|
15172
15482
|
if (p.structural && !isLost(p)) {
|
|
@@ -15270,10 +15580,122 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
15270
15580
|
setStage(o, p, 1, {}, src);
|
|
15271
15581
|
setStage(o, p, 2, { opening: { center: worldCenter, radius }, fragments }, src);
|
|
15272
15582
|
debris(o, p, p.mat.breakDebris, worldCenter, vec.scale(dir ?? [0, 1, 0], -1), 14, 3, src);
|
|
15583
|
+
dropUnsupported(o, p, src);
|
|
15273
15584
|
const reinforcement = p.spec.reinforcement;
|
|
15274
15585
|
if (reinforcement) exposeRebar(o, p, src);
|
|
15275
15586
|
if (p.structural) solveStructure(o);
|
|
15276
15587
|
};
|
|
15588
|
+
const blastReachOf = (p, energy) => {
|
|
15589
|
+
const perKg = p.mat.hits?.blunt?.unitJoules ?? chipEnergy;
|
|
15590
|
+
const resistance = Math.max(1, perKg * p.mat.density * p.thickness);
|
|
15591
|
+
return BREACH_BLAST_REACH * Math.cbrt(Math.max(0, energy) / 1e3) * clamp2(Math.cbrt(BLAST_REFERENCE_RESISTANCE / resistance), 0.3, 2);
|
|
15592
|
+
};
|
|
15593
|
+
const blastUnits = (o, p, wp, energy, dist, src) => {
|
|
15594
|
+
var _a, _b;
|
|
15595
|
+
if (!p.staged || p.st.stripped || isLost(p) || chipEnergy <= 0) return false;
|
|
15596
|
+
const reach = blastReachOf(p, energy);
|
|
15597
|
+
if (dist >= reach) return false;
|
|
15598
|
+
const radius = Math.sqrt(reach * reach - dist * dist);
|
|
15599
|
+
const [u, v] = planeAxes(p.thin);
|
|
15600
|
+
const lp = toLocal(p.pos, p.q, objLocal(o, wp));
|
|
15601
|
+
const op = p.st.opening ? toLocal(p.pos, p.q, p.st.opening.center) : null;
|
|
15602
|
+
const grid = gridOf(p);
|
|
15603
|
+
const worn = (_a = p.rt).chips ?? (_a.chips = {});
|
|
15604
|
+
const perKg = p.mat.hits?.blunt?.unitJoules ?? chipEnergy;
|
|
15605
|
+
const lobes = [rnd() * 2 * Math.PI, rnd() * 2 * Math.PI];
|
|
15606
|
+
const out = [];
|
|
15607
|
+
for (let i = 0; i < grid.length; i++) {
|
|
15608
|
+
const cell = grid[i];
|
|
15609
|
+
if (worn[i] === -1) continue;
|
|
15610
|
+
if (op && p.st.opening && Math.hypot(cell.center[u] - op[u], cell.center[v] - op[v]) <= p.st.opening.radius) continue;
|
|
15611
|
+
const du = cell.center[u] - lp[u], dv = cell.center[v] - lp[v];
|
|
15612
|
+
const angle = Math.atan2(dv, du);
|
|
15613
|
+
const edge = radius * (1 + BLAST_EDGE_RAGGED * (0.5 * Math.sin(3 * angle + lobes[0]) + 0.3 * Math.sin(5 * angle + lobes[1]) + 0.4 * (rnd() - 0.5)));
|
|
15614
|
+
const d = Math.hypot(du, dv);
|
|
15615
|
+
if (d <= edge) {
|
|
15616
|
+
worn[i] = -1;
|
|
15617
|
+
out.push(i);
|
|
15618
|
+
} else if (d <= edge * BLAST_EDGE_LOOSE) {
|
|
15619
|
+
const mass = p.mat.density * cell.size[0] * cell.size[1] * cell.size[2];
|
|
15620
|
+
worn[i] = Math.max(worn[i] ?? 0, perKg * mass * BLAST_EDGE_WEAR);
|
|
15621
|
+
}
|
|
15622
|
+
}
|
|
15623
|
+
if (out.length === 0) return false;
|
|
15624
|
+
const loose = unsupportedCells(grid, p.thin, p.dims, (i) => worn[i] !== -1 && !(op && p.st.opening && Math.hypot(grid[i].center[u] - op[u], grid[i].center[v] - op[v]) <= p.st.opening.radius));
|
|
15625
|
+
for (const i of loose) worn[i] = -1;
|
|
15626
|
+
const blown = out.map((i) => grid[i]), fallen = loose.map((i) => grid[i]);
|
|
15627
|
+
const volume = (cells2) => cells2.reduce((sum, cell) => sum + cell.size[0] * cell.size[1] * cell.size[2], 0);
|
|
15628
|
+
const blownMass = Math.min(p.st.mass, p.mat.density * volume(blown));
|
|
15629
|
+
const fallenMass = Math.min(p.st.mass - blownMass, p.mat.density * volume(fallen));
|
|
15630
|
+
const speed = clamp2(Math.sqrt(2 * energy * 0.02 / Math.max(blownMass, 0.05)), 1, 12);
|
|
15631
|
+
const { c, q } = partXf(o, p);
|
|
15632
|
+
const chipped = (_b = p.st).chipped ?? (_b.chipped = []);
|
|
15633
|
+
const tag = `x${chipped.length}_`;
|
|
15634
|
+
const fragments = [
|
|
15635
|
+
...cellFragments(o, p, clumpCells(blown, p.thin, maxFragments), wp, null, speed, blownMass, tag, true),
|
|
15636
|
+
...fallen.length > 0 ? cellFragments(o, p, clumpCells(fallen, p.thin, Math.max(2, Math.round(maxFragments / 4))), wp, null, 0.3, fallenMass, `${tag}f`, true) : []
|
|
15637
|
+
];
|
|
15638
|
+
const cells = [...blown, ...fallen];
|
|
15639
|
+
const holes = localHolesOf(p);
|
|
15640
|
+
p.st.holes = p.st.holes.filter((_, k) => !cells.some((cell) => marksInBox(cell.center, cell.size, void 0, [holes[k]]).length > 0));
|
|
15641
|
+
const lo = [Infinity, Infinity, Infinity], hi = [-Infinity, -Infinity, -Infinity];
|
|
15642
|
+
for (const cell of cells) {
|
|
15643
|
+
chipped.push({ center: [cell.center[0], cell.center[1], cell.center[2]], size: [cell.size[0], cell.size[1], cell.size[2]] });
|
|
15644
|
+
for (let k = 0; k < 3; k++) {
|
|
15645
|
+
lo[k] = Math.min(lo[k], cell.center[k] - cell.size[k] / 2);
|
|
15646
|
+
hi[k] = Math.max(hi[k], cell.center[k] + cell.size[k] / 2);
|
|
15647
|
+
}
|
|
15648
|
+
}
|
|
15649
|
+
p.st.mass = Math.max(0, p.st.mass - blownMass - fallenMass);
|
|
15650
|
+
p.rt.removed = (p.rt.removed ?? 0) + blownMass + fallenMass;
|
|
15651
|
+
markDamaged(p);
|
|
15652
|
+
setStage(o, p, 1, {}, src);
|
|
15653
|
+
const center = toWorld(c, q, [(lo[0] + hi[0]) / 2, (lo[1] + hi[1]) / 2, (lo[2] + hi[2]) / 2]);
|
|
15654
|
+
const units = cells.map((cell) => ({ center: toWorld(c, q, cell.center), size: [cell.size[0], cell.size[1], cell.size[2]] }));
|
|
15655
|
+
emit({ ...base(o, src), type: "chip", part: p.spec.id, center, size: [hi[0] - lo[0], hi[1] - lo[1], hi[2] - lo[2]], rotation: q, fragments, units });
|
|
15656
|
+
debris(o, p, p.mat.breakDebris, center, vec.normalize(vec.sub(center, wp), [0, 1, 0]), 14, 3, src);
|
|
15657
|
+
if (p.spec.reinforcement) exposeRebar(o, p, src);
|
|
15658
|
+
if (p.structural && !isLost(p)) {
|
|
15659
|
+
solveStructure(o);
|
|
15660
|
+
collapseCheck(o, src);
|
|
15661
|
+
}
|
|
15662
|
+
return true;
|
|
15663
|
+
};
|
|
15664
|
+
const dropUnsupported = (o, p, src) => {
|
|
15665
|
+
var _a, _b;
|
|
15666
|
+
if (!p.spec.units || p.spec.units.mortar === p.spec.material || p.st.stripped || isLost(p)) return 0;
|
|
15667
|
+
const [u, v] = planeAxes(p.thin);
|
|
15668
|
+
const grid = gridOf(p);
|
|
15669
|
+
const worn = (_a = p.rt).chips ?? (_a.chips = {});
|
|
15670
|
+
const op = p.st.opening ? toLocal(p.pos, p.q, p.st.opening.center) : null;
|
|
15671
|
+
const opened = (cell) => !!op && !!p.st.opening && Math.hypot(cell.center[u] - op[u], cell.center[v] - op[v]) <= p.st.opening.radius;
|
|
15672
|
+
const loose = unsupportedCells(grid, p.thin, p.dims, (i) => worn[i] !== -1 && !opened(grid[i]));
|
|
15673
|
+
if (loose.length === 0) return 0;
|
|
15674
|
+
for (const i of loose) worn[i] = -1;
|
|
15675
|
+
const fallen = loose.map((i) => grid[i]);
|
|
15676
|
+
const mass = Math.min(p.st.mass, p.mat.density * fallen.reduce((sum, cell) => sum + cell.size[0] * cell.size[1] * cell.size[2], 0));
|
|
15677
|
+
const { c, q } = partXf(o, p);
|
|
15678
|
+
const chipped = (_b = p.st).chipped ?? (_b.chipped = []);
|
|
15679
|
+
const lo = [Infinity, Infinity, Infinity], hi = [-Infinity, -Infinity, -Infinity];
|
|
15680
|
+
for (const cell of fallen) {
|
|
15681
|
+
for (let k = 0; k < 3; k++) {
|
|
15682
|
+
lo[k] = Math.min(lo[k], cell.center[k] - cell.size[k] / 2);
|
|
15683
|
+
hi[k] = Math.max(hi[k], cell.center[k] + cell.size[k] / 2);
|
|
15684
|
+
}
|
|
15685
|
+
}
|
|
15686
|
+
const center = toWorld(c, q, [(lo[0] + hi[0]) / 2, (lo[1] + hi[1]) / 2, (lo[2] + hi[2]) / 2]);
|
|
15687
|
+
const fragments = cellFragments(o, p, clumpCells(fallen, p.thin, Math.max(2, Math.round(maxFragments / 4))), vec.add(center, [0, 1, 0]), null, 0.3, mass, `u${chipped.length}_`, true);
|
|
15688
|
+
const holes = localHolesOf(p);
|
|
15689
|
+
p.st.holes = p.st.holes.filter((_, k) => !fallen.some((cell) => marksInBox(cell.center, cell.size, void 0, [holes[k]]).length > 0));
|
|
15690
|
+
for (const cell of fallen) chipped.push({ center: [cell.center[0], cell.center[1], cell.center[2]], size: [cell.size[0], cell.size[1], cell.size[2]] });
|
|
15691
|
+
p.st.mass = Math.max(0, p.st.mass - mass);
|
|
15692
|
+
p.rt.removed = (p.rt.removed ?? 0) + mass;
|
|
15693
|
+
markDamaged(p);
|
|
15694
|
+
const units = fallen.map((cell) => ({ center: toWorld(c, q, cell.center), size: [cell.size[0], cell.size[1], cell.size[2]] }));
|
|
15695
|
+
emit({ ...base(o, src), type: "chip", part: p.spec.id, center, size: [hi[0] - lo[0], hi[1] - lo[1], hi[2] - lo[2]], rotation: q, fragments, units });
|
|
15696
|
+
debris(o, p, p.mat.breakDebris, center, [0, -1, 0], 6, 1, src);
|
|
15697
|
+
return loose.length;
|
|
15698
|
+
};
|
|
15277
15699
|
const stripPart = (o, p, reinforcement, src) => {
|
|
15278
15700
|
const bars = rebarBars(p.dims, p.thin, reinforcement.spacing);
|
|
15279
15701
|
p.st.stripped = true;
|
|
@@ -15340,6 +15762,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
15340
15762
|
const unitPlan = p.spec.units ? unitFragmentPlan(p, maxFragments * UNIT_FRAGMENT_FACTOR) : [];
|
|
15341
15763
|
const axis = style === "splinter" || style === "snap" ? severAxis(p) : -1;
|
|
15342
15764
|
const cutAt2 = axis < 0 ? void 0 : p.rt.cutAt ?? (origin ? hitLocal[axis] : void 0);
|
|
15765
|
+
const planRnd = p.mat.splitForm ? splitStream(o.id, p.spec.id, hitLocal) : rnd;
|
|
15343
15766
|
const plan = unitPlan.length > 0 ? unitPlan : planFragments({
|
|
15344
15767
|
style,
|
|
15345
15768
|
dims: p.dims,
|
|
@@ -15348,8 +15771,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
15348
15771
|
hit: hitLocal,
|
|
15349
15772
|
fragmentSize: p.mat.fragmentSize ?? [0.04, 0.2],
|
|
15350
15773
|
maxFragments,
|
|
15351
|
-
rnd,
|
|
15774
|
+
rnd: planRnd,
|
|
15352
15775
|
sheet: p.mat.sheet,
|
|
15776
|
+
...p.mat.splitForm ? { form: p.mat.splitForm } : {},
|
|
15353
15777
|
...cutAt2 !== void 0 ? { cut: { at: cutAt2, gap: SEVER_BAND } } : {}
|
|
15354
15778
|
});
|
|
15355
15779
|
const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
|
|
@@ -15488,7 +15912,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
15488
15912
|
const fragments = [];
|
|
15489
15913
|
for (let i = 0; i < n; i++) {
|
|
15490
15914
|
const back = !exit || i % 3 === 2;
|
|
15491
|
-
const
|
|
15915
|
+
const plies = p.mat.splitForm === "plies";
|
|
15916
|
+
const len = scale * (fibre ? 1.2 + 1.2 * rnd() : 3 + 4 * rnd()), w = scale * (0.8 + 0.8 * rnd()), t = scale * (fibre ? 0.7 + 0.6 * rnd() : plies ? 0.15 + 0.15 * rnd() : 0.4 + 0.4 * rnd());
|
|
15492
15917
|
const size = [w, w, w];
|
|
15493
15918
|
size[along] = len;
|
|
15494
15919
|
size[(along + 1) % 3] = w;
|
|
@@ -15649,7 +16074,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
15649
16074
|
checkIgnite(o, p, src);
|
|
15650
16075
|
}
|
|
15651
16076
|
}
|
|
15652
|
-
wearJoints(o, p, Ein * 0.5, void 0, 0, src);
|
|
16077
|
+
wearJoints(o, p, Ein * 0.5, void 0, 0, src, false, 2 * BREACH_BLAST_REACH * Math.cbrt(Math.max(0, energy) / 1e3));
|
|
15653
16078
|
const amount = Ein * DMG_PER_J * (mat.damageScale?.explosive ?? 1);
|
|
15654
16079
|
if (blastFrontSpeed > 0 && dist > 0 && mat.tags?.includes("glass")) {
|
|
15655
16080
|
frontQueue.push({ o, p, due: time + dist / blastFrontSpeed, amount, fragEnergy: Ein * 0.3, point, ...src !== void 0 ? { src } : {} });
|
|
@@ -15659,7 +16084,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
15659
16084
|
const dealt = applyDamage(o, p, amount, "explosive", point, null, Ein * 0.3, 40, src);
|
|
15660
16085
|
res.damage += dealt;
|
|
15661
16086
|
if (p.bend && flexure.enabled) bendByBlast(o, p, point, energy, radius, reach, src);
|
|
15662
|
-
|
|
16087
|
+
const breachReach = BREACH_BLAST_REACH * Math.cbrt(Math.max(0, energy) / 1e3);
|
|
16088
|
+
if (p.spec.units && p.spec.units.mortar !== p.spec.material) blastUnits(o, p, wp, energy, dist, src);
|
|
16089
|
+
else if (p.spec.reinforcement && p.spec.units && dist > MEMBER_CUT_REACH * breachReach) blastUnits(o, p, wp, energy, dist, src);
|
|
16090
|
+
else if (dist <= breachReach) addBreach(o, p, wp, breachAreaOf(p, dealt, true), Ein, null, src);
|
|
15663
16091
|
return reach;
|
|
15664
16092
|
};
|
|
15665
16093
|
const blastProbe = { partsTested: 0, partsNear: 0 };
|
|
@@ -15905,6 +16333,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
15905
16333
|
}
|
|
15906
16334
|
return h | 0;
|
|
15907
16335
|
};
|
|
16336
|
+
const splitStream = (objectId, partId, hit) => {
|
|
16337
|
+
const codes = Array.from(`${objectId}|${partId}`, (char) => char.charCodeAt(0) / 1e3);
|
|
16338
|
+
const stream = createRng(seedOf2(rng2.state() / 1e3, ...codes, hit[0], hit[1], hit[2]));
|
|
16339
|
+
return () => stream.next();
|
|
16340
|
+
};
|
|
15908
16341
|
const throwFragment = (frag, objectId, from, g, centre, index, src, shields, results, reports, mult) => {
|
|
15909
16342
|
const v = frag.velocity;
|
|
15910
16343
|
const speed = Math.hypot(v[0], v[1], v[2]);
|
|
@@ -17321,12 +17754,15 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
17321
17754
|
builder,
|
|
17322
17755
|
porchRoofOnPostsOnly: options.porchRoofOnPostsOnly,
|
|
17323
17756
|
rafterRoof: options.rafterRoof,
|
|
17757
|
+
wallPillarSpacing: options.wallPillarSpacing,
|
|
17758
|
+
visorSections: options.visorSections,
|
|
17324
17759
|
floorCount: floorCount2,
|
|
17325
17760
|
isUnit,
|
|
17326
17761
|
elevation,
|
|
17327
17762
|
isTimber: system.timber,
|
|
17328
17763
|
walls: [],
|
|
17329
17764
|
wallParts: /* @__PURE__ */ new Map(),
|
|
17765
|
+
groundBeams: /* @__PURE__ */ new Map(),
|
|
17330
17766
|
carriers: /* @__PURE__ */ new Map(),
|
|
17331
17767
|
plates: /* @__PURE__ */ new Map(),
|
|
17332
17768
|
pillars: /* @__PURE__ */ new Map(),
|
|
@@ -17397,7 +17833,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
17397
17833
|
builder.rest(deck, base, system.joints.slab);
|
|
17398
17834
|
continue;
|
|
17399
17835
|
}
|
|
17400
|
-
const
|
|
17836
|
+
const half = context.visorSections ? 0.08 : 0.1;
|
|
17837
|
+
const pillar = builder.add({ id: `balcony_column_${room.id}`, role: "column", material: construction.frame.pillars, aabb: [box22[0] - half, box22[1] - half, box22[0] + half, box22[1] + half], y0: 0, y1: context.level(floor) - system.slabThickness }, { floor: 0 });
|
|
17401
17838
|
builder.rest(pillar, GROUND, "rebar");
|
|
17402
17839
|
builder.rest(deck, pillar, "rebar");
|
|
17403
17840
|
}
|
|
@@ -17462,8 +17899,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
17462
17899
|
const top = context.level(floor) - thickness * 0.15;
|
|
17463
17900
|
const out = [];
|
|
17464
17901
|
for (let k = 0; k < count; k++) {
|
|
17465
|
-
const
|
|
17466
|
-
const aabb = alongU ? [
|
|
17902
|
+
const centre2 = (alongU ? box22[0] : box22[1]) + 0.05 + k * (span - 0.1) / (count - 1);
|
|
17903
|
+
const aabb = alongU ? [centre2 - 0.025, box22[1], centre2 + 0.025, box22[3]] : [box22[0], centre2 - 0.025, box22[2], centre2 + 0.025];
|
|
17467
17904
|
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 });
|
|
17468
17905
|
out.push(joist);
|
|
17469
17906
|
const below = carriers.filter((carrier) => carrier.y0 <= top + 0.05);
|
|
@@ -17551,8 +17988,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
17551
17988
|
const at2 = alongU ? box22[1] + line * (box22[3] - box22[1]) / (rows - 1) : box22[0] + line * (box22[2] - box22[0]) / (columns - 1);
|
|
17552
17989
|
const aabb = alongU ? [box22[0], at2 - 0.07, box22[2], at2 + 0.07] : [at2 - 0.07, box22[1], at2 + 0.07, box22[3]];
|
|
17553
17990
|
const beam = builder.add({ id: `base_beam_${line}`, role: "beam", material: construction.frame.timber, aabb, y0: height, y1: elevation - context.system.slabThickness }, { floor: 0 });
|
|
17554
|
-
const
|
|
17555
|
-
for (const pier of piers.filter((q) => Math.abs(
|
|
17991
|
+
const centre2 = (q) => alongU ? (q.aabb[1] + q.aabb[3]) / 2 : (q.aabb[0] + q.aabb[2]) / 2;
|
|
17992
|
+
for (const pier of piers.filter((q) => Math.abs(centre2(q) - at2) < 0.05)) builder.rest(beam, pier, "bolt");
|
|
17556
17993
|
beams.push(beam);
|
|
17557
17994
|
}
|
|
17558
17995
|
return beams;
|
|
@@ -17572,17 +18009,110 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
17572
18009
|
else if (type === "crawl_space" || type === "piles" || type === "basement") addRaisedFloor(context);
|
|
17573
18010
|
else if (context.isTimber && type !== "ground") context.base0 = raft(context, Math.max(0.12, context.elevation), "concrete");
|
|
17574
18011
|
}
|
|
18012
|
+
var GROUND_BEAM_DEPTH = 0.3;
|
|
18013
|
+
function addGroundBeams(context) {
|
|
18014
|
+
const { builder, construction } = context;
|
|
18015
|
+
if (construction.system !== "masonry" || context.isUnit || construction.foundation.type !== "strip_footing") return;
|
|
18016
|
+
const top = context.level(0);
|
|
18017
|
+
for (const wall of context.walls) {
|
|
18018
|
+
if (wall.floor !== 0 || wall.type !== "wall") continue;
|
|
18019
|
+
const beam = builder.add({ id: `ground_beam_${context.groundBeams.size}`, role: "beam", material: construction.frame.beams, aabb: wall.box, y0: top - GROUND_BEAM_DEPTH, y1: top }, { floor: 0, beamKind: "ground_beam" });
|
|
18020
|
+
builder.rest(beam, GROUND, "rebar");
|
|
18021
|
+
context.groundBeams.set(wall.id, beam);
|
|
18022
|
+
}
|
|
18023
|
+
}
|
|
18024
|
+
var WALL_PILLAR_SPACING = 4;
|
|
18025
|
+
var WALL_PILLAR_SIDE = 0.2;
|
|
18026
|
+
var MERGE_DISTANCE = 0.45;
|
|
18027
|
+
var HALF = WALL_PILLAR_SIDE / 2;
|
|
18028
|
+
var TOLERANCE = 0.03;
|
|
18029
|
+
var alongRange = (wall) => wall.axis === "u" ? [Math.min(wall.u0, wall.u1), Math.max(wall.u0, wall.u1)] : [Math.min(wall.v0, wall.v1), Math.max(wall.v0, wall.v1)];
|
|
18030
|
+
var acrossOf = (wall) => wall.axis === "u" ? wall.v0 : wall.u0;
|
|
18031
|
+
function stretchesOf(context, wall) {
|
|
18032
|
+
const [low, high] = alongRange(wall);
|
|
18033
|
+
const windows = (context.source.windows ?? []).filter((w) => w.floor === wall.floor && w.axis === wall.axis && Math.abs(w.pos - acrossOf(wall)) <= TOLERANCE && w.at + w.width / 2 > low && w.at - w.width / 2 < high).map((w) => [w.at - w.width / 2, w.at + w.width / 2]).sort((a, b) => a[0] - b[0]);
|
|
18034
|
+
const out = [];
|
|
18035
|
+
let cursor = low;
|
|
18036
|
+
for (const [from, to] of windows) {
|
|
18037
|
+
if (from - cursor > TOLERANCE) out.push({ wall, from: cursor, to: from });
|
|
18038
|
+
cursor = Math.max(cursor, to);
|
|
18039
|
+
}
|
|
18040
|
+
if (high - cursor > TOLERANCE) out.push({ wall, from: cursor, to: high });
|
|
18041
|
+
return out;
|
|
18042
|
+
}
|
|
18043
|
+
function junctionsOf(context, wall) {
|
|
18044
|
+
const [low, high] = alongRange(wall);
|
|
18045
|
+
const line = acrossOf(wall);
|
|
18046
|
+
const out = [];
|
|
18047
|
+
for (const other of context.walls) {
|
|
18048
|
+
if (other === wall || other.floor !== wall.floor || other.axis === wall.axis || other.type !== "wall") continue;
|
|
18049
|
+
const at2 = acrossOf(other);
|
|
18050
|
+
const [a, b] = alongRange(other);
|
|
18051
|
+
const meets = Math.abs(a - line) <= (wall.thickness ?? 0.15) / 2 + TOLERANCE || Math.abs(b - line) <= (wall.thickness ?? 0.15) / 2 + TOLERANCE;
|
|
18052
|
+
if (meets && at2 > low + HALF && at2 < high - HALF) out.push(at2);
|
|
18053
|
+
}
|
|
18054
|
+
return out;
|
|
18055
|
+
}
|
|
18056
|
+
function pillarStations(from, to, junctions, spacing) {
|
|
18057
|
+
const length = to - from;
|
|
18058
|
+
if (length < WALL_PILLAR_SIDE - 1e-6) return [];
|
|
18059
|
+
if (length < 2 * WALL_PILLAR_SIDE) return [(from + to) / 2];
|
|
18060
|
+
if (length < WALL_PILLAR_SIDE + MERGE_DISTANCE) return [from + HALF, to - HALF];
|
|
18061
|
+
const fixed = [from + HALF, ...junctions.filter((at2) => at2 > from + HALF + MERGE_DISTANCE && at2 < to - HALF - MERGE_DISTANCE), to - HALF].sort((a, b) => a - b);
|
|
18062
|
+
const out = [];
|
|
18063
|
+
fixed.forEach((at2, k) => {
|
|
18064
|
+
out.push(at2);
|
|
18065
|
+
const next = fixed[k + 1];
|
|
18066
|
+
if (next === void 0) return;
|
|
18067
|
+
const spans = Math.ceil((next - at2) / spacing - 1e-6);
|
|
18068
|
+
for (let s = 1; s < spans; s++) out.push(at2 + (next - at2) * s / spans);
|
|
18069
|
+
});
|
|
18070
|
+
return out;
|
|
18071
|
+
}
|
|
18072
|
+
function addWallPillars(context, walls, floor, plates, taken, spacing) {
|
|
18073
|
+
const { builder } = context;
|
|
18074
|
+
const pillars = [];
|
|
18075
|
+
const placed = taken.map((p) => [(p.aabb[0] + p.aabb[2]) / 2, (p.aabb[1] + p.aabb[3]) / 2]);
|
|
18076
|
+
const base = context.baseOf(floor);
|
|
18077
|
+
for (const wall of walls) {
|
|
18078
|
+
if (!wall.exterior || wall.type !== "wall") continue;
|
|
18079
|
+
const top = context.wallParts.get(wall.id).y1;
|
|
18080
|
+
const across = acrossOf(wall);
|
|
18081
|
+
const junctions = junctionsOf(context, wall);
|
|
18082
|
+
for (const stretch of stretchesOf(context, wall)) {
|
|
18083
|
+
const standing = (end) => placed.map(([pu, pv]) => wall.axis === "u" ? [pu, pv] : [pv, pu]).find(([a, c]) => Math.abs(c - across) <= TOLERANCE && Math.abs(a - end) < MERGE_DISTANCE + HALF)?.[0];
|
|
18084
|
+
const first = standing(stretch.from);
|
|
18085
|
+
const last = standing(stretch.to);
|
|
18086
|
+
const from = first === void 0 ? stretch.from : Math.min(stretch.from, first - HALF);
|
|
18087
|
+
const to = last === void 0 ? stretch.to : Math.max(stretch.to, last + HALF);
|
|
18088
|
+
for (const along of pillarStations(from, to, junctions, spacing)) {
|
|
18089
|
+
const [u, v] = wall.axis === "u" ? [along, across] : [across, along];
|
|
18090
|
+
const end = Math.abs(along - stretch.from) < Math.abs(along - stretch.to) ? stretch.from : stretch.to;
|
|
18091
|
+
const endPoint = wall.axis === "u" ? [end, across] : [across, end];
|
|
18092
|
+
const isEnd = Math.abs(along - end) <= HALF + TOLERANCE;
|
|
18093
|
+
if (placed.some(([pu, pv]) => Math.hypot(pu - u, pv - v) < MERGE_DISTANCE && (!isEnd || Math.hypot(pu - endPoint[0], pv - endPoint[1]) < MERGE_DISTANCE))) continue;
|
|
18094
|
+
placed.push([u, v]);
|
|
18095
|
+
const box22 = [u - HALF, v - HALF, u + HALF, v + HALF];
|
|
18096
|
+
const pillar = builder.add({ id: `wall_column_${floor}_${pillars.length}`, role: "column", material: context.construction.frame.pillars, aabb: box22, y0: context.level(floor), y1: top }, { floor });
|
|
18097
|
+
builder.rest(pillar, base === GROUND || !base ? GROUND : base, "rebar");
|
|
18098
|
+
for (const plate of plates.filter((p) => touches(p.aabb, box22, 0.01))) builder.rest(plate, pillar, "rebar");
|
|
18099
|
+
pillars.push(pillar);
|
|
18100
|
+
}
|
|
18101
|
+
}
|
|
18102
|
+
}
|
|
18103
|
+
return pillars;
|
|
18104
|
+
}
|
|
17575
18105
|
var MAX_GAP_WITHIN_RUN = 3.2;
|
|
17576
18106
|
var POST_SPACING = 2.4;
|
|
17577
18107
|
var POST_SIDE = 0.05;
|
|
17578
18108
|
var alongOf = (wall, pick2) => wall.axis === "u" ? pick2(wall.u0, wall.u1) : pick2(wall.v0, wall.v1);
|
|
17579
|
-
var
|
|
18109
|
+
var acrossOf2 = (wall) => wall.axis === "u" ? wall.v0 : wall.u0;
|
|
17580
18110
|
function runsOf(context, walls, floor) {
|
|
17581
18111
|
const lines = /* @__PURE__ */ new Map();
|
|
17582
18112
|
for (const wall of walls) {
|
|
17583
18113
|
if (!wall.exterior || wall.type !== "wall") continue;
|
|
17584
18114
|
const top = round2(context.wallParts.get(wall.id).y1);
|
|
17585
|
-
const key = `${floor}|${wall.axis}|${
|
|
18115
|
+
const key = `${floor}|${wall.axis}|${acrossOf2(wall).toFixed(2)}|${top.toFixed(2)}`;
|
|
17586
18116
|
lines.set(key, [...lines.get(key) ?? [], wall]);
|
|
17587
18117
|
}
|
|
17588
18118
|
return [...lines.values()].map((line) => {
|
|
@@ -17602,7 +18132,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
17602
18132
|
}
|
|
17603
18133
|
function addShackPosts(context, run, plate, floor, index) {
|
|
17604
18134
|
const first = run.walls[0];
|
|
17605
|
-
const across =
|
|
18135
|
+
const across = acrossOf2(first);
|
|
17606
18136
|
const top = context.wallParts.get(first.id).y1;
|
|
17607
18137
|
const count = Math.max(1, Math.ceil((run.to - run.from) / POST_SPACING));
|
|
17608
18138
|
const posts = [];
|
|
@@ -17628,7 +18158,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
17628
18158
|
for (const run of runs) {
|
|
17629
18159
|
const first = run.walls[0];
|
|
17630
18160
|
const half = (first.thickness ?? 0.15) / 2;
|
|
17631
|
-
const across =
|
|
18161
|
+
const across = acrossOf2(first);
|
|
17632
18162
|
const top = context.wallParts.get(first.id).y1;
|
|
17633
18163
|
const aabb = first.axis === "u" ? [run.from - half, across - half, run.to + half, across + half] : [across - half, run.from - half, across + half, run.to + half];
|
|
17634
18164
|
const material = context.isTimber ? construction.frame.timber : construction.frame.beams;
|
|
@@ -17667,7 +18197,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
17667
18197
|
function addPlatesAndPillars(context, floor) {
|
|
17668
18198
|
const walls = context.walls.filter((wall) => wall.floor === floor);
|
|
17669
18199
|
const { plates, pillars } = addPlates(context, walls, floor);
|
|
17670
|
-
if (context.construction.system === "masonry")
|
|
18200
|
+
if (context.construction.system === "masonry") {
|
|
18201
|
+
const corners = addCornerPillars(context, walls, floor, plates);
|
|
18202
|
+
pillars.push(...corners);
|
|
18203
|
+
if (context.wallPillarSpacing) pillars.push(...addWallPillars(context, walls, floor, plates, corners, context.wallPillarSpacing));
|
|
18204
|
+
}
|
|
17671
18205
|
context.pillars.set(floor, pillars);
|
|
17672
18206
|
context.plates.set(floor, plates);
|
|
17673
18207
|
const innerBearing = walls.filter((wall) => wall.role === "wall" && !wall.exterior).map((wall) => context.wallParts.get(wall.id));
|
|
@@ -17942,21 +18476,21 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
17942
18476
|
builder.rest(flight, builder.find("basement_slab"), "resting");
|
|
17943
18477
|
if (context.base0 && context.base0 !== GROUND) builder.rest(flight, context.base0, "nail");
|
|
17944
18478
|
}
|
|
17945
|
-
var
|
|
18479
|
+
var TOLERANCE2 = 0.02;
|
|
17946
18480
|
var DOOR_HEIGHT = 2.1;
|
|
17947
18481
|
var LINTEL_DEPTH = 0.2;
|
|
17948
18482
|
var LINTEL_BEARING = 0.2;
|
|
17949
18483
|
var crossOf = (wall) => wall.axis === "u" ? wall.v0 : wall.u0;
|
|
17950
|
-
var
|
|
18484
|
+
var alongRange2 = (wall) => wall.axis === "u" ? [Math.min(wall.u0, wall.u1), Math.max(wall.u0, wall.u1)] : [Math.min(wall.v0, wall.v1), Math.max(wall.v0, wall.v1)];
|
|
17951
18485
|
function findOpenings(context) {
|
|
17952
18486
|
const openings = [];
|
|
17953
18487
|
for (const window of context.source.windows ?? []) {
|
|
17954
18488
|
const from = window.at - window.width / 2;
|
|
17955
18489
|
const to = window.at + window.width / 2;
|
|
17956
18490
|
const wall = context.walls.find((w) => {
|
|
17957
|
-
if (w.floor !== window.floor || w.axis !== window.axis || w.type !== "wall" || Math.abs(crossOf(w) - window.pos) >
|
|
17958
|
-
const [low, high] =
|
|
17959
|
-
return from >= low -
|
|
18491
|
+
if (w.floor !== window.floor || w.axis !== window.axis || w.type !== "wall" || Math.abs(crossOf(w) - window.pos) > TOLERANCE2) return false;
|
|
18492
|
+
const [low, high] = alongRange2(w);
|
|
18493
|
+
return from >= low - TOLERANCE2 && to <= high + TOLERANCE2;
|
|
17960
18494
|
});
|
|
17961
18495
|
if (!wall) continue;
|
|
17962
18496
|
const base = context.level(window.floor);
|
|
@@ -17971,18 +18505,39 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
17971
18505
|
const cross = door.axis === "u" ? door.v : door.u;
|
|
17972
18506
|
const from = along - door.width / 2;
|
|
17973
18507
|
const to = along + door.width / 2;
|
|
17974
|
-
const beside = context.walls.filter((w) => w.floor === door.floor && w.axis === door.axis && w.type === "wall" && Math.abs(crossOf(w) - cross) <=
|
|
18508
|
+
const beside = context.walls.filter((w) => w.floor === door.floor && w.axis === door.axis && w.type === "wall" && Math.abs(crossOf(w) - cross) <= TOLERANCE2 && (Math.abs(alongRange2(w)[1] - from) <= 0.05 || Math.abs(alongRange2(w)[0] - to) <= 0.05));
|
|
17975
18509
|
if (!beside.length) continue;
|
|
17976
18510
|
const half = (beside[0].thickness ?? 0.15) / 2;
|
|
17977
18511
|
const lo = from - LINTEL_BEARING;
|
|
17978
18512
|
const hi = to + LINTEL_BEARING;
|
|
17979
18513
|
const box22 = door.axis === "u" ? [lo, cross - half, hi, cross + half] : [cross - half, lo, cross + half, hi];
|
|
17980
18514
|
const y0 = context.level(door.floor) + DOOR_HEIGHT;
|
|
17981
|
-
const
|
|
18515
|
+
const depth = Math.max(LINTEL_DEPTH, (door.width + LINTEL_BEARING) / 10);
|
|
18516
|
+
const lintel = builder.add({ id: `lintel_${door.id}`, role: "beam", material: system.timber ? construction.frame.timber : "concrete", aabb: box22, y0, y1: y0 + depth }, { floor: door.floor, ...system.timber ? {} : { assembly: "reinforced_concrete" } });
|
|
17982
18517
|
for (const wall of beside) builder.rest(lintel, context.wallParts.get(wall.id), system.joints.beam);
|
|
18518
|
+
const jambs = (context.pillars.get(door.floor) ?? []).filter((pillar) => pillar.material === "concrete" && overlapsInPlan(pillar.aabb, box22) && pillar.y1 >= y0);
|
|
18519
|
+
if (construction.system === "masonry" && hi - lo > WIDE_DOOR) jambs.push(...addJambPillars(context, { door, beside, from, to, cross }, jambs));
|
|
18520
|
+
for (const pillar of jambs) builder.rest(lintel, pillar, "rebar");
|
|
17983
18521
|
for (const plate of context.plates.get(door.floor) ?? []) if (overlapsInPlan(plate.aabb, box22)) builder.rest(plate, lintel, system.joints.beam);
|
|
17984
18522
|
}
|
|
17985
18523
|
}
|
|
18524
|
+
var WIDE_DOOR = 3;
|
|
18525
|
+
var HALF_PILLAR = WALL_PILLAR_SIDE / 2;
|
|
18526
|
+
function addJambPillars(context, gap, standing) {
|
|
18527
|
+
const { builder, construction } = context;
|
|
18528
|
+
const top = Math.max(...gap.beside.map((w) => context.wallParts.get(w.id).y1));
|
|
18529
|
+
const base = context.baseOf(gap.door.floor);
|
|
18530
|
+
const out = [];
|
|
18531
|
+
for (const [k, along] of [[0, gap.from - HALF_PILLAR], [1, gap.to + HALF_PILLAR]]) {
|
|
18532
|
+
const [u, v] = gap.door.axis === "u" ? [along, gap.cross] : [gap.cross, along];
|
|
18533
|
+
const box22 = [u - HALF_PILLAR, v - HALF_PILLAR, u + HALF_PILLAR, v + HALF_PILLAR];
|
|
18534
|
+
if (standing.some((p) => overlapsInPlan(p.aabb, box22))) continue;
|
|
18535
|
+
const pillar = builder.add({ id: `jamb_column_${gap.door.id}_${k}`, role: "column", material: construction.frame.pillars, aabb: box22, y0: context.level(gap.door.floor), y1: top }, { floor: gap.door.floor });
|
|
18536
|
+
builder.rest(pillar, base === GROUND || !base ? GROUND : base, "rebar");
|
|
18537
|
+
out.push(pillar);
|
|
18538
|
+
}
|
|
18539
|
+
return out;
|
|
18540
|
+
}
|
|
17986
18541
|
var overlapsInPlan = (a, b) => a[0] < b[2] && a[2] > b[0] && a[1] < b[3] && a[3] > b[1];
|
|
17987
18542
|
var RISER = 0.18;
|
|
17988
18543
|
var TREAD = 0.28;
|
|
@@ -18154,10 +18709,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
18154
18709
|
const framed = system.studs !== void 0 && !isRailing;
|
|
18155
18710
|
const part = builder.add(
|
|
18156
18711
|
{ id: wall.id, role: wall.role, material: wall.material, aabb: wall.box, y0, y1: Math.max(y1, y0 + 0.2) },
|
|
18157
|
-
{ floor, fill: context.isTimber && wall.role === "wall" ? system.wallFill : 1, ...framed ? { studs: system.studs } : {}, ...isRailing ? {} : { assembly: assemblyOf(context) } }
|
|
18712
|
+
{ floor, fill: context.isTimber && wall.role === "wall" ? system.wallFill : 1, ...framed ? { studs: system.studs } : {}, ...isRailing ? {} : { assembly: assemblyOf(context) }, ...wall.exterior && !isRailing ? { exterior: true } : {} }
|
|
18158
18713
|
);
|
|
18159
18714
|
context.wallParts.set(wall.id, part);
|
|
18160
|
-
const base = isRailing ? railingBase(context, wall, floor) : context.baseOf(floor);
|
|
18715
|
+
const base = isRailing ? railingBase(context, wall, floor) : context.groundBeams.get(wall.id) ?? context.baseOf(floor);
|
|
18161
18716
|
const joint = isRailing ? "nail" : system.joints.wall;
|
|
18162
18717
|
if (base === GROUND || floor === 0 && !base) builder.rest(part, GROUND, joint);
|
|
18163
18718
|
else if (base) builder.rest(part, base, joint);
|
|
@@ -18173,6 +18728,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
18173
18728
|
addUpperFloor(context, floor);
|
|
18174
18729
|
}
|
|
18175
18730
|
}
|
|
18731
|
+
var wallPillarSpacingOf = (options) => {
|
|
18732
|
+
const pillars = options.wallPillars ?? true;
|
|
18733
|
+
if (pillars === false) return 0;
|
|
18734
|
+
return pillars === true ? WALL_PILLAR_SPACING : pillars.spacing ?? WALL_PILLAR_SPACING;
|
|
18735
|
+
};
|
|
18176
18736
|
function buildStructure(source, options = {}) {
|
|
18177
18737
|
const refined = options.wallDetail !== "coarse";
|
|
18178
18738
|
const built = options.construction ? { construction: options.construction, extras: options.extras ?? {} } : resolveConstruction(source);
|
|
@@ -18180,8 +18740,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
18180
18740
|
const extras = options.extras ?? built.extras ?? {};
|
|
18181
18741
|
if (construction.system === "canvas") return buildCanvasStructure(construction, extras);
|
|
18182
18742
|
const tank = options.tank !== void 0 ? options.tank : source.waterTank ?? null;
|
|
18183
|
-
const context = createContext(source, construction, extras, tank, { porchRoofOnPostsOnly: options.porchRoofOnPostsOnly ?? refined, rafterRoof: options.rafterRoof ?? refined });
|
|
18743
|
+
const context = createContext(source, construction, extras, tank, { porchRoofOnPostsOnly: options.porchRoofOnPostsOnly ?? refined, rafterRoof: options.rafterRoof ?? refined, wallPillarSpacing: wallPillarSpacingOf(options), visorSections: options.visorSections === true });
|
|
18184
18744
|
addFoundation(context);
|
|
18745
|
+
if (options.groundBeams !== false) addGroundBeams(context);
|
|
18185
18746
|
buildFloors(context);
|
|
18186
18747
|
addBalconies(context);
|
|
18187
18748
|
addPorchRoofs(context);
|
|
@@ -18930,6 +19491,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
18930
19491
|
}
|
|
18931
19492
|
var LINTEL_DEPTH2 = 0.2;
|
|
18932
19493
|
var LINTEL_BEARING2 = 0.2;
|
|
19494
|
+
var SILL_BEAM_DEPTH = 0.1;
|
|
19495
|
+
var LINTEL_SPAN_RATIO = 10;
|
|
18933
19496
|
var MIN_PIECE = 0.05;
|
|
18934
19497
|
var frameOf = (wall) => {
|
|
18935
19498
|
const alongU = wall.aabb[2] - wall.aabb[0] >= wall.aabb[3] - wall.aabb[1];
|
|
@@ -18943,6 +19506,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
18943
19506
|
const drafts = [];
|
|
18944
19507
|
const piers = [];
|
|
18945
19508
|
const lintels = [];
|
|
19509
|
+
const sillBeams = [];
|
|
18946
19510
|
const spandrels = [];
|
|
18947
19511
|
const height = wall.y1 - wall.y0;
|
|
18948
19512
|
let cursor = frame2.from;
|
|
@@ -18953,23 +19517,26 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
18953
19517
|
};
|
|
18954
19518
|
own.forEach((opening2, k) => {
|
|
18955
19519
|
addPier(Math.max(cursor, opening2.from));
|
|
18956
|
-
const
|
|
19520
|
+
const span = opening2.to - opening2.from;
|
|
19521
|
+
const lintelTop = Math.min(wall.y1, opening2.y1 + Math.max(LINTEL_DEPTH2, style.lintelDepth?.(span) ?? 0, (span + LINTEL_BEARING2) / LINTEL_SPAN_RATIO));
|
|
18957
19522
|
const lintelFrom = Math.max(frame2.from, opening2.from - LINTEL_BEARING2);
|
|
18958
19523
|
const lintelTo = Math.min(frame2.to, opening2.to + LINTEL_BEARING2);
|
|
18959
19524
|
const lintelHeight = lintelTop - opening2.y1;
|
|
18960
|
-
|
|
19525
|
+
const sillTop = style.sillBeam && opening2.y0 - wall.y0 > SILL_BEAM_DEPTH + MIN_PIECE ? opening2.y0 - SILL_BEAM_DEPTH : opening2.y0;
|
|
19526
|
+
if (sillTop - wall.y0 > MIN_PIECE) drafts.push({ id: `${wall.id}_sill_${k}`, role: "partition", material: wall.material, from: opening2.from, to: opening2.to, y0: wall.y0, y1: sillTop, volume: (opening2.to - opening2.from) * (sillTop - wall.y0) });
|
|
19527
|
+
if (sillTop < opening2.y0) sillBeams.push({ id: `${wall.id}_sillbeam_${k}`, role: "beam", material: style.lintelMaterial, from: lintelFrom, to: lintelTo, y0: sillTop, y1: opening2.y0, volume: (lintelTo - lintelFrom) * SILL_BEAM_DEPTH });
|
|
18961
19528
|
lintels.push({ id: `${wall.id}_lintel_${k}`, role: "beam", material: style.lintelMaterial, from: lintelFrom, to: lintelTo, y0: opening2.y1, y1: lintelTop, volume: (lintelTo - lintelFrom) * lintelHeight });
|
|
18962
19529
|
spandrels.push(wall.y1 - lintelTop > MIN_PIECE ? { id: `${wall.id}_spandrel_${k}`, role: roleOf2, material: wall.material, from: opening2.from, to: opening2.to, y0: lintelTop, y1: wall.y1, volume: (opening2.to - opening2.from) * (wall.y1 - lintelTop) } : void 0);
|
|
18963
19530
|
cursor = opening2.to;
|
|
18964
19531
|
});
|
|
18965
19532
|
addPier(frame2.to);
|
|
18966
19533
|
for (const pier of piers) {
|
|
18967
|
-
for (const lintel of lintels) {
|
|
19534
|
+
for (const lintel of [...lintels, ...sillBeams]) {
|
|
18968
19535
|
const overlap = Math.max(0, Math.min(pier.to, lintel.to) - Math.max(pier.from, lintel.from));
|
|
18969
19536
|
pier.volume -= overlap * (lintel.y1 - lintel.y0);
|
|
18970
19537
|
}
|
|
18971
19538
|
}
|
|
18972
|
-
const all = [...piers, ...drafts, ...lintels, ...spandrels.filter((d) => d !== void 0)];
|
|
19539
|
+
const all = [...piers, ...drafts, ...sillBeams, ...lintels, ...spandrels.filter((d) => d !== void 0)];
|
|
18973
19540
|
const total = all.reduce((sum, d) => sum + d.volume, 0);
|
|
18974
19541
|
const pieces = all.map((d) => ({
|
|
18975
19542
|
id: d.id,
|
|
@@ -18984,6 +19551,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
18984
19551
|
...d.role === "beam" ? style.lintelAssembly ? { assembly: style.lintelAssembly } : {} : wall.assembly ? { assembly: wall.assembly } : {}
|
|
18985
19552
|
}));
|
|
18986
19553
|
const internal = [];
|
|
19554
|
+
for (const beam of sillBeams) {
|
|
19555
|
+
const sill = drafts.find((d) => d.id === beam.id.replace("_sillbeam_", "_sill_"));
|
|
19556
|
+
if (sill) internal.push({ part: beam.id, on: sill.id, joint: style.lintelJoint });
|
|
19557
|
+
for (const pier of piers) if (Math.min(pier.to, beam.to) - Math.max(pier.from, beam.from) > 0) internal.push({ part: beam.id, on: pier.id, joint: style.lintelJoint });
|
|
19558
|
+
}
|
|
18987
19559
|
lintels.forEach((lintel, k) => {
|
|
18988
19560
|
for (const pier of piers) if (Math.min(pier.to, lintel.to) - Math.max(pier.from, lintel.from) > 0) internal.push({ part: lintel.id, on: pier.id, joint: style.lintelJoint });
|
|
18989
19561
|
const spandrel = spandrels[k];
|
|
@@ -19013,7 +19585,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19013
19585
|
...system.timber ? {} : { lintelAssembly: "reinforced_concrete" },
|
|
19014
19586
|
lintelJoint: system.joints.beam,
|
|
19015
19587
|
wallJoint: system.joints.wall,
|
|
19016
|
-
...lintelDepth && !system.timber ? { lintelDepth } : {}
|
|
19588
|
+
...lintelDepth && !system.timber ? { lintelDepth } : {},
|
|
19589
|
+
...construction.system === "masonry" ? { sillBeam: true } : {}
|
|
19017
19590
|
});
|
|
19018
19591
|
if (split.pieces.length > 1) splits.set(part.id, split);
|
|
19019
19592
|
}
|
|
@@ -19021,7 +19594,18 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19021
19594
|
}
|
|
19022
19595
|
function applyPanels(input, structure, detail, lintelDepth) {
|
|
19023
19596
|
if (detail === "coarse") return input;
|
|
19024
|
-
return applySplits(input, panelSplits(input, structure, lintelDepth));
|
|
19597
|
+
return onJambPillars(applySplits(input, panelSplits(input, structure, lintelDepth)));
|
|
19598
|
+
}
|
|
19599
|
+
var OPENING_BEAM = /_(lintel|sillbeam)_\d+$/;
|
|
19600
|
+
var overlaps2 = (a, b) => a[0] < b[2] && a[2] > b[0] && a[1] < b[3] && a[3] > b[1];
|
|
19601
|
+
function onJambPillars(split) {
|
|
19602
|
+
const pillars = split.parts.filter((part) => part.role === "column" && part.material === "concrete");
|
|
19603
|
+
const extra = [];
|
|
19604
|
+
for (const beam of split.parts) {
|
|
19605
|
+
if (beam.role !== "beam" || !OPENING_BEAM.test(beam.id)) continue;
|
|
19606
|
+
for (const pillar of pillars) if (pillar.floor === beam.floor && overlaps2(pillar.aabb, beam.aabb) && pillar.y0 <= beam.y0 && pillar.y1 >= beam.y1 - 0.01) extra.push({ part: beam.id, on: pillar.id, joint: "rebar" });
|
|
19607
|
+
}
|
|
19608
|
+
return extra.length ? { parts: split.parts, supports: [...split.supports, ...extra] } : split;
|
|
19025
19609
|
}
|
|
19026
19610
|
function applyUnits(input, structure) {
|
|
19027
19611
|
const splits = /* @__PURE__ */ new Map();
|
|
@@ -19083,21 +19667,35 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19083
19667
|
};
|
|
19084
19668
|
var MEMBER_CAGE = { spacing: 0.1, diameter: 8e-3, unit: 0.1 };
|
|
19085
19669
|
var CAGED_ROLES = /* @__PURE__ */ new Set(["beam", "column"]);
|
|
19670
|
+
var CAGE_COVER = 0.03;
|
|
19671
|
+
var CAGE_BUCKLING_SHARE = 0.5;
|
|
19672
|
+
var DEFAULT_RESIDUAL = 0.15;
|
|
19673
|
+
function cageResidualCapacity(size, reinforcement, capacity) {
|
|
19674
|
+
const [a = 0, b = 0] = [...size].sort((x, y) => x - y);
|
|
19675
|
+
const perSide = (w) => Math.max(2, Math.floor(Math.max(0, w - 2 * CAGE_COVER) / reinforcement.spacing) + 1);
|
|
19676
|
+
const bars = 2 * perSide(a) + 2 * perSide(b) - 4;
|
|
19677
|
+
const steel = MATERIALS2.find((m) => m.id === reinforcement.material)?.compressive ?? 0;
|
|
19678
|
+
const carries2 = bars * Math.PI * (reinforcement.diameter / 2) ** 2 * steel * CAGE_BUCKLING_SHARE;
|
|
19679
|
+
if (!capacity || capacity <= 0 || carries2 <= 0) return DEFAULT_RESIDUAL;
|
|
19680
|
+
return Math.min(1, Math.max(DEFAULT_RESIDUAL, carries2 / capacity));
|
|
19681
|
+
}
|
|
19086
19682
|
var caged = (spec, part, rebar) => {
|
|
19087
19683
|
if (!rebar || part.material !== "concrete" || !CAGED_ROLES.has(part.role) || spec.reinforcement) return spec;
|
|
19088
19684
|
const size = spec.shape.kind === "box" ? spec.shape.size : null;
|
|
19089
19685
|
if (!size) return spec;
|
|
19090
19686
|
const thin = Math.min(...size);
|
|
19687
|
+
const reinforcement = { material: rebar, spacing: MEMBER_CAGE.spacing, diameter: MEMBER_CAGE.diameter };
|
|
19091
19688
|
return {
|
|
19092
19689
|
...spec,
|
|
19093
|
-
reinforcement: {
|
|
19690
|
+
reinforcement: part.role === "column" ? { ...reinforcement, residualCapacity: cageResidualCapacity(size, reinforcement, spec.structure?.capacity) } : reinforcement,
|
|
19094
19691
|
units: { size: [MEMBER_CAGE.unit, MEMBER_CAGE.unit, thin], bond: "stack", mortar: "concrete" }
|
|
19095
19692
|
};
|
|
19096
19693
|
};
|
|
19097
19694
|
var partSpecOf = (part, capacity, detailed, rebar) => {
|
|
19098
19695
|
if (detailed && part.assembly) return assemblySpec(part, capacity);
|
|
19099
19696
|
const spec = boxSpec(part, capacity);
|
|
19100
|
-
|
|
19697
|
+
const out = detailed ? caged(spec, part, rebar) : spec;
|
|
19698
|
+
return part.beamKind === "ground_beam" ? { ...out, anchored: true, structure: { role: "foundation" } } : out;
|
|
19101
19699
|
};
|
|
19102
19700
|
function toDestructibleSpec(structure, options = {}) {
|
|
19103
19701
|
const { safety = 0.65, redundancy = 0.97, safetyOthers = 0.45 } = options;
|
|
@@ -19113,7 +19711,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19113
19711
|
const walled = applyPanels(slabbed, structure, detail, slabOptions && structure.construction.frame.rebar ? (span) => beamDepthFor(span, slabOptions) : void 0);
|
|
19114
19712
|
const panels = slabOptions && detail !== "coarse" ? deepenBondBeams(walled, structure, slabOptions) : walled;
|
|
19115
19713
|
const sizing = { safety, redundancy, safetyOthers };
|
|
19116
|
-
const
|
|
19714
|
+
const foundation = new Set(panels.parts.filter((part) => part.beamKind === "ground_beam").map((part) => part.id));
|
|
19715
|
+
const onFoundation = foundation.size ? panels.supports.filter((s) => !foundation.has(s.part)).map((s) => foundation.has(s.on) ? { ...s, on: GROUND } : s) : panels.supports;
|
|
19716
|
+
const sized = { ...panels, capacities: options.sized === false ? /* @__PURE__ */ new Map() : sizeCapacities(panels.parts.filter((part) => !foundation.has(part.id)), onFoundation, sizing) };
|
|
19117
19717
|
const { parts, supports, capacities } = detail === "units" ? applyUnits(sized, structure) : sized;
|
|
19118
19718
|
const specs = parts.map((part) => partSpecOf(part, capacities.get(part.id), detail !== "coarse", structure.construction.frame.rebar));
|
|
19119
19719
|
const margin = options.terrainMargin ?? TERRAIN_MARGIN;
|
|
@@ -19178,6 +19778,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19178
19778
|
out.parts = out.parts.map((p, i) => i === ground ? raft2 : p);
|
|
19179
19779
|
return out;
|
|
19180
19780
|
}
|
|
19781
|
+
var LOW_WINDOW = SILL_BEAM_DEPTH + 0.05;
|
|
19181
19782
|
var slabId = (g, f) => f === g.floors - 1 && g.roof ? "roof" : `slab_${f}`;
|
|
19182
19783
|
var storeyBase = (g, f) => f * (g.height + g.slab);
|
|
19183
19784
|
var slabHalf = (g) => [(g.spanX + g.columnSize + 0.2) / 2, (g.spanZ + g.columnSize + 0.2) / 2];
|
|
@@ -19336,7 +19937,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19336
19937
|
parts.push({ id, material: c.material, shape: box2(size), position, structure: { role: "partition" }, tags: ["infill"] });
|
|
19337
19938
|
for (const o of on) rests(joints, id, o, "mortar");
|
|
19338
19939
|
};
|
|
19339
|
-
const bay = (side, k, along,
|
|
19940
|
+
const bay = (side, k, along, centre2, fixed, length, c1, c2) => {
|
|
19340
19941
|
const clear = length - g.columnSize;
|
|
19341
19942
|
const win = clear * c.window;
|
|
19342
19943
|
const pier = (clear - win) / 2;
|
|
@@ -19347,11 +19948,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19347
19948
|
wall(`infill_${f}_${side}_${k}_${name}`, size(u, h), [p[0], y, p[2]], on);
|
|
19348
19949
|
};
|
|
19349
19950
|
const id = (name) => `infill_${f}_${side}_${k}_${name}`;
|
|
19350
|
-
put("sill", clear, sill, yb + sill / 2,
|
|
19351
|
-
put("pl", pier, head - sill, yb + sill + (head - sill) / 2,
|
|
19352
|
-
put("pr", pier, head - sill, yb + sill + (head - sill) / 2,
|
|
19353
|
-
put("head", clear, room - head, yb + head + (room - head) / 2,
|
|
19354
|
-
const gp = at2(
|
|
19951
|
+
put("sill", clear, sill, yb + sill / 2, centre2, [c1, c2]);
|
|
19952
|
+
put("pl", pier, head - sill, yb + sill + (head - sill) / 2, centre2 - clear / 2 + pier / 2, [c1, id("sill")]);
|
|
19953
|
+
put("pr", pier, head - sill, yb + sill + (head - sill) / 2, centre2 + clear / 2 - pier / 2, [c2, id("sill")]);
|
|
19954
|
+
put("head", clear, room - head, yb + head + (room - head) / 2, centre2, [c1, c2]);
|
|
19955
|
+
const gp = at2(centre2);
|
|
19355
19956
|
const gid = `glass_${f}_${side}_${k}`;
|
|
19356
19957
|
parts.push({
|
|
19357
19958
|
id: gid,
|
|
@@ -19995,7 +20596,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19995
20596
|
if (!pc || pc.unit) return sp;
|
|
19996
20597
|
if (pc.role !== "wall" && pc.role !== "partition") {
|
|
19997
20598
|
const def = getAssembly(pc.assembly || (pc.material === "concrete" && ["slab", "beam", "column", "stair"].includes(pc.role) ? "reinforced_concrete" : ""));
|
|
19998
|
-
const
|
|
20599
|
+
const cage = def && def.reinforcement ? pc.role === "column" && sp.shape.kind === "box" ? { ...def.reinforcement, residualCapacity: cageResidualCapacity(sp.shape.size, def.reinforcement, sp.structure?.capacity) } : { ...def.reinforcement } : null;
|
|
20600
|
+
const out = cage && !sp.reinforcement ? { ...sp, reinforcement: cage } : sp;
|
|
19999
20601
|
if (out.reinforcement && !out.units && pc.material === "concrete" && (pc.role === "beam" || pc.role === "column") && sp.shape.kind === "box") {
|
|
20000
20602
|
return { ...out, units: { size: [MEMBER_CAGE.unit, MEMBER_CAGE.unit, Math.min(...sp.shape.size)], bond: "stack", mortar: "concrete" } };
|
|
20001
20603
|
}
|
|
@@ -20023,6 +20625,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
20023
20625
|
const a = pos.array;
|
|
20024
20626
|
const u = mesh2.userData;
|
|
20025
20627
|
if (!u._p0) u._p0 = a.slice();
|
|
20628
|
+
if (!u.plainPositions) Object.defineProperty(u, "plainPositions", { value: u._p0, enumerable: false, configurable: true, writable: true });
|
|
20026
20629
|
const list2 = u.collapsedRanges ?? (u.collapsedRanges = []);
|
|
20027
20630
|
if (!list2.some((r) => r[0] === v0 && r[1] === v1)) list2.push([v0, v1]);
|
|
20028
20631
|
const saved = a.slice(v0 * 3, v1 * 3);
|
|
@@ -20106,10 +20709,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
20106
20709
|
const v = house.worldToLocal(tmp.p.set(p[0], p[1], p[2]));
|
|
20107
20710
|
return [v.x, v.y, v.z];
|
|
20108
20711
|
};
|
|
20109
|
-
function cells(part, units) {
|
|
20712
|
+
function cells(part, units, mask) {
|
|
20110
20713
|
const [u0, v0, u1, v1] = part.aabb;
|
|
20111
|
-
const alongU =
|
|
20714
|
+
const alongU = mask.alongU;
|
|
20112
20715
|
const len = alongU ? u1 - u0 : v1 - v0;
|
|
20716
|
+
const offset = (alongU ? u0 : v0) - mask.start;
|
|
20113
20717
|
const [ul, uh] = units.size;
|
|
20114
20718
|
const rows = Math.max(1, Math.round((part.y1 - part.y0) / uh));
|
|
20115
20719
|
const rowH = (part.y1 - part.y0) / rows;
|
|
@@ -20120,7 +20724,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
20120
20724
|
const shift = units.bond === "running" && r & 1 ? w / 2 : 0;
|
|
20121
20725
|
for (let i = 0; i < per + (shift ? 1 : 0); i++) {
|
|
20122
20726
|
const a = Math.max(0, i * w - shift), b = Math.min(len, i * w - shift + w);
|
|
20123
|
-
if (b - a > 1e-3) out.push({ a, b, y0: part.y0 + r * rowH, y1: part.y0 + (r + 1) * rowH, r, i });
|
|
20727
|
+
if (b - a > 1e-3) out.push({ a: offset + a, b: offset + b, y0: part.y0 + r * rowH, y1: part.y0 + (r + 1) * rowH, r, i });
|
|
20124
20728
|
}
|
|
20125
20729
|
}
|
|
20126
20730
|
return out;
|
|
@@ -20134,8 +20738,21 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
20134
20738
|
for (const q of structure.parts) {
|
|
20135
20739
|
if (q.id !== partId && same(q.aabb, part.aabb) && Math.abs(q.y1 - part.y0) < 0.01 && structure.meshesOf(q.id).some((it) => it.mesh)) return q.id;
|
|
20136
20740
|
}
|
|
20741
|
+
return part.role === "column" ? wallAround(part) : null;
|
|
20742
|
+
}
|
|
20743
|
+
function wallAround(part) {
|
|
20744
|
+
const cu = (part.aabb[0] + part.aabb[2]) / 2, cv = (part.aabb[1] + part.aabb[3]) / 2;
|
|
20745
|
+
for (const q of structure.parts) {
|
|
20746
|
+
if (q.id === part.id || q.role !== "wall" && q.role !== "partition" || !structure.meshesOf(q.id).some((it) => it.mesh)) continue;
|
|
20747
|
+
const inside2 = cu >= q.aabb[0] - 1e-3 && cu <= q.aabb[2] + 1e-3 && cv >= q.aabb[1] - 1e-3 && cv <= q.aabb[3] + 1e-3;
|
|
20748
|
+
if (inside2 && Math.min(q.y1, part.y1) - Math.max(q.y0, part.y0) > 0.5) return q.id;
|
|
20749
|
+
}
|
|
20137
20750
|
return null;
|
|
20138
20751
|
}
|
|
20752
|
+
const pillarSpans = (view) => structure.parts.filter((q) => q.role === "column" && wallAround(q) === view.host).map((q) => {
|
|
20753
|
+
const a = (view.mask.alongU ? q.aabb[0] : q.aabb[1]) - view.mask.start, b = (view.mask.alongU ? q.aabb[2] : q.aabb[3]) - view.mask.start;
|
|
20754
|
+
return { a, b, y0: q.y0, y1: q.y1 };
|
|
20755
|
+
});
|
|
20139
20756
|
const riders = (hostId) => structure.parts.filter((q) => q.id !== hostId && specOf(q.id)?.units && hostOf(q.id) === hostId && !structure.meshesOf(q.id).some((it) => it.mesh)).map((q) => q.id);
|
|
20140
20757
|
function shell(it, mask) {
|
|
20141
20758
|
const g0 = it.mesh.geometry, g = new THREE.BufferGeometry();
|
|
@@ -20224,6 +20841,17 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
20224
20841
|
texture.needsUpdate = true;
|
|
20225
20842
|
return { data, texture, w, h, alongU, start: alongU ? u0 : v0, len, y0, height: y1 - y0, rect: new THREE.Vector4(alongU ? u0 : v0, y0, len, y1 - y0) };
|
|
20226
20843
|
}
|
|
20844
|
+
function besidePillars(c, pillars) {
|
|
20845
|
+
let { a, b } = c;
|
|
20846
|
+
for (const k of pillars) {
|
|
20847
|
+
if (c.y1 <= k.y0 || c.y0 >= k.y1 || b <= k.a || a >= k.b) continue;
|
|
20848
|
+
const centre = (a + b) / 2;
|
|
20849
|
+
if (centre > k.a && centre < k.b) return [];
|
|
20850
|
+
if (centre <= k.a) b = Math.min(b, k.a);
|
|
20851
|
+
else a = Math.max(a, k.b);
|
|
20852
|
+
}
|
|
20853
|
+
return b - a > 1e-3 ? [{ ...c, a, b }] : [];
|
|
20854
|
+
}
|
|
20227
20855
|
const boxDistance = (s, y, a, b, c, d) => Math.hypot(Math.max(a - s, 0, s - b), Math.max(c - y, 0, y - d));
|
|
20228
20856
|
const ellipseDistance = (s, y, cs, cy, rs, ry) => (Math.hypot((s - cs) / Math.max(rs, 1e-4), (y - cy) / Math.max(ry, 1e-4)) - 1) * Math.min(rs, ry);
|
|
20229
20857
|
function covered(c, boxes) {
|
|
@@ -20246,9 +20874,15 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
20246
20874
|
const discs = [];
|
|
20247
20875
|
const flakes = [];
|
|
20248
20876
|
const shown = [];
|
|
20877
|
+
const pillars = pillarSpans(view);
|
|
20249
20878
|
for (const id of view.parts) {
|
|
20250
20879
|
const part = structure.byId.get(id), units2 = specOf(id).units, chips = view.chips.get(id), opening = view.openings.get(id), spalls = view.spalls.get(id);
|
|
20251
|
-
const
|
|
20880
|
+
const own = cells(part, units2, mask);
|
|
20881
|
+
const all = id === view.host ? own.flatMap((c) => besidePillars(c, pillars)) : own;
|
|
20882
|
+
if (view.fallen.has(id)) {
|
|
20883
|
+
holes.push(...all);
|
|
20884
|
+
continue;
|
|
20885
|
+
}
|
|
20252
20886
|
const thick = mask.alongU ? part.aabb[3] - part.aabb[1] : part.aabb[2] - part.aabb[0];
|
|
20253
20887
|
const mid = mask.alongU ? (part.aabb[1] + part.aabb[3]) / 2 : (part.aabb[0] + part.aabb[2]) / 2;
|
|
20254
20888
|
if (opening) discs.push({ s: along(opening.center), y: opening.center[1], r: opening.radius });
|
|
@@ -20344,52 +20978,55 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
20344
20978
|
}
|
|
20345
20979
|
return { center: local(chip.center), along: a, up: u };
|
|
20346
20980
|
}
|
|
20981
|
+
function newView(hostId) {
|
|
20982
|
+
const items = structure.meshesOf(hostId).filter((it) => it.mesh);
|
|
20983
|
+
if (!items.length) return null;
|
|
20984
|
+
const parts = [hostId, ...riders(hostId)].filter((id) => structure.byId.get(id) && specOf(id)?.units);
|
|
20985
|
+
const mask = maskOf(hostId, parts), core = maskOf(hostId, parts);
|
|
20986
|
+
return {
|
|
20987
|
+
host: hostId,
|
|
20988
|
+
parts,
|
|
20989
|
+
items,
|
|
20990
|
+
mask,
|
|
20991
|
+
core,
|
|
20992
|
+
unitMaterial: unitsMaterial(core),
|
|
20993
|
+
shells: [],
|
|
20994
|
+
saved: null,
|
|
20995
|
+
units: null,
|
|
20996
|
+
gone: 0,
|
|
20997
|
+
pits: 0,
|
|
20998
|
+
bounds: drawnBounds(items),
|
|
20999
|
+
chips: new Map(parts.map((id) => [id, []])),
|
|
21000
|
+
spalls: new Map(parts.map((id) => [id, []])),
|
|
21001
|
+
openings: /* @__PURE__ */ new Map(),
|
|
21002
|
+
flakes: 0,
|
|
21003
|
+
fallen: /* @__PURE__ */ new Set()
|
|
21004
|
+
};
|
|
21005
|
+
}
|
|
21006
|
+
function cut(view) {
|
|
21007
|
+
if (view.saved) return;
|
|
21008
|
+
view.shells = view.items.map((it) => shell(it, view.mask));
|
|
21009
|
+
for (const m of view.shells) m.userData.behind = () => view.units;
|
|
21010
|
+
view.saved = view.items.map((it) => ({ it, v0: it.v0, data: collapseRange(it.mesh, it.v0, it.v1) }));
|
|
21011
|
+
}
|
|
20347
21012
|
function draw2(partId, change) {
|
|
20348
21013
|
if (lod !== 0) return false;
|
|
20349
21014
|
const hostId = hostOf(partId);
|
|
20350
21015
|
if (!hostId || !specOf(partId)?.units) return false;
|
|
20351
|
-
|
|
20352
|
-
if (!view)
|
|
20353
|
-
const items = structure.meshesOf(hostId).filter((it) => it.mesh);
|
|
20354
|
-
if (!items.length) return false;
|
|
20355
|
-
const parts = [hostId, ...riders(hostId)].filter((id) => structure.byId.get(id) && specOf(id)?.units);
|
|
20356
|
-
if (!parts.includes(partId)) return false;
|
|
20357
|
-
const mask = maskOf(hostId, parts), core = maskOf(hostId, parts);
|
|
20358
|
-
view = {
|
|
20359
|
-
host: hostId,
|
|
20360
|
-
parts,
|
|
20361
|
-
items,
|
|
20362
|
-
mask,
|
|
20363
|
-
core,
|
|
20364
|
-
unitMaterial: unitsMaterial(core),
|
|
20365
|
-
shells: [],
|
|
20366
|
-
saved: null,
|
|
20367
|
-
units: null,
|
|
20368
|
-
gone: 0,
|
|
20369
|
-
pits: 0,
|
|
20370
|
-
bounds: drawnBounds(items),
|
|
20371
|
-
chips: new Map(parts.map((id) => [id, []])),
|
|
20372
|
-
spalls: new Map(parts.map((id) => [id, []])),
|
|
20373
|
-
openings: /* @__PURE__ */ new Map(),
|
|
20374
|
-
flakes: 0
|
|
20375
|
-
};
|
|
20376
|
-
}
|
|
20377
|
-
if (!view.chips.has(partId)) return false;
|
|
21016
|
+
const view = views.get(hostId) ?? newView(hostId);
|
|
21017
|
+
if (!view || !view.chips.has(partId)) return false;
|
|
20378
21018
|
if (change.opening) {
|
|
20379
21019
|
if (view.openings.has(partId)) return false;
|
|
20380
21020
|
view.openings.set(partId, { center: local(change.opening.center), radius: change.opening.radius });
|
|
20381
21021
|
}
|
|
20382
21022
|
if (change.chip) view.chips.get(partId).push(chipBox(change.chip, view.mask.alongU));
|
|
21023
|
+
for (const chip of change.chips ?? []) view.chips.get(partId).push(chipBox(chip, view.mask.alongU));
|
|
20383
21024
|
if (change.spall) {
|
|
20384
21025
|
const list2 = view.spalls.get(partId);
|
|
20385
21026
|
list2.push({ center: local(change.spall.center), radius: change.spall.radius });
|
|
20386
21027
|
if (list2.length > MAX_FLAKES) list2.shift();
|
|
20387
21028
|
}
|
|
20388
|
-
|
|
20389
|
-
view.shells = view.items.map((it) => shell(it, view.mask));
|
|
20390
|
-
for (const m of view.shells) m.userData.behind = () => view.units;
|
|
20391
|
-
view.saved = view.items.map((it) => ({ it, v0: it.v0, data: collapseRange(it.mesh, it.v0, it.v1) }));
|
|
20392
|
-
}
|
|
21029
|
+
cut(view);
|
|
20393
21030
|
views.set(hostId, view);
|
|
20394
21031
|
if (opts.deferPaint && change.spall) dirty.add(view);
|
|
20395
21032
|
else {
|
|
@@ -20500,14 +21137,32 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
20500
21137
|
*/
|
|
20501
21138
|
chip(event) {
|
|
20502
21139
|
if (event.torn) return false;
|
|
20503
|
-
|
|
21140
|
+
const rotation = event.rotation ?? [0, 0, 0, 1];
|
|
21141
|
+
if (event.units?.length) return draw2(event.part, { chips: event.units.map((unit) => ({ center: unit.center, size: unit.size, rotation })) });
|
|
21142
|
+
return draw2(event.part, { chip: { center: event.center, size: event.size ?? [0, 0, 0], rotation } });
|
|
20504
21143
|
},
|
|
20505
21144
|
/** The part broke or fell: its damage goes (the plain range stays collapsed, the renderer hides the part). */
|
|
20506
21145
|
drop(partId) {
|
|
20507
|
-
|
|
20508
|
-
if (!
|
|
21146
|
+
let host = views.get(partId) ?? [...views.values()].find((view) => view.parts.includes(partId));
|
|
21147
|
+
if (!host && lod === 0 && riders(partId).length > 0) {
|
|
21148
|
+
host = newView(partId);
|
|
21149
|
+
if (host) {
|
|
21150
|
+
cut(host);
|
|
21151
|
+
views.set(partId, host);
|
|
21152
|
+
}
|
|
21153
|
+
}
|
|
21154
|
+
if (!host) return;
|
|
21155
|
+
host.fallen.add(partId);
|
|
21156
|
+
if (host.parts.some((id) => !host.fallen.has(id))) {
|
|
21157
|
+
dirty.delete(host);
|
|
21158
|
+
paint(host);
|
|
21159
|
+
invalidate();
|
|
21160
|
+
invalidateShadow();
|
|
21161
|
+
return;
|
|
21162
|
+
}
|
|
21163
|
+
const v = host;
|
|
20509
21164
|
dispose(v);
|
|
20510
|
-
views.delete(
|
|
21165
|
+
views.delete(v.host);
|
|
20511
21166
|
if (v.saved) dropped.push(v);
|
|
20512
21167
|
},
|
|
20513
21168
|
/** LOD 0 draws the cut wall; LOD 1 and 2 give the wall its box back. */
|
|
@@ -20986,6 +21641,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
20986
21641
|
shared.ctx.noPiece = false;
|
|
20987
21642
|
cursor.separate = opts.pieces === "meshes";
|
|
20988
21643
|
cursor.pc = null;
|
|
21644
|
+
shared.ctx.showPillars = opts.pillars !== "hidden";
|
|
20989
21645
|
shared.ctx.apartment = r.template === "apartment_modern";
|
|
20990
21646
|
shared.ctx.pa = Math.max(3, Math.min(20, pr.floors != null ? pr.floors : 5));
|
|
20991
21647
|
shared.ctx.pf = Math.max(1, Math.min(shared.ctx.pa, pr.floor != null ? pr.floor : 3));
|
|
@@ -22153,10 +22809,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
22153
22809
|
const list2 = events(room);
|
|
22154
22810
|
const columns = groundColumns(parts);
|
|
22155
22811
|
const laj = slabs(parts);
|
|
22156
|
-
const first = columns[0];
|
|
22157
|
-
const mate = columns.
|
|
22158
|
-
|
|
22159
|
-
blunt(room, oid,
|
|
22812
|
+
const first = columns.find((p) => !/^wall_column_/.test(p.id)) ?? columns[0];
|
|
22813
|
+
const mate = columns.filter((p) => p !== first && !/^wall_column_/.test(p.id)).filter((p) => Math.abs(p.position[0] - first.position[0]) < 0.5).sort((a, b) => Math.abs(b.position[2] - first.position[2]) - Math.abs(a.position[2] - first.position[2]))[0] ?? columns[1];
|
|
22814
|
+
const along = columns.filter((p) => /^wall_column_/.test(p.id) && Math.abs(p.position[0] - first.position[0]) < 0.5);
|
|
22815
|
+
for (const p of [first, mate, ...along]) blunt(room, oid, p.id, centre(p.id), 3e6);
|
|
22160
22816
|
step(90);
|
|
22161
22817
|
const fell = collapsed(list2);
|
|
22162
22818
|
expect(laj.some((id) => fell.includes(id)), fill(T2.two_slab_fell, { n: fell.length }));
|
|
@@ -24329,6 +24985,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
24329
24985
|
}
|
|
24330
24986
|
|
|
24331
24987
|
// src/wall.ts
|
|
24988
|
+
var WARM_UP_SHOT = { type: "ballistic", point: [0, 1.2, 0.1], direction: [0, 0, -1], energy: 3500, caliber: 7.62 };
|
|
24989
|
+
var WARM_UP_FRAMES = 5;
|
|
24990
|
+
var isTexture = (value) => typeof value === "object" && value !== null && value.isTexture === true;
|
|
24332
24991
|
var PLASTER = 15262420;
|
|
24333
24992
|
var WALL_ID = "wall_0_0";
|
|
24334
24993
|
function wallPlan(options) {
|
|
@@ -24387,7 +25046,39 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
24387
25046
|
plan,
|
|
24388
25047
|
spec,
|
|
24389
25048
|
build: (T2) => label(draw(T2, plan, houseOptions)),
|
|
24390
|
-
follow: (root, world, objectId) => houses.get(root)?.destruction?.follow?.(world, objectId, spec) ?? null
|
|
25049
|
+
follow: (root, world, objectId) => houses.get(root)?.destruction?.follow?.(world, objectId, spec) ?? null,
|
|
25050
|
+
warmUpProbes: (T2) => {
|
|
25051
|
+
const house = draw(T2, plan, houseOptions);
|
|
25052
|
+
const root = label(house);
|
|
25053
|
+
const world = createDestruction({ seed: 1 });
|
|
25054
|
+
const objectId = world.add(spec, { id: "warm_up_wall" });
|
|
25055
|
+
const destruction = house.destruction;
|
|
25056
|
+
const follower = destruction?.follow?.(world, objectId, spec) ?? null;
|
|
25057
|
+
world.hitPart(objectId, WALL_ID, WARM_UP_SHOT);
|
|
25058
|
+
for (let frame2 = 0; frame2 < WARM_UP_FRAMES; frame2++) {
|
|
25059
|
+
world.update(1 / 60);
|
|
25060
|
+
follower?.update?.();
|
|
25061
|
+
}
|
|
25062
|
+
for (let guard = 0; guard < WARM_UP_FRAMES && (destruction?.breach?.flush?.(Infinity) ?? 0) > 0; guard++) ;
|
|
25063
|
+
root.traverse((node) => {
|
|
25064
|
+
node.visible = true;
|
|
25065
|
+
node.frustumCulled = false;
|
|
25066
|
+
});
|
|
25067
|
+
return {
|
|
25068
|
+
group: root,
|
|
25069
|
+
// the materials stay: the warmup keeps them as anchors, and disposing the last material of a program frees the program it was drawn for (so the
|
|
25070
|
+
// follower is not disposed either: it would put the wall back and dispose the breach's materials; its world goes with the probe)
|
|
25071
|
+
dispose: () => {
|
|
25072
|
+
root.traverse((node) => {
|
|
25073
|
+
node.geometry?.dispose();
|
|
25074
|
+
for (const material of Array.isArray(node.material) ? node.material : node.material ? [node.material] : []) {
|
|
25075
|
+
for (const uniform2 of Object.values(material.uniforms ?? {})) if (isTexture(uniform2.value)) uniform2.value.dispose();
|
|
25076
|
+
material.map?.dispose();
|
|
25077
|
+
}
|
|
25078
|
+
});
|
|
25079
|
+
}
|
|
25080
|
+
};
|
|
25081
|
+
}
|
|
24391
25082
|
};
|
|
24392
25083
|
}
|
|
24393
25084
|
return __toCommonJS(index_exports);
|