@adatechnology/game-weapons-three 0.10.0 → 0.10.1

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.
@@ -19681,6 +19681,45 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19681
19681
  return angle === 0 ? q : quat.mul(q, quat.fromAxisAngle([0, 0, 1], angle));
19682
19682
  };
19683
19683
  var clamp = (v, a, b) => Math.max(a, Math.min(b, v));
19684
+ var splinterBox = (inp, box5, limit = inp.maxFragments) => {
19685
+ const { rnd: rnd2 } = inp;
19686
+ const dims = box5.s;
19687
+ const [fmin, fmax] = inp.fragmentSize;
19688
+ const g = inp.grain ?? [0, 1, 2].sort((a2, b2) => dims[b2] - dims[a2])[0];
19689
+ const [a, b] = [0, 1, 2].filter((i) => i !== g);
19690
+ const w = Math.max(fmin * 1.5, 0.01);
19691
+ const c = clamp(Math.round(dims[a] * dims[b] / (w * w)), 3, Math.max(3, Math.floor(limit / 2)));
19692
+ const maxLen = fmax * 4;
19693
+ const cells = bsp(box5, c, [a, b], rnd2);
19694
+ const pieces = [];
19695
+ for (const cell of cells) {
19696
+ const extra = rnd2() < 0.4 ? 1 : 0;
19697
+ const k = Math.max(1, Math.ceil(dims[g] / maxLen)) + extra;
19698
+ for (const p of bsp(cell, k, [g], rnd2)) pieces.push(p);
19699
+ }
19700
+ 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] }));
19701
+ };
19702
+ var severAt = (inp, root, at3, gap) => {
19703
+ const dims = root.s;
19704
+ const L = [0, 1, 2].sort((x, y) => dims[y] - dims[x])[0];
19705
+ const half = dims[L] / 2;
19706
+ const lo = clamp(at3 - gap / 2, -half, half), hi = clamp(at3 + gap / 2, -half, half);
19707
+ const out = [];
19708
+ const stub = (from, to) => {
19709
+ if (to - from < 0.02) return;
19710
+ const c = [root.c[0], root.c[1], root.c[2]], sz = [dims[0], dims[1], dims[2]];
19711
+ c[L] = (from + to) / 2;
19712
+ sz[L] = to - from;
19713
+ out.push({ center: c, size: sz, q: quat.identity(), shape: "slab", weight: sz[0] * sz[1] * sz[2] });
19714
+ };
19715
+ stub(-half, lo);
19716
+ stub(hi, half);
19717
+ const band = { c: [root.c[0], root.c[1], root.c[2]], s: [dims[0], dims[1], dims[2]] };
19718
+ band.c[L] = (lo + hi) / 2;
19719
+ band.s[L] = Math.max(hi - lo, 0.01);
19720
+ for (const p of splinterBox(inp, band, Math.max(3, inp.maxFragments - out.length))) out.push(p);
19721
+ return out;
19722
+ };
19684
19723
  var tearStack = (inp, root, t, u, v, n, sheet) => {
19685
19724
  const { dims, rnd: rnd2, maxFragments } = inp;
19686
19725
  const layers = clamp(Math.ceil(dims[t] / STACK_LAYER), 2, 4);
@@ -19719,23 +19758,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19719
19758
  return out;
19720
19759
  }
19721
19760
  if (style === "splinter") {
19722
- const g = inp.grain ?? [0, 1, 2].sort((a2, b2) => dims[b2] - dims[a2])[0];
19723
- const [a, b] = [0, 1, 2].filter((i) => i !== g);
19724
- const w = Math.max(fmin * 1.5, 0.01);
19725
- const c = clamp(Math.round(dims[a] * dims[b] / (w * w)), 3, Math.max(3, Math.floor(maxFragments / 2)));
19726
- const maxLen = fmax * 4;
19727
- const cells = bsp(root, c, [a, b], rnd2);
19728
- const pieces = [];
19729
- for (const cell of cells) {
19730
- let list2 = [cell];
19731
- const extra = rnd2() < 0.4 ? 1 : 0;
19732
- const k = Math.max(1, Math.ceil(dims[g] / maxLen)) + extra;
19733
- list2 = bsp(cell, k, [g], rnd2);
19734
- for (const p of list2) pieces.push(p);
19735
- }
19736
- for (const p of pieces.slice(0, maxFragments)) {
19737
- out.push({ 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] });
19738
- }
19761
+ if (inp.cut) return severAt(inp, root, inp.cut.at, inp.cut.gap);
19762
+ for (const p of splinterBox(inp, root)) out.push(p);
19739
19763
  return out;
19740
19764
  }
19741
19765
  if (style === "tear") {
@@ -21723,9 +21747,14 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21723
21747
  var BREACH_AREA = 0.25;
21724
21748
  var BREACH_BLAST_REACH = 0.12;
21725
21749
  var RESIDUAL_CAPACITY = 0.15;
21750
+ var SEVER_BAND = 0.1;
21751
+ var SEVER_ASPECT = 3;
21752
+ var SEVER_BALLISTIC = 2;
21726
21753
  var SUPPORT_TOLERANCE = 0.05;
21727
21754
  var UNIT_FRAGMENT_FACTOR = 4;
21728
21755
  var BREACH_GRID_CAP = 4096;
21756
+ var CHIP_ENERGY = 200;
21757
+ var CHIP_SPLASH = 0.5;
21729
21758
  var CUT_SLICE = 1;
21730
21759
  var cutFactorOf = (shape) => clamp2(Math.max(...shapeDims(shape)) / CUT_SLICE, 1, 50);
21731
21760
  var clamp2 = (v, a, b) => Math.max(a, Math.min(b, v));
@@ -21769,6 +21798,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21769
21798
  let windCfg = windOpts ? resolveWind(windOpts) : null;
21770
21799
  const ignitionRules = resolveIgnitionRules(options.ignition);
21771
21800
  const breachBlastFactor = options.breach?.blastFactor ?? BREACH_BLAST_FACTOR;
21801
+ const chipEnergy = options.breach?.chipEnergy ?? CHIP_ENERGY;
21772
21802
  const rng2 = createRng2(options.seed ?? 1);
21773
21803
  const rnd2 = () => rng2.next();
21774
21804
  let time = 0;
@@ -22142,41 +22172,68 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
22142
22172
  markDamaged(p);
22143
22173
  if (p.rt.hinge >= flexure.failRotationDeg * Math.PI / 180) hingeDrop(o, p, src);
22144
22174
  };
22175
+ const freeSpan = (p, supports) => {
22176
+ if (supports.length === 0) return Infinity;
22177
+ const d = p.dims;
22178
+ const L = [0, 1, 2].sort((a, b) => d[b] - d[a])[0];
22179
+ const spans = supports.map((k) => contactBox(p, k)).map(([lo, hi]) => [lo[L], hi[L]]);
22180
+ let far = 0;
22181
+ for (let i = 0; i <= 16; i++) {
22182
+ const x = (i / 16 - 0.5) * d[L];
22183
+ let near = Infinity;
22184
+ for (const [lo, hi] of spans) near = Math.min(near, Math.max(lo - x, x - hi, 0));
22185
+ far = Math.max(far, near);
22186
+ }
22187
+ return 2 * far;
22188
+ };
22145
22189
  const solveStructure = (o) => {
22146
22190
  o.maxStress = 0;
22147
22191
  if (!o.hasStructure) return;
22148
22192
  const parts = o.parts;
22149
22193
  const n = parts.length;
22150
22194
  const dist2 = new Array(n).fill(-1);
22151
- const queue = [];
22152
22195
  const ok = (i) => parts[i].structural && !isLost(parts[i]);
22153
22196
  const isPartition = (i) => parts[i].spec.structure?.role === "partition";
22154
- for (let i = 0; i < n; i++) if (ok(i) && parts[i].anchored) {
22155
- dist2[i] = 0;
22156
- queue.push(i);
22157
- }
22158
- for (let h = 0; h < queue.length; h++) {
22159
- const s = queue[h];
22160
- if (isPartition(s)) continue;
22161
- for (const j of o.byPart.get(parts[s].spec.id)) {
22162
- if (j.st.open || !j.bears) continue;
22163
- const r = j.ia === s ? j.ib : j.ia;
22164
- if (dist2[r] >= 0 || !ok(r) || restsOn(o, j, r) !== s) continue;
22165
- dist2[r] = dist2[s] + 1;
22166
- queue.push(r);
22197
+ const out = /* @__PURE__ */ new Set();
22198
+ let queue = [];
22199
+ let lowerOf = /* @__PURE__ */ new Map();
22200
+ for (; ; ) {
22201
+ dist2.fill(-1);
22202
+ queue = [];
22203
+ for (let i = 0; i < n; i++) if (ok(i) && !out.has(i) && parts[i].anchored) {
22204
+ dist2[i] = 0;
22205
+ queue.push(i);
22206
+ }
22207
+ for (let h = 0; h < queue.length; h++) {
22208
+ const s = queue[h];
22209
+ if (isPartition(s)) continue;
22210
+ for (const j of o.byPart.get(parts[s].spec.id)) {
22211
+ if (j.st.open || !j.bears) continue;
22212
+ const r = j.ia === s ? j.ib : j.ia;
22213
+ if (dist2[r] >= 0 || !ok(r) || out.has(r) || restsOn(o, j, r) !== s) continue;
22214
+ dist2[r] = dist2[s] + 1;
22215
+ queue.push(r);
22216
+ }
22167
22217
  }
22168
- }
22169
- const incoming = new Array(n).fill(0);
22170
- const lowerOf = /* @__PURE__ */ new Map();
22171
- for (const i of queue) {
22172
- const lower = [];
22173
- for (const j of o.byPart.get(parts[i].spec.id)) {
22174
- if (j.st.open || !j.bears) continue;
22175
- const k = restsOn(o, j, i);
22176
- if (k >= 0 && dist2[k] >= 0 && dist2[k] < dist2[i] && !isPartition(k)) lower.push(k);
22218
+ lowerOf = /* @__PURE__ */ new Map();
22219
+ let dropped = false;
22220
+ for (const i of queue) {
22221
+ const lower = [];
22222
+ for (const j of o.byPart.get(parts[i].spec.id)) {
22223
+ if (j.st.open || !j.bears) continue;
22224
+ const k = restsOn(o, j, i);
22225
+ if (k >= 0 && dist2[k] >= 0 && dist2[k] < dist2[i] && !isPartition(k)) lower.push(k);
22226
+ }
22227
+ lowerOf.set(i, lower);
22228
+ const max2 = parts[i].spec.structure?.maxSpan;
22229
+ if (max2 !== void 0 && !parts[i].anchored && freeSpan(parts[i], lower.map((k) => parts[k])) > max2) {
22230
+ out.add(i);
22231
+ dropped = true;
22232
+ }
22177
22233
  }
22178
- lowerOf.set(i, lower);
22234
+ if (!dropped) break;
22179
22235
  }
22236
+ const incoming = new Array(n).fill(0);
22180
22237
  const bending = new Array(n).fill(0);
22181
22238
  const through = parts.map(() => []);
22182
22239
  const pass = (k, at3, mass) => {
@@ -22688,6 +22745,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
22688
22745
  const r = rayPart(o, p, origin, d);
22689
22746
  if (!r || r.t0 < 0 || r.t0 > maxDist) continue;
22690
22747
  if (p.st.opening && insideOpening(o, p, vec.madd(origin, d, r.t0))) continue;
22748
+ if (p.st.chipped && insideChip(p, objLocal(o, vec.madd(origin, d, r.t0)))) continue;
22691
22749
  if (!best || r.t0 < best.distance) best = { object: o.id, part: p.spec.id, point: vec.madd(origin, d, r.t0), normal: r.n, distance: r.t0, pathLen: r.t1 - r.t0 };
22692
22750
  }
22693
22751
  }
@@ -22951,6 +23009,62 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
22951
23009
  if (total < (p.spec.breach?.area ?? BREACH_AREA)) return;
22952
23010
  openBreach(o, p, [sum2[0] / total, sum2[1] / total, sum2[2] / total], total, energy, dir, src);
22953
23011
  };
23012
+ const unitGrids = /* @__PURE__ */ new WeakMap();
23013
+ const gridOf = (p) => {
23014
+ let grid = unitGrids.get(p);
23015
+ if (!grid) unitGrids.set(p, grid = unitGrid(p.dims, p.thin, unitsOf2(p), BREACH_GRID_CAP));
23016
+ return grid;
23017
+ };
23018
+ const insideChip = (p, objectPoint) => {
23019
+ const cells = p.st.chipped;
23020
+ if (!cells?.length) return false;
23021
+ const [u, v] = planeAxes(p.thin);
23022
+ const lp = toLocal(p.pos, p.q, objectPoint);
23023
+ return cells.some((c) => Math.abs(lp[u] - c.center[u]) <= c.size[u] / 2 && Math.abs(lp[v] - c.center[v]) <= c.size[v] / 2);
23024
+ };
23025
+ const chipUnits = (o, p, wp, energy, dir, radius, src) => {
23026
+ var _a, _b;
23027
+ if (!p.spec.units || p.spec.reinforcement || !p.staged || chipEnergy <= 0 || energy <= 0 || p.st.stripped || isLost(p) || (p.st.stage ?? 0) >= 2) return;
23028
+ const [u, v] = planeAxes(p.thin);
23029
+ const lp = toLocal(p.pos, p.q, objLocal(o, wp));
23030
+ const grid = gridOf(p);
23031
+ const worn = (_a = p.rt).chips ?? (_a.chips = {});
23032
+ const out = [];
23033
+ for (let i = 0; i < grid.length; i++) {
23034
+ const cell = grid[i];
23035
+ if (worn[i] === -1) continue;
23036
+ const gap = Math.hypot(Math.max(0, Math.abs(lp[u] - cell.center[u]) - cell.size[u] / 2), Math.max(0, Math.abs(lp[v] - cell.center[v]) - cell.size[v] / 2));
23037
+ if (gap > radius) continue;
23038
+ const share = gap === 0 ? 1 : CHIP_SPLASH * (1 - gap / radius);
23039
+ const total = (worn[i] ?? 0) + energy * share;
23040
+ const mass = p.mat.density * cell.size[0] * cell.size[1] * cell.size[2];
23041
+ if (total < chipEnergy * mass) {
23042
+ worn[i] = total;
23043
+ continue;
23044
+ }
23045
+ worn[i] = -1;
23046
+ out.push(cell);
23047
+ }
23048
+ if (out.length === 0) return;
23049
+ const { c, q } = partXf(o, p);
23050
+ const chipped = (_b = p.st).chipped ?? (_b.chipped = []);
23051
+ const holes = localHolesOf(p);
23052
+ for (const cell of out) {
23053
+ const mass = Math.min(p.st.mass, p.mat.density * cell.size[0] * cell.size[1] * cell.size[2]);
23054
+ const center = toWorld(c, q, cell.center);
23055
+ const speed = clamp2(Math.sqrt(2 * energy * 0.05 / Math.max(mass, 0.05)), 0.5, 6);
23056
+ const fragments = cellFragments(o, p, [cell], wp, dir, speed, mass, `c${chipped.length}_`);
23057
+ p.st.holes = p.st.holes.filter((_, k) => marksInBox(cell.center, cell.size, void 0, [holes[k]]).length === 0);
23058
+ chipped.push({ center: [cell.center[0], cell.center[1], cell.center[2]], size: [cell.size[0], cell.size[1], cell.size[2]] });
23059
+ p.st.mass = Math.max(0, p.st.mass - mass);
23060
+ p.rt.removed = (p.rt.removed ?? 0) + mass;
23061
+ markDamaged(p);
23062
+ setStage(o, p, 1, {}, src);
23063
+ emit2({ ...base(o, src), type: "chip", part: p.spec.id, center, size: [cell.size[0], cell.size[1], cell.size[2]], rotation: q, fragments });
23064
+ debris2(o, p, p.mat.breakDebris, center, vec.scale(dir ?? [0, 1, 0], -1), 6, 2, src);
23065
+ }
23066
+ for (const cell of out) addBreach(o, p, toWorld(c, q, cell.center), cell.size[u] * cell.size[v], energy, dir, src);
23067
+ };
22954
23068
  const openBreach = (o, p, center, total, energy, dir, src) => {
22955
23069
  const [u, v] = planeAxes(p.thin);
22956
23070
  const radius = clamp2(Math.sqrt(total / Math.PI), Math.sqrt((p.spec.breach?.area ?? BREACH_AREA) / Math.PI), breachRadius);
@@ -22962,7 +23076,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
22962
23076
  failPart(o, p, worldCenter, dir, energy, 12, src);
22963
23077
  return;
22964
23078
  }
22965
- const cells = cellsWithin(unitGrid(p.dims, p.thin, unitsOf2(p), BREACH_GRID_CAP), p.thin, lc, radius);
23079
+ const worn = p.rt.chips;
23080
+ const cells = cellsWithin(gridOf(p).filter((_, i) => worn?.[i] !== -1), p.thin, lc, radius);
22966
23081
  const removed = p.st.mass * cells.reduce((sum2, cell) => sum2 + cell.size[u] * cell.size[v], 0) / (p.dims[u] * p.dims[v]);
22967
23082
  const speed = clamp2(Math.sqrt(2 * energy * 0.1 / Math.max(removed, 0.05)), 0.5, 8);
22968
23083
  const fragments = cellFragments(o, p, cells, worldCenter, dir, speed, removed, "b");
@@ -23046,6 +23161,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23046
23161
  const grain = p.spec.grain ? AXIS[p.spec.grain] : void 0;
23047
23162
  const hitLocal = toLocal(c, q, hitW);
23048
23163
  const unitPlan = p.spec.units ? unitFragmentPlan(p, maxFragments * UNIT_FRAGMENT_FACTOR) : [];
23164
+ const axis2 = style === "splinter" ? severAxis(p) : -1;
23165
+ const cutAt2 = axis2 < 0 ? void 0 : p.rt.cutAt ?? (origin ? hitLocal[axis2] : void 0);
23049
23166
  const plan = unitPlan.length > 0 ? unitPlan : planFragments({
23050
23167
  style,
23051
23168
  dims: p.dims,
@@ -23055,7 +23172,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23055
23172
  fragmentSize: p.mat.fragmentSize ?? [0.04, 0.2],
23056
23173
  maxFragments,
23057
23174
  rnd: rnd2,
23058
- sheet: p.mat.sheet
23175
+ sheet: p.mat.sheet,
23176
+ ...cutAt2 !== void 0 ? { cut: { at: cutAt2, gap: SEVER_BAND } } : {}
23059
23177
  });
23060
23178
  const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
23061
23179
  const mass = p.st.mass;
@@ -23114,10 +23232,33 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23114
23232
  emit2({ ...base(o, src), type: "primed", part: p.spec.id, point: partXf(o, p).c, trigger, delay });
23115
23233
  };
23116
23234
  const CHAIN_TRIGGER_OF = { ballistic: "shot", explosive: "explosion" };
23235
+ const severAxis = (p) => {
23236
+ if (p.mat.fracture !== "splinter" || p.spec.shape.kind !== "box") return -1;
23237
+ const d = p.dims;
23238
+ const order = [0, 1, 2].sort((a, b) => d[b] - d[a]);
23239
+ return d[order[0]] >= SEVER_ASPECT * d[order[1]] && d[order[0]] >= 3 * SEVER_BAND ? order[0] : -1;
23240
+ };
23241
+ const bandOf = (o, p, axis2, origin) => {
23242
+ const { c, q } = partXf(o, p);
23243
+ const along = toLocal(c, q, origin)[axis2];
23244
+ const L = p.dims[axis2];
23245
+ return clamp2(Math.floor((along + L / 2) / SEVER_BAND), 0, Math.ceil(L / SEVER_BAND) - 1);
23246
+ };
23117
23247
  const applyDamage = (o, p, amount, type, origin, dir, fragEnergy, cap, src) => {
23248
+ var _a;
23118
23249
  if (isLost(p) || amount <= 0) return 0;
23119
23250
  const prev = p.st.hp;
23120
23251
  p.st.hp -= amount;
23252
+ const axis2 = origin && type !== "fire" && type !== "heat" ? severAxis(p) : -1;
23253
+ if (axis2 >= 0 && p.st.hp > 0) {
23254
+ const band = bandOf(o, p, axis2, origin);
23255
+ const cuts = (_a = p.rt).cuts ?? (_a.cuts = {});
23256
+ cuts[band] = (cuts[band] ?? 0) + amount * (type === "ballistic" ? SEVER_BALLISTIC : 1);
23257
+ if (cuts[band] >= p.st.maxHp * SEVER_BAND / p.dims[axis2]) {
23258
+ p.rt.cutAt = -p.dims[axis2] / 2 + (band + 0.5) * SEVER_BAND;
23259
+ p.st.hp = 0;
23260
+ }
23261
+ }
23121
23262
  markDamaged(p);
23122
23263
  const trigger = CHAIN_TRIGGER_OF[type];
23123
23264
  const chained = trigger !== void 0 && chainDelayOf(p.spec, trigger) !== void 0;
@@ -23156,7 +23297,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23156
23297
  const ballistic = (o, p, entry2, n, d, E, caliber, pathLen, src, round5 = {}) => {
23157
23298
  const mat = p.mat;
23158
23299
  const piercing = round5.piercing ?? 1;
23159
- if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry2))) {
23300
+ if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry2)) || insideChip(p, objLocal(o, entry2))) {
23160
23301
  return { penetrated: true, exitEnergy: E, exitPoint: vec.madd(entry2, d, Math.min(pathLen, p.thickness)), damage: 0 };
23161
23302
  }
23162
23303
  const sin2 = Math.min(1, Math.abs(vec.dot(d, n)));
@@ -23215,6 +23356,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23215
23356
  debris2(o, p, mat.hitDebris, entry2, n, clamp2(Math.round(E / 60) + 2, 2, 14), clamp2(Math.sqrt(E) / 6, 1, 8), src);
23216
23357
  wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true);
23217
23358
  const dmg = applyDamage(o, p, E * DMG_PER_J * scale, "ballistic", entry2, d, E * 0.1, 12, src);
23359
+ chipUnits(o, p, entry2, penetrated ? Math.min(E, Math.max(thr, E * 0.5)) : E, d, Math.max(0.05, caliber / 1e3 * 8), src);
23218
23360
  return { penetrated, exitEnergy: penetrated ? penMm !== void 0 ? E * (1 - thr / Ep) : E - thr / (hard ? piercing : 1) : 0, exitPoint, damage: dmg };
23219
23361
  };
23220
23362
  const melee = (o, p, kind, E, point, n, d, contactR, src) => {
@@ -23242,6 +23384,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23242
23384
  const scale = mat.damageScale?.[kind] ?? 1;
23243
23385
  const done = applyDamage(o, p, Ec * DMG_PER_J * scale * (kind === "slash" ? p.cutFactor : 1), kind, point, d, E * 0.1, 12, src);
23244
23386
  addBreach(o, p, point, breachAreaOf(p, done, false), E, d, src);
23387
+ if (kind === "blunt") chipUnits(o, p, point, E, d, Math.max(contactR, 0.03), src);
23245
23388
  return done;
23246
23389
  };
23247
23390
  const blastPart = (o, p, point, energy, radius, src, res, reachAt) => {
@@ -33260,12 +33403,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
33260
33403
  A.invalidate = () => {
33261
33404
  for (let i = 0; i < NS; i++) PL[i].shadow.needsUpdate = true;
33262
33405
  };
33263
- A.update = function(houses, focus, op) {
33406
+ A.update = function(houses2, focus, op) {
33264
33407
  op = op || {};
33265
- houses = (Array.isArray(houses) ? houses : [houses]).filter(Boolean);
33408
+ houses2 = (Array.isArray(houses2) ? houses2 : [houses2]).filter(Boolean);
33266
33409
  _f.set(focus.x, focus.y, focus.z);
33267
33410
  const rooms = [], exterior = [];
33268
- for (const house of houses) {
33411
+ for (const house of houses2) {
33269
33412
  if (house.group && !house.group.visible) continue;
33270
33413
  house.house.updateMatrixWorld(true);
33271
33414
  const M = house.house.matrixWorld, by = /* @__PURE__ */ new Map();
@@ -33644,7 +33787,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
33644
33787
  }
33645
33788
  return null;
33646
33789
  };
33647
- function courses(part, units, opening, paint2) {
33790
+ const local2 = (p) => {
33791
+ house.updateMatrixWorld(true);
33792
+ const v = house.worldToLocal(tmp.p.set(p[0], p[1], p[2]));
33793
+ return [v.x, v.y, v.z];
33794
+ };
33795
+ function courses(part, units, opening, chips2, paint2) {
33648
33796
  const [u0, v0, u1, v1] = part.aabb;
33649
33797
  const alongU = u1 - u0 >= v1 - v0;
33650
33798
  const len = alongU ? u1 - u0 : v1 - v0;
@@ -33668,23 +33816,39 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
33668
33816
  const x = alongU ? u0 + mid : (u0 + u1) / 2;
33669
33817
  const z = alongU ? (v0 + v1) / 2 : v0 + mid;
33670
33818
  const y = part.y0 + (r + 0.5) * rowH;
33671
- const d = Math.hypot(x - opening.center[0], y - opening.center[1], z - opening.center[2]);
33672
- if (d < opening.radius) continue;
33673
- list2.push({ x, y, z, sx: alongU ? b - a : thick, sy: rowH, sz: alongU ? thick : b - a, colour: d < opening.radius + 0.25 || paint2 === null ? edge : paint2 });
33819
+ const d = opening ? Math.hypot(x - opening.center[0], y - opening.center[1], z - opening.center[2]) : Infinity;
33820
+ if (opening && d < opening.radius) continue;
33821
+ const gone = (c) => Math.abs(c[1] - y) <= rowH / 2 && (alongU ? c[0] >= u0 + a && c[0] <= u0 + b : c[2] >= v0 + a && c[2] <= v0 + b);
33822
+ if (chips2.some(gone)) continue;
33823
+ const near = opening && d < opening.radius + 0.25 || chips2.some((c) => Math.hypot(c[0] - x, c[1] - y, c[2] - z) < 0.15);
33824
+ list2.push({ x, y, z, sx: alongU ? b - a : thick, sy: rowH, sz: alongU ? thick : b - a, colour: near || paint2 === null ? edge : paint2 });
33674
33825
  }
33675
33826
  }
33676
33827
  return list2;
33677
33828
  }
33678
- function open(event) {
33679
- if (lod !== 0 || views.has(event.part) || !event.opening) return false;
33680
- const part = structure.byId.get(event.part);
33681
- const spec = opts.spec()?.parts?.find((q) => q.id === event.part);
33829
+ function draw2(partId, change) {
33830
+ if (lod !== 0) return false;
33831
+ const part = structure.byId.get(partId);
33832
+ const spec = opts.spec()?.parts?.find((q) => q.id === partId);
33682
33833
  if (!part || !spec || !spec.units) return false;
33683
- const items = structure.meshesOf(event.part).filter((it) => it.mesh);
33684
- if (!items.length) return false;
33685
- const list2 = courses(part, spec.units, event.opening, wallColour(items));
33834
+ let view = views.get(partId);
33835
+ if (!view) {
33836
+ const items = structure.meshesOf(partId).filter((it) => it.mesh);
33837
+ if (!items.length) return false;
33838
+ view = { mesh: null, saved: null, items, paint: wallColour(items), units: 0, opening: null, chips: [] };
33839
+ }
33840
+ if (change.opening) {
33841
+ if (view.opening) return false;
33842
+ view.opening = { center: local2(change.opening.center), radius: change.opening.radius };
33843
+ }
33844
+ if (change.chip) view.chips.push(local2(change.chip));
33845
+ const list2 = courses(part, spec.units, view.opening, view.chips, view.paint);
33846
+ if (view.mesh) {
33847
+ view.mesh.removeFromParent();
33848
+ view.mesh.dispose();
33849
+ }
33686
33850
  const mesh3 = new THREE.InstancedMesh(unitGeometry, unitMaterial, Math.max(1, list2.length));
33687
- mesh3.name = "breach:" + event.part;
33851
+ mesh3.name = "breach:" + partId;
33688
33852
  list2.forEach((u, i) => {
33689
33853
  tmp.m.compose(tmp.p.set(u.x, u.y, u.z), tmp.q.identity(), tmp.s.set(u.sx, u.sy, u.sz));
33690
33854
  mesh3.setMatrixAt(i, tmp.m);
@@ -33696,8 +33860,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
33696
33860
  mesh3.castShadow = true;
33697
33861
  mesh3.receiveShadow = true;
33698
33862
  house.add(mesh3);
33699
- const saved = items.map((it) => ({ it, v0: it.v0, data: collapseRange(it.mesh, it.v0, it.v1) }));
33700
- views.set(event.part, { mesh: mesh3, saved, units: list2.length, opening: event.opening });
33863
+ if (!view.saved) view.saved = view.items.map((it) => ({ it, v0: it.v0, data: collapseRange(it.mesh, it.v0, it.v1) }));
33864
+ view.mesh = mesh3;
33865
+ view.units = list2.length;
33866
+ views.set(partId, view);
33701
33867
  invalidate();
33702
33868
  invalidateShadow();
33703
33869
  return true;
@@ -33711,7 +33877,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
33711
33877
  return {
33712
33878
  /** Handles a `stage` event of the house's object. Returns true when the wall was redrawn as courses. */
33713
33879
  stage(event) {
33714
- return event.stage === 2 ? open(event) : false;
33880
+ return event.stage === 2 && !!event.opening ? draw2(event.part, { opening: event.opening }) : false;
33881
+ },
33882
+ /** Handles a `chip` event: the unit that came out leaves a gap in the courses. Returns true when the wall was redrawn. */
33883
+ chip(event) {
33884
+ return draw2(event.part, { chip: event.center });
33715
33885
  },
33716
33886
  /** The part broke or fell: the courses go (the plain range stays collapsed, the renderer hides the part). */
33717
33887
  drop(partId) {
@@ -33941,6 +34111,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
33941
34111
  case "stage":
33942
34112
  breach.stage(e);
33943
34113
  break;
34114
+ case "chip":
34115
+ breach.chip(e);
34116
+ break;
33944
34117
  case "collapse":
33945
34118
  for (const id of e.parts) {
33946
34119
  breach.drop(id);
@@ -33995,6 +34168,32 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
33995
34168
  if (D.active && D.world.has(D.id)) D.world.remove(D.id);
33996
34169
  D.active = false;
33997
34170
  };
34171
+ D.follow = (world, id, spec) => {
34172
+ D.spec = spec;
34173
+ const offFollow = world.on("*", (e) => {
34174
+ if (e.object !== id) return;
34175
+ switch (e.type) {
34176
+ case "stage":
34177
+ breach.stage(e);
34178
+ break;
34179
+ case "chip":
34180
+ breach.chip(e);
34181
+ break;
34182
+ case "fracture":
34183
+ breach.drop(e.part);
34184
+ break;
34185
+ case "collapse":
34186
+ for (const p of e.parts) breach.drop(p);
34187
+ break;
34188
+ default:
34189
+ break;
34190
+ }
34191
+ });
34192
+ return { restore: () => breach.restore(), dispose: () => {
34193
+ offFollow();
34194
+ breach.restore();
34195
+ } };
34196
+ };
33998
34197
  D.piece = (id) => D.active ? D.world.part(D.id, id) : null;
33999
34198
  D.state = () => D.active ? D.world.state(D.id) : null;
34000
34199
  D.reset = () => {
@@ -34717,6 +34916,20 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
34717
34916
  return buildApi(K, shared);
34718
34917
  }
34719
34918
  var WOOD_THIN = 0.035;
34919
+ var SHELF_SPAN = 1.5;
34920
+ var SHELVING = /shelf|bookcase|rack/;
34921
+ function carcass(parts, drawn) {
34922
+ const all = [...drawn.values()];
34923
+ const width = Math.max(...all.map((p) => p.max[0])) - Math.min(...all.map((p) => p.min[0]));
34924
+ for (const part of parts) {
34925
+ if (LOOSE.has(part.material) || part.shape.kind !== "box" || part.rotation) continue;
34926
+ const [x, y, z] = part.shape.size;
34927
+ if (y >= x && y >= z) {
34928
+ if (z < x && x >= 0.6 * width) continue;
34929
+ part.structure = { role: "column" };
34930
+ } else if (y <= x && y <= z) part.structure = { role: "beam", maxSpan: SHELF_SPAN * Math.max(x, z) };
34931
+ }
34932
+ }
34720
34933
  var FLOOR_TOUCH = 5e-3;
34721
34934
  var LOOSE = /* @__PURE__ */ new Set(["paper"]);
34722
34935
  var BASE_MATERIAL = {
@@ -34832,6 +35045,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
34832
35045
  if (area3 < 0.6 * footprint || p.max[1] < 0.2) continue;
34833
35046
  if (!top || p.max[1] > top.y + 1e-6) top = { part: p.id, y: p.max[1] };
34834
35047
  }
35048
+ if (opts.carcass) carcass(parts, byId);
34835
35049
  const spec = { name, parts, joints, tags: ["furniture"] };
34836
35050
  const colors = new Map(drawn.map((p) => [p.id, p.color]));
34837
35051
  const build = (T3) => {
@@ -34875,7 +35089,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
34875
35089
  if (w === void 0 || d === void 0 || h === void 0) throw new Error(`game-floorplan-three: furniture type "${type}" is not in the catalogue, so give w, d and h`);
34876
35090
  const o = opts.options ?? {};
34877
35091
  const seed = Math.round(opts.seed ?? 1);
34878
- const plain2 = /shelf|bookcase|rack/.test(type) ? "paper" : "plastic";
35092
+ const plain2 = SHELVING.test(type) ? "paper" : "plastic";
34879
35093
  const root = { x: seed, z: 0, ry: 0, w, d, h, y: 0, base: 0, palette: o.palette, seed: `furniture_${type}_${seed}` };
34880
35094
  const { K, shared } = renderer.internals;
34881
35095
  const before = K.PARTS.length;
@@ -34894,7 +35108,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
34894
35108
  shared.ctx = saved.ctx;
34895
35109
  }
34896
35110
  },
34897
- { materials: { plain: plain2, ...o.materials }, chairs: o.chairs }
35111
+ { materials: { plain: plain2, ...o.materials }, chairs: o.chairs, carcass: SHELVING.test(type) }
34898
35112
  );
34899
35113
  const spouts = K.PARTS.slice(before).filter((p) => p.type === "water_tap").map((p) => [p.pos[0] - root.x, p.pos[1], p.pos[2] - root.z]);
34900
35114
  K.PARTS.length = before;