@adatechnology/game-destruction-three 0.1.2 → 0.2.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 +21 -0
- package/dist/index.d.ts +19 -3
- package/dist/index.global.js +425 -37
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +425 -37
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.global.js
CHANGED
|
@@ -2329,6 +2329,8 @@ void main() {
|
|
|
2329
2329
|
__publicField(this, "b");
|
|
2330
2330
|
__publicField(this, "m4");
|
|
2331
2331
|
__publicField(this, "box");
|
|
2332
|
+
__publicField(this, "t");
|
|
2333
|
+
__publicField(this, "lbox");
|
|
2332
2334
|
/** Total live decals across meshes (kept in sync by add/remove). */
|
|
2333
2335
|
__publicField(this, "decalCount", 0);
|
|
2334
2336
|
__publicField(this, "tints", /* @__PURE__ */ new WeakMap());
|
|
@@ -2337,9 +2339,22 @@ void main() {
|
|
|
2337
2339
|
this.b = tmpV(THREE);
|
|
2338
2340
|
this.m4 = new THREE.Matrix4();
|
|
2339
2341
|
this.box = new THREE.Box3();
|
|
2342
|
+
this.t = tmpV(THREE);
|
|
2343
|
+
this.lbox = new THREE.Box3();
|
|
2340
2344
|
}
|
|
2341
|
-
|
|
2342
|
-
|
|
2345
|
+
/**
|
|
2346
|
+
* @param range vertex range `[v0, v1)` of a merged mesh this part is drawn by (see `MeshRange`); `hosts` caches the shared state per mesh for one `bind`.
|
|
2347
|
+
*/
|
|
2348
|
+
makeMeshVis(mesh, home, range, hosts) {
|
|
2349
|
+
let host = null;
|
|
2350
|
+
if (range) {
|
|
2351
|
+
host = hosts?.get(mesh) ?? null;
|
|
2352
|
+
if (!host) {
|
|
2353
|
+
host = { mesh, origGeometry: mesh.geometry, origMaterial: mesh.material, ownGeo: false, work: null, ownMat: false, sharers: [] };
|
|
2354
|
+
hosts?.set(mesh, host);
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
const mv = {
|
|
2343
2358
|
mesh,
|
|
2344
2359
|
home,
|
|
2345
2360
|
origGeometry: mesh.geometry,
|
|
@@ -2356,31 +2371,72 @@ void main() {
|
|
|
2356
2371
|
decals: [],
|
|
2357
2372
|
meltApplied: 0,
|
|
2358
2373
|
bottomY: 0,
|
|
2359
|
-
moved: false
|
|
2374
|
+
moved: false,
|
|
2375
|
+
host,
|
|
2376
|
+
range: range ? [range[0], range[1]] : null,
|
|
2377
|
+
merged: host && range ? { host, range: [range[0], range[1]] } : null,
|
|
2378
|
+
loose: false,
|
|
2379
|
+
collapsed: false,
|
|
2380
|
+
hidden: false,
|
|
2381
|
+
touched: false,
|
|
2382
|
+
charK: 0,
|
|
2383
|
+
charHex: 0,
|
|
2384
|
+
glowHex: 0,
|
|
2385
|
+
glowK: 0
|
|
2360
2386
|
};
|
|
2387
|
+
host?.sharers.push(mv);
|
|
2388
|
+
return mv;
|
|
2361
2389
|
}
|
|
2362
2390
|
vertexCount(mv) {
|
|
2391
|
+
if (mv.range) return mv.range[1] - mv.range[0];
|
|
2363
2392
|
return mv.mesh.geometry.getAttribute(POS_ATTR)?.count ?? 0;
|
|
2364
2393
|
}
|
|
2394
|
+
/** Is the part drawn? (A ranged part shares its mesh, so `mesh.visible` says nothing about it.) */
|
|
2395
|
+
shown(mv) {
|
|
2396
|
+
return mv.host ? !mv.hidden : mv.mesh.visible;
|
|
2397
|
+
}
|
|
2398
|
+
/** Box of the part's vertices in the space of its mesh (the whole geometry for a plain mesh). */
|
|
2399
|
+
localBox(mv, out) {
|
|
2400
|
+
out.makeEmpty();
|
|
2401
|
+
if (!mv.host || !mv.range) {
|
|
2402
|
+
const geo = mv.mesh.geometry;
|
|
2403
|
+
if (!geo.boundingBox) geo.computeBoundingBox();
|
|
2404
|
+
return geo.boundingBox ? out.copy(geo.boundingBox) : out;
|
|
2405
|
+
}
|
|
2406
|
+
const arr = mv.host.work ?? positionAttr(mv.mesh.geometry)?.array;
|
|
2407
|
+
if (!arr) return out;
|
|
2408
|
+
for (let i = mv.range[0] * 3; i < mv.range[1] * 3; i += 3) out.expandByPoint(this.t.set(arr[i], arr[i + 1], arr[i + 2]));
|
|
2409
|
+
return out;
|
|
2410
|
+
}
|
|
2411
|
+
/** Box of the part in world space. */
|
|
2412
|
+
worldBox(mv, out) {
|
|
2413
|
+
if (!mv.host) return out.setFromObject(mv.mesh);
|
|
2414
|
+
this.localBox(mv, out);
|
|
2415
|
+
mv.mesh.updateWorldMatrix(true, false);
|
|
2416
|
+
return out.isEmpty() ? out : out.applyMatrix4(mv.mesh.matrixWorld);
|
|
2417
|
+
}
|
|
2365
2418
|
/**
|
|
2366
2419
|
* Average vertex colour of a mesh whose material uses `vertexColors` (computed once per mesh, from its original geometry), else null. Fragments
|
|
2367
2420
|
* and debris of such a mesh multiply it by `material.color`; without it they come out white.
|
|
2368
2421
|
*/
|
|
2369
2422
|
vertexTint(mv) {
|
|
2370
|
-
if (this.tints.has(mv
|
|
2423
|
+
if (this.tints.has(mv)) return this.tints.get(mv) ?? null;
|
|
2371
2424
|
let tint = null;
|
|
2372
2425
|
const used = materialsOf(mv.origMaterial).some((m) => m.vertexColors === true);
|
|
2373
2426
|
const attr = used ? mv.origGeometry.getAttribute("color") : void 0;
|
|
2374
2427
|
if (attr && attr.count > 0 && attr.itemSize >= 3) {
|
|
2428
|
+
const from = mv.merged ? mv.merged.range[0] : 0;
|
|
2429
|
+
const to = mv.merged ? Math.min(attr.count, mv.merged.range[1]) : attr.count;
|
|
2375
2430
|
let r = 0, g = 0, b = 0;
|
|
2376
|
-
for (let i =
|
|
2431
|
+
for (let i = from; i < to; i++) {
|
|
2377
2432
|
r += attr.getX(i);
|
|
2378
2433
|
g += attr.getY(i);
|
|
2379
2434
|
b += attr.getZ(i);
|
|
2380
2435
|
}
|
|
2381
|
-
|
|
2436
|
+
const n = Math.max(1, to - from);
|
|
2437
|
+
tint = new this.THREE.Color(r / n, g / n, b / n);
|
|
2382
2438
|
}
|
|
2383
|
-
this.tints.set(mv
|
|
2439
|
+
this.tints.set(mv, tint);
|
|
2384
2440
|
return tint;
|
|
2385
2441
|
}
|
|
2386
2442
|
/** Colour of the original material (before any char) times the vertex tint, `0xRRGGBB`, or -1. */
|
|
@@ -2404,6 +2460,7 @@ void main() {
|
|
|
2404
2460
|
}
|
|
2405
2461
|
/** Clone the geometry (vertex edits); false when its positions cannot be edited. */
|
|
2406
2462
|
ownGeometry(mv) {
|
|
2463
|
+
if (mv.host) return this.ownHostGeometry(mv.host);
|
|
2407
2464
|
if (mv.ownGeo) return true;
|
|
2408
2465
|
const pos = positionAttr(mv.mesh.geometry);
|
|
2409
2466
|
if (!pos) return false;
|
|
@@ -2413,6 +2470,16 @@ void main() {
|
|
|
2413
2470
|
mv.work = new Float32Array(positionAttr(g).array);
|
|
2414
2471
|
return true;
|
|
2415
2472
|
}
|
|
2473
|
+
ownHostGeometry(host) {
|
|
2474
|
+
if (host.ownGeo) return true;
|
|
2475
|
+
const pos = positionAttr(host.mesh.geometry);
|
|
2476
|
+
if (!pos) return false;
|
|
2477
|
+
host.ownGeo = true;
|
|
2478
|
+
const g = host.mesh.geometry.clone();
|
|
2479
|
+
host.mesh.geometry = g;
|
|
2480
|
+
host.work = new Float32Array(positionAttr(g).array);
|
|
2481
|
+
return true;
|
|
2482
|
+
}
|
|
2416
2483
|
// ------------------------------------------------------------------------------------------------------------------------------------
|
|
2417
2484
|
// Dents
|
|
2418
2485
|
// ------------------------------------------------------------------------------------------------------------------------------------
|
|
@@ -2430,10 +2497,15 @@ void main() {
|
|
|
2430
2497
|
const r = dent.radius / s;
|
|
2431
2498
|
const depth = dent.depth / s;
|
|
2432
2499
|
const geo = mesh.geometry;
|
|
2433
|
-
|
|
2434
|
-
if (
|
|
2500
|
+
let bb;
|
|
2501
|
+
if (mv.host) bb = this.localBox(mv, this.lbox);
|
|
2502
|
+
else {
|
|
2503
|
+
if (!geo.boundingBox) geo.computeBoundingBox();
|
|
2504
|
+
bb = geo.boundingBox;
|
|
2505
|
+
}
|
|
2506
|
+
if (bb && !bb.isEmpty() && bb.distanceToPoint(lp) > r) return false;
|
|
2435
2507
|
if (!this.ownGeometry(mv)) return false;
|
|
2436
|
-
const work = mv.work;
|
|
2508
|
+
const work = mv.host ? mv.host.work : mv.work;
|
|
2437
2509
|
const pos = positionAttr(mesh.geometry);
|
|
2438
2510
|
const r2 = r * r;
|
|
2439
2511
|
let touched = false;
|
|
@@ -2443,7 +2515,9 @@ void main() {
|
|
|
2443
2515
|
const nx = ln.x;
|
|
2444
2516
|
const ny = ln.y;
|
|
2445
2517
|
const nz = ln.z;
|
|
2446
|
-
|
|
2518
|
+
const from = mv.range ? mv.range[0] * 3 : 0;
|
|
2519
|
+
const to = mv.range ? mv.range[1] * 3 : work.length;
|
|
2520
|
+
for (let i = from; i < to; i += 3) {
|
|
2447
2521
|
const dx = work[i] - px;
|
|
2448
2522
|
const dy = work[i + 1] - py;
|
|
2449
2523
|
const dz = work[i + 2] - pz;
|
|
@@ -2457,11 +2531,16 @@ void main() {
|
|
|
2457
2531
|
touched = true;
|
|
2458
2532
|
}
|
|
2459
2533
|
if (!touched) return false;
|
|
2534
|
+
mv.touched = true;
|
|
2460
2535
|
this.writePositions(mv, pos);
|
|
2461
2536
|
return true;
|
|
2462
2537
|
}
|
|
2463
2538
|
/** Copy `work` (dents) into the geometry, adding the melt sag when the mesh is melting. */
|
|
2464
2539
|
writePositions(mv, pos) {
|
|
2540
|
+
if (mv.host) {
|
|
2541
|
+
this.rebuildHost(mv.host);
|
|
2542
|
+
return;
|
|
2543
|
+
}
|
|
2465
2544
|
const work = mv.work;
|
|
2466
2545
|
const arr = pos.array;
|
|
2467
2546
|
const melt = mv.meltApplied;
|
|
@@ -2575,6 +2654,12 @@ void main() {
|
|
|
2575
2654
|
pv.charApplied = char;
|
|
2576
2655
|
const target = new this.THREE.Color(charColor);
|
|
2577
2656
|
for (const mv of pv.meshes) {
|
|
2657
|
+
if (mv.host) {
|
|
2658
|
+
mv.charK = clamp(char, 0, 1);
|
|
2659
|
+
mv.charHex = charColor;
|
|
2660
|
+
this.paint(mv);
|
|
2661
|
+
continue;
|
|
2662
|
+
}
|
|
2578
2663
|
this.ownMaterial(mv);
|
|
2579
2664
|
const mats = materialsOf(mv.mesh.material);
|
|
2580
2665
|
mats.forEach((m, i) => {
|
|
@@ -2592,6 +2677,12 @@ void main() {
|
|
|
2592
2677
|
pv.glowApplied = key;
|
|
2593
2678
|
const intensity = hex === 0 ? 0 : clamp(0.35 + (temperatureC - 450) / 900, 0, 1.4);
|
|
2594
2679
|
for (const mv of pv.meshes) {
|
|
2680
|
+
if (mv.host) {
|
|
2681
|
+
mv.glowHex = hex;
|
|
2682
|
+
mv.glowK = intensity;
|
|
2683
|
+
this.paint(mv);
|
|
2684
|
+
continue;
|
|
2685
|
+
}
|
|
2595
2686
|
this.ownMaterial(mv);
|
|
2596
2687
|
for (const m of materialsOf(mv.mesh.material)) {
|
|
2597
2688
|
const e = m.emissive;
|
|
@@ -2612,6 +2703,12 @@ void main() {
|
|
|
2612
2703
|
pv.meltApplied = m;
|
|
2613
2704
|
for (const mv of pv.meshes) {
|
|
2614
2705
|
const mesh = mv.mesh;
|
|
2706
|
+
if (mv.host) {
|
|
2707
|
+
mv.meltApplied = m;
|
|
2708
|
+
mv.touched = true;
|
|
2709
|
+
if (!(this.lowQuality && this.vertexCount(mv) > 2e4) && this.ownGeometry(mv)) this.rebuildHost(mv.host);
|
|
2710
|
+
continue;
|
|
2711
|
+
}
|
|
2615
2712
|
if (mv.meltApplied === 0 && m > 0) {
|
|
2616
2713
|
mesh.updateWorldMatrix(true, false);
|
|
2617
2714
|
mv.bottomY = this.box.setFromObject(mesh).min.y;
|
|
@@ -2642,10 +2739,270 @@ void main() {
|
|
|
2642
2739
|
// ------------------------------------------------------------------------------------------------------------------------------------
|
|
2643
2740
|
hide(pv, reason) {
|
|
2644
2741
|
pv.gone = reason;
|
|
2645
|
-
for (const mv of pv.meshes)
|
|
2742
|
+
for (const mv of pv.meshes) {
|
|
2743
|
+
if (!mv.host || mv.loose) {
|
|
2744
|
+
mv.mesh.visible = false;
|
|
2745
|
+
mv.hidden = true;
|
|
2746
|
+
continue;
|
|
2747
|
+
}
|
|
2748
|
+
mv.hidden = true;
|
|
2749
|
+
for (const d of mv.decals) d.obj.visible = false;
|
|
2750
|
+
this.collapse(mv);
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
// ------------------------------------------------------------------------------------------------------------------------------------
|
|
2754
|
+
// Parts drawn by a vertex range of a merged mesh
|
|
2755
|
+
// ------------------------------------------------------------------------------------------------------------------------------------
|
|
2756
|
+
/** Collapse the part's vertices in the merged mesh to one point (its triangles become degenerate and vanish). */
|
|
2757
|
+
collapse(mv) {
|
|
2758
|
+
if (!mv.merged || !this.ownHostGeometry(mv.merged.host)) return;
|
|
2759
|
+
mv.collapsed = true;
|
|
2760
|
+
mv.touched = true;
|
|
2761
|
+
this.rebuildHost(mv.merged.host);
|
|
2762
|
+
}
|
|
2763
|
+
/** Write `work` into the geometry again with the melt of ranged parts and the collapsed ranges applied. */
|
|
2764
|
+
rebuildHost(host) {
|
|
2765
|
+
const geo = host.mesh.geometry;
|
|
2766
|
+
const pos = positionAttr(geo);
|
|
2767
|
+
const work = host.work;
|
|
2768
|
+
if (!pos || !work) return;
|
|
2769
|
+
const arr = pos.array;
|
|
2770
|
+
arr.set(work);
|
|
2771
|
+
for (const s of host.sharers) {
|
|
2772
|
+
if (s.merged && s.merged.host === host) {
|
|
2773
|
+
if (s.collapsed) this.collapseRange(arr, s.merged.range);
|
|
2774
|
+
else if (s.meltApplied > 0 && !s.loose) this.meltRange(arr, work, s.merged.range, s.meltApplied);
|
|
2775
|
+
} else if (s.host === host && s.range && s.meltApplied > 0) this.meltRange(arr, work, s.range, s.meltApplied);
|
|
2776
|
+
}
|
|
2777
|
+
pos.needsUpdate = true;
|
|
2778
|
+
geo.computeVertexNormals();
|
|
2779
|
+
geo.boundingBox = null;
|
|
2780
|
+
geo.boundingSphere = null;
|
|
2781
|
+
}
|
|
2782
|
+
collapseRange(arr, range) {
|
|
2783
|
+
const x = arr[range[0] * 3];
|
|
2784
|
+
const y = arr[range[0] * 3 + 1];
|
|
2785
|
+
const z = arr[range[0] * 3 + 2];
|
|
2786
|
+
for (let i = range[0] * 3; i < range[1] * 3; i += 3) {
|
|
2787
|
+
arr[i] = x;
|
|
2788
|
+
arr[i + 1] = y;
|
|
2789
|
+
arr[i + 2] = z;
|
|
2790
|
+
}
|
|
2791
|
+
}
|
|
2792
|
+
/** Melt of one range: shorter in Y (down to 25 %) with the bottom fixed, a bit wider around its centre, with the same noise as a whole mesh. */
|
|
2793
|
+
meltRange(arr, work, range, melt) {
|
|
2794
|
+
let minY = Infinity;
|
|
2795
|
+
let maxY = -Infinity;
|
|
2796
|
+
let cx = 0;
|
|
2797
|
+
let cz = 0;
|
|
2798
|
+
const n = Math.max(1, range[1] - range[0]);
|
|
2799
|
+
for (let i = range[0] * 3; i < range[1] * 3; i += 3) {
|
|
2800
|
+
minY = Math.min(minY, work[i + 1]);
|
|
2801
|
+
maxY = Math.max(maxY, work[i + 1]);
|
|
2802
|
+
cx += work[i];
|
|
2803
|
+
cz += work[i + 2];
|
|
2804
|
+
}
|
|
2805
|
+
cx /= n;
|
|
2806
|
+
cz /= n;
|
|
2807
|
+
const h = Math.max(1e-4, maxY - minY);
|
|
2808
|
+
const sy = lerp(1, 0.25, melt);
|
|
2809
|
+
const sxz = 1 + 0.18 * melt;
|
|
2810
|
+
for (let i = range[0] * 3, v = 0; i < range[1] * 3; i += 3, v++) {
|
|
2811
|
+
const noise = (Math.imul(v + 1, 2654435761) >>> 0) % 1e3 / 1e3;
|
|
2812
|
+
arr[i] = cx + (work[i] - cx) * sxz;
|
|
2813
|
+
arr[i + 2] = cz + (work[i + 2] - cz) * sxz;
|
|
2814
|
+
arr[i + 1] = Math.max(minY, minY + (work[i + 1] - minY) * sy - noise * melt * h * 0.04);
|
|
2815
|
+
}
|
|
2816
|
+
}
|
|
2817
|
+
/** The colour attribute of the host's current geometry (created white when there is none) with vertex colours switched on in its material. */
|
|
2818
|
+
ensureColors(host) {
|
|
2819
|
+
if (!this.ownHostGeometry(host)) return null;
|
|
2820
|
+
const geo = host.mesh.geometry;
|
|
2821
|
+
let attr = geo.getAttribute("color");
|
|
2822
|
+
if (!attr) {
|
|
2823
|
+
const n = geo.getAttribute(POS_ATTR).count;
|
|
2824
|
+
attr = new this.THREE.BufferAttribute(new Float32Array(n * 3).fill(1), 3);
|
|
2825
|
+
geo.setAttribute("color", attr);
|
|
2826
|
+
}
|
|
2827
|
+
if (!host.ownMat) {
|
|
2828
|
+
host.ownMat = true;
|
|
2829
|
+
const src = materialsOf(host.mesh.material);
|
|
2830
|
+
const cloned = src.map((m) => m.clone());
|
|
2831
|
+
host.mesh.material = Array.isArray(host.mesh.material) ? cloned : cloned[0];
|
|
2832
|
+
}
|
|
2833
|
+
for (const m of materialsOf(host.mesh.material)) {
|
|
2834
|
+
if (m.vertexColors !== true) {
|
|
2835
|
+
;
|
|
2836
|
+
m.vertexColors = true;
|
|
2837
|
+
m.needsUpdate = true;
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
return attr;
|
|
2841
|
+
}
|
|
2842
|
+
/** Colour of the part's vertices from the original colours, its char and its heat. Only the part's range is written. */
|
|
2843
|
+
paint(mv) {
|
|
2844
|
+
if (!mv.host || !mv.range || !mv.merged) return;
|
|
2845
|
+
const idle = mv.charK <= 0 && mv.glowK <= 0;
|
|
2846
|
+
if (idle && !mv.touched) return;
|
|
2847
|
+
const attr = this.ensureColors(mv.host);
|
|
2848
|
+
if (!attr) return;
|
|
2849
|
+
const size = attr.itemSize;
|
|
2850
|
+
const arr = attr.array;
|
|
2851
|
+
const orig = mv.merged.host.origGeometry.getAttribute("color");
|
|
2852
|
+
const oSize = orig?.itemSize ?? 3;
|
|
2853
|
+
const oArr = orig?.array;
|
|
2854
|
+
const shift = mv.merged.range[0] - mv.range[0];
|
|
2855
|
+
const ch = new this.THREE.Color(mv.charHex);
|
|
2856
|
+
const heat = new this.THREE.Color(mv.glowHex);
|
|
2857
|
+
const k = clamp(mv.charK, 0, 1);
|
|
2858
|
+
const gk = clamp(mv.glowK, 0, 1);
|
|
2859
|
+
const boost = 1 + Math.max(0, mv.glowK - 1);
|
|
2860
|
+
for (let i = mv.range[0]; i < mv.range[1]; i++) {
|
|
2861
|
+
const o = (i + shift) * oSize;
|
|
2862
|
+
let r = oArr ? oArr[o] : 1;
|
|
2863
|
+
let g = oArr ? oArr[o + 1] : 1;
|
|
2864
|
+
let b = oArr ? oArr[o + 2] : 1;
|
|
2865
|
+
if (k > 0) {
|
|
2866
|
+
r = lerp(r, ch.r, k);
|
|
2867
|
+
g = lerp(g, ch.g, k);
|
|
2868
|
+
b = lerp(b, ch.b, k);
|
|
2869
|
+
}
|
|
2870
|
+
if (gk > 0) {
|
|
2871
|
+
r = lerp(r, heat.r * boost, gk);
|
|
2872
|
+
g = lerp(g, heat.g * boost, gk);
|
|
2873
|
+
b = lerp(b, heat.b * boost, gk);
|
|
2874
|
+
}
|
|
2875
|
+
arr[i * size] = r;
|
|
2876
|
+
arr[i * size + 1] = g;
|
|
2877
|
+
arr[i * size + 2] = b;
|
|
2878
|
+
}
|
|
2879
|
+
mv.touched = true;
|
|
2880
|
+
attr.needsUpdate = true;
|
|
2881
|
+
}
|
|
2882
|
+
/**
|
|
2883
|
+
* Move a ranged part to a mesh of its own: the faces whose vertices all lie in its range are copied (with the current dents and colours) into a
|
|
2884
|
+
* new mesh that takes the merged mesh's transform and material, then attached to `group`; the range is collapsed in the merged mesh. Decals
|
|
2885
|
+
* go with it.
|
|
2886
|
+
*/
|
|
2887
|
+
extract(mv, group) {
|
|
2888
|
+
if (!mv.host || !mv.range || !mv.merged || mv.loose) return;
|
|
2889
|
+
const host = mv.host;
|
|
2890
|
+
const [v0, v1] = mv.range;
|
|
2891
|
+
const src = host.mesh.geometry;
|
|
2892
|
+
const geo = new this.THREE.BufferGeometry();
|
|
2893
|
+
const count = v1 - v0;
|
|
2894
|
+
for (const name of Object.keys(src.attributes)) {
|
|
2895
|
+
const a = src.getAttribute(name);
|
|
2896
|
+
if (a.isInterleavedBufferAttribute) continue;
|
|
2897
|
+
const size = a.itemSize;
|
|
2898
|
+
const from = (name === POS_ATTR ? host.work : null) ?? a.array;
|
|
2899
|
+
const slice = from.slice(v0 * size, v1 * size);
|
|
2900
|
+
const Ctor = a.array.constructor;
|
|
2901
|
+
geo.setAttribute(name, new this.THREE.BufferAttribute(new Ctor(slice), size, a.normalized));
|
|
2902
|
+
}
|
|
2903
|
+
const index = src.getIndex();
|
|
2904
|
+
if (index) {
|
|
2905
|
+
const out = [];
|
|
2906
|
+
for (let i = 0; i + 2 < index.count; i += 3) {
|
|
2907
|
+
const a = index.getX(i);
|
|
2908
|
+
const b = index.getX(i + 1);
|
|
2909
|
+
const c = index.getX(i + 2);
|
|
2910
|
+
if (a >= v0 && a < v1 && b >= v0 && b < v1 && c >= v0 && c < v1) out.push(a - v0, b - v0, c - v0);
|
|
2911
|
+
}
|
|
2912
|
+
geo.setIndex(count > 65535 ? new this.THREE.Uint32BufferAttribute(out, 1) : new this.THREE.Uint16BufferAttribute(out, 1));
|
|
2913
|
+
}
|
|
2914
|
+
const mats = materialsOf(host.mesh.material).map((m) => m.clone());
|
|
2915
|
+
const loose = new this.THREE.Mesh(geo, mats[0]);
|
|
2916
|
+
loose.name = `${host.mesh.name || "mesh"}:${v0}-${v1}`;
|
|
2917
|
+
loose.castShadow = host.mesh.castShadow;
|
|
2918
|
+
loose.receiveShadow = host.mesh.receiveShadow;
|
|
2919
|
+
loose.renderOrder = host.mesh.renderOrder;
|
|
2920
|
+
loose.frustumCulled = host.mesh.frustumCulled;
|
|
2921
|
+
loose.layers.mask = host.mesh.layers.mask;
|
|
2922
|
+
loose.position.copy(host.mesh.position);
|
|
2923
|
+
loose.quaternion.copy(host.mesh.quaternion);
|
|
2924
|
+
loose.scale.copy(host.mesh.scale);
|
|
2925
|
+
const parent = host.mesh.parent;
|
|
2926
|
+
if (parent) parent.add(loose);
|
|
2927
|
+
loose.updateMatrixWorld(true);
|
|
2928
|
+
this.collapse(mv);
|
|
2929
|
+
for (const d of mv.decals) {
|
|
2930
|
+
d.obj.visible = true;
|
|
2931
|
+
loose.add(d.obj);
|
|
2932
|
+
}
|
|
2933
|
+
const lp = positionAttr(geo);
|
|
2934
|
+
const lh = {
|
|
2935
|
+
mesh: loose,
|
|
2936
|
+
origGeometry: geo,
|
|
2937
|
+
origMaterial: loose.material,
|
|
2938
|
+
ownGeo: true,
|
|
2939
|
+
work: new Float32Array(lp.array),
|
|
2940
|
+
ownMat: true,
|
|
2941
|
+
sharers: [mv]
|
|
2942
|
+
};
|
|
2943
|
+
mv.host = lh;
|
|
2944
|
+
mv.range = [0, count];
|
|
2945
|
+
mv.mesh = loose;
|
|
2946
|
+
mv.loose = true;
|
|
2947
|
+
mv.moved = true;
|
|
2948
|
+
group.attach(loose);
|
|
2949
|
+
for (const m of materialsOf(loose.material)) m.needsUpdate = true;
|
|
2950
|
+
}
|
|
2951
|
+
restoreRanged(mv) {
|
|
2952
|
+
const merged = mv.merged;
|
|
2953
|
+
const host = merged.host;
|
|
2954
|
+
for (const d of mv.decals) this.disposeDecal(d);
|
|
2955
|
+
mv.decals.length = 0;
|
|
2956
|
+
if (mv.loose) {
|
|
2957
|
+
const loose = mv.mesh;
|
|
2958
|
+
loose.parent?.remove(loose);
|
|
2959
|
+
loose.geometry.dispose();
|
|
2960
|
+
for (const m of materialsOf(loose.material)) m.dispose();
|
|
2961
|
+
mv.host = host;
|
|
2962
|
+
mv.range = [merged.range[0], merged.range[1]];
|
|
2963
|
+
mv.mesh = host.mesh;
|
|
2964
|
+
mv.loose = false;
|
|
2965
|
+
mv.moved = false;
|
|
2966
|
+
}
|
|
2967
|
+
mv.collapsed = false;
|
|
2968
|
+
mv.hidden = false;
|
|
2969
|
+
mv.meltApplied = 0;
|
|
2970
|
+
mv.charK = 0;
|
|
2971
|
+
mv.glowK = 0;
|
|
2972
|
+
mv.touched = false;
|
|
2973
|
+
if (!host.ownGeo) return;
|
|
2974
|
+
const work = host.work;
|
|
2975
|
+
const op = positionAttr(host.origGeometry);
|
|
2976
|
+
const [v0, v1] = merged.range;
|
|
2977
|
+
if (op) work.set(op.array.subarray(v0 * 3, v1 * 3), v0 * 3);
|
|
2978
|
+
const attr = host.mesh.geometry.getAttribute("color");
|
|
2979
|
+
if (attr) {
|
|
2980
|
+
const orig = host.origGeometry.getAttribute("color");
|
|
2981
|
+
const size = attr.itemSize;
|
|
2982
|
+
const a = attr.array;
|
|
2983
|
+
for (let i = v0; i < v1; i++) {
|
|
2984
|
+
for (let c = 0; c < 3; c++) a[i * size + c] = orig ? orig.array[i * orig.itemSize + c] : 1;
|
|
2985
|
+
}
|
|
2986
|
+
attr.needsUpdate = true;
|
|
2987
|
+
}
|
|
2988
|
+
if (host.sharers.every((s) => !s.touched)) {
|
|
2989
|
+
host.mesh.geometry.dispose();
|
|
2990
|
+
host.mesh.geometry = host.origGeometry;
|
|
2991
|
+
host.work = null;
|
|
2992
|
+
host.ownGeo = false;
|
|
2993
|
+
if (host.ownMat) {
|
|
2994
|
+
for (const m of materialsOf(host.mesh.material)) m.dispose();
|
|
2995
|
+
host.mesh.material = host.origMaterial;
|
|
2996
|
+
host.ownMat = false;
|
|
2997
|
+
}
|
|
2998
|
+
} else this.rebuildHost(host);
|
|
2646
2999
|
}
|
|
2647
3000
|
/** Bring one mesh back to how it was when it was bound. */
|
|
2648
3001
|
restoreMesh(mv) {
|
|
3002
|
+
if (mv.merged) {
|
|
3003
|
+
this.restoreRanged(mv);
|
|
3004
|
+
return;
|
|
3005
|
+
}
|
|
2649
3006
|
const mesh = mv.mesh;
|
|
2650
3007
|
for (const d of mv.decals) this.disposeDecal(d);
|
|
2651
3008
|
mv.decals.length = 0;
|
|
@@ -2745,6 +3102,10 @@ void main() {
|
|
|
2745
3102
|
function materialsOf2(m) {
|
|
2746
3103
|
return Array.isArray(m) ? m : [m];
|
|
2747
3104
|
}
|
|
3105
|
+
function isMeshRange(v) {
|
|
3106
|
+
const r = v;
|
|
3107
|
+
return !!r.mesh && Array.isArray(r.range);
|
|
3108
|
+
}
|
|
2748
3109
|
function createDestructionRenderer(THREE, options) {
|
|
2749
3110
|
return new Renderer(THREE, options);
|
|
2750
3111
|
}
|
|
@@ -2838,12 +3199,23 @@ void main() {
|
|
|
2838
3199
|
const partIds = new Set((state?.parts ?? []).map((p) => p.id));
|
|
2839
3200
|
const byPart = /* @__PURE__ */ new Map();
|
|
2840
3201
|
const taken = /* @__PURE__ */ new Set();
|
|
3202
|
+
const put = (pid, e) => {
|
|
3203
|
+
const l = byPart.get(pid);
|
|
3204
|
+
if (l) l.push(e);
|
|
3205
|
+
else byPart.set(pid, [e]);
|
|
3206
|
+
};
|
|
2841
3207
|
const add = (pid, m) => {
|
|
2842
3208
|
if (taken.has(m)) return;
|
|
2843
3209
|
taken.add(m);
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
3210
|
+
put(pid, { mesh: m });
|
|
3211
|
+
};
|
|
3212
|
+
const addRange = (pid, r) => {
|
|
3213
|
+
const n = r.mesh.geometry.getAttribute("position")?.count ?? 0;
|
|
3214
|
+
const [v0, v1] = r.range;
|
|
3215
|
+
if (!Number.isInteger(v0) || !Number.isInteger(v1) || v0 < 0 || v1 <= v0 || v1 > n) {
|
|
3216
|
+
throw new Error(`game-destruction-three: invalid vertex range [${v0}, ${v1}) for part "${pid}" (the mesh has ${n} vertices)`);
|
|
3217
|
+
}
|
|
3218
|
+
put(pid, { mesh: r.mesh, range: [v0, v1] });
|
|
2847
3219
|
};
|
|
2848
3220
|
const meshesIn = (o) => {
|
|
2849
3221
|
const out = [];
|
|
@@ -2854,7 +3226,10 @@ void main() {
|
|
|
2854
3226
|
};
|
|
2855
3227
|
if (options.parts) {
|
|
2856
3228
|
for (const [pid, v] of Object.entries(options.parts)) {
|
|
2857
|
-
for (const o of Array.isArray(v) ? v : [v])
|
|
3229
|
+
for (const o of Array.isArray(v) ? v : [v]) {
|
|
3230
|
+
if (isMeshRange(o)) addRange(pid, o);
|
|
3231
|
+
else for (const m of meshesIn(o)) add(pid, m);
|
|
3232
|
+
}
|
|
2858
3233
|
}
|
|
2859
3234
|
} else {
|
|
2860
3235
|
const leftovers = [];
|
|
@@ -2867,14 +3242,15 @@ void main() {
|
|
|
2867
3242
|
if (leftovers.length) this.matchByBox(objectId, root, leftovers, add);
|
|
2868
3243
|
}
|
|
2869
3244
|
const bound = { id: objectId, root, sync: options.syncTransform !== false, parts: /* @__PURE__ */ new Map(), lastPos: null, lastQuat: null, piece: null, isSplit: false };
|
|
2870
|
-
|
|
3245
|
+
const hosts = /* @__PURE__ */ new Map();
|
|
3246
|
+
for (const pid of partIds) bound.parts.set(pid, this.makePartVis(objectId, pid, state?.parts.find((p) => p.id === pid)?.material ?? "", byPart.get(pid) ?? [], hosts));
|
|
2871
3247
|
for (const [pid, meshes] of byPart) {
|
|
2872
|
-
if (!bound.parts.has(pid)) bound.parts.set(pid, this.makePartVis(objectId, pid, this.world.part(objectId, pid)?.material ?? "", meshes));
|
|
3248
|
+
if (!bound.parts.has(pid)) bound.parts.set(pid, this.makePartVis(objectId, pid, this.world.part(objectId, pid)?.material ?? "", meshes, hosts));
|
|
2873
3249
|
}
|
|
2874
3250
|
this.bounds.set(objectId, bound);
|
|
2875
3251
|
}
|
|
2876
|
-
makePartVis(home, id, material, meshes) {
|
|
2877
|
-
return { id, home, material, meshes: meshes.map((m) => this.visuals.makeMeshVis(m, home)), charApplied: 0, glowApplied: 0, meltApplied: 0, gone: "none" };
|
|
3252
|
+
makePartVis(home, id, material, meshes, hosts) {
|
|
3253
|
+
return { id, home, material, meshes: meshes.map((m) => this.visuals.makeMeshVis(m.mesh, home, m.range, hosts)), charApplied: 0, glowApplied: 0, meltApplied: 0, gone: "none" };
|
|
2878
3254
|
}
|
|
2879
3255
|
/** Match unlabelled meshes to the part whose shape contains the centre of the mesh's bounding box. */
|
|
2880
3256
|
matchByBox(objectId, root, meshes, add) {
|
|
@@ -2934,11 +3310,13 @@ void main() {
|
|
|
2934
3310
|
const inv = root.matrixWorld.clone().invert();
|
|
2935
3311
|
const total = new this.THREE.Box3();
|
|
2936
3312
|
const m4 = new this.THREE.Matrix4();
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
3313
|
+
const lb = new this.THREE.Box3();
|
|
3314
|
+
for (const mv of meshes) {
|
|
3315
|
+
const m = mv.mesh;
|
|
3316
|
+
m.updateWorldMatrix(true, false);
|
|
3317
|
+
this.visuals.localBox(mv, lb);
|
|
3318
|
+
if (lb.isEmpty()) continue;
|
|
3319
|
+
total.union(lb.clone().applyMatrix4(m4.multiplyMatrices(inv, m.matrixWorld)));
|
|
2942
3320
|
}
|
|
2943
3321
|
if (total.isEmpty()) return { half: [0.25, 0.25, 0.25], center: new this.THREE.Vector3() };
|
|
2944
3322
|
const size = total.getSize(new this.THREE.Vector3());
|
|
@@ -2946,7 +3324,7 @@ void main() {
|
|
|
2946
3324
|
}
|
|
2947
3325
|
meshesOf(b) {
|
|
2948
3326
|
const out = [];
|
|
2949
|
-
for (const pv of b.parts.values()) if (pv.gone === "none") for (const mv of pv.meshes) out.push(mv
|
|
3327
|
+
for (const pv of b.parts.values()) if (pv.gone === "none") for (const mv of pv.meshes) out.push(mv);
|
|
2950
3328
|
return out;
|
|
2951
3329
|
}
|
|
2952
3330
|
massOf(objectId) {
|
|
@@ -3077,6 +3455,9 @@ void main() {
|
|
|
3077
3455
|
case "detach":
|
|
3078
3456
|
this.onDetach(ev);
|
|
3079
3457
|
break;
|
|
3458
|
+
case "collapse":
|
|
3459
|
+
this.splitLoose(ev.object);
|
|
3460
|
+
break;
|
|
3080
3461
|
case "disassemble_progress": {
|
|
3081
3462
|
const k = `unscrew:${ev.joint}`;
|
|
3082
3463
|
if (this.time - (this.throttles.get(k) ?? -1) > 0.25) {
|
|
@@ -3195,8 +3576,8 @@ void main() {
|
|
|
3195
3576
|
let best = null;
|
|
3196
3577
|
let bd = Infinity;
|
|
3197
3578
|
for (const mv of pv.meshes) {
|
|
3198
|
-
if (!
|
|
3199
|
-
const d = this.
|
|
3579
|
+
if (!this.visuals.shown(mv)) continue;
|
|
3580
|
+
const d = this.visuals.worldBox(mv, this.box).distanceToPoint(p);
|
|
3200
3581
|
if (d < bd) {
|
|
3201
3582
|
bd = d;
|
|
3202
3583
|
best = mv;
|
|
@@ -3216,7 +3597,7 @@ void main() {
|
|
|
3216
3597
|
const wp = new this.THREE.Vector3(ev.worldPoint[0], ev.worldPoint[1], ev.worldPoint[2]);
|
|
3217
3598
|
const wn = this.worldDir(b, ev.dent.normal);
|
|
3218
3599
|
for (const mv of pv.meshes) {
|
|
3219
|
-
if (!
|
|
3600
|
+
if (!this.visuals.shown(mv)) continue;
|
|
3220
3601
|
this.visuals.applyDent(mv, wp, wn, ev.dent);
|
|
3221
3602
|
}
|
|
3222
3603
|
if (def?.tags?.includes("metal")) this.particles.burst("sparks", ev.worldPoint, [wn.x, wn.y, wn.z], 4 + Math.min(8, ev.dent.depth * 150), 3.2);
|
|
@@ -3307,16 +3688,20 @@ void main() {
|
|
|
3307
3688
|
}
|
|
3308
3689
|
onDetach(ev) {
|
|
3309
3690
|
this.sound("detach", this.rootPoint(ev.object), 0.5);
|
|
3691
|
+
this.splitLoose(ev.object);
|
|
3692
|
+
}
|
|
3693
|
+
/** Turn every loose group of the object (a detach, a collapse) into a piece of its own. */
|
|
3694
|
+
splitLoose(objectId) {
|
|
3310
3695
|
if (!this.autoSplit) return;
|
|
3311
|
-
const b = this.bounds.get(
|
|
3696
|
+
const b = this.bounds.get(objectId);
|
|
3312
3697
|
if (!b) return;
|
|
3313
|
-
const st = this.world.state(
|
|
3698
|
+
const st = this.world.state(objectId);
|
|
3314
3699
|
if (!st || st.groups.length < 2) return;
|
|
3315
3700
|
const loose = st.groups.length - 1;
|
|
3316
3701
|
for (let i = 0; i < loose; i++) {
|
|
3317
3702
|
let newId;
|
|
3318
3703
|
try {
|
|
3319
|
-
newId = this.world.splitGroup(
|
|
3704
|
+
newId = this.world.splitGroup(objectId, 1);
|
|
3320
3705
|
} catch {
|
|
3321
3706
|
break;
|
|
3322
3707
|
}
|
|
@@ -3327,7 +3712,7 @@ void main() {
|
|
|
3327
3712
|
const { half, center } = this.measure(b.root, this.meshesOf(b));
|
|
3328
3713
|
old.halfExtents = half;
|
|
3329
3714
|
this.pieceSys.setCenter(old, center);
|
|
3330
|
-
old.mass = Math.max(0.05, this.massOf(
|
|
3715
|
+
old.mass = Math.max(0.05, this.massOf(objectId));
|
|
3331
3716
|
old.sleeping = false;
|
|
3332
3717
|
}
|
|
3333
3718
|
}
|
|
@@ -3349,8 +3734,11 @@ void main() {
|
|
|
3349
3734
|
from.parts.delete(p.id);
|
|
3350
3735
|
nb.parts.set(p.id, pv);
|
|
3351
3736
|
for (const mv of pv.meshes) {
|
|
3352
|
-
|
|
3353
|
-
|
|
3737
|
+
if (mv.host && !mv.loose) this.visuals.extract(mv, group);
|
|
3738
|
+
else {
|
|
3739
|
+
group.attach(mv.mesh);
|
|
3740
|
+
mv.moved = true;
|
|
3741
|
+
}
|
|
3354
3742
|
}
|
|
3355
3743
|
}
|
|
3356
3744
|
this.bounds.set(newId, nb);
|
|
@@ -3466,7 +3854,7 @@ void main() {
|
|
|
3466
3854
|
}
|
|
3467
3855
|
onGone(objectId, pv, reason, def) {
|
|
3468
3856
|
const bb = new this.THREE.Box3();
|
|
3469
|
-
for (const mv of pv.meshes) bb.union(this.
|
|
3857
|
+
for (const mv of pv.meshes) bb.union(this.visuals.worldBox(mv, this.box));
|
|
3470
3858
|
this.visuals.hide(pv, reason);
|
|
3471
3859
|
if (bb.isEmpty()) return;
|
|
3472
3860
|
const c = bb.getCenter(new this.THREE.Vector3());
|
|
@@ -3483,7 +3871,7 @@ void main() {
|
|
|
3483
3871
|
const bb = this.box;
|
|
3484
3872
|
bb.makeEmpty();
|
|
3485
3873
|
const tmp = new this.THREE.Box3();
|
|
3486
|
-
for (const mv of pv.meshes) if (
|
|
3874
|
+
for (const mv of pv.meshes) if (this.visuals.shown(mv)) bb.union(this.visuals.worldBox(mv, tmp));
|
|
3487
3875
|
let cx = f.point[0];
|
|
3488
3876
|
let cy = f.point[1];
|
|
3489
3877
|
let cz = f.point[2];
|