babylon.quarks-editor 0.18.1 → 0.20.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.
- package/CHANGELOG.md +28 -0
- package/README.md +6 -6
- package/dist/core/assetDragSession.d.ts +4 -0
- package/dist/core/assetDragSession.js +8 -0
- package/dist/core/assetTree.d.ts +39 -0
- package/dist/core/assetTree.js +112 -0
- package/dist/core/assets.d.ts +56 -0
- package/dist/core/assets.js +207 -0
- package/dist/core/binding.d.ts +6 -1
- package/dist/core/binding.js +7 -0
- package/dist/core/collision.d.ts +4 -5
- package/dist/core/collision.js +4 -5
- package/dist/core/colors.d.ts +1 -1
- package/dist/core/defaultEffect.d.ts +4 -5
- package/dist/core/defaultEffect.js +5 -14
- package/dist/core/effectTree.d.ts +1 -1
- package/dist/core/effectTree.js +18 -4
- package/dist/core/emitterShapeWireframe.d.ts +2 -1
- package/dist/core/emitterShapeWireframe.js +2 -1
- package/dist/core/galleryDropMarker.js +1 -1
- package/dist/core/galleryPlayback.d.ts +7 -7
- package/dist/core/galleryPlayback.js +2 -2
- package/dist/core/galleryScene.d.ts +8 -13
- package/dist/core/galleryScene.js +14 -42
- package/dist/core/gallerySelectionMarker.d.ts +1 -0
- package/dist/core/gallerySelectionMarker.js +1 -0
- package/dist/core/history.d.ts +12 -1
- package/dist/core/history.js +16 -3
- package/dist/core/indexedDbProjectStore.d.ts +35 -0
- package/dist/core/indexedDbProjectStore.js +299 -0
- package/dist/core/lineShaders.d.ts +2 -0
- package/dist/core/lineShaders.js +7 -0
- package/dist/core/loadEffect.d.ts +2 -2
- package/dist/core/loadEffect.js +1 -1
- package/dist/core/pickAssetFiles.d.ts +6 -0
- package/dist/core/{pickGalleryFiles.js → pickAssetFiles.js} +13 -8
- package/dist/core/projectStore.d.ts +61 -0
- package/dist/core/projectStore.js +14 -0
- package/dist/core/sceneEntries.d.ts +35 -0
- package/dist/core/sceneEntries.js +62 -0
- package/dist/core/shapes.d.ts +1 -1
- package/dist/core/shapes.js +35 -8
- package/dist/core/systems.d.ts +1 -1
- package/dist/core/systems.js +3 -1
- package/dist/core/timeline.d.ts +1 -2
- package/dist/core/transport.d.ts +12 -0
- package/dist/core/transport.js +36 -0
- package/dist/core/values.d.ts +1 -1
- package/dist/core/viewportPicker.d.ts +4 -4
- package/dist/core/viewportPicker.js +3 -3
- package/dist/index.d.ts +15 -11
- package/dist/index.js +8 -6
- package/dist/react/AssetsPanel.d.ts +22 -0
- package/dist/react/AssetsPanel.js +252 -0
- package/dist/react/ChoiceDialog.d.ts +18 -0
- package/dist/react/ChoiceDialog.js +48 -0
- package/dist/react/ContextMenu.d.ts +16 -0
- package/dist/react/ContextMenu.js +70 -0
- package/dist/react/CurveEditor.d.ts +1 -2
- package/dist/react/CurveEditor.js +14 -3
- package/dist/react/EditorWorkspace.d.ts +21 -0
- package/dist/react/EditorWorkspace.js +63 -0
- package/dist/react/EffectEditor.d.ts +3 -3
- package/dist/react/EffectEditor.js +9 -6
- package/dist/react/EffectEditorHost.d.ts +20 -3
- package/dist/react/EffectEditorHost.js +1426 -558
- package/dist/react/EffectTreeView.d.ts +17 -0
- package/dist/react/EffectTreeView.js +52 -0
- package/dist/react/GradientEditor.d.ts +1 -2
- package/dist/react/GradientEditor.js +8 -1
- package/dist/react/MessageDialog.js +7 -1
- package/dist/react/ModuleSection.js +10 -3
- package/dist/react/ObjectModule.d.ts +5 -2
- package/dist/react/ObjectModule.js +8 -11
- package/dist/react/ProjectsPage.d.ts +9 -0
- package/dist/react/ProjectsPage.js +140 -0
- package/dist/react/ScenePanel.d.ts +26 -0
- package/dist/react/ScenePanel.js +134 -0
- package/dist/react/SubEmittersModule.d.ts +1 -2
- package/dist/react/TimelinePanel.d.ts +11 -4
- package/dist/react/TimelinePanel.js +65 -40
- package/dist/react/TimelineTrackRow.d.ts +1 -1
- package/dist/react/TimelineTrackRow.js +20 -6
- package/dist/react/TransportBar.d.ts +24 -0
- package/dist/react/TransportBar.js +43 -0
- package/dist/react/ValueField.d.ts +1 -2
- package/dist/react/behaviorModules.js +24 -12
- package/dist/react/index.d.ts +31 -17
- package/dist/react/index.js +18 -11
- package/dist/react/inspectorHints.js +1 -2
- package/dist/react/modules.js +55 -25
- package/package.json +68 -68
- package/src/core/assetDragSession.ts +12 -0
- package/src/core/assetTree.ts +148 -0
- package/src/core/assets.ts +262 -0
- package/src/core/binding.ts +10 -2
- package/src/core/collision.ts +4 -5
- package/src/core/colors.ts +1 -1
- package/src/core/defaultEffect.ts +10 -23
- package/src/core/effectTree.ts +22 -7
- package/src/core/emitterShapeWireframe.ts +9 -4
- package/src/core/galleryDropMarker.ts +6 -2
- package/src/core/galleryPlayback.ts +9 -9
- package/src/core/galleryScene.ts +15 -63
- package/src/core/gallerySelectionMarker.ts +6 -1
- package/src/core/history.ts +24 -3
- package/src/core/indexedDbProjectStore.ts +357 -0
- package/src/core/lineShaders.ts +7 -0
- package/src/core/loadEffect.ts +3 -3
- package/src/core/{pickGalleryFiles.ts → pickAssetFiles.ts} +16 -8
- package/src/core/projectStore.ts +81 -0
- package/src/core/sceneEntries.ts +96 -0
- package/src/core/shapes.ts +48 -10
- package/src/core/systems.ts +9 -3
- package/src/core/timeline.ts +8 -8
- package/src/core/transport.ts +53 -0
- package/src/core/values.ts +1 -1
- package/src/core/viewportPicker.ts +5 -5
- package/src/index.ts +29 -18
- package/src/react/AssetsPanel.tsx +559 -0
- package/src/react/ChoiceDialog.tsx +93 -0
- package/src/react/ContextMenu.tsx +107 -0
- package/src/react/CurveEditor.tsx +40 -10
- package/src/react/EditorWorkspace.tsx +95 -0
- package/src/react/EffectEditor.tsx +28 -16
- package/src/react/EffectEditorHost.tsx +2005 -721
- package/src/react/EffectTreeView.tsx +117 -0
- package/src/react/GradientEditor.tsx +21 -4
- package/src/react/InspectorInfoIcon.tsx +6 -2
- package/src/react/MessageDialog.tsx +9 -1
- package/src/react/ModuleSection.tsx +12 -3
- package/src/react/ObjectModule.tsx +41 -16
- package/src/react/ProjectsPage.tsx +306 -0
- package/src/react/PromptDialog.tsx +5 -1
- package/src/react/ScenePanel.tsx +318 -0
- package/src/react/SubEmittersModule.tsx +1 -2
- package/src/react/TimelinePanel.tsx +171 -99
- package/src/react/TimelineTrackRow.tsx +71 -18
- package/src/react/TransportBar.tsx +114 -0
- package/src/react/ValueField.tsx +0 -1
- package/src/react/behaviorModules.tsx +148 -42
- package/src/react/index.ts +47 -33
- package/src/react/inspectorHints.ts +1 -2
- package/src/react/modules.tsx +173 -57
- package/dist/core/galleryDragSession.d.ts +0 -4
- package/dist/core/galleryDragSession.js +0 -8
- package/dist/core/galleryTree.d.ts +0 -49
- package/dist/core/galleryTree.js +0 -138
- package/dist/core/loadEffectGallery.d.ts +0 -36
- package/dist/core/loadEffectGallery.js +0 -97
- package/dist/core/pickGalleryFiles.d.ts +0 -5
- package/dist/react/GalleryPanel.d.ts +0 -20
- package/dist/react/GalleryPanel.js +0 -246
- package/src/core/galleryDragSession.ts +0 -12
- package/src/core/galleryTree.ts +0 -187
- package/src/core/loadEffectGallery.ts +0 -146
- package/src/react/GalleryPanel.tsx +0 -517
package/dist/react/modules.js
CHANGED
|
@@ -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,
|
|
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: {
|
|
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
|
|
75
|
-
|
|
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
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
97
|
-
|
|
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 =
|
|
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: {
|
|
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({
|
|
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
|
|
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 =
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
2
|
+
"name": "babylon.quarks-editor",
|
|
3
|
+
"version": "0.20.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
|
-
"
|
|
14
|
-
|
|
15
|
-
|
|
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
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type {AssetDragPayload} from './assetTree';
|
|
2
|
+
|
|
3
|
+
let activeDrag: AssetDragPayload | null = null;
|
|
4
|
+
|
|
5
|
+
/** Stores the current asset drag payload for viewport preview (getData is drop-only in most browsers). */
|
|
6
|
+
export function setAssetDragSession(payload: AssetDragPayload | null): void {
|
|
7
|
+
activeDrag = payload;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function getAssetDragSession(): AssetDragPayload | null {
|
|
11
|
+
return activeDrag;
|
|
12
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type {EffectAsset} from './assets';
|
|
2
|
+
|
|
3
|
+
export interface AssetTreeFolder {
|
|
4
|
+
kind: 'folder';
|
|
5
|
+
name: string;
|
|
6
|
+
path: string;
|
|
7
|
+
children: AssetTreeNode[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface AssetTreeEffect {
|
|
11
|
+
kind: 'asset';
|
|
12
|
+
name: string;
|
|
13
|
+
path: string;
|
|
14
|
+
asset: EffectAsset;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type AssetTreeNode = AssetTreeFolder | AssetTreeEffect;
|
|
18
|
+
|
|
19
|
+
function compareNodes(a: AssetTreeNode, b: AssetTreeNode): number {
|
|
20
|
+
if (a.kind !== b.kind) {
|
|
21
|
+
return a.kind === 'folder' ? -1 : 1;
|
|
22
|
+
}
|
|
23
|
+
return a.name.localeCompare(b.name, undefined, {sensitivity: 'base'});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Builds a folder tree from asset relative paths (e.g. `VFX/Fire/Explosion.json`). */
|
|
27
|
+
export function buildAssetTree(assets: EffectAsset[]): AssetTreeNode[] {
|
|
28
|
+
const roots: AssetTreeNode[] = [];
|
|
29
|
+
|
|
30
|
+
for (const asset of assets) {
|
|
31
|
+
const segments = asset.path
|
|
32
|
+
.replace(/\.json$/i, '')
|
|
33
|
+
.split('/')
|
|
34
|
+
.filter(Boolean);
|
|
35
|
+
const fileName = segments.pop() ?? asset.name;
|
|
36
|
+
let level = roots;
|
|
37
|
+
let pathSoFar = '';
|
|
38
|
+
|
|
39
|
+
for (const segment of segments) {
|
|
40
|
+
pathSoFar = pathSoFar ? `${pathSoFar}/${segment}` : segment;
|
|
41
|
+
let folder = level.find((node): node is AssetTreeFolder => node.kind === 'folder' && node.name === segment);
|
|
42
|
+
if (!folder) {
|
|
43
|
+
folder = {kind: 'folder', name: segment, path: pathSoFar, children: []};
|
|
44
|
+
level.push(folder);
|
|
45
|
+
}
|
|
46
|
+
level = folder.children;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
level.push({kind: 'asset', name: fileName, path: asset.path, asset});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const sortTree = (nodes: AssetTreeNode[]): void => {
|
|
53
|
+
nodes.sort(compareNodes);
|
|
54
|
+
for (const node of nodes) {
|
|
55
|
+
if (node.kind === 'folder') {
|
|
56
|
+
sortTree(node.children);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
sortTree(roots);
|
|
62
|
+
return roots;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Children of the folder at `path` (`''` is the top level), or null when it no longer exists. */
|
|
66
|
+
export function folderChildren(tree: AssetTreeNode[], path: string): AssetTreeNode[] | null {
|
|
67
|
+
let level = tree;
|
|
68
|
+
for (const segment of path.split('/').filter(Boolean)) {
|
|
69
|
+
const folder = level.find((node): node is AssetTreeFolder => node.kind === 'folder' && node.name === segment);
|
|
70
|
+
if (!folder) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
level = folder.children;
|
|
74
|
+
}
|
|
75
|
+
return level;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Assets whose name or path contains `query`, across every folder, sorted by name. */
|
|
79
|
+
export function searchAssets(assets: EffectAsset[], query: string): EffectAsset[] {
|
|
80
|
+
const q = query.trim().toLowerCase();
|
|
81
|
+
return assets
|
|
82
|
+
.filter((asset) => `${asset.path} ${asset.name}`.toLowerCase().includes(q))
|
|
83
|
+
.sort((a, b) => a.name.localeCompare(b.name, undefined, {sensitivity: 'base'}));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export const ASSET_DRAG_MIME = 'application/x-quarks-asset-drop';
|
|
87
|
+
|
|
88
|
+
export type AssetDragPayload = {kind: 'asset'; id: number} | {kind: 'folder'; path: string};
|
|
89
|
+
|
|
90
|
+
export function writeAssetDrag(dataTransfer: DataTransfer, payload: AssetDragPayload): void {
|
|
91
|
+
dataTransfer.setData(ASSET_DRAG_MIME, JSON.stringify(payload));
|
|
92
|
+
dataTransfer.effectAllowed = 'copy';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function readAssetDrag(dataTransfer: DataTransfer): AssetDragPayload | null {
|
|
96
|
+
const raw = dataTransfer.getData(ASSET_DRAG_MIME);
|
|
97
|
+
if (!raw) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
const parsed = JSON.parse(raw) as AssetDragPayload;
|
|
102
|
+
if (parsed?.kind === 'asset' && typeof parsed.id === 'number') {
|
|
103
|
+
return parsed;
|
|
104
|
+
}
|
|
105
|
+
if (parsed?.kind === 'folder' && typeof parsed.path === 'string') {
|
|
106
|
+
return parsed;
|
|
107
|
+
}
|
|
108
|
+
} catch {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Returns every asset whose JSON path sits inside `folderPath` (recursive). */
|
|
115
|
+
export function collectAssetsInFolder(assets: EffectAsset[], folderPath: string): EffectAsset[] {
|
|
116
|
+
const prefix = `${folderPath}/`;
|
|
117
|
+
return assets.filter((asset) => asset.path.startsWith(prefix));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Collects all assets under a folder tree node. */
|
|
121
|
+
export function collectFolderAssets(node: AssetTreeFolder): EffectAsset[] {
|
|
122
|
+
const collected: EffectAsset[] = [];
|
|
123
|
+
const walk = (nodes: AssetTreeNode[]): void => {
|
|
124
|
+
for (const child of nodes) {
|
|
125
|
+
if (child.kind === 'asset') {
|
|
126
|
+
collected.push(child.asset);
|
|
127
|
+
} else {
|
|
128
|
+
walk(child.children);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
walk(node.children);
|
|
133
|
+
return collected;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Resolves a drag payload to the assets that would be instantiated on drop. */
|
|
137
|
+
export function describeAssetDrag(
|
|
138
|
+
assets: EffectAsset[],
|
|
139
|
+
payload: AssetDragPayload
|
|
140
|
+
): {assets: EffectAsset[]; label: string} {
|
|
141
|
+
if (payload.kind === 'asset') {
|
|
142
|
+
const asset = assets.find((item) => item.id === payload.id);
|
|
143
|
+
return {assets: asset ? [asset] : [], label: asset?.name ?? 'Effect'};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const label = payload.path.split('/').filter(Boolean).pop() ?? payload.path;
|
|
147
|
+
return {assets: collectAssetsInFolder(assets, payload.path), label};
|
|
148
|
+
}
|