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
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,79 @@
1
+ import { buildArtifactDepthMaterial, buildArtifactMaterial } from "./FXArtifactMaterial.Internal.js";
2
+ /**
3
+ * @internal Runtime material driver: wraps a precompiled {@link FXRenderArtifact} and builds the
4
+ * concrete self-contained `ShaderMaterial` via {@link buildArtifactMaterial}, which picks the flat or
5
+ * lit variant from the artifact's lighting capability ({@link FXRenderArtifact.lightingIntrinsics}).
6
+ * The emitter owns one, calls {@link buildThreeMaterial} once, and pushes the shared clock each frame.
7
+ */
8
+ export class FXArtifactMaterial {
9
+ /**
10
+ * @param textures - App-supplied external textures by slot name. A missing one fails fast here
11
+ * rather than surfacing as a blank sampler at draw time.
12
+ */
13
+ constructor(artifact, textures = {}) {
14
+ this.artifact = artifact;
15
+ this.elapsedTime = { value: 0 };
16
+ this.deltaTime = { value: 0 };
17
+ this.velocity = { value: [0, 0, 0] };
18
+ this.angularVelocity = { value: [0, 0, 0] };
19
+ // Durable per-emitter slots for `external` texture uniforms. Unlike non-external uniforms (which
20
+ // bind the shared artifact slot directly), an external uniform resolves per emitter, so it gets
21
+ // its own slot here - and that slot is what gets bound, keeping it live-scrubbable.
22
+ this.externalSlots = {};
23
+ for (const name in artifact.uniforms) {
24
+ const uniform = artifact.uniforms[name];
25
+ if ("external" in uniform) {
26
+ if (!(uniform.external in textures)) {
27
+ throw new Error(`FXArtifactMaterial: no texture supplied for external slot "${uniform.external}" ` +
28
+ `(uniform "${name}"); pass it via FXEmitter.fromArtifacts({ textures })`);
29
+ }
30
+ this.externalSlots[name] = { value: textures[uniform.external] };
31
+ }
32
+ }
33
+ }
34
+ get attributeReads() {
35
+ return this.artifact.attributeReads;
36
+ }
37
+ buildThreeMaterial(varyings) {
38
+ return buildArtifactMaterial(this.artifact, varyings, this.elapsedTime, this.deltaTime, this.velocity, this.angularVelocity, this.externalSlots);
39
+ }
40
+ /**
41
+ * The `customDepthMaterial` for casting shadows: the same artifact IR spliced into a depth-packing
42
+ * material, so the shadow silhouette honors the authored shape/alpha-cutout (see
43
+ * {@link buildArtifactDepthMaterial}). Built only when the emitter opts into `castShadow`.
44
+ */
45
+ buildThreeDepthMaterial(varyings) {
46
+ return buildArtifactDepthMaterial(this.artifact, varyings, this.elapsedTime, this.deltaTime, this.velocity, this.angularVelocity, this.externalSlots);
47
+ }
48
+ setElapsedTime(seconds) {
49
+ this.elapsedTime.value = seconds;
50
+ }
51
+ setDeltaTime(seconds) {
52
+ this.deltaTime.value = seconds;
53
+ }
54
+ setObjectVelocity(velocity) {
55
+ this.velocity.value = velocity;
56
+ }
57
+ setObjectAngularVelocity(angularVelocity) {
58
+ this.angularVelocity.value = angularVelocity;
59
+ }
60
+ /**
61
+ * Render half of {@link FXEmitter.applyValues}. A non-external uniform mutates its shared artifact
62
+ * slot (module-scoped); an external uniform mutates this emitter's own slot. Unknown name = no-op.
63
+ */
64
+ applyUniformValues(values) {
65
+ for (const name in values) {
66
+ const value = values[name];
67
+ if (name in this.externalSlots) {
68
+ this.externalSlots[name].value = value;
69
+ }
70
+ else if (name in this.artifact.uniforms) {
71
+ this.artifact.uniforms[name].value =
72
+ value;
73
+ }
74
+ }
75
+ }
76
+ // No-op: the artifact owns no per-emitter resources. Generated textures are module-scoped and
77
+ // shared (see the `fromArtifacts` texture-ownership contract); external textures are app-owned.
78
+ destroy() { }
79
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Render options carried in `FXRenderArtifact.options`. Runtime-owned so the artifact contract has no
3
+ * editor dependency; the editor's graph material adapters import the same types.
4
+ */
5
+ /**
6
+ * How a particle fragment's alpha is composited into the framebuffer:
7
+ * - `blending` - alpha blending (transparent, no depth write). The only mode that honors the
8
+ * `additivity` output slot: 0 = normal "over", 1 = additive, blended continuously via the
9
+ * premultiplied-alpha channel (a single `NormalBlending`, no separate additive blend mode).
10
+ * - `alphaHash` - stochastic alpha hashing (opaque, depth-writing, dithered discard).
11
+ * - `alphaTest` - hard alpha cutout (opaque, depth-writing).
12
+ * - `opaque` - fully opaque (alpha forced to 1).
13
+ *
14
+ * The `alphaThreshold` output slot (hard discard cutoff, default 0.0075) applies in every mode except
15
+ * `opaque`; `additivity` applies only in `blending`.
16
+ */
17
+ export type FXRenderMode = "blending" | "alphaHash" | "alphaTest" | "opaque";
18
+ /** Unlit (flat albedo `ShaderMaterial`) particle material options. */
19
+ export interface FXGraphUnlitMaterialOptions {
20
+ /** @defaultValue `"blending"` */
21
+ renderMode?: FXRenderMode;
22
+ }
23
+ /** Lambert (lit `ShaderMaterial`, `lights`/`fog` from Three) particle material options. */
24
+ export interface FXGraphLambertMaterialOptions {
25
+ /** @defaultValue `"blending"` */
26
+ renderMode?: FXRenderMode;
27
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Render options carried in `FXRenderArtifact.options`. Runtime-owned so the artifact contract has no
3
+ * editor dependency; the editor's graph material adapters import the same types.
4
+ */
5
+ export {};
@@ -0,0 +1,40 @@
1
+ import type { Material, Texture } from "three";
2
+ import type { FXRenderArtifact } from "../artifact/FXArtifact.js";
3
+ /**
4
+ * Runtime material driver for a **VFX mesh**: the mesh twin of {@link FXArtifactMaterial}. Wraps a
5
+ * precompiled {@link FXRenderArtifact} (compiled against the mesh render target) and builds a
6
+ * self-contained `ShaderMaterial` for a single, non-instanced `THREE.Mesh`. Unlike the particle
7
+ * driver it takes no per-particle `varyings` - a VFX mesh has no simulation and no attributes. The
8
+ * host builds the material once and pushes the shared clock each frame.
9
+ */
10
+ export declare class FXMeshMaterial {
11
+ private readonly artifact;
12
+ private readonly elapsedTime;
13
+ private readonly deltaTime;
14
+ private readonly velocity;
15
+ private readonly angularVelocity;
16
+ private readonly externalSlots;
17
+ /**
18
+ * @param textures - App-supplied external textures by slot name. A missing one fails fast here
19
+ * rather than surfacing as a blank sampler at draw time.
20
+ */
21
+ constructor(artifact: FXRenderArtifact, textures?: Readonly<Record<string, Texture>>);
22
+ buildThreeMaterial(): Material;
23
+ /**
24
+ * The `customDepthMaterial` for casting shadows: the mesh twin of
25
+ * {@link FXArtifactMaterial.buildThreeDepthMaterial}. The mesh host attaches it on the `Mesh` only
26
+ * when the mesh opts into `castShadow`.
27
+ */
28
+ buildThreeDepthMaterial(): Material;
29
+ setElapsedTime(seconds: number): void;
30
+ setDeltaTime(seconds: number): void;
31
+ setObjectVelocity(velocity: readonly [number, number, number]): void;
32
+ setObjectAngularVelocity(angularVelocity: readonly [number, number, number]): void;
33
+ /**
34
+ * Applies a value edit (a rebind, no rebuild). A non-external uniform mutates its shared artifact
35
+ * slot; an external uniform mutates this mesh's own slot. Unknown name = no-op (an editor<->runtime
36
+ * name drift never corrupts values).
37
+ */
38
+ applyUniformValues(values: Readonly<Record<string, number | readonly number[] | Texture>>): void;
39
+ destroy(): void;
40
+ }
@@ -0,0 +1,77 @@
1
+ import { buildMeshArtifactDepthMaterial, buildMeshArtifactMaterial, } from "./FXArtifactMaterial.Internal.js";
2
+ /**
3
+ * Runtime material driver for a **VFX mesh**: the mesh twin of {@link FXArtifactMaterial}. Wraps a
4
+ * precompiled {@link FXRenderArtifact} (compiled against the mesh render target) and builds a
5
+ * self-contained `ShaderMaterial` for a single, non-instanced `THREE.Mesh`. Unlike the particle
6
+ * driver it takes no per-particle `varyings` - a VFX mesh has no simulation and no attributes. The
7
+ * host builds the material once and pushes the shared clock each frame.
8
+ */
9
+ export class FXMeshMaterial {
10
+ /**
11
+ * @param textures - App-supplied external textures by slot name. A missing one fails fast here
12
+ * rather than surfacing as a blank sampler at draw time.
13
+ */
14
+ constructor(artifact, textures = {}) {
15
+ this.artifact = artifact;
16
+ this.elapsedTime = { value: 0 };
17
+ this.deltaTime = { value: 0 };
18
+ this.velocity = { value: [0, 0, 0] };
19
+ this.angularVelocity = { value: [0, 0, 0] };
20
+ // Durable per-mesh slots for `external` texture uniforms: an external uniform resolves per mesh, so
21
+ // it binds its own live-scrubbable slot (non-external uniforms bind the shared artifact slot).
22
+ this.externalSlots = {};
23
+ for (const name in artifact.uniforms) {
24
+ const uniform = artifact.uniforms[name];
25
+ if ("external" in uniform) {
26
+ if (!(uniform.external in textures)) {
27
+ throw new Error(`FXMeshMaterial: no texture supplied for external slot "${uniform.external}" ` +
28
+ `(uniform "${name}"); pass it via the mesh host's textures`);
29
+ }
30
+ this.externalSlots[name] = { value: textures[uniform.external] };
31
+ }
32
+ }
33
+ }
34
+ buildThreeMaterial() {
35
+ return buildMeshArtifactMaterial(this.artifact, this.elapsedTime, this.deltaTime, this.velocity, this.angularVelocity, this.externalSlots);
36
+ }
37
+ /**
38
+ * The `customDepthMaterial` for casting shadows: the mesh twin of
39
+ * {@link FXArtifactMaterial.buildThreeDepthMaterial}. The mesh host attaches it on the `Mesh` only
40
+ * when the mesh opts into `castShadow`.
41
+ */
42
+ buildThreeDepthMaterial() {
43
+ return buildMeshArtifactDepthMaterial(this.artifact, this.elapsedTime, this.deltaTime, this.velocity, this.angularVelocity, this.externalSlots);
44
+ }
45
+ setElapsedTime(seconds) {
46
+ this.elapsedTime.value = seconds;
47
+ }
48
+ setDeltaTime(seconds) {
49
+ this.deltaTime.value = seconds;
50
+ }
51
+ setObjectVelocity(velocity) {
52
+ this.velocity.value = velocity;
53
+ }
54
+ setObjectAngularVelocity(angularVelocity) {
55
+ this.angularVelocity.value = angularVelocity;
56
+ }
57
+ /**
58
+ * Applies a value edit (a rebind, no rebuild). A non-external uniform mutates its shared artifact
59
+ * slot; an external uniform mutates this mesh's own slot. Unknown name = no-op (an editor<->runtime
60
+ * name drift never corrupts values).
61
+ */
62
+ applyUniformValues(values) {
63
+ for (const name in values) {
64
+ const value = values[name];
65
+ if (name in this.externalSlots) {
66
+ this.externalSlots[name].value = value;
67
+ }
68
+ else if (name in this.artifact.uniforms) {
69
+ this.artifact.uniforms[name].value =
70
+ value;
71
+ }
72
+ }
73
+ }
74
+ // No-op: the artifact owns no per-mesh resources (generated textures are module-scoped; external
75
+ // textures are app-owned).
76
+ destroy() { }
77
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * The tick domain that drives effects. A world owns a pool of emitters, a registry of effects, and
3
+ * a single monotonic clock; one {@link FXWorld.update} call per frame advances every playing
4
+ * effect's timeline and then ticks the shared particle pool exactly once.
5
+ *
6
+ * An {@link FXEffect} subscribes to a world on construction and unsubscribes on
7
+ * {@link FXEffect.dispose} - the host never wires that up by hand. Pass a world through
8
+ * `FXEffectOptions.world` to place an effect in a specific world; omit it and the effect joins the
9
+ * lazily created default world, which the static {@link FXWorld.update} ticks.
10
+ *
11
+ * A world is only a tick/clock domain, NOT a scene graph: an effect is still a `THREE.Object3D` you
12
+ * add to your own scene. Two worlds give two independent clocks (for an independent time scale or an
13
+ * isolated group); within one world every effect shares the clock and one delta per frame, so
14
+ * {@link FXEffect.stop} clears rather than freezes - freeze one effect independently by giving it
15
+ * its own world.
16
+ */
17
+ export declare class FXWorld {
18
+ private static defaultInstance;
19
+ private readonly emitters;
20
+ private readonly effects;
21
+ private elapsedTime;
22
+ private disposed;
23
+ /**
24
+ * The lazily created default world - the one an effect joins when its options carry no `world`.
25
+ * Nothing is allocated until the first effect or the first {@link FXWorld.update}.
26
+ */
27
+ static getDefault(): FXWorld;
28
+ /** Advances the default world by `deltaTime`; call once per frame. See {@link update}. */
29
+ static update(deltaTime: number): void;
30
+ /**
31
+ * Advances this world by `deltaTime` seconds: every playing effect drives its own timeline and
32
+ * meshes, then the shared particle pool ticks once. Call exactly once per frame, before rendering.
33
+ * A disposed world is a no-op.
34
+ */
35
+ update(deltaTime: number): void;
36
+ /**
37
+ * Disposes every effect registered in this world (each frees its emitters and mesh resources),
38
+ * then drops any remaining emitters. Idempotent. After this the world's {@link update} is a
39
+ * no-op; disposing the default world simply makes the next {@link getDefault} create a fresh one.
40
+ */
41
+ dispose(): void;
42
+ }
@@ -0,0 +1,103 @@
1
+ import { EMITTER_TIME_WRAP_PERIOD } from "../emitter/FXEmitter.Internal.js";
2
+ /**
3
+ * The tick domain that drives effects. A world owns a pool of emitters, a registry of effects, and
4
+ * a single monotonic clock; one {@link FXWorld.update} call per frame advances every playing
5
+ * effect's timeline and then ticks the shared particle pool exactly once.
6
+ *
7
+ * An {@link FXEffect} subscribes to a world on construction and unsubscribes on
8
+ * {@link FXEffect.dispose} - the host never wires that up by hand. Pass a world through
9
+ * `FXEffectOptions.world` to place an effect in a specific world; omit it and the effect joins the
10
+ * lazily created default world, which the static {@link FXWorld.update} ticks.
11
+ *
12
+ * A world is only a tick/clock domain, NOT a scene graph: an effect is still a `THREE.Object3D` you
13
+ * add to your own scene. Two worlds give two independent clocks (for an independent time scale or an
14
+ * isolated group); within one world every effect shares the clock and one delta per frame, so
15
+ * {@link FXEffect.stop} clears rather than freezes - freeze one effect independently by giving it
16
+ * its own world.
17
+ */
18
+ export class FXWorld {
19
+ constructor() {
20
+ this.emitters = [];
21
+ this.effects = [];
22
+ this.elapsedTime = 0;
23
+ this.disposed = false;
24
+ }
25
+ /**
26
+ * The lazily created default world - the one an effect joins when its options carry no `world`.
27
+ * Nothing is allocated until the first effect or the first {@link FXWorld.update}.
28
+ */
29
+ static getDefault() {
30
+ if (FXWorld.defaultInstance === undefined || FXWorld.defaultInstance.disposed) {
31
+ FXWorld.defaultInstance = new FXWorld();
32
+ }
33
+ return FXWorld.defaultInstance;
34
+ }
35
+ /** Advances the default world by `deltaTime`; call once per frame. See {@link update}. */
36
+ static update(deltaTime) {
37
+ FXWorld.getDefault().update(deltaTime);
38
+ }
39
+ /**
40
+ * Advances this world by `deltaTime` seconds: every playing effect drives its own timeline and
41
+ * meshes, then the shared particle pool ticks once. Call exactly once per frame, before rendering.
42
+ * A disposed world is a no-op.
43
+ */
44
+ update(deltaTime) {
45
+ if (this.disposed) {
46
+ return;
47
+ }
48
+ for (const effect of this.effects) {
49
+ effect.advanceFrame(deltaTime);
50
+ }
51
+ // Wrap the clock so a float32 `u_time` keeps sub-millisecond resolution in long sessions, then
52
+ // tick the whole pool once with the shared clock.
53
+ this.elapsedTime = (this.elapsedTime + deltaTime) % EMITTER_TIME_WRAP_PERIOD;
54
+ for (const emitter of this.emitters) {
55
+ emitter.onWorldTick(deltaTime, this.elapsedTime);
56
+ }
57
+ }
58
+ /**
59
+ * Disposes every effect registered in this world (each frees its emitters and mesh resources),
60
+ * then drops any remaining emitters. Idempotent. After this the world's {@link update} is a
61
+ * no-op; disposing the default world simply makes the next {@link getDefault} create a fresh one.
62
+ */
63
+ dispose() {
64
+ if (this.disposed) {
65
+ return;
66
+ }
67
+ this.disposed = true;
68
+ // dispose() unregisters each effect from this world, so iterate a snapshot.
69
+ for (const effect of [...this.effects]) {
70
+ effect.dispose();
71
+ }
72
+ for (const emitter of [...this.emitters]) {
73
+ emitter.destroy();
74
+ }
75
+ this.effects.length = 0;
76
+ this.emitters.length = 0;
77
+ if (FXWorld.defaultInstance === this) {
78
+ FXWorld.defaultInstance = undefined;
79
+ }
80
+ }
81
+ /** @internal Called by {@link FXEffect}'s constructor. */
82
+ registerEffect(effect) {
83
+ this.effects.push(effect);
84
+ }
85
+ /** @internal Called by {@link FXEffect.dispose}. */
86
+ unregisterEffect(effect) {
87
+ const index = this.effects.indexOf(effect);
88
+ if (index !== -1) {
89
+ this.effects.splice(index, 1);
90
+ }
91
+ }
92
+ /** @internal Called by {@link FXEmitter}'s constructor. */
93
+ registerEmitter(emitter) {
94
+ this.emitters.push(emitter);
95
+ }
96
+ /** @internal Called by {@link FXEmitter.destroy}. */
97
+ unregisterEmitter(emitter) {
98
+ const index = this.emitters.indexOf(emitter);
99
+ if (index !== -1) {
100
+ this.emitters.splice(index, 1);
101
+ }
102
+ }
103
+ }
package/package.json CHANGED
@@ -1,60 +1,76 @@
1
1
  {
2
2
  "name": "sparcoon",
3
- "version": "0.7.3",
4
- "description": "Instanced-mesh particle system for Three.js.",
3
+ "version": "0.9.0",
4
+ "description": "Runtime for VFX artifacts authored in the Sparcoon Editor.",
5
5
  "keywords": [
6
+ "three",
7
+ "threejs",
8
+ "three.js",
6
9
  "particles",
7
10
  "particle-system",
8
- "three.js",
9
- "3d",
10
- "webgl",
11
+ "vfx",
12
+ "visual-effects",
13
+ "fx",
11
14
  "instanced",
15
+ "instancing",
12
16
  "billboard",
13
- "typescript"
17
+ "webgl",
18
+ "3d",
19
+ "animation",
20
+ "typescript",
21
+ "sparcoon"
14
22
  ],
15
- "author": "jango",
16
- "license": "MIT",
23
+ "homepage": "https://github.com/jango-git/sparcoon#readme",
24
+ "bugs": {
25
+ "url": "https://github.com/jango-git/sparcoon/issues"
26
+ },
17
27
  "repository": {
18
28
  "type": "git",
19
29
  "url": "git+https://github.com/jango-git/sparcoon.git"
20
30
  },
31
+ "license": "MIT",
32
+ "author": "jango",
21
33
  "type": "module",
22
- "sideEffects": false,
23
- "scripts": {
24
- "clean": "rm -rf dist",
25
- "build": "npm run clean && rollup -c",
26
- "prepublishOnly": "npm run build"
34
+ "exports": {
35
+ ".": {
36
+ "types": "./dist/index.d.ts",
37
+ "import": "./dist/index.js"
38
+ },
39
+ "./package.json": "./package.json"
27
40
  },
28
41
  "main": "./dist/index.js",
29
- "module": "./dist/index.js",
42
+ "types": "./dist/index.d.ts",
30
43
  "files": [
31
44
  "dist",
32
45
  "README.md",
33
46
  "LICENSE"
34
47
  ],
35
- "exports": {
36
- ".": {
37
- "import": "./dist/index.js",
38
- "types": "./dist/index.d.ts"
39
- }
48
+ "sideEffects": false,
49
+ "engines": {
50
+ "node": ">=18.18.0"
51
+ },
52
+ "scripts": {
53
+ "clean": "rm -rf dist",
54
+ "build": "npm run clean && tsc -p tsconfig.json",
55
+ "lint": "eslint src",
56
+ "test": "vitest run",
57
+ "test:watch": "vitest",
58
+ "test:coverage": "vitest run --coverage",
59
+ "format": "prettier --write . && eslint src --fix",
60
+ "prepublishOnly": "npm run build"
40
61
  },
41
62
  "peerDependencies": {
42
- "fast-simplex-noise": "^4.0.0",
43
- "ferrsign": "^0.0.4",
44
63
  "three": ">=0.157.0 <0.180.0"
45
64
  },
46
65
  "devDependencies": {
47
- "@rollup/plugin-node-resolve": "^16.0.1",
48
- "@rollup/plugin-commonjs": "^29.0.2",
49
- "@types/node": "^25.0.2",
66
+ "@types/node": "^20.0.0",
50
67
  "@types/three": "^0.157.0",
51
68
  "@typescript-eslint/eslint-plugin": "^8.32.0",
52
69
  "@typescript-eslint/parser": "^8.32.0",
70
+ "@vitest/coverage-v8": "^4.1.9",
53
71
  "eslint": "^9.26.0",
54
- "rollup": "^4.40.2",
55
- "rollup-plugin-string": "^3.0.0",
56
- "rollup-plugin-typescript2": "^0.36.0",
57
- "ts-transformer-minify-privates": "^1.0.0",
58
- "typescript": "^5.8.3"
72
+ "prettier": "^3.9.5",
73
+ "typescript": "^5.8.3",
74
+ "vitest": "^4.1.9"
59
75
  }
60
76
  }
@@ -1,3 +0,0 @@
1
- var _2d = {};
2
-
3
- export { _2d as __exports };
@@ -1,3 +0,0 @@
1
- var _3d = {};
2
-
3
- export { _3d as __exports };
@@ -1,3 +0,0 @@
1
- var _4d = {};
2
-
3
- export { _4d as __exports };
@@ -1,5 +0,0 @@
1
- import { __require as requireMod } from '../node_modules/fast-simplex-noise/lib/mod.js';
2
-
3
- var modExports = requireMod();
4
-
5
- export { modExports as m };
@@ -1,3 +0,0 @@
1
- var mod = {};
2
-
3
- export { mod as __exports };
@@ -1,24 +0,0 @@
1
- import type { InstancedBufferAttribute } from "three";
2
- import type { FXPropertyName } from "../../instancedParticle/shared";
3
- /**
4
- * Abstract base for particle behavior modules that mutate per-particle properties each frame
5
- *
6
- * @typeParam T - Map of local property keys to {@link FXPropertyName} identifiers,
7
- * declaring which particle attributes this behavior reads or writes
8
- */
9
- export declare abstract class FXBehavior<T extends Record<string, FXPropertyName> = Record<string, FXPropertyName>> {
10
- /** Particle attributes required by this behavior */
11
- abstract readonly requiredProperties: T;
12
- /**
13
- * Called once per frame to update all live particles
14
- *
15
- * @param properties - Buffer attributes keyed by `requiredProperties`
16
- * @param instanceCount - Number of currently live particles
17
- * @param deltaTime - Elapsed time since the last frame, in seconds
18
- */
19
- abstract update(properties: {
20
- [K in keyof T]: InstancedBufferAttribute;
21
- }, instanceCount: number, deltaTime: number): void;
22
- /** Releases any resources held by this behavior */
23
- destroy?(): void;
24
- }
@@ -1,10 +0,0 @@
1
- /**
2
- * Abstract base for particle behavior modules that mutate per-particle properties each frame
3
- *
4
- * @typeParam T - Map of local property keys to {@link FXPropertyName} identifiers,
5
- * declaring which particle attributes this behavior reads or writes
6
- */
7
- class FXBehavior {
8
- }
9
-
10
- export { FXBehavior };
@@ -1,23 +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
- export declare class FXBehaviorDirectionalForce extends FXBehavior<{
8
- builtin: "Matrix4";
9
- }> {
10
- private readonly directionInternal;
11
- private magnitudeInternal;
12
- /**
13
- * @param direction - Direction vector (will be normalized)
14
- * @param magnitude - Acceleration magnitude in units/second²
15
- */
16
- constructor(direction: Vector3Like, magnitude: number);
17
- /** Direction vector (normalized) */
18
- get direction(): Vector3Like;
19
- /** Acceleration magnitude in units/second² */
20
- get magnitude(): number;
21
- set direction(value: FXVector3Config);
22
- set magnitude(value: number);
23
- }
@@ -1,72 +0,0 @@
1
- import { Vector3 } from 'three';
2
- import { assertValidNumber } from '../../miscellaneous/asserts.js';
3
- import { EPSILON } from '../../miscellaneous/math.js';
4
- import { resolveFXVector3Config, BUILTIN_OFFSET_VELOCITY_X, BUILTIN_OFFSET_VELOCITY_Y, BUILTIN_OFFSET_VELOCITY_Z } from '../../miscellaneous/miscellaneous.js';
5
- import { FXBehavior } from './FXBehavior.js';
6
-
7
- /**
8
- * Applies constant acceleration to all particles
9
- */
10
- class FXBehaviorDirectionalForce extends FXBehavior {
11
- /**
12
- * @param direction - Direction vector (will be normalized)
13
- * @param magnitude - Acceleration magnitude in units/second²
14
- */
15
- constructor(direction, magnitude) {
16
- super();
17
- /** @internal */
18
- this.requiredProperties = { builtin: "Matrix4" };
19
- const resolvedDirection = resolveFXVector3Config(direction);
20
- assertValidNumber(resolvedDirection.x, "FXBehaviorDirectionalForce.direction.x");
21
- assertValidNumber(resolvedDirection.y, "FXBehaviorDirectionalForce.direction.y");
22
- assertValidNumber(resolvedDirection.z, "FXBehaviorDirectionalForce.direction.z");
23
- assertValidNumber(magnitude, "FXBehaviorDirectionalForce.constructor.magnitude");
24
- this._private_directionInternal = new Vector3(resolvedDirection.x, resolvedDirection.y, resolvedDirection.z);
25
- if (this._private_directionInternal.lengthSq() <= EPSILON) {
26
- throw new Error("FXBehaviorDirectionalForce.direction: vector cannot be zero");
27
- }
28
- this._private_directionInternal.normalize();
29
- this._private_magnitudeInternal = magnitude;
30
- }
31
- /** Direction vector (normalized) */
32
- get direction() {
33
- return this._private_directionInternal;
34
- }
35
- /** Acceleration magnitude in units/second² */
36
- get magnitude() {
37
- return this._private_magnitudeInternal;
38
- }
39
- set direction(value) {
40
- const resolved = resolveFXVector3Config(value);
41
- assertValidNumber(resolved.x, "FXBehaviorDirectionalForce.direction.x");
42
- assertValidNumber(resolved.y, "FXBehaviorDirectionalForce.direction.y");
43
- assertValidNumber(resolved.z, "FXBehaviorDirectionalForce.direction.z");
44
- this._private_directionInternal.set(resolved.x, resolved.y, resolved.z);
45
- if (this._private_directionInternal.lengthSq() < EPSILON) {
46
- throw new Error("FXBehaviorDirectionalForce.direction: vector cannot be zero");
47
- }
48
- this._private_directionInternal.normalize();
49
- }
50
- set magnitude(value) {
51
- assertValidNumber(value, "FXBehaviorDirectionalForce.magnitude");
52
- this._private_magnitudeInternal = value;
53
- }
54
- /** @internal */
55
- update(properties, instanceCount, deltaTime) {
56
- const { builtin } = properties;
57
- const { array, itemSize } = builtin;
58
- const scale = this._private_magnitudeInternal * deltaTime;
59
- const offsetX = this._private_directionInternal.x * scale;
60
- const offsetY = this._private_directionInternal.y * scale;
61
- const offsetZ = this._private_directionInternal.z * scale;
62
- for (let i = 0; i < instanceCount; i++) {
63
- const itemOffset = i * itemSize;
64
- array[itemOffset + BUILTIN_OFFSET_VELOCITY_X] += offsetX;
65
- array[itemOffset + BUILTIN_OFFSET_VELOCITY_Y] += offsetY;
66
- array[itemOffset + BUILTIN_OFFSET_VELOCITY_Z] += offsetZ;
67
- }
68
- builtin.needsUpdate = true;
69
- }
70
- }
71
-
72
- export { FXBehaviorDirectionalForce };