babylon.quarks-editor 0.18.1 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +6 -6
  2. package/dist/core/binding.d.ts +1 -1
  3. package/dist/core/colors.d.ts +1 -1
  4. package/dist/core/defaultEffect.d.ts +1 -1
  5. package/dist/core/effectTree.d.ts +1 -1
  6. package/dist/core/effectTree.js +18 -4
  7. package/dist/core/emitterShapeWireframe.d.ts +1 -1
  8. package/dist/core/emitterShapeWireframe.js +1 -1
  9. package/dist/core/galleryDropMarker.js +1 -1
  10. package/dist/core/galleryScene.d.ts +1 -1
  11. package/dist/core/galleryTree.js +4 -1
  12. package/dist/core/loadEffect.d.ts +2 -2
  13. package/dist/core/loadEffect.js +1 -1
  14. package/dist/core/loadEffectGallery.d.ts +2 -2
  15. package/dist/core/shapes.d.ts +1 -1
  16. package/dist/core/shapes.js +35 -8
  17. package/dist/core/systems.d.ts +1 -1
  18. package/dist/core/systems.js +3 -1
  19. package/dist/core/timeline.d.ts +1 -2
  20. package/dist/core/values.d.ts +1 -1
  21. package/dist/index.d.ts +11 -11
  22. package/dist/index.js +6 -6
  23. package/dist/react/CurveEditor.d.ts +1 -2
  24. package/dist/react/CurveEditor.js +14 -3
  25. package/dist/react/EffectEditor.d.ts +2 -2
  26. package/dist/react/EffectEditor.js +2 -2
  27. package/dist/react/EffectEditorHost.d.ts +1 -1
  28. package/dist/react/EffectEditorHost.js +123 -33
  29. package/dist/react/GalleryPanel.d.ts +1 -1
  30. package/dist/react/GalleryPanel.js +14 -7
  31. package/dist/react/GradientEditor.d.ts +1 -2
  32. package/dist/react/GradientEditor.js +8 -1
  33. package/dist/react/MessageDialog.js +7 -1
  34. package/dist/react/ModuleSection.js +10 -3
  35. package/dist/react/ObjectModule.d.ts +1 -2
  36. package/dist/react/ObjectModule.js +1 -1
  37. package/dist/react/SubEmittersModule.d.ts +1 -2
  38. package/dist/react/TimelinePanel.d.ts +3 -3
  39. package/dist/react/TimelinePanel.js +55 -9
  40. package/dist/react/TimelineTrackRow.d.ts +1 -1
  41. package/dist/react/TimelineTrackRow.js +20 -6
  42. package/dist/react/ValueField.d.ts +1 -2
  43. package/dist/react/behaviorModules.js +19 -5
  44. package/dist/react/index.d.ts +17 -17
  45. package/dist/react/index.js +11 -11
  46. package/dist/react/modules.js +55 -25
  47. package/package.json +68 -68
  48. package/src/core/binding.ts +2 -2
  49. package/src/core/colors.ts +1 -1
  50. package/src/core/defaultEffect.ts +1 -1
  51. package/src/core/effectTree.ts +22 -7
  52. package/src/core/emitterShapeWireframe.ts +8 -4
  53. package/src/core/galleryDropMarker.ts +6 -2
  54. package/src/core/galleryPlayback.ts +1 -1
  55. package/src/core/galleryScene.ts +3 -7
  56. package/src/core/gallerySelectionMarker.ts +5 -1
  57. package/src/core/galleryTree.ts +8 -5
  58. package/src/core/loadEffect.ts +3 -3
  59. package/src/core/loadEffectGallery.ts +7 -3
  60. package/src/core/pickGalleryFiles.ts +4 -1
  61. package/src/core/shapes.ts +48 -10
  62. package/src/core/systems.ts +9 -3
  63. package/src/core/timeline.ts +8 -8
  64. package/src/core/values.ts +1 -1
  65. package/src/core/viewportPicker.ts +1 -1
  66. package/src/index.ts +18 -18
  67. package/src/react/CurveEditor.tsx +40 -10
  68. package/src/react/EffectEditor.tsx +14 -13
  69. package/src/react/EffectEditorHost.tsx +239 -64
  70. package/src/react/GalleryPanel.tsx +56 -12
  71. package/src/react/GradientEditor.tsx +21 -4
  72. package/src/react/InspectorInfoIcon.tsx +6 -2
  73. package/src/react/MessageDialog.tsx +9 -1
  74. package/src/react/ModuleSection.tsx +12 -3
  75. package/src/react/ObjectModule.tsx +36 -15
  76. package/src/react/PromptDialog.tsx +5 -1
  77. package/src/react/SubEmittersModule.tsx +1 -2
  78. package/src/react/TimelinePanel.tsx +94 -21
  79. package/src/react/TimelineTrackRow.tsx +71 -18
  80. package/src/react/ValueField.tsx +0 -1
  81. package/src/react/behaviorModules.tsx +143 -29
  82. package/src/react/index.ts +33 -33
  83. package/src/react/modules.tsx +173 -57
@@ -1,9 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useState } from 'react';
3
2
  import { CheckIcon, ChevronDownIcon, ChevronRightIcon, EyeIcon, EyeSlashIcon, PencilSquareIcon, PlusIcon, SparklesIcon, SquaresPlusIcon, XMarkIcon, } from '@heroicons/react/24/solid';
3
+ import { useState } from 'react';
4
4
  import { buildEffectTree, collectSystems } from '../core/effectTree';
5
- import { createChildSystem, removeSystems } from '../core/systems';
6
5
  import { ungroupNode } from '../core/groups';
6
+ import { createChildSystem, removeSystems } from '../core/systems';
7
7
  import { PromptDialog } from './PromptDialog';
8
8
  import { iconStyle } from './icons';
9
9
  import { theme } from './theme';
@@ -20,7 +20,7 @@ const ROW_HEIGHT = 28;
20
20
  const checkboxButton = { ...iconButton, width: 12, height: 12 };
21
21
  /** One row of the timeline: a label cell (name + actions) and, for tracks, a bar cell. */
22
22
  export function TimelineTrackRow(props) {
23
- const { row, binding, selected, onSelectNode, pxPerSecond, offsetPx, collapsed, onToggleCollapse, hidden, onToggleVisible, groupSelected, onToggleGroupSelect } = props;
23
+ const { row, binding, selected, onSelectNode, pxPerSecond, offsetPx, collapsed, onToggleCollapse, hidden, onToggleVisible, groupSelected, onToggleGroupSelect, } = props;
24
24
  const [renaming, setRenaming] = useState(false);
25
25
  const isRoot = row.node === binding.root;
26
26
  const labelStyle = {
@@ -44,10 +44,17 @@ export function TimelineTrackRow(props) {
44
44
  return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "qe-hover-bg", style: { ...labelStyle, borderTop: `1px solid ${theme.border}` }, onClick: () => onSelectNode(row.node), children: [!isRoot && (_jsx("button", { className: "qe-hover", title: groupSelected ? 'Remove from group selection' : 'Select for grouping', style: { ...checkboxButton, color: groupSelected ? theme.accent : theme.textDim }, onClick: (e) => {
45
45
  e.stopPropagation();
46
46
  onToggleGroupSelect();
47
- }, children: groupSelected ? (_jsx(CheckIcon, { style: iconStyle(12) })) : (_jsx("span", { style: { width: 10, height: 10, border: `1px solid currentColor`, borderRadius: 2, display: 'block', boxSizing: 'border-box' } })) })), _jsx("span", { className: "qe-hover", title: collapsed ? 'Expand' : 'Collapse', style: { color: theme.textDim, width: 12, cursor: 'pointer', display: 'inline-flex' }, onClick: (e) => {
47
+ }, children: groupSelected ? (_jsx(CheckIcon, { style: iconStyle(12) })) : (_jsx("span", { style: {
48
+ width: 10,
49
+ height: 10,
50
+ border: `1px solid currentColor`,
51
+ borderRadius: 2,
52
+ display: 'block',
53
+ boxSizing: 'border-box',
54
+ } })) })), _jsx("span", { className: "qe-hover", title: collapsed ? 'Expand' : 'Collapse', style: { color: theme.textDim, width: 12, cursor: 'pointer', display: 'inline-flex' }, onClick: (e) => {
48
55
  e.stopPropagation();
49
56
  onToggleCollapse?.();
50
- }, children: collapsed ? _jsx(ChevronRightIcon, { style: iconStyle(12) }) : _jsx(ChevronDownIcon, { style: iconStyle(12) }) }), _jsx("span", { style: { flex: 1, overflow: 'hidden', textOverflow: 'ellipsis' }, children: row.name }), _jsx("button", { className: "qe-hover", title: "Add system to this group", style: { ...iconButton, color: theme.accent }, onClick: (e) => {
57
+ }, children: collapsed ? (_jsx(ChevronRightIcon, { style: iconStyle(12) })) : (_jsx(ChevronDownIcon, { style: iconStyle(12) })) }), _jsx("span", { style: { flex: 1, overflow: 'hidden', textOverflow: 'ellipsis' }, children: row.name }), _jsx("button", { className: "qe-hover", title: "Add system to this group", style: { ...iconButton, color: theme.accent }, onClick: (e) => {
51
58
  e.stopPropagation();
52
59
  let created;
53
60
  binding.apply(() => {
@@ -82,7 +89,14 @@ export function TimelineTrackRow(props) {
82
89
  return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "qe-hover-bg", style: { ...labelStyle, opacity: hidden ? 0.5 : 1 }, onClick: () => onSelectNode(row.node), children: [!isRoot && (_jsx("button", { className: "qe-hover", title: groupSelected ? 'Remove from group selection' : 'Select for grouping', style: { ...checkboxButton, color: groupSelected ? theme.accent : theme.textDim }, onClick: (e) => {
83
90
  e.stopPropagation();
84
91
  onToggleGroupSelect();
85
- }, children: groupSelected ? (_jsx(CheckIcon, { style: iconStyle(12) })) : (_jsx("span", { style: { width: 10, height: 10, border: `1px solid currentColor`, borderRadius: 2, display: 'block', boxSizing: 'border-box' } })) })), _jsx("button", { className: "qe-hover", title: hidden ? 'Show in viewport' : 'Hide in viewport', style: { ...iconButton, color: hidden ? theme.textDim : theme.accent }, onClick: (e) => {
92
+ }, children: groupSelected ? (_jsx(CheckIcon, { style: iconStyle(12) })) : (_jsx("span", { style: {
93
+ width: 10,
94
+ height: 10,
95
+ border: `1px solid currentColor`,
96
+ borderRadius: 2,
97
+ display: 'block',
98
+ boxSizing: 'border-box',
99
+ } })) })), _jsx("button", { className: "qe-hover", title: hidden ? 'Show in viewport' : 'Hide in viewport', style: { ...iconButton, color: hidden ? theme.textDim : theme.accent }, onClick: (e) => {
86
100
  e.stopPropagation();
87
101
  onToggleVisible?.();
88
102
  }, children: hidden ? _jsx(EyeSlashIcon, { style: iconStyle(12) }) : _jsx(EyeIcon, { style: iconStyle(12) }) }), _jsx("span", { style: { color: theme.accent, display: 'inline-flex' }, children: _jsx(SparklesIcon, { style: iconStyle(11) }) }), _jsxs("span", { style: { flex: 1, overflow: 'hidden', textOverflow: 'ellipsis' }, children: [row.name, system.onlyUsedByOther ? ' (sub)' : ''] }), !isRoot && (_jsx("button", { className: "qe-hover", title: "Rename", style: { ...iconButton, color: theme.textDim }, onClick: (e) => {
@@ -1,4 +1,3 @@
1
- import React from 'react';
2
1
  import { ScalarGenerator } from '../core/values';
3
2
  /**
4
3
  * Unity-like value field: Constant | Random Between Two Constants | Curve,
@@ -12,4 +11,4 @@ export declare function ValueField(props: {
12
11
  onChange: (generator: ScalarGenerator) => void;
13
12
  min?: number;
14
13
  curveMax?: number;
15
- }): React.JSX.Element;
14
+ }): import("react").JSX.Element;
@@ -3,10 +3,10 @@ import { ApplyCollision, ApplyForce, ChangeEmitDirection, ColorBySpeed, Constant
3
3
  import { ensureGroundResolver } from '../core/collision';
4
4
  import { DEFAULT_GRADIENT_STOPS, buildGradient, findBehavior, readGradientStops } from '../core/colors';
5
5
  import { buildCurve, buildScalar, readPieces, readScalar } from '../core/values';
6
- import { GradientEditor } from './GradientEditor';
7
6
  import { CurveEditor } from './CurveEditor';
8
- import { ModuleSection } from './ModuleSection';
9
7
  import { NumberField, Row, SelectField } from './fields';
8
+ import { GradientEditor } from './GradientEditor';
9
+ import { ModuleSection } from './ModuleSection';
10
10
  import { ValueField } from './ValueField';
11
11
  function removeBehaviorOfType(behaviors, type) {
12
12
  for (let i = behaviors.length - 1; i >= 0; i--) {
@@ -81,7 +81,9 @@ function SpeedRangeRows({ binding, behavior }) {
81
81
  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))) })] }) }));
82
82
  }
83
83
  export function ColorBySpeedModule({ binding }) {
84
- 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 ? readGradientStops(behavior.color) : DEFAULT_GRADIENT_STOPS, onChange: (stops) => binding.apply(() => (behavior.color = buildGradient(stops))) }) }), _jsx(SpeedRangeRows, { binding: binding, behavior: behavior })] })) }));
84
+ 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
85
+ ? readGradientStops(behavior.color)
86
+ : DEFAULT_GRADIENT_STOPS, onChange: (stops) => binding.apply(() => (behavior.color = buildGradient(stops))) }) }), _jsx(SpeedRangeRows, { binding: binding, behavior: behavior })] })) }));
85
87
  }
86
88
  export function SizeBySpeedModule({ binding }) {
87
89
  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 })] })) }));
@@ -106,7 +108,13 @@ export function TextureSheetModule({ binding }) {
106
108
  const startIdx = Math.min(Math.max(0, Math.round(readScalar(system.startTileIndex).value)), tiles - 1);
107
109
  const startCol = startIdx % u;
108
110
  const startRow = Math.floor(startIdx / u);
109
- return (_jsxs(ModuleSection, { title: "Texture Sheet Animation", defaultOpen: false, children: [textureUrl && (_jsxs("div", { style: { marginTop: 6 }, children: [_jsxs("div", { style: { position: 'relative', width: 218, border: '1px solid #2b3761', borderRadius: 6, overflow: 'hidden' }, children: [_jsx("img", { src: textureUrl, alt: "texture atlas", style: { width: '100%', display: 'block' } }), _jsx("div", { style: {
111
+ return (_jsxs(ModuleSection, { title: "Texture Sheet Animation", defaultOpen: false, children: [textureUrl && (_jsxs("div", { style: { marginTop: 6 }, children: [_jsxs("div", { style: {
112
+ position: 'relative',
113
+ width: 218,
114
+ border: '1px solid #2b3761',
115
+ borderRadius: 6,
116
+ overflow: 'hidden',
117
+ }, children: [_jsx("img", { src: textureUrl, alt: "texture atlas", style: { width: '100%', display: 'block' } }), _jsx("div", { style: {
110
118
  position: 'absolute',
111
119
  inset: 0,
112
120
  pointerEvents: 'none',
@@ -133,7 +141,13 @@ export function TextureSheetModule({ binding }) {
133
141
  }, children: startIdx }) })] }), _jsxs("div", { style: { fontSize: 11, color: '#b7c6ea', marginTop: 3 }, children: [u, " \u00D7 ", v, " = ", tiles, " tiles; frame indices 0\u2013", tiles - 1, "; start tile", ' ', _jsx("span", { style: { color: '#ffce6b', fontWeight: 600 }, children: startIdx })] })] })), _jsx(Row, { label: "Tiles U", children: _jsx(NumberField, { value: system.uTileCount, min: 1, step: 1, onChange: (v) => binding.apply((s) => (s.uTileCount = Math.round(v))) }) }), _jsx(Row, { label: "Tiles V", children: _jsx(NumberField, { value: system.vTileCount, min: 1, step: 1, onChange: (v) => binding.apply((s) => (s.vTileCount = Math.round(v))) }) }), _jsx(Row, { label: "Blend tiles", children: _jsx("input", { type: "checkbox", className: "qe-hover", checked: system.blendTiles, style: { justifySelf: 'start', width: 15, height: 15, accentColor: '#78a5ff', cursor: 'pointer' }, onChange: (e) => binding.apply((s) => (s.blendTiles = e.target.checked)) }) }), _jsx(Row, { label: "Start tile", children: _jsx(NumberField, { value: readScalar(system.startTileIndex).value, min: 0, step: 1, onChange: (v) => binding.apply((s) => (s.startTileIndex = new ConstantValue(Math.round(v)))) }) }), _jsx(Row, { label: "Animate frames", children: _jsx("input", { type: "checkbox", className: "qe-hover", checked: !!frameBehavior, style: { justifySelf: 'start', width: 15, height: 15, accentColor: '#78a5ff', cursor: 'pointer' }, onChange: (e) => binding.apply((s) => {
134
142
  removeBehaviorOfType(s.behaviors, 'FrameOverLife');
135
143
  if (e.target.checked) {
136
- s.addBehavior(new FrameOverLife(buildScalar({ mode: 'curve', value: 0, min: 0, max: tiles - 1, curve: [0, (tiles - 1) / 3, (2 * (tiles - 1)) / 3, tiles - 1] })));
144
+ s.addBehavior(new FrameOverLife(buildScalar({
145
+ mode: 'curve',
146
+ value: 0,
147
+ min: 0,
148
+ max: tiles - 1,
149
+ curve: [0, (tiles - 1) / 3, (2 * (tiles - 1)) / 3, tiles - 1],
150
+ })));
137
151
  }
138
152
  }) }) }), 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))) }) }))] }));
139
153
  }
@@ -1,24 +1,24 @@
1
+ export { CollisionModule, ColorBySpeedModule, EmitDirectionModule, ForceOverLifeModule, GravityModule, LimitSpeedOverLifeModule, NoiseModule, OrbitOverLifeModule, RotationBySpeedModule, RotationOverLifeModule, SizeBySpeedModule, SpeedOverLifeModule, TextureSheetModule, TurbulenceModule, WidthOverTrailModule, } from './behaviorModules';
2
+ export { CurveEditor } from './CurveEditor';
1
3
  export { EffectEditor } from './EffectEditor';
2
4
  export type { EffectEditorProps } from './EffectEditor';
3
- export { SpeedOverLifeModule, LimitSpeedOverLifeModule, ForceOverLifeModule, GravityModule, RotationOverLifeModule, NoiseModule, TurbulenceModule, CollisionModule, EmitDirectionModule, TextureSheetModule, ColorBySpeedModule, SizeBySpeedModule, RotationBySpeedModule, OrbitOverLifeModule, WidthOverTrailModule, } from './behaviorModules';
4
- export type { TextureOption, GeometryOption, GeometryData } from './modules';
5
+ export { EffectEditorHost, QuarksEffectEditor } from './EffectEditorHost';
6
+ export type { EffectEditorHostHandle, EffectEditorHostProps } from './EffectEditorHost';
7
+ export { CheckboxField, NumberField, Row, SelectField } from './fields';
8
+ export { GalleryPanel } from './GalleryPanel';
9
+ export type { GalleryPanelProps } from './GalleryPanel';
10
+ export { GradientEditor } from './GradientEditor';
11
+ export { MessageDialog } from './MessageDialog';
12
+ export type { MessageDialogProps } from './MessageDialog';
13
+ export { ColorOverLifeModule, EmissionModule, MainModule, RendererModule, ShapeModule, SizeOverLifeModule, } from './modules';
14
+ export type { GeometryData, GeometryOption, TextureOption } from './modules';
15
+ export { ModuleSection } from './ModuleSection';
16
+ export { PromptDialog } from './PromptDialog';
17
+ export type { PromptDialogProps } from './PromptDialog';
5
18
  export { SubEmittersModule } from './SubEmittersModule';
19
+ export { buttonStyle, inputStyle, labelStyle, rowStyle, theme } from './theme';
6
20
  export { TimelinePanel } from './TimelinePanel';
7
- export type { TimelinePanelProps, PlaybackState } from './TimelinePanel';
21
+ export type { PlaybackState, TimelinePanelProps } from './TimelinePanel';
8
22
  export { TimelineTrackRow } from './TimelineTrackRow';
9
23
  export type { TimelineTrackRowProps } from './TimelineTrackRow';
10
- export { ModuleSection } from './ModuleSection';
11
- export { PromptDialog } from './PromptDialog';
12
- export type { PromptDialogProps } from './PromptDialog';
13
- export { CurveEditor } from './CurveEditor';
14
- export { GradientEditor } from './GradientEditor';
15
24
  export { ValueField } from './ValueField';
16
- export { Row, NumberField, CheckboxField, SelectField } from './fields';
17
- export { MainModule, EmissionModule, ShapeModule, SizeOverLifeModule, ColorOverLifeModule, RendererModule, } from './modules';
18
- export { theme, inputStyle, rowStyle, labelStyle, buttonStyle } from './theme';
19
- export { MessageDialog } from './MessageDialog';
20
- export type { MessageDialogProps } from './MessageDialog';
21
- export { GalleryPanel } from './GalleryPanel';
22
- export type { GalleryPanelProps } from './GalleryPanel';
23
- export { EffectEditorHost, QuarksEffectEditor } from './EffectEditorHost';
24
- export type { EffectEditorHostProps, EffectEditorHostHandle } from './EffectEditorHost';
@@ -1,16 +1,16 @@
1
+ export { CollisionModule, ColorBySpeedModule, EmitDirectionModule, ForceOverLifeModule, GravityModule, LimitSpeedOverLifeModule, NoiseModule, OrbitOverLifeModule, RotationBySpeedModule, RotationOverLifeModule, SizeBySpeedModule, SpeedOverLifeModule, TextureSheetModule, TurbulenceModule, WidthOverTrailModule, } from './behaviorModules';
2
+ export { CurveEditor } from './CurveEditor';
1
3
  export { EffectEditor } from './EffectEditor';
2
- export { SpeedOverLifeModule, LimitSpeedOverLifeModule, ForceOverLifeModule, GravityModule, RotationOverLifeModule, NoiseModule, TurbulenceModule, CollisionModule, EmitDirectionModule, TextureSheetModule, ColorBySpeedModule, SizeBySpeedModule, RotationBySpeedModule, OrbitOverLifeModule, WidthOverTrailModule, } from './behaviorModules';
4
+ export { EffectEditorHost, QuarksEffectEditor } from './EffectEditorHost';
5
+ export { CheckboxField, NumberField, Row, SelectField } from './fields';
6
+ export { GalleryPanel } from './GalleryPanel';
7
+ export { GradientEditor } from './GradientEditor';
8
+ export { MessageDialog } from './MessageDialog';
9
+ export { ColorOverLifeModule, EmissionModule, MainModule, RendererModule, ShapeModule, SizeOverLifeModule, } from './modules';
10
+ export { ModuleSection } from './ModuleSection';
11
+ export { PromptDialog } from './PromptDialog';
3
12
  export { SubEmittersModule } from './SubEmittersModule';
13
+ export { buttonStyle, inputStyle, labelStyle, rowStyle, theme } from './theme';
4
14
  export { TimelinePanel } from './TimelinePanel';
5
15
  export { TimelineTrackRow } from './TimelineTrackRow';
6
- export { ModuleSection } from './ModuleSection';
7
- export { PromptDialog } from './PromptDialog';
8
- export { CurveEditor } from './CurveEditor';
9
- export { GradientEditor } from './GradientEditor';
10
16
  export { ValueField } from './ValueField';
11
- export { Row, NumberField, CheckboxField, SelectField } from './fields';
12
- export { MainModule, EmissionModule, ShapeModule, SizeOverLifeModule, ColorOverLifeModule, RendererModule, } from './modules';
13
- export { theme, inputStyle, rowStyle, labelStyle, buttonStyle } from './theme';
14
- export { MessageDialog } from './MessageDialog';
15
- export { GalleryPanel } from './GalleryPanel';
16
- export { EffectEditorHost, QuarksEffectEditor } from './EffectEditorHost';
@@ -1,32 +1,31 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import React, { useMemo, useState } from 'react';
3
- import { Mesh } from '@babylonjs/core/Meshes/mesh';
4
2
  import { Texture } from '@babylonjs/core/Materials/Textures/texture';
3
+ import { Mesh } from '@babylonjs/core/Meshes/mesh';
4
+ import { PlusIcon, XMarkIcon } from '@heroicons/react/24/solid';
5
5
  import { ColorOverLife, ConstantColor, ConstantValue, EulerGenerator, Gradient, IntervalValue, RandomColor, RandomColorBetweenGradient, RandomQuatGenerator, RenderMode, SizeOverLife, Vector3Function, Vector4, } from 'babylon.quarks';
6
+ import React, { useMemo, useState } from 'react';
6
7
  import { DEFAULT_GRADIENT_STOPS, buildGradient, findBehavior, hexToRgb, readGradientStops, rgbToHex, } from '../core/colors';
7
- import { DEFAULT_SHAPE_PARAMS, SHAPE_PARAM_KEYS, SHAPE_TYPES, createShape, getShapeType, readShapeParams } from '../core/shapes';
8
- import { buildCurve, buildScalar, readPieces, readScalar } from '../core/values';
9
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';
10
11
  import { CurveEditor } from './CurveEditor';
11
12
  import { GradientEditor } from './GradientEditor';
13
+ import { InspectorInfoIcon } from './InspectorInfoIcon';
12
14
  import { ModuleSection } from './ModuleSection';
13
15
  import { PromptDialog } from './PromptDialog';
14
- import { CheckboxField, NumberField, Row, SelectField } from './fields';
15
16
  import { ValueField } from './ValueField';
17
+ import { CheckboxField, NumberField, Row, SelectField } from './fields';
16
18
  import { iconStyle } from './icons';
17
19
  import { theme } from './theme';
18
- import { PlusIcon, XMarkIcon } from '@heroicons/react/24/solid';
19
- import { InspectorInfoIcon } from './InspectorInfoIcon';
20
20
  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
21
  const MESH_QUAD_INDICES = new Uint32Array([0, 1, 2, 0, 2, 3]);
22
22
  const MESH_QUAD_UVS = new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]);
23
23
  const MESH_CUBE_POSITIONS = new Float32Array([
24
- -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5,
25
- -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5,
24
+ -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5,
25
+ -0.5, 0.5, 0.5,
26
26
  ]);
27
27
  const MESH_CUBE_INDICES = new Uint32Array([
28
- 0, 1, 2, 0, 2, 3, 4, 6, 5, 4, 7, 6, 0, 4, 5, 0, 5, 1,
29
- 3, 2, 6, 3, 6, 7, 0, 3, 7, 0, 7, 4, 1, 5, 6, 1, 6, 2,
28
+ 0, 1, 2, 0, 2, 3, 4, 6, 5, 4, 7, 6, 0, 4, 5, 0, 5, 1, 3, 2, 6, 3, 6, 7, 0, 3, 7, 0, 7, 4, 1, 5, 6, 1, 6, 2,
30
29
  ]);
31
30
  function arraysEqual(a, b) {
32
31
  if (a.length !== b.length) {
@@ -65,21 +64,33 @@ function ColorInput(props) {
65
64
  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) => {
66
65
  const rgb = hexToRgb(e.target.value);
67
66
  onChange(new Vector4(rgb.r, rgb.g, rgb.b, value.w));
68
- }, style: { width: 42, height: 24, border: 'none', background: 'transparent', padding: 0, cursor: 'pointer' } }), _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))) })] }));
67
+ }, style: {
68
+ width: 42,
69
+ height: 24,
70
+ border: 'none',
71
+ background: 'transparent',
72
+ padding: 0,
73
+ cursor: 'pointer',
74
+ } }), _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))) })] }));
69
75
  }
70
76
  export function MainModule({ binding }) {
71
77
  const system = binding.system;
72
78
  const startColor = system.startColor;
73
79
  const colorMode = startColor instanceof RandomColor ? 'random' : 'constant';
74
- const colorA = startColor instanceof RandomColor ? startColor.a
75
- : startColor instanceof ConstantColor ? startColor.color
80
+ const colorA = startColor instanceof RandomColor
81
+ ? startColor.a
82
+ : startColor instanceof ConstantColor
83
+ ? startColor.color
76
84
  : new Vector4(1, 1, 1, 1);
77
85
  const colorB = startColor instanceof RandomColor ? startColor.b : new Vector4(1, 0.5, 0.2, 1);
78
86
  const rotation = system.startRotation;
79
87
  const isEuler = rotation instanceof EulerGenerator;
80
- const rotationMode = rotation instanceof RandomQuatGenerator ? '3d'
81
- : isEuler ? 'euler'
82
- : readScalar(rotation).mode === 'random' ? 'random'
88
+ const rotationMode = rotation instanceof RandomQuatGenerator
89
+ ? '3d'
90
+ : isEuler
91
+ ? 'euler'
92
+ : readScalar(rotation).mode === 'random'
93
+ ? 'random'
83
94
  : 'angle';
84
95
  const rotationState = readScalar(rotation instanceof RandomQuatGenerator || isEuler ? undefined : rotation);
85
96
  const eulerAngles = {
@@ -93,8 +104,10 @@ export function MainModule({ binding }) {
93
104
  });
94
105
  const startSize = system.startSize;
95
106
  const size3D = startSize.type === 'vec3function';
96
- const colorGenMode = startColor instanceof RandomColorBetweenGradient ? 'gradient2'
97
- : startColor instanceof Gradient ? 'gradient'
107
+ const colorGenMode = startColor instanceof RandomColorBetweenGradient
108
+ ? 'gradient2'
109
+ : startColor instanceof Gradient
110
+ ? 'gradient'
98
111
  : colorMode;
99
112
  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(ValueField, { label: "Start delay", generator: system.startDelay, min: 0, curveMax: 5, onChange: (g) => binding.apply((s) => {
100
113
  s.startDelay = g;
@@ -134,7 +147,8 @@ export function MainModule({ binding }) {
134
147
  s.startColor = new ConstantColor(colorA);
135
148
  }
136
149
  }) }) }), (colorGenMode === 'constant' || colorGenMode === 'random') && (_jsx(Row, { label: "", children: _jsx(ColorInput, { value: colorA, onChange: (next) => binding.apply((s) => {
137
- s.startColor = colorGenMode === 'random' ? new RandomColor(next, colorB) : new ConstantColor(next);
150
+ s.startColor =
151
+ colorGenMode === 'random' ? new RandomColor(next, colorB) : new ConstantColor(next);
138
152
  }) }) })), colorGenMode === 'random' && (_jsx(Row, { label: "", children: _jsx(ColorInput, { value: colorB, onChange: (next) => binding.apply((s) => (s.startColor = new RandomColor(colorA, next))) }) })), colorGenMode === 'gradient' && (_jsx("div", { style: { marginTop: 6 }, children: _jsx(GradientEditor, { stops: readGradientStops(startColor), onChange: (stops) => binding.apply((s) => (s.startColor = buildGradient(stops))) }) })), colorGenMode === 'gradient2' && (_jsxs("div", { style: { marginTop: 6, display: 'flex', flexDirection: 'column', gap: 6 }, children: [_jsx(GradientEditor, { stops: readGradientStops(startColor.gradient1), onChange: (stops) => binding.apply((s) => {
139
153
  const g = s.startColor;
140
154
  s.startColor = new RandomColorBetweenGradient(buildGradient(stops), g.gradient2);
@@ -160,7 +174,14 @@ export function MainModule({ binding }) {
160
174
  export function EmissionModule({ binding }) {
161
175
  const system = binding.system;
162
176
  const bursts = system.emissionBursts ?? [];
163
- return (_jsxs(ModuleSection, { title: "Emission", children: [_jsx(ValueField, { label: "Rate over time", generator: system.emissionOverTime, min: 0, curveMax: 500, onChange: (g) => binding.apply((s) => (s.emissionOverTime = g)) }), _jsx(ValueField, { label: "Rate over distance", generator: system.emissionOverDistance, min: 0, curveMax: 50, onChange: (g) => binding.apply((s) => (s.emissionOverDistance = g)) }), bursts.map((burst, i) => (_jsxs("div", { style: { marginTop: 8, padding: '6px 8px', border: '1px solid #22305c', borderRadius: 8 }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between' }, children: [_jsxs("span", { style: { fontSize: 11.5, color: '#9eb9ff' }, children: ["Burst ", i + 1] }), _jsx("button", { className: "qe-hover", style: { background: 'none', border: 'none', color: '#e08c8c', cursor: 'pointer', padding: 0, display: 'inline-flex' }, title: "Remove burst", onClick: () => binding.apply((s) => s.emissionBursts.splice(i, 1)), children: _jsx(XMarkIcon, { style: iconStyle(14) }) })] }), _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 2, marginTop: 4 }, children: [_jsx(Row, { label: "Time", children: _jsx(NumberField, { value: burst.time, min: 0, onChange: (time) => binding.apply(() => (burst.time = time)) }) }), _jsx(Row, { label: "Count", children: _jsx(NumberField, { value: readScalar(burst.count).value, min: 0, step: 1, onChange: (count) => binding.apply(() => (burst.count = new ConstantValue(Math.round(count)))) }) }), _jsx(Row, { label: "Cycles", children: _jsx(NumberField, { value: burst.cycle, min: 1, step: 1, onChange: (v) => binding.apply(() => (burst.cycle = Math.round(v))) }) }), _jsx(Row, { label: "Interval", children: _jsx(NumberField, { value: burst.interval, min: 0.01, onChange: (v) => binding.apply(() => (burst.interval = v)) }) }), _jsx(Row, { label: "Probability", children: _jsx(NumberField, { value: burst.probability, min: 0, step: 0.05, onChange: (v) => binding.apply(() => (burst.probability = Math.min(1, v))) }) })] })] }, i))), _jsxs("button", { className: "qe-hover", style: {
177
+ return (_jsxs(ModuleSection, { title: "Emission", children: [_jsx(ValueField, { label: "Rate over time", generator: system.emissionOverTime, min: 0, curveMax: 500, onChange: (g) => binding.apply((s) => (s.emissionOverTime = g)) }), _jsx(ValueField, { label: "Rate over distance", generator: system.emissionOverDistance, min: 0, curveMax: 50, onChange: (g) => binding.apply((s) => (s.emissionOverDistance = g)) }), bursts.map((burst, i) => (_jsxs("div", { style: { marginTop: 8, padding: '6px 8px', border: '1px solid #22305c', borderRadius: 8 }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between' }, children: [_jsxs("span", { style: { fontSize: 11.5, color: '#9eb9ff' }, children: ["Burst ", i + 1] }), _jsx("button", { className: "qe-hover", style: {
178
+ background: 'none',
179
+ border: 'none',
180
+ color: '#e08c8c',
181
+ cursor: 'pointer',
182
+ padding: 0,
183
+ display: 'inline-flex',
184
+ }, title: "Remove burst", onClick: () => binding.apply((s) => s.emissionBursts.splice(i, 1)), children: _jsx(XMarkIcon, { style: iconStyle(14) }) })] }), _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 2, marginTop: 4 }, children: [_jsx(Row, { label: "Time", children: _jsx(NumberField, { value: burst.time, min: 0, onChange: (time) => binding.apply(() => (burst.time = time)) }) }), _jsx(Row, { label: "Count", children: _jsx(NumberField, { value: readScalar(burst.count).value, min: 0, step: 1, onChange: (count) => binding.apply(() => (burst.count = new ConstantValue(Math.round(count)))) }) }), _jsx(Row, { label: "Cycles", children: _jsx(NumberField, { value: burst.cycle, min: 1, step: 1, onChange: (v) => binding.apply(() => (burst.cycle = Math.round(v))) }) }), _jsx(Row, { label: "Interval", children: _jsx(NumberField, { value: burst.interval, min: 0.01, onChange: (v) => binding.apply(() => (burst.interval = v)) }) }), _jsx(Row, { label: "Probability", children: _jsx(NumberField, { value: burst.probability, min: 0, step: 0.05, onChange: (v) => binding.apply(() => (burst.probability = Math.min(1, v))) }) })] })] }, i))), _jsxs("button", { className: "qe-hover", style: {
164
185
  marginTop: 8,
165
186
  background: 'none',
166
187
  border: '1px dashed #34477f',
@@ -172,7 +193,13 @@ export function EmissionModule({ binding }) {
172
193
  display: 'inline-flex',
173
194
  alignItems: 'center',
174
195
  gap: 6,
175
- }, onClick: () => binding.apply((s) => s.emissionBursts.push({ time: 0, count: new ConstantValue(20), cycle: 1, interval: 0.01, probability: 1 })), children: [_jsx(PlusIcon, { style: iconStyle(13) }), "Add burst"] })] }));
196
+ }, onClick: () => binding.apply((s) => s.emissionBursts.push({
197
+ time: 0,
198
+ count: new ConstantValue(20),
199
+ cycle: 1,
200
+ interval: 0.01,
201
+ probability: 1,
202
+ })), children: [_jsx(PlusIcon, { style: iconStyle(13) }), "Add burst"] })] }));
176
203
  }
177
204
  // Shapes whose constructor doesn't accept `mode`/`spread` at all — showing those controls
178
205
  // for them would edit a value the shape silently ignores, just like the old "default to
@@ -202,7 +229,9 @@ export function ShapeModule({ binding }) {
202
229
  export function SizeOverLifeModule({ binding }) {
203
230
  const system = binding.system;
204
231
  const behavior = findBehavior(system.behaviors, 'SizeOverLife');
205
- const curve = behavior ? readScalar(behavior.size).curve : [0.2, 1, 1, 0.4];
232
+ const curve = behavior
233
+ ? readScalar(behavior.size).curve
234
+ : [0.2, 1, 1, 0.4];
206
235
  return (_jsx(ModuleSection, { title: "Size over Lifetime", enabled: !!behavior, onToggle: (enabled) => binding.apply((s) => {
207
236
  removeBehavior(s, 'SizeOverLife');
208
237
  if (enabled) {
@@ -233,7 +262,7 @@ export function ColorOverLifeModule({ binding }) {
233
262
  * isometric rotation, scales them to fit, and strokes the triangle edges on a 2D canvas — enough
234
263
  * for the user to recognise which mesh they picked without spinning up a second Babylon scene.
235
264
  */
236
- function MeshPreview({ positions, indices, size = 96 }) {
265
+ function MeshPreview({ positions, indices, size = 96, }) {
237
266
  const canvasRef = React.useRef(null);
238
267
  React.useEffect(() => {
239
268
  const canvas = canvasRef.current;
@@ -318,7 +347,8 @@ export function RendererModule({ binding, textureOptions, geometryOptions, resol
318
347
  { value: RenderMode.Trail, label: 'Trail' },
319
348
  { value: RenderMode.Mesh, label: 'Mesh' },
320
349
  ], onChange: (mode) => binding.apply((s) => (s.renderMode = mode)) }) }), system.renderMode === RenderMode.Trail && (_jsxs(_Fragment, { children: [_jsx(Row, { label: "Trail length", children: _jsx(NumberField, { value: readScalar(system.rendererEmitterSettings.startLength).value, min: 1, step: 1, onChange: (v) => binding.apply((s) => {
321
- s.rendererEmitterSettings.startLength = new ConstantValue(Math.round(v));
350
+ s.rendererEmitterSettings.startLength =
351
+ new ConstantValue(Math.round(v));
322
352
  }) }) }), _jsx(Row, { label: "Follow origin", children: _jsx(CheckboxField, { value: !!system.rendererEmitterSettings.followLocalOrigin, onChange: (v) => binding.apply((s) => {
323
353
  s.rendererEmitterSettings.followLocalOrigin = v;
324
354
  }) }) })] })), 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: {
package/package.json CHANGED
@@ -1,72 +1,72 @@
1
1
  {
2
- "name": "babylon.quarks-editor",
3
- "version": "0.18.1",
4
- "description": "Shuriken-style effect editor for babylon.quarks: headless edit-model core + React module-stack UI",
5
- "type": "module",
6
- "main": "./dist/index.js",
7
- "types": "./dist/index.d.ts",
8
- "exports": {
9
- ".": {
10
- "import": "./dist/index.js",
11
- "types": "./dist/index.d.ts"
2
+ "name": "babylon.quarks-editor",
3
+ "version": "0.19.0",
4
+ "description": "Shuriken-style effect editor for babylon.quarks: headless edit-model core + React module-stack UI",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ },
13
+ "./react": {
14
+ "import": "./dist/react/index.js",
15
+ "types": "./dist/react/index.d.ts"
16
+ }
12
17
  },
13
- "./react": {
14
- "import": "./dist/react/index.js",
15
- "types": "./dist/react/index.d.ts"
18
+ "files": [
19
+ "dist",
20
+ "src",
21
+ "LICENSE",
22
+ "README.md",
23
+ "CHANGELOG.md"
24
+ ],
25
+ "scripts": {
26
+ "clean": "rimraf dist",
27
+ "build": "npm run clean && tsc -p tsconfig.json"
28
+ },
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/Soullnik/babylon.quarks-standalone.git"
32
+ },
33
+ "keywords": [
34
+ "babylonjs",
35
+ "babylon.js",
36
+ "particles",
37
+ "particle-system",
38
+ "particle-editor",
39
+ "vfx",
40
+ "vfx-editor",
41
+ "shuriken",
42
+ "editor",
43
+ "react",
44
+ "quarks"
45
+ ],
46
+ "author": "soullnik <soullnik96@gmail.com>",
47
+ "license": "MIT",
48
+ "bugs": {
49
+ "url": "https://github.com/Soullnik/babylon.quarks-standalone/issues"
50
+ },
51
+ "homepage": "https://github.com/Soullnik/babylon.quarks-standalone#readme",
52
+ "peerDependencies": {
53
+ "@babylonjs/core": ">=9.0.0",
54
+ "@babylonjs/materials": ">=9.0.0",
55
+ "@heroicons/react": ">=2.0.0",
56
+ "@tanstack/react-virtual": ">=3.0.0",
57
+ "babylon.quarks": ">=0.17.0",
58
+ "react": ">=18",
59
+ "react-dom": ">=18"
60
+ },
61
+ "devDependencies": {
62
+ "@babylonjs/materials": "9.6.2",
63
+ "@heroicons/react": "^2.2.0",
64
+ "@tanstack/react-virtual": "^3.14.6",
65
+ "@types/react": "^19.0.0",
66
+ "@types/react-dom": "^19.0.0",
67
+ "react": "^19.0.0",
68
+ "react-dom": "^19.0.0",
69
+ "rimraf": "^6.0.0",
70
+ "typescript": "^5.9.3"
16
71
  }
17
- },
18
- "files": [
19
- "dist",
20
- "src",
21
- "LICENSE",
22
- "README.md",
23
- "CHANGELOG.md"
24
- ],
25
- "scripts": {
26
- "clean": "rimraf dist",
27
- "build": "npm run clean && tsc -p tsconfig.json"
28
- },
29
- "repository": {
30
- "type": "git",
31
- "url": "git+https://github.com/Soullnik/babylon.quarks-standalone.git"
32
- },
33
- "keywords": [
34
- "babylonjs",
35
- "babylon.js",
36
- "particles",
37
- "particle-system",
38
- "particle-editor",
39
- "vfx",
40
- "vfx-editor",
41
- "shuriken",
42
- "editor",
43
- "react",
44
- "quarks"
45
- ],
46
- "author": "soullnik <soullnik96@gmail.com>",
47
- "license": "MIT",
48
- "bugs": {
49
- "url": "https://github.com/Soullnik/babylon.quarks-standalone/issues"
50
- },
51
- "homepage": "https://github.com/Soullnik/babylon.quarks-standalone#readme",
52
- "peerDependencies": {
53
- "@babylonjs/core": ">=9.0.0",
54
- "@babylonjs/materials": ">=9.0.0",
55
- "@heroicons/react": ">=2.0.0",
56
- "@tanstack/react-virtual": ">=3.0.0",
57
- "babylon.quarks": ">=0.17.0",
58
- "react": ">=18",
59
- "react-dom": ">=18"
60
- },
61
- "devDependencies": {
62
- "@babylonjs/materials": "9.6.2",
63
- "@heroicons/react": "^2.2.0",
64
- "@tanstack/react-virtual": "^3.14.6",
65
- "@types/react": "^19.0.0",
66
- "@types/react-dom": "^19.0.0",
67
- "react": "^19.0.0",
68
- "react-dom": "^19.0.0",
69
- "rimraf": "^6.0.0",
70
- "typescript": "^5.9.3"
71
- }
72
72
  }
@@ -1,7 +1,7 @@
1
- import type {ParticleSystem} from 'babylon.quarks';
2
1
  import type {TransformNode} from '@babylonjs/core/Meshes/transformNode';
3
- import {buildEffectTree, serializeEffectTree} from './effectTree';
2
+ import type {ParticleSystem} from 'babylon.quarks';
4
3
  import type {EffectTreeNode} from './effectTree';
4
+ import {buildEffectTree, serializeEffectTree} from './effectTree';
5
5
 
6
6
  export type EditorListener = () => void;
7
7
 
@@ -1,5 +1,5 @@
1
- import {ColorOverLife, Gradient, Vector3, Vector4} from 'babylon.quarks';
2
1
  import type {Behavior} from 'babylon.quarks';
2
+ import {ColorOverLife, Gradient, Vector3, Vector4} from 'babylon.quarks';
3
3
 
4
4
  /** One RGBA stop of the editable gradient (all channels 0..1, pos 0..1). */
5
5
  export interface GradientStop {
@@ -1,5 +1,5 @@
1
- import type {Scene} from '@babylonjs/core/scene';
2
1
  import {TransformNode} from '@babylonjs/core/Meshes/transformNode';
2
+ import type {Scene} from '@babylonjs/core/scene';
3
3
  import {
4
4
  ColorOverLife,
5
5
  ConeEmitter,
@@ -1,7 +1,7 @@
1
- import {ParticleEmitter} from 'babylon.quarks';
2
- import type {ParticleSystem} from 'babylon.quarks';
3
- import {TransformNode} from '@babylonjs/core/Meshes/transformNode';
4
1
  import {Matrix, Quaternion} from '@babylonjs/core/Maths/math.vector';
2
+ import {TransformNode} from '@babylonjs/core/Meshes/transformNode';
3
+ import type {ParticleSystem} from 'babylon.quarks';
4
+ import {ParticleEmitter} from 'babylon.quarks';
5
5
  import {isEditorSceneNode} from './editorScene';
6
6
 
7
7
  /** A node in the editor's effect hierarchy: a Group (organizational) or a ParticleEmitter. */
@@ -46,7 +46,7 @@ export function collectSystems(tree: EffectTreeNode): ParticleSystem[] {
46
46
  }
47
47
 
48
48
  interface SerializeMeta {
49
- textures: {[k: string]: {url?: string; name?: string}};
49
+ textures: {[k: string]: {url?: string; name?: string; invertY?: boolean}};
50
50
  materials: {[k: string]: {[key: string]: unknown}};
51
51
  geometries: {[k: string]: unknown};
52
52
  }
@@ -72,7 +72,11 @@ function serializeNode(tree: EffectTreeNode, meta: SerializeMeta, fallbackUuid:
72
72
  children: children.length > 0 ? children : undefined,
73
73
  };
74
74
  if (tree.system) {
75
- return {...base, type: 'ParticleEmitter', ps: tree.system.toJSON(meta as never, {useUrlForImage: true} as never)};
75
+ return {
76
+ ...base,
77
+ type: 'ParticleEmitter',
78
+ ps: tree.system.toJSON(meta as never, {useUrlForImage: true} as never),
79
+ };
76
80
  }
77
81
  return {...base, type: 'Group'};
78
82
  }
@@ -113,13 +117,24 @@ function finishEnvelope(object: Record<string, unknown>, meta: SerializeMeta): s
113
117
  // toJSON stashes live Texture instances and material records in meta; flatten them into
114
118
  // the serializable images/textures/materials arrays QuarksLoader expects.
115
119
  const images: Array<{uuid: string; url: string}> = [];
116
- const textures: Array<{uuid: string; image?: string}> = [];
120
+ const textures: Array<{uuid: string; image?: string; invertY?: boolean; noMipmap?: boolean}> = [];
117
121
  for (const [uuid, tex] of Object.entries(meta.textures)) {
118
122
  const url = tex?.url ?? tex?.name;
123
+ const invertY = typeof tex?.invertY === 'boolean' ? tex.invertY : undefined;
124
+ const isEnvAtlas =
125
+ tex?.name === 'quarksEnvAtlas' || (typeof tex?.name === 'string' && tex.name.endsWith('_envAtlas'));
119
126
  if (url) {
120
127
  const imageUuid = `${uuid}-image`;
121
128
  images.push({uuid: imageUuid, url});
122
- textures.push({uuid, image: imageUuid});
129
+ const entry: {uuid: string; image?: string; invertY?: boolean; noMipmap?: boolean} = {
130
+ uuid,
131
+ image: imageUuid,
132
+ };
133
+ if (invertY === false || isEnvAtlas) {
134
+ entry.invertY = false;
135
+ entry.noMipmap = true;
136
+ }
137
+ textures.push(entry);
123
138
  } else {
124
139
  textures.push({uuid});
125
140
  }