babylon.quarks-editor 0.23.0 → 0.24.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 +22 -0
- package/dist/core/material.d.ts +3 -0
- package/dist/core/material.js +12 -0
- package/dist/core/shapes.d.ts +8 -2
- package/dist/core/shapes.js +25 -1
- package/dist/react/EffectEditor.js +2 -2
- package/dist/react/EffectEditorHost.js +14 -2
- package/dist/react/SubEmittersModule.js +3 -1
- package/dist/react/ValueField.d.ts +2 -5
- package/dist/react/ValueField.js +2 -1
- package/dist/react/behaviorModules.d.ts +11 -0
- package/dist/react/behaviorModules.js +81 -3
- package/dist/react/modules.js +74 -23
- package/package.json +4 -3
- package/src/core/material.ts +15 -0
- package/src/core/shapes.ts +36 -0
- package/src/react/EffectEditor.tsx +4 -0
- package/src/react/EffectEditorHost.tsx +17 -2
- package/src/react/SubEmittersModule.tsx +15 -0
- package/src/react/ValueField.tsx +6 -1
- package/src/react/behaviorModules.tsx +315 -0
- package/src/react/modules.tsx +282 -28
package/src/react/modules.tsx
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import {Texture} from '@babylonjs/core/Materials/Textures/texture';
|
|
2
2
|
import {Mesh} from '@babylonjs/core/Meshes/mesh';
|
|
3
3
|
import {PlusIcon, XMarkIcon} from '@heroicons/react/24/solid';
|
|
4
|
-
import type {Behavior, MeshLighting, MeshRim, ParticleSystem} from 'babylon.quarks';
|
|
4
|
+
import type {Behavior, CullingMode, MeshLighting, MeshRim, ParticleSystem, SortMode} from 'babylon.quarks';
|
|
5
5
|
import {
|
|
6
|
+
AxisAngleGenerator,
|
|
7
|
+
BoxEmitFrom,
|
|
6
8
|
ColorOverLife,
|
|
9
|
+
ColorRange,
|
|
7
10
|
ConstantColor,
|
|
8
11
|
ConstantValue,
|
|
9
12
|
EulerGenerator,
|
|
@@ -14,6 +17,7 @@ import {
|
|
|
14
17
|
RandomQuatGenerator,
|
|
15
18
|
RenderMode,
|
|
16
19
|
SizeOverLife,
|
|
20
|
+
Vector3,
|
|
17
21
|
Vector3Function,
|
|
18
22
|
Vector4,
|
|
19
23
|
} from 'babylon.quarks';
|
|
@@ -97,6 +101,8 @@ const PARAM_LABELS: {[key: string]: string} = {
|
|
|
97
101
|
height: 'Height',
|
|
98
102
|
column: 'Columns',
|
|
99
103
|
row: 'Rows',
|
|
104
|
+
depth: 'Depth',
|
|
105
|
+
speed: 'Speed (lengths/s)',
|
|
100
106
|
};
|
|
101
107
|
|
|
102
108
|
function ColorInput(props: {value: Vector4; onChange: (next: Vector4) => void}) {
|
|
@@ -133,28 +139,35 @@ function ColorInput(props: {value: Vector4; onChange: (next: Vector4) => void})
|
|
|
133
139
|
export function MainModule({binding}: ModuleProps) {
|
|
134
140
|
const system = binding.system;
|
|
135
141
|
const startColor = system.startColor;
|
|
136
|
-
const colorMode =
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
142
|
+
const colorMode =
|
|
143
|
+
startColor instanceof RandomColor ? 'random' : startColor instanceof ColorRange ? 'range' : 'constant';
|
|
144
|
+
const twoColors = startColor instanceof RandomColor || startColor instanceof ColorRange;
|
|
145
|
+
const colorA = twoColors
|
|
146
|
+
? (startColor as never as {a: Vector4}).a
|
|
147
|
+
: startColor instanceof ConstantColor
|
|
148
|
+
? startColor.color
|
|
149
|
+
: new Vector4(1, 1, 1, 1);
|
|
150
|
+
const colorB = twoColors ? (startColor as never as {b: Vector4}).b : new Vector4(1, 0.5, 0.2, 1);
|
|
151
|
+
/** The two-colour generator of the current mode. */
|
|
152
|
+
const makeTwoColors = (a: Vector4, b: Vector4) =>
|
|
153
|
+
colorMode === 'range' ? new ColorRange(a, b) : new RandomColor(a, b);
|
|
145
154
|
const rotation = system.startRotation;
|
|
146
155
|
const isEuler = rotation instanceof EulerGenerator;
|
|
156
|
+
const isAxisAngle = rotation instanceof AxisAngleGenerator;
|
|
147
157
|
const rotationMode =
|
|
148
158
|
rotation instanceof RandomQuatGenerator
|
|
149
159
|
? '3d'
|
|
150
160
|
: isEuler
|
|
151
161
|
? 'euler'
|
|
152
|
-
:
|
|
153
|
-
? '
|
|
154
|
-
: '
|
|
162
|
+
: isAxisAngle
|
|
163
|
+
? 'axisAngle'
|
|
164
|
+
: readScalar(rotation as never).mode === 'random'
|
|
165
|
+
? 'random'
|
|
166
|
+
: 'angle';
|
|
155
167
|
const rotationState = readScalar(
|
|
156
|
-
rotation instanceof RandomQuatGenerator || isEuler ? undefined : (rotation as never)
|
|
168
|
+
rotation instanceof RandomQuatGenerator || isEuler || isAxisAngle ? undefined : (rotation as never)
|
|
157
169
|
);
|
|
170
|
+
const axisAngle = isAxisAngle ? (rotation as AxisAngleGenerator) : undefined;
|
|
158
171
|
const eulerAngles = {
|
|
159
172
|
x: isEuler ? readScalar((rotation as EulerGenerator).angleX as never).value : 0,
|
|
160
173
|
y: isEuler ? readScalar((rotation as EulerGenerator).angleY as never).value : 0,
|
|
@@ -195,6 +208,39 @@ export function MainModule({binding}: ModuleProps) {
|
|
|
195
208
|
<Row label="Prewarm">
|
|
196
209
|
<CheckboxField value={system.prewarm} onChange={(v) => binding.apply((s) => (s.prewarm = v))} />
|
|
197
210
|
</Row>
|
|
211
|
+
<Row label="Playback speed">
|
|
212
|
+
<NumberField
|
|
213
|
+
value={system.playbackSpeed}
|
|
214
|
+
step={0.1}
|
|
215
|
+
min={0}
|
|
216
|
+
onChange={(v) => binding.apply((s) => (s.playbackSpeed = Math.max(0, v)))}
|
|
217
|
+
/>
|
|
218
|
+
</Row>
|
|
219
|
+
<Row label="Fixed seed">
|
|
220
|
+
<CheckboxField
|
|
221
|
+
value={system.seed !== undefined}
|
|
222
|
+
onChange={(v) => binding.apply((s) => (s.seed = v ? (s.seed ?? 1) : undefined))}
|
|
223
|
+
/>
|
|
224
|
+
{system.seed !== undefined && (
|
|
225
|
+
<NumberField
|
|
226
|
+
value={system.seed}
|
|
227
|
+
step={1}
|
|
228
|
+
min={0}
|
|
229
|
+
onChange={(v) => binding.apply((s) => (s.seed = Math.max(0, Math.floor(v))))}
|
|
230
|
+
/>
|
|
231
|
+
)}
|
|
232
|
+
</Row>
|
|
233
|
+
<Row label="Off screen">
|
|
234
|
+
<SelectField
|
|
235
|
+
value={system.cullingMode}
|
|
236
|
+
options={[
|
|
237
|
+
{value: 'always', label: 'Always simulate'},
|
|
238
|
+
{value: 'pause', label: 'Pause'},
|
|
239
|
+
{value: 'pauseAndCatchUp', label: 'Pause and catch up'},
|
|
240
|
+
]}
|
|
241
|
+
onChange={(mode) => binding.apply((s) => (s.cullingMode = mode as CullingMode))}
|
|
242
|
+
/>
|
|
243
|
+
</Row>
|
|
198
244
|
<ValueField
|
|
199
245
|
label="Start delay"
|
|
200
246
|
generator={system.startDelay}
|
|
@@ -280,6 +326,7 @@ export function MainModule({binding}: ModuleProps) {
|
|
|
280
326
|
options={[
|
|
281
327
|
{value: 'constant', label: 'Constant'},
|
|
282
328
|
{value: 'random', label: 'Random between two'},
|
|
329
|
+
{value: 'range', label: 'Blend between two'},
|
|
283
330
|
{value: 'gradient', label: 'Gradient'},
|
|
284
331
|
{value: 'gradient2', label: 'Random between gradients'},
|
|
285
332
|
]}
|
|
@@ -287,6 +334,8 @@ export function MainModule({binding}: ModuleProps) {
|
|
|
287
334
|
binding.apply((s) => {
|
|
288
335
|
if (mode === 'random') {
|
|
289
336
|
s.startColor = new RandomColor(colorA, colorB);
|
|
337
|
+
} else if (mode === 'range') {
|
|
338
|
+
s.startColor = new ColorRange(colorA, colorB);
|
|
290
339
|
} else if (mode === 'gradient') {
|
|
291
340
|
s.startColor = buildGradient(DEFAULT_GRADIENT_STOPS);
|
|
292
341
|
} else if (mode === 'gradient2') {
|
|
@@ -301,24 +350,23 @@ export function MainModule({binding}: ModuleProps) {
|
|
|
301
350
|
}
|
|
302
351
|
/>
|
|
303
352
|
</Row>
|
|
304
|
-
{(colorGenMode === 'constant' ||
|
|
353
|
+
{(colorGenMode === 'constant' || twoColors) && (
|
|
305
354
|
<Row label="">
|
|
306
355
|
<ColorInput
|
|
307
356
|
value={colorA}
|
|
308
357
|
onChange={(next) =>
|
|
309
358
|
binding.apply((s) => {
|
|
310
|
-
s.startColor =
|
|
311
|
-
colorGenMode === 'random' ? new RandomColor(next, colorB) : new ConstantColor(next);
|
|
359
|
+
s.startColor = twoColors ? makeTwoColors(next, colorB) : new ConstantColor(next);
|
|
312
360
|
})
|
|
313
361
|
}
|
|
314
362
|
/>
|
|
315
363
|
</Row>
|
|
316
364
|
)}
|
|
317
|
-
{
|
|
365
|
+
{twoColors && (
|
|
318
366
|
<Row label="">
|
|
319
367
|
<ColorInput
|
|
320
368
|
value={colorB}
|
|
321
|
-
onChange={(next) => binding.apply((s) => (s.startColor =
|
|
369
|
+
onChange={(next) => binding.apply((s) => (s.startColor = makeTwoColors(colorA, next)))}
|
|
322
370
|
/>
|
|
323
371
|
</Row>
|
|
324
372
|
)}
|
|
@@ -359,6 +407,7 @@ export function MainModule({binding}: ModuleProps) {
|
|
|
359
407
|
{value: 'angle', label: 'Angle (rad)'},
|
|
360
408
|
{value: 'random', label: 'Random angle'},
|
|
361
409
|
{value: 'euler', label: '3D (per axis)'},
|
|
410
|
+
{value: 'axisAngle', label: '3D (axis + angle)'},
|
|
362
411
|
{value: '3d', label: 'Random 3D'},
|
|
363
412
|
]}
|
|
364
413
|
onChange={(mode) =>
|
|
@@ -366,15 +415,20 @@ export function MainModule({binding}: ModuleProps) {
|
|
|
366
415
|
s.startRotation =
|
|
367
416
|
mode === '3d'
|
|
368
417
|
? new RandomQuatGenerator()
|
|
369
|
-
: mode === '
|
|
370
|
-
? new
|
|
371
|
-
new
|
|
372
|
-
new ConstantValue(
|
|
373
|
-
new ConstantValue(eulerAngles.z)
|
|
418
|
+
: mode === 'axisAngle'
|
|
419
|
+
? new AxisAngleGenerator(
|
|
420
|
+
new Vector3(0, 1, 0),
|
|
421
|
+
new ConstantValue(rotationState.value)
|
|
374
422
|
)
|
|
375
|
-
: mode === '
|
|
376
|
-
? new
|
|
377
|
-
|
|
423
|
+
: mode === 'euler'
|
|
424
|
+
? new EulerGenerator(
|
|
425
|
+
new ConstantValue(eulerAngles.x),
|
|
426
|
+
new ConstantValue(eulerAngles.y),
|
|
427
|
+
new ConstantValue(eulerAngles.z)
|
|
428
|
+
)
|
|
429
|
+
: mode === 'random'
|
|
430
|
+
? new IntervalValue(0, Math.PI * 2)
|
|
431
|
+
: new ConstantValue(rotationState.value);
|
|
378
432
|
})
|
|
379
433
|
}
|
|
380
434
|
/>
|
|
@@ -388,6 +442,36 @@ export function MainModule({binding}: ModuleProps) {
|
|
|
388
442
|
))}
|
|
389
443
|
</>
|
|
390
444
|
)}
|
|
445
|
+
{axisAngle && (
|
|
446
|
+
<>
|
|
447
|
+
<Row label=" Axis">
|
|
448
|
+
<div style={{display: 'flex', gap: 6}}>
|
|
449
|
+
{(['x', 'y', 'z'] as const).map((axis) => (
|
|
450
|
+
<NumberField
|
|
451
|
+
key={axis}
|
|
452
|
+
value={axisAngle.axis[axis]}
|
|
453
|
+
step={0.1}
|
|
454
|
+
onChange={(v) =>
|
|
455
|
+
binding.apply((s) => {
|
|
456
|
+
const next = axisAngle.axis.clone();
|
|
457
|
+
next[axis] = v;
|
|
458
|
+
s.startRotation = new AxisAngleGenerator(next, axisAngle.angle);
|
|
459
|
+
})
|
|
460
|
+
}
|
|
461
|
+
/>
|
|
462
|
+
))}
|
|
463
|
+
</div>
|
|
464
|
+
</Row>
|
|
465
|
+
<ValueField
|
|
466
|
+
label=" Angle (rad)"
|
|
467
|
+
generator={axisAngle.angle}
|
|
468
|
+
curveMax={Math.PI * 2}
|
|
469
|
+
onChange={(g) =>
|
|
470
|
+
binding.apply((s) => (s.startRotation = new AxisAngleGenerator(axisAngle.axis.clone(), g)))
|
|
471
|
+
}
|
|
472
|
+
/>
|
|
473
|
+
</>
|
|
474
|
+
)}
|
|
391
475
|
{rotationMode === 'angle' && (
|
|
392
476
|
<Row label="">
|
|
393
477
|
<NumberField
|
|
@@ -541,7 +625,7 @@ export function EmissionModule({binding}: ModuleProps) {
|
|
|
541
625
|
// Shapes whose constructor doesn't accept `mode`/`spread` at all — showing those controls
|
|
542
626
|
// for them would edit a value the shape silently ignores, just like the old "default to
|
|
543
627
|
// first texture" bug: a control that looks live but does nothing.
|
|
544
|
-
const SHAPES_WITHOUT_MODE_SPREAD = new Set<ShapeType>(['point', 'grid', 'mesh_surface']);
|
|
628
|
+
const SHAPES_WITHOUT_MODE_SPREAD = new Set<ShapeType>(['point', 'grid', 'box', 'mesh_surface']);
|
|
545
629
|
|
|
546
630
|
export function ShapeModule({binding}: ModuleProps) {
|
|
547
631
|
const system = binding.system;
|
|
@@ -589,6 +673,21 @@ export function ShapeModule({binding}: ModuleProps) {
|
|
|
589
673
|
/>
|
|
590
674
|
</Row>
|
|
591
675
|
)}
|
|
676
|
+
{type === 'box' && (
|
|
677
|
+
<Row label="Emit from">
|
|
678
|
+
<SelectField
|
|
679
|
+
value={params.emitFrom}
|
|
680
|
+
options={[
|
|
681
|
+
{value: BoxEmitFrom.Volume, label: 'Volume'},
|
|
682
|
+
{value: BoxEmitFrom.Shell, label: 'Shell (faces)'},
|
|
683
|
+
{value: BoxEmitFrom.Edge, label: 'Edges'},
|
|
684
|
+
]}
|
|
685
|
+
onChange={(emitFrom) =>
|
|
686
|
+
binding.apply((s) => (s.emitterShape = createShape(type, {...params, emitFrom})))
|
|
687
|
+
}
|
|
688
|
+
/>
|
|
689
|
+
</Row>
|
|
690
|
+
)}
|
|
592
691
|
{!SHAPES_WITHOUT_MODE_SPREAD.has(type) && (
|
|
593
692
|
<>
|
|
594
693
|
<Row label="Emit mode">
|
|
@@ -617,6 +716,12 @@ export function ShapeModule({binding}: ModuleProps) {
|
|
|
617
716
|
</Row>
|
|
618
717
|
</>
|
|
619
718
|
)}
|
|
719
|
+
<Row label="Align to direction">
|
|
720
|
+
<CheckboxField
|
|
721
|
+
value={binding.system.alignToDirection}
|
|
722
|
+
onChange={(v) => binding.apply((s) => (s.alignToDirection = v))}
|
|
723
|
+
/>
|
|
724
|
+
</Row>
|
|
620
725
|
</ModuleSection>
|
|
621
726
|
);
|
|
622
727
|
}
|
|
@@ -868,6 +973,37 @@ export function RendererModule({
|
|
|
868
973
|
}
|
|
869
974
|
/>
|
|
870
975
|
</Row>
|
|
976
|
+
{(
|
|
977
|
+
[
|
|
978
|
+
['recordWorldSpace', 'Record in world space'],
|
|
979
|
+
['uvFromHead', 'UV from head'],
|
|
980
|
+
['colorFromParticle', 'Color from particle'],
|
|
981
|
+
['dieWithParticle', 'Die with particle'],
|
|
982
|
+
] as const
|
|
983
|
+
).map(([key, label]) => (
|
|
984
|
+
<Row key={key} label={label}>
|
|
985
|
+
<CheckboxField
|
|
986
|
+
value={!!(system.rendererEmitterSettings as Record<string, unknown>)[key]}
|
|
987
|
+
onChange={(v) =>
|
|
988
|
+
binding.apply((s) => {
|
|
989
|
+
(s.rendererEmitterSettings as Record<string, unknown>)[key] = v;
|
|
990
|
+
})
|
|
991
|
+
}
|
|
992
|
+
/>
|
|
993
|
+
</Row>
|
|
994
|
+
))}
|
|
995
|
+
<Row label="Head width scale">
|
|
996
|
+
<NumberField
|
|
997
|
+
value={(system.rendererEmitterSettings as {headWidthScale?: number}).headWidthScale ?? 1}
|
|
998
|
+
min={0}
|
|
999
|
+
step={0.1}
|
|
1000
|
+
onChange={(v) =>
|
|
1001
|
+
binding.apply((s) => {
|
|
1002
|
+
(s.rendererEmitterSettings as {headWidthScale?: number}).headWidthScale = v;
|
|
1003
|
+
})
|
|
1004
|
+
}
|
|
1005
|
+
/>
|
|
1006
|
+
</Row>
|
|
871
1007
|
</>
|
|
872
1008
|
)}
|
|
873
1009
|
{system.renderMode === RenderMode.StretchedBillBoard && (
|
|
@@ -1204,6 +1340,124 @@ export function RendererModule({
|
|
|
1204
1340
|
onChange={(v) => binding.apply((s) => (s.renderOrder = Math.round(v)))}
|
|
1205
1341
|
/>
|
|
1206
1342
|
</Row>
|
|
1343
|
+
<Row label="Rendering group">
|
|
1344
|
+
<NumberField
|
|
1345
|
+
value={system.renderingGroupId}
|
|
1346
|
+
step={1}
|
|
1347
|
+
min={0}
|
|
1348
|
+
onChange={(v) => binding.apply((s) => (s.renderingGroupId = Math.max(0, Math.round(v))))}
|
|
1349
|
+
/>
|
|
1350
|
+
</Row>
|
|
1351
|
+
<Row label="Layer mask">
|
|
1352
|
+
<NumberField
|
|
1353
|
+
value={system.layers.mask}
|
|
1354
|
+
min={0}
|
|
1355
|
+
step={1}
|
|
1356
|
+
onChange={(v) => binding.apply((s) => (s.layers.mask = Math.max(0, Math.floor(v))))}
|
|
1357
|
+
/>
|
|
1358
|
+
</Row>
|
|
1359
|
+
<Row label="Max particles">
|
|
1360
|
+
<CheckboxField
|
|
1361
|
+
value={system.maxParticles !== undefined}
|
|
1362
|
+
onChange={(v) => binding.apply((s) => (s.maxParticles = v ? 1000 : undefined))}
|
|
1363
|
+
/>
|
|
1364
|
+
{system.maxParticles !== undefined && (
|
|
1365
|
+
<NumberField
|
|
1366
|
+
value={system.maxParticles}
|
|
1367
|
+
min={1}
|
|
1368
|
+
step={10}
|
|
1369
|
+
onChange={(v) => binding.apply((s) => (s.maxParticles = Math.max(1, Math.floor(v))))}
|
|
1370
|
+
/>
|
|
1371
|
+
)}
|
|
1372
|
+
</Row>
|
|
1373
|
+
{system.renderMode !== RenderMode.Mesh && system.renderMode !== RenderMode.Trail && (
|
|
1374
|
+
<Row label="Pivot">
|
|
1375
|
+
<div style={{display: 'flex', gap: 6}}>
|
|
1376
|
+
{(['x', 'y', 'z'] as const).map((axis) => (
|
|
1377
|
+
<NumberField
|
|
1378
|
+
key={axis}
|
|
1379
|
+
value={system.pivot?.[axis] ?? 0}
|
|
1380
|
+
step={0.05}
|
|
1381
|
+
onChange={(v) =>
|
|
1382
|
+
binding.apply((s) => {
|
|
1383
|
+
const next = (s.pivot ?? new Vector3()).clone();
|
|
1384
|
+
next[axis] = v;
|
|
1385
|
+
s.pivot = next;
|
|
1386
|
+
})
|
|
1387
|
+
}
|
|
1388
|
+
/>
|
|
1389
|
+
))}
|
|
1390
|
+
</div>
|
|
1391
|
+
</Row>
|
|
1392
|
+
)}
|
|
1393
|
+
{system.renderMode === RenderMode.Mesh && (
|
|
1394
|
+
<>
|
|
1395
|
+
<Row label="Env map atlas">
|
|
1396
|
+
<SelectField
|
|
1397
|
+
value={(system.getRendererSettings().reflectionAtlas as {url?: string} | null)?.url ?? ''}
|
|
1398
|
+
options={[
|
|
1399
|
+
{value: '', label: 'None'},
|
|
1400
|
+
...(textureOptions ?? []).map((option) => ({value: option.url, label: option.label})),
|
|
1401
|
+
]}
|
|
1402
|
+
onChange={(url) => patchMaterial({reflectionAtlas: url ? loadTexture(url) : null})}
|
|
1403
|
+
/>
|
|
1404
|
+
</Row>
|
|
1405
|
+
<Row label="Reflection level">
|
|
1406
|
+
<NumberField
|
|
1407
|
+
value={system.getRendererSettings().reflectionLevel ?? 1}
|
|
1408
|
+
min={0}
|
|
1409
|
+
step={0.1}
|
|
1410
|
+
onChange={(v) => patchMaterial({reflectionLevel: Math.max(0, v)})}
|
|
1411
|
+
/>
|
|
1412
|
+
</Row>
|
|
1413
|
+
</>
|
|
1414
|
+
)}
|
|
1415
|
+
<Row label="Sort mode">
|
|
1416
|
+
<SelectField
|
|
1417
|
+
value={system.sortMode}
|
|
1418
|
+
options={[
|
|
1419
|
+
{value: 'none', label: 'None'},
|
|
1420
|
+
{value: 'distance', label: 'By distance'},
|
|
1421
|
+
{value: 'oldestInFront', label: 'Oldest in front'},
|
|
1422
|
+
{value: 'youngestInFront', label: 'Youngest in front'},
|
|
1423
|
+
]}
|
|
1424
|
+
onChange={(mode) => binding.apply((s) => (s.sortMode = mode as SortMode))}
|
|
1425
|
+
/>
|
|
1426
|
+
</Row>
|
|
1427
|
+
<Row label="Limit screen size">
|
|
1428
|
+
<CheckboxField
|
|
1429
|
+
value={system.minParticleSize > 0 || system.maxParticleSize !== Infinity}
|
|
1430
|
+
onChange={(v) =>
|
|
1431
|
+
binding.apply((s) => {
|
|
1432
|
+
s.minParticleSize = 0;
|
|
1433
|
+
s.maxParticleSize = v ? 0.5 : Infinity;
|
|
1434
|
+
})
|
|
1435
|
+
}
|
|
1436
|
+
/>
|
|
1437
|
+
</Row>
|
|
1438
|
+
{(system.minParticleSize > 0 || system.maxParticleSize !== Infinity) && (
|
|
1439
|
+
<>
|
|
1440
|
+
<Row label="Min size (viewport)">
|
|
1441
|
+
<NumberField
|
|
1442
|
+
value={system.minParticleSize}
|
|
1443
|
+
step={0.01}
|
|
1444
|
+
min={0}
|
|
1445
|
+
onChange={(v) => binding.apply((s) => (s.minParticleSize = Math.max(0, v)))}
|
|
1446
|
+
/>
|
|
1447
|
+
</Row>
|
|
1448
|
+
<Row label="Max size (viewport)">
|
|
1449
|
+
<NumberField
|
|
1450
|
+
value={system.maxParticleSize === Infinity ? 0 : system.maxParticleSize}
|
|
1451
|
+
step={0.05}
|
|
1452
|
+
min={0}
|
|
1453
|
+
onChange={(v) => binding.apply((s) => (s.maxParticleSize = v > 0 ? v : Infinity))}
|
|
1454
|
+
/>
|
|
1455
|
+
</Row>
|
|
1456
|
+
</>
|
|
1457
|
+
)}
|
|
1458
|
+
<Row label="Apply fog">
|
|
1459
|
+
<CheckboxField value={system.applyFog} onChange={(v) => binding.apply((s) => (s.applyFog = v))} />
|
|
1460
|
+
</Row>
|
|
1207
1461
|
<Row label="Soft particles">
|
|
1208
1462
|
<CheckboxField
|
|
1209
1463
|
value={system.softParticles}
|