Prechádzať zdrojové kódy

Merge pull request #7204 from BabylonJS/home-diagram

New diagram
David Catuhe 5 rokov pred
rodič
commit
39d325da39
88 zmenil súbory, kde vykonal 2511 pridanie a 2897 odobranie
  1. 19 1
      dist/preview release/babylon.d.ts
  2. 69 27
      dist/preview release/babylon.max.js
  3. 38 2
      dist/preview release/babylon.module.d.ts
  4. 1 1
      nodeEditor/src/blockTools.ts
  5. 0 23
      nodeEditor/src/components/diagram/clamp/clampNodeFactory.tsx
  6. 0 25
      nodeEditor/src/components/diagram/clamp/clampNodeModel.tsx
  7. 0 64
      nodeEditor/src/components/diagram/clamp/clampNodeWidget.tsx
  8. 0 69
      nodeEditor/src/components/diagram/defaultNodeModel.ts
  9. 0 319
      nodeEditor/src/components/diagram/diagram.scss
  10. 0 39
      nodeEditor/src/components/diagram/generic/genericNodeFactory.tsx
  11. 0 84
      nodeEditor/src/components/diagram/generic/genericNodeModel.tsx
  12. 0 69
      nodeEditor/src/components/diagram/generic/genericNodeWidget.tsx
  13. 0 23
      nodeEditor/src/components/diagram/gradient/gradientNodeFactory.tsx
  14. 0 25
      nodeEditor/src/components/diagram/gradient/gradientNodeModel.tsx
  15. 0 67
      nodeEditor/src/components/diagram/gradient/gradientNodeWidget.tsx
  16. 0 1
      nodeEditor/src/components/diagram/images/Matrix.svg
  17. 0 1
      nodeEditor/src/components/diagram/images/Vector1.svg
  18. 0 1
      nodeEditor/src/components/diagram/images/Vector2.svg
  19. 0 1
      nodeEditor/src/components/diagram/images/Vector3.svg
  20. 0 1
      nodeEditor/src/components/diagram/images/Vector4.svg
  21. 0 39
      nodeEditor/src/components/diagram/input/inputNodeFactory.tsx
  22. 0 28
      nodeEditor/src/components/diagram/input/inputNodeModel.tsx
  23. 0 150
      nodeEditor/src/components/diagram/input/inputNodeWidget.tsx
  24. 0 39
      nodeEditor/src/components/diagram/light/lightNodeFactory.tsx
  25. 0 47
      nodeEditor/src/components/diagram/light/lightNodeModel.tsx
  26. 0 56
      nodeEditor/src/components/diagram/light/lightNodeWidget.tsx
  27. 0 39
      nodeEditor/src/components/diagram/lightInformation/lightInformationNodeFactory.tsx
  28. 0 44
      nodeEditor/src/components/diagram/lightInformation/lightInformationNodeModel.tsx
  29. 0 56
      nodeEditor/src/components/diagram/lightInformation/lightInformationNodeWidget.tsx
  30. 0 32
      nodeEditor/src/components/diagram/link/advancedLinkFactory.tsx
  31. 0 7
      nodeEditor/src/components/diagram/link/advancedLinkModel.tsx
  32. 0 75
      nodeEditor/src/components/diagram/port/defaultPortModel.ts
  33. 0 39
      nodeEditor/src/components/diagram/port/defaultPortWidget.tsx
  34. 0 114
      nodeEditor/src/components/diagram/portHelper.tsx
  35. 0 39
      nodeEditor/src/components/diagram/reflectionTexture/reflectionTextureNodeFactory.tsx
  36. 0 47
      nodeEditor/src/components/diagram/reflectionTexture/reflectionTextureNodeModel.tsx
  37. 0 61
      nodeEditor/src/components/diagram/reflectionTexture/reflectionTextureNodeWidget.tsx
  38. 0 39
      nodeEditor/src/components/diagram/remap/remapNodeFactory.tsx
  39. 0 28
      nodeEditor/src/components/diagram/remap/remapNodeModel.tsx
  40. 0 95
      nodeEditor/src/components/diagram/remap/remapNodeWidget.tsx
  41. 0 39
      nodeEditor/src/components/diagram/texture/textureNodeFactory.tsx
  42. 0 47
      nodeEditor/src/components/diagram/texture/textureNodeModel.tsx
  43. 0 61
      nodeEditor/src/components/diagram/texture/textureNodeWidget.tsx
  44. 0 39
      nodeEditor/src/components/diagram/trigonometry/trigonometryNodeFactory.tsx
  45. 0 28
      nodeEditor/src/components/diagram/trigonometry/trigonometryNodeModel.tsx
  46. 0 63
      nodeEditor/src/components/diagram/trigonometry/trigonometryNodeWidget.tsx
  47. 27 5
      nodeEditor/src/components/propertyTab/propertyTabComponent.tsx
  48. 28 0
      nodeEditor/src/diagram/display/clampDisplayManager.ts
  49. 9 0
      nodeEditor/src/diagram/display/displayManager.ts
  50. 29 0
      nodeEditor/src/diagram/display/gradientDisplayManager.ts
  51. 123 0
      nodeEditor/src/diagram/display/inputDisplayManager.ts
  52. 24 0
      nodeEditor/src/diagram/display/outputDisplayManager.ts
  53. 49 0
      nodeEditor/src/diagram/display/remapDisplayManager.ts
  54. 54 0
      nodeEditor/src/diagram/display/textureDisplayManager.ts
  55. 28 0
      nodeEditor/src/diagram/display/trigonometryDisplayManager.ts
  56. 21 0
      nodeEditor/src/diagram/displayLedger.ts
  57. 280 0
      nodeEditor/src/diagram/graphCanvas.scss
  58. 591 0
      nodeEditor/src/diagram/graphCanvas.tsx
  59. 384 0
      nodeEditor/src/diagram/graphNode.ts
  60. 116 0
      nodeEditor/src/diagram/nodeLink.ts
  61. 77 0
      nodeEditor/src/diagram/nodePort.ts
  62. 28 0
      nodeEditor/src/diagram/properties/PerturbNormalNodePropertyComponent.tsx
  63. 9 14
      nodeEditor/src/components/diagram/clamp/clampNodePropertyComponent.tsx
  64. 23 0
      nodeEditor/src/diagram/properties/genericNodePropertyComponent.tsx
  65. 11 17
      nodeEditor/src/components/diagram/gradient/gradientNodePropertyComponent.tsx
  66. 1 1
      nodeEditor/src/components/diagram/gradient/gradientStepComponent.tsx
  67. 29 27
      nodeEditor/src/components/diagram/input/inputNodePropertyComponent.tsx
  68. 12 15
      nodeEditor/src/components/diagram/lightInformation/lightInformationPropertyTabComponent.tsx
  69. 13 16
      nodeEditor/src/components/diagram/light/lightPropertyTabComponent.tsx
  70. 7 0
      nodeEditor/src/diagram/properties/propertyComponentProps.ts
  71. 9 14
      nodeEditor/src/components/diagram/remap/remapNodePropertyComponent.tsx
  72. 37 42
      nodeEditor/src/components/diagram/texture/texturePropertyTabComponent.tsx
  73. 36 0
      nodeEditor/src/diagram/properties/transformNodePropertyComponent.tsx
  74. 9 15
      nodeEditor/src/components/diagram/trigonometry/trigonometryNodePropertyComponent.tsx
  75. 27 0
      nodeEditor/src/diagram/properties/worleyNoise3DNodePropertyComponent.tsx
  76. 30 0
      nodeEditor/src/diagram/propertyLedger.ts
  77. 11 4
      nodeEditor/src/globalState.ts
  78. 147 441
      nodeEditor/src/graphEditor.tsx
  79. 0 59
      nodeEditor/src/graphHelper.ts
  80. 2 0
      nodeEditor/src/main.scss
  81. 7 0
      nodeEditor/src/nodeLocationInfo.ts
  82. 13 5
      nodeEditor/src/serializationTools.ts
  83. 30 19
      nodeEditor/src/sharedComponents/textureLineComponent.tsx
  84. 0 1
      package.json
  85. 5 1
      src/Materials/Node/Blocks/Fragment/fragmentOutputBlock.ts
  86. 26 3
      src/Materials/Node/nodeMaterial.ts
  87. 3 3
      src/Materials/Node/nodeMaterialBlock.ts
  88. 19 1
      src/Materials/Node/nodeMaterialBlockConnectionPoint.ts

+ 19 - 1
dist/preview release/babylon.d.ts

@@ -53988,6 +53988,11 @@ declare module BABYLON {
         private _initializeBlock;
         private _resetDualBlocks;
         /**
+         * Remove a block from the current node material
+         * @param block defines the block to remove
+         */
+        removeBlock(block: NodeMaterialBlock): void;
+        /**
          * Build the material and generates the inner effect
          * @param verbose defines if the build should log activity
          */
@@ -54765,6 +54770,15 @@ declare module BABYLON {
         TargetIncompatible = 2
     }
     /**
+     * Defines the direction of a connection point
+     */
+    export enum NodeMaterialConnectionPointDirection {
+        /** Input */
+        Input = 0,
+        /** Output */
+        Output = 1
+    }
+    /**
      * Defines a connection point for a block
      */
     export class NodeMaterialConnectionPoint {
@@ -54774,6 +54788,7 @@ declare module BABYLON {
         _connectedPoint: Nullable<NodeMaterialConnectionPoint>;
         private _endpoints;
         private _associatedVariableName;
+        private _direction;
         /** @hidden */
         _typeConnectionSource: Nullable<NodeMaterialConnectionPoint>;
         /** @hidden */
@@ -54781,6 +54796,8 @@ declare module BABYLON {
         private _type;
         /** @hidden */
         _enforceAssociatedVariableName: boolean;
+        /** Gets the direction of the point */
+        readonly direction: NodeMaterialConnectionPointDirection;
         /**
          * Gets or sets the additional types supported by this connection point
          */
@@ -54850,8 +54867,9 @@ declare module BABYLON {
          * Creates a new connection point
          * @param name defines the connection point name
          * @param ownerBlock defines the block hosting this connection point
+         * @param direction defines the direction of the connection point
          */
-        constructor(name: string, ownerBlock: NodeMaterialBlock);
+        constructor(name: string, ownerBlock: NodeMaterialBlock, direction: NodeMaterialConnectionPointDirection);
         /**
          * Gets the current class name e.g. "NodeMaterialConnectionPoint"
          * @returns the class name

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 69 - 27
dist/preview release/babylon.max.js


+ 38 - 2
dist/preview release/babylon.module.d.ts

@@ -56539,6 +56539,11 @@ declare module "babylonjs/Materials/Node/nodeMaterial" {
         private _initializeBlock;
         private _resetDualBlocks;
         /**
+         * Remove a block from the current node material
+         * @param block defines the block to remove
+         */
+        removeBlock(block: NodeMaterialBlock): void;
+        /**
          * Build the material and generates the inner effect
          * @param verbose defines if the build should log activity
          */
@@ -57365,6 +57370,15 @@ declare module "babylonjs/Materials/Node/nodeMaterialBlockConnectionPoint" {
         TargetIncompatible = 2
     }
     /**
+     * Defines the direction of a connection point
+     */
+    export enum NodeMaterialConnectionPointDirection {
+        /** Input */
+        Input = 0,
+        /** Output */
+        Output = 1
+    }
+    /**
      * Defines a connection point for a block
      */
     export class NodeMaterialConnectionPoint {
@@ -57374,6 +57388,7 @@ declare module "babylonjs/Materials/Node/nodeMaterialBlockConnectionPoint" {
         _connectedPoint: Nullable<NodeMaterialConnectionPoint>;
         private _endpoints;
         private _associatedVariableName;
+        private _direction;
         /** @hidden */
         _typeConnectionSource: Nullable<NodeMaterialConnectionPoint>;
         /** @hidden */
@@ -57381,6 +57396,8 @@ declare module "babylonjs/Materials/Node/nodeMaterialBlockConnectionPoint" {
         private _type;
         /** @hidden */
         _enforceAssociatedVariableName: boolean;
+        /** Gets the direction of the point */
+        readonly direction: NodeMaterialConnectionPointDirection;
         /**
          * Gets or sets the additional types supported by this connection point
          */
@@ -57450,8 +57467,9 @@ declare module "babylonjs/Materials/Node/nodeMaterialBlockConnectionPoint" {
          * Creates a new connection point
          * @param name defines the connection point name
          * @param ownerBlock defines the block hosting this connection point
+         * @param direction defines the direction of the connection point
          */
-        constructor(name: string, ownerBlock: NodeMaterialBlock);
+        constructor(name: string, ownerBlock: NodeMaterialBlock, direction: NodeMaterialConnectionPointDirection);
         /**
          * Gets the current class name e.g. "NodeMaterialConnectionPoint"
          * @returns the class name
@@ -122731,6 +122749,11 @@ declare module BABYLON {
         private _initializeBlock;
         private _resetDualBlocks;
         /**
+         * Remove a block from the current node material
+         * @param block defines the block to remove
+         */
+        removeBlock(block: NodeMaterialBlock): void;
+        /**
          * Build the material and generates the inner effect
          * @param verbose defines if the build should log activity
          */
@@ -123508,6 +123531,15 @@ declare module BABYLON {
         TargetIncompatible = 2
     }
     /**
+     * Defines the direction of a connection point
+     */
+    export enum NodeMaterialConnectionPointDirection {
+        /** Input */
+        Input = 0,
+        /** Output */
+        Output = 1
+    }
+    /**
      * Defines a connection point for a block
      */
     export class NodeMaterialConnectionPoint {
@@ -123517,6 +123549,7 @@ declare module BABYLON {
         _connectedPoint: Nullable<NodeMaterialConnectionPoint>;
         private _endpoints;
         private _associatedVariableName;
+        private _direction;
         /** @hidden */
         _typeConnectionSource: Nullable<NodeMaterialConnectionPoint>;
         /** @hidden */
@@ -123524,6 +123557,8 @@ declare module BABYLON {
         private _type;
         /** @hidden */
         _enforceAssociatedVariableName: boolean;
+        /** Gets the direction of the point */
+        readonly direction: NodeMaterialConnectionPointDirection;
         /**
          * Gets or sets the additional types supported by this connection point
          */
@@ -123593,8 +123628,9 @@ declare module BABYLON {
          * Creates a new connection point
          * @param name defines the connection point name
          * @param ownerBlock defines the block hosting this connection point
+         * @param direction defines the direction of the connection point
          */
-        constructor(name: string, ownerBlock: NodeMaterialBlock);
+        constructor(name: string, ownerBlock: NodeMaterialBlock, direction: NodeMaterialConnectionPointDirection);
         /**
          * Gets the current class name e.g. "NodeMaterialConnectionPoint"
          * @returns the class name

+ 1 - 1
nodeEditor/src/blockTools.ts

@@ -418,7 +418,7 @@ export class BlockTools {
     }
 
     public static GetColorFromConnectionNodeType(type: NodeMaterialBlockConnectionPointTypes) {
-        let color = "Red";
+        let color = "#880000";
         switch (type) {
             case NodeMaterialBlockConnectionPointTypes.Float:
 				color = "#cb9e27";

+ 0 - 23
nodeEditor/src/components/diagram/clamp/clampNodeFactory.tsx

@@ -1,23 +0,0 @@
-import * as SRD from "storm-react-diagrams";
-import * as React from "react";
-import { GlobalState } from '../../../globalState';
-import { ClampNodeWidget } from './clampNodeWidget';
-import { ClampNodeModel } from './clampNodeModel';
-
-export class ClampNodeFactory extends SRD.AbstractNodeFactory {
-    private _globalState: GlobalState;
-
-    constructor(globalState: GlobalState) {
-        super("clamp");
-
-        this._globalState = globalState;
-    }
-
-    generateReactWidget(diagramEngine: SRD.DiagramEngine, node: ClampNodeModel): JSX.Element {
-        return <ClampNodeWidget node={node} globalState={this._globalState} />;
-    }
-
-    getNewInstance() {
-        return new ClampNodeModel();
-    }
-}

+ 0 - 25
nodeEditor/src/components/diagram/clamp/clampNodeModel.tsx

@@ -1,25 +0,0 @@
-import * as React from "react";
-import { DefaultNodeModel } from '../defaultNodeModel';
-import { GlobalState } from '../../../globalState';
-import { ClampBlock } from 'babylonjs/Materials/Node/Blocks/clampBlock';
-import { ClampPropertyTabComponentProps } from './clampNodePropertyComponent';
-
-export class ClampNodeModel extends DefaultNodeModel {
-
-    public get clampBlock(): ClampBlock {
-        return this.block as ClampBlock;
-    }
-
-	/**
-	 * Constructs the node model
-	 */
-    constructor() {
-        super("clamp");
-    }
-
-    renderProperties(globalState: GlobalState) {
-        return (
-            <ClampPropertyTabComponentProps globalState={globalState} remapNode={this} />
-        );
-    }
-}

+ 0 - 64
nodeEditor/src/components/diagram/clamp/clampNodeWidget.tsx

@@ -1,64 +0,0 @@
-import * as React from "react";
-import { ClampNodeModel } from './clampNodeModel';
-import { Nullable } from 'babylonjs/types';
-import { GlobalState } from '../../../globalState';
-import { PortHelper } from '../portHelper';
-import { ClampBlock } from 'babylonjs/Materials/Node/Blocks/clampBlock';
-
-export interface ClampNodeWidgetProps {
-    node: Nullable<ClampNodeModel>;
-    globalState: GlobalState;
-}
-
-export class ClampNodeWidget extends React.Component<ClampNodeWidgetProps> {
-    constructor(props: ClampNodeWidgetProps) {
-        super(props);
-        this.state = {};
-
-        if (this.props.node) {
-            this.props.node.addListener({
-                selectionChanged: () => {
-                    let selected = (this.props.node as any).selected;
-                    this.props.globalState.onSelectionChangedObservable.notifyObservers(selected ? this.props.node : null);
-                }
-            });
-        }
-    }
-
-    renderValue(value: string) {
-        if (value) {
-            return (
-                <div className="value-text">
-                    {value}
-                </div>
-            )
-        }
-
-        return null;
-    }
-
-    render() {
-        var inputPorts = PortHelper.GenerateInputPorts(this.props.node, undefined, true);
-        var outputPorts = PortHelper.GenerateOutputPorts(this.props.node, true);
-        let clampBlock = this.props.node!.block! as ClampBlock;
-
-        return (
-            <div className={"diagramBlock clamp"}>
-                <div className="header">
-                    {clampBlock.name}
-                </div>
-                <div className="inputs">
-                    {inputPorts}
-                </div>
-                <div className="outputs">
-                    {outputPorts}
-                </div>
-                <div className="value">  
-                {
-                    `[${clampBlock.minimum}, ${clampBlock.maximum}]`
-                }                 
-                </div>
-            </div>
-        );
-    }
-}

+ 0 - 69
nodeEditor/src/components/diagram/defaultNodeModel.ts

@@ -1,69 +0,0 @@
-import { NodeModel, DiagramModel } from "storm-react-diagrams";
-import { Nullable } from 'babylonjs/types';
-import { NodeMaterialBlock } from 'babylonjs/Materials/Node/nodeMaterialBlock';
-import { GraphEditor, NodeCreationOptions } from '../../graphEditor';
-import { GlobalState } from '../../globalState';
-import { DefaultPortModel } from './port/defaultPortModel';
-
-/**
- * Generic node model which stores information about a node editor block
- */
-export class DefaultNodeModel extends NodeModel {
-	/**
-	 * The babylon block this node represents
-	 */
-    public block: Nullable<NodeMaterialBlock> = null;
-
-    public ports: { [s: string]: DefaultPortModel };
-
-	/**
-	 * Constructs the node model
-	 */
-    constructor(key: string) {
-        super(key);
-    }
-
-    prepare(options: NodeCreationOptions, nodes: Array<DefaultNodeModel>, model: DiagramModel, graphEditor: GraphEditor) {
-        this.block = options.nodeMaterialBlock || null;
-
-        if (!options.nodeMaterialBlock) {
-            return;
-        }
-        // Create output ports
-        options.nodeMaterialBlock._outputs.forEach((connection: any) => {
-            var outputPort = new DefaultPortModel(connection.name, "output");
-            outputPort.syncWithNodeMaterialConnectionPoint(connection);
-            this.addPort(outputPort)
-        })
-
-        // Create input ports and nodes if they exist
-        options.nodeMaterialBlock._inputs.forEach((connection) => {
-
-            var inputPort = new DefaultPortModel(connection.name, "input");
-            inputPort.connection = connection;
-            this.addPort(inputPort)
-
-            if (connection.connectedPoint) {
-                // Block is not a leaf node, create node for the given block type
-                var connectedNode;
-                var existingNodes = nodes.filter((n) => { return n.block === (connection as any)._connectedPoint._ownerBlock });
-                if (existingNodes.length == 0) {
-                    connectedNode = graphEditor.createNodeFromObject({ nodeMaterialBlock: connection.connectedPoint._ownerBlock });
-                } else {
-                    connectedNode = existingNodes[0];
-                }
-
-                let link = connectedNode.ports[connection.connectedPoint.name].link(inputPort);
-                if (graphEditor._toAdd) {
-                    graphEditor._toAdd.push(link);
-                } else {
-                    model.addAll(link);
-                }
-            }
-        });
-    }
-
-    renderProperties(globalState: GlobalState): JSX.Element | null {
-        return null;
-    }
-}

+ 0 - 319
nodeEditor/src/components/diagram/diagram.scss

@@ -1,319 +0,0 @@
-.srd-node {
-    width: 200px;
-}
-
-.srd-node--selected {
-    .diagramBlock {
-        border-color: rgb(255, 255, 255) !important;
-    }
-}
-
-.srd-default-link--path-selected {
-    animation: 0s !important;
-    stroke: #ffffff !important;
-}
-      
-.srd-port {
-    grid-column: 1;
-    grid-row: 1;
-    background: #2796B2;
-    border-radius: 20px;
-    transform: scale(1);
-    width: 20px;
-    height: 20px;
-
-    &.connected {
-        background: #CAB422;
-    }
-
-    &:hover {
-        background: #DDDDDD !important;
-    }
-}
-
-.diagramBlock {
-    background: gray;
-    width: 100%;
-    border: 4px solid black;
-    border-radius: 12px;
-    display: grid;
-    grid-template-rows: 30px auto;
-    grid-template-columns: 50% 50%;    
-    color:white;
-
-    &.input {
-        background: #40866E;
-        color:white;
-
-        .value {
-            grid-row: 2;
-        }
-
-        .outputs {
-            transform: translateY(5px);
-        }
-
-        &.Float {
-            background: #cb9e27;
-        }
-
-        &.Vector2 {
-            background: #16bcb1;
-        }      
-        
-        &.Vector3 {
-            background: #b786cb;
-        }          
-
-        &.Color3 {
-            background: #b786cb;
-        }          
-
-        &.Vector4 {
-            background: #be5126;
-        }          
-
-        &.Color4 {
-            background: #be5126;
-        }          
-
-        &.Matrix {
-            background: #591990;
-        }
-    }
-
-    &.trigonometry {
-        background: rgb(64, 92, 134);
-        color:white;
-
-        .value {
-            grid-row: 2;
-        }
-    }
-
-     &.remap {
-        color:white;
-        background: #4086BB;
-
-        .value {
-            grid-row: 3;
-        }
-
-        .outputs, .inputs {
-            transform: translateY(5px);
-        }        
-    }
-
-    &.clamp {
-        color:white;
-        background: #4086BB;
-
-        .value {
-            grid-row: 2;
-        }
-
-        .outputs, .inputs {
-            transform: translateY(5px);
-        }        
-    }    
-
-    &.gradient {
-        .outputs, .inputs {
-            transform: translateY(5px);
-        }        
-        height: 70px;
-    }
-
-    &.attribute {
-        background: #40866E;
-    }
-
-    &.output {
-        background: rgb(106, 44, 131);
-        color:white;
-
-        .inputs { 
-            color:white;
-        }
-    }
-
-    .header {            
-        grid-row: 1;
-        grid-column: 1 / span 2;
-        border: 4px solid black;
-        border-top-right-radius: 7px;
-        border-top-left-radius: 7px;
-        font-size: 16px;
-        text-align: center;
-        margin-top: -1px;    
-        transform: scaleX(1.001);
-        
-        white-space: nowrap;
-        text-overflow: ellipsis;
-        overflow: hidden;
-        background: black;
-        color: white;
-
-        &.constant {
-            border-color: #4E5C74;
-            background: #4E5C74 !important;
-        }
-
-        &.inspector {
-            border-color: #396437;
-            background: #396437 !important;
-        }
-    }
-
-    .value {
-        grid-row: 3;
-        grid-column: 1 / span 2;
-        height: 34px;
-        text-align: center;
-        font-size: 18px;
-        font-weight: bold;
-        margin: 0 10px;
-
-        .value-text {
-            white-space: nowrap;
-            text-overflow: ellipsis;
-            overflow: hidden;
-        }
-    }
-
-    .inputs {
-        grid-row: 2;
-        grid-column: 1;
-        padding-bottom: 8px;
-
-        .input-port {
-            display: grid;
-            grid-template-columns: 12px calc(100% - 12px);
-            grid-template-rows: 100%;
-
-            .input-port-plug {
-                grid-column: 1;
-                grid-row: 1;
-                display: grid;
-                align-content: center;
-                margin-left: -11px;
-                position: relative;
-
-                &:hover {           
-                    .input-port-connection {
-                        background: greenyellow !important;
-                    }
-                }
-
-                .input-port-type {
-                    width: 15px;
-                    pointer-events: none;
-                    grid-column: 1;
-                    grid-row: 1;
-                    display: grid;
-                    align-items: center;
-                    justify-items: center;     
-                    transform: scale(1);
-
-                    img {
-                        width: 20px;
-                    }
-                }
-            }
-
-            .input-port-label {
-                grid-column: 2;
-                grid-row: 1;         
-                margin-bottom: 4px;    
-                overflow: hidden;
-                white-space: nowrap;
-                text-overflow: ellipsis;   
-            }
-        }
-    }
-
-    .outputs {
-        grid-row: 2;
-        grid-column: 2;
-        padding-bottom: 8px;
-
-        .output-port {
-            display: grid;
-            grid-template-columns: calc(100% - 12px) 12px;
-            grid-template-rows: 100%;
-
-            .output-port-plug {
-                grid-column: 2;
-                grid-row: 1;
-                display: grid;
-                align-content: center;
-                position: relative;    
-                margin-left: 4px;
-
-                &:hover {           
-                    .output-port-connection {
-                        background: greenyellow !important;
-                    }
-                }
-
-                .output-port-type {
-                    pointer-events: none;
-                    grid-column: 1;
-                    grid-row: 1;
-                    display: grid;
-                    align-items: center;
-                    justify-items: center;
-                    transform: scale(1);
-
-                    img {
-                        width: 20px;
-                    }
-                }
-            }
-
-            .output-port-label {
-                text-align: right;
-                grid-column: 1;
-                grid-row: 1;                        
-                margin-bottom: 4px;   
-                overflow: hidden;
-                white-space: nowrap;
-                text-overflow: ellipsis;   
-            }
-        }
-    }
-
-    &.texture-block {
-        display: grid;
-        grid-template-rows: 30px auto 1fr;
-        grid-template-columns: calc(100% - 60px) 60px;
-        background: #323232;
-        color: white;
-
-        .inputs {
-            grid-column: 1;
-            grid-row: 2;
-        }
-
-        .outputs {
-            grid-column: 2;
-            grid-row: 2 / span 2;            
-        }
-
-        .textureLine {
-            height: 140px;
-            grid-column: 1;
-            grid-row: 3;     
-            overflow: hidden;
-            border-bottom-left-radius: 7px;
-            border: black 4px solid;
-            border-left: 0px;
-            border-bottom: 0px;
-
-            canvas {
-                width: 100%;
-                height: 100%;
-            }
-        }
-    }
-}

+ 0 - 39
nodeEditor/src/components/diagram/generic/genericNodeFactory.tsx

@@ -1,39 +0,0 @@
-import * as SRD from "storm-react-diagrams";
-import { GenericNodeWidget } from "./genericNodeWidget";
-import { GenericNodeModel } from "./genericNodeModel";
-import * as React from "react";
-import { GlobalState } from '../../../globalState';
-
-/**
- * Node factory which creates editor nodes
- */
-export class GenericNodeFactory extends SRD.AbstractNodeFactory {
-    private _globalState: GlobalState;
-
-	/**
-	 * Constructs a GenericNodeFactory
-	 */
-    constructor(globalState: GlobalState) {
-        super("generic");
-
-        this._globalState = globalState;
-    }
-
-	/**
-	 * Generates a node widget
-	 * @param diagramEngine diagram engine
-	 * @param node node to generate
-	 * @returns node widget jsx
-	 */
-    generateReactWidget(diagramEngine: SRD.DiagramEngine, node: GenericNodeModel): JSX.Element {
-        return <GenericNodeWidget node={node} globalState={this._globalState} />;
-    }
-
-	/**
-	 * Gets a new instance of a node model
-	 * @returns generic node model
-	 */
-    getNewInstance() {
-        return new GenericNodeModel();
-    }
-}

+ 0 - 84
nodeEditor/src/components/diagram/generic/genericNodeModel.tsx

@@ -1,84 +0,0 @@
-import * as React from "react";
-import { Nullable } from 'babylonjs/types';
-import { Vector2, Vector3, Vector4, Matrix } from 'babylonjs/Maths/math';
-import { DefaultNodeModel } from '../defaultNodeModel';
-import { DiagramModel } from 'storm-react-diagrams/dist/@types/src/models/DiagramModel';
-import { GraphEditor, NodeCreationOptions } from '../../../graphEditor';
-import { GlobalState } from '../../../globalState';
-import { TextLineComponent } from '../../../sharedComponents/textLineComponent';
-import { LineContainerComponent } from '../../../sharedComponents/lineContainerComponent';
-import { TextInputLineComponent } from '../../../sharedComponents/textInputLineComponent';
-import { CheckBoxLineComponent } from '../../../sharedComponents/checkBoxLineComponent';
-import { TransformBlock } from 'babylonjs/Materials/Node/Blocks/transformBlock';
-
-/**
- * Generic node model which stores information about a node editor block
- */
-export class GenericNodeModel extends DefaultNodeModel {
-	/**
-	 * Vector2 for the node if it exists
-	 */
-    public vector2: Nullable<Vector2> = null;
-	/**
-	 * Vector3 for the node if it exists
-	 */
-    public vector3: Nullable<Vector3> = null;
-	/**
-	 * Vector4 for the node if it exists
-	 */
-    public vector4: Nullable<Vector4> = null;
-	/**
-	 * Matrix for the node if it exists
-	 */
-    public matrix: Nullable<Matrix> = null;
-
-	/**
-	 * Constructs the node model
-	 */
-    constructor() {
-        super("generic");
-    }
-
-    prepare(options: NodeCreationOptions, nodes: Array<DefaultNodeModel>, model: DiagramModel, graphEditor: GraphEditor) {
-        super.prepare(options, nodes, model, graphEditor);
-    }
-
-    renderProperties(globalState: GlobalState) {
-
-        return (
-            <div>
-            <LineContainerComponent title="GENERAL">
-                <TextInputLineComponent globalState={globalState} label="Name" propertyName="name" target={this.block!} onChange={() => globalState.onUpdateRequiredObservable.notifyObservers()} />
-                <TextLineComponent label="Type" value={this.block!.getClassName()} />
-            </LineContainerComponent>
-            {
-                this.block!.getClassName() === "TransformBlock" &&
-                <LineContainerComponent title="PROPERTIES">
-                    <CheckBoxLineComponent label="Transform as direction" onSelect={value => {
-                        let transformBlock = this.block as TransformBlock;
-                        if (value) {
-                            transformBlock.complementW = 0;
-                        } else {
-                            transformBlock.complementW = 1;
-                        }
-                        globalState.onRebuildRequiredObservable.notifyObservers();
-                    }} isSelected={() => (this.block as TransformBlock).complementW === 0} />
-                </LineContainerComponent>
-            }                    
-            {
-                this.block!.getClassName() === "PerturbNormalBlock" &&
-                <LineContainerComponent title="PROPERTIES">
-                    <CheckBoxLineComponent label="Invert X axis" target={this.block} propertyName="invertX" onValueChanged={() => globalState.onRebuildRequiredObservable.notifyObservers()} />
-                    <CheckBoxLineComponent label="Invert Y axis" target={this.block} propertyName="invertY" onValueChanged={() => globalState.onRebuildRequiredObservable.notifyObservers()}/>                    
-                </LineContainerComponent>
-            }
-            {
-                this.block!.getClassName() === "WorleyNoise3DBlock" &&
-                <LineContainerComponent title="PROPERTIES">
-                    <CheckBoxLineComponent label="Use Manhattan Distance" target={this.block} propertyName="manhattanDistance" onValueChanged={() => globalState.onRebuildRequiredObservable.notifyObservers()} />              
-                </LineContainerComponent>
-            }
-            </div>
-        );
-    }
-}

+ 0 - 69
nodeEditor/src/components/diagram/generic/genericNodeWidget.tsx

@@ -1,69 +0,0 @@
-import * as React from "react";
-import { Nullable } from 'babylonjs/types';
-import { GlobalState } from '../../../globalState';
-import { GenericNodeModel } from './genericNodeModel';
-import { PortHelper } from '../portHelper';
-
-/**
- * GenericNodeWidgetProps
- */
-export interface GenericNodeWidgetProps {
-    node: Nullable<GenericNodeModel>;
-    globalState: GlobalState;
-}
-
-/**
- * GenericNodeWidgetState
- */
-export interface GenericNodeWidgetState {
-
-}
-
-/**
- * Used to display a node block for the node editor
- */
-export class GenericNodeWidget extends React.Component<GenericNodeWidgetProps, GenericNodeWidgetState> {
-	/**
-	 * Creates a GenericNodeWidget
-	 * @param props 
-	 */
-    constructor(props: GenericNodeWidgetProps) {
-        super(props);
-        this.state = {};
-
-        if (this.props.node) {
-            this.props.node.addListener({
-                selectionChanged: () => {
-                    let selected = (this.props.node as any).selected;
-                    this.props.globalState.onSelectionChangedObservable.notifyObservers(selected ? this.props.node : null);
-                }
-            });
-        }
-    }
-
-    render() {
-        // Header label
-        var header = "";
-        if (this.props.node && this.props.node.block) {
-            header = this.props.node.block.name;
-        }
-
-        // Input/Output ports
-        var outputPorts = PortHelper.GenerateOutputPorts(this.props.node, false);
-        var inputPorts = PortHelper.GenerateInputPorts(this.props.node);
-
-        return (
-            <div className={"diagramBlock" + (outputPorts.length === 0 ? " output" : "")}>
-                <div className="header">
-                    {header}
-                </div>
-                <div className="inputs">
-                    {inputPorts}
-                </div>
-                <div className="outputs">
-                    {outputPorts}
-                </div>
-            </div>
-        );
-    }
-}

+ 0 - 23
nodeEditor/src/components/diagram/gradient/gradientNodeFactory.tsx

@@ -1,23 +0,0 @@
-import * as SRD from "storm-react-diagrams";
-import * as React from "react";
-import { GlobalState } from '../../../globalState';
-import { GradientNodeWidget } from './gradientNodeWidget';
-import { GradientNodeModel } from './gradientNodeModel';
-
-export class GradientNodeFactory extends SRD.AbstractNodeFactory {
-    private _globalState: GlobalState;
-
-    constructor(globalState: GlobalState) {
-        super("gradient");
-
-        this._globalState = globalState;
-    }
-
-    generateReactWidget(diagramEngine: SRD.DiagramEngine, node: GradientNodeModel): JSX.Element {
-        return <GradientNodeWidget node={node} globalState={this._globalState} />;
-    }
-
-    getNewInstance() {
-        return new GradientNodeModel();
-    }
-}

+ 0 - 25
nodeEditor/src/components/diagram/gradient/gradientNodeModel.tsx

@@ -1,25 +0,0 @@
-import * as React from "react";
-import { DefaultNodeModel } from '../defaultNodeModel';
-import { GlobalState } from '../../../globalState';
-import { GradientBlock } from 'babylonjs/Materials/Node/Blocks/gradientBlock';
-import { GradientPropertyTabComponentProps } from './gradientNodePropertyComponent';
-
-export class GradientNodeModel extends DefaultNodeModel {
-
-    public get gradientBlock(): GradientBlock {
-        return this.block as GradientBlock;
-    }
-
-	/**
-	 * Constructs the node model
-	 */
-    constructor() {
-        super("gradient");
-    }
-
-    renderProperties(globalState: GlobalState) {
-        return (
-            <GradientPropertyTabComponentProps globalState={globalState} gradientNode={this} />
-        );
-    }
-}

+ 0 - 67
nodeEditor/src/components/diagram/gradient/gradientNodeWidget.tsx

@@ -1,67 +0,0 @@
-import * as React from "react";
-import { GradientNodeModel } from './gradientNodeModel';
-import { Nullable } from 'babylonjs/types';
-import { GlobalState } from '../../../globalState';
-import { PortHelper } from '../portHelper';
-import { GradientBlock } from 'babylonjs/Materials/Node/Blocks/gradientBlock';
-
-export interface IGradientNodeWidgetProps {
-    node: Nullable<GradientNodeModel>;
-    globalState: GlobalState;
-}
-
-export class GradientNodeWidget extends React.Component<IGradientNodeWidgetProps> {
-    constructor(props: IGradientNodeWidgetProps) {
-        super(props);
-        this.state = {};
-
-        if (this.props.node) {
-            this.props.node.addListener({
-                selectionChanged: () => {
-                    let selected = (this.props.node as any).selected;
-                    this.props.globalState.onSelectionChangedObservable.notifyObservers(selected ? this.props.node : null);
-                }
-            });
-        }
-    }
-
-    renderValue(value: string) {
-        if (value) {
-            return (
-                <div className="value-text">
-                    {value}
-                </div>
-            )
-        }
-
-        return null;
-    }
-
-    render() {
-        var inputPorts = PortHelper.GenerateInputPorts(this.props.node, undefined, true);
-        var outputPorts = PortHelper.GenerateOutputPorts(this.props.node, true);
-        let gradientBlock = this.props.node!.block! as GradientBlock;
-
-        let gradients = gradientBlock.colorSteps.map(c => `rgb(${c.color.r * 255}, ${c.color.g * 255}, ${c.color.b * 255}) ${c.step * 100}%`);
-
-        let style = {
-            background: gradients.length ? `linear-gradient(90deg, ${gradients.join(", ")})` : 'black'
-        };
-
-        return (
-            <div className={"diagramBlock gradient"} style={style}>
-                <div className="header">
-                    {gradientBlock.name}
-                </div>
-                <div className="inputs">
-                    {inputPorts}
-                </div>
-                <div className="outputs">
-                    {outputPorts}
-                </div>
-                <div className="value">                
-                </div>
-            </div>
-        );
-    }
-}

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 1
nodeEditor/src/components/diagram/images/Matrix.svg


+ 0 - 1
nodeEditor/src/components/diagram/images/Vector1.svg

@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21 21"><defs><style>.cls-1{fill:#fff;}</style></defs><title>Vector1</title><g id="Layer_5" data-name="Layer 5"><circle class="cls-1" cx="10.5" cy="10.5" r="7.5"/></g></svg>

+ 0 - 1
nodeEditor/src/components/diagram/images/Vector2.svg

@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21 21"><defs><style>.cls-1{fill:#fff;}</style></defs><title>Vector2</title><g id="Layer_5" data-name="Layer 5"><path class="cls-1" d="M3,10.5a7.52,7.52,0,0,0,6.5,7.43V3.07A7.52,7.52,0,0,0,3,10.5Z"/><path class="cls-1" d="M11.5,3.07V17.93a7.5,7.5,0,0,0,0-14.86Z"/></g></svg>

+ 0 - 1
nodeEditor/src/components/diagram/images/Vector3.svg

@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21 21"><defs><style>.cls-1{fill:#fff;}</style></defs><title>Vector3</title><g id="Layer_5" data-name="Layer 5"><path class="cls-1" d="M3.57,13.31,9.5,9.89V3A7.51,7.51,0,0,0,3,10.46,7.32,7.32,0,0,0,3.57,13.31Z"/><path class="cls-1" d="M16.43,15,10.5,11.62,4.57,15a7.48,7.48,0,0,0,11.86,0Z"/><path class="cls-1" d="M18,10.46A7.51,7.51,0,0,0,11.5,3V9.89l5.93,3.42A7.32,7.32,0,0,0,18,10.46Z"/></g></svg>

+ 0 - 1
nodeEditor/src/components/diagram/images/Vector4.svg

@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21 21"><defs><style>.cls-1{fill:#fff;}</style></defs><title>Vector4</title><g id="Layer_5" data-name="Layer 5"><path class="cls-1" d="M11.5,11.5v6.43a7.51,7.51,0,0,0,6.43-6.43Z"/><path class="cls-1" d="M11.5,3.07V9.5h6.43A7.51,7.51,0,0,0,11.5,3.07Z"/><path class="cls-1" d="M9.5,17.93V11.5H3.07A7.51,7.51,0,0,0,9.5,17.93Z"/><path class="cls-1" d="M9.5,3.07A7.51,7.51,0,0,0,3.07,9.5H9.5Z"/></g></svg>

+ 0 - 39
nodeEditor/src/components/diagram/input/inputNodeFactory.tsx

@@ -1,39 +0,0 @@
-import * as SRD from "storm-react-diagrams";
-import * as React from "react";
-import { GlobalState } from '../../../globalState';
-import { InputNodeModel } from './inputNodeModel';
-import { InputNodeWidget } from './inputNodeWidget';
-
-/**
- * Node factory which creates editor nodes
- */
-export class InputNodeFactory extends SRD.AbstractNodeFactory {
-    private _globalState: GlobalState;
-
-	/**
-	 * Constructs a GenericNodeFactory
-	 */
-    constructor(globalState: GlobalState) {
-        super("input");
-
-        this._globalState = globalState;
-    }
-
-	/**
-	 * Generates a node widget
-	 * @param diagramEngine diagram engine
-	 * @param node node to generate
-	 * @returns node widget jsx
-	 */
-    generateReactWidget(diagramEngine: SRD.DiagramEngine, node: InputNodeModel): JSX.Element {
-        return <InputNodeWidget node={node} globalState={this._globalState} />;
-    }
-
-	/**
-	 * Gets a new instance of a node model
-	 * @returns input node model
-	 */
-    getNewInstance() {
-        return new InputNodeModel();
-    }
-}

+ 0 - 28
nodeEditor/src/components/diagram/input/inputNodeModel.tsx

@@ -1,28 +0,0 @@
-import * as React from "react";
-import { DefaultNodeModel } from '../defaultNodeModel';
-import { GlobalState } from '../../../globalState';
-import { InputPropertyTabComponentProps } from './inputNodePropertyComponent';
-import { InputBlock } from 'babylonjs/Materials/Node/Blocks/Input/inputBlock';
-
-/**
- * Generic node model which stores information about a node editor block
- */
-export class InputNodeModel extends DefaultNodeModel {
-
-    public get inputBlock(): InputBlock {
-        return this.block as InputBlock;
-    }
-
-	/**
-	 * Constructs the node model
-	 */
-    constructor() {
-        super("input");
-    }
-
-    renderProperties(globalState: GlobalState) {
-        return (
-            <InputPropertyTabComponentProps globalState={globalState} inputNode={this} />
-        );
-    }
-}

+ 0 - 150
nodeEditor/src/components/diagram/input/inputNodeWidget.tsx

@@ -1,150 +0,0 @@
-import * as React from "react";
-import { InputNodeModel } from './inputNodeModel';
-import { Nullable } from 'babylonjs/types';
-import { GlobalState } from '../../../globalState';
-import { NodeMaterialSystemValues } from 'babylonjs/Materials/Node/Enums/nodeMaterialSystemValues';
-import { NodeMaterialBlockConnectionPointTypes } from 'babylonjs/Materials/Node/Enums/nodeMaterialBlockConnectionPointTypes';
-import { Color3, Vector2, Vector3, Vector4 } from 'babylonjs/Maths/math';
-import { StringTools } from '../../../stringTools';
-import { PortHelper } from '../portHelper';
-import { AnimatedInputBlockTypes } from 'babylonjs/Materials/Node/Blocks/Input/animatedInputBlockTypes';
-
-/**
- * GenericNodeWidgetProps
- */
-export interface IInputNodeWidgetProps {
-    node: Nullable<InputNodeModel>;
-    globalState: GlobalState;
-}
-
-/**
- * Used to display a node block for the node editor
- */
-export class InputNodeWidget extends React.Component<IInputNodeWidgetProps> {
-	/**
-	 * Creates a GenericNodeWidget
-	 * @param props 
-	 */
-    constructor(props: IInputNodeWidgetProps) {
-        super(props);
-        this.state = {};
-
-        if (this.props.node) {
-            this.props.node.addListener({
-                selectionChanged: () => {
-                    let selected = (this.props.node as any).selected;
-                    this.props.globalState.onSelectionChangedObservable.notifyObservers(selected ? this.props.node : null);
-                }
-            });
-        }
-    }
-
-    renderValue(value: string) {
-        if (value !== "") {
-            return (
-                <div className="value-text">
-                    {value}
-                </div>
-            )
-        }
-
-        return null;
-    }
-
-    render() {
-        var outputPorts = PortHelper.GenerateOutputPorts(this.props.node, true);
-
-        let inputBlock = this.props.node!.inputBlock;
-        let value = "";
-        let name = `${inputBlock.name} (${StringTools.GetBaseType(inputBlock.output.type)})`;
-        let color = "";
-
-        if (inputBlock) {
-            if (inputBlock.isAttribute) {
-                value = "mesh." + inputBlock.name;
-                name = StringTools.GetBaseType(inputBlock.output.type);
-            } else if (inputBlock.isSystemValue) {
-                switch (inputBlock.systemValue) {
-                    case NodeMaterialSystemValues.World:
-                        value = "World";
-                        break;
-                    case NodeMaterialSystemValues.WorldView:
-                        value = "World x View";
-                        break;
-                    case NodeMaterialSystemValues.WorldViewProjection:
-                        value = "World x View x Projection";
-                        break;
-                    case NodeMaterialSystemValues.View:
-                        value = "View";
-                        break;
-                    case NodeMaterialSystemValues.ViewProjection:
-                        value = "View x Projection";
-                        break;
-                    case NodeMaterialSystemValues.Projection:
-                        value = "Projection";
-                        break;
-                    case NodeMaterialSystemValues.CameraPosition:
-                        value = "Camera position";
-                        break;
-                    case NodeMaterialSystemValues.FogColor:
-                        value = "Fog color";
-                        break;
-                    case NodeMaterialSystemValues.DeltaTime:
-                        value = "Delta time";
-                        break;
-                }
-            } else {
-                if (!inputBlock || !inputBlock.isUniform) {
-                    return null;
-                }
-
-                switch (inputBlock.type) {
-                    case NodeMaterialBlockConnectionPointTypes.Float:
-                        if (inputBlock.animationType !== AnimatedInputBlockTypes.None) {
-                            value = AnimatedInputBlockTypes[inputBlock.animationType];
-                        } else {
-                            value = inputBlock.value.toFixed(2);
-                        }
-                        break;
-                    case NodeMaterialBlockConnectionPointTypes.Vector2:
-                        let vec2Value = inputBlock.value as Vector2
-                        value = `(${vec2Value.x.toFixed(2)}, ${vec2Value.y.toFixed(2)})`;
-                        break;
-                    case NodeMaterialBlockConnectionPointTypes.Vector3:
-                        let vec3Value = inputBlock.value as Vector3
-                        value = `(${vec3Value.x.toFixed(2)}, ${vec3Value.y.toFixed(2)}, ${vec3Value.z.toFixed(2)})`;
-                        break;
-                    case NodeMaterialBlockConnectionPointTypes.Vector4:
-                        let vec4Value = inputBlock.value as Vector4
-                        value = `(${vec4Value.x.toFixed(2)}, ${vec4Value.y.toFixed(2)}, ${vec4Value.z.toFixed(2)}, ${vec4Value.w.toFixed(2)})`;
-                        break;                        
-                    case NodeMaterialBlockConnectionPointTypes.Color3:
-                    case NodeMaterialBlockConnectionPointTypes.Color4: {
-                        color = (inputBlock.value as Color3).toHexString();
-                        break;
-                    }
-                }
-            }
-        } else {
-            name = "Not connected input";
-        }
-
-        return (
-            <div className={"diagramBlock input" + (inputBlock ? " " + NodeMaterialBlockConnectionPointTypes[inputBlock.type] : "")} style={{
-                background: color
-            }}>
-                <div className={"header" + (inputBlock && inputBlock.isConstant ? " constant" : "") + (inputBlock && inputBlock.visibleInInspector ? " inspector" : "")}>
-                    {name}
-                </div>
-                <div className="outputs">
-                    {outputPorts}
-                </div>
-                <div className="value">
-                    {
-                        this.renderValue(value)
-                    }
-                </div>
-            </div>
-        );
-    }
-}

+ 0 - 39
nodeEditor/src/components/diagram/light/lightNodeFactory.tsx

@@ -1,39 +0,0 @@
-import * as SRD from "storm-react-diagrams";
-import { LightNodeWidget } from "./lightNodeWidget";
-import { LightNodeModel } from "./lightNodeModel";
-import * as React from "react";
-import { GlobalState } from '../../../globalState';
-
-/**
- * Node factory which creates editor nodes
- */
-export class LightNodeFactory extends SRD.AbstractNodeFactory {
-    private _globalState: GlobalState;
-
-	/**
-	 * Constructs a LightNodeFactory
-	 */
-    constructor(globalState: GlobalState) {
-        super("light");
-
-        this._globalState = globalState;
-    }
-
-	/**
-	 * Generates a node widget
-	 * @param diagramEngine diagram engine
-	 * @param node node to generate
-	 * @returns node widget jsx
-	 */
-    generateReactWidget(diagramEngine: SRD.DiagramEngine, node: LightNodeModel): JSX.Element {
-        return <LightNodeWidget node={node} globalState={this._globalState} />;
-    }
-
-	/**
-	 * Gets a new instance of a node model
-	 * @returns light node model
-	 */
-    getNewInstance() {
-        return new LightNodeModel();
-    }
-}

+ 0 - 47
nodeEditor/src/components/diagram/light/lightNodeModel.tsx

@@ -1,47 +0,0 @@
-import * as React from 'react';
-import { Nullable } from 'babylonjs/types';
-import { Light } from 'babylonjs/Lights/light';
-import { DefaultNodeModel } from '../defaultNodeModel';
-import { GlobalState } from '../../../globalState';
-import { LightPropertyTabComponent } from './lightPropertyTabComponent';
-import { NodeCreationOptions, GraphEditor } from '../../../graphEditor';
-import { DiagramModel } from 'storm-react-diagrams/dist/@types/src/models/DiagramModel';
-import { LightBlock } from 'babylonjs/Materials/Node/Blocks/Dual/lightBlock';
-
-/**
- * Light node model which stores information about a node editor block
- */
-export class LightNodeModel extends DefaultNodeModel {
-    private _block: LightBlock;
-
-	/**
-	 * Light for the node if it exists
-	 */
-    public get light(): Nullable<Light> {
-        return this._block.light;
-    }
-
-    public set light(value: Nullable<Light>) {
-        this._block.light = value;
-    }
-
-	/**
-	 * Constructs the node model
-	 */
-    constructor() {
-        super("light");
-    }
-
-    renderProperties(globalState: GlobalState) {
-        return (
-            <LightPropertyTabComponent globalState={globalState} node={this} />
-        );
-    }
-
-    prepare(options: NodeCreationOptions, nodes: Array<DefaultNodeModel>, model: DiagramModel, graphEditor: GraphEditor) {
-        this._block = options.nodeMaterialBlock as LightBlock;
-
-        super.prepare(options, nodes, model, graphEditor);
-    }
-
-}

+ 0 - 56
nodeEditor/src/components/diagram/light/lightNodeWidget.tsx

@@ -1,56 +0,0 @@
-import * as React from "react";
-import { LightNodeModel } from './lightNodeModel';
-import { Nullable } from 'babylonjs/types';
-import { GlobalState } from '../../../globalState';
-import { PortHelper } from '../portHelper';
-
-/**
- * GenericNodeWidgetProps
- */
-export interface ILightNodeWidgetProps {
-    node: Nullable<LightNodeModel>;
-    globalState: GlobalState;
-}
-
-/**
- * Used to display a node block for the node editor
- */
-export class LightNodeWidget extends React.Component<ILightNodeWidgetProps> {
-	/**
-	 * Creates a GenericNodeWidget
-	 * @param props 
-	 */
-    constructor(props: ILightNodeWidgetProps) {
-        super(props);
-        this.state = {};
-
-        if (this.props.node) {
-            this.props.node.addListener({
-                selectionChanged: () => {
-                    let selected = (this.props.node as any).selected;
-                    this.props.globalState.onSelectionChangedObservable.notifyObservers(selected ? this.props.node : null);
-                }
-            });
-        }
-    }
-
-    render() {
-        // Input/Output ports
-        var outputPorts = PortHelper.GenerateOutputPorts(this.props.node, false);
-        var inputPorts = PortHelper.GenerateInputPorts(this.props.node);
-
-        return (
-            <div className={"diagramBlock"}>
-                <div className="header">
-                    {this.props.node!.block!.name}
-                </div>
-                <div className="inputs">
-                    {inputPorts}
-                </div>
-                <div className="outputs">
-                    {outputPorts}
-                </div>
-            </div>
-        );
-    }
-}

+ 0 - 39
nodeEditor/src/components/diagram/lightInformation/lightInformationNodeFactory.tsx

@@ -1,39 +0,0 @@
-import * as SRD from "storm-react-diagrams";
-import { LightInformationNodeWidget } from "./lightInformationNodeWidget";
-import { LightInformationNodeModel } from "./lightInformationNodeModel";
-import * as React from "react";
-import { GlobalState } from '../../../globalState';
-
-/**
- * Node factory which creates editor nodes
- */
-export class LightInformationNodeFactory extends SRD.AbstractNodeFactory {
-    private _globalState: GlobalState;
-
-	/**
-	 * Constructs a LightNodeFactory
-	 */
-    constructor(globalState: GlobalState) {
-        super("light-information");
-
-        this._globalState = globalState;
-    }
-
-	/**
-	 * Generates a node widget
-	 * @param diagramEngine diagram engine
-	 * @param node node to generate
-	 * @returns node widget jsx
-	 */
-    generateReactWidget(diagramEngine: SRD.DiagramEngine, node: LightInformationNodeModel): JSX.Element {
-        return <LightInformationNodeWidget node={node} globalState={this._globalState} />;
-    }
-
-	/**
-	 * Gets a new instance of a node model
-	 * @returns light node model
-	 */
-    getNewInstance() {
-        return new LightInformationNodeModel();
-    }
-}

+ 0 - 44
nodeEditor/src/components/diagram/lightInformation/lightInformationNodeModel.tsx

@@ -1,44 +0,0 @@
-import * as React from 'react';
-import { Nullable } from 'babylonjs/types';
-import { Light } from 'babylonjs/Lights/light';
-import { DefaultNodeModel } from '../defaultNodeModel';
-import { GlobalState } from '../../../globalState';
-import { LightInformationPropertyTabComponent } from './lightInformationPropertyTabComponent';
-import { NodeCreationOptions, GraphEditor } from '../../../graphEditor';
-import { DiagramModel } from 'storm-react-diagrams/dist/@types/src/models/DiagramModel';
-import { LightInformationBlock } from 'babylonjs/Materials/Node/Blocks/Vertex/lightInformationBlock';
-
-export class LightInformationNodeModel extends DefaultNodeModel {
-    private _block: LightInformationBlock;
-
-	/**
-	 * Light for the node if it exists
-	 */
-    public get light(): Nullable<Light> {
-        return this._block.light;
-    }
-
-    public set light(value: Nullable<Light>) {
-        this._block.light = value;
-    }
-
-	/**
-	 * Constructs the node model
-	 */
-    constructor() {
-        super("light-information");
-    }
-
-    renderProperties(globalState: GlobalState) {
-        return (
-            <LightInformationPropertyTabComponent globalState={globalState} node={this} />
-        );
-    }
-
-    prepare(options: NodeCreationOptions, nodes: Array<DefaultNodeModel>, model: DiagramModel, graphEditor: GraphEditor) {
-        this._block = options.nodeMaterialBlock as LightInformationBlock;
-
-        super.prepare(options, nodes, model, graphEditor);
-    }
-
-}

+ 0 - 56
nodeEditor/src/components/diagram/lightInformation/lightInformationNodeWidget.tsx

@@ -1,56 +0,0 @@
-import * as React from "react";
-import { LightInformationNodeModel } from './lightInformationNodeModel';
-import { Nullable } from 'babylonjs/types';
-import { GlobalState } from '../../../globalState';
-import { PortHelper } from '../portHelper';
-
-/**
- * GenericNodeWidgetProps
- */
-export interface ILightInformationNodeWidgetProps {
-    node: Nullable<LightInformationNodeModel>;
-    globalState: GlobalState;
-}
-
-/**
- * Used to display a node block for the node editor
- */
-export class LightInformationNodeWidget extends React.Component<ILightInformationNodeWidgetProps> {
-	/**
-	 * Creates a GenericNodeWidget
-	 * @param props 
-	 */
-    constructor(props: ILightInformationNodeWidgetProps) {
-        super(props);
-        this.state = {};
-
-        if (this.props.node) {
-            this.props.node.addListener({
-                selectionChanged: () => {
-                    let selected = (this.props.node as any).selected;
-                    this.props.globalState.onSelectionChangedObservable.notifyObservers(selected ? this.props.node : null);
-                }
-            });
-        }
-    }
-
-    render() {
-        // Input/Output ports
-        var outputPorts = PortHelper.GenerateOutputPorts(this.props.node, false);
-        var inputPorts = PortHelper.GenerateInputPorts(this.props.node);
-
-        return (
-            <div className={"diagramBlock"}>
-                <div className="header">
-                    {`${this.props.node!.block!.name} (${this.props.node!.light ? this.props.node!.light.name : "No light"})`}
-                </div>
-                <div className="inputs">
-                    {inputPorts}
-                </div>
-                <div className="outputs">
-                    {outputPorts}
-                </div>
-            </div>
-        );
-    }
-}

+ 0 - 32
nodeEditor/src/components/diagram/link/advancedLinkFactory.tsx

@@ -1,32 +0,0 @@
-import { DefaultLinkFactory, DefaultLinkWidget } from 'storm-react-diagrams';
-import * as React from 'react';
-import { DefaultPortModel } from '../port/defaultPortModel';
-import { AdvancedLinkModel } from './advancedLinkModel';
-import { BlockTools } from '../../../blockTools';
-
-export class AdvancedLinkFactory extends DefaultLinkFactory {
-	constructor() {
-		super();
-		this.type = "advanced";
-	}
-
-	getNewInstance(initialConfig?: any): AdvancedLinkModel {
-		return new AdvancedLinkModel();
-	}
-
-	generateLinkSegment(model: AdvancedLinkModel, widget: DefaultLinkWidget, selected: boolean, path: string) {
-        const portModel = (model.getTargetPort() || model.getSourcePort()) as DefaultPortModel;
-        const type = portModel.connection!.type;
-		let color = BlockTools.GetColorFromConnectionNodeType(type);
-		let width = 3;
-
-		return (
-			<path
-				className={selected ? widget.bem("--path-selected") : ""}
-				strokeWidth={width}
-				stroke={color}
-				d={path}
-			/>
-		);
-	}
-}

+ 0 - 7
nodeEditor/src/components/diagram/link/advancedLinkModel.tsx

@@ -1,7 +0,0 @@
-import { DefaultLinkModel } from 'storm-react-diagrams';
-
-export class AdvancedLinkModel extends DefaultLinkModel {
-	constructor() {
-		super("advanced");
-	}
-}

+ 0 - 75
nodeEditor/src/components/diagram/port/defaultPortModel.ts

@@ -1,75 +0,0 @@
-import { LinkModel, PortModel } from "storm-react-diagrams";
-import { Nullable } from 'babylonjs/types';
-import { NodeMaterialConnectionPoint } from 'babylonjs/Materials/Node/nodeMaterialBlockConnectionPoint';
-import { DefaultNodeModel } from '../defaultNodeModel';
-import { AdvancedLinkModel } from '../link/advancedLinkModel';
-
-/**
- * Port model
- */
-export class DefaultPortModel extends PortModel {
-	/**
-	 * If the port is input or output
-	 */
-    public position: string | "input" | "output";
-	/**
-	 * What the port is connected to
-	 */
-    public connection: Nullable<NodeMaterialConnectionPoint> = null;
-
-    public defaultValue: any;
-
-    static idCounter = 0;
-
-    constructor(name: string, type: string = "input") {
-        super(name, "generic");
-        this.position = type;
-        DefaultPortModel.idCounter++;
-    }
-
-    canLinkToPort(port: DefaultPortModel): boolean {
-        if (!this.connection || !port.connection) {
-            return true;
-        }
-
-        return this.connection.canConnectTo(port.connection);
-    }
-
-    syncWithNodeMaterialConnectionPoint(connection: NodeMaterialConnectionPoint) {
-        this.connection = connection;
-        this.name = connection.name;
-    }
-
-    getNodeModel() {
-        return this.parent as DefaultNodeModel
-    }
-
-    link(outPort: DefaultPortModel) {
-        var link = this.createLinkModel()
-        link.setSourcePort(this)
-        link.setTargetPort(outPort)
-        return link;
-    }
-
-    createLinkModel(): LinkModel {
-        return new AdvancedLinkModel();
-    }
-
-    static SortInputOutput(a: Nullable<DefaultPortModel>, b: Nullable<DefaultPortModel>) {
-        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;
-        }
-    }
-}

+ 0 - 39
nodeEditor/src/components/diagram/port/defaultPortWidget.tsx

@@ -1,39 +0,0 @@
-import { BaseWidget, PortState, NodeModel, BaseWidgetProps } from 'storm-react-diagrams';
-import * as React from 'react';
-
-
-export interface IDefaultPortWidgetProps extends BaseWidgetProps {
-	name: string;
-	node: NodeModel;
-    style: any;
-}
-
-export class DefaultPortWidget extends BaseWidget<IDefaultPortWidgetProps, PortState> {
-    constructor(props: IDefaultPortWidgetProps) {
-		super("srd-port", props);
-		this.state = {
-			selected: false
-		};
-	}
-
-	getClassName() {
-		return "port " + super.getClassName() + (this.state.selected ? this.bem("--selected") : "");
-	}
-
-	render() {
-		return (
-			<div
-				style={this.props.style}
-				{...this.getProps()}
-				onMouseEnter={() => {
-					this.setState({ selected: true });
-				}}
-				onMouseLeave={() => {
-					this.setState({ selected: false });
-				}}
-				data-name={this.props.name}
-				data-nodeid={this.props.node.getID()}
-			/>
-		);
-	}
-}

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 114
nodeEditor/src/components/diagram/portHelper.tsx


+ 0 - 39
nodeEditor/src/components/diagram/reflectionTexture/reflectionTextureNodeFactory.tsx

@@ -1,39 +0,0 @@
-import * as SRD from "storm-react-diagrams";
-import * as React from "react";
-import { GlobalState } from '../../../globalState';
-import { ReflectionTextureNodeModel } from './reflectionTextureNodeModel';
-import { ReflectionTextureNodeWidget } from './reflectionTextureNodeWidget';
-
-/**
- * Node factory which creates editor nodes
- */
-export class ReflectionTextureNodeFactory extends SRD.AbstractNodeFactory {
-    private _globalState: GlobalState;
-
-	/**
-	 * Constructs a TextureNodeFactory
-	 */
-    constructor(globalState: GlobalState) {
-        super("reflectiontexture");
-
-        this._globalState = globalState;
-    }
-
-	/**
-	 * Generates a node widget
-	 * @param diagramEngine diagram engine
-	 * @param node node to generate
-	 * @returns node widget jsx
-	 */
-    generateReactWidget(diagramEngine: SRD.DiagramEngine, node: ReflectionTextureNodeModel): JSX.Element {
-        return <ReflectionTextureNodeWidget node={node} globalState={this._globalState} />;
-    }
-
-	/**
-	 * Gets a new instance of a node model
-	 * @returns texture node model
-	 */
-    getNewInstance() {
-        return new ReflectionTextureNodeModel();
-    }
-}

+ 0 - 47
nodeEditor/src/components/diagram/reflectionTexture/reflectionTextureNodeModel.tsx

@@ -1,47 +0,0 @@
-import * as React from 'react';
-import { Nullable } from 'babylonjs/types';
-import { DefaultNodeModel } from '../defaultNodeModel';
-import { GlobalState } from '../../../globalState';
-import { NodeCreationOptions, GraphEditor } from '../../../graphEditor';
-import { DiagramModel } from 'storm-react-diagrams/dist/@types/src/models/DiagramModel';
-import { BaseTexture } from 'babylonjs/Materials/Textures/baseTexture';
-import { ReflectionTextureBlock } from 'babylonjs/Materials/Node/Blocks/Dual/reflectionTextureBlock';
-import { TexturePropertyTabComponent } from '../texture/texturePropertyTabComponent';
-
-/**
- * Texture node model which stores information about a node editor block
- */
-export class ReflectionTextureNodeModel extends DefaultNodeModel {
-    private _block: ReflectionTextureBlock;
-
-	/**
-	 * Texture for the node if it exists
-	 */
-    public get texture(): Nullable<BaseTexture> {
-        return this._block.texture;
-    }
-
-    public set texture(value: Nullable<BaseTexture>) {
-        this._block.texture = value;
-    }
-
-	/**
-	 * Constructs the node model
-	 */
-    constructor() {
-        super("reflectiontexture");
-    }
-
-    renderProperties(globalState: GlobalState) {
-        return (
-            <TexturePropertyTabComponent globalState={globalState} node={this} />
-        );
-    }
-
-    prepare(options: NodeCreationOptions, nodes: Array<DefaultNodeModel>, model: DiagramModel, graphEditor: GraphEditor) {
-        this._block = options.nodeMaterialBlock as ReflectionTextureBlock;
-
-        super.prepare(options, nodes, model, graphEditor);
-    }
-
-}

+ 0 - 61
nodeEditor/src/components/diagram/reflectionTexture/reflectionTextureNodeWidget.tsx

@@ -1,61 +0,0 @@
-import * as React from "react";
-import { TextureLineComponent } from "../../../sharedComponents/textureLineComponent"
-import { Nullable } from 'babylonjs/types';
-import { GlobalState } from '../../../globalState';
-import { PortHelper } from '../portHelper';
-import { ReflectionTextureNodeModel } from './reflectionTextureNodeModel';
-
-/**
- * GenericNodeWidgetProps
- */
-export interface IReflectionTextureNodeWidgetProps {
-    node: Nullable<ReflectionTextureNodeModel>;
-    globalState: GlobalState;
-}
-
-/**
- * Used to display a node block for the node editor
- */
-export class ReflectionTextureNodeWidget extends React.Component<IReflectionTextureNodeWidgetProps> {
-	/**
-	 * Creates a GenericNodeWidget
-	 * @param props 
-	 */
-    constructor(props: IReflectionTextureNodeWidgetProps) {
-        super(props);
-        this.state = {};
-
-        if (this.props.node) {
-            this.props.node.addListener({
-                selectionChanged: () => {
-                    let selected = (this.props.node as any).selected;
-                    this.props.globalState.onSelectionChangedObservable.notifyObservers(selected ? this.props.node : null);
-                }
-            });
-        }
-    }
-
-    render() {
-        // Input/Output ports
-        var outputPorts = PortHelper.GenerateOutputPorts(this.props.node, false);
-        var inputPorts = PortHelper.GenerateInputPorts(this.props.node);
-
-        return (
-            <div className={"diagramBlock texture-block"}>
-                <div className="header">
-                    {this.props.node!.block!.name}
-                </div>
-                <div className="inputs">
-                    {inputPorts}
-                </div>
-                <div className="outputs">
-                    {outputPorts}
-                </div>
-                {
-                    this.props.node && this.props.node.texture &&
-                    <TextureLineComponent ref="textureView" width={140} height={140} texture={this.props.node.texture} hideChannelSelect={true} />
-                }
-            </div>
-        );
-    }
-}

+ 0 - 39
nodeEditor/src/components/diagram/remap/remapNodeFactory.tsx

@@ -1,39 +0,0 @@
-import * as SRD from "storm-react-diagrams";
-import * as React from "react";
-import { GlobalState } from '../../../globalState';
-import { RemapNodeModel } from './remapNodeModel';
-import { RemapNodeWidget } from './remapNodeWidget';
-
-/**
- * Node factory which creates editor nodes
- */
-export class RemapNodeFactory extends SRD.AbstractNodeFactory {
-    private _globalState: GlobalState;
-
-	/**
-	 * Constructs a GenericNodeFactory
-	 */
-    constructor(globalState: GlobalState) {
-        super("remap");
-
-        this._globalState = globalState;
-    }
-
-	/**
-	 * Generates a node widget
-	 * @param diagramEngine diagram engine
-	 * @param node node to generate
-	 * @returns node widget jsx
-	 */
-    generateReactWidget(diagramEngine: SRD.DiagramEngine, node: RemapNodeModel): JSX.Element {
-        return <RemapNodeWidget node={node} globalState={this._globalState} />;
-    }
-
-	/**
-	 * Gets a new instance of a node model
-	 * @returns input node model
-	 */
-    getNewInstance() {
-        return new RemapNodeModel();
-    }
-}

+ 0 - 28
nodeEditor/src/components/diagram/remap/remapNodeModel.tsx

@@ -1,28 +0,0 @@
-import * as React from "react";
-import { DefaultNodeModel } from '../defaultNodeModel';
-import { GlobalState } from '../../../globalState';
-import { RemapPropertyTabComponentProps } from './remapNodePropertyComponent';
-import { RemapBlock } from 'babylonjs/Materials/Node/Blocks/remapBlock';
-
-/**
- * Generic node model which stores information about a node editor block
- */
-export class RemapNodeModel extends DefaultNodeModel {
-
-    public get remapBlock(): RemapBlock {
-        return this.block as RemapBlock;
-    }
-
-	/**
-	 * Constructs the node model
-	 */
-    constructor() {
-        super("remap");
-    }
-
-    renderProperties(globalState: GlobalState) {
-        return (
-            <RemapPropertyTabComponentProps globalState={globalState} remapNode={this} />
-        );
-    }
-}

+ 0 - 95
nodeEditor/src/components/diagram/remap/remapNodeWidget.tsx

@@ -1,95 +0,0 @@
-import * as React from "react";
-import { RemapNodeModel } from './remapNodeModel';
-import { Nullable } from 'babylonjs/types';
-import { GlobalState } from '../../../globalState';
-import { PortHelper } from '../portHelper';
-import { RemapBlock } from 'babylonjs/Materials/Node/Blocks/remapBlock';
-import { NodeMaterialConnectionPoint } from 'babylonjs/Materials/Node/nodeMaterialBlockConnectionPoint';
-import { InputBlock } from 'babylonjs/Materials/Node/Blocks/Input/inputBlock';
-
-/**
- * RemapNodeWidgetProps
- */
-export interface RemapNodeWidgetProps {
-    node: Nullable<RemapNodeModel>;
-    globalState: GlobalState;
-}
-
-/**
- * Used to display a node block for the node editor
- */
-export class RemapNodeWidget extends React.Component<RemapNodeWidgetProps> {
-	/**
-	 * Creates a GenericNodeWidget
-	 * @param props 
-	 */
-    constructor(props: RemapNodeWidgetProps) {
-        super(props);
-        this.state = {};
-
-        if (this.props.node) {
-            this.props.node.addListener({
-                selectionChanged: () => {
-                    let selected = (this.props.node as any).selected;
-                    this.props.globalState.onSelectionChangedObservable.notifyObservers(selected ? this.props.node : null);
-                }
-            });
-        }
-    }
-
-    renderValue(value: string) {
-        if (value) {
-            return (
-                <div className="value-text">
-                    {value}
-                </div>
-            )
-        }
-
-        return null;
-    }
-
-    extractInputValue(connectionPoint: NodeMaterialConnectionPoint) {
-        let connectedBlock = connectionPoint.connectedPoint!.ownerBlock;
-
-        if (connectedBlock.isInput) {
-            let inputBlock = connectedBlock as InputBlock;
-
-            if (inputBlock.isUniform && !inputBlock.isSystemValue) {
-                return inputBlock.value;
-            }
-        }
-
-        return "?";
-    }
-
-    render() {
-        var inputPorts = PortHelper.GenerateInputPorts(this.props.node, undefined, false);
-        var outputPorts = PortHelper.GenerateOutputPorts(this.props.node, false);
-        let remapBlock = this.props.node!.block! as RemapBlock;
-
-        let sourceRangeX = remapBlock.sourceMin.isConnected ? this.extractInputValue(remapBlock.sourceMin) : remapBlock.sourceRange.x;
-        let sourceRangeY = remapBlock.sourceMax.isConnected ? this.extractInputValue(remapBlock.sourceMax) : remapBlock.sourceRange.y;
-        let targetRangeX = remapBlock.targetMin.isConnected ? this.extractInputValue(remapBlock.targetMin) : remapBlock.targetRange.x;
-        let targetRangeY = remapBlock.targetMax.isConnected ? this.extractInputValue(remapBlock.targetMax) : remapBlock.targetRange.y;
-
-        return (
-            <div className={"diagramBlock remap"}>
-                <div className="header">
-                    {remapBlock.name}
-                </div>
-                <div className="inputs">
-                    {inputPorts}
-                </div>
-                <div className="outputs">
-                    {outputPorts}
-                </div>
-                <div className="value">  
-                {
-                    `[${sourceRangeX}, ${sourceRangeY}] -> [${targetRangeX}, ${targetRangeY}]`
-                }                 
-                </div>
-            </div>
-        );
-    }
-}

+ 0 - 39
nodeEditor/src/components/diagram/texture/textureNodeFactory.tsx

@@ -1,39 +0,0 @@
-import * as SRD from "storm-react-diagrams";
-import { TextureNodeWidget } from "./textureNodeWidget";
-import { TextureNodeModel } from "./textureNodeModel";
-import * as React from "react";
-import { GlobalState } from '../../../globalState';
-
-/**
- * Node factory which creates editor nodes
- */
-export class TextureNodeFactory extends SRD.AbstractNodeFactory {
-    private _globalState: GlobalState;
-
-	/**
-	 * Constructs a TextureNodeFactory
-	 */
-    constructor(globalState: GlobalState) {
-        super("texture");
-
-        this._globalState = globalState;
-    }
-
-	/**
-	 * Generates a node widget
-	 * @param diagramEngine diagram engine
-	 * @param node node to generate
-	 * @returns node widget jsx
-	 */
-    generateReactWidget(diagramEngine: SRD.DiagramEngine, node: TextureNodeModel): JSX.Element {
-        return <TextureNodeWidget node={node} globalState={this._globalState} />;
-    }
-
-	/**
-	 * Gets a new instance of a node model
-	 * @returns texture node model
-	 */
-    getNewInstance() {
-        return new TextureNodeModel();
-    }
-}

+ 0 - 47
nodeEditor/src/components/diagram/texture/textureNodeModel.tsx

@@ -1,47 +0,0 @@
-import * as React from 'react';
-import { Nullable } from 'babylonjs/types';
-import { DefaultNodeModel } from '../defaultNodeModel';
-import { GlobalState } from '../../../globalState';
-import { TexturePropertyTabComponent } from './texturePropertyTabComponent';
-import { NodeCreationOptions, GraphEditor } from '../../../graphEditor';
-import { DiagramModel } from 'storm-react-diagrams/dist/@types/src/models/DiagramModel';
-import { TextureBlock } from 'babylonjs/Materials/Node/Blocks/Dual/textureBlock';
-import { Texture } from 'babylonjs/Materials/Textures/texture';
-
-/**
- * Texture node model which stores information about a node editor block
- */
-export class TextureNodeModel extends DefaultNodeModel {
-    private _block: TextureBlock;
-
-	/**
-	 * Texture for the node if it exists
-	 */
-    public get texture(): Nullable<Texture> {
-        return this._block.texture;
-    }
-
-    public set texture(value: Nullable<Texture>) {
-        this._block.texture = value;
-    }
-
-	/**
-	 * Constructs the node model
-	 */
-    constructor() {
-        super("texture");
-    }
-
-    renderProperties(globalState: GlobalState) {
-        return (
-            <TexturePropertyTabComponent globalState={globalState} node={this} />
-        );
-    }
-
-    prepare(options: NodeCreationOptions, nodes: Array<DefaultNodeModel>, model: DiagramModel, graphEditor: GraphEditor) {
-        this._block = options.nodeMaterialBlock as TextureBlock;
-
-        super.prepare(options, nodes, model, graphEditor);
-    }
-
-}

+ 0 - 61
nodeEditor/src/components/diagram/texture/textureNodeWidget.tsx

@@ -1,61 +0,0 @@
-import * as React from "react";
-import { TextureNodeModel } from './textureNodeModel';
-import { TextureLineComponent } from "../../../sharedComponents/textureLineComponent"
-import { Nullable } from 'babylonjs/types';
-import { GlobalState } from '../../../globalState';
-import { PortHelper } from '../portHelper';
-
-/**
- * GenericNodeWidgetProps
- */
-export interface ITextureNodeWidgetProps {
-    node: Nullable<TextureNodeModel>;
-    globalState: GlobalState;
-}
-
-/**
- * Used to display a node block for the node editor
- */
-export class TextureNodeWidget extends React.Component<ITextureNodeWidgetProps> {
-	/**
-	 * Creates a GenericNodeWidget
-	 * @param props 
-	 */
-    constructor(props: ITextureNodeWidgetProps) {
-        super(props);
-        this.state = {};
-
-        if (this.props.node) {
-            this.props.node.addListener({
-                selectionChanged: () => {
-                    let selected = (this.props.node as any).selected;
-                    this.props.globalState.onSelectionChangedObservable.notifyObservers(selected ? this.props.node : null);
-                }
-            });
-        }
-    }
-
-    render() {
-        // Input/Output ports
-        var outputPorts = PortHelper.GenerateOutputPorts(this.props.node, false);
-        var inputPorts = PortHelper.GenerateInputPorts(this.props.node, ["uv"]);
-
-        return (
-            <div className={"diagramBlock texture-block"}>
-                <div className="header">
-                    {this.props.node!.block!.name}
-                </div>
-                <div className="inputs">
-                    {inputPorts}
-                </div>
-                <div className="outputs">
-                    {outputPorts}
-                </div>
-                {
-                    this.props.node && this.props.node.texture &&
-                    <TextureLineComponent ref="textureView" width={140} height={140} texture={this.props.node.texture} hideChannelSelect={true} />
-                }
-            </div>
-        );
-    }
-}

+ 0 - 39
nodeEditor/src/components/diagram/trigonometry/trigonometryNodeFactory.tsx

@@ -1,39 +0,0 @@
-import * as SRD from "storm-react-diagrams";
-import * as React from "react";
-import { GlobalState } from '../../../globalState';
-import { TrigonometryNodeModel } from './trigonometryNodeModel';
-import { TrigonometryNodeWidget } from './trigonometryNodeWidget';
-
-/**
- * Node factory which creates editor nodes
- */
-export class TrigonometryNodeFactory extends SRD.AbstractNodeFactory {
-    private _globalState: GlobalState;
-
-	/**
-	 * Constructs a GenericNodeFactory
-	 */
-    constructor(globalState: GlobalState) {
-        super("trigonometry");
-
-        this._globalState = globalState;
-    }
-
-	/**
-	 * Generates a node widget
-	 * @param diagramEngine diagram engine
-	 * @param node node to generate
-	 * @returns node widget jsx
-	 */
-    generateReactWidget(diagramEngine: SRD.DiagramEngine, node: TrigonometryNodeModel): JSX.Element {
-        return <TrigonometryNodeWidget node={node} globalState={this._globalState} />;
-    }
-
-	/**
-	 * Gets a new instance of a node model
-	 * @returns input node model
-	 */
-    getNewInstance() {
-        return new TrigonometryNodeModel();
-    }
-}

+ 0 - 28
nodeEditor/src/components/diagram/trigonometry/trigonometryNodeModel.tsx

@@ -1,28 +0,0 @@
-import * as React from "react";
-import { DefaultNodeModel } from '../defaultNodeModel';
-import { GlobalState } from '../../../globalState';
-import { TrigonometryPropertyTabComponentProps } from './trigonometryNodePropertyComponent';
-import { TrigonometryBlock } from 'babylonjs/Materials/Node/Blocks/trigonometryBlock';
-
-/**
- * Generic node model which stores information about a node editor block
- */
-export class TrigonometryNodeModel extends DefaultNodeModel {
-
-    public get trigonometryBlock(): TrigonometryBlock {
-        return this.block as TrigonometryBlock;
-    }
-
-	/**
-	 * Constructs the node model
-	 */
-    constructor() {
-        super("trigonometry");
-    }
-
-    renderProperties(globalState: GlobalState) {
-        return (
-            <TrigonometryPropertyTabComponentProps globalState={globalState} trigonometryNode={this} />
-        );
-    }
-}

+ 0 - 63
nodeEditor/src/components/diagram/trigonometry/trigonometryNodeWidget.tsx

@@ -1,63 +0,0 @@
-import * as React from "react";
-import { TrigonometryNodeModel } from './trigonometryNodeModel';
-import { Nullable } from 'babylonjs/types';
-import { GlobalState } from '../../../globalState';
-import { PortHelper } from '../portHelper';
-import { TrigonometryBlockOperations } from 'babylonjs/Materials/Node/Blocks/trigonometryBlock';
-
-/**
- * GenericNodeWidgetProps
- */
-export interface ITrigonometryNodeWidgetProps {
-    node: Nullable<TrigonometryNodeModel>;
-    globalState: GlobalState;
-}
-
-/**
- * Used to display a node block for the node editor
- */
-export class TrigonometryNodeWidget extends React.Component<ITrigonometryNodeWidgetProps> {
-	/**
-	 * Creates a GenericNodeWidget
-	 * @param props 
-	 */
-    constructor(props: ITrigonometryNodeWidgetProps) {
-        super(props);
-        this.state = {};
-
-        if (this.props.node) {
-            this.props.node.addListener({
-                selectionChanged: () => {
-                    let selected = (this.props.node as any).selected;
-                    this.props.globalState.onSelectionChangedObservable.notifyObservers(selected ? this.props.node : null);
-                }
-            });
-        }
-    }
-
-    render() {
-        var outputPorts = PortHelper.GenerateOutputPorts(this.props.node, false);        
-        var inputPorts = PortHelper.GenerateInputPorts(this.props.node);
-
-        let trigonometryBlock = this.props.node!.trigonometryBlock;
-
-        return (
-            <div className={"diagramBlock trigonometry"}>
-                <div className="header">
-                    {trigonometryBlock.name}
-                </div>
-                <div className="inputs">
-                    {inputPorts}
-                </div>
-                <div className="outputs">
-                    {outputPorts}
-                </div>
-                <div className="value">
-                    <div className="value-text">
-                        {TrigonometryBlockOperations[trigonometryBlock.operation]}
-                    </div>
-                </div>
-            </div>
-        );
-    }
-}

+ 27 - 5
nodeEditor/src/components/propertyTab/propertyTabComponent.tsx

@@ -2,7 +2,6 @@
 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';
 import { LineContainerComponent } from '../../sharedComponents/lineContainerComponent';
 import { StringTools } from '../../stringTools';
@@ -11,13 +10,15 @@ import { Tools } from 'babylonjs/Misc/tools';
 import { SerializationTools } from '../../serializationTools';
 import { CheckBoxLineComponent } from '../../sharedComponents/checkBoxLineComponent';
 import { DataStorage } from '../../dataStorage';
+import { GraphNode } from '../../diagram/graphNode';
+import { SliderLineComponent } from '../../sharedComponents/sliderLineComponent';
 require("./propertyTab.scss");
 
 interface IPropertyTabComponentProps {
     globalState: GlobalState;
 }
 
-export class PropertyTabComponent extends React.Component<IPropertyTabComponentProps, { currentNode: Nullable<DefaultNodeModel> }> {
+export class PropertyTabComponent extends React.Component<IPropertyTabComponentProps, { currentNode: Nullable<GraphNode> }> {
 
     constructor(props: IPropertyTabComponentProps) {
         super(props)
@@ -26,8 +27,12 @@ export class PropertyTabComponent extends React.Component<IPropertyTabComponentP
     }
 
     componentDidMount() {
-        this.props.globalState.onSelectionChangedObservable.add(block => {
-            this.setState({ currentNode: block });
+        this.props.globalState.onSelectionChangedObservable.add(selection => {
+            if (selection instanceof GraphNode) {
+                this.setState({ currentNode: selection });
+            } else {
+                this.setState({ currentNode: null });
+            }
         });
     }
 
@@ -63,10 +68,11 @@ export class PropertyTabComponent extends React.Component<IPropertyTabComponentP
                             NODE MATERIAL EDITOR
                         </div>
                     </div>
-                    {this.state.currentNode.renderProperties(this.props.globalState)}
+                    {this.state.currentNode.renderProperties()}
                 </div>
             );
         }
+        let gridSize = DataStorage.ReadNumber("GridSize", 20);
 
         return (
             <div id="propertyTab">
@@ -98,6 +104,22 @@ export class PropertyTabComponent extends React.Component<IPropertyTabComponentP
                                 DataStorage.StoreBoolean("EmbedTextures", value);
                             }}
                         />
+                        <SliderLineComponent label="Grid size" minimum={0} maximum={100} step={5} 
+                            decimalCount={0} 
+                            directValue={gridSize}
+                            onChange={value => {
+                                DataStorage.StoreNumber("GridSize", value);                                
+                                this.props.globalState.onGridSizeChanged.notifyObservers();
+                                this.forceUpdate();
+                            }}
+                        />
+                        <CheckBoxLineComponent label="Show grid" 
+                            isSelected={() => DataStorage.ReadBoolean("ShowGrid", true)}
+                            onSelect={(value: boolean) => {
+                                DataStorage.StoreBoolean("ShowGrid", value);                
+                                this.props.globalState.onGridSizeChanged.notifyObservers();
+                            }}
+                        />
                     </LineContainerComponent>
                     <LineContainerComponent title="FILE">                        
                         <FileButtonLineComponent label="Load" onClick={(file) => this.load(file)} accept=".json" />

+ 28 - 0
nodeEditor/src/diagram/display/clampDisplayManager.ts

@@ -0,0 +1,28 @@
+import { IDisplayManager } from './displayManager';
+import { NodeMaterialBlock } from 'babylonjs/Materials/Node/nodeMaterialBlock';
+import { ClampBlock } from 'babylonjs/Materials/Node/Blocks/clampBlock';
+
+export class ClampDisplayManager implements IDisplayManager {
+    public getHeaderClass(block: NodeMaterialBlock) {
+        return "";
+    }
+
+    public shouldDisplayPortLabels(block: NodeMaterialBlock): boolean {
+        return false;
+    }
+
+    public getHeaderText(block: NodeMaterialBlock): string {
+        return block.name;
+    }
+
+    public getBackgroundColor(block: NodeMaterialBlock): string {
+        return "#4086BB";
+    }
+
+    public updatePreviewContent(block: NodeMaterialBlock, contentArea: HTMLDivElement): void {       
+        const clampBlock = block as ClampBlock;
+
+        contentArea.classList.add("clamp-block");
+        contentArea.innerHTML = `[${clampBlock.minimum}, ${clampBlock.maximum}]`;
+    }
+}

+ 9 - 0
nodeEditor/src/diagram/display/displayManager.ts

@@ -0,0 +1,9 @@
+import { NodeMaterialBlock } from 'babylonjs/Materials/Node/nodeMaterialBlock';
+
+export interface IDisplayManager {
+    getHeaderClass(block: NodeMaterialBlock): string;
+    shouldDisplayPortLabels(block: NodeMaterialBlock): boolean;
+    updatePreviewContent(block: NodeMaterialBlock, contentArea: HTMLDivElement): void;
+    getBackgroundColor(block: NodeMaterialBlock): string;
+    getHeaderText(block: NodeMaterialBlock): string;
+}

+ 29 - 0
nodeEditor/src/diagram/display/gradientDisplayManager.ts

@@ -0,0 +1,29 @@
+import { IDisplayManager } from './displayManager';
+import { NodeMaterialBlock } from 'babylonjs/Materials/Node/nodeMaterialBlock';
+import { GradientBlock } from 'babylonjs/Materials/Node/Blocks/gradientBlock';
+
+export class GradientDisplayManager implements IDisplayManager {
+    public getHeaderClass(block: NodeMaterialBlock) {
+        return "";
+    }
+
+    public shouldDisplayPortLabels(block: NodeMaterialBlock): boolean {
+        return false;
+    }
+
+    public getHeaderText(block: NodeMaterialBlock): string {
+        return block.name;
+    }
+
+    public getBackgroundColor(block: NodeMaterialBlock): string {
+        let gradientBlock = block as GradientBlock;
+
+        let gradients = gradientBlock.colorSteps.map(c => `rgb(${c.color.r * 255}, ${c.color.g * 255}, ${c.color.b * 255}) ${c.step * 100}%`);
+
+        return gradients.length ? `linear-gradient(90deg, ${gradients.join(", ")})` : 'black';
+    }
+
+    public updatePreviewContent(block: NodeMaterialBlock, contentArea: HTMLDivElement): void {       
+        contentArea.classList.add("gradient-block");
+    }
+}

+ 123 - 0
nodeEditor/src/diagram/display/inputDisplayManager.ts

@@ -0,0 +1,123 @@
+import { IDisplayManager } from './displayManager';
+import { NodeMaterialBlock } from 'babylonjs/Materials/Node/nodeMaterialBlock';
+import { InputBlock } from 'babylonjs/Materials/Node/Blocks/Input/inputBlock';
+import { NodeMaterialSystemValues } from 'babylonjs/Materials/Node/Enums/nodeMaterialSystemValues';
+import { NodeMaterialBlockConnectionPointTypes } from 'babylonjs/Materials/Node/Enums/nodeMaterialBlockConnectionPointTypes';
+import { AnimatedInputBlockTypes } from 'babylonjs/Materials/Node/Blocks/Input/animatedInputBlockTypes';
+import { Vector2, Vector3, Vector4 } from 'babylonjs/Maths/math.vector';
+import { Color3 } from 'babylonjs/Maths/math.color';
+import { BlockTools } from '../../blockTools';
+import { StringTools } from '../../stringTools';
+
+export class InputDisplayManager implements IDisplayManager {
+    public getHeaderClass(block: NodeMaterialBlock) {
+        let inputBlock = block as InputBlock;
+
+        if (inputBlock.isConstant) {
+            return "constant"
+        }
+
+        if (inputBlock.visibleInInspector) {
+            return "inspector"
+        }
+
+        return "";
+    }
+
+    public shouldDisplayPortLabels(block: NodeMaterialBlock): boolean {
+        return false;
+    }
+
+    public getHeaderText(block: NodeMaterialBlock): string {
+        let inputBlock = block as InputBlock;
+        let name = `${inputBlock.name} (${StringTools.GetBaseType(inputBlock.output.type)})`;
+
+        if (inputBlock.isAttribute) {
+            name = StringTools.GetBaseType(inputBlock.output.type);
+        }
+
+        return name;
+    }
+
+    public getBackgroundColor(block: NodeMaterialBlock): string {
+        let color = "";        
+        let inputBlock = block as InputBlock;
+
+        switch (inputBlock.type) {                    
+            case NodeMaterialBlockConnectionPointTypes.Color3:
+            case NodeMaterialBlockConnectionPointTypes.Color4: {
+                color = (inputBlock.value as Color3).toHexString();
+                break;
+            }
+            default:
+                color = BlockTools.GetColorFromConnectionNodeType(inputBlock.type);
+                break;
+        }
+
+        return color;
+    }
+
+    public updatePreviewContent(block: NodeMaterialBlock, contentArea: HTMLDivElement): void {
+        let value = "";
+        let inputBlock = block as InputBlock;
+
+        if (inputBlock.isAttribute) {
+            value = "mesh." + inputBlock.name;
+        } else if (inputBlock.isSystemValue) {
+            switch (inputBlock.systemValue) {
+                case NodeMaterialSystemValues.World:
+                    value = "World";
+                    break;
+                case NodeMaterialSystemValues.WorldView:
+                    value = "World x View";
+                    break;
+                case NodeMaterialSystemValues.WorldViewProjection:
+                    value = "World x View x Projection";
+                    break;
+                case NodeMaterialSystemValues.View:
+                    value = "View";
+                    break;
+                case NodeMaterialSystemValues.ViewProjection:
+                    value = "View x Projection";
+                    break;
+                case NodeMaterialSystemValues.Projection:
+                    value = "Projection";
+                    break;
+                case NodeMaterialSystemValues.CameraPosition:
+                    value = "Camera position";
+                    break;
+                case NodeMaterialSystemValues.FogColor:
+                    value = "Fog color";
+                    break;
+                case NodeMaterialSystemValues.DeltaTime:
+                    value = "Delta time";
+                    break;
+            }
+        } else {
+            switch (inputBlock.type) {
+                case NodeMaterialBlockConnectionPointTypes.Float:
+                    if (inputBlock.animationType !== AnimatedInputBlockTypes.None) {
+                        value = AnimatedInputBlockTypes[inputBlock.animationType];
+                    } else {
+                        value = inputBlock.value.toFixed(2);
+                    }
+                    break;
+                case NodeMaterialBlockConnectionPointTypes.Vector2:
+                    let vec2Value = inputBlock.value as Vector2
+                    value = `(${vec2Value.x.toFixed(2)}, ${vec2Value.y.toFixed(2)})`;
+                    break;
+                case NodeMaterialBlockConnectionPointTypes.Vector3:
+                    let vec3Value = inputBlock.value as Vector3
+                    value = `(${vec3Value.x.toFixed(2)}, ${vec3Value.y.toFixed(2)}, ${vec3Value.z.toFixed(2)})`;
+                    break;
+                case NodeMaterialBlockConnectionPointTypes.Vector4:
+                    let vec4Value = inputBlock.value as Vector4
+                    value = `(${vec4Value.x.toFixed(2)}, ${vec4Value.y.toFixed(2)}, ${vec4Value.z.toFixed(2)}, ${vec4Value.w.toFixed(2)})`;
+                    break;                        
+            }
+        }
+
+        contentArea.innerHTML = value;
+        contentArea.classList.add("input-block");
+    }
+}

+ 24 - 0
nodeEditor/src/diagram/display/outputDisplayManager.ts

@@ -0,0 +1,24 @@
+import { IDisplayManager } from './displayManager';
+import { NodeMaterialBlock } from 'babylonjs/Materials/Node/nodeMaterialBlock';
+
+export class OutputDisplayManager implements IDisplayManager {
+    public getHeaderClass(block: NodeMaterialBlock) {
+        return "";
+    }
+
+    public shouldDisplayPortLabels(block: NodeMaterialBlock): boolean {
+        return true;
+    }
+
+    public getHeaderText(block: NodeMaterialBlock): string {
+        return block.name;
+    }
+
+    public getBackgroundColor(block: NodeMaterialBlock): string {
+        return "rgb(106, 44, 131)";
+    }
+
+    public updatePreviewContent(block: NodeMaterialBlock, contentArea: HTMLDivElement): void {       
+        contentArea.classList.add("output-block");
+    }
+}

+ 49 - 0
nodeEditor/src/diagram/display/remapDisplayManager.ts

@@ -0,0 +1,49 @@
+import { IDisplayManager } from './displayManager';
+import { NodeMaterialBlock } from 'babylonjs/Materials/Node/nodeMaterialBlock';
+import { InputBlock } from 'babylonjs/Materials/Node/Blocks/Input/inputBlock';
+import { RemapBlock } from 'babylonjs/Materials/Node/Blocks/remapBlock';
+import { NodeMaterialConnectionPoint } from 'babylonjs/Materials/Node/nodeMaterialBlockConnectionPoint';
+
+export class RemapDisplayManager implements IDisplayManager {
+    public getHeaderClass(block: NodeMaterialBlock) {
+        return "";
+    }
+
+    public shouldDisplayPortLabels(block: NodeMaterialBlock): boolean {
+        return true;
+    }
+
+    public getHeaderText(block: NodeMaterialBlock): string {
+        return block.name;
+    }
+
+    public getBackgroundColor(block: NodeMaterialBlock): string {
+        return "#4086BB";
+    }
+
+    private _extractInputValue(connectionPoint: NodeMaterialConnectionPoint) {
+        let connectedBlock = connectionPoint.connectedPoint!.ownerBlock;
+
+        if (connectedBlock.isInput) {
+            let inputBlock = connectedBlock as InputBlock;
+
+            if (inputBlock.isUniform && !inputBlock.isSystemValue) {
+                return inputBlock.value;
+            }
+        }
+
+        return "?";
+    }
+
+    public updatePreviewContent(block: NodeMaterialBlock, contentArea: HTMLDivElement): void {       
+        const remapBlock = block as RemapBlock;
+
+        let sourceRangeX = remapBlock.sourceMin.isConnected ? this._extractInputValue(remapBlock.sourceMin) : remapBlock.sourceRange.x;
+        let sourceRangeY = remapBlock.sourceMax.isConnected ? this._extractInputValue(remapBlock.sourceMax) : remapBlock.sourceRange.y;
+        let targetRangeX = remapBlock.targetMin.isConnected ? this._extractInputValue(remapBlock.targetMin) : remapBlock.targetRange.x;
+        let targetRangeY = remapBlock.targetMax.isConnected ? this._extractInputValue(remapBlock.targetMax) : remapBlock.targetRange.y;        
+
+        contentArea.classList.add("remap-block");
+        contentArea.innerHTML = `[${sourceRangeX}, ${sourceRangeY}] -> [${targetRangeX}, ${targetRangeY}]`;
+    }
+}

+ 54 - 0
nodeEditor/src/diagram/display/textureDisplayManager.ts

@@ -0,0 +1,54 @@
+import { IDisplayManager } from './displayManager';
+import { NodeMaterialBlock } from 'babylonjs/Materials/Node/nodeMaterialBlock';
+import { TextureBlock } from 'babylonjs/Materials/Node/Blocks/Dual/textureBlock';
+import { ReflectionTextureBlock } from 'babylonjs/Materials/Node/Blocks/Dual/reflectionTextureBlock';
+import { TextureLineComponent } from '../../sharedComponents/textureLineComponent';
+
+export class TextureDisplayManager implements IDisplayManager {
+    private _previewCanvas: HTMLCanvasElement;
+    private _previewImage: HTMLImageElement;
+
+    public getHeaderClass(block: NodeMaterialBlock) {
+        return "";
+    }
+
+    public shouldDisplayPortLabels(block: NodeMaterialBlock): boolean {
+        return true;
+    }
+
+    public getHeaderText(block: NodeMaterialBlock): string {
+        return block.name;
+    }
+
+    public getBackgroundColor(block: NodeMaterialBlock): string {
+        return "#323232";
+    }
+
+    public updatePreviewContent(block: NodeMaterialBlock, contentArea: HTMLDivElement): void {       
+        const textureBlock = block as TextureBlock | ReflectionTextureBlock;
+
+        if (!this._previewCanvas) {
+            contentArea.classList.add("texture-block");
+
+            this._previewCanvas = contentArea.ownerDocument!.createElement("canvas");
+            this._previewImage = contentArea.ownerDocument!.createElement("img");
+            contentArea.appendChild(this._previewImage);
+            this._previewImage.classList.add("empty");
+        }
+
+        if (textureBlock.texture) {
+            TextureLineComponent.UpdatePreview(this._previewCanvas, textureBlock.texture, 140, {
+                face: 0,
+                displayRed: true,
+                displayAlpha: true,
+                displayBlue: true,
+                displayGreen: true
+            }, () => {
+                this._previewImage.src = this._previewCanvas.toDataURL("image/png");
+                this._previewImage.classList.remove("empty");
+            });
+        } else {
+            this._previewImage.classList.add("empty");
+        }
+    }
+}

+ 28 - 0
nodeEditor/src/diagram/display/trigonometryDisplayManager.ts

@@ -0,0 +1,28 @@
+import { IDisplayManager } from './displayManager';
+import { NodeMaterialBlock } from 'babylonjs/Materials/Node/nodeMaterialBlock';
+import { TrigonometryBlock, TrigonometryBlockOperations } from 'babylonjs/Materials/Node/Blocks/trigonometryBlock';
+
+export class TrigonometryDisplayManager implements IDisplayManager {
+    public getHeaderClass(block: NodeMaterialBlock) {
+        return "";
+    }
+
+    public shouldDisplayPortLabels(block: NodeMaterialBlock): boolean {
+        return false;
+    }
+
+    public getHeaderText(block: NodeMaterialBlock): string {
+        return block.name;
+    }
+
+    public getBackgroundColor(block: NodeMaterialBlock): string {
+        return "#405C86";
+    }
+
+    public updatePreviewContent(block: NodeMaterialBlock, contentArea: HTMLDivElement): void {       
+        const trigonometryBlock = block as TrigonometryBlock;
+
+        contentArea.classList.add("trigonometry-block");
+        contentArea.innerHTML = TrigonometryBlockOperations[trigonometryBlock.operation];
+    }
+}

+ 21 - 0
nodeEditor/src/diagram/displayLedger.ts

@@ -0,0 +1,21 @@
+import { InputDisplayManager } from './display/inputDisplayManager';
+import { OutputDisplayManager } from './display/outputDisplayManager';
+import { ClampDisplayManager } from './display/clampDisplayManager';
+import { GradientDisplayManager } from './display/gradientDisplayManager';
+import { RemapDisplayManager } from './display/remapDisplayManager';
+import { TrigonometryDisplayManager } from './display/trigonometryDisplayManager';
+import { TextureDisplayManager } from './display/textureDisplayManager';
+
+export class DisplayLedger {
+    public static RegisteredControls: {[key: string] : any} = {};
+}
+
+DisplayLedger.RegisteredControls["InputBlock"] = InputDisplayManager;
+DisplayLedger.RegisteredControls["VertexOutputBlock"] = OutputDisplayManager;
+DisplayLedger.RegisteredControls["FragmentOutputBlock"] = OutputDisplayManager;
+DisplayLedger.RegisteredControls["ClampBlock"] = ClampDisplayManager;
+DisplayLedger.RegisteredControls["GradientBlock"] = GradientDisplayManager;
+DisplayLedger.RegisteredControls["RemapBlock"] = RemapDisplayManager;
+DisplayLedger.RegisteredControls["TrigonometryBlock"] = TrigonometryDisplayManager;
+DisplayLedger.RegisteredControls["TextureBlock"] = TextureDisplayManager;
+DisplayLedger.RegisteredControls["ReflectionTextureBlock"] = TextureDisplayManager;

+ 280 - 0
nodeEditor/src/diagram/graphCanvas.scss

@@ -0,0 +1,280 @@
+#graph-canvas {
+    width: 100%;
+    height: 100%;
+    margin: 0;
+    padding: 0;            
+    font: 14px "acumin-pro";  
+    user-select: none;
+    overflow: hidden;
+    background-image:
+        linear-gradient(to right, #4F4E4F 1px, transparent 1px),
+        linear-gradient(to bottom, #4F4E4F 1px, transparent 1px);  
+
+    #selection-container {
+        pointer-events: none;
+        
+        .selection-box {
+            z-index: 10;
+            position: absolute;
+            background: rgba(72, 72, 196, 0.5);
+            border: blue solid 4px;
+            border-radius: 10px;
+        }
+    }
+
+    #graph-container {
+        width: 100%;
+        height: 100%;
+        left: 0;
+        top: 0;
+        transform-origin: left top;
+        display: grid;
+        grid-template: 100% 100%;     
+        cursor: move;   
+
+        #graph-svg-container {
+            grid-row: 1;
+            grid-column: 1;
+            position: relative;
+            width: 100%;
+            height: 100%;  
+            overflow: visible; 
+            
+            .link {
+                stroke-width: 4px;    
+                &.selected {                    
+                    stroke: white !important;
+                    stroke-dasharray: 10, 2;
+                }       
+            }
+
+            .selection-link {
+                stroke-width: 16px;
+                opacity: 0;
+                &:hover, &.selected {
+                    stroke: white !important;
+                    opacity: 0.4;
+                }
+                transition: opacity 75ms;
+                stroke: transparent;                        
+                cursor: pointer;
+            }
+        }
+
+        #graph-canvas-container {
+            grid-row: 1;
+            grid-column: 1;
+            position: relative;
+            width: 100%;
+            height: 100%;                  
+
+            .visual {
+                z-index: 1;
+                width: 200px;
+                position: absolute;
+                left: 0;
+                top: 0;
+                background: gray;
+                border: 4px solid black;
+                border-radius: 12px;
+                display: grid;
+                grid-template-rows: 30px auto;
+                grid-template-columns: 100%;
+                color: white;
+
+                .selection-border {                    
+                    grid-row: 1 / span 3;
+                    grid-column: 1;
+                    margin: -4px;
+
+                    transition: border-color 100ms;
+
+                    border: 4px solid transparent;
+                    border-radius: 12px;
+                }
+
+                &.selected {
+                    .selection-border {  
+                        border-color: white;
+                    }
+                }
+
+                .header {
+                    grid-row: 1;
+                    grid-column: 1;
+                    border: 4px solid black;
+                    border-top-right-radius: 7px;
+                    border-top-left-radius: 7px;
+                    font-size: 16px;
+                    text-align: center;
+                    margin-top: -1px;
+                    margin-left: -1px;
+                    margin-right: -1px;
+                    white-space: nowrap;
+                    text-overflow: ellipsis;
+                    overflow: hidden;
+                    background: black;
+                    color: white;
+
+                    &.constant {
+                        border-color: #4E5C74;
+                        background: #4E5C74;
+                    }
+            
+                    &.inspector {
+                        border-color: #396437;
+                        background: #396437;
+                    }
+                }
+
+                .connections {
+                    grid-row: 2;
+                    grid-column: 1;
+
+                    display: grid;
+                    grid-template-columns: 50% 50%;
+
+                    .port {
+                        border-radius: 20px;
+                        width: 20px;
+                        height: 20px;                                                    
+                        align-self: center;   
+                        
+                        .img {
+                            width: 100%;
+                        }     
+                        
+                        &:hover, &.selected {
+                            filter: brightness(2);
+                        }
+                    }
+
+                    .portLine {
+                        height: 24px;
+                        display: grid;                
+                        grid-template-rows: 100%;
+                    }
+
+                    .label {                   
+                        align-items: center;
+                    }
+
+                    .inputsContainer {                        
+                        grid-row: 1;
+                        grid-column: 1;
+
+                        .portLine {
+                            grid-template-columns: 12px calc(100% - 15px);
+
+                            .label {                    
+                                grid-row: 1;
+                                grid-column: 2;
+                            }
+
+                            .port {                              
+                                grid-row: 1;
+                                grid-column: 1;
+                                transform: translateX(-12px);     
+                            }
+                        }
+                    }
+
+                    .outputsContainer {                        
+                        grid-row: 1;
+                        grid-column: 2;
+
+                        .portLine {
+                            grid-template-columns: calc(100% - 10px) 12px;
+
+                            .label {                    
+                                grid-row: 1;
+                                grid-column: 1;
+                                text-align: right;
+                            }
+
+                            .port {                              
+                                grid-row: 1;
+                                grid-column: 2;
+                                transform: translateX(2px);                        
+                            }
+                        
+                        }
+                    }
+                }
+
+                .content {
+                    min-height: 20px;
+                    grid-row: 3;
+                    grid-column: 1;
+
+                    &.input-block {
+                        grid-row: 2;
+                        height: 34px;
+                        text-align: center;
+                        font-size: 18px;
+                        font-weight: bold;
+                        margin: 0 10px;
+                    }
+
+                    &.output-block {
+                        min-height: 0px;
+                        height: 5px;
+                    }
+
+                    &.clamp-block {                    
+                        grid-row: 2;
+                        height: 34px;
+                        text-align: center;
+                        font-size: 18px;
+                        font-weight: bold;
+                        margin: 0 10px;
+                    }
+
+                    &.gradient-block {                    
+                        grid-row: 2;
+                        height: 34px;
+                    }
+
+                    &.texture-block {                    
+                        grid-row: 3;
+                        height: 140px;
+                        width: 140px;
+                        margin-top: -115px;
+                        overflow: hidden;
+                        border-bottom-left-radius: 7px;
+                        border: black 4px solid;
+                        border-left: 0px;
+                        border-bottom: 0px;
+
+                        img {
+                            width: 100%;
+                            height: 100%;
+                            pointer-events: none;
+
+                            &.empty {
+                                display: none;
+                            }
+                        }
+                    }
+
+                    &.remap-block {                    
+                        height: 34px;
+                        text-align: center;
+                        font-size: 18px;
+                        font-weight: bold;
+                        margin: 0 10px;
+                    }      
+                    
+                    &.trigonometry-block {                    
+                        grid-row: 2;
+                        height: 34px;
+                        text-align: center;
+                        font-size: 18px;
+                        font-weight: bold;
+                        margin: 0 10px;
+                    }
+                }
+            }
+        }
+    }
+}

+ 591 - 0
nodeEditor/src/diagram/graphCanvas.tsx

@@ -0,0 +1,591 @@
+import * as React from "react";
+import { GlobalState } from '../globalState';
+import { NodeMaterialBlock } from 'babylonjs/Materials/Node/nodeMaterialBlock';
+import { GraphNode } from './graphNode';
+import * as dagre from 'dagre';
+import { Nullable } from 'babylonjs/types';
+import { NodeLink } from './nodeLink';
+import { NodePort } from './nodePort';
+import { NodeMaterialConnectionPoint, NodeMaterialConnectionPointDirection, NodeMaterialConnectionPointCompatibilityStates } from 'babylonjs/Materials/Node/nodeMaterialBlockConnectionPoint';
+import { Vector2 } from 'babylonjs/Maths/math.vector';
+import { FragmentOutputBlock } from 'babylonjs/Materials/Node/Blocks/Fragment/fragmentOutputBlock';
+import { InputBlock } from 'babylonjs/Materials/Node/Blocks/Input/inputBlock';
+import { DataStorage } from '../dataStorage';
+
+require("./graphCanvas.scss");
+
+export interface IGraphCanvasComponentProps {
+    globalState: GlobalState
+}
+
+export class GraphCanvasComponent extends React.Component<IGraphCanvasComponentProps> {
+    private readonly MinZoom = 0.1;
+    private readonly MaxZoom = 4;
+
+    private _hostCanvas: HTMLDivElement;
+    private _graphCanvas: HTMLDivElement;
+    private _selectionContainer: HTMLDivElement;
+    private _svgCanvas: HTMLElement;
+    private _rootContainer: HTMLDivElement;
+    private _nodes: GraphNode[] = [];
+    private _links: NodeLink[] = [];
+    private _mouseStartPointX: Nullable<number> = null;
+    private _mouseStartPointY: Nullable<number> = null
+    private _dropPointX = 0;
+    private _dropPointY = 0;
+    private _selectionStartX = 0;
+    private _selectionStartY = 0;
+    private _x = 0;
+    private _y = 0;
+    private _zoom = 1;
+    private _selectedNodes: GraphNode[] = [];
+    private _selectedLink: Nullable<NodeLink> = null;
+    private _candidateLink: Nullable<NodeLink> = null;
+    private _candidatePort: Nullable<NodePort> = null;
+    private _gridSize = 20;
+    private _selectionBox: Nullable<HTMLDivElement> = null;
+
+    private _altKeyIsPressed = false;
+    private _ctrlKeyIsPressed = false;
+    private _oldY = -1;
+
+    public get gridSize() {
+        return this._gridSize;
+    }
+
+    public set gridSize(value: number) {
+        this._gridSize = value;
+        
+        this.updateTransform();
+    }
+
+    public get globalState(){
+        return this.props.globalState;
+    }
+
+    public get nodes() {
+        return this._nodes;
+    }
+
+    public get links() {
+        return this._links;
+    }
+
+    public get zoom() {
+        return this._zoom;
+    }
+
+    public set zoom(value: number) {
+        if (this._zoom === value) {
+            return;
+        }
+
+        this._zoom = value;
+        
+        this.updateTransform();
+    }    
+
+    public get x() {
+        return this._x;
+    }
+
+    public set x(value: number) {
+        this._x = value;
+        
+        this.updateTransform();
+    }
+
+    public get y() {
+        return this._y;
+    }
+
+    public set y(value: number) {
+        this._y = value;
+        
+        this.updateTransform();
+    }
+
+    public get selectedNodes() {
+        return this._selectedNodes;
+    }
+
+    public get selectedLink() {
+        return this._selectedLink;
+    }
+
+    public get canvasContainer() {
+        return this._graphCanvas;
+    }
+
+    public get svgCanvas() {
+        return this._svgCanvas;
+    }
+
+    constructor(props: IGraphCanvasComponentProps) {
+        super(props);
+
+        props.globalState.onSelectionChangedObservable.add(selection => {
+            
+            if (!selection) {
+                this._selectedNodes = [];
+                this._selectedLink = null;
+            } else {
+                if (selection instanceof NodeLink) {
+                    this._selectedLink = selection;
+                } else {
+                    if (this._ctrlKeyIsPressed) {
+                        if (this._selectedNodes.indexOf(selection) === -1) {
+                            this._selectedNodes.push(selection);
+                        }
+                    } else {                    
+                        this._selectedNodes = [selection];
+                    }
+                }
+            }
+        });
+
+        props.globalState.onCandidatePortSelected.add(port => {
+            this._candidatePort = port;
+        });
+
+        props.globalState.onGridSizeChanged.add(() => {
+            this.gridSize = DataStorage.ReadNumber("GridSize", 20);
+        });
+
+        this.props.globalState.hostDocument!.addEventListener("keyup", () => this.onKeyUp(), false);
+        this.props.globalState.hostDocument!.addEventListener("keydown", evt => {
+            this._altKeyIsPressed = evt.altKey;            
+            this._ctrlKeyIsPressed = evt.ctrlKey;
+        }, false);
+        this.props.globalState.hostDocument!.defaultView!.addEventListener("blur", () => {
+            this._altKeyIsPressed = false;
+            this._ctrlKeyIsPressed = false;
+        }, false);     
+    }
+
+    public getGridPosition(position: number) {
+        let gridSize = this.gridSize;
+		if (gridSize === 0) {
+			return position;
+		}
+		return gridSize * Math.floor(position / gridSize);
+	}
+
+    updateTransform() {
+        this._rootContainer.style.transform = `translate(${this._x}px, ${this._y}px) scale(${this._zoom})`;
+
+        if (DataStorage.ReadBoolean("ShowGrid", true)) {
+            this._hostCanvas.style.backgroundSize = `${this._gridSize * this._zoom}px ${this._gridSize * this._zoom}px`;
+            this._hostCanvas.style.backgroundPosition = `${this._x}px ${this._y}px`;
+        } else {
+            this._hostCanvas.style.backgroundSize = `0`;
+        }
+    }
+
+    onKeyUp() {        
+        this._altKeyIsPressed = false;
+        this._ctrlKeyIsPressed = false;
+        this._oldY = -1;
+    }
+
+    findNodeFromBlock(block: NodeMaterialBlock) {
+        return this.nodes.filter(n => n.block === block)[0];
+    }
+
+    reset() {
+        for (var node of this._nodes) {
+            node.dispose();
+        }
+        this._nodes = [];
+        this._links = [];
+        this._graphCanvas.innerHTML = "";
+        this._svgCanvas.innerHTML = "";
+    }
+
+    connectPorts(pointA: NodeMaterialConnectionPoint, pointB: NodeMaterialConnectionPoint) {
+        var blockA = pointA.ownerBlock;
+        var blockB = pointB.ownerBlock;
+        var nodeA = this.findNodeFromBlock(blockA);
+        var nodeB = this.findNodeFromBlock(blockB);
+
+        if (!nodeA || !nodeB) {
+            return;
+        }
+
+        var portA = nodeA.getPortForConnectionPoint(pointA);
+        var portB = nodeB.getPortForConnectionPoint(pointB);
+
+        if (!portA || !portB) {
+            return;
+        }
+
+        for (var currentLink of this._links) {
+            if (currentLink.portA === portA && currentLink.portB === portB) {
+                return;
+            }
+            if (currentLink.portA === portB && currentLink.portB === portA) {
+                return;
+            }
+        }
+
+        const link = new NodeLink(this, portA, nodeA, portB, nodeB);
+        this._links.push(link);
+
+        nodeA.links.push(link);
+        nodeB.links.push(link);
+    }
+
+    removeLink(link: NodeLink) {
+        let index = this._links.indexOf(link);
+
+        if (index > -1) {
+            this._links.splice(index, 1);
+        }
+
+        link.dispose();
+    }
+
+    appendBlock(block: NodeMaterialBlock) {
+        let newNode = new GraphNode(block, this.props.globalState);
+
+        newNode.appendVisual(this._graphCanvas, this);
+
+        this._nodes.push(newNode);
+
+        return newNode;
+    }
+
+    distributeGraph() {
+        this.x = 0;
+        this.y = 0;
+        this.zoom = 1;
+
+        let graph = new dagre.graphlib.Graph();
+        graph.setGraph({});
+        graph.setDefaultEdgeLabel(() => ({}));
+        graph.graph().rankdir = "LR";
+
+        // Build dagre graph
+        this._nodes.forEach(node => {
+            graph.setNode(node.id.toString(), {
+                id: node.id,
+                width: node.width,
+                height: node.height
+            });
+        });
+
+        this._nodes.forEach(node => {
+            node.block.outputs.forEach(output => {
+                if (!output.hasEndpoints) {
+                    return;
+                }
+
+                output.endpoints.forEach(endpoint => {
+                    graph.setEdge(node.id.toString(), endpoint.ownerBlock.uniqueId.toString());
+                });
+            });
+        });
+
+        // Distribute
+        dagre.layout(graph);
+
+        // Update graph
+        let dagreNodes = graph.nodes().map(node => graph.node(node));
+        dagreNodes.forEach(dagreNode => {
+            for (var node of this._nodes) {
+                if (node.id === dagreNode.id) {
+                    node.x = dagreNode.x - dagreNode.width / 2;
+                    node.y = dagreNode.y - dagreNode.height / 2;
+                    node.cleanAccumulation();
+                    return;
+                }
+            }
+        });        
+    }
+
+    componentDidMount() {
+        this._hostCanvas = this.props.globalState.hostDocument.getElementById("graph-canvas") as HTMLDivElement;
+        this._rootContainer = this.props.globalState.hostDocument.getElementById("graph-container") as HTMLDivElement;
+        this._graphCanvas = this.props.globalState.hostDocument.getElementById("graph-canvas-container") as HTMLDivElement;
+        this._svgCanvas = this.props.globalState.hostDocument.getElementById("graph-svg-container") as HTMLElement;        
+        this._selectionContainer = this.props.globalState.hostDocument.getElementById("selection-container") as HTMLDivElement;        
+        
+        this.gridSize = DataStorage.ReadNumber("GridSize", 20);
+        this.updateTransform();
+    }    
+
+    onMove(evt: React.PointerEvent) {        
+        // Selection box
+        if (this._selectionBox) {
+            const rootRect = this.canvasContainer.getBoundingClientRect();      
+
+            const localX = evt.pageX - rootRect.left;
+            const localY = evt.pageY - rootRect.top;
+
+            if (localX > this._selectionStartX) {
+                this._selectionBox.style.left = `${this._selectionStartX / this.zoom}px`;
+                this._selectionBox.style.width = `${(localX - this._selectionStartX) / this.zoom}px`;
+            } else {
+                this._selectionBox.style.left = `${localX / this.zoom}px`;
+                this._selectionBox.style.width = `${(this._selectionStartX - localX) / this.zoom}px`;
+            }
+
+            if (localY > this._selectionStartY) {                
+                this._selectionBox.style.top = `${this._selectionStartY / this.zoom}px`;
+                this._selectionBox.style.height = `${(localY - this._selectionStartY) / this.zoom}px`;
+            } else {
+                this._selectionBox.style.top = `${localY / this.zoom}px`;
+                this._selectionBox.style.height = `${(this._selectionStartY - localY) / this.zoom}px`;
+            }
+            
+            this.props.globalState.onSelectionBoxMoved.notifyObservers(this._selectionBox.getBoundingClientRect());
+
+            return;
+        }
+
+        // Candidate link
+        if (this._candidateLink) {        
+            const rootRect = this.canvasContainer.getBoundingClientRect();       
+            this._candidatePort = null; 
+            this.props.globalState.onCandidateLinkMoved.notifyObservers(new Vector2(evt.pageX, evt.pageY));
+            this._dropPointX = (evt.pageX - rootRect.left) / this.zoom;
+            this._dropPointY = (evt.pageY - rootRect.top) / this.zoom;
+
+            this._candidateLink.update(this._dropPointX, this._dropPointY, true);
+            
+            return;
+        }          
+
+        // Zoom with mouse + alt
+        if (this._altKeyIsPressed && evt.buttons === 1) {
+            if (this._oldY < 0) {
+                this._oldY = evt.pageY;
+            }
+
+            let zoomDelta = (evt.pageY - this._oldY) / 10;
+            if (Math.abs(zoomDelta) > 5) {
+                const oldZoom = this.zoom;
+                this.zoom = Math.max(Math.min(this.MaxZoom, this.zoom + zoomDelta / 100), this.MinZoom);
+
+                const boundingRect = evt.currentTarget.getBoundingClientRect();
+                const clientWidth = boundingRect.width;
+                const widthDiff = clientWidth * this.zoom - clientWidth * oldZoom;
+                const clientX = evt.clientX - boundingRect.left;
+        
+                const xFactor = (clientX - this.x) / oldZoom / clientWidth;
+        
+                this.x = this.x - widthDiff * xFactor;
+
+                this._oldY = evt.pageY;      
+            }
+            return;
+        }   
+
+        // Move canvas
+        this._rootContainer.style.cursor = "move";
+
+        if (this._mouseStartPointX === null || this._mouseStartPointY === null) {
+            return;
+        }
+        this.x += evt.clientX - this._mouseStartPointX;
+        this.y += evt.clientY - this._mouseStartPointY;
+
+        this._mouseStartPointX = evt.clientX;
+        this._mouseStartPointY = evt.clientY;
+    }
+
+    onDown(evt: React.PointerEvent<HTMLElement>) {
+        this._rootContainer.setPointerCapture(evt.pointerId);
+
+        // Selection?
+        if (evt.currentTarget === this._hostCanvas && evt.ctrlKey) {
+            this._selectionBox = this.props.globalState.hostDocument.createElement("div");
+            this._selectionBox.classList.add("selection-box");
+            this._selectionContainer.appendChild(this._selectionBox);
+
+            const rootRect = this.canvasContainer.getBoundingClientRect();      
+            this._selectionStartX = (evt.pageX - rootRect.left);
+            this._selectionStartY = (evt.pageY - rootRect.top);
+            this._selectionBox.style.left = `${this._selectionStartX / this.zoom}px`;
+            this._selectionBox.style.top = `${this._selectionStartY / this.zoom}px`;
+            this._selectionBox.style.width = "0px";
+            this._selectionBox.style.height = "0px";
+        }
+
+        // Port dragging
+        if (evt.nativeEvent.srcElement && (evt.nativeEvent.srcElement as HTMLElement).nodeName === "IMG") {
+            if (!this._candidateLink) {
+                let portElement = ((evt.nativeEvent.srcElement as HTMLElement).parentElement as any).port as NodePort;
+                this._candidateLink = new NodeLink(this, portElement, portElement.node);
+            }  
+            return;
+        }
+
+        this.props.globalState.onSelectionChangedObservable.notifyObservers(null);
+        this._mouseStartPointX = evt.clientX;
+        this._mouseStartPointY = evt.clientY;        
+    }
+
+    onUp(evt: React.PointerEvent) {
+        this._mouseStartPointX = null;
+        this._mouseStartPointY = null;
+        this._rootContainer.releasePointerCapture(evt.pointerId);   
+        this._oldY = -1; 
+
+        if (this._candidateLink) {        
+            this.processCandidatePort();          
+            this.props.globalState.onCandidateLinkMoved.notifyObservers(null);
+            this._candidateLink.dispose();
+            this._candidateLink = null;
+            this._candidatePort = null;
+        }
+
+        if (this._selectionBox) {
+           this._selectionBox.parentElement!.removeChild(this._selectionBox);
+           this._selectionBox = null;
+        }
+    }
+
+    onWheel(evt: React.WheelEvent) {
+        let delta = evt.deltaY < 0 ? 0.1 : -0.1;
+
+        let oldZoom = this.zoom;
+        this.zoom = Math.min(Math.max(this.MinZoom, this.zoom + delta * this.zoom), this.MaxZoom);
+
+        const boundingRect = evt.currentTarget.getBoundingClientRect();
+        const clientWidth = boundingRect.width;
+        const clientHeight = boundingRect.height;
+        const widthDiff = clientWidth * this.zoom - clientWidth * oldZoom;
+        const heightDiff = clientHeight * this.zoom - clientHeight * oldZoom;
+        const clientX = evt.clientX - boundingRect.left;
+        const clientY = evt.clientY - boundingRect.top;
+
+        const xFactor = (clientX - this.x) / oldZoom / clientWidth;
+        const yFactor = (clientY - this.y) / oldZoom / clientHeight;
+
+        this.x = this.x - widthDiff * xFactor;
+        this.y = this.y - heightDiff * yFactor;
+
+        evt.stopPropagation();
+    }
+
+    zoomToFit() {
+        const xFactor = this._rootContainer.clientWidth / this._rootContainer.scrollWidth;
+        const yFactor = this._rootContainer.clientHeight / this._rootContainer.scrollHeight;
+        const zoomFactor = xFactor < yFactor ? xFactor : yFactor;
+        
+        this.zoom = zoomFactor;
+        this.x = 0;
+        this.y = 0;
+    }
+
+    processCandidatePort() {
+        let pointB = this._candidateLink!.portA.connectionPoint;
+        let nodeB = this._candidateLink!.portA.node;
+        let pointA: NodeMaterialConnectionPoint;
+        let nodeA: GraphNode;
+
+        if (this._candidatePort) {
+            pointA = this._candidatePort.connectionPoint;
+            nodeA = this._candidatePort.node;
+        } else {
+            if (pointB.direction === NodeMaterialConnectionPointDirection.Output) {
+                return;
+            }
+
+            // No destination so let's spin a new input block
+            let inputBlock = new InputBlock("", undefined, this._candidateLink!.portA.connectionPoint.type);
+            pointA = inputBlock.output;
+            nodeA = this.appendBlock(inputBlock);
+            
+            nodeA.x = this._dropPointX - 200;
+            nodeA.y = this._dropPointY - 50;    
+        }
+
+        if (pointA.direction === NodeMaterialConnectionPointDirection.Input) {
+            let temp = pointB;
+            pointB = pointA;
+            pointA = temp;
+
+            let tempNode = nodeA;
+            nodeA = nodeB;
+            nodeB = tempNode;
+        }
+
+        if (pointB.connectedPoint === pointA) {
+            return;
+        }
+
+        if (pointB === pointA) {
+            return;
+        }
+
+        if (pointB.direction === pointA.direction) {
+            return;
+        }
+
+        // Check compatibility
+        let isFragmentOutput = pointB.ownerBlock.getClassName() === "FragmentOutputBlock";
+        let compatibilityState = pointA.checkCompatibilityState(pointB);
+        if (compatibilityState === NodeMaterialConnectionPointCompatibilityStates.Compatible) {
+            if (isFragmentOutput) {
+                let fragmentBlock = pointB.ownerBlock as FragmentOutputBlock;
+
+                if (pointB.name === "rgb" && fragmentBlock.rgba.isConnected) {
+                    nodeB.getLinksForConnectionPoint(fragmentBlock.rgba)[0].dispose();
+                } else if (pointB.name === "rgba" && fragmentBlock.rgb.isConnected) {
+                    nodeB.getLinksForConnectionPoint(fragmentBlock.rgb)[0].dispose();
+                }                     
+            }
+        } else {
+            let message = "";
+
+            switch (compatibilityState) {
+                case NodeMaterialConnectionPointCompatibilityStates.TypeIncompatible:
+                    message = "Cannot connect two different connection types";
+                    break;
+                case NodeMaterialConnectionPointCompatibilityStates.TargetIncompatible:
+                    message = "Source block can only work in fragment shader whereas destination block is currently aimed for the vertex shader";
+                    break;
+            }
+
+            this.props.globalState.onErrorMessageDialogRequiredObservable.notifyObservers(message);             
+            return;
+        }
+
+        if (pointB.isConnected) {
+            let links = nodeB.getLinksForConnectionPoint(pointB);
+
+            links.forEach(link => {
+                link.dispose();
+            });
+        }
+
+        pointA.connectTo(pointB);
+        this.connectPorts(pointA, pointB);
+
+        nodeB.refresh();
+
+        this.props.globalState.onRebuildRequiredObservable.notifyObservers();
+    }
+ 
+    render() {
+        return (
+            <div id="graph-canvas" 
+                onWheel={evt => this.onWheel(evt)}
+                onPointerMove={evt => this.onMove(evt)}
+                onPointerDown={evt =>  this.onDown(evt)}   
+                onPointerUp={evt =>  this.onUp(evt)} 
+            >    
+                <div id="graph-container">
+                    <div id="graph-canvas-container">
+                    </div>     
+                    <svg id="graph-svg-container">
+                    </svg>
+                    <div id="selection-container">                        
+                    </div>
+                </div>
+            </div>
+        );
+    }
+}

+ 384 - 0
nodeEditor/src/diagram/graphNode.ts

@@ -0,0 +1,384 @@
+import { NodeMaterialBlock } from 'babylonjs/Materials/Node/nodeMaterialBlock';
+import { GlobalState } from '../globalState';
+import { Nullable } from 'babylonjs/types';
+import { Observer } from 'babylonjs/Misc/observable';
+import { NodeMaterialConnectionPoint } from 'babylonjs/Materials/Node/nodeMaterialBlockConnectionPoint';
+import { GraphCanvasComponent } from './graphCanvas';
+import { PropertyLedger } from './propertyLedger';
+import * as React from 'react';
+import { GenericPropertyTabComponent } from './properties/genericNodePropertyComponent';
+import { DisplayLedger } from './displayLedger';
+import { IDisplayManager } from './display/displayManager';
+import { NodeLink } from './nodeLink';
+import { NodePort } from './nodePort';
+
+export class GraphNode {
+    private _visual: HTMLDivElement;
+    private _header: HTMLDivElement;
+    private _connections: HTMLDivElement;
+    private _inputsContainer: HTMLDivElement;
+    private _outputsContainer: HTMLDivElement;
+    private _content: HTMLDivElement;
+    private _inputPorts: NodePort[] = [];
+    private _outputPorts: NodePort[] = [];
+    private _links: NodeLink[] = [];    
+    private _x = 0;
+    private _y = 0;
+    private _gridAlignedX = 0;
+    private _gridAlignedY = 0;    
+    private _mouseStartPointX: Nullable<number> = null;
+    private _mouseStartPointY: Nullable<number> = null    
+    private _globalState: GlobalState;
+    private _onSelectionChangedObserver: Nullable<Observer<Nullable<GraphNode | NodeLink>>>;   
+    private _onSelectionBoxMovedObserver: Nullable<Observer<ClientRect | DOMRect>>;  
+    private _onUpdateRequiredObserver: Nullable<Observer<void>>;  
+    private _ownerCanvas: GraphCanvasComponent; 
+    private _isSelected: boolean;
+    private _displayManager: Nullable<IDisplayManager> = null;
+
+    public get links() {
+        return this._links;
+    }
+
+    public get gridAlignedX() {
+        return this._gridAlignedX;
+    }
+
+    public get gridAlignedY() {
+        return this._gridAlignedY;
+    }
+
+    public get x() {
+        return this._x;
+    }
+
+    public set x(value: number) {
+        if (this._x === value) {
+            return;
+        }
+        this._x = value;
+        
+        this._gridAlignedX = this._ownerCanvas.getGridPosition(value);
+        this._visual.style.left = `${this._gridAlignedX}px`;
+
+        this._refreshLinks();
+    }
+
+    public get y() {
+        return this._y;
+    }
+
+    public set y(value: number) {
+        if (this._y === value) {
+            return;
+        }
+
+        this._y = value;
+
+        this._gridAlignedY = this._ownerCanvas.getGridPosition(value);
+        this._visual.style.top = `${this._gridAlignedY}px`;
+
+        this._refreshLinks();
+    }
+
+    public get width() {
+        return this._visual.clientWidth;
+    }
+
+    public get height() {
+        return this._visual.clientHeight;
+    }
+
+    public get id() {
+        return this.block.uniqueId;
+    }
+
+    public get name() {
+        return this.block.name;
+    }
+
+    public get isSelected() {
+        return this._isSelected;
+    }
+
+    public set isSelected(value: boolean) {
+        if (this._isSelected === value) {
+            return;            
+        }
+
+        this._isSelected = value;
+
+        if (!value) {
+            this._visual.classList.remove("selected");    
+            let indexInSelection = this._ownerCanvas.selectedNodes.indexOf(this);
+
+            if (indexInSelection > -1) {
+                this._ownerCanvas.selectedNodes.splice(indexInSelection, 1);
+            }
+        } else {
+            this._globalState.onSelectionChangedObservable.notifyObservers(this);  
+        }
+    }
+
+    public constructor(public block: NodeMaterialBlock, globalState: GlobalState) {
+        this._globalState = globalState;
+
+        this._onSelectionChangedObserver = this._globalState.onSelectionChangedObservable.add(node => {
+            if (node === this) {
+                this._visual.classList.add("selected");
+            } else {
+                setTimeout(() => {
+                    if (this._ownerCanvas.selectedNodes.indexOf(this) === -1) {
+                        this._visual.classList.remove("selected");
+                    }
+                })
+            }
+        });
+
+        this._onUpdateRequiredObserver = this._globalState.onUpdateRequiredObservable.add(() => {
+            this.refresh();
+        });
+
+        this._onSelectionBoxMovedObserver = this._globalState.onSelectionBoxMoved.add(rect1 => {
+            const rect2 = this._visual.getBoundingClientRect();
+            var overlap = !(rect1.right < rect2.left || 
+                rect1.left > rect2.right || 
+                rect1.bottom < rect2.top || 
+                rect1.top > rect2.bottom);
+
+            this.isSelected = overlap;
+        });
+    }
+
+    public getPortForConnectionPoint(point: NodeMaterialConnectionPoint) {
+        for (var port of this._inputPorts) {
+            let attachedPoint = port.connectionPoint;
+
+            if (attachedPoint === point) {
+                return port;
+            }
+        }
+
+        for (var port of this._outputPorts) {
+            let attachedPoint = port.connectionPoint;
+
+            if (attachedPoint === point) {
+                return port;
+            }
+        }
+
+        return null;
+    }
+
+    public getLinksForConnectionPoint(point: NodeMaterialConnectionPoint) {
+        return this._links.filter(link => link.portA.connectionPoint === point || link.portB!.connectionPoint === point);
+    }
+
+    private _refreshLinks() {
+        for (var link of this._links) {
+            link.update();
+        }
+    }
+
+    public refresh() {
+        if (this._displayManager) {
+            this._header.innerHTML = this._displayManager.getHeaderText(this.block);
+            this._displayManager.updatePreviewContent(this.block, this._content);
+            this._visual.style.background = this._displayManager.getBackgroundColor(this.block);
+        } else {
+            this._header.innerHTML = this.block.name;
+        }
+
+        for (var port of this._inputPorts) {
+            port.refresh();
+        }
+
+        for (var port of this._outputPorts) {
+            port.refresh();
+        }
+    }
+
+    private _appendConnection(connectionPoint: NodeMaterialConnectionPoint, root: HTMLDivElement, displayManager: Nullable<IDisplayManager>) {
+        let portContainer = root.ownerDocument!.createElement("div");
+        portContainer.classList.add("portLine");
+        root.appendChild(portContainer);
+
+        if (!displayManager || displayManager.shouldDisplayPortLabels(this.block)) {
+            let portLabel = root.ownerDocument!.createElement("div");
+            portLabel.classList.add("label");
+            portLabel.innerHTML = connectionPoint.name;        
+            portContainer.appendChild(portLabel);
+        }
+    
+        return new NodePort(portContainer, connectionPoint, this, this._globalState);
+    }
+
+    private _onDown(evt: PointerEvent) {
+        // Check if this is coming from the port
+        if (evt.srcElement && (evt.srcElement as HTMLElement).nodeName === "IMG") {
+            return;
+        }
+
+        const indexInSelection = this._ownerCanvas.selectedNodes.indexOf(this) ;
+        if (indexInSelection=== -1) {
+            this._globalState.onSelectionChangedObservable.notifyObservers(this);
+        } else if (evt.ctrlKey) {
+            this.isSelected = false;
+        }
+
+        evt.stopPropagation();
+
+        this._mouseStartPointX = evt.clientX;
+        this._mouseStartPointY = evt.clientY;        
+        
+        this._visual.setPointerCapture(evt.pointerId);
+    }
+
+    public cleanAccumulation() {
+        this.x = this.gridAlignedX;
+        this.y = this.gridAlignedY;
+    }
+
+    private _onUp(evt: PointerEvent) {
+        evt.stopPropagation();
+
+        for (var selectedNode of this._ownerCanvas.selectedNodes) {
+            selectedNode.cleanAccumulation();
+        }
+        
+        this._mouseStartPointX = null;
+        this._mouseStartPointY = null;
+        this._visual.releasePointerCapture(evt.pointerId);
+    }
+
+    private _onMove(evt: PointerEvent) {
+        if (this._mouseStartPointX === null || this._mouseStartPointY === null || evt.ctrlKey) {
+            return;
+        }
+
+        let newX = evt.clientX - this._mouseStartPointX;
+        let newY = evt.clientY - this._mouseStartPointY;
+
+        for (var selectedNode of this._ownerCanvas.selectedNodes) {
+            selectedNode.x += newX / this._ownerCanvas.zoom;
+            selectedNode.y += newY / this._ownerCanvas.zoom;
+        }
+
+        this._mouseStartPointX = evt.clientX;
+        this._mouseStartPointY = evt.clientY;   
+
+        evt.stopPropagation();
+    }
+
+    public renderProperties(): Nullable<JSX.Element> {
+        let control = PropertyLedger.RegisteredControls[this.block.getClassName()];
+
+        if (!control) {
+            control = GenericPropertyTabComponent;
+        }
+
+        return React.createElement(control, {
+            globalState: this._globalState,
+            block: this.block
+        });
+    }
+
+    public appendVisual(root: HTMLDivElement, owner: GraphCanvasComponent) {
+        this._ownerCanvas = owner;
+
+        // Display manager
+        let displayManagerClass = DisplayLedger.RegisteredControls[this.block.getClassName()];
+        
+
+        if (displayManagerClass) {
+            this._displayManager = new displayManagerClass();
+        }
+
+        // DOM
+        this._visual = root.ownerDocument!.createElement("div");
+        this._visual.classList.add("visual");
+
+        this._visual.addEventListener("pointerdown", evt => this._onDown(evt));
+        this._visual.addEventListener("pointerup", evt => this._onUp(evt));
+        this._visual.addEventListener("pointermove", evt => this._onMove(evt));
+
+        this._header = root.ownerDocument!.createElement("div");
+        this._header.classList.add("header");
+
+        this._visual.appendChild(this._header);      
+
+        if (this._displayManager) {
+            let additionalClass = this._displayManager.getHeaderClass(this.block);
+            if (additionalClass) {
+                this._header.classList.add(additionalClass);
+            }
+        }
+
+        this._connections = root.ownerDocument!.createElement("div");
+        this._connections.classList.add("connections");
+        this._visual.appendChild(this._connections);        
+        
+        this._inputsContainer = root.ownerDocument!.createElement("div");
+        this._inputsContainer.classList.add("inputsContainer");
+        this._connections.appendChild(this._inputsContainer);      
+
+        this._outputsContainer = root.ownerDocument!.createElement("div");
+        this._outputsContainer.classList.add("outputsContainer");
+        this._connections.appendChild(this._outputsContainer);      
+
+        this._content = root.ownerDocument!.createElement("div");
+        this._content.classList.add("content");        
+        this._visual.appendChild(this._content);     
+
+        var selectionBorder = root.ownerDocument!.createElement("div");
+        selectionBorder.classList.add("selection-border");
+        this._visual.appendChild(selectionBorder);     
+
+
+        root.appendChild(this._visual);
+
+        // Connections
+        for (var input of this.block.inputs) {
+            this._inputPorts.push(this._appendConnection(input, this._inputsContainer, this._displayManager));
+        }
+
+        for (var output of this.block.outputs) {
+            this._outputPorts.push(this._appendConnection(output, this._outputsContainer, this._displayManager));
+        }
+
+        this.refresh();
+    }
+
+    public dispose() {
+        if (this._onSelectionChangedObserver) {
+            this._globalState.onSelectionChangedObservable.remove(this._onSelectionChangedObserver);
+        }
+
+        if (this._onUpdateRequiredObserver) {
+            this._globalState.onUpdateRequiredObservable.remove(this._onUpdateRequiredObserver);
+        }
+
+        if (this._onSelectionBoxMovedObserver) {
+            this._globalState.onSelectionBoxMoved.remove(this._onSelectionBoxMovedObserver);
+        }
+
+        if (this._visual.parentElement) {
+            this._visual.parentElement.removeChild(this._visual);
+        }
+
+        for (var port of this._inputPorts) {
+            port.dispose();
+        }
+
+        for (var port of this._outputPorts) {
+            port.dispose();
+        }
+
+        let links = this._links.slice(0);
+        for (var link of links) {
+            link.dispose();           
+        }
+
+        this.block.dispose();
+    }
+}

+ 116 - 0
nodeEditor/src/diagram/nodeLink.ts

@@ -0,0 +1,116 @@
+import { GraphCanvasComponent } from './graphCanvas';
+import { GraphNode } from './graphNode';
+import { NodePort } from './nodePort';
+import { Nullable } from 'babylonjs/types';
+import { Observer } from 'babylonjs/Misc/observable';
+
+export class NodeLink {   
+    private _graphCanvas: GraphCanvasComponent;
+    private _portA: NodePort;
+    private _portB?: NodePort;
+    private _nodeA: GraphNode;
+    private _nodeB?: GraphNode;
+    private _path: SVGPathElement;
+    private _selectionPath: SVGPathElement;
+    private _onSelectionChangedObserver: Nullable<Observer<Nullable<GraphNode | NodeLink>>>;
+
+    public get portA() {
+        return this._portA;
+    }
+
+    public get portB() {
+        return this._portB;
+    }
+
+    public update(endX = 0, endY = 0, straight = false) {   
+        const rectA = this._portA.element.getBoundingClientRect();
+        const rootRect = this._graphCanvas.canvasContainer.getBoundingClientRect();
+        const zoom = this._graphCanvas.zoom;
+        const xOffset = rootRect.left;
+        const yOffset = rootRect.top;
+       
+        var startX = (rectA.left - xOffset + 0.5 * rectA.width) / zoom;
+        var startY = (rectA.top - yOffset + 0.5 * rectA.height) / zoom;  
+
+        if (this._portB) {
+            const rectB = this._portB.element.getBoundingClientRect();
+            endX = (rectB.left - xOffset + 0.5 * rectB.width) / zoom;
+            endY = (rectB.top - yOffset + 0.5 * rectB.height) / zoom;  
+        }
+    
+        if (straight) {
+            this._path.setAttribute("d",  `M${startX},${startY} L${endX},${endY}`);      
+            this._path.setAttribute("stroke-dasharray", "10, 10");
+            this._path.setAttribute("stroke-linecap", "round");
+        } else {
+            this._path.setAttribute("d",  `M${startX},${startY} C${startX + 80},${startY} ${endX - 80},${endY} ${endX},${endY}`);        
+            this._selectionPath.setAttribute("d",  `M${startX},${startY} C${startX + 80},${startY} ${endX - 80},${endY} ${endX},${endY}`);
+        }
+        this._path.setAttribute("stroke", this._portA.element.style.backgroundColor!);
+    }
+
+    public constructor(graphCanvas: GraphCanvasComponent, portA: NodePort, nodeA: GraphNode, portB?: NodePort, nodeB?: GraphNode) {
+        this._portA = portA;
+        this._portB = portB;
+        this._nodeA = nodeA;
+        this._nodeB = nodeB;
+        this._graphCanvas = graphCanvas;
+
+        var document = portA.element.ownerDocument!;
+        var svg = graphCanvas.svgCanvas;
+
+        // Create path
+        this._path = document.createElementNS('http://www.w3.org/2000/svg',"path"); 
+        this._path.setAttribute("fill", "none");
+        this._path.classList.add("link");
+
+        svg.appendChild(this._path);
+
+        this._selectionPath = document.createElementNS('http://www.w3.org/2000/svg',"path"); 
+        this._selectionPath.setAttribute("fill", "none");
+        this._selectionPath.classList.add("selection-link");
+
+        svg.appendChild(this._selectionPath);
+
+        this._selectionPath.onmousedown = ()=> this.onClick();
+
+        if (this._portB) {
+            // Update
+            this.update();
+        }
+
+        this._onSelectionChangedObserver = this._graphCanvas.globalState.onSelectionChangedObservable.add(selection => {
+            if (selection === this) {                
+                this._path.classList.add("selected");
+                this._selectionPath.classList.add("selected");
+            } else {                
+                this._path.classList.remove("selected");
+                this._selectionPath.classList.remove("selected");
+            }
+        });
+    }
+
+    onClick() {
+        this._graphCanvas.globalState.onSelectionChangedObservable.notifyObservers(this);
+    }
+
+    public dispose() {
+        this._graphCanvas.globalState.onSelectionChangedObservable.remove(this._onSelectionChangedObserver);
+
+        if (this._path.parentElement) {
+            this._path.parentElement.removeChild(this._path);
+        }
+
+        if (this._selectionPath.parentElement) {
+            this._selectionPath.parentElement.removeChild(this._selectionPath);
+        }        
+
+        if (this._nodeB) {
+            this._nodeA.links.splice(this._nodeA.links.indexOf(this), 1);
+            this._nodeB.links.splice(this._nodeB.links.indexOf(this), 1);
+            this._graphCanvas.links.splice(this._graphCanvas.links.indexOf(this), 1);
+
+            this._portA.connectionPoint.disconnectFrom(this._portB!.connectionPoint);
+        }
+    }   
+}

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 77 - 0
nodeEditor/src/diagram/nodePort.ts


+ 28 - 0
nodeEditor/src/diagram/properties/PerturbNormalNodePropertyComponent.tsx

@@ -0,0 +1,28 @@
+
+import * as React from "react";
+import { LineContainerComponent } from '../../sharedComponents/lineContainerComponent';
+import { IPropertyComponentProps } from './propertyComponentProps';
+import { CheckBoxLineComponent } from '../../sharedComponents/checkBoxLineComponent';
+import { TextInputLineComponent } from '../../sharedComponents/textInputLineComponent';
+import { TextLineComponent } from '../../sharedComponents/textLineComponent';
+
+export class PerturbNormalPropertyTabComponent extends React.Component<IPropertyComponentProps> {
+    constructor(props: IPropertyComponentProps) {
+        super(props)
+    }
+
+    render() {
+        return (
+            <>
+                <LineContainerComponent title="GENERAL">
+                    <TextInputLineComponent globalState={this.props.globalState} label="Name" propertyName="name" target={this.props.block} onChange={() => this.props.globalState.onUpdateRequiredObservable.notifyObservers()} />
+                    <TextLineComponent label="Type" value={this.props.block.getClassName()} />
+                </LineContainerComponent>
+                <LineContainerComponent title="PROPERTIES">
+                    <CheckBoxLineComponent label="Invert X axis" target={this.props.block} propertyName="invertX" onValueChanged={() => this.props.globalState.onRebuildRequiredObservable.notifyObservers()} />
+                    <CheckBoxLineComponent label="Invert Y axis" target={this.props.block} propertyName="invertY" onValueChanged={() => this.props.globalState.onRebuildRequiredObservable.notifyObservers()}/>                    
+                </LineContainerComponent>        
+            </>
+        );
+    }
+}

+ 9 - 14
nodeEditor/src/components/diagram/clamp/clampNodePropertyComponent.tsx

@@ -1,20 +1,15 @@
 
 import * as React from "react";
-import { GlobalState } from '../../../globalState';
-import { LineContainerComponent } from '../../../sharedComponents/lineContainerComponent';
-import { TextInputLineComponent } from '../../../sharedComponents/textInputLineComponent';
-import { TextLineComponent } from '../../../sharedComponents/textLineComponent';
-import { ClampNodeModel } from './clampNodeModel';
-import { FloatLineComponent } from '../../../sharedComponents/floatLineComponent';
+import { LineContainerComponent } from '../../sharedComponents/lineContainerComponent';
+import { TextInputLineComponent } from '../../sharedComponents/textInputLineComponent';
+import { TextLineComponent } from '../../sharedComponents/textLineComponent';
+import { FloatLineComponent } from '../../sharedComponents/floatLineComponent';
+import { IPropertyComponentProps } from './propertyComponentProps';
+import { ClampBlock } from 'babylonjs/Materials/Node/Blocks/clampBlock';
 
-interface IClampPropertyTabComponentProps {
-    globalState: GlobalState;
-    remapNode: ClampNodeModel;
-}
+export class ClampPropertyTabComponent extends React.Component<IPropertyComponentProps> {
 
-export class ClampPropertyTabComponentProps extends React.Component<IClampPropertyTabComponentProps> {
-
-    constructor(props: IClampPropertyTabComponentProps) {
+    constructor(props: IPropertyComponentProps) {
         super(props)
     }
 
@@ -24,7 +19,7 @@ export class ClampPropertyTabComponentProps extends React.Component<IClampProper
     }
 
     render() {
-        let clampBlock = this.props.remapNode.clampBlock;
+        let clampBlock = this.props.block as ClampBlock
       
         return (
             <div>

+ 23 - 0
nodeEditor/src/diagram/properties/genericNodePropertyComponent.tsx

@@ -0,0 +1,23 @@
+
+import * as React from "react";
+import { LineContainerComponent } from '../../sharedComponents/lineContainerComponent';
+import { IPropertyComponentProps } from './propertyComponentProps';
+import { TextInputLineComponent } from '../../sharedComponents/textInputLineComponent';
+import { TextLineComponent } from '../../sharedComponents/textLineComponent';
+
+export class GenericPropertyTabComponent extends React.Component<IPropertyComponentProps> {
+    constructor(props: IPropertyComponentProps) {
+        super(props)
+    }
+
+    render() {
+        return (
+            <>
+                <LineContainerComponent title="GENERAL">
+                    <TextInputLineComponent globalState={this.props.globalState} label="Name" propertyName="name" target={this.props.block} onChange={() => this.props.globalState.onUpdateRequiredObservable.notifyObservers()} />
+                    <TextLineComponent label="Type" value={this.props.block.getClassName()} />
+                </LineContainerComponent>         
+            </>
+        );
+    }
+}

+ 11 - 17
nodeEditor/src/components/diagram/gradient/gradientNodePropertyComponent.tsx

@@ -1,23 +1,17 @@
 
 import * as React from "react";
-import { GlobalState } from '../../../globalState';
-import { LineContainerComponent } from '../../../sharedComponents/lineContainerComponent';
-import { TextInputLineComponent } from '../../../sharedComponents/textInputLineComponent';
-import { TextLineComponent } from '../../../sharedComponents/textLineComponent';
-import { GradientNodeModel } from './gradientNodeModel';
-import { GradientBlockColorStep } from 'babylonjs/Materials/Node/Blocks/gradientBlock';
+import { LineContainerComponent } from '../../sharedComponents/lineContainerComponent';
+import { TextInputLineComponent } from '../../sharedComponents/textInputLineComponent';
+import { TextLineComponent } from '../../sharedComponents/textLineComponent';
+import { GradientBlockColorStep, GradientBlock } from 'babylonjs/Materials/Node/Blocks/gradientBlock';
 import { GradientStepComponent } from './gradientStepComponent';
-import { ButtonLineComponent } from '../../../sharedComponents/buttonLineComponent';
+import { ButtonLineComponent } from '../../sharedComponents/buttonLineComponent';
 import { Color3 } from 'babylonjs/Maths/math.color';
+import { IPropertyComponentProps } from './propertyComponentProps';
 
-interface IGradientPropertyTabComponentProps {
-    globalState: GlobalState;
-    gradientNode: GradientNodeModel;
-}
+export class GradientPropertyTabComponent extends React.Component<IPropertyComponentProps> {
 
-export class GradientPropertyTabComponentProps extends React.Component<IGradientPropertyTabComponentProps> {
-
-    constructor(props: IGradientPropertyTabComponentProps) {
+    constructor(props: IPropertyComponentProps) {
         super(props)
     }
 
@@ -27,7 +21,7 @@ export class GradientPropertyTabComponentProps extends React.Component<IGradient
     }
 
     deleteStep(step: GradientBlockColorStep) {
-        let gradientBlock = this.props.gradientNode.gradientBlock;
+        let gradientBlock = this.props.block as GradientBlock;
 
         let index = gradientBlock.colorSteps.indexOf(step);
 
@@ -39,7 +33,7 @@ export class GradientPropertyTabComponentProps extends React.Component<IGradient
     }
 
     addNewStep() {
-        let gradientBlock = this.props.gradientNode.gradientBlock;
+        let gradientBlock = this.props.block as GradientBlock;
 
         let newStep = new GradientBlockColorStep(1.0, Color3.White());
         gradientBlock.colorSteps.push(newStep);
@@ -49,7 +43,7 @@ export class GradientPropertyTabComponentProps extends React.Component<IGradient
     }
 
     render() {
-        let gradientBlock = this.props.gradientNode.gradientBlock;
+        let gradientBlock = this.props.block as GradientBlock;
       
         return (
             <div>

+ 1 - 1
nodeEditor/src/components/diagram/gradient/gradientStepComponent.tsx

@@ -1,5 +1,5 @@
 import * as React from 'react';
-import { GlobalState } from '../../../globalState';
+import { GlobalState } from '../../globalState';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { faTrash } from '@fortawesome/free-solid-svg-icons';
 import { Color3 } from 'babylonjs/Maths/math.color';

+ 29 - 27
nodeEditor/src/components/diagram/input/inputNodePropertyComponent.tsx

@@ -1,37 +1,32 @@
 
 import * as React from "react";
-import { Vector2PropertyTabComponent } from '../../propertyTab/properties/vector2PropertyTabComponent';
-import { Vector3PropertyTabComponent } from '../../propertyTab/properties/vector3PropertyTabComponent';
-import { GlobalState } from '../../../globalState';
-import { InputNodeModel } from './inputNodeModel';
+import { GlobalState } from '../../globalState';
+import { FloatLineComponent } from '../../sharedComponents/floatLineComponent';
+import { FloatPropertyTabComponent } from '../../components/propertyTab/properties/floatPropertyTabComponent';
+import { SliderLineComponent } from '../../sharedComponents/sliderLineComponent';
+import { Vector2PropertyTabComponent } from '../../components/propertyTab/properties/vector2PropertyTabComponent';
+import { Color3PropertyTabComponent } from '../../components/propertyTab/properties/color3PropertyTabComponent';
+import { Vector3PropertyTabComponent } from '../../components/propertyTab/properties/vector3PropertyTabComponent';
+import { Vector4PropertyTabComponent } from '../../components/propertyTab/properties/vector4PropertyTabComponent';
+import { MatrixPropertyTabComponent } from '../../components/propertyTab/properties/matrixPropertyTabComponent';
+import { LineContainerComponent } from '../../sharedComponents/lineContainerComponent';
+import { TextInputLineComponent } from '../../sharedComponents/textInputLineComponent';
+import { TextLineComponent } from '../../sharedComponents/textLineComponent';
+import { OptionsLineComponent } from '../../sharedComponents/optionsLineComponent';
 import { NodeMaterialBlockConnectionPointTypes } from 'babylonjs/Materials/Node/Enums/nodeMaterialBlockConnectionPointTypes';
-import { OptionsLineComponent } from '../../../sharedComponents/optionsLineComponent';
 import { NodeMaterialSystemValues } from 'babylonjs/Materials/Node/Enums/nodeMaterialSystemValues';
-import { TextLineComponent } from '../../../sharedComponents/textLineComponent';
-import { Color3PropertyTabComponent } from '../../propertyTab/properties/color3PropertyTabComponent';
-import { FloatPropertyTabComponent } from '../../propertyTab/properties/floatPropertyTabComponent';
-import { LineContainerComponent } from '../../../sharedComponents/lineContainerComponent';
-import { StringTools } from '../../../stringTools';
 import { AnimatedInputBlockTypes } from 'babylonjs/Materials/Node/Blocks/Input/animatedInputBlockTypes';
-import { TextInputLineComponent } from '../../../sharedComponents/textInputLineComponent';
-import { Vector4PropertyTabComponent } from '../../propertyTab/properties/vector4PropertyTabComponent';
-import { MatrixPropertyTabComponent } from '../../propertyTab/properties/matrixPropertyTabComponent';
-import { FloatLineComponent } from '../../../sharedComponents/floatLineComponent';
-import { SliderLineComponent } from '../../../sharedComponents/sliderLineComponent';
+import { StringTools } from '../../stringTools';
+import { IPropertyComponentProps } from './propertyComponentProps';
+import { InputBlock } from 'babylonjs/Materials/Node/Blocks/Input/inputBlock';
 
-interface IInputPropertyTabComponentProps {
-    globalState: GlobalState;
-    inputNode: InputNodeModel;
-}
-
-export class InputPropertyTabComponentProps extends React.Component<IInputPropertyTabComponentProps> {
-
-    constructor(props: IInputPropertyTabComponentProps) {
+export class InputPropertyTabComponent extends React.Component<IPropertyComponentProps> {
+    constructor(props: IPropertyComponentProps) {
         super(props)
     }
 
     renderValue(globalState: GlobalState) {
-        let inputBlock = this.props.inputNode.inputBlock;
+        let inputBlock = this.props.block as InputBlock;
         switch (inputBlock.type) {
             case NodeMaterialBlockConnectionPointTypes.Float: {
                 let cantDisplaySlider = (isNaN(inputBlock.min) || isNaN(inputBlock.max) || inputBlock.min === inputBlock.max);
@@ -84,12 +79,12 @@ export class InputPropertyTabComponentProps extends React.Component<IInputProper
     }
 
     setDefaultValue() {
-        let inputBlock = this.props.inputNode.inputBlock;
+        let inputBlock = this.props.block as InputBlock;
         inputBlock.setDefaultValue();
     }
 
     render() {
-        let inputBlock = this.props.inputNode.inputBlock;
+        let inputBlock = this.props.block as InputBlock;
 
         var systemValuesOptions: {label: string, value: NodeMaterialSystemValues}[] = [];
         var attributeOptions: {label: string, value: string}[] = [];
@@ -239,7 +234,8 @@ export class InputPropertyTabComponentProps extends React.Component<IInputProper
                                     inputBlock.setAsSystemValue(systemValuesOptions[0].value);
                                     break;
                             }
-                            this.forceUpdate();
+                            this.forceUpdate();                            
+                            this.props.globalState.onUpdateRequiredObservable.notifyObservers();
                             this.props.globalState.onRebuildRequiredObservable.notifyObservers();
                         }} />
                     {
@@ -247,6 +243,8 @@ export class InputPropertyTabComponentProps extends React.Component<IInputProper
                         <OptionsLineComponent label="Attribute" valuesAreStrings={true} options={attributeOptions} target={inputBlock} propertyName="name" onSelect={(value: any) => {
                             inputBlock.setAsAttribute(value);
                             this.forceUpdate();
+                            
+                            this.props.globalState.onUpdateRequiredObservable.notifyObservers();
                             this.props.globalState.onRebuildRequiredObservable.notifyObservers();
                         }} />
                     }
@@ -254,6 +252,8 @@ export class InputPropertyTabComponentProps extends React.Component<IInputProper
                         inputBlock.isUniform && animationOptions.length > 0 &&
                         <OptionsLineComponent label="Animation type" options={animationOptions} target={inputBlock} propertyName="animationType" onSelect={(value: any) => {
                             this.forceUpdate();
+                            
+                            this.props.globalState.onUpdateRequiredObservable.notifyObservers();
                             this.props.globalState.onRebuildRequiredObservable.notifyObservers();
                         }} />
                     }   
@@ -266,6 +266,8 @@ export class InputPropertyTabComponentProps extends React.Component<IInputProper
                         <OptionsLineComponent label="System value" options={systemValuesOptions} target={inputBlock} propertyName="systemValue" onSelect={(value: any) => {
                             inputBlock.setAsSystemValue(value);
                             this.forceUpdate();
+                            
+                            this.props.globalState.onUpdateRequiredObservable.notifyObservers();
                             this.props.globalState.onRebuildRequiredObservable.notifyObservers();
                         }} />
                     }

+ 12 - 15
nodeEditor/src/components/diagram/lightInformation/lightInformationPropertyTabComponent.tsx

@@ -1,35 +1,32 @@
 
 import * as React from "react";
-import { GlobalState } from '../../../globalState';
-import { LightInformationNodeModel } from './lightInformationNodeModel';
-import { TextLineComponent } from '../../../sharedComponents/textLineComponent';
-import { LineContainerComponent } from '../../../sharedComponents/lineContainerComponent';
-import { TextInputLineComponent } from '../../../sharedComponents/textInputLineComponent';
-import { OptionsLineComponent } from '../../../sharedComponents/optionsLineComponent';
+import { TextLineComponent } from '../../sharedComponents/textLineComponent';
+import { LineContainerComponent } from '../../sharedComponents/lineContainerComponent';
+import { TextInputLineComponent } from '../../sharedComponents/textInputLineComponent';
+import { OptionsLineComponent } from '../../sharedComponents/optionsLineComponent';
+import { IPropertyComponentProps } from './propertyComponentProps';
+import { LightInformationBlock } from 'babylonjs/Materials/Node/Blocks/Vertex/lightInformationBlock';
 
-interface ILightPropertyTabComponentProps {
-    globalState: GlobalState;
-    node: LightInformationNodeModel;
-}
-
-export class LightInformationPropertyTabComponent extends React.Component<ILightPropertyTabComponentProps> {
+export class LightInformationPropertyTabComponent extends React.Component<IPropertyComponentProps> {
 
     render() {
         let scene = this.props.globalState.nodeMaterial!.getScene();
         var lightOptions = scene.lights.map(l => {
             return { label: l.name, value: l.name }
         });
+        
+        let lightInformationBlock = this.props.block as LightInformationBlock;
 
         return (
             <div>
                 <LineContainerComponent title="GENERAL">
                     <TextLineComponent label="Type" value="LightInformationBlock" />
-                    <TextInputLineComponent globalState={this.props.globalState} label="Name" propertyName="name" target={this.props.node.block!} onChange={() => this.props.globalState.onUpdateRequiredObservable.notifyObservers()} />
+                    <TextInputLineComponent globalState={this.props.globalState} label="Name" propertyName="name" target={lightInformationBlock} onChange={() => this.props.globalState.onUpdateRequiredObservable.notifyObservers()} />
                 </LineContainerComponent>
 
                 <LineContainerComponent title="PROPERTIES">
-                    <OptionsLineComponent label="Light" noDirectUpdate={true} valuesAreStrings={true} options={lightOptions} target={this.props.node.light} propertyName="name" onSelect={(name: any) => {
-                        this.props.node.light = scene.getLightByName(name);
+                    <OptionsLineComponent label="Light" noDirectUpdate={true} valuesAreStrings={true} options={lightOptions} target={lightInformationBlock} propertyName="name" onSelect={(name: any) => {
+                        lightInformationBlock.light = scene.getLightByName(name);
                         this.forceUpdate();
                         this.props.globalState.onRebuildRequiredObservable.notifyObservers();
                     }} />

+ 13 - 16
nodeEditor/src/components/diagram/light/lightPropertyTabComponent.tsx

@@ -1,18 +1,13 @@
 
 import * as React from "react";
-import { GlobalState } from '../../../globalState';
-import { LightNodeModel } from './lightNodeModel';
-import { TextLineComponent } from '../../../sharedComponents/textLineComponent';
-import { LineContainerComponent } from '../../../sharedComponents/lineContainerComponent';
-import { TextInputLineComponent } from '../../../sharedComponents/textInputLineComponent';
-import { OptionsLineComponent } from '../../../sharedComponents/optionsLineComponent';
+import { TextLineComponent } from '../../sharedComponents/textLineComponent';
+import { LineContainerComponent } from '../../sharedComponents/lineContainerComponent';
+import { TextInputLineComponent } from '../../sharedComponents/textInputLineComponent';
+import { OptionsLineComponent } from '../../sharedComponents/optionsLineComponent';
+import { IPropertyComponentProps } from './propertyComponentProps';
+import { LightBlock } from 'babylonjs/Materials/Node/Blocks/Dual/lightBlock';
 
-interface ILightPropertyTabComponentProps {
-    globalState: GlobalState;
-    node: LightNodeModel;
-}
-
-export class LightPropertyTabComponent extends React.Component<ILightPropertyTabComponentProps> {
+export class LightPropertyTabComponent extends React.Component<IPropertyComponentProps> {
 
     render() {
         let scene = this.props.globalState.nodeMaterial!.getScene();
@@ -22,19 +17,21 @@ export class LightPropertyTabComponent extends React.Component<ILightPropertyTab
 
         lightOptions.splice(0, 0, { label: "All", value: "" })
 
+        let lightBlock = this.props.block as LightBlock;
+
         return (
             <div>
                 <LineContainerComponent title="GENERAL">
                     <TextLineComponent label="Type" value="LightBlock" />
-                    <TextInputLineComponent globalState={this.props.globalState} label="Name" propertyName="name" target={this.props.node.block!} onChange={() => this.props.globalState.onUpdateRequiredObservable.notifyObservers()} />
+                    <TextInputLineComponent globalState={this.props.globalState} label="Name" propertyName="name" target={lightBlock} onChange={() => this.props.globalState.onUpdateRequiredObservable.notifyObservers()} />
                 </LineContainerComponent>
 
                 <LineContainerComponent title="PROPERTIES">
-                    <OptionsLineComponent label="Light" defaultIfNull={0} noDirectUpdate={true} valuesAreStrings={true} options={lightOptions} target={this.props.node.light} propertyName="name" onSelect={(name: any) => {
+                    <OptionsLineComponent label="Light" defaultIfNull={0} noDirectUpdate={true} valuesAreStrings={true} options={lightOptions} target={lightBlock} propertyName="name" onSelect={(name: any) => {
                         if (name === "") {
-                            this.props.node.light = null;
+                            lightBlock.light = null;
                         } else {
-                            this.props.node.light = scene.getLightByName(name);
+                            lightBlock.light = scene.getLightByName(name);
                         }
                         this.forceUpdate();
                         this.props.globalState.onRebuildRequiredObservable.notifyObservers();

+ 7 - 0
nodeEditor/src/diagram/properties/propertyComponentProps.ts

@@ -0,0 +1,7 @@
+import { GlobalState } from "../../globalState";
+import { NodeMaterialBlock } from 'babylonjs/Materials/Node/nodeMaterialBlock';
+
+export interface IPropertyComponentProps {
+    globalState: GlobalState;
+    block: NodeMaterialBlock;
+}

+ 9 - 14
nodeEditor/src/components/diagram/remap/remapNodePropertyComponent.tsx

@@ -1,20 +1,15 @@
 
 import * as React from "react";
-import { GlobalState } from '../../../globalState';
-import { RemapNodeModel } from './remapNodeModel';
-import { LineContainerComponent } from '../../../sharedComponents/lineContainerComponent';
-import { Vector2LineComponent } from '../../../sharedComponents/vector2LineComponent';
-import { TextInputLineComponent } from '../../../sharedComponents/textInputLineComponent';
-import { TextLineComponent } from '../../../sharedComponents/textLineComponent';
+import { LineContainerComponent } from '../../sharedComponents/lineContainerComponent';
+import { Vector2LineComponent } from '../../sharedComponents/vector2LineComponent';
+import { TextInputLineComponent } from '../../sharedComponents/textInputLineComponent';
+import { TextLineComponent } from '../../sharedComponents/textLineComponent';
+import { IPropertyComponentProps } from './propertyComponentProps';
+import { RemapBlock } from 'babylonjs/Materials/Node/Blocks/remapBlock';
 
-interface IRemapPropertyTabComponentProps {
-    globalState: GlobalState;
-    remapNode: RemapNodeModel;
-}
+export class RemapPropertyTabComponent extends React.Component<IPropertyComponentProps> {
 
-export class RemapPropertyTabComponentProps extends React.Component<IRemapPropertyTabComponentProps> {
-
-    constructor(props: IRemapPropertyTabComponentProps) {
+    constructor(props: IPropertyComponentProps) {
         super(props)
     }
 
@@ -24,7 +19,7 @@ export class RemapPropertyTabComponentProps extends React.Component<IRemapProper
     }
 
     render() {
-        let remapBlock = this.props.remapNode.remapBlock;
+        let remapBlock = this.props.block as RemapBlock;
       
         return (
             <div>

+ 37 - 42
nodeEditor/src/components/diagram/texture/texturePropertyTabComponent.tsx

@@ -1,40 +1,39 @@
 
 import * as React from "react";
-import { GlobalState } from '../../../globalState';
 import { BaseTexture } from 'babylonjs/Materials/Textures/baseTexture';
-import { FileButtonLineComponent } from '../../../sharedComponents/fileButtonLineComponent';
+import { FileButtonLineComponent } from '../../sharedComponents/fileButtonLineComponent';
 import { Tools } from 'babylonjs/Misc/tools';
-import { TextureNodeModel } from './textureNodeModel';
-import { TextLineComponent } from '../../../sharedComponents/textLineComponent';
-import { LineContainerComponent } from '../../../sharedComponents/lineContainerComponent';
-import { TextInputLineComponent } from '../../../sharedComponents/textInputLineComponent';
-import { CheckBoxLineComponent } from '../../../sharedComponents/checkBoxLineComponent';
+import { TextLineComponent } from '../../sharedComponents/textLineComponent';
+import { LineContainerComponent } from '../../sharedComponents/lineContainerComponent';
+import { TextInputLineComponent } from '../../sharedComponents/textInputLineComponent';
+import { CheckBoxLineComponent } from '../../sharedComponents/checkBoxLineComponent';
 import { Texture } from 'babylonjs/Materials/Textures/texture';
-import { SliderLineComponent } from '../../../sharedComponents/sliderLineComponent';
-import { FloatLineComponent } from '../../../sharedComponents/floatLineComponent';
-import { ButtonLineComponent } from '../../../sharedComponents/buttonLineComponent';
-import { ReflectionTextureNodeModel } from '../reflectionTexture/reflectionTextureNodeModel';
+import { SliderLineComponent } from '../../sharedComponents/sliderLineComponent';
+import { FloatLineComponent } from '../../sharedComponents/floatLineComponent';
+import { ButtonLineComponent } from '../../sharedComponents/buttonLineComponent';
 import { CubeTexture } from 'babylonjs/Materials/Textures/cubeTexture';
-import { OptionsLineComponent } from '../../../sharedComponents/optionsLineComponent';
+import { OptionsLineComponent } from '../../sharedComponents/optionsLineComponent';
+import { IPropertyComponentProps } from './propertyComponentProps';
+import { ReflectionTextureBlock } from 'babylonjs/Materials/Node/Blocks/Dual/reflectionTextureBlock';
+import { TextureBlock } from 'babylonjs/Materials/Node/Blocks/Dual/textureBlock';
 
-interface ITexturePropertyTabComponentProps {
-    globalState: GlobalState;
-    node: TextureNodeModel | ReflectionTextureNodeModel;
-}
+export class TexturePropertyTabComponent extends React.Component<IPropertyComponentProps, {isEmbedded: boolean, loadAsCubeTexture: boolean}> {
 
-export class TexturePropertyTabComponent extends React.Component<ITexturePropertyTabComponentProps, {isEmbedded: boolean, loadAsCubeTexture: boolean}> {
+    get textureBlock(): TextureBlock | ReflectionTextureBlock {
+        return this.props.block as TextureBlock | ReflectionTextureBlock;
+    }
 
-    constructor(props: ITexturePropertyTabComponentProps) {
+    constructor(props: IPropertyComponentProps) {
         super(props);
 
-        let texture = this.props.node.texture as BaseTexture;
+        let texture = this.textureBlock.texture as BaseTexture;
 
         this.state = {isEmbedded: !texture || texture.name.substring(0, 4) === "data", loadAsCubeTexture: texture && texture.isCube};
     }
 
-    UNSAFE_componentWillUpdate(nextProps: ITexturePropertyTabComponentProps, nextState: {isEmbedded: boolean, loadAsCubeTexture: boolean}) {
-        if (nextProps.node !== this.props.node) {
-            let texture = nextProps.node.texture as BaseTexture;
+    UNSAFE_componentWillUpdate(nextProps: IPropertyComponentProps, nextState: {isEmbedded: boolean, loadAsCubeTexture: boolean}) {
+        if (nextProps.block !== this.props.block) {
+            let texture = (nextProps.block as TextureBlock | ReflectionTextureBlock).texture as BaseTexture;
 
             nextState.isEmbedded = !texture || texture.name.substring(0, 4) === "data";
             nextState.loadAsCubeTexture = texture && texture.isCube;
@@ -56,19 +55,19 @@ export class TexturePropertyTabComponent extends React.Component<ITexturePropert
     }
 
     removeTexture() {
-        let texture = this.props.node.texture as BaseTexture;
+        let texture = this.textureBlock.texture as BaseTexture;
 
         if (texture) {
             texture.dispose();
             (texture as any) = null;
-            this.props.node.texture = null;
+            this.textureBlock.texture = null;
         }
 
         this.updateAfterTextureLoad();
     }
 
     _prepareTexture() {
-        let texture = this.props.node.texture as BaseTexture;
+        let texture = this.textureBlock.texture as BaseTexture;
 
         if (texture && texture.isCube !== this.state.loadAsCubeTexture) {
             texture.dispose();
@@ -77,12 +76,12 @@ export class TexturePropertyTabComponent extends React.Component<ITexturePropert
 
         if (!texture) {
             if (!this.state.loadAsCubeTexture) {
-                this.props.node.texture = new Texture(null, this.props.globalState.nodeMaterial.getScene(), false, this.props.node instanceof ReflectionTextureNodeModel);
-                texture = this.props.node.texture;
+                this.textureBlock.texture = new Texture(null, this.props.globalState.nodeMaterial.getScene(), false, this.textureBlock instanceof ReflectionTextureBlock);
+                texture = this.textureBlock.texture;
                 texture.coordinatesMode = Texture.EQUIRECTANGULAR_MODE;
             } else {
-                this.props.node.texture = new CubeTexture("", this.props.globalState.nodeMaterial.getScene());
-                texture = this.props.node.texture;
+                this.textureBlock.texture = new CubeTexture("", this.props.globalState.nodeMaterial.getScene());
+                texture = this.textureBlock.texture;
                 texture.coordinatesMode = Texture.CUBIC_MODE;
             }
         }  
@@ -93,13 +92,9 @@ export class TexturePropertyTabComponent extends React.Component<ITexturePropert
 	 * @param file the file of the texture to use
 	 */
     replaceTexture(file: File) {
-        if (!this.props.node) {
-            return;
-        }
-
         this._prepareTexture();
 
-        let texture = this.props.node.texture as BaseTexture;
+        let texture = this.textureBlock.texture as BaseTexture;
         Tools.ReadFile(file, (data) => {
             var blob = new Blob([data], { type: "octet/stream" });
 
@@ -123,8 +118,8 @@ export class TexturePropertyTabComponent extends React.Component<ITexturePropert
     replaceTextureWithUrl(url: string) {
         this._prepareTexture();
 
-        let texture = this.props.node.texture as BaseTexture;       
-        if (texture.isCube || this.props.node instanceof ReflectionTextureNodeModel) {
+        let texture = this.textureBlock.texture as BaseTexture;       
+        if (texture.isCube || this.textureBlock instanceof ReflectionTextureBlock) {
             let extension: string | undefined = undefined;
             if (url.toLowerCase().indexOf(".dds") > 0) {
                 extension = ".dds";
@@ -141,14 +136,14 @@ export class TexturePropertyTabComponent extends React.Component<ITexturePropert
     render() {
         let url = "";
 
-        let texture = this.props.node.texture as BaseTexture;
+        let texture = this.textureBlock.texture as BaseTexture;
         if (texture && texture.name && texture.name.substring(0, 4) !== "data") {
             url = texture.name;
         }
 
         url = url.replace(/\?nocache=\d+/, "");
 
-        let isInReflectionMode = this.props.node instanceof ReflectionTextureNodeModel;
+        let isInReflectionMode = this.textureBlock instanceof ReflectionTextureBlock;
 
         var reflectionModeOptions: {label: string, value: number}[] = [
             {
@@ -183,11 +178,11 @@ export class TexturePropertyTabComponent extends React.Component<ITexturePropert
         return (
             <div>
                 <LineContainerComponent title="GENERAL">
-                    <TextLineComponent label="Type" value={this.props.node.block!.getClassName()} />
-                    <TextInputLineComponent globalState={this.props.globalState} label="Name" propertyName="name" target={this.props.node.block!} onChange={() => this.props.globalState.onUpdateRequiredObservable.notifyObservers()} />
+                    <TextLineComponent label="Type" value={this.props.block.getClassName()} />
+                    <TextInputLineComponent globalState={this.props.globalState} label="Name" propertyName="name" target={this.props.block} onChange={() => this.props.globalState.onUpdateRequiredObservable.notifyObservers()} />
                 </LineContainerComponent>
                 <LineContainerComponent title="PROPERTIES">
-                    <CheckBoxLineComponent label="Auto select UV" propertyName="autoSelectUV" target={this.props.node.block!} onValueChanged={() => {                        
+                    <CheckBoxLineComponent label="Auto select UV" propertyName="autoSelectUV" target={this.props.block} onValueChanged={() => {                        
                         this.props.globalState.onUpdateRequiredObservable.notifyObservers();
                     }}/> 
                     {
@@ -276,7 +271,7 @@ export class TexturePropertyTabComponent extends React.Component<ITexturePropert
                 <LineContainerComponent title="SOURCE">
                     <CheckBoxLineComponent label="Embed static texture" isSelected={() => this.state.isEmbedded} onSelect={value => {
                         this.setState({isEmbedded: value});
-                        this.props.node.texture = null;
+                        this.textureBlock.texture = null;
                         this.updateAfterTextureLoad();
                     }}/>
                     {

+ 36 - 0
nodeEditor/src/diagram/properties/transformNodePropertyComponent.tsx

@@ -0,0 +1,36 @@
+
+import * as React from "react";
+import { LineContainerComponent } from '../../sharedComponents/lineContainerComponent';
+import { IPropertyComponentProps } from './propertyComponentProps';
+import { CheckBoxLineComponent } from '../../sharedComponents/checkBoxLineComponent';
+import { TransformBlock } from 'babylonjs/Materials/Node/Blocks/transformBlock';
+import { TextInputLineComponent } from '../../sharedComponents/textInputLineComponent';
+import { TextLineComponent } from '../../sharedComponents/textLineComponent';
+
+export class TransformPropertyTabComponent extends React.Component<IPropertyComponentProps> {
+    constructor(props: IPropertyComponentProps) {
+        super(props)
+    }
+
+    render() {
+        return (
+            <>
+                <LineContainerComponent title="GENERAL">
+                    <TextInputLineComponent globalState={this.props.globalState} label="Name" propertyName="name" target={this.props.block} onChange={() => this.props.globalState.onUpdateRequiredObservable.notifyObservers()} />
+                    <TextLineComponent label="Type" value={this.props.block.getClassName()} />
+                </LineContainerComponent>
+                <LineContainerComponent title="PROPERTIES">
+                    <CheckBoxLineComponent label="Transform as direction" onSelect={value => {
+                        let transformBlock = this.props.block as TransformBlock;
+                        if (value) {
+                            transformBlock.complementW = 0;
+                        } else {
+                            transformBlock.complementW = 1;
+                        }
+                        this.props.globalState.onRebuildRequiredObservable.notifyObservers();
+                    }} isSelected={() => (this.props.block as TransformBlock).complementW === 0} />
+                </LineContainerComponent>            
+            </>
+        );
+    }
+}

+ 9 - 15
nodeEditor/src/components/diagram/trigonometry/trigonometryNodePropertyComponent.tsx

@@ -1,26 +1,20 @@
 
 import * as React from "react";
-import { GlobalState } from '../../../globalState';
-import { TrigonometryNodeModel } from './trigonometryNodeModel';
-import { TextLineComponent } from '../../../sharedComponents/textLineComponent';
-import { LineContainerComponent } from '../../../sharedComponents/lineContainerComponent';
-import { TextInputLineComponent } from '../../../sharedComponents/textInputLineComponent';
-import { OptionsLineComponent } from '../../../sharedComponents/optionsLineComponent';
-import { TrigonometryBlockOperations } from 'babylonjs/Materials/Node/Blocks/trigonometryBlock';
+import { TextLineComponent } from '../../sharedComponents/textLineComponent';
+import { LineContainerComponent } from '../../sharedComponents/lineContainerComponent';
+import { TextInputLineComponent } from '../../sharedComponents/textInputLineComponent';
+import { OptionsLineComponent } from '../../sharedComponents/optionsLineComponent';
+import { TrigonometryBlockOperations, TrigonometryBlock } from 'babylonjs/Materials/Node/Blocks/trigonometryBlock';
+import { IPropertyComponentProps } from './propertyComponentProps';
 
-interface ITrigonometryTabComponentProps {
-    globalState: GlobalState;
-    trigonometryNode: TrigonometryNodeModel;
-}
+export class TrigonometryPropertyTabComponent extends React.Component<IPropertyComponentProps> {
 
-export class TrigonometryPropertyTabComponentProps extends React.Component<ITrigonometryTabComponentProps> {
-
-    constructor(props: ITrigonometryTabComponentProps) {
+    constructor(props: IPropertyComponentProps) {
         super(props)
     }
 
     render() {
-        let trigonometryBlock = this.props.trigonometryNode.trigonometryBlock;
+        let trigonometryBlock = this.props.block as TrigonometryBlock;
         
         var operationOptions: {label: string, value: TrigonometryBlockOperations}[] = [
             {label: "Cos", value: TrigonometryBlockOperations.Cos},

+ 27 - 0
nodeEditor/src/diagram/properties/worleyNoise3DNodePropertyComponent.tsx

@@ -0,0 +1,27 @@
+
+import * as React from "react";
+import { LineContainerComponent } from '../../sharedComponents/lineContainerComponent';
+import { IPropertyComponentProps } from './propertyComponentProps';
+import { CheckBoxLineComponent } from '../../sharedComponents/checkBoxLineComponent';
+import { TextInputLineComponent } from '../../sharedComponents/textInputLineComponent';
+import { TextLineComponent } from '../../sharedComponents/textLineComponent';
+
+export class WorleyNoise3DNodePropertyComponent extends React.Component<IPropertyComponentProps> {
+    constructor(props: IPropertyComponentProps) {
+        super(props)
+    }
+
+    render() {
+        return (
+            <>
+                <LineContainerComponent title="GENERAL">
+                    <TextInputLineComponent globalState={this.props.globalState} label="Name" propertyName="name" target={this.props.block} onChange={() => this.props.globalState.onUpdateRequiredObservable.notifyObservers()} />
+                    <TextLineComponent label="Type" value={this.props.block.getClassName()} />
+                </LineContainerComponent>
+                <LineContainerComponent title="PROPERTIES">
+                    <CheckBoxLineComponent label="Use Manhattan Distance" target={this.props.block} propertyName="manhattanDistance" onValueChanged={() => this.props.globalState.onRebuildRequiredObservable.notifyObservers()} />              
+                </LineContainerComponent>        
+            </>
+        );
+    }
+}

+ 30 - 0
nodeEditor/src/diagram/propertyLedger.ts

@@ -0,0 +1,30 @@
+import { ComponentClass } from 'react';
+import { InputPropertyTabComponent } from './properties/inputNodePropertyComponent';
+import { IPropertyComponentProps } from './properties/propertyComponentProps';
+import { TransformPropertyTabComponent } from './properties/transformNodePropertyComponent';
+import { PerturbNormalPropertyTabComponent } from './properties/PerturbNormalNodePropertyComponent';
+import { WorleyNoise3DNodePropertyComponent } from './properties/worleyNoise3DNodePropertyComponent';
+import { ClampPropertyTabComponent } from './properties/clampNodePropertyComponent';
+import { GradientPropertyTabComponent } from './properties/gradientNodePropertyComponent';
+import { LightPropertyTabComponent } from './properties/lightPropertyTabComponent';
+import { LightInformationPropertyTabComponent } from './properties/lightInformationPropertyTabComponent';
+import { RemapPropertyTabComponent } from './properties/remapNodePropertyComponent';
+import { TexturePropertyTabComponent } from './properties/texturePropertyTabComponent';
+import { TrigonometryPropertyTabComponent } from './properties/trigonometryNodePropertyComponent';
+
+export class PropertyLedger {
+    public static RegisteredControls: {[key: string] : ComponentClass<IPropertyComponentProps>} = {};
+}
+
+PropertyLedger.RegisteredControls["TransformBlock"] = TransformPropertyTabComponent;
+PropertyLedger.RegisteredControls["InputBlock"] = InputPropertyTabComponent;
+PropertyLedger.RegisteredControls["PerturbNormalBlock"] = PerturbNormalPropertyTabComponent;
+PropertyLedger.RegisteredControls["WorleyNoise3DBlock"] = WorleyNoise3DNodePropertyComponent;
+PropertyLedger.RegisteredControls["ClampBlock"] = ClampPropertyTabComponent;
+PropertyLedger.RegisteredControls["GradientBlock"] = GradientPropertyTabComponent;
+PropertyLedger.RegisteredControls["LightBlock"] = LightPropertyTabComponent;
+PropertyLedger.RegisteredControls["LightInformationBlock"] = LightInformationPropertyTabComponent;
+PropertyLedger.RegisteredControls["RemapBlock"] = RemapPropertyTabComponent;
+PropertyLedger.RegisteredControls["TextureBlock"] = TexturePropertyTabComponent;
+PropertyLedger.RegisteredControls["ReflectionTextureBlock"] = TexturePropertyTabComponent;
+PropertyLedger.RegisteredControls["TrigonometryBlock"] = TrigonometryPropertyTabComponent;

+ 11 - 4
nodeEditor/src/globalState.ts

@@ -1,19 +1,21 @@
 import { NodeMaterial } from "babylonjs/Materials/Node/nodeMaterial"
 import { Nullable } from "babylonjs/types"
 import { Observable } from 'babylonjs/Misc/observable';
-import { DefaultNodeModel } from './components/diagram/defaultNodeModel';
 import { LogEntry } from './components/log/logComponent';
-import { NodeModel } from 'storm-react-diagrams';
 import { NodeMaterialBlock } from 'babylonjs/Materials/Node/nodeMaterialBlock';
 import { PreviewMeshType } from './components/preview/previewMeshType';
 import { DataStorage } from './dataStorage';
 import { Color4 } from 'babylonjs/Maths/math.color';
+import { GraphNode } from './diagram/graphNode';
+import { Vector2 } from 'babylonjs/Maths/math.vector';
+import { NodePort } from './diagram/nodePort';
+import { NodeLink } from './diagram/nodeLink';
 
 export class GlobalState {
     nodeMaterial: NodeMaterial;
     hostElement: HTMLElement;
     hostDocument: HTMLDocument;
-    onSelectionChangedObservable = new Observable<Nullable<DefaultNodeModel>>();
+    onSelectionChangedObservable = new Observable<Nullable<GraphNode | NodeLink>>();
     onRebuildRequiredObservable = new Observable<void>();
     onResetRequiredObservable = new Observable<void>();
     onUpdateRequiredObservable = new Observable<void>();
@@ -28,7 +30,11 @@ export class GlobalState {
     onBackFaceCullingChanged = new Observable<void>();
     onDepthPrePassChanged = new Observable<void>();
     onAnimationCommandActivated = new Observable<void>();
-    onGetNodeFromBlock: (block: NodeMaterialBlock) => NodeModel;
+    onCandidateLinkMoved = new Observable<Nullable<Vector2>>();   
+    onSelectionBoxMoved = new Observable<ClientRect | DOMRect>();   
+    onCandidatePortSelected = new Observable<Nullable<NodePort>>();
+    onGetNodeFromBlock: (block: NodeMaterialBlock) => GraphNode;
+    onGridSizeChanged = new Observable<void>();
     previewMeshType: PreviewMeshType;
     previewMeshFile: File;
     rotatePreview: boolean;
@@ -40,6 +46,7 @@ export class GlobalState {
     directionalLight0: boolean;
     directionalLight1: boolean;
     controlCamera: boolean;    
+    storeEditorData:(serializationObject: any) => void;
     
     customSave?: {label: string, action: (data: string) => Promise<void>};
 

+ 147 - 441
nodeEditor/src/graphEditor.tsx

@@ -1,82 +1,34 @@
-import {
-    DiagramEngine,
-    DiagramModel,
-    DiagramWidget,
-    LinkModel
-} from "storm-react-diagrams";
-
 import * as React from "react";
 import { GlobalState } from './globalState';
 
-import { GenericNodeFactory } from './components/diagram/generic/genericNodeFactory';
-import { GenericNodeModel } from './components/diagram/generic/genericNodeModel';
 import { NodeMaterialBlock } from 'babylonjs/Materials/Node/nodeMaterialBlock';
-import { NodeMaterialConnectionPoint, NodeMaterialConnectionPointCompatibilityStates } from 'babylonjs/Materials/Node/nodeMaterialBlockConnectionPoint';
 import { NodeListComponent } from './components/nodeList/nodeListComponent';
 import { PropertyTabComponent } from './components/propertyTab/propertyTabComponent';
 import { Portal } from './portal';
-import { TextureNodeFactory } from './components/diagram/texture/textureNodeFactory';
-import { DefaultNodeModel } from './components/diagram/defaultNodeModel';
-import { TextureNodeModel } from './components/diagram/texture/textureNodeModel';
-import { DefaultPortModel } from './components/diagram/port/defaultPortModel';
-import { InputNodeFactory } from './components/diagram/input/inputNodeFactory';
-import { InputNodeModel } from './components/diagram/input/inputNodeModel';
-import { TextureBlock } from 'babylonjs/Materials/Node/Blocks/Dual/textureBlock';
 import { LogComponent, LogEntry } from './components/log/logComponent';
-import { LightBlock } from 'babylonjs/Materials/Node/Blocks/Dual/lightBlock';
-import { LightNodeModel } from './components/diagram/light/lightNodeModel';
-import { LightNodeFactory } from './components/diagram/light/lightNodeFactory';
 import { DataStorage } from './dataStorage';
 import { NodeMaterialBlockConnectionPointTypes } from 'babylonjs/Materials/Node/Enums/nodeMaterialBlockConnectionPointTypes';
 import { InputBlock } from 'babylonjs/Materials/Node/Blocks/Input/inputBlock';
 import { Nullable } from 'babylonjs/types';
 import { MessageDialogComponent } from './sharedComponents/messageDialog';
 import { BlockTools } from './blockTools';
-import { AdvancedLinkFactory } from './components/diagram/link/advancedLinkFactory';
-import { RemapNodeFactory } from './components/diagram/remap/remapNodeFactory';
-import { RemapNodeModel } from './components/diagram/remap/remapNodeModel';
-import { RemapBlock } from 'babylonjs/Materials/Node/Blocks/remapBlock';
-import { GraphHelper } from './graphHelper';
 import { PreviewManager } from './components/preview/previewManager';
-import { INodeLocationInfo } from './nodeLocationInfo';
+import { IEditorData } from './nodeLocationInfo';
 import { PreviewMeshControlComponent } from './components/preview/previewMeshControlComponent';
-import { TrigonometryNodeFactory } from './components/diagram/trigonometry/trigonometryNodeFactory';
-import { TrigonometryBlock } from 'babylonjs/Materials/Node/Blocks/trigonometryBlock';
-import { TrigonometryNodeModel } from './components/diagram/trigonometry/trigonometryNodeModel';
-import { AdvancedLinkModel } from './components/diagram/link/advancedLinkModel';
-import { ClampNodeFactory } from './components/diagram/clamp/clampNodeFactory';
-import { ClampNodeModel } from './components/diagram/clamp/clampNodeModel';
-import { ClampBlock } from 'babylonjs/Materials/Node/Blocks/clampBlock';
-import { LightInformationNodeFactory } from './components/diagram/lightInformation/lightInformationNodeFactory';
-import { LightInformationNodeModel } from './components/diagram/lightInformation/lightInformationNodeModel';
-import { LightInformationBlock } from 'babylonjs/Materials/Node/Blocks/Vertex/lightInformationBlock';
 import { PreviewAreaComponent } from './components/preview/previewAreaComponent';
-import { GradientBlock } from 'babylonjs/Materials/Node/Blocks/gradientBlock';
-import { GradientNodeModel } from './components/diagram/gradient/gradientNodeModel';
-import { GradientNodeFactory } from './components/diagram/gradient/gradientNodeFactory';
-import { ReflectionTextureBlock } from 'babylonjs/Materials/Node/Blocks/Dual/reflectionTextureBlock';
-import { ReflectionTextureNodeFactory } from './components/diagram/reflectionTexture/reflectionTextureNodeFactory';
-import { ReflectionTextureNodeModel } from './components/diagram/reflectionTexture/reflectionTextureNodeModel';
 import { SerializationTools } from './serializationTools';
+import { GraphCanvasComponent } from './diagram/graphCanvas';
+import { GraphNode } from './diagram/graphNode';
 
-require("storm-react-diagrams/dist/style.min.css");
 require("./main.scss");
-require("./components/diagram/diagram.scss");
 
 interface IGraphEditorProps {
     globalState: GlobalState;
 }
 
-export class NodeCreationOptions {
-    nodeMaterialBlock: NodeMaterialBlock;
-    type?: string;
-    connection?: NodeMaterialConnectionPoint;
-}
-
 export class GraphEditor extends React.Component<IGraphEditorProps> {
     private readonly NodeWidth = 100;
-    private _engine: DiagramEngine;
-    private _model: DiagramModel;
+    private _graphCanvas: GraphCanvasComponent;
 
     private _startX: number;
     private _moveInProgress: boolean;
@@ -84,135 +36,75 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
     private _leftWidth = DataStorage.ReadNumber("LeftWidth", 200);
     private _rightWidth = DataStorage.ReadNumber("RightWidth", 300);
 
-    private _nodes = new Array<DefaultNodeModel>();
     private _blocks = new Array<NodeMaterialBlock>();
 
     private _previewManager: PreviewManager;
-    private _copiedNodes: DefaultNodeModel[] = [];
+    private _copiedNodes: GraphNode[] = [];
     private _mouseLocationX = 0;
     private _mouseLocationY = 0;
     private _onWidgetKeyUpPointer: any;
 
-    private _altKeyIsPressed = false;
-    private _oldY = -1;
-
-    /** @hidden */
-    public _toAdd: LinkModel[] | null = [];
-
     /**
      * Creates a node and recursivly creates its parent nodes from it's input
      * @param nodeMaterialBlock 
      */
-    public createNodeFromObject(options: NodeCreationOptions) {
-        if (this._blocks.indexOf(options.nodeMaterialBlock) !== -1) {        
-            return this._nodes.filter(n => n.block === options.nodeMaterialBlock)[0];
+    public createNodeFromObject(block: NodeMaterialBlock) {
+        if (this._blocks.indexOf(block) !== -1) {        
+            return this._graphCanvas.nodes.filter(n => n.block === block)[0];
         }
 
-        this._blocks.push(options.nodeMaterialBlock);
+        this._blocks.push(block);
 
-        if (this.props.globalState.nodeMaterial!.attachedBlocks.indexOf(options.nodeMaterialBlock) === -1) {
-            this.props.globalState.nodeMaterial!.attachedBlocks.push(options.nodeMaterialBlock);
+        if (this.props.globalState.nodeMaterial!.attachedBlocks.indexOf(block) === -1) {
+            this.props.globalState.nodeMaterial!.attachedBlocks.push(block);
         }
 
-        // Create new node in the graph
-        var newNode: DefaultNodeModel;
-       
-        if (options.nodeMaterialBlock instanceof TextureBlock) {
-            newNode = new TextureNodeModel();
-        } else if (options.nodeMaterialBlock instanceof ReflectionTextureBlock) {
-            newNode = new ReflectionTextureNodeModel();            
-        } else if (options.nodeMaterialBlock instanceof LightBlock) {
-            newNode = new LightNodeModel();
-        } else if (options.nodeMaterialBlock instanceof InputBlock) {
-            newNode = new InputNodeModel();     
-        } else if (options.nodeMaterialBlock instanceof TrigonometryBlock) {
-            newNode = new TrigonometryNodeModel();                    
-        } else if (options.nodeMaterialBlock instanceof RemapBlock) {
-            newNode = new RemapNodeModel();
-        } else if (options.nodeMaterialBlock instanceof ClampBlock) {
-            newNode = new ClampNodeModel();        
-        } else if (options.nodeMaterialBlock instanceof LightInformationBlock) {
-            newNode = new LightInformationNodeModel();
-        } else if (options.nodeMaterialBlock instanceof GradientBlock) {
-            newNode = new GradientNodeModel();
-        } else {
-            newNode = new GenericNodeModel();
+        if (block.isFinalMerger) {
+            this.props.globalState.nodeMaterial!.addOutputNode(block);
         }
 
-        if (options.nodeMaterialBlock.isFinalMerger) {
-            this.props.globalState.nodeMaterial!.addOutputNode(options.nodeMaterialBlock);
+        // Connections
+        if (block.inputs.length) {
+            for (var input of block.inputs) {
+                if (input.isConnected) {
+                    this.createNodeFromObject(input.sourceBlock!);
+                }
+            }
         }
 
-        this._nodes.push(newNode);
-        this._model.addAll(newNode);
+        // Graph
+        const node = this._graphCanvas.appendBlock(block);
 
-        if (options.nodeMaterialBlock) {
-            newNode.prepare(options, this._nodes, this._model, this);
+        // Links
+        if (block.inputs.length) {
+            for (var input of block.inputs) {
+                if (input.isConnected) {
+                    this._graphCanvas.connectPorts(input.connectedPoint!, input);
+                }
+            }
         }
 
-        return newNode;
+        return node;
     }
     
     addValueNode(type: string) {
         let nodeType: NodeMaterialBlockConnectionPointTypes = BlockTools.GetConnectionNodeTypeFromString(type);
 
         let newInputBlock = new InputBlock(type, undefined, nodeType);
-        var localNode = this.createNodeFromObject({ type: type, nodeMaterialBlock: newInputBlock })
-
-        return localNode;
-    }
-
-    onWidgetKeyUp(evt: any) {        
-        this._altKeyIsPressed = false;
-        this._oldY = -1;
-
-        var widget = (this.refs["test"] as DiagramWidget);
-
-        if (!widget || this.props.globalState.blockKeyboardEvents) {
-            return;
-        }
-
-        widget.onKeyUp(evt)
+        return this.createNodeFromObject(newInputBlock)
     }
 
     componentDidMount() {
         if (this.props.globalState.hostDocument) {
-            var widget = (this.refs["test"] as DiagramWidget);
-            widget.setState({ document: this.props.globalState.hostDocument })
-            this._onWidgetKeyUpPointer = this.onWidgetKeyUp.bind(this)
-            this.props.globalState.hostDocument!.addEventListener("keyup", this._onWidgetKeyUpPointer, false);
-            this.props.globalState.hostDocument!.defaultView!.addEventListener("blur", () => this._altKeyIsPressed = false, false);
-
-            let previousMouseMove = widget.onMouseMove;
-            widget.onMouseMove = (evt: any) => {
-                if (this._altKeyIsPressed && evt.buttons === 1) {
-                    if (this._oldY < 0) {
-                        this._oldY = evt.pageY;
-                    }
-
-                    let zoomDelta = (evt.pageY - this._oldY) / 10;
-                    if (Math.abs(zoomDelta) > 5) {
-                        this._engine.diagramModel.setZoomLevel(this._engine.diagramModel.getZoomLevel() + zoomDelta);
-                        this._engine.repaintCanvas();
-                        this._oldY = evt.pageY;      
-                    }
-                    return;
-                }
-                previousMouseMove(evt);
-            }
-
-            let previousMouseUp = widget.onMouseUp;
-            widget.onMouseUp = (evt: any) => {
-                this._oldY = -1;
-                previousMouseUp(evt);
-            }
-
+            this._graphCanvas = (this.refs["graphCanvas"] as GraphCanvasComponent);
             this._previewManager = new PreviewManager(this.props.globalState.hostDocument.getElementById("preview-canvas") as HTMLCanvasElement, this.props.globalState);
         }
 
         if (navigator.userAgent.indexOf("Mobile") !== -1) {
             ((this.props.globalState.hostDocument || document).querySelector(".blocker") as HTMLElement).style.visibility = "visible";
         }
+
+        this.build();
     }
 
     componentWillUnmount() {
@@ -228,21 +120,6 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
     constructor(props: IGraphEditorProps) {
         super(props);
 
-        // setup the diagram engine
-        this._engine = new DiagramEngine();
-        this._engine.installDefaultFactories()
-        this._engine.registerNodeFactory(new GenericNodeFactory(this.props.globalState));
-        this._engine.registerNodeFactory(new TextureNodeFactory(this.props.globalState));
-        this._engine.registerNodeFactory(new LightNodeFactory(this.props.globalState));
-        this._engine.registerNodeFactory(new InputNodeFactory(this.props.globalState));
-        this._engine.registerNodeFactory(new RemapNodeFactory(this.props.globalState));
-        this._engine.registerNodeFactory(new TrigonometryNodeFactory(this.props.globalState));
-        this._engine.registerNodeFactory(new ClampNodeFactory(this.props.globalState));
-        this._engine.registerNodeFactory(new LightInformationNodeFactory(this.props.globalState));
-        this._engine.registerNodeFactory(new GradientNodeFactory(this.props.globalState));
-        this._engine.registerNodeFactory(new ReflectionTextureNodeFactory(this.props.globalState));
-        this._engine.registerLinkFactory(new AdvancedLinkFactory());
-
         this.props.globalState.onRebuildRequiredObservable.add(() => {
             if (this.props.globalState.nodeMaterial) {
                 this.buildMaterial();
@@ -250,16 +127,12 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
         });
 
         this.props.globalState.onResetRequiredObservable.add(() => {
-            this.build(false);
+            this.build();
             if (this.props.globalState.nodeMaterial) {
                 this.buildMaterial();
             }
         });
 
-        this.props.globalState.onUpdateRequiredObservable.add(() => {          
-            this._engine.repaintCanvas();  
-        });
-
         this.props.globalState.onZoomToFitRequiredObservable.add(() => {
             this.zoomToFit();
         });
@@ -269,39 +142,61 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
         });
 
         this.props.globalState.onGetNodeFromBlock = (block) => {
-            return this._nodes.filter(n => n.block === block)[0];
+             return this._graphCanvas.findNodeFromBlock(block);
         }
 
         this.props.globalState.hostDocument!.addEventListener("keydown", evt => {
-            this._altKeyIsPressed = evt.altKey;
+            if (evt.keyCode === 46) { // Delete                
+                let selectedItems = this._graphCanvas.selectedNodes;
+
+                for (var selectedItem of selectedItems) {
+                    selectedItem.dispose();
+
+                    let targetBlock = selectedItem.block;
+                    this.props.globalState.nodeMaterial!.removeBlock(targetBlock);
+                    let blockIndex = this._blocks.indexOf(targetBlock);
+
+                    if (blockIndex > -1) {
+                        this._blocks.splice(blockIndex, 1);
+                    }                                  
+                }
+
+                if (this._graphCanvas.selectedLink) {
+                    this._graphCanvas.selectedLink.dispose();
+                }
+
+                this.props.globalState.onSelectionChangedObservable.notifyObservers(null);  
+                this.props.globalState.onRebuildRequiredObservable.notifyObservers();  
+                return;
+            }
 
             if (!evt.ctrlKey) {
                 return;
             }
 
-            if (evt.key === "c") {
-                let selectedItems = this._engine.diagramModel.getSelectedItems();
+            if (evt.key === "c") { // Copy
+                let selectedItems = this._graphCanvas.selectedNodes;
                 if (!selectedItems.length) {
                     return;
                 }
     
-                let selectedItem = selectedItems[0] as DefaultNodeModel;
+                let selectedItem = selectedItems[0] as GraphNode;
     
                 if (!selectedItem.block) {
                     return;
                 }
 
-                this._copiedNodes = selectedItems.map(i => (i as DefaultNodeModel)!);
-            } else if (evt.key === "v") {
+                this._copiedNodes = selectedItems.slice(0);
+            } else if (evt.key === "v") { // Paste
                 if (!this._copiedNodes.length) {
                     return;
                 }
 
                 const rootElement = this.props.globalState.hostDocument!.querySelector(".diagram-container") as HTMLDivElement;
-                const zoomLevel = this._engine.diagramModel.getZoomLevel() / 100.0;
-                let currentX = (this._mouseLocationX - rootElement.offsetLeft - this._engine.diagramModel.getOffsetX() - this.NodeWidth) / zoomLevel;
-                let currentY = (this._mouseLocationY - rootElement.offsetTop - this._engine.diagramModel.getOffsetY() - 20) / zoomLevel;
-                let originalNode: Nullable<DefaultNodeModel> = null;
+                const zoomLevel = this._graphCanvas.zoom;
+                let currentX = (this._mouseLocationX - rootElement.offsetLeft - this._graphCanvas.x - this.NodeWidth) / zoomLevel;
+                let currentY = (this._mouseLocationY - rootElement.offsetTop - this._graphCanvas.y - 20) / zoomLevel;
+                let originalNode: Nullable<GraphNode> = null;
 
                 for (var node of this._copiedNodes) {
                     let block = node.block;
@@ -316,7 +211,7 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
                         return;
                     }
                     
-                    let newNode = this.createNodeFromObject({ nodeMaterialBlock: clone });
+                    let newNode = this.createNodeFromObject(clone);
 
                     let x = 0;
                     let y = 0;
@@ -329,33 +224,23 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
                         y = currentY;
                     }
 
-                    newNode.setPosition(x, y);
+                    newNode.x = x;
+                    newNode.y = y;
+                    newNode.cleanAccumulation();
                 }
-
-                this._engine.repaintCanvas();
             }
 
         }, false);
 
-        this.build(true);
-    }
-
-    zoomToFit(retry = 0) {
-        const xFactor = this._engine.canvas.clientWidth / this._engine.canvas.scrollWidth;
-        const yFactor = this._engine.canvas.clientHeight / this._engine.canvas.scrollHeight;
-        const zoomFactor = xFactor < yFactor ? xFactor : yFactor;
-
-        if (zoomFactor === 1) {
-            return;
+        this.props.globalState.storeEditorData = (editorData) => {
+            editorData.zoom = this._graphCanvas.zoom;
+            editorData.x = this._graphCanvas.x;
+            editorData.y = this._graphCanvas.y;
         }
+    }
 
-        this._engine.diagramModel.setZoomLevel(this._engine.diagramModel.getZoomLevel() * zoomFactor);
-        this._engine.diagramModel.setOffset(0, 0);
-        this._engine.repaintCanvas();
-        retry++;
-        if (retry < 4) {
-            setTimeout(() => this.zoomToFit(retry), 1);
-        }
+    zoomToFit() {
+        this._graphCanvas.zoomToFit();
     }
 
     buildMaterial() {
@@ -374,230 +259,66 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
         SerializationTools.UpdateLocations(this.props.globalState.nodeMaterial, this.props.globalState);
     }
 
-    applyFragmentOutputConstraints(rootInput: DefaultPortModel) {
-        var model = rootInput.parent as GenericNodeModel;
-        for (var inputKey in model.getPorts()) {                                       
-            let input = model.getPorts()[inputKey];
-
-            if (rootInput.name === "rgba" && (inputKey === "a" || inputKey === "rgb")
-                ||
-                (rootInput.name === "a" || rootInput.name === "rgb") && inputKey === "rgba") {
-                    for (var key in input.links) {
-                        let other = input.links[key];
-                        other.remove();
-                    }
-                continue;
+    build() {        
+        let editorData = this.props.globalState.nodeMaterial.editorData;
+
+        if (editorData instanceof Array) {
+            editorData = {
+                locations: editorData
             }
         }
-    }
 
-    build(needToWait = false) {        
-        let locations: Nullable<INodeLocationInfo[]> = this.props.globalState.nodeMaterial.editorData;
         // setup the diagram model
-        this._model = new DiagramModel();
-        this._nodes = [];
         this._blocks = [];
-
-        // Listen to events
-        this._model.addListener({
-            nodesUpdated: (e) => {                
-                if (!e.isCreated) {
-                    // Block is deleted
-                    let targetBlock = (e.node as GenericNodeModel).block;
-
-                    if (targetBlock) {
-                        let attachedBlockIndex = this.props.globalState.nodeMaterial!.attachedBlocks.indexOf(targetBlock);
-                        if (attachedBlockIndex > -1) {
-                            this.props.globalState.nodeMaterial!.attachedBlocks.splice(attachedBlockIndex, 1);
-                        }
-
-                        if (targetBlock.isFinalMerger) {
-                            this.props.globalState.nodeMaterial!.removeOutputNode(targetBlock);
-                        }
-                        let blockIndex = this._blocks.indexOf(targetBlock);
-
-                        if (blockIndex > -1) {
-                            this._blocks.splice(blockIndex, 1);
-                        }
-                    }                  
-
-                    this.props.globalState.onSelectionChangedObservable.notifyObservers(null);
-                } else {
-
-                }
-            },
-            linksUpdated: (e) => {
-                if (!e.isCreated) {
-                    // Link is deleted
-                    this.props.globalState.onSelectionChangedObservable.notifyObservers(null);
-                    let sourcePort = e.link.sourcePort as DefaultPortModel;
-
-                    var link = DefaultPortModel.SortInputOutput(sourcePort, e.link.targetPort as DefaultPortModel);
-                    if (link) {
-                        if (link.input.connection && link.output.connection) {
-                            if (link.input.connection.connectedPoint) {
-                                // Disconnect standard nodes
-                                link.output.connection.disconnectFrom(link.input.connection);
-                                link.input.syncWithNodeMaterialConnectionPoint(link.input.connection);
-                                link.output.syncWithNodeMaterialConnectionPoint(link.output.connection);
-                                
-                                this.props.globalState.onRebuildRequiredObservable.notifyObservers();
-                            }
-                        }
-                    } else {
-                        if (!e.link.targetPort && e.link.sourcePort && (e.link.sourcePort as DefaultPortModel).position === "input" && !(e.link.sourcePort as DefaultPortModel).connection!.isConnected) {
-                            // Drag from input port, we are going to build an input for it                            
-                            let input = e.link.sourcePort as DefaultPortModel;
-
-                            if (input.connection!.type == NodeMaterialBlockConnectionPointTypes.AutoDetect) {
-                                return;
-                            }
-
-                            let nodeModel = this.addValueNode(BlockTools.GetStringFromConnectionNodeType(input.connection!.type));
-                            let link = nodeModel.ports.output.link(input);
-
-                            nodeModel.x = e.link.points[1].x - this.NodeWidth;
-                            nodeModel.y = e.link.points[1].y;
-
-                            setTimeout(() => {
-                                this._model.addLink(link);
-                                input.syncWithNodeMaterialConnectionPoint(input.connection!);
-                                nodeModel.ports.output.syncWithNodeMaterialConnectionPoint(nodeModel.ports.output.connection!);      
-                                
-                                let isFragmentOutput = (input.parent as DefaultNodeModel).block!.getClassName() === "FragmentOutputBlock";
-
-                                if (isFragmentOutput) {
-                                    this.applyFragmentOutputConstraints(input);
-                                }
-
-                                this.forceUpdate();
-                            }, 1);
-                           
-                            nodeModel.ports.output.connection!.connectTo(input.connection!);
-                            this.props.globalState.onRebuildRequiredObservable.notifyObservers();
-                        }
-                    }
-                    this.forceUpdate();
-                    return;
-                } else {
-                    e.link.addListener({
-                        sourcePortChanged: () => {
-                        },
-                        targetPortChanged: (evt) => {
-                            // Link is created with a target port
-                            var link = DefaultPortModel.SortInputOutput(e.link.sourcePort as DefaultPortModel, e.link.targetPort as DefaultPortModel);
-    
-                            if (link) {
-                                if (link.output.connection && link.input.connection) {
-                                    let currentBlock = link.input.connection.ownerBlock;
-                                    let isFragmentOutput = currentBlock.getClassName() === "FragmentOutputBlock";
-    
-                                    // Disconnect previous connection
-                                    for (var key in link.input.links) {
-                                        let other = link.input.links[key];
-                                        let sourcePortConnection = (other.getSourcePort() as DefaultPortModel).connection;
-                                        let targetPortConnection = (other.getTargetPort() as DefaultPortModel).connection;
-    
-                                        if (
-                                            sourcePortConnection !== (link.output as DefaultPortModel).connection && 
-                                            targetPortConnection !== (link.output as DefaultPortModel).connection
-                                        ) {
-                                            other.remove();
-                                        }
-                                    }
-    
-                                    let compatibilityState = link.output.connection.checkCompatibilityState(link.input.connection);
-                                    if (compatibilityState === NodeMaterialConnectionPointCompatibilityStates.Compatible) {
-                                        if (isFragmentOutput) {
-                                            this.applyFragmentOutputConstraints(link.input);
-                                        }
-        
-                                        link.output.connection.connectTo(link.input.connection);
-                                    } else {
-                                        (evt.entity as AdvancedLinkModel).remove();
-
-                                        let message = "";
-
-                                        switch (compatibilityState) {
-                                            case NodeMaterialConnectionPointCompatibilityStates.TypeIncompatible:
-                                                message = "Cannot connect two different connection types";
-                                                break;
-                                            case NodeMaterialConnectionPointCompatibilityStates.TargetIncompatible:
-                                                message = "Source block can only work in fragment shader whereas destination block is currently aimed for the vertex shader";
-                                                break;
-                                        }
-
-                                        this.props.globalState.onErrorMessageDialogRequiredObservable.notifyObservers(message);    
-                                    }
-    
-                                    this.forceUpdate();
-                                }
-                                if (this.props.globalState.nodeMaterial) {
-                                    this.buildMaterial();
-                                }
-                            } else {
-                                e.link.remove();
-                            }
-                        }
-                    });
-                }             
-            }
-        });
+        this._graphCanvas.reset();
 
         // Load graph of nodes from the material
         if (this.props.globalState.nodeMaterial) {
             var material = this.props.globalState.nodeMaterial;
             material._vertexOutputNodes.forEach((n: any) => {
-                this.createNodeFromObject({ nodeMaterialBlock: n });
+                this.createNodeFromObject(n);
             });
             material._fragmentOutputNodes.forEach((n: any) => {
-                this.createNodeFromObject({ nodeMaterialBlock: n });
+                this.createNodeFromObject(n);
             });
 
             material.attachedBlocks.forEach((n: any) => {
-                this.createNodeFromObject({ nodeMaterialBlock: n });
+                this.createNodeFromObject(n);
             });
-        }
-
-        // load model into engine
-        setTimeout(() => {
-            if (this._toAdd) {
-                this._model.addAll(...this._toAdd);
-            }
-            this._toAdd = null;
-            this._engine.setDiagramModel(this._model);
 
-            this.forceUpdate();
+            // Links
+            material.attachedBlocks.forEach((n: any) => {
+                if (n.inputs.length) {
+                    for (var input of n.inputs) {
+                        if (input.isConnected) {
+                            this._graphCanvas.connectPorts(input.connectedPoint!, input);
+                        }
+                    }
+                }
+            });            
+        }
 
-            this.reOrganize(locations);
-        }, needToWait ? 500 : 1);
+        this.reOrganize(editorData);
     }
 
-    reOrganize(locations: Nullable<INodeLocationInfo[]> = null) {
-        if (!locations) {
-            let nodes = GraphHelper.DistributeGraph(this._model);
-            nodes.forEach(node => {
-                for (var nodeName in this._model.nodes) {
-                    let modelNode = this._model.nodes[nodeName];
-
-                    if (modelNode.id === node.id) {
-                        modelNode.setPosition(node.x - node.width / 2, node.y - node.height / 2);
-                        return;
-                    }
-                }
-            });
+    reOrganize(editorData: Nullable<IEditorData> = null) {
+        if (!editorData || !editorData.locations) {
+            this._graphCanvas.distributeGraph();
         } else {
-            for (var location of locations) {
-                for (var node of this._nodes) {
+            this._graphCanvas.x = editorData.x || 0;
+            this._graphCanvas.y = editorData.y || 0;
+            this._graphCanvas.zoom = editorData.zoom || 1;
+            for (var location of editorData.locations) {
+                for (var node of this._graphCanvas.nodes) {
                     if (node.block && node.block.uniqueId === location.blockId) {
-                        node.setPosition(location.x, location.y);
+                        node.x = location.x;
+                        node.y = location.y;
+                        node.cleanAccumulation();
                         break;
                     }
                 }
             }
         }
-
-        this._engine.repaintCanvas();
     }
 
     onPointerDown(evt: React.PointerEvent<HTMLDivElement>) {
@@ -641,64 +362,53 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
 
     emitNewBlock(event: React.DragEvent<HTMLDivElement>) {
         var data = event.dataTransfer.getData("babylonjs-material-node") as string;
-        let nodeModel: Nullable<DefaultNodeModel> = null;
+        let newNode: GraphNode;
 
         if (data.indexOf("Block") === -1) {
-            nodeModel = this.addValueNode(data);
+            newNode = this.addValueNode(data);
         } else {
-            let block = BlockTools.GetBlockFromString(data, this.props.globalState.nodeMaterial.getScene(), this.props.globalState.nodeMaterial);   
+            let block = BlockTools.GetBlockFromString(data, this.props.globalState.nodeMaterial.getScene(), this.props.globalState.nodeMaterial)!;   
             
-            if (block) {       
-                if (block.isUnique) {
-                    const className = block.getClassName();
-                    for (var other of this._blocks) {
-                        if (other !== block && other.getClassName() === className) {
-                            this.props.globalState.onErrorMessageDialogRequiredObservable.notifyObservers(`You can only have one ${className} per graph`);                                
-                            return;
-                        }
+            if (block.isUnique) {
+                const className = block.getClassName();
+                for (var other of this._blocks) {
+                    if (other !== block && other.getClassName() === className) {
+                        this.props.globalState.onErrorMessageDialogRequiredObservable.notifyObservers(`You can only have one ${className} per graph`);                                
+                        return;
                     }
-                } 
+                }
+            } 
 
-                this._toAdd = [];
-                block.autoConfigure(this.props.globalState.nodeMaterial);       
-                nodeModel = this.createNodeFromObject({ nodeMaterialBlock: block });
-            }
+            block.autoConfigure(this.props.globalState.nodeMaterial);       
+            newNode = this.createNodeFromObject(block);
         };
 
-        if (nodeModel) {
-            const zoomLevel = this._engine.diagramModel.getZoomLevel() / 100.0;
-
-            let x = (event.clientX - event.currentTarget.offsetLeft - this._engine.diagramModel.getOffsetX() - this.NodeWidth) / zoomLevel;
-            let y = (event.clientY - event.currentTarget.offsetTop - this._engine.diagramModel.getOffsetY() - 20) / zoomLevel;
-            nodeModel.setPosition(x, y);
+        let x = event.clientX - event.currentTarget.offsetLeft - this._graphCanvas.x - this.NodeWidth;
+        let y = event.clientY - event.currentTarget.offsetTop - this._graphCanvas.y - 20;
         
-            let block = nodeModel!.block;
+        newNode.x = x / this._graphCanvas.zoom;
+        newNode.y = y / this._graphCanvas.zoom;
+        newNode.cleanAccumulation();
 
-            x -= this.NodeWidth + 150;
+        this.props.globalState.onSelectionChangedObservable.notifyObservers(newNode);
 
-            block!._inputs.forEach((connection) => {       
-                if (connection.connectedPoint) {
-                    var existingNodes = this._nodes.filter((n) => { return n.block === (connection as any)._connectedPoint._ownerBlock });
-                    let connectedNode = existingNodes[0];
+        let block = newNode.block;
 
-                    if (connectedNode.x === 0 && connectedNode.y === 0) {
-                        connectedNode.setPosition(x, y);
-                        y += 80;
-                    }
-                }
-            });
-            
-            this._engine.repaintCanvas();
+        x -= this.NodeWidth + 150;
 
-            setTimeout(() => {
-                this._model.addAll(...this._toAdd!);            
-                this._toAdd = null;  
-                this._model.clearSelection();
-                nodeModel!.setSelected(true);
+        block.inputs.forEach((connection) => {       
+            if (connection.connectedPoint) {
+                var existingNodes = this._graphCanvas.nodes.filter((n) => { return n.block === (connection as any).connectedPoint.ownerBlock });
+                let connectedNode = existingNodes[0];
 
-                this._engine.repaintCanvas();  
-            }, 150);
-        }
+                if (connectedNode.x === 0 && connectedNode.y === 0) {
+                    connectedNode.x = x / this._graphCanvas.zoom; 
+                    connectedNode.y = y / this._graphCanvas.zoom;
+                    connectedNode.cleanAccumulation();
+                    y += 80;
+                }
+            }
+        });
 
         this.forceUpdate();
     }
@@ -710,7 +420,7 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
                     {
                         gridTemplateColumns: this.buildColumnLayout()
                     }}
-                    onMouseMove={evt => {
+                    onMouseMove={evt => {                
                         this._mouseLocationX = evt.pageX;
                         this._mouseLocationY = evt.pageY;
                     }}
@@ -739,11 +449,7 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
                             event.preventDefault();
                         }}
                     >
-                        <DiagramWidget className="diagram" deleteKeys={[46]} ref={"test"} 
-                        allowLooseLinks={false}
-                        inverseZoom={true} 
-                        diagramEngine={this._engine} 
-                        maxNumberPointsPerLink={0} />
+                        <GraphCanvasComponent ref={"graphCanvas"} globalState={this.props.globalState}/>
                     </div>
 
                     <div id="rightGrab"

+ 0 - 59
nodeEditor/src/graphHelper.ts

@@ -1,59 +0,0 @@
-
-import * as dagre from "dagre";
-import { DiagramModel } from 'storm-react-diagrams/dist/@types/src/models/DiagramModel';
-
-export class GraphHelper {
-    public static DistributeGraph(model: DiagramModel) {
-        let nodes = this._MapElements(model);
-        let edges = this._MapEdges(model);
-        let graph = new dagre.graphlib.Graph();
-        graph.setGraph({});
-        graph.setDefaultEdgeLabel(() => ({}));
-        graph.graph().rankdir = "LR";
-        //add elements to dagre graph
-        nodes.forEach(node => {
-            graph.setNode(node.id, node.metadata);
-        });
-        edges.forEach(edge => {
-            if (edge.from && edge.to) {
-                graph.setEdge(edge.from.id, edge.to.id);
-            }
-        });
-        //auto-distribute
-        dagre.layout(graph);
-        return graph.nodes().map(node => graph.node(node));
-    }
-
-    private static _MapElements(model: DiagramModel) {
-        let output = [];
-
-        // dagre compatible format
-        for (var nodeName in model.nodes) {
-            let node = model.nodes[nodeName];
-            let size = {
-                width: node.width | 200,
-                height: node.height | 100
-            };
-            output.push({ id: node.id, metadata: { ...size, id: node.id } });
-        }
-
-        return output;
-    }
-
-    private static _MapEdges(model: DiagramModel) {
-        // returns links which connects nodes
-        // we check are there both from and to nodes in the model. Sometimes links can be detached
-        let output = [];
-
-        for (var linkName in model.links) {
-            let link = model.links[linkName];
-
-            output.push({
-                from: link.sourcePort!.parent,
-                to: link.targetPort!.parent
-            });
-        }
-
-        return output;
-    }
-}

+ 2 - 0
nodeEditor/src/main.scss

@@ -32,6 +32,7 @@
     height: 100%;
 
     .diagram {
+        display: none;
         width: 100%;
         height: 100%;
     }
@@ -126,6 +127,7 @@
         width: 100%;
         display: grid;
         outline: 0 !important;
+        user-select: none;
 
         #preview-canvas {
             width: 100%;

+ 7 - 0
nodeEditor/src/nodeLocationInfo.ts

@@ -2,4 +2,11 @@ export interface INodeLocationInfo {
     blockId: number;
     x: number;
     y: number;
+}
+
+export interface IEditorData {
+    locations: INodeLocationInfo[];
+    x: number;
+    y: number;
+    zoom: number;
 }

+ 13 - 5
nodeEditor/src/serializationTools.ts

@@ -6,18 +6,22 @@ import { DataStorage } from './dataStorage';
 export class SerializationTools {
 
     public static UpdateLocations(material: NodeMaterial, globalState: GlobalState) {
-        material.editorData = [];
+        material.editorData = {
+            locations: []
+        };
 
         // Store node locations
         for (var block of material.attachedBlocks) {
             let node = globalState.onGetNodeFromBlock(block);
 
-            material.editorData.push({
+            material.editorData.locations.push({
                 blockId: block.uniqueId,
                 x: node ? node.x : 0,
                 y: node ? node.y : 0
             });
         }
+
+        globalState.storeEditorData(material.editorData);
     }
 
     public static Serialize(material: NodeMaterial, globalState: GlobalState) {
@@ -30,17 +34,21 @@ export class SerializationTools {
         for (var block of material.attachedBlocks) {
             let node = globalState.onGetNodeFromBlock(block);
 
-            if (!serializationObject.locations) {
-                serializationObject.locations = [];
+            if (!serializationObject.editorData) {
+                serializationObject.editorData = {
+                    locations: []
+                };
             }
 
-            serializationObject.locations.push({
+            serializationObject.editorData.locations.push({
                 blockId: block.uniqueId,
                 x: node ? node.x : 0,
                 y: node ? node.y : 0
             });
         }
 
+        globalState.storeEditorData(serializationObject.editorData);
+
         Texture.SerializeBuffers = bufferSerializationState;
 
         return JSON.stringify(serializationObject, undefined, 2);

+ 30 - 19
nodeEditor/src/sharedComponents/textureLineComponent.tsx

@@ -15,7 +15,15 @@ interface ITextureLineComponentProps {
     hideChannelSelect?: boolean;
 }
 
-export class TextureLineComponent extends React.Component<ITextureLineComponentProps, { displayRed: boolean, displayGreen: boolean, displayBlue: boolean, displayAlpha: boolean, face: number }> {
+export interface ITextureLineComponentState {
+    displayRed: boolean, 
+    displayGreen: boolean, 
+    displayBlue: boolean, 
+    displayAlpha: boolean, 
+    face: number
+}
+
+export class TextureLineComponent extends React.Component<ITextureLineComponentProps, ITextureLineComponentState> {
     constructor(props: ITextureLineComponentProps) {
         super(props);
 
@@ -40,18 +48,20 @@ export class TextureLineComponent extends React.Component<ITextureLineComponentP
         this.updatePreview();
     }
 
-    updatePreview() {
-        var texture = this.props.texture;
+    public updatePreview() {
+        TextureLineComponent.UpdatePreview(this.refs.canvas as HTMLCanvasElement, this.props.texture, this.props.width, this.state, undefined, this.props.globalState);
+    }
+
+    public static UpdatePreview(previewCanvas: HTMLCanvasElement, texture: BaseTexture, width: number, options: ITextureLineComponentState, onReady?: ()=> void, globalState?: any) {
         if (!texture.isReady() && texture._texture) {
             texture._texture.onLoadedObservable.addOnce(() => {
-                this.updatePreview();
+                TextureLineComponent.UpdatePreview(previewCanvas, texture, width, options, onReady, globalState);
             })
         }
         var scene = texture.getScene()!;
         var engine = scene.getEngine();
         var size = texture.getSize();
         var ratio = size.width / size.height;
-        var width = this.props.width;
         var height = (width / ratio) | 1;
 
         let passPostProcess: PostProcess;
@@ -60,7 +70,7 @@ export class TextureLineComponent extends React.Component<ITextureLineComponentP
             passPostProcess = new PassPostProcess("pass", 1, null, Texture.NEAREST_SAMPLINGMODE, engine, false, Constants.TEXTURETYPE_UNSIGNED_INT);
         } else {
             var passCubePostProcess = new PassCubePostProcess("pass", 1, null, Texture.NEAREST_SAMPLINGMODE, engine, false, Constants.TEXTURETYPE_UNSIGNED_INT);
-            passCubePostProcess.face = this.state.face;
+            passCubePostProcess.face = options.face;
 
             passPostProcess = passCubePostProcess;
         }
@@ -69,15 +79,13 @@ export class TextureLineComponent extends React.Component<ITextureLineComponentP
             // Try again later
             passPostProcess.dispose();
 
-            setTimeout(() => this.updatePreview(), 250);
+            setTimeout(() => TextureLineComponent.UpdatePreview(previewCanvas, texture, width, options, onReady, globalState), 250);
 
             return;
         }
 
-        const previewCanvas = this.refs.canvas as HTMLCanvasElement;
-
-        if (this.props.globalState) {
-            this.props.globalState.blockMutationUpdates = true;
+        if (globalState) {
+            globalState.blockMutationUpdates = true;
         }
 
         let rtt = new RenderTargetTexture(
@@ -102,22 +110,22 @@ export class TextureLineComponent extends React.Component<ITextureLineComponentP
             var data = engine.readPixels(0, 0, width, height);
 
             if (!texture.isCube) {
-                if (!this.state.displayRed || !this.state.displayGreen || !this.state.displayBlue) {
+                if (!options.displayRed || !options.displayGreen || !options.displayBlue) {
                     for (var i = 0; i < width * height * 4; i += 4) {
 
-                        if (!this.state.displayRed) {
+                        if (!options.displayRed) {
                             data[i] = 0;
                         }
 
-                        if (!this.state.displayGreen) {
+                        if (!options.displayGreen) {
                             data[i + 1] = 0;
                         }
 
-                        if (!this.state.displayBlue) {
+                        if (!options.displayBlue) {
                             data[i + 2] = 0;
                         }
 
-                        if (this.state.displayAlpha) {
+                        if (options.displayAlpha) {
                             var alpha = data[i + 2];
                             data[i] = alpha;
                             data[i + 1] = alpha;
@@ -153,6 +161,10 @@ export class TextureLineComponent extends React.Component<ITextureLineComponentP
                 var castData = imageData.data;
                 castData.set(data);
                 context.putImageData(imageData, 0, 0);
+
+                if (onReady) {
+                    onReady();
+                }
             }
 
             // Unbind
@@ -163,10 +175,9 @@ export class TextureLineComponent extends React.Component<ITextureLineComponentP
         passPostProcess.dispose();
 
         previewCanvas.style.height = height + "px";
-        if (this.props.globalState) {
-            this.props.globalState.blockMutationUpdates = false;
+        if (globalState) {
+            globalState.blockMutationUpdates = false;
         }
-
     }
 
     render() {

+ 0 - 1
package.json

@@ -91,7 +91,6 @@
         "shelljs": "^0.8.3",
         "sinon": "^6.1.4",
         "split.js": "^1.5.9",
-        "storm-react-diagrams": "^5.2.1",
         "style-loader": "^0.21.0",
         "through2": "~2.0.3",
         "ts-loader": "^5.2.1",

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

@@ -61,7 +61,11 @@ export class FragmentOutputBlock extends NodeMaterialBlock {
         state.sharedData.hints.needAlphaBlending = rgba.isConnected || a.isConnected;
 
         if (rgba.connectedPoint) {
-            state.compilationString += `gl_FragColor = ${rgba.associatedVariableName};\r\n`;
+            if (a.isConnected) {
+                state.compilationString += `gl_FragColor = vec4(${rgba.associatedVariableName}.rgb, ${a.associatedVariableName});\r\n`;
+            } else {
+                state.compilationString += `gl_FragColor = ${rgba.associatedVariableName};\r\n`;
+            }
         } else if (rgb.connectedPoint) {
             if (a.connectedPoint) {
                 state.compilationString += `gl_FragColor = vec4(${rgb.associatedVariableName}, ${a.associatedVariableName});\r\n`;

+ 26 - 3
src/Materials/Node/nodeMaterial.ts

@@ -522,6 +522,21 @@ export class NodeMaterial extends PushMaterial {
     }
 
     /**
+     * Remove a block from the current node material
+     * @param block defines the block to remove
+     */
+    public removeBlock(block: NodeMaterialBlock) {
+        let attachedBlockIndex = this.attachedBlocks.indexOf(block);
+        if (attachedBlockIndex > -1) {
+            this.attachedBlocks.splice(attachedBlockIndex, 1);
+        }
+
+        if (block.isFinalMerger) {
+            this.removeOutputNode(block);
+        }
+    }
+
+    /**
      * Build the material and generates the inner effect
      * @param verbose defines if the build should log activity
      */
@@ -1216,18 +1231,26 @@ export class NodeMaterial extends PushMaterial {
         }
 
         // UI related info
-        if (source.locations) {
+        if (source.locations || source.editorData && source.editorData.locations) {
             let locations: {
                 blockId: number;
                 x: number;
                 y: number;
-            }[] = source.locations;
+            }[] = source.locations || source.editorData.locations;
 
             for (var location of locations) {
                 location.blockId = map[location.blockId].uniqueId;
             }
 
-            this.editorData = locations;
+            if (source.locations) {
+                this.editorData = {
+                    locations: locations
+                };
+            } else {
+                this.editorData = source.editorData;
+                this.editorData.locations = locations;
+            }
+
         }
     }
 

+ 3 - 3
src/Materials/Node/nodeMaterialBlock.ts

@@ -1,7 +1,7 @@
 import { NodeMaterialBlockConnectionPointTypes } from './Enums/nodeMaterialBlockConnectionPointTypes';
 import { NodeMaterialBuildState } from './nodeMaterialBuildState';
 import { Nullable } from '../../types';
-import { NodeMaterialConnectionPoint } from './nodeMaterialBlockConnectionPoint';
+import { NodeMaterialConnectionPoint, NodeMaterialConnectionPointDirection } from './nodeMaterialBlockConnectionPoint';
 import { NodeMaterialBlockTargets } from './Enums/nodeMaterialBlockTargets';
 import { Effect } from '../effect';
 import { AbstractMesh } from '../../Meshes/abstractMesh';
@@ -208,7 +208,7 @@ export class NodeMaterialBlock {
      * @returns the current block
      */
     public registerInput(name: string, type: NodeMaterialBlockConnectionPointTypes, isOptional: boolean = false, target?: NodeMaterialBlockTargets) {
-        let point = new NodeMaterialConnectionPoint(name, this);
+        let point = new NodeMaterialConnectionPoint(name, this, NodeMaterialConnectionPointDirection.Input);
         point.type = type;
         point.isOptional = isOptional;
         if (target) {
@@ -228,7 +228,7 @@ export class NodeMaterialBlock {
      * @returns the current block
      */
     public registerOutput(name: string, type: NodeMaterialBlockConnectionPointTypes, target?: NodeMaterialBlockTargets) {
-        let point = new NodeMaterialConnectionPoint(name, this);
+        let point = new NodeMaterialConnectionPoint(name, this, NodeMaterialConnectionPointDirection.Output);
         point.type = type;
         if (target) {
             point.target = target;

+ 19 - 1
src/Materials/Node/nodeMaterialBlockConnectionPoint.ts

@@ -19,6 +19,16 @@ export enum NodeMaterialConnectionPointCompatibilityStates {
 }
 
 /**
+ * Defines the direction of a connection point
+ */
+export enum NodeMaterialConnectionPointDirection {
+    /** Input */
+    Input,
+    /** Output */
+    Output
+}
+
+/**
  * Defines a connection point for a block
  */
 export class NodeMaterialConnectionPoint {
@@ -29,6 +39,7 @@ export class NodeMaterialConnectionPoint {
 
     private _endpoints = new Array<NodeMaterialConnectionPoint>();
     private _associatedVariableName: string;
+    private _direction: NodeMaterialConnectionPointDirection;
 
     /** @hidden */
     public _typeConnectionSource: Nullable<NodeMaterialConnectionPoint> = null;
@@ -41,6 +52,11 @@ export class NodeMaterialConnectionPoint {
     /** @hidden */
     public _enforceAssociatedVariableName = false;
 
+    /** Gets the direction of the point */
+    public get direction() {
+        return this._direction;
+    }
+
     /**
      * Gets or sets the additional types supported by this connection point
      */
@@ -266,10 +282,12 @@ export class NodeMaterialConnectionPoint {
      * Creates a new connection point
      * @param name defines the connection point name
      * @param ownerBlock defines the block hosting this connection point
+     * @param direction defines the direction of the connection point
      */
-    public constructor(name: string, ownerBlock: NodeMaterialBlock) {
+    public constructor(name: string, ownerBlock: NodeMaterialBlock, direction: NodeMaterialConnectionPointDirection) {
         this._ownerBlock = ownerBlock;
         this.name = name;
+        this._direction = direction;
     }
 
     /**