@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.js
CHANGED
|
@@ -340,6 +340,47 @@ var frame = (ax, ay, az, angle = 0) => {
|
|
|
340
340
|
return angle === 0 ? q : quat.mul(q, quat.fromAxisAngle([0, 0, 1], angle));
|
|
341
341
|
};
|
|
342
342
|
var clamp = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
343
|
+
var splinterBox = (inp, box, limit = inp.maxFragments) => {
|
|
344
|
+
const { rnd } = inp;
|
|
345
|
+
const dims = box.s;
|
|
346
|
+
const [fmin, fmax] = inp.fragmentSize;
|
|
347
|
+
const g = inp.grain ?? [0, 1, 2].sort((a2, b2) => dims[b2] - dims[a2])[0];
|
|
348
|
+
const [a, b] = [0, 1, 2].filter((i) => i !== g);
|
|
349
|
+
const w = Math.max(fmin * 1.5, 0.01);
|
|
350
|
+
const c = clamp(Math.round(dims[a] * dims[b] / (w * w)), 3, Math.max(3, Math.floor(limit / 2)));
|
|
351
|
+
const maxLen = fmax * 4;
|
|
352
|
+
const cells = bsp(box, c, [a, b], rnd);
|
|
353
|
+
const pieces = [];
|
|
354
|
+
for (const cell of cells) {
|
|
355
|
+
const extra = rnd() < 0.4 ? 1 : 0;
|
|
356
|
+
const k = Math.max(1, Math.ceil(dims[g] / maxLen)) + extra;
|
|
357
|
+
for (const p of bsp(cell, k, [g], rnd)) pieces.push(p);
|
|
358
|
+
}
|
|
359
|
+
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] }));
|
|
360
|
+
};
|
|
361
|
+
var severAt = (inp, root, at, gap) => {
|
|
362
|
+
const dims = root.s;
|
|
363
|
+
const L = [0, 1, 2].sort((x, y) => dims[y] - dims[x])[0];
|
|
364
|
+
const half = dims[L] / 2;
|
|
365
|
+
const lo = clamp(at - gap / 2, -half, half), hi = clamp(at + gap / 2, -half, half);
|
|
366
|
+
const out = [];
|
|
367
|
+
const stub = (from, to) => {
|
|
368
|
+
if (to - from < 0.02) return;
|
|
369
|
+
const c = [root.c[0], root.c[1], root.c[2]], sz = [dims[0], dims[1], dims[2]];
|
|
370
|
+
c[L] = (from + to) / 2;
|
|
371
|
+
sz[L] = to - from;
|
|
372
|
+
out.push({ center: c, size: sz, q: quat.identity(), shape: "slab", weight: sz[0] * sz[1] * sz[2] });
|
|
373
|
+
};
|
|
374
|
+
stub(-half, lo);
|
|
375
|
+
stub(hi, half);
|
|
376
|
+
const band = { c: [root.c[0], root.c[1], root.c[2]], s: [dims[0], dims[1], dims[2]] };
|
|
377
|
+
band.c[L] = (lo + hi) / 2;
|
|
378
|
+
band.s[L] = Math.max(hi - lo, 0.01);
|
|
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);
|
|
382
|
+
return out;
|
|
383
|
+
};
|
|
343
384
|
var tearStack = (inp, root, t, u, v, n, sheet) => {
|
|
344
385
|
const { dims, rnd, maxFragments } = inp;
|
|
345
386
|
const layers = clamp(Math.ceil(dims[t] / STACK_LAYER), 2, 4);
|
|
@@ -368,6 +409,7 @@ var planFragments = (inp) => {
|
|
|
368
409
|
const t = sorted[0];
|
|
369
410
|
const [u, v] = [sorted[1], sorted[2]].sort((a, b) => a - b);
|
|
370
411
|
if (style === "snap") {
|
|
412
|
+
if (inp.cut) return severAt(inp, root, inp.cut.at, inp.cut.gap);
|
|
371
413
|
const n2 = Math.min(maxFragments, 2 + Math.floor(rnd() * 3));
|
|
372
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] });
|
|
373
415
|
return out;
|
|
@@ -378,23 +420,8 @@ var planFragments = (inp) => {
|
|
|
378
420
|
return out;
|
|
379
421
|
}
|
|
380
422
|
if (style === "splinter") {
|
|
381
|
-
|
|
382
|
-
const
|
|
383
|
-
const w = Math.max(fmin * 1.5, 0.01);
|
|
384
|
-
const c = clamp(Math.round(dims[a] * dims[b] / (w * w)), 3, Math.max(3, Math.floor(maxFragments / 2)));
|
|
385
|
-
const maxLen = fmax * 4;
|
|
386
|
-
const cells = bsp(root, c, [a, b], rnd);
|
|
387
|
-
const pieces = [];
|
|
388
|
-
for (const cell of cells) {
|
|
389
|
-
let list = [cell];
|
|
390
|
-
const extra = rnd() < 0.4 ? 1 : 0;
|
|
391
|
-
const k = Math.max(1, Math.ceil(dims[g] / maxLen)) + extra;
|
|
392
|
-
list = bsp(cell, k, [g], rnd);
|
|
393
|
-
for (const p of list) pieces.push(p);
|
|
394
|
-
}
|
|
395
|
-
for (const p of pieces.slice(0, maxFragments)) {
|
|
396
|
-
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] });
|
|
397
|
-
}
|
|
423
|
+
if (inp.cut) return severAt(inp, root, inp.cut.at, inp.cut.gap);
|
|
424
|
+
for (const p of splinterBox(inp, root)) out.push(p);
|
|
398
425
|
return out;
|
|
399
426
|
}
|
|
400
427
|
if (style === "tear") {
|
|
@@ -562,6 +589,27 @@ var rebarBars = (dims, thin, spacing) => {
|
|
|
562
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]]));
|
|
563
590
|
return out;
|
|
564
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
|
+
};
|
|
565
613
|
var clipBarsToDisc = (bars, u, v, center, radius) => {
|
|
566
614
|
const out = [];
|
|
567
615
|
for (const [a, b] of bars) {
|
|
@@ -1539,14 +1587,21 @@ var materials_default = [
|
|
|
1539
1587
|
smoke: "grey"
|
|
1540
1588
|
},
|
|
1541
1589
|
visual: {
|
|
1542
|
-
charColor: 1709072
|
|
1590
|
+
charColor: 1709072,
|
|
1591
|
+
innerColor: 14730131
|
|
1543
1592
|
},
|
|
1544
1593
|
tags: [
|
|
1545
1594
|
"wood",
|
|
1546
1595
|
"organic",
|
|
1547
1596
|
"flammable"
|
|
1548
1597
|
],
|
|
1549
|
-
compressive: 1e5
|
|
1598
|
+
compressive: 1e5,
|
|
1599
|
+
hits: {
|
|
1600
|
+
blunt: {
|
|
1601
|
+
tear: "splinters",
|
|
1602
|
+
tearJoules: 60
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1550
1605
|
},
|
|
1551
1606
|
{
|
|
1552
1607
|
id: "wood_hard",
|
|
@@ -1576,14 +1631,21 @@ var materials_default = [
|
|
|
1576
1631
|
smoke: "grey"
|
|
1577
1632
|
},
|
|
1578
1633
|
visual: {
|
|
1579
|
-
charColor: 1709072
|
|
1634
|
+
charColor: 1709072,
|
|
1635
|
+
innerColor: 13213802
|
|
1580
1636
|
},
|
|
1581
1637
|
tags: [
|
|
1582
1638
|
"wood",
|
|
1583
1639
|
"organic",
|
|
1584
1640
|
"flammable"
|
|
1585
1641
|
],
|
|
1586
|
-
compressive: 15e4
|
|
1642
|
+
compressive: 15e4,
|
|
1643
|
+
hits: {
|
|
1644
|
+
blunt: {
|
|
1645
|
+
tear: "splinters",
|
|
1646
|
+
tearJoules: 150
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1587
1649
|
},
|
|
1588
1650
|
{
|
|
1589
1651
|
id: "plywood",
|
|
@@ -1613,14 +1675,24 @@ var materials_default = [
|
|
|
1613
1675
|
smoke: "grey"
|
|
1614
1676
|
},
|
|
1615
1677
|
visual: {
|
|
1616
|
-
charColor: 1709072
|
|
1678
|
+
charColor: 1709072,
|
|
1679
|
+
innerColor: 14862234
|
|
1617
1680
|
},
|
|
1618
1681
|
tags: [
|
|
1619
1682
|
"wood",
|
|
1620
1683
|
"organic",
|
|
1621
1684
|
"flammable"
|
|
1622
1685
|
],
|
|
1623
|
-
compressive: 6e4
|
|
1686
|
+
compressive: 6e4,
|
|
1687
|
+
hits: {
|
|
1688
|
+
ballistic: {
|
|
1689
|
+
tear: "splinters"
|
|
1690
|
+
},
|
|
1691
|
+
blunt: {
|
|
1692
|
+
tear: "splinters",
|
|
1693
|
+
tearJoules: 80
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1624
1696
|
},
|
|
1625
1697
|
{
|
|
1626
1698
|
id: "mdf",
|
|
@@ -1647,14 +1719,25 @@ var materials_default = [
|
|
|
1647
1719
|
smoke: "grey"
|
|
1648
1720
|
},
|
|
1649
1721
|
visual: {
|
|
1650
|
-
charColor: 1709072
|
|
1722
|
+
charColor: 1709072,
|
|
1723
|
+
innerColor: 12096362
|
|
1651
1724
|
},
|
|
1652
1725
|
tags: [
|
|
1653
1726
|
"wood",
|
|
1654
1727
|
"organic",
|
|
1655
1728
|
"flammable"
|
|
1656
1729
|
],
|
|
1657
|
-
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
|
+
}
|
|
1658
1741
|
},
|
|
1659
1742
|
{
|
|
1660
1743
|
id: "glass",
|
|
@@ -1768,6 +1851,17 @@ var materials_default = [
|
|
|
1768
1851
|
damageScale: {
|
|
1769
1852
|
explosive: 1.2
|
|
1770
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
|
+
},
|
|
1771
1865
|
hitDebris: "chips",
|
|
1772
1866
|
breakDebris: "dust",
|
|
1773
1867
|
thermal: {
|
|
@@ -2136,7 +2230,8 @@ var materials_default = [
|
|
|
2136
2230
|
smoke: "white"
|
|
2137
2231
|
},
|
|
2138
2232
|
visual: {
|
|
2139
|
-
charColor: 1709072
|
|
2233
|
+
charColor: 1709072,
|
|
2234
|
+
innerColor: 14207126
|
|
2140
2235
|
},
|
|
2141
2236
|
tags: [
|
|
2142
2237
|
"plant",
|
|
@@ -2144,7 +2239,12 @@ var materials_default = [
|
|
|
2144
2239
|
"organic",
|
|
2145
2240
|
"flammable"
|
|
2146
2241
|
],
|
|
2147
|
-
compressive: 6e4
|
|
2242
|
+
compressive: 6e4,
|
|
2243
|
+
hits: {
|
|
2244
|
+
blunt: {
|
|
2245
|
+
tear: "none"
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2148
2248
|
},
|
|
2149
2249
|
{
|
|
2150
2250
|
id: "cactus",
|
|
@@ -2259,6 +2359,130 @@ var materials_default = [
|
|
|
2259
2359
|
"masonry"
|
|
2260
2360
|
],
|
|
2261
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
|
|
2262
2486
|
}
|
|
2263
2487
|
];
|
|
2264
2488
|
|
|
@@ -2444,6 +2668,32 @@ var fragility = (ductility, thickness) => {
|
|
|
2444
2668
|
// src/world.ts
|
|
2445
2669
|
import { ignitionChance, resolveIgnitionRules, sparkTargetFactor } from "@adatechnology/game-contracts";
|
|
2446
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;
|
|
2447
2697
|
var DMG_PER_J = 0.02;
|
|
2448
2698
|
var BLAST_COUPLING = 0.1;
|
|
2449
2699
|
var SHELTER_DEFAULTS = { k: 2800, brokenTransmission: 0.5, minPath: 5e-3 };
|
|
@@ -2457,9 +2707,17 @@ var BREACH_BLAST_FACTOR = 6;
|
|
|
2457
2707
|
var BREACH_AREA = 0.25;
|
|
2458
2708
|
var BREACH_BLAST_REACH = 0.12;
|
|
2459
2709
|
var RESIDUAL_CAPACITY = 0.15;
|
|
2710
|
+
var SEVER_BAND = 0.1;
|
|
2711
|
+
var SEVER_ASPECT = 3;
|
|
2712
|
+
var SEVER_BALLISTIC = 2;
|
|
2713
|
+
var TEAR_JOULES = 150;
|
|
2714
|
+
var tears = 0;
|
|
2460
2715
|
var SUPPORT_TOLERANCE = 0.05;
|
|
2461
2716
|
var UNIT_FRAGMENT_FACTOR = 4;
|
|
2462
2717
|
var BREACH_GRID_CAP = 4096;
|
|
2718
|
+
var CHIP_ENERGY = 200;
|
|
2719
|
+
var CHIP_SPLASH = 0.5;
|
|
2720
|
+
var BAR_BOW = 0.08;
|
|
2463
2721
|
var CUT_SLICE = 1;
|
|
2464
2722
|
var cutFactorOf = (shape) => clamp2(Math.max(...shapeDims(shape)) / CUT_SLICE, 1, 50);
|
|
2465
2723
|
var clamp2 = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
@@ -2469,8 +2727,24 @@ var penetrationOf = (mat, table) => {
|
|
|
2469
2727
|
return v === void 0 ? void 0 : v * (mat.penetrationScale ?? 1);
|
|
2470
2728
|
};
|
|
2471
2729
|
var capacityScale = (p) => {
|
|
2472
|
-
|
|
2473
|
-
|
|
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);
|
|
2474
2748
|
};
|
|
2475
2749
|
var isLost = (p) => p.st.status === "broken" || p.st.status === "melted" || p.st.status === "ashes";
|
|
2476
2750
|
var clone = (v) => structuredClone(v);
|
|
@@ -2503,6 +2777,7 @@ var createDestruction = (options = {}) => {
|
|
|
2503
2777
|
let windCfg = windOpts ? resolveWind(windOpts) : null;
|
|
2504
2778
|
const ignitionRules = resolveIgnitionRules(options.ignition);
|
|
2505
2779
|
const breachBlastFactor = options.breach?.blastFactor ?? BREACH_BLAST_FACTOR;
|
|
2780
|
+
const chipEnergy = options.breach?.chipEnergy ?? CHIP_ENERGY;
|
|
2506
2781
|
const rng = createRng(options.seed ?? 1);
|
|
2507
2782
|
const rnd = () => rng.next();
|
|
2508
2783
|
let time = 0;
|
|
@@ -2876,41 +3151,68 @@ var createDestruction = (options = {}) => {
|
|
|
2876
3151
|
markDamaged(p);
|
|
2877
3152
|
if (p.rt.hinge >= flexure.failRotationDeg * Math.PI / 180) hingeDrop(o, p, src);
|
|
2878
3153
|
};
|
|
3154
|
+
const freeSpan = (p, supports) => {
|
|
3155
|
+
if (supports.length === 0) return Infinity;
|
|
3156
|
+
const d = p.dims;
|
|
3157
|
+
const L = [0, 1, 2].sort((a, b) => d[b] - d[a])[0];
|
|
3158
|
+
const spans = supports.map((k) => contactBox(p, k)).map(([lo, hi]) => [lo[L], hi[L]]);
|
|
3159
|
+
let far = 0;
|
|
3160
|
+
for (let i = 0; i <= 16; i++) {
|
|
3161
|
+
const x = (i / 16 - 0.5) * d[L];
|
|
3162
|
+
let near = Infinity;
|
|
3163
|
+
for (const [lo, hi] of spans) near = Math.min(near, Math.max(lo - x, x - hi, 0));
|
|
3164
|
+
far = Math.max(far, near);
|
|
3165
|
+
}
|
|
3166
|
+
return 2 * far;
|
|
3167
|
+
};
|
|
2879
3168
|
const solveStructure = (o) => {
|
|
2880
3169
|
o.maxStress = 0;
|
|
2881
3170
|
if (!o.hasStructure) return;
|
|
2882
3171
|
const parts = o.parts;
|
|
2883
3172
|
const n = parts.length;
|
|
2884
3173
|
const dist = new Array(n).fill(-1);
|
|
2885
|
-
const queue = [];
|
|
2886
3174
|
const ok = (i) => parts[i].structural && !isLost(parts[i]);
|
|
2887
3175
|
const isPartition = (i) => parts[i].spec.structure?.role === "partition";
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
if (
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
const r = j.ia === s ? j.ib : j.ia;
|
|
2898
|
-
if (dist[r] >= 0 || !ok(r) || restsOn(o, j, r) !== s) continue;
|
|
2899
|
-
dist[r] = dist[s] + 1;
|
|
2900
|
-
queue.push(r);
|
|
3176
|
+
const out = /* @__PURE__ */ new Set();
|
|
3177
|
+
let queue = [];
|
|
3178
|
+
let lowerOf = /* @__PURE__ */ new Map();
|
|
3179
|
+
for (; ; ) {
|
|
3180
|
+
dist.fill(-1);
|
|
3181
|
+
queue = [];
|
|
3182
|
+
for (let i = 0; i < n; i++) if (ok(i) && !out.has(i) && parts[i].anchored) {
|
|
3183
|
+
dist[i] = 0;
|
|
3184
|
+
queue.push(i);
|
|
2901
3185
|
}
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
3186
|
+
for (let h = 0; h < queue.length; h++) {
|
|
3187
|
+
const s = queue[h];
|
|
3188
|
+
if (isPartition(s)) continue;
|
|
3189
|
+
for (const j of o.byPart.get(parts[s].spec.id)) {
|
|
3190
|
+
if (j.st.open || !j.bears) continue;
|
|
3191
|
+
const r = j.ia === s ? j.ib : j.ia;
|
|
3192
|
+
if (dist[r] >= 0 || !ok(r) || out.has(r) || restsOn(o, j, r) !== s) continue;
|
|
3193
|
+
dist[r] = dist[s] + 1;
|
|
3194
|
+
queue.push(r);
|
|
3195
|
+
}
|
|
2911
3196
|
}
|
|
2912
|
-
lowerOf
|
|
3197
|
+
lowerOf = /* @__PURE__ */ new Map();
|
|
3198
|
+
let dropped = false;
|
|
3199
|
+
for (const i of queue) {
|
|
3200
|
+
const lower = [];
|
|
3201
|
+
for (const j of o.byPart.get(parts[i].spec.id)) {
|
|
3202
|
+
if (j.st.open || !j.bears) continue;
|
|
3203
|
+
const k = restsOn(o, j, i);
|
|
3204
|
+
if (k >= 0 && dist[k] >= 0 && dist[k] < dist[i] && !isPartition(k)) lower.push(k);
|
|
3205
|
+
}
|
|
3206
|
+
lowerOf.set(i, lower);
|
|
3207
|
+
const max = parts[i].spec.structure?.maxSpan;
|
|
3208
|
+
if (max !== void 0 && !parts[i].anchored && freeSpan(parts[i], lower.map((k) => parts[k])) > max) {
|
|
3209
|
+
out.add(i);
|
|
3210
|
+
dropped = true;
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
if (!dropped) break;
|
|
2913
3214
|
}
|
|
3215
|
+
const incoming = new Array(n).fill(0);
|
|
2914
3216
|
const bending = new Array(n).fill(0);
|
|
2915
3217
|
const through = parts.map(() => []);
|
|
2916
3218
|
const pass = (k, at, mass) => {
|
|
@@ -3422,6 +3724,7 @@ var createDestruction = (options = {}) => {
|
|
|
3422
3724
|
const r = rayPart(o, p, origin, d);
|
|
3423
3725
|
if (!r || r.t0 < 0 || r.t0 > maxDist) continue;
|
|
3424
3726
|
if (p.st.opening && insideOpening(o, p, vec.madd(origin, d, r.t0))) continue;
|
|
3727
|
+
if (p.st.chipped && insideChip(p, objLocal(o, vec.madd(origin, d, r.t0)))) continue;
|
|
3425
3728
|
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 };
|
|
3426
3729
|
}
|
|
3427
3730
|
}
|
|
@@ -3622,6 +3925,12 @@ var createDestruction = (options = {}) => {
|
|
|
3622
3925
|
debris(o, p, layer.mat.hitDebris, wp, normal, 5, 2, src);
|
|
3623
3926
|
setStage(o, p, 1, {}, src);
|
|
3624
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
|
+
};
|
|
3625
3934
|
const insideOpening = (o, p, objectPoint) => {
|
|
3626
3935
|
const op = p.st.opening;
|
|
3627
3936
|
if (!op) return false;
|
|
@@ -3685,6 +3994,91 @@ var createDestruction = (options = {}) => {
|
|
|
3685
3994
|
if (total < (p.spec.breach?.area ?? BREACH_AREA)) return;
|
|
3686
3995
|
openBreach(o, p, [sum[0] / total, sum[1] / total, sum[2] / total], total, energy, dir, src);
|
|
3687
3996
|
};
|
|
3997
|
+
const unitGrids = /* @__PURE__ */ new WeakMap();
|
|
3998
|
+
const gridOf = (p) => {
|
|
3999
|
+
let grid = unitGrids.get(p);
|
|
4000
|
+
if (!grid) unitGrids.set(p, grid = unitGrid(p.dims, p.thin, unitsOf(p), BREACH_GRID_CAP));
|
|
4001
|
+
return grid;
|
|
4002
|
+
};
|
|
4003
|
+
const insideChip = (p, objectPoint) => {
|
|
4004
|
+
const cells = p.st.chipped;
|
|
4005
|
+
if (!cells?.length) return false;
|
|
4006
|
+
const [u, v] = planeAxes(p.thin);
|
|
4007
|
+
const lp = toLocal(p.pos, p.q, objectPoint);
|
|
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);
|
|
4009
|
+
};
|
|
4010
|
+
const chipUnits = (o, p, kind, wp, hitEnergy, dir, radius, src) => {
|
|
4011
|
+
var _a, _b;
|
|
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;
|
|
4017
|
+
const [u, v] = planeAxes(p.thin);
|
|
4018
|
+
const lp = toLocal(p.pos, p.q, objLocal(o, wp));
|
|
4019
|
+
const grid = gridOf(p);
|
|
4020
|
+
const worn = (_a = p.rt).chips ?? (_a.chips = {});
|
|
4021
|
+
const out = [];
|
|
4022
|
+
for (let i = 0; i < grid.length; i++) {
|
|
4023
|
+
const cell = grid[i];
|
|
4024
|
+
if (worn[i] === -1) continue;
|
|
4025
|
+
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));
|
|
4026
|
+
if (gap > radius) continue;
|
|
4027
|
+
const share = gap === 0 ? 1 : CHIP_SPLASH * (1 - gap / radius);
|
|
4028
|
+
const total = (worn[i] ?? 0) + energy * share;
|
|
4029
|
+
const mass = p.mat.density * cell.size[0] * cell.size[1] * cell.size[2];
|
|
4030
|
+
if (total < perKg * mass) {
|
|
4031
|
+
worn[i] = total;
|
|
4032
|
+
continue;
|
|
4033
|
+
}
|
|
4034
|
+
worn[i] = -1;
|
|
4035
|
+
out.push(cell);
|
|
4036
|
+
}
|
|
4037
|
+
if (out.length === 0) return;
|
|
4038
|
+
const { c, q } = partXf(o, p);
|
|
4039
|
+
const chipped = (_b = p.st).chipped ?? (_b.chipped = []);
|
|
4040
|
+
const holes = localHolesOf(p);
|
|
4041
|
+
for (const cell of out) {
|
|
4042
|
+
const mass = Math.min(p.st.mass, p.mat.density * cell.size[0] * cell.size[1] * cell.size[2]);
|
|
4043
|
+
const center = toWorld(c, q, cell.center);
|
|
4044
|
+
const speed = clamp2(Math.sqrt(2 * energy * 0.05 / Math.max(mass, 0.05)), 0.5, 6);
|
|
4045
|
+
const fragments = cellFragments(o, p, [cell], wp, dir, speed, mass, `c${chipped.length}_`);
|
|
4046
|
+
p.st.holes = p.st.holes.filter((_, k) => marksInBox(cell.center, cell.size, void 0, [holes[k]]).length === 0);
|
|
4047
|
+
chipped.push({ center: [cell.center[0], cell.center[1], cell.center[2]], size: [cell.size[0], cell.size[1], cell.size[2]] });
|
|
4048
|
+
p.st.mass = Math.max(0, p.st.mass - mass);
|
|
4049
|
+
p.rt.removed = (p.rt.removed ?? 0) + mass;
|
|
4050
|
+
markDamaged(p);
|
|
4051
|
+
setStage(o, p, 1, {}, src);
|
|
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 });
|
|
4053
|
+
debris(o, p, p.mat.breakDebris, center, vec.scale(dir ?? [0, 1, 0], -1), 6, 2, src);
|
|
4054
|
+
}
|
|
4055
|
+
if (p.spec.reinforcement) exposeRebar(o, p, src);
|
|
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) });
|
|
4081
|
+
};
|
|
3688
4082
|
const openBreach = (o, p, center, total, energy, dir, src) => {
|
|
3689
4083
|
const [u, v] = planeAxes(p.thin);
|
|
3690
4084
|
const radius = clamp2(Math.sqrt(total / Math.PI), Math.sqrt((p.spec.breach?.area ?? BREACH_AREA) / Math.PI), breachRadius);
|
|
@@ -3696,7 +4090,8 @@ var createDestruction = (options = {}) => {
|
|
|
3696
4090
|
failPart(o, p, worldCenter, dir, energy, 12, src);
|
|
3697
4091
|
return;
|
|
3698
4092
|
}
|
|
3699
|
-
const
|
|
4093
|
+
const worn = p.rt.chips;
|
|
4094
|
+
const cells = cellsWithin(gridOf(p).filter((_, i) => worn?.[i] !== -1), p.thin, lc, radius);
|
|
3700
4095
|
const removed = p.st.mass * cells.reduce((sum, cell) => sum + cell.size[u] * cell.size[v], 0) / (p.dims[u] * p.dims[v]);
|
|
3701
4096
|
const speed = clamp2(Math.sqrt(2 * energy * 0.1 / Math.max(removed, 0.05)), 0.5, 8);
|
|
3702
4097
|
const fragments = cellFragments(o, p, cells, worldCenter, dir, speed, removed, "b");
|
|
@@ -3710,10 +4105,7 @@ var createDestruction = (options = {}) => {
|
|
|
3710
4105
|
setStage(o, p, 2, { opening: { center: worldCenter, radius }, fragments }, src);
|
|
3711
4106
|
debris(o, p, p.mat.breakDebris, worldCenter, vec.scale(dir ?? [0, 1, 0], -1), 14, 3, src);
|
|
3712
4107
|
const reinforcement = p.spec.reinforcement;
|
|
3713
|
-
if (reinforcement)
|
|
3714
|
-
const bars = worldBars(o, p, clipBarsToDisc(rebarBars(p.dims, p.thin, reinforcement.spacing), u, v, [lc[u], lc[v]], radius + 0.05));
|
|
3715
|
-
if (bars.length > 0) emit({ ...base(o, src), type: "expose", part: p.spec.id, bars });
|
|
3716
|
-
}
|
|
4108
|
+
if (reinforcement) exposeRebar(o, p, src);
|
|
3717
4109
|
if (p.structural) solveStructure(o);
|
|
3718
4110
|
};
|
|
3719
4111
|
const stripPart = (o, p, reinforcement, src) => {
|
|
@@ -3725,8 +4117,8 @@ var createDestruction = (options = {}) => {
|
|
|
3725
4117
|
p.st.burning = false;
|
|
3726
4118
|
p.rt.jet = false;
|
|
3727
4119
|
markDamaged(p);
|
|
3728
|
-
emit({ ...base(o, src), type: "expose", part: p.spec.id, bars: worldBars(o, p, bars) });
|
|
3729
4120
|
solveStructure(o);
|
|
4121
|
+
exposeRebar(o, p, src);
|
|
3730
4122
|
collapseCheck(o, src);
|
|
3731
4123
|
};
|
|
3732
4124
|
const unitFragmentPlan = (p, cap) => {
|
|
@@ -3780,6 +4172,8 @@ var createDestruction = (options = {}) => {
|
|
|
3780
4172
|
const grain = p.spec.grain ? AXIS[p.spec.grain] : void 0;
|
|
3781
4173
|
const hitLocal = toLocal(c, q, hitW);
|
|
3782
4174
|
const unitPlan = p.spec.units ? unitFragmentPlan(p, maxFragments * UNIT_FRAGMENT_FACTOR) : [];
|
|
4175
|
+
const axis = style === "splinter" || style === "snap" ? severAxis(p) : -1;
|
|
4176
|
+
const cutAt = axis < 0 ? void 0 : p.rt.cutAt ?? (origin ? hitLocal[axis] : void 0);
|
|
3783
4177
|
const plan = unitPlan.length > 0 ? unitPlan : planFragments({
|
|
3784
4178
|
style,
|
|
3785
4179
|
dims: p.dims,
|
|
@@ -3789,7 +4183,8 @@ var createDestruction = (options = {}) => {
|
|
|
3789
4183
|
fragmentSize: p.mat.fragmentSize ?? [0.04, 0.2],
|
|
3790
4184
|
maxFragments,
|
|
3791
4185
|
rnd,
|
|
3792
|
-
sheet: p.mat.sheet
|
|
4186
|
+
sheet: p.mat.sheet,
|
|
4187
|
+
...cutAt !== void 0 ? { cut: { at: cutAt, gap: SEVER_BAND } } : {}
|
|
3793
4188
|
});
|
|
3794
4189
|
const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
|
|
3795
4190
|
const mass = p.st.mass;
|
|
@@ -3848,10 +4243,33 @@ var createDestruction = (options = {}) => {
|
|
|
3848
4243
|
emit({ ...base(o, src), type: "primed", part: p.spec.id, point: partXf(o, p).c, trigger, delay });
|
|
3849
4244
|
};
|
|
3850
4245
|
const CHAIN_TRIGGER_OF = { ballistic: "shot", explosive: "explosion" };
|
|
4246
|
+
const severAxis = (p) => {
|
|
4247
|
+
if (p.mat.fracture !== "splinter" && p.mat.fracture !== "snap" || p.spec.shape.kind !== "box") return -1;
|
|
4248
|
+
const d = p.dims;
|
|
4249
|
+
const order = [0, 1, 2].sort((a, b) => d[b] - d[a]);
|
|
4250
|
+
return d[order[0]] >= SEVER_ASPECT * d[order[1]] && d[order[0]] >= 3 * SEVER_BAND ? order[0] : -1;
|
|
4251
|
+
};
|
|
4252
|
+
const bandOf = (o, p, axis, origin) => {
|
|
4253
|
+
const { c, q } = partXf(o, p);
|
|
4254
|
+
const along = toLocal(c, q, origin)[axis];
|
|
4255
|
+
const L = p.dims[axis];
|
|
4256
|
+
return clamp2(Math.floor((along + L / 2) / SEVER_BAND), 0, Math.ceil(L / SEVER_BAND) - 1);
|
|
4257
|
+
};
|
|
3851
4258
|
const applyDamage = (o, p, amount, type, origin, dir, fragEnergy, cap, src) => {
|
|
4259
|
+
var _a;
|
|
3852
4260
|
if (isLost(p) || amount <= 0) return 0;
|
|
3853
4261
|
const prev = p.st.hp;
|
|
3854
4262
|
p.st.hp -= amount;
|
|
4263
|
+
const axis = origin && type !== "fire" && type !== "heat" ? severAxis(p) : -1;
|
|
4264
|
+
if (axis >= 0 && p.st.hp > 0) {
|
|
4265
|
+
const band = bandOf(o, p, axis, origin);
|
|
4266
|
+
const cuts = (_a = p.rt).cuts ?? (_a.cuts = {});
|
|
4267
|
+
cuts[band] = (cuts[band] ?? 0) + amount * (type === "ballistic" ? SEVER_BALLISTIC : 1);
|
|
4268
|
+
if (cuts[band] >= p.st.maxHp * SEVER_BAND / p.dims[axis]) {
|
|
4269
|
+
p.rt.cutAt = -p.dims[axis] / 2 + (band + 0.5) * SEVER_BAND;
|
|
4270
|
+
p.st.hp = 0;
|
|
4271
|
+
}
|
|
4272
|
+
}
|
|
3855
4273
|
markDamaged(p);
|
|
3856
4274
|
const trigger = CHAIN_TRIGGER_OF[type];
|
|
3857
4275
|
const chained = trigger !== void 0 && chainDelayOf(p.spec, trigger) !== void 0;
|
|
@@ -3887,10 +4305,53 @@ var createDestruction = (options = {}) => {
|
|
|
3887
4305
|
}
|
|
3888
4306
|
}
|
|
3889
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
|
+
};
|
|
3890
4351
|
const ballistic = (o, p, entry, n, d, E, caliber, pathLen, src, round = {}) => {
|
|
3891
4352
|
const mat = p.mat;
|
|
3892
4353
|
const piercing = round.piercing ?? 1;
|
|
3893
|
-
if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry))) {
|
|
4354
|
+
if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry)) || insideChip(p, objLocal(o, entry))) {
|
|
3894
4355
|
return { penetrated: true, exitEnergy: E, exitPoint: vec.madd(entry, d, Math.min(pathLen, p.thickness)), damage: 0 };
|
|
3895
4356
|
}
|
|
3896
4357
|
const sin = Math.min(1, Math.abs(vec.dot(d, n)));
|
|
@@ -3915,6 +4376,7 @@ var createDestruction = (options = {}) => {
|
|
|
3915
4376
|
return { penetrated: false, exitEnergy: 0, damage: dmg2 };
|
|
3916
4377
|
}
|
|
3917
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);
|
|
3918
4380
|
const brittle = mat.ductility < 0.3;
|
|
3919
4381
|
const ratio = Ep / Math.max(thr, 1e-6);
|
|
3920
4382
|
const crackLen = clamp2(0.03 + ratio * 0.2, 0.03, 0.8);
|
|
@@ -3948,7 +4410,10 @@ var createDestruction = (options = {}) => {
|
|
|
3948
4410
|
} else addHole(o, p, entry, n, caliber / 1e3, false, void 0, src);
|
|
3949
4411
|
debris(o, p, mat.hitDebris, entry, n, clamp2(Math.round(E / 60) + 2, 2, 14), clamp2(Math.sqrt(E) / 6, 1, 8), src);
|
|
3950
4412
|
wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true);
|
|
3951
|
-
const
|
|
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);
|
|
3952
4417
|
return { penetrated, exitEnergy: penetrated ? penMm !== void 0 ? E * (1 - thr / Ep) : E - thr / (hard ? piercing : 1) : 0, exitPoint, damage: dmg };
|
|
3953
4418
|
};
|
|
3954
4419
|
const melee = (o, p, kind, E, point, n, d, contactR, src) => {
|
|
@@ -3956,14 +4421,20 @@ var createDestruction = (options = {}) => {
|
|
|
3956
4421
|
const thrMm = mat.penetration * p.thickness * 1e3;
|
|
3957
4422
|
const fragile = kind === "blunt" ? fragility(mat.ductility, p.thickness) : 1;
|
|
3958
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;
|
|
3959
4427
|
if (E > 0) {
|
|
3960
4428
|
if (kind === "blunt") {
|
|
3961
|
-
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);
|
|
3962
4430
|
else if (mat.ductility < 0.3) {
|
|
3963
4431
|
if (Ec >= 5) addCrack(o, p, point, n, clamp2(0.04 + Math.min(Ec, E * 6) * 8e-4, 0.04, 0.6), src);
|
|
3964
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);
|
|
3965
4433
|
} else if (mat.fracture === "tear") {
|
|
3966
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);
|
|
3967
4438
|
} else if (mat.ductility >= 0.5) {
|
|
3968
4439
|
addDent(o, p, point, n, Math.max(contactR, 0.01), clamp2(E * 4e-5, 5e-4, 0.02), src);
|
|
3969
4440
|
}
|
|
@@ -3976,6 +4447,8 @@ var createDestruction = (options = {}) => {
|
|
|
3976
4447
|
const scale = mat.damageScale?.[kind] ?? 1;
|
|
3977
4448
|
const done = applyDamage(o, p, Ec * DMG_PER_J * scale * (kind === "slash" ? p.cutFactor : 1), kind, point, d, E * 0.1, 12, src);
|
|
3978
4449
|
addBreach(o, p, point, breachAreaOf(p, done, false), E, d, 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);
|
|
3979
4452
|
return done;
|
|
3980
4453
|
};
|
|
3981
4454
|
const blastPart = (o, p, point, energy, radius, src, res, reachAt) => {
|
|
@@ -4554,6 +5027,7 @@ var createDestruction = (options = {}) => {
|
|
|
4554
5027
|
if (p.rt.stressT <= 0) {
|
|
4555
5028
|
p.rt.stressT = 0.5;
|
|
4556
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);
|
|
4557
5031
|
}
|
|
4558
5032
|
applyDamage(o, p, overloadRate(stress) * p.st.maxHp * dt, "blunt", null, null, 5, 3);
|
|
4559
5033
|
} else if (stress > 0.8 && p.mat.ductility < 0.3 && p.rt.stressT <= 0 && p.st.cracks.length < 6) {
|
|
@@ -4770,6 +5244,18 @@ var createDestruction = (options = {}) => {
|
|
|
4770
5244
|
const r = raycastInternal(origin, direction, maxDistance);
|
|
4771
5245
|
return r ? { object: r.object, part: r.part, point: r.point, normal: r.normal, distance: r.distance } : null;
|
|
4772
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
|
+
},
|
|
4773
5259
|
shoot(origin, direction, energy, caliber, source, round) {
|
|
4774
5260
|
const extras = typeof round === "number" ? { piercing: round } : round ?? {};
|
|
4775
5261
|
return withCollapses(run(() => {
|
|
@@ -5414,7 +5900,12 @@ var locales = {
|
|
|
5414
5900
|
cactus: "cactus",
|
|
5415
5901
|
flesh_fruit: "fruit",
|
|
5416
5902
|
wax: "wax",
|
|
5417
|
-
soil: "soil"
|
|
5903
|
+
soil: "soil",
|
|
5904
|
+
asphalt: "asphalt",
|
|
5905
|
+
sand: "sand",
|
|
5906
|
+
gravel: "gravel",
|
|
5907
|
+
turf: "turf",
|
|
5908
|
+
mortar: "mortar"
|
|
5418
5909
|
},
|
|
5419
5910
|
partStatus: { intact: "intact", damaged: "damaged", collapsed: "collapsed", broken: "broken", melted: "melted", ashes: "ashes" },
|
|
5420
5911
|
jointKinds: {
|
|
@@ -5467,6 +5958,7 @@ var locales = {
|
|
|
5467
5958
|
sway: "sway",
|
|
5468
5959
|
spall: "plaster came off",
|
|
5469
5960
|
stage: "damage stage",
|
|
5961
|
+
chip: "brick knocked out",
|
|
5470
5962
|
expose: "rebar bare",
|
|
5471
5963
|
body: "body",
|
|
5472
5964
|
blast: "blast",
|
|
@@ -5504,7 +5996,12 @@ var locales = {
|
|
|
5504
5996
|
cactus: "cacto",
|
|
5505
5997
|
flesh_fruit: "fruta",
|
|
5506
5998
|
wax: "cera",
|
|
5507
|
-
soil: "terra"
|
|
5999
|
+
soil: "terra",
|
|
6000
|
+
asphalt: "asfalto",
|
|
6001
|
+
sand: "areia",
|
|
6002
|
+
gravel: "brita",
|
|
6003
|
+
turf: "grama",
|
|
6004
|
+
mortar: "argamassa"
|
|
5508
6005
|
},
|
|
5509
6006
|
partStatus: { intact: "intacto", damaged: "danificado", collapsed: "desabado", broken: "quebrado", melted: "derretido", ashes: "cinzas" },
|
|
5510
6007
|
jointKinds: {
|
|
@@ -5557,6 +6054,7 @@ var locales = {
|
|
|
5557
6054
|
sway: "balan\xE7o",
|
|
5558
6055
|
spall: "reboco soltou",
|
|
5559
6056
|
stage: "est\xE1gio de dano",
|
|
6057
|
+
chip: "tijolo arrancado",
|
|
5560
6058
|
expose: "vergalh\xE3o \xE0 mostra",
|
|
5561
6059
|
body: "corpo",
|
|
5562
6060
|
blast: "explos\xE3o",
|