@adatechnology/game-destruction 0.4.2 → 0.6.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/README.md +33 -4
- package/dist/index.d.ts +77 -6
- package/dist/index.global.js +508 -23
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +508 -23
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/dist/index.global.js
CHANGED
|
@@ -21,17 +21,21 @@ var GameDestruction = (() => {
|
|
|
21
21
|
// src/index.ts
|
|
22
22
|
var index_exports = {};
|
|
23
23
|
__export(index_exports, {
|
|
24
|
+
ASSEMBLIES: () => ASSEMBLIES,
|
|
24
25
|
FIRE_PRIME_C: () => FIRE_PRIME_C,
|
|
25
26
|
IMPACT_CONTRACT: () => IMPACT_CONTRACT,
|
|
26
27
|
JOINT_BY_MATERIALS: () => JOINT_BY_MATERIALS,
|
|
27
28
|
JOINT_DEFAULTS: () => JOINT_DEFAULTS,
|
|
28
29
|
MATERIALS: () => MATERIALS,
|
|
30
|
+
assemblyDepth: () => assemblyDepth,
|
|
31
|
+
assemblyPart: () => assemblyPart,
|
|
29
32
|
buildFrame: () => buildFrame,
|
|
30
33
|
checkDestructible: () => checkDestructible2,
|
|
31
34
|
checkHit: () => checkHit,
|
|
32
35
|
createDestruction: () => createDestruction,
|
|
33
36
|
explosivePart: () => explosivePart,
|
|
34
37
|
fragility: () => fragility,
|
|
38
|
+
getAssembly: () => getAssembly,
|
|
35
39
|
guessMaterial: () => guessMaterial,
|
|
36
40
|
inferJoints: () => inferJoints,
|
|
37
41
|
locales: () => locales,
|
|
@@ -41,6 +45,7 @@ var GameDestruction = (() => {
|
|
|
41
45
|
partVolume: () => partVolume,
|
|
42
46
|
quat: () => quat,
|
|
43
47
|
quatFromEuler: () => quatFromEuler,
|
|
48
|
+
registerAssembly: () => registerAssembly,
|
|
44
49
|
resolveStrings: () => resolveStrings,
|
|
45
50
|
rotateVec: () => rotateVec,
|
|
46
51
|
sizeStructure: () => sizeStructure,
|
|
@@ -470,6 +475,153 @@ var GameDestruction = (() => {
|
|
|
470
475
|
return out;
|
|
471
476
|
};
|
|
472
477
|
|
|
478
|
+
// src/breach.ts
|
|
479
|
+
var planeAxes = (thin) => thin === 1 ? [0, 2] : [thin === 0 ? 2 : 0, 1];
|
|
480
|
+
var unitGrid = (dims, thin, units, cap) => {
|
|
481
|
+
const [u, v] = planeAxes(thin);
|
|
482
|
+
let unitU = Math.min(units.size[0], dims[u]), unitV = Math.min(units.size[1], dims[v]);
|
|
483
|
+
const rowsFor = (h) => Math.ceil(dims[v] / h - 1e-9);
|
|
484
|
+
const colsFor = (w) => Math.ceil(dims[u] / w - 1e-9) + (units.bond === "running" ? 1 : 0);
|
|
485
|
+
const merge = Math.ceil(Math.sqrt(rowsFor(unitV) * colsFor(unitU) / cap));
|
|
486
|
+
if (merge > 1) {
|
|
487
|
+
unitU = Math.min(unitU * merge, dims[u]);
|
|
488
|
+
unitV = Math.min(unitV * merge, dims[v]);
|
|
489
|
+
}
|
|
490
|
+
const cells = [];
|
|
491
|
+
for (let row = 0; row < rowsFor(unitV); row++) {
|
|
492
|
+
const v0 = -dims[v] / 2 + row * unitV, v1 = Math.min(v0 + unitV, dims[v] / 2);
|
|
493
|
+
const shift = units.bond === "running" && row % 2 === 1 ? unitU / 2 : 0;
|
|
494
|
+
for (let col = 0; col < colsFor(unitU); col++) {
|
|
495
|
+
const u0 = Math.max(-dims[u] / 2 + col * unitU - shift, -dims[u] / 2), u1 = Math.min(-dims[u] / 2 + (col + 1) * unitU - shift, dims[u] / 2);
|
|
496
|
+
if (u1 - u0 < 0.01) continue;
|
|
497
|
+
const center = [0, 0, 0], size = [0, 0, 0];
|
|
498
|
+
center[u] = (u0 + u1) / 2;
|
|
499
|
+
center[v] = (v0 + v1) / 2;
|
|
500
|
+
size[u] = u1 - u0;
|
|
501
|
+
size[v] = v1 - v0;
|
|
502
|
+
size[thin] = dims[thin];
|
|
503
|
+
cells.push({ center, size });
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return cells;
|
|
507
|
+
};
|
|
508
|
+
var planeDistance = (cell, thin, point) => {
|
|
509
|
+
const [u, v] = planeAxes(thin);
|
|
510
|
+
const du = cell.center[u] - point[u], dv = cell.center[v] - point[v];
|
|
511
|
+
return Math.sqrt(du * du + dv * dv);
|
|
512
|
+
};
|
|
513
|
+
var cellsWithin = (cells, thin, point, radius) => {
|
|
514
|
+
const inside = cells.filter((c) => planeDistance(c, thin, point) <= radius);
|
|
515
|
+
if (inside.length > 0 || cells.length === 0) return inside;
|
|
516
|
+
return [cells.reduce((best, c) => planeDistance(c, thin, point) < planeDistance(best, thin, point) ? c : best)];
|
|
517
|
+
};
|
|
518
|
+
var cellsOutside = (cells, thin, point, radius) => cells.filter((c) => planeDistance(c, thin, point) > radius);
|
|
519
|
+
var MAX_MARKS_PER_FRAGMENT = 12;
|
|
520
|
+
var marksInBox = (center, size, rotation, holes) => {
|
|
521
|
+
const inverse = rotation ? quat.conjugate(rotation) : void 0;
|
|
522
|
+
const marks = [];
|
|
523
|
+
for (const hole of holes) {
|
|
524
|
+
const offset = [hole.point[0] - center[0], hole.point[1] - center[1], hole.point[2] - center[2]];
|
|
525
|
+
const point = inverse ? rotateVec(inverse, offset) : offset;
|
|
526
|
+
const inside = point.every((value, k) => Math.abs(value) <= size[k] / 2 + hole.diameter / 2);
|
|
527
|
+
if (!inside) continue;
|
|
528
|
+
marks.push({ point, normal: inverse ? rotateVec(inverse, hole.normal) : hole.normal, diameter: hole.diameter });
|
|
529
|
+
if (marks.length >= MAX_MARKS_PER_FRAGMENT) break;
|
|
530
|
+
}
|
|
531
|
+
return marks;
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
// src/layers.ts
|
|
535
|
+
var MAX_SPALLS = 64;
|
|
536
|
+
var thinAxisOf = (dims) => dims[0] <= dims[1] && dims[0] <= dims[2] ? 0 : dims[1] <= dims[2] ? 1 : 2;
|
|
537
|
+
var resolveLayer = (def, index, mat) => ({ index, def, mat, energy: mat.penetration * def.thickness * 1e3 });
|
|
538
|
+
var onFace = (layer, face) => layer.def.faces === "both" || layer.def.faces === (face > 0 ? "+" : "-");
|
|
539
|
+
var isSpalled = (spalls, layer, face, point) => {
|
|
540
|
+
for (const s of spalls ?? []) {
|
|
541
|
+
if (s[0] !== layer || s[1] !== face) continue;
|
|
542
|
+
const dx = s[2] - point[0], dy = s[3] - point[1], dz = s[4] - point[2];
|
|
543
|
+
if (dx * dx + dy * dy + dz * dz <= s[5] * s[5]) return true;
|
|
544
|
+
}
|
|
545
|
+
return false;
|
|
546
|
+
};
|
|
547
|
+
var outerLayer = (layers, spalls, face, point) => layers.find((l) => onFace(l, face) && !isSpalled(spalls, l.index, face, point));
|
|
548
|
+
var layersEnergy = (layers, spalls, face, point) => {
|
|
549
|
+
let sum = 0;
|
|
550
|
+
for (const l of layers) if (onFace(l, face) && !isSpalled(spalls, l.index, face, point)) sum += l.energy;
|
|
551
|
+
return sum;
|
|
552
|
+
};
|
|
553
|
+
var layersThickness = (layers, face) => {
|
|
554
|
+
let sum = 0;
|
|
555
|
+
for (const l of layers) if (onFace(l, face)) sum += l.def.thickness;
|
|
556
|
+
return sum;
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
// src/rebar.ts
|
|
560
|
+
var COVER = 0.03;
|
|
561
|
+
var MAX_BARS_PER_DIRECTION = 24;
|
|
562
|
+
var MAX_MEMBER_BARS_PER_SIDE = 6;
|
|
563
|
+
var STEEL_DENSITY = 7850;
|
|
564
|
+
var spread = (length, cover, spacing, cap) => {
|
|
565
|
+
const room = Math.max(0, length - 2 * cover);
|
|
566
|
+
const count = Math.min(cap, Math.max(2, Math.floor(room / spacing) + 1));
|
|
567
|
+
const step = count > 1 && room / (count - 1) < spacing ? room / (count - 1) : spacing;
|
|
568
|
+
return Array.from({ length: count }, (_, i) => (i - (count - 1) / 2) * step);
|
|
569
|
+
};
|
|
570
|
+
var bar = (axis, along, fixed) => {
|
|
571
|
+
const a = [0, 0, 0], b = [0, 0, 0];
|
|
572
|
+
for (const [index, value] of fixed) {
|
|
573
|
+
a[index] = value;
|
|
574
|
+
b[index] = value;
|
|
575
|
+
}
|
|
576
|
+
a[axis] = -along;
|
|
577
|
+
b[axis] = along;
|
|
578
|
+
return [a, b];
|
|
579
|
+
};
|
|
580
|
+
var isMember = (dims) => {
|
|
581
|
+
const [a = 0, b = 0, c = 0] = [...dims].sort((x, y) => x - y);
|
|
582
|
+
return b / a < 3 && c / b >= 3;
|
|
583
|
+
};
|
|
584
|
+
var rebarBars = (dims, thin, spacing) => {
|
|
585
|
+
const out = [];
|
|
586
|
+
if (isMember(dims)) {
|
|
587
|
+
const long = dims.indexOf(Math.max(...dims));
|
|
588
|
+
const [u2 = 0, v2 = 0] = [0, 1, 2].filter((i) => i !== long);
|
|
589
|
+
const us = spread(dims[u2], COVER, spacing, MAX_MEMBER_BARS_PER_SIDE);
|
|
590
|
+
const vs = spread(dims[v2], COVER, spacing, MAX_MEMBER_BARS_PER_SIDE);
|
|
591
|
+
us.forEach((x, i) => vs.forEach((y, j) => {
|
|
592
|
+
if (i === 0 || j === 0 || i === us.length - 1 || j === vs.length - 1) out.push(bar(long, dims[long] / 2 - 0.02, [[u2, x], [v2, y]]));
|
|
593
|
+
}));
|
|
594
|
+
return out;
|
|
595
|
+
}
|
|
596
|
+
const [u = 0, v = 0] = [0, 1, 2].filter((i) => i !== thin);
|
|
597
|
+
for (const y of spread(dims[v], COVER, spacing, MAX_BARS_PER_DIRECTION)) out.push(bar(u, dims[u] / 2 - 0.02, [[v, y], [thin, 0]]));
|
|
598
|
+
for (const x of spread(dims[u], COVER, spacing, MAX_BARS_PER_DIRECTION)) out.push(bar(v, dims[v] / 2 - 0.02, [[u, x], [thin, 0]]));
|
|
599
|
+
return out;
|
|
600
|
+
};
|
|
601
|
+
var clipBarsToDisc = (bars, u, v, center, radius) => {
|
|
602
|
+
const out = [];
|
|
603
|
+
for (const [a, b] of bars) {
|
|
604
|
+
const axis = a[u] !== b[u] ? u : v;
|
|
605
|
+
const other = axis === u ? v : u;
|
|
606
|
+
const off = a[other] - center[other === u ? 0 : 1];
|
|
607
|
+
if (Math.abs(off) >= radius) continue;
|
|
608
|
+
const half = Math.sqrt(radius * radius - off * off);
|
|
609
|
+
const mid = center[axis === u ? 0 : 1];
|
|
610
|
+
const lo = Math.max(Math.min(a[axis], b[axis]), mid - half), hi = Math.min(Math.max(a[axis], b[axis]), mid + half);
|
|
611
|
+
if (hi - lo < 1e-6) continue;
|
|
612
|
+
const p = [a[0], a[1], a[2]], q = [b[0], b[1], b[2]];
|
|
613
|
+
p[axis] = lo;
|
|
614
|
+
q[axis] = hi;
|
|
615
|
+
out.push([p, q]);
|
|
616
|
+
}
|
|
617
|
+
return out;
|
|
618
|
+
};
|
|
619
|
+
var rebarMass = (bars, diameter, density = STEEL_DENSITY) => {
|
|
620
|
+
let length = 0;
|
|
621
|
+
for (const [a, b] of bars) length += Math.abs(a[0] - b[0]) + Math.abs(a[1] - b[1]) + Math.abs(a[2] - b[2]);
|
|
622
|
+
return Math.PI * (diameter / 2) * (diameter / 2) * length * density;
|
|
623
|
+
};
|
|
624
|
+
|
|
473
625
|
// src/data/materials.json
|
|
474
626
|
var materials_default = [
|
|
475
627
|
{
|
|
@@ -1478,9 +1630,19 @@ var GameDestruction = (() => {
|
|
|
1478
1630
|
var DMG_PER_J = 0.02;
|
|
1479
1631
|
var BLAST_COUPLING = 0.1;
|
|
1480
1632
|
var FLAMMABLE_FLUIDS = ["gas", "fuel"];
|
|
1633
|
+
var BREACH_RADIUS = 0.55;
|
|
1634
|
+
var BREACH_BLAST_FACTOR = 6;
|
|
1635
|
+
var BREACH_AREA = 0.25;
|
|
1636
|
+
var RESIDUAL_CAPACITY = 0.15;
|
|
1637
|
+
var UNIT_FRAGMENT_FACTOR = 4;
|
|
1638
|
+
var BREACH_GRID_CAP = 4096;
|
|
1481
1639
|
var CUT_SLICE = 1;
|
|
1482
1640
|
var cutFactorOf = (shape) => clamp2(Math.max(...shapeDims(shape)) / CUT_SLICE, 1, 50);
|
|
1483
1641
|
var clamp2 = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
1642
|
+
var capacityScale = (p) => {
|
|
1643
|
+
if (p.st.stripped) return p.spec.reinforcement?.residualCapacity ?? RESIDUAL_CAPACITY;
|
|
1644
|
+
return p.rt.removed ? Math.max(0.05, 1 - p.rt.removed / p.mass0) : 1;
|
|
1645
|
+
};
|
|
1484
1646
|
var isLost = (p) => p.st.status === "broken" || p.st.status === "melted" || p.st.status === "ashes";
|
|
1485
1647
|
var clone = (v) => structuredClone(v);
|
|
1486
1648
|
var overloadRate = (stress) => Math.min(0.6, 0.12 + 0.8 * (stress - 1));
|
|
@@ -1503,6 +1665,8 @@ var GameDestruction = (() => {
|
|
|
1503
1665
|
const maxDents = options.maxDents ?? 24;
|
|
1504
1666
|
const maxHoles = options.maxHoles ?? 32;
|
|
1505
1667
|
const fireSpread = options.fireSpread ?? 0.6;
|
|
1668
|
+
const breachRadius = options.breach?.radius ?? BREACH_RADIUS;
|
|
1669
|
+
const breachBlastFactor = options.breach?.blastFactor ?? BREACH_BLAST_FACTOR;
|
|
1506
1670
|
const rng = createRng(options.seed ?? 1);
|
|
1507
1671
|
const rnd = () => rng.next();
|
|
1508
1672
|
let time = 0;
|
|
@@ -1511,6 +1675,8 @@ var GameDestruction = (() => {
|
|
|
1511
1675
|
const listeners = /* @__PURE__ */ new Map();
|
|
1512
1676
|
let cur = [];
|
|
1513
1677
|
const blasts = [];
|
|
1678
|
+
const frontQueue = [];
|
|
1679
|
+
const blastFrontSpeed = options.blastFrontSpeed ?? 0;
|
|
1514
1680
|
const primed = /* @__PURE__ */ new Map();
|
|
1515
1681
|
let orderCounter = 0;
|
|
1516
1682
|
const impls = /* @__PURE__ */ new Map();
|
|
@@ -1607,6 +1773,13 @@ var GameDestruction = (() => {
|
|
|
1607
1773
|
const maxHp = Math.max(1, ps.hp ?? mass0 * mat.hpPerKg);
|
|
1608
1774
|
const fuel0 = mat.thermal.ignitionC !== void 0 ? mass0 * (mat.thermal.fuelFraction ?? 0) : 0;
|
|
1609
1775
|
const c = carry?.parts.get(ps.id);
|
|
1776
|
+
const staged = !!(ps.layers?.length || ps.reinforcement || ps.units || ps.breach);
|
|
1777
|
+
const layers = (ps.layers ?? []).map((l, i) => {
|
|
1778
|
+
const lm = materials.get(l.material);
|
|
1779
|
+
if (!lm) throw new Error(`unknown material "${l.material}" on layer ${i} of part "${ps.id}"`);
|
|
1780
|
+
return resolveLayer(l, i, lm);
|
|
1781
|
+
});
|
|
1782
|
+
if (ps.reinforcement && !materials.has(ps.reinforcement.material)) throw new Error(`unknown material "${ps.reinforcement.material}" on the reinforcement of part "${ps.id}"`);
|
|
1610
1783
|
const pst = c?.st ?? {
|
|
1611
1784
|
id: ps.id,
|
|
1612
1785
|
material: ps.material,
|
|
@@ -1622,7 +1795,8 @@ var GameDestruction = (() => {
|
|
|
1622
1795
|
dents: [],
|
|
1623
1796
|
holes: [],
|
|
1624
1797
|
cracks: [],
|
|
1625
|
-
group: 0
|
|
1798
|
+
group: 0,
|
|
1799
|
+
...staged ? { stage: 0 } : {}
|
|
1626
1800
|
};
|
|
1627
1801
|
if (!c && ps.contains) pst.liters = ps.contains.liters;
|
|
1628
1802
|
const p = {
|
|
@@ -1641,7 +1815,10 @@ var GameDestruction = (() => {
|
|
|
1641
1815
|
rt: c?.rt ?? { ...freshRuntime(), prevTemp: pst.temperature },
|
|
1642
1816
|
structural: !!ps.structure && ps.structure.role !== "none",
|
|
1643
1817
|
anchored: !!ps.anchored || ps.structure?.role === "foundation",
|
|
1644
|
-
cap: ps.structure ? partCapacity(ps, mat) : 0
|
|
1818
|
+
cap: ps.structure ? partCapacity(ps, mat) : 0,
|
|
1819
|
+
staged,
|
|
1820
|
+
thin: thinAxisOf(shapeDims(ps.shape)),
|
|
1821
|
+
layers
|
|
1645
1822
|
};
|
|
1646
1823
|
if (p.structural) o.hasStructure = true;
|
|
1647
1824
|
if (p.rt.chainDue !== void 0 && !isLost(p)) primed.set(p, o);
|
|
@@ -1781,7 +1958,7 @@ var GameDestruction = (() => {
|
|
|
1781
1958
|
o.unsup[i] = o.anyAnchor && !isLost(p);
|
|
1782
1959
|
continue;
|
|
1783
1960
|
}
|
|
1784
|
-
p.st.stress = p.st.load / p.cap;
|
|
1961
|
+
p.st.stress = p.st.load / (p.cap * capacityScale(p));
|
|
1785
1962
|
if (p.st.stress > o.maxStress) o.maxStress = p.st.stress;
|
|
1786
1963
|
}
|
|
1787
1964
|
if (o.maxStress > 0.8) o.active = true;
|
|
@@ -1800,6 +1977,7 @@ var GameDestruction = (() => {
|
|
|
1800
1977
|
for (const p of ps) {
|
|
1801
1978
|
o.collapsed.add(p.spec.id);
|
|
1802
1979
|
p.st.status = "collapsed";
|
|
1980
|
+
setStage(o, p, 3, {}, src);
|
|
1803
1981
|
}
|
|
1804
1982
|
emit({ ...base(o, src), type: "collapse", parts: ps.map((p) => p.spec.id), mass: ps.reduce((sum, p) => sum + p.st.mass, 0) });
|
|
1805
1983
|
}
|
|
@@ -1853,6 +2031,7 @@ var GameDestruction = (() => {
|
|
|
1853
2031
|
if (isLost(p) || ignore?.has(`${o.id}\0${p.spec.id}`)) continue;
|
|
1854
2032
|
const r = rayPart(o, p, origin, d);
|
|
1855
2033
|
if (!r || r.t0 < 0 || r.t0 > maxDist) continue;
|
|
2034
|
+
if (p.st.opening && insideOpening(o, p, vec.madd(origin, d, r.t0))) continue;
|
|
1856
2035
|
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 };
|
|
1857
2036
|
}
|
|
1858
2037
|
}
|
|
@@ -1961,6 +2140,7 @@ var GameDestruction = (() => {
|
|
|
1961
2140
|
if (p.st.holes.length > maxHoles) p.st.holes.shift();
|
|
1962
2141
|
markDamaged(p);
|
|
1963
2142
|
emit({ ...base(o, src), type: "hole", part: p.spec.id, hole, worldPoint: wp, ...exit ? { exitPoint: exit } : {} });
|
|
2143
|
+
setStage(o, p, 1, {}, src);
|
|
1964
2144
|
return hole;
|
|
1965
2145
|
};
|
|
1966
2146
|
const addCrack = (o, p, wp, wn, length, src) => {
|
|
@@ -1969,6 +2149,7 @@ var GameDestruction = (() => {
|
|
|
1969
2149
|
if (p.st.cracks.length > 16) p.st.cracks.shift();
|
|
1970
2150
|
markDamaged(p);
|
|
1971
2151
|
emit({ ...base(o, src), type: "crack", part: p.spec.id, crack, worldPoint: wp });
|
|
2152
|
+
setStage(o, p, 1, {}, src);
|
|
1972
2153
|
};
|
|
1973
2154
|
const debris = (o, p, kind, point, normal, count, speed, src) => emit({ ...base(o, src), type: "debris", part: p.spec.id, kind, point, normal, count, speed });
|
|
1974
2155
|
const holeRate = (hole, pressurized) => (pressurized ? 0.5 : 0.1) * (hole.diameter / 9e-3);
|
|
@@ -2017,6 +2198,7 @@ var GameDestruction = (() => {
|
|
|
2017
2198
|
if (split && mine.length) amount /= mine.length;
|
|
2018
2199
|
for (const j of mine) {
|
|
2019
2200
|
if (j.st.open) continue;
|
|
2201
|
+
if (j.spec.kind === "rebar" && (o.parts[j.ia].spec.reinforcement || o.parts[j.ib].spec.reinforcement)) continue;
|
|
2020
2202
|
if (near && jointReach(o, j, near) > Math.max(radius, jointReachLimit(o, j))) continue;
|
|
2021
2203
|
j.st.hp -= amount;
|
|
2022
2204
|
if (j.st.hp <= 0) {
|
|
@@ -2030,23 +2212,172 @@ var GameDestruction = (() => {
|
|
|
2030
2212
|
}
|
|
2031
2213
|
}
|
|
2032
2214
|
};
|
|
2215
|
+
const setStage = (o, p, stage, extra = {}, src) => {
|
|
2216
|
+
if (!p.staged || (p.st.stage ?? 0) >= stage) return;
|
|
2217
|
+
p.st.stage = stage;
|
|
2218
|
+
emit({ ...base(o, src), type: "stage", part: p.spec.id, stage, ...extra });
|
|
2219
|
+
};
|
|
2220
|
+
const faceOf = (o, p, worldNormal) => rotateVec(quat.conjugate(partXf(o, p).q), worldNormal)[p.thin] >= 0 ? 1 : -1;
|
|
2221
|
+
const spallOuter = (o, p, wp, face, normal, radius, src) => {
|
|
2222
|
+
var _a;
|
|
2223
|
+
if (p.layers.length === 0) return;
|
|
2224
|
+
const lp = objLocal(o, wp);
|
|
2225
|
+
const layer = outerLayer(p.layers, p.rt.spalls, face, lp);
|
|
2226
|
+
if (!layer) return;
|
|
2227
|
+
const spalls = (_a = p.rt).spalls ?? (_a.spalls = []);
|
|
2228
|
+
spalls.push([layer.index, face, lp[0], lp[1], lp[2], radius]);
|
|
2229
|
+
if (spalls.length > MAX_SPALLS) spalls.shift();
|
|
2230
|
+
markDamaged(p);
|
|
2231
|
+
emit({ ...base(o, src), type: "spall", part: p.spec.id, layer: layer.index, point: wp, area: Math.PI * radius * radius });
|
|
2232
|
+
debris(o, p, layer.mat.hitDebris, wp, normal, 5, 2, src);
|
|
2233
|
+
setStage(o, p, 1, {}, src);
|
|
2234
|
+
};
|
|
2235
|
+
const insideOpening = (o, p, objectPoint) => {
|
|
2236
|
+
const op = p.st.opening;
|
|
2237
|
+
if (!op) return false;
|
|
2238
|
+
const [u, v] = planeAxes(p.thin);
|
|
2239
|
+
const lp = toLocal(p.pos, p.q, objectPoint);
|
|
2240
|
+
const lc = toLocal(p.pos, p.q, op.center);
|
|
2241
|
+
return Math.hypot(lp[u] - lc[u], lp[v] - lc[v]) <= op.radius;
|
|
2242
|
+
};
|
|
2243
|
+
const breachAreaOf = (p, damage, blast) => damage / (p.mat.hpPerKg * p.mat.density) / Math.max(p.thickness, 5e-3) * (blast ? breachBlastFactor : 1);
|
|
2244
|
+
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 }));
|
|
2245
|
+
const unitsOf = (p) => p.spec.units ?? { size: [p.mat.fragmentSize?.[1] ?? 0.2, p.mat.fragmentSize?.[1] ?? 0.2, p.thickness], bond: "stack" };
|
|
2246
|
+
const FRAGMENT_SHAPE = { shatter: "shard", splinter: "splinter", tear: "scrap" };
|
|
2247
|
+
const cellFragments = (o, p, cells, hitW, dir, speed, mass, tag) => {
|
|
2248
|
+
const { c, q } = partXf(o, p);
|
|
2249
|
+
const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
|
|
2250
|
+
const [u, v] = planeAxes(p.thin);
|
|
2251
|
+
const faceArea = (cell) => cell.size[u] * cell.size[v];
|
|
2252
|
+
const total = cells.reduce((sum, cell) => sum + faceArea(cell), 0) || 1;
|
|
2253
|
+
const shape = p.spec.units ? "chunk" : FRAGMENT_SHAPE[p.mat.fracture] ?? "chunk";
|
|
2254
|
+
return cells.map((cell, i) => {
|
|
2255
|
+
const cw = toWorld(c, q, cell.center);
|
|
2256
|
+
const out = vec.normalize(vec.sub(cw, hitW), [0, 1, 0]);
|
|
2257
|
+
const dv = dir ? vec.normalize(vec.madd(out, dir, 0.6)) : out;
|
|
2258
|
+
const k = speed * (0.6 + 0.8 * rnd());
|
|
2259
|
+
const marks = marksInBox(cell.center, cell.size, void 0, holes);
|
|
2260
|
+
return {
|
|
2261
|
+
id: `${p.spec.id}#${tag}${i}`,
|
|
2262
|
+
part: p.spec.id,
|
|
2263
|
+
material: p.spec.material,
|
|
2264
|
+
shape,
|
|
2265
|
+
center: cw,
|
|
2266
|
+
size: [cell.size[0], cell.size[1], cell.size[2]],
|
|
2267
|
+
rotation: q,
|
|
2268
|
+
velocity: [dv[0] * k, dv[1] * k + 0.5 * rnd(), dv[2] * k],
|
|
2269
|
+
angularVelocity: [(rnd() - 0.5) * 8, (rnd() - 0.5) * 8, (rnd() - 0.5) * 8],
|
|
2270
|
+
mass: mass * faceArea(cell) / total,
|
|
2271
|
+
...marks.length > 0 ? { marks } : {}
|
|
2272
|
+
};
|
|
2273
|
+
});
|
|
2274
|
+
};
|
|
2275
|
+
const worldBars = (o, p, bars) => {
|
|
2276
|
+
const { c, q } = partXf(o, p);
|
|
2277
|
+
return bars.map(([a, b]) => [toWorld(c, q, a), toWorld(c, q, b)]);
|
|
2278
|
+
};
|
|
2279
|
+
const addBreach = (o, p, wp, area, energy, dir, src) => {
|
|
2280
|
+
var _a;
|
|
2281
|
+
if (!p.staged || area <= 0 || (p.st.stage ?? 0) >= 2 || p.st.stripped || isLost(p)) return;
|
|
2282
|
+
const lp = objLocal(o, wp);
|
|
2283
|
+
const list = (_a = p.rt).breach ?? (_a.breach = []);
|
|
2284
|
+
list.push([lp[0], lp[1], lp[2], area]);
|
|
2285
|
+
if (list.length > MAX_SPALLS) list.shift();
|
|
2286
|
+
let total = 0;
|
|
2287
|
+
const sum = [0, 0, 0];
|
|
2288
|
+
for (const [x, y, z, a] of list) {
|
|
2289
|
+
if (vec.distance([x, y, z], lp) > breachRadius) continue;
|
|
2290
|
+
total += a;
|
|
2291
|
+
sum[0] += x * a;
|
|
2292
|
+
sum[1] += y * a;
|
|
2293
|
+
sum[2] += z * a;
|
|
2294
|
+
}
|
|
2295
|
+
if (total < (p.spec.breach?.area ?? BREACH_AREA)) return;
|
|
2296
|
+
openBreach(o, p, [sum[0] / total, sum[1] / total, sum[2] / total], total, energy, dir, src);
|
|
2297
|
+
};
|
|
2298
|
+
const openBreach = (o, p, center, total, energy, dir, src) => {
|
|
2299
|
+
const [u, v] = planeAxes(p.thin);
|
|
2300
|
+
const radius = clamp2(Math.sqrt(total / Math.PI), Math.sqrt((p.spec.breach?.area ?? BREACH_AREA) / Math.PI), breachRadius);
|
|
2301
|
+
const lc = toLocal(p.pos, p.q, center);
|
|
2302
|
+
lc[p.thin] = 0;
|
|
2303
|
+
const { c, q } = partXf(o, p);
|
|
2304
|
+
const worldCenter = toWorld(c, q, lc);
|
|
2305
|
+
if (2 * radius >= Math.min(p.dims[u], p.dims[v])) {
|
|
2306
|
+
failPart(o, p, worldCenter, dir, energy, 12, src);
|
|
2307
|
+
return;
|
|
2308
|
+
}
|
|
2309
|
+
const cells = cellsWithin(unitGrid(p.dims, p.thin, unitsOf(p), BREACH_GRID_CAP), p.thin, lc, radius);
|
|
2310
|
+
const removed = p.st.mass * cells.reduce((sum, cell) => sum + cell.size[u] * cell.size[v], 0) / (p.dims[u] * p.dims[v]);
|
|
2311
|
+
const speed = clamp2(Math.sqrt(2 * energy * 0.1 / Math.max(removed, 0.05)), 0.5, 8);
|
|
2312
|
+
const fragments = cellFragments(o, p, cells, worldCenter, dir, speed, removed, "b");
|
|
2313
|
+
const holes = localHolesOf(p);
|
|
2314
|
+
p.st.holes = p.st.holes.filter((_, i) => !cells.some((cell) => marksInBox(cell.center, cell.size, void 0, [holes[i]]).length > 0));
|
|
2315
|
+
p.st.mass = Math.max(0, p.st.mass - removed);
|
|
2316
|
+
p.rt.removed = (p.rt.removed ?? 0) + removed;
|
|
2317
|
+
p.st.opening = { center: objLocal(o, worldCenter), radius };
|
|
2318
|
+
markDamaged(p);
|
|
2319
|
+
setStage(o, p, 1, {}, src);
|
|
2320
|
+
setStage(o, p, 2, { opening: { center: worldCenter, radius }, fragments }, src);
|
|
2321
|
+
debris(o, p, p.mat.breakDebris, worldCenter, vec.scale(dir ?? [0, 1, 0], -1), 14, 3, src);
|
|
2322
|
+
const reinforcement = p.spec.reinforcement;
|
|
2323
|
+
if (reinforcement) {
|
|
2324
|
+
const bars = worldBars(o, p, clipBarsToDisc(rebarBars(p.dims, p.thin, reinforcement.spacing), u, v, [lc[u], lc[v]], radius + 0.05));
|
|
2325
|
+
if (bars.length > 0) emit({ ...base(o, src), type: "expose", part: p.spec.id, bars });
|
|
2326
|
+
}
|
|
2327
|
+
if (p.structural) solveStructure(o);
|
|
2328
|
+
};
|
|
2329
|
+
const stripPart = (o, p, reinforcement, src) => {
|
|
2330
|
+
const bars = rebarBars(p.dims, p.thin, reinforcement.spacing);
|
|
2331
|
+
p.st.stripped = true;
|
|
2332
|
+
p.st.hp = p.st.maxHp;
|
|
2333
|
+
p.st.mass = rebarMass(bars, reinforcement.diameter, materials.get(reinforcement.material)?.density);
|
|
2334
|
+
p.st.holes = [];
|
|
2335
|
+
p.st.burning = false;
|
|
2336
|
+
p.rt.jet = false;
|
|
2337
|
+
markDamaged(p);
|
|
2338
|
+
emit({ ...base(o, src), type: "expose", part: p.spec.id, bars: worldBars(o, p, bars) });
|
|
2339
|
+
solveStructure(o);
|
|
2340
|
+
collapseCheck(o, src);
|
|
2341
|
+
};
|
|
2342
|
+
const unitFragmentPlan = (p, cap) => {
|
|
2343
|
+
const grid = unitGrid(p.dims, p.thin, unitsOf(p), cap);
|
|
2344
|
+
const op = p.st.opening;
|
|
2345
|
+
const cells = op ? cellsOutside(grid, p.thin, toLocal(p.pos, p.q, op.center), op.radius) : grid;
|
|
2346
|
+
const [u, v] = planeAxes(p.thin);
|
|
2347
|
+
return cells.map((cell) => ({ center: cell.center, size: cell.size, q: quat.identity(), shape: "chunk", weight: cell.size[u] * cell.size[v] }));
|
|
2348
|
+
};
|
|
2349
|
+
const loseCage = (o, p, src) => {
|
|
2350
|
+
p.st.status = "broken";
|
|
2351
|
+
p.st.hp = 0;
|
|
2352
|
+
p.st.mass = 0;
|
|
2353
|
+
emit({ ...base(o, src), type: "expose", part: p.spec.id, bars: [] });
|
|
2354
|
+
debris(o, p, "sparks", partXf(o, p).c, [0, 1, 0], 6, 3, src);
|
|
2355
|
+
partLost(o, p, src);
|
|
2356
|
+
};
|
|
2033
2357
|
const failPart = (o, p, origin, dir, fragEnergy, cap, src) => {
|
|
2034
2358
|
if (isLost(p)) return;
|
|
2359
|
+
if (p.st.stripped) {
|
|
2360
|
+
loseCage(o, p, src);
|
|
2361
|
+
return;
|
|
2362
|
+
}
|
|
2035
2363
|
const ex = p.spec.explodes;
|
|
2036
2364
|
const style = p.mat.fracture === "none" ? "snap" : p.mat.fracture;
|
|
2037
2365
|
const { c, q } = partXf(o, p);
|
|
2038
2366
|
const hitW = origin ?? c;
|
|
2039
2367
|
const grain = p.spec.grain ? AXIS[p.spec.grain] : void 0;
|
|
2040
|
-
const
|
|
2368
|
+
const hitLocal = toLocal(c, q, hitW);
|
|
2369
|
+
const unitPlan = p.spec.units ? unitFragmentPlan(p, maxFragments * UNIT_FRAGMENT_FACTOR) : [];
|
|
2370
|
+
const plan = unitPlan.length > 0 ? unitPlan : planFragments({
|
|
2041
2371
|
style,
|
|
2042
2372
|
dims: p.dims,
|
|
2043
2373
|
thickness: p.thickness,
|
|
2044
2374
|
grain,
|
|
2045
|
-
hit:
|
|
2375
|
+
hit: hitLocal,
|
|
2046
2376
|
fragmentSize: p.mat.fragmentSize ?? [0.04, 0.2],
|
|
2047
2377
|
maxFragments,
|
|
2048
2378
|
rnd
|
|
2049
2379
|
});
|
|
2380
|
+
const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
|
|
2050
2381
|
const mass = p.st.mass;
|
|
2051
2382
|
const totW = plan.reduce((s, f) => s + f.weight, 0) || 1;
|
|
2052
2383
|
const energy = ex ? ex.energy * 0.2 : fragEnergy;
|
|
@@ -2056,6 +2387,7 @@ var GameDestruction = (() => {
|
|
|
2056
2387
|
const out = vec.normalize(vec.sub(cw, hitW), [0, 1, 0]);
|
|
2057
2388
|
const dv = dir ? vec.normalize(vec.madd(out, dir, 0.6)) : out;
|
|
2058
2389
|
const k = speed * (0.6 + 0.8 * rnd());
|
|
2390
|
+
const marks = marksInBox(f.center, f.size, f.q, holes);
|
|
2059
2391
|
return {
|
|
2060
2392
|
id: `${p.spec.id}#${i}`,
|
|
2061
2393
|
part: p.spec.id,
|
|
@@ -2066,18 +2398,27 @@ var GameDestruction = (() => {
|
|
|
2066
2398
|
rotation: quat.mul(q, f.q),
|
|
2067
2399
|
velocity: [dv[0] * k, dv[1] * k + 0.5 * rnd(), dv[2] * k],
|
|
2068
2400
|
angularVelocity: [(rnd() - 0.5) * 12, (rnd() - 0.5) * 12, (rnd() - 0.5) * 12],
|
|
2069
|
-
mass: mass * f.weight / totW
|
|
2401
|
+
mass: mass * f.weight / totW,
|
|
2402
|
+
...marks.length > 0 ? { marks } : {}
|
|
2070
2403
|
};
|
|
2071
2404
|
});
|
|
2072
|
-
p.
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2405
|
+
const reinforcement = p.spec.reinforcement;
|
|
2406
|
+
if (!reinforcement) {
|
|
2407
|
+
p.st.status = "broken";
|
|
2408
|
+
p.st.hp = 0;
|
|
2409
|
+
p.st.mass = 0;
|
|
2410
|
+
p.st.burning = false;
|
|
2411
|
+
p.rt.jet = false;
|
|
2412
|
+
}
|
|
2077
2413
|
emit({ ...base(o, src), type: "fracture", part: p.spec.id, style, fragments, point: hitW });
|
|
2078
2414
|
debris(o, p, p.mat.breakDebris, c, [0, 1, 0], clamp2(Math.round(Math.cbrt(partVolume(p.spec.shape)) * 30), 3, 20), 2, src);
|
|
2415
|
+
setStage(o, p, 3, {}, src);
|
|
2416
|
+
if (reinforcement) {
|
|
2417
|
+
stripPart(o, p, reinforcement, src);
|
|
2418
|
+
return;
|
|
2419
|
+
}
|
|
2079
2420
|
if (ex) {
|
|
2080
|
-
emit({ ...base(o, src), type: "explode", part: p.spec.id, point: c, energy: ex.energy, radius: ex.radius });
|
|
2421
|
+
emit({ ...base(o, src), type: "explode", part: p.spec.id, point: c, energy: ex.energy, radius: ex.radius, ...ex.effect !== void 0 ? { effect: ex.effect } : {} });
|
|
2081
2422
|
blasts.push({ point: c, energy: ex.energy, radius: ex.radius, source: src });
|
|
2082
2423
|
}
|
|
2083
2424
|
partLost(o, p, src);
|
|
@@ -2109,10 +2450,17 @@ var GameDestruction = (() => {
|
|
|
2109
2450
|
};
|
|
2110
2451
|
const ballistic = (o, p, entry, n, d, E, caliber, pathLen, src) => {
|
|
2111
2452
|
const mat = p.mat;
|
|
2453
|
+
if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry))) {
|
|
2454
|
+
return { penetrated: true, exitEnergy: E, exitPoint: vec.madd(entry, d, Math.min(pathLen, p.thickness)), damage: 0 };
|
|
2455
|
+
}
|
|
2112
2456
|
const sin = Math.min(1, Math.abs(vec.dot(d, n)));
|
|
2113
2457
|
const angle = Math.asin(sin) * 180 / Math.PI;
|
|
2114
|
-
const
|
|
2115
|
-
const
|
|
2458
|
+
const slant = Math.max(sin, 0.3);
|
|
2459
|
+
const effM = p.thickness / slant;
|
|
2460
|
+
const face = p.layers.length > 0 ? faceOf(o, p, n) : 1;
|
|
2461
|
+
const exitLocal = objLocal(o, vec.madd(entry, d, Math.min(pathLen, effM)));
|
|
2462
|
+
const coats = p.layers.length > 0 ? layersEnergy(p.layers, p.rt.spalls, face, objLocal(o, entry)) + layersEnergy(p.layers, p.rt.spalls, -face, exitLocal) : 0;
|
|
2463
|
+
const thr = coats > 0 ? (mat.penetration * p.thickness * 1e3 + coats) / slant : mat.penetration * effM * 1e3;
|
|
2116
2464
|
const penetrated = E > thr;
|
|
2117
2465
|
const scale = (mat.damageScale?.ballistic ?? 1) * (mat.fracture === "shatter" ? fragility(mat.ductility, p.thickness) : 1);
|
|
2118
2466
|
const id = p.spec.id;
|
|
@@ -2123,13 +2471,16 @@ var GameDestruction = (() => {
|
|
|
2123
2471
|
const dmg2 = applyDamage(o, p, E * DMG_PER_J * scale * 0.2, "ballistic", entry, d, E * 0.1, 12, src);
|
|
2124
2472
|
return { penetrated: false, exitEnergy: 0, damage: dmg2 };
|
|
2125
2473
|
}
|
|
2474
|
+
if (p.layers.length > 0) spallOuter(o, p, entry, face, n, Math.max(0.03, caliber / 1e3 * 4), src);
|
|
2126
2475
|
const brittle = mat.ductility < 0.3;
|
|
2127
2476
|
const ratio = E / Math.max(thr, 1e-6);
|
|
2128
2477
|
const crackLen = clamp2(0.03 + ratio * 0.2, 0.03, 0.8);
|
|
2129
2478
|
let exitPoint;
|
|
2130
2479
|
if (penetrated) {
|
|
2131
|
-
exitPoint = vec.madd(entry, d, Math.min(pathLen, effM));
|
|
2480
|
+
exitPoint = vec.madd(entry, d, Math.min(pathLen, effM + layersThickness(p.layers, face) / slant + layersThickness(p.layers, -face) / slant));
|
|
2132
2481
|
const hole = addHole(o, p, entry, n, caliber / 1e3 * (mat.ductility >= 0.3 ? 1 : 1.5), true, exitPoint, src);
|
|
2482
|
+
if (p.layers.length > 0) spallOuter(o, p, exitPoint, -face, vec.scale(n, -1), Math.max(0.03, caliber / 1e3 * 4), src);
|
|
2483
|
+
addBreach(o, p, entry, Math.PI * (hole.diameter / 2) * (hole.diameter / 2), E, d, src);
|
|
2133
2484
|
if (brittle && mat.fracture === "shatter") addCrack(o, p, entry, n, crackLen, src);
|
|
2134
2485
|
if (p.spec.contains && (p.st.liters ?? 0) > 0) {
|
|
2135
2486
|
emit({
|
|
@@ -2174,8 +2525,11 @@ var GameDestruction = (() => {
|
|
|
2174
2525
|
}
|
|
2175
2526
|
if (kind === "blunt") wearJoints(o, p, E * 0.3, void 0, 0, src);
|
|
2176
2527
|
else wearJoints(o, p, E * 0.3, point, 0.06, src);
|
|
2528
|
+
if (kind === "blunt" && E > 0 && p.layers.length > 0) spallOuter(o, p, point, faceOf(o, p, n), n, Math.max(contactR, 0.05), src);
|
|
2177
2529
|
const scale = mat.damageScale?.[kind] ?? 1;
|
|
2178
|
-
|
|
2530
|
+
const done = applyDamage(o, p, Ec * DMG_PER_J * scale * (kind === "slash" ? p.cutFactor : 1), kind, point, d, E * 0.1, 12, src);
|
|
2531
|
+
addBreach(o, p, point, breachAreaOf(p, done, false), E, d, src);
|
|
2532
|
+
return done;
|
|
2179
2533
|
};
|
|
2180
2534
|
const blastObject = (o, point, energy, radius, src) => {
|
|
2181
2535
|
const start = cur.length;
|
|
@@ -2211,7 +2565,15 @@ var GameDestruction = (() => {
|
|
|
2211
2565
|
}
|
|
2212
2566
|
}
|
|
2213
2567
|
wearJoints(o, p, Ein * 0.5, void 0, 0, src);
|
|
2214
|
-
|
|
2568
|
+
const amount = Ein * DMG_PER_J * (mat.damageScale?.explosive ?? 1);
|
|
2569
|
+
if (blastFrontSpeed > 0 && dist > 0 && mat.tags?.includes("glass")) {
|
|
2570
|
+
frontQueue.push({ o, p, due: time + dist / blastFrontSpeed, amount, fragEnergy: Ein * 0.3, point, ...src !== void 0 ? { src } : {} });
|
|
2571
|
+
continue;
|
|
2572
|
+
}
|
|
2573
|
+
if (Ein > 20 && p.layers.length > 0) spallOuter(o, p, wp, faceOf(o, p, wn), wn, clamp2(0.05 + Math.sqrt(Ein) * 2e-3, 0.05, 0.4), src);
|
|
2574
|
+
const dealt = applyDamage(o, p, amount, "explosive", point, null, Ein * 0.3, 40, src);
|
|
2575
|
+
res.damage += dealt;
|
|
2576
|
+
addBreach(o, p, wp, breachAreaOf(p, dealt, true), Ein, null, src);
|
|
2215
2577
|
}
|
|
2216
2578
|
res.events = cur.slice(start);
|
|
2217
2579
|
return res;
|
|
@@ -2482,7 +2844,7 @@ var GameDestruction = (() => {
|
|
|
2482
2844
|
}
|
|
2483
2845
|
o.active = hot;
|
|
2484
2846
|
};
|
|
2485
|
-
const
|
|
2847
|
+
const spread2 = (sources, dt) => {
|
|
2486
2848
|
for (const src of sources) {
|
|
2487
2849
|
if (!src.p.st.burning && !src.p.rt.jet) continue;
|
|
2488
2850
|
const sc = toWorld(src.o.st.position, src.o.st.rotation, src.p.pos);
|
|
@@ -2534,6 +2896,15 @@ var GameDestruction = (() => {
|
|
|
2534
2896
|
}
|
|
2535
2897
|
time = end;
|
|
2536
2898
|
};
|
|
2899
|
+
const releaseFront = () => {
|
|
2900
|
+
if (frontQueue.length === 0) return;
|
|
2901
|
+
frontQueue.sort((a, b) => a.due - b.due);
|
|
2902
|
+
while (frontQueue.length > 0 && frontQueue[0].due <= time) {
|
|
2903
|
+
const hit = frontQueue.shift();
|
|
2904
|
+
if (objects.get(hit.o.id) !== hit.o) continue;
|
|
2905
|
+
applyDamage(hit.o, hit.p, hit.amount, "explosive", hit.point, null, hit.fragEnergy, 40, hit.src);
|
|
2906
|
+
}
|
|
2907
|
+
};
|
|
2537
2908
|
const withCollapses = (r) => {
|
|
2538
2909
|
const last = r.value[r.value.length - 1];
|
|
2539
2910
|
if (!last) return r.value;
|
|
@@ -2783,8 +3154,9 @@ var GameDestruction = (() => {
|
|
|
2783
3154
|
time += dt;
|
|
2784
3155
|
const sources = [];
|
|
2785
3156
|
for (const o of [...objects.values()]) if (o.active) stepObject(o, dt, sources);
|
|
2786
|
-
if (sources.length)
|
|
3157
|
+
if (sources.length) spread2(sources, dt);
|
|
2787
3158
|
detonatePrimed(time);
|
|
3159
|
+
releaseFront();
|
|
2788
3160
|
for (const im of [...impls.values()]) {
|
|
2789
3161
|
if (!im.impl.update) continue;
|
|
2790
3162
|
for (const e of im.impl.update(dt) ?? []) emit(e.time === void 0 ? { ...e, time } : e);
|
|
@@ -2822,7 +3194,7 @@ var GameDestruction = (() => {
|
|
|
2822
3194
|
counter,
|
|
2823
3195
|
objects: [...objects.values()].map((o) => {
|
|
2824
3196
|
const runtime = { parts: {}, joints: {}, collapsed: [...o.collapsed] };
|
|
2825
|
-
for (const p of o.parts) runtime.parts[p.spec.id] =
|
|
3197
|
+
for (const p of o.parts) runtime.parts[p.spec.id] = clone(p.rt);
|
|
2826
3198
|
for (const j of o.joints) runtime.joints[j.spec.id] = j.step;
|
|
2827
3199
|
return { id: o.id, spec: clone(o.spec), state: clone(o.st), runtime };
|
|
2828
3200
|
}),
|
|
@@ -2839,7 +3211,7 @@ var GameDestruction = (() => {
|
|
|
2839
3211
|
const state = clone(e.state);
|
|
2840
3212
|
const carry = {
|
|
2841
3213
|
parts: new Map(
|
|
2842
|
-
state.parts.map((st) => [st.id, { st, rt: e.runtime?.parts[st.id] ?
|
|
3214
|
+
state.parts.map((st) => [st.id, { st, rt: e.runtime?.parts[st.id] ? clone(e.runtime.parts[st.id]) : { ...freshRuntime(), prevTemp: st.temperature } }])
|
|
2843
3215
|
),
|
|
2844
3216
|
joints: new Map(state.joints.map((st) => [st.id, { st, step: e.runtime?.joints[st.id] ?? 0 }])),
|
|
2845
3217
|
collapsed: e.runtime?.collapsed
|
|
@@ -2858,6 +3230,109 @@ var GameDestruction = (() => {
|
|
|
2858
3230
|
return world;
|
|
2859
3231
|
};
|
|
2860
3232
|
|
|
3233
|
+
// src/data/assemblies.json
|
|
3234
|
+
var assemblies_default = [
|
|
3235
|
+
{
|
|
3236
|
+
id: "brick_wall_plastered",
|
|
3237
|
+
name: "Plastered brick wall",
|
|
3238
|
+
core: { material: "brick", thickness: 0.09 },
|
|
3239
|
+
layers: [{ material: "plaster", thickness: 0.02, faces: "both" }],
|
|
3240
|
+
units: { size: [0.23, 0.075, 0.11], bond: "running", mortar: "plaster" },
|
|
3241
|
+
breach: { area: 0.25 }
|
|
3242
|
+
},
|
|
3243
|
+
{
|
|
3244
|
+
id: "concrete_block_wall",
|
|
3245
|
+
name: "Concrete block wall",
|
|
3246
|
+
core: { material: "concrete", thickness: 0.14 },
|
|
3247
|
+
fill: 0.55,
|
|
3248
|
+
units: { size: [0.39, 0.19, 0.14], bond: "running", mortar: "plaster" },
|
|
3249
|
+
breach: { area: 0.25 }
|
|
3250
|
+
},
|
|
3251
|
+
{
|
|
3252
|
+
id: "reinforced_concrete",
|
|
3253
|
+
name: "Reinforced concrete wall",
|
|
3254
|
+
core: { material: "concrete", thickness: 0.2 },
|
|
3255
|
+
reinforcement: { material: "steel", spacing: 0.18, diameter: 8e-3, residualCapacity: 0.15 },
|
|
3256
|
+
units: { size: [0.6, 0.42, 0.2], bond: "stack", mortar: "concrete" },
|
|
3257
|
+
breach: { area: 0.25 }
|
|
3258
|
+
},
|
|
3259
|
+
{
|
|
3260
|
+
id: "drywall",
|
|
3261
|
+
name: "Drywall partition on steel studs",
|
|
3262
|
+
core: { material: "plaster", thickness: 0.0125 },
|
|
3263
|
+
layers: [{ material: "plaster", thickness: 0.0125, faces: "+" }],
|
|
3264
|
+
depth: 0.095,
|
|
3265
|
+
breach: { area: 0.25 }
|
|
3266
|
+
},
|
|
3267
|
+
{
|
|
3268
|
+
id: "wood_frame_wall",
|
|
3269
|
+
name: "Wood frame wall",
|
|
3270
|
+
core: { material: "plywood", thickness: 0.012 },
|
|
3271
|
+
layers: [{ material: "plaster", thickness: 0.0125, faces: "-" }],
|
|
3272
|
+
depth: 0.1135,
|
|
3273
|
+
breach: { area: 0.25 }
|
|
3274
|
+
},
|
|
3275
|
+
{
|
|
3276
|
+
id: "log_wall",
|
|
3277
|
+
name: "Log wall",
|
|
3278
|
+
core: { material: "wood_soft", thickness: 0.2 },
|
|
3279
|
+
fill: 0.785,
|
|
3280
|
+
breach: { area: 0.25 }
|
|
3281
|
+
},
|
|
3282
|
+
{
|
|
3283
|
+
id: "sheet_metal_shack",
|
|
3284
|
+
name: "Corrugated sheet metal wall",
|
|
3285
|
+
core: { material: "steel", thickness: 6e-4 },
|
|
3286
|
+
depth: 0.02,
|
|
3287
|
+
breach: { area: 0.25 }
|
|
3288
|
+
},
|
|
3289
|
+
{
|
|
3290
|
+
id: "glass_pane",
|
|
3291
|
+
name: "Glass pane",
|
|
3292
|
+
core: { material: "glass", thickness: 6e-3 },
|
|
3293
|
+
breach: { area: 0.1 }
|
|
3294
|
+
}
|
|
3295
|
+
];
|
|
3296
|
+
|
|
3297
|
+
// src/assemblies.ts
|
|
3298
|
+
var ASSEMBLIES = assemblies_default;
|
|
3299
|
+
var registerAssembly = (def) => {
|
|
3300
|
+
const at = ASSEMBLIES.findIndex((a) => a.id === def.id);
|
|
3301
|
+
if (at >= 0) ASSEMBLIES[at] = def;
|
|
3302
|
+
else ASSEMBLIES.push(def);
|
|
3303
|
+
};
|
|
3304
|
+
var getAssembly = (id) => ASSEMBLIES.find((a) => a.id === id);
|
|
3305
|
+
var faceCount = (layer) => layer.faces === "both" ? 2 : 1;
|
|
3306
|
+
var assemblyDepth = (def) => def.depth ?? def.core.thickness + (def.layers ?? []).reduce((sum, l) => sum + l.thickness * faceCount(l), 0);
|
|
3307
|
+
var assemblyPart = (assemblyId, options) => {
|
|
3308
|
+
const def = getAssembly(assemblyId);
|
|
3309
|
+
if (!def) throw new Error(`unknown assembly "${assemblyId}"`);
|
|
3310
|
+
const densityOf = (id) => {
|
|
3311
|
+
const m = options.materials?.get(id) ?? MATERIALS.find((x) => x.id === id);
|
|
3312
|
+
if (!m) throw new Error(`unknown material "${id}" in assembly "${assemblyId}"`);
|
|
3313
|
+
return m.density;
|
|
3314
|
+
};
|
|
3315
|
+
const fill = def.fill ?? 1;
|
|
3316
|
+
const area = options.width * options.height;
|
|
3317
|
+
const coreMass = area * def.core.thickness * fill * densityOf(def.core.material);
|
|
3318
|
+
const layerMass = (def.layers ?? []).reduce((sum, l) => sum + area * l.thickness * faceCount(l) * densityOf(l.material), 0);
|
|
3319
|
+
return {
|
|
3320
|
+
id: options.id,
|
|
3321
|
+
material: def.core.material,
|
|
3322
|
+
shape: { kind: "box", size: [options.width, options.height, assemblyDepth(def)] },
|
|
3323
|
+
position: options.position,
|
|
3324
|
+
...options.rotation ? { rotation: options.rotation } : {},
|
|
3325
|
+
mass: coreMass + layerMass,
|
|
3326
|
+
thickness: def.core.thickness * fill,
|
|
3327
|
+
...def.layers ? { layers: def.layers.map((l) => ({ ...l })) } : {},
|
|
3328
|
+
...def.units ? { units: { ...def.units, size: [...def.units.size] } } : {},
|
|
3329
|
+
...def.reinforcement ? { reinforcement: { ...def.reinforcement } } : {},
|
|
3330
|
+
...def.breach ? { breach: { ...def.breach } } : {},
|
|
3331
|
+
...options.structure ? { structure: options.structure } : {},
|
|
3332
|
+
...options.extra
|
|
3333
|
+
};
|
|
3334
|
+
};
|
|
3335
|
+
|
|
2861
3336
|
// src/frame.ts
|
|
2862
3337
|
var buildFrame = (o = {}) => {
|
|
2863
3338
|
const spanX = o.spanX ?? 8, spanZ = o.spanZ ?? 8;
|
|
@@ -2965,6 +3440,8 @@ var GameDestruction = (() => {
|
|
|
2965
3440
|
var sizeStructure = (spec, options = {}) => {
|
|
2966
3441
|
const safety = options.safety ?? 0.65;
|
|
2967
3442
|
const redundancy = options.redundancy;
|
|
3443
|
+
const redundancyRoles = options.redundancyRoles ?? ["column"];
|
|
3444
|
+
const safetyOf = (role) => options.safetyByRole?.[role] ?? safety;
|
|
2968
3445
|
const loads = (s) => {
|
|
2969
3446
|
const w = createDestruction(options.materials ? { materials: options.materials } : {});
|
|
2970
3447
|
const id = w.add(s);
|
|
@@ -2977,11 +3454,11 @@ var GameDestruction = (() => {
|
|
|
2977
3454
|
if (!p.structure || p.structure.role === "none") continue;
|
|
2978
3455
|
const b = base.get(p.id);
|
|
2979
3456
|
if (!b || b.stress <= 0) continue;
|
|
2980
|
-
need.set(p.id, b.load /
|
|
3457
|
+
need.set(p.id, b.load / safetyOf(p.structure.role));
|
|
2981
3458
|
}
|
|
2982
3459
|
if (redundancy !== void 0) {
|
|
2983
3460
|
for (const col of out.parts) {
|
|
2984
|
-
if (col.structure
|
|
3461
|
+
if (!col.structure || !redundancyRoles.includes(col.structure.role)) continue;
|
|
2985
3462
|
const reduced = {
|
|
2986
3463
|
...out,
|
|
2987
3464
|
parts: out.parts.filter((p) => p.id !== col.id),
|
|
@@ -3081,6 +3558,10 @@ var GameDestruction = (() => {
|
|
|
3081
3558
|
primed: "about to explode",
|
|
3082
3559
|
stress: "overload",
|
|
3083
3560
|
collapse: "collapsed",
|
|
3561
|
+
spall: "plaster came off",
|
|
3562
|
+
stage: "damage stage",
|
|
3563
|
+
expose: "rebar bare",
|
|
3564
|
+
body: "body",
|
|
3084
3565
|
destroyed: "destroyed"
|
|
3085
3566
|
},
|
|
3086
3567
|
fractureStyles: { shatter: "shatter", crumble: "crumble", splinter: "splinter", tear: "tear", snap: "snap", none: "none" },
|
|
@@ -3163,6 +3644,10 @@ var GameDestruction = (() => {
|
|
|
3163
3644
|
primed: "prestes a explodir",
|
|
3164
3645
|
stress: "sobrecarga",
|
|
3165
3646
|
collapse: "desabou",
|
|
3647
|
+
spall: "reboco soltou",
|
|
3648
|
+
stage: "est\xE1gio de dano",
|
|
3649
|
+
expose: "vergalh\xE3o \xE0 mostra",
|
|
3650
|
+
body: "corpo",
|
|
3166
3651
|
destroyed: "destru\xEDdo"
|
|
3167
3652
|
},
|
|
3168
3653
|
fractureStyles: { shatter: "estilha\xE7ar", crumble: "esfarelar", splinter: "lascar", tear: "rasgar", snap: "partir", none: "nenhuma" },
|