@adatechnology/game-floorplan-structure 1.3.1 → 1.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.
@@ -5047,6 +5047,7 @@ var GameFloorplanStructure = (() => {
5047
5047
  var FLAMMABLE_FLUIDS = ["gas", "fuel"];
5048
5048
  var VENT_DIAMETER = 8e-3;
5049
5049
  var FLAME_LIGHTS_LEAK_C = 470;
5050
+ var RESTING_HOLD_COS = Math.cos(Math.PI / 6);
5050
5051
  var BREACH_RADIUS = 0.55;
5051
5052
  var BREACH_BLAST_FACTOR = 6;
5052
5053
  var BREACH_AREA = 0.25;
@@ -5062,6 +5063,7 @@ var GameFloorplanStructure = (() => {
5062
5063
  var BREACH_GRID_CAP = 4096;
5063
5064
  var CHIP_ENERGY = 200;
5064
5065
  var CHIP_SPLASH = 0.5;
5066
+ var CRATER_SPENT = 0.6;
5065
5067
  var BAR_BOW = 0.08;
5066
5068
  var BULLET_JOINT_SPAN = 0.3;
5067
5069
  var CUT_SLICE = 1;
@@ -5082,12 +5084,22 @@ var GameFloorplanStructure = (() => {
5082
5084
  const cells = p.st.chipped;
5083
5085
  if (!cells?.length) return 0;
5084
5086
  const [u, v] = planeAxes(p.thin);
5085
- const along = p.dims[u] >= p.dims[v] ? u : v;
5087
+ const role = p.spec.structure?.role;
5088
+ const up = rotateVec(p.q, [0, 0, 0].map((_, k) => k === v ? 1 : 0));
5089
+ const upright = Math.abs(up[1]) > 0.7 ? v : Math.abs(rotateVec(p.q, [0, 0, 0].map((_, k) => k === u ? 1 : 0))[1]) > 0.7 ? u : -1;
5090
+ const along = (role === "wall" || role === "partition" || role === "column") && upright >= 0 ? upright : p.dims[u] >= p.dims[v] ? u : v;
5086
5091
  const across = along === u ? v : u;
5087
5092
  let worst = 0;
5088
5093
  for (const c of cells) {
5089
- let gone = 0;
5090
- for (const d of cells) if (Math.abs(d.center[along] - c.center[along]) < d.size[along] / 2 + 1e-6) gone += d.size[across];
5094
+ const spans = [];
5095
+ for (const d of cells) if (Math.abs(d.center[along] - c.center[along]) < d.size[along] / 2 + 1e-6) spans.push([d.center[across] - d.size[across] / 2, d.center[across] + d.size[across] / 2]);
5096
+ spans.sort((a, b) => a[0] - b[0]);
5097
+ let gone = 0, end = -Infinity;
5098
+ for (const [a, b] of spans) {
5099
+ if (b <= end) continue;
5100
+ gone += b - Math.max(a, end);
5101
+ end = b;
5102
+ }
5091
5103
  worst = Math.max(worst, gone / Math.max(p.dims[across], 1e-6));
5092
5104
  }
5093
5105
  return Math.min(1, worst);
@@ -5383,7 +5395,18 @@ var GameFloorplanStructure = (() => {
5383
5395
  if (k < 0 || j.spec.kind !== "tie") return k;
5384
5396
  return partXf(o, o.parts[k]).c[1] > partXf(o, o.parts[r]).c[1] + 1e-6 ? k : -1;
5385
5397
  };
5398
+ let poseVersion = 0;
5399
+ const contactMemo = /* @__PURE__ */ new WeakMap();
5386
5400
  const contactBox = (p, support) => {
5401
+ let byTouching = contactMemo.get(p);
5402
+ if (!byTouching) contactMemo.set(p, byTouching = /* @__PURE__ */ new Map());
5403
+ const known = byTouching.get(support);
5404
+ if (known && known.version === poseVersion) return known.box;
5405
+ const box2 = measureContact(p, support);
5406
+ byTouching.set(support, { version: poseVersion, box: box2 });
5407
+ return box2;
5408
+ };
5409
+ const measureContact = (p, support) => {
5387
5410
  const A = aabbOf(p.spec.shape, p.pos, p.q), B = aabbOf(support.spec.shape, support.pos, support.q);
5388
5411
  const lo = [0, 0, 0], hi = [0, 0, 0];
5389
5412
  for (let k = 0; k < 3; k++) {
@@ -6178,7 +6201,7 @@ var GameFloorplanStructure = (() => {
6178
6201
  p.st.holes.push(hole);
6179
6202
  if (p.st.holes.length > maxHoles) p.st.holes.shift();
6180
6203
  markDamaged(p);
6181
- emit({ ...base(o, src), type: "hole", part: p.spec.id, hole, worldPoint: wp, ...exit ? { exitPoint: exit } : {} });
6204
+ emit({ ...base(o, src), type: "hole", part: p.spec.id, hole, worldPoint: wp, ...exit ? { exitPoint: exit } : {}, ...p.spec.powder ? { powder: { ...p.spec.powder } } : {} });
6182
6205
  setStage(o, p, 1, {}, src);
6183
6206
  return hole;
6184
6207
  };
@@ -6195,6 +6218,22 @@ var GameFloorplanStructure = (() => {
6195
6218
  const detachEvents = (o, opened, by, src) => {
6196
6219
  for (const j of opened) emit({ ...base(o, src), type: "detach", joint: j.spec.id, parts: [j.spec.a, j.spec.b], by, groups: o.st.groups.map((g) => [...g]) });
6197
6220
  };
6221
+ const slideOffResting = (o, rotation) => {
6222
+ const upY = 1 - 2 * (rotation[0] * rotation[0] + rotation[2] * rotation[2]);
6223
+ if (upY >= RESTING_HOLD_COS) return;
6224
+ const opened = o.joints.filter((j) => j.spec.kind === "resting" && !j.st.open);
6225
+ if (opened.length === 0) return;
6226
+ run(() => {
6227
+ for (const j of opened) {
6228
+ j.st.open = true;
6229
+ j.st.openedBy = "break";
6230
+ }
6231
+ solveStructure(o);
6232
+ recomputeGroups(o);
6233
+ detachEvents(o, opened, "break");
6234
+ collapseCheck(o);
6235
+ });
6236
+ };
6198
6237
  const checkDestroyed = (o, src) => {
6199
6238
  if (!o.st.destroyed && o.parts.length > 0 && o.parts.every(isLost)) {
6200
6239
  o.st.destroyed = true;
@@ -6295,7 +6334,7 @@ var GameFloorplanStructure = (() => {
6295
6334
  const localHolesOf = (p) => p.st.holes.map((h) => ({ point: toLocal(p.pos, p.q, h.point), normal: rotateVec(quat.conjugate(p.q), h.normal), diameter: h.diameter }));
6296
6335
  const unitsOf2 = (p) => p.spec.units ?? { size: [p.mat.fragmentSize?.[1] ?? 0.2, p.mat.fragmentSize?.[1] ?? 0.2, p.thickness], bond: "stack" };
6297
6336
  const FRAGMENT_SHAPE = { shatter: "shard", splinter: "splinter", tear: "scrap" };
6298
- const cellFragments = (o, p, cells, hitW, dir, speed, mass, tag) => {
6337
+ const cellFragments = (o, p, cells, hitW, dir, speed, mass, tag, inner2 = false) => {
6299
6338
  const { c, q } = partXf(o, p);
6300
6339
  const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
6301
6340
  const [u, v] = planeAxes(p.thin);
@@ -6319,7 +6358,8 @@ var GameFloorplanStructure = (() => {
6319
6358
  velocity: [dv[0] * k, dv[1] * k + 0.5 * rnd(), dv[2] * k],
6320
6359
  angularVelocity: [(rnd() - 0.5) * 8, (rnd() - 0.5) * 8, (rnd() - 0.5) * 8],
6321
6360
  mass: mass * faceArea(cell) / total,
6322
- ...marks.length > 0 ? { marks } : {}
6361
+ ...marks.length > 0 ? { marks } : {},
6362
+ ...inner2 ? { inner: inner2 } : {}
6323
6363
  };
6324
6364
  });
6325
6365
  };
@@ -6360,7 +6400,7 @@ var GameFloorplanStructure = (() => {
6360
6400
  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);
6361
6401
  };
6362
6402
  const chipUnits = (o, p, kind, wp, hitEnergy, dir, radius, src) => {
6363
- var _a, _b;
6403
+ var _a, _b, _c;
6364
6404
  if (!p.spec.units || !p.staged || chipEnergy <= 0 || p.st.stripped || isLost(p) || (p.st.stage ?? 0) >= 2) return;
6365
6405
  const rule = p.mat.hits?.[kind];
6366
6406
  const perKg = rule?.unitJoules ?? chipEnergy;
@@ -6370,7 +6410,9 @@ var GameFloorplanStructure = (() => {
6370
6410
  const lp = toLocal(p.pos, p.q, objLocal(o, wp));
6371
6411
  const grid = gridOf(p);
6372
6412
  const worn = (_a = p.rt).chips ?? (_a.chips = {});
6413
+ const cut = (_b = p.rt).craters ?? (_b.craters = {});
6373
6414
  const out = [];
6415
+ const shot = kind === "ballistic";
6374
6416
  for (let i = 0; i < grid.length; i++) {
6375
6417
  const cell = grid[i];
6376
6418
  if (worn[i] === -1) continue;
@@ -6383,18 +6425,39 @@ var GameFloorplanStructure = (() => {
6383
6425
  worn[i] = total;
6384
6426
  continue;
6385
6427
  }
6386
- worn[i] = -1;
6387
- out.push(cell);
6428
+ if (!shot) {
6429
+ worn[i] = -1;
6430
+ out.push(cell);
6431
+ continue;
6432
+ }
6433
+ const size = [cell.size[0], cell.size[1], cell.size[2]];
6434
+ const center = [cell.center[0], cell.center[1], cell.center[2]];
6435
+ for (const a of [u, v]) {
6436
+ const w = Math.min(cell.size[a], craterAcross(hitEnergy) * (0.85 + 0.3 * rnd()));
6437
+ const lo = cell.center[a] - cell.size[a] / 2 + w / 2, hi = cell.center[a] + cell.size[a] / 2 - w / 2;
6438
+ size[a] = w;
6439
+ center[a] = clamp22(lp[a], lo, hi);
6440
+ }
6441
+ const taken2 = (cut[i] ?? 0) + size[u] * size[v];
6442
+ if (taken2 >= cell.size[u] * cell.size[v] * CRATER_SPENT) {
6443
+ worn[i] = -1;
6444
+ out.push({ ...cell, crater: true });
6445
+ continue;
6446
+ }
6447
+ worn[i] = 0;
6448
+ cut[i] = taken2;
6449
+ out.push({ center, size, crater: true });
6388
6450
  }
6389
6451
  if (out.length === 0) return;
6390
6452
  const { c, q } = partXf(o, p);
6391
- const chipped = (_b = p.st).chipped ?? (_b.chipped = []);
6453
+ const chipped = (_c = p.st).chipped ?? (_c.chipped = []);
6392
6454
  const holes = localHolesOf(p);
6393
6455
  for (const cell of out) {
6394
6456
  const mass = Math.min(p.st.mass, p.mat.density * cell.size[0] * cell.size[1] * cell.size[2]);
6395
6457
  const center = toWorld(c, q, cell.center);
6396
6458
  const speed = clamp22(Math.sqrt(2 * energy * 0.05 / Math.max(mass, 0.05)), 0.5, 6);
6397
- const fragments = cellFragments(o, p, [cell], wp, dir, speed, mass, `c${chipped.length}_`);
6459
+ const tag = `c${chipped.length}_`;
6460
+ const fragments = cell.crater ? craterFragments(o, p, cell, dir, speed, mass, tag) : cellFragments(o, p, [cell], wp, dir, speed, mass, tag, true);
6398
6461
  p.st.holes = p.st.holes.filter((_, k) => marksInBox(cell.center, cell.size, void 0, [holes[k]]).length === 0);
6399
6462
  chipped.push({ center: [cell.center[0], cell.center[1], cell.center[2]], size: [cell.size[0], cell.size[1], cell.size[2]] });
6400
6463
  p.st.mass = Math.max(0, p.st.mass - mass);
@@ -6411,6 +6474,44 @@ var GameFloorplanStructure = (() => {
6411
6474
  collapseCheck(o, src);
6412
6475
  }
6413
6476
  };
6477
+ const craterAcross = (energy) => clamp22(0.03 + 12e-4 * Math.sqrt(Math.max(0, energy)), 0.03, 0.14);
6478
+ const craterFragments = (o, p, cell, dir, speed, mass, tag) => {
6479
+ const { c, q } = partXf(o, p);
6480
+ const [u, v] = planeAxes(p.thin);
6481
+ const d = dir ?? rotateVec(q, [p.thin === 0 ? 1 : 0, p.thin === 1 ? 1 : 0, p.thin === 2 ? 1 : 0]);
6482
+ const exitSign = Math.sign(rotateVec(quat.conjugate(q), d)[p.thin]) || 1;
6483
+ const bit = Math.max(0.01, Math.min(cell.size[u], cell.size[v]) / 3);
6484
+ const n = clamp22(Math.round(cell.size[u] * cell.size[v] / (bit * bit * 3)), 4, 10) + Math.floor(rnd() * 3);
6485
+ const out = [];
6486
+ for (let i = 0; i < n; i++) {
6487
+ const back = i % 3 === 2;
6488
+ const lc = [cell.center[0], cell.center[1], cell.center[2]];
6489
+ lc[u] += (rnd() - 0.5) * cell.size[u] * 0.7;
6490
+ lc[v] += (rnd() - 0.5) * cell.size[v] * 0.7;
6491
+ lc[p.thin] += (back ? -exitSign : exitSign) * (p.thickness / 2 + 0.01);
6492
+ const size = [0, 0, 0];
6493
+ size[u] = bit * (0.6 + 0.8 * rnd());
6494
+ size[v] = bit * (0.5 + 0.7 * rnd());
6495
+ size[p.thin] = bit * (0.4 + 0.6 * rnd());
6496
+ const spread3 = [(rnd() - 0.5) * 0.9, (rnd() - 0.3) * 0.9, (rnd() - 0.5) * 0.9];
6497
+ const k = speed * (back ? 0.4 + 0.4 * rnd() : 0.7 + 0.8 * rnd());
6498
+ const vdir = vec.normalize(vec.add(back ? vec.scale(d, -1) : d, spread3));
6499
+ out.push({
6500
+ id: `${p.spec.id}#${tag}${i}`,
6501
+ part: p.spec.id,
6502
+ material: p.spec.material,
6503
+ shape: "chunk",
6504
+ center: toWorld(c, q, lc),
6505
+ size,
6506
+ rotation: q,
6507
+ velocity: [vdir[0] * k, vdir[1] * k + 0.5 * rnd(), vdir[2] * k],
6508
+ angularVelocity: [(rnd() - 0.5) * 20, (rnd() - 0.5) * 20, (rnd() - 0.5) * 20],
6509
+ mass: mass / n,
6510
+ inner: true
6511
+ });
6512
+ }
6513
+ return out;
6514
+ };
6414
6515
  const exposeRebar = (o, p, src) => {
6415
6516
  const r = p.spec.reinforcement;
6416
6517
  if (!r) return;
@@ -6698,7 +6799,7 @@ var GameFloorplanStructure = (() => {
6698
6799
  });
6699
6800
  }
6700
6801
  const patch = [scale * 4, scale * 4, scale * 4];
6701
- emit({ ...base(o, src), type: "chip", part: p.spec.id, center: exit ?? entry2, size: patch, rotation: q, fragments });
6802
+ emit({ ...base(o, src), type: "chip", part: p.spec.id, center: exit ?? entry2, size: patch, rotation: q, fragments, torn: true });
6702
6803
  };
6703
6804
  const ballistic = (o, p, entry2, n, d, E, caliber, pathLen, src, round3 = {}) => {
6704
6805
  const mat = p.mat;
@@ -6757,12 +6858,13 @@ var GameFloorplanStructure = (() => {
6757
6858
  } else if (mat.ductility >= 0.5) {
6758
6859
  addDent(o, p, entry2, n, Math.max(caliber * 3 / 1e3, 0.012), clamp22(Math.min(Math.max(p.thickness * 3, 3e-3), ratio * 0.01), 1e-3, 0.05), src);
6759
6860
  } else if (brittle) {
6760
- addCrack(o, p, entry2, n, crackLen, src);
6861
+ if (!p.spec.units) addCrack(o, p, entry2, n, crackLen, src);
6761
6862
  if (mat.fracture !== "shatter") addHole(o, p, entry2, n, caliber / 1e3, false, void 0, src);
6762
6863
  } else addHole(o, p, entry2, n, caliber / 1e3, false, void 0, src);
6763
6864
  debris(o, p, mat.hitDebris, entry2, n, clamp22(Math.round(E / 60) + 2, 2, 14), clamp22(Math.sqrt(E) / 6, 1, 8), src);
6764
6865
  wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true, BULLET_JOINT_SPAN);
6765
- const absorbed = penetrated && mat.ductility >= 0.5 && mat.fracture !== "tear" ? Math.min(E, thr) : E;
6866
+ const hollowTear = mat.fracture === "tear" && p.spec.fill !== void 0 && p.spec.fill < 0.5;
6867
+ const absorbed = penetrated && (mat.ductility >= 0.5 && mat.fracture !== "tear" || hollowTear) ? Math.min(E, thr) : E;
6766
6868
  const dmg = applyDamage(o, p, absorbed * DMG_PER_J * scale, "ballistic", entry2, d, absorbed * 0.1, 12, src);
6767
6869
  if (!isLost(p)) tearAt(o, p, "ballistic", entry2, penetrated ? exitPoint : void 0, d, E, caliber / 1e3, src);
6768
6870
  chipUnits(o, p, "ballistic", entry2, penetrated ? Math.min(E, Math.max(thr, E * 0.5)) : E, d, Math.max(0.05, caliber / 1e3 * 8), src);
@@ -6847,10 +6949,25 @@ var GameFloorplanStructure = (() => {
6847
6949
  if (dist <= BREACH_BLAST_REACH * Math.cbrt(Math.max(0, energy) / 1e3)) addBreach(o, p, wp, breachAreaOf(p, dealt, true), Ein, null, src);
6848
6950
  return reach;
6849
6951
  };
6952
+ const blastProbe = { partsTested: 0, partsNear: 0 };
6953
+ const blastCandidates = (o, point, radius) => {
6954
+ const local = toLocal(o.st.position, o.st.rotation, point);
6955
+ const near = [];
6956
+ for (const p of o.parts) {
6957
+ const reach = radius + p.bound;
6958
+ const dx = local[0] - p.pos[0];
6959
+ const dy = local[1] - p.pos[1];
6960
+ const dz = local[2] - p.pos[2];
6961
+ if (dx * dx + dy * dy + dz * dz < reach * reach) near.push(p);
6962
+ }
6963
+ blastProbe.partsTested += o.parts.length;
6964
+ blastProbe.partsNear += near.length;
6965
+ return near;
6966
+ };
6850
6967
  const blastObject = (o, point, energy, radius, src) => {
6851
6968
  const start = cur.length;
6852
6969
  const res = { object: o.id, damage: 0, penetrated: false, exitEnergy: 0, events: [] };
6853
- for (const p of o.parts) blastPart(o, p, point, energy, radius, src, res);
6970
+ for (const p of blastCandidates(o, point, radius)) blastPart(o, p, point, energy, radius, src, res);
6854
6971
  res.events = cur.slice(start);
6855
6972
  return res;
6856
6973
  };
@@ -6974,7 +7091,7 @@ var GameFloorplanStructure = (() => {
6974
7091
  let order = 0;
6975
7092
  for (const o of inRange) {
6976
7093
  if (only && only !== o) continue;
6977
- for (const p of o.parts) {
7094
+ for (const p of blastCandidates(o, point, radius)) {
6978
7095
  if (isLost(p)) continue;
6979
7096
  const dist = distToPart(o, p, point);
6980
7097
  if (dist < radius) items.push({ dist, order: order++, o, p });
@@ -7521,6 +7638,7 @@ var GameFloorplanStructure = (() => {
7521
7638
  const p = getPart(o, partId);
7522
7639
  p.pos = [position[0], position[1], position[2]];
7523
7640
  p.q = q;
7641
+ poseVersion++;
7524
7642
  updateBound(o);
7525
7643
  },
7526
7644
  remove(id) {
@@ -7539,6 +7657,7 @@ var GameFloorplanStructure = (() => {
7539
7657
  const o = getObj(id);
7540
7658
  o.base = { pos: [position[0], position[1], position[2]], rot: [rotation[0], rotation[1], rotation[2], rotation[3]] };
7541
7659
  applySoilPose(o);
7660
+ slideOffResting(o, rotation);
7542
7661
  },
7543
7662
  splitGroup(id, group, newId) {
7544
7663
  if (impls.has(id)) throw new Error(`object "${id}" is a registered DestructibleImpl and cannot be split; its module owns its pieces`);
@@ -7562,7 +7681,7 @@ var GameFloorplanStructure = (() => {
7562
7681
  if (o.spec.name !== void 0) s.name = o.spec.name;
7563
7682
  if (o.spec.tags) s.tags = [...o.spec.tags];
7564
7683
  if (o.spec.soil !== void 0) s.soil = o.spec.soil;
7565
- return clone(s);
7684
+ return s;
7566
7685
  };
7567
7686
  const inNew = (pid) => moved.has(pid);
7568
7687
  const inOld = (pid) => !moved.has(pid);
@@ -7589,7 +7708,12 @@ var GameFloorplanStructure = (() => {
7589
7708
  const p = getPart(o, partId);
7590
7709
  return whole(() => doHit(o, h, p));
7591
7710
  },
7711
+ blastProbe() {
7712
+ return { ...blastProbe };
7713
+ },
7592
7714
  explode(point, energy, radius, source, blast) {
7715
+ blastProbe.partsTested = 0;
7716
+ blastProbe.partsNear = 0;
7593
7717
  return withCollapses(run(() => blastWorld(point, energy, radius, source, blast)));
7594
7718
  },
7595
7719
  raycast(origin, direction, maxDistance = Infinity) {
@@ -7599,7 +7723,8 @@ var GameFloorplanStructure = (() => {
7599
7723
  sweepBox(box2, direction, distance2, options2 = {}) {
7600
7724
  const d = vec.normalize(direction);
7601
7725
  const ignored = options2.ignore === void 0 ? void 0 : objects.get(options2.ignore);
7602
- const ignore = ignored ? new Set(ignored.parts.map((p) => `${ignored.id}\0${p.spec.id}`)) : void 0;
7726
+ const hosted = options2.ignore === void 0 || ignored ? void 0 : impls.get(options2.ignore);
7727
+ const ignore = ignored ? new Set(ignored.parts.map((p) => `${ignored.id}\0${p.spec.id}`)) : hosted ? new Set(hosted.impl.describe().parts.map((p) => `${hosted.impl.id}\0${p.id}`)) : void 0;
7603
7728
  let best = null;
7604
7729
  for (const origin of sweepOrigins(box2, d, options2.spacing ?? 0.2)) {
7605
7730
  const r = raycastInternal(origin, d, distance2 + SWEEP_BACK, ignore);