Browse Source

first large block: fog

David Catuhe 6 years ago
parent
commit
3be0abfeba
27 changed files with 1230 additions and 304 deletions
  1. 96 0
      src/Materials/Node/Blocks/Dual/fogBlock.ts
  2. 2 0
      src/Materials/Node/Blocks/Dual/index.ts
  3. 44 0
      src/Materials/Node/Blocks/Fragment/alphaTestBlock.ts
  4. 19 11
      src/Materials/Node/Blocks/Fragment/fragmentOutputBlock.ts
  5. 5 1
      src/Materials/Node/Blocks/Fragment/index.ts
  6. 36 0
      src/Materials/Node/Blocks/Fragment/rgbMergerBlock.ts
  7. 42 0
      src/Materials/Node/Blocks/Fragment/rgbSplitterBlock.ts
  8. 44 0
      src/Materials/Node/Blocks/Fragment/rgbaMergerBlock.ts
  9. 48 0
      src/Materials/Node/Blocks/Fragment/rgbaSplitterBlock.ts
  10. 0 36
      src/Materials/Node/Blocks/Fragment/textureBlock.ts
  11. 1 3
      src/Materials/Node/Blocks/Vertex/index.ts
  12. 0 39
      src/Materials/Node/Blocks/Vertex/vector3TransformBlock.ts
  13. 0 35
      src/Materials/Node/Blocks/Vertex/vector4TransformBlock.ts
  14. 19 11
      src/Materials/Node/Blocks/Vertex/vertexOutputBlock.ts
  15. 7 1
      src/Materials/Node/Blocks/index.ts
  16. 32 0
      src/Materials/Node/Blocks/mixBlock.ts
  17. 34 0
      src/Materials/Node/Blocks/textureBlock.ts
  18. 41 0
      src/Materials/Node/Blocks/vector2TransformBlock.ts
  19. 37 0
      src/Materials/Node/Blocks/vector3TransformBlock.ts
  20. 33 0
      src/Materials/Node/Blocks/vector4TransformBlock.ts
  21. 1 0
      src/Materials/Node/index.ts
  22. 203 30
      src/Materials/Node/nodeMaterial.ts
  23. 198 59
      src/Materials/Node/nodeMaterialBlock.ts
  24. 141 18
      src/Materials/Node/nodeMaterialBlockConnectionPoint.ts
  25. 15 9
      src/Materials/Node/nodeMaterialBlockConnectionPointTypes.ts
  26. 111 51
      src/Materials/Node/nodeMaterialCompilationState.ts
  27. 21 0
      src/Materials/Node/nodeMaterialWellKnownValues.ts

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

@@ -0,0 +1,96 @@
+import { NodeMaterialBlock, NodeMaterialBlockTargets } from '../../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../../nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialCompilationState } from '../../nodeMaterialCompilationState';
+import { NodeMaterialWellKnownValues } from '../../nodeMaterialWellKnownValues';
+
+/**
+ * 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);
+
+        // Vertex
+        this.registerInput("worldPos", NodeMaterialBlockConnectionPointTypes.Vector4, NodeMaterialBlockTargets.Vertex);
+        this.registerInput("view", NodeMaterialBlockConnectionPointTypes.Matrix, NodeMaterialBlockTargets.Vertex);
+
+        this.registerOutput("vFogDistance", NodeMaterialBlockConnectionPointTypes.Vector3, NodeMaterialBlockTargets.Vertex);
+
+        // Fragment
+        this.registerInput("input", NodeMaterialBlockConnectionPointTypes.Color3OrColor4, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("fogColor", NodeMaterialBlockConnectionPointTypes.Color3, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("fogParameters", NodeMaterialBlockConnectionPointTypes.Vector4, NodeMaterialBlockTargets.Fragment);
+        this.registerOutput("output", NodeMaterialBlockConnectionPointTypes.Color3, NodeMaterialBlockTargets.Fragment);
+
+        // Auto configuration
+        this._inputs[1].setAsWellKnownValue(NodeMaterialWellKnownValues.View);
+        this._inputs[3].setAsWellKnownValue(NodeMaterialWellKnownValues.FogColor);
+        this._inputs[4].setAsWellKnownValue(NodeMaterialWellKnownValues.FogParameters);
+        this._outputs[0].isVarying = true;
+    }
+
+    /** @hidden */
+    public get _canAddAtFragmentRoot(): boolean {
+        return false;
+    }
+
+    protected _buildBlock(state: NodeMaterialCompilationState) {
+        super._buildBlock(state);
+
+        if (state.target === NodeMaterialBlockTargets.Fragment) {
+            state._emitFunction("CalcFogFactor",
+                `
+                    #define FOGMODE_NONE    0.
+                    #define FOGMODE_EXP     1.
+                    #define FOGMODE_EXP2    2.
+                    #define FOGMODE_LINEAR  3.
+                    #define E 2.71828
+
+                    float CalcFogFactor(vec3 vFogDistance, vec4 fogInfos)
+                    {
+                        float fogCoeff = 1.0;
+                        float fogStart = fogInfos.y;
+                        float fogEnd = fogInfos.z;
+                        float fogDensity = fogInfos.w;
+                        float fogDistance = length(vFogDistance);
+
+                        if (FOGMODE_LINEAR == fogInfos.x)
+                        {
+                            fogCoeff = (fogEnd - fogDistance) / (fogEnd - fogStart);
+                        }
+                        else if (FOGMODE_EXP == fogInfos.x)
+                        {
+                            fogCoeff = 1.0 / pow(E, fogDistance * fogDensity);
+                        }
+                        else if (FOGMODE_EXP2 == fogInfos.x)
+                        {
+                            fogCoeff = 1.0 / pow(E, fogDistance * fogDistance * fogDensity * fogDensity);
+                        }
+
+                        return clamp(fogCoeff, 0.0, 1.0);
+                    }
+                `);
+
+            let tempFogVariablename = state._getFreeVariableName("fog");
+            let input = this._inputs[2];
+            let fogColor = this._inputs[3];
+            let fogParameters = this._inputs[4];
+            let output = this._outputs[1];
+            let vFogDistance = this._outputs[0];
+
+            state.compilationString += `float ${tempFogVariablename} = CalcFogFactor(${vFogDistance.associatedVariableName}, ${fogParameters.associatedVariableName});\r\n`;
+            state.compilationString += this._declareOutput(output, state) + ` = ${tempFogVariablename} * ${input.associatedVariableName}.rgb + (1.0 - ${tempFogVariablename}) * ${fogColor.associatedVariableName};\r\n`;
+        } else {
+            let worldPos = this._inputs[0];
+            let view = this._inputs[1];
+            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";

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

@@ -0,0 +1,44 @@
+import { NodeMaterialBlock, NodeMaterialBlockTargets } from '../../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../../nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialCompilationState } from '../../nodeMaterialCompilationState';
+
+/**
+ * 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);
+    }
+
+    /** @hidden */
+    public get _canAddAtVertexRoot(): boolean {
+        return false;
+    }
+
+    /** @hidden */
+    public get _canAddAtFragmentRoot(): boolean {
+        return false;
+    }
+
+    protected _buildBlock(state: NodeMaterialCompilationState) {
+        super._buildBlock(state);
+
+        let input = this._inputs[0];
+
+        state.compilationString += `if (${input.associatedVariableName}.a < ${this.alphaCutOff}) discard;\r\n`;
+
+        return this;
+    }
+}

+ 19 - 11
src/Materials/Node/Blocks/Fragment/fragmentOutputBlock.ts

@@ -1,4 +1,4 @@
-import { NodeMaterialBlock } from '../../nodeMaterialBlock';
+import { NodeMaterialBlock, NodeMaterialBlockTargets } from '../../nodeMaterialBlock';
 import { NodeMaterialBlockConnectionPointTypes } from '../../nodeMaterialBlockConnectionPointTypes';
 import { NodeMaterialCompilationState } from '../../nodeMaterialCompilationState';
 
@@ -11,20 +11,28 @@ export class FragmentOutputBlock extends NodeMaterialBlock {
      * @param name defines the block name
      */
     public constructor(name: string) {
-        super(name);
+        super(name, NodeMaterialBlockTargets.Fragment);
 
-        this.registerEntryPoint("color", NodeMaterialBlockConnectionPointTypes.Color4);
+        this.registerInput("color", NodeMaterialBlockConnectionPointTypes.Color4);
     }
 
-    /**
-     * Compile the block
-     * @param state defines the current compilation state
-     */
-    public compile(state: NodeMaterialCompilationState) {
-        super.compile(state);
+    /** @hidden */
+    public get _canAddAtVertexRoot(): boolean {
+        return false;
+    }
+
+    /** @hidden */
+    public get _canAddAtFragmentRoot(): boolean {
+        return false;
+    }
+
+    protected _buildBlock(state: NodeMaterialCompilationState) {
+        super._buildBlock(state);
+
+        let input = this._inputs[0];
 
-        let entryPoint = this.entryPoints[0];
+        state.compilationString += `gl_FragColor = ${input.associatedVariableName};\r\n`;
 
-        state.compilationString += `gl_FragColor = ${entryPoint.associatedVariableName};\r\n`;
+        return this;
     }
 }

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

@@ -1,3 +1,7 @@
 
 export * from "./fragmentOutputBlock";
-export * from "./textureBlock";
+export * from "./alphaTestBlock";
+export * from "./rgbaMergerBlock";
+export * from "./rgbMergerBlock";
+export * from "./rgbaSplitterBlock";
+export * from "./rgbSplitterBlock";

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

@@ -0,0 +1,36 @@
+import { NodeMaterialBlock, NodeMaterialBlockTargets } from '../../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../../nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialCompilationState } from '../../nodeMaterialCompilationState';
+
+/**
+ * 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);
+    }
+
+    protected _buildBlock(state: NodeMaterialCompilationState) {
+        super._buildBlock(state);
+
+        let rInput = this._inputs[0];
+        let gInput = this._inputs[1];
+        let bInput = this._inputs[2];
+
+        let output = this._outputs[0];
+
+        state.compilationString += this._declareOutput(output, state) + ` = vec3(${rInput.associatedVariableName}, ${gInput.associatedVariableName}, ${bInput.associatedVariableName});\r\n`;
+
+        return this;
+    }
+}

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

@@ -0,0 +1,42 @@
+import { NodeMaterialBlock, NodeMaterialBlockTargets } from '../../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../../nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialCompilationState } from '../../nodeMaterialCompilationState';
+
+/**
+ * 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);
+    }
+
+    protected _buildBlock(state: NodeMaterialCompilationState) {
+        super._buildBlock(state);
+
+        let input = this._inputs[0];
+        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;
+    }
+}

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

@@ -0,0 +1,44 @@
+import { NodeMaterialBlock, NodeMaterialBlockTargets } from '../../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../../nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialCompilationState } from '../../nodeMaterialCompilationState';
+
+/**
+ * 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.Vector3OrColor3, true);
+        this.registerInput("a", NodeMaterialBlockConnectionPointTypes.Float, true);
+
+        this.registerOutput("output", NodeMaterialBlockConnectionPointTypes.Color4);
+    }
+
+    protected _buildBlock(state: NodeMaterialCompilationState) {
+        super._buildBlock(state);
+
+        let rgbInput = this._inputs[3];
+        let aInput = this._inputs[4];
+        let output = this._outputs[0];
+
+        if (rgbInput.connectedPoint) {
+            state.compilationString += this._declareOutput(output, state) + ` = vec4(${rgbInput.associatedVariableName}, ${aInput.associatedVariableName});\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(${rInput.associatedVariableName}, ${gInput.associatedVariableName}, ${bInput.associatedVariableName}, ${aInput.associatedVariableName});\r\n`;
+        }
+
+
+        return this;
+    }
+}

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

@@ -0,0 +1,48 @@
+import { NodeMaterialBlock, NodeMaterialBlockTargets } from '../../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../../nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialCompilationState } from '../../nodeMaterialCompilationState';
+
+/**
+ * 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);
+    }
+
+    protected _buildBlock(state: NodeMaterialCompilationState) {
+        super._buildBlock(state);
+
+        let input = this._inputs[0];
+        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;
+    }
+}

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

@@ -1,36 +0,0 @@
-import { NodeMaterialBlock } from '../../nodeMaterialBlock';
-import { NodeMaterialBlockConnectionPointTypes } from '../../nodeMaterialBlockConnectionPointTypes';
-import { NodeMaterialCompilationState } from '../../nodeMaterialCompilationState';
-
-/**
- * Block used to read a texture from a sampler
- */
-export class TextureBlock extends NodeMaterialBlock {
-    /**
-     * Create a new TextureBlock
-     * @param name defines the block name
-     */
-    public constructor(name: string) {
-        super(name);
-
-        this.registerEntryPoint("uv", NodeMaterialBlockConnectionPointTypes.Vector2);
-        this.registerEntryPoint("texture", NodeMaterialBlockConnectionPointTypes.Texture);
-
-        this.registerExitPoint("color", NodeMaterialBlockConnectionPointTypes.Color4);
-    }
-
-    /**
-     * Compile the block
-     * @param state defines the current compilation state
-     */
-    public compile(state: NodeMaterialCompilationState) {
-        super.compile(state);
-
-        let uvEntryPoint = this.entryPoints[0];
-        let samplerEntryPoint = this.entryPoints[1];
-
-        let output = this.exitPoints[0];
-
-        state.compilationString += `vec4 ${output.associatedVariableName} = texture2D(${samplerEntryPoint.associatedVariableName}, ${uvEntryPoint.associatedVariableName});\r\n`;
-    }
-}

+ 1 - 3
src/Materials/Node/Blocks/Vertex/index.ts

@@ -1,3 +1 @@
-export * from "./vertexOutputBlock";
-export * from "./vector3TransformBlock";
-export * from "./vector4TransformBlock";
+export * from "./vertexOutputBlock";

+ 0 - 39
src/Materials/Node/Blocks/Vertex/vector3TransformBlock.ts

@@ -1,39 +0,0 @@
-import { NodeMaterialBlock } from '../../nodeMaterialBlock';
-import { NodeMaterialBlockConnectionPointTypes } from '../../nodeMaterialBlockConnectionPointTypes';
-import { NodeMaterialCompilationState } from '../../nodeMaterialCompilationState';
-
-/**
- * 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 Vector4TransformBlock
-     * @param name defines the block name
-     */
-    public constructor(name: string) {
-        super(name);
-
-        this.registerEntryPoint("vector", NodeMaterialBlockConnectionPointTypes.Vector3);
-        this.registerEntryPoint("transform", NodeMaterialBlockConnectionPointTypes.Matrix);
-        this.registerExitPoint("output", NodeMaterialBlockConnectionPointTypes.Vector4);
-    }
-
-    /**
-     * Compile the block
-     * @param state defines the current compilation state
-     */
-    public compile(state: NodeMaterialCompilationState) {
-        super.compile(state);
-
-        let output = this._exitPoints[0];
-        let vector = this._entryPoints[0];
-        let transform = this._entryPoints[1];
-
-        state.compilationString += `vec4 ${output.associatedVariableName} = ${transform.associatedVariableName} * vec4(${vector.associatedVariableName}, ${this.complement});\r\n`;
-    }
-}

+ 0 - 35
src/Materials/Node/Blocks/Vertex/vector4TransformBlock.ts

@@ -1,35 +0,0 @@
-import { NodeMaterialBlock } from '../../nodeMaterialBlock';
-import { NodeMaterialBlockConnectionPointTypes } from '../../nodeMaterialBlockConnectionPointTypes';
-import { NodeMaterialCompilationState } from '../../nodeMaterialCompilationState';
-
-/**
- * Block used to transform a vector4 with a matrix
- */
-export class Vector4TransformBlock extends NodeMaterialBlock {
-
-    /**
-     * Creates a new Vector4TransformBlock
-     * @param name defines the block name
-     */
-    public constructor(name: string) {
-        super(name);
-
-        this.registerEntryPoint("vector", NodeMaterialBlockConnectionPointTypes.Vector4);
-        this.registerEntryPoint("transform", NodeMaterialBlockConnectionPointTypes.Matrix);
-        this.registerExitPoint("output", NodeMaterialBlockConnectionPointTypes.Vector4);
-    }
-
-    /**
-     * Compile the block
-     * @param state defines the current compilation state
-     */
-    public compile(state: NodeMaterialCompilationState) {
-        super.compile(state);
-
-        let output = this._exitPoints[0];
-        let vector = this._entryPoints[0];
-        let transform = this._entryPoints[1];
-
-        state.compilationString += `vec4 ${output.associatedVariableName} = ${transform.associatedVariableName} * ${vector.associatedVariableName};\r\n`;
-    }
-}

+ 19 - 11
src/Materials/Node/Blocks/Vertex/vertexOutputBlock.ts

@@ -1,4 +1,4 @@
-import { NodeMaterialBlock } from '../../nodeMaterialBlock';
+import { NodeMaterialBlock, NodeMaterialBlockTargets } from '../../nodeMaterialBlock';
 import { NodeMaterialBlockConnectionPointTypes } from '../../nodeMaterialBlockConnectionPointTypes';
 import { NodeMaterialCompilationState } from '../../nodeMaterialCompilationState';
 
@@ -12,20 +12,28 @@ export class VertexOutputBlock extends NodeMaterialBlock {
      * @param name defines the block name
      */
     public constructor(name: string) {
-        super(name);
+        super(name, NodeMaterialBlockTargets.Vertex);
 
-        this.registerEntryPoint("vector", NodeMaterialBlockConnectionPointTypes.Vector3);
+        this.registerInput("vector", NodeMaterialBlockConnectionPointTypes.Vector4);
     }
 
-    /**
-     * Compile the block
-     * @param state defines the current compilation state
-     */
-    public compile(state: NodeMaterialCompilationState) {
-        super.compile(state);
+    /** @hidden */
+    public get _canAddAtVertexRoot(): boolean {
+        return false;
+    }
+
+    /** @hidden */
+    public get _canAddAtFragmentRoot(): boolean {
+        return false;
+    }
+
+    protected _buildBlock(state: NodeMaterialCompilationState) {
+        super._buildBlock(state);
+
+        let input = this._inputs[0];
 
-        let entryPoint = this.entryPoints[0];
+        state.compilationString += `gl_Position = ${input.associatedVariableName};\r\n`;
 
-        state.compilationString += `gl_Position = ${entryPoint.associatedVariableName};\r\n`;
+        return this;
     }
 }

+ 7 - 1
src/Materials/Node/Blocks/index.ts

@@ -1,2 +1,8 @@
 export * from "./Vertex/index";
-export * from "./Fragment/index";
+export * from "./Fragment/index";
+export * from "./Dual/index";
+export * from "./mixBlock";
+export * from "./textureBlock";
+export * from "./vector2TransformBlock";
+export * from "./vector3TransformBlock";
+export * from "./vector4TransformBlock";

+ 32 - 0
src/Materials/Node/Blocks/mixBlock.ts

@@ -0,0 +1,32 @@
+import { NodeMaterialBlock } from '../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialCompilationState } from '../nodeMaterialCompilationState';
+/**
+ * Block used to mix 2 vector4
+ */
+export class MixBlock extends NodeMaterialBlock {
+    /**
+     * Creates a new MixBlock
+     * @param name defines the block name
+     */
+    public constructor(name: string) {
+        super(name);
+
+        this.registerInput("vector0", NodeMaterialBlockConnectionPointTypes.Vector4OrColor4);
+        this.registerInput("vector1", NodeMaterialBlockConnectionPointTypes.Vector4OrColor4);
+        this.registerOutput("output", NodeMaterialBlockConnectionPointTypes.Vector4OrColor4);
+    }
+
+    protected _buildBlock(state: NodeMaterialCompilationState) {
+        super._buildBlock(state);
+
+        let output = this._outputs[0];
+
+        let vector0 = this._inputs[0];
+        let vector1 = this._inputs[1];
+
+        state.compilationString += this._declareOutput(output, state) + ` = ${vector0.associatedVariableName} * ${vector1.associatedVariableName};\r\n`;
+
+        return this;
+    }
+}

+ 34 - 0
src/Materials/Node/Blocks/textureBlock.ts

@@ -0,0 +1,34 @@
+import { NodeMaterialBlock, NodeMaterialBlockTargets } from '../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialCompilationState } from '../nodeMaterialCompilationState';
+
+/**
+ * Block used to read a texture from a sampler
+ */
+export class TextureBlock extends NodeMaterialBlock {
+    /**
+     * 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("texture", NodeMaterialBlockConnectionPointTypes.Texture);
+
+        this.registerOutput("color", NodeMaterialBlockConnectionPointTypes.Color4);
+    }
+
+    protected _buildBlock(state: NodeMaterialCompilationState) {
+        super._buildBlock(state);
+
+        let uvInput = this._inputs[0];
+        let samplerInput = this._inputs[1];
+
+        let output = this._outputs[0];
+
+        state.compilationString += `vec4 ${output.associatedVariableName} = texture2D(${samplerInput.associatedVariableName}, ${uvInput.associatedVariableName});\r\n`;
+
+        return this;
+    }
+}

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

@@ -0,0 +1,41 @@
+import { NodeMaterialBlock, NodeMaterialBlockTargets } from '../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialCompilationState } from '../nodeMaterialCompilationState';
+/**
+ * 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);
+    }
+
+    protected _buildBlock(state: NodeMaterialCompilationState) {
+        super._buildBlock(state);
+
+        let output = this._outputs[0];
+        let vector = this._inputs[0];
+        let transform = this._inputs[1];
+
+        state.compilationString += this._declareOutput(output, state) + ` = vec2(${transform.associatedVariableName} * vec4(${vector.associatedVariableName}, ${this.complementZ}, ${this.complementW}));\r\n`;
+
+        return this;
+    }
+}

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

@@ -0,0 +1,37 @@
+import { NodeMaterialBlock, NodeMaterialBlockTargets } from '../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialCompilationState } from '../nodeMaterialCompilationState';
+
+/**
+ * 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);
+    }
+
+    protected _buildBlock(state: NodeMaterialCompilationState) {
+        super._buildBlock(state);
+
+        let output = this._outputs[0];
+        let vector = this._inputs[0];
+        let transform = this._inputs[1];
+
+        state.compilationString += this._declareOutput(output, state) + ` = ${transform.associatedVariableName} * vec4(${vector.associatedVariableName}, ${this.complement});\r\n`;
+
+        return this;
+    }
+}

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

@@ -0,0 +1,33 @@
+import { NodeMaterialBlock, NodeMaterialBlockTargets } from '../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialCompilationState } from '../nodeMaterialCompilationState';
+
+/**
+ * Block used to transform a vector4 with a matrix
+ */
+export class Vector4TransformBlock extends NodeMaterialBlock {
+
+    /**
+     * Creates a new Vector4TransformBlock
+     * @param name defines the block name
+     */
+    public constructor(name: string) {
+        super(name, NodeMaterialBlockTargets.Vertex);
+
+        this.registerInput("vector", NodeMaterialBlockConnectionPointTypes.Vector4);
+        this.registerInput("transform", NodeMaterialBlockConnectionPointTypes.Matrix);
+        this.registerOutput("output", NodeMaterialBlockConnectionPointTypes.Vector4);
+    }
+
+    protected _buildBlock(state: NodeMaterialCompilationState) {
+        super._buildBlock(state);
+
+        let output = this._outputs[0];
+        let vector = this._inputs[0];
+        let transform = this._inputs[1];
+
+        state.compilationString += this._declareOutput(output, state) + ` = ${transform.associatedVariableName} * ${vector.associatedVariableName};\r\n`;
+
+        return this;
+    }
+}

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

@@ -2,4 +2,5 @@ export * from "./nodeMaterialBlockConnectionPointTypes";
 export * from "./nodeMaterialBlockConnectionPoint";
 export * from "./nodeMaterialBlock";
 export * from "./nodeMaterial";
+export * from "./nodeMaterialWellKnownValues";
 export * from "./Blocks/index";

+ 203 - 30
src/Materials/Node/nodeMaterial.ts

@@ -1,15 +1,16 @@
-import { NodeMaterialBlock } from './nodeMaterialBlock';
+import { NodeMaterialBlock, NodeMaterialBlockTargets } from './nodeMaterialBlock';
 import { Material } from '../material';
 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 { NodeMaterialCompilationState } from './nodeMaterialCompilationState';
+import { NodeMaterialCompilationState, NodeMaterialCompilationStateSharedData } from './nodeMaterialCompilationState';
 import { EffectCreationOptions } from '../effect';
 import { BaseTexture } from '../../Materials/Textures/baseTexture';
 import { NodeMaterialConnectionPoint } from './nodeMaterialBlockConnectionPoint';
 import { NodeMaterialBlockConnectionPointTypes } from './nodeMaterialBlockConnectionPointTypes';
+import { Observable } from '../../Misc/observable';
 
 /**
  * Class used to configure NodeMaterial
@@ -23,6 +24,11 @@ export interface INodeMaterialOptions {
      * Defines if the material needs alpha testing
      */
     needAlphaTesting: boolean;
+
+    /**
+     * Defines if blocks should emit comments
+     */
+    emitComments: boolean;
 }
 
 /**
@@ -32,7 +38,7 @@ export class NodeMaterial extends Material {
     private _options: INodeMaterialOptions;
     private _vertexCompilationState: NodeMaterialCompilationState;
     private _fragmentCompilationState: NodeMaterialCompilationState;
-    private _compileId: number = 0;
+    private _buildId: number = 0;
     private _renderId: number;
     private _effectCompileId: number = 0;
     private _cachedWorldViewMatrix = new Matrix();
@@ -40,14 +46,19 @@ export class NodeMaterial extends Material {
     private _textureConnectionPoints = new Array<NodeMaterialConnectionPoint>();
 
     /**
+     * Observable raised when the material is built
+     */
+    public onBuildObservable = new Observable<NodeMaterial>();
+
+    /**
      * Gets or sets the root nodes of the material vertex shader
      */
-    public vertexRootNodes = new Array<NodeMaterialBlock>();
+    private _vertexRootNodes = new Array<NodeMaterialBlock>();
 
     /**
      * Gets or sets the root nodes of the material fragment (pixel) shader
      */
-    public fragmentRootNodes = new Array<NodeMaterialBlock>();
+    private _fragmentRootNodes = new Array<NodeMaterialBlock>();
 
     /** Gets or sets options to control the node material overall behavior */
     public get options() {
@@ -70,6 +81,7 @@ export class NodeMaterial extends Material {
         this._options = {
             needAlphaBlending: false,
             needAlphaTesting: false,
+            emitComments: false,
             ...options
         };
     }
@@ -83,6 +95,92 @@ export class NodeMaterial extends Material {
     }
 
     /**
+     * Add a new block to the list of root nodes
+     * @param node defines the node to add
+     * @returns the current material
+     */
+    public addRootNode(node: NodeMaterialBlock) {
+        if (node.target === null) {
+            throw "This node is not meant to be at root level. You may want to explicitly set its target value.";
+        }
+
+        if ((node.target & NodeMaterialBlockTargets.Vertex) !== 0 && node._canAddAtVertexRoot) {
+            this._addVertexRootNode(node);
+        }
+
+        if ((node.target & NodeMaterialBlockTargets.Fragment) !== 0 && node._canAddAtFragmentRoot) {
+            this._addFragmentRootNode(node);
+        }
+
+        return this;
+    }
+
+    /**
+     * Remove a block from the list of root nodes
+     * @param node defines the node to remove
+     * @returns the current material
+     */
+    public removeRootNode(node: NodeMaterialBlock) {
+        if (node.target === null) {
+            return this;
+        }
+
+        if ((node.target & NodeMaterialBlockTargets.Vertex) !== 0) {
+            this._removeVertexRootNode(node);
+        }
+
+        if ((node.target & NodeMaterialBlockTargets.Fragment) !== 0) {
+            this._removeFragmentRootNode(node);
+        }
+
+        return this;
+    }
+
+    private _addVertexRootNode(node: NodeMaterialBlock) {
+        if (this._vertexRootNodes.indexOf(node) !== -1) {
+            return;
+        }
+
+        node.target = NodeMaterialBlockTargets.Vertex;
+        this._vertexRootNodes.push(node);
+
+        return this;
+    }
+
+    private _removeVertexRootNode(node: NodeMaterialBlock) {
+        let index = this._vertexRootNodes.indexOf(node);
+        if (index === -1) {
+            return;
+        }
+
+        this._vertexRootNodes.splice(index, 1);
+
+        return this;
+    }
+
+    private _addFragmentRootNode(node: NodeMaterialBlock) {
+        if (this._fragmentRootNodes.indexOf(node) !== -1) {
+            return;
+        }
+
+        node.target = NodeMaterialBlockTargets.Fragment;
+        this._fragmentRootNodes.push(node);
+
+        return this;
+    }
+
+    private _removeFragmentRootNode(node: NodeMaterialBlock) {
+        let index = this._fragmentRootNodes.indexOf(node);
+        if (index === -1) {
+            return;
+        }
+
+        this._fragmentRootNodes.splice(index, 1);
+
+        return this;
+    }
+
+    /**
      * Specifies if the material will require alpha blending
      * @returns a boolean specifying if alpha blending is needed
      */
@@ -98,50 +196,94 @@ export class NodeMaterial extends Material {
         return this._options.needAlphaTesting;
     }
 
+    private _propagateTarget(node: NodeMaterialBlock, target: NodeMaterialBlockTargets) {
+        node.target = target;
+
+        for (var exitPoint of node.outputs) {
+            for (var block of exitPoint.connectedBlocks) {
+                if (block) {
+                    this._propagateTarget(block, target);
+                }
+            }
+        }
+    }
+
+    private _resetDualBlocks(node: NodeMaterialBlock, id: number) {
+        if (node.target === NodeMaterialBlockTargets.VertexAndFragment) {
+            node.buildId = id;
+        }
+
+        for (var exitPoint of node.outputs) {
+            for (var block of exitPoint.connectedBlocks) {
+                if (block) {
+                    this._resetDualBlocks(block, id);
+                }
+            }
+        }
+    }
+
     /**
-     * Compile the material and generates the inner effect
+     * Build the material and generates the inner effect
      */
-    public compile() {
-        if (this.vertexRootNodes.length === 0) {
+    public build() {
+        if (this._vertexRootNodes.length === 0) {
             throw "You must define at least one vertexRootNode";
         }
 
-        if (this.fragmentRootNodes.length === 0) {
+        if (this._fragmentRootNodes.length === 0) {
             throw "You must define at least one fragmentRootNode";
         }
 
         // Go through the nodes and do some magic :)
         // Needs to create the code and deduce samplers and uniforms in order to populate some lists used during bindings
 
+        // Propagate targets
+        for (var vertexRootNode of this._vertexRootNodes) {
+            this._propagateTarget(vertexRootNode, NodeMaterialBlockTargets.Vertex);
+        }
+
+        for (var fragmentRootNode of this._fragmentRootNodes) {
+            this._propagateTarget(fragmentRootNode, NodeMaterialBlockTargets.Fragment);
+        }
+
         // Vertex
         this._vertexCompilationState = new NodeMaterialCompilationState();
-
-        for (var vertexRootNode of this.vertexRootNodes) {
-            vertexRootNode.compile(this._vertexCompilationState);
-            vertexRootNode.compileChildren(this._vertexCompilationState);
+        this._vertexCompilationState.target = NodeMaterialBlockTargets.Vertex;
+        this._fragmentCompilationState = new NodeMaterialCompilationState();
+        let sharedData = new NodeMaterialCompilationStateSharedData();
+        this._vertexCompilationState.sharedData = sharedData;
+        this._fragmentCompilationState.sharedData = sharedData;
+        sharedData.buildId = this._buildId;
+        sharedData.emitComments = this._options.emitComments;
+
+        for (var vertexRootNode of this._vertexRootNodes) {
+            vertexRootNode.build(this._vertexCompilationState);
         }
 
         // Fragment
-        this._fragmentCompilationState = new NodeMaterialCompilationState();
-        this._fragmentCompilationState.isInFragmentMode = true;
+        this._fragmentCompilationState.target = NodeMaterialBlockTargets.Fragment;
         this._fragmentCompilationState._vertexState = this._vertexCompilationState;
         this._fragmentCompilationState.hints = this._vertexCompilationState.hints;
         this._fragmentCompilationState._uniformConnectionPoints = this._vertexCompilationState._uniformConnectionPoints;
 
-        for (var fragmentRootNode of this.fragmentRootNodes) {
-            fragmentRootNode.compile(this._fragmentCompilationState);
-            fragmentRootNode.compileChildren(this._fragmentCompilationState);
+        for (var fragmentRootNode of this._fragmentRootNodes) {
+            this._resetDualBlocks(fragmentRootNode, this._buildId - 1);
+        }
+
+        for (var fragmentRootNode of this._fragmentRootNodes) {
+            fragmentRootNode.build(this._fragmentCompilationState);
         }
 
         // Finalize
-        this._vertexCompilationState.varyings = this._fragmentCompilationState.varyings;
-        this._vertexCompilationState.finalize();
-        this._fragmentCompilationState.finalize();
+        this._vertexCompilationState.finalize(this._vertexCompilationState);
+        this._fragmentCompilationState.finalize(this._fragmentCompilationState);
 
         // Textures
         this._textureConnectionPoints = this._fragmentCompilationState._uniformConnectionPoints.filter((u) => u.type === NodeMaterialBlockConnectionPointTypes.Texture);
 
-        this._compileId++;
+        this._buildId++;
+
+        this.onBuildObservable.notifyObservers(this);
     }
 
     /**
@@ -170,7 +312,7 @@ export class NodeMaterial extends Material {
 
         this._renderId = scene.getRenderId();
 
-        if (this._effectCompileId === this._compileId) {
+        if (this._effectCompileId === this._buildId) {
             return true;
         }
 
@@ -202,8 +344,8 @@ export class NodeMaterial extends Material {
 
         // Compilation
         this._effect = engine.createEffect({
-            vertex: "nodeMaterial" + this._compileId,
-            fragment: "nodeMaterial" + this._compileId,
+            vertex: "nodeMaterial" + this._buildId,
+            fragment: "nodeMaterial" + this._buildId,
             vertexSource: this._vertexCompilationState.compilationString,
             fragmentSource: this._fragmentCompilationState.compilationString
         }, <EffectCreationOptions>{
@@ -223,7 +365,7 @@ export class NodeMaterial extends Material {
             scene.resetCachedMaterial();
         }
 
-        this._effectCompileId = this._compileId;
+        this._effectCompileId = this._buildId;
 
         return true;
     }
@@ -261,22 +403,31 @@ export class NodeMaterial extends Material {
      * @param mesh defines the mesh to bind the material to
      */
     public bind(world: Matrix, mesh?: Mesh): void {
+        let scene = this.getScene();
         // Std values
         this.bindOnlyWorldMatrix(world);
 
-        if (this._effect && this.getScene().getCachedMaterial() !== this) {
+        if (this._effect && scene.getCachedMaterial() !== this) {
             let hints = this._fragmentCompilationState.hints;
 
             if (hints.needViewMatrix) {
-                this._effect.setMatrix("view", this.getScene().getViewMatrix());
+                this._effect.setMatrix("view", scene.getViewMatrix());
             }
 
             if (hints.needProjectionMatrix) {
-                this._effect.setMatrix("projection", this.getScene().getProjectionMatrix());
+                this._effect.setMatrix("projection", scene.getProjectionMatrix());
             }
 
             if (hints.needViewProjectionMatrix) {
-                this._effect.setMatrix("viewProjection", this.getScene().getTransformMatrix());
+                this._effect.setMatrix("viewProjection", scene.getTransformMatrix());
+            }
+
+            if (hints.needFogColor) {
+                this._effect.setColor3("fogColor", scene.fogColor);
+            }
+
+            if (hints.needFogParameters) {
+                this._effect.setFloat4("fogParameters", scene.fogMode, scene.fogStart, scene.fogEnd, scene.fogDensity);
             }
 
             for (var connectionPoint of this._fragmentCompilationState._uniformConnectionPoints) {
@@ -321,4 +472,26 @@ export class NodeMaterial extends Material {
 
         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);
+    }
 }

+ 198 - 59
src/Materials/Node/nodeMaterialBlock.ts

@@ -1,15 +1,32 @@
 import { NodeMaterialConnectionPoint } from './nodeMaterialBlockConnectionPoint';
 import { NodeMaterialBlockConnectionPointTypes } from './nodeMaterialBlockConnectionPointTypes';
 import { NodeMaterialCompilationState } from './nodeMaterialCompilationState';
+import { Nullable } from '../../types';
+
+/**
+ * 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
+}
 
 /**
  * Defines a block that can be used inside a node based material
  */
 export class NodeMaterialBlock {
+    private _buildId: number;
+    private _userDefinedTarget: Nullable<NodeMaterialBlockTargets> = null;
+    private _restrictedTarget: Nullable<NodeMaterialBlockTargets> = null;
+
     /** @hidden */
-    protected _entryPoints = new Array<NodeMaterialConnectionPoint>();
+    protected _inputs = new Array<NodeMaterialConnectionPoint>();
     /** @hidden */
-    protected _exitPoints = new Array<NodeMaterialConnectionPoint>();
+    protected _outputs = new Array<NodeMaterialConnectionPoint>();
 
     /**
      * Gets or sets the name of the block
@@ -17,24 +34,50 @@ export class NodeMaterialBlock {
     public name: string;
 
     /**
-     * Gets the list of entry points
+     * 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 type of the block
+     */
+    public get target() {
+        if (this._restrictedTarget !== null) {
+            return this._restrictedTarget;
+        }
+
+        return this._userDefinedTarget;
+    }
+
+    public set target(value: Nullable<NodeMaterialBlockTargets>) {
+        this._userDefinedTarget = value;
+    }
+
+    /**
+     * Gets the list of input points
      */
-    public get entryPoints(): NodeMaterialConnectionPoint[] {
-        return this._entryPoints;
+    public get inputs(): NodeMaterialConnectionPoint[] {
+        return this._inputs;
     }
 
-    /** Gets the list of exit points */
-    public get exitPoints(): NodeMaterialConnectionPoint[] {
-        return this._exitPoints;
+    /** Gets the list of output points */
+    public get outputs(): NodeMaterialConnectionPoint[] {
+        return this._outputs;
     }
 
     /**
-     * Find an entry point by its name
-     * @param name defines the name of the entry point to look for
-     * @returns the entry point or null if not found
+     * 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 getEntryPointByName(name: string) {
-        let filter = this._entryPoints.filter((e) => e.name === name);
+    public getInputByName(name: string) {
+        let filter = this._inputs.filter((e) => e.name === name);
 
         if (filter.length) {
             return filter[0];
@@ -44,12 +87,12 @@ export class NodeMaterialBlock {
     }
 
     /**
-     * Find an exit point by its name
-     * @param name defines the name of the exit point to look for
-     * @returns the exit point or null if not found
+     * 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 getExitPointByName(name: string) {
-        let filter = this._exitPoints.filter((e) => e.name === name);
+    public getOutputByName(name: string) {
+        let filter = this._outputs.filter((e) => e.name === name);
 
         if (filter.length) {
             return filter[0];
@@ -61,9 +104,22 @@ export class NodeMaterialBlock {
     /**
      * Creates a new NodeMaterialBlock
      * @param name defines the block name
+     * @param restrictedTarget defines the target of that block (can be null)
      */
-    public constructor(name: string) {
+    public constructor(name: string, restrictedTarget?: NodeMaterialBlockTargets) {
         this.name = name;
+
+        if (restrictedTarget !== undefined) {
+            this._restrictedTarget = restrictedTarget;
+        }
+    }
+
+    protected _declareOutput(output: NodeMaterialConnectionPoint, state: NodeMaterialCompilationState): string {
+        if (output.isVarying) {
+            return `${output.associatedVariableName}`;
+        }
+
+        return `${state._getGLType(output.type)} ${output.associatedVariableName}`;
     }
 
     /**
@@ -75,37 +131,71 @@ export class NodeMaterialBlock {
     }
 
     /**
-     * Register a new entry point. Must be called inside a block constructor
+     * 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 registerEntryPoint(name: string, type: NodeMaterialBlockConnectionPointTypes) {
+    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);
 
-        this._entryPoints.push(point);
+        return this;
     }
 
     /**
-     * Register a new exit point. Must be called inside a block constructor
+     * 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 registerExitPoint(name: string, type: NodeMaterialBlockConnectionPointTypes) {
+    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);
 
-        this._exitPoints.push(point);
+        return this;
     }
 
     /**
-     * Will return the first available entry point e.g. the first one which is not an uniform or an attribute
-     * @returns the first available entry point or null
+     * 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 getFirstAvailableEntryPoint() {
-        for (var entryPoint of this._entryPoints) {
-            if (!entryPoint.isUniform && !entryPoint.isAttribute) {
-                return entryPoint;
+    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;
             }
         }
 
@@ -115,58 +205,107 @@ export class NodeMaterialBlock {
     /**
      * Connect current block with another block
      * @param other defines the block to connect with
-     * @param entryPointName define the name of the other block entry point (will take the first available one if not defined)
-     * @param exitPointName define the name of current block exit point (will take the first one if not defined)
+     * @param inputName define the name of the other block input (will take the first available one if not defined)
+     * @param outputName define the name of current block output (will take the first one if not defined)
+     * @returns the current block
      */
-    public connectTo(other: NodeMaterialBlock, entryPointName?: string, exitPointName?: string) {
-        if (this._exitPoints.length === 0) {
+    public connectTo(other: NodeMaterialBlock, inputName?: string, outputName?: string) {
+        if (this._outputs.length === 0) {
             return;
         }
 
-        let output = exitPointName ? this.getExitPointByName(exitPointName) : this._exitPoints[0];
-        let input = entryPointName ? other.getEntryPointByName(entryPointName) : other.getFirstAvailableEntryPoint();
+        let output = outputName ? this.getOutputByName(outputName) : this.getFirstAvailableOutput(other);
+        let input = inputName ? other.getInputByName(inputName) : other.getFirstAvailableInput(output);
 
         if (output && input) {
             output.connectTo(input);
+        } else {
+            throw "Unable to find a compatible match";
         }
+
+        return this;
+    }
+
+    protected _buildBlock(state: NodeMaterialCompilationState) {
+        // Empty. Must be defined by child nodes
+    }
+
+    /** @hidden */
+    public get _canAddAtVertexRoot(): boolean {
+        return true; // Must be overriden by children
+    }
+
+    /** @hidden */
+    public get _canAddAtFragmentRoot(): boolean {
+        return true; // Must be overriden by children
     }
 
     /**
      * 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 compile(state: NodeMaterialCompilationState) {
-        for (var entryPoint of this._entryPoints) {
-            state._emitUniformOrAttributes(entryPoint);
+    public build(state: NodeMaterialCompilationState) {
+        if (this._buildId === state.sharedData.buildId) {
+            return;
         }
 
-        for (var exitPoint of this._exitPoints) {
-            exitPoint.associatedVariableName = state._getFreeVariableName(exitPoint.name);
-            state._emitVaryings(exitPoint);
+        // Check if "parent" blocks are compiled
+        for (var input of this._inputs) {
+            if (!input.connectedPoint) {
+                continue;
+            }
+
+            if ((input.target & this.target!) === 0) {
+                continue;
+            }
+
+            let block = input.connectedPoint.ownerBlock;
+            if (block && block.target === this.target && block.buildId !== state.sharedData.buildId) {
+                block.build(state);
+            }
         }
-    }
 
-    /**
-     * Compile the block children
-     * @param state defines the current compilation state
-     */
-    public compileChildren(state: NodeMaterialCompilationState) {
-        // Compile blocks
-        for (var exitPoint of this._exitPoints) {
-            let block = exitPoint.connectedBlock;
+        if (this._buildId === state.sharedData.buildId) {
+            return; // Need to check again as inputs can be connected multiple time to this endpoint
+        }
 
-            if (block) {
-                block.compile(state);
+        // Build
+        for (var input of this._inputs) {
+            if ((input.target & this.target!) === 0) {
+                continue;
             }
+            state._emitUniformOrAttributes(input);
         }
 
-        // Compile children
-        for (var exitPoint of this._exitPoints) {
-            let block = exitPoint.connectedBlock;
+        for (var output of this._outputs) {
+            if ((output.target & this.target!) === 0 || output.associatedVariableName) {
+                continue;
+            }
+            output.associatedVariableName = state._getFreeVariableName(output.name);
+            state._emitVaryings(output);
+        }
+
+        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;
+            }
 
-            if (block) {
-                block.compileChildren(state);
+            for (var block of output.connectedBlocks) {
+                if (block && (!block.target || (block.target & this.target!) !== 0)) {
+                    block.build(state);
+                }
             }
         }
+        return this;
     }
 }

+ 141 - 18
src/Materials/Node/nodeMaterialBlockConnectionPoint.ts

@@ -1,7 +1,8 @@
 import { NodeMaterialBlockConnectionPointTypes } from './nodeMaterialBlockConnectionPointTypes';
-import { NodeMaterialBlock } from './nodeMaterialBlock';
+import { NodeMaterialBlock, NodeMaterialBlockTargets } from './nodeMaterialBlock';
 import { Nullable } from '../../types';
 import { Effect } from '../effect';
+import { NodeMaterialWellKnownValues } from './nodeMaterialWellKnownValues';
 
 /**
  * Defines a connection point for a block
@@ -10,8 +11,13 @@ export class NodeMaterialConnectionPoint {
     private _ownerBlock: NodeMaterialBlock;
     private _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)
@@ -24,7 +30,16 @@ export class NodeMaterialConnectionPoint {
     public name: string;
 
     /**
-     * Gets or sets the value of that point (when defined as isUniform === true)
+     * Gets or sets a boolean indicating that this input can be omitted
+     */
+    public isOptional: boolean
+
+    /** 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;
@@ -32,6 +47,20 @@ export class NodeMaterialConnectionPoint {
 
     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;
     }
 
     /**
@@ -52,14 +81,14 @@ export class NodeMaterialConnectionPoint {
     /**
      * 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 entry points
+     * 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 entry points
+     * Can only be set on inputs
      */
     public isAttribute: boolean;
 
@@ -67,7 +96,19 @@ export class NodeMaterialConnectionPoint {
      * Gets or sets a boolean indicating that this connection point is generating a varying variable.
      * Can only be set on exit points
      */
-    public isVarying: boolean;
+    public get isVarying(): boolean {
+        for (var connectedBlock of this.connectedBlocks) {
+            if (connectedBlock.target && this.ownerBlock.target && (connectedBlock.target & this.ownerBlock.target) === 0) {
+                return true;
+            }
+        }
+
+        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> {
@@ -80,7 +121,7 @@ export class NodeMaterialConnectionPoint {
     }
 
     /** Get the block connected on the other side of this connection (if any) */
-    public get connectedBlock(): Nullable<NodeMaterialBlock> {
+    public get sourceBlock(): Nullable<NodeMaterialBlock> {
         if (!this._connectedPoint) {
             return null;
         }
@@ -88,6 +129,15 @@ export class NodeMaterialConnectionPoint {
         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
@@ -107,12 +157,83 @@ export class NodeMaterialConnectionPoint {
     }
 
     /**
+     * Set the source of this connection point to a vertex attribute
+     * @param attributeName defines the attribute name (position, uv, normal, etc...)
+     * @returns the current connection point
+     */
+    public setAsAttribute(attributeName: string): NodeMaterialConnectionPoint {
+        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 {
+        switch (value) {
+            case NodeMaterialWellKnownValues.World:
+                this.name = "world";
+                break;
+            case NodeMaterialWellKnownValues.View:
+                this.name = "view";
+                break;
+            case NodeMaterialWellKnownValues.Projection:
+                this.name = "projection";
+                break;
+            case NodeMaterialWellKnownValues.WorldView:
+                this.name = "worldView";
+                break;
+            case NodeMaterialWellKnownValues.WorldViewProjection:
+                this.name = "worldViewProjection";
+                break;
+            case NodeMaterialWellKnownValues.ViewProjection:
+                this.name = "viewProjection";
+                break;
+            case NodeMaterialWellKnownValues.FogColor:
+                this.name = "fogColor";
+                break;
+            case NodeMaterialWellKnownValues.FogParameters:
+                this.name = "fogParameters";
+                break;
+        }
+        this.isUniform = true;
+        this._wellKnownValue = value;
+        return this;
+    }
+
+    /**
      * Connect this point to another connection point
      * @param connectionPoint defines the other connection point
+     * @returns the current connection point
      */
-    public connectTo(connectionPoint: NodeMaterialConnectionPoint) {
-        this._connectedPoint = connectionPoint;
+    public connectTo(connectionPoint: NodeMaterialConnectionPoint): NodeMaterialConnectionPoint {
+        if ((this.type & connectionPoint.type) === 0) {
+            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;
     }
 
     /**
@@ -120,33 +241,35 @@ export class NodeMaterialConnectionPoint {
      * @param effect defines the effect to transmit value to
      */
     public transmit(effect: Effect) {
+        let value = this._valueCallback ? this._valueCallback() : this._storedValue;
+
         switch (this.type) {
             case NodeMaterialBlockConnectionPointTypes.Float:
-                effect.setFloat(this.name, this.value);
+                effect.setFloat(this.name, value);
                 break;
             case NodeMaterialBlockConnectionPointTypes.Int:
-                effect.setInt(this.name, this.value);
+                effect.setInt(this.name, value);
                 break;
             case NodeMaterialBlockConnectionPointTypes.Color3:
-                effect.setColor3(this.name, this.value);
+                effect.setColor3(this.name, value);
                 break;
             case NodeMaterialBlockConnectionPointTypes.Color4:
-                effect.setDirectColor4(this.name, this.value);
+                effect.setDirectColor4(this.name, value);
                 break;
             case NodeMaterialBlockConnectionPointTypes.Vector2:
-                effect.setVector2(this.name, this.value);
+                effect.setVector2(this.name, value);
                 break;
             case NodeMaterialBlockConnectionPointTypes.Vector3:
-                effect.setVector3(this.name, this.value);
+                effect.setVector3(this.name, value);
                 break;
             case NodeMaterialBlockConnectionPointTypes.Vector4:
-                effect.setVector4(this.name, this.value);
+                effect.setVector4(this.name, value);
                 break;
             case NodeMaterialBlockConnectionPointTypes.Matrix:
-                effect.setMatrix(this.name, this.value);
+                effect.setMatrix(this.name, value);
                 break;
             case NodeMaterialBlockConnectionPointTypes.Texture:
-                effect.setTexture(this.name, this.value);
+                effect.setTexture(this.name, value);
                 break;
         }
     }

+ 15 - 9
src/Materials/Node/nodeMaterialBlockConnectionPointTypes.ts

@@ -3,21 +3,27 @@
  */
 export enum NodeMaterialBlockConnectionPointTypes {
     /** Float */
-    Float,
+    Float = 1,
     /** Int */
-    Int,
+    Int = 2,
     /** Vector2 */
-    Vector2,
+    Vector2 = 4,
     /** Vector3 */
-    Vector3,
+    Vector3 = 8,
     /** Vector4 */
-    Vector4,
+    Vector4 = 16,
     /** Color3 */
-    Color3,
+    Color3 = 32,
     /** Color4 */
-    Color4,
+    Color4 = 64,
     /** Matrix */
-    Matrix,
+    Matrix = 128,
     /** Texture */
-    Texture
+    Texture = 256,
+    /** Vector3 or Color3 */
+    Vector3OrColor3 = Vector3 | Color3,
+    /** Vector4 or Color4 */
+    Vector4OrColor4 = Vector4 | Color4,
+    /** Color3 or Color4 */
+    Color3OrColor4 = Color3 | Color4,
 }

+ 111 - 51
src/Materials/Node/nodeMaterialCompilationState.ts

@@ -1,5 +1,33 @@
 import { NodeMaterialConnectionPoint } from './nodeMaterialBlockConnectionPoint';
 import { NodeMaterialBlockConnectionPointTypes } from './nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialWellKnownValues } from './nodeMaterialWellKnownValues';
+import { NodeMaterialBlockTargets } from './nodeMaterialBlock';
+
+/**
+ * Class used to store shared data between 2 NodeMaterialCompilationState
+ */
+export class NodeMaterialCompilationStateSharedData {
+    /**
+     * Gets the list of emitted varyings
+     */
+    public varyings = new Array<string>();
+
+    /**
+     * Gets the varying declaration string
+     */
+    public varyingDeclaration = "";
+
+    /**
+     * Build Id used to avoid multiple recompilations
+     */
+    public buildId: number;
+
+    /** List of emitted variables */
+    public variableNames: { [key: string]: number } = {};
+
+    /** Should emit comments? */
+    public emitComments: boolean;
+}
 
 /**
  * Class used to store node based material compilation state
@@ -18,16 +46,18 @@ export class NodeMaterialCompilationState {
      */
     public samplers = new Array<string>();
     /**
-     * Gets the list of emitted varyings
+     * Gets the list of emitted functions
      */
-    public varyings = new Array<string>();
+    public functions: { [key: string]: string } = {};
     /**
-     * Gets a boolean indicating if this state was emitted for a fragment shader
+     * Gets the target of the compilation state
      */
-    public isInFragmentMode = false;
+    public target: NodeMaterialBlockTargets;
 
-    /** @hidden */
-    public _variableNames: { [key: string]: number } = {};
+    /**
+     * Shared data between multiple NodeMaterialCompilationState instances
+     */
+    public sharedData: NodeMaterialCompilationStateSharedData;
 
     /** @hidden */
     public _uniformConnectionPoints = new Array<NodeMaterialConnectionPoint>();
@@ -44,13 +74,14 @@ export class NodeMaterialCompilationState {
         needProjectionMatrix: false,
         needViewProjectionMatrix: false,
         needWorldViewMatrix: false,
-        needWorldViewProjectionMatrix: false
+        needWorldViewProjectionMatrix: false,
+        needFogColor: false,
+        needFogParameters: false
     };
 
     private _attributeDeclaration = "";
     private _uniformDeclaration = "";
     private _samplerDeclaration = "";
-    private _varyingDeclaration = "";
     private _varyingTransfer = "";
 
     /**
@@ -60,46 +91,55 @@ export class NodeMaterialCompilationState {
 
     /**
      * Finalize the compilation strings
+     * @param state defines the current compilation state
      */
-    public finalize() {
-        this.compilationString = `\r\n//Entry point\r\nvoid main(void) {\r\n${this.compilationString}`;
+    public finalize(state: NodeMaterialCompilationState) {
+        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}`;
+
+        for (var functionName in this.functions) {
+            let functionCode = this.functions[functionName];
+            this.compilationString = `\r\n${functionCode}\r\n${this.compilationString}`;
+        }
 
-        if (!this.isInFragmentMode && this.varyings.length > 0) {
+        if (!isFragmentMode && this._varyingTransfer) {
             this.compilationString = `${this.compilationString}\r\n${this._varyingTransfer}`;
         }
 
         this.compilationString = `${this.compilationString}\r\n}`;
 
-        if (this._varyingDeclaration) {
-            this.compilationString = `\r\n//Varyings\r\n${this._varyingDeclaration}\r\n\r\n${this.compilationString}`;
+        if (this.sharedData.varyingDeclaration) {
+            this.compilationString = `\r\n${emitComments ? "//Varyings\r\n" : ""}${this.sharedData.varyingDeclaration}\r\n\r\n${this.compilationString}`;
         }
 
         if (this._samplerDeclaration) {
-            this.compilationString = `\r\n//Samplers\r\n${this._samplerDeclaration}\r\n\r\n${this.compilationString}`;
+            this.compilationString = `\r\n${emitComments ? "//Samplers\r\n" : ""}${this._samplerDeclaration}\r\n\r\n${this.compilationString}`;
         }
 
         if (this._uniformDeclaration) {
-            this.compilationString = `\r\n//Uniforms\r\n${this._uniformDeclaration}\r\n\r\n${this.compilationString}`;
+            this.compilationString = `\r\n${emitComments ? "//Uniforms\r\n" : ""}${this._uniformDeclaration}\r\n\r\n${this.compilationString}`;
         }
 
-        if (this._attributeDeclaration && !this.isInFragmentMode) {
-            this.compilationString = `\r\n//Attributes\r\n${this._attributeDeclaration}\r\n\r\n${this.compilationString}`;
+        if (this._attributeDeclaration && !isFragmentMode) {
+            this.compilationString = `\r\n${emitComments ? "//Attributes\r\n" : ""}${this._attributeDeclaration}\r\n\r\n${this.compilationString}`;
         }
     }
 
     /** @hidden */
     public _getFreeVariableName(prefix: string): string {
-        if (this._variableNames[prefix] === undefined) {
-            this._variableNames[prefix] = 0;
+        if (this.sharedData.variableNames[prefix] === undefined) {
+            this.sharedData.variableNames[prefix] = 0;
         } else {
-            this._variableNames[prefix]++;
+            this.sharedData.variableNames[prefix]++;
         }
 
-        return prefix + this._variableNames[prefix];
+        return prefix + this.sharedData.variableNames[prefix];
     }
 
     /** @hidden */
-    private _getGLType(type: NodeMaterialBlockConnectionPointTypes): string {
+    public _getGLType(type: NodeMaterialBlockConnectionPointTypes): string {
         switch (type) {
             case NodeMaterialBlockConnectionPointTypes.Float:
                 return "float";
@@ -109,28 +149,41 @@ export class NodeMaterialCompilationState {
                 return "vec2";
             case NodeMaterialBlockConnectionPointTypes.Color3:
             case NodeMaterialBlockConnectionPointTypes.Vector3:
+            case NodeMaterialBlockConnectionPointTypes.Vector3OrColor3:
                 return "vec3";
             case NodeMaterialBlockConnectionPointTypes.Color4:
             case NodeMaterialBlockConnectionPointTypes.Vector4:
+            case NodeMaterialBlockConnectionPointTypes.Vector4OrColor4:
                 return "vec4";
             case NodeMaterialBlockConnectionPointTypes.Matrix:
                 return "mat4";
             case NodeMaterialBlockConnectionPointTypes.Texture:
                 return "sampler2D";
         }
+
+        return "";
+    }
+
+    /** @hidden */
+    public _emitFunction(name: string, code: string) {
+        if (this.functions[name]) {
+            return;
+        }
+
+        this.functions[name] = code;
     }
 
     /** @hidden */
-    public _emitVaryings(point: NodeMaterialConnectionPoint, force = false) {
+    public _emitVaryings(point: NodeMaterialConnectionPoint, force = false, fromFragment = false) {
         if (point.isVarying || force) {
-            if (this.varyings.indexOf(point.associatedVariableName) !== -1) {
+            if (this.sharedData.varyings.indexOf(point.associatedVariableName) !== -1) {
                 return;
             }
 
-            this.varyings.push(point.associatedVariableName);
-            this._varyingDeclaration += `varying ${this._getGLType(point.type)} ${point.associatedVariableName};\r\n`;
+            this.sharedData.varyings.push(point.associatedVariableName);
+            this.sharedData.varyingDeclaration += `varying ${this._getGLType(point.type)} ${point.associatedVariableName};\r\n`;
 
-            if (!this.isInFragmentMode) {
+            if (this.target === NodeMaterialBlockTargets.Vertex && fromFragment) {
                 this._varyingTransfer += `${point.associatedVariableName} = ${point.name};\r\n`;
             }
         }
@@ -169,39 +222,46 @@ export class NodeMaterialCompilationState {
             this._uniformDeclaration += `uniform ${this._getGLType(point.type)} ${point.name};\r\n`;
 
             // well known
-            switch (point.name) {
-                case "world":
-                    this.hints.needWorldMatrix = true;
-                    break;
-                case "view":
-                    this.hints.needViewMatrix = true;
-                    break;
-                case "projection":
-                    this.hints.needProjectionMatrix = true;
-                    break;
-                case "viewProjection":
-                    this.hints.needViewProjectionMatrix = true;
-                    break;
-                case "worldView":
-                    this.hints.needWorldViewMatrix = true;
-                    break;
-                case "worldViewProjection":
-                    this.hints.needWorldViewProjectionMatrix = true;
-                    break;
-                default:
-                    this._uniformConnectionPoints.push(point);
-                    break;
+            if (point._wellKnownValue !== null) {
+                switch (point._wellKnownValue) {
+                    case NodeMaterialWellKnownValues.World:
+                        this.hints.needWorldMatrix = true;
+                        break;
+                    case NodeMaterialWellKnownValues.View:
+                        this.hints.needViewMatrix = true;
+                        break;
+                    case NodeMaterialWellKnownValues.Projection:
+                        this.hints.needProjectionMatrix = true;
+                        break;
+                    case NodeMaterialWellKnownValues.ViewProjection:
+                        this.hints.needViewProjectionMatrix = true;
+                        break;
+                    case NodeMaterialWellKnownValues.WorldView:
+                        this.hints.needWorldViewMatrix = true;
+                        break;
+                    case NodeMaterialWellKnownValues.WorldViewProjection:
+                        this.hints.needWorldViewProjectionMatrix = true;
+                        break;
+                    case NodeMaterialWellKnownValues.FogColor:
+                        this.hints.needFogColor = true;
+                        break;
+                    case NodeMaterialWellKnownValues.FogParameters:
+                        this.hints.needFogParameters = true;
+                        break;
+                }
+            } else {
+                this._uniformConnectionPoints.push(point);
             }
 
             return;
         }
 
         if (point.isAttribute) {
-            if (this.isInFragmentMode) {
+            if (this.target === NodeMaterialBlockTargets.Fragment) { // Attribute for fragment need to be carried over by varyings
                 this._vertexState._emitUniformOrAttributes(point);
                 point.associatedVariableName = this._getFreeVariableName(point.name);
                 this._emitVaryings(point, true);
-                this._vertexState._emitVaryings(point, true);
+                this._vertexState._emitVaryings(point, true, true);
                 return;
             }
 

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

@@ -0,0 +1,21 @@
+/**
+ * Enum used to define well known values e.g. values automatically provided by the system
+ */
+export enum NodeMaterialWellKnownValues {
+    /** World */
+    World,
+    /** View */
+    View,
+    /** Projection */
+    Projection,
+    /** ViewProjection */
+    ViewProjection,
+    /** WorldView */
+    WorldView,
+    /** WorldViewProjection */
+    WorldViewProjection,
+    /** Fog color */
+    FogColor,
+    /** Fog parameters */
+    FogParameters
+}