@adatechnology/game-destruction 0.10.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 +39 -1
- package/dist/index.global.js +382 -29
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +382 -29
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -376,7 +376,9 @@ var severAt = (inp, root, at, gap) => {
|
|
|
376
376
|
const band = { c: [root.c[0], root.c[1], root.c[2]], s: [dims[0], dims[1], dims[2]] };
|
|
377
377
|
band.c[L] = (lo + hi) / 2;
|
|
378
378
|
band.s[L] = Math.max(hi - lo, 0.01);
|
|
379
|
-
|
|
379
|
+
if (inp.style === "snap") {
|
|
380
|
+
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] });
|
|
381
|
+
} else for (const p of splinterBox(inp, band, Math.max(3, inp.maxFragments - out.length))) out.push(p);
|
|
380
382
|
return out;
|
|
381
383
|
};
|
|
382
384
|
var tearStack = (inp, root, t, u, v, n, sheet) => {
|
|
@@ -407,6 +409,7 @@ var planFragments = (inp) => {
|
|
|
407
409
|
const t = sorted[0];
|
|
408
410
|
const [u, v] = [sorted[1], sorted[2]].sort((a, b) => a - b);
|
|
409
411
|
if (style === "snap") {
|
|
412
|
+
if (inp.cut) return severAt(inp, root, inp.cut.at, inp.cut.gap);
|
|
410
413
|
const n2 = Math.min(maxFragments, 2 + Math.floor(rnd() * 3));
|
|
411
414
|
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] });
|
|
412
415
|
return out;
|
|
@@ -586,6 +589,27 @@ var rebarBars = (dims, thin, spacing) => {
|
|
|
586
589
|
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]]));
|
|
587
590
|
return out;
|
|
588
591
|
};
|
|
592
|
+
var clipBarsToBox = (bars, u, v, center, size) => {
|
|
593
|
+
const out = [];
|
|
594
|
+
for (const [a, b] of bars) {
|
|
595
|
+
const axis = a[u] !== b[u] ? u : v;
|
|
596
|
+
const other = axis === u ? v : u;
|
|
597
|
+
if (Math.abs(a[other] - center[other]) > size[other] / 2) continue;
|
|
598
|
+
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);
|
|
599
|
+
if (hi - lo < 1e-6) continue;
|
|
600
|
+
const p = [a[0], a[1], a[2]], q = [b[0], b[1], b[2]];
|
|
601
|
+
p[axis] = lo;
|
|
602
|
+
q[axis] = hi;
|
|
603
|
+
out.push([p, q]);
|
|
604
|
+
}
|
|
605
|
+
return out;
|
|
606
|
+
};
|
|
607
|
+
var bendBar = ([a, b], thin, bow, i) => {
|
|
608
|
+
const length = Math.hypot(b[0] - a[0], b[1] - a[1], b[2] - a[2]);
|
|
609
|
+
const m = [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2, (a[2] + b[2]) / 2];
|
|
610
|
+
m[thin] += (i % 2 === 0 ? 1 : -1) * bow * length;
|
|
611
|
+
return [[a, m], [m, b]];
|
|
612
|
+
};
|
|
589
613
|
var clipBarsToDisc = (bars, u, v, center, radius) => {
|
|
590
614
|
const out = [];
|
|
591
615
|
for (const [a, b] of bars) {
|
|
@@ -1563,14 +1587,21 @@ var materials_default = [
|
|
|
1563
1587
|
smoke: "grey"
|
|
1564
1588
|
},
|
|
1565
1589
|
visual: {
|
|
1566
|
-
charColor: 1709072
|
|
1590
|
+
charColor: 1709072,
|
|
1591
|
+
innerColor: 14730131
|
|
1567
1592
|
},
|
|
1568
1593
|
tags: [
|
|
1569
1594
|
"wood",
|
|
1570
1595
|
"organic",
|
|
1571
1596
|
"flammable"
|
|
1572
1597
|
],
|
|
1573
|
-
compressive: 1e5
|
|
1598
|
+
compressive: 1e5,
|
|
1599
|
+
hits: {
|
|
1600
|
+
blunt: {
|
|
1601
|
+
tear: "splinters",
|
|
1602
|
+
tearJoules: 60
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1574
1605
|
},
|
|
1575
1606
|
{
|
|
1576
1607
|
id: "wood_hard",
|
|
@@ -1600,14 +1631,21 @@ var materials_default = [
|
|
|
1600
1631
|
smoke: "grey"
|
|
1601
1632
|
},
|
|
1602
1633
|
visual: {
|
|
1603
|
-
charColor: 1709072
|
|
1634
|
+
charColor: 1709072,
|
|
1635
|
+
innerColor: 13213802
|
|
1604
1636
|
},
|
|
1605
1637
|
tags: [
|
|
1606
1638
|
"wood",
|
|
1607
1639
|
"organic",
|
|
1608
1640
|
"flammable"
|
|
1609
1641
|
],
|
|
1610
|
-
compressive: 15e4
|
|
1642
|
+
compressive: 15e4,
|
|
1643
|
+
hits: {
|
|
1644
|
+
blunt: {
|
|
1645
|
+
tear: "splinters",
|
|
1646
|
+
tearJoules: 150
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1611
1649
|
},
|
|
1612
1650
|
{
|
|
1613
1651
|
id: "plywood",
|
|
@@ -1637,14 +1675,24 @@ var materials_default = [
|
|
|
1637
1675
|
smoke: "grey"
|
|
1638
1676
|
},
|
|
1639
1677
|
visual: {
|
|
1640
|
-
charColor: 1709072
|
|
1678
|
+
charColor: 1709072,
|
|
1679
|
+
innerColor: 14862234
|
|
1641
1680
|
},
|
|
1642
1681
|
tags: [
|
|
1643
1682
|
"wood",
|
|
1644
1683
|
"organic",
|
|
1645
1684
|
"flammable"
|
|
1646
1685
|
],
|
|
1647
|
-
compressive: 6e4
|
|
1686
|
+
compressive: 6e4,
|
|
1687
|
+
hits: {
|
|
1688
|
+
ballistic: {
|
|
1689
|
+
tear: "splinters"
|
|
1690
|
+
},
|
|
1691
|
+
blunt: {
|
|
1692
|
+
tear: "splinters",
|
|
1693
|
+
tearJoules: 80
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1648
1696
|
},
|
|
1649
1697
|
{
|
|
1650
1698
|
id: "mdf",
|
|
@@ -1671,14 +1719,25 @@ var materials_default = [
|
|
|
1671
1719
|
smoke: "grey"
|
|
1672
1720
|
},
|
|
1673
1721
|
visual: {
|
|
1674
|
-
charColor: 1709072
|
|
1722
|
+
charColor: 1709072,
|
|
1723
|
+
innerColor: 12096362
|
|
1675
1724
|
},
|
|
1676
1725
|
tags: [
|
|
1677
1726
|
"wood",
|
|
1678
1727
|
"organic",
|
|
1679
1728
|
"flammable"
|
|
1680
1729
|
],
|
|
1681
|
-
compressive: 3e4
|
|
1730
|
+
compressive: 3e4,
|
|
1731
|
+
hits: {
|
|
1732
|
+
ballistic: {
|
|
1733
|
+
tear: "lumps",
|
|
1734
|
+
tearJoules: 120
|
|
1735
|
+
},
|
|
1736
|
+
blunt: {
|
|
1737
|
+
tear: "lumps",
|
|
1738
|
+
tearJoules: 20
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1682
1741
|
},
|
|
1683
1742
|
{
|
|
1684
1743
|
id: "glass",
|
|
@@ -1792,6 +1851,17 @@ var materials_default = [
|
|
|
1792
1851
|
damageScale: {
|
|
1793
1852
|
explosive: 1.2
|
|
1794
1853
|
},
|
|
1854
|
+
hits: {
|
|
1855
|
+
ballistic: {
|
|
1856
|
+
unitJoules: 500,
|
|
1857
|
+
floor: 900,
|
|
1858
|
+
crater: true
|
|
1859
|
+
},
|
|
1860
|
+
blunt: {
|
|
1861
|
+
unitJoules: 1500,
|
|
1862
|
+
floor: 60
|
|
1863
|
+
}
|
|
1864
|
+
},
|
|
1795
1865
|
hitDebris: "chips",
|
|
1796
1866
|
breakDebris: "dust",
|
|
1797
1867
|
thermal: {
|
|
@@ -2160,7 +2230,8 @@ var materials_default = [
|
|
|
2160
2230
|
smoke: "white"
|
|
2161
2231
|
},
|
|
2162
2232
|
visual: {
|
|
2163
|
-
charColor: 1709072
|
|
2233
|
+
charColor: 1709072,
|
|
2234
|
+
innerColor: 14207126
|
|
2164
2235
|
},
|
|
2165
2236
|
tags: [
|
|
2166
2237
|
"plant",
|
|
@@ -2168,7 +2239,12 @@ var materials_default = [
|
|
|
2168
2239
|
"organic",
|
|
2169
2240
|
"flammable"
|
|
2170
2241
|
],
|
|
2171
|
-
compressive: 6e4
|
|
2242
|
+
compressive: 6e4,
|
|
2243
|
+
hits: {
|
|
2244
|
+
blunt: {
|
|
2245
|
+
tear: "none"
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2172
2248
|
},
|
|
2173
2249
|
{
|
|
2174
2250
|
id: "cactus",
|
|
@@ -2283,6 +2359,130 @@ var materials_default = [
|
|
|
2283
2359
|
"masonry"
|
|
2284
2360
|
],
|
|
2285
2361
|
compressive: 2e4
|
|
2362
|
+
},
|
|
2363
|
+
{
|
|
2364
|
+
id: "asphalt",
|
|
2365
|
+
density: 2350,
|
|
2366
|
+
hpPerKg: 10,
|
|
2367
|
+
penetration: 14,
|
|
2368
|
+
penetrationClass: "concrete",
|
|
2369
|
+
penetrationScale: 0.8,
|
|
2370
|
+
ductility: 0.1,
|
|
2371
|
+
fracture: "crumble",
|
|
2372
|
+
fragmentSize: [
|
|
2373
|
+
0.03,
|
|
2374
|
+
0.15
|
|
2375
|
+
],
|
|
2376
|
+
hitDebris: "chips",
|
|
2377
|
+
breakDebris: "dust",
|
|
2378
|
+
thermal: {
|
|
2379
|
+
conductivity: 0.1,
|
|
2380
|
+
heatCapacity: 2,
|
|
2381
|
+
ignitionC: 400,
|
|
2382
|
+
burnRate: 2e-3,
|
|
2383
|
+
smoke: "black"
|
|
2384
|
+
},
|
|
2385
|
+
tags: [
|
|
2386
|
+
"masonry",
|
|
2387
|
+
"brittle"
|
|
2388
|
+
],
|
|
2389
|
+
compressive: 3e4
|
|
2390
|
+
},
|
|
2391
|
+
{
|
|
2392
|
+
id: "sand",
|
|
2393
|
+
density: 1600,
|
|
2394
|
+
hpPerKg: 2,
|
|
2395
|
+
penetration: 6,
|
|
2396
|
+
ductility: 0.4,
|
|
2397
|
+
fracture: "crumble",
|
|
2398
|
+
fragmentSize: [
|
|
2399
|
+
0.01,
|
|
2400
|
+
0.04
|
|
2401
|
+
],
|
|
2402
|
+
hitDebris: "dust",
|
|
2403
|
+
breakDebris: "dust",
|
|
2404
|
+
thermal: {
|
|
2405
|
+
conductivity: 0.1,
|
|
2406
|
+
heatCapacity: 2
|
|
2407
|
+
},
|
|
2408
|
+
tags: [
|
|
2409
|
+
"soil"
|
|
2410
|
+
],
|
|
2411
|
+
compressive: 1e4
|
|
2412
|
+
},
|
|
2413
|
+
{
|
|
2414
|
+
id: "gravel",
|
|
2415
|
+
density: 1800,
|
|
2416
|
+
hpPerKg: 3,
|
|
2417
|
+
penetration: 8,
|
|
2418
|
+
ductility: 0.3,
|
|
2419
|
+
fracture: "crumble",
|
|
2420
|
+
fragmentSize: [
|
|
2421
|
+
0.02,
|
|
2422
|
+
0.06
|
|
2423
|
+
],
|
|
2424
|
+
hitDebris: "chips",
|
|
2425
|
+
breakDebris: "dust",
|
|
2426
|
+
thermal: {
|
|
2427
|
+
conductivity: 0.1,
|
|
2428
|
+
heatCapacity: 2
|
|
2429
|
+
},
|
|
2430
|
+
tags: [
|
|
2431
|
+
"soil",
|
|
2432
|
+
"masonry"
|
|
2433
|
+
],
|
|
2434
|
+
compressive: 2e4
|
|
2435
|
+
},
|
|
2436
|
+
{
|
|
2437
|
+
id: "turf",
|
|
2438
|
+
density: 900,
|
|
2439
|
+
hpPerKg: 3,
|
|
2440
|
+
penetration: 4,
|
|
2441
|
+
ductility: 0.5,
|
|
2442
|
+
fracture: "crumble",
|
|
2443
|
+
fragmentSize: [
|
|
2444
|
+
0.03,
|
|
2445
|
+
0.1
|
|
2446
|
+
],
|
|
2447
|
+
hitDebris: "dust",
|
|
2448
|
+
breakDebris: "dust",
|
|
2449
|
+
thermal: {
|
|
2450
|
+
conductivity: 0.1,
|
|
2451
|
+
heatCapacity: 1.5,
|
|
2452
|
+
ignitionC: 250,
|
|
2453
|
+
burnRate: 0.03,
|
|
2454
|
+
smoke: "white"
|
|
2455
|
+
},
|
|
2456
|
+
tags: [
|
|
2457
|
+
"soil",
|
|
2458
|
+
"organic"
|
|
2459
|
+
],
|
|
2460
|
+
compressive: 15e3
|
|
2461
|
+
},
|
|
2462
|
+
{
|
|
2463
|
+
id: "mortar",
|
|
2464
|
+
density: 1900,
|
|
2465
|
+
hpPerKg: 10,
|
|
2466
|
+
penetration: 12,
|
|
2467
|
+
penetrationClass: "concrete",
|
|
2468
|
+
penetrationScale: 1.2,
|
|
2469
|
+
ductility: 0.03,
|
|
2470
|
+
fracture: "crumble",
|
|
2471
|
+
fragmentSize: [
|
|
2472
|
+
0.02,
|
|
2473
|
+
0.08
|
|
2474
|
+
],
|
|
2475
|
+
hitDebris: "chips",
|
|
2476
|
+
breakDebris: "dust",
|
|
2477
|
+
thermal: {
|
|
2478
|
+
conductivity: 0.15,
|
|
2479
|
+
heatCapacity: 2.5
|
|
2480
|
+
},
|
|
2481
|
+
tags: [
|
|
2482
|
+
"masonry",
|
|
2483
|
+
"brittle"
|
|
2484
|
+
],
|
|
2485
|
+
compressive: 1e5
|
|
2286
2486
|
}
|
|
2287
2487
|
];
|
|
2288
2488
|
|
|
@@ -2468,6 +2668,32 @@ var fragility = (ductility, thickness) => {
|
|
|
2468
2668
|
// src/world.ts
|
|
2469
2669
|
import { ignitionChance, resolveIgnitionRules, sparkTargetFactor } from "@adatechnology/game-contracts";
|
|
2470
2670
|
var THIN_SHEET_DISHING = 5e-3;
|
|
2671
|
+
var SWEEP_BACK = 0.01;
|
|
2672
|
+
function sweepOrigins(box, d, spacing) {
|
|
2673
|
+
const origins = [];
|
|
2674
|
+
const line = (from, to) => {
|
|
2675
|
+
const n = Math.max(1, Math.ceil((to - from) / spacing));
|
|
2676
|
+
return Array.from({ length: n + 1 }, (_, i) => from + (to - from) * i / n);
|
|
2677
|
+
};
|
|
2678
|
+
for (const axis of [0, 1, 2]) {
|
|
2679
|
+
if (Math.abs(d[axis]) < 1e-3) continue;
|
|
2680
|
+
const lead = d[axis] > 0 ? box.max[axis] - SWEEP_BACK : box.min[axis] + SWEEP_BACK;
|
|
2681
|
+
const u = (axis + 1) % 3;
|
|
2682
|
+
const v = (axis + 2) % 3;
|
|
2683
|
+
for (const a of line(box.min[u], box.max[u])) {
|
|
2684
|
+
for (const b of line(box.min[v], box.max[v])) {
|
|
2685
|
+
const point = [0, 0, 0];
|
|
2686
|
+
point[axis] = lead;
|
|
2687
|
+
point[u] = a;
|
|
2688
|
+
point[v] = b;
|
|
2689
|
+
origins.push(point);
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
return origins;
|
|
2694
|
+
}
|
|
2695
|
+
var THIN_SHEET_CRUSH = 5e-4;
|
|
2696
|
+
var THIN_SHEET_CONTACT = 0.025;
|
|
2471
2697
|
var DMG_PER_J = 0.02;
|
|
2472
2698
|
var BLAST_COUPLING = 0.1;
|
|
2473
2699
|
var SHELTER_DEFAULTS = { k: 2800, brokenTransmission: 0.5, minPath: 5e-3 };
|
|
@@ -2484,11 +2710,14 @@ var RESIDUAL_CAPACITY = 0.15;
|
|
|
2484
2710
|
var SEVER_BAND = 0.1;
|
|
2485
2711
|
var SEVER_ASPECT = 3;
|
|
2486
2712
|
var SEVER_BALLISTIC = 2;
|
|
2713
|
+
var TEAR_JOULES = 150;
|
|
2714
|
+
var tears = 0;
|
|
2487
2715
|
var SUPPORT_TOLERANCE = 0.05;
|
|
2488
2716
|
var UNIT_FRAGMENT_FACTOR = 4;
|
|
2489
2717
|
var BREACH_GRID_CAP = 4096;
|
|
2490
2718
|
var CHIP_ENERGY = 200;
|
|
2491
2719
|
var CHIP_SPLASH = 0.5;
|
|
2720
|
+
var BAR_BOW = 0.08;
|
|
2492
2721
|
var CUT_SLICE = 1;
|
|
2493
2722
|
var cutFactorOf = (shape) => clamp2(Math.max(...shapeDims(shape)) / CUT_SLICE, 1, 50);
|
|
2494
2723
|
var clamp2 = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
@@ -2498,8 +2727,24 @@ var penetrationOf = (mat, table) => {
|
|
|
2498
2727
|
return v === void 0 ? void 0 : v * (mat.penetrationScale ?? 1);
|
|
2499
2728
|
};
|
|
2500
2729
|
var capacityScale = (p) => {
|
|
2501
|
-
|
|
2502
|
-
|
|
2730
|
+
const residual = p.spec.reinforcement?.residualCapacity ?? RESIDUAL_CAPACITY;
|
|
2731
|
+
if (p.st.stripped) return residual;
|
|
2732
|
+
const kept = Math.min(p.rt.removed ? 1 - p.rt.removed / p.mass0 : 1, 1 - sectionLoss(p));
|
|
2733
|
+
return Math.max(p.spec.reinforcement ? residual : 0.05, kept);
|
|
2734
|
+
};
|
|
2735
|
+
var sectionLoss = (p) => {
|
|
2736
|
+
const cells = p.st.chipped;
|
|
2737
|
+
if (!cells?.length) return 0;
|
|
2738
|
+
const [u, v] = planeAxes(p.thin);
|
|
2739
|
+
const along = p.dims[u] >= p.dims[v] ? u : v;
|
|
2740
|
+
const across = along === u ? v : u;
|
|
2741
|
+
let worst = 0;
|
|
2742
|
+
for (const c of cells) {
|
|
2743
|
+
let gone = 0;
|
|
2744
|
+
for (const d of cells) if (Math.abs(d.center[along] - c.center[along]) < d.size[along] / 2 + 1e-6) gone += d.size[across];
|
|
2745
|
+
worst = Math.max(worst, gone / Math.max(p.dims[across], 1e-6));
|
|
2746
|
+
}
|
|
2747
|
+
return Math.min(1, worst);
|
|
2503
2748
|
};
|
|
2504
2749
|
var isLost = (p) => p.st.status === "broken" || p.st.status === "melted" || p.st.status === "ashes";
|
|
2505
2750
|
var clone = (v) => structuredClone(v);
|
|
@@ -3680,6 +3925,12 @@ var createDestruction = (options = {}) => {
|
|
|
3680
3925
|
debris(o, p, layer.mat.hitDebris, wp, normal, 5, 2, src);
|
|
3681
3926
|
setStage(o, p, 1, {}, src);
|
|
3682
3927
|
};
|
|
3928
|
+
const crater = (o, p, wp, normal, radius, src) => {
|
|
3929
|
+
markDamaged(p);
|
|
3930
|
+
emit({ ...base(o, src), type: "spall", part: p.spec.id, layer: -1, point: wp, area: Math.PI * radius * radius });
|
|
3931
|
+
debris(o, p, p.mat.breakDebris, wp, normal, 6, 1.5, src);
|
|
3932
|
+
setStage(o, p, 1, {}, src);
|
|
3933
|
+
};
|
|
3683
3934
|
const insideOpening = (o, p, objectPoint) => {
|
|
3684
3935
|
const op = p.st.opening;
|
|
3685
3936
|
if (!op) return false;
|
|
@@ -3756,9 +4007,13 @@ var createDestruction = (options = {}) => {
|
|
|
3756
4007
|
const lp = toLocal(p.pos, p.q, objectPoint);
|
|
3757
4008
|
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);
|
|
3758
4009
|
};
|
|
3759
|
-
const chipUnits = (o, p, wp,
|
|
4010
|
+
const chipUnits = (o, p, kind, wp, hitEnergy, dir, radius, src) => {
|
|
3760
4011
|
var _a, _b;
|
|
3761
|
-
if (!p.spec.units ||
|
|
4012
|
+
if (!p.spec.units || !p.staged || chipEnergy <= 0 || p.st.stripped || isLost(p) || (p.st.stage ?? 0) >= 2) return;
|
|
4013
|
+
const rule = p.mat.hits?.[kind];
|
|
4014
|
+
const perKg = rule?.unitJoules ?? chipEnergy;
|
|
4015
|
+
const energy = hitEnergy - (rule?.floor ?? 0);
|
|
4016
|
+
if (perKg <= 0 || energy <= 0) return;
|
|
3762
4017
|
const [u, v] = planeAxes(p.thin);
|
|
3763
4018
|
const lp = toLocal(p.pos, p.q, objLocal(o, wp));
|
|
3764
4019
|
const grid = gridOf(p);
|
|
@@ -3772,7 +4027,7 @@ var createDestruction = (options = {}) => {
|
|
|
3772
4027
|
const share = gap === 0 ? 1 : CHIP_SPLASH * (1 - gap / radius);
|
|
3773
4028
|
const total = (worn[i] ?? 0) + energy * share;
|
|
3774
4029
|
const mass = p.mat.density * cell.size[0] * cell.size[1] * cell.size[2];
|
|
3775
|
-
if (total <
|
|
4030
|
+
if (total < perKg * mass) {
|
|
3776
4031
|
worn[i] = total;
|
|
3777
4032
|
continue;
|
|
3778
4033
|
}
|
|
@@ -3797,7 +4052,32 @@ var createDestruction = (options = {}) => {
|
|
|
3797
4052
|
emit({ ...base(o, src), type: "chip", part: p.spec.id, center, size: [cell.size[0], cell.size[1], cell.size[2]], rotation: q, fragments });
|
|
3798
4053
|
debris(o, p, p.mat.breakDebris, center, vec.scale(dir ?? [0, 1, 0], -1), 6, 2, src);
|
|
3799
4054
|
}
|
|
4055
|
+
if (p.spec.reinforcement) exposeRebar(o, p, src);
|
|
3800
4056
|
for (const cell of out) addBreach(o, p, toWorld(c, q, cell.center), cell.size[u] * cell.size[v], energy, dir, src);
|
|
4057
|
+
if (p.structural && !isLost(p)) {
|
|
4058
|
+
solveStructure(o);
|
|
4059
|
+
collapseCheck(o, src);
|
|
4060
|
+
}
|
|
4061
|
+
};
|
|
4062
|
+
const exposeRebar = (o, p, src) => {
|
|
4063
|
+
const r = p.spec.reinforcement;
|
|
4064
|
+
if (!r) return;
|
|
4065
|
+
const [u, v] = planeAxes(p.thin);
|
|
4066
|
+
const all = rebarBars(p.dims, p.thin, r.spacing);
|
|
4067
|
+
let bars;
|
|
4068
|
+
if (p.st.stripped) bars = all;
|
|
4069
|
+
else {
|
|
4070
|
+
bars = [];
|
|
4071
|
+
for (const cell of p.st.chipped ?? []) bars.push(...clipBarsToBox(all, u, v, cell.center, cell.size));
|
|
4072
|
+
const op = p.st.opening;
|
|
4073
|
+
if (op) {
|
|
4074
|
+
const lc = toLocal(p.pos, p.q, op.center);
|
|
4075
|
+
bars.push(...clipBarsToDisc(all, u, v, [lc[u], lc[v]], op.radius + 0.05));
|
|
4076
|
+
}
|
|
4077
|
+
}
|
|
4078
|
+
const bow = clamp2(((p.st.stress ?? 0) - 1) / 0.5, 0, 1);
|
|
4079
|
+
if (bow > 0) bars = bars.flatMap((bar2, i) => bendBar(bar2, p.thin, bow * BAR_BOW, i));
|
|
4080
|
+
emit({ ...base(o, src), type: "expose", part: p.spec.id, bars: worldBars(o, p, bars) });
|
|
3801
4081
|
};
|
|
3802
4082
|
const openBreach = (o, p, center, total, energy, dir, src) => {
|
|
3803
4083
|
const [u, v] = planeAxes(p.thin);
|
|
@@ -3825,10 +4105,7 @@ var createDestruction = (options = {}) => {
|
|
|
3825
4105
|
setStage(o, p, 2, { opening: { center: worldCenter, radius }, fragments }, src);
|
|
3826
4106
|
debris(o, p, p.mat.breakDebris, worldCenter, vec.scale(dir ?? [0, 1, 0], -1), 14, 3, src);
|
|
3827
4107
|
const reinforcement = p.spec.reinforcement;
|
|
3828
|
-
if (reinforcement)
|
|
3829
|
-
const bars = worldBars(o, p, clipBarsToDisc(rebarBars(p.dims, p.thin, reinforcement.spacing), u, v, [lc[u], lc[v]], radius + 0.05));
|
|
3830
|
-
if (bars.length > 0) emit({ ...base(o, src), type: "expose", part: p.spec.id, bars });
|
|
3831
|
-
}
|
|
4108
|
+
if (reinforcement) exposeRebar(o, p, src);
|
|
3832
4109
|
if (p.structural) solveStructure(o);
|
|
3833
4110
|
};
|
|
3834
4111
|
const stripPart = (o, p, reinforcement, src) => {
|
|
@@ -3840,8 +4117,8 @@ var createDestruction = (options = {}) => {
|
|
|
3840
4117
|
p.st.burning = false;
|
|
3841
4118
|
p.rt.jet = false;
|
|
3842
4119
|
markDamaged(p);
|
|
3843
|
-
emit({ ...base(o, src), type: "expose", part: p.spec.id, bars: worldBars(o, p, bars) });
|
|
3844
4120
|
solveStructure(o);
|
|
4121
|
+
exposeRebar(o, p, src);
|
|
3845
4122
|
collapseCheck(o, src);
|
|
3846
4123
|
};
|
|
3847
4124
|
const unitFragmentPlan = (p, cap) => {
|
|
@@ -3895,7 +4172,7 @@ var createDestruction = (options = {}) => {
|
|
|
3895
4172
|
const grain = p.spec.grain ? AXIS[p.spec.grain] : void 0;
|
|
3896
4173
|
const hitLocal = toLocal(c, q, hitW);
|
|
3897
4174
|
const unitPlan = p.spec.units ? unitFragmentPlan(p, maxFragments * UNIT_FRAGMENT_FACTOR) : [];
|
|
3898
|
-
const axis = style === "splinter" ? severAxis(p) : -1;
|
|
4175
|
+
const axis = style === "splinter" || style === "snap" ? severAxis(p) : -1;
|
|
3899
4176
|
const cutAt = axis < 0 ? void 0 : p.rt.cutAt ?? (origin ? hitLocal[axis] : void 0);
|
|
3900
4177
|
const plan = unitPlan.length > 0 ? unitPlan : planFragments({
|
|
3901
4178
|
style,
|
|
@@ -3967,7 +4244,7 @@ var createDestruction = (options = {}) => {
|
|
|
3967
4244
|
};
|
|
3968
4245
|
const CHAIN_TRIGGER_OF = { ballistic: "shot", explosive: "explosion" };
|
|
3969
4246
|
const severAxis = (p) => {
|
|
3970
|
-
if (p.mat.fracture !== "splinter" || p.spec.shape.kind !== "box") return -1;
|
|
4247
|
+
if (p.mat.fracture !== "splinter" && p.mat.fracture !== "snap" || p.spec.shape.kind !== "box") return -1;
|
|
3971
4248
|
const d = p.dims;
|
|
3972
4249
|
const order = [0, 1, 2].sort((a, b) => d[b] - d[a]);
|
|
3973
4250
|
return d[order[0]] >= SEVER_ASPECT * d[order[1]] && d[order[0]] >= 3 * SEVER_BAND ? order[0] : -1;
|
|
@@ -4028,6 +4305,49 @@ var createDestruction = (options = {}) => {
|
|
|
4028
4305
|
}
|
|
4029
4306
|
}
|
|
4030
4307
|
};
|
|
4308
|
+
const tearOf = (mat, kind) => {
|
|
4309
|
+
const h = mat.hits?.[kind];
|
|
4310
|
+
const tear = h?.tear ?? (kind === "ballistic" && mat.penetrationClass === "wood" ? "splinters" : "none");
|
|
4311
|
+
return { tear, joules: h?.tearJoules ?? TEAR_JOULES };
|
|
4312
|
+
};
|
|
4313
|
+
const tearAt = (o, p, kind, entry, exit, d, E, scale, src) => {
|
|
4314
|
+
const { tear, joules } = tearOf(p.mat, kind);
|
|
4315
|
+
if (tear === "none") return;
|
|
4316
|
+
const shot = kind === "ballistic";
|
|
4317
|
+
const n = shot ? exit ? clamp2(Math.round(E / joules), 1, 7) : 1 : clamp2(Math.floor(E / joules), 0, 5);
|
|
4318
|
+
if (n === 0) return;
|
|
4319
|
+
const fibre = tear === "lumps";
|
|
4320
|
+
const { q } = partXf(o, p);
|
|
4321
|
+
const along = (p.spec.grain ? AXIS[p.spec.grain] : void 0) ?? [0, 1, 2].sort((a, b) => p.dims[b] - p.dims[a])[0];
|
|
4322
|
+
const fragments = [];
|
|
4323
|
+
for (let i = 0; i < n; i++) {
|
|
4324
|
+
const back = !exit || i % 3 === 2;
|
|
4325
|
+
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());
|
|
4326
|
+
const size = [w, w, w];
|
|
4327
|
+
size[along] = len;
|
|
4328
|
+
size[(along + 1) % 3] = w;
|
|
4329
|
+
size[(along + 2) % 3] = t;
|
|
4330
|
+
const spread3 = [(rnd() - 0.5) * 0.8, (rnd() - 0.2) * 0.8, (rnd() - 0.5) * 0.8];
|
|
4331
|
+
const speed = shot ? back ? 1.5 + 2 * rnd() : 2 + 4 * rnd() : 0.8 + 1.2 * rnd();
|
|
4332
|
+
const v = vec.scale(vec.normalize(vec.add(back ? vec.scale(d, -1) : d, spread3)), speed);
|
|
4333
|
+
const mass = size[0] * size[1] * size[2] * p.mat.density;
|
|
4334
|
+
fragments.push({
|
|
4335
|
+
id: `${p.spec.id}#t${tears++}-${i}`,
|
|
4336
|
+
part: p.spec.id,
|
|
4337
|
+
material: p.spec.material,
|
|
4338
|
+
shape: fibre ? "chunk" : "splinter",
|
|
4339
|
+
center: back || !exit ? vec.madd(entry, d, -0.01) : vec.madd(exit, d, 0.01),
|
|
4340
|
+
size,
|
|
4341
|
+
rotation: q,
|
|
4342
|
+
velocity: v,
|
|
4343
|
+
inner: true,
|
|
4344
|
+
angularVelocity: [(rnd() - 0.5) * 30, (rnd() - 0.5) * 30, (rnd() - 0.5) * 30],
|
|
4345
|
+
mass
|
|
4346
|
+
});
|
|
4347
|
+
}
|
|
4348
|
+
const patch = [scale * 4, scale * 4, scale * 4];
|
|
4349
|
+
emit({ ...base(o, src), type: "chip", part: p.spec.id, center: exit ?? entry, size: patch, rotation: q, fragments });
|
|
4350
|
+
};
|
|
4031
4351
|
const ballistic = (o, p, entry, n, d, E, caliber, pathLen, src, round = {}) => {
|
|
4032
4352
|
const mat = p.mat;
|
|
4033
4353
|
const piercing = round.piercing ?? 1;
|
|
@@ -4056,6 +4376,7 @@ var createDestruction = (options = {}) => {
|
|
|
4056
4376
|
return { penetrated: false, exitEnergy: 0, damage: dmg2 };
|
|
4057
4377
|
}
|
|
4058
4378
|
if (p.layers.length > 0) spallOuter(o, p, entry, face, n, Math.max(0.03, caliber / 1e3 * 4), src);
|
|
4379
|
+
else if (mat.hits?.ballistic?.crater) crater(o, p, entry, n, clamp2(0.02 + Math.sqrt(E) * 15e-4, 0.02, 0.15), src);
|
|
4059
4380
|
const brittle = mat.ductility < 0.3;
|
|
4060
4381
|
const ratio = Ep / Math.max(thr, 1e-6);
|
|
4061
4382
|
const crackLen = clamp2(0.03 + ratio * 0.2, 0.03, 0.8);
|
|
@@ -4089,8 +4410,10 @@ var createDestruction = (options = {}) => {
|
|
|
4089
4410
|
} else addHole(o, p, entry, n, caliber / 1e3, false, void 0, src);
|
|
4090
4411
|
debris(o, p, mat.hitDebris, entry, n, clamp2(Math.round(E / 60) + 2, 2, 14), clamp2(Math.sqrt(E) / 6, 1, 8), src);
|
|
4091
4412
|
wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true);
|
|
4092
|
-
const
|
|
4093
|
-
|
|
4413
|
+
const absorbed = penetrated && mat.ductility >= 0.5 && mat.fracture !== "tear" ? Math.min(E, thr) : E;
|
|
4414
|
+
const dmg = applyDamage(o, p, absorbed * DMG_PER_J * scale, "ballistic", entry, d, absorbed * 0.1, 12, src);
|
|
4415
|
+
if (!isLost(p)) tearAt(o, p, "ballistic", entry, penetrated ? exitPoint : void 0, d, E, caliber / 1e3, src);
|
|
4416
|
+
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);
|
|
4094
4417
|
return { penetrated, exitEnergy: penetrated ? penMm !== void 0 ? E * (1 - thr / Ep) : E - thr / (hard ? piercing : 1) : 0, exitPoint, damage: dmg };
|
|
4095
4418
|
};
|
|
4096
4419
|
const melee = (o, p, kind, E, point, n, d, contactR, src) => {
|
|
@@ -4098,14 +4421,20 @@ var createDestruction = (options = {}) => {
|
|
|
4098
4421
|
const thrMm = mat.penetration * p.thickness * 1e3;
|
|
4099
4422
|
const fragile = kind === "blunt" ? fragility(mat.ductility, p.thickness) : 1;
|
|
4100
4423
|
const Ec = E * fragile;
|
|
4424
|
+
const sheet = mat.ductility >= 0.5 && p.thickness <= THIN_SHEET_CRUSH;
|
|
4425
|
+
const fold = sheet ? Math.min(4, Math.sqrt(1e-3 / p.thickness)) : 1;
|
|
4426
|
+
const patch = sheet ? Math.min(contactR, THIN_SHEET_CONTACT) : contactR;
|
|
4101
4427
|
if (E > 0) {
|
|
4102
4428
|
if (kind === "blunt") {
|
|
4103
|
-
if (mat.ductility >= 0.5) addDent(o, p, point, n, Math.max(
|
|
4429
|
+
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);
|
|
4104
4430
|
else if (mat.ductility < 0.3) {
|
|
4105
4431
|
if (Ec >= 5) addCrack(o, p, point, n, clamp2(0.04 + Math.min(Ec, E * 6) * 8e-4, 0.04, 0.6), src);
|
|
4106
4432
|
} 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);
|
|
4107
4433
|
} else if (mat.fracture === "tear") {
|
|
4108
4434
|
addHole(o, p, point, n, clamp2(0.02 + E * 5e-4, 0.02, 0.3), true, void 0, src);
|
|
4435
|
+
} else if (mat.ductility >= 0.3 && E >= thrMm) {
|
|
4436
|
+
addHole(o, p, point, n, clamp2(contactR, 0.01, 0.08), true, void 0, src);
|
|
4437
|
+
if (sheet) addDent(o, p, point, n, Math.max(patch * 0.6, 0.01), clamp2(E * 4e-5 * fold, 5e-4, 0.02), src);
|
|
4109
4438
|
} else if (mat.ductility >= 0.5) {
|
|
4110
4439
|
addDent(o, p, point, n, Math.max(contactR, 0.01), clamp2(E * 4e-5, 5e-4, 0.02), src);
|
|
4111
4440
|
}
|
|
@@ -4118,7 +4447,8 @@ var createDestruction = (options = {}) => {
|
|
|
4118
4447
|
const scale = mat.damageScale?.[kind] ?? 1;
|
|
4119
4448
|
const done = applyDamage(o, p, Ec * DMG_PER_J * scale * (kind === "slash" ? p.cutFactor : 1), kind, point, d, E * 0.1, 12, src);
|
|
4120
4449
|
addBreach(o, p, point, breachAreaOf(p, done, false), E, d, src);
|
|
4121
|
-
if (kind === "blunt") chipUnits(o, p, point, E, d, Math.max(contactR, 0.03), src);
|
|
4450
|
+
if (kind === "blunt") chipUnits(o, p, "blunt", point, E, d, Math.max(contactR, 0.03), src);
|
|
4451
|
+
if (E > 0 && !isLost(p)) tearAt(o, p, kind, point, void 0, d, E, clamp2(contactR * 0.2, 6e-3, 0.02), src);
|
|
4122
4452
|
return done;
|
|
4123
4453
|
};
|
|
4124
4454
|
const blastPart = (o, p, point, energy, radius, src, res, reachAt) => {
|
|
@@ -4697,6 +5027,7 @@ var createDestruction = (options = {}) => {
|
|
|
4697
5027
|
if (p.rt.stressT <= 0) {
|
|
4698
5028
|
p.rt.stressT = 0.5;
|
|
4699
5029
|
emit({ ...base(o), type: "stress", part: p.spec.id, stress, load: p.st.load ?? 0 });
|
|
5030
|
+
if (p.spec.reinforcement && (p.st.stripped || p.st.chipped?.length || p.st.opening)) exposeRebar(o, p);
|
|
4700
5031
|
}
|
|
4701
5032
|
applyDamage(o, p, overloadRate(stress) * p.st.maxHp * dt, "blunt", null, null, 5, 3);
|
|
4702
5033
|
} else if (stress > 0.8 && p.mat.ductility < 0.3 && p.rt.stressT <= 0 && p.st.cracks.length < 6) {
|
|
@@ -4913,6 +5244,18 @@ var createDestruction = (options = {}) => {
|
|
|
4913
5244
|
const r = raycastInternal(origin, direction, maxDistance);
|
|
4914
5245
|
return r ? { object: r.object, part: r.part, point: r.point, normal: r.normal, distance: r.distance } : null;
|
|
4915
5246
|
},
|
|
5247
|
+
sweepBox(box, direction, distance, options2 = {}) {
|
|
5248
|
+
const d = vec.normalize(direction);
|
|
5249
|
+
const ignored = options2.ignore === void 0 ? void 0 : objects.get(options2.ignore);
|
|
5250
|
+
const ignore = ignored ? new Set(ignored.parts.map((p) => `${ignored.id}\0${p.spec.id}`)) : void 0;
|
|
5251
|
+
let best = null;
|
|
5252
|
+
for (const origin of sweepOrigins(box, d, options2.spacing ?? 0.2)) {
|
|
5253
|
+
const r = raycastInternal(origin, d, distance + SWEEP_BACK, ignore);
|
|
5254
|
+
if (!r || best && r.distance >= best.distance) continue;
|
|
5255
|
+
best = { object: r.object, part: r.part, point: r.point, normal: r.normal, distance: Math.max(0, r.distance - SWEEP_BACK) };
|
|
5256
|
+
}
|
|
5257
|
+
return best;
|
|
5258
|
+
},
|
|
4916
5259
|
shoot(origin, direction, energy, caliber, source, round) {
|
|
4917
5260
|
const extras = typeof round === "number" ? { piercing: round } : round ?? {};
|
|
4918
5261
|
return withCollapses(run(() => {
|
|
@@ -5557,7 +5900,12 @@ var locales = {
|
|
|
5557
5900
|
cactus: "cactus",
|
|
5558
5901
|
flesh_fruit: "fruit",
|
|
5559
5902
|
wax: "wax",
|
|
5560
|
-
soil: "soil"
|
|
5903
|
+
soil: "soil",
|
|
5904
|
+
asphalt: "asphalt",
|
|
5905
|
+
sand: "sand",
|
|
5906
|
+
gravel: "gravel",
|
|
5907
|
+
turf: "turf",
|
|
5908
|
+
mortar: "mortar"
|
|
5561
5909
|
},
|
|
5562
5910
|
partStatus: { intact: "intact", damaged: "damaged", collapsed: "collapsed", broken: "broken", melted: "melted", ashes: "ashes" },
|
|
5563
5911
|
jointKinds: {
|
|
@@ -5648,7 +5996,12 @@ var locales = {
|
|
|
5648
5996
|
cactus: "cacto",
|
|
5649
5997
|
flesh_fruit: "fruta",
|
|
5650
5998
|
wax: "cera",
|
|
5651
|
-
soil: "terra"
|
|
5999
|
+
soil: "terra",
|
|
6000
|
+
asphalt: "asfalto",
|
|
6001
|
+
sand: "areia",
|
|
6002
|
+
gravel: "brita",
|
|
6003
|
+
turf: "grama",
|
|
6004
|
+
mortar: "argamassa"
|
|
5652
6005
|
},
|
|
5653
6006
|
partStatus: { intact: "intacto", damaged: "danificado", collapsed: "desabado", broken: "quebrado", melted: "derretido", ashes: "cinzas" },
|
|
5654
6007
|
jointKinds: {
|