babylon.quarks 0.18.0 → 0.18.1

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.
@@ -2645,6 +2645,8 @@ declare class ParticleSystem implements IParticleSystem {
2645
2645
  private materialRef;
2646
2646
  private qualityFactor;
2647
2647
  private useFastTrailHistory;
2648
+ private simulationAccumulator;
2649
+ private destroyed;
2648
2650
  /** @internal **/
2649
2651
  _renderer?: BatchedRenderer;
2650
2652
  set time(time: number);
@@ -2699,6 +2701,8 @@ declare class ParticleSystem implements IParticleSystem {
2699
2701
  private firstTimeUpdate;
2700
2702
  /** @internal */
2701
2703
  update(delta: number): void;
2704
+ /** Advances emission, behaviors, motion and culling by one fixed timestep. */
2705
+ private simulateStep;
2702
2706
  private notifyFinished;
2703
2707
  emit(delta: number, emissionState: EmissionState, emitterMatrix: Matrix4): void;
2704
2708
  toJSON(metaData?: BabylonMetaData | string, _options?: SerializationOptions): ParticleSystemJSONParameters;
@@ -2749,6 +2753,13 @@ declare class QuarksLoader {
2749
2753
  private extractInterleavedAttribute;
2750
2754
  private int32ArrayToFloat32;
2751
2755
  private parseImages;
2756
+ /**
2757
+ * `img.url` is already absolute for data/blob URIs and full http(s) URLs — common for
2758
+ * "portable" exports that embed textures as base64 (see ensurePortableTextureUrl in
2759
+ * babylon.quarks-editor). Prepending baseUrl to those turns e.g.
2760
+ * "data:image/png;base64,AAAA" into an invalid path like "/some/dir/data:image/png;base64,AAAA".
2761
+ */
2762
+ private resolveImageUrl;
2752
2763
  private parseTextures;
2753
2764
  private mapWrapMode;
2754
2765
  private parseMaterials;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "babylon.quarks",
3
- "version": "0.18.0",
3
+ "version": "0.18.1",
4
4
  "description": "A High-Performance Particle System for Babylon.js",
5
5
  "type": "module",
6
6
  "types": "./dist/types/index.d.ts",
@@ -56,6 +56,8 @@ const tempQ = new Quaternion();
56
56
  const tempV = new Vector3();
57
57
  const tempV2 = new Vector3();
58
58
  const PREWARM_FPS = 60;
59
+ const SIMULATION_STEP = 1 / PREWARM_FPS;
60
+ const MAX_SIMULATION_STEPS_PER_FRAME = 8;
59
61
 
60
62
  const DEFAULT_POSITIONS = new Float32Array([
61
63
  -0.5, -0.5, 0,
@@ -217,6 +219,8 @@ export class ParticleSystem implements IParticleSystem {
217
219
  private materialRef: any = null;
218
220
  private qualityFactor = 1;
219
221
  private useFastTrailHistory = true;
222
+ private simulationAccumulator = 0;
223
+ private destroyed = false;
220
224
  /** @internal **/
221
225
  _renderer?: BatchedRenderer;
222
226
 
@@ -784,6 +788,10 @@ export class ParticleSystem implements IParticleSystem {
784
788
  }
785
789
 
786
790
  dispose() {
791
+ if (this.destroyed) {
792
+ return;
793
+ }
794
+ this.destroyed = true;
787
795
  if (this._renderer) this._renderer.deleteSystem(this);
788
796
  this.emitter.dispose();
789
797
  this.fire({type: 'destroy', particleSystem: this});
@@ -809,6 +817,8 @@ export class ParticleSystem implements IParticleSystem {
809
817
  this.startDelayTimeLeft = this.startDelay.genValue(this.memory, 0);
810
818
  this.previousEmitterPos = undefined;
811
819
  this.emitterVelocity.set(0, 0, 0);
820
+ this.simulationAccumulator = 0;
821
+ this.destroyed = false;
812
822
  }
813
823
 
814
824
  private firstTimeUpdate = true;
@@ -862,6 +872,19 @@ export class ParticleSystem implements IParticleSystem {
862
872
  this.startDelayTimeLeft = 0;
863
873
  }
864
874
 
875
+ this.simulationAccumulator += delta;
876
+ const stepsToRun = Math.min(
877
+ MAX_SIMULATION_STEPS_PER_FRAME,
878
+ Math.floor((this.simulationAccumulator + 1e-9) / SIMULATION_STEP)
879
+ );
880
+ for (let i = 0; i < stepsToRun; i++) {
881
+ this.simulateStep(SIMULATION_STEP);
882
+ }
883
+ this.simulationAccumulator -= stepsToRun * SIMULATION_STEP;
884
+ }
885
+
886
+ /** Advances emission, behaviors, motion and culling by one fixed timestep. */
887
+ private simulateStep(delta: number): void {
865
888
  if (!this.onlyUsedByOther) {
866
889
  this.emit(delta, this.emissionState, this.emitter.matrixWorld);
867
890
  }
@@ -961,12 +984,6 @@ export class ParticleSystem implements IParticleSystem {
961
984
  const emissionBurstCount = emissionBursts.length;
962
985
  const qualityFactor = this.qualityFactor;
963
986
 
964
- const totalSpawn = Math.floor(emissionState.waitEmiting);
965
- if (totalSpawn > 0) {
966
- this.spawn(totalSpawn, emissionState, emitterMatrix);
967
- emissionState.waitEmiting -= totalSpawn;
968
- }
969
-
970
987
  while (
971
988
  emissionState.burstIndex < emissionBurstCount &&
972
989
  emissionBursts[emissionState.burstIndex].time <= emissionState.time
@@ -999,6 +1016,15 @@ export class ParticleSystem implements IParticleSystem {
999
1016
  }
1000
1017
  }
1001
1018
  }
1019
+
1020
+ // Accrue before spawning so replacement particles can appear on the same
1021
+ // frame an old one dies (rate * lifetime ≈ 1 would otherwise flicker).
1022
+ const totalSpawn = Math.floor(emissionState.waitEmiting);
1023
+ if (totalSpawn > 0) {
1024
+ this.spawn(totalSpawn, emissionState, emitterMatrix);
1025
+ emissionState.waitEmiting -= totalSpawn;
1026
+ }
1027
+
1002
1028
  if (emissionState.previousWorldPos === undefined) emissionState.previousWorldPos = new Vector3();
1003
1029
  emissionState.previousWorldPos.set(emitterMatrix.elements[12], emitterMatrix.elements[13], emitterMatrix.elements[14]);
1004
1030
  emissionState.time += delta;
@@ -255,10 +255,23 @@ export class QuarksLoader {
255
255
  private parseImages(images: any[], baseUrl: string, _meta: LoadedMeta): void {
256
256
  (this as any)._images = {};
257
257
  for (const img of images) {
258
- (this as any)._images[img.uuid] = img.url ? baseUrl + img.url : null;
258
+ (this as any)._images[img.uuid] = img.url ? this.resolveImageUrl(baseUrl, img.url) : null;
259
259
  }
260
260
  }
261
261
 
262
+ /**
263
+ * `img.url` is already absolute for data/blob URIs and full http(s) URLs — common for
264
+ * "portable" exports that embed textures as base64 (see ensurePortableTextureUrl in
265
+ * babylon.quarks-editor). Prepending baseUrl to those turns e.g.
266
+ * "data:image/png;base64,AAAA" into an invalid path like "/some/dir/data:image/png;base64,AAAA".
267
+ */
268
+ private resolveImageUrl(baseUrl: string, url: string): string {
269
+ if (/^(data|blob):/i.test(url) || /^[a-z][a-z0-9+.-]*:\/\//i.test(url) || url.startsWith('/')) {
270
+ return url;
271
+ }
272
+ return baseUrl + url;
273
+ }
274
+
262
275
  private parseTextures(textures: any[], meta: LoadedMeta): void {
263
276
  const images = (this as any)._images || {};
264
277
  for (const texDef of textures) {