babylon.quarks-editor 0.27.0 → 0.29.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 +92 -0
- package/README.md +31 -4
- package/dist/core/assetTree.d.ts +5 -0
- package/dist/core/assets.d.ts +7 -0
- package/dist/core/assets.js +11 -7
- package/dist/core/binding.d.ts +9 -0
- package/dist/core/binding.js +8 -0
- package/dist/core/collision.d.ts +4 -0
- package/dist/core/collision.js +4 -0
- package/dist/core/colors.d.ts +16 -0
- package/dist/core/colors.js +8 -0
- package/dist/core/defaultEffect.d.ts +3 -2
- package/dist/core/defaultEffect.js +3 -1
- package/dist/core/editorSession.d.ts +229 -0
- package/dist/core/editorSession.js +983 -0
- package/dist/core/effectTree.d.ts +5 -0
- package/dist/core/emitterShapeWireframe.js +11 -4
- package/dist/core/galleryPlayback.js +2 -1
- package/dist/core/history.d.ts +6 -0
- package/dist/core/history.js +4 -0
- package/dist/core/indexedDbProjectStore.d.ts +13 -0
- package/dist/core/indexedDbProjectStore.js +13 -0
- package/dist/core/loadEffect.js +2 -0
- package/dist/core/material.d.ts +12 -2
- package/dist/core/playback.d.ts +6 -0
- package/dist/core/projectStore.d.ts +26 -0
- package/dist/core/projectStore.js +1 -0
- package/dist/core/sceneEntries.d.ts +3 -0
- package/dist/core/scrub.d.ts +1 -1
- package/dist/core/scrub.js +2 -2
- package/dist/core/shapes.d.ts +31 -1
- package/dist/core/shapes.js +18 -3
- package/dist/core/systems.d.ts +2 -0
- package/dist/core/systems.js +5 -1
- package/dist/core/timeline.d.ts +23 -3
- package/dist/core/values.d.ts +42 -8
- package/dist/core/values.js +52 -9
- package/dist/index.d.ts +5 -1
- package/dist/react/AssetsPanel.d.ts +7 -0
- package/dist/react/AxesCurveField.d.ts +21 -0
- package/dist/react/AxesCurveField.js +25 -0
- package/dist/react/ChoiceDialog.d.ts +7 -0
- package/dist/react/ContextMenu.d.ts +10 -1
- package/dist/react/EditorWorkspace.d.ts +7 -0
- package/dist/react/EditorWorkspace.js +6 -3
- package/dist/react/EffectEditor.d.ts +8 -2
- package/dist/react/EffectEditorHost.d.ts +32 -28
- package/dist/react/EffectEditorHost.js +241 -1665
- package/dist/react/EffectTreeView.d.ts +3 -0
- package/dist/react/LifeCurveField.d.ts +18 -0
- package/dist/react/LifeCurveField.js +26 -0
- package/dist/react/MessageDialog.d.ts +5 -0
- package/dist/react/ModuleSection.d.ts +10 -0
- package/dist/react/ModuleSection.js +4 -1
- package/dist/react/ProjectsPage.d.ts +6 -0
- package/dist/react/ProjectsPage.js +27 -11
- package/dist/react/PromptDialog.d.ts +7 -0
- package/dist/react/ScenePanel.d.ts +7 -0
- package/dist/react/ScenePanel.js +7 -5
- package/dist/react/TextureSelect.d.ts +5 -5
- package/dist/react/TimelinePanel.d.ts +13 -0
- package/dist/react/TimelinePanel.js +10 -6
- package/dist/react/TimelineTrackRow.d.ts +10 -0
- package/dist/react/TransportBar.d.ts +9 -0
- package/dist/react/ValueField.d.ts +33 -5
- package/dist/react/ValueField.js +34 -9
- package/dist/react/behaviorModules.d.ts +16 -5
- package/dist/react/behaviorModules.js +28 -9
- package/dist/react/fields.d.ts +8 -0
- package/dist/react/fields.js +3 -0
- package/dist/react/host/BottomDocks.d.ts +36 -0
- package/dist/react/host/BottomDocks.js +63 -0
- package/dist/react/host/HostDialogs.d.ts +50 -0
- package/dist/react/host/HostDialogs.js +113 -0
- package/dist/react/host/InspectorSidebar.d.ts +32 -0
- package/dist/react/host/InspectorSidebar.js +59 -0
- package/dist/react/host/SceneSidebar.d.ts +53 -0
- package/dist/react/host/SceneSidebar.js +62 -0
- package/dist/react/host/ViewportPane.d.ts +56 -0
- package/dist/react/host/ViewportPane.js +91 -0
- package/dist/react/host/hostHooks.d.ts +44 -0
- package/dist/react/host/hostHooks.js +164 -0
- package/dist/react/host/hostStyles.d.ts +21 -0
- package/dist/react/host/hostStyles.js +57 -0
- package/dist/react/index.d.ts +3 -1
- package/dist/react/inspectorHints.js +1 -1
- package/dist/react/modules.d.ts +17 -6
- package/dist/react/modules.js +77 -34
- package/dist/react/theme.d.ts +15 -0
- package/dist/react/theme.js +15 -0
- package/dist/session.d.ts +9 -0
- package/dist/session.js +8 -0
- package/package.json +17 -11
- package/src/core/assetTree.ts +14 -1
- package/src/core/assets.ts +25 -10
- package/src/core/binding.ts +9 -0
- package/src/core/collision.ts +4 -0
- package/src/core/colors.ts +21 -1
- package/src/core/defaultEffect.ts +6 -3
- package/src/core/editorSession.ts +1299 -0
- package/src/core/effectTree.ts +5 -0
- package/src/core/emitterShapeWireframe.ts +12 -4
- package/src/core/galleryPlayback.ts +2 -1
- package/src/core/history.ts +6 -0
- package/src/core/indexedDbProjectStore.ts +13 -0
- package/src/core/loadEffect.ts +3 -1
- package/src/core/material.ts +14 -4
- package/src/core/playback.ts +6 -0
- package/src/core/projectStore.ts +26 -0
- package/src/core/sceneEntries.ts +3 -0
- package/src/core/scrub.ts +2 -2
- package/src/core/shapes.ts +48 -3
- package/src/core/systems.ts +11 -2
- package/src/core/timeline.ts +40 -15
- package/src/core/values.ts +91 -14
- package/src/index.ts +5 -1
- package/src/react/AssetsPanel.tsx +7 -0
- package/src/react/AxesCurveField.tsx +72 -0
- package/src/react/ChoiceDialog.tsx +7 -0
- package/src/react/ContextMenu.tsx +10 -1
- package/src/react/EditorWorkspace.tsx +11 -5
- package/src/react/EffectEditor.tsx +7 -1
- package/src/react/EffectEditorHost.tsx +406 -2340
- package/src/react/EffectTreeView.tsx +3 -0
- package/src/react/LifeCurveField.tsx +57 -0
- package/src/react/MessageDialog.tsx +5 -0
- package/src/react/ModuleSection.tsx +11 -0
- package/src/react/ProjectsPage.tsx +38 -11
- package/src/react/PromptDialog.tsx +7 -0
- package/src/react/ScenePanel.tsx +14 -5
- package/src/react/TextureSelect.tsx +6 -6
- package/src/react/TimelinePanel.tsx +23 -6
- package/src/react/TimelineTrackRow.tsx +10 -0
- package/src/react/TransportBar.tsx +9 -0
- package/src/react/ValueField.tsx +99 -15
- package/src/react/behaviorModules.tsx +58 -42
- package/src/react/fields.tsx +12 -1
- package/src/react/host/BottomDocks.tsx +178 -0
- package/src/react/host/HostDialogs.tsx +228 -0
- package/src/react/host/InspectorSidebar.tsx +147 -0
- package/src/react/host/SceneSidebar.tsx +227 -0
- package/src/react/host/ViewportPane.tsx +300 -0
- package/src/react/host/hostHooks.ts +198 -0
- package/src/react/host/hostStyles.ts +65 -0
- package/src/react/index.ts +3 -1
- package/src/react/inspectorHints.ts +1 -1
- package/src/react/modules.tsx +192 -71
- package/src/react/theme.ts +15 -0
- package/src/session.ts +17 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,98 @@ 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.29.0] — 2026-10-24
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- **Renderer module** has an "Image processing" switch beside "Apply fog", for
|
|
12
|
+
`ParticleSystem.applyImageProcessing`.
|
|
13
|
+
|
|
14
|
+
- **Value fields** offer Random Between Two Curves, with a curve editor for each bound. Switching
|
|
15
|
+
between Curve and Random Between Two Curves keeps every segment.
|
|
16
|
+
- **Curve modules** (Size, Speed and Limit Speed over Lifetime, Size by Speed, Width over Trail,
|
|
17
|
+
Texture Sheet frames) have a "Two curves" switch and show both bounds of an imported Random
|
|
18
|
+
Between Two Curves; an edit used to drop the upper one.
|
|
19
|
+
- **Stop action** in Main and **Ring buffer** (with its loop range) beside Max particles, as
|
|
20
|
+
Unity's. The editor saves the stop action but does not apply it, so previews replay.
|
|
21
|
+
- **Separate axes** in Limit Speed over Lifetime (with its Space), Size over Lifetime and Size by
|
|
22
|
+
Speed: a curve for each of X, Y and Z, as Unity's Separate Axes.
|
|
23
|
+
- **Scaling mode** offers Local (the default), Hierarchy and Shape, as Unity's, for world- and
|
|
24
|
+
local-space systems alike.
|
|
25
|
+
- **Shape module:** a cone's Emit from (Base, Volume) and Length, shown for Volume only, and a
|
|
26
|
+
grid's Sampling (Random, Sequential).
|
|
27
|
+
|
|
28
|
+
## [0.28.0] — 2026-10-23
|
|
29
|
+
|
|
30
|
+
### Breaking
|
|
31
|
+
|
|
32
|
+
- **Texture callbacks are typed.** `resolveTexture` returns `Texture | null | undefined` instead
|
|
33
|
+
of `unknown` (on `EffectEditorHost`, `QuarksEffectEditor.Show`, `EffectEditor`,
|
|
34
|
+
`RendererModule` and `createDefaultEffect`). `TextureSelect`'s `loadTexture` and `onChange` use
|
|
35
|
+
`Texture`, and `RendererMaterialPatch` types `texture` as `Texture | null` and
|
|
36
|
+
`reflectionAtlas` as `BaseTexture | null`. A resolver that returns a Babylon `Texture` needs no
|
|
37
|
+
change; one typed to return something else must now return a `Texture`.
|
|
38
|
+
- **`ValueField` has two prop shapes.** `CurveValueFieldProps` (the default, `curve` unset or
|
|
39
|
+
`true`) calls `onChange` with a `ScalarGenerator`; `ConstantValueFieldProps` (`curve: false`)
|
|
40
|
+
calls it with a `ValueGenerator`. Code that passed `curve={false}` and expected a curve back no
|
|
41
|
+
longer type-checks.
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
|
|
45
|
+
- A **Render face** field (Both, Front, Back) for mesh systems, editing the library's new
|
|
46
|
+
`renderFace`.
|
|
47
|
+
- **`createEditorSession`** and the `EditorSession` interface, in a new
|
|
48
|
+
`babylon.quarks-editor/session` entry: the scene, camera, gizmos, history, scene entries, assets,
|
|
49
|
+
prefab mode and project save/load without React. The host passes only the view callbacks it
|
|
50
|
+
needs; none runs after `dispose()`, which also drops a project load or an import still in
|
|
51
|
+
flight. `EffectEditorHost` is now a view over a session. The session has its own entry because
|
|
52
|
+
it builds the viewport (grid, gizmos, markers), which the UI-free `.` entry does not load.
|
|
53
|
+
- **`createEngine`** on `EffectEditorHostProps` (and `Show()`), to draw with a WebGPU or other
|
|
54
|
+
engine instead of the default WebGL `Engine`. It may return a promise, so a `WebGPUEngine` can
|
|
55
|
+
finish `initAsync()` first.
|
|
56
|
+
- Exported types that public signatures already used: `AssetLoadProgress`, `EffectAsset`,
|
|
57
|
+
`AssetDragPayload`, `SceneEntry`, `TimelineRow` (`TimelineGroupRow | TimelineSystemRow`),
|
|
58
|
+
`AnyGenerator`, `ModuleProps`, `ConstantValueFieldProps` and `CurveValueFieldProps`, and
|
|
59
|
+
`EditorProject` from `./react`.
|
|
60
|
+
- TSDoc on every public export. `npm run docs:check` now builds the editor's API docs and fails on
|
|
61
|
+
an undocumented export or a broken link.
|
|
62
|
+
|
|
63
|
+
### Fixed
|
|
64
|
+
|
|
65
|
+
- A mesh from geometry without indices (a preset or a loaded GLB) no longer leaves the system
|
|
66
|
+
with no index buffer: it gets sequential indices, so it draws and exports. A mesh without UVs or
|
|
67
|
+
normals gets the library's fallback ones instead of keeping the previous mesh's.
|
|
68
|
+
- Randomize Direction's angle no longer offers a curve. The angle is sampled once per particle,
|
|
69
|
+
so a curve could not apply, and a curve loaded from JSON shows as its starting value.
|
|
70
|
+
- `EffectEditorHost` under React StrictMode, or remounted, no longer lets the first mount's project
|
|
71
|
+
load fill the live editor with entries from a disposed scene.
|
|
72
|
+
- The transport's play resumes what pause froze and no longer replays effects or bursts that had
|
|
73
|
+
already finished (it uses the library's new `resume()`, now that `play()` replays a finished
|
|
74
|
+
system).
|
|
75
|
+
- A node deleted by an undo while selected hands the selection back to its effect's root on the
|
|
76
|
+
next render. The host only checked when the selection or the session changed, so the inspector
|
|
77
|
+
and timeline could keep showing the deleted node.
|
|
78
|
+
|
|
79
|
+
### Internal
|
|
80
|
+
|
|
81
|
+
- The asset import checks every field it reads from an effect file, with no `any`.
|
|
82
|
+
- The React panels follow the React Compiler's rules (eslint-plugin-react-hooks 7): no ref
|
|
83
|
+
written during render, and state that follows a prop is adjusted during render instead of in an
|
|
84
|
+
effect, which saves a render each time.
|
|
85
|
+
- `EffectEditorHost` is split into a session and panel components under `src/react/host/`, and
|
|
86
|
+
every `as never` cast is gone. The session and the host have their own tests, and the React
|
|
87
|
+
code has a coverage floor.
|
|
88
|
+
|
|
89
|
+
## [0.27.1] — 2026-10-23 (not published)
|
|
90
|
+
|
|
91
|
+
### Changed
|
|
92
|
+
|
|
93
|
+
- **Peer ranges:** `@babylonjs/core` and `@babylonjs/materials` are `^9.0.0` instead of
|
|
94
|
+
`>=9.0.0` (no Babylon 10 exists yet, so no install that worked stops working). `babylon.quarks` stays `~0.27.0`: the editor follows the library's minor line, and
|
|
95
|
+
the release check now enforces it.
|
|
96
|
+
- The `exports` map lists `types` first and exports `./package.json`. The README says the package
|
|
97
|
+
is ES modules only.
|
|
98
|
+
|
|
7
99
|
## [0.27.0] — 2026-10-23
|
|
8
100
|
|
|
9
101
|
A 0.27.0 dated 2026-10-21 was prepared but never published; its entries are folded in here.
|
package/README.md
CHANGED
|
@@ -10,6 +10,9 @@ Shuriken-style effect editor for [babylon.quarks](https://www.npmjs.com/package/
|
|
|
10
10
|
npm install babylon.quarks-editor babylon.quarks @babylonjs/core @babylonjs/materials react react-dom @heroicons/react @tanstack/react-virtual
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
The editor is an ES module package (`import`, not `require`). Its version follows babylon.quarks:
|
|
14
|
+
editor 0.29.x needs babylon.quarks 0.29.x.
|
|
15
|
+
|
|
13
16
|
## Usage
|
|
14
17
|
|
|
15
18
|
The supported embed is the host: it owns the scene, the timeline, undo/redo, playback and the
|
|
@@ -66,8 +69,9 @@ The `.` export is UI-free (edit model, serialization, value/shape/gradient helpe
|
|
|
66
69
|
The editor never loads assets itself — it defers to host-supplied callbacks so it doesn't need
|
|
67
70
|
an opinion on your CDN, asset pipeline, or loader (`@babylonjs/loaders`, your own backend, etc.):
|
|
68
71
|
|
|
69
|
-
- `resolveTexture(url) => Texture
|
|
70
|
-
"Load from file…" in the Renderer module) into
|
|
72
|
+
- `resolveTexture(url) => Texture | null | undefined` — turns a picked URL or `data:` URI (from
|
|
73
|
+
"Custom URL…" / "Load from file…" in the Renderer module) into the Babylon `Texture` assigned as
|
|
74
|
+
`system.texture`. Return `null` or `undefined` to leave the texture unset.
|
|
71
75
|
- `resolveGeometry(file) => Promise<GeometryData>` — turns a picked file (from "Load from file…"
|
|
72
76
|
in the Mesh render mode's geometry dropdown) into vertex buffers: `{positions, indices?, uvs?,
|
|
73
77
|
uvs2?, normals?}` (typed arrays or plain number arrays; `uvs2` is the second UV set textures on
|
|
@@ -80,10 +84,10 @@ uvs2?, normals?}[]` — preset lists shown alongside "Custom URL…" / "Load fro
|
|
|
80
84
|
host" bugs:
|
|
81
85
|
|
|
82
86
|
- `QuarksEffectEditor.Show(...)` / `<EffectEditorHost>` call your callbacks with the live
|
|
83
|
-
`Scene` as a second argument: `resolveTexture: (url, scene) =>
|
|
87
|
+
`Scene` as a second argument: `resolveTexture: (url, scene) => Texture | null | undefined`,
|
|
84
88
|
`resolveGeometry: (file, scene) => Promise<GeometryData>`.
|
|
85
89
|
- The lower-level `<EffectEditor>` (no Host wrapper) does **not** pass a scene:
|
|
86
|
-
`resolveTexture: (url) =>
|
|
90
|
+
`resolveTexture: (url) => Texture | null | undefined`, `resolveGeometry: (file) => Promise<GeometryData>`. If you
|
|
87
91
|
copy a `Show()`-style resolver that reads its `scene` parameter into a raw `<EffectEditor>`,
|
|
88
92
|
`scene` is `undefined` and your resolver throws.
|
|
89
93
|
|
|
@@ -112,6 +116,29 @@ resolveGeometry: async (file, scene) => {
|
|
|
112
116
|
},
|
|
113
117
|
```
|
|
114
118
|
|
|
119
|
+
## Custom engines and headless use
|
|
120
|
+
|
|
121
|
+
`<EffectEditorHost>` creates a WebGL `Engine` on its canvas. Pass `createEngine` to build another
|
|
122
|
+
engine on that canvas instead, or a `NullEngine` in tests. It may return a promise:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
createEngine: async (canvas) => {
|
|
126
|
+
const engine = new WebGPUEngine(canvas);
|
|
127
|
+
await engine.initAsync();
|
|
128
|
+
return engine;
|
|
129
|
+
},
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Build a new engine on every call: the host disposes it when it unmounts, and React StrictMode
|
|
133
|
+
mounts the host twice.
|
|
134
|
+
|
|
135
|
+
Everything the host does besides drawing its panels lives in an `EditorSession`, from the
|
|
136
|
+
`babylon.quarks-editor/session` entry: `createEditorSession({engine, playback, view, ...})`. The
|
|
137
|
+
session owns the scene, camera, gizmos, history, scene entries, assets, prefab mode and project
|
|
138
|
+
save/load, and reports changes through the `view` callbacks you pass. Use it to drive the editor
|
|
139
|
+
from your own UI, or to script and test editing without React. It builds the viewport (grid,
|
|
140
|
+
gizmos, markers), which is why it is not part of the lighter UI-free `.` entry.
|
|
141
|
+
|
|
115
142
|
## License
|
|
116
143
|
|
|
117
144
|
MIT
|
package/dist/core/assetTree.d.ts
CHANGED
|
@@ -19,11 +19,16 @@ export declare function folderChildren(tree: AssetTreeNode[], path: string): Ass
|
|
|
19
19
|
/** Assets whose name or path contains `query`, across every folder, sorted by name. */
|
|
20
20
|
export declare function searchAssets(assets: EffectAsset[], query: string): EffectAsset[];
|
|
21
21
|
export declare const ASSET_DRAG_MIME = "application/x-quarks-asset-drop";
|
|
22
|
+
/** What is dragged out of the Assets panel: one asset, or every asset under a folder. */
|
|
22
23
|
export type AssetDragPayload = {
|
|
24
|
+
/** One asset. */
|
|
23
25
|
kind: 'asset';
|
|
26
|
+
/** The asset's id. */
|
|
24
27
|
id: number;
|
|
25
28
|
} | {
|
|
29
|
+
/** A folder. */
|
|
26
30
|
kind: 'folder';
|
|
31
|
+
/** The folder's path; every asset under it is dragged. */
|
|
27
32
|
path: string;
|
|
28
33
|
};
|
|
29
34
|
export declare function writeAssetDrag(dataTransfer: DataTransfer, payload: AssetDragPayload): void;
|
package/dist/core/assets.d.ts
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
* fresh, independent copy (see `instantiateSceneEntry`).
|
|
5
5
|
*/
|
|
6
6
|
export interface EffectAsset {
|
|
7
|
+
/** The asset's id in this session; scene entries made from it keep it. */
|
|
7
8
|
id: number;
|
|
9
|
+
/** The asset's display name, its file name without `.json`. */
|
|
8
10
|
name: string;
|
|
9
11
|
/** Relative path inside the imported folder (preserves subfolders). */
|
|
10
12
|
path: string;
|
|
@@ -13,10 +15,15 @@ export interface EffectAsset {
|
|
|
13
15
|
/** Texture/geometry files the effect references that were not among the imported files. */
|
|
14
16
|
missingFiles: string[];
|
|
15
17
|
}
|
|
18
|
+
/** How far an asset import got. */
|
|
16
19
|
export interface AssetLoadProgress {
|
|
20
|
+
/** JSON files read so far. */
|
|
17
21
|
done: number;
|
|
22
|
+
/** JSON files in the import. */
|
|
18
23
|
total: number;
|
|
24
|
+
/** Effects found so far. */
|
|
19
25
|
loaded: number;
|
|
26
|
+
/** Files that could not be read. */
|
|
20
27
|
failed: number;
|
|
21
28
|
}
|
|
22
29
|
export declare function relativePathOf(file: File): string;
|
package/dist/core/assets.js
CHANGED
|
@@ -42,6 +42,9 @@ function readAsDataUrl(file) {
|
|
|
42
42
|
reader.readAsDataURL(file);
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
|
+
function isRecord(value) {
|
|
46
|
+
return value !== null && typeof value === 'object';
|
|
47
|
+
}
|
|
45
48
|
/** True for a Quarks effect envelope with at least one particle emitter. */
|
|
46
49
|
export function isEffectJson(json) {
|
|
47
50
|
const object = json?.object;
|
|
@@ -206,11 +209,11 @@ export function assetIconUrl(asset) {
|
|
|
206
209
|
if (cached !== undefined) {
|
|
207
210
|
return cached;
|
|
208
211
|
}
|
|
209
|
-
const json = asset.json;
|
|
212
|
+
const json = isRecord(asset.json) ? asset.json : {};
|
|
210
213
|
const findMaterial = (node) => {
|
|
211
|
-
if (!node
|
|
214
|
+
if (!isRecord(node))
|
|
212
215
|
return undefined;
|
|
213
|
-
if (node.type === 'ParticleEmitter' && node.ps
|
|
216
|
+
if (node.type === 'ParticleEmitter' && isRecord(node.ps) && node.ps.material)
|
|
214
217
|
return node.ps.material;
|
|
215
218
|
for (const child of Array.isArray(node.children) ? node.children : []) {
|
|
216
219
|
const found = findMaterial(child);
|
|
@@ -219,10 +222,11 @@ export function assetIconUrl(asset) {
|
|
|
219
222
|
}
|
|
220
223
|
return undefined;
|
|
221
224
|
};
|
|
222
|
-
const byUuid = (list, uuid) => (Array.isArray(list) ? list : []).find((item) => item
|
|
223
|
-
const material = byUuid(json
|
|
224
|
-
const texture = byUuid(json
|
|
225
|
-
const
|
|
225
|
+
const byUuid = (list, uuid) => (Array.isArray(list) ? list : []).find((item) => isRecord(item) && item.uuid === uuid);
|
|
226
|
+
const material = byUuid(json.materials, findMaterial(json.object));
|
|
227
|
+
const texture = byUuid(json.textures, material?.map ?? material?.texture);
|
|
228
|
+
const firstImage = Array.isArray(json.images) ? json.images[0] : undefined;
|
|
229
|
+
const image = byUuid(json.images, texture?.image) ?? (isRecord(firstImage) ? firstImage : undefined);
|
|
226
230
|
const url = typeof image?.url === 'string' && image.url ? image.url : null;
|
|
227
231
|
iconCache.set(asset, url);
|
|
228
232
|
return url;
|
package/dist/core/binding.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { TransformNode } from '@babylonjs/core/Meshes/transformNode.js';
|
|
2
2
|
import type { ParticleSystem } from 'babylon.quarks';
|
|
3
3
|
import type { EffectTreeNode } from './effectTree.js';
|
|
4
|
+
/** Called after every change made through an {@link EffectBinding}. */
|
|
4
5
|
export type EditorListener = () => void;
|
|
5
6
|
/**
|
|
6
7
|
* Headless edit-model over a live ParticleSystem: mutate through apply() so every
|
|
@@ -20,11 +21,18 @@ export declare class EffectBinding {
|
|
|
20
21
|
constructor(system: ParticleSystem, subSystems?: ParticleSystem[], root?: TransformNode);
|
|
21
22
|
/** Editor hierarchy tree (groups + emitters) rooted at this effect's scene root. */
|
|
22
23
|
getTree(): EffectTreeNode;
|
|
24
|
+
/** Calls `listener` after every change; returns the call that stops it. */
|
|
23
25
|
subscribe(listener: EditorListener): () => void;
|
|
24
26
|
/** Monotonic change counter, usable as a React external-store snapshot. */
|
|
25
27
|
getRevision(): number;
|
|
28
|
+
/**
|
|
29
|
+
* Changes the main system through `mutate`: takes an undo snapshot first, then notifies
|
|
30
|
+
* subscribers. Every inspector edit goes through here.
|
|
31
|
+
*/
|
|
26
32
|
apply(mutate: (system: ParticleSystem) => void): void;
|
|
33
|
+
/** Adds a system to the effect's sub systems. */
|
|
27
34
|
addSubSystem(sub: ParticleSystem): void;
|
|
35
|
+
/** Removes a system from the effect's sub systems; a system it does not have is ignored. */
|
|
28
36
|
removeSubSystem(sub: ParticleSystem): void;
|
|
29
37
|
/**
|
|
30
38
|
* Swaps which system is "main" — used when the current `system` is about to be removed
|
|
@@ -39,6 +47,7 @@ export declare class EffectBinding {
|
|
|
39
47
|
*/
|
|
40
48
|
markChanged(): void;
|
|
41
49
|
private notify;
|
|
50
|
+
/** Restarts every system of the effect from time 0. */
|
|
42
51
|
restart(): void;
|
|
43
52
|
/**
|
|
44
53
|
* Serializes the effect into the Quarks JSON envelope understood by QuarksLoader,
|
package/dist/core/binding.js
CHANGED
|
@@ -17,6 +17,7 @@ export class EffectBinding {
|
|
|
17
17
|
getTree() {
|
|
18
18
|
return buildEffectTree(this.root);
|
|
19
19
|
}
|
|
20
|
+
/** Calls `listener` after every change; returns the call that stops it. */
|
|
20
21
|
subscribe(listener) {
|
|
21
22
|
this.listeners.add(listener);
|
|
22
23
|
return () => this.listeners.delete(listener);
|
|
@@ -25,15 +26,21 @@ export class EffectBinding {
|
|
|
25
26
|
getRevision() {
|
|
26
27
|
return this.revision;
|
|
27
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Changes the main system through `mutate`: takes an undo snapshot first, then notifies
|
|
31
|
+
* subscribers. Every inspector edit goes through here.
|
|
32
|
+
*/
|
|
28
33
|
apply(mutate) {
|
|
29
34
|
this.onBeforeChange?.();
|
|
30
35
|
mutate(this.system);
|
|
31
36
|
this.notify();
|
|
32
37
|
}
|
|
38
|
+
/** Adds a system to the effect's sub systems. */
|
|
33
39
|
addSubSystem(sub) {
|
|
34
40
|
this.subSystems.push(sub);
|
|
35
41
|
this.notify();
|
|
36
42
|
}
|
|
43
|
+
/** Removes a system from the effect's sub systems; a system it does not have is ignored. */
|
|
37
44
|
removeSubSystem(sub) {
|
|
38
45
|
const index = this.subSystems.indexOf(sub);
|
|
39
46
|
if (index >= 0) {
|
|
@@ -69,6 +76,7 @@ export class EffectBinding {
|
|
|
69
76
|
listener();
|
|
70
77
|
}
|
|
71
78
|
}
|
|
79
|
+
/** Restarts every system of the effect from time 0. */
|
|
72
80
|
restart() {
|
|
73
81
|
// onlyUsedByOther systems skip self-emission in update(); play() only unpauses sim.
|
|
74
82
|
for (const system of [this.system, ...this.subSystems]) {
|
package/dist/core/collision.d.ts
CHANGED
|
@@ -8,7 +8,11 @@ import { Vector3 } from 'babylon.quarks';
|
|
|
8
8
|
*/
|
|
9
9
|
export declare class GroundPlaneResolver {
|
|
10
10
|
y: number;
|
|
11
|
+
/** @param y - Height of the ground plane. */
|
|
11
12
|
constructor(y?: number);
|
|
13
|
+
/**
|
|
14
|
+
* True when `position` is at or below the plane, with the plane's normal written to `normal`.
|
|
15
|
+
*/
|
|
12
16
|
resolve(position: Vector3, normal: Vector3): boolean;
|
|
13
17
|
}
|
|
14
18
|
/**
|
package/dist/core/collision.js
CHANGED
|
@@ -7,9 +7,13 @@ import { getPhysicsResolver, setPhysicsResolver } from 'babylon.quarks';
|
|
|
7
7
|
* which is exactly what ApplyCollision.update reflects the velocity against.
|
|
8
8
|
*/
|
|
9
9
|
export class GroundPlaneResolver {
|
|
10
|
+
/** @param y - Height of the ground plane. */
|
|
10
11
|
constructor(y = 0) {
|
|
11
12
|
this.y = y;
|
|
12
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* True when `position` is at or below the plane, with the plane's normal written to `normal`.
|
|
16
|
+
*/
|
|
13
17
|
resolve(position, normal) {
|
|
14
18
|
if (position.y <= this.y) {
|
|
15
19
|
normal.set(0, 1, 0);
|
package/dist/core/colors.d.ts
CHANGED
|
@@ -2,28 +2,44 @@ import type { Behavior } from 'babylon.quarks';
|
|
|
2
2
|
import { ColorOverLife, Gradient, Vector4 } from 'babylon.quarks';
|
|
3
3
|
/** One RGBA stop of the editable gradient (all channels 0..1, pos 0..1). */
|
|
4
4
|
export interface GradientStop {
|
|
5
|
+
/** Where the stop sits over the particle's life, 0..1. */
|
|
5
6
|
pos: number;
|
|
7
|
+
/** Red, 0..1. */
|
|
6
8
|
r: number;
|
|
9
|
+
/** Green, 0..1. */
|
|
7
10
|
g: number;
|
|
11
|
+
/** Blue, 0..1. */
|
|
8
12
|
b: number;
|
|
13
|
+
/** Alpha, 0..1. */
|
|
9
14
|
a: number;
|
|
10
15
|
}
|
|
16
|
+
/** The gradient a new Color over Lifetime starts with: warm yellow fading out through red. */
|
|
11
17
|
export declare const DEFAULT_GRADIENT_STOPS: GradientStop[];
|
|
18
|
+
/** A quarks gradient through `stops`, sorted by position; colour and alpha share the stops. */
|
|
12
19
|
export declare function buildGradient(stops: GradientStop[]): Gradient;
|
|
20
|
+
/** A Color over Lifetime behavior through `stops`. */
|
|
13
21
|
export declare function buildColorOverLife(stops: GradientStop[]): ColorOverLife;
|
|
22
|
+
/** An RGBA colour as a quarks `Vector4`. */
|
|
14
23
|
export declare function rgbaToVector4(stop: {
|
|
15
24
|
r: number;
|
|
16
25
|
g: number;
|
|
17
26
|
b: number;
|
|
18
27
|
a: number;
|
|
19
28
|
}): Vector4;
|
|
29
|
+
/** A stop's colour as CSS `rgba()`. */
|
|
20
30
|
export declare function stopToCss(stop: GradientStop): string;
|
|
31
|
+
/** The stops as a left-to-right CSS `linear-gradient`, for a gradient preview. */
|
|
21
32
|
export declare function stopsToCssGradient(stops: GradientStop[]): string;
|
|
33
|
+
/** A `#rrggbb` colour as channels 0..1. */
|
|
22
34
|
export declare function hexToRgb(hex: string): {
|
|
35
|
+
/** Red, 0..1. */
|
|
23
36
|
r: number;
|
|
37
|
+
/** Green, 0..1. */
|
|
24
38
|
g: number;
|
|
39
|
+
/** Blue, 0..1. */
|
|
25
40
|
b: number;
|
|
26
41
|
};
|
|
42
|
+
/** Channels 0..1, clamped, as a `#rrggbb` colour. */
|
|
27
43
|
export declare function rgbToHex(r: number, g: number, b: number): string;
|
|
28
44
|
/** Reads RGBA stops back from a Gradient (union of color/alpha key positions). */
|
|
29
45
|
export declare function readGradientStops(gradient: Gradient): GradientStop[];
|
package/dist/core/colors.js
CHANGED
|
@@ -1,27 +1,34 @@
|
|
|
1
1
|
import { ColorOverLife, Gradient, Vector3, Vector4 } from 'babylon.quarks';
|
|
2
|
+
/** The gradient a new Color over Lifetime starts with: warm yellow fading out through red. */
|
|
2
3
|
export const DEFAULT_GRADIENT_STOPS = [
|
|
3
4
|
{ pos: 0, r: 1, g: 0.85, b: 0.4, a: 1 },
|
|
4
5
|
{ pos: 1, r: 1, g: 0.25, b: 0.05, a: 0 },
|
|
5
6
|
];
|
|
7
|
+
/** A quarks gradient through `stops`, sorted by position; colour and alpha share the stops. */
|
|
6
8
|
export function buildGradient(stops) {
|
|
7
9
|
const sorted = [...stops].sort((a, b) => a.pos - b.pos);
|
|
8
10
|
return new Gradient(sorted.map((s) => [new Vector3(s.r, s.g, s.b), s.pos]), sorted.map((s) => [s.a, s.pos]));
|
|
9
11
|
}
|
|
12
|
+
/** A Color over Lifetime behavior through `stops`. */
|
|
10
13
|
export function buildColorOverLife(stops) {
|
|
11
14
|
return new ColorOverLife(buildGradient(stops));
|
|
12
15
|
}
|
|
16
|
+
/** An RGBA colour as a quarks `Vector4`. */
|
|
13
17
|
export function rgbaToVector4(stop) {
|
|
14
18
|
return new Vector4(stop.r, stop.g, stop.b, stop.a);
|
|
15
19
|
}
|
|
20
|
+
/** A stop's colour as CSS `rgba()`. */
|
|
16
21
|
export function stopToCss(stop) {
|
|
17
22
|
const c = (v) => Math.round(v * 255);
|
|
18
23
|
return `rgba(${c(stop.r)}, ${c(stop.g)}, ${c(stop.b)}, ${stop.a.toFixed(2)})`;
|
|
19
24
|
}
|
|
25
|
+
/** The stops as a left-to-right CSS `linear-gradient`, for a gradient preview. */
|
|
20
26
|
export function stopsToCssGradient(stops) {
|
|
21
27
|
const sorted = [...stops].sort((a, b) => a.pos - b.pos);
|
|
22
28
|
const parts = sorted.map((s) => `${stopToCss(s)} ${(s.pos * 100).toFixed(1)}%`);
|
|
23
29
|
return `linear-gradient(90deg, ${parts.join(', ')})`;
|
|
24
30
|
}
|
|
31
|
+
/** A `#rrggbb` colour as channels 0..1. */
|
|
25
32
|
export function hexToRgb(hex) {
|
|
26
33
|
const value = hex.replace('#', '');
|
|
27
34
|
return {
|
|
@@ -30,6 +37,7 @@ export function hexToRgb(hex) {
|
|
|
30
37
|
b: parseInt(value.slice(4, 6), 16) / 255,
|
|
31
38
|
};
|
|
32
39
|
}
|
|
40
|
+
/** Channels 0..1, clamped, as a `#rrggbb` colour. */
|
|
33
41
|
export function rgbToHex(r, g, b) {
|
|
34
42
|
const c = (v) => Math.round(Math.min(1, Math.max(0, v)) * 255)
|
|
35
43
|
.toString(16)
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
import type { Texture } from '@babylonjs/core/Materials/Textures/texture.js';
|
|
1
2
|
import type { Scene } from '@babylonjs/core/scene.js';
|
|
2
3
|
import { ParticleSystem } from 'babylon.quarks';
|
|
3
4
|
import { type SceneEntry } from './sceneEntries.js';
|
|
4
5
|
export interface CreateSceneDefaultOptions {
|
|
5
|
-
resolveTexture?: (url: string, scene: Scene) =>
|
|
6
|
+
resolveTexture?: (url: string, scene: Scene) => Texture | null | undefined;
|
|
6
7
|
textureUrl?: string;
|
|
7
8
|
}
|
|
8
9
|
/** Creates a simple cone burst preset for new editor effects. */
|
|
9
|
-
export declare function createDefaultEffect(scene: Scene, resolveTexture?: (url: string, scene: Scene) =>
|
|
10
|
+
export declare function createDefaultEffect(scene: Scene, resolveTexture?: (url: string, scene: Scene) => Texture | null | undefined, textureUrl?: string): ParticleSystem;
|
|
10
11
|
/** Creates a preset effect as a new scene entry (systems not registered with the renderer yet). */
|
|
11
12
|
export declare function createSceneDefaultEntry(scene: Scene, existing: SceneEntry[], options?: CreateSceneDefaultOptions): SceneEntry;
|
|
@@ -15,10 +15,12 @@ export function createDefaultEffect(scene, resolveTexture, textureUrl) {
|
|
|
15
15
|
emissionOverTime: new ConstantValue(80),
|
|
16
16
|
shape: new ConeEmitter({ radius: 0.3, angle: 0.25 }),
|
|
17
17
|
renderMode: RenderMode.BillBoard,
|
|
18
|
-
texture:
|
|
18
|
+
texture: textureUrl && resolveTexture ? resolveTexture(textureUrl, scene) : undefined,
|
|
19
19
|
transparent: true,
|
|
20
20
|
blendMode: 1,
|
|
21
21
|
});
|
|
22
|
+
// The editor previews over and over; the stop action is saved, not applied.
|
|
23
|
+
system.ignoreStopAction = true;
|
|
22
24
|
system.addBehavior(new ColorOverLife(buildGradient(DEFAULT_GRADIENT_STOPS)));
|
|
23
25
|
system.emitter.rotation.x = -Math.PI / 2;
|
|
24
26
|
return system;
|