Trevor Baron 6 anos atrás
pai
commit
1e47fabc9b

+ 18 - 0
nodeEditor/src/components/customDiragramNodes/generic/genericNodeFactory.tsx

@@ -0,0 +1,18 @@
+import * as SRD from "storm-react-diagrams";
+import { GenericNodeWidget } from "./genericNodeWidget";
+import { GenericNodeModel } from "./genericNodeModel";
+import * as React from "react";
+
+export class GenericNodeFactory extends SRD.AbstractNodeFactory {
+	constructor() {
+		super("generic");
+	}
+
+	generateReactWidget(diagramEngine: SRD.DiagramEngine, node: GenericNodeModel): JSX.Element {
+		return <GenericNodeWidget node={node} />;
+	}
+
+	getNewInstance() {
+		return new GenericNodeModel();
+	}
+}

+ 22 - 0
nodeEditor/src/components/customDiragramNodes/generic/genericNodeModel.ts

@@ -0,0 +1,22 @@
+import { NodeModel } from "storm-react-diagrams";
+import { Nullable } from 'babylonjs/types';
+import { NodeMaterialBlock } from 'babylonjs/Materials/Node/nodeMaterialBlock';
+import { Texture } from 'babylonjs/Materials/Textures/texture';
+import { Vector2, Vector3, Vector4, Matrix } from 'babylonjs/Maths/math';
+
+export class GenericNodeModel extends NodeModel {
+	public block:Nullable<NodeMaterialBlock> = null;
+	public headerLabels:Array<{text: string}> = []
+	texture: Nullable<Texture> = null;
+	vector2: Nullable<Vector2> = null;
+	vector3: Nullable<Vector3> = null;
+	vector4: Nullable<Vector4> = null;
+	matrix: Nullable<Matrix> = null;
+	//public textureInputs:Array<{text: string, initialValue: string}> = []
+
+	constructor() {
+		super("generic");
+		//this.addPort(new GenericPortModel("right"));
+	}
+
+}

+ 152 - 0
nodeEditor/src/components/customDiragramNodes/generic/genericNodeWidget.tsx

@@ -0,0 +1,152 @@
+import * as React from "react";
+import { PortWidget } from "storm-react-diagrams";
+import { GenericNodeModel } from './genericNodeModel';
+import { GenericPortModel } from './genericPortModel';
+import {TextureLineComponent} from "../../../../../inspector/src/components/actionTabs/lines/textureLineComponent"
+import {FileButtonLineComponent} from "../../../../../inspector/src/components/actionTabs/lines/fileButtonLineComponent"
+import { Vector2LineComponent } from '../../../../../inspector/src/components/actionTabs/lines/vector2LineComponent';
+import { Vector3LineComponent } from '../../../../../inspector/src/components/actionTabs/lines/vector3LineComponent';
+import { Nullable } from 'babylonjs/types';
+import { Texture } from 'babylonjs/Materials/Textures/texture';
+import { Engine } from 'babylonjs/Engines/engine';
+import { Tools } from 'babylonjs/Misc/tools';
+
+export interface GenericNodeWidgetProps {
+	node: Nullable<GenericNodeModel>;
+}
+
+export interface GenericNodeWidgetState {}
+
+
+export class GenericNodeWidget extends React.Component<GenericNodeWidgetProps, GenericNodeWidgetState> {
+
+	constructor(props: GenericNodeWidgetProps) {
+		super(props);
+		this.state = {}
+	}
+
+	// componentDidUpdate() {
+	// 	this.updateTexture()
+	// }
+
+	// componentDidMount() {
+	// 	this.updateTexture()
+	// }
+
+
+	replaceTexture(file: File) {
+		if(!this.props.node){
+			return;
+		}
+		let texture = this.props.node.texture as Texture;
+		if(!texture){
+			this.props.node.texture = new Texture(null, Engine.LastCreatedScene)
+			texture = this.props.node.texture;
+		}
+        Tools.ReadFile(file, (data) => {
+            var blob = new Blob([data], { type: "octet/stream" });
+            var url = URL.createObjectURL(blob);
+
+            if (texture.isCube) {
+                let extension: string | undefined = undefined;
+                if (file.name.toLowerCase().indexOf(".dds") > 0) {
+                    extension = ".dds";
+                } else if (file.name.toLowerCase().indexOf(".env") > 0) {
+                    extension = ".env";
+                }
+
+                (texture as Texture).updateURL(url, extension, () => this.forceUpdate());
+            } else {
+                (texture as Texture).updateURL(url, null, () => this.forceUpdate());
+            }
+			(this.refs.textureView as TextureLineComponent).updatePreview()
+        }, undefined, true);
+    }
+
+	render() {
+		var headers = new Array<JSX.Element>()
+		var inputPorts = new Array<JSX.Element>()
+		var outputPorts = new Array<JSX.Element>()
+		var value = <div></div>
+		if(this.props.node){
+			// Header labels
+			this.props.node.headerLabels.forEach((h, i)=>{
+				headers.push(<div style={{fontWeight: "bold", borderBottomStyle: "solid"}} key={i}>{h.text}</div>)
+			})
+
+			// Input/Output ports
+			for(var key in this.props.node.ports){
+				var port = this.props.node.ports[key] as GenericPortModel;
+				if(port.position == "input"){
+					var control = <div></div>
+
+					var color = "black"
+					if(port.connection){
+						if(port.connection.isAttribute){
+							color = "red"
+						}else if(port.connection.isUniform){
+							color = "brown"
+						}
+						else if(port.connection.isVarying){
+							color = "purple"
+						}
+					}
+
+					inputPorts.push(
+						<div key={key} style={{paddingBottom: "8px"}}>
+							<div style={{display: "inline-block", borderStyle: "solid", marginBottom: "-4px", position: "absolute", left: "-17px", background: "#777777"}}>
+								<PortWidget key={key} name={port.name} node={this.props.node} />
+							</div>
+							<div style={{display: "inline-block", color: color}}>
+								{port.name} 
+							</div>
+							{control}
+						</div>
+					)
+				}else{
+					outputPorts.push(
+						<div key={key} style={{paddingBottom: "8px"}}>
+							<div style={{display: "inline-block"}}>
+								{port.name}
+							</div>
+							<div style={{display: "inline-block", borderStyle: "solid", marginBottom: "-4px", position: "absolute", right: "-17px", background: "#777777"}}>
+								<PortWidget key={key} name={port.name} node={this.props.node} />
+							</div>
+						</div>
+					)
+				}
+				
+			}
+
+			if(this.props.node.texture){
+				value = (
+					<div>
+						<TextureLineComponent ref="textureView" width={100} height={100} texture={this.props.node.texture} hideChannelSelect={true}/>
+						<FileButtonLineComponent label="" onClick={(file) => this.replaceTexture(file)} accept=".jpg, .png, .tga, .dds, .env" />
+					</div>
+				)
+			} else if(this.props.node.vector3){
+				value = (
+					<div style={{width: "220px"}}>
+						<Vector3LineComponent label="" target={this.props.node} propertyName="vector3"></Vector3LineComponent>
+					</div>
+				)
+			} else if(this.props.node.vector2){
+				value = (
+					<div style={{width: "220px"}}>
+						<Vector2LineComponent label="" target={this.props.node} propertyName="vector2"></Vector2LineComponent>
+					</div>
+				)
+			}
+		}
+
+		return (
+			<div style={{background: "white", borderStyle: "solid", padding: "10px"}}>
+				{headers}
+				{inputPorts}
+				{outputPorts}
+				{value}
+			</div>
+		);
+	}
+}

+ 91 - 0
nodeEditor/src/components/customDiragramNodes/generic/genericPortModel.ts

@@ -0,0 +1,91 @@
+import { LinkModel, PortModel, DefaultLinkModel } from "storm-react-diagrams";
+import { Nullable } from 'babylonjs/types';
+import { NodeMaterialConnectionPoint } from 'babylonjs/Materials/Node/nodeMaterialBlockConnectionPoint';
+import { GenericNodeModel } from './genericNodeModel';
+
+
+export class GenericPortModel extends PortModel {
+	position: string | "input" | "output";
+	connection: Nullable<NodeMaterialConnectionPoint> = null;
+	static idCounter = 0;
+
+	constructor(name:string, type: string = "input") {
+		//(""+GenericPortModel.idCounter)
+		super(name, "generic");
+		this.position = type;
+		GenericPortModel.idCounter++;
+		// this.addListener({
+		// 	selectionChanged: ()=>{
+		// 		console.log("change")
+		// 	},
+		// 	lockChanged: ()=>{
+		// 		console.log("lock")
+		// 	},
+		// 	entityRemoved: ()=>{
+		// 		console.log("rem")
+		// 	}
+		// })
+	}
+
+	syncWithNodeMaterialConnectionPoint(connection:NodeMaterialConnectionPoint){
+		this.connection = connection;
+		this.name = connection.name;
+	}
+
+	getNodeModel(){
+		return this.parent as GenericNodeModel
+	}
+
+	// serialize() {
+	// 	return _.merge(super.serialize(), {
+	// 		position: this.position
+	// 	});
+	// }
+
+	// deSerialize(data: any, engine: DiagramEngine) {
+	// 	super.deSerialize(data, engine);
+	// 	this.position = data.position;
+	// }
+
+	link(outPort:GenericPortModel){
+		var link = this.createLinkModel()
+		// link.addListener({
+		// 	selectionChanged: ()=>{
+		// 		console.log("hit")
+		// 	}
+		// })
+		link.setSourcePort(this)
+		link.setTargetPort(outPort)
+		return link;
+	}
+
+	getInputFromBlock(){
+
+	}
+
+	createLinkModel(): LinkModel {
+		return new DefaultLinkModel();
+	}
+
+	getValue:Function = ()=>{
+		return null;
+	}
+
+	static SortInputOutput(a:Nullable<GenericPortModel>, b:Nullable<GenericPortModel>){
+		if(!a || !b){
+			return null;
+		}else if(a.position == "output" && b.position == "input"){
+			return {
+				input: b,
+				output: a
+			}
+		}else if(b.position == "output" && a.position == "input"){
+			return {
+				input: a,
+				output: b
+			}
+		}else{
+			return null;
+		}
+	}
+}

+ 18 - 0
nodeEditor/src/components/customDiragramNodes/texture/textureNodeFactory.tsx

@@ -0,0 +1,18 @@
+import * as SRD from "storm-react-diagrams";
+import { TextureNodeWidget } from "./textureNodeWidget";
+import { TextureNodeModel } from "./textureNodeModel";
+import * as React from "react";
+
+export class TextureNodeFactory extends SRD.AbstractNodeFactory {
+	constructor() {
+		super("texture");
+	}
+
+	generateReactWidget(diagramEngine: SRD.DiagramEngine, node: SRD.NodeModel): JSX.Element {
+		return <TextureNodeWidget node={node} />;
+	}
+
+	getNewInstance() {
+		return new TextureNodeModel();
+	}
+}

+ 12 - 0
nodeEditor/src/components/customDiragramNodes/texture/textureNodeModel.ts

@@ -0,0 +1,12 @@
+import { NodeModel } from "storm-react-diagrams";
+import { TexturePortModel } from './texturePortModel';
+
+export class TextureNodeModel extends NodeModel {
+	constructor() {
+		super("texture");
+		this.addPort(new TexturePortModel("right"));
+		// this.addPort(new DiamondPortModel("left"));
+		// this.addPort(new DiamondPortModel("bottom"));
+		// this.addPort(new DiamondPortModel("right"));
+	}
+}

+ 32 - 0
nodeEditor/src/components/customDiragramNodes/texture/textureNodeWidget.tsx

@@ -0,0 +1,32 @@
+import * as React from "react";
+import { PortWidget } from "storm-react-diagrams";
+
+export interface TextureNodeWidgetProps {
+	node: any;
+	size?: number;
+}
+
+export interface TextureNodeWidgetState {}
+
+
+export class TextureNodeWidget extends React.Component<TextureNodeWidgetProps, TextureNodeWidgetState> {
+	public static defaultProps: TextureNodeWidgetProps = {
+		size: 150,
+		node: null
+	};
+
+	constructor(props: TextureNodeWidgetProps) {
+		super(props);
+		this.state = {};
+	}
+
+	render() {
+		return (
+			<div style={{background: "white", borderStyle: "solid", padding: "10px"}}>
+				<p>Texture Node</p>
+				<img src="../Playground/textures/bloc.jpg" width="30px"></img>
+				<PortWidget name="right" node={this.props.node} />
+			</div>
+		);
+	}
+}

+ 25 - 0
nodeEditor/src/components/customDiragramNodes/texture/texturePortModel.ts

@@ -0,0 +1,25 @@
+import { LinkModel, PortModel, DefaultLinkModel } from "storm-react-diagrams";
+
+export class TexturePortModel extends PortModel {
+	position: string | "top" | "bottom" | "left" | "right";
+
+	constructor(pos: string = "top") {
+		super(pos, "texture");
+		this.position = pos;
+	}
+
+	// serialize() {
+	// 	return _.merge(super.serialize(), {
+	// 		position: this.position
+	// 	});
+	// }
+
+	// deSerialize(data: any, engine: DiagramEngine) {
+	// 	super.deSerialize(data, engine);
+	// 	this.position = data.position;
+	// }
+
+	createLinkModel(): LinkModel {
+		return new DefaultLinkModel();
+	}
+}

+ 364 - 1
nodeEditor/src/components/graphEditor.tsx

@@ -1,20 +1,383 @@
+import {
+	DiagramEngine,
+	DiagramModel,
+	DiagramWidget,
+    MoveCanvasAction
+} from "storm-react-diagrams";
+
 import * as React from "react";
 import { GlobalState } from '../globalState';
 
+import { GenericNodeFactory } from './customDiragramNodes/generic/genericNodeFactory';
+import { NodeMaterialBlockConnectionPointTypes } from 'babylonjs/Materials/Node/nodeMaterialBlockConnectionPointTypes';
+import { GenericNodeModel } from './customDiragramNodes/generic/genericNodeModel';
+import { GenericPortModel } from './customDiragramNodes/generic/genericPortModel';
+import { Engine } from 'babylonjs/Engines/engine';
+import { LineContainerComponent } from "../../../inspector/src/components/actionTabs/lineContainerComponent"
+import { ButtonLineComponent } from '../../../inspector/src/components/actionTabs/lines/buttonLineComponent';
+import { NodeMaterialBlock } from 'babylonjs/Materials/Node/nodeMaterialBlock';
+import { NodeMaterialConnectionPoint } from 'babylonjs/Materials/Node/nodeMaterialBlockConnectionPoint';
+import { Texture } from 'babylonjs/Materials/Textures/texture';
+import { Vector2, Vector3, Vector4, Matrix } from 'babylonjs/Maths/math';
+import { AlphaTestBlock } from 'babylonjs/Materials/Node/Blocks/Fragment/alphaTestBlock';
+import { FragmentOutputBlock } from 'babylonjs/Materials/Node/Blocks/Fragment/fragmentOutputBlock';
+import { ImageProcessingBlock } from 'babylonjs/Materials/Node/Blocks/Fragment/imageProcessingBlock';
+import { RGBAMergerBlock } from 'babylonjs/Materials/Node/Blocks/Fragment/rgbaMergerBlock';
+import { RGBASplitterBlock } from 'babylonjs/Materials/Node/Blocks/Fragment/rgbaSplitterBlock';
+import { TextureBlock } from 'babylonjs/Materials/Node/Blocks/Fragment/textureBlock';
+import { BonesBlock } from 'babylonjs/Materials/Node/Blocks/Vertex/bonesBlock';
+import { InstancesBlock } from 'babylonjs/Materials/Node/Blocks/Vertex/instancesBlock';
+import { MorphTargetsBlock } from 'babylonjs/Materials/Node/Blocks/Vertex/morphTargetsBlock';
+import { VertexOutputBlock } from 'babylonjs/Materials/Node/Blocks/Vertex/vertexOutputBlock';
+import { FogBlock } from 'babylonjs/Materials/Node/Blocks/Dual/fogBlock';
+import { AddBlock } from 'babylonjs/Materials/Node/Blocks/addBlock';
+import { ClampBlock } from 'babylonjs/Materials/Node/Blocks/clampBlock';
+import { MatrixMultiplicationBlock } from 'babylonjs/Materials/Node/Blocks/matrixMultiplicationBlock';
+import { MultiplyBlock } from 'babylonjs/Materials/Node/Blocks/multiplyBlock';
+import { Vector2TransformBlock } from 'babylonjs/Materials/Node/Blocks/vector2TransformBlock';
+import { Vector3TransformBlock } from 'babylonjs/Materials/Node/Blocks/vector3TransformBlock';
+import { Vector4TransformBlock } from 'babylonjs/Materials/Node/Blocks/vector4TransformBlock';
+require("../../../inspector/src/components/actionTabs/actionTabs.scss");
+require("storm-react-diagrams/dist/style.min.css");
+/*
+Data vs View
+NodeMaterialBlock = GenericNodeModel
+NodeMaterialConnectionPoint = GenericPortModel (Connection is a LinkModel, which is a built in react-storm type)
+
+You can only access data from view, view is not accessible from data
+
+Traversing data to create view is done in createNodeFromObject method
+*/
+
+
+
+
+
 interface IGraphEditorProps {
     globalState: GlobalState;
 }
 
 export class GraphEditor extends React.Component<IGraphEditorProps> {
+    engine:DiagramEngine;
+    model: DiagramModel;
+
+    nodes = new Array<any>();
+
+    rowPos = new Array<number>()
+    
+    /**
+     * Creates a node and recursivly creates its parent nodes from it's input
+     * @param nodeMaterialBlock 
+     */
+    public createNodeFromObject(
+        options:{
+            column:number,
+            nodeMaterialBlock?:NodeMaterialBlock                      
+        }
+    ){
+        // Update rows/columns
+        if(this.rowPos[options.column] == undefined){
+            this.rowPos[options.column] = 0;
+        }else{
+            this.rowPos[options.column]++;
+        }
+
+        // Create new node in the graph
+        var outputNode = new GenericNodeModel();
+        this.nodes.push(outputNode)
+        outputNode.setPosition(1600-(300*options.column), 200*this.rowPos[options.column])
+        this.model.addAll(outputNode);
+
+        if(options.nodeMaterialBlock){
+            outputNode.block = options.nodeMaterialBlock
+            outputNode.headerLabels.push({text: options.nodeMaterialBlock.getClassName()})
+
+            // Create output ports
+            options.nodeMaterialBlock._outputs.forEach((connection:any)=>{
+                var outputPort = new GenericPortModel(connection.name, "output");
+                outputPort.syncWithNodeMaterialConnectionPoint(connection);
+                outputNode.addPort(outputPort)
+            })
+
+            // Create input ports and nodes if they exist
+            options.nodeMaterialBlock._inputs.forEach((connection)=>{
+                var inputPort = new GenericPortModel(connection.name, "input");
+                inputPort.connection = connection;
+                outputNode.addPort(inputPort)
+                
+                if(connection._connectedPoint){
+                    // Block is not a leaf node, create node for the given block type
+                    var connectedNode;
+                    var existingNodes = this.nodes.filter((n)=>{return n.block == (connection as any)._connectedPoint._ownerBlock});
+                    if(existingNodes.length == 0){
+                        connectedNode = this.createNodeFromObject({column: options.column+1, nodeMaterialBlock: connection._connectedPoint._ownerBlock});
+                    }else{
+                        connectedNode = existingNodes[0];
+                    }
+           
+                    let link = connectedNode.ports[connection._connectedPoint.name].link(inputPort);
+                    this.model.addAll(link);
+                    
+                }else {
+                    // Create value node for the connection
+                    var type = ""
+                    if(connection.type == NodeMaterialBlockConnectionPointTypes.Texture){
+                        type = "Texture"
+                    } else if(connection.type == NodeMaterialBlockConnectionPointTypes.Matrix){
+                        type = "Matrix"
+                    } else if(connection.type & NodeMaterialBlockConnectionPointTypes.Vector3OrColor3){
+                        type = "Vector3"
+                    } else if(connection.type & NodeMaterialBlockConnectionPointTypes.Vector2){
+                        type = "Vector2"
+                    }else if(connection.type & NodeMaterialBlockConnectionPointTypes.Vector3OrColor3OrVector4OrColor4){
+                        type = "Vector4"
+                    }
+                    
+                    // Create links
+                    var localNode = this.addValueNode(type, options.column+1, connection);
+                    var ports = localNode.getPorts()
+                    for(var key in ports){
+                        let link = (ports[key] as GenericPortModel).link(inputPort);
+                        this.model.addAll(link);
+                    }
+                }
+            })
+        }
+        
+        
+    
+        return outputNode;
+    }
+
+    componentDidMount(){
+        if(this.props.globalState.hostDocument){
+            var widget = (this.refs["test"] as DiagramWidget);
+            widget.setState({document: this.props.globalState.hostDocument})
+            this.props.globalState.hostDocument!.addEventListener("keyup", widget.onKeyUpPointer as any, false);
+        }
+    }
+
+    componentWillUnmount(){
+        if(this.props.globalState.hostDocument){
+            var widget = (this.refs["test"] as DiagramWidget);
+            this.props.globalState.hostDocument!.removeEventListener("keyup", widget.onKeyUpPointer as any, false);
+        }
+    }
+
     constructor(props: IGraphEditorProps) {
         super(props);
+        
+
+        // setup the diagram engine
+        this.engine = new DiagramEngine();
+        this.engine.installDefaultFactories()
+        this.engine.registerNodeFactory(new GenericNodeFactory());
+
+        // setup the diagram model
+        this.model = new DiagramModel();
+
+        // Listen to events to connect/disconnect blocks or
+        this.model.addListener({
+            linksUpdated: (e)=>{
+                if(!e.isCreated){
+                    // Link is deleted
+                    console.log("link deleted");
+                    var link = GenericPortModel.SortInputOutput(e.link.sourcePort as GenericPortModel, e.link.targetPort as GenericPortModel);
+                    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 = GenericPortModel.SortInputOutput(e.link.sourcePort as GenericPortModel, e.link.targetPort as GenericPortModel);
+                        
+                        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();
+                                
+                            }
+                            if(this.props.globalState.nodeMaterial){
+                                this.props.globalState.nodeMaterial.build()
+                            }
+                        }
+                    }
+                    
+                })
+                
+            },
+            nodesUpdated: (e)=>{
+                if(e.isCreated){
+                    console.log("new node")
+                }else{
+                    console.log("node deleted")
+                }
+            }
+        })
+
+        // Load graph of nodes from the material
+        if(this.props.globalState.nodeMaterial){
+            var material:any = this.props.globalState.nodeMaterial;
+            material._vertexOutputNodes.forEach((n:any)=>{
+                this.createNodeFromObject({column: 0, nodeMaterialBlock: n});
+            })
+            material._fragmentOutputNodes.forEach((n:any)=>{
+                this.createNodeFromObject({column: 0, nodeMaterialBlock: n});
+            })
+        }
+
+        // Zoom out a bit at the start
+        this.model.setZoomLevel(20)
+
+        // load model into engine
+        this.engine.setDiagramModel(this.model);
+    }
+
+    addNodeFromClass(ObjectClass:typeof NodeMaterialBlock){
+        var block = new ObjectClass(ObjectClass.prototype.getClassName()+"sdfsdf")
+        var localNode = this.createNodeFromObject({column: 0, nodeMaterialBlock: block})
+        var widget = (this.refs["test"] as DiagramWidget);
+       
+        this.forceUpdate()
+
+        // This is needed to fix link offsets when created, (eg. create a fog block)
+        // Todo figure out how to correct this without this
+        setTimeout(() => {
+            widget.startFiringAction(new MoveCanvasAction(1,0, this.model));
+        }, 500);
+
+        return localNode
+    }
+
+    addValueNode(type: string, column = 0, connection?: NodeMaterialConnectionPoint){
+        var localNode = this.createNodeFromObject({column: column})
+        var outPort = new GenericPortModel(type, "output");
+        if(type == "Texture"){
+            outPort.getValue = ()=>{
+                return localNode.texture;
+            }
+            if(connection && connection.value){
+                localNode.texture = connection.value
+            }else{
+                localNode.texture = new Texture(null, Engine.LastCreatedScene)
+            }
+        }else if(type == "Vector2"){
+            outPort.getValue = ()=>{
+                return localNode.vector2;
+            }
+            if(connection && connection.value){
+                localNode.vector2 = connection.value
+            }else{
+                localNode.vector2 = new Vector2()
+            }
+        }else if(type == "Vector3"){
+            outPort.getValue = ()=>{
+                return localNode.vector3;
+            }
+            if(connection && connection.value){
+                localNode.vector3 = connection.value
+            }else{
+                localNode.vector3 = new Vector3()
+            }
+        }else if(type == "Vector4"){
+            outPort.getValue = ()=>{
+                return localNode.vector4;
+            }
+            if(connection && connection.value){
+                localNode.vector4 = connection.value
+            }else{
+                localNode.vector4 = new Vector4(0,0,0,1)
+            }
+        }else if(type == "Matrix"){
+            outPort.getValue = ()=>{
+                return localNode.matrix;
+            }
+            if(connection && connection.value){
+                localNode.matrix = connection.value
+            }else{
+                localNode.matrix = new Matrix()
+            }
+        }else{
+            console.log("Node type "+type+"is not supported")
+        }
+        localNode.addPort(outPort)
+        this.forceUpdate()
+
+        return localNode;
+    }
+
+    
+
+    // Block types used to create the menu from
+    allBlocks = {
+        Fragment: [AlphaTestBlock, FragmentOutputBlock, ImageProcessingBlock, RGBAMergerBlock, RGBASplitterBlock, TextureBlock],
+        Vertex: [BonesBlock, InstancesBlock, MorphTargetsBlock, VertexOutputBlock],
+        Dual: [FogBlock],
+        Other: [AddBlock, ClampBlock, MatrixMultiplicationBlock, MultiplyBlock, Vector2TransformBlock, Vector3TransformBlock, Vector4TransformBlock],
+        Value: ["Texture", "Vector2", "Vector3", "Matrix"],
     }
 
     render() {
+        // Create node menu
+        var blockMenu = []
+        for(var key in this.allBlocks){
+            var blockList = (this.allBlocks as any)[key].map((b:any)=>{
+                var label = typeof b === "string" ? b : b.prototype.getClassName()
+                var onClick =typeof b === "string" ? () => {this.addValueNode(b)} : () => {this.addNodeFromClass(b)};
+                return  <ButtonLineComponent label={label} onClick={onClick} />
+            })
+            blockMenu.push(
+                <LineContainerComponent  title={key+" blocks"}>
+                    {blockList}
+                </LineContainerComponent>
+            )
+        }
 
         return (
-            <div>
+            <div style={{
+                display: "flex",
+                height: "100%",
+                background: "#464646",
+            }}>
+                {/* Node creation menu */}
+                <div id="actionTabs" style={{width: "170px", borderRightStyle: "solid", borderColor: "grey", borderWidth: "1px" }} >
+                    <div className="tabs" style={{gridTemplateRows: "0px 1fr"}}>
+                        <div className="labels"/>
+                        <div className="panes">
+                            <div className="pane">
+                                {blockMenu}
+                            </div>
+                        </div>
+                    </div>
+                </div>
+                
+                {/* The node graph diagram */}
+                <DiagramWidget deleteKeys={[46]} ref={"test"} inverseZoom={true} className="srd-demo-canvas" diagramEngine={this.engine} maxNumberPointsPerLink={0} />
             </div>
         );
+
     }
 }

+ 3 - 1
nodeEditor/src/globalState.ts

@@ -1,3 +1,5 @@
-
+import {NodeMaterial, Nullable} from "babylonjs"
 export class GlobalState {
+    nodeMaterial?:NodeMaterial;
+    hostDocument?:Nullable<Document>;
 }

+ 42 - 2
nodeEditor/src/nodeEditor.ts

@@ -2,7 +2,8 @@ import * as React from "react";
 import * as ReactDOM from "react-dom";
 import { GlobalState } from './globalState';
 import { GraphEditor } from './components/graphEditor';
-
+import {NodeMaterial} from "babylonjs"
+import {Inspector} from "../../inspector/src"
 /**
  * Interface used to specify creation options for the node editor
  */
@@ -10,7 +11,8 @@ export interface INodeEditorOptions {
     /**
      * Defines the DOM element that will host the node editor
      */
-    hostElement: HTMLDivElement
+    hostElement?: HTMLDivElement
+    nodeMaterial?: NodeMaterial
 }
 
 /**
@@ -22,13 +24,51 @@ export class NodeEditor {
      * @param options defines the options to use to configure the node editor
      */
     public static Show(options: INodeEditorOptions) {
+        if(!options.hostElement){
+            
+            // var divElement = document.createElement("div");
+            // document.body.prepend(divElement)
+            // divElement.id = "node-editor";
+            // divElement.style.background = "#474646"
+            // divElement.style.width = "100%"
+            // divElement.style.height = "300px"
+            // divElement.style.display = "flex"
+            // options.hostElement = divElement
+            // debugger;
+
+            // var canvas = EngineStore.LastCreatedEngine!.getRenderingCanvas();
+            // let parentControl = (canvas!.parentElement) as HTMLDivElement;
+            // Inspector._CreateCanvasContainer(parentControl)
+            // options.hostElement = parentControl!;//Inspector._CreatePopup("SCENE EXPLORER", "node-editor")!;
+
+            options.hostElement = Inspector._CreatePopup("SCENE EXPLORER", "node-editor", 1000, 800)!;
+            
+        }
         let globalState = new GlobalState();
+        globalState.nodeMaterial = options.nodeMaterial
+        globalState.hostDocument = options.hostElement.ownerDocument
 
         const graphEditor = React.createElement(GraphEditor, {
             globalState: globalState
         });
 
         ReactDOM.render(graphEditor, options.hostElement);
+
+        // Close the popup window when the page is refreshed or scene is disposed
+        var popupWindow = (Inspector as any)["node-editor"];
+        if(globalState.nodeMaterial && popupWindow){
+            globalState.nodeMaterial.getScene().onDisposeObservable.addOnce(()=>{
+                if(popupWindow){
+                    popupWindow.close();
+                }
+            })
+            window.onbeforeunload = function(event) {
+                var popupWindow = (Inspector as any)["node-editor"];
+                if(popupWindow){
+                    popupWindow.close();
+                }
+            };
+        }
     }
 }
 

+ 1 - 1
nodeEditor/tsconfig.json

@@ -5,7 +5,7 @@
         "baseUrl": "./src/",
         "rootDir": "./src/",
         "paths": {
-            "babylonjs/*": [
+            "babylonjs": [
                 "../../dist/preview release/babylon.module.d.ts"
             ]
         }