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
package/src/react/modules.tsx
CHANGED
|
@@ -1,7 +1,20 @@
|
|
|
1
|
-
import {Texture} from '@babylonjs/core/Materials/Textures/texture';
|
|
2
|
-
import {Mesh} from '@babylonjs/core/Meshes/mesh';
|
|
1
|
+
import {Texture} from '@babylonjs/core/Materials/Textures/texture.js';
|
|
2
|
+
import {Mesh} from '@babylonjs/core/Meshes/mesh.js';
|
|
3
3
|
import {PlusIcon, XMarkIcon} from '@heroicons/react/24/solid';
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
Behavior,
|
|
6
|
+
CullingMode,
|
|
7
|
+
GradientMapChannel,
|
|
8
|
+
MeshLighting,
|
|
9
|
+
MeshRim,
|
|
10
|
+
ParticleErosionOptions,
|
|
11
|
+
ParticleSystem,
|
|
12
|
+
ParticleUVDistortionOptions,
|
|
13
|
+
RenderFace,
|
|
14
|
+
ScalingMode,
|
|
15
|
+
SortMode,
|
|
16
|
+
TrailSettings,
|
|
17
|
+
} from 'babylon.quarks';
|
|
5
18
|
import {
|
|
6
19
|
AxisAngleGenerator,
|
|
7
20
|
BoxEmitFrom,
|
|
@@ -17,12 +30,13 @@ import {
|
|
|
17
30
|
RandomQuatGenerator,
|
|
18
31
|
RenderMode,
|
|
19
32
|
SizeOverLife,
|
|
33
|
+
Vector2,
|
|
20
34
|
Vector3,
|
|
21
35
|
Vector3Function,
|
|
22
36
|
Vector4,
|
|
37
|
+
normalizeTextureUV,
|
|
23
38
|
} from 'babylon.quarks';
|
|
24
|
-
import React, {useMemo
|
|
25
|
-
import {EffectBinding} from '../core/binding';
|
|
39
|
+
import React, {useMemo} from 'react';
|
|
26
40
|
import {
|
|
27
41
|
DEFAULT_GRADIENT_STOPS,
|
|
28
42
|
GradientStop,
|
|
@@ -31,9 +45,9 @@ import {
|
|
|
31
45
|
hexToRgb,
|
|
32
46
|
readGradientStops,
|
|
33
47
|
rgbToHex,
|
|
34
|
-
} from '../core/colors';
|
|
35
|
-
import {applyRendererMaterial, getMaterialLabel} from '../core/material';
|
|
36
|
-
import type {ShapeType} from '../core/shapes';
|
|
48
|
+
} from '../core/colors.js';
|
|
49
|
+
import {applyRendererMaterial, getMaterialLabel} from '../core/material.js';
|
|
50
|
+
import type {ShapeType} from '../core/shapes.js';
|
|
37
51
|
import {
|
|
38
52
|
DEFAULT_SHAPE_PARAMS,
|
|
39
53
|
SHAPE_PARAM_KEYS,
|
|
@@ -41,21 +55,18 @@ import {
|
|
|
41
55
|
createShape,
|
|
42
56
|
getShapeType,
|
|
43
57
|
readShapeParams,
|
|
44
|
-
} from '../core/shapes';
|
|
45
|
-
import {buildCurve, buildScalar, readPieces, readScalar} from '../core/values';
|
|
46
|
-
import {CurveEditor} from './CurveEditor';
|
|
47
|
-
import {GradientEditor} from './GradientEditor';
|
|
48
|
-
import {InspectorInfoIcon} from './InspectorInfoIcon';
|
|
49
|
-
import {ModuleSection} from './ModuleSection';
|
|
50
|
-
import {
|
|
51
|
-
import {
|
|
52
|
-
import {
|
|
53
|
-
import {
|
|
54
|
-
import {
|
|
55
|
-
|
|
56
|
-
interface ModuleProps {
|
|
57
|
-
binding: EffectBinding;
|
|
58
|
-
}
|
|
58
|
+
} from '../core/shapes.js';
|
|
59
|
+
import {buildCurve, buildScalar, readPieces, readScalar} from '../core/values.js';
|
|
60
|
+
import {CurveEditor} from './CurveEditor.js';
|
|
61
|
+
import {GradientEditor} from './GradientEditor.js';
|
|
62
|
+
import {InspectorInfoIcon} from './InspectorInfoIcon.js';
|
|
63
|
+
import {ModuleSection, type ModuleProps} from './ModuleSection.js';
|
|
64
|
+
import {TextureSelect} from './TextureSelect.js';
|
|
65
|
+
import {TextureUVField} from './TextureUVField.js';
|
|
66
|
+
import {ValueField} from './ValueField.js';
|
|
67
|
+
import {CheckboxField, NumberField, Row, SelectField} from './fields.js';
|
|
68
|
+
import {iconStyle} from './icons.js';
|
|
69
|
+
import {theme} from './theme.js';
|
|
59
70
|
|
|
60
71
|
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]);
|
|
61
72
|
const MESH_QUAD_INDICES = new Uint32Array([0, 1, 2, 0, 2, 3]);
|
|
@@ -105,6 +116,49 @@ const PARAM_LABELS: {[key: string]: string} = {
|
|
|
105
116
|
speed: 'Speed (lengths/s)',
|
|
106
117
|
};
|
|
107
118
|
|
|
119
|
+
/** Uppercase group title inside a module, as the Renderer's "Material" heading. */
|
|
120
|
+
function SubHeader(props: {title: string; hint: string}) {
|
|
121
|
+
return (
|
|
122
|
+
<div
|
|
123
|
+
style={{
|
|
124
|
+
marginTop: 10,
|
|
125
|
+
marginBottom: 2,
|
|
126
|
+
fontSize: 11,
|
|
127
|
+
fontWeight: 600,
|
|
128
|
+
color: '#7c8db5',
|
|
129
|
+
textTransform: 'uppercase',
|
|
130
|
+
letterSpacing: 0.4,
|
|
131
|
+
display: 'flex',
|
|
132
|
+
alignItems: 'center',
|
|
133
|
+
gap: 5,
|
|
134
|
+
}}
|
|
135
|
+
>
|
|
136
|
+
{props.title}
|
|
137
|
+
<InspectorInfoIcon content={props.hint} size={12} />
|
|
138
|
+
</div>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Three number inputs editing a Vector3 by value. */
|
|
143
|
+
function AxisField(props: {value: Vector3; onChange: (next: Vector3) => void}) {
|
|
144
|
+
return (
|
|
145
|
+
<div style={{display: 'flex', gap: 6}}>
|
|
146
|
+
{(['x', 'y', 'z'] as const).map((axis) => (
|
|
147
|
+
<NumberField
|
|
148
|
+
key={axis}
|
|
149
|
+
value={props.value[axis]}
|
|
150
|
+
step={0.1}
|
|
151
|
+
onChange={(v) => {
|
|
152
|
+
const next = props.value.clone();
|
|
153
|
+
next[axis] = v;
|
|
154
|
+
props.onChange(next);
|
|
155
|
+
}}
|
|
156
|
+
/>
|
|
157
|
+
))}
|
|
158
|
+
</div>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
108
162
|
function ColorInput(props: {value: Vector4; onChange: (next: Vector4) => void}) {
|
|
109
163
|
const {value, onChange} = props;
|
|
110
164
|
return (
|
|
@@ -136,18 +190,20 @@ function ColorInput(props: {value: Vector4; onChange: (next: Vector4) => void})
|
|
|
136
190
|
);
|
|
137
191
|
}
|
|
138
192
|
|
|
193
|
+
/** The Main module (Unity's main module): duration, looping, start delay, life, speed, size, rotation and colour. */
|
|
139
194
|
export function MainModule({binding}: ModuleProps) {
|
|
140
195
|
const system = binding.system;
|
|
141
196
|
const startColor = system.startColor;
|
|
142
197
|
const colorMode =
|
|
143
198
|
startColor instanceof RandomColor ? 'random' : startColor instanceof ColorRange ? 'range' : 'constant';
|
|
144
|
-
const
|
|
145
|
-
const colorA =
|
|
146
|
-
?
|
|
199
|
+
const twoColor = startColor instanceof RandomColor || startColor instanceof ColorRange ? startColor : null;
|
|
200
|
+
const colorA = twoColor
|
|
201
|
+
? twoColor.a
|
|
147
202
|
: startColor instanceof ConstantColor
|
|
148
203
|
? startColor.color
|
|
149
204
|
: new Vector4(1, 1, 1, 1);
|
|
150
|
-
const colorB =
|
|
205
|
+
const colorB = twoColor ? twoColor.b : new Vector4(1, 0.5, 0.2, 1);
|
|
206
|
+
const twoColors = twoColor !== null;
|
|
151
207
|
/** The two-colour generator of the current mode. */
|
|
152
208
|
const makeTwoColors = (a: Vector4, b: Vector4) =>
|
|
153
209
|
colorMode === 'range' ? new ColorRange(a, b) : new RandomColor(a, b);
|
|
@@ -161,17 +217,17 @@ export function MainModule({binding}: ModuleProps) {
|
|
|
161
217
|
? 'euler'
|
|
162
218
|
: isAxisAngle
|
|
163
219
|
? 'axisAngle'
|
|
164
|
-
: readScalar(rotation
|
|
220
|
+
: readScalar(rotation).mode === 'random'
|
|
165
221
|
? 'random'
|
|
166
222
|
: 'angle';
|
|
167
223
|
const rotationState = readScalar(
|
|
168
|
-
rotation instanceof RandomQuatGenerator || isEuler || isAxisAngle ? undefined :
|
|
224
|
+
rotation instanceof RandomQuatGenerator || isEuler || isAxisAngle ? undefined : rotation
|
|
169
225
|
);
|
|
170
226
|
const axisAngle = isAxisAngle ? (rotation as AxisAngleGenerator) : undefined;
|
|
171
227
|
const eulerAngles = {
|
|
172
|
-
x: isEuler ? readScalar((rotation as EulerGenerator).angleX
|
|
173
|
-
y: isEuler ? readScalar((rotation as EulerGenerator).angleY
|
|
174
|
-
z: isEuler ? readScalar((rotation as EulerGenerator).angleZ
|
|
228
|
+
x: isEuler ? readScalar((rotation as EulerGenerator).angleX).value : 0,
|
|
229
|
+
y: isEuler ? readScalar((rotation as EulerGenerator).angleY).value : 0,
|
|
230
|
+
z: isEuler ? readScalar((rotation as EulerGenerator).angleZ).value : 0,
|
|
175
231
|
};
|
|
176
232
|
const setEulerAxis = (axis: 'x' | 'y' | 'z', v: number) =>
|
|
177
233
|
binding.apply((s) => {
|
|
@@ -182,8 +238,7 @@ export function MainModule({binding}: ModuleProps) {
|
|
|
182
238
|
axis === 'z' ? new ConstantValue(v) : cur.angleZ
|
|
183
239
|
);
|
|
184
240
|
});
|
|
185
|
-
const
|
|
186
|
-
const size3D = startSize.type === 'vec3function';
|
|
241
|
+
const startSize3D = system.startSize instanceof Vector3Function ? system.startSize : null;
|
|
187
242
|
const colorGenMode =
|
|
188
243
|
startColor instanceof RandomColorBetweenGradient
|
|
189
244
|
? 'gradient2'
|
|
@@ -205,9 +260,31 @@ export function MainModule({binding}: ModuleProps) {
|
|
|
205
260
|
<Row label="World space">
|
|
206
261
|
<CheckboxField value={system.worldSpace} onChange={(v) => binding.apply((s) => (s.worldSpace = v))} />
|
|
207
262
|
</Row>
|
|
263
|
+
{system.worldSpace && (
|
|
264
|
+
<Row label="Scaling mode">
|
|
265
|
+
<SelectField<ScalingMode>
|
|
266
|
+
value={system.scalingMode}
|
|
267
|
+
options={[
|
|
268
|
+
{value: 'shape', label: 'Shape (sizes and shape only)'},
|
|
269
|
+
{value: 'hierarchy', label: 'Hierarchy (motion scales too)'},
|
|
270
|
+
]}
|
|
271
|
+
onChange={(mode) => binding.apply((s) => (s.scalingMode = mode))}
|
|
272
|
+
/>
|
|
273
|
+
</Row>
|
|
274
|
+
)}
|
|
208
275
|
<Row label="Prewarm">
|
|
209
276
|
<CheckboxField value={system.prewarm} onChange={(v) => binding.apply((s) => (s.prewarm = v))} />
|
|
210
277
|
</Row>
|
|
278
|
+
{system.prewarm && (
|
|
279
|
+
<Row label="Warm-up time">
|
|
280
|
+
<NumberField
|
|
281
|
+
value={system.prewarmTime}
|
|
282
|
+
step={0.1}
|
|
283
|
+
min={0}
|
|
284
|
+
onChange={(v) => binding.apply((s) => (s.prewarmTime = Math.max(0, v)))}
|
|
285
|
+
/>
|
|
286
|
+
</Row>
|
|
287
|
+
)}
|
|
211
288
|
<Row label="Playback speed">
|
|
212
289
|
<NumberField
|
|
213
290
|
value={system.playbackSpeed}
|
|
@@ -269,50 +346,50 @@ export function MainModule({binding}: ModuleProps) {
|
|
|
269
346
|
/>
|
|
270
347
|
<Row label="Start size">
|
|
271
348
|
<SelectField
|
|
272
|
-
value={
|
|
349
|
+
value={startSize3D ? '3d' : 'uniform'}
|
|
273
350
|
options={[
|
|
274
351
|
{value: 'uniform', label: 'Uniform'},
|
|
275
352
|
{value: '3d', label: '3D (per axis)'},
|
|
276
353
|
]}
|
|
277
354
|
onChange={(mode) =>
|
|
278
355
|
binding.apply((s) => {
|
|
279
|
-
const cur = s.startSize
|
|
280
|
-
if (mode === '3d') {
|
|
356
|
+
const cur = s.startSize;
|
|
357
|
+
if (mode === '3d' && cur.type !== 'vec3function') {
|
|
281
358
|
// Seed all three axes from the current uniform generator.
|
|
282
359
|
s.startSize = new Vector3Function(cur.clone(), cur.clone(), cur.clone());
|
|
283
|
-
} else {
|
|
360
|
+
} else if (mode !== '3d' && cur instanceof Vector3Function) {
|
|
284
361
|
// Collapse back to the X-axis generator.
|
|
285
|
-
s.startSize =
|
|
362
|
+
s.startSize = cur.x;
|
|
286
363
|
}
|
|
287
364
|
})
|
|
288
365
|
}
|
|
289
366
|
/>
|
|
290
367
|
</Row>
|
|
291
|
-
{!
|
|
368
|
+
{!startSize3D && (
|
|
292
369
|
<ValueField
|
|
293
370
|
label=""
|
|
294
|
-
generator={system.startSize
|
|
371
|
+
generator={system.startSize}
|
|
295
372
|
min={0}
|
|
296
373
|
curveMax={5}
|
|
297
374
|
onChange={(g) => binding.apply((s) => (s.startSize = g))}
|
|
298
375
|
/>
|
|
299
376
|
)}
|
|
300
|
-
{
|
|
377
|
+
{startSize3D && (
|
|
301
378
|
<>
|
|
302
379
|
{(['x', 'y', 'z'] as const).map((axis) => (
|
|
303
380
|
<ValueField
|
|
304
381
|
key={axis}
|
|
305
382
|
label={axis.toUpperCase()}
|
|
306
|
-
generator={
|
|
383
|
+
generator={startSize3D[axis]}
|
|
307
384
|
min={0}
|
|
308
385
|
curveMax={5}
|
|
309
386
|
onChange={(g) =>
|
|
310
387
|
binding.apply((s) => {
|
|
311
388
|
const cur = s.startSize as Vector3Function;
|
|
312
389
|
s.startSize = new Vector3Function(
|
|
313
|
-
axis === 'x' ?
|
|
314
|
-
axis === 'y' ?
|
|
315
|
-
axis === 'z' ?
|
|
390
|
+
axis === 'x' ? g : cur.x,
|
|
391
|
+
axis === 'y' ? g : cur.y,
|
|
392
|
+
axis === 'z' ? g : cur.z
|
|
316
393
|
);
|
|
317
394
|
})
|
|
318
395
|
}
|
|
@@ -505,6 +582,7 @@ export function MainModule({binding}: ModuleProps) {
|
|
|
505
582
|
);
|
|
506
583
|
}
|
|
507
584
|
|
|
585
|
+
/** The Emission module (Unity's Emission): rate over time and distance, and bursts. */
|
|
508
586
|
export function EmissionModule({binding}: ModuleProps) {
|
|
509
587
|
const system = binding.system;
|
|
510
588
|
const bursts = system.emissionBursts ?? [];
|
|
@@ -554,7 +632,7 @@ export function EmissionModule({binding}: ModuleProps) {
|
|
|
554
632
|
</Row>
|
|
555
633
|
<Row label="Count">
|
|
556
634
|
<NumberField
|
|
557
|
-
value={readScalar(burst.count
|
|
635
|
+
value={readScalar(burst.count).value}
|
|
558
636
|
min={0}
|
|
559
637
|
step={1}
|
|
560
638
|
onChange={(count) =>
|
|
@@ -627,6 +705,7 @@ export function EmissionModule({binding}: ModuleProps) {
|
|
|
627
705
|
// first texture" bug: a control that looks live but does nothing.
|
|
628
706
|
const SHAPES_WITHOUT_MODE_SPREAD = new Set<ShapeType>(['point', 'grid', 'box', 'mesh_surface']);
|
|
629
707
|
|
|
708
|
+
/** The Shape module (Unity's Shape): the emitter shape and its settings. */
|
|
630
709
|
export function ShapeModule({binding}: ModuleProps) {
|
|
631
710
|
const system = binding.system;
|
|
632
711
|
const type = getShapeType(system.emitterShape);
|
|
@@ -726,12 +805,11 @@ export function ShapeModule({binding}: ModuleProps) {
|
|
|
726
805
|
);
|
|
727
806
|
}
|
|
728
807
|
|
|
808
|
+
/** The Size over Lifetime module (Unity's Size over Lifetime): size by a curve over the particle's life. */
|
|
729
809
|
export function SizeOverLifeModule({binding}: ModuleProps) {
|
|
730
810
|
const system = binding.system;
|
|
731
811
|
const behavior = findBehavior<SizeOverLife>(system.behaviors, 'SizeOverLife');
|
|
732
|
-
const curve = behavior
|
|
733
|
-
? readScalar(behavior.size as never).curve
|
|
734
|
-
: ([0.2, 1, 1, 0.4] as [number, number, number, number]);
|
|
812
|
+
const curve = behavior ? readScalar(behavior.size).curve : ([0.2, 1, 1, 0.4] as [number, number, number, number]);
|
|
735
813
|
return (
|
|
736
814
|
<ModuleSection
|
|
737
815
|
title="Size over Lifetime"
|
|
@@ -740,21 +818,19 @@ export function SizeOverLifeModule({binding}: ModuleProps) {
|
|
|
740
818
|
binding.apply((s) => {
|
|
741
819
|
removeBehavior(s, 'SizeOverLife');
|
|
742
820
|
if (enabled) {
|
|
743
|
-
s.addBehavior(
|
|
744
|
-
new SizeOverLife(buildScalar({mode: 'curve', value: 1, min: 0, max: 1, curve}) as never)
|
|
745
|
-
);
|
|
821
|
+
s.addBehavior(new SizeOverLife(buildScalar({mode: 'curve', value: 1, min: 0, max: 1, curve})));
|
|
746
822
|
}
|
|
747
823
|
})
|
|
748
824
|
}
|
|
749
825
|
>
|
|
750
826
|
<div style={{marginTop: 6}}>
|
|
751
827
|
<CurveEditor
|
|
752
|
-
pieces={behavior ? readPieces(behavior.size
|
|
828
|
+
pieces={behavior ? readPieces(behavior.size) : [{start: 0, p: curve}]}
|
|
753
829
|
maxValue={2}
|
|
754
830
|
onChange={(pieces) =>
|
|
755
831
|
binding.apply((s) => {
|
|
756
832
|
removeBehavior(s, 'SizeOverLife');
|
|
757
|
-
s.addBehavior(new SizeOverLife(buildCurve(pieces)
|
|
833
|
+
s.addBehavior(new SizeOverLife(buildCurve(pieces)));
|
|
758
834
|
})
|
|
759
835
|
}
|
|
760
836
|
/>
|
|
@@ -763,6 +839,7 @@ export function SizeOverLifeModule({binding}: ModuleProps) {
|
|
|
763
839
|
);
|
|
764
840
|
}
|
|
765
841
|
|
|
842
|
+
/** The Color over Lifetime module (Unity's Color over Lifetime): a gradient over the particle's life. */
|
|
766
843
|
export function ColorOverLifeModule({binding}: ModuleProps) {
|
|
767
844
|
const system = binding.system;
|
|
768
845
|
const behavior = findBehavior<ColorOverLife>(system.behaviors, 'ColorOverLife');
|
|
@@ -793,8 +870,11 @@ export function ColorOverLifeModule({binding}: ModuleProps) {
|
|
|
793
870
|
);
|
|
794
871
|
}
|
|
795
872
|
|
|
873
|
+
/** A texture preset the host offers in the Renderer module's texture menus. */
|
|
796
874
|
export interface TextureOption {
|
|
875
|
+
/** Its label in the menu. */
|
|
797
876
|
label: string;
|
|
877
|
+
/** The URL the host's texture loader receives. */
|
|
798
878
|
url: string;
|
|
799
879
|
}
|
|
800
880
|
|
|
@@ -804,10 +884,17 @@ export interface TextureOption {
|
|
|
804
884
|
* them into the system's instancing geometry.
|
|
805
885
|
*/
|
|
806
886
|
export interface GeometryOption {
|
|
887
|
+
/** Its label in the menu. */
|
|
807
888
|
label: string;
|
|
889
|
+
/** Vertex positions, three values per vertex. */
|
|
808
890
|
positions: Float32Array | number[];
|
|
891
|
+
/** Triangle indices; without them the vertices are drawn in sequence. */
|
|
809
892
|
indices?: Uint32Array | number[];
|
|
893
|
+
/** The first UV set, two values per vertex; without it the mesh samples UV (0, 0). */
|
|
810
894
|
uvs?: Float32Array | number[];
|
|
895
|
+
/** A second UV set (glTF `TEXCOORD_1`), what textures on UV channel 1 read on the mesh. */
|
|
896
|
+
uvs2?: Float32Array | number[];
|
|
897
|
+
/** Vertex normals, three values per vertex; without them lighting uses +Z. */
|
|
811
898
|
normals?: Float32Array | number[];
|
|
812
899
|
}
|
|
813
900
|
|
|
@@ -900,6 +987,7 @@ function MeshPreview({
|
|
|
900
987
|
);
|
|
901
988
|
}
|
|
902
989
|
|
|
990
|
+
/** The Renderer module (Unity's Renderer): render mode, material, textures, sorting and mesh settings. */
|
|
903
991
|
export function RendererModule({
|
|
904
992
|
binding,
|
|
905
993
|
textureOptions,
|
|
@@ -909,13 +997,19 @@ export function RendererModule({
|
|
|
909
997
|
}: ModuleProps & {
|
|
910
998
|
textureOptions?: TextureOption[];
|
|
911
999
|
geometryOptions?: GeometryOption[];
|
|
912
|
-
resolveTexture?: (url: string) =>
|
|
1000
|
+
resolveTexture?: (url: string) => Texture | null | undefined;
|
|
913
1001
|
resolveGeometry?: (file: File) => Promise<GeometryData>;
|
|
914
1002
|
}) {
|
|
915
1003
|
const system = binding.system;
|
|
916
|
-
const currentTextureUrl = (system.texture as {url?: string} | null)?.url ?? '';
|
|
917
|
-
const [promptingTextureUrl, setPromptingTextureUrl] = useState(false);
|
|
918
1004
|
const materialLabel = getMaterialLabel(system.material);
|
|
1005
|
+
const isMesh = system.renderMode === RenderMode.Mesh;
|
|
1006
|
+
const gradientMap = system.gradientMap;
|
|
1007
|
+
const erosion = system.erosion;
|
|
1008
|
+
const distortion = system.uvDistortion;
|
|
1009
|
+
const patchErosion = (patch: ParticleErosionOptions) =>
|
|
1010
|
+
binding.apply((s) => (s.erosion = {...s.erosion, ...patch}));
|
|
1011
|
+
const patchDistortion = (patch: ParticleUVDistortionOptions) =>
|
|
1012
|
+
binding.apply((s) => (s.uvDistortion = {...s.uvDistortion, ...patch}));
|
|
919
1013
|
const loadTexture = useMemo(() => {
|
|
920
1014
|
return (url: string) => {
|
|
921
1015
|
if (resolveTexture) {
|
|
@@ -993,18 +1087,112 @@ export function RendererModule({
|
|
|
993
1087
|
/>
|
|
994
1088
|
</Row>
|
|
995
1089
|
))}
|
|
996
|
-
<Row label="
|
|
1090
|
+
<Row label="Tessellation">
|
|
1091
|
+
<NumberField
|
|
1092
|
+
value={(system.rendererEmitterSettings as {tessellation?: number}).tessellation ?? 0}
|
|
1093
|
+
min={0}
|
|
1094
|
+
max={16}
|
|
1095
|
+
step={1}
|
|
1096
|
+
onChange={(v) =>
|
|
1097
|
+
binding.apply((s) => {
|
|
1098
|
+
(s.rendererEmitterSettings as {tessellation?: number}).tessellation = Math.round(v);
|
|
1099
|
+
})
|
|
1100
|
+
}
|
|
1101
|
+
/>
|
|
1102
|
+
</Row>
|
|
1103
|
+
<Row label="Curve tension">
|
|
1104
|
+
<NumberField
|
|
1105
|
+
value={(system.rendererEmitterSettings as {curveTension?: number}).curveTension ?? 0}
|
|
1106
|
+
min={0}
|
|
1107
|
+
max={1}
|
|
1108
|
+
step={0.05}
|
|
1109
|
+
onChange={(v) =>
|
|
1110
|
+
binding.apply((s) => {
|
|
1111
|
+
(s.rendererEmitterSettings as {curveTension?: number}).curveTension = v;
|
|
1112
|
+
})
|
|
1113
|
+
}
|
|
1114
|
+
/>
|
|
1115
|
+
</Row>
|
|
1116
|
+
<Row label="Texture mode">
|
|
1117
|
+
<SelectField
|
|
1118
|
+
value={(system.rendererEmitterSettings as TrailSettings).textureMode ?? 'stretch'}
|
|
1119
|
+
options={[
|
|
1120
|
+
{value: 'stretch', label: 'Stretch (once, by distance)'},
|
|
1121
|
+
{value: 'distributePerSegment', label: 'Distribute per segment'},
|
|
1122
|
+
{value: 'tile', label: 'Tile (per world unit)'},
|
|
1123
|
+
{value: 'repeatPerSegment', label: 'Repeat per segment'},
|
|
1124
|
+
]}
|
|
1125
|
+
onChange={(mode) =>
|
|
1126
|
+
binding.apply((s) => {
|
|
1127
|
+
(s.rendererEmitterSettings as TrailSettings).textureMode =
|
|
1128
|
+
mode === 'stretch' ? undefined : mode;
|
|
1129
|
+
})
|
|
1130
|
+
}
|
|
1131
|
+
/>
|
|
1132
|
+
</Row>
|
|
1133
|
+
<Row label="Texture scale">
|
|
1134
|
+
<NumberField
|
|
1135
|
+
value={(system.rendererEmitterSettings as TrailSettings).textureScale ?? 1}
|
|
1136
|
+
min={0.01}
|
|
1137
|
+
step={0.1}
|
|
1138
|
+
onChange={(v) =>
|
|
1139
|
+
binding.apply((s) => {
|
|
1140
|
+
(s.rendererEmitterSettings as TrailSettings).textureScale = v === 1 ? undefined : v;
|
|
1141
|
+
})
|
|
1142
|
+
}
|
|
1143
|
+
/>
|
|
1144
|
+
</Row>
|
|
1145
|
+
<Row label="Min vertex distance">
|
|
997
1146
|
<NumberField
|
|
998
|
-
value={(system.rendererEmitterSettings as
|
|
1147
|
+
value={(system.rendererEmitterSettings as TrailSettings).minVertexDistance ?? 0}
|
|
1148
|
+
min={0}
|
|
1149
|
+
step={0.05}
|
|
1150
|
+
onChange={(v) =>
|
|
1151
|
+
binding.apply((s) => {
|
|
1152
|
+
(s.rendererEmitterSettings as TrailSettings).minVertexDistance =
|
|
1153
|
+
v > 0 ? v : undefined;
|
|
1154
|
+
})
|
|
1155
|
+
}
|
|
1156
|
+
/>
|
|
1157
|
+
</Row>
|
|
1158
|
+
<Row label="Trail time">
|
|
1159
|
+
<NumberField
|
|
1160
|
+
value={(system.rendererEmitterSettings as TrailSettings).lifetime ?? 0}
|
|
999
1161
|
min={0}
|
|
1000
1162
|
step={0.1}
|
|
1001
1163
|
onChange={(v) =>
|
|
1002
1164
|
binding.apply((s) => {
|
|
1003
|
-
(s.rendererEmitterSettings as
|
|
1165
|
+
(s.rendererEmitterSettings as TrailSettings).lifetime = v > 0 ? v : undefined;
|
|
1166
|
+
})
|
|
1167
|
+
}
|
|
1168
|
+
/>
|
|
1169
|
+
</Row>
|
|
1170
|
+
<Row label="Swept trail">
|
|
1171
|
+
<CheckboxField
|
|
1172
|
+
value={!!(system.rendererEmitterSettings as TrailSettings).sideAxis}
|
|
1173
|
+
onChange={(v) =>
|
|
1174
|
+
binding.apply((s) => {
|
|
1175
|
+
(s.rendererEmitterSettings as TrailSettings).sideAxis = v
|
|
1176
|
+
? new Vector3(0, 1, 0)
|
|
1177
|
+
: undefined;
|
|
1004
1178
|
})
|
|
1005
1179
|
}
|
|
1006
1180
|
/>
|
|
1007
1181
|
</Row>
|
|
1182
|
+
{(system.rendererEmitterSettings as TrailSettings).sideAxis && (
|
|
1183
|
+
<Row label="Side axis">
|
|
1184
|
+
<AxisField
|
|
1185
|
+
value={(system.rendererEmitterSettings as TrailSettings).sideAxis!}
|
|
1186
|
+
onChange={(axis) =>
|
|
1187
|
+
binding.apply((s) => {
|
|
1188
|
+
// The axis's length scales the width: keep it a unit vector.
|
|
1189
|
+
(s.rendererEmitterSettings as TrailSettings).sideAxis =
|
|
1190
|
+
axis.length() > 0 ? axis.normalize() : axis;
|
|
1191
|
+
})
|
|
1192
|
+
}
|
|
1193
|
+
/>
|
|
1194
|
+
</Row>
|
|
1195
|
+
)}
|
|
1008
1196
|
</>
|
|
1009
1197
|
)}
|
|
1010
1198
|
{system.renderMode === RenderMode.StretchedBillBoard && (
|
|
@@ -1033,26 +1221,7 @@ export function RendererModule({
|
|
|
1033
1221
|
</Row>
|
|
1034
1222
|
</>
|
|
1035
1223
|
)}
|
|
1036
|
-
<
|
|
1037
|
-
style={{
|
|
1038
|
-
marginTop: 10,
|
|
1039
|
-
marginBottom: 2,
|
|
1040
|
-
fontSize: 11,
|
|
1041
|
-
fontWeight: 600,
|
|
1042
|
-
color: '#7c8db5',
|
|
1043
|
-
textTransform: 'uppercase',
|
|
1044
|
-
letterSpacing: 0.4,
|
|
1045
|
-
display: 'flex',
|
|
1046
|
-
alignItems: 'center',
|
|
1047
|
-
gap: 5,
|
|
1048
|
-
}}
|
|
1049
|
-
>
|
|
1050
|
-
Material
|
|
1051
|
-
<InspectorInfoIcon
|
|
1052
|
-
content="Shader and blending settings applied to the particle sprite or mesh."
|
|
1053
|
-
size={12}
|
|
1054
|
-
/>
|
|
1055
|
-
</div>
|
|
1224
|
+
<SubHeader title="Material" hint="Shader and blending settings applied to the particle sprite or mesh." />
|
|
1056
1225
|
{materialLabel ? (
|
|
1057
1226
|
<Row label="Source">
|
|
1058
1227
|
<div style={{fontSize: 12, color: theme.textDim, lineHeight: 1.45}}>{materialLabel}</div>
|
|
@@ -1077,93 +1246,41 @@ export function RendererModule({
|
|
|
1077
1246
|
/>
|
|
1078
1247
|
</Row>
|
|
1079
1248
|
<Row label="Texture">
|
|
1080
|
-
<
|
|
1081
|
-
value={
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
: '__current'
|
|
1087
|
-
}
|
|
1088
|
-
options={[
|
|
1089
|
-
{value: '__none', label: '(none)'},
|
|
1090
|
-
...(currentTextureUrl && !(textureOptions ?? []).some((o) => o.url === currentTextureUrl)
|
|
1091
|
-
? [{value: '__current', label: `(current) ${currentTextureUrl.slice(-24)}`}]
|
|
1092
|
-
: []),
|
|
1093
|
-
...(textureOptions ?? []).map((o) => ({value: o.url, label: o.label})),
|
|
1094
|
-
{value: '__url', label: 'Custom URL…'},
|
|
1095
|
-
{value: '__file', label: 'Load from file…'},
|
|
1096
|
-
]}
|
|
1097
|
-
onChange={(url) => {
|
|
1098
|
-
if (url === '__current') {
|
|
1099
|
-
return;
|
|
1100
|
-
}
|
|
1101
|
-
if (url === '__none') {
|
|
1102
|
-
patchMaterial({texture: null});
|
|
1103
|
-
return;
|
|
1104
|
-
}
|
|
1105
|
-
if (url === '__url') {
|
|
1106
|
-
setPromptingTextureUrl(true);
|
|
1107
|
-
return;
|
|
1108
|
-
}
|
|
1109
|
-
if (url === '__file') {
|
|
1110
|
-
const input = document.createElement('input');
|
|
1111
|
-
input.type = 'file';
|
|
1112
|
-
input.accept = 'image/*';
|
|
1113
|
-
input.onchange = () => {
|
|
1114
|
-
const file = input.files?.[0];
|
|
1115
|
-
if (!file) {
|
|
1116
|
-
return;
|
|
1117
|
-
}
|
|
1118
|
-
const reader = new FileReader();
|
|
1119
|
-
reader.onload = () => {
|
|
1120
|
-
const texture = loadTexture(reader.result as string);
|
|
1121
|
-
if (texture) {
|
|
1122
|
-
patchMaterial({texture});
|
|
1123
|
-
}
|
|
1124
|
-
};
|
|
1125
|
-
reader.readAsDataURL(file);
|
|
1126
|
-
};
|
|
1127
|
-
input.click();
|
|
1128
|
-
return;
|
|
1129
|
-
}
|
|
1130
|
-
const texture = loadTexture(url);
|
|
1131
|
-
if (texture) {
|
|
1132
|
-
patchMaterial({texture});
|
|
1133
|
-
}
|
|
1134
|
-
}}
|
|
1249
|
+
<TextureSelect
|
|
1250
|
+
value={system.texture}
|
|
1251
|
+
textureOptions={textureOptions}
|
|
1252
|
+
loadTexture={loadTexture}
|
|
1253
|
+
onChange={(texture) => patchMaterial({texture})}
|
|
1254
|
+
preview
|
|
1135
1255
|
/>
|
|
1136
|
-
{promptingTextureUrl && (
|
|
1137
|
-
<PromptDialog
|
|
1138
|
-
title="Texture URL"
|
|
1139
|
-
defaultValue={currentTextureUrl}
|
|
1140
|
-
placeholder="https://…"
|
|
1141
|
-
onSubmit={(url) => {
|
|
1142
|
-
const texture = loadTexture(url);
|
|
1143
|
-
if (texture) {
|
|
1144
|
-
patchMaterial({texture});
|
|
1145
|
-
}
|
|
1146
|
-
setPromptingTextureUrl(false);
|
|
1147
|
-
}}
|
|
1148
|
-
onCancel={() => setPromptingTextureUrl(false)}
|
|
1149
|
-
/>
|
|
1150
|
-
)}
|
|
1151
|
-
{currentTextureUrl && (
|
|
1152
|
-
<img
|
|
1153
|
-
src={currentTextureUrl}
|
|
1154
|
-
alt="texture preview"
|
|
1155
|
-
style={{
|
|
1156
|
-
width: 64,
|
|
1157
|
-
height: 64,
|
|
1158
|
-
marginTop: 6,
|
|
1159
|
-
objectFit: 'contain',
|
|
1160
|
-
borderRadius: 6,
|
|
1161
|
-
border: '1px solid #2b3761',
|
|
1162
|
-
background: 'repeating-conic-gradient(#2a3252 0% 25%, #171d33 0% 50%) 0 0 / 12px 12px',
|
|
1163
|
-
}}
|
|
1164
|
-
/>
|
|
1165
|
-
)}
|
|
1166
1256
|
</Row>
|
|
1257
|
+
<TextureUVField
|
|
1258
|
+
labelPrefix="Map "
|
|
1259
|
+
value={system.mapUV ?? normalizeTextureUV(undefined)}
|
|
1260
|
+
onChange={(uv) => binding.apply((s) => (s.mapUV = uv))}
|
|
1261
|
+
/>
|
|
1262
|
+
{!isMesh && (
|
|
1263
|
+
<Row label="Flip U/V">
|
|
1264
|
+
<div style={{display: 'flex', gap: 6}}>
|
|
1265
|
+
{(['x', 'y'] as const).map((axis) => (
|
|
1266
|
+
<NumberField
|
|
1267
|
+
key={axis}
|
|
1268
|
+
value={system.flip?.[axis] ?? 0}
|
|
1269
|
+
min={0}
|
|
1270
|
+
max={1}
|
|
1271
|
+
step={0.1}
|
|
1272
|
+
onChange={(v) =>
|
|
1273
|
+
binding.apply((s) => {
|
|
1274
|
+
const next = s.flip?.clone() ?? new Vector2(0, 0);
|
|
1275
|
+
next[axis] = Math.min(1, Math.max(0, v));
|
|
1276
|
+
s.flip = next.x === 0 && next.y === 0 ? undefined : next;
|
|
1277
|
+
})
|
|
1278
|
+
}
|
|
1279
|
+
/>
|
|
1280
|
+
))}
|
|
1281
|
+
</div>
|
|
1282
|
+
</Row>
|
|
1283
|
+
)}
|
|
1167
1284
|
<Row label="Alpha test">
|
|
1168
1285
|
<NumberField
|
|
1169
1286
|
value={system.getRendererSettings().materialAlphaTest ?? 0}
|
|
@@ -1184,6 +1301,134 @@ export function RendererModule({
|
|
|
1184
1301
|
onChange={(v) => patchMaterial({depthTest: v})}
|
|
1185
1302
|
/>
|
|
1186
1303
|
</Row>
|
|
1304
|
+
<SubHeader
|
|
1305
|
+
title="Gradient map"
|
|
1306
|
+
hint="Recolours the map through a colour ramp: one channel of the map, or its luminance, picks a colour along the ramp."
|
|
1307
|
+
/>
|
|
1308
|
+
<Row label="Ramp">
|
|
1309
|
+
<TextureSelect
|
|
1310
|
+
value={gradientMap?.ramp}
|
|
1311
|
+
textureOptions={textureOptions}
|
|
1312
|
+
loadTexture={loadTexture}
|
|
1313
|
+
promptTitle="Ramp texture URL"
|
|
1314
|
+
onChange={(ramp) =>
|
|
1315
|
+
binding.apply((s) => (s.gradientMap = ramp ? {...s.gradientMap, ramp} : undefined))
|
|
1316
|
+
}
|
|
1317
|
+
/>
|
|
1318
|
+
</Row>
|
|
1319
|
+
{gradientMap && (
|
|
1320
|
+
<Row label="Ramp channel">
|
|
1321
|
+
<SelectField<GradientMapChannel>
|
|
1322
|
+
value={gradientMap.channel}
|
|
1323
|
+
options={[
|
|
1324
|
+
{value: 'r', label: 'Red'},
|
|
1325
|
+
{value: 'g', label: 'Green'},
|
|
1326
|
+
{value: 'b', label: 'Blue'},
|
|
1327
|
+
{value: 'a', label: 'Alpha (ramp alpha replaces)'},
|
|
1328
|
+
{value: 'luminance', label: 'Luminance'},
|
|
1329
|
+
]}
|
|
1330
|
+
onChange={(channel) => binding.apply((s) => (s.gradientMap = {...s.gradientMap, channel}))}
|
|
1331
|
+
/>
|
|
1332
|
+
</Row>
|
|
1333
|
+
)}
|
|
1334
|
+
<SubHeader
|
|
1335
|
+
title="Erosion"
|
|
1336
|
+
hint="Alpha decides how much of the shape is left instead of how transparent it is, so a fading alpha dissolves or wipes the shape away."
|
|
1337
|
+
/>
|
|
1338
|
+
<Row label="Erosion">
|
|
1339
|
+
<CheckboxField
|
|
1340
|
+
value={!!erosion}
|
|
1341
|
+
onChange={(v) => binding.apply((s) => (s.erosion = v ? {} : undefined))}
|
|
1342
|
+
/>
|
|
1343
|
+
</Row>
|
|
1344
|
+
{erosion && (
|
|
1345
|
+
<>
|
|
1346
|
+
<Row label="Erosion mask">
|
|
1347
|
+
<TextureSelect
|
|
1348
|
+
value={erosion.mask}
|
|
1349
|
+
textureOptions={textureOptions}
|
|
1350
|
+
loadTexture={loadTexture}
|
|
1351
|
+
noneLabel="(map alpha)"
|
|
1352
|
+
promptTitle="Erosion mask URL"
|
|
1353
|
+
onChange={(mask) => patchErosion({mask})}
|
|
1354
|
+
/>
|
|
1355
|
+
</Row>
|
|
1356
|
+
{erosion.mask && (
|
|
1357
|
+
<TextureUVField
|
|
1358
|
+
labelPrefix="Mask "
|
|
1359
|
+
value={erosion.maskUV}
|
|
1360
|
+
onChange={(maskUV) => patchErosion({maskUV})}
|
|
1361
|
+
/>
|
|
1362
|
+
)}
|
|
1363
|
+
<Row label="Softness">
|
|
1364
|
+
<NumberField
|
|
1365
|
+
value={erosion.softness}
|
|
1366
|
+
min={0.0001}
|
|
1367
|
+
step={0.01}
|
|
1368
|
+
onChange={(softness) => patchErosion({softness})}
|
|
1369
|
+
/>
|
|
1370
|
+
</Row>
|
|
1371
|
+
<Row label="Edge width">
|
|
1372
|
+
<NumberField
|
|
1373
|
+
value={erosion.edgeWidth}
|
|
1374
|
+
min={0}
|
|
1375
|
+
step={0.01}
|
|
1376
|
+
onChange={(edgeWidth) => patchErosion({edgeWidth})}
|
|
1377
|
+
/>
|
|
1378
|
+
</Row>
|
|
1379
|
+
{(erosion.edgeWidth > 0 || erosion.edgeColor.some((c) => c !== 1)) && (
|
|
1380
|
+
<Row label="Edge color">
|
|
1381
|
+
<div style={{display: 'flex', gap: 6}}>
|
|
1382
|
+
{[0, 1, 2].map((channel) => (
|
|
1383
|
+
<NumberField
|
|
1384
|
+
key={channel}
|
|
1385
|
+
value={erosion.edgeColor[channel]}
|
|
1386
|
+
min={0}
|
|
1387
|
+
step={0.05}
|
|
1388
|
+
onChange={(v) => {
|
|
1389
|
+
const edgeColor: [number, number, number] = [...erosion.edgeColor];
|
|
1390
|
+
edgeColor[channel] = v;
|
|
1391
|
+
patchErosion({edgeColor});
|
|
1392
|
+
}}
|
|
1393
|
+
/>
|
|
1394
|
+
))}
|
|
1395
|
+
</div>
|
|
1396
|
+
</Row>
|
|
1397
|
+
)}
|
|
1398
|
+
</>
|
|
1399
|
+
)}
|
|
1400
|
+
<SubHeader
|
|
1401
|
+
title="UV distortion"
|
|
1402
|
+
hint="A scrolling noise texture pushes the map's UV about, for heat shimmer and licking, breathing shapes."
|
|
1403
|
+
/>
|
|
1404
|
+
<Row label="Noise texture">
|
|
1405
|
+
<TextureSelect
|
|
1406
|
+
value={distortion?.map}
|
|
1407
|
+
textureOptions={textureOptions}
|
|
1408
|
+
loadTexture={loadTexture}
|
|
1409
|
+
promptTitle="Distortion noise URL"
|
|
1410
|
+
onChange={(map) =>
|
|
1411
|
+
binding.apply((s) => (s.uvDistortion = map ? {...s.uvDistortion, map} : undefined))
|
|
1412
|
+
}
|
|
1413
|
+
/>
|
|
1414
|
+
</Row>
|
|
1415
|
+
{distortion && (
|
|
1416
|
+
<>
|
|
1417
|
+
<Row label="Distortion strength">
|
|
1418
|
+
<NumberField
|
|
1419
|
+
value={distortion.strength}
|
|
1420
|
+
min={0}
|
|
1421
|
+
step={0.01}
|
|
1422
|
+
onChange={(strength) => patchDistortion({strength})}
|
|
1423
|
+
/>
|
|
1424
|
+
</Row>
|
|
1425
|
+
<TextureUVField
|
|
1426
|
+
labelPrefix="Noise "
|
|
1427
|
+
value={distortion.mapUV}
|
|
1428
|
+
onChange={(mapUV) => patchDistortion({mapUV})}
|
|
1429
|
+
/>
|
|
1430
|
+
</>
|
|
1431
|
+
)}
|
|
1187
1432
|
{system.renderMode === RenderMode.Mesh &&
|
|
1188
1433
|
(() => {
|
|
1189
1434
|
const preset = detectGeometryPreset(system.instancingGeometry);
|
|
@@ -1192,11 +1437,20 @@ export function RendererModule({
|
|
|
1192
1437
|
const hostGeometry = geometryOptions ?? [];
|
|
1193
1438
|
const applyGeometry = (g: GeometryData) =>
|
|
1194
1439
|
binding.apply((s) => {
|
|
1440
|
+
// The previous mesh's sets go first, so the positions setter gives
|
|
1441
|
+
// this mesh the library's own fallback UVs and normals; then every set
|
|
1442
|
+
// this mesh has. An unindexed mesh draws its vertices in sequence.
|
|
1195
1443
|
const settings = s.getRendererSettings();
|
|
1196
|
-
settings.
|
|
1197
|
-
settings.
|
|
1198
|
-
settings.instancingNormals =
|
|
1444
|
+
settings.instancingUVs = undefined;
|
|
1445
|
+
settings.instancingUVs2 = undefined;
|
|
1446
|
+
settings.instancingNormals = undefined;
|
|
1199
1447
|
s.instancingGeometry = new Float32Array(g.positions);
|
|
1448
|
+
s.instancingIndices = g.indices
|
|
1449
|
+
? new Uint32Array(g.indices)
|
|
1450
|
+
: Uint32Array.from({length: g.positions.length / 3}, (_, i) => i);
|
|
1451
|
+
if (g.uvs) s.instancingUVs = new Float32Array(g.uvs);
|
|
1452
|
+
if (g.uvs2) s.instancingUVs2 = new Float32Array(g.uvs2);
|
|
1453
|
+
if (g.normals) settings.instancingNormals = new Float32Array(g.normals);
|
|
1200
1454
|
});
|
|
1201
1455
|
return (
|
|
1202
1456
|
<Row label="Geometry">
|
|
@@ -1233,6 +1487,7 @@ export function RendererModule({
|
|
|
1233
1487
|
}
|
|
1234
1488
|
binding.apply((s) => {
|
|
1235
1489
|
const settings = s.getRendererSettings();
|
|
1490
|
+
settings.instancingUVs2 = undefined;
|
|
1236
1491
|
if (next === 'cube') {
|
|
1237
1492
|
settings.instancingIndices = MESH_CUBE_INDICES;
|
|
1238
1493
|
settings.instancingUVs = undefined;
|
|
@@ -1252,7 +1507,9 @@ export function RendererModule({
|
|
|
1252
1507
|
/>
|
|
1253
1508
|
{preset === 'custom' && (
|
|
1254
1509
|
<span style={{fontSize: 10.5, color: '#9eb9ff'}}>
|
|
1255
|
-
{vertexCount} verts / {triangleCount} tris
|
|
1510
|
+
{vertexCount} verts / {triangleCount} tris
|
|
1511
|
+
{system.getRendererSettings().instancingUVs2 ? ' / 2 UV sets' : ''} — picking a
|
|
1512
|
+
preset replaces this mesh
|
|
1256
1513
|
</span>
|
|
1257
1514
|
)}
|
|
1258
1515
|
</div>
|
|
@@ -1414,6 +1671,19 @@ export function RendererModule({
|
|
|
1414
1671
|
</Row>
|
|
1415
1672
|
</>
|
|
1416
1673
|
)}
|
|
1674
|
+
{isMesh && (
|
|
1675
|
+
<Row label="Render face">
|
|
1676
|
+
<SelectField
|
|
1677
|
+
value={system.renderFace}
|
|
1678
|
+
options={[
|
|
1679
|
+
{value: 'both', label: 'Both'},
|
|
1680
|
+
{value: 'front', label: 'Front'},
|
|
1681
|
+
{value: 'back', label: 'Back'},
|
|
1682
|
+
]}
|
|
1683
|
+
onChange={(face) => binding.apply((s) => (s.renderFace = face as RenderFace))}
|
|
1684
|
+
/>
|
|
1685
|
+
</Row>
|
|
1686
|
+
)}
|
|
1417
1687
|
<Row label="Sort mode">
|
|
1418
1688
|
<SelectField
|
|
1419
1689
|
value={system.sortMode}
|