babylon.quarks-editor 0.26.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +63 -0
- package/README.md +38 -5
- package/dist/core/assetDragSession.d.ts +1 -1
- package/dist/core/assetTree.d.ts +1 -1
- package/dist/core/binding.d.ts +2 -2
- package/dist/core/binding.js +1 -1
- package/dist/core/defaultEffect.d.ts +2 -2
- package/dist/core/defaultEffect.js +3 -3
- package/dist/core/editorScene.d.ts +1 -1
- package/dist/core/effectTree.d.ts +1 -1
- package/dist/core/effectTree.js +13 -88
- package/dist/core/emitterShapeWireframe.d.ts +2 -2
- package/dist/core/emitterShapeWireframe.js +45 -13
- package/dist/core/galleryDropMarker.d.ts +2 -2
- package/dist/core/galleryDropMarker.js +6 -6
- package/dist/core/galleryLayout.d.ts +3 -3
- package/dist/core/galleryLayout.js +1 -1
- package/dist/core/galleryPlayback.d.ts +1 -1
- package/dist/core/galleryScene.d.ts +4 -4
- package/dist/core/galleryScene.js +3 -3
- package/dist/core/gallerySelectionMarker.d.ts +4 -4
- package/dist/core/gallerySelectionMarker.js +5 -5
- package/dist/core/groups.d.ts +1 -1
- package/dist/core/groups.js +1 -1
- package/dist/core/history.d.ts +1 -1
- package/dist/core/indexedDbProjectStore.d.ts +1 -1
- package/dist/core/lineShaders.d.ts +2 -2
- package/dist/core/lineShaders.js +2 -2
- package/dist/core/loadEffect.d.ts +2 -2
- package/dist/core/loadEffect.js +1 -1
- package/dist/core/pickAssetFiles.js +1 -1
- package/dist/core/sceneEntries.d.ts +4 -4
- package/dist/core/sceneEntries.js +2 -2
- package/dist/core/scrub.d.ts +1 -1
- package/dist/core/shapes.d.ts +1 -1
- package/dist/core/systems.d.ts +2 -2
- package/dist/core/textureExport.d.ts +1 -1
- package/dist/core/timeline.d.ts +2 -2
- package/dist/core/transport.d.ts +3 -3
- package/dist/core/transport.js +2 -2
- package/dist/core/viewportPicker.d.ts +4 -4
- package/dist/core/viewportPicker.js +8 -7
- package/dist/index.d.ts +21 -21
- package/dist/index.js +12 -12
- package/dist/react/AssetsPanel.d.ts +2 -2
- package/dist/react/AssetsPanel.js +6 -6
- package/dist/react/ChoiceDialog.js +1 -1
- package/dist/react/ContextMenu.js +1 -1
- package/dist/react/CurveEditor.d.ts +1 -1
- package/dist/react/CurveEditor.js +2 -2
- package/dist/react/EditorWorkspace.d.ts +2 -2
- package/dist/react/EditorWorkspace.js +4 -4
- package/dist/react/EffectEditor.d.ts +3 -3
- package/dist/react/EffectEditor.js +7 -7
- package/dist/react/EffectEditorHost.d.ts +6 -6
- package/dist/react/EffectEditorHost.js +59 -62
- package/dist/react/EffectTreeView.d.ts +1 -1
- package/dist/react/EffectTreeView.js +3 -3
- package/dist/react/GradientEditor.d.ts +1 -1
- package/dist/react/GradientEditor.js +3 -3
- package/dist/react/InspectorInfoIcon.js +3 -3
- package/dist/react/InspectorLabel.js +3 -3
- package/dist/react/MessageDialog.js +1 -1
- package/dist/react/ModuleSection.js +4 -4
- package/dist/react/ObjectModule.d.ts +1 -1
- package/dist/react/ObjectModule.js +4 -4
- package/dist/react/ProjectsPage.d.ts +1 -1
- package/dist/react/ProjectsPage.js +6 -6
- package/dist/react/PromptDialog.js +1 -1
- package/dist/react/ScenePanel.d.ts +2 -2
- package/dist/react/ScenePanel.js +5 -5
- package/dist/react/SubEmittersModule.d.ts +1 -1
- package/dist/react/SubEmittersModule.js +5 -5
- package/dist/react/TextureSelect.d.ts +24 -0
- package/dist/react/TextureSelect.js +74 -0
- package/dist/react/TextureUVField.d.ts +14 -0
- package/dist/react/TextureUVField.js +23 -0
- package/dist/react/TimelinePanel.d.ts +7 -7
- package/dist/react/TimelinePanel.js +11 -9
- package/dist/react/TimelineTrackRow.d.ts +3 -3
- package/dist/react/TimelineTrackRow.js +6 -6
- package/dist/react/TransportBar.d.ts +4 -4
- package/dist/react/TransportBar.js +3 -3
- package/dist/react/ValueField.d.ts +1 -1
- package/dist/react/ValueField.js +3 -3
- package/dist/react/behaviorModules.d.ts +4 -2
- package/dist/react/behaviorModules.js +52 -16
- package/dist/react/fields.d.ts +1 -0
- package/dist/react/fields.js +3 -3
- package/dist/react/index.d.ts +40 -38
- package/dist/react/index.js +25 -23
- package/dist/react/inspectorHints.js +31 -0
- package/dist/react/modules.d.ts +3 -1
- package/dist/react/modules.js +99 -100
- package/package.json +3 -2
- package/src/core/assetDragSession.ts +1 -1
- package/src/core/assetTree.ts +1 -1
- package/src/core/binding.ts +3 -3
- package/src/core/defaultEffect.ts +4 -4
- package/src/core/editorScene.ts +1 -1
- package/src/core/effectTree.ts +13 -101
- package/src/core/emitterShapeWireframe.ts +48 -16
- package/src/core/galleryDropMarker.ts +8 -8
- package/src/core/galleryLayout.ts +4 -4
- package/src/core/galleryPlayback.ts +1 -1
- package/src/core/galleryScene.ts +5 -5
- package/src/core/gallerySelectionMarker.ts +7 -7
- package/src/core/groups.ts +1 -1
- package/src/core/history.ts +1 -1
- package/src/core/indexedDbProjectStore.ts +1 -1
- package/src/core/lineShaders.ts +2 -2
- package/src/core/loadEffect.ts +3 -3
- package/src/core/pickAssetFiles.ts +2 -2
- package/src/core/sceneEntries.ts +5 -5
- package/src/core/scrub.ts +1 -1
- package/src/core/shapes.ts +1 -1
- package/src/core/systems.ts +2 -2
- package/src/core/textureExport.ts +1 -1
- package/src/core/timeline.ts +3 -3
- package/src/core/transport.ts +4 -4
- package/src/core/viewportPicker.ts +12 -11
- package/src/index.ts +21 -21
- package/src/react/AssetsPanel.tsx +6 -6
- package/src/react/ChoiceDialog.tsx +1 -1
- package/src/react/ContextMenu.tsx +1 -1
- package/src/react/CurveEditor.tsx +2 -2
- package/src/react/EditorWorkspace.tsx +5 -5
- package/src/react/EffectEditor.tsx +10 -8
- package/src/react/EffectEditorHost.tsx +64 -69
- package/src/react/EffectTreeView.tsx +4 -4
- package/src/react/GradientEditor.tsx +3 -3
- package/src/react/InspectorInfoIcon.tsx +3 -3
- package/src/react/InspectorLabel.tsx +3 -3
- package/src/react/MessageDialog.tsx +1 -1
- package/src/react/ModuleSection.tsx +4 -4
- package/src/react/ObjectModule.tsx +5 -5
- package/src/react/ProjectsPage.tsx +6 -6
- package/src/react/PromptDialog.tsx +1 -1
- package/src/react/ScenePanel.tsx +7 -7
- package/src/react/SubEmittersModule.tsx +6 -6
- package/src/react/TextureSelect.tsx +118 -0
- package/src/react/TextureUVField.tsx +62 -0
- package/src/react/TimelinePanel.tsx +17 -15
- package/src/react/TimelineTrackRow.tsx +9 -9
- package/src/react/TransportBar.tsx +7 -7
- package/src/react/ValueField.tsx +3 -3
- package/src/react/behaviorModules.tsx +211 -28
- package/src/react/fields.tsx +10 -3
- package/src/react/index.ts +41 -38
- package/src/react/inspectorHints.ts +40 -0
- package/src/react/modules.tsx +375 -131
package/src/react/modules.tsx
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
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
|
+
ScalingMode,
|
|
14
|
+
SortMode,
|
|
15
|
+
TrailSettings,
|
|
16
|
+
} from 'babylon.quarks';
|
|
5
17
|
import {
|
|
6
18
|
AxisAngleGenerator,
|
|
7
19
|
BoxEmitFrom,
|
|
@@ -17,12 +29,14 @@ import {
|
|
|
17
29
|
RandomQuatGenerator,
|
|
18
30
|
RenderMode,
|
|
19
31
|
SizeOverLife,
|
|
32
|
+
Vector2,
|
|
20
33
|
Vector3,
|
|
21
34
|
Vector3Function,
|
|
22
35
|
Vector4,
|
|
36
|
+
normalizeTextureUV,
|
|
23
37
|
} from 'babylon.quarks';
|
|
24
|
-
import React, {useMemo
|
|
25
|
-
import {EffectBinding} from '../core/binding';
|
|
38
|
+
import React, {useMemo} from 'react';
|
|
39
|
+
import {EffectBinding} from '../core/binding.js';
|
|
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,17 +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 {
|
|
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} 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';
|
|
55
70
|
|
|
56
71
|
interface ModuleProps {
|
|
57
72
|
binding: EffectBinding;
|
|
@@ -105,6 +120,49 @@ const PARAM_LABELS: {[key: string]: string} = {
|
|
|
105
120
|
speed: 'Speed (lengths/s)',
|
|
106
121
|
};
|
|
107
122
|
|
|
123
|
+
/** Uppercase group title inside a module, as the Renderer's "Material" heading. */
|
|
124
|
+
function SubHeader(props: {title: string; hint: string}) {
|
|
125
|
+
return (
|
|
126
|
+
<div
|
|
127
|
+
style={{
|
|
128
|
+
marginTop: 10,
|
|
129
|
+
marginBottom: 2,
|
|
130
|
+
fontSize: 11,
|
|
131
|
+
fontWeight: 600,
|
|
132
|
+
color: '#7c8db5',
|
|
133
|
+
textTransform: 'uppercase',
|
|
134
|
+
letterSpacing: 0.4,
|
|
135
|
+
display: 'flex',
|
|
136
|
+
alignItems: 'center',
|
|
137
|
+
gap: 5,
|
|
138
|
+
}}
|
|
139
|
+
>
|
|
140
|
+
{props.title}
|
|
141
|
+
<InspectorInfoIcon content={props.hint} size={12} />
|
|
142
|
+
</div>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Three number inputs editing a Vector3 by value. */
|
|
147
|
+
function AxisField(props: {value: Vector3; onChange: (next: Vector3) => void}) {
|
|
148
|
+
return (
|
|
149
|
+
<div style={{display: 'flex', gap: 6}}>
|
|
150
|
+
{(['x', 'y', 'z'] as const).map((axis) => (
|
|
151
|
+
<NumberField
|
|
152
|
+
key={axis}
|
|
153
|
+
value={props.value[axis]}
|
|
154
|
+
step={0.1}
|
|
155
|
+
onChange={(v) => {
|
|
156
|
+
const next = props.value.clone();
|
|
157
|
+
next[axis] = v;
|
|
158
|
+
props.onChange(next);
|
|
159
|
+
}}
|
|
160
|
+
/>
|
|
161
|
+
))}
|
|
162
|
+
</div>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
108
166
|
function ColorInput(props: {value: Vector4; onChange: (next: Vector4) => void}) {
|
|
109
167
|
const {value, onChange} = props;
|
|
110
168
|
return (
|
|
@@ -205,9 +263,31 @@ export function MainModule({binding}: ModuleProps) {
|
|
|
205
263
|
<Row label="World space">
|
|
206
264
|
<CheckboxField value={system.worldSpace} onChange={(v) => binding.apply((s) => (s.worldSpace = v))} />
|
|
207
265
|
</Row>
|
|
266
|
+
{system.worldSpace && (
|
|
267
|
+
<Row label="Scaling mode">
|
|
268
|
+
<SelectField<ScalingMode>
|
|
269
|
+
value={system.scalingMode}
|
|
270
|
+
options={[
|
|
271
|
+
{value: 'shape', label: 'Shape (sizes and shape only)'},
|
|
272
|
+
{value: 'hierarchy', label: 'Hierarchy (motion scales too)'},
|
|
273
|
+
]}
|
|
274
|
+
onChange={(mode) => binding.apply((s) => (s.scalingMode = mode))}
|
|
275
|
+
/>
|
|
276
|
+
</Row>
|
|
277
|
+
)}
|
|
208
278
|
<Row label="Prewarm">
|
|
209
279
|
<CheckboxField value={system.prewarm} onChange={(v) => binding.apply((s) => (s.prewarm = v))} />
|
|
210
280
|
</Row>
|
|
281
|
+
{system.prewarm && (
|
|
282
|
+
<Row label="Warm-up time">
|
|
283
|
+
<NumberField
|
|
284
|
+
value={system.prewarmTime}
|
|
285
|
+
step={0.1}
|
|
286
|
+
min={0}
|
|
287
|
+
onChange={(v) => binding.apply((s) => (s.prewarmTime = Math.max(0, v)))}
|
|
288
|
+
/>
|
|
289
|
+
</Row>
|
|
290
|
+
)}
|
|
211
291
|
<Row label="Playback speed">
|
|
212
292
|
<NumberField
|
|
213
293
|
value={system.playbackSpeed}
|
|
@@ -808,6 +888,8 @@ export interface GeometryOption {
|
|
|
808
888
|
positions: Float32Array | number[];
|
|
809
889
|
indices?: Uint32Array | number[];
|
|
810
890
|
uvs?: Float32Array | number[];
|
|
891
|
+
/** A second UV set (glTF `TEXCOORD_1`), what textures on UV channel 1 read on the mesh. */
|
|
892
|
+
uvs2?: Float32Array | number[];
|
|
811
893
|
normals?: Float32Array | number[];
|
|
812
894
|
}
|
|
813
895
|
|
|
@@ -913,9 +995,15 @@ export function RendererModule({
|
|
|
913
995
|
resolveGeometry?: (file: File) => Promise<GeometryData>;
|
|
914
996
|
}) {
|
|
915
997
|
const system = binding.system;
|
|
916
|
-
const currentTextureUrl = (system.texture as {url?: string} | null)?.url ?? '';
|
|
917
|
-
const [promptingTextureUrl, setPromptingTextureUrl] = useState(false);
|
|
918
998
|
const materialLabel = getMaterialLabel(system.material);
|
|
999
|
+
const isMesh = system.renderMode === RenderMode.Mesh;
|
|
1000
|
+
const gradientMap = system.gradientMap;
|
|
1001
|
+
const erosion = system.erosion;
|
|
1002
|
+
const distortion = system.uvDistortion;
|
|
1003
|
+
const patchErosion = (patch: ParticleErosionOptions) =>
|
|
1004
|
+
binding.apply((s) => (s.erosion = {...s.erosion, ...patch}));
|
|
1005
|
+
const patchDistortion = (patch: ParticleUVDistortionOptions) =>
|
|
1006
|
+
binding.apply((s) => (s.uvDistortion = {...s.uvDistortion, ...patch}));
|
|
919
1007
|
const loadTexture = useMemo(() => {
|
|
920
1008
|
return (url: string) => {
|
|
921
1009
|
if (resolveTexture) {
|
|
@@ -993,18 +1081,112 @@ export function RendererModule({
|
|
|
993
1081
|
/>
|
|
994
1082
|
</Row>
|
|
995
1083
|
))}
|
|
996
|
-
<Row label="
|
|
1084
|
+
<Row label="Tessellation">
|
|
1085
|
+
<NumberField
|
|
1086
|
+
value={(system.rendererEmitterSettings as {tessellation?: number}).tessellation ?? 0}
|
|
1087
|
+
min={0}
|
|
1088
|
+
max={16}
|
|
1089
|
+
step={1}
|
|
1090
|
+
onChange={(v) =>
|
|
1091
|
+
binding.apply((s) => {
|
|
1092
|
+
(s.rendererEmitterSettings as {tessellation?: number}).tessellation = Math.round(v);
|
|
1093
|
+
})
|
|
1094
|
+
}
|
|
1095
|
+
/>
|
|
1096
|
+
</Row>
|
|
1097
|
+
<Row label="Curve tension">
|
|
1098
|
+
<NumberField
|
|
1099
|
+
value={(system.rendererEmitterSettings as {curveTension?: number}).curveTension ?? 0}
|
|
1100
|
+
min={0}
|
|
1101
|
+
max={1}
|
|
1102
|
+
step={0.05}
|
|
1103
|
+
onChange={(v) =>
|
|
1104
|
+
binding.apply((s) => {
|
|
1105
|
+
(s.rendererEmitterSettings as {curveTension?: number}).curveTension = v;
|
|
1106
|
+
})
|
|
1107
|
+
}
|
|
1108
|
+
/>
|
|
1109
|
+
</Row>
|
|
1110
|
+
<Row label="Texture mode">
|
|
1111
|
+
<SelectField
|
|
1112
|
+
value={(system.rendererEmitterSettings as TrailSettings).textureMode ?? 'stretch'}
|
|
1113
|
+
options={[
|
|
1114
|
+
{value: 'stretch', label: 'Stretch (once, by distance)'},
|
|
1115
|
+
{value: 'distributePerSegment', label: 'Distribute per segment'},
|
|
1116
|
+
{value: 'tile', label: 'Tile (per world unit)'},
|
|
1117
|
+
{value: 'repeatPerSegment', label: 'Repeat per segment'},
|
|
1118
|
+
]}
|
|
1119
|
+
onChange={(mode) =>
|
|
1120
|
+
binding.apply((s) => {
|
|
1121
|
+
(s.rendererEmitterSettings as TrailSettings).textureMode =
|
|
1122
|
+
mode === 'stretch' ? undefined : mode;
|
|
1123
|
+
})
|
|
1124
|
+
}
|
|
1125
|
+
/>
|
|
1126
|
+
</Row>
|
|
1127
|
+
<Row label="Texture scale">
|
|
1128
|
+
<NumberField
|
|
1129
|
+
value={(system.rendererEmitterSettings as TrailSettings).textureScale ?? 1}
|
|
1130
|
+
min={0.01}
|
|
1131
|
+
step={0.1}
|
|
1132
|
+
onChange={(v) =>
|
|
1133
|
+
binding.apply((s) => {
|
|
1134
|
+
(s.rendererEmitterSettings as TrailSettings).textureScale = v === 1 ? undefined : v;
|
|
1135
|
+
})
|
|
1136
|
+
}
|
|
1137
|
+
/>
|
|
1138
|
+
</Row>
|
|
1139
|
+
<Row label="Min vertex distance">
|
|
997
1140
|
<NumberField
|
|
998
|
-
value={(system.rendererEmitterSettings as
|
|
1141
|
+
value={(system.rendererEmitterSettings as TrailSettings).minVertexDistance ?? 0}
|
|
1142
|
+
min={0}
|
|
1143
|
+
step={0.05}
|
|
1144
|
+
onChange={(v) =>
|
|
1145
|
+
binding.apply((s) => {
|
|
1146
|
+
(s.rendererEmitterSettings as TrailSettings).minVertexDistance =
|
|
1147
|
+
v > 0 ? v : undefined;
|
|
1148
|
+
})
|
|
1149
|
+
}
|
|
1150
|
+
/>
|
|
1151
|
+
</Row>
|
|
1152
|
+
<Row label="Trail time">
|
|
1153
|
+
<NumberField
|
|
1154
|
+
value={(system.rendererEmitterSettings as TrailSettings).lifetime ?? 0}
|
|
999
1155
|
min={0}
|
|
1000
1156
|
step={0.1}
|
|
1001
1157
|
onChange={(v) =>
|
|
1002
1158
|
binding.apply((s) => {
|
|
1003
|
-
(s.rendererEmitterSettings as
|
|
1159
|
+
(s.rendererEmitterSettings as TrailSettings).lifetime = v > 0 ? v : undefined;
|
|
1004
1160
|
})
|
|
1005
1161
|
}
|
|
1006
1162
|
/>
|
|
1007
1163
|
</Row>
|
|
1164
|
+
<Row label="Swept trail">
|
|
1165
|
+
<CheckboxField
|
|
1166
|
+
value={!!(system.rendererEmitterSettings as TrailSettings).sideAxis}
|
|
1167
|
+
onChange={(v) =>
|
|
1168
|
+
binding.apply((s) => {
|
|
1169
|
+
(s.rendererEmitterSettings as TrailSettings).sideAxis = v
|
|
1170
|
+
? new Vector3(0, 1, 0)
|
|
1171
|
+
: undefined;
|
|
1172
|
+
})
|
|
1173
|
+
}
|
|
1174
|
+
/>
|
|
1175
|
+
</Row>
|
|
1176
|
+
{(system.rendererEmitterSettings as TrailSettings).sideAxis && (
|
|
1177
|
+
<Row label="Side axis">
|
|
1178
|
+
<AxisField
|
|
1179
|
+
value={(system.rendererEmitterSettings as TrailSettings).sideAxis!}
|
|
1180
|
+
onChange={(axis) =>
|
|
1181
|
+
binding.apply((s) => {
|
|
1182
|
+
// The axis's length scales the width: keep it a unit vector.
|
|
1183
|
+
(s.rendererEmitterSettings as TrailSettings).sideAxis =
|
|
1184
|
+
axis.length() > 0 ? axis.normalize() : axis;
|
|
1185
|
+
})
|
|
1186
|
+
}
|
|
1187
|
+
/>
|
|
1188
|
+
</Row>
|
|
1189
|
+
)}
|
|
1008
1190
|
</>
|
|
1009
1191
|
)}
|
|
1010
1192
|
{system.renderMode === RenderMode.StretchedBillBoard && (
|
|
@@ -1033,26 +1215,7 @@ export function RendererModule({
|
|
|
1033
1215
|
</Row>
|
|
1034
1216
|
</>
|
|
1035
1217
|
)}
|
|
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>
|
|
1218
|
+
<SubHeader title="Material" hint="Shader and blending settings applied to the particle sprite or mesh." />
|
|
1056
1219
|
{materialLabel ? (
|
|
1057
1220
|
<Row label="Source">
|
|
1058
1221
|
<div style={{fontSize: 12, color: theme.textDim, lineHeight: 1.45}}>{materialLabel}</div>
|
|
@@ -1077,93 +1240,41 @@ export function RendererModule({
|
|
|
1077
1240
|
/>
|
|
1078
1241
|
</Row>
|
|
1079
1242
|
<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
|
-
}}
|
|
1243
|
+
<TextureSelect
|
|
1244
|
+
value={system.texture}
|
|
1245
|
+
textureOptions={textureOptions}
|
|
1246
|
+
loadTexture={loadTexture}
|
|
1247
|
+
onChange={(texture) => patchMaterial({texture})}
|
|
1248
|
+
preview
|
|
1135
1249
|
/>
|
|
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
1250
|
</Row>
|
|
1251
|
+
<TextureUVField
|
|
1252
|
+
labelPrefix="Map "
|
|
1253
|
+
value={system.mapUV ?? normalizeTextureUV(undefined)}
|
|
1254
|
+
onChange={(uv) => binding.apply((s) => (s.mapUV = uv))}
|
|
1255
|
+
/>
|
|
1256
|
+
{!isMesh && (
|
|
1257
|
+
<Row label="Flip U/V">
|
|
1258
|
+
<div style={{display: 'flex', gap: 6}}>
|
|
1259
|
+
{(['x', 'y'] as const).map((axis) => (
|
|
1260
|
+
<NumberField
|
|
1261
|
+
key={axis}
|
|
1262
|
+
value={system.flip?.[axis] ?? 0}
|
|
1263
|
+
min={0}
|
|
1264
|
+
max={1}
|
|
1265
|
+
step={0.1}
|
|
1266
|
+
onChange={(v) =>
|
|
1267
|
+
binding.apply((s) => {
|
|
1268
|
+
const next = s.flip?.clone() ?? new Vector2(0, 0);
|
|
1269
|
+
next[axis] = Math.min(1, Math.max(0, v));
|
|
1270
|
+
s.flip = next.x === 0 && next.y === 0 ? undefined : next;
|
|
1271
|
+
})
|
|
1272
|
+
}
|
|
1273
|
+
/>
|
|
1274
|
+
))}
|
|
1275
|
+
</div>
|
|
1276
|
+
</Row>
|
|
1277
|
+
)}
|
|
1167
1278
|
<Row label="Alpha test">
|
|
1168
1279
|
<NumberField
|
|
1169
1280
|
value={system.getRendererSettings().materialAlphaTest ?? 0}
|
|
@@ -1184,6 +1295,134 @@ export function RendererModule({
|
|
|
1184
1295
|
onChange={(v) => patchMaterial({depthTest: v})}
|
|
1185
1296
|
/>
|
|
1186
1297
|
</Row>
|
|
1298
|
+
<SubHeader
|
|
1299
|
+
title="Gradient map"
|
|
1300
|
+
hint="Recolours the map through a colour ramp: one channel of the map, or its luminance, picks a colour along the ramp."
|
|
1301
|
+
/>
|
|
1302
|
+
<Row label="Ramp">
|
|
1303
|
+
<TextureSelect
|
|
1304
|
+
value={gradientMap?.ramp}
|
|
1305
|
+
textureOptions={textureOptions}
|
|
1306
|
+
loadTexture={loadTexture}
|
|
1307
|
+
promptTitle="Ramp texture URL"
|
|
1308
|
+
onChange={(ramp) =>
|
|
1309
|
+
binding.apply((s) => (s.gradientMap = ramp ? {...s.gradientMap, ramp} : undefined))
|
|
1310
|
+
}
|
|
1311
|
+
/>
|
|
1312
|
+
</Row>
|
|
1313
|
+
{gradientMap && (
|
|
1314
|
+
<Row label="Ramp channel">
|
|
1315
|
+
<SelectField<GradientMapChannel>
|
|
1316
|
+
value={gradientMap.channel}
|
|
1317
|
+
options={[
|
|
1318
|
+
{value: 'r', label: 'Red'},
|
|
1319
|
+
{value: 'g', label: 'Green'},
|
|
1320
|
+
{value: 'b', label: 'Blue'},
|
|
1321
|
+
{value: 'a', label: 'Alpha (ramp alpha replaces)'},
|
|
1322
|
+
{value: 'luminance', label: 'Luminance'},
|
|
1323
|
+
]}
|
|
1324
|
+
onChange={(channel) => binding.apply((s) => (s.gradientMap = {...s.gradientMap, channel}))}
|
|
1325
|
+
/>
|
|
1326
|
+
</Row>
|
|
1327
|
+
)}
|
|
1328
|
+
<SubHeader
|
|
1329
|
+
title="Erosion"
|
|
1330
|
+
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."
|
|
1331
|
+
/>
|
|
1332
|
+
<Row label="Erosion">
|
|
1333
|
+
<CheckboxField
|
|
1334
|
+
value={!!erosion}
|
|
1335
|
+
onChange={(v) => binding.apply((s) => (s.erosion = v ? {} : undefined))}
|
|
1336
|
+
/>
|
|
1337
|
+
</Row>
|
|
1338
|
+
{erosion && (
|
|
1339
|
+
<>
|
|
1340
|
+
<Row label="Erosion mask">
|
|
1341
|
+
<TextureSelect
|
|
1342
|
+
value={erosion.mask}
|
|
1343
|
+
textureOptions={textureOptions}
|
|
1344
|
+
loadTexture={loadTexture}
|
|
1345
|
+
noneLabel="(map alpha)"
|
|
1346
|
+
promptTitle="Erosion mask URL"
|
|
1347
|
+
onChange={(mask) => patchErosion({mask})}
|
|
1348
|
+
/>
|
|
1349
|
+
</Row>
|
|
1350
|
+
{erosion.mask && (
|
|
1351
|
+
<TextureUVField
|
|
1352
|
+
labelPrefix="Mask "
|
|
1353
|
+
value={erosion.maskUV}
|
|
1354
|
+
onChange={(maskUV) => patchErosion({maskUV})}
|
|
1355
|
+
/>
|
|
1356
|
+
)}
|
|
1357
|
+
<Row label="Softness">
|
|
1358
|
+
<NumberField
|
|
1359
|
+
value={erosion.softness}
|
|
1360
|
+
min={0.0001}
|
|
1361
|
+
step={0.01}
|
|
1362
|
+
onChange={(softness) => patchErosion({softness})}
|
|
1363
|
+
/>
|
|
1364
|
+
</Row>
|
|
1365
|
+
<Row label="Edge width">
|
|
1366
|
+
<NumberField
|
|
1367
|
+
value={erosion.edgeWidth}
|
|
1368
|
+
min={0}
|
|
1369
|
+
step={0.01}
|
|
1370
|
+
onChange={(edgeWidth) => patchErosion({edgeWidth})}
|
|
1371
|
+
/>
|
|
1372
|
+
</Row>
|
|
1373
|
+
{(erosion.edgeWidth > 0 || erosion.edgeColor.some((c) => c !== 1)) && (
|
|
1374
|
+
<Row label="Edge color">
|
|
1375
|
+
<div style={{display: 'flex', gap: 6}}>
|
|
1376
|
+
{[0, 1, 2].map((channel) => (
|
|
1377
|
+
<NumberField
|
|
1378
|
+
key={channel}
|
|
1379
|
+
value={erosion.edgeColor[channel]}
|
|
1380
|
+
min={0}
|
|
1381
|
+
step={0.05}
|
|
1382
|
+
onChange={(v) => {
|
|
1383
|
+
const edgeColor: [number, number, number] = [...erosion.edgeColor];
|
|
1384
|
+
edgeColor[channel] = v;
|
|
1385
|
+
patchErosion({edgeColor});
|
|
1386
|
+
}}
|
|
1387
|
+
/>
|
|
1388
|
+
))}
|
|
1389
|
+
</div>
|
|
1390
|
+
</Row>
|
|
1391
|
+
)}
|
|
1392
|
+
</>
|
|
1393
|
+
)}
|
|
1394
|
+
<SubHeader
|
|
1395
|
+
title="UV distortion"
|
|
1396
|
+
hint="A scrolling noise texture pushes the map's UV about, for heat shimmer and licking, breathing shapes."
|
|
1397
|
+
/>
|
|
1398
|
+
<Row label="Noise texture">
|
|
1399
|
+
<TextureSelect
|
|
1400
|
+
value={distortion?.map}
|
|
1401
|
+
textureOptions={textureOptions}
|
|
1402
|
+
loadTexture={loadTexture}
|
|
1403
|
+
promptTitle="Distortion noise URL"
|
|
1404
|
+
onChange={(map) =>
|
|
1405
|
+
binding.apply((s) => (s.uvDistortion = map ? {...s.uvDistortion, map} : undefined))
|
|
1406
|
+
}
|
|
1407
|
+
/>
|
|
1408
|
+
</Row>
|
|
1409
|
+
{distortion && (
|
|
1410
|
+
<>
|
|
1411
|
+
<Row label="Distortion strength">
|
|
1412
|
+
<NumberField
|
|
1413
|
+
value={distortion.strength}
|
|
1414
|
+
min={0}
|
|
1415
|
+
step={0.01}
|
|
1416
|
+
onChange={(strength) => patchDistortion({strength})}
|
|
1417
|
+
/>
|
|
1418
|
+
</Row>
|
|
1419
|
+
<TextureUVField
|
|
1420
|
+
labelPrefix="Noise "
|
|
1421
|
+
value={distortion.mapUV}
|
|
1422
|
+
onChange={(mapUV) => patchDistortion({mapUV})}
|
|
1423
|
+
/>
|
|
1424
|
+
</>
|
|
1425
|
+
)}
|
|
1187
1426
|
{system.renderMode === RenderMode.Mesh &&
|
|
1188
1427
|
(() => {
|
|
1189
1428
|
const preset = detectGeometryPreset(system.instancingGeometry);
|
|
@@ -1193,10 +1432,12 @@ export function RendererModule({
|
|
|
1193
1432
|
const applyGeometry = (g: GeometryData) =>
|
|
1194
1433
|
binding.apply((s) => {
|
|
1195
1434
|
const settings = s.getRendererSettings();
|
|
1435
|
+
// Positions first: the setter keeps or drops the other sets by vertex count.
|
|
1436
|
+
s.instancingGeometry = new Float32Array(g.positions);
|
|
1196
1437
|
settings.instancingIndices = (g.indices ? new Uint32Array(g.indices) : undefined) as never;
|
|
1197
1438
|
settings.instancingUVs = (g.uvs ? new Float32Array(g.uvs) : undefined) as never;
|
|
1439
|
+
settings.instancingUVs2 = (g.uvs2 ? new Float32Array(g.uvs2) : undefined) as never;
|
|
1198
1440
|
settings.instancingNormals = (g.normals ? new Float32Array(g.normals) : undefined) as never;
|
|
1199
|
-
s.instancingGeometry = new Float32Array(g.positions);
|
|
1200
1441
|
});
|
|
1201
1442
|
return (
|
|
1202
1443
|
<Row label="Geometry">
|
|
@@ -1233,6 +1474,7 @@ export function RendererModule({
|
|
|
1233
1474
|
}
|
|
1234
1475
|
binding.apply((s) => {
|
|
1235
1476
|
const settings = s.getRendererSettings();
|
|
1477
|
+
settings.instancingUVs2 = undefined;
|
|
1236
1478
|
if (next === 'cube') {
|
|
1237
1479
|
settings.instancingIndices = MESH_CUBE_INDICES;
|
|
1238
1480
|
settings.instancingUVs = undefined;
|
|
@@ -1252,7 +1494,9 @@ export function RendererModule({
|
|
|
1252
1494
|
/>
|
|
1253
1495
|
{preset === 'custom' && (
|
|
1254
1496
|
<span style={{fontSize: 10.5, color: '#9eb9ff'}}>
|
|
1255
|
-
{vertexCount} verts / {triangleCount} tris
|
|
1497
|
+
{vertexCount} verts / {triangleCount} tris
|
|
1498
|
+
{system.getRendererSettings().instancingUVs2 ? ' / 2 UV sets' : ''} — picking a
|
|
1499
|
+
preset replaces this mesh
|
|
1256
1500
|
</span>
|
|
1257
1501
|
)}
|
|
1258
1502
|
</div>
|