@adatechnology/game-floorplan-three 0.5.0 → 0.7.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 +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.global.js +661 -109
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +257 -76
- package/dist/index.js.map +1 -1
- package/package.json +11 -11
package/dist/index.global.js
CHANGED
|
@@ -11268,7 +11268,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
11268
11268
|
const band = { c: [root.c[0], root.c[1], root.c[2]], s: [dims[0], dims[1], dims[2]] };
|
|
11269
11269
|
band.c[L] = (lo + hi) / 2;
|
|
11270
11270
|
band.s[L] = Math.max(hi - lo, 0.01);
|
|
11271
|
-
|
|
11271
|
+
if (inp.style === "snap") {
|
|
11272
|
+
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] });
|
|
11273
|
+
} else for (const p of splinterBox(inp, band, Math.max(3, inp.maxFragments - out.length))) out.push(p);
|
|
11272
11274
|
return out;
|
|
11273
11275
|
};
|
|
11274
11276
|
var tearStack = (inp, root, t, u, v, n, sheet) => {
|
|
@@ -11299,6 +11301,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
11299
11301
|
const t = sorted[0];
|
|
11300
11302
|
const [u, v] = [sorted[1], sorted[2]].sort((a, b) => a - b);
|
|
11301
11303
|
if (style === "snap") {
|
|
11304
|
+
if (inp.cut) return severAt(inp, root, inp.cut.at, inp.cut.gap);
|
|
11302
11305
|
const n2 = Math.min(maxFragments, 2 + Math.floor(rnd() * 3));
|
|
11303
11306
|
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] });
|
|
11304
11307
|
return out;
|
|
@@ -11472,6 +11475,27 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
11472
11475
|
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]]));
|
|
11473
11476
|
return out;
|
|
11474
11477
|
};
|
|
11478
|
+
var clipBarsToBox = (bars, u, v, center, size) => {
|
|
11479
|
+
const out = [];
|
|
11480
|
+
for (const [a, b] of bars) {
|
|
11481
|
+
const axis = a[u] !== b[u] ? u : v;
|
|
11482
|
+
const other = axis === u ? v : u;
|
|
11483
|
+
if (Math.abs(a[other] - center[other]) > size[other] / 2) continue;
|
|
11484
|
+
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);
|
|
11485
|
+
if (hi - lo < 1e-6) continue;
|
|
11486
|
+
const p = [a[0], a[1], a[2]], q = [b[0], b[1], b[2]];
|
|
11487
|
+
p[axis] = lo;
|
|
11488
|
+
q[axis] = hi;
|
|
11489
|
+
out.push([p, q]);
|
|
11490
|
+
}
|
|
11491
|
+
return out;
|
|
11492
|
+
};
|
|
11493
|
+
var bendBar = ([a, b], thin, bow, i) => {
|
|
11494
|
+
const length = Math.hypot(b[0] - a[0], b[1] - a[1], b[2] - a[2]);
|
|
11495
|
+
const m = [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2, (a[2] + b[2]) / 2];
|
|
11496
|
+
m[thin] += (i % 2 === 0 ? 1 : -1) * bow * length;
|
|
11497
|
+
return [[a, m], [m, b]];
|
|
11498
|
+
};
|
|
11475
11499
|
var clipBarsToDisc = (bars, u, v, center, radius) => {
|
|
11476
11500
|
const out = [];
|
|
11477
11501
|
for (const [a, b] of bars) {
|
|
@@ -12392,14 +12416,21 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
12392
12416
|
smoke: "grey"
|
|
12393
12417
|
},
|
|
12394
12418
|
visual: {
|
|
12395
|
-
charColor: 1709072
|
|
12419
|
+
charColor: 1709072,
|
|
12420
|
+
innerColor: 14730131
|
|
12396
12421
|
},
|
|
12397
12422
|
tags: [
|
|
12398
12423
|
"wood",
|
|
12399
12424
|
"organic",
|
|
12400
12425
|
"flammable"
|
|
12401
12426
|
],
|
|
12402
|
-
compressive: 1e5
|
|
12427
|
+
compressive: 1e5,
|
|
12428
|
+
hits: {
|
|
12429
|
+
blunt: {
|
|
12430
|
+
tear: "splinters",
|
|
12431
|
+
tearJoules: 60
|
|
12432
|
+
}
|
|
12433
|
+
}
|
|
12403
12434
|
},
|
|
12404
12435
|
{
|
|
12405
12436
|
id: "wood_hard",
|
|
@@ -12429,14 +12460,21 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
12429
12460
|
smoke: "grey"
|
|
12430
12461
|
},
|
|
12431
12462
|
visual: {
|
|
12432
|
-
charColor: 1709072
|
|
12463
|
+
charColor: 1709072,
|
|
12464
|
+
innerColor: 13213802
|
|
12433
12465
|
},
|
|
12434
12466
|
tags: [
|
|
12435
12467
|
"wood",
|
|
12436
12468
|
"organic",
|
|
12437
12469
|
"flammable"
|
|
12438
12470
|
],
|
|
12439
|
-
compressive: 15e4
|
|
12471
|
+
compressive: 15e4,
|
|
12472
|
+
hits: {
|
|
12473
|
+
blunt: {
|
|
12474
|
+
tear: "splinters",
|
|
12475
|
+
tearJoules: 150
|
|
12476
|
+
}
|
|
12477
|
+
}
|
|
12440
12478
|
},
|
|
12441
12479
|
{
|
|
12442
12480
|
id: "plywood",
|
|
@@ -12466,14 +12504,24 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
12466
12504
|
smoke: "grey"
|
|
12467
12505
|
},
|
|
12468
12506
|
visual: {
|
|
12469
|
-
charColor: 1709072
|
|
12507
|
+
charColor: 1709072,
|
|
12508
|
+
innerColor: 14862234
|
|
12470
12509
|
},
|
|
12471
12510
|
tags: [
|
|
12472
12511
|
"wood",
|
|
12473
12512
|
"organic",
|
|
12474
12513
|
"flammable"
|
|
12475
12514
|
],
|
|
12476
|
-
compressive: 6e4
|
|
12515
|
+
compressive: 6e4,
|
|
12516
|
+
hits: {
|
|
12517
|
+
ballistic: {
|
|
12518
|
+
tear: "splinters"
|
|
12519
|
+
},
|
|
12520
|
+
blunt: {
|
|
12521
|
+
tear: "splinters",
|
|
12522
|
+
tearJoules: 80
|
|
12523
|
+
}
|
|
12524
|
+
}
|
|
12477
12525
|
},
|
|
12478
12526
|
{
|
|
12479
12527
|
id: "mdf",
|
|
@@ -12500,14 +12548,25 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
12500
12548
|
smoke: "grey"
|
|
12501
12549
|
},
|
|
12502
12550
|
visual: {
|
|
12503
|
-
charColor: 1709072
|
|
12551
|
+
charColor: 1709072,
|
|
12552
|
+
innerColor: 12096362
|
|
12504
12553
|
},
|
|
12505
12554
|
tags: [
|
|
12506
12555
|
"wood",
|
|
12507
12556
|
"organic",
|
|
12508
12557
|
"flammable"
|
|
12509
12558
|
],
|
|
12510
|
-
compressive: 3e4
|
|
12559
|
+
compressive: 3e4,
|
|
12560
|
+
hits: {
|
|
12561
|
+
ballistic: {
|
|
12562
|
+
tear: "lumps",
|
|
12563
|
+
tearJoules: 120
|
|
12564
|
+
},
|
|
12565
|
+
blunt: {
|
|
12566
|
+
tear: "lumps",
|
|
12567
|
+
tearJoules: 20
|
|
12568
|
+
}
|
|
12569
|
+
}
|
|
12511
12570
|
},
|
|
12512
12571
|
{
|
|
12513
12572
|
id: "glass",
|
|
@@ -12621,8 +12680,20 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
12621
12680
|
damageScale: {
|
|
12622
12681
|
explosive: 1.2
|
|
12623
12682
|
},
|
|
12683
|
+
hits: {
|
|
12684
|
+
ballistic: {
|
|
12685
|
+
unitJoules: 500,
|
|
12686
|
+
floor: 900,
|
|
12687
|
+
crater: true
|
|
12688
|
+
},
|
|
12689
|
+
blunt: {
|
|
12690
|
+
unitJoules: 1500,
|
|
12691
|
+
floor: 60
|
|
12692
|
+
}
|
|
12693
|
+
},
|
|
12624
12694
|
hitDebris: "chips",
|
|
12625
12695
|
breakDebris: "dust",
|
|
12696
|
+
visual: { innerColor: 10197652 },
|
|
12626
12697
|
thermal: {
|
|
12627
12698
|
conductivity: 0.15,
|
|
12628
12699
|
heatCapacity: 2.5
|
|
@@ -12653,6 +12724,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
12653
12724
|
],
|
|
12654
12725
|
hitDebris: "chips",
|
|
12655
12726
|
breakDebris: "dust",
|
|
12727
|
+
visual: { innerColor: 12085567 },
|
|
12656
12728
|
thermal: {
|
|
12657
12729
|
conductivity: 0.15,
|
|
12658
12730
|
heatCapacity: 2.5
|
|
@@ -12683,6 +12755,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
12683
12755
|
],
|
|
12684
12756
|
hitDebris: "dust",
|
|
12685
12757
|
breakDebris: "dust",
|
|
12758
|
+
visual: { innerColor: 14999507 },
|
|
12686
12759
|
thermal: {
|
|
12687
12760
|
conductivity: 0.1,
|
|
12688
12761
|
heatCapacity: 1.5
|
|
@@ -12708,6 +12781,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
12708
12781
|
],
|
|
12709
12782
|
hitDebris: "chips",
|
|
12710
12783
|
breakDebris: "dust",
|
|
12784
|
+
visual: { innerColor: 9407364 },
|
|
12711
12785
|
thermal: {
|
|
12712
12786
|
conductivity: 0.2,
|
|
12713
12787
|
heatCapacity: 2.5
|
|
@@ -12989,7 +13063,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
12989
13063
|
smoke: "white"
|
|
12990
13064
|
},
|
|
12991
13065
|
visual: {
|
|
12992
|
-
charColor: 1709072
|
|
13066
|
+
charColor: 1709072,
|
|
13067
|
+
innerColor: 14207126
|
|
12993
13068
|
},
|
|
12994
13069
|
tags: [
|
|
12995
13070
|
"plant",
|
|
@@ -12997,7 +13072,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
12997
13072
|
"organic",
|
|
12998
13073
|
"flammable"
|
|
12999
13074
|
],
|
|
13000
|
-
compressive: 6e4
|
|
13075
|
+
compressive: 6e4,
|
|
13076
|
+
hits: {
|
|
13077
|
+
blunt: {
|
|
13078
|
+
tear: "none"
|
|
13079
|
+
}
|
|
13080
|
+
}
|
|
13001
13081
|
},
|
|
13002
13082
|
{
|
|
13003
13083
|
id: "cactus",
|
|
@@ -13112,6 +13192,130 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
13112
13192
|
"masonry"
|
|
13113
13193
|
],
|
|
13114
13194
|
compressive: 2e4
|
|
13195
|
+
},
|
|
13196
|
+
{
|
|
13197
|
+
id: "asphalt",
|
|
13198
|
+
density: 2350,
|
|
13199
|
+
hpPerKg: 10,
|
|
13200
|
+
penetration: 14,
|
|
13201
|
+
penetrationClass: "concrete",
|
|
13202
|
+
penetrationScale: 0.8,
|
|
13203
|
+
ductility: 0.1,
|
|
13204
|
+
fracture: "crumble",
|
|
13205
|
+
fragmentSize: [
|
|
13206
|
+
0.03,
|
|
13207
|
+
0.15
|
|
13208
|
+
],
|
|
13209
|
+
hitDebris: "chips",
|
|
13210
|
+
breakDebris: "dust",
|
|
13211
|
+
thermal: {
|
|
13212
|
+
conductivity: 0.1,
|
|
13213
|
+
heatCapacity: 2,
|
|
13214
|
+
ignitionC: 400,
|
|
13215
|
+
burnRate: 2e-3,
|
|
13216
|
+
smoke: "black"
|
|
13217
|
+
},
|
|
13218
|
+
tags: [
|
|
13219
|
+
"masonry",
|
|
13220
|
+
"brittle"
|
|
13221
|
+
],
|
|
13222
|
+
compressive: 3e4
|
|
13223
|
+
},
|
|
13224
|
+
{
|
|
13225
|
+
id: "sand",
|
|
13226
|
+
density: 1600,
|
|
13227
|
+
hpPerKg: 2,
|
|
13228
|
+
penetration: 6,
|
|
13229
|
+
ductility: 0.4,
|
|
13230
|
+
fracture: "crumble",
|
|
13231
|
+
fragmentSize: [
|
|
13232
|
+
0.01,
|
|
13233
|
+
0.04
|
|
13234
|
+
],
|
|
13235
|
+
hitDebris: "dust",
|
|
13236
|
+
breakDebris: "dust",
|
|
13237
|
+
thermal: {
|
|
13238
|
+
conductivity: 0.1,
|
|
13239
|
+
heatCapacity: 2
|
|
13240
|
+
},
|
|
13241
|
+
tags: [
|
|
13242
|
+
"soil"
|
|
13243
|
+
],
|
|
13244
|
+
compressive: 1e4
|
|
13245
|
+
},
|
|
13246
|
+
{
|
|
13247
|
+
id: "gravel",
|
|
13248
|
+
density: 1800,
|
|
13249
|
+
hpPerKg: 3,
|
|
13250
|
+
penetration: 8,
|
|
13251
|
+
ductility: 0.3,
|
|
13252
|
+
fracture: "crumble",
|
|
13253
|
+
fragmentSize: [
|
|
13254
|
+
0.02,
|
|
13255
|
+
0.06
|
|
13256
|
+
],
|
|
13257
|
+
hitDebris: "chips",
|
|
13258
|
+
breakDebris: "dust",
|
|
13259
|
+
thermal: {
|
|
13260
|
+
conductivity: 0.1,
|
|
13261
|
+
heatCapacity: 2
|
|
13262
|
+
},
|
|
13263
|
+
tags: [
|
|
13264
|
+
"soil",
|
|
13265
|
+
"masonry"
|
|
13266
|
+
],
|
|
13267
|
+
compressive: 2e4
|
|
13268
|
+
},
|
|
13269
|
+
{
|
|
13270
|
+
id: "turf",
|
|
13271
|
+
density: 900,
|
|
13272
|
+
hpPerKg: 3,
|
|
13273
|
+
penetration: 4,
|
|
13274
|
+
ductility: 0.5,
|
|
13275
|
+
fracture: "crumble",
|
|
13276
|
+
fragmentSize: [
|
|
13277
|
+
0.03,
|
|
13278
|
+
0.1
|
|
13279
|
+
],
|
|
13280
|
+
hitDebris: "dust",
|
|
13281
|
+
breakDebris: "dust",
|
|
13282
|
+
thermal: {
|
|
13283
|
+
conductivity: 0.1,
|
|
13284
|
+
heatCapacity: 1.5,
|
|
13285
|
+
ignitionC: 250,
|
|
13286
|
+
burnRate: 0.03,
|
|
13287
|
+
smoke: "white"
|
|
13288
|
+
},
|
|
13289
|
+
tags: [
|
|
13290
|
+
"soil",
|
|
13291
|
+
"organic"
|
|
13292
|
+
],
|
|
13293
|
+
compressive: 15e3
|
|
13294
|
+
},
|
|
13295
|
+
{
|
|
13296
|
+
id: "mortar",
|
|
13297
|
+
density: 1900,
|
|
13298
|
+
hpPerKg: 10,
|
|
13299
|
+
penetration: 12,
|
|
13300
|
+
penetrationClass: "concrete",
|
|
13301
|
+
penetrationScale: 1.2,
|
|
13302
|
+
ductility: 0.03,
|
|
13303
|
+
fracture: "crumble",
|
|
13304
|
+
fragmentSize: [
|
|
13305
|
+
0.02,
|
|
13306
|
+
0.08
|
|
13307
|
+
],
|
|
13308
|
+
hitDebris: "chips",
|
|
13309
|
+
breakDebris: "dust",
|
|
13310
|
+
thermal: {
|
|
13311
|
+
conductivity: 0.15,
|
|
13312
|
+
heatCapacity: 2.5
|
|
13313
|
+
},
|
|
13314
|
+
tags: [
|
|
13315
|
+
"masonry",
|
|
13316
|
+
"brittle"
|
|
13317
|
+
],
|
|
13318
|
+
compressive: 1e5
|
|
13115
13319
|
}
|
|
13116
13320
|
];
|
|
13117
13321
|
var material_hints_default = {
|
|
@@ -13285,6 +13489,32 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
13285
13489
|
return (1 + 4 * (0.3 - ductility) / 0.3) * Math.min(25, Math.max(1, (0.02 / Math.max(thickness, 1e-4)) ** 2));
|
|
13286
13490
|
};
|
|
13287
13491
|
var THIN_SHEET_DISHING = 5e-3;
|
|
13492
|
+
var SWEEP_BACK = 0.01;
|
|
13493
|
+
function sweepOrigins(box3, d, spacing) {
|
|
13494
|
+
const origins = [];
|
|
13495
|
+
const line = (from, to) => {
|
|
13496
|
+
const n = Math.max(1, Math.ceil((to - from) / spacing));
|
|
13497
|
+
return Array.from({ length: n + 1 }, (_, i) => from + (to - from) * i / n);
|
|
13498
|
+
};
|
|
13499
|
+
for (const axis of [0, 1, 2]) {
|
|
13500
|
+
if (Math.abs(d[axis]) < 1e-3) continue;
|
|
13501
|
+
const lead = d[axis] > 0 ? box3.max[axis] - SWEEP_BACK : box3.min[axis] + SWEEP_BACK;
|
|
13502
|
+
const u = (axis + 1) % 3;
|
|
13503
|
+
const v = (axis + 2) % 3;
|
|
13504
|
+
for (const a of line(box3.min[u], box3.max[u])) {
|
|
13505
|
+
for (const b of line(box3.min[v], box3.max[v])) {
|
|
13506
|
+
const point = [0, 0, 0];
|
|
13507
|
+
point[axis] = lead;
|
|
13508
|
+
point[u] = a;
|
|
13509
|
+
point[v] = b;
|
|
13510
|
+
origins.push(point);
|
|
13511
|
+
}
|
|
13512
|
+
}
|
|
13513
|
+
}
|
|
13514
|
+
return origins;
|
|
13515
|
+
}
|
|
13516
|
+
var THIN_SHEET_CRUSH = 5e-4;
|
|
13517
|
+
var THIN_SHEET_CONTACT = 0.025;
|
|
13288
13518
|
var DMG_PER_J = 0.02;
|
|
13289
13519
|
var BLAST_COUPLING = 0.1;
|
|
13290
13520
|
var SHELTER_DEFAULTS = { k: 2800, brokenTransmission: 0.5, minPath: 5e-3 };
|
|
@@ -13301,11 +13531,15 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
13301
13531
|
var SEVER_BAND = 0.1;
|
|
13302
13532
|
var SEVER_ASPECT = 3;
|
|
13303
13533
|
var SEVER_BALLISTIC = 2;
|
|
13534
|
+
var TEAR_JOULES = 150;
|
|
13535
|
+
var tears = 0;
|
|
13304
13536
|
var SUPPORT_TOLERANCE = 0.05;
|
|
13305
13537
|
var UNIT_FRAGMENT_FACTOR = 4;
|
|
13306
13538
|
var BREACH_GRID_CAP = 4096;
|
|
13307
13539
|
var CHIP_ENERGY = 200;
|
|
13308
13540
|
var CHIP_SPLASH = 0.5;
|
|
13541
|
+
var BAR_BOW = 0.08;
|
|
13542
|
+
var BULLET_JOINT_SPAN = 0.3;
|
|
13309
13543
|
var CUT_SLICE = 1;
|
|
13310
13544
|
var cutFactorOf = (shape) => clamp2(Math.max(...shapeDims(shape)) / CUT_SLICE, 1, 50);
|
|
13311
13545
|
var clamp2 = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
@@ -13315,8 +13549,24 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
13315
13549
|
return v === void 0 ? void 0 : v * (mat.penetrationScale ?? 1);
|
|
13316
13550
|
};
|
|
13317
13551
|
var capacityScale = (p) => {
|
|
13318
|
-
|
|
13319
|
-
|
|
13552
|
+
const residual = p.spec.reinforcement?.residualCapacity ?? RESIDUAL_CAPACITY;
|
|
13553
|
+
if (p.st.stripped) return residual;
|
|
13554
|
+
const kept = Math.min(p.rt.removed ? 1 - p.rt.removed / p.mass0 : 1, 1 - sectionLoss(p));
|
|
13555
|
+
return Math.max(p.spec.reinforcement ? residual : 0.05, kept);
|
|
13556
|
+
};
|
|
13557
|
+
var sectionLoss = (p) => {
|
|
13558
|
+
const cells = p.st.chipped;
|
|
13559
|
+
if (!cells?.length) return 0;
|
|
13560
|
+
const [u, v] = planeAxes(p.thin);
|
|
13561
|
+
const along = p.dims[u] >= p.dims[v] ? u : v;
|
|
13562
|
+
const across = along === u ? v : u;
|
|
13563
|
+
let worst = 0;
|
|
13564
|
+
for (const c of cells) {
|
|
13565
|
+
let gone = 0;
|
|
13566
|
+
for (const d of cells) if (Math.abs(d.center[along] - c.center[along]) < d.size[along] / 2 + 1e-6) gone += d.size[across];
|
|
13567
|
+
worst = Math.max(worst, gone / Math.max(p.dims[across], 1e-6));
|
|
13568
|
+
}
|
|
13569
|
+
return Math.min(1, worst);
|
|
13320
13570
|
};
|
|
13321
13571
|
var isLost = (p) => p.st.status === "broken" || p.st.status === "melted" || p.st.status === "ashes";
|
|
13322
13572
|
var clone = (v) => structuredClone(v);
|
|
@@ -14416,7 +14666,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14416
14666
|
emit({ ...base(o, src), type: "crack", part: p.spec.id, crack, worldPoint: wp });
|
|
14417
14667
|
setStage(o, p, 1, {}, src);
|
|
14418
14668
|
};
|
|
14419
|
-
const debris = (o, p, kind, point, normal, count, speed, src) => emit({ ...base(o, src), type: "debris", part: p.spec.id, kind, point, normal, count, speed });
|
|
14669
|
+
const debris = (o, p, kind, point, normal, count, speed, src, material) => emit({ ...base(o, src), type: "debris", part: p.spec.id, kind, point, normal, count, speed, ...material && material !== p.spec.material ? { material } : {} });
|
|
14420
14670
|
const holeRate = (hole, pressurized) => (pressurized ? 0.5 : 0.1) * (hole.diameter / 9e-3);
|
|
14421
14671
|
const detachEvents = (o, opened, by, src) => {
|
|
14422
14672
|
for (const j of opened) emit({ ...base(o, src), type: "detach", joint: j.spec.id, parts: [j.spec.a, j.spec.b], by, groups: o.st.groups.map((g) => [...g]) });
|
|
@@ -14457,7 +14707,13 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14457
14707
|
const small = pa.dims[0] * pa.dims[1] * pa.dims[2] <= pb.dims[0] * pb.dims[1] * pb.dims[2] ? pa : pb;
|
|
14458
14708
|
return Math.max(0.06, 0.5 * Math.min(small.dims[0], small.dims[1], small.dims[2]));
|
|
14459
14709
|
};
|
|
14460
|
-
const
|
|
14710
|
+
const jointLength = (o, j) => {
|
|
14711
|
+
const A = partWorldAabb(o, o.parts[j.ia]), B = partWorldAabb(o, o.parts[j.ib]);
|
|
14712
|
+
let len = 0;
|
|
14713
|
+
for (let k = 0; k < 3; k++) len = Math.max(len, Math.min(A.max[k], B.max[k]) - Math.max(A.min[k], B.min[k]));
|
|
14714
|
+
return len;
|
|
14715
|
+
};
|
|
14716
|
+
const wearJoints = (o, p, amount, near, radius = 0.06, src, split = false, span) => {
|
|
14461
14717
|
if (amount <= 0) return;
|
|
14462
14718
|
const mine = (o.byPart.get(p.spec.id) ?? []).filter((j) => !j.st.open);
|
|
14463
14719
|
if (split && mine.length) amount /= mine.length;
|
|
@@ -14465,7 +14721,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14465
14721
|
if (j.st.open) continue;
|
|
14466
14722
|
if (j.spec.kind === "rebar" && (isReinforced(o.parts[j.ia].spec) || isReinforced(o.parts[j.ib].spec))) continue;
|
|
14467
14723
|
if (near && jointReach(o, j, near) > Math.max(radius, jointReachLimit(o, j))) continue;
|
|
14468
|
-
j.st.hp -= amount;
|
|
14724
|
+
j.st.hp -= span === void 0 ? amount : amount * Math.min(1, span / Math.max(jointLength(o, j), 1e-6));
|
|
14469
14725
|
if (j.st.hp <= 0) {
|
|
14470
14726
|
j.st.hp = 0;
|
|
14471
14727
|
j.st.open = true;
|
|
@@ -14494,7 +14750,13 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14494
14750
|
if (spalls.length > MAX_SPALLS) spalls.shift();
|
|
14495
14751
|
markDamaged(p);
|
|
14496
14752
|
emit({ ...base(o, src), type: "spall", part: p.spec.id, layer: layer.index, point: wp, area: Math.PI * radius * radius });
|
|
14497
|
-
debris(o, p, layer.mat.hitDebris, wp, normal, 5, 2, src);
|
|
14753
|
+
debris(o, p, layer.mat.hitDebris, wp, normal, 5, 2, src, layer.mat.id);
|
|
14754
|
+
setStage(o, p, 1, {}, src);
|
|
14755
|
+
};
|
|
14756
|
+
const crater = (o, p, wp, normal, radius, src) => {
|
|
14757
|
+
markDamaged(p);
|
|
14758
|
+
emit({ ...base(o, src), type: "spall", part: p.spec.id, layer: -1, point: wp, area: Math.PI * radius * radius });
|
|
14759
|
+
debris(o, p, p.mat.breakDebris, wp, normal, 6, 1.5, src);
|
|
14498
14760
|
setStage(o, p, 1, {}, src);
|
|
14499
14761
|
};
|
|
14500
14762
|
const insideOpening = (o, p, objectPoint) => {
|
|
@@ -14573,9 +14835,13 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14573
14835
|
const lp = toLocal(p.pos, p.q, objectPoint);
|
|
14574
14836
|
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);
|
|
14575
14837
|
};
|
|
14576
|
-
const chipUnits = (o, p, wp,
|
|
14838
|
+
const chipUnits = (o, p, kind, wp, hitEnergy, dir, radius, src) => {
|
|
14577
14839
|
var _a, _b;
|
|
14578
|
-
if (!p.spec.units ||
|
|
14840
|
+
if (!p.spec.units || !p.staged || chipEnergy <= 0 || p.st.stripped || isLost(p) || (p.st.stage ?? 0) >= 2) return;
|
|
14841
|
+
const rule = p.mat.hits?.[kind];
|
|
14842
|
+
const perKg = rule?.unitJoules ?? chipEnergy;
|
|
14843
|
+
const energy = hitEnergy - (rule?.floor ?? 0);
|
|
14844
|
+
if (perKg <= 0 || energy <= 0) return;
|
|
14579
14845
|
const [u, v] = planeAxes(p.thin);
|
|
14580
14846
|
const lp = toLocal(p.pos, p.q, objLocal(o, wp));
|
|
14581
14847
|
const grid = gridOf(p);
|
|
@@ -14589,7 +14855,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14589
14855
|
const share = gap === 0 ? 1 : CHIP_SPLASH * (1 - gap / radius);
|
|
14590
14856
|
const total = (worn[i] ?? 0) + energy * share;
|
|
14591
14857
|
const mass = p.mat.density * cell.size[0] * cell.size[1] * cell.size[2];
|
|
14592
|
-
if (total <
|
|
14858
|
+
if (total < perKg * mass) {
|
|
14593
14859
|
worn[i] = total;
|
|
14594
14860
|
continue;
|
|
14595
14861
|
}
|
|
@@ -14614,7 +14880,32 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14614
14880
|
emit({ ...base(o, src), type: "chip", part: p.spec.id, center, size: [cell.size[0], cell.size[1], cell.size[2]], rotation: q, fragments });
|
|
14615
14881
|
debris(o, p, p.mat.breakDebris, center, vec.scale(dir ?? [0, 1, 0], -1), 6, 2, src);
|
|
14616
14882
|
}
|
|
14883
|
+
if (p.spec.reinforcement) exposeRebar(o, p, src);
|
|
14617
14884
|
for (const cell of out) addBreach(o, p, toWorld(c, q, cell.center), cell.size[u] * cell.size[v], energy, dir, src);
|
|
14885
|
+
if (p.structural && !isLost(p)) {
|
|
14886
|
+
solveStructure(o);
|
|
14887
|
+
collapseCheck(o, src);
|
|
14888
|
+
}
|
|
14889
|
+
};
|
|
14890
|
+
const exposeRebar = (o, p, src) => {
|
|
14891
|
+
const r = p.spec.reinforcement;
|
|
14892
|
+
if (!r) return;
|
|
14893
|
+
const [u, v] = planeAxes(p.thin);
|
|
14894
|
+
const all = rebarBars(p.dims, p.thin, r.spacing);
|
|
14895
|
+
let bars;
|
|
14896
|
+
if (p.st.stripped) bars = all;
|
|
14897
|
+
else {
|
|
14898
|
+
bars = [];
|
|
14899
|
+
for (const cell of p.st.chipped ?? []) bars.push(...clipBarsToBox(all, u, v, cell.center, cell.size));
|
|
14900
|
+
const op = p.st.opening;
|
|
14901
|
+
if (op) {
|
|
14902
|
+
const lc = toLocal(p.pos, p.q, op.center);
|
|
14903
|
+
bars.push(...clipBarsToDisc(all, u, v, [lc[u], lc[v]], op.radius + 0.05));
|
|
14904
|
+
}
|
|
14905
|
+
}
|
|
14906
|
+
const bow = clamp2(((p.st.stress ?? 0) - 1) / 0.5, 0, 1);
|
|
14907
|
+
if (bow > 0) bars = bars.flatMap((bar2, i) => bendBar(bar2, p.thin, bow * BAR_BOW, i));
|
|
14908
|
+
emit({ ...base(o, src), type: "expose", part: p.spec.id, bars: worldBars(o, p, bars) });
|
|
14618
14909
|
};
|
|
14619
14910
|
const openBreach = (o, p, center, total, energy, dir, src) => {
|
|
14620
14911
|
const [u, v] = planeAxes(p.thin);
|
|
@@ -14642,10 +14933,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14642
14933
|
setStage(o, p, 2, { opening: { center: worldCenter, radius }, fragments }, src);
|
|
14643
14934
|
debris(o, p, p.mat.breakDebris, worldCenter, vec.scale(dir ?? [0, 1, 0], -1), 14, 3, src);
|
|
14644
14935
|
const reinforcement = p.spec.reinforcement;
|
|
14645
|
-
if (reinforcement)
|
|
14646
|
-
const bars = worldBars(o, p, clipBarsToDisc(rebarBars(p.dims, p.thin, reinforcement.spacing), u, v, [lc[u], lc[v]], radius + 0.05));
|
|
14647
|
-
if (bars.length > 0) emit({ ...base(o, src), type: "expose", part: p.spec.id, bars });
|
|
14648
|
-
}
|
|
14936
|
+
if (reinforcement) exposeRebar(o, p, src);
|
|
14649
14937
|
if (p.structural) solveStructure(o);
|
|
14650
14938
|
};
|
|
14651
14939
|
const stripPart = (o, p, reinforcement, src) => {
|
|
@@ -14657,8 +14945,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14657
14945
|
p.st.burning = false;
|
|
14658
14946
|
p.rt.jet = false;
|
|
14659
14947
|
markDamaged(p);
|
|
14660
|
-
emit({ ...base(o, src), type: "expose", part: p.spec.id, bars: worldBars(o, p, bars) });
|
|
14661
14948
|
solveStructure(o);
|
|
14949
|
+
exposeRebar(o, p, src);
|
|
14662
14950
|
collapseCheck(o, src);
|
|
14663
14951
|
};
|
|
14664
14952
|
const unitFragmentPlan = (p, cap) => {
|
|
@@ -14712,7 +15000,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14712
15000
|
const grain = p.spec.grain ? AXIS[p.spec.grain] : void 0;
|
|
14713
15001
|
const hitLocal = toLocal(c, q, hitW);
|
|
14714
15002
|
const unitPlan = p.spec.units ? unitFragmentPlan(p, maxFragments * UNIT_FRAGMENT_FACTOR) : [];
|
|
14715
|
-
const axis = style === "splinter" ? severAxis(p) : -1;
|
|
15003
|
+
const axis = style === "splinter" || style === "snap" ? severAxis(p) : -1;
|
|
14716
15004
|
const cutAt2 = axis < 0 ? void 0 : p.rt.cutAt ?? (origin ? hitLocal[axis] : void 0);
|
|
14717
15005
|
const plan = unitPlan.length > 0 ? unitPlan : planFragments({
|
|
14718
15006
|
style,
|
|
@@ -14784,7 +15072,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14784
15072
|
};
|
|
14785
15073
|
const CHAIN_TRIGGER_OF = { ballistic: "shot", explosive: "explosion" };
|
|
14786
15074
|
const severAxis = (p) => {
|
|
14787
|
-
if (p.mat.fracture !== "splinter" || p.spec.shape.kind !== "box") return -1;
|
|
15075
|
+
if (p.mat.fracture !== "splinter" && p.mat.fracture !== "snap" || p.spec.shape.kind !== "box") return -1;
|
|
14788
15076
|
const d = p.dims;
|
|
14789
15077
|
const order = [0, 1, 2].sort((a, b) => d[b] - d[a]);
|
|
14790
15078
|
return d[order[0]] >= SEVER_ASPECT * d[order[1]] && d[order[0]] >= 3 * SEVER_BAND ? order[0] : -1;
|
|
@@ -14845,6 +15133,49 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14845
15133
|
}
|
|
14846
15134
|
}
|
|
14847
15135
|
};
|
|
15136
|
+
const tearOf = (mat, kind) => {
|
|
15137
|
+
const h = mat.hits?.[kind];
|
|
15138
|
+
const tear = h?.tear ?? (kind === "ballistic" && mat.penetrationClass === "wood" ? "splinters" : "none");
|
|
15139
|
+
return { tear, joules: h?.tearJoules ?? TEAR_JOULES };
|
|
15140
|
+
};
|
|
15141
|
+
const tearAt = (o, p, kind, entry2, exit, d, E, scale, src) => {
|
|
15142
|
+
const { tear, joules } = tearOf(p.mat, kind);
|
|
15143
|
+
if (tear === "none") return;
|
|
15144
|
+
const shot = kind === "ballistic";
|
|
15145
|
+
const n = shot ? exit ? clamp2(Math.round(E / joules), 1, 7) : 1 : clamp2(Math.floor(E / joules), 0, 5);
|
|
15146
|
+
if (n === 0) return;
|
|
15147
|
+
const fibre = tear === "lumps";
|
|
15148
|
+
const { q } = partXf(o, p);
|
|
15149
|
+
const along = (p.spec.grain ? AXIS[p.spec.grain] : void 0) ?? [0, 1, 2].sort((a, b) => p.dims[b] - p.dims[a])[0];
|
|
15150
|
+
const fragments = [];
|
|
15151
|
+
for (let i = 0; i < n; i++) {
|
|
15152
|
+
const back = !exit || i % 3 === 2;
|
|
15153
|
+
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());
|
|
15154
|
+
const size = [w, w, w];
|
|
15155
|
+
size[along] = len;
|
|
15156
|
+
size[(along + 1) % 3] = w;
|
|
15157
|
+
size[(along + 2) % 3] = t;
|
|
15158
|
+
const spread3 = [(rnd() - 0.5) * 0.8, (rnd() - 0.2) * 0.8, (rnd() - 0.5) * 0.8];
|
|
15159
|
+
const speed = shot ? back ? 1.5 + 2 * rnd() : 2 + 4 * rnd() : 0.8 + 1.2 * rnd();
|
|
15160
|
+
const v = vec.scale(vec.normalize(vec.add(back ? vec.scale(d, -1) : d, spread3)), speed);
|
|
15161
|
+
const mass = size[0] * size[1] * size[2] * p.mat.density;
|
|
15162
|
+
fragments.push({
|
|
15163
|
+
id: `${p.spec.id}#t${tears++}-${i}`,
|
|
15164
|
+
part: p.spec.id,
|
|
15165
|
+
material: p.spec.material,
|
|
15166
|
+
shape: fibre ? "chunk" : "splinter",
|
|
15167
|
+
center: back || !exit ? vec.madd(entry2, d, -0.01) : vec.madd(exit, d, 0.01),
|
|
15168
|
+
size,
|
|
15169
|
+
rotation: q,
|
|
15170
|
+
velocity: v,
|
|
15171
|
+
inner: true,
|
|
15172
|
+
angularVelocity: [(rnd() - 0.5) * 30, (rnd() - 0.5) * 30, (rnd() - 0.5) * 30],
|
|
15173
|
+
mass
|
|
15174
|
+
});
|
|
15175
|
+
}
|
|
15176
|
+
const patch = [scale * 4, scale * 4, scale * 4];
|
|
15177
|
+
emit({ ...base(o, src), type: "chip", part: p.spec.id, center: exit ?? entry2, size: patch, rotation: q, fragments });
|
|
15178
|
+
};
|
|
14848
15179
|
const ballistic = (o, p, entry2, n, d, E, caliber, pathLen, src, round3 = {}) => {
|
|
14849
15180
|
const mat = p.mat;
|
|
14850
15181
|
const piercing = round3.piercing ?? 1;
|
|
@@ -14873,6 +15204,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14873
15204
|
return { penetrated: false, exitEnergy: 0, damage: dmg2 };
|
|
14874
15205
|
}
|
|
14875
15206
|
if (p.layers.length > 0) spallOuter(o, p, entry2, face, n, Math.max(0.03, caliber / 1e3 * 4), src);
|
|
15207
|
+
else if (mat.hits?.ballistic?.crater) crater(o, p, entry2, n, clamp2(0.02 + Math.sqrt(E) * 15e-4, 0.02, 0.15), src);
|
|
14876
15208
|
const brittle = mat.ductility < 0.3;
|
|
14877
15209
|
const ratio = Ep / Math.max(thr, 1e-6);
|
|
14878
15210
|
const crackLen = clamp2(0.03 + ratio * 0.2, 0.03, 0.8);
|
|
@@ -14905,9 +15237,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14905
15237
|
if (mat.fracture !== "shatter") addHole(o, p, entry2, n, caliber / 1e3, false, void 0, src);
|
|
14906
15238
|
} else addHole(o, p, entry2, n, caliber / 1e3, false, void 0, src);
|
|
14907
15239
|
debris(o, p, mat.hitDebris, entry2, n, clamp2(Math.round(E / 60) + 2, 2, 14), clamp2(Math.sqrt(E) / 6, 1, 8), src);
|
|
14908
|
-
wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true);
|
|
14909
|
-
const
|
|
14910
|
-
|
|
15240
|
+
wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true, BULLET_JOINT_SPAN);
|
|
15241
|
+
const absorbed = penetrated && mat.ductility >= 0.5 && mat.fracture !== "tear" ? Math.min(E, thr) : E;
|
|
15242
|
+
const dmg = applyDamage(o, p, absorbed * DMG_PER_J * scale, "ballistic", entry2, d, absorbed * 0.1, 12, src);
|
|
15243
|
+
if (!isLost(p)) tearAt(o, p, "ballistic", entry2, penetrated ? exitPoint : void 0, d, E, caliber / 1e3, src);
|
|
15244
|
+
chipUnits(o, p, "ballistic", entry2, penetrated ? Math.min(E, Math.max(thr, E * 0.5)) : E, d, Math.max(0.05, caliber / 1e3 * 8), src);
|
|
14911
15245
|
return { penetrated, exitEnergy: penetrated ? penMm !== void 0 ? E * (1 - thr / Ep) : E - thr / (hard ? piercing : 1) : 0, exitPoint, damage: dmg };
|
|
14912
15246
|
};
|
|
14913
15247
|
const melee = (o, p, kind, E, point, n, d, contactR, src) => {
|
|
@@ -14915,14 +15249,20 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14915
15249
|
const thrMm = mat.penetration * p.thickness * 1e3;
|
|
14916
15250
|
const fragile = kind === "blunt" ? fragility(mat.ductility, p.thickness) : 1;
|
|
14917
15251
|
const Ec = E * fragile;
|
|
15252
|
+
const sheet = mat.ductility >= 0.5 && p.thickness <= THIN_SHEET_CRUSH;
|
|
15253
|
+
const fold = sheet ? Math.min(4, Math.sqrt(1e-3 / p.thickness)) : 1;
|
|
15254
|
+
const patch = sheet ? Math.min(contactR, THIN_SHEET_CONTACT) : contactR;
|
|
14918
15255
|
if (E > 0) {
|
|
14919
15256
|
if (kind === "blunt") {
|
|
14920
|
-
if (mat.ductility >= 0.5) addDent(o, p, point, n, Math.max(
|
|
15257
|
+
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);
|
|
14921
15258
|
else if (mat.ductility < 0.3) {
|
|
14922
15259
|
if (Ec >= 5) addCrack(o, p, point, n, clamp2(0.04 + Math.min(Ec, E * 6) * 8e-4, 0.04, 0.6), src);
|
|
14923
15260
|
} 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);
|
|
14924
15261
|
} else if (mat.fracture === "tear") {
|
|
14925
15262
|
addHole(o, p, point, n, clamp2(0.02 + E * 5e-4, 0.02, 0.3), true, void 0, src);
|
|
15263
|
+
} else if (mat.ductility >= 0.3 && E >= thrMm) {
|
|
15264
|
+
addHole(o, p, point, n, clamp2(contactR, 0.01, 0.08), true, void 0, src);
|
|
15265
|
+
if (sheet) addDent(o, p, point, n, Math.max(patch * 0.6, 0.01), clamp2(E * 4e-5 * fold, 5e-4, 0.02), src);
|
|
14926
15266
|
} else if (mat.ductility >= 0.5) {
|
|
14927
15267
|
addDent(o, p, point, n, Math.max(contactR, 0.01), clamp2(E * 4e-5, 5e-4, 0.02), src);
|
|
14928
15268
|
}
|
|
@@ -14935,7 +15275,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14935
15275
|
const scale = mat.damageScale?.[kind] ?? 1;
|
|
14936
15276
|
const done = applyDamage(o, p, Ec * DMG_PER_J * scale * (kind === "slash" ? p.cutFactor : 1), kind, point, d, E * 0.1, 12, src);
|
|
14937
15277
|
addBreach(o, p, point, breachAreaOf(p, done, false), E, d, src);
|
|
14938
|
-
if (kind === "blunt") chipUnits(o, p, point, E, d, Math.max(contactR, 0.03), src);
|
|
15278
|
+
if (kind === "blunt") chipUnits(o, p, "blunt", point, E, d, Math.max(contactR, 0.03), src);
|
|
15279
|
+
if (E > 0 && !isLost(p)) tearAt(o, p, kind, point, void 0, d, E, clamp2(contactR * 0.2, 6e-3, 0.02), src);
|
|
14939
15280
|
return done;
|
|
14940
15281
|
};
|
|
14941
15282
|
const blastPart = (o, p, point, energy, radius, src, res, reachAt) => {
|
|
@@ -15514,6 +15855,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
15514
15855
|
if (p.rt.stressT <= 0) {
|
|
15515
15856
|
p.rt.stressT = 0.5;
|
|
15516
15857
|
emit({ ...base(o), type: "stress", part: p.spec.id, stress, load: p.st.load ?? 0 });
|
|
15858
|
+
if (p.spec.reinforcement && (p.st.stripped || p.st.chipped?.length || p.st.opening)) exposeRebar(o, p);
|
|
15517
15859
|
}
|
|
15518
15860
|
applyDamage(o, p, overloadRate(stress) * p.st.maxHp * dt, "blunt", null, null, 5, 3);
|
|
15519
15861
|
} else if (stress > 0.8 && p.mat.ductility < 0.3 && p.rt.stressT <= 0 && p.st.cracks.length < 6) {
|
|
@@ -15730,6 +16072,18 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
15730
16072
|
const r = raycastInternal(origin, direction, maxDistance);
|
|
15731
16073
|
return r ? { object: r.object, part: r.part, point: r.point, normal: r.normal, distance: r.distance } : null;
|
|
15732
16074
|
},
|
|
16075
|
+
sweepBox(box3, direction, distance2, options2 = {}) {
|
|
16076
|
+
const d = vec.normalize(direction);
|
|
16077
|
+
const ignored = options2.ignore === void 0 ? void 0 : objects.get(options2.ignore);
|
|
16078
|
+
const ignore = ignored ? new Set(ignored.parts.map((p) => `${ignored.id}\0${p.spec.id}`)) : void 0;
|
|
16079
|
+
let best = null;
|
|
16080
|
+
for (const origin of sweepOrigins(box3, d, options2.spacing ?? 0.2)) {
|
|
16081
|
+
const r = raycastInternal(origin, d, distance2 + SWEEP_BACK, ignore);
|
|
16082
|
+
if (!r || best && r.distance >= best.distance) continue;
|
|
16083
|
+
best = { object: r.object, part: r.part, point: r.point, normal: r.normal, distance: Math.max(0, r.distance - SWEEP_BACK) };
|
|
16084
|
+
}
|
|
16085
|
+
return best;
|
|
16086
|
+
},
|
|
15733
16087
|
shoot(origin, direction, energy, caliber, source, round3) {
|
|
15734
16088
|
const extras = typeof round3 === "number" ? { piercing: round3 } : round3 ?? {};
|
|
15735
16089
|
return withCollapses(run(() => {
|
|
@@ -18295,7 +18649,24 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
18295
18649
|
}
|
|
18296
18650
|
return spec;
|
|
18297
18651
|
};
|
|
18298
|
-
var
|
|
18652
|
+
var MEMBER_CAGE = { spacing: 0.1, diameter: 8e-3, unit: 0.1 };
|
|
18653
|
+
var CAGED_ROLES = /* @__PURE__ */ new Set(["beam", "column"]);
|
|
18654
|
+
var caged = (spec, part, rebar) => {
|
|
18655
|
+
if (!rebar || part.material !== "concrete" || !CAGED_ROLES.has(part.role) || spec.reinforcement) return spec;
|
|
18656
|
+
const size = spec.shape.kind === "box" ? spec.shape.size : null;
|
|
18657
|
+
if (!size) return spec;
|
|
18658
|
+
const thin = Math.min(...size);
|
|
18659
|
+
return {
|
|
18660
|
+
...spec,
|
|
18661
|
+
reinforcement: { material: rebar, spacing: MEMBER_CAGE.spacing, diameter: MEMBER_CAGE.diameter },
|
|
18662
|
+
units: { size: [MEMBER_CAGE.unit, MEMBER_CAGE.unit, thin], bond: "stack", mortar: "concrete" }
|
|
18663
|
+
};
|
|
18664
|
+
};
|
|
18665
|
+
var partSpecOf = (part, capacity, detailed, rebar) => {
|
|
18666
|
+
if (detailed && part.assembly) return assemblySpec(part, capacity);
|
|
18667
|
+
const spec = boxSpec(part, capacity);
|
|
18668
|
+
return detailed ? caged(spec, part, rebar) : spec;
|
|
18669
|
+
};
|
|
18299
18670
|
function toDestructibleSpec(structure, options = {}) {
|
|
18300
18671
|
const { safety = 0.65, redundancy = 0.97, safetyOthers = 0.45 } = options;
|
|
18301
18672
|
const kept = structure.parts.filter((part) => !options.filter || options.filter(part));
|
|
@@ -18312,7 +18683,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
18312
18683
|
const sizing = { safety, redundancy, safetyOthers };
|
|
18313
18684
|
const sized = { ...panels, capacities: options.sized === false ? /* @__PURE__ */ new Map() : sizeCapacities(panels.parts, panels.supports, sizing) };
|
|
18314
18685
|
const { parts, supports, capacities } = detail === "units" ? applyUnits(sized, structure) : sized;
|
|
18315
|
-
const specs = parts.map((part) => partSpecOf(part, capacities.get(part.id), detail !== "coarse"));
|
|
18686
|
+
const specs = parts.map((part) => partSpecOf(part, capacities.get(part.id), detail !== "coarse", structure.construction.frame.rebar));
|
|
18316
18687
|
const margin = options.terrainMargin ?? TERRAIN_MARGIN;
|
|
18317
18688
|
const x0 = Math.min(...parts.map((part) => part.aabb[0]));
|
|
18318
18689
|
const z0 = Math.min(...parts.map((part) => part.aabb[1]));
|
|
@@ -19192,7 +19563,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19192
19563
|
if (!pc || pc.unit) return sp;
|
|
19193
19564
|
if (pc.role !== "wall" && pc.role !== "partition") {
|
|
19194
19565
|
const def = getAssembly(pc.assembly || (pc.material === "concrete" && ["slab", "beam", "column", "stair"].includes(pc.role) ? "reinforced_concrete" : ""));
|
|
19195
|
-
|
|
19566
|
+
const out = def && def.reinforcement && !sp.reinforcement ? { ...sp, reinforcement: { ...def.reinforcement } } : sp;
|
|
19567
|
+
if (out.reinforcement && !out.units && pc.material === "concrete" && (pc.role === "beam" || pc.role === "column") && sp.shape.kind === "box") {
|
|
19568
|
+
return { ...out, units: { size: [MEMBER_CAGE.unit, MEMBER_CAGE.unit, Math.min(...sp.shape.size)], bond: "stack", mortar: "concrete" } };
|
|
19569
|
+
}
|
|
19570
|
+
return out;
|
|
19196
19571
|
}
|
|
19197
19572
|
if (!pc.assembly) return sp;
|
|
19198
19573
|
const [u0, v0, u1, v1] = pc.aabb, alongU = u1 - u0 >= v1 - v0;
|
|
@@ -19229,6 +19604,19 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19229
19604
|
pos.array.set(saved, v0 * 3);
|
|
19230
19605
|
pos.needsUpdate = true;
|
|
19231
19606
|
}
|
|
19607
|
+
function noise2(x, y, cell) {
|
|
19608
|
+
const h = (i2, j2) => {
|
|
19609
|
+
const s = Math.sin(i2 * 127.1 + j2 * 311.7) * 43758.5453;
|
|
19610
|
+
return s - Math.floor(s);
|
|
19611
|
+
};
|
|
19612
|
+
const fx = x / cell, fy = y / cell, i = Math.floor(fx), j = Math.floor(fy), u = fx - i, v = fy - j;
|
|
19613
|
+
const su = u * u * (3 - 2 * u), sv = v * v * (3 - 2 * v);
|
|
19614
|
+
return (h(i, j) * (1 - su) + h(i + 1, j) * su) * (1 - sv) + (h(i, j + 1) * (1 - su) + h(i + 1, j + 1) * su) * sv;
|
|
19615
|
+
}
|
|
19616
|
+
var MASK_TEXEL = 0.02;
|
|
19617
|
+
var SPALL_MIN = 0.025;
|
|
19618
|
+
var SPALL_RAGGED = 0.045;
|
|
19619
|
+
var COVER2 = 0.015;
|
|
19232
19620
|
function installDestructionBreach(K, _shared) {
|
|
19233
19621
|
const { THREE, fragmentMaterial, invalidate, invalidateShadow } = K;
|
|
19234
19622
|
function createBreachView(opts) {
|
|
@@ -19238,94 +19626,198 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19238
19626
|
const unitMaterial = new THREE.MeshLambertMaterial({ color: 16777215 });
|
|
19239
19627
|
const unitGeometry = new THREE.BoxGeometry(1, 1, 1);
|
|
19240
19628
|
const tmp = { m: new THREE.Matrix4(), q: new THREE.Quaternion(), p: new THREE.Vector3(), s: new THREE.Vector3(), c: new THREE.Color() };
|
|
19241
|
-
const wallColour = (items) => {
|
|
19242
|
-
for (const it of items) {
|
|
19243
|
-
if (!it.mesh) continue;
|
|
19244
|
-
const c = it.mesh.geometry.attributes.color;
|
|
19245
|
-
if (!c) continue;
|
|
19246
|
-
const a = c.array;
|
|
19247
|
-
tmp.c.setRGB(a[it.v0 * 3], a[it.v0 * 3 + 1], a[it.v0 * 3 + 2]);
|
|
19248
|
-
return tmp.c.getHex();
|
|
19249
|
-
}
|
|
19250
|
-
return null;
|
|
19251
|
-
};
|
|
19252
19629
|
const local = (p) => {
|
|
19253
19630
|
house.updateMatrixWorld(true);
|
|
19254
19631
|
const v = house.worldToLocal(tmp.p.set(p[0], p[1], p[2]));
|
|
19255
19632
|
return [v.x, v.y, v.z];
|
|
19256
19633
|
};
|
|
19257
|
-
function
|
|
19634
|
+
function cells(part, units) {
|
|
19258
19635
|
const [u0, v0, u1, v1] = part.aabb;
|
|
19259
19636
|
const alongU = u1 - u0 >= v1 - v0;
|
|
19260
19637
|
const len = alongU ? u1 - u0 : v1 - v0;
|
|
19261
|
-
const thick = alongU ? v1 - v0 : u1 - u0;
|
|
19262
19638
|
const [ul, uh] = units.size;
|
|
19263
19639
|
const rows = Math.max(1, Math.round((part.y1 - part.y0) / uh));
|
|
19264
19640
|
const rowH = (part.y1 - part.y0) / rows;
|
|
19265
19641
|
const per = Math.max(1, Math.round(len / ul));
|
|
19266
19642
|
const w = len / per;
|
|
19267
|
-
const
|
|
19268
|
-
const list2 = [];
|
|
19643
|
+
const out = [];
|
|
19269
19644
|
for (let r = 0; r < rows; r++) {
|
|
19270
19645
|
const shift = units.bond === "running" && r & 1 ? w / 2 : 0;
|
|
19271
|
-
|
|
19272
|
-
|
|
19273
|
-
|
|
19274
|
-
a = Math.max(0, a);
|
|
19275
|
-
b = Math.min(len, b);
|
|
19276
|
-
if (b - a < 1e-3) continue;
|
|
19277
|
-
const mid = (a + b) / 2;
|
|
19278
|
-
const x = alongU ? u0 + mid : (u0 + u1) / 2;
|
|
19279
|
-
const z = alongU ? (v0 + v1) / 2 : v0 + mid;
|
|
19280
|
-
const y = part.y0 + (r + 0.5) * rowH;
|
|
19281
|
-
const d = opening ? Math.hypot(x - opening.center[0], y - opening.center[1], z - opening.center[2]) : Infinity;
|
|
19282
|
-
if (opening && d < opening.radius) continue;
|
|
19283
|
-
const gone = (c) => Math.abs(c[1] - y) <= rowH / 2 && (alongU ? c[0] >= u0 + a && c[0] <= u0 + b : c[2] >= v0 + a && c[2] <= v0 + b);
|
|
19284
|
-
if (chips.some(gone)) continue;
|
|
19285
|
-
const near = opening && d < opening.radius + 0.25 || chips.some((c) => Math.hypot(c[0] - x, c[1] - y, c[2] - z) < 0.15);
|
|
19286
|
-
list2.push({ x, y, z, sx: alongU ? b - a : thick, sy: rowH, sz: alongU ? thick : b - a, colour: near || paint === null ? edge : paint });
|
|
19646
|
+
for (let i = 0; i < per + (shift ? 1 : 0); i++) {
|
|
19647
|
+
const a = Math.max(0, i * w - shift), b = Math.min(len, i * w - shift + w);
|
|
19648
|
+
if (b - a > 1e-3) out.push({ a, b, y0: part.y0 + r * rowH, y1: part.y0 + (r + 1) * rowH, r, i });
|
|
19287
19649
|
}
|
|
19288
19650
|
}
|
|
19289
|
-
return
|
|
19651
|
+
return out;
|
|
19652
|
+
}
|
|
19653
|
+
const specOf = (id) => opts.spec()?.parts?.find((q) => q.id === id);
|
|
19654
|
+
const same = (a, b) => a.every((x, k) => Math.abs(x - b[k]) < 1e-3);
|
|
19655
|
+
function hostOf(partId) {
|
|
19656
|
+
if (structure.meshesOf(partId).some((it) => it.mesh)) return partId;
|
|
19657
|
+
const part = structure.byId.get(partId);
|
|
19658
|
+
if (!part) return null;
|
|
19659
|
+
for (const q of structure.parts) {
|
|
19660
|
+
if (q.id !== partId && same(q.aabb, part.aabb) && Math.abs(q.y1 - part.y0) < 0.01 && structure.meshesOf(q.id).some((it) => it.mesh)) return q.id;
|
|
19661
|
+
}
|
|
19662
|
+
return null;
|
|
19663
|
+
}
|
|
19664
|
+
const riders = (hostId) => structure.parts.filter((q) => q.id !== hostId && specOf(q.id)?.units && hostOf(q.id) === hostId && !structure.meshesOf(q.id).some((it) => it.mesh)).map((q) => q.id);
|
|
19665
|
+
function shell(it, mask) {
|
|
19666
|
+
const g0 = it.mesh.geometry, g = new THREE.BufferGeometry();
|
|
19667
|
+
const src = it.mesh.userData._p0 || g0.attributes.position.array;
|
|
19668
|
+
for (const [name, attr] of Object.entries(g0.attributes)) {
|
|
19669
|
+
const k = attr.itemSize;
|
|
19670
|
+
g.setAttribute(name, new THREE.BufferAttribute((name === "position" ? src : attr.array).slice(it.v0 * k, it.v1 * k), k));
|
|
19671
|
+
}
|
|
19672
|
+
if (g0.index) {
|
|
19673
|
+
const ix = g0.index.array.slice(it.i0, it.i1);
|
|
19674
|
+
for (let k = 0; k < ix.length; k++) ix[k] -= it.v0;
|
|
19675
|
+
g.setIndex(new THREE.BufferAttribute(ix, 1));
|
|
19676
|
+
}
|
|
19677
|
+
g.computeBoundingBox();
|
|
19678
|
+
g.computeBoundingSphere();
|
|
19679
|
+
const base = it.mesh.material;
|
|
19680
|
+
const material = base.clone();
|
|
19681
|
+
const toHouse = { value: new THREE.Matrix4() };
|
|
19682
|
+
material.onBeforeCompile = (sh) => {
|
|
19683
|
+
base.onBeforeCompile?.(sh);
|
|
19684
|
+
sh.uniforms.uToHouse = toHouse;
|
|
19685
|
+
sh.uniforms.uMask = { value: mask.texture };
|
|
19686
|
+
sh.uniforms.uMaskRect = { value: mask.rect };
|
|
19687
|
+
sh.uniforms.uMaskAlongX = { value: mask.alongU ? 1 : 0 };
|
|
19688
|
+
sh.vertexShader = sh.vertexShader.replace("void main() {", "uniform mat4 uToHouse;\nvarying vec3 vHousePos;\nvoid main() {").replace("#include <begin_vertex>", "#include <begin_vertex>\nvHousePos = (uToHouse * vec4(transformed, 1.0)).xyz;");
|
|
19689
|
+
sh.fragmentShader = sh.fragmentShader.replace("void main() {", "uniform sampler2D uMask;\nuniform vec4 uMaskRect;\nuniform float uMaskAlongX;\nvarying vec3 vHousePos;\nvoid main() {\n vec2 mUv = vec2(((uMaskAlongX > 0.5 ? vHousePos.x : vHousePos.z) - uMaskRect.x) / uMaskRect.z, (vHousePos.y - uMaskRect.y) / uMaskRect.w);\n if (mUv.x >= 0.0 && mUv.x <= 1.0 && mUv.y >= 0.0 && mUv.y <= 1.0 && texture2D(uMask, mUv).r > 0.5) discard;");
|
|
19690
|
+
};
|
|
19691
|
+
material.customProgramCacheKey = () => "breachMask" + (base.customProgramCacheKey?.() ?? "");
|
|
19692
|
+
const mesh2 = new THREE.Mesh(g, material);
|
|
19693
|
+
mesh2.name = "breach-shell";
|
|
19694
|
+
mesh2.userData.breachMask = mask;
|
|
19695
|
+
mesh2.position.copy(it.mesh.position);
|
|
19696
|
+
mesh2.quaternion.copy(it.mesh.quaternion);
|
|
19697
|
+
mesh2.scale.copy(it.mesh.scale);
|
|
19698
|
+
mesh2.castShadow = it.mesh.castShadow;
|
|
19699
|
+
mesh2.receiveShadow = it.mesh.receiveShadow;
|
|
19700
|
+
mesh2.renderOrder = it.mesh.renderOrder;
|
|
19701
|
+
mesh2.onBeforeRender = () => {
|
|
19702
|
+
house.updateMatrixWorld();
|
|
19703
|
+
toHouse.value.copy(house.matrixWorld).invert().multiply(mesh2.matrixWorld);
|
|
19704
|
+
};
|
|
19705
|
+
it.mesh.parent.add(mesh2);
|
|
19706
|
+
mesh2.updateMatrixWorld(true);
|
|
19707
|
+
toHouse.value.copy(house.matrixWorld).invert().multiply(mesh2.matrixWorld);
|
|
19708
|
+
return mesh2;
|
|
19709
|
+
}
|
|
19710
|
+
function maskOf(hostId, parts) {
|
|
19711
|
+
const host = structure.byId.get(hostId);
|
|
19712
|
+
const [u0, v0, u1, v1] = host.aabb;
|
|
19713
|
+
const alongU = u1 - u0 >= v1 - v0;
|
|
19714
|
+
const y0 = Math.min(...parts.map((id) => structure.byId.get(id).y0)), y1 = Math.max(...parts.map((id) => structure.byId.get(id).y1));
|
|
19715
|
+
const len = alongU ? u1 - u0 : v1 - v0;
|
|
19716
|
+
const w = Math.max(2, Math.min(2048, Math.ceil(len / MASK_TEXEL))), h = Math.max(2, Math.min(1024, Math.ceil((y1 - y0) / MASK_TEXEL)));
|
|
19717
|
+
const data = new Uint8Array(w * h);
|
|
19718
|
+
const texture = new THREE.DataTexture(data, w, h, THREE.RedFormat);
|
|
19719
|
+
texture.magFilter = THREE.LinearFilter;
|
|
19720
|
+
texture.minFilter = THREE.LinearFilter;
|
|
19721
|
+
texture.needsUpdate = true;
|
|
19722
|
+
return { data, texture, w, h, alongU, start: alongU ? u0 : v0, len, y0, height: y1 - y0, rect: new THREE.Vector4(alongU ? u0 : v0, y0, len, y1 - y0) };
|
|
19723
|
+
}
|
|
19724
|
+
const boxDistance = (s, y, a, b, c, d) => Math.hypot(Math.max(a - s, 0, s - b), Math.max(c - y, 0, y - d));
|
|
19725
|
+
function paint(view) {
|
|
19726
|
+
const { mask } = view;
|
|
19727
|
+
const along = (p) => (mask.alongU ? p[0] : p[2]) - mask.start;
|
|
19728
|
+
const holes = [];
|
|
19729
|
+
const discs = [];
|
|
19730
|
+
const shown = [];
|
|
19731
|
+
for (const id of view.parts) {
|
|
19732
|
+
const part = structure.byId.get(id), units2 = specOf(id).units, chips = view.chips.get(id), opening = view.openings.get(id);
|
|
19733
|
+
const all = cells(part, units2);
|
|
19734
|
+
const thick = mask.alongU ? part.aabb[3] - part.aabb[1] : part.aabb[2] - part.aabb[0];
|
|
19735
|
+
const mid = mask.alongU ? (part.aabb[1] + part.aabb[3]) / 2 : (part.aabb[0] + part.aabb[2]) / 2;
|
|
19736
|
+
if (opening) discs.push({ s: along(opening.center), y: opening.center[1], r: opening.radius });
|
|
19737
|
+
const gone = (c) => chips.some((p) => p[1] >= c.y0 && p[1] <= c.y1 && along(p) >= c.a && along(p) <= c.b) || opening && Math.hypot((c.a + c.b) / 2 - along(opening.center), (c.y0 + c.y1) / 2 - opening.center[1]) < opening.radius;
|
|
19738
|
+
const kept = [];
|
|
19739
|
+
for (const c of all) (gone(c) ? holes : kept).push(c);
|
|
19740
|
+
for (const c of kept) shown.push({ c, part, units: units2, thick, mid });
|
|
19741
|
+
}
|
|
19742
|
+
const reach = SPALL_MIN + SPALL_RAGGED;
|
|
19743
|
+
mask.data.fill(0);
|
|
19744
|
+
const stamp = (s0, s1, y0, y1, inside2) => {
|
|
19745
|
+
const i0 = Math.max(0, Math.floor((s0 - reach) / mask.len * mask.w)), i1 = Math.min(mask.w - 1, Math.ceil((s1 + reach) / mask.len * mask.w));
|
|
19746
|
+
const j0 = Math.max(0, Math.floor((y0 - reach - mask.y0) / mask.height * mask.h)), j1 = Math.min(mask.h - 1, Math.ceil((y1 + reach - mask.y0) / mask.height * mask.h));
|
|
19747
|
+
for (let j = j0; j <= j1; j++) {
|
|
19748
|
+
const y = mask.y0 + (j + 0.5) / mask.h * mask.height;
|
|
19749
|
+
for (let i = i0; i <= i1; i++) {
|
|
19750
|
+
const s = (i + 0.5) / mask.w * mask.len;
|
|
19751
|
+
const edge = SPALL_MIN + SPALL_RAGGED * (0.6 * noise2(s, y, 0.03) + 0.4 * noise2(s + 7.3, y - 3.1, 0.011));
|
|
19752
|
+
if (inside2(s, y) <= edge) mask.data[j * mask.w + i] = 255;
|
|
19753
|
+
}
|
|
19754
|
+
}
|
|
19755
|
+
};
|
|
19756
|
+
for (const c of holes) stamp(c.a, c.b, c.y0, c.y1, (s, y) => boxDistance(s, y, c.a, c.b, c.y0, c.y1));
|
|
19757
|
+
for (const d of discs) stamp(d.s - d.r, d.s + d.r, d.y - d.r, d.y + d.r, (s, y) => Math.max(0, Math.hypot(s - d.s, y - d.y) - d.r));
|
|
19758
|
+
mask.texture.needsUpdate = true;
|
|
19759
|
+
const near = (c) => holes.some((h) => boxDistance((c.a + c.b) / 2, (c.y0 + c.y1) / 2, h.a - (c.b - c.a) / 2, h.b + (c.b - c.a) / 2, h.y0 - (c.y1 - c.y0) / 2, h.y1 + (c.y1 - c.y0) / 2) <= reach) || discs.some((d) => boxDistance(d.s, d.y, c.a, c.b, c.y0, c.y1) <= d.r + reach);
|
|
19760
|
+
const list2 = [];
|
|
19761
|
+
const tone = (hex2, k) => tmp.c.setHex(hex2).multiplyScalar(k).getHex();
|
|
19762
|
+
for (const { c, part, units: units2, thick, mid } of shown) {
|
|
19763
|
+
if (!near(c)) continue;
|
|
19764
|
+
const centre = mask.start + (c.a + c.b) / 2, y = (c.y0 + c.y1) / 2, len = c.b - c.a, h = c.y1 - c.y0;
|
|
19765
|
+
const at = (s, dy, d, colour) => list2.push({ x: mask.alongU ? centre : mid, y, z: mask.alongU ? mid : centre, sx: mask.alongU ? s : d, sy: dy, sz: mask.alongU ? d : s, colour });
|
|
19766
|
+
const base = fragmentMaterial(part.material).color.getHex();
|
|
19767
|
+
const n = noise2(centre * 3.1, y * 2.7, 0.07);
|
|
19768
|
+
if (units2.mortar === "concrete") {
|
|
19769
|
+
at(len, h, thick - 2 * COVER2 - 0.03 * n, tone(base, 0.86 + 0.2 * n));
|
|
19770
|
+
} else {
|
|
19771
|
+
at(len, h, thick - 2 * COVER2 - 0.012, 10327948);
|
|
19772
|
+
at(len - 0.012, h - 0.012, thick - 2 * COVER2, tone(base, 0.8 + 0.35 * n));
|
|
19773
|
+
}
|
|
19774
|
+
}
|
|
19775
|
+
if (view.units) {
|
|
19776
|
+
view.units.removeFromParent();
|
|
19777
|
+
view.units.dispose();
|
|
19778
|
+
}
|
|
19779
|
+
const units = new THREE.InstancedMesh(unitGeometry, unitMaterial, Math.max(1, list2.length));
|
|
19780
|
+
units.name = "breach:" + view.host;
|
|
19781
|
+
list2.forEach((u, i) => {
|
|
19782
|
+
tmp.m.compose(tmp.p.set(u.x, u.y, u.z), tmp.q.identity(), tmp.s.set(u.sx, u.sy, u.sz));
|
|
19783
|
+
units.setMatrixAt(i, tmp.m);
|
|
19784
|
+
units.setColorAt(i, tmp.c.setHex(u.colour));
|
|
19785
|
+
});
|
|
19786
|
+
units.count = list2.length;
|
|
19787
|
+
units.instanceMatrix.needsUpdate = true;
|
|
19788
|
+
if (units.instanceColor) units.instanceColor.needsUpdate = true;
|
|
19789
|
+
units.castShadow = true;
|
|
19790
|
+
units.receiveShadow = true;
|
|
19791
|
+
units.visible = lod === 0;
|
|
19792
|
+
house.add(units);
|
|
19793
|
+
view.units = units;
|
|
19794
|
+
view.gone = holes.length;
|
|
19290
19795
|
}
|
|
19291
19796
|
function draw2(partId, change) {
|
|
19292
19797
|
if (lod !== 0) return false;
|
|
19293
|
-
const
|
|
19294
|
-
|
|
19295
|
-
|
|
19296
|
-
let view = views.get(partId);
|
|
19798
|
+
const hostId = hostOf(partId);
|
|
19799
|
+
if (!hostId || !specOf(partId)?.units) return false;
|
|
19800
|
+
let view = views.get(hostId);
|
|
19297
19801
|
if (!view) {
|
|
19298
|
-
const items = structure.meshesOf(
|
|
19802
|
+
const items = structure.meshesOf(hostId).filter((it) => it.mesh);
|
|
19299
19803
|
if (!items.length) return false;
|
|
19300
|
-
|
|
19804
|
+
const parts = [hostId, ...riders(hostId)].filter((id) => structure.byId.get(id) && specOf(id)?.units);
|
|
19805
|
+
if (!parts.includes(partId)) return false;
|
|
19806
|
+
const mask = maskOf(hostId, parts);
|
|
19807
|
+
view = { host: hostId, parts, items, mask, shells: [], saved: null, units: null, gone: 0, chips: new Map(parts.map((id) => [id, []])), openings: /* @__PURE__ */ new Map() };
|
|
19301
19808
|
}
|
|
19809
|
+
if (!view.chips.has(partId)) return false;
|
|
19302
19810
|
if (change.opening) {
|
|
19303
|
-
if (view.
|
|
19304
|
-
view.
|
|
19811
|
+
if (view.openings.has(partId)) return false;
|
|
19812
|
+
view.openings.set(partId, { center: local(change.opening.center), radius: change.opening.radius });
|
|
19305
19813
|
}
|
|
19306
|
-
if (change.chip) view.chips.push(local(change.chip));
|
|
19307
|
-
|
|
19308
|
-
|
|
19309
|
-
view.
|
|
19310
|
-
view.mesh.dispose();
|
|
19814
|
+
if (change.chip) view.chips.get(partId).push(local(change.chip));
|
|
19815
|
+
if (!view.saved) {
|
|
19816
|
+
view.shells = view.items.map((it) => shell(it, view.mask));
|
|
19817
|
+
view.saved = view.items.map((it) => ({ it, v0: it.v0, data: collapseRange(it.mesh, it.v0, it.v1) }));
|
|
19311
19818
|
}
|
|
19312
|
-
|
|
19313
|
-
|
|
19314
|
-
list2.forEach((u, i) => {
|
|
19315
|
-
tmp.m.compose(tmp.p.set(u.x, u.y, u.z), tmp.q.identity(), tmp.s.set(u.sx, u.sy, u.sz));
|
|
19316
|
-
mesh2.setMatrixAt(i, tmp.m);
|
|
19317
|
-
mesh2.setColorAt(i, tmp.c.setHex(u.colour));
|
|
19318
|
-
});
|
|
19319
|
-
mesh2.count = list2.length;
|
|
19320
|
-
mesh2.instanceMatrix.needsUpdate = true;
|
|
19321
|
-
if (mesh2.instanceColor) mesh2.instanceColor.needsUpdate = true;
|
|
19322
|
-
mesh2.castShadow = true;
|
|
19323
|
-
mesh2.receiveShadow = true;
|
|
19324
|
-
house.add(mesh2);
|
|
19325
|
-
if (!view.saved) view.saved = view.items.map((it) => ({ it, v0: it.v0, data: collapseRange(it.mesh, it.v0, it.v1) }));
|
|
19326
|
-
view.mesh = mesh2;
|
|
19327
|
-
view.units = list2.length;
|
|
19328
|
-
views.set(partId, view);
|
|
19819
|
+
paint(view);
|
|
19820
|
+
views.set(hostId, view);
|
|
19329
19821
|
invalidate();
|
|
19330
19822
|
invalidateShadow();
|
|
19331
19823
|
return true;
|
|
@@ -19336,30 +19828,40 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19336
19828
|
else s.data = collapseRange(s.it.mesh, s.v0, s.it.v1);
|
|
19337
19829
|
}
|
|
19338
19830
|
}
|
|
19831
|
+
function dispose(view) {
|
|
19832
|
+
view.units.removeFromParent();
|
|
19833
|
+
view.units.dispose();
|
|
19834
|
+
for (const m of view.shells) {
|
|
19835
|
+
m.removeFromParent();
|
|
19836
|
+
m.geometry.dispose();
|
|
19837
|
+
m.material.dispose();
|
|
19838
|
+
}
|
|
19839
|
+
view.mask.texture.dispose();
|
|
19840
|
+
}
|
|
19339
19841
|
return {
|
|
19340
|
-
/** Handles a `stage` event of the house's object. Returns true when the wall was
|
|
19842
|
+
/** Handles a `stage` event of the house's object. Returns true when the wall was cut. */
|
|
19341
19843
|
stage(event) {
|
|
19342
19844
|
return event.stage === 2 && !!event.opening ? draw2(event.part, { opening: event.opening }) : false;
|
|
19343
19845
|
},
|
|
19344
|
-
/** Handles a `chip` event: the unit that came out leaves a
|
|
19846
|
+
/** Handles a `chip` event: the unit that came out leaves a hole in the wall where it was. Returns true when the wall was cut. */
|
|
19345
19847
|
chip(event) {
|
|
19346
19848
|
return draw2(event.part, { chip: event.center });
|
|
19347
19849
|
},
|
|
19348
|
-
/** The part broke or fell:
|
|
19850
|
+
/** The part broke or fell: its damage goes (the plain range stays collapsed, the renderer hides the part). */
|
|
19349
19851
|
drop(partId) {
|
|
19350
19852
|
const v = views.get(partId);
|
|
19351
19853
|
if (!v) return;
|
|
19352
|
-
v
|
|
19353
|
-
v.mesh.dispose();
|
|
19854
|
+
dispose(v);
|
|
19354
19855
|
views.delete(partId);
|
|
19355
19856
|
},
|
|
19356
|
-
/** LOD 0 draws the
|
|
19857
|
+
/** LOD 0 draws the cut wall; LOD 1 and 2 give the wall its box back. */
|
|
19357
19858
|
setLod(level) {
|
|
19358
19859
|
const was = lod;
|
|
19359
19860
|
lod = level;
|
|
19360
19861
|
if (was === 0 === (level === 0)) return;
|
|
19361
19862
|
for (const v of views.values()) {
|
|
19362
|
-
v.
|
|
19863
|
+
v.units.visible = level === 0;
|
|
19864
|
+
for (const m of v.shells) m.visible = level === 0;
|
|
19363
19865
|
plain2(v, level !== 0);
|
|
19364
19866
|
}
|
|
19365
19867
|
invalidate();
|
|
@@ -19368,17 +19870,21 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19368
19870
|
/** Everything back to what it was (after `world.reset`). */
|
|
19369
19871
|
restore() {
|
|
19370
19872
|
for (const v of views.values()) {
|
|
19371
|
-
v
|
|
19372
|
-
v.mesh.dispose();
|
|
19873
|
+
dispose(v);
|
|
19373
19874
|
if (lod === 0) plain2(v, true);
|
|
19374
19875
|
}
|
|
19375
19876
|
views.clear();
|
|
19376
19877
|
},
|
|
19377
|
-
/** The wall
|
|
19878
|
+
/** The wall has damage drawn. */
|
|
19378
19879
|
has(partId) {
|
|
19379
19880
|
return views.has(partId);
|
|
19380
19881
|
},
|
|
19381
|
-
|
|
19882
|
+
/** Walls with damage drawn, units out of them, and units laid bare at the edge of the cuts. */
|
|
19883
|
+
statistics: () => ({
|
|
19884
|
+
walls: views.size,
|
|
19885
|
+
gone: [...views.values()].reduce((n, v) => n + v.gone, 0),
|
|
19886
|
+
units: [...views.values()].reduce((n, v) => n + v.units.count, 0)
|
|
19887
|
+
})
|
|
19382
19888
|
};
|
|
19383
19889
|
}
|
|
19384
19890
|
Object.assign(K, { createBreachView });
|
|
@@ -21495,6 +22001,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
21495
22001
|
return {
|
|
21496
22002
|
id: FLOORPLAN_AREA_ID,
|
|
21497
22003
|
name: text.name,
|
|
22004
|
+
ground: { surface: "lawn" },
|
|
21498
22005
|
group: text.group,
|
|
21499
22006
|
get houses() {
|
|
21500
22007
|
return placed;
|
|
@@ -22779,6 +23286,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
22779
23286
|
|
|
22780
23287
|
// src/furniture/spec.ts
|
|
22781
23288
|
var WOOD_THIN = 0.035;
|
|
23289
|
+
var BOARD_TOP_THICK = 0.07;
|
|
23290
|
+
var BOARD_TOP_WIDTH = 0.5;
|
|
23291
|
+
var BOARD_WIDTH = 0.16;
|
|
22782
23292
|
var SHELF_SPAN = 1.5;
|
|
22783
23293
|
var SHELVING = /shelf|bookcase|rack/;
|
|
22784
23294
|
function carcass(parts, drawn) {
|
|
@@ -22793,6 +23303,39 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
22793
23303
|
} else if (y <= x && y <= z) part.structure = { role: "beam", maxSpan: SHELF_SPAN * Math.max(x, z) };
|
|
22794
23304
|
}
|
|
22795
23305
|
}
|
|
23306
|
+
function splitTopsIntoBoards(drawn, materialOf2, carcassed) {
|
|
23307
|
+
const tops = /* @__PURE__ */ new Map();
|
|
23308
|
+
if (carcassed) return tops;
|
|
23309
|
+
for (let i = drawn.length - 1; i >= 0; i--) {
|
|
23310
|
+
const p = drawn[i];
|
|
23311
|
+
if (p.key !== "wood" || materialOf2.wood !== "wood_hard" || p.shape.kind !== "box" || p.rotation) continue;
|
|
23312
|
+
const [sx, sy, sz] = p.shape.size;
|
|
23313
|
+
const across = sx < sz ? 0 : 2;
|
|
23314
|
+
const width = Math.min(sx, sz);
|
|
23315
|
+
if (sy < WOOD_THIN || sy > BOARD_TOP_THICK || sy >= width || width < BOARD_TOP_WIDTH) continue;
|
|
23316
|
+
const count = Math.round(width / BOARD_WIDTH);
|
|
23317
|
+
const boards = [];
|
|
23318
|
+
for (let b = 0; b < count; b++) {
|
|
23319
|
+
const size = [sx, sy, sz];
|
|
23320
|
+
size[across] = width / count;
|
|
23321
|
+
const position = [...p.position];
|
|
23322
|
+
position[across] += (b + 0.5) * size[across] - width / 2;
|
|
23323
|
+
const min = [...p.min];
|
|
23324
|
+
const max = [...p.max];
|
|
23325
|
+
min[across] = position[across] - size[across] / 2;
|
|
23326
|
+
max[across] = position[across] + size[across] / 2;
|
|
23327
|
+
boards.push({ ...p, id: `${p.id}_b${b}`, shape: { kind: "box", size }, position, min, max, color: shadeBoard(p.color, b) });
|
|
23328
|
+
}
|
|
23329
|
+
drawn.splice(i, 1, ...boards);
|
|
23330
|
+
tops.set(p.id, boards);
|
|
23331
|
+
}
|
|
23332
|
+
return tops;
|
|
23333
|
+
}
|
|
23334
|
+
function shadeBoard(color2, board) {
|
|
23335
|
+
const k = [1, 0.94, 1.05, 0.97, 1.03][board % 5];
|
|
23336
|
+
const channel = (shift) => Math.min(255, Math.round((color2 >> shift & 255) * k)) << shift;
|
|
23337
|
+
return channel(16) | channel(8) | channel(0);
|
|
23338
|
+
}
|
|
22796
23339
|
var FLOOR_TOUCH = 5e-3;
|
|
22797
23340
|
var LOOSE = /* @__PURE__ */ new Set(["paper"]);
|
|
22798
23341
|
var BASE_MATERIAL = {
|
|
@@ -22870,10 +23413,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
22870
23413
|
};
|
|
22871
23414
|
run(A);
|
|
22872
23415
|
if (!drawn.length) throw new Error(`game-floorplan-three: the builder of "${name}" drew no box or cylinder`);
|
|
23416
|
+
const boardsOf = splitTopsIntoBoards(drawn, materialOf2, Boolean(opts.carcass));
|
|
22873
23417
|
const parts = drawn.map((p) => {
|
|
22874
23418
|
const thin = Math.min(...p.shape.kind === "box" ? p.shape.size : p.shape.kind === "cylinder" ? [p.shape.radius * 2, p.shape.height] : [p.shape.radius * 2]);
|
|
22875
23419
|
let material = materialOf2[p.key] ?? "plastic";
|
|
22876
|
-
if (p.key === "wood" && material === "wood_hard" &&
|
|
23420
|
+
if (p.key === "wood" && material === "wood_hard" && opts.carcass && !opts.materials?.wood) material = "mdf";
|
|
23421
|
+
else if (p.key === "wood" && material === "wood_hard" && thin < WOOD_THIN) material = "wood_soft";
|
|
22877
23422
|
const part = { id: p.id, material, shape: p.shape, position: p.position };
|
|
22878
23423
|
if (p.rotation) part.rotation = p.rotation;
|
|
22879
23424
|
if (!LOOSE.has(material) && p.min[1] <= FLOOR_TOUCH) part.anchored = true;
|
|
@@ -22908,6 +23453,13 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
22908
23453
|
if (area3 < 0.6 * footprint || p.max[1] < 0.2) continue;
|
|
22909
23454
|
if (!top || p.max[1] > top.y + 1e-6) top = { part: p.id, y: p.max[1] };
|
|
22910
23455
|
}
|
|
23456
|
+
for (const boards of boardsOf.values()) {
|
|
23457
|
+
if (top && boards[0].max[1] <= top.y - 1e-6) continue;
|
|
23458
|
+
const mid = boards[boards.length >> 1];
|
|
23459
|
+
const min = [Math.min(...boards.map((b) => b.min[0])), Math.min(...boards.map((b) => b.min[2]))];
|
|
23460
|
+
const max = [Math.max(...boards.map((b) => b.max[0])), Math.max(...boards.map((b) => b.max[2]))];
|
|
23461
|
+
if ((max[0] - min[0]) * (max[1] - min[1]) >= 0.6 * footprint && mid.max[1] >= 0.2) top = { part: mid.id, y: mid.max[1], parts: boards.map((b) => b.id) };
|
|
23462
|
+
}
|
|
22911
23463
|
if (opts.carcass) carcass(parts, byId);
|
|
22912
23464
|
const spec = { name, parts, joints, tags: ["furniture"] };
|
|
22913
23465
|
const colors = new Map(drawn.map((p) => [p.id, p.color]));
|