babylon.quarks-editor 0.17.7
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 +13 -0
- package/LICENSE +21 -0
- package/README.md +84 -0
- package/dist/core/binding.d.ts +43 -0
- package/dist/core/binding.js +82 -0
- package/dist/core/collision.d.ts +20 -0
- package/dist/core/collision.js +39 -0
- package/dist/core/colors.d.ts +31 -0
- package/dist/core/colors.js +60 -0
- package/dist/core/effectTree.d.ts +25 -0
- package/dist/core/effectTree.js +115 -0
- package/dist/core/groups.d.ts +12 -0
- package/dist/core/groups.js +27 -0
- package/dist/core/history.d.ts +25 -0
- package/dist/core/history.js +69 -0
- package/dist/core/loadEffect.d.ts +8 -0
- package/dist/core/loadEffect.js +87 -0
- package/dist/core/scrub.d.ts +11 -0
- package/dist/core/scrub.js +24 -0
- package/dist/core/shapes.d.ts +31 -0
- package/dist/core/shapes.js +98 -0
- package/dist/core/systems.d.ts +25 -0
- package/dist/core/systems.js +76 -0
- package/dist/core/textureExport.d.ts +9 -0
- package/dist/core/textureExport.js +58 -0
- package/dist/core/timeline.d.ts +35 -0
- package/dist/core/timeline.js +69 -0
- package/dist/core/values.d.ts +31 -0
- package/dist/core/values.js +81 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +9 -0
- package/dist/react/CurveEditor.d.ts +12 -0
- package/dist/react/CurveEditor.js +106 -0
- package/dist/react/EffectEditor.d.ts +22 -0
- package/dist/react/EffectEditor.js +30 -0
- package/dist/react/EffectEditorHost.d.ts +42 -0
- package/dist/react/EffectEditorHost.js +247 -0
- package/dist/react/GradientEditor.d.ts +7 -0
- package/dist/react/GradientEditor.js +72 -0
- package/dist/react/ModuleSection.d.ts +9 -0
- package/dist/react/ModuleSection.js +24 -0
- package/dist/react/PromptDialog.d.ts +14 -0
- package/dist/react/PromptDialog.js +45 -0
- package/dist/react/SubEmittersModule.d.ts +10 -0
- package/dist/react/SubEmittersModule.js +39 -0
- package/dist/react/TimelinePanel.d.ts +32 -0
- package/dist/react/TimelinePanel.js +225 -0
- package/dist/react/TimelineTrackRow.d.ts +23 -0
- package/dist/react/TimelineTrackRow.js +107 -0
- package/dist/react/ValueField.d.ts +13 -0
- package/dist/react/ValueField.js +19 -0
- package/dist/react/behaviorModules.d.ts +23 -0
- package/dist/react/behaviorModules.js +139 -0
- package/dist/react/fields.d.ts +24 -0
- package/dist/react/fields.js +30 -0
- package/dist/react/index.d.ts +20 -0
- package/dist/react/index.js +14 -0
- package/dist/react/modules.d.ts +35 -0
- package/dist/react/modules.js +439 -0
- package/dist/react/theme.d.ts +26 -0
- package/dist/react/theme.js +78 -0
- package/package.json +63 -0
- package/src/core/binding.ts +105 -0
- package/src/core/collision.ts +40 -0
- package/src/core/colors.ts +84 -0
- package/src/core/effectTree.ts +143 -0
- package/src/core/groups.ts +29 -0
- package/src/core/history.ts +77 -0
- package/src/core/loadEffect.ts +102 -0
- package/src/core/scrub.ts +28 -0
- package/src/core/shapes.ts +133 -0
- package/src/core/systems.ts +98 -0
- package/src/core/textureExport.ts +63 -0
- package/src/core/timeline.ts +94 -0
- package/src/core/values.ts +113 -0
- package/src/index.ts +32 -0
- package/src/react/CurveEditor.tsx +168 -0
- package/src/react/EffectEditor.tsx +111 -0
- package/src/react/EffectEditorHost.tsx +377 -0
- package/src/react/GradientEditor.tsx +127 -0
- package/src/react/ModuleSection.tsx +54 -0
- package/src/react/PromptDialog.tsx +85 -0
- package/src/react/SubEmittersModule.tsx +80 -0
- package/src/react/TimelinePanel.tsx +354 -0
- package/src/react/TimelineTrackRow.tsx +267 -0
- package/src/react/ValueField.tsx +60 -0
- package/src/react/behaviorModules.tsx +658 -0
- package/src/react/fields.tsx +76 -0
- package/src/react/index.ts +43 -0
- package/src/react/modules.tsx +1034 -0
- package/src/react/theme.ts +85 -0
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import React, { useState } from 'react';
|
|
3
|
+
import { Mesh } from '@babylonjs/core/Meshes/mesh';
|
|
4
|
+
import { ColorOverLife, ConstantColor, ConstantValue, EulerGenerator, Gradient, IntervalValue, RandomColor, RandomColorBetweenGradient, RandomQuatGenerator, RenderMode, SizeOverLife, Vector3Function, Vector4, } from 'babylon.quarks';
|
|
5
|
+
import { DEFAULT_GRADIENT_STOPS, buildGradient, findBehavior, hexToRgb, readGradientStops, rgbToHex, } from '../core/colors';
|
|
6
|
+
import { DEFAULT_SHAPE_PARAMS, SHAPE_PARAM_KEYS, SHAPE_TYPES, createShape, getShapeType, readShapeParams } from '../core/shapes';
|
|
7
|
+
import { buildCurve, buildScalar, readPieces, readScalar } from '../core/values';
|
|
8
|
+
import { CurveEditor } from './CurveEditor';
|
|
9
|
+
import { GradientEditor } from './GradientEditor';
|
|
10
|
+
import { ModuleSection } from './ModuleSection';
|
|
11
|
+
import { PromptDialog } from './PromptDialog';
|
|
12
|
+
import { CheckboxField, NumberField, Row, SelectField } from './fields';
|
|
13
|
+
import { ValueField } from './ValueField';
|
|
14
|
+
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]);
|
|
15
|
+
const MESH_QUAD_INDICES = new Uint32Array([0, 1, 2, 0, 2, 3]);
|
|
16
|
+
const MESH_QUAD_UVS = new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]);
|
|
17
|
+
const MESH_CUBE_POSITIONS = new Float32Array([
|
|
18
|
+
-0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5,
|
|
19
|
+
-0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5,
|
|
20
|
+
]);
|
|
21
|
+
const MESH_CUBE_INDICES = new Uint32Array([
|
|
22
|
+
0, 1, 2, 0, 2, 3, 4, 6, 5, 4, 7, 6, 0, 4, 5, 0, 5, 1,
|
|
23
|
+
3, 2, 6, 3, 6, 7, 0, 3, 7, 0, 7, 4, 1, 5, 6, 1, 6, 2,
|
|
24
|
+
]);
|
|
25
|
+
function arraysEqual(a, b) {
|
|
26
|
+
if (a.length !== b.length) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
for (let i = 0; i < a.length; i++) {
|
|
30
|
+
if (a[i] !== b[i]) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
/** Detects whether the current mesh geometry is one of the editor's built-in presets or was imported. */
|
|
37
|
+
function detectGeometryPreset(positions) {
|
|
38
|
+
if (arraysEqual(positions, MESH_CUBE_POSITIONS)) {
|
|
39
|
+
return 'cube';
|
|
40
|
+
}
|
|
41
|
+
if (arraysEqual(positions, MESH_QUAD_POSITIONS)) {
|
|
42
|
+
return 'quad';
|
|
43
|
+
}
|
|
44
|
+
return 'custom';
|
|
45
|
+
}
|
|
46
|
+
const PARAM_LABELS = {
|
|
47
|
+
radius: 'Radius',
|
|
48
|
+
arc: 'Arc',
|
|
49
|
+
thickness: 'Thickness',
|
|
50
|
+
angle: 'Angle',
|
|
51
|
+
donutRadius: 'Donut radius',
|
|
52
|
+
width: 'Width',
|
|
53
|
+
height: 'Height',
|
|
54
|
+
column: 'Columns',
|
|
55
|
+
row: 'Rows',
|
|
56
|
+
};
|
|
57
|
+
function ColorInput(props) {
|
|
58
|
+
const { value, onChange } = props;
|
|
59
|
+
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) => {
|
|
60
|
+
const rgb = hexToRgb(e.target.value);
|
|
61
|
+
onChange(new Vector4(rgb.r, rgb.g, rgb.b, value.w));
|
|
62
|
+
}, 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))) })] }));
|
|
63
|
+
}
|
|
64
|
+
export function MainModule({ binding }) {
|
|
65
|
+
const system = binding.system;
|
|
66
|
+
const startColor = system.startColor;
|
|
67
|
+
const colorMode = startColor instanceof RandomColor ? 'random' : 'constant';
|
|
68
|
+
const colorA = startColor instanceof RandomColor ? startColor.a
|
|
69
|
+
: startColor instanceof ConstantColor ? startColor.color
|
|
70
|
+
: new Vector4(1, 1, 1, 1);
|
|
71
|
+
const colorB = startColor instanceof RandomColor ? startColor.b : new Vector4(1, 0.5, 0.2, 1);
|
|
72
|
+
const rotation = system.startRotation;
|
|
73
|
+
const isEuler = rotation instanceof EulerGenerator;
|
|
74
|
+
const rotationMode = rotation instanceof RandomQuatGenerator ? '3d'
|
|
75
|
+
: isEuler ? 'euler'
|
|
76
|
+
: readScalar(rotation).mode === 'random' ? 'random'
|
|
77
|
+
: 'angle';
|
|
78
|
+
const rotationState = readScalar(rotation instanceof RandomQuatGenerator || isEuler ? undefined : rotation);
|
|
79
|
+
const eulerAngles = {
|
|
80
|
+
x: isEuler ? readScalar(rotation.angleX).value : 0,
|
|
81
|
+
y: isEuler ? readScalar(rotation.angleY).value : 0,
|
|
82
|
+
z: isEuler ? readScalar(rotation.angleZ).value : 0,
|
|
83
|
+
};
|
|
84
|
+
const setEulerAxis = (axis, v) => binding.apply((s) => {
|
|
85
|
+
const cur = s.startRotation;
|
|
86
|
+
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);
|
|
87
|
+
});
|
|
88
|
+
const startSize = system.startSize;
|
|
89
|
+
const size3D = startSize.type === 'vec3function';
|
|
90
|
+
const colorGenMode = startColor instanceof RandomColorBetweenGradient ? 'gradient2'
|
|
91
|
+
: startColor instanceof Gradient ? 'gradient'
|
|
92
|
+
: colorMode;
|
|
93
|
+
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) => {
|
|
94
|
+
s.startDelay = g;
|
|
95
|
+
s.restart();
|
|
96
|
+
}) }), _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: [
|
|
97
|
+
{ value: 'uniform', label: 'Uniform' },
|
|
98
|
+
{ value: '3d', label: '3D (per axis)' },
|
|
99
|
+
], onChange: (mode) => binding.apply((s) => {
|
|
100
|
+
const cur = s.startSize;
|
|
101
|
+
if (mode === '3d') {
|
|
102
|
+
// Seed all three axes from the current uniform generator.
|
|
103
|
+
s.startSize = new Vector3Function(cur.clone(), cur.clone(), cur.clone());
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
// Collapse back to the X-axis generator.
|
|
107
|
+
s.startSize = s.startSize.x;
|
|
108
|
+
}
|
|
109
|
+
}) }) }), !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) => {
|
|
110
|
+
const cur = s.startSize;
|
|
111
|
+
s.startSize = new Vector3Function(axis === 'x' ? g : cur.x, axis === 'y' ? g : cur.y, axis === 'z' ? g : cur.z);
|
|
112
|
+
}) }, axis))) })), _jsx(Row, { label: "Start color", children: _jsx(SelectField, { value: colorGenMode, options: [
|
|
113
|
+
{ value: 'constant', label: 'Constant' },
|
|
114
|
+
{ value: 'random', label: 'Random between two' },
|
|
115
|
+
{ value: 'gradient', label: 'Gradient' },
|
|
116
|
+
{ value: 'gradient2', label: 'Random between gradients' },
|
|
117
|
+
], onChange: (mode) => binding.apply((s) => {
|
|
118
|
+
if (mode === 'random') {
|
|
119
|
+
s.startColor = new RandomColor(colorA, colorB);
|
|
120
|
+
}
|
|
121
|
+
else if (mode === 'gradient') {
|
|
122
|
+
s.startColor = buildGradient(DEFAULT_GRADIENT_STOPS);
|
|
123
|
+
}
|
|
124
|
+
else if (mode === 'gradient2') {
|
|
125
|
+
s.startColor = new RandomColorBetweenGradient(buildGradient(DEFAULT_GRADIENT_STOPS), buildGradient(DEFAULT_GRADIENT_STOPS));
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
s.startColor = new ConstantColor(colorA);
|
|
129
|
+
}
|
|
130
|
+
}) }) }), (colorGenMode === 'constant' || colorGenMode === 'random') && (_jsx(Row, { label: "", children: _jsx(ColorInput, { value: colorA, onChange: (next) => binding.apply((s) => {
|
|
131
|
+
s.startColor = colorGenMode === 'random' ? new RandomColor(next, colorB) : new ConstantColor(next);
|
|
132
|
+
}) }) })), 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) => {
|
|
133
|
+
const g = s.startColor;
|
|
134
|
+
s.startColor = new RandomColorBetweenGradient(buildGradient(stops), g.gradient2);
|
|
135
|
+
}) }), _jsx(GradientEditor, { stops: readGradientStops(startColor.gradient2), onChange: (stops) => binding.apply((s) => {
|
|
136
|
+
const g = s.startColor;
|
|
137
|
+
s.startColor = new RandomColorBetweenGradient(g.gradient1, buildGradient(stops));
|
|
138
|
+
}) })] })), _jsx(Row, { label: "Start rotation", children: _jsx(SelectField, { value: rotationMode, options: [
|
|
139
|
+
{ value: 'angle', label: 'Angle (rad)' },
|
|
140
|
+
{ value: 'random', label: 'Random angle' },
|
|
141
|
+
{ value: 'euler', label: '3D (per axis)' },
|
|
142
|
+
{ value: '3d', label: 'Random 3D' },
|
|
143
|
+
], onChange: (mode) => binding.apply((s) => {
|
|
144
|
+
s.startRotation =
|
|
145
|
+
mode === '3d'
|
|
146
|
+
? new RandomQuatGenerator()
|
|
147
|
+
: mode === 'euler'
|
|
148
|
+
? new EulerGenerator(new ConstantValue(eulerAngles.x), new ConstantValue(eulerAngles.y), new ConstantValue(eulerAngles.z))
|
|
149
|
+
: mode === 'random'
|
|
150
|
+
? new IntervalValue(0, Math.PI * 2)
|
|
151
|
+
: new ConstantValue(rotationState.value);
|
|
152
|
+
}) }) }), rotationMode === 'euler' && (_jsx(_Fragment, { children: ['x', 'y', 'z'].map((axis) => (_jsx(Row, { label: ` ${axis.toUpperCase()} (rad)`, children: _jsx(NumberField, { value: eulerAngles[axis], step: 0.1, onChange: (v) => setEulerAxis(axis, v) }) }, axis))) })), 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))) })] }) }))] }));
|
|
153
|
+
}
|
|
154
|
+
export function EmissionModule({ binding }) {
|
|
155
|
+
const system = binding.system;
|
|
156
|
+
const bursts = system.emissionBursts ?? [];
|
|
157
|
+
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', fontSize: 13 }, title: "Remove burst", onClick: () => binding.apply((s) => s.emissionBursts.splice(i, 1)), children: "\u2715" })] }), _jsxs("div", { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6, marginTop: 4 }, children: [_jsxs("label", { style: { fontSize: 10.5, color: '#b7c6ea' }, children: ["Time", _jsx(NumberField, { value: burst.time, min: 0, onChange: (time) => binding.apply(() => (burst.time = time)) })] }), _jsxs("label", { style: { fontSize: 10.5, color: '#b7c6ea' }, children: ["Count", _jsx(NumberField, { value: readScalar(burst.count).value, min: 0, step: 1, onChange: (count) => binding.apply(() => (burst.count = new ConstantValue(Math.round(count)))) })] }), _jsxs("label", { style: { fontSize: 10.5, color: '#b7c6ea' }, children: ["Cycles", _jsx(NumberField, { value: burst.cycle, min: 1, step: 1, onChange: (v) => binding.apply(() => (burst.cycle = Math.round(v))) })] }), _jsxs("label", { style: { fontSize: 10.5, color: '#b7c6ea' }, children: ["Interval", _jsx(NumberField, { value: burst.interval, min: 0.01, onChange: (v) => binding.apply(() => (burst.interval = v)) })] }), _jsxs("label", { style: { fontSize: 10.5, color: '#b7c6ea' }, children: ["Probability", _jsx(NumberField, { value: burst.probability, min: 0, step: 0.05, onChange: (v) => binding.apply(() => (burst.probability = Math.min(1, v))) })] })] })] }, i))), _jsx("button", { className: "qe-hover", style: { marginTop: 8, background: 'none', border: '1px dashed #34477f', color: '#9eb9ff', borderRadius: 8, padding: '5px 10px', cursor: 'pointer', fontSize: 12.5 }, onClick: () => binding.apply((s) => s.emissionBursts.push({ time: 0, count: new ConstantValue(20), cycle: 1, interval: 0.01, probability: 1 })), children: "+ Add burst" })] }));
|
|
158
|
+
}
|
|
159
|
+
// Shapes whose constructor doesn't accept `mode`/`spread` at all — showing those controls
|
|
160
|
+
// for them would edit a value the shape silently ignores, just like the old "default to
|
|
161
|
+
// first texture" bug: a control that looks live but does nothing.
|
|
162
|
+
const SHAPES_WITHOUT_MODE_SPREAD = new Set(['point', 'grid', 'mesh_surface']);
|
|
163
|
+
export function ShapeModule({ binding }) {
|
|
164
|
+
const system = binding.system;
|
|
165
|
+
const type = getShapeType(system.emitterShape);
|
|
166
|
+
const params = { ...DEFAULT_SHAPE_PARAMS, ...readShapeParams(system.emitterShape) };
|
|
167
|
+
const keys = SHAPE_PARAM_KEYS[type];
|
|
168
|
+
const scene = system.emitter.getScene();
|
|
169
|
+
// 'vfxBatch' is the renderer's own internal instancing mesh — never a valid emission source.
|
|
170
|
+
const meshOptions = scene.meshes.filter((m) => m instanceof Mesh && m.name !== 'vfxBatch' && m.getTotalVertices() > 0);
|
|
171
|
+
return (_jsxs(ModuleSection, { title: "Shape", children: [_jsx(Row, { label: "Shape", children: _jsx(SelectField, { value: type, options: SHAPE_TYPES, onChange: (next) => binding.apply((s) => (s.emitterShape = createShape(next, params))) }) }), keys.map((key) => (_jsx(Row, { label: PARAM_LABELS[key], children: _jsx(NumberField, { value: params[key], min: 0, onChange: (v) => binding.apply((s) => (s.emitterShape = createShape(type, { ...params, [key]: v }))) }) }, key))), type === 'mesh_surface' && (_jsx(Row, { label: "Source mesh", children: _jsx(SelectField, { value: params.mesh?.uniqueId != null ? String(params.mesh.uniqueId) : '__none', options: [
|
|
172
|
+
{ value: '__none', label: '(none)' },
|
|
173
|
+
...meshOptions.map((m) => ({ value: String(m.uniqueId), label: m.name })),
|
|
174
|
+
], onChange: (id) => {
|
|
175
|
+
const mesh = id === '__none' ? undefined : meshOptions.find((m) => String(m.uniqueId) === id);
|
|
176
|
+
binding.apply((s) => (s.emitterShape = createShape('mesh_surface', { ...params, mesh })));
|
|
177
|
+
} }) })), !SHAPES_WITHOUT_MODE_SPREAD.has(type) && (_jsxs(_Fragment, { children: [_jsx(Row, { label: "Emit mode", children: _jsx(SelectField, { value: params.mode, options: [
|
|
178
|
+
{ value: 0, label: 'Random' },
|
|
179
|
+
{ value: 1, label: 'Loop' },
|
|
180
|
+
{ value: 2, label: 'Ping-pong' },
|
|
181
|
+
{ value: 3, label: 'Burst spread' },
|
|
182
|
+
], 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 }))) }) })] }))] }));
|
|
183
|
+
}
|
|
184
|
+
export function SizeOverLifeModule({ binding }) {
|
|
185
|
+
const system = binding.system;
|
|
186
|
+
const behavior = findBehavior(system.behaviors, 'SizeOverLife');
|
|
187
|
+
const curve = behavior ? readScalar(behavior.size).curve : [0.2, 1, 1, 0.4];
|
|
188
|
+
return (_jsx(ModuleSection, { title: "Size over Lifetime", enabled: !!behavior, onToggle: (enabled) => binding.apply((s) => {
|
|
189
|
+
removeBehavior(s, 'SizeOverLife');
|
|
190
|
+
if (enabled) {
|
|
191
|
+
s.addBehavior(new SizeOverLife(buildScalar({ mode: 'curve', value: 1, min: 0, max: 1, curve })));
|
|
192
|
+
}
|
|
193
|
+
}), children: _jsx("div", { style: { marginTop: 6 }, children: _jsx(CurveEditor, { pieces: behavior ? readPieces(behavior.size) : [{ start: 0, p: curve }], maxValue: 2, onChange: (pieces) => binding.apply((s) => {
|
|
194
|
+
removeBehavior(s, 'SizeOverLife');
|
|
195
|
+
s.addBehavior(new SizeOverLife(buildCurve(pieces)));
|
|
196
|
+
}) }) }) }));
|
|
197
|
+
}
|
|
198
|
+
export function ColorOverLifeModule({ binding }) {
|
|
199
|
+
const system = binding.system;
|
|
200
|
+
const behavior = findBehavior(system.behaviors, 'ColorOverLife');
|
|
201
|
+
const stops = behavior && behavior.color instanceof Gradient ? readGradientStops(behavior.color) : DEFAULT_GRADIENT_STOPS;
|
|
202
|
+
const write = (nextStops) => binding.apply((s) => {
|
|
203
|
+
removeBehavior(s, 'ColorOverLife');
|
|
204
|
+
s.addBehavior(new ColorOverLife(buildGradient(nextStops)));
|
|
205
|
+
});
|
|
206
|
+
return (_jsx(ModuleSection, { title: "Color over Lifetime", enabled: !!behavior, onToggle: (enabled) => binding.apply((s) => {
|
|
207
|
+
removeBehavior(s, 'ColorOverLife');
|
|
208
|
+
if (enabled) {
|
|
209
|
+
s.addBehavior(new ColorOverLife(buildGradient(stops)));
|
|
210
|
+
}
|
|
211
|
+
}), children: _jsx("div", { style: { marginTop: 6 }, children: _jsx(GradientEditor, { stops: stops, onChange: write }) }) }));
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Tiny dependency-free wireframe thumbnail of a mesh geometry. Projects positions with a fixed
|
|
215
|
+
* isometric rotation, scales them to fit, and strokes the triangle edges on a 2D canvas — enough
|
|
216
|
+
* for the user to recognise which mesh they picked without spinning up a second Babylon scene.
|
|
217
|
+
*/
|
|
218
|
+
function MeshPreview({ positions, indices, size = 96 }) {
|
|
219
|
+
const canvasRef = React.useRef(null);
|
|
220
|
+
React.useEffect(() => {
|
|
221
|
+
const canvas = canvasRef.current;
|
|
222
|
+
if (!canvas || positions.length < 9) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const ctx = canvas.getContext('2d');
|
|
226
|
+
if (!ctx) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
const n = positions.length / 3;
|
|
230
|
+
// Fixed iso rotation (yaw ~35°, pitch ~30°) so depth reads clearly.
|
|
231
|
+
const cy = Math.cos(0.6), sy = Math.sin(0.6), cx = Math.cos(0.52), sx = Math.sin(0.52);
|
|
232
|
+
const pts = [];
|
|
233
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
234
|
+
for (let i = 0; i < n; i++) {
|
|
235
|
+
const x = positions[i * 3], y = positions[i * 3 + 1], z = positions[i * 3 + 2];
|
|
236
|
+
const rx = x * cy - z * sy;
|
|
237
|
+
const rz = x * sy + z * cy;
|
|
238
|
+
const ry = y * cx - rz * sx;
|
|
239
|
+
pts.push([rx, ry]);
|
|
240
|
+
if (rx < minX)
|
|
241
|
+
minX = rx;
|
|
242
|
+
if (rx > maxX)
|
|
243
|
+
maxX = rx;
|
|
244
|
+
if (ry < minY)
|
|
245
|
+
minY = ry;
|
|
246
|
+
if (ry > maxY)
|
|
247
|
+
maxY = ry;
|
|
248
|
+
}
|
|
249
|
+
const pad = 8;
|
|
250
|
+
const span = Math.max(maxX - minX, maxY - minY, 1e-6);
|
|
251
|
+
const scale = (size - pad * 2) / span;
|
|
252
|
+
const ox = pad + (size - pad * 2 - (maxX - minX) * scale) / 2;
|
|
253
|
+
const oy = pad + (size - pad * 2 - (maxY - minY) * scale) / 2;
|
|
254
|
+
const px = (i) => ox + (pts[i][0] - minX) * scale;
|
|
255
|
+
// Flip Y so +Y points up on screen.
|
|
256
|
+
const py = (i) => size - (oy + (pts[i][1] - minY) * scale);
|
|
257
|
+
ctx.clearRect(0, 0, size, size);
|
|
258
|
+
ctx.strokeStyle = 'rgba(158,185,255,0.85)';
|
|
259
|
+
ctx.lineWidth = 1;
|
|
260
|
+
const edge = (a, b) => {
|
|
261
|
+
ctx.beginPath();
|
|
262
|
+
ctx.moveTo(px(a), py(a));
|
|
263
|
+
ctx.lineTo(px(b), py(b));
|
|
264
|
+
ctx.stroke();
|
|
265
|
+
};
|
|
266
|
+
const tris = indices && indices.length ? indices.length : n;
|
|
267
|
+
for (let i = 0; i + 2 < tris; i += 3) {
|
|
268
|
+
const a = indices && indices.length ? indices[i] : i;
|
|
269
|
+
const b = indices && indices.length ? indices[i + 1] : i + 1;
|
|
270
|
+
const c = indices && indices.length ? indices[i + 2] : i + 2;
|
|
271
|
+
edge(a, b);
|
|
272
|
+
edge(b, c);
|
|
273
|
+
edge(c, a);
|
|
274
|
+
}
|
|
275
|
+
}, [positions, indices, size]);
|
|
276
|
+
return (_jsx("canvas", { ref: canvasRef, width: size, height: size, style: { width: size, height: size, border: '1px solid #2b3761', borderRadius: 6, background: '#0c1330' } }));
|
|
277
|
+
}
|
|
278
|
+
export function RendererModule({ binding, textureOptions, geometryOptions, resolveTexture, resolveGeometry, }) {
|
|
279
|
+
const system = binding.system;
|
|
280
|
+
const currentTextureUrl = system.texture?.url ?? '';
|
|
281
|
+
const [promptingTextureUrl, setPromptingTextureUrl] = useState(false);
|
|
282
|
+
return (_jsxs(ModuleSection, { title: "Renderer", children: [_jsx(Row, { label: "Render mode", children: _jsx(SelectField, { value: system.renderMode, options: [
|
|
283
|
+
{ value: RenderMode.BillBoard, label: 'Billboard' },
|
|
284
|
+
{ value: RenderMode.StretchedBillBoard, label: 'Stretched billboard' },
|
|
285
|
+
{ value: RenderMode.HorizontalBillBoard, label: 'Horizontal billboard' },
|
|
286
|
+
{ value: RenderMode.VerticalBillBoard, label: 'Vertical billboard' },
|
|
287
|
+
{ value: RenderMode.Trail, label: 'Trail' },
|
|
288
|
+
{ value: RenderMode.Mesh, label: 'Mesh' },
|
|
289
|
+
], 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) => {
|
|
290
|
+
s.rendererEmitterSettings.startLength = new ConstantValue(Math.round(v));
|
|
291
|
+
}) }) }), _jsx(Row, { label: "Follow origin", children: _jsx(CheckboxField, { value: !!system.rendererEmitterSettings.followLocalOrigin, onChange: (v) => binding.apply((s) => {
|
|
292
|
+
s.rendererEmitterSettings.followLocalOrigin = v;
|
|
293
|
+
}) }) })] })), 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)) }) })] })), system.renderMode === RenderMode.Mesh &&
|
|
294
|
+
(() => {
|
|
295
|
+
const preset = detectGeometryPreset(system.instancingGeometry);
|
|
296
|
+
const vertexCount = system.instancingGeometry.length / 3;
|
|
297
|
+
const triangleCount = (system.getRendererSettings().instancingIndices?.length ?? 0) / 3;
|
|
298
|
+
const hostGeometry = geometryOptions ?? [];
|
|
299
|
+
const applyGeometry = (g) => binding.apply((s) => {
|
|
300
|
+
const settings = s.getRendererSettings();
|
|
301
|
+
settings.instancingIndices = (g.indices ? new Uint32Array(g.indices) : undefined);
|
|
302
|
+
settings.instancingUVs = (g.uvs ? new Float32Array(g.uvs) : undefined);
|
|
303
|
+
settings.instancingNormals = (g.normals ? new Float32Array(g.normals) : undefined);
|
|
304
|
+
s.instancingGeometry = new Float32Array(g.positions);
|
|
305
|
+
});
|
|
306
|
+
return (_jsx(Row, { label: "Geometry", children: _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 6 }, children: [_jsx(SelectField, { value: preset, options: [
|
|
307
|
+
...(preset === 'custom' ? [{ value: 'custom', label: 'Custom (imported)' }] : []),
|
|
308
|
+
{ value: 'quad', label: 'Quad' },
|
|
309
|
+
{ value: 'cube', label: 'Cube' },
|
|
310
|
+
...hostGeometry.map((g, i) => ({ value: `host:${i}`, label: g.label })),
|
|
311
|
+
...(resolveGeometry ? [{ value: '__file', label: 'Load from file…' }] : []),
|
|
312
|
+
], onChange: (next) => {
|
|
313
|
+
if (next === 'custom') {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (next === '__file') {
|
|
317
|
+
const input = document.createElement('input');
|
|
318
|
+
input.type = 'file';
|
|
319
|
+
input.accept = '.glb,.gltf,model/gltf-binary,model/gltf+json';
|
|
320
|
+
input.onchange = () => {
|
|
321
|
+
const file = input.files?.[0];
|
|
322
|
+
if (file) {
|
|
323
|
+
resolveGeometry(file).then(applyGeometry);
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
input.click();
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
if (next.startsWith('host:')) {
|
|
330
|
+
applyGeometry(hostGeometry[Number(next.slice(5))]);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
binding.apply((s) => {
|
|
334
|
+
const settings = s.getRendererSettings();
|
|
335
|
+
if (next === 'cube') {
|
|
336
|
+
settings.instancingIndices = MESH_CUBE_INDICES;
|
|
337
|
+
settings.instancingUVs = undefined;
|
|
338
|
+
settings.instancingNormals = undefined;
|
|
339
|
+
s.instancingGeometry = MESH_CUBE_POSITIONS;
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
settings.instancingIndices = MESH_QUAD_INDICES;
|
|
343
|
+
settings.instancingUVs = MESH_QUAD_UVS;
|
|
344
|
+
s.instancingGeometry = MESH_QUAD_POSITIONS;
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
} }), _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"] }))] }) }));
|
|
348
|
+
})(), _jsx("div", { style: {
|
|
349
|
+
marginTop: 10,
|
|
350
|
+
marginBottom: 2,
|
|
351
|
+
fontSize: 11,
|
|
352
|
+
fontWeight: 600,
|
|
353
|
+
color: '#7c8db5',
|
|
354
|
+
textTransform: 'uppercase',
|
|
355
|
+
letterSpacing: 0.4,
|
|
356
|
+
}, children: "Material" }), system.material != null && (_jsxs("div", { style: { fontSize: 10.5, color: '#9eb9ff', marginBottom: 2 }, children: ["Imported material: ", system.material.name ?? 'unnamed', " \u2014 settings below were derived from it"] })), _jsx(Row, { label: "Blend mode", children: _jsx(SelectField, { value: system.blending, options: [
|
|
357
|
+
{ value: 1, label: 'Additive' },
|
|
358
|
+
{ value: 2, label: 'Alpha blend' },
|
|
359
|
+
{ value: 3, label: 'Subtract' },
|
|
360
|
+
{ value: 4, label: 'Multiply' },
|
|
361
|
+
], onChange: (blend) => binding.apply((s) => (s.blending = blend)) }) }), _jsx(Row, { label: "Transparent", children: _jsx(CheckboxField, { value: !!system.getRendererSettings().materialTransparent, onChange: (v) => binding.apply((s) => {
|
|
362
|
+
s.getRendererSettings().materialTransparent = v;
|
|
363
|
+
s.neededToUpdateRender = true;
|
|
364
|
+
}) }) }), resolveTexture && (_jsxs(Row, { label: "Texture", children: [_jsx(SelectField, { value: !currentTextureUrl
|
|
365
|
+
? '__none'
|
|
366
|
+
: (textureOptions ?? []).some((o) => o.url === currentTextureUrl)
|
|
367
|
+
? currentTextureUrl
|
|
368
|
+
: '__current', options: [
|
|
369
|
+
{ value: '__none', label: '(none)' },
|
|
370
|
+
...(currentTextureUrl && !(textureOptions ?? []).some((o) => o.url === currentTextureUrl)
|
|
371
|
+
? [{ value: '__current', label: `(current) ${currentTextureUrl.slice(-24)}` }]
|
|
372
|
+
: []),
|
|
373
|
+
...(textureOptions ?? []).map((o) => ({ value: o.url, label: o.label })),
|
|
374
|
+
{ value: '__url', label: 'Custom URL…' },
|
|
375
|
+
{ value: '__file', label: 'Load from file…' },
|
|
376
|
+
], onChange: (url) => {
|
|
377
|
+
if (url === '__current') {
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
if (url === '__none') {
|
|
381
|
+
binding.apply((s) => (s.texture = null));
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
if (url === '__url') {
|
|
385
|
+
setPromptingTextureUrl(true);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
if (url === '__file') {
|
|
389
|
+
const input = document.createElement('input');
|
|
390
|
+
input.type = 'file';
|
|
391
|
+
input.accept = 'image/*';
|
|
392
|
+
input.onchange = () => {
|
|
393
|
+
const file = input.files?.[0];
|
|
394
|
+
if (!file) {
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
// Read as a data URI so the texture survives Export/Save/reimport;
|
|
398
|
+
// an object URL (blob:) would be dead after a reload.
|
|
399
|
+
const reader = new FileReader();
|
|
400
|
+
reader.onload = () => {
|
|
401
|
+
binding.apply((s) => (s.texture = resolveTexture(reader.result)));
|
|
402
|
+
};
|
|
403
|
+
reader.readAsDataURL(file);
|
|
404
|
+
};
|
|
405
|
+
input.click();
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
binding.apply((s) => (s.texture = resolveTexture(url)));
|
|
409
|
+
} }), promptingTextureUrl && (_jsx(PromptDialog, { title: "Texture URL", defaultValue: currentTextureUrl, placeholder: "https://\u2026", onSubmit: (url) => {
|
|
410
|
+
binding.apply((s) => (s.texture = resolveTexture(url)));
|
|
411
|
+
setPromptingTextureUrl(false);
|
|
412
|
+
}, onCancel: () => setPromptingTextureUrl(false) })), currentTextureUrl && (_jsx("img", { src: currentTextureUrl, alt: "texture preview", style: {
|
|
413
|
+
width: 64,
|
|
414
|
+
height: 64,
|
|
415
|
+
marginTop: 6,
|
|
416
|
+
objectFit: 'contain',
|
|
417
|
+
borderRadius: 6,
|
|
418
|
+
border: '1px solid #2b3761',
|
|
419
|
+
// Checkerboard so texture transparency is visible.
|
|
420
|
+
background: 'repeating-conic-gradient(#2a3252 0% 25%, #171d33 0% 50%) 0 0 / 12px 12px',
|
|
421
|
+
} }))] })), _jsx(Row, { label: "Render order", children: _jsx(NumberField, { value: system.renderOrder, step: 1, onChange: (v) => binding.apply((s) => (s.renderOrder = Math.round(v))) }) }), _jsx(Row, { label: "Soft particles", children: _jsx(CheckboxField, { value: system.softParticles, onChange: (v) => binding.apply((s) => (s.softParticles = v)) }) }), system.softParticles && (_jsx(Row, { label: "Fade near/far", children: _jsxs("div", { style: { display: 'flex', gap: 6 }, children: [_jsx(NumberField, { value: system.softNearFade, min: 0, onChange: (v) => binding.apply((s) => (s.softNearFade = v)) }), _jsx(NumberField, { value: system.softFarFade, min: 0, onChange: (v) => binding.apply((s) => (s.softFarFade = v)) })] }) })), _jsx(Row, { label: "Alpha test", children: _jsx(NumberField, { value: system.getRendererSettings().materialAlphaTest ?? 0, min: 0, step: 0.05, onChange: (v) => binding.apply((s) => {
|
|
422
|
+
s.getRendererSettings().materialAlphaTest = Math.min(1, v);
|
|
423
|
+
s.neededToUpdateRender = true;
|
|
424
|
+
}) }) }), _jsx(Row, { label: "Depth write", children: _jsx(CheckboxField, { value: system.getRendererSettings().materialDepthWrite, onChange: (v) => binding.apply((s) => {
|
|
425
|
+
s.getRendererSettings().materialDepthWrite = v;
|
|
426
|
+
s.neededToUpdateRender = true;
|
|
427
|
+
}) }) }), _jsx(Row, { label: "Depth test", children: _jsx(CheckboxField, { value: system.getRendererSettings().materialDepthTest, onChange: (v) => binding.apply((s) => {
|
|
428
|
+
s.getRendererSettings().materialDepthTest = v;
|
|
429
|
+
s.neededToUpdateRender = true;
|
|
430
|
+
}) }) })] }));
|
|
431
|
+
}
|
|
432
|
+
function removeBehavior(system, type) {
|
|
433
|
+
const behaviors = system.behaviors;
|
|
434
|
+
for (let i = behaviors.length - 1; i >= 0; i--) {
|
|
435
|
+
if (behaviors[i].type === type) {
|
|
436
|
+
behaviors.splice(i, 1);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { CSSProperties } from 'react';
|
|
2
|
+
export declare const theme: {
|
|
3
|
+
panelBg: string;
|
|
4
|
+
sectionBg: string;
|
|
5
|
+
border: string;
|
|
6
|
+
borderActive: string;
|
|
7
|
+
text: string;
|
|
8
|
+
textDim: string;
|
|
9
|
+
accent: string;
|
|
10
|
+
inputBg: string;
|
|
11
|
+
curveStroke: string;
|
|
12
|
+
font: string;
|
|
13
|
+
};
|
|
14
|
+
export declare const inputStyle: CSSProperties;
|
|
15
|
+
export declare const rowStyle: CSSProperties;
|
|
16
|
+
export declare const labelStyle: CSSProperties;
|
|
17
|
+
export declare const buttonStyle: CSSProperties;
|
|
18
|
+
/**
|
|
19
|
+
* Two hover conventions, applied via className (inline `style` can't express `:hover`):
|
|
20
|
+
* - `qe-hover` — brightens elements that already have a solid inline background/border
|
|
21
|
+
* (buttons, icon buttons, bars, gradient/curve handles, module headers).
|
|
22
|
+
* - `qe-hover-bg` — adds a background tint for elements left transparent by default (timeline
|
|
23
|
+
* rows), since brightening transparent does nothing. Must not be combined with an inline
|
|
24
|
+
* `background` on the same element or the tint would need `!important` to show through.
|
|
25
|
+
*/
|
|
26
|
+
export declare const globalEditorStyles: string;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export const theme = {
|
|
2
|
+
panelBg: 'linear-gradient(180deg, rgba(12, 18, 38, 0.92), rgba(6, 10, 22, 0.94))',
|
|
3
|
+
sectionBg: 'rgba(10, 16, 34, 0.6)',
|
|
4
|
+
border: '#2a365f',
|
|
5
|
+
borderActive: '#4f6fbe',
|
|
6
|
+
text: '#eef3ff',
|
|
7
|
+
textDim: '#b7c6ea',
|
|
8
|
+
accent: '#9eb9ff',
|
|
9
|
+
inputBg: 'rgba(7, 11, 22, 0.82)',
|
|
10
|
+
curveStroke: '#78a5ff',
|
|
11
|
+
font: 'Inter, Segoe UI, Roboto, sans-serif',
|
|
12
|
+
};
|
|
13
|
+
export const inputStyle = {
|
|
14
|
+
background: theme.inputBg,
|
|
15
|
+
border: `1px solid ${theme.border}`,
|
|
16
|
+
borderRadius: 8,
|
|
17
|
+
color: theme.text,
|
|
18
|
+
fontSize: 12.5,
|
|
19
|
+
padding: '5px 8px',
|
|
20
|
+
outline: 'none',
|
|
21
|
+
width: '100%',
|
|
22
|
+
boxSizing: 'border-box',
|
|
23
|
+
fontFamily: theme.font,
|
|
24
|
+
};
|
|
25
|
+
export const rowStyle = {
|
|
26
|
+
display: 'grid',
|
|
27
|
+
gridTemplateColumns: '110px 1fr',
|
|
28
|
+
alignItems: 'center',
|
|
29
|
+
gap: 8,
|
|
30
|
+
marginTop: 6,
|
|
31
|
+
};
|
|
32
|
+
export const labelStyle = {
|
|
33
|
+
color: theme.textDim,
|
|
34
|
+
fontSize: 12,
|
|
35
|
+
whiteSpace: 'nowrap',
|
|
36
|
+
overflow: 'hidden',
|
|
37
|
+
textOverflow: 'ellipsis',
|
|
38
|
+
};
|
|
39
|
+
export const buttonStyle = {
|
|
40
|
+
border: `1px solid #4563ad`,
|
|
41
|
+
background: 'rgba(28, 48, 98, 0.55)',
|
|
42
|
+
color: '#d8e3ff',
|
|
43
|
+
padding: '6px 10px',
|
|
44
|
+
borderRadius: 8,
|
|
45
|
+
fontSize: 12.5,
|
|
46
|
+
cursor: 'pointer',
|
|
47
|
+
fontFamily: theme.font,
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Two hover conventions, applied via className (inline `style` can't express `:hover`):
|
|
51
|
+
* - `qe-hover` — brightens elements that already have a solid inline background/border
|
|
52
|
+
* (buttons, icon buttons, bars, gradient/curve handles, module headers).
|
|
53
|
+
* - `qe-hover-bg` — adds a background tint for elements left transparent by default (timeline
|
|
54
|
+
* rows), since brightening transparent does nothing. Must not be combined with an inline
|
|
55
|
+
* `background` on the same element or the tint would need `!important` to show through.
|
|
56
|
+
*/
|
|
57
|
+
export const globalEditorStyles = `
|
|
58
|
+
.qe-hover, .qe-hover-bg {
|
|
59
|
+
transition: filter 0.12s ease, background 0.12s ease;
|
|
60
|
+
}
|
|
61
|
+
.qe-hover:hover:not(:disabled) {
|
|
62
|
+
filter: brightness(1.3);
|
|
63
|
+
}
|
|
64
|
+
.qe-hover:active:not(:disabled) {
|
|
65
|
+
filter: brightness(0.85);
|
|
66
|
+
}
|
|
67
|
+
.qe-hover:disabled {
|
|
68
|
+
opacity: 0.45;
|
|
69
|
+
cursor: default;
|
|
70
|
+
}
|
|
71
|
+
.qe-hover-bg:hover {
|
|
72
|
+
background: rgba(255, 255, 255, 0.07);
|
|
73
|
+
}
|
|
74
|
+
select.qe-hover:hover, input.qe-hover:hover, select.qe-hover:focus, input.qe-hover:focus {
|
|
75
|
+
border-color: ${theme.borderActive};
|
|
76
|
+
filter: none;
|
|
77
|
+
}
|
|
78
|
+
`;
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "babylon.quarks-editor",
|
|
3
|
+
"version": "0.17.7",
|
|
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
|
+
}
|
|
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
|
+
"particles",
|
|
36
|
+
"vfx",
|
|
37
|
+
"editor",
|
|
38
|
+
"react",
|
|
39
|
+
"quarks"
|
|
40
|
+
],
|
|
41
|
+
"author": "soullnik <soullnik96@gmail.com>",
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/Soullnik/babylon.quarks-standalone/issues"
|
|
45
|
+
},
|
|
46
|
+
"homepage": "https://github.com/Soullnik/babylon.quarks-standalone#readme",
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"@babylonjs/core": ">=9.0.0",
|
|
49
|
+
"@babylonjs/materials": ">=9.0.0",
|
|
50
|
+
"babylon.quarks": ">=0.17.0",
|
|
51
|
+
"react": ">=18",
|
|
52
|
+
"react-dom": ">=18"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@babylonjs/materials": "9.6.2",
|
|
56
|
+
"@types/react": "^19.0.0",
|
|
57
|
+
"react": "^19.0.0",
|
|
58
|
+
"rimraf": "^6.0.0",
|
|
59
|
+
"typescript": "^5.9.3",
|
|
60
|
+
"react-dom": "^19.0.0",
|
|
61
|
+
"@types/react-dom": "^19.0.0"
|
|
62
|
+
}
|
|
63
|
+
}
|