@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.d.ts +19 -2
- package/dist/index.global.js +100 -48
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +100 -48
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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:
|
|
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.
|
|
1587
|
-
|
|
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.
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
|
|
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 &&
|
|
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.
|
|
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
|
|
2111
|
-
const
|
|
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.
|
|
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(
|
|
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
|
-
|
|
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
|
|
2560
|
-
|
|
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
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
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
|
-
|
|
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);
|