@@ -16,7 +16,7 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigge
1616import { HiOutlineTrash } from "react-icons/hi2" ;
1717import { 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" ;
2020import { FunctionEditor , ColorFunctionEditor } from "../editors" ;
2121
2222// Types
@@ -36,14 +36,19 @@ export interface IBehaviorProperty {
3636export 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)
4347export 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
460483interface 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 >
0 commit comments