@adatechnology/game-floorplan-structure 1.2.2 → 1.2.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.
@@ -2755,6 +2755,45 @@ var GameFloorplanStructure = (() => {
2755
2755
  return angle === 0 ? q : quat.mul(q, quat.fromAxisAngle([0, 0, 1], angle));
2756
2756
  };
2757
2757
  var clamp3 = (v, a, b) => Math.max(a, Math.min(b, v));
2758
+ var splinterBox = (inp, box2, limit = inp.maxFragments) => {
2759
+ const { rnd } = inp;
2760
+ const dims = box2.s;
2761
+ const [fmin, fmax] = inp.fragmentSize;
2762
+ const g = inp.grain ?? [0, 1, 2].sort((a2, b2) => dims[b2] - dims[a2])[0];
2763
+ const [a, b] = [0, 1, 2].filter((i) => i !== g);
2764
+ const w = Math.max(fmin * 1.5, 0.01);
2765
+ const c = clamp3(Math.round(dims[a] * dims[b] / (w * w)), 3, Math.max(3, Math.floor(limit / 2)));
2766
+ const maxLen = fmax * 4;
2767
+ const cells = bsp(box2, c, [a, b], rnd);
2768
+ const pieces = [];
2769
+ for (const cell of cells) {
2770
+ const extra = rnd() < 0.4 ? 1 : 0;
2771
+ const k = Math.max(1, Math.ceil(dims[g] / maxLen)) + extra;
2772
+ for (const p of bsp(cell, k, [g], rnd)) pieces.push(p);
2773
+ }
2774
+ 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] }));
2775
+ };
2776
+ var severAt = (inp, root, at2, gap) => {
2777
+ const dims = root.s;
2778
+ const L = [0, 1, 2].sort((x, y) => dims[y] - dims[x])[0];
2779
+ const half = dims[L] / 2;
2780
+ const lo = clamp3(at2 - gap / 2, -half, half), hi = clamp3(at2 + gap / 2, -half, half);
2781
+ const out = [];
2782
+ const stub = (from, to) => {
2783
+ if (to - from < 0.02) return;
2784
+ const c = [root.c[0], root.c[1], root.c[2]], sz = [dims[0], dims[1], dims[2]];
2785
+ c[L] = (from + to) / 2;
2786
+ sz[L] = to - from;
2787
+ out.push({ center: c, size: sz, q: quat.identity(), shape: "slab", weight: sz[0] * sz[1] * sz[2] });
2788
+ };
2789
+ stub(-half, lo);
2790
+ stub(hi, half);
2791
+ const band = { c: [root.c[0], root.c[1], root.c[2]], s: [dims[0], dims[1], dims[2]] };
2792
+ band.c[L] = (lo + hi) / 2;
2793
+ band.s[L] = Math.max(hi - lo, 0.01);
2794
+ for (const p of splinterBox(inp, band, Math.max(3, inp.maxFragments - out.length))) out.push(p);
2795
+ return out;
2796
+ };
2758
2797
  var tearStack = (inp, root, t, u, v, n, sheet) => {
2759
2798
  const { dims, rnd, maxFragments } = inp;
2760
2799
  const layers = clamp3(Math.ceil(dims[t] / STACK_LAYER), 2, 4);
@@ -2793,23 +2832,8 @@ var GameFloorplanStructure = (() => {
2793
2832
  return out;
2794
2833
  }
2795
2834
  if (style === "splinter") {
2796
- const g = inp.grain ?? [0, 1, 2].sort((a2, b2) => dims[b2] - dims[a2])[0];
2797
- const [a, b] = [0, 1, 2].filter((i) => i !== g);
2798
- const w = Math.max(fmin * 1.5, 0.01);
2799
- const c = clamp3(Math.round(dims[a] * dims[b] / (w * w)), 3, Math.max(3, Math.floor(maxFragments / 2)));
2800
- const maxLen = fmax * 4;
2801
- const cells = bsp(root, c, [a, b], rnd);
2802
- const pieces = [];
2803
- for (const cell of cells) {
2804
- let list = [cell];
2805
- const extra = rnd() < 0.4 ? 1 : 0;
2806
- const k = Math.max(1, Math.ceil(dims[g] / maxLen)) + extra;
2807
- list = bsp(cell, k, [g], rnd);
2808
- for (const p of list) pieces.push(p);
2809
- }
2810
- for (const p of pieces.slice(0, maxFragments)) {
2811
- 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] });
2812
- }
2835
+ if (inp.cut) return severAt(inp, root, inp.cut.at, inp.cut.gap);
2836
+ for (const p of splinterBox(inp, root)) out.push(p);
2813
2837
  return out;
2814
2838
  }
2815
2839
  if (style === "tear") {
@@ -4797,9 +4821,14 @@ var GameFloorplanStructure = (() => {
4797
4821
  var BREACH_AREA = 0.25;
4798
4822
  var BREACH_BLAST_REACH = 0.12;
4799
4823
  var RESIDUAL_CAPACITY = 0.15;
4824
+ var SEVER_BAND = 0.1;
4825
+ var SEVER_ASPECT = 3;
4826
+ var SEVER_BALLISTIC = 2;
4800
4827
  var SUPPORT_TOLERANCE = 0.05;
4801
4828
  var UNIT_FRAGMENT_FACTOR = 4;
4802
4829
  var BREACH_GRID_CAP = 4096;
4830
+ var CHIP_ENERGY = 200;
4831
+ var CHIP_SPLASH = 0.5;
4803
4832
  var CUT_SLICE = 1;
4804
4833
  var cutFactorOf = (shape) => clamp22(Math.max(...shapeDims(shape)) / CUT_SLICE, 1, 50);
4805
4834
  var clamp22 = (v, a, b) => Math.max(a, Math.min(b, v));
@@ -4843,6 +4872,7 @@ var GameFloorplanStructure = (() => {
4843
4872
  let windCfg = windOpts ? resolveWind(windOpts) : null;
4844
4873
  const ignitionRules = resolveIgnitionRules(options.ignition);
4845
4874
  const breachBlastFactor = options.breach?.blastFactor ?? BREACH_BLAST_FACTOR;
4875
+ const chipEnergy = options.breach?.chipEnergy ?? CHIP_ENERGY;
4846
4876
  const rng = createRng(options.seed ?? 1);
4847
4877
  const rnd = () => rng.next();
4848
4878
  let time = 0;
@@ -5216,41 +5246,68 @@ var GameFloorplanStructure = (() => {
5216
5246
  markDamaged(p);
5217
5247
  if (p.rt.hinge >= flexure.failRotationDeg * Math.PI / 180) hingeDrop(o, p, src);
5218
5248
  };
5249
+ const freeSpan = (p, supports) => {
5250
+ if (supports.length === 0) return Infinity;
5251
+ const d = p.dims;
5252
+ const L = [0, 1, 2].sort((a, b) => d[b] - d[a])[0];
5253
+ const spans = supports.map((k) => contactBox(p, k)).map(([lo, hi]) => [lo[L], hi[L]]);
5254
+ let far = 0;
5255
+ for (let i = 0; i <= 16; i++) {
5256
+ const x = (i / 16 - 0.5) * d[L];
5257
+ let near = Infinity;
5258
+ for (const [lo, hi] of spans) near = Math.min(near, Math.max(lo - x, x - hi, 0));
5259
+ far = Math.max(far, near);
5260
+ }
5261
+ return 2 * far;
5262
+ };
5219
5263
  const solveStructure = (o) => {
5220
5264
  o.maxStress = 0;
5221
5265
  if (!o.hasStructure) return;
5222
5266
  const parts = o.parts;
5223
5267
  const n = parts.length;
5224
5268
  const dist = new Array(n).fill(-1);
5225
- const queue = [];
5226
5269
  const ok = (i) => parts[i].structural && !isLost(parts[i]);
5227
5270
  const isPartition = (i) => parts[i].spec.structure?.role === "partition";
5228
- for (let i = 0; i < n; i++) if (ok(i) && parts[i].anchored) {
5229
- dist[i] = 0;
5230
- queue.push(i);
5231
- }
5232
- for (let h = 0; h < queue.length; h++) {
5233
- const s = queue[h];
5234
- if (isPartition(s)) continue;
5235
- for (const j of o.byPart.get(parts[s].spec.id)) {
5236
- if (j.st.open || !j.bears) continue;
5237
- const r = j.ia === s ? j.ib : j.ia;
5238
- if (dist[r] >= 0 || !ok(r) || restsOn(o, j, r) !== s) continue;
5239
- dist[r] = dist[s] + 1;
5240
- queue.push(r);
5271
+ const out = /* @__PURE__ */ new Set();
5272
+ let queue = [];
5273
+ let lowerOf = /* @__PURE__ */ new Map();
5274
+ for (; ; ) {
5275
+ dist.fill(-1);
5276
+ queue = [];
5277
+ for (let i = 0; i < n; i++) if (ok(i) && !out.has(i) && parts[i].anchored) {
5278
+ dist[i] = 0;
5279
+ queue.push(i);
5241
5280
  }
5242
- }
5243
- const incoming = new Array(n).fill(0);
5244
- const lowerOf = /* @__PURE__ */ new Map();
5245
- for (const i of queue) {
5246
- const lower = [];
5247
- for (const j of o.byPart.get(parts[i].spec.id)) {
5248
- if (j.st.open || !j.bears) continue;
5249
- const k = restsOn(o, j, i);
5250
- if (k >= 0 && dist[k] >= 0 && dist[k] < dist[i] && !isPartition(k)) lower.push(k);
5281
+ for (let h = 0; h < queue.length; h++) {
5282
+ const s = queue[h];
5283
+ if (isPartition(s)) continue;
5284
+ for (const j of o.byPart.get(parts[s].spec.id)) {
5285
+ if (j.st.open || !j.bears) continue;
5286
+ const r = j.ia === s ? j.ib : j.ia;
5287
+ if (dist[r] >= 0 || !ok(r) || out.has(r) || restsOn(o, j, r) !== s) continue;
5288
+ dist[r] = dist[s] + 1;
5289
+ queue.push(r);
5290
+ }
5251
5291
  }
5252
- lowerOf.set(i, lower);
5292
+ lowerOf = /* @__PURE__ */ new Map();
5293
+ let dropped = false;
5294
+ for (const i of queue) {
5295
+ const lower = [];
5296
+ for (const j of o.byPart.get(parts[i].spec.id)) {
5297
+ if (j.st.open || !j.bears) continue;
5298
+ const k = restsOn(o, j, i);
5299
+ if (k >= 0 && dist[k] >= 0 && dist[k] < dist[i] && !isPartition(k)) lower.push(k);
5300
+ }
5301
+ lowerOf.set(i, lower);
5302
+ const max = parts[i].spec.structure?.maxSpan;
5303
+ if (max !== void 0 && !parts[i].anchored && freeSpan(parts[i], lower.map((k) => parts[k])) > max) {
5304
+ out.add(i);
5305
+ dropped = true;
5306
+ }
5307
+ }
5308
+ if (!dropped) break;
5253
5309
  }
5310
+ const incoming = new Array(n).fill(0);
5254
5311
  const bending = new Array(n).fill(0);
5255
5312
  const through = parts.map(() => []);
5256
5313
  const pass = (k, at2, mass) => {
@@ -5762,6 +5819,7 @@ var GameFloorplanStructure = (() => {
5762
5819
  const r = rayPart(o, p, origin, d);
5763
5820
  if (!r || r.t0 < 0 || r.t0 > maxDist) continue;
5764
5821
  if (p.st.opening && insideOpening(o, p, vec.madd(origin, d, r.t0))) continue;
5822
+ if (p.st.chipped && insideChip(p, objLocal(o, vec.madd(origin, d, r.t0)))) continue;
5765
5823
  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 };
5766
5824
  }
5767
5825
  }
@@ -6025,6 +6083,62 @@ var GameFloorplanStructure = (() => {
6025
6083
  if (total < (p.spec.breach?.area ?? BREACH_AREA)) return;
6026
6084
  openBreach(o, p, [sum[0] / total, sum[1] / total, sum[2] / total], total, energy, dir, src);
6027
6085
  };
6086
+ const unitGrids = /* @__PURE__ */ new WeakMap();
6087
+ const gridOf = (p) => {
6088
+ let grid = unitGrids.get(p);
6089
+ if (!grid) unitGrids.set(p, grid = unitGrid(p.dims, p.thin, unitsOf2(p), BREACH_GRID_CAP));
6090
+ return grid;
6091
+ };
6092
+ const insideChip = (p, objectPoint) => {
6093
+ const cells = p.st.chipped;
6094
+ if (!cells?.length) return false;
6095
+ const [u, v] = planeAxes(p.thin);
6096
+ const lp = toLocal(p.pos, p.q, objectPoint);
6097
+ 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);
6098
+ };
6099
+ const chipUnits = (o, p, wp, energy, dir, radius, src) => {
6100
+ var _a, _b;
6101
+ if (!p.spec.units || p.spec.reinforcement || !p.staged || chipEnergy <= 0 || energy <= 0 || p.st.stripped || isLost(p) || (p.st.stage ?? 0) >= 2) return;
6102
+ const [u, v] = planeAxes(p.thin);
6103
+ const lp = toLocal(p.pos, p.q, objLocal(o, wp));
6104
+ const grid = gridOf(p);
6105
+ const worn = (_a = p.rt).chips ?? (_a.chips = {});
6106
+ const out = [];
6107
+ for (let i = 0; i < grid.length; i++) {
6108
+ const cell = grid[i];
6109
+ if (worn[i] === -1) continue;
6110
+ 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));
6111
+ if (gap > radius) continue;
6112
+ const share = gap === 0 ? 1 : CHIP_SPLASH * (1 - gap / radius);
6113
+ const total = (worn[i] ?? 0) + energy * share;
6114
+ const mass = p.mat.density * cell.size[0] * cell.size[1] * cell.size[2];
6115
+ if (total < chipEnergy * mass) {
6116
+ worn[i] = total;
6117
+ continue;
6118
+ }
6119
+ worn[i] = -1;
6120
+ out.push(cell);
6121
+ }
6122
+ if (out.length === 0) return;
6123
+ const { c, q } = partXf(o, p);
6124
+ const chipped = (_b = p.st).chipped ?? (_b.chipped = []);
6125
+ const holes = localHolesOf(p);
6126
+ for (const cell of out) {
6127
+ const mass = Math.min(p.st.mass, p.mat.density * cell.size[0] * cell.size[1] * cell.size[2]);
6128
+ const center = toWorld(c, q, cell.center);
6129
+ const speed = clamp22(Math.sqrt(2 * energy * 0.05 / Math.max(mass, 0.05)), 0.5, 6);
6130
+ const fragments = cellFragments(o, p, [cell], wp, dir, speed, mass, `c${chipped.length}_`);
6131
+ p.st.holes = p.st.holes.filter((_, k) => marksInBox(cell.center, cell.size, void 0, [holes[k]]).length === 0);
6132
+ chipped.push({ center: [cell.center[0], cell.center[1], cell.center[2]], size: [cell.size[0], cell.size[1], cell.size[2]] });
6133
+ p.st.mass = Math.max(0, p.st.mass - mass);
6134
+ p.rt.removed = (p.rt.removed ?? 0) + mass;
6135
+ markDamaged(p);
6136
+ setStage(o, p, 1, {}, src);
6137
+ emit({ ...base(o, src), type: "chip", part: p.spec.id, center, size: [cell.size[0], cell.size[1], cell.size[2]], rotation: q, fragments });
6138
+ debris(o, p, p.mat.breakDebris, center, vec.scale(dir ?? [0, 1, 0], -1), 6, 2, src);
6139
+ }
6140
+ for (const cell of out) addBreach(o, p, toWorld(c, q, cell.center), cell.size[u] * cell.size[v], energy, dir, src);
6141
+ };
6028
6142
  const openBreach = (o, p, center, total, energy, dir, src) => {
6029
6143
  const [u, v] = planeAxes(p.thin);
6030
6144
  const radius = clamp22(Math.sqrt(total / Math.PI), Math.sqrt((p.spec.breach?.area ?? BREACH_AREA) / Math.PI), breachRadius);
@@ -6036,7 +6150,8 @@ var GameFloorplanStructure = (() => {
6036
6150
  failPart(o, p, worldCenter, dir, energy, 12, src);
6037
6151
  return;
6038
6152
  }
6039
- const cells = cellsWithin(unitGrid(p.dims, p.thin, unitsOf2(p), BREACH_GRID_CAP), p.thin, lc, radius);
6153
+ const worn = p.rt.chips;
6154
+ const cells = cellsWithin(gridOf(p).filter((_, i) => worn?.[i] !== -1), p.thin, lc, radius);
6040
6155
  const removed = p.st.mass * cells.reduce((sum, cell) => sum + cell.size[u] * cell.size[v], 0) / (p.dims[u] * p.dims[v]);
6041
6156
  const speed = clamp22(Math.sqrt(2 * energy * 0.1 / Math.max(removed, 0.05)), 0.5, 8);
6042
6157
  const fragments = cellFragments(o, p, cells, worldCenter, dir, speed, removed, "b");
@@ -6120,6 +6235,8 @@ var GameFloorplanStructure = (() => {
6120
6235
  const grain = p.spec.grain ? AXIS[p.spec.grain] : void 0;
6121
6236
  const hitLocal = toLocal(c, q, hitW);
6122
6237
  const unitPlan = p.spec.units ? unitFragmentPlan(p, maxFragments * UNIT_FRAGMENT_FACTOR) : [];
6238
+ const axis = style === "splinter" ? severAxis(p) : -1;
6239
+ const cutAt2 = axis < 0 ? void 0 : p.rt.cutAt ?? (origin ? hitLocal[axis] : void 0);
6123
6240
  const plan = unitPlan.length > 0 ? unitPlan : planFragments({
6124
6241
  style,
6125
6242
  dims: p.dims,
@@ -6129,7 +6246,8 @@ var GameFloorplanStructure = (() => {
6129
6246
  fragmentSize: p.mat.fragmentSize ?? [0.04, 0.2],
6130
6247
  maxFragments,
6131
6248
  rnd,
6132
- sheet: p.mat.sheet
6249
+ sheet: p.mat.sheet,
6250
+ ...cutAt2 !== void 0 ? { cut: { at: cutAt2, gap: SEVER_BAND } } : {}
6133
6251
  });
6134
6252
  const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
6135
6253
  const mass = p.st.mass;
@@ -6188,10 +6306,33 @@ var GameFloorplanStructure = (() => {
6188
6306
  emit({ ...base(o, src), type: "primed", part: p.spec.id, point: partXf(o, p).c, trigger, delay });
6189
6307
  };
6190
6308
  const CHAIN_TRIGGER_OF = { ballistic: "shot", explosive: "explosion" };
6309
+ const severAxis = (p) => {
6310
+ if (p.mat.fracture !== "splinter" || p.spec.shape.kind !== "box") return -1;
6311
+ const d = p.dims;
6312
+ const order = [0, 1, 2].sort((a, b) => d[b] - d[a]);
6313
+ return d[order[0]] >= SEVER_ASPECT * d[order[1]] && d[order[0]] >= 3 * SEVER_BAND ? order[0] : -1;
6314
+ };
6315
+ const bandOf = (o, p, axis, origin) => {
6316
+ const { c, q } = partXf(o, p);
6317
+ const along = toLocal(c, q, origin)[axis];
6318
+ const L = p.dims[axis];
6319
+ return clamp22(Math.floor((along + L / 2) / SEVER_BAND), 0, Math.ceil(L / SEVER_BAND) - 1);
6320
+ };
6191
6321
  const applyDamage = (o, p, amount, type, origin, dir, fragEnergy, cap, src) => {
6322
+ var _a;
6192
6323
  if (isLost(p) || amount <= 0) return 0;
6193
6324
  const prev = p.st.hp;
6194
6325
  p.st.hp -= amount;
6326
+ const axis = origin && type !== "fire" && type !== "heat" ? severAxis(p) : -1;
6327
+ if (axis >= 0 && p.st.hp > 0) {
6328
+ const band = bandOf(o, p, axis, origin);
6329
+ const cuts = (_a = p.rt).cuts ?? (_a.cuts = {});
6330
+ cuts[band] = (cuts[band] ?? 0) + amount * (type === "ballistic" ? SEVER_BALLISTIC : 1);
6331
+ if (cuts[band] >= p.st.maxHp * SEVER_BAND / p.dims[axis]) {
6332
+ p.rt.cutAt = -p.dims[axis] / 2 + (band + 0.5) * SEVER_BAND;
6333
+ p.st.hp = 0;
6334
+ }
6335
+ }
6195
6336
  markDamaged(p);
6196
6337
  const trigger = CHAIN_TRIGGER_OF[type];
6197
6338
  const chained = trigger !== void 0 && chainDelayOf(p.spec, trigger) !== void 0;
@@ -6230,7 +6371,7 @@ var GameFloorplanStructure = (() => {
6230
6371
  const ballistic = (o, p, entry2, n, d, E, caliber, pathLen, src, round3 = {}) => {
6231
6372
  const mat = p.mat;
6232
6373
  const piercing = round3.piercing ?? 1;
6233
- if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry2))) {
6374
+ if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry2)) || insideChip(p, objLocal(o, entry2))) {
6234
6375
  return { penetrated: true, exitEnergy: E, exitPoint: vec.madd(entry2, d, Math.min(pathLen, p.thickness)), damage: 0 };
6235
6376
  }
6236
6377
  const sin = Math.min(1, Math.abs(vec.dot(d, n)));
@@ -6289,6 +6430,7 @@ var GameFloorplanStructure = (() => {
6289
6430
  debris(o, p, mat.hitDebris, entry2, n, clamp22(Math.round(E / 60) + 2, 2, 14), clamp22(Math.sqrt(E) / 6, 1, 8), src);
6290
6431
  wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true);
6291
6432
  const dmg = applyDamage(o, p, E * DMG_PER_J * scale, "ballistic", entry2, d, E * 0.1, 12, src);
6433
+ chipUnits(o, p, entry2, penetrated ? Math.min(E, Math.max(thr, E * 0.5)) : E, d, Math.max(0.05, caliber / 1e3 * 8), src);
6292
6434
  return { penetrated, exitEnergy: penetrated ? penMm !== void 0 ? E * (1 - thr / Ep) : E - thr / (hard ? piercing : 1) : 0, exitPoint, damage: dmg };
6293
6435
  };
6294
6436
  const melee = (o, p, kind, E, point, n, d, contactR, src) => {
@@ -6316,6 +6458,7 @@ var GameFloorplanStructure = (() => {
6316
6458
  const scale = mat.damageScale?.[kind] ?? 1;
6317
6459
  const done = applyDamage(o, p, Ec * DMG_PER_J * scale * (kind === "slash" ? p.cutFactor : 1), kind, point, d, E * 0.1, 12, src);
6318
6460
  addBreach(o, p, point, breachAreaOf(p, done, false), E, d, src);
6461
+ if (kind === "blunt") chipUnits(o, p, point, E, d, Math.max(contactR, 0.03), src);
6319
6462
  return done;
6320
6463
  };
6321
6464
  const blastPart = (o, p, point, energy, radius, src, res, reachAt) => {