babylon.quarks-editor 0.26.0 → 0.27.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 +63 -0
- package/README.md +38 -5
- package/dist/core/assetDragSession.d.ts +1 -1
- package/dist/core/assetTree.d.ts +1 -1
- package/dist/core/binding.d.ts +2 -2
- package/dist/core/binding.js +1 -1
- package/dist/core/defaultEffect.d.ts +2 -2
- package/dist/core/defaultEffect.js +3 -3
- package/dist/core/editorScene.d.ts +1 -1
- package/dist/core/effectTree.d.ts +1 -1
- package/dist/core/effectTree.js +13 -88
- package/dist/core/emitterShapeWireframe.d.ts +2 -2
- package/dist/core/emitterShapeWireframe.js +45 -13
- package/dist/core/galleryDropMarker.d.ts +2 -2
- package/dist/core/galleryDropMarker.js +6 -6
- package/dist/core/galleryLayout.d.ts +3 -3
- package/dist/core/galleryLayout.js +1 -1
- package/dist/core/galleryPlayback.d.ts +1 -1
- package/dist/core/galleryScene.d.ts +4 -4
- package/dist/core/galleryScene.js +3 -3
- package/dist/core/gallerySelectionMarker.d.ts +4 -4
- package/dist/core/gallerySelectionMarker.js +5 -5
- package/dist/core/groups.d.ts +1 -1
- package/dist/core/groups.js +1 -1
- package/dist/core/history.d.ts +1 -1
- package/dist/core/indexedDbProjectStore.d.ts +1 -1
- package/dist/core/lineShaders.d.ts +2 -2
- package/dist/core/lineShaders.js +2 -2
- package/dist/core/loadEffect.d.ts +2 -2
- package/dist/core/loadEffect.js +1 -1
- package/dist/core/pickAssetFiles.js +1 -1
- package/dist/core/sceneEntries.d.ts +4 -4
- package/dist/core/sceneEntries.js +2 -2
- package/dist/core/scrub.d.ts +1 -1
- package/dist/core/shapes.d.ts +1 -1
- package/dist/core/systems.d.ts +2 -2
- package/dist/core/textureExport.d.ts +1 -1
- package/dist/core/timeline.d.ts +2 -2
- package/dist/core/transport.d.ts +3 -3
- package/dist/core/transport.js +2 -2
- package/dist/core/viewportPicker.d.ts +4 -4
- package/dist/core/viewportPicker.js +8 -7
- package/dist/index.d.ts +21 -21
- package/dist/index.js +12 -12
- package/dist/react/AssetsPanel.d.ts +2 -2
- package/dist/react/AssetsPanel.js +6 -6
- package/dist/react/ChoiceDialog.js +1 -1
- package/dist/react/ContextMenu.js +1 -1
- package/dist/react/CurveEditor.d.ts +1 -1
- package/dist/react/CurveEditor.js +2 -2
- package/dist/react/EditorWorkspace.d.ts +2 -2
- package/dist/react/EditorWorkspace.js +4 -4
- package/dist/react/EffectEditor.d.ts +3 -3
- package/dist/react/EffectEditor.js +7 -7
- package/dist/react/EffectEditorHost.d.ts +6 -6
- package/dist/react/EffectEditorHost.js +59 -62
- package/dist/react/EffectTreeView.d.ts +1 -1
- package/dist/react/EffectTreeView.js +3 -3
- package/dist/react/GradientEditor.d.ts +1 -1
- package/dist/react/GradientEditor.js +3 -3
- package/dist/react/InspectorInfoIcon.js +3 -3
- package/dist/react/InspectorLabel.js +3 -3
- package/dist/react/MessageDialog.js +1 -1
- package/dist/react/ModuleSection.js +4 -4
- package/dist/react/ObjectModule.d.ts +1 -1
- package/dist/react/ObjectModule.js +4 -4
- package/dist/react/ProjectsPage.d.ts +1 -1
- package/dist/react/ProjectsPage.js +6 -6
- package/dist/react/PromptDialog.js +1 -1
- package/dist/react/ScenePanel.d.ts +2 -2
- package/dist/react/ScenePanel.js +5 -5
- package/dist/react/SubEmittersModule.d.ts +1 -1
- package/dist/react/SubEmittersModule.js +5 -5
- package/dist/react/TextureSelect.d.ts +24 -0
- package/dist/react/TextureSelect.js +74 -0
- package/dist/react/TextureUVField.d.ts +14 -0
- package/dist/react/TextureUVField.js +23 -0
- package/dist/react/TimelinePanel.d.ts +7 -7
- package/dist/react/TimelinePanel.js +11 -9
- package/dist/react/TimelineTrackRow.d.ts +3 -3
- package/dist/react/TimelineTrackRow.js +6 -6
- package/dist/react/TransportBar.d.ts +4 -4
- package/dist/react/TransportBar.js +3 -3
- package/dist/react/ValueField.d.ts +1 -1
- package/dist/react/ValueField.js +3 -3
- package/dist/react/behaviorModules.d.ts +4 -2
- package/dist/react/behaviorModules.js +52 -16
- package/dist/react/fields.d.ts +1 -0
- package/dist/react/fields.js +3 -3
- package/dist/react/index.d.ts +40 -38
- package/dist/react/index.js +25 -23
- package/dist/react/inspectorHints.js +31 -0
- package/dist/react/modules.d.ts +3 -1
- package/dist/react/modules.js +99 -100
- package/package.json +3 -2
- package/src/core/assetDragSession.ts +1 -1
- package/src/core/assetTree.ts +1 -1
- package/src/core/binding.ts +3 -3
- package/src/core/defaultEffect.ts +4 -4
- package/src/core/editorScene.ts +1 -1
- package/src/core/effectTree.ts +13 -101
- package/src/core/emitterShapeWireframe.ts +48 -16
- package/src/core/galleryDropMarker.ts +8 -8
- package/src/core/galleryLayout.ts +4 -4
- package/src/core/galleryPlayback.ts +1 -1
- package/src/core/galleryScene.ts +5 -5
- package/src/core/gallerySelectionMarker.ts +7 -7
- package/src/core/groups.ts +1 -1
- package/src/core/history.ts +1 -1
- package/src/core/indexedDbProjectStore.ts +1 -1
- package/src/core/lineShaders.ts +2 -2
- package/src/core/loadEffect.ts +3 -3
- package/src/core/pickAssetFiles.ts +2 -2
- package/src/core/sceneEntries.ts +5 -5
- package/src/core/scrub.ts +1 -1
- package/src/core/shapes.ts +1 -1
- package/src/core/systems.ts +2 -2
- package/src/core/textureExport.ts +1 -1
- package/src/core/timeline.ts +3 -3
- package/src/core/transport.ts +4 -4
- package/src/core/viewportPicker.ts +12 -11
- package/src/index.ts +21 -21
- package/src/react/AssetsPanel.tsx +6 -6
- package/src/react/ChoiceDialog.tsx +1 -1
- package/src/react/ContextMenu.tsx +1 -1
- package/src/react/CurveEditor.tsx +2 -2
- package/src/react/EditorWorkspace.tsx +5 -5
- package/src/react/EffectEditor.tsx +10 -8
- package/src/react/EffectEditorHost.tsx +64 -69
- package/src/react/EffectTreeView.tsx +4 -4
- package/src/react/GradientEditor.tsx +3 -3
- package/src/react/InspectorInfoIcon.tsx +3 -3
- package/src/react/InspectorLabel.tsx +3 -3
- package/src/react/MessageDialog.tsx +1 -1
- package/src/react/ModuleSection.tsx +4 -4
- package/src/react/ObjectModule.tsx +5 -5
- package/src/react/ProjectsPage.tsx +6 -6
- package/src/react/PromptDialog.tsx +1 -1
- package/src/react/ScenePanel.tsx +7 -7
- package/src/react/SubEmittersModule.tsx +6 -6
- package/src/react/TextureSelect.tsx +118 -0
- package/src/react/TextureUVField.tsx +62 -0
- package/src/react/TimelinePanel.tsx +17 -15
- package/src/react/TimelineTrackRow.tsx +9 -9
- package/src/react/TransportBar.tsx +7 -7
- package/src/react/ValueField.tsx +3 -3
- package/src/react/behaviorModules.tsx +211 -28
- package/src/react/fields.tsx +10 -3
- package/src/react/index.ts +41 -38
- package/src/react/inspectorHints.ts +40 -0
- package/src/react/modules.tsx +375 -131
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type {TransformNode} from '@babylonjs/core/Meshes/transformNode';
|
|
1
|
+
import type {TransformNode} from '@babylonjs/core/Meshes/transformNode.js';
|
|
2
2
|
import type {ParticleSystem} from 'babylon.quarks';
|
|
3
3
|
import React, {useEffect, useSyncExternalStore} from 'react';
|
|
4
|
-
import {EffectBinding} from '../core/binding';
|
|
4
|
+
import {EffectBinding} from '../core/binding.js';
|
|
5
5
|
import {
|
|
6
|
+
AttractorModule,
|
|
6
7
|
CollisionModule,
|
|
7
8
|
ColorBySpeedModule,
|
|
8
9
|
EmitDirectionModule,
|
|
@@ -22,8 +23,8 @@ import {
|
|
|
22
23
|
TurbulenceModule,
|
|
23
24
|
VelocityOverLifeModule,
|
|
24
25
|
WidthOverTrailModule,
|
|
25
|
-
} from './behaviorModules';
|
|
26
|
-
import type {GeometryData, GeometryOption, TextureOption} from './modules';
|
|
26
|
+
} from './behaviorModules.js';
|
|
27
|
+
import type {GeometryData, GeometryOption, TextureOption} from './modules.js';
|
|
27
28
|
import {
|
|
28
29
|
ColorOverLifeModule,
|
|
29
30
|
EmissionModule,
|
|
@@ -31,10 +32,10 @@ import {
|
|
|
31
32
|
RendererModule,
|
|
32
33
|
ShapeModule,
|
|
33
34
|
SizeOverLifeModule,
|
|
34
|
-
} from './modules';
|
|
35
|
-
import {ObjectModule} from './ObjectModule';
|
|
36
|
-
import {SubEmittersModule} from './SubEmittersModule';
|
|
37
|
-
import {theme} from './theme';
|
|
35
|
+
} from './modules.js';
|
|
36
|
+
import {ObjectModule} from './ObjectModule.js';
|
|
37
|
+
import {SubEmittersModule} from './SubEmittersModule.js';
|
|
38
|
+
import {theme} from './theme.js';
|
|
38
39
|
|
|
39
40
|
export interface EffectEditorProps {
|
|
40
41
|
binding: EffectBinding;
|
|
@@ -112,6 +113,7 @@ export const EffectEditor = React.memo(function EffectEditor(props: EffectEditor
|
|
|
112
113
|
<InheritVelocityModule binding={binding} />
|
|
113
114
|
<ForceOverLifeModule binding={binding} />
|
|
114
115
|
<GravityModule binding={binding} />
|
|
116
|
+
<AttractorModule binding={binding} />
|
|
115
117
|
<OrbitOverLifeModule binding={binding} />
|
|
116
118
|
<ColorOverLifeModule binding={binding} />
|
|
117
119
|
<ColorBySpeedModule binding={binding} />
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import {ArcRotateCamera} from '@babylonjs/core/Cameras/arcRotateCamera';
|
|
1
|
+
import {ArcRotateCamera} from '@babylonjs/core/Cameras/arcRotateCamera.js';
|
|
2
2
|
// scene.pick lives in Ray's side effect (Babylon 9.31 no longer pulls it in); gizmos pick through it.
|
|
3
|
-
import '@babylonjs/core/Culling/ray';
|
|
4
|
-
import {Engine} from '@babylonjs/core/Engines/engine';
|
|
5
|
-
import {PointerEventTypes} from '@babylonjs/core/Events/pointerEvents';
|
|
6
|
-
import {GizmoManager} from '@babylonjs/core/Gizmos/gizmoManager';
|
|
7
|
-
import {DirectionalLight} from '@babylonjs/core/Lights/directionalLight';
|
|
8
|
-
import {Color3, Color4} from '@babylonjs/core/Maths/math.color';
|
|
9
|
-
import {Vector3 as BVector3} from '@babylonjs/core/Maths/math.vector';
|
|
10
|
-
import {Viewport} from '@babylonjs/core/Maths/math.viewport';
|
|
11
|
-
import type {Mesh} from '@babylonjs/core/Meshes/mesh';
|
|
12
|
-
import {MeshBuilder} from '@babylonjs/core/Meshes/meshBuilder';
|
|
13
|
-
import type {TransformNode} from '@babylonjs/core/Meshes/transformNode';
|
|
14
|
-
import {UtilityLayerRenderer} from '@babylonjs/core/Rendering/utilityLayerRenderer';
|
|
15
|
-
import {Scene} from '@babylonjs/core/scene';
|
|
16
|
-
import {GridMaterial} from '@babylonjs/materials/grid/gridMaterial';
|
|
3
|
+
import '@babylonjs/core/Culling/ray.js';
|
|
4
|
+
import {Engine} from '@babylonjs/core/Engines/engine.js';
|
|
5
|
+
import {PointerEventTypes} from '@babylonjs/core/Events/pointerEvents.js';
|
|
6
|
+
import {GizmoManager} from '@babylonjs/core/Gizmos/gizmoManager.js';
|
|
7
|
+
import {DirectionalLight} from '@babylonjs/core/Lights/directionalLight.js';
|
|
8
|
+
import {Color3, Color4} from '@babylonjs/core/Maths/math.color.js';
|
|
9
|
+
import {Vector3 as BVector3} from '@babylonjs/core/Maths/math.vector.js';
|
|
10
|
+
import {Viewport} from '@babylonjs/core/Maths/math.viewport.js';
|
|
11
|
+
import type {Mesh} from '@babylonjs/core/Meshes/mesh.js';
|
|
12
|
+
import {MeshBuilder} from '@babylonjs/core/Meshes/meshBuilder.js';
|
|
13
|
+
import type {TransformNode} from '@babylonjs/core/Meshes/transformNode.js';
|
|
14
|
+
import {UtilityLayerRenderer} from '@babylonjs/core/Rendering/utilityLayerRenderer.js';
|
|
15
|
+
import {Scene} from '@babylonjs/core/scene.js';
|
|
16
|
+
import {GridMaterial} from '@babylonjs/materials/grid/gridMaterial.js';
|
|
17
17
|
import {
|
|
18
18
|
ArrowLeftIcon,
|
|
19
19
|
ArrowPathIcon,
|
|
@@ -31,8 +31,8 @@ import {
|
|
|
31
31
|
import {BatchedRenderer, QuarksUtil} from 'babylon.quarks';
|
|
32
32
|
import React, {useCallback, useDeferredValue, useEffect, useMemo, useReducer, useRef, useState} from 'react';
|
|
33
33
|
import {createRoot} from 'react-dom/client';
|
|
34
|
-
import {getAssetDragSession, setAssetDragSession} from '../core/assetDragSession';
|
|
35
|
-
import {ASSET_DRAG_MIME, describeAssetDrag, readAssetDrag, type AssetDragPayload} from '../core/assetTree';
|
|
34
|
+
import {getAssetDragSession, setAssetDragSession} from '../core/assetDragSession.js';
|
|
35
|
+
import {ASSET_DRAG_MIME, describeAssetDrag, readAssetDrag, type AssetDragPayload} from '../core/assetTree.js';
|
|
36
36
|
import {
|
|
37
37
|
collectJsonFiles,
|
|
38
38
|
createEffectAsset,
|
|
@@ -41,24 +41,24 @@ import {
|
|
|
41
41
|
removeAssets,
|
|
42
42
|
type AssetLoadProgress,
|
|
43
43
|
type EffectAsset,
|
|
44
|
-
} from '../core/assets';
|
|
45
|
-
import {EffectBinding} from '../core/binding';
|
|
46
|
-
import {createSceneDefaultEntry} from '../core/defaultEffect';
|
|
47
|
-
import {serializeEffectTree} from '../core/effectTree';
|
|
48
|
-
import {EmitterShapeWireframes} from '../core/emitterShapeWireframe';
|
|
49
|
-
import {GalleryDropMarker} from '../core/galleryDropMarker';
|
|
50
|
-
import {cancelGalleryCameraFocus, focusGalleryEntry} from '../core/galleryLayout';
|
|
44
|
+
} from '../core/assets.js';
|
|
45
|
+
import {EffectBinding} from '../core/binding.js';
|
|
46
|
+
import {createSceneDefaultEntry} from '../core/defaultEffect.js';
|
|
47
|
+
import {serializeEffectTree} from '../core/effectTree.js';
|
|
48
|
+
import {EmitterShapeWireframes} from '../core/emitterShapeWireframe.js';
|
|
49
|
+
import {GalleryDropMarker} from '../core/galleryDropMarker.js';
|
|
50
|
+
import {cancelGalleryCameraFocus, focusGalleryEntry} from '../core/galleryLayout.js';
|
|
51
51
|
import {
|
|
52
52
|
applyGalleryPlayback,
|
|
53
53
|
countGalleryParticles,
|
|
54
54
|
hasActivePreview,
|
|
55
55
|
pauseFinishedPreviews,
|
|
56
|
-
} from '../core/galleryPlayback';
|
|
57
|
-
import {pickGroundPosition, placeSceneEntriesAt, setSceneEntryVisible} from '../core/galleryScene';
|
|
58
|
-
import {GallerySelectionMarker} from '../core/gallerySelectionMarker';
|
|
59
|
-
import {EffectHistory, type HistoryStacks} from '../core/history';
|
|
60
|
-
import {parseEffectFromJson} from '../core/loadEffect';
|
|
61
|
-
import {pickAssetFolderFiles} from '../core/pickAssetFiles';
|
|
56
|
+
} from '../core/galleryPlayback.js';
|
|
57
|
+
import {pickGroundPosition, placeSceneEntriesAt, setSceneEntryVisible} from '../core/galleryScene.js';
|
|
58
|
+
import {GallerySelectionMarker} from '../core/gallerySelectionMarker.js';
|
|
59
|
+
import {EffectHistory, type HistoryStacks} from '../core/history.js';
|
|
60
|
+
import {parseEffectFromJson} from '../core/loadEffect.js';
|
|
61
|
+
import {pickAssetFolderFiles} from '../core/pickAssetFiles.js';
|
|
62
62
|
import {
|
|
63
63
|
clearFocusFinished,
|
|
64
64
|
createPlaybackState,
|
|
@@ -67,33 +67,33 @@ import {
|
|
|
67
67
|
markFocusFinished,
|
|
68
68
|
setFocusElapsed,
|
|
69
69
|
type PlaybackState,
|
|
70
|
-
} from '../core/playback';
|
|
71
|
-
import type {ProjectStore, StoredAsset, StoredScene} from '../core/projectStore';
|
|
72
|
-
import {logRenderMismatch} from '../core/renderDebug';
|
|
70
|
+
} from '../core/playback.js';
|
|
71
|
+
import type {ProjectStore, StoredAsset, StoredScene} from '../core/projectStore.js';
|
|
72
|
+
import {logRenderMismatch} from '../core/renderDebug.js';
|
|
73
73
|
import {
|
|
74
74
|
bindingFromSceneEntry,
|
|
75
75
|
countInstancesByAsset,
|
|
76
76
|
disposeSceneEntry,
|
|
77
77
|
instantiateSceneEntry,
|
|
78
78
|
type SceneEntry,
|
|
79
|
-
} from '../core/sceneEntries';
|
|
80
|
-
import {pauseFocus, scrubFocusToSettled} from '../core/scrub';
|
|
81
|
-
import {computeTimelineRows, computeTimelineSpan} from '../core/timeline';
|
|
82
|
-
import {notifyTransformChanged, subscribeTransform} from '../core/transformStore';
|
|
83
|
-
import {toggleStagePlayback} from '../core/transport';
|
|
84
|
-
import {GalleryViewportPicker} from '../core/viewportPicker';
|
|
85
|
-
import {AssetsPanel} from './AssetsPanel';
|
|
86
|
-
import {ChoiceDialog} from './ChoiceDialog';
|
|
87
|
-
import {EffectEditor} from './EffectEditor';
|
|
88
|
-
import {EffectTreeView} from './EffectTreeView';
|
|
89
|
-
import {MessageDialog} from './MessageDialog';
|
|
90
|
-
import {PromptDialog} from './PromptDialog';
|
|
91
|
-
import {ScenePanel} from './ScenePanel';
|
|
92
|
-
import {TimelinePanel} from './TimelinePanel';
|
|
93
|
-
import {TransportBar} from './TransportBar';
|
|
94
|
-
import {iconStyle, type HeroIcon} from './icons';
|
|
95
|
-
import type {GeometryData, GeometryOption, TextureOption} from './modules';
|
|
96
|
-
import {buttonStyle, globalEditorStyles, theme} from './theme';
|
|
79
|
+
} from '../core/sceneEntries.js';
|
|
80
|
+
import {pauseFocus, scrubFocusToSettled} from '../core/scrub.js';
|
|
81
|
+
import {computeTimelineRows, computeTimelineSpan} from '../core/timeline.js';
|
|
82
|
+
import {notifyTransformChanged, subscribeTransform} from '../core/transformStore.js';
|
|
83
|
+
import {toggleStagePlayback} from '../core/transport.js';
|
|
84
|
+
import {GalleryViewportPicker} from '../core/viewportPicker.js';
|
|
85
|
+
import {AssetsPanel} from './AssetsPanel.js';
|
|
86
|
+
import {ChoiceDialog} from './ChoiceDialog.js';
|
|
87
|
+
import {EffectEditor} from './EffectEditor.js';
|
|
88
|
+
import {EffectTreeView} from './EffectTreeView.js';
|
|
89
|
+
import {MessageDialog} from './MessageDialog.js';
|
|
90
|
+
import {PromptDialog} from './PromptDialog.js';
|
|
91
|
+
import {ScenePanel} from './ScenePanel.js';
|
|
92
|
+
import {TimelinePanel} from './TimelinePanel.js';
|
|
93
|
+
import {TransportBar} from './TransportBar.js';
|
|
94
|
+
import {iconStyle, type HeroIcon} from './icons.js';
|
|
95
|
+
import type {GeometryData, GeometryOption, TextureOption} from './modules.js';
|
|
96
|
+
import {buttonStyle, globalEditorStyles, theme} from './theme.js';
|
|
97
97
|
|
|
98
98
|
/**
|
|
99
99
|
* The effect's JSON, with a warning when it points at object URLs: a texture
|
|
@@ -390,11 +390,12 @@ export function EffectEditorHost(props: EffectEditorHostProps) {
|
|
|
390
390
|
};
|
|
391
391
|
|
|
392
392
|
useEffect(() => {
|
|
393
|
-
const
|
|
393
|
+
const canvas = canvasRef.current!;
|
|
394
|
+
const engine = new Engine(canvas, true);
|
|
394
395
|
const scene = new Scene(engine);
|
|
395
396
|
scene.clearColor = new Color4(0.03, 0.04, 0.09, 1);
|
|
396
397
|
const camera = new ArcRotateCamera('cam', -Math.PI / 2, 1.15, 9, new BVector3(0, 1.4, 0), scene);
|
|
397
|
-
camera.attachControl(
|
|
398
|
+
camera.attachControl(canvas, true);
|
|
398
399
|
camera.wheelDeltaPercentage = 0.01;
|
|
399
400
|
|
|
400
401
|
// Mesh render mode particles are lit (billboard modes ignore these); the grid is visual
|
|
@@ -453,7 +454,7 @@ export function EffectEditorHost(props: EffectEditorHostProps) {
|
|
|
453
454
|
const state = stateRef.current;
|
|
454
455
|
|
|
455
456
|
const handleViewportPointerLeave = () => viewportPicker.setHover(null);
|
|
456
|
-
|
|
457
|
+
canvas.addEventListener('pointerleave', handleViewportPointerLeave);
|
|
457
458
|
|
|
458
459
|
const pointerObserver = scene.onPointerObservable.add((pointerInfo) => {
|
|
459
460
|
if (assetDragDepthRef.current > 0) {
|
|
@@ -1313,7 +1314,7 @@ export function EffectEditorHost(props: EffectEditorHostProps) {
|
|
|
1313
1314
|
});
|
|
1314
1315
|
|
|
1315
1316
|
const resizeObserver = new ResizeObserver(() => engine.resize());
|
|
1316
|
-
resizeObserver.observe(
|
|
1317
|
+
resizeObserver.observe(canvas);
|
|
1317
1318
|
const onKey = (e: KeyboardEvent) => {
|
|
1318
1319
|
const key = e.key.toLowerCase();
|
|
1319
1320
|
if ((e.ctrlKey || e.metaKey) && key === 's') {
|
|
@@ -1375,13 +1376,13 @@ export function EffectEditorHost(props: EffectEditorHostProps) {
|
|
|
1375
1376
|
emitterWireframesRef.current = null;
|
|
1376
1377
|
viewportPicker.dispose();
|
|
1377
1378
|
viewportPickerRef.current = null;
|
|
1378
|
-
|
|
1379
|
+
canvas.removeEventListener('pointerleave', handleViewportPointerLeave);
|
|
1379
1380
|
scene.onPointerObservable.remove(pointerObserver);
|
|
1380
1381
|
gizmoManager.dispose();
|
|
1381
1382
|
gizmoUtilityLayer.dispose();
|
|
1382
1383
|
engine.dispose();
|
|
1383
1384
|
};
|
|
1384
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1385
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- mount only: builds the engine and scene once
|
|
1385
1386
|
}, []);
|
|
1386
1387
|
|
|
1387
1388
|
useEffect(() => {
|
|
@@ -1527,13 +1528,6 @@ export function EffectEditorHost(props: EffectEditorHostProps) {
|
|
|
1527
1528
|
}, [selectedNode, binding, isAssetsLoading]);
|
|
1528
1529
|
|
|
1529
1530
|
const state = stateRef.current;
|
|
1530
|
-
const gizmoNode =
|
|
1531
|
-
selectedNode && !selectedNode.isDisposed()
|
|
1532
|
-
? selectedNode
|
|
1533
|
-
: binding?.root && !binding.root.isDisposed()
|
|
1534
|
-
? binding.root
|
|
1535
|
-
: null;
|
|
1536
|
-
|
|
1537
1531
|
const allSystems = binding ? [binding.system, ...binding.subSystems] : [];
|
|
1538
1532
|
const activeSystem = binding ? (allSystems.find((s) => s.emitter === selectedNode) ?? binding.system) : null;
|
|
1539
1533
|
// The inspector is the expensive part of a selection change (every module re-renders for the
|
|
@@ -1554,22 +1548,23 @@ export function EffectEditorHost(props: EffectEditorHostProps) {
|
|
|
1554
1548
|
const instanceCounts = useMemo(() => countInstancesByAsset(sceneEntries), [sceneEntries]);
|
|
1555
1549
|
const showTimeline = Boolean(binding && activeSystem && state);
|
|
1556
1550
|
const timelinePlaybackEnabled = !focusedRoot || visibleRootIdSet.has(focusedRoot.uniqueId);
|
|
1551
|
+
const {resolveTexture, resolveGeometry} = props;
|
|
1557
1552
|
const resolveTextureForEditor = useCallback(
|
|
1558
1553
|
(url: string) => {
|
|
1559
1554
|
const scene = stateRef.current?.scene;
|
|
1560
|
-
return scene &&
|
|
1555
|
+
return scene && resolveTexture ? resolveTexture(url, scene) : undefined;
|
|
1561
1556
|
},
|
|
1562
|
-
[
|
|
1557
|
+
[resolveTexture]
|
|
1563
1558
|
);
|
|
1564
1559
|
const resolveGeometryForEditor = useCallback(
|
|
1565
1560
|
(file: File) => {
|
|
1566
1561
|
const scene = stateRef.current?.scene;
|
|
1567
|
-
if (!scene || !
|
|
1562
|
+
if (!scene || !resolveGeometry) {
|
|
1568
1563
|
return Promise.reject(new Error('Geometry loader is not available.'));
|
|
1569
1564
|
}
|
|
1570
|
-
return
|
|
1565
|
+
return resolveGeometry(file, scene);
|
|
1571
1566
|
},
|
|
1572
|
-
[
|
|
1567
|
+
[resolveGeometry]
|
|
1573
1568
|
);
|
|
1574
1569
|
|
|
1575
1570
|
return (
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type {TransformNode} from '@babylonjs/core/Meshes/transformNode';
|
|
1
|
+
import type {TransformNode} from '@babylonjs/core/Meshes/transformNode.js';
|
|
2
2
|
import {ChevronDownIcon, ChevronRightIcon, FolderIcon, SparklesIcon} from '@heroicons/react/24/solid';
|
|
3
3
|
import React, {useState} from 'react';
|
|
4
|
-
import {buildEffectTree, type EffectTreeNode} from '../core/effectTree';
|
|
5
|
-
import {iconStyle} from './icons';
|
|
6
|
-
import {theme} from './theme';
|
|
4
|
+
import {buildEffectTree, type EffectTreeNode} from '../core/effectTree.js';
|
|
5
|
+
import {iconStyle} from './icons.js';
|
|
6
|
+
import {theme} from './theme.js';
|
|
7
7
|
|
|
8
8
|
export interface EffectTreeViewProps {
|
|
9
9
|
root: TransformNode;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {useRef, useState} from 'react';
|
|
2
|
-
import {GradientStop, hexToRgb, rgbToHex, stopsToCssGradient} from '../core/colors';
|
|
3
|
-
import {NumberField, Row} from './fields';
|
|
4
|
-
import {buttonStyle, theme} from './theme';
|
|
2
|
+
import {GradientStop, hexToRgb, rgbToHex, stopsToCssGradient} from '../core/colors.js';
|
|
3
|
+
import {NumberField, Row} from './fields.js';
|
|
4
|
+
import {buttonStyle, theme} from './theme.js';
|
|
5
5
|
|
|
6
6
|
const BAR_WIDTH = 218;
|
|
7
7
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {InformationCircleIcon} from '@heroicons/react/24/solid';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import {iconStyle} from './icons';
|
|
4
|
-
import {theme} from './theme';
|
|
5
|
-
import {Tooltip} from './Tooltip';
|
|
3
|
+
import {iconStyle} from './icons.js';
|
|
4
|
+
import {theme} from './theme.js';
|
|
5
|
+
import {Tooltip} from './Tooltip.js';
|
|
6
6
|
|
|
7
7
|
/** Small info icon with a themed inspector tooltip. */
|
|
8
8
|
export function InspectorInfoIcon(props: {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {getInspectorHint} from './inspectorHints';
|
|
3
|
-
import {InspectorInfoIcon} from './InspectorInfoIcon';
|
|
4
|
-
import {labelStyle} from './theme';
|
|
2
|
+
import {getInspectorHint} from './inspectorHints.js';
|
|
3
|
+
import {InspectorInfoIcon} from './InspectorInfoIcon.js';
|
|
4
|
+
import {labelStyle} from './theme.js';
|
|
5
5
|
|
|
6
6
|
/** Inspector row label with an optional info icon tooltip. */
|
|
7
7
|
export function InspectorLabel(props: {label: string; hint?: string; hintKey?: string}): React.ReactElement | null {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {ChevronDownIcon, ChevronRightIcon} from '@heroicons/react/24/solid';
|
|
2
2
|
import React, {useState} from 'react';
|
|
3
|
-
import {iconStyle} from './icons';
|
|
4
|
-
import {getInspectorHint} from './inspectorHints';
|
|
5
|
-
import {InspectorInfoIcon} from './InspectorInfoIcon';
|
|
6
|
-
import {theme} from './theme';
|
|
3
|
+
import {iconStyle} from './icons.js';
|
|
4
|
+
import {getInspectorHint} from './inspectorHints.js';
|
|
5
|
+
import {InspectorInfoIcon} from './InspectorInfoIcon.js';
|
|
6
|
+
import {theme} from './theme.js';
|
|
7
7
|
|
|
8
8
|
/** Unity-style module foldout with an optional enable checkbox. */
|
|
9
9
|
export function ModuleSection(props: {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {Quaternion} from '@babylonjs/core/Maths/math.vector';
|
|
2
|
-
import type {TransformNode} from '@babylonjs/core/Meshes/transformNode';
|
|
1
|
+
import {Quaternion} from '@babylonjs/core/Maths/math.vector.js';
|
|
2
|
+
import type {TransformNode} from '@babylonjs/core/Meshes/transformNode.js';
|
|
3
3
|
import {useSyncExternalStore} from 'react';
|
|
4
|
-
import {getTransformRevision, notifyTransformChanged, subscribeTransform} from '../core/transformStore';
|
|
5
|
-
import {ModuleSection} from './ModuleSection';
|
|
6
|
-
import {NumberField, Row} from './fields';
|
|
4
|
+
import {getTransformRevision, notifyTransformChanged, subscribeTransform} from '../core/transformStore.js';
|
|
5
|
+
import {ModuleSection} from './ModuleSection.js';
|
|
6
|
+
import {NumberField, Row} from './fields.js';
|
|
7
7
|
|
|
8
8
|
const RAD_TO_DEG = 180 / Math.PI;
|
|
9
9
|
const DEG_TO_RAD = Math.PI / 180;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {ArrowLeftIcon, FolderIcon, PlusIcon} from '@heroicons/react/24/solid';
|
|
2
2
|
import React, {useCallback, useEffect, useState} from 'react';
|
|
3
|
-
import {formatBytes, type ProjectMeta, type ProjectStore, type StorageEstimate} from '../core/projectStore';
|
|
4
|
-
import {ChoiceDialog} from './ChoiceDialog';
|
|
5
|
-
import {ContextMenu, type ContextMenuItem} from './ContextMenu';
|
|
6
|
-
import {iconStyle} from './icons';
|
|
7
|
-
import {PromptDialog} from './PromptDialog';
|
|
8
|
-
import {buttonStyle, globalEditorStyles, theme} from './theme';
|
|
3
|
+
import {formatBytes, type ProjectMeta, type ProjectStore, type StorageEstimate} from '../core/projectStore.js';
|
|
4
|
+
import {ChoiceDialog} from './ChoiceDialog.js';
|
|
5
|
+
import {ContextMenu, type ContextMenuItem} from './ContextMenu.js';
|
|
6
|
+
import {iconStyle} from './icons.js';
|
|
7
|
+
import {PromptDialog} from './PromptDialog.js';
|
|
8
|
+
import {buttonStyle, globalEditorStyles, theme} from './theme.js';
|
|
9
9
|
|
|
10
10
|
export interface ProjectsPageProps {
|
|
11
11
|
store: ProjectStore;
|
package/src/react/ScenePanel.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {TransformNode} from '@babylonjs/core/Meshes/transformNode';
|
|
1
|
+
import type {TransformNode} from '@babylonjs/core/Meshes/transformNode.js';
|
|
2
2
|
import {
|
|
3
3
|
ChevronDownIcon,
|
|
4
4
|
ChevronRightIcon,
|
|
@@ -9,12 +9,12 @@ import {
|
|
|
9
9
|
ViewfinderCircleIcon,
|
|
10
10
|
} from '@heroicons/react/24/solid';
|
|
11
11
|
import React, {useEffect, useState} from 'react';
|
|
12
|
-
import {buildEffectTree} from '../core/effectTree';
|
|
13
|
-
import type {SceneEntry} from '../core/sceneEntries';
|
|
14
|
-
import {ContextMenu, type ContextMenuItem} from './ContextMenu';
|
|
15
|
-
import {EffectTreeView} from './EffectTreeView';
|
|
16
|
-
import {iconStyle} from './icons';
|
|
17
|
-
import {buttonStyle, theme} from './theme';
|
|
12
|
+
import {buildEffectTree} from '../core/effectTree.js';
|
|
13
|
+
import type {SceneEntry} from '../core/sceneEntries.js';
|
|
14
|
+
import {ContextMenu, type ContextMenuItem} from './ContextMenu.js';
|
|
15
|
+
import {EffectTreeView} from './EffectTreeView.js';
|
|
16
|
+
import {iconStyle} from './icons.js';
|
|
17
|
+
import {buttonStyle, theme} from './theme.js';
|
|
18
18
|
|
|
19
19
|
const ROW_HEIGHT = 34;
|
|
20
20
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type {ParticleEmitter, ParticleSystem} from 'babylon.quarks';
|
|
2
2
|
import {EmitSubParticleSystem, SubParticleEmitMode} from 'babylon.quarks';
|
|
3
|
-
import {EffectBinding} from '../core/binding';
|
|
4
|
-
import {findBehavior} from '../core/colors';
|
|
5
|
-
import {createChildSystem} from '../core/systems';
|
|
6
|
-
import {ModuleSection} from './ModuleSection';
|
|
7
|
-
import {CheckboxField, NumberField, Row, SelectField} from './fields';
|
|
8
|
-
import {theme} from './theme';
|
|
3
|
+
import {EffectBinding} from '../core/binding.js';
|
|
4
|
+
import {findBehavior} from '../core/colors.js';
|
|
5
|
+
import {createChildSystem} from '../core/systems.js';
|
|
6
|
+
import {ModuleSection} from './ModuleSection.js';
|
|
7
|
+
import {CheckboxField, NumberField, Row, SelectField} from './fields.js';
|
|
8
|
+
import {theme} from './theme.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Sub Emitters module for the currently selected system. The spawned target lives as a
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type {BaseTexture} from '@babylonjs/core/Materials/Textures/baseTexture.js';
|
|
2
|
+
import {useState} from 'react';
|
|
3
|
+
import {SelectField} from './fields.js';
|
|
4
|
+
import type {TextureOption} from './modules.js';
|
|
5
|
+
import {PromptDialog} from './PromptDialog.js';
|
|
6
|
+
|
|
7
|
+
/** A texture-like object the host's `resolveTexture` returned; only its `url` is read here. */
|
|
8
|
+
type TextureLike = {url?: string} | null | undefined;
|
|
9
|
+
|
|
10
|
+
/** The url a texture-like object was loaded from, or '' for none. */
|
|
11
|
+
export function textureUrlOf(texture: unknown): string {
|
|
12
|
+
return (texture as TextureLike)?.url ?? '';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Picks a texture for one slot: a host preset, a custom URL, a file from disk, or none. The
|
|
17
|
+
* current texture stays selectable when it is not among the presets. Used by the Renderer
|
|
18
|
+
* module for the map and for the ramp, mask and noise of the texture effects.
|
|
19
|
+
*/
|
|
20
|
+
export function TextureSelect(props: {
|
|
21
|
+
/** The slot's current texture (anything with a `url`), or null for none. */
|
|
22
|
+
value: unknown;
|
|
23
|
+
textureOptions?: TextureOption[];
|
|
24
|
+
/** Turns a URL or `data:` URI into the host's texture object; null when it cannot. */
|
|
25
|
+
loadTexture: (url: string) => unknown;
|
|
26
|
+
/** The host's texture object, assumed to be a Babylon texture, or null for none. */
|
|
27
|
+
onChange: (texture: BaseTexture | null) => void;
|
|
28
|
+
/** Label of the "none" entry. Default "(none)". */
|
|
29
|
+
noneLabel?: string;
|
|
30
|
+
/** Shows a thumbnail of the current texture under the select. Default false. */
|
|
31
|
+
preview?: boolean;
|
|
32
|
+
/** Title of the custom URL prompt. Default "Texture URL". */
|
|
33
|
+
promptTitle?: string;
|
|
34
|
+
}) {
|
|
35
|
+
const currentUrl = textureUrlOf(props.value);
|
|
36
|
+
const options = props.textureOptions ?? [];
|
|
37
|
+
const isPreset = options.some((o) => o.url === currentUrl);
|
|
38
|
+
const [prompting, setPrompting] = useState(false);
|
|
39
|
+
const apply = (url: string) => {
|
|
40
|
+
const texture = props.loadTexture(url);
|
|
41
|
+
if (texture) {
|
|
42
|
+
props.onChange(texture as BaseTexture);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
return (
|
|
46
|
+
<>
|
|
47
|
+
<SelectField
|
|
48
|
+
value={!currentUrl ? '__none' : isPreset ? currentUrl : '__current'}
|
|
49
|
+
options={[
|
|
50
|
+
{value: '__none', label: props.noneLabel ?? '(none)'},
|
|
51
|
+
...(currentUrl && !isPreset
|
|
52
|
+
? [{value: '__current', label: `(current) ${currentUrl.slice(-24)}`}]
|
|
53
|
+
: []),
|
|
54
|
+
...options.map((o) => ({value: o.url, label: o.label})),
|
|
55
|
+
{value: '__url', label: 'Custom URL…'},
|
|
56
|
+
{value: '__file', label: 'Load from file…'},
|
|
57
|
+
]}
|
|
58
|
+
onChange={(url) => {
|
|
59
|
+
if (url === '__current') {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (url === '__none') {
|
|
63
|
+
props.onChange(null);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (url === '__url') {
|
|
67
|
+
setPrompting(true);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (url === '__file') {
|
|
71
|
+
const input = document.createElement('input');
|
|
72
|
+
input.type = 'file';
|
|
73
|
+
input.accept = 'image/*';
|
|
74
|
+
input.onchange = () => {
|
|
75
|
+
const file = input.files?.[0];
|
|
76
|
+
if (!file) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const reader = new FileReader();
|
|
80
|
+
reader.onload = () => apply(reader.result as string);
|
|
81
|
+
reader.readAsDataURL(file);
|
|
82
|
+
};
|
|
83
|
+
input.click();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
apply(url);
|
|
87
|
+
}}
|
|
88
|
+
/>
|
|
89
|
+
{prompting && (
|
|
90
|
+
<PromptDialog
|
|
91
|
+
title={props.promptTitle ?? 'Texture URL'}
|
|
92
|
+
defaultValue={currentUrl}
|
|
93
|
+
placeholder="https://…"
|
|
94
|
+
onSubmit={(url) => {
|
|
95
|
+
apply(url);
|
|
96
|
+
setPrompting(false);
|
|
97
|
+
}}
|
|
98
|
+
onCancel={() => setPrompting(false)}
|
|
99
|
+
/>
|
|
100
|
+
)}
|
|
101
|
+
{props.preview && currentUrl && (
|
|
102
|
+
<img
|
|
103
|
+
src={currentUrl}
|
|
104
|
+
alt="texture preview"
|
|
105
|
+
style={{
|
|
106
|
+
width: 64,
|
|
107
|
+
height: 64,
|
|
108
|
+
marginTop: 6,
|
|
109
|
+
objectFit: 'contain',
|
|
110
|
+
borderRadius: 6,
|
|
111
|
+
border: '1px solid #2b3761',
|
|
112
|
+
background: 'repeating-conic-gradient(#2a3252 0% 25%, #171d33 0% 50%) 0 0 / 12px 12px',
|
|
113
|
+
}}
|
|
114
|
+
/>
|
|
115
|
+
)}
|
|
116
|
+
</>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type {TextureUV, UVChannel} from 'babylon.quarks';
|
|
2
|
+
import {NumberField, Row, SelectField} from './fields.js';
|
|
3
|
+
|
|
4
|
+
/** A `[u, v]` pair of number inputs. */
|
|
5
|
+
function PairField(props: {value: [number, number]; step: number; onChange: (next: [number, number]) => void}) {
|
|
6
|
+
return (
|
|
7
|
+
<div style={{display: 'flex', gap: 6}}>
|
|
8
|
+
{[0, 1].map((i) => (
|
|
9
|
+
<NumberField
|
|
10
|
+
key={i}
|
|
11
|
+
value={props.value[i]}
|
|
12
|
+
step={props.step}
|
|
13
|
+
onChange={(v) => {
|
|
14
|
+
const next: [number, number] = [props.value[0], props.value[1]];
|
|
15
|
+
next[i] = v;
|
|
16
|
+
props.onChange(next);
|
|
17
|
+
}}
|
|
18
|
+
/>
|
|
19
|
+
))}
|
|
20
|
+
</div>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Rows for how a texture is laid over the particle (`TextureUV`): its UV channel, then
|
|
26
|
+
* tiling and offset as Unity's material Tiling / Offset, then a scroll as Unreal's Panner.
|
|
27
|
+
* One field for the map, the erosion mask and the distortion noise alike.
|
|
28
|
+
*/
|
|
29
|
+
export function TextureUVField(props: {
|
|
30
|
+
/** The complete layout as the system holds it. */
|
|
31
|
+
value: TextureUV;
|
|
32
|
+
/** Called with the whole layout, one field changed. */
|
|
33
|
+
onChange: (next: TextureUV) => void;
|
|
34
|
+
/** Prefix of each row's label, so several layouts in one module stay apart. Default none. */
|
|
35
|
+
labelPrefix?: string;
|
|
36
|
+
}) {
|
|
37
|
+
const prefix = props.labelPrefix ?? '';
|
|
38
|
+
const patch = (fields: Partial<TextureUV>) => props.onChange({...props.value, ...fields});
|
|
39
|
+
return (
|
|
40
|
+
<>
|
|
41
|
+
<Row label={`${prefix}UV channel`} hintKey="UV channel">
|
|
42
|
+
<SelectField<UVChannel>
|
|
43
|
+
value={props.value.channel}
|
|
44
|
+
options={[
|
|
45
|
+
{value: 0, label: 'UV0 (sprite / mesh UV)'},
|
|
46
|
+
{value: 1, label: 'UV1 (mesh second set, along a trail)'},
|
|
47
|
+
]}
|
|
48
|
+
onChange={(channel) => patch({channel})}
|
|
49
|
+
/>
|
|
50
|
+
</Row>
|
|
51
|
+
<Row label={`${prefix}Tiling`} hintKey="Tiling">
|
|
52
|
+
<PairField value={props.value.tiling} step={0.5} onChange={(tiling) => patch({tiling})} />
|
|
53
|
+
</Row>
|
|
54
|
+
<Row label={`${prefix}Offset`} hintKey="Offset">
|
|
55
|
+
<PairField value={props.value.offset} step={0.05} onChange={(offset) => patch({offset})} />
|
|
56
|
+
</Row>
|
|
57
|
+
<Row label={`${prefix}Scroll`} hintKey="Scroll">
|
|
58
|
+
<PairField value={props.value.scroll} step={0.05} onChange={(scroll) => patch({scroll})} />
|
|
59
|
+
</Row>
|
|
60
|
+
</>
|
|
61
|
+
);
|
|
62
|
+
}
|