@adatechnology/game-destruction 0.15.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -1
- package/dist/index.d.ts +12 -1
- package/dist/index.global.js +436 -17
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +435 -17
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.global.js
CHANGED
|
@@ -452,6 +452,22 @@ var GameDestruction = (() => {
|
|
|
452
452
|
return angle === 0 ? q : quat.mul(q, quat.fromAxisAngle([0, 0, 1], angle));
|
|
453
453
|
};
|
|
454
454
|
var clamp = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
455
|
+
var splitAlong = (box, count, axis, rnd) => {
|
|
456
|
+
const shares = Array.from({ length: count }, () => 0.25 + 1.5 * rnd());
|
|
457
|
+
const total = shares.reduce((sum, share) => sum + share, 0);
|
|
458
|
+
const out = [];
|
|
459
|
+
let from = box.c[axis] - box.s[axis] / 2;
|
|
460
|
+
for (const share of shares) {
|
|
461
|
+
const length = box.s[axis] * share / total;
|
|
462
|
+
const c = [box.c[0], box.c[1], box.c[2]];
|
|
463
|
+
const size = [box.s[0], box.s[1], box.s[2]];
|
|
464
|
+
c[axis] = from + length / 2;
|
|
465
|
+
size[axis] = length;
|
|
466
|
+
out.push({ c, s: size });
|
|
467
|
+
from += length;
|
|
468
|
+
}
|
|
469
|
+
return out;
|
|
470
|
+
};
|
|
455
471
|
var splinterBox = (inp, box, limit = inp.maxFragments) => {
|
|
456
472
|
const { rnd } = inp;
|
|
457
473
|
const dims = box.s;
|
|
@@ -461,35 +477,81 @@ var GameDestruction = (() => {
|
|
|
461
477
|
const w = Math.max(fmin * 1.5, 0.01);
|
|
462
478
|
const c = clamp(Math.round(dims[a] * dims[b] / (w * w)), 3, Math.max(3, Math.floor(limit / 2)));
|
|
463
479
|
const maxLen = fmax * 4;
|
|
464
|
-
const cells =
|
|
480
|
+
const cells = [];
|
|
481
|
+
for (const cell of bsp(box, c, [a, b], rnd)) {
|
|
482
|
+
const wide = cell.s[a] >= cell.s[b] ? a : b;
|
|
483
|
+
if (rnd() < 0.4 && cell.s[wide] > fmin * 0.5) for (const part of splitAlong(cell, 2, wide, rnd)) cells.push(part);
|
|
484
|
+
else cells.push(cell);
|
|
485
|
+
}
|
|
465
486
|
const pieces = [];
|
|
466
487
|
for (const cell of cells) {
|
|
467
|
-
const extra = rnd()
|
|
488
|
+
const extra = Math.floor(rnd() * 3);
|
|
468
489
|
const k = Math.max(1, Math.ceil(dims[g] / maxLen)) + extra;
|
|
469
|
-
for (const p of
|
|
490
|
+
for (const p of splitAlong(cell, k, g, rnd)) pieces.push(p);
|
|
470
491
|
}
|
|
471
492
|
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] }));
|
|
472
493
|
};
|
|
494
|
+
var PLY = 3e-3;
|
|
495
|
+
var PLY_GAP = 0.6;
|
|
496
|
+
var PLY_SHEET = 0.012;
|
|
497
|
+
var plyBreak = (inp, root, limit) => {
|
|
498
|
+
const { rnd } = inp;
|
|
499
|
+
const dims = root.s;
|
|
500
|
+
const sorted = [0, 1, 2].sort((x, y) => dims[x] - dims[y]);
|
|
501
|
+
const t = sorted[0];
|
|
502
|
+
const [u, v] = [sorted[1], sorted[2]].sort((x, y) => x - y);
|
|
503
|
+
const along = dims[u] >= dims[v] ? u : v;
|
|
504
|
+
const across = along === u ? v : u;
|
|
505
|
+
const layers = clamp(Math.round(dims[t] / PLY / 2), 2, 3);
|
|
506
|
+
const pitch = dims[t] / layers;
|
|
507
|
+
const th = Math.min(pitch, PLY_SHEET);
|
|
508
|
+
const per = Math.max(2, Math.floor(limit / layers));
|
|
509
|
+
const out = [];
|
|
510
|
+
for (let k = 0; k < layers; k++) {
|
|
511
|
+
const layer = { c: [root.c[0], root.c[1], root.c[2]], s: [dims[0], dims[1], dims[2]] };
|
|
512
|
+
layer.c[t] = root.c[t] + (-dims[t] / 2 + (k + 0.5) * pitch) * (pitch > PLY_SHEET ? 1 : 1 + PLY_GAP);
|
|
513
|
+
layer.s[t] = th;
|
|
514
|
+
for (const cell of bsp(layer, per, [u, v], rnd)) {
|
|
515
|
+
if (out.length >= limit) return out;
|
|
516
|
+
if (rnd() < 0.35) {
|
|
517
|
+
const strand = cell.s[across] * (0.15 + 0.2 * rnd());
|
|
518
|
+
out.push({ center: cell.c, size: [cell.s[along], strand, th * 0.7], q: frame(along, across, t), shape: "splinter", weight: cell.s[along] * strand * th });
|
|
519
|
+
} else {
|
|
520
|
+
out.push({ center: cell.c, size: [cell.s[u], cell.s[v], th], q: frame(u, v, t, (rnd() - 0.5) * 0.5), shape: "scrap", weight: cell.s[u] * cell.s[v] * th });
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
return out;
|
|
525
|
+
};
|
|
526
|
+
var granules = (inp, box, count) => bsp(box, count, [0, 1, 2], inp.rnd).map((b) => ({ center: b.c, size: b.s, q: quat.identity(), shape: "chunk", weight: b.s[0] * b.s[1] * b.s[2] }));
|
|
527
|
+
var CRUMBS = 6;
|
|
473
528
|
var severAt = (inp, root, at, gap) => {
|
|
474
529
|
const dims = root.s;
|
|
475
530
|
const L = [0, 1, 2].sort((x, y) => dims[y] - dims[x])[0];
|
|
476
531
|
const half = dims[L] / 2;
|
|
477
532
|
const lo = clamp(at - gap / 2, -half, half), hi = clamp(at + gap / 2, -half, half);
|
|
478
533
|
const out = [];
|
|
479
|
-
const
|
|
534
|
+
const fibrous = inp.form === "slivers" || inp.form === "plies";
|
|
535
|
+
const [a, b] = [0, 1, 2].filter((i) => i !== L);
|
|
536
|
+
const stub = (from, to, breakAtEnd) => {
|
|
480
537
|
if (to - from < 0.02) return;
|
|
481
538
|
const c = [root.c[0], root.c[1], root.c[2]], sz = [dims[0], dims[1], dims[2]];
|
|
482
539
|
c[L] = (from + to) / 2;
|
|
483
540
|
sz[L] = to - from;
|
|
484
|
-
|
|
541
|
+
const weight = sz[0] * sz[1] * sz[2];
|
|
542
|
+
if (fibrous) out.push({ center: c, size: [sz[L], sz[a], sz[b]], q: frame(L, a, b, breakAtEnd ? 0 : Math.PI), shape: "slab", weight });
|
|
543
|
+
else out.push({ center: c, size: sz, q: quat.identity(), shape: "slab", weight });
|
|
485
544
|
};
|
|
486
|
-
stub(-half, lo);
|
|
487
|
-
stub(hi, half);
|
|
545
|
+
stub(-half, lo, true);
|
|
546
|
+
stub(hi, half, false);
|
|
488
547
|
const band = { c: [root.c[0], root.c[1], root.c[2]], s: [dims[0], dims[1], dims[2]] };
|
|
489
548
|
band.c[L] = (lo + hi) / 2;
|
|
490
549
|
band.s[L] = Math.max(hi - lo, 0.01);
|
|
491
|
-
|
|
492
|
-
|
|
550
|
+
const room = inp.maxFragments - out.length;
|
|
551
|
+
if (inp.style === "snap" && inp.form === "plies") for (const p of plyBreak(inp, band, Math.max(3, room))) out.push(p);
|
|
552
|
+
else if (inp.style === "snap" && inp.form === "granules") for (const p of granules(inp, band, clamp(room, 3, 12))) out.push(p);
|
|
553
|
+
else if (inp.style === "snap") {
|
|
554
|
+
for (const b2 of bsp(band, Math.max(3, Math.min(6, room)), [0, 1, 2], inp.rnd)) out.push({ center: b2.c, size: b2.s, q: quat.identity(), shape: "chunk", weight: b2.s[0] * b2.s[1] * b2.s[2] });
|
|
493
555
|
} else for (const p of splinterBox(inp, band, Math.max(3, inp.maxFragments - out.length))) out.push(p);
|
|
494
556
|
return out;
|
|
495
557
|
};
|
|
@@ -522,8 +584,18 @@ var GameDestruction = (() => {
|
|
|
522
584
|
const [u, v] = [sorted[1], sorted[2]].sort((a, b) => a - b);
|
|
523
585
|
if (style === "snap") {
|
|
524
586
|
if (inp.cut) return severAt(inp, root, inp.cut.at, inp.cut.gap);
|
|
587
|
+
if (inp.form === "plies") return plyBreak(inp, root, maxFragments);
|
|
525
588
|
const n2 = Math.min(maxFragments, 2 + Math.floor(rnd() * 3));
|
|
526
589
|
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] });
|
|
590
|
+
if (inp.form !== "granules") return out;
|
|
591
|
+
const crumbs = Math.min(CRUMBS, maxFragments - out.length);
|
|
592
|
+
if (crumbs <= 0) return out;
|
|
593
|
+
const band = { c: [0, 0, 0], s: [0, 0, 0] };
|
|
594
|
+
for (let i = 0; i < 3; i++) {
|
|
595
|
+
band.s[i] = Math.min(dims[i], Math.max(fmin, dims[i] * 0.2));
|
|
596
|
+
band.c[i] = clamp(inp.hit[i], -dims[i] / 2 + band.s[i] / 2, dims[i] / 2 - band.s[i] / 2);
|
|
597
|
+
}
|
|
598
|
+
for (const p of granules(inp, band, crumbs)) out.push({ ...p, weight: p.weight * 0.5 });
|
|
527
599
|
return out;
|
|
528
600
|
}
|
|
529
601
|
if (style === "crumble" || style === "none") {
|
|
@@ -632,6 +704,88 @@ var GameDestruction = (() => {
|
|
|
632
704
|
}
|
|
633
705
|
return marks;
|
|
634
706
|
};
|
|
707
|
+
var JOINT_GAP = 1e-4;
|
|
708
|
+
var MIN_BEARING_SHARE = 0.25;
|
|
709
|
+
var unsupportedCells = (cells, thin, dims, standing) => {
|
|
710
|
+
if (thin === 1) return [];
|
|
711
|
+
const [u, v] = planeAxes(thin);
|
|
712
|
+
const bottom = -dims[v] / 2;
|
|
713
|
+
const rowOf = (y) => Math.round((y - bottom) * 1e4);
|
|
714
|
+
const rows = /* @__PURE__ */ new Map();
|
|
715
|
+
for (let i = 0; i < cells.length; i++) {
|
|
716
|
+
if (!standing(i)) continue;
|
|
717
|
+
const cell = cells[i];
|
|
718
|
+
const key = rowOf(cell.center[v] - cell.size[v] / 2);
|
|
719
|
+
const row = rows.get(key);
|
|
720
|
+
if (row) row.push(i);
|
|
721
|
+
else rows.set(key, [i]);
|
|
722
|
+
}
|
|
723
|
+
const held = /* @__PURE__ */ new Set();
|
|
724
|
+
const queue = [];
|
|
725
|
+
for (const i of rows.get(0) ?? []) {
|
|
726
|
+
held.add(i);
|
|
727
|
+
queue.push(i);
|
|
728
|
+
}
|
|
729
|
+
const overlap = (a, b) => Math.min(a.center[u] + a.size[u] / 2, b.center[u] + b.size[u] / 2) - Math.max(a.center[u] - a.size[u] / 2, b.center[u] - b.size[u] / 2);
|
|
730
|
+
const climb = () => {
|
|
731
|
+
while (queue.length > 0) {
|
|
732
|
+
const cell = cells[queue.pop()];
|
|
733
|
+
for (const j of rows.get(rowOf(cell.center[v] + cell.size[v] / 2)) ?? []) {
|
|
734
|
+
const other = cells[j];
|
|
735
|
+
if (held.has(j) || overlap(cell, other) <= Math.max(JOINT_GAP, MIN_BEARING_SHARE * Math.min(cell.size[u], other.size[u]))) continue;
|
|
736
|
+
held.add(j);
|
|
737
|
+
queue.push(j);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
for (const row of rows.values()) row.sort((a, b) => cells[a].center[u] - cells[b].center[u]);
|
|
742
|
+
const touching = (a, b) => Math.abs(a.center[u] + a.size[u] / 2 - (b.center[u] - b.size[u] / 2)) <= 1e-3;
|
|
743
|
+
for (let wedged = true; wedged; ) {
|
|
744
|
+
climb();
|
|
745
|
+
wedged = false;
|
|
746
|
+
for (const row of rows.values()) {
|
|
747
|
+
for (let k = 1; k < row.length - 1; k++) {
|
|
748
|
+
const i = row[k];
|
|
749
|
+
if (held.has(i) || !held.has(row[k - 1]) || !held.has(row[k + 1])) continue;
|
|
750
|
+
if (!touching(cells[row[k - 1]], cells[i]) || !touching(cells[i], cells[row[k + 1]])) continue;
|
|
751
|
+
held.add(i);
|
|
752
|
+
queue.push(i);
|
|
753
|
+
wedged = true;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
const loose = [];
|
|
758
|
+
for (const row of rows.values()) for (const i of row) if (!held.has(i)) loose.push(i);
|
|
759
|
+
return loose;
|
|
760
|
+
};
|
|
761
|
+
var clumpCells = (cells, thin, cap) => {
|
|
762
|
+
if (cells.length <= cap) return [...cells];
|
|
763
|
+
const [u, v] = planeAxes(thin);
|
|
764
|
+
let lo0 = Infinity, lo1 = Infinity, hi0 = -Infinity, hi1 = -Infinity;
|
|
765
|
+
for (const cell of cells) {
|
|
766
|
+
lo0 = Math.min(lo0, cell.center[u] - cell.size[u] / 2);
|
|
767
|
+
hi0 = Math.max(hi0, cell.center[u] + cell.size[u] / 2);
|
|
768
|
+
lo1 = Math.min(lo1, cell.center[v] - cell.size[v] / 2);
|
|
769
|
+
hi1 = Math.max(hi1, cell.center[v] + cell.size[v] / 2);
|
|
770
|
+
}
|
|
771
|
+
let side = Math.sqrt((hi0 - lo0) * (hi1 - lo1) / cap);
|
|
772
|
+
for (; ; ) {
|
|
773
|
+
const bins = /* @__PURE__ */ new Map();
|
|
774
|
+
for (const cell of cells) {
|
|
775
|
+
const key = `${Math.floor((cell.center[u] - lo0) / side)}:${Math.floor((cell.center[v] - lo1) / side)}`;
|
|
776
|
+
const bin = bins.get(key) ?? { lo: [Infinity, Infinity, Infinity], hi: [-Infinity, -Infinity, -Infinity] };
|
|
777
|
+
for (let k = 0; k < 3; k++) {
|
|
778
|
+
bin.lo[k] = Math.min(bin.lo[k], cell.center[k] - cell.size[k] / 2);
|
|
779
|
+
bin.hi[k] = Math.max(bin.hi[k], cell.center[k] + cell.size[k] / 2);
|
|
780
|
+
}
|
|
781
|
+
bins.set(key, bin);
|
|
782
|
+
}
|
|
783
|
+
if (bins.size <= cap) {
|
|
784
|
+
return [...bins.values()].map(({ lo, hi }) => ({ center: [(lo[0] + hi[0]) / 2, (lo[1] + hi[1]) / 2, (lo[2] + hi[2]) / 2], size: [hi[0] - lo[0], hi[1] - lo[1], hi[2] - lo[2]] }));
|
|
785
|
+
}
|
|
786
|
+
side *= 1.25;
|
|
787
|
+
}
|
|
788
|
+
};
|
|
635
789
|
|
|
636
790
|
// src/layers.ts
|
|
637
791
|
var MAX_SPALLS = 64;
|
|
@@ -1672,6 +1826,7 @@ var GameDestruction = (() => {
|
|
|
1672
1826
|
},
|
|
1673
1827
|
{
|
|
1674
1828
|
id: "wood_soft",
|
|
1829
|
+
splitForm: "slivers",
|
|
1675
1830
|
density: 500,
|
|
1676
1831
|
hpPerKg: 90,
|
|
1677
1832
|
penetration: 9,
|
|
@@ -1717,6 +1872,7 @@ var GameDestruction = (() => {
|
|
|
1717
1872
|
},
|
|
1718
1873
|
{
|
|
1719
1874
|
id: "wood_hard",
|
|
1875
|
+
splitForm: "slivers",
|
|
1720
1876
|
density: 750,
|
|
1721
1877
|
hpPerKg: 140,
|
|
1722
1878
|
penetration: 14,
|
|
@@ -1761,6 +1917,7 @@ var GameDestruction = (() => {
|
|
|
1761
1917
|
},
|
|
1762
1918
|
{
|
|
1763
1919
|
id: "plywood",
|
|
1920
|
+
splitForm: "plies",
|
|
1764
1921
|
density: 600,
|
|
1765
1922
|
hpPerKg: 70,
|
|
1766
1923
|
penetration: 7,
|
|
@@ -1808,6 +1965,7 @@ var GameDestruction = (() => {
|
|
|
1808
1965
|
},
|
|
1809
1966
|
{
|
|
1810
1967
|
id: "mdf",
|
|
1968
|
+
splitForm: "granules",
|
|
1811
1969
|
density: 750,
|
|
1812
1970
|
hpPerKg: 45,
|
|
1813
1971
|
penetration: 6,
|
|
@@ -2319,6 +2477,7 @@ var GameDestruction = (() => {
|
|
|
2319
2477
|
},
|
|
2320
2478
|
{
|
|
2321
2479
|
id: "green_wood",
|
|
2480
|
+
splitForm: "slivers",
|
|
2322
2481
|
density: 650,
|
|
2323
2482
|
hpPerKg: 5,
|
|
2324
2483
|
penetration: 10,
|
|
@@ -2599,6 +2758,125 @@ var GameDestruction = (() => {
|
|
|
2599
2758
|
"brittle"
|
|
2600
2759
|
],
|
|
2601
2760
|
compressive: 1e5
|
|
2761
|
+
},
|
|
2762
|
+
{
|
|
2763
|
+
id: "mud",
|
|
2764
|
+
density: 1700,
|
|
2765
|
+
hpPerKg: 1,
|
|
2766
|
+
penetration: 5,
|
|
2767
|
+
ductility: 0.8,
|
|
2768
|
+
fracture: "crumble",
|
|
2769
|
+
fragmentSize: [
|
|
2770
|
+
0.02,
|
|
2771
|
+
0.08
|
|
2772
|
+
],
|
|
2773
|
+
hitDebris: "dust",
|
|
2774
|
+
breakDebris: "dust",
|
|
2775
|
+
thermal: {
|
|
2776
|
+
conductivity: 0.5,
|
|
2777
|
+
heatCapacity: 3.2
|
|
2778
|
+
},
|
|
2779
|
+
tags: [
|
|
2780
|
+
"soil",
|
|
2781
|
+
"wet"
|
|
2782
|
+
],
|
|
2783
|
+
compressive: 5e3
|
|
2784
|
+
},
|
|
2785
|
+
{
|
|
2786
|
+
id: "clay",
|
|
2787
|
+
density: 1800,
|
|
2788
|
+
hpPerKg: 5,
|
|
2789
|
+
penetration: 7,
|
|
2790
|
+
ductility: 0.05,
|
|
2791
|
+
fracture: "crumble",
|
|
2792
|
+
fragmentSize: [
|
|
2793
|
+
0.02,
|
|
2794
|
+
0.1
|
|
2795
|
+
],
|
|
2796
|
+
hitDebris: "chips",
|
|
2797
|
+
breakDebris: "dust",
|
|
2798
|
+
thermal: {
|
|
2799
|
+
conductivity: 0.15,
|
|
2800
|
+
heatCapacity: 2.2
|
|
2801
|
+
},
|
|
2802
|
+
tags: [
|
|
2803
|
+
"soil",
|
|
2804
|
+
"brittle"
|
|
2805
|
+
],
|
|
2806
|
+
compressive: 6e4
|
|
2807
|
+
},
|
|
2808
|
+
{
|
|
2809
|
+
id: "snow",
|
|
2810
|
+
density: 300,
|
|
2811
|
+
hpPerKg: 1,
|
|
2812
|
+
penetration: 3,
|
|
2813
|
+
ductility: 0.6,
|
|
2814
|
+
fracture: "crumble",
|
|
2815
|
+
fragmentSize: [
|
|
2816
|
+
0.01,
|
|
2817
|
+
0.05
|
|
2818
|
+
],
|
|
2819
|
+
hitDebris: "dust",
|
|
2820
|
+
breakDebris: "dust",
|
|
2821
|
+
thermal: {
|
|
2822
|
+
conductivity: 0.1,
|
|
2823
|
+
heatCapacity: 2.1
|
|
2824
|
+
},
|
|
2825
|
+
tags: [
|
|
2826
|
+
"soil",
|
|
2827
|
+
"frozen"
|
|
2828
|
+
],
|
|
2829
|
+
compressive: 1e3
|
|
2830
|
+
},
|
|
2831
|
+
{
|
|
2832
|
+
id: "cobble",
|
|
2833
|
+
density: 2650,
|
|
2834
|
+
hpPerKg: 25,
|
|
2835
|
+
penetration: 25,
|
|
2836
|
+
penetrationClass: "concrete",
|
|
2837
|
+
penetrationScale: 0.9,
|
|
2838
|
+
ductility: 0.03,
|
|
2839
|
+
fracture: "crumble",
|
|
2840
|
+
fragmentSize: [
|
|
2841
|
+
0.05,
|
|
2842
|
+
0.15
|
|
2843
|
+
],
|
|
2844
|
+
hitDebris: "chips",
|
|
2845
|
+
breakDebris: "chips",
|
|
2846
|
+
thermal: {
|
|
2847
|
+
conductivity: 0.2,
|
|
2848
|
+
heatCapacity: 2.5
|
|
2849
|
+
},
|
|
2850
|
+
tags: [
|
|
2851
|
+
"masonry",
|
|
2852
|
+
"brittle"
|
|
2853
|
+
],
|
|
2854
|
+
compressive: 3e5
|
|
2855
|
+
},
|
|
2856
|
+
{
|
|
2857
|
+
id: "paver",
|
|
2858
|
+
density: 2300,
|
|
2859
|
+
hpPerKg: 14,
|
|
2860
|
+
penetration: 18,
|
|
2861
|
+
penetrationClass: "concrete",
|
|
2862
|
+
penetrationScale: 1,
|
|
2863
|
+
ductility: 0.03,
|
|
2864
|
+
fracture: "crumble",
|
|
2865
|
+
fragmentSize: [
|
|
2866
|
+
0.04,
|
|
2867
|
+
0.12
|
|
2868
|
+
],
|
|
2869
|
+
hitDebris: "chips",
|
|
2870
|
+
breakDebris: "chips",
|
|
2871
|
+
thermal: {
|
|
2872
|
+
conductivity: 0.15,
|
|
2873
|
+
heatCapacity: 2.5
|
|
2874
|
+
},
|
|
2875
|
+
tags: [
|
|
2876
|
+
"masonry",
|
|
2877
|
+
"brittle"
|
|
2878
|
+
],
|
|
2879
|
+
compressive: 35e4
|
|
2602
2880
|
}
|
|
2603
2881
|
];
|
|
2604
2882
|
|
|
@@ -2782,6 +3060,7 @@ var GameDestruction = (() => {
|
|
|
2782
3060
|
};
|
|
2783
3061
|
|
|
2784
3062
|
// ../game-contracts/dist/index.js
|
|
3063
|
+
var DEFAULT_BLAST_IMPULSE = { bodyArea: 0.7, lift: 0.15, minSpeed: 0.3, tntPerJoule: 0.24 / 2e5 };
|
|
2785
3064
|
var DEFAULT_COST_READS = 1e4;
|
|
2786
3065
|
var DEFAULT_COST_CEILING_MS = 50;
|
|
2787
3066
|
var DEFAULT_COST_ALLOCATION_BYTES = 1024 * 1024;
|
|
@@ -3102,6 +3381,11 @@ var GameDestruction = (() => {
|
|
|
3102
3381
|
var BREACH_BLAST_FACTOR = 6;
|
|
3103
3382
|
var BREACH_AREA = 0.25;
|
|
3104
3383
|
var BREACH_BLAST_REACH = 0.12;
|
|
3384
|
+
var MEMBER_CUT_REACH = 0.5;
|
|
3385
|
+
var BLAST_REFERENCE_RESISTANCE = 200 * 1900 * 0.13;
|
|
3386
|
+
var BLAST_EDGE_RAGGED = 0.3;
|
|
3387
|
+
var BLAST_EDGE_LOOSE = 1.35;
|
|
3388
|
+
var BLAST_EDGE_WEAR = 0.6;
|
|
3105
3389
|
var RESIDUAL_CAPACITY = 0.15;
|
|
3106
3390
|
var SEVER_BAND = 0.1;
|
|
3107
3391
|
var SEVER_ASPECT = 3;
|
|
@@ -4354,7 +4638,7 @@ var GameDestruction = (() => {
|
|
|
4354
4638
|
if (j.st.open) continue;
|
|
4355
4639
|
if (j.spec.kind === "rebar" && (isReinforced(o.parts[j.ia].spec) || isReinforced(o.parts[j.ib].spec))) continue;
|
|
4356
4640
|
if (near && jointReach(o, j, near) > Math.max(radius, jointReachLimit(o, j))) continue;
|
|
4357
|
-
j.st.hp -= span === void 0 ? amount : amount * Math.min(1, span / Math.max(jointLength(o, j), 1e-6));
|
|
4641
|
+
j.st.hp -= span === void 0 ? amount : Math.min(amount, j.st.maxHp) * Math.min(1, span / Math.max(jointLength(o, j), 1e-6));
|
|
4358
4642
|
if (j.st.hp <= 0) {
|
|
4359
4643
|
j.st.hp = 0;
|
|
4360
4644
|
j.st.open = true;
|
|
@@ -4482,6 +4766,7 @@ var GameDestruction = (() => {
|
|
|
4482
4766
|
const worn = (_a = p.rt).chips ?? (_a.chips = {});
|
|
4483
4767
|
const cut = (_b = p.rt).craters ?? (_b.craters = {});
|
|
4484
4768
|
const out = [];
|
|
4769
|
+
const wholeBefore = Object.values(worn).filter((w) => w === -1).length;
|
|
4485
4770
|
const shot = kind === "ballistic";
|
|
4486
4771
|
for (let i = 0; i < grid.length; i++) {
|
|
4487
4772
|
const cell = grid[i];
|
|
@@ -4540,6 +4825,7 @@ var GameDestruction = (() => {
|
|
|
4540
4825
|
emit({ ...base(o, src), type: "chip", part: p.spec.id, center, size: [cell.size[0], cell.size[1], cell.size[2]], rotation: q, fragments });
|
|
4541
4826
|
debris(o, p, p.mat.breakDebris, center, vec.scale(dir ?? [0, 1, 0], -1), 6, 2, src);
|
|
4542
4827
|
}
|
|
4828
|
+
if (Object.values(worn).filter((w) => w === -1).length > wholeBefore) dropUnsupported(o, p, src);
|
|
4543
4829
|
if (p.spec.reinforcement) exposeRebar(o, p, src);
|
|
4544
4830
|
for (const cell of out) addBreach(o, p, toWorld(c, q, cell.center), cell.size[u] * cell.size[v], energy, dir, src);
|
|
4545
4831
|
if (p.structural && !isLost(p)) {
|
|
@@ -4643,10 +4929,122 @@ var GameDestruction = (() => {
|
|
|
4643
4929
|
setStage(o, p, 1, {}, src);
|
|
4644
4930
|
setStage(o, p, 2, { opening: { center: worldCenter, radius }, fragments }, src);
|
|
4645
4931
|
debris(o, p, p.mat.breakDebris, worldCenter, vec.scale(dir ?? [0, 1, 0], -1), 14, 3, src);
|
|
4932
|
+
dropUnsupported(o, p, src);
|
|
4646
4933
|
const reinforcement = p.spec.reinforcement;
|
|
4647
4934
|
if (reinforcement) exposeRebar(o, p, src);
|
|
4648
4935
|
if (p.structural) solveStructure(o);
|
|
4649
4936
|
};
|
|
4937
|
+
const blastReachOf = (p, energy) => {
|
|
4938
|
+
const perKg = p.mat.hits?.blunt?.unitJoules ?? chipEnergy;
|
|
4939
|
+
const resistance = Math.max(1, perKg * p.mat.density * p.thickness);
|
|
4940
|
+
return BREACH_BLAST_REACH * Math.cbrt(Math.max(0, energy) / 1e3) * clamp2(Math.cbrt(BLAST_REFERENCE_RESISTANCE / resistance), 0.3, 2);
|
|
4941
|
+
};
|
|
4942
|
+
const blastUnits = (o, p, wp, energy, dist, src) => {
|
|
4943
|
+
var _a, _b;
|
|
4944
|
+
if (!p.staged || p.st.stripped || isLost(p) || chipEnergy <= 0) return false;
|
|
4945
|
+
const reach = blastReachOf(p, energy);
|
|
4946
|
+
if (dist >= reach) return false;
|
|
4947
|
+
const radius = Math.sqrt(reach * reach - dist * dist);
|
|
4948
|
+
const [u, v] = planeAxes(p.thin);
|
|
4949
|
+
const lp = toLocal(p.pos, p.q, objLocal(o, wp));
|
|
4950
|
+
const op = p.st.opening ? toLocal(p.pos, p.q, p.st.opening.center) : null;
|
|
4951
|
+
const grid = gridOf(p);
|
|
4952
|
+
const worn = (_a = p.rt).chips ?? (_a.chips = {});
|
|
4953
|
+
const perKg = p.mat.hits?.blunt?.unitJoules ?? chipEnergy;
|
|
4954
|
+
const lobes = [rnd() * 2 * Math.PI, rnd() * 2 * Math.PI];
|
|
4955
|
+
const out = [];
|
|
4956
|
+
for (let i = 0; i < grid.length; i++) {
|
|
4957
|
+
const cell = grid[i];
|
|
4958
|
+
if (worn[i] === -1) continue;
|
|
4959
|
+
if (op && p.st.opening && Math.hypot(cell.center[u] - op[u], cell.center[v] - op[v]) <= p.st.opening.radius) continue;
|
|
4960
|
+
const du = cell.center[u] - lp[u], dv = cell.center[v] - lp[v];
|
|
4961
|
+
const angle = Math.atan2(dv, du);
|
|
4962
|
+
const edge = radius * (1 + BLAST_EDGE_RAGGED * (0.5 * Math.sin(3 * angle + lobes[0]) + 0.3 * Math.sin(5 * angle + lobes[1]) + 0.4 * (rnd() - 0.5)));
|
|
4963
|
+
const d = Math.hypot(du, dv);
|
|
4964
|
+
if (d <= edge) {
|
|
4965
|
+
worn[i] = -1;
|
|
4966
|
+
out.push(i);
|
|
4967
|
+
} else if (d <= edge * BLAST_EDGE_LOOSE) {
|
|
4968
|
+
const mass = p.mat.density * cell.size[0] * cell.size[1] * cell.size[2];
|
|
4969
|
+
worn[i] = Math.max(worn[i] ?? 0, perKg * mass * BLAST_EDGE_WEAR);
|
|
4970
|
+
}
|
|
4971
|
+
}
|
|
4972
|
+
if (out.length === 0) return false;
|
|
4973
|
+
const loose = unsupportedCells(grid, p.thin, p.dims, (i) => worn[i] !== -1 && !(op && p.st.opening && Math.hypot(grid[i].center[u] - op[u], grid[i].center[v] - op[v]) <= p.st.opening.radius));
|
|
4974
|
+
for (const i of loose) worn[i] = -1;
|
|
4975
|
+
const blown = out.map((i) => grid[i]), fallen = loose.map((i) => grid[i]);
|
|
4976
|
+
const volume = (cells2) => cells2.reduce((sum, cell) => sum + cell.size[0] * cell.size[1] * cell.size[2], 0);
|
|
4977
|
+
const blownMass = Math.min(p.st.mass, p.mat.density * volume(blown));
|
|
4978
|
+
const fallenMass = Math.min(p.st.mass - blownMass, p.mat.density * volume(fallen));
|
|
4979
|
+
const speed = clamp2(Math.sqrt(2 * energy * 0.02 / Math.max(blownMass, 0.05)), 1, 12);
|
|
4980
|
+
const { c, q } = partXf(o, p);
|
|
4981
|
+
const chipped = (_b = p.st).chipped ?? (_b.chipped = []);
|
|
4982
|
+
const tag = `x${chipped.length}_`;
|
|
4983
|
+
const fragments = [
|
|
4984
|
+
...cellFragments(o, p, clumpCells(blown, p.thin, maxFragments), wp, null, speed, blownMass, tag, true),
|
|
4985
|
+
...fallen.length > 0 ? cellFragments(o, p, clumpCells(fallen, p.thin, Math.max(2, Math.round(maxFragments / 4))), wp, null, 0.3, fallenMass, `${tag}f`, true) : []
|
|
4986
|
+
];
|
|
4987
|
+
const cells = [...blown, ...fallen];
|
|
4988
|
+
const holes = localHolesOf(p);
|
|
4989
|
+
p.st.holes = p.st.holes.filter((_, k) => !cells.some((cell) => marksInBox(cell.center, cell.size, void 0, [holes[k]]).length > 0));
|
|
4990
|
+
const lo = [Infinity, Infinity, Infinity], hi = [-Infinity, -Infinity, -Infinity];
|
|
4991
|
+
for (const cell of cells) {
|
|
4992
|
+
chipped.push({ center: [cell.center[0], cell.center[1], cell.center[2]], size: [cell.size[0], cell.size[1], cell.size[2]] });
|
|
4993
|
+
for (let k = 0; k < 3; k++) {
|
|
4994
|
+
lo[k] = Math.min(lo[k], cell.center[k] - cell.size[k] / 2);
|
|
4995
|
+
hi[k] = Math.max(hi[k], cell.center[k] + cell.size[k] / 2);
|
|
4996
|
+
}
|
|
4997
|
+
}
|
|
4998
|
+
p.st.mass = Math.max(0, p.st.mass - blownMass - fallenMass);
|
|
4999
|
+
p.rt.removed = (p.rt.removed ?? 0) + blownMass + fallenMass;
|
|
5000
|
+
markDamaged(p);
|
|
5001
|
+
setStage(o, p, 1, {}, src);
|
|
5002
|
+
const center = toWorld(c, q, [(lo[0] + hi[0]) / 2, (lo[1] + hi[1]) / 2, (lo[2] + hi[2]) / 2]);
|
|
5003
|
+
const units = cells.map((cell) => ({ center: toWorld(c, q, cell.center), size: [cell.size[0], cell.size[1], cell.size[2]] }));
|
|
5004
|
+
emit({ ...base(o, src), type: "chip", part: p.spec.id, center, size: [hi[0] - lo[0], hi[1] - lo[1], hi[2] - lo[2]], rotation: q, fragments, units });
|
|
5005
|
+
debris(o, p, p.mat.breakDebris, center, vec.normalize(vec.sub(center, wp), [0, 1, 0]), 14, 3, src);
|
|
5006
|
+
if (p.spec.reinforcement) exposeRebar(o, p, src);
|
|
5007
|
+
if (p.structural && !isLost(p)) {
|
|
5008
|
+
solveStructure(o);
|
|
5009
|
+
collapseCheck(o, src);
|
|
5010
|
+
}
|
|
5011
|
+
return true;
|
|
5012
|
+
};
|
|
5013
|
+
const dropUnsupported = (o, p, src) => {
|
|
5014
|
+
var _a, _b;
|
|
5015
|
+
if (!p.spec.units || p.spec.units.mortar === p.spec.material || p.st.stripped || isLost(p)) return 0;
|
|
5016
|
+
const [u, v] = planeAxes(p.thin);
|
|
5017
|
+
const grid = gridOf(p);
|
|
5018
|
+
const worn = (_a = p.rt).chips ?? (_a.chips = {});
|
|
5019
|
+
const op = p.st.opening ? toLocal(p.pos, p.q, p.st.opening.center) : null;
|
|
5020
|
+
const opened = (cell) => !!op && !!p.st.opening && Math.hypot(cell.center[u] - op[u], cell.center[v] - op[v]) <= p.st.opening.radius;
|
|
5021
|
+
const loose = unsupportedCells(grid, p.thin, p.dims, (i) => worn[i] !== -1 && !opened(grid[i]));
|
|
5022
|
+
if (loose.length === 0) return 0;
|
|
5023
|
+
for (const i of loose) worn[i] = -1;
|
|
5024
|
+
const fallen = loose.map((i) => grid[i]);
|
|
5025
|
+
const mass = Math.min(p.st.mass, p.mat.density * fallen.reduce((sum, cell) => sum + cell.size[0] * cell.size[1] * cell.size[2], 0));
|
|
5026
|
+
const { c, q } = partXf(o, p);
|
|
5027
|
+
const chipped = (_b = p.st).chipped ?? (_b.chipped = []);
|
|
5028
|
+
const lo = [Infinity, Infinity, Infinity], hi = [-Infinity, -Infinity, -Infinity];
|
|
5029
|
+
for (const cell of fallen) {
|
|
5030
|
+
for (let k = 0; k < 3; k++) {
|
|
5031
|
+
lo[k] = Math.min(lo[k], cell.center[k] - cell.size[k] / 2);
|
|
5032
|
+
hi[k] = Math.max(hi[k], cell.center[k] + cell.size[k] / 2);
|
|
5033
|
+
}
|
|
5034
|
+
}
|
|
5035
|
+
const center = toWorld(c, q, [(lo[0] + hi[0]) / 2, (lo[1] + hi[1]) / 2, (lo[2] + hi[2]) / 2]);
|
|
5036
|
+
const fragments = cellFragments(o, p, clumpCells(fallen, p.thin, Math.max(2, Math.round(maxFragments / 4))), vec.add(center, [0, 1, 0]), null, 0.3, mass, `u${chipped.length}_`, true);
|
|
5037
|
+
const holes = localHolesOf(p);
|
|
5038
|
+
p.st.holes = p.st.holes.filter((_, k) => !fallen.some((cell) => marksInBox(cell.center, cell.size, void 0, [holes[k]]).length > 0));
|
|
5039
|
+
for (const cell of fallen) chipped.push({ center: [cell.center[0], cell.center[1], cell.center[2]], size: [cell.size[0], cell.size[1], cell.size[2]] });
|
|
5040
|
+
p.st.mass = Math.max(0, p.st.mass - mass);
|
|
5041
|
+
p.rt.removed = (p.rt.removed ?? 0) + mass;
|
|
5042
|
+
markDamaged(p);
|
|
5043
|
+
const units = fallen.map((cell) => ({ center: toWorld(c, q, cell.center), size: [cell.size[0], cell.size[1], cell.size[2]] }));
|
|
5044
|
+
emit({ ...base(o, src), type: "chip", part: p.spec.id, center, size: [hi[0] - lo[0], hi[1] - lo[1], hi[2] - lo[2]], rotation: q, fragments, units });
|
|
5045
|
+
debris(o, p, p.mat.breakDebris, center, [0, -1, 0], 6, 1, src);
|
|
5046
|
+
return loose.length;
|
|
5047
|
+
};
|
|
4650
5048
|
const stripPart = (o, p, reinforcement, src) => {
|
|
4651
5049
|
const bars = rebarBars(p.dims, p.thin, reinforcement.spacing);
|
|
4652
5050
|
p.st.stripped = true;
|
|
@@ -4713,6 +5111,7 @@ var GameDestruction = (() => {
|
|
|
4713
5111
|
const unitPlan = p.spec.units ? unitFragmentPlan(p, maxFragments * UNIT_FRAGMENT_FACTOR) : [];
|
|
4714
5112
|
const axis = style === "splinter" || style === "snap" ? severAxis(p) : -1;
|
|
4715
5113
|
const cutAt = axis < 0 ? void 0 : p.rt.cutAt ?? (origin ? hitLocal[axis] : void 0);
|
|
5114
|
+
const planRnd = p.mat.splitForm ? splitStream(o.id, p.spec.id, hitLocal) : rnd;
|
|
4716
5115
|
const plan = unitPlan.length > 0 ? unitPlan : planFragments({
|
|
4717
5116
|
style,
|
|
4718
5117
|
dims: p.dims,
|
|
@@ -4721,8 +5120,9 @@ var GameDestruction = (() => {
|
|
|
4721
5120
|
hit: hitLocal,
|
|
4722
5121
|
fragmentSize: p.mat.fragmentSize ?? [0.04, 0.2],
|
|
4723
5122
|
maxFragments,
|
|
4724
|
-
rnd,
|
|
5123
|
+
rnd: planRnd,
|
|
4725
5124
|
sheet: p.mat.sheet,
|
|
5125
|
+
...p.mat.splitForm ? { form: p.mat.splitForm } : {},
|
|
4726
5126
|
...cutAt !== void 0 ? { cut: { at: cutAt, gap: SEVER_BAND } } : {}
|
|
4727
5127
|
});
|
|
4728
5128
|
const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
|
|
@@ -4767,7 +5167,7 @@ var GameDestruction = (() => {
|
|
|
4767
5167
|
return;
|
|
4768
5168
|
}
|
|
4769
5169
|
if (ex) {
|
|
4770
|
-
emit({ ...base(o, src), type: "explode", part: p.spec.id, point: c, energy: ex.energy, radius: ex.radius, ...ex.lethalRadius !== void 0 ? { lethalRadius: ex.lethalRadius } : {}, ...ex.effect !== void 0 ? { effect: ex.effect } : {} });
|
|
5170
|
+
emit({ ...base(o, src), type: "explode", part: p.spec.id, point: c, energy: ex.energy, radius: ex.radius, ...ex.lethalRadius !== void 0 ? { lethalRadius: ex.lethalRadius } : {}, ...ex.tnt !== void 0 ? { tnt: ex.tnt } : {}, ...ex.effect !== void 0 ? { effect: ex.effect } : {} });
|
|
4771
5171
|
blasts.push({ point: c, energy: ex.energy, radius: ex.radius, ...ex.lethalRadius !== void 0 ? { lethalRadius: ex.lethalRadius } : {}, source: src });
|
|
4772
5172
|
}
|
|
4773
5173
|
partLost(o, p, src);
|
|
@@ -4861,7 +5261,8 @@ var GameDestruction = (() => {
|
|
|
4861
5261
|
const fragments = [];
|
|
4862
5262
|
for (let i = 0; i < n; i++) {
|
|
4863
5263
|
const back = !exit || i % 3 === 2;
|
|
4864
|
-
const
|
|
5264
|
+
const plies = p.mat.splitForm === "plies";
|
|
5265
|
+
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() : plies ? 0.15 + 0.15 * rnd() : 0.4 + 0.4 * rnd());
|
|
4865
5266
|
const size = [w, w, w];
|
|
4866
5267
|
size[along] = len;
|
|
4867
5268
|
size[(along + 1) % 3] = w;
|
|
@@ -5022,7 +5423,7 @@ var GameDestruction = (() => {
|
|
|
5022
5423
|
checkIgnite(o, p, src);
|
|
5023
5424
|
}
|
|
5024
5425
|
}
|
|
5025
|
-
wearJoints(o, p, Ein * 0.5, void 0, 0, src);
|
|
5426
|
+
wearJoints(o, p, Ein * 0.5, void 0, 0, src, false, 2 * BREACH_BLAST_REACH * Math.cbrt(Math.max(0, energy) / 1e3));
|
|
5026
5427
|
const amount = Ein * DMG_PER_J * (mat.damageScale?.explosive ?? 1);
|
|
5027
5428
|
if (blastFrontSpeed > 0 && dist > 0 && mat.tags?.includes("glass")) {
|
|
5028
5429
|
frontQueue.push({ o, p, due: time + dist / blastFrontSpeed, amount, fragEnergy: Ein * 0.3, point, ...src !== void 0 ? { src } : {} });
|
|
@@ -5032,7 +5433,10 @@ var GameDestruction = (() => {
|
|
|
5032
5433
|
const dealt = applyDamage(o, p, amount, "explosive", point, null, Ein * 0.3, 40, src);
|
|
5033
5434
|
res.damage += dealt;
|
|
5034
5435
|
if (p.bend && flexure.enabled) bendByBlast(o, p, point, energy, radius, reach, src);
|
|
5035
|
-
|
|
5436
|
+
const breachReach = BREACH_BLAST_REACH * Math.cbrt(Math.max(0, energy) / 1e3);
|
|
5437
|
+
if (p.spec.units && p.spec.units.mortar !== p.spec.material) blastUnits(o, p, wp, energy, dist, src);
|
|
5438
|
+
else if (p.spec.reinforcement && p.spec.units && dist > MEMBER_CUT_REACH * breachReach) blastUnits(o, p, wp, energy, dist, src);
|
|
5439
|
+
else if (dist <= breachReach) addBreach(o, p, wp, breachAreaOf(p, dealt, true), Ein, null, src);
|
|
5036
5440
|
return reach;
|
|
5037
5441
|
};
|
|
5038
5442
|
const blastProbe = { partsTested: 0, partsNear: 0 };
|
|
@@ -5278,6 +5682,11 @@ var GameDestruction = (() => {
|
|
|
5278
5682
|
}
|
|
5279
5683
|
return h | 0;
|
|
5280
5684
|
};
|
|
5685
|
+
const splitStream = (objectId, partId, hit) => {
|
|
5686
|
+
const codes = Array.from(`${objectId}|${partId}`, (char) => char.charCodeAt(0) / 1e3);
|
|
5687
|
+
const stream = createRng(seedOf(rng.state() / 1e3, ...codes, hit[0], hit[1], hit[2]));
|
|
5688
|
+
return () => stream.next();
|
|
5689
|
+
};
|
|
5281
5690
|
const throwFragment = (frag, objectId, from, g, centre, index, src, shields, results, reports, mult) => {
|
|
5282
5691
|
const v = frag.velocity;
|
|
5283
5692
|
const speed = Math.hypot(v[0], v[1], v[2]);
|
|
@@ -6539,7 +6948,12 @@ var GameDestruction = (() => {
|
|
|
6539
6948
|
sand: "sand",
|
|
6540
6949
|
gravel: "gravel",
|
|
6541
6950
|
turf: "turf",
|
|
6542
|
-
mortar: "mortar"
|
|
6951
|
+
mortar: "mortar",
|
|
6952
|
+
mud: "mud",
|
|
6953
|
+
clay: "dry clay",
|
|
6954
|
+
snow: "snow",
|
|
6955
|
+
cobble: "cobblestone",
|
|
6956
|
+
paver: "concrete paver"
|
|
6543
6957
|
},
|
|
6544
6958
|
partStatus: { intact: "intact", damaged: "damaged", collapsed: "collapsed", broken: "broken", melted: "melted", ashes: "ashes" },
|
|
6545
6959
|
jointKinds: {
|
|
@@ -6635,7 +7049,12 @@ var GameDestruction = (() => {
|
|
|
6635
7049
|
sand: "areia",
|
|
6636
7050
|
gravel: "brita",
|
|
6637
7051
|
turf: "grama",
|
|
6638
|
-
mortar: "argamassa"
|
|
7052
|
+
mortar: "argamassa",
|
|
7053
|
+
mud: "lama",
|
|
7054
|
+
clay: "barro seco",
|
|
7055
|
+
snow: "neve",
|
|
7056
|
+
cobble: "paralelep\xEDpedo",
|
|
7057
|
+
paver: "bloquete"
|
|
6639
7058
|
},
|
|
6640
7059
|
partStatus: { intact: "intacto", damaged: "danificado", collapsed: "desabado", broken: "quebrado", melted: "derretido", ashes: "cinzas" },
|
|
6641
7060
|
jointKinds: {
|