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,23 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { Texture } from '@babylonjs/core/Materials/Textures/texture';
3
- import { Mesh } from '@babylonjs/core/Meshes/mesh';
2
+ import { Texture } from '@babylonjs/core/Materials/Textures/texture.js';
3
+ import { Mesh } from '@babylonjs/core/Meshes/mesh.js';
4
4
  import { PlusIcon, XMarkIcon } from '@heroicons/react/24/solid';
5
- import { AxisAngleGenerator, BoxEmitFrom, ColorOverLife, ColorRange, ConstantColor, ConstantValue, EulerGenerator, Gradient, IntervalValue, RandomColor, RandomColorBetweenGradient, RandomQuatGenerator, RenderMode, SizeOverLife, Vector3, Vector3Function, Vector4, } from 'babylon.quarks';
6
- import React, { useMemo, useState } from 'react';
7
- import { DEFAULT_GRADIENT_STOPS, buildGradient, findBehavior, hexToRgb, readGradientStops, rgbToHex, } from '../core/colors';
8
- import { applyRendererMaterial, getMaterialLabel } from '../core/material';
9
- import { DEFAULT_SHAPE_PARAMS, SHAPE_PARAM_KEYS, SHAPE_TYPES, createShape, getShapeType, readShapeParams, } from '../core/shapes';
10
- import { buildCurve, buildScalar, readPieces, readScalar } from '../core/values';
11
- import { CurveEditor } from './CurveEditor';
12
- import { GradientEditor } from './GradientEditor';
13
- import { InspectorInfoIcon } from './InspectorInfoIcon';
14
- import { ModuleSection } from './ModuleSection';
15
- import { PromptDialog } from './PromptDialog';
16
- import { ValueField } from './ValueField';
17
- import { CheckboxField, NumberField, Row, SelectField } from './fields';
18
- import { iconStyle } from './icons';
19
- import { theme } from './theme';
5
+ import { AxisAngleGenerator, BoxEmitFrom, ColorOverLife, ColorRange, ConstantColor, ConstantValue, EulerGenerator, Gradient, IntervalValue, RandomColor, RandomColorBetweenGradient, RandomQuatGenerator, RenderMode, SizeOverLife, Vector2, Vector3, Vector3Function, Vector4, normalizeTextureUV, } from 'babylon.quarks';
6
+ import React, { useMemo } from 'react';
7
+ import { DEFAULT_GRADIENT_STOPS, buildGradient, findBehavior, hexToRgb, readGradientStops, rgbToHex, } from '../core/colors.js';
8
+ import { applyRendererMaterial, getMaterialLabel } from '../core/material.js';
9
+ import { DEFAULT_SHAPE_PARAMS, SHAPE_PARAM_KEYS, SHAPE_TYPES, createShape, getShapeType, readShapeParams, } from '../core/shapes.js';
10
+ import { buildCurve, buildScalar, readPieces, readScalar } from '../core/values.js';
11
+ import { CurveEditor } from './CurveEditor.js';
12
+ import { GradientEditor } from './GradientEditor.js';
13
+ import { InspectorInfoIcon } from './InspectorInfoIcon.js';
14
+ import { ModuleSection } from './ModuleSection.js';
15
+ import { TextureSelect } from './TextureSelect.js';
16
+ import { TextureUVField } from './TextureUVField.js';
17
+ import { ValueField } from './ValueField.js';
18
+ import { CheckboxField, NumberField, Row, SelectField } from './fields.js';
19
+ import { iconStyle } from './icons.js';
20
+ import { theme } from './theme.js';
20
21
  const MESH_QUAD_POSITIONS = new Float32Array([-0.5, -0.5, 0, 0.5, -0.5, 0, 0.5, 0.5, 0, -0.5, 0.5, 0]);
21
22
  const MESH_QUAD_INDICES = new Uint32Array([0, 1, 2, 0, 2, 3]);
22
23
  const MESH_QUAD_UVS = new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]);
@@ -61,6 +62,29 @@ const PARAM_LABELS = {
61
62
  depth: 'Depth',
62
63
  speed: 'Speed (lengths/s)',
63
64
  };
65
+ /** Uppercase group title inside a module, as the Renderer's "Material" heading. */
66
+ function SubHeader(props) {
67
+ return (_jsxs("div", { style: {
68
+ marginTop: 10,
69
+ marginBottom: 2,
70
+ fontSize: 11,
71
+ fontWeight: 600,
72
+ color: '#7c8db5',
73
+ textTransform: 'uppercase',
74
+ letterSpacing: 0.4,
75
+ display: 'flex',
76
+ alignItems: 'center',
77
+ gap: 5,
78
+ }, children: [props.title, _jsx(InspectorInfoIcon, { content: props.hint, size: 12 })] }));
79
+ }
80
+ /** Three number inputs editing a Vector3 by value. */
81
+ function AxisField(props) {
82
+ return (_jsx("div", { style: { display: 'flex', gap: 6 }, children: ['x', 'y', 'z'].map((axis) => (_jsx(NumberField, { value: props.value[axis], step: 0.1, onChange: (v) => {
83
+ const next = props.value.clone();
84
+ next[axis] = v;
85
+ props.onChange(next);
86
+ } }, axis))) }));
87
+ }
64
88
  function ColorInput(props) {
65
89
  const { value, onChange } = props;
66
90
  return (_jsxs("div", { style: { display: 'flex', gap: 6, alignItems: 'center' }, children: [_jsx("input", { type: "color", className: "qe-hover", value: rgbToHex(value.x, value.y, value.z), onChange: (e) => {
@@ -75,17 +99,19 @@ function ColorInput(props) {
75
99
  cursor: 'pointer',
76
100
  } }), _jsx(NumberField, { value: value.w, min: 0, step: 0.05, onChange: (a) => onChange(new Vector4(value.x, value.y, value.z, Math.min(1, a))) })] }));
77
101
  }
102
+ /** The Main module (Unity's main module): duration, looping, start delay, life, speed, size, rotation and colour. */
78
103
  export function MainModule({ binding }) {
79
104
  const system = binding.system;
80
105
  const startColor = system.startColor;
81
106
  const colorMode = startColor instanceof RandomColor ? 'random' : startColor instanceof ColorRange ? 'range' : 'constant';
82
- const twoColors = startColor instanceof RandomColor || startColor instanceof ColorRange;
83
- const colorA = twoColors
84
- ? startColor.a
107
+ const twoColor = startColor instanceof RandomColor || startColor instanceof ColorRange ? startColor : null;
108
+ const colorA = twoColor
109
+ ? twoColor.a
85
110
  : startColor instanceof ConstantColor
86
111
  ? startColor.color
87
112
  : new Vector4(1, 1, 1, 1);
88
- const colorB = twoColors ? startColor.b : new Vector4(1, 0.5, 0.2, 1);
113
+ const colorB = twoColor ? twoColor.b : new Vector4(1, 0.5, 0.2, 1);
114
+ const twoColors = twoColor !== null;
89
115
  /** The two-colour generator of the current mode. */
90
116
  const makeTwoColors = (a, b) => colorMode === 'range' ? new ColorRange(a, b) : new RandomColor(a, b);
91
117
  const rotation = system.startRotation;
@@ -111,34 +137,36 @@ export function MainModule({ binding }) {
111
137
  const cur = s.startRotation;
112
138
  s.startRotation = new EulerGenerator(axis === 'x' ? new ConstantValue(v) : cur.angleX, axis === 'y' ? new ConstantValue(v) : cur.angleY, axis === 'z' ? new ConstantValue(v) : cur.angleZ);
113
139
  });
114
- const startSize = system.startSize;
115
- const size3D = startSize.type === 'vec3function';
140
+ const startSize3D = system.startSize instanceof Vector3Function ? system.startSize : null;
116
141
  const colorGenMode = startColor instanceof RandomColorBetweenGradient
117
142
  ? 'gradient2'
118
143
  : startColor instanceof Gradient
119
144
  ? 'gradient'
120
145
  : colorMode;
121
- return (_jsxs(ModuleSection, { title: "Main", children: [_jsx(Row, { label: "Duration", children: _jsx(NumberField, { value: system.duration, min: 0.05, onChange: (v) => binding.apply((s) => (s.duration = v)) }) }), _jsx(Row, { label: "Looping", children: _jsx(CheckboxField, { value: system.looping, onChange: (v) => binding.apply((s) => (s.looping = v)) }) }), _jsx(Row, { label: "World space", children: _jsx(CheckboxField, { value: system.worldSpace, onChange: (v) => binding.apply((s) => (s.worldSpace = v)) }) }), _jsx(Row, { label: "Prewarm", children: _jsx(CheckboxField, { value: system.prewarm, onChange: (v) => binding.apply((s) => (s.prewarm = v)) }) }), _jsx(Row, { label: "Playback speed", children: _jsx(NumberField, { value: system.playbackSpeed, step: 0.1, min: 0, onChange: (v) => binding.apply((s) => (s.playbackSpeed = Math.max(0, v))) }) }), _jsxs(Row, { label: "Fixed seed", children: [_jsx(CheckboxField, { value: system.seed !== undefined, onChange: (v) => binding.apply((s) => (s.seed = v ? (s.seed ?? 1) : undefined)) }), system.seed !== undefined && (_jsx(NumberField, { value: system.seed, step: 1, min: 0, onChange: (v) => binding.apply((s) => (s.seed = Math.max(0, Math.floor(v)))) }))] }), _jsx(Row, { label: "Off screen", children: _jsx(SelectField, { value: system.cullingMode, options: [
146
+ return (_jsxs(ModuleSection, { title: "Main", children: [_jsx(Row, { label: "Duration", children: _jsx(NumberField, { value: system.duration, min: 0.05, onChange: (v) => binding.apply((s) => (s.duration = v)) }) }), _jsx(Row, { label: "Looping", children: _jsx(CheckboxField, { value: system.looping, onChange: (v) => binding.apply((s) => (s.looping = v)) }) }), _jsx(Row, { label: "World space", children: _jsx(CheckboxField, { value: system.worldSpace, onChange: (v) => binding.apply((s) => (s.worldSpace = v)) }) }), system.worldSpace && (_jsx(Row, { label: "Scaling mode", children: _jsx(SelectField, { value: system.scalingMode, options: [
147
+ { value: 'shape', label: 'Shape (sizes and shape only)' },
148
+ { value: 'hierarchy', label: 'Hierarchy (motion scales too)' },
149
+ ], onChange: (mode) => binding.apply((s) => (s.scalingMode = mode)) }) })), _jsx(Row, { label: "Prewarm", children: _jsx(CheckboxField, { value: system.prewarm, onChange: (v) => binding.apply((s) => (s.prewarm = v)) }) }), system.prewarm && (_jsx(Row, { label: "Warm-up time", children: _jsx(NumberField, { value: system.prewarmTime, step: 0.1, min: 0, onChange: (v) => binding.apply((s) => (s.prewarmTime = Math.max(0, v))) }) })), _jsx(Row, { label: "Playback speed", children: _jsx(NumberField, { value: system.playbackSpeed, step: 0.1, min: 0, onChange: (v) => binding.apply((s) => (s.playbackSpeed = Math.max(0, v))) }) }), _jsxs(Row, { label: "Fixed seed", children: [_jsx(CheckboxField, { value: system.seed !== undefined, onChange: (v) => binding.apply((s) => (s.seed = v ? (s.seed ?? 1) : undefined)) }), system.seed !== undefined && (_jsx(NumberField, { value: system.seed, step: 1, min: 0, onChange: (v) => binding.apply((s) => (s.seed = Math.max(0, Math.floor(v)))) }))] }), _jsx(Row, { label: "Off screen", children: _jsx(SelectField, { value: system.cullingMode, options: [
122
150
  { value: 'always', label: 'Always simulate' },
123
151
  { value: 'pause', label: 'Pause' },
124
152
  { value: 'pauseAndCatchUp', label: 'Pause and catch up' },
125
153
  ], onChange: (mode) => binding.apply((s) => (s.cullingMode = mode)) }) }), _jsx(ValueField, { label: "Start delay", generator: system.startDelay, min: 0, curveMax: 5, onChange: (g) => binding.apply((s) => {
126
154
  s.startDelay = g;
127
155
  s.restart();
128
- }) }), _jsx(ValueField, { label: "Start lifetime", generator: system.startLife, min: 0.01, curveMax: 10, onChange: (g) => binding.apply((s) => (s.startLife = g)) }), _jsx(ValueField, { label: "Start speed", generator: system.startSpeed, min: 0, curveMax: 20, onChange: (g) => binding.apply((s) => (s.startSpeed = g)) }), _jsx(Row, { label: "Start size", children: _jsx(SelectField, { value: size3D ? '3d' : 'uniform', options: [
156
+ }) }), _jsx(ValueField, { label: "Start lifetime", generator: system.startLife, min: 0.01, curveMax: 10, onChange: (g) => binding.apply((s) => (s.startLife = g)) }), _jsx(ValueField, { label: "Start speed", generator: system.startSpeed, min: 0, curveMax: 20, onChange: (g) => binding.apply((s) => (s.startSpeed = g)) }), _jsx(Row, { label: "Start size", children: _jsx(SelectField, { value: startSize3D ? '3d' : 'uniform', options: [
129
157
  { value: 'uniform', label: 'Uniform' },
130
158
  { value: '3d', label: '3D (per axis)' },
131
159
  ], onChange: (mode) => binding.apply((s) => {
132
160
  const cur = s.startSize;
133
- if (mode === '3d') {
161
+ if (mode === '3d' && cur.type !== 'vec3function') {
134
162
  // Seed all three axes from the current uniform generator.
135
163
  s.startSize = new Vector3Function(cur.clone(), cur.clone(), cur.clone());
136
164
  }
137
- else {
165
+ else if (mode !== '3d' && cur instanceof Vector3Function) {
138
166
  // Collapse back to the X-axis generator.
139
- s.startSize = s.startSize.x;
167
+ s.startSize = cur.x;
140
168
  }
141
- }) }) }), !size3D && (_jsx(ValueField, { label: "", generator: system.startSize, min: 0, curveMax: 5, onChange: (g) => binding.apply((s) => (s.startSize = g)) })), size3D && (_jsx(_Fragment, { children: ['x', 'y', 'z'].map((axis) => (_jsx(ValueField, { label: axis.toUpperCase(), generator: startSize[axis], min: 0, curveMax: 5, onChange: (g) => binding.apply((s) => {
169
+ }) }) }), !startSize3D && (_jsx(ValueField, { label: "", generator: system.startSize, min: 0, curveMax: 5, onChange: (g) => binding.apply((s) => (s.startSize = g)) })), startSize3D && (_jsx(_Fragment, { children: ['x', 'y', 'z'].map((axis) => (_jsx(ValueField, { label: axis.toUpperCase(), generator: startSize3D[axis], min: 0, curveMax: 5, onChange: (g) => binding.apply((s) => {
142
170
  const cur = s.startSize;
143
171
  s.startSize = new Vector3Function(axis === 'x' ? g : cur.x, axis === 'y' ? g : cur.y, axis === 'z' ? g : cur.z);
144
172
  }) }, axis))) })), _jsx(Row, { label: "Start color", children: _jsx(SelectField, { value: colorGenMode, options: [
@@ -194,6 +222,7 @@ export function MainModule({ binding }) {
194
222
  s.startRotation = new AxisAngleGenerator(next, axisAngle.angle);
195
223
  }) }, axis))) }) }), _jsx(ValueField, { label: " Angle (rad)", generator: axisAngle.angle, curveMax: Math.PI * 2, onChange: (g) => binding.apply((s) => (s.startRotation = new AxisAngleGenerator(axisAngle.axis.clone(), g))) })] })), rotationMode === 'angle' && (_jsx(Row, { label: "", children: _jsx(NumberField, { value: rotationState.value, step: 0.1, onChange: (v) => binding.apply((s) => (s.startRotation = new ConstantValue(v))) }) })), rotationMode === 'random' && (_jsx(Row, { label: "", children: _jsxs("div", { style: { display: 'flex', gap: 6 }, children: [_jsx(NumberField, { value: rotationState.min, step: 0.1, onChange: (v) => binding.apply((s) => (s.startRotation = new IntervalValue(v, rotationState.max))) }), _jsx(NumberField, { value: rotationState.max, step: 0.1, onChange: (v) => binding.apply((s) => (s.startRotation = new IntervalValue(rotationState.min, v))) })] }) }))] }));
196
224
  }
225
+ /** The Emission module (Unity's Emission): rate over time and distance, and bursts. */
197
226
  export function EmissionModule({ binding }) {
198
227
  const system = binding.system;
199
228
  const bursts = system.emissionBursts ?? [];
@@ -228,6 +257,7 @@ export function EmissionModule({ binding }) {
228
257
  // for them would edit a value the shape silently ignores, just like the old "default to
229
258
  // first texture" bug: a control that looks live but does nothing.
230
259
  const SHAPES_WITHOUT_MODE_SPREAD = new Set(['point', 'grid', 'box', 'mesh_surface']);
260
+ /** The Shape module (Unity's Shape): the emitter shape and its settings. */
231
261
  export function ShapeModule({ binding }) {
232
262
  const system = binding.system;
233
263
  const type = getShapeType(system.emitterShape);
@@ -253,12 +283,11 @@ export function ShapeModule({ binding }) {
253
283
  { value: 3, label: 'Burst spread' },
254
284
  ], onChange: (mode) => binding.apply((s) => (s.emitterShape = createShape(type, { ...params, mode }))) }) }), _jsx(Row, { label: "Spread", children: _jsx(NumberField, { value: params.spread, min: 0, step: 0.05, onChange: (spread) => binding.apply((s) => (s.emitterShape = createShape(type, { ...params, spread }))) }) })] })), _jsx(Row, { label: "Align to direction", children: _jsx(CheckboxField, { value: binding.system.alignToDirection, onChange: (v) => binding.apply((s) => (s.alignToDirection = v)) }) })] }));
255
285
  }
286
+ /** The Size over Lifetime module (Unity's Size over Lifetime): size by a curve over the particle's life. */
256
287
  export function SizeOverLifeModule({ binding }) {
257
288
  const system = binding.system;
258
289
  const behavior = findBehavior(system.behaviors, 'SizeOverLife');
259
- const curve = behavior
260
- ? readScalar(behavior.size).curve
261
- : [0.2, 1, 1, 0.4];
290
+ const curve = behavior ? readScalar(behavior.size).curve : [0.2, 1, 1, 0.4];
262
291
  return (_jsx(ModuleSection, { title: "Size over Lifetime", enabled: !!behavior, onToggle: (enabled) => binding.apply((s) => {
263
292
  removeBehavior(s, 'SizeOverLife');
264
293
  if (enabled) {
@@ -269,6 +298,7 @@ export function SizeOverLifeModule({ binding }) {
269
298
  s.addBehavior(new SizeOverLife(buildCurve(pieces)));
270
299
  }) }) }) }));
271
300
  }
301
+ /** The Color over Lifetime module (Unity's Color over Lifetime): a gradient over the particle's life. */
272
302
  export function ColorOverLifeModule({ binding }) {
273
303
  const system = binding.system;
274
304
  const behavior = findBehavior(system.behaviors, 'ColorOverLife');
@@ -349,11 +379,16 @@ function MeshPreview({ positions, indices, size = 96, }) {
349
379
  }, [positions, indices, size]);
350
380
  return (_jsx("canvas", { ref: canvasRef, width: size, height: size, style: { width: size, height: size, border: '1px solid #2b3761', borderRadius: 6, background: '#0c1330' } }));
351
381
  }
382
+ /** The Renderer module (Unity's Renderer): render mode, material, textures, sorting and mesh settings. */
352
383
  export function RendererModule({ binding, textureOptions, geometryOptions, resolveTexture, resolveGeometry, }) {
353
384
  const system = binding.system;
354
- const currentTextureUrl = system.texture?.url ?? '';
355
- const [promptingTextureUrl, setPromptingTextureUrl] = useState(false);
356
385
  const materialLabel = getMaterialLabel(system.material);
386
+ const isMesh = system.renderMode === RenderMode.Mesh;
387
+ const gradientMap = system.gradientMap;
388
+ const erosion = system.erosion;
389
+ const distortion = system.uvDistortion;
390
+ const patchErosion = (patch) => binding.apply((s) => (s.erosion = { ...s.erosion, ...patch }));
391
+ const patchDistortion = (patch) => binding.apply((s) => (s.uvDistortion = { ...s.uvDistortion, ...patch }));
357
392
  const loadTexture = useMemo(() => {
358
393
  return (url) => {
359
394
  if (resolveTexture) {
@@ -386,99 +421,76 @@ export function RendererModule({ binding, textureOptions, geometryOptions, resol
386
421
  ['dieWithParticle', 'Die with particle'],
387
422
  ].map(([key, label]) => (_jsx(Row, { label: label, children: _jsx(CheckboxField, { value: !!system.rendererEmitterSettings[key], onChange: (v) => binding.apply((s) => {
388
423
  s.rendererEmitterSettings[key] = v;
389
- }) }) }, key))), _jsx(Row, { label: "Head width scale", children: _jsx(NumberField, { value: system.rendererEmitterSettings.headWidthScale ?? 1, min: 0, step: 0.1, onChange: (v) => binding.apply((s) => {
390
- s.rendererEmitterSettings.headWidthScale = v;
391
- }) }) })] })), system.renderMode === RenderMode.StretchedBillBoard && (_jsxs(_Fragment, { children: [_jsx(Row, { label: "Speed factor", children: _jsx(NumberField, { value: system.rendererEmitterSettings.speedFactor ?? 0, step: 0.1, onChange: (v) => binding.apply((s) => (s.rendererEmitterSettings.speedFactor = v)) }) }), _jsx(Row, { label: "Length factor", children: _jsx(NumberField, { value: system.rendererEmitterSettings.lengthFactor ?? 2, step: 0.1, onChange: (v) => binding.apply((s) => (s.rendererEmitterSettings.lengthFactor = v)) }) })] })), _jsxs("div", { style: {
392
- marginTop: 10,
393
- marginBottom: 2,
394
- fontSize: 11,
395
- fontWeight: 600,
396
- color: '#7c8db5',
397
- textTransform: 'uppercase',
398
- letterSpacing: 0.4,
399
- display: 'flex',
400
- alignItems: 'center',
401
- gap: 5,
402
- }, children: ["Material", _jsx(InspectorInfoIcon, { content: "Shader and blending settings applied to the particle sprite or mesh.", size: 12 })] }), materialLabel ? (_jsx(Row, { label: "Source", children: _jsx("div", { style: { fontSize: 12, color: theme.textDim, lineHeight: 1.45 }, children: materialLabel }) })) : null, _jsx(Row, { label: "Blend mode", children: _jsx(SelectField, { value: system.blending, options: [
424
+ }) }) }, key))), _jsx(Row, { label: "Tessellation", children: _jsx(NumberField, { value: system.rendererEmitterSettings.tessellation ?? 0, min: 0, max: 16, step: 1, onChange: (v) => binding.apply((s) => {
425
+ s.rendererEmitterSettings.tessellation = Math.round(v);
426
+ }) }) }), _jsx(Row, { label: "Curve tension", children: _jsx(NumberField, { value: system.rendererEmitterSettings.curveTension ?? 0, min: 0, max: 1, step: 0.05, onChange: (v) => binding.apply((s) => {
427
+ s.rendererEmitterSettings.curveTension = v;
428
+ }) }) }), _jsx(Row, { label: "Texture mode", children: _jsx(SelectField, { value: system.rendererEmitterSettings.textureMode ?? 'stretch', options: [
429
+ { value: 'stretch', label: 'Stretch (once, by distance)' },
430
+ { value: 'distributePerSegment', label: 'Distribute per segment' },
431
+ { value: 'tile', label: 'Tile (per world unit)' },
432
+ { value: 'repeatPerSegment', label: 'Repeat per segment' },
433
+ ], onChange: (mode) => binding.apply((s) => {
434
+ s.rendererEmitterSettings.textureMode =
435
+ mode === 'stretch' ? undefined : mode;
436
+ }) }) }), _jsx(Row, { label: "Texture scale", children: _jsx(NumberField, { value: system.rendererEmitterSettings.textureScale ?? 1, min: 0.01, step: 0.1, onChange: (v) => binding.apply((s) => {
437
+ s.rendererEmitterSettings.textureScale = v === 1 ? undefined : v;
438
+ }) }) }), _jsx(Row, { label: "Min vertex distance", children: _jsx(NumberField, { value: system.rendererEmitterSettings.minVertexDistance ?? 0, min: 0, step: 0.05, onChange: (v) => binding.apply((s) => {
439
+ s.rendererEmitterSettings.minVertexDistance =
440
+ v > 0 ? v : undefined;
441
+ }) }) }), _jsx(Row, { label: "Trail time", children: _jsx(NumberField, { value: system.rendererEmitterSettings.lifetime ?? 0, min: 0, step: 0.1, onChange: (v) => binding.apply((s) => {
442
+ s.rendererEmitterSettings.lifetime = v > 0 ? v : undefined;
443
+ }) }) }), _jsx(Row, { label: "Swept trail", children: _jsx(CheckboxField, { value: !!system.rendererEmitterSettings.sideAxis, onChange: (v) => binding.apply((s) => {
444
+ s.rendererEmitterSettings.sideAxis = v
445
+ ? new Vector3(0, 1, 0)
446
+ : undefined;
447
+ }) }) }), system.rendererEmitterSettings.sideAxis && (_jsx(Row, { label: "Side axis", children: _jsx(AxisField, { value: system.rendererEmitterSettings.sideAxis, onChange: (axis) => binding.apply((s) => {
448
+ // The axis's length scales the width: keep it a unit vector.
449
+ s.rendererEmitterSettings.sideAxis =
450
+ axis.length() > 0 ? axis.normalize() : axis;
451
+ }) }) }))] })), system.renderMode === RenderMode.StretchedBillBoard && (_jsxs(_Fragment, { children: [_jsx(Row, { label: "Speed factor", children: _jsx(NumberField, { value: system.rendererEmitterSettings.speedFactor ?? 0, step: 0.1, onChange: (v) => binding.apply((s) => (s.rendererEmitterSettings.speedFactor = v)) }) }), _jsx(Row, { label: "Length factor", children: _jsx(NumberField, { value: system.rendererEmitterSettings.lengthFactor ?? 2, step: 0.1, onChange: (v) => binding.apply((s) => (s.rendererEmitterSettings.lengthFactor = v)) }) })] })), _jsx(SubHeader, { title: "Material", hint: "Shader and blending settings applied to the particle sprite or mesh." }), materialLabel ? (_jsx(Row, { label: "Source", children: _jsx("div", { style: { fontSize: 12, color: theme.textDim, lineHeight: 1.45 }, children: materialLabel }) })) : null, _jsx(Row, { label: "Blend mode", children: _jsx(SelectField, { value: system.blending, options: [
403
452
  { value: 1, label: 'Additive' },
404
453
  { value: 2, label: 'Alpha blend' },
405
454
  { value: 3, label: 'Subtract' },
406
455
  { value: 4, label: 'Multiply' },
407
- ], onChange: (blend) => patchMaterial({ blendMode: blend }) }) }), _jsx(Row, { label: "Transparent", children: _jsx(CheckboxField, { value: !!system.getRendererSettings().materialTransparent, onChange: (v) => patchMaterial({ transparent: v }) }) }), _jsxs(Row, { label: "Texture", children: [_jsx(SelectField, { value: !currentTextureUrl
408
- ? '__none'
409
- : (textureOptions ?? []).some((o) => o.url === currentTextureUrl)
410
- ? currentTextureUrl
411
- : '__current', options: [
412
- { value: '__none', label: '(none)' },
413
- ...(currentTextureUrl && !(textureOptions ?? []).some((o) => o.url === currentTextureUrl)
414
- ? [{ value: '__current', label: `(current) ${currentTextureUrl.slice(-24)}` }]
415
- : []),
416
- ...(textureOptions ?? []).map((o) => ({ value: o.url, label: o.label })),
417
- { value: '__url', label: 'Custom URL…' },
418
- { value: '__file', label: 'Load from file…' },
419
- ], onChange: (url) => {
420
- if (url === '__current') {
421
- return;
422
- }
423
- if (url === '__none') {
424
- patchMaterial({ texture: null });
425
- return;
426
- }
427
- if (url === '__url') {
428
- setPromptingTextureUrl(true);
429
- return;
430
- }
431
- if (url === '__file') {
432
- const input = document.createElement('input');
433
- input.type = 'file';
434
- input.accept = 'image/*';
435
- input.onchange = () => {
436
- const file = input.files?.[0];
437
- if (!file) {
438
- return;
439
- }
440
- const reader = new FileReader();
441
- reader.onload = () => {
442
- const texture = loadTexture(reader.result);
443
- if (texture) {
444
- patchMaterial({ texture });
445
- }
446
- };
447
- reader.readAsDataURL(file);
448
- };
449
- input.click();
450
- return;
451
- }
452
- const texture = loadTexture(url);
453
- if (texture) {
454
- patchMaterial({ texture });
455
- }
456
- } }), promptingTextureUrl && (_jsx(PromptDialog, { title: "Texture URL", defaultValue: currentTextureUrl, placeholder: "https://\u2026", onSubmit: (url) => {
457
- const texture = loadTexture(url);
458
- if (texture) {
459
- patchMaterial({ texture });
460
- }
461
- setPromptingTextureUrl(false);
462
- }, onCancel: () => setPromptingTextureUrl(false) })), currentTextureUrl && (_jsx("img", { src: currentTextureUrl, alt: "texture preview", style: {
463
- width: 64,
464
- height: 64,
465
- marginTop: 6,
466
- objectFit: 'contain',
467
- borderRadius: 6,
468
- border: '1px solid #2b3761',
469
- background: 'repeating-conic-gradient(#2a3252 0% 25%, #171d33 0% 50%) 0 0 / 12px 12px',
470
- } }))] }), _jsx(Row, { label: "Alpha test", children: _jsx(NumberField, { value: system.getRendererSettings().materialAlphaTest ?? 0, min: 0, step: 0.05, onChange: (v) => patchMaterial({ alphaTest: Math.min(1, v) }) }) }), _jsx(Row, { label: "Depth write", children: _jsx(CheckboxField, { value: system.getRendererSettings().materialDepthWrite, onChange: (v) => patchMaterial({ depthWrite: v }) }) }), _jsx(Row, { label: "Depth test", children: _jsx(CheckboxField, { value: system.getRendererSettings().materialDepthTest, onChange: (v) => patchMaterial({ depthTest: v }) }) }), system.renderMode === RenderMode.Mesh &&
456
+ ], onChange: (blend) => patchMaterial({ blendMode: blend }) }) }), _jsx(Row, { label: "Transparent", children: _jsx(CheckboxField, { value: !!system.getRendererSettings().materialTransparent, onChange: (v) => patchMaterial({ transparent: v }) }) }), _jsx(Row, { label: "Texture", children: _jsx(TextureSelect, { value: system.texture, textureOptions: textureOptions, loadTexture: loadTexture, onChange: (texture) => patchMaterial({ texture }), preview: true }) }), _jsx(TextureUVField, { labelPrefix: "Map ", value: system.mapUV ?? normalizeTextureUV(undefined), onChange: (uv) => binding.apply((s) => (s.mapUV = uv)) }), !isMesh && (_jsx(Row, { label: "Flip U/V", children: _jsx("div", { style: { display: 'flex', gap: 6 }, children: ['x', 'y'].map((axis) => (_jsx(NumberField, { value: system.flip?.[axis] ?? 0, min: 0, max: 1, step: 0.1, onChange: (v) => binding.apply((s) => {
457
+ const next = s.flip?.clone() ?? new Vector2(0, 0);
458
+ next[axis] = Math.min(1, Math.max(0, v));
459
+ s.flip = next.x === 0 && next.y === 0 ? undefined : next;
460
+ }) }, axis))) }) })), _jsx(Row, { label: "Alpha test", children: _jsx(NumberField, { value: system.getRendererSettings().materialAlphaTest ?? 0, min: 0, step: 0.05, onChange: (v) => patchMaterial({ alphaTest: Math.min(1, v) }) }) }), _jsx(Row, { label: "Depth write", children: _jsx(CheckboxField, { value: system.getRendererSettings().materialDepthWrite, onChange: (v) => patchMaterial({ depthWrite: v }) }) }), _jsx(Row, { label: "Depth test", children: _jsx(CheckboxField, { value: system.getRendererSettings().materialDepthTest, onChange: (v) => patchMaterial({ depthTest: v }) }) }), _jsx(SubHeader, { title: "Gradient map", hint: "Recolours the map through a colour ramp: one channel of the map, or its luminance, picks a colour along the ramp." }), _jsx(Row, { label: "Ramp", children: _jsx(TextureSelect, { value: gradientMap?.ramp, textureOptions: textureOptions, loadTexture: loadTexture, promptTitle: "Ramp texture URL", onChange: (ramp) => binding.apply((s) => (s.gradientMap = ramp ? { ...s.gradientMap, ramp } : undefined)) }) }), gradientMap && (_jsx(Row, { label: "Ramp channel", children: _jsx(SelectField, { value: gradientMap.channel, options: [
461
+ { value: 'r', label: 'Red' },
462
+ { value: 'g', label: 'Green' },
463
+ { value: 'b', label: 'Blue' },
464
+ { value: 'a', label: 'Alpha (ramp alpha replaces)' },
465
+ { value: 'luminance', label: 'Luminance' },
466
+ ], onChange: (channel) => binding.apply((s) => (s.gradientMap = { ...s.gradientMap, channel })) }) })), _jsx(SubHeader, { title: "Erosion", hint: "Alpha decides how much of the shape is left instead of how transparent it is, so a fading alpha dissolves or wipes the shape away." }), _jsx(Row, { label: "Erosion", children: _jsx(CheckboxField, { value: !!erosion, onChange: (v) => binding.apply((s) => (s.erosion = v ? {} : undefined)) }) }), erosion && (_jsxs(_Fragment, { children: [_jsx(Row, { label: "Erosion mask", children: _jsx(TextureSelect, { value: erosion.mask, textureOptions: textureOptions, loadTexture: loadTexture, noneLabel: "(map alpha)", promptTitle: "Erosion mask URL", onChange: (mask) => patchErosion({ mask }) }) }), erosion.mask && (_jsx(TextureUVField, { labelPrefix: "Mask ", value: erosion.maskUV, onChange: (maskUV) => patchErosion({ maskUV }) })), _jsx(Row, { label: "Softness", children: _jsx(NumberField, { value: erosion.softness, min: 0.0001, step: 0.01, onChange: (softness) => patchErosion({ softness }) }) }), _jsx(Row, { label: "Edge width", children: _jsx(NumberField, { value: erosion.edgeWidth, min: 0, step: 0.01, onChange: (edgeWidth) => patchErosion({ edgeWidth }) }) }), (erosion.edgeWidth > 0 || erosion.edgeColor.some((c) => c !== 1)) && (_jsx(Row, { label: "Edge color", children: _jsx("div", { style: { display: 'flex', gap: 6 }, children: [0, 1, 2].map((channel) => (_jsx(NumberField, { value: erosion.edgeColor[channel], min: 0, step: 0.05, onChange: (v) => {
467
+ const edgeColor = [...erosion.edgeColor];
468
+ edgeColor[channel] = v;
469
+ patchErosion({ edgeColor });
470
+ } }, channel))) }) }))] })), _jsx(SubHeader, { title: "UV distortion", hint: "A scrolling noise texture pushes the map's UV about, for heat shimmer and licking, breathing shapes." }), _jsx(Row, { label: "Noise texture", children: _jsx(TextureSelect, { value: distortion?.map, textureOptions: textureOptions, loadTexture: loadTexture, promptTitle: "Distortion noise URL", onChange: (map) => binding.apply((s) => (s.uvDistortion = map ? { ...s.uvDistortion, map } : undefined)) }) }), distortion && (_jsxs(_Fragment, { children: [_jsx(Row, { label: "Distortion strength", children: _jsx(NumberField, { value: distortion.strength, min: 0, step: 0.01, onChange: (strength) => patchDistortion({ strength }) }) }), _jsx(TextureUVField, { labelPrefix: "Noise ", value: distortion.mapUV, onChange: (mapUV) => patchDistortion({ mapUV }) })] })), system.renderMode === RenderMode.Mesh &&
471
471
  (() => {
472
472
  const preset = detectGeometryPreset(system.instancingGeometry);
473
473
  const vertexCount = system.instancingGeometry.length / 3;
474
474
  const triangleCount = (system.getRendererSettings().instancingIndices?.length ?? 0) / 3;
475
475
  const hostGeometry = geometryOptions ?? [];
476
476
  const applyGeometry = (g) => binding.apply((s) => {
477
+ // The previous mesh's sets go first, so the positions setter gives
478
+ // this mesh the library's own fallback UVs and normals; then every set
479
+ // this mesh has. An unindexed mesh draws its vertices in sequence.
477
480
  const settings = s.getRendererSettings();
478
- settings.instancingIndices = (g.indices ? new Uint32Array(g.indices) : undefined);
479
- settings.instancingUVs = (g.uvs ? new Float32Array(g.uvs) : undefined);
480
- settings.instancingNormals = (g.normals ? new Float32Array(g.normals) : undefined);
481
+ settings.instancingUVs = undefined;
482
+ settings.instancingUVs2 = undefined;
483
+ settings.instancingNormals = undefined;
481
484
  s.instancingGeometry = new Float32Array(g.positions);
485
+ s.instancingIndices = g.indices
486
+ ? new Uint32Array(g.indices)
487
+ : Uint32Array.from({ length: g.positions.length / 3 }, (_, i) => i);
488
+ if (g.uvs)
489
+ s.instancingUVs = new Float32Array(g.uvs);
490
+ if (g.uvs2)
491
+ s.instancingUVs2 = new Float32Array(g.uvs2);
492
+ if (g.normals)
493
+ settings.instancingNormals = new Float32Array(g.normals);
482
494
  });
483
495
  return (_jsx(Row, { label: "Geometry", children: _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 6 }, children: [_jsx(SelectField, { value: preset, options: [
484
496
  ...(preset === 'custom' ? [{ value: 'custom', label: 'Custom (imported)' }] : []),
@@ -509,6 +521,7 @@ export function RendererModule({ binding, textureOptions, geometryOptions, resol
509
521
  }
510
522
  binding.apply((s) => {
511
523
  const settings = s.getRendererSettings();
524
+ settings.instancingUVs2 = undefined;
512
525
  if (next === 'cube') {
513
526
  settings.instancingIndices = MESH_CUBE_INDICES;
514
527
  settings.instancingUVs = undefined;
@@ -521,7 +534,7 @@ export function RendererModule({ binding, textureOptions, geometryOptions, resol
521
534
  s.instancingGeometry = MESH_QUAD_POSITIONS;
522
535
  }
523
536
  });
524
- } }), _jsx(MeshPreview, { positions: system.instancingGeometry, indices: system.getRendererSettings().instancingIndices }), preset === 'custom' && (_jsxs("span", { style: { fontSize: 10.5, color: '#9eb9ff' }, children: [vertexCount, " verts / ", triangleCount, " tris \u2014 picking a preset replaces this mesh"] }))] }) }));
537
+ } }), _jsx(MeshPreview, { positions: system.instancingGeometry, indices: system.getRendererSettings().instancingIndices }), preset === 'custom' && (_jsxs("span", { style: { fontSize: 10.5, color: '#9eb9ff' }, children: [vertexCount, " verts / ", triangleCount, " tris", system.getRendererSettings().instancingUVs2 ? ' / 2 UV sets' : '', " \u2014 picking a preset replaces this mesh"] }))] }) }));
525
538
  })(), system.renderMode === RenderMode.Mesh && (_jsx(Row, { label: "Lighting", children: _jsx(SelectField, { value: system.lighting, options: [
526
539
  { value: 'fixed', label: 'Fixed light' },
527
540
  { value: 'scene', label: 'Scene light' },
@@ -548,7 +561,11 @@ export function RendererModule({ binding, textureOptions, geometryOptions, resol
548
561
  }) }, axis))) }) })), system.renderMode === RenderMode.Mesh && (_jsxs(_Fragment, { children: [_jsx(Row, { label: "Env map atlas", children: _jsx(SelectField, { value: system.getRendererSettings().reflectionAtlas?.url ?? '', options: [
549
562
  { value: '', label: 'None' },
550
563
  ...(textureOptions ?? []).map((option) => ({ value: option.url, label: option.label })),
551
- ], onChange: (url) => patchMaterial({ reflectionAtlas: url ? loadTexture(url) : null }) }) }), _jsx(Row, { label: "Reflection level", children: _jsx(NumberField, { value: system.getRendererSettings().reflectionLevel ?? 1, min: 0, step: 0.1, onChange: (v) => patchMaterial({ reflectionLevel: Math.max(0, v) }) }) })] })), _jsx(Row, { label: "Sort mode", children: _jsx(SelectField, { value: system.sortMode, options: [
564
+ ], onChange: (url) => patchMaterial({ reflectionAtlas: url ? loadTexture(url) : null }) }) }), _jsx(Row, { label: "Reflection level", children: _jsx(NumberField, { value: system.getRendererSettings().reflectionLevel ?? 1, min: 0, step: 0.1, onChange: (v) => patchMaterial({ reflectionLevel: Math.max(0, v) }) }) })] })), isMesh && (_jsx(Row, { label: "Render face", children: _jsx(SelectField, { value: system.renderFace, options: [
565
+ { value: 'both', label: 'Both' },
566
+ { value: 'front', label: 'Front' },
567
+ { value: 'back', label: 'Back' },
568
+ ], onChange: (face) => binding.apply((s) => (s.renderFace = face)) }) })), _jsx(Row, { label: "Sort mode", children: _jsx(SelectField, { value: system.sortMode, options: [
552
569
  { value: 'none', label: 'None' },
553
570
  { value: 'distance', label: 'By distance' },
554
571
  { value: 'oldestInFront', label: 'Oldest in front' },
@@ -1,19 +1,34 @@
1
1
  import type { CSSProperties } from 'react';
2
+ /** The editor's colours and font, for hosts that style their own panels to match. */
2
3
  export declare const theme: {
4
+ /** Background of the side panels and docks. */
3
5
  panelBg: string;
6
+ /** Background of an inspector module or a framed list. */
4
7
  sectionBg: string;
8
+ /** Borders and separators. */
5
9
  border: string;
10
+ /** The border of a selected or switched-on control. */
6
11
  borderActive: string;
12
+ /** Body text. */
7
13
  text: string;
14
+ /** Labels and secondary text. */
8
15
  textDim: string;
16
+ /** Highlights, such as a Save button with something to save. */
9
17
  accent: string;
18
+ /** Background of inputs. */
10
19
  inputBg: string;
20
+ /** The line of curve editors. */
11
21
  curveStroke: string;
22
+ /** The editor's font stack. */
12
23
  font: string;
13
24
  };
25
+ /** Style of a text or number input. */
14
26
  export declare const inputStyle: CSSProperties;
27
+ /** Style of an inspector row: a label column, then the controls. */
15
28
  export declare const rowStyle: CSSProperties;
29
+ /** Style of an inspector row's label. */
16
30
  export declare const labelStyle: CSSProperties;
31
+ /** Style of a button. */
17
32
  export declare const buttonStyle: CSSProperties;
18
33
  /** Visually hides a file input while keeping it activatable via an associated <label>. */
19
34
  export declare const hiddenFileInputStyle: CSSProperties;
@@ -1,15 +1,27 @@
1
+ /** The editor's colours and font, for hosts that style their own panels to match. */
1
2
  export const theme = {
3
+ /** Background of the side panels and docks. */
2
4
  panelBg: 'linear-gradient(180deg, rgba(12, 18, 38, 0.92), rgba(6, 10, 22, 0.94))',
5
+ /** Background of an inspector module or a framed list. */
3
6
  sectionBg: 'rgba(10, 16, 34, 0.6)',
7
+ /** Borders and separators. */
4
8
  border: '#2a365f',
9
+ /** The border of a selected or switched-on control. */
5
10
  borderActive: '#4f6fbe',
11
+ /** Body text. */
6
12
  text: '#eef3ff',
13
+ /** Labels and secondary text. */
7
14
  textDim: '#b7c6ea',
15
+ /** Highlights, such as a Save button with something to save. */
8
16
  accent: '#9eb9ff',
17
+ /** Background of inputs. */
9
18
  inputBg: 'rgba(7, 11, 22, 0.82)',
19
+ /** The line of curve editors. */
10
20
  curveStroke: '#78a5ff',
21
+ /** The editor's font stack. */
11
22
  font: 'Inter, Segoe UI, Roboto, sans-serif',
12
23
  };
24
+ /** Style of a text or number input. */
13
25
  export const inputStyle = {
14
26
  background: theme.inputBg,
15
27
  border: `1px solid ${theme.border}`,
@@ -22,6 +34,7 @@ export const inputStyle = {
22
34
  boxSizing: 'border-box',
23
35
  fontFamily: theme.font,
24
36
  };
37
+ /** Style of an inspector row: a label column, then the controls. */
25
38
  export const rowStyle = {
26
39
  display: 'grid',
27
40
  gridTemplateColumns: '110px 1fr',
@@ -29,6 +42,7 @@ export const rowStyle = {
29
42
  gap: 8,
30
43
  marginTop: 6,
31
44
  };
45
+ /** Style of an inspector row's label. */
32
46
  export const labelStyle = {
33
47
  color: theme.textDim,
34
48
  fontSize: 12,
@@ -36,6 +50,7 @@ export const labelStyle = {
36
50
  overflow: 'hidden',
37
51
  textOverflow: 'ellipsis',
38
52
  };
53
+ /** Style of a button. */
39
54
  export const buttonStyle = {
40
55
  border: `1px solid #4563ad`,
41
56
  background: 'rgba(28, 48, 98, 0.55)',
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The editor's headless session: scene, camera, gizmos, history, scene entries, assets, prefab
3
+ * mode and project save/load, without React. It builds the whole viewport (grid, gizmos, markers),
4
+ * so it has its own entry point and the UI-free `babylon.quarks-editor` entry stays light.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+ export { createEditorSession } from './core/editorSession.js';
9
+ export type { EditorFrameStats, EditorProject, EditorSession, EditorSessionOptions, EditorSessionView, EffectEditorHostHandle, PrefabInfo, } from './core/editorSession.js';
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The editor's headless session: scene, camera, gizmos, history, scene entries, assets, prefab
3
+ * mode and project save/load, without React. It builds the whole viewport (grid, gizmos, markers),
4
+ * so it has its own entry point and the UI-free `babylon.quarks-editor` entry stays light.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+ export { createEditorSession } from './core/editorSession.js';
package/package.json CHANGED
@@ -1,19 +1,24 @@
1
1
  {
2
2
  "name": "babylon.quarks-editor",
3
- "version": "0.26.0",
3
+ "version": "0.28.0",
4
4
  "description": "Shuriken-style effect editor for babylon.quarks: headless edit-model core + React module-stack UI",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
8
8
  "exports": {
9
9
  ".": {
10
- "import": "./dist/index.js",
11
- "types": "./dist/index.d.ts"
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./session": {
14
+ "types": "./dist/session.d.ts",
15
+ "import": "./dist/session.js"
12
16
  },
13
17
  "./react": {
14
- "import": "./dist/react/index.js",
15
- "types": "./dist/react/index.d.ts"
16
- }
18
+ "types": "./dist/react/index.d.ts",
19
+ "import": "./dist/react/index.js"
20
+ },
21
+ "./package.json": "./package.json"
17
22
  },
18
23
  "files": [
19
24
  "dist",
@@ -25,7 +30,8 @@
25
30
  "scripts": {
26
31
  "clean": "rimraf dist",
27
32
  "build": "npm run clean && tsc -p tsconfig.json",
28
- "test": "jest"
33
+ "test": "jest",
34
+ "docs:check": "typedoc --out node_modules/.cache/typedoc-check"
29
35
  },
30
36
  "repository": {
31
37
  "type": "git",
@@ -51,11 +57,11 @@
51
57
  },
52
58
  "homepage": "https://github.com/Soullnik/babylon.quarks-standalone#readme",
53
59
  "peerDependencies": {
54
- "@babylonjs/core": ">=9.0.0",
55
- "@babylonjs/materials": ">=9.0.0",
60
+ "@babylonjs/core": "^9.0.0",
61
+ "@babylonjs/materials": "^9.0.0",
56
62
  "@heroicons/react": ">=2.0.0",
57
63
  "@tanstack/react-virtual": ">=3.0.0",
58
- "babylon.quarks": ">=0.17.0",
64
+ "babylon.quarks": "~0.28.0",
59
65
  "react": ">=18",
60
66
  "react-dom": ">=18"
61
67
  },
@@ -65,9 +71,10 @@
65
71
  "@tanstack/react-virtual": "^3.14.6",
66
72
  "@types/react": "^19.0.0",
67
73
  "@types/react-dom": "^19.0.0",
74
+ "fake-indexeddb": "^6.2.6",
68
75
  "react": "^19.0.0",
69
76
  "react-dom": "^19.0.0",
70
77
  "rimraf": "^6.0.0",
71
- "typescript": "^5.9.3"
78
+ "typescript": "^6.0.3"
72
79
  }
73
80
  }
@@ -1,4 +1,4 @@
1
- import type {AssetDragPayload} from './assetTree';
1
+ import type {AssetDragPayload} from './assetTree.js';
2
2
 
3
3
  let activeDrag: AssetDragPayload | null = null;
4
4
 
@@ -1,4 +1,4 @@
1
- import type {EffectAsset} from './assets';
1
+ import type {EffectAsset} from './assets.js';
2
2
 
3
3
  export interface AssetTreeFolder {
4
4
  kind: 'folder';
@@ -85,7 +85,20 @@ export function searchAssets(assets: EffectAsset[], query: string): EffectAsset[
85
85
 
86
86
  export const ASSET_DRAG_MIME = 'application/x-quarks-asset-drop';
87
87
 
88
- export type AssetDragPayload = {kind: 'asset'; id: number} | {kind: 'folder'; path: string};
88
+ /** What is dragged out of the Assets panel: one asset, or every asset under a folder. */
89
+ export type AssetDragPayload =
90
+ | {
91
+ /** One asset. */
92
+ kind: 'asset';
93
+ /** The asset's id. */
94
+ id: number;
95
+ }
96
+ | {
97
+ /** A folder. */
98
+ kind: 'folder';
99
+ /** The folder's path; every asset under it is dragged. */
100
+ path: string;
101
+ };
89
102
 
90
103
  export function writeAssetDrag(dataTransfer: DataTransfer, payload: AssetDragPayload): void {
91
104
  dataTransfer.setData(ASSET_DRAG_MIME, JSON.stringify(payload));