Ver código fonte

More feature for particle editor

David Catuhe 5 anos atrás
pai
commit
01259548d6

+ 45 - 1
inspector/src/components/actionTabs/actionTabs.scss

@@ -213,6 +213,48 @@ $line-padding-left: 2px;
                     }
                 }
 
+                .linkButtonLine {
+                    padding-left: $line-padding-left;
+                    height: 30px;
+                    display: grid;
+                    grid-template-columns: 1fr auto;
+
+                    .link {
+                        grid-column: 1;
+                        display: flex;
+                        align-items: center;
+                        text-decoration: underline;
+                        cursor: pointer;
+                    }
+
+                    .button {
+                        grid-column: 2;
+
+                        button {
+                            background: #222222;
+                            border: 1px solid rgb(51, 122, 183);
+                            margin: 5px 10px 5px 10px;
+                            color:white;
+                            padding: 4px 5px;
+                            opacity: 0.9;
+                            cursor: pointer;
+                        }
+    
+                        button:hover {
+                            opacity: 1.0;
+                        }
+    
+                        button:active {
+                            background: #282828;
+                        }   
+                        
+                        button:focus {
+                            border: 1px solid rgb(51, 122, 183);
+                            outline: 0px;
+                        } 
+                    }
+                }
+
                 .textLine {
                     padding-left: $line-padding-left;
                     height: 30px;
@@ -276,11 +318,12 @@ $line-padding-left: 2px;
                     .gradient-step {
                         display: grid;
                         grid-template-rows: 100%;
-                        grid-template-columns: 30px 50px 55px 40px auto 20px 5px;
+                        grid-template-columns: 25px 50px 55px 40px auto 20px 5px;
                         padding-top: 5px;
                         padding-left: 5px;
                         padding-bottom: 5px;    
                         align-items: center;
+                        border-left: orange 3px solid;
                 
                         .step {
                             grid-row: 1;
@@ -400,6 +443,7 @@ $line-padding-left: 2px;
                         color:white;
                         padding: 4px 5px;
                         opacity: 0.9;
+                        cursor: pointer;
                     }
 
                     button:hover {

+ 33 - 0
inspector/src/components/actionTabs/lines/linkButtonComponent.tsx

@@ -0,0 +1,33 @@
+import * as React from "react";
+
+interface ILinkButtonComponentProps {
+    label: string;
+    buttonLabel: string;
+    url?: string;
+    onClick: () => void;
+}
+
+export class LinkButtonComponent extends React.Component<ILinkButtonComponentProps> {
+    constructor(props: ILinkButtonComponentProps) {
+        super(props);
+    }
+
+    onLink() {
+        if (this.props.url) {
+            window.open(this.props.url, '_blank');
+        }
+    }
+
+    render() {
+        return (
+            <div className={"linkButtonLine"}>
+                <div className="link" title={this.props.label} onClick={() => this.onLink()}>
+                    {this.props.label}
+                </div>
+                <div className="button">
+                    <button onClick={() => this.props.onClick()}>{this.props.buttonLabel}</button>
+                </div> 
+            </div>
+        );
+    }
+}

+ 6 - 5
inspector/src/components/actionTabs/lines/textLineComponent.tsx

@@ -16,6 +16,10 @@ export class TextLineComponent extends React.Component<ITextLineComponentProps>
     }
 
     onLink() {
+        if (this.props.url) {
+            window.open(this.props.url, '_blank');
+            return;
+        }
         if (!this.props.onLink) {
             return;
         }
@@ -28,13 +32,10 @@ export class TextLineComponent extends React.Component<ITextLineComponentProps>
             return null;
         }
 
-        if (this.props.url) {
-            window.open(this.props.url, '_blank');
-            return null;
-        } else if (this.props.onLink) {
+        if (this.props.onLink || this.props.url) {
             return (
                 <div className="link-value" title={this.props.value} onClick={() => this.onLink()}>
-                    {this.props.value || "no name"}
+                    {this.props.url ? "doc" : (this.props.value || "no name")}
                 </div>
             )
         }

+ 135 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/particleSystems/colorGradientStepGridComponent.tsx

@@ -0,0 +1,135 @@
+import * as React from 'react';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faTrash } from '@fortawesome/free-solid-svg-icons';
+import { GlobalState } from '../../../../globalState';
+import { ColorGradient, Color3Gradient } from 'babylonjs/Misc/gradients';
+import { LockObject } from '../lockObject';
+import { Color3, Color4 } from 'babylonjs/Maths/math.color';
+import { IParticleSystem } from 'babylonjs/Particles/IParticleSystem';
+
+interface IColorGradientStepGridComponent {
+    globalState: GlobalState;
+    gradient: ColorGradient | Color3Gradient;
+    lockObject: LockObject;
+    lineIndex: number;
+    isColor3: boolean;
+    onDelete: () => void;
+    onUpdateGradient: () => void;
+    onCheckForReOrder: () => void;
+    host: IParticleSystem,
+    codeRecorderPropertyName: string,
+}
+
+export class ColorGradientStepGridComponent extends React.Component<IColorGradientStepGridComponent, {gradient: number}> {
+
+    constructor(props: IColorGradientStepGridComponent) {
+        super(props);
+
+        this.state={gradient: props.gradient.gradient};
+    }
+
+    updateColor1(color: string) {
+        if (this.props.gradient instanceof ColorGradient) {
+            this.props.gradient.color1 = Color4.FromColor3(Color3.FromHexString(color));
+            
+            this.props.globalState.onCodeChangedObservable.notifyObservers({
+                object: this.props.host,
+                code: `TARGET.${this.props.codeRecorderPropertyName}.color1 = BABYLON.Color4.FromColor3(BABYLON.Color3.FromHexString(${color}));`
+            });              
+        } else {
+            this.props.gradient.color = Color3.FromHexString(color);
+
+            this.props.globalState.onCodeChangedObservable.notifyObservers({
+                object: this.props.host,
+                code: `TARGET.${this.props.codeRecorderPropertyName}.color = BABYLON.Color3.FromHexString(${color});`
+            });              
+        }
+
+        this.props.onUpdateGradient();
+        this.forceUpdate();
+    }    
+
+    updateColor2(color: string) {
+        if (this.props.gradient instanceof ColorGradient) {
+            this.props.gradient.color2 = Color4.FromColor3(Color3.FromHexString(color));
+
+            this.props.globalState.onCodeChangedObservable.notifyObservers({
+                object: this.props.host,
+                code: `TARGET.${this.props.codeRecorderPropertyName}.color2 = BABYLON.Color4.FromColor3(BABYLON.Color3.FromHexString(${color}));`
+            });              
+        }
+
+        this.props.onUpdateGradient();
+        this.forceUpdate();
+    }   
+    
+    updateGradient(gradient: number) {
+        this.props.gradient.gradient = gradient;
+
+        this.setState({gradient: gradient});
+
+        this.props.globalState.onCodeChangedObservable.notifyObservers({
+            object: this.props.host,
+            code: `TARGET.${this.props.codeRecorderPropertyName}.gradient = ${gradient};`
+        });         
+
+        this.props.onUpdateGradient();
+    }
+
+    onPointerUp() {
+        this.props.onCheckForReOrder();
+    }
+
+    lock() {
+        if (this.props.lockObject) {
+            this.props.lockObject.lock = true;
+        }
+    }
+
+    unlock() {
+        if (this.props.lockObject) {
+            this.props.lockObject.lock = false;
+        }
+    }
+
+    render() {
+        let gradient = this.props.gradient;
+
+        return (
+            <div className="gradient-step">
+                <div className="step">
+                    {`#${this.props.lineIndex}`}
+                </div>
+                {
+                    gradient instanceof ColorGradient &&
+                   <div className="color1">
+                        <input type="color" value={gradient.color1.toHexString(true)} onChange={(evt) => this.updateColor1(evt.target.value)} />
+                    </div>
+                }
+                {
+                    gradient instanceof Color3Gradient &&
+                   <div className="color1">
+                        <input type="color" value={gradient.color.toHexString()} onChange={(evt) => this.updateColor1(evt.target.value)} />
+                    </div>
+                }
+                {
+                    gradient instanceof ColorGradient &&
+                    <div className="color1">
+                        <input type="color" value={gradient.color2 ? gradient.color2.toHexString(true) : "#000000"} onChange={(evt) => this.updateColor2(evt.target.value)} />
+                    </div>
+                }
+                <div className="step-value">
+                    {gradient.gradient.toFixed(2)}
+                </div>
+                <div className="step-slider">
+                    <input className="range" type="range" step={0.01} min={0} max={1.0} value={gradient.gradient}
+                        onPointerUp={evt => this.onPointerUp()}
+                        onChange={evt => this.updateGradient(parseFloat(evt.target.value))} />
+                </div>
+                <div className="gradient-delete" onClick={() => this.props.onDelete()}>
+                    <FontAwesomeIcon icon={faTrash} />
+                </div>
+            </div>
+        )
+    }
+}

+ 0 - 103
inspector/src/components/actionTabs/tabs/propertyGrids/particleSystems/factorGradientGridComponent.tsx

@@ -1,103 +0,0 @@
-import * as React from "react";
-import { Observable } from 'babylonjs/Misc/observable';
-import { PropertyChangedEvent } from '../../../../propertyChangedEvent';
-import { GlobalState } from '../../../../globalState';
-import { FactorGradient } from 'babylonjs/Misc/gradients';
-import { LockObject } from '../lockObject';
-import { ButtonLineComponent } from '../../../lines/buttonLineComponent';
-import { FactorGradientStepGridComponent } from './factorGradientStepGridComponent';
-import { Nullable } from 'babylonjs/types';
-import { TextLineComponent } from '../../../lines/textLineComponent';
-
-interface IFactorGradientGridComponent {
-    globalState: GlobalState;
-    label: string;
-    gradients: Nullable<Array<FactorGradient>>,
-    lockObject: LockObject,
-    docLink?: string,
-    replaySourceReplacement?: string,
-    onCreateRequired: () => void,
-    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
-}
-
-export class FactorGradientGridComponent extends React.Component<IFactorGradientGridComponent> {
-
-    constructor(props: IFactorGradientGridComponent) {
-        super(props)
-    }
-
-    deleteStep(step: FactorGradient) {
-        let gradients = this.props.gradients as Array<FactorGradient>;
-
-        let index = gradients.indexOf(step);
-
-        if (index > -1) {
-            gradients.splice(index, 1);
-            this.forceUpdate();
-        }
-    }
-
-    addNewStep() {
-        let gradients = this.props.gradients as Array<FactorGradient>;
-
-        let newStep = new FactorGradient();
-        newStep.gradient = 1.0;
-        newStep.factor1 = 1.0;
-        newStep.factor2 = 1.0;
-
-        gradients.push(newStep);
-
-        this.forceUpdate();
-    }
-
-    checkForReOrder() {
-        let gradients = this.props.gradients as Array<FactorGradient>;
-        gradients.sort((a, b) => {
-            if (a.gradient === b.gradient) {
-                return 0;
-            }
-
-            if (a.gradient > b.gradient) {
-                return 1;
-            }
-
-            return -1;
-        });
-
-        this.forceUpdate();
-    }
-
-    render() {
-        let gradients = this.props.gradients as Nullable<Array<FactorGradient>>;
-      
-        return (
-            <div>
-                {
-                    gradients &&
-                    <div className="gradient-container">
-                        <TextLineComponent label={this.props.label} url={this.props.docLink}/>
-                        <ButtonLineComponent label="Add new step" onClick={() => this.addNewStep()} />
-                        {
-                            gradients.map((g, i) => {
-                                return (
-                                    <FactorGradientStepGridComponent globalState={this.props.globalState} 
-                                    lockObject={this.props.lockObject}
-                                    onCheckForReOrder={() => this.checkForReOrder()}
-                                    onUpdateGradient={() => this.forceUpdate()}
-                                    key={"step-" + i} lineIndex={i} gradient={g} onDelete={() => this.deleteStep(g)}/>
-                                )
-                            })
-                        }
-                    </div>
-                }
-                {
-                    !gradients &&                    
-                    <ButtonLineComponent label={"Use " + this.props.label} onClick={() => {
-                        this.props.onCreateRequired();
-                        this.forceUpdate();
-                    }} />
-                }
-            </div>
-        );
-    }
-}

+ 18 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/particleSystems/factorGradientStepGridComponent.tsx

@@ -4,6 +4,7 @@ import { faTrash } from '@fortawesome/free-solid-svg-icons';
 import { GlobalState } from '../../../../globalState';
 import { FactorGradient } from 'babylonjs/Misc/gradients';
 import { LockObject } from '../lockObject';
+import { IParticleSystem } from 'babylonjs/Particles/IParticleSystem';
 
 interface IFactorGradientStepGridComponent {
     globalState: GlobalState;
@@ -13,6 +14,8 @@ interface IFactorGradientStepGridComponent {
     onDelete: () => void;
     onUpdateGradient: () => void;
     onCheckForReOrder: () => void;
+    host: IParticleSystem,
+    codeRecorderPropertyName: string,
 }
 
 export class FactorGradientStepGridComponent extends React.Component<IFactorGradientStepGridComponent, {gradient: number}> {
@@ -26,6 +29,11 @@ export class FactorGradientStepGridComponent extends React.Component<IFactorGrad
     updateFactor1(factor: number) {
         this.props.gradient.factor1 = factor;
 
+        this.props.globalState.onCodeChangedObservable.notifyObservers({
+            object: this.props.host,
+            code: `TARGET.${this.props.codeRecorderPropertyName}.factor1 = ${factor};`
+        });                 
+
         this.props.onUpdateGradient();
         this.forceUpdate();
     }    
@@ -33,6 +41,11 @@ export class FactorGradientStepGridComponent extends React.Component<IFactorGrad
     updateFactor2(factor: number) {
         this.props.gradient.factor2 = factor;
 
+        this.props.globalState.onCodeChangedObservable.notifyObservers({
+            object: this.props.host,
+            code: `TARGET.${this.props.codeRecorderPropertyName}.factor2 = ${factor};`
+        });         
+
         this.props.onUpdateGradient();
         this.forceUpdate();
     }   
@@ -42,6 +55,11 @@ export class FactorGradientStepGridComponent extends React.Component<IFactorGrad
 
         this.setState({gradient: gradient});
 
+        this.props.globalState.onCodeChangedObservable.notifyObservers({
+            object: this.props.host,
+            code: `TARGET.${this.props.codeRecorderPropertyName}.gradient = ${gradient};`
+        });         
+
         this.props.onUpdateGradient();
     }
 

+ 128 - 25
inspector/src/components/actionTabs/tabs/propertyGrids/particleSystems/particleSystemPropertyGridComponent.tsx

@@ -34,7 +34,8 @@ import { Vector3 } from 'babylonjs/Maths/math.vector';
 import { AbstractMesh } from 'babylonjs/Meshes/abstractMesh';
 import { MeshParticleEmitter } from 'babylonjs/Particles/EmitterTypes/meshParticleEmitter';
 import { MeshEmitterGridComponent } from './meshEmitterGridComponent';
-import { FactorGradientGridComponent } from './factorGradientGridComponent';
+import { ValueGradientGridComponent, GradientGridMode } from './valueGradientGridComponent';
+import { Color3, Color4 } from 'babylonjs/Maths/math.color';
 
 interface IParticleSystemPropertyGridComponentProps {
     globalState: GlobalState;
@@ -284,36 +285,64 @@ export class ParticleSystemPropertyGridComponent extends React.Component<IPartic
                 </LineContainerComponent>       
                 <LineContainerComponent globalState={this.props.globalState} title="EMISSION">
                     <FloatLineComponent lockObject={this.props.lockObject} label="Rate" target={system} propertyName="emitRate" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                    <FactorGradientGridComponent globalState={this.props.globalState} gradients={system.getEmitRateGradients()!} 
+                    <ValueGradientGridComponent globalState={this.props.globalState} gradients={system.getEmitRateGradients()!} 
                         label="Velocity gradients"                        
                         docLink="https://doc.babylonjs.com/babylon101/particles#emit-rate-over-time"
                         onCreateRequired={() => {
                             system.addEmitRateGradient(0, 50, 50);
+                            this.props.globalState.onCodeChangedObservable.notifyObservers({
+                                object: system,
+                                code: `TARGET.addEmitRateGradient(0, 50, 50);`
+                            });
                         }}
-                        lockObject={this.props.lockObject} onPropertyChangedObservable={this.props.onPropertyChangedObservable}/>                  
+                        mode={GradientGridMode.Factor}
+                        host={system}    
+                        codeRecorderPropertyName="getEmitRateGradients()"
+                        lockObject={this.props.lockObject}/>                  
                     <FloatLineComponent lockObject={this.props.lockObject} label="Min emit power" target={system} propertyName="minEmitPower" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     <FloatLineComponent lockObject={this.props.lockObject} label="Max emit power" target={system} propertyName="maxEmitPower" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />               
-                    <FactorGradientGridComponent globalState={this.props.globalState} gradients={system.getVelocityGradients()!} 
+                    <ValueGradientGridComponent globalState={this.props.globalState} gradients={system.getVelocityGradients()!} 
                         label="Velocity gradients"                        
                         docLink="https://doc.babylonjs.com/babylon101/particles#velocity-over-time"
                         onCreateRequired={() => {
                             system.addVelocityGradient(0, 0.1, 0.1);
-                        }}
-                        lockObject={this.props.lockObject} onPropertyChangedObservable={this.props.onPropertyChangedObservable}/>
-                    <FactorGradientGridComponent globalState={this.props.globalState} gradients={system.getLimitVelocityGradients()!} 
+                            this.props.globalState.onCodeChangedObservable.notifyObservers({
+                                object: system,
+                                code: `TARGET.addVelocityGradient(0, 0.1, 0.1);`
+                            });                            
+                        }}                        
+                        mode={GradientGridMode.Factor}
+                        host={system}    
+                        codeRecorderPropertyName="getVelocityGradients()"
+                        lockObject={this.props.lockObject}/>
+                    <ValueGradientGridComponent globalState={this.props.globalState} gradients={system.getLimitVelocityGradients()!} 
                         label="Limit velocity gradients"
                         docLink="https://doc.babylonjs.com/babylon101/particles#limit-velocity-over-time"
                         onCreateRequired={() => {
                             system.addLimitVelocityGradient(0, 0.1, 0.1);
-                        }}
-                        lockObject={this.props.lockObject} onPropertyChangedObservable={this.props.onPropertyChangedObservable}/>
-                    <FactorGradientGridComponent globalState={this.props.globalState} gradients={system.getDragGradients()!} 
+                            this.props.globalState.onCodeChangedObservable.notifyObservers({
+                                object: system,
+                                code: `TARGET.addLimitVelocityGradient(0, 0.1, 0.1);`
+                            });                             
+                        }}           
+                        mode={GradientGridMode.Factor}
+                        host={system}    
+                        codeRecorderPropertyName="getLimitVelocityGradients()"                        
+                        lockObject={this.props.lockObject}/>
+                    <ValueGradientGridComponent globalState={this.props.globalState} gradients={system.getDragGradients()!} 
                         label="Drag gradients"                        
                         docLink="https://doc.babylonjs.com/babylon101/particles#drag-factor"
                         onCreateRequired={() => {
                             system.addDragGradient(0, 0.1, 0.1);
+                            this.props.globalState.onCodeChangedObservable.notifyObservers({
+                                object: system,
+                                code: `TARGET.addDragGradient(0, 0.1, 0.1);`
+                            });                                
                         }}
-                        lockObject={this.props.lockObject} onPropertyChangedObservable={this.props.onPropertyChangedObservable}/>
+                        host={system}    
+                        codeRecorderPropertyName="getDragGradients()"                         
+                        mode={GradientGridMode.Factor}                        
+                        lockObject={this.props.lockObject}/>
                 </LineContainerComponent>                  
                 <LineContainerComponent globalState={this.props.globalState} title="SIZE">
                     <FloatLineComponent lockObject={this.props.lockObject} label="Min size" target={system} propertyName="minSize" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
@@ -322,32 +351,54 @@ export class ParticleSystemPropertyGridComponent extends React.Component<IPartic
                     <FloatLineComponent lockObject={this.props.lockObject} label="Max scale X" target={system} propertyName="maxScaleX" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     <FloatLineComponent lockObject={this.props.lockObject} label="Min scale Y" target={system} propertyName="minScaleY" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     <FloatLineComponent lockObject={this.props.lockObject} label="Max scale Y" target={system} propertyName="maxScaleY" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                    <FactorGradientGridComponent globalState={this.props.globalState} gradients={system.getStartSizeGradients()!} 
+                    <ValueGradientGridComponent globalState={this.props.globalState} gradients={system.getStartSizeGradients()!} 
                         label="Start size gradients"
                         docLink="https://doc.babylonjs.com/babylon101/particles#start-size-over-time"
                         onCreateRequired={() => {
                             system.addStartSizeGradient(0, 1, 1);
-                        }}
-                        lockObject={this.props.lockObject} onPropertyChangedObservable={this.props.onPropertyChangedObservable}/>                                    
+                            this.props.globalState.onCodeChangedObservable.notifyObservers({
+                                object: system,
+                                code: `TARGET.addStartSizeGradient(0, 1, 1);`
+                            });                                
+                        }}       
+                        host={system}    
+                        codeRecorderPropertyName="getStartSizeGradients()"
+                        mode={GradientGridMode.Factor}
+                        lockObject={this.props.lockObject}/>                                    
                
-                    <FactorGradientGridComponent globalState={this.props.globalState} gradients={system.getSizeGradients()!} 
+                    <ValueGradientGridComponent globalState={this.props.globalState} gradients={system.getSizeGradients()!} 
                         label="Size gradients"
                         docLink="https://doc.babylonjs.com/babylon101/particles#size"
                         onCreateRequired={() => {
                             system.addSizeGradient(0, 1, 1);
+                            this.props.globalState.onCodeChangedObservable.notifyObservers({
+                                object: system,
+                                code: `TARGET.addSizeGradient(0, 1, 1);`
+                            });                             
                         }}
-                        lockObject={this.props.lockObject} onPropertyChangedObservable={this.props.onPropertyChangedObservable}/>
+                        host={system}    
+                        codeRecorderPropertyName="getSizeGradients()"                            
+                        mode={GradientGridMode.Factor}                        
+                        lockObject={this.props.lockObject}/>
                 </LineContainerComponent>          
                 <LineContainerComponent globalState={this.props.globalState} title="LIFETIME">
                     <FloatLineComponent lockObject={this.props.lockObject} label="Min lifetime" target={system} propertyName="minLifeTime" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     <FloatLineComponent lockObject={this.props.lockObject} label="Max lifetime" target={system} propertyName="maxLifeTime" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                    <FactorGradientGridComponent globalState={this.props.globalState} gradients={system.getLifeTimeGradients()!} 
+                    <FloatLineComponent lockObject={this.props.lockObject} label="Target stop duration" target={system} propertyName="targetStopDuration" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <ValueGradientGridComponent globalState={this.props.globalState} gradients={system.getLifeTimeGradients()!} 
                         label="Lifetime gradients"
                         docLink="https://doc.babylonjs.com/babylon101/particles#lifetime"
                         onCreateRequired={() => {
                             system.addLifeTimeGradient(0, 1, 1);
+                            this.props.globalState.onCodeChangedObservable.notifyObservers({
+                                object: system,
+                                code: `TARGET.addLifeTimeGradient(0, 1, 1);`
+                            });                               
                         }}
-                        lockObject={this.props.lockObject} onPropertyChangedObservable={this.props.onPropertyChangedObservable}/>                    
+                        host={system}    
+                        codeRecorderPropertyName="getLifeTimeGradients()"                          
+                        mode={GradientGridMode.Factor}                        
+                        lockObject={this.props.lockObject}/>                    
                 </LineContainerComponent>    
                 <LineContainerComponent globalState={this.props.globalState} title="COLORS">
                     <Color4LineComponent label="Color 1" target={system} propertyName="color1" 
@@ -356,33 +407,85 @@ export class ParticleSystemPropertyGridComponent extends React.Component<IPartic
                         onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     <Color4LineComponent label="Color dead" target={system} propertyName="colorDead" 
                         onPropertyChangedObservable={this.props.onPropertyChangedObservable} />  
-                    <FactorGradientGridComponent globalState={this.props.globalState} gradients={system.getColorRemapGradients()!} 
+                    <ValueGradientGridComponent globalState={this.props.globalState} gradients={system.getColorGradients()!} 
+                        label="Color gradients"
+                        docLink="https://doc.babylonjs.com/babylon101/particles#particle-colors"
+                        onCreateRequired={() => {
+                            system.addColorGradient(0, new Color4(0, 0, 0, 1), new Color4(1, 1, 1, 1));
+                            this.props.globalState.onCodeChangedObservable.notifyObservers({
+                                object: system,
+                                code: `TARGET.addColorGradient(0, new BABYLON.Color4(0, 0, 0, 1), new BABYLON.Color4(1, 1, 1, 1));`
+                            });                                 
+                        }}
+                        host={system}    
+                        codeRecorderPropertyName="getColorGradients()"                              
+                        mode={GradientGridMode.Color4}                        
+                        lockObject={this.props.lockObject}/>                    
+
+                    <CheckBoxLineComponent label="Use ramp grandients" target={system} propertyName="useRampGradients"/>
+                    <ValueGradientGridComponent globalState={this.props.globalState} gradients={system.getRampGradients()!} 
+                        label="Ramp gradients"
+                        docLink="https://doc.babylonjs.com/babylon101/particles#ramp-gradients"
+                        onCreateRequired={() => {
+                            system.addRampGradient(0, Color3.White());
+                            this.props.globalState.onCodeChangedObservable.notifyObservers({
+                                object: system,
+                                code: `TARGET.addRampGradient(0, BABYLON.Color3.White());`
+                            });                               
+                        }}
+                        mode={GradientGridMode.Color3}      
+                        host={system}    
+                        codeRecorderPropertyName="getRampGradients()"                                                   
+                        lockObject={this.props.lockObject}/>                    
+                                                               
+                    <ValueGradientGridComponent globalState={this.props.globalState} gradients={system.getColorRemapGradients()!} 
                         label="Color remap gradients"
                         docLink="https://doc.babylonjs.com/babylon101/particles#ramp-gradients"
                         onCreateRequired={() => {
                             system.addColorRemapGradient(0, 1, 1);
+                            this.props.globalState.onCodeChangedObservable.notifyObservers({
+                                object: system,
+                                code: `TARGET.addColorRemapGradient(0, 1, 1);`
+                            });
                         }}
-                        lockObject={this.props.lockObject} onPropertyChangedObservable={this.props.onPropertyChangedObservable}/>                    
-                    <FactorGradientGridComponent globalState={this.props.globalState} gradients={system.getAlphaRemapGradients()!} 
+                        host={system}    
+                        codeRecorderPropertyName="getColorRemapGradients()"      
+                        mode={GradientGridMode.Factor}                        
+                        lockObject={this.props.lockObject}/>                    
+                    <ValueGradientGridComponent globalState={this.props.globalState} gradients={system.getAlphaRemapGradients()!} 
                         label="Alpha remap gradients"
                         docLink="https://doc.babylonjs.com/babylon101/particles#ramp-gradients"
                         onCreateRequired={() => {
                             system.addAlphaRemapGradient(0, 1, 1);
+                            this.props.globalState.onCodeChangedObservable.notifyObservers({
+                                object: system,
+                                code: `TARGET.addAlphaRemapGradient(0, 1, 1);`
+                            });                            
                         }}
-                        lockObject={this.props.lockObject} onPropertyChangedObservable={this.props.onPropertyChangedObservable}/>                                    
+                        host={system}    
+                        codeRecorderPropertyName="getAlphaRemapGradients()"                             
+                        mode={GradientGridMode.Factor}                        
+                        lockObject={this.props.lockObject}/>                                    
                 </LineContainerComponent>                     
                 <LineContainerComponent globalState={this.props.globalState} title="ROTATION">
                     <FloatLineComponent lockObject={this.props.lockObject} label="Min angular speed" target={system} propertyName="minAngularSpeed" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     <FloatLineComponent lockObject={this.props.lockObject} label="Max angular speed" target={system} propertyName="maxAngularSpeed" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     <FloatLineComponent lockObject={this.props.lockObject} label="Min initial rotation" target={system} propertyName="minInitialRotation" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     <FloatLineComponent lockObject={this.props.lockObject} label="Max initial rotation" target={system} propertyName="maxInitialRotation" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                    <FactorGradientGridComponent globalState={this.props.globalState} gradients={system.getAngularSpeedGradients()!} 
+                    <ValueGradientGridComponent globalState={this.props.globalState} gradients={system.getAngularSpeedGradients()!} 
                         label="Angular speed gradients"                        
                         docLink="hhttps://doc.babylonjs.com/babylon101/particles#rotation"
                         onCreateRequired={() => {
                             system.addAngularSpeedGradient(0, 0.1, 0.1);
-                        }}
-                        lockObject={this.props.lockObject} onPropertyChangedObservable={this.props.onPropertyChangedObservable}/>                    
+                            this.props.globalState.onCodeChangedObservable.notifyObservers({
+                                object: system,
+                                code: `TARGET.addAngularSpeedGradient(0, 0.1, 0.1);`
+                            });                               
+                        }}                        
+                        host={system}    
+                        codeRecorderPropertyName="getAngularSpeedGradients()"    
+                        mode={GradientGridMode.Factor}                        
+                        lockObject={this.props.lockObject}/>                    
                 </LineContainerComponent>  
             </div>
         );

+ 187 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/particleSystems/valueGradientGridComponent.tsx

@@ -0,0 +1,187 @@
+import * as React from "react";
+import { GlobalState } from '../../../../globalState';
+import { IValueGradient, FactorGradient, ColorGradient, Color3Gradient } from 'babylonjs/Misc/gradients';
+import { LockObject } from '../lockObject';
+import { ButtonLineComponent } from '../../../lines/buttonLineComponent';
+import { FactorGradientStepGridComponent } from './factorGradientStepGridComponent';
+import { Nullable } from 'babylonjs/types';
+import { ColorGradientStepGridComponent } from './colorGradientStepGridComponent';
+import { Color4, Color3 } from 'babylonjs/Maths/math.color';
+import { LinkButtonComponent } from '../../../lines/linkButtonComponent';
+import { IParticleSystem } from 'babylonjs';
+
+export enum GradientGridMode {
+    Factor,
+    Color3,
+    Color4
+}
+
+interface IValueGradientGridComponent {
+    globalState: GlobalState;
+    label: string;
+    gradients: Nullable<Array<IValueGradient>>,
+    lockObject: LockObject,
+    docLink?: string,
+    mode: GradientGridMode,
+    host: IParticleSystem,
+    codeRecorderPropertyName: string,
+    onCreateRequired: () => void
+}
+
+export class ValueGradientGridComponent extends React.Component<IValueGradientGridComponent> {
+
+    constructor(props: IValueGradientGridComponent) {
+        super(props)
+    }
+
+    deleteStep(step: IValueGradient) {
+        let gradients = this.props.gradients as Array<IValueGradient>;
+
+        let index = gradients.indexOf(step);
+
+        if (index > -1) {
+            gradients.splice(index, 1);
+            this.forceUpdate();
+
+            this.props.globalState.onCodeChangedObservable.notifyObservers({
+                object: this.props.host,
+                code: `TARGET.${this.props.codeRecorderPropertyName}.splice(${index}, 1);`
+            });
+        }
+    }
+
+    addNewStep() {
+        let gradients = this.props.gradients as Array<IValueGradient>;
+
+        switch(this.props.mode) {
+            case GradientGridMode.Factor:
+                let newStep = new FactorGradient();
+                newStep.gradient = 1.0;
+                newStep.factor1 = 1.0;
+                newStep.factor2 = 1.0;
+                gradients.push(newStep);
+                this.props.globalState.onCodeChangedObservable.notifyObservers({
+                    object: this.props.host,
+                    code: `TARGET.${this.props.codeRecorderPropertyName}.push(new BABYLON.FactorGradient(1, 1, 1));`
+                });
+                break;
+            case GradientGridMode.Color4:
+                let newStepColor = new ColorGradient();
+                newStepColor.gradient = 1.0;
+                newStepColor.color1 = new Color4(1, 1, 1, 1);
+                newStepColor.color2 = new Color4(1, 1, 1, 1);
+                gradients.push(newStepColor);
+                this.props.globalState.onCodeChangedObservable.notifyObservers({
+                    object: this.props.host,
+                    code: `TARGET.${this.props.codeRecorderPropertyName}.push(new BABYLON.ColorGradient(1, new BABYLON.Color4(1, 1, 1, 1), new BABYLON.Color4(1, 1, 1, 1)));`
+                });
+                break;    
+            case GradientGridMode.Color3:
+                let newStepColor3 = new Color3Gradient();
+                newStepColor3.gradient = 1.0;
+                newStepColor3.color = Color3.White();
+                gradients.push(newStepColor3);
+                this.props.globalState.onCodeChangedObservable.notifyObservers({
+                    object: this.props.host,
+                    code: `TARGET.${this.props.codeRecorderPropertyName}.push(new BABYLON.Color3Gradient(1, BABYLON.Color3.White()));`
+                });
+                break;              
+        }
+
+        this.forceUpdate();
+    }
+
+    checkForReOrder() {
+        let gradients = this.props.gradients as Array<IValueGradient>;
+        gradients.sort((a, b) => {
+            if (a.gradient === b.gradient) {
+                return 0;
+            }
+
+            if (a.gradient > b.gradient) {
+                return 1;
+            }
+
+            return -1;
+        });
+
+        this.props.globalState.onCodeChangedObservable.notifyObservers({
+            object: this.props.host,
+            code: `TARGET.${this.props.codeRecorderPropertyName}.sort((a, b) => {
+                if (a.gradient === b.gradient) {
+                    return 0;
+                }
+    
+                if (a.gradient > b.gradient) {
+                    return 1;
+                }
+    
+                return -1;
+            });`
+        });
+
+        this.forceUpdate();
+    }
+
+    render() {
+        let gradients = this.props.gradients as Nullable<Array<IValueGradient>>;
+      
+        return (
+            <div>
+                {
+                    gradients &&
+                    <div className="gradient-container">
+                        <LinkButtonComponent label={this.props.label} url={this.props.docLink} 
+                            buttonLabel="Add new step" onClick={() => this.addNewStep()} />
+                        {
+                            gradients.map((g, i) => {
+                                let codeRecorderPropertyName = this.props.codeRecorderPropertyName + `[${i}]`;
+                                switch(this.props.mode) {
+                                    case GradientGridMode.Factor:
+                                        return (
+                                            <FactorGradientStepGridComponent globalState={this.props.globalState} 
+                                                lockObject={this.props.lockObject}
+                                                onCheckForReOrder={() => this.checkForReOrder()}
+                                                onUpdateGradient={() => this.forceUpdate()}
+                                                host={this.props.host}
+                                                codeRecorderPropertyName={codeRecorderPropertyName}
+                                                key={"step-" + i} lineIndex={i} gradient={g as FactorGradient} onDelete={() => this.deleteStep(g)}/>
+                                        );
+                                    case GradientGridMode.Color4:
+                                        return (
+                                            <ColorGradientStepGridComponent globalState={this.props.globalState} 
+                                                host={this.props.host}
+                                                codeRecorderPropertyName={codeRecorderPropertyName}
+                                                lockObject={this.props.lockObject}
+                                                isColor3={false}
+                                                onCheckForReOrder={() => this.checkForReOrder()}
+                                                onUpdateGradient={() => this.forceUpdate()}
+                                                key={"step-" + i} lineIndex={i} gradient={g as ColorGradient} onDelete={() => this.deleteStep(g)}/>
+                                        );   
+                                    case GradientGridMode.Color3:
+                                        return (
+                                            <ColorGradientStepGridComponent globalState={this.props.globalState} 
+                                                host={this.props.host}
+                                                codeRecorderPropertyName={codeRecorderPropertyName}
+                                                lockObject={this.props.lockObject}
+                                                isColor3={true}
+                                                onCheckForReOrder={() => this.checkForReOrder()}
+                                                onUpdateGradient={() => this.forceUpdate()}
+                                                key={"step-" + i} lineIndex={i} gradient={g as Color3Gradient} onDelete={() => this.deleteStep(g)}/>
+                                        );                                      
+                                }
+                            })
+                        }
+                    </div>
+                }
+                {
+                    !gradients &&                    
+                    <ButtonLineComponent label={"Use " + this.props.label} onClick={() => {
+                        this.props.onCreateRequired();
+                        this.forceUpdate();
+                    }} />
+                }
+            </div>
+        );
+    }
+}

+ 4 - 0
inspector/src/components/codeChangedEvent.ts

@@ -0,0 +1,4 @@
+export class CodeChangedEvent {
+    public object: any;
+    public code: string;
+}

+ 7 - 1
inspector/src/components/globalState.ts

@@ -10,10 +10,12 @@ import { LightGizmo } from "babylonjs/Gizmos/lightGizmo";
 import { PropertyChangedEvent } from "./propertyChangedEvent";
 import { ReplayRecorder } from './replayRecorder';
 import { DataStorage } from 'babylonjs/Misc/dataStorage';
+import { CodeChangedEvent } from './codeChangedEvent';
 
 export class GlobalState {
     public onSelectionChangedObservable: Observable<any>;
     public onPropertyChangedObservable: Observable<PropertyChangedEvent>;
+    public onCodeChangedObservable = new Observable<CodeChangedEvent>();
     public onInspectorClosedObservable = new Observable<Scene>();
     public onTabChangedObservable = new Observable<number>();
     public onPluginActivatedObserver: Nullable<Observer<ISceneLoaderPlugin | ISceneLoaderPluginAsync>>;
@@ -69,7 +71,11 @@ export class GlobalState {
 
         propertyChangedObservable.add(event => {
             this.recorder.record(event);
-        })
+        });
+
+        this.onCodeChangedObservable.add(code => {
+            this.recorder.recordCode(code);
+        });
     }
 
     public prepareGLTFPlugin(loader: GLTFFileLoader) {

+ 13 - 0
inspector/src/components/replayRecorder.ts

@@ -1,5 +1,6 @@
 import { PropertyChangedEvent } from './propertyChangedEvent';
 import { Tools } from 'babylonjs/Misc/tools';
+import { CodeChangedEvent } from './codeChangedEvent';
 
 export class ReplayRecorder {
     private _recordedCodeLines: string[];
@@ -34,6 +35,8 @@ export class ReplayRecorder {
                 indirectData = `scene.getSkeletonById("${data.id}")`;
             } else if (indirectData.indexOf("material") > -1) {
                 indirectData = `scene.getMaterialByID("${data.id}")`;
+            }else if (indirectData.indexOf("particle") > -1) {
+                indirectData = `scene.getParticleSystemById("${data.id}")`;
             }
         } else {
             indirectData = "new BABYLON." + data.getClassName() + "()";
@@ -42,6 +45,16 @@ export class ReplayRecorder {
         return indirectData;
     }
 
+    public recordCode(event: CodeChangedEvent) {
+        if (!this._recordedCodeLines) {
+            this._recordedCodeLines = [];
+        }
+
+        let target = this._getIndirectData(event.object);
+
+        this._recordedCodeLines.push(event.code.replace(/TARGET/g, target));        
+    }
+
     public record(event: PropertyChangedEvent) {
         if (!this._recordedCodeLines) {
             this._recordedCodeLines = [];

+ 6 - 1
src/Maths/math.color.ts

@@ -825,10 +825,15 @@ export class Color4 {
      * Compute the Color4 hexadecimal code as a string
      * @returns a string containing the hexadecimal representation of the Color4 object
      */
-    public toHexString(): string {
+    public toHexString(returnAsColor3 = false): string {
         var intR = (this.r * 255) | 0;
         var intG = (this.g * 255) | 0;
         var intB = (this.b * 255) | 0;
+
+        if (returnAsColor3) {
+            return "#" + Scalar.ToHex(intR) + Scalar.ToHex(intG) + Scalar.ToHex(intB);
+        }
+
         var intA = (this.a * 255) | 0;
         return "#" + Scalar.ToHex(intR) + Scalar.ToHex(intG) + Scalar.ToHex(intB) + Scalar.ToHex(intA);
     }

+ 21 - 0
src/Misc/gradients.ts

@@ -23,6 +23,14 @@ export class ColorGradient implements IValueGradient {
      */
     public color2?: Color4;
 
+
+    /** Creates a new color4 gradient */
+    public constructor(gradient: number, color1: Color4, color2?: Color4) {
+        this.gradient = gradient;
+        this.color1 = color1;
+        this.color2 = color2;
+    }       
+
     /**
      * Will get a color picked randomly between color1 and color2.
      * If color2 is undefined then color1 will be used
@@ -48,6 +56,12 @@ export class Color3Gradient implements IValueGradient {
      * Gets or sets the associated color
      */
     public color: Color3;
+
+    /** Creates a new color3 gradient */
+    public constructor(gradient: number, color: Color3) {
+        this.gradient = gradient;
+        this.color = color;
+    }    
 }
 
 /** Class used to store factor gradient */
@@ -65,6 +79,13 @@ export class FactorGradient implements IValueGradient {
      */
     public factor2?: number;
 
+    /** Creates a new factor gradient */
+    public constructor(gradient: number, factor1: number, factor2?: number) {
+        this.gradient = gradient;
+        this.factor1 = factor1;
+        this.factor2 = factor2;
+    }
+
     /**
      * Will get a number picked randomly between factor1 and factor2.
      * If factor2 is undefined then factor1 will be used

+ 5 - 1
src/Particles/particle.ts

@@ -298,7 +298,11 @@ export class Particle {
             other._initialEndSpriteCellID = this._initialEndSpriteCellID;
         }
         if (this.particleSystem.useRampGradients) {
-            other.remapData.copyFrom(this.remapData);
+            if (other.remapData) {
+                other.remapData.copyFrom(this.remapData);
+            } else {
+                other.remapData = new Vector4(0, 0, 0, 0);
+            }
         }
         if (this._randomNoiseCoordinates1) {
             if (other._randomNoiseCoordinates1) {

+ 1 - 1
src/Particles/particleSystem.ts

@@ -1187,7 +1187,7 @@ export class ParticleSystem extends BaseParticleSystem implements IDisposable, I
             this._vertexData[offset++] = particle.direction.z;
         }
 
-        if (this._useRampGradients) {
+        if (this._useRampGradients && particle.remapData) {
             this._vertexData[offset++] = particle.remapData.x;
             this._vertexData[offset++] = particle.remapData.y;
             this._vertexData[offset++] = particle.remapData.z;