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
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { TransformNode } from '@babylonjs/core/Meshes/transformNode.js';
|
|
2
2
|
import type { ParticleSystem } from 'babylon.quarks';
|
|
3
3
|
/** A node in the editor's effect hierarchy: a Group (organizational) or a ParticleEmitter. */
|
|
4
|
+
/** One node of an effect's tree: a group, or an emitter with its system. */
|
|
4
5
|
export interface EffectTreeNode {
|
|
6
|
+
/** The node. */
|
|
5
7
|
node: TransformNode;
|
|
8
|
+
/** The node's name, or a readable stand-in when it has none. */
|
|
6
9
|
name: string;
|
|
7
10
|
/** Present when the node is a ParticleEmitter. */
|
|
8
11
|
system?: ParticleSystem;
|
|
12
|
+
/** How deep the node sits; the root is 0. */
|
|
9
13
|
depth: number;
|
|
14
|
+
/** The node's child groups and emitters. */
|
|
10
15
|
children: EffectTreeNode[];
|
|
11
16
|
}
|
|
12
17
|
/** Walks a loaded root TransformNode into the editor tree (groups + emitters). */
|
|
@@ -2,6 +2,7 @@ import { Color3 } from '@babylonjs/core/Maths/math.color.js';
|
|
|
2
2
|
import { Vector3 } from '@babylonjs/core/Maths/math.vector.js';
|
|
3
3
|
import { MeshBuilder } from '@babylonjs/core/Meshes/meshBuilder.js';
|
|
4
4
|
import { TransformNode } from '@babylonjs/core/Meshes/transformNode.js';
|
|
5
|
+
import { ConeEmitFrom } from 'babylon.quarks';
|
|
5
6
|
import { EDITOR_SCENE_NODE_METADATA } from './editorScene.js';
|
|
6
7
|
import './lineShaders.js';
|
|
7
8
|
import { getShapeType, readShapeParams } from './shapes.js';
|
|
@@ -103,17 +104,23 @@ function buildEmitterWireframe(system, scene) {
|
|
|
103
104
|
lines('wf-cone-spoke-a', [Vector3.Zero(), arcPoint(radius, 0)]);
|
|
104
105
|
lines('wf-cone-spoke-b', [Vector3.Zero(), arcPoint(radius, arc)]);
|
|
105
106
|
}
|
|
106
|
-
|
|
107
|
-
|
|
107
|
+
// Emit from Volume draws the volume itself, out to its length; the base a stretch of the
|
|
108
|
+
// directions.
|
|
109
|
+
const preview = params.coneEmitFrom === ConeEmitFrom.Volume
|
|
110
|
+
? Math.max(Number.isFinite(params.length) ? params.length : 0, 0.001)
|
|
111
|
+
: Math.max(radius * 2, 1.25);
|
|
112
|
+
// The rays and the far rim end at the same point, which a near-flat cone keeps in view.
|
|
113
|
+
const travel = preview / Math.max(Math.cos(angle), 0.05);
|
|
114
|
+
const farRadius = radius + Math.sin(angle) * travel;
|
|
115
|
+
const farZ = Math.cos(angle) * travel;
|
|
108
116
|
const spokeCount = Math.max(4, Math.ceil((arc / (Math.PI * 2)) * 12));
|
|
109
117
|
for (let i = 0; i <= spokeCount; i++) {
|
|
110
118
|
const theta = (i / spokeCount) * arc;
|
|
111
119
|
const rim = arcPoint(radius, theta);
|
|
112
120
|
const dir = new Vector3(Math.sin(angle) * Math.cos(theta), Math.sin(angle) * Math.sin(theta), Math.cos(angle));
|
|
113
|
-
const travel = preview / Math.max(Math.cos(angle), 0.05);
|
|
114
121
|
lines(`wf-cone-ray-${i}`, [rim, rim.add(dir.scale(travel))], DIRECTION_COLOR);
|
|
115
122
|
}
|
|
116
|
-
lines('wf-cone-far', xyArc(farRadius, arc, 32,
|
|
123
|
+
lines('wf-cone-far', xyArc(farRadius, arc, 32, farZ), DIRECTION_COLOR);
|
|
117
124
|
break;
|
|
118
125
|
}
|
|
119
126
|
case 'sphere': {
|
package/dist/core/history.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { EffectBinding } from './binding.js';
|
|
2
2
|
/** Undo/redo stacks of one effect, kept by the host while another effect is focused. */
|
|
3
3
|
export interface HistoryStacks {
|
|
4
|
+
/** Snapshots to undo to, oldest first. */
|
|
4
5
|
undo: string[];
|
|
6
|
+
/** Snapshots to redo to, most recent undo last. */
|
|
5
7
|
redo: string[];
|
|
6
8
|
}
|
|
7
9
|
/**
|
|
@@ -18,11 +20,15 @@ export declare class EffectHistory {
|
|
|
18
20
|
private binding;
|
|
19
21
|
/** Points the history at the active binding and hooks snapshot capture. */
|
|
20
22
|
attach(binding: EffectBinding): void;
|
|
23
|
+
/** There is a change to undo. */
|
|
21
24
|
get canUndo(): boolean;
|
|
25
|
+
/** There is an undone change to redo. */
|
|
22
26
|
get canRedo(): boolean;
|
|
27
|
+
/** Forgets every snapshot. */
|
|
23
28
|
clear(): void;
|
|
24
29
|
/** The current stacks, to hand back to `restoreStacks` when this effect is focused again. */
|
|
25
30
|
saveStacks(): HistoryStacks;
|
|
31
|
+
/** Puts back stacks from `saveStacks`; undefined starts empty. */
|
|
26
32
|
restoreStacks(stacks: HistoryStacks | undefined): void;
|
|
27
33
|
/**
|
|
28
34
|
* Pushes an undo snapshot. A burst of edits — typing a number, dragging a curve handle or a
|
package/dist/core/history.js
CHANGED
|
@@ -19,12 +19,15 @@ export class EffectHistory {
|
|
|
19
19
|
this.binding = binding;
|
|
20
20
|
binding.onBeforeChange = () => this.capture();
|
|
21
21
|
}
|
|
22
|
+
/** There is a change to undo. */
|
|
22
23
|
get canUndo() {
|
|
23
24
|
return this.undoStack.length > 0;
|
|
24
25
|
}
|
|
26
|
+
/** There is an undone change to redo. */
|
|
25
27
|
get canRedo() {
|
|
26
28
|
return this.redoStack.length > 0;
|
|
27
29
|
}
|
|
30
|
+
/** Forgets every snapshot. */
|
|
28
31
|
clear() {
|
|
29
32
|
this.undoStack = [];
|
|
30
33
|
this.redoStack = [];
|
|
@@ -34,6 +37,7 @@ export class EffectHistory {
|
|
|
34
37
|
saveStacks() {
|
|
35
38
|
return { undo: this.undoStack, redo: this.redoStack };
|
|
36
39
|
}
|
|
40
|
+
/** Puts back stacks from `saveStacks`; undefined starts empty. */
|
|
37
41
|
restoreStacks(stacks) {
|
|
38
42
|
this.undoStack = stacks?.undo ?? [];
|
|
39
43
|
this.redoStack = stacks?.redo ?? [];
|
|
@@ -11,18 +11,31 @@ export declare class IndexedDbProjectStore implements ProjectStore {
|
|
|
11
11
|
/** `${projectId}/${hash}` → data URI, so every effect sharing a file gets the same string. */
|
|
12
12
|
private readonly dataUrlByFile;
|
|
13
13
|
private db;
|
|
14
|
+
/** {@inheritDoc ProjectStore.listProjects} */
|
|
14
15
|
listProjects(): Promise<ProjectMeta[]>;
|
|
16
|
+
/** {@inheritDoc ProjectStore.getProject} */
|
|
15
17
|
getProject(id: string): Promise<ProjectMeta | null>;
|
|
18
|
+
/** {@inheritDoc ProjectStore.createProject} */
|
|
16
19
|
createProject(name: string): Promise<ProjectMeta>;
|
|
20
|
+
/** {@inheritDoc ProjectStore.renameProject} */
|
|
17
21
|
renameProject(id: string, name: string): Promise<void>;
|
|
22
|
+
/** {@inheritDoc ProjectStore.duplicateProject} */
|
|
18
23
|
duplicateProject(id: string, name: string): Promise<ProjectMeta>;
|
|
24
|
+
/** {@inheritDoc ProjectStore.deleteProject} */
|
|
19
25
|
deleteProject(id: string): Promise<void>;
|
|
26
|
+
/** {@inheritDoc ProjectStore.loadAssets} */
|
|
20
27
|
loadAssets(projectId: string): Promise<StoredAsset[]>;
|
|
28
|
+
/** {@inheritDoc ProjectStore.putAssets} */
|
|
21
29
|
putAssets(projectId: string, assets: StoredAsset[]): Promise<void>;
|
|
30
|
+
/** {@inheritDoc ProjectStore.deleteAssets} */
|
|
22
31
|
deleteAssets(projectId: string, paths: string[]): Promise<void>;
|
|
32
|
+
/** {@inheritDoc ProjectStore.loadScene} */
|
|
23
33
|
loadScene(projectId: string): Promise<StoredScene | null>;
|
|
34
|
+
/** {@inheritDoc ProjectStore.saveScene} */
|
|
24
35
|
saveScene(projectId: string, scene: StoredScene): Promise<void>;
|
|
36
|
+
/** {@inheritDoc ProjectStore.estimate} */
|
|
25
37
|
estimate(): Promise<StorageEstimate>;
|
|
38
|
+
/** {@inheritDoc ProjectStore.requestPersistence} */
|
|
26
39
|
requestPersistence(): Promise<boolean>;
|
|
27
40
|
/** Clones `json` with each texture data URI replaced by a `quarks-file:<hash>` reference. */
|
|
28
41
|
private pack;
|
|
@@ -84,15 +84,18 @@ export class IndexedDbProjectStore {
|
|
|
84
84
|
return this.dbPromise;
|
|
85
85
|
}
|
|
86
86
|
// ---- projects -------------------------------------------------------------------------
|
|
87
|
+
/** {@inheritDoc ProjectStore.listProjects} */
|
|
87
88
|
async listProjects() {
|
|
88
89
|
const db = await this.db();
|
|
89
90
|
const all = await request(db.transaction('projects').objectStore('projects').getAll());
|
|
90
91
|
return all.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
91
92
|
}
|
|
93
|
+
/** {@inheritDoc ProjectStore.getProject} */
|
|
92
94
|
async getProject(id) {
|
|
93
95
|
const db = await this.db();
|
|
94
96
|
return ((await request(db.transaction('projects').objectStore('projects').get(id))) ?? null);
|
|
95
97
|
}
|
|
98
|
+
/** {@inheritDoc ProjectStore.createProject} */
|
|
96
99
|
async createProject(name) {
|
|
97
100
|
const now = Date.now();
|
|
98
101
|
const meta = { id: newId(), name, createdAt: now, updatedAt: now, assetCount: 0, sizeBytes: 0 };
|
|
@@ -102,6 +105,7 @@ export class IndexedDbProjectStore {
|
|
|
102
105
|
await done(tx);
|
|
103
106
|
return meta;
|
|
104
107
|
}
|
|
108
|
+
/** {@inheritDoc ProjectStore.renameProject} */
|
|
105
109
|
async renameProject(id, name) {
|
|
106
110
|
const meta = await this.getProject(id);
|
|
107
111
|
if (!meta)
|
|
@@ -111,6 +115,7 @@ export class IndexedDbProjectStore {
|
|
|
111
115
|
tx.objectStore('projects').put({ ...meta, name, updatedAt: Date.now() });
|
|
112
116
|
await done(tx);
|
|
113
117
|
}
|
|
118
|
+
/** {@inheritDoc ProjectStore.duplicateProject} */
|
|
114
119
|
async duplicateProject(id, name) {
|
|
115
120
|
const source = await this.getProject(id);
|
|
116
121
|
if (!source)
|
|
@@ -135,6 +140,7 @@ export class IndexedDbProjectStore {
|
|
|
135
140
|
await done(tx);
|
|
136
141
|
return meta;
|
|
137
142
|
}
|
|
143
|
+
/** {@inheritDoc ProjectStore.deleteProject} */
|
|
138
144
|
async deleteProject(id) {
|
|
139
145
|
const db = await this.db();
|
|
140
146
|
const tx = db.transaction(['projects', 'assets', 'files', 'scenes'], 'readwrite');
|
|
@@ -152,6 +158,7 @@ export class IndexedDbProjectStore {
|
|
|
152
158
|
}
|
|
153
159
|
}
|
|
154
160
|
// ---- assets ---------------------------------------------------------------------------
|
|
161
|
+
/** {@inheritDoc ProjectStore.loadAssets} */
|
|
155
162
|
async loadAssets(projectId) {
|
|
156
163
|
const db = await this.db();
|
|
157
164
|
const records = await request(db.transaction('assets').objectStore('assets').index('projectId').getAll(projectId));
|
|
@@ -162,6 +169,7 @@ export class IndexedDbProjectStore {
|
|
|
162
169
|
missingFiles,
|
|
163
170
|
})));
|
|
164
171
|
}
|
|
172
|
+
/** {@inheritDoc ProjectStore.putAssets} */
|
|
165
173
|
async putAssets(projectId, assets) {
|
|
166
174
|
const files = new Map();
|
|
167
175
|
const packed = await Promise.all(assets.map(async (asset) => ({ ...asset, projectId, json: await this.pack(asset.json, files) })));
|
|
@@ -173,6 +181,7 @@ export class IndexedDbProjectStore {
|
|
|
173
181
|
await done(tx);
|
|
174
182
|
await this.refresh(projectId);
|
|
175
183
|
}
|
|
184
|
+
/** {@inheritDoc ProjectStore.deleteAssets} */
|
|
176
185
|
async deleteAssets(projectId, paths) {
|
|
177
186
|
const db = await this.db();
|
|
178
187
|
const tx = db.transaction('assets', 'readwrite');
|
|
@@ -182,6 +191,7 @@ export class IndexedDbProjectStore {
|
|
|
182
191
|
await this.refresh(projectId);
|
|
183
192
|
}
|
|
184
193
|
// ---- scene ----------------------------------------------------------------------------
|
|
194
|
+
/** {@inheritDoc ProjectStore.loadScene} */
|
|
185
195
|
async loadScene(projectId) {
|
|
186
196
|
const db = await this.db();
|
|
187
197
|
const record = await request(db.transaction('scenes').objectStore('scenes').get(projectId));
|
|
@@ -190,6 +200,7 @@ export class IndexedDbProjectStore {
|
|
|
190
200
|
const entries = await Promise.all(record.entries.map(async (entry) => ({ ...entry, json: await this.unpack(projectId, entry.json) })));
|
|
191
201
|
return { entries, savedAt: record.savedAt };
|
|
192
202
|
}
|
|
203
|
+
/** {@inheritDoc ProjectStore.saveScene} */
|
|
193
204
|
async saveScene(projectId, scene) {
|
|
194
205
|
const files = new Map();
|
|
195
206
|
const entries = await Promise.all(scene.entries.map(async (entry) => ({ ...entry, json: await this.pack(entry.json, files) })));
|
|
@@ -201,12 +212,14 @@ export class IndexedDbProjectStore {
|
|
|
201
212
|
await this.refresh(projectId);
|
|
202
213
|
}
|
|
203
214
|
// ---- storage ----------------------------------------------------------------------------
|
|
215
|
+
/** {@inheritDoc ProjectStore.estimate} */
|
|
204
216
|
async estimate() {
|
|
205
217
|
const storage = typeof navigator !== 'undefined' ? navigator.storage : undefined;
|
|
206
218
|
const estimate = storage?.estimate ? await storage.estimate() : undefined;
|
|
207
219
|
const persisted = storage?.persisted ? await storage.persisted() : false;
|
|
208
220
|
return { usage: estimate?.usage ?? null, quota: estimate?.quota ?? null, persisted };
|
|
209
221
|
}
|
|
222
|
+
/** {@inheritDoc ProjectStore.requestPersistence} */
|
|
210
223
|
async requestPersistence() {
|
|
211
224
|
const storage = typeof navigator !== 'undefined' ? navigator.storage : undefined;
|
|
212
225
|
return storage?.persist ? storage.persist() : false;
|
package/dist/core/loadEffect.js
CHANGED
|
@@ -79,6 +79,8 @@ export function parseEffectFromJson(scene, renderer, json, options = {}, baseUrl
|
|
|
79
79
|
const systems = [];
|
|
80
80
|
QuarksUtil.runOnAllParticleEmitters(root, (emitter) => {
|
|
81
81
|
const system = emitter.system;
|
|
82
|
+
// The editor previews over and over; the stop action is saved, not applied.
|
|
83
|
+
system.ignoreStopAction = true;
|
|
82
84
|
if (registerRenderer && renderer) {
|
|
83
85
|
renderer.addSystem(system);
|
|
84
86
|
}
|
package/dist/core/material.d.ts
CHANGED
|
@@ -1,13 +1,23 @@
|
|
|
1
|
+
import type { BaseTexture } from '@babylonjs/core/Materials/Textures/baseTexture.js';
|
|
2
|
+
import type { Texture } from '@babylonjs/core/Materials/Textures/texture.js';
|
|
1
3
|
import type { ParticleSystem } from 'babylon.quarks';
|
|
4
|
+
/** Material settings the Renderer module changes; fields left out keep their value. */
|
|
2
5
|
export interface RendererMaterialPatch {
|
|
6
|
+
/** The blend mode, see `ParticleSystem.blending`. */
|
|
3
7
|
blendMode?: number;
|
|
8
|
+
/** Draws with alpha blending. */
|
|
4
9
|
transparent?: boolean;
|
|
10
|
+
/** Tests against the depth buffer. */
|
|
5
11
|
depthTest?: boolean;
|
|
12
|
+
/** Writes to the depth buffer. */
|
|
6
13
|
depthWrite?: boolean;
|
|
14
|
+
/** Discards pixels whose alpha is below this. */
|
|
7
15
|
alphaTest?: number;
|
|
8
|
-
texture
|
|
16
|
+
/** The main texture, or null for none. */
|
|
17
|
+
texture?: Texture | null;
|
|
9
18
|
/** A 3×2 cube-face atlas mesh particles reflect, or null for none. */
|
|
10
|
-
reflectionAtlas?:
|
|
19
|
+
reflectionAtlas?: BaseTexture | null;
|
|
20
|
+
/** How strongly mesh particles reflect the atlas, 0..1. */
|
|
11
21
|
reflectionLevel?: number;
|
|
12
22
|
}
|
|
13
23
|
/** Applies renderer material settings and keeps imported QuarksMaterial JSON in sync for export. */
|
package/dist/core/playback.d.ts
CHANGED
|
@@ -2,11 +2,17 @@
|
|
|
2
2
|
export interface PlaybackState {
|
|
3
3
|
/** When true, all in-scene preview effects simulate. */
|
|
4
4
|
stagePlaying: boolean;
|
|
5
|
+
/** Root `uniqueId` of the focused effect, whose time the timeline shows. */
|
|
5
6
|
focusRootId: number | null;
|
|
7
|
+
/** Each effect's timeline time, in seconds, by root `uniqueId`. */
|
|
6
8
|
focusElapsedByRoot: Map<number, number>;
|
|
9
|
+
/** Effects parked at their end, by root `uniqueId`; Play restarts them. */
|
|
7
10
|
focusFinishedByRoot: Set<number>;
|
|
11
|
+
/** Playback speed, 1 for real time. */
|
|
8
12
|
speed: number;
|
|
13
|
+
/** While paused: advance one frame on the next render. */
|
|
9
14
|
stepQueued: boolean;
|
|
15
|
+
/** The timeline is being scrubbed; the render loop does not simulate. */
|
|
10
16
|
scrubbing: boolean;
|
|
11
17
|
}
|
|
12
18
|
/** Default playback state for a new editor session. */
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
/** One editor project: a library of effect assets plus the scene built from them. */
|
|
2
2
|
export interface ProjectMeta {
|
|
3
|
+
/** The project's key in its store. */
|
|
3
4
|
id: string;
|
|
5
|
+
/** The project's display name. */
|
|
4
6
|
name: string;
|
|
7
|
+
/** When the project was created, in milliseconds since the epoch. */
|
|
5
8
|
createdAt: number;
|
|
9
|
+
/** When the project last changed, in milliseconds since the epoch. */
|
|
6
10
|
updatedAt: number;
|
|
11
|
+
/** How many effect assets the project holds. */
|
|
7
12
|
assetCount: number;
|
|
8
13
|
/** Approximate bytes the project takes in storage (JSON + texture files). */
|
|
9
14
|
sizeBytes: number;
|
|
@@ -12,23 +17,32 @@ export interface ProjectMeta {
|
|
|
12
17
|
export interface StoredAsset {
|
|
13
18
|
/** Relative path inside the project (`Fire/Explosion.json`) — the asset's key. */
|
|
14
19
|
path: string;
|
|
20
|
+
/** The asset's display name, its file name without `.json`. */
|
|
15
21
|
name: string;
|
|
22
|
+
/** The effect, as quarks JSON with its textures inlined. */
|
|
16
23
|
json: unknown;
|
|
24
|
+
/** Files the effect referenced that were not imported with it. */
|
|
17
25
|
missingFiles: string[];
|
|
18
26
|
}
|
|
19
27
|
/** One effect instance in a saved scene. */
|
|
20
28
|
export interface StoredSceneEntry {
|
|
29
|
+
/** The instance's name in the Scene list. */
|
|
21
30
|
name: string;
|
|
22
31
|
/** Path of the asset it was made from, if any. */
|
|
23
32
|
assetPath: string | null;
|
|
24
33
|
/** The instance's own (possibly edited) effect, root transform included. */
|
|
25
34
|
json: unknown;
|
|
35
|
+
/** Whether the instance was shown. */
|
|
26
36
|
visible: boolean;
|
|
27
37
|
}
|
|
38
|
+
/** A project's saved scene: the effects placed in it, in Scene list order. */
|
|
28
39
|
export interface StoredScene {
|
|
40
|
+
/** The scene's effect instances. */
|
|
29
41
|
entries: StoredSceneEntry[];
|
|
42
|
+
/** When it was saved, in milliseconds since the epoch. */
|
|
30
43
|
savedAt: number;
|
|
31
44
|
}
|
|
45
|
+
/** How much storage projects use, as far as the browser tells. */
|
|
32
46
|
export interface StorageEstimate {
|
|
33
47
|
/** Bytes the whole site uses (all projects), when the browser reports it. */
|
|
34
48
|
usage: number | null;
|
|
@@ -42,20 +56,32 @@ export interface StorageEstimate {
|
|
|
42
56
|
* everything in the browser, and an embedding host can supply its own (e.g. on disk).
|
|
43
57
|
*/
|
|
44
58
|
export interface ProjectStore {
|
|
59
|
+
/** Every project, most recently changed first. */
|
|
45
60
|
listProjects(): Promise<ProjectMeta[]>;
|
|
61
|
+
/** One project, or null when there is none with that id. */
|
|
46
62
|
getProject(id: string): Promise<ProjectMeta | null>;
|
|
63
|
+
/** Creates an empty project. */
|
|
47
64
|
createProject(name: string): Promise<ProjectMeta>;
|
|
65
|
+
/** Renames a project. */
|
|
48
66
|
renameProject(id: string, name: string): Promise<void>;
|
|
67
|
+
/** Copies a project, its assets and its scene, under a new name. */
|
|
49
68
|
duplicateProject(id: string, name: string): Promise<ProjectMeta>;
|
|
69
|
+
/** Deletes a project, its assets and its scene. */
|
|
50
70
|
deleteProject(id: string): Promise<void>;
|
|
71
|
+
/** A project's assets. */
|
|
51
72
|
loadAssets(projectId: string): Promise<StoredAsset[]>;
|
|
52
73
|
/** Adds or replaces assets by path. */
|
|
53
74
|
putAssets(projectId: string, assets: StoredAsset[]): Promise<void>;
|
|
75
|
+
/** Deletes assets by path; paths it does not have are ignored. */
|
|
54
76
|
deleteAssets(projectId: string, paths: string[]): Promise<void>;
|
|
77
|
+
/** A project's saved scene, or null when it was never saved. */
|
|
55
78
|
loadScene(projectId: string): Promise<StoredScene | null>;
|
|
79
|
+
/** Replaces a project's saved scene. */
|
|
56
80
|
saveScene(projectId: string, scene: StoredScene): Promise<void>;
|
|
81
|
+
/** How much storage projects use. */
|
|
57
82
|
estimate(): Promise<StorageEstimate>;
|
|
58
83
|
/** Asks the browser to keep the data even under storage pressure; resolves to the result. */
|
|
59
84
|
requestPersistence(): Promise<boolean>;
|
|
60
85
|
}
|
|
86
|
+
/** A byte count for people: `512 B`, `1.5 KB`, `20 MB`; `—` when unknown. */
|
|
61
87
|
export declare function formatBytes(bytes: number | null | undefined): string;
|
|
@@ -9,12 +9,15 @@ import { EffectBinding } from './binding.js';
|
|
|
9
9
|
* that are edited separately.
|
|
10
10
|
*/
|
|
11
11
|
export interface SceneEntry {
|
|
12
|
+
/** The instance's name in the Scene list, unique in the scene. */
|
|
12
13
|
name: string;
|
|
13
14
|
/** Path of the asset it came from, or a synthetic `_created/…` path for new effects. */
|
|
14
15
|
path: string;
|
|
15
16
|
/** Asset this instance was made from, if any. */
|
|
16
17
|
assetId: number | null;
|
|
18
|
+
/** The instance's root node. */
|
|
17
19
|
root: TransformNode;
|
|
20
|
+
/** Every particle system of the instance. */
|
|
18
21
|
systems: ParticleSystem[];
|
|
19
22
|
}
|
|
20
23
|
/** `name`, or `name (2)`, `name (3)`… when an entry already uses it. */
|
package/dist/core/scrub.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { BatchedRenderer } from 'babylon.quarks';
|
|
2
2
|
import type { EffectBinding } from './binding.js';
|
|
3
|
-
/** Unpauses every system in the focused effect. */
|
|
3
|
+
/** Unpauses every system in the focused effect; one that has finished stays finished. */
|
|
4
4
|
export declare function playFocus(binding: EffectBinding): void;
|
|
5
5
|
/** Freezes the focused effect at its current simulation state. */
|
|
6
6
|
export declare function pauseFocus(binding: EffectBinding): void;
|
package/dist/core/scrub.js
CHANGED
|
@@ -2,10 +2,10 @@ const WIDE_STEP_THRESHOLD = 8;
|
|
|
2
2
|
function bindingSystems(binding) {
|
|
3
3
|
return [binding.system, ...binding.subSystems];
|
|
4
4
|
}
|
|
5
|
-
/** Unpauses every system in the focused effect. */
|
|
5
|
+
/** Unpauses every system in the focused effect; one that has finished stays finished. */
|
|
6
6
|
export function playFocus(binding) {
|
|
7
7
|
for (const system of bindingSystems(binding)) {
|
|
8
|
-
system.
|
|
8
|
+
system.resume();
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
11
|
/** Freezes the focused effect at its current simulation state. */
|
package/dist/core/shapes.d.ts
CHANGED
|
@@ -1,37 +1,67 @@
|
|
|
1
1
|
import type { Mesh } from '@babylonjs/core/Meshes/mesh.js';
|
|
2
2
|
import type { EmitterShape } from 'babylon.quarks';
|
|
3
|
-
import { BoxEmitFrom, EmitterMode } from 'babylon.quarks';
|
|
3
|
+
import { BoxEmitFrom, ConeEmitFrom, EmitterMode, GridSampling } from 'babylon.quarks';
|
|
4
|
+
/** The emitter shapes the Shape module offers, by the `type` they serialise with. */
|
|
4
5
|
export type ShapeType = 'point' | 'sphere' | 'hemisphere' | 'cone' | 'circle' | 'donut' | 'rectangle' | 'grid' | 'box' | 'edge' | 'mesh_surface';
|
|
6
|
+
/** The Shape module's shape list, in menu order, with the label each shows. */
|
|
5
7
|
export declare const SHAPE_TYPES: Array<{
|
|
8
|
+
/** The shape's type. */
|
|
6
9
|
value: ShapeType;
|
|
10
|
+
/** Its label in the menu. */
|
|
7
11
|
label: string;
|
|
8
12
|
}>;
|
|
13
|
+
/**
|
|
14
|
+
* Every shape's settings in one flat record, so switching shapes keeps what the shapes share
|
|
15
|
+
* (a cone's radius becomes the sphere's). Each shape reads the fields it has.
|
|
16
|
+
*/
|
|
9
17
|
export interface ShapeParams {
|
|
18
|
+
/** Radius of a sphere, hemisphere, cone, circle or donut. */
|
|
10
19
|
radius: number;
|
|
20
|
+
/** The part of the circle emitted from, in radians (2π: all of it). */
|
|
11
21
|
arc: number;
|
|
22
|
+
/** 0 emits from the surface only, 1 from the whole volume. */
|
|
12
23
|
thickness: number;
|
|
24
|
+
/** A cone's half angle, in radians. */
|
|
13
25
|
angle: number;
|
|
26
|
+
/** The radius of a donut's tube. */
|
|
14
27
|
donutRadius: number;
|
|
28
|
+
/** Width of a rectangle, grid or box (X). */
|
|
15
29
|
width: number;
|
|
30
|
+
/** Height of a rectangle, grid or box (Y). */
|
|
16
31
|
height: number;
|
|
32
|
+
/** How points around the arc are picked: at random, in a loop, back and forth or in bursts. */
|
|
17
33
|
mode: EmitterMode;
|
|
34
|
+
/** Snaps the arc position to steps of this fraction; 0 for none. */
|
|
18
35
|
spread: number;
|
|
36
|
+
/** A grid's column count. */
|
|
19
37
|
column: number;
|
|
38
|
+
/** A grid's row count. */
|
|
20
39
|
row: number;
|
|
21
40
|
/** Box depth (Z). */
|
|
22
41
|
depth: number;
|
|
23
42
|
/** Where a box emits from: its volume, its faces or its edges. */
|
|
24
43
|
emitFrom: BoxEmitFrom;
|
|
44
|
+
/** Where a cone emits from: its base or its volume. */
|
|
45
|
+
coneEmitFrom: ConeEmitFrom;
|
|
46
|
+
/** Height of a cone's volume (Unity's Length), for Emit from Volume. */
|
|
47
|
+
length: number;
|
|
48
|
+
/** How a grid picks its cells: at random or in turn. */
|
|
49
|
+
sampling: GridSampling;
|
|
25
50
|
/** How fast an edge emitter's loop / ping-pong point travels, in lengths per second. */
|
|
26
51
|
speed: number;
|
|
52
|
+
/** The mesh a mesh surface emitter emits from. */
|
|
27
53
|
mesh?: Mesh;
|
|
28
54
|
}
|
|
55
|
+
/** The settings a new shape starts from. */
|
|
29
56
|
export declare const DEFAULT_SHAPE_PARAMS: ShapeParams;
|
|
57
|
+
/** The type of a shape, as the Shape module lists it. */
|
|
30
58
|
export declare function getShapeType(shape: EmitterShape): ShapeType;
|
|
59
|
+
/** A shape's settings, the fields it does not have taken from {@link DEFAULT_SHAPE_PARAMS}. */
|
|
31
60
|
export declare function readShapeParams(shape: EmitterShape): ShapeParams;
|
|
32
61
|
/** Which parameters are relevant per shape type (drives the UI). `mesh_surface`'s mesh
|
|
33
62
|
* reference isn't a NumberField, so it's handled separately by the Shape module. */
|
|
34
63
|
export declare const SHAPE_PARAM_KEYS: {
|
|
35
64
|
[K in ShapeType]: Array<keyof ShapeParams>;
|
|
36
65
|
};
|
|
66
|
+
/** A new shape of `type`, built from the fields of `params` it has. */
|
|
37
67
|
export declare function createShape(type: ShapeType, params: ShapeParams): EmitterShape;
|
package/dist/core/shapes.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { BoxEmitFrom, BoxEmitter, CircleEmitter, ConeEmitter, ConstantValue, DonutEmitter, EdgeEmitter, EmitterMode, GridEmitter, HemisphereEmitter, MeshSurfaceEmitter, PointEmitter, RectangleEmitter, SphereEmitter, } from 'babylon.quarks';
|
|
1
|
+
import { BoxEmitFrom, BoxEmitter, CircleEmitter, ConeEmitFrom, ConeEmitter, ConstantValue, DonutEmitter, EdgeEmitter, EmitterMode, GridEmitter, GridSampling, HemisphereEmitter, MeshSurfaceEmitter, PointEmitter, RectangleEmitter, SphereEmitter, } from 'babylon.quarks';
|
|
2
|
+
/** The Shape module's shape list, in menu order, with the label each shows. */
|
|
2
3
|
export const SHAPE_TYPES = [
|
|
3
4
|
{ value: 'cone', label: 'Cone' },
|
|
4
5
|
{ value: 'sphere', label: 'Sphere' },
|
|
@@ -12,6 +13,7 @@ export const SHAPE_TYPES = [
|
|
|
12
13
|
{ value: 'mesh_surface', label: 'Mesh surface' },
|
|
13
14
|
{ value: 'point', label: 'Point' },
|
|
14
15
|
];
|
|
16
|
+
/** The settings a new shape starts from. */
|
|
15
17
|
export const DEFAULT_SHAPE_PARAMS = {
|
|
16
18
|
radius: 0.3,
|
|
17
19
|
arc: Math.PI * 2,
|
|
@@ -26,13 +28,18 @@ export const DEFAULT_SHAPE_PARAMS = {
|
|
|
26
28
|
row: 10,
|
|
27
29
|
depth: 1,
|
|
28
30
|
emitFrom: BoxEmitFrom.Volume,
|
|
31
|
+
coneEmitFrom: ConeEmitFrom.Base,
|
|
32
|
+
length: 5,
|
|
33
|
+
sampling: GridSampling.Random,
|
|
29
34
|
speed: 1,
|
|
30
35
|
mesh: undefined,
|
|
31
36
|
};
|
|
37
|
+
/** The type of a shape, as the Shape module lists it. */
|
|
32
38
|
export function getShapeType(shape) {
|
|
33
39
|
const type = shape.type ?? 'point';
|
|
34
40
|
return (SHAPE_TYPES.some((s) => s.value === type) ? type : 'point');
|
|
35
41
|
}
|
|
42
|
+
/** A shape's settings, the fields it does not have taken from {@link DEFAULT_SHAPE_PARAMS}. */
|
|
36
43
|
export function readShapeParams(shape) {
|
|
37
44
|
const anyShape = shape;
|
|
38
45
|
const num = (key, fallback) => typeof anyShape[key] === 'number' ? anyShape[key] : fallback;
|
|
@@ -49,7 +56,11 @@ export function readShapeParams(shape) {
|
|
|
49
56
|
column: num('column', DEFAULT_SHAPE_PARAMS.column),
|
|
50
57
|
row: num('row', DEFAULT_SHAPE_PARAMS.row),
|
|
51
58
|
depth: num('depth', DEFAULT_SHAPE_PARAMS.depth),
|
|
52
|
-
|
|
59
|
+
// A box and a cone both name it emitFrom, with different enums: each keeps its own.
|
|
60
|
+
emitFrom: shape instanceof BoxEmitter ? shape.emitFrom : DEFAULT_SHAPE_PARAMS.emitFrom,
|
|
61
|
+
coneEmitFrom: shape instanceof ConeEmitter ? shape.emitFrom : DEFAULT_SHAPE_PARAMS.coneEmitFrom,
|
|
62
|
+
length: num('length', DEFAULT_SHAPE_PARAMS.length),
|
|
63
|
+
sampling: num('sampling', DEFAULT_SHAPE_PARAMS.sampling),
|
|
53
64
|
speed: anyShape.speed instanceof ConstantValue ? anyShape.speed.value : DEFAULT_SHAPE_PARAMS.speed,
|
|
54
65
|
mesh: anyShape.mesh,
|
|
55
66
|
};
|
|
@@ -60,7 +71,7 @@ export const SHAPE_PARAM_KEYS = {
|
|
|
60
71
|
point: [],
|
|
61
72
|
sphere: ['radius', 'arc', 'thickness'],
|
|
62
73
|
hemisphere: ['radius', 'arc', 'thickness'],
|
|
63
|
-
cone: ['radius', 'arc', 'thickness', 'angle'],
|
|
74
|
+
cone: ['radius', 'arc', 'thickness', 'angle', 'length'],
|
|
64
75
|
circle: ['radius', 'arc', 'thickness'],
|
|
65
76
|
donut: ['radius', 'arc', 'thickness', 'donutRadius'],
|
|
66
77
|
rectangle: ['width', 'height'],
|
|
@@ -69,6 +80,7 @@ export const SHAPE_PARAM_KEYS = {
|
|
|
69
80
|
edge: ['radius', 'speed'],
|
|
70
81
|
mesh_surface: [],
|
|
71
82
|
};
|
|
83
|
+
/** A new shape of `type`, built from the fields of `params` it has. */
|
|
72
84
|
export function createShape(type, params) {
|
|
73
85
|
switch (type) {
|
|
74
86
|
case 'point':
|
|
@@ -95,6 +107,8 @@ export function createShape(type, params) {
|
|
|
95
107
|
arc: params.arc,
|
|
96
108
|
thickness: params.thickness,
|
|
97
109
|
angle: params.angle,
|
|
110
|
+
emitFrom: params.coneEmitFrom,
|
|
111
|
+
length: params.length,
|
|
98
112
|
mode: params.mode,
|
|
99
113
|
spread: params.spread,
|
|
100
114
|
});
|
|
@@ -128,6 +142,7 @@ export function createShape(type, params) {
|
|
|
128
142
|
height: params.height,
|
|
129
143
|
column: params.column,
|
|
130
144
|
row: params.row,
|
|
145
|
+
sampling: params.sampling,
|
|
131
146
|
});
|
|
132
147
|
case 'box':
|
|
133
148
|
return new BoxEmitter({
|
package/dist/core/systems.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { TransformNode } from '@babylonjs/core/Meshes/transformNode.js';
|
|
2
2
|
import { ParticleSystem } from 'babylon.quarks';
|
|
3
3
|
import type { EffectBinding } from './binding.js';
|
|
4
|
+
/** How {@link createChildSystem} builds a new system. */
|
|
4
5
|
export interface ChildSystemOptions {
|
|
6
|
+
/** The new emitter's name. */
|
|
5
7
|
name?: string;
|
|
6
8
|
/** One-shot burst (sub-emitter target) instead of a looping stream. */
|
|
7
9
|
oneShot?: boolean;
|
package/dist/core/systems.js
CHANGED
|
@@ -26,6 +26,8 @@ export function createChildSystem(parent, options = {}) {
|
|
|
26
26
|
worldSpace: true,
|
|
27
27
|
});
|
|
28
28
|
child.emitter.name = options.name ?? (oneShot ? 'sub-emitter' : 'child-system');
|
|
29
|
+
// The editor previews over and over; the stop action is saved, not applied.
|
|
30
|
+
child.ignoreStopAction = true;
|
|
29
31
|
if (oneShot) {
|
|
30
32
|
child.onlyUsedByOther = true;
|
|
31
33
|
}
|
|
@@ -43,7 +45,9 @@ export function unlinkSubEmitterReferences(systems, removed) {
|
|
|
43
45
|
const behaviors = system.behaviors;
|
|
44
46
|
for (let i = behaviors.length - 1; i >= 0; i--) {
|
|
45
47
|
const behavior = behaviors[i];
|
|
46
|
-
if (behavior.type === 'EmitSubParticleSystem' &&
|
|
48
|
+
if (behavior.type === 'EmitSubParticleSystem' &&
|
|
49
|
+
behavior.subParticleSystem &&
|
|
50
|
+
removedEmitters.has(behavior.subParticleSystem)) {
|
|
47
51
|
behaviors.splice(i, 1);
|
|
48
52
|
}
|
|
49
53
|
}
|