@adatechnology/game-items-three 0.7.2 → 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.
@@ -2731,6 +2731,19 @@ var GameItemsThree = (() => {
2731
2731
  "drink",
2732
2732
  "sticky"
2733
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
+ ]
2734
2747
  }
2735
2748
  ];
2736
2749
  var LIQUIDS = liquids_default;
@@ -13254,6 +13267,22 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
13254
13267
  return angle === 0 ? q : quat.mul(q, quat.fromAxisAngle([0, 0, 1], angle));
13255
13268
  };
13256
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
+ };
13257
13286
  var splinterBox = (inp, box4, limit = inp.maxFragments) => {
13258
13287
  const { rnd } = inp;
13259
13288
  const dims = box4.s;
@@ -13263,35 +13292,81 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
13263
13292
  const w = Math.max(fmin * 1.5, 0.01);
13264
13293
  const c = clamp(Math.round(dims[a] * dims[b] / (w * w)), 3, Math.max(3, Math.floor(limit / 2)));
13265
13294
  const maxLen = fmax * 4;
13266
- 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
+ }
13267
13301
  const pieces = [];
13268
13302
  for (const cell of cells) {
13269
- const extra = rnd() < 0.4 ? 1 : 0;
13303
+ const extra = Math.floor(rnd() * 3);
13270
13304
  const k = Math.max(1, Math.ceil(dims[g] / maxLen)) + extra;
13271
- 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);
13272
13306
  }
13273
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] }));
13274
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;
13275
13343
  var severAt = (inp, root, at2, gap) => {
13276
13344
  const dims = root.s;
13277
13345
  const L = [0, 1, 2].sort((x, y) => dims[y] - dims[x])[0];
13278
13346
  const half = dims[L] / 2;
13279
13347
  const lo = clamp(at2 - gap / 2, -half, half), hi = clamp(at2 + gap / 2, -half, half);
13280
13348
  const out = [];
13281
- 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) => {
13282
13352
  if (to - from < 0.02) return;
13283
13353
  const c = [root.c[0], root.c[1], root.c[2]], sz = [dims[0], dims[1], dims[2]];
13284
13354
  c[L] = (from + to) / 2;
13285
13355
  sz[L] = to - from;
13286
- 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 });
13287
13359
  };
13288
- stub(-half, lo);
13289
- stub(hi, half);
13360
+ stub(-half, lo, true);
13361
+ stub(hi, half, false);
13290
13362
  const band = { c: [root.c[0], root.c[1], root.c[2]], s: [dims[0], dims[1], dims[2]] };
13291
13363
  band.c[L] = (lo + hi) / 2;
13292
13364
  band.s[L] = Math.max(hi - lo, 0.01);
13293
- if (inp.style === "snap") {
13294
- 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] });
13295
13370
  } else for (const p of splinterBox(inp, band, Math.max(3, inp.maxFragments - out.length))) out.push(p);
13296
13371
  return out;
13297
13372
  };
@@ -13324,8 +13399,18 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
13324
13399
  const [u, v] = [sorted[1], sorted[2]].sort((a, b) => a - b);
13325
13400
  if (style === "snap") {
13326
13401
  if (inp.cut) return severAt(inp, root, inp.cut.at, inp.cut.gap);
13402
+ if (inp.form === "plies") return plyBreak(inp, root, maxFragments);
13327
13403
  const n2 = Math.min(maxFragments, 2 + Math.floor(rnd() * 3));
13328
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 });
13329
13414
  return out;
13330
13415
  }
13331
13416
  if (style === "crumble" || style === "none") {
@@ -13432,6 +13517,88 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
13432
13517
  }
13433
13518
  return marks;
13434
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
+ };
13435
13602
  var MAX_SPALLS = 64;
13436
13603
  var thinAxisOf = (dims) => dims[0] <= dims[1] && dims[0] <= dims[2] ? 0 : dims[1] <= dims[2] ? 1 : 2;
13437
13604
  var resolveLayer = (def, index, mat) => ({ index, def, mat, energy: mat.penetration * def.thickness * 1e3 });
@@ -14411,6 +14578,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
14411
14578
  },
14412
14579
  {
14413
14580
  id: "wood_soft",
14581
+ splitForm: "slivers",
14414
14582
  density: 500,
14415
14583
  hpPerKg: 90,
14416
14584
  penetration: 9,
@@ -14456,6 +14624,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
14456
14624
  },
14457
14625
  {
14458
14626
  id: "wood_hard",
14627
+ splitForm: "slivers",
14459
14628
  density: 750,
14460
14629
  hpPerKg: 140,
14461
14630
  penetration: 14,
@@ -14500,6 +14669,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
14500
14669
  },
14501
14670
  {
14502
14671
  id: "plywood",
14672
+ splitForm: "plies",
14503
14673
  density: 600,
14504
14674
  hpPerKg: 70,
14505
14675
  penetration: 7,
@@ -14547,6 +14717,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
14547
14717
  },
14548
14718
  {
14549
14719
  id: "mdf",
14720
+ splitForm: "granules",
14550
14721
  density: 750,
14551
14722
  hpPerKg: 45,
14552
14723
  penetration: 6,
@@ -15058,6 +15229,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
15058
15229
  },
15059
15230
  {
15060
15231
  id: "green_wood",
15232
+ splitForm: "slivers",
15061
15233
  density: 650,
15062
15234
  hpPerKg: 5,
15063
15235
  penetration: 10,
@@ -15338,6 +15510,125 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
15338
15510
  "brittle"
15339
15511
  ],
15340
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
15341
15632
  }
15342
15633
  ];
15343
15634
  var material_hints_default = {
@@ -15550,6 +15841,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
15550
15841
  var BREACH_BLAST_FACTOR = 6;
15551
15842
  var BREACH_AREA = 0.25;
15552
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;
15553
15849
  var RESIDUAL_CAPACITY = 0.15;
15554
15850
  var SEVER_BAND = 0.1;
15555
15851
  var SEVER_ASPECT = 3;
@@ -16802,7 +17098,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
16802
17098
  if (j.st.open) continue;
16803
17099
  if (j.spec.kind === "rebar" && (isReinforced(o.parts[j.ia].spec) || isReinforced(o.parts[j.ib].spec))) continue;
16804
17100
  if (near && jointReach(o, j, near) > Math.max(radius, jointReachLimit(o, j))) continue;
16805
- 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));
16806
17102
  if (j.st.hp <= 0) {
16807
17103
  j.st.hp = 0;
16808
17104
  j.st.open = true;
@@ -16930,6 +17226,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
16930
17226
  const worn = (_a = p.rt).chips ?? (_a.chips = {});
16931
17227
  const cut = (_b = p.rt).craters ?? (_b.craters = {});
16932
17228
  const out = [];
17229
+ const wholeBefore = Object.values(worn).filter((w) => w === -1).length;
16933
17230
  const shot = kind === "ballistic";
16934
17231
  for (let i = 0; i < grid.length; i++) {
16935
17232
  const cell = grid[i];
@@ -16988,6 +17285,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
16988
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 });
16989
17286
  debris(o, p, p.mat.breakDebris, center, vec.scale(dir ?? [0, 1, 0], -1), 6, 2, src);
16990
17287
  }
17288
+ if (Object.values(worn).filter((w) => w === -1).length > wholeBefore) dropUnsupported(o, p, src);
16991
17289
  if (p.spec.reinforcement) exposeRebar(o, p, src);
16992
17290
  for (const cell of out) addBreach(o, p, toWorld(c, q, cell.center), cell.size[u] * cell.size[v], energy, dir, src);
16993
17291
  if (p.structural && !isLost(p)) {
@@ -17091,10 +17389,122 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
17091
17389
  setStage(o, p, 1, {}, src);
17092
17390
  setStage(o, p, 2, { opening: { center: worldCenter, radius }, fragments }, src);
17093
17391
  debris(o, p, p.mat.breakDebris, worldCenter, vec.scale(dir ?? [0, 1, 0], -1), 14, 3, src);
17392
+ dropUnsupported(o, p, src);
17094
17393
  const reinforcement = p.spec.reinforcement;
17095
17394
  if (reinforcement) exposeRebar(o, p, src);
17096
17395
  if (p.structural) solveStructure(o);
17097
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
+ };
17098
17508
  const stripPart = (o, p, reinforcement, src) => {
17099
17509
  const bars = rebarBars(p.dims, p.thin, reinforcement.spacing);
17100
17510
  p.st.stripped = true;
@@ -17161,6 +17571,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
17161
17571
  const unitPlan = p.spec.units ? unitFragmentPlan(p, maxFragments * UNIT_FRAGMENT_FACTOR) : [];
17162
17572
  const axis = style === "splinter" || style === "snap" ? severAxis(p) : -1;
17163
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;
17164
17575
  const plan = unitPlan.length > 0 ? unitPlan : planFragments({
17165
17576
  style,
17166
17577
  dims: p.dims,
@@ -17169,8 +17580,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
17169
17580
  hit: hitLocal,
17170
17581
  fragmentSize: p.mat.fragmentSize ?? [0.04, 0.2],
17171
17582
  maxFragments,
17172
- rnd,
17583
+ rnd: planRnd,
17173
17584
  sheet: p.mat.sheet,
17585
+ ...p.mat.splitForm ? { form: p.mat.splitForm } : {},
17174
17586
  ...cutAt2 !== void 0 ? { cut: { at: cutAt2, gap: SEVER_BAND } } : {}
17175
17587
  });
17176
17588
  const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
@@ -17309,7 +17721,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
17309
17721
  const fragments = [];
17310
17722
  for (let i = 0; i < n; i++) {
17311
17723
  const back = !exit || i % 3 === 2;
17312
- 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());
17313
17726
  const size = [w, w, w];
17314
17727
  size[along] = len;
17315
17728
  size[(along + 1) % 3] = w;
@@ -17470,7 +17883,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
17470
17883
  checkIgnite(o, p, src);
17471
17884
  }
17472
17885
  }
17473
- 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));
17474
17887
  const amount = Ein * DMG_PER_J * (mat.damageScale?.explosive ?? 1);
17475
17888
  if (blastFrontSpeed > 0 && dist > 0 && mat.tags?.includes("glass")) {
17476
17889
  frontQueue.push({ o, p, due: time + dist / blastFrontSpeed, amount, fragEnergy: Ein * 0.3, point, ...src !== void 0 ? { src } : {} });
@@ -17480,7 +17893,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
17480
17893
  const dealt = applyDamage(o, p, amount, "explosive", point, null, Ein * 0.3, 40, src);
17481
17894
  res.damage += dealt;
17482
17895
  if (p.bend && flexure.enabled) bendByBlast(o, p, point, energy, radius, reach, src);
17483
- 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);
17484
17900
  return reach;
17485
17901
  };
17486
17902
  const blastProbe = { partsTested: 0, partsNear: 0 };
@@ -17726,6 +18142,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
17726
18142
  }
17727
18143
  return h | 0;
17728
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
+ };
17729
18150
  const throwFragment = (frag, objectId, from, g, centre, index, src, shields, results, reports, mult) => {
17730
18151
  const v = frag.velocity;
17731
18152
  const speed = Math.hypot(v[0], v[1], v[2]);
@@ -19021,7 +19442,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19021
19442
  var DEFAULT_DENSITY = 500;
19022
19443
  var densityOf = (material) => DENSITY[material] ?? DEFAULT_DENSITY;
19023
19444
  var OPEN_ROOM_TYPES = ["balcony", "terrace", "porch"];
19445
+ var WALL_PILLAR_SIDE = 0.2;
19446
+ var HALF = WALL_PILLAR_SIDE / 2;
19024
19447
  var CLOSED_ONLY = [...OPEN_ROOM_TYPES, "void"];
19448
+ var HALF_PILLAR = WALL_PILLAR_SIDE / 2;
19025
19449
  var entry = (itemId, min, max, group, pickable, weight) => ({ itemId, min, max, group, pickable, weight });
19026
19450
  var FRIDGE = [
19027
19451
  entry("water_bottle", 1, 3, "water", true, 3),
@@ -19756,6 +20180,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19756
20180
  }
19757
20181
  var LINTEL_DEPTH2 = 0.2;
19758
20182
  var LINTEL_BEARING2 = 0.2;
20183
+ var SILL_BEAM_DEPTH = 0.1;
20184
+ var LINTEL_SPAN_RATIO = 10;
19759
20185
  var MIN_PIECE = 0.05;
19760
20186
  var frameOf = (wall) => {
19761
20187
  const alongU = wall.aabb[2] - wall.aabb[0] >= wall.aabb[3] - wall.aabb[1];
@@ -19769,6 +20195,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19769
20195
  const drafts = [];
19770
20196
  const piers = [];
19771
20197
  const lintels = [];
20198
+ const sillBeams = [];
19772
20199
  const spandrels = [];
19773
20200
  const height = wall.y1 - wall.y0;
19774
20201
  let cursor = frame2.from;
@@ -19779,23 +20206,26 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19779
20206
  };
19780
20207
  own.forEach((opening2, k) => {
19781
20208
  addPier(Math.max(cursor, opening2.from));
19782
- 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));
19783
20211
  const lintelFrom = Math.max(frame2.from, opening2.from - LINTEL_BEARING2);
19784
20212
  const lintelTo = Math.min(frame2.to, opening2.to + LINTEL_BEARING2);
19785
20213
  const lintelHeight = lintelTop - opening2.y1;
19786
- 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 });
19787
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 });
19788
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);
19789
20219
  cursor = opening2.to;
19790
20220
  });
19791
20221
  addPier(frame2.to);
19792
20222
  for (const pier of piers) {
19793
- for (const lintel of lintels) {
20223
+ for (const lintel of [...lintels, ...sillBeams]) {
19794
20224
  const overlap = Math.max(0, Math.min(pier.to, lintel.to) - Math.max(pier.from, lintel.from));
19795
20225
  pier.volume -= overlap * (lintel.y1 - lintel.y0);
19796
20226
  }
19797
20227
  }
19798
- const all = [...piers, ...drafts, ...lintels, ...spandrels.filter((d) => d !== void 0)];
20228
+ const all = [...piers, ...drafts, ...sillBeams, ...lintels, ...spandrels.filter((d) => d !== void 0)];
19799
20229
  const total = all.reduce((sum, d) => sum + d.volume, 0);
19800
20230
  const pieces = all.map((d) => ({
19801
20231
  id: d.id,
@@ -19810,6 +20240,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19810
20240
  ...d.role === "beam" ? style.lintelAssembly ? { assembly: style.lintelAssembly } : {} : wall.assembly ? { assembly: wall.assembly } : {}
19811
20241
  }));
19812
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
+ }
19813
20248
  lintels.forEach((lintel, k) => {
19814
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 });
19815
20250
  const spandrel = spandrels[k];
@@ -19839,7 +20274,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19839
20274
  ...system.timber ? {} : { lintelAssembly: "reinforced_concrete" },
19840
20275
  lintelJoint: system.joints.beam,
19841
20276
  wallJoint: system.joints.wall,
19842
- ...lintelDepth && !system.timber ? { lintelDepth } : {}
20277
+ ...lintelDepth && !system.timber ? { lintelDepth } : {},
20278
+ ...construction.system === "masonry" ? { sillBeam: true } : {}
19843
20279
  });
19844
20280
  if (split.pieces.length > 1) splits.set(part.id, split);
19845
20281
  }
@@ -19847,7 +20283,18 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19847
20283
  }
19848
20284
  function applyPanels(input, structure, detail, lintelDepth) {
19849
20285
  if (detail === "coarse") return input;
19850
- 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;
19851
20298
  }
19852
20299
  function applyUnits(input, structure) {
19853
20300
  const splits = /* @__PURE__ */ new Map();
@@ -19909,21 +20356,35 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19909
20356
  };
19910
20357
  var MEMBER_CAGE = { spacing: 0.1, diameter: 8e-3, unit: 0.1 };
19911
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
+ }
19912
20371
  var caged = (spec, part, rebar2) => {
19913
20372
  if (!rebar2 || part.material !== "concrete" || !CAGED_ROLES.has(part.role) || spec.reinforcement) return spec;
19914
20373
  const size = spec.shape.kind === "box" ? spec.shape.size : null;
19915
20374
  if (!size) return spec;
19916
20375
  const thin = Math.min(...size);
20376
+ const reinforcement = { material: rebar2, spacing: MEMBER_CAGE.spacing, diameter: MEMBER_CAGE.diameter };
19917
20377
  return {
19918
20378
  ...spec,
19919
- 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,
19920
20380
  units: { size: [MEMBER_CAGE.unit, MEMBER_CAGE.unit, thin], bond: "stack", mortar: "concrete" }
19921
20381
  };
19922
20382
  };
19923
20383
  var partSpecOf = (part, capacity, detailed, rebar2) => {
19924
20384
  if (detailed && part.assembly) return assemblySpec(part, capacity);
19925
20385
  const spec = boxSpec(part, capacity);
19926
- 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;
19927
20388
  };
19928
20389
  function toDestructibleSpec(structure, options = {}) {
19929
20390
  const { safety = 0.65, redundancy = 0.97, safetyOthers = 0.45 } = options;
@@ -19939,7 +20400,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19939
20400
  const walled = applyPanels(slabbed, structure, detail, slabOptions && structure.construction.frame.rebar ? (span) => beamDepthFor(span, slabOptions) : void 0);
19940
20401
  const panels = slabOptions && detail !== "coarse" ? deepenBondBeams(walled, structure, slabOptions) : walled;
19941
20402
  const sizing = { safety, redundancy, safetyOthers };
19942
- 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) };
19943
20406
  const { parts, supports, capacities } = detail === "units" ? applyUnits(sized, structure) : sized;
19944
20407
  const specs = parts.map((part) => partSpecOf(part, capacities.get(part.id), detail !== "coarse", structure.construction.frame.rebar));
19945
20408
  const margin = options.terrainMargin ?? TERRAIN_MARGIN;
@@ -20004,6 +20467,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20004
20467
  out.parts = out.parts.map((p, i) => i === ground ? raft2 : p);
20005
20468
  return out;
20006
20469
  }
20470
+ var LOW_WINDOW = SILL_BEAM_DEPTH + 0.05;
20007
20471
  var slabId = (g, f) => f === g.floors - 1 && g.roof ? "roof" : `slab_${f}`;
20008
20472
  var storeyBase = (g, f) => f * (g.height + g.slab);
20009
20473
  var slabHalf = (g) => [(g.spanX + g.columnSize + 0.2) / 2, (g.spanZ + g.columnSize + 0.2) / 2];
@@ -20162,7 +20626,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20162
20626
  parts.push({ id, material: c.material, shape: box3(size), position, structure: { role: "partition" }, tags: ["infill"] });
20163
20627
  for (const o of on) rests(joints, id, o, "mortar");
20164
20628
  };
20165
- const bay = (side, k, along, centre, fixed, length, c1, c2) => {
20629
+ const bay = (side, k, along, centre2, fixed, length, c1, c2) => {
20166
20630
  const clear = length - g.columnSize;
20167
20631
  const win = clear * c.window;
20168
20632
  const pier = (clear - win) / 2;
@@ -20173,11 +20637,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20173
20637
  wall(`infill_${f}_${side}_${k}_${name}`, size(u, h), [p[0], y, p[2]], on);
20174
20638
  };
20175
20639
  const id = (name) => `infill_${f}_${side}_${k}_${name}`;
20176
- put("sill", clear, sill, yb + sill / 2, centre, [c1, c2]);
20177
- put("pl", pier, head - sill, yb + sill + (head - sill) / 2, centre - clear / 2 + pier / 2, [c1, id("sill")]);
20178
- put("pr", pier, head - sill, yb + sill + (head - sill) / 2, centre + clear / 2 - pier / 2, [c2, id("sill")]);
20179
- put("head", clear, room - head, yb + head + (room - head) / 2, centre, [c1, c2]);
20180
- 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);
20181
20645
  const gid = `glass_${f}_${side}_${k}`;
20182
20646
  parts.push({
20183
20647
  id: gid,
@@ -20644,7 +21108,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20644
21108
  }
20645
21109
  var towerLocales = { en: { labels: en_default.labels }, "pt-BR": { labels: pt_BR_default.labels } };
20646
21110
 
20647
- // ../game-floorplan-three/dist/chunk-QUVGWVEC.js
21111
+ // ../game-floorplan-three/dist/chunk-7XVCZF6H.js
20648
21112
  var en2 = {
20649
21113
  "prompt.open": "E: open {what}",
20650
21114
  "prompt.close": "E: close {what}",
@@ -21386,6 +21850,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21386
21850
  }
21387
21851
  function installHouseWalls(K, shared2) {
21388
21852
  const { EPS: EPS3, Y0, boxC, bx, cursor, effectiveHeight, pieceRecord, structurePiece, wallColor } = K;
21853
+ const sh = (color2, factor) => K.sh(color2, factor);
21389
21854
  function wallBox(alongU, L, a, b, ya, yb, th, cL, cH, mL, mH) {
21390
21855
  if (yb - ya < 1e-4 || b - a < 1e-4) return;
21391
21856
  if (alongU) {
@@ -21429,6 +21894,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21429
21894
  function walls() {
21430
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)));
21431
21896
  const corners = /* @__PURE__ */ new Map();
21897
+ const looks = /* @__PURE__ */ new Map();
21432
21898
  for (const w of R.walls) {
21433
21899
  const f = w.floor;
21434
21900
  cursor.group = "f" + f;
@@ -21438,6 +21904,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21438
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);
21439
21905
  const front = alongU && Math.abs(L - vMin) < 0.05;
21440
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
+ }
21441
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 });
21442
21912
  if (w.type === "railing") {
21443
21913
  const c0 = shared2.ctx.colors.metal != null ? shared2.ctx.colors.metal : 9080466;
@@ -21484,8 +21954,27 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21484
21954
  cursor.group = "f" + e.f;
21485
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);
21486
21956
  }
21957
+ if (shared2.ctx.showPillars) wallPillars(looks);
21487
21958
  }
21488
- 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 });
21489
21978
  }
21490
21979
  function installHouseDoors(K, shared2) {
21491
21980
  const seedKey = (id) => K.seedKey(id);
@@ -26857,7 +27346,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
26857
27346
  if (!pc || pc.unit) return sp;
26858
27347
  if (pc.role !== "wall" && pc.role !== "partition") {
26859
27348
  const def = getAssembly(pc.assembly || (pc.material === "concrete" && ["slab", "beam", "column", "stair"].includes(pc.role) ? "reinforced_concrete" : ""));
26860
- 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;
26861
27351
  if (out.reinforcement && !out.units && pc.material === "concrete" && (pc.role === "beam" || pc.role === "column") && sp.shape.kind === "box") {
26862
27352
  return { ...out, units: { size: [MEMBER_CAGE.unit, MEMBER_CAGE.unit, Math.min(...sp.shape.size)], bond: "stack", mortar: "concrete" } };
26863
27353
  }
@@ -26883,6 +27373,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
26883
27373
  const a = pos.array;
26884
27374
  const u = mesh2.userData;
26885
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 });
26886
27377
  const list2 = u.collapsedRanges ?? (u.collapsedRanges = []);
26887
27378
  if (!list2.some((r) => r[0] === v0 && r[1] === v1)) list2.push([v0, v1]);
26888
27379
  const saved = a.slice(v0 * 3, v1 * 3);
@@ -26966,10 +27457,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
26966
27457
  const v = house.worldToLocal(tmp.p.set(p[0], p[1], p[2]));
26967
27458
  return [v.x, v.y, v.z];
26968
27459
  };
26969
- function cells(part, units) {
27460
+ function cells(part, units, mask) {
26970
27461
  const [u0, v0, u1, v1] = part.aabb;
26971
- const alongU = u1 - u0 >= v1 - v0;
27462
+ const alongU = mask.alongU;
26972
27463
  const len = alongU ? u1 - u0 : v1 - v0;
27464
+ const offset = (alongU ? u0 : v0) - mask.start;
26973
27465
  const [ul, uh] = units.size;
26974
27466
  const rows = Math.max(1, Math.round((part.y1 - part.y0) / uh));
26975
27467
  const rowH = (part.y1 - part.y0) / rows;
@@ -26980,7 +27472,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
26980
27472
  const shift = units.bond === "running" && r & 1 ? w / 2 : 0;
26981
27473
  for (let i = 0; i < per + (shift ? 1 : 0); i++) {
26982
27474
  const a = Math.max(0, i * w - shift), b = Math.min(len, i * w - shift + w);
26983
- 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 });
26984
27476
  }
26985
27477
  }
26986
27478
  return out;
@@ -26994,8 +27486,21 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
26994
27486
  for (const q of structure.parts) {
26995
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;
26996
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
+ }
26997
27498
  return null;
26998
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
+ });
26999
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);
27000
27505
  function shell(it, mask) {
27001
27506
  const g0 = it.mesh.geometry, g = new THREE.BufferGeometry();
@@ -27084,6 +27589,17 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
27084
27589
  texture.needsUpdate = true;
27085
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) };
27086
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
+ }
27087
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));
27088
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);
27089
27605
  function covered(c, boxes) {
@@ -27106,9 +27622,15 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
27106
27622
  const discs = [];
27107
27623
  const flakes = [];
27108
27624
  const shown = [];
27625
+ const pillars = pillarSpans(view);
27109
27626
  for (const id of view.parts) {
27110
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);
27111
- 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
+ }
27112
27634
  const thick = mask.alongU ? part.aabb[3] - part.aabb[1] : part.aabb[2] - part.aabb[0];
27113
27635
  const mid = mask.alongU ? (part.aabb[1] + part.aabb[3]) / 2 : (part.aabb[0] + part.aabb[2]) / 2;
27114
27636
  if (opening) discs.push({ s: along(opening.center), y: opening.center[1], r: opening.radius });
@@ -27204,52 +27726,55 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
27204
27726
  }
27205
27727
  return { center: local(chip.center), along: a, up: u };
27206
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
+ }
27207
27760
  function draw(partId, change) {
27208
27761
  if (lod !== 0) return false;
27209
27762
  const hostId = hostOf(partId);
27210
27763
  if (!hostId || !specOf(partId)?.units) return false;
27211
- let view = views.get(hostId);
27212
- if (!view) {
27213
- const items = structure.meshesOf(hostId).filter((it) => it.mesh);
27214
- if (!items.length) return false;
27215
- const parts = [hostId, ...riders(hostId)].filter((id) => structure.byId.get(id) && specOf(id)?.units);
27216
- if (!parts.includes(partId)) return false;
27217
- const mask = maskOf(hostId, parts), core = maskOf(hostId, parts);
27218
- view = {
27219
- host: hostId,
27220
- parts,
27221
- items,
27222
- mask,
27223
- core,
27224
- unitMaterial: unitsMaterial(core),
27225
- shells: [],
27226
- saved: null,
27227
- units: null,
27228
- gone: 0,
27229
- pits: 0,
27230
- bounds: drawnBounds(items),
27231
- chips: new Map(parts.map((id) => [id, []])),
27232
- spalls: new Map(parts.map((id) => [id, []])),
27233
- openings: /* @__PURE__ */ new Map(),
27234
- flakes: 0
27235
- };
27236
- }
27237
- if (!view.chips.has(partId)) return false;
27764
+ const view = views.get(hostId) ?? newView(hostId);
27765
+ if (!view || !view.chips.has(partId)) return false;
27238
27766
  if (change.opening) {
27239
27767
  if (view.openings.has(partId)) return false;
27240
27768
  view.openings.set(partId, { center: local(change.opening.center), radius: change.opening.radius });
27241
27769
  }
27242
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));
27243
27772
  if (change.spall) {
27244
27773
  const list2 = view.spalls.get(partId);
27245
27774
  list2.push({ center: local(change.spall.center), radius: change.spall.radius });
27246
27775
  if (list2.length > MAX_FLAKES) list2.shift();
27247
27776
  }
27248
- if (!view.saved) {
27249
- view.shells = view.items.map((it) => shell(it, view.mask));
27250
- for (const m of view.shells) m.userData.behind = () => view.units;
27251
- view.saved = view.items.map((it) => ({ it, v0: it.v0, data: collapseRange(it.mesh, it.v0, it.v1) }));
27252
- }
27777
+ cut(view);
27253
27778
  views.set(hostId, view);
27254
27779
  if (opts.deferPaint && change.spall) dirty.add(view);
27255
27780
  else {
@@ -27360,14 +27885,32 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
27360
27885
  */
27361
27886
  chip(event) {
27362
27887
  if (event.torn) return false;
27363
- 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 } });
27364
27891
  },
27365
27892
  /** The part broke or fell: its damage goes (the plain range stays collapsed, the renderer hides the part). */
27366
27893
  drop(partId) {
27367
- const v = views.get(partId);
27368
- 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;
27369
27912
  dispose(v);
27370
- views.delete(partId);
27913
+ views.delete(v.host);
27371
27914
  if (v.saved) dropped.push(v);
27372
27915
  },
27373
27916
  /** LOD 0 draws the cut wall; LOD 1 and 2 give the wall its box back. */
@@ -27842,6 +28385,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
27842
28385
  shared2.ctx.noPiece = false;
27843
28386
  cursor.separate = opts.pieces === "meshes";
27844
28387
  cursor.pc = null;
28388
+ shared2.ctx.showPillars = opts.pillars !== "hidden";
27845
28389
  shared2.ctx.apartment = r.template === "apartment_modern";
27846
28390
  shared2.ctx.pa = Math.max(3, Math.min(20, pr.floors != null ? pr.floors : 5));
27847
28391
  shared2.ctx.pf = Math.max(1, Math.min(shared2.ctx.pa, pr.floor != null ? pr.floor : 3));