sparcoon 0.9.0 → 0.9.1

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.
@@ -0,0 +1,8 @@
1
+ export { FXEmitter } from "../emitter/FXEmitter.js";
2
+ export type { FXFromArtifactsOptions } from "../emitter/FXFromArtifactsOptions.js";
3
+ export type { FXApplyValues, FXEmitterOptions, FXEmitterBurstOptions, FXEmitterPlayOptions, } from "../emitter/FXEmitter.Internal.js";
4
+ export { FXMesh } from "../render/FXMesh.js";
5
+ export type { FXFromMeshArtifactOptions } from "../render/FXMesh.js";
6
+ export { FXMeshMaterial } from "../render/FXMeshMaterial.js";
7
+ export { resolveGeometrySource } from "../instancedParticle/primitiveGeometry.js";
8
+ export { frameOfTime, keyframeSegment, normalizeQuaternion, quaternionFromValue, sampleTrack, sampleTracks, sampleTransform, slerpQuaternion, timeOfFrame, vectorFromValue, } from "../effect/FXEffectSampling.Internal.js";
@@ -0,0 +1,17 @@
1
+ // Editor-tooling surface: `import ... from "sparcoon/editor"`.
2
+ //
3
+ // UNSTABLE - no semver guarantees. These are the low-level building blocks the Sparcoon Editor's
4
+ // live preview needs (rebuild a single emitter/mesh from freshly compiled artifacts on every graph
5
+ // edit), plus the keyframe-sampling math the editor and runtime must share so a baked preview and
6
+ // runtime playback never drift. Regular game/scene hosts use the main "sparcoon" entry
7
+ // (FXEffect + FXWorld); they should not reach for anything here.
8
+ export { FXEmitter } from "../emitter/FXEmitter.js";
9
+ // Self-contained VFX mesh (the mesh twin of FXEmitter): build one from a render artifact and let
10
+ // FXWorld.update drive it, including the object velocity/angular velocity a mesh render graph reads.
11
+ export { FXMesh } from "../render/FXMesh.js";
12
+ // Lower-level pieces FXMesh is built from - kept for tooling that assembles a mesh by hand.
13
+ export { FXMeshMaterial } from "../render/FXMeshMaterial.js";
14
+ export { resolveGeometrySource } from "../instancedParticle/primitiveGeometry.js";
15
+ // Shared keyframe/transform sampling: the editor bakes keyframes and the runtime samples them, so
16
+ // both sides must sample identically. See the frozen-contract note in CLAUDE.md.
17
+ export { frameOfTime, keyframeSegment, normalizeQuaternion, quaternionFromValue, sampleTrack, sampleTracks, sampleTransform, slerpQuaternion, timeOfFrame, vectorFromValue, } from "../effect/FXEffectSampling.Internal.js";
@@ -1,5 +1,8 @@
1
1
  import type { Camera } from "three";
2
2
  import { Object3D } from "three";
3
+ import type { FXBehaviorArtifact, FXRenderArtifact } from "../artifact/FXArtifact.js";
4
+ import type { FXFromArtifactsOptions } from "./FXFromArtifactsOptions.js";
5
+ import { FXWorld } from "../world/FXWorld.js";
3
6
  import type { FXApplyValues, FXEmitterBurstOptions, FXEmitterPlayOptions } from "./FXEmitter.Internal.js";
4
7
  /**
5
8
  * Particle emitter - one instanced billboard/mesh draw call, per-particle state driven by a
@@ -33,6 +36,13 @@ export declare class FXEmitter extends Object3D {
33
36
  private kernelBuffersCacheVersion;
34
37
  get particleCount(): number;
35
38
  get particleCapacity(): number;
39
+ /**
40
+ * Builds an emitter into `world` from a precompiled effect - the low-level building block behind
41
+ * {@link FXEffect}. Exposed for editor tooling through the `sparcoon/editor` entry (live preview
42
+ * rebuilds a single emitter per graph edit); not part of the main package surface, where the
43
+ * runtime path is running whole projects through `FXEffect`.
44
+ */
45
+ static fromArtifacts(render: FXRenderArtifact, behavior: FXBehaviorArtifact, options?: FXFromArtifactsOptions, world?: FXWorld): FXEmitter;
36
46
  /** Unregisters, removes from the scene, disposes mesh and material. Idempotent. */
37
47
  destroy(): void;
38
48
  /**
@@ -101,7 +101,7 @@ export class FXEmitter extends Object3D {
101
101
  }
102
102
  this.assertCanSpawn("FXEmitter.constructor");
103
103
  this.add(this.mesh);
104
- this.world.registerEmitter(this);
104
+ this.world.registerObject(this);
105
105
  }
106
106
  get particleCount() {
107
107
  return this.mesh.instanceCount;
@@ -110,8 +110,10 @@ export class FXEmitter extends Object3D {
110
110
  return this.mesh.instanceCapacity;
111
111
  }
112
112
  /**
113
- * @internal Builds an emitter into `world` from a precompiled effect. Called by {@link FXEffect};
114
- * the editor emits the two artifacts as an ESM module and the effect hands them here.
113
+ * Builds an emitter into `world` from a precompiled effect - the low-level building block behind
114
+ * {@link FXEffect}. Exposed for editor tooling through the `sparcoon/editor` entry (live preview
115
+ * rebuilds a single emitter per graph edit); not part of the main package surface, where the
116
+ * runtime path is running whole projects through `FXEffect`.
115
117
  */
116
118
  static fromArtifacts(render, behavior, options = {}, world = FXWorld.getDefault()) {
117
119
  const material = new FXArtifactMaterial(render, options.textures);
@@ -138,7 +140,7 @@ export class FXEmitter extends Object3D {
138
140
  return;
139
141
  }
140
142
  this.destroyed = true;
141
- this.world.unregisterEmitter(this);
143
+ this.world.unregisterObject(this);
142
144
  // Drop scheduled work and the buffer cache so nothing references the mesh's buffers after teardown.
143
145
  this.pendingBursts.length = 0;
144
146
  this.activePlays.length = 0;
package/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ export type { FXGraphLambertMaterialOptions, FXGraphUnlitMaterialOptions, FXRend
4
4
  export type { FXGeometryPrimitive } from "./artifact/FXArtifact.js";
5
5
  export { FXEffect } from "./effect/FXEffect.js";
6
6
  export type { FXEffectOptions, FXEffectSpec } from "./effect/FXEffectSpec.js";
7
+ export type { FXEmitter } from "./emitter/FXEmitter.js";
7
8
  export { FXWorld } from "./world/FXWorld.js";
8
9
  export { fxFbm, fxFract, fxHash, fxMix, fxMod, fxSampleLut, fxSmoothstep, fxSnoise2, fxSnoise3, fxValueNoise, } from "./miscellaneous/fxMath.js";
9
10
  export { fxDataTexture } from "./miscellaneous/texture/fxDataTexture.js";
@@ -1 +1,10 @@
1
- export {};
1
+ import type { BufferGeometry } from "three";
2
+ import type { FXGeometrySource } from "../artifact/FXArtifact.js";
3
+ /**
4
+ * Resolves an artifact's {@link FXGeometrySource} to a concrete `BufferGeometry`: a
5
+ * built-in primitive is built fresh; a `"custom"` slot is looked up by name in `customGeometries`
6
+ * (app-supplied, e.g. from the exported module's `assets` or the editor's own live content
7
+ * library) - falling back to the `"plane"` primitive when the name is unbound, the same graceful
8
+ * degradation an unbound external texture slot gets.
9
+ */
10
+ export declare function resolveGeometrySource(source: FXGeometrySource | undefined, customGeometries: Readonly<Record<string, BufferGeometry>>): BufferGeometry;
@@ -16,7 +16,7 @@ export function buildPrimitiveGeometry(primitive) {
16
16
  }
17
17
  }
18
18
  /**
19
- * @internal Resolves an artifact's {@link FXGeometrySource} to a concrete `BufferGeometry`: a
19
+ * Resolves an artifact's {@link FXGeometrySource} to a concrete `BufferGeometry`: a
20
20
  * built-in primitive is built fresh; a `"custom"` slot is looked up by name in `customGeometries`
21
21
  * (app-supplied, e.g. from the exported module's `assets` or the editor's own live content
22
22
  * library) - falling back to the `"plane"` primitive when the name is unbound, the same graceful
@@ -0,0 +1,47 @@
1
+ import { Mesh, type BufferGeometry, type Texture } from "three";
2
+ import type { FXRenderArtifact } from "../artifact/FXArtifact.js";
3
+ import { FXWorld } from "../world/FXWorld.js";
4
+ /** Options for {@link FXMesh.fromArtifact}. */
5
+ export interface FXFromMeshArtifactOptions {
6
+ /** External textures by slot name (a `{ type: "sampler2D", external }` uniform binds from here). */
7
+ readonly textures?: Readonly<Record<string, Texture>>;
8
+ /** Custom geometries by name, resolving a `{ type: "custom", external }` geometry source. */
9
+ readonly geometries?: Readonly<Record<string, BufferGeometry>>;
10
+ /** Cast a shape-aware shadow (builds a depth material from the render artifact). default false */
11
+ readonly castShadow?: boolean;
12
+ /** Receive shadows. default false */
13
+ readonly receiveShadow?: boolean;
14
+ }
15
+ /**
16
+ * A single, non-instanced VFX mesh built from one precompiled render artifact - the mesh twin of
17
+ * {@link FXEmitter}. Extends `THREE.Mesh`, so the host adds it to its own scene; it self-subscribes
18
+ * to an {@link FXWorld} on construction and unsubscribes on {@link destroy}, so one
19
+ * {@link FXWorld.update} per frame drives it alongside every emitter. Each tick pushes the shared
20
+ * clock and the mesh's world-space velocity/angular velocity, so a render graph reading the
21
+ * object-velocity/object-angular-velocity builtins works without the host tracking motion by hand.
22
+ *
23
+ * Not exported from the main package (the runtime path is running whole projects through
24
+ * {@link FXEffect}); reached through the `sparcoon/editor` entry, for editor live preview and
25
+ * similar tooling that renders one artifact-driven mesh at a time.
26
+ */
27
+ export declare class FXMesh extends Mesh {
28
+ private readonly world;
29
+ private readonly driver;
30
+ private readonly motionTracker;
31
+ private readonly ownsGeometry;
32
+ private readonly depthMaterial?;
33
+ private readonly motionPositionScratch;
34
+ private readonly motionQuaternionScratch;
35
+ private readonly motionScaleScratch;
36
+ private destroyed;
37
+ private constructor();
38
+ /** Builds a mesh into `world` from a precompiled render artifact. */
39
+ static fromArtifact(render: FXRenderArtifact, options?: FXFromMeshArtifactOptions, world?: FXWorld): FXMesh;
40
+ /** Scrubs live uniform values by name (picked up next frame). Unknown name = safe no-op. */
41
+ applyValues(values: Readonly<Record<string, number | readonly number[] | Texture>>): void;
42
+ /**
43
+ * Unsubscribes from its world and disposes GPU resources (the mounted material, its depth
44
+ * material, and a built-in primitive geometry - never an app-supplied custom one). Idempotent.
45
+ */
46
+ destroy(): void;
47
+ }
@@ -0,0 +1,99 @@
1
+ import { Mesh, Quaternion, Vector3, } from "three";
2
+ import { FXObjectMotionTracker } from "../emitter/FXObjectMotion.Internal.js";
3
+ import { resolveGeometrySource } from "../instancedParticle/primitiveGeometry.js";
4
+ import { FXWorld } from "../world/FXWorld.js";
5
+ import { FXMeshMaterial } from "./FXMeshMaterial.js";
6
+ /**
7
+ * A single, non-instanced VFX mesh built from one precompiled render artifact - the mesh twin of
8
+ * {@link FXEmitter}. Extends `THREE.Mesh`, so the host adds it to its own scene; it self-subscribes
9
+ * to an {@link FXWorld} on construction and unsubscribes on {@link destroy}, so one
10
+ * {@link FXWorld.update} per frame drives it alongside every emitter. Each tick pushes the shared
11
+ * clock and the mesh's world-space velocity/angular velocity, so a render graph reading the
12
+ * object-velocity/object-angular-velocity builtins works without the host tracking motion by hand.
13
+ *
14
+ * Not exported from the main package (the runtime path is running whole projects through
15
+ * {@link FXEffect}); reached through the `sparcoon/editor` entry, for editor live preview and
16
+ * similar tooling that renders one artifact-driven mesh at a time.
17
+ */
18
+ export class FXMesh extends Mesh {
19
+ constructor(geometry, driver, threeMaterial, depthMaterial, ownsGeometry, world) {
20
+ super(geometry, threeMaterial);
21
+ this.motionTracker = new FXObjectMotionTracker();
22
+ // Reused decompose scratch (avoids a per-tick allocation); scale is discarded.
23
+ this.motionPositionScratch = new Vector3();
24
+ this.motionQuaternionScratch = new Quaternion();
25
+ this.motionScaleScratch = new Vector3();
26
+ this.destroyed = false;
27
+ this.driver = driver;
28
+ this.ownsGeometry = ownsGeometry;
29
+ this.world = world;
30
+ if (depthMaterial !== undefined) {
31
+ this.depthMaterial = depthMaterial;
32
+ this.castShadow = true;
33
+ this.customDepthMaterial = depthMaterial;
34
+ }
35
+ this.world.registerObject(this);
36
+ }
37
+ /** Builds a mesh into `world` from a precompiled render artifact. */
38
+ static fromArtifact(render, options = {}, world = FXWorld.getDefault()) {
39
+ const driver = new FXMeshMaterial(render, options.textures);
40
+ const geometry = resolveGeometrySource(render.geometry, options.geometries ?? {});
41
+ const ownsGeometry = render.geometry === undefined || render.geometry.type === "primitive";
42
+ const depthMaterial = (options.castShadow ?? false)
43
+ ? driver.buildThreeDepthMaterial()
44
+ : undefined;
45
+ const mesh = new FXMesh(geometry, driver, driver.buildThreeMaterial(), depthMaterial, ownsGeometry, world);
46
+ if (options.receiveShadow ?? false) {
47
+ mesh.receiveShadow = true;
48
+ }
49
+ return mesh;
50
+ }
51
+ /** @internal Driven by {@link FXWorld.update}. */
52
+ onWorldTick(deltaTime, elapsedTime) {
53
+ this.updateWorldMatrix(true, false);
54
+ this.matrixWorld.decompose(this.motionPositionScratch, this.motionQuaternionScratch, this.motionScaleScratch);
55
+ const motion = this.motionTracker.sample({
56
+ position: [
57
+ this.motionPositionScratch.x,
58
+ this.motionPositionScratch.y,
59
+ this.motionPositionScratch.z,
60
+ ],
61
+ quaternion: [
62
+ this.motionQuaternionScratch.x,
63
+ this.motionQuaternionScratch.y,
64
+ this.motionQuaternionScratch.z,
65
+ this.motionQuaternionScratch.w,
66
+ ],
67
+ }, deltaTime);
68
+ this.driver.setElapsedTime(elapsedTime);
69
+ this.driver.setDeltaTime(deltaTime);
70
+ this.driver.setObjectVelocity(motion.velocity);
71
+ this.driver.setObjectAngularVelocity(motion.angularVelocity);
72
+ }
73
+ /** Scrubs live uniform values by name (picked up next frame). Unknown name = safe no-op. */
74
+ applyValues(values) {
75
+ if (this.destroyed) {
76
+ return;
77
+ }
78
+ this.driver.applyUniformValues(values);
79
+ }
80
+ /**
81
+ * Unsubscribes from its world and disposes GPU resources (the mounted material, its depth
82
+ * material, and a built-in primitive geometry - never an app-supplied custom one). Idempotent.
83
+ */
84
+ destroy() {
85
+ if (this.destroyed) {
86
+ return;
87
+ }
88
+ this.destroyed = true;
89
+ this.world.unregisterObject(this);
90
+ if (this.ownsGeometry) {
91
+ this.geometry.dispose();
92
+ }
93
+ this.material.dispose();
94
+ if (this.depthMaterial !== undefined) {
95
+ this.depthMaterial.dispose();
96
+ }
97
+ this.removeFromParent();
98
+ }
99
+ }
@@ -1,22 +1,32 @@
1
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.
2
+ * A world-registered renderable driven once per frame: an emitter or a VFX mesh. `onWorldTick` reads
3
+ * the object's own `matrixWorld` and pushes the shared clock plus its world-space motion; `destroy`
4
+ * frees its GPU resources and unregisters it.
5
+ */
6
+ export interface FXWorldObject {
7
+ onWorldTick(deltaTime: number, elapsedTime: number): void;
8
+ destroy(): void;
9
+ }
10
+ /**
11
+ * The tick domain that drives effects and their renderables. A world owns a registry of effects, a
12
+ * pool of world objects (emitters and meshes), and a single monotonic clock; one
13
+ * {@link FXWorld.update} call per frame advances every playing effect's timeline, then ticks every
14
+ * registered emitter and mesh exactly once with the shared clock.
5
15
  *
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.
16
+ * An {@link FXEffect} (and each standalone emitter/mesh it or the editor builds) subscribes to a
17
+ * world on construction and unsubscribes on disposal - the host never wires that up by hand. Pass a
18
+ * world through `FXEffectOptions.world` to place an effect in a specific world; omit it and it joins
19
+ * the lazily created default world, which the static {@link FXWorld.update} ticks.
10
20
  *
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.
21
+ * A world is only a tick/clock domain, NOT a scene graph: a renderable is still a `THREE.Object3D`
22
+ * you add to your own scene. Two worlds give two independent clocks (for an independent time scale or
23
+ * an isolated group); within one world every effect shares the clock and one delta per frame, so
24
+ * {@link FXEffect.stop} clears rather than freezes - freeze one effect independently by giving it its
25
+ * own world.
16
26
  */
17
27
  export declare class FXWorld {
18
28
  private static defaultInstance;
19
- private readonly emitters;
29
+ private readonly objects;
20
30
  private readonly effects;
21
31
  private elapsedTime;
22
32
  private disposed;
@@ -29,14 +39,14 @@ export declare class FXWorld {
29
39
  static update(deltaTime: number): void;
30
40
  /**
31
41
  * 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.
42
+ * meshes, then every registered emitter and mesh ticks once. Call exactly once per frame, before
43
+ * rendering. A disposed world is a no-op.
34
44
  */
35
45
  update(deltaTime: number): void;
36
46
  /**
37
47
  * 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.
48
+ * then drops any remaining objects. Idempotent. After this the world's {@link update} is a no-op;
49
+ * disposing the default world simply makes the next {@link getDefault} create a fresh one.
40
50
  */
41
51
  dispose(): void;
42
52
  }
@@ -1,23 +1,24 @@
1
1
  import { EMITTER_TIME_WRAP_PERIOD } from "../emitter/FXEmitter.Internal.js";
2
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.
3
+ * The tick domain that drives effects and their renderables. A world owns a registry of effects, a
4
+ * pool of world objects (emitters and meshes), and a single monotonic clock; one
5
+ * {@link FXWorld.update} call per frame advances every playing effect's timeline, then ticks every
6
+ * registered emitter and mesh exactly once with the shared clock.
6
7
  *
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.
8
+ * An {@link FXEffect} (and each standalone emitter/mesh it or the editor builds) subscribes to a
9
+ * world on construction and unsubscribes on disposal - the host never wires that up by hand. Pass a
10
+ * world through `FXEffectOptions.world` to place an effect in a specific world; omit it and it joins
11
+ * the lazily created default world, which the static {@link FXWorld.update} ticks.
11
12
  *
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.
13
+ * A world is only a tick/clock domain, NOT a scene graph: a renderable is still a `THREE.Object3D`
14
+ * you add to your own scene. Two worlds give two independent clocks (for an independent time scale or
15
+ * an isolated group); within one world every effect shares the clock and one delta per frame, so
16
+ * {@link FXEffect.stop} clears rather than freezes - freeze one effect independently by giving it its
17
+ * own world.
17
18
  */
18
19
  export class FXWorld {
19
20
  constructor() {
20
- this.emitters = [];
21
+ this.objects = [];
21
22
  this.effects = [];
22
23
  this.elapsedTime = 0;
23
24
  this.disposed = false;
@@ -38,8 +39,8 @@ export class FXWorld {
38
39
  }
39
40
  /**
40
41
  * 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.
42
+ * meshes, then every registered emitter and mesh ticks once. Call exactly once per frame, before
43
+ * rendering. A disposed world is a no-op.
43
44
  */
44
45
  update(deltaTime) {
45
46
  if (this.disposed) {
@@ -49,31 +50,31 @@ export class FXWorld {
49
50
  effect.advanceFrame(deltaTime);
50
51
  }
51
52
  // 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
+ // tick every registered object once with the shared clock.
53
54
  this.elapsedTime = (this.elapsedTime + deltaTime) % EMITTER_TIME_WRAP_PERIOD;
54
- for (const emitter of this.emitters) {
55
- emitter.onWorldTick(deltaTime, this.elapsedTime);
55
+ for (const object of this.objects) {
56
+ object.onWorldTick(deltaTime, this.elapsedTime);
56
57
  }
57
58
  }
58
59
  /**
59
60
  * 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.
61
+ * then drops any remaining objects. Idempotent. After this the world's {@link update} is a no-op;
62
+ * disposing the default world simply makes the next {@link getDefault} create a fresh one.
62
63
  */
63
64
  dispose() {
64
65
  if (this.disposed) {
65
66
  return;
66
67
  }
67
68
  this.disposed = true;
68
- // dispose() unregisters each effect from this world, so iterate a snapshot.
69
+ // Each disposal unregisters from this world, so iterate snapshots.
69
70
  for (const effect of [...this.effects]) {
70
71
  effect.dispose();
71
72
  }
72
- for (const emitter of [...this.emitters]) {
73
- emitter.destroy();
73
+ for (const object of [...this.objects]) {
74
+ object.destroy();
74
75
  }
75
76
  this.effects.length = 0;
76
- this.emitters.length = 0;
77
+ this.objects.length = 0;
77
78
  if (FXWorld.defaultInstance === this) {
78
79
  FXWorld.defaultInstance = undefined;
79
80
  }
@@ -89,15 +90,15 @@ export class FXWorld {
89
90
  this.effects.splice(index, 1);
90
91
  }
91
92
  }
92
- /** @internal Called by {@link FXEmitter}'s constructor. */
93
- registerEmitter(emitter) {
94
- this.emitters.push(emitter);
93
+ /** @internal Called by an emitter's or mesh's constructor. */
94
+ registerObject(object) {
95
+ this.objects.push(object);
95
96
  }
96
- /** @internal Called by {@link FXEmitter.destroy}. */
97
- unregisterEmitter(emitter) {
98
- const index = this.emitters.indexOf(emitter);
97
+ /** @internal Called on an emitter's or mesh's destroy. */
98
+ unregisterObject(object) {
99
+ const index = this.objects.indexOf(object);
99
100
  if (index !== -1) {
100
- this.emitters.splice(index, 1);
101
+ this.objects.splice(index, 1);
101
102
  }
102
103
  }
103
104
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sparcoon",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "Runtime for VFX artifacts authored in the Sparcoon Editor.",
5
5
  "keywords": [
6
6
  "three",
@@ -36,6 +36,10 @@
36
36
  "types": "./dist/index.d.ts",
37
37
  "import": "./dist/index.js"
38
38
  },
39
+ "./editor": {
40
+ "types": "./dist/editor/index.d.ts",
41
+ "import": "./dist/editor/index.js"
42
+ },
39
43
  "./package.json": "./package.json"
40
44
  },
41
45
  "main": "./dist/index.js",