sparcoon 0.7.2 → 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.
- package/README.md +124 -76
- package/dist/artifact/FXArtifact.d.ts +120 -0
- package/dist/artifact/FXArtifact.js +1 -0
- package/dist/coreLayout.d.ts +22 -0
- package/dist/coreLayout.js +32 -0
- package/dist/effect/FXEffect.d.ts +77 -0
- package/dist/effect/FXEffect.js +454 -0
- package/dist/effect/FXEffectSampling.Internal.d.ts +24 -0
- package/dist/effect/FXEffectSampling.Internal.js +165 -0
- package/dist/effect/FXEffectSpec.d.ts +115 -0
- package/dist/effect/FXEffectSpec.js +1 -0
- package/dist/emitter/FXEmitter.Internal.d.ts +23 -58
- package/dist/emitter/FXEmitter.Internal.js +13 -29
- package/dist/emitter/FXEmitter.d.ts +46 -48
- package/dist/emitter/FXEmitter.js +355 -186
- package/dist/emitter/FXFromArtifactsOptions.d.ts +13 -0
- package/dist/emitter/FXFromArtifactsOptions.js +1 -0
- package/dist/emitter/FXObjectMotion.Internal.d.ts +33 -0
- package/dist/emitter/FXObjectMotion.Internal.js +87 -0
- package/dist/emitter/FXSimulationHolder.d.ts +1 -0
- package/dist/emitter/FXSimulationHolder.js +43 -0
- package/dist/index.d.ts +10 -39
- package/dist/index.js +13 -36
- package/dist/instancedParticle/FXInstancedParticle.d.ts +1 -22
- package/dist/instancedParticle/FXInstancedParticle.js +145 -85
- package/dist/instancedParticle/glTypeInfo.d.ts +5 -0
- package/dist/instancedParticle/glTypeInfo.js +3 -0
- package/dist/instancedParticle/primitiveGeometry.d.ts +1 -0
- package/dist/instancedParticle/primitiveGeometry.js +30 -0
- package/dist/miscellaneous/asserts.d.ts +1 -18
- package/dist/miscellaneous/asserts.js +21 -28
- package/dist/miscellaneous/fxMath.d.ts +26 -0
- package/dist/miscellaneous/fxMath.js +261 -0
- package/dist/miscellaneous/math.d.ts +2 -7
- package/dist/miscellaneous/math.js +8 -9
- package/dist/miscellaneous/texture/fxDataTexture.d.ts +26 -0
- package/dist/miscellaneous/texture/fxDataTexture.js +20 -0
- package/dist/render/FXArtifactMaterial.Internal.d.ts +16 -0
- package/dist/render/FXArtifactMaterial.Internal.js +944 -0
- package/dist/render/FXArtifactMaterial.d.ts +1 -0
- package/dist/render/FXArtifactMaterial.js +79 -0
- package/dist/render/FXMaterialOptions.d.ts +27 -0
- package/dist/render/FXMaterialOptions.js +5 -0
- package/dist/render/FXMeshMaterial.d.ts +40 -0
- package/dist/render/FXMeshMaterial.js +77 -0
- package/dist/world/FXWorld.d.ts +42 -0
- package/dist/world/FXWorld.js +103 -0
- package/package.json +45 -29
- package/dist/_virtual/2d.js +0 -3
- package/dist/_virtual/3d.js +0 -3
- package/dist/_virtual/4d.js +0 -3
- package/dist/_virtual/mod.js +0 -5
- package/dist/_virtual/mod2.js +0 -3
- package/dist/emitter/behavior/FXBehavior.d.ts +0 -24
- package/dist/emitter/behavior/FXBehavior.js +0 -10
- package/dist/emitter/behavior/FXBehaviorDirectionalForce.d.ts +0 -23
- package/dist/emitter/behavior/FXBehaviorDirectionalForce.js +0 -72
- package/dist/emitter/behavior/FXBehaviorPointForce.d.ts +0 -36
- package/dist/emitter/behavior/FXBehaviorPointForce.js +0 -94
- package/dist/emitter/behavior/FXBehaviorScaleOverLife.d.ts +0 -24
- package/dist/emitter/behavior/FXBehaviorScaleOverLife.js +0 -55
- package/dist/emitter/behavior/FXBehaviorTorqueDamping.d.ts +0 -20
- package/dist/emitter/behavior/FXBehaviorTorqueDamping.js +0 -48
- package/dist/emitter/behavior/FXBehaviorTorqueNoise.d.ts +0 -25
- package/dist/emitter/behavior/FXBehaviorTorqueNoise.js +0 -67
- package/dist/emitter/behavior/FXBehaviorTorqueOverLife.d.ts +0 -19
- package/dist/emitter/behavior/FXBehaviorTorqueOverLife.js +0 -41
- package/dist/emitter/behavior/FXBehaviorVelocityDamping.d.ts +0 -20
- package/dist/emitter/behavior/FXBehaviorVelocityDamping.js +0 -50
- package/dist/emitter/behavior/FXBehaviorVelocityNoise.d.ts +0 -25
- package/dist/emitter/behavior/FXBehaviorVelocityNoise.js +0 -76
- package/dist/emitter/behavior/FXBehaviorVelocityOverLife.d.ts +0 -22
- package/dist/emitter/behavior/FXBehaviorVelocityOverLife.js +0 -55
- package/dist/emitter/spawn/FXSpawn.d.ts +0 -11
- package/dist/emitter/spawn/FXSpawn.js +0 -10
- package/dist/emitter/spawn/FXSpawnBox.d.ts +0 -18
- package/dist/emitter/spawn/FXSpawnBox.js +0 -50
- package/dist/emitter/spawn/FXSpawnLifetime.d.ts +0 -17
- package/dist/emitter/spawn/FXSpawnLifetime.js +0 -44
- package/dist/emitter/spawn/FXSpawnOffset.d.ts +0 -21
- package/dist/emitter/spawn/FXSpawnOffset.js +0 -49
- package/dist/emitter/spawn/FXSpawnPoint.d.ts +0 -18
- package/dist/emitter/spawn/FXSpawnPoint.js +0 -46
- package/dist/emitter/spawn/FXSpawnRotation.d.ts +0 -17
- package/dist/emitter/spawn/FXSpawnRotation.js +0 -42
- package/dist/emitter/spawn/FXSpawnScale.d.ts +0 -22
- package/dist/emitter/spawn/FXSpawnScale.js +0 -56
- package/dist/emitter/spawn/FXSpawnSphere.d.ts +0 -36
- package/dist/emitter/spawn/FXSpawnSphere.js +0 -87
- package/dist/emitter/spawn/FXSpawnTorque.d.ts +0 -17
- package/dist/emitter/spawn/FXSpawnTorque.js +0 -42
- package/dist/emitter/spawn/FXSpawnVelocity.d.ts +0 -29
- package/dist/emitter/spawn/FXSpawnVelocity.js +0 -91
- package/dist/instancedParticle/FXInstancedParticle.Internal.d.ts +0 -2
- package/dist/instancedParticle/shared.d.ts +0 -19
- package/dist/instancedParticle/shared.js +0 -116
- package/dist/materials/FXDiffuseMaterial/FXDiffuseMaterial.Internal.d.ts +0 -24
- package/dist/materials/FXDiffuseMaterial/FXDiffuseMaterial.Internal.js +0 -275
- package/dist/materials/FXDiffuseMaterial/FXDiffuseMaterial.d.ts +0 -75
- package/dist/materials/FXDiffuseMaterial/FXDiffuseMaterial.js +0 -63
- package/dist/materials/FXMaterial/FXBlending.Internal.d.ts +0 -24
- package/dist/materials/FXMaterial/FXBlending.Internal.js +0 -64
- package/dist/materials/FXMaterial/FXDepthMaterial.Internal.d.ts +0 -4
- package/dist/materials/FXMaterial/FXDepthMaterial.Internal.js +0 -144
- package/dist/materials/FXMaterial/FXDistanceMaterial.Internal.d.ts +0 -4
- package/dist/materials/FXMaterial/FXDistanceMaterial.Internal.js +0 -175
- package/dist/materials/FXMaterial/FXMaterial.d.ts +0 -70
- package/dist/materials/FXMaterial/FXMaterial.js +0 -51
- package/dist/materials/FXUnlitMaterial/FXUnlitMaterial.Internal.d.ts +0 -7
- package/dist/materials/FXUnlitMaterial/FXUnlitMaterial.Internal.js +0 -103
- package/dist/materials/FXUnlitMaterial/FXUnlitMaterial.d.ts +0 -7
- package/dist/materials/FXUnlitMaterial/FXUnlitMaterial.js +0 -21
- package/dist/miscellaneous/color/FXColor.Internal.d.ts +0 -7
- package/dist/miscellaneous/color/FXColor.Internal.js +0 -42
- package/dist/miscellaneous/color/FXColor.d.ts +0 -234
- package/dist/miscellaneous/color/FXColor.js +0 -636
- package/dist/miscellaneous/curve/FXCurve1D.Internal.d.ts +0 -20
- package/dist/miscellaneous/curve/FXCurve1D.d.ts +0 -17
- package/dist/miscellaneous/curve/FXCurve1D.js +0 -94
- package/dist/miscellaneous/miscellaneous.d.ts +0 -34
- package/dist/miscellaneous/miscellaneous.js +0 -85
- package/dist/miscellaneous/texture/FXTextureView.Internal.d.ts +0 -49
- package/dist/miscellaneous/texture/FXTextureView.Internal.js +0 -6
- package/dist/miscellaneous/texture/FXTextureView.d.ts +0 -72
- package/dist/miscellaneous/texture/FXTextureView.js +0 -234
- package/dist/miscellaneous/webglCapabilities.d.ts +0 -1
- package/dist/miscellaneous/webglCapabilities.js +0 -45
- package/dist/node_modules/fast-simplex-noise/lib/2d.js +0 -93
- package/dist/node_modules/fast-simplex-noise/lib/3d.js +0 -142
- package/dist/node_modules/fast-simplex-noise/lib/4d.js +0 -196
- package/dist/node_modules/fast-simplex-noise/lib/mod.js +0 -24
- package/dist/nodes/FXNode.d.ts +0 -5
- package/dist/nodes/FXNode.js +0 -10
- package/dist/nodes/blending/FXNodeBlending.d.ts +0 -6
- package/dist/nodes/blending/FXNodeBlending.js +0 -9
- package/dist/nodes/blending/FXNodeLightnessBlendingMask.d.ts +0 -19
- package/dist/nodes/blending/FXNodeLightnessBlendingMask.js +0 -65
- package/dist/nodes/color/FXNodeColor.d.ts +0 -6
- package/dist/nodes/color/FXNodeColor.js +0 -9
- package/dist/nodes/color/FXNodeColorOverLife.d.ts +0 -22
- package/dist/nodes/color/FXNodeColorOverLife.js +0 -64
- package/dist/nodes/color/FXNodeSphericalClip.d.ts +0 -21
- package/dist/nodes/color/FXNodeSphericalClip.js +0 -54
- package/dist/nodes/normal/FXNodeFlatNormal.d.ts +0 -6
- package/dist/nodes/normal/FXNodeFlatNormal.js +0 -17
- package/dist/nodes/normal/FXNodeNormal.d.ts +0 -6
- package/dist/nodes/normal/FXNodeNormal.js +0 -9
- package/dist/nodes/normal/FXNodeSphericalNormal.d.ts +0 -6
- package/dist/nodes/normal/FXNodeSphericalNormal.js +0 -24
- package/dist/nodes/texture/FXNodeAnimatedTexture.d.ts +0 -52
- package/dist/nodes/texture/FXNodeAnimatedTexture.js +0 -151
- package/dist/nodes/texture/FXNodeStaticTexture.d.ts +0 -20
- package/dist/nodes/texture/FXNodeStaticTexture.js +0 -67
- package/dist/nodes/texture/FXNodeTexture.d.ts +0 -6
- package/dist/nodes/texture/FXNodeTexture.js +0 -9
package/README.md
CHANGED
|
@@ -1,85 +1,133 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {};
|