babylon.quarks-editor 0.18.0 → 0.19.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/README.md +6 -6
- package/dist/core/binding.d.ts +1 -1
- package/dist/core/colors.d.ts +1 -1
- package/dist/core/defaultEffect.d.ts +1 -1
- package/dist/core/effectTree.d.ts +1 -1
- package/dist/core/effectTree.js +18 -4
- package/dist/core/emitterShapeWireframe.d.ts +1 -1
- package/dist/core/emitterShapeWireframe.js +1 -1
- package/dist/core/galleryDropMarker.js +1 -1
- package/dist/core/galleryScene.d.ts +1 -1
- package/dist/core/galleryTree.js +4 -1
- package/dist/core/loadEffect.d.ts +2 -2
- package/dist/core/loadEffect.js +1 -1
- package/dist/core/loadEffectGallery.d.ts +2 -2
- package/dist/core/shapes.d.ts +1 -1
- package/dist/core/shapes.js +35 -8
- package/dist/core/systems.d.ts +1 -1
- package/dist/core/systems.js +3 -1
- package/dist/core/timeline.d.ts +1 -2
- package/dist/core/values.d.ts +1 -1
- package/dist/index.d.ts +11 -11
- package/dist/index.js +6 -6
- package/dist/react/CurveEditor.d.ts +1 -2
- package/dist/react/CurveEditor.js +14 -3
- package/dist/react/EffectEditor.d.ts +2 -2
- package/dist/react/EffectEditor.js +2 -2
- package/dist/react/EffectEditorHost.d.ts +1 -1
- package/dist/react/EffectEditorHost.js +123 -33
- package/dist/react/GalleryPanel.d.ts +1 -1
- package/dist/react/GalleryPanel.js +14 -7
- package/dist/react/GradientEditor.d.ts +1 -2
- package/dist/react/GradientEditor.js +8 -1
- package/dist/react/MessageDialog.js +7 -1
- package/dist/react/ModuleSection.js +10 -3
- package/dist/react/ObjectModule.d.ts +1 -2
- package/dist/react/ObjectModule.js +1 -1
- package/dist/react/SubEmittersModule.d.ts +1 -2
- package/dist/react/TimelinePanel.d.ts +3 -3
- package/dist/react/TimelinePanel.js +55 -9
- package/dist/react/TimelineTrackRow.d.ts +1 -1
- package/dist/react/TimelineTrackRow.js +20 -6
- package/dist/react/ValueField.d.ts +1 -2
- package/dist/react/behaviorModules.js +19 -5
- package/dist/react/index.d.ts +17 -17
- package/dist/react/index.js +11 -11
- package/dist/react/modules.js +55 -25
- package/package.json +68 -68
- package/src/core/binding.ts +2 -2
- package/src/core/colors.ts +1 -1
- package/src/core/defaultEffect.ts +1 -1
- package/src/core/effectTree.ts +22 -7
- package/src/core/emitterShapeWireframe.ts +8 -4
- package/src/core/galleryDropMarker.ts +6 -2
- package/src/core/galleryPlayback.ts +1 -1
- package/src/core/galleryScene.ts +3 -7
- package/src/core/gallerySelectionMarker.ts +5 -1
- package/src/core/galleryTree.ts +8 -5
- package/src/core/loadEffect.ts +3 -3
- package/src/core/loadEffectGallery.ts +7 -3
- package/src/core/pickGalleryFiles.ts +4 -1
- package/src/core/shapes.ts +48 -10
- package/src/core/systems.ts +9 -3
- package/src/core/timeline.ts +8 -8
- package/src/core/values.ts +1 -1
- package/src/core/viewportPicker.ts +1 -1
- package/src/index.ts +18 -18
- package/src/react/CurveEditor.tsx +40 -10
- package/src/react/EffectEditor.tsx +14 -13
- package/src/react/EffectEditorHost.tsx +239 -64
- package/src/react/GalleryPanel.tsx +56 -12
- package/src/react/GradientEditor.tsx +21 -4
- package/src/react/InspectorInfoIcon.tsx +6 -2
- package/src/react/MessageDialog.tsx +9 -1
- package/src/react/ModuleSection.tsx +12 -3
- package/src/react/ObjectModule.tsx +36 -15
- package/src/react/PromptDialog.tsx +5 -1
- package/src/react/SubEmittersModule.tsx +1 -2
- package/src/react/TimelinePanel.tsx +94 -21
- package/src/react/TimelineTrackRow.tsx +71 -18
- package/src/react/ValueField.tsx +0 -1
- package/src/react/behaviorModules.tsx +143 -29
- package/src/react/index.ts +33 -33
- package/src/react/modules.tsx +173 -57
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import type {ParticleSystem} from 'babylon.quarks';
|
|
2
1
|
import {Color3} from '@babylonjs/core/Maths/math.color';
|
|
3
2
|
import {Vector3} from '@babylonjs/core/Maths/math.vector';
|
|
4
|
-
import {Mesh} from '@babylonjs/core/Meshes/mesh';
|
|
5
3
|
import {LinesMesh} from '@babylonjs/core/Meshes/linesMesh';
|
|
4
|
+
import {Mesh} from '@babylonjs/core/Meshes/mesh';
|
|
6
5
|
import {MeshBuilder} from '@babylonjs/core/Meshes/meshBuilder';
|
|
7
6
|
import {TransformNode} from '@babylonjs/core/Meshes/transformNode';
|
|
8
7
|
import type {Scene} from '@babylonjs/core/scene';
|
|
9
|
-
import {
|
|
8
|
+
import type {ParticleSystem} from 'babylon.quarks';
|
|
10
9
|
import {EDITOR_SCENE_NODE_METADATA} from './editorScene';
|
|
10
|
+
import {getShapeType, readShapeParams} from './shapes';
|
|
11
11
|
|
|
12
12
|
const LINE_COLOR = new Color3(0.45, 0.82, 1);
|
|
13
13
|
const DIRECTION_COLOR = new Color3(0.75, 0.92, 1);
|
|
@@ -125,7 +125,11 @@ function buildEmitterWireframe(system: ParticleSystem, scene: Scene): Mesh[] {
|
|
|
125
125
|
for (let i = 0; i <= spokeCount; i++) {
|
|
126
126
|
const theta = (i / spokeCount) * arc;
|
|
127
127
|
const rim = arcPoint(radius, theta);
|
|
128
|
-
const dir = new Vector3(
|
|
128
|
+
const dir = new Vector3(
|
|
129
|
+
Math.sin(angle) * Math.cos(theta),
|
|
130
|
+
Math.sin(angle) * Math.sin(theta),
|
|
131
|
+
Math.cos(angle)
|
|
132
|
+
);
|
|
129
133
|
const travel = preview / Math.max(Math.cos(angle), 0.05);
|
|
130
134
|
lines(`wf-cone-ray-${i}`, [rim, rim.add(dir.scale(travel))], DIRECTION_COLOR);
|
|
131
135
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import {StandardMaterial} from '@babylonjs/core/Materials/standardMaterial';
|
|
1
2
|
import {Color3} from '@babylonjs/core/Maths/math.color';
|
|
2
3
|
import {Vector3} from '@babylonjs/core/Maths/math.vector';
|
|
3
|
-
import {StandardMaterial} from '@babylonjs/core/Materials/standardMaterial';
|
|
4
4
|
import {Mesh} from '@babylonjs/core/Meshes/mesh';
|
|
5
5
|
import {MeshBuilder} from '@babylonjs/core/Meshes/meshBuilder';
|
|
6
6
|
import {TransformNode} from '@babylonjs/core/Meshes/transformNode';
|
|
@@ -15,7 +15,11 @@ export class GalleryDropMarker {
|
|
|
15
15
|
|
|
16
16
|
constructor(scene: Scene) {
|
|
17
17
|
this.root = new TransformNode('gallery-drop-root', scene);
|
|
18
|
-
this.disc = MeshBuilder.CreateDisc(
|
|
18
|
+
this.disc = MeshBuilder.CreateDisc(
|
|
19
|
+
'gallery-drop-fill',
|
|
20
|
+
{radius: 1, tessellation: 48, sideOrientation: Mesh.DOUBLESIDE},
|
|
21
|
+
scene
|
|
22
|
+
);
|
|
19
23
|
this.disc.parent = this.root;
|
|
20
24
|
this.disc.rotation.x = Math.PI / 2;
|
|
21
25
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {GalleryEntry} from './loadEffectGallery';
|
|
2
1
|
import {QuarksUtil} from 'babylon.quarks';
|
|
2
|
+
import type {GalleryEntry} from './loadEffectGallery';
|
|
3
3
|
|
|
4
4
|
function isPreviewEntry(entry: GalleryEntry, previewRootIds: ReadonlySet<number>): boolean {
|
|
5
5
|
return entry.inScene && previewRootIds.has(entry.root.uniqueId);
|
package/src/core/galleryScene.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {Vector3} from '@babylonjs/core/Maths/math.vector';
|
|
2
|
-
import type {Scene} from '@babylonjs/core/scene';
|
|
3
2
|
import type {TransformNode} from '@babylonjs/core/Meshes/transformNode';
|
|
3
|
+
import type {Scene} from '@babylonjs/core/scene';
|
|
4
4
|
import type {BatchedRenderer} from 'babylon.quarks';
|
|
5
|
-
import type {GalleryEntry} from './loadEffectGallery';
|
|
6
5
|
import {DEFAULT_GALLERY_SPACING, galleryGridPosition} from './galleryLayout';
|
|
6
|
+
import type {GalleryEntry} from './loadEffectGallery';
|
|
7
7
|
|
|
8
8
|
/** Clears live particles and freezes simulation for one placed catalog entry. */
|
|
9
9
|
export function clearGalleryEntrySimulation(entry: GalleryEntry, renderer: BatchedRenderer): void {
|
|
@@ -15,11 +15,7 @@ export function clearGalleryEntrySimulation(entry: GalleryEntry, renderer: Batch
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
/** Parents a catalog entry into the live scene and registers its systems with the renderer. */
|
|
18
|
-
export function placeGalleryEntryInScene(
|
|
19
|
-
entry: GalleryEntry,
|
|
20
|
-
renderer: BatchedRenderer,
|
|
21
|
-
worldPosition: Vector3
|
|
22
|
-
): void {
|
|
18
|
+
export function placeGalleryEntryInScene(entry: GalleryEntry, renderer: BatchedRenderer, worldPosition: Vector3): void {
|
|
23
19
|
entry.root.setEnabled(true);
|
|
24
20
|
entry.root.parent = null;
|
|
25
21
|
entry.root.position.copyFrom(worldPosition);
|
|
@@ -28,7 +28,11 @@ class GroundRingMarker {
|
|
|
28
28
|
|
|
29
29
|
constructor(scene: Scene, name: string, color: Color3) {
|
|
30
30
|
this.anchor = new TransformNode(`${name}-root`, scene);
|
|
31
|
-
this.ring = MeshBuilder.CreateLines(
|
|
31
|
+
this.ring = MeshBuilder.CreateLines(
|
|
32
|
+
`${name}-ring`,
|
|
33
|
+
{points: buildGroundRingPoints(GROUND_RING_RADIUS)},
|
|
34
|
+
scene
|
|
35
|
+
) as LinesMesh;
|
|
32
36
|
this.ring.color = color;
|
|
33
37
|
this.ring.parent = this.anchor;
|
|
34
38
|
this.ring.isPickable = false;
|
package/src/core/galleryTree.ts
CHANGED
|
@@ -29,14 +29,19 @@ export function buildGalleryTree(entries: GalleryEntry[]): GalleryTreeNode[] {
|
|
|
29
29
|
|
|
30
30
|
for (const entry of entries) {
|
|
31
31
|
const normalized = entry.path.replace(/\\/g, '/');
|
|
32
|
-
const segments = normalized
|
|
32
|
+
const segments = normalized
|
|
33
|
+
.replace(/\.json$/i, '')
|
|
34
|
+
.split('/')
|
|
35
|
+
.filter(Boolean);
|
|
33
36
|
const fileName = segments.pop() ?? entry.name;
|
|
34
37
|
let level = roots;
|
|
35
38
|
let pathSoFar = '';
|
|
36
39
|
|
|
37
40
|
for (const segment of segments) {
|
|
38
41
|
pathSoFar = pathSoFar ? `${pathSoFar}/${segment}` : segment;
|
|
39
|
-
let folder = level.find(
|
|
42
|
+
let folder = level.find(
|
|
43
|
+
(node): node is GalleryTreeFolder => node.kind === 'folder' && node.name === segment
|
|
44
|
+
);
|
|
40
45
|
if (!folder) {
|
|
41
46
|
folder = {kind: 'folder', name: segment, path: pathSoFar, children: []};
|
|
42
47
|
level.push(folder);
|
|
@@ -90,9 +95,7 @@ export function filterGalleryTree(nodes: GalleryTreeNode[], query: string): Gall
|
|
|
90
95
|
|
|
91
96
|
export const GALLERY_DRAG_MIME = 'application/x-quarks-gallery-drop';
|
|
92
97
|
|
|
93
|
-
export type GalleryDragPayload =
|
|
94
|
-
| {kind: 'effect'; id: number}
|
|
95
|
-
| {kind: 'folder'; path: string};
|
|
98
|
+
export type GalleryDragPayload = {kind: 'effect'; id: number} | {kind: 'folder'; path: string};
|
|
96
99
|
|
|
97
100
|
export function writeGalleryDrag(dataTransfer: DataTransfer, payload: GalleryDragPayload): void {
|
|
98
101
|
dataTransfer.setData(GALLERY_DRAG_MIME, JSON.stringify(payload));
|
package/src/core/loadEffect.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {ParticleEmitter, QuarksLoader, QuarksUtil} from 'babylon.quarks';
|
|
2
|
-
import type {BatchedRenderer, ParticleSystem} from 'babylon.quarks';
|
|
3
|
-
import type {Scene} from '@babylonjs/core/scene';
|
|
4
1
|
import {TransformNode} from '@babylonjs/core/Meshes/transformNode';
|
|
5
2
|
import type {Node} from '@babylonjs/core/node';
|
|
3
|
+
import type {Scene} from '@babylonjs/core/scene';
|
|
4
|
+
import type {BatchedRenderer, ParticleSystem} from 'babylon.quarks';
|
|
5
|
+
import {ParticleEmitter, QuarksLoader, QuarksUtil} from 'babylon.quarks';
|
|
6
6
|
|
|
7
7
|
/** `ParticleEmitter`'s constructor default (see babylon.quarks/src/ParticleEmitter.ts) — the
|
|
8
8
|
* name every emitter gets when the source JSON didn't give it one of its own. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type {ParticleSystem} from 'babylon.quarks';
|
|
2
|
-
import type {Scene} from '@babylonjs/core/scene';
|
|
3
1
|
import {TransformNode} from '@babylonjs/core/Meshes/transformNode';
|
|
2
|
+
import type {Scene} from '@babylonjs/core/scene';
|
|
3
|
+
import type {ParticleSystem} from 'babylon.quarks';
|
|
4
4
|
import {EffectBinding} from './binding';
|
|
5
5
|
import {disposeLoadedEffect, parseEffectFromJson} from './loadEffect';
|
|
6
6
|
|
|
@@ -105,7 +105,11 @@ export async function loadEffectGallery(
|
|
|
105
105
|
/** Builds an edit binding for one catalog entry without unloading the rest. */
|
|
106
106
|
export function bindingFromGalleryEntry(entry: GalleryEntry): EffectBinding {
|
|
107
107
|
const main = entry.systems.find((s) => !s.onlyUsedByOther) ?? entry.systems[0];
|
|
108
|
-
return new EffectBinding(
|
|
108
|
+
return new EffectBinding(
|
|
109
|
+
main,
|
|
110
|
+
entry.systems.filter((s) => s !== main),
|
|
111
|
+
entry.root
|
|
112
|
+
);
|
|
109
113
|
}
|
|
110
114
|
|
|
111
115
|
/** Merges newly loaded catalog entries, skipping duplicate paths. */
|
|
@@ -29,7 +29,10 @@ async function readJsonFilesFromDirectory(dir: DirectoryHandleWithEntries, prefi
|
|
|
29
29
|
}
|
|
30
30
|
if (entry.kind === 'directory') {
|
|
31
31
|
files.push(
|
|
32
|
-
...(await readJsonFilesFromDirectory(
|
|
32
|
+
...(await readJsonFilesFromDirectory(
|
|
33
|
+
entry as unknown as DirectoryHandleWithEntries,
|
|
34
|
+
`${prefix}${name}/`
|
|
35
|
+
))
|
|
33
36
|
);
|
|
34
37
|
}
|
|
35
38
|
}
|
package/src/core/shapes.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import type {Mesh} from '@babylonjs/core/Meshes/mesh';
|
|
2
|
+
import type {EmitterShape} from 'babylon.quarks';
|
|
1
3
|
import {
|
|
2
4
|
CircleEmitter,
|
|
3
5
|
ConeEmitter,
|
|
4
6
|
DonutEmitter,
|
|
7
|
+
EmitterMode,
|
|
5
8
|
GridEmitter,
|
|
6
9
|
HemisphereEmitter,
|
|
7
10
|
MeshSurfaceEmitter,
|
|
@@ -9,11 +12,17 @@ import {
|
|
|
9
12
|
RectangleEmitter,
|
|
10
13
|
SphereEmitter,
|
|
11
14
|
} from 'babylon.quarks';
|
|
12
|
-
import type {EmitterShape} from 'babylon.quarks';
|
|
13
|
-
import {EmitterMode} from 'babylon.quarks';
|
|
14
|
-
import type {Mesh} from '@babylonjs/core/Meshes/mesh';
|
|
15
15
|
|
|
16
|
-
export type ShapeType =
|
|
16
|
+
export type ShapeType =
|
|
17
|
+
| 'point'
|
|
18
|
+
| 'sphere'
|
|
19
|
+
| 'hemisphere'
|
|
20
|
+
| 'cone'
|
|
21
|
+
| 'circle'
|
|
22
|
+
| 'donut'
|
|
23
|
+
| 'rectangle'
|
|
24
|
+
| 'grid'
|
|
25
|
+
| 'mesh_surface';
|
|
17
26
|
|
|
18
27
|
export const SHAPE_TYPES: Array<{value: ShapeType; label: string}> = [
|
|
19
28
|
{value: 'cone', label: 'Cone'},
|
|
@@ -64,7 +73,8 @@ export function getShapeType(shape: EmitterShape): ShapeType {
|
|
|
64
73
|
|
|
65
74
|
export function readShapeParams(shape: EmitterShape): ShapeParams {
|
|
66
75
|
const anyShape = shape as unknown as {[key: string]: unknown};
|
|
67
|
-
const num = (key: string, fallback: number) =>
|
|
76
|
+
const num = (key: string, fallback: number) =>
|
|
77
|
+
typeof anyShape[key] === 'number' ? (anyShape[key] as number) : fallback;
|
|
68
78
|
return {
|
|
69
79
|
radius: num('radius', DEFAULT_SHAPE_PARAMS.radius),
|
|
70
80
|
mode: num('mode', DEFAULT_SHAPE_PARAMS.mode) as EmitterMode,
|
|
@@ -100,9 +110,21 @@ export function createShape(type: ShapeType, params: ShapeParams): EmitterShape
|
|
|
100
110
|
case 'point':
|
|
101
111
|
return new PointEmitter();
|
|
102
112
|
case 'sphere':
|
|
103
|
-
return new SphereEmitter({
|
|
113
|
+
return new SphereEmitter({
|
|
114
|
+
radius: params.radius,
|
|
115
|
+
arc: params.arc,
|
|
116
|
+
thickness: params.thickness,
|
|
117
|
+
mode: params.mode,
|
|
118
|
+
spread: params.spread,
|
|
119
|
+
});
|
|
104
120
|
case 'hemisphere':
|
|
105
|
-
return new HemisphereEmitter({
|
|
121
|
+
return new HemisphereEmitter({
|
|
122
|
+
radius: params.radius,
|
|
123
|
+
arc: params.arc,
|
|
124
|
+
thickness: params.thickness,
|
|
125
|
+
mode: params.mode,
|
|
126
|
+
spread: params.spread,
|
|
127
|
+
});
|
|
106
128
|
case 'cone':
|
|
107
129
|
return new ConeEmitter({
|
|
108
130
|
radius: params.radius,
|
|
@@ -113,7 +135,13 @@ export function createShape(type: ShapeType, params: ShapeParams): EmitterShape
|
|
|
113
135
|
spread: params.spread,
|
|
114
136
|
});
|
|
115
137
|
case 'circle':
|
|
116
|
-
return new CircleEmitter({
|
|
138
|
+
return new CircleEmitter({
|
|
139
|
+
radius: params.radius,
|
|
140
|
+
arc: params.arc,
|
|
141
|
+
thickness: params.thickness,
|
|
142
|
+
mode: params.mode,
|
|
143
|
+
spread: params.spread,
|
|
144
|
+
});
|
|
117
145
|
case 'donut':
|
|
118
146
|
return new DonutEmitter({
|
|
119
147
|
radius: params.radius,
|
|
@@ -124,9 +152,19 @@ export function createShape(type: ShapeType, params: ShapeParams): EmitterShape
|
|
|
124
152
|
spread: params.spread,
|
|
125
153
|
});
|
|
126
154
|
case 'rectangle':
|
|
127
|
-
return new RectangleEmitter({
|
|
155
|
+
return new RectangleEmitter({
|
|
156
|
+
width: params.width,
|
|
157
|
+
height: params.height,
|
|
158
|
+
mode: params.mode,
|
|
159
|
+
spread: params.spread,
|
|
160
|
+
});
|
|
128
161
|
case 'grid':
|
|
129
|
-
return new GridEmitter({
|
|
162
|
+
return new GridEmitter({
|
|
163
|
+
width: params.width,
|
|
164
|
+
height: params.height,
|
|
165
|
+
column: params.column,
|
|
166
|
+
row: params.row,
|
|
167
|
+
});
|
|
130
168
|
case 'mesh_surface':
|
|
131
169
|
return new MeshSurfaceEmitter(params.mesh);
|
|
132
170
|
}
|
package/src/core/systems.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type {TransformNode} from '@babylonjs/core/Meshes/transformNode';
|
|
1
2
|
import {
|
|
2
3
|
ConeEmitter,
|
|
3
4
|
ConstantColor,
|
|
@@ -7,7 +8,6 @@ import {
|
|
|
7
8
|
RenderMode,
|
|
8
9
|
Vector4,
|
|
9
10
|
} from 'babylon.quarks';
|
|
10
|
-
import type {TransformNode} from '@babylonjs/core/Meshes/transformNode';
|
|
11
11
|
import type {EffectBinding} from './binding';
|
|
12
12
|
|
|
13
13
|
export interface ChildSystemOptions {
|
|
@@ -35,7 +35,9 @@ export function createChildSystem(parent: ParticleSystem, options: ChildSystemOp
|
|
|
35
35
|
startSize: new IntervalValue(0.05, 0.2),
|
|
36
36
|
startColor: new ConstantColor(new Vector4(1, 0.9, 0.6, 1)),
|
|
37
37
|
emissionOverTime: new ConstantValue(oneShot ? 0 : 30),
|
|
38
|
-
emissionBursts: oneShot
|
|
38
|
+
emissionBursts: oneShot
|
|
39
|
+
? [{time: 0, count: new ConstantValue(8), cycle: 1, interval: 0.01, probability: 1}]
|
|
40
|
+
: [],
|
|
39
41
|
shape: new ConeEmitter({radius: 0.1, angle: Math.PI / 3}),
|
|
40
42
|
renderMode: RenderMode.BillBoard,
|
|
41
43
|
texture: parent.texture ?? undefined,
|
|
@@ -70,7 +72,11 @@ export function unlinkSubEmitterReferences(systems: ParticleSystem[], removed: P
|
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
/** Disposes the given systems, first unlinking any sub-emitter behaviors that target them. */
|
|
73
|
-
export function removeSystems(
|
|
75
|
+
export function removeSystems(
|
|
76
|
+
binding: EffectBinding,
|
|
77
|
+
targets: ParticleSystem[],
|
|
78
|
+
onSelect: (system: ParticleSystem) => void
|
|
79
|
+
): void {
|
|
74
80
|
// binding.system is "removable" too as long as some other top-level system can take over
|
|
75
81
|
// as the new main — being main vs. sub is an internal bookkeeping detail, not something
|
|
76
82
|
// that should make one arbitrary track un-deletable in the UI.
|
package/src/core/timeline.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import type {ParticleSystem} from 'babylon.quarks';
|
|
2
|
-
import {ConstantValue, IntervalValue} from 'babylon.quarks';
|
|
3
|
-
import type {FunctionValueGenerator, ValueGenerator} from 'babylon.quarks';
|
|
4
1
|
import type {TransformNode} from '@babylonjs/core/Meshes/transformNode';
|
|
2
|
+
import type {FunctionValueGenerator, ParticleSystem, ValueGenerator} from 'babylon.quarks';
|
|
3
|
+
import {ConstantValue, IntervalValue} from 'babylon.quarks';
|
|
5
4
|
import type {EffectBinding} from './binding';
|
|
6
5
|
import type {EffectTreeNode} from './effectTree';
|
|
7
6
|
|
|
@@ -29,7 +28,10 @@ export type TimelineRow =
|
|
|
29
28
|
* stateful `genValue` (IntervalValue caches its sampled slot on first call and returns NaN
|
|
30
29
|
* on a later call with a fresh memory array, so it can't be safely re-read on every render).
|
|
31
30
|
*/
|
|
32
|
-
export function computeStartOffset(gen: ValueGenerator | FunctionValueGenerator): {
|
|
31
|
+
export function computeStartOffset(gen: ValueGenerator | FunctionValueGenerator): {
|
|
32
|
+
value: number;
|
|
33
|
+
startOffsetVariable: boolean;
|
|
34
|
+
} {
|
|
33
35
|
if (gen instanceof ConstantValue) {
|
|
34
36
|
return {value: gen.value, startOffsetVariable: false};
|
|
35
37
|
}
|
|
@@ -97,10 +99,8 @@ function estimateMaxStartLife(system: ParticleSystem): number {
|
|
|
97
99
|
|
|
98
100
|
/** When emission is burst-only, the effect window ends at the last burst time, not full duration. */
|
|
99
101
|
function estimateEmissionEnd(system: ParticleSystem): number {
|
|
100
|
-
const rate =
|
|
101
|
-
|
|
102
|
-
const perMeter =
|
|
103
|
-
system.emissionOverDistance instanceof ConstantValue ? system.emissionOverDistance.value : null;
|
|
102
|
+
const rate = system.emissionOverTime instanceof ConstantValue ? system.emissionOverTime.value : null;
|
|
103
|
+
const perMeter = system.emissionOverDistance instanceof ConstantValue ? system.emissionOverDistance.value : null;
|
|
104
104
|
if ((rate != null && rate > 0) || (perMeter != null && perMeter > 0)) {
|
|
105
105
|
return system.duration;
|
|
106
106
|
}
|
package/src/core/values.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {Bezier, ConstantValue, IntervalValue, PiecewiseBezier} from 'babylon.quarks';
|
|
2
1
|
import type {FunctionValueGenerator, ValueGenerator} from 'babylon.quarks';
|
|
2
|
+
import {Bezier, ConstantValue, IntervalValue, PiecewiseBezier} from 'babylon.quarks';
|
|
3
3
|
|
|
4
4
|
export type ScalarGenerator = ValueGenerator | FunctionValueGenerator;
|
|
5
5
|
export type ScalarMode = 'constant' | 'random' | 'curve';
|
|
@@ -3,8 +3,8 @@ import {MeshBuilder} from '@babylonjs/core/Meshes/meshBuilder';
|
|
|
3
3
|
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
|
-
import type {GalleryEntry} from './loadEffectGallery';
|
|
7
6
|
import {GROUND_RING_RADIUS, GalleryHoverMarker} from './gallerySelectionMarker';
|
|
7
|
+
import type {GalleryEntry} from './loadEffectGallery';
|
|
8
8
|
|
|
9
9
|
interface PickMetadata {
|
|
10
10
|
quarksEditorPick: true;
|
package/src/index.ts
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
export {EffectBinding} from './core/binding';
|
|
2
2
|
export type {EditorListener} from './core/binding';
|
|
3
|
-
export {
|
|
4
|
-
export {createChildSystem} from './core/systems';
|
|
5
|
-
export {buildEffectTree, collectSystems, serializeEffectTree, serializeEffectForest} from './core/effectTree';
|
|
6
|
-
export type {EffectTreeNode} from './core/effectTree';
|
|
7
|
-
export type {ChildSystemOptions} from './core/systems';
|
|
8
|
-
export {readScalar, buildScalar, sampleCurve, readPieces, buildCurve, splitPieces, mergePieces} from './core/values';
|
|
9
|
-
export type {ScalarGenerator, ScalarMode, ScalarValueState, CurvePiece} from './core/values';
|
|
3
|
+
export {GroundPlaneResolver, ensureGroundResolver} from './core/collision';
|
|
10
4
|
export {
|
|
11
5
|
DEFAULT_GRADIENT_STOPS,
|
|
12
|
-
buildGradient,
|
|
13
6
|
buildColorOverLife,
|
|
7
|
+
buildGradient,
|
|
8
|
+
findBehavior,
|
|
9
|
+
hexToRgb,
|
|
10
|
+
rgbToHex,
|
|
14
11
|
rgbaToVector4,
|
|
15
12
|
stopToCss,
|
|
16
13
|
stopsToCssGradient,
|
|
17
|
-
hexToRgb,
|
|
18
|
-
rgbToHex,
|
|
19
|
-
findBehavior,
|
|
20
14
|
} from './core/colors';
|
|
21
15
|
export type {GradientStop} from './core/colors';
|
|
16
|
+
export {buildEffectTree, collectSystems, serializeEffectForest, serializeEffectTree} from './core/effectTree';
|
|
17
|
+
export type {EffectTreeNode} from './core/effectTree';
|
|
18
|
+
export {EffectHistory} from './core/history';
|
|
19
|
+
export {applyRendererMaterial, getMaterialLabel} from './core/material';
|
|
20
|
+
export type {RendererMaterialPatch} from './core/material';
|
|
22
21
|
export {
|
|
23
|
-
SHAPE_TYPES,
|
|
24
|
-
SHAPE_PARAM_KEYS,
|
|
25
22
|
DEFAULT_SHAPE_PARAMS,
|
|
23
|
+
SHAPE_PARAM_KEYS,
|
|
24
|
+
SHAPE_TYPES,
|
|
25
|
+
createShape,
|
|
26
26
|
getShapeType,
|
|
27
27
|
readShapeParams,
|
|
28
|
-
createShape,
|
|
29
28
|
} from './core/shapes';
|
|
30
|
-
export type {
|
|
31
|
-
export {
|
|
32
|
-
export {
|
|
33
|
-
export type {RendererMaterialPatch} from './core/material';
|
|
29
|
+
export type {ShapeParams, ShapeType} from './core/shapes';
|
|
30
|
+
export {createChildSystem} from './core/systems';
|
|
31
|
+
export type {ChildSystemOptions} from './core/systems';
|
|
34
32
|
export {ensurePortableTextureUrl} from './core/textureExport';
|
|
33
|
+
export {buildCurve, buildScalar, mergePieces, readPieces, readScalar, sampleCurve, splitPieces} from './core/values';
|
|
34
|
+
export type {CurvePiece, ScalarGenerator, ScalarMode, ScalarValueState} from './core/values';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {useRef, useState} from 'react';
|
|
2
2
|
import {CurvePiece, mergePieces, sampleCurve, splitPieces} from '../core/values';
|
|
3
3
|
import {inputStyle, theme} from './theme';
|
|
4
4
|
|
|
@@ -17,7 +17,11 @@ interface PointRef {
|
|
|
17
17
|
* drag inner keys horizontally, double-click the curve to add a key, double-click
|
|
18
18
|
* a key to remove it (Unity-like).
|
|
19
19
|
*/
|
|
20
|
-
export function CurveEditor(props: {
|
|
20
|
+
export function CurveEditor(props: {
|
|
21
|
+
pieces: CurvePiece[];
|
|
22
|
+
onChange: (pieces: CurvePiece[]) => void;
|
|
23
|
+
maxValue?: number;
|
|
24
|
+
}) {
|
|
21
25
|
const svgRef = useRef<SVGSVGElement>(null);
|
|
22
26
|
const drag = useRef<PointRef | null>(null);
|
|
23
27
|
const [selected, setSelected] = useState<PointRef | null>(null);
|
|
@@ -29,7 +33,8 @@ export function CurveEditor(props: {pieces: CurvePiece[]; onChange: (pieces: Cur
|
|
|
29
33
|
const spanEnd = (i: number) => (i + 1 < pieces.length ? pieces[i + 1].start : 1);
|
|
30
34
|
const toX = (x: number) => PAD + x * (WIDTH - PAD * 2);
|
|
31
35
|
const toY = (v: number) => HEIGHT - PAD - (v / max) * (HEIGHT - PAD * 2);
|
|
32
|
-
const fromY = (y: number) =>
|
|
36
|
+
const fromY = (y: number) =>
|
|
37
|
+
Math.round(Math.min(max, Math.max(0, ((HEIGHT - PAD - y) / (HEIGHT - PAD * 2)) * max)) * 1000) / 1000;
|
|
33
38
|
const fromX = (px: number) => Math.min(1, Math.max(0, (px - PAD) / (WIDTH - PAD * 2)));
|
|
34
39
|
|
|
35
40
|
// Control point positions: piece start key, two handles, plus the final end key.
|
|
@@ -50,7 +55,9 @@ export function CurveEditor(props: {pieces: CurvePiece[]; onChange: (pieces: Cur
|
|
|
50
55
|
const end = spanEnd(i);
|
|
51
56
|
for (let s = 0; s <= 16; s++) {
|
|
52
57
|
const t = s / 16;
|
|
53
|
-
samples.push(
|
|
58
|
+
samples.push(
|
|
59
|
+
`${toX(piece.start + t * (end - piece.start)).toFixed(1)},${toY(sampleCurve(piece.p, t)).toFixed(1)}`
|
|
60
|
+
);
|
|
54
61
|
}
|
|
55
62
|
});
|
|
56
63
|
|
|
@@ -81,7 +88,9 @@ export function CurveEditor(props: {pieces: CurvePiece[]; onChange: (pieces: Cur
|
|
|
81
88
|
props.onChange(next);
|
|
82
89
|
};
|
|
83
90
|
|
|
84
|
-
const selectedPoint = selected
|
|
91
|
+
const selectedPoint = selected
|
|
92
|
+
? points.find((pt) => pt.ref.piece === selected.piece && pt.ref.idx === selected.idx)
|
|
93
|
+
: null;
|
|
85
94
|
|
|
86
95
|
return (
|
|
87
96
|
<div>
|
|
@@ -89,7 +98,13 @@ export function CurveEditor(props: {pieces: CurvePiece[]; onChange: (pieces: Cur
|
|
|
89
98
|
ref={svgRef}
|
|
90
99
|
width={WIDTH}
|
|
91
100
|
height={HEIGHT}
|
|
92
|
-
style={{
|
|
101
|
+
style={{
|
|
102
|
+
background: 'rgba(5, 8, 18, 0.85)',
|
|
103
|
+
border: `1px solid ${theme.border}`,
|
|
104
|
+
borderRadius: 8,
|
|
105
|
+
touchAction: 'none',
|
|
106
|
+
display: 'block',
|
|
107
|
+
}}
|
|
93
108
|
onPointerMove={(e) => applyDrag(e.clientX, e.clientY)}
|
|
94
109
|
onPointerUp={(e) => {
|
|
95
110
|
drag.current = null;
|
|
@@ -104,7 +119,15 @@ export function CurveEditor(props: {pieces: CurvePiece[]; onChange: (pieces: Cur
|
|
|
104
119
|
}}
|
|
105
120
|
>
|
|
106
121
|
{[0.25, 0.5, 0.75].map((f) => (
|
|
107
|
-
<line
|
|
122
|
+
<line
|
|
123
|
+
key={f}
|
|
124
|
+
x1={PAD}
|
|
125
|
+
x2={WIDTH - PAD}
|
|
126
|
+
y1={PAD + f * (HEIGHT - PAD * 2)}
|
|
127
|
+
y2={PAD + f * (HEIGHT - PAD * 2)}
|
|
128
|
+
stroke="#1c274a"
|
|
129
|
+
strokeWidth={1}
|
|
130
|
+
/>
|
|
108
131
|
))}
|
|
109
132
|
<polyline points={samples.join(' ')} fill="none" stroke={theme.curveStroke} strokeWidth={2} />
|
|
110
133
|
{points.map((pt) => (
|
|
@@ -148,7 +171,10 @@ export function CurveEditor(props: {pieces: CurvePiece[]; onChange: (pieces: Cur
|
|
|
148
171
|
if (Number.isNaN(parsed)) {
|
|
149
172
|
return;
|
|
150
173
|
}
|
|
151
|
-
const next = pieces.map((piece) => ({
|
|
174
|
+
const next = pieces.map((piece) => ({
|
|
175
|
+
start: piece.start,
|
|
176
|
+
p: [...piece.p] as CurvePiece['p'],
|
|
177
|
+
}));
|
|
152
178
|
const {piece, idx} = selectedPoint.ref;
|
|
153
179
|
if (idx === 0 && piece > 0) {
|
|
154
180
|
next[piece - 1].p[3] = parsed;
|
|
@@ -157,10 +183,14 @@ export function CurveEditor(props: {pieces: CurvePiece[]; onChange: (pieces: Cur
|
|
|
157
183
|
props.onChange(next);
|
|
158
184
|
}}
|
|
159
185
|
/>
|
|
160
|
-
<span style={{fontSize: 10.5, color: theme.textDim}}>
|
|
186
|
+
<span style={{fontSize: 10.5, color: theme.textDim}}>
|
|
187
|
+
t={Math.round(selectedPoint.x * 100) / 100}
|
|
188
|
+
</span>
|
|
161
189
|
</>
|
|
162
190
|
) : (
|
|
163
|
-
<span style={{fontSize: 10.5, color: theme.textDim}}>
|
|
191
|
+
<span style={{fontSize: 10.5, color: theme.textDim}}>
|
|
192
|
+
dbl-click curve: add key · dbl-click key: remove
|
|
193
|
+
</span>
|
|
164
194
|
)}
|
|
165
195
|
</div>
|
|
166
196
|
</div>
|
|
@@ -1,16 +1,7 @@
|
|
|
1
|
-
import React, {useSyncExternalStore} from 'react';
|
|
2
1
|
import type {TransformNode} from '@babylonjs/core/Meshes/transformNode';
|
|
2
|
+
import type {ParticleSystem} from 'babylon.quarks';
|
|
3
|
+
import React, {useSyncExternalStore} from 'react';
|
|
3
4
|
import {EffectBinding} from '../core/binding';
|
|
4
|
-
import {ObjectModule} from './ObjectModule';
|
|
5
|
-
import {
|
|
6
|
-
ColorOverLifeModule,
|
|
7
|
-
EmissionModule,
|
|
8
|
-
MainModule,
|
|
9
|
-
RendererModule,
|
|
10
|
-
ShapeModule,
|
|
11
|
-
SizeOverLifeModule,
|
|
12
|
-
} from './modules';
|
|
13
|
-
import type {GeometryData, GeometryOption, TextureOption} from './modules';
|
|
14
5
|
import {
|
|
15
6
|
CollisionModule,
|
|
16
7
|
ColorBySpeedModule,
|
|
@@ -30,9 +21,18 @@ import {
|
|
|
30
21
|
VelocityOverLifeModule,
|
|
31
22
|
WidthOverTrailModule,
|
|
32
23
|
} from './behaviorModules';
|
|
24
|
+
import type {GeometryData, GeometryOption, TextureOption} from './modules';
|
|
25
|
+
import {
|
|
26
|
+
ColorOverLifeModule,
|
|
27
|
+
EmissionModule,
|
|
28
|
+
MainModule,
|
|
29
|
+
RendererModule,
|
|
30
|
+
ShapeModule,
|
|
31
|
+
SizeOverLifeModule,
|
|
32
|
+
} from './modules';
|
|
33
|
+
import {ObjectModule} from './ObjectModule';
|
|
33
34
|
import {SubEmittersModule} from './SubEmittersModule';
|
|
34
35
|
import {theme} from './theme';
|
|
35
|
-
import type {ParticleSystem} from 'babylon.quarks';
|
|
36
36
|
|
|
37
37
|
export interface EffectEditorProps {
|
|
38
38
|
binding: EffectBinding;
|
|
@@ -85,7 +85,8 @@ export function EffectEditor(props: EffectEditorProps) {
|
|
|
85
85
|
<ObjectModule node={props.gizmoTargetNode} label={props.gizmoTargetLabel} />
|
|
86
86
|
{!gizmoTargetIsEmitter && (
|
|
87
87
|
<div style={{fontSize: 11, color: theme.textDim, margin: '0 0 10px', lineHeight: 1.45}}>
|
|
88
|
-
Modules below edit <strong>{activeSystem.emitter.name || 'particle system'}</strong>. Select its
|
|
88
|
+
Modules below edit <strong>{activeSystem.emitter.name || 'particle system'}</strong>. Select its
|
|
89
|
+
emitter in the timeline to move it with the gizmo.
|
|
89
90
|
</div>
|
|
90
91
|
)}
|
|
91
92
|
<MainModule binding={binding} />
|