瀏覽代碼

Preliminary work

David Catuhe 5 年之前
父節點
當前提交
ca39b04944
共有 46 個文件被更改,包括 1263 次插入647 次删除
  1. 1 5
      nodeEditor/src/components/diagram/clamp/clampNodeModel.tsx
  2. 0 9
      nodeEditor/src/components/diagram/clamp/clampNodeWidget.tsx
  3. 13 13
      nodeEditor/src/components/diagram/defaultNodeModel.ts
  4. 0 9
      nodeEditor/src/components/diagram/generic/genericNodeWidget.tsx
  5. 1 5
      nodeEditor/src/components/diagram/gradient/gradientNodeModel.tsx
  6. 0 9
      nodeEditor/src/components/diagram/gradient/gradientNodeWidget.tsx
  7. 1 5
      nodeEditor/src/components/diagram/input/inputNodeModel.tsx
  8. 0 9
      nodeEditor/src/components/diagram/input/inputNodeWidget.tsx
  9. 1 5
      nodeEditor/src/components/diagram/light/lightNodeModel.tsx
  10. 0 9
      nodeEditor/src/components/diagram/light/lightNodeWidget.tsx
  11. 1 5
      nodeEditor/src/components/diagram/lightInformation/lightInformationNodeModel.tsx
  12. 0 9
      nodeEditor/src/components/diagram/lightInformation/lightInformationNodeWidget.tsx
  13. 1 5
      nodeEditor/src/components/diagram/reflectionTexture/reflectionTextureNodeModel.tsx
  14. 0 9
      nodeEditor/src/components/diagram/reflectionTexture/reflectionTextureNodeWidget.tsx
  15. 1 5
      nodeEditor/src/components/diagram/remap/remapNodeModel.tsx
  16. 0 9
      nodeEditor/src/components/diagram/remap/remapNodeWidget.tsx
  17. 1 5
      nodeEditor/src/components/diagram/texture/textureNodeModel.tsx
  18. 0 9
      nodeEditor/src/components/diagram/texture/textureNodeWidget.tsx
  19. 1 5
      nodeEditor/src/components/diagram/trigonometry/trigonometryNodeModel.tsx
  20. 0 9
      nodeEditor/src/components/diagram/trigonometry/trigonometryNodeWidget.tsx
  21. 5 5
      nodeEditor/src/components/propertyTab/propertyTabComponent.tsx
  22. 10 0
      nodeEditor/src/diagram/displayLedger.ts
  23. 155 0
      nodeEditor/src/diagram/graphCanvas.scss
  24. 183 0
      nodeEditor/src/diagram/graphCanvas.tsx
  25. 241 0
      nodeEditor/src/diagram/graphNode.ts
  26. 9 0
      nodeEditor/src/diagram/previews/displayManager.ts
  27. 123 0
      nodeEditor/src/diagram/previews/inputDisplayManager.ts
  28. 26 0
      nodeEditor/src/diagram/previews/outputDisplayManager.ts
  29. 28 0
      nodeEditor/src/diagram/properties/PerturbNormalNodePropertyComponent.tsx
  30. 9 14
      nodeEditor/src/components/diagram/clamp/clampNodePropertyComponent.tsx
  31. 23 0
      nodeEditor/src/diagram/properties/genericNodePropertyComponent.tsx
  32. 12 18
      nodeEditor/src/components/diagram/gradient/gradientNodePropertyComponent.tsx
  33. 21 26
      nodeEditor/src/components/diagram/input/inputNodePropertyComponent.tsx
  34. 12 15
      nodeEditor/src/components/diagram/lightInformation/lightInformationPropertyTabComponent.tsx
  35. 13 16
      nodeEditor/src/components/diagram/light/lightPropertyTabComponent.tsx
  36. 7 0
      nodeEditor/src/diagram/properties/propertyComponentProps.ts
  37. 9 14
      nodeEditor/src/components/diagram/remap/remapNodePropertyComponent.tsx
  38. 37 42
      nodeEditor/src/components/diagram/texture/texturePropertyTabComponent.tsx
  39. 36 0
      nodeEditor/src/diagram/properties/transformNodePropertyComponent.tsx
  40. 9 15
      nodeEditor/src/components/diagram/trigonometry/trigonometryNodePropertyComponent.tsx
  41. 27 0
      nodeEditor/src/diagram/properties/worleyNoise3DNodePropertyComponent.tsx
  42. 30 0
      nodeEditor/src/diagram/propertyLedger.ts
  43. 2 2
      nodeEditor/src/globalState.ts
  44. 213 273
      nodeEditor/src/graphEditor.tsx
  45. 0 59
      nodeEditor/src/graphHelper.ts
  46. 1 0
      nodeEditor/src/main.scss

+ 1 - 5
nodeEditor/src/components/diagram/clamp/clampNodeModel.tsx

@@ -1,8 +1,6 @@
-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 {
 
@@ -18,8 +16,6 @@ export class ClampNodeModel extends DefaultNodeModel {
     }
 
     renderProperties(globalState: GlobalState) {
-        return (
-            <ClampPropertyTabComponentProps globalState={globalState} remapNode={this} />
-        );
+        return null;
     }
 }

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

@@ -14,15 +14,6 @@ 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) {

+ 13 - 13
nodeEditor/src/components/diagram/defaultNodeModel.ts

@@ -45,20 +45,20 @@ export class DefaultNodeModel extends NodeModel {
 
             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];
-                }
+              //  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);
-                }
+                // let link = connectedNode.ports[connection.connectedPoint.name].link(inputPort);
+                // if (graphEditor._toAdd) {
+                //     graphEditor._toAdd.push(link);
+                // } else {
+                //     model.addAll(link);
+                // }
             }
         });
     }

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

@@ -30,15 +30,6 @@ export class GenericNodeWidget extends React.Component<GenericNodeWidgetProps, G
     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() {

+ 1 - 5
nodeEditor/src/components/diagram/gradient/gradientNodeModel.tsx

@@ -1,8 +1,6 @@
-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 {
 
@@ -18,8 +16,6 @@ export class GradientNodeModel extends DefaultNodeModel {
     }
 
     renderProperties(globalState: GlobalState) {
-        return (
-            <GradientPropertyTabComponentProps globalState={globalState} gradientNode={this} />
-        );
+        return null;
     }
 }

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

@@ -14,15 +14,6 @@ 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) {

+ 1 - 5
nodeEditor/src/components/diagram/input/inputNodeModel.tsx

@@ -1,7 +1,5 @@
-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';
 
 /**
@@ -21,8 +19,6 @@ export class InputNodeModel extends DefaultNodeModel {
     }
 
     renderProperties(globalState: GlobalState) {
-        return (
-            <InputPropertyTabComponentProps globalState={globalState} inputNode={this} />
-        );
+        return null;
     }
 }

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

@@ -28,15 +28,6 @@ export class InputNodeWidget extends React.Component<IInputNodeWidgetProps> {
     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) {

+ 1 - 5
nodeEditor/src/components/diagram/light/lightNodeModel.tsx

@@ -1,9 +1,7 @@
-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';
@@ -33,9 +31,7 @@ export class LightNodeModel extends DefaultNodeModel {
     }
 
     renderProperties(globalState: GlobalState) {
-        return (
-            <LightPropertyTabComponent globalState={globalState} node={this} />
-        );
+        return null;
     }
 
     prepare(options: NodeCreationOptions, nodes: Array<DefaultNodeModel>, model: DiagramModel, graphEditor: GraphEditor) {

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

@@ -23,15 +23,6 @@ export class LightNodeWidget extends React.Component<ILightNodeWidgetProps> {
     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() {

+ 1 - 5
nodeEditor/src/components/diagram/lightInformation/lightInformationNodeModel.tsx

@@ -1,9 +1,7 @@
-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';
@@ -30,9 +28,7 @@ export class LightInformationNodeModel extends DefaultNodeModel {
     }
 
     renderProperties(globalState: GlobalState) {
-        return (
-            <LightInformationPropertyTabComponent globalState={globalState} node={this} />
-        );
+        return null;
     }
 
     prepare(options: NodeCreationOptions, nodes: Array<DefaultNodeModel>, model: DiagramModel, graphEditor: GraphEditor) {

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

@@ -23,15 +23,6 @@ export class LightInformationNodeWidget extends React.Component<ILightInformatio
     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() {

+ 1 - 5
nodeEditor/src/components/diagram/reflectionTexture/reflectionTextureNodeModel.tsx

@@ -1,4 +1,3 @@
-import * as React from 'react';
 import { Nullable } from 'babylonjs/types';
 import { DefaultNodeModel } from '../defaultNodeModel';
 import { GlobalState } from '../../../globalState';
@@ -6,7 +5,6 @@ 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
@@ -33,9 +31,7 @@ export class ReflectionTextureNodeModel extends DefaultNodeModel {
     }
 
     renderProperties(globalState: GlobalState) {
-        return (
-            <TexturePropertyTabComponent globalState={globalState} node={this} />
-        );
+        return null;
     }
 
     prepare(options: NodeCreationOptions, nodes: Array<DefaultNodeModel>, model: DiagramModel, graphEditor: GraphEditor) {

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

@@ -24,15 +24,6 @@ export class ReflectionTextureNodeWidget extends React.Component<IReflectionText
     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() {

+ 1 - 5
nodeEditor/src/components/diagram/remap/remapNodeModel.tsx

@@ -1,7 +1,5 @@
-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';
 
 /**
@@ -21,8 +19,6 @@ export class RemapNodeModel extends DefaultNodeModel {
     }
 
     renderProperties(globalState: GlobalState) {
-        return (
-            <RemapPropertyTabComponentProps globalState={globalState} remapNode={this} />
-        );
+        return null;
     }
 }

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

@@ -26,15 +26,6 @@ export class RemapNodeWidget extends React.Component<RemapNodeWidgetProps> {
     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) {

+ 1 - 5
nodeEditor/src/components/diagram/texture/textureNodeModel.tsx

@@ -1,8 +1,6 @@
-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';
@@ -33,9 +31,7 @@ export class TextureNodeModel extends DefaultNodeModel {
     }
 
     renderProperties(globalState: GlobalState) {
-        return (
-            <TexturePropertyTabComponent globalState={globalState} node={this} />
-        );
+        return null;
     }
 
     prepare(options: NodeCreationOptions, nodes: Array<DefaultNodeModel>, model: DiagramModel, graphEditor: GraphEditor) {

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

@@ -24,15 +24,6 @@ export class TextureNodeWidget extends React.Component<ITextureNodeWidgetProps>
     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() {

+ 1 - 5
nodeEditor/src/components/diagram/trigonometry/trigonometryNodeModel.tsx

@@ -1,7 +1,5 @@
-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';
 
 /**
@@ -21,8 +19,6 @@ export class TrigonometryNodeModel extends DefaultNodeModel {
     }
 
     renderProperties(globalState: GlobalState) {
-        return (
-            <TrigonometryPropertyTabComponentProps globalState={globalState} trigonometryNode={this} />
-        );
+        return null;
     }
 }

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

@@ -24,15 +24,6 @@ export class TrigonometryNodeWidget extends React.Component<ITrigonometryNodeWid
     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() {

+ 5 - 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,14 @@ import { Tools } from 'babylonjs/Misc/tools';
 import { SerializationTools } from '../../serializationTools';
 import { CheckBoxLineComponent } from '../../sharedComponents/checkBoxLineComponent';
 import { DataStorage } from '../../dataStorage';
+import { GraphNode } from '../../diagram/graphNode';
 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 +26,8 @@ export class PropertyTabComponent extends React.Component<IPropertyTabComponentP
     }
 
     componentDidMount() {
-        this.props.globalState.onSelectionChangedObservable.add(block => {
-            this.setState({ currentNode: block });
+        this.props.globalState.onSelectionChangedObservable.add(graphNode => {
+            this.setState({ currentNode: graphNode });
         });
     }
 
@@ -63,7 +63,7 @@ export class PropertyTabComponent extends React.Component<IPropertyTabComponentP
                             NODE MATERIAL EDITOR
                         </div>
                     </div>
-                    {this.state.currentNode.renderProperties(this.props.globalState)}
+                    {this.state.currentNode.renderProperties()}
                 </div>
             );
         }

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

@@ -0,0 +1,10 @@
+import { InputDisplayManager } from './previews/inputDisplayManager';
+import { OutputDisplayManager } from './previews/outputDisplayManager';
+
+export class DisplayLedger {
+    public static RegisteredControls: {[key: string] : any} = {};
+}
+
+DisplayLedger.RegisteredControls["InputBlock"] = InputDisplayManager;
+DisplayLedger.RegisteredControls["VertexOutputBlock"] = OutputDisplayManager;
+DisplayLedger.RegisteredControls["FragmentOutputBlock"] = OutputDisplayManager;

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

@@ -0,0 +1,155 @@
+#graph-canvas {
+    position: relative;
+    width: 100%;
+    height: 100%;
+    margin: 0;
+    padding: 0;            
+    font: 14px "acumin-pro";  
+    user-select: none;
+    overflow: hidden;
+
+    #graph-canvas-container {
+        position: absolute;
+        width: 100%;
+        height: 100%;
+        left: 0;
+        top: 0;
+
+        .visual {
+            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;
+
+            &.selected {
+                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;
+                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;
+                }
+
+                .portLine {
+                    height: 24px;
+                    display: grid;                
+                    grid-template-rows: 100%;
+                }
+
+                .label {                   
+                    align-items: center;
+                }
+
+                .port {                              
+                    align-self: center;   
+                    .img {
+                        width: 100%;
+                    }            
+                }
+
+                .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;
+                }
+            }
+        }
+    }
+}

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

@@ -0,0 +1,183 @@
+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';
+
+require("./graphCanvas.scss");
+
+export interface IGraphCanvasComponentProps {
+    globalState: GlobalState
+}
+
+export class GraphCanvasComponent extends React.Component<IGraphCanvasComponentProps> {
+    private _rootCanvas: HTMLDivElement;
+    private _nodes: GraphNode[] = [];
+    private _mouseStartPointX: Nullable<number> = null;
+    private _mouseStartPointY: Nullable<number> = null
+    private _x = 0;
+    private _y = 0;
+    private _zoom = 1;
+
+    public get zoom() {
+        return this._zoom;
+    }
+
+    public set zoom(value: number) {
+        this._zoom = value;
+        this._rootCanvas.style.transform = `scale(${value})`;
+    }    
+
+    public get x() {
+        return this._x;
+    }
+
+    public set x(value: number) {
+        this._x = value;
+        this._rootCanvas.style.left = `${value}px`;
+    }
+
+    public get y() {
+        return this._y;
+    }
+
+    public set y(value: number) {
+        this._y = value;
+        this._rootCanvas.style.top = `${value}px`;
+    }
+
+    constructor(props: IGraphCanvasComponentProps) {
+        super(props);
+    }
+
+    reset() {
+        this._nodes = [];
+        this._rootCanvas.innerHTML = "";
+    }
+
+    appendBlock(block: NodeMaterialBlock) {
+        let newNode = new GraphNode(block, this.props.globalState);
+
+        newNode.appendVisual(this._rootCanvas, 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;
+                    return;
+                }
+            }
+        });        
+    }
+
+    componentDidMount() {
+        this._rootCanvas = this.props.globalState.hostDocument.getElementById("graph-canvas-container") as HTMLDivElement;
+    }    
+
+    onMove(evt: React.PointerEvent) {        
+        this._rootCanvas.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) {
+        this.props.globalState.onSelectionChangedObservable.notifyObservers(null);
+        this._mouseStartPointX = evt.clientX;
+        this._mouseStartPointY = evt.clientY;
+        this._rootCanvas.setPointerCapture(evt.pointerId);
+    }
+
+    onUp(evt: React.PointerEvent) {
+        this._mouseStartPointX = null;
+        this._mouseStartPointY = null;
+        this._rootCanvas.releasePointerCapture(evt.pointerId);
+    }
+
+    onWheel(evt: React.WheelEvent) {
+        let delta = -evt.deltaY / 50;
+
+        if (evt.ctrlKey && delta % 1 !== 0) {
+            delta /= 3;
+        } else {
+            delta /= 60;
+        }
+
+        this.zoom = Math.min(Math.max(0.4, this.zoom + delta), 4);
+
+        evt.stopPropagation();
+    }
+
+    zoomToFit() {
+        const xFactor = this._rootCanvas.clientWidth / this._rootCanvas.scrollWidth;
+        const yFactor = this._rootCanvas.clientHeight / this._rootCanvas.scrollHeight;
+        const zoomFactor = xFactor < yFactor ? xFactor : yFactor;
+        
+        this.zoom = zoomFactor;
+        this.x = 0;
+        this.y = 0;
+    }
+ 
+    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-canvas-container">
+                </div>     
+            </div>
+        );
+    }
+}

文件差異過大導致無法顯示
+ 241 - 0
nodeEditor/src/diagram/graphNode.ts


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

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

+ 123 - 0
nodeEditor/src/diagram/previews/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 setPreviewContent(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");
+    }
+}

+ 26 - 0
nodeEditor/src/diagram/previews/outputDisplayManager.ts

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

+ 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>         
+            </>
+        );
+    }
+}

+ 12 - 18
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 { GradientStepComponent } from './gradientStepComponent';
-import { ButtonLineComponent } from '../../../sharedComponents/buttonLineComponent';
+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 '../../components/diagram/gradient/gradientStepComponent';
+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>

+ 21 - 26
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}[] = [];

+ 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';
 
-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;

+ 2 - 2
nodeEditor/src/globalState.ts

@@ -1,19 +1,19 @@
 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';
 
 export class GlobalState {
     nodeMaterial: NodeMaterial;
     hostElement: HTMLElement;
     hostDocument: HTMLDocument;
-    onSelectionChangedObservable = new Observable<Nullable<DefaultNodeModel>>();
+    onSelectionChangedObservable = new Observable<Nullable<GraphNode>>();
     onRebuildRequiredObservable = new Observable<void>();
     onResetRequiredObservable = new Observable<void>();
     onUpdateRequiredObservable = new Observable<void>();

+ 213 - 273
nodeEditor/src/graphEditor.tsx

@@ -11,20 +11,15 @@ 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 { NodeMaterialConnectionPoint } 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';
@@ -34,30 +29,18 @@ 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 { 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");
@@ -75,6 +58,7 @@ export class NodeCreationOptions {
 
 export class GraphEditor extends React.Component<IGraphEditorProps> {
     private readonly NodeWidth = 100;
+    private _graphCanvas: GraphCanvasComponent;
     private _engine: DiagramEngine;
     private _model: DiagramModel;
 
@@ -84,7 +68,7 @@ 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 _nodes = new Array<GraphNode>();
     private _blocks = new Array<NodeMaterialBlock>();
 
     private _previewManager: PreviewManager;
@@ -114,52 +98,19 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
             this.props.globalState.nodeMaterial!.attachedBlocks.push(options.nodeMaterialBlock);
         }
 
-        // 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 (options.nodeMaterialBlock.isFinalMerger) {
             this.props.globalState.nodeMaterial!.addOutputNode(options.nodeMaterialBlock);
         }
 
-        this._nodes.push(newNode);
-        this._model.addAll(newNode);
-
-        if (options.nodeMaterialBlock) {
-            newNode.prepare(options, this._nodes, this._model, this);
-        }
-
-        return newNode;
+        // Graph
+        return this._graphCanvas.appendBlock(options.nodeMaterialBlock);
     }
     
     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;
+        return this.createNodeFromObject({ type: type, nodeMaterialBlock: newInputBlock })
     }
 
     onWidgetKeyUp(evt: any) {        
@@ -177,6 +128,9 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
 
     componentDidMount() {
         if (this.props.globalState.hostDocument) {
+            this._graphCanvas = (this.refs["graphCanvas"] as GraphCanvasComponent);
+
+
             var widget = (this.refs["test"] as DiagramWidget);
             widget.setState({ document: this.props.globalState.hostDocument })
             this._onWidgetKeyUpPointer = this.onWidgetKeyUp.bind(this)
@@ -213,6 +167,8 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
         if (navigator.userAgent.indexOf("Mobile") !== -1) {
             ((this.props.globalState.hostDocument || document).querySelector(".blocker") as HTMLElement).style.visibility = "visible";
         }
+
+        this.build(true);
     }
 
     componentWillUnmount() {
@@ -268,9 +224,9 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
             this.reOrganize();
         });
 
-        this.props.globalState.onGetNodeFromBlock = (block) => {
-            return this._nodes.filter(n => n.block === block)[0];
-        }
+        // this.props.globalState.onGetNodeFromBlock = (block) => {
+        //     return this._nodes.filter(n => n.block === block)[0];
+        // }
 
         this.props.globalState.hostDocument!.addEventListener("keydown", evt => {
             this._altKeyIsPressed = evt.altKey;
@@ -329,33 +285,18 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
                         y = currentY;
                     }
 
-                    newNode.setPosition(x, y);
+                    newNode.x = x;
+                    newNode.y = y;
                 }
 
                 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._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() {
@@ -397,152 +338,153 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
         this._model = new DiagramModel();
         this._nodes = [];
         this._blocks = [];
+        this._graphCanvas.reset();
 
         // 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._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!);      
+        //                         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";
+        //                         let isFragmentOutput = (input.parent as DefaultNodeModel).block!.getClassName() === "FragmentOutputBlock";
 
-                                if (isFragmentOutput) {
-                                    this.applyFragmentOutputConstraints(input);
-                                }
+        //                         if (isFragmentOutput) {
+        //                             this.applyFragmentOutputConstraints(input);
+        //                         }
 
-                                this.forceUpdate();
-                            }, 1);
+        //                         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);
+        //                     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";
+        //                     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;
+        //                             // 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();
-                                        }
-                                    }
+        //                                 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);
-                                        }
+        //                             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);    
-                                    }
+        //                                 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.forceUpdate();
+        //                         }
+        //                         if (this.props.globalState.nodeMaterial) {
+        //                             this.buildMaterial();
+        //                         }
+        //                     } else {
+        //                         e.link.remove();
+        //                     }
+        //                 }
+        //             });
+        //         }             
+        //     }
+        // });
 
         // Load graph of nodes from the material
         if (this.props.globalState.nodeMaterial) {
@@ -575,26 +517,17 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
 
     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;
-                    }
-                }
-            });
+            this._graphCanvas.distributeGraph();
         } else {
-            for (var location of locations) {
-                for (var node of this._nodes) {
-                    if (node.block && node.block.uniqueId === location.blockId) {
-                        node.setPosition(location.x, location.y);
-                        break;
-                    }
-                }
-            }
+            // TO DO
+            // for (var location of locations) {
+            //     for (var node of this._nodes) {
+            //         if (node.block && node.block.uniqueId === location.blockId) {
+            //             node.setPosition(location.x, location.y);
+            //             break;
+            //         }
+            //     }
+            // }
         }
 
         this._engine.repaintCanvas();
@@ -641,64 +574,70 @@ 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 });
-            }
+            this._toAdd = [];
+            block.autoConfigure(this.props.globalState.nodeMaterial);       
+            newNode = this.createNodeFromObject({ nodeMaterialBlock: block });
         };
 
-        if (nodeModel) {
-            const zoomLevel = this._engine.diagramModel.getZoomLevel() / 100.0;
+        let x = (event.clientX - event.currentTarget.offsetLeft - this._graphCanvas.x - this.NodeWidth) / this._graphCanvas.zoom;
+        let y = (event.clientY - event.currentTarget.offsetTop - this._graphCanvas.y - 20) / this._graphCanvas.zoom;
+        
+        newNode.x = x;
+        newNode.y = y;
+
+        this.props.globalState.onSelectionChangedObservable.notifyObservers(newNode);
 
-            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);
+        // 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 block = nodeModel!.block;
+        //     let block = nodeModel!.block;
 
-            x -= this.NodeWidth + 150;
+        //     x -= this.NodeWidth + 150;
 
-            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];
+        //     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];
 
-                    if (connectedNode.x === 0 && connectedNode.y === 0) {
-                        connectedNode.setPosition(x, y);
-                        y += 80;
-                    }
-                }
-            });
+        //             if (connectedNode.x === 0 && connectedNode.y === 0) {
+        //                 connectedNode.setPosition(x, y);
+        //                 y += 80;
+        //             }
+        //         }
+        //     });
             
-            this._engine.repaintCanvas();
+        //     this._engine.repaintCanvas();
 
-            setTimeout(() => {
-                this._model.addAll(...this._toAdd!);            
-                this._toAdd = null;  
-                this._model.clearSelection();
-                nodeModel!.setSelected(true);
+        //     setTimeout(() => {
+        //         this._model.addAll(...this._toAdd!);            
+        //         this._toAdd = null;  
+        //         this._model.clearSelection();
+        //         nodeModel!.setSelected(true);
 
-                this._engine.repaintCanvas();  
-            }, 150);
-        }
+        //         this._engine.repaintCanvas();  
+        //     }, 150);
+        // }
 
         this.forceUpdate();
     }
@@ -744,6 +683,7 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
                         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;
-    }
-}

+ 1 - 0
nodeEditor/src/main.scss

@@ -32,6 +32,7 @@
     height: 100%;
 
     .diagram {
+        display: none;
         width: 100%;
         height: 100%;
     }