sparcoon 0.7.3 → 0.9.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 (155) hide show
  1. package/README.md +124 -76
  2. package/dist/artifact/FXArtifact.d.ts +120 -0
  3. package/dist/artifact/FXArtifact.js +1 -0
  4. package/dist/coreLayout.d.ts +22 -0
  5. package/dist/coreLayout.js +32 -0
  6. package/dist/effect/FXEffect.d.ts +77 -0
  7. package/dist/effect/FXEffect.js +454 -0
  8. package/dist/effect/FXEffectSampling.Internal.d.ts +24 -0
  9. package/dist/effect/FXEffectSampling.Internal.js +165 -0
  10. package/dist/effect/FXEffectSpec.d.ts +115 -0
  11. package/dist/effect/FXEffectSpec.js +1 -0
  12. package/dist/emitter/FXEmitter.Internal.d.ts +23 -58
  13. package/dist/emitter/FXEmitter.Internal.js +13 -29
  14. package/dist/emitter/FXEmitter.d.ts +46 -48
  15. package/dist/emitter/FXEmitter.js +355 -186
  16. package/dist/emitter/FXFromArtifactsOptions.d.ts +13 -0
  17. package/dist/emitter/FXFromArtifactsOptions.js +1 -0
  18. package/dist/emitter/FXObjectMotion.Internal.d.ts +33 -0
  19. package/dist/emitter/FXObjectMotion.Internal.js +87 -0
  20. package/dist/emitter/FXSimulationHolder.d.ts +1 -0
  21. package/dist/emitter/FXSimulationHolder.js +43 -0
  22. package/dist/index.d.ts +10 -39
  23. package/dist/index.js +13 -36
  24. package/dist/instancedParticle/FXInstancedParticle.d.ts +1 -22
  25. package/dist/instancedParticle/FXInstancedParticle.js +145 -85
  26. package/dist/instancedParticle/glTypeInfo.d.ts +5 -0
  27. package/dist/instancedParticle/glTypeInfo.js +3 -0
  28. package/dist/instancedParticle/primitiveGeometry.d.ts +1 -0
  29. package/dist/instancedParticle/primitiveGeometry.js +30 -0
  30. package/dist/miscellaneous/asserts.d.ts +1 -18
  31. package/dist/miscellaneous/asserts.js +21 -28
  32. package/dist/miscellaneous/fxMath.d.ts +26 -0
  33. package/dist/miscellaneous/fxMath.js +261 -0
  34. package/dist/miscellaneous/math.d.ts +2 -7
  35. package/dist/miscellaneous/math.js +8 -9
  36. package/dist/miscellaneous/texture/fxDataTexture.d.ts +26 -0
  37. package/dist/miscellaneous/texture/fxDataTexture.js +20 -0
  38. package/dist/render/FXArtifactMaterial.Internal.d.ts +16 -0
  39. package/dist/render/FXArtifactMaterial.Internal.js +944 -0
  40. package/dist/render/FXArtifactMaterial.d.ts +1 -0
  41. package/dist/render/FXArtifactMaterial.js +79 -0
  42. package/dist/render/FXMaterialOptions.d.ts +27 -0
  43. package/dist/render/FXMaterialOptions.js +5 -0
  44. package/dist/render/FXMeshMaterial.d.ts +40 -0
  45. package/dist/render/FXMeshMaterial.js +77 -0
  46. package/dist/world/FXWorld.d.ts +42 -0
  47. package/dist/world/FXWorld.js +103 -0
  48. package/package.json +45 -29
  49. package/dist/_virtual/2d.js +0 -3
  50. package/dist/_virtual/3d.js +0 -3
  51. package/dist/_virtual/4d.js +0 -3
  52. package/dist/_virtual/mod.js +0 -5
  53. package/dist/_virtual/mod2.js +0 -3
  54. package/dist/emitter/behavior/FXBehavior.d.ts +0 -24
  55. package/dist/emitter/behavior/FXBehavior.js +0 -10
  56. package/dist/emitter/behavior/FXBehaviorDirectionalForce.d.ts +0 -23
  57. package/dist/emitter/behavior/FXBehaviorDirectionalForce.js +0 -72
  58. package/dist/emitter/behavior/FXBehaviorPointForce.d.ts +0 -36
  59. package/dist/emitter/behavior/FXBehaviorPointForce.js +0 -94
  60. package/dist/emitter/behavior/FXBehaviorScaleOverLife.d.ts +0 -24
  61. package/dist/emitter/behavior/FXBehaviorScaleOverLife.js +0 -55
  62. package/dist/emitter/behavior/FXBehaviorTorqueDamping.d.ts +0 -20
  63. package/dist/emitter/behavior/FXBehaviorTorqueDamping.js +0 -48
  64. package/dist/emitter/behavior/FXBehaviorTorqueNoise.d.ts +0 -25
  65. package/dist/emitter/behavior/FXBehaviorTorqueNoise.js +0 -67
  66. package/dist/emitter/behavior/FXBehaviorTorqueOverLife.d.ts +0 -19
  67. package/dist/emitter/behavior/FXBehaviorTorqueOverLife.js +0 -41
  68. package/dist/emitter/behavior/FXBehaviorVelocityDamping.d.ts +0 -20
  69. package/dist/emitter/behavior/FXBehaviorVelocityDamping.js +0 -50
  70. package/dist/emitter/behavior/FXBehaviorVelocityNoise.d.ts +0 -25
  71. package/dist/emitter/behavior/FXBehaviorVelocityNoise.js +0 -76
  72. package/dist/emitter/behavior/FXBehaviorVelocityOverLife.d.ts +0 -22
  73. package/dist/emitter/behavior/FXBehaviorVelocityOverLife.js +0 -55
  74. package/dist/emitter/spawn/FXSpawn.d.ts +0 -11
  75. package/dist/emitter/spawn/FXSpawn.js +0 -10
  76. package/dist/emitter/spawn/FXSpawnBox.d.ts +0 -18
  77. package/dist/emitter/spawn/FXSpawnBox.js +0 -50
  78. package/dist/emitter/spawn/FXSpawnLifetime.d.ts +0 -17
  79. package/dist/emitter/spawn/FXSpawnLifetime.js +0 -44
  80. package/dist/emitter/spawn/FXSpawnOffset.d.ts +0 -21
  81. package/dist/emitter/spawn/FXSpawnOffset.js +0 -49
  82. package/dist/emitter/spawn/FXSpawnPoint.d.ts +0 -18
  83. package/dist/emitter/spawn/FXSpawnPoint.js +0 -46
  84. package/dist/emitter/spawn/FXSpawnRotation.d.ts +0 -17
  85. package/dist/emitter/spawn/FXSpawnRotation.js +0 -42
  86. package/dist/emitter/spawn/FXSpawnScale.d.ts +0 -22
  87. package/dist/emitter/spawn/FXSpawnScale.js +0 -56
  88. package/dist/emitter/spawn/FXSpawnSphere.d.ts +0 -36
  89. package/dist/emitter/spawn/FXSpawnSphere.js +0 -87
  90. package/dist/emitter/spawn/FXSpawnTorque.d.ts +0 -17
  91. package/dist/emitter/spawn/FXSpawnTorque.js +0 -42
  92. package/dist/emitter/spawn/FXSpawnVelocity.d.ts +0 -29
  93. package/dist/emitter/spawn/FXSpawnVelocity.js +0 -91
  94. package/dist/instancedParticle/FXInstancedParticle.Internal.d.ts +0 -2
  95. package/dist/instancedParticle/shared.d.ts +0 -19
  96. package/dist/instancedParticle/shared.js +0 -116
  97. package/dist/materials/FXDiffuseMaterial/FXDiffuseMaterial.Internal.d.ts +0 -24
  98. package/dist/materials/FXDiffuseMaterial/FXDiffuseMaterial.Internal.js +0 -275
  99. package/dist/materials/FXDiffuseMaterial/FXDiffuseMaterial.d.ts +0 -75
  100. package/dist/materials/FXDiffuseMaterial/FXDiffuseMaterial.js +0 -63
  101. package/dist/materials/FXMaterial/FXBlending.Internal.d.ts +0 -24
  102. package/dist/materials/FXMaterial/FXBlending.Internal.js +0 -64
  103. package/dist/materials/FXMaterial/FXDepthMaterial.Internal.d.ts +0 -4
  104. package/dist/materials/FXMaterial/FXDepthMaterial.Internal.js +0 -144
  105. package/dist/materials/FXMaterial/FXDistanceMaterial.Internal.d.ts +0 -4
  106. package/dist/materials/FXMaterial/FXDistanceMaterial.Internal.js +0 -175
  107. package/dist/materials/FXMaterial/FXMaterial.d.ts +0 -70
  108. package/dist/materials/FXMaterial/FXMaterial.js +0 -51
  109. package/dist/materials/FXUnlitMaterial/FXUnlitMaterial.Internal.d.ts +0 -7
  110. package/dist/materials/FXUnlitMaterial/FXUnlitMaterial.Internal.js +0 -103
  111. package/dist/materials/FXUnlitMaterial/FXUnlitMaterial.d.ts +0 -7
  112. package/dist/materials/FXUnlitMaterial/FXUnlitMaterial.js +0 -21
  113. package/dist/miscellaneous/color/FXColor.Internal.d.ts +0 -7
  114. package/dist/miscellaneous/color/FXColor.Internal.js +0 -42
  115. package/dist/miscellaneous/color/FXColor.d.ts +0 -234
  116. package/dist/miscellaneous/color/FXColor.js +0 -636
  117. package/dist/miscellaneous/curve/FXCurve1D.Internal.d.ts +0 -20
  118. package/dist/miscellaneous/curve/FXCurve1D.d.ts +0 -17
  119. package/dist/miscellaneous/curve/FXCurve1D.js +0 -94
  120. package/dist/miscellaneous/miscellaneous.d.ts +0 -34
  121. package/dist/miscellaneous/miscellaneous.js +0 -85
  122. package/dist/miscellaneous/texture/FXTextureView.Internal.d.ts +0 -49
  123. package/dist/miscellaneous/texture/FXTextureView.Internal.js +0 -6
  124. package/dist/miscellaneous/texture/FXTextureView.d.ts +0 -72
  125. package/dist/miscellaneous/texture/FXTextureView.js +0 -234
  126. package/dist/miscellaneous/webglCapabilities.d.ts +0 -1
  127. package/dist/miscellaneous/webglCapabilities.js +0 -45
  128. package/dist/node_modules/fast-simplex-noise/lib/2d.js +0 -93
  129. package/dist/node_modules/fast-simplex-noise/lib/3d.js +0 -142
  130. package/dist/node_modules/fast-simplex-noise/lib/4d.js +0 -196
  131. package/dist/node_modules/fast-simplex-noise/lib/mod.js +0 -24
  132. package/dist/nodes/FXNode.d.ts +0 -5
  133. package/dist/nodes/FXNode.js +0 -10
  134. package/dist/nodes/blending/FXNodeBlending.d.ts +0 -6
  135. package/dist/nodes/blending/FXNodeBlending.js +0 -9
  136. package/dist/nodes/blending/FXNodeLightnessBlendingMask.d.ts +0 -19
  137. package/dist/nodes/blending/FXNodeLightnessBlendingMask.js +0 -65
  138. package/dist/nodes/color/FXNodeColor.d.ts +0 -6
  139. package/dist/nodes/color/FXNodeColor.js +0 -9
  140. package/dist/nodes/color/FXNodeColorOverLife.d.ts +0 -22
  141. package/dist/nodes/color/FXNodeColorOverLife.js +0 -64
  142. package/dist/nodes/color/FXNodeSphericalClip.d.ts +0 -21
  143. package/dist/nodes/color/FXNodeSphericalClip.js +0 -54
  144. package/dist/nodes/normal/FXNodeFlatNormal.d.ts +0 -6
  145. package/dist/nodes/normal/FXNodeFlatNormal.js +0 -16
  146. package/dist/nodes/normal/FXNodeNormal.d.ts +0 -6
  147. package/dist/nodes/normal/FXNodeNormal.js +0 -9
  148. package/dist/nodes/normal/FXNodeSphericalNormal.d.ts +0 -6
  149. package/dist/nodes/normal/FXNodeSphericalNormal.js +0 -23
  150. package/dist/nodes/texture/FXNodeAnimatedTexture.d.ts +0 -52
  151. package/dist/nodes/texture/FXNodeAnimatedTexture.js +0 -151
  152. package/dist/nodes/texture/FXNodeStaticTexture.d.ts +0 -20
  153. package/dist/nodes/texture/FXNodeStaticTexture.js +0 -67
  154. package/dist/nodes/texture/FXNodeTexture.d.ts +0 -6
  155. package/dist/nodes/texture/FXNodeTexture.js +0 -9
package/README.md CHANGED
@@ -1,85 +1,133 @@
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.
7
-
8
- ## Editor
9
-
10
- Interactive visual **[EDITOR](https://jango-git.github.io/sparcoon/)**
11
-
12
- ![Editor](assets/editor.jpg)
13
-
14
- ## API
15
-
16
- - **FXEmitter** extends `Object3D`. Manages emission, lifetime, pooling, rendering. One draw call per emitter. Optional back-to-front sorting.
17
- - **Spawn modules** set initial particle state: position (box/sphere/point), velocity (cone), rotation, scale, lifetime. Numeric params accept ranges.
18
- - **Behavior modules** update particles per frame: directional/point force, velocity/torque damping, simplex noise, scale-over-life, velocity/torque over lifetime.
19
- - **Materials**: `FXUnlitMaterial` (MeshBasicMaterial), `FXDiffuseMaterial` (MeshLambertMaterial + optional subsurface scatter). Shadow support.
20
- - **Nodes** are composable material inputs: color-over-life, static/animated textures, normals (flat/spherical/map).
21
-
22
- ## Example
23
-
24
- Smoke volume with animated sprite sheet, subsurface scattering, and shadow receiving:
25
-
26
- ```typescript
27
- const RATE = 10;
28
- const LIFETIME = 6;
29
-
30
- const smoke = new FXEmitter(
31
- [
32
- new FXSpawnBox(areaMin, areaMax),
33
- new FXSpawnLifetime({ min: LIFETIME * 0.5, max: LIFETIME }),
34
- new FXSpawnScale({ min: 8, max: 16 }),
35
- new FXSpawnRotation(),
36
- ],
37
- [],
38
- new FXDiffuseMaterial({
39
- enableScatter: true,
40
- scatterPower: 10,
41
- forwardScatterStrength: 1,
42
- backScatterStrength: 0.25,
43
- shadowSensitivity: 0.25,
44
- premultipliedAlpha: true,
45
- albedoNodes: [
46
- new FXNodeAnimatedTexture({
47
- texture: smokeAtlas,
48
- rows: 8,
49
- columns: 8,
50
- interpolate: true,
51
- }),
52
- new FXNodeColorOverLife([
53
- new FXColor(0xffffff, 0),
54
- new FXColor(0xffffff, 0.4),
55
- new FXColor(0xffffff, 0.4),
56
- new FXColor(0xffffff, 0),
57
- ]),
58
- ],
59
- }),
60
- {
61
- expectedCapacity: Math.ceil(LIFETIME * RATE) + 1,
62
- receiveShadow: true,
63
- sortCamera: camera,
64
- sortFraction: 0.25,
65
- },
66
- );
67
-
68
- scene.add(smoke);
69
- smoke.play(RATE);
70
-
71
- // render loop
72
- FXEmitter.onWillRender(delta);
73
- ```
1
+ # Sparcoon
2
+
3
+ Three.js runtime for VFX authored in the Sparcoon Editor.
4
+
5
+ `sparcoon` plays back particle effects designed in the Sparcoon Editor: the
6
+ editor compiles a node graph into a plain ES module of precompiled artifacts,
7
+ and this thin runtime loads and runs it -- instanced-particle rendering in one
8
+ draw call plus a small timeline player. No graph, compiler, or live protocol
9
+ ships to your app.
10
+
11
+ > Editor: link coming soon.
12
+
13
+ ## Features
14
+
15
+ - Instanced billboard and 3D-mesh particles in a single draw call.
16
+ - Per-particle simulation from a precompiled kernel -- no `eval`, no codegen.
17
+ - Whole-project playback: an editor module subclasses `FXEffect`.
18
+ - One `FXWorld.update` per frame drives every effect.
19
+ - Optional depth sorting, shadows, and custom-geometry particles.
20
+ - Tree-shakeable ESM with full TypeScript types.
74
21
 
75
22
  ## Install
76
23
 
24
+ ```sh
25
+ npm install sparcoon three
26
+ ```
27
+
28
+ `three` (>= 0.157) is a peer dependency.
29
+
30
+ ## Quick start
31
+
32
+ An editor project exports an `FXEffect` subclass plus a typed map of the
33
+ textures it needs. Construct it (it joins the default world automatically), add
34
+ it to your scene, and call `FXWorld.update(deltaTime)` once per frame.
35
+
36
+ ```ts
37
+ import { TextureLoader } from "three";
38
+ import { FXWorld } from "sparcoon";
39
+ import { VFXDemo0 } from "./effects/VFXDemo_0"; // emitted by the editor
40
+
41
+ const loader = new TextureLoader();
42
+ const assets = {
43
+ heart: loader.load("heart.png"),
44
+ fire: loader.load("fire.png"),
45
+ };
46
+ const effect = new VFXDemo0(assets);
47
+
48
+ scene.add(effect); // FXEffect is a THREE.Group
49
+ effect.play();
50
+
51
+ function frame(deltaTime: number): void {
52
+ FXWorld.update(deltaTime); // advances every effect's timeline and its particles
53
+ renderer.render(scene, camera);
54
+ }
55
+ ```
56
+
57
+ The effect is an `Object3D` -- position, rotate, and scale it like any other.
58
+ Call `effect.dispose()` to remove it, free its GPU resources, and unsubscribe it
59
+ from the world.
60
+
61
+ ### Live parameters
62
+
63
+ Channels the editor marked "live" are driven from your code, not the timeline:
64
+
65
+ ```ts
66
+ effect.setEmitterParam("Hearts", "spawnRate", 64);
67
+ effect.setMeshParam("Mesh", "glow", [1, 0.4, 0.1]);
68
+
69
+ effect.getEmitter("Hearts"); // the underlying emitter (Object3D)
70
+ effect.getMesh("Mesh"); // the underlying THREE.Mesh
77
71
  ```
78
- npm install sparcoon
72
+
73
+ ### Many effects, and separate worlds
74
+
75
+ Every effect joins the default world on construction, so any number of them are
76
+ driven by the same single `FXWorld.update(deltaTime)` -- there is nothing extra
77
+ to wire up per effect.
78
+
79
+ A world is a tick and clock domain (not a scene graph). Create your own
80
+ `FXWorld` when you want an independent clock -- an isolated time scale, or a
81
+ group you pause on its own -- and pass it to the effect:
82
+
83
+ ```ts
84
+ import { FXWorld } from "sparcoon";
85
+
86
+ const slowmo = new FXWorld();
87
+ const boss = new VFXDemo0(assets, { world: slowmo });
88
+ scene.add(boss); // still added to your scene yourself
89
+
90
+ function frame(deltaTime: number): void {
91
+ FXWorld.update(deltaTime); // the default world
92
+ slowmo.update(deltaTime * 0.3); // its own, slower clock
93
+ renderer.render(scene, camera);
94
+ }
79
95
  ```
80
96
 
81
- Peer dep: `three` >=0.157.0 <0.180.0
97
+ Within one world every effect shares the clock and one delta per frame, so
98
+ `effect.stop()` clears rather than freezes -- to freeze one effect on its own,
99
+ give it its own world. `world.dispose()` disposes every effect in it.
100
+
101
+ ## How it works
102
+
103
+ The **Sparcoon Editor** compiles a node graph into a plain ES module exporting
104
+ a render artifact (GLSL, uniforms, textures) and a behavior artifact (authored
105
+ spawn/update functions). This runtime executes them -- your bundler compiles
106
+ the authored functions like any other source, so nothing is evaluated at
107
+ runtime. The editor/runtime boundary is a frozen ABI: two core per-particle
108
+ buffers, `position` and `lifecycle` (`[age, lifetime]`).
109
+
110
+ ## Requirements
111
+
112
+ - **A bundler** (Vite, webpack, Rollup, esbuild). `sparcoon` ships as
113
+ unbundled, unminified ES2020 modules plus types; your bundler tree-shakes and
114
+ minifies them, the same way three.js is consumed. Not a drop-in `<script>`.
115
+ - **`three` >= 0.157**, as a peer dependency.
116
+ - **Old devices.** Output targets ES2020; to run on pre-2015 hardware (for
117
+ example a first-generation iPad Pro), downlevel it in your own build
118
+ (Babel / SWC / your bundler's `target`). `sparcoon` leaves that final step
119
+ to you.
120
+
121
+ ## API overview
122
+
123
+ - `FXEffect` -- whole-project timeline player (a `Group`); emitted modules
124
+ subclass it. `play` / `stop`, `setEmitterParam` / `setMeshParam`,
125
+ `getEmitter` / `getMesh`, `dispose`.
126
+ - `FXWorld` -- the tick domain. `FXWorld.update(deltaTime)` per frame drives the
127
+ default world; `new FXWorld()` is an isolated clock and pool.
128
+ - Artifact contract types, the `FX_*` core-layout constants, CPU math helpers
129
+ (`fxMix`, `fxSnoise3`, ...), and `fxDataTexture`.
82
130
 
83
131
  ## License
84
132
 
85
- MIT
133
+ MIT (c) jango
@@ -0,0 +1,120 @@
1
+ import type { Texture } from "three";
2
+ import type { FXGraphLambertMaterialOptions, FXGraphUnlitMaterialOptions } from "../render/FXMaterialOptions.js";
3
+ /**
4
+ * Frozen editor<->runtime boundary. The editor compiles a graph into a plain ESM module that
5
+ * exports an {@link FXRenderArtifact} (GLSL + uniforms + textures) and an {@link FXBehaviorArtifact}
6
+ * (authored spawn/update functions + bindings + buffer layout); the runtime only executes them.
7
+ * Runtime-owned and exported; no code generation, no eval (the app's bundler compiles the authored
8
+ * functions). Every value scrub must no-op on an unknown name so a name drift never corrupts values.
9
+ */
10
+ /** One state buffer: name + per-particle stride in floats. */
11
+ export interface FXBufferLayout {
12
+ readonly name: string;
13
+ readonly stride: number;
14
+ }
15
+ /** A user attribute declaration (width in components). */
16
+ export interface FXAttributeDecl {
17
+ readonly name: string;
18
+ readonly components: 1 | 2 | 3 | 4;
19
+ }
20
+ /** Named set of state buffers handed to a kernel (core + attribute buffers). */
21
+ export type FXKernelBuffers = Readonly<Record<string, Float32Array>>;
22
+ /**
23
+ * Emitter world transform passed to spawn/update: `matrixWorld` as a column-major mat4 (16 floats),
24
+ * read through the shared `world-matrix` node; `velocity`/`angularVelocity` are the emitter's
25
+ * world-space linear/angular velocity (units and radians per second), read through the
26
+ * `object-velocity`/`object-angular-velocity` nodes. The whole argument is optional - a graph
27
+ * reading none of these never touches it - but each field, once passed, is always fully populated.
28
+ */
29
+ export interface FXEmitterTransform {
30
+ readonly worldMatrix: readonly number[];
31
+ readonly velocity: readonly number[];
32
+ readonly angularVelocity: readonly number[];
33
+ }
34
+ /** A live value slot (uniform/binding): the runtime mutates `value` in place. */
35
+ export interface FXValueSlot<T> {
36
+ value: T;
37
+ }
38
+ /**
39
+ * Built-in primitive instanced per particle. The vertex program is geometry-agnostic, so `"plane"`
40
+ * + a camera-facing `particleTransform` is the classic billboard while `"box"`/`"sphere"` are true
41
+ * 3D mesh particles. Absent => `"plane"`.
42
+ */
43
+ export type FXGeometryPrimitive = "plane" | "box" | "sphere";
44
+ /**
45
+ * The geometry an emitter/mesh instances: a built-in primitive, or an app-supplied `BufferGeometry`
46
+ * bound by name (a custom mesh baked in the editor's content library) - the same external-slot
47
+ * duality as {@link FXUniformInit}'s `sampler2D` case. Absent `FXRenderArtifact.geometry` => `"plane"`.
48
+ */
49
+ export type FXGeometrySource = {
50
+ readonly type: "primitive";
51
+ readonly primitive: FXGeometryPrimitive;
52
+ } | {
53
+ readonly type: "custom";
54
+ readonly external: string;
55
+ };
56
+ /** A uniform's serialized initial value. */
57
+ export type FXUniformInit = {
58
+ readonly type: string;
59
+ value: number | readonly number[];
60
+ } | {
61
+ readonly type: "sampler2D";
62
+ value: Texture;
63
+ } | {
64
+ readonly type: "sampler2D";
65
+ readonly external: string;
66
+ value?: Texture;
67
+ };
68
+ /** One shader stage's spliceable source (matches the compiler's per-stage IR shape). */
69
+ export interface FXShaderStageSource {
70
+ readonly varyingDeclarations: readonly string[];
71
+ readonly helperFunctions: readonly string[];
72
+ readonly body: readonly string[];
73
+ }
74
+ /** Render half of an emitted effect: compiled GLSL spliced into a Three material, plus live uniforms. */
75
+ export interface FXRenderArtifact {
76
+ /**
77
+ * Lighting capability: the sorted, deduplicated set of `fx_`-ABI shade intrinsics the graph's
78
+ * lighting nodes call (e.g. `["fxLambertShade"]`), derived from the graph (not authored). Empty =>
79
+ * unlit: the runtime builds the flat material. Non-empty => the runtime lights up Three's light
80
+ * infrastructure and defines exactly these functions. The shaded color is produced inside the graph
81
+ * (the `albedo` output already carries it), so there is no per-model material dispatch.
82
+ */
83
+ readonly lightingIntrinsics: readonly string[];
84
+ /** Absent => `"plane"`. */
85
+ readonly geometry?: FXGeometrySource;
86
+ readonly options?: FXGraphUnlitMaterialOptions | FXGraphLambertMaterialOptions;
87
+ readonly uniformDeclarations: readonly string[];
88
+ readonly vertex: FXShaderStageSource;
89
+ readonly fragment: FXShaderStageSource;
90
+ /**
91
+ * slot -> GLSL expression: `albedo` (required; already carries the shaded color when lit), the
92
+ * optional fragment-stage compositing slots (`additivity` / `alphaThreshold`), and the optional
93
+ * vertex-stage `particleTransform` / `vertexTransform` (mat4). Shading normal and emission are node
94
+ * inputs now, not surface slots.
95
+ */
96
+ readonly outputs: Readonly<Record<string, string>>;
97
+ readonly uniforms: Record<string, FXUniformInit>;
98
+ /** Attributes the shader reads (`p_fx_<name>`); the emitter merges these with the sim's write set. */
99
+ readonly attributeReads: readonly FXAttributeDecl[];
100
+ }
101
+ /**
102
+ * Behavior half of an emitted effect: authored spawn/update functions mutating the packed state
103
+ * buffers, plus buffer layout and live bindings. Math helpers are inlined, so the artifact carries
104
+ * no runtime dependency. Attribute offsets are literals derived from {@link buffers}; only the core
105
+ * position/lifecycle offsets are the fixed ABI (`coreLayout.ts`).
106
+ */
107
+ export interface FXBehaviorArtifact {
108
+ readonly buffers: readonly FXBufferLayout[];
109
+ readonly attributeWrites: readonly FXAttributeDecl[];
110
+ /** number, or Float32Array (a curve LUT). */
111
+ readonly bindings: Record<string, FXValueSlot<number | Float32Array>>;
112
+ /** Buffers `spawn` writes; the host marks only these `needsUpdate`. */
113
+ readonly spawnWrittenBuffers?: readonly string[];
114
+ /** Buffers `update` writes; the host marks only these `needsUpdate`. */
115
+ readonly updateWrittenBuffers: readonly string[];
116
+ /** Seeds newborns `[start, start+count)`. Optional (an update-only, non-particle host). */
117
+ spawn?(buffers: FXKernelBuffers, start: number, count: number, bindings: Record<string, FXValueSlot<number | Float32Array>>, emitter?: FXEmitterTransform): void;
118
+ /** Advances all live particles `[0, count)`. */
119
+ update(buffers: FXKernelBuffers, count: number, deltaTime: number, bindings: Record<string, FXValueSlot<number | Float32Array>>, emitter?: FXEmitterTransform): void;
120
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Frozen per-particle ABI shared by the runtime loop and every editor-emitted module.
3
+ * Two GPU-mirrored core buffers only: `position` (vec3) and `lifecycle` (vec2 [age, lifetime]);
4
+ * a particle is dead once `age >= lifetime`. Everything else (velocity, scale, ...) is an ordinary
5
+ * node-declared `a_fx_<name>` attribute, not core.
6
+ *
7
+ * Split into two buffers because a Three instanced attribute is at most a vec4 - a 5-float core
8
+ * cannot be one attribute, whereas vec3 + vec2 are two clean varyings. Dependency-free (the editor's
9
+ * module emitter imports this too), so keep it three-free and portable.
10
+ */
11
+ export declare const FX_CORE_POSITION = "position";
12
+ export declare const FX_CORE_LIFECYCLE = "lifecycle";
13
+ export declare const FX_CORE_POSITION_STRIDE = 3;
14
+ export declare const FX_CORE_LIFECYCLE_STRIDE = 2;
15
+ export declare const FX_POSITION_X = 0;
16
+ export declare const FX_POSITION_Y = 1;
17
+ export declare const FX_POSITION_Z = 2;
18
+ export declare const FX_AGE = 0;
19
+ export declare const FX_LIFETIME = 1;
20
+ export declare const FX_CORE_POSITION_VARYING = "p_position";
21
+ export declare const FX_CORE_LIFECYCLE_VARYING = "p_lifecycle";
22
+ export declare const FX_CORE_PARTICLE_DEFINES = "\n #define PARTICLE_POSITION_X p_position.x\n #define PARTICLE_POSITION_Y p_position.y\n #define PARTICLE_POSITION_Z p_position.z\n #define PARTICLE_POSITION p_position\n\n #define PARTICLE_AGE p_lifecycle.x\n #define PARTICLE_LIFETIME p_lifecycle.y\n";
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Frozen per-particle ABI shared by the runtime loop and every editor-emitted module.
3
+ * Two GPU-mirrored core buffers only: `position` (vec3) and `lifecycle` (vec2 [age, lifetime]);
4
+ * a particle is dead once `age >= lifetime`. Everything else (velocity, scale, ...) is an ordinary
5
+ * node-declared `a_fx_<name>` attribute, not core.
6
+ *
7
+ * Split into two buffers because a Three instanced attribute is at most a vec4 - a 5-float core
8
+ * cannot be one attribute, whereas vec3 + vec2 are two clean varyings. Dependency-free (the editor's
9
+ * module emitter imports this too), so keep it three-free and portable.
10
+ */
11
+ export const FX_CORE_POSITION = "position";
12
+ export const FX_CORE_LIFECYCLE = "lifecycle";
13
+ export const FX_CORE_POSITION_STRIDE = 3;
14
+ export const FX_CORE_LIFECYCLE_STRIDE = 2;
15
+ export const FX_POSITION_X = 0;
16
+ export const FX_POSITION_Y = 1;
17
+ export const FX_POSITION_Z = 2;
18
+ export const FX_AGE = 0;
19
+ export const FX_LIFETIME = 1;
20
+ export const FX_CORE_POSITION_VARYING = "p_position";
21
+ export const FX_CORE_LIFECYCLE_VARYING = "p_lifecycle";
22
+ // Stable macro names so emitted render GLSL reads PARTICLE_POSITION_X / PARTICLE_AGE etc. instead
23
+ // of hardcoding the varying names this module owns.
24
+ export const FX_CORE_PARTICLE_DEFINES = `
25
+ #define PARTICLE_POSITION_X ${FX_CORE_POSITION_VARYING}.x
26
+ #define PARTICLE_POSITION_Y ${FX_CORE_POSITION_VARYING}.y
27
+ #define PARTICLE_POSITION_Z ${FX_CORE_POSITION_VARYING}.z
28
+ #define PARTICLE_POSITION ${FX_CORE_POSITION_VARYING}
29
+
30
+ #define PARTICLE_AGE ${FX_CORE_LIFECYCLE_VARYING}.x
31
+ #define PARTICLE_LIFETIME ${FX_CORE_LIFECYCLE_VARYING}.y
32
+ `;
@@ -0,0 +1,77 @@
1
+ import { Group, Mesh, type BufferGeometry, type Texture } from "three";
2
+ import { FXEmitter } from "../emitter/FXEmitter.js";
3
+ import type { FXEffectOptions, FXEffectSpec } from "./FXEffectSpec.js";
4
+ /** One asset slot: an external texture OR (per {@link FXGeometrySource}) an external geometry, keyed by the same flat `assets` namespace. */
5
+ type FXEffectAsset = Texture | BufferGeometry;
6
+ /**
7
+ * Runtime for a whole exported Sparcoon project: a `Group` that builds every emitter + VFX mesh
8
+ * from precompiled artifacts and drives the shared timeline. Add it to your scene, call `play()`,
9
+ * and `update(deltaSeconds)` each frame. Lit graphs read the scene's lights, so a lit effect needs
10
+ * a light probe + directional light in the host scene (studio lighting is not part of the export).
11
+ * An editor-emitted module supplies only the data (`FXEffectSpec` + a scene name) and a thin named
12
+ * subclass; this base class is identical for every export.
13
+ *
14
+ * The timeline drive mirrors the editor's own transport: burst/play events fire across the
15
+ * continuous swept interval, while value + transform tracks are frame-stepped onto the `fps` grid.
16
+ */
17
+ export declare abstract class FXEffect extends Group {
18
+ private readonly specification;
19
+ private readonly world;
20
+ private readonly emitters;
21
+ private readonly meshMaterials;
22
+ private readonly meshes;
23
+ private readonly meshMotionTrackers;
24
+ private readonly emitterDriven;
25
+ private readonly meshDriven;
26
+ private readonly emitterIndexByName;
27
+ private readonly meshIndexByName;
28
+ private time;
29
+ private lastTime;
30
+ private lastFrame;
31
+ private meshClock;
32
+ private playing;
33
+ private disposed;
34
+ private readonly meshPositionScratch;
35
+ private readonly meshQuaternionScratch;
36
+ private readonly meshScaleScratch;
37
+ protected constructor(specification: FXEffectSpec, assets: Record<string, FXEffectAsset>, options?: FXEffectOptions);
38
+ /** Restarts the timeline from the top: clears live particles and begins playback. */
39
+ play(): void;
40
+ /** Halts playback and clears live particles, returning to the authored frame-0 state. */
41
+ stop(): void;
42
+ /**
43
+ * The runtime emitter named `name` (its `FXEmitter`, an `Object3D`) - for a live channel the
44
+ * editor marked "fake" (see {@link FXEffectSpec}), pose it directly through its own
45
+ * `position`/`quaternion`/`scale`; `driveFrame` never touches that channel again. `undefined`
46
+ * for an unknown name or once disposed (matching {@link dispose}'s emptied index).
47
+ */
48
+ getEmitter(name: string): FXEmitter | undefined;
49
+ /** The runtime mesh named `name` (a `three.Mesh`), for the same live-channel posing as {@link getEmitter}. */
50
+ getMesh(name: string): Mesh | undefined;
51
+ /**
52
+ * Pushes `value` into emitter `name`'s Timeline Value `parameter` (a uniform/binding slot the
53
+ * editor marked "fake", so it is never sampled from baked keyframes). No-op for an unknown
54
+ * emitter, or once disposed - the underlying slot writer already no-ops for an unknown parameter.
55
+ */
56
+ setEmitterParam(name: string, parameter: string, value: number | readonly number[]): void;
57
+ /** Pushes `value` into mesh `name`'s Timeline Value `parameter`. See {@link setEmitterParam}. */
58
+ setMeshParam(name: string, parameter: string, value: number | readonly number[]): void;
59
+ /**
60
+ * Unsubscribes from its world, destroys every emitter, and disposes mesh resources; the group
61
+ * empties itself from its parent. Idempotent.
62
+ */
63
+ dispose(): void;
64
+ private resetTransport;
65
+ private advance;
66
+ /** Fires burst/play events whose time lies in `[from, to)` on their owning emitter. */
67
+ private fire;
68
+ /**
69
+ * Poses every entity and scrubs its sampled parameter values into the runtime at time `time`. The
70
+ * root itself is never re-posed here (see {@link FXEffectSpec.transformTracks}) - only
71
+ * emitters/meshes, and only their non-live channels/parameters.
72
+ */
73
+ private driveFrame;
74
+ private driveEmitterValues;
75
+ private driveMeshValues;
76
+ }
77
+ export {};