@adatechnology/game-floorplan-three 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +5 -1
- package/dist/index.global.js +373 -49
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +230 -31
- package/dist/index.js.map +1 -1
- package/package.json +20 -21
package/dist/index.js
CHANGED
|
@@ -6236,6 +6236,10 @@ function installDestructionPieces(K, shared) {
|
|
|
6236
6236
|
function collapseRange(mesh, v0, v1) {
|
|
6237
6237
|
const pos = mesh.geometry.attributes.position;
|
|
6238
6238
|
const a = pos.array;
|
|
6239
|
+
const u = mesh.userData;
|
|
6240
|
+
if (!u._p0) u._p0 = a.slice();
|
|
6241
|
+
const list = u.collapsedRanges ?? (u.collapsedRanges = []);
|
|
6242
|
+
if (!list.some((r) => r[0] === v0 && r[1] === v1)) list.push([v0, v1]);
|
|
6239
6243
|
const saved = a.slice(v0 * 3, v1 * 3);
|
|
6240
6244
|
const x = a[v0 * 3], y = a[v0 * 3 + 1], z = a[v0 * 3 + 2];
|
|
6241
6245
|
for (let k = v0; k < v1; k++) {
|
|
@@ -6250,6 +6254,8 @@ function restoreRange(mesh, v0, saved) {
|
|
|
6250
6254
|
const pos = mesh.geometry.attributes.position;
|
|
6251
6255
|
pos.array.set(saved, v0 * 3);
|
|
6252
6256
|
pos.needsUpdate = true;
|
|
6257
|
+
const list = mesh.userData.collapsedRanges;
|
|
6258
|
+
if (list) mesh.userData.collapsedRanges = list.filter((r) => r[0] !== v0);
|
|
6253
6259
|
}
|
|
6254
6260
|
function noise2(x, y, cell) {
|
|
6255
6261
|
const h = (i2, j2) => {
|
|
@@ -6264,13 +6270,16 @@ var MASK_TEXEL = 0.02;
|
|
|
6264
6270
|
var SPALL_MIN = 0.025;
|
|
6265
6271
|
var SPALL_RAGGED = 0.045;
|
|
6266
6272
|
var COVER = 0.015;
|
|
6273
|
+
var MAX_FLAKES = 64;
|
|
6267
6274
|
function installDestructionBreach(K, _shared) {
|
|
6268
6275
|
const { THREE, fragmentMaterial, invalidate, invalidateShadow } = K;
|
|
6269
6276
|
function createBreachView(opts) {
|
|
6270
6277
|
const { house, structure } = opts;
|
|
6271
6278
|
const views = /* @__PURE__ */ new Map();
|
|
6279
|
+
const dirty = /* @__PURE__ */ new Set();
|
|
6280
|
+
const paintBudgetMs = opts.paintBudgetMs ?? Infinity;
|
|
6281
|
+
const dropped = [];
|
|
6272
6282
|
let lod = 0;
|
|
6273
|
-
const unitMaterial = new THREE.MeshLambertMaterial({ color: 16777215 });
|
|
6274
6283
|
const unitGeometry = new THREE.BoxGeometry(1, 1, 1);
|
|
6275
6284
|
const tmp = { m: new THREE.Matrix4(), q: new THREE.Quaternion(), p: new THREE.Vector3(), s: new THREE.Vector3(), c: new THREE.Color() };
|
|
6276
6285
|
const local = (p) => {
|
|
@@ -6339,6 +6348,11 @@ function installDestructionBreach(K, _shared) {
|
|
|
6339
6348
|
const mesh = new THREE.Mesh(g, material);
|
|
6340
6349
|
mesh.name = "breach-shell";
|
|
6341
6350
|
mesh.userData.breachMask = mask;
|
|
6351
|
+
mesh.userData.cutAt = (p) => {
|
|
6352
|
+
const v = local([p.x, p.y, p.z]);
|
|
6353
|
+
const i = Math.floor(((mask.alongU ? v[0] : v[2]) - mask.start) / mask.len * mask.w), j = Math.floor((v[1] - mask.y0) / mask.height * mask.h);
|
|
6354
|
+
return i >= 0 && i < mask.w && j >= 0 && j < mask.h && mask.data[j * mask.w + i] > 127;
|
|
6355
|
+
};
|
|
6342
6356
|
mesh.position.copy(it.mesh.position);
|
|
6343
6357
|
mesh.quaternion.copy(it.mesh.quaternion);
|
|
6344
6358
|
mesh.scale.copy(it.mesh.scale);
|
|
@@ -6354,6 +6368,19 @@ function installDestructionBreach(K, _shared) {
|
|
|
6354
6368
|
toHouse.value.copy(house.matrixWorld).invert().multiply(mesh.matrixWorld);
|
|
6355
6369
|
return mesh;
|
|
6356
6370
|
}
|
|
6371
|
+
function unitsMaterial(core) {
|
|
6372
|
+
const material = new THREE.MeshLambertMaterial({ color: 16777215, side: THREE.DoubleSide });
|
|
6373
|
+
material.onBeforeCompile = (sh) => {
|
|
6374
|
+
sh.uniforms.uCore = { value: core.texture };
|
|
6375
|
+
sh.uniforms.uCoreRect = { value: core.rect };
|
|
6376
|
+
sh.uniforms.uCoreAlongX = { value: core.alongU ? 1 : 0 };
|
|
6377
|
+
sh.vertexShader = sh.vertexShader.replace("void main() {", "varying vec3 vHousePos;\nvoid main() {").replace("#include <begin_vertex>", "#include <begin_vertex>\n#ifdef USE_INSTANCING\nvHousePos = (instanceMatrix * vec4(transformed, 1.0)).xyz;\n#else\nvHousePos = transformed;\n#endif");
|
|
6378
|
+
sh.fragmentShader = sh.fragmentShader.replace("void main() {", "uniform sampler2D uCore;\nuniform vec4 uCoreRect;\nuniform float uCoreAlongX;\nvarying vec3 vHousePos;\nvoid main() {\n vec2 cUv = vec2(((uCoreAlongX > 0.5 ? vHousePos.x : vHousePos.z) - uCoreRect.x) / uCoreRect.z, (vHousePos.y - uCoreRect.y) / uCoreRect.w);\n if (cUv.x >= 0.0 && cUv.x <= 1.0 && cUv.y >= 0.0 && cUv.y <= 1.0 && texture2D(uCore, cUv).r > 0.5) discard;").replace("#include <color_fragment>", "#include <color_fragment>\nif (!gl_FrontFacing) diffuseColor.rgb *= 0.55;");
|
|
6379
|
+
};
|
|
6380
|
+
material.customProgramCacheKey = () => "breachCore";
|
|
6381
|
+
material.userData.breachCore = core;
|
|
6382
|
+
return material;
|
|
6383
|
+
}
|
|
6357
6384
|
function maskOf(hostId, parts) {
|
|
6358
6385
|
const host = structure.byId.get(hostId);
|
|
6359
6386
|
const [u0, v0, u1, v1] = host.aabb;
|
|
@@ -6369,41 +6396,73 @@ function installDestructionBreach(K, _shared) {
|
|
|
6369
6396
|
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) };
|
|
6370
6397
|
}
|
|
6371
6398
|
const boxDistance = (s, y, a, b, c, d) => Math.hypot(Math.max(a - s, 0, s - b), Math.max(c - y, 0, y - d));
|
|
6399
|
+
const ellipseDistance = (s, y, cs, cy, rs, ry) => (Math.hypot((s - cs) / Math.max(rs, 1e-4), (y - cy) / Math.max(ry, 1e-4)) - 1) * Math.min(rs, ry);
|
|
6400
|
+
function covered(c, boxes) {
|
|
6401
|
+
const n = 8;
|
|
6402
|
+
let hit = 0;
|
|
6403
|
+
for (let j = 0; j < n; j++) {
|
|
6404
|
+
const y = c.y0 + (j + 0.5) / n * (c.y1 - c.y0);
|
|
6405
|
+
for (let i = 0; i < n; i++) {
|
|
6406
|
+
const s = c.a + (i + 0.5) / n * (c.b - c.a);
|
|
6407
|
+
if (boxes.some((k) => s >= k.s0 && s <= k.s1 && y >= k.y0 && y <= k.y1)) hit++;
|
|
6408
|
+
}
|
|
6409
|
+
}
|
|
6410
|
+
return hit / (n * n);
|
|
6411
|
+
}
|
|
6372
6412
|
function paint(view) {
|
|
6373
|
-
const { mask } = view;
|
|
6413
|
+
const { mask, core } = view;
|
|
6374
6414
|
const along = (p) => (mask.alongU ? p[0] : p[2]) - mask.start;
|
|
6375
6415
|
const holes = [];
|
|
6416
|
+
const pits = [];
|
|
6376
6417
|
const discs = [];
|
|
6418
|
+
const flakes = [];
|
|
6377
6419
|
const shown = [];
|
|
6378
6420
|
for (const id of view.parts) {
|
|
6379
|
-
const part = structure.byId.get(id), units2 = specOf(id).units, chips = view.chips.get(id), opening = view.openings.get(id);
|
|
6421
|
+
const part = structure.byId.get(id), units2 = specOf(id).units, chips = view.chips.get(id), opening = view.openings.get(id), spalls = view.spalls.get(id);
|
|
6380
6422
|
const all = cells(part, units2);
|
|
6381
6423
|
const thick = mask.alongU ? part.aabb[3] - part.aabb[1] : part.aabb[2] - part.aabb[0];
|
|
6382
6424
|
const mid = mask.alongU ? (part.aabb[1] + part.aabb[3]) / 2 : (part.aabb[0] + part.aabb[2]) / 2;
|
|
6383
6425
|
if (opening) discs.push({ s: along(opening.center), y: opening.center[1], r: opening.radius });
|
|
6384
|
-
|
|
6426
|
+
for (const k of spalls) flakes.push({ s: along(k.center), y: k.center[1], r: k.radius });
|
|
6427
|
+
const boxes = chips.map((k) => ({ s0: along(k.center) - k.along, s1: along(k.center) + k.along, y0: k.center[1] - k.up, y1: k.center[1] + k.up }));
|
|
6428
|
+
const gone = (c) => covered(c, boxes.filter((k) => k.s1 > c.a && k.s0 < c.b && k.y1 > c.y0 && k.y0 < c.y1)) >= 0.9 || opening && Math.hypot((c.a + c.b) / 2 - along(opening.center), (c.y0 + c.y1) / 2 - opening.center[1]) < opening.radius;
|
|
6385
6429
|
const kept = [];
|
|
6386
6430
|
for (const c of all) (gone(c) ? holes : kept).push(c);
|
|
6387
6431
|
for (const c of kept) shown.push({ c, part, units: units2, thick, mid });
|
|
6432
|
+
for (const k of boxes) {
|
|
6433
|
+
if (holes.some((h) => k.s0 >= h.a - 1e-4 && k.s1 <= h.b + 1e-4 && k.y0 >= h.y0 - 1e-4 && k.y1 <= h.y1 + 1e-4)) continue;
|
|
6434
|
+
pits.push({ s: (k.s0 + k.s1) / 2, y: (k.y0 + k.y1) / 2, rs: (k.s1 - k.s0) / 2, ry: (k.y1 - k.y0) / 2 });
|
|
6435
|
+
}
|
|
6388
6436
|
}
|
|
6389
6437
|
const reach = SPALL_MIN + SPALL_RAGGED;
|
|
6438
|
+
const ragged = (s, y) => 0.6 * noise2(s, y, 0.03) + 0.4 * noise2(s + 7.3, y - 3.1, 0.011);
|
|
6390
6439
|
mask.data.fill(0);
|
|
6391
|
-
|
|
6392
|
-
|
|
6393
|
-
const
|
|
6440
|
+
core.data.fill(0);
|
|
6441
|
+
const stamp = (m, s0, s1, y0, y1, inside) => {
|
|
6442
|
+
const i0 = Math.max(0, Math.floor((s0 - reach) / m.len * m.w)), i1 = Math.min(m.w - 1, Math.ceil((s1 + reach) / m.len * m.w));
|
|
6443
|
+
const j0 = Math.max(0, Math.floor((y0 - reach - m.y0) / m.height * m.h)), j1 = Math.min(m.h - 1, Math.ceil((y1 + reach - m.y0) / m.height * m.h));
|
|
6394
6444
|
for (let j = j0; j <= j1; j++) {
|
|
6395
|
-
const y =
|
|
6445
|
+
const y = m.y0 + (j + 0.5) / m.h * m.height;
|
|
6396
6446
|
for (let i = i0; i <= i1; i++) {
|
|
6397
|
-
const s = (i + 0.5) /
|
|
6398
|
-
|
|
6399
|
-
if (inside(s, y) <= edge) mask.data[j * mask.w + i] = 255;
|
|
6447
|
+
const s = (i + 0.5) / m.w * m.len;
|
|
6448
|
+
if (inside(s, y)) m.data[j * m.w + i] = 255;
|
|
6400
6449
|
}
|
|
6401
6450
|
}
|
|
6402
6451
|
};
|
|
6403
|
-
|
|
6404
|
-
for (const
|
|
6452
|
+
const plaster = (d, s, y) => d <= SPALL_MIN + SPALL_RAGGED * ragged(s, y);
|
|
6453
|
+
for (const c of holes) stamp(mask, c.a, c.b, c.y0, c.y1, (s, y) => plaster(boxDistance(s, y, c.a, c.b, c.y0, c.y1), s, y));
|
|
6454
|
+
for (const d of discs) stamp(mask, d.s - d.r, d.s + d.r, d.y - d.r, d.y + d.r, (s, y) => plaster(Math.max(0, Math.hypot(s - d.s, y - d.y) - d.r), s, y));
|
|
6455
|
+
for (const f of flakes) {
|
|
6456
|
+
const rough = (s, y) => Math.hypot(s - f.s, y - f.y) - f.r * (0.65 + 0.7 * ragged(s * 2.3, y * 2.3));
|
|
6457
|
+
stamp(mask, f.s - 2 * f.r, f.s + 2 * f.r, f.y - 2 * f.r, f.y + 2 * f.r, (s, y) => rough(s, y) <= 0);
|
|
6458
|
+
}
|
|
6459
|
+
for (const p of pits) {
|
|
6460
|
+
stamp(mask, p.s - p.rs, p.s + p.rs, p.y - p.ry, p.y + p.ry, (s, y) => plaster(Math.max(0, ellipseDistance(s, y, p.s, p.y, p.rs, p.ry)), s, y));
|
|
6461
|
+
stamp(core, p.s - p.rs, p.s + p.rs, p.y - p.ry, p.y + p.ry, (s, y) => ellipseDistance(s, y, p.s, p.y, p.rs, p.ry) <= -0.35 * Math.min(p.rs, p.ry) * noise2(s * 1.7, y * 1.3, 0.012));
|
|
6462
|
+
}
|
|
6405
6463
|
mask.texture.needsUpdate = true;
|
|
6406
|
-
|
|
6464
|
+
core.texture.needsUpdate = true;
|
|
6465
|
+
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) || pits.some((p) => boxDistance(p.s, p.y, c.a, c.b, c.y0, c.y1) <= Math.max(p.rs, p.ry) + reach) || flakes.some((f) => boxDistance(f.s, f.y, c.a, c.b, c.y0, c.y1) <= 2 * f.r);
|
|
6407
6466
|
const list = [];
|
|
6408
6467
|
const tone = (hex, k) => tmp.c.setHex(hex).multiplyScalar(k).getHex();
|
|
6409
6468
|
for (const { c, part, units: units2, thick, mid } of shown) {
|
|
@@ -6413,7 +6472,7 @@ function installDestructionBreach(K, _shared) {
|
|
|
6413
6472
|
const base = fragmentMaterial(part.material).color.getHex();
|
|
6414
6473
|
const n = noise2(centre * 3.1, y * 2.7, 0.07);
|
|
6415
6474
|
if (units2.mortar === "concrete") {
|
|
6416
|
-
at(len, h, thick - 2 * COVER
|
|
6475
|
+
at(len, h, thick - 2 * COVER, tone(base, 0.9 + 0.04 * n));
|
|
6417
6476
|
} else {
|
|
6418
6477
|
at(len, h, thick - 2 * COVER - 0.012, 10327948);
|
|
6419
6478
|
at(len - 0.012, h - 0.012, thick - 2 * COVER, tone(base, 0.8 + 0.35 * n));
|
|
@@ -6423,7 +6482,7 @@ function installDestructionBreach(K, _shared) {
|
|
|
6423
6482
|
view.units.removeFromParent();
|
|
6424
6483
|
view.units.dispose();
|
|
6425
6484
|
}
|
|
6426
|
-
const units = new THREE.InstancedMesh(unitGeometry, unitMaterial, Math.max(1, list.length));
|
|
6485
|
+
const units = new THREE.InstancedMesh(unitGeometry, view.unitMaterial, Math.max(1, list.length));
|
|
6427
6486
|
units.name = "breach:" + view.host;
|
|
6428
6487
|
list.forEach((u, i) => {
|
|
6429
6488
|
tmp.m.compose(tmp.p.set(u.x, u.y, u.z), tmp.q.identity(), tmp.s.set(u.sx, u.sy, u.sz));
|
|
@@ -6439,6 +6498,19 @@ function installDestructionBreach(K, _shared) {
|
|
|
6439
6498
|
house.add(units);
|
|
6440
6499
|
view.units = units;
|
|
6441
6500
|
view.gone = holes.length;
|
|
6501
|
+
view.pits = pits.length;
|
|
6502
|
+
view.flakes = flakes.length;
|
|
6503
|
+
}
|
|
6504
|
+
function chipBox(chip, alongU) {
|
|
6505
|
+
house.updateMatrixWorld(true);
|
|
6506
|
+
const q = tmp.q.set(chip.rotation[0], chip.rotation[1], chip.rotation[2], chip.rotation[3]).premultiply(house.getWorldQuaternion(new THREE.Quaternion()).invert());
|
|
6507
|
+
let a = 0, u = 0;
|
|
6508
|
+
for (let k = 0; k < 3; k++) {
|
|
6509
|
+
const axis = tmp.p.set(k === 0 ? 1 : 0, k === 1 ? 1 : 0, k === 2 ? 1 : 0).applyQuaternion(q);
|
|
6510
|
+
a += Math.abs(alongU ? axis.x : axis.z) * chip.size[k] / 2;
|
|
6511
|
+
u += Math.abs(axis.y) * chip.size[k] / 2;
|
|
6512
|
+
}
|
|
6513
|
+
return { center: local(chip.center), along: a, up: u };
|
|
6442
6514
|
}
|
|
6443
6515
|
function draw2(partId, change) {
|
|
6444
6516
|
if (lod !== 0) return false;
|
|
@@ -6450,23 +6522,49 @@ function installDestructionBreach(K, _shared) {
|
|
|
6450
6522
|
if (!items.length) return false;
|
|
6451
6523
|
const parts = [hostId, ...riders(hostId)].filter((id) => structure.byId.get(id) && specOf(id)?.units);
|
|
6452
6524
|
if (!parts.includes(partId)) return false;
|
|
6453
|
-
const mask = maskOf(hostId, parts);
|
|
6454
|
-
view = {
|
|
6525
|
+
const mask = maskOf(hostId, parts), core = maskOf(hostId, parts);
|
|
6526
|
+
view = {
|
|
6527
|
+
host: hostId,
|
|
6528
|
+
parts,
|
|
6529
|
+
items,
|
|
6530
|
+
mask,
|
|
6531
|
+
core,
|
|
6532
|
+
unitMaterial: unitsMaterial(core),
|
|
6533
|
+
shells: [],
|
|
6534
|
+
saved: null,
|
|
6535
|
+
units: null,
|
|
6536
|
+
gone: 0,
|
|
6537
|
+
pits: 0,
|
|
6538
|
+
chips: new Map(parts.map((id) => [id, []])),
|
|
6539
|
+
spalls: new Map(parts.map((id) => [id, []])),
|
|
6540
|
+
openings: /* @__PURE__ */ new Map(),
|
|
6541
|
+
flakes: 0
|
|
6542
|
+
};
|
|
6455
6543
|
}
|
|
6456
6544
|
if (!view.chips.has(partId)) return false;
|
|
6457
6545
|
if (change.opening) {
|
|
6458
6546
|
if (view.openings.has(partId)) return false;
|
|
6459
6547
|
view.openings.set(partId, { center: local(change.opening.center), radius: change.opening.radius });
|
|
6460
6548
|
}
|
|
6461
|
-
if (change.chip) view.chips.get(partId).push(
|
|
6549
|
+
if (change.chip) view.chips.get(partId).push(chipBox(change.chip, view.mask.alongU));
|
|
6550
|
+
if (change.spall) {
|
|
6551
|
+
const list = view.spalls.get(partId);
|
|
6552
|
+
list.push({ center: local(change.spall.center), radius: change.spall.radius });
|
|
6553
|
+
if (list.length > MAX_FLAKES) list.shift();
|
|
6554
|
+
}
|
|
6462
6555
|
if (!view.saved) {
|
|
6463
6556
|
view.shells = view.items.map((it) => shell(it, view.mask));
|
|
6557
|
+
for (const m of view.shells) m.userData.behind = () => view.units;
|
|
6464
6558
|
view.saved = view.items.map((it) => ({ it, v0: it.v0, data: collapseRange(it.mesh, it.v0, it.v1) }));
|
|
6465
6559
|
}
|
|
6466
|
-
paint(view);
|
|
6467
6560
|
views.set(hostId, view);
|
|
6468
|
-
|
|
6469
|
-
|
|
6561
|
+
if (opts.deferPaint && change.spall) dirty.add(view);
|
|
6562
|
+
else {
|
|
6563
|
+
dirty.delete(view);
|
|
6564
|
+
paint(view);
|
|
6565
|
+
invalidate();
|
|
6566
|
+
invalidateShadow();
|
|
6567
|
+
}
|
|
6470
6568
|
return true;
|
|
6471
6569
|
}
|
|
6472
6570
|
function plain(view, shown) {
|
|
@@ -6475,24 +6573,70 @@ function installDestructionBreach(K, _shared) {
|
|
|
6475
6573
|
else s.data = collapseRange(s.it.mesh, s.v0, s.it.v1);
|
|
6476
6574
|
}
|
|
6477
6575
|
}
|
|
6576
|
+
function flush(budgetMs = paintBudgetMs) {
|
|
6577
|
+
if (dirty.size === 0) return 0;
|
|
6578
|
+
const deadline = performance.now() + budgetMs;
|
|
6579
|
+
for (const view of dirty) {
|
|
6580
|
+
dirty.delete(view);
|
|
6581
|
+
paint(view);
|
|
6582
|
+
if (performance.now() >= deadline) break;
|
|
6583
|
+
}
|
|
6584
|
+
invalidate();
|
|
6585
|
+
invalidateShadow();
|
|
6586
|
+
return dirty.size;
|
|
6587
|
+
}
|
|
6478
6588
|
function dispose(view) {
|
|
6479
|
-
|
|
6480
|
-
view.units
|
|
6589
|
+
dirty.delete(view);
|
|
6590
|
+
if (view.units) {
|
|
6591
|
+
view.units.removeFromParent();
|
|
6592
|
+
view.units.dispose();
|
|
6593
|
+
}
|
|
6594
|
+
view.unitMaterial.dispose();
|
|
6481
6595
|
for (const m of view.shells) {
|
|
6482
6596
|
m.removeFromParent();
|
|
6483
6597
|
m.geometry.dispose();
|
|
6484
6598
|
m.material.dispose();
|
|
6485
6599
|
}
|
|
6486
6600
|
view.mask.texture.dispose();
|
|
6601
|
+
view.core.texture.dispose();
|
|
6487
6602
|
}
|
|
6488
6603
|
return {
|
|
6489
6604
|
/** Handles a `stage` event of the house's object. Returns true when the wall was cut. */
|
|
6490
6605
|
stage(event) {
|
|
6491
6606
|
return event.stage === 2 && !!event.opening ? draw2(event.part, { opening: event.opening }) : false;
|
|
6492
6607
|
},
|
|
6493
|
-
/**
|
|
6608
|
+
/**
|
|
6609
|
+
* Handles a `spall` of a wall of units: the coat comes off in a ragged patch and the units show under it, drawn by the house (the destruction
|
|
6610
|
+
* renderer leaves its flat patch out on the meshes `claim` marked). Returns true when the wall was cut.
|
|
6611
|
+
*/
|
|
6612
|
+
spall(event) {
|
|
6613
|
+
const r = Math.sqrt(Math.max(0, event.area) / Math.PI);
|
|
6614
|
+
return r > 0 ? draw2(event.part, { spall: { center: event.point, radius: r } }) : false;
|
|
6615
|
+
},
|
|
6616
|
+
/**
|
|
6617
|
+
* Marks the meshes that draw a wall of units as drawing its spalls itself (`userData.drawnSpalls`, the part ids): the destruction renderer then
|
|
6618
|
+
* throws the bits and leaves its patch out. Call it once the spec is known.
|
|
6619
|
+
*/
|
|
6620
|
+
claim() {
|
|
6621
|
+
var _a;
|
|
6622
|
+
for (const q of opts.spec()?.parts ?? []) {
|
|
6623
|
+
if (!q.units) continue;
|
|
6624
|
+
const host = hostOf(q.id);
|
|
6625
|
+
if (!host) continue;
|
|
6626
|
+
for (const it of structure.meshesOf(host)) {
|
|
6627
|
+
if (!it.mesh) continue;
|
|
6628
|
+
const list = (_a = it.mesh.userData).drawnSpalls ?? (_a.drawnSpalls = []);
|
|
6629
|
+
if (!list.includes(q.id)) list.push(q.id);
|
|
6630
|
+
}
|
|
6631
|
+
}
|
|
6632
|
+
},
|
|
6633
|
+
/**
|
|
6634
|
+
* Handles a `chip` event: what came out (a unit, or a crater a shot broke through one) leaves a hole in the wall where it was; a `torn` chip (what a blow
|
|
6635
|
+
* tore off a face) leaves the marks the renderer drew. Returns true when the wall was cut.
|
|
6636
|
+
*/
|
|
6494
6637
|
chip(event) {
|
|
6495
|
-
|
|
6638
|
+
if (event.torn) return false;
|
|
6639
|
+
return draw2(event.part, { chip: { center: event.center, size: event.size ?? [0, 0, 0], rotation: event.rotation ?? [0, 0, 0, 1] } });
|
|
6496
6640
|
},
|
|
6497
6641
|
/** The part broke or fell: its damage goes (the plain range stays collapsed, the renderer hides the part). */
|
|
6498
6642
|
drop(partId) {
|
|
@@ -6500,9 +6644,11 @@ function installDestructionBreach(K, _shared) {
|
|
|
6500
6644
|
if (!v) return;
|
|
6501
6645
|
dispose(v);
|
|
6502
6646
|
views.delete(partId);
|
|
6647
|
+
if (v.saved) dropped.push(v);
|
|
6503
6648
|
},
|
|
6504
6649
|
/** LOD 0 draws the cut wall; LOD 1 and 2 give the wall its box back. */
|
|
6505
6650
|
setLod(level) {
|
|
6651
|
+
flush(Infinity);
|
|
6506
6652
|
const was = lod;
|
|
6507
6653
|
lod = level;
|
|
6508
6654
|
if (was === 0 === (level === 0)) return;
|
|
@@ -6521,15 +6667,20 @@ function installDestructionBreach(K, _shared) {
|
|
|
6521
6667
|
if (lod === 0) plain(v, true);
|
|
6522
6668
|
}
|
|
6523
6669
|
views.clear();
|
|
6670
|
+
for (const v of dropped) plain(v, true);
|
|
6671
|
+
dropped.length = 0;
|
|
6524
6672
|
},
|
|
6673
|
+
flush,
|
|
6525
6674
|
/** The wall has damage drawn. */
|
|
6526
6675
|
has(partId) {
|
|
6527
6676
|
return views.has(partId);
|
|
6528
6677
|
},
|
|
6529
|
-
/** Walls with damage drawn, units out of them, and units laid bare at the edge of the cuts. */
|
|
6530
|
-
statistics: () => ({
|
|
6678
|
+
/** Walls with damage drawn, units out of them, craters shots broke through units that stay, and units laid bare at the edge of the cuts. */
|
|
6679
|
+
statistics: () => (flush(Infinity), {
|
|
6531
6680
|
walls: views.size,
|
|
6532
6681
|
gone: [...views.values()].reduce((n, v) => n + v.gone, 0),
|
|
6682
|
+
craters: [...views.values()].reduce((n, v) => n + v.pits, 0),
|
|
6683
|
+
flakes: [...views.values()].reduce((n, v) => n + v.flakes, 0),
|
|
6533
6684
|
units: [...views.values()].reduce((n, v) => n + v.units.count, 0)
|
|
6534
6685
|
})
|
|
6535
6686
|
};
|
|
@@ -6538,6 +6689,7 @@ function installDestructionBreach(K, _shared) {
|
|
|
6538
6689
|
}
|
|
6539
6690
|
|
|
6540
6691
|
// src/destruction/world.ts
|
|
6692
|
+
var FOLLOW_FLUSH_FALLBACK_MS = 100;
|
|
6541
6693
|
function installDestructionWorld(K, shared) {
|
|
6542
6694
|
const { THREE, cfg, invalidateShadow, invalidate, rngString, FRAGMENT_GEO, fragmentMaterial, createBreachView } = K;
|
|
6543
6695
|
function createHouseDestruction(root, house, structure, parts, colliders, water) {
|
|
@@ -6546,7 +6698,7 @@ function installDestructionWorld(K, shared) {
|
|
|
6546
6698
|
const R = cfg.destructionRenderer || null, listeners = /* @__PURE__ */ new Set();
|
|
6547
6699
|
D.renderer = R;
|
|
6548
6700
|
D.spec = null;
|
|
6549
|
-
const breach = createBreachView({ house, structure, spec: () => D.spec });
|
|
6701
|
+
const breach = createBreachView({ house, structure, spec: () => D.spec, deferPaint: true, ...cfg.breachPaintBudgetMs !== void 0 ? { paintBudgetMs: cfg.breachPaintBudgetMs } : {} });
|
|
6550
6702
|
D.breach = breach;
|
|
6551
6703
|
D.onEvent = (fn) => {
|
|
6552
6704
|
listeners.add(fn);
|
|
@@ -6731,6 +6883,9 @@ function installDestructionWorld(K, shared) {
|
|
|
6731
6883
|
case "chip":
|
|
6732
6884
|
breach.chip(e);
|
|
6733
6885
|
break;
|
|
6886
|
+
case "spall":
|
|
6887
|
+
breach.spall(e);
|
|
6888
|
+
break;
|
|
6734
6889
|
case "collapse":
|
|
6735
6890
|
for (const id of e.parts) {
|
|
6736
6891
|
breach.drop(id);
|
|
@@ -6772,7 +6927,8 @@ function installDestructionWorld(K, shared) {
|
|
|
6772
6927
|
D.specOptions = specOptions || null;
|
|
6773
6928
|
D.spec = structure.spec(specOptions);
|
|
6774
6929
|
D.id = D.world.add(D.spec, { id: "house3d:" + ++shared.destructionSeq, position: [p.x, p.y, p.z], rotation: [q.x, q.y, q.z, q.w] });
|
|
6775
|
-
|
|
6930
|
+
breach.claim();
|
|
6931
|
+
if (R) R.bind(D.id, house, { parts: rangesOf(), onFrame: () => breach.flush() });
|
|
6776
6932
|
off = D.world.on("*", handleEvent);
|
|
6777
6933
|
D.active = true;
|
|
6778
6934
|
return D;
|
|
@@ -6787,6 +6943,8 @@ function installDestructionWorld(K, shared) {
|
|
|
6787
6943
|
};
|
|
6788
6944
|
D.follow = (world, id, spec) => {
|
|
6789
6945
|
D.spec = spec;
|
|
6946
|
+
breach.claim();
|
|
6947
|
+
let fallback = null;
|
|
6790
6948
|
const offFollow = world.on("*", (e) => {
|
|
6791
6949
|
if (e.object !== id) return;
|
|
6792
6950
|
switch (e.type) {
|
|
@@ -6796,6 +6954,9 @@ function installDestructionWorld(K, shared) {
|
|
|
6796
6954
|
case "chip":
|
|
6797
6955
|
breach.chip(e);
|
|
6798
6956
|
break;
|
|
6957
|
+
case "spall":
|
|
6958
|
+
breach.spall(e);
|
|
6959
|
+
break;
|
|
6799
6960
|
case "fracture":
|
|
6800
6961
|
breach.drop(e.part);
|
|
6801
6962
|
break;
|
|
@@ -6805,10 +6966,16 @@ function installDestructionWorld(K, shared) {
|
|
|
6805
6966
|
default:
|
|
6806
6967
|
break;
|
|
6807
6968
|
}
|
|
6969
|
+
if (!fallback && typeof setTimeout === "function") fallback = setTimeout(() => {
|
|
6970
|
+
fallback = null;
|
|
6971
|
+
breach.flush(Infinity);
|
|
6972
|
+
}, FOLLOW_FLUSH_FALLBACK_MS);
|
|
6808
6973
|
});
|
|
6809
6974
|
return { restore: () => breach.restore(), dispose: () => {
|
|
6810
6975
|
offFollow();
|
|
6811
6976
|
breach.restore();
|
|
6977
|
+
}, update: () => {
|
|
6978
|
+
breach.flush();
|
|
6812
6979
|
} };
|
|
6813
6980
|
};
|
|
6814
6981
|
D.piece = (id) => D.active ? D.world.part(D.id, id) : null;
|
|
@@ -6841,6 +7008,7 @@ function installDestructionWorld(K, shared) {
|
|
|
6841
7008
|
return r;
|
|
6842
7009
|
};
|
|
6843
7010
|
D.animate = (dt) => {
|
|
7011
|
+
breach.flush();
|
|
6844
7012
|
if (R) return;
|
|
6845
7013
|
let moved = false;
|
|
6846
7014
|
for (const [id, list] of bodies) {
|
|
@@ -8307,6 +8475,7 @@ function createFloorplanTestModule(options = {}) {
|
|
|
8307
8475
|
houseRenderer = create(THREE, {
|
|
8308
8476
|
locale,
|
|
8309
8477
|
lod: { interior: 10, hide: 40 },
|
|
8478
|
+
breachPaintBudgetMs: 4,
|
|
8310
8479
|
...options.house,
|
|
8311
8480
|
quality: settings.quality,
|
|
8312
8481
|
...options.world ? { destruction: options.world } : {},
|
|
@@ -8462,9 +8631,39 @@ function createFloorplanTestModule(options = {}) {
|
|
|
8462
8631
|
host?.log(text.steps.no_door);
|
|
8463
8632
|
return;
|
|
8464
8633
|
}
|
|
8465
|
-
|
|
8634
|
+
const fo = target.target;
|
|
8635
|
+
const reach = fo && host?.reachFor && fo.type === "swing" && fo.gs?.[0] && fo.handles?.[0] ? doorReach(p, fo) : null;
|
|
8636
|
+
if (reach && host?.reachFor) {
|
|
8637
|
+
host.reachFor({ ...reach, onGrip: () => void p.house.toggleDoor(fo) });
|
|
8638
|
+
} else p.house.interact(eyeOf(p));
|
|
8466
8639
|
host?.log(fill(text.steps.door, { text: String(target.text) }));
|
|
8467
8640
|
};
|
|
8641
|
+
const doorReach = (p, fo) => {
|
|
8642
|
+
const g = fo.gs[0];
|
|
8643
|
+
const pivot = fo.handles[0].pivot;
|
|
8644
|
+
const at = (angle) => {
|
|
8645
|
+
const c = Math.cos(angle);
|
|
8646
|
+
const s = Math.sin(angle);
|
|
8647
|
+
const v = new THREE.Vector3(pivot[0] * c + pivot[2] * s, pivot[1], -pivot[0] * s + pivot[2] * c).add(g.position);
|
|
8648
|
+
return g.parent ? g.parent.localToWorld(v) : v;
|
|
8649
|
+
};
|
|
8650
|
+
const camera = host?.camera;
|
|
8651
|
+
const eye = new THREE.Vector3(camera.position.x, 0, camera.position.z);
|
|
8652
|
+
const flat = (v) => Math.hypot(v.x - eye.x, v.z - eye.z);
|
|
8653
|
+
const closed = at(0);
|
|
8654
|
+
const open = at(fo.rot);
|
|
8655
|
+
const opening = !fo.open;
|
|
8656
|
+
return {
|
|
8657
|
+
handle: () => {
|
|
8658
|
+
g.updateMatrixWorld(true);
|
|
8659
|
+
return g.localToWorld(new THREE.Vector3(pivot[0], pivot[1], pivot[2])).toArray();
|
|
8660
|
+
},
|
|
8661
|
+
hinge: () => g.getWorldPosition(new THREE.Vector3()).toArray(),
|
|
8662
|
+
seconds: fo.locked || fo.blockedFlag ? 0.6 : 0.55,
|
|
8663
|
+
pull: opening ? flat(open) <= flat(closed) : flat(closed) <= flat(open),
|
|
8664
|
+
locked: !!(fo.locked || fo.blockedFlag)
|
|
8665
|
+
};
|
|
8666
|
+
};
|
|
8468
8667
|
const strike = () => {
|
|
8469
8668
|
const world = options.world;
|
|
8470
8669
|
if (!world || !host) return;
|