@adatechnology/game-destruction 0.4.1 → 0.5.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 +32 -3
- package/dist/index.d.ts +72 -6
- package/dist/index.global.js +490 -22
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +490 -22
- 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;
|
|
@@ -1607,6 +1771,13 @@ var GameDestruction = (() => {
|
|
|
1607
1771
|
const maxHp = Math.max(1, ps.hp ?? mass0 * mat.hpPerKg);
|
|
1608
1772
|
const fuel0 = mat.thermal.ignitionC !== void 0 ? mass0 * (mat.thermal.fuelFraction ?? 0) : 0;
|
|
1609
1773
|
const c = carry?.parts.get(ps.id);
|
|
1774
|
+
const staged = !!(ps.layers?.length || ps.reinforcement || ps.units || ps.breach);
|
|
1775
|
+
const layers = (ps.layers ?? []).map((l, i) => {
|
|
1776
|
+
const lm = materials.get(l.material);
|
|
1777
|
+
if (!lm) throw new Error(`unknown material "${l.material}" on layer ${i} of part "${ps.id}"`);
|
|
1778
|
+
return resolveLayer(l, i, lm);
|
|
1779
|
+
});
|
|
1780
|
+
if (ps.reinforcement && !materials.has(ps.reinforcement.material)) throw new Error(`unknown material "${ps.reinforcement.material}" on the reinforcement of part "${ps.id}"`);
|
|
1610
1781
|
const pst = c?.st ?? {
|
|
1611
1782
|
id: ps.id,
|
|
1612
1783
|
material: ps.material,
|
|
@@ -1622,7 +1793,8 @@ var GameDestruction = (() => {
|
|
|
1622
1793
|
dents: [],
|
|
1623
1794
|
holes: [],
|
|
1624
1795
|
cracks: [],
|
|
1625
|
-
group: 0
|
|
1796
|
+
group: 0,
|
|
1797
|
+
...staged ? { stage: 0 } : {}
|
|
1626
1798
|
};
|
|
1627
1799
|
if (!c && ps.contains) pst.liters = ps.contains.liters;
|
|
1628
1800
|
const p = {
|
|
@@ -1641,7 +1813,10 @@ var GameDestruction = (() => {
|
|
|
1641
1813
|
rt: c?.rt ?? { ...freshRuntime(), prevTemp: pst.temperature },
|
|
1642
1814
|
structural: !!ps.structure && ps.structure.role !== "none",
|
|
1643
1815
|
anchored: !!ps.anchored || ps.structure?.role === "foundation",
|
|
1644
|
-
cap: ps.structure ? partCapacity(ps, mat) : 0
|
|
1816
|
+
cap: ps.structure ? partCapacity(ps, mat) : 0,
|
|
1817
|
+
staged,
|
|
1818
|
+
thin: thinAxisOf(shapeDims(ps.shape)),
|
|
1819
|
+
layers
|
|
1645
1820
|
};
|
|
1646
1821
|
if (p.structural) o.hasStructure = true;
|
|
1647
1822
|
if (p.rt.chainDue !== void 0 && !isLost(p)) primed.set(p, o);
|
|
@@ -1781,7 +1956,7 @@ var GameDestruction = (() => {
|
|
|
1781
1956
|
o.unsup[i] = o.anyAnchor && !isLost(p);
|
|
1782
1957
|
continue;
|
|
1783
1958
|
}
|
|
1784
|
-
p.st.stress = p.st.load / p.cap;
|
|
1959
|
+
p.st.stress = p.st.load / (p.cap * capacityScale(p));
|
|
1785
1960
|
if (p.st.stress > o.maxStress) o.maxStress = p.st.stress;
|
|
1786
1961
|
}
|
|
1787
1962
|
if (o.maxStress > 0.8) o.active = true;
|
|
@@ -1800,6 +1975,7 @@ var GameDestruction = (() => {
|
|
|
1800
1975
|
for (const p of ps) {
|
|
1801
1976
|
o.collapsed.add(p.spec.id);
|
|
1802
1977
|
p.st.status = "collapsed";
|
|
1978
|
+
setStage(o, p, 3, {}, src);
|
|
1803
1979
|
}
|
|
1804
1980
|
emit({ ...base(o, src), type: "collapse", parts: ps.map((p) => p.spec.id), mass: ps.reduce((sum, p) => sum + p.st.mass, 0) });
|
|
1805
1981
|
}
|
|
@@ -1853,6 +2029,7 @@ var GameDestruction = (() => {
|
|
|
1853
2029
|
if (isLost(p) || ignore?.has(`${o.id}\0${p.spec.id}`)) continue;
|
|
1854
2030
|
const r = rayPart(o, p, origin, d);
|
|
1855
2031
|
if (!r || r.t0 < 0 || r.t0 > maxDist) continue;
|
|
2032
|
+
if (p.st.opening && insideOpening(o, p, vec.madd(origin, d, r.t0))) continue;
|
|
1856
2033
|
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
2034
|
}
|
|
1858
2035
|
}
|
|
@@ -1961,6 +2138,7 @@ var GameDestruction = (() => {
|
|
|
1961
2138
|
if (p.st.holes.length > maxHoles) p.st.holes.shift();
|
|
1962
2139
|
markDamaged(p);
|
|
1963
2140
|
emit({ ...base(o, src), type: "hole", part: p.spec.id, hole, worldPoint: wp, ...exit ? { exitPoint: exit } : {} });
|
|
2141
|
+
setStage(o, p, 1, {}, src);
|
|
1964
2142
|
return hole;
|
|
1965
2143
|
};
|
|
1966
2144
|
const addCrack = (o, p, wp, wn, length, src) => {
|
|
@@ -1969,6 +2147,7 @@ var GameDestruction = (() => {
|
|
|
1969
2147
|
if (p.st.cracks.length > 16) p.st.cracks.shift();
|
|
1970
2148
|
markDamaged(p);
|
|
1971
2149
|
emit({ ...base(o, src), type: "crack", part: p.spec.id, crack, worldPoint: wp });
|
|
2150
|
+
setStage(o, p, 1, {}, src);
|
|
1972
2151
|
};
|
|
1973
2152
|
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
2153
|
const holeRate = (hole, pressurized) => (pressurized ? 0.5 : 0.1) * (hole.diameter / 9e-3);
|
|
@@ -2017,6 +2196,7 @@ var GameDestruction = (() => {
|
|
|
2017
2196
|
if (split && mine.length) amount /= mine.length;
|
|
2018
2197
|
for (const j of mine) {
|
|
2019
2198
|
if (j.st.open) continue;
|
|
2199
|
+
if (j.spec.kind === "rebar" && (o.parts[j.ia].spec.reinforcement || o.parts[j.ib].spec.reinforcement)) continue;
|
|
2020
2200
|
if (near && jointReach(o, j, near) > Math.max(radius, jointReachLimit(o, j))) continue;
|
|
2021
2201
|
j.st.hp -= amount;
|
|
2022
2202
|
if (j.st.hp <= 0) {
|
|
@@ -2030,23 +2210,172 @@ var GameDestruction = (() => {
|
|
|
2030
2210
|
}
|
|
2031
2211
|
}
|
|
2032
2212
|
};
|
|
2213
|
+
const setStage = (o, p, stage, extra = {}, src) => {
|
|
2214
|
+
if (!p.staged || (p.st.stage ?? 0) >= stage) return;
|
|
2215
|
+
p.st.stage = stage;
|
|
2216
|
+
emit({ ...base(o, src), type: "stage", part: p.spec.id, stage, ...extra });
|
|
2217
|
+
};
|
|
2218
|
+
const faceOf = (o, p, worldNormal) => rotateVec(quat.conjugate(partXf(o, p).q), worldNormal)[p.thin] >= 0 ? 1 : -1;
|
|
2219
|
+
const spallOuter = (o, p, wp, face, normal, radius, src) => {
|
|
2220
|
+
var _a;
|
|
2221
|
+
if (p.layers.length === 0) return;
|
|
2222
|
+
const lp = objLocal(o, wp);
|
|
2223
|
+
const layer = outerLayer(p.layers, p.rt.spalls, face, lp);
|
|
2224
|
+
if (!layer) return;
|
|
2225
|
+
const spalls = (_a = p.rt).spalls ?? (_a.spalls = []);
|
|
2226
|
+
spalls.push([layer.index, face, lp[0], lp[1], lp[2], radius]);
|
|
2227
|
+
if (spalls.length > MAX_SPALLS) spalls.shift();
|
|
2228
|
+
markDamaged(p);
|
|
2229
|
+
emit({ ...base(o, src), type: "spall", part: p.spec.id, layer: layer.index, point: wp, area: Math.PI * radius * radius });
|
|
2230
|
+
debris(o, p, layer.mat.hitDebris, wp, normal, 5, 2, src);
|
|
2231
|
+
setStage(o, p, 1, {}, src);
|
|
2232
|
+
};
|
|
2233
|
+
const insideOpening = (o, p, objectPoint) => {
|
|
2234
|
+
const op = p.st.opening;
|
|
2235
|
+
if (!op) return false;
|
|
2236
|
+
const [u, v] = planeAxes(p.thin);
|
|
2237
|
+
const lp = toLocal(p.pos, p.q, objectPoint);
|
|
2238
|
+
const lc = toLocal(p.pos, p.q, op.center);
|
|
2239
|
+
return Math.hypot(lp[u] - lc[u], lp[v] - lc[v]) <= op.radius;
|
|
2240
|
+
};
|
|
2241
|
+
const breachAreaOf = (p, damage, blast) => damage / (p.mat.hpPerKg * p.mat.density) / Math.max(p.thickness, 5e-3) * (blast ? breachBlastFactor : 1);
|
|
2242
|
+
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 }));
|
|
2243
|
+
const unitsOf = (p) => p.spec.units ?? { size: [p.mat.fragmentSize?.[1] ?? 0.2, p.mat.fragmentSize?.[1] ?? 0.2, p.thickness], bond: "stack" };
|
|
2244
|
+
const FRAGMENT_SHAPE = { shatter: "shard", splinter: "splinter", tear: "scrap" };
|
|
2245
|
+
const cellFragments = (o, p, cells, hitW, dir, speed, mass, tag) => {
|
|
2246
|
+
const { c, q } = partXf(o, p);
|
|
2247
|
+
const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
|
|
2248
|
+
const [u, v] = planeAxes(p.thin);
|
|
2249
|
+
const faceArea = (cell) => cell.size[u] * cell.size[v];
|
|
2250
|
+
const total = cells.reduce((sum, cell) => sum + faceArea(cell), 0) || 1;
|
|
2251
|
+
const shape = p.spec.units ? "chunk" : FRAGMENT_SHAPE[p.mat.fracture] ?? "chunk";
|
|
2252
|
+
return cells.map((cell, i) => {
|
|
2253
|
+
const cw = toWorld(c, q, cell.center);
|
|
2254
|
+
const out = vec.normalize(vec.sub(cw, hitW), [0, 1, 0]);
|
|
2255
|
+
const dv = dir ? vec.normalize(vec.madd(out, dir, 0.6)) : out;
|
|
2256
|
+
const k = speed * (0.6 + 0.8 * rnd());
|
|
2257
|
+
const marks = marksInBox(cell.center, cell.size, void 0, holes);
|
|
2258
|
+
return {
|
|
2259
|
+
id: `${p.spec.id}#${tag}${i}`,
|
|
2260
|
+
part: p.spec.id,
|
|
2261
|
+
material: p.spec.material,
|
|
2262
|
+
shape,
|
|
2263
|
+
center: cw,
|
|
2264
|
+
size: [cell.size[0], cell.size[1], cell.size[2]],
|
|
2265
|
+
rotation: q,
|
|
2266
|
+
velocity: [dv[0] * k, dv[1] * k + 0.5 * rnd(), dv[2] * k],
|
|
2267
|
+
angularVelocity: [(rnd() - 0.5) * 8, (rnd() - 0.5) * 8, (rnd() - 0.5) * 8],
|
|
2268
|
+
mass: mass * faceArea(cell) / total,
|
|
2269
|
+
...marks.length > 0 ? { marks } : {}
|
|
2270
|
+
};
|
|
2271
|
+
});
|
|
2272
|
+
};
|
|
2273
|
+
const worldBars = (o, p, bars) => {
|
|
2274
|
+
const { c, q } = partXf(o, p);
|
|
2275
|
+
return bars.map(([a, b]) => [toWorld(c, q, a), toWorld(c, q, b)]);
|
|
2276
|
+
};
|
|
2277
|
+
const addBreach = (o, p, wp, area, energy, dir, src) => {
|
|
2278
|
+
var _a;
|
|
2279
|
+
if (!p.staged || area <= 0 || (p.st.stage ?? 0) >= 2 || p.st.stripped || isLost(p)) return;
|
|
2280
|
+
const lp = objLocal(o, wp);
|
|
2281
|
+
const list = (_a = p.rt).breach ?? (_a.breach = []);
|
|
2282
|
+
list.push([lp[0], lp[1], lp[2], area]);
|
|
2283
|
+
if (list.length > MAX_SPALLS) list.shift();
|
|
2284
|
+
let total = 0;
|
|
2285
|
+
const sum = [0, 0, 0];
|
|
2286
|
+
for (const [x, y, z, a] of list) {
|
|
2287
|
+
if (vec.distance([x, y, z], lp) > breachRadius) continue;
|
|
2288
|
+
total += a;
|
|
2289
|
+
sum[0] += x * a;
|
|
2290
|
+
sum[1] += y * a;
|
|
2291
|
+
sum[2] += z * a;
|
|
2292
|
+
}
|
|
2293
|
+
if (total < (p.spec.breach?.area ?? BREACH_AREA)) return;
|
|
2294
|
+
openBreach(o, p, [sum[0] / total, sum[1] / total, sum[2] / total], total, energy, dir, src);
|
|
2295
|
+
};
|
|
2296
|
+
const openBreach = (o, p, center, total, energy, dir, src) => {
|
|
2297
|
+
const [u, v] = planeAxes(p.thin);
|
|
2298
|
+
const radius = clamp2(Math.sqrt(total / Math.PI), Math.sqrt((p.spec.breach?.area ?? BREACH_AREA) / Math.PI), breachRadius);
|
|
2299
|
+
const lc = toLocal(p.pos, p.q, center);
|
|
2300
|
+
lc[p.thin] = 0;
|
|
2301
|
+
const { c, q } = partXf(o, p);
|
|
2302
|
+
const worldCenter = toWorld(c, q, lc);
|
|
2303
|
+
if (2 * radius >= Math.min(p.dims[u], p.dims[v])) {
|
|
2304
|
+
failPart(o, p, worldCenter, dir, energy, 12, src);
|
|
2305
|
+
return;
|
|
2306
|
+
}
|
|
2307
|
+
const cells = cellsWithin(unitGrid(p.dims, p.thin, unitsOf(p), BREACH_GRID_CAP), p.thin, lc, radius);
|
|
2308
|
+
const removed = p.st.mass * cells.reduce((sum, cell) => sum + cell.size[u] * cell.size[v], 0) / (p.dims[u] * p.dims[v]);
|
|
2309
|
+
const speed = clamp2(Math.sqrt(2 * energy * 0.1 / Math.max(removed, 0.05)), 0.5, 8);
|
|
2310
|
+
const fragments = cellFragments(o, p, cells, worldCenter, dir, speed, removed, "b");
|
|
2311
|
+
const holes = localHolesOf(p);
|
|
2312
|
+
p.st.holes = p.st.holes.filter((_, i) => !cells.some((cell) => marksInBox(cell.center, cell.size, void 0, [holes[i]]).length > 0));
|
|
2313
|
+
p.st.mass = Math.max(0, p.st.mass - removed);
|
|
2314
|
+
p.rt.removed = (p.rt.removed ?? 0) + removed;
|
|
2315
|
+
p.st.opening = { center: objLocal(o, worldCenter), radius };
|
|
2316
|
+
markDamaged(p);
|
|
2317
|
+
setStage(o, p, 1, {}, src);
|
|
2318
|
+
setStage(o, p, 2, { opening: { center: worldCenter, radius }, fragments }, src);
|
|
2319
|
+
debris(o, p, p.mat.breakDebris, worldCenter, vec.scale(dir ?? [0, 1, 0], -1), 14, 3, src);
|
|
2320
|
+
const reinforcement = p.spec.reinforcement;
|
|
2321
|
+
if (reinforcement) {
|
|
2322
|
+
const bars = worldBars(o, p, clipBarsToDisc(rebarBars(p.dims, p.thin, reinforcement.spacing), u, v, [lc[u], lc[v]], radius + 0.05));
|
|
2323
|
+
if (bars.length > 0) emit({ ...base(o, src), type: "expose", part: p.spec.id, bars });
|
|
2324
|
+
}
|
|
2325
|
+
if (p.structural) solveStructure(o);
|
|
2326
|
+
};
|
|
2327
|
+
const stripPart = (o, p, reinforcement, src) => {
|
|
2328
|
+
const bars = rebarBars(p.dims, p.thin, reinforcement.spacing);
|
|
2329
|
+
p.st.stripped = true;
|
|
2330
|
+
p.st.hp = p.st.maxHp;
|
|
2331
|
+
p.st.mass = rebarMass(bars, reinforcement.diameter, materials.get(reinforcement.material)?.density);
|
|
2332
|
+
p.st.holes = [];
|
|
2333
|
+
p.st.burning = false;
|
|
2334
|
+
p.rt.jet = false;
|
|
2335
|
+
markDamaged(p);
|
|
2336
|
+
emit({ ...base(o, src), type: "expose", part: p.spec.id, bars: worldBars(o, p, bars) });
|
|
2337
|
+
solveStructure(o);
|
|
2338
|
+
collapseCheck(o, src);
|
|
2339
|
+
};
|
|
2340
|
+
const unitFragmentPlan = (p, cap) => {
|
|
2341
|
+
const grid = unitGrid(p.dims, p.thin, unitsOf(p), cap);
|
|
2342
|
+
const op = p.st.opening;
|
|
2343
|
+
const cells = op ? cellsOutside(grid, p.thin, toLocal(p.pos, p.q, op.center), op.radius) : grid;
|
|
2344
|
+
const [u, v] = planeAxes(p.thin);
|
|
2345
|
+
return cells.map((cell) => ({ center: cell.center, size: cell.size, q: quat.identity(), shape: "chunk", weight: cell.size[u] * cell.size[v] }));
|
|
2346
|
+
};
|
|
2347
|
+
const loseCage = (o, p, src) => {
|
|
2348
|
+
p.st.status = "broken";
|
|
2349
|
+
p.st.hp = 0;
|
|
2350
|
+
p.st.mass = 0;
|
|
2351
|
+
emit({ ...base(o, src), type: "expose", part: p.spec.id, bars: [] });
|
|
2352
|
+
debris(o, p, "sparks", partXf(o, p).c, [0, 1, 0], 6, 3, src);
|
|
2353
|
+
partLost(o, p, src);
|
|
2354
|
+
};
|
|
2033
2355
|
const failPart = (o, p, origin, dir, fragEnergy, cap, src) => {
|
|
2034
2356
|
if (isLost(p)) return;
|
|
2357
|
+
if (p.st.stripped) {
|
|
2358
|
+
loseCage(o, p, src);
|
|
2359
|
+
return;
|
|
2360
|
+
}
|
|
2035
2361
|
const ex = p.spec.explodes;
|
|
2036
2362
|
const style = p.mat.fracture === "none" ? "snap" : p.mat.fracture;
|
|
2037
2363
|
const { c, q } = partXf(o, p);
|
|
2038
2364
|
const hitW = origin ?? c;
|
|
2039
2365
|
const grain = p.spec.grain ? AXIS[p.spec.grain] : void 0;
|
|
2040
|
-
const
|
|
2366
|
+
const hitLocal = toLocal(c, q, hitW);
|
|
2367
|
+
const unitPlan = p.spec.units ? unitFragmentPlan(p, maxFragments * UNIT_FRAGMENT_FACTOR) : [];
|
|
2368
|
+
const plan = unitPlan.length > 0 ? unitPlan : planFragments({
|
|
2041
2369
|
style,
|
|
2042
2370
|
dims: p.dims,
|
|
2043
2371
|
thickness: p.thickness,
|
|
2044
2372
|
grain,
|
|
2045
|
-
hit:
|
|
2373
|
+
hit: hitLocal,
|
|
2046
2374
|
fragmentSize: p.mat.fragmentSize ?? [0.04, 0.2],
|
|
2047
2375
|
maxFragments,
|
|
2048
2376
|
rnd
|
|
2049
2377
|
});
|
|
2378
|
+
const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
|
|
2050
2379
|
const mass = p.st.mass;
|
|
2051
2380
|
const totW = plan.reduce((s, f) => s + f.weight, 0) || 1;
|
|
2052
2381
|
const energy = ex ? ex.energy * 0.2 : fragEnergy;
|
|
@@ -2056,6 +2385,7 @@ var GameDestruction = (() => {
|
|
|
2056
2385
|
const out = vec.normalize(vec.sub(cw, hitW), [0, 1, 0]);
|
|
2057
2386
|
const dv = dir ? vec.normalize(vec.madd(out, dir, 0.6)) : out;
|
|
2058
2387
|
const k = speed * (0.6 + 0.8 * rnd());
|
|
2388
|
+
const marks = marksInBox(f.center, f.size, f.q, holes);
|
|
2059
2389
|
return {
|
|
2060
2390
|
id: `${p.spec.id}#${i}`,
|
|
2061
2391
|
part: p.spec.id,
|
|
@@ -2066,16 +2396,25 @@ var GameDestruction = (() => {
|
|
|
2066
2396
|
rotation: quat.mul(q, f.q),
|
|
2067
2397
|
velocity: [dv[0] * k, dv[1] * k + 0.5 * rnd(), dv[2] * k],
|
|
2068
2398
|
angularVelocity: [(rnd() - 0.5) * 12, (rnd() - 0.5) * 12, (rnd() - 0.5) * 12],
|
|
2069
|
-
mass: mass * f.weight / totW
|
|
2399
|
+
mass: mass * f.weight / totW,
|
|
2400
|
+
...marks.length > 0 ? { marks } : {}
|
|
2070
2401
|
};
|
|
2071
2402
|
});
|
|
2072
|
-
p.
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2403
|
+
const reinforcement = p.spec.reinforcement;
|
|
2404
|
+
if (!reinforcement) {
|
|
2405
|
+
p.st.status = "broken";
|
|
2406
|
+
p.st.hp = 0;
|
|
2407
|
+
p.st.mass = 0;
|
|
2408
|
+
p.st.burning = false;
|
|
2409
|
+
p.rt.jet = false;
|
|
2410
|
+
}
|
|
2077
2411
|
emit({ ...base(o, src), type: "fracture", part: p.spec.id, style, fragments, point: hitW });
|
|
2078
2412
|
debris(o, p, p.mat.breakDebris, c, [0, 1, 0], clamp2(Math.round(Math.cbrt(partVolume(p.spec.shape)) * 30), 3, 20), 2, src);
|
|
2413
|
+
setStage(o, p, 3, {}, src);
|
|
2414
|
+
if (reinforcement) {
|
|
2415
|
+
stripPart(o, p, reinforcement, src);
|
|
2416
|
+
return;
|
|
2417
|
+
}
|
|
2079
2418
|
if (ex) {
|
|
2080
2419
|
emit({ ...base(o, src), type: "explode", part: p.spec.id, point: c, energy: ex.energy, radius: ex.radius });
|
|
2081
2420
|
blasts.push({ point: c, energy: ex.energy, radius: ex.radius, source: src });
|
|
@@ -2109,10 +2448,17 @@ var GameDestruction = (() => {
|
|
|
2109
2448
|
};
|
|
2110
2449
|
const ballistic = (o, p, entry, n, d, E, caliber, pathLen, src) => {
|
|
2111
2450
|
const mat = p.mat;
|
|
2451
|
+
if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry))) {
|
|
2452
|
+
return { penetrated: true, exitEnergy: E, exitPoint: vec.madd(entry, d, Math.min(pathLen, p.thickness)), damage: 0 };
|
|
2453
|
+
}
|
|
2112
2454
|
const sin = Math.min(1, Math.abs(vec.dot(d, n)));
|
|
2113
2455
|
const angle = Math.asin(sin) * 180 / Math.PI;
|
|
2114
|
-
const
|
|
2115
|
-
const
|
|
2456
|
+
const slant = Math.max(sin, 0.3);
|
|
2457
|
+
const effM = p.thickness / slant;
|
|
2458
|
+
const face = p.layers.length > 0 ? faceOf(o, p, n) : 1;
|
|
2459
|
+
const exitLocal = objLocal(o, vec.madd(entry, d, Math.min(pathLen, effM)));
|
|
2460
|
+
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;
|
|
2461
|
+
const thr = coats > 0 ? (mat.penetration * p.thickness * 1e3 + coats) / slant : mat.penetration * effM * 1e3;
|
|
2116
2462
|
const penetrated = E > thr;
|
|
2117
2463
|
const scale = (mat.damageScale?.ballistic ?? 1) * (mat.fracture === "shatter" ? fragility(mat.ductility, p.thickness) : 1);
|
|
2118
2464
|
const id = p.spec.id;
|
|
@@ -2123,13 +2469,16 @@ var GameDestruction = (() => {
|
|
|
2123
2469
|
const dmg2 = applyDamage(o, p, E * DMG_PER_J * scale * 0.2, "ballistic", entry, d, E * 0.1, 12, src);
|
|
2124
2470
|
return { penetrated: false, exitEnergy: 0, damage: dmg2 };
|
|
2125
2471
|
}
|
|
2472
|
+
if (p.layers.length > 0) spallOuter(o, p, entry, face, n, Math.max(0.03, caliber / 1e3 * 4), src);
|
|
2126
2473
|
const brittle = mat.ductility < 0.3;
|
|
2127
2474
|
const ratio = E / Math.max(thr, 1e-6);
|
|
2128
2475
|
const crackLen = clamp2(0.03 + ratio * 0.2, 0.03, 0.8);
|
|
2129
2476
|
let exitPoint;
|
|
2130
2477
|
if (penetrated) {
|
|
2131
|
-
exitPoint = vec.madd(entry, d, Math.min(pathLen, effM));
|
|
2478
|
+
exitPoint = vec.madd(entry, d, Math.min(pathLen, effM + layersThickness(p.layers, face) / slant + layersThickness(p.layers, -face) / slant));
|
|
2132
2479
|
const hole = addHole(o, p, entry, n, caliber / 1e3 * (mat.ductility >= 0.3 ? 1 : 1.5), true, exitPoint, src);
|
|
2480
|
+
if (p.layers.length > 0) spallOuter(o, p, exitPoint, -face, vec.scale(n, -1), Math.max(0.03, caliber / 1e3 * 4), src);
|
|
2481
|
+
addBreach(o, p, entry, Math.PI * (hole.diameter / 2) * (hole.diameter / 2), E, d, src);
|
|
2133
2482
|
if (brittle && mat.fracture === "shatter") addCrack(o, p, entry, n, crackLen, src);
|
|
2134
2483
|
if (p.spec.contains && (p.st.liters ?? 0) > 0) {
|
|
2135
2484
|
emit({
|
|
@@ -2174,8 +2523,11 @@ var GameDestruction = (() => {
|
|
|
2174
2523
|
}
|
|
2175
2524
|
if (kind === "blunt") wearJoints(o, p, E * 0.3, void 0, 0, src);
|
|
2176
2525
|
else wearJoints(o, p, E * 0.3, point, 0.06, src);
|
|
2526
|
+
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
2527
|
const scale = mat.damageScale?.[kind] ?? 1;
|
|
2178
|
-
|
|
2528
|
+
const done = applyDamage(o, p, Ec * DMG_PER_J * scale * (kind === "slash" ? p.cutFactor : 1), kind, point, d, E * 0.1, 12, src);
|
|
2529
|
+
addBreach(o, p, point, breachAreaOf(p, done, false), E, d, src);
|
|
2530
|
+
return done;
|
|
2179
2531
|
};
|
|
2180
2532
|
const blastObject = (o, point, energy, radius, src) => {
|
|
2181
2533
|
const start = cur.length;
|
|
@@ -2211,7 +2563,10 @@ var GameDestruction = (() => {
|
|
|
2211
2563
|
}
|
|
2212
2564
|
}
|
|
2213
2565
|
wearJoints(o, p, Ein * 0.5, void 0, 0, src);
|
|
2214
|
-
|
|
2566
|
+
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);
|
|
2567
|
+
const dealt = applyDamage(o, p, Ein * DMG_PER_J * (mat.damageScale?.explosive ?? 1), "explosive", point, null, Ein * 0.3, 40, src);
|
|
2568
|
+
res.damage += dealt;
|
|
2569
|
+
addBreach(o, p, wp, breachAreaOf(p, dealt, true), Ein, null, src);
|
|
2215
2570
|
}
|
|
2216
2571
|
res.events = cur.slice(start);
|
|
2217
2572
|
return res;
|
|
@@ -2482,7 +2837,7 @@ var GameDestruction = (() => {
|
|
|
2482
2837
|
}
|
|
2483
2838
|
o.active = hot;
|
|
2484
2839
|
};
|
|
2485
|
-
const
|
|
2840
|
+
const spread2 = (sources, dt) => {
|
|
2486
2841
|
for (const src of sources) {
|
|
2487
2842
|
if (!src.p.st.burning && !src.p.rt.jet) continue;
|
|
2488
2843
|
const sc = toWorld(src.o.st.position, src.o.st.rotation, src.p.pos);
|
|
@@ -2783,7 +3138,7 @@ var GameDestruction = (() => {
|
|
|
2783
3138
|
time += dt;
|
|
2784
3139
|
const sources = [];
|
|
2785
3140
|
for (const o of [...objects.values()]) if (o.active) stepObject(o, dt, sources);
|
|
2786
|
-
if (sources.length)
|
|
3141
|
+
if (sources.length) spread2(sources, dt);
|
|
2787
3142
|
detonatePrimed(time);
|
|
2788
3143
|
for (const im of [...impls.values()]) {
|
|
2789
3144
|
if (!im.impl.update) continue;
|
|
@@ -2822,7 +3177,7 @@ var GameDestruction = (() => {
|
|
|
2822
3177
|
counter,
|
|
2823
3178
|
objects: [...objects.values()].map((o) => {
|
|
2824
3179
|
const runtime = { parts: {}, joints: {}, collapsed: [...o.collapsed] };
|
|
2825
|
-
for (const p of o.parts) runtime.parts[p.spec.id] =
|
|
3180
|
+
for (const p of o.parts) runtime.parts[p.spec.id] = clone(p.rt);
|
|
2826
3181
|
for (const j of o.joints) runtime.joints[j.spec.id] = j.step;
|
|
2827
3182
|
return { id: o.id, spec: clone(o.spec), state: clone(o.st), runtime };
|
|
2828
3183
|
}),
|
|
@@ -2839,7 +3194,7 @@ var GameDestruction = (() => {
|
|
|
2839
3194
|
const state = clone(e.state);
|
|
2840
3195
|
const carry = {
|
|
2841
3196
|
parts: new Map(
|
|
2842
|
-
state.parts.map((st) => [st.id, { st, rt: e.runtime?.parts[st.id] ?
|
|
3197
|
+
state.parts.map((st) => [st.id, { st, rt: e.runtime?.parts[st.id] ? clone(e.runtime.parts[st.id]) : { ...freshRuntime(), prevTemp: st.temperature } }])
|
|
2843
3198
|
),
|
|
2844
3199
|
joints: new Map(state.joints.map((st) => [st.id, { st, step: e.runtime?.joints[st.id] ?? 0 }])),
|
|
2845
3200
|
collapsed: e.runtime?.collapsed
|
|
@@ -2858,6 +3213,109 @@ var GameDestruction = (() => {
|
|
|
2858
3213
|
return world;
|
|
2859
3214
|
};
|
|
2860
3215
|
|
|
3216
|
+
// src/data/assemblies.json
|
|
3217
|
+
var assemblies_default = [
|
|
3218
|
+
{
|
|
3219
|
+
id: "brick_wall_plastered",
|
|
3220
|
+
name: "Plastered brick wall",
|
|
3221
|
+
core: { material: "brick", thickness: 0.09 },
|
|
3222
|
+
layers: [{ material: "plaster", thickness: 0.02, faces: "both" }],
|
|
3223
|
+
units: { size: [0.23, 0.075, 0.11], bond: "running", mortar: "plaster" },
|
|
3224
|
+
breach: { area: 0.25 }
|
|
3225
|
+
},
|
|
3226
|
+
{
|
|
3227
|
+
id: "concrete_block_wall",
|
|
3228
|
+
name: "Concrete block wall",
|
|
3229
|
+
core: { material: "concrete", thickness: 0.14 },
|
|
3230
|
+
fill: 0.55,
|
|
3231
|
+
units: { size: [0.39, 0.19, 0.14], bond: "running", mortar: "plaster" },
|
|
3232
|
+
breach: { area: 0.25 }
|
|
3233
|
+
},
|
|
3234
|
+
{
|
|
3235
|
+
id: "reinforced_concrete",
|
|
3236
|
+
name: "Reinforced concrete wall",
|
|
3237
|
+
core: { material: "concrete", thickness: 0.2 },
|
|
3238
|
+
reinforcement: { material: "steel", spacing: 0.18, diameter: 8e-3, residualCapacity: 0.15 },
|
|
3239
|
+
units: { size: [0.6, 0.42, 0.2], bond: "stack", mortar: "concrete" },
|
|
3240
|
+
breach: { area: 0.25 }
|
|
3241
|
+
},
|
|
3242
|
+
{
|
|
3243
|
+
id: "drywall",
|
|
3244
|
+
name: "Drywall partition on steel studs",
|
|
3245
|
+
core: { material: "plaster", thickness: 0.0125 },
|
|
3246
|
+
layers: [{ material: "plaster", thickness: 0.0125, faces: "+" }],
|
|
3247
|
+
depth: 0.095,
|
|
3248
|
+
breach: { area: 0.25 }
|
|
3249
|
+
},
|
|
3250
|
+
{
|
|
3251
|
+
id: "wood_frame_wall",
|
|
3252
|
+
name: "Wood frame wall",
|
|
3253
|
+
core: { material: "plywood", thickness: 0.012 },
|
|
3254
|
+
layers: [{ material: "plaster", thickness: 0.0125, faces: "-" }],
|
|
3255
|
+
depth: 0.1135,
|
|
3256
|
+
breach: { area: 0.25 }
|
|
3257
|
+
},
|
|
3258
|
+
{
|
|
3259
|
+
id: "log_wall",
|
|
3260
|
+
name: "Log wall",
|
|
3261
|
+
core: { material: "wood_soft", thickness: 0.2 },
|
|
3262
|
+
fill: 0.785,
|
|
3263
|
+
breach: { area: 0.25 }
|
|
3264
|
+
},
|
|
3265
|
+
{
|
|
3266
|
+
id: "sheet_metal_shack",
|
|
3267
|
+
name: "Corrugated sheet metal wall",
|
|
3268
|
+
core: { material: "steel", thickness: 6e-4 },
|
|
3269
|
+
depth: 0.02,
|
|
3270
|
+
breach: { area: 0.25 }
|
|
3271
|
+
},
|
|
3272
|
+
{
|
|
3273
|
+
id: "glass_pane",
|
|
3274
|
+
name: "Glass pane",
|
|
3275
|
+
core: { material: "glass", thickness: 6e-3 },
|
|
3276
|
+
breach: { area: 0.1 }
|
|
3277
|
+
}
|
|
3278
|
+
];
|
|
3279
|
+
|
|
3280
|
+
// src/assemblies.ts
|
|
3281
|
+
var ASSEMBLIES = assemblies_default;
|
|
3282
|
+
var registerAssembly = (def) => {
|
|
3283
|
+
const at = ASSEMBLIES.findIndex((a) => a.id === def.id);
|
|
3284
|
+
if (at >= 0) ASSEMBLIES[at] = def;
|
|
3285
|
+
else ASSEMBLIES.push(def);
|
|
3286
|
+
};
|
|
3287
|
+
var getAssembly = (id) => ASSEMBLIES.find((a) => a.id === id);
|
|
3288
|
+
var faceCount = (layer) => layer.faces === "both" ? 2 : 1;
|
|
3289
|
+
var assemblyDepth = (def) => def.depth ?? def.core.thickness + (def.layers ?? []).reduce((sum, l) => sum + l.thickness * faceCount(l), 0);
|
|
3290
|
+
var assemblyPart = (assemblyId, options) => {
|
|
3291
|
+
const def = getAssembly(assemblyId);
|
|
3292
|
+
if (!def) throw new Error(`unknown assembly "${assemblyId}"`);
|
|
3293
|
+
const densityOf = (id) => {
|
|
3294
|
+
const m = options.materials?.get(id) ?? MATERIALS.find((x) => x.id === id);
|
|
3295
|
+
if (!m) throw new Error(`unknown material "${id}" in assembly "${assemblyId}"`);
|
|
3296
|
+
return m.density;
|
|
3297
|
+
};
|
|
3298
|
+
const fill = def.fill ?? 1;
|
|
3299
|
+
const area = options.width * options.height;
|
|
3300
|
+
const coreMass = area * def.core.thickness * fill * densityOf(def.core.material);
|
|
3301
|
+
const layerMass = (def.layers ?? []).reduce((sum, l) => sum + area * l.thickness * faceCount(l) * densityOf(l.material), 0);
|
|
3302
|
+
return {
|
|
3303
|
+
id: options.id,
|
|
3304
|
+
material: def.core.material,
|
|
3305
|
+
shape: { kind: "box", size: [options.width, options.height, assemblyDepth(def)] },
|
|
3306
|
+
position: options.position,
|
|
3307
|
+
...options.rotation ? { rotation: options.rotation } : {},
|
|
3308
|
+
mass: coreMass + layerMass,
|
|
3309
|
+
thickness: def.core.thickness * fill,
|
|
3310
|
+
...def.layers ? { layers: def.layers.map((l) => ({ ...l })) } : {},
|
|
3311
|
+
...def.units ? { units: { ...def.units, size: [...def.units.size] } } : {},
|
|
3312
|
+
...def.reinforcement ? { reinforcement: { ...def.reinforcement } } : {},
|
|
3313
|
+
...def.breach ? { breach: { ...def.breach } } : {},
|
|
3314
|
+
...options.structure ? { structure: options.structure } : {},
|
|
3315
|
+
...options.extra
|
|
3316
|
+
};
|
|
3317
|
+
};
|
|
3318
|
+
|
|
2861
3319
|
// src/frame.ts
|
|
2862
3320
|
var buildFrame = (o = {}) => {
|
|
2863
3321
|
const spanX = o.spanX ?? 8, spanZ = o.spanZ ?? 8;
|
|
@@ -2965,6 +3423,8 @@ var GameDestruction = (() => {
|
|
|
2965
3423
|
var sizeStructure = (spec, options = {}) => {
|
|
2966
3424
|
const safety = options.safety ?? 0.65;
|
|
2967
3425
|
const redundancy = options.redundancy;
|
|
3426
|
+
const redundancyRoles = options.redundancyRoles ?? ["column"];
|
|
3427
|
+
const safetyOf = (role) => options.safetyByRole?.[role] ?? safety;
|
|
2968
3428
|
const loads = (s) => {
|
|
2969
3429
|
const w = createDestruction(options.materials ? { materials: options.materials } : {});
|
|
2970
3430
|
const id = w.add(s);
|
|
@@ -2977,11 +3437,11 @@ var GameDestruction = (() => {
|
|
|
2977
3437
|
if (!p.structure || p.structure.role === "none") continue;
|
|
2978
3438
|
const b = base.get(p.id);
|
|
2979
3439
|
if (!b || b.stress <= 0) continue;
|
|
2980
|
-
need.set(p.id, b.load /
|
|
3440
|
+
need.set(p.id, b.load / safetyOf(p.structure.role));
|
|
2981
3441
|
}
|
|
2982
3442
|
if (redundancy !== void 0) {
|
|
2983
3443
|
for (const col of out.parts) {
|
|
2984
|
-
if (col.structure
|
|
3444
|
+
if (!col.structure || !redundancyRoles.includes(col.structure.role)) continue;
|
|
2985
3445
|
const reduced = {
|
|
2986
3446
|
...out,
|
|
2987
3447
|
parts: out.parts.filter((p) => p.id !== col.id),
|
|
@@ -3081,6 +3541,10 @@ var GameDestruction = (() => {
|
|
|
3081
3541
|
primed: "about to explode",
|
|
3082
3542
|
stress: "overload",
|
|
3083
3543
|
collapse: "collapsed",
|
|
3544
|
+
spall: "plaster came off",
|
|
3545
|
+
stage: "damage stage",
|
|
3546
|
+
expose: "rebar bare",
|
|
3547
|
+
body: "body",
|
|
3084
3548
|
destroyed: "destroyed"
|
|
3085
3549
|
},
|
|
3086
3550
|
fractureStyles: { shatter: "shatter", crumble: "crumble", splinter: "splinter", tear: "tear", snap: "snap", none: "none" },
|
|
@@ -3163,6 +3627,10 @@ var GameDestruction = (() => {
|
|
|
3163
3627
|
primed: "prestes a explodir",
|
|
3164
3628
|
stress: "sobrecarga",
|
|
3165
3629
|
collapse: "desabou",
|
|
3630
|
+
spall: "reboco soltou",
|
|
3631
|
+
stage: "est\xE1gio de dano",
|
|
3632
|
+
expose: "vergalh\xE3o \xE0 mostra",
|
|
3633
|
+
body: "corpo",
|
|
3166
3634
|
destroyed: "destru\xEDdo"
|
|
3167
3635
|
},
|
|
3168
3636
|
fractureStyles: { shatter: "estilha\xE7ar", crumble: "esfarelar", splinter: "lascar", tear: "rasgar", snap: "partir", none: "nenhuma" },
|