@adatechnology/game-destruction 0.1.0 → 0.2.0

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.
package/dist/index.js CHANGED
@@ -1088,7 +1088,7 @@ var materials_default = [
1088
1088
  {
1089
1089
  id: "green_wood",
1090
1090
  density: 650,
1091
- hpPerKg: 120,
1091
+ hpPerKg: 5,
1092
1092
  penetration: 10,
1093
1093
  ductility: 0.5,
1094
1094
  fracture: "splinter",
@@ -1097,7 +1097,8 @@ var materials_default = [
1097
1097
  0.2
1098
1098
  ],
1099
1099
  damageScale: {
1100
- slash: 1.3
1100
+ slash: 1.3,
1101
+ ballistic: 0.1
1101
1102
  },
1102
1103
  hitDebris: "splinters",
1103
1104
  breakDebris: "leaves",
@@ -1407,9 +1408,20 @@ var fragility = (ductility, thickness) => {
1407
1408
  var DMG_PER_J = 0.02;
1408
1409
  var BLAST_COUPLING = 0.1;
1409
1410
  var FLAMMABLE_FLUIDS = ["gas", "fuel"];
1411
+ var CUT_SLICE = 1;
1412
+ var cutFactorOf = (shape) => clamp2(Math.max(...shapeDims(shape)) / CUT_SLICE, 1, 50);
1410
1413
  var clamp2 = (v, a, b) => Math.max(a, Math.min(b, v));
1411
1414
  var isLost = (p) => p.st.status === "broken" || p.st.status === "melted" || p.st.status === "ashes";
1412
1415
  var clone = (v) => structuredClone(v);
1416
+ var updateBound = (o) => {
1417
+ if (!o.parts.length) return;
1418
+ const c = [0, 0, 0];
1419
+ for (const p of o.parts) for (let k = 0; k < 3; k++) c[k] += p.pos[k] / o.parts.length;
1420
+ let r = 0;
1421
+ for (const p of o.parts) r = Math.max(r, vec.distance(c, p.pos) + p.bound);
1422
+ o.bc = c;
1423
+ o.br = r;
1424
+ };
1413
1425
  var AXIS = { x: 0, y: 1, z: 2 };
1414
1426
  var createDestruction = (options = {}) => {
1415
1427
  const materials = /* @__PURE__ */ new Map();
@@ -1545,10 +1557,12 @@ var createDestruction = (options = {}) => {
1545
1557
  mat,
1546
1558
  st: pst,
1547
1559
  q: quatFromEuler(...ps.rotation ?? [0, 0, 0]),
1560
+ pos: [ps.position[0], ps.position[1], ps.position[2]],
1548
1561
  mass0,
1549
1562
  fuel0,
1550
1563
  area: shapeArea(ps.shape),
1551
1564
  thickness: partThickness(ps),
1565
+ cutFactor: cutFactorOf(ps.shape),
1552
1566
  dims: shapeDims(ps.shape),
1553
1567
  bound: shapeBound(ps.shape),
1554
1568
  rt: c?.rt ?? { ...freshRuntime(), prevTemp: pst.temperature },
@@ -1583,14 +1597,8 @@ var createDestruction = (options = {}) => {
1583
1597
  o.byPart.get(r.b).push(j);
1584
1598
  o.st.joints.push(jst);
1585
1599
  }
1586
- if (o.parts.length) {
1587
- const c = [0, 0, 0];
1588
- for (const p of o.parts) for (let k = 0; k < 3; k++) c[k] += p.spec.position[k] / o.parts.length;
1589
- let r = 0;
1590
- for (const p of o.parts) r = Math.max(r, vec.distance(c, p.spec.position) + p.bound);
1591
- o.bc = c;
1592
- o.br = r;
1593
- }
1600
+ for (const [jid, c] of carry?.joints ?? []) if (c.st.open && !o.jointMap.has(jid)) o.st.joints.push(c.st);
1601
+ updateBound(o);
1594
1602
  o.anyAnchor = o.parts.some((p) => p.anchored);
1595
1603
  o.unsup = o.parts.map(() => false);
1596
1604
  solveStructure(o);
@@ -1730,7 +1738,7 @@ var createDestruction = (options = {}) => {
1730
1738
  if (!p) throw new Error(`unknown part "${id}" in object "${o.id}"`);
1731
1739
  return p;
1732
1740
  };
1733
- const partXf = (o, p) => ({ c: toWorld(o.st.position, o.st.rotation, p.spec.position), q: quat.mul(o.st.rotation, p.q) });
1741
+ const partXf = (o, p) => ({ c: toWorld(o.st.position, o.st.rotation, p.pos), q: quat.mul(o.st.rotation, p.q) });
1734
1742
  const objLocal = (o, w) => toLocal(o.st.position, o.st.rotation, w);
1735
1743
  const objLocalDir = (o, w) => rotateVec(quat.conjugate(o.st.rotation), w);
1736
1744
  const jointWorld = (o, j) => toWorld(o.st.position, o.st.rotation, j.pos);
@@ -1742,14 +1750,14 @@ var createDestruction = (options = {}) => {
1742
1750
  const { c, q } = partXf(o, p);
1743
1751
  return aabbOf(p.spec.shape, c, q);
1744
1752
  };
1745
- const rayPartRaw = (pos, rot, spec, pq, origin, d) => {
1746
- const c = toWorld(pos, rot, spec.position);
1753
+ const rayPartRaw = (pos, rot, spec, pq, origin, d, lp = spec.position) => {
1754
+ const c = toWorld(pos, rot, lp);
1747
1755
  const q = quat.mul(rot, pq);
1748
1756
  const qi = quat.conjugate(q);
1749
1757
  const r = rayShape(spec.shape, rotateVec(qi, vec.sub(origin, c)), rotateVec(qi, d));
1750
1758
  return r ? { t0: r.t0, t1: r.t1, n: rotateVec(q, r.n0) } : null;
1751
1759
  };
1752
- const rayPart = (o, p, origin, d) => rayPartRaw(o.st.position, o.st.rotation, p.spec, p.q, origin, d);
1760
+ const rayPart = (o, p, origin, d) => rayPartRaw(o.st.position, o.st.rotation, p.spec, p.q, origin, d, p.pos);
1753
1761
  const raySphereMiss = (origin, d, c, r, maxDist) => {
1754
1762
  const oc = vec.sub(origin, c);
1755
1763
  const b = vec.dot(oc, d);
@@ -1774,7 +1782,8 @@ var createDestruction = (options = {}) => {
1774
1782
  for (const im of impls.values()) {
1775
1783
  for (const ps of im.impl.describe().parts) {
1776
1784
  if (ignore?.has(`${im.impl.id}\0${ps.id}`)) continue;
1777
- const r = rayPartRaw(im.position, im.rotation, ps, quatFromEuler(...ps.rotation ?? [0, 0, 0]), origin, d);
1785
+ const ov = im.poses.get(ps.id);
1786
+ const r = rayPartRaw(im.position, im.rotation, ps, ov?.q ?? quatFromEuler(...ps.rotation ?? [0, 0, 0]), origin, d, ov?.pos);
1778
1787
  if (!r || r.t0 < 0 || r.t0 > maxDist) continue;
1779
1788
  if (!best || r.t0 < best.distance) best = { object: im.impl.id, part: ps.id, point: vec.madd(origin, d, r.t0), normal: r.n, distance: r.t0, pathLen: r.t1 - r.t0 };
1780
1789
  }
@@ -1909,11 +1918,29 @@ var createDestruction = (options = {}) => {
1909
1918
  collapseCheck(o, src);
1910
1919
  checkDestroyed(o, src);
1911
1920
  };
1912
- const wearJoints = (o, p, amount, near, radius = 0.06, src) => {
1921
+ const jointReach = (o, j, point) => {
1922
+ const A = partWorldAabb(o, o.parts[j.ia]), B = partWorldAabb(o, o.parts[j.ib]);
1923
+ let d2 = 0;
1924
+ for (let k = 0; k < 3; k++) {
1925
+ const lo = Math.min(Math.max(A.min[k], B.min[k]), Math.min(A.max[k], B.max[k]));
1926
+ const hi = Math.max(Math.max(A.min[k], B.min[k]), Math.min(A.max[k], B.max[k]));
1927
+ const d = point[k] < lo ? lo - point[k] : point[k] > hi ? point[k] - hi : 0;
1928
+ d2 += d * d;
1929
+ }
1930
+ return Math.sqrt(d2);
1931
+ };
1932
+ const jointReachLimit = (o, j) => {
1933
+ const pa = o.parts[j.ia], pb = o.parts[j.ib];
1934
+ const small = pa.dims[0] * pa.dims[1] * pa.dims[2] <= pb.dims[0] * pb.dims[1] * pb.dims[2] ? pa : pb;
1935
+ return Math.max(0.06, 0.5 * Math.min(small.dims[0], small.dims[1], small.dims[2]));
1936
+ };
1937
+ const wearJoints = (o, p, amount, near, radius = 0.06, src, split = false) => {
1913
1938
  if (amount <= 0) return;
1914
- for (const j of o.byPart.get(p.spec.id) ?? []) {
1939
+ const mine = (o.byPart.get(p.spec.id) ?? []).filter((j) => !j.st.open);
1940
+ if (split && mine.length) amount /= mine.length;
1941
+ for (const j of mine) {
1915
1942
  if (j.st.open) continue;
1916
- if (near && vec.distance(jointWorld(o, j), near) > radius) continue;
1943
+ if (near && jointReach(o, j, near) > Math.max(radius, jointReachLimit(o, j))) continue;
1917
1944
  j.st.hp -= amount;
1918
1945
  if (j.st.hp <= 0) {
1919
1946
  j.st.hp = 0;
@@ -2032,7 +2059,7 @@ var createDestruction = (options = {}) => {
2032
2059
  if (mat.fracture !== "shatter") addHole(o, p, entry, n, caliber / 1e3, false, void 0, src);
2033
2060
  } else addHole(o, p, entry, n, caliber / 1e3, false, void 0, src);
2034
2061
  debris(o, p, mat.hitDebris, entry, n, clamp2(Math.round(E / 60) + 2, 2, 14), clamp2(Math.sqrt(E) / 6, 1, 8), src);
2035
- wearJoints(o, p, E * 0.2, entry, 0.06, src);
2062
+ wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true);
2036
2063
  const dmg = applyDamage(o, p, E * DMG_PER_J * scale, "ballistic", entry, d, E * 0.1, 12, src);
2037
2064
  return { penetrated, exitEnergy: penetrated ? E - thr : 0, exitPoint, damage: dmg };
2038
2065
  };
@@ -2057,7 +2084,7 @@ var createDestruction = (options = {}) => {
2057
2084
  if (kind === "blunt") wearJoints(o, p, E * 0.3, void 0, 0, src);
2058
2085
  else wearJoints(o, p, E * 0.3, point, 0.06, src);
2059
2086
  const scale = mat.damageScale?.[kind] ?? 1;
2060
- return applyDamage(o, p, Ec * DMG_PER_J * scale, kind, point, d, E * 0.1, 12, src);
2087
+ return applyDamage(o, p, Ec * DMG_PER_J * scale * (kind === "slash" ? p.cutFactor : 1), kind, point, d, E * 0.1, 12, src);
2061
2088
  };
2062
2089
  const blastObject = (o, point, energy, radius, src) => {
2063
2090
  const start = cur.length;
@@ -2107,8 +2134,9 @@ var createDestruction = (options = {}) => {
2107
2134
  }
2108
2135
  for (const im of [...impls.values()]) {
2109
2136
  const inRange = im.impl.describe().parts.some((ps) => {
2110
- const c = toWorld(im.position, im.rotation, ps.position);
2111
- const q = quat.mul(im.rotation, quatFromEuler(...ps.rotation ?? [0, 0, 0]));
2137
+ const ov = im.poses.get(ps.id);
2138
+ const c = toWorld(im.position, im.rotation, ov?.pos ?? ps.position);
2139
+ const q = quat.mul(im.rotation, ov?.q ?? quatFromEuler(...ps.rotation ?? [0, 0, 0]));
2112
2140
  return distanceToShape(ps.shape, toLocal(c, q, point)) < radius;
2113
2141
  });
2114
2142
  if (inRange) out.push(implHit(im, { type: "explosive", point, energy, radius, ...src !== void 0 ? { source: src } : {} }));
@@ -2360,7 +2388,7 @@ var createDestruction = (options = {}) => {
2360
2388
  const spread = (sources, dt) => {
2361
2389
  for (const src of sources) {
2362
2390
  if (!src.p.st.burning && !src.p.rt.jet) continue;
2363
- const sc = toWorld(src.o.st.position, src.o.st.rotation, src.p.spec.position);
2391
+ const sc = toWorld(src.o.st.position, src.o.st.rotation, src.p.pos);
2364
2392
  const A = partWorldAabb(src.o, src.p);
2365
2393
  for (const o of objects.values()) {
2366
2394
  if (o === src.o) continue;
@@ -2406,9 +2434,23 @@ var createDestruction = (options = {}) => {
2406
2434
  register(impl, opts = {}) {
2407
2435
  const id = opts.id ?? impl.id;
2408
2436
  if (taken(id)) throw new Error(`object "${id}" already exists`);
2409
- impls.set(id, { impl, position: opts.position ?? [0, 0, 0], rotation: opts.rotation ?? [0, 0, 0, 1] });
2437
+ impls.set(id, { impl, position: opts.position ?? [0, 0, 0], rotation: opts.rotation ?? [0, 0, 0, 1], poses: /* @__PURE__ */ new Map() });
2410
2438
  return id;
2411
2439
  },
2440
+ setPartPose(objectId, partId, position, rotation) {
2441
+ const q = quatFromEuler(...rotation ?? [0, 0, 0]);
2442
+ const im = impls.get(objectId);
2443
+ if (im) {
2444
+ im.impl.setPartPose?.(partId, position, rotation);
2445
+ im.poses.set(partId, { pos: [position[0], position[1], position[2]], q });
2446
+ return;
2447
+ }
2448
+ const o = getObj(objectId);
2449
+ const p = getPart(o, partId);
2450
+ p.pos = [position[0], position[1], position[2]];
2451
+ p.q = q;
2452
+ updateBound(o);
2453
+ },
2412
2454
  remove(id) {
2413
2455
  objects.delete(id);
2414
2456
  impls.delete(id);
@@ -2435,9 +2477,12 @@ var createDestruction = (options = {}) => {
2435
2477
  const moved = new Set(names);
2436
2478
  const nid = newId ?? nextId();
2437
2479
  if (objects.has(nid)) throw new Error(`object "${nid}" already exists`);
2438
- const carryFor = (keep) => ({
2480
+ const carryFor = (keep, keepOpen = () => false) => ({
2439
2481
  parts: new Map(o.parts.filter((p) => keep(p.spec.id)).map((p) => [p.spec.id, { st: p.st, rt: p.rt }])),
2440
- joints: new Map(o.joints.filter((j) => keep(j.spec.a) && keep(j.spec.b)).map((j) => [j.spec.id, { st: j.st, step: j.step }])),
2482
+ joints: new Map([
2483
+ ...o.joints.filter((j) => keep(j.spec.a) && keep(j.spec.b) || j.st.open && keepOpen(j.spec.a, j.spec.b)).map((j) => [j.spec.id, { st: j.st, step: j.step }]),
2484
+ ...o.st.joints.filter((st) => st.open && !o.jointMap.has(st.id) && keepOpen(st.a, st.b)).map((st) => [st.id, { st, step: 0 }])
2485
+ ]),
2441
2486
  collapsed: [...o.collapsed].filter(keep)
2442
2487
  });
2443
2488
  const sub = (keep) => {
@@ -2449,8 +2494,8 @@ var createDestruction = (options = {}) => {
2449
2494
  const inNew = (pid) => moved.has(pid);
2450
2495
  const inOld = (pid) => !moved.has(pid);
2451
2496
  const pos = o.st.position, rot = o.st.rotation;
2452
- const fresh = build(sub(inNew), nid, pos, rot, carryFor(inNew));
2453
- const rest = build(sub(inOld), id, pos, rot, carryFor(inOld));
2497
+ const fresh = build(sub(inNew), nid, pos, rot, carryFor(inNew, (a, b) => inNew(a) && inNew(b)));
2498
+ const rest = build(sub(inOld), id, pos, rot, carryFor(inOld, (a, b) => !(inNew(a) && inNew(b))));
2454
2499
  objects.set(id, rest);
2455
2500
  objects.set(nid, fresh);
2456
2501
  return nid;
@@ -2548,34 +2593,37 @@ var createDestruction = (options = {}) => {
2548
2593
  }
2549
2594
  return getObj(objectId).joints.filter((j) => !j.st.open && j.spec.tools.includes(tool)).map((j) => j.st);
2550
2595
  },
2551
- nearestJoint(objectId, point, maxDistance = 0.3) {
2596
+ nearestJoint(objectId, point, maxDistance = 0.3, tool) {
2552
2597
  const im = impls.get(objectId);
2598
+ const cands = [];
2553
2599
  if (im) {
2554
- let best2 = null;
2555
- let bd2 = maxDistance;
2600
+ const spec = im.impl.describe();
2556
2601
  for (const js of im.impl.state().joints) {
2557
2602
  if (js.open) continue;
2558
2603
  const pos = implJointPos(im, js.a, js.b);
2559
- const d = pos ? vec.distance(pos, point) : Infinity;
2560
- if (d <= bd2) {
2561
- bd2 = d;
2562
- best2 = js;
2563
- }
2604
+ const j = (spec.joints ?? []).find((x) => (x.id ?? `${x.a}~${x.b}`) === js.id);
2605
+ cands.push({ st: js, d: pos ? vec.distance(pos, point) : Infinity, ok: !!j && (j.tools ?? JOINT_DEFAULTS[j.kind].tools).includes(tool) });
2564
2606
  }
2565
- return best2;
2566
- }
2567
- const o = getObj(objectId);
2568
- let best = null;
2569
- let bd = maxDistance;
2570
- for (const j of o.joints) {
2571
- if (j.st.open) continue;
2572
- const d = vec.distance(jointWorld(o, j), point);
2573
- if (d <= bd) {
2574
- bd = d;
2575
- best = j.st;
2607
+ } else {
2608
+ const o = getObj(objectId);
2609
+ for (const j of o.joints) {
2610
+ if (j.st.open) continue;
2611
+ cands.push({ st: j.st, d: vec.distance(jointWorld(o, j), point), ok: tool ? j.spec.tools.includes(tool) : true });
2576
2612
  }
2577
2613
  }
2578
- return best;
2614
+ const pick = (only) => {
2615
+ let best = null;
2616
+ let bd = maxDistance;
2617
+ for (const c of cands) {
2618
+ if (only && !c.ok) continue;
2619
+ if (c.d <= bd) {
2620
+ bd = c.d;
2621
+ best = c.st;
2622
+ }
2623
+ }
2624
+ return best;
2625
+ };
2626
+ return (tool ? pick(true) : null) ?? pick(false);
2579
2627
  },
2580
2628
  extinguish(objectId, partId, coolC = 200) {
2581
2629
  const im = impls.get(objectId);
@@ -2618,6 +2666,10 @@ var createDestruction = (options = {}) => {
2618
2666
  }).events;
2619
2667
  },
2620
2668
  state: (id) => objects.get(id)?.st ?? impls.get(id)?.impl.state() ?? null,
2669
+ snapshot: (id) => {
2670
+ const st = objects.get(id)?.st ?? impls.get(id)?.impl.state();
2671
+ return st ? clone(st) : null;
2672
+ },
2621
2673
  part: (id, partId) => objects.get(id)?.byId.get(partId)?.st ?? impls.get(id)?.impl.state().parts.find((p) => p.id === partId) ?? null,
2622
2674
  reset(id) {
2623
2675
  const im = impls.get(id);