@adatechnology/game-items-three 0.3.0 → 0.3.2

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.
@@ -12694,6 +12694,62 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
12694
12694
  return angle === 0 ? q : quat.mul(q, quat.fromAxisAngle([0, 0, 1], angle));
12695
12695
  };
12696
12696
  var clamp = (v, a, b) => Math.max(a, Math.min(b, v));
12697
+ var splinterBox = (inp, box4, limit = inp.maxFragments) => {
12698
+ const { rnd } = inp;
12699
+ const dims = box4.s;
12700
+ const [fmin, fmax] = inp.fragmentSize;
12701
+ const g = inp.grain ?? [0, 1, 2].sort((a2, b2) => dims[b2] - dims[a2])[0];
12702
+ const [a, b] = [0, 1, 2].filter((i) => i !== g);
12703
+ const w = Math.max(fmin * 1.5, 0.01);
12704
+ const c = clamp(Math.round(dims[a] * dims[b] / (w * w)), 3, Math.max(3, Math.floor(limit / 2)));
12705
+ const maxLen = fmax * 4;
12706
+ const cells = bsp(box4, c, [a, b], rnd);
12707
+ const pieces = [];
12708
+ for (const cell of cells) {
12709
+ const extra = rnd() < 0.4 ? 1 : 0;
12710
+ const k = Math.max(1, Math.ceil(dims[g] / maxLen)) + extra;
12711
+ for (const p of bsp(cell, k, [g], rnd)) pieces.push(p);
12712
+ }
12713
+ 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] }));
12714
+ };
12715
+ var severAt = (inp, root, at, gap) => {
12716
+ const dims = root.s;
12717
+ const L = [0, 1, 2].sort((x, y) => dims[y] - dims[x])[0];
12718
+ const half = dims[L] / 2;
12719
+ const lo = clamp(at - gap / 2, -half, half), hi = clamp(at + gap / 2, -half, half);
12720
+ const out = [];
12721
+ const stub = (from, to) => {
12722
+ if (to - from < 0.02) return;
12723
+ const c = [root.c[0], root.c[1], root.c[2]], sz = [dims[0], dims[1], dims[2]];
12724
+ c[L] = (from + to) / 2;
12725
+ sz[L] = to - from;
12726
+ out.push({ center: c, size: sz, q: quat.identity(), shape: "slab", weight: sz[0] * sz[1] * sz[2] });
12727
+ };
12728
+ stub(-half, lo);
12729
+ stub(hi, half);
12730
+ const band = { c: [root.c[0], root.c[1], root.c[2]], s: [dims[0], dims[1], dims[2]] };
12731
+ band.c[L] = (lo + hi) / 2;
12732
+ band.s[L] = Math.max(hi - lo, 0.01);
12733
+ for (const p of splinterBox(inp, band, Math.max(3, inp.maxFragments - out.length))) out.push(p);
12734
+ return out;
12735
+ };
12736
+ var tearStack = (inp, root, t, u, v, n, sheet) => {
12737
+ const { dims, rnd, maxFragments } = inp;
12738
+ const layers = clamp(Math.ceil(dims[t] / STACK_LAYER), 2, 4);
12739
+ const per = clamp(n, 3, Math.max(3, Math.floor(maxFragments / layers)));
12740
+ const out = [];
12741
+ for (let k = 0; k < layers; k++) {
12742
+ const off = -dims[t] / 2 + (k + 0.5) * dims[t] / layers;
12743
+ for (const b of bsp(root, per, [u, v], rnd)) {
12744
+ if (out.length >= maxFragments) return out;
12745
+ const c = [b.c[0], b.c[1], b.c[2]];
12746
+ c[t] = off;
12747
+ out.push({ center: c, size: [b.s[u], b.s[v], sheet], q: frame(u, v, t, (rnd() - 0.5) * 0.8), shape: "scrap", weight: b.s[u] * b.s[v], inner: out.length % 4 !== 0 });
12748
+ }
12749
+ }
12750
+ return out;
12751
+ };
12752
+ var STACK_LAYER = 0.012;
12697
12753
  var planFragments = (inp) => {
12698
12754
  const { style, dims, rnd, maxFragments } = inp;
12699
12755
  const [fmin, fmax] = inp.fragmentSize;
@@ -12715,27 +12771,14 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
12715
12771
  return out;
12716
12772
  }
12717
12773
  if (style === "splinter") {
12718
- const g = inp.grain ?? [0, 1, 2].sort((a2, b2) => dims[b2] - dims[a2])[0];
12719
- const [a, b] = [0, 1, 2].filter((i) => i !== g);
12720
- const w = Math.max(fmin * 1.5, 0.01);
12721
- const c = clamp(Math.round(dims[a] * dims[b] / (w * w)), 3, Math.max(3, Math.floor(maxFragments / 2)));
12722
- const maxLen = fmax * 4;
12723
- const cells = bsp(root, c, [a, b], rnd);
12724
- const pieces = [];
12725
- for (const cell of cells) {
12726
- let list2 = [cell];
12727
- const extra = rnd() < 0.4 ? 1 : 0;
12728
- const k = Math.max(1, Math.ceil(dims[g] / maxLen)) + extra;
12729
- list2 = bsp(cell, k, [g], rnd);
12730
- for (const p of list2) pieces.push(p);
12731
- }
12732
- for (const p of pieces.slice(0, maxFragments)) {
12733
- 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] });
12734
- }
12774
+ if (inp.cut) return severAt(inp, root, inp.cut.at, inp.cut.gap);
12775
+ for (const p of splinterBox(inp, root)) out.push(p);
12735
12776
  return out;
12736
12777
  }
12737
12778
  if (style === "tear") {
12738
12779
  const n2 = clamp(Math.round(dims[u] * dims[v] / (mean * mean)), 3, maxFragments);
12780
+ const sheet = inp.sheet ?? 0;
12781
+ if (sheet > 0 && dims[t] > sheet * 4) return tearStack(inp, root, t, u, v, n2, sheet);
12739
12782
  const th2 = Math.min(inp.thickness, dims[t]);
12740
12783
  for (const b of bsp(root, n2, [u, v], rnd)) {
12741
12784
  out.push({ center: [b.c[0], b.c[1], b.c[2]], size: [b.s[u], b.s[v], th2], q: frame(u, v, t), shape: "scrap", weight: b.s[u] * b.s[v] });
@@ -14318,6 +14361,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
14318
14361
  0.03,
14319
14362
  0.15
14320
14363
  ],
14364
+ sheet: 1e-3,
14321
14365
  damageScale: {
14322
14366
  slash: 3
14323
14367
  },
@@ -14333,7 +14377,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
14333
14377
  smoke: "grey"
14334
14378
  },
14335
14379
  visual: {
14336
- charColor: 1709072
14380
+ charColor: 1709072,
14381
+ innerColor: 15524048
14337
14382
  },
14338
14383
  tags: [
14339
14384
  "fabric",
@@ -14715,9 +14760,14 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
14715
14760
  var BREACH_AREA = 0.25;
14716
14761
  var BREACH_BLAST_REACH = 0.12;
14717
14762
  var RESIDUAL_CAPACITY = 0.15;
14763
+ var SEVER_BAND = 0.1;
14764
+ var SEVER_ASPECT = 3;
14765
+ var SEVER_BALLISTIC = 2;
14718
14766
  var SUPPORT_TOLERANCE = 0.05;
14719
14767
  var UNIT_FRAGMENT_FACTOR = 4;
14720
14768
  var BREACH_GRID_CAP = 4096;
14769
+ var CHIP_ENERGY = 200;
14770
+ var CHIP_SPLASH = 0.5;
14721
14771
  var CUT_SLICE = 1;
14722
14772
  var cutFactorOf = (shape) => clamp2(Math.max(...shapeDims(shape)) / CUT_SLICE, 1, 50);
14723
14773
  var clamp2 = (v, a, b) => Math.max(a, Math.min(b, v));
@@ -14761,6 +14811,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
14761
14811
  let windCfg = windOpts ? resolveWind(windOpts) : null;
14762
14812
  const ignitionRules = resolveIgnitionRules(options.ignition);
14763
14813
  const breachBlastFactor = options.breach?.blastFactor ?? BREACH_BLAST_FACTOR;
14814
+ const chipEnergy = options.breach?.chipEnergy ?? CHIP_ENERGY;
14764
14815
  const rng3 = createRng(options.seed ?? 1);
14765
14816
  const rnd = () => rng3.next();
14766
14817
  let time = 0;
@@ -15134,41 +15185,68 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
15134
15185
  markDamaged(p);
15135
15186
  if (p.rt.hinge >= flexure.failRotationDeg * Math.PI / 180) hingeDrop(o, p, src);
15136
15187
  };
15188
+ const freeSpan = (p, supports) => {
15189
+ if (supports.length === 0) return Infinity;
15190
+ const d = p.dims;
15191
+ const L = [0, 1, 2].sort((a, b) => d[b] - d[a])[0];
15192
+ const spans = supports.map((k) => contactBox(p, k)).map(([lo, hi]) => [lo[L], hi[L]]);
15193
+ let far = 0;
15194
+ for (let i = 0; i <= 16; i++) {
15195
+ const x = (i / 16 - 0.5) * d[L];
15196
+ let near = Infinity;
15197
+ for (const [lo, hi] of spans) near = Math.min(near, Math.max(lo - x, x - hi, 0));
15198
+ far = Math.max(far, near);
15199
+ }
15200
+ return 2 * far;
15201
+ };
15137
15202
  const solveStructure = (o) => {
15138
15203
  o.maxStress = 0;
15139
15204
  if (!o.hasStructure) return;
15140
15205
  const parts = o.parts;
15141
15206
  const n = parts.length;
15142
15207
  const dist = new Array(n).fill(-1);
15143
- const queue = [];
15144
15208
  const ok = (i) => parts[i].structural && !isLost(parts[i]);
15145
15209
  const isPartition = (i) => parts[i].spec.structure?.role === "partition";
15146
- for (let i = 0; i < n; i++) if (ok(i) && parts[i].anchored) {
15147
- dist[i] = 0;
15148
- queue.push(i);
15149
- }
15150
- for (let h = 0; h < queue.length; h++) {
15151
- const s = queue[h];
15152
- if (isPartition(s)) continue;
15153
- for (const j of o.byPart.get(parts[s].spec.id)) {
15154
- if (j.st.open || !j.bears) continue;
15155
- const r = j.ia === s ? j.ib : j.ia;
15156
- if (dist[r] >= 0 || !ok(r) || restsOn(o, j, r) !== s) continue;
15157
- dist[r] = dist[s] + 1;
15158
- queue.push(r);
15210
+ const out = /* @__PURE__ */ new Set();
15211
+ let queue = [];
15212
+ let lowerOf = /* @__PURE__ */ new Map();
15213
+ for (; ; ) {
15214
+ dist.fill(-1);
15215
+ queue = [];
15216
+ for (let i = 0; i < n; i++) if (ok(i) && !out.has(i) && parts[i].anchored) {
15217
+ dist[i] = 0;
15218
+ queue.push(i);
15219
+ }
15220
+ for (let h = 0; h < queue.length; h++) {
15221
+ const s = queue[h];
15222
+ if (isPartition(s)) continue;
15223
+ for (const j of o.byPart.get(parts[s].spec.id)) {
15224
+ if (j.st.open || !j.bears) continue;
15225
+ const r = j.ia === s ? j.ib : j.ia;
15226
+ if (dist[r] >= 0 || !ok(r) || out.has(r) || restsOn(o, j, r) !== s) continue;
15227
+ dist[r] = dist[s] + 1;
15228
+ queue.push(r);
15229
+ }
15159
15230
  }
15160
- }
15161
- const incoming = new Array(n).fill(0);
15162
- const lowerOf = /* @__PURE__ */ new Map();
15163
- for (const i of queue) {
15164
- const lower = [];
15165
- for (const j of o.byPart.get(parts[i].spec.id)) {
15166
- if (j.st.open || !j.bears) continue;
15167
- const k = restsOn(o, j, i);
15168
- if (k >= 0 && dist[k] >= 0 && dist[k] < dist[i] && !isPartition(k)) lower.push(k);
15231
+ lowerOf = /* @__PURE__ */ new Map();
15232
+ let dropped = false;
15233
+ for (const i of queue) {
15234
+ const lower = [];
15235
+ for (const j of o.byPart.get(parts[i].spec.id)) {
15236
+ if (j.st.open || !j.bears) continue;
15237
+ const k = restsOn(o, j, i);
15238
+ if (k >= 0 && dist[k] >= 0 && dist[k] < dist[i] && !isPartition(k)) lower.push(k);
15239
+ }
15240
+ lowerOf.set(i, lower);
15241
+ const max = parts[i].spec.structure?.maxSpan;
15242
+ if (max !== void 0 && !parts[i].anchored && freeSpan(parts[i], lower.map((k) => parts[k])) > max) {
15243
+ out.add(i);
15244
+ dropped = true;
15245
+ }
15169
15246
  }
15170
- lowerOf.set(i, lower);
15247
+ if (!dropped) break;
15171
15248
  }
15249
+ const incoming = new Array(n).fill(0);
15172
15250
  const bending = new Array(n).fill(0);
15173
15251
  const through = parts.map(() => []);
15174
15252
  const pass = (k, at, mass) => {
@@ -15680,6 +15758,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
15680
15758
  const r = rayPart(o, p, origin, d);
15681
15759
  if (!r || r.t0 < 0 || r.t0 > maxDist) continue;
15682
15760
  if (p.st.opening && insideOpening(o, p, vec.madd(origin, d, r.t0))) continue;
15761
+ if (p.st.chipped && insideChip(p, objLocal(o, vec.madd(origin, d, r.t0)))) continue;
15683
15762
  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 };
15684
15763
  }
15685
15764
  }
@@ -15943,6 +16022,62 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
15943
16022
  if (total < (p.spec.breach?.area ?? BREACH_AREA)) return;
15944
16023
  openBreach(o, p, [sum[0] / total, sum[1] / total, sum[2] / total], total, energy, dir, src);
15945
16024
  };
16025
+ const unitGrids = /* @__PURE__ */ new WeakMap();
16026
+ const gridOf = (p) => {
16027
+ let grid = unitGrids.get(p);
16028
+ if (!grid) unitGrids.set(p, grid = unitGrid(p.dims, p.thin, unitsOf2(p), BREACH_GRID_CAP));
16029
+ return grid;
16030
+ };
16031
+ const insideChip = (p, objectPoint) => {
16032
+ const cells = p.st.chipped;
16033
+ if (!cells?.length) return false;
16034
+ const [u, v] = planeAxes(p.thin);
16035
+ const lp = toLocal(p.pos, p.q, objectPoint);
16036
+ 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);
16037
+ };
16038
+ const chipUnits = (o, p, wp, energy, dir, radius, src) => {
16039
+ var _a, _b;
16040
+ if (!p.spec.units || p.spec.reinforcement || !p.staged || chipEnergy <= 0 || energy <= 0 || p.st.stripped || isLost(p) || (p.st.stage ?? 0) >= 2) return;
16041
+ const [u, v] = planeAxes(p.thin);
16042
+ const lp = toLocal(p.pos, p.q, objLocal(o, wp));
16043
+ const grid = gridOf(p);
16044
+ const worn = (_a = p.rt).chips ?? (_a.chips = {});
16045
+ const out = [];
16046
+ for (let i = 0; i < grid.length; i++) {
16047
+ const cell = grid[i];
16048
+ if (worn[i] === -1) continue;
16049
+ 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));
16050
+ if (gap > radius) continue;
16051
+ const share = gap === 0 ? 1 : CHIP_SPLASH * (1 - gap / radius);
16052
+ const total = (worn[i] ?? 0) + energy * share;
16053
+ const mass = p.mat.density * cell.size[0] * cell.size[1] * cell.size[2];
16054
+ if (total < chipEnergy * mass) {
16055
+ worn[i] = total;
16056
+ continue;
16057
+ }
16058
+ worn[i] = -1;
16059
+ out.push(cell);
16060
+ }
16061
+ if (out.length === 0) return;
16062
+ const { c, q } = partXf(o, p);
16063
+ const chipped = (_b = p.st).chipped ?? (_b.chipped = []);
16064
+ const holes = localHolesOf(p);
16065
+ for (const cell of out) {
16066
+ const mass = Math.min(p.st.mass, p.mat.density * cell.size[0] * cell.size[1] * cell.size[2]);
16067
+ const center = toWorld(c, q, cell.center);
16068
+ const speed = clamp2(Math.sqrt(2 * energy * 0.05 / Math.max(mass, 0.05)), 0.5, 6);
16069
+ const fragments = cellFragments(o, p, [cell], wp, dir, speed, mass, `c${chipped.length}_`);
16070
+ p.st.holes = p.st.holes.filter((_, k) => marksInBox(cell.center, cell.size, void 0, [holes[k]]).length === 0);
16071
+ chipped.push({ center: [cell.center[0], cell.center[1], cell.center[2]], size: [cell.size[0], cell.size[1], cell.size[2]] });
16072
+ p.st.mass = Math.max(0, p.st.mass - mass);
16073
+ p.rt.removed = (p.rt.removed ?? 0) + mass;
16074
+ markDamaged(p);
16075
+ setStage(o, p, 1, {}, src);
16076
+ emit({ ...base(o, src), type: "chip", part: p.spec.id, center, size: [cell.size[0], cell.size[1], cell.size[2]], rotation: q, fragments });
16077
+ debris(o, p, p.mat.breakDebris, center, vec.scale(dir ?? [0, 1, 0], -1), 6, 2, src);
16078
+ }
16079
+ for (const cell of out) addBreach(o, p, toWorld(c, q, cell.center), cell.size[u] * cell.size[v], energy, dir, src);
16080
+ };
15946
16081
  const openBreach = (o, p, center, total, energy, dir, src) => {
15947
16082
  const [u, v] = planeAxes(p.thin);
15948
16083
  const radius = clamp2(Math.sqrt(total / Math.PI), Math.sqrt((p.spec.breach?.area ?? BREACH_AREA) / Math.PI), breachRadius);
@@ -15954,7 +16089,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
15954
16089
  failPart(o, p, worldCenter, dir, energy, 12, src);
15955
16090
  return;
15956
16091
  }
15957
- const cells = cellsWithin(unitGrid(p.dims, p.thin, unitsOf2(p), BREACH_GRID_CAP), p.thin, lc, radius);
16092
+ const worn = p.rt.chips;
16093
+ const cells = cellsWithin(gridOf(p).filter((_, i) => worn?.[i] !== -1), p.thin, lc, radius);
15958
16094
  const removed = p.st.mass * cells.reduce((sum, cell) => sum + cell.size[u] * cell.size[v], 0) / (p.dims[u] * p.dims[v]);
15959
16095
  const speed = clamp2(Math.sqrt(2 * energy * 0.1 / Math.max(removed, 0.05)), 0.5, 8);
15960
16096
  const fragments = cellFragments(o, p, cells, worldCenter, dir, speed, removed, "b");
@@ -16038,6 +16174,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
16038
16174
  const grain = p.spec.grain ? AXIS[p.spec.grain] : void 0;
16039
16175
  const hitLocal = toLocal(c, q, hitW);
16040
16176
  const unitPlan = p.spec.units ? unitFragmentPlan(p, maxFragments * UNIT_FRAGMENT_FACTOR) : [];
16177
+ const axis = style === "splinter" ? severAxis(p) : -1;
16178
+ const cutAt2 = axis < 0 ? void 0 : p.rt.cutAt ?? (origin ? hitLocal[axis] : void 0);
16041
16179
  const plan = unitPlan.length > 0 ? unitPlan : planFragments({
16042
16180
  style,
16043
16181
  dims: p.dims,
@@ -16046,7 +16184,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
16046
16184
  hit: hitLocal,
16047
16185
  fragmentSize: p.mat.fragmentSize ?? [0.04, 0.2],
16048
16186
  maxFragments,
16049
- rnd
16187
+ rnd,
16188
+ sheet: p.mat.sheet,
16189
+ ...cutAt2 !== void 0 ? { cut: { at: cutAt2, gap: SEVER_BAND } } : {}
16050
16190
  });
16051
16191
  const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
16052
16192
  const mass = p.st.mass;
@@ -16070,7 +16210,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
16070
16210
  velocity: [dv[0] * k, dv[1] * k + 0.5 * rnd(), dv[2] * k],
16071
16211
  angularVelocity: [(rnd() - 0.5) * 12, (rnd() - 0.5) * 12, (rnd() - 0.5) * 12],
16072
16212
  mass: mass * f.weight / totW,
16073
- ...marks.length > 0 ? { marks } : {}
16213
+ ...marks.length > 0 ? { marks } : {},
16214
+ ...f.inner ? { inner: true } : {}
16074
16215
  };
16075
16216
  });
16076
16217
  const reinforcement = p.spec.reinforcement;
@@ -16104,10 +16245,33 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
16104
16245
  emit({ ...base(o, src), type: "primed", part: p.spec.id, point: partXf(o, p).c, trigger, delay });
16105
16246
  };
16106
16247
  const CHAIN_TRIGGER_OF = { ballistic: "shot", explosive: "explosion" };
16248
+ const severAxis = (p) => {
16249
+ if (p.mat.fracture !== "splinter" || p.spec.shape.kind !== "box") return -1;
16250
+ const d = p.dims;
16251
+ const order = [0, 1, 2].sort((a, b) => d[b] - d[a]);
16252
+ return d[order[0]] >= SEVER_ASPECT * d[order[1]] && d[order[0]] >= 3 * SEVER_BAND ? order[0] : -1;
16253
+ };
16254
+ const bandOf = (o, p, axis, origin) => {
16255
+ const { c, q } = partXf(o, p);
16256
+ const along = toLocal(c, q, origin)[axis];
16257
+ const L = p.dims[axis];
16258
+ return clamp2(Math.floor((along + L / 2) / SEVER_BAND), 0, Math.ceil(L / SEVER_BAND) - 1);
16259
+ };
16107
16260
  const applyDamage = (o, p, amount, type, origin, dir, fragEnergy, cap, src) => {
16261
+ var _a;
16108
16262
  if (isLost(p) || amount <= 0) return 0;
16109
16263
  const prev = p.st.hp;
16110
16264
  p.st.hp -= amount;
16265
+ const axis = origin && type !== "fire" && type !== "heat" ? severAxis(p) : -1;
16266
+ if (axis >= 0 && p.st.hp > 0) {
16267
+ const band = bandOf(o, p, axis, origin);
16268
+ const cuts = (_a = p.rt).cuts ?? (_a.cuts = {});
16269
+ cuts[band] = (cuts[band] ?? 0) + amount * (type === "ballistic" ? SEVER_BALLISTIC : 1);
16270
+ if (cuts[band] >= p.st.maxHp * SEVER_BAND / p.dims[axis]) {
16271
+ p.rt.cutAt = -p.dims[axis] / 2 + (band + 0.5) * SEVER_BAND;
16272
+ p.st.hp = 0;
16273
+ }
16274
+ }
16111
16275
  markDamaged(p);
16112
16276
  const trigger = CHAIN_TRIGGER_OF[type];
16113
16277
  const chained = trigger !== void 0 && chainDelayOf(p.spec, trigger) !== void 0;
@@ -16146,7 +16310,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
16146
16310
  const ballistic = (o, p, entry2, n, d, E, caliber, pathLen, src, round3 = {}) => {
16147
16311
  const mat = p.mat;
16148
16312
  const piercing = round3.piercing ?? 1;
16149
- if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry2))) {
16313
+ if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry2)) || insideChip(p, objLocal(o, entry2))) {
16150
16314
  return { penetrated: true, exitEnergy: E, exitPoint: vec.madd(entry2, d, Math.min(pathLen, p.thickness)), damage: 0 };
16151
16315
  }
16152
16316
  const sin = Math.min(1, Math.abs(vec.dot(d, n)));
@@ -16205,6 +16369,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
16205
16369
  debris(o, p, mat.hitDebris, entry2, n, clamp2(Math.round(E / 60) + 2, 2, 14), clamp2(Math.sqrt(E) / 6, 1, 8), src);
16206
16370
  wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true);
16207
16371
  const dmg = applyDamage(o, p, E * DMG_PER_J * scale, "ballistic", entry2, d, E * 0.1, 12, src);
16372
+ chipUnits(o, p, entry2, penetrated ? Math.min(E, Math.max(thr, E * 0.5)) : E, d, Math.max(0.05, caliber / 1e3 * 8), src);
16208
16373
  return { penetrated, exitEnergy: penetrated ? penMm !== void 0 ? E * (1 - thr / Ep) : E - thr / (hard ? piercing : 1) : 0, exitPoint, damage: dmg };
16209
16374
  };
16210
16375
  const melee = (o, p, kind, E, point, n, d, contactR, src) => {
@@ -16232,6 +16397,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
16232
16397
  const scale = mat.damageScale?.[kind] ?? 1;
16233
16398
  const done = applyDamage(o, p, Ec * DMG_PER_J * scale * (kind === "slash" ? p.cutFactor : 1), kind, point, d, E * 0.1, 12, src);
16234
16399
  addBreach(o, p, point, breachAreaOf(p, done, false), E, d, src);
16400
+ if (kind === "blunt") chipUnits(o, p, point, E, d, Math.max(contactR, 0.03), src);
16235
16401
  return done;
16236
16402
  };
16237
16403
  const blastPart = (o, p, point, energy, radius, src, res, reachAt) => {
@@ -26250,12 +26416,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
26250
26416
  A.invalidate = () => {
26251
26417
  for (let i = 0; i < NS; i++) PL[i].shadow.needsUpdate = true;
26252
26418
  };
26253
- A.update = function(houses, focus, op) {
26419
+ A.update = function(houses2, focus, op) {
26254
26420
  op = op || {};
26255
- houses = (Array.isArray(houses) ? houses : [houses]).filter(Boolean);
26421
+ houses2 = (Array.isArray(houses2) ? houses2 : [houses2]).filter(Boolean);
26256
26422
  _f.set(focus.x, focus.y, focus.z);
26257
26423
  const rooms = [], exterior = [];
26258
- for (const house of houses) {
26424
+ for (const house of houses2) {
26259
26425
  if (house.group && !house.group.visible) continue;
26260
26426
  house.house.updateMatrixWorld(true);
26261
26427
  const M = house.house.matrixWorld, by = /* @__PURE__ */ new Map();
@@ -26634,7 +26800,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
26634
26800
  }
26635
26801
  return null;
26636
26802
  };
26637
- function courses(part, units, opening, paint) {
26803
+ const local = (p) => {
26804
+ house.updateMatrixWorld(true);
26805
+ const v = house.worldToLocal(tmp.p.set(p[0], p[1], p[2]));
26806
+ return [v.x, v.y, v.z];
26807
+ };
26808
+ function courses(part, units, opening, chips, paint) {
26638
26809
  const [u0, v0, u1, v1] = part.aabb;
26639
26810
  const alongU = u1 - u0 >= v1 - v0;
26640
26811
  const len = alongU ? u1 - u0 : v1 - v0;
@@ -26658,23 +26829,39 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
26658
26829
  const x = alongU ? u0 + mid : (u0 + u1) / 2;
26659
26830
  const z = alongU ? (v0 + v1) / 2 : v0 + mid;
26660
26831
  const y = part.y0 + (r + 0.5) * rowH;
26661
- const d = Math.hypot(x - opening.center[0], y - opening.center[1], z - opening.center[2]);
26662
- if (d < opening.radius) continue;
26663
- list2.push({ x, y, z, sx: alongU ? b - a : thick, sy: rowH, sz: alongU ? thick : b - a, colour: d < opening.radius + 0.25 || paint === null ? edge : paint });
26832
+ const d = opening ? Math.hypot(x - opening.center[0], y - opening.center[1], z - opening.center[2]) : Infinity;
26833
+ if (opening && d < opening.radius) continue;
26834
+ 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);
26835
+ if (chips.some(gone)) continue;
26836
+ const near = opening && d < opening.radius + 0.25 || chips.some((c) => Math.hypot(c[0] - x, c[1] - y, c[2] - z) < 0.15);
26837
+ list2.push({ x, y, z, sx: alongU ? b - a : thick, sy: rowH, sz: alongU ? thick : b - a, colour: near || paint === null ? edge : paint });
26664
26838
  }
26665
26839
  }
26666
26840
  return list2;
26667
26841
  }
26668
- function open(event) {
26669
- if (lod !== 0 || views.has(event.part) || !event.opening) return false;
26670
- const part = structure.byId.get(event.part);
26671
- const spec = opts.spec()?.parts?.find((q) => q.id === event.part);
26842
+ function draw2(partId, change) {
26843
+ if (lod !== 0) return false;
26844
+ const part = structure.byId.get(partId);
26845
+ const spec = opts.spec()?.parts?.find((q) => q.id === partId);
26672
26846
  if (!part || !spec || !spec.units) return false;
26673
- const items = structure.meshesOf(event.part).filter((it) => it.mesh);
26674
- if (!items.length) return false;
26675
- const list2 = courses(part, spec.units, event.opening, wallColour(items));
26847
+ let view = views.get(partId);
26848
+ if (!view) {
26849
+ const items = structure.meshesOf(partId).filter((it) => it.mesh);
26850
+ if (!items.length) return false;
26851
+ view = { mesh: null, saved: null, items, paint: wallColour(items), units: 0, opening: null, chips: [] };
26852
+ }
26853
+ if (change.opening) {
26854
+ if (view.opening) return false;
26855
+ view.opening = { center: local(change.opening.center), radius: change.opening.radius };
26856
+ }
26857
+ if (change.chip) view.chips.push(local(change.chip));
26858
+ const list2 = courses(part, spec.units, view.opening, view.chips, view.paint);
26859
+ if (view.mesh) {
26860
+ view.mesh.removeFromParent();
26861
+ view.mesh.dispose();
26862
+ }
26676
26863
  const mesh2 = new THREE.InstancedMesh(unitGeometry, unitMaterial, Math.max(1, list2.length));
26677
- mesh2.name = "breach:" + event.part;
26864
+ mesh2.name = "breach:" + partId;
26678
26865
  list2.forEach((u, i) => {
26679
26866
  tmp.m.compose(tmp.p.set(u.x, u.y, u.z), tmp.q.identity(), tmp.s.set(u.sx, u.sy, u.sz));
26680
26867
  mesh2.setMatrixAt(i, tmp.m);
@@ -26686,8 +26873,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
26686
26873
  mesh2.castShadow = true;
26687
26874
  mesh2.receiveShadow = true;
26688
26875
  house.add(mesh2);
26689
- const saved = items.map((it) => ({ it, v0: it.v0, data: collapseRange(it.mesh, it.v0, it.v1) }));
26690
- views.set(event.part, { mesh: mesh2, saved, units: list2.length, opening: event.opening });
26876
+ if (!view.saved) view.saved = view.items.map((it) => ({ it, v0: it.v0, data: collapseRange(it.mesh, it.v0, it.v1) }));
26877
+ view.mesh = mesh2;
26878
+ view.units = list2.length;
26879
+ views.set(partId, view);
26691
26880
  invalidate();
26692
26881
  invalidateShadow();
26693
26882
  return true;
@@ -26701,7 +26890,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
26701
26890
  return {
26702
26891
  /** Handles a `stage` event of the house's object. Returns true when the wall was redrawn as courses. */
26703
26892
  stage(event) {
26704
- return event.stage === 2 ? open(event) : false;
26893
+ return event.stage === 2 && !!event.opening ? draw2(event.part, { opening: event.opening }) : false;
26894
+ },
26895
+ /** Handles a `chip` event: the unit that came out leaves a gap in the courses. Returns true when the wall was redrawn. */
26896
+ chip(event) {
26897
+ return draw2(event.part, { chip: event.center });
26705
26898
  },
26706
26899
  /** The part broke or fell: the courses go (the plain range stays collapsed, the renderer hides the part). */
26707
26900
  drop(partId) {
@@ -26931,6 +27124,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
26931
27124
  case "stage":
26932
27125
  breach.stage(e);
26933
27126
  break;
27127
+ case "chip":
27128
+ breach.chip(e);
27129
+ break;
26934
27130
  case "collapse":
26935
27131
  for (const id of e.parts) {
26936
27132
  breach.drop(id);
@@ -26985,6 +27181,32 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
26985
27181
  if (D.active && D.world.has(D.id)) D.world.remove(D.id);
26986
27182
  D.active = false;
26987
27183
  };
27184
+ D.follow = (world, id, spec) => {
27185
+ D.spec = spec;
27186
+ const offFollow = world.on("*", (e) => {
27187
+ if (e.object !== id) return;
27188
+ switch (e.type) {
27189
+ case "stage":
27190
+ breach.stage(e);
27191
+ break;
27192
+ case "chip":
27193
+ breach.chip(e);
27194
+ break;
27195
+ case "fracture":
27196
+ breach.drop(e.part);
27197
+ break;
27198
+ case "collapse":
27199
+ for (const p of e.parts) breach.drop(p);
27200
+ break;
27201
+ default:
27202
+ break;
27203
+ }
27204
+ });
27205
+ return { restore: () => breach.restore(), dispose: () => {
27206
+ offFollow();
27207
+ breach.restore();
27208
+ } };
27209
+ };
26988
27210
  D.piece = (id) => D.active ? D.world.part(D.id, id) : null;
26989
27211
  D.state = () => D.active ? D.world.state(D.id) : null;
26990
27212
  D.reset = () => {
@@ -27707,6 +27929,20 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
27707
27929
  return buildApi(K, shared);
27708
27930
  }
27709
27931
  var WOOD_THIN = 0.035;
27932
+ var SHELF_SPAN = 1.5;
27933
+ var SHELVING = /shelf|bookcase|rack/;
27934
+ function carcass(parts, drawn) {
27935
+ const all = [...drawn.values()];
27936
+ const width = Math.max(...all.map((p) => p.max[0])) - Math.min(...all.map((p) => p.min[0]));
27937
+ for (const part of parts) {
27938
+ if (LOOSE.has(part.material) || part.shape.kind !== "box" || part.rotation) continue;
27939
+ const [x, y, z] = part.shape.size;
27940
+ if (y >= x && y >= z) {
27941
+ if (z < x && x >= 0.6 * width) continue;
27942
+ part.structure = { role: "column" };
27943
+ } else if (y <= x && y <= z) part.structure = { role: "beam", maxSpan: SHELF_SPAN * Math.max(x, z) };
27944
+ }
27945
+ }
27710
27946
  var FLOOR_TOUCH = 5e-3;
27711
27947
  var LOOSE = /* @__PURE__ */ new Set(["paper"]);
27712
27948
  var BASE_MATERIAL = {
@@ -27822,6 +28058,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
27822
28058
  if (area3 < 0.6 * footprint || p.max[1] < 0.2) continue;
27823
28059
  if (!top || p.max[1] > top.y + 1e-6) top = { part: p.id, y: p.max[1] };
27824
28060
  }
28061
+ if (opts.carcass) carcass(parts, byId);
27825
28062
  const spec = { name, parts, joints, tags: ["furniture"] };
27826
28063
  const colors = new Map(drawn.map((p) => [p.id, p.color]));
27827
28064
  const build = (T3) => {
@@ -27865,7 +28102,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
27865
28102
  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`);
27866
28103
  const o = opts.options ?? {};
27867
28104
  const seed = Math.round(opts.seed ?? 1);
27868
- const plain2 = /shelf|bookcase|rack/.test(type) ? "paper" : "plastic";
28105
+ const plain2 = SHELVING.test(type) ? "paper" : "plastic";
27869
28106
  const root = { x: seed, z: 0, ry: 0, w, d, h, y: 0, base: 0, palette: o.palette, seed: `furniture_${type}_${seed}` };
27870
28107
  const { K, shared } = renderer.internals;
27871
28108
  const before = K.PARTS.length;
@@ -27884,7 +28121,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
27884
28121
  shared.ctx = saved.ctx;
27885
28122
  }
27886
28123
  },
27887
- { materials: { plain: plain2, ...o.materials }, chairs: o.chairs }
28124
+ { materials: { plain: plain2, ...o.materials }, chairs: o.chairs, carcass: SHELVING.test(type) }
27888
28125
  );
27889
28126
  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]);
27890
28127
  K.PARTS.length = before;