@adatechnology/game-destruction 0.1.0 → 0.1.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.
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",
@@ -1410,6 +1411,15 @@ var FLAMMABLE_FLUIDS = ["gas", "fuel"];
1410
1411
  var clamp2 = (v, a, b) => Math.max(a, Math.min(b, v));
1411
1412
  var isLost = (p) => p.st.status === "broken" || p.st.status === "melted" || p.st.status === "ashes";
1412
1413
  var clone = (v) => structuredClone(v);
1414
+ var updateBound = (o) => {
1415
+ if (!o.parts.length) return;
1416
+ const c = [0, 0, 0];
1417
+ for (const p of o.parts) for (let k = 0; k < 3; k++) c[k] += p.pos[k] / o.parts.length;
1418
+ let r = 0;
1419
+ for (const p of o.parts) r = Math.max(r, vec.distance(c, p.pos) + p.bound);
1420
+ o.bc = c;
1421
+ o.br = r;
1422
+ };
1413
1423
  var AXIS = { x: 0, y: 1, z: 2 };
1414
1424
  var createDestruction = (options = {}) => {
1415
1425
  const materials = /* @__PURE__ */ new Map();
@@ -1545,6 +1555,7 @@ var createDestruction = (options = {}) => {
1545
1555
  mat,
1546
1556
  st: pst,
1547
1557
  q: quatFromEuler(...ps.rotation ?? [0, 0, 0]),
1558
+ pos: [ps.position[0], ps.position[1], ps.position[2]],
1548
1559
  mass0,
1549
1560
  fuel0,
1550
1561
  area: shapeArea(ps.shape),
@@ -1583,14 +1594,8 @@ var createDestruction = (options = {}) => {
1583
1594
  o.byPart.get(r.b).push(j);
1584
1595
  o.st.joints.push(jst);
1585
1596
  }
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
- }
1597
+ for (const [jid, c] of carry?.joints ?? []) if (c.st.open && !o.jointMap.has(jid)) o.st.joints.push(c.st);
1598
+ updateBound(o);
1594
1599
  o.anyAnchor = o.parts.some((p) => p.anchored);
1595
1600
  o.unsup = o.parts.map(() => false);
1596
1601
  solveStructure(o);
@@ -1730,7 +1735,7 @@ var createDestruction = (options = {}) => {
1730
1735
  if (!p) throw new Error(`unknown part "${id}" in object "${o.id}"`);
1731
1736
  return p;
1732
1737
  };
1733
- const partXf = (o, p) => ({ c: toWorld(o.st.position, o.st.rotation, p.spec.position), q: quat.mul(o.st.rotation, p.q) });
1738
+ const partXf = (o, p) => ({ c: toWorld(o.st.position, o.st.rotation, p.pos), q: quat.mul(o.st.rotation, p.q) });
1734
1739
  const objLocal = (o, w) => toLocal(o.st.position, o.st.rotation, w);
1735
1740
  const objLocalDir = (o, w) => rotateVec(quat.conjugate(o.st.rotation), w);
1736
1741
  const jointWorld = (o, j) => toWorld(o.st.position, o.st.rotation, j.pos);
@@ -1742,14 +1747,14 @@ var createDestruction = (options = {}) => {
1742
1747
  const { c, q } = partXf(o, p);
1743
1748
  return aabbOf(p.spec.shape, c, q);
1744
1749
  };
1745
- const rayPartRaw = (pos, rot, spec, pq, origin, d) => {
1746
- const c = toWorld(pos, rot, spec.position);
1750
+ const rayPartRaw = (pos, rot, spec, pq, origin, d, lp = spec.position) => {
1751
+ const c = toWorld(pos, rot, lp);
1747
1752
  const q = quat.mul(rot, pq);
1748
1753
  const qi = quat.conjugate(q);
1749
1754
  const r = rayShape(spec.shape, rotateVec(qi, vec.sub(origin, c)), rotateVec(qi, d));
1750
1755
  return r ? { t0: r.t0, t1: r.t1, n: rotateVec(q, r.n0) } : null;
1751
1756
  };
1752
- const rayPart = (o, p, origin, d) => rayPartRaw(o.st.position, o.st.rotation, p.spec, p.q, origin, d);
1757
+ const rayPart = (o, p, origin, d) => rayPartRaw(o.st.position, o.st.rotation, p.spec, p.q, origin, d, p.pos);
1753
1758
  const raySphereMiss = (origin, d, c, r, maxDist) => {
1754
1759
  const oc = vec.sub(origin, c);
1755
1760
  const b = vec.dot(oc, d);
@@ -1774,7 +1779,8 @@ var createDestruction = (options = {}) => {
1774
1779
  for (const im of impls.values()) {
1775
1780
  for (const ps of im.impl.describe().parts) {
1776
1781
  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);
1782
+ const ov = im.poses.get(ps.id);
1783
+ const r = rayPartRaw(im.position, im.rotation, ps, ov?.q ?? quatFromEuler(...ps.rotation ?? [0, 0, 0]), origin, d, ov?.pos);
1778
1784
  if (!r || r.t0 < 0 || r.t0 > maxDist) continue;
1779
1785
  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
1786
  }
@@ -1909,11 +1915,29 @@ var createDestruction = (options = {}) => {
1909
1915
  collapseCheck(o, src);
1910
1916
  checkDestroyed(o, src);
1911
1917
  };
1912
- const wearJoints = (o, p, amount, near, radius = 0.06, src) => {
1918
+ const jointReach = (o, j, point) => {
1919
+ const A = partWorldAabb(o, o.parts[j.ia]), B = partWorldAabb(o, o.parts[j.ib]);
1920
+ let d2 = 0;
1921
+ for (let k = 0; k < 3; k++) {
1922
+ const lo = Math.min(Math.max(A.min[k], B.min[k]), Math.min(A.max[k], B.max[k]));
1923
+ const hi = Math.max(Math.max(A.min[k], B.min[k]), Math.min(A.max[k], B.max[k]));
1924
+ const d = point[k] < lo ? lo - point[k] : point[k] > hi ? point[k] - hi : 0;
1925
+ d2 += d * d;
1926
+ }
1927
+ return Math.sqrt(d2);
1928
+ };
1929
+ const jointReachLimit = (o, j) => {
1930
+ const pa = o.parts[j.ia], pb = o.parts[j.ib];
1931
+ const small = pa.dims[0] * pa.dims[1] * pa.dims[2] <= pb.dims[0] * pb.dims[1] * pb.dims[2] ? pa : pb;
1932
+ return Math.max(0.06, 0.5 * Math.min(small.dims[0], small.dims[1], small.dims[2]));
1933
+ };
1934
+ const wearJoints = (o, p, amount, near, radius = 0.06, src, split = false) => {
1913
1935
  if (amount <= 0) return;
1914
- for (const j of o.byPart.get(p.spec.id) ?? []) {
1936
+ const mine = (o.byPart.get(p.spec.id) ?? []).filter((j) => !j.st.open);
1937
+ if (split && mine.length) amount /= mine.length;
1938
+ for (const j of mine) {
1915
1939
  if (j.st.open) continue;
1916
- if (near && vec.distance(jointWorld(o, j), near) > radius) continue;
1940
+ if (near && jointReach(o, j, near) > Math.max(radius, jointReachLimit(o, j))) continue;
1917
1941
  j.st.hp -= amount;
1918
1942
  if (j.st.hp <= 0) {
1919
1943
  j.st.hp = 0;
@@ -2032,7 +2056,7 @@ var createDestruction = (options = {}) => {
2032
2056
  if (mat.fracture !== "shatter") addHole(o, p, entry, n, caliber / 1e3, false, void 0, src);
2033
2057
  } else addHole(o, p, entry, n, caliber / 1e3, false, void 0, src);
2034
2058
  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);
2059
+ wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true);
2036
2060
  const dmg = applyDamage(o, p, E * DMG_PER_J * scale, "ballistic", entry, d, E * 0.1, 12, src);
2037
2061
  return { penetrated, exitEnergy: penetrated ? E - thr : 0, exitPoint, damage: dmg };
2038
2062
  };
@@ -2107,8 +2131,9 @@ var createDestruction = (options = {}) => {
2107
2131
  }
2108
2132
  for (const im of [...impls.values()]) {
2109
2133
  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]));
2134
+ const ov = im.poses.get(ps.id);
2135
+ const c = toWorld(im.position, im.rotation, ov?.pos ?? ps.position);
2136
+ const q = quat.mul(im.rotation, ov?.q ?? quatFromEuler(...ps.rotation ?? [0, 0, 0]));
2112
2137
  return distanceToShape(ps.shape, toLocal(c, q, point)) < radius;
2113
2138
  });
2114
2139
  if (inRange) out.push(implHit(im, { type: "explosive", point, energy, radius, ...src !== void 0 ? { source: src } : {} }));
@@ -2360,7 +2385,7 @@ var createDestruction = (options = {}) => {
2360
2385
  const spread = (sources, dt) => {
2361
2386
  for (const src of sources) {
2362
2387
  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);
2388
+ const sc = toWorld(src.o.st.position, src.o.st.rotation, src.p.pos);
2364
2389
  const A = partWorldAabb(src.o, src.p);
2365
2390
  for (const o of objects.values()) {
2366
2391
  if (o === src.o) continue;
@@ -2406,9 +2431,23 @@ var createDestruction = (options = {}) => {
2406
2431
  register(impl, opts = {}) {
2407
2432
  const id = opts.id ?? impl.id;
2408
2433
  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] });
2434
+ impls.set(id, { impl, position: opts.position ?? [0, 0, 0], rotation: opts.rotation ?? [0, 0, 0, 1], poses: /* @__PURE__ */ new Map() });
2410
2435
  return id;
2411
2436
  },
2437
+ setPartPose(objectId, partId, position, rotation) {
2438
+ const q = quatFromEuler(...rotation ?? [0, 0, 0]);
2439
+ const im = impls.get(objectId);
2440
+ if (im) {
2441
+ im.impl.setPartPose?.(partId, position, rotation);
2442
+ im.poses.set(partId, { pos: [position[0], position[1], position[2]], q });
2443
+ return;
2444
+ }
2445
+ const o = getObj(objectId);
2446
+ const p = getPart(o, partId);
2447
+ p.pos = [position[0], position[1], position[2]];
2448
+ p.q = q;
2449
+ updateBound(o);
2450
+ },
2412
2451
  remove(id) {
2413
2452
  objects.delete(id);
2414
2453
  impls.delete(id);
@@ -2435,9 +2474,12 @@ var createDestruction = (options = {}) => {
2435
2474
  const moved = new Set(names);
2436
2475
  const nid = newId ?? nextId();
2437
2476
  if (objects.has(nid)) throw new Error(`object "${nid}" already exists`);
2438
- const carryFor = (keep) => ({
2477
+ const carryFor = (keep, keepOpen = () => false) => ({
2439
2478
  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 }])),
2479
+ joints: new Map([
2480
+ ...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 }]),
2481
+ ...o.st.joints.filter((st) => st.open && !o.jointMap.has(st.id) && keepOpen(st.a, st.b)).map((st) => [st.id, { st, step: 0 }])
2482
+ ]),
2441
2483
  collapsed: [...o.collapsed].filter(keep)
2442
2484
  });
2443
2485
  const sub = (keep) => {
@@ -2449,8 +2491,8 @@ var createDestruction = (options = {}) => {
2449
2491
  const inNew = (pid) => moved.has(pid);
2450
2492
  const inOld = (pid) => !moved.has(pid);
2451
2493
  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));
2494
+ const fresh = build(sub(inNew), nid, pos, rot, carryFor(inNew, (a, b) => inNew(a) && inNew(b)));
2495
+ const rest = build(sub(inOld), id, pos, rot, carryFor(inOld, (a, b) => !(inNew(a) && inNew(b))));
2454
2496
  objects.set(id, rest);
2455
2497
  objects.set(nid, fresh);
2456
2498
  return nid;
@@ -2548,34 +2590,37 @@ var createDestruction = (options = {}) => {
2548
2590
  }
2549
2591
  return getObj(objectId).joints.filter((j) => !j.st.open && j.spec.tools.includes(tool)).map((j) => j.st);
2550
2592
  },
2551
- nearestJoint(objectId, point, maxDistance = 0.3) {
2593
+ nearestJoint(objectId, point, maxDistance = 0.3, tool) {
2552
2594
  const im = impls.get(objectId);
2595
+ const cands = [];
2553
2596
  if (im) {
2554
- let best2 = null;
2555
- let bd2 = maxDistance;
2597
+ const spec = im.impl.describe();
2556
2598
  for (const js of im.impl.state().joints) {
2557
2599
  if (js.open) continue;
2558
2600
  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
- }
2601
+ const j = (spec.joints ?? []).find((x) => (x.id ?? `${x.a}~${x.b}`) === js.id);
2602
+ cands.push({ st: js, d: pos ? vec.distance(pos, point) : Infinity, ok: !!j && (j.tools ?? JOINT_DEFAULTS[j.kind].tools).includes(tool) });
2564
2603
  }
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;
2604
+ } else {
2605
+ const o = getObj(objectId);
2606
+ for (const j of o.joints) {
2607
+ if (j.st.open) continue;
2608
+ cands.push({ st: j.st, d: vec.distance(jointWorld(o, j), point), ok: tool ? j.spec.tools.includes(tool) : true });
2576
2609
  }
2577
2610
  }
2578
- return best;
2611
+ const pick = (only) => {
2612
+ let best = null;
2613
+ let bd = maxDistance;
2614
+ for (const c of cands) {
2615
+ if (only && !c.ok) continue;
2616
+ if (c.d <= bd) {
2617
+ bd = c.d;
2618
+ best = c.st;
2619
+ }
2620
+ }
2621
+ return best;
2622
+ };
2623
+ return (tool ? pick(true) : null) ?? pick(false);
2579
2624
  },
2580
2625
  extinguish(objectId, partId, coolC = 200) {
2581
2626
  const im = impls.get(objectId);
@@ -2618,6 +2663,10 @@ var createDestruction = (options = {}) => {
2618
2663
  }).events;
2619
2664
  },
2620
2665
  state: (id) => objects.get(id)?.st ?? impls.get(id)?.impl.state() ?? null,
2666
+ snapshot: (id) => {
2667
+ const st = objects.get(id)?.st ?? impls.get(id)?.impl.state();
2668
+ return st ? clone(st) : null;
2669
+ },
2621
2670
  part: (id, partId) => objects.get(id)?.byId.get(partId)?.st ?? impls.get(id)?.impl.state().parts.find((p) => p.id === partId) ?? null,
2622
2671
  reset(id) {
2623
2672
  const im = impls.get(id);