@adatechnology/game-floorplan-three 0.4.2 → 0.6.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 +13 -1
- package/dist/index.global.js +734 -92
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +167 -28
- package/dist/index.js.map +1 -1
- package/package.json +12 -12
package/dist/index.global.js
CHANGED
|
@@ -3551,8 +3551,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
3551
3551
|
var OPEN_ROOM_TYPES = /* @__PURE__ */ new Set(["balcony", "terrace", "veranda", "balcony_gourmet"]);
|
|
3552
3552
|
var rectsOf = (c) => c.parts && c.parts.length ? c.parts : [[c.u0, c.v0, c.u1, c.v1]];
|
|
3553
3553
|
var isOpenArea = (c) => OPEN_ROOM_TYPES.has(c.type) || c.outdoor === true;
|
|
3554
|
-
function planLod(
|
|
3555
|
-
const l =
|
|
3554
|
+
function planLod(houses2, focus, budget) {
|
|
3555
|
+
const l = houses2.map((c) => ({ c, d: c.distance(focus), n0: c.stats.drawCalls, n1: c.stats.drawCallsLOD1, level: 0 })).sort((a, b) => a.d - b.d);
|
|
3556
3556
|
let total = l.reduce((s, x) => s + x.n0, 0);
|
|
3557
3557
|
for (let i = l.length - 1; i >= 0 && total > budget; i--) {
|
|
3558
3558
|
total -= l[i].n0 - l[i].n1;
|
|
@@ -10695,12 +10695,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
10695
10695
|
A.invalidate = () => {
|
|
10696
10696
|
for (let i = 0; i < NS; i++) PL[i].shadow.needsUpdate = true;
|
|
10697
10697
|
};
|
|
10698
|
-
A.update = function(
|
|
10698
|
+
A.update = function(houses2, focus, op) {
|
|
10699
10699
|
op = op || {};
|
|
10700
|
-
|
|
10700
|
+
houses2 = (Array.isArray(houses2) ? houses2 : [houses2]).filter(Boolean);
|
|
10701
10701
|
_f.set(focus.x, focus.y, focus.z);
|
|
10702
10702
|
const rooms = [], exterior = [];
|
|
10703
|
-
for (const house of
|
|
10703
|
+
for (const house of houses2) {
|
|
10704
10704
|
if (house.group && !house.group.visible) continue;
|
|
10705
10705
|
house.house.updateMatrixWorld(true);
|
|
10706
10706
|
const M = house.house.matrixWorld, by = /* @__PURE__ */ new Map();
|
|
@@ -11232,6 +11232,47 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
11232
11232
|
return angle === 0 ? q : quat.mul(q, quat.fromAxisAngle([0, 0, 1], angle));
|
|
11233
11233
|
};
|
|
11234
11234
|
var clamp = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
11235
|
+
var splinterBox = (inp, box3, limit = inp.maxFragments) => {
|
|
11236
|
+
const { rnd } = inp;
|
|
11237
|
+
const dims = box3.s;
|
|
11238
|
+
const [fmin, fmax] = inp.fragmentSize;
|
|
11239
|
+
const g = inp.grain ?? [0, 1, 2].sort((a2, b2) => dims[b2] - dims[a2])[0];
|
|
11240
|
+
const [a, b] = [0, 1, 2].filter((i) => i !== g);
|
|
11241
|
+
const w = Math.max(fmin * 1.5, 0.01);
|
|
11242
|
+
const c = clamp(Math.round(dims[a] * dims[b] / (w * w)), 3, Math.max(3, Math.floor(limit / 2)));
|
|
11243
|
+
const maxLen = fmax * 4;
|
|
11244
|
+
const cells = bsp(box3, c, [a, b], rnd);
|
|
11245
|
+
const pieces = [];
|
|
11246
|
+
for (const cell of cells) {
|
|
11247
|
+
const extra = rnd() < 0.4 ? 1 : 0;
|
|
11248
|
+
const k = Math.max(1, Math.ceil(dims[g] / maxLen)) + extra;
|
|
11249
|
+
for (const p of bsp(cell, k, [g], rnd)) pieces.push(p);
|
|
11250
|
+
}
|
|
11251
|
+
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] }));
|
|
11252
|
+
};
|
|
11253
|
+
var severAt = (inp, root, at, gap) => {
|
|
11254
|
+
const dims = root.s;
|
|
11255
|
+
const L = [0, 1, 2].sort((x, y) => dims[y] - dims[x])[0];
|
|
11256
|
+
const half = dims[L] / 2;
|
|
11257
|
+
const lo = clamp(at - gap / 2, -half, half), hi = clamp(at + gap / 2, -half, half);
|
|
11258
|
+
const out = [];
|
|
11259
|
+
const stub = (from, to) => {
|
|
11260
|
+
if (to - from < 0.02) return;
|
|
11261
|
+
const c = [root.c[0], root.c[1], root.c[2]], sz = [dims[0], dims[1], dims[2]];
|
|
11262
|
+
c[L] = (from + to) / 2;
|
|
11263
|
+
sz[L] = to - from;
|
|
11264
|
+
out.push({ center: c, size: sz, q: quat.identity(), shape: "slab", weight: sz[0] * sz[1] * sz[2] });
|
|
11265
|
+
};
|
|
11266
|
+
stub(-half, lo);
|
|
11267
|
+
stub(hi, half);
|
|
11268
|
+
const band = { c: [root.c[0], root.c[1], root.c[2]], s: [dims[0], dims[1], dims[2]] };
|
|
11269
|
+
band.c[L] = (lo + hi) / 2;
|
|
11270
|
+
band.s[L] = Math.max(hi - lo, 0.01);
|
|
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);
|
|
11274
|
+
return out;
|
|
11275
|
+
};
|
|
11235
11276
|
var tearStack = (inp, root, t, u, v, n, sheet) => {
|
|
11236
11277
|
const { dims, rnd, maxFragments } = inp;
|
|
11237
11278
|
const layers = clamp(Math.ceil(dims[t] / STACK_LAYER), 2, 4);
|
|
@@ -11260,6 +11301,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
11260
11301
|
const t = sorted[0];
|
|
11261
11302
|
const [u, v] = [sorted[1], sorted[2]].sort((a, b) => a - b);
|
|
11262
11303
|
if (style === "snap") {
|
|
11304
|
+
if (inp.cut) return severAt(inp, root, inp.cut.at, inp.cut.gap);
|
|
11263
11305
|
const n2 = Math.min(maxFragments, 2 + Math.floor(rnd() * 3));
|
|
11264
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] });
|
|
11265
11307
|
return out;
|
|
@@ -11270,23 +11312,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
11270
11312
|
return out;
|
|
11271
11313
|
}
|
|
11272
11314
|
if (style === "splinter") {
|
|
11273
|
-
|
|
11274
|
-
const
|
|
11275
|
-
const w = Math.max(fmin * 1.5, 0.01);
|
|
11276
|
-
const c = clamp(Math.round(dims[a] * dims[b] / (w * w)), 3, Math.max(3, Math.floor(maxFragments / 2)));
|
|
11277
|
-
const maxLen = fmax * 4;
|
|
11278
|
-
const cells = bsp(root, c, [a, b], rnd);
|
|
11279
|
-
const pieces = [];
|
|
11280
|
-
for (const cell of cells) {
|
|
11281
|
-
let list2 = [cell];
|
|
11282
|
-
const extra = rnd() < 0.4 ? 1 : 0;
|
|
11283
|
-
const k = Math.max(1, Math.ceil(dims[g] / maxLen)) + extra;
|
|
11284
|
-
list2 = bsp(cell, k, [g], rnd);
|
|
11285
|
-
for (const p of list2) pieces.push(p);
|
|
11286
|
-
}
|
|
11287
|
-
for (const p of pieces.slice(0, maxFragments)) {
|
|
11288
|
-
out.push({ center: p.c, size: [p.s[g], p.s[a], p.s[b]], q: frame(g, a, b), shape: "splinter", weight: p.s[0] * p.s[1] * p.s[2] });
|
|
11289
|
-
}
|
|
11315
|
+
if (inp.cut) return severAt(inp, root, inp.cut.at, inp.cut.gap);
|
|
11316
|
+
for (const p of splinterBox(inp, root)) out.push(p);
|
|
11290
11317
|
return out;
|
|
11291
11318
|
}
|
|
11292
11319
|
if (style === "tear") {
|
|
@@ -11448,6 +11475,27 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
11448
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]]));
|
|
11449
11476
|
return out;
|
|
11450
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
|
+
};
|
|
11451
11499
|
var clipBarsToDisc = (bars, u, v, center, radius) => {
|
|
11452
11500
|
const out = [];
|
|
11453
11501
|
for (const [a, b] of bars) {
|
|
@@ -12368,14 +12416,21 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
12368
12416
|
smoke: "grey"
|
|
12369
12417
|
},
|
|
12370
12418
|
visual: {
|
|
12371
|
-
charColor: 1709072
|
|
12419
|
+
charColor: 1709072,
|
|
12420
|
+
innerColor: 14730131
|
|
12372
12421
|
},
|
|
12373
12422
|
tags: [
|
|
12374
12423
|
"wood",
|
|
12375
12424
|
"organic",
|
|
12376
12425
|
"flammable"
|
|
12377
12426
|
],
|
|
12378
|
-
compressive: 1e5
|
|
12427
|
+
compressive: 1e5,
|
|
12428
|
+
hits: {
|
|
12429
|
+
blunt: {
|
|
12430
|
+
tear: "splinters",
|
|
12431
|
+
tearJoules: 60
|
|
12432
|
+
}
|
|
12433
|
+
}
|
|
12379
12434
|
},
|
|
12380
12435
|
{
|
|
12381
12436
|
id: "wood_hard",
|
|
@@ -12405,14 +12460,21 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
12405
12460
|
smoke: "grey"
|
|
12406
12461
|
},
|
|
12407
12462
|
visual: {
|
|
12408
|
-
charColor: 1709072
|
|
12463
|
+
charColor: 1709072,
|
|
12464
|
+
innerColor: 13213802
|
|
12409
12465
|
},
|
|
12410
12466
|
tags: [
|
|
12411
12467
|
"wood",
|
|
12412
12468
|
"organic",
|
|
12413
12469
|
"flammable"
|
|
12414
12470
|
],
|
|
12415
|
-
compressive: 15e4
|
|
12471
|
+
compressive: 15e4,
|
|
12472
|
+
hits: {
|
|
12473
|
+
blunt: {
|
|
12474
|
+
tear: "splinters",
|
|
12475
|
+
tearJoules: 150
|
|
12476
|
+
}
|
|
12477
|
+
}
|
|
12416
12478
|
},
|
|
12417
12479
|
{
|
|
12418
12480
|
id: "plywood",
|
|
@@ -12442,14 +12504,24 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
12442
12504
|
smoke: "grey"
|
|
12443
12505
|
},
|
|
12444
12506
|
visual: {
|
|
12445
|
-
charColor: 1709072
|
|
12507
|
+
charColor: 1709072,
|
|
12508
|
+
innerColor: 14862234
|
|
12446
12509
|
},
|
|
12447
12510
|
tags: [
|
|
12448
12511
|
"wood",
|
|
12449
12512
|
"organic",
|
|
12450
12513
|
"flammable"
|
|
12451
12514
|
],
|
|
12452
|
-
compressive: 6e4
|
|
12515
|
+
compressive: 6e4,
|
|
12516
|
+
hits: {
|
|
12517
|
+
ballistic: {
|
|
12518
|
+
tear: "splinters"
|
|
12519
|
+
},
|
|
12520
|
+
blunt: {
|
|
12521
|
+
tear: "splinters",
|
|
12522
|
+
tearJoules: 80
|
|
12523
|
+
}
|
|
12524
|
+
}
|
|
12453
12525
|
},
|
|
12454
12526
|
{
|
|
12455
12527
|
id: "mdf",
|
|
@@ -12476,14 +12548,25 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
12476
12548
|
smoke: "grey"
|
|
12477
12549
|
},
|
|
12478
12550
|
visual: {
|
|
12479
|
-
charColor: 1709072
|
|
12551
|
+
charColor: 1709072,
|
|
12552
|
+
innerColor: 12096362
|
|
12480
12553
|
},
|
|
12481
12554
|
tags: [
|
|
12482
12555
|
"wood",
|
|
12483
12556
|
"organic",
|
|
12484
12557
|
"flammable"
|
|
12485
12558
|
],
|
|
12486
|
-
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
|
+
}
|
|
12487
12570
|
},
|
|
12488
12571
|
{
|
|
12489
12572
|
id: "glass",
|
|
@@ -12597,6 +12680,17 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
12597
12680
|
damageScale: {
|
|
12598
12681
|
explosive: 1.2
|
|
12599
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
|
+
},
|
|
12600
12694
|
hitDebris: "chips",
|
|
12601
12695
|
breakDebris: "dust",
|
|
12602
12696
|
thermal: {
|
|
@@ -12965,7 +13059,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
12965
13059
|
smoke: "white"
|
|
12966
13060
|
},
|
|
12967
13061
|
visual: {
|
|
12968
|
-
charColor: 1709072
|
|
13062
|
+
charColor: 1709072,
|
|
13063
|
+
innerColor: 14207126
|
|
12969
13064
|
},
|
|
12970
13065
|
tags: [
|
|
12971
13066
|
"plant",
|
|
@@ -12973,7 +13068,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
12973
13068
|
"organic",
|
|
12974
13069
|
"flammable"
|
|
12975
13070
|
],
|
|
12976
|
-
compressive: 6e4
|
|
13071
|
+
compressive: 6e4,
|
|
13072
|
+
hits: {
|
|
13073
|
+
blunt: {
|
|
13074
|
+
tear: "none"
|
|
13075
|
+
}
|
|
13076
|
+
}
|
|
12977
13077
|
},
|
|
12978
13078
|
{
|
|
12979
13079
|
id: "cactus",
|
|
@@ -13088,6 +13188,130 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
13088
13188
|
"masonry"
|
|
13089
13189
|
],
|
|
13090
13190
|
compressive: 2e4
|
|
13191
|
+
},
|
|
13192
|
+
{
|
|
13193
|
+
id: "asphalt",
|
|
13194
|
+
density: 2350,
|
|
13195
|
+
hpPerKg: 10,
|
|
13196
|
+
penetration: 14,
|
|
13197
|
+
penetrationClass: "concrete",
|
|
13198
|
+
penetrationScale: 0.8,
|
|
13199
|
+
ductility: 0.1,
|
|
13200
|
+
fracture: "crumble",
|
|
13201
|
+
fragmentSize: [
|
|
13202
|
+
0.03,
|
|
13203
|
+
0.15
|
|
13204
|
+
],
|
|
13205
|
+
hitDebris: "chips",
|
|
13206
|
+
breakDebris: "dust",
|
|
13207
|
+
thermal: {
|
|
13208
|
+
conductivity: 0.1,
|
|
13209
|
+
heatCapacity: 2,
|
|
13210
|
+
ignitionC: 400,
|
|
13211
|
+
burnRate: 2e-3,
|
|
13212
|
+
smoke: "black"
|
|
13213
|
+
},
|
|
13214
|
+
tags: [
|
|
13215
|
+
"masonry",
|
|
13216
|
+
"brittle"
|
|
13217
|
+
],
|
|
13218
|
+
compressive: 3e4
|
|
13219
|
+
},
|
|
13220
|
+
{
|
|
13221
|
+
id: "sand",
|
|
13222
|
+
density: 1600,
|
|
13223
|
+
hpPerKg: 2,
|
|
13224
|
+
penetration: 6,
|
|
13225
|
+
ductility: 0.4,
|
|
13226
|
+
fracture: "crumble",
|
|
13227
|
+
fragmentSize: [
|
|
13228
|
+
0.01,
|
|
13229
|
+
0.04
|
|
13230
|
+
],
|
|
13231
|
+
hitDebris: "dust",
|
|
13232
|
+
breakDebris: "dust",
|
|
13233
|
+
thermal: {
|
|
13234
|
+
conductivity: 0.1,
|
|
13235
|
+
heatCapacity: 2
|
|
13236
|
+
},
|
|
13237
|
+
tags: [
|
|
13238
|
+
"soil"
|
|
13239
|
+
],
|
|
13240
|
+
compressive: 1e4
|
|
13241
|
+
},
|
|
13242
|
+
{
|
|
13243
|
+
id: "gravel",
|
|
13244
|
+
density: 1800,
|
|
13245
|
+
hpPerKg: 3,
|
|
13246
|
+
penetration: 8,
|
|
13247
|
+
ductility: 0.3,
|
|
13248
|
+
fracture: "crumble",
|
|
13249
|
+
fragmentSize: [
|
|
13250
|
+
0.02,
|
|
13251
|
+
0.06
|
|
13252
|
+
],
|
|
13253
|
+
hitDebris: "chips",
|
|
13254
|
+
breakDebris: "dust",
|
|
13255
|
+
thermal: {
|
|
13256
|
+
conductivity: 0.1,
|
|
13257
|
+
heatCapacity: 2
|
|
13258
|
+
},
|
|
13259
|
+
tags: [
|
|
13260
|
+
"soil",
|
|
13261
|
+
"masonry"
|
|
13262
|
+
],
|
|
13263
|
+
compressive: 2e4
|
|
13264
|
+
},
|
|
13265
|
+
{
|
|
13266
|
+
id: "turf",
|
|
13267
|
+
density: 900,
|
|
13268
|
+
hpPerKg: 3,
|
|
13269
|
+
penetration: 4,
|
|
13270
|
+
ductility: 0.5,
|
|
13271
|
+
fracture: "crumble",
|
|
13272
|
+
fragmentSize: [
|
|
13273
|
+
0.03,
|
|
13274
|
+
0.1
|
|
13275
|
+
],
|
|
13276
|
+
hitDebris: "dust",
|
|
13277
|
+
breakDebris: "dust",
|
|
13278
|
+
thermal: {
|
|
13279
|
+
conductivity: 0.1,
|
|
13280
|
+
heatCapacity: 1.5,
|
|
13281
|
+
ignitionC: 250,
|
|
13282
|
+
burnRate: 0.03,
|
|
13283
|
+
smoke: "white"
|
|
13284
|
+
},
|
|
13285
|
+
tags: [
|
|
13286
|
+
"soil",
|
|
13287
|
+
"organic"
|
|
13288
|
+
],
|
|
13289
|
+
compressive: 15e3
|
|
13290
|
+
},
|
|
13291
|
+
{
|
|
13292
|
+
id: "mortar",
|
|
13293
|
+
density: 1900,
|
|
13294
|
+
hpPerKg: 10,
|
|
13295
|
+
penetration: 12,
|
|
13296
|
+
penetrationClass: "concrete",
|
|
13297
|
+
penetrationScale: 1.2,
|
|
13298
|
+
ductility: 0.03,
|
|
13299
|
+
fracture: "crumble",
|
|
13300
|
+
fragmentSize: [
|
|
13301
|
+
0.02,
|
|
13302
|
+
0.08
|
|
13303
|
+
],
|
|
13304
|
+
hitDebris: "chips",
|
|
13305
|
+
breakDebris: "dust",
|
|
13306
|
+
thermal: {
|
|
13307
|
+
conductivity: 0.15,
|
|
13308
|
+
heatCapacity: 2.5
|
|
13309
|
+
},
|
|
13310
|
+
tags: [
|
|
13311
|
+
"masonry",
|
|
13312
|
+
"brittle"
|
|
13313
|
+
],
|
|
13314
|
+
compressive: 1e5
|
|
13091
13315
|
}
|
|
13092
13316
|
];
|
|
13093
13317
|
var material_hints_default = {
|
|
@@ -13261,6 +13485,32 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
13261
13485
|
return (1 + 4 * (0.3 - ductility) / 0.3) * Math.min(25, Math.max(1, (0.02 / Math.max(thickness, 1e-4)) ** 2));
|
|
13262
13486
|
};
|
|
13263
13487
|
var THIN_SHEET_DISHING = 5e-3;
|
|
13488
|
+
var SWEEP_BACK = 0.01;
|
|
13489
|
+
function sweepOrigins(box3, d, spacing) {
|
|
13490
|
+
const origins = [];
|
|
13491
|
+
const line = (from, to) => {
|
|
13492
|
+
const n = Math.max(1, Math.ceil((to - from) / spacing));
|
|
13493
|
+
return Array.from({ length: n + 1 }, (_, i) => from + (to - from) * i / n);
|
|
13494
|
+
};
|
|
13495
|
+
for (const axis of [0, 1, 2]) {
|
|
13496
|
+
if (Math.abs(d[axis]) < 1e-3) continue;
|
|
13497
|
+
const lead = d[axis] > 0 ? box3.max[axis] - SWEEP_BACK : box3.min[axis] + SWEEP_BACK;
|
|
13498
|
+
const u = (axis + 1) % 3;
|
|
13499
|
+
const v = (axis + 2) % 3;
|
|
13500
|
+
for (const a of line(box3.min[u], box3.max[u])) {
|
|
13501
|
+
for (const b of line(box3.min[v], box3.max[v])) {
|
|
13502
|
+
const point = [0, 0, 0];
|
|
13503
|
+
point[axis] = lead;
|
|
13504
|
+
point[u] = a;
|
|
13505
|
+
point[v] = b;
|
|
13506
|
+
origins.push(point);
|
|
13507
|
+
}
|
|
13508
|
+
}
|
|
13509
|
+
}
|
|
13510
|
+
return origins;
|
|
13511
|
+
}
|
|
13512
|
+
var THIN_SHEET_CRUSH = 5e-4;
|
|
13513
|
+
var THIN_SHEET_CONTACT = 0.025;
|
|
13264
13514
|
var DMG_PER_J = 0.02;
|
|
13265
13515
|
var BLAST_COUPLING = 0.1;
|
|
13266
13516
|
var SHELTER_DEFAULTS = { k: 2800, brokenTransmission: 0.5, minPath: 5e-3 };
|
|
@@ -13274,9 +13524,17 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
13274
13524
|
var BREACH_AREA = 0.25;
|
|
13275
13525
|
var BREACH_BLAST_REACH = 0.12;
|
|
13276
13526
|
var RESIDUAL_CAPACITY = 0.15;
|
|
13527
|
+
var SEVER_BAND = 0.1;
|
|
13528
|
+
var SEVER_ASPECT = 3;
|
|
13529
|
+
var SEVER_BALLISTIC = 2;
|
|
13530
|
+
var TEAR_JOULES = 150;
|
|
13531
|
+
var tears = 0;
|
|
13277
13532
|
var SUPPORT_TOLERANCE = 0.05;
|
|
13278
13533
|
var UNIT_FRAGMENT_FACTOR = 4;
|
|
13279
13534
|
var BREACH_GRID_CAP = 4096;
|
|
13535
|
+
var CHIP_ENERGY = 200;
|
|
13536
|
+
var CHIP_SPLASH = 0.5;
|
|
13537
|
+
var BAR_BOW = 0.08;
|
|
13280
13538
|
var CUT_SLICE = 1;
|
|
13281
13539
|
var cutFactorOf = (shape) => clamp2(Math.max(...shapeDims(shape)) / CUT_SLICE, 1, 50);
|
|
13282
13540
|
var clamp2 = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
@@ -13286,8 +13544,24 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
13286
13544
|
return v === void 0 ? void 0 : v * (mat.penetrationScale ?? 1);
|
|
13287
13545
|
};
|
|
13288
13546
|
var capacityScale = (p) => {
|
|
13289
|
-
|
|
13290
|
-
|
|
13547
|
+
const residual = p.spec.reinforcement?.residualCapacity ?? RESIDUAL_CAPACITY;
|
|
13548
|
+
if (p.st.stripped) return residual;
|
|
13549
|
+
const kept = Math.min(p.rt.removed ? 1 - p.rt.removed / p.mass0 : 1, 1 - sectionLoss(p));
|
|
13550
|
+
return Math.max(p.spec.reinforcement ? residual : 0.05, kept);
|
|
13551
|
+
};
|
|
13552
|
+
var sectionLoss = (p) => {
|
|
13553
|
+
const cells = p.st.chipped;
|
|
13554
|
+
if (!cells?.length) return 0;
|
|
13555
|
+
const [u, v] = planeAxes(p.thin);
|
|
13556
|
+
const along = p.dims[u] >= p.dims[v] ? u : v;
|
|
13557
|
+
const across = along === u ? v : u;
|
|
13558
|
+
let worst = 0;
|
|
13559
|
+
for (const c of cells) {
|
|
13560
|
+
let gone = 0;
|
|
13561
|
+
for (const d of cells) if (Math.abs(d.center[along] - c.center[along]) < d.size[along] / 2 + 1e-6) gone += d.size[across];
|
|
13562
|
+
worst = Math.max(worst, gone / Math.max(p.dims[across], 1e-6));
|
|
13563
|
+
}
|
|
13564
|
+
return Math.min(1, worst);
|
|
13291
13565
|
};
|
|
13292
13566
|
var isLost = (p) => p.st.status === "broken" || p.st.status === "melted" || p.st.status === "ashes";
|
|
13293
13567
|
var clone = (v) => structuredClone(v);
|
|
@@ -13320,6 +13594,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
13320
13594
|
let windCfg = windOpts ? resolveWind(windOpts) : null;
|
|
13321
13595
|
const ignitionRules = resolveIgnitionRules(options.ignition);
|
|
13322
13596
|
const breachBlastFactor = options.breach?.blastFactor ?? BREACH_BLAST_FACTOR;
|
|
13597
|
+
const chipEnergy = options.breach?.chipEnergy ?? CHIP_ENERGY;
|
|
13323
13598
|
const rng2 = createRng(options.seed ?? 1);
|
|
13324
13599
|
const rnd = () => rng2.next();
|
|
13325
13600
|
let time = 0;
|
|
@@ -13693,41 +13968,68 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
13693
13968
|
markDamaged(p);
|
|
13694
13969
|
if (p.rt.hinge >= flexure.failRotationDeg * Math.PI / 180) hingeDrop(o, p, src);
|
|
13695
13970
|
};
|
|
13971
|
+
const freeSpan = (p, supports) => {
|
|
13972
|
+
if (supports.length === 0) return Infinity;
|
|
13973
|
+
const d = p.dims;
|
|
13974
|
+
const L = [0, 1, 2].sort((a, b) => d[b] - d[a])[0];
|
|
13975
|
+
const spans = supports.map((k) => contactBox(p, k)).map(([lo, hi]) => [lo[L], hi[L]]);
|
|
13976
|
+
let far = 0;
|
|
13977
|
+
for (let i = 0; i <= 16; i++) {
|
|
13978
|
+
const x = (i / 16 - 0.5) * d[L];
|
|
13979
|
+
let near = Infinity;
|
|
13980
|
+
for (const [lo, hi] of spans) near = Math.min(near, Math.max(lo - x, x - hi, 0));
|
|
13981
|
+
far = Math.max(far, near);
|
|
13982
|
+
}
|
|
13983
|
+
return 2 * far;
|
|
13984
|
+
};
|
|
13696
13985
|
const solveStructure = (o) => {
|
|
13697
13986
|
o.maxStress = 0;
|
|
13698
13987
|
if (!o.hasStructure) return;
|
|
13699
13988
|
const parts = o.parts;
|
|
13700
13989
|
const n = parts.length;
|
|
13701
13990
|
const dist = new Array(n).fill(-1);
|
|
13702
|
-
const queue = [];
|
|
13703
13991
|
const ok = (i) => parts[i].structural && !isLost(parts[i]);
|
|
13704
13992
|
const isPartition = (i) => parts[i].spec.structure?.role === "partition";
|
|
13705
|
-
|
|
13706
|
-
|
|
13707
|
-
|
|
13708
|
-
|
|
13709
|
-
|
|
13710
|
-
|
|
13711
|
-
if (
|
|
13712
|
-
|
|
13713
|
-
|
|
13714
|
-
|
|
13715
|
-
|
|
13716
|
-
|
|
13717
|
-
|
|
13993
|
+
const out = /* @__PURE__ */ new Set();
|
|
13994
|
+
let queue = [];
|
|
13995
|
+
let lowerOf = /* @__PURE__ */ new Map();
|
|
13996
|
+
for (; ; ) {
|
|
13997
|
+
dist.fill(-1);
|
|
13998
|
+
queue = [];
|
|
13999
|
+
for (let i = 0; i < n; i++) if (ok(i) && !out.has(i) && parts[i].anchored) {
|
|
14000
|
+
dist[i] = 0;
|
|
14001
|
+
queue.push(i);
|
|
14002
|
+
}
|
|
14003
|
+
for (let h = 0; h < queue.length; h++) {
|
|
14004
|
+
const s = queue[h];
|
|
14005
|
+
if (isPartition(s)) continue;
|
|
14006
|
+
for (const j of o.byPart.get(parts[s].spec.id)) {
|
|
14007
|
+
if (j.st.open || !j.bears) continue;
|
|
14008
|
+
const r = j.ia === s ? j.ib : j.ia;
|
|
14009
|
+
if (dist[r] >= 0 || !ok(r) || out.has(r) || restsOn(o, j, r) !== s) continue;
|
|
14010
|
+
dist[r] = dist[s] + 1;
|
|
14011
|
+
queue.push(r);
|
|
14012
|
+
}
|
|
13718
14013
|
}
|
|
13719
|
-
|
|
13720
|
-
|
|
13721
|
-
|
|
13722
|
-
|
|
13723
|
-
|
|
13724
|
-
|
|
13725
|
-
|
|
13726
|
-
|
|
13727
|
-
|
|
14014
|
+
lowerOf = /* @__PURE__ */ new Map();
|
|
14015
|
+
let dropped = false;
|
|
14016
|
+
for (const i of queue) {
|
|
14017
|
+
const lower = [];
|
|
14018
|
+
for (const j of o.byPart.get(parts[i].spec.id)) {
|
|
14019
|
+
if (j.st.open || !j.bears) continue;
|
|
14020
|
+
const k = restsOn(o, j, i);
|
|
14021
|
+
if (k >= 0 && dist[k] >= 0 && dist[k] < dist[i] && !isPartition(k)) lower.push(k);
|
|
14022
|
+
}
|
|
14023
|
+
lowerOf.set(i, lower);
|
|
14024
|
+
const max = parts[i].spec.structure?.maxSpan;
|
|
14025
|
+
if (max !== void 0 && !parts[i].anchored && freeSpan(parts[i], lower.map((k) => parts[k])) > max) {
|
|
14026
|
+
out.add(i);
|
|
14027
|
+
dropped = true;
|
|
14028
|
+
}
|
|
13728
14029
|
}
|
|
13729
|
-
|
|
14030
|
+
if (!dropped) break;
|
|
13730
14031
|
}
|
|
14032
|
+
const incoming = new Array(n).fill(0);
|
|
13731
14033
|
const bending = new Array(n).fill(0);
|
|
13732
14034
|
const through = parts.map(() => []);
|
|
13733
14035
|
const pass = (k, at, mass) => {
|
|
@@ -14239,6 +14541,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14239
14541
|
const r = rayPart(o, p, origin, d);
|
|
14240
14542
|
if (!r || r.t0 < 0 || r.t0 > maxDist) continue;
|
|
14241
14543
|
if (p.st.opening && insideOpening(o, p, vec.madd(origin, d, r.t0))) continue;
|
|
14544
|
+
if (p.st.chipped && insideChip(p, objLocal(o, vec.madd(origin, d, r.t0)))) continue;
|
|
14242
14545
|
if (!best || r.t0 < best.distance) best = { object: o.id, part: p.spec.id, point: vec.madd(origin, d, r.t0), normal: r.n, distance: r.t0, pathLen: r.t1 - r.t0 };
|
|
14243
14546
|
}
|
|
14244
14547
|
}
|
|
@@ -14439,6 +14742,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14439
14742
|
debris(o, p, layer.mat.hitDebris, wp, normal, 5, 2, src);
|
|
14440
14743
|
setStage(o, p, 1, {}, src);
|
|
14441
14744
|
};
|
|
14745
|
+
const crater = (o, p, wp, normal, radius, src) => {
|
|
14746
|
+
markDamaged(p);
|
|
14747
|
+
emit({ ...base(o, src), type: "spall", part: p.spec.id, layer: -1, point: wp, area: Math.PI * radius * radius });
|
|
14748
|
+
debris(o, p, p.mat.breakDebris, wp, normal, 6, 1.5, src);
|
|
14749
|
+
setStage(o, p, 1, {}, src);
|
|
14750
|
+
};
|
|
14442
14751
|
const insideOpening = (o, p, objectPoint) => {
|
|
14443
14752
|
const op = p.st.opening;
|
|
14444
14753
|
if (!op) return false;
|
|
@@ -14502,6 +14811,91 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14502
14811
|
if (total < (p.spec.breach?.area ?? BREACH_AREA)) return;
|
|
14503
14812
|
openBreach(o, p, [sum[0] / total, sum[1] / total, sum[2] / total], total, energy, dir, src);
|
|
14504
14813
|
};
|
|
14814
|
+
const unitGrids = /* @__PURE__ */ new WeakMap();
|
|
14815
|
+
const gridOf = (p) => {
|
|
14816
|
+
let grid = unitGrids.get(p);
|
|
14817
|
+
if (!grid) unitGrids.set(p, grid = unitGrid(p.dims, p.thin, unitsOf2(p), BREACH_GRID_CAP));
|
|
14818
|
+
return grid;
|
|
14819
|
+
};
|
|
14820
|
+
const insideChip = (p, objectPoint) => {
|
|
14821
|
+
const cells = p.st.chipped;
|
|
14822
|
+
if (!cells?.length) return false;
|
|
14823
|
+
const [u, v] = planeAxes(p.thin);
|
|
14824
|
+
const lp = toLocal(p.pos, p.q, objectPoint);
|
|
14825
|
+
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);
|
|
14826
|
+
};
|
|
14827
|
+
const chipUnits = (o, p, kind, wp, hitEnergy, dir, radius, src) => {
|
|
14828
|
+
var _a, _b;
|
|
14829
|
+
if (!p.spec.units || !p.staged || chipEnergy <= 0 || p.st.stripped || isLost(p) || (p.st.stage ?? 0) >= 2) return;
|
|
14830
|
+
const rule = p.mat.hits?.[kind];
|
|
14831
|
+
const perKg = rule?.unitJoules ?? chipEnergy;
|
|
14832
|
+
const energy = hitEnergy - (rule?.floor ?? 0);
|
|
14833
|
+
if (perKg <= 0 || energy <= 0) return;
|
|
14834
|
+
const [u, v] = planeAxes(p.thin);
|
|
14835
|
+
const lp = toLocal(p.pos, p.q, objLocal(o, wp));
|
|
14836
|
+
const grid = gridOf(p);
|
|
14837
|
+
const worn = (_a = p.rt).chips ?? (_a.chips = {});
|
|
14838
|
+
const out = [];
|
|
14839
|
+
for (let i = 0; i < grid.length; i++) {
|
|
14840
|
+
const cell = grid[i];
|
|
14841
|
+
if (worn[i] === -1) continue;
|
|
14842
|
+
const gap = Math.hypot(Math.max(0, Math.abs(lp[u] - cell.center[u]) - cell.size[u] / 2), Math.max(0, Math.abs(lp[v] - cell.center[v]) - cell.size[v] / 2));
|
|
14843
|
+
if (gap > radius) continue;
|
|
14844
|
+
const share = gap === 0 ? 1 : CHIP_SPLASH * (1 - gap / radius);
|
|
14845
|
+
const total = (worn[i] ?? 0) + energy * share;
|
|
14846
|
+
const mass = p.mat.density * cell.size[0] * cell.size[1] * cell.size[2];
|
|
14847
|
+
if (total < perKg * mass) {
|
|
14848
|
+
worn[i] = total;
|
|
14849
|
+
continue;
|
|
14850
|
+
}
|
|
14851
|
+
worn[i] = -1;
|
|
14852
|
+
out.push(cell);
|
|
14853
|
+
}
|
|
14854
|
+
if (out.length === 0) return;
|
|
14855
|
+
const { c, q } = partXf(o, p);
|
|
14856
|
+
const chipped = (_b = p.st).chipped ?? (_b.chipped = []);
|
|
14857
|
+
const holes = localHolesOf(p);
|
|
14858
|
+
for (const cell of out) {
|
|
14859
|
+
const mass = Math.min(p.st.mass, p.mat.density * cell.size[0] * cell.size[1] * cell.size[2]);
|
|
14860
|
+
const center = toWorld(c, q, cell.center);
|
|
14861
|
+
const speed = clamp2(Math.sqrt(2 * energy * 0.05 / Math.max(mass, 0.05)), 0.5, 6);
|
|
14862
|
+
const fragments = cellFragments(o, p, [cell], wp, dir, speed, mass, `c${chipped.length}_`);
|
|
14863
|
+
p.st.holes = p.st.holes.filter((_, k) => marksInBox(cell.center, cell.size, void 0, [holes[k]]).length === 0);
|
|
14864
|
+
chipped.push({ center: [cell.center[0], cell.center[1], cell.center[2]], size: [cell.size[0], cell.size[1], cell.size[2]] });
|
|
14865
|
+
p.st.mass = Math.max(0, p.st.mass - mass);
|
|
14866
|
+
p.rt.removed = (p.rt.removed ?? 0) + mass;
|
|
14867
|
+
markDamaged(p);
|
|
14868
|
+
setStage(o, p, 1, {}, src);
|
|
14869
|
+
emit({ ...base(o, src), type: "chip", part: p.spec.id, center, size: [cell.size[0], cell.size[1], cell.size[2]], rotation: q, fragments });
|
|
14870
|
+
debris(o, p, p.mat.breakDebris, center, vec.scale(dir ?? [0, 1, 0], -1), 6, 2, src);
|
|
14871
|
+
}
|
|
14872
|
+
if (p.spec.reinforcement) exposeRebar(o, p, src);
|
|
14873
|
+
for (const cell of out) addBreach(o, p, toWorld(c, q, cell.center), cell.size[u] * cell.size[v], energy, dir, src);
|
|
14874
|
+
if (p.structural && !isLost(p)) {
|
|
14875
|
+
solveStructure(o);
|
|
14876
|
+
collapseCheck(o, src);
|
|
14877
|
+
}
|
|
14878
|
+
};
|
|
14879
|
+
const exposeRebar = (o, p, src) => {
|
|
14880
|
+
const r = p.spec.reinforcement;
|
|
14881
|
+
if (!r) return;
|
|
14882
|
+
const [u, v] = planeAxes(p.thin);
|
|
14883
|
+
const all = rebarBars(p.dims, p.thin, r.spacing);
|
|
14884
|
+
let bars;
|
|
14885
|
+
if (p.st.stripped) bars = all;
|
|
14886
|
+
else {
|
|
14887
|
+
bars = [];
|
|
14888
|
+
for (const cell of p.st.chipped ?? []) bars.push(...clipBarsToBox(all, u, v, cell.center, cell.size));
|
|
14889
|
+
const op = p.st.opening;
|
|
14890
|
+
if (op) {
|
|
14891
|
+
const lc = toLocal(p.pos, p.q, op.center);
|
|
14892
|
+
bars.push(...clipBarsToDisc(all, u, v, [lc[u], lc[v]], op.radius + 0.05));
|
|
14893
|
+
}
|
|
14894
|
+
}
|
|
14895
|
+
const bow = clamp2(((p.st.stress ?? 0) - 1) / 0.5, 0, 1);
|
|
14896
|
+
if (bow > 0) bars = bars.flatMap((bar2, i) => bendBar(bar2, p.thin, bow * BAR_BOW, i));
|
|
14897
|
+
emit({ ...base(o, src), type: "expose", part: p.spec.id, bars: worldBars(o, p, bars) });
|
|
14898
|
+
};
|
|
14505
14899
|
const openBreach = (o, p, center, total, energy, dir, src) => {
|
|
14506
14900
|
const [u, v] = planeAxes(p.thin);
|
|
14507
14901
|
const radius = clamp2(Math.sqrt(total / Math.PI), Math.sqrt((p.spec.breach?.area ?? BREACH_AREA) / Math.PI), breachRadius);
|
|
@@ -14513,7 +14907,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14513
14907
|
failPart(o, p, worldCenter, dir, energy, 12, src);
|
|
14514
14908
|
return;
|
|
14515
14909
|
}
|
|
14516
|
-
const
|
|
14910
|
+
const worn = p.rt.chips;
|
|
14911
|
+
const cells = cellsWithin(gridOf(p).filter((_, i) => worn?.[i] !== -1), p.thin, lc, radius);
|
|
14517
14912
|
const removed = p.st.mass * cells.reduce((sum, cell) => sum + cell.size[u] * cell.size[v], 0) / (p.dims[u] * p.dims[v]);
|
|
14518
14913
|
const speed = clamp2(Math.sqrt(2 * energy * 0.1 / Math.max(removed, 0.05)), 0.5, 8);
|
|
14519
14914
|
const fragments = cellFragments(o, p, cells, worldCenter, dir, speed, removed, "b");
|
|
@@ -14527,10 +14922,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14527
14922
|
setStage(o, p, 2, { opening: { center: worldCenter, radius }, fragments }, src);
|
|
14528
14923
|
debris(o, p, p.mat.breakDebris, worldCenter, vec.scale(dir ?? [0, 1, 0], -1), 14, 3, src);
|
|
14529
14924
|
const reinforcement = p.spec.reinforcement;
|
|
14530
|
-
if (reinforcement)
|
|
14531
|
-
const bars = worldBars(o, p, clipBarsToDisc(rebarBars(p.dims, p.thin, reinforcement.spacing), u, v, [lc[u], lc[v]], radius + 0.05));
|
|
14532
|
-
if (bars.length > 0) emit({ ...base(o, src), type: "expose", part: p.spec.id, bars });
|
|
14533
|
-
}
|
|
14925
|
+
if (reinforcement) exposeRebar(o, p, src);
|
|
14534
14926
|
if (p.structural) solveStructure(o);
|
|
14535
14927
|
};
|
|
14536
14928
|
const stripPart = (o, p, reinforcement, src) => {
|
|
@@ -14542,8 +14934,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14542
14934
|
p.st.burning = false;
|
|
14543
14935
|
p.rt.jet = false;
|
|
14544
14936
|
markDamaged(p);
|
|
14545
|
-
emit({ ...base(o, src), type: "expose", part: p.spec.id, bars: worldBars(o, p, bars) });
|
|
14546
14937
|
solveStructure(o);
|
|
14938
|
+
exposeRebar(o, p, src);
|
|
14547
14939
|
collapseCheck(o, src);
|
|
14548
14940
|
};
|
|
14549
14941
|
const unitFragmentPlan = (p, cap) => {
|
|
@@ -14597,6 +14989,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14597
14989
|
const grain = p.spec.grain ? AXIS[p.spec.grain] : void 0;
|
|
14598
14990
|
const hitLocal = toLocal(c, q, hitW);
|
|
14599
14991
|
const unitPlan = p.spec.units ? unitFragmentPlan(p, maxFragments * UNIT_FRAGMENT_FACTOR) : [];
|
|
14992
|
+
const axis = style === "splinter" || style === "snap" ? severAxis(p) : -1;
|
|
14993
|
+
const cutAt2 = axis < 0 ? void 0 : p.rt.cutAt ?? (origin ? hitLocal[axis] : void 0);
|
|
14600
14994
|
const plan = unitPlan.length > 0 ? unitPlan : planFragments({
|
|
14601
14995
|
style,
|
|
14602
14996
|
dims: p.dims,
|
|
@@ -14606,7 +15000,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14606
15000
|
fragmentSize: p.mat.fragmentSize ?? [0.04, 0.2],
|
|
14607
15001
|
maxFragments,
|
|
14608
15002
|
rnd,
|
|
14609
|
-
sheet: p.mat.sheet
|
|
15003
|
+
sheet: p.mat.sheet,
|
|
15004
|
+
...cutAt2 !== void 0 ? { cut: { at: cutAt2, gap: SEVER_BAND } } : {}
|
|
14610
15005
|
});
|
|
14611
15006
|
const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
|
|
14612
15007
|
const mass = p.st.mass;
|
|
@@ -14665,10 +15060,33 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14665
15060
|
emit({ ...base(o, src), type: "primed", part: p.spec.id, point: partXf(o, p).c, trigger, delay });
|
|
14666
15061
|
};
|
|
14667
15062
|
const CHAIN_TRIGGER_OF = { ballistic: "shot", explosive: "explosion" };
|
|
15063
|
+
const severAxis = (p) => {
|
|
15064
|
+
if (p.mat.fracture !== "splinter" && p.mat.fracture !== "snap" || p.spec.shape.kind !== "box") return -1;
|
|
15065
|
+
const d = p.dims;
|
|
15066
|
+
const order = [0, 1, 2].sort((a, b) => d[b] - d[a]);
|
|
15067
|
+
return d[order[0]] >= SEVER_ASPECT * d[order[1]] && d[order[0]] >= 3 * SEVER_BAND ? order[0] : -1;
|
|
15068
|
+
};
|
|
15069
|
+
const bandOf = (o, p, axis, origin) => {
|
|
15070
|
+
const { c, q } = partXf(o, p);
|
|
15071
|
+
const along = toLocal(c, q, origin)[axis];
|
|
15072
|
+
const L = p.dims[axis];
|
|
15073
|
+
return clamp2(Math.floor((along + L / 2) / SEVER_BAND), 0, Math.ceil(L / SEVER_BAND) - 1);
|
|
15074
|
+
};
|
|
14668
15075
|
const applyDamage = (o, p, amount, type, origin, dir, fragEnergy, cap, src) => {
|
|
15076
|
+
var _a;
|
|
14669
15077
|
if (isLost(p) || amount <= 0) return 0;
|
|
14670
15078
|
const prev = p.st.hp;
|
|
14671
15079
|
p.st.hp -= amount;
|
|
15080
|
+
const axis = origin && type !== "fire" && type !== "heat" ? severAxis(p) : -1;
|
|
15081
|
+
if (axis >= 0 && p.st.hp > 0) {
|
|
15082
|
+
const band = bandOf(o, p, axis, origin);
|
|
15083
|
+
const cuts = (_a = p.rt).cuts ?? (_a.cuts = {});
|
|
15084
|
+
cuts[band] = (cuts[band] ?? 0) + amount * (type === "ballistic" ? SEVER_BALLISTIC : 1);
|
|
15085
|
+
if (cuts[band] >= p.st.maxHp * SEVER_BAND / p.dims[axis]) {
|
|
15086
|
+
p.rt.cutAt = -p.dims[axis] / 2 + (band + 0.5) * SEVER_BAND;
|
|
15087
|
+
p.st.hp = 0;
|
|
15088
|
+
}
|
|
15089
|
+
}
|
|
14672
15090
|
markDamaged(p);
|
|
14673
15091
|
const trigger = CHAIN_TRIGGER_OF[type];
|
|
14674
15092
|
const chained = trigger !== void 0 && chainDelayOf(p.spec, trigger) !== void 0;
|
|
@@ -14704,10 +15122,53 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14704
15122
|
}
|
|
14705
15123
|
}
|
|
14706
15124
|
};
|
|
15125
|
+
const tearOf = (mat, kind) => {
|
|
15126
|
+
const h = mat.hits?.[kind];
|
|
15127
|
+
const tear = h?.tear ?? (kind === "ballistic" && mat.penetrationClass === "wood" ? "splinters" : "none");
|
|
15128
|
+
return { tear, joules: h?.tearJoules ?? TEAR_JOULES };
|
|
15129
|
+
};
|
|
15130
|
+
const tearAt = (o, p, kind, entry2, exit, d, E, scale, src) => {
|
|
15131
|
+
const { tear, joules } = tearOf(p.mat, kind);
|
|
15132
|
+
if (tear === "none") return;
|
|
15133
|
+
const shot = kind === "ballistic";
|
|
15134
|
+
const n = shot ? exit ? clamp2(Math.round(E / joules), 1, 7) : 1 : clamp2(Math.floor(E / joules), 0, 5);
|
|
15135
|
+
if (n === 0) return;
|
|
15136
|
+
const fibre = tear === "lumps";
|
|
15137
|
+
const { q } = partXf(o, p);
|
|
15138
|
+
const along = (p.spec.grain ? AXIS[p.spec.grain] : void 0) ?? [0, 1, 2].sort((a, b) => p.dims[b] - p.dims[a])[0];
|
|
15139
|
+
const fragments = [];
|
|
15140
|
+
for (let i = 0; i < n; i++) {
|
|
15141
|
+
const back = !exit || i % 3 === 2;
|
|
15142
|
+
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());
|
|
15143
|
+
const size = [w, w, w];
|
|
15144
|
+
size[along] = len;
|
|
15145
|
+
size[(along + 1) % 3] = w;
|
|
15146
|
+
size[(along + 2) % 3] = t;
|
|
15147
|
+
const spread3 = [(rnd() - 0.5) * 0.8, (rnd() - 0.2) * 0.8, (rnd() - 0.5) * 0.8];
|
|
15148
|
+
const speed = shot ? back ? 1.5 + 2 * rnd() : 2 + 4 * rnd() : 0.8 + 1.2 * rnd();
|
|
15149
|
+
const v = vec.scale(vec.normalize(vec.add(back ? vec.scale(d, -1) : d, spread3)), speed);
|
|
15150
|
+
const mass = size[0] * size[1] * size[2] * p.mat.density;
|
|
15151
|
+
fragments.push({
|
|
15152
|
+
id: `${p.spec.id}#t${tears++}-${i}`,
|
|
15153
|
+
part: p.spec.id,
|
|
15154
|
+
material: p.spec.material,
|
|
15155
|
+
shape: fibre ? "chunk" : "splinter",
|
|
15156
|
+
center: back || !exit ? vec.madd(entry2, d, -0.01) : vec.madd(exit, d, 0.01),
|
|
15157
|
+
size,
|
|
15158
|
+
rotation: q,
|
|
15159
|
+
velocity: v,
|
|
15160
|
+
inner: true,
|
|
15161
|
+
angularVelocity: [(rnd() - 0.5) * 30, (rnd() - 0.5) * 30, (rnd() - 0.5) * 30],
|
|
15162
|
+
mass
|
|
15163
|
+
});
|
|
15164
|
+
}
|
|
15165
|
+
const patch = [scale * 4, scale * 4, scale * 4];
|
|
15166
|
+
emit({ ...base(o, src), type: "chip", part: p.spec.id, center: exit ?? entry2, size: patch, rotation: q, fragments });
|
|
15167
|
+
};
|
|
14707
15168
|
const ballistic = (o, p, entry2, n, d, E, caliber, pathLen, src, round3 = {}) => {
|
|
14708
15169
|
const mat = p.mat;
|
|
14709
15170
|
const piercing = round3.piercing ?? 1;
|
|
14710
|
-
if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry2))) {
|
|
15171
|
+
if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry2)) || insideChip(p, objLocal(o, entry2))) {
|
|
14711
15172
|
return { penetrated: true, exitEnergy: E, exitPoint: vec.madd(entry2, d, Math.min(pathLen, p.thickness)), damage: 0 };
|
|
14712
15173
|
}
|
|
14713
15174
|
const sin = Math.min(1, Math.abs(vec.dot(d, n)));
|
|
@@ -14732,6 +15193,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14732
15193
|
return { penetrated: false, exitEnergy: 0, damage: dmg2 };
|
|
14733
15194
|
}
|
|
14734
15195
|
if (p.layers.length > 0) spallOuter(o, p, entry2, face, n, Math.max(0.03, caliber / 1e3 * 4), src);
|
|
15196
|
+
else if (mat.hits?.ballistic?.crater) crater(o, p, entry2, n, clamp2(0.02 + Math.sqrt(E) * 15e-4, 0.02, 0.15), src);
|
|
14735
15197
|
const brittle = mat.ductility < 0.3;
|
|
14736
15198
|
const ratio = Ep / Math.max(thr, 1e-6);
|
|
14737
15199
|
const crackLen = clamp2(0.03 + ratio * 0.2, 0.03, 0.8);
|
|
@@ -14765,7 +15227,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14765
15227
|
} else addHole(o, p, entry2, n, caliber / 1e3, false, void 0, src);
|
|
14766
15228
|
debris(o, p, mat.hitDebris, entry2, n, clamp2(Math.round(E / 60) + 2, 2, 14), clamp2(Math.sqrt(E) / 6, 1, 8), src);
|
|
14767
15229
|
wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true);
|
|
14768
|
-
const
|
|
15230
|
+
const absorbed = penetrated && mat.ductility >= 0.5 && mat.fracture !== "tear" ? Math.min(E, thr) : E;
|
|
15231
|
+
const dmg = applyDamage(o, p, absorbed * DMG_PER_J * scale, "ballistic", entry2, d, absorbed * 0.1, 12, src);
|
|
15232
|
+
if (!isLost(p)) tearAt(o, p, "ballistic", entry2, penetrated ? exitPoint : void 0, d, E, caliber / 1e3, src);
|
|
15233
|
+
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);
|
|
14769
15234
|
return { penetrated, exitEnergy: penetrated ? penMm !== void 0 ? E * (1 - thr / Ep) : E - thr / (hard ? piercing : 1) : 0, exitPoint, damage: dmg };
|
|
14770
15235
|
};
|
|
14771
15236
|
const melee = (o, p, kind, E, point, n, d, contactR, src) => {
|
|
@@ -14773,14 +15238,20 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14773
15238
|
const thrMm = mat.penetration * p.thickness * 1e3;
|
|
14774
15239
|
const fragile = kind === "blunt" ? fragility(mat.ductility, p.thickness) : 1;
|
|
14775
15240
|
const Ec = E * fragile;
|
|
15241
|
+
const sheet = mat.ductility >= 0.5 && p.thickness <= THIN_SHEET_CRUSH;
|
|
15242
|
+
const fold = sheet ? Math.min(4, Math.sqrt(1e-3 / p.thickness)) : 1;
|
|
15243
|
+
const patch = sheet ? Math.min(contactR, THIN_SHEET_CONTACT) : contactR;
|
|
14776
15244
|
if (E > 0) {
|
|
14777
15245
|
if (kind === "blunt") {
|
|
14778
|
-
if (mat.ductility >= 0.5) addDent(o, p, point, n, Math.max(
|
|
15246
|
+
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);
|
|
14779
15247
|
else if (mat.ductility < 0.3) {
|
|
14780
15248
|
if (Ec >= 5) addCrack(o, p, point, n, clamp2(0.04 + Math.min(Ec, E * 6) * 8e-4, 0.04, 0.6), src);
|
|
14781
15249
|
} 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);
|
|
14782
15250
|
} else if (mat.fracture === "tear") {
|
|
14783
15251
|
addHole(o, p, point, n, clamp2(0.02 + E * 5e-4, 0.02, 0.3), true, void 0, src);
|
|
15252
|
+
} else if (mat.ductility >= 0.3 && E >= thrMm) {
|
|
15253
|
+
addHole(o, p, point, n, clamp2(contactR, 0.01, 0.08), true, void 0, src);
|
|
15254
|
+
if (sheet) addDent(o, p, point, n, Math.max(patch * 0.6, 0.01), clamp2(E * 4e-5 * fold, 5e-4, 0.02), src);
|
|
14784
15255
|
} else if (mat.ductility >= 0.5) {
|
|
14785
15256
|
addDent(o, p, point, n, Math.max(contactR, 0.01), clamp2(E * 4e-5, 5e-4, 0.02), src);
|
|
14786
15257
|
}
|
|
@@ -14793,6 +15264,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14793
15264
|
const scale = mat.damageScale?.[kind] ?? 1;
|
|
14794
15265
|
const done = applyDamage(o, p, Ec * DMG_PER_J * scale * (kind === "slash" ? p.cutFactor : 1), kind, point, d, E * 0.1, 12, src);
|
|
14795
15266
|
addBreach(o, p, point, breachAreaOf(p, done, false), E, d, src);
|
|
15267
|
+
if (kind === "blunt") chipUnits(o, p, "blunt", point, E, d, Math.max(contactR, 0.03), src);
|
|
15268
|
+
if (E > 0 && !isLost(p)) tearAt(o, p, kind, point, void 0, d, E, clamp2(contactR * 0.2, 6e-3, 0.02), src);
|
|
14796
15269
|
return done;
|
|
14797
15270
|
};
|
|
14798
15271
|
const blastPart = (o, p, point, energy, radius, src, res, reachAt) => {
|
|
@@ -15371,6 +15844,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
15371
15844
|
if (p.rt.stressT <= 0) {
|
|
15372
15845
|
p.rt.stressT = 0.5;
|
|
15373
15846
|
emit({ ...base(o), type: "stress", part: p.spec.id, stress, load: p.st.load ?? 0 });
|
|
15847
|
+
if (p.spec.reinforcement && (p.st.stripped || p.st.chipped?.length || p.st.opening)) exposeRebar(o, p);
|
|
15374
15848
|
}
|
|
15375
15849
|
applyDamage(o, p, overloadRate(stress) * p.st.maxHp * dt, "blunt", null, null, 5, 3);
|
|
15376
15850
|
} else if (stress > 0.8 && p.mat.ductility < 0.3 && p.rt.stressT <= 0 && p.st.cracks.length < 6) {
|
|
@@ -15587,6 +16061,18 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
15587
16061
|
const r = raycastInternal(origin, direction, maxDistance);
|
|
15588
16062
|
return r ? { object: r.object, part: r.part, point: r.point, normal: r.normal, distance: r.distance } : null;
|
|
15589
16063
|
},
|
|
16064
|
+
sweepBox(box3, direction, distance2, options2 = {}) {
|
|
16065
|
+
const d = vec.normalize(direction);
|
|
16066
|
+
const ignored = options2.ignore === void 0 ? void 0 : objects.get(options2.ignore);
|
|
16067
|
+
const ignore = ignored ? new Set(ignored.parts.map((p) => `${ignored.id}\0${p.spec.id}`)) : void 0;
|
|
16068
|
+
let best = null;
|
|
16069
|
+
for (const origin of sweepOrigins(box3, d, options2.spacing ?? 0.2)) {
|
|
16070
|
+
const r = raycastInternal(origin, d, distance2 + SWEEP_BACK, ignore);
|
|
16071
|
+
if (!r || best && r.distance >= best.distance) continue;
|
|
16072
|
+
best = { object: r.object, part: r.part, point: r.point, normal: r.normal, distance: Math.max(0, r.distance - SWEEP_BACK) };
|
|
16073
|
+
}
|
|
16074
|
+
return best;
|
|
16075
|
+
},
|
|
15590
16076
|
shoot(origin, direction, energy, caliber, source, round3) {
|
|
15591
16077
|
const extras = typeof round3 === "number" ? { piercing: round3 } : round3 ?? {};
|
|
15592
16078
|
return withCollapses(run(() => {
|
|
@@ -18152,7 +18638,24 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
18152
18638
|
}
|
|
18153
18639
|
return spec;
|
|
18154
18640
|
};
|
|
18155
|
-
var
|
|
18641
|
+
var MEMBER_CAGE = { spacing: 0.1, diameter: 8e-3, unit: 0.1 };
|
|
18642
|
+
var CAGED_ROLES = /* @__PURE__ */ new Set(["beam", "column"]);
|
|
18643
|
+
var caged = (spec, part, rebar) => {
|
|
18644
|
+
if (!rebar || part.material !== "concrete" || !CAGED_ROLES.has(part.role) || spec.reinforcement) return spec;
|
|
18645
|
+
const size = spec.shape.kind === "box" ? spec.shape.size : null;
|
|
18646
|
+
if (!size) return spec;
|
|
18647
|
+
const thin = Math.min(...size);
|
|
18648
|
+
return {
|
|
18649
|
+
...spec,
|
|
18650
|
+
reinforcement: { material: rebar, spacing: MEMBER_CAGE.spacing, diameter: MEMBER_CAGE.diameter },
|
|
18651
|
+
units: { size: [MEMBER_CAGE.unit, MEMBER_CAGE.unit, thin], bond: "stack", mortar: "concrete" }
|
|
18652
|
+
};
|
|
18653
|
+
};
|
|
18654
|
+
var partSpecOf = (part, capacity, detailed, rebar) => {
|
|
18655
|
+
if (detailed && part.assembly) return assemblySpec(part, capacity);
|
|
18656
|
+
const spec = boxSpec(part, capacity);
|
|
18657
|
+
return detailed ? caged(spec, part, rebar) : spec;
|
|
18658
|
+
};
|
|
18156
18659
|
function toDestructibleSpec(structure, options = {}) {
|
|
18157
18660
|
const { safety = 0.65, redundancy = 0.97, safetyOthers = 0.45 } = options;
|
|
18158
18661
|
const kept = structure.parts.filter((part) => !options.filter || options.filter(part));
|
|
@@ -18169,7 +18672,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
18169
18672
|
const sizing = { safety, redundancy, safetyOthers };
|
|
18170
18673
|
const sized = { ...panels, capacities: options.sized === false ? /* @__PURE__ */ new Map() : sizeCapacities(panels.parts, panels.supports, sizing) };
|
|
18171
18674
|
const { parts, supports, capacities } = detail === "units" ? applyUnits(sized, structure) : sized;
|
|
18172
|
-
const specs = parts.map((part) => partSpecOf(part, capacities.get(part.id), detail !== "coarse"));
|
|
18675
|
+
const specs = parts.map((part) => partSpecOf(part, capacities.get(part.id), detail !== "coarse", structure.construction.frame.rebar));
|
|
18173
18676
|
const margin = options.terrainMargin ?? TERRAIN_MARGIN;
|
|
18174
18677
|
const x0 = Math.min(...parts.map((part) => part.aabb[0]));
|
|
18175
18678
|
const z0 = Math.min(...parts.map((part) => part.aabb[1]));
|
|
@@ -19049,7 +19552,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19049
19552
|
if (!pc || pc.unit) return sp;
|
|
19050
19553
|
if (pc.role !== "wall" && pc.role !== "partition") {
|
|
19051
19554
|
const def = getAssembly(pc.assembly || (pc.material === "concrete" && ["slab", "beam", "column", "stair"].includes(pc.role) ? "reinforced_concrete" : ""));
|
|
19052
|
-
|
|
19555
|
+
const out = def && def.reinforcement && !sp.reinforcement ? { ...sp, reinforcement: { ...def.reinforcement } } : sp;
|
|
19556
|
+
if (out.reinforcement && !out.units && pc.material === "concrete" && (pc.role === "beam" || pc.role === "column") && sp.shape.kind === "box") {
|
|
19557
|
+
return { ...out, units: { size: [MEMBER_CAGE.unit, MEMBER_CAGE.unit, Math.min(...sp.shape.size)], bond: "stack", mortar: "concrete" } };
|
|
19558
|
+
}
|
|
19559
|
+
return out;
|
|
19053
19560
|
}
|
|
19054
19561
|
if (!pc.assembly) return sp;
|
|
19055
19562
|
const [u0, v0, u1, v1] = pc.aabb, alongU = u1 - u0 >= v1 - v0;
|
|
@@ -19106,7 +19613,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19106
19613
|
}
|
|
19107
19614
|
return null;
|
|
19108
19615
|
};
|
|
19109
|
-
|
|
19616
|
+
const local = (p) => {
|
|
19617
|
+
house.updateMatrixWorld(true);
|
|
19618
|
+
const v = house.worldToLocal(tmp.p.set(p[0], p[1], p[2]));
|
|
19619
|
+
return [v.x, v.y, v.z];
|
|
19620
|
+
};
|
|
19621
|
+
function courses(part, units, opening, chips, paint) {
|
|
19110
19622
|
const [u0, v0, u1, v1] = part.aabb;
|
|
19111
19623
|
const alongU = u1 - u0 >= v1 - v0;
|
|
19112
19624
|
const len = alongU ? u1 - u0 : v1 - v0;
|
|
@@ -19130,23 +19642,54 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19130
19642
|
const x = alongU ? u0 + mid : (u0 + u1) / 2;
|
|
19131
19643
|
const z = alongU ? (v0 + v1) / 2 : v0 + mid;
|
|
19132
19644
|
const y = part.y0 + (r + 0.5) * rowH;
|
|
19133
|
-
const d = Math.hypot(x - opening.center[0], y - opening.center[1], z - opening.center[2]);
|
|
19134
|
-
if (d < opening.radius) continue;
|
|
19135
|
-
|
|
19645
|
+
const d = opening ? Math.hypot(x - opening.center[0], y - opening.center[1], z - opening.center[2]) : Infinity;
|
|
19646
|
+
if (opening && d < opening.radius) continue;
|
|
19647
|
+
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);
|
|
19648
|
+
if (chips.some(gone)) continue;
|
|
19649
|
+
const near = opening && d < opening.radius + 0.25 || chips.some((c) => Math.hypot(c[0] - x, c[1] - y, c[2] - z) < 0.15);
|
|
19650
|
+
list2.push({ x, y, z, sx: alongU ? b - a : thick, sy: rowH, sz: alongU ? thick : b - a, colour: near || paint === null ? edge : paint });
|
|
19136
19651
|
}
|
|
19137
19652
|
}
|
|
19138
19653
|
return list2;
|
|
19139
19654
|
}
|
|
19140
|
-
|
|
19141
|
-
|
|
19142
|
-
|
|
19143
|
-
|
|
19144
|
-
|
|
19145
|
-
|
|
19146
|
-
|
|
19147
|
-
|
|
19655
|
+
const specOf = (id) => opts.spec()?.parts?.find((q) => q.id === id);
|
|
19656
|
+
const same = (a, b) => a.every((x, k) => Math.abs(x - b[k]) < 1e-3);
|
|
19657
|
+
function hostOf(partId) {
|
|
19658
|
+
if (structure.meshesOf(partId).some((it) => it.mesh)) return partId;
|
|
19659
|
+
const part = structure.byId.get(partId);
|
|
19660
|
+
if (!part) return null;
|
|
19661
|
+
for (const q of structure.parts) {
|
|
19662
|
+
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;
|
|
19663
|
+
}
|
|
19664
|
+
return null;
|
|
19665
|
+
}
|
|
19666
|
+
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);
|
|
19667
|
+
function draw2(partId, change) {
|
|
19668
|
+
if (lod !== 0) return false;
|
|
19669
|
+
const hostId = hostOf(partId);
|
|
19670
|
+
if (!hostId || !specOf(partId)?.units) return false;
|
|
19671
|
+
let view = views.get(hostId);
|
|
19672
|
+
if (!view) {
|
|
19673
|
+
const items = structure.meshesOf(hostId).filter((it) => it.mesh);
|
|
19674
|
+
if (!items.length) return false;
|
|
19675
|
+
const parts = [hostId, ...riders(hostId)].filter((id) => structure.byId.get(id) && specOf(id)?.units);
|
|
19676
|
+
view = { mesh: null, saved: null, items, paint: wallColour(items), units: 0, opening: null, chips: new Map(parts.map((id) => [id, []])), openings: /* @__PURE__ */ new Map() };
|
|
19677
|
+
}
|
|
19678
|
+
if (!view.chips.has(partId)) return false;
|
|
19679
|
+
if (change.opening) {
|
|
19680
|
+
if (view.openings.has(partId)) return false;
|
|
19681
|
+
view.openings.set(partId, { center: local(change.opening.center), radius: change.opening.radius });
|
|
19682
|
+
if (partId === hostId) view.opening = view.openings.get(partId);
|
|
19683
|
+
}
|
|
19684
|
+
if (change.chip) view.chips.get(partId).push(local(change.chip));
|
|
19685
|
+
const list2 = [];
|
|
19686
|
+
for (const [id, chips] of view.chips) list2.push(...courses(structure.byId.get(id), specOf(id).units, view.openings.get(id) ?? null, chips, view.paint));
|
|
19687
|
+
if (view.mesh) {
|
|
19688
|
+
view.mesh.removeFromParent();
|
|
19689
|
+
view.mesh.dispose();
|
|
19690
|
+
}
|
|
19148
19691
|
const mesh2 = new THREE.InstancedMesh(unitGeometry, unitMaterial, Math.max(1, list2.length));
|
|
19149
|
-
mesh2.name = "breach:" +
|
|
19692
|
+
mesh2.name = "breach:" + hostId;
|
|
19150
19693
|
list2.forEach((u, i) => {
|
|
19151
19694
|
tmp.m.compose(tmp.p.set(u.x, u.y, u.z), tmp.q.identity(), tmp.s.set(u.sx, u.sy, u.sz));
|
|
19152
19695
|
mesh2.setMatrixAt(i, tmp.m);
|
|
@@ -19158,8 +19701,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19158
19701
|
mesh2.castShadow = true;
|
|
19159
19702
|
mesh2.receiveShadow = true;
|
|
19160
19703
|
house.add(mesh2);
|
|
19161
|
-
|
|
19162
|
-
|
|
19704
|
+
if (!view.saved) view.saved = view.items.map((it) => ({ it, v0: it.v0, data: collapseRange(it.mesh, it.v0, it.v1) }));
|
|
19705
|
+
view.mesh = mesh2;
|
|
19706
|
+
view.units = list2.length;
|
|
19707
|
+
views.set(hostId, view);
|
|
19163
19708
|
invalidate();
|
|
19164
19709
|
invalidateShadow();
|
|
19165
19710
|
return true;
|
|
@@ -19173,7 +19718,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19173
19718
|
return {
|
|
19174
19719
|
/** Handles a `stage` event of the house's object. Returns true when the wall was redrawn as courses. */
|
|
19175
19720
|
stage(event) {
|
|
19176
|
-
return event.stage === 2 ?
|
|
19721
|
+
return event.stage === 2 && !!event.opening ? draw2(event.part, { opening: event.opening }) : false;
|
|
19722
|
+
},
|
|
19723
|
+
/** Handles a `chip` event: the unit that came out leaves a gap in the courses. Returns true when the wall was redrawn. */
|
|
19724
|
+
chip(event) {
|
|
19725
|
+
return draw2(event.part, { chip: event.center });
|
|
19177
19726
|
},
|
|
19178
19727
|
/** The part broke or fell: the courses go (the plain range stays collapsed, the renderer hides the part). */
|
|
19179
19728
|
drop(partId) {
|
|
@@ -19405,6 +19954,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19405
19954
|
case "stage":
|
|
19406
19955
|
breach.stage(e);
|
|
19407
19956
|
break;
|
|
19957
|
+
case "chip":
|
|
19958
|
+
breach.chip(e);
|
|
19959
|
+
break;
|
|
19408
19960
|
case "collapse":
|
|
19409
19961
|
for (const id of e.parts) {
|
|
19410
19962
|
breach.drop(id);
|
|
@@ -19459,6 +20011,32 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19459
20011
|
if (D.active && D.world.has(D.id)) D.world.remove(D.id);
|
|
19460
20012
|
D.active = false;
|
|
19461
20013
|
};
|
|
20014
|
+
D.follow = (world, id, spec) => {
|
|
20015
|
+
D.spec = spec;
|
|
20016
|
+
const offFollow = world.on("*", (e) => {
|
|
20017
|
+
if (e.object !== id) return;
|
|
20018
|
+
switch (e.type) {
|
|
20019
|
+
case "stage":
|
|
20020
|
+
breach.stage(e);
|
|
20021
|
+
break;
|
|
20022
|
+
case "chip":
|
|
20023
|
+
breach.chip(e);
|
|
20024
|
+
break;
|
|
20025
|
+
case "fracture":
|
|
20026
|
+
breach.drop(e.part);
|
|
20027
|
+
break;
|
|
20028
|
+
case "collapse":
|
|
20029
|
+
for (const p of e.parts) breach.drop(p);
|
|
20030
|
+
break;
|
|
20031
|
+
default:
|
|
20032
|
+
break;
|
|
20033
|
+
}
|
|
20034
|
+
});
|
|
20035
|
+
return { restore: () => breach.restore(), dispose: () => {
|
|
20036
|
+
offFollow();
|
|
20037
|
+
breach.restore();
|
|
20038
|
+
} };
|
|
20039
|
+
};
|
|
19462
20040
|
D.piece = (id) => D.active ? D.world.part(D.id, id) : null;
|
|
19463
20041
|
D.state = () => D.active ? D.world.state(D.id) : null;
|
|
19464
20042
|
D.reset = () => {
|
|
@@ -21296,6 +21874,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
21296
21874
|
return {
|
|
21297
21875
|
id: FLOORPLAN_AREA_ID,
|
|
21298
21876
|
name: text.name,
|
|
21877
|
+
ground: { surface: "lawn" },
|
|
21299
21878
|
group: text.group,
|
|
21300
21879
|
get houses() {
|
|
21301
21880
|
return placed;
|
|
@@ -22580,6 +23159,56 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
22580
23159
|
|
|
22581
23160
|
// src/furniture/spec.ts
|
|
22582
23161
|
var WOOD_THIN = 0.035;
|
|
23162
|
+
var BOARD_TOP_THICK = 0.07;
|
|
23163
|
+
var BOARD_TOP_WIDTH = 0.5;
|
|
23164
|
+
var BOARD_WIDTH = 0.16;
|
|
23165
|
+
var SHELF_SPAN = 1.5;
|
|
23166
|
+
var SHELVING = /shelf|bookcase|rack/;
|
|
23167
|
+
function carcass(parts, drawn) {
|
|
23168
|
+
const all = [...drawn.values()];
|
|
23169
|
+
const width = Math.max(...all.map((p) => p.max[0])) - Math.min(...all.map((p) => p.min[0]));
|
|
23170
|
+
for (const part of parts) {
|
|
23171
|
+
if (LOOSE.has(part.material) || part.shape.kind !== "box" || part.rotation) continue;
|
|
23172
|
+
const [x, y, z] = part.shape.size;
|
|
23173
|
+
if (y >= x && y >= z) {
|
|
23174
|
+
if (z < x && x >= 0.6 * width) continue;
|
|
23175
|
+
part.structure = { role: "column" };
|
|
23176
|
+
} else if (y <= x && y <= z) part.structure = { role: "beam", maxSpan: SHELF_SPAN * Math.max(x, z) };
|
|
23177
|
+
}
|
|
23178
|
+
}
|
|
23179
|
+
function splitTopsIntoBoards(drawn, materialOf2, carcassed) {
|
|
23180
|
+
const tops = /* @__PURE__ */ new Map();
|
|
23181
|
+
if (carcassed) return tops;
|
|
23182
|
+
for (let i = drawn.length - 1; i >= 0; i--) {
|
|
23183
|
+
const p = drawn[i];
|
|
23184
|
+
if (p.key !== "wood" || materialOf2.wood !== "wood_hard" || p.shape.kind !== "box" || p.rotation) continue;
|
|
23185
|
+
const [sx, sy, sz] = p.shape.size;
|
|
23186
|
+
const across = sx < sz ? 0 : 2;
|
|
23187
|
+
const width = Math.min(sx, sz);
|
|
23188
|
+
if (sy < WOOD_THIN || sy > BOARD_TOP_THICK || sy >= width || width < BOARD_TOP_WIDTH) continue;
|
|
23189
|
+
const count = Math.round(width / BOARD_WIDTH);
|
|
23190
|
+
const boards = [];
|
|
23191
|
+
for (let b = 0; b < count; b++) {
|
|
23192
|
+
const size = [sx, sy, sz];
|
|
23193
|
+
size[across] = width / count;
|
|
23194
|
+
const position = [...p.position];
|
|
23195
|
+
position[across] += (b + 0.5) * size[across] - width / 2;
|
|
23196
|
+
const min = [...p.min];
|
|
23197
|
+
const max = [...p.max];
|
|
23198
|
+
min[across] = position[across] - size[across] / 2;
|
|
23199
|
+
max[across] = position[across] + size[across] / 2;
|
|
23200
|
+
boards.push({ ...p, id: `${p.id}_b${b}`, shape: { kind: "box", size }, position, min, max, color: shadeBoard(p.color, b) });
|
|
23201
|
+
}
|
|
23202
|
+
drawn.splice(i, 1, ...boards);
|
|
23203
|
+
tops.set(p.id, boards);
|
|
23204
|
+
}
|
|
23205
|
+
return tops;
|
|
23206
|
+
}
|
|
23207
|
+
function shadeBoard(color2, board) {
|
|
23208
|
+
const k = [1, 0.94, 1.05, 0.97, 1.03][board % 5];
|
|
23209
|
+
const channel = (shift) => Math.min(255, Math.round((color2 >> shift & 255) * k)) << shift;
|
|
23210
|
+
return channel(16) | channel(8) | channel(0);
|
|
23211
|
+
}
|
|
22583
23212
|
var FLOOR_TOUCH = 5e-3;
|
|
22584
23213
|
var LOOSE = /* @__PURE__ */ new Set(["paper"]);
|
|
22585
23214
|
var BASE_MATERIAL = {
|
|
@@ -22657,10 +23286,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
22657
23286
|
};
|
|
22658
23287
|
run(A);
|
|
22659
23288
|
if (!drawn.length) throw new Error(`game-floorplan-three: the builder of "${name}" drew no box or cylinder`);
|
|
23289
|
+
const boardsOf = splitTopsIntoBoards(drawn, materialOf2, Boolean(opts.carcass));
|
|
22660
23290
|
const parts = drawn.map((p) => {
|
|
22661
23291
|
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]);
|
|
22662
23292
|
let material = materialOf2[p.key] ?? "plastic";
|
|
22663
|
-
if (p.key === "wood" && material === "wood_hard" &&
|
|
23293
|
+
if (p.key === "wood" && material === "wood_hard" && opts.carcass && !opts.materials?.wood) material = "mdf";
|
|
23294
|
+
else if (p.key === "wood" && material === "wood_hard" && thin < WOOD_THIN) material = "wood_soft";
|
|
22664
23295
|
const part = { id: p.id, material, shape: p.shape, position: p.position };
|
|
22665
23296
|
if (p.rotation) part.rotation = p.rotation;
|
|
22666
23297
|
if (!LOOSE.has(material) && p.min[1] <= FLOOR_TOUCH) part.anchored = true;
|
|
@@ -22695,6 +23326,14 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
22695
23326
|
if (area3 < 0.6 * footprint || p.max[1] < 0.2) continue;
|
|
22696
23327
|
if (!top || p.max[1] > top.y + 1e-6) top = { part: p.id, y: p.max[1] };
|
|
22697
23328
|
}
|
|
23329
|
+
for (const boards of boardsOf.values()) {
|
|
23330
|
+
if (top && boards[0].max[1] <= top.y - 1e-6) continue;
|
|
23331
|
+
const mid = boards[boards.length >> 1];
|
|
23332
|
+
const min = [Math.min(...boards.map((b) => b.min[0])), Math.min(...boards.map((b) => b.min[2]))];
|
|
23333
|
+
const max = [Math.max(...boards.map((b) => b.max[0])), Math.max(...boards.map((b) => b.max[2]))];
|
|
23334
|
+
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) };
|
|
23335
|
+
}
|
|
23336
|
+
if (opts.carcass) carcass(parts, byId);
|
|
22698
23337
|
const spec = { name, parts, joints, tags: ["furniture"] };
|
|
22699
23338
|
const colors = new Map(drawn.map((p) => [p.id, p.color]));
|
|
22700
23339
|
const build = (T3) => {
|
|
@@ -22738,7 +23377,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
22738
23377
|
if (w === void 0 || d === void 0 || h === void 0) throw new Error(`game-floorplan-three: furniture type "${type}" is not in the catalogue, so give w, d and h`);
|
|
22739
23378
|
const o = opts.options ?? {};
|
|
22740
23379
|
const seed = Math.round(opts.seed ?? 1);
|
|
22741
|
-
const plain2 =
|
|
23380
|
+
const plain2 = SHELVING.test(type) ? "paper" : "plastic";
|
|
22742
23381
|
const root = { x: seed, z: 0, ry: 0, w, d, h, y: 0, base: 0, palette: o.palette, seed: `furniture_${type}_${seed}` };
|
|
22743
23382
|
const { K, shared } = renderer.internals;
|
|
22744
23383
|
const before = K.PARTS.length;
|
|
@@ -22757,7 +23396,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
22757
23396
|
shared.ctx = saved.ctx;
|
|
22758
23397
|
}
|
|
22759
23398
|
},
|
|
22760
|
-
{ materials: { plain: plain2, ...o.materials }, chairs: o.chairs }
|
|
23399
|
+
{ materials: { plain: plain2, ...o.materials }, chairs: o.chairs, carcass: SHELVING.test(type) }
|
|
22761
23400
|
);
|
|
22762
23401
|
const spouts = K.PARTS.slice(before).filter((p) => p.type === "water_tap").map((p) => [p.pos[0] - root.x, p.pos[1], p.pos[2] - root.z]);
|
|
22763
23402
|
K.PARTS.length = before;
|
|
@@ -22824,7 +23463,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
22824
23463
|
function draw(THREE, plan, options) {
|
|
22825
23464
|
return create3(THREE, { ...options, fleet: null }).build(plan, { quality: "game", terrain: false, noPerimeter: true, pieces: "meshes" });
|
|
22826
23465
|
}
|
|
23466
|
+
var houses = /* @__PURE__ */ new WeakMap();
|
|
22827
23467
|
function label(house) {
|
|
23468
|
+
houses.set(house.group, house);
|
|
22828
23469
|
for (const [id, items] of Object.entries(house.destruction.ranges())) {
|
|
22829
23470
|
for (const item of items) {
|
|
22830
23471
|
const object2 = item.isObject3D ? item : item.mesh;
|
|
@@ -22837,12 +23478,13 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
22837
23478
|
const plan = wallPlan(options);
|
|
22838
23479
|
const houseOptions = options.house ?? {};
|
|
22839
23480
|
const probe = draw(THREE, plan, { ...houseOptions, destruction: void 0, destructionRenderer: void 0 });
|
|
22840
|
-
const spec = probe.structure.spec(
|
|
23481
|
+
const spec = probe.structure.spec();
|
|
22841
23482
|
probe.dispose();
|
|
22842
23483
|
return {
|
|
22843
23484
|
plan,
|
|
22844
23485
|
spec,
|
|
22845
|
-
build: (T2) => label(draw(T2, plan, houseOptions))
|
|
23486
|
+
build: (T2) => label(draw(T2, plan, houseOptions)),
|
|
23487
|
+
follow: (root, world, objectId) => houses.get(root)?.destruction?.follow?.(world, objectId, spec) ?? null
|
|
22846
23488
|
};
|
|
22847
23489
|
}
|
|
22848
23490
|
return __toCommonJS(index_exports);
|