babylon.quarks-editor 0.17.8 → 0.18.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 +46 -0
- package/README.md +1 -1
- package/dist/core/binding.js +2 -5
- package/dist/core/defaultEffect.d.ts +12 -0
- package/dist/core/defaultEffect.js +43 -0
- package/dist/core/editorScene.d.ts +7 -0
- package/dist/core/editorScene.js +11 -0
- package/dist/core/effectTree.js +2 -1
- package/dist/core/emitterShapeWireframe.d.ts +20 -0
- package/dist/core/emitterShapeWireframe.js +329 -0
- package/dist/core/galleryDragSession.d.ts +4 -0
- package/dist/core/galleryDragSession.js +8 -0
- package/dist/core/galleryDropMarker.d.ts +13 -0
- package/dist/core/galleryDropMarker.js +42 -0
- package/dist/core/galleryLayout.d.ts +20 -0
- package/dist/core/galleryLayout.js +68 -0
- package/dist/core/galleryPlayback.d.ts +14 -0
- package/dist/core/galleryPlayback.js +72 -0
- package/dist/core/galleryScene.d.ts +20 -0
- package/dist/core/galleryScene.js +65 -0
- package/dist/core/gallerySelectionMarker.d.ts +25 -0
- package/dist/core/gallerySelectionMarker.js +69 -0
- package/dist/core/galleryTree.d.ts +49 -0
- package/dist/core/galleryTree.js +138 -0
- package/dist/core/loadEffect.d.ts +13 -0
- package/dist/core/loadEffect.js +31 -17
- package/dist/core/loadEffectGallery.d.ts +36 -0
- package/dist/core/loadEffectGallery.js +97 -0
- package/dist/core/material.d.ts +13 -0
- package/dist/core/material.js +68 -0
- package/dist/core/pickGalleryFiles.d.ts +5 -0
- package/dist/core/pickGalleryFiles.js +42 -0
- package/dist/core/playback.d.ts +23 -0
- package/dist/core/playback.js +32 -0
- package/dist/core/renderDebug.d.ts +8 -0
- package/dist/core/renderDebug.js +42 -0
- package/dist/core/scrub.d.ts +14 -0
- package/dist/core/scrub.js +70 -0
- package/dist/core/timeline.d.ts +4 -1
- package/dist/core/timeline.js +30 -3
- package/dist/core/transformStore.d.ts +3 -0
- package/dist/core/transformStore.js +16 -0
- package/dist/core/viewportPicker.d.ts +25 -0
- package/dist/core/viewportPicker.js +86 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/react/EffectEditor.js +1 -4
- package/dist/react/EffectEditorHost.d.ts +7 -0
- package/dist/react/EffectEditorHost.js +745 -115
- package/dist/react/GalleryPanel.d.ts +20 -0
- package/dist/react/GalleryPanel.js +246 -0
- package/dist/react/InspectorInfoIcon.d.ts +7 -0
- package/dist/react/InspectorInfoIcon.js +22 -0
- package/dist/react/InspectorLabel.d.ts +7 -0
- package/dist/react/InspectorLabel.js +12 -0
- package/dist/react/MessageDialog.d.ts +9 -0
- package/dist/react/MessageDialog.js +24 -0
- package/dist/react/ModuleSection.d.ts +2 -0
- package/dist/react/ModuleSection.js +8 -1
- package/dist/react/ObjectModule.js +14 -2
- package/dist/react/TimelinePanel.d.ts +8 -11
- package/dist/react/TimelinePanel.js +129 -98
- package/dist/react/TimelineTrackRow.js +29 -15
- package/dist/react/Tooltip.d.ts +6 -0
- package/dist/react/Tooltip.js +65 -0
- package/dist/react/ValueField.d.ts +2 -0
- package/dist/react/ValueField.js +1 -1
- package/dist/react/behaviorModules.js +8 -8
- package/dist/react/fields.d.ts +2 -0
- package/dist/react/fields.js +14 -5
- package/dist/react/icons.d.ts +7 -0
- package/dist/react/icons.js +10 -0
- package/dist/react/index.d.ts +4 -0
- package/dist/react/index.js +2 -0
- package/dist/react/inspectorHints.d.ts +2 -0
- package/dist/react/inspectorHints.js +138 -0
- package/dist/react/modules.js +109 -81
- package/dist/react/theme.d.ts +2 -0
- package/dist/react/theme.js +53 -1
- package/package.json +8 -4
- package/src/core/binding.ts +2 -5
- package/src/core/defaultEffect.ts +74 -0
- package/src/core/editorScene.ts +14 -0
- package/src/core/effectTree.ts +2 -1
- package/src/core/emitterShapeWireframe.ts +373 -0
- package/src/core/galleryDragSession.ts +12 -0
- package/src/core/galleryDropMarker.ts +57 -0
- package/src/core/galleryLayout.ts +90 -0
- package/src/core/galleryPlayback.ts +90 -0
- package/src/core/galleryScene.ts +92 -0
- package/src/core/gallerySelectionMarker.ts +84 -0
- package/src/core/galleryTree.ts +187 -0
- package/src/core/loadEffect.ts +55 -18
- package/src/core/loadEffectGallery.ts +146 -0
- package/src/core/material.ts +84 -0
- package/src/core/pickGalleryFiles.ts +58 -0
- package/src/core/playback.ts +49 -0
- package/src/core/renderDebug.ts +46 -0
- package/src/core/scrub.ts +76 -1
- package/src/core/timeline.ts +34 -3
- package/src/core/transformStore.ts +19 -0
- package/src/core/viewportPicker.ts +114 -0
- package/src/index.ts +2 -0
- package/src/react/EffectEditor.tsx +5 -7
- package/src/react/EffectEditorHost.tsx +936 -142
- package/src/react/GalleryPanel.tsx +517 -0
- package/src/react/InspectorInfoIcon.tsx +36 -0
- package/src/react/InspectorLabel.tsx +18 -0
- package/src/react/MessageDialog.tsx +57 -0
- package/src/react/ModuleSection.tsx +17 -2
- package/src/react/ObjectModule.tsx +17 -6
- package/src/react/TimelinePanel.tsx +227 -174
- package/src/react/TimelineTrackRow.tsx +58 -23
- package/src/react/Tooltip.tsx +93 -0
- package/src/react/ValueField.tsx +3 -1
- package/src/react/behaviorModules.tsx +13 -8
- package/src/react/fields.tsx +15 -6
- package/src/react/icons.ts +19 -0
- package/src/react/index.ts +4 -0
- package/src/react/inspectorHints.ts +150 -0
- package/src/react/modules.tsx +198 -183
- package/src/react/theme.ts +54 -1
package/dist/react/fields.js
CHANGED
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useState } from 'react';
|
|
3
|
-
import {
|
|
2
|
+
import React, { useEffect, useState } from 'react';
|
|
3
|
+
import { InspectorLabel } from './InspectorLabel';
|
|
4
|
+
import { inputStyle, rowStyle } from './theme';
|
|
4
5
|
export function Row(props) {
|
|
5
|
-
return (_jsxs("div", { style: rowStyle, children: [_jsx(
|
|
6
|
+
return (_jsxs("div", { style: rowStyle, children: [_jsx(InspectorLabel, { label: props.label, hint: props.hint, hintKey: props.hintKey }), props.children] }));
|
|
6
7
|
}
|
|
7
8
|
/** Number input that keeps focus-friendly local text state and commits parsed values. */
|
|
8
9
|
export function NumberField(props) {
|
|
9
10
|
const [text, setText] = useState(String(round(props.value)));
|
|
11
|
+
const focusedRef = React.useRef(false);
|
|
10
12
|
useEffect(() => {
|
|
11
|
-
|
|
13
|
+
if (!focusedRef.current) {
|
|
14
|
+
setText(String(round(props.value)));
|
|
15
|
+
}
|
|
12
16
|
}, [props.value]);
|
|
13
|
-
return (_jsx("input", { type: "number", className: "qe-hover", style: inputStyle, value: text, step: props.step ?? 0.1, min: props.min,
|
|
17
|
+
return (_jsx("input", { type: "number", className: "qe-hover", style: inputStyle, value: text, step: props.step ?? 0.1, min: props.min, onFocus: () => {
|
|
18
|
+
focusedRef.current = true;
|
|
19
|
+
}, onBlur: () => {
|
|
20
|
+
focusedRef.current = false;
|
|
21
|
+
setText(String(round(props.value)));
|
|
22
|
+
}, onChange: (e) => {
|
|
14
23
|
setText(e.target.value);
|
|
15
24
|
const parsed = parseFloat(e.target.value);
|
|
16
25
|
if (!Number.isNaN(parsed)) {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ComponentType, CSSProperties, SVGProps } from 'react';
|
|
2
|
+
export type HeroIcon = ComponentType<SVGProps<SVGSVGElement> & {
|
|
3
|
+
title?: string;
|
|
4
|
+
titleId?: string;
|
|
5
|
+
}>;
|
|
6
|
+
/** Shared size/style for toolbar and inline icon buttons. */
|
|
7
|
+
export declare function iconStyle(size?: number, extra?: CSSProperties): CSSProperties;
|
package/dist/react/index.d.ts
CHANGED
|
@@ -16,5 +16,9 @@ export { ValueField } from './ValueField';
|
|
|
16
16
|
export { Row, NumberField, CheckboxField, SelectField } from './fields';
|
|
17
17
|
export { MainModule, EmissionModule, ShapeModule, SizeOverLifeModule, ColorOverLifeModule, RendererModule, } from './modules';
|
|
18
18
|
export { theme, inputStyle, rowStyle, labelStyle, buttonStyle } from './theme';
|
|
19
|
+
export { MessageDialog } from './MessageDialog';
|
|
20
|
+
export type { MessageDialogProps } from './MessageDialog';
|
|
21
|
+
export { GalleryPanel } from './GalleryPanel';
|
|
22
|
+
export type { GalleryPanelProps } from './GalleryPanel';
|
|
19
23
|
export { EffectEditorHost, QuarksEffectEditor } from './EffectEditorHost';
|
|
20
24
|
export type { EffectEditorHostProps, EffectEditorHostHandle } from './EffectEditorHost';
|
package/dist/react/index.js
CHANGED
|
@@ -11,4 +11,6 @@ export { ValueField } from './ValueField';
|
|
|
11
11
|
export { Row, NumberField, CheckboxField, SelectField } from './fields';
|
|
12
12
|
export { MainModule, EmissionModule, ShapeModule, SizeOverLifeModule, ColorOverLifeModule, RendererModule, } from './modules';
|
|
13
13
|
export { theme, inputStyle, rowStyle, labelStyle, buttonStyle } from './theme';
|
|
14
|
+
export { MessageDialog } from './MessageDialog';
|
|
15
|
+
export { GalleryPanel } from './GalleryPanel';
|
|
14
16
|
export { EffectEditorHost, QuarksEffectEditor } from './EffectEditorHost';
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/** English tooltips for inspector labels and module titles. */
|
|
2
|
+
const INSPECTOR_HINTS = {
|
|
3
|
+
// Module titles
|
|
4
|
+
Main: 'Core timing and initial particle properties when each particle is born.',
|
|
5
|
+
Emission: 'How many particles spawn continuously, along movement, or in timed bursts.',
|
|
6
|
+
Shape: 'The volume or surface particles spawn from (sphere, box, cone, mesh, etc.).',
|
|
7
|
+
'Size over Lifetime': 'Multiplies each particle’s size over its normalized lifetime (0 = birth, 1 = death).',
|
|
8
|
+
'Color over Lifetime': 'Tints color and alpha over each particle’s normalized lifetime.',
|
|
9
|
+
Renderer: 'How particles are rendered: billboard mode, material, texture, depth, and sort order.',
|
|
10
|
+
'Speed over Lifetime': 'Multiplies particle speed over normalized lifetime.',
|
|
11
|
+
'Limit Speed over Lifetime': 'Clamps particle speed and optionally dampens velocity toward the limit.',
|
|
12
|
+
'Force over Lifetime': 'Adds constant world-space acceleration (force per mass) over lifetime.',
|
|
13
|
+
'Velocity over Lifetime': 'Adds linear and orbital velocity each frame in local or world space.',
|
|
14
|
+
'Inherit Velocity': 'Copies the emitter’s movement into new particle velocity.',
|
|
15
|
+
Gravity: 'Applies a constant downward force (Earth-like gravity by default).',
|
|
16
|
+
'Rotation over Lifetime': 'Spins particles by setting angular velocity over lifetime.',
|
|
17
|
+
Noise: 'Procedural noise that jitters particle position and rotation.',
|
|
18
|
+
Turbulence: '3D turbulent velocity field that swirls particles through space.',
|
|
19
|
+
'Randomize Direction': 'Randomizes emission direction within a cone angle around the shape normal.',
|
|
20
|
+
Collision: 'Bounces particles off a ground plane in the editor preview.',
|
|
21
|
+
'Color by Speed': 'Maps particle color from current speed using a gradient.',
|
|
22
|
+
'Size by Speed': 'Maps particle size from current speed using a curve.',
|
|
23
|
+
'Rotation by Speed': 'Maps angular velocity from current speed.',
|
|
24
|
+
'Orbit over Lifetime': 'Orbits particles around a local axis over their lifetime.',
|
|
25
|
+
'Width over Trail': 'Controls trail ribbon width along the particle path (trail render mode).',
|
|
26
|
+
'Texture Sheet Animation': 'Animates UV tiles on the texture like a flipbook sprite sheet.',
|
|
27
|
+
'Sub Emitters': 'Spawns a linked child particle system on birth, death, or every frame.',
|
|
28
|
+
Object: 'Transform of the selected node. Moving the stage parent is preview-only and not exported.',
|
|
29
|
+
// Main
|
|
30
|
+
Duration: 'How long the particle system runs before stopping (non-looping) or one loop cycle.',
|
|
31
|
+
Looping: 'When enabled, the effect restarts from the beginning after duration ends.',
|
|
32
|
+
'World space': 'Simulates in world coordinates so moving the emitter does not drag existing particles.',
|
|
33
|
+
Prewarm: 'Simulates the effect once at start so it already looks mid-playback when play begins.',
|
|
34
|
+
'Start delay': 'Wait time before emission starts after play or restart.',
|
|
35
|
+
'Start lifetime': 'How long each spawned particle lives before dying.',
|
|
36
|
+
'Start speed': 'Initial speed along the emission direction when a particle is born.',
|
|
37
|
+
'Start size': 'Initial size of particles — uniform scalar or separate X/Y/Z for mesh billboards.',
|
|
38
|
+
'Start color': 'Initial color: solid, random between two, or gradient-based.',
|
|
39
|
+
'Start rotation': 'Initial orientation: fixed angle, random, per-axis Euler, or random 3D quaternion.',
|
|
40
|
+
' X (rad)': 'Start rotation around the local X axis in radians.',
|
|
41
|
+
' Y (rad)': 'Start rotation around the local Y axis in radians.',
|
|
42
|
+
' Z (rad)': 'Start rotation around the local Z axis in radians.',
|
|
43
|
+
// Emission
|
|
44
|
+
'Rate over time': 'Particles spawned per second while the system is playing.',
|
|
45
|
+
'Rate over distance': 'Particles spawned per unit of emitter movement.',
|
|
46
|
+
Time: 'Seconds from effect start when this burst fires.',
|
|
47
|
+
Count: 'Number of particles emitted in each burst cycle.',
|
|
48
|
+
Cycles: 'How many times the burst repeats.',
|
|
49
|
+
Interval: 'Seconds between repeated cycles of the same burst.',
|
|
50
|
+
Probability: 'Chance (0–1) that the burst or sub-emitter spawn actually happens.',
|
|
51
|
+
// Shape
|
|
52
|
+
'shape.type': 'Emitter volume type that defines where new particles appear.',
|
|
53
|
+
Radius: 'Outer radius of spherical or circular emitter shapes.',
|
|
54
|
+
Arc: 'Angular span (radians) of partial circle or sphere emission.',
|
|
55
|
+
Thickness: 'Shell thickness for hollow shapes (0 = surface, 1 = filled volume).',
|
|
56
|
+
Angle: 'Cone opening angle for cone-shaped emitters.',
|
|
57
|
+
'Donut radius': 'Inner tube radius of a torus/donut emitter.',
|
|
58
|
+
Width: 'Emitter extent along the local X axis (box/grid shapes).',
|
|
59
|
+
Height: 'Emitter extent along the local Y axis (box/grid shapes).',
|
|
60
|
+
Columns: 'Number of grid columns for grid-shaped emission.',
|
|
61
|
+
Rows: 'Number of grid rows for grid-shaped emission.',
|
|
62
|
+
'Source mesh': 'Scene mesh whose surface particles spawn from (mesh surface shape).',
|
|
63
|
+
'Emit mode': 'How particles are distributed across the shape: random, loop, ping-pong, or burst spread.',
|
|
64
|
+
Spread: 'Randomizes spawn position within the shape (0 = exact shape, higher = more scatter).',
|
|
65
|
+
// Renderer
|
|
66
|
+
'Render mode': 'Orientation and geometry used to draw each particle.',
|
|
67
|
+
'Trail length': 'Maximum number of trail segments per particle (trail mode).',
|
|
68
|
+
'Follow origin': 'Keeps the trail anchored to the emitter origin instead of the particle head.',
|
|
69
|
+
'Speed factor': 'How much particle speed stretches the billboard (stretched billboard mode).',
|
|
70
|
+
'Length factor': 'Base stretch multiplier for stretched billboards.',
|
|
71
|
+
Source: 'Material preset loaded from the effect JSON (read-only reference).',
|
|
72
|
+
'Blend mode': 'How particle color is composited over the background.',
|
|
73
|
+
Transparent: 'Enables alpha blending for soft edges and fade-outs.',
|
|
74
|
+
Texture: 'Sprite or flipbook image applied to each particle.',
|
|
75
|
+
'Alpha test': 'Pixels below this alpha are discarded (cutout/hard edges).',
|
|
76
|
+
'Depth write': 'Writes to the depth buffer — usually off for additive particles.',
|
|
77
|
+
'Depth test': 'Whether particles are occluded by scene geometry.',
|
|
78
|
+
Geometry: 'Mesh vertices used when render mode is Mesh.',
|
|
79
|
+
'Render order': 'Sort priority within the batch — higher draws on top.',
|
|
80
|
+
'Soft particles': 'Fades particles near intersecting geometry to reduce hard cutouts.',
|
|
81
|
+
'Fade near/far': 'Distance range (near, far) for soft-particle depth fading.',
|
|
82
|
+
// Texture sheet
|
|
83
|
+
'Tiles U': 'Number of horizontal cells in the texture atlas.',
|
|
84
|
+
'Tiles V': 'Number of vertical cells in the texture atlas.',
|
|
85
|
+
'Blend tiles': 'Smoothly blends between adjacent atlas tiles during animation.',
|
|
86
|
+
'Start tile': 'Atlas cell index where playback begins (0 = top-left).',
|
|
87
|
+
'Animate frames': 'Enables frame-over-lifetime curve to cycle through atlas tiles.',
|
|
88
|
+
// Sub emitters
|
|
89
|
+
Trigger: 'When the child system is spawned: on particle birth, death, or every frame.',
|
|
90
|
+
'Use velocity': 'Orients the sub-emitter spawn using the parent particle’s velocity.',
|
|
91
|
+
// Object transform
|
|
92
|
+
Position: 'Local X/Y/Z position of the selected emitter or group node.',
|
|
93
|
+
Rotation: 'Local rotation in degrees (Euler) of the selected node.',
|
|
94
|
+
Scale: 'Local X/Y/Z scale of the selected node.',
|
|
95
|
+
// Gradient editor
|
|
96
|
+
'Stop color': 'RGB color at the selected gradient key.',
|
|
97
|
+
'Stop alpha': 'Opacity at the selected gradient key (0 = transparent, 1 = opaque).',
|
|
98
|
+
'Stop position': 'Normalized position of this key along the gradient (0 = start, 1 = end).',
|
|
99
|
+
// Shared / behavior fields
|
|
100
|
+
Dampen: 'How quickly velocity is reduced when speed exceeds the limit (0 = hard clamp, 1 = smooth).',
|
|
101
|
+
Space: 'Whether velocity is applied in local emitter space or world space.',
|
|
102
|
+
'Linear X': 'Added velocity along local/world X over lifetime.',
|
|
103
|
+
'Linear Y': 'Added velocity along local/world Y over lifetime.',
|
|
104
|
+
'Linear Z': 'Added velocity along local/world Z over lifetime.',
|
|
105
|
+
'Orbital X': 'Orbital rotation speed around the X axis (radians per second).',
|
|
106
|
+
'Orbital Y': 'Orbital rotation speed around the Y axis (radians per second).',
|
|
107
|
+
'Orbital Z': 'Orbital rotation speed around the Z axis (radians per second).',
|
|
108
|
+
Multiplier: 'Scales how much emitter velocity is copied to new particles.',
|
|
109
|
+
'inheritVelocity.mode': 'Initial copies spawn-time velocity; Current uses live emitter velocity.',
|
|
110
|
+
'gravity.strength': 'Magnitude of the downward force applied to every particle.',
|
|
111
|
+
'noise.frequency': 'How quickly the noise pattern changes over time and space.',
|
|
112
|
+
'noise.strength': 'Overall intensity of the noise displacement.',
|
|
113
|
+
'Position amount': 'How strongly noise affects particle position.',
|
|
114
|
+
'Rotation amount': 'How strongly noise affects particle rotation.',
|
|
115
|
+
'turbulence.scale': 'Size of turbulence cells — larger values make smoother, broader swirls.',
|
|
116
|
+
Octaves: 'Number of noise layers combined for finer turbulent detail.',
|
|
117
|
+
'turbulence.strength': 'Per-axis multiplier for turbulent velocity.',
|
|
118
|
+
'Time scale': 'How fast the turbulence field evolves over time (per axis).',
|
|
119
|
+
'Angle (rad)': 'Maximum random deflection from the base emit direction, in radians.',
|
|
120
|
+
Bounce: 'Energy retained after hitting the ground (0 = no bounce, 1 = full bounce).',
|
|
121
|
+
'Floor Y': 'World-space Y height of the preview ground plane used for collision.',
|
|
122
|
+
'Speed range': 'Particle speed range (min, max) that maps to the color/size/rotation curve.',
|
|
123
|
+
'Orbit speed': 'Angular speed of orbit around the configured axis over lifetime.',
|
|
124
|
+
'rotationOverLife.velocity': 'Angular velocity applied over normalized lifetime (rad/s).',
|
|
125
|
+
'rotationBySpeed.velocity': 'Angular velocity mapped from particle speed (rad/s).',
|
|
126
|
+
// Size main axes
|
|
127
|
+
X: 'Start size along the local X axis.',
|
|
128
|
+
Y: 'Start size along the local Y axis.',
|
|
129
|
+
Z: 'Start size along the local Z axis.',
|
|
130
|
+
// Force over life axes
|
|
131
|
+
'force.x': 'Acceleration along world X applied over lifetime.',
|
|
132
|
+
'force.y': 'Acceleration along world Y applied over lifetime.',
|
|
133
|
+
'force.z': 'Acceleration along world Z applied over lifetime.',
|
|
134
|
+
};
|
|
135
|
+
/** Returns the English tooltip for an inspector label or hint key. */
|
|
136
|
+
export function getInspectorHint(key) {
|
|
137
|
+
return INSPECTOR_HINTS[key];
|
|
138
|
+
}
|
package/dist/react/modules.js
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import React, { useState } from 'react';
|
|
2
|
+
import React, { useMemo, useState } from 'react';
|
|
3
3
|
import { Mesh } from '@babylonjs/core/Meshes/mesh';
|
|
4
|
+
import { Texture } from '@babylonjs/core/Materials/Textures/texture';
|
|
4
5
|
import { ColorOverLife, ConstantColor, ConstantValue, EulerGenerator, Gradient, IntervalValue, RandomColor, RandomColorBetweenGradient, RandomQuatGenerator, RenderMode, SizeOverLife, Vector3Function, Vector4, } from 'babylon.quarks';
|
|
5
6
|
import { DEFAULT_GRADIENT_STOPS, buildGradient, findBehavior, hexToRgb, readGradientStops, rgbToHex, } from '../core/colors';
|
|
6
7
|
import { DEFAULT_SHAPE_PARAMS, SHAPE_PARAM_KEYS, SHAPE_TYPES, createShape, getShapeType, readShapeParams } from '../core/shapes';
|
|
7
8
|
import { buildCurve, buildScalar, readPieces, readScalar } from '../core/values';
|
|
9
|
+
import { applyRendererMaterial, getMaterialLabel } from '../core/material';
|
|
8
10
|
import { CurveEditor } from './CurveEditor';
|
|
9
11
|
import { GradientEditor } from './GradientEditor';
|
|
10
12
|
import { ModuleSection } from './ModuleSection';
|
|
11
13
|
import { PromptDialog } from './PromptDialog';
|
|
12
14
|
import { CheckboxField, NumberField, Row, SelectField } from './fields';
|
|
13
15
|
import { ValueField } from './ValueField';
|
|
16
|
+
import { iconStyle } from './icons';
|
|
17
|
+
import { theme } from './theme';
|
|
18
|
+
import { PlusIcon, XMarkIcon } from '@heroicons/react/24/solid';
|
|
19
|
+
import { InspectorInfoIcon } from './InspectorInfoIcon';
|
|
14
20
|
const MESH_QUAD_POSITIONS = new Float32Array([-0.5, -0.5, 0, 0.5, -0.5, 0, 0.5, 0.5, 0, -0.5, 0.5, 0]);
|
|
15
21
|
const MESH_QUAD_INDICES = new Uint32Array([0, 1, 2, 0, 2, 3]);
|
|
16
22
|
const MESH_QUAD_UVS = new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]);
|
|
@@ -154,7 +160,19 @@ export function MainModule({ binding }) {
|
|
|
154
160
|
export function EmissionModule({ binding }) {
|
|
155
161
|
const system = binding.system;
|
|
156
162
|
const bursts = system.emissionBursts ?? [];
|
|
157
|
-
return (_jsxs(ModuleSection, { title: "Emission", children: [_jsx(ValueField, { label: "Rate over time", generator: system.emissionOverTime, min: 0, curveMax: 500, onChange: (g) => binding.apply((s) => (s.emissionOverTime = g)) }), _jsx(ValueField, { label: "Rate over distance", generator: system.emissionOverDistance, min: 0, curveMax: 50, onChange: (g) => binding.apply((s) => (s.emissionOverDistance = g)) }), bursts.map((burst, i) => (_jsxs("div", { style: { marginTop: 8, padding: '6px 8px', border: '1px solid #22305c', borderRadius: 8 }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between' }, children: [_jsxs("span", { style: { fontSize: 11.5, color: '#9eb9ff' }, children: ["Burst ", i + 1] }), _jsx("button", { className: "qe-hover", style: { background: 'none', border: 'none', color: '#e08c8c', cursor: 'pointer',
|
|
163
|
+
return (_jsxs(ModuleSection, { title: "Emission", children: [_jsx(ValueField, { label: "Rate over time", generator: system.emissionOverTime, min: 0, curveMax: 500, onChange: (g) => binding.apply((s) => (s.emissionOverTime = g)) }), _jsx(ValueField, { label: "Rate over distance", generator: system.emissionOverDistance, min: 0, curveMax: 50, onChange: (g) => binding.apply((s) => (s.emissionOverDistance = g)) }), bursts.map((burst, i) => (_jsxs("div", { style: { marginTop: 8, padding: '6px 8px', border: '1px solid #22305c', borderRadius: 8 }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between' }, children: [_jsxs("span", { style: { fontSize: 11.5, color: '#9eb9ff' }, children: ["Burst ", i + 1] }), _jsx("button", { className: "qe-hover", style: { background: 'none', border: 'none', color: '#e08c8c', cursor: 'pointer', padding: 0, display: 'inline-flex' }, title: "Remove burst", onClick: () => binding.apply((s) => s.emissionBursts.splice(i, 1)), children: _jsx(XMarkIcon, { style: iconStyle(14) }) })] }), _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 2, marginTop: 4 }, children: [_jsx(Row, { label: "Time", children: _jsx(NumberField, { value: burst.time, min: 0, onChange: (time) => binding.apply(() => (burst.time = time)) }) }), _jsx(Row, { label: "Count", children: _jsx(NumberField, { value: readScalar(burst.count).value, min: 0, step: 1, onChange: (count) => binding.apply(() => (burst.count = new ConstantValue(Math.round(count)))) }) }), _jsx(Row, { label: "Cycles", children: _jsx(NumberField, { value: burst.cycle, min: 1, step: 1, onChange: (v) => binding.apply(() => (burst.cycle = Math.round(v))) }) }), _jsx(Row, { label: "Interval", children: _jsx(NumberField, { value: burst.interval, min: 0.01, onChange: (v) => binding.apply(() => (burst.interval = v)) }) }), _jsx(Row, { label: "Probability", children: _jsx(NumberField, { value: burst.probability, min: 0, step: 0.05, onChange: (v) => binding.apply(() => (burst.probability = Math.min(1, v))) }) })] })] }, i))), _jsxs("button", { className: "qe-hover", style: {
|
|
164
|
+
marginTop: 8,
|
|
165
|
+
background: 'none',
|
|
166
|
+
border: '1px dashed #34477f',
|
|
167
|
+
color: '#9eb9ff',
|
|
168
|
+
borderRadius: 8,
|
|
169
|
+
padding: '5px 10px',
|
|
170
|
+
cursor: 'pointer',
|
|
171
|
+
fontSize: 12.5,
|
|
172
|
+
display: 'inline-flex',
|
|
173
|
+
alignItems: 'center',
|
|
174
|
+
gap: 6,
|
|
175
|
+
}, onClick: () => binding.apply((s) => s.emissionBursts.push({ time: 0, count: new ConstantValue(20), cycle: 1, interval: 0.01, probability: 1 })), children: [_jsx(PlusIcon, { style: iconStyle(13) }), "Add burst"] })] }));
|
|
158
176
|
}
|
|
159
177
|
// Shapes whose constructor doesn't accept `mode`/`spread` at all — showing those controls
|
|
160
178
|
// for them would edit a value the shape silently ignores, just like the old "default to
|
|
@@ -168,7 +186,7 @@ export function ShapeModule({ binding }) {
|
|
|
168
186
|
const scene = system.emitter.getScene();
|
|
169
187
|
// 'vfxBatch' is the renderer's own internal instancing mesh — never a valid emission source.
|
|
170
188
|
const meshOptions = scene.meshes.filter((m) => m instanceof Mesh && m.name !== 'vfxBatch' && m.getTotalVertices() > 0);
|
|
171
|
-
return (_jsxs(ModuleSection, { title: "Shape", children: [_jsx(Row, { label: "Shape", children: _jsx(SelectField, { value: type, options: SHAPE_TYPES, onChange: (next) => binding.apply((s) => (s.emitterShape = createShape(next, params))) }) }), keys.map((key) => (_jsx(Row, { label: PARAM_LABELS[key], children: _jsx(NumberField, { value: params[key], min: 0, onChange: (v) => binding.apply((s) => (s.emitterShape = createShape(type, { ...params, [key]: v }))) }) }, key))), type === 'mesh_surface' && (_jsx(Row, { label: "Source mesh", children: _jsx(SelectField, { value: params.mesh?.uniqueId != null ? String(params.mesh.uniqueId) : '__none', options: [
|
|
189
|
+
return (_jsxs(ModuleSection, { title: "Shape", children: [_jsx(Row, { label: "Shape", hintKey: "shape.type", children: _jsx(SelectField, { value: type, options: SHAPE_TYPES, onChange: (next) => binding.apply((s) => (s.emitterShape = createShape(next, params))) }) }), keys.map((key) => (_jsx(Row, { label: PARAM_LABELS[key], children: _jsx(NumberField, { value: params[key], min: 0, onChange: (v) => binding.apply((s) => (s.emitterShape = createShape(type, { ...params, [key]: v }))) }) }, key))), type === 'mesh_surface' && (_jsx(Row, { label: "Source mesh", children: _jsx(SelectField, { value: params.mesh?.uniqueId != null ? String(params.mesh.uniqueId) : '__none', options: [
|
|
172
190
|
{ value: '__none', label: '(none)' },
|
|
173
191
|
...meshOptions.map((m) => ({ value: String(m.uniqueId), label: m.name })),
|
|
174
192
|
], onChange: (id) => {
|
|
@@ -279,6 +297,19 @@ export function RendererModule({ binding, textureOptions, geometryOptions, resol
|
|
|
279
297
|
const system = binding.system;
|
|
280
298
|
const currentTextureUrl = system.texture?.url ?? '';
|
|
281
299
|
const [promptingTextureUrl, setPromptingTextureUrl] = useState(false);
|
|
300
|
+
const materialLabel = getMaterialLabel(system.material);
|
|
301
|
+
const loadTexture = useMemo(() => {
|
|
302
|
+
return (url) => {
|
|
303
|
+
if (resolveTexture) {
|
|
304
|
+
return resolveTexture(url);
|
|
305
|
+
}
|
|
306
|
+
const scene = system.emitter.getScene();
|
|
307
|
+
return scene ? new Texture(url, scene) : null;
|
|
308
|
+
};
|
|
309
|
+
}, [resolveTexture, system]);
|
|
310
|
+
const patchMaterial = (patch) => {
|
|
311
|
+
binding.apply((s) => applyRendererMaterial(s, patch));
|
|
312
|
+
};
|
|
282
313
|
return (_jsxs(ModuleSection, { title: "Renderer", children: [_jsx(Row, { label: "Render mode", children: _jsx(SelectField, { value: system.renderMode, options: [
|
|
283
314
|
{ value: RenderMode.BillBoard, label: 'Billboard' },
|
|
284
315
|
{ value: RenderMode.StretchedBillBoard, label: 'Stretched billboard' },
|
|
@@ -290,62 +321,7 @@ export function RendererModule({ binding, textureOptions, geometryOptions, resol
|
|
|
290
321
|
s.rendererEmitterSettings.startLength = new ConstantValue(Math.round(v));
|
|
291
322
|
}) }) }), _jsx(Row, { label: "Follow origin", children: _jsx(CheckboxField, { value: !!system.rendererEmitterSettings.followLocalOrigin, onChange: (v) => binding.apply((s) => {
|
|
292
323
|
s.rendererEmitterSettings.followLocalOrigin = v;
|
|
293
|
-
}) }) })] })), system.renderMode === RenderMode.StretchedBillBoard && (_jsxs(_Fragment, { children: [_jsx(Row, { label: "Speed factor", children: _jsx(NumberField, { value: system.rendererEmitterSettings.speedFactor ?? 0, step: 0.1, onChange: (v) => binding.apply((s) => (s.rendererEmitterSettings.speedFactor = v)) }) }), _jsx(Row, { label: "Length factor", children: _jsx(NumberField, { value: system.rendererEmitterSettings.lengthFactor ?? 2, step: 0.1, onChange: (v) => binding.apply((s) => (s.rendererEmitterSettings.lengthFactor = v)) }) })] })),
|
|
294
|
-
(() => {
|
|
295
|
-
const preset = detectGeometryPreset(system.instancingGeometry);
|
|
296
|
-
const vertexCount = system.instancingGeometry.length / 3;
|
|
297
|
-
const triangleCount = (system.getRendererSettings().instancingIndices?.length ?? 0) / 3;
|
|
298
|
-
const hostGeometry = geometryOptions ?? [];
|
|
299
|
-
const applyGeometry = (g) => binding.apply((s) => {
|
|
300
|
-
const settings = s.getRendererSettings();
|
|
301
|
-
settings.instancingIndices = (g.indices ? new Uint32Array(g.indices) : undefined);
|
|
302
|
-
settings.instancingUVs = (g.uvs ? new Float32Array(g.uvs) : undefined);
|
|
303
|
-
settings.instancingNormals = (g.normals ? new Float32Array(g.normals) : undefined);
|
|
304
|
-
s.instancingGeometry = new Float32Array(g.positions);
|
|
305
|
-
});
|
|
306
|
-
return (_jsx(Row, { label: "Geometry", children: _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 6 }, children: [_jsx(SelectField, { value: preset, options: [
|
|
307
|
-
...(preset === 'custom' ? [{ value: 'custom', label: 'Custom (imported)' }] : []),
|
|
308
|
-
{ value: 'quad', label: 'Quad' },
|
|
309
|
-
{ value: 'cube', label: 'Cube' },
|
|
310
|
-
...hostGeometry.map((g, i) => ({ value: `host:${i}`, label: g.label })),
|
|
311
|
-
...(resolveGeometry ? [{ value: '__file', label: 'Load from file…' }] : []),
|
|
312
|
-
], onChange: (next) => {
|
|
313
|
-
if (next === 'custom') {
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
if (next === '__file') {
|
|
317
|
-
const input = document.createElement('input');
|
|
318
|
-
input.type = 'file';
|
|
319
|
-
input.accept = '.glb,.gltf,model/gltf-binary,model/gltf+json';
|
|
320
|
-
input.onchange = () => {
|
|
321
|
-
const file = input.files?.[0];
|
|
322
|
-
if (file) {
|
|
323
|
-
resolveGeometry(file).then(applyGeometry);
|
|
324
|
-
}
|
|
325
|
-
};
|
|
326
|
-
input.click();
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
if (next.startsWith('host:')) {
|
|
330
|
-
applyGeometry(hostGeometry[Number(next.slice(5))]);
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
binding.apply((s) => {
|
|
334
|
-
const settings = s.getRendererSettings();
|
|
335
|
-
if (next === 'cube') {
|
|
336
|
-
settings.instancingIndices = MESH_CUBE_INDICES;
|
|
337
|
-
settings.instancingUVs = undefined;
|
|
338
|
-
settings.instancingNormals = undefined;
|
|
339
|
-
s.instancingGeometry = MESH_CUBE_POSITIONS;
|
|
340
|
-
}
|
|
341
|
-
else {
|
|
342
|
-
settings.instancingIndices = MESH_QUAD_INDICES;
|
|
343
|
-
settings.instancingUVs = MESH_QUAD_UVS;
|
|
344
|
-
s.instancingGeometry = MESH_QUAD_POSITIONS;
|
|
345
|
-
}
|
|
346
|
-
});
|
|
347
|
-
} }), _jsx(MeshPreview, { positions: system.instancingGeometry, indices: system.getRendererSettings().instancingIndices }), preset === 'custom' && (_jsxs("span", { style: { fontSize: 10.5, color: '#9eb9ff' }, children: [vertexCount, " verts / ", triangleCount, " tris \u2014 picking a preset replaces this mesh"] }))] }) }));
|
|
348
|
-
})(), _jsx("div", { style: {
|
|
324
|
+
}) }) })] })), system.renderMode === RenderMode.StretchedBillBoard && (_jsxs(_Fragment, { children: [_jsx(Row, { label: "Speed factor", children: _jsx(NumberField, { value: system.rendererEmitterSettings.speedFactor ?? 0, step: 0.1, onChange: (v) => binding.apply((s) => (s.rendererEmitterSettings.speedFactor = v)) }) }), _jsx(Row, { label: "Length factor", children: _jsx(NumberField, { value: system.rendererEmitterSettings.lengthFactor ?? 2, step: 0.1, onChange: (v) => binding.apply((s) => (s.rendererEmitterSettings.lengthFactor = v)) }) })] })), _jsxs("div", { style: {
|
|
349
325
|
marginTop: 10,
|
|
350
326
|
marginBottom: 2,
|
|
351
327
|
fontSize: 11,
|
|
@@ -353,15 +329,15 @@ export function RendererModule({ binding, textureOptions, geometryOptions, resol
|
|
|
353
329
|
color: '#7c8db5',
|
|
354
330
|
textTransform: 'uppercase',
|
|
355
331
|
letterSpacing: 0.4,
|
|
356
|
-
|
|
332
|
+
display: 'flex',
|
|
333
|
+
alignItems: 'center',
|
|
334
|
+
gap: 5,
|
|
335
|
+
}, children: ["Material", _jsx(InspectorInfoIcon, { content: "Shader and blending settings applied to the particle sprite or mesh.", size: 12 })] }), materialLabel ? (_jsx(Row, { label: "Source", children: _jsx("div", { style: { fontSize: 12, color: theme.textDim, lineHeight: 1.45 }, children: materialLabel }) })) : null, _jsx(Row, { label: "Blend mode", children: _jsx(SelectField, { value: system.blending, options: [
|
|
357
336
|
{ value: 1, label: 'Additive' },
|
|
358
337
|
{ value: 2, label: 'Alpha blend' },
|
|
359
338
|
{ value: 3, label: 'Subtract' },
|
|
360
339
|
{ value: 4, label: 'Multiply' },
|
|
361
|
-
], onChange: (blend) =>
|
|
362
|
-
s.getRendererSettings().materialTransparent = v;
|
|
363
|
-
s.neededToUpdateRender = true;
|
|
364
|
-
}) }) }), resolveTexture && (_jsxs(Row, { label: "Texture", children: [_jsx(SelectField, { value: !currentTextureUrl
|
|
340
|
+
], onChange: (blend) => patchMaterial({ blendMode: blend }) }) }), _jsx(Row, { label: "Transparent", children: _jsx(CheckboxField, { value: !!system.getRendererSettings().materialTransparent, onChange: (v) => patchMaterial({ transparent: v }) }) }), _jsxs(Row, { label: "Texture", children: [_jsx(SelectField, { value: !currentTextureUrl
|
|
365
341
|
? '__none'
|
|
366
342
|
: (textureOptions ?? []).some((o) => o.url === currentTextureUrl)
|
|
367
343
|
? currentTextureUrl
|
|
@@ -378,7 +354,7 @@ export function RendererModule({ binding, textureOptions, geometryOptions, resol
|
|
|
378
354
|
return;
|
|
379
355
|
}
|
|
380
356
|
if (url === '__none') {
|
|
381
|
-
|
|
357
|
+
patchMaterial({ texture: null });
|
|
382
358
|
return;
|
|
383
359
|
}
|
|
384
360
|
if (url === '__url') {
|
|
@@ -394,20 +370,27 @@ export function RendererModule({ binding, textureOptions, geometryOptions, resol
|
|
|
394
370
|
if (!file) {
|
|
395
371
|
return;
|
|
396
372
|
}
|
|
397
|
-
// Read as a data URI so the texture survives Export/Save/reimport;
|
|
398
|
-
// an object URL (blob:) would be dead after a reload.
|
|
399
373
|
const reader = new FileReader();
|
|
400
374
|
reader.onload = () => {
|
|
401
|
-
|
|
375
|
+
const texture = loadTexture(reader.result);
|
|
376
|
+
if (texture) {
|
|
377
|
+
patchMaterial({ texture });
|
|
378
|
+
}
|
|
402
379
|
};
|
|
403
380
|
reader.readAsDataURL(file);
|
|
404
381
|
};
|
|
405
382
|
input.click();
|
|
406
383
|
return;
|
|
407
384
|
}
|
|
408
|
-
|
|
385
|
+
const texture = loadTexture(url);
|
|
386
|
+
if (texture) {
|
|
387
|
+
patchMaterial({ texture });
|
|
388
|
+
}
|
|
409
389
|
} }), promptingTextureUrl && (_jsx(PromptDialog, { title: "Texture URL", defaultValue: currentTextureUrl, placeholder: "https://\u2026", onSubmit: (url) => {
|
|
410
|
-
|
|
390
|
+
const texture = loadTexture(url);
|
|
391
|
+
if (texture) {
|
|
392
|
+
patchMaterial({ texture });
|
|
393
|
+
}
|
|
411
394
|
setPromptingTextureUrl(false);
|
|
412
395
|
}, onCancel: () => setPromptingTextureUrl(false) })), currentTextureUrl && (_jsx("img", { src: currentTextureUrl, alt: "texture preview", style: {
|
|
413
396
|
width: 64,
|
|
@@ -416,18 +399,63 @@ export function RendererModule({ binding, textureOptions, geometryOptions, resol
|
|
|
416
399
|
objectFit: 'contain',
|
|
417
400
|
borderRadius: 6,
|
|
418
401
|
border: '1px solid #2b3761',
|
|
419
|
-
// Checkerboard so texture transparency is visible.
|
|
420
402
|
background: 'repeating-conic-gradient(#2a3252 0% 25%, #171d33 0% 50%) 0 0 / 12px 12px',
|
|
421
|
-
} }))] })
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
s.getRendererSettings()
|
|
429
|
-
|
|
430
|
-
|
|
403
|
+
} }))] }), _jsx(Row, { label: "Alpha test", children: _jsx(NumberField, { value: system.getRendererSettings().materialAlphaTest ?? 0, min: 0, step: 0.05, onChange: (v) => patchMaterial({ alphaTest: Math.min(1, v) }) }) }), _jsx(Row, { label: "Depth write", children: _jsx(CheckboxField, { value: system.getRendererSettings().materialDepthWrite, onChange: (v) => patchMaterial({ depthWrite: v }) }) }), _jsx(Row, { label: "Depth test", children: _jsx(CheckboxField, { value: system.getRendererSettings().materialDepthTest, onChange: (v) => patchMaterial({ depthTest: v }) }) }), system.renderMode === RenderMode.Mesh &&
|
|
404
|
+
(() => {
|
|
405
|
+
const preset = detectGeometryPreset(system.instancingGeometry);
|
|
406
|
+
const vertexCount = system.instancingGeometry.length / 3;
|
|
407
|
+
const triangleCount = (system.getRendererSettings().instancingIndices?.length ?? 0) / 3;
|
|
408
|
+
const hostGeometry = geometryOptions ?? [];
|
|
409
|
+
const applyGeometry = (g) => binding.apply((s) => {
|
|
410
|
+
const settings = s.getRendererSettings();
|
|
411
|
+
settings.instancingIndices = (g.indices ? new Uint32Array(g.indices) : undefined);
|
|
412
|
+
settings.instancingUVs = (g.uvs ? new Float32Array(g.uvs) : undefined);
|
|
413
|
+
settings.instancingNormals = (g.normals ? new Float32Array(g.normals) : undefined);
|
|
414
|
+
s.instancingGeometry = new Float32Array(g.positions);
|
|
415
|
+
});
|
|
416
|
+
return (_jsx(Row, { label: "Geometry", children: _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 6 }, children: [_jsx(SelectField, { value: preset, options: [
|
|
417
|
+
...(preset === 'custom' ? [{ value: 'custom', label: 'Custom (imported)' }] : []),
|
|
418
|
+
{ value: 'quad', label: 'Quad' },
|
|
419
|
+
{ value: 'cube', label: 'Cube' },
|
|
420
|
+
...hostGeometry.map((g, i) => ({ value: `host:${i}`, label: g.label })),
|
|
421
|
+
...(resolveGeometry ? [{ value: '__file', label: 'Load from file…' }] : []),
|
|
422
|
+
], onChange: (next) => {
|
|
423
|
+
if (next === 'custom') {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
if (next === '__file') {
|
|
427
|
+
const input = document.createElement('input');
|
|
428
|
+
input.type = 'file';
|
|
429
|
+
input.accept = '.glb,.gltf,model/gltf-binary,model/gltf+json';
|
|
430
|
+
input.onchange = () => {
|
|
431
|
+
const file = input.files?.[0];
|
|
432
|
+
if (file) {
|
|
433
|
+
resolveGeometry(file).then(applyGeometry);
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
input.click();
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
if (next.startsWith('host:')) {
|
|
440
|
+
applyGeometry(hostGeometry[Number(next.slice(5))]);
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
binding.apply((s) => {
|
|
444
|
+
const settings = s.getRendererSettings();
|
|
445
|
+
if (next === 'cube') {
|
|
446
|
+
settings.instancingIndices = MESH_CUBE_INDICES;
|
|
447
|
+
settings.instancingUVs = undefined;
|
|
448
|
+
settings.instancingNormals = undefined;
|
|
449
|
+
s.instancingGeometry = MESH_CUBE_POSITIONS;
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
settings.instancingIndices = MESH_QUAD_INDICES;
|
|
453
|
+
settings.instancingUVs = MESH_QUAD_UVS;
|
|
454
|
+
s.instancingGeometry = MESH_QUAD_POSITIONS;
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
} }), _jsx(MeshPreview, { positions: system.instancingGeometry, indices: system.getRendererSettings().instancingIndices }), preset === 'custom' && (_jsxs("span", { style: { fontSize: 10.5, color: '#9eb9ff' }, children: [vertexCount, " verts / ", triangleCount, " tris \u2014 picking a preset replaces this mesh"] }))] }) }));
|
|
458
|
+
})(), _jsx(Row, { label: "Render order", children: _jsx(NumberField, { value: system.renderOrder, step: 1, onChange: (v) => binding.apply((s) => (s.renderOrder = Math.round(v))) }) }), _jsx(Row, { label: "Soft particles", children: _jsx(CheckboxField, { value: system.softParticles, onChange: (v) => binding.apply((s) => (s.softParticles = v)) }) }), system.softParticles && (_jsx(Row, { label: "Fade near/far", children: _jsxs("div", { style: { display: 'flex', gap: 6 }, children: [_jsx(NumberField, { value: system.softNearFade, min: 0, onChange: (v) => binding.apply((s) => (s.softNearFade = v)) }), _jsx(NumberField, { value: system.softFarFade, min: 0, onChange: (v) => binding.apply((s) => (s.softFarFade = v)) })] }) }))] }));
|
|
431
459
|
}
|
|
432
460
|
function removeBehavior(system, type) {
|
|
433
461
|
const behaviors = system.behaviors;
|
package/dist/react/theme.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ export declare const inputStyle: CSSProperties;
|
|
|
15
15
|
export declare const rowStyle: CSSProperties;
|
|
16
16
|
export declare const labelStyle: CSSProperties;
|
|
17
17
|
export declare const buttonStyle: CSSProperties;
|
|
18
|
+
/** Visually hides a file input while keeping it activatable via an associated <label>. */
|
|
19
|
+
export declare const hiddenFileInputStyle: CSSProperties;
|
|
18
20
|
/**
|
|
19
21
|
* Two hover conventions, applied via className (inline `style` can't express `:hover`):
|
|
20
22
|
* - `qe-hover` — brightens elements that already have a solid inline background/border
|
package/dist/react/theme.js
CHANGED
|
@@ -42,10 +42,22 @@ export const buttonStyle = {
|
|
|
42
42
|
color: '#d8e3ff',
|
|
43
43
|
padding: '6px 10px',
|
|
44
44
|
borderRadius: 8,
|
|
45
|
-
fontSize: 12.5,
|
|
46
45
|
cursor: 'pointer',
|
|
46
|
+
fontSize: 12.5,
|
|
47
47
|
fontFamily: theme.font,
|
|
48
48
|
};
|
|
49
|
+
/** Visually hides a file input while keeping it activatable via an associated <label>. */
|
|
50
|
+
export const hiddenFileInputStyle = {
|
|
51
|
+
position: 'absolute',
|
|
52
|
+
width: 1,
|
|
53
|
+
height: 1,
|
|
54
|
+
padding: 0,
|
|
55
|
+
margin: -1,
|
|
56
|
+
overflow: 'hidden',
|
|
57
|
+
clip: 'rect(0, 0, 0, 0)',
|
|
58
|
+
whiteSpace: 'nowrap',
|
|
59
|
+
border: 0,
|
|
60
|
+
};
|
|
49
61
|
/**
|
|
50
62
|
* Two hover conventions, applied via className (inline `style` can't express `:hover`):
|
|
51
63
|
* - `qe-hover` — brightens elements that already have a solid inline background/border
|
|
@@ -98,4 +110,44 @@ select.qe-hover:hover, input.qe-hover:hover, select.qe-hover:focus, input.qe-hov
|
|
|
98
110
|
.qe-root ::-webkit-scrollbar-corner {
|
|
99
111
|
background: transparent;
|
|
100
112
|
}
|
|
113
|
+
.qe-tooltip {
|
|
114
|
+
z-index: 100000;
|
|
115
|
+
padding: 8px 10px;
|
|
116
|
+
font-size: 11.5px;
|
|
117
|
+
line-height: 1.45;
|
|
118
|
+
color: ${theme.text};
|
|
119
|
+
background: linear-gradient(180deg, rgba(18, 26, 52, 0.98), rgba(8, 12, 26, 0.98));
|
|
120
|
+
border: 1px solid ${theme.borderActive};
|
|
121
|
+
border-radius: 8px;
|
|
122
|
+
box-shadow: 0 10px 28px rgba(0, 0, 0, 0.45);
|
|
123
|
+
pointer-events: none;
|
|
124
|
+
font-family: ${theme.font};
|
|
125
|
+
animation: qe-tooltip-in 0.12s ease;
|
|
126
|
+
}
|
|
127
|
+
.qe-tooltip--right::before,
|
|
128
|
+
.qe-tooltip--left::before {
|
|
129
|
+
content: '';
|
|
130
|
+
position: absolute;
|
|
131
|
+
top: 50%;
|
|
132
|
+
width: 0;
|
|
133
|
+
height: 0;
|
|
134
|
+
border: 5px solid transparent;
|
|
135
|
+
transform: translateY(-50%);
|
|
136
|
+
}
|
|
137
|
+
.qe-tooltip--right::before {
|
|
138
|
+
left: -10px;
|
|
139
|
+
border-right-color: ${theme.borderActive};
|
|
140
|
+
}
|
|
141
|
+
.qe-tooltip--left::before {
|
|
142
|
+
right: -10px;
|
|
143
|
+
border-left-color: ${theme.borderActive};
|
|
144
|
+
}
|
|
145
|
+
@keyframes qe-tooltip-in {
|
|
146
|
+
from {
|
|
147
|
+
opacity: 0;
|
|
148
|
+
}
|
|
149
|
+
to {
|
|
150
|
+
opacity: 1;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
101
153
|
`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "babylon.quarks-editor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Shuriken-style effect editor for babylon.quarks: headless edit-model core + React module-stack UI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -52,17 +52,21 @@
|
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"@babylonjs/core": ">=9.0.0",
|
|
54
54
|
"@babylonjs/materials": ">=9.0.0",
|
|
55
|
+
"@heroicons/react": ">=2.0.0",
|
|
56
|
+
"@tanstack/react-virtual": ">=3.0.0",
|
|
55
57
|
"babylon.quarks": ">=0.17.0",
|
|
56
58
|
"react": ">=18",
|
|
57
59
|
"react-dom": ">=18"
|
|
58
60
|
},
|
|
59
61
|
"devDependencies": {
|
|
60
62
|
"@babylonjs/materials": "9.6.2",
|
|
63
|
+
"@heroicons/react": "^2.2.0",
|
|
64
|
+
"@tanstack/react-virtual": "^3.14.6",
|
|
61
65
|
"@types/react": "^19.0.0",
|
|
66
|
+
"@types/react-dom": "^19.0.0",
|
|
62
67
|
"react": "^19.0.0",
|
|
63
|
-
"rimraf": "^6.0.0",
|
|
64
|
-
"typescript": "^5.9.3",
|
|
65
68
|
"react-dom": "^19.0.0",
|
|
66
|
-
"
|
|
69
|
+
"rimraf": "^6.0.0",
|
|
70
|
+
"typescript": "^5.9.3"
|
|
67
71
|
}
|
|
68
72
|
}
|