瀏覽代碼

Merge branch 'master' of https://github.com/BabylonJS/Babylon.js

David Catuhe 6 年之前
父節點
當前提交
b00932943e
共有 87 個文件被更改,包括 8148 次插入237 次删除
  1. 1388 1
      Playground/babylon.d.txt
  2. 55 2
      Tools/Config/config.json
  3. 37 38
      bower.json
  4. 27 0
      dist/preview release/nodeEditor/package.json
  5. 1 0
      dist/preview release/nodeEditor/readme-es6.md
  6. 1 0
      dist/preview release/nodeEditor/readme.md
  7. 1 0
      dist/preview release/what's new.md
  8. 2 2
      inspector/README.md
  9. 46 33
      inspector/src/components/actionTabs/actionTabsComponent.tsx
  10. 4 4
      inspector/src/components/actionTabs/lineContainerComponent.tsx
  11. 17 5
      inspector/src/components/actionTabs/lines/textureLineComponent.tsx
  12. 0 0
      inspector/src/index.css
  13. 0 2
      inspector/src/index.ts
  14. 25 20
      inspector/src/inspector.ts
  15. 二進制
      inspector/test/environment.dds
  16. 二進制
      inspector/test/explosion.wav
  17. 0 91
      inspector/test/index.js
  18. 二進制
      inspector/test/jump.wav
  19. 二進制
      inspector/test/test_1.dds
  20. 1 0
      nodeEditor/README-ES6.md
  21. 18 0
      nodeEditor/README.md
  22. 34 0
      nodeEditor/src/components/customDiragramNodes/generic/genericNodeFactory.tsx
  23. 50 0
      nodeEditor/src/components/customDiragramNodes/generic/genericNodeModel.ts
  24. 162 0
      nodeEditor/src/components/customDiragramNodes/generic/genericNodeWidget.tsx
  25. 73 0
      nodeEditor/src/components/customDiragramNodes/generic/genericPortModel.ts
  26. 385 0
      nodeEditor/src/components/graphEditor.tsx
  27. 892 0
      nodeEditor/src/components/main.scss
  28. 6 0
      nodeEditor/src/globalState.ts
  29. 1 0
      nodeEditor/src/index.ts
  30. 9 0
      nodeEditor/src/legacy/legacy.ts
  31. 57 0
      nodeEditor/src/nodeEditor.ts
  32. 21 0
      nodeEditor/src/sharedComponents/buttonLineComponent.tsx
  33. 33 0
      nodeEditor/src/sharedComponents/fileButtonLineComponent.tsx
  34. 120 0
      nodeEditor/src/sharedComponents/lineContainerComponent.tsx
  35. 69 0
      nodeEditor/src/sharedComponents/numericInputComponent.tsx
  36. 70 0
      nodeEditor/src/sharedComponents/popup.ts
  37. 6 0
      nodeEditor/src/sharedComponents/propertyChangedEvent.ts
  38. 202 0
      nodeEditor/src/sharedComponents/textureLineComponent.tsx
  39. 114 0
      nodeEditor/src/sharedComponents/vector2LineComponent.tsx
  40. 124 0
      nodeEditor/src/sharedComponents/vector3LineComponent.tsx
  41. 28 0
      nodeEditor/tsconfig.json
  42. 34 0
      nodeEditor/webpack.config.js
  43. 2 1
      package.json
  44. 2 2
      src/Loading/sceneLoader.ts
  45. 143 0
      src/Materials/Node/Blocks/Dual/fogBlock.ts
  46. 2 0
      src/Materials/Node/Blocks/Dual/index.ts
  47. 51 0
      src/Materials/Node/Blocks/Fragment/alphaTestBlock.ts
  48. 54 0
      src/Materials/Node/Blocks/Fragment/fragmentOutputBlock.ts
  49. 134 0
      src/Materials/Node/Blocks/Fragment/imageProcessingBlock.ts
  50. 9 0
      src/Materials/Node/Blocks/Fragment/index.ts
  51. 67 0
      src/Materials/Node/Blocks/Fragment/rgbMergerBlock.ts
  52. 59 0
      src/Materials/Node/Blocks/Fragment/rgbSplitterBlock.ts
  53. 88 0
      src/Materials/Node/Blocks/Fragment/rgbaMergerBlock.ts
  54. 65 0
      src/Materials/Node/Blocks/Fragment/rgbaSplitterBlock.ts
  55. 194 0
      src/Materials/Node/Blocks/Fragment/textureBlock.ts
  56. 170 0
      src/Materials/Node/Blocks/Vertex/bonesBlock.ts
  57. 4 0
      src/Materials/Node/Blocks/Vertex/index.ts
  58. 133 0
      src/Materials/Node/Blocks/Vertex/instancesBlock.ts
  59. 211 0
      src/Materials/Node/Blocks/Vertex/morphTargetsBlock.ts
  60. 46 0
      src/Materials/Node/Blocks/Vertex/vertexOutputBlock.ts
  61. 52 0
      src/Materials/Node/Blocks/addBlock.ts
  62. 50 0
      src/Materials/Node/Blocks/clampBlock.ts
  63. 10 0
      src/Materials/Node/Blocks/index.ts
  64. 54 0
      src/Materials/Node/Blocks/matrixMultiplicationBlock.ts
  65. 52 0
      src/Materials/Node/Blocks/multiplyBlock.ts
  66. 66 0
      src/Materials/Node/Blocks/vector2TransformBlock.ts
  67. 61 0
      src/Materials/Node/Blocks/vector3TransformBlock.ts
  68. 65 0
      src/Materials/Node/Blocks/vector4TransformBlock.ts
  69. 1 0
      src/Materials/Node/Optimizers/index.ts
  70. 15 0
      src/Materials/Node/Optimizers/nodeMaterialOptimizer.ts
  71. 8 0
      src/Materials/Node/index.ts
  72. 721 0
      src/Materials/Node/nodeMaterial.ts
  73. 420 0
      src/Materials/Node/nodeMaterialBlock.ts
  74. 342 0
      src/Materials/Node/nodeMaterialBlockConnectionPoint.ts
  75. 35 0
      src/Materials/Node/nodeMaterialBlockConnectionPointTypes.ts
  76. 11 0
      src/Materials/Node/nodeMaterialBlockTargets.ts
  77. 389 0
      src/Materials/Node/nodeMaterialBuildState.ts
  78. 137 0
      src/Materials/Node/nodeMaterialBuildStateSharedData.ts
  79. 19 0
      src/Materials/Node/nodeMaterialWellKnownValues.ts
  80. 14 0
      src/Materials/effect.ts
  81. 2 1
      src/Materials/index.ts
  82. 8 0
      src/Materials/material.ts
  83. 4 1
      src/Materials/materialDefines.ts
  84. 56 28
      src/Materials/materialHelper.ts
  85. 4 1
      src/Materials/shaderMaterial.ts
  86. 3 3
      src/Materials/standardMaterial.ts
  87. 2 2
      src/Meshes/mesh.ts

文件差異過大導致無法顯示
+ 1388 - 1
Playground/babylon.d.txt


+ 55 - 2
Tools/Config/config.json

@@ -46,7 +46,8 @@
         "loaders",
         "loaders",
         "serializers",
         "serializers",
         "gui",
         "gui",
-        "inspector"
+        "inspector",
+        "nodeEditor"
     ],
     ],
     "es6modules": [
     "es6modules": [
         "core",
         "core",
@@ -57,7 +58,8 @@
         "serializers",
         "serializers",
         "gui",
         "gui",
         "inspector",
         "inspector",
-        "viewer"
+        "viewer",
+        "nodeEditor"
     ],
     ],
     "lintModules": [
     "lintModules": [
         "core",
         "core",
@@ -581,6 +583,57 @@
             }
             }
         }
         }
     },
     },
+    "nodeEditor": {
+        "libraries": [
+            {
+                "output": "babylon.nodeEditor.js",
+                "entry": "./legacy/legacy.ts"
+            }
+        ],
+        "build": {
+            "ignoreInTestMode": true,
+            "mainFolder": "./nodeEditor/",
+            "uncheckedLintImports": [
+                "react",
+                "react-dom",
+                "re-resizable",
+                "glTF"
+            ],
+            "umd": {
+                "packageName": "babylonjs-node-editor",
+                "webpackRoot": "NODEEDITOR",
+                "processDeclaration": {
+                    "filename": "babylon.nodeEditor.module.d.ts",
+                    "moduleName": "NODEEDITOR",
+                    "importsToRemove": [],
+                    "classMap": {
+                        "babylonjs": "BABYLON",
+                        "react": "React",
+                        "@babylonjs/core": "BABYLON",
+                        "@fortawesome": false,
+                        "react-contextmenu": false
+                    }
+                }
+            },
+            "es6": {
+                "webpackBuild": true,
+                "buildDependencies": [
+                    "node_modules/re-resizable/lib/index.es5.js",
+                    "Tools/**/*"
+                ],
+                "packageName": "@babylonjs/node-editor",
+                "readme": "dist/preview release/nodeEditor/readme-es6.md",
+                "packagesFiles": [
+                    "babylon.nodeEditor.max.js",
+                    "babylon.nodeEditor.max.js.map",
+                    "babylon.nodeEditor.module.d.ts",
+                    "readme.md"
+                ],
+                "typings": "babylon.nodeEditor.module.d.ts",
+                "index": "babylon.nodeEditor.max.js"
+            }
+        }
+    },
     "viewer": {
     "viewer": {
         "libraries": [
         "libraries": [
             {
             {

+ 37 - 38
bower.json

@@ -1,39 +1,38 @@
 {
 {
-  "name": "babylonjs",
-  "description": "Babylon.js is a complete JavaScript framework for building 3D games with HTML 5 and WebGL",
-  "main": "./dist/babylon.2.5.js",
-  "homepage": "https://www.babylonjs.com",
-  "repository": {
-    "type": "git",
-    "url": "git://github.com/BabylonJS/Babylon.js.git"
-  },
-  "authors": [
-    "David Catuhe",
-    "David Rousset"
-  ],
-  "keywords": [
-    "3D",
-    "WebGL",
-    "WebAudio",
-    "Shaders",
-    "Realtime"
-  ],
-  "license": "Apache-2.0",
-  "ignore": [
-    "**/.*",
-    "node_modules",
-    "Babylon",
-    "Exporters",
-    "Loaders",
-    "Previous releases",
-    "/Tools",
-    "gulpfile.js",
-    "package.json",
-    "babylon.2.1*.*",
-    "*.md",
-    "*.yml",
-    "cannon.js",
-    "Oimo.js",
-    "poly2tri.js"
-  ]
-}
+    "name": "babylonjs",
+    "description": "Babylon.js is a complete JavaScript framework for building 3D games with HTML 5 and WebGL",
+    "main": "./dist/babylon.js",
+    "homepage": "https://www.babylonjs.com",
+    "repository": {
+        "type": "git",
+        "url": "git://github.com/BabylonJS/Babylon.js.git"
+    },
+    "authors": [
+        "David Catuhe"
+    ],
+    "keywords": [
+        "3D",
+        "WebGL",
+        "WebAudio",
+        "Shaders",
+        "Realtime"
+    ],
+    "license": "Apache-2.0",
+    "ignore": [
+        "**/.*",
+        "node_modules",
+        "Babylon",
+        "Exporters",
+        "Loaders",
+        "Previous releases",
+        "/Tools",
+        "gulpfile.js",
+        "package.json",
+        "babylon.2.1*.*",
+        "*.md",
+        "*.yml",
+        "cannon.js",
+        "Oimo.js",
+        "poly2tri.js"
+    ]
+}

+ 27 - 0
dist/preview release/nodeEditor/package.json

@@ -0,0 +1,27 @@
+{
+    "author": {
+        "name": "David CATUHE"
+    },
+    "name": "babylonjs-node-editor",
+    "description": "The Babylon.js node material editor.",
+    "version": "4.1.0-alpha.0",
+    "repository": {
+        "type": "git",
+        "url": "https://github.com/BabylonJS/Babylon.js.git"
+    },
+    "license": "Apache-2.0",
+    "dependencies": {
+        "babylonjs": "4.1.0-alpha.0"
+    },
+    "files": [
+        "babylon.nodeEditor.max.js.map",
+        "babylon.nodeEditor.max.js",
+        "babylon.nodeEditor.js",
+        "babylon.nodeEditor.module.d.ts",
+        "readme.md",
+        "package.json"
+    ],
+    "engines": {
+        "node": "*"
+    }
+}

+ 1 - 0
dist/preview release/nodeEditor/readme-es6.md

@@ -0,0 +1 @@
+Node Editor es6

+ 1 - 0
dist/preview release/nodeEditor/readme.md

@@ -0,0 +1 @@
+Node Editor

+ 1 - 0
dist/preview release/what's new.md

@@ -11,6 +11,7 @@
 - Added support for `ShadowGenerator` ([Deltakosh](https://github.com/deltakosh/))
 - Added support for `ShadowGenerator` ([Deltakosh](https://github.com/deltakosh/))
 - Added support for scene normalization ([Deltakosh](https://github.com/deltakosh/))
 - Added support for scene normalization ([Deltakosh](https://github.com/deltakosh/))
 - Added support for texture creation and assignments per material ([Deltakosh](https://github.com/deltakosh/))
 - Added support for texture creation and assignments per material ([Deltakosh](https://github.com/deltakosh/))
+- Node material editor ([Deltakosh](https://github.com/deltakosh/)/[TrevorDev](https://github.com/TrevorDev))
 
 
 ### Tools
 ### Tools
 - Added `Color3.toHSV()`, `Color3.toHSVToRef()` and `Color3.HSVtoRGBToRef()` ([Deltakosh](https://github.com/deltakosh/))
 - Added `Color3.toHSV()`, `Color3.toHSVToRef()` and `Color3.HSVtoRGBToRef()` ([Deltakosh](https://github.com/deltakosh/))

+ 2 - 2
inspector/README.md

@@ -9,13 +9,13 @@ Call the method `show` of the scene debugLayer:
 ```
 ```
 scene.debugLayer.show();
 scene.debugLayer.show();
 ```
 ```
-This method will retrieve dynamically the library `inspector.js`, download it and add
+This method will retrieve dynamically the library `babylon.inspector.bundle.js`, download it and add
 it to the html page.
 it to the html page.
 
 
 ### Offline method
 ### Offline method
 If you don't have access to internet, the inspector should be imported manually in your HTML page :
 If you don't have access to internet, the inspector should be imported manually in your HTML page :
 ```
 ```
-<script src="babylon.inspector.js" />
+<script src="babylon.inspector.bundle.js" />
 ``` 
 ``` 
 Then, call the method `show` of the scene debugLayer: 
 Then, call the method `show` of the scene debugLayer: 
 ```
 ```

+ 46 - 33
inspector/src/components/actionTabs/actionTabsComponent.tsx

@@ -15,7 +15,7 @@ import { GlobalState } from "../../components/globalState";
 require("./actionTabs.scss");
 require("./actionTabs.scss");
 
 
 interface IActionTabsComponentProps {
 interface IActionTabsComponentProps {
-    scene: Scene,
+    scene?: Scene,
     noCommands?: boolean,
     noCommands?: boolean,
     noHeader?: boolean,
     noHeader?: boolean,
     noExpand?: boolean,
     noExpand?: boolean,
@@ -23,7 +23,7 @@ interface IActionTabsComponentProps {
     popupMode?: boolean,
     popupMode?: boolean,
     onPopup?: () => void,
     onPopup?: () => void,
     onClose?: () => void,
     onClose?: () => void,
-    globalState: GlobalState
+    globalState?: GlobalState
 }
 }
 
 
 export class ActionTabsComponent extends React.Component<IActionTabsComponentProps, { selectedEntity: any, selectedIndex: number }> {
 export class ActionTabsComponent extends React.Component<IActionTabsComponentProps, { selectedEntity: any, selectedIndex: number }> {
@@ -36,53 +36,66 @@ export class ActionTabsComponent extends React.Component<IActionTabsComponentPro
 
 
         let initialIndex = 0;
         let initialIndex = 0;
 
 
-        const validationResutls = this.props.globalState.validationResults;
-        if (validationResutls) {
-            if (validationResutls.issues.numErrors || validationResutls.issues.numWarnings) {
-                initialIndex = 3;
+        if(this.props.globalState){
+            const validationResutls = this.props.globalState.validationResults;
+            if (validationResutls) {
+                if (validationResutls.issues.numErrors || validationResutls.issues.numWarnings) {
+                    initialIndex = 3;
+                }
             }
             }
         }
         }
+        
 
 
         this.state = { selectedEntity: null, selectedIndex: initialIndex }
         this.state = { selectedEntity: null, selectedIndex: initialIndex }
     }
     }
 
 
     componentWillMount() {
     componentWillMount() {
-        this._onSelectionChangeObserver = this.props.globalState.onSelectionChangedObservable.add((entity) => {
-            this.setState({ selectedEntity: entity, selectedIndex: 0 });
-        });
-
-        this._onTabChangedObserver = this.props.globalState.onTabChangedObservable.add(index => {
-            this.setState({ selectedIndex: index });
-        });
+        if(this.props.globalState){
+            this._onSelectionChangeObserver = this.props.globalState.onSelectionChangedObservable.add((entity) => {
+                this.setState({ selectedEntity: entity, selectedIndex: 0 });
+            });
+
+            this._onTabChangedObserver = this.props.globalState.onTabChangedObservable.add(index => {
+                this.setState({ selectedIndex: index });
+            });
+        }
     }
     }
 
 
     componentWillUnmount() {
     componentWillUnmount() {
-        if (this._onSelectionChangeObserver) {
-            this.props.globalState.onSelectionChangedObservable.remove(this._onSelectionChangeObserver);
-        }
+        if(this.props.globalState){
+            if (this._onSelectionChangeObserver) {
+                this.props.globalState.onSelectionChangedObservable.remove(this._onSelectionChangeObserver);
+            }
 
 
-        if (this._onTabChangedObserver) {
-            this.props.globalState.onTabChangedObservable.remove(this._onTabChangedObserver);
+            if (this._onTabChangedObserver) {
+                this.props.globalState.onTabChangedObservable.remove(this._onTabChangedObserver);
+            }
         }
         }
     }
     }
 
 
     changeSelectedTab(index: number) {
     changeSelectedTab(index: number) {
-        this.props.globalState.onTabChangedObservable.notifyObservers(index);
+        if(this.props.globalState){
+            this.props.globalState.onTabChangedObservable.notifyObservers(index);
+        }
     }
     }
 
 
     renderContent() {
     renderContent() {
-        return (
-            <TabsComponent selectedIndex={this.state.selectedIndex} onSelectedIndexChange={(value) => this.changeSelectedTab(value)}>
-                <PropertyGridTabComponent
-                    title="Properties" icon={faFileAlt} scene={this.props.scene} selectedEntity={this.state.selectedEntity}
-                    globalState={this.props.globalState}
-                    onSelectionChangedObservable={this.props.globalState.onSelectionChangedObservable}
-                    onPropertyChangedObservable={this.props.globalState.onPropertyChangedObservable} />
-                <DebugTabComponent title="Debug" icon={faBug} scene={this.props.scene} globalState={this.props.globalState} />
-                <StatisticsTabComponent title="Statistics" icon={faChartBar} scene={this.props.scene} globalState={this.props.globalState} />
-                <ToolsTabComponent title="Tools" icon={faWrench} scene={this.props.scene} globalState={this.props.globalState} />
-            </TabsComponent>
-        )
+        if(this.props.globalState && this.props.scene){
+            return (
+                <TabsComponent selectedIndex={this.state.selectedIndex} onSelectedIndexChange={(value) => this.changeSelectedTab(value)}>
+                    <PropertyGridTabComponent
+                        title="Properties" icon={faFileAlt} scene={this.props.scene} selectedEntity={this.state.selectedEntity}
+                        globalState={this.props.globalState}
+                        onSelectionChangedObservable={this.props.globalState.onSelectionChangedObservable}
+                        onPropertyChangedObservable={this.props.globalState.onPropertyChangedObservable} />
+                    <DebugTabComponent title="Debug" icon={faBug} scene={this.props.scene} globalState={this.props.globalState} />
+                    <StatisticsTabComponent title="Statistics" icon={faChartBar} scene={this.props.scene} globalState={this.props.globalState} />
+                    <ToolsTabComponent title="Tools" icon={faWrench} scene={this.props.scene} globalState={this.props.globalState} />
+                </TabsComponent>
+            )
+        }else{
+            return null;
+        }
     }
     }
 
 
     onClose() {
     onClose() {
@@ -105,7 +118,7 @@ export class ActionTabsComponent extends React.Component<IActionTabsComponentPro
                 <div id="actionTabs">
                 <div id="actionTabs">
                     {
                     {
                         !this.props.noHeader &&
                         !this.props.noHeader &&
-                        <HeaderComponent title="INSPECTOR" handleBack={true} noClose={this.props.noClose} noExpand={this.props.noExpand} noCommands={this.props.noCommands} onClose={() => this.onClose()} onPopup={() => this.onPopup()} onSelectionChangedObservable={this.props.globalState.onSelectionChangedObservable} />
+                        <HeaderComponent title="INSPECTOR" handleBack={true} noClose={this.props.noClose} noExpand={this.props.noExpand} noCommands={this.props.noCommands} onClose={() => this.onClose()} onPopup={() => this.onPopup()} onSelectionChangedObservable={this.props.globalState ? this.props.globalState.onSelectionChangedObservable : undefined} />
                     }
                     }
                     {this.renderContent()}
                     {this.renderContent()}
                 </div>
                 </div>
@@ -128,7 +141,7 @@ export class ActionTabsComponent extends React.Component<IActionTabsComponentPro
             <Resizable id="actionTabs" minWidth={300} maxWidth={600} size={{ height: "100%" }} minHeight="100%" enable={{ top: false, right: false, bottom: false, left: true, topRight: false, bottomRight: false, bottomLeft: false, topLeft: false }}>
             <Resizable id="actionTabs" minWidth={300} maxWidth={600} size={{ height: "100%" }} minHeight="100%" enable={{ top: false, right: false, bottom: false, left: true, topRight: false, bottomRight: false, bottomLeft: false, topLeft: false }}>
                 {
                 {
                     !this.props.noHeader &&
                     !this.props.noHeader &&
-                    <HeaderComponent title="INSPECTOR" handleBack={true} noClose={this.props.noClose} noExpand={this.props.noExpand} noCommands={this.props.noCommands} onClose={() => this.onClose()} onPopup={() => this.onPopup()} onSelectionChangedObservable={this.props.globalState.onSelectionChangedObservable} />
+                    <HeaderComponent title="INSPECTOR" handleBack={true} noClose={this.props.noClose} noExpand={this.props.noExpand} noCommands={this.props.noCommands} onClose={() => this.onClose()} onPopup={() => this.onPopup()} onSelectionChangedObservable={this.props.globalState ? this.props.globalState.onSelectionChangedObservable : undefined} />
                 }
                 }
                 {this.renderContent()}
                 {this.renderContent()}
             </Resizable>
             </Resizable>

+ 4 - 4
inspector/src/components/actionTabs/lineContainerComponent.tsx

@@ -4,7 +4,7 @@ import { faChevronDown } from '@fortawesome/free-solid-svg-icons';
 import { GlobalState } from '../../components/globalState';
 import { GlobalState } from '../../components/globalState';
 
 
 interface ILineContainerComponentProps {
 interface ILineContainerComponentProps {
-    globalState: GlobalState;
+    globalState?: GlobalState;
     title: string;
     title: string;
     children: any[] | any;
     children: any[] | any;
     closed?: boolean;
     closed?: boolean;
@@ -55,13 +55,13 @@ export class LineContainerComponent extends React.Component<ILineContainerCompon
     }
     }
 
 
     componentDidMount() {
     componentDidMount() {
-        if (!this.props.globalState.selectedLineContainerTitle) {
+        if (this.props.globalState && !this.props.globalState.selectedLineContainerTitle) {
             return;
             return;
         }
         }
 
 
-        if (this.props.globalState.selectedLineContainerTitle === this.props.title) {
+        if (this.props.globalState && this.props.globalState.selectedLineContainerTitle === this.props.title) {
             setTimeout(() => {
             setTimeout(() => {
-                this.props.globalState.selectedLineContainerTitle = "";
+                this.props.globalState!.selectedLineContainerTitle = "";
             });
             });
 
 
             this.setState({ isExpanded: true, isHighlighted: true });
             this.setState({ isExpanded: true, isHighlighted: true });

+ 17 - 5
inspector/src/components/actionTabs/lines/textureLineComponent.tsx

@@ -13,7 +13,8 @@ interface ITextureLineComponentProps {
     texture: BaseTexture;
     texture: BaseTexture;
     width: number;
     width: number;
     height: number;
     height: number;
-    globalState: GlobalState;
+    globalState?: GlobalState;
+    hideChannelSelect?:boolean;
 }
 }
 
 
 export class TextureLineComponent extends React.Component<ITextureLineComponentProps, { displayRed: boolean, displayGreen: boolean, displayBlue: boolean, displayAlpha: boolean, face: number }> {
 export class TextureLineComponent extends React.Component<ITextureLineComponentProps, { displayRed: boolean, displayGreen: boolean, displayBlue: boolean, displayAlpha: boolean, face: number }> {
@@ -43,6 +44,11 @@ export class TextureLineComponent extends React.Component<ITextureLineComponentP
 
 
     updatePreview() {
     updatePreview() {
         var texture = this.props.texture;
         var texture = this.props.texture;
+        if(!texture.isReady() && texture._texture){
+            texture._texture.onLoadedObservable.addOnce(()=>{
+                this.updatePreview();
+            })
+        }
         var scene = texture.getScene()!;
         var scene = texture.getScene()!;
         var engine = scene.getEngine();
         var engine = scene.getEngine();
         var size = texture.getSize();
         var size = texture.getSize();
@@ -72,7 +78,10 @@ export class TextureLineComponent extends React.Component<ITextureLineComponentP
 
 
         const previewCanvas = this.refs.canvas as HTMLCanvasElement;
         const previewCanvas = this.refs.canvas as HTMLCanvasElement;
 
 
-        this.props.globalState.blockMutationUpdates = true;
+        if(this.props.globalState){
+            this.props.globalState.blockMutationUpdates = true;
+        }
+        
         let rtt = new RenderTargetTexture(
         let rtt = new RenderTargetTexture(
             "temp",
             "temp",
             { width: width, height: height },
             { width: width, height: height },
@@ -156,7 +165,10 @@ export class TextureLineComponent extends React.Component<ITextureLineComponentP
         passPostProcess.dispose();
         passPostProcess.dispose();
 
 
         previewCanvas.style.height = height + "px";
         previewCanvas.style.height = height + "px";
-        this.props.globalState.blockMutationUpdates = false;
+        if(this.props.globalState){
+            this.props.globalState.blockMutationUpdates = false;
+        }
+        
     }
     }
 
 
     render() {
     render() {
@@ -165,7 +177,7 @@ export class TextureLineComponent extends React.Component<ITextureLineComponentP
         return (
         return (
             <div className="textureLine">
             <div className="textureLine">
                 {
                 {
-                    texture.isCube &&
+                    !this.props.hideChannelSelect && texture.isCube &&
                     <div className="control3D">
                     <div className="control3D">
                         <button className={this.state.face === 0 ? "px command selected" : "px command"} onClick={() => this.setState({ face: 0 })}>PX</button>
                         <button className={this.state.face === 0 ? "px command selected" : "px command"} onClick={() => this.setState({ face: 0 })}>PX</button>
                         <button className={this.state.face === 1 ? "nx command selected" : "nx command"} onClick={() => this.setState({ face: 1 })}>NX</button>
                         <button className={this.state.face === 1 ? "nx command selected" : "nx command"} onClick={() => this.setState({ face: 1 })}>NX</button>
@@ -176,7 +188,7 @@ export class TextureLineComponent extends React.Component<ITextureLineComponentP
                     </div>
                     </div>
                 }
                 }
                 {
                 {
-                    !texture.isCube &&
+                    !this.props.hideChannelSelect && !texture.isCube &&
                     <div className="control">
                     <div className="control">
                         <button className={this.state.displayRed && !this.state.displayGreen ? "red command selected" : "red command"} onClick={() => this.setState({ displayRed: true, displayGreen: false, displayBlue: false, displayAlpha: false })}>R</button>
                         <button className={this.state.displayRed && !this.state.displayGreen ? "red command selected" : "red command"} onClick={() => this.setState({ displayRed: true, displayGreen: false, displayBlue: false, displayAlpha: false })}>R</button>
                         <button className={this.state.displayGreen && !this.state.displayBlue ? "green command selected" : "green command"} onClick={() => this.setState({ displayRed: false, displayGreen: true, displayBlue: false, displayAlpha: false })}>G</button>
                         <button className={this.state.displayGreen && !this.state.displayBlue ? "green command selected" : "green command"} onClick={() => this.setState({ displayRed: false, displayGreen: true, displayBlue: false, displayAlpha: false })}>G</button>

+ 0 - 0
inspector/src/index.css


+ 0 - 2
inspector/src/index.ts

@@ -1,3 +1 @@
-require("./index.css");
-
 export * from "./inspector";
 export * from "./inspector";

+ 25 - 20
inspector/src/inspector.ts

@@ -48,22 +48,27 @@ export class Inspector {
     private static _CopyStyles(sourceDoc: HTMLDocument, targetDoc: HTMLDocument) {
     private static _CopyStyles(sourceDoc: HTMLDocument, targetDoc: HTMLDocument) {
         for (var index = 0; index < sourceDoc.styleSheets.length; index++) {
         for (var index = 0; index < sourceDoc.styleSheets.length; index++) {
             var styleSheet: any = sourceDoc.styleSheets[index];
             var styleSheet: any = sourceDoc.styleSheets[index];
-            if (styleSheet.cssRules) { // for <style> elements
-                const newStyleEl = sourceDoc.createElement('style');
-
-                for (var cssRule of styleSheet.cssRules) {
-                    // write the text of each rule into the body of the style element
-                    newStyleEl.appendChild(sourceDoc.createTextNode(cssRule.cssText));
+            try{
+                if (styleSheet.cssRules) { // for <style> elements
+                    const newStyleEl = sourceDoc.createElement('style');
+    
+                    for (var cssRule of styleSheet.cssRules) {
+                        // write the text of each rule into the body of the style element
+                        newStyleEl.appendChild(sourceDoc.createTextNode(cssRule.cssText));
+                    }
+    
+                    targetDoc.head!.appendChild(newStyleEl);
+                } else if (styleSheet.href) { // for <link> elements loading CSS from a URL
+                    const newLinkEl = sourceDoc.createElement('link');
+    
+                    newLinkEl.rel = 'stylesheet';
+                    newLinkEl.href = styleSheet.href;
+                    targetDoc.head!.appendChild(newLinkEl);
                 }
                 }
-
-                targetDoc.head!.appendChild(newStyleEl);
-            } else if (styleSheet.href) { // for <link> elements loading CSS from a URL
-                const newLinkEl = sourceDoc.createElement('link');
-
-                newLinkEl.rel = 'stylesheet';
-                newLinkEl.href = styleSheet.href;
-                targetDoc.head!.appendChild(newLinkEl);
+            }catch(e){
+                console.log(e)
             }
             }
+            
         }
         }
     }
     }
 
 
@@ -250,12 +255,12 @@ export class Inspector {
             ReactDOM.render(embedHostElement, this._EmbedHost);
             ReactDOM.render(embedHostElement, this._EmbedHost);
         }
         }
     }
     }
-    private static _CreatePopup(title: string, windowVariableName: string) {
+    public static _CreatePopup(title: string, windowVariableName: string, width = 300, height = 800) {
         const windowCreationOptionsList = {
         const windowCreationOptionsList = {
-            width: 300,
-            height: 800,
-            top: (window.innerHeight - 800) / 2 + window.screenY,
-            left: (window.innerWidth - 300) / 2 + window.screenX
+            width: width,
+            height: height,
+            top: (window.innerHeight - width) / 2 + window.screenY,
+            left: (window.innerWidth - height) / 2 + window.screenX
         };
         };
 
 
         var windowCreationOptions = Object.keys(windowCreationOptionsList)
         var windowCreationOptions = Object.keys(windowCreationOptionsList)
@@ -410,7 +415,7 @@ export class Inspector {
         }
         }
     }
     }
 
 
-    private static _CreateCanvasContainer(parentControl: HTMLElement) {
+    public static _CreateCanvasContainer(parentControl: HTMLElement) {
         // Create a container for previous elements
         // Create a container for previous elements
         this._NewCanvasContainer = parentControl.ownerDocument!.createElement("div");
         this._NewCanvasContainer = parentControl.ownerDocument!.createElement("div");
         this._NewCanvasContainer.style.display = parentControl.style.display;
         this._NewCanvasContainer.style.display = parentControl.style.display;

二進制
inspector/test/environment.dds


二進制
inspector/test/explosion.wav


+ 0 - 91
inspector/test/index.js

@@ -1,91 +0,0 @@
-/// <reference path="../../dist/preview release/babylon.d.ts"/>
-
-var Test = (function() {
-    function Test(canvasId) {
-        var _this = this;
-        var canvas = document.getElementById(canvasId);
-        this.engine = new BABYLON.Engine(canvas, true);
-        this.scene = null;
-        window.addEventListener("resize", function() {
-            _this.engine.resize();
-        });
-        this._run();
-    }
-    Test.prototype._run = function() {
-        var _this = this;
-        this._initScene();
-        this.scene.executeWhenReady(function() {
-            _this.engine.runRenderLoop(function() {
-                _this.scene.render();
-            });
-        });
-    };
-    Test.prototype._initScene = function() {
-        var scene = new BABYLON.Scene(this.engine);
-        var canvas = scene.getEngine().getRenderingCanvas();
-
-        var camera = new BABYLON.FreeCamera("Camera", new BABYLON.Vector3(0, 2, -2), scene);
-
-        var camera2 = new BABYLON.ArcRotateCamera("Camera2", 0, 0, 5, new BABYLON.Vector3(0, 0, 0), scene);
-
-        var camera3 = new BABYLON.ArcRotateCamera("Camera3", 0, 0, 10, new BABYLON.Vector3(0, 0, 0), scene);
-
-        var camera4 = new BABYLON.ArcRotateCamera("Camera4", 0, 0, 15, new BABYLON.Vector3(0, 0, 0), scene);
-
-        var camera5 = new BABYLON.ArcRotateCamera("Camera5", 0, 0, 20, new BABYLON.Vector3(0, 0, 0), scene);
-
-        var camera6 = new BABYLON.ArcRotateCamera("Camera6", 0, 0, 25, new BABYLON.Vector3(0, 0, 0), scene);
-
-        scene.activeCamera = camera2;
-
-        camera2.attachControl(canvas);
-
-        var sceneRoot = new BABYLON.TransformNode("abstractmesh");
-
-        var tn = new BABYLON.TransformNode("transform node");
-
-        let DDSTexture = new BABYLON.CubeTexture("test/environment.dds", scene);
-        let DDSTexture2 = new BABYLON.Texture("test/test_1.dds", scene);
-
-        // Our built-in 'ground' shape. Params: name, width, depth, subdivs, scene
-        var ground = BABYLON.Mesh.CreateGround("node_damagedHelmet_-6514", 6, 6, 2, scene);
-        ground.parent = tn;
-
-        let num = 5;
-        let angStep = 6.283185307 / num;
-        let rad = 2;
-        let p = sceneRoot;
-        for (let i = 0; i < num; i++) {
-            // Our built-in 'sphere' shape. Params: name, subdivs, size, scene
-            let sphere = BABYLON.Mesh.CreateSphere('sphere' + i, 16, 2, scene);
-
-            // Move the sphere upward 1/2 its height        
-            sphere.position.y = 0.2;
-            sphere.position.x = Math.sin(i * angStep) * rad;
-            sphere.position.z = Math.cos(i * angStep) * rad;
-            sphere.parent = p;
-            p = sphere;
-        }
-
-        let t = 0;
-        scene.registerBeforeRender(() => {
-            ground.rotation.y += 0.01;
-            ground.position.y = Math.cos(t += 0.01);
-        });
-
-        scene.createDefaultCameraOrLight(true);
-        scene.activeCamera.attachControl(canvas);
-        
-        scene.debugLayer.show({embedMode: true});
-        //scene.debugLayer.show();
-        scene.debugLayer.onPropertyChangedObservable.add((result) => {
-            console.log(result.object);
-            console.log("Property : " + result.property);
-            console.log("New value : " + result.value);
-            console.log("Old value : " + result.initialValue);
-        });
-
-        this.scene = scene;
-    };
-    return Test;
-}());

二進制
inspector/test/jump.wav


二進制
inspector/test/test_1.dds


+ 1 - 0
nodeEditor/README-ES6.md

@@ -0,0 +1 @@
+Node Editor

+ 18 - 0
nodeEditor/README.md

@@ -0,0 +1,18 @@
+# Babylon.js Node Editor
+
+An extension to easily create or update any NodeMaterial.
+
+## Usage
+### Online method
+Call the method `Show` of the `BABYLON.NoteMaterial` class: 
+```
+BABYLON.NoteMaterial.Show({hostElement: document.getElementById("host")});
+```
+This method will retrieve dynamically the library `nodeEditor.js`, download it and add
+it to the html page.
+
+### Offline method
+If you don't have access to internet, the node editor should be imported manually in your HTML page :
+```
+<script src="babylon.nodeEditor.js" />
+``` 

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

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

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

@@ -0,0 +1,50 @@
+import { NodeModel } from "storm-react-diagrams";
+import { Nullable } from 'babylonjs/types';
+import { NodeMaterialBlock } from 'babylonjs/Materials/Node/nodeMaterialBlock';
+import { Texture } from 'babylonjs/Materials/Textures/texture';
+import { Vector2, Vector3, Vector4, Matrix } from 'babylonjs/Maths/math';
+import { GenericPortModel } from './genericPortModel';
+
+/**
+ * Generic node model which stores information about a node editor block
+ */
+export class GenericNodeModel extends NodeModel {
+	/**
+	 * The babylon block this node represents
+	 */
+	public block:Nullable<NodeMaterialBlock> = null;
+	/**
+	 * Labels for the block
+	 */
+	public headerLabels:Array<{text: string}> = []
+	/**
+	 * Texture for the node if it exists
+	 */
+	public texture: Nullable<Texture> = null;
+	/**
+	 * Vector2 for the node if it exists
+	 */
+	public vector2: Nullable<Vector2> = null;
+	/**
+	 * Vector3 for the node if it exists
+	 */
+	public vector3: Nullable<Vector3> = null;
+	/**
+	 * Vector4 for the node if it exists
+	 */
+	public vector4: Nullable<Vector4> = null;
+	/**
+	 * Matrix for the node if it exists
+	 */
+	public matrix: Nullable<Matrix> = null;
+
+	public ports: {[s:string]:GenericPortModel};
+
+	/**
+	 * Constructs the node model
+	 */
+	constructor() {
+		super("generic");
+	}
+
+}

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

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

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

@@ -0,0 +1,73 @@
+import { LinkModel, PortModel, DefaultLinkModel } from "storm-react-diagrams";
+import { Nullable } from 'babylonjs/types';
+import { NodeMaterialConnectionPoint } from 'babylonjs/Materials/Node/nodeMaterialBlockConnectionPoint';
+import { GenericNodeModel } from './genericNodeModel';
+
+/**
+ * Port model for the generic node
+ */
+export class GenericPortModel extends PortModel {
+	/**
+	 * If the port is input or output
+	 */
+	public position: string | "input" | "output";
+	/**
+	 * What the port is connected to
+	 */
+	public connection: Nullable<NodeMaterialConnectionPoint> = null;
+
+	
+	static idCounter = 0;
+
+	constructor(name:string, type: string = "input") {
+		super(name, "generic");
+		this.position = type;
+		GenericPortModel.idCounter++;
+	}
+
+	syncWithNodeMaterialConnectionPoint(connection:NodeMaterialConnectionPoint){
+		this.connection = connection;
+		this.name = connection.name;
+	}
+
+	getNodeModel(){
+		return this.parent as GenericNodeModel
+	}
+
+	link(outPort:GenericPortModel){
+		var link = this.createLinkModel()
+		link.setSourcePort(this)
+		link.setTargetPort(outPort)
+		return link;
+	}
+
+	getInputFromBlock(){
+
+	}
+
+	createLinkModel(): LinkModel {
+		return new DefaultLinkModel();
+	}
+
+	getValue:Function = ()=>{
+		return null;
+	}
+
+	static SortInputOutput(a:Nullable<GenericPortModel>, b:Nullable<GenericPortModel>){
+		if(!a || !b){
+			return null;
+		}else if(a.position == "output" && b.position == "input"){
+			return {
+				input: b,
+				output: a
+			}
+		}else if(b.position == "output" && a.position == "input"){
+			return {
+				input: a,
+				output: b
+			}
+		}else{
+			return null;
+		}
+	}
+}

+ 385 - 0
nodeEditor/src/components/graphEditor.tsx

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

+ 892 - 0
nodeEditor/src/components/main.scss

@@ -0,0 +1,892 @@
+
+#node-editor-graph-root {
+    $line-padding-left: 2px;
+
+    #inspector-host {
+        position: absolute;
+        right: 0px;
+        top:0px;
+        bottom: 0px;
+    }
+    
+    #__resizable_base__ {
+        display: none;
+    }
+    
+    #actionTabs {
+        background: #333333;
+        height: 100%;
+        margin: 0;
+        padding: 0;
+        display: grid;
+        grid-template-rows: auto 1fr;
+        font: 14px "Arial";    
+        overflow: hidden;
+    
+        .hoverIcon:hover {
+            opacity: 0.8;
+        }
+    
+        #header {
+            height: 30px;
+            font-size: 16px;
+            color: white;
+            background: #222222;
+            grid-row: 1;
+            text-align: center;
+            display: grid;
+            grid-template-columns: 30px 1fr 50px;        
+            -webkit-user-select: none; 
+            -moz-user-select: none;   
+            -ms-user-select: none;    
+            user-select: none;                
+    
+            #logo {
+                grid-column: 1; 
+                width: 24px;
+                height: 24px;
+                display: flex;
+                align-self: center;   
+                justify-self: center;
+            }        
+    
+            #back {
+                grid-column: 1; 
+                display: grid;
+                align-self: center;   
+                justify-self: center;
+                cursor: pointer;
+            }              
+    
+            #title {
+                grid-column: 2; 
+                display: grid;
+                align-items: center;   
+                text-align: center;
+            }
+    
+            #commands {
+                grid-column: 3; 
+                display: grid;
+                align-items: center;  
+                grid-template-columns: 1fr 1fr;   
+                
+                .expand {
+                    grid-column: 1;
+                    display: grid;
+                    align-items: center;   
+                    justify-items: center;
+                    cursor: pointer;     
+                }
+    
+                .close {
+                    grid-column: 2;
+                    display: grid;
+                    align-items: center;   
+                    justify-items: center;
+                    cursor: pointer;     
+                }        
+            }
+        }
+    
+        .tabs {
+            display: grid;
+            grid-row: 2;
+            grid-template-rows: 40px 1fr;
+            font: 14px "Arial";
+            overflow: hidden;
+    
+            .labels {
+                grid-row: 1;
+                display: flex;
+                align-items: center;
+                justify-items: center;
+                border-bottom: 1px solid #ffffff; 
+                margin: 0;
+                padding: 0;         
+    
+                .label {
+                    font-size: 24px;
+                    color: white;
+                    width: 40px;
+                    display: flex;
+                    align-content: center;
+                    justify-content: center;
+                    border: 1px solid transparent;            
+                    border-bottom: none;    
+                    background: #333333;
+                    padding: 5px;  
+                    height: 28px;
+                    cursor: pointer;
+    
+                    &.active {
+                        border-color: #ffffff;  
+                        border-bottom: 2px solid transparent;           
+                        margin-bottom: -2px;
+                    }
+                }
+            }
+    
+            .panes {
+                grid-row: 2;
+    
+                display: grid;
+                grid-template-rows: 100%;
+    
+                overflow: hidden;
+    
+                .infoMessage {
+                    opacity: 0.5;
+                    color: white;
+                    margin: 15px 5px 0px 5px;
+                                    
+                }
+    
+                .pane {
+                    color: white;
+    
+                    overflow-x: hidden;
+                    overflow-y: auto;
+                    height: 100%;
+    
+                    -webkit-user-select: none; 
+                    -moz-user-select: none;   
+                    -ms-user-select: none;    
+                    user-select: none;     
+    
+                    .underline {
+                        border-bottom: 0.5px solid rgba(255, 255, 255, 0.5);
+                    }
+                    
+                    .textureLinkLine {
+                        display: grid;
+                        grid-template-columns: auto 1fr;
+    
+                        .debug {
+                            grid-column: 1;
+                            margin-left: 5px;
+                            margin-right: 5px;
+                            display: grid;
+                            align-items: center; 
+                            justify-items: center;                          
+                            cursor: pointer;
+                            opacity: 0.5;
+    
+                            &.selected {
+                                opacity: 1.0;
+                            }
+                        }
+    
+                        .textLine {
+                            grid-column: 2;
+                        }
+                    }
+    
+                    .messageLine {
+                        text-align: center;
+                        font-size: 12px;
+                        font-style: italic;
+                        opacity: 0.6;
+                    }
+    
+                    .iconMessageLine {
+                        padding-left: $line-padding-left;
+                        height: 30px;
+                        display: grid;
+                        grid-template-columns: 30px 1fr;
+    
+                        .icon {
+                            grid-column: 1;
+                            display: grid;
+                            align-items: center;
+                            justify-items: center;
+                        }
+    
+                        .value {
+                            grid-column: 2;
+                            display: flex;
+                            align-items: center;
+                        }
+                    }
+    
+                    .textLine {
+                        padding-left: $line-padding-left;
+                        height: 30px;
+                        display: grid;
+                        grid-template-columns: 1fr auto;
+    
+                        .label {
+                            grid-column: 1;
+                            display: flex;
+                            align-items: center;
+                        }
+    
+                        .link-value {
+                            grid-column: 2;
+                            white-space: nowrap;
+                            text-overflow: ellipsis;
+                            overflow: hidden;
+                            text-align: end;
+                            opacity: 0.8;
+                            margin:5px;
+                            margin-top: 6px;
+                            max-width: 140px;
+                            text-decoration: underline;
+                            cursor: pointer;
+                        }
+    
+                        .value {
+                            grid-column: 2;
+                            white-space: nowrap;
+                            text-overflow: ellipsis;
+                            overflow: hidden;
+                            text-align: end;
+                            opacity: 0.8;
+                            margin:5px;
+                            margin-top: 6px;
+                            max-width: 200px;
+                            -webkit-user-select: text; 
+                            -moz-user-select: text;   
+                            -ms-user-select: text;    
+                            user-select: text;                
+    
+                            &.check {
+                                color: green;
+                            }
+    
+                            &.uncheck {
+                                color: red;
+                            }  
+                        }
+                    }
+    
+                    .textInputLine {
+                        padding-left: $line-padding-left;
+                        height: 30px;
+                        display: grid;
+                        grid-template-columns: 1fr 120px;
+    
+                        .label {
+                            grid-column: 1;
+                            display: flex;
+                            align-items: center;
+                        }
+    
+                        .value {                        
+                            display: flex;
+                            align-items: center;
+                            grid-column: 2;
+                            
+                            input {
+                                width: 110px;
+                            }
+                        }
+                    }
+    
+                    .buttonLine {
+                        height: 30px;
+                        display: grid;
+                        align-items: center;
+                        justify-items: stretch;
+    
+                        input[type="file"] {
+                            display: none;
+                        }
+    
+                        .file-upload {
+                            background: transparent;
+                            border: 1px solid rgb(51, 122, 183);
+                            margin: 0px 10px;
+                            color:white;
+                            padding: 4px 5px;
+                            opacity: 0.9;
+                            cursor: pointer;
+                            text-align: center;
+                        }
+    
+                        .file-upload:hover {
+                            opacity: 1.0;
+                        }
+    
+                        .file-upload:active {
+                            transform: scale(0.98);
+                            transform-origin: 0.5 0.5;
+                        }
+    
+                        button {
+                            background: #222222;
+                            border: 1px solid rgb(51, 122, 183);
+                            margin: 5px 10px 5px 10px;
+                            color:white;
+                            padding: 4px 5px;
+                            opacity: 0.9;
+                        }
+    
+                        button:hover {
+                            opacity: 1.0;
+                        }
+    
+                        button:active {
+                            background: #282828;
+                        }   
+                        
+                        button:focus {
+                            border: 1px solid rgb(51, 122, 183);
+                            outline: 0px;
+                        }  
+                    }
+    
+                    .radioLine {
+                        padding-left: $line-padding-left;
+                        height: 30px;
+                        display: grid;
+                        grid-template-columns: 1fr 24px;
+    
+                        .label {
+                            grid-column: 1;
+                            display: flex;
+                            align-items: center;
+                        }
+    
+                        .radioContainer {
+                            display: flex;
+                            align-items: center;
+    
+                            .radio {
+                                grid-column: 2;                        
+                                display: none;
+    
+                                &:checked + label:before {
+                                    border-color: rgb(51, 122, 183);
+                                }
+                                &:checked + label:after {
+                                    transform: scale(1);
+                                }                        
+                            }
+    
+                            .labelForRadio {
+                                display: inline-block;
+                                height: 14px;
+                                position: relative;
+                                padding: 0 24px;
+                                margin-bottom: 0;
+                                cursor: pointer;
+                                vertical-align: bottom;
+                                &:before, &:after {
+                                    position: absolute;            
+                                    content: '';  
+                                    border-radius: 50%;
+                                    transition: all .3s ease;
+                                    transition-property: transform, border-color;
+                                }
+                                &:before {
+                                    left: 0px;
+                                    top: 0;
+                                    width: 16px;
+                                    height: 16px;
+                                    border: 2px solid white;
+                                }
+                                &:after {
+                                    top: 6px;
+                                    left: 6px;
+                                    width: 8px;
+                                    height: 8px;
+                                    transform: scale(0);
+                                    background:rgb(51, 122, 183);
+                                }
+                            }
+                        }
+                    }
+    
+                    .vector3Line {
+                        padding-left:$line-padding-left;                    
+                        display: grid;
+    
+                        .firstLine {
+                            display: grid;
+                            grid-template-columns: 1fr auto 20px;
+                            height: 30px;
+    
+                            .label {
+                                grid-column: 1;
+                                display: flex;
+                                align-items: center;
+                            }
+    
+                            .vector {
+                                grid-column: 2;
+                                display: flex;
+                                align-items: center;
+                                text-align: right;
+                                opacity: 0.8;
+                            }
+    
+                            .expand {
+                                grid-column: 3;
+                                display: grid;
+                                align-items: center;
+                                justify-items: center;
+                                cursor: pointer;
+                            }
+                        }
+    
+                        .secondLine {
+                            display: grid;
+                            padding-right: 5px;  
+                            border-left: 1px solid rgb(51, 122, 183);
+    
+                            .numeric {
+                                display: grid;
+                                grid-template-columns: 1fr auto;
+                            }
+    
+                            .numeric-label {
+                                text-align: right;
+                                grid-column: 1;
+                                display: flex;
+                                align-items: center;                            
+                                justify-self: right;
+                                margin-right: 10px;                          
+                            }
+    
+                            .numeric-value {
+                                width: 120px;
+                                grid-column: 2;
+                                display: flex;
+                                align-items: center;  
+                                border: 1px solid  rgb(51, 122, 183);
+                            }                        
+                        }
+                    }
+    
+                    .checkBoxLine {
+                        padding-left: $line-padding-left;
+                        height: 30px;
+                        display: grid;
+                        grid-template-columns: 1fr auto;
+    
+    
+                        .label {
+                            grid-column: 1;
+                            display: flex;
+                            align-items: center;
+                        }
+    
+                        .checkBox {
+                            grid-column: 2;
+                            
+                            display: flex;
+                            align-items: center;
+    
+                            .lbl {
+                                position: relative;
+                                display: block;
+                                height: 14px;
+                                width: 34px;
+                                margin-right: 5px;
+                                background: #898989;
+                                border-radius: 100px;
+                                cursor: pointer;
+                                transition: all 0.3s ease;
+                            }
+    
+                            .lbl:after {
+                                position: absolute;
+                                left: 3px;
+                                top: 2px;
+                                display: block;
+                                width: 10px;
+                                height: 10px;
+                                border-radius: 100px;
+                                background: #fff;
+                                box-shadow: 0px 3px 3px rgba(0,0,0,0.05);
+                                content: '';
+                                transition: all 0.15s ease;
+                            }
+    
+                            .lbl:active:after { 
+                                transform: scale(1.15, 0.85); 
+                            }
+    
+                            .cbx:checked ~ label { 
+                                background: rgb(51, 122, 183);
+                            }
+    
+                            .cbx:checked ~ label:after {
+                                left: 20px;
+                                background: rgb(22, 73, 117);
+                            }
+    
+                            .hidden { 
+                                display: none; 
+                            }               
+                        }                    
+                    }                   
+    
+                    .textureLine {                   
+                        display: grid;
+                        grid-template-rows: 30px auto;
+    
+                        .control {
+                            margin-top: 2px;
+                            grid-row: 1;
+                            display: grid;
+                            grid-template-columns: 1fr 40px 40px 40px 40px 40px 1fr;
+    
+                            .red {
+                                grid-column: 2;
+                            }
+    
+                            .green {
+                                grid-column: 3;
+                            }
+    
+                            .blue {
+                                grid-column: 4;
+                            }
+    
+                            .alpha {
+                                grid-column: 5;
+                            }                        
+    
+                            .all {
+                                grid-column: 6;
+                            }                        
+                        }
+    
+                        .control3D {
+                            margin-top: 2px;
+                            grid-row: 1;
+                            display: grid;
+                            grid-template-columns: 1fr 40px 40px 40px 40px 40px 40px 1fr;
+    
+                            .px {
+                                grid-column: 2;
+                            }
+    
+                            .nx {
+                                grid-column: 3;
+                            }
+    
+                            .py {
+                                grid-column: 4;
+                            }
+    
+                            .ny {
+                                grid-column: 5;
+                            }   
+    
+                            .pz {
+                                grid-column: 6;
+                            }
+    
+                            .nz {
+                                grid-column: 7;
+                            }                     
+                        }                    
+    
+                        .command {
+                            border: 1px solid transparent;
+                            background:transparent;
+                            color: white;
+                        }
+    
+                        .selected {
+                            border: 1px solid rgb(51, 122, 183);
+                        }
+    
+                        .preview {
+                            grid-row: 2;
+                            display: grid;
+                            align-self: center;
+                            justify-self: center;
+                            height: 256px;
+                            width: 256px;
+                            margin-top: 5px;
+                            margin-bottom: 5px;
+                            border: 2px solid rgba(255, 255, 255, 0.4);
+                        }
+                    }
+    
+                    .gltf-extension-property {
+                        margin-left: 30px;
+                        border-left: 1px solid rgb(51, 122, 183);
+                    }
+    
+                    .floatLine {
+                        padding-left: $line-padding-left;
+                        height: 30px;
+                        display: grid;
+                        grid-template-columns: 1fr 120px;
+    
+    
+                        .label {
+                            grid-column: 1;
+                            display: flex;
+                            align-items: center;
+                        }
+    
+                        .value {
+                            grid-column: 2;
+                            
+                            display: flex;
+                            align-items: center;
+                            
+                            input {
+                                width: 110px;
+                            }
+                        }
+                    }
+    
+                    .sliderLine {
+                        padding-left: 2px;
+                        height: 30px;
+                        display: grid;
+                        grid-template-columns: 1fr auto;
+    
+                        .label {
+                            grid-column: 1;
+                            display: flex;
+                            align-items: center;
+                        }
+    
+                        .slider {
+                            grid-column: 2;
+                            margin-right: 5px;
+                            
+                            display: flex;
+                            align-items: center;
+    
+                            .range {
+                                -webkit-appearance: none;
+                                width: 120px;
+                                height: 6px;
+                                background: #d3d3d3;
+                                border-radius: 5px;
+                                outline: none;
+                                opacity: 0.7;
+                                -webkit-transition: .2s;
+                                transition: opacity .2s;
+                            }
+                            
+                            .range:hover {
+                                opacity: 1;
+                            }
+                            
+                            .range::-webkit-slider-thumb {
+                                -webkit-appearance: none;
+                                appearance: none;
+                                width: 14px;
+                                height: 14px;
+                                border-radius: 50%;
+                                background: rgb(51, 122, 183);
+                                cursor: pointer;
+                            }
+                            
+                            .range::-moz-range-thumb {
+                                width: 14px;
+                                height: 14px;
+                                border-radius: 50%;
+                                background: rgb(51, 122, 183);
+                                cursor: pointer;
+                            }
+                        }                    
+                    }       
+                    
+                    .color3Line {
+                        padding-left: $line-padding-left;
+                        display: grid;
+    
+                        .firstLine {
+                            height: 30px;
+                            display: grid;
+                            grid-template-columns: 1fr auto 20px 20px;
+    
+                            .label {
+                                grid-column: 1;
+                                display: flex;
+                                align-items: center;
+                            }
+    
+                            .color3 {
+                                grid-column: 2;
+                                
+                                display: flex;
+                                align-items: center;   
+    
+                                input[type="color"] {
+                                    -webkit-appearance: none;
+                                    border: 1px solid rgba(255, 255, 255, 0.5);
+                                    padding: 0;
+                                    width: 30px;
+                                    height: 20px;
+                                }
+                                input[type="color"]::-webkit-color-swatch-wrapper {
+                                    padding: 0;
+                                }
+                                input[type="color"]::-webkit-color-swatch {
+                                    border: none;
+                                }
+                                
+                                input {
+                                    margin-right: 5px;
+                                }
+                            }
+    
+                            .copy {
+                                grid-column: 3;
+                                display: grid;
+                                align-items: center;
+                                justify-items: center;
+                                cursor: pointer;
+                            }
+    
+                            .expand {
+                                grid-column: 4;
+                                display: grid;
+                                align-items: center;
+                                justify-items: center;
+                                cursor: pointer;
+                            }
+                        }   
+    
+                        .secondLine {
+                            display: grid;
+                            padding-right: 5px;  
+                            border-left: 1px solid rgb(51, 122, 183);
+    
+                            .numeric {
+                                display: grid;
+                                grid-template-columns: 1fr auto;
+                            }
+    
+                            .numeric-label {
+                                text-align: right;
+                                grid-column: 1;
+                                display: flex;
+                                align-items: center;                            
+                                justify-self: right;
+                                margin-right: 10px;                          
+                            }
+    
+                            .numeric-value {
+                                width: 120px;
+                                grid-column: 2;
+                                display: flex;
+                                align-items: center;  
+                                border: 1px solid  rgb(51, 122, 183);
+                            }                        
+                        }                  
+                    }     
+                    
+                    .listLine {
+                        padding-left: $line-padding-left;
+                        height: 30px;
+                        display: grid;
+                        grid-template-columns: 1fr auto;
+    
+    
+                        .label {
+                            grid-column: 1;
+                            display: flex;
+                            align-items: center;
+                        }
+    
+                        .options {
+                            grid-column: 2;
+                            
+                            display: flex;
+                            align-items: center;   
+                            margin-right: 5px;
+    
+                            select {
+                                width: 115px;
+                            }
+                        }                    
+                    }                   
+    
+                    .paneContainer {
+                        margin-top: 3px;
+                        display:grid;
+                        grid-template-rows: 100%;
+                        grid-template-columns: 100%;
+                        
+                        .paneList {
+                            border-left: 3px solid transparent;
+                        }
+    
+                        &:hover {  
+                            .paneList {                      
+                                border-left: 3px solid rgba(51, 122, 183, 0.8);
+                            }
+    
+                            .paneContainer-content {
+                                .header {
+                                    .title {   
+                                        border-left: 3px solid rgb(51, 122, 183);
+                                    }
+                                }
+                            }
+                        }
+                        
+                        .paneContainer-highlight-border {
+                            grid-row: 1;
+                            grid-column: 1;
+                            opacity: 1;
+                            border: 3px solid red;
+                            transition: opacity 250ms;
+                            pointer-events: none;
+                            
+                            &.transparent {
+                                opacity: 0;
+                            }
+                        }
+    
+                        .paneContainer-content {
+                            grid-row: 1;
+                            grid-column: 1;
+    
+                            .header {
+                                display: grid;
+                                grid-template-columns: 1fr auto;
+                                background: #555555;    
+                                height: 30px;   
+                                padding-right: 5px;                        
+                                cursor: pointer;
+                                
+                                .title {                                
+                                    border-left: 3px solid transparent;
+                                    padding-left: 5px;
+                                    grid-column: 1;
+                                    display: flex;
+                                    align-items: center;
+                                }
+    
+                                .collapse {
+                                    grid-column: 2;
+                                    display: flex;
+                                    align-items: center;  
+                                    justify-items: center;
+                                    transform-origin: center;
+    
+                                    &.closed {
+                                        transform: rotate(180deg);
+                                    }
+                                }                        
+                            }
+    
+                            .paneList > div:not(:last-child) {
+                                border-bottom: 0.5px solid rgba(255, 255, 255, 0.1);
+                            }
+    
+                            .fragment > div:not(:last-child)  {
+                                border-bottom: 0.5px solid rgba(255, 255, 255, 0.1);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+

+ 6 - 0
nodeEditor/src/globalState.ts

@@ -0,0 +1,6 @@
+import {NodeMaterial} from "babylonjs/Materials/Node/nodeMaterial"
+import {Nullable} from "babylonjs/types"
+export class GlobalState {
+    nodeMaterial?:NodeMaterial;
+    hostDocument?:Nullable<Document>;
+}

+ 1 - 0
nodeEditor/src/index.ts

@@ -0,0 +1 @@
+export * from "./nodeEditor";

+ 9 - 0
nodeEditor/src/legacy/legacy.ts

@@ -0,0 +1,9 @@
+import { NodeEditor } from "../index";
+
+var globalObject = (typeof global !== 'undefined') ? global : ((typeof window !== 'undefined') ? window : undefined);
+if (typeof globalObject !== "undefined") {
+    (<any>globalObject).BABYLON = (<any>globalObject).BABYLON || {};
+    (<any>globalObject).BABYLON.NodeEditor = NodeEditor;
+}
+
+export * from "../index";

+ 57 - 0
nodeEditor/src/nodeEditor.ts

@@ -0,0 +1,57 @@
+import * as React from "react";
+import * as ReactDOM from "react-dom";
+import { GlobalState } from './globalState';
+import { GraphEditor } from './components/graphEditor';
+import {NodeMaterial} from "babylonjs/Materials/Node/nodeMaterial"
+import {Popup} from "../src/sharedComponents/popup"
+/**
+ * Interface used to specify creation options for the node editor
+ */
+export interface INodeEditorOptions {
+    /**
+     * Defines the DOM element that will host the node editor
+     */
+    hostElement?: HTMLDivElement
+    nodeMaterial?: NodeMaterial
+}
+
+/**
+ * Class used to create a node editor
+ */
+export class NodeEditor {
+    /**
+     * Show the node editor
+     * @param options defines the options to use to configure the node editor
+     */
+    public static Show(options: INodeEditorOptions) {
+        if(!options.hostElement){
+            options.hostElement = Popup.CreatePopup("SCENE EXPLORER", "node-editor", 1000, 800)!;
+        }
+        let globalState = new GlobalState();
+        globalState.nodeMaterial = options.nodeMaterial
+        globalState.hostDocument = options.hostElement.ownerDocument
+
+        const graphEditor = React.createElement(GraphEditor, {
+            globalState: globalState
+        });
+
+        ReactDOM.render(graphEditor, options.hostElement);
+
+        // Close the popup window when the page is refreshed or scene is disposed
+        var popupWindow = (Popup as any)["node-editor"];
+        if(globalState.nodeMaterial && popupWindow){
+            globalState.nodeMaterial.getScene().onDisposeObservable.addOnce(()=>{
+                if(popupWindow){
+                    popupWindow.close();
+                }
+            })
+            window.onbeforeunload = function(event) {
+                var popupWindow = (Popup as any)["node-editor"];
+                if(popupWindow){
+                    popupWindow.close();
+                }
+            };
+        }
+    }
+}
+

+ 21 - 0
nodeEditor/src/sharedComponents/buttonLineComponent.tsx

@@ -0,0 +1,21 @@
+import * as React from "react";
+
+export interface IButtonLineComponentProps {
+    label: string;
+    onClick: () => void;
+}
+
+export class ButtonLineComponent extends React.Component<IButtonLineComponentProps> {
+    constructor(props: IButtonLineComponentProps) {
+        super(props);
+    }
+
+    render() {
+
+        return (
+            <div className="buttonLine">
+                <button onClick={() => this.props.onClick()}>{this.props.label}</button>
+            </div>
+        );
+    }
+}

+ 33 - 0
nodeEditor/src/sharedComponents/fileButtonLineComponent.tsx

@@ -0,0 +1,33 @@
+import * as React from "react";
+
+interface IFileButtonLineComponentProps {
+    label: string;
+    onClick: (file: File) => void;
+    accept: string;
+}
+
+export class FileButtonLineComponent extends React.Component<IFileButtonLineComponentProps> {
+    constructor(props: IFileButtonLineComponentProps) {
+        super(props);
+    }
+
+    onChange(evt: any) {
+        var files: File[] = evt.target.files;
+        if (files && files.length) {
+            this.props.onClick(files[0]);
+        }
+
+        evt.target.value = "";
+    }
+
+    render() {
+        return (
+            <div className="buttonLine">
+                <label htmlFor="file-upload" className="file-upload">
+                    {this.props.label}
+                </label>
+                <input ref="upload" id="file-upload" type="file" accept={this.props.accept} onChange={evt => this.onChange(evt)} />
+            </div>
+        );
+    }
+}

+ 120 - 0
nodeEditor/src/sharedComponents/lineContainerComponent.tsx

@@ -0,0 +1,120 @@
+import * as React from "react";
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faChevronDown } from '@fortawesome/free-solid-svg-icons';
+
+
+interface ILineContainerComponentProps {
+    globalState?: any; //
+    title: string;
+    children: any[] | any;
+    closed?: boolean;
+}
+
+export class LineContainerComponent extends React.Component<ILineContainerComponentProps, { isExpanded: boolean, isHighlighted: boolean }> {
+    private static _InMemoryStorage: { [key: string]: boolean };
+
+    constructor(props: ILineContainerComponentProps) {
+        super(props);
+
+        let initialState: boolean;
+
+        try {
+            if (LineContainerComponent._InMemoryStorage && LineContainerComponent._InMemoryStorage[this.props.title] !== undefined) {
+                initialState = LineContainerComponent._InMemoryStorage[this.props.title];
+            } else if (typeof (Storage) !== "undefined" && localStorage.getItem(this.props.title) !== null) {
+                initialState = localStorage.getItem(this.props.title) === "true";
+            } else {
+                initialState = !this.props.closed;
+            }
+        }
+        catch (e) {
+            LineContainerComponent._InMemoryStorage = {};
+            LineContainerComponent._InMemoryStorage[this.props.title] = !this.props.closed
+            initialState = !this.props.closed;
+        }
+
+        this.state = { isExpanded: initialState, isHighlighted: false };
+    }
+
+    switchExpandedState(): void {
+        const newState = !this.state.isExpanded;
+
+        try {
+            if (LineContainerComponent._InMemoryStorage) {
+                LineContainerComponent._InMemoryStorage[this.props.title] = newState;
+            } else if (typeof (Storage) !== "undefined") {
+                localStorage.setItem(this.props.title, newState ? "true" : "false");
+            }
+        }
+        catch (e) {
+            LineContainerComponent._InMemoryStorage = {};
+            LineContainerComponent._InMemoryStorage[this.props.title] = newState;
+        }
+
+        this.setState({ isExpanded: newState });
+    }
+
+    componentDidMount() {
+        if (this.props.globalState && !this.props.globalState.selectedLineContainerTitle) {
+            return;
+        }
+
+        if (this.props.globalState && this.props.globalState.selectedLineContainerTitle === this.props.title) {
+            setTimeout(() => {
+                this.props.globalState!.selectedLineContainerTitle = "";
+            });
+
+            this.setState({ isExpanded: true, isHighlighted: true });
+
+            window.setTimeout(() => {
+                this.setState({ isHighlighted: false });
+            }, 5000);
+        } else {
+            this.setState({isExpanded: false});
+        }        
+    }
+
+    renderHeader() {
+        const className = this.state.isExpanded ? "collapse" : "collapse closed";
+
+        return (
+            <div className="header" onClick={() => this.switchExpandedState()}>
+                <div className="title">
+                    {this.props.title}
+                </div>
+                <div className={className}>
+                    <FontAwesomeIcon icon={faChevronDown} />
+                </div>
+            </div>
+        );
+    }
+
+    render() {
+        if (!this.state.isExpanded) {
+            return (
+                <div className="paneContainer">
+                    <div className="paneContainer-content">
+                        {
+                            this.renderHeader()
+                        }
+                    </div>
+                </div>
+            );
+        }
+
+        return (
+            <div className="paneContainer">
+                <div className="paneContainer-content">
+                    {
+                        this.renderHeader()
+                    }
+                    <div className="paneList">
+                        {this.props.children}
+                    </div >
+                </div>
+                <div className={"paneContainer-highlight-border" + (!this.state.isHighlighted ? " transparent" : "")}>
+                </div>
+            </div>
+        );
+    }
+}

+ 69 - 0
nodeEditor/src/sharedComponents/numericInputComponent.tsx

@@ -0,0 +1,69 @@
+import * as React from "react";
+
+interface INumericInputComponentProps {
+    label: string;
+    value: number;
+    step?: number;
+    onChange: (value: number) => void;
+}
+
+export class NumericInputComponent extends React.Component<INumericInputComponentProps, { value: string }> {
+
+    static defaultProps = {
+        step: 1,
+    };
+
+    private _localChange = false;
+    constructor(props: INumericInputComponentProps) {
+        super(props);
+
+        this.state = { value: this.props.value.toFixed(3) }
+    }
+
+    shouldComponentUpdate(nextProps: INumericInputComponentProps, nextState: { value: string }) {
+        if (this._localChange) {
+            this._localChange = false;
+            return true;
+        }
+
+        if (nextProps.value.toString() !== nextState.value) {
+            nextState.value = nextProps.value.toFixed(3);
+            return true;
+        }
+        return false;
+    }
+
+    updateValue(evt: any) {
+        let value = evt.target.value;
+
+        if (/[^0-9\.\-]/g.test(value)) {
+            return;
+        }
+
+        let valueAsNumber = parseFloat(value);
+
+        this._localChange = true;
+        this.setState({ value: value });
+
+        if (isNaN(valueAsNumber)) {
+            return;
+        }
+
+        this.props.onChange(valueAsNumber);
+    }
+
+
+    render() {
+        return (
+            <div className="numeric">
+                {
+                    this.props.label &&
+                    <div className="numeric-label">
+                        {`${this.props.label}: `}
+                    </div>
+                }
+                <input type="number" step={this.props.step} className="numeric-input" value={this.state.value} onChange={evt => this.updateValue(evt)} />
+            </div>
+        )
+    }
+}

+ 70 - 0
nodeEditor/src/sharedComponents/popup.ts

@@ -0,0 +1,70 @@
+export class Popup {
+    public static CreatePopup(title: string, windowVariableName: string, width = 300, height = 800) {
+        const windowCreationOptionsList = {
+            width: width,
+            height: height,
+            top: (window.innerHeight - width) / 2 + window.screenY,
+            left: (window.innerWidth - height) / 2 + window.screenX
+        };
+    
+        var windowCreationOptions = Object.keys(windowCreationOptionsList)
+            .map(
+                (key) => key + '=' + (windowCreationOptionsList as any)[key]
+            )
+            .join(',');
+    
+        const popupWindow = window.open("", title, windowCreationOptions);
+        if (!popupWindow) {
+            return null;
+        }
+    
+        const parentDocument = popupWindow.document;
+    
+        parentDocument.title = title;
+        parentDocument.body.style.width = "100%";
+        parentDocument.body.style.height = "100%";
+        parentDocument.body.style.margin = "0";
+        parentDocument.body.style.padding = "0";
+    
+        let parentControl = parentDocument.createElement("div");
+        parentControl.style.width = "100%";
+        parentControl.style.height = "100%";
+        parentControl.style.margin = "0";
+        parentControl.style.padding = "0";
+    
+        popupWindow.document.body.appendChild(parentControl);
+    
+        this._CopyStyles(window.document, parentDocument);
+    
+        (this as any)[windowVariableName] = popupWindow;
+    
+        return parentControl;
+    }
+
+    private static _CopyStyles(sourceDoc: HTMLDocument, targetDoc: HTMLDocument) {
+        for (var index = 0; index < sourceDoc.styleSheets.length; index++) {
+            var styleSheet: any = sourceDoc.styleSheets[index];
+            try{
+                if (styleSheet.cssRules) { // for <style> elements
+                    const newStyleEl = sourceDoc.createElement('style');
+    
+                    for (var cssRule of styleSheet.cssRules) {
+                        // write the text of each rule into the body of the style element
+                        newStyleEl.appendChild(sourceDoc.createTextNode(cssRule.cssText));
+                    }
+    
+                    targetDoc.head!.appendChild(newStyleEl);
+                } else if (styleSheet.href) { // for <link> elements loading CSS from a URL
+                    const newLinkEl = sourceDoc.createElement('link');
+    
+                    newLinkEl.rel = 'stylesheet';
+                    newLinkEl.href = styleSheet.href;
+                    targetDoc.head!.appendChild(newLinkEl);
+                }
+            }catch(e){
+                console.log(e)
+            }
+            
+        }
+    }
+}

+ 6 - 0
nodeEditor/src/sharedComponents/propertyChangedEvent.ts

@@ -0,0 +1,6 @@
+export class PropertyChangedEvent {
+    public object: any;
+    public property: string;
+    public value: any;
+    public initialValue: any;
+}

+ 202 - 0
nodeEditor/src/sharedComponents/textureLineComponent.tsx

@@ -0,0 +1,202 @@
+import * as React from "react";
+
+import { Constants } from "babylonjs/Engines/constants";
+import { BaseTexture } from "babylonjs/Materials/Textures/baseTexture";
+import { Texture } from "babylonjs/Materials/Textures/texture";
+import { RenderTargetTexture } from "babylonjs/Materials/Textures/renderTargetTexture";
+import { PostProcess } from "babylonjs/PostProcesses/postProcess";
+import { PassPostProcess, PassCubePostProcess } from "babylonjs/PostProcesses/passPostProcess";
+
+interface ITextureLineComponentProps {
+    texture: BaseTexture;
+    width: number;
+    height: number;
+    globalState?: any;
+    hideChannelSelect?:boolean;
+}
+
+export class TextureLineComponent extends React.Component<ITextureLineComponentProps, { displayRed: boolean, displayGreen: boolean, displayBlue: boolean, displayAlpha: boolean, face: number }> {
+    constructor(props: ITextureLineComponentProps) {
+        super(props);
+
+        this.state = {
+            displayRed: true,
+            displayGreen: true,
+            displayBlue: true,
+            displayAlpha: true,
+            face: 0
+        };
+    }
+
+    shouldComponentUpdate(nextProps: ITextureLineComponentProps, nextState: { displayRed: boolean, displayGreen: boolean, displayBlue: boolean, displayAlpha: boolean, face: number }): boolean {
+        return (nextProps.texture !== this.props.texture || nextState.displayRed !== this.state.displayRed || nextState.displayGreen !== this.state.displayGreen || nextState.displayBlue !== this.state.displayBlue || nextState.displayAlpha !== this.state.displayAlpha || nextState.face !== this.state.face);
+    }
+
+    componentDidMount() {
+        this.updatePreview();
+    }
+
+    componentDidUpdate() {
+        this.updatePreview();
+    }
+
+    updatePreview() {
+        var texture = this.props.texture;
+        if(!texture.isReady() && texture._texture){
+            texture._texture.onLoadedObservable.addOnce(()=>{
+                this.updatePreview();
+            })
+        }
+        var scene = texture.getScene()!;
+        var engine = scene.getEngine();
+        var size = texture.getSize();
+        var ratio = size.width / size.height;
+        var width = this.props.width;
+        var height = (width / ratio) | 1;
+
+        let passPostProcess: PostProcess;
+
+        if (!texture.isCube) {
+            passPostProcess = new PassPostProcess("pass", 1, null, Texture.NEAREST_SAMPLINGMODE, engine, false, Constants.TEXTURETYPE_UNSIGNED_INT);
+        } else {
+            var passCubePostProcess = new PassCubePostProcess("pass", 1, null, Texture.NEAREST_SAMPLINGMODE, engine, false, Constants.TEXTURETYPE_UNSIGNED_INT);
+            passCubePostProcess.face = this.state.face;
+
+            passPostProcess = passCubePostProcess;
+        }
+
+        if (!passPostProcess.getEffect().isReady()) {
+            // Try again later
+            passPostProcess.dispose();
+
+            setTimeout(() => this.updatePreview(), 250);
+
+            return;
+        }
+
+        const previewCanvas = this.refs.canvas as HTMLCanvasElement;
+
+        if(this.props.globalState){
+            this.props.globalState.blockMutationUpdates = true;
+        }
+        
+        let rtt = new RenderTargetTexture(
+            "temp",
+            { width: width, height: height },
+            scene, false);
+
+        passPostProcess.onApply = function(effect) {
+            effect.setTexture("textureSampler", texture);
+        };
+
+        let internalTexture = rtt.getInternalTexture();
+
+        if (internalTexture) {
+            scene.postProcessManager.directRender([passPostProcess], internalTexture);
+
+            // Read the contents of the framebuffer
+            var numberOfChannelsByLine = width * 4;
+            var halfHeight = height / 2;
+
+            //Reading datas from WebGL
+            var data = engine.readPixels(0, 0, width, height);
+
+            if (!texture.isCube) {
+                if (!this.state.displayRed || !this.state.displayGreen || !this.state.displayBlue) {
+                    for (var i = 0; i < width * height * 4; i += 4) {
+
+                        if (!this.state.displayRed) {
+                            data[i] = 0;
+                        }
+
+                        if (!this.state.displayGreen) {
+                            data[i + 1] = 0;
+                        }
+
+                        if (!this.state.displayBlue) {
+                            data[i + 2] = 0;
+                        }
+
+                        if (this.state.displayAlpha) {
+                            var alpha = data[i + 2];
+                            data[i] = alpha;
+                            data[i + 1] = alpha;
+                            data[i + 2] = alpha;
+                            data[i + 2] = 0;
+                        }
+                    }
+                }
+            }
+
+            //To flip image on Y axis.
+            if ((texture as Texture).invertY || texture.isCube) {
+                for (var i = 0; i < halfHeight; i++) {
+                    for (var j = 0; j < numberOfChannelsByLine; j++) {
+                        var currentCell = j + i * numberOfChannelsByLine;
+                        var targetLine = height - i - 1;
+                        var targetCell = j + targetLine * numberOfChannelsByLine;
+
+                        var temp = data[currentCell];
+                        data[currentCell] = data[targetCell];
+                        data[targetCell] = temp;
+                    }
+                }
+            }
+
+            previewCanvas.width = width;
+            previewCanvas.height = height;
+            var context = previewCanvas.getContext('2d');
+
+            if (context) {
+                // Copy the pixels to the preview canvas
+                var imageData = context.createImageData(width, height);
+                var castData = imageData.data;
+                castData.set(data);
+                context.putImageData(imageData, 0, 0);
+            }
+
+            // Unbind
+            engine.unBindFramebuffer(internalTexture);
+        }
+
+        rtt.dispose();
+        passPostProcess.dispose();
+
+        previewCanvas.style.height = height + "px";
+        if(this.props.globalState){
+            this.props.globalState.blockMutationUpdates = false;
+        }
+        
+    }
+
+    render() {
+        var texture = this.props.texture;
+
+        return (
+            <div className="textureLine">
+                {
+                    !this.props.hideChannelSelect && texture.isCube &&
+                    <div className="control3D">
+                        <button className={this.state.face === 0 ? "px command selected" : "px command"} onClick={() => this.setState({ face: 0 })}>PX</button>
+                        <button className={this.state.face === 1 ? "nx command selected" : "nx command"} onClick={() => this.setState({ face: 1 })}>NX</button>
+                        <button className={this.state.face === 2 ? "py command selected" : "py command"} onClick={() => this.setState({ face: 2 })}>PY</button>
+                        <button className={this.state.face === 3 ? "ny command selected" : "ny command"} onClick={() => this.setState({ face: 3 })}>NY</button>
+                        <button className={this.state.face === 4 ? "pz command selected" : "pz command"} onClick={() => this.setState({ face: 4 })}>PZ</button>
+                        <button className={this.state.face === 5 ? "nz command selected" : "nz command"} onClick={() => this.setState({ face: 5 })}>NZ</button>
+                    </div>
+                }
+                {
+                    !this.props.hideChannelSelect && !texture.isCube &&
+                    <div className="control">
+                        <button className={this.state.displayRed && !this.state.displayGreen ? "red command selected" : "red command"} onClick={() => this.setState({ displayRed: true, displayGreen: false, displayBlue: false, displayAlpha: false })}>R</button>
+                        <button className={this.state.displayGreen && !this.state.displayBlue ? "green command selected" : "green command"} onClick={() => this.setState({ displayRed: false, displayGreen: true, displayBlue: false, displayAlpha: false })}>G</button>
+                        <button className={this.state.displayBlue && !this.state.displayAlpha ? "blue command selected" : "blue command"} onClick={() => this.setState({ displayRed: false, displayGreen: false, displayBlue: true, displayAlpha: false })}>B</button>
+                        <button className={this.state.displayAlpha && !this.state.displayRed ? "alpha command selected" : "alpha command"} onClick={() => this.setState({ displayRed: false, displayGreen: false, displayBlue: false, displayAlpha: true })}>A</button>
+                        <button className={this.state.displayRed && this.state.displayGreen ? "all command selected" : "all command"} onClick={() => this.setState({ displayRed: true, displayGreen: true, displayBlue: true, displayAlpha: true })}>ALL</button>
+                    </div>
+                }
+                <canvas ref="canvas" className="preview" />
+            </div>
+        );
+    }
+}

+ 114 - 0
nodeEditor/src/sharedComponents/vector2LineComponent.tsx

@@ -0,0 +1,114 @@
+import * as React from "react";
+import { Vector2 } from "babylonjs/Maths/math";
+import { Observable } from "babylonjs/Misc/observable";
+
+import { NumericInputComponent } from "./numericInputComponent";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faMinus, faPlus } from "@fortawesome/free-solid-svg-icons";
+import { PropertyChangedEvent } from "./propertyChangedEvent";
+
+interface IVector2LineComponentProps {
+    label: string;
+    target: any;
+    propertyName: string;
+    step?: number;
+    onChange?: (newvalue: Vector2) => void;
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
+}
+
+export class Vector2LineComponent extends React.Component<IVector2LineComponentProps, { isExpanded: boolean, value: Vector2 }> {
+
+    static defaultProps = {
+        step: 0.001, // cm
+    };
+
+    private _localChange = false;
+
+    constructor(props: IVector2LineComponentProps) {
+        super(props);
+
+        this.state = { isExpanded: false, value: this.props.target[this.props.propertyName].clone() }
+    }
+
+    shouldComponentUpdate(nextProps: IVector2LineComponentProps, nextState: { isExpanded: boolean, value: Vector2 }) {
+        const nextPropsValue = nextProps.target[nextProps.propertyName];
+
+        if (!nextPropsValue.equals(nextState.value) || this._localChange) {
+            nextState.value = nextPropsValue.clone();
+            this._localChange = false;
+            return true;
+        }
+        return false;
+    }
+
+    switchExpandState() {
+        this._localChange = true;
+        this.setState({ isExpanded: !this.state.isExpanded });
+    }
+
+    raiseOnPropertyChanged(previousValue: Vector2) {
+        if (this.props.onChange) {
+            this.props.onChange(this.state.value);
+        }
+
+        if (!this.props.onPropertyChangedObservable) {
+            return;
+        }
+        this.props.onPropertyChangedObservable.notifyObservers({
+            object: this.props.target,
+            property: this.props.propertyName,
+            value: this.state.value,
+            initialValue: previousValue
+        });
+    }
+
+    updateStateX(value: number) {
+        this._localChange = true;
+
+        const store = this.state.value.clone();
+        this.props.target[this.props.propertyName].x = value;
+        this.state.value.x = value;
+        this.setState({ value: this.state.value });
+
+        this.raiseOnPropertyChanged(store);
+    }
+
+    updateStateY(value: number) {
+        this._localChange = true;
+
+        const store = this.state.value.clone();
+        this.props.target[this.props.propertyName].y = value;
+        this.state.value.y = value;
+        this.setState({ value: this.state.value });
+
+        this.raiseOnPropertyChanged(store);
+    }
+
+    render() {
+        const chevron = this.state.isExpanded ? <FontAwesomeIcon icon={faMinus} /> : <FontAwesomeIcon icon={faPlus} />
+
+        return (
+            <div className="vector3Line">
+                <div className="firstLine">
+                    <div className="label">
+                        {this.props.label}
+                    </div>
+                    <div className="vector">
+                        {`X: ${this.state.value.x.toFixed(2)}, Y: ${this.state.value.y.toFixed(2)}`}
+
+                    </div>
+                    <div className="expand hoverIcon" onClick={() => this.switchExpandState()} title="Expand">
+                        {chevron}
+                    </div>
+                </div>
+                {
+                    this.state.isExpanded &&
+                    <div className="secondLine">
+                        <NumericInputComponent label="x" step={this.props.step} value={this.state.value.x} onChange={value => this.updateStateX(value)} />
+                        <NumericInputComponent label="y" step={this.props.step} value={this.state.value.y} onChange={value => this.updateStateY(value)} />
+                    </div>
+                }
+            </div>
+        );
+    }
+}

+ 124 - 0
nodeEditor/src/sharedComponents/vector3LineComponent.tsx

@@ -0,0 +1,124 @@
+import * as React from "react";
+import { Vector3 } from "babylonjs/Maths/math";
+import { Observable } from "babylonjs/Misc/observable";
+
+import { NumericInputComponent } from "./numericInputComponent";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faMinus, faPlus } from "@fortawesome/free-solid-svg-icons";
+import { PropertyChangedEvent } from "./propertyChangedEvent";
+
+interface IVector3LineComponentProps {
+    label: string;
+    target: any;
+    propertyName: string;
+    step?: number;
+    onChange?: (newvalue: Vector3) => void;
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
+}
+
+export class Vector3LineComponent extends React.Component<IVector3LineComponentProps, { isExpanded: boolean, value: Vector3 }> {
+
+    static defaultProps = {
+        step: 0.001, // cm
+    };
+
+    private _localChange = false;
+
+    constructor(props: IVector3LineComponentProps) {
+        super(props);
+
+        this.state = { isExpanded: false, value: this.props.target[this.props.propertyName].clone() }
+    }
+
+    shouldComponentUpdate(nextProps: IVector3LineComponentProps, nextState: { isExpanded: boolean, value: Vector3 }) {
+        const nextPropsValue = nextProps.target[nextProps.propertyName];
+
+        if (!nextPropsValue.equals(nextState.value) || this._localChange) {
+            nextState.value = nextPropsValue.clone();
+            this._localChange = false;
+            return true;
+        }
+        return false;
+    }
+
+    switchExpandState() {
+        this._localChange = true;
+        this.setState({ isExpanded: !this.state.isExpanded });
+    }
+
+    raiseOnPropertyChanged(previousValue: Vector3) {
+        if (this.props.onChange) {
+            this.props.onChange(this.state.value);
+        }
+
+        if (!this.props.onPropertyChangedObservable) {
+            return;
+        }
+        this.props.onPropertyChangedObservable.notifyObservers({
+            object: this.props.target,
+            property: this.props.propertyName,
+            value: this.state.value,
+            initialValue: previousValue
+        });
+    }
+
+    updateVector3() {
+        const store = this.props.target[this.props.propertyName].clone();
+        this.props.target[this.props.propertyName] = this.state.value;
+
+        this.setState({ value: store });
+
+        this.raiseOnPropertyChanged(store);
+    }
+
+
+    updateStateX(value: number) {
+        this._localChange = true;
+
+        this.state.value.x = value;
+        this.updateVector3();
+    }
+
+    updateStateY(value: number) {
+        this._localChange = true;
+
+        this.state.value.y = value;
+        this.updateVector3();
+    }
+
+    updateStateZ(value: number) {
+        this._localChange = true;
+
+        this.state.value.z = value;
+        this.updateVector3();
+    }
+
+    render() {
+        const chevron = this.state.isExpanded ? <FontAwesomeIcon icon={faMinus} /> : <FontAwesomeIcon icon={faPlus} />
+
+        return (
+            <div className="vector3Line">
+                <div className="firstLine">
+                    <div className="label">
+                        {this.props.label}
+                    </div>
+                    <div className="vector">
+                        {`X: ${this.state.value.x.toFixed(2)}, Y: ${this.state.value.y.toFixed(2)}, Z: ${this.state.value.z.toFixed(2)}`}
+
+                    </div>
+                    <div className="expand hoverIcon" onClick={() => this.switchExpandState()} title="Expand">
+                        {chevron}
+                    </div>
+                </div>
+                {
+                    this.state.isExpanded &&
+                    <div className="secondLine">
+                        <NumericInputComponent label="x" step={this.props.step} value={this.state.value.x} onChange={value => this.updateStateX(value)} />
+                        <NumericInputComponent label="y" step={this.props.step} value={this.state.value.y} onChange={value => this.updateStateY(value)} />
+                        <NumericInputComponent label="z" step={this.props.step} value={this.state.value.z} onChange={value => this.updateStateZ(value)} />
+                    </div>
+                }
+            </div>
+        );
+    }
+}

+ 28 - 0
nodeEditor/tsconfig.json

@@ -0,0 +1,28 @@
+{
+    "extends": "../tsconfigRules",
+    "compilerOptions": {
+        "jsx": "react",
+        "baseUrl": "./src/",
+        "rootDir": "./src/",
+        "paths": {
+            "babylonjs-gui/*": [
+                "../../dist/preview release/gui/babylon.gui.module.d.ts"
+            ],
+            "babylonjs-gltf2interface/*": [
+                "../../dist/preview release/glTF2Interface/babylon.glTF2Interface.d.ts"
+            ],
+            "babylonjs-loaders/*": [
+                "../../dist/preview release/loaders/babylonjs.loaders.module.d.ts"
+            ],
+            "babylonjs-serializers/*": [
+                "../../dist/preview release/serializers/babylonjs.serializers.module.d.ts"
+            ],
+            "babylonjs/*": [
+                "../../dist/preview release/babylon.module.d.ts"
+            ]
+        }
+    },
+    "exclude": [
+        "../../inspector"
+    ]
+}

+ 34 - 0
nodeEditor/webpack.config.js

@@ -0,0 +1,34 @@
+const path = require("path");
+const MiniCssExtractPlugin = require("mini-css-extract-plugin");
+const babylonWebpackConfig = require('../Tools/WebpackPlugins/babylonWebpackConfig');
+
+var config = babylonWebpackConfig({
+    module: "nodeEditor",
+    resolve: {
+        extensions: [".js", '.ts', ".tsx"],
+    },
+    moduleRules: [
+        {
+            test: /\.scss$/,
+            use: [
+                // fallback to style-loader in development
+                process.env.NODE_ENV !== 'production' ? 'style-loader' : MiniCssExtractPlugin.loader,
+                "css-loader",
+                "sass-loader"
+            ]
+        },
+        {
+            test: /\.css$/,
+            use: ['style-loader', 'css-loader']
+        }],
+    plugins: [
+        new MiniCssExtractPlugin({
+            // Options similar to the same options in webpackOptions.output
+            // both options are optional
+            filename: "[name].css",
+            chunkFilename: "[id].css"
+        })
+    ]
+});
+
+module.exports = config;

+ 2 - 1
package.json

@@ -37,6 +37,7 @@
     "readmeFilename": "README.md",
     "readmeFilename": "README.md",
     "dependencies": {},
     "dependencies": {},
     "devDependencies": {
     "devDependencies": {
+        "storm-react-diagrams": "^5.2.1",
         "@fortawesome/fontawesome-svg-core": "~1.2.8",
         "@fortawesome/fontawesome-svg-core": "~1.2.8",
         "@fortawesome/free-regular-svg-icons": "~5.4.1",
         "@fortawesome/free-regular-svg-icons": "~5.4.1",
         "@fortawesome/free-solid-svg-icons": "~5.4.1",
         "@fortawesome/free-solid-svg-icons": "~5.4.1",
@@ -105,4 +106,4 @@
         "xhr2": "^0.1.4",
         "xhr2": "^0.1.4",
         "xmlbuilder": "8.2.2"
         "xmlbuilder": "8.2.2"
     }
     }
-}
+}

+ 2 - 2
src/Loading/sceneLoader.ts

@@ -584,7 +584,7 @@ export class SceneLoader {
                 onProgress(event);
                 onProgress(event);
             }
             }
             catch (e) {
             catch (e) {
-                errorHandler("Error in onProgress callback", e);
+                errorHandler("Error in onProgress callback: " + e, e);
             }
             }
         } : undefined;
         } : undefined;
 
 
@@ -596,7 +596,7 @@ export class SceneLoader {
                     onSuccess(meshes, particleSystems, skeletons, animationGroups);
                     onSuccess(meshes, particleSystems, skeletons, animationGroups);
                 }
                 }
                 catch (e) {
                 catch (e) {
-                    errorHandler("Error in onSuccess callback", e);
+                    errorHandler("Error in onSuccess callback: " + e, e);
                 }
                 }
             }
             }
 
 

+ 143 - 0
src/Materials/Node/Blocks/Dual/fogBlock.ts

@@ -0,0 +1,143 @@
+import { NodeMaterialBlock } from '../../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../../nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialBuildState } from '../../nodeMaterialBuildState';
+import { NodeMaterialWellKnownValues } from '../../nodeMaterialWellKnownValues';
+import { NodeMaterialBlockTargets } from '../../nodeMaterialBlockTargets';
+import { Mesh } from '../../../../Meshes/mesh';
+import { Effect } from '../../../effect';
+import { NodeMaterialConnectionPoint } from '../../nodeMaterialBlockConnectionPoint';
+import { AbstractMesh } from '../../../../Meshes/abstractMesh';
+import { MaterialHelper } from '../../../materialHelper';
+import { NodeMaterial, NodeMaterialDefines } from '../../nodeMaterial';
+
+/**
+ * Block used to add support for scene fog
+ */
+export class FogBlock extends NodeMaterialBlock {
+    /**
+     * Create a new FogBlock
+     * @param name defines the block name
+     */
+    public constructor(name: string) {
+        super(name, NodeMaterialBlockTargets.VertexAndFragment, true);
+
+        // Vertex
+        this.registerInput("worldPosition", NodeMaterialBlockConnectionPointTypes.Vector4, false, NodeMaterialBlockTargets.Vertex);
+        this.registerInput("view", NodeMaterialBlockConnectionPointTypes.Matrix, false, NodeMaterialBlockTargets.Vertex);
+
+        this.registerOutput("vFogDistance", NodeMaterialBlockConnectionPointTypes.Vector3, NodeMaterialBlockTargets.Vertex);
+
+        // Fragment
+        this.registerInput("color", NodeMaterialBlockConnectionPointTypes.Color3OrColor4, false, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("fogColor", NodeMaterialBlockConnectionPointTypes.Color3, false, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("fogParameters", NodeMaterialBlockConnectionPointTypes.Vector4, false, NodeMaterialBlockTargets.Fragment);
+
+        this.registerOutput("output", NodeMaterialBlockConnectionPointTypes.Color3, NodeMaterialBlockTargets.Fragment);
+    }
+
+    /**
+     * Gets the current class name
+     * @returns the class name
+     */
+    public getClassName() {
+        return "FogBlock";
+    }
+
+    /**
+     * Gets the world position input component
+     */
+    public get worldPosition(): NodeMaterialConnectionPoint {
+        return this._inputs[0];
+    }
+
+    /**
+     * Gets the view input component
+     */
+    public get view(): NodeMaterialConnectionPoint {
+        return this._inputs[1];
+    }
+
+    /**
+     * Gets the color input component
+     */
+    public get color(): NodeMaterialConnectionPoint {
+        return this._inputs[2];
+    }
+
+    /**
+     * Gets the fog color input component
+     */
+    public get fogColor(): NodeMaterialConnectionPoint {
+        return this._inputs[3];
+    }
+
+    /**
+     * Gets the for parameter input component
+     */
+    public get fogParameters(): NodeMaterialConnectionPoint {
+        return this._inputs[4];
+    }
+
+    public autoConfigure() {
+        if (!this.view.connectedPoint) {
+            this.view.setAsWellKnownValue(NodeMaterialWellKnownValues.View);
+        }
+        if (!this.fogColor.connectedPoint) {
+            this.fogColor.setAsWellKnownValue(NodeMaterialWellKnownValues.BlockBased);
+        }
+        if (!this.fogParameters.connectedPoint) {
+            this.fogParameters.setAsWellKnownValue(NodeMaterialWellKnownValues.BlockBased);
+        }
+        this._outputs[0].isVarying = true;
+    }
+
+    public prepareDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines) {
+        let scene = mesh.getScene();
+        defines.setValue("FOG", nodeMaterial.fogEnabled && MaterialHelper.GetFogState(mesh, scene));
+    }
+
+    public bind(effect: Effect, nodeMaterial: NodeMaterial, mesh?: Mesh) {
+        if (!mesh) {
+            return;
+        }
+
+        const scene = mesh.getScene();
+        effect.setColor3("fogColor", scene.fogColor);
+        effect.setFloat4("fogParameters", scene.fogMode, scene.fogStart, scene.fogEnd, scene.fogDensity);
+    }
+
+    protected _buildBlock(state: NodeMaterialBuildState) {
+        super._buildBlock(state);
+
+        state.sharedData.blocksWithDefines.push(this);
+
+        if (state.target === NodeMaterialBlockTargets.Fragment) {
+            state._emitFunctionFromInclude("fogFragmentDeclaration", `//${this.name}`, {
+                removeUniforms: true,
+                removeVaryings: true,
+                removeIfDef: false,
+                replaceStrings: [{ search: /float CalcFogFactor\(\)/, replace: "float CalcFogFactor(vec3 vFogDistance, vec4 vFogInfos)" }]
+            });
+
+            let tempFogVariablename = state._getFreeVariableName("fog");
+            let color = this.color;
+            let fogColor = this.fogColor;
+            let fogParameters = this.fogParameters;
+            let output = this._outputs[1];
+            let vFogDistance = this._outputs[0];
+
+            state.compilationString += `#ifdef FOG\r\n`;
+            state.compilationString += `float ${tempFogVariablename} = CalcFogFactor(${vFogDistance.associatedVariableName}, ${fogParameters.associatedVariableName});\r\n`;
+            state.compilationString += this._declareOutput(output, state) + ` = ${tempFogVariablename} * ${color.associatedVariableName}.rgb + (1.0 - ${tempFogVariablename}) * ${fogColor.associatedVariableName};\r\n`;
+            state.compilationString += `#else\r\n${this._declareOutput(output, state)} =  ${color.associatedVariableName}.rgb;\r\n`;
+            state.compilationString += `#endif\r\n`;
+        } else {
+            let worldPos = this.worldPosition;
+            let view = this.view;
+            let vFogDistance = this._outputs[0];
+            state.compilationString += this._declareOutput(vFogDistance, state) + ` = (${view.associatedVariableName} * ${worldPos.associatedVariableName}).xyz;\r\n`;
+        }
+
+        return this;
+    }
+}

+ 2 - 0
src/Materials/Node/Blocks/Dual/index.ts

@@ -0,0 +1,2 @@
+
+export * from "./fogBlock";

+ 51 - 0
src/Materials/Node/Blocks/Fragment/alphaTestBlock.ts

@@ -0,0 +1,51 @@
+import { NodeMaterialBlock } from '../../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../../nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialBuildState } from '../../nodeMaterialBuildState';
+import { NodeMaterialBlockTargets } from '../../nodeMaterialBlockTargets';
+import { NodeMaterialConnectionPoint } from '../../nodeMaterialBlockConnectionPoint';
+
+/**
+ * Block used to add an alpha test in the fragment shader
+ */
+export class AlphaTestBlock extends NodeMaterialBlock {
+
+    /**
+     * Gets or sets the alpha value where alpha testing happens
+     */
+    public alphaCutOff = 0.4;
+
+    /**
+     * Create a new AlphaTestBlock
+     * @param name defines the block name
+     */
+    public constructor(name: string) {
+        super(name, NodeMaterialBlockTargets.Fragment);
+
+        this.registerInput("color", NodeMaterialBlockConnectionPointTypes.Color4);
+    }
+
+    /**
+     * Gets the current class name
+     * @returns the class name
+     */
+    public getClassName() {
+        return "AlphaTestBlock";
+    }
+
+    /**
+     * Gets the color input component
+     */
+    public get color(): NodeMaterialConnectionPoint {
+        return this._inputs[0];
+    }
+
+    protected _buildBlock(state: NodeMaterialBuildState) {
+        super._buildBlock(state);
+
+        state.sharedData.hints.needAlphaTesting = true;
+
+        state.compilationString += `if (${this.color.associatedVariableName}.a < ${this.alphaCutOff}) discard;\r\n`;
+
+        return this;
+    }
+}

+ 54 - 0
src/Materials/Node/Blocks/Fragment/fragmentOutputBlock.ts

@@ -0,0 +1,54 @@
+import { NodeMaterialBlock } from '../../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../../nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialBuildState } from '../../nodeMaterialBuildState';
+import { NodeMaterialBlockTargets } from '../../nodeMaterialBlockTargets';
+import { NodeMaterialConnectionPoint } from '../../nodeMaterialBlockConnectionPoint';
+
+/**
+ * Block used to output the final color
+ */
+export class FragmentOutputBlock extends NodeMaterialBlock {
+    /**
+     * Gets or sets a boolean indicating if this block will output an alpha value
+     */
+    public alphaBlendingEnabled = false;
+    /**
+     * Create a new FragmentOutputBlock
+     * @param name defines the block name
+     */
+    public constructor(name: string) {
+        super(name, NodeMaterialBlockTargets.Fragment, true);
+
+        this.registerInput("color", NodeMaterialBlockConnectionPointTypes.Color3OrColor4);
+    }
+
+    /**
+     * Gets the current class name
+     * @returns the class name
+     */
+    public getClassName() {
+        return "FragmentOutputBlock";
+    }
+
+    /**
+     * Gets the color input component
+     */
+    public get color(): NodeMaterialConnectionPoint {
+        return this._inputs[0];
+    }
+
+    protected _buildBlock(state: NodeMaterialBuildState) {
+        super._buildBlock(state);
+
+        let input = this.color;
+        state.sharedData.hints.needAlphaBlending = this.alphaBlendingEnabled;
+
+        if (input.connectedPoint!.type === NodeMaterialBlockConnectionPointTypes.Color3) {
+            state.compilationString += `gl_FragColor = vec4(${input.associatedVariableName}, 1.0);\r\n`;
+        } else {
+            state.compilationString += `gl_FragColor = ${input.associatedVariableName};\r\n`;
+        }
+
+        return this;
+    }
+}

+ 134 - 0
src/Materials/Node/Blocks/Fragment/imageProcessingBlock.ts

@@ -0,0 +1,134 @@
+import { NodeMaterialBlock } from '../../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../../nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialBuildState } from '../../nodeMaterialBuildState';
+import { NodeMaterialBlockTargets } from '../../nodeMaterialBlockTargets';
+import { NodeMaterialConnectionPoint } from '../../nodeMaterialBlockConnectionPoint';
+import { AbstractMesh } from '../../../../Meshes/abstractMesh';
+import { NodeMaterial, NodeMaterialDefines } from '../../nodeMaterial';
+import { Effect } from '../../../effect';
+import { Mesh } from '../../../../Meshes/mesh';
+
+/**
+ * Block used to add image processing support to fragment shader
+ */
+export class ImageProcessingBlock extends NodeMaterialBlock {
+    /**
+     * Create a new ImageProcessingBlock
+     * @param name defines the block name
+     */
+    public constructor(name: string) {
+        super(name, NodeMaterialBlockTargets.Fragment);
+
+        this.registerInput("color", NodeMaterialBlockConnectionPointTypes.Color3OrColor4);
+        this.registerOutput("output", NodeMaterialBlockConnectionPointTypes.Color4);
+    }
+
+    /**
+     * Gets the current class name
+     * @returns the class name
+     */
+    public getClassName() {
+        return "ImageProcessingBlock";
+    }
+
+    /**
+     * Gets the color input component
+     */
+    public get color(): NodeMaterialConnectionPoint {
+        return this._inputs[0];
+    }
+
+    /**
+     * Initialize the block and prepare the context for build
+     * @param state defines the state that will be used for the build
+     */
+    public initialize(state: NodeMaterialBuildState) {
+        state._excludeVariableName("exposureLinear");
+        state._excludeVariableName("contrast");
+        state._excludeVariableName("vInverseScreenSize");
+        state._excludeVariableName("vignetteSettings1");
+        state._excludeVariableName("vignetteSettings2");
+        state._excludeVariableName("vCameraColorCurveNegative");
+        state._excludeVariableName("vCameraColorCurveNeutral");
+        state._excludeVariableName("vCameraColorCurvePositive");
+        state._excludeVariableName("txColorTransform");
+        state._excludeVariableName("colorTransformSettings");
+    }
+
+    public isReady(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines) {
+        if (defines._areImageProcessingDirty && nodeMaterial.imageProcessingConfiguration) {
+            if (!nodeMaterial.imageProcessingConfiguration.isReady()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public prepareDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines) {
+        if (defines._areImageProcessingDirty && nodeMaterial.imageProcessingConfiguration) {
+            nodeMaterial.imageProcessingConfiguration.prepareDefines(defines);
+        }
+    }
+
+    public bind(effect: Effect, nodeMaterial: NodeMaterial, mesh?: Mesh) {
+        if (!mesh) {
+            return;
+        }
+
+        if (!nodeMaterial.imageProcessingConfiguration) {
+            return;
+        }
+
+        nodeMaterial.imageProcessingConfiguration.bind(effect);
+    }
+
+    protected _buildBlock(state: NodeMaterialBuildState) {
+        super._buildBlock(state);
+
+        // Register for defines
+        state.sharedData.blocksWithDefines.push(this);
+
+        // Register for blocking
+        state.sharedData.blockingBlocks.push(this);
+
+        // Register for binding
+        state.sharedData.bindableBlocks.push(this);
+
+        // Uniforms
+        state.uniforms.push("exposureLinear");
+        state.uniforms.push("contrast");
+        state.uniforms.push("vInverseScreenSize");
+        state.uniforms.push("vignetteSettings1");
+        state.uniforms.push("vignetteSettings2");
+        state.uniforms.push("vCameraColorCurveNegative");
+        state.uniforms.push("vCameraColorCurveNeutral");
+        state.uniforms.push("vCameraColorCurvePositive");
+        state.uniforms.push("txColorTransform");
+        state.uniforms.push("colorTransformSettings");
+
+        // Emit code
+        let color = this.color;
+        let output = this._outputs[0];
+        let comments = `//${this.name}`;
+
+        state._emitFunctionFromInclude("helperFunctions", comments);
+        state._emitFunctionFromInclude("imageProcessingDeclaration", comments);
+        state._emitFunctionFromInclude("imageProcessingFunctions", comments);
+
+        if (color.connectedPoint!.type === NodeMaterialBlockConnectionPointTypes.Color4) {
+            state.compilationString += `${this._declareOutput(output, state)} = ${color.associatedVariableName};\r\n`;
+        } else {
+            state.compilationString += `${this._declareOutput(output, state)} = vec4(${color.associatedVariableName}, 1.0);\r\n`;
+        }
+        state.compilationString += `#ifdef IMAGEPROCESSINGPOSTPROCESS\r\n`;
+        state.compilationString += `${output.associatedVariableName}.rgb = toLinearSpace(${color.associatedVariableName}.rgb);\r\n`;
+        state.compilationString += `#else\r\n`;
+        state.compilationString += `#ifdef IMAGEPROCESSING\r\n`;
+        state.compilationString += `${output.associatedVariableName}.rgb = toLinearSpace(${color.associatedVariableName}.rgb);\r\n`;
+        state.compilationString += `${output.associatedVariableName} = applyImageProcessing(${output.associatedVariableName});\r\n`;
+        state.compilationString += `#endif\r\n`;
+        state.compilationString += `#endif\r\n`;
+
+        return this;
+    }
+}

+ 9 - 0
src/Materials/Node/Blocks/Fragment/index.ts

@@ -0,0 +1,9 @@
+
+export * from "./fragmentOutputBlock";
+export * from "./alphaTestBlock";
+export * from "./rgbaMergerBlock";
+export * from "./rgbMergerBlock";
+export * from "./rgbaSplitterBlock";
+export * from "./rgbSplitterBlock";
+export * from "./textureBlock";
+export * from "./imageProcessingBlock";

+ 67 - 0
src/Materials/Node/Blocks/Fragment/rgbMergerBlock.ts

@@ -0,0 +1,67 @@
+import { NodeMaterialBlock } from '../../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../../nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialBuildState } from '../../nodeMaterialBuildState';
+import { NodeMaterialBlockTargets } from '../../nodeMaterialBlockTargets';
+import { NodeMaterialConnectionPoint } from '../../nodeMaterialBlockConnectionPoint';
+
+/**
+ * Block used to create a Color3 out of 3 inputs (one for each component)
+ */
+export class RGBMergerBlock extends NodeMaterialBlock {
+    /**
+     * Create a new RGBMergerBlock
+     * @param name defines the block name
+     */
+    public constructor(name: string) {
+        super(name, NodeMaterialBlockTargets.Fragment);
+
+        this.registerInput("r", NodeMaterialBlockConnectionPointTypes.Float);
+        this.registerInput("g", NodeMaterialBlockConnectionPointTypes.Float);
+        this.registerInput("b", NodeMaterialBlockConnectionPointTypes.Float);
+
+        this.registerOutput("output", NodeMaterialBlockConnectionPointTypes.Color3);
+    }
+
+    /**
+     * Gets the current class name
+     * @returns the class name
+     */
+    public getClassName() {
+        return "RGBMergerBlock";
+    }
+
+    /**
+     * Gets the R component input
+     */
+    public get r(): NodeMaterialConnectionPoint {
+        return this._inputs[0];
+    }
+
+    /**
+     * Gets the G component input
+     */
+    public get g(): NodeMaterialConnectionPoint {
+        return this._inputs[1];
+    }
+
+    /**
+     * Gets the B component input
+     */
+    public get b(): NodeMaterialConnectionPoint {
+        return this._inputs[2];
+    }
+
+    protected _buildBlock(state: NodeMaterialBuildState) {
+        super._buildBlock(state);
+
+        let rInput = this.r;
+        let gInput = this.g;
+        let bInput = this.b;
+
+        let output = this._outputs[0];
+
+        state.compilationString += this._declareOutput(output, state) + ` = vec3(${this._writeVariable(rInput)}, ${this._writeVariable(gInput)}, ${this._writeVariable(bInput)});\r\n`;
+
+        return this;
+    }
+}

+ 59 - 0
src/Materials/Node/Blocks/Fragment/rgbSplitterBlock.ts

@@ -0,0 +1,59 @@
+import { NodeMaterialBlock } from '../../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../../nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialBuildState } from '../../nodeMaterialBuildState';
+import { NodeMaterialBlockTargets } from '../../nodeMaterialBlockTargets';
+import { NodeMaterialConnectionPoint } from '../../nodeMaterialBlockConnectionPoint';
+
+/**
+ * Block used to expand a Color3 or a Vector3 into 3 outputs (one for each component)
+ */
+export class RGBSplitterBlock extends NodeMaterialBlock {
+
+    /**
+     * Create a new RGBSplitterBlock
+     * @param name defines the block name
+     */
+    public constructor(name: string) {
+        super(name, NodeMaterialBlockTargets.Fragment);
+
+        this.registerInput("input", NodeMaterialBlockConnectionPointTypes.Vector3OrColor3);
+        this.registerOutput("r", NodeMaterialBlockConnectionPointTypes.Float);
+        this.registerOutput("g", NodeMaterialBlockConnectionPointTypes.Float);
+        this.registerOutput("b", NodeMaterialBlockConnectionPointTypes.Float);
+    }
+
+    /**
+     * Gets the current class name
+     * @returns the class name
+     */
+    public getClassName() {
+        return "RGBSplitterBlock";
+    }
+
+    /**
+     * Gets the input component
+     */
+    public get input(): NodeMaterialConnectionPoint {
+        return this._inputs[0];
+    }
+
+    protected _buildBlock(state: NodeMaterialBuildState) {
+        super._buildBlock(state);
+
+        let input = this.input;
+        let rOutput = this._outputs[0];
+        let gOutput = this._outputs[1];
+        let bOutput = this._outputs[2];
+
+        if (rOutput.connectedBlocks.length > 0) {
+            state.compilationString += this._declareOutput(rOutput, state) + ` = ${input.associatedVariableName}.r;\r\n`;
+        }
+        if (gOutput.connectedBlocks.length > 0) {
+            state.compilationString += this._declareOutput(gOutput, state) + ` = ${input.associatedVariableName}.g;\r\n`;
+        }
+        if (bOutput.connectedBlocks.length > 0) {
+            state.compilationString += this._declareOutput(bOutput, state) + ` = ${input.associatedVariableName}.b;\r\n`;
+        }
+        return this;
+    }
+}

+ 88 - 0
src/Materials/Node/Blocks/Fragment/rgbaMergerBlock.ts

@@ -0,0 +1,88 @@
+import { NodeMaterialBlock } from '../../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../../nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialBuildState } from '../../nodeMaterialBuildState';
+import { NodeMaterialBlockTargets } from '../../nodeMaterialBlockTargets';
+import { NodeMaterialConnectionPoint } from '../../nodeMaterialBlockConnectionPoint';
+
+/**
+ * Block used to create a Color4 out of 4 inputs (one for each component)
+ */
+export class RGBAMergerBlock extends NodeMaterialBlock {
+    /**
+     * Create a new RGBAMergerBlock
+     * @param name defines the block name
+     */
+    public constructor(name: string) {
+        super(name, NodeMaterialBlockTargets.Fragment);
+
+        this.registerInput("r", NodeMaterialBlockConnectionPointTypes.Float, true);
+        this.registerInput("g", NodeMaterialBlockConnectionPointTypes.Float, true);
+        this.registerInput("b", NodeMaterialBlockConnectionPointTypes.Float, true);
+        this.registerInput("rgb", NodeMaterialBlockConnectionPointTypes.Vector3OrColor3OrVector4OrColor4, true);
+        this.registerInput("a", NodeMaterialBlockConnectionPointTypes.Float, true);
+
+        this.registerOutput("output", NodeMaterialBlockConnectionPointTypes.Color4);
+    }
+
+    /**
+     * Gets the current class name
+     * @returns the class name
+     */
+    public getClassName() {
+        return "RGBAMergerBlock";
+    }
+
+    /**
+     * Gets the R input component
+     */
+    public get r(): NodeMaterialConnectionPoint {
+        return this._inputs[0];
+    }
+
+    /**
+     * Gets the G input component
+     */
+    public get g(): NodeMaterialConnectionPoint {
+        return this._inputs[1];
+    }
+
+    /**
+     * Gets the B input component
+     */
+    public get b(): NodeMaterialConnectionPoint {
+        return this._inputs[2];
+    }
+
+    /**
+     * Gets the RGB input component
+     */
+    public get rgb(): NodeMaterialConnectionPoint {
+        return this._inputs[3];
+    }
+
+    /**
+     * Gets the R input component
+     */
+    public get a(): NodeMaterialConnectionPoint {
+        return this._inputs[4];
+    }
+
+    protected _buildBlock(state: NodeMaterialBuildState) {
+        super._buildBlock(state);
+
+        let rgbInput = this.rgb;
+        let aInput = this.a;
+        let output = this._outputs[0];
+
+        if (rgbInput.connectedPoint) {
+            state.compilationString += this._declareOutput(output, state) + ` = vec4(${rgbInput.associatedVariableName}.rgb, ${this._writeVariable(aInput)});\r\n`;
+        } else {
+            let rInput = this._inputs[0];
+            let gInput = this._inputs[1];
+            let bInput = this._inputs[2];
+            state.compilationString += this._declareOutput(output, state) + ` = vec4(${this._writeVariable(rInput)}, ${this._writeVariable(gInput)}, ${this._writeVariable(bInput)}, ${this._writeVariable(aInput)});\r\n`;
+        }
+
+        return this;
+    }
+}

+ 65 - 0
src/Materials/Node/Blocks/Fragment/rgbaSplitterBlock.ts

@@ -0,0 +1,65 @@
+import { NodeMaterialBlock } from '../../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../../nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialBuildState } from '../../nodeMaterialBuildState';
+import { NodeMaterialBlockTargets } from '../../nodeMaterialBlockTargets';
+import { NodeMaterialConnectionPoint } from '../../nodeMaterialBlockConnectionPoint';
+
+/**
+ * Block used to expand a Color4 or a Vector4 into 4 outputs (one for each component)
+ */
+export class RGBASplitterBlock extends NodeMaterialBlock {
+
+    /**
+     * Create a new RGBASplitterBlock
+     * @param name defines the block name
+     */
+    public constructor(name: string) {
+        super(name, NodeMaterialBlockTargets.Fragment);
+
+        this.registerInput("input", NodeMaterialBlockConnectionPointTypes.Vector4OrColor4);
+        this.registerOutput("r", NodeMaterialBlockConnectionPointTypes.Float);
+        this.registerOutput("g", NodeMaterialBlockConnectionPointTypes.Float);
+        this.registerOutput("b", NodeMaterialBlockConnectionPointTypes.Float);
+        this.registerOutput("a", NodeMaterialBlockConnectionPointTypes.Float);
+    }
+
+    /**
+     * Gets the current class name
+     * @returns the class name
+     */
+    public getClassName() {
+        return "RGBASplitterBlock";
+    }
+
+    /**
+     * Gets the input component
+     */
+    public get input(): NodeMaterialConnectionPoint {
+        return this._inputs[0];
+    }
+
+    protected _buildBlock(state: NodeMaterialBuildState) {
+        super._buildBlock(state);
+
+        let input = this.input;
+        let rOutput = this._outputs[0];
+        let gOutput = this._outputs[1];
+        let bOutput = this._outputs[2];
+        let aOutput = this._outputs[3];
+
+        if (rOutput.connectedBlocks.length > 0) {
+            state.compilationString += this._declareOutput(rOutput, state) + ` = ${input.associatedVariableName}.r;\r\n`;
+        }
+        if (gOutput.connectedBlocks.length > 0) {
+            state.compilationString += this._declareOutput(gOutput, state) + ` = ${input.associatedVariableName}.g;\r\n`;
+        }
+        if (bOutput.connectedBlocks.length > 0) {
+            state.compilationString += this._declareOutput(bOutput, state) + ` = ${input.associatedVariableName}.b;\r\n`;
+        }
+        if (aOutput.connectedBlocks.length > 0) {
+            state.compilationString += this._declareOutput(aOutput, state) + ` = ${input.associatedVariableName}.a;\r\n`;
+        }
+
+        return this;
+    }
+}

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

@@ -0,0 +1,194 @@
+import { NodeMaterialBlock } from '../../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../../nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialBuildState } from '../../nodeMaterialBuildState';
+import { NodeMaterialBlockTargets } from '../../nodeMaterialBlockTargets';
+import { NodeMaterialConnectionPoint } from '../../nodeMaterialBlockConnectionPoint';
+import { BaseTexture } from '../../../Textures/baseTexture';
+import { AbstractMesh } from '../../../../Meshes/abstractMesh';
+import { NodeMaterial, NodeMaterialDefines } from '../../nodeMaterial';
+
+/**
+ * Block used to read a texture from a sampler
+ */
+export class TextureBlock extends NodeMaterialBlock {
+    private _defineName: string;
+
+    /**
+     * Gets or sets a boolean indicating that the block can automatically fetch the texture matrix
+     */
+    public autoConnectTextureMatrix = true;
+
+    /**
+     * Gets or sets a boolean indicating that the block can automatically select the uv channel based on texture
+     */
+    public autoSelectUV = true;
+
+    /**
+     * Create a new TextureBlock
+     * @param name defines the block name
+     */
+    public constructor(name: string) {
+        super(name, NodeMaterialBlockTargets.Fragment);
+
+        this.registerInput("uv", NodeMaterialBlockConnectionPointTypes.Vector2);
+        this.registerInput("textureInfo", NodeMaterialBlockConnectionPointTypes.Vector2, true);
+
+        this.registerInput("transformedUV", NodeMaterialBlockConnectionPointTypes.Vector2, false, NodeMaterialBlockTargets.Vertex);
+        this.registerInput("texture", NodeMaterialBlockConnectionPointTypes.Texture, false, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("textureTransform", NodeMaterialBlockConnectionPointTypes.Matrix, true, NodeMaterialBlockTargets.Vertex);
+
+        this.registerOutput("color", NodeMaterialBlockConnectionPointTypes.Color4);
+    }
+
+    /**
+     * Gets the current class name
+     * @returns the class name
+     */
+    public getClassName() {
+        return "TextureBlock";
+    }
+
+    /**
+     * Gets the uv input component
+     */
+    public get uv(): NodeMaterialConnectionPoint {
+        return this._inputs[0];
+    }
+
+    /**
+     * Gets the texture information input component
+     */
+    public get textureInfo(): NodeMaterialConnectionPoint {
+        return this._inputs[1];
+    }
+
+    /**
+     * Gets the transformed uv input component
+     */
+    public get transformedUV(): NodeMaterialConnectionPoint {
+        return this._inputs[2];
+    }
+
+    /**
+     * Gets the texture input component
+     */
+    public get texture(): NodeMaterialConnectionPoint {
+        return this._inputs[3];
+    }
+
+    /**
+     * Gets the texture transform input component
+     */
+    public get textureTransform(): NodeMaterialConnectionPoint {
+        return this._inputs[4];
+    }
+
+    public autoConfigure() {
+        if (!this.uv.connectedPoint) {
+            this.uv.setAsAttribute();
+            this.uv.connectTo(this.transformedUV);
+        }
+    }
+
+    public initialize(state: NodeMaterialBuildState) {
+        if (this.texture.value && this.texture.value.getTextureMatrix) {
+            const texture = this.texture.value as BaseTexture;
+
+            if (this.autoConnectTextureMatrix) {
+                this.textureTransform.valueCallback = () => texture.getTextureMatrix();
+            }
+            if (this.autoSelectUV) {
+                this.uv.setAsAttribute("uv" + (texture.coordinatesIndex ? (texture.coordinatesIndex + 1) : ""));
+            }
+        }
+    }
+
+    public prepareDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines) {
+        if (!this.texture.value || !this.texture.value.getTextureMatrix) {
+            return;
+        }
+
+        let uvInput = this.uv;
+        let textureTransform = this.textureTransform;
+        let isTextureTransformConnected = textureTransform.connectedPoint != null || textureTransform.isUniform;
+
+        const texture = this.texture.value as BaseTexture;
+        let mainUVName = ("vMain" + uvInput.associatedVariableName).toUpperCase();
+
+        if (isTextureTransformConnected && !texture.getTextureMatrix().isIdentityAs3x2()) {
+            defines.setValue(this._defineName, true);
+            defines.setValue(mainUVName, false);
+        } else {
+            defines.setValue(this._defineName, false);
+            defines.setValue(mainUVName, true);
+        }
+    }
+
+    public isReady() {
+        let texture = this.texture.value as BaseTexture;
+        if (texture && !texture.isReadyOrNotBlocking()) {
+            return false;
+        }
+
+        return true;
+    }
+
+    private _injectVertexCode(state: NodeMaterialBuildState) {
+        let uvInput = this.uv;
+        let transformedUV = this.transformedUV;
+        let textureTransform = this.textureTransform;
+        let isTextureTransformConnected = textureTransform.connectedPoint != null || textureTransform.isUniform;
+
+        // Inject code in vertex
+        this._defineName = state._getFreeDefineName("UVTRANSFORM");
+        let mainUVName = "vMain" + uvInput.associatedVariableName;
+
+        transformedUV.associatedVariableName = state._getFreeVariableName(transformedUV.name);
+        state._emitVaryings(transformedUV, this._defineName, true);
+        state._emitVaryings(transformedUV, mainUVName.toUpperCase(), true, false, mainUVName);
+
+        textureTransform.associatedVariableName = state._getFreeVariableName(textureTransform.name);
+        state._emitUniformOrAttributes(textureTransform, this._defineName);
+
+        if (isTextureTransformConnected) {
+            if (state.sharedData.emitComments) {
+                state.compilationString += `\r\n//${this.name}\r\n`;
+            }
+            state.compilationString += `#ifdef ${this._defineName}\r\n`;
+            state.compilationString += `${transformedUV.associatedVariableName} = vec2(${textureTransform.associatedVariableName} * vec4(${uvInput.associatedVariableName}, 1.0, 0.0));\r\n`;
+            state.compilationString += `#else\r\n`;
+            state.compilationString += `${mainUVName} = ${uvInput.associatedVariableName};\r\n`;
+            state.compilationString += `#endif\r\n`;
+        } else {
+            state.compilationString += `${mainUVName} = ${uvInput.associatedVariableName};\r\n`;
+        }
+    }
+
+    protected _buildBlock(state: NodeMaterialBuildState) {
+        super._buildBlock(state);
+
+        state.sharedData.blockingBlocks.push(this);
+
+        // Vertex
+        this._injectVertexCode(state._vertexState);
+
+        // Fragment
+        state.sharedData.blocksWithDefines.push(this);
+
+        let uvInput = this.uv;
+        let transformedUV = this.transformedUV;
+        let textureInfo = this.textureInfo;
+        let samplerInput = this.texture;
+        let output = this._outputs[0];
+        let isTextureInfoConnected = textureInfo.connectedPoint != null || textureInfo.isUniform;
+        const complement = isTextureInfoConnected ? ` * ${textureInfo.associatedVariableName}.y` : "";
+
+        state.compilationString += `#ifdef ${this._defineName}\r\n`;
+        state.compilationString += `vec4 ${output.associatedVariableName} = texture2D(${samplerInput.associatedVariableName}, ${transformedUV.associatedVariableName})${complement};\r\n`;
+        state.compilationString += `#else\r\n`;
+        state.compilationString += `vec4 ${output.associatedVariableName} = texture2D(${samplerInput.associatedVariableName}, ${"vMain" + uvInput.associatedVariableName})${complement};\r\n`;
+        state.compilationString += `#endif\r\n`;
+
+        return this;
+    }
+}

+ 170 - 0
src/Materials/Node/Blocks/Vertex/bonesBlock.ts

@@ -0,0 +1,170 @@
+import { NodeMaterialBlock } from '../../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../../nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialBuildState } from '../../nodeMaterialBuildState';
+import { NodeMaterialWellKnownValues } from '../../nodeMaterialWellKnownValues';
+import { NodeMaterialBlockTargets } from '../../nodeMaterialBlockTargets';
+import { AbstractMesh } from '../../../../Meshes/abstractMesh';
+import { Mesh } from '../../../../Meshes/mesh';
+import { Effect, EffectFallbacks } from '../../../effect';
+import { MaterialHelper } from '../../../materialHelper';
+import { NodeMaterialConnectionPoint } from '../../nodeMaterialBlockConnectionPoint';
+import { NodeMaterial, NodeMaterialDefines } from '../../nodeMaterial';
+
+/**
+ * Block used to add support for vertex skinning (bones)
+ */
+export class BonesBlock extends NodeMaterialBlock {
+    /**
+     * Creates a new BonesBlock
+     * @param name defines the block name
+     */
+    public constructor(name: string) {
+        super(name, NodeMaterialBlockTargets.Vertex);
+
+        this.registerInput("matricesIndices", NodeMaterialBlockConnectionPointTypes.Vector4);
+        this.registerInput("matricesWeights", NodeMaterialBlockConnectionPointTypes.Vector4);
+        this.registerInput("matricesIndicesExtra", NodeMaterialBlockConnectionPointTypes.Vector4);
+        this.registerInput("matricesWeightsExtra", NodeMaterialBlockConnectionPointTypes.Vector4);
+        this.registerInput("world", NodeMaterialBlockConnectionPointTypes.Matrix);
+
+        this.registerOutput("output", NodeMaterialBlockConnectionPointTypes.Matrix);
+    }
+
+    /**
+     * Initialize the block and prepare the context for build
+     * @param state defines the state that will be used for the build
+     */
+    public initialize(state: NodeMaterialBuildState) {
+        state._excludeVariableName("boneSampler");
+        state._excludeVariableName("boneTextureWidth");
+        state._excludeVariableName("mBones");
+        state._excludeVariableName("BonesPerMesh");
+    }
+
+    /**
+     * Gets the current class name
+     * @returns the class name
+     */
+    public getClassName() {
+        return "BonesBlock";
+    }
+
+    /**
+     * Gets the matrix indices input component
+     */
+    public get matricesIndices(): NodeMaterialConnectionPoint {
+        return this._inputs[0];
+    }
+
+    /**
+     * Gets the matrix weights input component
+     */
+    public get matricesWeights(): NodeMaterialConnectionPoint {
+        return this._inputs[1];
+    }
+
+    /**
+     * Gets the extra matrix indices input component
+     */
+    public get matricesIndicesExtra(): NodeMaterialConnectionPoint {
+        return this._inputs[2];
+    }
+
+    /**
+     * Gets the extra matrix weights input component
+     */
+    public get matricesWeightsExtra(): NodeMaterialConnectionPoint {
+        return this._inputs[3];
+    }
+
+    /**
+     * Gets the world input component
+     */
+    public get world(): NodeMaterialConnectionPoint {
+        return this._inputs[4];
+    }
+
+    public autoConfigure() {
+        if (!this.matricesIndices.connectedPoint) {
+            this.matricesIndices.setAsAttribute();
+        }
+        if (!this.matricesWeights.connectedPoint) {
+            this.matricesWeights.setAsAttribute();
+        }
+        if (!this.matricesIndicesExtra.connectedPoint) {
+            this.matricesIndicesExtra.setAsAttribute();
+        }
+        if (!this.matricesWeightsExtra.connectedPoint) {
+            this.matricesWeightsExtra.setAsAttribute();
+        }
+        if (!this.world.connectedPoint) {
+            this.world.setAsWellKnownValue(NodeMaterialWellKnownValues.World);
+        }
+    }
+
+    public provideFallbacks(mesh: AbstractMesh, fallbacks: EffectFallbacks) {
+        if (mesh && mesh.useBones && mesh.computeBonesUsingShaders && mesh.skeleton) {
+            fallbacks.addCPUSkinningFallback(0, mesh);
+        }
+    }
+
+    public bind(effect: Effect, nodeMaterial: NodeMaterial, mesh?: Mesh) {
+        MaterialHelper.BindBonesParameters(mesh, effect);
+    }
+
+    public prepareDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines) {
+        if (!defines._areAttributesDirty) {
+            return;
+        }
+        MaterialHelper.PrepareDefinesForBones(mesh, defines);
+    }
+
+    protected _buildBlock(state: NodeMaterialBuildState) {
+        super._buildBlock(state);
+
+        // Register for compilation fallbacks
+        state.sharedData.blocksWithFallbacks.push(this);
+
+        // Register for binding
+        state.sharedData.bindableBlocks.push(this);
+
+        // Register for defines
+        state.sharedData.blocksWithDefines.push(this);
+
+        // Register internal uniforms and samplers
+        state.uniforms.push("boneTextureWidth");
+        state.uniforms.push("mBones");
+
+        state.samplers.push("boneSampler");
+
+        // Emit code
+        let comments = `//${this.name}`;
+        state._emitFunctionFromInclude("bonesDeclaration", comments, {
+            removeAttributes: true,
+            removeUniforms: false,
+            removeVaryings: true,
+            removeIfDef: false
+        });
+
+        let influenceVariablename = state._getFreeVariableName("influence");
+
+        state.compilationString += state._emitCodeFromInclude("bonesVertex", comments, {
+            replaceStrings: [
+                {
+                    search: /finalWorld=finalWorld\*influence;/,
+                    replace: ""
+                },
+                {
+                    search: /influence/gm,
+                    replace: influenceVariablename
+                }
+            ]
+        });
+
+        let output = this._outputs[0];
+        let worldInput = this.world;
+
+        state.compilationString += this._declareOutput(output, state) + ` = ${worldInput.associatedVariableName} * ${influenceVariablename};`;
+        return this;
+    }
+}

+ 4 - 0
src/Materials/Node/Blocks/Vertex/index.ts

@@ -0,0 +1,4 @@
+export * from "./vertexOutputBlock";
+export * from "./bonesBlock";
+export * from "./instancesBlock";
+export * from "./morphTargetsBlock";

+ 133 - 0
src/Materials/Node/Blocks/Vertex/instancesBlock.ts

@@ -0,0 +1,133 @@
+import { NodeMaterialBlock } from '../../nodeMaterialBlock';
+import { NodeMaterialBlockTargets } from '../../nodeMaterialBlockTargets';
+import { NodeMaterialBlockConnectionPointTypes } from '../../nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialConnectionPoint } from '../../nodeMaterialBlockConnectionPoint';
+import { NodeMaterialBuildState } from '../../nodeMaterialBuildState';
+import { AbstractMesh } from '../../../../Meshes/abstractMesh';
+import { NodeMaterial, NodeMaterialDefines } from '../../nodeMaterial';
+import { NodeMaterialWellKnownValues } from '../../nodeMaterialWellKnownValues';
+
+/**
+ * Block used to add support for instances
+ * @see https://doc.babylonjs.com/how_to/how_to_use_instances
+ */
+export class InstancesBlock extends NodeMaterialBlock {
+    /**
+     * Creates a new InstancesBlock
+     * @param name defines the block name
+     */
+    public constructor(name: string) {
+        super(name, NodeMaterialBlockTargets.Vertex);
+
+        this.registerInput("world0", NodeMaterialBlockConnectionPointTypes.Vector4);
+        this.registerInput("world1", NodeMaterialBlockConnectionPointTypes.Vector4);
+        this.registerInput("world2", NodeMaterialBlockConnectionPointTypes.Vector4);
+        this.registerInput("world3", NodeMaterialBlockConnectionPointTypes.Vector4);
+        this.registerInput("world", NodeMaterialBlockConnectionPointTypes.Matrix, true);
+
+        this.registerOutput("output", NodeMaterialBlockConnectionPointTypes.Matrix);
+    }
+
+    /**
+     * Gets the current class name
+     * @returns the class name
+     */
+    public getClassName() {
+        return "InstancesBlock";
+    }
+
+    /**
+     * Gets the first world row input component
+     */
+    public get world0(): NodeMaterialConnectionPoint {
+        return this._inputs[0];
+    }
+
+    /**
+     * Gets the second world row input component
+     */
+    public get world1(): NodeMaterialConnectionPoint {
+        return this._inputs[1];
+    }
+
+    /**
+     * Gets the third world row input component
+     */
+    public get world2(): NodeMaterialConnectionPoint {
+        return this._inputs[2];
+    }
+
+    /**
+     * Gets the forth world row input component
+     */
+    public get world3(): NodeMaterialConnectionPoint {
+        return this._inputs[3];
+    }
+
+    /**
+     * Gets the world input component
+     */
+    public get world(): NodeMaterialConnectionPoint {
+        return this._inputs[4];
+    }
+
+    /**
+     * Gets the output component
+     */
+    public get output(): NodeMaterialConnectionPoint {
+        return this._outputs[0];
+    }
+
+    public autoConfigure() {
+        if (!this.world0.connectedPoint) {
+            this.world0.setAsAttribute();
+        }
+        if (!this.world1.connectedPoint) {
+            this.world1.setAsAttribute();
+        }
+        if (!this.world2.connectedPoint) {
+            this.world2.setAsAttribute();
+        }
+        if (!this.world3.connectedPoint) {
+            this.world3.setAsAttribute();
+        }
+        if (!this.world.connectedPoint) {
+            this.world.setAsWellKnownValue(NodeMaterialWellKnownValues.World);
+        }
+
+        this.world.define = "!INSTANCES";
+    }
+
+    public prepareDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines, useInstances: boolean = false) {
+        let changed = false;
+        if (defines["INSTANCES"] !== useInstances) {
+            defines.setValue("INSTANCES", useInstances);
+            changed = true;
+        }
+
+        if (changed) {
+            defines.markAsUnprocessed();
+        }
+    }
+
+    protected _buildBlock(state: NodeMaterialBuildState) {
+        super._buildBlock(state);
+
+        // Register for defines
+        state.sharedData.blocksWithDefines.push(this);
+
+        // Emit code
+        let output = this._outputs[0];
+        let world0 = this.world0;
+        let world1 = this.world1;
+        let world2 = this.world2;
+        let world3 = this.world3;
+
+        state.compilationString += `#ifdef INSTANCES\r\n`;
+        state.compilationString += this._declareOutput(output, state) + ` = mat4(${world0.associatedVariableName}, ${world1.associatedVariableName}, ${world2.associatedVariableName}, ${world3.associatedVariableName});\r\n`;
+        state.compilationString += `#else\r\n`;
+        state.compilationString += this._declareOutput(output, state) + ` = ${this.world.associatedVariableName};\r\n`;
+        state.compilationString += `#endif\r\n`;
+        return this;
+    }
+}

+ 211 - 0
src/Materials/Node/Blocks/Vertex/morphTargetsBlock.ts

@@ -0,0 +1,211 @@
+import { NodeMaterialBlock } from '../../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../../nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialBuildState } from '../../nodeMaterialBuildState';
+import { NodeMaterialBlockTargets } from '../../nodeMaterialBlockTargets';
+import { NodeMaterialConnectionPoint } from '../../nodeMaterialBlockConnectionPoint';
+import { AbstractMesh } from '../../../../Meshes/abstractMesh';
+import { NodeMaterial, NodeMaterialDefines } from '../../nodeMaterial';
+import { Effect } from '../../../effect';
+import { Mesh } from '../../../../Meshes/mesh';
+import { MaterialHelper } from '../../../materialHelper';
+import { VertexBuffer } from '../../../../Meshes/buffer';
+
+/**
+ * Block used to add morph targets support to vertex shader
+ */
+export class MorphTargetsBlock extends NodeMaterialBlock {
+    private _repeatableContentAnchor: string;
+    private _repeatebleContentGenerated = 0;
+
+    /**
+     * Create a new MorphTargetsBlock
+     * @param name defines the block name
+     */
+    public constructor(name: string) {
+        super(name, NodeMaterialBlockTargets.Vertex);
+
+        this.registerInput("position", NodeMaterialBlockConnectionPointTypes.Vector3);
+        this.registerInput("normal", NodeMaterialBlockConnectionPointTypes.Vector3);
+        this.registerInput("tangent", NodeMaterialBlockConnectionPointTypes.Vector3);
+        this.registerOutput("positionOutput", NodeMaterialBlockConnectionPointTypes.Vector3);
+        this.registerOutput("normalOutput", NodeMaterialBlockConnectionPointTypes.Vector3);
+        this.registerOutput("tangentOutput", NodeMaterialBlockConnectionPointTypes.Vector3);
+    }
+
+    /**
+     * Gets the current class name
+     * @returns the class name
+     */
+    public getClassName() {
+        return "MorphTargetsBlock";
+    }
+
+    /**
+     * Gets the position input component
+     */
+    public get position(): NodeMaterialConnectionPoint {
+        return this._inputs[0];
+    }
+
+    /**
+     * Gets the normal input component
+     */
+    public get normal(): NodeMaterialConnectionPoint {
+        return this._inputs[1];
+    }
+
+    /**
+     * Gets the tangent input component
+     */
+    public get tangent(): NodeMaterialConnectionPoint {
+        return this._inputs[2];
+    }
+
+    /**
+     * Gets the position output component
+     */
+    public get positionOutput(): NodeMaterialConnectionPoint {
+        return this._outputs[0];
+    }
+
+    /**
+     * Gets the normal output component
+     */
+    public get normalOutput(): NodeMaterialConnectionPoint {
+        return this._outputs[1];
+    }
+
+    /**
+     * Gets the tangent output component
+     */
+    public get tangentOutput(): NodeMaterialConnectionPoint {
+        return this._outputs[2];
+    }
+
+    public initialize(state: NodeMaterialBuildState) {
+        state._excludeVariableName("morphTargetInfluences");
+    }
+
+    public autoConfigure() {
+        if (!this.position.connectedPoint) {
+            this.position.setAsAttribute();
+        }
+        if (!this.normal.connectedPoint) {
+            this.normal.setAsAttribute();
+            this.normal.define = "NORMAL";
+        }
+        if (!this.tangent.connectedPoint) {
+            this.tangent.setAsAttribute();
+            this.tangent.define = "TANGENT";
+        }
+    }
+
+    public prepareDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines) {
+        if (!defines._areAttributesDirty) {
+            return;
+        }
+        MaterialHelper.PrepareDefinesForMorphTargets(mesh, defines);
+    }
+
+    public bind(effect: Effect, nodeMaterial: NodeMaterial, mesh?: Mesh) {
+        if (mesh && this._repeatebleContentGenerated) {
+            MaterialHelper.BindMorphTargetParameters(mesh, effect);
+        }
+    }
+
+    public replaceRepeatableContent(vertexShaderState: NodeMaterialBuildState, fragmentShaderState: NodeMaterialBuildState, mesh: AbstractMesh, defines: NodeMaterialDefines) {
+        let position = this.position;
+        let normal = this.normal;
+        let tangent = this.tangent;
+        let positionOutput = this.positionOutput;
+        let normalOutput = this.normalOutput;
+        let tangentOutput = this.tangentOutput;
+        let state = vertexShaderState;
+        let repeatCount = defines.NUM_MORPH_INFLUENCERS as number;
+        this._repeatebleContentGenerated = repeatCount;
+
+        var manager = (<Mesh>mesh).morphTargetManager;
+        var hasNormals = manager && manager.supportsNormals && defines["NORMAL"];
+        var hasTangents = manager && manager.supportsTangents && defines["TANGENT"];
+
+        let injectionCode = "";
+
+        for (var index = 0; index < repeatCount; index++) {
+            injectionCode += `#ifdef MORPHTARGETS\r\n`;
+            injectionCode += `${positionOutput.associatedVariableName} += (position${index} - ${position.associatedVariableName}) * morphTargetInfluences[${index}];\r\n`;
+
+            if (hasNormals) {
+                injectionCode += `#ifdef MORPHTARGETS_NORMAL\r\n`;
+                injectionCode += `${normalOutput.associatedVariableName} += (normal${index} - ${normal.associatedVariableName}) * morphTargetInfluences[${index}];\r\n`;
+                injectionCode += `#endif\r\n`;
+            }
+
+            if (hasTangents) {
+                injectionCode += `#ifdef MORPHTARGETS_TANGENT\r\n`;
+                injectionCode += `${tangentOutput.associatedVariableName}.xyz += (tangent${index} - ${tangent.associatedVariableName}.xyz) * morphTargetInfluences[${index}];\r\n`;
+                injectionCode += `#endif\r\n`;
+            }
+
+            injectionCode += `#endif\r\n`;
+        }
+
+        state.compilationString = state.compilationString.replace(this._repeatableContentAnchor, injectionCode);
+
+        if (repeatCount > 0) {
+            for (var index = 0; index < repeatCount; index++) {
+                state.attributes.push(VertexBuffer.PositionKind + index);
+
+                if (hasNormals) {
+                    state.attributes.push(VertexBuffer.NormalKind + index);
+                }
+
+                if (hasTangents) {
+                    state.attributes.push(VertexBuffer.TangentKind + index);
+                }
+            }
+        }
+    }
+
+    protected _buildBlock(state: NodeMaterialBuildState) {
+        super._buildBlock(state);
+
+        // Register for defines
+        state.sharedData.blocksWithDefines.push(this);
+
+        // Register for binding
+        state.sharedData.bindableBlocks.push(this);
+
+        // Register for repeatable content generation
+        state.sharedData.repeatableContentBlocks.push(this);
+
+        // Emit code
+        let position = this.position;
+        let normal = this.normal;
+        let tangent = this.tangent;
+        let positionOutput = this.positionOutput;
+        let normalOutput = this.normalOutput;
+        let tangentOutput = this.tangentOutput;
+        let comments = `//${this.name}`;
+
+        state.uniforms.push("morphTargetInfluences");
+
+        state._emitFunctionFromInclude("morphTargetsVertexGlobalDeclaration", comments);
+        state._emitFunctionFromInclude("morphTargetsVertexDeclaration", comments, {
+            repeatKey: "maxSimultaneousMorphTargets"
+        });
+
+        state.compilationString += `${this._declareOutput(positionOutput, state)} = ${position.associatedVariableName};\r\n`;
+        state.compilationString += `#ifdef NORMAL\r\n`;
+        state.compilationString += `${this._declareOutput(normalOutput, state)} = ${normal.associatedVariableName};\r\n`;
+        state.compilationString += `#endif\r\n`;
+        state.compilationString += `#ifdef TANGENT\r\n`;
+        state.compilationString += `${this._declareOutput(tangentOutput, state)} = ${tangent.associatedVariableName};\r\n`;
+        state.compilationString += `#endif\r\n`;
+
+        // Repeatable content
+        this._repeatableContentAnchor = state._repeatableContentAnchor;
+        state.compilationString += this._repeatableContentAnchor;
+
+        return this;
+    }
+}

+ 46 - 0
src/Materials/Node/Blocks/Vertex/vertexOutputBlock.ts

@@ -0,0 +1,46 @@
+import { NodeMaterialBlock } from '../../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../../nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialBuildState } from '../../nodeMaterialBuildState';
+import { NodeMaterialBlockTargets } from '../../nodeMaterialBlockTargets';
+import { NodeMaterialConnectionPoint } from '../../nodeMaterialBlockConnectionPoint';
+
+/**
+ * Block used to output the vertex position
+ */
+export class VertexOutputBlock extends NodeMaterialBlock {
+
+    /**
+     * Creates a new VertexOutputBlock
+     * @param name defines the block name
+     */
+    public constructor(name: string) {
+        super(name, NodeMaterialBlockTargets.Vertex, true);
+
+        this.registerInput("vector", NodeMaterialBlockConnectionPointTypes.Vector4);
+    }
+
+    /**
+     * Gets the current class name
+     * @returns the class name
+     */
+    public getClassName() {
+        return "VertexOutputBlock";
+    }
+
+    /**
+     * Gets the vector input component
+     */
+    public get vector(): NodeMaterialConnectionPoint {
+        return this._inputs[0];
+    }
+
+    protected _buildBlock(state: NodeMaterialBuildState) {
+        super._buildBlock(state);
+
+        let input = this.vector;
+
+        state.compilationString += `gl_Position = ${input.associatedVariableName};\r\n`;
+
+        return this;
+    }
+}

+ 52 - 0
src/Materials/Node/Blocks/addBlock.ts

@@ -0,0 +1,52 @@
+import { NodeMaterialBlock } from '../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialBuildState } from '../nodeMaterialBuildState';
+import { NodeMaterialConnectionPoint } from '../nodeMaterialBlockConnectionPoint';
+/**
+ * Block used to add 2 vector4
+ */
+export class AddBlock extends NodeMaterialBlock {
+    /**
+     * Creates a new AddBlock
+     * @param name defines the block name
+     */
+    public constructor(name: string) {
+        super(name);
+
+        this.registerInput("left", NodeMaterialBlockConnectionPointTypes.Vector4OrColor4);
+        this.registerInput("right", NodeMaterialBlockConnectionPointTypes.Vector4OrColor4);
+        this.registerOutput("output", NodeMaterialBlockConnectionPointTypes.Vector4OrColor4);
+    }
+
+    /**
+     * Gets the current class name
+     * @returns the class name
+     */
+    public getClassName() {
+        return "AddBlock";
+    }
+
+    /**
+     * Gets the left operand input component
+     */
+    public get left(): NodeMaterialConnectionPoint {
+        return this._inputs[0];
+    }
+
+    /**
+     * Gets the right operand input component
+     */
+    public get right(): NodeMaterialConnectionPoint {
+        return this._inputs[1];
+    }
+
+    protected _buildBlock(state: NodeMaterialBuildState) {
+        super._buildBlock(state);
+
+        let output = this._outputs[0];
+
+        state.compilationString += this._declareOutput(output, state) + ` = ${this.left.associatedVariableName} + ${this.right.associatedVariableName};\r\n`;
+
+        return this;
+    }
+}

+ 50 - 0
src/Materials/Node/Blocks/clampBlock.ts

@@ -0,0 +1,50 @@
+import { NodeMaterialBlock } from '../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialBuildState } from '../nodeMaterialBuildState';
+import { NodeMaterialConnectionPoint } from '../nodeMaterialBlockConnectionPoint';
+/**
+ * Block used to clamp a float
+ */
+export class ClampBlock extends NodeMaterialBlock {
+
+    /** Gets or sets the minimum range */
+    public minimum = 0.0;
+    /** Gets or sets the maximum range */
+    public maximum = 1.0;
+
+    /**
+     * Creates a new ClampBlock
+     * @param name defines the block name
+     */
+    public constructor(name: string) {
+        super(name);
+
+        this.registerInput("value", NodeMaterialBlockConnectionPointTypes.Float);
+        this.registerOutput("output", NodeMaterialBlockConnectionPointTypes.Float);
+    }
+
+    /**
+     * Gets the current class name
+     * @returns the class name
+     */
+    public getClassName() {
+        return "ClampBlock";
+    }
+
+    /**
+     * Gets the value input component
+     */
+    public get value(): NodeMaterialConnectionPoint {
+        return this._inputs[0];
+    }
+
+    protected _buildBlock(state: NodeMaterialBuildState) {
+        super._buildBlock(state);
+
+        let output = this._outputs[0];
+
+        state.compilationString += this._declareOutput(output, state) + ` = clamp(${this.value.associatedVariableName}, ${this._writeFloat(this.minimum)}, ${this._writeFloat(this.maximum)});\r\n`;
+
+        return this;
+    }
+}

+ 10 - 0
src/Materials/Node/Blocks/index.ts

@@ -0,0 +1,10 @@
+export * from "./Vertex/index";
+export * from "./Fragment/index";
+export * from "./Dual/index";
+export * from "./multiplyBlock";
+export * from "./addBlock";
+export * from "./clampBlock";
+export * from "./vector2TransformBlock";
+export * from "./vector3TransformBlock";
+export * from "./vector4TransformBlock";
+export * from "./matrixMultiplicationBlock";

+ 54 - 0
src/Materials/Node/Blocks/matrixMultiplicationBlock.ts

@@ -0,0 +1,54 @@
+import { NodeMaterialBlock } from '../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialBuildState } from '../nodeMaterialBuildState';
+import { NodeMaterialBlockTargets } from '../nodeMaterialBlockTargets';
+import { NodeMaterialConnectionPoint } from '../nodeMaterialBlockConnectionPoint';
+
+/**
+ * Block used to multiply two matrices
+ */
+export class MatrixMultiplicationBlock extends NodeMaterialBlock {
+    /**
+     * Creates a new MatrixMultiplicationBlock
+     * @param name defines the block name
+     */
+    public constructor(name: string) {
+        super(name, NodeMaterialBlockTargets.Vertex);
+
+        this.registerInput("left", NodeMaterialBlockConnectionPointTypes.Matrix);
+        this.registerInput("right", NodeMaterialBlockConnectionPointTypes.Matrix);
+        this.registerOutput("output", NodeMaterialBlockConnectionPointTypes.Matrix);
+    }
+
+    /**
+     * Gets the left operand
+     */
+    public get left(): NodeMaterialConnectionPoint {
+        return this._inputs[0];
+    }
+
+    /**
+     * Gets the right operand
+     */
+    public get right(): NodeMaterialConnectionPoint {
+        return this._inputs[1];
+    }
+
+    /**
+     * Gets the current class name
+     * @returns the class name
+     */
+    public getClassName() {
+        return "MatrixMultiplicationBlock";
+    }
+
+    protected _buildBlock(state: NodeMaterialBuildState) {
+        super._buildBlock(state);
+
+        let output = this._outputs[0];
+
+        state.compilationString += this._declareOutput(output, state) + ` = ${this.left.associatedVariableName} * ${this.right.associatedVariableName};\r\n`;
+
+        return this;
+    }
+}

+ 52 - 0
src/Materials/Node/Blocks/multiplyBlock.ts

@@ -0,0 +1,52 @@
+import { NodeMaterialBlock } from '../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialBuildState } from '../nodeMaterialBuildState';
+import { NodeMaterialConnectionPoint } from '../nodeMaterialBlockConnectionPoint';
+/**
+ * Block used to multiply 2 vector4
+ */
+export class MultiplyBlock extends NodeMaterialBlock {
+    /**
+     * Creates a new MultiplyBlock
+     * @param name defines the block name
+     */
+    public constructor(name: string) {
+        super(name);
+
+        this.registerInput("left", NodeMaterialBlockConnectionPointTypes.Vector4OrColor4);
+        this.registerInput("right", NodeMaterialBlockConnectionPointTypes.Vector4OrColor4);
+        this.registerOutput("output", NodeMaterialBlockConnectionPointTypes.Vector4OrColor4);
+    }
+
+    /**
+     * Gets the current class name
+     * @returns the class name
+     */
+    public getClassName() {
+        return "MultiplyBlock";
+    }
+
+    /**
+     * Gets the left operand input component
+     */
+    public get left(): NodeMaterialConnectionPoint {
+        return this._inputs[0];
+    }
+
+    /**
+     * Gets the right operand input component
+     */
+    public get right(): NodeMaterialConnectionPoint {
+        return this._inputs[1];
+    }
+
+    protected _buildBlock(state: NodeMaterialBuildState) {
+        super._buildBlock(state);
+
+        let output = this._outputs[0];
+
+        state.compilationString += this._declareOutput(output, state) + ` = ${this.left.associatedVariableName} * ${this.right.associatedVariableName};\r\n`;
+
+        return this;
+    }
+}

+ 66 - 0
src/Materials/Node/Blocks/vector2TransformBlock.ts

@@ -0,0 +1,66 @@
+import { NodeMaterialBlock } from '../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialBuildState } from '../nodeMaterialBuildState';
+import { NodeMaterialBlockTargets } from '../nodeMaterialBlockTargets';
+import { NodeMaterialConnectionPoint } from '../nodeMaterialBlockConnectionPoint';
+
+/**
+ * Block used to transform a vector2 with a matrix
+ */
+export class Vector2TransformBlock extends NodeMaterialBlock {
+    /**
+     * Defines the value to use to complement Vector2 to transform it to a Vector4
+     */
+    public complementZ = 1;
+
+    /**
+     * Defines the value to use to complement Vector2 to transform it to a Vector4
+     */
+    public complementW = 0;
+
+    /**
+     * Creates a new Vector2TransformBlock
+     * @param name defines the block name
+     */
+    public constructor(name: string) {
+        super(name, NodeMaterialBlockTargets.Vertex);
+
+        this.registerInput("vector", NodeMaterialBlockConnectionPointTypes.Vector2);
+        this.registerInput("transform", NodeMaterialBlockConnectionPointTypes.Matrix);
+        this.registerOutput("output", NodeMaterialBlockConnectionPointTypes.Vector2);
+    }
+
+    /**
+     * Gets the vector input
+     */
+    public get vector(): NodeMaterialConnectionPoint {
+        return this._inputs[0];
+    }
+
+    /**
+     * Gets the matrix transform input
+     */
+    public get transform(): NodeMaterialConnectionPoint {
+        return this._inputs[1];
+    }
+
+    /**
+     * Gets the current class name
+     * @returns the class name
+     */
+    public getClassName() {
+        return "Vector2TransformBlock";
+    }
+
+    protected _buildBlock(state: NodeMaterialBuildState) {
+        super._buildBlock(state);
+
+        let output = this._outputs[0];
+        let vector = this.vector;
+        let transform = this.transform;
+
+        state.compilationString += this._declareOutput(output, state) + ` = vec2(${transform.associatedVariableName} * vec4(${vector.associatedVariableName}, ${this.complementZ}, ${this.complementW}));\r\n`;
+
+        return this;
+    }
+}

+ 61 - 0
src/Materials/Node/Blocks/vector3TransformBlock.ts

@@ -0,0 +1,61 @@
+import { NodeMaterialBlock } from '../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialBuildState } from '../nodeMaterialBuildState';
+import { NodeMaterialBlockTargets } from '../nodeMaterialBlockTargets';
+import { NodeMaterialConnectionPoint } from '../nodeMaterialBlockConnectionPoint';
+
+/**
+ * Block used to transform a vector3 with a matrix
+ */
+export class Vector3TransformBlock extends NodeMaterialBlock {
+    /**
+     * Defines the value to use to complement Vector3 to transform it to a Vector4
+     */
+    public complement = 1;
+
+    /**
+     * Creates a new Vector3TransformBlock
+     * @param name defines the block name
+     */
+    public constructor(name: string) {
+        super(name, NodeMaterialBlockTargets.Vertex);
+
+        this.registerInput("vector", NodeMaterialBlockConnectionPointTypes.Vector3);
+        this.registerInput("transform", NodeMaterialBlockConnectionPointTypes.Matrix);
+        this.registerOutput("output", NodeMaterialBlockConnectionPointTypes.Vector4);
+    }
+
+    /**
+     * Gets the vector input
+     */
+    public get vector(): NodeMaterialConnectionPoint {
+        return this._inputs[0];
+    }
+
+    /**
+     * Gets the matrix transform input
+     */
+    public get transform(): NodeMaterialConnectionPoint {
+        return this._inputs[1];
+    }
+
+    /**
+     * Gets the current class name
+     * @returns the class name
+     */
+    public getClassName() {
+        return "Vector3TransformBlock";
+    }
+
+    protected _buildBlock(state: NodeMaterialBuildState) {
+        super._buildBlock(state);
+
+        let output = this._outputs[0];
+        let vector = this.vector;
+        let transform = this.transform;
+
+        state.compilationString += this._declareOutput(output, state) + ` = ${transform.associatedVariableName} * vec4(${vector.associatedVariableName}, ${this.complement});\r\n`;
+
+        return this;
+    }
+}

+ 65 - 0
src/Materials/Node/Blocks/vector4TransformBlock.ts

@@ -0,0 +1,65 @@
+import { NodeMaterialBlock } from '../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialBuildState } from '../nodeMaterialBuildState';
+import { NodeMaterialBlockTargets } from '../nodeMaterialBlockTargets';
+import { NodeMaterialConnectionPoint } from '../nodeMaterialBlockConnectionPoint';
+
+/**
+ * Block used to transform a vector4 with a matrix
+ */
+export class Vector4TransformBlock extends NodeMaterialBlock {
+    /**
+     * Defines the value to use to complement Vector3 to transform it to a Vector4
+     */
+    public complementW = 1;
+
+    /**
+     * Creates a new Vector4TransformBlock
+     * @param name defines the block name
+     */
+    public constructor(name: string) {
+        super(name, NodeMaterialBlockTargets.Vertex);
+
+        this.registerInput("vector", NodeMaterialBlockConnectionPointTypes.Vector3OrVector4);
+        this.registerInput("transform", NodeMaterialBlockConnectionPointTypes.Matrix);
+        this.registerOutput("output", NodeMaterialBlockConnectionPointTypes.Vector4);
+    }
+
+    /**
+     * Gets the current class name
+     * @returns the class name
+     */
+    public getClassName() {
+        return "Vector4TransformBlock";
+    }
+
+    /**
+     * Gets the vector input
+     */
+    public get vector(): NodeMaterialConnectionPoint {
+        return this._inputs[0];
+    }
+
+    /**
+     * Gets the matrix transform input
+     */
+    public get transform(): NodeMaterialConnectionPoint {
+        return this._inputs[1];
+    }
+
+    protected _buildBlock(state: NodeMaterialBuildState) {
+        super._buildBlock(state);
+
+        let output = this._outputs[0];
+        let vector = this.vector;
+        let transform = this.transform;
+
+        if (vector.connectedPoint!.type === NodeMaterialBlockConnectionPointTypes.Vector3) {
+            state.compilationString += this._declareOutput(output, state) + ` = ${transform.associatedVariableName} * vec4(${vector.associatedVariableName}, ${this._writeFloat(this.complementW)});\r\n`;
+        } else {
+            state.compilationString += this._declareOutput(output, state) + ` = ${transform.associatedVariableName} * ${vector.associatedVariableName};\r\n`;
+        }
+
+        return this;
+    }
+}

+ 1 - 0
src/Materials/Node/Optimizers/index.ts

@@ -0,0 +1 @@
+export * from "./nodeMaterialOptimizer";

+ 15 - 0
src/Materials/Node/Optimizers/nodeMaterialOptimizer.ts

@@ -0,0 +1,15 @@
+import { NodeMaterialBlock } from '../nodeMaterialBlock';
+
+/**
+ * Root class for all node material optimizers
+ */
+export class NodeMaterialOptimizer {
+    /**
+     * Function used to optimize a NodeMaterial graph
+     * @param vertexOutputNodes defines the list of output nodes for the vertex shader
+     * @param fragmentOutputNodes defines the list of output nodes for the fragment shader
+     */
+    public optimize(vertexOutputNodes: NodeMaterialBlock[], fragmentOutputNodes: NodeMaterialBlock[]) {
+        // Do nothing by default
+    }
+}

+ 8 - 0
src/Materials/Node/index.ts

@@ -0,0 +1,8 @@
+export * from "./nodeMaterialBlockTargets";
+export * from "./nodeMaterialBlockConnectionPointTypes";
+export * from "./nodeMaterialBlockConnectionPoint";
+export * from "./nodeMaterialBlock";
+export * from "./nodeMaterial";
+export * from "./nodeMaterialWellKnownValues";
+export * from "./Blocks/index";
+export * from "./Optimizers/index";

+ 721 - 0
src/Materials/Node/nodeMaterial.ts

@@ -0,0 +1,721 @@
+import { NodeMaterialBlock } from './nodeMaterialBlock';
+import { PushMaterial } from '../pushMaterial';
+import { Scene } from '../../scene';
+import { AbstractMesh } from '../../Meshes/abstractMesh';
+import { Matrix } from '../../Maths/math';
+import { Mesh } from '../../Meshes/mesh';
+import { Engine } from '../../Engines/engine';
+import { NodeMaterialBuildState } from './nodeMaterialBuildState';
+import { EffectCreationOptions, EffectFallbacks } from '../effect';
+import { BaseTexture } from '../../Materials/Textures/baseTexture';
+import { NodeMaterialConnectionPoint } from './nodeMaterialBlockConnectionPoint';
+import { NodeMaterialBlockConnectionPointTypes } from './nodeMaterialBlockConnectionPointTypes';
+import { Observable, Observer } from '../../Misc/observable';
+import { NodeMaterialBlockTargets } from './nodeMaterialBlockTargets';
+import { NodeMaterialBuildStateSharedData } from './nodeMaterialBuildStateSharedData';
+import { SubMesh } from '../../Meshes/subMesh';
+import { MaterialDefines } from '../../Materials/materialDefines';
+import { NodeMaterialOptimizer } from './Optimizers/nodeMaterialOptimizer';
+import { ImageProcessingConfiguration, IImageProcessingConfigurationDefines } from '../imageProcessingConfiguration';
+import { Nullable } from '../../types';
+import { VertexBuffer } from '../../Meshes/buffer';
+
+/** @hidden */
+export class NodeMaterialDefines extends MaterialDefines implements IImageProcessingConfigurationDefines {
+    /** BONES */
+    public NUM_BONE_INFLUENCERS = 0;
+    public BonesPerMesh = 0;
+    public BONETEXTURE = false;
+
+    /** MORPH TARGETS */
+    public MORPHTARGETS = false;
+    public MORPHTARGETS_NORMAL = false;
+    public MORPHTARGETS_TANGENT = false;
+    public NUM_MORPH_INFLUENCERS = 0;
+
+    /** IMAGE PROCESSING */
+    public IMAGEPROCESSING = false;
+    public VIGNETTE = false;
+    public VIGNETTEBLENDMODEMULTIPLY = false;
+    public VIGNETTEBLENDMODEOPAQUE = false;
+    public TONEMAPPING = false;
+    public TONEMAPPING_ACES = false;
+    public CONTRAST = false;
+    public EXPOSURE = false;
+    public COLORCURVES = false;
+    public COLORGRADING = false;
+    public COLORGRADING3D = false;
+    public SAMPLER3DGREENDEPTH = false;
+    public SAMPLER3DBGRMAP = false;
+    public IMAGEPROCESSINGPOSTPROCESS = false;
+
+    constructor() {
+        super();
+        this.rebuild();
+    }
+
+    public setValue(name: string, value: boolean) {
+        if (this[name] === undefined) {
+            this._keys.push(name);
+        }
+
+        this[name] = value;
+    }
+}
+
+/**
+ * Class used to configure NodeMaterial
+ */
+export interface INodeMaterialOptions {
+    /**
+     * Defines if blocks should emit comments
+     */
+    emitComments: boolean;
+}
+
+/**
+ * Class used to create a node based material built by assembling shader blocks
+ */
+export class NodeMaterial extends PushMaterial {
+    private _options: INodeMaterialOptions;
+    private _vertexCompilationState: NodeMaterialBuildState;
+    private _fragmentCompilationState: NodeMaterialBuildState;
+    private _sharedData: NodeMaterialBuildStateSharedData;
+    private _buildId: number = 0;
+    private _buildWasSuccessful = false;
+    private _cachedWorldViewMatrix = new Matrix();
+    private _cachedWorldViewProjectionMatrix = new Matrix();
+    private _textureConnectionPoints = new Array<NodeMaterialConnectionPoint>();
+    private _optimizers = new Array<NodeMaterialOptimizer>();
+
+    /**
+    * Defines the maximum number of lights that can be used in the material
+    */
+    public maxSimultaneousLights = 4;
+
+    /**
+     * Observable raised when the material is built
+     */
+    public onBuildObservable = new Observable<NodeMaterial>();
+
+    /**
+     * Gets or sets the root nodes of the material vertex shader
+     */
+    public _vertexOutputNodes = new Array<NodeMaterialBlock>();
+
+    /**
+     * Gets or sets the root nodes of the material fragment (pixel) shader
+     */
+    public _fragmentOutputNodes = new Array<NodeMaterialBlock>();
+
+    /** Gets or sets options to control the node material overall behavior */
+    public get options() {
+        return this._options;
+    }
+
+    public set options(options: INodeMaterialOptions) {
+        this._options = options;
+    }
+
+    /**
+     * Default configuration related to image processing available in the standard Material.
+     */
+    protected _imageProcessingConfiguration: ImageProcessingConfiguration;
+
+    /**
+     * Gets the image processing configuration used either in this material.
+     */
+    public get imageProcessingConfiguration(): ImageProcessingConfiguration {
+        return this._imageProcessingConfiguration;
+    }
+
+    /**
+     * Sets the Default image processing configuration used either in the this material.
+     *
+     * If sets to null, the scene one is in use.
+     */
+    public set imageProcessingConfiguration(value: ImageProcessingConfiguration) {
+        this._attachImageProcessingConfiguration(value);
+
+        // Ensure the effect will be rebuilt.
+        this._markAllSubMeshesAsTexturesDirty();
+    }
+
+    /**
+     * Create a new node based material
+     * @param name defines the material name
+     * @param scene defines the hosting scene
+     * @param options defines creation option
+     */
+    constructor(name: string, scene?: Scene, options: Partial<INodeMaterialOptions> = {}) {
+        super(name, scene || Engine.LastCreatedScene!);
+
+        this._options = {
+            emitComments: false,
+            ...options
+        };
+
+        // Setup the default processing configuration to the scene.
+        this._attachImageProcessingConfiguration(null);
+    }
+
+    /**
+     * Gets the current class name of the material e.g. "NodeMaterial"
+     * @returns the class name
+     */
+    public getClassName(): string {
+        return "NodeMaterial";
+    }
+
+    /**
+     * Keep track of the image processing observer to allow dispose and replace.
+     */
+    private _imageProcessingObserver: Nullable<Observer<ImageProcessingConfiguration>>;
+
+    /**
+     * Attaches a new image processing configuration to the Standard Material.
+     * @param configuration
+     */
+    protected _attachImageProcessingConfiguration(configuration: Nullable<ImageProcessingConfiguration>): void {
+        if (configuration === this._imageProcessingConfiguration) {
+            return;
+        }
+
+        // Detaches observer.
+        if (this._imageProcessingConfiguration && this._imageProcessingObserver) {
+            this._imageProcessingConfiguration.onUpdateParameters.remove(this._imageProcessingObserver);
+        }
+
+        // Pick the scene configuration if needed.
+        if (!configuration) {
+            this._imageProcessingConfiguration = this.getScene().imageProcessingConfiguration;
+        }
+        else {
+            this._imageProcessingConfiguration = configuration;
+        }
+
+        // Attaches observer.
+        if (this._imageProcessingConfiguration) {
+            this._imageProcessingObserver = this._imageProcessingConfiguration.onUpdateParameters.add(() => {
+                this._markAllSubMeshesAsImageProcessingDirty();
+            });
+        }
+    }
+
+    /**
+     * Adds a new optimizer to the list of optimizers
+     * @param optimizer defines the optimizers to add
+     * @returns the current material
+     */
+    public registerOptimizer(optimizer: NodeMaterialOptimizer) {
+        let index = this._optimizers.indexOf(optimizer);
+
+        if (index > -1) {
+            return;
+        }
+
+        this._optimizers.push(optimizer);
+
+        return this;
+    }
+
+    /**
+     * Remove an optimizer from the list of optimizers
+     * @param optimizer defines the optimizers to remove
+     * @returns the current material
+     */
+    public unregisterOptimizer(optimizer: NodeMaterialOptimizer) {
+        let index = this._optimizers.indexOf(optimizer);
+
+        if (index === -1) {
+            return;
+        }
+
+        this._optimizers.splice(index, 1);
+
+        return this;
+    }
+
+    /**
+     * Add a new block to the list of output nodes
+     * @param node defines the node to add
+     * @returns the current material
+     */
+    public addOutputNode(node: NodeMaterialBlock) {
+        if (node.target === null) {
+            throw "This node is not meant to be an output node. You may want to explicitly set its target value.";
+        }
+
+        if ((node.target & NodeMaterialBlockTargets.Vertex) !== 0) {
+            this._addVertexOutputNode(node);
+        }
+
+        if ((node.target & NodeMaterialBlockTargets.Fragment) !== 0) {
+            this._addFragmentOutputNode(node);
+        }
+
+        return this;
+    }
+
+    /**
+     * Remove a block from the list of root nodes
+     * @param node defines the node to remove
+     * @returns the current material
+     */
+    public removeOutputNode(node: NodeMaterialBlock) {
+        if (node.target === null) {
+            return this;
+        }
+
+        if ((node.target & NodeMaterialBlockTargets.Vertex) !== 0) {
+            this._removeVertexOutputNode(node);
+        }
+
+        if ((node.target & NodeMaterialBlockTargets.Fragment) !== 0) {
+            this._removeFragmentOutputNode(node);
+        }
+
+        return this;
+    }
+
+    private _addVertexOutputNode(node: NodeMaterialBlock) {
+        if (this._vertexOutputNodes.indexOf(node) !== -1) {
+            return;
+        }
+
+        node.target = NodeMaterialBlockTargets.Vertex;
+        this._vertexOutputNodes.push(node);
+
+        return this;
+    }
+
+    private _removeVertexOutputNode(node: NodeMaterialBlock) {
+        let index = this._vertexOutputNodes.indexOf(node);
+        if (index === -1) {
+            return;
+        }
+
+        this._vertexOutputNodes.splice(index, 1);
+
+        return this;
+    }
+
+    private _addFragmentOutputNode(node: NodeMaterialBlock) {
+        if (this._fragmentOutputNodes.indexOf(node) !== -1) {
+            return;
+        }
+
+        node.target = NodeMaterialBlockTargets.Fragment;
+        this._fragmentOutputNodes.push(node);
+
+        return this;
+    }
+
+    private _removeFragmentOutputNode(node: NodeMaterialBlock) {
+        let index = this._fragmentOutputNodes.indexOf(node);
+        if (index === -1) {
+            return;
+        }
+
+        this._fragmentOutputNodes.splice(index, 1);
+
+        return this;
+    }
+
+    /**
+     * Specifies if the material will require alpha blending
+     * @returns a boolean specifying if alpha blending is needed
+     */
+    public needAlphaBlending(): boolean {
+        return (this.alpha < 1.0) || this._sharedData.hints.needAlphaBlending;
+    }
+
+    /**
+     * Specifies if this material should be rendered in alpha test mode
+     * @returns a boolean specifying if an alpha test is needed.
+     */
+    public needAlphaTesting(): boolean {
+        return this._sharedData.hints.needAlphaTesting;
+    }
+
+    private _initializeBlock(node: NodeMaterialBlock, state: NodeMaterialBuildState) {
+        node.initialize(state);
+        node.autoConfigure();
+
+        for (var inputs of node.inputs) {
+            let connectedPoint = inputs.connectedPoint;
+            if (connectedPoint) {
+                let block = connectedPoint.ownerBlock;
+                if (block !== node) {
+                    this._initializeBlock(block, state);
+                }
+            }
+        }
+    }
+
+    private _resetDualBlocks(node: NodeMaterialBlock, id: number) {
+        if (node.target === NodeMaterialBlockTargets.VertexAndFragment) {
+            node.buildId = id;
+        }
+
+        for (var inputs of node.inputs) {
+            let connectedPoint = inputs.connectedPoint;
+            if (connectedPoint) {
+                let block = connectedPoint.ownerBlock;
+                if (block !== node) {
+                    this._resetDualBlocks(block, id);
+                }
+            }
+        }
+    }
+
+    /**
+     * Build the material and generates the inner effect
+     * @param verbose defines if the build should log activity
+     */
+    public build(verbose: boolean = false) {
+        this._buildWasSuccessful = false;
+        if (this._vertexOutputNodes.length === 0) {
+            throw "You must define at least one vertexOutputNode";
+        }
+
+        if (this._fragmentOutputNodes.length === 0) {
+            throw "You must define at least one fragmentOutputNode";
+        }
+
+        // Compilation state
+        this._vertexCompilationState = new NodeMaterialBuildState();
+        this._vertexCompilationState.target = NodeMaterialBlockTargets.Vertex;
+        this._fragmentCompilationState = new NodeMaterialBuildState();
+        this._fragmentCompilationState.target = NodeMaterialBlockTargets.Fragment;
+
+        // Shared data
+        this._sharedData = new NodeMaterialBuildStateSharedData();
+        this._vertexCompilationState.sharedData = this._sharedData;
+        this._fragmentCompilationState.sharedData = this._sharedData;
+        this._sharedData.buildId = this._buildId;
+        this._sharedData.emitComments = this._options.emitComments;
+        this._sharedData.verbose = verbose;
+
+        // Initialize blocks
+        for (var vertexOutputNode of this._vertexOutputNodes) {
+            this._initializeBlock(vertexOutputNode, this._vertexCompilationState);
+        }
+
+        for (var fragmentOutputNode of this._fragmentOutputNodes) {
+            this._initializeBlock(fragmentOutputNode, this._fragmentCompilationState);
+        }
+
+        // Optimize
+        this.optimize();
+
+        // Vertex
+        for (var vertexOutputNode of this._vertexOutputNodes) {
+            vertexOutputNode.build(this._vertexCompilationState);
+        }
+
+        // Fragment
+        this._fragmentCompilationState._vertexState = this._vertexCompilationState;
+
+        for (var fragmentOutputNode of this._fragmentOutputNodes) {
+            this._resetDualBlocks(fragmentOutputNode, this._buildId - 1);
+        }
+
+        for (var fragmentOutputNode of this._fragmentOutputNodes) {
+            fragmentOutputNode.build(this._fragmentCompilationState);
+        }
+
+        // Finalize
+        this._vertexCompilationState.finalize(this._vertexCompilationState);
+        this._fragmentCompilationState.finalize(this._fragmentCompilationState);
+
+        // Textures
+        this._textureConnectionPoints =
+            this._sharedData.uniformConnectionPoints.filter((u) => u.type === NodeMaterialBlockConnectionPointTypes.Texture || u.type === NodeMaterialBlockConnectionPointTypes.Texture3D);
+
+        this._buildId++;
+
+        // Errors
+        this._sharedData.emitErrors();
+
+        if (verbose) {
+            console.log("Vertex shader:");
+            console.log(this._vertexCompilationState.compilationString);
+            console.log("Fragment shader:");
+            console.log(this._fragmentCompilationState.compilationString);
+        }
+
+        this._buildWasSuccessful = true;
+        this.onBuildObservable.notifyObservers(this);
+
+        this._markAllSubMeshesAsAllDirty();
+    }
+
+    /**
+     * Runs an otpimization phase to try to improve the shader code
+     */
+    public optimize() {
+        for (var optimizer of this._optimizers) {
+            optimizer.optimize(this._vertexOutputNodes, this._fragmentOutputNodes);
+        }
+    }
+
+    private _prepareDefinesForAttributes(mesh: AbstractMesh, defines: NodeMaterialDefines) {
+        if (!defines._areAttributesDirty && defines._needNormals === defines._normals && defines._needUVs === defines._uvs) {
+            return;
+        }
+
+        defines._normals = defines._needNormals;
+        defines._uvs = defines._needUVs;
+
+        defines.setValue("NORMAL", (defines._needNormals && mesh.isVerticesDataPresent(VertexBuffer.NormalKind)));
+        defines.setValue("TANGENT", mesh.isVerticesDataPresent(VertexBuffer.TangentKind));
+    }
+
+    /**
+      * Get if the submesh is ready to be used and all its information available.
+      * Child classes can use it to update shaders
+      * @param mesh defines the mesh to check
+      * @param subMesh defines which submesh to check
+      * @param useInstances specifies that instances should be used
+      * @returns a boolean indicating that the submesh is ready or not
+      */
+    public isReadyForSubMesh(mesh: AbstractMesh, subMesh: SubMesh, useInstances: boolean = false): boolean {
+        if (!this._buildWasSuccessful) {
+            return false;
+        }
+
+        if (subMesh.effect && this.isFrozen) {
+            if (this._wasPreviouslyReady) {
+                return true;
+            }
+        }
+
+        if (!subMesh._materialDefines) {
+            subMesh._materialDefines = new NodeMaterialDefines();
+        }
+
+        var scene = this.getScene();
+        var defines = <NodeMaterialDefines>subMesh._materialDefines;
+        if (!this.checkReadyOnEveryCall && subMesh.effect) {
+            if (defines._renderId === scene.getRenderId()) {
+                return true;
+            }
+        }
+
+        var engine = scene.getEngine();
+
+        this._prepareDefinesForAttributes(mesh, defines);
+
+        // Check if blocks are ready
+        if (this._sharedData.blockingBlocks.some((b) => !b.isReady(mesh, this, defines, useInstances))) {
+            return false;
+        }
+
+        // Shared defines
+        this._sharedData.blocksWithDefines.forEach((b) => {
+            b.prepareDefines(mesh, this, defines, useInstances);
+        });
+
+        // Need to recompile?
+        if (defines.isDirty) {
+            defines.markAsProcessed();
+
+            // Repeatable content generators
+            this._vertexCompilationState.compilationString = this._vertexCompilationState._builtCompilationString;
+            this._fragmentCompilationState.compilationString = this._fragmentCompilationState._builtCompilationString;
+
+            this._sharedData.repeatableContentBlocks.forEach((b) => {
+                b.replaceRepeatableContent(this._vertexCompilationState, this._fragmentCompilationState, mesh, defines);
+            });
+
+            // Uniforms
+            let mergedUniforms = this._vertexCompilationState.uniforms;
+
+            this._fragmentCompilationState.uniforms.forEach((u) => {
+                let index = mergedUniforms.indexOf(u);
+
+                if (index === -1) {
+                    mergedUniforms.push(u);
+                }
+            });
+
+            // Samplers
+            let mergedSamplers = this._vertexCompilationState.samplers;
+
+            this._fragmentCompilationState.samplers.forEach((s) => {
+                let index = mergedSamplers.indexOf(s);
+
+                if (index === -1) {
+                    mergedSamplers.push(s);
+                }
+            });
+
+            var fallbacks = new EffectFallbacks();
+
+            this._sharedData.blocksWithFallbacks.forEach((b) => {
+                b.provideFallbacks(mesh, fallbacks);
+            });
+
+            let previousEffect = subMesh.effect;
+            // Compilation
+            var join = defines.toString();
+            var effect = engine.createEffect({
+                vertex: "nodeMaterial" + this._buildId,
+                fragment: "nodeMaterial" + this._buildId,
+                vertexSource: this._vertexCompilationState.compilationString,
+                fragmentSource: this._fragmentCompilationState.compilationString
+            }, <EffectCreationOptions>{
+                attributes: this._vertexCompilationState.attributes,
+                uniformsNames: mergedUniforms,
+                samplers: mergedSamplers,
+                defines: join,
+                fallbacks: fallbacks,
+                onCompiled: this.onCompiled,
+                onError: this.onError,
+                indexParameters: { maxSimultaneousLights: this.maxSimultaneousLights, maxSimultaneousMorphTargets: defines.NUM_MORPH_INFLUENCERS }
+            }, engine);
+
+            if (effect) {
+                // Use previous effect while new one is compiling
+                if (this.allowShaderHotSwapping && previousEffect && !effect.isReady()) {
+                    effect = previousEffect;
+                    defines.markAsUnprocessed();
+                } else {
+                    scene.resetCachedMaterial();
+                    subMesh.setEffect(effect, defines);
+                }
+            }
+        }
+
+        if (!subMesh.effect || !subMesh.effect.isReady()) {
+            return false;
+        }
+
+        defines._renderId = scene.getRenderId();
+        this._wasPreviouslyReady = true;
+
+        return true;
+    }
+
+    /**
+     * Binds the world matrix to the material
+     * @param world defines the world transformation matrix
+     */
+    public bindOnlyWorldMatrix(world: Matrix): void {
+        var scene = this.getScene();
+
+        if (!this._activeEffect) {
+            return;
+        }
+
+        let hints = this._sharedData.hints;
+
+        if (hints.needWorldViewMatrix) {
+            world.multiplyToRef(scene.getViewMatrix(), this._cachedWorldViewMatrix);
+        }
+
+        if (hints.needWorldViewProjectionMatrix) {
+            world.multiplyToRef(scene.getTransformMatrix(), this._cachedWorldViewProjectionMatrix);
+        }
+
+        // Connection points
+        for (var connectionPoint of this._sharedData.uniformConnectionPoints) {
+            connectionPoint.transmitWorld(this._activeEffect, world, this._cachedWorldViewMatrix, this._cachedWorldViewProjectionMatrix);
+        }
+    }
+
+    /**
+     * Binds the submesh to this material by preparing the effect and shader to draw
+     * @param world defines the world transformation matrix
+     * @param mesh defines the mesh containing the submesh
+     * @param subMesh defines the submesh to bind the material to
+     */
+    public bindForSubMesh(world: Matrix, mesh: Mesh, subMesh: SubMesh): void {
+        let scene = this.getScene();
+        var effect = subMesh.effect;
+        if (!effect) {
+            return;
+        }
+        this._activeEffect = effect;
+
+        // Matrices
+        this.bindOnlyWorldMatrix(world);
+
+        let mustRebind = this._mustRebind(scene, effect, mesh.visibility);
+
+        if (mustRebind) {
+            let sharedData = this._sharedData;
+            if (effect && scene.getCachedMaterial() !== this) {
+                // Bindable blocks
+                for (var block of sharedData.bindableBlocks) {
+                    block.bind(effect, this, mesh);
+                }
+
+                // Connection points
+                for (var connectionPoint of sharedData.uniformConnectionPoints) {
+                    connectionPoint.transmit(effect, scene);
+                }
+            }
+        }
+
+        this._afterBind(mesh, this._activeEffect);
+    }
+
+    /**
+     * Gets the active textures from the material
+     * @returns an array of textures
+     */
+    public getActiveTextures(): BaseTexture[] {
+        var activeTextures = super.getActiveTextures();
+
+        for (var connectionPoint of this._textureConnectionPoints) {
+            if (connectionPoint.value) {
+                activeTextures.push(connectionPoint.value);
+            }
+        }
+
+        return activeTextures;
+    }
+
+    /**
+     * Specifies if the material uses a texture
+     * @param texture defines the texture to check against the material
+     * @returns a boolean specifying if the material uses the texture
+     */
+    public hasTexture(texture: BaseTexture): boolean {
+        if (super.hasTexture(texture)) {
+            return true;
+        }
+
+        for (var connectionPoint of this._textureConnectionPoints) {
+            if (connectionPoint.value === texture) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Disposes the material
+     * @param forceDisposeEffect specifies if effects should be forcefully disposed
+     * @param forceDisposeTextures specifies if textures should be forcefully disposed
+     * @param notBoundToMesh specifies if the material that is being disposed is known to be not bound to any mesh
+     */
+    public dispose(forceDisposeEffect?: boolean, forceDisposeTextures?: boolean, notBoundToMesh?: boolean): void {
+
+        if (forceDisposeTextures) {
+            for (var connectionPoint of this._textureConnectionPoints) {
+                if (connectionPoint.value) {
+                    (connectionPoint.value as BaseTexture).dispose();
+                }
+            }
+        }
+
+        this._textureConnectionPoints = [];
+        this.onBuildObservable.clear();
+
+        super.dispose(forceDisposeEffect, forceDisposeTextures, notBoundToMesh);
+    }
+}

+ 420 - 0
src/Materials/Node/nodeMaterialBlock.ts

@@ -0,0 +1,420 @@
+import { NodeMaterialBlockConnectionPointTypes } from './nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialBuildState } from './nodeMaterialBuildState';
+import { Nullable } from '../../types';
+import { NodeMaterialConnectionPoint } from './nodeMaterialBlockConnectionPoint';
+import { NodeMaterialBlockTargets } from './nodeMaterialBlockTargets';
+import { Effect, EffectFallbacks } from '../effect';
+import { AbstractMesh } from '../../Meshes/abstractMesh';
+import { Mesh } from '../../Meshes/mesh';
+import { NodeMaterial, NodeMaterialDefines } from './nodeMaterial';
+
+/**
+ * Defines a block that can be used inside a node based material
+ */
+export class NodeMaterialBlock {
+    private _buildId: number;
+    private _target: NodeMaterialBlockTargets;
+    private _isFinalMerger = false;
+
+    /** @hidden */
+    public _inputs = new Array<NodeMaterialConnectionPoint>();
+    /** @hidden */
+    public _outputs = new Array<NodeMaterialConnectionPoint>();
+
+    /**
+     * Gets or sets the name of the block
+     */
+    public name: string;
+
+    /**
+     * Gets a boolean indicating that this block is an end block (e.g. it is generating a system value)
+     */
+    public get isFinalMerger(): boolean {
+        return this._isFinalMerger;
+    }
+
+    /**
+     * Gets or sets the build Id
+     */
+    public get buildId(): number {
+        return this._buildId;
+    }
+
+    public set buildId(value: number) {
+        this._buildId = value;
+    }
+
+    /**
+     * Gets or sets the target of the block
+     */
+    public get target() {
+        return this._target;
+    }
+
+    public set target(value: NodeMaterialBlockTargets) {
+        if ((this._target & value) !== 0) {
+            return;
+        }
+        this._target = value;
+    }
+
+    /**
+     * Gets the list of input points
+     */
+    public get inputs(): NodeMaterialConnectionPoint[] {
+        return this._inputs;
+    }
+
+    /** Gets the list of output points */
+    public get outputs(): NodeMaterialConnectionPoint[] {
+        return this._outputs;
+    }
+
+    /**
+     * Find an input by its name
+     * @param name defines the name of the input to look for
+     * @returns the input or null if not found
+     */
+    public getInputByName(name: string) {
+        let filter = this._inputs.filter((e) => e.name === name);
+
+        if (filter.length) {
+            return filter[0];
+        }
+
+        return null;
+    }
+
+    /**
+     * Find an output by its name
+     * @param name defines the name of the outputto look for
+     * @returns the output or null if not found
+     */
+    public getOutputByName(name: string) {
+        let filter = this._outputs.filter((e) => e.name === name);
+
+        if (filter.length) {
+            return filter[0];
+        }
+
+        return null;
+    }
+
+    /**
+     * Creates a new NodeMaterialBlock
+     * @param name defines the block name
+     * @param target defines the target of that block (Vertex by default)
+     * @param isFinalMerger defines a boolean indicating that this block is an end block (e.g. it is generating a system value). Default is false
+     */
+    public constructor(name: string, target = NodeMaterialBlockTargets.Vertex, isFinalMerger = false) {
+        this.name = name;
+
+        this._target = target;
+
+        if (isFinalMerger) {
+            this._isFinalMerger = true;
+        }
+    }
+
+    /**
+     * Initialize the block and prepare the context for build
+     * @param state defines the state that will be used for the build
+     */
+    public initialize(state: NodeMaterialBuildState) {
+        // Do nothing
+    }
+
+    /**
+     * Bind data to effect. Will only be called for blocks with isBindable === true
+     * @param effect defines the effect to bind data to
+     * @param nodeMaterial defines the hosting NodeMaterial
+     * @param mesh defines the mesh that will be rendered
+     */
+    public bind(effect: Effect, nodeMaterial: NodeMaterial, mesh?: Mesh) {
+        // Do nothing
+    }
+
+    protected _declareOutput(output: NodeMaterialConnectionPoint, state: NodeMaterialBuildState): string {
+        if (output.isVarying) {
+            return `${output.associatedVariableName}`;
+        }
+
+        return `${state._getGLType(output.type)} ${output.associatedVariableName}`;
+    }
+
+    protected _writeVariable(currentPoint: NodeMaterialConnectionPoint): string {
+        let connectionPoint = currentPoint.connectedPoint!;
+        return `${currentPoint.associatedVariableName}${connectionPoint.swizzle ? "." + connectionPoint.swizzle : ""}`;
+    }
+
+    protected _writeFloat(value: number) {
+        let stringVersion = value.toString();
+
+        if (stringVersion.indexOf(".") === -1) {
+            stringVersion += ".0";
+        }
+        return `${stringVersion}`;
+    }
+
+    /**
+     * Gets the current class name e.g. "NodeMaterialBlock"
+     * @returns the class name
+     */
+    public getClassName() {
+        return "NodeMaterialBlock";
+    }
+
+    /**
+     * Register a new input. Must be called inside a block constructor
+     * @param name defines the connection point name
+     * @param type defines the connection point type
+     * @param isOptional defines a boolean indicating that this input can be omitted
+     * @param target defines the target to use to limit the connection point (will be VetexAndFragment by default)
+     * @returns the current block
+     */
+    public registerInput(name: string, type: NodeMaterialBlockConnectionPointTypes, isOptional: boolean = false, target?: NodeMaterialBlockTargets) {
+        let point = new NodeMaterialConnectionPoint(name, this);
+        point.type = type;
+        point.isOptional = isOptional;
+        if (target) {
+            point.target = target;
+        }
+
+        this._inputs.push(point);
+
+        return this;
+    }
+
+    /**
+     * Register a new output. Must be called inside a block constructor
+     * @param name defines the connection point name
+     * @param type defines the connection point type
+     * @param target defines the target to use to limit the connection point (will be VetexAndFragment by default)
+     * @returns the current block
+     */
+    public registerOutput(name: string, type: NodeMaterialBlockConnectionPointTypes, target?: NodeMaterialBlockTargets) {
+        let point = new NodeMaterialConnectionPoint(name, this);
+        point.type = type;
+        if (target) {
+            point.target = target;
+        }
+
+        this._outputs.push(point);
+
+        return this;
+    }
+
+    /**
+     * Will return the first available input e.g. the first one which is not an uniform or an attribute
+     * @param forOutput defines an optional connection point to check compatibility with
+     * @returns the first available input or null
+     */
+    public getFirstAvailableInput(forOutput: Nullable<NodeMaterialConnectionPoint> = null) {
+        for (var input of this._inputs) {
+            if (!input.isUniform && !input.isAttribute && !input.connectedPoint) {
+                if (!forOutput || (forOutput.type & input.type) !== 0) {
+                    return input;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Will return the first available output e.g. the first one which is not yet connected and not a varying
+     * @param forBlock defines an optional block to check compatibility with
+     * @returns the first available input or null
+     */
+    public getFirstAvailableOutput(forBlock: Nullable<NodeMaterialBlock> = null) {
+        for (var output of this._outputs) {
+            if (!forBlock || !forBlock.target || (forBlock.target & output.target) !== 0) {
+                return output;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Connect current block with another block
+     * @param other defines the block to connect with
+     * @param options define the various options to help pick the right connections
+     * @returns the current block
+     */
+    public connectTo(other: NodeMaterialBlock, options?: {
+        input?: string,
+        output?: string,
+        outputSwizzle?: string
+    }) {
+        if (this._outputs.length === 0) {
+            return;
+        }
+
+        let output = options && options.output ? this.getOutputByName(options.output) : this.getFirstAvailableOutput(other);
+        let input = options && options.input ? other.getInputByName(options.input) : other.getFirstAvailableInput(output);
+
+        if (output && input) {
+            output.swizzle = options ? options.outputSwizzle || "" : "";
+            output.connectTo(input);
+        } else {
+            throw "Unable to find a compatible match";
+        }
+
+        return this;
+    }
+
+    protected _buildBlock(state: NodeMaterialBuildState) {
+        // Empty. Must be defined by child nodes
+    }
+
+    /**
+     * Add potential fallbacks if shader compilation fails
+     * @param mesh defines the mesh to be rendered
+     * @param fallbacks defines the current prioritized list of fallbacks
+     */
+    public provideFallbacks(mesh: AbstractMesh, fallbacks: EffectFallbacks) {
+        // Do nothing
+    }
+
+    /**
+     * Update defines for shader compilation
+     * @param mesh defines the mesh to be rendered
+     * @param nodeMaterial defines the node material requesting the update
+     * @param defines defines the material defines to update
+     * @param useInstances specifies that instances should be used
+     */
+    public prepareDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines, useInstances: boolean = false) {
+        // Do nothing
+    }
+
+    /**
+     * Lets the block try to connect some inputs automatically
+     */
+    public autoConfigure() {
+        // Do nothing
+    }
+
+    /**
+     * Function called when a block is declared as repeatable content generator
+     * @param vertexShaderState defines the current compilation state for the vertex shader
+     * @param fragmentShaderState defines the current compilation state for the fragment shader
+     * @param mesh defines the mesh to be rendered
+     * @param defines defines the material defines to update
+     */
+    public replaceRepeatableContent(vertexShaderState: NodeMaterialBuildState, fragmentShaderState: NodeMaterialBuildState, mesh: AbstractMesh, defines: NodeMaterialDefines) {
+        // Do nothing
+    }
+
+    /**
+     * Checks if the block is ready
+     * @param mesh defines the mesh to be rendered
+     * @param nodeMaterial defines the node material requesting the update
+     * @param defines defines the material defines to update
+     * @param useInstances specifies that instances should be used
+     * @returns true if the block is ready
+     */
+    public isReady(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines, useInstances: boolean = false) {
+        return true;
+    }
+
+    /**
+     * Compile the current node and generate the shader code
+     * @param state defines the current compilation state (uniforms, samplers, current string)
+     * @returns the current block
+     */
+    public build(state: NodeMaterialBuildState) {
+        if (this._buildId === state.sharedData.buildId) {
+            return;
+        }
+
+        // Check if "parent" blocks are compiled
+        for (var input of this._inputs) {
+            if (!input.connectedPoint) {
+                if (!input.isOptional && !input.isAttribute && !input.isUniform) { // Emit a warning
+                    state.sharedData.checks.notConnectedNonOptionalInputs.push(input);
+                }
+                continue;
+            }
+
+            if ((input.target & this.target!) === 0) {
+                continue;
+            }
+
+            if ((input.target & state.target!) === 0) {
+                continue;
+            }
+
+            let block = input.connectedPoint.ownerBlock;
+            if (block && block !== this && block.buildId !== state.sharedData.buildId) {
+                block.build(state);
+            }
+        }
+
+        if (this._buildId === state.sharedData.buildId) {
+            return; // Need to check again as inputs can be connected multiple time to this endpoint
+        }
+
+        // Logs
+        if (state.sharedData.verbose) {
+            console.log(`${state.target === NodeMaterialBlockTargets.Vertex ? "Vertex shader" : "Fragment shader"}: Building ${this.name} [${this.getClassName()}]`);
+        }
+
+        /** Prepare outputs */
+        for (var output of this._outputs) {
+            if ((output.target & this.target!) === 0) {
+                continue;
+            }
+            if ((output.target & state.target!) === 0) {
+                continue;
+            }
+            output.associatedVariableName = state._getFreeVariableName(output.name);
+            state._emitVaryings(output);
+        }
+
+        // Build
+        for (var input of this._inputs) {
+            if ((input.target & this.target!) === 0) {
+                continue;
+            }
+            if ((input.target & state.target!) === 0) {
+                continue;
+            }
+            state._emitUniformOrAttributes(input);
+        }
+
+        // Checks final outputs
+        if (this.isFinalMerger) {
+            switch (state.target) {
+                case NodeMaterialBlockTargets.Vertex:
+                    state.sharedData.checks.emitVertex = true;
+                    break;
+                case NodeMaterialBlockTargets.Fragment:
+                    state.sharedData.checks.emitFragment = true;
+                    break;
+            }
+        }
+
+        if (state.sharedData.emitComments) {
+            state.compilationString += `\r\n//${this.name}\r\n`;
+        }
+
+        this._buildBlock(state);
+
+        this._buildId = state.sharedData.buildId;
+
+        // Compile connected blocks
+        for (var output of this._outputs) {
+            if ((output.target & state.target) === 0) {
+                continue;
+            }
+
+            for (var block of output.connectedBlocks) {
+                if (block && (block.target & state.target) !== 0) {
+                    block.build(state);
+                }
+            }
+        }
+        return this;
+    }
+}

+ 342 - 0
src/Materials/Node/nodeMaterialBlockConnectionPoint.ts

@@ -0,0 +1,342 @@
+import { NodeMaterialBlockConnectionPointTypes } from './nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialBlockTargets } from './nodeMaterialBlockTargets';
+import { Nullable } from '../../types';
+import { Effect } from '../effect';
+import { NodeMaterialWellKnownValues } from './nodeMaterialWellKnownValues';
+import { Scene } from '../../scene';
+import { Matrix } from '../../Maths/math';
+
+declare type NodeMaterialBlock = import("./nodeMaterialBlock").NodeMaterialBlock;
+
+/**
+ * Defines a connection point for a block
+ */
+export class NodeMaterialConnectionPoint {
+    /** @hidden */
+    public _ownerBlock: NodeMaterialBlock;
+    /** @hidden */
+    public _connectedPoint: Nullable<NodeMaterialConnectionPoint>;
+    private _associatedVariableName: string;
+    private _endpoints = new Array<NodeMaterialConnectionPoint>();
+    private _storedValue: any;
+    private _valueCallback: () => any;
+    private _isVarying = false;
+
+    /** @hidden */
+    public _wellKnownValue: Nullable<NodeMaterialWellKnownValues> = null;
+
+    /**
+     * Gets or sets the connection point type (default is float)
+     */
+    public type: NodeMaterialBlockConnectionPointTypes = NodeMaterialBlockConnectionPointTypes.Float;
+
+    /**
+     * Gets or sets the connection point name
+     */
+    public name: string;
+
+    /**
+     * Gets or sets the swizzle to apply to this connection point when reading or writing
+     */
+    public swizzle: string;
+
+    /**
+     * Gets or sets a boolean indicating that this connection point can be omitted
+     */
+    public isOptional: boolean;
+
+    /**
+     * Gets or sets a string indicating that this uniform must be defined under a #ifdef
+     */
+    public define: string;
+
+    /** Gets or sets the target of that connection point */
+    public target: NodeMaterialBlockTargets = NodeMaterialBlockTargets.VertexAndFragment;
+
+    /**
+     * Gets or sets the value of that point.
+     * Please note that this value will be ignored if valueCallback is defined
+     */
+    public get value(): any {
+        return this._storedValue;
+    }
+
+    public set value(value: any) {
+        this._storedValue = value;
+        this.isUniform = true;
+    }
+
+    /**
+     * Gets or sets a callback used to get the value of that point.
+     * Please note that setting this value will force the connection point to ignore the value property
+     */
+    public get valueCallback(): () => any {
+        return this._valueCallback;
+    }
+
+    public set valueCallback(value: () => any) {
+        this._valueCallback = value;
+        this.isUniform = true;
+    }
+
+    /**
+     * Gets or sets the associated variable name in the shader
+     */
+    public get associatedVariableName(): string {
+        if (!this._associatedVariableName && this._connectedPoint) {
+            return this._connectedPoint.associatedVariableName;
+        }
+
+        return this._associatedVariableName;
+    }
+
+    public set associatedVariableName(value: string) {
+        this._associatedVariableName = value;
+    }
+
+    /**
+     * Gets or sets a boolean indicating that this connection point is coming from an uniform.
+     * In this case the connection point name must be the name of the uniform to use.
+     * Can only be set on inputs
+     */
+    public isUniform: boolean;
+
+    /**
+     * Gets or sets a boolean indicating that this connection point is coming from an attribute.
+     * In this case the connection point name must be the name of the attribute to use
+     * Can only be set on inputs
+     */
+    public isAttribute: boolean;
+
+    /**
+     * Gets or sets a boolean indicating that this connection point is generating a varying variable.
+     * Can only be set on exit points
+     */
+    public get isVarying(): boolean {
+        return this._isVarying;
+    }
+
+    public set isVarying(value: boolean) {
+        this._isVarying = value;
+    }
+
+    /** Get the other side of the connection (if any) */
+    public get connectedPoint(): Nullable<NodeMaterialConnectionPoint> {
+        return this._connectedPoint;
+    }
+
+    /** Get the block that owns this connection point */
+    public get ownerBlock(): NodeMaterialBlock {
+        return this._ownerBlock;
+    }
+
+    /** Get the block connected on the other side of this connection (if any) */
+    public get sourceBlock(): Nullable<NodeMaterialBlock> {
+        if (!this._connectedPoint) {
+            return null;
+        }
+
+        return this._connectedPoint.ownerBlock;
+    }
+
+    /** Get the block connected on the endpoints of this connection (if any) */
+    public get connectedBlocks(): Array<NodeMaterialBlock> {
+        if (this._endpoints.length === 0) {
+            return [];
+        }
+
+        return this._endpoints.map((e) => e.ownerBlock);
+    }
+
+    /**
+     * Creates a new connection point
+     * @param name defines the connection point name
+     * @param ownerBlock defines the block hosting this connection point
+     */
+    public constructor(name: string, ownerBlock: NodeMaterialBlock) {
+        this._ownerBlock = ownerBlock;
+        this.name = name;
+    }
+
+    /**
+     * Gets the current class name e.g. "NodeMaterialConnectionPoint"
+     * @returns the class name
+     */
+    public getClassName(): string {
+        return "NodeMaterialConnectionPoint";
+    }
+
+    /**
+     * Set the source of this connection point to a vertex attribute
+     * @param attributeName defines the attribute name (position, uv, normal, etc...). If not specified it will take the connection point name
+     * @returns the current connection point
+     */
+    public setAsAttribute(attributeName?: string): NodeMaterialConnectionPoint {
+        if (attributeName) {
+            this.name = attributeName;
+        }
+        this.isAttribute = true;
+        return this;
+    }
+
+    /**
+     * Set the source of this connection point to a well known value
+     * @param value define the well known value to use (world, view, etc...)
+     * @returns the current connection point
+     */
+    public setAsWellKnownValue(value: NodeMaterialWellKnownValues): NodeMaterialConnectionPoint {
+        this.isUniform = true;
+        this._wellKnownValue = value;
+        return this;
+    }
+
+    private _getTypeLength(type: NodeMaterialBlockConnectionPointTypes) {
+        switch (type) {
+            case NodeMaterialBlockConnectionPointTypes.Float:
+                return 1;
+            case NodeMaterialBlockConnectionPointTypes.Vector2:
+                return 2;
+            case NodeMaterialBlockConnectionPointTypes.Vector3:
+            case NodeMaterialBlockConnectionPointTypes.Color3:
+                return 3;
+            case NodeMaterialBlockConnectionPointTypes.Vector4:
+            case NodeMaterialBlockConnectionPointTypes.Color4:
+                return 3;
+        }
+
+        return -1;
+    }
+
+    /**
+     * Connect this point to another connection point
+     * @param connectionPoint defines the other connection point
+     * @returns the current connection point
+     */
+    public connectTo(connectionPoint: NodeMaterialConnectionPoint): NodeMaterialConnectionPoint {
+        if ((this.type & connectionPoint.type) === 0) {
+            let fail = true;
+            // Check swizzle
+            if (this.swizzle) {
+                let swizzleLength = this.swizzle.length;
+                let connectionLength = this._getTypeLength(connectionPoint.type);
+
+                if (swizzleLength === connectionLength) {
+                    fail = false;
+                }
+            }
+
+            if (fail) {
+                throw "Cannot connect two different connection types.";
+            }
+        }
+
+        this._endpoints.push(connectionPoint);
+        connectionPoint._connectedPoint = this;
+        return this;
+    }
+
+    /**
+     * Disconnect this point from one of his endpoint
+     * @param endpoint defines the other connection point
+     * @returns the current connection point
+     */
+    public disconnectFrom(endpoint: NodeMaterialConnectionPoint): NodeMaterialConnectionPoint {
+        let index = this._endpoints.indexOf(endpoint);
+
+        if (index === -1) {
+            return this;
+        }
+
+        this._endpoints.splice(index, 1);
+        endpoint._connectedPoint = null;
+        return this;
+    }
+
+    /**
+     * When connection point is an uniform, this function will send its value to the effect
+     * @param effect defines the effect to transmit value to
+     * @param world defines the world matrix
+     * @param worldView defines the worldxview matrix
+     * @param worldViewProjection defines the worldxviewxprojection matrix
+     */
+    public transmitWorld(effect: Effect, world: Matrix, worldView: Matrix, worldViewProjection: Matrix) {
+        if (!this._wellKnownValue) {
+            return;
+        }
+
+        let variableName = this.associatedVariableName;
+        switch (this._wellKnownValue) {
+            case NodeMaterialWellKnownValues.World:
+                effect.setMatrix(variableName, world);
+                break;
+            case NodeMaterialWellKnownValues.WorldView:
+                effect.setMatrix(variableName, worldView);
+                break;
+            case NodeMaterialWellKnownValues.WorldViewProjection:
+                effect.setMatrix(variableName, worldViewProjection);
+                break;
+        }
+    }
+
+    /**
+     * When connection point is an uniform, this function will send its value to the effect
+     * @param effect defines the effect to transmit value to
+     * @param scene defines the hosting scene
+     */
+    public transmit(effect: Effect, scene: Scene) {
+        if (this._wellKnownValue) {
+            let variableName = this.associatedVariableName;
+            switch (this._wellKnownValue) {
+                case NodeMaterialWellKnownValues.World:
+                case NodeMaterialWellKnownValues.WorldView:
+                case NodeMaterialWellKnownValues.WorldViewProjection:
+                    return;
+                case NodeMaterialWellKnownValues.View:
+                    effect.setMatrix(variableName, scene.getViewMatrix());
+                    break;
+                case NodeMaterialWellKnownValues.Projection:
+                    effect.setMatrix(variableName, scene.getProjectionMatrix());
+                    break;
+                case NodeMaterialWellKnownValues.ViewProjection:
+                    effect.setMatrix(variableName, scene.getTransformMatrix());
+                    break;
+            }
+            return;
+        }
+
+        let value = this._valueCallback ? this._valueCallback() : this._storedValue;
+
+        switch (this.type) {
+            case NodeMaterialBlockConnectionPointTypes.Float:
+                effect.setFloat(this.name, value);
+                break;
+            case NodeMaterialBlockConnectionPointTypes.Int:
+                effect.setInt(this.name, value);
+                break;
+            case NodeMaterialBlockConnectionPointTypes.Color3OrColor4:
+            case NodeMaterialBlockConnectionPointTypes.Color3:
+                effect.setColor3(this.name, value);
+                break;
+            case NodeMaterialBlockConnectionPointTypes.Color4:
+                effect.setDirectColor4(this.name, value);
+                break;
+            case NodeMaterialBlockConnectionPointTypes.Vector2:
+                effect.setVector2(this.name, value);
+                break;
+            case NodeMaterialBlockConnectionPointTypes.Vector3:
+                effect.setVector3(this.name, value);
+                break;
+            case NodeMaterialBlockConnectionPointTypes.Vector4OrColor4:
+            case NodeMaterialBlockConnectionPointTypes.Vector4:
+                effect.setVector4(this.name, value);
+                break;
+            case NodeMaterialBlockConnectionPointTypes.Matrix:
+                effect.setMatrix(this.name, value);
+                break;
+            case NodeMaterialBlockConnectionPointTypes.Texture:
+            case NodeMaterialBlockConnectionPointTypes.Texture3D:
+                effect.setTexture(this.name, value);
+                break;
+        }
+    }
+}

+ 35 - 0
src/Materials/Node/nodeMaterialBlockConnectionPointTypes.ts

@@ -0,0 +1,35 @@
+/**
+ * Defines the kind of connection point for node based material
+ */
+export enum NodeMaterialBlockConnectionPointTypes {
+    /** Float */
+    Float = 1,
+    /** Int */
+    Int = 2,
+    /** Vector2 */
+    Vector2 = 4,
+    /** Vector3 */
+    Vector3 = 8,
+    /** Vector4 */
+    Vector4 = 16,
+    /** Color3 */
+    Color3 = 32,
+    /** Color4 */
+    Color4 = 64,
+    /** Matrix */
+    Matrix = 128,
+    /** Texture */
+    Texture = 256,
+    /** Texture3D */
+    Texture3D = 512,
+    /** Vector3 or Color3 */
+    Vector3OrColor3 = Vector3 | Color3,
+    /** Vector3 or Vector4 */
+    Vector3OrVector4 = Vector3 | Vector4,
+    /** Vector4 or Color4 */
+    Vector4OrColor4 = Vector4 | Color4,
+    /** Color3 or Color4 */
+    Color3OrColor4 = Color3 | Color4,
+    /** Vector3 or Color3 */
+    Vector3OrColor3OrVector4OrColor4 = Vector3 | Color3 | Vector4 | Color4,
+}

+ 11 - 0
src/Materials/Node/nodeMaterialBlockTargets.ts

@@ -0,0 +1,11 @@
+/**
+ * Enum used to define the target of a block
+ */
+export enum NodeMaterialBlockTargets {
+    /** Vertex shader */
+    Vertex = 1,
+    /** Fragment shader */
+    Fragment = 2,
+    /** Vertex and Fragment */
+    VertexAndFragment = Vertex | Fragment
+}

+ 389 - 0
src/Materials/Node/nodeMaterialBuildState.ts

@@ -0,0 +1,389 @@
+import { NodeMaterialConnectionPoint } from './nodeMaterialBlockConnectionPoint';
+import { NodeMaterialBlockConnectionPointTypes } from './nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialWellKnownValues } from './nodeMaterialWellKnownValues';
+import { NodeMaterialBlockTargets } from './nodeMaterialBlockTargets';
+import { NodeMaterialBuildStateSharedData } from './nodeMaterialBuildStateSharedData';
+import { Effect } from '../effect';
+
+/**
+ * Class used to store node based material build state
+ */
+export class NodeMaterialBuildState {
+    /**
+     * Gets the list of emitted attributes
+     */
+    public attributes = new Array<string>();
+    /**
+     * Gets the list of emitted uniforms
+     */
+    public uniforms = new Array<string>();
+    /**
+     * Gets the list of emitted samplers
+     */
+    public samplers = new Array<string>();
+    /**
+     * Gets the list of emitted functions
+     */
+    public functions: { [key: string]: string } = {};
+    /**
+     * Gets the target of the compilation state
+     */
+    public target: NodeMaterialBlockTargets;
+
+    /**
+     * Shared data between multiple NodeMaterialBuildState instances
+     */
+    public sharedData: NodeMaterialBuildStateSharedData;
+
+    /** @hidden */
+    public _vertexState: NodeMaterialBuildState;
+
+    private _attributeDeclaration = "";
+    private _uniformDeclaration = "";
+    private _samplerDeclaration = "";
+    private _varyingTransfer = "";
+
+    private _repeatableContentAnchorIndex = 0;
+    /** @hidden */
+    public _builtCompilationString = "";
+
+    /**
+     * Gets the emitted compilation strings
+     */
+    public compilationString = "";
+
+    /**
+     * Finalize the compilation strings
+     * @param state defines the current compilation state
+     */
+    public finalize(state: NodeMaterialBuildState) {
+        let emitComments = state.sharedData.emitComments;
+        let isFragmentMode = (this.target === NodeMaterialBlockTargets.Fragment);
+
+        this.compilationString = `\r\n${emitComments ? "//Entry point\r\n" : ""}void main(void) {\r\n${this.compilationString}`;
+
+        let functionCode = "";
+        for (var functionName in this.functions) {
+            functionCode += this.functions[functionName] + `\r\n`;
+        }
+        this.compilationString = `\r\n${functionCode}\r\n${this.compilationString}`;
+
+        if (!isFragmentMode && this._varyingTransfer) {
+            this.compilationString = `${this.compilationString}\r\n${this._varyingTransfer}`;
+        }
+
+        this.compilationString = `${this.compilationString}\r\n}`;
+
+        if (this.sharedData.varyingDeclaration) {
+            this.compilationString = `\r\n${emitComments ? "//Varyings\r\n" : ""}${this.sharedData.varyingDeclaration}\r\n${this.compilationString}`;
+        }
+
+        if (this._samplerDeclaration) {
+            this.compilationString = `\r\n${emitComments ? "//Samplers\r\n" : ""}${this._samplerDeclaration}\r\n${this.compilationString}`;
+        }
+
+        if (this._uniformDeclaration) {
+            this.compilationString = `\r\n${emitComments ? "//Uniforms\r\n" : ""}${this._uniformDeclaration}\r\n${this.compilationString}`;
+        }
+
+        if (this._attributeDeclaration && !isFragmentMode) {
+            this.compilationString = `\r\n${emitComments ? "//Attributes\r\n" : ""}${this._attributeDeclaration}\r\n${this.compilationString}`;
+        }
+
+        this._builtCompilationString = this.compilationString;
+    }
+
+    /** @hidden */
+    public get _repeatableContentAnchor(): string {
+        return `###___ANCHOR${this._repeatableContentAnchorIndex++}___###`;
+    }
+
+    /** @hidden */
+    public _getFreeVariableName(prefix: string): string {
+        if (this.sharedData.variableNames[prefix] === undefined) {
+            this.sharedData.variableNames[prefix] = 0;
+
+            // Check reserved words
+            if (prefix === "output" || prefix === "texture") {
+                return prefix + this.sharedData.variableNames[prefix];
+            }
+
+            return prefix;
+        } else {
+            this.sharedData.variableNames[prefix]++;
+        }
+
+        return prefix + this.sharedData.variableNames[prefix];
+    }
+
+    /** @hidden */
+    public _getFreeDefineName(prefix: string): string {
+        if (this.sharedData.defineNames[prefix] === undefined) {
+            this.sharedData.defineNames[prefix] = 0;
+        } else {
+            this.sharedData.defineNames[prefix]++;
+        }
+
+        return prefix + this.sharedData.defineNames[prefix];
+    }
+
+    /** @hidden */
+    public _excludeVariableName(name: string) {
+        this.sharedData.variableNames[name] = 0;
+    }
+
+    /** @hidden */
+    public _getGLType(type: NodeMaterialBlockConnectionPointTypes): string {
+        switch (type) {
+            case NodeMaterialBlockConnectionPointTypes.Float:
+                return "float";
+            case NodeMaterialBlockConnectionPointTypes.Int:
+                return "int";
+            case NodeMaterialBlockConnectionPointTypes.Vector2:
+                return "vec2";
+            case NodeMaterialBlockConnectionPointTypes.Color3:
+            case NodeMaterialBlockConnectionPointTypes.Vector3:
+            case NodeMaterialBlockConnectionPointTypes.Vector3OrColor3:
+            case NodeMaterialBlockConnectionPointTypes.Color3OrColor4:
+                return "vec3";
+            case NodeMaterialBlockConnectionPointTypes.Color4:
+            case NodeMaterialBlockConnectionPointTypes.Vector4:
+            case NodeMaterialBlockConnectionPointTypes.Vector4OrColor4:
+            case NodeMaterialBlockConnectionPointTypes.Vector3OrVector4:
+                return "vec4";
+            case NodeMaterialBlockConnectionPointTypes.Matrix:
+                return "mat4";
+            case NodeMaterialBlockConnectionPointTypes.Texture:
+                return "sampler2D";
+            case NodeMaterialBlockConnectionPointTypes.Texture3D:
+                return "sampler3D";
+        }
+
+        return "";
+    }
+
+    /** @hidden */
+    public _emitFunction(name: string, code: string, comments: string) {
+        if (this.functions[name]) {
+            return;
+        }
+
+        if (this.sharedData.emitComments) {
+            code = comments + `\r\n` + code;
+        }
+
+        this.functions[name] = code;
+    }
+
+    /** @hidden */
+    public _emitCodeFromInclude(includeName: string, comments: string, options?: {
+        replaceStrings?: { search: RegExp, replace: string }[],
+    }) {
+        let code = Effect.IncludesShadersStore[includeName] + "\r\n";
+
+        if (this.sharedData.emitComments) {
+            code = comments + `\r\n` + code;
+        }
+
+        if (!options) {
+            return code;
+        }
+
+        if (options.replaceStrings) {
+            for (var index = 0; index < options.replaceStrings.length; index++) {
+                let replaceString = options.replaceStrings[index];
+                code = code.replace(replaceString.search, replaceString.replace);
+            }
+        }
+
+        return code;
+    }
+
+    /** @hidden */
+    public _emitFunctionFromInclude(includeName: string, comments: string, options?: {
+        repeatKey?: string,
+        removeAttributes?: boolean,
+        removeUniforms?: boolean,
+        removeVaryings?: boolean,
+        removeIfDef?: boolean,
+        replaceStrings?: { search: RegExp, replace: string }[],
+    }) {
+        if (this.functions[includeName]) {
+            return;
+        }
+
+        if (!options || (!options.removeAttributes && !options.removeUniforms && !options.removeVaryings && !options.removeIfDef && !options.replaceStrings)) {
+
+            if (options && options.repeatKey) {
+                this.functions[includeName] = `#include<${includeName}>[0..${options.repeatKey}]\r\n`;
+            } else {
+                this.functions[includeName] = `#include<${includeName}>\r\n`;
+            }
+
+            if (this.sharedData.emitComments) {
+                this.functions[includeName] = comments + `\r\n` + this.functions[includeName];
+            }
+
+            return;
+        }
+
+        this.functions[includeName] = Effect.IncludesShadersStore[includeName];
+
+        if (this.sharedData.emitComments) {
+            this.functions[includeName] = comments + `\r\n` + this.functions[includeName];
+        }
+
+        if (options.removeIfDef) {
+            this.functions[includeName] = this.functions[includeName].replace(/^\s*?#ifdef.+$/gm, "");
+            this.functions[includeName] = this.functions[includeName].replace(/^\s*?#endif.*$/gm, "");
+            this.functions[includeName] = this.functions[includeName].replace(/^\s*?#else.*$/gm, "");
+            this.functions[includeName] = this.functions[includeName].replace(/^\s*?#elif.*$/gm, "");
+        }
+
+        if (options.removeAttributes) {
+            this.functions[includeName] = this.functions[includeName].replace(/^\s*?attribute.+$/gm, "");
+        }
+
+        if (options.removeUniforms) {
+            this.functions[includeName] = this.functions[includeName].replace(/^\s*?uniform.+$/gm, "");
+        }
+
+        if (options.removeVaryings) {
+            this.functions[includeName] = this.functions[includeName].replace(/^\s*?varying.+$/gm, "");
+        }
+
+        if (options.replaceStrings) {
+            for (var index = 0; index < options.replaceStrings.length; index++) {
+                let replaceString = options.replaceStrings[index];
+                this.functions[includeName] = this.functions[includeName].replace(replaceString.search, replaceString.replace);
+            }
+        }
+    }
+
+    /** @hidden */
+    public _emitVaryings(point: NodeMaterialConnectionPoint, define: string = "", force = false, fromFragment = false, replacementName: string = "") {
+        let name = replacementName || point.associatedVariableName;
+        if (point.isVarying || force) {
+            if (this.sharedData.varyings.indexOf(name) !== -1) {
+                return;
+            }
+
+            this.sharedData.varyings.push(name);
+
+            if (define) {
+                this.sharedData.varyingDeclaration += `#ifdef ${define}\r\n`;
+            }
+            this.sharedData.varyingDeclaration += `varying ${this._getGLType(point.type)} ${name};\r\n`;
+            if (define) {
+                this.sharedData.varyingDeclaration += `#endif\r\n`;
+            }
+
+            if (this.target === NodeMaterialBlockTargets.Vertex && fromFragment) {
+                if (define) {
+                    this.sharedData.varyingDeclaration += `#ifdef ${define}\r\n`;
+                }
+                this._varyingTransfer += `${name} = ${point.name};\r\n`;
+                if (define) {
+                    this.sharedData.varyingDeclaration += `#endif\r\n`;
+                }
+            }
+        }
+    }
+
+    private _emitDefine(define: string): string {
+        if (define[0] === "!") {
+            return `#ifndef ${define.substring(1)}\r\n`;
+        }
+
+        return `#ifdef ${define}\r\n`;
+    }
+
+    /** @hidden */
+    public _emitUniformOrAttributes(point: NodeMaterialConnectionPoint, define?: string) {
+        define = define || point.define;
+
+        // Samplers
+        if (point.type === NodeMaterialBlockConnectionPointTypes.Texture) {
+            point.name = this._getFreeVariableName(point.name);
+            point.associatedVariableName = point.name;
+
+            if (this.samplers.indexOf(point.name) !== -1) {
+                return;
+            }
+
+            this.samplers.push(point.name);
+            if (define) {
+                this._uniformDeclaration += this._emitDefine(define);
+            }
+            this._samplerDeclaration += `uniform ${this._getGLType(point.type)} ${point.name};\r\n`;
+            if (define) {
+                this._uniformDeclaration += `#endif\r\n`;
+            }
+            this.sharedData.uniformConnectionPoints.push(point);
+            return;
+        }
+
+        if (!point.isUniform && !point.isAttribute) {
+            return;
+        }
+
+        // Uniforms
+        if (point.isUniform) {
+            if (!point.associatedVariableName) {
+                point.associatedVariableName = this._getFreeVariableName(point.name);
+            }
+            if (this.uniforms.indexOf(point.associatedVariableName) !== -1) {
+                return;
+            }
+
+            this.uniforms.push(point.associatedVariableName);
+            if (define) {
+                this._uniformDeclaration += this._emitDefine(define);
+            }
+            this._uniformDeclaration += `uniform ${this._getGLType(point.type)} ${point.associatedVariableName};\r\n`;
+            if (define) {
+                this._uniformDeclaration += `#endif\r\n`;
+            }
+
+            // well known
+            let hints = this.sharedData.hints;
+            if (point._wellKnownValue !== null) {
+                switch (point._wellKnownValue) {
+                    case NodeMaterialWellKnownValues.WorldView:
+                        hints.needWorldViewMatrix = true;
+                        break;
+                    case NodeMaterialWellKnownValues.WorldViewProjection:
+                        hints.needWorldViewProjectionMatrix = true;
+                        break;
+                }
+            }
+
+            this.sharedData.uniformConnectionPoints.push(point);
+
+            return;
+        }
+
+        // Attribute
+        if (point.isAttribute) {
+            point.associatedVariableName = point.name;
+
+            if (this.target === NodeMaterialBlockTargets.Fragment) { // Attribute for fragment need to be carried over by varyings
+                this._vertexState._emitUniformOrAttributes(point);
+                return;
+            }
+
+            if (this.attributes.indexOf(point.associatedVariableName) !== -1) {
+                return;
+            }
+
+            this.attributes.push(point.associatedVariableName);
+            if (define) {
+                this._attributeDeclaration += this._emitDefine(define);
+            }
+            this._attributeDeclaration += `attribute ${this._getGLType(point.type)} ${point.associatedVariableName};\r\n`;
+            if (define) {
+                this._attributeDeclaration += `#endif\r\n`;
+            }
+        }
+    }
+}

+ 137 - 0
src/Materials/Node/nodeMaterialBuildStateSharedData.ts

@@ -0,0 +1,137 @@
+import { NodeMaterialConnectionPoint } from './nodeMaterialBlockConnectionPoint';
+import { NodeMaterialBlock } from './nodeMaterialBlock';
+
+/**
+ * Class used to store shared data between 2 NodeMaterialBuildState
+ */
+export class NodeMaterialBuildStateSharedData {
+    /**
+     * Gets the list of emitted varyings
+     */
+    public varyings = new Array<string>();
+
+    /**
+     * Gets the varying declaration string
+     */
+    public varyingDeclaration = "";
+
+    /**
+     * Uniform connection points
+     */
+    public uniformConnectionPoints = new Array<NodeMaterialConnectionPoint>();
+
+    /**
+     * Bindable blocks (Blocks that need to set data to the effect)
+     */
+    public bindableBlocks = new Array<NodeMaterialBlock>();
+
+    /**
+     * List of blocks that can provide a compilation fallback
+     */
+    public blocksWithFallbacks = new Array<NodeMaterialBlock>();
+
+    /**
+     * List of blocks that can provide a define update
+     */
+    public blocksWithDefines = new Array<NodeMaterialBlock>();
+
+    /**
+    * List of blocks that can provide a repeatable content
+    */
+    public repeatableContentBlocks = new Array<NodeMaterialBlock>();
+
+    /**
+     * List of blocks that can block the isReady function for the material
+     */
+    public blockingBlocks = new Array<NodeMaterialBlock>();
+
+    /**
+     * Build Id used to avoid multiple recompilations
+     */
+    public buildId: number;
+
+    /** List of emitted variables */
+    public variableNames: { [key: string]: number } = {};
+
+    /** List of emitted defines */
+    public defineNames: { [key: string]: number } = {};
+
+    /** Should emit comments? */
+    public emitComments: boolean;
+
+    /** Emit build activity */
+    public verbose: boolean;
+
+    /**
+     * Gets the compilation hints emitted at compilation time
+     */
+    public hints = {
+        needWorldViewMatrix: false,
+        needWorldViewProjectionMatrix: false,
+        needAlphaBlending: false,
+        needAlphaTesting: false
+    };
+
+    /**
+     * List of compilation checks
+     */
+    public checks = {
+        emitVertex: false,
+        emitFragment: false,
+        notConnectedNonOptionalInputs: new Array<NodeMaterialConnectionPoint>()
+    };
+
+    /** Creates a new shared data */
+    public constructor() {
+        // Exclude usual attributes from free variable names
+        this.variableNames["position"] = 0;
+        this.variableNames["normal"] = 0;
+        this.variableNames["tangent"] = 0;
+        this.variableNames["uv"] = 0;
+        this.variableNames["uv2"] = 0;
+        this.variableNames["uv3"] = 0;
+        this.variableNames["uv4"] = 0;
+        this.variableNames["uv4"] = 0;
+        this.variableNames["uv5"] = 0;
+        this.variableNames["uv6"] = 0;
+        this.variableNames["color"] = 0;
+        this.variableNames["matricesIndices"] = 0;
+        this.variableNames["matricesWeights"] = 0;
+        this.variableNames["matricesIndicesExtra"] = 0;
+        this.variableNames["matricesWeightsExtra"] = 0;
+
+        // Exclude defines
+        this.defineNames["MAINUV0"] = 0;
+        this.defineNames["MAINUV1"] = 0;
+        this.defineNames["MAINUV2"] = 0;
+        this.defineNames["MAINUV3"] = 0;
+        this.defineNames["MAINUV4"] = 0;
+        this.defineNames["MAINUV5"] = 0;
+        this.defineNames["MAINUV6"] = 0;
+        this.defineNames["MAINUV7"] = 0;
+    }
+
+    /**
+     * Emits console errors and exceptions if there is a failing check
+     */
+    public emitErrors() {
+        let shouldThrowError = false;
+
+        if (!this.checks.emitVertex) {
+            shouldThrowError = true;
+            console.error("NodeMaterial does not have a vertex output. You need to at least add a block that generates a glPosition value.");
+        }
+        if (!this.checks.emitFragment) {
+            shouldThrowError = true;
+            console.error("NodeMaterial does not have a fragment output. You need to at least add a block that generates a glFragColor value.");
+        }
+        for (var notConnectedInput of this.checks.notConnectedNonOptionalInputs) {
+            shouldThrowError = true;
+            console.error(`input ${notConnectedInput.name} from block ${notConnectedInput.ownerBlock.name}[${notConnectedInput.ownerBlock.getClassName()}] is not connected and is not optional.`);
+        }
+
+        if (shouldThrowError) {
+            throw "Build of NodeMaterial failed.";
+        }
+    }
+}

+ 19 - 0
src/Materials/Node/nodeMaterialWellKnownValues.ts

@@ -0,0 +1,19 @@
+/**
+ * Enum used to define well known values e.g. values automatically provided by the system
+ */
+export enum NodeMaterialWellKnownValues {
+    /** World */
+    World = 1,
+    /** View */
+    View = 2,
+    /** Projection */
+    Projection = 3,
+    /** ViewProjection */
+    ViewProjection = 4,
+    /** WorldView */
+    WorldView = 5,
+    /** WorldViewProjection */
+    WorldViewProjection = 6,
+    /** Will be filled by the block itself */
+    BlockBased = 7
+}

+ 14 - 0
src/Materials/effect.ts

@@ -334,6 +334,8 @@ export class Effect implements IDisposable {
             if (!vertexSource) {
             if (!vertexSource) {
                 vertexSource = baseName.vertexElement;
                 vertexSource = baseName.vertexElement;
             }
             }
+        } else if (baseName.vertexSource) {
+            vertexSource = "source:" + baseName.vertexSource;
         } else {
         } else {
             vertexSource = baseName.vertex || baseName;
             vertexSource = baseName.vertex || baseName;
         }
         }
@@ -344,6 +346,8 @@ export class Effect implements IDisposable {
             if (!fragmentSource) {
             if (!fragmentSource) {
                 fragmentSource = baseName.fragmentElement;
                 fragmentSource = baseName.fragmentElement;
             }
             }
+        } else if (baseName.fragmentSource) {
+            fragmentSource = "source:" + baseName.fragmentSource;
         } else {
         } else {
             fragmentSource = baseName.fragment || baseName;
             fragmentSource = baseName.fragment || baseName;
         }
         }
@@ -518,6 +522,11 @@ export class Effect implements IDisposable {
 
 
     /** @hidden */
     /** @hidden */
     public _loadVertexShader(vertex: any, callback: (data: any) => void): void {
     public _loadVertexShader(vertex: any, callback: (data: any) => void): void {
+        if (vertex.substr(0, 7) === "source:") {
+            callback(vertex.substr(7));
+            return;
+        }
+
         if (DomManagement.IsWindowObjectExist()) {
         if (DomManagement.IsWindowObjectExist()) {
             // DOM element ?
             // DOM element ?
             if (vertex instanceof HTMLElement) {
             if (vertex instanceof HTMLElement) {
@@ -554,6 +563,11 @@ export class Effect implements IDisposable {
 
 
     /** @hidden */
     /** @hidden */
     public _loadFragmentShader(fragment: any, callback: (data: any) => void): void {
     public _loadFragmentShader(fragment: any, callback: (data: any) => void): void {
+        if (fragment.substr(0, 7) === "source:") {
+            callback(fragment.substr(7));
+            return;
+        }
+
         if (DomManagement.IsWindowObjectExist()) {
         if (DomManagement.IsWindowObjectExist()) {
             // DOM element ?
             // DOM element ?
             if (fragment instanceof HTMLElement) {
             if (fragment instanceof HTMLElement) {

+ 2 - 1
src/Materials/index.ts

@@ -13,4 +13,5 @@ export * from "./shaderMaterial";
 export * from "./standardMaterial";
 export * from "./standardMaterial";
 export * from "./Textures/index";
 export * from "./Textures/index";
 export * from "./uniformBuffer";
 export * from "./uniformBuffer";
-export * from "./materialFlags";
+export * from "./materialFlags";
+export * from "./Node/index";

+ 8 - 0
src/Materials/material.ts

@@ -917,6 +917,7 @@ export class Material implements IAnimatable {
         });
         });
     }
     }
 
 
+    private static readonly _AllDirtyCallBack = (defines: MaterialDefines) => defines.markAllAsDirty();
     private static readonly _ImageProcessingDirtyCallBack = (defines: MaterialDefines) => defines.markAsImageProcessingDirty();
     private static readonly _ImageProcessingDirtyCallBack = (defines: MaterialDefines) => defines.markAsImageProcessingDirty();
     private static readonly _TextureDirtyCallBack = (defines: MaterialDefines) => defines.markAsTexturesDirty();
     private static readonly _TextureDirtyCallBack = (defines: MaterialDefines) => defines.markAsTexturesDirty();
     private static readonly _FresnelDirtyCallBack = (defines: MaterialDefines) => defines.markAsFresnelDirty();
     private static readonly _FresnelDirtyCallBack = (defines: MaterialDefines) => defines.markAsFresnelDirty();
@@ -1007,6 +1008,13 @@ export class Material implements IAnimatable {
         }
         }
     }
     }
 
 
+        /**
+     * Indicates that we need to re-calculated for all submeshes
+     */
+    protected _markAllSubMeshesAsAllDirty() {
+        this._markAllSubMeshesAsDirty(Material._AllDirtyCallBack);
+    }
+
     /**
     /**
      * Indicates that image processing needs to be re-calculated for all submeshes
      * Indicates that image processing needs to be re-calculated for all submeshes
      */
      */

+ 4 - 1
src/Materials/materialDefines.ts

@@ -2,7 +2,8 @@
  * Manages the defines for the Material
  * Manages the defines for the Material
  */
  */
 export class MaterialDefines {
 export class MaterialDefines {
-    private _keys: string[];
+    /** @hidden */
+    protected _keys: string[];
     private _isDirty = true;
     private _isDirty = true;
     /** @hidden */
     /** @hidden */
     public _renderId: number;
     public _renderId: number;
@@ -30,6 +31,8 @@ export class MaterialDefines {
     /** @hidden */
     /** @hidden */
     public _needUVs = false;
     public _needUVs = false;
 
 
+    [id: string]: any;
+
     /**
     /**
      * Specifies if the material needs to be re-calculated
      * Specifies if the material needs to be re-calculated
      */
      */

+ 56 - 28
src/Materials/materialHelper.ts

@@ -80,6 +80,16 @@ export class MaterialHelper {
     }
     }
 
 
     /**
     /**
+     * Gets the current status of the fog (should it be enabled?)
+     * @param mesh defines the mesh to evaluate for fog support
+     * @param scene defines the hosting scene
+     * @returns true if fog must be enabled
+     */
+    public static GetFogState(mesh: AbstractMesh, scene: Scene) {
+        return (scene.fogEnabled && mesh.applyFog && scene.fogMode !== Scene.FOGMODE_NONE);
+    }
+
+    /**
      * Helper used to prepare the list of defines associated with misc. values for shader compilation
      * Helper used to prepare the list of defines associated with misc. values for shader compilation
      * @param mesh defines the current mesh
      * @param mesh defines the current mesh
      * @param scene defines the current scene
      * @param scene defines the current scene
@@ -93,7 +103,7 @@ export class MaterialHelper {
         if (defines._areMiscDirty) {
         if (defines._areMiscDirty) {
             defines["LOGARITHMICDEPTH"] = useLogarithmicDepth;
             defines["LOGARITHMICDEPTH"] = useLogarithmicDepth;
             defines["POINTSIZE"] = pointsCloud;
             defines["POINTSIZE"] = pointsCloud;
-            defines["FOG"] = (scene.fogEnabled && mesh.applyFog && scene.fogMode !== Scene.FOGMODE_NONE && fogEnabled);
+            defines["FOG"] = fogEnabled && this.GetFogState(mesh, scene);
             defines["NONUNIFORMSCALING"] = mesh.nonUniformScaling;
             defines["NONUNIFORMSCALING"] = mesh.nonUniformScaling;
             defines["ALPHATEST"] = alphaTest;
             defines["ALPHATEST"] = alphaTest;
         }
         }
@@ -155,6 +165,49 @@ export class MaterialHelper {
     }
     }
 
 
     /**
     /**
+     * Prepares the defines for bones
+     * @param mesh The mesh containing the geometry data we will draw
+     * @param defines The defines to update
+     */
+    public static PrepareDefinesForBones(mesh: AbstractMesh, defines: any) {
+        if (mesh.useBones && mesh.computeBonesUsingShaders && mesh.skeleton) {
+            defines["NUM_BONE_INFLUENCERS"] = mesh.numBoneInfluencers;
+
+            const materialSupportsBoneTexture = defines["BONETEXTURE"] !== undefined;
+
+            if (mesh.skeleton.isUsingTextureForMatrices && materialSupportsBoneTexture) {
+                defines["BONETEXTURE"] = true;
+            } else {
+                defines["BonesPerMesh"] = (mesh.skeleton.bones.length + 1);
+                defines["BONETEXTURE"] = materialSupportsBoneTexture ? false : undefined;
+            }
+        } else {
+            defines["NUM_BONE_INFLUENCERS"] = 0;
+            defines["BonesPerMesh"] = 0;
+        }
+    }
+
+    /**
+     * Prepares the defines for morph targets
+     * @param mesh The mesh containing the geometry data we will draw
+     * @param defines The defines to update
+     */
+    public static PrepareDefinesForMorphTargets(mesh: AbstractMesh, defines: any) {
+        var manager = (<Mesh>mesh).morphTargetManager;
+        if (manager) {
+            defines["MORPHTARGETS_TANGENT"] = manager.supportsTangents && defines["TANGENT"];
+            defines["MORPHTARGETS_NORMAL"] = manager.supportsNormals && defines["NORMAL"];
+            defines["MORPHTARGETS"] = (manager.numInfluencers > 0);
+            defines["NUM_MORPH_INFLUENCERS"] = manager.numInfluencers;
+        } else {
+            defines["MORPHTARGETS_TANGENT"] = false;
+            defines["MORPHTARGETS_NORMAL"] = false;
+            defines["MORPHTARGETS"] = false;
+            defines["NUM_MORPH_INFLUENCERS"] = 0;
+        }
+    }
+
+    /**
      * Prepares the defines used in the shader depending on the attributes data available in the mesh
      * Prepares the defines used in the shader depending on the attributes data available in the mesh
      * @param mesh The mesh containing the geometry data we will draw
      * @param mesh The mesh containing the geometry data we will draw
      * @param defines The defines to update
      * @param defines The defines to update
@@ -193,36 +246,11 @@ export class MaterialHelper {
         }
         }
 
 
         if (useBones) {
         if (useBones) {
-            if (mesh.useBones && mesh.computeBonesUsingShaders && mesh.skeleton) {
-                defines["NUM_BONE_INFLUENCERS"] = mesh.numBoneInfluencers;
-
-                const materialSupportsBoneTexture = defines["BONETEXTURE"] !== undefined;
-
-                if (mesh.skeleton.isUsingTextureForMatrices && materialSupportsBoneTexture) {
-                    defines["BONETEXTURE"] = true;
-                } else {
-                    defines["BonesPerMesh"] = (mesh.skeleton.bones.length + 1);
-                    defines["BONETEXTURE"] = materialSupportsBoneTexture ? false : undefined;
-                }
-            } else {
-                defines["NUM_BONE_INFLUENCERS"] = 0;
-                defines["BonesPerMesh"] = 0;
-            }
+            this.PrepareDefinesForBones(mesh, defines);
         }
         }
 
 
         if (useMorphTargets) {
         if (useMorphTargets) {
-            var manager = (<Mesh>mesh).morphTargetManager;
-            if (manager) {
-                defines["MORPHTARGETS_TANGENT"] = manager.supportsTangents && defines["TANGENT"];
-                defines["MORPHTARGETS_NORMAL"] = manager.supportsNormals && defines["NORMAL"];
-                defines["MORPHTARGETS"] = (manager.numInfluencers > 0);
-                defines["NUM_MORPH_INFLUENCERS"] = manager.numInfluencers;
-            } else {
-                defines["MORPHTARGETS_TANGENT"] = false;
-                defines["MORPHTARGETS_NORMAL"] = false;
-                defines["MORPHTARGETS"] = false;
-                defines["NUM_MORPH_INFLUENCERS"] = 0;
-            }
+            this.PrepareDefinesForMorphTargets(mesh, defines);
         }
         }
 
 
         return true;
         return true;

+ 4 - 1
src/Materials/shaderMaterial.ts

@@ -79,6 +79,7 @@ export class ShaderMaterial extends Material {
     private _vectors2Arrays: { [name: string]: number[] } = {};
     private _vectors2Arrays: { [name: string]: number[] } = {};
     private _vectors3Arrays: { [name: string]: number[] } = {};
     private _vectors3Arrays: { [name: string]: number[] } = {};
     private _cachedWorldViewMatrix = new Matrix();
     private _cachedWorldViewMatrix = new Matrix();
+    private _cachedWorldViewProjectionMatrix = new Matrix();
     private _renderId: number;
     private _renderId: number;
 
 
     /**
     /**
@@ -526,7 +527,9 @@ export class ShaderMaterial extends Material {
         }
         }
 
 
         if (this._options.uniforms.indexOf("worldViewProjection") !== -1) {
         if (this._options.uniforms.indexOf("worldViewProjection") !== -1) {
-            this._effect.setMatrix("worldViewProjection", world.multiply(scene.getTransformMatrix()));
+            world.multiplyToRef(scene.getTransformMatrix(), this._cachedWorldViewProjectionMatrix);
+            this._effect.setMatrix("worldViewProjection", this._cachedWorldViewProjectionMatrix);
+
         }
         }
     }
     }
 
 

+ 3 - 3
src/Materials/standardMaterial.ts

@@ -532,12 +532,12 @@ export class StandardMaterial extends PushMaterial {
             return;
             return;
         }
         }
 
 
-        // Detaches observer.
+        // Detaches observer
         if (this._imageProcessingConfiguration && this._imageProcessingObserver) {
         if (this._imageProcessingConfiguration && this._imageProcessingObserver) {
             this._imageProcessingConfiguration.onUpdateParameters.remove(this._imageProcessingObserver);
             this._imageProcessingConfiguration.onUpdateParameters.remove(this._imageProcessingObserver);
         }
         }
 
 
-        // Pick the scene configuration if needed.
+        // Pick the scene configuration if needed
         if (!configuration) {
         if (!configuration) {
             this._imageProcessingConfiguration = this.getScene().imageProcessingConfiguration;
             this._imageProcessingConfiguration = this.getScene().imageProcessingConfiguration;
         }
         }
@@ -545,7 +545,7 @@ export class StandardMaterial extends PushMaterial {
             this._imageProcessingConfiguration = configuration;
             this._imageProcessingConfiguration = configuration;
         }
         }
 
 
-        // Attaches observer.
+        // Attaches observer
         if (this._imageProcessingConfiguration) {
         if (this._imageProcessingConfiguration) {
             this._imageProcessingObserver = this._imageProcessingConfiguration.onUpdateParameters.add(() => {
             this._imageProcessingObserver = this._imageProcessingConfiguration.onUpdateParameters.add(() => {
                 this._markAllSubMeshesAsImageProcessingDirty();
                 this._markAllSubMeshesAsImageProcessingDirty();

+ 2 - 2
src/Meshes/mesh.ts

@@ -2459,7 +2459,7 @@ export class Mesh extends AbstractMesh implements IGetSetVerticesData {
             }
             }
         }
         }
 
 
-        vertex_data.applyToMesh(this);
+        vertex_data.applyToMesh(this, this.isVertexBufferUpdatable(VertexBuffer.PositionKind));
         return this;
         return this;
     }
     }
 
 
@@ -2665,7 +2665,7 @@ export class Mesh extends AbstractMesh implements IGetSetVerticesData {
             vertex_data.normals = normals;
             vertex_data.normals = normals;
             vertex_data.uvs = uvs;
             vertex_data.uvs = uvs;
 
 
-            vertex_data.applyToMesh(this);
+            vertex_data.applyToMesh(this, this.isVertexBufferUpdatable(VertexBuffer.PositionKind));
         }
         }
     }
     }