@adatechnology/game-items-three 0.7.1 → 0.7.3

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.
@@ -1558,6 +1558,7 @@ var GameItemsThree = (() => {
1558
1558
  }
1559
1559
 
1560
1560
  // ../game-contracts/dist/index.js
1561
+ var DEFAULT_BLAST_IMPULSE = { bodyArea: 0.7, lift: 0.15, minSpeed: 0.3, tntPerJoule: 0.24 / 2e5 };
1561
1562
  var DEFAULT_COST_ALLOCATION_BYTES = 1024 * 1024;
1562
1563
  var DEFAULT_IGNITION_RULES = {
1563
1564
  fillCurve: [
@@ -2730,6 +2731,19 @@ var GameItemsThree = (() => {
2730
2731
  "drink",
2731
2732
  "sticky"
2732
2733
  ]
2734
+ },
2735
+ {
2736
+ id: "mud",
2737
+ color: 4008988,
2738
+ opacity: 0.95,
2739
+ stainColor: 3811866,
2740
+ viscosity: 40,
2741
+ density: 1600,
2742
+ dryTime: 900,
2743
+ gloss: 0.4,
2744
+ tags: [
2745
+ "dirty"
2746
+ ]
2733
2747
  }
2734
2748
  ];
2735
2749
  var LIQUIDS = liquids_default;
@@ -13253,6 +13267,22 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
13253
13267
  return angle === 0 ? q : quat.mul(q, quat.fromAxisAngle([0, 0, 1], angle));
13254
13268
  };
13255
13269
  var clamp = (v, a, b) => Math.max(a, Math.min(b, v));
13270
+ var splitAlong = (box4, count, axis, rnd) => {
13271
+ const shares = Array.from({ length: count }, () => 0.25 + 1.5 * rnd());
13272
+ const total = shares.reduce((sum, share) => sum + share, 0);
13273
+ const out = [];
13274
+ let from = box4.c[axis] - box4.s[axis] / 2;
13275
+ for (const share of shares) {
13276
+ const length = box4.s[axis] * share / total;
13277
+ const c = [box4.c[0], box4.c[1], box4.c[2]];
13278
+ const size = [box4.s[0], box4.s[1], box4.s[2]];
13279
+ c[axis] = from + length / 2;
13280
+ size[axis] = length;
13281
+ out.push({ c, s: size });
13282
+ from += length;
13283
+ }
13284
+ return out;
13285
+ };
13256
13286
  var splinterBox = (inp, box4, limit = inp.maxFragments) => {
13257
13287
  const { rnd } = inp;
13258
13288
  const dims = box4.s;
@@ -13262,35 +13292,81 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
13262
13292
  const w = Math.max(fmin * 1.5, 0.01);
13263
13293
  const c = clamp(Math.round(dims[a] * dims[b] / (w * w)), 3, Math.max(3, Math.floor(limit / 2)));
13264
13294
  const maxLen = fmax * 4;
13265
- const cells = bsp(box4, c, [a, b], rnd);
13295
+ const cells = [];
13296
+ for (const cell of bsp(box4, c, [a, b], rnd)) {
13297
+ const wide = cell.s[a] >= cell.s[b] ? a : b;
13298
+ if (rnd() < 0.4 && cell.s[wide] > fmin * 0.5) for (const part of splitAlong(cell, 2, wide, rnd)) cells.push(part);
13299
+ else cells.push(cell);
13300
+ }
13266
13301
  const pieces = [];
13267
13302
  for (const cell of cells) {
13268
- const extra = rnd() < 0.4 ? 1 : 0;
13303
+ const extra = Math.floor(rnd() * 3);
13269
13304
  const k = Math.max(1, Math.ceil(dims[g] / maxLen)) + extra;
13270
- for (const p of bsp(cell, k, [g], rnd)) pieces.push(p);
13305
+ for (const p of splitAlong(cell, k, g, rnd)) pieces.push(p);
13271
13306
  }
13272
13307
  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] }));
13273
13308
  };
13309
+ var PLY = 3e-3;
13310
+ var PLY_GAP = 0.6;
13311
+ var PLY_SHEET = 0.012;
13312
+ var plyBreak = (inp, root, limit) => {
13313
+ const { rnd } = inp;
13314
+ const dims = root.s;
13315
+ const sorted = [0, 1, 2].sort((x, y) => dims[x] - dims[y]);
13316
+ const t = sorted[0];
13317
+ const [u, v] = [sorted[1], sorted[2]].sort((x, y) => x - y);
13318
+ const along = dims[u] >= dims[v] ? u : v;
13319
+ const across2 = along === u ? v : u;
13320
+ const layers = clamp(Math.round(dims[t] / PLY / 2), 2, 3);
13321
+ const pitch = dims[t] / layers;
13322
+ const th = Math.min(pitch, PLY_SHEET);
13323
+ const per = Math.max(2, Math.floor(limit / layers));
13324
+ const out = [];
13325
+ for (let k = 0; k < layers; k++) {
13326
+ const layer = { c: [root.c[0], root.c[1], root.c[2]], s: [dims[0], dims[1], dims[2]] };
13327
+ layer.c[t] = root.c[t] + (-dims[t] / 2 + (k + 0.5) * pitch) * (pitch > PLY_SHEET ? 1 : 1 + PLY_GAP);
13328
+ layer.s[t] = th;
13329
+ for (const cell of bsp(layer, per, [u, v], rnd)) {
13330
+ if (out.length >= limit) return out;
13331
+ if (rnd() < 0.35) {
13332
+ const strand = cell.s[across2] * (0.15 + 0.2 * rnd());
13333
+ out.push({ center: cell.c, size: [cell.s[along], strand, th * 0.7], q: frame(along, across2, t), shape: "splinter", weight: cell.s[along] * strand * th });
13334
+ } else {
13335
+ 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 });
13336
+ }
13337
+ }
13338
+ }
13339
+ return out;
13340
+ };
13341
+ var granules = (inp, box4, count) => bsp(box4, 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] }));
13342
+ var CRUMBS = 6;
13274
13343
  var severAt = (inp, root, at2, gap) => {
13275
13344
  const dims = root.s;
13276
13345
  const L = [0, 1, 2].sort((x, y) => dims[y] - dims[x])[0];
13277
13346
  const half = dims[L] / 2;
13278
13347
  const lo = clamp(at2 - gap / 2, -half, half), hi = clamp(at2 + gap / 2, -half, half);
13279
13348
  const out = [];
13280
- const stub = (from, to) => {
13349
+ const fibrous = inp.form === "slivers" || inp.form === "plies";
13350
+ const [a, b] = [0, 1, 2].filter((i) => i !== L);
13351
+ const stub = (from, to, breakAtEnd) => {
13281
13352
  if (to - from < 0.02) return;
13282
13353
  const c = [root.c[0], root.c[1], root.c[2]], sz = [dims[0], dims[1], dims[2]];
13283
13354
  c[L] = (from + to) / 2;
13284
13355
  sz[L] = to - from;
13285
- out.push({ center: c, size: sz, q: quat.identity(), shape: "slab", weight: sz[0] * sz[1] * sz[2] });
13356
+ const weight = sz[0] * sz[1] * sz[2];
13357
+ 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 });
13358
+ else out.push({ center: c, size: sz, q: quat.identity(), shape: "slab", weight });
13286
13359
  };
13287
- stub(-half, lo);
13288
- stub(hi, half);
13360
+ stub(-half, lo, true);
13361
+ stub(hi, half, false);
13289
13362
  const band = { c: [root.c[0], root.c[1], root.c[2]], s: [dims[0], dims[1], dims[2]] };
13290
13363
  band.c[L] = (lo + hi) / 2;
13291
13364
  band.s[L] = Math.max(hi - lo, 0.01);
13292
- if (inp.style === "snap") {
13293
- for (const b of bsp(band, Math.max(3, Math.min(6, inp.maxFragments - out.length)), [0, 1, 2], inp.rnd)) out.push({ center: b.c, size: b.s, q: quat.identity(), shape: "chunk", weight: b.s[0] * b.s[1] * b.s[2] });
13365
+ const room = inp.maxFragments - out.length;
13366
+ if (inp.style === "snap" && inp.form === "plies") for (const p of plyBreak(inp, band, Math.max(3, room))) out.push(p);
13367
+ else if (inp.style === "snap" && inp.form === "granules") for (const p of granules(inp, band, clamp(room, 3, 12))) out.push(p);
13368
+ else if (inp.style === "snap") {
13369
+ 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] });
13294
13370
  } else for (const p of splinterBox(inp, band, Math.max(3, inp.maxFragments - out.length))) out.push(p);
13295
13371
  return out;
13296
13372
  };
@@ -13323,8 +13399,18 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
13323
13399
  const [u, v] = [sorted[1], sorted[2]].sort((a, b) => a - b);
13324
13400
  if (style === "snap") {
13325
13401
  if (inp.cut) return severAt(inp, root, inp.cut.at, inp.cut.gap);
13402
+ if (inp.form === "plies") return plyBreak(inp, root, maxFragments);
13326
13403
  const n2 = Math.min(maxFragments, 2 + Math.floor(rnd() * 3));
13327
13404
  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] });
13405
+ if (inp.form !== "granules") return out;
13406
+ const crumbs = Math.min(CRUMBS, maxFragments - out.length);
13407
+ if (crumbs <= 0) return out;
13408
+ const band = { c: [0, 0, 0], s: [0, 0, 0] };
13409
+ for (let i = 0; i < 3; i++) {
13410
+ band.s[i] = Math.min(dims[i], Math.max(fmin, dims[i] * 0.2));
13411
+ band.c[i] = clamp(inp.hit[i], -dims[i] / 2 + band.s[i] / 2, dims[i] / 2 - band.s[i] / 2);
13412
+ }
13413
+ for (const p of granules(inp, band, crumbs)) out.push({ ...p, weight: p.weight * 0.5 });
13328
13414
  return out;
13329
13415
  }
13330
13416
  if (style === "crumble" || style === "none") {
@@ -13431,6 +13517,88 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
13431
13517
  }
13432
13518
  return marks;
13433
13519
  };
13520
+ var JOINT_GAP = 1e-4;
13521
+ var MIN_BEARING_SHARE = 0.25;
13522
+ var unsupportedCells = (cells, thin, dims, standing) => {
13523
+ if (thin === 1) return [];
13524
+ const [u, v] = planeAxes(thin);
13525
+ const bottom = -dims[v] / 2;
13526
+ const rowOf = (y) => Math.round((y - bottom) * 1e4);
13527
+ const rows = /* @__PURE__ */ new Map();
13528
+ for (let i = 0; i < cells.length; i++) {
13529
+ if (!standing(i)) continue;
13530
+ const cell = cells[i];
13531
+ const key2 = rowOf(cell.center[v] - cell.size[v] / 2);
13532
+ const row = rows.get(key2);
13533
+ if (row) row.push(i);
13534
+ else rows.set(key2, [i]);
13535
+ }
13536
+ const held = /* @__PURE__ */ new Set();
13537
+ const queue = [];
13538
+ for (const i of rows.get(0) ?? []) {
13539
+ held.add(i);
13540
+ queue.push(i);
13541
+ }
13542
+ 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);
13543
+ const climb = () => {
13544
+ while (queue.length > 0) {
13545
+ const cell = cells[queue.pop()];
13546
+ for (const j of rows.get(rowOf(cell.center[v] + cell.size[v] / 2)) ?? []) {
13547
+ const other = cells[j];
13548
+ if (held.has(j) || overlap(cell, other) <= Math.max(JOINT_GAP, MIN_BEARING_SHARE * Math.min(cell.size[u], other.size[u]))) continue;
13549
+ held.add(j);
13550
+ queue.push(j);
13551
+ }
13552
+ }
13553
+ };
13554
+ for (const row of rows.values()) row.sort((a, b) => cells[a].center[u] - cells[b].center[u]);
13555
+ const touching = (a, b) => Math.abs(a.center[u] + a.size[u] / 2 - (b.center[u] - b.size[u] / 2)) <= 1e-3;
13556
+ for (let wedged = true; wedged; ) {
13557
+ climb();
13558
+ wedged = false;
13559
+ for (const row of rows.values()) {
13560
+ for (let k = 1; k < row.length - 1; k++) {
13561
+ const i = row[k];
13562
+ if (held.has(i) || !held.has(row[k - 1]) || !held.has(row[k + 1])) continue;
13563
+ if (!touching(cells[row[k - 1]], cells[i]) || !touching(cells[i], cells[row[k + 1]])) continue;
13564
+ held.add(i);
13565
+ queue.push(i);
13566
+ wedged = true;
13567
+ }
13568
+ }
13569
+ }
13570
+ const loose = [];
13571
+ for (const row of rows.values()) for (const i of row) if (!held.has(i)) loose.push(i);
13572
+ return loose;
13573
+ };
13574
+ var clumpCells = (cells, thin, cap) => {
13575
+ if (cells.length <= cap) return [...cells];
13576
+ const [u, v] = planeAxes(thin);
13577
+ let lo0 = Infinity, lo1 = Infinity, hi0 = -Infinity, hi1 = -Infinity;
13578
+ for (const cell of cells) {
13579
+ lo0 = Math.min(lo0, cell.center[u] - cell.size[u] / 2);
13580
+ hi0 = Math.max(hi0, cell.center[u] + cell.size[u] / 2);
13581
+ lo1 = Math.min(lo1, cell.center[v] - cell.size[v] / 2);
13582
+ hi1 = Math.max(hi1, cell.center[v] + cell.size[v] / 2);
13583
+ }
13584
+ let side = Math.sqrt((hi0 - lo0) * (hi1 - lo1) / cap);
13585
+ for (; ; ) {
13586
+ const bins = /* @__PURE__ */ new Map();
13587
+ for (const cell of cells) {
13588
+ const key2 = `${Math.floor((cell.center[u] - lo0) / side)}:${Math.floor((cell.center[v] - lo1) / side)}`;
13589
+ const bin = bins.get(key2) ?? { lo: [Infinity, Infinity, Infinity], hi: [-Infinity, -Infinity, -Infinity] };
13590
+ for (let k = 0; k < 3; k++) {
13591
+ bin.lo[k] = Math.min(bin.lo[k], cell.center[k] - cell.size[k] / 2);
13592
+ bin.hi[k] = Math.max(bin.hi[k], cell.center[k] + cell.size[k] / 2);
13593
+ }
13594
+ bins.set(key2, bin);
13595
+ }
13596
+ if (bins.size <= cap) {
13597
+ 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]] }));
13598
+ }
13599
+ side *= 1.25;
13600
+ }
13601
+ };
13434
13602
  var MAX_SPALLS = 64;
13435
13603
  var thinAxisOf = (dims) => dims[0] <= dims[1] && dims[0] <= dims[2] ? 0 : dims[1] <= dims[2] ? 1 : 2;
13436
13604
  var resolveLayer = (def, index, mat) => ({ index, def, mat, energy: mat.penetration * def.thickness * 1e3 });
@@ -14410,6 +14578,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
14410
14578
  },
14411
14579
  {
14412
14580
  id: "wood_soft",
14581
+ splitForm: "slivers",
14413
14582
  density: 500,
14414
14583
  hpPerKg: 90,
14415
14584
  penetration: 9,
@@ -14455,6 +14624,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
14455
14624
  },
14456
14625
  {
14457
14626
  id: "wood_hard",
14627
+ splitForm: "slivers",
14458
14628
  density: 750,
14459
14629
  hpPerKg: 140,
14460
14630
  penetration: 14,
@@ -14499,6 +14669,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
14499
14669
  },
14500
14670
  {
14501
14671
  id: "plywood",
14672
+ splitForm: "plies",
14502
14673
  density: 600,
14503
14674
  hpPerKg: 70,
14504
14675
  penetration: 7,
@@ -14546,6 +14717,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
14546
14717
  },
14547
14718
  {
14548
14719
  id: "mdf",
14720
+ splitForm: "granules",
14549
14721
  density: 750,
14550
14722
  hpPerKg: 45,
14551
14723
  penetration: 6,
@@ -15057,6 +15229,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
15057
15229
  },
15058
15230
  {
15059
15231
  id: "green_wood",
15232
+ splitForm: "slivers",
15060
15233
  density: 650,
15061
15234
  hpPerKg: 5,
15062
15235
  penetration: 10,
@@ -15337,6 +15510,125 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
15337
15510
  "brittle"
15338
15511
  ],
15339
15512
  compressive: 1e5
15513
+ },
15514
+ {
15515
+ id: "mud",
15516
+ density: 1700,
15517
+ hpPerKg: 1,
15518
+ penetration: 5,
15519
+ ductility: 0.8,
15520
+ fracture: "crumble",
15521
+ fragmentSize: [
15522
+ 0.02,
15523
+ 0.08
15524
+ ],
15525
+ hitDebris: "dust",
15526
+ breakDebris: "dust",
15527
+ thermal: {
15528
+ conductivity: 0.5,
15529
+ heatCapacity: 3.2
15530
+ },
15531
+ tags: [
15532
+ "soil",
15533
+ "wet"
15534
+ ],
15535
+ compressive: 5e3
15536
+ },
15537
+ {
15538
+ id: "clay",
15539
+ density: 1800,
15540
+ hpPerKg: 5,
15541
+ penetration: 7,
15542
+ ductility: 0.05,
15543
+ fracture: "crumble",
15544
+ fragmentSize: [
15545
+ 0.02,
15546
+ 0.1
15547
+ ],
15548
+ hitDebris: "chips",
15549
+ breakDebris: "dust",
15550
+ thermal: {
15551
+ conductivity: 0.15,
15552
+ heatCapacity: 2.2
15553
+ },
15554
+ tags: [
15555
+ "soil",
15556
+ "brittle"
15557
+ ],
15558
+ compressive: 6e4
15559
+ },
15560
+ {
15561
+ id: "snow",
15562
+ density: 300,
15563
+ hpPerKg: 1,
15564
+ penetration: 3,
15565
+ ductility: 0.6,
15566
+ fracture: "crumble",
15567
+ fragmentSize: [
15568
+ 0.01,
15569
+ 0.05
15570
+ ],
15571
+ hitDebris: "dust",
15572
+ breakDebris: "dust",
15573
+ thermal: {
15574
+ conductivity: 0.1,
15575
+ heatCapacity: 2.1
15576
+ },
15577
+ tags: [
15578
+ "soil",
15579
+ "frozen"
15580
+ ],
15581
+ compressive: 1e3
15582
+ },
15583
+ {
15584
+ id: "cobble",
15585
+ density: 2650,
15586
+ hpPerKg: 25,
15587
+ penetration: 25,
15588
+ penetrationClass: "concrete",
15589
+ penetrationScale: 0.9,
15590
+ ductility: 0.03,
15591
+ fracture: "crumble",
15592
+ fragmentSize: [
15593
+ 0.05,
15594
+ 0.15
15595
+ ],
15596
+ hitDebris: "chips",
15597
+ breakDebris: "chips",
15598
+ thermal: {
15599
+ conductivity: 0.2,
15600
+ heatCapacity: 2.5
15601
+ },
15602
+ tags: [
15603
+ "masonry",
15604
+ "brittle"
15605
+ ],
15606
+ compressive: 3e5
15607
+ },
15608
+ {
15609
+ id: "paver",
15610
+ density: 2300,
15611
+ hpPerKg: 14,
15612
+ penetration: 18,
15613
+ penetrationClass: "concrete",
15614
+ penetrationScale: 1,
15615
+ ductility: 0.03,
15616
+ fracture: "crumble",
15617
+ fragmentSize: [
15618
+ 0.04,
15619
+ 0.12
15620
+ ],
15621
+ hitDebris: "chips",
15622
+ breakDebris: "chips",
15623
+ thermal: {
15624
+ conductivity: 0.15,
15625
+ heatCapacity: 2.5
15626
+ },
15627
+ tags: [
15628
+ "masonry",
15629
+ "brittle"
15630
+ ],
15631
+ compressive: 35e4
15340
15632
  }
15341
15633
  ];
15342
15634
  var material_hints_default = {
@@ -15549,6 +15841,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
15549
15841
  var BREACH_BLAST_FACTOR = 6;
15550
15842
  var BREACH_AREA = 0.25;
15551
15843
  var BREACH_BLAST_REACH = 0.12;
15844
+ var MEMBER_CUT_REACH = 0.5;
15845
+ var BLAST_REFERENCE_RESISTANCE = 200 * 1900 * 0.13;
15846
+ var BLAST_EDGE_RAGGED = 0.3;
15847
+ var BLAST_EDGE_LOOSE = 1.35;
15848
+ var BLAST_EDGE_WEAR = 0.6;
15552
15849
  var RESIDUAL_CAPACITY = 0.15;
15553
15850
  var SEVER_BAND = 0.1;
15554
15851
  var SEVER_ASPECT = 3;
@@ -16801,7 +17098,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
16801
17098
  if (j.st.open) continue;
16802
17099
  if (j.spec.kind === "rebar" && (isReinforced(o.parts[j.ia].spec) || isReinforced(o.parts[j.ib].spec))) continue;
16803
17100
  if (near && jointReach(o, j, near) > Math.max(radius, jointReachLimit(o, j))) continue;
16804
- j.st.hp -= span === void 0 ? amount : amount * Math.min(1, span / Math.max(jointLength(o, j), 1e-6));
17101
+ j.st.hp -= span === void 0 ? amount : Math.min(amount, j.st.maxHp) * Math.min(1, span / Math.max(jointLength(o, j), 1e-6));
16805
17102
  if (j.st.hp <= 0) {
16806
17103
  j.st.hp = 0;
16807
17104
  j.st.open = true;
@@ -16929,6 +17226,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
16929
17226
  const worn = (_a = p.rt).chips ?? (_a.chips = {});
16930
17227
  const cut = (_b = p.rt).craters ?? (_b.craters = {});
16931
17228
  const out = [];
17229
+ const wholeBefore = Object.values(worn).filter((w) => w === -1).length;
16932
17230
  const shot = kind === "ballistic";
16933
17231
  for (let i = 0; i < grid.length; i++) {
16934
17232
  const cell = grid[i];
@@ -16987,6 +17285,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
16987
17285
  emit({ ...base(o, src), type: "chip", part: p.spec.id, center, size: [cell.size[0], cell.size[1], cell.size[2]], rotation: q, fragments });
16988
17286
  debris(o, p, p.mat.breakDebris, center, vec.scale(dir ?? [0, 1, 0], -1), 6, 2, src);
16989
17287
  }
17288
+ if (Object.values(worn).filter((w) => w === -1).length > wholeBefore) dropUnsupported(o, p, src);
16990
17289
  if (p.spec.reinforcement) exposeRebar(o, p, src);
16991
17290
  for (const cell of out) addBreach(o, p, toWorld(c, q, cell.center), cell.size[u] * cell.size[v], energy, dir, src);
16992
17291
  if (p.structural && !isLost(p)) {
@@ -17090,10 +17389,122 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
17090
17389
  setStage(o, p, 1, {}, src);
17091
17390
  setStage(o, p, 2, { opening: { center: worldCenter, radius }, fragments }, src);
17092
17391
  debris(o, p, p.mat.breakDebris, worldCenter, vec.scale(dir ?? [0, 1, 0], -1), 14, 3, src);
17392
+ dropUnsupported(o, p, src);
17093
17393
  const reinforcement = p.spec.reinforcement;
17094
17394
  if (reinforcement) exposeRebar(o, p, src);
17095
17395
  if (p.structural) solveStructure(o);
17096
17396
  };
17397
+ const blastReachOf = (p, energy) => {
17398
+ const perKg = p.mat.hits?.blunt?.unitJoules ?? chipEnergy;
17399
+ const resistance = Math.max(1, perKg * p.mat.density * p.thickness);
17400
+ return BREACH_BLAST_REACH * Math.cbrt(Math.max(0, energy) / 1e3) * clamp2(Math.cbrt(BLAST_REFERENCE_RESISTANCE / resistance), 0.3, 2);
17401
+ };
17402
+ const blastUnits = (o, p, wp, energy, dist, src) => {
17403
+ var _a, _b;
17404
+ if (!p.staged || p.st.stripped || isLost(p) || chipEnergy <= 0) return false;
17405
+ const reach = blastReachOf(p, energy);
17406
+ if (dist >= reach) return false;
17407
+ const radius = Math.sqrt(reach * reach - dist * dist);
17408
+ const [u, v] = planeAxes(p.thin);
17409
+ const lp = toLocal(p.pos, p.q, objLocal(o, wp));
17410
+ const op = p.st.opening ? toLocal(p.pos, p.q, p.st.opening.center) : null;
17411
+ const grid = gridOf(p);
17412
+ const worn = (_a = p.rt).chips ?? (_a.chips = {});
17413
+ const perKg = p.mat.hits?.blunt?.unitJoules ?? chipEnergy;
17414
+ const lobes = [rnd() * 2 * Math.PI, rnd() * 2 * Math.PI];
17415
+ const out = [];
17416
+ for (let i = 0; i < grid.length; i++) {
17417
+ const cell = grid[i];
17418
+ if (worn[i] === -1) continue;
17419
+ if (op && p.st.opening && Math.hypot(cell.center[u] - op[u], cell.center[v] - op[v]) <= p.st.opening.radius) continue;
17420
+ const du = cell.center[u] - lp[u], dv = cell.center[v] - lp[v];
17421
+ const angle = Math.atan2(dv, du);
17422
+ 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)));
17423
+ const d = Math.hypot(du, dv);
17424
+ if (d <= edge) {
17425
+ worn[i] = -1;
17426
+ out.push(i);
17427
+ } else if (d <= edge * BLAST_EDGE_LOOSE) {
17428
+ const mass = p.mat.density * cell.size[0] * cell.size[1] * cell.size[2];
17429
+ worn[i] = Math.max(worn[i] ?? 0, perKg * mass * BLAST_EDGE_WEAR);
17430
+ }
17431
+ }
17432
+ if (out.length === 0) return false;
17433
+ 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));
17434
+ for (const i of loose) worn[i] = -1;
17435
+ const blown = out.map((i) => grid[i]), fallen = loose.map((i) => grid[i]);
17436
+ const volume = (cells2) => cells2.reduce((sum, cell) => sum + cell.size[0] * cell.size[1] * cell.size[2], 0);
17437
+ const blownMass = Math.min(p.st.mass, p.mat.density * volume(blown));
17438
+ const fallenMass = Math.min(p.st.mass - blownMass, p.mat.density * volume(fallen));
17439
+ const speed = clamp2(Math.sqrt(2 * energy * 0.02 / Math.max(blownMass, 0.05)), 1, 12);
17440
+ const { c, q } = partXf(o, p);
17441
+ const chipped = (_b = p.st).chipped ?? (_b.chipped = []);
17442
+ const tag = `x${chipped.length}_`;
17443
+ const fragments = [
17444
+ ...cellFragments(o, p, clumpCells(blown, p.thin, maxFragments), wp, null, speed, blownMass, tag, true),
17445
+ ...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) : []
17446
+ ];
17447
+ const cells = [...blown, ...fallen];
17448
+ const holes = localHolesOf(p);
17449
+ p.st.holes = p.st.holes.filter((_, k) => !cells.some((cell) => marksInBox(cell.center, cell.size, void 0, [holes[k]]).length > 0));
17450
+ const lo = [Infinity, Infinity, Infinity], hi = [-Infinity, -Infinity, -Infinity];
17451
+ for (const cell of cells) {
17452
+ chipped.push({ center: [cell.center[0], cell.center[1], cell.center[2]], size: [cell.size[0], cell.size[1], cell.size[2]] });
17453
+ for (let k = 0; k < 3; k++) {
17454
+ lo[k] = Math.min(lo[k], cell.center[k] - cell.size[k] / 2);
17455
+ hi[k] = Math.max(hi[k], cell.center[k] + cell.size[k] / 2);
17456
+ }
17457
+ }
17458
+ p.st.mass = Math.max(0, p.st.mass - blownMass - fallenMass);
17459
+ p.rt.removed = (p.rt.removed ?? 0) + blownMass + fallenMass;
17460
+ markDamaged(p);
17461
+ setStage(o, p, 1, {}, src);
17462
+ const center = toWorld(c, q, [(lo[0] + hi[0]) / 2, (lo[1] + hi[1]) / 2, (lo[2] + hi[2]) / 2]);
17463
+ const units = cells.map((cell) => ({ center: toWorld(c, q, cell.center), size: [cell.size[0], cell.size[1], cell.size[2]] }));
17464
+ 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 });
17465
+ debris(o, p, p.mat.breakDebris, center, vec.normalize(vec.sub(center, wp), [0, 1, 0]), 14, 3, src);
17466
+ if (p.spec.reinforcement) exposeRebar(o, p, src);
17467
+ if (p.structural && !isLost(p)) {
17468
+ solveStructure(o);
17469
+ collapseCheck(o, src);
17470
+ }
17471
+ return true;
17472
+ };
17473
+ const dropUnsupported = (o, p, src) => {
17474
+ var _a, _b;
17475
+ if (!p.spec.units || p.spec.units.mortar === p.spec.material || p.st.stripped || isLost(p)) return 0;
17476
+ const [u, v] = planeAxes(p.thin);
17477
+ const grid = gridOf(p);
17478
+ const worn = (_a = p.rt).chips ?? (_a.chips = {});
17479
+ const op = p.st.opening ? toLocal(p.pos, p.q, p.st.opening.center) : null;
17480
+ const opened = (cell) => !!op && !!p.st.opening && Math.hypot(cell.center[u] - op[u], cell.center[v] - op[v]) <= p.st.opening.radius;
17481
+ const loose = unsupportedCells(grid, p.thin, p.dims, (i) => worn[i] !== -1 && !opened(grid[i]));
17482
+ if (loose.length === 0) return 0;
17483
+ for (const i of loose) worn[i] = -1;
17484
+ const fallen = loose.map((i) => grid[i]);
17485
+ 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));
17486
+ const { c, q } = partXf(o, p);
17487
+ const chipped = (_b = p.st).chipped ?? (_b.chipped = []);
17488
+ const lo = [Infinity, Infinity, Infinity], hi = [-Infinity, -Infinity, -Infinity];
17489
+ for (const cell of fallen) {
17490
+ for (let k = 0; k < 3; k++) {
17491
+ lo[k] = Math.min(lo[k], cell.center[k] - cell.size[k] / 2);
17492
+ hi[k] = Math.max(hi[k], cell.center[k] + cell.size[k] / 2);
17493
+ }
17494
+ }
17495
+ const center = toWorld(c, q, [(lo[0] + hi[0]) / 2, (lo[1] + hi[1]) / 2, (lo[2] + hi[2]) / 2]);
17496
+ 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);
17497
+ const holes = localHolesOf(p);
17498
+ p.st.holes = p.st.holes.filter((_, k) => !fallen.some((cell) => marksInBox(cell.center, cell.size, void 0, [holes[k]]).length > 0));
17499
+ 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]] });
17500
+ p.st.mass = Math.max(0, p.st.mass - mass);
17501
+ p.rt.removed = (p.rt.removed ?? 0) + mass;
17502
+ markDamaged(p);
17503
+ const units = fallen.map((cell) => ({ center: toWorld(c, q, cell.center), size: [cell.size[0], cell.size[1], cell.size[2]] }));
17504
+ 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 });
17505
+ debris(o, p, p.mat.breakDebris, center, [0, -1, 0], 6, 1, src);
17506
+ return loose.length;
17507
+ };
17097
17508
  const stripPart = (o, p, reinforcement, src) => {
17098
17509
  const bars = rebarBars(p.dims, p.thin, reinforcement.spacing);
17099
17510
  p.st.stripped = true;
@@ -17160,6 +17571,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
17160
17571
  const unitPlan = p.spec.units ? unitFragmentPlan(p, maxFragments * UNIT_FRAGMENT_FACTOR) : [];
17161
17572
  const axis = style === "splinter" || style === "snap" ? severAxis(p) : -1;
17162
17573
  const cutAt2 = axis < 0 ? void 0 : p.rt.cutAt ?? (origin ? hitLocal[axis] : void 0);
17574
+ const planRnd = p.mat.splitForm ? splitStream(o.id, p.spec.id, hitLocal) : rnd;
17163
17575
  const plan = unitPlan.length > 0 ? unitPlan : planFragments({
17164
17576
  style,
17165
17577
  dims: p.dims,
@@ -17168,8 +17580,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
17168
17580
  hit: hitLocal,
17169
17581
  fragmentSize: p.mat.fragmentSize ?? [0.04, 0.2],
17170
17582
  maxFragments,
17171
- rnd,
17583
+ rnd: planRnd,
17172
17584
  sheet: p.mat.sheet,
17585
+ ...p.mat.splitForm ? { form: p.mat.splitForm } : {},
17173
17586
  ...cutAt2 !== void 0 ? { cut: { at: cutAt2, gap: SEVER_BAND } } : {}
17174
17587
  });
17175
17588
  const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
@@ -17214,7 +17627,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
17214
17627
  return;
17215
17628
  }
17216
17629
  if (ex) {
17217
- emit({ ...base(o, src), type: "explode", part: p.spec.id, point: c, energy: ex.energy, radius: ex.radius, ...ex.lethalRadius !== void 0 ? { lethalRadius: ex.lethalRadius } : {}, ...ex.effect !== void 0 ? { effect: ex.effect } : {} });
17630
+ emit({ ...base(o, src), type: "explode", part: p.spec.id, point: c, energy: ex.energy, radius: ex.radius, ...ex.lethalRadius !== void 0 ? { lethalRadius: ex.lethalRadius } : {}, ...ex.tnt !== void 0 ? { tnt: ex.tnt } : {}, ...ex.effect !== void 0 ? { effect: ex.effect } : {} });
17218
17631
  blasts.push({ point: c, energy: ex.energy, radius: ex.radius, ...ex.lethalRadius !== void 0 ? { lethalRadius: ex.lethalRadius } : {}, source: src });
17219
17632
  }
17220
17633
  partLost(o, p, src);
@@ -17308,7 +17721,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
17308
17721
  const fragments = [];
17309
17722
  for (let i = 0; i < n; i++) {
17310
17723
  const back = !exit || i % 3 === 2;
17311
- 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() : 0.4 + 0.4 * rnd());
17724
+ const plies = p.mat.splitForm === "plies";
17725
+ 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());
17312
17726
  const size = [w, w, w];
17313
17727
  size[along] = len;
17314
17728
  size[(along + 1) % 3] = w;
@@ -17469,7 +17883,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
17469
17883
  checkIgnite(o, p, src);
17470
17884
  }
17471
17885
  }
17472
- wearJoints(o, p, Ein * 0.5, void 0, 0, src);
17886
+ wearJoints(o, p, Ein * 0.5, void 0, 0, src, false, 2 * BREACH_BLAST_REACH * Math.cbrt(Math.max(0, energy) / 1e3));
17473
17887
  const amount = Ein * DMG_PER_J * (mat.damageScale?.explosive ?? 1);
17474
17888
  if (blastFrontSpeed > 0 && dist > 0 && mat.tags?.includes("glass")) {
17475
17889
  frontQueue.push({ o, p, due: time + dist / blastFrontSpeed, amount, fragEnergy: Ein * 0.3, point, ...src !== void 0 ? { src } : {} });
@@ -17479,7 +17893,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
17479
17893
  const dealt = applyDamage(o, p, amount, "explosive", point, null, Ein * 0.3, 40, src);
17480
17894
  res.damage += dealt;
17481
17895
  if (p.bend && flexure.enabled) bendByBlast(o, p, point, energy, radius, reach, src);
17482
- if (dist <= BREACH_BLAST_REACH * Math.cbrt(Math.max(0, energy) / 1e3)) addBreach(o, p, wp, breachAreaOf(p, dealt, true), Ein, null, src);
17896
+ const breachReach = BREACH_BLAST_REACH * Math.cbrt(Math.max(0, energy) / 1e3);
17897
+ if (p.spec.units && p.spec.units.mortar !== p.spec.material) blastUnits(o, p, wp, energy, dist, src);
17898
+ else if (p.spec.reinforcement && p.spec.units && dist > MEMBER_CUT_REACH * breachReach) blastUnits(o, p, wp, energy, dist, src);
17899
+ else if (dist <= breachReach) addBreach(o, p, wp, breachAreaOf(p, dealt, true), Ein, null, src);
17483
17900
  return reach;
17484
17901
  };
17485
17902
  const blastProbe = { partsTested: 0, partsNear: 0 };
@@ -17725,6 +18142,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
17725
18142
  }
17726
18143
  return h | 0;
17727
18144
  };
18145
+ const splitStream = (objectId, partId, hit) => {
18146
+ const codes = Array.from(`${objectId}|${partId}`, (char) => char.charCodeAt(0) / 1e3);
18147
+ const stream = createRng(seedOf2(rng3.state() / 1e3, ...codes, hit[0], hit[1], hit[2]));
18148
+ return () => stream.next();
18149
+ };
17728
18150
  const throwFragment = (frag, objectId, from, g, centre, index, src, shields, results, reports, mult) => {
17729
18151
  const v = frag.velocity;
17730
18152
  const speed = Math.hypot(v[0], v[1], v[2]);
@@ -19020,7 +19442,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19020
19442
  var DEFAULT_DENSITY = 500;
19021
19443
  var densityOf = (material) => DENSITY[material] ?? DEFAULT_DENSITY;
19022
19444
  var OPEN_ROOM_TYPES = ["balcony", "terrace", "porch"];
19445
+ var WALL_PILLAR_SIDE = 0.2;
19446
+ var HALF = WALL_PILLAR_SIDE / 2;
19023
19447
  var CLOSED_ONLY = [...OPEN_ROOM_TYPES, "void"];
19448
+ var HALF_PILLAR = WALL_PILLAR_SIDE / 2;
19024
19449
  var entry = (itemId, min, max, group, pickable, weight) => ({ itemId, min, max, group, pickable, weight });
19025
19450
  var FRIDGE = [
19026
19451
  entry("water_bottle", 1, 3, "water", true, 3),
@@ -19755,6 +20180,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19755
20180
  }
19756
20181
  var LINTEL_DEPTH2 = 0.2;
19757
20182
  var LINTEL_BEARING2 = 0.2;
20183
+ var SILL_BEAM_DEPTH = 0.1;
20184
+ var LINTEL_SPAN_RATIO = 10;
19758
20185
  var MIN_PIECE = 0.05;
19759
20186
  var frameOf = (wall) => {
19760
20187
  const alongU = wall.aabb[2] - wall.aabb[0] >= wall.aabb[3] - wall.aabb[1];
@@ -19768,6 +20195,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19768
20195
  const drafts = [];
19769
20196
  const piers = [];
19770
20197
  const lintels = [];
20198
+ const sillBeams = [];
19771
20199
  const spandrels = [];
19772
20200
  const height = wall.y1 - wall.y0;
19773
20201
  let cursor = frame2.from;
@@ -19778,23 +20206,26 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19778
20206
  };
19779
20207
  own.forEach((opening2, k) => {
19780
20208
  addPier(Math.max(cursor, opening2.from));
19781
- const lintelTop = Math.min(wall.y1, opening2.y1 + Math.max(LINTEL_DEPTH2, style.lintelDepth?.(opening2.to - opening2.from) ?? 0));
20209
+ const span = opening2.to - opening2.from;
20210
+ const lintelTop = Math.min(wall.y1, opening2.y1 + Math.max(LINTEL_DEPTH2, style.lintelDepth?.(span) ?? 0, (span + LINTEL_BEARING2) / LINTEL_SPAN_RATIO));
19782
20211
  const lintelFrom = Math.max(frame2.from, opening2.from - LINTEL_BEARING2);
19783
20212
  const lintelTo = Math.min(frame2.to, opening2.to + LINTEL_BEARING2);
19784
20213
  const lintelHeight = lintelTop - opening2.y1;
19785
- if (opening2.y0 - 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: opening2.y0, volume: (opening2.to - opening2.from) * (opening2.y0 - wall.y0) });
20214
+ const sillTop = style.sillBeam && opening2.y0 - wall.y0 > SILL_BEAM_DEPTH + MIN_PIECE ? opening2.y0 - SILL_BEAM_DEPTH : opening2.y0;
20215
+ 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) });
20216
+ 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 });
19786
20217
  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 });
19787
20218
  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);
19788
20219
  cursor = opening2.to;
19789
20220
  });
19790
20221
  addPier(frame2.to);
19791
20222
  for (const pier of piers) {
19792
- for (const lintel of lintels) {
20223
+ for (const lintel of [...lintels, ...sillBeams]) {
19793
20224
  const overlap = Math.max(0, Math.min(pier.to, lintel.to) - Math.max(pier.from, lintel.from));
19794
20225
  pier.volume -= overlap * (lintel.y1 - lintel.y0);
19795
20226
  }
19796
20227
  }
19797
- const all = [...piers, ...drafts, ...lintels, ...spandrels.filter((d) => d !== void 0)];
20228
+ const all = [...piers, ...drafts, ...sillBeams, ...lintels, ...spandrels.filter((d) => d !== void 0)];
19798
20229
  const total = all.reduce((sum, d) => sum + d.volume, 0);
19799
20230
  const pieces = all.map((d) => ({
19800
20231
  id: d.id,
@@ -19809,6 +20240,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19809
20240
  ...d.role === "beam" ? style.lintelAssembly ? { assembly: style.lintelAssembly } : {} : wall.assembly ? { assembly: wall.assembly } : {}
19810
20241
  }));
19811
20242
  const internal = [];
20243
+ for (const beam of sillBeams) {
20244
+ const sill = drafts.find((d) => d.id === beam.id.replace("_sillbeam_", "_sill_"));
20245
+ if (sill) internal.push({ part: beam.id, on: sill.id, joint: style.lintelJoint });
20246
+ 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 });
20247
+ }
19812
20248
  lintels.forEach((lintel, k) => {
19813
20249
  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 });
19814
20250
  const spandrel = spandrels[k];
@@ -19838,7 +20274,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19838
20274
  ...system.timber ? {} : { lintelAssembly: "reinforced_concrete" },
19839
20275
  lintelJoint: system.joints.beam,
19840
20276
  wallJoint: system.joints.wall,
19841
- ...lintelDepth && !system.timber ? { lintelDepth } : {}
20277
+ ...lintelDepth && !system.timber ? { lintelDepth } : {},
20278
+ ...construction.system === "masonry" ? { sillBeam: true } : {}
19842
20279
  });
19843
20280
  if (split.pieces.length > 1) splits.set(part.id, split);
19844
20281
  }
@@ -19846,7 +20283,18 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19846
20283
  }
19847
20284
  function applyPanels(input, structure, detail, lintelDepth) {
19848
20285
  if (detail === "coarse") return input;
19849
- return applySplits(input, panelSplits(input, structure, lintelDepth));
20286
+ return onJambPillars(applySplits(input, panelSplits(input, structure, lintelDepth)));
20287
+ }
20288
+ var OPENING_BEAM = /_(lintel|sillbeam)_\d+$/;
20289
+ var overlaps2 = (a, b) => a[0] < b[2] && a[2] > b[0] && a[1] < b[3] && a[3] > b[1];
20290
+ function onJambPillars(split) {
20291
+ const pillars = split.parts.filter((part) => part.role === "column" && part.material === "concrete");
20292
+ const extra = [];
20293
+ for (const beam of split.parts) {
20294
+ if (beam.role !== "beam" || !OPENING_BEAM.test(beam.id)) continue;
20295
+ 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" });
20296
+ }
20297
+ return extra.length ? { parts: split.parts, supports: [...split.supports, ...extra] } : split;
19850
20298
  }
19851
20299
  function applyUnits(input, structure) {
19852
20300
  const splits = /* @__PURE__ */ new Map();
@@ -19908,21 +20356,35 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19908
20356
  };
19909
20357
  var MEMBER_CAGE = { spacing: 0.1, diameter: 8e-3, unit: 0.1 };
19910
20358
  var CAGED_ROLES = /* @__PURE__ */ new Set(["beam", "column"]);
20359
+ var CAGE_COVER = 0.03;
20360
+ var CAGE_BUCKLING_SHARE = 0.5;
20361
+ var DEFAULT_RESIDUAL = 0.15;
20362
+ function cageResidualCapacity(size, reinforcement, capacity) {
20363
+ const [a = 0, b = 0] = [...size].sort((x, y) => x - y);
20364
+ const perSide = (w) => Math.max(2, Math.floor(Math.max(0, w - 2 * CAGE_COVER) / reinforcement.spacing) + 1);
20365
+ const bars = 2 * perSide(a) + 2 * perSide(b) - 4;
20366
+ const steel = MATERIALS2.find((m) => m.id === reinforcement.material)?.compressive ?? 0;
20367
+ const carries2 = bars * Math.PI * (reinforcement.diameter / 2) ** 2 * steel * CAGE_BUCKLING_SHARE;
20368
+ if (!capacity || capacity <= 0 || carries2 <= 0) return DEFAULT_RESIDUAL;
20369
+ return Math.min(1, Math.max(DEFAULT_RESIDUAL, carries2 / capacity));
20370
+ }
19911
20371
  var caged = (spec, part, rebar2) => {
19912
20372
  if (!rebar2 || part.material !== "concrete" || !CAGED_ROLES.has(part.role) || spec.reinforcement) return spec;
19913
20373
  const size = spec.shape.kind === "box" ? spec.shape.size : null;
19914
20374
  if (!size) return spec;
19915
20375
  const thin = Math.min(...size);
20376
+ const reinforcement = { material: rebar2, spacing: MEMBER_CAGE.spacing, diameter: MEMBER_CAGE.diameter };
19916
20377
  return {
19917
20378
  ...spec,
19918
- reinforcement: { material: rebar2, spacing: MEMBER_CAGE.spacing, diameter: MEMBER_CAGE.diameter },
20379
+ reinforcement: part.role === "column" ? { ...reinforcement, residualCapacity: cageResidualCapacity(size, reinforcement, spec.structure?.capacity) } : reinforcement,
19919
20380
  units: { size: [MEMBER_CAGE.unit, MEMBER_CAGE.unit, thin], bond: "stack", mortar: "concrete" }
19920
20381
  };
19921
20382
  };
19922
20383
  var partSpecOf = (part, capacity, detailed, rebar2) => {
19923
20384
  if (detailed && part.assembly) return assemblySpec(part, capacity);
19924
20385
  const spec = boxSpec(part, capacity);
19925
- return detailed ? caged(spec, part, rebar2) : spec;
20386
+ const out = detailed ? caged(spec, part, rebar2) : spec;
20387
+ return part.beamKind === "ground_beam" ? { ...out, anchored: true, structure: { role: "foundation" } } : out;
19926
20388
  };
19927
20389
  function toDestructibleSpec(structure, options = {}) {
19928
20390
  const { safety = 0.65, redundancy = 0.97, safetyOthers = 0.45 } = options;
@@ -19938,7 +20400,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19938
20400
  const walled = applyPanels(slabbed, structure, detail, slabOptions && structure.construction.frame.rebar ? (span) => beamDepthFor(span, slabOptions) : void 0);
19939
20401
  const panels = slabOptions && detail !== "coarse" ? deepenBondBeams(walled, structure, slabOptions) : walled;
19940
20402
  const sizing = { safety, redundancy, safetyOthers };
19941
- const sized = { ...panels, capacities: options.sized === false ? /* @__PURE__ */ new Map() : sizeCapacities(panels.parts, panels.supports, sizing) };
20403
+ const foundation = new Set(panels.parts.filter((part) => part.beamKind === "ground_beam").map((part) => part.id));
20404
+ const onFoundation = foundation.size ? panels.supports.filter((s) => !foundation.has(s.part)).map((s) => foundation.has(s.on) ? { ...s, on: GROUND } : s) : panels.supports;
20405
+ const sized = { ...panels, capacities: options.sized === false ? /* @__PURE__ */ new Map() : sizeCapacities(panels.parts.filter((part) => !foundation.has(part.id)), onFoundation, sizing) };
19942
20406
  const { parts, supports, capacities } = detail === "units" ? applyUnits(sized, structure) : sized;
19943
20407
  const specs = parts.map((part) => partSpecOf(part, capacities.get(part.id), detail !== "coarse", structure.construction.frame.rebar));
19944
20408
  const margin = options.terrainMargin ?? TERRAIN_MARGIN;
@@ -20003,6 +20467,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20003
20467
  out.parts = out.parts.map((p, i) => i === ground ? raft2 : p);
20004
20468
  return out;
20005
20469
  }
20470
+ var LOW_WINDOW = SILL_BEAM_DEPTH + 0.05;
20006
20471
  var slabId = (g, f) => f === g.floors - 1 && g.roof ? "roof" : `slab_${f}`;
20007
20472
  var storeyBase = (g, f) => f * (g.height + g.slab);
20008
20473
  var slabHalf = (g) => [(g.spanX + g.columnSize + 0.2) / 2, (g.spanZ + g.columnSize + 0.2) / 2];
@@ -20161,7 +20626,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20161
20626
  parts.push({ id, material: c.material, shape: box3(size), position, structure: { role: "partition" }, tags: ["infill"] });
20162
20627
  for (const o of on) rests(joints, id, o, "mortar");
20163
20628
  };
20164
- const bay = (side, k, along, centre, fixed, length, c1, c2) => {
20629
+ const bay = (side, k, along, centre2, fixed, length, c1, c2) => {
20165
20630
  const clear = length - g.columnSize;
20166
20631
  const win = clear * c.window;
20167
20632
  const pier = (clear - win) / 2;
@@ -20172,11 +20637,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20172
20637
  wall(`infill_${f}_${side}_${k}_${name}`, size(u, h), [p[0], y, p[2]], on);
20173
20638
  };
20174
20639
  const id = (name) => `infill_${f}_${side}_${k}_${name}`;
20175
- put("sill", clear, sill, yb + sill / 2, centre, [c1, c2]);
20176
- put("pl", pier, head - sill, yb + sill + (head - sill) / 2, centre - clear / 2 + pier / 2, [c1, id("sill")]);
20177
- put("pr", pier, head - sill, yb + sill + (head - sill) / 2, centre + clear / 2 - pier / 2, [c2, id("sill")]);
20178
- put("head", clear, room - head, yb + head + (room - head) / 2, centre, [c1, c2]);
20179
- const gp = at2(centre);
20640
+ put("sill", clear, sill, yb + sill / 2, centre2, [c1, c2]);
20641
+ put("pl", pier, head - sill, yb + sill + (head - sill) / 2, centre2 - clear / 2 + pier / 2, [c1, id("sill")]);
20642
+ put("pr", pier, head - sill, yb + sill + (head - sill) / 2, centre2 + clear / 2 - pier / 2, [c2, id("sill")]);
20643
+ put("head", clear, room - head, yb + head + (room - head) / 2, centre2, [c1, c2]);
20644
+ const gp = at2(centre2);
20180
20645
  const gid = `glass_${f}_${side}_${k}`;
20181
20646
  parts.push({
20182
20647
  id: gid,
@@ -20643,7 +21108,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20643
21108
  }
20644
21109
  var towerLocales = { en: { labels: en_default.labels }, "pt-BR": { labels: pt_BR_default.labels } };
20645
21110
 
20646
- // ../game-floorplan-three/dist/chunk-QUVGWVEC.js
21111
+ // ../game-floorplan-three/dist/chunk-7XVCZF6H.js
20647
21112
  var en2 = {
20648
21113
  "prompt.open": "E: open {what}",
20649
21114
  "prompt.close": "E: close {what}",
@@ -21385,6 +21850,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21385
21850
  }
21386
21851
  function installHouseWalls(K, shared2) {
21387
21852
  const { EPS: EPS3, Y0, boxC, bx, cursor, effectiveHeight, pieceRecord, structurePiece, wallColor } = K;
21853
+ const sh = (color2, factor) => K.sh(color2, factor);
21388
21854
  function wallBox(alongU, L, a, b, ya, yb, th, cL, cH, mL, mH) {
21389
21855
  if (yb - ya < 1e-4 || b - a < 1e-4) return;
21390
21856
  if (alongU) {
@@ -21428,6 +21894,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21428
21894
  function walls() {
21429
21895
  const R = shared2.ctx.r, cut = shared2.ctx.cut, nA = R.floors.length, vMin = Math.min(...R.walls.map((w) => w.axis === "u" ? w.v0 : Math.min(w.v0, w.v1)));
21430
21896
  const corners = /* @__PURE__ */ new Map();
21897
+ const looks = /* @__PURE__ */ new Map();
21431
21898
  for (const w of R.walls) {
21432
21899
  const f = w.floor;
21433
21900
  cursor.group = "f" + f;
@@ -21437,6 +21904,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21437
21904
  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);
21438
21905
  const front = alongU && Math.abs(L - vMin) < 0.05;
21439
21906
  const sideL = sideOf(w.sides[0], front), sideH = sideOf(w.sides[1], front);
21907
+ if (w.id) {
21908
+ const face = sideL.exterior ? sideL : sideH;
21909
+ looks.set(w.id, { mat: face.mat, color: face.color, top: y0 + hh });
21910
+ }
21440
21911
  shared2.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 });
21441
21912
  if (w.type === "railing") {
21442
21913
  const c0 = shared2.ctx.colors.metal != null ? shared2.ctx.colors.metal : 9080466;
@@ -21483,8 +21954,27 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21483
21954
  cursor.group = "f" + e.f;
21484
21955
  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);
21485
21956
  }
21957
+ if (shared2.ctx.showPillars) wallPillars(looks);
21486
21958
  }
21487
- Object.assign(K, { wallBox, sideOf, pieceOf, nearestWall, walls });
21959
+ const PILLAR_IN_WALL = /^(wall_column_|column_\d|jamb_column_)/;
21960
+ const wallOf = (pillar, parts) => {
21961
+ const u = (pillar.aabb[0] + pillar.aabb[2]) / 2, v = (pillar.aabb[1] + pillar.aabb[3]) / 2;
21962
+ 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);
21963
+ };
21964
+ function wallPillars(looks) {
21965
+ const parts = Object.values(shared2.ctx.structureIndex);
21966
+ for (const e of parts) {
21967
+ if (e.role !== "column" || !PILLAR_IN_WALL.test(e.id)) continue;
21968
+ const host = wallOf(e, parts);
21969
+ const look = host && looks.get(host.id);
21970
+ if (!host || !look) continue;
21971
+ cursor.group = "f" + e.floor;
21972
+ cursor.pc = structurePiece(host.id);
21973
+ 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));
21974
+ }
21975
+ cursor.pc = null;
21976
+ }
21977
+ Object.assign(K, { wallBox, sideOf, pieceOf, nearestWall, walls, wallPillars });
21488
21978
  }
21489
21979
  function installHouseDoors(K, shared2) {
21490
21980
  const seedKey = (id) => K.seedKey(id);
@@ -26856,7 +27346,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
26856
27346
  if (!pc || pc.unit) return sp;
26857
27347
  if (pc.role !== "wall" && pc.role !== "partition") {
26858
27348
  const def = getAssembly(pc.assembly || (pc.material === "concrete" && ["slab", "beam", "column", "stair"].includes(pc.role) ? "reinforced_concrete" : ""));
26859
- const out = def && def.reinforcement && !sp.reinforcement ? { ...sp, reinforcement: { ...def.reinforcement } } : sp;
27349
+ 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;
27350
+ const out = cage && !sp.reinforcement ? { ...sp, reinforcement: cage } : sp;
26860
27351
  if (out.reinforcement && !out.units && pc.material === "concrete" && (pc.role === "beam" || pc.role === "column") && sp.shape.kind === "box") {
26861
27352
  return { ...out, units: { size: [MEMBER_CAGE.unit, MEMBER_CAGE.unit, Math.min(...sp.shape.size)], bond: "stack", mortar: "concrete" } };
26862
27353
  }
@@ -26882,6 +27373,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
26882
27373
  const a = pos.array;
26883
27374
  const u = mesh2.userData;
26884
27375
  if (!u._p0) u._p0 = a.slice();
27376
+ if (!u.plainPositions) Object.defineProperty(u, "plainPositions", { value: u._p0, enumerable: false, configurable: true, writable: true });
26885
27377
  const list2 = u.collapsedRanges ?? (u.collapsedRanges = []);
26886
27378
  if (!list2.some((r) => r[0] === v0 && r[1] === v1)) list2.push([v0, v1]);
26887
27379
  const saved = a.slice(v0 * 3, v1 * 3);
@@ -26965,10 +27457,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
26965
27457
  const v = house.worldToLocal(tmp.p.set(p[0], p[1], p[2]));
26966
27458
  return [v.x, v.y, v.z];
26967
27459
  };
26968
- function cells(part, units) {
27460
+ function cells(part, units, mask) {
26969
27461
  const [u0, v0, u1, v1] = part.aabb;
26970
- const alongU = u1 - u0 >= v1 - v0;
27462
+ const alongU = mask.alongU;
26971
27463
  const len = alongU ? u1 - u0 : v1 - v0;
27464
+ const offset = (alongU ? u0 : v0) - mask.start;
26972
27465
  const [ul, uh] = units.size;
26973
27466
  const rows = Math.max(1, Math.round((part.y1 - part.y0) / uh));
26974
27467
  const rowH = (part.y1 - part.y0) / rows;
@@ -26979,7 +27472,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
26979
27472
  const shift = units.bond === "running" && r & 1 ? w / 2 : 0;
26980
27473
  for (let i = 0; i < per + (shift ? 1 : 0); i++) {
26981
27474
  const a = Math.max(0, i * w - shift), b = Math.min(len, i * w - shift + w);
26982
- if (b - a > 1e-3) out.push({ a, b, y0: part.y0 + r * rowH, y1: part.y0 + (r + 1) * rowH, r, i });
27475
+ 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 });
26983
27476
  }
26984
27477
  }
26985
27478
  return out;
@@ -26993,8 +27486,21 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
26993
27486
  for (const q of structure.parts) {
26994
27487
  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;
26995
27488
  }
27489
+ return part.role === "column" ? wallAround(part) : null;
27490
+ }
27491
+ function wallAround(part) {
27492
+ const cu = (part.aabb[0] + part.aabb[2]) / 2, cv = (part.aabb[1] + part.aabb[3]) / 2;
27493
+ for (const q of structure.parts) {
27494
+ if (q.id === part.id || q.role !== "wall" && q.role !== "partition" || !structure.meshesOf(q.id).some((it) => it.mesh)) continue;
27495
+ 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;
27496
+ if (inside2 && Math.min(q.y1, part.y1) - Math.max(q.y0, part.y0) > 0.5) return q.id;
27497
+ }
26996
27498
  return null;
26997
27499
  }
27500
+ const pillarSpans = (view) => structure.parts.filter((q) => q.role === "column" && wallAround(q) === view.host).map((q) => {
27501
+ 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;
27502
+ return { a, b, y0: q.y0, y1: q.y1 };
27503
+ });
26998
27504
  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);
26999
27505
  function shell(it, mask) {
27000
27506
  const g0 = it.mesh.geometry, g = new THREE.BufferGeometry();
@@ -27083,6 +27589,17 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
27083
27589
  texture.needsUpdate = true;
27084
27590
  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) };
27085
27591
  }
27592
+ function besidePillars(c, pillars) {
27593
+ let { a, b } = c;
27594
+ for (const k of pillars) {
27595
+ if (c.y1 <= k.y0 || c.y0 >= k.y1 || b <= k.a || a >= k.b) continue;
27596
+ const centre = (a + b) / 2;
27597
+ if (centre > k.a && centre < k.b) return [];
27598
+ if (centre <= k.a) b = Math.min(b, k.a);
27599
+ else a = Math.max(a, k.b);
27600
+ }
27601
+ return b - a > 1e-3 ? [{ ...c, a, b }] : [];
27602
+ }
27086
27603
  const boxDistance = (s, y, a, b, c, d) => Math.hypot(Math.max(a - s, 0, s - b), Math.max(c - y, 0, y - d));
27087
27604
  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);
27088
27605
  function covered(c, boxes) {
@@ -27105,9 +27622,15 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
27105
27622
  const discs = [];
27106
27623
  const flakes = [];
27107
27624
  const shown = [];
27625
+ const pillars = pillarSpans(view);
27108
27626
  for (const id of view.parts) {
27109
27627
  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);
27110
- const all = cells(part, units2);
27628
+ const own = cells(part, units2, mask);
27629
+ const all = id === view.host ? own.flatMap((c) => besidePillars(c, pillars)) : own;
27630
+ if (view.fallen.has(id)) {
27631
+ holes.push(...all);
27632
+ continue;
27633
+ }
27111
27634
  const thick = mask.alongU ? part.aabb[3] - part.aabb[1] : part.aabb[2] - part.aabb[0];
27112
27635
  const mid = mask.alongU ? (part.aabb[1] + part.aabb[3]) / 2 : (part.aabb[0] + part.aabb[2]) / 2;
27113
27636
  if (opening) discs.push({ s: along(opening.center), y: opening.center[1], r: opening.radius });
@@ -27203,52 +27726,55 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
27203
27726
  }
27204
27727
  return { center: local(chip.center), along: a, up: u };
27205
27728
  }
27729
+ function newView(hostId) {
27730
+ const items = structure.meshesOf(hostId).filter((it) => it.mesh);
27731
+ if (!items.length) return null;
27732
+ const parts = [hostId, ...riders(hostId)].filter((id) => structure.byId.get(id) && specOf(id)?.units);
27733
+ const mask = maskOf(hostId, parts), core = maskOf(hostId, parts);
27734
+ return {
27735
+ host: hostId,
27736
+ parts,
27737
+ items,
27738
+ mask,
27739
+ core,
27740
+ unitMaterial: unitsMaterial(core),
27741
+ shells: [],
27742
+ saved: null,
27743
+ units: null,
27744
+ gone: 0,
27745
+ pits: 0,
27746
+ bounds: drawnBounds(items),
27747
+ chips: new Map(parts.map((id) => [id, []])),
27748
+ spalls: new Map(parts.map((id) => [id, []])),
27749
+ openings: /* @__PURE__ */ new Map(),
27750
+ flakes: 0,
27751
+ fallen: /* @__PURE__ */ new Set()
27752
+ };
27753
+ }
27754
+ function cut(view) {
27755
+ if (view.saved) return;
27756
+ view.shells = view.items.map((it) => shell(it, view.mask));
27757
+ for (const m of view.shells) m.userData.behind = () => view.units;
27758
+ view.saved = view.items.map((it) => ({ it, v0: it.v0, data: collapseRange(it.mesh, it.v0, it.v1) }));
27759
+ }
27206
27760
  function draw(partId, change) {
27207
27761
  if (lod !== 0) return false;
27208
27762
  const hostId = hostOf(partId);
27209
27763
  if (!hostId || !specOf(partId)?.units) return false;
27210
- let view = views.get(hostId);
27211
- if (!view) {
27212
- const items = structure.meshesOf(hostId).filter((it) => it.mesh);
27213
- if (!items.length) return false;
27214
- const parts = [hostId, ...riders(hostId)].filter((id) => structure.byId.get(id) && specOf(id)?.units);
27215
- if (!parts.includes(partId)) return false;
27216
- const mask = maskOf(hostId, parts), core = maskOf(hostId, parts);
27217
- view = {
27218
- host: hostId,
27219
- parts,
27220
- items,
27221
- mask,
27222
- core,
27223
- unitMaterial: unitsMaterial(core),
27224
- shells: [],
27225
- saved: null,
27226
- units: null,
27227
- gone: 0,
27228
- pits: 0,
27229
- bounds: drawnBounds(items),
27230
- chips: new Map(parts.map((id) => [id, []])),
27231
- spalls: new Map(parts.map((id) => [id, []])),
27232
- openings: /* @__PURE__ */ new Map(),
27233
- flakes: 0
27234
- };
27235
- }
27236
- if (!view.chips.has(partId)) return false;
27764
+ const view = views.get(hostId) ?? newView(hostId);
27765
+ if (!view || !view.chips.has(partId)) return false;
27237
27766
  if (change.opening) {
27238
27767
  if (view.openings.has(partId)) return false;
27239
27768
  view.openings.set(partId, { center: local(change.opening.center), radius: change.opening.radius });
27240
27769
  }
27241
27770
  if (change.chip) view.chips.get(partId).push(chipBox(change.chip, view.mask.alongU));
27771
+ for (const chip of change.chips ?? []) view.chips.get(partId).push(chipBox(chip, view.mask.alongU));
27242
27772
  if (change.spall) {
27243
27773
  const list2 = view.spalls.get(partId);
27244
27774
  list2.push({ center: local(change.spall.center), radius: change.spall.radius });
27245
27775
  if (list2.length > MAX_FLAKES) list2.shift();
27246
27776
  }
27247
- if (!view.saved) {
27248
- view.shells = view.items.map((it) => shell(it, view.mask));
27249
- for (const m of view.shells) m.userData.behind = () => view.units;
27250
- view.saved = view.items.map((it) => ({ it, v0: it.v0, data: collapseRange(it.mesh, it.v0, it.v1) }));
27251
- }
27777
+ cut(view);
27252
27778
  views.set(hostId, view);
27253
27779
  if (opts.deferPaint && change.spall) dirty.add(view);
27254
27780
  else {
@@ -27359,14 +27885,32 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
27359
27885
  */
27360
27886
  chip(event) {
27361
27887
  if (event.torn) return false;
27362
- return draw(event.part, { chip: { center: event.center, size: event.size ?? [0, 0, 0], rotation: event.rotation ?? [0, 0, 0, 1] } });
27888
+ const rotation = event.rotation ?? [0, 0, 0, 1];
27889
+ if (event.units?.length) return draw(event.part, { chips: event.units.map((unit) => ({ center: unit.center, size: unit.size, rotation })) });
27890
+ return draw(event.part, { chip: { center: event.center, size: event.size ?? [0, 0, 0], rotation } });
27363
27891
  },
27364
27892
  /** The part broke or fell: its damage goes (the plain range stays collapsed, the renderer hides the part). */
27365
27893
  drop(partId) {
27366
- const v = views.get(partId);
27367
- if (!v) return;
27894
+ let host = views.get(partId) ?? [...views.values()].find((view) => view.parts.includes(partId));
27895
+ if (!host && lod === 0 && riders(partId).length > 0) {
27896
+ host = newView(partId);
27897
+ if (host) {
27898
+ cut(host);
27899
+ views.set(partId, host);
27900
+ }
27901
+ }
27902
+ if (!host) return;
27903
+ host.fallen.add(partId);
27904
+ if (host.parts.some((id) => !host.fallen.has(id))) {
27905
+ dirty.delete(host);
27906
+ paint(host);
27907
+ invalidate();
27908
+ invalidateShadow();
27909
+ return;
27910
+ }
27911
+ const v = host;
27368
27912
  dispose(v);
27369
- views.delete(partId);
27913
+ views.delete(v.host);
27370
27914
  if (v.saved) dropped.push(v);
27371
27915
  },
27372
27916
  /** LOD 0 draws the cut wall; LOD 1 and 2 give the wall its box back. */
@@ -27841,6 +28385,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
27841
28385
  shared2.ctx.noPiece = false;
27842
28386
  cursor.separate = opts.pieces === "meshes";
27843
28387
  cursor.pc = null;
28388
+ shared2.ctx.showPillars = opts.pillars !== "hidden";
27844
28389
  shared2.ctx.apartment = r.template === "apartment_modern";
27845
28390
  shared2.ctx.pa = Math.max(3, Math.min(20, pr.floors != null ? pr.floors : 5));
27846
28391
  shared2.ctx.pf = Math.max(1, Math.min(shared2.ctx.pa, pr.floor != null ? pr.floor : 3));