babylon.quarks 0.17.9 → 0.18.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/babylon.quarks.cjs +1 -1
- package/dist/babylon.quarks.cjs.map +1 -1
- package/dist/babylon.quarks.esm.js +1 -1
- package/dist/babylon.quarks.esm.js.map +1 -1
- package/dist/babylon.quarks.umd.min.js +1 -1
- package/dist/babylon.quarks.umd.min.js.map +1 -1
- package/dist/types/index.d.ts +28 -1
- package/package.json +1 -1
- package/src/BatchedRenderer.ts +34 -3
- package/src/ParticleSystem.ts +64 -2
- package/src/QuarksUtil.ts +18 -0
- package/src/SpriteBatch.ts +32 -9
package/dist/types/index.d.ts
CHANGED
|
@@ -823,7 +823,7 @@ interface IEmitter {
|
|
|
823
823
|
visible: boolean;
|
|
824
824
|
matrixWorld: any;
|
|
825
825
|
}
|
|
826
|
-
type ParticleSystemEventType = "emitEnd" | "destroy" | "particleDied";
|
|
826
|
+
type ParticleSystemEventType = "emitEnd" | "finished" | "destroy" | "particleDied";
|
|
827
827
|
interface ParticleSystemEvent {
|
|
828
828
|
type: ParticleSystemEventType;
|
|
829
829
|
particleSystem: IParticleSystem;
|
|
@@ -877,6 +877,12 @@ interface IParticleSystem {
|
|
|
877
877
|
rendererEmitterSettings: RendererEmitterSettings;
|
|
878
878
|
emissionState: EmissionState;
|
|
879
879
|
paused: boolean;
|
|
880
|
+
/** True after `endEmit()` — emission window closed (non-looping duration elapsed). */
|
|
881
|
+
readonly isEmitEnded: boolean;
|
|
882
|
+
/** Whether more particles can still spawn (bursts, rate, distance). */
|
|
883
|
+
hasPendingEmission(): boolean;
|
|
884
|
+
/** Non-looping system with no live particles and no pending emission. */
|
|
885
|
+
isFinished(): boolean;
|
|
880
886
|
pause(): void;
|
|
881
887
|
stop(): void;
|
|
882
888
|
play(): void;
|
|
@@ -2382,6 +2388,16 @@ declare class BatchedRenderer extends TransformNode {
|
|
|
2382
2388
|
getAdaptivePerformanceState(): AdaptivePerformanceState;
|
|
2383
2389
|
private applyAdaptiveQuality;
|
|
2384
2390
|
update(delta: number): void;
|
|
2391
|
+
/** Rebuild instance buffers from current particle state without advancing simulation. */
|
|
2392
|
+
refreshBatches(): void;
|
|
2393
|
+
/** Per-batch particle vs GPU instance counts — for diagnosing stale render state. */
|
|
2394
|
+
getBatchRenderStats(): Array<{
|
|
2395
|
+
batchIndex: number;
|
|
2396
|
+
renderMode: RenderMode;
|
|
2397
|
+
instanceCount: number;
|
|
2398
|
+
particleCount: number;
|
|
2399
|
+
systemNames: string[];
|
|
2400
|
+
}>;
|
|
2385
2401
|
dispose(): void;
|
|
2386
2402
|
}
|
|
2387
2403
|
|
|
@@ -2415,6 +2431,8 @@ declare class SpriteBatch extends VFXBatch {
|
|
|
2415
2431
|
private rotationMat2_;
|
|
2416
2432
|
private lastStretchedSpeedFactor;
|
|
2417
2433
|
update(): void;
|
|
2434
|
+
/** Pushes a zero-alpha instance so stale instancing data cannot linger on screen. */
|
|
2435
|
+
private uploadClearedInstance;
|
|
2418
2436
|
dispose(): void;
|
|
2419
2437
|
}
|
|
2420
2438
|
|
|
@@ -2615,6 +2633,7 @@ declare class ParticleSystem implements IParticleSystem {
|
|
|
2615
2633
|
private prewarmed;
|
|
2616
2634
|
private emitEnded;
|
|
2617
2635
|
private markForDestroy;
|
|
2636
|
+
private finishedEventFired;
|
|
2618
2637
|
private startDelayTimeLeft;
|
|
2619
2638
|
private previousEmitterPos?;
|
|
2620
2639
|
private tempEmitterPos;
|
|
@@ -2630,6 +2649,11 @@ declare class ParticleSystem implements IParticleSystem {
|
|
|
2630
2649
|
_renderer?: BatchedRenderer;
|
|
2631
2650
|
set time(time: number);
|
|
2632
2651
|
get time(): number;
|
|
2652
|
+
get isEmitEnded(): boolean;
|
|
2653
|
+
/** Whether continuous or queued emission can still spawn particles before the system is done. */
|
|
2654
|
+
hasPendingEmission(): boolean;
|
|
2655
|
+
/** Non-looping system with no particles left and nothing left to emit. */
|
|
2656
|
+
isFinished(): boolean;
|
|
2633
2657
|
get layers(): {
|
|
2634
2658
|
mask: number;
|
|
2635
2659
|
};
|
|
@@ -2675,6 +2699,7 @@ declare class ParticleSystem implements IParticleSystem {
|
|
|
2675
2699
|
private firstTimeUpdate;
|
|
2676
2700
|
/** @internal */
|
|
2677
2701
|
update(delta: number): void;
|
|
2702
|
+
private notifyFinished;
|
|
2678
2703
|
emit(delta: number, emissionState: EmissionState, emitterMatrix: Matrix4): void;
|
|
2679
2704
|
toJSON(metaData?: BabylonMetaData | string, _options?: SerializationOptions): ParticleSystemJSONParameters;
|
|
2680
2705
|
static fromJSON(json: ParticleSystemJSONParameters, meta: BabylonMetaData, dependencies?: {
|
|
@@ -2704,6 +2729,8 @@ declare class QuarksUtil {
|
|
|
2704
2729
|
static restart(root: Node): void;
|
|
2705
2730
|
static setAutoDestroy(root: Node, autoDestroy: boolean): void;
|
|
2706
2731
|
static endEmit(root: Node): void;
|
|
2732
|
+
/** True when every emitter under `root` is a finished non-looping system (or looping). */
|
|
2733
|
+
static isEffectFinished(root: Node): boolean;
|
|
2707
2734
|
private static traverseNode;
|
|
2708
2735
|
}
|
|
2709
2736
|
|
package/package.json
CHANGED
package/src/BatchedRenderer.ts
CHANGED
|
@@ -194,9 +194,7 @@ export class BatchedRenderer extends TransformNode {
|
|
|
194
194
|
for (const ps of this.systemToBatchIndex.keys()) {
|
|
195
195
|
(ps as ParticleSystem).update(delta);
|
|
196
196
|
}
|
|
197
|
-
|
|
198
|
-
this.batches[i].update();
|
|
199
|
-
}
|
|
197
|
+
this.refreshBatches();
|
|
200
198
|
if (!adaptiveState.enabled) {
|
|
201
199
|
return;
|
|
202
200
|
}
|
|
@@ -217,6 +215,39 @@ export class BatchedRenderer extends TransformNode {
|
|
|
217
215
|
}
|
|
218
216
|
}
|
|
219
217
|
|
|
218
|
+
/** Rebuild instance buffers from current particle state without advancing simulation. */
|
|
219
|
+
refreshBatches(): void {
|
|
220
|
+
for (let i = 0; i < this.batches.length; i++) {
|
|
221
|
+
this.batches[i].update();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Per-batch particle vs GPU instance counts — for diagnosing stale render state. */
|
|
226
|
+
getBatchRenderStats(): Array<{
|
|
227
|
+
batchIndex: number;
|
|
228
|
+
renderMode: RenderMode;
|
|
229
|
+
instanceCount: number;
|
|
230
|
+
particleCount: number;
|
|
231
|
+
systemNames: string[];
|
|
232
|
+
}> {
|
|
233
|
+
return this.batches.map((batch, batchIndex) => {
|
|
234
|
+
let particleCount = 0;
|
|
235
|
+
const systemNames: string[] = [];
|
|
236
|
+
for (const system of batch.systems) {
|
|
237
|
+
const ps = system as ParticleSystem;
|
|
238
|
+
particleCount += ps.particleNum;
|
|
239
|
+
systemNames.push(ps.emitter.name);
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
batchIndex,
|
|
243
|
+
renderMode: batch.settings.renderMode,
|
|
244
|
+
instanceCount: batch.mesh.forcedInstanceCount ?? 0,
|
|
245
|
+
particleCount,
|
|
246
|
+
systemNames,
|
|
247
|
+
};
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
220
251
|
dispose(): void {
|
|
221
252
|
for (const batch of this.batches) {
|
|
222
253
|
batch.dispose();
|
package/src/ParticleSystem.ts
CHANGED
|
@@ -205,6 +205,7 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
205
205
|
private prewarmed: boolean;
|
|
206
206
|
private emitEnded: boolean;
|
|
207
207
|
private markForDestroy: boolean;
|
|
208
|
+
private finishedEventFired = false;
|
|
208
209
|
private startDelayTimeLeft = 0;
|
|
209
210
|
private previousEmitterPos?: Vector3;
|
|
210
211
|
private tempEmitterPos: Vector3 = new Vector3();
|
|
@@ -227,6 +228,52 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
227
228
|
return this.emissionState.time;
|
|
228
229
|
}
|
|
229
230
|
|
|
231
|
+
get isEmitEnded(): boolean {
|
|
232
|
+
return this.emitEnded;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Whether continuous or queued emission can still spawn particles before the system is done. */
|
|
236
|
+
hasPendingEmission(): boolean {
|
|
237
|
+
if (this.looping) {
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
if (this.emitEnded) {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
if (this.onlyUsedByOther) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
const state = this.emissionState;
|
|
247
|
+
if (state.waitEmiting > 0) {
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
if (state.burstIndex < this.emissionBursts.length) {
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
if (state.time >= this.duration) {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
const timeRatio = state.time / this.duration;
|
|
257
|
+
if (this.emissionOverTime.genValue(this.memory, timeRatio) > 0) {
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
return this.emissionOverDistance.genValue(this.memory, timeRatio) > 0;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** Non-looping system with no particles left and nothing left to emit. */
|
|
264
|
+
isFinished(): boolean {
|
|
265
|
+
if (this.looping) {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
if (this.particleNum > 0) {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
if (this.onlyUsedByOther) {
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
return !this.hasPendingEmission();
|
|
275
|
+
}
|
|
276
|
+
|
|
230
277
|
get layers() {
|
|
231
278
|
return this.layerMaskProxy;
|
|
232
279
|
}
|
|
@@ -754,6 +801,7 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
754
801
|
this.behaviors.forEach((behavior) => behavior.reset());
|
|
755
802
|
this.emitEnded = false;
|
|
756
803
|
this.markForDestroy = false;
|
|
804
|
+
this.finishedEventFired = false;
|
|
757
805
|
this.prewarmed = false;
|
|
758
806
|
this.emissionBursts.forEach((burst) => burst.count.startGen(this.memory));
|
|
759
807
|
this.emissionOverDistance.startGen(this.memory);
|
|
@@ -774,8 +822,7 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
774
822
|
this.emitter.computeWorldMatrix(true);
|
|
775
823
|
}
|
|
776
824
|
|
|
777
|
-
if (this.
|
|
778
|
-
if (this.markForDestroy) this.dispose();
|
|
825
|
+
if (!this.looping && this.finishedEventFired && this.particleNum === 0) {
|
|
779
826
|
return;
|
|
780
827
|
}
|
|
781
828
|
|
|
@@ -879,6 +926,21 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
879
926
|
}
|
|
880
927
|
}
|
|
881
928
|
this.particleNum = liveParticleCount;
|
|
929
|
+
this.notifyFinished();
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
private notifyFinished(): void {
|
|
933
|
+
if (!this.isFinished()) {
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
if (!this.finishedEventFired) {
|
|
937
|
+
this.finishedEventFired = true;
|
|
938
|
+
this.fire({type: 'finished', particleSystem: this});
|
|
939
|
+
}
|
|
940
|
+
if (this.autoDestroy) {
|
|
941
|
+
this.markForDestroy = true;
|
|
942
|
+
this.dispose();
|
|
943
|
+
}
|
|
882
944
|
}
|
|
883
945
|
|
|
884
946
|
emit(delta: number, emissionState: EmissionState, emitterMatrix: Matrix4) {
|
package/src/QuarksUtil.ts
CHANGED
|
@@ -56,6 +56,24 @@ export class QuarksUtil {
|
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
/** True when every emitter under `root` is a finished non-looping system (or looping). */
|
|
60
|
+
static isEffectFinished(root: Node): boolean {
|
|
61
|
+
let found = false;
|
|
62
|
+
let finished = true;
|
|
63
|
+
QuarksUtil.runOnAllParticleEmitters(root, (emitter) => {
|
|
64
|
+
found = true;
|
|
65
|
+
const system = emitter.system as unknown as ParticleSystem;
|
|
66
|
+
if (system.looping) {
|
|
67
|
+
finished = false;
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (!system.isFinished()) {
|
|
71
|
+
finished = false;
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
return found && finished;
|
|
75
|
+
}
|
|
76
|
+
|
|
59
77
|
private static traverseNode(node: Node, callback: (node: Node) => void): void {
|
|
60
78
|
callback(node);
|
|
61
79
|
const children = node.getChildren();
|
package/src/SpriteBatch.ts
CHANGED
|
@@ -383,15 +383,38 @@ export class SpriteBatch extends VFXBatch {
|
|
|
383
383
|
// single frame (common with short lifetimes + low emission overlap) causes flicker.
|
|
384
384
|
this.mesh.forcedInstanceCount = index;
|
|
385
385
|
|
|
386
|
-
if (index
|
|
387
|
-
this.
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
386
|
+
if (index === 0) {
|
|
387
|
+
this.uploadClearedInstance(isMeshRender, isStretchedRender);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
this.offsetVB.update(this.offsetBuffer);
|
|
392
|
+
this.sizeVB.update(this.sizeBuffer);
|
|
393
|
+
this.colorVB.update(this.colorBuffer);
|
|
394
|
+
this.uvTileVB.update(this.uvTileBuffer);
|
|
395
|
+
this.rotationVB.update(this.rotationBuffer);
|
|
396
|
+
if (isStretchedRender && this.velocityVB && this.velocityBuffer) {
|
|
397
|
+
this.velocityVB.update(this.velocityBuffer);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/** Pushes a zero-alpha instance so stale instancing data cannot linger on screen. */
|
|
402
|
+
private uploadClearedInstance(isMeshRender: boolean, isStretchedRender: boolean): void {
|
|
403
|
+
this.colorBuffer[3] = 0;
|
|
404
|
+
this.sizeBuffer[0] = 0;
|
|
405
|
+
this.sizeBuffer[1] = 0;
|
|
406
|
+
this.sizeBuffer[2] = 0;
|
|
407
|
+
this.colorVB.update(this.colorBuffer.subarray(0, 4));
|
|
408
|
+
this.sizeVB.update(this.sizeBuffer.subarray(0, 3));
|
|
409
|
+
this.offsetVB.update(this.offsetBuffer.subarray(0, 3));
|
|
410
|
+
this.uvTileVB.update(this.uvTileBuffer.subarray(0, 1));
|
|
411
|
+
if (isMeshRender) {
|
|
412
|
+
this.rotationVB.update(this.rotationBuffer.subarray(0, 4));
|
|
413
|
+
} else {
|
|
414
|
+
this.rotationVB.update(this.rotationBuffer.subarray(0, 1));
|
|
415
|
+
}
|
|
416
|
+
if (isStretchedRender && this.velocityVB && this.velocityBuffer) {
|
|
417
|
+
this.velocityVB.update(this.velocityBuffer.subarray(0, 4));
|
|
395
418
|
}
|
|
396
419
|
}
|
|
397
420
|
|