@adatechnology/game-destruction 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +51 -1
- package/dist/index.global.js +563 -65
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +563 -65
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.global.js
CHANGED
|
@@ -450,6 +450,47 @@ var GameDestruction = (() => {
|
|
|
450
450
|
return angle === 0 ? q : quat.mul(q, quat.fromAxisAngle([0, 0, 1], angle));
|
|
451
451
|
};
|
|
452
452
|
var clamp = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
453
|
+
var splinterBox = (inp, box, limit = inp.maxFragments) => {
|
|
454
|
+
const { rnd } = inp;
|
|
455
|
+
const dims = box.s;
|
|
456
|
+
const [fmin, fmax] = inp.fragmentSize;
|
|
457
|
+
const g = inp.grain ?? [0, 1, 2].sort((a2, b2) => dims[b2] - dims[a2])[0];
|
|
458
|
+
const [a, b] = [0, 1, 2].filter((i) => i !== g);
|
|
459
|
+
const w = Math.max(fmin * 1.5, 0.01);
|
|
460
|
+
const c = clamp(Math.round(dims[a] * dims[b] / (w * w)), 3, Math.max(3, Math.floor(limit / 2)));
|
|
461
|
+
const maxLen = fmax * 4;
|
|
462
|
+
const cells = bsp(box, c, [a, b], rnd);
|
|
463
|
+
const pieces = [];
|
|
464
|
+
for (const cell of cells) {
|
|
465
|
+
const extra = rnd() < 0.4 ? 1 : 0;
|
|
466
|
+
const k = Math.max(1, Math.ceil(dims[g] / maxLen)) + extra;
|
|
467
|
+
for (const p of bsp(cell, k, [g], rnd)) pieces.push(p);
|
|
468
|
+
}
|
|
469
|
+
return pieces.slice(0, limit).map((p) => ({ center: p.c, size: [p.s[g], p.s[a], p.s[b]], q: frame(g, a, b), shape: "splinter", weight: p.s[0] * p.s[1] * p.s[2] }));
|
|
470
|
+
};
|
|
471
|
+
var severAt = (inp, root, at, gap) => {
|
|
472
|
+
const dims = root.s;
|
|
473
|
+
const L = [0, 1, 2].sort((x, y) => dims[y] - dims[x])[0];
|
|
474
|
+
const half = dims[L] / 2;
|
|
475
|
+
const lo = clamp(at - gap / 2, -half, half), hi = clamp(at + gap / 2, -half, half);
|
|
476
|
+
const out = [];
|
|
477
|
+
const stub = (from, to) => {
|
|
478
|
+
if (to - from < 0.02) return;
|
|
479
|
+
const c = [root.c[0], root.c[1], root.c[2]], sz = [dims[0], dims[1], dims[2]];
|
|
480
|
+
c[L] = (from + to) / 2;
|
|
481
|
+
sz[L] = to - from;
|
|
482
|
+
out.push({ center: c, size: sz, q: quat.identity(), shape: "slab", weight: sz[0] * sz[1] * sz[2] });
|
|
483
|
+
};
|
|
484
|
+
stub(-half, lo);
|
|
485
|
+
stub(hi, half);
|
|
486
|
+
const band = { c: [root.c[0], root.c[1], root.c[2]], s: [dims[0], dims[1], dims[2]] };
|
|
487
|
+
band.c[L] = (lo + hi) / 2;
|
|
488
|
+
band.s[L] = Math.max(hi - lo, 0.01);
|
|
489
|
+
if (inp.style === "snap") {
|
|
490
|
+
for (const b of bsp(band, Math.max(3, Math.min(6, inp.maxFragments - out.length)), [0, 1, 2], inp.rnd)) out.push({ center: b.c, size: b.s, q: quat.identity(), shape: "chunk", weight: b.s[0] * b.s[1] * b.s[2] });
|
|
491
|
+
} else for (const p of splinterBox(inp, band, Math.max(3, inp.maxFragments - out.length))) out.push(p);
|
|
492
|
+
return out;
|
|
493
|
+
};
|
|
453
494
|
var tearStack = (inp, root, t, u, v, n, sheet) => {
|
|
454
495
|
const { dims, rnd, maxFragments } = inp;
|
|
455
496
|
const layers = clamp(Math.ceil(dims[t] / STACK_LAYER), 2, 4);
|
|
@@ -478,6 +519,7 @@ var GameDestruction = (() => {
|
|
|
478
519
|
const t = sorted[0];
|
|
479
520
|
const [u, v] = [sorted[1], sorted[2]].sort((a, b) => a - b);
|
|
480
521
|
if (style === "snap") {
|
|
522
|
+
if (inp.cut) return severAt(inp, root, inp.cut.at, inp.cut.gap);
|
|
481
523
|
const n2 = Math.min(maxFragments, 2 + Math.floor(rnd() * 3));
|
|
482
524
|
for (const b of bsp(root, n2, [0, 1, 2], rnd)) out.push({ center: b.c, size: b.s, q: idq, shape: "slab", weight: b.s[0] * b.s[1] * b.s[2] });
|
|
483
525
|
return out;
|
|
@@ -488,23 +530,8 @@ var GameDestruction = (() => {
|
|
|
488
530
|
return out;
|
|
489
531
|
}
|
|
490
532
|
if (style === "splinter") {
|
|
491
|
-
|
|
492
|
-
const
|
|
493
|
-
const w = Math.max(fmin * 1.5, 0.01);
|
|
494
|
-
const c = clamp(Math.round(dims[a] * dims[b] / (w * w)), 3, Math.max(3, Math.floor(maxFragments / 2)));
|
|
495
|
-
const maxLen = fmax * 4;
|
|
496
|
-
const cells = bsp(root, c, [a, b], rnd);
|
|
497
|
-
const pieces = [];
|
|
498
|
-
for (const cell of cells) {
|
|
499
|
-
let list = [cell];
|
|
500
|
-
const extra = rnd() < 0.4 ? 1 : 0;
|
|
501
|
-
const k = Math.max(1, Math.ceil(dims[g] / maxLen)) + extra;
|
|
502
|
-
list = bsp(cell, k, [g], rnd);
|
|
503
|
-
for (const p of list) pieces.push(p);
|
|
504
|
-
}
|
|
505
|
-
for (const p of pieces.slice(0, maxFragments)) {
|
|
506
|
-
out.push({ center: p.c, size: [p.s[g], p.s[a], p.s[b]], q: frame(g, a, b), shape: "splinter", weight: p.s[0] * p.s[1] * p.s[2] });
|
|
507
|
-
}
|
|
533
|
+
if (inp.cut) return severAt(inp, root, inp.cut.at, inp.cut.gap);
|
|
534
|
+
for (const p of splinterBox(inp, root)) out.push(p);
|
|
508
535
|
return out;
|
|
509
536
|
}
|
|
510
537
|
if (style === "tear") {
|
|
@@ -672,6 +699,27 @@ var GameDestruction = (() => {
|
|
|
672
699
|
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]]));
|
|
673
700
|
return out;
|
|
674
701
|
};
|
|
702
|
+
var clipBarsToBox = (bars, u, v, center, size) => {
|
|
703
|
+
const out = [];
|
|
704
|
+
for (const [a, b] of bars) {
|
|
705
|
+
const axis = a[u] !== b[u] ? u : v;
|
|
706
|
+
const other = axis === u ? v : u;
|
|
707
|
+
if (Math.abs(a[other] - center[other]) > size[other] / 2) continue;
|
|
708
|
+
const lo = Math.max(Math.min(a[axis], b[axis]), center[axis] - size[axis] / 2), hi = Math.min(Math.max(a[axis], b[axis]), center[axis] + size[axis] / 2);
|
|
709
|
+
if (hi - lo < 1e-6) continue;
|
|
710
|
+
const p = [a[0], a[1], a[2]], q = [b[0], b[1], b[2]];
|
|
711
|
+
p[axis] = lo;
|
|
712
|
+
q[axis] = hi;
|
|
713
|
+
out.push([p, q]);
|
|
714
|
+
}
|
|
715
|
+
return out;
|
|
716
|
+
};
|
|
717
|
+
var bendBar = ([a, b], thin, bow, i) => {
|
|
718
|
+
const length = Math.hypot(b[0] - a[0], b[1] - a[1], b[2] - a[2]);
|
|
719
|
+
const m = [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2, (a[2] + b[2]) / 2];
|
|
720
|
+
m[thin] += (i % 2 === 0 ? 1 : -1) * bow * length;
|
|
721
|
+
return [[a, m], [m, b]];
|
|
722
|
+
};
|
|
675
723
|
var clipBarsToDisc = (bars, u, v, center, radius) => {
|
|
676
724
|
const out = [];
|
|
677
725
|
for (const [a, b] of bars) {
|
|
@@ -1649,14 +1697,21 @@ var GameDestruction = (() => {
|
|
|
1649
1697
|
smoke: "grey"
|
|
1650
1698
|
},
|
|
1651
1699
|
visual: {
|
|
1652
|
-
charColor: 1709072
|
|
1700
|
+
charColor: 1709072,
|
|
1701
|
+
innerColor: 14730131
|
|
1653
1702
|
},
|
|
1654
1703
|
tags: [
|
|
1655
1704
|
"wood",
|
|
1656
1705
|
"organic",
|
|
1657
1706
|
"flammable"
|
|
1658
1707
|
],
|
|
1659
|
-
compressive: 1e5
|
|
1708
|
+
compressive: 1e5,
|
|
1709
|
+
hits: {
|
|
1710
|
+
blunt: {
|
|
1711
|
+
tear: "splinters",
|
|
1712
|
+
tearJoules: 60
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1660
1715
|
},
|
|
1661
1716
|
{
|
|
1662
1717
|
id: "wood_hard",
|
|
@@ -1686,14 +1741,21 @@ var GameDestruction = (() => {
|
|
|
1686
1741
|
smoke: "grey"
|
|
1687
1742
|
},
|
|
1688
1743
|
visual: {
|
|
1689
|
-
charColor: 1709072
|
|
1744
|
+
charColor: 1709072,
|
|
1745
|
+
innerColor: 13213802
|
|
1690
1746
|
},
|
|
1691
1747
|
tags: [
|
|
1692
1748
|
"wood",
|
|
1693
1749
|
"organic",
|
|
1694
1750
|
"flammable"
|
|
1695
1751
|
],
|
|
1696
|
-
compressive: 15e4
|
|
1752
|
+
compressive: 15e4,
|
|
1753
|
+
hits: {
|
|
1754
|
+
blunt: {
|
|
1755
|
+
tear: "splinters",
|
|
1756
|
+
tearJoules: 150
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1697
1759
|
},
|
|
1698
1760
|
{
|
|
1699
1761
|
id: "plywood",
|
|
@@ -1723,14 +1785,24 @@ var GameDestruction = (() => {
|
|
|
1723
1785
|
smoke: "grey"
|
|
1724
1786
|
},
|
|
1725
1787
|
visual: {
|
|
1726
|
-
charColor: 1709072
|
|
1788
|
+
charColor: 1709072,
|
|
1789
|
+
innerColor: 14862234
|
|
1727
1790
|
},
|
|
1728
1791
|
tags: [
|
|
1729
1792
|
"wood",
|
|
1730
1793
|
"organic",
|
|
1731
1794
|
"flammable"
|
|
1732
1795
|
],
|
|
1733
|
-
compressive: 6e4
|
|
1796
|
+
compressive: 6e4,
|
|
1797
|
+
hits: {
|
|
1798
|
+
ballistic: {
|
|
1799
|
+
tear: "splinters"
|
|
1800
|
+
},
|
|
1801
|
+
blunt: {
|
|
1802
|
+
tear: "splinters",
|
|
1803
|
+
tearJoules: 80
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1734
1806
|
},
|
|
1735
1807
|
{
|
|
1736
1808
|
id: "mdf",
|
|
@@ -1757,14 +1829,25 @@ var GameDestruction = (() => {
|
|
|
1757
1829
|
smoke: "grey"
|
|
1758
1830
|
},
|
|
1759
1831
|
visual: {
|
|
1760
|
-
charColor: 1709072
|
|
1832
|
+
charColor: 1709072,
|
|
1833
|
+
innerColor: 12096362
|
|
1761
1834
|
},
|
|
1762
1835
|
tags: [
|
|
1763
1836
|
"wood",
|
|
1764
1837
|
"organic",
|
|
1765
1838
|
"flammable"
|
|
1766
1839
|
],
|
|
1767
|
-
compressive: 3e4
|
|
1840
|
+
compressive: 3e4,
|
|
1841
|
+
hits: {
|
|
1842
|
+
ballistic: {
|
|
1843
|
+
tear: "lumps",
|
|
1844
|
+
tearJoules: 120
|
|
1845
|
+
},
|
|
1846
|
+
blunt: {
|
|
1847
|
+
tear: "lumps",
|
|
1848
|
+
tearJoules: 20
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1768
1851
|
},
|
|
1769
1852
|
{
|
|
1770
1853
|
id: "glass",
|
|
@@ -1878,6 +1961,17 @@ var GameDestruction = (() => {
|
|
|
1878
1961
|
damageScale: {
|
|
1879
1962
|
explosive: 1.2
|
|
1880
1963
|
},
|
|
1964
|
+
hits: {
|
|
1965
|
+
ballistic: {
|
|
1966
|
+
unitJoules: 500,
|
|
1967
|
+
floor: 900,
|
|
1968
|
+
crater: true
|
|
1969
|
+
},
|
|
1970
|
+
blunt: {
|
|
1971
|
+
unitJoules: 1500,
|
|
1972
|
+
floor: 60
|
|
1973
|
+
}
|
|
1974
|
+
},
|
|
1881
1975
|
hitDebris: "chips",
|
|
1882
1976
|
breakDebris: "dust",
|
|
1883
1977
|
thermal: {
|
|
@@ -2246,7 +2340,8 @@ var GameDestruction = (() => {
|
|
|
2246
2340
|
smoke: "white"
|
|
2247
2341
|
},
|
|
2248
2342
|
visual: {
|
|
2249
|
-
charColor: 1709072
|
|
2343
|
+
charColor: 1709072,
|
|
2344
|
+
innerColor: 14207126
|
|
2250
2345
|
},
|
|
2251
2346
|
tags: [
|
|
2252
2347
|
"plant",
|
|
@@ -2254,7 +2349,12 @@ var GameDestruction = (() => {
|
|
|
2254
2349
|
"organic",
|
|
2255
2350
|
"flammable"
|
|
2256
2351
|
],
|
|
2257
|
-
compressive: 6e4
|
|
2352
|
+
compressive: 6e4,
|
|
2353
|
+
hits: {
|
|
2354
|
+
blunt: {
|
|
2355
|
+
tear: "none"
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2258
2358
|
},
|
|
2259
2359
|
{
|
|
2260
2360
|
id: "cactus",
|
|
@@ -2369,6 +2469,130 @@ var GameDestruction = (() => {
|
|
|
2369
2469
|
"masonry"
|
|
2370
2470
|
],
|
|
2371
2471
|
compressive: 2e4
|
|
2472
|
+
},
|
|
2473
|
+
{
|
|
2474
|
+
id: "asphalt",
|
|
2475
|
+
density: 2350,
|
|
2476
|
+
hpPerKg: 10,
|
|
2477
|
+
penetration: 14,
|
|
2478
|
+
penetrationClass: "concrete",
|
|
2479
|
+
penetrationScale: 0.8,
|
|
2480
|
+
ductility: 0.1,
|
|
2481
|
+
fracture: "crumble",
|
|
2482
|
+
fragmentSize: [
|
|
2483
|
+
0.03,
|
|
2484
|
+
0.15
|
|
2485
|
+
],
|
|
2486
|
+
hitDebris: "chips",
|
|
2487
|
+
breakDebris: "dust",
|
|
2488
|
+
thermal: {
|
|
2489
|
+
conductivity: 0.1,
|
|
2490
|
+
heatCapacity: 2,
|
|
2491
|
+
ignitionC: 400,
|
|
2492
|
+
burnRate: 2e-3,
|
|
2493
|
+
smoke: "black"
|
|
2494
|
+
},
|
|
2495
|
+
tags: [
|
|
2496
|
+
"masonry",
|
|
2497
|
+
"brittle"
|
|
2498
|
+
],
|
|
2499
|
+
compressive: 3e4
|
|
2500
|
+
},
|
|
2501
|
+
{
|
|
2502
|
+
id: "sand",
|
|
2503
|
+
density: 1600,
|
|
2504
|
+
hpPerKg: 2,
|
|
2505
|
+
penetration: 6,
|
|
2506
|
+
ductility: 0.4,
|
|
2507
|
+
fracture: "crumble",
|
|
2508
|
+
fragmentSize: [
|
|
2509
|
+
0.01,
|
|
2510
|
+
0.04
|
|
2511
|
+
],
|
|
2512
|
+
hitDebris: "dust",
|
|
2513
|
+
breakDebris: "dust",
|
|
2514
|
+
thermal: {
|
|
2515
|
+
conductivity: 0.1,
|
|
2516
|
+
heatCapacity: 2
|
|
2517
|
+
},
|
|
2518
|
+
tags: [
|
|
2519
|
+
"soil"
|
|
2520
|
+
],
|
|
2521
|
+
compressive: 1e4
|
|
2522
|
+
},
|
|
2523
|
+
{
|
|
2524
|
+
id: "gravel",
|
|
2525
|
+
density: 1800,
|
|
2526
|
+
hpPerKg: 3,
|
|
2527
|
+
penetration: 8,
|
|
2528
|
+
ductility: 0.3,
|
|
2529
|
+
fracture: "crumble",
|
|
2530
|
+
fragmentSize: [
|
|
2531
|
+
0.02,
|
|
2532
|
+
0.06
|
|
2533
|
+
],
|
|
2534
|
+
hitDebris: "chips",
|
|
2535
|
+
breakDebris: "dust",
|
|
2536
|
+
thermal: {
|
|
2537
|
+
conductivity: 0.1,
|
|
2538
|
+
heatCapacity: 2
|
|
2539
|
+
},
|
|
2540
|
+
tags: [
|
|
2541
|
+
"soil",
|
|
2542
|
+
"masonry"
|
|
2543
|
+
],
|
|
2544
|
+
compressive: 2e4
|
|
2545
|
+
},
|
|
2546
|
+
{
|
|
2547
|
+
id: "turf",
|
|
2548
|
+
density: 900,
|
|
2549
|
+
hpPerKg: 3,
|
|
2550
|
+
penetration: 4,
|
|
2551
|
+
ductility: 0.5,
|
|
2552
|
+
fracture: "crumble",
|
|
2553
|
+
fragmentSize: [
|
|
2554
|
+
0.03,
|
|
2555
|
+
0.1
|
|
2556
|
+
],
|
|
2557
|
+
hitDebris: "dust",
|
|
2558
|
+
breakDebris: "dust",
|
|
2559
|
+
thermal: {
|
|
2560
|
+
conductivity: 0.1,
|
|
2561
|
+
heatCapacity: 1.5,
|
|
2562
|
+
ignitionC: 250,
|
|
2563
|
+
burnRate: 0.03,
|
|
2564
|
+
smoke: "white"
|
|
2565
|
+
},
|
|
2566
|
+
tags: [
|
|
2567
|
+
"soil",
|
|
2568
|
+
"organic"
|
|
2569
|
+
],
|
|
2570
|
+
compressive: 15e3
|
|
2571
|
+
},
|
|
2572
|
+
{
|
|
2573
|
+
id: "mortar",
|
|
2574
|
+
density: 1900,
|
|
2575
|
+
hpPerKg: 10,
|
|
2576
|
+
penetration: 12,
|
|
2577
|
+
penetrationClass: "concrete",
|
|
2578
|
+
penetrationScale: 1.2,
|
|
2579
|
+
ductility: 0.03,
|
|
2580
|
+
fracture: "crumble",
|
|
2581
|
+
fragmentSize: [
|
|
2582
|
+
0.02,
|
|
2583
|
+
0.08
|
|
2584
|
+
],
|
|
2585
|
+
hitDebris: "chips",
|
|
2586
|
+
breakDebris: "dust",
|
|
2587
|
+
thermal: {
|
|
2588
|
+
conductivity: 0.15,
|
|
2589
|
+
heatCapacity: 2.5
|
|
2590
|
+
},
|
|
2591
|
+
tags: [
|
|
2592
|
+
"masonry",
|
|
2593
|
+
"brittle"
|
|
2594
|
+
],
|
|
2595
|
+
compressive: 1e5
|
|
2372
2596
|
}
|
|
2373
2597
|
];
|
|
2374
2598
|
|
|
@@ -2705,6 +2929,32 @@ var GameDestruction = (() => {
|
|
|
2705
2929
|
|
|
2706
2930
|
// src/world.ts
|
|
2707
2931
|
var THIN_SHEET_DISHING = 5e-3;
|
|
2932
|
+
var SWEEP_BACK = 0.01;
|
|
2933
|
+
function sweepOrigins(box, d, spacing) {
|
|
2934
|
+
const origins = [];
|
|
2935
|
+
const line = (from, to) => {
|
|
2936
|
+
const n = Math.max(1, Math.ceil((to - from) / spacing));
|
|
2937
|
+
return Array.from({ length: n + 1 }, (_, i) => from + (to - from) * i / n);
|
|
2938
|
+
};
|
|
2939
|
+
for (const axis of [0, 1, 2]) {
|
|
2940
|
+
if (Math.abs(d[axis]) < 1e-3) continue;
|
|
2941
|
+
const lead = d[axis] > 0 ? box.max[axis] - SWEEP_BACK : box.min[axis] + SWEEP_BACK;
|
|
2942
|
+
const u = (axis + 1) % 3;
|
|
2943
|
+
const v = (axis + 2) % 3;
|
|
2944
|
+
for (const a of line(box.min[u], box.max[u])) {
|
|
2945
|
+
for (const b of line(box.min[v], box.max[v])) {
|
|
2946
|
+
const point = [0, 0, 0];
|
|
2947
|
+
point[axis] = lead;
|
|
2948
|
+
point[u] = a;
|
|
2949
|
+
point[v] = b;
|
|
2950
|
+
origins.push(point);
|
|
2951
|
+
}
|
|
2952
|
+
}
|
|
2953
|
+
}
|
|
2954
|
+
return origins;
|
|
2955
|
+
}
|
|
2956
|
+
var THIN_SHEET_CRUSH = 5e-4;
|
|
2957
|
+
var THIN_SHEET_CONTACT = 0.025;
|
|
2708
2958
|
var DMG_PER_J = 0.02;
|
|
2709
2959
|
var BLAST_COUPLING = 0.1;
|
|
2710
2960
|
var SHELTER_DEFAULTS = { k: 2800, brokenTransmission: 0.5, minPath: 5e-3 };
|
|
@@ -2718,9 +2968,17 @@ var GameDestruction = (() => {
|
|
|
2718
2968
|
var BREACH_AREA = 0.25;
|
|
2719
2969
|
var BREACH_BLAST_REACH = 0.12;
|
|
2720
2970
|
var RESIDUAL_CAPACITY = 0.15;
|
|
2971
|
+
var SEVER_BAND = 0.1;
|
|
2972
|
+
var SEVER_ASPECT = 3;
|
|
2973
|
+
var SEVER_BALLISTIC = 2;
|
|
2974
|
+
var TEAR_JOULES = 150;
|
|
2975
|
+
var tears = 0;
|
|
2721
2976
|
var SUPPORT_TOLERANCE = 0.05;
|
|
2722
2977
|
var UNIT_FRAGMENT_FACTOR = 4;
|
|
2723
2978
|
var BREACH_GRID_CAP = 4096;
|
|
2979
|
+
var CHIP_ENERGY = 200;
|
|
2980
|
+
var CHIP_SPLASH = 0.5;
|
|
2981
|
+
var BAR_BOW = 0.08;
|
|
2724
2982
|
var CUT_SLICE = 1;
|
|
2725
2983
|
var cutFactorOf = (shape) => clamp2(Math.max(...shapeDims(shape)) / CUT_SLICE, 1, 50);
|
|
2726
2984
|
var clamp2 = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
@@ -2730,8 +2988,24 @@ var GameDestruction = (() => {
|
|
|
2730
2988
|
return v === void 0 ? void 0 : v * (mat.penetrationScale ?? 1);
|
|
2731
2989
|
};
|
|
2732
2990
|
var capacityScale = (p) => {
|
|
2733
|
-
|
|
2734
|
-
|
|
2991
|
+
const residual = p.spec.reinforcement?.residualCapacity ?? RESIDUAL_CAPACITY;
|
|
2992
|
+
if (p.st.stripped) return residual;
|
|
2993
|
+
const kept = Math.min(p.rt.removed ? 1 - p.rt.removed / p.mass0 : 1, 1 - sectionLoss(p));
|
|
2994
|
+
return Math.max(p.spec.reinforcement ? residual : 0.05, kept);
|
|
2995
|
+
};
|
|
2996
|
+
var sectionLoss = (p) => {
|
|
2997
|
+
const cells = p.st.chipped;
|
|
2998
|
+
if (!cells?.length) return 0;
|
|
2999
|
+
const [u, v] = planeAxes(p.thin);
|
|
3000
|
+
const along = p.dims[u] >= p.dims[v] ? u : v;
|
|
3001
|
+
const across = along === u ? v : u;
|
|
3002
|
+
let worst = 0;
|
|
3003
|
+
for (const c of cells) {
|
|
3004
|
+
let gone = 0;
|
|
3005
|
+
for (const d of cells) if (Math.abs(d.center[along] - c.center[along]) < d.size[along] / 2 + 1e-6) gone += d.size[across];
|
|
3006
|
+
worst = Math.max(worst, gone / Math.max(p.dims[across], 1e-6));
|
|
3007
|
+
}
|
|
3008
|
+
return Math.min(1, worst);
|
|
2735
3009
|
};
|
|
2736
3010
|
var isLost = (p) => p.st.status === "broken" || p.st.status === "melted" || p.st.status === "ashes";
|
|
2737
3011
|
var clone = (v) => structuredClone(v);
|
|
@@ -2764,6 +3038,7 @@ var GameDestruction = (() => {
|
|
|
2764
3038
|
let windCfg = windOpts ? resolveWind(windOpts) : null;
|
|
2765
3039
|
const ignitionRules = resolveIgnitionRules(options.ignition);
|
|
2766
3040
|
const breachBlastFactor = options.breach?.blastFactor ?? BREACH_BLAST_FACTOR;
|
|
3041
|
+
const chipEnergy = options.breach?.chipEnergy ?? CHIP_ENERGY;
|
|
2767
3042
|
const rng = createRng(options.seed ?? 1);
|
|
2768
3043
|
const rnd = () => rng.next();
|
|
2769
3044
|
let time = 0;
|
|
@@ -3137,41 +3412,68 @@ var GameDestruction = (() => {
|
|
|
3137
3412
|
markDamaged(p);
|
|
3138
3413
|
if (p.rt.hinge >= flexure.failRotationDeg * Math.PI / 180) hingeDrop(o, p, src);
|
|
3139
3414
|
};
|
|
3415
|
+
const freeSpan = (p, supports) => {
|
|
3416
|
+
if (supports.length === 0) return Infinity;
|
|
3417
|
+
const d = p.dims;
|
|
3418
|
+
const L = [0, 1, 2].sort((a, b) => d[b] - d[a])[0];
|
|
3419
|
+
const spans = supports.map((k) => contactBox(p, k)).map(([lo, hi]) => [lo[L], hi[L]]);
|
|
3420
|
+
let far = 0;
|
|
3421
|
+
for (let i = 0; i <= 16; i++) {
|
|
3422
|
+
const x = (i / 16 - 0.5) * d[L];
|
|
3423
|
+
let near = Infinity;
|
|
3424
|
+
for (const [lo, hi] of spans) near = Math.min(near, Math.max(lo - x, x - hi, 0));
|
|
3425
|
+
far = Math.max(far, near);
|
|
3426
|
+
}
|
|
3427
|
+
return 2 * far;
|
|
3428
|
+
};
|
|
3140
3429
|
const solveStructure = (o) => {
|
|
3141
3430
|
o.maxStress = 0;
|
|
3142
3431
|
if (!o.hasStructure) return;
|
|
3143
3432
|
const parts = o.parts;
|
|
3144
3433
|
const n = parts.length;
|
|
3145
3434
|
const dist = new Array(n).fill(-1);
|
|
3146
|
-
const queue = [];
|
|
3147
3435
|
const ok = (i) => parts[i].structural && !isLost(parts[i]);
|
|
3148
3436
|
const isPartition = (i) => parts[i].spec.structure?.role === "partition";
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
if (
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
const r = j.ia === s ? j.ib : j.ia;
|
|
3159
|
-
if (dist[r] >= 0 || !ok(r) || restsOn(o, j, r) !== s) continue;
|
|
3160
|
-
dist[r] = dist[s] + 1;
|
|
3161
|
-
queue.push(r);
|
|
3437
|
+
const out = /* @__PURE__ */ new Set();
|
|
3438
|
+
let queue = [];
|
|
3439
|
+
let lowerOf = /* @__PURE__ */ new Map();
|
|
3440
|
+
for (; ; ) {
|
|
3441
|
+
dist.fill(-1);
|
|
3442
|
+
queue = [];
|
|
3443
|
+
for (let i = 0; i < n; i++) if (ok(i) && !out.has(i) && parts[i].anchored) {
|
|
3444
|
+
dist[i] = 0;
|
|
3445
|
+
queue.push(i);
|
|
3162
3446
|
}
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3447
|
+
for (let h = 0; h < queue.length; h++) {
|
|
3448
|
+
const s = queue[h];
|
|
3449
|
+
if (isPartition(s)) continue;
|
|
3450
|
+
for (const j of o.byPart.get(parts[s].spec.id)) {
|
|
3451
|
+
if (j.st.open || !j.bears) continue;
|
|
3452
|
+
const r = j.ia === s ? j.ib : j.ia;
|
|
3453
|
+
if (dist[r] >= 0 || !ok(r) || out.has(r) || restsOn(o, j, r) !== s) continue;
|
|
3454
|
+
dist[r] = dist[s] + 1;
|
|
3455
|
+
queue.push(r);
|
|
3456
|
+
}
|
|
3172
3457
|
}
|
|
3173
|
-
lowerOf
|
|
3458
|
+
lowerOf = /* @__PURE__ */ new Map();
|
|
3459
|
+
let dropped = false;
|
|
3460
|
+
for (const i of queue) {
|
|
3461
|
+
const lower = [];
|
|
3462
|
+
for (const j of o.byPart.get(parts[i].spec.id)) {
|
|
3463
|
+
if (j.st.open || !j.bears) continue;
|
|
3464
|
+
const k = restsOn(o, j, i);
|
|
3465
|
+
if (k >= 0 && dist[k] >= 0 && dist[k] < dist[i] && !isPartition(k)) lower.push(k);
|
|
3466
|
+
}
|
|
3467
|
+
lowerOf.set(i, lower);
|
|
3468
|
+
const max = parts[i].spec.structure?.maxSpan;
|
|
3469
|
+
if (max !== void 0 && !parts[i].anchored && freeSpan(parts[i], lower.map((k) => parts[k])) > max) {
|
|
3470
|
+
out.add(i);
|
|
3471
|
+
dropped = true;
|
|
3472
|
+
}
|
|
3473
|
+
}
|
|
3474
|
+
if (!dropped) break;
|
|
3174
3475
|
}
|
|
3476
|
+
const incoming = new Array(n).fill(0);
|
|
3175
3477
|
const bending = new Array(n).fill(0);
|
|
3176
3478
|
const through = parts.map(() => []);
|
|
3177
3479
|
const pass = (k, at, mass) => {
|
|
@@ -3683,6 +3985,7 @@ var GameDestruction = (() => {
|
|
|
3683
3985
|
const r = rayPart(o, p, origin, d);
|
|
3684
3986
|
if (!r || r.t0 < 0 || r.t0 > maxDist) continue;
|
|
3685
3987
|
if (p.st.opening && insideOpening(o, p, vec.madd(origin, d, r.t0))) continue;
|
|
3988
|
+
if (p.st.chipped && insideChip(p, objLocal(o, vec.madd(origin, d, r.t0)))) continue;
|
|
3686
3989
|
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 };
|
|
3687
3990
|
}
|
|
3688
3991
|
}
|
|
@@ -3883,6 +4186,12 @@ var GameDestruction = (() => {
|
|
|
3883
4186
|
debris(o, p, layer.mat.hitDebris, wp, normal, 5, 2, src);
|
|
3884
4187
|
setStage(o, p, 1, {}, src);
|
|
3885
4188
|
};
|
|
4189
|
+
const crater = (o, p, wp, normal, radius, src) => {
|
|
4190
|
+
markDamaged(p);
|
|
4191
|
+
emit({ ...base(o, src), type: "spall", part: p.spec.id, layer: -1, point: wp, area: Math.PI * radius * radius });
|
|
4192
|
+
debris(o, p, p.mat.breakDebris, wp, normal, 6, 1.5, src);
|
|
4193
|
+
setStage(o, p, 1, {}, src);
|
|
4194
|
+
};
|
|
3886
4195
|
const insideOpening = (o, p, objectPoint) => {
|
|
3887
4196
|
const op = p.st.opening;
|
|
3888
4197
|
if (!op) return false;
|
|
@@ -3946,6 +4255,91 @@ var GameDestruction = (() => {
|
|
|
3946
4255
|
if (total < (p.spec.breach?.area ?? BREACH_AREA)) return;
|
|
3947
4256
|
openBreach(o, p, [sum[0] / total, sum[1] / total, sum[2] / total], total, energy, dir, src);
|
|
3948
4257
|
};
|
|
4258
|
+
const unitGrids = /* @__PURE__ */ new WeakMap();
|
|
4259
|
+
const gridOf = (p) => {
|
|
4260
|
+
let grid = unitGrids.get(p);
|
|
4261
|
+
if (!grid) unitGrids.set(p, grid = unitGrid(p.dims, p.thin, unitsOf(p), BREACH_GRID_CAP));
|
|
4262
|
+
return grid;
|
|
4263
|
+
};
|
|
4264
|
+
const insideChip = (p, objectPoint) => {
|
|
4265
|
+
const cells = p.st.chipped;
|
|
4266
|
+
if (!cells?.length) return false;
|
|
4267
|
+
const [u, v] = planeAxes(p.thin);
|
|
4268
|
+
const lp = toLocal(p.pos, p.q, objectPoint);
|
|
4269
|
+
return cells.some((c) => Math.abs(lp[u] - c.center[u]) <= c.size[u] / 2 && Math.abs(lp[v] - c.center[v]) <= c.size[v] / 2);
|
|
4270
|
+
};
|
|
4271
|
+
const chipUnits = (o, p, kind, wp, hitEnergy, dir, radius, src) => {
|
|
4272
|
+
var _a, _b;
|
|
4273
|
+
if (!p.spec.units || !p.staged || chipEnergy <= 0 || p.st.stripped || isLost(p) || (p.st.stage ?? 0) >= 2) return;
|
|
4274
|
+
const rule = p.mat.hits?.[kind];
|
|
4275
|
+
const perKg = rule?.unitJoules ?? chipEnergy;
|
|
4276
|
+
const energy = hitEnergy - (rule?.floor ?? 0);
|
|
4277
|
+
if (perKg <= 0 || energy <= 0) return;
|
|
4278
|
+
const [u, v] = planeAxes(p.thin);
|
|
4279
|
+
const lp = toLocal(p.pos, p.q, objLocal(o, wp));
|
|
4280
|
+
const grid = gridOf(p);
|
|
4281
|
+
const worn = (_a = p.rt).chips ?? (_a.chips = {});
|
|
4282
|
+
const out = [];
|
|
4283
|
+
for (let i = 0; i < grid.length; i++) {
|
|
4284
|
+
const cell = grid[i];
|
|
4285
|
+
if (worn[i] === -1) continue;
|
|
4286
|
+
const gap = Math.hypot(Math.max(0, Math.abs(lp[u] - cell.center[u]) - cell.size[u] / 2), Math.max(0, Math.abs(lp[v] - cell.center[v]) - cell.size[v] / 2));
|
|
4287
|
+
if (gap > radius) continue;
|
|
4288
|
+
const share = gap === 0 ? 1 : CHIP_SPLASH * (1 - gap / radius);
|
|
4289
|
+
const total = (worn[i] ?? 0) + energy * share;
|
|
4290
|
+
const mass = p.mat.density * cell.size[0] * cell.size[1] * cell.size[2];
|
|
4291
|
+
if (total < perKg * mass) {
|
|
4292
|
+
worn[i] = total;
|
|
4293
|
+
continue;
|
|
4294
|
+
}
|
|
4295
|
+
worn[i] = -1;
|
|
4296
|
+
out.push(cell);
|
|
4297
|
+
}
|
|
4298
|
+
if (out.length === 0) return;
|
|
4299
|
+
const { c, q } = partXf(o, p);
|
|
4300
|
+
const chipped = (_b = p.st).chipped ?? (_b.chipped = []);
|
|
4301
|
+
const holes = localHolesOf(p);
|
|
4302
|
+
for (const cell of out) {
|
|
4303
|
+
const mass = Math.min(p.st.mass, p.mat.density * cell.size[0] * cell.size[1] * cell.size[2]);
|
|
4304
|
+
const center = toWorld(c, q, cell.center);
|
|
4305
|
+
const speed = clamp2(Math.sqrt(2 * energy * 0.05 / Math.max(mass, 0.05)), 0.5, 6);
|
|
4306
|
+
const fragments = cellFragments(o, p, [cell], wp, dir, speed, mass, `c${chipped.length}_`);
|
|
4307
|
+
p.st.holes = p.st.holes.filter((_, k) => marksInBox(cell.center, cell.size, void 0, [holes[k]]).length === 0);
|
|
4308
|
+
chipped.push({ center: [cell.center[0], cell.center[1], cell.center[2]], size: [cell.size[0], cell.size[1], cell.size[2]] });
|
|
4309
|
+
p.st.mass = Math.max(0, p.st.mass - mass);
|
|
4310
|
+
p.rt.removed = (p.rt.removed ?? 0) + mass;
|
|
4311
|
+
markDamaged(p);
|
|
4312
|
+
setStage(o, p, 1, {}, src);
|
|
4313
|
+
emit({ ...base(o, src), type: "chip", part: p.spec.id, center, size: [cell.size[0], cell.size[1], cell.size[2]], rotation: q, fragments });
|
|
4314
|
+
debris(o, p, p.mat.breakDebris, center, vec.scale(dir ?? [0, 1, 0], -1), 6, 2, src);
|
|
4315
|
+
}
|
|
4316
|
+
if (p.spec.reinforcement) exposeRebar(o, p, src);
|
|
4317
|
+
for (const cell of out) addBreach(o, p, toWorld(c, q, cell.center), cell.size[u] * cell.size[v], energy, dir, src);
|
|
4318
|
+
if (p.structural && !isLost(p)) {
|
|
4319
|
+
solveStructure(o);
|
|
4320
|
+
collapseCheck(o, src);
|
|
4321
|
+
}
|
|
4322
|
+
};
|
|
4323
|
+
const exposeRebar = (o, p, src) => {
|
|
4324
|
+
const r = p.spec.reinforcement;
|
|
4325
|
+
if (!r) return;
|
|
4326
|
+
const [u, v] = planeAxes(p.thin);
|
|
4327
|
+
const all = rebarBars(p.dims, p.thin, r.spacing);
|
|
4328
|
+
let bars;
|
|
4329
|
+
if (p.st.stripped) bars = all;
|
|
4330
|
+
else {
|
|
4331
|
+
bars = [];
|
|
4332
|
+
for (const cell of p.st.chipped ?? []) bars.push(...clipBarsToBox(all, u, v, cell.center, cell.size));
|
|
4333
|
+
const op = p.st.opening;
|
|
4334
|
+
if (op) {
|
|
4335
|
+
const lc = toLocal(p.pos, p.q, op.center);
|
|
4336
|
+
bars.push(...clipBarsToDisc(all, u, v, [lc[u], lc[v]], op.radius + 0.05));
|
|
4337
|
+
}
|
|
4338
|
+
}
|
|
4339
|
+
const bow = clamp2(((p.st.stress ?? 0) - 1) / 0.5, 0, 1);
|
|
4340
|
+
if (bow > 0) bars = bars.flatMap((bar2, i) => bendBar(bar2, p.thin, bow * BAR_BOW, i));
|
|
4341
|
+
emit({ ...base(o, src), type: "expose", part: p.spec.id, bars: worldBars(o, p, bars) });
|
|
4342
|
+
};
|
|
3949
4343
|
const openBreach = (o, p, center, total, energy, dir, src) => {
|
|
3950
4344
|
const [u, v] = planeAxes(p.thin);
|
|
3951
4345
|
const radius = clamp2(Math.sqrt(total / Math.PI), Math.sqrt((p.spec.breach?.area ?? BREACH_AREA) / Math.PI), breachRadius);
|
|
@@ -3957,7 +4351,8 @@ var GameDestruction = (() => {
|
|
|
3957
4351
|
failPart(o, p, worldCenter, dir, energy, 12, src);
|
|
3958
4352
|
return;
|
|
3959
4353
|
}
|
|
3960
|
-
const
|
|
4354
|
+
const worn = p.rt.chips;
|
|
4355
|
+
const cells = cellsWithin(gridOf(p).filter((_, i) => worn?.[i] !== -1), p.thin, lc, radius);
|
|
3961
4356
|
const removed = p.st.mass * cells.reduce((sum, cell) => sum + cell.size[u] * cell.size[v], 0) / (p.dims[u] * p.dims[v]);
|
|
3962
4357
|
const speed = clamp2(Math.sqrt(2 * energy * 0.1 / Math.max(removed, 0.05)), 0.5, 8);
|
|
3963
4358
|
const fragments = cellFragments(o, p, cells, worldCenter, dir, speed, removed, "b");
|
|
@@ -3971,10 +4366,7 @@ var GameDestruction = (() => {
|
|
|
3971
4366
|
setStage(o, p, 2, { opening: { center: worldCenter, radius }, fragments }, src);
|
|
3972
4367
|
debris(o, p, p.mat.breakDebris, worldCenter, vec.scale(dir ?? [0, 1, 0], -1), 14, 3, src);
|
|
3973
4368
|
const reinforcement = p.spec.reinforcement;
|
|
3974
|
-
if (reinforcement)
|
|
3975
|
-
const bars = worldBars(o, p, clipBarsToDisc(rebarBars(p.dims, p.thin, reinforcement.spacing), u, v, [lc[u], lc[v]], radius + 0.05));
|
|
3976
|
-
if (bars.length > 0) emit({ ...base(o, src), type: "expose", part: p.spec.id, bars });
|
|
3977
|
-
}
|
|
4369
|
+
if (reinforcement) exposeRebar(o, p, src);
|
|
3978
4370
|
if (p.structural) solveStructure(o);
|
|
3979
4371
|
};
|
|
3980
4372
|
const stripPart = (o, p, reinforcement, src) => {
|
|
@@ -3986,8 +4378,8 @@ var GameDestruction = (() => {
|
|
|
3986
4378
|
p.st.burning = false;
|
|
3987
4379
|
p.rt.jet = false;
|
|
3988
4380
|
markDamaged(p);
|
|
3989
|
-
emit({ ...base(o, src), type: "expose", part: p.spec.id, bars: worldBars(o, p, bars) });
|
|
3990
4381
|
solveStructure(o);
|
|
4382
|
+
exposeRebar(o, p, src);
|
|
3991
4383
|
collapseCheck(o, src);
|
|
3992
4384
|
};
|
|
3993
4385
|
const unitFragmentPlan = (p, cap) => {
|
|
@@ -4041,6 +4433,8 @@ var GameDestruction = (() => {
|
|
|
4041
4433
|
const grain = p.spec.grain ? AXIS[p.spec.grain] : void 0;
|
|
4042
4434
|
const hitLocal = toLocal(c, q, hitW);
|
|
4043
4435
|
const unitPlan = p.spec.units ? unitFragmentPlan(p, maxFragments * UNIT_FRAGMENT_FACTOR) : [];
|
|
4436
|
+
const axis = style === "splinter" || style === "snap" ? severAxis(p) : -1;
|
|
4437
|
+
const cutAt = axis < 0 ? void 0 : p.rt.cutAt ?? (origin ? hitLocal[axis] : void 0);
|
|
4044
4438
|
const plan = unitPlan.length > 0 ? unitPlan : planFragments({
|
|
4045
4439
|
style,
|
|
4046
4440
|
dims: p.dims,
|
|
@@ -4050,7 +4444,8 @@ var GameDestruction = (() => {
|
|
|
4050
4444
|
fragmentSize: p.mat.fragmentSize ?? [0.04, 0.2],
|
|
4051
4445
|
maxFragments,
|
|
4052
4446
|
rnd,
|
|
4053
|
-
sheet: p.mat.sheet
|
|
4447
|
+
sheet: p.mat.sheet,
|
|
4448
|
+
...cutAt !== void 0 ? { cut: { at: cutAt, gap: SEVER_BAND } } : {}
|
|
4054
4449
|
});
|
|
4055
4450
|
const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
|
|
4056
4451
|
const mass = p.st.mass;
|
|
@@ -4109,10 +4504,33 @@ var GameDestruction = (() => {
|
|
|
4109
4504
|
emit({ ...base(o, src), type: "primed", part: p.spec.id, point: partXf(o, p).c, trigger, delay });
|
|
4110
4505
|
};
|
|
4111
4506
|
const CHAIN_TRIGGER_OF = { ballistic: "shot", explosive: "explosion" };
|
|
4507
|
+
const severAxis = (p) => {
|
|
4508
|
+
if (p.mat.fracture !== "splinter" && p.mat.fracture !== "snap" || p.spec.shape.kind !== "box") return -1;
|
|
4509
|
+
const d = p.dims;
|
|
4510
|
+
const order = [0, 1, 2].sort((a, b) => d[b] - d[a]);
|
|
4511
|
+
return d[order[0]] >= SEVER_ASPECT * d[order[1]] && d[order[0]] >= 3 * SEVER_BAND ? order[0] : -1;
|
|
4512
|
+
};
|
|
4513
|
+
const bandOf = (o, p, axis, origin) => {
|
|
4514
|
+
const { c, q } = partXf(o, p);
|
|
4515
|
+
const along = toLocal(c, q, origin)[axis];
|
|
4516
|
+
const L = p.dims[axis];
|
|
4517
|
+
return clamp2(Math.floor((along + L / 2) / SEVER_BAND), 0, Math.ceil(L / SEVER_BAND) - 1);
|
|
4518
|
+
};
|
|
4112
4519
|
const applyDamage = (o, p, amount, type, origin, dir, fragEnergy, cap, src) => {
|
|
4520
|
+
var _a;
|
|
4113
4521
|
if (isLost(p) || amount <= 0) return 0;
|
|
4114
4522
|
const prev = p.st.hp;
|
|
4115
4523
|
p.st.hp -= amount;
|
|
4524
|
+
const axis = origin && type !== "fire" && type !== "heat" ? severAxis(p) : -1;
|
|
4525
|
+
if (axis >= 0 && p.st.hp > 0) {
|
|
4526
|
+
const band = bandOf(o, p, axis, origin);
|
|
4527
|
+
const cuts = (_a = p.rt).cuts ?? (_a.cuts = {});
|
|
4528
|
+
cuts[band] = (cuts[band] ?? 0) + amount * (type === "ballistic" ? SEVER_BALLISTIC : 1);
|
|
4529
|
+
if (cuts[band] >= p.st.maxHp * SEVER_BAND / p.dims[axis]) {
|
|
4530
|
+
p.rt.cutAt = -p.dims[axis] / 2 + (band + 0.5) * SEVER_BAND;
|
|
4531
|
+
p.st.hp = 0;
|
|
4532
|
+
}
|
|
4533
|
+
}
|
|
4116
4534
|
markDamaged(p);
|
|
4117
4535
|
const trigger = CHAIN_TRIGGER_OF[type];
|
|
4118
4536
|
const chained = trigger !== void 0 && chainDelayOf(p.spec, trigger) !== void 0;
|
|
@@ -4148,10 +4566,53 @@ var GameDestruction = (() => {
|
|
|
4148
4566
|
}
|
|
4149
4567
|
}
|
|
4150
4568
|
};
|
|
4569
|
+
const tearOf = (mat, kind) => {
|
|
4570
|
+
const h = mat.hits?.[kind];
|
|
4571
|
+
const tear = h?.tear ?? (kind === "ballistic" && mat.penetrationClass === "wood" ? "splinters" : "none");
|
|
4572
|
+
return { tear, joules: h?.tearJoules ?? TEAR_JOULES };
|
|
4573
|
+
};
|
|
4574
|
+
const tearAt = (o, p, kind, entry, exit, d, E, scale, src) => {
|
|
4575
|
+
const { tear, joules } = tearOf(p.mat, kind);
|
|
4576
|
+
if (tear === "none") return;
|
|
4577
|
+
const shot = kind === "ballistic";
|
|
4578
|
+
const n = shot ? exit ? clamp2(Math.round(E / joules), 1, 7) : 1 : clamp2(Math.floor(E / joules), 0, 5);
|
|
4579
|
+
if (n === 0) return;
|
|
4580
|
+
const fibre = tear === "lumps";
|
|
4581
|
+
const { q } = partXf(o, p);
|
|
4582
|
+
const along = (p.spec.grain ? AXIS[p.spec.grain] : void 0) ?? [0, 1, 2].sort((a, b) => p.dims[b] - p.dims[a])[0];
|
|
4583
|
+
const fragments = [];
|
|
4584
|
+
for (let i = 0; i < n; i++) {
|
|
4585
|
+
const back = !exit || i % 3 === 2;
|
|
4586
|
+
const len = scale * (fibre ? 1.2 + 1.2 * rnd() : 3 + 4 * rnd()), w = scale * (0.8 + 0.8 * rnd()), t = scale * (fibre ? 0.7 + 0.6 * rnd() : 0.4 + 0.4 * rnd());
|
|
4587
|
+
const size = [w, w, w];
|
|
4588
|
+
size[along] = len;
|
|
4589
|
+
size[(along + 1) % 3] = w;
|
|
4590
|
+
size[(along + 2) % 3] = t;
|
|
4591
|
+
const spread3 = [(rnd() - 0.5) * 0.8, (rnd() - 0.2) * 0.8, (rnd() - 0.5) * 0.8];
|
|
4592
|
+
const speed = shot ? back ? 1.5 + 2 * rnd() : 2 + 4 * rnd() : 0.8 + 1.2 * rnd();
|
|
4593
|
+
const v = vec.scale(vec.normalize(vec.add(back ? vec.scale(d, -1) : d, spread3)), speed);
|
|
4594
|
+
const mass = size[0] * size[1] * size[2] * p.mat.density;
|
|
4595
|
+
fragments.push({
|
|
4596
|
+
id: `${p.spec.id}#t${tears++}-${i}`,
|
|
4597
|
+
part: p.spec.id,
|
|
4598
|
+
material: p.spec.material,
|
|
4599
|
+
shape: fibre ? "chunk" : "splinter",
|
|
4600
|
+
center: back || !exit ? vec.madd(entry, d, -0.01) : vec.madd(exit, d, 0.01),
|
|
4601
|
+
size,
|
|
4602
|
+
rotation: q,
|
|
4603
|
+
velocity: v,
|
|
4604
|
+
inner: true,
|
|
4605
|
+
angularVelocity: [(rnd() - 0.5) * 30, (rnd() - 0.5) * 30, (rnd() - 0.5) * 30],
|
|
4606
|
+
mass
|
|
4607
|
+
});
|
|
4608
|
+
}
|
|
4609
|
+
const patch = [scale * 4, scale * 4, scale * 4];
|
|
4610
|
+
emit({ ...base(o, src), type: "chip", part: p.spec.id, center: exit ?? entry, size: patch, rotation: q, fragments });
|
|
4611
|
+
};
|
|
4151
4612
|
const ballistic = (o, p, entry, n, d, E, caliber, pathLen, src, round = {}) => {
|
|
4152
4613
|
const mat = p.mat;
|
|
4153
4614
|
const piercing = round.piercing ?? 1;
|
|
4154
|
-
if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry))) {
|
|
4615
|
+
if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry)) || insideChip(p, objLocal(o, entry))) {
|
|
4155
4616
|
return { penetrated: true, exitEnergy: E, exitPoint: vec.madd(entry, d, Math.min(pathLen, p.thickness)), damage: 0 };
|
|
4156
4617
|
}
|
|
4157
4618
|
const sin = Math.min(1, Math.abs(vec.dot(d, n)));
|
|
@@ -4176,6 +4637,7 @@ var GameDestruction = (() => {
|
|
|
4176
4637
|
return { penetrated: false, exitEnergy: 0, damage: dmg2 };
|
|
4177
4638
|
}
|
|
4178
4639
|
if (p.layers.length > 0) spallOuter(o, p, entry, face, n, Math.max(0.03, caliber / 1e3 * 4), src);
|
|
4640
|
+
else if (mat.hits?.ballistic?.crater) crater(o, p, entry, n, clamp2(0.02 + Math.sqrt(E) * 15e-4, 0.02, 0.15), src);
|
|
4179
4641
|
const brittle = mat.ductility < 0.3;
|
|
4180
4642
|
const ratio = Ep / Math.max(thr, 1e-6);
|
|
4181
4643
|
const crackLen = clamp2(0.03 + ratio * 0.2, 0.03, 0.8);
|
|
@@ -4209,7 +4671,10 @@ var GameDestruction = (() => {
|
|
|
4209
4671
|
} else addHole(o, p, entry, n, caliber / 1e3, false, void 0, src);
|
|
4210
4672
|
debris(o, p, mat.hitDebris, entry, n, clamp2(Math.round(E / 60) + 2, 2, 14), clamp2(Math.sqrt(E) / 6, 1, 8), src);
|
|
4211
4673
|
wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true);
|
|
4212
|
-
const
|
|
4674
|
+
const absorbed = penetrated && mat.ductility >= 0.5 && mat.fracture !== "tear" ? Math.min(E, thr) : E;
|
|
4675
|
+
const dmg = applyDamage(o, p, absorbed * DMG_PER_J * scale, "ballistic", entry, d, absorbed * 0.1, 12, src);
|
|
4676
|
+
if (!isLost(p)) tearAt(o, p, "ballistic", entry, penetrated ? exitPoint : void 0, d, E, caliber / 1e3, src);
|
|
4677
|
+
chipUnits(o, p, "ballistic", entry, penetrated ? Math.min(E, Math.max(thr, E * 0.5)) : E, d, Math.max(0.05, caliber / 1e3 * 8), src);
|
|
4213
4678
|
return { penetrated, exitEnergy: penetrated ? penMm !== void 0 ? E * (1 - thr / Ep) : E - thr / (hard ? piercing : 1) : 0, exitPoint, damage: dmg };
|
|
4214
4679
|
};
|
|
4215
4680
|
const melee = (o, p, kind, E, point, n, d, contactR, src) => {
|
|
@@ -4217,14 +4682,20 @@ var GameDestruction = (() => {
|
|
|
4217
4682
|
const thrMm = mat.penetration * p.thickness * 1e3;
|
|
4218
4683
|
const fragile = kind === "blunt" ? fragility(mat.ductility, p.thickness) : 1;
|
|
4219
4684
|
const Ec = E * fragile;
|
|
4685
|
+
const sheet = mat.ductility >= 0.5 && p.thickness <= THIN_SHEET_CRUSH;
|
|
4686
|
+
const fold = sheet ? Math.min(4, Math.sqrt(1e-3 / p.thickness)) : 1;
|
|
4687
|
+
const patch = sheet ? Math.min(contactR, THIN_SHEET_CONTACT) : contactR;
|
|
4220
4688
|
if (E > 0) {
|
|
4221
4689
|
if (kind === "blunt") {
|
|
4222
|
-
if (mat.ductility >= 0.5) addDent(o, p, point, n, Math.max(
|
|
4690
|
+
if (mat.ductility >= 0.5) addDent(o, p, point, n, Math.max(patch, 0.015), clamp2(E * 1e-4 * fold, 1e-3, sheet ? Math.min(0.06, Math.min(...p.dims) * 0.45) : 0.06), src);
|
|
4223
4691
|
else if (mat.ductility < 0.3) {
|
|
4224
4692
|
if (Ec >= 5) addCrack(o, p, point, n, clamp2(0.04 + Math.min(Ec, E * 6) * 8e-4, 0.04, 0.6), src);
|
|
4225
4693
|
} else if (E >= thrMm * 0.2) addDent(o, p, point, n, Math.max(contactR, 0.015), clamp2(E * 6e-5, 1e-3, 0.04), src);
|
|
4226
4694
|
} else if (mat.fracture === "tear") {
|
|
4227
4695
|
addHole(o, p, point, n, clamp2(0.02 + E * 5e-4, 0.02, 0.3), true, void 0, src);
|
|
4696
|
+
} else if (mat.ductility >= 0.3 && E >= thrMm) {
|
|
4697
|
+
addHole(o, p, point, n, clamp2(contactR, 0.01, 0.08), true, void 0, src);
|
|
4698
|
+
if (sheet) addDent(o, p, point, n, Math.max(patch * 0.6, 0.01), clamp2(E * 4e-5 * fold, 5e-4, 0.02), src);
|
|
4228
4699
|
} else if (mat.ductility >= 0.5) {
|
|
4229
4700
|
addDent(o, p, point, n, Math.max(contactR, 0.01), clamp2(E * 4e-5, 5e-4, 0.02), src);
|
|
4230
4701
|
}
|
|
@@ -4237,6 +4708,8 @@ var GameDestruction = (() => {
|
|
|
4237
4708
|
const scale = mat.damageScale?.[kind] ?? 1;
|
|
4238
4709
|
const done = applyDamage(o, p, Ec * DMG_PER_J * scale * (kind === "slash" ? p.cutFactor : 1), kind, point, d, E * 0.1, 12, src);
|
|
4239
4710
|
addBreach(o, p, point, breachAreaOf(p, done, false), E, d, src);
|
|
4711
|
+
if (kind === "blunt") chipUnits(o, p, "blunt", point, E, d, Math.max(contactR, 0.03), src);
|
|
4712
|
+
if (E > 0 && !isLost(p)) tearAt(o, p, kind, point, void 0, d, E, clamp2(contactR * 0.2, 6e-3, 0.02), src);
|
|
4240
4713
|
return done;
|
|
4241
4714
|
};
|
|
4242
4715
|
const blastPart = (o, p, point, energy, radius, src, res, reachAt) => {
|
|
@@ -4815,6 +5288,7 @@ var GameDestruction = (() => {
|
|
|
4815
5288
|
if (p.rt.stressT <= 0) {
|
|
4816
5289
|
p.rt.stressT = 0.5;
|
|
4817
5290
|
emit({ ...base(o), type: "stress", part: p.spec.id, stress, load: p.st.load ?? 0 });
|
|
5291
|
+
if (p.spec.reinforcement && (p.st.stripped || p.st.chipped?.length || p.st.opening)) exposeRebar(o, p);
|
|
4818
5292
|
}
|
|
4819
5293
|
applyDamage(o, p, overloadRate(stress) * p.st.maxHp * dt, "blunt", null, null, 5, 3);
|
|
4820
5294
|
} else if (stress > 0.8 && p.mat.ductility < 0.3 && p.rt.stressT <= 0 && p.st.cracks.length < 6) {
|
|
@@ -5031,6 +5505,18 @@ var GameDestruction = (() => {
|
|
|
5031
5505
|
const r = raycastInternal(origin, direction, maxDistance);
|
|
5032
5506
|
return r ? { object: r.object, part: r.part, point: r.point, normal: r.normal, distance: r.distance } : null;
|
|
5033
5507
|
},
|
|
5508
|
+
sweepBox(box, direction, distance, options2 = {}) {
|
|
5509
|
+
const d = vec.normalize(direction);
|
|
5510
|
+
const ignored = options2.ignore === void 0 ? void 0 : objects.get(options2.ignore);
|
|
5511
|
+
const ignore = ignored ? new Set(ignored.parts.map((p) => `${ignored.id}\0${p.spec.id}`)) : void 0;
|
|
5512
|
+
let best = null;
|
|
5513
|
+
for (const origin of sweepOrigins(box, d, options2.spacing ?? 0.2)) {
|
|
5514
|
+
const r = raycastInternal(origin, d, distance + SWEEP_BACK, ignore);
|
|
5515
|
+
if (!r || best && r.distance >= best.distance) continue;
|
|
5516
|
+
best = { object: r.object, part: r.part, point: r.point, normal: r.normal, distance: Math.max(0, r.distance - SWEEP_BACK) };
|
|
5517
|
+
}
|
|
5518
|
+
return best;
|
|
5519
|
+
},
|
|
5034
5520
|
shoot(origin, direction, energy, caliber, source, round) {
|
|
5035
5521
|
const extras = typeof round === "number" ? { piercing: round } : round ?? {};
|
|
5036
5522
|
return withCollapses(run(() => {
|
|
@@ -5675,7 +6161,12 @@ var GameDestruction = (() => {
|
|
|
5675
6161
|
cactus: "cactus",
|
|
5676
6162
|
flesh_fruit: "fruit",
|
|
5677
6163
|
wax: "wax",
|
|
5678
|
-
soil: "soil"
|
|
6164
|
+
soil: "soil",
|
|
6165
|
+
asphalt: "asphalt",
|
|
6166
|
+
sand: "sand",
|
|
6167
|
+
gravel: "gravel",
|
|
6168
|
+
turf: "turf",
|
|
6169
|
+
mortar: "mortar"
|
|
5679
6170
|
},
|
|
5680
6171
|
partStatus: { intact: "intact", damaged: "damaged", collapsed: "collapsed", broken: "broken", melted: "melted", ashes: "ashes" },
|
|
5681
6172
|
jointKinds: {
|
|
@@ -5728,6 +6219,7 @@ var GameDestruction = (() => {
|
|
|
5728
6219
|
sway: "sway",
|
|
5729
6220
|
spall: "plaster came off",
|
|
5730
6221
|
stage: "damage stage",
|
|
6222
|
+
chip: "brick knocked out",
|
|
5731
6223
|
expose: "rebar bare",
|
|
5732
6224
|
body: "body",
|
|
5733
6225
|
blast: "blast",
|
|
@@ -5765,7 +6257,12 @@ var GameDestruction = (() => {
|
|
|
5765
6257
|
cactus: "cacto",
|
|
5766
6258
|
flesh_fruit: "fruta",
|
|
5767
6259
|
wax: "cera",
|
|
5768
|
-
soil: "terra"
|
|
6260
|
+
soil: "terra",
|
|
6261
|
+
asphalt: "asfalto",
|
|
6262
|
+
sand: "areia",
|
|
6263
|
+
gravel: "brita",
|
|
6264
|
+
turf: "grama",
|
|
6265
|
+
mortar: "argamassa"
|
|
5769
6266
|
},
|
|
5770
6267
|
partStatus: { intact: "intacto", damaged: "danificado", collapsed: "desabado", broken: "quebrado", melted: "derretido", ashes: "cinzas" },
|
|
5771
6268
|
jointKinds: {
|
|
@@ -5818,6 +6315,7 @@ var GameDestruction = (() => {
|
|
|
5818
6315
|
sway: "balan\xE7o",
|
|
5819
6316
|
spall: "reboco soltou",
|
|
5820
6317
|
stage: "est\xE1gio de dano",
|
|
6318
|
+
chip: "tijolo arrancado",
|
|
5821
6319
|
expose: "vergalh\xE3o \xE0 mostra",
|
|
5822
6320
|
body: "corpo",
|
|
5823
6321
|
blast: "explos\xE3o",
|