@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.js
CHANGED
|
@@ -419,6 +419,153 @@ var planFragments = (inp) => {
|
|
|
419
419
|
return out;
|
|
420
420
|
};
|
|
421
421
|
|
|
422
|
+
// src/breach.ts
|
|
423
|
+
var planeAxes = (thin) => thin === 1 ? [0, 2] : [thin === 0 ? 2 : 0, 1];
|
|
424
|
+
var unitGrid = (dims, thin, units, cap) => {
|
|
425
|
+
const [u, v] = planeAxes(thin);
|
|
426
|
+
let unitU = Math.min(units.size[0], dims[u]), unitV = Math.min(units.size[1], dims[v]);
|
|
427
|
+
const rowsFor = (h) => Math.ceil(dims[v] / h - 1e-9);
|
|
428
|
+
const colsFor = (w) => Math.ceil(dims[u] / w - 1e-9) + (units.bond === "running" ? 1 : 0);
|
|
429
|
+
const merge = Math.ceil(Math.sqrt(rowsFor(unitV) * colsFor(unitU) / cap));
|
|
430
|
+
if (merge > 1) {
|
|
431
|
+
unitU = Math.min(unitU * merge, dims[u]);
|
|
432
|
+
unitV = Math.min(unitV * merge, dims[v]);
|
|
433
|
+
}
|
|
434
|
+
const cells = [];
|
|
435
|
+
for (let row = 0; row < rowsFor(unitV); row++) {
|
|
436
|
+
const v0 = -dims[v] / 2 + row * unitV, v1 = Math.min(v0 + unitV, dims[v] / 2);
|
|
437
|
+
const shift = units.bond === "running" && row % 2 === 1 ? unitU / 2 : 0;
|
|
438
|
+
for (let col = 0; col < colsFor(unitU); col++) {
|
|
439
|
+
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);
|
|
440
|
+
if (u1 - u0 < 0.01) continue;
|
|
441
|
+
const center = [0, 0, 0], size = [0, 0, 0];
|
|
442
|
+
center[u] = (u0 + u1) / 2;
|
|
443
|
+
center[v] = (v0 + v1) / 2;
|
|
444
|
+
size[u] = u1 - u0;
|
|
445
|
+
size[v] = v1 - v0;
|
|
446
|
+
size[thin] = dims[thin];
|
|
447
|
+
cells.push({ center, size });
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return cells;
|
|
451
|
+
};
|
|
452
|
+
var planeDistance = (cell, thin, point) => {
|
|
453
|
+
const [u, v] = planeAxes(thin);
|
|
454
|
+
const du = cell.center[u] - point[u], dv = cell.center[v] - point[v];
|
|
455
|
+
return Math.sqrt(du * du + dv * dv);
|
|
456
|
+
};
|
|
457
|
+
var cellsWithin = (cells, thin, point, radius) => {
|
|
458
|
+
const inside = cells.filter((c) => planeDistance(c, thin, point) <= radius);
|
|
459
|
+
if (inside.length > 0 || cells.length === 0) return inside;
|
|
460
|
+
return [cells.reduce((best, c) => planeDistance(c, thin, point) < planeDistance(best, thin, point) ? c : best)];
|
|
461
|
+
};
|
|
462
|
+
var cellsOutside = (cells, thin, point, radius) => cells.filter((c) => planeDistance(c, thin, point) > radius);
|
|
463
|
+
var MAX_MARKS_PER_FRAGMENT = 12;
|
|
464
|
+
var marksInBox = (center, size, rotation, holes) => {
|
|
465
|
+
const inverse = rotation ? quat.conjugate(rotation) : void 0;
|
|
466
|
+
const marks = [];
|
|
467
|
+
for (const hole of holes) {
|
|
468
|
+
const offset = [hole.point[0] - center[0], hole.point[1] - center[1], hole.point[2] - center[2]];
|
|
469
|
+
const point = inverse ? rotateVec(inverse, offset) : offset;
|
|
470
|
+
const inside = point.every((value, k) => Math.abs(value) <= size[k] / 2 + hole.diameter / 2);
|
|
471
|
+
if (!inside) continue;
|
|
472
|
+
marks.push({ point, normal: inverse ? rotateVec(inverse, hole.normal) : hole.normal, diameter: hole.diameter });
|
|
473
|
+
if (marks.length >= MAX_MARKS_PER_FRAGMENT) break;
|
|
474
|
+
}
|
|
475
|
+
return marks;
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
// src/layers.ts
|
|
479
|
+
var MAX_SPALLS = 64;
|
|
480
|
+
var thinAxisOf = (dims) => dims[0] <= dims[1] && dims[0] <= dims[2] ? 0 : dims[1] <= dims[2] ? 1 : 2;
|
|
481
|
+
var resolveLayer = (def, index, mat) => ({ index, def, mat, energy: mat.penetration * def.thickness * 1e3 });
|
|
482
|
+
var onFace = (layer, face) => layer.def.faces === "both" || layer.def.faces === (face > 0 ? "+" : "-");
|
|
483
|
+
var isSpalled = (spalls, layer, face, point) => {
|
|
484
|
+
for (const s of spalls ?? []) {
|
|
485
|
+
if (s[0] !== layer || s[1] !== face) continue;
|
|
486
|
+
const dx = s[2] - point[0], dy = s[3] - point[1], dz = s[4] - point[2];
|
|
487
|
+
if (dx * dx + dy * dy + dz * dz <= s[5] * s[5]) return true;
|
|
488
|
+
}
|
|
489
|
+
return false;
|
|
490
|
+
};
|
|
491
|
+
var outerLayer = (layers, spalls, face, point) => layers.find((l) => onFace(l, face) && !isSpalled(spalls, l.index, face, point));
|
|
492
|
+
var layersEnergy = (layers, spalls, face, point) => {
|
|
493
|
+
let sum = 0;
|
|
494
|
+
for (const l of layers) if (onFace(l, face) && !isSpalled(spalls, l.index, face, point)) sum += l.energy;
|
|
495
|
+
return sum;
|
|
496
|
+
};
|
|
497
|
+
var layersThickness = (layers, face) => {
|
|
498
|
+
let sum = 0;
|
|
499
|
+
for (const l of layers) if (onFace(l, face)) sum += l.def.thickness;
|
|
500
|
+
return sum;
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
// src/rebar.ts
|
|
504
|
+
var COVER = 0.03;
|
|
505
|
+
var MAX_BARS_PER_DIRECTION = 24;
|
|
506
|
+
var MAX_MEMBER_BARS_PER_SIDE = 6;
|
|
507
|
+
var STEEL_DENSITY = 7850;
|
|
508
|
+
var spread = (length, cover, spacing, cap) => {
|
|
509
|
+
const room = Math.max(0, length - 2 * cover);
|
|
510
|
+
const count = Math.min(cap, Math.max(2, Math.floor(room / spacing) + 1));
|
|
511
|
+
const step = count > 1 && room / (count - 1) < spacing ? room / (count - 1) : spacing;
|
|
512
|
+
return Array.from({ length: count }, (_, i) => (i - (count - 1) / 2) * step);
|
|
513
|
+
};
|
|
514
|
+
var bar = (axis, along, fixed) => {
|
|
515
|
+
const a = [0, 0, 0], b = [0, 0, 0];
|
|
516
|
+
for (const [index, value] of fixed) {
|
|
517
|
+
a[index] = value;
|
|
518
|
+
b[index] = value;
|
|
519
|
+
}
|
|
520
|
+
a[axis] = -along;
|
|
521
|
+
b[axis] = along;
|
|
522
|
+
return [a, b];
|
|
523
|
+
};
|
|
524
|
+
var isMember = (dims) => {
|
|
525
|
+
const [a = 0, b = 0, c = 0] = [...dims].sort((x, y) => x - y);
|
|
526
|
+
return b / a < 3 && c / b >= 3;
|
|
527
|
+
};
|
|
528
|
+
var rebarBars = (dims, thin, spacing) => {
|
|
529
|
+
const out = [];
|
|
530
|
+
if (isMember(dims)) {
|
|
531
|
+
const long = dims.indexOf(Math.max(...dims));
|
|
532
|
+
const [u2 = 0, v2 = 0] = [0, 1, 2].filter((i) => i !== long);
|
|
533
|
+
const us = spread(dims[u2], COVER, spacing, MAX_MEMBER_BARS_PER_SIDE);
|
|
534
|
+
const vs = spread(dims[v2], COVER, spacing, MAX_MEMBER_BARS_PER_SIDE);
|
|
535
|
+
us.forEach((x, i) => vs.forEach((y, j) => {
|
|
536
|
+
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]]));
|
|
537
|
+
}));
|
|
538
|
+
return out;
|
|
539
|
+
}
|
|
540
|
+
const [u = 0, v = 0] = [0, 1, 2].filter((i) => i !== thin);
|
|
541
|
+
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]]));
|
|
542
|
+
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]]));
|
|
543
|
+
return out;
|
|
544
|
+
};
|
|
545
|
+
var clipBarsToDisc = (bars, u, v, center, radius) => {
|
|
546
|
+
const out = [];
|
|
547
|
+
for (const [a, b] of bars) {
|
|
548
|
+
const axis = a[u] !== b[u] ? u : v;
|
|
549
|
+
const other = axis === u ? v : u;
|
|
550
|
+
const off = a[other] - center[other === u ? 0 : 1];
|
|
551
|
+
if (Math.abs(off) >= radius) continue;
|
|
552
|
+
const half = Math.sqrt(radius * radius - off * off);
|
|
553
|
+
const mid = center[axis === u ? 0 : 1];
|
|
554
|
+
const lo = Math.max(Math.min(a[axis], b[axis]), mid - half), hi = Math.min(Math.max(a[axis], b[axis]), mid + half);
|
|
555
|
+
if (hi - lo < 1e-6) continue;
|
|
556
|
+
const p = [a[0], a[1], a[2]], q = [b[0], b[1], b[2]];
|
|
557
|
+
p[axis] = lo;
|
|
558
|
+
q[axis] = hi;
|
|
559
|
+
out.push([p, q]);
|
|
560
|
+
}
|
|
561
|
+
return out;
|
|
562
|
+
};
|
|
563
|
+
var rebarMass = (bars, diameter, density = STEEL_DENSITY) => {
|
|
564
|
+
let length = 0;
|
|
565
|
+
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]);
|
|
566
|
+
return Math.PI * (diameter / 2) * (diameter / 2) * length * density;
|
|
567
|
+
};
|
|
568
|
+
|
|
422
569
|
// src/data/materials.json
|
|
423
570
|
var materials_default = [
|
|
424
571
|
{
|
|
@@ -1427,9 +1574,19 @@ var fragility = (ductility, thickness) => {
|
|
|
1427
1574
|
var DMG_PER_J = 0.02;
|
|
1428
1575
|
var BLAST_COUPLING = 0.1;
|
|
1429
1576
|
var FLAMMABLE_FLUIDS = ["gas", "fuel"];
|
|
1577
|
+
var BREACH_RADIUS = 0.55;
|
|
1578
|
+
var BREACH_BLAST_FACTOR = 6;
|
|
1579
|
+
var BREACH_AREA = 0.25;
|
|
1580
|
+
var RESIDUAL_CAPACITY = 0.15;
|
|
1581
|
+
var UNIT_FRAGMENT_FACTOR = 4;
|
|
1582
|
+
var BREACH_GRID_CAP = 4096;
|
|
1430
1583
|
var CUT_SLICE = 1;
|
|
1431
1584
|
var cutFactorOf = (shape) => clamp2(Math.max(...shapeDims(shape)) / CUT_SLICE, 1, 50);
|
|
1432
1585
|
var clamp2 = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
1586
|
+
var capacityScale = (p) => {
|
|
1587
|
+
if (p.st.stripped) return p.spec.reinforcement?.residualCapacity ?? RESIDUAL_CAPACITY;
|
|
1588
|
+
return p.rt.removed ? Math.max(0.05, 1 - p.rt.removed / p.mass0) : 1;
|
|
1589
|
+
};
|
|
1433
1590
|
var isLost = (p) => p.st.status === "broken" || p.st.status === "melted" || p.st.status === "ashes";
|
|
1434
1591
|
var clone = (v) => structuredClone(v);
|
|
1435
1592
|
var overloadRate = (stress) => Math.min(0.6, 0.12 + 0.8 * (stress - 1));
|
|
@@ -1452,6 +1609,8 @@ var createDestruction = (options = {}) => {
|
|
|
1452
1609
|
const maxDents = options.maxDents ?? 24;
|
|
1453
1610
|
const maxHoles = options.maxHoles ?? 32;
|
|
1454
1611
|
const fireSpread = options.fireSpread ?? 0.6;
|
|
1612
|
+
const breachRadius = options.breach?.radius ?? BREACH_RADIUS;
|
|
1613
|
+
const breachBlastFactor = options.breach?.blastFactor ?? BREACH_BLAST_FACTOR;
|
|
1455
1614
|
const rng = createRng(options.seed ?? 1);
|
|
1456
1615
|
const rnd = () => rng.next();
|
|
1457
1616
|
let time = 0;
|
|
@@ -1460,6 +1619,8 @@ var createDestruction = (options = {}) => {
|
|
|
1460
1619
|
const listeners = /* @__PURE__ */ new Map();
|
|
1461
1620
|
let cur = [];
|
|
1462
1621
|
const blasts = [];
|
|
1622
|
+
const frontQueue = [];
|
|
1623
|
+
const blastFrontSpeed = options.blastFrontSpeed ?? 0;
|
|
1463
1624
|
const primed = /* @__PURE__ */ new Map();
|
|
1464
1625
|
let orderCounter = 0;
|
|
1465
1626
|
const impls = /* @__PURE__ */ new Map();
|
|
@@ -1556,6 +1717,13 @@ var createDestruction = (options = {}) => {
|
|
|
1556
1717
|
const maxHp = Math.max(1, ps.hp ?? mass0 * mat.hpPerKg);
|
|
1557
1718
|
const fuel0 = mat.thermal.ignitionC !== void 0 ? mass0 * (mat.thermal.fuelFraction ?? 0) : 0;
|
|
1558
1719
|
const c = carry?.parts.get(ps.id);
|
|
1720
|
+
const staged = !!(ps.layers?.length || ps.reinforcement || ps.units || ps.breach);
|
|
1721
|
+
const layers = (ps.layers ?? []).map((l, i) => {
|
|
1722
|
+
const lm = materials.get(l.material);
|
|
1723
|
+
if (!lm) throw new Error(`unknown material "${l.material}" on layer ${i} of part "${ps.id}"`);
|
|
1724
|
+
return resolveLayer(l, i, lm);
|
|
1725
|
+
});
|
|
1726
|
+
if (ps.reinforcement && !materials.has(ps.reinforcement.material)) throw new Error(`unknown material "${ps.reinforcement.material}" on the reinforcement of part "${ps.id}"`);
|
|
1559
1727
|
const pst = c?.st ?? {
|
|
1560
1728
|
id: ps.id,
|
|
1561
1729
|
material: ps.material,
|
|
@@ -1571,7 +1739,8 @@ var createDestruction = (options = {}) => {
|
|
|
1571
1739
|
dents: [],
|
|
1572
1740
|
holes: [],
|
|
1573
1741
|
cracks: [],
|
|
1574
|
-
group: 0
|
|
1742
|
+
group: 0,
|
|
1743
|
+
...staged ? { stage: 0 } : {}
|
|
1575
1744
|
};
|
|
1576
1745
|
if (!c && ps.contains) pst.liters = ps.contains.liters;
|
|
1577
1746
|
const p = {
|
|
@@ -1590,7 +1759,10 @@ var createDestruction = (options = {}) => {
|
|
|
1590
1759
|
rt: c?.rt ?? { ...freshRuntime(), prevTemp: pst.temperature },
|
|
1591
1760
|
structural: !!ps.structure && ps.structure.role !== "none",
|
|
1592
1761
|
anchored: !!ps.anchored || ps.structure?.role === "foundation",
|
|
1593
|
-
cap: ps.structure ? partCapacity(ps, mat) : 0
|
|
1762
|
+
cap: ps.structure ? partCapacity(ps, mat) : 0,
|
|
1763
|
+
staged,
|
|
1764
|
+
thin: thinAxisOf(shapeDims(ps.shape)),
|
|
1765
|
+
layers
|
|
1594
1766
|
};
|
|
1595
1767
|
if (p.structural) o.hasStructure = true;
|
|
1596
1768
|
if (p.rt.chainDue !== void 0 && !isLost(p)) primed.set(p, o);
|
|
@@ -1730,7 +1902,7 @@ var createDestruction = (options = {}) => {
|
|
|
1730
1902
|
o.unsup[i] = o.anyAnchor && !isLost(p);
|
|
1731
1903
|
continue;
|
|
1732
1904
|
}
|
|
1733
|
-
p.st.stress = p.st.load / p.cap;
|
|
1905
|
+
p.st.stress = p.st.load / (p.cap * capacityScale(p));
|
|
1734
1906
|
if (p.st.stress > o.maxStress) o.maxStress = p.st.stress;
|
|
1735
1907
|
}
|
|
1736
1908
|
if (o.maxStress > 0.8) o.active = true;
|
|
@@ -1749,6 +1921,7 @@ var createDestruction = (options = {}) => {
|
|
|
1749
1921
|
for (const p of ps) {
|
|
1750
1922
|
o.collapsed.add(p.spec.id);
|
|
1751
1923
|
p.st.status = "collapsed";
|
|
1924
|
+
setStage(o, p, 3, {}, src);
|
|
1752
1925
|
}
|
|
1753
1926
|
emit({ ...base(o, src), type: "collapse", parts: ps.map((p) => p.spec.id), mass: ps.reduce((sum, p) => sum + p.st.mass, 0) });
|
|
1754
1927
|
}
|
|
@@ -1802,6 +1975,7 @@ var createDestruction = (options = {}) => {
|
|
|
1802
1975
|
if (isLost(p) || ignore?.has(`${o.id}\0${p.spec.id}`)) continue;
|
|
1803
1976
|
const r = rayPart(o, p, origin, d);
|
|
1804
1977
|
if (!r || r.t0 < 0 || r.t0 > maxDist) continue;
|
|
1978
|
+
if (p.st.opening && insideOpening(o, p, vec.madd(origin, d, r.t0))) continue;
|
|
1805
1979
|
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 };
|
|
1806
1980
|
}
|
|
1807
1981
|
}
|
|
@@ -1910,6 +2084,7 @@ var createDestruction = (options = {}) => {
|
|
|
1910
2084
|
if (p.st.holes.length > maxHoles) p.st.holes.shift();
|
|
1911
2085
|
markDamaged(p);
|
|
1912
2086
|
emit({ ...base(o, src), type: "hole", part: p.spec.id, hole, worldPoint: wp, ...exit ? { exitPoint: exit } : {} });
|
|
2087
|
+
setStage(o, p, 1, {}, src);
|
|
1913
2088
|
return hole;
|
|
1914
2089
|
};
|
|
1915
2090
|
const addCrack = (o, p, wp, wn, length, src) => {
|
|
@@ -1918,6 +2093,7 @@ var createDestruction = (options = {}) => {
|
|
|
1918
2093
|
if (p.st.cracks.length > 16) p.st.cracks.shift();
|
|
1919
2094
|
markDamaged(p);
|
|
1920
2095
|
emit({ ...base(o, src), type: "crack", part: p.spec.id, crack, worldPoint: wp });
|
|
2096
|
+
setStage(o, p, 1, {}, src);
|
|
1921
2097
|
};
|
|
1922
2098
|
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 });
|
|
1923
2099
|
const holeRate = (hole, pressurized) => (pressurized ? 0.5 : 0.1) * (hole.diameter / 9e-3);
|
|
@@ -1966,6 +2142,7 @@ var createDestruction = (options = {}) => {
|
|
|
1966
2142
|
if (split && mine.length) amount /= mine.length;
|
|
1967
2143
|
for (const j of mine) {
|
|
1968
2144
|
if (j.st.open) continue;
|
|
2145
|
+
if (j.spec.kind === "rebar" && (o.parts[j.ia].spec.reinforcement || o.parts[j.ib].spec.reinforcement)) continue;
|
|
1969
2146
|
if (near && jointReach(o, j, near) > Math.max(radius, jointReachLimit(o, j))) continue;
|
|
1970
2147
|
j.st.hp -= amount;
|
|
1971
2148
|
if (j.st.hp <= 0) {
|
|
@@ -1979,23 +2156,172 @@ var createDestruction = (options = {}) => {
|
|
|
1979
2156
|
}
|
|
1980
2157
|
}
|
|
1981
2158
|
};
|
|
2159
|
+
const setStage = (o, p, stage, extra = {}, src) => {
|
|
2160
|
+
if (!p.staged || (p.st.stage ?? 0) >= stage) return;
|
|
2161
|
+
p.st.stage = stage;
|
|
2162
|
+
emit({ ...base(o, src), type: "stage", part: p.spec.id, stage, ...extra });
|
|
2163
|
+
};
|
|
2164
|
+
const faceOf = (o, p, worldNormal) => rotateVec(quat.conjugate(partXf(o, p).q), worldNormal)[p.thin] >= 0 ? 1 : -1;
|
|
2165
|
+
const spallOuter = (o, p, wp, face, normal, radius, src) => {
|
|
2166
|
+
var _a;
|
|
2167
|
+
if (p.layers.length === 0) return;
|
|
2168
|
+
const lp = objLocal(o, wp);
|
|
2169
|
+
const layer = outerLayer(p.layers, p.rt.spalls, face, lp);
|
|
2170
|
+
if (!layer) return;
|
|
2171
|
+
const spalls = (_a = p.rt).spalls ?? (_a.spalls = []);
|
|
2172
|
+
spalls.push([layer.index, face, lp[0], lp[1], lp[2], radius]);
|
|
2173
|
+
if (spalls.length > MAX_SPALLS) spalls.shift();
|
|
2174
|
+
markDamaged(p);
|
|
2175
|
+
emit({ ...base(o, src), type: "spall", part: p.spec.id, layer: layer.index, point: wp, area: Math.PI * radius * radius });
|
|
2176
|
+
debris(o, p, layer.mat.hitDebris, wp, normal, 5, 2, src);
|
|
2177
|
+
setStage(o, p, 1, {}, src);
|
|
2178
|
+
};
|
|
2179
|
+
const insideOpening = (o, p, objectPoint) => {
|
|
2180
|
+
const op = p.st.opening;
|
|
2181
|
+
if (!op) return false;
|
|
2182
|
+
const [u, v] = planeAxes(p.thin);
|
|
2183
|
+
const lp = toLocal(p.pos, p.q, objectPoint);
|
|
2184
|
+
const lc = toLocal(p.pos, p.q, op.center);
|
|
2185
|
+
return Math.hypot(lp[u] - lc[u], lp[v] - lc[v]) <= op.radius;
|
|
2186
|
+
};
|
|
2187
|
+
const breachAreaOf = (p, damage, blast) => damage / (p.mat.hpPerKg * p.mat.density) / Math.max(p.thickness, 5e-3) * (blast ? breachBlastFactor : 1);
|
|
2188
|
+
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 }));
|
|
2189
|
+
const unitsOf = (p) => p.spec.units ?? { size: [p.mat.fragmentSize?.[1] ?? 0.2, p.mat.fragmentSize?.[1] ?? 0.2, p.thickness], bond: "stack" };
|
|
2190
|
+
const FRAGMENT_SHAPE = { shatter: "shard", splinter: "splinter", tear: "scrap" };
|
|
2191
|
+
const cellFragments = (o, p, cells, hitW, dir, speed, mass, tag) => {
|
|
2192
|
+
const { c, q } = partXf(o, p);
|
|
2193
|
+
const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
|
|
2194
|
+
const [u, v] = planeAxes(p.thin);
|
|
2195
|
+
const faceArea = (cell) => cell.size[u] * cell.size[v];
|
|
2196
|
+
const total = cells.reduce((sum, cell) => sum + faceArea(cell), 0) || 1;
|
|
2197
|
+
const shape = p.spec.units ? "chunk" : FRAGMENT_SHAPE[p.mat.fracture] ?? "chunk";
|
|
2198
|
+
return cells.map((cell, i) => {
|
|
2199
|
+
const cw = toWorld(c, q, cell.center);
|
|
2200
|
+
const out = vec.normalize(vec.sub(cw, hitW), [0, 1, 0]);
|
|
2201
|
+
const dv = dir ? vec.normalize(vec.madd(out, dir, 0.6)) : out;
|
|
2202
|
+
const k = speed * (0.6 + 0.8 * rnd());
|
|
2203
|
+
const marks = marksInBox(cell.center, cell.size, void 0, holes);
|
|
2204
|
+
return {
|
|
2205
|
+
id: `${p.spec.id}#${tag}${i}`,
|
|
2206
|
+
part: p.spec.id,
|
|
2207
|
+
material: p.spec.material,
|
|
2208
|
+
shape,
|
|
2209
|
+
center: cw,
|
|
2210
|
+
size: [cell.size[0], cell.size[1], cell.size[2]],
|
|
2211
|
+
rotation: q,
|
|
2212
|
+
velocity: [dv[0] * k, dv[1] * k + 0.5 * rnd(), dv[2] * k],
|
|
2213
|
+
angularVelocity: [(rnd() - 0.5) * 8, (rnd() - 0.5) * 8, (rnd() - 0.5) * 8],
|
|
2214
|
+
mass: mass * faceArea(cell) / total,
|
|
2215
|
+
...marks.length > 0 ? { marks } : {}
|
|
2216
|
+
};
|
|
2217
|
+
});
|
|
2218
|
+
};
|
|
2219
|
+
const worldBars = (o, p, bars) => {
|
|
2220
|
+
const { c, q } = partXf(o, p);
|
|
2221
|
+
return bars.map(([a, b]) => [toWorld(c, q, a), toWorld(c, q, b)]);
|
|
2222
|
+
};
|
|
2223
|
+
const addBreach = (o, p, wp, area, energy, dir, src) => {
|
|
2224
|
+
var _a;
|
|
2225
|
+
if (!p.staged || area <= 0 || (p.st.stage ?? 0) >= 2 || p.st.stripped || isLost(p)) return;
|
|
2226
|
+
const lp = objLocal(o, wp);
|
|
2227
|
+
const list = (_a = p.rt).breach ?? (_a.breach = []);
|
|
2228
|
+
list.push([lp[0], lp[1], lp[2], area]);
|
|
2229
|
+
if (list.length > MAX_SPALLS) list.shift();
|
|
2230
|
+
let total = 0;
|
|
2231
|
+
const sum = [0, 0, 0];
|
|
2232
|
+
for (const [x, y, z, a] of list) {
|
|
2233
|
+
if (vec.distance([x, y, z], lp) > breachRadius) continue;
|
|
2234
|
+
total += a;
|
|
2235
|
+
sum[0] += x * a;
|
|
2236
|
+
sum[1] += y * a;
|
|
2237
|
+
sum[2] += z * a;
|
|
2238
|
+
}
|
|
2239
|
+
if (total < (p.spec.breach?.area ?? BREACH_AREA)) return;
|
|
2240
|
+
openBreach(o, p, [sum[0] / total, sum[1] / total, sum[2] / total], total, energy, dir, src);
|
|
2241
|
+
};
|
|
2242
|
+
const openBreach = (o, p, center, total, energy, dir, src) => {
|
|
2243
|
+
const [u, v] = planeAxes(p.thin);
|
|
2244
|
+
const radius = clamp2(Math.sqrt(total / Math.PI), Math.sqrt((p.spec.breach?.area ?? BREACH_AREA) / Math.PI), breachRadius);
|
|
2245
|
+
const lc = toLocal(p.pos, p.q, center);
|
|
2246
|
+
lc[p.thin] = 0;
|
|
2247
|
+
const { c, q } = partXf(o, p);
|
|
2248
|
+
const worldCenter = toWorld(c, q, lc);
|
|
2249
|
+
if (2 * radius >= Math.min(p.dims[u], p.dims[v])) {
|
|
2250
|
+
failPart(o, p, worldCenter, dir, energy, 12, src);
|
|
2251
|
+
return;
|
|
2252
|
+
}
|
|
2253
|
+
const cells = cellsWithin(unitGrid(p.dims, p.thin, unitsOf(p), BREACH_GRID_CAP), p.thin, lc, radius);
|
|
2254
|
+
const removed = p.st.mass * cells.reduce((sum, cell) => sum + cell.size[u] * cell.size[v], 0) / (p.dims[u] * p.dims[v]);
|
|
2255
|
+
const speed = clamp2(Math.sqrt(2 * energy * 0.1 / Math.max(removed, 0.05)), 0.5, 8);
|
|
2256
|
+
const fragments = cellFragments(o, p, cells, worldCenter, dir, speed, removed, "b");
|
|
2257
|
+
const holes = localHolesOf(p);
|
|
2258
|
+
p.st.holes = p.st.holes.filter((_, i) => !cells.some((cell) => marksInBox(cell.center, cell.size, void 0, [holes[i]]).length > 0));
|
|
2259
|
+
p.st.mass = Math.max(0, p.st.mass - removed);
|
|
2260
|
+
p.rt.removed = (p.rt.removed ?? 0) + removed;
|
|
2261
|
+
p.st.opening = { center: objLocal(o, worldCenter), radius };
|
|
2262
|
+
markDamaged(p);
|
|
2263
|
+
setStage(o, p, 1, {}, src);
|
|
2264
|
+
setStage(o, p, 2, { opening: { center: worldCenter, radius }, fragments }, src);
|
|
2265
|
+
debris(o, p, p.mat.breakDebris, worldCenter, vec.scale(dir ?? [0, 1, 0], -1), 14, 3, src);
|
|
2266
|
+
const reinforcement = p.spec.reinforcement;
|
|
2267
|
+
if (reinforcement) {
|
|
2268
|
+
const bars = worldBars(o, p, clipBarsToDisc(rebarBars(p.dims, p.thin, reinforcement.spacing), u, v, [lc[u], lc[v]], radius + 0.05));
|
|
2269
|
+
if (bars.length > 0) emit({ ...base(o, src), type: "expose", part: p.spec.id, bars });
|
|
2270
|
+
}
|
|
2271
|
+
if (p.structural) solveStructure(o);
|
|
2272
|
+
};
|
|
2273
|
+
const stripPart = (o, p, reinforcement, src) => {
|
|
2274
|
+
const bars = rebarBars(p.dims, p.thin, reinforcement.spacing);
|
|
2275
|
+
p.st.stripped = true;
|
|
2276
|
+
p.st.hp = p.st.maxHp;
|
|
2277
|
+
p.st.mass = rebarMass(bars, reinforcement.diameter, materials.get(reinforcement.material)?.density);
|
|
2278
|
+
p.st.holes = [];
|
|
2279
|
+
p.st.burning = false;
|
|
2280
|
+
p.rt.jet = false;
|
|
2281
|
+
markDamaged(p);
|
|
2282
|
+
emit({ ...base(o, src), type: "expose", part: p.spec.id, bars: worldBars(o, p, bars) });
|
|
2283
|
+
solveStructure(o);
|
|
2284
|
+
collapseCheck(o, src);
|
|
2285
|
+
};
|
|
2286
|
+
const unitFragmentPlan = (p, cap) => {
|
|
2287
|
+
const grid = unitGrid(p.dims, p.thin, unitsOf(p), cap);
|
|
2288
|
+
const op = p.st.opening;
|
|
2289
|
+
const cells = op ? cellsOutside(grid, p.thin, toLocal(p.pos, p.q, op.center), op.radius) : grid;
|
|
2290
|
+
const [u, v] = planeAxes(p.thin);
|
|
2291
|
+
return cells.map((cell) => ({ center: cell.center, size: cell.size, q: quat.identity(), shape: "chunk", weight: cell.size[u] * cell.size[v] }));
|
|
2292
|
+
};
|
|
2293
|
+
const loseCage = (o, p, src) => {
|
|
2294
|
+
p.st.status = "broken";
|
|
2295
|
+
p.st.hp = 0;
|
|
2296
|
+
p.st.mass = 0;
|
|
2297
|
+
emit({ ...base(o, src), type: "expose", part: p.spec.id, bars: [] });
|
|
2298
|
+
debris(o, p, "sparks", partXf(o, p).c, [0, 1, 0], 6, 3, src);
|
|
2299
|
+
partLost(o, p, src);
|
|
2300
|
+
};
|
|
1982
2301
|
const failPart = (o, p, origin, dir, fragEnergy, cap, src) => {
|
|
1983
2302
|
if (isLost(p)) return;
|
|
2303
|
+
if (p.st.stripped) {
|
|
2304
|
+
loseCage(o, p, src);
|
|
2305
|
+
return;
|
|
2306
|
+
}
|
|
1984
2307
|
const ex = p.spec.explodes;
|
|
1985
2308
|
const style = p.mat.fracture === "none" ? "snap" : p.mat.fracture;
|
|
1986
2309
|
const { c, q } = partXf(o, p);
|
|
1987
2310
|
const hitW = origin ?? c;
|
|
1988
2311
|
const grain = p.spec.grain ? AXIS[p.spec.grain] : void 0;
|
|
1989
|
-
const
|
|
2312
|
+
const hitLocal = toLocal(c, q, hitW);
|
|
2313
|
+
const unitPlan = p.spec.units ? unitFragmentPlan(p, maxFragments * UNIT_FRAGMENT_FACTOR) : [];
|
|
2314
|
+
const plan = unitPlan.length > 0 ? unitPlan : planFragments({
|
|
1990
2315
|
style,
|
|
1991
2316
|
dims: p.dims,
|
|
1992
2317
|
thickness: p.thickness,
|
|
1993
2318
|
grain,
|
|
1994
|
-
hit:
|
|
2319
|
+
hit: hitLocal,
|
|
1995
2320
|
fragmentSize: p.mat.fragmentSize ?? [0.04, 0.2],
|
|
1996
2321
|
maxFragments,
|
|
1997
2322
|
rnd
|
|
1998
2323
|
});
|
|
2324
|
+
const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
|
|
1999
2325
|
const mass = p.st.mass;
|
|
2000
2326
|
const totW = plan.reduce((s, f) => s + f.weight, 0) || 1;
|
|
2001
2327
|
const energy = ex ? ex.energy * 0.2 : fragEnergy;
|
|
@@ -2005,6 +2331,7 @@ var createDestruction = (options = {}) => {
|
|
|
2005
2331
|
const out = vec.normalize(vec.sub(cw, hitW), [0, 1, 0]);
|
|
2006
2332
|
const dv = dir ? vec.normalize(vec.madd(out, dir, 0.6)) : out;
|
|
2007
2333
|
const k = speed * (0.6 + 0.8 * rnd());
|
|
2334
|
+
const marks = marksInBox(f.center, f.size, f.q, holes);
|
|
2008
2335
|
return {
|
|
2009
2336
|
id: `${p.spec.id}#${i}`,
|
|
2010
2337
|
part: p.spec.id,
|
|
@@ -2015,18 +2342,27 @@ var createDestruction = (options = {}) => {
|
|
|
2015
2342
|
rotation: quat.mul(q, f.q),
|
|
2016
2343
|
velocity: [dv[0] * k, dv[1] * k + 0.5 * rnd(), dv[2] * k],
|
|
2017
2344
|
angularVelocity: [(rnd() - 0.5) * 12, (rnd() - 0.5) * 12, (rnd() - 0.5) * 12],
|
|
2018
|
-
mass: mass * f.weight / totW
|
|
2345
|
+
mass: mass * f.weight / totW,
|
|
2346
|
+
...marks.length > 0 ? { marks } : {}
|
|
2019
2347
|
};
|
|
2020
2348
|
});
|
|
2021
|
-
p.
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2349
|
+
const reinforcement = p.spec.reinforcement;
|
|
2350
|
+
if (!reinforcement) {
|
|
2351
|
+
p.st.status = "broken";
|
|
2352
|
+
p.st.hp = 0;
|
|
2353
|
+
p.st.mass = 0;
|
|
2354
|
+
p.st.burning = false;
|
|
2355
|
+
p.rt.jet = false;
|
|
2356
|
+
}
|
|
2026
2357
|
emit({ ...base(o, src), type: "fracture", part: p.spec.id, style, fragments, point: hitW });
|
|
2027
2358
|
debris(o, p, p.mat.breakDebris, c, [0, 1, 0], clamp2(Math.round(Math.cbrt(partVolume(p.spec.shape)) * 30), 3, 20), 2, src);
|
|
2359
|
+
setStage(o, p, 3, {}, src);
|
|
2360
|
+
if (reinforcement) {
|
|
2361
|
+
stripPart(o, p, reinforcement, src);
|
|
2362
|
+
return;
|
|
2363
|
+
}
|
|
2028
2364
|
if (ex) {
|
|
2029
|
-
emit({ ...base(o, src), type: "explode", part: p.spec.id, point: c, energy: ex.energy, radius: ex.radius });
|
|
2365
|
+
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 } : {} });
|
|
2030
2366
|
blasts.push({ point: c, energy: ex.energy, radius: ex.radius, source: src });
|
|
2031
2367
|
}
|
|
2032
2368
|
partLost(o, p, src);
|
|
@@ -2058,10 +2394,17 @@ var createDestruction = (options = {}) => {
|
|
|
2058
2394
|
};
|
|
2059
2395
|
const ballistic = (o, p, entry, n, d, E, caliber, pathLen, src) => {
|
|
2060
2396
|
const mat = p.mat;
|
|
2397
|
+
if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry))) {
|
|
2398
|
+
return { penetrated: true, exitEnergy: E, exitPoint: vec.madd(entry, d, Math.min(pathLen, p.thickness)), damage: 0 };
|
|
2399
|
+
}
|
|
2061
2400
|
const sin = Math.min(1, Math.abs(vec.dot(d, n)));
|
|
2062
2401
|
const angle = Math.asin(sin) * 180 / Math.PI;
|
|
2063
|
-
const
|
|
2064
|
-
const
|
|
2402
|
+
const slant = Math.max(sin, 0.3);
|
|
2403
|
+
const effM = p.thickness / slant;
|
|
2404
|
+
const face = p.layers.length > 0 ? faceOf(o, p, n) : 1;
|
|
2405
|
+
const exitLocal = objLocal(o, vec.madd(entry, d, Math.min(pathLen, effM)));
|
|
2406
|
+
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;
|
|
2407
|
+
const thr = coats > 0 ? (mat.penetration * p.thickness * 1e3 + coats) / slant : mat.penetration * effM * 1e3;
|
|
2065
2408
|
const penetrated = E > thr;
|
|
2066
2409
|
const scale = (mat.damageScale?.ballistic ?? 1) * (mat.fracture === "shatter" ? fragility(mat.ductility, p.thickness) : 1);
|
|
2067
2410
|
const id = p.spec.id;
|
|
@@ -2072,13 +2415,16 @@ var createDestruction = (options = {}) => {
|
|
|
2072
2415
|
const dmg2 = applyDamage(o, p, E * DMG_PER_J * scale * 0.2, "ballistic", entry, d, E * 0.1, 12, src);
|
|
2073
2416
|
return { penetrated: false, exitEnergy: 0, damage: dmg2 };
|
|
2074
2417
|
}
|
|
2418
|
+
if (p.layers.length > 0) spallOuter(o, p, entry, face, n, Math.max(0.03, caliber / 1e3 * 4), src);
|
|
2075
2419
|
const brittle = mat.ductility < 0.3;
|
|
2076
2420
|
const ratio = E / Math.max(thr, 1e-6);
|
|
2077
2421
|
const crackLen = clamp2(0.03 + ratio * 0.2, 0.03, 0.8);
|
|
2078
2422
|
let exitPoint;
|
|
2079
2423
|
if (penetrated) {
|
|
2080
|
-
exitPoint = vec.madd(entry, d, Math.min(pathLen, effM));
|
|
2424
|
+
exitPoint = vec.madd(entry, d, Math.min(pathLen, effM + layersThickness(p.layers, face) / slant + layersThickness(p.layers, -face) / slant));
|
|
2081
2425
|
const hole = addHole(o, p, entry, n, caliber / 1e3 * (mat.ductility >= 0.3 ? 1 : 1.5), true, exitPoint, src);
|
|
2426
|
+
if (p.layers.length > 0) spallOuter(o, p, exitPoint, -face, vec.scale(n, -1), Math.max(0.03, caliber / 1e3 * 4), src);
|
|
2427
|
+
addBreach(o, p, entry, Math.PI * (hole.diameter / 2) * (hole.diameter / 2), E, d, src);
|
|
2082
2428
|
if (brittle && mat.fracture === "shatter") addCrack(o, p, entry, n, crackLen, src);
|
|
2083
2429
|
if (p.spec.contains && (p.st.liters ?? 0) > 0) {
|
|
2084
2430
|
emit({
|
|
@@ -2123,8 +2469,11 @@ var createDestruction = (options = {}) => {
|
|
|
2123
2469
|
}
|
|
2124
2470
|
if (kind === "blunt") wearJoints(o, p, E * 0.3, void 0, 0, src);
|
|
2125
2471
|
else wearJoints(o, p, E * 0.3, point, 0.06, src);
|
|
2472
|
+
if (kind === "blunt" && E > 0 && p.layers.length > 0) spallOuter(o, p, point, faceOf(o, p, n), n, Math.max(contactR, 0.05), src);
|
|
2126
2473
|
const scale = mat.damageScale?.[kind] ?? 1;
|
|
2127
|
-
|
|
2474
|
+
const done = applyDamage(o, p, Ec * DMG_PER_J * scale * (kind === "slash" ? p.cutFactor : 1), kind, point, d, E * 0.1, 12, src);
|
|
2475
|
+
addBreach(o, p, point, breachAreaOf(p, done, false), E, d, src);
|
|
2476
|
+
return done;
|
|
2128
2477
|
};
|
|
2129
2478
|
const blastObject = (o, point, energy, radius, src) => {
|
|
2130
2479
|
const start = cur.length;
|
|
@@ -2160,7 +2509,15 @@ var createDestruction = (options = {}) => {
|
|
|
2160
2509
|
}
|
|
2161
2510
|
}
|
|
2162
2511
|
wearJoints(o, p, Ein * 0.5, void 0, 0, src);
|
|
2163
|
-
|
|
2512
|
+
const amount = Ein * DMG_PER_J * (mat.damageScale?.explosive ?? 1);
|
|
2513
|
+
if (blastFrontSpeed > 0 && dist > 0 && mat.tags?.includes("glass")) {
|
|
2514
|
+
frontQueue.push({ o, p, due: time + dist / blastFrontSpeed, amount, fragEnergy: Ein * 0.3, point, ...src !== void 0 ? { src } : {} });
|
|
2515
|
+
continue;
|
|
2516
|
+
}
|
|
2517
|
+
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);
|
|
2518
|
+
const dealt = applyDamage(o, p, amount, "explosive", point, null, Ein * 0.3, 40, src);
|
|
2519
|
+
res.damage += dealt;
|
|
2520
|
+
addBreach(o, p, wp, breachAreaOf(p, dealt, true), Ein, null, src);
|
|
2164
2521
|
}
|
|
2165
2522
|
res.events = cur.slice(start);
|
|
2166
2523
|
return res;
|
|
@@ -2431,7 +2788,7 @@ var createDestruction = (options = {}) => {
|
|
|
2431
2788
|
}
|
|
2432
2789
|
o.active = hot;
|
|
2433
2790
|
};
|
|
2434
|
-
const
|
|
2791
|
+
const spread2 = (sources, dt) => {
|
|
2435
2792
|
for (const src of sources) {
|
|
2436
2793
|
if (!src.p.st.burning && !src.p.rt.jet) continue;
|
|
2437
2794
|
const sc = toWorld(src.o.st.position, src.o.st.rotation, src.p.pos);
|
|
@@ -2483,6 +2840,15 @@ var createDestruction = (options = {}) => {
|
|
|
2483
2840
|
}
|
|
2484
2841
|
time = end;
|
|
2485
2842
|
};
|
|
2843
|
+
const releaseFront = () => {
|
|
2844
|
+
if (frontQueue.length === 0) return;
|
|
2845
|
+
frontQueue.sort((a, b) => a.due - b.due);
|
|
2846
|
+
while (frontQueue.length > 0 && frontQueue[0].due <= time) {
|
|
2847
|
+
const hit = frontQueue.shift();
|
|
2848
|
+
if (objects.get(hit.o.id) !== hit.o) continue;
|
|
2849
|
+
applyDamage(hit.o, hit.p, hit.amount, "explosive", hit.point, null, hit.fragEnergy, 40, hit.src);
|
|
2850
|
+
}
|
|
2851
|
+
};
|
|
2486
2852
|
const withCollapses = (r) => {
|
|
2487
2853
|
const last = r.value[r.value.length - 1];
|
|
2488
2854
|
if (!last) return r.value;
|
|
@@ -2732,8 +3098,9 @@ var createDestruction = (options = {}) => {
|
|
|
2732
3098
|
time += dt;
|
|
2733
3099
|
const sources = [];
|
|
2734
3100
|
for (const o of [...objects.values()]) if (o.active) stepObject(o, dt, sources);
|
|
2735
|
-
if (sources.length)
|
|
3101
|
+
if (sources.length) spread2(sources, dt);
|
|
2736
3102
|
detonatePrimed(time);
|
|
3103
|
+
releaseFront();
|
|
2737
3104
|
for (const im of [...impls.values()]) {
|
|
2738
3105
|
if (!im.impl.update) continue;
|
|
2739
3106
|
for (const e of im.impl.update(dt) ?? []) emit(e.time === void 0 ? { ...e, time } : e);
|
|
@@ -2771,7 +3138,7 @@ var createDestruction = (options = {}) => {
|
|
|
2771
3138
|
counter,
|
|
2772
3139
|
objects: [...objects.values()].map((o) => {
|
|
2773
3140
|
const runtime = { parts: {}, joints: {}, collapsed: [...o.collapsed] };
|
|
2774
|
-
for (const p of o.parts) runtime.parts[p.spec.id] =
|
|
3141
|
+
for (const p of o.parts) runtime.parts[p.spec.id] = clone(p.rt);
|
|
2775
3142
|
for (const j of o.joints) runtime.joints[j.spec.id] = j.step;
|
|
2776
3143
|
return { id: o.id, spec: clone(o.spec), state: clone(o.st), runtime };
|
|
2777
3144
|
}),
|
|
@@ -2788,7 +3155,7 @@ var createDestruction = (options = {}) => {
|
|
|
2788
3155
|
const state = clone(e.state);
|
|
2789
3156
|
const carry = {
|
|
2790
3157
|
parts: new Map(
|
|
2791
|
-
state.parts.map((st) => [st.id, { st, rt: e.runtime?.parts[st.id] ?
|
|
3158
|
+
state.parts.map((st) => [st.id, { st, rt: e.runtime?.parts[st.id] ? clone(e.runtime.parts[st.id]) : { ...freshRuntime(), prevTemp: st.temperature } }])
|
|
2792
3159
|
),
|
|
2793
3160
|
joints: new Map(state.joints.map((st) => [st.id, { st, step: e.runtime?.joints[st.id] ?? 0 }])),
|
|
2794
3161
|
collapsed: e.runtime?.collapsed
|
|
@@ -2807,6 +3174,109 @@ var createDestruction = (options = {}) => {
|
|
|
2807
3174
|
return world;
|
|
2808
3175
|
};
|
|
2809
3176
|
|
|
3177
|
+
// src/data/assemblies.json
|
|
3178
|
+
var assemblies_default = [
|
|
3179
|
+
{
|
|
3180
|
+
id: "brick_wall_plastered",
|
|
3181
|
+
name: "Plastered brick wall",
|
|
3182
|
+
core: { material: "brick", thickness: 0.09 },
|
|
3183
|
+
layers: [{ material: "plaster", thickness: 0.02, faces: "both" }],
|
|
3184
|
+
units: { size: [0.23, 0.075, 0.11], bond: "running", mortar: "plaster" },
|
|
3185
|
+
breach: { area: 0.25 }
|
|
3186
|
+
},
|
|
3187
|
+
{
|
|
3188
|
+
id: "concrete_block_wall",
|
|
3189
|
+
name: "Concrete block wall",
|
|
3190
|
+
core: { material: "concrete", thickness: 0.14 },
|
|
3191
|
+
fill: 0.55,
|
|
3192
|
+
units: { size: [0.39, 0.19, 0.14], bond: "running", mortar: "plaster" },
|
|
3193
|
+
breach: { area: 0.25 }
|
|
3194
|
+
},
|
|
3195
|
+
{
|
|
3196
|
+
id: "reinforced_concrete",
|
|
3197
|
+
name: "Reinforced concrete wall",
|
|
3198
|
+
core: { material: "concrete", thickness: 0.2 },
|
|
3199
|
+
reinforcement: { material: "steel", spacing: 0.18, diameter: 8e-3, residualCapacity: 0.15 },
|
|
3200
|
+
units: { size: [0.6, 0.42, 0.2], bond: "stack", mortar: "concrete" },
|
|
3201
|
+
breach: { area: 0.25 }
|
|
3202
|
+
},
|
|
3203
|
+
{
|
|
3204
|
+
id: "drywall",
|
|
3205
|
+
name: "Drywall partition on steel studs",
|
|
3206
|
+
core: { material: "plaster", thickness: 0.0125 },
|
|
3207
|
+
layers: [{ material: "plaster", thickness: 0.0125, faces: "+" }],
|
|
3208
|
+
depth: 0.095,
|
|
3209
|
+
breach: { area: 0.25 }
|
|
3210
|
+
},
|
|
3211
|
+
{
|
|
3212
|
+
id: "wood_frame_wall",
|
|
3213
|
+
name: "Wood frame wall",
|
|
3214
|
+
core: { material: "plywood", thickness: 0.012 },
|
|
3215
|
+
layers: [{ material: "plaster", thickness: 0.0125, faces: "-" }],
|
|
3216
|
+
depth: 0.1135,
|
|
3217
|
+
breach: { area: 0.25 }
|
|
3218
|
+
},
|
|
3219
|
+
{
|
|
3220
|
+
id: "log_wall",
|
|
3221
|
+
name: "Log wall",
|
|
3222
|
+
core: { material: "wood_soft", thickness: 0.2 },
|
|
3223
|
+
fill: 0.785,
|
|
3224
|
+
breach: { area: 0.25 }
|
|
3225
|
+
},
|
|
3226
|
+
{
|
|
3227
|
+
id: "sheet_metal_shack",
|
|
3228
|
+
name: "Corrugated sheet metal wall",
|
|
3229
|
+
core: { material: "steel", thickness: 6e-4 },
|
|
3230
|
+
depth: 0.02,
|
|
3231
|
+
breach: { area: 0.25 }
|
|
3232
|
+
},
|
|
3233
|
+
{
|
|
3234
|
+
id: "glass_pane",
|
|
3235
|
+
name: "Glass pane",
|
|
3236
|
+
core: { material: "glass", thickness: 6e-3 },
|
|
3237
|
+
breach: { area: 0.1 }
|
|
3238
|
+
}
|
|
3239
|
+
];
|
|
3240
|
+
|
|
3241
|
+
// src/assemblies.ts
|
|
3242
|
+
var ASSEMBLIES = assemblies_default;
|
|
3243
|
+
var registerAssembly = (def) => {
|
|
3244
|
+
const at = ASSEMBLIES.findIndex((a) => a.id === def.id);
|
|
3245
|
+
if (at >= 0) ASSEMBLIES[at] = def;
|
|
3246
|
+
else ASSEMBLIES.push(def);
|
|
3247
|
+
};
|
|
3248
|
+
var getAssembly = (id) => ASSEMBLIES.find((a) => a.id === id);
|
|
3249
|
+
var faceCount = (layer) => layer.faces === "both" ? 2 : 1;
|
|
3250
|
+
var assemblyDepth = (def) => def.depth ?? def.core.thickness + (def.layers ?? []).reduce((sum, l) => sum + l.thickness * faceCount(l), 0);
|
|
3251
|
+
var assemblyPart = (assemblyId, options) => {
|
|
3252
|
+
const def = getAssembly(assemblyId);
|
|
3253
|
+
if (!def) throw new Error(`unknown assembly "${assemblyId}"`);
|
|
3254
|
+
const densityOf = (id) => {
|
|
3255
|
+
const m = options.materials?.get(id) ?? MATERIALS.find((x) => x.id === id);
|
|
3256
|
+
if (!m) throw new Error(`unknown material "${id}" in assembly "${assemblyId}"`);
|
|
3257
|
+
return m.density;
|
|
3258
|
+
};
|
|
3259
|
+
const fill = def.fill ?? 1;
|
|
3260
|
+
const area = options.width * options.height;
|
|
3261
|
+
const coreMass = area * def.core.thickness * fill * densityOf(def.core.material);
|
|
3262
|
+
const layerMass = (def.layers ?? []).reduce((sum, l) => sum + area * l.thickness * faceCount(l) * densityOf(l.material), 0);
|
|
3263
|
+
return {
|
|
3264
|
+
id: options.id,
|
|
3265
|
+
material: def.core.material,
|
|
3266
|
+
shape: { kind: "box", size: [options.width, options.height, assemblyDepth(def)] },
|
|
3267
|
+
position: options.position,
|
|
3268
|
+
...options.rotation ? { rotation: options.rotation } : {},
|
|
3269
|
+
mass: coreMass + layerMass,
|
|
3270
|
+
thickness: def.core.thickness * fill,
|
|
3271
|
+
...def.layers ? { layers: def.layers.map((l) => ({ ...l })) } : {},
|
|
3272
|
+
...def.units ? { units: { ...def.units, size: [...def.units.size] } } : {},
|
|
3273
|
+
...def.reinforcement ? { reinforcement: { ...def.reinforcement } } : {},
|
|
3274
|
+
...def.breach ? { breach: { ...def.breach } } : {},
|
|
3275
|
+
...options.structure ? { structure: options.structure } : {},
|
|
3276
|
+
...options.extra
|
|
3277
|
+
};
|
|
3278
|
+
};
|
|
3279
|
+
|
|
2810
3280
|
// src/frame.ts
|
|
2811
3281
|
var buildFrame = (o = {}) => {
|
|
2812
3282
|
const spanX = o.spanX ?? 8, spanZ = o.spanZ ?? 8;
|
|
@@ -2914,6 +3384,8 @@ var buildFrame = (o = {}) => {
|
|
|
2914
3384
|
var sizeStructure = (spec, options = {}) => {
|
|
2915
3385
|
const safety = options.safety ?? 0.65;
|
|
2916
3386
|
const redundancy = options.redundancy;
|
|
3387
|
+
const redundancyRoles = options.redundancyRoles ?? ["column"];
|
|
3388
|
+
const safetyOf = (role) => options.safetyByRole?.[role] ?? safety;
|
|
2917
3389
|
const loads = (s) => {
|
|
2918
3390
|
const w = createDestruction(options.materials ? { materials: options.materials } : {});
|
|
2919
3391
|
const id = w.add(s);
|
|
@@ -2926,11 +3398,11 @@ var sizeStructure = (spec, options = {}) => {
|
|
|
2926
3398
|
if (!p.structure || p.structure.role === "none") continue;
|
|
2927
3399
|
const b = base.get(p.id);
|
|
2928
3400
|
if (!b || b.stress <= 0) continue;
|
|
2929
|
-
need.set(p.id, b.load /
|
|
3401
|
+
need.set(p.id, b.load / safetyOf(p.structure.role));
|
|
2930
3402
|
}
|
|
2931
3403
|
if (redundancy !== void 0) {
|
|
2932
3404
|
for (const col of out.parts) {
|
|
2933
|
-
if (col.structure
|
|
3405
|
+
if (!col.structure || !redundancyRoles.includes(col.structure.role)) continue;
|
|
2934
3406
|
const reduced = {
|
|
2935
3407
|
...out,
|
|
2936
3408
|
parts: out.parts.filter((p) => p.id !== col.id),
|
|
@@ -3030,6 +3502,10 @@ var locales = {
|
|
|
3030
3502
|
primed: "about to explode",
|
|
3031
3503
|
stress: "overload",
|
|
3032
3504
|
collapse: "collapsed",
|
|
3505
|
+
spall: "plaster came off",
|
|
3506
|
+
stage: "damage stage",
|
|
3507
|
+
expose: "rebar bare",
|
|
3508
|
+
body: "body",
|
|
3033
3509
|
destroyed: "destroyed"
|
|
3034
3510
|
},
|
|
3035
3511
|
fractureStyles: { shatter: "shatter", crumble: "crumble", splinter: "splinter", tear: "tear", snap: "snap", none: "none" },
|
|
@@ -3112,6 +3588,10 @@ var locales = {
|
|
|
3112
3588
|
primed: "prestes a explodir",
|
|
3113
3589
|
stress: "sobrecarga",
|
|
3114
3590
|
collapse: "desabou",
|
|
3591
|
+
spall: "reboco soltou",
|
|
3592
|
+
stage: "est\xE1gio de dano",
|
|
3593
|
+
expose: "vergalh\xE3o \xE0 mostra",
|
|
3594
|
+
body: "corpo",
|
|
3115
3595
|
destroyed: "destru\xEDdo"
|
|
3116
3596
|
},
|
|
3117
3597
|
fractureStyles: { shatter: "estilha\xE7ar", crumble: "esfarelar", splinter: "lascar", tear: "rasgar", snap: "partir", none: "nenhuma" },
|
|
@@ -3128,17 +3608,21 @@ var checkDestructible = (impl, options = {}) => {
|
|
|
3128
3608
|
return check(impl, contractOptions);
|
|
3129
3609
|
};
|
|
3130
3610
|
export {
|
|
3611
|
+
ASSEMBLIES,
|
|
3131
3612
|
FIRE_PRIME_C,
|
|
3132
3613
|
IMPACT_CONTRACT,
|
|
3133
3614
|
JOINT_BY_MATERIALS,
|
|
3134
3615
|
JOINT_DEFAULTS,
|
|
3135
3616
|
MATERIALS,
|
|
3617
|
+
assemblyDepth,
|
|
3618
|
+
assemblyPart,
|
|
3136
3619
|
buildFrame,
|
|
3137
3620
|
checkDestructible,
|
|
3138
3621
|
checkHit,
|
|
3139
3622
|
createDestruction,
|
|
3140
3623
|
explosivePart,
|
|
3141
3624
|
fragility,
|
|
3625
|
+
getAssembly,
|
|
3142
3626
|
guessMaterial,
|
|
3143
3627
|
inferJoints,
|
|
3144
3628
|
locales,
|
|
@@ -3148,6 +3632,7 @@ export {
|
|
|
3148
3632
|
partVolume,
|
|
3149
3633
|
quat,
|
|
3150
3634
|
quatFromEuler,
|
|
3635
|
+
registerAssembly,
|
|
3151
3636
|
resolveStrings,
|
|
3152
3637
|
rotateVec,
|
|
3153
3638
|
sizeStructure,
|