@newkrok/three-particles 2.14.0 → 2.15.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 CHANGED
@@ -3,10 +3,12 @@
3
3
  </p>
4
4
 
5
5
  # THREE Particles
6
- [![Run Tests](https://github.com/NewKrok/three-particles/actions/workflows/test.yml/badge.svg)](https://github.com/NewKrok/three-particles/actions/workflows/test.yml)
7
- [![NPM Version](https://img.shields.io/npm/v/@newkrok/three-particles.svg)](https://www.npmjs.com/package/@newkrok/three-particles)
8
- [![NPM Downloads](https://img.shields.io/npm/dw/@newkrok/three-particles.svg)](https://www.npmjs.com/package/@newkrok/three-particles)
9
- [![Bundle Size](https://img.shields.io/bundlephobia/minzip/@newkrok/three-particles)](https://bundlephobia.com/package/@newkrok/three-particles)
6
+ [![npm](https://img.shields.io/npm/v/@newkrok/three-particles.svg)](https://www.npmjs.com/package/@newkrok/three-particles)
7
+ [![downloads](https://img.shields.io/npm/dm/@newkrok/three-particles.svg)](https://www.npmjs.com/package/@newkrok/three-particles)
8
+ [![CI](https://github.com/NewKrok/three-particles/actions/workflows/ci.yml/badge.svg)](https://github.com/NewKrok/three-particles/actions/workflows/ci.yml)
9
+ [![gzip](https://img.shields.io/bundlephobia/minzip/@newkrok/three-particles)](https://bundlephobia.com/package/@newkrok/three-particles)
10
+ [![license](https://img.shields.io/npm/l/@newkrok/three-particles.svg)](https://github.com/NewKrok/three-particles/blob/master/LICENSE)
11
+ [![docs](https://img.shields.io/badge/docs-online-blue)](https://newkrok.github.io/three-particles/api/)
10
12
 
11
13
  Particle system for ThreeJS.
12
14
 
@@ -62,12 +64,18 @@ const effect = {
62
64
  // Your effect configuration here
63
65
  // It can be empty to use default settings
64
66
  };
65
- const { instance } = createParticleSystem(effect);
66
- scene.add(instance);
67
+ const system = createParticleSystem(effect);
68
+ scene.add(system.instance);
67
69
 
68
70
  // Update the particle system in your animation loop
69
71
  // Pass the current time, delta time, and elapsed time
70
72
  updateParticleSystems({now, delta, elapsed});
73
+
74
+ // Update configuration at runtime without recreating the system
75
+ system.updateConfig({
76
+ gravity: -9.8,
77
+ forceFields: [{ type: 'DIRECTIONAL', direction: { x: 1, y: 0, z: 0 }, strength: 5 }],
78
+ });
71
79
  ```
72
80
 
73
81
  # Usage with React Three Fiber
package/dist/index.d.ts CHANGED
@@ -1849,6 +1849,39 @@ type ParticleSystem = {
1849
1849
  pauseEmitter: () => void;
1850
1850
  dispose: () => void;
1851
1851
  update: (cycleData: CycleData) => void;
1852
+ /**
1853
+ * Updates the particle system configuration at runtime without recreating the system.
1854
+ *
1855
+ * System-level properties (gravity, force fields, noise, emission rates, color/size/opacity
1856
+ * over lifetime curves) take effect immediately for all particles.
1857
+ * Per-particle spawn properties (startColor, startSize, startSpeed, startLifetime, etc.)
1858
+ * only affect newly emitted particles — already-alive particles retain their original values.
1859
+ *
1860
+ * @param config - A partial configuration object. Only the provided properties will be updated;
1861
+ * all other settings remain unchanged.
1862
+ *
1863
+ * @remarks
1864
+ * Structural properties that are set at creation time cannot be changed at runtime:
1865
+ * `maxParticles`, `renderer.rendererType`, `shape`, and `map` (texture).
1866
+ * Passing these will update the internal config but have no visible effect since the
1867
+ * geometry and material are pre-allocated.
1868
+ *
1869
+ * @example
1870
+ * ```typescript
1871
+ * const system = createParticleSystem(config);
1872
+ *
1873
+ * // Change wind direction in real time
1874
+ * system.updateConfig({
1875
+ * forceFields: [{ type: ForceFieldType.DIRECTIONAL, direction: { x: 1, y: 0, z: 0 }, strength: 5 }],
1876
+ * });
1877
+ *
1878
+ * // Gradually change color of new particles
1879
+ * system.updateConfig({
1880
+ * startColor: { min: { r: 1, g: 0, b: 0 }, max: { r: 1, g: 0.5, b: 0 } },
1881
+ * });
1882
+ * ```
1883
+ */
1884
+ updateConfig: (config: Partial<ParticleSystemConfig>) => void;
1852
1885
  };
1853
1886
  /**
1854
1887
  * Data representing the current cycle of the particle system's update loop.
package/dist/index.js CHANGED
@@ -1184,6 +1184,16 @@ var _modifierParams = {
1184
1184
  particleLifetimePercentage: 0,
1185
1185
  particleIndex: 0
1186
1186
  };
1187
+ var toVector3 = (v, fallback) => v ? new THREE4.Vector3(v.x ?? 0, v.y ?? 0, v.z ?? 0) : fallback.clone();
1188
+ var normalizeForceFields = (rawForceFields) => (rawForceFields ?? []).map((ff) => ({
1189
+ isActive: ff.isActive ?? true,
1190
+ type: ff.type ?? "POINT" /* POINT */,
1191
+ position: toVector3(ff.position, new THREE4.Vector3(0, 0, 0)),
1192
+ direction: toVector3(ff.direction, new THREE4.Vector3(0, 1, 0)).normalize(),
1193
+ strength: ff.strength ?? 1,
1194
+ range: Math.max(0, ff.range ?? Infinity),
1195
+ falloff: ff.falloff ?? "LINEAR" /* LINEAR */
1196
+ }));
1187
1197
  var blendingMap = {
1188
1198
  "THREE.NoBlending": THREE4.NoBlending,
1189
1199
  "THREE.NormalBlending": THREE4.NormalBlending,
@@ -1507,16 +1517,7 @@ var createParticleSystem = (config = DEFAULT_PARTICLE_SYSTEM_CONFIG, externalNow
1507
1517
  subEmitters,
1508
1518
  forceFields: rawForceFields
1509
1519
  } = normalizedConfig;
1510
- const toVector3 = (v, fallback) => v ? new THREE4.Vector3(v.x ?? 0, v.y ?? 0, v.z ?? 0) : fallback.clone();
1511
- const normalizedForceFields = (rawForceFields ?? []).map((ff) => ({
1512
- isActive: ff.isActive ?? true,
1513
- type: ff.type ?? "POINT" /* POINT */,
1514
- position: toVector3(ff.position, new THREE4.Vector3(0, 0, 0)),
1515
- direction: toVector3(ff.direction, new THREE4.Vector3(0, 1, 0)).normalize(),
1516
- strength: ff.strength ?? 1,
1517
- range: Math.max(0, ff.range ?? Infinity),
1518
- falloff: ff.falloff ?? "LINEAR" /* LINEAR */
1519
- }));
1520
+ const normalizedForceFields = normalizeForceFields(rawForceFields);
1520
1521
  if (typeof renderer?.blending === "string")
1521
1522
  renderer.blending = blendingMap[renderer.blending];
1522
1523
  const startPositions = Array.from(
@@ -1952,44 +1953,45 @@ var createParticleSystem = (config = DEFAULT_PARTICLE_SYSTEM_CONFIG, externalNow
1952
1953
  if (generalData.noise.offsets)
1953
1954
  generalData.noise.offsets[particleIndex] = Math.random() * 100;
1954
1955
  const colorRandomRatio2 = Math.random();
1955
- aColorR.array[particleIndex] = startColor.min.r + colorRandomRatio2 * (startColor.max.r - startColor.min.r);
1956
+ const cfgStartColor = normalizedConfig.startColor;
1957
+ aColorR.array[particleIndex] = cfgStartColor.min.r + colorRandomRatio2 * (cfgStartColor.max.r - cfgStartColor.min.r);
1956
1958
  aColorR.needsUpdate = true;
1957
- aColorG.array[particleIndex] = startColor.min.g + colorRandomRatio2 * (startColor.max.g - startColor.min.g);
1959
+ aColorG.array[particleIndex] = cfgStartColor.min.g + colorRandomRatio2 * (cfgStartColor.max.g - cfgStartColor.min.g);
1958
1960
  aColorG.needsUpdate = true;
1959
- aColorB.array[particleIndex] = startColor.min.b + colorRandomRatio2 * (startColor.max.b - startColor.min.b);
1961
+ aColorB.array[particleIndex] = cfgStartColor.min.b + colorRandomRatio2 * (cfgStartColor.max.b - cfgStartColor.min.b);
1960
1962
  aColorB.needsUpdate = true;
1961
1963
  generalData.startValues.startColorR[particleIndex] = aColorR.array[particleIndex];
1962
1964
  generalData.startValues.startColorG[particleIndex] = aColorG.array[particleIndex];
1963
1965
  generalData.startValues.startColorB[particleIndex] = aColorB.array[particleIndex];
1964
- aStartFrame.array[particleIndex] = textureSheetAnimation.startFrame ? calculateValue(
1966
+ aStartFrame.array[particleIndex] = normalizedConfig.textureSheetAnimation.startFrame ? calculateValue(
1965
1967
  generalData.particleSystemId,
1966
- textureSheetAnimation.startFrame,
1968
+ normalizedConfig.textureSheetAnimation.startFrame,
1967
1969
  0
1968
1970
  ) : 0;
1969
1971
  aStartFrame.needsUpdate = true;
1970
1972
  aStartLifetime.array[particleIndex] = calculateValue(
1971
1973
  generalData.particleSystemId,
1972
- startLifetime,
1974
+ normalizedConfig.startLifetime,
1973
1975
  generalData.normalizedLifetimePercentage
1974
1976
  ) * 1e3;
1975
1977
  aStartLifetime.needsUpdate = true;
1976
1978
  generalData.startValues.startSize[particleIndex] = calculateValue(
1977
1979
  generalData.particleSystemId,
1978
- startSize,
1980
+ normalizedConfig.startSize,
1979
1981
  generalData.normalizedLifetimePercentage
1980
1982
  );
1981
1983
  aSize.array[particleIndex] = generalData.startValues.startSize[particleIndex];
1982
1984
  aSize.needsUpdate = true;
1983
1985
  generalData.startValues.startOpacity[particleIndex] = calculateValue(
1984
1986
  generalData.particleSystemId,
1985
- startOpacity,
1987
+ normalizedConfig.startOpacity,
1986
1988
  generalData.normalizedLifetimePercentage
1987
1989
  );
1988
1990
  aColorA.array[particleIndex] = generalData.startValues.startOpacity[particleIndex];
1989
1991
  aColorA.needsUpdate = true;
1990
1992
  aRotation.array[particleIndex] = calculateValue(
1991
1993
  generalData.particleSystemId,
1992
- startRotation,
1994
+ normalizedConfig.startRotation,
1993
1995
  generalData.normalizedLifetimePercentage
1994
1996
  );
1995
1997
  aRotation.needsUpdate = true;
@@ -2010,8 +2012,8 @@ var createParticleSystem = (config = DEFAULT_PARTICLE_SYSTEM_CONFIG, externalNow
2010
2012
  );
2011
2013
  calculatePositionAndVelocity(
2012
2014
  generalData,
2013
- shape,
2014
- startSpeed,
2015
+ normalizedConfig.shape,
2016
+ normalizedConfig.startSpeed,
2015
2017
  startPositions[particleIndex],
2016
2018
  velocities[particleIndex]
2017
2019
  );
@@ -2406,12 +2408,53 @@ var createParticleSystem = (config = DEFAULT_PARTICLE_SYSTEM_CONFIG, externalNow
2406
2408
  for (const sub of instances) sub.update(cycleData);
2407
2409
  }
2408
2410
  };
2411
+ const updateConfig = (partialConfig) => {
2412
+ ObjectUtils.deepMerge(instanceData.normalizedConfig, partialConfig, {
2413
+ applyToFirstObject: true,
2414
+ skippedProperties: []
2415
+ });
2416
+ const cfg = instanceData.normalizedConfig;
2417
+ if (partialConfig.gravity !== void 0) {
2418
+ instanceData.gravity = cfg.gravity;
2419
+ generalData.lastWorldQuaternion.x = -99999;
2420
+ }
2421
+ if (partialConfig.duration !== void 0)
2422
+ instanceData.duration = cfg.duration;
2423
+ if (partialConfig.looping !== void 0) instanceData.looping = cfg.looping;
2424
+ if (partialConfig.simulationSpace !== void 0)
2425
+ instanceData.simulationSpace = cfg.simulationSpace;
2426
+ if (partialConfig.emission !== void 0)
2427
+ instanceData.emission = cfg.emission;
2428
+ if (partialConfig.forceFields !== void 0) {
2429
+ instanceData.normalizedForceFields = normalizeForceFields(
2430
+ cfg.forceFields
2431
+ );
2432
+ }
2433
+ if (partialConfig.noise !== void 0) {
2434
+ const n = cfg.noise;
2435
+ generalData.noise = {
2436
+ isActive: n.isActive,
2437
+ strength: n.strength,
2438
+ noisePower: 0.15 * n.strength,
2439
+ positionAmount: n.positionAmount,
2440
+ rotationAmount: n.rotationAmount,
2441
+ sizeAmount: n.sizeAmount,
2442
+ sampler: n.isActive ? new FBM({
2443
+ seed: Math.random(),
2444
+ scale: n.frequency,
2445
+ octaves: n.octaves
2446
+ }) : void 0,
2447
+ offsets: n.useRandomOffset ? generalData.noise.offsets ?? Array.from({ length: maxParticles }, () => Math.random() * 100) : void 0
2448
+ };
2449
+ }
2450
+ };
2409
2451
  return {
2410
2452
  instance: wrapper || particleSystem,
2411
2453
  resumeEmitter,
2412
2454
  pauseEmitter,
2413
2455
  dispose,
2414
- update
2456
+ update,
2457
+ updateConfig
2415
2458
  };
2416
2459
  };
2417
2460
  var updateParticleSystemInstance = (props, { now, delta, elapsed }) => {