Bläddra i källkod

Improved automatic input additions #6012

David Catuhe 6 år sedan
förälder
incheckning
ec485fbb3c

+ 9 - 2
dist/preview release/babylon.d.ts

@@ -52128,6 +52128,12 @@ declare module BABYLON {
          * Clear the current material and set it to a default state
          */
         setToDefault(): void;
+        /**
+         * Loads the current Node Material from a url pointing to a file save by the Node Material Editor
+         * @param url defines the url to load from
+         * @returns a promise that will fullfil when the material is fully loaded
+         */
+        loadAsync(url: string): Promise<unknown>;
         private _gatherBlocks;
         /**
          * Serializes this material in a JSON representation
@@ -52585,8 +52591,9 @@ declare module BABYLON {
         initializeDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines, useInstances?: boolean): void;
         /**
          * Lets the block try to connect some inputs automatically
+         * @param material defines the hosting NodeMaterial
          */
-        autoConfigure(): void;
+        autoConfigure(material: NodeMaterial): void;
         /**
          * Function called when a block is declared as repeatable content generator
          * @param vertexShaderState defines the current compilation state for the vertex shader
@@ -53090,7 +53097,7 @@ declare module BABYLON {
          * Gets the fresnel output component
          */
         readonly fresnel: NodeMaterialConnectionPoint;
-        autoConfigure(): void;
+        autoConfigure(material: NodeMaterial): void;
         protected _buildBlock(state: NodeMaterialBuildState): this | undefined;
     }
 }

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1 - 1
dist/preview release/babylon.js


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 88 - 49
dist/preview release/babylon.max.js


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1 - 1
dist/preview release/babylon.max.js.map


+ 19 - 4
dist/preview release/babylon.module.d.ts

@@ -54552,6 +54552,12 @@ declare module "babylonjs/Materials/Node/nodeMaterial" {
          * Clear the current material and set it to a default state
          */
         setToDefault(): void;
+        /**
+         * Loads the current Node Material from a url pointing to a file save by the Node Material Editor
+         * @param url defines the url to load from
+         * @returns a promise that will fullfil when the material is fully loaded
+         */
+        loadAsync(url: string): Promise<unknown>;
         private _gatherBlocks;
         /**
          * Serializes this material in a JSON representation
@@ -55037,8 +55043,9 @@ declare module "babylonjs/Materials/Node/nodeMaterialBlock" {
         initializeDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines, useInstances?: boolean): void;
         /**
          * Lets the block try to connect some inputs automatically
+         * @param material defines the hosting NodeMaterial
          */
-        autoConfigure(): void;
+        autoConfigure(material: NodeMaterial): void;
         /**
          * Function called when a block is declared as repeatable content generator
          * @param vertexShaderState defines the current compilation state for the vertex shader
@@ -55551,6 +55558,7 @@ declare module "babylonjs/Materials/Node/Blocks/Fragment/fresnelBlock" {
     import { NodeMaterialBlock } from "babylonjs/Materials/Node/nodeMaterialBlock";
     import { NodeMaterialBuildState } from "babylonjs/Materials/Node/nodeMaterialBuildState";
     import { NodeMaterialConnectionPoint } from "babylonjs/Materials/Node/nodeMaterialBlockConnectionPoint";
+    import { NodeMaterial } from "babylonjs/Materials/Node/nodeMaterial";
     /**
      * Block used to compute fresnel value
      */
@@ -55589,7 +55597,7 @@ declare module "babylonjs/Materials/Node/Blocks/Fragment/fresnelBlock" {
          * Gets the fresnel output component
          */
         readonly fresnel: NodeMaterialConnectionPoint;
-        autoConfigure(): void;
+        autoConfigure(material: NodeMaterial): void;
         protected _buildBlock(state: NodeMaterialBuildState): this | undefined;
     }
 }
@@ -116874,6 +116882,12 @@ declare module BABYLON {
          * Clear the current material and set it to a default state
          */
         setToDefault(): void;
+        /**
+         * Loads the current Node Material from a url pointing to a file save by the Node Material Editor
+         * @param url defines the url to load from
+         * @returns a promise that will fullfil when the material is fully loaded
+         */
+        loadAsync(url: string): Promise<unknown>;
         private _gatherBlocks;
         /**
          * Serializes this material in a JSON representation
@@ -117331,8 +117345,9 @@ declare module BABYLON {
         initializeDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines, useInstances?: boolean): void;
         /**
          * Lets the block try to connect some inputs automatically
+         * @param material defines the hosting NodeMaterial
          */
-        autoConfigure(): void;
+        autoConfigure(material: NodeMaterial): void;
         /**
          * Function called when a block is declared as repeatable content generator
          * @param vertexShaderState defines the current compilation state for the vertex shader
@@ -117836,7 +117851,7 @@ declare module BABYLON {
          * Gets the fresnel output component
          */
         readonly fresnel: NodeMaterialConnectionPoint;
-        autoConfigure(): void;
+        autoConfigure(material: NodeMaterial): void;
         protected _buildBlock(state: NodeMaterialBuildState): this | undefined;
     }
 }

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

@@ -1000,7 +1000,7 @@ declare module NODEEDITOR {
         private _nodes;
         private _blocks;
         private _previewManager;
-        private _copiedNode;
+        private _copiedNodes;
         private _mouseLocationX;
         private _mouseLocationY;
         private _onWidgetKeyUpPointer;

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1 - 1
dist/preview release/nodeEditor/babylon.nodeEditor.js


+ 55 - 16
dist/preview release/nodeEditor/babylon.nodeEditor.max.js

@@ -65105,7 +65105,7 @@ var GraphEditor = /** @class */ (function (_super) {
         _this._rightWidth = _dataStorage__WEBPACK_IMPORTED_MODULE_17__["DataStorage"].ReadNumber("RightWidth", 300);
         _this._nodes = new Array();
         _this._blocks = new Array();
-        _this._copiedNode = null;
+        _this._copiedNodes = [];
         _this._mouseLocationX = 0;
         _this._mouseLocationY = 0;
         /** @hidden */
@@ -65157,24 +65157,41 @@ var GraphEditor = /** @class */ (function (_super) {
                 if (!selectedItem.block) {
                     return;
                 }
-                _this._copiedNode = selectedItem;
+                _this._copiedNodes = selectedItems.map(function (i) { return i; });
             }
             else if (evt.key === "v") {
-                if (!_this._copiedNode) {
+                if (!_this._copiedNodes.length) {
                     return;
                 }
-                var block = _this._copiedNode.block;
-                var clone = block.clone(_this.props.globalState.nodeMaterial.getScene());
-                if (!clone) {
-                    return;
-                }
-                var newNode = _this.createNodeFromObject({ nodeMaterialBlock: clone });
                 var rootElement = _this.props.globalState.hostDocument.querySelector(".diagram-container");
                 var zoomLevel = _this._engine.diagramModel.getZoomLevel() / 100.0;
-                var x = (_this._mouseLocationX - rootElement.offsetLeft - _this._engine.diagramModel.getOffsetX() - _this.NodeWidth) / zoomLevel;
-                var y = (_this._mouseLocationY - rootElement.offsetTop - _this._engine.diagramModel.getOffsetY() - 20) / zoomLevel;
-                newNode.x = x;
-                newNode.y = y;
+                var currentX = (_this._mouseLocationX - rootElement.offsetLeft - _this._engine.diagramModel.getOffsetX() - _this.NodeWidth) / zoomLevel;
+                var currentY = (_this._mouseLocationY - rootElement.offsetTop - _this._engine.diagramModel.getOffsetY() - 20) / zoomLevel;
+                var originalNode = null;
+                for (var _i = 0, _a = _this._copiedNodes; _i < _a.length; _i++) {
+                    var node = _a[_i];
+                    var block = node.block;
+                    if (!block) {
+                        continue;
+                    }
+                    var clone = block.clone(_this.props.globalState.nodeMaterial.getScene());
+                    if (!clone) {
+                        return;
+                    }
+                    var newNode = _this.createNodeFromObject({ nodeMaterialBlock: clone });
+                    var x = 0;
+                    var y = 0;
+                    if (originalNode) {
+                        x = currentX + node.x - originalNode.x;
+                        y = currentY + node.y - originalNode.y;
+                    }
+                    else {
+                        originalNode = node;
+                        x = currentX;
+                        y = currentY;
+                    }
+                    newNode.setPosition(x, y);
+                }
                 _this._engine.repaintCanvas();
             }
         }, false);
@@ -65469,6 +65486,7 @@ var GraphEditor = /** @class */ (function (_super) {
         return this._leftWidth + "px 4px calc(100% - " + (this._leftWidth + 8 + this._rightWidth) + "px) 4px " + this._rightWidth + "px";
     };
     GraphEditor.prototype.emitNewBlock = function (event) {
+        var _this = this;
         var data = event.dataTransfer.getData("babylonjs-material-node");
         var nodeModel = null;
         if (data.indexOf("Block") === -1) {
@@ -65477,15 +65495,36 @@ var GraphEditor = /** @class */ (function (_super) {
         else {
             var block = _blockTools__WEBPACK_IMPORTED_MODULE_19__["BlockTools"].GetBlockFromString(data);
             if (block) {
+                this._toAdd = [];
+                block.autoConfigure(this.props.globalState.nodeMaterial);
                 nodeModel = this.createNodeFromObject({ nodeMaterialBlock: block });
             }
         }
         ;
         if (nodeModel) {
             var zoomLevel = this._engine.diagramModel.getZoomLevel() / 100.0;
-            var x = (event.clientX - event.currentTarget.offsetLeft - this._engine.diagramModel.getOffsetX() - this.NodeWidth) / zoomLevel;
-            var y = (event.clientY - event.currentTarget.offsetTop - this._engine.diagramModel.getOffsetY() - 20) / zoomLevel;
-            nodeModel.setPosition(x, y);
+            var x_1 = (event.clientX - event.currentTarget.offsetLeft - this._engine.diagramModel.getOffsetX() - this.NodeWidth) / zoomLevel;
+            var y_1 = (event.clientY - event.currentTarget.offsetTop - this._engine.diagramModel.getOffsetY() - 20) / zoomLevel;
+            nodeModel.setPosition(x_1, y_1);
+            var block = nodeModel.block;
+            x_1 -= this.NodeWidth + 150;
+            block._inputs.forEach(function (connection) {
+                if (connection.connectedPoint) {
+                    var existingNodes = _this._nodes.filter(function (n) { return n.block === connection._connectedPoint._ownerBlock; });
+                    var connectedNode = existingNodes[0];
+                    if (connectedNode.x === 0 && connectedNode.y === 0) {
+                        connectedNode.setPosition(x_1, y_1);
+                        y_1 += 80;
+                    }
+                }
+            });
+            this._engine.repaintCanvas();
+            setTimeout(function () {
+                var _a;
+                (_a = _this._model).addAll.apply(_a, _this._toAdd);
+                _this._toAdd = null;
+                _this._engine.repaintCanvas();
+            }, 150);
         }
         this.forceUpdate();
     };

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1 - 1
dist/preview release/nodeEditor/babylon.nodeEditor.max.js.map


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

@@ -1195,7 +1195,7 @@ declare module "babylonjs-node-editor/graphEditor" {
         private _nodes;
         private _blocks;
         private _previewManager;
-        private _copiedNode;
+        private _copiedNodes;
         private _mouseLocationX;
         private _mouseLocationY;
         private _onWidgetKeyUpPointer;
@@ -2314,7 +2314,7 @@ declare module NODEEDITOR {
         private _nodes;
         private _blocks;
         private _previewManager;
-        private _copiedNode;
+        private _copiedNodes;
         private _mouseLocationX;
         private _mouseLocationY;
         private _onWidgetKeyUpPointer;

+ 19 - 4
dist/preview release/viewer/babylon.module.d.ts

@@ -54552,6 +54552,12 @@ declare module "babylonjs/Materials/Node/nodeMaterial" {
          * Clear the current material and set it to a default state
          */
         setToDefault(): void;
+        /**
+         * Loads the current Node Material from a url pointing to a file save by the Node Material Editor
+         * @param url defines the url to load from
+         * @returns a promise that will fullfil when the material is fully loaded
+         */
+        loadAsync(url: string): Promise<unknown>;
         private _gatherBlocks;
         /**
          * Serializes this material in a JSON representation
@@ -55037,8 +55043,9 @@ declare module "babylonjs/Materials/Node/nodeMaterialBlock" {
         initializeDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines, useInstances?: boolean): void;
         /**
          * Lets the block try to connect some inputs automatically
+         * @param material defines the hosting NodeMaterial
          */
-        autoConfigure(): void;
+        autoConfigure(material: NodeMaterial): void;
         /**
          * Function called when a block is declared as repeatable content generator
          * @param vertexShaderState defines the current compilation state for the vertex shader
@@ -55551,6 +55558,7 @@ declare module "babylonjs/Materials/Node/Blocks/Fragment/fresnelBlock" {
     import { NodeMaterialBlock } from "babylonjs/Materials/Node/nodeMaterialBlock";
     import { NodeMaterialBuildState } from "babylonjs/Materials/Node/nodeMaterialBuildState";
     import { NodeMaterialConnectionPoint } from "babylonjs/Materials/Node/nodeMaterialBlockConnectionPoint";
+    import { NodeMaterial } from "babylonjs/Materials/Node/nodeMaterial";
     /**
      * Block used to compute fresnel value
      */
@@ -55589,7 +55597,7 @@ declare module "babylonjs/Materials/Node/Blocks/Fragment/fresnelBlock" {
          * Gets the fresnel output component
          */
         readonly fresnel: NodeMaterialConnectionPoint;
-        autoConfigure(): void;
+        autoConfigure(material: NodeMaterial): void;
         protected _buildBlock(state: NodeMaterialBuildState): this | undefined;
     }
 }
@@ -116874,6 +116882,12 @@ declare module BABYLON {
          * Clear the current material and set it to a default state
          */
         setToDefault(): void;
+        /**
+         * Loads the current Node Material from a url pointing to a file save by the Node Material Editor
+         * @param url defines the url to load from
+         * @returns a promise that will fullfil when the material is fully loaded
+         */
+        loadAsync(url: string): Promise<unknown>;
         private _gatherBlocks;
         /**
          * Serializes this material in a JSON representation
@@ -117331,8 +117345,9 @@ declare module BABYLON {
         initializeDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines, useInstances?: boolean): void;
         /**
          * Lets the block try to connect some inputs automatically
+         * @param material defines the hosting NodeMaterial
          */
-        autoConfigure(): void;
+        autoConfigure(material: NodeMaterial): void;
         /**
          * Function called when a block is declared as repeatable content generator
          * @param vertexShaderState defines the current compilation state for the vertex shader
@@ -117836,7 +117851,7 @@ declare module BABYLON {
          * Gets the fresnel output component
          */
         readonly fresnel: NodeMaterialConnectionPoint;
-        autoConfigure(): void;
+        autoConfigure(material: NodeMaterial): void;
         protected _buildBlock(state: NodeMaterialBuildState): this | undefined;
     }
 }

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 7 - 7
dist/preview release/viewer/babylon.viewer.js


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1 - 1
dist/preview release/viewer/babylon.viewer.max.js


+ 817 - 0
localDev/fresnel.json

@@ -0,0 +1,817 @@
+{
+  "tags": null,
+  "id": "dummy node",
+  "uniqueId": 78,
+  "name": "dummy node",
+  "checkReadyOnEveryCall": false,
+  "checkReadyOnlyOnce": false,
+  "state": "",
+  "alpha": 1,
+  "backFaceCulling": true,
+  "sideOrientation": 1,
+  "alphaMode": 2,
+  "_needDepthPrePass": false,
+  "disableDepthWrite": false,
+  "forceDepthWrite": false,
+  "separateCullingPass": false,
+  "fogEnabled": true,
+  "pointSize": 1,
+  "zOffset": 0,
+  "wireframe": false,
+  "pointsCloud": false,
+  "fillMode": 0,
+  "customType": "BABYLON.NodeMaterial",
+  "outputNodes": [
+    200,
+    213,
+    226
+  ],
+  "blocks": [
+    {
+      "customType": "BABYLON.VertexOutputBlock",
+      "id": 200,
+      "name": "vertexOutput",
+      "inputs": [
+        {
+          "name": "vector",
+          "inputName": "vector",
+          "targetBlockId": 201,
+          "targetConnectionName": "output"
+        }
+      ]
+    },
+    {
+      "customType": "BABYLON.TransformBlock",
+      "id": 201,
+      "name": "worldPos * viewProjectionTransform",
+      "inputs": [
+        {
+          "name": "vector",
+          "inputName": "vector",
+          "targetBlockId": 202,
+          "targetConnectionName": "output"
+        },
+        {
+          "name": "transform",
+          "inputName": "transform",
+          "targetBlockId": 212,
+          "targetConnectionName": "output"
+        }
+      ],
+      "complementZ": 0,
+      "complementW": 1
+    },
+    {
+      "customType": "BABYLON.TransformBlock",
+      "id": 202,
+      "name": "worldPos",
+      "inputs": [
+        {
+          "name": "vector",
+          "inputName": "vector",
+          "targetBlockId": 203,
+          "targetConnectionName": "positionOutput"
+        },
+        {
+          "name": "transform",
+          "inputName": "transform",
+          "targetBlockId": 208,
+          "targetConnectionName": "output"
+        }
+      ],
+      "complementZ": 0,
+      "complementW": 1
+    },
+    {
+      "customType": "BABYLON.MorphTargetsBlock",
+      "id": 203,
+      "name": "morphTargets",
+      "inputs": [
+        {
+          "name": "position",
+          "inputName": "position",
+          "targetBlockId": 204,
+          "targetConnectionName": "output"
+        },
+        {
+          "name": "normal",
+          "inputName": "normal",
+          "targetBlockId": 205,
+          "targetConnectionName": "output"
+        },
+        {
+          "name": "tangent",
+          "inputName": "tangent",
+          "targetBlockId": 206,
+          "targetConnectionName": "output"
+        },
+        {
+          "name": "uv",
+          "inputName": "uv",
+          "targetBlockId": 207,
+          "targetConnectionName": "output"
+        }
+      ]
+    },
+    {
+      "customType": "BABYLON.InputBlock",
+      "id": 204,
+      "name": "position",
+      "inputs": [],
+      "type": 8,
+      "mode": 1,
+      "wellKnownValue": null,
+      "animationType": 0,
+      "visibleInInspector": false
+    },
+    {
+      "customType": "BABYLON.InputBlock",
+      "id": 205,
+      "name": "normal",
+      "inputs": [],
+      "type": 8,
+      "mode": 1,
+      "wellKnownValue": null,
+      "animationType": 0,
+      "visibleInInspector": false
+    },
+    {
+      "customType": "BABYLON.InputBlock",
+      "id": 206,
+      "name": "tangent",
+      "inputs": [],
+      "type": 8,
+      "mode": 1,
+      "wellKnownValue": null,
+      "animationType": 0,
+      "visibleInInspector": false
+    },
+    {
+      "customType": "BABYLON.InputBlock",
+      "id": 207,
+      "name": "uv",
+      "inputs": [],
+      "type": 4,
+      "mode": 1,
+      "wellKnownValue": null,
+      "animationType": 0,
+      "visibleInInspector": false
+    },
+    {
+      "customType": "BABYLON.BonesBlock",
+      "id": 208,
+      "name": "bonesBlock",
+      "inputs": [
+        {
+          "name": "matricesIndices",
+          "inputName": "matricesIndices",
+          "targetBlockId": 209,
+          "targetConnectionName": "output"
+        },
+        {
+          "name": "matricesWeights",
+          "inputName": "matricesWeights",
+          "targetBlockId": 210,
+          "targetConnectionName": "output"
+        },
+        {
+          "name": "matricesIndicesExtra"
+        },
+        {
+          "name": "matricesWeightsExtra"
+        },
+        {
+          "name": "world",
+          "inputName": "world",
+          "targetBlockId": 211,
+          "targetConnectionName": "output"
+        }
+      ]
+    },
+    {
+      "customType": "BABYLON.InputBlock",
+      "id": 209,
+      "name": "matricesIndices",
+      "inputs": [],
+      "type": 16,
+      "mode": 1,
+      "wellKnownValue": null,
+      "animationType": 0,
+      "visibleInInspector": false
+    },
+    {
+      "customType": "BABYLON.InputBlock",
+      "id": 210,
+      "name": "matricesWeights",
+      "inputs": [],
+      "type": 16,
+      "mode": 1,
+      "wellKnownValue": null,
+      "animationType": 0,
+      "visibleInInspector": false
+    },
+    {
+      "customType": "BABYLON.InputBlock",
+      "id": 211,
+      "name": "world",
+      "inputs": [],
+      "type": 128,
+      "mode": 0,
+      "wellKnownValue": 1,
+      "animationType": 0,
+      "visibleInInspector": false,
+      "valueType": "BABYLON.Matrix",
+      "value": {
+        "0": 1,
+        "1": 0,
+        "2": 0,
+        "3": 0,
+        "4": 0,
+        "5": 1,
+        "6": 0,
+        "7": 0,
+        "8": 0,
+        "9": 0,
+        "10": 1,
+        "11": 0,
+        "12": 0,
+        "13": 0,
+        "14": 0,
+        "15": 1
+      }
+    },
+    {
+      "customType": "BABYLON.InputBlock",
+      "id": 212,
+      "name": "viewProjection",
+      "inputs": [],
+      "type": 128,
+      "mode": 0,
+      "wellKnownValue": 4,
+      "animationType": 0,
+      "visibleInInspector": false
+    },
+    {
+      "customType": "BABYLON.FogBlock",
+      "id": 213,
+      "name": "fog",
+      "inputs": [
+        {
+          "name": "worldPosition",
+          "inputName": "worldPosition",
+          "targetBlockId": 202,
+          "targetConnectionName": "output"
+        },
+        {
+          "name": "view",
+          "inputName": "view",
+          "targetBlockId": 214,
+          "targetConnectionName": "output"
+        },
+        {
+          "name": "color",
+          "inputName": "color",
+          "targetBlockId": 215,
+          "targetConnectionName": "rgb"
+        },
+        {
+          "name": "fogColor",
+          "inputName": "fogColor",
+          "targetBlockId": 225,
+          "targetConnectionName": "output"
+        }
+      ]
+    },
+    {
+      "customType": "BABYLON.InputBlock",
+      "id": 214,
+      "name": "view",
+      "inputs": [],
+      "type": 128,
+      "mode": 0,
+      "wellKnownValue": 2,
+      "animationType": 0,
+      "visibleInInspector": false
+    },
+    {
+      "customType": "BABYLON.ColorSplitterBlock",
+      "id": 215,
+      "name": "color4 splitter",
+      "inputs": [
+        {
+          "name": "rgba",
+          "inputName": "rgba",
+          "targetBlockId": 216,
+          "targetConnectionName": "output"
+        },
+        {
+          "name": "rgb "
+        }
+      ]
+    },
+    {
+      "customType": "BABYLON.ScaleBlock",
+      "id": 216,
+      "name": "color scale",
+      "inputs": [
+        {
+          "name": "input",
+          "inputName": "input",
+          "targetBlockId": 217,
+          "targetConnectionName": "output"
+        },
+        {
+          "name": "factor",
+          "inputName": "factor",
+          "targetBlockId": 224,
+          "targetConnectionName": "output"
+        }
+      ]
+    },
+    {
+      "customType": "BABYLON.MultiplyBlock",
+      "id": 217,
+      "name": "color multiplier",
+      "inputs": [
+        {
+          "name": "left",
+          "inputName": "left",
+          "targetBlockId": 218,
+          "targetConnectionName": "rgba"
+        },
+        {
+          "name": "right",
+          "inputName": "right",
+          "targetBlockId": 221,
+          "targetConnectionName": "rgba"
+        }
+      ]
+    },
+    {
+      "customType": "BABYLON.TextureBlock",
+      "id": 218,
+      "name": "diffuseTexture",
+      "inputs": [
+        {
+          "name": "uv",
+          "inputName": "uv",
+          "targetBlockId": 220,
+          "targetConnectionName": "output"
+        }
+      ],
+      "texture": {
+        "tags": null,
+        "url": "/playground/textures/bloc.jpg",
+        "uOffset": 0,
+        "vOffset": 0,
+        "uScale": 1,
+        "vScale": 1,
+        "uAng": 0,
+        "vAng": 0,
+        "wAng": 0,
+        "uRotationCenter": 0.5,
+        "vRotationCenter": 0.5,
+        "wRotationCenter": 0.5,
+        "isBlocking": true,
+        "uniqueId": 88,
+        "name": "/playground/textures/bloc.jpg",
+        "hasAlpha": false,
+        "getAlphaFromRGB": false,
+        "level": 1,
+        "coordinatesIndex": 0,
+        "coordinatesMode": 0,
+        "wrapU": 1,
+        "wrapV": 1,
+        "wrapR": 1,
+        "anisotropicFilteringLevel": 4,
+        "isCube": false,
+        "is3D": false,
+        "gammaSpace": true,
+        "invertZ": false,
+        "lodLevelInAlpha": false,
+        "lodGenerationOffset": 0,
+        "lodGenerationScale": 0,
+        "linearSpecularLOD": false,
+        "isRenderTarget": false,
+        "animations": [],
+        "invertY": true,
+        "samplingMode": 3
+      }
+    },
+    {
+      "customType": "BABYLON.InputBlock",
+      "id": 220,
+      "name": "uv",
+      "inputs": [],
+      "type": 4,
+      "mode": 1,
+      "wellKnownValue": null,
+      "animationType": 0,
+      "visibleInInspector": false
+    },
+    {
+      "customType": "BABYLON.TextureBlock",
+      "id": 221,
+      "name": "diffuseTexture2",
+      "inputs": [
+        {
+          "name": "uv",
+          "inputName": "uv",
+          "targetBlockId": 223,
+          "targetConnectionName": "output"
+        }
+      ],
+      "texture": {
+        "tags": null,
+        "url": "/playground/textures/crate.png",
+        "uOffset": 0,
+        "vOffset": 0,
+        "uScale": 1,
+        "vScale": 1,
+        "uAng": 0,
+        "vAng": 0,
+        "wAng": 0,
+        "uRotationCenter": 0.5,
+        "vRotationCenter": 0.5,
+        "wRotationCenter": 0.5,
+        "isBlocking": true,
+        "uniqueId": 90,
+        "name": "/playground/textures/crate.png",
+        "hasAlpha": false,
+        "getAlphaFromRGB": false,
+        "level": 1,
+        "coordinatesIndex": 0,
+        "coordinatesMode": 0,
+        "wrapU": 1,
+        "wrapV": 1,
+        "wrapR": 1,
+        "anisotropicFilteringLevel": 4,
+        "isCube": false,
+        "is3D": false,
+        "gammaSpace": true,
+        "invertZ": false,
+        "lodLevelInAlpha": false,
+        "lodGenerationOffset": 0,
+        "lodGenerationScale": 0,
+        "linearSpecularLOD": false,
+        "isRenderTarget": false,
+        "animations": [],
+        "invertY": true,
+        "samplingMode": 3
+      }
+    },
+    {
+      "customType": "BABYLON.InputBlock",
+      "id": 223,
+      "name": "uv",
+      "inputs": [],
+      "type": 4,
+      "mode": 1,
+      "wellKnownValue": null,
+      "animationType": 0,
+      "visibleInInspector": false
+    },
+    {
+      "customType": "BABYLON.InputBlock",
+      "id": 224,
+      "name": "time",
+      "inputs": [],
+      "type": 1,
+      "mode": 0,
+      "wellKnownValue": null,
+      "animationType": 0,
+      "visibleInInspector": false,
+      "valueType": "number",
+      "value": 1
+    },
+    {
+      "customType": "BABYLON.InputBlock",
+      "id": 225,
+      "name": "fogColor",
+      "inputs": [],
+      "type": 32,
+      "mode": 0,
+      "wellKnownValue": 8,
+      "animationType": 0,
+      "visibleInInspector": false,
+      "valueType": "BABYLON.Color3",
+      "value": [
+        1,
+        1,
+        1
+      ]
+    },
+    {
+      "customType": "BABYLON.FragmentOutputBlock",
+      "id": 226,
+      "name": "pixelOutput",
+      "inputs": [
+        {
+          "name": "rgba"
+        },
+        {
+          "name": "rgb",
+          "inputName": "rgb",
+          "targetBlockId": 524,
+          "targetConnectionName": "output"
+        },
+        {
+          "name": "a"
+        }
+      ]
+    },
+    {
+      "customType": "BABYLON.ScaleBlock",
+      "id": 524,
+      "name": "Scale",
+      "inputs": [
+        {
+          "name": "input",
+          "inputName": "input",
+          "targetBlockId": 213,
+          "targetConnectionName": "output"
+        },
+        {
+          "name": "factor",
+          "inputName": "factor",
+          "targetBlockId": 228,
+          "targetConnectionName": "fresnel"
+        }
+      ]
+    },
+    {
+      "customType": "BABYLON.FresnelBlock",
+      "id": 228,
+      "name": "Fresnel",
+      "inputs": [
+        {
+          "name": "worldPosition",
+          "inputName": "worldPosition",
+          "targetBlockId": 202,
+          "targetConnectionName": "output"
+        },
+        {
+          "name": "worldNormal",
+          "inputName": "worldNormal",
+          "targetBlockId": 229,
+          "targetConnectionName": "output"
+        },
+        {
+          "name": "cameraPosition",
+          "inputName": "cameraPosition",
+          "targetBlockId": 231,
+          "targetConnectionName": "output"
+        },
+        {
+          "name": "bias",
+          "inputName": "bias",
+          "targetBlockId": 232,
+          "targetConnectionName": "output"
+        },
+        {
+          "name": "power",
+          "inputName": "power",
+          "targetBlockId": 233,
+          "targetConnectionName": "output"
+        }
+      ]
+    },
+    {
+      "customType": "BABYLON.TransformBlock",
+      "id": 229,
+      "name": "Transform",
+      "inputs": [
+        {
+          "name": "vector",
+          "inputName": "vector",
+          "targetBlockId": 230,
+          "targetConnectionName": "output"
+        },
+        {
+          "name": "transform",
+          "inputName": "transform",
+          "targetBlockId": 208,
+          "targetConnectionName": "output"
+        }
+      ],
+      "complementZ": 0,
+      "complementW": 1
+    },
+    {
+      "customType": "BABYLON.InputBlock",
+      "id": 230,
+      "name": "normal",
+      "inputs": [],
+      "type": 8,
+      "mode": 1,
+      "wellKnownValue": null,
+      "animationType": 0,
+      "visibleInInspector": false
+    },
+    {
+      "customType": "BABYLON.InputBlock",
+      "id": 231,
+      "name": "Vector3",
+      "inputs": [],
+      "type": 8,
+      "mode": 0,
+      "wellKnownValue": 7,
+      "animationType": 0,
+      "visibleInInspector": false,
+      "valueType": "BABYLON.Vector3",
+      "value": [
+        0,
+        0,
+        0
+      ]
+    },
+    {
+      "customType": "BABYLON.InputBlock",
+      "id": 232,
+      "name": "Float",
+      "inputs": [],
+      "type": 1,
+      "mode": 0,
+      "wellKnownValue": null,
+      "animationType": 0,
+      "visibleInInspector": false,
+      "valueType": "number",
+      "value": 0
+    },
+    {
+      "customType": "BABYLON.InputBlock",
+      "id": 233,
+      "name": "Float",
+      "inputs": [],
+      "type": 1,
+      "mode": 0,
+      "wellKnownValue": null,
+      "animationType": 0,
+      "visibleInInspector": false,
+      "valueType": "number",
+      "value": 1
+    }
+  ],
+  "locations": [
+    {
+      "blockId": 200,
+      "x": 1500,
+      "y": 561.5
+    },
+    {
+      "blockId": 201,
+      "x": 1250,
+      "y": 548.5
+    },
+    {
+      "blockId": 202,
+      "x": 1000,
+      "y": 465
+    },
+    {
+      "blockId": 203,
+      "x": 750,
+      "y": 172
+    },
+    {
+      "blockId": 204,
+      "x": 500,
+      "y": 0
+    },
+    {
+      "blockId": 205,
+      "x": 500,
+      "y": 158
+    },
+    {
+      "blockId": 206,
+      "x": 500,
+      "y": 316
+    },
+    {
+      "blockId": 207,
+      "x": 500,
+      "y": 474
+    },
+    {
+      "blockId": 208,
+      "x": 750,
+      "y": 639
+    },
+    {
+      "blockId": 209,
+      "x": 500,
+      "y": 632
+    },
+    {
+      "blockId": 210,
+      "x": 500,
+      "y": 790
+    },
+    {
+      "blockId": 211,
+      "x": 500,
+      "y": 948
+    },
+    {
+      "blockId": 212,
+      "x": 1000,
+      "y": 842
+    },
+    {
+      "blockId": 213,
+      "x": 1473,
+      "y": 1109
+    },
+    {
+      "blockId": 214,
+      "x": 1000,
+      "y": 1000
+    },
+    {
+      "blockId": 215,
+      "x": 1000,
+      "y": 1158
+    },
+    {
+      "blockId": 216,
+      "x": 750,
+      "y": 1221
+    },
+    {
+      "blockId": 217,
+      "x": 500,
+      "y": 1137.5
+    },
+    {
+      "blockId": 218,
+      "x": 250,
+      "y": 931.5
+    },
+    {
+      "blockId": 220,
+      "x": 0,
+      "y": 999.5
+    },
+    {
+      "blockId": 221,
+      "x": 250,
+      "y": 1225.5
+    },
+    {
+      "blockId": 223,
+      "x": 0,
+      "y": 1293.5
+    },
+    {
+      "blockId": 224,
+      "x": 500,
+      "y": 1313.5
+    },
+    {
+      "blockId": 225,
+      "x": 1000,
+      "y": 1460
+    },
+    {
+      "blockId": 226,
+      "x": 2470.619469026549,
+      "y": 953.8318584070795
+    },
+    {
+      "blockId": 228,
+      "x": 1721.8141602372705,
+      "y": 789.0458809677541
+    },
+    {
+      "blockId": 229,
+      "x": 1176.0973460779783,
+      "y": 704.7538455695241
+    },
+    {
+      "blockId": 230,
+      "x": 989.1946912107215,
+      "y": 592.1874738881081
+    },
+    {
+      "blockId": 231,
+      "x": 1432.91148916599,
+      "y": 680.329066808462
+    },
+    {
+      "blockId": 232,
+      "x": 1411.6725511128925,
+      "y": 834.3113676934179
+    },
+    {
+      "blockId": 233,
+      "x": 1369.1946750066977,
+      "y": 962.8688898173117
+    },
+    {
+      "blockId": 524,
+      "x": 2063,
+      "y": 1078
+    }
+  ]
+}

+ 63 - 19
nodeEditor/src/graphEditor.tsx

@@ -73,7 +73,7 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
     private _blocks = new Array<NodeMaterialBlock>();
 
     private _previewManager: PreviewManager;
-    private _copiedNode: Nullable<DefaultNodeModel> = null;
+    private _copiedNodes: DefaultNodeModel[] = [];
     private _mouseLocationX = 0;
     private _mouseLocationY = 0;
     private _onWidgetKeyUpPointer: any;
@@ -117,7 +117,7 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
             this.props.globalState.nodeMaterial!.addOutputNode(options.nodeMaterialBlock);
         }
 
-        this._nodes.push(newNode)
+        this._nodes.push(newNode);
         this._model.addAll(newNode);
 
         if (options.nodeMaterialBlock) {
@@ -226,28 +226,46 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
                     return;
                 }
 
-                this._copiedNode = selectedItem;
+                this._copiedNodes = selectedItems.map(i => (i as DefaultNodeModel)!);
             } else if (evt.key === "v") {
-                if (!this._copiedNode) {
+                if (!this._copiedNodes.length) {
                     return;
                 }
 
-                let block = this._copiedNode.block!;
-                let clone = block.clone(this.props.globalState.nodeMaterial.getScene());
-
-                if (!clone) {
-                    return;
-                }
-                
-                let newNode = this.createNodeFromObject({ nodeMaterialBlock: clone });
                 const rootElement = this.props.globalState.hostDocument!.querySelector(".diagram-container") as HTMLDivElement;
                 const zoomLevel = this._engine.diagramModel.getZoomLevel() / 100.0;
+                let currentX = (this._mouseLocationX - rootElement.offsetLeft - this._engine.diagramModel.getOffsetX() - this.NodeWidth) / zoomLevel;
+                let currentY = (this._mouseLocationY - rootElement.offsetTop - this._engine.diagramModel.getOffsetY() - 20) / zoomLevel;
+                let originalNode: Nullable<DefaultNodeModel> = null;
+
+                for (var node of this._copiedNodes) {
+                    let block = node.block;
+
+                    if (!block) {
+                        continue;
+                    }
 
-                let x = (this._mouseLocationX - rootElement.offsetLeft - this._engine.diagramModel.getOffsetX() - this.NodeWidth) / zoomLevel;
-                let y = (this._mouseLocationY - rootElement.offsetTop - this._engine.diagramModel.getOffsetY() - 20) / zoomLevel;
+                    let clone = block.clone(this.props.globalState.nodeMaterial.getScene());
 
-                newNode.x = x;
-                newNode.y = y;
+                    if (!clone) {
+                        return;
+                    }
+                    
+                    let newNode = this.createNodeFromObject({ nodeMaterialBlock: clone });
+
+                    let x = 0;
+                    let y = 0;
+                    if (originalNode) {
+                        x = currentX + node.x - originalNode.x;
+                        y = currentY + node.y - originalNode.y;
+                    } else {
+                        originalNode = node;
+                        x = currentX;
+                        y = currentY;
+                    }
+
+                    newNode.setPosition(x, y);
+                }
 
                 this._engine.repaintCanvas();
             }
@@ -505,9 +523,11 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
         if (data.indexOf("Block") === -1) {
             nodeModel = this.addValueNode(data);
         } else {
-            let block = BlockTools.GetBlockFromString(data);          
-
-            if (block) {
+            let block = BlockTools.GetBlockFromString(data);   
+            
+            if (block) {                
+                this._toAdd = [];
+                block.autoConfigure(this.props.globalState.nodeMaterial);       
                 nodeModel = this.createNodeFromObject({ nodeMaterialBlock: block });
             }
         };
@@ -518,6 +538,30 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
             let x = (event.clientX - event.currentTarget.offsetLeft - this._engine.diagramModel.getOffsetX() - this.NodeWidth) / zoomLevel;
             let y = (event.clientY - event.currentTarget.offsetTop - this._engine.diagramModel.getOffsetY() - 20) / zoomLevel;
             nodeModel.setPosition(x, y);
+        
+            let block = nodeModel!.block;
+
+            x -= this.NodeWidth + 150;
+
+            block!._inputs.forEach((connection) => {       
+                if (connection.connectedPoint) {
+                    var existingNodes = this._nodes.filter((n) => { return n.block === (connection as any)._connectedPoint._ownerBlock });
+                    let connectedNode = existingNodes[0];
+
+                    if (connectedNode.x === 0 && connectedNode.y === 0) {
+                        connectedNode.setPosition(x, y);
+                        y += 80;
+                    }
+                }
+            });
+            
+            this._engine.repaintCanvas();
+
+            setTimeout(() => {
+                this._model.addAll(...this._toAdd!);            
+                this._toAdd = null;  
+                this._engine.repaintCanvas();  
+            }, 150);
         }
 
         this.forceUpdate();

+ 21 - 1
src/Materials/Node/Blocks/Fragment/fresnelBlock.ts

@@ -4,6 +4,9 @@ import { NodeMaterialBlockConnectionPointTypes } from '../../nodeMaterialBlockCo
 import { NodeMaterialBuildState } from '../../nodeMaterialBuildState';
 import { NodeMaterialConnectionPoint } from '../../nodeMaterialBlockConnectionPoint';
 import { _TypeStore } from '../../../../Misc/typeStore';
+import { InputBlock } from '../Input/inputBlock';
+import { NodeMaterialWellKnownValues } from '../../nodeMaterialWellKnownValues';
+import { NodeMaterial } from '../../nodeMaterial';
 
 /**
  * Block used to compute fresnel value
@@ -77,7 +80,24 @@ export class FresnelBlock extends NodeMaterialBlock {
         return this._outputs[0];
     }
 
-    public autoConfigure() {
+    public autoConfigure(material: NodeMaterial) {
+        if (!this.cameraPosition.isConnected) {
+            let cameraPositionInput = new InputBlock("cameraPosition");
+            cameraPositionInput.setAsWellKnownValue(NodeMaterialWellKnownValues.CameraPosition);
+            cameraPositionInput.output.connectTo(this.cameraPosition);
+        }
+
+        if (!this.bias.isConnected) {
+            let biasInput = new InputBlock("bias");
+            biasInput.value = 0;
+            biasInput.output.connectTo(this.bias);
+        }
+
+        if (!this.power.isConnected) {
+            let powerInput = new InputBlock("power");
+            powerInput.value = 1;
+            powerInput.output.connectTo(this.power);
+        }
     }
 
     protected _buildBlock(state: NodeMaterialBuildState) {

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

@@ -27,6 +27,7 @@ import { _TypeStore } from '../../Misc/typeStore';
 import { SerializationHelper } from '../../Misc/decorators';
 import { TextureBlock } from './Blocks/Dual/textureBlock';
 import { ReflectionTextureBlock } from './Blocks/Dual/reflectionTextureBlock';
+import { FileTools } from '../../Misc/fileTools';
 
 // declare NODEEDITOR namespace for compilation issue
 declare var NODEEDITOR: any;
@@ -425,7 +426,7 @@ export class NodeMaterial extends PushMaterial {
 
     private _initializeBlock(node: NodeMaterialBlock, state: NodeMaterialBuildState, nodesToProcessForOtherBuildState: NodeMaterialBlock[]) {
         node.initialize(state);
-        node.autoConfigure();
+        node.autoConfigure(this);
 
         if (this.attachedBlocks.indexOf(node) === -1) {
             this.attachedBlocks.push(node);
@@ -951,6 +952,25 @@ export class NodeMaterial extends PushMaterial {
         this.addOutputNode(fragmentOutput);
     }
 
+    /**
+     * Loads the current Node Material from a url pointing to a file save by the Node Material Editor
+     * @param url defines the url to load from
+     * @returns a promise that will fullfil when the material is fully loaded
+     */
+    public loadAsync(url: string) {
+        return new Promise((resolve, reject) => {
+            FileTools.LoadFile(url, (data) => {
+                let serializationObject = JSON.parse(data as string);
+
+                this.loadFromSerialization(serializationObject, "");
+
+                resolve();
+            }, undefined, undefined, false, (request, exception) => {
+                reject(exception.message);
+            });
+        });
+    }
+
     private _gatherBlocks(rootNode: NodeMaterialBlock, list: NodeMaterialBlock[]) {
         if (list.indexOf(rootNode) !== -1) {
             return;

+ 2 - 1
src/Materials/Node/nodeMaterialBlock.ts

@@ -352,8 +352,9 @@ export class NodeMaterialBlock {
 
     /**
      * Lets the block try to connect some inputs automatically
+     * @param material defines the hosting NodeMaterial
      */
-    public autoConfigure() {
+    public autoConfigure(material: NodeMaterial) {
         // Do nothing
     }