babylon.quarks-editor 0.21.0 → 0.23.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 +21 -0
- package/dist/react/EditorWorkspace.js +1 -1
- package/dist/react/ProjectsPage.d.ts +5 -0
- package/dist/react/ProjectsPage.js +16 -3
- package/dist/react/behaviorModules.js +2 -2
- package/dist/react/modules.js +18 -0
- package/package.json +1 -1
- package/src/react/EditorWorkspace.tsx +1 -0
- package/src/react/ProjectsPage.tsx +27 -2
- package/src/react/behaviorModules.tsx +17 -8
- package/src/react/modules.tsx +76 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,27 @@ All notable changes to the `babylon.quarks-editor` package are documented here.
|
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
5
5
|
and the project follows the `quarks.core` 0.x version line.
|
|
6
6
|
|
|
7
|
+
## [0.23.0] — 2026-10-18
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- The projects page shows the host's `backLink` (the same one the editor viewport shows), so a
|
|
12
|
+
workspace embedded in a site can be left for the hosting page without the browser's back button.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Version aligned with babylon.quarks 0.23.0, whose simulation now runs on store columns; the
|
|
17
|
+
editor's preview and gallery get the same speed-up with no editor changes.
|
|
18
|
+
|
|
19
|
+
## [0.22.0] — 2026-10-17
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- Renderer module: a "Lighting" selector for mesh particles (`fixed` / `unlit` / `scene`) and
|
|
24
|
+
rim (Fresnel) controls — colour, power, strength, add or mix.
|
|
25
|
+
- Width over Trail module: an "Absolute width" checkbox for `WidthOverLength`'s `absolute`
|
|
26
|
+
flag (the curve as world-unit widths, the pre-0.22 reading).
|
|
27
|
+
|
|
7
28
|
## [0.21.0] — 2026-10-16
|
|
8
29
|
|
|
9
30
|
### Changed
|
|
@@ -42,7 +42,7 @@ export function EditorWorkspace(props) {
|
|
|
42
42
|
}, children: "Opening project\u2026" }));
|
|
43
43
|
}
|
|
44
44
|
if (!project) {
|
|
45
|
-
return (_jsx(ProjectsPage, { store: store, title: hostProps.title, onOpen: (meta) => {
|
|
45
|
+
return (_jsx(ProjectsPage, { store: store, title: hostProps.title, backLink: hostProps.backLink, onOpen: (meta) => {
|
|
46
46
|
setProject(meta);
|
|
47
47
|
onProjectChange?.(meta.id);
|
|
48
48
|
} }));
|
|
@@ -4,6 +4,11 @@ export interface ProjectsPageProps {
|
|
|
4
4
|
store: ProjectStore;
|
|
5
5
|
onOpen: (project: ProjectMeta) => void;
|
|
6
6
|
title?: string;
|
|
7
|
+
/** Optional link back to the page that hosts the editor (e.g. a gallery), shown above the title. */
|
|
8
|
+
backLink?: {
|
|
9
|
+
href: string;
|
|
10
|
+
label: string;
|
|
11
|
+
};
|
|
7
12
|
}
|
|
8
13
|
/** Start page: the projects saved in the store, plus storage usage. */
|
|
9
14
|
export declare function ProjectsPage(props: ProjectsPageProps): React.ReactElement;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { FolderIcon, PlusIcon } from '@heroicons/react/24/solid';
|
|
2
|
+
import { ArrowLeftIcon, FolderIcon, PlusIcon } from '@heroicons/react/24/solid';
|
|
3
3
|
import { useCallback, useEffect, useState } from 'react';
|
|
4
4
|
import { formatBytes } from '../core/projectStore';
|
|
5
5
|
import { ChoiceDialog } from './ChoiceDialog';
|
|
@@ -31,7 +31,7 @@ function StorageBar(props) {
|
|
|
31
31
|
}
|
|
32
32
|
/** Start page: the projects saved in the store, plus storage usage. */
|
|
33
33
|
export function ProjectsPage(props) {
|
|
34
|
-
const { store, onOpen, title = 'Effect editor' } = props;
|
|
34
|
+
const { store, onOpen, title = 'Effect editor', backLink } = props;
|
|
35
35
|
const [projects, setProjects] = useState(null);
|
|
36
36
|
const [estimate, setEstimate] = useState(null);
|
|
37
37
|
const [dialog, setDialog] = useState(null);
|
|
@@ -84,7 +84,20 @@ export function ProjectsPage(props) {
|
|
|
84
84
|
display: 'flex',
|
|
85
85
|
flexDirection: 'column',
|
|
86
86
|
gap: 18,
|
|
87
|
-
}, children: [
|
|
87
|
+
}, children: [backLink && (_jsxs("a", { className: "qe-hover", href: backLink.href, style: {
|
|
88
|
+
alignSelf: 'flex-start',
|
|
89
|
+
display: 'inline-flex',
|
|
90
|
+
alignItems: 'center',
|
|
91
|
+
gap: 6,
|
|
92
|
+
background: theme.panelBg,
|
|
93
|
+
border: `1px solid ${theme.border}`,
|
|
94
|
+
borderRadius: 8,
|
|
95
|
+
padding: '6px 12px',
|
|
96
|
+
fontSize: 13,
|
|
97
|
+
fontWeight: 500,
|
|
98
|
+
color: theme.text,
|
|
99
|
+
textDecoration: 'none',
|
|
100
|
+
}, children: [_jsx(ArrowLeftIcon, { style: iconStyle(14) }), backLink.label.replace(/^←\s*/, '')] })), _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 12 }, children: [_jsxs("div", { children: [_jsx("div", { style: { fontSize: 22, fontWeight: 700 }, children: title }), _jsx("div", { style: { fontSize: 13, color: theme.textDim, marginTop: 4 }, children: "Projects keep their assets and scene between sessions." })] }), _jsx("span", { style: { flex: 1 } }), _jsxs("button", { className: "qe-hover", style: {
|
|
88
101
|
...buttonStyle,
|
|
89
102
|
display: 'inline-flex',
|
|
90
103
|
alignItems: 'center',
|
|
@@ -3,7 +3,7 @@ import { ApplyCollision, ApplyForce, ChangeEmitDirection, ColorBySpeed, Constant
|
|
|
3
3
|
import { buildGradient, DEFAULT_GRADIENT_STOPS, findBehavior, readGradientStops } from '../core/colors';
|
|
4
4
|
import { buildCurve, buildScalar, readPieces, readScalar } from '../core/values';
|
|
5
5
|
import { CurveEditor } from './CurveEditor';
|
|
6
|
-
import { NumberField, Row, SelectField } from './fields';
|
|
6
|
+
import { CheckboxField, NumberField, Row, SelectField } from './fields';
|
|
7
7
|
import { GradientEditor } from './GradientEditor';
|
|
8
8
|
import { ModuleSection } from './ModuleSection';
|
|
9
9
|
import { ValueField } from './ValueField';
|
|
@@ -93,7 +93,7 @@ export function OrbitOverLifeModule({ binding }) {
|
|
|
93
93
|
return (_jsx(BehaviorModule, { binding: binding, title: "Orbit over Lifetime", type: "OrbitOverLife", create: () => new OrbitOverLife(new ConstantValue(Math.PI), new Vector3(0, 1, 0)), children: (behavior) => (_jsx(ValueField, { label: "Orbit speed", generator: behavior.orbitSpeed, curveMax: Math.PI * 4, onChange: (g) => binding.apply(() => (behavior.orbitSpeed = g)) })) }));
|
|
94
94
|
}
|
|
95
95
|
export function WidthOverTrailModule({ binding }) {
|
|
96
|
-
return (_jsx(BehaviorModule, { binding: binding, title: "Width over Trail", type: "WidthOverLength", create: () => new WidthOverLength(buildScalar({ mode: 'curve', value: 1, min: 0, max: 1, curve: [1, 0.75, 0.4, 0] })), children: (behavior) => (_jsx("div", { style: { marginTop: 6 }, children: _jsx(CurveEditor, { pieces: readPieces(behavior.width), maxValue: 2, onChange: (pieces) => binding.apply(() => (behavior.width = buildCurve(pieces))) }) })) }));
|
|
96
|
+
return (_jsx(BehaviorModule, { binding: binding, title: "Width over Trail", type: "WidthOverLength", create: () => new WidthOverLength(buildScalar({ mode: 'curve', value: 1, min: 0, max: 1, curve: [1, 0.75, 0.4, 0] })), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx("div", { style: { marginTop: 6 }, children: _jsx(CurveEditor, { pieces: readPieces(behavior.width), maxValue: 2, onChange: (pieces) => binding.apply(() => (behavior.width = buildCurve(pieces))) }) }), _jsx(Row, { label: "Absolute width", children: _jsx(CheckboxField, { value: behavior.absolute, onChange: (v) => binding.apply(() => (behavior.absolute = v)) }) })] })) }));
|
|
97
97
|
}
|
|
98
98
|
export function TextureSheetModule({ binding }) {
|
|
99
99
|
const system = binding.system;
|
package/dist/react/modules.js
CHANGED
|
@@ -485,6 +485,24 @@ export function RendererModule({ binding, textureOptions, geometryOptions, resol
|
|
|
485
485
|
}
|
|
486
486
|
});
|
|
487
487
|
} }), _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"] }))] }) }));
|
|
488
|
+
})(), system.renderMode === RenderMode.Mesh && (_jsx(Row, { label: "Lighting", children: _jsx(SelectField, { value: system.lighting, options: [
|
|
489
|
+
{ value: 'fixed', label: 'Fixed light' },
|
|
490
|
+
{ value: 'scene', label: 'Scene light' },
|
|
491
|
+
{ value: 'unlit', label: 'Unlit' },
|
|
492
|
+
], onChange: (v) => binding.apply((s) => (s.lighting = v)) }) })), system.renderMode === RenderMode.Mesh && (_jsx(Row, { label: "Rim", children: _jsx(CheckboxField, { value: !!system.rim, onChange: (v) => binding.apply((s) => (s.rim = v ? {} : undefined)) }) })), system.renderMode === RenderMode.Mesh &&
|
|
493
|
+
system.rim &&
|
|
494
|
+
(() => {
|
|
495
|
+
const rim = system.rim;
|
|
496
|
+
const patchRim = (patch) => binding.apply((s) => (s.rim = { ...s.rim, ...patch }));
|
|
497
|
+
const setChannel = (channel, value) => {
|
|
498
|
+
const color = [...rim.color];
|
|
499
|
+
color[channel] = value;
|
|
500
|
+
patchRim({ color });
|
|
501
|
+
};
|
|
502
|
+
return (_jsxs(_Fragment, { children: [_jsx(Row, { label: "Rim mode", children: _jsx(SelectField, { value: rim.mode, options: [
|
|
503
|
+
{ value: 'add', label: 'Add (glow)' },
|
|
504
|
+
{ value: 'mix', label: 'Mix (outline)' },
|
|
505
|
+
], onChange: (mode) => patchRim({ mode }) }) }), _jsx(Row, { label: "Rim color", children: _jsx("div", { style: { display: 'flex', gap: 6 }, children: [0, 1, 2].map((channel) => (_jsx(NumberField, { value: rim.color[channel], min: 0, step: 0.05, onChange: (v) => setChannel(channel, v) }, channel))) }) }), _jsx(Row, { label: "Rim power", children: _jsx(NumberField, { value: rim.power, min: 0.01, step: 0.25, onChange: (power) => patchRim({ power }) }) }), _jsx(Row, { label: "Rim strength", children: _jsx(NumberField, { value: rim.strength, min: 0, step: 0.05, onChange: (strength) => patchRim({ strength }) }) })] }));
|
|
488
506
|
})(), _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)) })] }) }))] }));
|
|
489
507
|
}
|
|
490
508
|
function removeBehavior(system, type) {
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {FolderIcon, PlusIcon} from '@heroicons/react/24/solid';
|
|
1
|
+
import {ArrowLeftIcon, FolderIcon, PlusIcon} from '@heroicons/react/24/solid';
|
|
2
2
|
import React, {useCallback, useEffect, useState} from 'react';
|
|
3
3
|
import {formatBytes, type ProjectMeta, type ProjectStore, type StorageEstimate} from '../core/projectStore';
|
|
4
4
|
import {ChoiceDialog} from './ChoiceDialog';
|
|
@@ -11,6 +11,8 @@ export interface ProjectsPageProps {
|
|
|
11
11
|
store: ProjectStore;
|
|
12
12
|
onOpen: (project: ProjectMeta) => void;
|
|
13
13
|
title?: string;
|
|
14
|
+
/** Optional link back to the page that hosts the editor (e.g. a gallery), shown above the title. */
|
|
15
|
+
backLink?: {href: string; label: string};
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
type Dialog =
|
|
@@ -79,7 +81,7 @@ function StorageBar(props: {estimate: StorageEstimate | null; onPersist: () => v
|
|
|
79
81
|
|
|
80
82
|
/** Start page: the projects saved in the store, plus storage usage. */
|
|
81
83
|
export function ProjectsPage(props: ProjectsPageProps): React.ReactElement {
|
|
82
|
-
const {store, onOpen, title = 'Effect editor'} = props;
|
|
84
|
+
const {store, onOpen, title = 'Effect editor', backLink} = props;
|
|
83
85
|
const [projects, setProjects] = useState<ProjectMeta[] | null>(null);
|
|
84
86
|
const [estimate, setEstimate] = useState<StorageEstimate | null>(null);
|
|
85
87
|
const [dialog, setDialog] = useState<Dialog>(null);
|
|
@@ -145,6 +147,29 @@ export function ProjectsPage(props: ProjectsPageProps): React.ReactElement {
|
|
|
145
147
|
gap: 18,
|
|
146
148
|
}}
|
|
147
149
|
>
|
|
150
|
+
{backLink && (
|
|
151
|
+
<a
|
|
152
|
+
className="qe-hover"
|
|
153
|
+
href={backLink.href}
|
|
154
|
+
style={{
|
|
155
|
+
alignSelf: 'flex-start',
|
|
156
|
+
display: 'inline-flex',
|
|
157
|
+
alignItems: 'center',
|
|
158
|
+
gap: 6,
|
|
159
|
+
background: theme.panelBg,
|
|
160
|
+
border: `1px solid ${theme.border}`,
|
|
161
|
+
borderRadius: 8,
|
|
162
|
+
padding: '6px 12px',
|
|
163
|
+
fontSize: 13,
|
|
164
|
+
fontWeight: 500,
|
|
165
|
+
color: theme.text,
|
|
166
|
+
textDecoration: 'none',
|
|
167
|
+
}}
|
|
168
|
+
>
|
|
169
|
+
<ArrowLeftIcon style={iconStyle(14)} />
|
|
170
|
+
{backLink.label.replace(/^←\s*/, '')}
|
|
171
|
+
</a>
|
|
172
|
+
)}
|
|
148
173
|
<div style={{display: 'flex', alignItems: 'center', gap: 12}}>
|
|
149
174
|
<div>
|
|
150
175
|
<div style={{fontSize: 22, fontWeight: 700}}>{title}</div>
|
|
@@ -28,7 +28,7 @@ import {EffectBinding} from '../core/binding';
|
|
|
28
28
|
import {buildGradient, DEFAULT_GRADIENT_STOPS, findBehavior, readGradientStops} from '../core/colors';
|
|
29
29
|
import {buildCurve, buildScalar, readPieces, readScalar} from '../core/values';
|
|
30
30
|
import {CurveEditor} from './CurveEditor';
|
|
31
|
-
import {NumberField, Row, SelectField} from './fields';
|
|
31
|
+
import {CheckboxField, NumberField, Row, SelectField} from './fields';
|
|
32
32
|
import {GradientEditor} from './GradientEditor';
|
|
33
33
|
import {ModuleSection} from './ModuleSection';
|
|
34
34
|
import {ValueField} from './ValueField';
|
|
@@ -614,13 +614,22 @@ export function WidthOverTrailModule({binding}: ModuleProps) {
|
|
|
614
614
|
}
|
|
615
615
|
>
|
|
616
616
|
{(behavior) => (
|
|
617
|
-
|
|
618
|
-
<
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
617
|
+
<>
|
|
618
|
+
<div style={{marginTop: 6}}>
|
|
619
|
+
<CurveEditor
|
|
620
|
+
pieces={readPieces(behavior.width as never)}
|
|
621
|
+
maxValue={2}
|
|
622
|
+
onChange={(pieces) => binding.apply(() => (behavior.width = buildCurve(pieces) as never))}
|
|
623
|
+
/>
|
|
624
|
+
</div>
|
|
625
|
+
{/* Off: the curve multiplies the particle size. On: it is the width in world units (files from 0.21 and earlier). */}
|
|
626
|
+
<Row label="Absolute width">
|
|
627
|
+
<CheckboxField
|
|
628
|
+
value={behavior.absolute}
|
|
629
|
+
onChange={(v) => binding.apply(() => (behavior.absolute = v))}
|
|
630
|
+
/>
|
|
631
|
+
</Row>
|
|
632
|
+
</>
|
|
624
633
|
)}
|
|
625
634
|
</BehaviorModule>
|
|
626
635
|
);
|
package/src/react/modules.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {Texture} from '@babylonjs/core/Materials/Textures/texture';
|
|
2
2
|
import {Mesh} from '@babylonjs/core/Meshes/mesh';
|
|
3
3
|
import {PlusIcon, XMarkIcon} from '@heroicons/react/24/solid';
|
|
4
|
-
import type {Behavior, ParticleSystem} from 'babylon.quarks';
|
|
4
|
+
import type {Behavior, MeshLighting, MeshRim, ParticleSystem} from 'babylon.quarks';
|
|
5
5
|
import {
|
|
6
6
|
ColorOverLife,
|
|
7
7
|
ConstantColor,
|
|
@@ -1122,6 +1122,81 @@ export function RendererModule({
|
|
|
1122
1122
|
</Row>
|
|
1123
1123
|
);
|
|
1124
1124
|
})()}
|
|
1125
|
+
{system.renderMode === RenderMode.Mesh && (
|
|
1126
|
+
<Row label="Lighting">
|
|
1127
|
+
<SelectField<MeshLighting>
|
|
1128
|
+
value={system.lighting}
|
|
1129
|
+
options={[
|
|
1130
|
+
{value: 'fixed', label: 'Fixed light'},
|
|
1131
|
+
{value: 'scene', label: 'Scene light'},
|
|
1132
|
+
{value: 'unlit', label: 'Unlit'},
|
|
1133
|
+
]}
|
|
1134
|
+
onChange={(v) => binding.apply((s) => (s.lighting = v))}
|
|
1135
|
+
/>
|
|
1136
|
+
</Row>
|
|
1137
|
+
)}
|
|
1138
|
+
{system.renderMode === RenderMode.Mesh && (
|
|
1139
|
+
<Row label="Rim">
|
|
1140
|
+
<CheckboxField
|
|
1141
|
+
value={!!system.rim}
|
|
1142
|
+
onChange={(v) => binding.apply((s) => (s.rim = v ? {} : undefined))}
|
|
1143
|
+
/>
|
|
1144
|
+
</Row>
|
|
1145
|
+
)}
|
|
1146
|
+
{system.renderMode === RenderMode.Mesh &&
|
|
1147
|
+
system.rim &&
|
|
1148
|
+
(() => {
|
|
1149
|
+
const rim = system.rim;
|
|
1150
|
+
const patchRim = (patch: Partial<MeshRim>) => binding.apply((s) => (s.rim = {...s.rim, ...patch}));
|
|
1151
|
+
const setChannel = (channel: number, value: number) => {
|
|
1152
|
+
const color: [number, number, number] = [...rim.color];
|
|
1153
|
+
color[channel] = value;
|
|
1154
|
+
patchRim({color});
|
|
1155
|
+
};
|
|
1156
|
+
return (
|
|
1157
|
+
<>
|
|
1158
|
+
<Row label="Rim mode">
|
|
1159
|
+
<SelectField<MeshRim['mode']>
|
|
1160
|
+
value={rim.mode}
|
|
1161
|
+
options={[
|
|
1162
|
+
{value: 'add', label: 'Add (glow)'},
|
|
1163
|
+
{value: 'mix', label: 'Mix (outline)'},
|
|
1164
|
+
]}
|
|
1165
|
+
onChange={(mode) => patchRim({mode})}
|
|
1166
|
+
/>
|
|
1167
|
+
</Row>
|
|
1168
|
+
<Row label="Rim color">
|
|
1169
|
+
<div style={{display: 'flex', gap: 6}}>
|
|
1170
|
+
{[0, 1, 2].map((channel) => (
|
|
1171
|
+
<NumberField
|
|
1172
|
+
key={channel}
|
|
1173
|
+
value={rim.color[channel]}
|
|
1174
|
+
min={0}
|
|
1175
|
+
step={0.05}
|
|
1176
|
+
onChange={(v) => setChannel(channel, v)}
|
|
1177
|
+
/>
|
|
1178
|
+
))}
|
|
1179
|
+
</div>
|
|
1180
|
+
</Row>
|
|
1181
|
+
<Row label="Rim power">
|
|
1182
|
+
<NumberField
|
|
1183
|
+
value={rim.power}
|
|
1184
|
+
min={0.01}
|
|
1185
|
+
step={0.25}
|
|
1186
|
+
onChange={(power) => patchRim({power})}
|
|
1187
|
+
/>
|
|
1188
|
+
</Row>
|
|
1189
|
+
<Row label="Rim strength">
|
|
1190
|
+
<NumberField
|
|
1191
|
+
value={rim.strength}
|
|
1192
|
+
min={0}
|
|
1193
|
+
step={0.05}
|
|
1194
|
+
onChange={(strength) => patchRim({strength})}
|
|
1195
|
+
/>
|
|
1196
|
+
</Row>
|
|
1197
|
+
</>
|
|
1198
|
+
);
|
|
1199
|
+
})()}
|
|
1125
1200
|
<Row label="Render order">
|
|
1126
1201
|
<NumberField
|
|
1127
1202
|
value={system.renderOrder}
|