babylon.quarks-editor 0.18.0 → 0.19.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 (83) hide show
  1. package/README.md +6 -6
  2. package/dist/core/binding.d.ts +1 -1
  3. package/dist/core/colors.d.ts +1 -1
  4. package/dist/core/defaultEffect.d.ts +1 -1
  5. package/dist/core/effectTree.d.ts +1 -1
  6. package/dist/core/effectTree.js +18 -4
  7. package/dist/core/emitterShapeWireframe.d.ts +1 -1
  8. package/dist/core/emitterShapeWireframe.js +1 -1
  9. package/dist/core/galleryDropMarker.js +1 -1
  10. package/dist/core/galleryScene.d.ts +1 -1
  11. package/dist/core/galleryTree.js +4 -1
  12. package/dist/core/loadEffect.d.ts +2 -2
  13. package/dist/core/loadEffect.js +1 -1
  14. package/dist/core/loadEffectGallery.d.ts +2 -2
  15. package/dist/core/shapes.d.ts +1 -1
  16. package/dist/core/shapes.js +35 -8
  17. package/dist/core/systems.d.ts +1 -1
  18. package/dist/core/systems.js +3 -1
  19. package/dist/core/timeline.d.ts +1 -2
  20. package/dist/core/values.d.ts +1 -1
  21. package/dist/index.d.ts +11 -11
  22. package/dist/index.js +6 -6
  23. package/dist/react/CurveEditor.d.ts +1 -2
  24. package/dist/react/CurveEditor.js +14 -3
  25. package/dist/react/EffectEditor.d.ts +2 -2
  26. package/dist/react/EffectEditor.js +2 -2
  27. package/dist/react/EffectEditorHost.d.ts +1 -1
  28. package/dist/react/EffectEditorHost.js +123 -33
  29. package/dist/react/GalleryPanel.d.ts +1 -1
  30. package/dist/react/GalleryPanel.js +14 -7
  31. package/dist/react/GradientEditor.d.ts +1 -2
  32. package/dist/react/GradientEditor.js +8 -1
  33. package/dist/react/MessageDialog.js +7 -1
  34. package/dist/react/ModuleSection.js +10 -3
  35. package/dist/react/ObjectModule.d.ts +1 -2
  36. package/dist/react/ObjectModule.js +1 -1
  37. package/dist/react/SubEmittersModule.d.ts +1 -2
  38. package/dist/react/TimelinePanel.d.ts +3 -3
  39. package/dist/react/TimelinePanel.js +55 -9
  40. package/dist/react/TimelineTrackRow.d.ts +1 -1
  41. package/dist/react/TimelineTrackRow.js +20 -6
  42. package/dist/react/ValueField.d.ts +1 -2
  43. package/dist/react/behaviorModules.js +19 -5
  44. package/dist/react/index.d.ts +17 -17
  45. package/dist/react/index.js +11 -11
  46. package/dist/react/modules.js +55 -25
  47. package/package.json +68 -68
  48. package/src/core/binding.ts +2 -2
  49. package/src/core/colors.ts +1 -1
  50. package/src/core/defaultEffect.ts +1 -1
  51. package/src/core/effectTree.ts +22 -7
  52. package/src/core/emitterShapeWireframe.ts +8 -4
  53. package/src/core/galleryDropMarker.ts +6 -2
  54. package/src/core/galleryPlayback.ts +1 -1
  55. package/src/core/galleryScene.ts +3 -7
  56. package/src/core/gallerySelectionMarker.ts +5 -1
  57. package/src/core/galleryTree.ts +8 -5
  58. package/src/core/loadEffect.ts +3 -3
  59. package/src/core/loadEffectGallery.ts +7 -3
  60. package/src/core/pickGalleryFiles.ts +4 -1
  61. package/src/core/shapes.ts +48 -10
  62. package/src/core/systems.ts +9 -3
  63. package/src/core/timeline.ts +8 -8
  64. package/src/core/values.ts +1 -1
  65. package/src/core/viewportPicker.ts +1 -1
  66. package/src/index.ts +18 -18
  67. package/src/react/CurveEditor.tsx +40 -10
  68. package/src/react/EffectEditor.tsx +14 -13
  69. package/src/react/EffectEditorHost.tsx +239 -64
  70. package/src/react/GalleryPanel.tsx +56 -12
  71. package/src/react/GradientEditor.tsx +21 -4
  72. package/src/react/InspectorInfoIcon.tsx +6 -2
  73. package/src/react/MessageDialog.tsx +9 -1
  74. package/src/react/ModuleSection.tsx +12 -3
  75. package/src/react/ObjectModule.tsx +36 -15
  76. package/src/react/PromptDialog.tsx +5 -1
  77. package/src/react/SubEmittersModule.tsx +1 -2
  78. package/src/react/TimelinePanel.tsx +94 -21
  79. package/src/react/TimelineTrackRow.tsx +71 -18
  80. package/src/react/ValueField.tsx +0 -1
  81. package/src/react/behaviorModules.tsx +143 -29
  82. package/src/react/index.ts +33 -33
  83. package/src/react/modules.tsx +173 -57
@@ -1,13 +1,13 @@
1
- import type {ParticleSystem} from 'babylon.quarks';
2
1
  import {Color3} from '@babylonjs/core/Maths/math.color';
3
2
  import {Vector3} from '@babylonjs/core/Maths/math.vector';
4
- import {Mesh} from '@babylonjs/core/Meshes/mesh';
5
3
  import {LinesMesh} from '@babylonjs/core/Meshes/linesMesh';
4
+ import {Mesh} from '@babylonjs/core/Meshes/mesh';
6
5
  import {MeshBuilder} from '@babylonjs/core/Meshes/meshBuilder';
7
6
  import {TransformNode} from '@babylonjs/core/Meshes/transformNode';
8
7
  import type {Scene} from '@babylonjs/core/scene';
9
- import {getShapeType, readShapeParams} from './shapes';
8
+ import type {ParticleSystem} from 'babylon.quarks';
10
9
  import {EDITOR_SCENE_NODE_METADATA} from './editorScene';
10
+ import {getShapeType, readShapeParams} from './shapes';
11
11
 
12
12
  const LINE_COLOR = new Color3(0.45, 0.82, 1);
13
13
  const DIRECTION_COLOR = new Color3(0.75, 0.92, 1);
@@ -125,7 +125,11 @@ function buildEmitterWireframe(system: ParticleSystem, scene: Scene): Mesh[] {
125
125
  for (let i = 0; i <= spokeCount; i++) {
126
126
  const theta = (i / spokeCount) * arc;
127
127
  const rim = arcPoint(radius, theta);
128
- const dir = new Vector3(Math.sin(angle) * Math.cos(theta), Math.sin(angle) * Math.sin(theta), Math.cos(angle));
128
+ const dir = new Vector3(
129
+ Math.sin(angle) * Math.cos(theta),
130
+ Math.sin(angle) * Math.sin(theta),
131
+ Math.cos(angle)
132
+ );
129
133
  const travel = preview / Math.max(Math.cos(angle), 0.05);
130
134
  lines(`wf-cone-ray-${i}`, [rim, rim.add(dir.scale(travel))], DIRECTION_COLOR);
131
135
  }
@@ -1,6 +1,6 @@
1
+ import {StandardMaterial} from '@babylonjs/core/Materials/standardMaterial';
1
2
  import {Color3} from '@babylonjs/core/Maths/math.color';
2
3
  import {Vector3} from '@babylonjs/core/Maths/math.vector';
3
- import {StandardMaterial} from '@babylonjs/core/Materials/standardMaterial';
4
4
  import {Mesh} from '@babylonjs/core/Meshes/mesh';
5
5
  import {MeshBuilder} from '@babylonjs/core/Meshes/meshBuilder';
6
6
  import {TransformNode} from '@babylonjs/core/Meshes/transformNode';
@@ -15,7 +15,11 @@ export class GalleryDropMarker {
15
15
 
16
16
  constructor(scene: Scene) {
17
17
  this.root = new TransformNode('gallery-drop-root', scene);
18
- this.disc = MeshBuilder.CreateDisc('gallery-drop-fill', {radius: 1, tessellation: 48, sideOrientation: Mesh.DOUBLESIDE}, scene);
18
+ this.disc = MeshBuilder.CreateDisc(
19
+ 'gallery-drop-fill',
20
+ {radius: 1, tessellation: 48, sideOrientation: Mesh.DOUBLESIDE},
21
+ scene
22
+ );
19
23
  this.disc.parent = this.root;
20
24
  this.disc.rotation.x = Math.PI / 2;
21
25
 
@@ -1,5 +1,5 @@
1
- import type {GalleryEntry} from './loadEffectGallery';
2
1
  import {QuarksUtil} from 'babylon.quarks';
2
+ import type {GalleryEntry} from './loadEffectGallery';
3
3
 
4
4
  function isPreviewEntry(entry: GalleryEntry, previewRootIds: ReadonlySet<number>): boolean {
5
5
  return entry.inScene && previewRootIds.has(entry.root.uniqueId);
@@ -1,9 +1,9 @@
1
1
  import {Vector3} from '@babylonjs/core/Maths/math.vector';
2
- import type {Scene} from '@babylonjs/core/scene';
3
2
  import type {TransformNode} from '@babylonjs/core/Meshes/transformNode';
3
+ import type {Scene} from '@babylonjs/core/scene';
4
4
  import type {BatchedRenderer} from 'babylon.quarks';
5
- import type {GalleryEntry} from './loadEffectGallery';
6
5
  import {DEFAULT_GALLERY_SPACING, galleryGridPosition} from './galleryLayout';
6
+ import type {GalleryEntry} from './loadEffectGallery';
7
7
 
8
8
  /** Clears live particles and freezes simulation for one placed catalog entry. */
9
9
  export function clearGalleryEntrySimulation(entry: GalleryEntry, renderer: BatchedRenderer): void {
@@ -15,11 +15,7 @@ export function clearGalleryEntrySimulation(entry: GalleryEntry, renderer: Batch
15
15
  }
16
16
 
17
17
  /** Parents a catalog entry into the live scene and registers its systems with the renderer. */
18
- export function placeGalleryEntryInScene(
19
- entry: GalleryEntry,
20
- renderer: BatchedRenderer,
21
- worldPosition: Vector3
22
- ): void {
18
+ export function placeGalleryEntryInScene(entry: GalleryEntry, renderer: BatchedRenderer, worldPosition: Vector3): void {
23
19
  entry.root.setEnabled(true);
24
20
  entry.root.parent = null;
25
21
  entry.root.position.copyFrom(worldPosition);
@@ -28,7 +28,11 @@ class GroundRingMarker {
28
28
 
29
29
  constructor(scene: Scene, name: string, color: Color3) {
30
30
  this.anchor = new TransformNode(`${name}-root`, scene);
31
- this.ring = MeshBuilder.CreateLines(`${name}-ring`, {points: buildGroundRingPoints(GROUND_RING_RADIUS)}, scene) as LinesMesh;
31
+ this.ring = MeshBuilder.CreateLines(
32
+ `${name}-ring`,
33
+ {points: buildGroundRingPoints(GROUND_RING_RADIUS)},
34
+ scene
35
+ ) as LinesMesh;
32
36
  this.ring.color = color;
33
37
  this.ring.parent = this.anchor;
34
38
  this.ring.isPickable = false;
@@ -29,14 +29,19 @@ export function buildGalleryTree(entries: GalleryEntry[]): GalleryTreeNode[] {
29
29
 
30
30
  for (const entry of entries) {
31
31
  const normalized = entry.path.replace(/\\/g, '/');
32
- const segments = normalized.replace(/\.json$/i, '').split('/').filter(Boolean);
32
+ const segments = normalized
33
+ .replace(/\.json$/i, '')
34
+ .split('/')
35
+ .filter(Boolean);
33
36
  const fileName = segments.pop() ?? entry.name;
34
37
  let level = roots;
35
38
  let pathSoFar = '';
36
39
 
37
40
  for (const segment of segments) {
38
41
  pathSoFar = pathSoFar ? `${pathSoFar}/${segment}` : segment;
39
- let folder = level.find((node): node is GalleryTreeFolder => node.kind === 'folder' && node.name === segment);
42
+ let folder = level.find(
43
+ (node): node is GalleryTreeFolder => node.kind === 'folder' && node.name === segment
44
+ );
40
45
  if (!folder) {
41
46
  folder = {kind: 'folder', name: segment, path: pathSoFar, children: []};
42
47
  level.push(folder);
@@ -90,9 +95,7 @@ export function filterGalleryTree(nodes: GalleryTreeNode[], query: string): Gall
90
95
 
91
96
  export const GALLERY_DRAG_MIME = 'application/x-quarks-gallery-drop';
92
97
 
93
- export type GalleryDragPayload =
94
- | {kind: 'effect'; id: number}
95
- | {kind: 'folder'; path: string};
98
+ export type GalleryDragPayload = {kind: 'effect'; id: number} | {kind: 'folder'; path: string};
96
99
 
97
100
  export function writeGalleryDrag(dataTransfer: DataTransfer, payload: GalleryDragPayload): void {
98
101
  dataTransfer.setData(GALLERY_DRAG_MIME, JSON.stringify(payload));
@@ -1,8 +1,8 @@
1
- import {ParticleEmitter, QuarksLoader, QuarksUtil} from 'babylon.quarks';
2
- import type {BatchedRenderer, ParticleSystem} from 'babylon.quarks';
3
- import type {Scene} from '@babylonjs/core/scene';
4
1
  import {TransformNode} from '@babylonjs/core/Meshes/transformNode';
5
2
  import type {Node} from '@babylonjs/core/node';
3
+ import type {Scene} from '@babylonjs/core/scene';
4
+ import type {BatchedRenderer, ParticleSystem} from 'babylon.quarks';
5
+ import {ParticleEmitter, QuarksLoader, QuarksUtil} from 'babylon.quarks';
6
6
 
7
7
  /** `ParticleEmitter`'s constructor default (see babylon.quarks/src/ParticleEmitter.ts) — the
8
8
  * name every emitter gets when the source JSON didn't give it one of its own. */
@@ -1,6 +1,6 @@
1
- import type {ParticleSystem} from 'babylon.quarks';
2
- import type {Scene} from '@babylonjs/core/scene';
3
1
  import {TransformNode} from '@babylonjs/core/Meshes/transformNode';
2
+ import type {Scene} from '@babylonjs/core/scene';
3
+ import type {ParticleSystem} from 'babylon.quarks';
4
4
  import {EffectBinding} from './binding';
5
5
  import {disposeLoadedEffect, parseEffectFromJson} from './loadEffect';
6
6
 
@@ -105,7 +105,11 @@ export async function loadEffectGallery(
105
105
  /** Builds an edit binding for one catalog entry without unloading the rest. */
106
106
  export function bindingFromGalleryEntry(entry: GalleryEntry): EffectBinding {
107
107
  const main = entry.systems.find((s) => !s.onlyUsedByOther) ?? entry.systems[0];
108
- return new EffectBinding(main, entry.systems.filter((s) => s !== main), entry.root);
108
+ return new EffectBinding(
109
+ main,
110
+ entry.systems.filter((s) => s !== main),
111
+ entry.root
112
+ );
109
113
  }
110
114
 
111
115
  /** Merges newly loaded catalog entries, skipping duplicate paths. */
@@ -29,7 +29,10 @@ async function readJsonFilesFromDirectory(dir: DirectoryHandleWithEntries, prefi
29
29
  }
30
30
  if (entry.kind === 'directory') {
31
31
  files.push(
32
- ...(await readJsonFilesFromDirectory(entry as unknown as DirectoryHandleWithEntries, `${prefix}${name}/`))
32
+ ...(await readJsonFilesFromDirectory(
33
+ entry as unknown as DirectoryHandleWithEntries,
34
+ `${prefix}${name}/`
35
+ ))
33
36
  );
34
37
  }
35
38
  }
@@ -1,7 +1,10 @@
1
+ import type {Mesh} from '@babylonjs/core/Meshes/mesh';
2
+ import type {EmitterShape} from 'babylon.quarks';
1
3
  import {
2
4
  CircleEmitter,
3
5
  ConeEmitter,
4
6
  DonutEmitter,
7
+ EmitterMode,
5
8
  GridEmitter,
6
9
  HemisphereEmitter,
7
10
  MeshSurfaceEmitter,
@@ -9,11 +12,17 @@ import {
9
12
  RectangleEmitter,
10
13
  SphereEmitter,
11
14
  } from 'babylon.quarks';
12
- import type {EmitterShape} from 'babylon.quarks';
13
- import {EmitterMode} from 'babylon.quarks';
14
- import type {Mesh} from '@babylonjs/core/Meshes/mesh';
15
15
 
16
- export type ShapeType = 'point' | 'sphere' | 'hemisphere' | 'cone' | 'circle' | 'donut' | 'rectangle' | 'grid' | 'mesh_surface';
16
+ export type ShapeType =
17
+ | 'point'
18
+ | 'sphere'
19
+ | 'hemisphere'
20
+ | 'cone'
21
+ | 'circle'
22
+ | 'donut'
23
+ | 'rectangle'
24
+ | 'grid'
25
+ | 'mesh_surface';
17
26
 
18
27
  export const SHAPE_TYPES: Array<{value: ShapeType; label: string}> = [
19
28
  {value: 'cone', label: 'Cone'},
@@ -64,7 +73,8 @@ export function getShapeType(shape: EmitterShape): ShapeType {
64
73
 
65
74
  export function readShapeParams(shape: EmitterShape): ShapeParams {
66
75
  const anyShape = shape as unknown as {[key: string]: unknown};
67
- const num = (key: string, fallback: number) => (typeof anyShape[key] === 'number' ? (anyShape[key] as number) : fallback);
76
+ const num = (key: string, fallback: number) =>
77
+ typeof anyShape[key] === 'number' ? (anyShape[key] as number) : fallback;
68
78
  return {
69
79
  radius: num('radius', DEFAULT_SHAPE_PARAMS.radius),
70
80
  mode: num('mode', DEFAULT_SHAPE_PARAMS.mode) as EmitterMode,
@@ -100,9 +110,21 @@ export function createShape(type: ShapeType, params: ShapeParams): EmitterShape
100
110
  case 'point':
101
111
  return new PointEmitter();
102
112
  case 'sphere':
103
- return new SphereEmitter({radius: params.radius, arc: params.arc, thickness: params.thickness, mode: params.mode, spread: params.spread});
113
+ return new SphereEmitter({
114
+ radius: params.radius,
115
+ arc: params.arc,
116
+ thickness: params.thickness,
117
+ mode: params.mode,
118
+ spread: params.spread,
119
+ });
104
120
  case 'hemisphere':
105
- return new HemisphereEmitter({radius: params.radius, arc: params.arc, thickness: params.thickness, mode: params.mode, spread: params.spread});
121
+ return new HemisphereEmitter({
122
+ radius: params.radius,
123
+ arc: params.arc,
124
+ thickness: params.thickness,
125
+ mode: params.mode,
126
+ spread: params.spread,
127
+ });
106
128
  case 'cone':
107
129
  return new ConeEmitter({
108
130
  radius: params.radius,
@@ -113,7 +135,13 @@ export function createShape(type: ShapeType, params: ShapeParams): EmitterShape
113
135
  spread: params.spread,
114
136
  });
115
137
  case 'circle':
116
- return new CircleEmitter({radius: params.radius, arc: params.arc, thickness: params.thickness, mode: params.mode, spread: params.spread});
138
+ return new CircleEmitter({
139
+ radius: params.radius,
140
+ arc: params.arc,
141
+ thickness: params.thickness,
142
+ mode: params.mode,
143
+ spread: params.spread,
144
+ });
117
145
  case 'donut':
118
146
  return new DonutEmitter({
119
147
  radius: params.radius,
@@ -124,9 +152,19 @@ export function createShape(type: ShapeType, params: ShapeParams): EmitterShape
124
152
  spread: params.spread,
125
153
  });
126
154
  case 'rectangle':
127
- return new RectangleEmitter({width: params.width, height: params.height, mode: params.mode, spread: params.spread});
155
+ return new RectangleEmitter({
156
+ width: params.width,
157
+ height: params.height,
158
+ mode: params.mode,
159
+ spread: params.spread,
160
+ });
128
161
  case 'grid':
129
- return new GridEmitter({width: params.width, height: params.height, column: params.column, row: params.row});
162
+ return new GridEmitter({
163
+ width: params.width,
164
+ height: params.height,
165
+ column: params.column,
166
+ row: params.row,
167
+ });
130
168
  case 'mesh_surface':
131
169
  return new MeshSurfaceEmitter(params.mesh);
132
170
  }
@@ -1,3 +1,4 @@
1
+ import type {TransformNode} from '@babylonjs/core/Meshes/transformNode';
1
2
  import {
2
3
  ConeEmitter,
3
4
  ConstantColor,
@@ -7,7 +8,6 @@ import {
7
8
  RenderMode,
8
9
  Vector4,
9
10
  } from 'babylon.quarks';
10
- import type {TransformNode} from '@babylonjs/core/Meshes/transformNode';
11
11
  import type {EffectBinding} from './binding';
12
12
 
13
13
  export interface ChildSystemOptions {
@@ -35,7 +35,9 @@ export function createChildSystem(parent: ParticleSystem, options: ChildSystemOp
35
35
  startSize: new IntervalValue(0.05, 0.2),
36
36
  startColor: new ConstantColor(new Vector4(1, 0.9, 0.6, 1)),
37
37
  emissionOverTime: new ConstantValue(oneShot ? 0 : 30),
38
- emissionBursts: oneShot ? [{time: 0, count: new ConstantValue(8), cycle: 1, interval: 0.01, probability: 1}] : [],
38
+ emissionBursts: oneShot
39
+ ? [{time: 0, count: new ConstantValue(8), cycle: 1, interval: 0.01, probability: 1}]
40
+ : [],
39
41
  shape: new ConeEmitter({radius: 0.1, angle: Math.PI / 3}),
40
42
  renderMode: RenderMode.BillBoard,
41
43
  texture: parent.texture ?? undefined,
@@ -70,7 +72,11 @@ export function unlinkSubEmitterReferences(systems: ParticleSystem[], removed: P
70
72
  }
71
73
 
72
74
  /** Disposes the given systems, first unlinking any sub-emitter behaviors that target them. */
73
- export function removeSystems(binding: EffectBinding, targets: ParticleSystem[], onSelect: (system: ParticleSystem) => void): void {
75
+ export function removeSystems(
76
+ binding: EffectBinding,
77
+ targets: ParticleSystem[],
78
+ onSelect: (system: ParticleSystem) => void
79
+ ): void {
74
80
  // binding.system is "removable" too as long as some other top-level system can take over
75
81
  // as the new main — being main vs. sub is an internal bookkeeping detail, not something
76
82
  // that should make one arbitrary track un-deletable in the UI.
@@ -1,7 +1,6 @@
1
- import type {ParticleSystem} from 'babylon.quarks';
2
- import {ConstantValue, IntervalValue} from 'babylon.quarks';
3
- import type {FunctionValueGenerator, ValueGenerator} from 'babylon.quarks';
4
1
  import type {TransformNode} from '@babylonjs/core/Meshes/transformNode';
2
+ import type {FunctionValueGenerator, ParticleSystem, ValueGenerator} from 'babylon.quarks';
3
+ import {ConstantValue, IntervalValue} from 'babylon.quarks';
5
4
  import type {EffectBinding} from './binding';
6
5
  import type {EffectTreeNode} from './effectTree';
7
6
 
@@ -29,7 +28,10 @@ export type TimelineRow =
29
28
  * stateful `genValue` (IntervalValue caches its sampled slot on first call and returns NaN
30
29
  * on a later call with a fresh memory array, so it can't be safely re-read on every render).
31
30
  */
32
- export function computeStartOffset(gen: ValueGenerator | FunctionValueGenerator): {value: number; startOffsetVariable: boolean} {
31
+ export function computeStartOffset(gen: ValueGenerator | FunctionValueGenerator): {
32
+ value: number;
33
+ startOffsetVariable: boolean;
34
+ } {
33
35
  if (gen instanceof ConstantValue) {
34
36
  return {value: gen.value, startOffsetVariable: false};
35
37
  }
@@ -97,10 +99,8 @@ function estimateMaxStartLife(system: ParticleSystem): number {
97
99
 
98
100
  /** When emission is burst-only, the effect window ends at the last burst time, not full duration. */
99
101
  function estimateEmissionEnd(system: ParticleSystem): number {
100
- const rate =
101
- system.emissionOverTime instanceof ConstantValue ? system.emissionOverTime.value : null;
102
- const perMeter =
103
- system.emissionOverDistance instanceof ConstantValue ? system.emissionOverDistance.value : null;
102
+ const rate = system.emissionOverTime instanceof ConstantValue ? system.emissionOverTime.value : null;
103
+ const perMeter = system.emissionOverDistance instanceof ConstantValue ? system.emissionOverDistance.value : null;
104
104
  if ((rate != null && rate > 0) || (perMeter != null && perMeter > 0)) {
105
105
  return system.duration;
106
106
  }
@@ -1,5 +1,5 @@
1
- import {Bezier, ConstantValue, IntervalValue, PiecewiseBezier} from 'babylon.quarks';
2
1
  import type {FunctionValueGenerator, ValueGenerator} from 'babylon.quarks';
2
+ import {Bezier, ConstantValue, IntervalValue, PiecewiseBezier} from 'babylon.quarks';
3
3
 
4
4
  export type ScalarGenerator = ValueGenerator | FunctionValueGenerator;
5
5
  export type ScalarMode = 'constant' | 'random' | 'curve';
@@ -3,8 +3,8 @@ import {MeshBuilder} from '@babylonjs/core/Meshes/meshBuilder';
3
3
  import {TransformNode} from '@babylonjs/core/Meshes/transformNode';
4
4
  import type {Scene} from '@babylonjs/core/scene';
5
5
  import {EDITOR_SCENE_NODE_METADATA} from './editorScene';
6
- import type {GalleryEntry} from './loadEffectGallery';
7
6
  import {GROUND_RING_RADIUS, GalleryHoverMarker} from './gallerySelectionMarker';
7
+ import type {GalleryEntry} from './loadEffectGallery';
8
8
 
9
9
  interface PickMetadata {
10
10
  quarksEditorPick: true;
package/src/index.ts CHANGED
@@ -1,34 +1,34 @@
1
1
  export {EffectBinding} from './core/binding';
2
2
  export type {EditorListener} from './core/binding';
3
- export {EffectHistory} from './core/history';
4
- export {createChildSystem} from './core/systems';
5
- export {buildEffectTree, collectSystems, serializeEffectTree, serializeEffectForest} from './core/effectTree';
6
- export type {EffectTreeNode} from './core/effectTree';
7
- export type {ChildSystemOptions} from './core/systems';
8
- export {readScalar, buildScalar, sampleCurve, readPieces, buildCurve, splitPieces, mergePieces} from './core/values';
9
- export type {ScalarGenerator, ScalarMode, ScalarValueState, CurvePiece} from './core/values';
3
+ export {GroundPlaneResolver, ensureGroundResolver} from './core/collision';
10
4
  export {
11
5
  DEFAULT_GRADIENT_STOPS,
12
- buildGradient,
13
6
  buildColorOverLife,
7
+ buildGradient,
8
+ findBehavior,
9
+ hexToRgb,
10
+ rgbToHex,
14
11
  rgbaToVector4,
15
12
  stopToCss,
16
13
  stopsToCssGradient,
17
- hexToRgb,
18
- rgbToHex,
19
- findBehavior,
20
14
  } from './core/colors';
21
15
  export type {GradientStop} from './core/colors';
16
+ export {buildEffectTree, collectSystems, serializeEffectForest, serializeEffectTree} from './core/effectTree';
17
+ export type {EffectTreeNode} from './core/effectTree';
18
+ export {EffectHistory} from './core/history';
19
+ export {applyRendererMaterial, getMaterialLabel} from './core/material';
20
+ export type {RendererMaterialPatch} from './core/material';
22
21
  export {
23
- SHAPE_TYPES,
24
- SHAPE_PARAM_KEYS,
25
22
  DEFAULT_SHAPE_PARAMS,
23
+ SHAPE_PARAM_KEYS,
24
+ SHAPE_TYPES,
25
+ createShape,
26
26
  getShapeType,
27
27
  readShapeParams,
28
- createShape,
29
28
  } from './core/shapes';
30
- export type {ShapeType, ShapeParams} from './core/shapes';
31
- export {GroundPlaneResolver, ensureGroundResolver} from './core/collision';
32
- export {applyRendererMaterial, getMaterialLabel} from './core/material';
33
- export type {RendererMaterialPatch} from './core/material';
29
+ export type {ShapeParams, ShapeType} from './core/shapes';
30
+ export {createChildSystem} from './core/systems';
31
+ export type {ChildSystemOptions} from './core/systems';
34
32
  export {ensurePortableTextureUrl} from './core/textureExport';
33
+ export {buildCurve, buildScalar, mergePieces, readPieces, readScalar, sampleCurve, splitPieces} from './core/values';
34
+ export type {CurvePiece, ScalarGenerator, ScalarMode, ScalarValueState} from './core/values';
@@ -1,4 +1,4 @@
1
- import React, {useRef, useState} from 'react';
1
+ import {useRef, useState} from 'react';
2
2
  import {CurvePiece, mergePieces, sampleCurve, splitPieces} from '../core/values';
3
3
  import {inputStyle, theme} from './theme';
4
4
 
@@ -17,7 +17,11 @@ interface PointRef {
17
17
  * drag inner keys horizontally, double-click the curve to add a key, double-click
18
18
  * a key to remove it (Unity-like).
19
19
  */
20
- export function CurveEditor(props: {pieces: CurvePiece[]; onChange: (pieces: CurvePiece[]) => void; maxValue?: number}) {
20
+ export function CurveEditor(props: {
21
+ pieces: CurvePiece[];
22
+ onChange: (pieces: CurvePiece[]) => void;
23
+ maxValue?: number;
24
+ }) {
21
25
  const svgRef = useRef<SVGSVGElement>(null);
22
26
  const drag = useRef<PointRef | null>(null);
23
27
  const [selected, setSelected] = useState<PointRef | null>(null);
@@ -29,7 +33,8 @@ export function CurveEditor(props: {pieces: CurvePiece[]; onChange: (pieces: Cur
29
33
  const spanEnd = (i: number) => (i + 1 < pieces.length ? pieces[i + 1].start : 1);
30
34
  const toX = (x: number) => PAD + x * (WIDTH - PAD * 2);
31
35
  const toY = (v: number) => HEIGHT - PAD - (v / max) * (HEIGHT - PAD * 2);
32
- const fromY = (y: number) => Math.round(Math.min(max, Math.max(0, ((HEIGHT - PAD - y) / (HEIGHT - PAD * 2)) * max)) * 1000) / 1000;
36
+ const fromY = (y: number) =>
37
+ Math.round(Math.min(max, Math.max(0, ((HEIGHT - PAD - y) / (HEIGHT - PAD * 2)) * max)) * 1000) / 1000;
33
38
  const fromX = (px: number) => Math.min(1, Math.max(0, (px - PAD) / (WIDTH - PAD * 2)));
34
39
 
35
40
  // Control point positions: piece start key, two handles, plus the final end key.
@@ -50,7 +55,9 @@ export function CurveEditor(props: {pieces: CurvePiece[]; onChange: (pieces: Cur
50
55
  const end = spanEnd(i);
51
56
  for (let s = 0; s <= 16; s++) {
52
57
  const t = s / 16;
53
- samples.push(`${toX(piece.start + t * (end - piece.start)).toFixed(1)},${toY(sampleCurve(piece.p, t)).toFixed(1)}`);
58
+ samples.push(
59
+ `${toX(piece.start + t * (end - piece.start)).toFixed(1)},${toY(sampleCurve(piece.p, t)).toFixed(1)}`
60
+ );
54
61
  }
55
62
  });
56
63
 
@@ -81,7 +88,9 @@ export function CurveEditor(props: {pieces: CurvePiece[]; onChange: (pieces: Cur
81
88
  props.onChange(next);
82
89
  };
83
90
 
84
- const selectedPoint = selected ? points.find((pt) => pt.ref.piece === selected.piece && pt.ref.idx === selected.idx) : null;
91
+ const selectedPoint = selected
92
+ ? points.find((pt) => pt.ref.piece === selected.piece && pt.ref.idx === selected.idx)
93
+ : null;
85
94
 
86
95
  return (
87
96
  <div>
@@ -89,7 +98,13 @@ export function CurveEditor(props: {pieces: CurvePiece[]; onChange: (pieces: Cur
89
98
  ref={svgRef}
90
99
  width={WIDTH}
91
100
  height={HEIGHT}
92
- style={{background: 'rgba(5, 8, 18, 0.85)', border: `1px solid ${theme.border}`, borderRadius: 8, touchAction: 'none', display: 'block'}}
101
+ style={{
102
+ background: 'rgba(5, 8, 18, 0.85)',
103
+ border: `1px solid ${theme.border}`,
104
+ borderRadius: 8,
105
+ touchAction: 'none',
106
+ display: 'block',
107
+ }}
93
108
  onPointerMove={(e) => applyDrag(e.clientX, e.clientY)}
94
109
  onPointerUp={(e) => {
95
110
  drag.current = null;
@@ -104,7 +119,15 @@ export function CurveEditor(props: {pieces: CurvePiece[]; onChange: (pieces: Cur
104
119
  }}
105
120
  >
106
121
  {[0.25, 0.5, 0.75].map((f) => (
107
- <line key={f} x1={PAD} x2={WIDTH - PAD} y1={PAD + f * (HEIGHT - PAD * 2)} y2={PAD + f * (HEIGHT - PAD * 2)} stroke="#1c274a" strokeWidth={1} />
122
+ <line
123
+ key={f}
124
+ x1={PAD}
125
+ x2={WIDTH - PAD}
126
+ y1={PAD + f * (HEIGHT - PAD * 2)}
127
+ y2={PAD + f * (HEIGHT - PAD * 2)}
128
+ stroke="#1c274a"
129
+ strokeWidth={1}
130
+ />
108
131
  ))}
109
132
  <polyline points={samples.join(' ')} fill="none" stroke={theme.curveStroke} strokeWidth={2} />
110
133
  {points.map((pt) => (
@@ -148,7 +171,10 @@ export function CurveEditor(props: {pieces: CurvePiece[]; onChange: (pieces: Cur
148
171
  if (Number.isNaN(parsed)) {
149
172
  return;
150
173
  }
151
- const next = pieces.map((piece) => ({start: piece.start, p: [...piece.p] as CurvePiece['p']}));
174
+ const next = pieces.map((piece) => ({
175
+ start: piece.start,
176
+ p: [...piece.p] as CurvePiece['p'],
177
+ }));
152
178
  const {piece, idx} = selectedPoint.ref;
153
179
  if (idx === 0 && piece > 0) {
154
180
  next[piece - 1].p[3] = parsed;
@@ -157,10 +183,14 @@ export function CurveEditor(props: {pieces: CurvePiece[]; onChange: (pieces: Cur
157
183
  props.onChange(next);
158
184
  }}
159
185
  />
160
- <span style={{fontSize: 10.5, color: theme.textDim}}>t={Math.round(selectedPoint.x * 100) / 100}</span>
186
+ <span style={{fontSize: 10.5, color: theme.textDim}}>
187
+ t={Math.round(selectedPoint.x * 100) / 100}
188
+ </span>
161
189
  </>
162
190
  ) : (
163
- <span style={{fontSize: 10.5, color: theme.textDim}}>dbl-click curve: add key · dbl-click key: remove</span>
191
+ <span style={{fontSize: 10.5, color: theme.textDim}}>
192
+ dbl-click curve: add key · dbl-click key: remove
193
+ </span>
164
194
  )}
165
195
  </div>
166
196
  </div>
@@ -1,16 +1,7 @@
1
- import React, {useSyncExternalStore} from 'react';
2
1
  import type {TransformNode} from '@babylonjs/core/Meshes/transformNode';
2
+ import type {ParticleSystem} from 'babylon.quarks';
3
+ import React, {useSyncExternalStore} from 'react';
3
4
  import {EffectBinding} from '../core/binding';
4
- import {ObjectModule} from './ObjectModule';
5
- import {
6
- ColorOverLifeModule,
7
- EmissionModule,
8
- MainModule,
9
- RendererModule,
10
- ShapeModule,
11
- SizeOverLifeModule,
12
- } from './modules';
13
- import type {GeometryData, GeometryOption, TextureOption} from './modules';
14
5
  import {
15
6
  CollisionModule,
16
7
  ColorBySpeedModule,
@@ -30,9 +21,18 @@ import {
30
21
  VelocityOverLifeModule,
31
22
  WidthOverTrailModule,
32
23
  } from './behaviorModules';
24
+ import type {GeometryData, GeometryOption, TextureOption} from './modules';
25
+ import {
26
+ ColorOverLifeModule,
27
+ EmissionModule,
28
+ MainModule,
29
+ RendererModule,
30
+ ShapeModule,
31
+ SizeOverLifeModule,
32
+ } from './modules';
33
+ import {ObjectModule} from './ObjectModule';
33
34
  import {SubEmittersModule} from './SubEmittersModule';
34
35
  import {theme} from './theme';
35
- import type {ParticleSystem} from 'babylon.quarks';
36
36
 
37
37
  export interface EffectEditorProps {
38
38
  binding: EffectBinding;
@@ -85,7 +85,8 @@ export function EffectEditor(props: EffectEditorProps) {
85
85
  <ObjectModule node={props.gizmoTargetNode} label={props.gizmoTargetLabel} />
86
86
  {!gizmoTargetIsEmitter && (
87
87
  <div style={{fontSize: 11, color: theme.textDim, margin: '0 0 10px', lineHeight: 1.45}}>
88
- Modules below edit <strong>{activeSystem.emitter.name || 'particle system'}</strong>. Select its emitter in the timeline to move it with the gizmo.
88
+ Modules below edit <strong>{activeSystem.emitter.name || 'particle system'}</strong>. Select its
89
+ emitter in the timeline to move it with the gizmo.
89
90
  </div>
90
91
  )}
91
92
  <MainModule binding={binding} />