babylon.quarks-editor 0.26.0 → 0.28.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 +134 -0
- package/README.md +69 -9
- package/dist/core/assetDragSession.d.ts +1 -1
- package/dist/core/assetTree.d.ts +6 -1
- package/dist/core/assets.d.ts +7 -0
- package/dist/core/assets.js +11 -7
- package/dist/core/binding.d.ts +11 -2
- package/dist/core/binding.js +9 -1
- 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 +5 -4
- package/dist/core/defaultEffect.js +4 -4
- package/dist/core/editorScene.d.ts +1 -1
- package/dist/core/editorSession.d.ts +229 -0
- package/dist/core/editorSession.js +983 -0
- package/dist/core/effectTree.d.ts +6 -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/galleryPlayback.js +2 -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 +7 -1
- package/dist/core/history.js +4 -0
- package/dist/core/indexedDbProjectStore.d.ts +14 -1
- package/dist/core/indexedDbProjectStore.js +13 -0
- 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/material.d.ts +12 -2
- package/dist/core/pickAssetFiles.js +1 -1
- 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 +7 -4
- package/dist/core/sceneEntries.js +2 -2
- package/dist/core/scrub.d.ts +2 -2
- package/dist/core/scrub.js +2 -2
- package/dist/core/shapes.d.ts +25 -1
- package/dist/core/shapes.js +5 -0
- package/dist/core/systems.d.ts +4 -2
- package/dist/core/systems.js +3 -1
- package/dist/core/textureExport.d.ts +1 -1
- package/dist/core/timeline.d.ts +25 -5
- package/dist/core/transport.d.ts +3 -3
- package/dist/core/transport.js +2 -2
- package/dist/core/values.d.ts +21 -4
- package/dist/core/values.js +5 -0
- package/dist/core/viewportPicker.d.ts +4 -4
- package/dist/core/viewportPicker.js +8 -7
- package/dist/index.d.ts +25 -21
- package/dist/index.js +12 -12
- package/dist/react/AssetsPanel.d.ts +9 -2
- package/dist/react/AssetsPanel.js +6 -6
- package/dist/react/ChoiceDialog.d.ts +7 -0
- package/dist/react/ChoiceDialog.js +1 -1
- package/dist/react/ContextMenu.d.ts +10 -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 +9 -2
- package/dist/react/EditorWorkspace.js +10 -7
- package/dist/react/EffectEditor.d.ts +11 -5
- package/dist/react/EffectEditor.js +7 -7
- package/dist/react/EffectEditorHost.d.ts +33 -29
- package/dist/react/EffectEditorHost.js +247 -1674
- package/dist/react/EffectTreeView.d.ts +4 -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.d.ts +5 -0
- package/dist/react/MessageDialog.js +1 -1
- package/dist/react/ModuleSection.d.ts +10 -0
- package/dist/react/ModuleSection.js +8 -5
- package/dist/react/ObjectModule.d.ts +1 -1
- package/dist/react/ObjectModule.js +4 -4
- package/dist/react/ProjectsPage.d.ts +7 -1
- package/dist/react/ProjectsPage.js +33 -17
- package/dist/react/PromptDialog.d.ts +7 -0
- package/dist/react/PromptDialog.js +1 -1
- package/dist/react/ScenePanel.d.ts +9 -2
- package/dist/react/ScenePanel.js +12 -10
- 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 +20 -7
- package/dist/react/TimelinePanel.js +21 -15
- package/dist/react/TimelineTrackRow.d.ts +13 -3
- package/dist/react/TimelineTrackRow.js +6 -6
- package/dist/react/TransportBar.d.ts +13 -4
- package/dist/react/TransportBar.js +3 -3
- package/dist/react/ValueField.d.ts +32 -5
- package/dist/react/ValueField.js +22 -11
- package/dist/react/behaviorModules.d.ts +19 -6
- package/dist/react/behaviorModules.js +68 -17
- package/dist/react/fields.d.ts +9 -0
- package/dist/react/fields.js +6 -3
- 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 +42 -38
- package/dist/react/index.js +25 -23
- package/dist/react/inspectorHints.js +31 -0
- package/dist/react/modules.d.ts +19 -6
- package/dist/react/modules.js +134 -117
- 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 +18 -11
- package/src/core/assetDragSession.ts +1 -1
- package/src/core/assetTree.ts +15 -2
- package/src/core/assets.ts +25 -10
- package/src/core/binding.ts +12 -3
- package/src/core/collision.ts +4 -0
- package/src/core/colors.ts +21 -1
- package/src/core/defaultEffect.ts +8 -7
- package/src/core/editorScene.ts +1 -1
- package/src/core/editorSession.ts +1299 -0
- package/src/core/effectTree.ts +18 -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 +3 -2
- 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 +7 -1
- package/src/core/indexedDbProjectStore.ts +14 -1
- package/src/core/lineShaders.ts +2 -2
- package/src/core/loadEffect.ts +4 -4
- package/src/core/material.ts +14 -4
- package/src/core/pickAssetFiles.ts +2 -2
- package/src/core/playback.ts +6 -0
- package/src/core/projectStore.ts +26 -0
- package/src/core/sceneEntries.ts +8 -5
- package/src/core/scrub.ts +3 -3
- package/src/core/shapes.ts +29 -2
- package/src/core/systems.ts +11 -4
- package/src/core/textureExport.ts +1 -1
- package/src/core/timeline.ts +43 -18
- package/src/core/transport.ts +4 -4
- package/src/core/values.ts +20 -4
- package/src/core/viewportPicker.ts +12 -11
- package/src/index.ts +25 -21
- package/src/react/AssetsPanel.tsx +13 -6
- package/src/react/ChoiceDialog.tsx +8 -1
- package/src/react/ContextMenu.tsx +11 -2
- package/src/react/CurveEditor.tsx +2 -2
- package/src/react/EditorWorkspace.tsx +16 -10
- package/src/react/EffectEditor.tsx +17 -9
- package/src/react/EffectEditorHost.tsx +415 -2354
- package/src/react/EffectTreeView.tsx +7 -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 +6 -1
- package/src/react/ModuleSection.tsx +15 -4
- package/src/react/ObjectModule.tsx +5 -5
- package/src/react/ProjectsPage.tsx +44 -17
- package/src/react/PromptDialog.tsx +8 -1
- package/src/react/ScenePanel.tsx +21 -12
- 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 +40 -21
- package/src/react/TimelineTrackRow.tsx +19 -9
- package/src/react/TransportBar.tsx +16 -7
- package/src/react/ValueField.tsx +60 -16
- package/src/react/behaviorModules.tsx +240 -55
- package/src/react/fields.tsx +22 -4
- 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 +43 -38
- package/src/react/inspectorHints.ts +40 -0
- package/src/react/modules.tsx +440 -170
- package/src/react/theme.ts +15 -0
- package/src/session.ts +17 -0
|
@@ -1,22 +1,32 @@
|
|
|
1
|
-
import type { TransformNode } from '@babylonjs/core/Meshes/transformNode';
|
|
1
|
+
import type { TransformNode } from '@babylonjs/core/Meshes/transformNode.js';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import type { EffectBinding } from '../core/binding';
|
|
4
|
-
import type { TimelineRow } from '../core/timeline';
|
|
3
|
+
import type { EffectBinding } from '../core/binding.js';
|
|
4
|
+
import type { TimelineRow } from '../core/timeline.js';
|
|
5
|
+
/** Props of {@link TimelineTrackRow}. */
|
|
5
6
|
export interface TimelineTrackRowProps {
|
|
7
|
+
/** The group or track the row shows. */
|
|
6
8
|
row: TimelineRow;
|
|
9
|
+
/** The focused effect, edited by the row's actions. */
|
|
7
10
|
binding: EffectBinding;
|
|
11
|
+
/** The row's node is the one in the inspector. */
|
|
8
12
|
selected: boolean;
|
|
13
|
+
/** Selects the row's node. */
|
|
9
14
|
onSelectNode: (node: TransformNode | null) => void;
|
|
15
|
+
/** The ruler's scale, pixels per second. */
|
|
10
16
|
pxPerSecond: number;
|
|
11
17
|
/** Matches the ruler's left gutter so bars line up under the correct tick. */
|
|
12
18
|
offsetPx: number;
|
|
19
|
+
/** A group row's children are hidden. */
|
|
13
20
|
collapsed: boolean;
|
|
21
|
+
/** Collapses or expands a group row. */
|
|
14
22
|
onToggleCollapse?: () => void;
|
|
15
23
|
/** Preview-only visibility (toggles `system.emitter.visible`); lets you isolate one track. */
|
|
16
24
|
hidden?: boolean;
|
|
25
|
+
/** Shows or hides the track in the preview. */
|
|
17
26
|
onToggleVisible?: () => void;
|
|
18
27
|
/** Membership in the "group selected rows together" set (unrelated to inspector selection). */
|
|
19
28
|
groupSelected: boolean;
|
|
29
|
+
/** Adds the row to the set, or takes it out. */
|
|
20
30
|
onToggleGroupSelect: () => void;
|
|
21
31
|
}
|
|
22
32
|
/** One row of the timeline: a label cell (name + actions) and, for tracks, a bar cell. */
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { CheckIcon, ChevronDownIcon, ChevronRightIcon, EyeIcon, EyeSlashIcon, PencilSquareIcon, PlusIcon, SparklesIcon, SquaresPlusIcon, XMarkIcon, } from '@heroicons/react/24/solid';
|
|
3
3
|
import { useState } from 'react';
|
|
4
|
-
import { buildEffectTree, collectSystems } from '../core/effectTree';
|
|
5
|
-
import { ungroupNode } from '../core/groups';
|
|
6
|
-
import { createChildSystem, removeSystems } from '../core/systems';
|
|
7
|
-
import { PromptDialog } from './PromptDialog';
|
|
8
|
-
import { iconStyle } from './icons';
|
|
9
|
-
import { theme } from './theme';
|
|
4
|
+
import { buildEffectTree, collectSystems } from '../core/effectTree.js';
|
|
5
|
+
import { ungroupNode } from '../core/groups.js';
|
|
6
|
+
import { createChildSystem, removeSystems } from '../core/systems.js';
|
|
7
|
+
import { PromptDialog } from './PromptDialog.js';
|
|
8
|
+
import { iconStyle } from './icons.js';
|
|
9
|
+
import { theme } from './theme.js';
|
|
10
10
|
const iconButton = {
|
|
11
11
|
background: 'none',
|
|
12
12
|
border: 'none',
|
|
@@ -1,18 +1,27 @@
|
|
|
1
|
-
import type { Scene } from '@babylonjs/core/scene';
|
|
1
|
+
import type { Scene } from '@babylonjs/core/scene.js';
|
|
2
2
|
import type { BatchedRenderer } from 'babylon.quarks';
|
|
3
3
|
import React from 'react';
|
|
4
|
-
import type { EffectBinding } from '../core/binding';
|
|
5
|
-
import type { PlaybackState } from '../core/playback';
|
|
4
|
+
import type { EffectBinding } from '../core/binding.js';
|
|
5
|
+
import type { PlaybackState } from '../core/playback.js';
|
|
6
|
+
/** Props of {@link TransportBar}. */
|
|
6
7
|
export interface TransportBarProps {
|
|
8
|
+
/** The focused effect, which Restart and Step act on. */
|
|
7
9
|
binding: EffectBinding | null;
|
|
10
|
+
/** The preview scene, redrawn after a restart. */
|
|
8
11
|
scene: Scene;
|
|
12
|
+
/** The renderer the effects draw through. */
|
|
9
13
|
renderer: BatchedRenderer;
|
|
14
|
+
/** Playback state shared with the host's render loop. */
|
|
10
15
|
playbackRef: React.MutableRefObject<PlaybackState>;
|
|
11
16
|
/** The host's render loop writes time and particle count here. */
|
|
12
|
-
counterRef: React.RefObject<HTMLSpanElement>;
|
|
17
|
+
counterRef: React.RefObject<HTMLSpanElement | null>;
|
|
18
|
+
/** The stage is playing. */
|
|
13
19
|
stagePlaying: boolean;
|
|
20
|
+
/** Plays or pauses the stage. */
|
|
14
21
|
setStagePlaying: (playing: boolean) => void;
|
|
22
|
+
/** Playback speed, 1 for real time. */
|
|
15
23
|
speed: number;
|
|
24
|
+
/** Sets the playback speed. */
|
|
16
25
|
setSpeed: (speed: number) => void;
|
|
17
26
|
/** False when the focused effect is hidden. */
|
|
18
27
|
enabled: boolean;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { ArrowPathIcon, ForwardIcon, PauseIcon, PlayIcon } from '@heroicons/react/24/solid';
|
|
3
|
-
import { PLAYBACK_SPEEDS, restartFocusPlayback, toggleStagePlayback } from '../core/transport';
|
|
4
|
-
import { iconStyle } from './icons';
|
|
5
|
-
import { buttonStyle, theme } from './theme';
|
|
3
|
+
import { PLAYBACK_SPEEDS, restartFocusPlayback, toggleStagePlayback } from '../core/transport.js';
|
|
4
|
+
import { iconStyle } from './icons.js';
|
|
5
|
+
import { buttonStyle, theme } from './theme.js';
|
|
6
6
|
const barButton = {
|
|
7
7
|
...buttonStyle,
|
|
8
8
|
width: 28,
|
|
@@ -1,11 +1,38 @@
|
|
|
1
|
+
import type { ValueGenerator } from 'babylon.quarks';
|
|
1
2
|
import React from 'react';
|
|
2
|
-
import { ScalarGenerator } from '../core/values';
|
|
3
|
-
|
|
3
|
+
import { AnyGenerator, ScalarGenerator } from '../core/values.js';
|
|
4
|
+
/** Props every {@link ValueField} takes. */
|
|
5
|
+
interface ValueFieldBaseProps {
|
|
6
|
+
/** The row's label. */
|
|
4
7
|
label: string;
|
|
8
|
+
/** Tooltip text of the label. */
|
|
5
9
|
hint?: string;
|
|
10
|
+
/** Key into the inspector hints, used when `hint` is not given. */
|
|
6
11
|
hintKey?: string;
|
|
7
|
-
generator
|
|
8
|
-
|
|
12
|
+
/** The generator shown; one that is not a scalar reads as the constant 1. */
|
|
13
|
+
generator: AnyGenerator | undefined;
|
|
14
|
+
/** Smallest value the number fields accept. */
|
|
9
15
|
min?: number;
|
|
16
|
+
/** Top of the curve editor's value range. */
|
|
10
17
|
curveMax?: number;
|
|
11
|
-
}
|
|
18
|
+
}
|
|
19
|
+
/** A value field for a setting that takes a curve over the particle's life. */
|
|
20
|
+
export interface CurveValueFieldProps extends ValueFieldBaseProps {
|
|
21
|
+
/** Offers Curve beside Constant and Random (the default). */
|
|
22
|
+
curve?: true;
|
|
23
|
+
/** Receives the edited generator. */
|
|
24
|
+
onChange: (generator: ScalarGenerator) => void;
|
|
25
|
+
}
|
|
26
|
+
/** A value field for a setting sampled once, without a curve: Constant or Random. */
|
|
27
|
+
export interface ConstantValueFieldProps extends ValueFieldBaseProps {
|
|
28
|
+
/** Hides Curve: the setting takes no curve. */
|
|
29
|
+
curve: false;
|
|
30
|
+
/** Receives the edited generator. */
|
|
31
|
+
onChange: (generator: ValueGenerator) => void;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Unity-like value field: Constant | Random Between Two Constants | Curve, bound to a quarks value
|
|
35
|
+
* generator. With `curve={false}` it offers only the first two, for settings sampled once.
|
|
36
|
+
*/
|
|
37
|
+
export declare function ValueField(props: CurveValueFieldProps | ConstantValueFieldProps): React.ReactElement;
|
|
38
|
+
export {};
|
package/dist/react/ValueField.js
CHANGED
|
@@ -1,20 +1,31 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { buildCurve, buildScalar, readPieces, readScalar } from '../core/values';
|
|
3
|
-
import { CurveEditor } from './CurveEditor';
|
|
4
|
-
import { NumberField, Row, SelectField } from './fields';
|
|
2
|
+
import { buildCurve, buildScalar, readPieces, readScalar, } from '../core/values.js';
|
|
3
|
+
import { CurveEditor } from './CurveEditor.js';
|
|
4
|
+
import { NumberField, Row, SelectField } from './fields.js';
|
|
5
|
+
const captionStyle = { fontSize: 10.5, opacity: 0.6, flex: 'none' };
|
|
6
|
+
function isValueGenerator(generator) {
|
|
7
|
+
return generator.type === 'value';
|
|
8
|
+
}
|
|
5
9
|
/**
|
|
6
|
-
* Unity-like value field: Constant | Random Between Two Constants | Curve,
|
|
7
|
-
*
|
|
10
|
+
* Unity-like value field: Constant | Random Between Two Constants | Curve, bound to a quarks value
|
|
11
|
+
* generator. With `curve={false}` it offers only the first two, for settings sampled once.
|
|
8
12
|
*/
|
|
9
|
-
const captionStyle = { fontSize: 10.5, opacity: 0.6, flex: 'none' };
|
|
10
13
|
export function ValueField(props) {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
const read = readScalar(props.generator);
|
|
15
|
+
// A setting without curves shows a curve it was loaded with as a constant, so an edit replaces it.
|
|
16
|
+
const state = props.curve === false && read.mode === 'curve' ? { ...read, mode: 'constant' } : read;
|
|
17
|
+
const emit = (generator) => {
|
|
18
|
+
if (props.curve !== false) {
|
|
19
|
+
props.onChange(generator);
|
|
20
|
+
}
|
|
21
|
+
else if (isValueGenerator(generator)) {
|
|
22
|
+
props.onChange(generator);
|
|
23
|
+
}
|
|
14
24
|
};
|
|
25
|
+
const commit = (patch) => emit(buildScalar({ ...state, ...patch }));
|
|
15
26
|
return (_jsxs(_Fragment, { children: [_jsx(Row, { label: props.label, hint: props.hint, hintKey: props.hintKey, children: _jsx(SelectField, { value: state.mode, options: [
|
|
16
27
|
{ value: 'constant', label: 'Constant' },
|
|
17
28
|
{ value: 'random', label: 'Random between two' },
|
|
18
|
-
{ value: 'curve', label: 'Curve' },
|
|
19
|
-
], onChange: (mode) => commit({ mode }) }) }), state.mode === 'constant' && (_jsx(Row, { label: "", children: _jsx(NumberField, { value: state.value, min: props.min, onChange: (value) => commit({ value }) }) })), state.mode === 'random' && (_jsx(Row, { label: "", children: _jsxs("div", { style: { display: 'flex', gap: 6, alignItems: 'center' }, children: [_jsx("span", { style: captionStyle, children: "min" }), _jsx(NumberField, { value: state.min, min: props.min, onChange: (min) => commit({ min }) }), _jsx("span", { style: captionStyle, children: "max" }), _jsx(NumberField, { value: state.max, min: props.min, onChange: (max) => commit({ max }) })] }) })), state.mode === 'curve' && (_jsx("div", { style: { marginTop: 6 }, children: _jsx(CurveEditor, { pieces: readPieces(props.generator), maxValue: props.curveMax, onChange: (pieces) =>
|
|
29
|
+
...(props.curve !== false ? [{ value: 'curve', label: 'Curve' }] : []),
|
|
30
|
+
], onChange: (mode) => commit({ mode }) }) }), state.mode === 'constant' && (_jsx(Row, { label: "", children: _jsx(NumberField, { value: state.value, min: props.min, onChange: (value) => commit({ value }) }) })), state.mode === 'random' && (_jsx(Row, { label: "", children: _jsxs("div", { style: { display: 'flex', gap: 6, alignItems: 'center' }, children: [_jsx("span", { style: captionStyle, children: "min" }), _jsx(NumberField, { value: state.min, min: props.min, onChange: (min) => commit({ min }) }), _jsx("span", { style: captionStyle, children: "max" }), _jsx(NumberField, { value: state.max, min: props.min, onChange: (max) => commit({ max }) })] }) })), state.mode === 'curve' && (_jsx("div", { style: { marginTop: 6 }, children: _jsx(CurveEditor, { pieces: readPieces(props.generator), maxValue: props.curveMax, onChange: (pieces) => emit(buildCurve(pieces)) }) }))] }));
|
|
20
31
|
}
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import type
|
|
4
|
-
|
|
5
|
-
binding: EffectBinding;
|
|
6
|
-
}
|
|
2
|
+
import type { TextureOption } from './modules.js';
|
|
3
|
+
import { type ModuleProps } from './ModuleSection.js';
|
|
4
|
+
/** The Speed over Lifetime module (Unity's Speed Modifier): scales speed by a curve over the particle's life. */
|
|
7
5
|
export declare function SpeedOverLifeModule({ binding }: ModuleProps): React.JSX.Element;
|
|
6
|
+
/** The Limit Speed over Lifetime module (Unity's Limit Velocity over Lifetime): caps speed by a curve, with dampening. */
|
|
8
7
|
export declare function LimitSpeedOverLifeModule({ binding }: ModuleProps): React.JSX.Element;
|
|
8
|
+
/** The Force over Lifetime module (Unity's Force over Lifetime): a constant push per axis. */
|
|
9
9
|
export declare function ForceOverLifeModule({ binding }: ModuleProps): React.JSX.Element;
|
|
10
10
|
export declare function VelocityOverLifeModule({ binding }: ModuleProps): React.JSX.Element;
|
|
11
11
|
export declare function InheritVelocityModule({ binding }: ModuleProps): React.JSX.Element;
|
|
12
|
+
/** A point attractor (`GravityForce`): particles fall towards a point, harder the nearer they are. */
|
|
13
|
+
export declare function AttractorModule({ binding }: ModuleProps): React.JSX.Element;
|
|
14
|
+
/** The Gravity module: pulls particles towards a point. */
|
|
12
15
|
export declare function GravityModule({ binding }: ModuleProps): React.JSX.Element;
|
|
16
|
+
/** The Rotation over Lifetime module (Unity's Rotation over Lifetime): spin rate over the particle's life. */
|
|
13
17
|
export declare function RotationOverLifeModule({ binding }: ModuleProps): React.JSX.Element;
|
|
14
18
|
/** Turns mesh particles by a 3D angular velocity; billboards turn through Rotation over Lifetime. */
|
|
15
19
|
export declare function Rotation3DOverLifeModule({ binding }: ModuleProps): React.JSX.Element;
|
|
@@ -21,14 +25,23 @@ export declare function Rotation3DOverLifeModule({ binding }: ModuleProps): Reac
|
|
|
21
25
|
export declare function SequencesModule({ binding, textureOptions }: ModuleProps & {
|
|
22
26
|
textureOptions?: TextureOption[];
|
|
23
27
|
}): React.JSX.Element;
|
|
28
|
+
/** The Noise module (Unity's Noise): moves and turns particles by a noise field. */
|
|
24
29
|
export declare function NoiseModule({ binding }: ModuleProps): React.JSX.Element;
|
|
30
|
+
/** The Turbulence module: a curl noise force field. */
|
|
25
31
|
export declare function TurbulenceModule({ binding }: ModuleProps): React.JSX.Element;
|
|
32
|
+
/** The Randomize Direction module: tilts each particle's start direction by a random angle. */
|
|
26
33
|
export declare function EmitDirectionModule({ binding }: ModuleProps): React.JSX.Element;
|
|
34
|
+
/** The Collision module (Unity's Collision): particles bounce off or die on a ground plane. */
|
|
27
35
|
export declare function CollisionModule({ binding }: ModuleProps): React.JSX.Element;
|
|
36
|
+
/** The Color by Speed module (Unity's Color by Speed): a gradient over a speed range. */
|
|
28
37
|
export declare function ColorBySpeedModule({ binding }: ModuleProps): React.JSX.Element;
|
|
38
|
+
/** The Size by Speed module (Unity's Size by Speed): size over a speed range. */
|
|
29
39
|
export declare function SizeBySpeedModule({ binding }: ModuleProps): React.JSX.Element;
|
|
40
|
+
/** The Rotation by Speed module (Unity's Rotation by Speed): spin rate over a speed range. */
|
|
30
41
|
export declare function RotationBySpeedModule({ binding }: ModuleProps): React.JSX.Element;
|
|
42
|
+
/** The Orbit over Lifetime module (Unity's Velocity over Lifetime orbital): turns particles about an axis. */
|
|
31
43
|
export declare function OrbitOverLifeModule({ binding }: ModuleProps): React.JSX.Element;
|
|
44
|
+
/** The Width over Trail module (Unity's Trails width over trail): a trail's width along its length. */
|
|
32
45
|
export declare function WidthOverTrailModule({ binding }: ModuleProps): React.JSX.Element;
|
|
46
|
+
/** The Texture Sheet Animation module (Unity's Texture Sheet Animation): plays the sheet's frames by life or by frame rate. */
|
|
33
47
|
export declare function TextureSheetModule({ binding }: ModuleProps): React.JSX.Element;
|
|
34
|
-
export {};
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { PlusIcon, XMarkIcon } from '@heroicons/react/24/solid';
|
|
3
|
-
import { ApplyCollision, ApplyForce, ApplySequences, AxisAngleGenerator, Bezier, ChangeEmitDirection, ColorBySpeed, ConstantValue, EulerGenerator, ForceOverLife, FrameOverLife,
|
|
3
|
+
import { ApplyCollision, ApplyForce, ApplySequences, AxisAngleGenerator, Bezier, ChangeEmitDirection, ColorBySpeed, ConstantValue, EulerGenerator, ForceOverLife, FrameOverLife, Gradient, GravityForce, InheritVelocity, IntervalValue, LimitSpeedOverLife, Noise, OrbitOverLife, PiecewiseBezier, RandomQuatGenerator, Rotation3DOverLife, RotationBySpeed, RotationOverLife, SizeBySpeed, SpeedOverLife, TextureSequencer, TurbulenceField, Vector3, VelocityOverLife, WidthOverLength, } from 'babylon.quarks';
|
|
4
4
|
import React from 'react';
|
|
5
|
-
import { buildGradient, DEFAULT_GRADIENT_STOPS, findBehavior, readGradientStops } from '../core/colors';
|
|
6
|
-
import { buildCurve, buildScalar, readPieces, readScalar } from '../core/values';
|
|
7
|
-
import { CurveEditor } from './CurveEditor';
|
|
8
|
-
import { CheckboxField, NumberField, Row, SelectField } from './fields';
|
|
9
|
-
import { GradientEditor } from './GradientEditor';
|
|
10
|
-
import { iconStyle } from './icons';
|
|
11
|
-
import { ModuleSection } from './ModuleSection';
|
|
12
|
-
import { theme } from './theme';
|
|
13
|
-
import { ValueField } from './ValueField';
|
|
5
|
+
import { buildGradient, DEFAULT_GRADIENT_STOPS, findBehavior, readGradientStops } from '../core/colors.js';
|
|
6
|
+
import { buildCurve, buildScalar, readPieces, readScalar } from '../core/values.js';
|
|
7
|
+
import { CurveEditor } from './CurveEditor.js';
|
|
8
|
+
import { CheckboxField, NumberField, Row, SelectField } from './fields.js';
|
|
9
|
+
import { GradientEditor } from './GradientEditor.js';
|
|
10
|
+
import { iconStyle } from './icons.js';
|
|
11
|
+
import { ModuleSection } from './ModuleSection.js';
|
|
12
|
+
import { theme } from './theme.js';
|
|
13
|
+
import { ValueField } from './ValueField.js';
|
|
14
14
|
function removeBehaviorOfType(behaviors, type) {
|
|
15
15
|
for (let i = behaviors.length - 1; i >= 0; i--) {
|
|
16
16
|
if (behaviors[i].type === type) {
|
|
@@ -28,14 +28,20 @@ function BehaviorModule(props) {
|
|
|
28
28
|
}
|
|
29
29
|
}), children: behavior ? props.children(behavior) : null }));
|
|
30
30
|
}
|
|
31
|
+
/** The Speed over Lifetime module (Unity's Speed Modifier): scales speed by a curve over the particle's life. */
|
|
31
32
|
export function SpeedOverLifeModule({ binding }) {
|
|
32
33
|
return (_jsx(BehaviorModule, { binding: binding, title: "Speed over Lifetime", type: "SpeedOverLife", create: () => new SpeedOverLife(buildScalar({ mode: 'curve', value: 1, min: 0, max: 1, curve: [1, 0.75, 0.5, 0] })), children: (behavior) => (_jsx("div", { style: { marginTop: 6 }, children: _jsx(CurveEditor, { pieces: readPieces(behavior.speed), maxValue: 2, onChange: (pieces) => binding.apply(() => (behavior.speed = buildCurve(pieces))) }) })) }));
|
|
33
34
|
}
|
|
35
|
+
/** The Limit Speed over Lifetime module (Unity's Limit Velocity over Lifetime): caps speed by a curve, with dampening. */
|
|
34
36
|
export function LimitSpeedOverLifeModule({ binding }) {
|
|
35
37
|
return (_jsx(BehaviorModule, { binding: binding, title: "Limit Speed over Lifetime", type: "LimitSpeedOverLife", create: () => new LimitSpeedOverLife(buildScalar({ mode: 'curve', value: 1, min: 0, max: 1, curve: [4, 2, 1, 1] }), 0.5), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx("div", { style: { marginTop: 6 }, children: _jsx(CurveEditor, { pieces: readPieces(behavior.speed), maxValue: 8, onChange: (pieces) => binding.apply(() => (behavior.speed = buildCurve(pieces))) }) }), _jsx(Row, { label: "Dampen", children: _jsx(NumberField, { value: behavior.dampen, min: 0, step: 0.05, onChange: (v) => binding.apply(() => (behavior.dampen = Math.min(1, v))) }) })] })) }));
|
|
36
38
|
}
|
|
39
|
+
/** The Force over Lifetime module (Unity's Force over Lifetime): a constant push per axis. */
|
|
37
40
|
export function ForceOverLifeModule({ binding }) {
|
|
38
|
-
return (_jsx(BehaviorModule, { binding: binding, title: "Force over Lifetime", type: "ForceOverLife", create: () => new ForceOverLife(new ConstantValue(0), new ConstantValue(2), new ConstantValue(0)), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx(
|
|
41
|
+
return (_jsx(BehaviorModule, { binding: binding, title: "Force over Lifetime", type: "ForceOverLife", create: () => new ForceOverLife(new ConstantValue(0), new ConstantValue(2), new ConstantValue(0)), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx(Row, { label: "Space", children: _jsx(SelectField, { value: behavior.space, options: [
|
|
42
|
+
{ value: 'world', label: 'World' },
|
|
43
|
+
{ value: 'local', label: 'Local' },
|
|
44
|
+
], onChange: (v) => binding.apply(() => (behavior.space = v)) }) }), _jsx(ValueField, { hintKey: "force.x", label: "X", generator: behavior.x, curveMax: 10, onChange: (g) => binding.apply(() => (behavior.x = g)) }), _jsx(ValueField, { hintKey: "force.y", label: "Y", generator: behavior.y, curveMax: 10, onChange: (g) => binding.apply(() => (behavior.y = g)) }), _jsx(ValueField, { hintKey: "force.z", label: "Z", generator: behavior.z, curveMax: 10, onChange: (g) => binding.apply(() => (behavior.z = g)) })] })) }));
|
|
39
45
|
}
|
|
40
46
|
export function VelocityOverLifeModule({ binding }) {
|
|
41
47
|
return (_jsx(BehaviorModule, { binding: binding, title: "Velocity over Lifetime", type: "VelocityOverLife", create: () => new VelocityOverLife(new ConstantValue(0), new ConstantValue(1), new ConstantValue(0)), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx(Row, { label: "Space", children: _jsx(SelectField, { value: behavior.space, options: [
|
|
@@ -49,9 +55,18 @@ export function InheritVelocityModule({ binding }) {
|
|
|
49
55
|
{ value: 'current', label: 'Current' },
|
|
50
56
|
], onChange: (v) => binding.apply(() => (behavior.mode = v)) }) }), _jsx(ValueField, { label: "Multiplier", generator: behavior.multiplier, curveMax: 2, onChange: (g) => binding.apply(() => (behavior.multiplier = g)) })] })) }));
|
|
51
57
|
}
|
|
58
|
+
/** A point attractor (`GravityForce`): particles fall towards a point, harder the nearer they are. */
|
|
59
|
+
export function AttractorModule({ binding }) {
|
|
60
|
+
return (_jsx(BehaviorModule, { binding: binding, title: "Attractor", type: "GravityForce", create: () => new GravityForce(new Vector3(0, 0, 0), 1), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx(Row, { label: "Center", hintKey: "attractor.center", children: _jsx("div", { style: { display: 'flex', gap: 6 }, children: ['x', 'y', 'z'].map((axis) => (_jsx(NumberField, { value: behavior.center[axis], step: 0.1, onChange: (v) => binding.apply(() => (behavior.center[axis] = v)) }, axis))) }) }), _jsx(Row, { label: "Strength", hintKey: "attractor.magnitude", children: _jsx(NumberField, { value: behavior.magnitude, step: 0.1, onChange: (v) => binding.apply(() => (behavior.magnitude = v)) }) })] })) }));
|
|
61
|
+
}
|
|
62
|
+
/** The Gravity module: pulls particles towards a point. */
|
|
52
63
|
export function GravityModule({ binding }) {
|
|
53
|
-
return (_jsx(BehaviorModule, { binding: binding, title: "Gravity", type: "ApplyForce", create: () => new ApplyForce(new Vector3(0, -1, 0), new ConstantValue(9.81)), children: (behavior) => (_jsx(Row, { label: "Strength", hintKey: "gravity.strength", children: _jsx(NumberField, { value: readScalar(behavior.magnitude).value, step: 0.5, onChange: (v) => binding.apply(() => (behavior.magnitude = new ConstantValue(v))) }) })
|
|
64
|
+
return (_jsx(BehaviorModule, { binding: binding, title: "Gravity", type: "ApplyForce", create: () => new ApplyForce(new Vector3(0, -1, 0), new ConstantValue(9.81)), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx(Row, { label: "Strength", hintKey: "gravity.strength", children: _jsx(NumberField, { value: readScalar(behavior.magnitude).value, step: 0.5, onChange: (v) => binding.apply(() => (behavior.magnitude = new ConstantValue(v))) }) }), _jsx(Row, { label: "Space", children: _jsx(SelectField, { value: behavior.space, options: [
|
|
65
|
+
{ value: 'world', label: 'World' },
|
|
66
|
+
{ value: 'local', label: 'Local' },
|
|
67
|
+
], onChange: (v) => binding.apply(() => (behavior.space = v)) }) })] })) }));
|
|
54
68
|
}
|
|
69
|
+
/** The Rotation over Lifetime module (Unity's Rotation over Lifetime): spin rate over the particle's life. */
|
|
55
70
|
export function RotationOverLifeModule({ binding }) {
|
|
56
71
|
return (_jsx(BehaviorModule, { binding: binding, title: "Rotation over Lifetime", type: "RotationOverLife", create: () => new RotationOverLife(new ConstantValue(Math.PI)), children: (behavior) => (_jsx(ValueField, { hintKey: "rotationOverLife.velocity", label: "Velocity (rad/s)", generator: behavior.angularVelocity, curveMax: Math.PI * 4, onChange: (g) => binding.apply(() => (behavior.angularVelocity = g)) })) }));
|
|
57
72
|
}
|
|
@@ -127,6 +142,7 @@ export function SequencesModule({ binding, textureOptions }) {
|
|
|
127
142
|
}, title: "Remove sequence", onClick: () => binding.apply(() => behavior.sequencers.splice(index, 1)), children: _jsx(XMarkIcon, { style: iconStyle(13) }) })] }), _jsx(Row, { label: "Time from / to (s)", children: pair(range.a, range.b, (v) => binding.apply(() => (range.a = v)), (v) => binding.apply(() => (range.b = v)), 0.1) }), texture.locations !== undefined && (_jsxs(_Fragment, { children: [_jsx(Row, { label: "Scale X / Y", children: pair(texture.scaleX, texture.scaleY, (v) => binding.apply(() => (texture.scaleX = v)), (v) => binding.apply(() => (texture.scaleY = v)), 0.01) }), _jsx(Vector3Row, { label: "Position", value: texture.position, step: 0.1, onChange: (v) => binding.apply(() => texture.position.copy(v)) })] }))] }, index));
|
|
128
143
|
}), _jsx(Row, { label: "Image", children: textureOptions && textureOptions.length > 0 ? (_jsx(SelectField, { value: imageUrl, options: textureOptions.map((option) => ({ value: option.url, label: option.label })), onChange: (url) => setImageUrl(url) })) : (_jsx("input", { className: "qe-hover", style: { flex: 1, minWidth: 0, fontSize: 12 }, placeholder: "Image URL", value: imageUrl, onChange: (event) => setImageUrl(event.target.value) })) }), _jsx(Row, { label: "Alpha threshold", children: _jsx(NumberField, { value: threshold, min: 0, step: 0.05, onChange: (v) => setThreshold(Math.min(1, v)) }) }), _jsxs("button", { className: "qe-hover", style: { display: 'flex', alignItems: 'center', gap: 4, marginTop: 6, fontSize: 12 }, onClick: () => void addFromImage(behavior), children: [_jsx(PlusIcon, { style: iconStyle(13) }), "Add sequence from image"] }), status && _jsx("p", { style: { margin: '4px 0 0', color: theme.textDim, fontSize: 11.5 }, children: status })] })) }));
|
|
129
144
|
}
|
|
145
|
+
/** The Noise module (Unity's Noise): moves and turns particles by a noise field. */
|
|
130
146
|
export function NoiseModule({ binding }) {
|
|
131
147
|
return (_jsx(BehaviorModule, { binding: binding, title: "Noise", type: "Noise", create: () => new Noise(new ConstantValue(1), new ConstantValue(1), new ConstantValue(1), new ConstantValue(0)), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx(ValueField, { hintKey: "noise.frequency", label: "Frequency", generator: behavior.frequency, min: 0, curveMax: 10, onChange: (g) => binding.apply(() => (behavior.frequency = g)) }), _jsx(ValueField, { hintKey: "noise.strength", label: "Strength", generator: behavior.power, min: 0, curveMax: 10, onChange: (g) => binding.apply(() => (behavior.power = g)) }), _jsx(ValueField, { label: "Position amount", generator: behavior.positionAmount, min: 0, curveMax: 2, onChange: (g) => binding.apply(() => (behavior.positionAmount = g)) }), _jsx(ValueField, { label: "Rotation amount", generator: behavior.rotationAmount, min: 0, curveMax: Math.PI, onChange: (g) => binding.apply(() => (behavior.rotationAmount = g)) }), _jsx(ValueField, { label: "Scroll speed", generator: behavior.scrollSpeed, curveMax: 5, onChange: (g) => binding.apply(() => (behavior.scrollSpeed = g)) }), _jsx(Row, { label: "Remap", children: _jsx(CheckboxField, { value: behavior.remap !== undefined, onChange: (v) => binding.apply(() => (behavior.remap = v
|
|
132
148
|
? new PiecewiseBezier([[new Bezier(-1, -1 / 3, 1 / 3, 1), 0]])
|
|
@@ -141,38 +157,59 @@ function Vector3Row(props) {
|
|
|
141
157
|
};
|
|
142
158
|
return (_jsx(Row, { label: props.label, hintKey: props.hintKey, children: _jsxs("div", { style: { display: 'flex', gap: 6 }, children: [_jsx(NumberField, { value: props.value.x, min: props.min, step: props.step, onChange: (n) => set('x', n) }), _jsx(NumberField, { value: props.value.y, min: props.min, step: props.step, onChange: (n) => set('y', n) }), _jsx(NumberField, { value: props.value.z, min: props.min, step: props.step, onChange: (n) => set('z', n) })] }) }));
|
|
143
159
|
}
|
|
160
|
+
/** The Turbulence module: a curl noise force field. */
|
|
144
161
|
export function TurbulenceModule({ binding }) {
|
|
145
|
-
return (_jsx(BehaviorModule, { binding: binding, title: "Turbulence", type: "TurbulenceField", create: () => new TurbulenceField(new Vector3(1, 1, 1), 3, new Vector3(1, 1, 1), new Vector3(1, 1, 1)), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx(Vector3Row, { hintKey: "turbulence.scale", label: "Scale", value: behavior.scale, min: 0.01, step: 0.1, onChange: (v) => binding.apply(() => (behavior.scale = v)) }), _jsx(Row, { label: "Octaves", children: _jsx(NumberField, { value: behavior.octaves, min: 1, step: 1, onChange: (v) => binding.apply(() => (behavior.octaves = Math.max(1, Math.round(v)))) }) }), _jsx(Vector3Row, { hintKey: "turbulence.strength", label: "Strength", value: behavior.velocityMultiplier, step: 0.5, onChange: (v) => binding.apply(() => (behavior.velocityMultiplier = v)) }), _jsx(Vector3Row, { label: "Time scale", value: behavior.timeScale, step: 0.1, onChange: (v) => binding.apply(() => (behavior.timeScale = v)) })
|
|
162
|
+
return (_jsx(BehaviorModule, { binding: binding, title: "Turbulence", type: "TurbulenceField", create: () => new TurbulenceField(new Vector3(1, 1, 1), 3, new Vector3(1, 1, 1), new Vector3(1, 1, 1)), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx(Vector3Row, { hintKey: "turbulence.scale", label: "Scale", value: behavior.scale, min: 0.01, step: 0.1, onChange: (v) => binding.apply(() => (behavior.scale = v)) }), _jsx(Row, { label: "Octaves", children: _jsx(NumberField, { value: behavior.octaves, min: 1, step: 1, onChange: (v) => binding.apply(() => (behavior.octaves = Math.max(1, Math.round(v)))) }) }), _jsx(Vector3Row, { hintKey: "turbulence.strength", label: "Strength", value: behavior.velocityMultiplier, step: 0.5, onChange: (v) => binding.apply(() => (behavior.velocityMultiplier = v)) }), _jsx(Vector3Row, { label: "Time scale", value: behavior.timeScale, step: 0.1, onChange: (v) => binding.apply(() => (behavior.timeScale = v)) }), _jsx(Row, { label: "Seed", children: _jsx(NumberField, { value: behavior.seed, step: 1, onChange: (v) => binding.apply(() => {
|
|
163
|
+
const seed = Math.round(v);
|
|
164
|
+
const next = new TurbulenceField(behavior.scale, behavior.octaves, behavior.velocityMultiplier, behavior.timeScale, seed);
|
|
165
|
+
behavior.seed = seed;
|
|
166
|
+
behavior.generator = next.generator;
|
|
167
|
+
behavior.initialOffset.copy(next.initialOffset);
|
|
168
|
+
behavior.timeOffset.copy(next.initialOffset);
|
|
169
|
+
}) }) })] })) }));
|
|
146
170
|
}
|
|
171
|
+
/** The Randomize Direction module: tilts each particle's start direction by a random angle. */
|
|
147
172
|
export function EmitDirectionModule({ binding }) {
|
|
148
|
-
return (_jsx(BehaviorModule, { binding: binding, title: "Randomize Direction", type: "ChangeEmitDirection", create: () => new ChangeEmitDirection(new IntervalValue(0, Math.PI / 6)), children: (behavior) => (_jsx(ValueField, { label: "Angle (rad)", generator: behavior.angle,
|
|
173
|
+
return (_jsx(BehaviorModule, { binding: binding, title: "Randomize Direction", type: "ChangeEmitDirection", create: () => new ChangeEmitDirection(new IntervalValue(0, Math.PI / 6)), children: (behavior) => (_jsx(ValueField, { label: "Angle (rad)", generator: behavior.angle, curve: false, min: 0, onChange: (g) => binding.apply(() => (behavior.angle = g)) })) }));
|
|
149
174
|
}
|
|
175
|
+
/** The Collision module (Unity's Collision): particles bounce off or die on a ground plane. */
|
|
150
176
|
export function CollisionModule({ binding }) {
|
|
151
177
|
// Like Unity: Collision stores bounce only. Colliders are host-provided via
|
|
152
|
-
// setPhysicsResolver — the editor never invents a ground plane.
|
|
153
|
-
return (_jsx(BehaviorModule, { binding: binding, title: "Collision", type: "ApplyCollision", create: () => new ApplyCollision(
|
|
178
|
+
// setPhysicsResolver, asked when a particle is tested — the editor never invents a ground plane.
|
|
179
|
+
return (_jsx(BehaviorModule, { binding: binding, title: "Collision", type: "ApplyCollision", create: () => new ApplyCollision(undefined, 0.5), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx(Row, { label: "Bounce", children: _jsx(NumberField, { value: behavior.bounce, min: 0, step: 0.05, onChange: (v) => binding.apply(() => (behavior.bounce = v)) }) }), _jsx("div", { style: { fontSize: 11, opacity: 0.6, marginTop: 4 }, children: "Bounce is saved with the effect. Colliders come from the host (setPhysicsResolver) \u2014 without one, Collision is inert." })] })) }));
|
|
154
180
|
}
|
|
155
181
|
/** Shared speed-range rows for the "by Speed" trio. */
|
|
156
182
|
function SpeedRangeRows({ binding, behavior }) {
|
|
157
183
|
return (_jsx(Row, { label: "Speed range", children: _jsxs("div", { style: { display: 'flex', gap: 6 }, children: [_jsx(NumberField, { value: behavior.speedRange.a, min: 0, onChange: (v) => binding.apply(() => (behavior.speedRange = new IntervalValue(v, behavior.speedRange.b))) }), _jsx(NumberField, { value: behavior.speedRange.b, min: 0, onChange: (v) => binding.apply(() => (behavior.speedRange = new IntervalValue(behavior.speedRange.a, v))) })] }) }));
|
|
158
184
|
}
|
|
185
|
+
/** The Color by Speed module (Unity's Color by Speed): a gradient over a speed range. */
|
|
159
186
|
export function ColorBySpeedModule({ binding }) {
|
|
160
187
|
return (_jsx(BehaviorModule, { binding: binding, title: "Color by Speed", type: "ColorBySpeed", create: () => new ColorBySpeed(buildGradient(DEFAULT_GRADIENT_STOPS), new IntervalValue(0, 5)), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx("div", { style: { marginTop: 6 }, children: _jsx(GradientEditor, { stops: behavior.color instanceof Gradient
|
|
161
188
|
? readGradientStops(behavior.color)
|
|
162
189
|
: DEFAULT_GRADIENT_STOPS, onChange: (stops) => binding.apply(() => (behavior.color = buildGradient(stops))) }) }), _jsx(SpeedRangeRows, { binding: binding, behavior: behavior })] })) }));
|
|
163
190
|
}
|
|
191
|
+
/** The Size by Speed module (Unity's Size by Speed): size over a speed range. */
|
|
164
192
|
export function SizeBySpeedModule({ binding }) {
|
|
165
193
|
return (_jsx(BehaviorModule, { binding: binding, title: "Size by Speed", type: "SizeBySpeed", create: () => new SizeBySpeed(buildScalar({ mode: 'curve', value: 1, min: 0, max: 1, curve: [0.5, 0.8, 1.2, 1.5] }), new IntervalValue(0, 5)), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx("div", { style: { marginTop: 6 }, children: _jsx(CurveEditor, { pieces: readPieces(behavior.size), maxValue: 2, onChange: (pieces) => binding.apply(() => (behavior.size = buildCurve(pieces))) }) }), _jsx(SpeedRangeRows, { binding: binding, behavior: behavior })] })) }));
|
|
166
194
|
}
|
|
195
|
+
/** The Rotation by Speed module (Unity's Rotation by Speed): spin rate over a speed range. */
|
|
167
196
|
export function RotationBySpeedModule({ binding }) {
|
|
168
197
|
return (_jsx(BehaviorModule, { binding: binding, title: "Rotation by Speed", type: "RotationBySpeed", create: () => new RotationBySpeed(new ConstantValue(Math.PI), new IntervalValue(0, 5)), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx(ValueField, { hintKey: "rotationBySpeed.velocity", label: "Velocity (rad/s)", generator: behavior.angularVelocity, curveMax: Math.PI * 4, onChange: (g) => binding.apply(() => (behavior.angularVelocity = g)) }), _jsx(SpeedRangeRows, { binding: binding, behavior: behavior })] })) }));
|
|
169
198
|
}
|
|
199
|
+
/** The Orbit over Lifetime module (Unity's Velocity over Lifetime orbital): turns particles about an axis. */
|
|
170
200
|
export function OrbitOverLifeModule({ binding }) {
|
|
171
201
|
return (_jsx(BehaviorModule, { binding: binding, title: "Orbit over Lifetime", type: "OrbitOverLife", create: () => new OrbitOverLife(new ConstantValue(Math.PI), new Vector3(0, 1, 0)), children: (behavior) => (_jsx(ValueField, { label: "Orbit speed", generator: behavior.orbitSpeed, curveMax: Math.PI * 4, onChange: (g) => binding.apply(() => (behavior.orbitSpeed = g)) })) }));
|
|
172
202
|
}
|
|
203
|
+
/** The Width over Trail module (Unity's Trails width over trail): a trail's width along its length. */
|
|
173
204
|
export function WidthOverTrailModule({ binding }) {
|
|
174
205
|
return (_jsx(BehaviorModule, { binding: binding, title: "Width over Trail", type: "WidthOverLength", create: () => new WidthOverLength(buildScalar({ mode: 'curve', value: 1, min: 0, max: 1, curve: [1, 0.75, 0.4, 0] })), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx("div", { style: { marginTop: 6 }, children: _jsx(CurveEditor, { pieces: readPieces(behavior.width), maxValue: 2, onChange: (pieces) => binding.apply(() => (behavior.width = buildCurve(pieces))) }) }), _jsx(Row, { label: "Absolute width", children: _jsx(CheckboxField, { value: behavior.absolute, onChange: (v) => binding.apply(() => (behavior.absolute = v)) }) })] })) }));
|
|
175
206
|
}
|
|
207
|
+
/** How many of the first frames a start-frame generator picks from: its exclusive upper bound. */
|
|
208
|
+
function startFrameCount(startFrame) {
|
|
209
|
+
const state = readScalar(startFrame);
|
|
210
|
+
return Math.round(state.mode === 'random' ? state.max : state.value);
|
|
211
|
+
}
|
|
212
|
+
/** The Texture Sheet Animation module (Unity's Texture Sheet Animation): plays the sheet's frames by life or by frame rate. */
|
|
176
213
|
export function TextureSheetModule({ binding }) {
|
|
177
214
|
const system = binding.system;
|
|
178
215
|
const frameBehavior = findBehavior(system.behaviors, 'FrameOverLife');
|
|
@@ -225,5 +262,19 @@ export function TextureSheetModule({ binding }) {
|
|
|
225
262
|
curve: [0, (tiles - 1) / 3, (2 * (tiles - 1)) / 3, tiles - 1],
|
|
226
263
|
})));
|
|
227
264
|
}
|
|
228
|
-
}) }) }), frameBehavior && (
|
|
265
|
+
}) }) }), frameBehavior && (_jsxs(_Fragment, { children: [_jsx(Row, { label: "Time mode", children: _jsx(SelectField, { value: frameBehavior.fps > 0 ? 'fps' : 'life', options: [
|
|
266
|
+
{ value: 'life', label: 'Lifetime (curve over life)' },
|
|
267
|
+
{ value: 'fps', label: 'FPS (frame from curve, then by age)' },
|
|
268
|
+
], onChange: (mode) => binding.apply(() => {
|
|
269
|
+
frameBehavior.fps = mode === 'fps' ? 12 : 0;
|
|
270
|
+
if (mode === 'fps' && frameBehavior.frameCount <= 0) {
|
|
271
|
+
frameBehavior.frameCount = tiles;
|
|
272
|
+
}
|
|
273
|
+
}) }) }), frameBehavior.fps > 0 ? (_jsxs(_Fragment, { children: [_jsx(Row, { label: "FPS", children: _jsx(NumberField, { value: frameBehavior.fps, min: 0.01, step: 1, onChange: (v) => binding.apply(() => (frameBehavior.fps = v)) }) }), _jsx(Row, { label: "Loop", children: _jsx(CheckboxField, { value: frameBehavior.loop, onChange: (v) => binding.apply(() => (frameBehavior.loop = v)) }) }), _jsx(Row, { label: "Random frame", children: _jsx(CheckboxField, { value: frameBehavior.random, onChange: (v) => binding.apply(() => (frameBehavior.random = v)) }) })] })) : (_jsx(Row, { label: "Cycles", children: _jsx(NumberField, { value: frameBehavior.cycles, min: 0.01, step: 1, onChange: (v) => binding.apply(() => (frameBehavior.cycles = v)) }) })), _jsx(Row, { label: "Frame count", children: _jsx(NumberField, { value: frameBehavior.frameCount, min: frameBehavior.fps > 0 || frameBehavior.startFrame ? 1 : 0, max: tiles, step: 1, onChange: (v) => binding.apply(() => (frameBehavior.frameCount = Math.max(0, Math.round(v)))) }) }), _jsxs(Row, { label: "Start frame", children: [_jsx(CheckboxField, { value: !!frameBehavior.startFrame, onChange: (v) => binding.apply(() => {
|
|
274
|
+
frameBehavior.startFrame = v ? new IntervalValue(0, tiles) : undefined;
|
|
275
|
+
// The offset only wraps into a segment; without one it runs off the sheet.
|
|
276
|
+
if (v && frameBehavior.frameCount <= 0) {
|
|
277
|
+
frameBehavior.frameCount = tiles;
|
|
278
|
+
}
|
|
279
|
+
}) }), frameBehavior.startFrame && (_jsx(NumberField, { value: startFrameCount(frameBehavior.startFrame), min: 1, max: tiles, step: 1, onChange: (v) => binding.apply(() => (frameBehavior.startFrame = new IntervalValue(0, Math.max(1, Math.round(v))))) }))] }), _jsx("div", { style: { marginTop: 6 }, children: _jsx(CurveEditor, { pieces: readPieces(frameBehavior.frame), maxValue: Math.max(1, tiles - 1), onChange: (pieces) => binding.apply(() => (frameBehavior.frame = buildCurve(pieces))) }) })] }))] }));
|
|
229
280
|
}
|
package/dist/react/fields.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
/** An inspector row: a label, with an optional hint, and its controls. */
|
|
2
3
|
export declare function Row(props: {
|
|
3
4
|
label: string;
|
|
4
5
|
hint?: string;
|
|
@@ -11,16 +12,24 @@ export declare function NumberField(props: {
|
|
|
11
12
|
onChange: (value: number) => void;
|
|
12
13
|
step?: number;
|
|
13
14
|
min?: number;
|
|
15
|
+
max?: number;
|
|
14
16
|
}): React.JSX.Element;
|
|
17
|
+
/** A checkbox bound to a boolean. */
|
|
15
18
|
export declare function CheckboxField(props: {
|
|
16
19
|
value: boolean;
|
|
17
20
|
onChange: (value: boolean) => void;
|
|
18
21
|
}): React.JSX.Element;
|
|
22
|
+
/** A select bound to a value, from a list of options. */
|
|
19
23
|
export declare function SelectField<T extends string | number>(props: {
|
|
24
|
+
/** The selected value. */
|
|
20
25
|
value: T;
|
|
26
|
+
/** The options, in menu order. */
|
|
21
27
|
options: Array<{
|
|
28
|
+
/** The option's value. */
|
|
22
29
|
value: T;
|
|
30
|
+
/** Its text. */
|
|
23
31
|
label: string;
|
|
24
32
|
}>;
|
|
33
|
+
/** Receives the picked value, a number when the values are numbers. */
|
|
25
34
|
onChange: (value: T) => void;
|
|
26
35
|
}): React.JSX.Element;
|
package/dist/react/fields.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import React, { useEffect, useState } from 'react';
|
|
3
|
-
import { InspectorLabel } from './InspectorLabel';
|
|
4
|
-
import { inputStyle, rowStyle } from './theme';
|
|
3
|
+
import { InspectorLabel } from './InspectorLabel.js';
|
|
4
|
+
import { inputStyle, rowStyle } from './theme.js';
|
|
5
|
+
/** An inspector row: a label, with an optional hint, and its controls. */
|
|
5
6
|
export function Row(props) {
|
|
6
7
|
return (_jsxs("div", { style: rowStyle, children: [_jsx(InspectorLabel, { label: props.label, hint: props.hint, hintKey: props.hintKey }), props.children] }));
|
|
7
8
|
}
|
|
@@ -14,7 +15,7 @@ export function NumberField(props) {
|
|
|
14
15
|
setText(String(round(props.value)));
|
|
15
16
|
}
|
|
16
17
|
}, [props.value]);
|
|
17
|
-
return (_jsx("input", { type: "number", className: "qe-hover", style: inputStyle, value: text, step: props.step ?? 0.1, min: props.min, onFocus: () => {
|
|
18
|
+
return (_jsx("input", { type: "number", className: "qe-hover", style: inputStyle, value: text, step: props.step ?? 0.1, min: props.min, max: props.max, onFocus: () => {
|
|
18
19
|
focusedRef.current = true;
|
|
19
20
|
}, onBlur: () => {
|
|
20
21
|
focusedRef.current = false;
|
|
@@ -27,9 +28,11 @@ export function NumberField(props) {
|
|
|
27
28
|
}
|
|
28
29
|
} }));
|
|
29
30
|
}
|
|
31
|
+
/** A checkbox bound to a boolean. */
|
|
30
32
|
export function CheckboxField(props) {
|
|
31
33
|
return (_jsx("input", { type: "checkbox", className: "qe-hover", checked: props.value, onChange: (e) => props.onChange(e.target.checked), style: { justifySelf: 'start', width: 15, height: 15, accentColor: '#78a5ff', cursor: 'pointer' } }));
|
|
32
34
|
}
|
|
35
|
+
/** A select bound to a value, from a list of options. */
|
|
33
36
|
export function SelectField(props) {
|
|
34
37
|
const numeric = typeof props.value === 'number';
|
|
35
38
|
return (_jsx("select", { className: "qe-hover", style: { ...inputStyle, cursor: 'pointer' }, value: String(props.value), onChange: (e) => props.onChange((numeric ? Number(e.target.value) : e.target.value)), children: props.options.map((o) => (_jsx("option", { value: String(o.value), children: o.label }, String(o.value)))) }));
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type AssetsPanelProps } from '../AssetsPanel.js';
|
|
3
|
+
/** Props of {@link AssetsDrawer}. */
|
|
4
|
+
export interface AssetsDrawerProps {
|
|
5
|
+
/** The drawer is shown. */
|
|
6
|
+
open: boolean;
|
|
7
|
+
/** The drawer is docked above the timeline instead of floating over the viewport. */
|
|
8
|
+
pinned: boolean;
|
|
9
|
+
/** Pin or unpin it (it stays open). */
|
|
10
|
+
onTogglePin(): void;
|
|
11
|
+
/** Close and unpin it. */
|
|
12
|
+
onClose(): void;
|
|
13
|
+
/** The Assets browser inside. */
|
|
14
|
+
panel: AssetsPanelProps;
|
|
15
|
+
}
|
|
16
|
+
/** The Assets browser, floating over the viewport or pinned above the timeline. */
|
|
17
|
+
export declare function AssetsDrawer(props: AssetsDrawerProps): React.ReactElement;
|
|
18
|
+
/** Props of {@link TimelineDock}. */
|
|
19
|
+
export interface TimelineDockProps {
|
|
20
|
+
/** The timeline is collapsed to its header. */
|
|
21
|
+
collapsed: boolean;
|
|
22
|
+
/** Collapse or expand it. */
|
|
23
|
+
onToggleCollapsed(): void;
|
|
24
|
+
/** Name of the focused effect, if any. */
|
|
25
|
+
effectName?: string;
|
|
26
|
+
/** Assets in the project, counted on the Assets button. */
|
|
27
|
+
assetCount: number;
|
|
28
|
+
/** The Assets button shows as switched on. */
|
|
29
|
+
assetsActive: boolean;
|
|
30
|
+
/** The Assets button. */
|
|
31
|
+
onToggleAssets(): void;
|
|
32
|
+
/** The timeline, or `null` when nothing is focused. */
|
|
33
|
+
children: React.ReactNode;
|
|
34
|
+
}
|
|
35
|
+
/** The bottom dock: the focused effect's timeline, and the Assets button. */
|
|
36
|
+
export declare function TimelineDock(props: TimelineDockProps): React.ReactElement;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ChevronDownIcon, ChevronUpIcon, MapPinIcon, XMarkIcon } from '@heroicons/react/24/solid';
|
|
3
|
+
import { AssetsPanel } from '../AssetsPanel.js';
|
|
4
|
+
import { iconStyle } from '../icons.js';
|
|
5
|
+
import { buttonStyle, theme } from '../theme.js';
|
|
6
|
+
import { activeSquareButtonStyle, dockIconButtonStyle, dockTitleStyle, DRAWER_HEIGHT, TIMELINE_HEIGHT, } from './hostStyles.js';
|
|
7
|
+
/** The Assets browser, floating over the viewport or pinned above the timeline. */
|
|
8
|
+
export function AssetsDrawer(props) {
|
|
9
|
+
const { open, pinned } = props;
|
|
10
|
+
return (_jsxs("div", { "aria-hidden": !open && !pinned, style: {
|
|
11
|
+
height: DRAWER_HEIGHT,
|
|
12
|
+
display: 'flex',
|
|
13
|
+
flexDirection: 'column',
|
|
14
|
+
background: theme.panelBg,
|
|
15
|
+
borderTop: `1px solid ${theme.border}`,
|
|
16
|
+
...(pinned
|
|
17
|
+
? { flexShrink: 0 }
|
|
18
|
+
: {
|
|
19
|
+
position: 'absolute',
|
|
20
|
+
left: 0,
|
|
21
|
+
right: 0,
|
|
22
|
+
bottom: 0,
|
|
23
|
+
zIndex: 20,
|
|
24
|
+
background: '#0a0f20',
|
|
25
|
+
boxShadow: '0 -10px 30px rgba(0, 0, 0, 0.5)',
|
|
26
|
+
transform: open ? 'none' : 'translateY(100%)',
|
|
27
|
+
opacity: open ? 1 : 0,
|
|
28
|
+
pointerEvents: open ? 'auto' : 'none',
|
|
29
|
+
transition: 'transform 0.18s ease, opacity 0.18s ease',
|
|
30
|
+
}),
|
|
31
|
+
}, children: [_jsxs("div", { style: {
|
|
32
|
+
display: 'flex',
|
|
33
|
+
alignItems: 'center',
|
|
34
|
+
gap: 6,
|
|
35
|
+
padding: '4px 6px 4px 12px',
|
|
36
|
+
borderBottom: `1px solid ${theme.border}`,
|
|
37
|
+
}, children: [_jsx("span", { style: dockTitleStyle, children: "Assets" }), _jsx("span", { style: { fontSize: 11, color: theme.textDim }, children: "Ctrl+Space" }), _jsx("span", { style: { flex: 1 } }), _jsx("button", { type: "button", className: "qe-hover", title: pinned ? 'Unpin — float over the viewport again' : 'Pin — keep it docked above the timeline', style: { ...dockIconButtonStyle, ...(pinned ? activeSquareButtonStyle : {}) }, onClick: props.onTogglePin, children: _jsx(MapPinIcon, { style: iconStyle(13) }) }), _jsx("button", { type: "button", className: "qe-hover", title: "Close (Esc)", style: dockIconButtonStyle, onClick: props.onClose, children: _jsx(XMarkIcon, { style: iconStyle(13) }) })] }), _jsx(AssetsPanel, { ...props.panel })] }));
|
|
38
|
+
}
|
|
39
|
+
/** The bottom dock: the focused effect's timeline, and the Assets button. */
|
|
40
|
+
export function TimelineDock(props) {
|
|
41
|
+
const { collapsed } = props;
|
|
42
|
+
return (_jsxs("div", { style: {
|
|
43
|
+
flexShrink: 0,
|
|
44
|
+
borderTop: `1px solid ${theme.border}`,
|
|
45
|
+
background: theme.panelBg,
|
|
46
|
+
display: 'flex',
|
|
47
|
+
flexDirection: 'column',
|
|
48
|
+
}, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 6, padding: '3px 6px' }, children: [_jsx("button", { type: "button", className: "qe-hover-bg", title: collapsed ? 'Expand timeline' : 'Collapse timeline', style: { ...dockIconButtonStyle, border: 'none', background: 'transparent' }, onClick: props.onToggleCollapsed, children: collapsed ? _jsx(ChevronUpIcon, { style: iconStyle(13) }) : _jsx(ChevronDownIcon, { style: iconStyle(13) }) }), _jsx("span", { style: dockTitleStyle, children: "Timeline" }), props.effectName !== undefined && (_jsx("span", { style: {
|
|
49
|
+
fontSize: 12,
|
|
50
|
+
color: theme.textDim,
|
|
51
|
+
overflow: 'hidden',
|
|
52
|
+
textOverflow: 'ellipsis',
|
|
53
|
+
whiteSpace: 'nowrap',
|
|
54
|
+
}, children: props.effectName })), _jsx("span", { style: { flex: 1 } }), _jsxs("button", { type: "button", className: "qe-hover", title: "Assets (Ctrl+Space)", style: {
|
|
55
|
+
...buttonStyle,
|
|
56
|
+
padding: '3px 10px',
|
|
57
|
+
...(props.assetsActive ? activeSquareButtonStyle : {}),
|
|
58
|
+
}, onClick: props.onToggleAssets, children: ["Assets", props.assetCount > 0 ? ` · ${props.assetCount}` : ''] })] }), _jsx("div", { style: {
|
|
59
|
+
height: TIMELINE_HEIGHT,
|
|
60
|
+
display: collapsed ? 'none' : 'flex',
|
|
61
|
+
borderTop: `1px solid ${theme.border}`,
|
|
62
|
+
}, children: props.children ?? (_jsx("div", { style: { padding: '14px', fontSize: 12, color: theme.textDim }, children: "Select an effect in the Scene to see its timeline." })) })] }));
|
|
63
|
+
}
|