sparcoon 0.5.0 → 0.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.
Files changed (105) hide show
  1. package/README.md +6 -6
  2. package/dist/emitter/FXEmitter.Internal.d.ts +55 -0
  3. package/dist/emitter/FXEmitter.d.ts +49 -0
  4. package/dist/emitter/FXEmitter.js +64 -0
  5. package/dist/emitter/behavior/FXBehavior.d.ts +12 -2
  6. package/dist/emitter/behavior/FXBehavior.js +3 -2
  7. package/dist/emitter/behavior/FXBehaviorDirectionalForce.d.ts +25 -0
  8. package/dist/emitter/behavior/FXBehaviorDirectionalForce.js +74 -0
  9. package/dist/emitter/behavior/FXBehaviorPointForce.d.ts +40 -0
  10. package/dist/emitter/behavior/FXBehaviorPointForce.js +98 -0
  11. package/dist/emitter/behavior/FXBehaviorScaleOverLife.d.ts +9 -8
  12. package/dist/emitter/behavior/FXBehaviorScaleOverLife.js +12 -29
  13. package/dist/emitter/behavior/FXBehaviorTorqueDamping.d.ts +4 -3
  14. package/dist/emitter/behavior/FXBehaviorTorqueDamping.js +3 -2
  15. package/dist/emitter/behavior/FXBehaviorTorqueNoise.d.ts +4 -3
  16. package/dist/emitter/behavior/FXBehaviorTorqueNoise.js +3 -2
  17. package/dist/emitter/behavior/FXBehaviorTorqueOverLife.d.ts +17 -0
  18. package/dist/emitter/behavior/FXBehaviorTorqueOverLife.js +34 -0
  19. package/dist/emitter/behavior/FXBehaviorVelocityDamping.d.ts +3 -2
  20. package/dist/emitter/behavior/FXBehaviorVelocityDamping.js +3 -2
  21. package/dist/emitter/behavior/FXBehaviorVelocityNoise.d.ts +6 -5
  22. package/dist/emitter/behavior/FXBehaviorVelocityNoise.js +6 -5
  23. package/dist/emitter/behavior/FXBehaviorVelocityOverLife.d.ts +20 -0
  24. package/dist/emitter/behavior/FXBehaviorVelocityOverLife.js +48 -0
  25. package/dist/emitter/spawn/FXSpawn.d.ts +4 -2
  26. package/dist/emitter/spawn/FXSpawn.js +3 -2
  27. package/dist/emitter/spawn/FXSpawnBox.d.ts +8 -16
  28. package/dist/emitter/spawn/FXSpawnBox.js +23 -39
  29. package/dist/emitter/spawn/{FXSpawnRandomLifetime.d.ts → FXSpawnLifetime.d.ts} +3 -5
  30. package/dist/emitter/spawn/{FXSpawnRandomLifetime.js → FXSpawnLifetime.js} +8 -10
  31. package/dist/emitter/spawn/FXSpawnOffset.d.ts +9 -0
  32. package/dist/emitter/spawn/FXSpawnOffset.js +9 -0
  33. package/dist/emitter/spawn/FXSpawnPoint.d.ts +8 -0
  34. package/dist/emitter/spawn/FXSpawnPoint.js +8 -0
  35. package/dist/emitter/spawn/{FXSpawnRandomRotation.d.ts → FXSpawnRotation.d.ts} +3 -5
  36. package/dist/emitter/spawn/{FXSpawnRandomRotation.js → FXSpawnRotation.js} +8 -10
  37. package/dist/emitter/spawn/{FXSpawnRandomScale.d.ts → FXSpawnScale.d.ts} +4 -6
  38. package/dist/emitter/spawn/{FXSpawnRandomScale.js → FXSpawnScale.js} +12 -14
  39. package/dist/emitter/spawn/FXSpawnSphere.d.ts +11 -6
  40. package/dist/emitter/spawn/FXSpawnSphere.js +11 -6
  41. package/dist/emitter/spawn/{FXSpawnRandomTorque.d.ts → FXSpawnTorque.d.ts} +3 -5
  42. package/dist/emitter/spawn/{FXSpawnRandomTorque.js → FXSpawnTorque.js} +8 -10
  43. package/dist/emitter/spawn/FXSpawnVelocity.d.ts +32 -0
  44. package/dist/emitter/spawn/{FXSpawnRandomVelocity.js → FXSpawnVelocity.js} +26 -11
  45. package/dist/index.d.ts +23 -16
  46. package/dist/index.js +20 -16
  47. package/dist/materials/FXDiffuseMaterial/FXDiffuseMaterial.Internal.d.ts +4 -4
  48. package/dist/materials/FXDiffuseMaterial/FXDiffuseMaterial.Internal.js +2 -2
  49. package/dist/materials/FXDiffuseMaterial/FXDiffuseMaterial.d.ts +55 -5
  50. package/dist/materials/FXDiffuseMaterial/FXDiffuseMaterial.js +9 -2
  51. package/dist/materials/FXMaterial/FXDepthMaterial.Internal.d.ts +3 -3
  52. package/dist/materials/FXMaterial/FXDistanceMaterial.Internal.d.ts +3 -3
  53. package/dist/materials/FXMaterial/FXMaterial.d.ts +51 -5
  54. package/dist/materials/FXMaterial/FXMaterial.js +5 -0
  55. package/dist/materials/FXUnlitMaterial/FXUnlitMaterial.Internal.d.ts +3 -3
  56. package/dist/materials/FXUnlitMaterial/FXUnlitMaterial.d.ts +3 -3
  57. package/dist/materials/FXUnlitMaterial/FXUnlitMaterial.js +3 -3
  58. package/dist/miscellaneous/curve/FXCurve1D.Internal.d.ts +20 -0
  59. package/dist/miscellaneous/curve/FXCurve1D.d.ts +17 -0
  60. package/dist/miscellaneous/curve/FXCurve1D.js +94 -0
  61. package/dist/miscellaneous/miscellaneous.d.ts +3 -1
  62. package/dist/miscellaneous/miscellaneous.js +8 -4
  63. package/dist/miscellaneous/texture/FXTextureView.Internal.js +6 -0
  64. package/dist/miscellaneous/texture/FXTextureView.js +234 -0
  65. package/dist/nodes/color/FXNodeColor.d.ts +7 -0
  66. package/dist/nodes/color/FXNodeColor.js +11 -0
  67. package/dist/nodes/color/FXNodeColorOverLife.d.ts +25 -0
  68. package/dist/nodes/color/FXNodeColorOverLife.js +60 -0
  69. package/dist/nodes/color/FXNodeSphericalClip.d.ts +18 -0
  70. package/dist/nodes/color/FXNodeSphericalClip.js +45 -0
  71. package/dist/nodes/normal/FXNodeFlatNormal.d.ts +7 -0
  72. package/dist/nodes/normal/FXNodeFlatNormal.js +17 -0
  73. package/dist/nodes/normal/FXNodeNormal.d.ts +7 -0
  74. package/dist/nodes/normal/FXNodeNormal.js +7 -0
  75. package/dist/nodes/normal/FXNodeSphericalNormal.d.ts +7 -0
  76. package/dist/nodes/normal/{FXSphericalNormalNode.js → FXNodeSphericalNormal.js} +7 -4
  77. package/dist/nodes/texture/FXNodeAnimatedTexture.d.ts +47 -0
  78. package/dist/nodes/texture/FXNodeAnimatedTexture.js +137 -0
  79. package/dist/nodes/texture/FXNodeStaticTexture.d.ts +18 -0
  80. package/dist/nodes/texture/FXNodeStaticTexture.js +60 -0
  81. package/dist/nodes/texture/FXNodeTexture.d.ts +7 -0
  82. package/dist/nodes/texture/FXNodeTexture.js +11 -0
  83. package/package.json +2 -4
  84. package/dist/emitter/behavior/FXBehaviorDirectionalGravity.d.ts +0 -21
  85. package/dist/emitter/behavior/FXBehaviorDirectionalGravity.js +0 -51
  86. package/dist/emitter/behavior/FXBehaviorPointGravity.d.ts +0 -39
  87. package/dist/emitter/behavior/FXBehaviorPointGravity.js +0 -116
  88. package/dist/emitter/spawn/FXSpawnRandomVelocity.d.ts +0 -17
  89. package/dist/nodes/color/FXColorNode.d.ts +0 -3
  90. package/dist/nodes/color/FXColorNode.js +0 -8
  91. package/dist/nodes/color/FXColorOverLifeNode.d.ts +0 -6
  92. package/dist/nodes/color/FXColorOverLifeNode.js +0 -44
  93. package/dist/nodes/color/FXSphericalClipNode.d.ts +0 -5
  94. package/dist/nodes/color/FXSphericalClipNode.js +0 -30
  95. package/dist/nodes/normal/FXFlatNormalNode.d.ts +0 -4
  96. package/dist/nodes/normal/FXFlatNormalNode.js +0 -14
  97. package/dist/nodes/normal/FXNormalNode.d.ts +0 -3
  98. package/dist/nodes/normal/FXNormalNode.js +0 -4
  99. package/dist/nodes/normal/FXSphericalNormalNode.d.ts +0 -4
  100. package/dist/nodes/texture/FXAnimatedTextureNode.d.ts +0 -12
  101. package/dist/nodes/texture/FXAnimatedTextureNode.js +0 -88
  102. package/dist/nodes/texture/FXStaticTextureNode.d.ts +0 -6
  103. package/dist/nodes/texture/FXStaticTextureNode.js +0 -34
  104. package/dist/nodes/texture/FXTextureNode.d.ts +0 -3
  105. package/dist/nodes/texture/FXTextureNode.js +0 -8
@@ -0,0 +1,60 @@
1
+ import { FXCurve1D } from '../../miscellaneous/curve/FXCurve1D.js';
2
+ import { buildGradientTexture, getNextInstanceId } from '../../miscellaneous/miscellaneous.js';
3
+ import { checkSRGBSupport } from '../../miscellaneous/webglCapabilities.js';
4
+ import { FXNodeColor } from './FXNodeColor.js';
5
+
6
+ /**
7
+ * Color node that animates particle color along a gradient curve over its lifetime
8
+ */
9
+ class FXNodeColorOverLife extends FXNodeColor {
10
+ /**
11
+ * @param curve - Color gradient defining the animation over the particle lifetime
12
+ */
13
+ constructor(curve) {
14
+ super();
15
+ const id = getNextInstanceId();
16
+ this._private_uniformName = `u_ColorOverLife_${id}`;
17
+ this._private_curveInternal = new FXCurve1D(curve);
18
+ this._private_gradientTexture = buildGradientTexture(this._private_curveInternal);
19
+ this.affectsDepth = true;
20
+ this.cacheKey = "color-over-life";
21
+ this.uniformDeclarations = [`uniform sampler2D ${this._private_uniformName};`];
22
+ this.uniforms = {
23
+ [this._private_uniformName]: { value: this._private_gradientTexture },
24
+ };
25
+ this.helperFunctions = `
26
+ vec4 fxSampleColorOverLife(sampler2D gradientTexture) {
27
+ // Normalized particle age [0, 1] maps to the gradient texture's x-axis
28
+ float lifetimeRatio = clamp(PARTICLE_AGE / PARTICLE_LIFETIME, 0.0, 1.0);
29
+ vec4 color = texture2D(gradientTexture, vec2(lifetimeRatio, 0.5));
30
+
31
+ // Apply gamma correction when sRGB textures are not natively supported
32
+ ${checkSRGBSupport() ? "" : "color = vec4(pow(color.rgb, vec3(2.2)), color.a);"}
33
+
34
+ return color;
35
+ }
36
+ `;
37
+ this.colorExpression = `fxSampleColorOverLife(${this._private_uniformName})`;
38
+ }
39
+ /** Current color gradient curve */
40
+ get curve() {
41
+ return this._private_curveInternal;
42
+ }
43
+ /**
44
+ * Replaces the color gradient curve and rebuilds the backing texture
45
+ *
46
+ * @param value - New color gradient configuration
47
+ */
48
+ set curve(value) {
49
+ this._private_curveInternal = new FXCurve1D(value);
50
+ this._private_gradientTexture.dispose();
51
+ this._private_gradientTexture = buildGradientTexture(this._private_curveInternal);
52
+ this.uniforms[this._private_uniformName].value = this._private_gradientTexture;
53
+ }
54
+ /** Releases the backing gradient texture */
55
+ destroy() {
56
+ this._private_gradientTexture.dispose();
57
+ }
58
+ }
59
+
60
+ export { FXNodeColorOverLife };
@@ -0,0 +1,18 @@
1
+ import { FXNodeColor } from "./FXNodeColor";
2
+ /**
3
+ * Color node that clips a particle to a circle with a configurable soft edge
4
+ *
5
+ * @remarks
6
+ * Fades alpha from fully opaque at `innerRadius` to fully transparent at the billboard edge
7
+ * (UV radius `0.5`). An `innerRadius` of `0.5` produces a hard circular clip with no gradient.
8
+ */
9
+ export declare class FXNodeSphericalClip extends FXNodeColor {
10
+ private readonly uniformInnerRadius;
11
+ /**
12
+ * @param innerRadius - UV radius where the fade begins; must be non-negative. Defaults to `0.5`
13
+ */
14
+ constructor(innerRadius?: number);
15
+ /** UV radius of the fully opaque inner area */
16
+ get innerRadius(): number;
17
+ set innerRadius(value: number);
18
+ }
@@ -0,0 +1,45 @@
1
+ import { assertValidNonNegativeNumber } from '../../miscellaneous/asserts.js';
2
+ import { getNextInstanceId } from '../../miscellaneous/miscellaneous.js';
3
+ import { FXNodeColor } from './FXNodeColor.js';
4
+
5
+ /**
6
+ * Color node that clips a particle to a circle with a configurable soft edge
7
+ *
8
+ * @remarks
9
+ * Fades alpha from fully opaque at `innerRadius` to fully transparent at the billboard edge
10
+ * (UV radius `0.5`). An `innerRadius` of `0.5` produces a hard circular clip with no gradient.
11
+ */
12
+ class FXNodeSphericalClip extends FXNodeColor {
13
+ /**
14
+ * @param innerRadius - UV radius where the fade begins; must be non-negative. Defaults to `0.5`
15
+ */
16
+ constructor(innerRadius = 0.5) {
17
+ super();
18
+ assertValidNonNegativeNumber(innerRadius, "FXNodeSphericalClip.constructor.innerRadius");
19
+ const id = getNextInstanceId();
20
+ this._private_uniformInnerRadius = `u_SphericalClipInnerRadius_${id}`;
21
+ this.affectsDepth = true;
22
+ this.cacheKey = "spherical-clip";
23
+ this.uniformDeclarations = [`uniform float ${this._private_uniformInnerRadius};`];
24
+ this.uniforms = {
25
+ [this._private_uniformInnerRadius]: { value: innerRadius },
26
+ };
27
+ this.helperFunctions = `
28
+ vec4 fxApplySphericalClip(float innerRadius) {
29
+ float distanceToCenter = distance(p_uv, vec2(0.5, 0.5));
30
+ return vec4(1.0, 1.0, 1.0, 1.0 - smoothstep(innerRadius, 0.5, distanceToCenter));
31
+ }
32
+ `;
33
+ this.colorExpression = `fxApplySphericalClip(${this._private_uniformInnerRadius})`;
34
+ }
35
+ /** UV radius of the fully opaque inner area */
36
+ get innerRadius() {
37
+ return this.uniforms[this._private_uniformInnerRadius].value;
38
+ }
39
+ set innerRadius(value) {
40
+ assertValidNonNegativeNumber(value, "FXNodeSphericalClip.innerRadius");
41
+ this.uniforms[this._private_uniformInnerRadius].value = value;
42
+ }
43
+ }
44
+
45
+ export { FXNodeSphericalClip };
@@ -0,0 +1,7 @@
1
+ import { FXNodeNormal } from "./FXNodeNormal";
2
+ /**
3
+ * Normal node that outputs a constant face-aligned normal pointing toward the camera
4
+ */
5
+ export declare class FXNodeFlatNormal extends FXNodeNormal {
6
+ constructor();
7
+ }
@@ -0,0 +1,17 @@
1
+ import { FXNodeNormal } from './FXNodeNormal.js';
2
+
3
+ /**
4
+ * Normal node that outputs a constant face-aligned normal pointing toward the camera
5
+ */
6
+ class FXNodeFlatNormal extends FXNodeNormal {
7
+ constructor() {
8
+ super();
9
+ this.cacheKey = "flat-normal";
10
+ this.uniformDeclarations = [];
11
+ this.uniforms = {};
12
+ this.helperFunctions = "";
13
+ this.normalExpression = "vec3(0.0, 0.0, 1.0)";
14
+ }
15
+ }
16
+
17
+ export { FXNodeFlatNormal };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Abstract base class for particle normal nodes
3
+ */
4
+ export declare abstract class FXNodeNormal {
5
+ /** Releases any GPU resources held by this node */
6
+ destroy?(): void;
7
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Abstract base class for particle normal nodes
3
+ */
4
+ class FXNodeNormal {
5
+ }
6
+
7
+ export { FXNodeNormal };
@@ -0,0 +1,7 @@
1
+ import { FXNodeNormal } from "./FXNodeNormal";
2
+ /**
3
+ * Normal node that makes flat billboard particles appear as spheres under directional lighting
4
+ */
5
+ export declare class FXNodeSphericalNormal extends FXNodeNormal {
6
+ constructor();
7
+ }
@@ -1,9 +1,12 @@
1
- import { FXNormalNode } from './FXNormalNode.js';
1
+ import { FXNodeNormal } from './FXNodeNormal.js';
2
2
 
3
- class FXSphericalNormalNode extends FXNormalNode {
3
+ /**
4
+ * Normal node that makes flat billboard particles appear as spheres under directional lighting
5
+ */
6
+ class FXNodeSphericalNormal extends FXNodeNormal {
4
7
  constructor() {
5
8
  super();
6
- this.cacheKey = "spherical";
9
+ this.cacheKey = "spherical-normal";
7
10
  this.uniformDeclarations = [];
8
11
  this.uniforms = {};
9
12
  this.helperFunctions = `
@@ -16,4 +19,4 @@ class FXSphericalNormalNode extends FXNormalNode {
16
19
  }
17
20
  }
18
21
 
19
- export { FXSphericalNormalNode };
22
+ export { FXNodeSphericalNormal };
@@ -0,0 +1,47 @@
1
+ import { FXTextureView } from "../../miscellaneous/texture/FXTextureView";
2
+ import type { FXTextureConfig } from "../../miscellaneous/texture/FXTextureView.Internal";
3
+ import { FXNodeTexture } from "./FXNodeTexture";
4
+ /**
5
+ * Options for {@link FXNodeAnimatedTexture}
6
+ */
7
+ export interface FXNodeAnimatedTextureOptions {
8
+ /** Texture or atlas configuration containing all animation frames */
9
+ texture: FXTextureConfig;
10
+ /** Number of frame columns in the sprite sheet */
11
+ columns: number;
12
+ /** Number of frame rows in the sprite sheet */
13
+ rows: number;
14
+ /**
15
+ * If true, blends between adjacent frames for smoother animation
16
+ *
17
+ * @defaultValue `false`
18
+ */
19
+ interpolate?: boolean;
20
+ }
21
+ /**
22
+ * Texture node that animates through sprite sheet frames over a particle's lifetime
23
+ *
24
+ * @remarks
25
+ * Frames advance from the first to the last as the particle ages. The total frame count
26
+ * is `columns * rows`. Both `columns` and `rows` must be positive integers.
27
+ */
28
+ export declare class FXNodeAnimatedTexture extends FXNodeTexture {
29
+ private readonly uniformTexture;
30
+ private readonly uniformUVTransform;
31
+ private readonly uniformColumns;
32
+ private readonly uniformRows;
33
+ private readonly textureViewInternal;
34
+ /**
35
+ * @param options - Sprite sheet configuration
36
+ */
37
+ constructor({ texture, columns, rows, interpolate }: FXNodeAnimatedTextureOptions);
38
+ /** View describing the texture and its atlas region */
39
+ get textureView(): FXTextureView;
40
+ /** Number of frame columns in the sprite sheet */
41
+ get columns(): number;
42
+ /** Number of frame rows in the sprite sheet */
43
+ get rows(): number;
44
+ set textureView(config: FXTextureConfig);
45
+ set columns(value: number);
46
+ set rows(value: number);
47
+ }
@@ -0,0 +1,137 @@
1
+ import { assertValidPositiveNumber } from '../../miscellaneous/asserts.js';
2
+ import { getNextInstanceId } from '../../miscellaneous/miscellaneous.js';
3
+ import { FXTextureView } from '../../miscellaneous/texture/FXTextureView.js';
4
+ import { checkSRGBSupport } from '../../miscellaneous/webglCapabilities.js';
5
+ import { FXNodeTexture } from './FXNodeTexture.js';
6
+
7
+ /**
8
+ * Texture node that animates through sprite sheet frames over a particle's lifetime
9
+ *
10
+ * @remarks
11
+ * Frames advance from the first to the last as the particle ages. The total frame count
12
+ * is `columns * rows`. Both `columns` and `rows` must be positive integers.
13
+ */
14
+ class FXNodeAnimatedTexture extends FXNodeTexture {
15
+ /**
16
+ * @param options - Sprite sheet configuration
17
+ */
18
+ constructor({ texture, columns, rows, interpolate = false }) {
19
+ super();
20
+ assertValidPositiveNumber(columns, "FXNodeAnimatedTexture.constructor.columns");
21
+ assertValidPositiveNumber(rows, "FXNodeAnimatedTexture.constructor.rows");
22
+ const idx = getNextInstanceId();
23
+ this._private_uniformTexture = `u_AnimatedTexture_${idx}`;
24
+ this._private_uniformUVTransform = `u_AnimatedTextureUVTransform_${idx}`;
25
+ this._private_uniformColumns = `u_AnimatedTextureColumns_${idx}`;
26
+ this._private_uniformRows = `u_AnimatedTextureRows_${idx}`;
27
+ this._private_textureViewInternal = new FXTextureView(texture);
28
+ const uvTransform = this._private_textureViewInternal.calculateUVTransform();
29
+ this._private_textureViewInternal.setTextureDirtyFalse();
30
+ this._private_textureViewInternal.setUVTransformDirtyFalse();
31
+ const gammaCorrection = checkSRGBSupport()
32
+ ? ""
33
+ : "color = vec4(pow(color.rgb, vec3(2.2)), color.a);";
34
+ this.affectsDepth = true;
35
+ this.cacheKey = interpolate ? "animated-texture-interpolated" : "animated-texture";
36
+ this.uniformDeclarations = [
37
+ `uniform sampler2D ${this._private_uniformTexture};`,
38
+ `uniform mat3 ${this._private_uniformUVTransform};`,
39
+ `uniform float ${this._private_uniformColumns};`,
40
+ `uniform float ${this._private_uniformRows};`,
41
+ ];
42
+ this.uniforms = {
43
+ [this._private_uniformTexture]: { value: this._private_textureViewInternal.texture },
44
+ [this._private_uniformUVTransform]: { value: uvTransform },
45
+ [this._private_uniformColumns]: { value: columns },
46
+ [this._private_uniformRows]: { value: rows },
47
+ };
48
+ this.helperFunctions = interpolate
49
+ ? `
50
+ vec2 fxFrameToUV(vec2 uv, float frame, float columns, float rows) {
51
+ float column = mod(frame, columns);
52
+ float row = floor(frame / columns);
53
+ return vec2((column + uv.x) / columns, 1.0 - (row + 1.0 - uv.y) / rows);
54
+ }
55
+
56
+ vec4 fxSampleAnimatedTexture(sampler2D spriteSheet, mat3 uvTransform, vec2 uv, float columns, float rows) {
57
+ float totalFrames = columns * rows;
58
+
59
+ // Fractional frame position drives both frame selection and blend weight
60
+ float exactFrame = PARTICLE_AGE / PARTICLE_LIFETIME * totalFrames;
61
+ float currentFrame = clamp(floor(exactFrame), 0.0, totalFrames - 1.0);
62
+ float nextFrame = clamp(currentFrame + 1.0, 0.0, totalFrames - 1.0);
63
+ float blendWeight = fract(exactFrame);
64
+
65
+ vec2 currentUV = (uvTransform * vec3(fxFrameToUV(uv, currentFrame, columns, rows), 1.0)).xy;
66
+ vec2 nextUV = (uvTransform * vec3(fxFrameToUV(uv, nextFrame, columns, rows), 1.0)).xy;
67
+
68
+ vec4 color = mix(
69
+ texture2D(spriteSheet, currentUV),
70
+ texture2D(spriteSheet, nextUV),
71
+ blendWeight
72
+ );
73
+
74
+ // Apply gamma correction when sRGB textures are not natively supported
75
+ ${gammaCorrection}
76
+
77
+ return color;
78
+ }
79
+ `
80
+ : `
81
+ vec4 fxSampleAnimatedTexture(sampler2D spriteSheet, mat3 uvTransform, vec2 uv, float columns, float rows) {
82
+ // Determine which frame to display based on normalized particle age
83
+ float totalFrames = columns * rows;
84
+ float frame = floor(PARTICLE_AGE / PARTICLE_LIFETIME * totalFrames);
85
+ frame = clamp(frame, 0.0, totalFrames - 1.0);
86
+
87
+ // Locate the frame's column and row in the sprite sheet grid
88
+ float column = mod(frame, columns);
89
+ float row = floor(frame / columns);
90
+
91
+ // Remap UV to the frame's tile region, then apply atlas transform
92
+ vec2 frameUV = vec2((column + uv.x) / columns, 1.0 - (row + 1.0 - uv.y) / rows);
93
+ vec2 atlasUV = (uvTransform * vec3(frameUV, 1.0)).xy;
94
+ vec4 color = texture2D(spriteSheet, atlasUV);
95
+
96
+ // Apply gamma correction when sRGB textures are not natively supported
97
+ ${gammaCorrection}
98
+
99
+ return color;
100
+ }
101
+ `;
102
+ this.colorExpression = `fxSampleAnimatedTexture(${this._private_uniformTexture}, ${this._private_uniformUVTransform}, p_uv, ${this._private_uniformColumns}, ${this._private_uniformRows})`;
103
+ }
104
+ /** View describing the texture and its atlas region */
105
+ get textureView() {
106
+ return this._private_textureViewInternal;
107
+ }
108
+ /** Number of frame columns in the sprite sheet */
109
+ get columns() {
110
+ return this.uniforms[this._private_uniformColumns].value;
111
+ }
112
+ /** Number of frame rows in the sprite sheet */
113
+ get rows() {
114
+ return this.uniforms[this._private_uniformRows].value;
115
+ }
116
+ set textureView(config) {
117
+ this._private_textureViewInternal.set(config);
118
+ if (this._private_textureViewInternal.textureDirty) {
119
+ this.uniforms[this._private_uniformTexture].value = this._private_textureViewInternal.texture;
120
+ this._private_textureViewInternal.setTextureDirtyFalse();
121
+ }
122
+ if (this._private_textureViewInternal.uvTransformDirty) {
123
+ this._private_textureViewInternal.calculateUVTransform(this.uniforms[this._private_uniformUVTransform].value);
124
+ this._private_textureViewInternal.setUVTransformDirtyFalse();
125
+ }
126
+ }
127
+ set columns(value) {
128
+ assertValidPositiveNumber(value, "FXNodeAnimatedTexture.columns");
129
+ this.uniforms[this._private_uniformColumns].value = value;
130
+ }
131
+ set rows(value) {
132
+ assertValidPositiveNumber(value, "FXNodeAnimatedTexture.rows");
133
+ this.uniforms[this._private_uniformRows].value = value;
134
+ }
135
+ }
136
+
137
+ export { FXNodeAnimatedTexture };
@@ -0,0 +1,18 @@
1
+ import { FXTextureView } from "../../miscellaneous/texture/FXTextureView";
2
+ import type { FXTextureConfig } from "../../miscellaneous/texture/FXTextureView.Internal";
3
+ import { FXNodeTexture } from "./FXNodeTexture";
4
+ /**
5
+ * Texture node that applies a single static texture to every particle
6
+ */
7
+ export declare class FXNodeStaticTexture extends FXNodeTexture {
8
+ private readonly uniformTexture;
9
+ private readonly uniformUVTransform;
10
+ private readonly textureViewInternal;
11
+ /**
12
+ * @param config - Texture or atlas configuration applied to every particle
13
+ */
14
+ constructor(config: FXTextureConfig);
15
+ /** View describing the texture and its atlas region */
16
+ get textureView(): FXTextureView;
17
+ set textureView(config: FXTextureConfig);
18
+ }
@@ -0,0 +1,60 @@
1
+ import { getNextInstanceId } from '../../miscellaneous/miscellaneous.js';
2
+ import { FXTextureView } from '../../miscellaneous/texture/FXTextureView.js';
3
+ import { checkSRGBSupport } from '../../miscellaneous/webglCapabilities.js';
4
+ import { FXNodeTexture } from './FXNodeTexture.js';
5
+
6
+ /**
7
+ * Texture node that applies a single static texture to every particle
8
+ */
9
+ class FXNodeStaticTexture extends FXNodeTexture {
10
+ /**
11
+ * @param config - Texture or atlas configuration applied to every particle
12
+ */
13
+ constructor(config) {
14
+ super();
15
+ const id = getNextInstanceId();
16
+ this._private_uniformTexture = `u_StaticTexture_${id}`;
17
+ this._private_uniformUVTransform = `u_StaticTextureUVTransform_${id}`;
18
+ this._private_textureViewInternal = new FXTextureView(config);
19
+ const uvTransform = this._private_textureViewInternal.calculateUVTransform();
20
+ this._private_textureViewInternal.setTextureDirtyFalse();
21
+ this._private_textureViewInternal.setUVTransformDirtyFalse();
22
+ this.affectsDepth = true;
23
+ this.cacheKey = "static-texture";
24
+ this.uniformDeclarations = [
25
+ `uniform sampler2D ${this._private_uniformTexture};`,
26
+ `uniform mat3 ${this._private_uniformUVTransform};`,
27
+ ];
28
+ this.uniforms = {
29
+ [this._private_uniformTexture]: { value: this._private_textureViewInternal.texture },
30
+ [this._private_uniformUVTransform]: { value: uvTransform },
31
+ };
32
+ this.helperFunctions = `
33
+ vec4 fxSampleStaticTexture(sampler2D colorTexture, mat3 uvTransform, vec2 uv) {
34
+ vec2 transformedUV = (uvTransform * vec3(uv, 1.0)).xy;
35
+ vec4 color = texture2D(colorTexture, transformedUV);
36
+ // Apply gamma correction when sRGB textures are not natively supported
37
+ ${checkSRGBSupport() ? "" : "color = vec4(pow(color.rgb, vec3(2.2)), color.a);"}
38
+ return color;
39
+ }
40
+ `;
41
+ this.colorExpression = `fxSampleStaticTexture(${this._private_uniformTexture}, ${this._private_uniformUVTransform}, p_uv)`;
42
+ }
43
+ /** View describing the texture and its atlas region */
44
+ get textureView() {
45
+ return this._private_textureViewInternal;
46
+ }
47
+ set textureView(config) {
48
+ this._private_textureViewInternal.set(config);
49
+ if (this._private_textureViewInternal.textureDirty) {
50
+ this.uniforms[this._private_uniformTexture].value = this._private_textureViewInternal.texture;
51
+ this._private_textureViewInternal.setTextureDirtyFalse();
52
+ }
53
+ if (this._private_textureViewInternal.uvTransformDirty) {
54
+ this._private_textureViewInternal.calculateUVTransform(this.uniforms[this._private_uniformUVTransform].value);
55
+ this._private_textureViewInternal.setUVTransformDirtyFalse();
56
+ }
57
+ }
58
+ }
59
+
60
+ export { FXNodeStaticTexture };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Abstract base class for particle texture nodes
3
+ */
4
+ export declare abstract class FXNodeTexture {
5
+ /** Releases any GPU resources held by this node */
6
+ destroy?(): void;
7
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Abstract base class for particle texture nodes
3
+ */
4
+ class FXNodeTexture {
5
+ constructor() {
6
+ /** @internal */
7
+ this.affectsDepth = false;
8
+ }
9
+ }
10
+
11
+ export { FXNodeTexture };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sparcoon",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Instanced-mesh particle system for Three.js.",
5
5
  "keywords": [
6
6
  "particles",
@@ -45,6 +45,7 @@
45
45
  },
46
46
  "devDependencies": {
47
47
  "@rollup/plugin-node-resolve": "^16.0.1",
48
+ "@rollup/plugin-commonjs": "^29.0.2",
48
49
  "@types/node": "^25.0.2",
49
50
  "@types/three": "^0.157.0",
50
51
  "@typescript-eslint/eslint-plugin": "^8.32.0",
@@ -55,8 +56,5 @@
55
56
  "rollup-plugin-typescript2": "^0.36.0",
56
57
  "ts-transformer-minify-privates": "^1.0.0",
57
58
  "typescript": "^5.8.3"
58
- },
59
- "dependencies": {
60
- "@rollup/plugin-commonjs": "^29.0.2"
61
59
  }
62
60
  }
@@ -1,21 +0,0 @@
1
- import type { Vector3Like } from "../../miscellaneous/math";
2
- import type { FXVector3Config } from "../../miscellaneous/miscellaneous";
3
- import { FXBehavior } from "./FXBehavior";
4
- /**
5
- * Applies constant acceleration to all particles.
6
- *
7
- * Adds the direction vector to particle velocity each frame.
8
- */
9
- export declare class FXBehaviorDirectionalGravity extends FXBehavior<{
10
- builtin: "Matrix4";
11
- }> {
12
- private directionInternal;
13
- /**
14
- * @param direction - Acceleration vector in units/second²
15
- */
16
- constructor(direction: Vector3Like);
17
- /** Acceleration vector in units/second² */
18
- get direction(): Vector3Like;
19
- /** Acceleration vector in units/second² */
20
- set direction(value: FXVector3Config);
21
- }
@@ -1,51 +0,0 @@
1
- import { assertValidNumber } from '../../miscellaneous/asserts.js';
2
- import { resolveFXVector3Config, BUILTIN_OFFSET_VELOCITY_X, BUILTIN_OFFSET_VELOCITY_Y, BUILTIN_OFFSET_VELOCITY_Z } from '../../miscellaneous/miscellaneous.js';
3
- import { FXBehavior } from './FXBehavior.js';
4
-
5
- /**
6
- * Applies constant acceleration to all particles.
7
- *
8
- * Adds the direction vector to particle velocity each frame.
9
- */
10
- class FXBehaviorDirectionalGravity extends FXBehavior {
11
- /**
12
- * @param direction - Acceleration vector in units/second²
13
- */
14
- constructor(direction) {
15
- super();
16
- /** @internal */
17
- this.requiredProperties = { builtin: "Matrix4" };
18
- this._private_directionInternal = resolveFXVector3Config(direction);
19
- assertValidNumber(this._private_directionInternal.x, "FXBehaviorDirectionalGravity.constructor.direction.x");
20
- assertValidNumber(this._private_directionInternal.y, "FXBehaviorDirectionalGravity.constructor.direction.y");
21
- assertValidNumber(this._private_directionInternal.z, "FXBehaviorDirectionalGravity.constructor.direction.z");
22
- }
23
- /** Acceleration vector in units/second² */
24
- get direction() {
25
- return this._private_directionInternal;
26
- }
27
- /** Acceleration vector in units/second² */
28
- set direction(value) {
29
- this._private_directionInternal = resolveFXVector3Config(value);
30
- assertValidNumber(this._private_directionInternal.x, "FXBehaviorDirectionalGravity.direction.x");
31
- assertValidNumber(this._private_directionInternal.y, "FXBehaviorDirectionalGravity.direction.y");
32
- assertValidNumber(this._private_directionInternal.z, "FXBehaviorDirectionalGravity.direction.z");
33
- }
34
- /** @internal */
35
- update(properties, instanceCount, deltaTime) {
36
- const { builtin } = properties;
37
- const { array, itemSize } = builtin;
38
- const offsetX = this._private_directionInternal.x * deltaTime;
39
- const offsetY = this._private_directionInternal.y * deltaTime;
40
- const offsetZ = this._private_directionInternal.z * deltaTime;
41
- for (let i = 0; i < instanceCount; i++) {
42
- const itemOffset = i * itemSize;
43
- array[itemOffset + BUILTIN_OFFSET_VELOCITY_X] += offsetX;
44
- array[itemOffset + BUILTIN_OFFSET_VELOCITY_Y] += offsetY;
45
- array[itemOffset + BUILTIN_OFFSET_VELOCITY_Z] += offsetZ;
46
- }
47
- builtin.needsUpdate = true;
48
- }
49
- }
50
-
51
- export { FXBehaviorDirectionalGravity };
@@ -1,39 +0,0 @@
1
- import type { Vector3Like } from "../../miscellaneous/math";
2
- import { type FXRange, type FXRangeConfig, type FXVector3Config } from "../../miscellaneous/miscellaneous";
3
- import { FXBehavior } from "./FXBehavior";
4
- /**
5
- * Applies gravity attraction toward a point.
6
- *
7
- * Force follows inverse power law: strength / distance^exponent. Particles closer than threshold are unaffected.
8
- */
9
- export declare class FXBehaviorPointGravity extends FXBehavior<{
10
- builtin: "Matrix4";
11
- }> {
12
- private centerInternal;
13
- private strengthInternal;
14
- private exponentInternal;
15
- private thresholdInternal;
16
- /**
17
- * @param center - Gravity center position
18
- * @param strength - Force multiplier range. Accepts number, tuple, or range object
19
- * @param exponent - Distance power exponent range. Accepts number, tuple, or range object
20
- * @param threshold - Minimum distance range. Accepts number, tuple, or range object
21
- */
22
- constructor(center: Vector3Like, strength: FXRangeConfig, exponent?: FXRangeConfig, threshold?: FXRangeConfig);
23
- /** Gravity center position */
24
- get center(): Vector3Like;
25
- /** Force multiplier range */
26
- get strength(): FXRange;
27
- /** Distance power exponent range */
28
- get exponent(): FXRange;
29
- /** Minimum distance range */
30
- get threshold(): FXRange;
31
- /** Gravity center position */
32
- set center(value: FXVector3Config);
33
- /** Force multiplier range */
34
- set strength(value: FXRangeConfig);
35
- /** Distance power exponent range */
36
- set exponent(value: FXRangeConfig);
37
- /** Minimum distance range */
38
- set threshold(value: FXRangeConfig);
39
- }