Prechádzať zdrojové kódy

Merge pull request #861 from Cubees/master

Fur  Added
Temechon 9 rokov pred
rodič
commit
94c8ffd356
27 zmenil súbory, kde vykonal 2402 pridanie a 114 odobranie
  1. 8 0
      materialsLibrary/config.json
  2. 2 2
      materialsLibrary/dist/babylon.fireMaterial.js
  3. 1 1
      materialsLibrary/dist/babylon.fireMaterial.min.js
  4. 499 0
      materialsLibrary/dist/babylon.furMaterial.js
  5. 1 0
      materialsLibrary/dist/babylon.furMaterial.min.js
  6. 1 1
      materialsLibrary/dist/babylon.lavaMaterial.js
  7. 1 1
      materialsLibrary/dist/babylon.lavaMaterial.min.js
  8. 2 2
      materialsLibrary/dist/babylon.normalMaterial.js
  9. 1 1
      materialsLibrary/dist/babylon.normalMaterial.min.js
  10. 4 4
      materialsLibrary/dist/babylon.pbrMaterial.js
  11. 3 3
      materialsLibrary/dist/babylon.pbrMaterial.min.js
  12. 2 2
      materialsLibrary/dist/babylon.simpleMaterial.js
  13. 1 1
      materialsLibrary/dist/babylon.simpleMaterial.min.js
  14. 3 3
      materialsLibrary/dist/babylon.terrainMaterial.js
  15. 1 1
      materialsLibrary/dist/babylon.terrainMaterial.min.js
  16. 1 1
      materialsLibrary/dist/babylon.waterMaterial.js
  17. 1 1
      materialsLibrary/dist/babylon.waterMaterial.min.js
  18. 30 0
      materialsLibrary/dist/dts/babylon.furMaterial.d.ts
  19. 601 0
      materialsLibrary/materials/fur/babylon.furMaterial.ts
  20. 565 0
      materialsLibrary/materials/fur/fur.fragment.fx
  21. 202 0
      materialsLibrary/materials/fur/fur.vertex.fx
  22. 236 0
      materialsLibrary/materials/fur/readme.md
  23. 54 0
      materialsLibrary/test/add/addfur.js
  24. 173 88
      materialsLibrary/test/babylon.max.js
  25. 9 2
      materialsLibrary/test/index.html
  26. BIN
      materialsLibrary/test/textures/leopard_fur.JPG
  27. BIN
      materialsLibrary/test/textures/speckles.jpg

+ 8 - 0
materialsLibrary/config.json

@@ -51,6 +51,14 @@
       "output": "babylon.fireMaterial.js"
     },
     {
+      "file": "materials/fur/babylon.furMaterial.ts",
+      "shaderFiles": [
+        "materials/fur/fur.vertex.fx",
+        "materials/fur/fur.fragment.fx"
+      ],
+      "output": "babylon.furMaterial.js"
+    },
+    {
       "file": "materials/terrain/babylon.terrainMaterial.ts",
       "shaderFiles": [
         "materials/terrain/terrain.vertex.fx",

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 2 - 2
materialsLibrary/dist/babylon.fireMaterial.js


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 1 - 1
materialsLibrary/dist/babylon.fireMaterial.min.js


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 499 - 0
materialsLibrary/dist/babylon.furMaterial.js


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 1 - 0
materialsLibrary/dist/babylon.furMaterial.min.js


+ 1 - 1
materialsLibrary/dist/babylon.lavaMaterial.js

@@ -455,7 +455,7 @@ var BABYLON;
         };
         LavaMaterial.prototype.serialize = function () {
             var serializationObject = _super.prototype.serialize.call(this);
-            serializationObject.customType = "lava";
+            serializationObject.customType = "BABYLON.LavaMaterial";
             serializationObject.diffuseColor = this.diffuseColor.asArray();
             serializationObject.fogColor = this.fogColor.asArray();
             serializationObject.speed = this.speed;

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 1 - 1
materialsLibrary/dist/babylon.lavaMaterial.min.js


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 2 - 2
materialsLibrary/dist/babylon.normalMaterial.js


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 1 - 1
materialsLibrary/dist/babylon.normalMaterial.min.js


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 4 - 4
materialsLibrary/dist/babylon.pbrMaterial.js


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 3 - 3
materialsLibrary/dist/babylon.pbrMaterial.min.js


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 2 - 2
materialsLibrary/dist/babylon.simpleMaterial.js


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 1 - 1
materialsLibrary/dist/babylon.simpleMaterial.min.js


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 3 - 3
materialsLibrary/dist/babylon.terrainMaterial.js


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 1 - 1
materialsLibrary/dist/babylon.terrainMaterial.min.js


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 1 - 1
materialsLibrary/dist/babylon.waterMaterial.js


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 1 - 1
materialsLibrary/dist/babylon.waterMaterial.min.js


+ 30 - 0
materialsLibrary/dist/dts/babylon.furMaterial.d.ts

@@ -0,0 +1,30 @@
+/// <reference path="../../../dist/preview release/babylon.d.ts" />
+declare module BABYLON {
+    class FurMaterial extends Material {
+        diffuseTexture: BaseTexture;
+        heightTexture: BaseTexture;
+        diffuseColor: Color3;
+        furLength: number;
+        furAngle: number;
+        furColor: Color3;
+        disableLighting: boolean;
+        private _worldViewProjectionMatrix;
+        private _scaledDiffuse;
+        private _renderId;
+        private _defines;
+        private _cachedDefines;
+        constructor(name: string, scene: Scene);
+        needAlphaBlending(): boolean;
+        needAlphaTesting(): boolean;
+        getAlphaTestTexture(): BaseTexture;
+        private _checkCache(scene, mesh?, useInstances?);
+        isReady(mesh?: AbstractMesh, useInstances?: boolean): boolean;
+        bindOnlyWorldMatrix(world: Matrix): void;
+        bind(world: Matrix, mesh?: Mesh): void;
+        getAnimatables(): IAnimatable[];
+        dispose(forceDisposeEffect?: boolean): void;
+        clone(name: string): FurMaterial;
+        serialize(): any;
+        static Parse(source: any, scene: Scene, rootUrl: string): FurMaterial;
+    }
+}

+ 601 - 0
materialsLibrary/materials/fur/babylon.furMaterial.ts

@@ -0,0 +1,601 @@
+/// <reference path="../../../dist/preview release/babylon.d.ts"/>
+
+module BABYLON {
+    var maxSimultaneousLights = 4;
+
+    class FurMaterialDefines extends MaterialDefines {
+        public DIFFUSE = false;
+        public HEIGHTMAP = false;
+        public CLIPPLANE = false;
+        public ALPHATEST = false;
+        public POINTSIZE = false;
+        public FOG = false;
+        public LIGHT0 = false;
+        public LIGHT1 = false;
+        public LIGHT2 = false;
+        public LIGHT3 = false;
+        public SPOTLIGHT0 = false;
+        public SPOTLIGHT1 = false;
+        public SPOTLIGHT2 = false;
+        public SPOTLIGHT3 = false;
+        public HEMILIGHT0 = false;
+        public HEMILIGHT1 = false;
+        public HEMILIGHT2 = false;
+        public HEMILIGHT3 = false;
+        public DIRLIGHT0 = false;
+        public DIRLIGHT1 = false;
+        public DIRLIGHT2 = false;
+        public DIRLIGHT3 = false;
+        public POINTLIGHT0 = false;
+        public POINTLIGHT1 = false;
+        public POINTLIGHT2 = false;
+        public POINTLIGHT3 = false;        
+        public SHADOW0 = false;
+        public SHADOW1 = false;
+        public SHADOW2 = false;
+        public SHADOW3 = false;
+        public SHADOWS = false;
+        public SHADOWVSM0 = false;
+        public SHADOWVSM1 = false;
+        public SHADOWVSM2 = false;
+        public SHADOWVSM3 = false;
+        public SHADOWPCF0 = false;
+        public SHADOWPCF1 = false;
+        public SHADOWPCF2 = false;
+        public SHADOWPCF3 = false;
+        public NORMAL = false;
+        public UV1 = false;
+        public UV2 = false;
+        public VERTEXCOLOR = false;
+        public VERTEXALPHA = false;
+        public BONES = false;
+        public BONES4 = false;
+        public BonesPerMesh = 0;
+        public INSTANCES = false;
+
+        constructor() {
+            super();
+            this._keys = Object.keys(this);
+        }
+    }
+
+    export class FurMaterial extends Material {
+        public diffuseTexture: BaseTexture;
+        public heightTexture: BaseTexture;
+        public diffuseColor = new Color3(1, 1, 1);
+        public furLength: number = 1;
+        public furAngle: number = 0;
+        public furColor = new Color3(0.44,0.21,0.02);
+        public disableLighting = false;
+
+        private _worldViewProjectionMatrix = Matrix.Zero();
+        private _scaledDiffuse = new Color3(1.,1.,1.);
+        private _renderId: number;
+
+        private _defines = new FurMaterialDefines();
+        private _cachedDefines = new FurMaterialDefines();
+
+        constructor(name: string, scene: Scene) {
+            super(name, scene);
+
+            this._cachedDefines.BonesPerMesh = -1;
+        }
+
+        public needAlphaBlending(): boolean {
+            return (this.alpha < 1.0);
+        }
+
+        public needAlphaTesting(): boolean {
+            return false;
+        }
+
+        public getAlphaTestTexture(): BaseTexture {
+            return null;
+        }
+
+        // Methods   
+        private _checkCache(scene: Scene, mesh?: AbstractMesh, useInstances?: boolean): boolean {
+            if (!mesh) {
+                return true;
+            }
+
+            if (this._defines.INSTANCES !== useInstances) {
+                return false;
+            }
+
+            if (mesh._materialDefines && mesh._materialDefines.isEqual(this._defines)) {
+                return true;
+            }
+
+            return false;
+        }
+
+        public isReady(mesh?: AbstractMesh, useInstances?: boolean): boolean {
+            if (this.checkReadyOnlyOnce) {
+                if (this._wasPreviouslyReady) {
+                    return true;
+                }
+            }
+
+            var scene = this.getScene();
+
+            if (!this.checkReadyOnEveryCall) {
+                if (this._renderId === scene.getRenderId()) {
+                    if (this._checkCache(scene, mesh, useInstances)) {
+                        return true;
+                    }
+                }
+            }
+
+            var engine = scene.getEngine();
+            var needNormals = false;
+            var needUVs = false;
+
+            this._defines.reset();
+
+            // Textures
+            if (scene.texturesEnabled) {
+                if (this.diffuseTexture && StandardMaterial.DiffuseTextureEnabled) {
+                    if (!this.diffuseTexture.isReady()) {
+                        return false;
+                    } else {
+                        needUVs = true;
+                        this._defines.DIFFUSE = true;
+                    }
+                } 
+                if (this.heightTexture) {
+                    if (!this.heightTexture.isReady()) {
+                        return false;
+                    } else {
+                        needUVs = true;
+                        this._defines.HEIGHTMAP = true;
+                    }
+                }               
+            }
+
+            // Effect
+            if (scene.clipPlane) {
+                this._defines.CLIPPLANE = true;
+            }
+
+            if (engine.getAlphaTesting()) {
+                this._defines.ALPHATEST = true;
+            }
+
+            // Point size
+            if (this.pointsCloud || scene.forcePointsCloud) {
+                this._defines.POINTSIZE = true;
+            }
+
+            // Fog
+            if (scene.fogEnabled && mesh && mesh.applyFog && scene.fogMode !== Scene.FOGMODE_NONE && this.fogEnabled) {
+                this._defines.FOG = true;
+            }
+
+            var lightIndex = 0;
+            if (scene.lightsEnabled && !this.disableLighting) {
+                for (var index = 0; index < scene.lights.length; index++) {
+                    var light = scene.lights[index];
+
+                    if (!light.isEnabled()) {
+                        continue;
+                    }
+
+                    // Excluded check
+                    if (light._excludedMeshesIds.length > 0) {
+                        for (var excludedIndex = 0; excludedIndex < light._excludedMeshesIds.length; excludedIndex++) {
+                            var excludedMesh = scene.getMeshByID(light._excludedMeshesIds[excludedIndex]);
+
+                            if (excludedMesh) {
+                                light.excludedMeshes.push(excludedMesh);
+                            }
+                        }
+
+                        light._excludedMeshesIds = [];
+                    }
+
+                    // Included check
+                    if (light._includedOnlyMeshesIds.length > 0) {
+                        for (var includedOnlyIndex = 0; includedOnlyIndex < light._includedOnlyMeshesIds.length; includedOnlyIndex++) {
+                            var includedOnlyMesh = scene.getMeshByID(light._includedOnlyMeshesIds[includedOnlyIndex]);
+
+                            if (includedOnlyMesh) {
+                                light.includedOnlyMeshes.push(includedOnlyMesh);
+                            }
+                        }
+
+                        light._includedOnlyMeshesIds = [];
+                    }
+
+                    if (!light.canAffectMesh(mesh)) {
+                        continue;
+                    }
+                    needNormals = true;
+                    this._defines["LIGHT" + lightIndex] = true;
+
+                    var type;
+                    if (light instanceof SpotLight) {
+                        type = "SPOTLIGHT" + lightIndex;
+                    } else if (light instanceof HemisphericLight) {
+                        type = "HEMILIGHT" + lightIndex;
+                    } else if (light instanceof PointLight) {
+                        type = "POINTLIGHT" + lightIndex;
+                    } else {
+                        type = "DIRLIGHT" + lightIndex;
+                    }
+
+                    this._defines[type] = true;
+
+                    // Shadows
+                    if (scene.shadowsEnabled) {
+                        var shadowGenerator = light.getShadowGenerator();
+                        if (mesh && mesh.receiveShadows && shadowGenerator) {
+                            this._defines["SHADOW" + lightIndex] = true;
+
+                            this._defines.SHADOWS = true;
+
+                            if (shadowGenerator.useVarianceShadowMap || shadowGenerator.useBlurVarianceShadowMap) {
+                                this._defines["SHADOWVSM" + lightIndex] = true;
+                            }
+
+                            if (shadowGenerator.usePoissonSampling) {
+                                this._defines["SHADOWPCF" + lightIndex] = true;
+                            }
+                        }
+                    }
+
+                    lightIndex++;
+                    if (lightIndex === maxSimultaneousLights)
+                        break;
+                }
+            }
+
+            // Attribs
+            if (mesh) {
+                if (needNormals && mesh.isVerticesDataPresent(VertexBuffer.NormalKind)) {
+                    this._defines.NORMAL = true;
+                }
+                if (needUVs) {
+                    if (mesh.isVerticesDataPresent(VertexBuffer.UVKind)) {
+                        this._defines.UV1 = true;
+                    }
+                    if (mesh.isVerticesDataPresent(VertexBuffer.UV2Kind)) {
+                        this._defines.UV2 = true;
+                    }
+                }
+                if (mesh.useVertexColors && mesh.isVerticesDataPresent(VertexBuffer.ColorKind)) {
+                    this._defines.VERTEXCOLOR = true;
+
+                    if (mesh.hasVertexAlpha) {
+                        this._defines.VERTEXALPHA = true;
+                    }
+                }
+                if (mesh.useBones && mesh.computeBonesUsingShaders) {
+                    this._defines.BONES = true;
+                    this._defines.BonesPerMesh = (mesh.skeleton.bones.length + 1);
+                    this._defines.BONES4 = true;
+                }
+
+                // Instances
+                if (useInstances) {
+                    this._defines.INSTANCES = true;
+                }
+            }
+
+            // Get correct effect      
+            if (!this._defines.isEqual(this._cachedDefines)) {
+                this._defines.cloneTo(this._cachedDefines);
+
+                scene.resetCachedMaterial();
+
+                // Fallbacks
+                var fallbacks = new EffectFallbacks();             
+                if (this._defines.FOG) {
+                    fallbacks.addFallback(1, "FOG");
+                }
+
+                for (lightIndex = 0; lightIndex < maxSimultaneousLights; lightIndex++) {
+                    if (!this._defines["LIGHT" + lightIndex]) {
+                        continue;
+                    }
+
+                    if (lightIndex > 0) {
+                        fallbacks.addFallback(lightIndex, "LIGHT" + lightIndex);
+                    }
+
+                    if (this._defines["SHADOW" + lightIndex]) {
+                        fallbacks.addFallback(0, "SHADOW" + lightIndex);
+                    }
+
+                    if (this._defines["SHADOWPCF" + lightIndex]) {
+                        fallbacks.addFallback(0, "SHADOWPCF" + lightIndex);
+                    }
+
+                    if (this._defines["SHADOWVSM" + lightIndex]) {
+                        fallbacks.addFallback(0, "SHADOWVSM" + lightIndex);
+                    }
+                }
+             
+                if (this._defines.BONES4) {
+                    fallbacks.addFallback(0, "BONES4");
+                }
+
+                //Attributes
+                var attribs = [VertexBuffer.PositionKind];
+
+                if (this._defines.NORMAL) {
+                    attribs.push(VertexBuffer.NormalKind);
+                }
+
+                if (this._defines.UV1) {
+                    attribs.push(VertexBuffer.UVKind);
+                }
+
+                if (this._defines.UV2) {
+                    attribs.push(VertexBuffer.UV2Kind);
+                }
+
+                if (this._defines.VERTEXCOLOR) {
+                    attribs.push(VertexBuffer.ColorKind);
+                }
+
+                if (this._defines.BONES) {
+                    attribs.push(VertexBuffer.MatricesIndicesKind);
+                    attribs.push(VertexBuffer.MatricesWeightsKind);
+                }
+
+                if (this._defines.INSTANCES) {
+                    attribs.push("world0");
+                    attribs.push("world1");
+                    attribs.push("world2");
+                    attribs.push("world3");
+                }
+
+                // Legacy browser patch
+                var shaderName = "fur";
+                var join = this._defines.toString();
+                this._effect = scene.getEngine().createEffect(shaderName,
+                    attribs,
+                    ["world", "view", "viewProjection", "vEyePosition", "vLightsType", "vDiffuseColor",
+                        "vLightData0", "vLightDiffuse0", "vLightSpecular0", "vLightDirection0", "vLightGround0", "lightMatrix0",
+                        "vLightData1", "vLightDiffuse1", "vLightSpecular1", "vLightDirection1", "vLightGround1", "lightMatrix1",
+                        "vLightData2", "vLightDiffuse2", "vLightSpecular2", "vLightDirection2", "vLightGround2", "lightMatrix2",
+                        "vLightData3", "vLightDiffuse3", "vLightSpecular3", "vLightDirection3", "vLightGround3", "lightMatrix3",
+                        "vFogInfos", "vFogColor", "pointSize",
+                        "vDiffuseInfos", 
+                        "mBones",
+                        "vClipPlane", "diffuseMatrix",
+                        "shadowsInfo0", "shadowsInfo1", "shadowsInfo2", "shadowsInfo3",
+                        "furLength", "furAngle", "furColor"
+                    ],
+                    ["diffuseSampler",
+                        "shadowSampler0", "shadowSampler1", "shadowSampler2", "shadowSampler3",
+                        "heightTexture"
+                    ],
+                    join, fallbacks, this.onCompiled, this.onError);
+            }
+            if (!this._effect.isReady()) {
+                return false;
+            }
+
+            this._renderId = scene.getRenderId();
+            this._wasPreviouslyReady = true;
+
+            if (mesh) {
+                if (!mesh._materialDefines) {
+                    mesh._materialDefines = new FurMaterialDefines();
+                }
+
+                this._defines.cloneTo(mesh._materialDefines);
+            }
+
+            return true;
+        }
+
+        public bindOnlyWorldMatrix(world: Matrix): void {
+            this._effect.setMatrix("world", world);
+        }
+
+        public bind(world: Matrix, mesh?: Mesh): void {
+            var scene = this.getScene();
+
+            // Matrices        
+            this.bindOnlyWorldMatrix(world);
+            this._effect.setMatrix("viewProjection", scene.getTransformMatrix());
+
+            // Bones
+            if (mesh && mesh.useBones && mesh.computeBonesUsingShaders) {
+                this._effect.setMatrices("mBones", mesh.skeleton.getTransformMatrices());
+            }
+
+            if (scene.getCachedMaterial() !== this) {
+                // Textures        
+                if (this.diffuseTexture && StandardMaterial.DiffuseTextureEnabled) {
+                    this._effect.setTexture("diffuseSampler", this.diffuseTexture);
+
+                    this._effect.setFloat2("vDiffuseInfos", this.diffuseTexture.coordinatesIndex, this.diffuseTexture.level);
+                    this._effect.setMatrix("diffuseMatrix", this.diffuseTexture.getTextureMatrix());
+                }
+                
+                if (this.heightTexture) {
+                    this._effect.setTexture("heightTexture", this.heightTexture);
+                }
+                
+                // Clip plane
+                if (scene.clipPlane) {
+                    var clipPlane = scene.clipPlane;
+                    this._effect.setFloat4("vClipPlane", clipPlane.normal.x, clipPlane.normal.y, clipPlane.normal.z, clipPlane.d);
+                }
+
+                // Point size
+                if (this.pointsCloud) {
+                    this._effect.setFloat("pointSize", this.pointSize);
+                }
+
+                this._effect.setVector3("vEyePosition", scene._mirroredCameraPosition ? scene._mirroredCameraPosition : scene.activeCamera.position);                
+            }
+
+            this._effect.setColor4("vDiffuseColor", this._scaledDiffuse, this.alpha * mesh.visibility);
+
+            if (scene.lightsEnabled && !this.disableLighting) {
+                var lightIndex = 0;
+                for (var index = 0; index < scene.lights.length; index++) {
+                    var light = scene.lights[index];
+
+                    if (!light.isEnabled()) {
+                        continue;
+                    }
+
+                    if (!light.canAffectMesh(mesh)) {
+                        continue;
+                    }
+
+                    if (light instanceof PointLight) {
+                        // Point Light
+                        light.transferToEffect(this._effect, "vLightData" + lightIndex);
+                    } else if (light instanceof DirectionalLight) {
+                        // Directional Light
+                        light.transferToEffect(this._effect, "vLightData" + lightIndex);
+                    } else if (light instanceof SpotLight) {
+                        // Spot Light
+                        light.transferToEffect(this._effect, "vLightData" + lightIndex, "vLightDirection" + lightIndex);
+                    } else if (light instanceof HemisphericLight) {
+                        // Hemispheric Light
+                        light.transferToEffect(this._effect, "vLightData" + lightIndex, "vLightGround" + lightIndex);
+                    }
+
+                    light.diffuse.scaleToRef(light.intensity, this._scaledDiffuse);
+                    this._effect.setColor4("vLightDiffuse" + lightIndex, this._scaledDiffuse, light.range);
+
+                    // Shadows
+                    if (scene.shadowsEnabled) {
+                        var shadowGenerator = light.getShadowGenerator();
+                        if (mesh.receiveShadows && shadowGenerator) {
+                            this._effect.setMatrix("lightMatrix" + lightIndex, shadowGenerator.getTransformMatrix());
+                            this._effect.setTexture("shadowSampler" + lightIndex, shadowGenerator.getShadowMapForRendering());
+                            this._effect.setFloat3("shadowsInfo" + lightIndex, shadowGenerator.getDarkness(), shadowGenerator.getShadowMap().getSize().width, shadowGenerator.bias);
+                        }
+                    }
+
+                    lightIndex++;
+
+                    if (lightIndex === maxSimultaneousLights)
+                        break;
+                }
+            }
+
+            // View
+            if (scene.fogEnabled && mesh.applyFog && scene.fogMode !== Scene.FOGMODE_NONE) {
+                this._effect.setMatrix("view", scene.getViewMatrix());
+            }
+
+            // Fog
+            if (scene.fogEnabled && mesh.applyFog && scene.fogMode !== Scene.FOGMODE_NONE) {
+                this._effect.setFloat4("vFogInfos", scene.fogMode, scene.fogStart, scene.fogEnd, scene.fogDensity);
+                this._effect.setColor3("vFogColor", scene.fogColor);
+            }
+            
+            this._effect.setFloat("furLength", this.furLength);
+            this._effect.setFloat("furAngle", this.furAngle);
+            this._effect.setColor4("furColor", this.furColor, 1.0);
+
+ 
+            super.bind(world, mesh);
+        }
+
+        public getAnimatables(): IAnimatable[] {
+            var results = [];
+
+            if (this.diffuseTexture && this.diffuseTexture.animations && this.diffuseTexture.animations.length > 0) {
+                results.push(this.diffuseTexture);
+            }
+            
+            if (this.heightTexture && this.heightTexture.animations && this.heightTexture.animations.length > 0) {
+                results.push(this.heightTexture);
+            }
+
+            return results;
+        }
+
+        public dispose(forceDisposeEffect?: boolean): void {
+            if (this.diffuseTexture) {
+                this.diffuseTexture.dispose();
+            }
+
+            super.dispose(forceDisposeEffect);
+        }
+
+        public clone(name: string): FurMaterial {
+            var newMaterial = new FurMaterial(name, this.getScene());
+
+            // Base material
+            this.copyTo(newMaterial);
+
+            // Fur material
+            if (this.diffuseTexture && this.diffuseTexture.clone) {
+                newMaterial.diffuseTexture = this.diffuseTexture.clone();
+            }
+            if (this.heightTexture && this.heightTexture.clone) {
+                newMaterial.heightTexture = this.heightTexture.clone();
+            }
+            if (this.diffuseColor && this.diffuseColor.clone) {
+                newMaterial.diffuseColor = this.diffuseColor.clone();
+            }
+            
+            return newMaterial;
+        }
+        
+        public serialize(): any {		
+            var serializationObject = super.serialize();
+            serializationObject.customType      = "BABYLON.FurMaterial";
+            serializationObject.diffuseColor    = this.diffuseColor.asArray();
+            serializationObject.disableLighting = this.disableLighting;
+            serializationObject.furLength = this.furLength;
+            serializationObject.furAngle = this.furAngle;
+            serializationObject.furColor = this.furColor.asArray();
+            
+            if (this.diffuseTexture) {
+                serializationObject.diffuseTexture = this.diffuseTexture.serialize();
+            }
+            
+            if (this.heightTexture) {
+                serializationObject.heightTexture = this.heightTexture.serialize();
+            }
+
+            return serializationObject;
+        }
+
+        public static Parse(source: any, scene: Scene, rootUrl: string): FurMaterial {
+            var material = new FurMaterial(source.name, scene);
+
+            material.diffuseColor       = Color3.FromArray(source.diffuseColor);
+            material.furLength          = source.furLength;
+            material.furAngle           = source.furAngle;
+            material.furColor           = Color3.FromArray(source.furColor);
+            material.disableLighting    = source.disableLighting;
+
+            material.alpha          = source.alpha;
+
+            material.id             = source.id;
+
+            Tags.AddTagsTo(material, source.tags);
+            material.backFaceCulling = source.backFaceCulling;
+            material.wireframe = source.wireframe;
+
+            if (source.diffuseTexture) {
+                material.diffuseTexture = Texture.Parse(source.diffuseTexture, scene, rootUrl);
+            }
+            
+            if (source.heightTexture) {
+                material.heightTexture = Texture.Parse(source.heightTexture, scene, rootUrl);
+            }
+
+            if (source.checkReadyOnlyOnce) {
+                material.checkReadyOnlyOnce = source.checkReadyOnlyOnce;
+            }
+
+            return material;
+        }
+    }
+} 
+

+ 565 - 0
materialsLibrary/materials/fur/fur.fragment.fx

@@ -0,0 +1,565 @@
+precision highp float;
+
+// Constants
+uniform vec3 vEyePosition;
+uniform vec4 vDiffuseColor;
+
+// Input
+uniform vec4 furColor;
+varying vec3 vPositionW;
+varying float vfur_length;
+
+#ifdef NORMAL
+varying vec3 vNormalW;
+#endif
+
+#ifdef VERTEXCOLOR
+varying vec4 vColor;
+#endif
+
+// Lights
+#ifdef LIGHT0
+uniform vec4 vLightData0;
+uniform vec4 vLightDiffuse0;
+#ifdef SHADOW0
+#if defined(SPOTLIGHT0) || defined(DIRLIGHT0)
+varying vec4 vPositionFromLight0;
+uniform sampler2D shadowSampler0;
+#else
+uniform samplerCube shadowSampler0;
+#endif
+uniform vec3 shadowsInfo0;
+#endif
+#ifdef SPOTLIGHT0
+uniform vec4 vLightDirection0;
+#endif
+#ifdef HEMILIGHT0
+uniform vec3 vLightGround0;
+#endif
+#endif
+
+#ifdef LIGHT1
+uniform vec4 vLightData1;
+uniform vec4 vLightDiffuse1;
+#ifdef SHADOW1
+#if defined(SPOTLIGHT1) || defined(DIRLIGHT1)
+varying vec4 vPositionFromLight1;
+uniform sampler2D shadowSampler1;
+#else
+uniform samplerCube shadowSampler1;
+#endif
+uniform vec3 shadowsInfo1;
+#endif
+#ifdef SPOTLIGHT1
+uniform vec4 vLightDirection1;
+#endif
+#ifdef HEMILIGHT1
+uniform vec3 vLightGround1;
+#endif
+#endif
+
+#ifdef LIGHT2
+uniform vec4 vLightData2;
+uniform vec4 vLightDiffuse2;
+#ifdef SHADOW2
+#if defined(SPOTLIGHT2) || defined(DIRLIGHT2)
+varying vec4 vPositionFromLight2;
+uniform sampler2D shadowSampler2;
+#else
+uniform samplerCube shadowSampler2;
+#endif
+uniform vec3 shadowsInfo2;
+#endif
+#ifdef SPOTLIGHT2
+uniform vec4 vLightDirection2;
+#endif
+#ifdef HEMILIGHT2
+uniform vec3 vLightGround2;
+#endif
+#endif
+
+#ifdef LIGHT3
+uniform vec4 vLightData3;
+uniform vec4 vLightDiffuse3;
+#ifdef SHADOW3
+#if defined(SPOTLIGHT3) || defined(DIRLIGHT3)
+varying vec4 vPositionFromLight3;
+uniform sampler2D shadowSampler3;
+#else
+uniform samplerCube shadowSampler3;
+#endif
+uniform vec3 shadowsInfo3;
+#endif
+#ifdef SPOTLIGHT3
+uniform vec4 vLightDirection3;
+#endif
+#ifdef HEMILIGHT3
+uniform vec3 vLightGround3;
+#endif
+#endif
+
+// Samplers
+#ifdef DIFFUSE
+varying vec2 vDiffuseUV;
+uniform sampler2D diffuseSampler;
+uniform vec2 vDiffuseInfos;
+#endif
+
+// Shadows
+#ifdef SHADOWS
+
+float unpack(vec4 color)
+{
+	const vec4 bit_shift = vec4(1.0 / (255.0 * 255.0 * 255.0), 1.0 / (255.0 * 255.0), 1.0 / 255.0, 1.0);
+	return dot(color, bit_shift);
+}
+
+#if defined(POINTLIGHT0) || defined(POINTLIGHT1) || defined(POINTLIGHT2) || defined(POINTLIGHT3)
+float computeShadowCube(vec3 lightPosition, samplerCube shadowSampler, float darkness, float bias)
+{
+	vec3 directionToLight = vPositionW - lightPosition;
+	float depth = length(directionToLight);
+
+	depth = clamp(depth, 0., 1.);
+
+	directionToLight.y = 1.0 - directionToLight.y;
+
+	float shadow = unpack(textureCube(shadowSampler, directionToLight)) + bias;
+
+	if (depth > shadow)
+	{
+		return darkness;
+	}
+	return 1.0;
+}
+
+float computeShadowWithPCFCube(vec3 lightPosition, samplerCube shadowSampler, float bias, float darkness)
+{
+	vec3 directionToLight = vPositionW - lightPosition;
+	float depth = length(directionToLight);
+
+	depth = clamp(depth, 0., 1.);
+
+	directionToLight.y = 1.0 - directionToLight.y;
+
+	float visibility = 1.;
+
+	vec3 poissonDisk[4];
+	poissonDisk[0] = vec3(-0.094201624, 0.04, -0.039906216);
+	poissonDisk[1] = vec3(0.094558609, -0.04, -0.076890725);
+	poissonDisk[2] = vec3(-0.094184101, 0.01, -0.092938870);
+	poissonDisk[3] = vec3(0.034495938, -0.01, 0.029387760);
+
+	// Poisson Sampling
+	float biasedDepth = depth - bias;
+
+	if (unpack(textureCube(shadowSampler, directionToLight + poissonDisk[0])) < biasedDepth) visibility -= 0.25;
+	if (unpack(textureCube(shadowSampler, directionToLight + poissonDisk[1])) < biasedDepth) visibility -= 0.25;
+	if (unpack(textureCube(shadowSampler, directionToLight + poissonDisk[2])) < biasedDepth) visibility -= 0.25;
+	if (unpack(textureCube(shadowSampler, directionToLight + poissonDisk[3])) < biasedDepth) visibility -= 0.25;
+
+	return  min(1.0, visibility + darkness);
+}
+#endif
+
+#if defined(SPOTLIGHT0) || defined(SPOTLIGHT1) || defined(SPOTLIGHT2) || defined(SPOTLIGHT3) ||  defined(DIRLIGHT0) || defined(DIRLIGHT1) || defined(DIRLIGHT2) || defined(DIRLIGHT3)
+float computeShadow(vec4 vPositionFromLight, sampler2D shadowSampler, float darkness, float bias)
+{
+	vec3 depth = vPositionFromLight.xyz / vPositionFromLight.w;
+	depth = 0.5 * depth + vec3(0.5);
+	vec2 uv = depth.xy;
+
+	if (uv.x < 0. || uv.x > 1.0 || uv.y < 0. || uv.y > 1.0)
+	{
+		return 1.0;
+	}
+
+	float shadow = unpack(texture2D(shadowSampler, uv)) + bias;
+
+	if (depth.z > shadow)
+	{
+		return darkness;
+	}
+	return 1.;
+}
+
+float computeShadowWithPCF(vec4 vPositionFromLight, sampler2D shadowSampler, float mapSize, float bias, float darkness)
+{
+	vec3 depth = vPositionFromLight.xyz / vPositionFromLight.w;
+	depth = 0.5 * depth + vec3(0.5);
+	vec2 uv = depth.xy;
+
+	if (uv.x < 0. || uv.x > 1.0 || uv.y < 0. || uv.y > 1.0)
+	{
+		return 1.0;
+	}
+
+	float visibility = 1.;
+
+	vec2 poissonDisk[4];
+	poissonDisk[0] = vec2(-0.94201624, -0.39906216);
+	poissonDisk[1] = vec2(0.94558609, -0.76890725);
+	poissonDisk[2] = vec2(-0.094184101, -0.92938870);
+	poissonDisk[3] = vec2(0.34495938, 0.29387760);
+
+	// Poisson Sampling
+	float biasedDepth = depth.z - bias;
+
+	if (unpack(texture2D(shadowSampler, uv + poissonDisk[0] / mapSize)) < biasedDepth) visibility -= 0.25;
+	if (unpack(texture2D(shadowSampler, uv + poissonDisk[1] / mapSize)) < biasedDepth) visibility -= 0.25;
+	if (unpack(texture2D(shadowSampler, uv + poissonDisk[2] / mapSize)) < biasedDepth) visibility -= 0.25;
+	if (unpack(texture2D(shadowSampler, uv + poissonDisk[3] / mapSize)) < biasedDepth) visibility -= 0.25;
+
+	return  min(1.0, visibility + darkness);
+}
+
+// Thanks to http://devmaster.net/
+float unpackHalf(vec2 color)
+{
+	return color.x + (color.y / 255.0);
+}
+
+float linstep(float low, float high, float v) {
+	return clamp((v - low) / (high - low), 0.0, 1.0);
+}
+
+float ChebychevInequality(vec2 moments, float compare, float bias)
+{
+	float p = smoothstep(compare - bias, compare, moments.x);
+	float variance = max(moments.y - moments.x * moments.x, 0.02);
+	float d = compare - moments.x;
+	float p_max = linstep(0.2, 1.0, variance / (variance + d * d));
+
+	return clamp(max(p, p_max), 0.0, 1.0);
+}
+
+float computeShadowWithVSM(vec4 vPositionFromLight, sampler2D shadowSampler, float bias, float darkness)
+{
+	vec3 depth = vPositionFromLight.xyz / vPositionFromLight.w;
+	depth = 0.5 * depth + vec3(0.5);
+	vec2 uv = depth.xy;
+
+	if (uv.x < 0. || uv.x > 1.0 || uv.y < 0. || uv.y > 1.0 || depth.z >= 1.0)
+	{
+		return 1.0;
+	}
+
+	vec4 texel = texture2D(shadowSampler, uv);
+
+	vec2 moments = vec2(unpackHalf(texel.xy), unpackHalf(texel.zw));
+	return min(1.0, 1.0 - ChebychevInequality(moments, depth.z, bias) + darkness);
+}
+#endif
+#endif
+
+
+#ifdef CLIPPLANE
+varying float fClipDistance;
+#endif
+
+// Fog
+#ifdef FOG
+
+#define FOGMODE_NONE    0.
+#define FOGMODE_EXP     1.
+#define FOGMODE_EXP2    2.
+#define FOGMODE_LINEAR  3.
+#define E 2.71828
+
+uniform vec4 vFogInfos;
+uniform vec3 vFogColor;
+varying float fFogDistance;
+
+float CalcFogFactor()
+{
+	float fogCoeff = 1.0;
+	float fogStart = vFogInfos.y;
+	float fogEnd = vFogInfos.z;
+	float fogDensity = vFogInfos.w;
+
+	if (FOGMODE_LINEAR == vFogInfos.x)
+	{
+		fogCoeff = (fogEnd - fFogDistance) / (fogEnd - fogStart);
+	}
+	else if (FOGMODE_EXP == vFogInfos.x)
+	{
+		fogCoeff = 1.0 / pow(E, fFogDistance * fogDensity);
+	}
+	else if (FOGMODE_EXP2 == vFogInfos.x)
+	{
+		fogCoeff = 1.0 / pow(E, fFogDistance * fFogDistance * fogDensity * fogDensity);
+	}
+
+	return clamp(fogCoeff, 0.0, 1.0);
+}
+#endif
+
+// Light Computing
+struct lightingInfo
+{
+	vec3 diffuse;
+};
+
+lightingInfo computeLighting(vec3 viewDirectionW, vec3 vNormal, vec4 lightData, vec3 diffuseColor, float range) {
+	lightingInfo result;
+
+	vec3 lightVectorW;
+	float attenuation = 1.0;
+	if (lightData.w == 0.)
+	{
+		vec3 direction = lightData.xyz - vPositionW;
+
+		attenuation = max(0., 1.0 - length(direction) / range);
+		lightVectorW = normalize(direction);
+	}
+	else
+	{
+		lightVectorW = normalize(-lightData.xyz);
+	}
+
+	// diffuse
+	float ndl = max(0., dot(vNormal, lightVectorW));
+	result.diffuse = ndl * diffuseColor * attenuation;
+
+	return result;
+}
+
+lightingInfo computeSpotLighting(vec3 viewDirectionW, vec3 vNormal, vec4 lightData, vec4 lightDirection, vec3 diffuseColor, float range) {
+	lightingInfo result;
+
+	vec3 direction = lightData.xyz - vPositionW;
+	vec3 lightVectorW = normalize(direction);
+	float attenuation = max(0., 1.0 - length(direction) / range);
+
+	// diffuse
+	float cosAngle = max(0., dot(-lightDirection.xyz, lightVectorW));
+	float spotAtten = 0.0;
+
+	if (cosAngle >= lightDirection.w)
+	{
+		cosAngle = max(0., pow(cosAngle, lightData.w));
+		spotAtten = clamp((cosAngle - lightDirection.w) / (1. - cosAngle), 0.0, 1.0);
+
+		// Diffuse
+		float ndl = max(0., dot(vNormal, -lightDirection.xyz));
+		result.diffuse = ndl * spotAtten * diffuseColor * attenuation;
+
+		return result;
+	}
+
+	result.diffuse = vec3(0.);
+
+	return result;
+}
+
+lightingInfo computeHemisphericLighting(vec3 viewDirectionW, vec3 vNormal, vec4 lightData, vec3 diffuseColor, vec3 groundColor) {
+	lightingInfo result;
+
+	// Diffuse
+	float ndl = dot(vNormal, lightData.xyz) * 0.5 + 0.5;
+	result.diffuse = mix(groundColor, diffuseColor, ndl);
+
+	return result;
+}
+
+float Rand(vec3 rv) {
+	float x = dot(rv, vec3(12.9898,78.233, 24.65487));
+	return fract(sin(x) * 43758.5453);
+}
+
+void main(void) {
+	// Clip plane
+#ifdef CLIPPLANE
+	if (fClipDistance > 0.0)
+		discard;
+#endif
+
+	vec3 viewDirectionW = normalize(vEyePosition - vPositionW);
+
+	// Base color
+	vec4 baseColor = furColor;
+	vec3 diffuseColor = vDiffuseColor.rgb;
+
+	// Alpha
+	float alpha = vDiffuseColor.a;
+
+#ifdef DIFFUSE
+	baseColor = texture2D(diffuseSampler, vDiffuseUV);
+
+#ifdef ALPHATEST
+	if (baseColor.a < 0.4)
+		discard;
+#endif
+
+	baseColor.rgb *= vDiffuseInfos.y;
+#endif
+
+#ifdef VERTEXCOLOR
+	baseColor.rgb *= vColor.rgb;
+#endif
+
+	// Bump
+#ifdef NORMAL
+	vec3 normalW = normalize(vNormalW);
+#else
+	vec3 normalW = vec3(1.0, 1.0, 1.0);
+#endif
+
+	// Lighting
+	vec3 diffuseBase = vec3(0., 0., 0.);
+	float shadow = 1.;
+
+#ifdef LIGHT0
+#ifdef SPOTLIGHT0
+	lightingInfo info = computeSpotLighting(viewDirectionW, normalW, vLightData0, vLightDirection0, vLightDiffuse0.rgb, vLightDiffuse0.a);
+#endif
+#ifdef HEMILIGHT0
+	lightingInfo info = computeHemisphericLighting(viewDirectionW, normalW, vLightData0, vLightDiffuse0.rgb, vLightGround0);
+#endif
+#if defined(POINTLIGHT0) || defined(DIRLIGHT0)
+	lightingInfo info = computeLighting(viewDirectionW, normalW, vLightData0, vLightDiffuse0.rgb, vLightDiffuse0.a);
+#endif
+#ifdef SHADOW0
+#ifdef SHADOWVSM0
+	shadow = computeShadowWithVSM(vPositionFromLight0, shadowSampler0, shadowsInfo0.z, shadowsInfo0.x);
+#else
+#ifdef SHADOWPCF0
+	#if defined(POINTLIGHT0)
+	shadow = computeShadowWithPCFCube(vLightData0.xyz, shadowSampler0, shadowsInfo0.z, shadowsInfo0.x);
+	#else
+	shadow = computeShadowWithPCF(vPositionFromLight0, shadowSampler0, shadowsInfo0.y, shadowsInfo0.z, shadowsInfo0.x);
+	#endif
+#else
+	#if defined(POINTLIGHT0)
+	shadow = computeShadowCube(vLightData0.xyz, shadowSampler0, shadowsInfo0.x, shadowsInfo0.z);
+	#else
+	shadow = computeShadow(vPositionFromLight0, shadowSampler0, shadowsInfo0.x, shadowsInfo0.z);
+	#endif
+#endif
+#endif
+#else
+	shadow = 1.;
+#endif
+	diffuseBase += info.diffuse * shadow;
+#endif
+
+#ifdef LIGHT1
+#ifdef SPOTLIGHT1
+	info = computeSpotLighting(viewDirectionW, normalW, vLightData1, vLightDirection1, vLightDiffuse1.rgb, vLightDiffuse1.a);
+#endif
+#ifdef HEMILIGHT1
+	info = computeHemisphericLighting(viewDirectionW, normalW, vLightData1, vLightDiffuse1.rgb, vLightGround1.a);
+#endif
+#if defined(POINTLIGHT1) || defined(DIRLIGHT1)
+	info = computeLighting(viewDirectionW, normalW, vLightData1, vLightDiffuse1.rgb, vLightDiffuse1.a);
+#endif
+#ifdef SHADOW1
+#ifdef SHADOWVSM1
+	shadow = computeShadowWithVSM(vPositionFromLight1, shadowSampler1, shadowsInfo1.z, shadowsInfo1.x);
+#else
+#ifdef SHADOWPCF1
+#if defined(POINTLIGHT1)
+	shadow = computeShadowWithPCFCube(vLightData1.xyz, shadowSampler1, shadowsInfo1.z, shadowsInfo1.x);
+#else
+	shadow = computeShadowWithPCF(vPositionFromLight1, shadowSampler1, shadowsInfo1.y, shadowsInfo1.z, shadowsInfo1.x);
+#endif
+#else
+	#if defined(POINTLIGHT1)
+	shadow = computeShadowCube(vLightData1.xyz, shadowSampler1, shadowsInfo1.x, shadowsInfo1.z);
+	#else
+	shadow = computeShadow(vPositionFromLight1, shadowSampler1, shadowsInfo1.x, shadowsInfo1.z);
+	#endif
+#endif
+#endif
+#else
+	shadow = 1.;
+#endif
+	diffuseBase += info.diffuse * shadow;
+#endif
+
+#ifdef LIGHT2
+#ifdef SPOTLIGHT2
+	info = computeSpotLighting(viewDirectionW, normalW, vLightData2, vLightDirection2, vLightDiffuse2.rgb, vLightDiffuse2.a);
+#endif
+#ifdef HEMILIGHT2
+	info = computeHemisphericLighting(viewDirectionW, normalW, vLightData2, vLightDiffuse2.rgb, vLightGround2);
+#endif
+#if defined(POINTLIGHT2) || defined(DIRLIGHT2)
+	info = computeLighting(viewDirectionW, normalW, vLightData2, vLightDiffuse2.rgb, vLightDiffuse2.a);
+#endif
+#ifdef SHADOW2
+#ifdef SHADOWVSM2
+	shadow = computeShadowWithVSM(vPositionFromLight2, shadowSampler2, shadowsInfo2.z, shadowsInfo2.x);
+#else
+#ifdef SHADOWPCF2
+#if defined(POINTLIGHT2)
+	shadow = computeShadowWithPCFCube(vLightData2.xyz, shadowSampler2, shadowsInfo2.z, shadowsInfo2.x);
+#else
+	shadow = computeShadowWithPCF(vPositionFromLight2, shadowSampler2, shadowsInfo2.y, shadowsInfo2.z, shadowsInfo2.x);
+#endif
+#else
+	#if defined(POINTLIGHT2)
+	shadow = computeShadowCube(vLightData2.xyz, shadowSampler2, shadowsInfo2.x, shadowsInfo2.z);
+	#else
+	shadow = computeShadow(vPositionFromLight2, shadowSampler2, shadowsInfo2.x, shadowsInfo2.z);
+	#endif
+#endif	
+#endif	
+#else
+	shadow = 1.;
+#endif
+	diffuseBase += info.diffuse * shadow;
+#endif
+
+#ifdef LIGHT3
+#ifdef SPOTLIGHT3
+	info = computeSpotLighting(viewDirectionW, normalW, vLightData3, vLightDirection3, vLightDiffuse3.rgb, vLightDiffuse3.a);
+#endif
+#ifdef HEMILIGHT3
+	info = computeHemisphericLighting(viewDirectionW, normalW, vLightData3, vLightDiffuse3.rgb, vLightGround3);
+#endif
+#if defined(POINTLIGHT3) || defined(DIRLIGHT3)
+	info = computeLighting(viewDirectionW, normalW, vLightData3, vLightDiffuse3.rgb, vLightDiffuse3.a);
+#endif
+#ifdef SHADOW3
+#ifdef SHADOWVSM3
+		shadow = computeShadowWithVSM(vPositionFromLight3, shadowSampler3, shadowsInfo3.z, shadowsInfo3.x);
+#else
+#ifdef SHADOWPCF3
+#if defined(POINTLIGHT3)
+	shadow = computeShadowWithPCFCube(vLightData3.xyz, shadowSampler3, shadowsInfo3.z, shadowsInfo3.x);
+#else
+	shadow = computeShadowWithPCF(vPositionFromLight3, shadowSampler3, shadowsInfo3.y, shadowsInfo3.z, shadowsInfo3.x);
+#endif
+#else
+	#if defined(POINTLIGHT3)
+	shadow = computeShadowCube(vLightData3.xyz, shadowSampler3, shadowsInfo3.x, shadowsInfo3.z);
+	#else
+	shadow = computeShadow(vPositionFromLight3, shadowSampler3, shadowsInfo3.x, shadowsInfo3.z);
+	#endif
+#endif	
+#endif	
+#else
+	shadow = 1.;
+#endif
+	diffuseBase += info.diffuse * shadow;
+#endif
+
+#ifdef VERTEXALPHA
+	alpha *= vColor.a;
+#endif
+
+	vec3 finalDiffuse = clamp(diffuseBase * diffuseColor, 0.0, 1.0) * baseColor.rgb;
+
+	// Composition
+	//float r = Rand(vPositionW) * 0.5;
+	float r = vfur_length * 0.5;
+	vec4 color = vec4(finalDiffuse * (0.5 + r), alpha);
+
+#ifdef FOG
+	float fog = CalcFogFactor();
+	color.rgb = fog * color.rgb + (1.0 - fog) * vFogColor;
+#endif
+
+	gl_FragColor = color;
+}

+ 202 - 0
materialsLibrary/materials/fur/fur.vertex.fx

@@ -0,0 +1,202 @@
+precision highp float;
+
+// Attributes
+attribute vec3 position;
+attribute vec3 normal;
+
+#ifdef UV1
+attribute vec2 uv;
+#endif
+#ifdef UV2
+attribute vec2 uv2;
+#endif
+#ifdef VERTEXCOLOR
+attribute vec4 color;
+#endif
+#ifdef BONES
+attribute vec4 matricesIndices;
+attribute vec4 matricesWeights;
+#endif
+
+
+// Uniforms
+uniform float furLength;
+uniform float furAngle;
+#ifdef HEIGHTMAP
+uniform sampler2D heightTexture;
+#endif
+
+#ifdef INSTANCES
+attribute vec4 world0;
+attribute vec4 world1;
+attribute vec4 world2;
+attribute vec4 world3;
+#else
+uniform mat4 world;
+#endif
+
+uniform mat4 view;
+uniform mat4 viewProjection;
+
+#ifdef DIFFUSE
+varying vec2 vDiffuseUV;
+uniform mat4 diffuseMatrix;
+uniform vec2 vDiffuseInfos;
+#endif
+
+#ifdef BONES
+uniform mat4 mBones[BonesPerMesh];
+#endif
+
+#ifdef POINTSIZE
+uniform float pointSize;
+#endif
+
+// Output
+varying vec3 vPositionW;
+#ifdef NORMAL
+varying vec3 vNormalW;
+#endif
+varying float vfur_length;
+
+#ifdef VERTEXCOLOR
+varying vec4 vColor;
+#endif
+
+#ifdef CLIPPLANE
+uniform vec4 vClipPlane;
+varying float fClipDistance;
+#endif
+
+#ifdef FOG
+varying float fFogDistance;
+#endif
+
+#ifdef SHADOWS
+#if defined(SPOTLIGHT0) || defined(DIRLIGHT0)
+uniform mat4 lightMatrix0;
+varying vec4 vPositionFromLight0;
+#endif
+#if defined(SPOTLIGHT1) || defined(DIRLIGHT1)
+uniform mat4 lightMatrix1;
+varying vec4 vPositionFromLight1;
+#endif
+#if defined(SPOTLIGHT2) || defined(DIRLIGHT2)
+uniform mat4 lightMatrix2;
+varying vec4 vPositionFromLight2;
+#endif
+#if defined(SPOTLIGHT3) || defined(DIRLIGHT3)
+uniform mat4 lightMatrix3;
+varying vec4 vPositionFromLight3;
+#endif
+#endif
+
+float Rand(vec3 rv) {
+	float x = dot(rv, vec3(12.9898,78.233, 24.65487));
+	return fract(sin(x) * 43758.5453);
+}
+
+void main(void) {
+	mat4 finalWorld;
+
+#ifdef INSTANCES
+	finalWorld = mat4(world0, world1, world2, world3);
+#else
+	finalWorld = world;
+#endif
+
+#ifdef BONES
+	mat4 m0 = mBones[int(matricesIndices.x)] * matricesWeights.x;
+	mat4 m1 = mBones[int(matricesIndices.y)] * matricesWeights.y;
+	mat4 m2 = mBones[int(matricesIndices.z)] * matricesWeights.z;
+
+#ifdef BONES4
+	mat4 m3 = mBones[int(matricesIndices.w)] * matricesWeights.w;
+	finalWorld = finalWorld * (m0 + m1 + m2 + m3);
+#else
+	finalWorld = finalWorld * (m0 + m1 + m2);
+#endif 
+
+#endif
+//FUR
+float r = Rand(position);
+#ifdef HEIGHTMAP	
+	vfur_length = furLength * texture2D(heightTexture, uv).rgb.x;
+#else	
+	vfur_length = (furLength * r);
+#endif
+	vec3 tangent1 = vec3(normal.y, -normal.x, 0);
+	vec3 tangent2 = vec3(-normal.z, 0, normal.x);
+	r = Rand(tangent1*r);
+	float J = (2.0 + 4.0* r);
+	r = Rand(tangent2*r);
+	float K = (2.0 + 2.0* r);
+	tangent1 = tangent1*J + tangent2*K;
+	tangent1 = normalize(tangent1);
+    vec3 newPosition = position + normal * vfur_length*cos(furAngle) + tangent1*vfur_length*sin(furAngle);
+	
+//END FUR
+	gl_Position = viewProjection * finalWorld * vec4(newPosition, 1.0);
+
+	vec4 worldPos = finalWorld * vec4(newPosition, 1.0);
+	vPositionW = vec3(worldPos);
+
+#ifdef NORMAL
+	vNormalW = normalize(vec3(finalWorld * vec4(normal, 0.0)));
+#endif
+
+	// Texture coordinates
+#ifndef UV1
+	vec2 uv = vec2(0., 0.);
+#endif
+#ifndef UV2
+	vec2 uv2 = vec2(0., 0.);
+#endif
+
+#ifdef DIFFUSE
+	if (vDiffuseInfos.x == 0.)
+	{
+		vDiffuseUV = vec2(diffuseMatrix * vec4(uv, 1.0, 0.0));
+	}
+	else
+	{
+		vDiffuseUV = vec2(diffuseMatrix * vec4(uv2, 1.0, 0.0));
+	}
+#endif
+
+	// Clip plane
+#ifdef CLIPPLANE
+	fClipDistance = dot(worldPos, vClipPlane);
+#endif
+
+	// Fog
+#ifdef FOG
+	fFogDistance = (view * worldPos).z;
+#endif
+
+	// Shadows
+#ifdef SHADOWS
+#if defined(SPOTLIGHT0) || defined(DIRLIGHT0)
+	vPositionFromLight0 = lightMatrix0 * worldPos;
+#endif
+#if defined(SPOTLIGHT1) || defined(DIRLIGHT1)
+	vPositionFromLight1 = lightMatrix1 * worldPos;
+#endif
+#if defined(SPOTLIGHT2) || defined(DIRLIGHT2)
+	vPositionFromLight2 = lightMatrix2 * worldPos;
+#endif
+#if defined(SPOTLIGHT3) || defined(DIRLIGHT3)
+	vPositionFromLight3 = lightMatrix3 * worldPos;
+#endif
+#endif
+
+	// Vertex color
+#ifdef VERTEXCOLOR
+	vColor = color;
+#endif
+
+	// Point size
+#ifdef POINTSIZE
+	gl_PointSize = pointSize;
+#endif
+}

+ 236 - 0
materialsLibrary/materials/fur/readme.md

@@ -0,0 +1,236 @@
+# Fur material
+
+## Using the fur material
+
+The fur material needs a high number of the triangular facets that make up a mesh to work well.
+The number of facets needed also depends on the size of the mesh.
+Example that seem to work for ground and sphere are:
+
+```
+var ground = BABYLON.Mesh.CreateGround("ground", 8, 8, 200, scene);
+var sphere = BABYLON.Mesh.CreateSphere("sphere", 500, 8, scene);
+```
+
+The fur material is created using 
+
+```
+var furMaterial = new BABYLON.furMaterial("fur_material", scene);
+waterMaterial.bumpTexture = new BABYLON.Texture("bump.png", scene); // Set the bump texture
+
+ground.material = waterMaterial;
+```
+
+## Customize the fur material
+
+You can customise three properties of the fur material:
+
+```
+furMaterial.furLength = 3; // Represents the maximum length of the fur, which is then adjusted randomly. Default value is 1.
+furMaterial.furAngle = Math.PI/6; // Represents the angle the fur lies on the mesh from 0 to Math.PI/2. The default angle of 0 gives fur sticking straight up and PI/2 lies along the mesh.
+furMaterial.furColor = new BABYLON.Color3(0.44, 0.21, 0.02); // is the default color if furColor is not set.
+```
+
+## Using textures
+
+#heightTexture
+
+A greyscale image can be used to set the fur length. 
+A speckled greyscale image can produce fur like results.
+Any greyscale image with affect the fur length producing a heightMap type effect.
+
+```
+furMaterial.heightTexture = new BABYLON.Texture("speckles.jpg", scene); // Set the fur length with a texture.
+```
+#diffuseTexture
+A texture can also be used to paint the mesh. 
+The leopard fur texture used in the test is by Martin Wegmann from [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Leopard_fur.JPG)
+under the [license](https://creativecommons.org/licenses/by-sa/3.0/deed.en)
+
+```
+furMaterial.diffuseTexture = new BABYLON.Texture("leopard_fur.jpg", scene); // Set the fur length with a texture.
+```
+
+## Meshes where the number of facets is not user controlled on creation.
+
+Unlike the ground mesh where you can supply the number of subdivisions or the sphere mesh where you can supply the number of segments the majority of meshes are created using a minimum number of facets.
+To apply the fur material to these the number of facets per face of the mesh needs to be increased.
+
+The function increasedFacets will do this:
+When n is the number of points per side added to each side of a facet the number of facets is increased by the square of (n + 1).
+
+```
+function increasedFacets(mesh, pps) { //pps points per side		
+	var gaps = pps+1;
+	var n = gaps + 1;
+	var fvs =[];
+	for(var i=0; i<n; i++) {
+		fvs[i] = [];
+	}	
+	var A,B;
+	var d ={x:0,y:0,z:0};
+	var u ={x:0,y:0};
+	var indices = [];
+	var vertexIndex = [];
+	var side = [];
+	var uvs = mesh.getVerticesData(BABYLON.VertexBuffer.UVKind);
+	var meshIndices = mesh.getIndices();
+	var positions = mesh.getVerticesData(BABYLON.VertexBuffer.PositionKind);	
+	var normals =[];	
+	
+	for(var i = 0; i<meshIndices.length; i+=3) {
+		vertexIndex[0] = meshIndices[i];
+		vertexIndex[1] = meshIndices[i + 1];
+		vertexIndex[2] = meshIndices[i + 2];		
+		for(var j = 0; j<3; j++) {
+			A = vertexIndex[j];
+			B = vertexIndex[(j+1)%3];		
+			if(side[A] === undefined  && side[B] ===  undefined) {			
+				side[A] = [];
+				side[B] = [];			
+			}
+			else {
+				if(side[A] === undefined) {					
+					side[A] = [];
+				}
+				if(side[B] === undefined) {					
+					side[B] = [];								
+				}
+			}
+			if(side[A][B]  === undefined  && side[B][A] === undefined) {			
+				side[A][B] = [];
+				d.x = (positions[3 * B] - positions[3 * A])/gaps;
+				d.y = (positions[3 * B + 1] - positions[3 * A + 1])/gaps;
+				d.z = (positions[3 * B + 2] - positions[3 * A + 2])/gaps;
+				u.x = (uvs[2*B] - uvs[2*A])/gaps;
+				u.y = (uvs[2*B + 1] - uvs[2*A + 1])/gaps;
+				side[A][B].push(A);				
+				for(var k=1; k<gaps; k++) {				
+					side[A][B].push(positions.length/3);				
+					positions.push(positions[3 * A] + k*d.x, positions[3 * A + 1] + k*d.y, positions[3 * A + 2] + k*d.z);
+					uvs.push(uvs[2*A] + k*u.x, uvs[2*A + 1] + k*u.y);
+				}				
+				side[A][B].push(B);
+				side[B][A]=[];
+				l = side[A][B].length;
+				for(var a=0; a<l; a++) {
+					side[B][A][a] = side[A][B][l-1-a];
+				}
+			}
+			else {
+				if(side[A][B] === undefined) {			
+					side[A][B]=[];
+					l = side[B][A].length;
+					for(var a=0; a<l; a++) {
+						side[A][B][a] = side[B][A][l-1-a];
+					}
+				}
+				if(side[B][A] === undefined) {			
+					side[B][A]=[];				
+					l = side[A][B].length;
+					for(var a=0; a<l; a++) {
+						side[B][A][a] = side[A][B][l-1-a];
+					}
+				}
+			}					
+		}	
+		fvs[0][0] = meshIndices[i];
+		fvs[1][0] = side[meshIndices[i]][meshIndices[i + 1]][1];
+		fvs[1][1] = side[meshIndices[i]][meshIndices[i + 2]][1];		
+		for(var k = 2; k<gaps; k++) {
+			fvs[k][0] = side[meshIndices[i]][meshIndices[i + 1]][k];
+			fvs[k][k] = side[meshIndices[i]][meshIndices[i + 2]][k];		
+			d.x = (positions[3 * fvs[k][k]] - positions[3 * fvs[k][0]])/k;
+			d.y = (positions[3 * fvs[k][k] + 1] - positions[3 * fvs[k][0] + 1])/k;
+			d.z = (positions[3 * fvs[k][k] + 2] - positions[3 * fvs[k][0] + 2])/k;
+			u.x = (uvs[2*fvs[k][k]] - uvs[2*fvs[k][0]])/k;
+			u.y = (uvs[2*fvs[k][k] + 1] - uvs[2*fvs[k][0] + 1])/k;
+			for(var j = 1; j<k; j++) {				
+				fvs[k][j] = positions.length/3;				
+				positions.push(positions[3 * fvs[k][0]] + j*d.x, positions[3 * fvs[k][0] + 1] + j*d.y, positions[3 * fvs[k][0] + 2] + j*d.z);
+				uvs.push(uvs[2*fvs[k][0]] + j*u.x, uvs[2*fvs[k][0] + 1] + j*u.y);
+			}		
+		}
+		fvs[gaps] = side[meshIndices[i + 1]][meshIndices[i + 2]];
+		
+		indices.push(fvs[0][0],fvs[1][0],fvs[1][1]);
+		for(var k = 1; k<gaps; k++) {
+			for(var j = 0; j<k; j++) {			
+				indices.push(fvs[k][j],fvs[k+1][j],fvs[k+1][j+1]);
+				indices.push(fvs[k][j],fvs[k+1][j+1],fvs[k][j+1]);
+			}		
+			indices.push(fvs[k][j],fvs[k+1][j],fvs[k+1][j+1]);
+		}
+
+	}							
+
+	var vertexData = new BABYLON.VertexData();
+	vertexData.positions = positions;
+	vertexData.indices = indices;
+	vertexData.uvs = uvs;
+	
+	BABYLON.VertexData.ComputeNormals(positions, indices, normals);
+	vertexData.normals = normals;
+	mesh.dispose();
+	var newmesh = new BABYLON.Mesh("newmesh", scene);	
+	vertexData.applyToMesh(newmesh);
+
+	return newmesh;
+}
+```
+
+For sharp edged meshes such as a box the shader can separate the faces since the faces meeting at the corners have there own vertices and normals at these vertices. 
+These meshes are flat shaded. If this separation of the edges is a problem then the function convertToSmoothShadedMesh() can be used.
+However this can then produce some artefacts at the edges.
+
+```
+function convertToSmoothShadedMesh(mesh) {
+	var meshIndices = mesh.getIndices();
+	var meshPositions = mesh.getVerticesData(BABYLON.VertexBuffer.PositionKind);
+	var mesh_uvs = mesh.getVerticesData(BABYLON.VertexBuffer.UVKind);
+	var setPositions = [];
+	var indices = [];
+	var positions = [];
+	var uvs = [];
+	var normals = [];
+	var p;
+	var indexMap = [];
+	for(var i=0; i<meshPositions.length; i+=3) {
+		var temp =[];
+		temp.push(i/3, meshPositions[i], meshPositions[i + 1], meshPositions[i + 2], mesh_uvs[2*i/3], mesh_uvs[2*i/3 + 1]);
+		setPositions.push(temp);
+	}	
+	var i=0;
+	while(setPositions.length>0) {
+		p = setPositions.shift();
+		positions.push(p[1],p[2],p[3]);
+		uvs.push(p[4],p[5]);
+		indexMap[p[0]] = i;		
+		var j = 0;
+		while(j<setPositions.length) {		
+			if (Math.abs(p[1] - setPositions[j][1])<Math.pow(0.1, 10) && Math.abs(p[2] - setPositions[j][2])<Math.pow(0.1, 10) && Math.abs(p[3] - setPositions[j][3])<Math.pow(0.1, 10) ) {
+				indexMap[setPositions[j][0]] = i;			
+				setPositions.splice(j,1);
+			}
+			else {
+				j++;
+			}
+		}
+		i++;
+	}	
+	for(var i=0; i<meshIndices.length; i++) {
+		indices.push(indexMap[meshIndices[i]]);
+	}
+	
+	var vertexData = new BABYLON.VertexData();
+	vertexData.positions = positions;
+	vertexData.indices = indices;
+	vertexData.uvs = uvs;
+	
+	BABYLON.VertexData.ComputeNormals(positions, indices, normals);
+	vertexData.normals = normals;
+	vertexData.applyToMesh(mesh);
+
+	return mesh;
+	
+}
+```

+ 54 - 0
materialsLibrary/test/add/addfur.js

@@ -0,0 +1,54 @@
+window.prepareFur = function() {
+	var fur = new BABYLON.FurMaterial("fur", scene);
+	fur.furLength = 1;
+	fur.furAngle = 0;
+    fur.furColor = new BABYLON.Color3(0.44,0.21,0.02);
+
+
+// fur length
+    registerRangeUI("fur", "Fur length", 0, 15, function(value) {
+        fur.furLength = value;
+    }, function() {
+        return fur.furLength;
+    });
+	
+	// fur angle
+    registerRangeUI("fur", "Fur angle", 0, Math.PI/2, function(value) {
+        fur.furAngle = value;
+    }, function() {
+        return fur.furAngle;
+    });
+    
+    // fur color
+	registerColorPicker("fur", "Fur color", "#703605", function(value) {		
+		fur.furColor.r = value.r/255;
+		fur.furColor.g = value.g/255;
+		fur.furColor.b = value.b/255;
+	}, function() {
+		return fur.furColor;
+	});
+	
+	var DTON = false;
+	registerButtonUI("fur", "Tgl Diffuse Tex", function() {
+		DTON = !DTON;
+		if(DTON) {
+			fur.diffuseTexture = new BABYLON.Texture("textures/leopard_fur.jpg", scene);
+		}
+		else {
+			fur.diffuseTexture = null;
+		}
+	})
+	
+	var HTON = false;
+	registerButtonUI("fur", "Tgl Height Tex", function() {
+		HTON = !HTON;
+		if(HTON) {
+			fur.heightTexture = new BABYLON.Texture("textures/speckles.jpg", scene);
+		}
+		else {
+			fur.heightTexture = null;
+		}
+	})
+    
+    return fur;
+};

+ 173 - 88
materialsLibrary/test/babylon.max.js

@@ -4034,10 +4034,10 @@ var BABYLON;
         // Misc.
         Tools.GetPointerPrefix = function () {
             var eventPrefix = "pointer";
-            // Check if hand.js is referenced or if the browser natively supports pointer events
-            //if (!navigator.pointerEnabled) {
-            //    eventPrefix = "mouse";
-            //}
+            // Check if pointer events are supported
+            if (!window.PointerEvent && !navigator.pointerEnabled) {
+                eventPrefix = "mouse";
+            }
             return eventPrefix;
         };
         Tools.QueueNewFrame = function (func) {
@@ -11275,7 +11275,7 @@ var BABYLON;
                     _this._previousPosition.copyFrom(_this.position);
                 }
                 else {
-                    _this.setPosition(_this.position);
+                    _this.setPosition(newPosition);
                     if (_this.onCollide) {
                         _this.onCollide(collidedMesh);
                     }
@@ -12818,6 +12818,38 @@ var BABYLON;
             return null;
         };
         /**
+         * get a bone using its id
+         * @param {string} the bone's id
+         * @return {BABYLON.Bone|null} the bone or null if not found
+         */
+        Scene.prototype.getBoneByID = function (id) {
+            for (var skeletonIndex = 0; skeletonIndex < this.skeletons.length; skeletonIndex++) {
+                var skeleton = this.skeletons[skeletonIndex];
+                for (var boneIndex = 0; boneIndex < skeleton.bones.length; boneIndex++) {
+                    if (skeleton.bones[boneIndex].id === id) {
+                        return skeleton.bones[boneIndex];
+                    }
+                }
+            }
+            return null;
+        };
+        /**
+        * get a bone using its id
+        * @param {string} the bone's name
+        * @return {BABYLON.Bone|null} the bone or null if not found
+        */
+        Scene.prototype.getBoneByName = function (name) {
+            for (var skeletonIndex = 0; skeletonIndex < this.skeletons.length; skeletonIndex++) {
+                var skeleton = this.skeletons[skeletonIndex];
+                for (var boneIndex = 0; boneIndex < skeleton.bones.length; boneIndex++) {
+                    if (skeleton.bones[boneIndex].name === name) {
+                        return skeleton.bones[boneIndex];
+                    }
+                }
+            }
+            return null;
+        };
+        /**
          * get a light node using its name
          * @param {string} the light's name
          * @return {BABYLON.Light|null} the light or null if none found.
@@ -12980,7 +13012,12 @@ var BABYLON;
             if (light) {
                 return light;
             }
-            return this.getCameraByID(id);
+            var camera = this.getCameraByID(id);
+            if (camera) {
+                return camera;
+            }
+            var bone = this.getBoneByID(id);
+            return bone;
         };
         Scene.prototype.getNodeByName = function (name) {
             var mesh = this.getMeshByName(name);
@@ -12991,7 +13028,12 @@ var BABYLON;
             if (light) {
                 return light;
             }
-            return this.getCameraByName(name);
+            var camera = this.getCameraByName(name);
+            if (camera) {
+                return camera;
+            }
+            var bone = this.getBoneByName(name);
+            return bone;
         };
         Scene.prototype.getMeshByName = function (name) {
             for (var index = 0; index < this.meshes.length; index++) {
@@ -14216,6 +14258,13 @@ var BABYLON;
             enumerable: true,
             configurable: true
         });
+        Object.defineProperty(InstancedMesh.prototype, "renderingGroupId", {
+            get: function () {
+                return this._sourceMesh.renderingGroupId;
+            },
+            enumerable: true,
+            configurable: true
+        });
         InstancedMesh.prototype.getTotalVertices = function () {
             return this._sourceMesh.getTotalVertices();
         };
@@ -14226,8 +14275,8 @@ var BABYLON;
             enumerable: true,
             configurable: true
         });
-        InstancedMesh.prototype.getVerticesData = function (kind) {
-            return this._sourceMesh.getVerticesData(kind);
+        InstancedMesh.prototype.getVerticesData = function (kind, copyWhenShared) {
+            return this._sourceMesh.getVerticesData(kind, copyWhenShared);
         };
         InstancedMesh.prototype.isVerticesDataPresent = function (kind) {
             return this._sourceMesh.isVerticesDataPresent(kind);
@@ -15572,6 +15621,14 @@ var BABYLON;
         };
         // Cylinder and cone
         Mesh.CreateCylinder = function (name, height, diameterTop, diameterBottom, tessellation, subdivisions, scene, updatable, sideOrientation) {
+            if (scene === undefined || !(scene instanceof BABYLON.Scene)) {
+                if (scene !== undefined) {
+                    sideOrientation = updatable || Mesh.DEFAULTSIDE;
+                    updatable = scene;
+                }
+                scene = subdivisions;
+                subdivisions = 1;
+            }
             var options = {
                 height: height,
                 diameterTop: diameterTop,
@@ -16891,6 +16948,8 @@ var BABYLON;
             if (this._texture === undefined) {
                 return;
             }
+            // Release
+            this.releaseInternalTexture();
             // Callback
             if (this.onDispose) {
                 this.onDispose();
@@ -17402,6 +17461,9 @@ var BABYLON;
         };
         RenderTargetTexture.prototype.render = function (useCameraPostProcess, dumpForDebug) {
             var scene = this.getScene();
+            if (this.activeCamera && this.activeCamera !== scene.activeCamera) {
+                scene.setTransformMatrix(this.activeCamera.getViewMatrix(), this.activeCamera.getProjectionMatrix(true));
+            }
             if (this._waitingRenderList) {
                 this.renderList = [];
                 for (var index = 0; index < this._waitingRenderList.length; index++) {
@@ -17445,6 +17507,9 @@ var BABYLON;
             if (this.onAfterUnbind) {
                 this.onAfterUnbind();
             }
+            if (this.activeCamera && this.activeCamera !== scene.activeCamera) {
+                scene.setTransformMatrix(scene.activeCamera.getViewMatrix(), scene.activeCamera.getProjectionMatrix(true));
+            }
             scene.resetCachedMaterial();
         };
         RenderTargetTexture.prototype.renderToTarget = function (faceIndex, currentRenderList, useCameraPostProcess, dumpForDebug) {
@@ -18755,6 +18820,8 @@ var BABYLON;
             serializationObject.id = this.id;
             serializationObject.tags = BABYLON.Tags.GetTags(this);
             serializationObject.backFaceCulling = this.backFaceCulling;
+            serializationObject.checkReadyOnlyOnce = this.checkReadyOnlyOnce;
+            serializationObject.disableDepthWrite = this.disableDepthWrite;
             return serializationObject;
         };
         Material.ParseMultiMaterial = function (parsedMultiMaterial, scene) {
@@ -19490,51 +19557,53 @@ var BABYLON;
                         this._effect.setColor4("emissiveRightColor", this.emissiveFresnelParameters.rightColor, this.emissiveFresnelParameters.bias);
                     }
                 }
-                // Textures        
-                if (this.diffuseTexture && StandardMaterial.DiffuseTextureEnabled) {
-                    this._effect.setTexture("diffuseSampler", this.diffuseTexture);
-                    this._effect.setFloat2("vDiffuseInfos", this.diffuseTexture.coordinatesIndex, this.diffuseTexture.level);
-                    this._effect.setMatrix("diffuseMatrix", this.diffuseTexture.getTextureMatrix());
-                }
-                if (this.ambientTexture && StandardMaterial.AmbientTextureEnabled) {
-                    this._effect.setTexture("ambientSampler", this.ambientTexture);
-                    this._effect.setFloat2("vAmbientInfos", this.ambientTexture.coordinatesIndex, this.ambientTexture.level);
-                    this._effect.setMatrix("ambientMatrix", this.ambientTexture.getTextureMatrix());
-                }
-                if (this.opacityTexture && StandardMaterial.OpacityTextureEnabled) {
-                    this._effect.setTexture("opacitySampler", this.opacityTexture);
-                    this._effect.setFloat2("vOpacityInfos", this.opacityTexture.coordinatesIndex, this.opacityTexture.level);
-                    this._effect.setMatrix("opacityMatrix", this.opacityTexture.getTextureMatrix());
-                }
-                if (this.reflectionTexture && StandardMaterial.ReflectionTextureEnabled) {
-                    if (this.reflectionTexture.isCube) {
-                        this._effect.setTexture("reflectionCubeSampler", this.reflectionTexture);
+                // Textures     
+                if (scene.texturesEnabled) {
+                    if (this.diffuseTexture && StandardMaterial.DiffuseTextureEnabled) {
+                        this._effect.setTexture("diffuseSampler", this.diffuseTexture);
+                        this._effect.setFloat2("vDiffuseInfos", this.diffuseTexture.coordinatesIndex, this.diffuseTexture.level);
+                        this._effect.setMatrix("diffuseMatrix", this.diffuseTexture.getTextureMatrix());
                     }
-                    else {
-                        this._effect.setTexture("reflection2DSampler", this.reflectionTexture);
+                    if (this.ambientTexture && StandardMaterial.AmbientTextureEnabled) {
+                        this._effect.setTexture("ambientSampler", this.ambientTexture);
+                        this._effect.setFloat2("vAmbientInfos", this.ambientTexture.coordinatesIndex, this.ambientTexture.level);
+                        this._effect.setMatrix("ambientMatrix", this.ambientTexture.getTextureMatrix());
+                    }
+                    if (this.opacityTexture && StandardMaterial.OpacityTextureEnabled) {
+                        this._effect.setTexture("opacitySampler", this.opacityTexture);
+                        this._effect.setFloat2("vOpacityInfos", this.opacityTexture.coordinatesIndex, this.opacityTexture.level);
+                        this._effect.setMatrix("opacityMatrix", this.opacityTexture.getTextureMatrix());
+                    }
+                    if (this.reflectionTexture && StandardMaterial.ReflectionTextureEnabled) {
+                        if (this.reflectionTexture.isCube) {
+                            this._effect.setTexture("reflectionCubeSampler", this.reflectionTexture);
+                        }
+                        else {
+                            this._effect.setTexture("reflection2DSampler", this.reflectionTexture);
+                        }
+                        this._effect.setMatrix("reflectionMatrix", this.reflectionTexture.getReflectionTextureMatrix());
+                        this._effect.setFloat2("vReflectionInfos", this.reflectionTexture.level, this.roughness);
+                    }
+                    if (this.emissiveTexture && StandardMaterial.EmissiveTextureEnabled) {
+                        this._effect.setTexture("emissiveSampler", this.emissiveTexture);
+                        this._effect.setFloat2("vEmissiveInfos", this.emissiveTexture.coordinatesIndex, this.emissiveTexture.level);
+                        this._effect.setMatrix("emissiveMatrix", this.emissiveTexture.getTextureMatrix());
+                    }
+                    if (this.lightmapTexture && StandardMaterial.LightmapEnabled) {
+                        this._effect.setTexture("lightmapSampler", this.lightmapTexture);
+                        this._effect.setFloat2("vLightmapInfos", this.lightmapTexture.coordinatesIndex, this.lightmapTexture.level);
+                        this._effect.setMatrix("lightmapMatrix", this.lightmapTexture.getTextureMatrix());
+                    }
+                    if (this.specularTexture && StandardMaterial.SpecularTextureEnabled) {
+                        this._effect.setTexture("specularSampler", this.specularTexture);
+                        this._effect.setFloat2("vSpecularInfos", this.specularTexture.coordinatesIndex, this.specularTexture.level);
+                        this._effect.setMatrix("specularMatrix", this.specularTexture.getTextureMatrix());
+                    }
+                    if (this.bumpTexture && scene.getEngine().getCaps().standardDerivatives && StandardMaterial.BumpTextureEnabled) {
+                        this._effect.setTexture("bumpSampler", this.bumpTexture);
+                        this._effect.setFloat2("vBumpInfos", this.bumpTexture.coordinatesIndex, 1.0 / this.bumpTexture.level);
+                        this._effect.setMatrix("bumpMatrix", this.bumpTexture.getTextureMatrix());
                     }
-                    this._effect.setMatrix("reflectionMatrix", this.reflectionTexture.getReflectionTextureMatrix());
-                    this._effect.setFloat2("vReflectionInfos", this.reflectionTexture.level, this.roughness);
-                }
-                if (this.emissiveTexture && StandardMaterial.EmissiveTextureEnabled) {
-                    this._effect.setTexture("emissiveSampler", this.emissiveTexture);
-                    this._effect.setFloat2("vEmissiveInfos", this.emissiveTexture.coordinatesIndex, this.emissiveTexture.level);
-                    this._effect.setMatrix("emissiveMatrix", this.emissiveTexture.getTextureMatrix());
-                }
-                if (this.lightmapTexture && StandardMaterial.LightmapEnabled) {
-                    this._effect.setTexture("lightmapSampler", this.lightmapTexture);
-                    this._effect.setFloat2("vLightmapInfos", this.lightmapTexture.coordinatesIndex, this.lightmapTexture.level);
-                    this._effect.setMatrix("lightmapMatrix", this.lightmapTexture.getTextureMatrix());
-                }
-                if (this.specularTexture && StandardMaterial.SpecularTextureEnabled) {
-                    this._effect.setTexture("specularSampler", this.specularTexture);
-                    this._effect.setFloat2("vSpecularInfos", this.specularTexture.coordinatesIndex, this.specularTexture.level);
-                    this._effect.setMatrix("specularMatrix", this.specularTexture.getTextureMatrix());
-                }
-                if (this.bumpTexture && scene.getEngine().getCaps().standardDerivatives && StandardMaterial.BumpTextureEnabled) {
-                    this._effect.setTexture("bumpSampler", this.bumpTexture);
-                    this._effect.setFloat2("vBumpInfos", this.bumpTexture.coordinatesIndex, 1.0 / this.bumpTexture.level);
-                    this._effect.setMatrix("bumpMatrix", this.bumpTexture.getTextureMatrix());
                 }
                 // Clip plane
                 if (scene.clipPlane) {
@@ -19838,10 +19907,16 @@ var BABYLON;
             }
             return true;
         };
-        MultiMaterial.prototype.clone = function (name) {
+        MultiMaterial.prototype.clone = function (name, cloneChildren) {
             var newMultiMaterial = new MultiMaterial(name, this.getScene());
             for (var index = 0; index < this.subMaterials.length; index++) {
-                var subMaterial = this.subMaterials[index];
+                var subMaterial = null;
+                if (cloneChildren) {
+                    subMaterial = this.subMaterials[index].clone(name + "-" + this.subMaterials[index].name);
+                }
+                else {
+                    subMaterial = this.subMaterials[index];
+                }
                 newMultiMaterial.subMaterials.push(subMaterial);
             }
             return newMultiMaterial;
@@ -20050,7 +20125,7 @@ var BABYLON;
     var Internals;
     (function (Internals) {
         var parseMaterialById = function (id, parsedData, scene, rootUrl) {
-            for (var index = 0; index < parsedData.materials.length; index++) {
+            for (var index = 0, cache = parsedData.materials.length; index < cache; index++) {
                 var parsedMaterial = parsedData.materials[index];
                 if (parsedMaterial.id === id) {
                     return BABYLON.Material.Parse(parsedMaterial, scene, rootUrl);
@@ -20080,7 +20155,8 @@ var BABYLON;
                 var loadedMaterialsIds = [];
                 var hierarchyIds = [];
                 var index;
-                for (index = 0; index < parsedData.meshes.length; index++) {
+                var cache;
+                for (index = 0, cache = parsedData.meshes.length; index < cache; index++) {
                     var parsedMesh = parsedData.meshes[index];
                     if (!meshesNames || isDescendantOf(parsedMesh, meshesNames, hierarchyIds)) {
                         if (meshesNames instanceof Array) {
@@ -20140,16 +20216,16 @@ var BABYLON;
                         if (parsedMesh.materialId) {
                             var materialFound = (loadedMaterialsIds.indexOf(parsedMesh.materialId) !== -1);
                             if (!materialFound && parsedData.multiMaterials) {
-                                for (var multimatIndex = 0; multimatIndex < parsedData.multiMaterials.length; multimatIndex++) {
+                                for (var multimatIndex = 0, multimatCache = parsedData.multiMaterials.length; multimatIndex < multimatCache; multimatIndex++) {
                                     var parsedMultiMaterial = parsedData.multiMaterials[multimatIndex];
                                     if (parsedMultiMaterial.id === parsedMesh.materialId) {
-                                        for (var matIndex = 0; matIndex < parsedMultiMaterial.materials.length; matIndex++) {
+                                        for (var matIndex = 0, matCache = parsedMultiMaterial.materials.length; matIndex < matCache; matIndex++) {
                                             var subMatId = parsedMultiMaterial.materials[matIndex];
                                             loadedMaterialsIds.push(subMatId);
                                             parseMaterialById(subMatId, parsedData, scene, rootUrl);
                                         }
                                         loadedMaterialsIds.push(parsedMultiMaterial.id);
-                                        parsedMultiMaterial.Parse(parsedMultiMaterial, scene);
+                                        BABYLON.Material.ParseMultiMaterial(parsedMultiMaterial, scene);
                                         materialFound = true;
                                         break;
                                     }
@@ -20166,7 +20242,7 @@ var BABYLON;
                         if (parsedMesh.skeletonId > -1 && scene.skeletons) {
                             var skeletonAlreadyLoaded = (loadedSkeletonsIds.indexOf(parsedMesh.skeletonId) > -1);
                             if (!skeletonAlreadyLoaded) {
-                                for (var skeletonIndex = 0; skeletonIndex < parsedData.skeletons.length; skeletonIndex++) {
+                                for (var skeletonIndex = 0, skeletonCache = parsedData.skeletons.length; skeletonIndex < skeletonCache; skeletonIndex++) {
                                     var parsedSkeleton = parsedData.skeletons[skeletonIndex];
                                     if (parsedSkeleton.id === parsedMesh.skeletonId) {
                                         skeletons.push(BABYLON.Skeleton.Parse(parsedSkeleton, scene));
@@ -20181,7 +20257,7 @@ var BABYLON;
                 }
                 // Connecting parents
                 var currentMesh;
-                for (index = 0; index < scene.meshes.length; index++) {
+                for (index = 0, cache = scene.meshes.length; index < cache; index++) {
                     currentMesh = scene.meshes[index];
                     if (currentMesh._waitingParentId) {
                         currentMesh.parent = scene.getLastEntryByID(currentMesh._waitingParentId);
@@ -20189,7 +20265,7 @@ var BABYLON;
                     }
                 }
                 // freeze world matrix application
-                for (index = 0; index < scene.meshes.length; index++) {
+                for (index = 0, cache = scene.meshes.length; index < cache; index++) {
                     currentMesh = scene.meshes[index];
                     if (currentMesh._waitingFreezeWorldMatrix) {
                         currentMesh.freezeWorldMatrix();
@@ -20198,7 +20274,7 @@ var BABYLON;
                 }
                 // Particles
                 if (parsedData.particleSystems) {
-                    for (index = 0; index < parsedData.particleSystems.length; index++) {
+                    for (index = 0, cache = parsedData.particleSystems.length; index < cache; index++) {
                         var parsedParticleSystem = parsedData.particleSystems[index];
                         if (hierarchyIds.indexOf(parsedParticleSystem.emitterId) !== -1) {
                             particleSystems.push(BABYLON.ParticleSystem.Parse(parsedParticleSystem, scene, rootUrl));
@@ -20243,28 +20319,29 @@ var BABYLON;
                     scene.collisionsEnabled = parsedData.collisionsEnabled;
                 }
                 scene.workerCollisions = !!parsedData.workerCollisions;
-                // Lights
                 var index;
-                for (index = 0; index < parsedData.lights.length; index++) {
+                var cache;
+                // Lights
+                for (index = 0, cache = parsedData.lights.length; index < cache; index++) {
                     var parsedLight = parsedData.lights[index];
                     BABYLON.Light.Parse(parsedLight, scene);
                 }
                 // Materials
                 if (parsedData.materials) {
-                    for (index = 0; index < parsedData.materials.length; index++) {
+                    for (index = 0, cache = parsedData.materials.length; index < cache; index++) {
                         var parsedMaterial = parsedData.materials[index];
                         BABYLON.Material.Parse(parsedMaterial, scene, rootUrl);
                     }
                 }
                 if (parsedData.multiMaterials) {
-                    for (index = 0; index < parsedData.multiMaterials.length; index++) {
+                    for (index = 0, cache = parsedData.multiMaterials.length; index < cache; index++) {
                         var parsedMultiMaterial = parsedData.multiMaterials[index];
                         BABYLON.Material.ParseMultiMaterial(parsedMultiMaterial, scene);
                     }
                 }
                 // Skeletons
                 if (parsedData.skeletons) {
-                    for (index = 0; index < parsedData.skeletons.length; index++) {
+                    for (index = 0, cache = parsedData.skeletons.length; index < cache; index++) {
                         var parsedSkeleton = parsedData.skeletons[index];
                         BABYLON.Skeleton.Parse(parsedSkeleton, scene);
                     }
@@ -20275,7 +20352,7 @@ var BABYLON;
                     // Boxes
                     var boxes = geometries.boxes;
                     if (boxes) {
-                        for (index = 0; index < boxes.length; index++) {
+                        for (index = 0, cache = boxes.length; index < cache; index++) {
                             var parsedBox = boxes[index];
                             BABYLON.Geometry.Primitives.Box.Parse(parsedBox, scene);
                         }
@@ -20283,7 +20360,7 @@ var BABYLON;
                     // Spheres
                     var spheres = geometries.spheres;
                     if (spheres) {
-                        for (index = 0; index < spheres.length; index++) {
+                        for (index = 0, cache = spheres.length; index < cache; index++) {
                             var parsedSphere = spheres[index];
                             BABYLON.Geometry.Primitives.Sphere.Parse(parsedSphere, scene);
                         }
@@ -20291,7 +20368,7 @@ var BABYLON;
                     // Cylinders
                     var cylinders = geometries.cylinders;
                     if (cylinders) {
-                        for (index = 0; index < cylinders.length; index++) {
+                        for (index = 0, cache = cylinders.length; index < cache; index++) {
                             var parsedCylinder = cylinders[index];
                             BABYLON.Geometry.Primitives.Cylinder.Parse(parsedCylinder, scene);
                         }
@@ -20299,7 +20376,7 @@ var BABYLON;
                     // Toruses
                     var toruses = geometries.toruses;
                     if (toruses) {
-                        for (index = 0; index < toruses.length; index++) {
+                        for (index = 0, cache = toruses.length; index < cache; index++) {
                             var parsedTorus = toruses[index];
                             BABYLON.Geometry.Primitives.Torus.Parse(parsedTorus, scene);
                         }
@@ -20307,7 +20384,7 @@ var BABYLON;
                     // Grounds
                     var grounds = geometries.grounds;
                     if (grounds) {
-                        for (index = 0; index < grounds.length; index++) {
+                        for (index = 0, cache = grounds.length; index < cache; index++) {
                             var parsedGround = grounds[index];
                             BABYLON.Geometry.Primitives.Ground.Parse(parsedGround, scene);
                         }
@@ -20315,7 +20392,7 @@ var BABYLON;
                     // Planes
                     var planes = geometries.planes;
                     if (planes) {
-                        for (index = 0; index < planes.length; index++) {
+                        for (index = 0, cache = planes.length; index < cache; index++) {
                             var parsedPlane = planes[index];
                             BABYLON.Geometry.Primitives.Plane.Parse(parsedPlane, scene);
                         }
@@ -20323,7 +20400,7 @@ var BABYLON;
                     // TorusKnots
                     var torusKnots = geometries.torusKnots;
                     if (torusKnots) {
-                        for (index = 0; index < torusKnots.length; index++) {
+                        for (index = 0, cache = torusKnots.length; index < cache; index++) {
                             var parsedTorusKnot = torusKnots[index];
                             BABYLON.Geometry.Primitives.TorusKnot.Parse(parsedTorusKnot, scene);
                         }
@@ -20331,19 +20408,19 @@ var BABYLON;
                     // VertexData
                     var vertexData = geometries.vertexData;
                     if (vertexData) {
-                        for (index = 0; index < vertexData.length; index++) {
+                        for (index = 0, cache = vertexData.length; index < cache; index++) {
                             var parsedVertexData = vertexData[index];
                             BABYLON.Geometry.Parse(parsedVertexData, scene, rootUrl);
                         }
                     }
                 }
                 // Meshes
-                for (index = 0; index < parsedData.meshes.length; index++) {
+                for (index = 0, cache = parsedData.meshes.length; index < cache; index++) {
                     var parsedMesh = parsedData.meshes[index];
                     BABYLON.Mesh.Parse(parsedMesh, scene, rootUrl);
                 }
                 // Cameras
-                for (index = 0; index < parsedData.cameras.length; index++) {
+                for (index = 0, cache = parsedData.cameras.length; index < cache; index++) {
                     var parsedCamera = parsedData.cameras[index];
                     BABYLON.Camera.Parse(parsedCamera, scene);
                 }
@@ -20351,14 +20428,14 @@ var BABYLON;
                     scene.setActiveCameraByID(parsedData.activeCameraID);
                 }
                 // Browsing all the graph to connect the dots
-                for (index = 0; index < scene.cameras.length; index++) {
+                for (index = 0, cache = scene.cameras.length; index < cache; index++) {
                     var camera = scene.cameras[index];
                     if (camera._waitingParentId) {
                         camera.parent = scene.getLastEntryByID(camera._waitingParentId);
                         camera._waitingParentId = undefined;
                     }
                 }
-                for (index = 0; index < scene.lights.length; index++) {
+                for (index = 0, cache = scene.lights.length; index < cache; index++) {
                     var light = scene.lights[index];
                     if (light._waitingParentId) {
                         light.parent = scene.getLastEntryByID(light._waitingParentId);
@@ -20367,7 +20444,7 @@ var BABYLON;
                 }
                 // Sounds
                 if (BABYLON.AudioEngine && parsedData.sounds) {
-                    for (index = 0; index < parsedData.sounds.length; index++) {
+                    for (index = 0, cache = parsedData.sounds.length; index < cache; index++) {
                         var parsedSound = parsedData.sounds[index];
                         if (BABYLON.Engine.audioEngine.canUseWebAudio) {
                             BABYLON.Sound.Parse(parsedSound, scene, rootUrl);
@@ -20378,7 +20455,7 @@ var BABYLON;
                     }
                 }
                 // Connect parents & children and parse actions
-                for (index = 0; index < scene.meshes.length; index++) {
+                for (index = 0, cache = scene.meshes.length; index < cache; index++) {
                     var mesh = scene.meshes[index];
                     if (mesh._waitingParentId) {
                         mesh.parent = scene.getLastEntryByID(mesh._waitingParentId);
@@ -20390,7 +20467,7 @@ var BABYLON;
                     }
                 }
                 // freeze world matrix application
-                for (index = 0; index < scene.meshes.length; index++) {
+                for (index = 0, cache = scene.meshes.length; index < cache; index++) {
                     var currentMesh = scene.meshes[index];
                     if (currentMesh._waitingFreezeWorldMatrix) {
                         currentMesh.freezeWorldMatrix();
@@ -20399,21 +20476,21 @@ var BABYLON;
                 }
                 // Particles Systems
                 if (parsedData.particleSystems) {
-                    for (index = 0; index < parsedData.particleSystems.length; index++) {
+                    for (index = 0, cache = parsedData.particleSystems.length; index < cache; index++) {
                         var parsedParticleSystem = parsedData.particleSystems[index];
                         BABYLON.ParticleSystem.Parse(parsedParticleSystem, scene, rootUrl);
                     }
                 }
                 // Lens flares
                 if (parsedData.lensFlareSystems) {
-                    for (index = 0; index < parsedData.lensFlareSystems.length; index++) {
+                    for (index = 0, cache = parsedData.lensFlareSystems.length; index < cache; index++) {
                         var parsedLensFlareSystem = parsedData.lensFlareSystems[index];
                         BABYLON.LensFlareSystem.Parse(parsedLensFlareSystem, scene, rootUrl);
                     }
                 }
                 // Shadows
                 if (parsedData.shadowGenerators) {
-                    for (index = 0; index < parsedData.shadowGenerators.length; index++) {
+                    for (index = 0, cache = parsedData.shadowGenerators.length; index < cache; index++) {
                         var parsedShadowGenerator = parsedData.shadowGenerators[index];
                         BABYLON.ShadowGenerator.Parse(parsedShadowGenerator, scene);
                     }
@@ -30221,10 +30298,14 @@ var BABYLON;
             var serializationInstance = {
                 name: instance.name,
                 position: instance.position.asArray(),
-                rotation: instance.rotation.asArray(),
-                rotationQuaternion: instance.rotationQuaternion.asArray(),
                 scaling: instance.scaling.asArray()
             };
+            if (instance.rotationQuaternion) {
+                serializationInstance.rotationQuaternion = instance.rotationQuaternion.asArray();
+            }
+            else if (instance.rotation) {
+                serializationInstance.rotation = instance.rotation.asArray();
+            }
             serializationObject.instances.push(serializationInstance);
             // Animations
             BABYLON.Animation.AppendSerializedAnimations(instance, serializationInstance);
@@ -36261,6 +36342,10 @@ var BABYLON;
                 // Remove from the scene if found 
                 this._scene.reflectionProbes.splice(index, 1);
             }
+            if (this._renderTargetTexture) {
+                this._renderTargetTexture.dispose();
+                this._renderTargetTexture = null;
+            }
         };
         return ReflectionProbe;
     })();

+ 9 - 2
materialsLibrary/test/index.html

@@ -11,6 +11,7 @@
     <script src="../dist/babylon.lavaMaterial.js"></script>
 	<script src="../dist/babylon.terrainMaterial.js"></script>
 	<script src="../dist/babylon.pbrMaterial.js"></script>
+	<script src="../dist/babylon.furMaterial.js"></script>
 
 	<style>
 		html, body {
@@ -49,6 +50,7 @@
     <script src="add/addlava.js"></script>
     <script src="add/addnormal.js"></script>
 	<script src="add/addwater.js"></script>
+	<script src="add/addfur.js"></script>
 	
 	<script>
 		if (BABYLON.Engine.isSupported()) {
@@ -170,6 +172,8 @@
 
 				var normal = prepareNormal();
 				
+				var fur = prepareFur();
+				
 				var water = prepareWater();
 				water.addToRenderList(skybox);
 				water.addToRenderList(shadowCaster);
@@ -204,7 +208,7 @@
 				sphere.material = std;				
 				sphere.receiveShadows = true;
 
-				gui.add(options, 'material', ['standard', 'simple', 'water', 'fire', 'lava', 'normal', 'terrain', 'pbr']).onFinishChange(function () {
+				gui.add(options, 'material', ['standard', 'simple', 'water', 'fire', 'lava', 'normal', 'terrain', 'pbr', 'fur']).onFinishChange(function () {
 					water.enableRenderTargets(false);
 					
 					switch (options.material) {
@@ -230,7 +234,10 @@
 							break;
 						case "pbr":
 							currentMaterial = pbr;
-							break;													
+							break;
+						case "fur":
+							currentMaterial = fur;
+							break;														
 						default:
 							currentMaterial = std;
 							break;

BIN
materialsLibrary/test/textures/leopard_fur.JPG


BIN
materialsLibrary/test/textures/speckles.jpg