babylon.quarks-editor 0.26.0 → 0.28.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 (211) hide show
  1. package/CHANGELOG.md +134 -0
  2. package/README.md +69 -9
  3. package/dist/core/assetDragSession.d.ts +1 -1
  4. package/dist/core/assetTree.d.ts +6 -1
  5. package/dist/core/assets.d.ts +7 -0
  6. package/dist/core/assets.js +11 -7
  7. package/dist/core/binding.d.ts +11 -2
  8. package/dist/core/binding.js +9 -1
  9. package/dist/core/collision.d.ts +4 -0
  10. package/dist/core/collision.js +4 -0
  11. package/dist/core/colors.d.ts +16 -0
  12. package/dist/core/colors.js +8 -0
  13. package/dist/core/defaultEffect.d.ts +5 -4
  14. package/dist/core/defaultEffect.js +4 -4
  15. package/dist/core/editorScene.d.ts +1 -1
  16. package/dist/core/editorSession.d.ts +229 -0
  17. package/dist/core/editorSession.js +983 -0
  18. package/dist/core/effectTree.d.ts +6 -1
  19. package/dist/core/effectTree.js +13 -88
  20. package/dist/core/emitterShapeWireframe.d.ts +2 -2
  21. package/dist/core/emitterShapeWireframe.js +45 -13
  22. package/dist/core/galleryDropMarker.d.ts +2 -2
  23. package/dist/core/galleryDropMarker.js +6 -6
  24. package/dist/core/galleryLayout.d.ts +3 -3
  25. package/dist/core/galleryLayout.js +1 -1
  26. package/dist/core/galleryPlayback.d.ts +1 -1
  27. package/dist/core/galleryPlayback.js +2 -1
  28. package/dist/core/galleryScene.d.ts +4 -4
  29. package/dist/core/galleryScene.js +3 -3
  30. package/dist/core/gallerySelectionMarker.d.ts +4 -4
  31. package/dist/core/gallerySelectionMarker.js +5 -5
  32. package/dist/core/groups.d.ts +1 -1
  33. package/dist/core/groups.js +1 -1
  34. package/dist/core/history.d.ts +7 -1
  35. package/dist/core/history.js +4 -0
  36. package/dist/core/indexedDbProjectStore.d.ts +14 -1
  37. package/dist/core/indexedDbProjectStore.js +13 -0
  38. package/dist/core/lineShaders.d.ts +2 -2
  39. package/dist/core/lineShaders.js +2 -2
  40. package/dist/core/loadEffect.d.ts +2 -2
  41. package/dist/core/loadEffect.js +1 -1
  42. package/dist/core/material.d.ts +12 -2
  43. package/dist/core/pickAssetFiles.js +1 -1
  44. package/dist/core/playback.d.ts +6 -0
  45. package/dist/core/projectStore.d.ts +26 -0
  46. package/dist/core/projectStore.js +1 -0
  47. package/dist/core/sceneEntries.d.ts +7 -4
  48. package/dist/core/sceneEntries.js +2 -2
  49. package/dist/core/scrub.d.ts +2 -2
  50. package/dist/core/scrub.js +2 -2
  51. package/dist/core/shapes.d.ts +25 -1
  52. package/dist/core/shapes.js +5 -0
  53. package/dist/core/systems.d.ts +4 -2
  54. package/dist/core/systems.js +3 -1
  55. package/dist/core/textureExport.d.ts +1 -1
  56. package/dist/core/timeline.d.ts +25 -5
  57. package/dist/core/transport.d.ts +3 -3
  58. package/dist/core/transport.js +2 -2
  59. package/dist/core/values.d.ts +21 -4
  60. package/dist/core/values.js +5 -0
  61. package/dist/core/viewportPicker.d.ts +4 -4
  62. package/dist/core/viewportPicker.js +8 -7
  63. package/dist/index.d.ts +25 -21
  64. package/dist/index.js +12 -12
  65. package/dist/react/AssetsPanel.d.ts +9 -2
  66. package/dist/react/AssetsPanel.js +6 -6
  67. package/dist/react/ChoiceDialog.d.ts +7 -0
  68. package/dist/react/ChoiceDialog.js +1 -1
  69. package/dist/react/ContextMenu.d.ts +10 -1
  70. package/dist/react/ContextMenu.js +1 -1
  71. package/dist/react/CurveEditor.d.ts +1 -1
  72. package/dist/react/CurveEditor.js +2 -2
  73. package/dist/react/EditorWorkspace.d.ts +9 -2
  74. package/dist/react/EditorWorkspace.js +10 -7
  75. package/dist/react/EffectEditor.d.ts +11 -5
  76. package/dist/react/EffectEditor.js +7 -7
  77. package/dist/react/EffectEditorHost.d.ts +33 -29
  78. package/dist/react/EffectEditorHost.js +247 -1674
  79. package/dist/react/EffectTreeView.d.ts +4 -1
  80. package/dist/react/EffectTreeView.js +3 -3
  81. package/dist/react/GradientEditor.d.ts +1 -1
  82. package/dist/react/GradientEditor.js +3 -3
  83. package/dist/react/InspectorInfoIcon.js +3 -3
  84. package/dist/react/InspectorLabel.js +3 -3
  85. package/dist/react/MessageDialog.d.ts +5 -0
  86. package/dist/react/MessageDialog.js +1 -1
  87. package/dist/react/ModuleSection.d.ts +10 -0
  88. package/dist/react/ModuleSection.js +8 -5
  89. package/dist/react/ObjectModule.d.ts +1 -1
  90. package/dist/react/ObjectModule.js +4 -4
  91. package/dist/react/ProjectsPage.d.ts +7 -1
  92. package/dist/react/ProjectsPage.js +33 -17
  93. package/dist/react/PromptDialog.d.ts +7 -0
  94. package/dist/react/PromptDialog.js +1 -1
  95. package/dist/react/ScenePanel.d.ts +9 -2
  96. package/dist/react/ScenePanel.js +12 -10
  97. package/dist/react/SubEmittersModule.d.ts +1 -1
  98. package/dist/react/SubEmittersModule.js +5 -5
  99. package/dist/react/TextureSelect.d.ts +24 -0
  100. package/dist/react/TextureSelect.js +74 -0
  101. package/dist/react/TextureUVField.d.ts +14 -0
  102. package/dist/react/TextureUVField.js +23 -0
  103. package/dist/react/TimelinePanel.d.ts +20 -7
  104. package/dist/react/TimelinePanel.js +21 -15
  105. package/dist/react/TimelineTrackRow.d.ts +13 -3
  106. package/dist/react/TimelineTrackRow.js +6 -6
  107. package/dist/react/TransportBar.d.ts +13 -4
  108. package/dist/react/TransportBar.js +3 -3
  109. package/dist/react/ValueField.d.ts +32 -5
  110. package/dist/react/ValueField.js +22 -11
  111. package/dist/react/behaviorModules.d.ts +19 -6
  112. package/dist/react/behaviorModules.js +68 -17
  113. package/dist/react/fields.d.ts +9 -0
  114. package/dist/react/fields.js +6 -3
  115. package/dist/react/host/BottomDocks.d.ts +36 -0
  116. package/dist/react/host/BottomDocks.js +63 -0
  117. package/dist/react/host/HostDialogs.d.ts +50 -0
  118. package/dist/react/host/HostDialogs.js +113 -0
  119. package/dist/react/host/InspectorSidebar.d.ts +32 -0
  120. package/dist/react/host/InspectorSidebar.js +59 -0
  121. package/dist/react/host/SceneSidebar.d.ts +53 -0
  122. package/dist/react/host/SceneSidebar.js +62 -0
  123. package/dist/react/host/ViewportPane.d.ts +56 -0
  124. package/dist/react/host/ViewportPane.js +91 -0
  125. package/dist/react/host/hostHooks.d.ts +44 -0
  126. package/dist/react/host/hostHooks.js +164 -0
  127. package/dist/react/host/hostStyles.d.ts +21 -0
  128. package/dist/react/host/hostStyles.js +57 -0
  129. package/dist/react/index.d.ts +42 -38
  130. package/dist/react/index.js +25 -23
  131. package/dist/react/inspectorHints.js +31 -0
  132. package/dist/react/modules.d.ts +19 -6
  133. package/dist/react/modules.js +134 -117
  134. package/dist/react/theme.d.ts +15 -0
  135. package/dist/react/theme.js +15 -0
  136. package/dist/session.d.ts +9 -0
  137. package/dist/session.js +8 -0
  138. package/package.json +18 -11
  139. package/src/core/assetDragSession.ts +1 -1
  140. package/src/core/assetTree.ts +15 -2
  141. package/src/core/assets.ts +25 -10
  142. package/src/core/binding.ts +12 -3
  143. package/src/core/collision.ts +4 -0
  144. package/src/core/colors.ts +21 -1
  145. package/src/core/defaultEffect.ts +8 -7
  146. package/src/core/editorScene.ts +1 -1
  147. package/src/core/editorSession.ts +1299 -0
  148. package/src/core/effectTree.ts +18 -101
  149. package/src/core/emitterShapeWireframe.ts +48 -16
  150. package/src/core/galleryDropMarker.ts +8 -8
  151. package/src/core/galleryLayout.ts +4 -4
  152. package/src/core/galleryPlayback.ts +3 -2
  153. package/src/core/galleryScene.ts +5 -5
  154. package/src/core/gallerySelectionMarker.ts +7 -7
  155. package/src/core/groups.ts +1 -1
  156. package/src/core/history.ts +7 -1
  157. package/src/core/indexedDbProjectStore.ts +14 -1
  158. package/src/core/lineShaders.ts +2 -2
  159. package/src/core/loadEffect.ts +4 -4
  160. package/src/core/material.ts +14 -4
  161. package/src/core/pickAssetFiles.ts +2 -2
  162. package/src/core/playback.ts +6 -0
  163. package/src/core/projectStore.ts +26 -0
  164. package/src/core/sceneEntries.ts +8 -5
  165. package/src/core/scrub.ts +3 -3
  166. package/src/core/shapes.ts +29 -2
  167. package/src/core/systems.ts +11 -4
  168. package/src/core/textureExport.ts +1 -1
  169. package/src/core/timeline.ts +43 -18
  170. package/src/core/transport.ts +4 -4
  171. package/src/core/values.ts +20 -4
  172. package/src/core/viewportPicker.ts +12 -11
  173. package/src/index.ts +25 -21
  174. package/src/react/AssetsPanel.tsx +13 -6
  175. package/src/react/ChoiceDialog.tsx +8 -1
  176. package/src/react/ContextMenu.tsx +11 -2
  177. package/src/react/CurveEditor.tsx +2 -2
  178. package/src/react/EditorWorkspace.tsx +16 -10
  179. package/src/react/EffectEditor.tsx +17 -9
  180. package/src/react/EffectEditorHost.tsx +415 -2354
  181. package/src/react/EffectTreeView.tsx +7 -4
  182. package/src/react/GradientEditor.tsx +3 -3
  183. package/src/react/InspectorInfoIcon.tsx +3 -3
  184. package/src/react/InspectorLabel.tsx +3 -3
  185. package/src/react/MessageDialog.tsx +6 -1
  186. package/src/react/ModuleSection.tsx +15 -4
  187. package/src/react/ObjectModule.tsx +5 -5
  188. package/src/react/ProjectsPage.tsx +44 -17
  189. package/src/react/PromptDialog.tsx +8 -1
  190. package/src/react/ScenePanel.tsx +21 -12
  191. package/src/react/SubEmittersModule.tsx +6 -6
  192. package/src/react/TextureSelect.tsx +118 -0
  193. package/src/react/TextureUVField.tsx +62 -0
  194. package/src/react/TimelinePanel.tsx +40 -21
  195. package/src/react/TimelineTrackRow.tsx +19 -9
  196. package/src/react/TransportBar.tsx +16 -7
  197. package/src/react/ValueField.tsx +60 -16
  198. package/src/react/behaviorModules.tsx +240 -55
  199. package/src/react/fields.tsx +22 -4
  200. package/src/react/host/BottomDocks.tsx +178 -0
  201. package/src/react/host/HostDialogs.tsx +228 -0
  202. package/src/react/host/InspectorSidebar.tsx +147 -0
  203. package/src/react/host/SceneSidebar.tsx +227 -0
  204. package/src/react/host/ViewportPane.tsx +300 -0
  205. package/src/react/host/hostHooks.ts +198 -0
  206. package/src/react/host/hostStyles.ts +65 -0
  207. package/src/react/index.ts +43 -38
  208. package/src/react/inspectorHints.ts +40 -0
  209. package/src/react/modules.tsx +440 -170
  210. package/src/react/theme.ts +15 -0
  211. package/src/session.ts +17 -0
@@ -1,22 +1,32 @@
1
- import type { TransformNode } from '@babylonjs/core/Meshes/transformNode';
1
+ import type { TransformNode } from '@babylonjs/core/Meshes/transformNode.js';
2
2
  import React from 'react';
3
- import type { EffectBinding } from '../core/binding';
4
- import type { TimelineRow } from '../core/timeline';
3
+ import type { EffectBinding } from '../core/binding.js';
4
+ import type { TimelineRow } from '../core/timeline.js';
5
+ /** Props of {@link TimelineTrackRow}. */
5
6
  export interface TimelineTrackRowProps {
7
+ /** The group or track the row shows. */
6
8
  row: TimelineRow;
9
+ /** The focused effect, edited by the row's actions. */
7
10
  binding: EffectBinding;
11
+ /** The row's node is the one in the inspector. */
8
12
  selected: boolean;
13
+ /** Selects the row's node. */
9
14
  onSelectNode: (node: TransformNode | null) => void;
15
+ /** The ruler's scale, pixels per second. */
10
16
  pxPerSecond: number;
11
17
  /** Matches the ruler's left gutter so bars line up under the correct tick. */
12
18
  offsetPx: number;
19
+ /** A group row's children are hidden. */
13
20
  collapsed: boolean;
21
+ /** Collapses or expands a group row. */
14
22
  onToggleCollapse?: () => void;
15
23
  /** Preview-only visibility (toggles `system.emitter.visible`); lets you isolate one track. */
16
24
  hidden?: boolean;
25
+ /** Shows or hides the track in the preview. */
17
26
  onToggleVisible?: () => void;
18
27
  /** Membership in the "group selected rows together" set (unrelated to inspector selection). */
19
28
  groupSelected: boolean;
29
+ /** Adds the row to the set, or takes it out. */
20
30
  onToggleGroupSelect: () => void;
21
31
  }
22
32
  /** One row of the timeline: a label cell (name + actions) and, for tracks, a bar cell. */
@@ -1,12 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { CheckIcon, ChevronDownIcon, ChevronRightIcon, EyeIcon, EyeSlashIcon, PencilSquareIcon, PlusIcon, SparklesIcon, SquaresPlusIcon, XMarkIcon, } from '@heroicons/react/24/solid';
3
3
  import { useState } from 'react';
4
- import { buildEffectTree, collectSystems } from '../core/effectTree';
5
- import { ungroupNode } from '../core/groups';
6
- import { createChildSystem, removeSystems } from '../core/systems';
7
- import { PromptDialog } from './PromptDialog';
8
- import { iconStyle } from './icons';
9
- import { theme } from './theme';
4
+ import { buildEffectTree, collectSystems } from '../core/effectTree.js';
5
+ import { ungroupNode } from '../core/groups.js';
6
+ import { createChildSystem, removeSystems } from '../core/systems.js';
7
+ import { PromptDialog } from './PromptDialog.js';
8
+ import { iconStyle } from './icons.js';
9
+ import { theme } from './theme.js';
10
10
  const iconButton = {
11
11
  background: 'none',
12
12
  border: 'none',
@@ -1,18 +1,27 @@
1
- import type { Scene } from '@babylonjs/core/scene';
1
+ import type { Scene } from '@babylonjs/core/scene.js';
2
2
  import type { BatchedRenderer } from 'babylon.quarks';
3
3
  import React from 'react';
4
- import type { EffectBinding } from '../core/binding';
5
- import type { PlaybackState } from '../core/playback';
4
+ import type { EffectBinding } from '../core/binding.js';
5
+ import type { PlaybackState } from '../core/playback.js';
6
+ /** Props of {@link TransportBar}. */
6
7
  export interface TransportBarProps {
8
+ /** The focused effect, which Restart and Step act on. */
7
9
  binding: EffectBinding | null;
10
+ /** The preview scene, redrawn after a restart. */
8
11
  scene: Scene;
12
+ /** The renderer the effects draw through. */
9
13
  renderer: BatchedRenderer;
14
+ /** Playback state shared with the host's render loop. */
10
15
  playbackRef: React.MutableRefObject<PlaybackState>;
11
16
  /** The host's render loop writes time and particle count here. */
12
- counterRef: React.RefObject<HTMLSpanElement>;
17
+ counterRef: React.RefObject<HTMLSpanElement | null>;
18
+ /** The stage is playing. */
13
19
  stagePlaying: boolean;
20
+ /** Plays or pauses the stage. */
14
21
  setStagePlaying: (playing: boolean) => void;
22
+ /** Playback speed, 1 for real time. */
15
23
  speed: number;
24
+ /** Sets the playback speed. */
16
25
  setSpeed: (speed: number) => void;
17
26
  /** False when the focused effect is hidden. */
18
27
  enabled: boolean;
@@ -1,8 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ArrowPathIcon, ForwardIcon, PauseIcon, PlayIcon } from '@heroicons/react/24/solid';
3
- import { PLAYBACK_SPEEDS, restartFocusPlayback, toggleStagePlayback } from '../core/transport';
4
- import { iconStyle } from './icons';
5
- import { buttonStyle, theme } from './theme';
3
+ import { PLAYBACK_SPEEDS, restartFocusPlayback, toggleStagePlayback } from '../core/transport.js';
4
+ import { iconStyle } from './icons.js';
5
+ import { buttonStyle, theme } from './theme.js';
6
6
  const barButton = {
7
7
  ...buttonStyle,
8
8
  width: 28,
@@ -1,11 +1,38 @@
1
+ import type { ValueGenerator } from 'babylon.quarks';
1
2
  import React from 'react';
2
- import { ScalarGenerator } from '../core/values';
3
- export declare function ValueField(props: {
3
+ import { AnyGenerator, ScalarGenerator } from '../core/values.js';
4
+ /** Props every {@link ValueField} takes. */
5
+ interface ValueFieldBaseProps {
6
+ /** The row's label. */
4
7
  label: string;
8
+ /** Tooltip text of the label. */
5
9
  hint?: string;
10
+ /** Key into the inspector hints, used when `hint` is not given. */
6
11
  hintKey?: string;
7
- generator: ScalarGenerator | undefined;
8
- onChange: (generator: ScalarGenerator) => void;
12
+ /** The generator shown; one that is not a scalar reads as the constant 1. */
13
+ generator: AnyGenerator | undefined;
14
+ /** Smallest value the number fields accept. */
9
15
  min?: number;
16
+ /** Top of the curve editor's value range. */
10
17
  curveMax?: number;
11
- }): React.JSX.Element;
18
+ }
19
+ /** A value field for a setting that takes a curve over the particle's life. */
20
+ export interface CurveValueFieldProps extends ValueFieldBaseProps {
21
+ /** Offers Curve beside Constant and Random (the default). */
22
+ curve?: true;
23
+ /** Receives the edited generator. */
24
+ onChange: (generator: ScalarGenerator) => void;
25
+ }
26
+ /** A value field for a setting sampled once, without a curve: Constant or Random. */
27
+ export interface ConstantValueFieldProps extends ValueFieldBaseProps {
28
+ /** Hides Curve: the setting takes no curve. */
29
+ curve: false;
30
+ /** Receives the edited generator. */
31
+ onChange: (generator: ValueGenerator) => void;
32
+ }
33
+ /**
34
+ * Unity-like value field: Constant | Random Between Two Constants | Curve, bound to a quarks value
35
+ * generator. With `curve={false}` it offers only the first two, for settings sampled once.
36
+ */
37
+ export declare function ValueField(props: CurveValueFieldProps | ConstantValueFieldProps): React.ReactElement;
38
+ export {};
@@ -1,20 +1,31 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { buildCurve, buildScalar, readPieces, readScalar } from '../core/values';
3
- import { CurveEditor } from './CurveEditor';
4
- import { NumberField, Row, SelectField } from './fields';
2
+ import { buildCurve, buildScalar, readPieces, readScalar, } from '../core/values.js';
3
+ import { CurveEditor } from './CurveEditor.js';
4
+ import { NumberField, Row, SelectField } from './fields.js';
5
+ const captionStyle = { fontSize: 10.5, opacity: 0.6, flex: 'none' };
6
+ function isValueGenerator(generator) {
7
+ return generator.type === 'value';
8
+ }
5
9
  /**
6
- * Unity-like value field: Constant | Random Between Two Constants | Curve,
7
- * bound to a quarks value generator.
10
+ * Unity-like value field: Constant | Random Between Two Constants | Curve, bound to a quarks value
11
+ * generator. With `curve={false}` it offers only the first two, for settings sampled once.
8
12
  */
9
- const captionStyle = { fontSize: 10.5, opacity: 0.6, flex: 'none' };
10
13
  export function ValueField(props) {
11
- const state = readScalar(props.generator);
12
- const commit = (patch) => {
13
- props.onChange(buildScalar({ ...state, ...patch }));
14
+ const read = readScalar(props.generator);
15
+ // A setting without curves shows a curve it was loaded with as a constant, so an edit replaces it.
16
+ const state = props.curve === false && read.mode === 'curve' ? { ...read, mode: 'constant' } : read;
17
+ const emit = (generator) => {
18
+ if (props.curve !== false) {
19
+ props.onChange(generator);
20
+ }
21
+ else if (isValueGenerator(generator)) {
22
+ props.onChange(generator);
23
+ }
14
24
  };
25
+ const commit = (patch) => emit(buildScalar({ ...state, ...patch }));
15
26
  return (_jsxs(_Fragment, { children: [_jsx(Row, { label: props.label, hint: props.hint, hintKey: props.hintKey, children: _jsx(SelectField, { value: state.mode, options: [
16
27
  { value: 'constant', label: 'Constant' },
17
28
  { value: 'random', label: 'Random between two' },
18
- { value: 'curve', label: 'Curve' },
19
- ], onChange: (mode) => commit({ mode }) }) }), state.mode === 'constant' && (_jsx(Row, { label: "", children: _jsx(NumberField, { value: state.value, min: props.min, onChange: (value) => commit({ value }) }) })), state.mode === 'random' && (_jsx(Row, { label: "", children: _jsxs("div", { style: { display: 'flex', gap: 6, alignItems: 'center' }, children: [_jsx("span", { style: captionStyle, children: "min" }), _jsx(NumberField, { value: state.min, min: props.min, onChange: (min) => commit({ min }) }), _jsx("span", { style: captionStyle, children: "max" }), _jsx(NumberField, { value: state.max, min: props.min, onChange: (max) => commit({ max }) })] }) })), state.mode === 'curve' && (_jsx("div", { style: { marginTop: 6 }, children: _jsx(CurveEditor, { pieces: readPieces(props.generator), maxValue: props.curveMax, onChange: (pieces) => props.onChange(buildCurve(pieces)) }) }))] }));
29
+ ...(props.curve !== false ? [{ value: 'curve', label: 'Curve' }] : []),
30
+ ], onChange: (mode) => commit({ mode }) }) }), state.mode === 'constant' && (_jsx(Row, { label: "", children: _jsx(NumberField, { value: state.value, min: props.min, onChange: (value) => commit({ value }) }) })), state.mode === 'random' && (_jsx(Row, { label: "", children: _jsxs("div", { style: { display: 'flex', gap: 6, alignItems: 'center' }, children: [_jsx("span", { style: captionStyle, children: "min" }), _jsx(NumberField, { value: state.min, min: props.min, onChange: (min) => commit({ min }) }), _jsx("span", { style: captionStyle, children: "max" }), _jsx(NumberField, { value: state.max, min: props.min, onChange: (max) => commit({ max }) })] }) })), state.mode === 'curve' && (_jsx("div", { style: { marginTop: 6 }, children: _jsx(CurveEditor, { pieces: readPieces(props.generator), maxValue: props.curveMax, onChange: (pieces) => emit(buildCurve(pieces)) }) }))] }));
20
31
  }
@@ -1,15 +1,19 @@
1
1
  import React from 'react';
2
- import { EffectBinding } from '../core/binding';
3
- import type { TextureOption } from './modules';
4
- interface ModuleProps {
5
- binding: EffectBinding;
6
- }
2
+ import type { TextureOption } from './modules.js';
3
+ import { type ModuleProps } from './ModuleSection.js';
4
+ /** The Speed over Lifetime module (Unity's Speed Modifier): scales speed by a curve over the particle's life. */
7
5
  export declare function SpeedOverLifeModule({ binding }: ModuleProps): React.JSX.Element;
6
+ /** The Limit Speed over Lifetime module (Unity's Limit Velocity over Lifetime): caps speed by a curve, with dampening. */
8
7
  export declare function LimitSpeedOverLifeModule({ binding }: ModuleProps): React.JSX.Element;
8
+ /** The Force over Lifetime module (Unity's Force over Lifetime): a constant push per axis. */
9
9
  export declare function ForceOverLifeModule({ binding }: ModuleProps): React.JSX.Element;
10
10
  export declare function VelocityOverLifeModule({ binding }: ModuleProps): React.JSX.Element;
11
11
  export declare function InheritVelocityModule({ binding }: ModuleProps): React.JSX.Element;
12
+ /** A point attractor (`GravityForce`): particles fall towards a point, harder the nearer they are. */
13
+ export declare function AttractorModule({ binding }: ModuleProps): React.JSX.Element;
14
+ /** The Gravity module: pulls particles towards a point. */
12
15
  export declare function GravityModule({ binding }: ModuleProps): React.JSX.Element;
16
+ /** The Rotation over Lifetime module (Unity's Rotation over Lifetime): spin rate over the particle's life. */
13
17
  export declare function RotationOverLifeModule({ binding }: ModuleProps): React.JSX.Element;
14
18
  /** Turns mesh particles by a 3D angular velocity; billboards turn through Rotation over Lifetime. */
15
19
  export declare function Rotation3DOverLifeModule({ binding }: ModuleProps): React.JSX.Element;
@@ -21,14 +25,23 @@ export declare function Rotation3DOverLifeModule({ binding }: ModuleProps): Reac
21
25
  export declare function SequencesModule({ binding, textureOptions }: ModuleProps & {
22
26
  textureOptions?: TextureOption[];
23
27
  }): React.JSX.Element;
28
+ /** The Noise module (Unity's Noise): moves and turns particles by a noise field. */
24
29
  export declare function NoiseModule({ binding }: ModuleProps): React.JSX.Element;
30
+ /** The Turbulence module: a curl noise force field. */
25
31
  export declare function TurbulenceModule({ binding }: ModuleProps): React.JSX.Element;
32
+ /** The Randomize Direction module: tilts each particle's start direction by a random angle. */
26
33
  export declare function EmitDirectionModule({ binding }: ModuleProps): React.JSX.Element;
34
+ /** The Collision module (Unity's Collision): particles bounce off or die on a ground plane. */
27
35
  export declare function CollisionModule({ binding }: ModuleProps): React.JSX.Element;
36
+ /** The Color by Speed module (Unity's Color by Speed): a gradient over a speed range. */
28
37
  export declare function ColorBySpeedModule({ binding }: ModuleProps): React.JSX.Element;
38
+ /** The Size by Speed module (Unity's Size by Speed): size over a speed range. */
29
39
  export declare function SizeBySpeedModule({ binding }: ModuleProps): React.JSX.Element;
40
+ /** The Rotation by Speed module (Unity's Rotation by Speed): spin rate over a speed range. */
30
41
  export declare function RotationBySpeedModule({ binding }: ModuleProps): React.JSX.Element;
42
+ /** The Orbit over Lifetime module (Unity's Velocity over Lifetime orbital): turns particles about an axis. */
31
43
  export declare function OrbitOverLifeModule({ binding }: ModuleProps): React.JSX.Element;
44
+ /** The Width over Trail module (Unity's Trails width over trail): a trail's width along its length. */
32
45
  export declare function WidthOverTrailModule({ binding }: ModuleProps): React.JSX.Element;
46
+ /** The Texture Sheet Animation module (Unity's Texture Sheet Animation): plays the sheet's frames by life or by frame rate. */
33
47
  export declare function TextureSheetModule({ binding }: ModuleProps): React.JSX.Element;
34
- export {};
@@ -1,16 +1,16 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { PlusIcon, XMarkIcon } from '@heroicons/react/24/solid';
3
- import { ApplyCollision, ApplyForce, ApplySequences, AxisAngleGenerator, Bezier, ChangeEmitDirection, ColorBySpeed, ConstantValue, EulerGenerator, ForceOverLife, FrameOverLife, getPhysicsResolver, Gradient, InheritVelocity, IntervalValue, LimitSpeedOverLife, Noise, OrbitOverLife, PiecewiseBezier, RandomQuatGenerator, Rotation3DOverLife, RotationBySpeed, RotationOverLife, SizeBySpeed, SpeedOverLife, TextureSequencer, TurbulenceField, Vector3, VelocityOverLife, WidthOverLength, } from 'babylon.quarks';
3
+ import { ApplyCollision, ApplyForce, ApplySequences, AxisAngleGenerator, Bezier, ChangeEmitDirection, ColorBySpeed, ConstantValue, EulerGenerator, ForceOverLife, FrameOverLife, Gradient, GravityForce, InheritVelocity, IntervalValue, LimitSpeedOverLife, Noise, OrbitOverLife, PiecewiseBezier, RandomQuatGenerator, Rotation3DOverLife, RotationBySpeed, RotationOverLife, SizeBySpeed, SpeedOverLife, TextureSequencer, TurbulenceField, Vector3, VelocityOverLife, WidthOverLength, } from 'babylon.quarks';
4
4
  import React from 'react';
5
- import { buildGradient, DEFAULT_GRADIENT_STOPS, findBehavior, readGradientStops } from '../core/colors';
6
- import { buildCurve, buildScalar, readPieces, readScalar } from '../core/values';
7
- import { CurveEditor } from './CurveEditor';
8
- import { CheckboxField, NumberField, Row, SelectField } from './fields';
9
- import { GradientEditor } from './GradientEditor';
10
- import { iconStyle } from './icons';
11
- import { ModuleSection } from './ModuleSection';
12
- import { theme } from './theme';
13
- import { ValueField } from './ValueField';
5
+ import { buildGradient, DEFAULT_GRADIENT_STOPS, findBehavior, readGradientStops } from '../core/colors.js';
6
+ import { buildCurve, buildScalar, readPieces, readScalar } from '../core/values.js';
7
+ import { CurveEditor } from './CurveEditor.js';
8
+ import { CheckboxField, NumberField, Row, SelectField } from './fields.js';
9
+ import { GradientEditor } from './GradientEditor.js';
10
+ import { iconStyle } from './icons.js';
11
+ import { ModuleSection } from './ModuleSection.js';
12
+ import { theme } from './theme.js';
13
+ import { ValueField } from './ValueField.js';
14
14
  function removeBehaviorOfType(behaviors, type) {
15
15
  for (let i = behaviors.length - 1; i >= 0; i--) {
16
16
  if (behaviors[i].type === type) {
@@ -28,14 +28,20 @@ function BehaviorModule(props) {
28
28
  }
29
29
  }), children: behavior ? props.children(behavior) : null }));
30
30
  }
31
+ /** The Speed over Lifetime module (Unity's Speed Modifier): scales speed by a curve over the particle's life. */
31
32
  export function SpeedOverLifeModule({ binding }) {
32
33
  return (_jsx(BehaviorModule, { binding: binding, title: "Speed over Lifetime", type: "SpeedOverLife", create: () => new SpeedOverLife(buildScalar({ mode: 'curve', value: 1, min: 0, max: 1, curve: [1, 0.75, 0.5, 0] })), children: (behavior) => (_jsx("div", { style: { marginTop: 6 }, children: _jsx(CurveEditor, { pieces: readPieces(behavior.speed), maxValue: 2, onChange: (pieces) => binding.apply(() => (behavior.speed = buildCurve(pieces))) }) })) }));
33
34
  }
35
+ /** The Limit Speed over Lifetime module (Unity's Limit Velocity over Lifetime): caps speed by a curve, with dampening. */
34
36
  export function LimitSpeedOverLifeModule({ binding }) {
35
37
  return (_jsx(BehaviorModule, { binding: binding, title: "Limit Speed over Lifetime", type: "LimitSpeedOverLife", create: () => new LimitSpeedOverLife(buildScalar({ mode: 'curve', value: 1, min: 0, max: 1, curve: [4, 2, 1, 1] }), 0.5), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx("div", { style: { marginTop: 6 }, children: _jsx(CurveEditor, { pieces: readPieces(behavior.speed), maxValue: 8, onChange: (pieces) => binding.apply(() => (behavior.speed = buildCurve(pieces))) }) }), _jsx(Row, { label: "Dampen", children: _jsx(NumberField, { value: behavior.dampen, min: 0, step: 0.05, onChange: (v) => binding.apply(() => (behavior.dampen = Math.min(1, v))) }) })] })) }));
36
38
  }
39
+ /** The Force over Lifetime module (Unity's Force over Lifetime): a constant push per axis. */
37
40
  export function ForceOverLifeModule({ binding }) {
38
- return (_jsx(BehaviorModule, { binding: binding, title: "Force over Lifetime", type: "ForceOverLife", create: () => new ForceOverLife(new ConstantValue(0), new ConstantValue(2), new ConstantValue(0)), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx(ValueField, { hintKey: "force.x", label: "X", generator: behavior.x, curveMax: 10, onChange: (g) => binding.apply(() => (behavior.x = g)) }), _jsx(ValueField, { hintKey: "force.y", label: "Y", generator: behavior.y, curveMax: 10, onChange: (g) => binding.apply(() => (behavior.y = g)) }), _jsx(ValueField, { hintKey: "force.z", label: "Z", generator: behavior.z, curveMax: 10, onChange: (g) => binding.apply(() => (behavior.z = g)) })] })) }));
41
+ return (_jsx(BehaviorModule, { binding: binding, title: "Force over Lifetime", type: "ForceOverLife", create: () => new ForceOverLife(new ConstantValue(0), new ConstantValue(2), new ConstantValue(0)), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx(Row, { label: "Space", children: _jsx(SelectField, { value: behavior.space, options: [
42
+ { value: 'world', label: 'World' },
43
+ { value: 'local', label: 'Local' },
44
+ ], onChange: (v) => binding.apply(() => (behavior.space = v)) }) }), _jsx(ValueField, { hintKey: "force.x", label: "X", generator: behavior.x, curveMax: 10, onChange: (g) => binding.apply(() => (behavior.x = g)) }), _jsx(ValueField, { hintKey: "force.y", label: "Y", generator: behavior.y, curveMax: 10, onChange: (g) => binding.apply(() => (behavior.y = g)) }), _jsx(ValueField, { hintKey: "force.z", label: "Z", generator: behavior.z, curveMax: 10, onChange: (g) => binding.apply(() => (behavior.z = g)) })] })) }));
39
45
  }
40
46
  export function VelocityOverLifeModule({ binding }) {
41
47
  return (_jsx(BehaviorModule, { binding: binding, title: "Velocity over Lifetime", type: "VelocityOverLife", create: () => new VelocityOverLife(new ConstantValue(0), new ConstantValue(1), new ConstantValue(0)), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx(Row, { label: "Space", children: _jsx(SelectField, { value: behavior.space, options: [
@@ -49,9 +55,18 @@ export function InheritVelocityModule({ binding }) {
49
55
  { value: 'current', label: 'Current' },
50
56
  ], onChange: (v) => binding.apply(() => (behavior.mode = v)) }) }), _jsx(ValueField, { label: "Multiplier", generator: behavior.multiplier, curveMax: 2, onChange: (g) => binding.apply(() => (behavior.multiplier = g)) })] })) }));
51
57
  }
58
+ /** A point attractor (`GravityForce`): particles fall towards a point, harder the nearer they are. */
59
+ export function AttractorModule({ binding }) {
60
+ return (_jsx(BehaviorModule, { binding: binding, title: "Attractor", type: "GravityForce", create: () => new GravityForce(new Vector3(0, 0, 0), 1), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx(Row, { label: "Center", hintKey: "attractor.center", children: _jsx("div", { style: { display: 'flex', gap: 6 }, children: ['x', 'y', 'z'].map((axis) => (_jsx(NumberField, { value: behavior.center[axis], step: 0.1, onChange: (v) => binding.apply(() => (behavior.center[axis] = v)) }, axis))) }) }), _jsx(Row, { label: "Strength", hintKey: "attractor.magnitude", children: _jsx(NumberField, { value: behavior.magnitude, step: 0.1, onChange: (v) => binding.apply(() => (behavior.magnitude = v)) }) })] })) }));
61
+ }
62
+ /** The Gravity module: pulls particles towards a point. */
52
63
  export function GravityModule({ binding }) {
53
- return (_jsx(BehaviorModule, { binding: binding, title: "Gravity", type: "ApplyForce", create: () => new ApplyForce(new Vector3(0, -1, 0), new ConstantValue(9.81)), children: (behavior) => (_jsx(Row, { label: "Strength", hintKey: "gravity.strength", children: _jsx(NumberField, { value: readScalar(behavior.magnitude).value, step: 0.5, onChange: (v) => binding.apply(() => (behavior.magnitude = new ConstantValue(v))) }) })) }));
64
+ return (_jsx(BehaviorModule, { binding: binding, title: "Gravity", type: "ApplyForce", create: () => new ApplyForce(new Vector3(0, -1, 0), new ConstantValue(9.81)), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx(Row, { label: "Strength", hintKey: "gravity.strength", children: _jsx(NumberField, { value: readScalar(behavior.magnitude).value, step: 0.5, onChange: (v) => binding.apply(() => (behavior.magnitude = new ConstantValue(v))) }) }), _jsx(Row, { label: "Space", children: _jsx(SelectField, { value: behavior.space, options: [
65
+ { value: 'world', label: 'World' },
66
+ { value: 'local', label: 'Local' },
67
+ ], onChange: (v) => binding.apply(() => (behavior.space = v)) }) })] })) }));
54
68
  }
69
+ /** The Rotation over Lifetime module (Unity's Rotation over Lifetime): spin rate over the particle's life. */
55
70
  export function RotationOverLifeModule({ binding }) {
56
71
  return (_jsx(BehaviorModule, { binding: binding, title: "Rotation over Lifetime", type: "RotationOverLife", create: () => new RotationOverLife(new ConstantValue(Math.PI)), children: (behavior) => (_jsx(ValueField, { hintKey: "rotationOverLife.velocity", label: "Velocity (rad/s)", generator: behavior.angularVelocity, curveMax: Math.PI * 4, onChange: (g) => binding.apply(() => (behavior.angularVelocity = g)) })) }));
57
72
  }
@@ -127,6 +142,7 @@ export function SequencesModule({ binding, textureOptions }) {
127
142
  }, title: "Remove sequence", onClick: () => binding.apply(() => behavior.sequencers.splice(index, 1)), children: _jsx(XMarkIcon, { style: iconStyle(13) }) })] }), _jsx(Row, { label: "Time from / to (s)", children: pair(range.a, range.b, (v) => binding.apply(() => (range.a = v)), (v) => binding.apply(() => (range.b = v)), 0.1) }), texture.locations !== undefined && (_jsxs(_Fragment, { children: [_jsx(Row, { label: "Scale X / Y", children: pair(texture.scaleX, texture.scaleY, (v) => binding.apply(() => (texture.scaleX = v)), (v) => binding.apply(() => (texture.scaleY = v)), 0.01) }), _jsx(Vector3Row, { label: "Position", value: texture.position, step: 0.1, onChange: (v) => binding.apply(() => texture.position.copy(v)) })] }))] }, index));
128
143
  }), _jsx(Row, { label: "Image", children: textureOptions && textureOptions.length > 0 ? (_jsx(SelectField, { value: imageUrl, options: textureOptions.map((option) => ({ value: option.url, label: option.label })), onChange: (url) => setImageUrl(url) })) : (_jsx("input", { className: "qe-hover", style: { flex: 1, minWidth: 0, fontSize: 12 }, placeholder: "Image URL", value: imageUrl, onChange: (event) => setImageUrl(event.target.value) })) }), _jsx(Row, { label: "Alpha threshold", children: _jsx(NumberField, { value: threshold, min: 0, step: 0.05, onChange: (v) => setThreshold(Math.min(1, v)) }) }), _jsxs("button", { className: "qe-hover", style: { display: 'flex', alignItems: 'center', gap: 4, marginTop: 6, fontSize: 12 }, onClick: () => void addFromImage(behavior), children: [_jsx(PlusIcon, { style: iconStyle(13) }), "Add sequence from image"] }), status && _jsx("p", { style: { margin: '4px 0 0', color: theme.textDim, fontSize: 11.5 }, children: status })] })) }));
129
144
  }
145
+ /** The Noise module (Unity's Noise): moves and turns particles by a noise field. */
130
146
  export function NoiseModule({ binding }) {
131
147
  return (_jsx(BehaviorModule, { binding: binding, title: "Noise", type: "Noise", create: () => new Noise(new ConstantValue(1), new ConstantValue(1), new ConstantValue(1), new ConstantValue(0)), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx(ValueField, { hintKey: "noise.frequency", label: "Frequency", generator: behavior.frequency, min: 0, curveMax: 10, onChange: (g) => binding.apply(() => (behavior.frequency = g)) }), _jsx(ValueField, { hintKey: "noise.strength", label: "Strength", generator: behavior.power, min: 0, curveMax: 10, onChange: (g) => binding.apply(() => (behavior.power = g)) }), _jsx(ValueField, { label: "Position amount", generator: behavior.positionAmount, min: 0, curveMax: 2, onChange: (g) => binding.apply(() => (behavior.positionAmount = g)) }), _jsx(ValueField, { label: "Rotation amount", generator: behavior.rotationAmount, min: 0, curveMax: Math.PI, onChange: (g) => binding.apply(() => (behavior.rotationAmount = g)) }), _jsx(ValueField, { label: "Scroll speed", generator: behavior.scrollSpeed, curveMax: 5, onChange: (g) => binding.apply(() => (behavior.scrollSpeed = g)) }), _jsx(Row, { label: "Remap", children: _jsx(CheckboxField, { value: behavior.remap !== undefined, onChange: (v) => binding.apply(() => (behavior.remap = v
132
148
  ? new PiecewiseBezier([[new Bezier(-1, -1 / 3, 1 / 3, 1), 0]])
@@ -141,38 +157,59 @@ function Vector3Row(props) {
141
157
  };
142
158
  return (_jsx(Row, { label: props.label, hintKey: props.hintKey, children: _jsxs("div", { style: { display: 'flex', gap: 6 }, children: [_jsx(NumberField, { value: props.value.x, min: props.min, step: props.step, onChange: (n) => set('x', n) }), _jsx(NumberField, { value: props.value.y, min: props.min, step: props.step, onChange: (n) => set('y', n) }), _jsx(NumberField, { value: props.value.z, min: props.min, step: props.step, onChange: (n) => set('z', n) })] }) }));
143
159
  }
160
+ /** The Turbulence module: a curl noise force field. */
144
161
  export function TurbulenceModule({ binding }) {
145
- return (_jsx(BehaviorModule, { binding: binding, title: "Turbulence", type: "TurbulenceField", create: () => new TurbulenceField(new Vector3(1, 1, 1), 3, new Vector3(1, 1, 1), new Vector3(1, 1, 1)), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx(Vector3Row, { hintKey: "turbulence.scale", label: "Scale", value: behavior.scale, min: 0.01, step: 0.1, onChange: (v) => binding.apply(() => (behavior.scale = v)) }), _jsx(Row, { label: "Octaves", children: _jsx(NumberField, { value: behavior.octaves, min: 1, step: 1, onChange: (v) => binding.apply(() => (behavior.octaves = Math.max(1, Math.round(v)))) }) }), _jsx(Vector3Row, { hintKey: "turbulence.strength", label: "Strength", value: behavior.velocityMultiplier, step: 0.5, onChange: (v) => binding.apply(() => (behavior.velocityMultiplier = v)) }), _jsx(Vector3Row, { label: "Time scale", value: behavior.timeScale, step: 0.1, onChange: (v) => binding.apply(() => (behavior.timeScale = v)) })] })) }));
162
+ return (_jsx(BehaviorModule, { binding: binding, title: "Turbulence", type: "TurbulenceField", create: () => new TurbulenceField(new Vector3(1, 1, 1), 3, new Vector3(1, 1, 1), new Vector3(1, 1, 1)), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx(Vector3Row, { hintKey: "turbulence.scale", label: "Scale", value: behavior.scale, min: 0.01, step: 0.1, onChange: (v) => binding.apply(() => (behavior.scale = v)) }), _jsx(Row, { label: "Octaves", children: _jsx(NumberField, { value: behavior.octaves, min: 1, step: 1, onChange: (v) => binding.apply(() => (behavior.octaves = Math.max(1, Math.round(v)))) }) }), _jsx(Vector3Row, { hintKey: "turbulence.strength", label: "Strength", value: behavior.velocityMultiplier, step: 0.5, onChange: (v) => binding.apply(() => (behavior.velocityMultiplier = v)) }), _jsx(Vector3Row, { label: "Time scale", value: behavior.timeScale, step: 0.1, onChange: (v) => binding.apply(() => (behavior.timeScale = v)) }), _jsx(Row, { label: "Seed", children: _jsx(NumberField, { value: behavior.seed, step: 1, onChange: (v) => binding.apply(() => {
163
+ const seed = Math.round(v);
164
+ const next = new TurbulenceField(behavior.scale, behavior.octaves, behavior.velocityMultiplier, behavior.timeScale, seed);
165
+ behavior.seed = seed;
166
+ behavior.generator = next.generator;
167
+ behavior.initialOffset.copy(next.initialOffset);
168
+ behavior.timeOffset.copy(next.initialOffset);
169
+ }) }) })] })) }));
146
170
  }
171
+ /** The Randomize Direction module: tilts each particle's start direction by a random angle. */
147
172
  export function EmitDirectionModule({ binding }) {
148
- return (_jsx(BehaviorModule, { binding: binding, title: "Randomize Direction", type: "ChangeEmitDirection", create: () => new ChangeEmitDirection(new IntervalValue(0, Math.PI / 6)), children: (behavior) => (_jsx(ValueField, { label: "Angle (rad)", generator: behavior.angle, min: 0, curveMax: Math.PI, onChange: (g) => binding.apply(() => (behavior.angle = g)) })) }));
173
+ return (_jsx(BehaviorModule, { binding: binding, title: "Randomize Direction", type: "ChangeEmitDirection", create: () => new ChangeEmitDirection(new IntervalValue(0, Math.PI / 6)), children: (behavior) => (_jsx(ValueField, { label: "Angle (rad)", generator: behavior.angle, curve: false, min: 0, onChange: (g) => binding.apply(() => (behavior.angle = g)) })) }));
149
174
  }
175
+ /** The Collision module (Unity's Collision): particles bounce off or die on a ground plane. */
150
176
  export function CollisionModule({ binding }) {
151
177
  // Like Unity: Collision stores bounce only. Colliders are host-provided via
152
- // setPhysicsResolver — the editor never invents a ground plane.
153
- return (_jsx(BehaviorModule, { binding: binding, title: "Collision", type: "ApplyCollision", create: () => new ApplyCollision(getPhysicsResolver(), 0.5), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx(Row, { label: "Bounce", children: _jsx(NumberField, { value: behavior.bounce, min: 0, step: 0.05, onChange: (v) => binding.apply(() => (behavior.bounce = v)) }) }), _jsx("div", { style: { fontSize: 11, opacity: 0.6, marginTop: 4 }, children: "Bounce is saved with the effect. Colliders come from the host (setPhysicsResolver) \u2014 without one, Collision is inert." })] })) }));
178
+ // setPhysicsResolver, asked when a particle is tested — the editor never invents a ground plane.
179
+ return (_jsx(BehaviorModule, { binding: binding, title: "Collision", type: "ApplyCollision", create: () => new ApplyCollision(undefined, 0.5), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx(Row, { label: "Bounce", children: _jsx(NumberField, { value: behavior.bounce, min: 0, step: 0.05, onChange: (v) => binding.apply(() => (behavior.bounce = v)) }) }), _jsx("div", { style: { fontSize: 11, opacity: 0.6, marginTop: 4 }, children: "Bounce is saved with the effect. Colliders come from the host (setPhysicsResolver) \u2014 without one, Collision is inert." })] })) }));
154
180
  }
155
181
  /** Shared speed-range rows for the "by Speed" trio. */
156
182
  function SpeedRangeRows({ binding, behavior }) {
157
183
  return (_jsx(Row, { label: "Speed range", children: _jsxs("div", { style: { display: 'flex', gap: 6 }, children: [_jsx(NumberField, { value: behavior.speedRange.a, min: 0, onChange: (v) => binding.apply(() => (behavior.speedRange = new IntervalValue(v, behavior.speedRange.b))) }), _jsx(NumberField, { value: behavior.speedRange.b, min: 0, onChange: (v) => binding.apply(() => (behavior.speedRange = new IntervalValue(behavior.speedRange.a, v))) })] }) }));
158
184
  }
185
+ /** The Color by Speed module (Unity's Color by Speed): a gradient over a speed range. */
159
186
  export function ColorBySpeedModule({ binding }) {
160
187
  return (_jsx(BehaviorModule, { binding: binding, title: "Color by Speed", type: "ColorBySpeed", create: () => new ColorBySpeed(buildGradient(DEFAULT_GRADIENT_STOPS), new IntervalValue(0, 5)), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx("div", { style: { marginTop: 6 }, children: _jsx(GradientEditor, { stops: behavior.color instanceof Gradient
161
188
  ? readGradientStops(behavior.color)
162
189
  : DEFAULT_GRADIENT_STOPS, onChange: (stops) => binding.apply(() => (behavior.color = buildGradient(stops))) }) }), _jsx(SpeedRangeRows, { binding: binding, behavior: behavior })] })) }));
163
190
  }
191
+ /** The Size by Speed module (Unity's Size by Speed): size over a speed range. */
164
192
  export function SizeBySpeedModule({ binding }) {
165
193
  return (_jsx(BehaviorModule, { binding: binding, title: "Size by Speed", type: "SizeBySpeed", create: () => new SizeBySpeed(buildScalar({ mode: 'curve', value: 1, min: 0, max: 1, curve: [0.5, 0.8, 1.2, 1.5] }), new IntervalValue(0, 5)), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx("div", { style: { marginTop: 6 }, children: _jsx(CurveEditor, { pieces: readPieces(behavior.size), maxValue: 2, onChange: (pieces) => binding.apply(() => (behavior.size = buildCurve(pieces))) }) }), _jsx(SpeedRangeRows, { binding: binding, behavior: behavior })] })) }));
166
194
  }
195
+ /** The Rotation by Speed module (Unity's Rotation by Speed): spin rate over a speed range. */
167
196
  export function RotationBySpeedModule({ binding }) {
168
197
  return (_jsx(BehaviorModule, { binding: binding, title: "Rotation by Speed", type: "RotationBySpeed", create: () => new RotationBySpeed(new ConstantValue(Math.PI), new IntervalValue(0, 5)), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx(ValueField, { hintKey: "rotationBySpeed.velocity", label: "Velocity (rad/s)", generator: behavior.angularVelocity, curveMax: Math.PI * 4, onChange: (g) => binding.apply(() => (behavior.angularVelocity = g)) }), _jsx(SpeedRangeRows, { binding: binding, behavior: behavior })] })) }));
169
198
  }
199
+ /** The Orbit over Lifetime module (Unity's Velocity over Lifetime orbital): turns particles about an axis. */
170
200
  export function OrbitOverLifeModule({ binding }) {
171
201
  return (_jsx(BehaviorModule, { binding: binding, title: "Orbit over Lifetime", type: "OrbitOverLife", create: () => new OrbitOverLife(new ConstantValue(Math.PI), new Vector3(0, 1, 0)), children: (behavior) => (_jsx(ValueField, { label: "Orbit speed", generator: behavior.orbitSpeed, curveMax: Math.PI * 4, onChange: (g) => binding.apply(() => (behavior.orbitSpeed = g)) })) }));
172
202
  }
203
+ /** The Width over Trail module (Unity's Trails width over trail): a trail's width along its length. */
173
204
  export function WidthOverTrailModule({ binding }) {
174
205
  return (_jsx(BehaviorModule, { binding: binding, title: "Width over Trail", type: "WidthOverLength", create: () => new WidthOverLength(buildScalar({ mode: 'curve', value: 1, min: 0, max: 1, curve: [1, 0.75, 0.4, 0] })), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx("div", { style: { marginTop: 6 }, children: _jsx(CurveEditor, { pieces: readPieces(behavior.width), maxValue: 2, onChange: (pieces) => binding.apply(() => (behavior.width = buildCurve(pieces))) }) }), _jsx(Row, { label: "Absolute width", children: _jsx(CheckboxField, { value: behavior.absolute, onChange: (v) => binding.apply(() => (behavior.absolute = v)) }) })] })) }));
175
206
  }
207
+ /** How many of the first frames a start-frame generator picks from: its exclusive upper bound. */
208
+ function startFrameCount(startFrame) {
209
+ const state = readScalar(startFrame);
210
+ return Math.round(state.mode === 'random' ? state.max : state.value);
211
+ }
212
+ /** The Texture Sheet Animation module (Unity's Texture Sheet Animation): plays the sheet's frames by life or by frame rate. */
176
213
  export function TextureSheetModule({ binding }) {
177
214
  const system = binding.system;
178
215
  const frameBehavior = findBehavior(system.behaviors, 'FrameOverLife');
@@ -225,5 +262,19 @@ export function TextureSheetModule({ binding }) {
225
262
  curve: [0, (tiles - 1) / 3, (2 * (tiles - 1)) / 3, tiles - 1],
226
263
  })));
227
264
  }
228
- }) }) }), frameBehavior && (_jsx("div", { style: { marginTop: 6 }, children: _jsx(CurveEditor, { pieces: readPieces(frameBehavior.frame), maxValue: Math.max(1, tiles - 1), onChange: (pieces) => binding.apply(() => (frameBehavior.frame = buildCurve(pieces))) }) }))] }));
265
+ }) }) }), frameBehavior && (_jsxs(_Fragment, { children: [_jsx(Row, { label: "Time mode", children: _jsx(SelectField, { value: frameBehavior.fps > 0 ? 'fps' : 'life', options: [
266
+ { value: 'life', label: 'Lifetime (curve over life)' },
267
+ { value: 'fps', label: 'FPS (frame from curve, then by age)' },
268
+ ], onChange: (mode) => binding.apply(() => {
269
+ frameBehavior.fps = mode === 'fps' ? 12 : 0;
270
+ if (mode === 'fps' && frameBehavior.frameCount <= 0) {
271
+ frameBehavior.frameCount = tiles;
272
+ }
273
+ }) }) }), frameBehavior.fps > 0 ? (_jsxs(_Fragment, { children: [_jsx(Row, { label: "FPS", children: _jsx(NumberField, { value: frameBehavior.fps, min: 0.01, step: 1, onChange: (v) => binding.apply(() => (frameBehavior.fps = v)) }) }), _jsx(Row, { label: "Loop", children: _jsx(CheckboxField, { value: frameBehavior.loop, onChange: (v) => binding.apply(() => (frameBehavior.loop = v)) }) }), _jsx(Row, { label: "Random frame", children: _jsx(CheckboxField, { value: frameBehavior.random, onChange: (v) => binding.apply(() => (frameBehavior.random = v)) }) })] })) : (_jsx(Row, { label: "Cycles", children: _jsx(NumberField, { value: frameBehavior.cycles, min: 0.01, step: 1, onChange: (v) => binding.apply(() => (frameBehavior.cycles = v)) }) })), _jsx(Row, { label: "Frame count", children: _jsx(NumberField, { value: frameBehavior.frameCount, min: frameBehavior.fps > 0 || frameBehavior.startFrame ? 1 : 0, max: tiles, step: 1, onChange: (v) => binding.apply(() => (frameBehavior.frameCount = Math.max(0, Math.round(v)))) }) }), _jsxs(Row, { label: "Start frame", children: [_jsx(CheckboxField, { value: !!frameBehavior.startFrame, onChange: (v) => binding.apply(() => {
274
+ frameBehavior.startFrame = v ? new IntervalValue(0, tiles) : undefined;
275
+ // The offset only wraps into a segment; without one it runs off the sheet.
276
+ if (v && frameBehavior.frameCount <= 0) {
277
+ frameBehavior.frameCount = tiles;
278
+ }
279
+ }) }), frameBehavior.startFrame && (_jsx(NumberField, { value: startFrameCount(frameBehavior.startFrame), min: 1, max: tiles, step: 1, onChange: (v) => binding.apply(() => (frameBehavior.startFrame = new IntervalValue(0, Math.max(1, Math.round(v))))) }))] }), _jsx("div", { style: { marginTop: 6 }, children: _jsx(CurveEditor, { pieces: readPieces(frameBehavior.frame), maxValue: Math.max(1, tiles - 1), onChange: (pieces) => binding.apply(() => (frameBehavior.frame = buildCurve(pieces))) }) })] }))] }));
229
280
  }
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+ /** An inspector row: a label, with an optional hint, and its controls. */
2
3
  export declare function Row(props: {
3
4
  label: string;
4
5
  hint?: string;
@@ -11,16 +12,24 @@ export declare function NumberField(props: {
11
12
  onChange: (value: number) => void;
12
13
  step?: number;
13
14
  min?: number;
15
+ max?: number;
14
16
  }): React.JSX.Element;
17
+ /** A checkbox bound to a boolean. */
15
18
  export declare function CheckboxField(props: {
16
19
  value: boolean;
17
20
  onChange: (value: boolean) => void;
18
21
  }): React.JSX.Element;
22
+ /** A select bound to a value, from a list of options. */
19
23
  export declare function SelectField<T extends string | number>(props: {
24
+ /** The selected value. */
20
25
  value: T;
26
+ /** The options, in menu order. */
21
27
  options: Array<{
28
+ /** The option's value. */
22
29
  value: T;
30
+ /** Its text. */
23
31
  label: string;
24
32
  }>;
33
+ /** Receives the picked value, a number when the values are numbers. */
25
34
  onChange: (value: T) => void;
26
35
  }): React.JSX.Element;
@@ -1,7 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React, { useEffect, useState } from 'react';
3
- import { InspectorLabel } from './InspectorLabel';
4
- import { inputStyle, rowStyle } from './theme';
3
+ import { InspectorLabel } from './InspectorLabel.js';
4
+ import { inputStyle, rowStyle } from './theme.js';
5
+ /** An inspector row: a label, with an optional hint, and its controls. */
5
6
  export function Row(props) {
6
7
  return (_jsxs("div", { style: rowStyle, children: [_jsx(InspectorLabel, { label: props.label, hint: props.hint, hintKey: props.hintKey }), props.children] }));
7
8
  }
@@ -14,7 +15,7 @@ export function NumberField(props) {
14
15
  setText(String(round(props.value)));
15
16
  }
16
17
  }, [props.value]);
17
- return (_jsx("input", { type: "number", className: "qe-hover", style: inputStyle, value: text, step: props.step ?? 0.1, min: props.min, onFocus: () => {
18
+ return (_jsx("input", { type: "number", className: "qe-hover", style: inputStyle, value: text, step: props.step ?? 0.1, min: props.min, max: props.max, onFocus: () => {
18
19
  focusedRef.current = true;
19
20
  }, onBlur: () => {
20
21
  focusedRef.current = false;
@@ -27,9 +28,11 @@ export function NumberField(props) {
27
28
  }
28
29
  } }));
29
30
  }
31
+ /** A checkbox bound to a boolean. */
30
32
  export function CheckboxField(props) {
31
33
  return (_jsx("input", { type: "checkbox", className: "qe-hover", checked: props.value, onChange: (e) => props.onChange(e.target.checked), style: { justifySelf: 'start', width: 15, height: 15, accentColor: '#78a5ff', cursor: 'pointer' } }));
32
34
  }
35
+ /** A select bound to a value, from a list of options. */
33
36
  export function SelectField(props) {
34
37
  const numeric = typeof props.value === 'number';
35
38
  return (_jsx("select", { className: "qe-hover", style: { ...inputStyle, cursor: 'pointer' }, value: String(props.value), onChange: (e) => props.onChange((numeric ? Number(e.target.value) : e.target.value)), children: props.options.map((o) => (_jsx("option", { value: String(o.value), children: o.label }, String(o.value)))) }));
@@ -0,0 +1,36 @@
1
+ import React from 'react';
2
+ import { type AssetsPanelProps } from '../AssetsPanel.js';
3
+ /** Props of {@link AssetsDrawer}. */
4
+ export interface AssetsDrawerProps {
5
+ /** The drawer is shown. */
6
+ open: boolean;
7
+ /** The drawer is docked above the timeline instead of floating over the viewport. */
8
+ pinned: boolean;
9
+ /** Pin or unpin it (it stays open). */
10
+ onTogglePin(): void;
11
+ /** Close and unpin it. */
12
+ onClose(): void;
13
+ /** The Assets browser inside. */
14
+ panel: AssetsPanelProps;
15
+ }
16
+ /** The Assets browser, floating over the viewport or pinned above the timeline. */
17
+ export declare function AssetsDrawer(props: AssetsDrawerProps): React.ReactElement;
18
+ /** Props of {@link TimelineDock}. */
19
+ export interface TimelineDockProps {
20
+ /** The timeline is collapsed to its header. */
21
+ collapsed: boolean;
22
+ /** Collapse or expand it. */
23
+ onToggleCollapsed(): void;
24
+ /** Name of the focused effect, if any. */
25
+ effectName?: string;
26
+ /** Assets in the project, counted on the Assets button. */
27
+ assetCount: number;
28
+ /** The Assets button shows as switched on. */
29
+ assetsActive: boolean;
30
+ /** The Assets button. */
31
+ onToggleAssets(): void;
32
+ /** The timeline, or `null` when nothing is focused. */
33
+ children: React.ReactNode;
34
+ }
35
+ /** The bottom dock: the focused effect's timeline, and the Assets button. */
36
+ export declare function TimelineDock(props: TimelineDockProps): React.ReactElement;
@@ -0,0 +1,63 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ChevronDownIcon, ChevronUpIcon, MapPinIcon, XMarkIcon } from '@heroicons/react/24/solid';
3
+ import { AssetsPanel } from '../AssetsPanel.js';
4
+ import { iconStyle } from '../icons.js';
5
+ import { buttonStyle, theme } from '../theme.js';
6
+ import { activeSquareButtonStyle, dockIconButtonStyle, dockTitleStyle, DRAWER_HEIGHT, TIMELINE_HEIGHT, } from './hostStyles.js';
7
+ /** The Assets browser, floating over the viewport or pinned above the timeline. */
8
+ export function AssetsDrawer(props) {
9
+ const { open, pinned } = props;
10
+ return (_jsxs("div", { "aria-hidden": !open && !pinned, style: {
11
+ height: DRAWER_HEIGHT,
12
+ display: 'flex',
13
+ flexDirection: 'column',
14
+ background: theme.panelBg,
15
+ borderTop: `1px solid ${theme.border}`,
16
+ ...(pinned
17
+ ? { flexShrink: 0 }
18
+ : {
19
+ position: 'absolute',
20
+ left: 0,
21
+ right: 0,
22
+ bottom: 0,
23
+ zIndex: 20,
24
+ background: '#0a0f20',
25
+ boxShadow: '0 -10px 30px rgba(0, 0, 0, 0.5)',
26
+ transform: open ? 'none' : 'translateY(100%)',
27
+ opacity: open ? 1 : 0,
28
+ pointerEvents: open ? 'auto' : 'none',
29
+ transition: 'transform 0.18s ease, opacity 0.18s ease',
30
+ }),
31
+ }, children: [_jsxs("div", { style: {
32
+ display: 'flex',
33
+ alignItems: 'center',
34
+ gap: 6,
35
+ padding: '4px 6px 4px 12px',
36
+ borderBottom: `1px solid ${theme.border}`,
37
+ }, children: [_jsx("span", { style: dockTitleStyle, children: "Assets" }), _jsx("span", { style: { fontSize: 11, color: theme.textDim }, children: "Ctrl+Space" }), _jsx("span", { style: { flex: 1 } }), _jsx("button", { type: "button", className: "qe-hover", title: pinned ? 'Unpin — float over the viewport again' : 'Pin — keep it docked above the timeline', style: { ...dockIconButtonStyle, ...(pinned ? activeSquareButtonStyle : {}) }, onClick: props.onTogglePin, children: _jsx(MapPinIcon, { style: iconStyle(13) }) }), _jsx("button", { type: "button", className: "qe-hover", title: "Close (Esc)", style: dockIconButtonStyle, onClick: props.onClose, children: _jsx(XMarkIcon, { style: iconStyle(13) }) })] }), _jsx(AssetsPanel, { ...props.panel })] }));
38
+ }
39
+ /** The bottom dock: the focused effect's timeline, and the Assets button. */
40
+ export function TimelineDock(props) {
41
+ const { collapsed } = props;
42
+ return (_jsxs("div", { style: {
43
+ flexShrink: 0,
44
+ borderTop: `1px solid ${theme.border}`,
45
+ background: theme.panelBg,
46
+ display: 'flex',
47
+ flexDirection: 'column',
48
+ }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 6, padding: '3px 6px' }, children: [_jsx("button", { type: "button", className: "qe-hover-bg", title: collapsed ? 'Expand timeline' : 'Collapse timeline', style: { ...dockIconButtonStyle, border: 'none', background: 'transparent' }, onClick: props.onToggleCollapsed, children: collapsed ? _jsx(ChevronUpIcon, { style: iconStyle(13) }) : _jsx(ChevronDownIcon, { style: iconStyle(13) }) }), _jsx("span", { style: dockTitleStyle, children: "Timeline" }), props.effectName !== undefined && (_jsx("span", { style: {
49
+ fontSize: 12,
50
+ color: theme.textDim,
51
+ overflow: 'hidden',
52
+ textOverflow: 'ellipsis',
53
+ whiteSpace: 'nowrap',
54
+ }, children: props.effectName })), _jsx("span", { style: { flex: 1 } }), _jsxs("button", { type: "button", className: "qe-hover", title: "Assets (Ctrl+Space)", style: {
55
+ ...buttonStyle,
56
+ padding: '3px 10px',
57
+ ...(props.assetsActive ? activeSquareButtonStyle : {}),
58
+ }, onClick: props.onToggleAssets, children: ["Assets", props.assetCount > 0 ? ` · ${props.assetCount}` : ''] })] }), _jsx("div", { style: {
59
+ height: TIMELINE_HEIGHT,
60
+ display: collapsed ? 'none' : 'flex',
61
+ borderTop: `1px solid ${theme.border}`,
62
+ }, children: props.children ?? (_jsx("div", { style: { padding: '14px', fontSize: 12, color: theme.textDim }, children: "Select an effect in the Scene to see its timeline." })) })] }));
63
+ }