@newkrok/three-particles 2.3.0 → 2.6.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/bundle-report.json +1 -1
- package/dist/js/effects/three-particles/three-particles-modifiers.js +1 -2
- package/dist/js/effects/three-particles/three-particles.d.ts +1 -73
- package/dist/js/effects/three-particles/three-particles.d.ts.map +1 -1
- package/dist/js/effects/three-particles/three-particles.js +308 -173
- package/dist/js/effects/three-particles/types.d.ts +67 -0
- package/dist/js/effects/three-particles/types.d.ts.map +1 -1
- package/dist/three-particles.min.js +1 -1
- package/llms-full.txt +523 -0
- package/llms.txt +121 -0
- package/package.json +33 -23
|
@@ -10,6 +10,18 @@ import { calculateRandomPositionAndVelocityOnBox, calculateRandomPositionAndVelo
|
|
|
10
10
|
export * from './types.js';
|
|
11
11
|
let _particleSystemId = 0;
|
|
12
12
|
let createdParticleSystems = [];
|
|
13
|
+
// Pre-allocated objects for updateParticleSystemInstance to avoid GC pressure
|
|
14
|
+
const _lastWorldPositionSnapshot = new THREE.Vector3();
|
|
15
|
+
const _distanceStep = { x: 0, y: 0, z: 0 };
|
|
16
|
+
const _tempPosition = { x: 0, y: 0, z: 0 };
|
|
17
|
+
const _modifierParams = {
|
|
18
|
+
delta: 0,
|
|
19
|
+
generalData: null,
|
|
20
|
+
normalizedConfig: null,
|
|
21
|
+
attributes: null,
|
|
22
|
+
particleLifetimePercentage: 0,
|
|
23
|
+
particleIndex: 0,
|
|
24
|
+
};
|
|
13
25
|
/**
|
|
14
26
|
* Mapping of blending mode string identifiers to Three.js blending constants.
|
|
15
27
|
*
|
|
@@ -78,6 +90,7 @@ const DEFAULT_PARTICLE_SYSTEM_CONFIG = {
|
|
|
78
90
|
emission: {
|
|
79
91
|
rateOverTime: 10.0,
|
|
80
92
|
rateOverDistance: 0.0,
|
|
93
|
+
bursts: [],
|
|
81
94
|
},
|
|
82
95
|
shape: {
|
|
83
96
|
shape: "SPHERE" /* Shape.SPHERE */,
|
|
@@ -201,7 +214,16 @@ const DEFAULT_PARTICLE_SYSTEM_CONFIG = {
|
|
|
201
214
|
},
|
|
202
215
|
};
|
|
203
216
|
const createFloat32Attributes = ({ geometry, propertyName, maxParticles, factory, }) => {
|
|
204
|
-
|
|
217
|
+
const array = new Float32Array(maxParticles);
|
|
218
|
+
if (typeof factory === 'function') {
|
|
219
|
+
for (let i = 0; i < maxParticles; i++) {
|
|
220
|
+
array[i] = factory(undefined, i);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
array.fill(factory);
|
|
225
|
+
}
|
|
226
|
+
geometry.setAttribute(propertyName, new THREE.BufferAttribute(array, 1));
|
|
205
227
|
};
|
|
206
228
|
const calculatePositionAndVelocity = (generalData, { shape, sphere, cone, circle, rectangle, box }, startSpeed, position, velocity) => {
|
|
207
229
|
const calculatedStartSpeed = calculateValue(generalData.particleSystemId, startSpeed, generalData.normalizedLifetimePercentage);
|
|
@@ -320,6 +342,7 @@ export const createParticleSystem = (config = DEFAULT_PARTICLE_SYSTEM_CONFIG, ex
|
|
|
320
342
|
noise: {
|
|
321
343
|
isActive: false,
|
|
322
344
|
strength: 0,
|
|
345
|
+
noisePower: 0,
|
|
323
346
|
positionAmount: 0,
|
|
324
347
|
rotationAmount: 0,
|
|
325
348
|
sizeAmount: 0,
|
|
@@ -334,6 +357,8 @@ export const createParticleSystem = (config = DEFAULT_PARTICLE_SYSTEM_CONFIG, ex
|
|
|
334
357
|
const startPositions = Array.from({ length: maxParticles }, () => new THREE.Vector3());
|
|
335
358
|
const velocities = Array.from({ length: maxParticles }, () => new THREE.Vector3());
|
|
336
359
|
generalData.creationTimes = Array.from({ length: maxParticles }, () => 0);
|
|
360
|
+
// Free list for O(1) inactive particle lookup (stack, top = end of array)
|
|
361
|
+
const freeList = Array.from({ length: maxParticles }, (_, i) => maxParticles - 1 - i);
|
|
337
362
|
if (velocityOverLifetime.isActive) {
|
|
338
363
|
generalData.linearVelocityData = Array.from({ length: maxParticles }, () => ({
|
|
339
364
|
speed: new THREE.Vector3(velocityOverLifetime.linear.x
|
|
@@ -398,6 +423,7 @@ export const createParticleSystem = (config = DEFAULT_PARTICLE_SYSTEM_CONFIG, ex
|
|
|
398
423
|
generalData.noise = {
|
|
399
424
|
isActive: noise.isActive,
|
|
400
425
|
strength: noise.strength,
|
|
426
|
+
noisePower: 0.15 * noise.strength,
|
|
401
427
|
positionAmount: noise.positionAmount,
|
|
402
428
|
rotationAmount: noise.rotationAmount,
|
|
403
429
|
sizeAmount: noise.sizeAmount,
|
|
@@ -412,6 +438,14 @@ export const createParticleSystem = (config = DEFAULT_PARTICLE_SYSTEM_CONFIG, ex
|
|
|
412
438
|
? Array.from({ length: maxParticles }, () => Math.random() * 100)
|
|
413
439
|
: undefined,
|
|
414
440
|
};
|
|
441
|
+
// Initialize burst states if bursts are configured
|
|
442
|
+
if (emission.bursts && emission.bursts.length > 0) {
|
|
443
|
+
generalData.burstStates = emission.bursts.map(() => ({
|
|
444
|
+
cyclesExecuted: 0,
|
|
445
|
+
lastCycleTime: 0,
|
|
446
|
+
probabilityPassed: false,
|
|
447
|
+
}));
|
|
448
|
+
}
|
|
415
449
|
const material = new THREE.ShaderMaterial({
|
|
416
450
|
uniforms: {
|
|
417
451
|
elapsed: {
|
|
@@ -449,37 +483,96 @@ export const createParticleSystem = (config = DEFAULT_PARTICLE_SYSTEM_CONFIG, ex
|
|
|
449
483
|
const geometry = new THREE.BufferGeometry();
|
|
450
484
|
for (let i = 0; i < maxParticles; i++)
|
|
451
485
|
calculatePositionAndVelocity(generalData, shape, startSpeed, startPositions[i], velocities[i]);
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
486
|
+
const positionArray = new Float32Array(maxParticles * 3);
|
|
487
|
+
for (let i = 0; i < maxParticles; i++) {
|
|
488
|
+
positionArray[i * 3] = startPositions[i].x;
|
|
489
|
+
positionArray[i * 3 + 1] = startPositions[i].y;
|
|
490
|
+
positionArray[i * 3 + 2] = startPositions[i].z;
|
|
491
|
+
}
|
|
492
|
+
geometry.setAttribute('position', new THREE.BufferAttribute(positionArray, 3));
|
|
493
|
+
createFloat32Attributes({
|
|
494
|
+
geometry,
|
|
495
|
+
propertyName: 'isActive',
|
|
496
|
+
maxParticles,
|
|
497
|
+
factory: 0,
|
|
498
|
+
});
|
|
499
|
+
createFloat32Attributes({
|
|
500
|
+
geometry,
|
|
501
|
+
propertyName: 'lifetime',
|
|
502
|
+
maxParticles,
|
|
503
|
+
factory: 0,
|
|
504
|
+
});
|
|
505
|
+
createFloat32Attributes({
|
|
506
|
+
geometry,
|
|
507
|
+
propertyName: 'startLifetime',
|
|
508
|
+
maxParticles,
|
|
509
|
+
factory: () => calculateValue(generalData.particleSystemId, startLifetime, 0) * 1000,
|
|
510
|
+
});
|
|
511
|
+
createFloat32Attributes({
|
|
512
|
+
geometry,
|
|
513
|
+
propertyName: 'startFrame',
|
|
514
|
+
maxParticles,
|
|
515
|
+
factory: () => textureSheetAnimation.startFrame
|
|
516
|
+
? calculateValue(generalData.particleSystemId, textureSheetAnimation.startFrame, 0)
|
|
517
|
+
: 0,
|
|
518
|
+
});
|
|
519
|
+
createFloat32Attributes({
|
|
520
|
+
geometry,
|
|
521
|
+
propertyName: 'opacity',
|
|
522
|
+
maxParticles,
|
|
523
|
+
factory: () => calculateValue(generalData.particleSystemId, startOpacity, 0),
|
|
524
|
+
});
|
|
525
|
+
createFloat32Attributes({
|
|
526
|
+
geometry,
|
|
527
|
+
propertyName: 'rotation',
|
|
528
|
+
maxParticles,
|
|
529
|
+
factory: () => calculateValue(generalData.particleSystemId, startRotation, 0),
|
|
530
|
+
});
|
|
531
|
+
createFloat32Attributes({
|
|
532
|
+
geometry,
|
|
533
|
+
propertyName: 'size',
|
|
534
|
+
maxParticles,
|
|
535
|
+
factory: (_, index) => generalData.startValues.startSize[index],
|
|
536
|
+
});
|
|
537
|
+
createFloat32Attributes({
|
|
538
|
+
geometry,
|
|
539
|
+
propertyName: 'rotation',
|
|
540
|
+
maxParticles,
|
|
541
|
+
factory: 0,
|
|
542
|
+
});
|
|
471
543
|
const colorRandomRatio = Math.random();
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
544
|
+
createFloat32Attributes({
|
|
545
|
+
geometry,
|
|
546
|
+
propertyName: 'colorR',
|
|
547
|
+
maxParticles,
|
|
548
|
+
factory: () => startColor.min.r +
|
|
549
|
+
colorRandomRatio * (startColor.max.r - startColor.min.r),
|
|
550
|
+
});
|
|
551
|
+
createFloat32Attributes({
|
|
552
|
+
geometry,
|
|
553
|
+
propertyName: 'colorG',
|
|
554
|
+
maxParticles,
|
|
555
|
+
factory: () => startColor.min.g +
|
|
556
|
+
colorRandomRatio * (startColor.max.g - startColor.min.g),
|
|
557
|
+
});
|
|
558
|
+
createFloat32Attributes({
|
|
559
|
+
geometry,
|
|
560
|
+
propertyName: 'colorB',
|
|
561
|
+
maxParticles,
|
|
562
|
+
factory: () => startColor.min.b +
|
|
563
|
+
colorRandomRatio * (startColor.max.b - startColor.min.b),
|
|
564
|
+
});
|
|
565
|
+
createFloat32Attributes({
|
|
566
|
+
geometry,
|
|
567
|
+
propertyName: 'colorA',
|
|
568
|
+
maxParticles,
|
|
569
|
+
factory: 0,
|
|
570
|
+
});
|
|
479
571
|
const deactivateParticle = (particleIndex) => {
|
|
480
572
|
geometry.attributes.isActive.array[particleIndex] = 0;
|
|
481
573
|
geometry.attributes.colorA.array[particleIndex] = 0;
|
|
482
574
|
geometry.attributes.colorA.needsUpdate = true;
|
|
575
|
+
freeList.push(particleIndex);
|
|
483
576
|
};
|
|
484
577
|
const activateParticle = ({ particleIndex, activationTime, position, }) => {
|
|
485
578
|
geometry.attributes.isActive.array[particleIndex] = 1;
|
|
@@ -577,9 +670,10 @@ export const createParticleSystem = (config = DEFAULT_PARTICLE_SYSTEM_CONFIG, ex
|
|
|
577
670
|
wrapper = new Gyroscope();
|
|
578
671
|
wrapper.add(particleSystem);
|
|
579
672
|
}
|
|
580
|
-
|
|
673
|
+
const instanceData = {
|
|
581
674
|
particleSystem,
|
|
582
675
|
wrapper,
|
|
676
|
+
elapsedUniform: material.uniforms.elapsed,
|
|
583
677
|
generalData,
|
|
584
678
|
onUpdate,
|
|
585
679
|
onComplete,
|
|
@@ -593,17 +687,21 @@ export const createParticleSystem = (config = DEFAULT_PARTICLE_SYSTEM_CONFIG, ex
|
|
|
593
687
|
normalizedConfig,
|
|
594
688
|
iterationCount: 0,
|
|
595
689
|
velocities,
|
|
690
|
+
freeList,
|
|
596
691
|
deactivateParticle,
|
|
597
692
|
activateParticle,
|
|
598
|
-
}
|
|
693
|
+
};
|
|
694
|
+
createdParticleSystems.push(instanceData);
|
|
599
695
|
const resumeEmitter = () => (generalData.isEnabled = true);
|
|
600
696
|
const pauseEmitter = () => (generalData.isEnabled = false);
|
|
601
697
|
const dispose = () => destroyParticleSystem(particleSystem);
|
|
698
|
+
const update = (cycleData) => updateParticleSystemInstance(instanceData, cycleData);
|
|
602
699
|
return {
|
|
603
700
|
instance: wrapper || particleSystem,
|
|
604
701
|
resumeEmitter,
|
|
605
702
|
pauseEmitter,
|
|
606
703
|
dispose,
|
|
704
|
+
update,
|
|
607
705
|
};
|
|
608
706
|
};
|
|
609
707
|
/**
|
|
@@ -678,160 +776,197 @@ export const createParticleSystem = (config = DEFAULT_PARTICLE_SYSTEM_CONFIG, ex
|
|
|
678
776
|
* @see {@link createParticleSystem} - Creates particle systems to be updated
|
|
679
777
|
* @see {@link CycleData} - Timing data structure
|
|
680
778
|
*/
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
else {
|
|
697
|
-
if (particleSystem.material instanceof THREE.ShaderMaterial)
|
|
698
|
-
particleSystem.material.uniforms.elapsed.value = elapsed;
|
|
699
|
-
}
|
|
700
|
-
particleSystem.getWorldPosition(currentWorldPosition);
|
|
701
|
-
if (lastWorldPosition.x !== -99999) {
|
|
702
|
-
worldPositionChange.set(currentWorldPosition.x - lastWorldPosition.x, currentWorldPosition.y - lastWorldPosition.y, currentWorldPosition.z - lastWorldPosition.z);
|
|
703
|
-
}
|
|
779
|
+
const updateParticleSystemInstance = (props, { now, delta, elapsed }) => {
|
|
780
|
+
const { onUpdate, generalData, onComplete, particleSystem, wrapper, elapsedUniform, creationTime, lastEmissionTime, duration, looping, emission, normalizedConfig, iterationCount, velocities, freeList, deactivateParticle, activateParticle, simulationSpace, gravity, } = props;
|
|
781
|
+
const lifetime = now - creationTime;
|
|
782
|
+
const normalizedLifetime = lifetime % (duration * 1000);
|
|
783
|
+
generalData.normalizedLifetimePercentage = Math.max(Math.min(normalizedLifetime / (duration * 1000), 1), 0);
|
|
784
|
+
const { lastWorldPosition, currentWorldPosition, worldPositionChange, lastWorldQuaternion, worldQuaternion, worldEuler, gravityVelocity, isEnabled, } = generalData;
|
|
785
|
+
if (wrapper?.parent)
|
|
786
|
+
generalData.wrapperQuaternion.copy(wrapper.parent.quaternion);
|
|
787
|
+
_lastWorldPositionSnapshot.copy(lastWorldPosition);
|
|
788
|
+
elapsedUniform.value = elapsed;
|
|
789
|
+
particleSystem.getWorldPosition(currentWorldPosition);
|
|
790
|
+
if (lastWorldPosition.x !== -99999) {
|
|
791
|
+
worldPositionChange.set(currentWorldPosition.x - lastWorldPosition.x, currentWorldPosition.y - lastWorldPosition.y, currentWorldPosition.z - lastWorldPosition.z);
|
|
792
|
+
}
|
|
793
|
+
if (isEnabled) {
|
|
704
794
|
generalData.distanceFromLastEmitByDistance += worldPositionChange.length();
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
795
|
+
}
|
|
796
|
+
particleSystem.getWorldPosition(lastWorldPosition);
|
|
797
|
+
particleSystem.getWorldQuaternion(worldQuaternion);
|
|
798
|
+
if (lastWorldQuaternion.x === -99999 ||
|
|
799
|
+
lastWorldQuaternion.x !== worldQuaternion.x ||
|
|
800
|
+
lastWorldQuaternion.y !== worldQuaternion.y ||
|
|
801
|
+
lastWorldQuaternion.z !== worldQuaternion.z) {
|
|
802
|
+
worldEuler.setFromQuaternion(worldQuaternion);
|
|
803
|
+
lastWorldQuaternion.copy(worldQuaternion);
|
|
804
|
+
gravityVelocity.set(lastWorldPosition.x, lastWorldPosition.y + gravity, lastWorldPosition.z);
|
|
805
|
+
particleSystem.worldToLocal(gravityVelocity);
|
|
806
|
+
}
|
|
807
|
+
const creationTimes = generalData.creationTimes;
|
|
808
|
+
const attributes = particleSystem.geometry.attributes;
|
|
809
|
+
const isActiveArr = attributes.isActive.array;
|
|
810
|
+
const startLifetimeArr = attributes.startLifetime.array;
|
|
811
|
+
const positionArr = attributes.position.array;
|
|
812
|
+
const lifetimeArr = attributes.lifetime.array;
|
|
813
|
+
const creationTimesLength = creationTimes.length;
|
|
814
|
+
let positionNeedsUpdate = false;
|
|
815
|
+
let lifetimeNeedsUpdate = false;
|
|
816
|
+
_modifierParams.delta = delta;
|
|
817
|
+
_modifierParams.generalData = generalData;
|
|
818
|
+
_modifierParams.normalizedConfig = normalizedConfig;
|
|
819
|
+
_modifierParams.attributes = attributes;
|
|
820
|
+
for (let index = 0; index < creationTimesLength; index++) {
|
|
821
|
+
if (isActiveArr[index]) {
|
|
822
|
+
const particleLifetime = now - creationTimes[index];
|
|
823
|
+
if (particleLifetime > startLifetimeArr[index]) {
|
|
824
|
+
deactivateParticle(index);
|
|
825
|
+
}
|
|
826
|
+
else {
|
|
827
|
+
const velocity = velocities[index];
|
|
828
|
+
velocity.x -= gravityVelocity.x * delta;
|
|
829
|
+
velocity.y -= gravityVelocity.y * delta;
|
|
830
|
+
velocity.z -= gravityVelocity.z * delta;
|
|
831
|
+
if (gravity !== 0 ||
|
|
832
|
+
velocity.x !== 0 ||
|
|
833
|
+
velocity.y !== 0 ||
|
|
834
|
+
velocity.z !== 0 ||
|
|
835
|
+
worldPositionChange.x !== 0 ||
|
|
836
|
+
worldPositionChange.y !== 0 ||
|
|
837
|
+
worldPositionChange.z !== 0) {
|
|
838
|
+
const positionIndex = index * 3;
|
|
839
|
+
if (simulationSpace === "WORLD" /* SimulationSpace.WORLD */) {
|
|
840
|
+
positionArr[positionIndex] -= worldPositionChange.x;
|
|
841
|
+
positionArr[positionIndex + 1] -= worldPositionChange.y;
|
|
842
|
+
positionArr[positionIndex + 2] -= worldPositionChange.z;
|
|
745
843
|
}
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
particleSystem.geometry.attributes.startLifetime.array[index];
|
|
751
|
-
applyModifiers({
|
|
752
|
-
delta,
|
|
753
|
-
generalData,
|
|
754
|
-
normalizedConfig,
|
|
755
|
-
attributes: particleSystem.geometry.attributes,
|
|
756
|
-
particleLifetimePercentage,
|
|
757
|
-
particleIndex: index,
|
|
758
|
-
});
|
|
844
|
+
positionArr[positionIndex] += velocity.x * delta;
|
|
845
|
+
positionArr[positionIndex + 1] += velocity.y * delta;
|
|
846
|
+
positionArr[positionIndex + 2] += velocity.z * delta;
|
|
847
|
+
positionNeedsUpdate = true;
|
|
759
848
|
}
|
|
849
|
+
lifetimeArr[index] = particleLifetime;
|
|
850
|
+
lifetimeNeedsUpdate = true;
|
|
851
|
+
_modifierParams.particleLifetimePercentage =
|
|
852
|
+
particleLifetime / startLifetimeArr[index];
|
|
853
|
+
_modifierParams.particleIndex = index;
|
|
854
|
+
applyModifiers(_modifierParams);
|
|
760
855
|
}
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
if (positionNeedsUpdate)
|
|
859
|
+
attributes.position.needsUpdate = true;
|
|
860
|
+
if (lifetimeNeedsUpdate)
|
|
861
|
+
attributes.lifetime.needsUpdate = true;
|
|
862
|
+
if (isEnabled && (looping || lifetime < duration * 1000)) {
|
|
863
|
+
const emissionDelta = now - lastEmissionTime;
|
|
864
|
+
const neededParticlesByTime = emission.rateOverTime
|
|
865
|
+
? Math.floor(calculateValue(generalData.particleSystemId, emission.rateOverTime, generalData.normalizedLifetimePercentage) *
|
|
866
|
+
(emissionDelta / 1000))
|
|
867
|
+
: 0;
|
|
868
|
+
const rateOverDistance = emission.rateOverDistance
|
|
869
|
+
? calculateValue(generalData.particleSystemId, emission.rateOverDistance, generalData.normalizedLifetimePercentage)
|
|
870
|
+
: 0;
|
|
871
|
+
const neededParticlesByDistance = rateOverDistance > 0 && generalData.distanceFromLastEmitByDistance > 0
|
|
872
|
+
? Math.floor(generalData.distanceFromLastEmitByDistance / (1 / rateOverDistance))
|
|
873
|
+
: 0;
|
|
874
|
+
const useDistanceStep = neededParticlesByDistance > 0;
|
|
875
|
+
if (useDistanceStep) {
|
|
876
|
+
_distanceStep.x =
|
|
877
|
+
(currentWorldPosition.x - _lastWorldPositionSnapshot.x) /
|
|
878
|
+
neededParticlesByDistance;
|
|
879
|
+
_distanceStep.y =
|
|
880
|
+
(currentWorldPosition.y - _lastWorldPositionSnapshot.y) /
|
|
881
|
+
neededParticlesByDistance;
|
|
882
|
+
_distanceStep.z =
|
|
883
|
+
(currentWorldPosition.z - _lastWorldPositionSnapshot.z) /
|
|
884
|
+
neededParticlesByDistance;
|
|
885
|
+
}
|
|
886
|
+
let neededParticles = neededParticlesByTime + neededParticlesByDistance;
|
|
887
|
+
if (rateOverDistance > 0 && neededParticlesByDistance >= 1) {
|
|
888
|
+
generalData.distanceFromLastEmitByDistance = 0;
|
|
889
|
+
}
|
|
890
|
+
// Process burst emissions
|
|
891
|
+
if (emission.bursts && generalData.burstStates) {
|
|
892
|
+
const bursts = emission.bursts;
|
|
893
|
+
const burstStates = generalData.burstStates;
|
|
894
|
+
const currentIterationTime = normalizedLifetime;
|
|
895
|
+
for (let i = 0; i < bursts.length; i++) {
|
|
896
|
+
const burst = bursts[i];
|
|
897
|
+
const state = burstStates[i];
|
|
898
|
+
const burstTimeMs = burst.time * 1000;
|
|
899
|
+
const cycles = burst.cycles ?? 1;
|
|
900
|
+
const intervalMs = (burst.interval ?? 0) * 1000;
|
|
901
|
+
const probability = burst.probability ?? 1;
|
|
902
|
+
// Check if we've looped and need to reset burst states
|
|
903
|
+
if (looping &&
|
|
904
|
+
currentIterationTime < burstTimeMs &&
|
|
905
|
+
state.cyclesExecuted > 0) {
|
|
906
|
+
state.cyclesExecuted = 0;
|
|
907
|
+
state.lastCycleTime = 0;
|
|
908
|
+
state.probabilityPassed = false;
|
|
783
909
|
}
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
});
|
|
800
|
-
if (particleIndex !== -1 &&
|
|
801
|
-
particleIndex <
|
|
802
|
-
particleSystem.geometry.attributes.isActive.array.length) {
|
|
803
|
-
let position = { x: 0, y: 0, z: 0 };
|
|
804
|
-
if (distanceStep &&
|
|
805
|
-
generatedParticlesByDistanceNeeds < neededParticlesByDistance) {
|
|
806
|
-
position = {
|
|
807
|
-
x: distanceStep.x * generatedParticlesByDistanceNeeds,
|
|
808
|
-
y: distanceStep.y * generatedParticlesByDistanceNeeds,
|
|
809
|
-
z: distanceStep.z * generatedParticlesByDistanceNeeds,
|
|
810
|
-
};
|
|
811
|
-
generatedParticlesByDistanceNeeds++;
|
|
812
|
-
}
|
|
813
|
-
activateParticle({
|
|
814
|
-
particleIndex,
|
|
815
|
-
activationTime: now,
|
|
816
|
-
position,
|
|
817
|
-
});
|
|
818
|
-
props.lastEmissionTime = now;
|
|
910
|
+
// Check if all cycles for this burst have been executed
|
|
911
|
+
if (state.cyclesExecuted >= cycles)
|
|
912
|
+
continue;
|
|
913
|
+
// Calculate the time for the next cycle
|
|
914
|
+
const nextCycleTime = burstTimeMs + state.cyclesExecuted * intervalMs;
|
|
915
|
+
// Check if it's time for the next cycle
|
|
916
|
+
if (currentIterationTime >= nextCycleTime) {
|
|
917
|
+
// On first cycle, determine if probability check passes
|
|
918
|
+
if (state.cyclesExecuted === 0) {
|
|
919
|
+
state.probabilityPassed = Math.random() < probability;
|
|
920
|
+
}
|
|
921
|
+
// Only emit if probability check passed
|
|
922
|
+
if (state.probabilityPassed) {
|
|
923
|
+
const burstCount = Math.floor(calculateValue(generalData.particleSystemId, burst.count, generalData.normalizedLifetimePercentage));
|
|
924
|
+
neededParticles += burstCount;
|
|
819
925
|
}
|
|
926
|
+
state.cyclesExecuted++;
|
|
927
|
+
state.lastCycleTime = currentIterationTime;
|
|
820
928
|
}
|
|
821
929
|
}
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
930
|
+
}
|
|
931
|
+
if (neededParticles > 0) {
|
|
932
|
+
let generatedParticlesByDistanceNeeds = 0;
|
|
933
|
+
for (let i = 0; i < neededParticles; i++) {
|
|
934
|
+
if (freeList.length === 0)
|
|
935
|
+
break;
|
|
936
|
+
const particleIndex = freeList.pop();
|
|
937
|
+
_tempPosition.x = 0;
|
|
938
|
+
_tempPosition.y = 0;
|
|
939
|
+
_tempPosition.z = 0;
|
|
940
|
+
if (useDistanceStep &&
|
|
941
|
+
generatedParticlesByDistanceNeeds < neededParticlesByDistance) {
|
|
942
|
+
_tempPosition.x = _distanceStep.x * generatedParticlesByDistanceNeeds;
|
|
943
|
+
_tempPosition.y = _distanceStep.y * generatedParticlesByDistanceNeeds;
|
|
944
|
+
_tempPosition.z = _distanceStep.z * generatedParticlesByDistanceNeeds;
|
|
945
|
+
generatedParticlesByDistanceNeeds++;
|
|
946
|
+
}
|
|
947
|
+
activateParticle({
|
|
948
|
+
particleIndex,
|
|
949
|
+
activationTime: now,
|
|
950
|
+
position: _tempPosition,
|
|
830
951
|
});
|
|
952
|
+
props.lastEmissionTime = now;
|
|
953
|
+
}
|
|
831
954
|
}
|
|
832
|
-
|
|
833
|
-
|
|
955
|
+
if (onUpdate)
|
|
956
|
+
onUpdate({
|
|
834
957
|
particleSystem,
|
|
958
|
+
delta,
|
|
959
|
+
elapsed,
|
|
960
|
+
lifetime,
|
|
961
|
+
normalizedLifetime,
|
|
962
|
+
iterationCount: iterationCount + 1,
|
|
835
963
|
});
|
|
836
|
-
}
|
|
964
|
+
}
|
|
965
|
+
else if (onComplete)
|
|
966
|
+
onComplete({
|
|
967
|
+
particleSystem,
|
|
968
|
+
});
|
|
969
|
+
};
|
|
970
|
+
export const updateParticleSystems = (cycleData) => {
|
|
971
|
+
createdParticleSystems.forEach((props) => updateParticleSystemInstance(props, cycleData));
|
|
837
972
|
};
|