babylon.quarks-editor 0.19.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/dist/core/assetDragSession.d.ts +4 -0
- package/dist/core/assetDragSession.js +8 -0
- package/dist/core/assetTree.d.ts +39 -0
- package/dist/core/assetTree.js +112 -0
- package/dist/core/assets.d.ts +56 -0
- package/dist/core/assets.js +207 -0
- package/dist/core/binding.d.ts +5 -0
- package/dist/core/binding.js +7 -0
- package/dist/core/collision.d.ts +4 -5
- package/dist/core/collision.js +4 -5
- package/dist/core/defaultEffect.d.ts +4 -5
- package/dist/core/defaultEffect.js +5 -14
- package/dist/core/emitterShapeWireframe.d.ts +1 -0
- package/dist/core/emitterShapeWireframe.js +1 -0
- package/dist/core/galleryPlayback.d.ts +7 -7
- package/dist/core/galleryPlayback.js +2 -2
- package/dist/core/galleryScene.d.ts +8 -13
- package/dist/core/galleryScene.js +14 -42
- package/dist/core/gallerySelectionMarker.d.ts +1 -0
- package/dist/core/gallerySelectionMarker.js +1 -0
- package/dist/core/history.d.ts +12 -1
- package/dist/core/history.js +16 -3
- package/dist/core/indexedDbProjectStore.d.ts +35 -0
- package/dist/core/indexedDbProjectStore.js +299 -0
- package/dist/core/lineShaders.d.ts +2 -0
- package/dist/core/lineShaders.js +7 -0
- package/dist/core/pickAssetFiles.d.ts +6 -0
- package/dist/core/{pickGalleryFiles.js → pickAssetFiles.js} +13 -8
- package/dist/core/projectStore.d.ts +61 -0
- package/dist/core/projectStore.js +14 -0
- package/dist/core/sceneEntries.d.ts +35 -0
- package/dist/core/sceneEntries.js +62 -0
- package/dist/core/transport.d.ts +12 -0
- package/dist/core/transport.js +36 -0
- package/dist/core/viewportPicker.d.ts +4 -4
- package/dist/core/viewportPicker.js +3 -3
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -0
- package/dist/react/AssetsPanel.d.ts +22 -0
- package/dist/react/AssetsPanel.js +252 -0
- package/dist/react/ChoiceDialog.d.ts +18 -0
- package/dist/react/ChoiceDialog.js +48 -0
- package/dist/react/ContextMenu.d.ts +16 -0
- package/dist/react/ContextMenu.js +70 -0
- package/dist/react/EditorWorkspace.d.ts +21 -0
- package/dist/react/EditorWorkspace.js +63 -0
- package/dist/react/EffectEditor.d.ts +1 -1
- package/dist/react/EffectEditor.js +7 -4
- package/dist/react/EffectEditorHost.d.ts +19 -2
- package/dist/react/EffectEditorHost.js +1385 -607
- package/dist/react/EffectTreeView.d.ts +17 -0
- package/dist/react/EffectTreeView.js +52 -0
- package/dist/react/ObjectModule.d.ts +4 -0
- package/dist/react/ObjectModule.js +7 -10
- package/dist/react/ProjectsPage.d.ts +9 -0
- package/dist/react/ProjectsPage.js +140 -0
- package/dist/react/ScenePanel.d.ts +26 -0
- package/dist/react/ScenePanel.js +134 -0
- package/dist/react/TimelinePanel.d.ts +8 -1
- package/dist/react/TimelinePanel.js +20 -41
- package/dist/react/TransportBar.d.ts +24 -0
- package/dist/react/TransportBar.js +43 -0
- package/dist/react/behaviorModules.js +5 -7
- package/dist/react/index.d.ts +16 -2
- package/dist/react/index.js +8 -1
- package/dist/react/inspectorHints.js +1 -2
- package/package.json +1 -1
- package/src/core/assetDragSession.ts +12 -0
- package/src/core/assetTree.ts +148 -0
- package/src/core/assets.ts +262 -0
- package/src/core/binding.ts +8 -0
- package/src/core/collision.ts +4 -5
- package/src/core/defaultEffect.ts +9 -22
- package/src/core/emitterShapeWireframe.ts +1 -0
- package/src/core/galleryPlayback.ts +9 -9
- package/src/core/galleryScene.ts +15 -59
- package/src/core/gallerySelectionMarker.ts +1 -0
- package/src/core/history.ts +24 -3
- package/src/core/indexedDbProjectStore.ts +357 -0
- package/src/core/lineShaders.ts +7 -0
- package/src/core/{pickGalleryFiles.ts → pickAssetFiles.ts} +13 -8
- package/src/core/projectStore.ts +81 -0
- package/src/core/sceneEntries.ts +96 -0
- package/src/core/transport.ts +53 -0
- package/src/core/viewportPicker.ts +5 -5
- package/src/index.ts +11 -0
- package/src/react/AssetsPanel.tsx +559 -0
- package/src/react/ChoiceDialog.tsx +93 -0
- package/src/react/ContextMenu.tsx +107 -0
- package/src/react/EditorWorkspace.tsx +95 -0
- package/src/react/EffectEditor.tsx +15 -4
- package/src/react/EffectEditorHost.tsx +1922 -813
- package/src/react/EffectTreeView.tsx +117 -0
- package/src/react/ObjectModule.tsx +17 -13
- package/src/react/ProjectsPage.tsx +306 -0
- package/src/react/ScenePanel.tsx +318 -0
- package/src/react/TimelinePanel.tsx +102 -103
- package/src/react/TransportBar.tsx +114 -0
- package/src/react/behaviorModules.tsx +7 -15
- package/src/react/index.ts +16 -2
- package/src/react/inspectorHints.ts +1 -2
- package/dist/core/galleryDragSession.d.ts +0 -4
- package/dist/core/galleryDragSession.js +0 -8
- package/dist/core/galleryTree.d.ts +0 -49
- package/dist/core/galleryTree.js +0 -141
- package/dist/core/loadEffectGallery.d.ts +0 -36
- package/dist/core/loadEffectGallery.js +0 -97
- package/dist/core/pickGalleryFiles.d.ts +0 -5
- package/dist/react/GalleryPanel.d.ts +0 -20
- package/dist/react/GalleryPanel.js +0 -253
- package/src/core/galleryDragSession.ts +0 -12
- package/src/core/galleryTree.ts +0 -190
- package/src/core/loadEffectGallery.ts +0 -150
- package/src/react/GalleryPanel.tsx +0 -561
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/** One editor project: a library of effect assets plus the scene built from them. */
|
|
2
|
+
export interface ProjectMeta {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
createdAt: number;
|
|
6
|
+
updatedAt: number;
|
|
7
|
+
assetCount: number;
|
|
8
|
+
/** Approximate bytes the project takes in storage (JSON + texture files). */
|
|
9
|
+
sizeBytes: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** An effect asset as the store keeps it. `json` is a self-contained Quarks effect. */
|
|
13
|
+
export interface StoredAsset {
|
|
14
|
+
/** Relative path inside the project (`Fire/Explosion.json`) — the asset's key. */
|
|
15
|
+
path: string;
|
|
16
|
+
name: string;
|
|
17
|
+
json: unknown;
|
|
18
|
+
missingFiles: string[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** One effect instance in a saved scene. */
|
|
22
|
+
export interface StoredSceneEntry {
|
|
23
|
+
name: string;
|
|
24
|
+
/** Path of the asset it was made from, if any. */
|
|
25
|
+
assetPath: string | null;
|
|
26
|
+
/** The instance's own (possibly edited) effect, root transform included. */
|
|
27
|
+
json: unknown;
|
|
28
|
+
visible: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface StoredScene {
|
|
32
|
+
entries: StoredSceneEntry[];
|
|
33
|
+
savedAt: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface StorageEstimate {
|
|
37
|
+
/** Bytes the whole site uses (all projects), when the browser reports it. */
|
|
38
|
+
usage: number | null;
|
|
39
|
+
/** Bytes the browser lets the site use, when it reports it. */
|
|
40
|
+
quota: number | null;
|
|
41
|
+
/** True when the browser promised not to evict the data under storage pressure. */
|
|
42
|
+
persisted: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Where projects live. The editor only talks to this interface; `IndexedDbProjectStore` keeps
|
|
47
|
+
* everything in the browser, and an embedding host can supply its own (e.g. on disk).
|
|
48
|
+
*/
|
|
49
|
+
export interface ProjectStore {
|
|
50
|
+
listProjects(): Promise<ProjectMeta[]>;
|
|
51
|
+
getProject(id: string): Promise<ProjectMeta | null>;
|
|
52
|
+
createProject(name: string): Promise<ProjectMeta>;
|
|
53
|
+
renameProject(id: string, name: string): Promise<void>;
|
|
54
|
+
duplicateProject(id: string, name: string): Promise<ProjectMeta>;
|
|
55
|
+
deleteProject(id: string): Promise<void>;
|
|
56
|
+
|
|
57
|
+
loadAssets(projectId: string): Promise<StoredAsset[]>;
|
|
58
|
+
/** Adds or replaces assets by path. */
|
|
59
|
+
putAssets(projectId: string, assets: StoredAsset[]): Promise<void>;
|
|
60
|
+
deleteAssets(projectId: string, paths: string[]): Promise<void>;
|
|
61
|
+
|
|
62
|
+
loadScene(projectId: string): Promise<StoredScene | null>;
|
|
63
|
+
saveScene(projectId: string, scene: StoredScene): Promise<void>;
|
|
64
|
+
|
|
65
|
+
estimate(): Promise<StorageEstimate>;
|
|
66
|
+
/** Asks the browser to keep the data even under storage pressure; resolves to the result. */
|
|
67
|
+
requestPersistence(): Promise<boolean>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function formatBytes(bytes: number | null | undefined): string {
|
|
71
|
+
if (bytes == null) return '—';
|
|
72
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
73
|
+
const units = ['KB', 'MB', 'GB', 'TB'];
|
|
74
|
+
let value = bytes / 1024;
|
|
75
|
+
let unit = 0;
|
|
76
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
77
|
+
value /= 1024;
|
|
78
|
+
unit++;
|
|
79
|
+
}
|
|
80
|
+
return `${value < 10 ? value.toFixed(1) : Math.round(value)} ${units[unit]}`;
|
|
81
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type {TransformNode} from '@babylonjs/core/Meshes/transformNode';
|
|
2
|
+
import type {Scene} from '@babylonjs/core/scene';
|
|
3
|
+
import type {BatchedRenderer, ParticleEmitter, ParticleSystem} from 'babylon.quarks';
|
|
4
|
+
import {QuarksUtil} from 'babylon.quarks';
|
|
5
|
+
import type {EffectAsset} from './assets';
|
|
6
|
+
import {EffectBinding} from './binding';
|
|
7
|
+
import {disposeLoadedEffect, parseEffectFromJson} from './loadEffect';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* One effect instance placed in the scene. Every instance is live (its systems are registered
|
|
11
|
+
* with the renderer) and independent: dropping the same asset three times gives three entries
|
|
12
|
+
* that are edited separately.
|
|
13
|
+
*/
|
|
14
|
+
export interface SceneEntry {
|
|
15
|
+
name: string;
|
|
16
|
+
/** Path of the asset it came from, or a synthetic `_created/…` path for new effects. */
|
|
17
|
+
path: string;
|
|
18
|
+
/** Asset this instance was made from, if any. */
|
|
19
|
+
assetId: number | null;
|
|
20
|
+
root: TransformNode;
|
|
21
|
+
systems: ParticleSystem[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** `name`, or `name (2)`, `name (3)`… when an entry already uses it. */
|
|
25
|
+
export function uniqueEntryName(name: string, existing: SceneEntry[]): string {
|
|
26
|
+
const taken = new Set(existing.map((entry) => entry.name));
|
|
27
|
+
if (!taken.has(name)) {
|
|
28
|
+
return name;
|
|
29
|
+
}
|
|
30
|
+
let n = 2;
|
|
31
|
+
while (taken.has(`${name} (${n})`)) {
|
|
32
|
+
n++;
|
|
33
|
+
}
|
|
34
|
+
return `${name} (${n})`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function isPlaceholderName(name: string): boolean {
|
|
38
|
+
return !name || name === 'Effect' || name === 'Object3D' || name === 'Group';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Parses an asset into a new scene instance with its systems registered (paused). Returns null
|
|
43
|
+
* when the JSON holds no particle systems.
|
|
44
|
+
*/
|
|
45
|
+
export function instantiateSceneEntry(
|
|
46
|
+
scene: Scene,
|
|
47
|
+
renderer: BatchedRenderer,
|
|
48
|
+
asset: EffectAsset,
|
|
49
|
+
existing: SceneEntry[]
|
|
50
|
+
): SceneEntry | null {
|
|
51
|
+
const {root, systems} = parseEffectFromJson(scene, renderer, asset.json, {
|
|
52
|
+
registerRenderer: true,
|
|
53
|
+
autoplay: false,
|
|
54
|
+
});
|
|
55
|
+
if (systems.length === 0) {
|
|
56
|
+
disposeLoadedEffect(root, systems, renderer, true);
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
const name = uniqueEntryName(isPlaceholderName(root.name) ? asset.name : root.name, existing);
|
|
60
|
+
root.name = name;
|
|
61
|
+
root.parent = null;
|
|
62
|
+
return {name, path: asset.path, assetId: asset.id, root, systems};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Builds an edit binding for one scene entry. */
|
|
66
|
+
export function bindingFromSceneEntry(entry: SceneEntry): EffectBinding {
|
|
67
|
+
const main = entry.systems.find((s) => !s.onlyUsedByOther) ?? entry.systems[0];
|
|
68
|
+
return new EffectBinding(
|
|
69
|
+
main,
|
|
70
|
+
entry.systems.filter((s) => s !== main),
|
|
71
|
+
entry.root
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Unregisters and disposes one scene entry — including systems the inspector added after the
|
|
77
|
+
* entry was created, which only the live emitter tree knows about.
|
|
78
|
+
*/
|
|
79
|
+
export function disposeSceneEntry(entry: SceneEntry, renderer: BatchedRenderer): void {
|
|
80
|
+
const systems = new Set<ParticleSystem>(entry.systems);
|
|
81
|
+
QuarksUtil.runOnAllParticleEmitters(entry.root, (emitter: ParticleEmitter) => {
|
|
82
|
+
systems.add(emitter.system as ParticleSystem);
|
|
83
|
+
});
|
|
84
|
+
disposeLoadedEffect(entry.root, [...systems], renderer, true);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Number of scene instances per asset id. */
|
|
88
|
+
export function countInstancesByAsset(entries: SceneEntry[]): Map<number, number> {
|
|
89
|
+
const counts = new Map<number, number>();
|
|
90
|
+
for (const entry of entries) {
|
|
91
|
+
if (entry.assetId != null) {
|
|
92
|
+
counts.set(entry.assetId, (counts.get(entry.assetId) ?? 0) + 1);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return counts;
|
|
96
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type {Scene} from '@babylonjs/core/scene';
|
|
2
|
+
import type {BatchedRenderer} from 'babylon.quarks';
|
|
3
|
+
import type {EffectBinding} from './binding';
|
|
4
|
+
import {clearFocusFinished, isFocusFinished, setFocusElapsed, type PlaybackState} from './playback';
|
|
5
|
+
import {playFocus, scrubFocusTo} from './scrub';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Stage Play/Pause shared by the viewport transport bar and the timeline. Pressing Play on a
|
|
9
|
+
* one-shot focus that already parked at its end replays it from the start.
|
|
10
|
+
*/
|
|
11
|
+
export function toggleStagePlayback(
|
|
12
|
+
binding: EffectBinding | null,
|
|
13
|
+
scene: Scene,
|
|
14
|
+
playback: PlaybackState,
|
|
15
|
+
stagePlaying: boolean,
|
|
16
|
+
setStagePlaying: (playing: boolean) => void
|
|
17
|
+
): void {
|
|
18
|
+
if (stagePlaying) {
|
|
19
|
+
playback.stagePlaying = false;
|
|
20
|
+
setStagePlaying(false);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (binding) {
|
|
24
|
+
const rootId = binding.root.uniqueId;
|
|
25
|
+
if (isFocusFinished(playback, rootId)) {
|
|
26
|
+
binding.restart();
|
|
27
|
+
clearFocusFinished(playback, rootId);
|
|
28
|
+
setFocusElapsed(playback, rootId, 0);
|
|
29
|
+
scene.render();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
playback.stagePlaying = true;
|
|
33
|
+
setStagePlaying(true);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Restarts the focused effect only; other visible effects keep playing. */
|
|
37
|
+
export function restartFocusPlayback(
|
|
38
|
+
binding: EffectBinding,
|
|
39
|
+
renderer: BatchedRenderer,
|
|
40
|
+
scene: Scene,
|
|
41
|
+
playback: PlaybackState
|
|
42
|
+
): void {
|
|
43
|
+
const rootId = binding.root.uniqueId;
|
|
44
|
+
scrubFocusTo(binding, renderer, 0);
|
|
45
|
+
setFocusElapsed(playback, rootId, 0);
|
|
46
|
+
clearFocusFinished(playback, rootId);
|
|
47
|
+
if (playback.stagePlaying) {
|
|
48
|
+
playFocus(binding);
|
|
49
|
+
}
|
|
50
|
+
scene.render();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const PLAYBACK_SPEEDS = [0.1, 0.25, 0.5, 1, 2, 4];
|
|
@@ -4,7 +4,7 @@ import {TransformNode} from '@babylonjs/core/Meshes/transformNode';
|
|
|
4
4
|
import type {Scene} from '@babylonjs/core/scene';
|
|
5
5
|
import {EDITOR_SCENE_NODE_METADATA} from './editorScene';
|
|
6
6
|
import {GROUND_RING_RADIUS, GalleryHoverMarker} from './gallerySelectionMarker';
|
|
7
|
-
import type {
|
|
7
|
+
import type {SceneEntry} from './sceneEntries';
|
|
8
8
|
|
|
9
9
|
interface PickMetadata {
|
|
10
10
|
quarksEditorPick: true;
|
|
@@ -15,7 +15,7 @@ interface PickMetadata {
|
|
|
15
15
|
const PICK_DIAMETER = GROUND_RING_RADIUS * 2;
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
* Viewport pick/hover for
|
|
18
|
+
* Viewport pick/hover for visible scene effect roots only (not child emitters).
|
|
19
19
|
* Hover uses the same ground ring as focus selection, in yellow.
|
|
20
20
|
*/
|
|
21
21
|
export class GalleryViewportPicker {
|
|
@@ -30,8 +30,8 @@ export class GalleryViewportPicker {
|
|
|
30
30
|
this.hoverMarker = new GalleryHoverMarker(scene);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
/** Rebuilds root pick colliders when the
|
|
34
|
-
sync(entries:
|
|
33
|
+
/** Rebuilds root pick colliders when the scene changes. */
|
|
34
|
+
sync(entries: SceneEntry[], soloRoot: TransformNode | null, syncKey: string): void {
|
|
35
35
|
if (syncKey === this.syncKey) {
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
@@ -39,7 +39,7 @@ export class GalleryViewportPicker {
|
|
|
39
39
|
this.clearHandles();
|
|
40
40
|
if (entries.length > 0) {
|
|
41
41
|
for (const entry of entries) {
|
|
42
|
-
if (
|
|
42
|
+
if (entry.root.isDisposed() || !entry.root.isEnabled(false)) {
|
|
43
43
|
continue;
|
|
44
44
|
}
|
|
45
45
|
this.createHandle(entry.root);
|
package/src/index.ts
CHANGED
|
@@ -16,8 +16,19 @@ export type {GradientStop} from './core/colors';
|
|
|
16
16
|
export {buildEffectTree, collectSystems, serializeEffectForest, serializeEffectTree} from './core/effectTree';
|
|
17
17
|
export type {EffectTreeNode} from './core/effectTree';
|
|
18
18
|
export {EffectHistory} from './core/history';
|
|
19
|
+
export type {HistoryStacks} from './core/history';
|
|
20
|
+
export {IndexedDbProjectStore} from './core/indexedDbProjectStore';
|
|
19
21
|
export {applyRendererMaterial, getMaterialLabel} from './core/material';
|
|
20
22
|
export type {RendererMaterialPatch} from './core/material';
|
|
23
|
+
export {formatBytes} from './core/projectStore';
|
|
24
|
+
export type {
|
|
25
|
+
ProjectMeta,
|
|
26
|
+
ProjectStore,
|
|
27
|
+
StorageEstimate,
|
|
28
|
+
StoredAsset,
|
|
29
|
+
StoredScene,
|
|
30
|
+
StoredSceneEntry,
|
|
31
|
+
} from './core/projectStore';
|
|
21
32
|
export {
|
|
22
33
|
DEFAULT_SHAPE_PARAMS,
|
|
23
34
|
SHAPE_PARAM_KEYS,
|