@adatechnology/game-destruction-three 0.12.1 → 0.13.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 +10 -2
- package/dist/index.global.js +651 -153
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +65 -14
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
|
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
4
|
|
|
5
5
|
// src/renderer.ts
|
|
6
|
-
import { blastShove } from "@adatechnology/game-contracts";
|
|
6
|
+
import { DEFERRED_QUEUE_ID, blastShove, warnOnce } from "@adatechnology/game-contracts";
|
|
7
7
|
import { createVfx, effectForEvent, EVENT_EFFECTS_MAP } from "@adatechnology/game-vfx";
|
|
8
8
|
import { createVfxRenderer } from "@adatechnology/game-vfx-three";
|
|
9
9
|
|
|
@@ -92,6 +92,7 @@ var SHEET_SWAY = 3;
|
|
|
92
92
|
var BIG_LENGTH = 0.2;
|
|
93
93
|
var BIG_WIDTH = 0.03;
|
|
94
94
|
var FRAGMENT_SEED = 1374496523;
|
|
95
|
+
var SHARD_SHAPES = 6;
|
|
95
96
|
var isBig = (size) => {
|
|
96
97
|
const [, middle, longest] = [...size].sort((a, b) => a - b);
|
|
97
98
|
return longest >= BIG_LENGTH && middle >= BIG_WIDTH;
|
|
@@ -141,7 +142,7 @@ var FragmentSystem = class {
|
|
|
141
142
|
return m;
|
|
142
143
|
}
|
|
143
144
|
shardGeometry(seed) {
|
|
144
|
-
const idx = seed %
|
|
145
|
+
const idx = seed % SHARD_SHAPES;
|
|
145
146
|
let g = this.shardGeos[idx];
|
|
146
147
|
if (!g) {
|
|
147
148
|
const r = createRng(1e3 + idx);
|
|
@@ -162,17 +163,32 @@ var FragmentSystem = class {
|
|
|
162
163
|
}
|
|
163
164
|
return g;
|
|
164
165
|
}
|
|
166
|
+
markParts() {
|
|
167
|
+
this.markGeometry ?? (this.markGeometry = new this.THREE.CircleGeometry(1, 10));
|
|
168
|
+
this.markMaterial ?? (this.markMaterial = new this.THREE.MeshBasicMaterial({ color: 723465, depthWrite: false, polygonOffset: true, polygonOffsetFactor: -4, polygonOffsetUnits: -4 }));
|
|
169
|
+
return { geometry: this.markGeometry, material: this.markMaterial };
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Meshes, never put in a scene by this class, that draw every shared piece of a fragment once: the box, the six shards, the bullet-hole disc, and the opaque and
|
|
173
|
+
* glass materials. A host draws them at load so the first blast does not upload these buffers in the middle of a frame.
|
|
174
|
+
*/
|
|
175
|
+
warmMeshes() {
|
|
176
|
+
const opaque = this.materialFor("warm", 8421504, false);
|
|
177
|
+
const glass = this.materialFor("warm", 8421504, true);
|
|
178
|
+
const geometries = [this.box, ...Array.from({ length: SHARD_SHAPES }, (_, index) => this.shardGeometry(index))];
|
|
179
|
+
const { geometry, material } = this.markParts();
|
|
180
|
+
return [...geometries.flatMap((shared) => [new this.THREE.Mesh(shared, opaque), new this.THREE.Mesh(shared, glass)]), new this.THREE.Mesh(geometry, material)];
|
|
181
|
+
}
|
|
165
182
|
/**
|
|
166
183
|
* Bullet holes that stay on a fragment (`Fragment.marks`): dark discs that are children of its mesh, so they fall, spin and settle with it. The mesh is
|
|
167
184
|
* a unit box scaled to the fragment, so each disc is scaled back; exact for a mark on a face, which is where a bullet hole is.
|
|
168
185
|
*/
|
|
169
186
|
addMarks(mesh, f) {
|
|
170
187
|
if (!f.marks?.length) return;
|
|
171
|
-
|
|
172
|
-
this.markMaterial ?? (this.markMaterial = new this.THREE.MeshBasicMaterial({ color: 723465, depthWrite: false, polygonOffset: true, polygonOffsetFactor: -4, polygonOffsetUnits: -4 }));
|
|
188
|
+
const { geometry, material } = this.markParts();
|
|
173
189
|
const sc = mesh.scale;
|
|
174
190
|
for (const mark of f.marks) {
|
|
175
|
-
const disc = new this.THREE.Mesh(
|
|
191
|
+
const disc = new this.THREE.Mesh(geometry, material);
|
|
176
192
|
const radius = Math.max(4e-3, mark.diameter) * 0.6;
|
|
177
193
|
const n = this.v.set(mark.normal[0], mark.normal[1], mark.normal[2]).normalize();
|
|
178
194
|
disc.position.set(mark.point[0] / sc.x + n.x * 2e-3 / sc.x, mark.point[1] / sc.y + n.y * 2e-3 / sc.y, mark.point[2] / sc.z + n.z * 2e-3 / sc.z);
|
|
@@ -1517,6 +1533,7 @@ var Visuals = class {
|
|
|
1517
1533
|
this.b = tmpV(THREE);
|
|
1518
1534
|
this.m4 = new THREE.Matrix4();
|
|
1519
1535
|
this.ray = new THREE.Raycaster();
|
|
1536
|
+
this.ray.layers.enableAll();
|
|
1520
1537
|
this.box = new THREE.Box3();
|
|
1521
1538
|
this.t = tmpV(THREE);
|
|
1522
1539
|
this.lbox = new THREE.Box3();
|
|
@@ -2507,8 +2524,26 @@ var Renderer = class {
|
|
|
2507
2524
|
__publicField(this, "queue", []);
|
|
2508
2525
|
/** Presentation work an event left for later frames (fragments still to spawn, loose groups still to split), run in order within the frame budget. */
|
|
2509
2526
|
__publicField(this, "slices", []);
|
|
2510
|
-
/** When the work of this frame must stop; `Infinity` outside
|
|
2527
|
+
/** When the work of this frame must stop; `Infinity` outside a drain, where a call finishes what it started. */
|
|
2511
2528
|
__publicField(this, "sliceDeadline", Infinity);
|
|
2529
|
+
/** The clock the drain reads; a budget lends its own while it runs the queue. */
|
|
2530
|
+
__publicField(this, "drainClock", () => performance.now());
|
|
2531
|
+
/** true once a frame budget owns the draining: `update` no longer drains by itself. */
|
|
2532
|
+
__publicField(this, "attached", false);
|
|
2533
|
+
__publicField(this, "eventQueue", {
|
|
2534
|
+
id: DEFERRED_QUEUE_ID.DESTRUCTION_EVENTS,
|
|
2535
|
+
pending: () => this.queue.length + this.slices.length,
|
|
2536
|
+
run: (deadline, clock) => this.runBudgeted(deadline, clock),
|
|
2537
|
+
flush: () => {
|
|
2538
|
+
for (let guard = 0; guard < 16 && this.queue.length + this.slices.length > 0; guard++) this.runBudgeted(Infinity, () => 0);
|
|
2539
|
+
},
|
|
2540
|
+
attach: () => {
|
|
2541
|
+
this.attached = true;
|
|
2542
|
+
},
|
|
2543
|
+
detach: () => {
|
|
2544
|
+
this.attached = false;
|
|
2545
|
+
}
|
|
2546
|
+
});
|
|
2512
2547
|
__publicField(this, "unsubscribe");
|
|
2513
2548
|
__publicField(this, "visuals");
|
|
2514
2549
|
__publicField(this, "fragments");
|
|
@@ -2554,6 +2589,7 @@ var Renderer = class {
|
|
|
2554
2589
|
const maxParticles = Math.max(8, Math.floor((o.maxParticles ?? 2500) * (this.low ? 0.5 : 1)));
|
|
2555
2590
|
this.visuals = new Visuals(THREE, this.low, 600);
|
|
2556
2591
|
this.ground = new GroundSpots(THREE, o.scene, this.groundAt, 48);
|
|
2592
|
+
if (!o.vfx) warnOnce("game-destruction-three.own-effects-world", "game-destruction-three made an effects world of its own because the host lent none (options.vfx); pass the host effects so the effects share one budget");
|
|
2557
2593
|
this.vfx = o.vfx ?? createVfx({ profile: this.quality, gravity: this.gravity, groundAt: this.groundAt, sprites: { capacity: maxParticles, density: this.low ? 0.6 : 1 } });
|
|
2558
2594
|
this.ownVfx = o.vfx ? void 0 : createVfxRenderer(THREE, { scene: o.scene, camera: o.camera ?? new THREE.PerspectiveCamera(), vfx: this.vfx, pointScale: o.pointScale ?? 700 });
|
|
2559
2595
|
this.stopLanding = this.vfx.onLand(this.owner, (x, _y, z, color, tag) => {
|
|
@@ -2791,7 +2827,7 @@ var Renderer = class {
|
|
|
2791
2827
|
if (this.disposed) return;
|
|
2792
2828
|
dt = clamp(dt, 0, 0.1);
|
|
2793
2829
|
this.time += dt;
|
|
2794
|
-
this.drainQueue();
|
|
2830
|
+
if (!this.attached) this.drainQueue();
|
|
2795
2831
|
for (const b of this.bounds.values()) b.onFrame?.(dt);
|
|
2796
2832
|
for (const b of this.bounds.values()) if (b.soilNode && this.world.state(b.id)?.soil?.status === "yielding") this.onSettle(b.id);
|
|
2797
2833
|
this.updateActive(dt);
|
|
@@ -2802,36 +2838,50 @@ var Renderer = class {
|
|
|
2802
2838
|
this.ownVfx?.update(dt);
|
|
2803
2839
|
this.syncTransforms();
|
|
2804
2840
|
}
|
|
2841
|
+
deferredQueue() {
|
|
2842
|
+
return this.eventQueue;
|
|
2843
|
+
}
|
|
2805
2844
|
drainQueue() {
|
|
2845
|
+
this.runBudgeted(performance.now() + this.eventBudgetMs, () => performance.now());
|
|
2846
|
+
}
|
|
2847
|
+
/** Applies the waiting events in order until the deadline, after the slices earlier events left; always at least one unit when something waits. Returns the units run. */
|
|
2848
|
+
runBudgeted(deadline, clock) {
|
|
2806
2849
|
const batch = this.queue.splice(0, this.queue.length);
|
|
2807
|
-
const
|
|
2850
|
+
const outerClock = this.drainClock;
|
|
2851
|
+
this.drainClock = clock;
|
|
2808
2852
|
this.sliceDeadline = deadline;
|
|
2853
|
+
let ran = 0;
|
|
2809
2854
|
try {
|
|
2810
|
-
this.runSlices(deadline);
|
|
2855
|
+
ran = this.runSlices(deadline);
|
|
2811
2856
|
for (let i = 0; i < batch.length; i++) {
|
|
2812
|
-
if (i > 0 &&
|
|
2857
|
+
if ((i > 0 || ran > 0 && this.attached) && clock() >= deadline) {
|
|
2813
2858
|
this.queue.unshift(...batch.slice(i));
|
|
2814
|
-
return;
|
|
2859
|
+
return ran;
|
|
2815
2860
|
}
|
|
2816
2861
|
try {
|
|
2817
2862
|
this.apply(batch[i]);
|
|
2818
2863
|
} catch (err) {
|
|
2819
2864
|
if (typeof console !== "undefined") console.error("game-destruction-three: failed to apply", batch[i].type, err);
|
|
2820
2865
|
}
|
|
2866
|
+
ran++;
|
|
2821
2867
|
}
|
|
2822
2868
|
} finally {
|
|
2823
2869
|
this.sliceDeadline = Infinity;
|
|
2870
|
+
this.drainClock = outerClock;
|
|
2824
2871
|
}
|
|
2872
|
+
return ran;
|
|
2825
2873
|
}
|
|
2826
2874
|
/** Runs the work left by earlier frames until the deadline; at least one piece per frame, so it always finishes. */
|
|
2827
2875
|
runSlices(deadline) {
|
|
2828
|
-
|
|
2876
|
+
let ran = 0;
|
|
2877
|
+
for (; this.slices.length > 0 && (ran === 0 || this.drainClock() < deadline); ran++) {
|
|
2829
2878
|
try {
|
|
2830
2879
|
this.slices.shift()();
|
|
2831
2880
|
} catch (err) {
|
|
2832
2881
|
if (typeof console !== "undefined") console.error("game-destruction-three: failed to run deferred work", err);
|
|
2833
2882
|
}
|
|
2834
2883
|
}
|
|
2884
|
+
return ran;
|
|
2835
2885
|
}
|
|
2836
2886
|
/**
|
|
2837
2887
|
* The ground under an object settled or tilted: its parts are drawn under a node with the soil's offset (`ObjectState.soil.offset`), so the host's
|
|
@@ -3389,7 +3439,7 @@ var Renderer = class {
|
|
|
3389
3439
|
const maxAlive = this.low ? Math.floor(this.maxFragments / 2) : this.maxFragments;
|
|
3390
3440
|
const step = this.low ? 2 : 1;
|
|
3391
3441
|
for (let i = from; i < ev.fragments.length; i += step) {
|
|
3392
|
-
if (i - from >= MIN_FRAGMENTS_PER_SLICE * step &&
|
|
3442
|
+
if (i - from >= MIN_FRAGMENTS_PER_SLICE * step && this.drainClock() >= this.sliceDeadline) {
|
|
3393
3443
|
this.slices.push(() => this.spawnFragments(ev, i, look));
|
|
3394
3444
|
return;
|
|
3395
3445
|
}
|
|
@@ -3413,7 +3463,7 @@ var Renderer = class {
|
|
|
3413
3463
|
if (!st || st.groups.length < 2) return;
|
|
3414
3464
|
const loose = st.groups.length - 1;
|
|
3415
3465
|
for (let i = 0; i < loose; i++) {
|
|
3416
|
-
if (i > 0 &&
|
|
3466
|
+
if (i > 0 && this.drainClock() >= this.sliceDeadline) {
|
|
3417
3467
|
this.slices.push(() => {
|
|
3418
3468
|
this.staticDirty.add(objectId);
|
|
3419
3469
|
this.splitLoose(objectId);
|
|
@@ -3792,6 +3842,7 @@ var Renderer = class {
|
|
|
3792
3842
|
const materials = [kit.discMaterial().clone(), kit.patchMaterial(8421504).clone(), kit.lineMaterial(0).clone()];
|
|
3793
3843
|
const group = new T.Group();
|
|
3794
3844
|
group.add(new T.Mesh(kit.discGeometry(), materials[0]), new T.Mesh(kit.discGeometry(), materials[1]), new T.LineSegments(geometry, materials[2]));
|
|
3845
|
+
group.add(...this.fragments.warmMeshes());
|
|
3795
3846
|
for (const probe of group.children) probe.frustumCulled = false;
|
|
3796
3847
|
this.warmProbes = { group, geometry, materials };
|
|
3797
3848
|
return { group, dispose: () => {
|