babylon.quarks-editor 0.26.0 → 0.27.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 (151) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +38 -5
  3. package/dist/core/assetDragSession.d.ts +1 -1
  4. package/dist/core/assetTree.d.ts +1 -1
  5. package/dist/core/binding.d.ts +2 -2
  6. package/dist/core/binding.js +1 -1
  7. package/dist/core/defaultEffect.d.ts +2 -2
  8. package/dist/core/defaultEffect.js +3 -3
  9. package/dist/core/editorScene.d.ts +1 -1
  10. package/dist/core/effectTree.d.ts +1 -1
  11. package/dist/core/effectTree.js +13 -88
  12. package/dist/core/emitterShapeWireframe.d.ts +2 -2
  13. package/dist/core/emitterShapeWireframe.js +45 -13
  14. package/dist/core/galleryDropMarker.d.ts +2 -2
  15. package/dist/core/galleryDropMarker.js +6 -6
  16. package/dist/core/galleryLayout.d.ts +3 -3
  17. package/dist/core/galleryLayout.js +1 -1
  18. package/dist/core/galleryPlayback.d.ts +1 -1
  19. package/dist/core/galleryScene.d.ts +4 -4
  20. package/dist/core/galleryScene.js +3 -3
  21. package/dist/core/gallerySelectionMarker.d.ts +4 -4
  22. package/dist/core/gallerySelectionMarker.js +5 -5
  23. package/dist/core/groups.d.ts +1 -1
  24. package/dist/core/groups.js +1 -1
  25. package/dist/core/history.d.ts +1 -1
  26. package/dist/core/indexedDbProjectStore.d.ts +1 -1
  27. package/dist/core/lineShaders.d.ts +2 -2
  28. package/dist/core/lineShaders.js +2 -2
  29. package/dist/core/loadEffect.d.ts +2 -2
  30. package/dist/core/loadEffect.js +1 -1
  31. package/dist/core/pickAssetFiles.js +1 -1
  32. package/dist/core/sceneEntries.d.ts +4 -4
  33. package/dist/core/sceneEntries.js +2 -2
  34. package/dist/core/scrub.d.ts +1 -1
  35. package/dist/core/shapes.d.ts +1 -1
  36. package/dist/core/systems.d.ts +2 -2
  37. package/dist/core/textureExport.d.ts +1 -1
  38. package/dist/core/timeline.d.ts +2 -2
  39. package/dist/core/transport.d.ts +3 -3
  40. package/dist/core/transport.js +2 -2
  41. package/dist/core/viewportPicker.d.ts +4 -4
  42. package/dist/core/viewportPicker.js +8 -7
  43. package/dist/index.d.ts +21 -21
  44. package/dist/index.js +12 -12
  45. package/dist/react/AssetsPanel.d.ts +2 -2
  46. package/dist/react/AssetsPanel.js +6 -6
  47. package/dist/react/ChoiceDialog.js +1 -1
  48. package/dist/react/ContextMenu.js +1 -1
  49. package/dist/react/CurveEditor.d.ts +1 -1
  50. package/dist/react/CurveEditor.js +2 -2
  51. package/dist/react/EditorWorkspace.d.ts +2 -2
  52. package/dist/react/EditorWorkspace.js +4 -4
  53. package/dist/react/EffectEditor.d.ts +3 -3
  54. package/dist/react/EffectEditor.js +7 -7
  55. package/dist/react/EffectEditorHost.d.ts +6 -6
  56. package/dist/react/EffectEditorHost.js +59 -62
  57. package/dist/react/EffectTreeView.d.ts +1 -1
  58. package/dist/react/EffectTreeView.js +3 -3
  59. package/dist/react/GradientEditor.d.ts +1 -1
  60. package/dist/react/GradientEditor.js +3 -3
  61. package/dist/react/InspectorInfoIcon.js +3 -3
  62. package/dist/react/InspectorLabel.js +3 -3
  63. package/dist/react/MessageDialog.js +1 -1
  64. package/dist/react/ModuleSection.js +4 -4
  65. package/dist/react/ObjectModule.d.ts +1 -1
  66. package/dist/react/ObjectModule.js +4 -4
  67. package/dist/react/ProjectsPage.d.ts +1 -1
  68. package/dist/react/ProjectsPage.js +6 -6
  69. package/dist/react/PromptDialog.js +1 -1
  70. package/dist/react/ScenePanel.d.ts +2 -2
  71. package/dist/react/ScenePanel.js +5 -5
  72. package/dist/react/SubEmittersModule.d.ts +1 -1
  73. package/dist/react/SubEmittersModule.js +5 -5
  74. package/dist/react/TextureSelect.d.ts +24 -0
  75. package/dist/react/TextureSelect.js +74 -0
  76. package/dist/react/TextureUVField.d.ts +14 -0
  77. package/dist/react/TextureUVField.js +23 -0
  78. package/dist/react/TimelinePanel.d.ts +7 -7
  79. package/dist/react/TimelinePanel.js +11 -9
  80. package/dist/react/TimelineTrackRow.d.ts +3 -3
  81. package/dist/react/TimelineTrackRow.js +6 -6
  82. package/dist/react/TransportBar.d.ts +4 -4
  83. package/dist/react/TransportBar.js +3 -3
  84. package/dist/react/ValueField.d.ts +1 -1
  85. package/dist/react/ValueField.js +3 -3
  86. package/dist/react/behaviorModules.d.ts +4 -2
  87. package/dist/react/behaviorModules.js +52 -16
  88. package/dist/react/fields.d.ts +1 -0
  89. package/dist/react/fields.js +3 -3
  90. package/dist/react/index.d.ts +40 -38
  91. package/dist/react/index.js +25 -23
  92. package/dist/react/inspectorHints.js +31 -0
  93. package/dist/react/modules.d.ts +3 -1
  94. package/dist/react/modules.js +99 -100
  95. package/package.json +3 -2
  96. package/src/core/assetDragSession.ts +1 -1
  97. package/src/core/assetTree.ts +1 -1
  98. package/src/core/binding.ts +3 -3
  99. package/src/core/defaultEffect.ts +4 -4
  100. package/src/core/editorScene.ts +1 -1
  101. package/src/core/effectTree.ts +13 -101
  102. package/src/core/emitterShapeWireframe.ts +48 -16
  103. package/src/core/galleryDropMarker.ts +8 -8
  104. package/src/core/galleryLayout.ts +4 -4
  105. package/src/core/galleryPlayback.ts +1 -1
  106. package/src/core/galleryScene.ts +5 -5
  107. package/src/core/gallerySelectionMarker.ts +7 -7
  108. package/src/core/groups.ts +1 -1
  109. package/src/core/history.ts +1 -1
  110. package/src/core/indexedDbProjectStore.ts +1 -1
  111. package/src/core/lineShaders.ts +2 -2
  112. package/src/core/loadEffect.ts +3 -3
  113. package/src/core/pickAssetFiles.ts +2 -2
  114. package/src/core/sceneEntries.ts +5 -5
  115. package/src/core/scrub.ts +1 -1
  116. package/src/core/shapes.ts +1 -1
  117. package/src/core/systems.ts +2 -2
  118. package/src/core/textureExport.ts +1 -1
  119. package/src/core/timeline.ts +3 -3
  120. package/src/core/transport.ts +4 -4
  121. package/src/core/viewportPicker.ts +12 -11
  122. package/src/index.ts +21 -21
  123. package/src/react/AssetsPanel.tsx +6 -6
  124. package/src/react/ChoiceDialog.tsx +1 -1
  125. package/src/react/ContextMenu.tsx +1 -1
  126. package/src/react/CurveEditor.tsx +2 -2
  127. package/src/react/EditorWorkspace.tsx +5 -5
  128. package/src/react/EffectEditor.tsx +10 -8
  129. package/src/react/EffectEditorHost.tsx +64 -69
  130. package/src/react/EffectTreeView.tsx +4 -4
  131. package/src/react/GradientEditor.tsx +3 -3
  132. package/src/react/InspectorInfoIcon.tsx +3 -3
  133. package/src/react/InspectorLabel.tsx +3 -3
  134. package/src/react/MessageDialog.tsx +1 -1
  135. package/src/react/ModuleSection.tsx +4 -4
  136. package/src/react/ObjectModule.tsx +5 -5
  137. package/src/react/ProjectsPage.tsx +6 -6
  138. package/src/react/PromptDialog.tsx +1 -1
  139. package/src/react/ScenePanel.tsx +7 -7
  140. package/src/react/SubEmittersModule.tsx +6 -6
  141. package/src/react/TextureSelect.tsx +118 -0
  142. package/src/react/TextureUVField.tsx +62 -0
  143. package/src/react/TimelinePanel.tsx +17 -15
  144. package/src/react/TimelineTrackRow.tsx +9 -9
  145. package/src/react/TransportBar.tsx +7 -7
  146. package/src/react/ValueField.tsx +3 -3
  147. package/src/react/behaviorModules.tsx +211 -28
  148. package/src/react/fields.tsx +10 -3
  149. package/src/react/index.ts +41 -38
  150. package/src/react/inspectorHints.ts +40 -0
  151. package/src/react/modules.tsx +375 -131
package/CHANGELOG.md CHANGED
@@ -4,6 +4,69 @@ All notable changes to the `babylon.quarks-editor` package are documented here.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
5
5
  and the project follows the `quarks.core` 0.x version line.
6
6
 
7
+ ## [0.27.0] — 2026-10-23
8
+
9
+ A 0.27.0 dated 2026-10-21 was prepared but never published; its entries are folded in here.
10
+
11
+ ### Added
12
+
13
+ - Main module: "Warm-up time", shown while Prewarm is on, the seconds of the cycle simulated
14
+ before the first frame (`prewarmTime`; 0 warms up a whole cycle).
15
+ - **The Renderer module reaches the 0.25–0.26 texture features.** Map layout (UV channel,
16
+ tiling, offset, scroll), Flip, a Gradient map section (ramp and channel), an Erosion section
17
+ (mask with its own layout, softness, edge width and colour) and a UV distortion section (noise
18
+ with its layout, strength). The ramp, mask and noise are picked the way the texture is: from
19
+ the host's presets, a URL or a file.
20
+ - Renderer: a swept trail's side axis. A host geometry
21
+ (`GeometryOption`, `resolveGeometry`) may carry a second UV set, `uvs2`, which mesh particles
22
+ read on UV channel 1; the demo's glTF loader passes `TEXCOORD_1` through.
23
+ - Main: Scaling mode for world-space systems (shape only, or the hierarchy's motion too).
24
+ - Texture Sheet Animation: time mode (lifetime or FPS), FPS, loop or hold, random frame, cycles,
25
+ frame count and a random start frame.
26
+ - Attractor module: a point gravity (`GravityForce`) with its centre and strength.
27
+ - Emitter shape wireframes for the Box and Edge shapes: the box outline with its +Z emit
28
+ direction, and the edge line with its +Y emit direction.
29
+ - `TextureSelect` and `TextureUVField` components, exported from `babylon.quarks-editor/react`.
30
+ - Renderer module, trails: "Tessellation" and "Curve tension".
31
+
32
+ ### Changed
33
+
34
+ - Renderer, trails: "Texture mode" (stretch, distribute per segment, tile, repeat per segment),
35
+ "Texture scale", "Min vertex distance" and "Trail time", each left unset at its default so the
36
+ JSON stays sparse. The "Head width scale" row is gone: it wrote a `headWidthScale` field no
37
+ runtime read (the head's width comes from the Width over Trail module).
38
+ - **`TimelinePanelProps` and `TransportBarProps` take `RefObject<HTMLElement | null>` refs**, the
39
+ type `useRef<HTMLElement>(null)` returns, now that the editor compiles with `strict`. Code that
40
+ passed the old type still compiles.
41
+
42
+ ### Fixed
43
+
44
+ - An imported effect whose images were inlined from the picked files is saved with those inlined
45
+ images; `serializeEffect` writes a texture's source path only while the texture still shows the
46
+ image it was loaded with, and an inlined image has no path. A test covers import → scene → save.
47
+ - **Saving lost mesh nodes and texture settings.** The editor now saves through `serializeEffect`:
48
+ mesh nodes keep their geometry (so mesh surface emitters still emit after a save and reload),
49
+ and textures keep their wrap, sampling, repeat, offset and rotation. Textures with no url a file
50
+ can point at are reported instead of written as an empty entry.
51
+ - The yellow hover ring stayed on the ground after the hovered effect was removed from the scene.
52
+ - **Node ESM and webpack 5 could not import the package.** `dist` was compiled with relative and
53
+ `@babylonjs` deep imports that had no file extension. Every import now ends in `.js`.
54
+ - The `babylon.quarks` peer range was `>=0.17.0`, but the editor needs current APIs (beam mode,
55
+ rim fade, culling and sort settings). It is now `~0.27.0`.
56
+
57
+ ### Internal
58
+
59
+ - Jest tests now cover the whole editor core (`src/core`): systems, material, timeline, scrub,
60
+ playback and transport, groups, collision, effect tree, assets and asset tree, scene entries,
61
+ gallery layout, playback, markers and picker, emitter shape wireframes, texture export and the
62
+ IndexedDB project store. The coverage threshold for the editor is raised to match.
63
+ - Editor tests resolve `babylon.quarks` to its sources, so they pick up library changes made in
64
+ the same branch without rebuilding it.
65
+ - React modules are tested in jsdom through the DOM, and the whole `EffectEditor` is mounted in
66
+ every render mode.
67
+ - The README leads with `QuarksEffectEditor.Show`, lists every peer and every module, and the
68
+ `EffectEditor` snippet carries all of its props.
69
+
7
70
  ## [0.26.0] — 2026-10-21
8
71
 
9
72
  ### Added
package/README.md CHANGED
@@ -7,11 +7,30 @@ Shuriken-style effect editor for [babylon.quarks](https://www.npmjs.com/package/
7
7
  ## Install
8
8
 
9
9
  ```bash
10
- npm install babylon.quarks-editor babylon.quarks @babylonjs/core react @heroicons/react @tanstack/react-virtual
10
+ npm install babylon.quarks-editor babylon.quarks @babylonjs/core @babylonjs/materials react react-dom @heroicons/react @tanstack/react-virtual
11
11
  ```
12
12
 
13
13
  ## Usage
14
14
 
15
+ The supported embed is the host: it owns the scene, the timeline, undo/redo, playback and the
16
+ module inspector, and mounts into any element.
17
+
18
+ ```ts
19
+ import {Texture} from '@babylonjs/core/Materials/Textures/texture';
20
+ import {QuarksEffectEditor} from 'babylon.quarks-editor/react';
21
+
22
+ const editor = QuarksEffectEditor.Show({
23
+ hostElement: document.getElementById('editor')!,
24
+ effectJson, // optional: a Quarks JSON export to open; a default effect otherwise
25
+ textureOptions: [{label: 'Glow', url: '/textures/glow.png'}],
26
+ resolveTexture: (url, scene) => new Texture(url, scene),
27
+ onSave: (json) => save(json), // the effect as Quarks JSON, round-trips through QuarksLoader
28
+ });
29
+ editor.dispose();
30
+ ```
31
+
32
+ The module inspector on its own, for a host that already has a scene and a selection:
33
+
15
34
  ```tsx
16
35
  import {EffectBinding, EffectHistory} from 'babylon.quarks-editor';
17
36
  import {EffectEditor} from 'babylon.quarks-editor/react';
@@ -20,13 +39,25 @@ const binding = new EffectBinding(particleSystem); // your babylon.quarks Partic
20
39
  const history = new EffectHistory();
21
40
  history.attach(binding);
22
41
 
23
- <EffectEditor binding={binding} />;
42
+ <EffectEditor
43
+ binding={binding}
44
+ selectedSystem={binding.system}
45
+ gizmoTargetNode={binding.system.emitter}
46
+ gizmoTargetLabel={binding.system.emitter.name}
47
+ />;
24
48
 
25
49
  // Serialize to Quarks JSON (round-trips through QuarksLoader):
26
50
  const json = binding.exportJSON('MyEffect');
27
51
  ```
28
52
 
29
- Modules: Main, Emission (bursts), Shape (7 emitter types), Speed/Limit Speed/Force/Rotation over Lifetime, Gravity, Color over Lifetime (gradient), Size over Lifetime (curve), Noise, Sub Emitters, Texture Sheet Animation, Renderer. Multi-system hierarchy with add/remove/rename, undo/redo via `EffectHistory`.
53
+ Modules, in Unity's order: Main, Emission (rate over time and distance, bursts), Shape (cone,
54
+ sphere, hemisphere, circle, donut, rectangle, grid, box, edge, mesh surface, point), Randomize
55
+ Direction, Velocity / Speed / Limit Speed / Force over Lifetime, Inherit Velocity, Gravity,
56
+ Attractor, Orbit over Lifetime, Color over Lifetime and by Speed, Size over Lifetime and by Speed,
57
+ Rotation over Lifetime (2D and 3D) and by Speed, Noise, Turbulence, Collision, Width over Trail,
58
+ Sub Emitters, Texture Sheet Animation, Sequences, Renderer (render modes incl. trail, mesh and
59
+ beam; material, texture layout, flip, gradient map, erosion, UV distortion, soft particles, sorting).
60
+ Multi-system hierarchy with add/remove/rename, undo/redo via `EffectHistory`.
30
61
 
31
62
  The `.` export is UI-free (edit model, serialization, value/shape/gradient helpers) — usable headless. The `./react` export ships the components; React is a peer dependency, so the same UI can mount in a standalone page or inside a [BabylonJS Editor](https://github.com/BabylonJS/Editor) plugin.
32
63
 
@@ -39,9 +70,11 @@ an opinion on your CDN, asset pipeline, or loader (`@babylonjs/loaders`, your ow
39
70
  "Load from file…" in the Renderer module) into whatever object you assign as `system.texture`.
40
71
  - `resolveGeometry(file) => Promise<GeometryData>` — turns a picked file (from "Load from file…"
41
72
  in the Mesh render mode's geometry dropdown) into vertex buffers: `{positions, indices?, uvs?,
42
- normals?}` (typed arrays or plain number arrays).
73
+ uvs2?, normals?}` (typed arrays or plain number arrays; `uvs2` is the second UV set textures on
74
+ UV channel 1 read).
43
75
  - `textureOptions?: {label, url}[]` / `geometryOptions?: {label, positions, indices?, uvs?,
44
- normals?}[]` — preset lists shown alongside "Custom URL…" / "Load from file…".
76
+ uvs2?, normals?}[]` — preset lists shown alongside "Custom URL…" / "Load from file…". The texture
77
+ presets also serve the gradient map's ramp, the erosion mask and the distortion noise.
45
78
 
46
79
  **Signature differs by entry point** — a common source of "works in the demo, throws in my
47
80
  host" bugs:
@@ -1,4 +1,4 @@
1
- import type { AssetDragPayload } from './assetTree';
1
+ import type { AssetDragPayload } from './assetTree.js';
2
2
  /** Stores the current asset drag payload for viewport preview (getData is drop-only in most browsers). */
3
3
  export declare function setAssetDragSession(payload: AssetDragPayload | null): void;
4
4
  export declare function getAssetDragSession(): AssetDragPayload | null;
@@ -1,4 +1,4 @@
1
- import type { EffectAsset } from './assets';
1
+ import type { EffectAsset } from './assets.js';
2
2
  export interface AssetTreeFolder {
3
3
  kind: 'folder';
4
4
  name: string;
@@ -1,6 +1,6 @@
1
- import type { TransformNode } from '@babylonjs/core/Meshes/transformNode';
1
+ import type { TransformNode } from '@babylonjs/core/Meshes/transformNode.js';
2
2
  import type { ParticleSystem } from 'babylon.quarks';
3
- import type { EffectTreeNode } from './effectTree';
3
+ import type { EffectTreeNode } from './effectTree.js';
4
4
  export type EditorListener = () => void;
5
5
  /**
6
6
  * Headless edit-model over a live ParticleSystem: mutate through apply() so every
@@ -1,4 +1,4 @@
1
- import { buildEffectTree, serializeEffectTree } from './effectTree';
1
+ import { buildEffectTree, serializeEffectTree } from './effectTree.js';
2
2
  /**
3
3
  * Headless edit-model over a live ParticleSystem: mutate through apply() so every
4
4
  * change notifies subscribers (React UI, save indicators, hosts like BabylonJS Editor).
@@ -1,6 +1,6 @@
1
- import type { Scene } from '@babylonjs/core/scene';
1
+ import type { Scene } from '@babylonjs/core/scene.js';
2
2
  import { ParticleSystem } from 'babylon.quarks';
3
- import { type SceneEntry } from './sceneEntries';
3
+ import { type SceneEntry } from './sceneEntries.js';
4
4
  export interface CreateSceneDefaultOptions {
5
5
  resolveTexture?: (url: string, scene: Scene) => unknown;
6
6
  textureUrl?: string;
@@ -1,7 +1,7 @@
1
- import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
1
+ import { TransformNode } from '@babylonjs/core/Meshes/transformNode.js';
2
2
  import { ColorOverLife, ConeEmitter, ConstantColor, ConstantValue, IntervalValue, ParticleSystem, RenderMode, Vector4, } from 'babylon.quarks';
3
- import { DEFAULT_GRADIENT_STOPS, buildGradient } from './colors';
4
- import { uniqueEntryName } from './sceneEntries';
3
+ import { DEFAULT_GRADIENT_STOPS, buildGradient } from './colors.js';
4
+ import { uniqueEntryName } from './sceneEntries.js';
5
5
  /** Creates a simple cone burst preset for new editor effects. */
6
6
  export function createDefaultEffect(scene, resolveTexture, textureUrl) {
7
7
  const system = new ParticleSystem({
@@ -1,4 +1,4 @@
1
- import type { TransformNode } from '@babylonjs/core/Meshes/transformNode';
1
+ import type { TransformNode } from '@babylonjs/core/Meshes/transformNode.js';
2
2
  /** Marks meshes/nodes created only for editor interaction or overlays (not part of the effect). */
3
3
  export declare const EDITOR_SCENE_NODE_METADATA: {
4
4
  quarksEditorSceneNode: true;
@@ -1,4 +1,4 @@
1
- import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
1
+ import { TransformNode } from '@babylonjs/core/Meshes/transformNode.js';
2
2
  import type { ParticleSystem } from 'babylon.quarks';
3
3
  /** A node in the editor's effect hierarchy: a Group (organizational) or a ParticleEmitter. */
4
4
  export interface EffectTreeNode {
@@ -1,7 +1,6 @@
1
- import { Matrix, Quaternion } from '@babylonjs/core/Maths/math.vector';
2
- import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
3
- import { ParticleEmitter } from 'babylon.quarks';
4
- import { isEditorSceneNode } from './editorScene';
1
+ import { TransformNode } from '@babylonjs/core/Meshes/transformNode.js';
2
+ import { ParticleEmitter, serializeEffect } from 'babylon.quarks';
3
+ import { isEditorSceneNode } from './editorScene.js';
5
4
  /** Walks a loaded root TransformNode into the editor tree (groups + emitters). */
6
5
  export function buildEffectTree(root, depth = 0) {
7
6
  const system = root instanceof ParticleEmitter ? root.system : undefined;
@@ -31,100 +30,26 @@ export function collectSystems(tree) {
31
30
  walk(tree);
32
31
  return out;
33
32
  }
34
- function localMatrix(node) {
35
- const rotation = node.rotationQuaternion ?? Quaternion.FromEulerVector(node.rotation);
36
- return Matrix.Compose(node.scaling, rotation, node.position).asArray().slice();
37
- }
38
- function serializeNode(tree, meta, fallbackUuid) {
39
- // Emitter nodes must serialize with the ParticleEmitter's own `.uuid` — that is exactly
40
- // what EmitSubParticleSystem.toJSON writes as its target reference, so the two match and
41
- // QuarksLoader.linkReferences can re-resolve sub-emitters on reimport.
42
- const uuid = tree.system
43
- ? tree.node.uuid
44
- : (tree.node._quarksUUID ?? fallbackUuid);
45
- const children = tree.children.map((child, i) => serializeNode(child, meta, `${fallbackUuid}-${i}`));
46
- const base = {
47
- uuid,
48
- name: tree.name,
49
- layers: 1,
50
- matrix: localMatrix(tree.node),
51
- children: children.length > 0 ? children : undefined,
52
- };
53
- if (tree.system) {
54
- return {
55
- ...base,
56
- type: 'ParticleEmitter',
57
- ps: tree.system.toJSON(meta, { useUrlForImage: true }),
58
- };
59
- }
60
- return { ...base, type: 'Group' };
33
+ /** Writes the effect under `roots`, leaving out the editor's own helper nodes. */
34
+ function serialize(roots, name) {
35
+ const json = serializeEffect(roots, {
36
+ name,
37
+ generator: 'babylon.quarks-editor',
38
+ filter: (node) => !isEditorSceneNode(node),
39
+ });
40
+ return JSON.stringify(json, null, 2);
61
41
  }
62
42
  /**
63
43
  * Serializes an effect tree into the Quarks JSON envelope understood by QuarksLoader,
64
44
  * preserving the group/emitter hierarchy (not flattened).
65
45
  */
66
46
  export function serializeEffectTree(root, name = 'effect') {
67
- const meta = { textures: {}, materials: {}, geometries: {} };
68
- const object = serializeNode(buildEffectTree(root), meta, `${name}-node`);
69
- return finishEnvelope(object, meta);
47
+ return serialize(root, name);
70
48
  }
71
49
  /**
72
50
  * Serializes several effect roots as children of a synthetic group — used when an effect
73
51
  * spans multiple top-level trees (e.g. a demo running several loaded effects at once).
74
52
  */
75
53
  export function serializeEffectForest(roots, name = 'effect') {
76
- if (roots.length === 1) {
77
- return serializeEffectTree(roots[0], name);
78
- }
79
- const meta = { textures: {}, materials: {}, geometries: {} };
80
- const children = roots.map((root, i) => serializeNode(buildEffectTree(root), meta, `${name}-node-${i}`));
81
- const object = {
82
- uuid: `${name}-root`,
83
- name,
84
- type: 'Group',
85
- layers: 1,
86
- matrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
87
- children,
88
- };
89
- return finishEnvelope(object, meta);
90
- }
91
- /** Flattens accumulated meta and wraps the serialized object into the Quarks envelope. */
92
- function finishEnvelope(object, meta) {
93
- // toJSON stashes live Texture instances and material records in meta; flatten them into
94
- // the serializable images/textures/materials arrays QuarksLoader expects.
95
- const images = [];
96
- const textures = [];
97
- for (const [uuid, tex] of Object.entries(meta.textures)) {
98
- const url = tex?.url ?? tex?.name;
99
- const invertY = typeof tex?.invertY === 'boolean' ? tex.invertY : undefined;
100
- const isEnvAtlas = tex?.name === 'quarksEnvAtlas' || (typeof tex?.name === 'string' && tex.name.endsWith('_envAtlas'));
101
- if (url) {
102
- const imageUuid = `${uuid}-image`;
103
- images.push({ uuid: imageUuid, url });
104
- const entry = {
105
- uuid,
106
- image: imageUuid,
107
- };
108
- if (invertY === false || isEnvAtlas) {
109
- entry.invertY = false;
110
- entry.noMipmap = true;
111
- }
112
- textures.push(entry);
113
- }
114
- else {
115
- textures.push({ uuid });
116
- }
117
- }
118
- const materials = Object.values(meta.materials).map((material) => {
119
- const { sourceMaterial: _sourceMaterial, ...serializable } = material;
120
- return serializable;
121
- });
122
- return JSON.stringify({
123
- metadata: { version: 4.5, type: 'Object3D', generator: 'babylon.quarks-editor' },
124
- geometries: Object.values(meta.geometries),
125
- materials,
126
- textures,
127
- images,
128
- object,
129
- }, null, 2);
54
+ return serialize(roots.length === 1 ? roots[0] : roots, name);
130
55
  }
@@ -1,6 +1,6 @@
1
- import type { Scene } from '@babylonjs/core/scene';
1
+ import type { Scene } from '@babylonjs/core/scene.js';
2
2
  import type { ParticleSystem } from 'babylon.quarks';
3
- import './lineShaders';
3
+ import './lineShaders.js';
4
4
  /**
5
5
  * Optional wireframe overlays for every emitter shape in the current effect.
6
6
  * Geometry follows quarks.core spawn math (local XY spawn plane, +Z emit axis).
@@ -1,10 +1,10 @@
1
- import { Color3 } from '@babylonjs/core/Maths/math.color';
2
- import { Vector3 } from '@babylonjs/core/Maths/math.vector';
3
- import { MeshBuilder } from '@babylonjs/core/Meshes/meshBuilder';
4
- import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
5
- import { EDITOR_SCENE_NODE_METADATA } from './editorScene';
6
- import './lineShaders';
7
- import { getShapeType, readShapeParams } from './shapes';
1
+ import { Color3 } from '@babylonjs/core/Maths/math.color.js';
2
+ import { Vector3 } from '@babylonjs/core/Maths/math.vector.js';
3
+ import { MeshBuilder } from '@babylonjs/core/Meshes/meshBuilder.js';
4
+ import { TransformNode } from '@babylonjs/core/Meshes/transformNode.js';
5
+ import { EDITOR_SCENE_NODE_METADATA } from './editorScene.js';
6
+ import './lineShaders.js';
7
+ import { getShapeType, readShapeParams } from './shapes.js';
8
8
  const LINE_COLOR = new Color3(0.45, 0.82, 1);
9
9
  const DIRECTION_COLOR = new Color3(0.75, 0.92, 1);
10
10
  /**
@@ -204,6 +204,38 @@ function buildEmitterWireframe(system, scene) {
204
204
  }
205
205
  break;
206
206
  }
207
+ case 'box': {
208
+ // Spawn anywhere in the box (volume), on its faces (shell) or along its edges;
209
+ // velocity straight +Z, like Unity's Box shape.
210
+ const halfW = params.width / 2;
211
+ const halfH = params.height / 2;
212
+ const halfD = params.depth / 2;
213
+ lines('wf-box-front', xyRect(params.width, params.height, halfD));
214
+ lines('wf-box-back', xyRect(params.width, params.height, -halfD));
215
+ const corners = [
216
+ [-halfW, -halfH],
217
+ [halfW, -halfH],
218
+ [halfW, halfH],
219
+ [-halfW, halfH],
220
+ ];
221
+ corners.forEach(([x, y], i) => {
222
+ lines(`wf-box-side-${i}`, [new Vector3(x, y, -halfD), new Vector3(x, y, halfD)]);
223
+ });
224
+ const ray = Math.max(halfD * 2, 0.25);
225
+ lines('wf-box-dir', [new Vector3(0, 0, halfD), new Vector3(0, 0, halfD + ray)], DIRECTION_COLOR);
226
+ break;
227
+ }
228
+ case 'edge': {
229
+ // Spawn on a line along X of half-length `radius`; velocity straight +Y.
230
+ const radius = Math.max(params.radius, 0.001);
231
+ lines('wf-edge', [new Vector3(-radius, 0, 0), new Vector3(radius, 0, 0)]);
232
+ const ray = Math.max(radius * 0.5, 0.25);
233
+ for (let i = 0; i <= 4; i++) {
234
+ const x = -radius + (i / 4) * radius * 2;
235
+ lines(`wf-edge-ray-${i}`, [new Vector3(x, 0, 0), new Vector3(x, ray, 0)], DIRECTION_COLOR);
236
+ }
237
+ break;
238
+ }
207
239
  case 'mesh_surface': {
208
240
  const source = params.mesh;
209
241
  if (source && !source.isDisposed()) {
@@ -305,15 +337,15 @@ function xyArc(radius, arc, segments = 48, z = 0) {
305
337
  function arcPoint(radius, theta, z = 0) {
306
338
  return new Vector3(Math.cos(theta) * radius, Math.sin(theta) * radius, z);
307
339
  }
308
- function xyRect(width, height) {
340
+ function xyRect(width, height, z = 0) {
309
341
  const halfW = width / 2;
310
342
  const halfH = height / 2;
311
343
  return [
312
- new Vector3(-halfW, -halfH, 0),
313
- new Vector3(halfW, -halfH, 0),
314
- new Vector3(halfW, halfH, 0),
315
- new Vector3(-halfW, halfH, 0),
316
- new Vector3(-halfW, -halfH, 0),
344
+ new Vector3(-halfW, -halfH, z),
345
+ new Vector3(halfW, -halfH, z),
346
+ new Vector3(halfW, halfH, z),
347
+ new Vector3(-halfW, halfH, z),
348
+ new Vector3(-halfW, -halfH, z),
317
349
  ];
318
350
  }
319
351
  function clampArc(arc) {
@@ -1,5 +1,5 @@
1
- import { Vector3 } from '@babylonjs/core/Maths/math.vector';
2
- import type { Scene } from '@babylonjs/core/scene';
1
+ import { Vector3 } from '@babylonjs/core/Maths/math.vector.js';
2
+ import type { Scene } from '@babylonjs/core/scene.js';
3
3
  /** Ground decal shown while dragging gallery items over the viewport. */
4
4
  export declare class GalleryDropMarker {
5
5
  private readonly root;
@@ -1,9 +1,9 @@
1
- import { StandardMaterial } from '@babylonjs/core/Materials/standardMaterial';
2
- import { Color3 } from '@babylonjs/core/Maths/math.color';
3
- import { Mesh } from '@babylonjs/core/Meshes/mesh';
4
- import { MeshBuilder } from '@babylonjs/core/Meshes/meshBuilder';
5
- import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
6
- import { DEFAULT_GALLERY_SPACING, gallerySpan } from './galleryLayout';
1
+ import { StandardMaterial } from '@babylonjs/core/Materials/standardMaterial.js';
2
+ import { Color3 } from '@babylonjs/core/Maths/math.color.js';
3
+ import { Mesh } from '@babylonjs/core/Meshes/mesh.js';
4
+ import { MeshBuilder } from '@babylonjs/core/Meshes/meshBuilder.js';
5
+ import { TransformNode } from '@babylonjs/core/Meshes/transformNode.js';
6
+ import { DEFAULT_GALLERY_SPACING, gallerySpan } from './galleryLayout.js';
7
7
  /** Ground decal shown while dragging gallery items over the viewport. */
8
8
  export class GalleryDropMarker {
9
9
  constructor(scene) {
@@ -1,6 +1,6 @@
1
- import type { ArcRotateCamera } from '@babylonjs/core/Cameras/arcRotateCamera';
2
- import type { Mesh } from '@babylonjs/core/Meshes/mesh';
3
- import type { TransformNode } from '@babylonjs/core/Meshes/transformNode';
1
+ import type { ArcRotateCamera } from '@babylonjs/core/Cameras/arcRotateCamera.js';
2
+ import type { Mesh } from '@babylonjs/core/Meshes/mesh.js';
3
+ import type { TransformNode } from '@babylonjs/core/Meshes/transformNode.js';
4
4
  /** Default gap between neighbouring effects in a folder gallery import. */
5
5
  export declare const DEFAULT_GALLERY_SPACING = 4;
6
6
  /** Square-grid column count for `count` items. */
@@ -1,4 +1,4 @@
1
- import { Vector3 } from '@babylonjs/core/Maths/math.vector';
1
+ import { Vector3 } from '@babylonjs/core/Maths/math.vector.js';
2
2
  /** Default gap between neighbouring effects in a folder gallery import. */
3
3
  export const DEFAULT_GALLERY_SPACING = 4;
4
4
  /** Square-grid column count for `count` items. */
@@ -1,4 +1,4 @@
1
- import type { SceneEntry } from './sceneEntries';
1
+ import type { SceneEntry } from './sceneEntries.js';
2
2
  /** Freezes every scene entry; unpauses the visible ones (the preview set) when transport is playing. */
3
3
  export declare function applyGalleryPlayback(entries: SceneEntry[], previewRootIds: ReadonlySet<number>, transportPlaying: boolean, focusFinishedRootIds?: ReadonlySet<number>): void;
4
4
  /** Whether any preview effect on stage should keep simulating. */
@@ -1,8 +1,8 @@
1
- import '@babylonjs/core/Culling/ray';
2
- import { Vector3 } from '@babylonjs/core/Maths/math.vector';
3
- import type { Scene } from '@babylonjs/core/scene';
1
+ import '@babylonjs/core/Culling/ray.js';
2
+ import { Vector3 } from '@babylonjs/core/Maths/math.vector.js';
3
+ import type { Scene } from '@babylonjs/core/scene.js';
4
4
  import type { BatchedRenderer } from 'babylon.quarks';
5
- import type { SceneEntry } from './sceneEntries';
5
+ import type { SceneEntry } from './sceneEntries.js';
6
6
  /** Clears live particles and freezes simulation for one scene entry. */
7
7
  export declare function clearSceneEntrySimulation(entry: SceneEntry, renderer: BatchedRenderer): void;
8
8
  /**
@@ -1,6 +1,6 @@
1
- import '@babylonjs/core/Culling/ray';
2
- import { Vector3 } from '@babylonjs/core/Maths/math.vector';
3
- import { DEFAULT_GALLERY_SPACING, galleryGridPosition } from './galleryLayout';
1
+ import '@babylonjs/core/Culling/ray.js';
2
+ import { Vector3 } from '@babylonjs/core/Maths/math.vector.js';
3
+ import { DEFAULT_GALLERY_SPACING, galleryGridPosition } from './galleryLayout.js';
4
4
  /** Clears live particles and freezes simulation for one scene entry. */
5
5
  export function clearSceneEntrySimulation(entry, renderer) {
6
6
  for (const system of entry.systems) {
@@ -1,7 +1,7 @@
1
- import { Color3 } from '@babylonjs/core/Maths/math.color';
2
- import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
3
- import type { Scene } from '@babylonjs/core/scene';
4
- import './lineShaders';
1
+ import { Color3 } from '@babylonjs/core/Maths/math.color.js';
2
+ import { TransformNode } from '@babylonjs/core/Meshes/transformNode.js';
3
+ import type { Scene } from '@babylonjs/core/scene.js';
4
+ import './lineShaders.js';
5
5
  export declare const GROUND_RING_RADIUS = 1.2;
6
6
  /** Flat ground ring that tracks a transform on the XZ plane. */
7
7
  declare class GroundRingMarker {
@@ -1,8 +1,8 @@
1
- import { Color3 } from '@babylonjs/core/Maths/math.color';
2
- import { Vector3 } from '@babylonjs/core/Maths/math.vector';
3
- import { MeshBuilder } from '@babylonjs/core/Meshes/meshBuilder';
4
- import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
5
- import './lineShaders';
1
+ import { Color3 } from '@babylonjs/core/Maths/math.color.js';
2
+ import { Vector3 } from '@babylonjs/core/Maths/math.vector.js';
3
+ import { MeshBuilder } from '@babylonjs/core/Meshes/meshBuilder.js';
4
+ import { TransformNode } from '@babylonjs/core/Meshes/transformNode.js';
5
+ import './lineShaders.js';
6
6
  export const GROUND_RING_RADIUS = 1.2;
7
7
  const RING_SEGMENTS = 64;
8
8
  const RING_Y = 0.03;
@@ -1,4 +1,4 @@
1
- import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
1
+ import { TransformNode } from '@babylonjs/core/Meshes/transformNode.js';
2
2
  /**
3
3
  * Groups the given nodes under a new organizational TransformNode. Each node's world
4
4
  * transform is preserved via `setParent` — grouping never moves anything visually, it only
@@ -1,4 +1,4 @@
1
- import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
1
+ import { TransformNode } from '@babylonjs/core/Meshes/transformNode.js';
2
2
  /**
3
3
  * Groups the given nodes under a new organizational TransformNode. Each node's world
4
4
  * transform is preserved via `setParent` — grouping never moves anything visually, it only
@@ -1,4 +1,4 @@
1
- import type { EffectBinding } from './binding';
1
+ import type { EffectBinding } from './binding.js';
2
2
  /** Undo/redo stacks of one effect, kept by the host while another effect is focused. */
3
3
  export interface HistoryStacks {
4
4
  undo: string[];
@@ -1,4 +1,4 @@
1
- import type { ProjectMeta, ProjectStore, StorageEstimate, StoredAsset, StoredScene } from './projectStore';
1
+ import type { ProjectMeta, ProjectStore, StorageEstimate, StoredAsset, StoredScene } from './projectStore.js';
2
2
  /**
3
3
  * Projects in the browser's IndexedDB. Texture data URIs are pulled out of the effect JSON into
4
4
  * a per-project file store keyed by content hash — 50 effects sharing a texture store it once —
@@ -1,2 +1,2 @@
1
- import '@babylonjs/core/Shaders/color.fragment';
2
- import '@babylonjs/core/Shaders/color.vertex';
1
+ import '@babylonjs/core/Shaders/color.fragment.js';
2
+ import '@babylonjs/core/Shaders/color.vertex.js';
@@ -3,5 +3,5 @@
3
3
  // Babylon never looks, it falls back to fetching `src/Shaders/color.fragment.fx`, gets the
4
4
  // page's HTML back and the selection rings never render. Registering the shader up front, from
5
5
  // the same module graph as the rest of the editor, makes the lookup succeed either way.
6
- import '@babylonjs/core/Shaders/color.fragment';
7
- import '@babylonjs/core/Shaders/color.vertex';
6
+ import '@babylonjs/core/Shaders/color.fragment.js';
7
+ import '@babylonjs/core/Shaders/color.vertex.js';
@@ -1,5 +1,5 @@
1
- import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
2
- import type { Scene } from '@babylonjs/core/scene';
1
+ import { TransformNode } from '@babylonjs/core/Meshes/transformNode.js';
2
+ import type { Scene } from '@babylonjs/core/scene.js';
3
3
  import type { BatchedRenderer, ParticleSystem } from 'babylon.quarks';
4
4
  export interface LoadEffectOptions {
5
5
  /** Registers systems with the batched renderer (default true). */
@@ -1,4 +1,4 @@
1
- import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
1
+ import { TransformNode } from '@babylonjs/core/Meshes/transformNode.js';
2
2
  import { ParticleEmitter, QuarksLoader, QuarksUtil } from 'babylon.quarks';
3
3
  /** `ParticleEmitter`'s constructor default (see babylon.quarks/src/ParticleEmitter.ts) — the
4
4
  * name every emitter gets when the source JSON didn't give it one of its own. */
@@ -1,4 +1,4 @@
1
- import { isAssetImportFile, relativePathOf } from './assets';
1
+ import { isAssetImportFile, relativePathOf } from './assets.js';
2
2
  /**
3
3
  * Recursively collects effect `.json` files and the image files they may reference from a
4
4
  * directory handle (File System Access API).
@@ -1,8 +1,8 @@
1
- import type { TransformNode } from '@babylonjs/core/Meshes/transformNode';
2
- import type { Scene } from '@babylonjs/core/scene';
1
+ import type { TransformNode } from '@babylonjs/core/Meshes/transformNode.js';
2
+ import type { Scene } from '@babylonjs/core/scene.js';
3
3
  import type { BatchedRenderer, ParticleSystem } from 'babylon.quarks';
4
- import type { EffectAsset } from './assets';
5
- import { EffectBinding } from './binding';
4
+ import type { EffectAsset } from './assets.js';
5
+ import { EffectBinding } from './binding.js';
6
6
  /**
7
7
  * One effect instance placed in the scene. Every instance is live (its systems are registered
8
8
  * with the renderer) and independent: dropping the same asset three times gives three entries
@@ -1,6 +1,6 @@
1
1
  import { QuarksUtil } from 'babylon.quarks';
2
- import { EffectBinding } from './binding';
3
- import { disposeLoadedEffect, parseEffectFromJson } from './loadEffect';
2
+ import { EffectBinding } from './binding.js';
3
+ import { disposeLoadedEffect, parseEffectFromJson } from './loadEffect.js';
4
4
  /** `name`, or `name (2)`, `name (3)`… when an entry already uses it. */
5
5
  export function uniqueEntryName(name, existing) {
6
6
  const taken = new Set(existing.map((entry) => entry.name));