David Catuhe il y a 6 ans
Parent
commit
8484109a3d

+ 10 - 4
nodeEditor/src/components/diagram/defaultNodeModel.ts

@@ -47,8 +47,6 @@ export class DefaultNodeModel extends NodeModel {
             inputPort.connection = connection;
             this.addPort(inputPort)
 
-            console.log(connection.name + " for " + options.nodeMaterialBlock!.name)
-
             if (connection.connectedPoint) {
                 // Block is not a leaf node, create node for the given block type
                 var connectedNode;
@@ -60,7 +58,11 @@ export class DefaultNodeModel extends NodeModel {
                 }
 
                 let link = connectedNode.ports[connection.connectedPoint.name].link(inputPort);
-                model.addAll(link);
+                if (graphEditor._toAdd) {
+                    graphEditor._toAdd.push(link);
+                } else {
+                    model.addAll(link);
+                }
             } else {
                 // Create value node for the connection
                 var type = ""
@@ -82,7 +84,11 @@ export class DefaultNodeModel extends NodeModel {
                     var ports = localNode.getPorts()
                     for (var key in ports) {
                         let link = (ports[key] as DefaultPortModel).link(inputPort);
-                        model.addAll(link);
+                        if (graphEditor._toAdd) {
+                            graphEditor._toAdd.push(link);
+                        } else {
+                            model.addAll(link);
+                        }
                     }
                 }
             }

+ 32 - 6
nodeEditor/src/components/diagram/input/inputNodePropertyComponent.tsx

@@ -10,6 +10,7 @@ import { OptionsLineComponent } from '../../../sharedComponents/optionsLineCompo
 import { NodeMaterialWellKnownValues } from 'babylonjs/Materials/Node/nodeMaterialWellKnownValues';
 import { Vector2, Vector3, Matrix } from 'babylonjs/Maths/math';
 import { TextLineComponent } from '../../../sharedComponents/textLineComponent';
+import { Color3PropertyTabComponent } from '../../propertyTab/properties/color3PropertyTabComponent';
 
 interface IInputPropertyTabComponentProps {
     globalState: GlobalState;
@@ -31,6 +32,11 @@ export class InputPropertyTabComponentProps extends React.Component<IInputProper
                 );
             case NodeMaterialBlockConnectionPointTypes.Vector3:
             case NodeMaterialBlockConnectionPointTypes.Color3:
+            case NodeMaterialBlockConnectionPointTypes.Color3OrColor4:
+            case NodeMaterialBlockConnectionPointTypes.Color4:
+                return (
+                    <Color3PropertyTabComponent globalState={globalState} connection={connection} />
+                );
             case NodeMaterialBlockConnectionPointTypes.Vector3OrColor3:
                 return (
                     <Vector3PropertyTabComponent globalState={globalState} connection={connection} />
@@ -69,6 +75,15 @@ export class InputPropertyTabComponentProps extends React.Component<IInputProper
             { label: "Automatic", value: NodeMaterialWellKnownValues.Automatic },
         ];
 
+        var attributeOptions = [
+            { label: "position", value: "position" },
+            { label: "normal", value: "normal" },
+            { label: "tangent", value: "tangent" },
+            { label: "color", value: "color" },
+            { label: "uv", value: "uv" },
+            { label: "uv2", value: "uv2" },
+        ];
+
         /**
          * Gets the base math type of node material block connection point.
          * @param type Type to parse.
@@ -76,15 +91,14 @@ export class InputPropertyTabComponentProps extends React.Component<IInputProper
         function getBaseType(type: NodeMaterialBlockConnectionPointTypes): string {
             switch (type) {
                 case NodeMaterialBlockConnectionPointTypes.Vector3OrColor3:
-                case NodeMaterialBlockConnectionPointTypes.Color3: {
-                    return NodeMaterialBlockConnectionPointTypes[NodeMaterialBlockConnectionPointTypes.Vector3];
-                }
                 case NodeMaterialBlockConnectionPointTypes.Vector4OrColor4:
                 case NodeMaterialBlockConnectionPointTypes.Vector3OrVector4:
-                case NodeMaterialBlockConnectionPointTypes.Color3OrColor4:
                 case NodeMaterialBlockConnectionPointTypes.Vector3OrColor3OrVector4OrColor4:
+                    return "Vector";
+                case NodeMaterialBlockConnectionPointTypes.Color3:
+                case NodeMaterialBlockConnectionPointTypes.Color3OrColor4:
                 case NodeMaterialBlockConnectionPointTypes.Color4: {
-                    return NodeMaterialBlockConnectionPointTypes[NodeMaterialBlockConnectionPointTypes.Vector4];
+                    return "Color";
                 }
                 default: {
                     return NodeMaterialBlockConnectionPointTypes[type];
@@ -96,11 +110,23 @@ export class InputPropertyTabComponentProps extends React.Component<IInputProper
             <div>
                 <TextLineComponent label="Type" value={getBaseType(connection.type)} />
                 <CheckBoxLineComponent label="Is mesh attribute" onSelect={value => {
-                    connection!.isAttribute = value;
+                    if (!value) {
+                        connection.isUniform = true;
+                    } else {
+                        connection.isAttribute = true;
+                    }
                     this.props.globalState.onRebuildRequiredObservable.notifyObservers();
                     this.forceUpdate();
                 }} isSelected={() => connection!.isAttribute} />
                 {
+                    connection.isAttribute &&
+                    <OptionsLineComponent label="Attribute" valuesAreStrings={true} options={attributeOptions} target={connection} propertyName="name" onSelect={(value: any) => {
+                        connection.setAsAttribute(value);
+                        this.forceUpdate();
+                        this.props.globalState.onRebuildRequiredObservable.notifyObservers();
+                    }} />
+                }
+                {
                     connection.isUniform &&
                     <CheckBoxLineComponent label="Is well known value" onSelect={value => {
                         if (value) {

+ 3 - 3
nodeEditor/src/components/nodeList/nodeListComponent.tsx

@@ -40,7 +40,7 @@ export class NodeListComponent extends React.Component<INodeListComponentProps>
             Outputs: [VertexOutputBlock, FragmentOutputBlock],
             Dual: [FogBlock],
             Math: [AddBlock, ClampBlock, MatrixMultiplicationBlock, MultiplyBlock, Vector2TransformBlock, Vector3TransformBlock, Vector4TransformBlock],
-            Inputs: ["Texture", "Vector2", "Vector3", "Matrix"],
+            Inputs: ["Vector2", "Vector3", "Matrix"],
         }
 
         // Create node menu
@@ -49,10 +49,10 @@ export class NodeListComponent extends React.Component<INodeListComponentProps>
             var blockList = (allBlocks as any)[key].map((b: any) => {
                 var label = typeof b === "string" ? b : b.prototype.getClassName().replace("Block", "")
                 var onClick = typeof b === "string" ? () => { this.props.onAddValueNode(b) } : () => { this.props.onAddNodeFromClass(b) };
-                return <ButtonLineComponent label={label} onClick={onClick} />
+                return <ButtonLineComponent key={label} label={label} onClick={onClick} />
             })
             blockMenu.push(
-                <LineContainerComponent title={key + " blocks"} closed={false}>
+                <LineContainerComponent key={key + " blocks"} title={key + " blocks"} closed={false}>
                     {blockList}
                 </LineContainerComponent>
             )

+ 19 - 0
nodeEditor/src/components/propertyTab/properties/color3PropertyTabComponent.tsx

@@ -0,0 +1,19 @@
+
+import * as React from "react";
+import { GlobalState } from '../../../globalState';
+import { NodeMaterialConnectionPoint } from 'babylonjs/Materials/Node/nodeMaterialBlockConnectionPoint';
+import { Color3LineComponent } from '../../../sharedComponents/color3LineComponent';
+
+interface IColor3PropertyTabComponentProps {
+    globalState: GlobalState;
+    connection: NodeMaterialConnectionPoint;
+}
+
+export class Color3PropertyTabComponent extends React.Component<IColor3PropertyTabComponentProps> {
+
+    render() {
+        return (
+            <Color3LineComponent label="Value" target={this.props.connection} propertyName="value"></Color3LineComponent>
+        );
+    }
+}

+ 2 - 1
nodeEditor/src/components/propertyTab/properties/texturePropertyTabComponent.tsx

@@ -6,6 +6,7 @@ import { FileButtonLineComponent } from '../../../sharedComponents/fileButtonLin
 import { Tools } from 'babylonjs/Misc/tools';
 import { Engine } from 'babylonjs/Engines/engine';
 import { TextureNodeModel } from '../../../components/diagram/texture/textureNodeModel';
+import { TextLineComponent } from '../../../sharedComponents/textLineComponent';
 
 interface ITexturePropertyTabComponentProps {
     globalState: GlobalState;
@@ -51,7 +52,7 @@ export class TexturePropertyTabComponent extends React.Component<ITexturePropert
     render() {
         return (
             <div>
-                <h1>Texture</h1>
+                <TextLineComponent label="Type" value="Texture" />
                 <FileButtonLineComponent label="Replace texture" onClick={(file) => this.replaceTexture(file)} accept=".jpg, .png, .tga, .dds, .env" />
             </div>
         );

+ 87 - 1
nodeEditor/src/components/propertyTab/propertyTab.scss

@@ -1,5 +1,5 @@
 #propertyTab {
-    $line-padding-left: 2px;
+    $line-padding-left: 5px;
     color:white;
 
       #header {
@@ -238,6 +238,92 @@
             }
         }                    
     }  
+                    
+    .color3Line {
+        padding-left: $line-padding-left;
+        display: grid;
+
+        .firstLine {
+            height: 30px;
+            display: grid;
+            grid-template-columns: 1fr auto 20px 20px;
+
+            .label {
+                grid-column: 1;
+                display: flex;
+                align-items: center;
+            }
+
+            .color3 {
+                grid-column: 2;
+                
+                display: flex;
+                align-items: center;   
+
+                input[type="color"] {
+                    -webkit-appearance: none;
+                    border: 1px solid rgba(255, 255, 255, 0.5);
+                    padding: 0;
+                    width: 30px;
+                    height: 20px;
+                }
+                input[type="color"]::-webkit-color-swatch-wrapper {
+                    padding: 0;
+                }
+                input[type="color"]::-webkit-color-swatch {
+                    border: none;
+                }
+                
+                input {
+                    margin-right: 5px;
+                }
+            }
+
+            .copy {
+                grid-column: 3;
+                display: grid;
+                align-items: center;
+                justify-items: center;
+                cursor: pointer;
+            }
+
+            .expand {
+                grid-column: 4;
+                display: grid;
+                align-items: center;
+                justify-items: center;
+                cursor: pointer;
+            }
+        }   
+
+        .secondLine {
+            display: grid;
+            padding-right: 5px;  
+            border-left: 1px solid rgb(51, 122, 183);
+
+            .numeric {
+                display: grid;
+                grid-template-columns: 1fr auto;
+            }
+
+            .numeric-label {
+                text-align: right;
+                grid-column: 1;
+                display: flex;
+                align-items: center;                            
+                justify-self: right;
+                margin-right: 10px;                          
+            }
+
+            .numeric-value {
+                width: 120px;
+                grid-column: 2;
+                display: flex;
+                align-items: center;  
+                border: 1px solid  rgb(51, 122, 183);
+            }                        
+        }                  
+    }     
     
     .textLine {
         padding-left: $line-padding-left;

+ 7 - 0
nodeEditor/src/components/propertyTab/propertyTabComponent.tsx

@@ -3,6 +3,7 @@ import * as React from "react";
 import { GlobalState } from '../../globalState';
 import { Nullable } from 'babylonjs/types';
 import { DefaultNodeModel } from '../../components/diagram/defaultNodeModel';
+import { ButtonLineComponent } from '../../sharedComponents/buttonLineComponent';
 require("./propertyTab.scss");
 
 interface IPropertyTabComponentProps {
@@ -46,6 +47,12 @@ export class PropertyTabComponent extends React.Component<IPropertyTabComponentP
                         NODE MATERIAL EDITOR
                     </div>
                 </div>
+                <div>
+                    <ButtonLineComponent label="Reset to default" onClick={() => {
+                        this.props.globalState.nodeMaterial!.setToDefault();
+                        this.props.globalState.onResetRequiredObservable.notifyObservers();
+                    }} />
+                </div>
             </div>
         );
     }

+ 1 - 0
nodeEditor/src/globalState.ts

@@ -9,4 +9,5 @@ export class GlobalState {
     hostDocument: HTMLDocument;
     onSelectionChangedObservable = new Observable<Nullable<DefaultNodeModel>>();
     onRebuildRequiredObservable = new Observable<void>();
+    onResetRequiredObservable = new Observable<void>();
 }

+ 46 - 40
nodeEditor/src/graphEditor.tsx

@@ -2,7 +2,8 @@ import {
     DiagramEngine,
     DiagramModel,
     DiagramWidget,
-    MoveCanvasAction
+    MoveCanvasAction,
+    LinkModel
 } from "storm-react-diagrams";
 
 import * as React from "react";
@@ -58,10 +59,13 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
 
     private _nodes = new Array<DefaultNodeModel>();
 
+    /** @hidden */
+    public _toAdd: LinkModel[] | null = [];
+
     /**
      * Current row/column position used when adding new nodes
      */
-    private _rowPos = new Array<number>()
+    private _rowPos = new Array<number>();
 
     /**
      * Creates a node and recursivly creates its parent nodes from it's input
@@ -76,29 +80,29 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
         }
 
         // Create new node in the graph
-        var outputNode: DefaultNodeModel;
+        var newNode: DefaultNodeModel;
         var filterInputs = [];
 
         if (options.nodeMaterialBlock) {
             if (options.nodeMaterialBlock instanceof TextureBlock) {
-                outputNode = new TextureNodeModel();
+                newNode = new TextureNodeModel();
                 filterInputs.push("uv");
             } else {
-                outputNode = new GenericNodeModel();
+                newNode = new GenericNodeModel();
             }
         } else {
-            outputNode = new InputNodeModel();
-            (outputNode as InputNodeModel).connection = options.connection;
+            newNode = new InputNodeModel();
+            (newNode as InputNodeModel).connection = options.connection;
         }
-        this._nodes.push(outputNode)
-        outputNode.setPosition(1600 - (300 * options.column), 210 * this._rowPos[options.column])
-        this._model.addAll(outputNode);
+        this._nodes.push(newNode)
+        newNode.setPosition(1600 - (300 * options.column), 210 * this._rowPos[options.column])
+        this._model.addAll(newNode);
 
         if (options.nodeMaterialBlock) {
-            outputNode.prepare(options, this._nodes, this._model, this, filterInputs);
+            newNode.prepare(options, this._nodes, this._model, this, filterInputs);
         }
 
-        return outputNode;
+        return newNode;
     }
 
     componentDidMount() {
@@ -126,6 +130,25 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
         this._engine.registerNodeFactory(new TextureNodeFactory(this.props.globalState));
         this._engine.registerNodeFactory(new InputNodeFactory(this.props.globalState));
 
+        this.props.globalState.onRebuildRequiredObservable.add(() => {
+            if (this.props.globalState.nodeMaterial) {
+                this.props.globalState.nodeMaterial.build(true);
+            }
+            this.forceUpdate();
+        });
+
+        this.props.globalState.onResetRequiredObservable.add(() => {
+            this._rowPos = [];
+            this.build();
+            if (this.props.globalState.nodeMaterial) {
+                this.props.globalState.nodeMaterial.build(true);
+            }
+        });
+
+        this.build();
+    }
+
+    build() {
         // setup the diagram model
         this._model = new DiagramModel();
 
@@ -134,42 +157,33 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
             linksUpdated: (e) => {
                 if (!e.isCreated) {
                     // Link is deleted
-                    console.log("link deleted");
+                    this.props.globalState.onSelectionChangedObservable.notifyObservers(null);
                     var link = DefaultPortModel.SortInputOutput(e.link.sourcePort as DefaultPortModel, e.link.targetPort as DefaultPortModel);
                     console.log(link)
                     if (link) {
                         if (link.output.connection && link.input.connection) {
                             // Disconnect standard nodes
-                            console.log("disconnected " + link.output.connection.name + " from " + link.input.connection.name)
                             link.output.connection.disconnectFrom(link.input.connection)
                             link.input.syncWithNodeMaterialConnectionPoint(link.input.connection)
                             link.output.syncWithNodeMaterialConnectionPoint(link.output.connection)
                         } else if (link.input.connection && link.input.connection.value) {
-                            console.log("value link removed");
                             link.input.connection.value = null;
-                        } else {
-                            console.log("invalid link error");
                         }
                     }
-                } else {
-                    console.log("link created")
-                    console.log(e.link.sourcePort)
                 }
+
                 e.link.addListener({
                     sourcePortChanged: () => {
                         console.log("port change")
                     },
                     targetPortChanged: () => {
                         // Link is created with a target port
-                        console.log("Link set to target")
                         var link = DefaultPortModel.SortInputOutput(e.link.sourcePort as DefaultPortModel, e.link.targetPort as DefaultPortModel);
 
                         if (link) {
                             if (link.output.connection && link.input.connection) {
-                                console.log("link standard blocks")
                                 link.output.connection.connectTo(link.input.connection)
                             } else if (link.input.connection) {
-                                console.log("link value to standard block")
                                 link.input.connection.value = link.output.getValue();
 
                             }
@@ -178,22 +192,7 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
                             }
                         }
                     }
-
                 })
-
-            },
-            nodesUpdated: (e) => {
-                if (e.isCreated) {
-                    console.log("new node")
-                } else {
-                    console.log("node deleted")
-                }
-            }
-        });
-
-        this.props.globalState.onRebuildRequiredObservable.add(() => {
-            if (this.props.globalState.nodeMaterial) {
-                this.props.globalState.nodeMaterial.build();
             }
         });
 
@@ -212,11 +211,18 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
         this._model.setZoomLevel(80)
 
         // load model into engine
-        this._engine.setDiagramModel(this._model);
+        setTimeout(() => {
+            if (this._toAdd) {
+                this._model.addAll(...this._toAdd);
+            }
+            this._toAdd = null;
+            this._engine.setDiagramModel(this._model);
+            this.forceUpdate();
+        }, 15);
     }
 
     addNodeFromClass(ObjectClass: typeof NodeMaterialBlock) {
-        var block = new ObjectClass(ObjectClass.prototype.getClassName() + "sdfsdf")
+        var block = new ObjectClass(ObjectClass.prototype.getClassName())
         var localNode = this.createNodeFromObject({ column: 0, nodeMaterialBlock: block })
         var widget = (this.refs["test"] as DiagramWidget);
 

+ 155 - 0
nodeEditor/src/sharedComponents/color3LineComponent.tsx

@@ -0,0 +1,155 @@
+import * as React from "react";
+import { Observable } from "babylonjs/Misc/observable";
+import { Color3 } from "babylonjs/Maths/math";
+import { PropertyChangedEvent } from "./propertyChangedEvent";
+import { NumericInputComponent } from "./numericInputComponent";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faMinus, faPlus, faCopy } from "@fortawesome/free-solid-svg-icons";
+
+export interface IColor3LineComponentProps {
+    label: string;
+    target: any;
+    propertyName: string;
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
+}
+
+export class Color3LineComponent extends React.Component<IColor3LineComponentProps, { isExpanded: boolean, color: Color3 }> {
+    private _localChange = false;
+    constructor(props: IColor3LineComponentProps) {
+        super(props);
+
+        this.state = { isExpanded: false, color: this.props.target[this.props.propertyName].clone() };
+    }
+
+    shouldComponentUpdate(nextProps: IColor3LineComponentProps, nextState: { color: Color3 }) {
+        const currentState = nextProps.target[nextProps.propertyName];
+
+        if (!currentState.equals(nextState.color) || this._localChange) {
+            nextState.color = currentState.clone();
+            this._localChange = false;
+            return true;
+        }
+        return false;
+    }
+
+    onChange(newValue: string) {
+        this._localChange = true;
+        const newColor = Color3.FromHexString(newValue);
+
+        if (this.props.onPropertyChangedObservable) {
+            this.props.onPropertyChangedObservable.notifyObservers({
+                object: this.props.target,
+                property: this.props.propertyName,
+                value: newColor,
+                initialValue: this.state.color
+            });
+        }
+
+        this.props.target[this.props.propertyName] = newColor;
+
+        this.setState({ color: newColor });
+    }
+
+    switchExpandState() {
+        this._localChange = true;
+        this.setState({ isExpanded: !this.state.isExpanded });
+    }
+
+    raiseOnPropertyChanged(previousValue: Color3) {
+
+        if (!this.props.onPropertyChangedObservable) {
+            return;
+        }
+        this.props.onPropertyChangedObservable.notifyObservers({
+            object: this.props.target,
+            property: this.props.propertyName,
+            value: this.state.color,
+            initialValue: previousValue
+        });
+    }
+
+    updateStateR(value: number) {
+        this._localChange = true;
+
+        const store = this.state.color.clone();
+        this.props.target[this.props.propertyName].x = value;
+        this.state.color.r = value;
+        this.props.target[this.props.propertyName] = this.state.color;
+        this.setState({ color: this.state.color });
+
+        this.raiseOnPropertyChanged(store);
+    }
+
+    updateStateG(value: number) {
+        this._localChange = true;
+
+        const store = this.state.color.clone();
+        this.props.target[this.props.propertyName].g = value;
+        this.state.color.g = value;
+        this.props.target[this.props.propertyName] = this.state.color;
+        this.setState({ color: this.state.color });
+
+        this.raiseOnPropertyChanged(store);
+    }
+
+    updateStateB(value: number) {
+        this._localChange = true;
+
+        const store = this.state.color.clone();
+        this.props.target[this.props.propertyName].b = value;
+        this.state.color.b = value;
+        this.props.target[this.props.propertyName] = this.state.color;
+        this.setState({ color: this.state.color });
+
+        this.raiseOnPropertyChanged(store);
+    }
+
+    copyToClipboard() {
+        var element = document.createElement('div');
+        element.textContent = this.state.color.toHexString();
+        document.body.appendChild(element);
+
+        if (window.getSelection) {
+            var range = document.createRange();
+            range.selectNode(element);
+            window.getSelection()!.removeAllRanges();
+            window.getSelection()!.addRange(range);
+        }
+
+        document.execCommand('copy');
+        element.remove();
+    }
+
+    render() {
+
+        const chevron = this.state.isExpanded ? <FontAwesomeIcon icon={faMinus} /> : <FontAwesomeIcon icon={faPlus} />
+        const colorAsColor3 = this.state.color.getClassName() === "Color3" ? this.state.color : new Color3(this.state.color.r, this.state.color.g, this.state.color.b);
+
+        return (
+            <div className="color3Line">
+                <div className="firstLine">
+                    <div className="label">
+                        {this.props.label}
+                    </div>
+                    <div className="color3">
+                        <input type="color" value={colorAsColor3.toHexString()} onChange={(evt) => this.onChange(evt.target.value)} />
+                    </div>
+                    <div className="copy hoverIcon" onClick={() => this.copyToClipboard()} title="Copy to clipboard">
+                        <FontAwesomeIcon icon={faCopy} />
+                    </div>
+                    <div className="expand hoverIcon" onClick={() => this.switchExpandState()} title="Expand">
+                        {chevron}
+                    </div>
+                </div>
+                {
+                    this.state.isExpanded &&
+                    <div className="secondLine">
+                        <NumericInputComponent label="r" value={this.state.color.r} onChange={value => this.updateStateR(value)} />
+                        <NumericInputComponent label="g" value={this.state.color.g} onChange={value => this.updateStateG(value)} />
+                        <NumericInputComponent label="b" value={this.state.color.b} onChange={value => this.updateStateB(value)} />
+                    </div>
+                }
+            </div>
+        );
+    }
+}

+ 7 - 6
nodeEditor/src/sharedComponents/optionsLineComponent.tsx

@@ -5,7 +5,7 @@ import { PropertyChangedEvent } from "./propertyChangedEvent";
 
 class ListLineOption {
     public label: string;
-    public value: number;
+    public value: number | string;
 }
 
 interface IOptionsLineComponentProps {
@@ -14,11 +14,12 @@ interface IOptionsLineComponentProps {
     propertyName: string,
     options: ListLineOption[],
     noDirectUpdate?: boolean,
-    onSelect?: (value: number) => void,
-    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+    onSelect?: (value: number | string) => void,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>,
+    valuesAreStrings?: boolean
 }
 
-export class OptionsLineComponent extends React.Component<IOptionsLineComponentProps, { value: number }> {
+export class OptionsLineComponent extends React.Component<IOptionsLineComponentProps, { value: number | string }> {
     private _localChange = false;
 
     constructor(props: IOptionsLineComponentProps) {
@@ -41,7 +42,7 @@ export class OptionsLineComponent extends React.Component<IOptionsLineComponentP
         return false;
     }
 
-    raiseOnPropertyChanged(newValue: number, previousValue: number) {
+    raiseOnPropertyChanged(newValue: number | string, previousValue: number | string) {
         if (!this.props.onPropertyChangedObservable) {
             return;
         }
@@ -55,7 +56,7 @@ export class OptionsLineComponent extends React.Component<IOptionsLineComponentP
     }
 
     updateValue(valueString: string) {
-        const value = parseInt(valueString);
+        const value = this.props.valuesAreStrings ? valueString : parseInt(valueString);
         this._localChange = true;
 
         const store = this.state.value;

+ 1 - 1
src/Materials/Node/Blocks/Fragment/fragmentOutputBlock.ts

@@ -43,7 +43,7 @@ export class FragmentOutputBlock extends NodeMaterialBlock {
         let input = this.color;
         state.sharedData.hints.needAlphaBlending = this.alphaBlendingEnabled;
 
-        if (input.connectedPoint!.type === NodeMaterialBlockConnectionPointTypes.Color3) {
+        if (input.connectedPoint && input.connectedPoint!.type === NodeMaterialBlockConnectionPointTypes.Color3) {
             state.compilationString += `gl_FragColor = vec4(${input.associatedVariableName}, 1.0);\r\n`;
         } else {
             state.compilationString += `gl_FragColor = ${input.associatedVariableName};\r\n`;

+ 3 - 0
src/Materials/Node/Blocks/Fragment/textureBlock.ts

@@ -38,6 +38,9 @@ export class TextureBlock extends NodeMaterialBlock {
         this.registerInput("textureTransform", NodeMaterialBlockConnectionPointTypes.Matrix, true, NodeMaterialBlockTargets.Vertex);
 
         this.registerOutput("color", NodeMaterialBlockConnectionPointTypes.Color4);
+
+        // Setup
+        this._inputs[0]._needToEmitVarying = false;
     }
 
     /**

+ 38 - 1
src/Materials/Node/nodeMaterial.ts

@@ -2,7 +2,7 @@ import { NodeMaterialBlock } from './nodeMaterialBlock';
 import { PushMaterial } from '../pushMaterial';
 import { Scene } from '../../scene';
 import { AbstractMesh } from '../../Meshes/abstractMesh';
-import { Matrix } from '../../Maths/math';
+import { Matrix, Color4 } from '../../Maths/math';
 import { Mesh } from '../../Meshes/mesh';
 import { Engine } from '../../Engines/engine';
 import { NodeMaterialBuildState } from './nodeMaterialBuildState';
@@ -20,6 +20,9 @@ import { ImageProcessingConfiguration, IImageProcessingConfigurationDefines } fr
 import { Nullable } from '../../types';
 import { VertexBuffer } from '../../Meshes/buffer';
 import { Tools } from '../../Misc/tools';
+import { Vector4TransformBlock } from './Blocks/vector4TransformBlock';
+import { VertexOutputBlock } from './Blocks/Vertex/vertexOutputBlock';
+import { FragmentOutputBlock } from './Blocks/Fragment/fragmentOutputBlock';
 
 // declare NODEEDITOR namespace for compilation issue
 declare var NODEEDITOR: any;
@@ -783,4 +786,38 @@ export class NodeMaterial extends PushMaterial {
             }
         });
     }
+
+    /**
+     * Clear the current material
+     */
+    public clear() {
+        this._vertexOutputNodes = [];
+        this._fragmentOutputNodes = [];
+    }
+
+    /**
+     * Clear the current material and set it to a default state
+     */
+    public setToDefault() {
+        this.clear();
+
+        var worldPos = new Vector4TransformBlock("worldPos");
+        worldPos.vector.setAsAttribute("position");
+        worldPos.transform.setAsWellKnownValue(BABYLON.NodeMaterialWellKnownValues.World);
+
+        var worldPosdMultipliedByViewProjection = new Vector4TransformBlock("worldPos * viewProjectionTransform");
+        worldPos.connectTo(worldPosdMultipliedByViewProjection);
+        worldPosdMultipliedByViewProjection.transform.setAsWellKnownValue(BABYLON.NodeMaterialWellKnownValues.ViewProjection);
+
+        var vertexOutput = new VertexOutputBlock("vertexOutput");
+        worldPosdMultipliedByViewProjection.connectTo(vertexOutput);
+
+        // Pixel       
+        var pixelOutput = new FragmentOutputBlock("pixelOutput");
+        pixelOutput.color.value = new Color4(0.8, 0.8, 0.8, 1);
+
+        // Add to nodes
+        this.addOutputNode(vertexOutput);
+        this.addOutputNode(pixelOutput);
+    }
 }

+ 20 - 11
src/Materials/Node/nodeMaterialBlockConnectionPoint.ts

@@ -26,6 +26,9 @@ export class NodeMaterialConnectionPoint {
     /** @hidden */
     public _wellKnownValue: Nullable<NodeMaterialWellKnownValues> = null;
 
+    /** @hidden */
+    public _needToEmitVarying = true;
+
     /**
      * Gets or sets the connection point type (default is float)
      */
@@ -315,8 +318,8 @@ export class NodeMaterialConnectionPoint {
      * @param scene defines the hosting scene
      */
     public transmit(effect: Effect, scene: Scene) {
+        let variableName = this.associatedVariableName;
         if (this._wellKnownValue) {
-            let variableName = this.associatedVariableName;
             switch (this._wellKnownValue) {
                 case NodeMaterialWellKnownValues.World:
                 case NodeMaterialWellKnownValues.WorldView:
@@ -337,36 +340,42 @@ export class NodeMaterialConnectionPoint {
 
         let value = this._valueCallback ? this._valueCallback() : this._storedValue;
 
+        if (value === null) {
+            return;
+        }
+
         switch (this.type) {
             case NodeMaterialBlockConnectionPointTypes.Float:
-                effect.setFloat(this.name, value);
+                effect.setFloat(variableName, value);
                 break;
             case NodeMaterialBlockConnectionPointTypes.Int:
-                effect.setInt(this.name, value);
+                effect.setInt(variableName, value);
                 break;
-            case NodeMaterialBlockConnectionPointTypes.Color3OrColor4:
             case NodeMaterialBlockConnectionPointTypes.Color3:
-                effect.setColor3(this.name, value);
+                effect.setColor3(variableName, value);
                 break;
             case NodeMaterialBlockConnectionPointTypes.Color4:
-                effect.setDirectColor4(this.name, value);
+                effect.setDirectColor4(variableName, value);
                 break;
             case NodeMaterialBlockConnectionPointTypes.Vector2:
-                effect.setVector2(this.name, value);
+                effect.setVector2(variableName, value);
                 break;
             case NodeMaterialBlockConnectionPointTypes.Vector3:
-                effect.setVector3(this.name, value);
+                effect.setVector3(variableName, value);
+                break;
+            case NodeMaterialBlockConnectionPointTypes.Color3OrColor4:
+                effect.setFloat4(variableName, value.r, value.g, value.b, value.a || 1.0);
                 break;
             case NodeMaterialBlockConnectionPointTypes.Vector4OrColor4:
             case NodeMaterialBlockConnectionPointTypes.Vector4:
-                effect.setVector4(this.name, value);
+                effect.setVector4(variableName, value);
                 break;
             case NodeMaterialBlockConnectionPointTypes.Matrix:
-                effect.setMatrix(this.name, value);
+                effect.setMatrix(variableName, value);
                 break;
             case NodeMaterialBlockConnectionPointTypes.Texture:
             case NodeMaterialBlockConnectionPointTypes.Texture3D:
-                effect.setTexture(this.name, value);
+                effect.setTexture(variableName, value);
                 break;
         }
     }

+ 6 - 1
src/Materials/Node/nodeMaterialBuildState.ts

@@ -144,12 +144,12 @@ export class NodeMaterialBuildState {
             case NodeMaterialBlockConnectionPointTypes.Color3:
             case NodeMaterialBlockConnectionPointTypes.Vector3:
             case NodeMaterialBlockConnectionPointTypes.Vector3OrColor3:
-            case NodeMaterialBlockConnectionPointTypes.Color3OrColor4:
                 return "vec3";
             case NodeMaterialBlockConnectionPointTypes.Color4:
             case NodeMaterialBlockConnectionPointTypes.Vector4:
             case NodeMaterialBlockConnectionPointTypes.Vector4OrColor4:
             case NodeMaterialBlockConnectionPointTypes.Vector3OrVector4:
+            case NodeMaterialBlockConnectionPointTypes.Color3OrColor4:
                 return "vec4";
             case NodeMaterialBlockConnectionPointTypes.Matrix:
                 return "mat4";
@@ -369,6 +369,11 @@ export class NodeMaterialBuildState {
 
             if (this.target === NodeMaterialBlockTargets.Fragment) { // Attribute for fragment need to be carried over by varyings
                 this._vertexState._emitUniformOrAttributes(point);
+
+                if (point._needToEmitVarying) {
+                    this._vertexState._emitVaryings(point, undefined, true, true, "v" + point.associatedVariableName);
+                    point.associatedVariableName = "v" + point.associatedVariableName;
+                }
                 return;
             }