sparcoon 0.4.0 → 0.4.2

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 ADDED
@@ -0,0 +1,79 @@
1
+ # sparcoon
2
+
3
+ [![npm](https://img.shields.io/npm/v/sparcoon.svg)](https://www.npmjs.com/package/sparcoon)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ Instanced-mesh particle system for Three.js. WebGL 1 compatible. **[EDITOR](https://jango-git.github.io/sparcoon/)**
7
+
8
+ ## API
9
+
10
+ - **FXEmitter** extends `Object3D`. Manages emission, lifetime, pooling, rendering. One draw call per emitter. Optional back-to-front sorting.
11
+ - **Spawn modules** set initial particle state: position (box/sphere/point), velocity (cone), rotation, scale, lifetime. Numeric params accept ranges.
12
+ - **Behavior modules** update particles per frame: directional/point gravity, velocity/torque damping, simplex noise, scale-over-life.
13
+ - **Materials**: `FXUnlitMaterial` (MeshBasicMaterial), `FXDiffuseMaterial` (MeshLambertMaterial + optional subsurface scatter). Shadow support.
14
+ - **Nodes** are composable material inputs: color-over-life, static/animated textures, normals (flat/spherical/map).
15
+
16
+ ## Example
17
+
18
+ Smoke volume with animated sprite sheet, subsurface scattering, and shadow receiving:
19
+
20
+ ```typescript
21
+ const RATE = 10;
22
+ const LIFETIME = 6;
23
+
24
+ const smoke = new FXEmitter(
25
+ [
26
+ new FXSpawnBox(areaMin, areaMax),
27
+ new FXSpawnRandomLifetime({ min: LIFETIME * 0.5, max: LIFETIME }),
28
+ new FXSpawnRandomScale({ min: 8, max: 16 }),
29
+ new FXSpawnRandomRotation(),
30
+ ],
31
+ [],
32
+ new FXDiffuseMaterial({
33
+ enableScatter: true,
34
+ scatterPower: 10,
35
+ forwardScatterStrength: 1,
36
+ backScatterStrength: 0.25,
37
+ shadowSensitivity: 0.25,
38
+ premultipliedAlpha: true,
39
+ albedoNodes: [
40
+ new FXAnimatedTextureNode({
41
+ texture: smokeAtlas,
42
+ rows: 8,
43
+ columns: 8,
44
+ interpolate: true,
45
+ }),
46
+ new FXColorOverLifeNode([
47
+ new FXColor(0xffffff, 0),
48
+ new FXColor(0xffffff, 0.4),
49
+ new FXColor(0xffffff, 0.4),
50
+ new FXColor(0xffffff, 0),
51
+ ]),
52
+ ],
53
+ }),
54
+ {
55
+ expectedCapacity: Math.ceil(LIFETIME * RATE) + 1,
56
+ receiveShadow: true,
57
+ sortCamera: camera,
58
+ sortFraction: 0.25,
59
+ },
60
+ );
61
+
62
+ scene.add(smoke);
63
+ smoke.play(RATE);
64
+
65
+ // render loop
66
+ FXEmitter.onWillRender(delta);
67
+ ```
68
+
69
+ ## Install
70
+
71
+ ```
72
+ npm install sparcoon
73
+ ```
74
+
75
+ Peer dep: `three` >=0.157.0 <0.180.0
76
+
77
+ ## License
78
+
79
+ MIT
@@ -17,7 +17,7 @@ export declare class FXEmitter extends Object3D {
17
17
  private readonly spawnSequence;
18
18
  private readonly behaviorSequence;
19
19
  automaticallyDestroyModules: boolean;
20
- sortCamera: Camera | undefined;
20
+ sortCamera?: Camera;
21
21
  sortFraction: number;
22
22
  private readonly mesh;
23
23
  private readonly material;
@@ -18,6 +18,6 @@ export interface FXScatterUniforms {
18
18
  value: number;
19
19
  };
20
20
  }
21
- export declare function buildFXDiffuseMaterial(varyings: Record<string, GLTypeInfo>, blending: Blending, useAlphaHashing: boolean, alphaTest: number, albedoNodes: readonly (FXColorNode | FXTextureNode)[], normalNodes: readonly (FXTextureNode | FXNormalNode)[], emissionNodes: readonly (FXColorNode | FXTextureNode)[], scatterUniforms?: FXScatterUniforms, shadowSensitivity?: {
21
+ export declare function buildFXDiffuseMaterial(varyings: Record<string, GLTypeInfo>, blending: Blending, useAlphaHashing: boolean, alphaTest: number, premultipliedAlpha: boolean, albedoNodes: readonly (FXColorNode | FXTextureNode)[], normalNodes: readonly (FXTextureNode | FXNormalNode)[], emissionNodes: readonly (FXColorNode | FXTextureNode)[], scatterUniforms?: FXScatterUniforms, shadowSensitivity?: {
22
22
  value: number;
23
- }, premultipliedAlpha?: boolean): MeshLambertMaterial;
23
+ }): MeshLambertMaterial;
@@ -37,7 +37,7 @@ function wrapShadowCalls(source, uniformName) {
37
37
  }
38
38
  return result;
39
39
  }
40
- function buildFXDiffuseMaterial(varyings, blending, useAlphaHashing, alphaTest, albedoNodes, normalNodes, emissionNodes, scatterUniforms, shadowSensitivity, premultipliedAlpha) {
40
+ function buildFXDiffuseMaterial(varyings, blending, useAlphaHashing, alphaTest, premultipliedAlpha, albedoNodes, normalNodes, emissionNodes, scatterUniforms, shadowSensitivity) {
41
41
  const useScatter = scatterUniforms !== undefined;
42
42
  const useShadowSensitivity = shadowSensitivity !== undefined;
43
43
  const attributeDeclarations = [];
@@ -67,6 +67,7 @@ function buildFXDiffuseMaterial(varyings, blending, useAlphaHashing, alphaTest,
67
67
  PARTICLE_DEFINES,
68
68
  attributeDeclarations.join("\n"),
69
69
  "varying vec2 p_uv;",
70
+ "varying float p_rotation;",
70
71
  useScatter ? "varying vec3 v_viewPosition;" : "",
71
72
  varyingDeclarations.join("\n"),
72
73
  ].join("\n");
@@ -78,6 +79,7 @@ function buildFXDiffuseMaterial(varyings, blending, useAlphaHashing, alphaTest,
78
79
  .replace("#include <defaultnormal_vertex>", "vec3 transformedNormal = vec3(0.0, 0.0, 1.0);")
79
80
  .replace("#include <begin_vertex>", [
80
81
  "p_uv = uv;",
82
+ "p_rotation = PARTICLE_ROTATION;",
81
83
  vertexAssignments.join("\n"),
82
84
  "vec3 transformed = position;",
83
85
  "#ifdef USE_ALPHAHASH",
@@ -153,6 +155,7 @@ function buildFXDiffuseMaterial(varyings, blending, useAlphaHashing, alphaTest,
153
155
  const fragmentPreamble = [
154
156
  PARTICLE_DEFINES,
155
157
  "varying vec2 p_uv;",
158
+ "varying float p_rotation;",
156
159
  varyingDeclarations.join("\n"),
157
160
  albedoNodes.flatMap((n) => n.uniformDeclarations).join("\n"),
158
161
  uniqueHelpers(albedoNodes),
@@ -197,6 +200,10 @@ function buildFXDiffuseMaterial(varyings, blending, useAlphaHashing, alphaTest,
197
200
  "float faceDirection = gl_FrontFacing ? 1.0 : -1.0;",
198
201
  `vec3 normal = ${normalExpressions[0]};`,
199
202
  ...blendLines,
203
+ "// Rotate normal xy to match billboard PARTICLE_ROTATION",
204
+ "float cosR = cos(p_rotation);",
205
+ "float sinR = sin(p_rotation);",
206
+ "normal = vec3(normal.x * cosR - normal.y * sinR, normal.x * sinR + normal.y * cosR, normal.z);",
200
207
  "normal *= faceDirection;",
201
208
  ].join("\n");
202
209
  fragmentShader = fragmentShader.replace("#include <normal_fragment_begin>", normalCode);
@@ -211,8 +218,6 @@ function buildFXDiffuseMaterial(varyings, blending, useAlphaHashing, alphaTest,
211
218
  fragmentShader = fragmentShader.replace("#include <emissivemap_fragment>", `totalEmissiveRadiance = (${combinedExpression}).rgb;`);
212
219
  }
213
220
  if (useScatter) {
214
- // Per-fragment Mie scatter for all light types.
215
- // viewDirection and all light directions/positions are in view space.
216
221
  fragmentShader = fragmentShader.replace("#include <opaque_fragment>", `
217
222
  #ifdef OPAQUE
218
223
  diffuseColor.a = 1.0;
@@ -13,14 +13,12 @@ export interface FXDiffuseMaterialOptions extends FXMaterialOptions {
13
13
  forwardScatterStrength: number;
14
14
  backScatterStrength: number;
15
15
  shadowSensitivity: number;
16
- premultipliedAlpha: boolean;
17
16
  }
18
17
  export declare class FXDiffuseMaterial extends FXMaterial {
19
18
  private readonly normalNodes;
20
19
  private readonly emissionNodes;
21
20
  private readonly scatterUniforms;
22
21
  private readonly shadowSensitivity;
23
- private readonly premultipliedAlpha;
24
22
  constructor(options?: Partial<FXDiffuseMaterialOptions>);
25
23
  destroy(): void;
26
24
  }
@@ -22,7 +22,6 @@ class FXDiffuseMaterial extends FXMaterial {
22
22
  }
23
23
  : undefined;
24
24
  this._private_shadowSensitivity = { value: options.shadowSensitivity ?? 1 };
25
- this._private_premultipliedAlpha = options.premultipliedAlpha ?? false;
26
25
  }
27
26
  destroy() {
28
27
  super.destroy();
@@ -35,7 +34,7 @@ class FXDiffuseMaterial extends FXMaterial {
35
34
  }
36
35
  /** @internal */
37
36
  buildThreeMaterial(varyings) {
38
- return buildFXDiffuseMaterial(varyings, this.blending, this.useAlphaHashing, this.alphaTest, this.albedoNodes, this._private_normalNodes, this._private_emissionNodes, this._private_scatterUniforms, this._private_shadowSensitivity, this._private_premultipliedAlpha);
37
+ return buildFXDiffuseMaterial(varyings, this.blending, this.useAlphaHashing, this.alphaTest, this.premultipliedAlpha, this.albedoNodes, this._private_normalNodes, this._private_emissionNodes, this._private_scatterUniforms, this._private_shadowSensitivity);
39
38
  }
40
39
  }
41
40
 
@@ -2,19 +2,20 @@ import type { Blending, MeshDistanceMaterial } from "three";
2
2
  import type { FXColorNode } from "../../nodes/color/FXColorNode";
3
3
  import type { FXTextureNode } from "../../nodes/texture/FXTextureNode";
4
4
  export interface FXMaterialOptions {
5
- albedoNodes?: (FXColorNode | FXTextureNode)[];
6
- blending?: Blending;
7
- useAlphaHashing?: boolean;
8
- alphaTest?: number;
9
- depthAlphaTest?: number;
10
- useDepthAlphaHash?: boolean;
11
- useSphericalDepth?: boolean;
5
+ albedoNodes: (FXColorNode | FXTextureNode)[];
6
+ blending: Blending;
7
+ useAlphaHashing: boolean;
8
+ alphaTest: number;
9
+ depthAlphaTest: number;
10
+ useDepthAlphaHash: boolean;
11
+ useSphericalDepth: boolean;
12
+ premultipliedAlpha: boolean;
12
13
  }
13
14
  export declare abstract class FXMaterial {
14
15
  private readonly depthAlphaTest;
15
16
  private readonly useDepthAlphaHash;
16
17
  private readonly useSphericalDepth;
17
- constructor(options?: FXMaterialOptions);
18
+ constructor(options?: Partial<FXMaterialOptions>);
18
19
  destroy(): void;
19
20
  buildDistanceMaterial(): MeshDistanceMaterial;
20
21
  }
@@ -8,6 +8,8 @@ class FXMaterial {
8
8
  this.blending = options.blending ?? NormalBlending;
9
9
  this.useAlphaHashing = options.useAlphaHashing ?? false;
10
10
  this.alphaTest = options.alphaTest ?? 0.0075;
11
+ this.premultipliedAlpha =
12
+ (options.useAlphaHashing === undefined && options.premultipliedAlpha) ?? true;
11
13
  this._private_depthAlphaTest = options.depthAlphaTest ?? 0.5;
12
14
  this._private_useDepthAlphaHash = options.useDepthAlphaHash ?? false;
13
15
  this._private_useSphericalDepth = options.useSphericalDepth ?? true;
@@ -3,4 +3,4 @@ import { MeshBasicMaterial } from "three";
3
3
  import type { GLTypeInfo } from "../../instancedParticle/shared";
4
4
  import type { FXColorNode } from "../../nodes/color/FXColorNode";
5
5
  import type { FXTextureNode } from "../../nodes/texture/FXTextureNode";
6
- export declare function buildFXUnlitMaterial(varyings: Record<string, GLTypeInfo>, blending: Blending, useAlphaHashing: boolean, alphaTest: number, albedoNodes: readonly (FXColorNode | FXTextureNode)[]): MeshBasicMaterial;
6
+ export declare function buildFXUnlitMaterial(varyings: Record<string, GLTypeInfo>, blending: Blending, useAlphaHashing: boolean, alphaTest: number, premultipliedAlpha: boolean, albedoNodes: readonly (FXColorNode | FXTextureNode)[]): MeshBasicMaterial;
@@ -1,7 +1,7 @@
1
1
  import { MeshBasicMaterial, DoubleSide } from 'three';
2
2
  import { PARTICLE_DEFINES } from '../../miscellaneous/miscellaneous.js';
3
3
 
4
- function buildFXUnlitMaterial(varyings, blending, useAlphaHashing, alphaTest, albedoNodes) {
4
+ function buildFXUnlitMaterial(varyings, blending, useAlphaHashing, alphaTest, premultipliedAlpha, albedoNodes) {
5
5
  const attributeDeclarations = [];
6
6
  const varyingDeclarations = [];
7
7
  const vertexAssignments = [];
@@ -21,6 +21,7 @@ function buildFXUnlitMaterial(varyings, blending, useAlphaHashing, alphaTest, al
21
21
  side: DoubleSide,
22
22
  forceSinglePass: true,
23
23
  alphaTest,
24
+ premultipliedAlpha,
24
25
  });
25
26
  material.onBeforeCompile = (shader) => {
26
27
  const vertexPreamble = [
@@ -2,5 +2,5 @@ import type { FXMaterialOptions } from "../FXMaterial/FXMaterial";
2
2
  import { FXMaterial } from "../FXMaterial/FXMaterial";
3
3
  export type FXUnlitMaterialOptions = FXMaterialOptions;
4
4
  export declare class FXUnlitMaterial extends FXMaterial {
5
- constructor(options?: FXUnlitMaterialOptions);
5
+ constructor(options?: Partial<FXUnlitMaterialOptions>);
6
6
  }
@@ -7,7 +7,7 @@ class FXUnlitMaterial extends FXMaterial {
7
7
  }
8
8
  /** @internal */
9
9
  buildThreeMaterial(varyings) {
10
- return buildFXUnlitMaterial(varyings, this.blending, this.useAlphaHashing, this.alphaTest, this.albedoNodes);
10
+ return buildFXUnlitMaterial(varyings, this.blending, this.useAlphaHashing, this.alphaTest, this.premultipliedAlpha, this.albedoNodes);
11
11
  }
12
12
  }
13
13
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sparcoon",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "",
5
5
  "keywords": [
6
6
  "particles",