Skip to content

Commit f54a4e7

Browse files
Mikalai Lazitskijulien-moreau
authored andcommitted
feat: enhance behavior system with new constants and refactor behavior application
- Introduced BEHAVIOR_TYPES constants for consistent behavior type management across the editor and runtime. - Updated the BehaviorRegistry to utilize these constants, improving maintainability and reducing potential errors. - Refactored behavior application logic in EffectParticleSystem and EffectSolidParticleSystem to streamline the application of system-level and per-particle behaviors. - Added a new behaviorApplier module to centralize behavior application logic, enhancing code organization and clarity. - Improved color handling in various behaviors to ensure colors are converted to linear space for accurate rendering.
1 parent 0838819 commit f54a4e7

13 files changed

Lines changed: 481 additions & 462 deletions

File tree

editor/src/editor/windows/effect-editor/properties/behaviors.tsx

Lines changed: 93 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigge
1616
import { HiOutlineTrash } from "react-icons/hi2";
1717
import { IoAddSharp } from "react-icons/io5";
1818

19-
import { type IEffectNode, EffectParticleSystem, EffectSolidParticleSystem } from "babylonjs-editor-tools";
19+
import { type IEffectNode, EffectParticleSystem, EffectSolidParticleSystem, BEHAVIOR_TYPES, type BehaviorKind, type Behavior } from "babylonjs-editor-tools";
2020
import { FunctionEditor, ColorFunctionEditor } from "../editors";
2121

2222
// Types
@@ -36,14 +36,19 @@ export interface IBehaviorProperty {
3636
export interface IBehaviorDefinition {
3737
type: string;
3838
label: string;
39+
kind?: BehaviorKind;
3940
properties: IBehaviorProperty[];
4041
}
4142

42-
// Behavior Registry
43+
/** Behavior config with optional editor-only id (for React keys). Runtime ignores id. */
44+
export type EditorBehavior = Behavior & { id?: string };
45+
46+
// Behavior Registry (keys from BEHAVIOR_TYPES; kind = system-level gradients vs per-particle)
4347
export const BehaviorRegistry: { [key: string]: IBehaviorDefinition } = {
44-
ApplyForce: {
45-
type: "ApplyForce",
48+
[BEHAVIOR_TYPES.ApplyForce]: {
49+
type: BEHAVIOR_TYPES.ApplyForce,
4650
label: "Apply Force",
51+
kind: "perParticle",
4752
properties: [
4853
{ name: "direction", type: "vector3", label: "Direction", default: { x: 0, y: 1, z: 0 } },
4954
{
@@ -55,9 +60,10 @@ export const BehaviorRegistry: { [key: string]: IBehaviorDefinition } = {
5560
},
5661
],
5762
},
58-
Noise: {
59-
type: "Noise",
63+
[BEHAVIOR_TYPES.Noise]: {
64+
type: BEHAVIOR_TYPES.Noise,
6065
label: "Noise",
66+
kind: "perParticle",
6167
properties: [
6268
{
6369
name: "frequency",
@@ -89,27 +95,30 @@ export const BehaviorRegistry: { [key: string]: IBehaviorDefinition } = {
8995
},
9096
],
9197
},
92-
TurbulenceField: {
93-
type: "TurbulenceField",
98+
[BEHAVIOR_TYPES.TurbulenceField]: {
99+
type: BEHAVIOR_TYPES.TurbulenceField,
94100
label: "Turbulence Field",
101+
kind: "perParticle",
95102
properties: [
96103
{ name: "scale", type: "vector3", label: "Scale", default: { x: 1, y: 1, z: 1 } },
97104
{ name: "octaves", type: "number", label: "Octaves", default: 1 },
98105
{ name: "velocityMultiplier", type: "vector3", label: "Velocity Multiplier", default: { x: 1, y: 1, z: 1 } },
99106
{ name: "timeScale", type: "vector3", label: "Time Scale", default: { x: 1, y: 1, z: 1 } },
100107
],
101108
},
102-
GravityForce: {
103-
type: "GravityForce",
109+
[BEHAVIOR_TYPES.GravityForce]: {
110+
type: BEHAVIOR_TYPES.GravityForce,
104111
label: "Gravity Force",
112+
kind: "perParticle",
105113
properties: [
106114
{ name: "center", type: "vector3", label: "Center", default: { x: 0, y: 0, z: 0 } },
107115
{ name: "magnitude", type: "number", label: "Magnitude", default: 1.0 },
108116
],
109117
},
110-
ColorOverLife: {
111-
type: "ColorOverLife",
118+
[BEHAVIOR_TYPES.ColorOverLife]: {
119+
type: BEHAVIOR_TYPES.ColorOverLife,
112120
label: "Color Over Life",
121+
kind: "system",
113122
properties: [
114123
{
115124
name: "color",
@@ -120,9 +129,10 @@ export const BehaviorRegistry: { [key: string]: IBehaviorDefinition } = {
120129
},
121130
],
122131
},
123-
RotationOverLife: {
124-
type: "RotationOverLife",
132+
[BEHAVIOR_TYPES.RotationOverLife]: {
133+
type: BEHAVIOR_TYPES.RotationOverLife,
125134
label: "Rotation Over Life",
135+
kind: "system",
126136
properties: [
127137
{
128138
name: "angularVelocity",
@@ -133,9 +143,10 @@ export const BehaviorRegistry: { [key: string]: IBehaviorDefinition } = {
133143
},
134144
],
135145
},
136-
Rotation3DOverLife: {
137-
type: "Rotation3DOverLife",
146+
[BEHAVIOR_TYPES.Rotation3DOverLife]: {
147+
type: BEHAVIOR_TYPES.Rotation3DOverLife,
138148
label: "Rotation 3D Over Life",
149+
kind: "system",
139150
properties: [
140151
{
141152
name: "angularVelocity",
@@ -146,9 +157,10 @@ export const BehaviorRegistry: { [key: string]: IBehaviorDefinition } = {
146157
},
147158
],
148159
},
149-
SizeOverLife: {
150-
type: "SizeOverLife",
160+
[BEHAVIOR_TYPES.SizeOverLife]: {
161+
type: BEHAVIOR_TYPES.SizeOverLife,
151162
label: "Size Over Life",
163+
kind: "system",
152164
properties: [
153165
{
154166
name: "size",
@@ -159,9 +171,10 @@ export const BehaviorRegistry: { [key: string]: IBehaviorDefinition } = {
159171
},
160172
],
161173
},
162-
ColorBySpeed: {
163-
type: "ColorBySpeed",
174+
[BEHAVIOR_TYPES.ColorBySpeed]: {
175+
type: BEHAVIOR_TYPES.ColorBySpeed,
164176
label: "Color By Speed",
177+
kind: "perParticle",
165178
properties: [
166179
{
167180
name: "color",
@@ -173,9 +186,10 @@ export const BehaviorRegistry: { [key: string]: IBehaviorDefinition } = {
173186
{ name: "speedRange", type: "range", label: "Speed Range", default: { min: 0, max: 10 } },
174187
],
175188
},
176-
RotationBySpeed: {
177-
type: "RotationBySpeed",
189+
[BEHAVIOR_TYPES.RotationBySpeed]: {
190+
type: BEHAVIOR_TYPES.RotationBySpeed,
178191
label: "Rotation By Speed",
192+
kind: "perParticle",
179193
properties: [
180194
{
181195
name: "angularVelocity",
@@ -187,9 +201,10 @@ export const BehaviorRegistry: { [key: string]: IBehaviorDefinition } = {
187201
{ name: "speedRange", type: "range", label: "Speed Range", default: { min: 0, max: 10 } },
188202
],
189203
},
190-
SizeBySpeed: {
191-
type: "SizeBySpeed",
204+
[BEHAVIOR_TYPES.SizeBySpeed]: {
205+
type: BEHAVIOR_TYPES.SizeBySpeed,
192206
label: "Size By Speed",
207+
kind: "perParticle",
193208
properties: [
194209
{
195210
name: "size",
@@ -201,9 +216,10 @@ export const BehaviorRegistry: { [key: string]: IBehaviorDefinition } = {
201216
{ name: "speedRange", type: "range", label: "Speed Range", default: { min: 0, max: 10 } },
202217
],
203218
},
204-
SpeedOverLife: {
205-
type: "SpeedOverLife",
219+
[BEHAVIOR_TYPES.SpeedOverLife]: {
220+
type: BEHAVIOR_TYPES.SpeedOverLife,
206221
label: "Speed Over Life",
222+
kind: "system",
207223
properties: [
208224
{
209225
name: "speed",
@@ -214,9 +230,10 @@ export const BehaviorRegistry: { [key: string]: IBehaviorDefinition } = {
214230
},
215231
],
216232
},
217-
FrameOverLife: {
218-
type: "FrameOverLife",
233+
[BEHAVIOR_TYPES.FrameOverLife]: {
234+
type: BEHAVIOR_TYPES.FrameOverLife,
219235
label: "Frame Over Life",
236+
kind: "system",
220237
properties: [
221238
{
222239
name: "frame",
@@ -227,9 +244,10 @@ export const BehaviorRegistry: { [key: string]: IBehaviorDefinition } = {
227244
},
228245
],
229246
},
230-
ForceOverLife: {
231-
type: "ForceOverLife",
247+
[BEHAVIOR_TYPES.ForceOverLife]: {
248+
type: BEHAVIOR_TYPES.ForceOverLife,
232249
label: "Force Over Life",
250+
kind: "perParticle",
233251
properties: [
234252
{
235253
name: "x",
@@ -254,9 +272,10 @@ export const BehaviorRegistry: { [key: string]: IBehaviorDefinition } = {
254272
},
255273
],
256274
},
257-
OrbitOverLife: {
258-
type: "OrbitOverLife",
275+
[BEHAVIOR_TYPES.OrbitOverLife]: {
276+
type: BEHAVIOR_TYPES.OrbitOverLife,
259277
label: "Orbit Over Life",
278+
kind: "perParticle",
260279
properties: [
261280
{
262281
name: "orbitSpeed",
@@ -268,9 +287,10 @@ export const BehaviorRegistry: { [key: string]: IBehaviorDefinition } = {
268287
{ name: "axis", type: "vector3", label: "Axis", default: { x: 0, y: 1, z: 0 } },
269288
],
270289
},
271-
WidthOverLength: {
272-
type: "WidthOverLength",
290+
[BEHAVIOR_TYPES.WidthOverLength]: {
291+
type: BEHAVIOR_TYPES.WidthOverLength,
273292
label: "Width Over Length",
293+
kind: "perParticle",
274294
properties: [
275295
{
276296
name: "width",
@@ -281,9 +301,10 @@ export const BehaviorRegistry: { [key: string]: IBehaviorDefinition } = {
281301
},
282302
],
283303
},
284-
ChangeEmitDirection: {
285-
type: "ChangeEmitDirection",
304+
[BEHAVIOR_TYPES.ChangeEmitDirection]: {
305+
type: BEHAVIOR_TYPES.ChangeEmitDirection,
286306
label: "Change Emit Direction",
307+
kind: "perParticle",
287308
properties: [
288309
{
289310
name: "angle",
@@ -294,9 +315,10 @@ export const BehaviorRegistry: { [key: string]: IBehaviorDefinition } = {
294315
},
295316
],
296317
},
297-
EmitSubParticleSystem: {
298-
type: "EmitSubParticleSystem",
318+
[BEHAVIOR_TYPES.EmitSubParticleSystem]: {
319+
type: BEHAVIOR_TYPES.EmitSubParticleSystem,
299320
label: "Emit Sub Particle System",
321+
kind: "perParticle",
300322
properties: [
301323
{ name: "subParticleSystem", type: "string", label: "Sub Particle System", default: "" },
302324
{ name: "useVelocityAsBasis", type: "boolean", label: "Use Velocity As Basis", default: false },
@@ -314,9 +336,10 @@ export const BehaviorRegistry: { [key: string]: IBehaviorDefinition } = {
314336
{ name: "emitProbability", type: "number", label: "Emit Probability", default: 1.0 },
315337
],
316338
},
317-
LimitSpeedOverLife: {
318-
type: "LimitSpeedOverLife",
339+
[BEHAVIOR_TYPES.LimitSpeedOverLife]: {
340+
type: BEHAVIOR_TYPES.LimitSpeedOverLife,
319341
label: "Limit Speed Over Life",
342+
kind: "system",
320343
properties: [
321344
{
322345
name: "speed",
@@ -335,25 +358,25 @@ export function getBehaviorDefinition(type: string): IBehaviorDefinition | undef
335358
return BehaviorRegistry[type];
336359
}
337360

338-
export function createDefaultBehaviorData(type: string): any {
361+
/** Creates a minimal behavior config for the given type; returned object may be extended with editor-only fields (e.g. id). */
362+
export function createDefaultBehaviorData(type: string): Behavior {
339363
const definition = BehaviorRegistry[type];
340364
if (!definition) {
341365
return { type };
342366
}
343367

344-
const data: any = { type };
368+
const data: Record<string, unknown> = { type };
345369
for (const prop of definition.properties) {
346370
if (prop.type === "function") {
347-
data[prop.name] = {
348-
functionType: prop.functionTypes?.[0] || "ConstantValue",
349-
data: {},
350-
};
351-
if (data[prop.name].functionType === "ConstantValue") {
352-
data[prop.name].data.value = prop.default !== undefined ? prop.default : 1.0;
353-
} else if (data[prop.name].functionType === "IntervalValue") {
354-
data[prop.name].data.min = 0;
355-
data[prop.name].data.max = 1;
371+
const fnData: Record<string, unknown> = {};
372+
const fnType = prop.functionTypes?.[0] || "ConstantValue";
373+
if (fnType === "ConstantValue") {
374+
fnData.value = prop.default !== undefined ? prop.default : 1.0;
375+
} else if (fnType === "IntervalValue") {
376+
fnData.min = 0;
377+
fnData.max = 1;
356378
}
379+
data[prop.name] = { functionType: fnType, data: fnData };
357380
} else if (prop.type === "colorFunction") {
358381
data[prop.name] = {
359382
colorFunctionType: prop.colorFunctionTypes?.[0] || "ConstantColor",
@@ -369,11 +392,11 @@ export function createDefaultBehaviorData(type: string): any {
369392
}
370393
}
371394
}
372-
return data;
395+
return data as Behavior;
373396
}
374397

375-
// Helper function to render a single property
376-
function renderProperty(prop: IBehaviorProperty, behavior: any, onChange: () => void): ReactNode {
398+
// Helper function to render a single property (behavior may be mutated with Vector3/Color4 for inspector)
399+
function renderProperty(prop: IBehaviorProperty, behavior: Behavior, onChange: () => void): ReactNode {
377400
switch (prop.type) {
378401
case "vector3":
379402
if (!behavior[prop.name]) {
@@ -458,7 +481,7 @@ function renderProperty(prop: IBehaviorProperty, behavior: any, onChange: () =>
458481

459482
// Component to render behavior properties
460483
interface IBehaviorPropertiesProps {
461-
behavior: any;
484+
behavior: Behavior;
462485
onChange: () => void;
463486
}
464487

@@ -487,22 +510,30 @@ export function EffectEditorBehaviorsProperties(props: IEffectEditorBehaviorsPro
487510
}
488511

489512
const system = nodeData.data;
490-
const behaviorConfigs: any[] = system instanceof EffectParticleSystem || system instanceof EffectSolidParticleSystem ? system.behaviorConfigs || [] : [];
513+
if (!(system instanceof EffectParticleSystem || system instanceof EffectSolidParticleSystem)) {
514+
return null;
515+
}
516+
517+
const behaviorConfigs: EditorBehavior[] = system.behaviorConfigs ?? [];
518+
519+
const applyBehaviors = (): void => {
520+
system.setBehaviors(behaviorConfigs);
521+
onChange();
522+
};
491523

492524
const handleAddBehavior = (behaviorType: string): void => {
493-
const newBehavior = createDefaultBehaviorData(behaviorType);
494-
newBehavior.id = `behavior-${Date.now()}-${Math.random()}`;
525+
const newBehavior: EditorBehavior = { ...createDefaultBehaviorData(behaviorType), id: `behavior-${Date.now()}-${Math.random()}` };
495526
behaviorConfigs.push(newBehavior);
496-
onChange();
527+
applyBehaviors();
497528
};
498529

499530
const handleRemoveBehavior = (index: number): void => {
500531
behaviorConfigs.splice(index, 1);
501-
onChange();
532+
applyBehaviors();
502533
};
503534

504535
const handleBehaviorChange = (): void => {
505-
onChange();
536+
applyBehaviors();
506537
};
507538

508539
return (
@@ -514,7 +545,7 @@ export function EffectEditorBehaviorsProperties(props: IEffectEditorBehaviorsPro
514545

515546
return (
516547
<EditorInspectorSectionField
517-
key={behavior.id || `behavior-${index}`}
548+
key={behavior.id ?? `behavior-${index}`}
518549
title={
519550
<div className="flex items-center justify-between w-full">
520551
<span>{title}</span>

editor/src/editor/windows/effect-editor/properties/initialization.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ export function EffectEditorParticleInitializationProperties(props: IEffectEdito
8181

8282
const setStartColor = (value: Color): void => {
8383
const color = parseConstantColor(value);
84+
// Convert to linear space for PBR material with unlit
85+
color.toLinearSpaceToRef(color);
8486
(system as any).color1 = color;
8587
onChange();
8688
};

0 commit comments

Comments
 (0)