@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.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;
|
|
@@ -1556,6 +1715,13 @@ var createDestruction = (options = {}) => {
|
|
|
1556
1715
|
const maxHp = Math.max(1, ps.hp ?? mass0 * mat.hpPerKg);
|
|
1557
1716
|
const fuel0 = mat.thermal.ignitionC !== void 0 ? mass0 * (mat.thermal.fuelFraction ?? 0) : 0;
|
|
1558
1717
|
const c = carry?.parts.get(ps.id);
|
|
1718
|
+
const staged = !!(ps.layers?.length || ps.reinforcement || ps.units || ps.breach);
|
|
1719
|
+
const layers = (ps.layers ?? []).map((l, i) => {
|
|
1720
|
+
const lm = materials.get(l.material);
|
|
1721
|
+
if (!lm) throw new Error(`unknown material "${l.material}" on layer ${i} of part "${ps.id}"`);
|
|
1722
|
+
return resolveLayer(l, i, lm);
|
|
1723
|
+
});
|
|
1724
|
+
if (ps.reinforcement && !materials.has(ps.reinforcement.material)) throw new Error(`unknown material "${ps.reinforcement.material}" on the reinforcement of part "${ps.id}"`);
|
|
1559
1725
|
const pst = c?.st ?? {
|
|
1560
1726
|
id: ps.id,
|
|
1561
1727
|
material: ps.material,
|
|
@@ -1571,7 +1737,8 @@ var createDestruction = (options = {}) => {
|
|
|
1571
1737
|
dents: [],
|
|
1572
1738
|
holes: [],
|
|
1573
1739
|
cracks: [],
|
|
1574
|
-
group: 0
|
|
1740
|
+
group: 0,
|
|
1741
|
+
...staged ? { stage: 0 } : {}
|
|
1575
1742
|
};
|
|
1576
1743
|
if (!c && ps.contains) pst.liters = ps.contains.liters;
|
|
1577
1744
|
const p = {
|
|
@@ -1590,7 +1757,10 @@ var createDestruction = (options = {}) => {
|
|
|
1590
1757
|
rt: c?.rt ?? { ...freshRuntime(), prevTemp: pst.temperature },
|
|
1591
1758
|
structural: !!ps.structure && ps.structure.role !== "none",
|
|
1592
1759
|
anchored: !!ps.anchored || ps.structure?.role === "foundation",
|
|
1593
|
-
cap: ps.structure ? partCapacity(ps, mat) : 0
|
|
1760
|
+
cap: ps.structure ? partCapacity(ps, mat) : 0,
|
|
1761
|
+
staged,
|
|
1762
|
+
thin: thinAxisOf(shapeDims(ps.shape)),
|
|
1763
|
+
layers
|
|
1594
1764
|
};
|
|
1595
1765
|
if (p.structural) o.hasStructure = true;
|
|
1596
1766
|
if (p.rt.chainDue !== void 0 && !isLost(p)) primed.set(p, o);
|
|
@@ -1730,7 +1900,7 @@ var createDestruction = (options = {}) => {
|
|
|
1730
1900
|
o.unsup[i] = o.anyAnchor && !isLost(p);
|
|
1731
1901
|
continue;
|
|
1732
1902
|
}
|
|
1733
|
-
p.st.stress = p.st.load / p.cap;
|
|
1903
|
+
p.st.stress = p.st.load / (p.cap * capacityScale(p));
|
|
1734
1904
|
if (p.st.stress > o.maxStress) o.maxStress = p.st.stress;
|
|
1735
1905
|
}
|
|
1736
1906
|
if (o.maxStress > 0.8) o.active = true;
|
|
@@ -1749,6 +1919,7 @@ var createDestruction = (options = {}) => {
|
|
|
1749
1919
|
for (const p of ps) {
|
|
1750
1920
|
o.collapsed.add(p.spec.id);
|
|
1751
1921
|
p.st.status = "collapsed";
|
|
1922
|
+
setStage(o, p, 3, {}, src);
|
|
1752
1923
|
}
|
|
1753
1924
|
emit({ ...base(o, src), type: "collapse", parts: ps.map((p) => p.spec.id), mass: ps.reduce((sum, p) => sum + p.st.mass, 0) });
|
|
1754
1925
|
}
|
|
@@ -1802,6 +1973,7 @@ var createDestruction = (options = {}) => {
|
|
|
1802
1973
|
if (isLost(p) || ignore?.has(`${o.id}\0${p.spec.id}`)) continue;
|
|
1803
1974
|
const r = rayPart(o, p, origin, d);
|
|
1804
1975
|
if (!r || r.t0 < 0 || r.t0 > maxDist) continue;
|
|
1976
|
+
if (p.st.opening && insideOpening(o, p, vec.madd(origin, d, r.t0))) continue;
|
|
1805
1977
|
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
1978
|
}
|
|
1807
1979
|
}
|
|
@@ -1910,6 +2082,7 @@ var createDestruction = (options = {}) => {
|
|
|
1910
2082
|
if (p.st.holes.length > maxHoles) p.st.holes.shift();
|
|
1911
2083
|
markDamaged(p);
|
|
1912
2084
|
emit({ ...base(o, src), type: "hole", part: p.spec.id, hole, worldPoint: wp, ...exit ? { exitPoint: exit } : {} });
|
|
2085
|
+
setStage(o, p, 1, {}, src);
|
|
1913
2086
|
return hole;
|
|
1914
2087
|
};
|
|
1915
2088
|
const addCrack = (o, p, wp, wn, length, src) => {
|
|
@@ -1918,6 +2091,7 @@ var createDestruction = (options = {}) => {
|
|
|
1918
2091
|
if (p.st.cracks.length > 16) p.st.cracks.shift();
|
|
1919
2092
|
markDamaged(p);
|
|
1920
2093
|
emit({ ...base(o, src), type: "crack", part: p.spec.id, crack, worldPoint: wp });
|
|
2094
|
+
setStage(o, p, 1, {}, src);
|
|
1921
2095
|
};
|
|
1922
2096
|
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
2097
|
const holeRate = (hole, pressurized) => (pressurized ? 0.5 : 0.1) * (hole.diameter / 9e-3);
|
|
@@ -1966,6 +2140,7 @@ var createDestruction = (options = {}) => {
|
|
|
1966
2140
|
if (split && mine.length) amount /= mine.length;
|
|
1967
2141
|
for (const j of mine) {
|
|
1968
2142
|
if (j.st.open) continue;
|
|
2143
|
+
if (j.spec.kind === "rebar" && (o.parts[j.ia].spec.reinforcement || o.parts[j.ib].spec.reinforcement)) continue;
|
|
1969
2144
|
if (near && jointReach(o, j, near) > Math.max(radius, jointReachLimit(o, j))) continue;
|
|
1970
2145
|
j.st.hp -= amount;
|
|
1971
2146
|
if (j.st.hp <= 0) {
|
|
@@ -1979,23 +2154,172 @@ var createDestruction = (options = {}) => {
|
|
|
1979
2154
|
}
|
|
1980
2155
|
}
|
|
1981
2156
|
};
|
|
2157
|
+
const setStage = (o, p, stage, extra = {}, src) => {
|
|
2158
|
+
if (!p.staged || (p.st.stage ?? 0) >= stage) return;
|
|
2159
|
+
p.st.stage = stage;
|
|
2160
|
+
emit({ ...base(o, src), type: "stage", part: p.spec.id, stage, ...extra });
|
|
2161
|
+
};
|
|
2162
|
+
const faceOf = (o, p, worldNormal) => rotateVec(quat.conjugate(partXf(o, p).q), worldNormal)[p.thin] >= 0 ? 1 : -1;
|
|
2163
|
+
const spallOuter = (o, p, wp, face, normal, radius, src) => {
|
|
2164
|
+
var _a;
|
|
2165
|
+
if (p.layers.length === 0) return;
|
|
2166
|
+
const lp = objLocal(o, wp);
|
|
2167
|
+
const layer = outerLayer(p.layers, p.rt.spalls, face, lp);
|
|
2168
|
+
if (!layer) return;
|
|
2169
|
+
const spalls = (_a = p.rt).spalls ?? (_a.spalls = []);
|
|
2170
|
+
spalls.push([layer.index, face, lp[0], lp[1], lp[2], radius]);
|
|
2171
|
+
if (spalls.length > MAX_SPALLS) spalls.shift();
|
|
2172
|
+
markDamaged(p);
|
|
2173
|
+
emit({ ...base(o, src), type: "spall", part: p.spec.id, layer: layer.index, point: wp, area: Math.PI * radius * radius });
|
|
2174
|
+
debris(o, p, layer.mat.hitDebris, wp, normal, 5, 2, src);
|
|
2175
|
+
setStage(o, p, 1, {}, src);
|
|
2176
|
+
};
|
|
2177
|
+
const insideOpening = (o, p, objectPoint) => {
|
|
2178
|
+
const op = p.st.opening;
|
|
2179
|
+
if (!op) return false;
|
|
2180
|
+
const [u, v] = planeAxes(p.thin);
|
|
2181
|
+
const lp = toLocal(p.pos, p.q, objectPoint);
|
|
2182
|
+
const lc = toLocal(p.pos, p.q, op.center);
|
|
2183
|
+
return Math.hypot(lp[u] - lc[u], lp[v] - lc[v]) <= op.radius;
|
|
2184
|
+
};
|
|
2185
|
+
const breachAreaOf = (p, damage, blast) => damage / (p.mat.hpPerKg * p.mat.density) / Math.max(p.thickness, 5e-3) * (blast ? breachBlastFactor : 1);
|
|
2186
|
+
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 }));
|
|
2187
|
+
const unitsOf = (p) => p.spec.units ?? { size: [p.mat.fragmentSize?.[1] ?? 0.2, p.mat.fragmentSize?.[1] ?? 0.2, p.thickness], bond: "stack" };
|
|
2188
|
+
const FRAGMENT_SHAPE = { shatter: "shard", splinter: "splinter", tear: "scrap" };
|
|
2189
|
+
const cellFragments = (o, p, cells, hitW, dir, speed, mass, tag) => {
|
|
2190
|
+
const { c, q } = partXf(o, p);
|
|
2191
|
+
const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
|
|
2192
|
+
const [u, v] = planeAxes(p.thin);
|
|
2193
|
+
const faceArea = (cell) => cell.size[u] * cell.size[v];
|
|
2194
|
+
const total = cells.reduce((sum, cell) => sum + faceArea(cell), 0) || 1;
|
|
2195
|
+
const shape = p.spec.units ? "chunk" : FRAGMENT_SHAPE[p.mat.fracture] ?? "chunk";
|
|
2196
|
+
return cells.map((cell, i) => {
|
|
2197
|
+
const cw = toWorld(c, q, cell.center);
|
|
2198
|
+
const out = vec.normalize(vec.sub(cw, hitW), [0, 1, 0]);
|
|
2199
|
+
const dv = dir ? vec.normalize(vec.madd(out, dir, 0.6)) : out;
|
|
2200
|
+
const k = speed * (0.6 + 0.8 * rnd());
|
|
2201
|
+
const marks = marksInBox(cell.center, cell.size, void 0, holes);
|
|
2202
|
+
return {
|
|
2203
|
+
id: `${p.spec.id}#${tag}${i}`,
|
|
2204
|
+
part: p.spec.id,
|
|
2205
|
+
material: p.spec.material,
|
|
2206
|
+
shape,
|
|
2207
|
+
center: cw,
|
|
2208
|
+
size: [cell.size[0], cell.size[1], cell.size[2]],
|
|
2209
|
+
rotation: q,
|
|
2210
|
+
velocity: [dv[0] * k, dv[1] * k + 0.5 * rnd(), dv[2] * k],
|
|
2211
|
+
angularVelocity: [(rnd() - 0.5) * 8, (rnd() - 0.5) * 8, (rnd() - 0.5) * 8],
|
|
2212
|
+
mass: mass * faceArea(cell) / total,
|
|
2213
|
+
...marks.length > 0 ? { marks } : {}
|
|
2214
|
+
};
|
|
2215
|
+
});
|
|
2216
|
+
};
|
|
2217
|
+
const worldBars = (o, p, bars) => {
|
|
2218
|
+
const { c, q } = partXf(o, p);
|
|
2219
|
+
return bars.map(([a, b]) => [toWorld(c, q, a), toWorld(c, q, b)]);
|
|
2220
|
+
};
|
|
2221
|
+
const addBreach = (o, p, wp, area, energy, dir, src) => {
|
|
2222
|
+
var _a;
|
|
2223
|
+
if (!p.staged || area <= 0 || (p.st.stage ?? 0) >= 2 || p.st.stripped || isLost(p)) return;
|
|
2224
|
+
const lp = objLocal(o, wp);
|
|
2225
|
+
const list = (_a = p.rt).breach ?? (_a.breach = []);
|
|
2226
|
+
list.push([lp[0], lp[1], lp[2], area]);
|
|
2227
|
+
if (list.length > MAX_SPALLS) list.shift();
|
|
2228
|
+
let total = 0;
|
|
2229
|
+
const sum = [0, 0, 0];
|
|
2230
|
+
for (const [x, y, z, a] of list) {
|
|
2231
|
+
if (vec.distance([x, y, z], lp) > breachRadius) continue;
|
|
2232
|
+
total += a;
|
|
2233
|
+
sum[0] += x * a;
|
|
2234
|
+
sum[1] += y * a;
|
|
2235
|
+
sum[2] += z * a;
|
|
2236
|
+
}
|
|
2237
|
+
if (total < (p.spec.breach?.area ?? BREACH_AREA)) return;
|
|
2238
|
+
openBreach(o, p, [sum[0] / total, sum[1] / total, sum[2] / total], total, energy, dir, src);
|
|
2239
|
+
};
|
|
2240
|
+
const openBreach = (o, p, center, total, energy, dir, src) => {
|
|
2241
|
+
const [u, v] = planeAxes(p.thin);
|
|
2242
|
+
const radius = clamp2(Math.sqrt(total / Math.PI), Math.sqrt((p.spec.breach?.area ?? BREACH_AREA) / Math.PI), breachRadius);
|
|
2243
|
+
const lc = toLocal(p.pos, p.q, center);
|
|
2244
|
+
lc[p.thin] = 0;
|
|
2245
|
+
const { c, q } = partXf(o, p);
|
|
2246
|
+
const worldCenter = toWorld(c, q, lc);
|
|
2247
|
+
if (2 * radius >= Math.min(p.dims[u], p.dims[v])) {
|
|
2248
|
+
failPart(o, p, worldCenter, dir, energy, 12, src);
|
|
2249
|
+
return;
|
|
2250
|
+
}
|
|
2251
|
+
const cells = cellsWithin(unitGrid(p.dims, p.thin, unitsOf(p), BREACH_GRID_CAP), p.thin, lc, radius);
|
|
2252
|
+
const removed = p.st.mass * cells.reduce((sum, cell) => sum + cell.size[u] * cell.size[v], 0) / (p.dims[u] * p.dims[v]);
|
|
2253
|
+
const speed = clamp2(Math.sqrt(2 * energy * 0.1 / Math.max(removed, 0.05)), 0.5, 8);
|
|
2254
|
+
const fragments = cellFragments(o, p, cells, worldCenter, dir, speed, removed, "b");
|
|
2255
|
+
const holes = localHolesOf(p);
|
|
2256
|
+
p.st.holes = p.st.holes.filter((_, i) => !cells.some((cell) => marksInBox(cell.center, cell.size, void 0, [holes[i]]).length > 0));
|
|
2257
|
+
p.st.mass = Math.max(0, p.st.mass - removed);
|
|
2258
|
+
p.rt.removed = (p.rt.removed ?? 0) + removed;
|
|
2259
|
+
p.st.opening = { center: objLocal(o, worldCenter), radius };
|
|
2260
|
+
markDamaged(p);
|
|
2261
|
+
setStage(o, p, 1, {}, src);
|
|
2262
|
+
setStage(o, p, 2, { opening: { center: worldCenter, radius }, fragments }, src);
|
|
2263
|
+
debris(o, p, p.mat.breakDebris, worldCenter, vec.scale(dir ?? [0, 1, 0], -1), 14, 3, src);
|
|
2264
|
+
const reinforcement = p.spec.reinforcement;
|
|
2265
|
+
if (reinforcement) {
|
|
2266
|
+
const bars = worldBars(o, p, clipBarsToDisc(rebarBars(p.dims, p.thin, reinforcement.spacing), u, v, [lc[u], lc[v]], radius + 0.05));
|
|
2267
|
+
if (bars.length > 0) emit({ ...base(o, src), type: "expose", part: p.spec.id, bars });
|
|
2268
|
+
}
|
|
2269
|
+
if (p.structural) solveStructure(o);
|
|
2270
|
+
};
|
|
2271
|
+
const stripPart = (o, p, reinforcement, src) => {
|
|
2272
|
+
const bars = rebarBars(p.dims, p.thin, reinforcement.spacing);
|
|
2273
|
+
p.st.stripped = true;
|
|
2274
|
+
p.st.hp = p.st.maxHp;
|
|
2275
|
+
p.st.mass = rebarMass(bars, reinforcement.diameter, materials.get(reinforcement.material)?.density);
|
|
2276
|
+
p.st.holes = [];
|
|
2277
|
+
p.st.burning = false;
|
|
2278
|
+
p.rt.jet = false;
|
|
2279
|
+
markDamaged(p);
|
|
2280
|
+
emit({ ...base(o, src), type: "expose", part: p.spec.id, bars: worldBars(o, p, bars) });
|
|
2281
|
+
solveStructure(o);
|
|
2282
|
+
collapseCheck(o, src);
|
|
2283
|
+
};
|
|
2284
|
+
const unitFragmentPlan = (p, cap) => {
|
|
2285
|
+
const grid = unitGrid(p.dims, p.thin, unitsOf(p), cap);
|
|
2286
|
+
const op = p.st.opening;
|
|
2287
|
+
const cells = op ? cellsOutside(grid, p.thin, toLocal(p.pos, p.q, op.center), op.radius) : grid;
|
|
2288
|
+
const [u, v] = planeAxes(p.thin);
|
|
2289
|
+
return cells.map((cell) => ({ center: cell.center, size: cell.size, q: quat.identity(), shape: "chunk", weight: cell.size[u] * cell.size[v] }));
|
|
2290
|
+
};
|
|
2291
|
+
const loseCage = (o, p, src) => {
|
|
2292
|
+
p.st.status = "broken";
|
|
2293
|
+
p.st.hp = 0;
|
|
2294
|
+
p.st.mass = 0;
|
|
2295
|
+
emit({ ...base(o, src), type: "expose", part: p.spec.id, bars: [] });
|
|
2296
|
+
debris(o, p, "sparks", partXf(o, p).c, [0, 1, 0], 6, 3, src);
|
|
2297
|
+
partLost(o, p, src);
|
|
2298
|
+
};
|
|
1982
2299
|
const failPart = (o, p, origin, dir, fragEnergy, cap, src) => {
|
|
1983
2300
|
if (isLost(p)) return;
|
|
2301
|
+
if (p.st.stripped) {
|
|
2302
|
+
loseCage(o, p, src);
|
|
2303
|
+
return;
|
|
2304
|
+
}
|
|
1984
2305
|
const ex = p.spec.explodes;
|
|
1985
2306
|
const style = p.mat.fracture === "none" ? "snap" : p.mat.fracture;
|
|
1986
2307
|
const { c, q } = partXf(o, p);
|
|
1987
2308
|
const hitW = origin ?? c;
|
|
1988
2309
|
const grain = p.spec.grain ? AXIS[p.spec.grain] : void 0;
|
|
1989
|
-
const
|
|
2310
|
+
const hitLocal = toLocal(c, q, hitW);
|
|
2311
|
+
const unitPlan = p.spec.units ? unitFragmentPlan(p, maxFragments * UNIT_FRAGMENT_FACTOR) : [];
|
|
2312
|
+
const plan = unitPlan.length > 0 ? unitPlan : planFragments({
|
|
1990
2313
|
style,
|
|
1991
2314
|
dims: p.dims,
|
|
1992
2315
|
thickness: p.thickness,
|
|
1993
2316
|
grain,
|
|
1994
|
-
hit:
|
|
2317
|
+
hit: hitLocal,
|
|
1995
2318
|
fragmentSize: p.mat.fragmentSize ?? [0.04, 0.2],
|
|
1996
2319
|
maxFragments,
|
|
1997
2320
|
rnd
|
|
1998
2321
|
});
|
|
2322
|
+
const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
|
|
1999
2323
|
const mass = p.st.mass;
|
|
2000
2324
|
const totW = plan.reduce((s, f) => s + f.weight, 0) || 1;
|
|
2001
2325
|
const energy = ex ? ex.energy * 0.2 : fragEnergy;
|
|
@@ -2005,6 +2329,7 @@ var createDestruction = (options = {}) => {
|
|
|
2005
2329
|
const out = vec.normalize(vec.sub(cw, hitW), [0, 1, 0]);
|
|
2006
2330
|
const dv = dir ? vec.normalize(vec.madd(out, dir, 0.6)) : out;
|
|
2007
2331
|
const k = speed * (0.6 + 0.8 * rnd());
|
|
2332
|
+
const marks = marksInBox(f.center, f.size, f.q, holes);
|
|
2008
2333
|
return {
|
|
2009
2334
|
id: `${p.spec.id}#${i}`,
|
|
2010
2335
|
part: p.spec.id,
|
|
@@ -2015,16 +2340,25 @@ var createDestruction = (options = {}) => {
|
|
|
2015
2340
|
rotation: quat.mul(q, f.q),
|
|
2016
2341
|
velocity: [dv[0] * k, dv[1] * k + 0.5 * rnd(), dv[2] * k],
|
|
2017
2342
|
angularVelocity: [(rnd() - 0.5) * 12, (rnd() - 0.5) * 12, (rnd() - 0.5) * 12],
|
|
2018
|
-
mass: mass * f.weight / totW
|
|
2343
|
+
mass: mass * f.weight / totW,
|
|
2344
|
+
...marks.length > 0 ? { marks } : {}
|
|
2019
2345
|
};
|
|
2020
2346
|
});
|
|
2021
|
-
p.
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2347
|
+
const reinforcement = p.spec.reinforcement;
|
|
2348
|
+
if (!reinforcement) {
|
|
2349
|
+
p.st.status = "broken";
|
|
2350
|
+
p.st.hp = 0;
|
|
2351
|
+
p.st.mass = 0;
|
|
2352
|
+
p.st.burning = false;
|
|
2353
|
+
p.rt.jet = false;
|
|
2354
|
+
}
|
|
2026
2355
|
emit({ ...base(o, src), type: "fracture", part: p.spec.id, style, fragments, point: hitW });
|
|
2027
2356
|
debris(o, p, p.mat.breakDebris, c, [0, 1, 0], clamp2(Math.round(Math.cbrt(partVolume(p.spec.shape)) * 30), 3, 20), 2, src);
|
|
2357
|
+
setStage(o, p, 3, {}, src);
|
|
2358
|
+
if (reinforcement) {
|
|
2359
|
+
stripPart(o, p, reinforcement, src);
|
|
2360
|
+
return;
|
|
2361
|
+
}
|
|
2028
2362
|
if (ex) {
|
|
2029
2363
|
emit({ ...base(o, src), type: "explode", part: p.spec.id, point: c, energy: ex.energy, radius: ex.radius });
|
|
2030
2364
|
blasts.push({ point: c, energy: ex.energy, radius: ex.radius, source: src });
|
|
@@ -2058,10 +2392,17 @@ var createDestruction = (options = {}) => {
|
|
|
2058
2392
|
};
|
|
2059
2393
|
const ballistic = (o, p, entry, n, d, E, caliber, pathLen, src) => {
|
|
2060
2394
|
const mat = p.mat;
|
|
2395
|
+
if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry))) {
|
|
2396
|
+
return { penetrated: true, exitEnergy: E, exitPoint: vec.madd(entry, d, Math.min(pathLen, p.thickness)), damage: 0 };
|
|
2397
|
+
}
|
|
2061
2398
|
const sin = Math.min(1, Math.abs(vec.dot(d, n)));
|
|
2062
2399
|
const angle = Math.asin(sin) * 180 / Math.PI;
|
|
2063
|
-
const
|
|
2064
|
-
const
|
|
2400
|
+
const slant = Math.max(sin, 0.3);
|
|
2401
|
+
const effM = p.thickness / slant;
|
|
2402
|
+
const face = p.layers.length > 0 ? faceOf(o, p, n) : 1;
|
|
2403
|
+
const exitLocal = objLocal(o, vec.madd(entry, d, Math.min(pathLen, effM)));
|
|
2404
|
+
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;
|
|
2405
|
+
const thr = coats > 0 ? (mat.penetration * p.thickness * 1e3 + coats) / slant : mat.penetration * effM * 1e3;
|
|
2065
2406
|
const penetrated = E > thr;
|
|
2066
2407
|
const scale = (mat.damageScale?.ballistic ?? 1) * (mat.fracture === "shatter" ? fragility(mat.ductility, p.thickness) : 1);
|
|
2067
2408
|
const id = p.spec.id;
|
|
@@ -2072,13 +2413,16 @@ var createDestruction = (options = {}) => {
|
|
|
2072
2413
|
const dmg2 = applyDamage(o, p, E * DMG_PER_J * scale * 0.2, "ballistic", entry, d, E * 0.1, 12, src);
|
|
2073
2414
|
return { penetrated: false, exitEnergy: 0, damage: dmg2 };
|
|
2074
2415
|
}
|
|
2416
|
+
if (p.layers.length > 0) spallOuter(o, p, entry, face, n, Math.max(0.03, caliber / 1e3 * 4), src);
|
|
2075
2417
|
const brittle = mat.ductility < 0.3;
|
|
2076
2418
|
const ratio = E / Math.max(thr, 1e-6);
|
|
2077
2419
|
const crackLen = clamp2(0.03 + ratio * 0.2, 0.03, 0.8);
|
|
2078
2420
|
let exitPoint;
|
|
2079
2421
|
if (penetrated) {
|
|
2080
|
-
exitPoint = vec.madd(entry, d, Math.min(pathLen, effM));
|
|
2422
|
+
exitPoint = vec.madd(entry, d, Math.min(pathLen, effM + layersThickness(p.layers, face) / slant + layersThickness(p.layers, -face) / slant));
|
|
2081
2423
|
const hole = addHole(o, p, entry, n, caliber / 1e3 * (mat.ductility >= 0.3 ? 1 : 1.5), true, exitPoint, src);
|
|
2424
|
+
if (p.layers.length > 0) spallOuter(o, p, exitPoint, -face, vec.scale(n, -1), Math.max(0.03, caliber / 1e3 * 4), src);
|
|
2425
|
+
addBreach(o, p, entry, Math.PI * (hole.diameter / 2) * (hole.diameter / 2), E, d, src);
|
|
2082
2426
|
if (brittle && mat.fracture === "shatter") addCrack(o, p, entry, n, crackLen, src);
|
|
2083
2427
|
if (p.spec.contains && (p.st.liters ?? 0) > 0) {
|
|
2084
2428
|
emit({
|
|
@@ -2123,8 +2467,11 @@ var createDestruction = (options = {}) => {
|
|
|
2123
2467
|
}
|
|
2124
2468
|
if (kind === "blunt") wearJoints(o, p, E * 0.3, void 0, 0, src);
|
|
2125
2469
|
else wearJoints(o, p, E * 0.3, point, 0.06, src);
|
|
2470
|
+
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
2471
|
const scale = mat.damageScale?.[kind] ?? 1;
|
|
2127
|
-
|
|
2472
|
+
const done = applyDamage(o, p, Ec * DMG_PER_J * scale * (kind === "slash" ? p.cutFactor : 1), kind, point, d, E * 0.1, 12, src);
|
|
2473
|
+
addBreach(o, p, point, breachAreaOf(p, done, false), E, d, src);
|
|
2474
|
+
return done;
|
|
2128
2475
|
};
|
|
2129
2476
|
const blastObject = (o, point, energy, radius, src) => {
|
|
2130
2477
|
const start = cur.length;
|
|
@@ -2160,7 +2507,10 @@ var createDestruction = (options = {}) => {
|
|
|
2160
2507
|
}
|
|
2161
2508
|
}
|
|
2162
2509
|
wearJoints(o, p, Ein * 0.5, void 0, 0, src);
|
|
2163
|
-
|
|
2510
|
+
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);
|
|
2511
|
+
const dealt = applyDamage(o, p, Ein * DMG_PER_J * (mat.damageScale?.explosive ?? 1), "explosive", point, null, Ein * 0.3, 40, src);
|
|
2512
|
+
res.damage += dealt;
|
|
2513
|
+
addBreach(o, p, wp, breachAreaOf(p, dealt, true), Ein, null, src);
|
|
2164
2514
|
}
|
|
2165
2515
|
res.events = cur.slice(start);
|
|
2166
2516
|
return res;
|
|
@@ -2431,7 +2781,7 @@ var createDestruction = (options = {}) => {
|
|
|
2431
2781
|
}
|
|
2432
2782
|
o.active = hot;
|
|
2433
2783
|
};
|
|
2434
|
-
const
|
|
2784
|
+
const spread2 = (sources, dt) => {
|
|
2435
2785
|
for (const src of sources) {
|
|
2436
2786
|
if (!src.p.st.burning && !src.p.rt.jet) continue;
|
|
2437
2787
|
const sc = toWorld(src.o.st.position, src.o.st.rotation, src.p.pos);
|
|
@@ -2732,7 +3082,7 @@ var createDestruction = (options = {}) => {
|
|
|
2732
3082
|
time += dt;
|
|
2733
3083
|
const sources = [];
|
|
2734
3084
|
for (const o of [...objects.values()]) if (o.active) stepObject(o, dt, sources);
|
|
2735
|
-
if (sources.length)
|
|
3085
|
+
if (sources.length) spread2(sources, dt);
|
|
2736
3086
|
detonatePrimed(time);
|
|
2737
3087
|
for (const im of [...impls.values()]) {
|
|
2738
3088
|
if (!im.impl.update) continue;
|
|
@@ -2771,7 +3121,7 @@ var createDestruction = (options = {}) => {
|
|
|
2771
3121
|
counter,
|
|
2772
3122
|
objects: [...objects.values()].map((o) => {
|
|
2773
3123
|
const runtime = { parts: {}, joints: {}, collapsed: [...o.collapsed] };
|
|
2774
|
-
for (const p of o.parts) runtime.parts[p.spec.id] =
|
|
3124
|
+
for (const p of o.parts) runtime.parts[p.spec.id] = clone(p.rt);
|
|
2775
3125
|
for (const j of o.joints) runtime.joints[j.spec.id] = j.step;
|
|
2776
3126
|
return { id: o.id, spec: clone(o.spec), state: clone(o.st), runtime };
|
|
2777
3127
|
}),
|
|
@@ -2788,7 +3138,7 @@ var createDestruction = (options = {}) => {
|
|
|
2788
3138
|
const state = clone(e.state);
|
|
2789
3139
|
const carry = {
|
|
2790
3140
|
parts: new Map(
|
|
2791
|
-
state.parts.map((st) => [st.id, { st, rt: e.runtime?.parts[st.id] ?
|
|
3141
|
+
state.parts.map((st) => [st.id, { st, rt: e.runtime?.parts[st.id] ? clone(e.runtime.parts[st.id]) : { ...freshRuntime(), prevTemp: st.temperature } }])
|
|
2792
3142
|
),
|
|
2793
3143
|
joints: new Map(state.joints.map((st) => [st.id, { st, step: e.runtime?.joints[st.id] ?? 0 }])),
|
|
2794
3144
|
collapsed: e.runtime?.collapsed
|
|
@@ -2807,6 +3157,109 @@ var createDestruction = (options = {}) => {
|
|
|
2807
3157
|
return world;
|
|
2808
3158
|
};
|
|
2809
3159
|
|
|
3160
|
+
// src/data/assemblies.json
|
|
3161
|
+
var assemblies_default = [
|
|
3162
|
+
{
|
|
3163
|
+
id: "brick_wall_plastered",
|
|
3164
|
+
name: "Plastered brick wall",
|
|
3165
|
+
core: { material: "brick", thickness: 0.09 },
|
|
3166
|
+
layers: [{ material: "plaster", thickness: 0.02, faces: "both" }],
|
|
3167
|
+
units: { size: [0.23, 0.075, 0.11], bond: "running", mortar: "plaster" },
|
|
3168
|
+
breach: { area: 0.25 }
|
|
3169
|
+
},
|
|
3170
|
+
{
|
|
3171
|
+
id: "concrete_block_wall",
|
|
3172
|
+
name: "Concrete block wall",
|
|
3173
|
+
core: { material: "concrete", thickness: 0.14 },
|
|
3174
|
+
fill: 0.55,
|
|
3175
|
+
units: { size: [0.39, 0.19, 0.14], bond: "running", mortar: "plaster" },
|
|
3176
|
+
breach: { area: 0.25 }
|
|
3177
|
+
},
|
|
3178
|
+
{
|
|
3179
|
+
id: "reinforced_concrete",
|
|
3180
|
+
name: "Reinforced concrete wall",
|
|
3181
|
+
core: { material: "concrete", thickness: 0.2 },
|
|
3182
|
+
reinforcement: { material: "steel", spacing: 0.18, diameter: 8e-3, residualCapacity: 0.15 },
|
|
3183
|
+
units: { size: [0.6, 0.42, 0.2], bond: "stack", mortar: "concrete" },
|
|
3184
|
+
breach: { area: 0.25 }
|
|
3185
|
+
},
|
|
3186
|
+
{
|
|
3187
|
+
id: "drywall",
|
|
3188
|
+
name: "Drywall partition on steel studs",
|
|
3189
|
+
core: { material: "plaster", thickness: 0.0125 },
|
|
3190
|
+
layers: [{ material: "plaster", thickness: 0.0125, faces: "+" }],
|
|
3191
|
+
depth: 0.095,
|
|
3192
|
+
breach: { area: 0.25 }
|
|
3193
|
+
},
|
|
3194
|
+
{
|
|
3195
|
+
id: "wood_frame_wall",
|
|
3196
|
+
name: "Wood frame wall",
|
|
3197
|
+
core: { material: "plywood", thickness: 0.012 },
|
|
3198
|
+
layers: [{ material: "plaster", thickness: 0.0125, faces: "-" }],
|
|
3199
|
+
depth: 0.1135,
|
|
3200
|
+
breach: { area: 0.25 }
|
|
3201
|
+
},
|
|
3202
|
+
{
|
|
3203
|
+
id: "log_wall",
|
|
3204
|
+
name: "Log wall",
|
|
3205
|
+
core: { material: "wood_soft", thickness: 0.2 },
|
|
3206
|
+
fill: 0.785,
|
|
3207
|
+
breach: { area: 0.25 }
|
|
3208
|
+
},
|
|
3209
|
+
{
|
|
3210
|
+
id: "sheet_metal_shack",
|
|
3211
|
+
name: "Corrugated sheet metal wall",
|
|
3212
|
+
core: { material: "steel", thickness: 6e-4 },
|
|
3213
|
+
depth: 0.02,
|
|
3214
|
+
breach: { area: 0.25 }
|
|
3215
|
+
},
|
|
3216
|
+
{
|
|
3217
|
+
id: "glass_pane",
|
|
3218
|
+
name: "Glass pane",
|
|
3219
|
+
core: { material: "glass", thickness: 6e-3 },
|
|
3220
|
+
breach: { area: 0.1 }
|
|
3221
|
+
}
|
|
3222
|
+
];
|
|
3223
|
+
|
|
3224
|
+
// src/assemblies.ts
|
|
3225
|
+
var ASSEMBLIES = assemblies_default;
|
|
3226
|
+
var registerAssembly = (def) => {
|
|
3227
|
+
const at = ASSEMBLIES.findIndex((a) => a.id === def.id);
|
|
3228
|
+
if (at >= 0) ASSEMBLIES[at] = def;
|
|
3229
|
+
else ASSEMBLIES.push(def);
|
|
3230
|
+
};
|
|
3231
|
+
var getAssembly = (id) => ASSEMBLIES.find((a) => a.id === id);
|
|
3232
|
+
var faceCount = (layer) => layer.faces === "both" ? 2 : 1;
|
|
3233
|
+
var assemblyDepth = (def) => def.depth ?? def.core.thickness + (def.layers ?? []).reduce((sum, l) => sum + l.thickness * faceCount(l), 0);
|
|
3234
|
+
var assemblyPart = (assemblyId, options) => {
|
|
3235
|
+
const def = getAssembly(assemblyId);
|
|
3236
|
+
if (!def) throw new Error(`unknown assembly "${assemblyId}"`);
|
|
3237
|
+
const densityOf = (id) => {
|
|
3238
|
+
const m = options.materials?.get(id) ?? MATERIALS.find((x) => x.id === id);
|
|
3239
|
+
if (!m) throw new Error(`unknown material "${id}" in assembly "${assemblyId}"`);
|
|
3240
|
+
return m.density;
|
|
3241
|
+
};
|
|
3242
|
+
const fill = def.fill ?? 1;
|
|
3243
|
+
const area = options.width * options.height;
|
|
3244
|
+
const coreMass = area * def.core.thickness * fill * densityOf(def.core.material);
|
|
3245
|
+
const layerMass = (def.layers ?? []).reduce((sum, l) => sum + area * l.thickness * faceCount(l) * densityOf(l.material), 0);
|
|
3246
|
+
return {
|
|
3247
|
+
id: options.id,
|
|
3248
|
+
material: def.core.material,
|
|
3249
|
+
shape: { kind: "box", size: [options.width, options.height, assemblyDepth(def)] },
|
|
3250
|
+
position: options.position,
|
|
3251
|
+
...options.rotation ? { rotation: options.rotation } : {},
|
|
3252
|
+
mass: coreMass + layerMass,
|
|
3253
|
+
thickness: def.core.thickness * fill,
|
|
3254
|
+
...def.layers ? { layers: def.layers.map((l) => ({ ...l })) } : {},
|
|
3255
|
+
...def.units ? { units: { ...def.units, size: [...def.units.size] } } : {},
|
|
3256
|
+
...def.reinforcement ? { reinforcement: { ...def.reinforcement } } : {},
|
|
3257
|
+
...def.breach ? { breach: { ...def.breach } } : {},
|
|
3258
|
+
...options.structure ? { structure: options.structure } : {},
|
|
3259
|
+
...options.extra
|
|
3260
|
+
};
|
|
3261
|
+
};
|
|
3262
|
+
|
|
2810
3263
|
// src/frame.ts
|
|
2811
3264
|
var buildFrame = (o = {}) => {
|
|
2812
3265
|
const spanX = o.spanX ?? 8, spanZ = o.spanZ ?? 8;
|
|
@@ -2914,6 +3367,8 @@ var buildFrame = (o = {}) => {
|
|
|
2914
3367
|
var sizeStructure = (spec, options = {}) => {
|
|
2915
3368
|
const safety = options.safety ?? 0.65;
|
|
2916
3369
|
const redundancy = options.redundancy;
|
|
3370
|
+
const redundancyRoles = options.redundancyRoles ?? ["column"];
|
|
3371
|
+
const safetyOf = (role) => options.safetyByRole?.[role] ?? safety;
|
|
2917
3372
|
const loads = (s) => {
|
|
2918
3373
|
const w = createDestruction(options.materials ? { materials: options.materials } : {});
|
|
2919
3374
|
const id = w.add(s);
|
|
@@ -2926,11 +3381,11 @@ var sizeStructure = (spec, options = {}) => {
|
|
|
2926
3381
|
if (!p.structure || p.structure.role === "none") continue;
|
|
2927
3382
|
const b = base.get(p.id);
|
|
2928
3383
|
if (!b || b.stress <= 0) continue;
|
|
2929
|
-
need.set(p.id, b.load /
|
|
3384
|
+
need.set(p.id, b.load / safetyOf(p.structure.role));
|
|
2930
3385
|
}
|
|
2931
3386
|
if (redundancy !== void 0) {
|
|
2932
3387
|
for (const col of out.parts) {
|
|
2933
|
-
if (col.structure
|
|
3388
|
+
if (!col.structure || !redundancyRoles.includes(col.structure.role)) continue;
|
|
2934
3389
|
const reduced = {
|
|
2935
3390
|
...out,
|
|
2936
3391
|
parts: out.parts.filter((p) => p.id !== col.id),
|
|
@@ -3030,6 +3485,10 @@ var locales = {
|
|
|
3030
3485
|
primed: "about to explode",
|
|
3031
3486
|
stress: "overload",
|
|
3032
3487
|
collapse: "collapsed",
|
|
3488
|
+
spall: "plaster came off",
|
|
3489
|
+
stage: "damage stage",
|
|
3490
|
+
expose: "rebar bare",
|
|
3491
|
+
body: "body",
|
|
3033
3492
|
destroyed: "destroyed"
|
|
3034
3493
|
},
|
|
3035
3494
|
fractureStyles: { shatter: "shatter", crumble: "crumble", splinter: "splinter", tear: "tear", snap: "snap", none: "none" },
|
|
@@ -3112,6 +3571,10 @@ var locales = {
|
|
|
3112
3571
|
primed: "prestes a explodir",
|
|
3113
3572
|
stress: "sobrecarga",
|
|
3114
3573
|
collapse: "desabou",
|
|
3574
|
+
spall: "reboco soltou",
|
|
3575
|
+
stage: "est\xE1gio de dano",
|
|
3576
|
+
expose: "vergalh\xE3o \xE0 mostra",
|
|
3577
|
+
body: "corpo",
|
|
3115
3578
|
destroyed: "destru\xEDdo"
|
|
3116
3579
|
},
|
|
3117
3580
|
fractureStyles: { shatter: "estilha\xE7ar", crumble: "esfarelar", splinter: "lascar", tear: "rasgar", snap: "partir", none: "nenhuma" },
|
|
@@ -3128,17 +3591,21 @@ var checkDestructible = (impl, options = {}) => {
|
|
|
3128
3591
|
return check(impl, contractOptions);
|
|
3129
3592
|
};
|
|
3130
3593
|
export {
|
|
3594
|
+
ASSEMBLIES,
|
|
3131
3595
|
FIRE_PRIME_C,
|
|
3132
3596
|
IMPACT_CONTRACT,
|
|
3133
3597
|
JOINT_BY_MATERIALS,
|
|
3134
3598
|
JOINT_DEFAULTS,
|
|
3135
3599
|
MATERIALS,
|
|
3600
|
+
assemblyDepth,
|
|
3601
|
+
assemblyPart,
|
|
3136
3602
|
buildFrame,
|
|
3137
3603
|
checkDestructible,
|
|
3138
3604
|
checkHit,
|
|
3139
3605
|
createDestruction,
|
|
3140
3606
|
explosivePart,
|
|
3141
3607
|
fragility,
|
|
3608
|
+
getAssembly,
|
|
3142
3609
|
guessMaterial,
|
|
3143
3610
|
inferJoints,
|
|
3144
3611
|
locales,
|
|
@@ -3148,6 +3615,7 @@ export {
|
|
|
3148
3615
|
partVolume,
|
|
3149
3616
|
quat,
|
|
3150
3617
|
quatFromEuler,
|
|
3618
|
+
registerAssembly,
|
|
3151
3619
|
resolveStrings,
|
|
3152
3620
|
rotateVec,
|
|
3153
3621
|
sizeStructure,
|