David Catuhe 9 سال پیش
والد
کامیت
f7bf90b5ba

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 23 - 23
dist/preview release/babylon.core.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 3420 - 3205
dist/preview release/babylon.d.ts


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 42 - 41
dist/preview release/babylon.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 641 - 99
dist/preview release/babylon.max.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 42 - 41
dist/preview release/babylon.noworker.js


+ 1 - 1
src/Canvas2d/babylon.canvas2d.js

@@ -148,7 +148,7 @@ var BABYLON;
             });
             if (this._isScreenSpace) {
                 this._afterRenderObserver = this._scene.onAfterRenderObservable.add(function (d, s) {
-                    _this._engine.clear(null, false, true);
+                    _this._engine.clear(null, false, true, true);
                     _this._render();
                 });
             }

+ 2 - 2
src/Lights/Shadows/babylon.shadowGenerator.js

@@ -123,10 +123,10 @@ var BABYLON;
             };
             this._shadowMap.onClearObservable.add(function (engine) {
                 if (_this.useBlurVarianceShadowMap || _this.useVarianceShadowMap) {
-                    engine.clear(new BABYLON.Color4(0, 0, 0, 0), true, true);
+                    engine.clear(new BABYLON.Color4(0, 0, 0, 0), true, true, true);
                 }
                 else {
-                    engine.clear(new BABYLON.Color4(1.0, 1.0, 1.0, 1.0), true, true);
+                    engine.clear(new BABYLON.Color4(1.0, 1.0, 1.0, 1.0), true, true, true);
                 }
             });
         }

+ 2 - 2
src/Materials/Textures/Procedurals/babylon.proceduralTexture.js

@@ -231,7 +231,7 @@ var BABYLON;
                     engine.bindFramebuffer(this._texture, face);
                     this._effect.setFloat("face", face);
                     // Clear
-                    engine.clear(scene.clearColor, true, true);
+                    engine.clear(scene.clearColor, true, true, true);
                     // Draw order
                     engine.draw(true, 0, 6);
                     // Mipmaps
@@ -243,7 +243,7 @@ var BABYLON;
             else {
                 engine.bindFramebuffer(this._texture);
                 // Clear
-                engine.clear(scene.clearColor, true, true);
+                engine.clear(scene.clearColor, true, true, true);
                 // Draw order
                 engine.draw(true, 0, 6);
             }

+ 25 - 1
src/Materials/Textures/babylon.renderTargetTexture.js

@@ -275,7 +275,7 @@ var BABYLON;
                 this.onClearObservable.notifyObservers(engine);
             }
             else {
-                engine.clear(scene.clearColor, true, true);
+                engine.clear(scene.clearColor, true, true, true);
             }
             if (!this._doNotChangeAspectRatio) {
                 scene.updateTransformMatrix(true);
@@ -303,6 +303,30 @@ var BABYLON;
                 engine.unBindFramebuffer(this._texture, this.isCube);
             }
         };
+        /**
+         * Overrides the default sort function applied in the renderging group to prepare the meshes.
+         * This allowed control for front to back rendering or reversly depending of the special needs.
+         *
+         * @param renderingGroupId The rendering group id corresponding to its index
+         * @param opaqueSortCompareFn The opaque queue comparison function use to sort.
+         * @param alphaTestSortCompareFn The alpha test queue comparison function use to sort.
+         * @param transparentSortCompareFn The transparent queue comparison function use to sort.
+         */
+        RenderTargetTexture.prototype.setRenderingOrder = function (renderingGroupId, opaqueSortCompareFn, alphaTestSortCompareFn, transparentSortCompareFn) {
+            if (opaqueSortCompareFn === void 0) { opaqueSortCompareFn = null; }
+            if (alphaTestSortCompareFn === void 0) { alphaTestSortCompareFn = null; }
+            if (transparentSortCompareFn === void 0) { transparentSortCompareFn = null; }
+            this._renderingManager.setRenderingOrder(renderingGroupId, opaqueSortCompareFn, alphaTestSortCompareFn, transparentSortCompareFn);
+        };
+        /**
+         * Specifies whether or not the stencil and depth buffer are cleared between two rendering groups.
+         *
+         * @param renderingGroupId The rendering group id corresponding to its index
+         * @param autoClearDepthStencil Automatically clears depth and stencil between groups if true.
+         */
+        RenderTargetTexture.prototype.setRenderingAutoClearDepthStencil = function (renderingGroupId, autoClearDepthStencil) {
+            this._renderingManager.setRenderingAutoClearDepthStencil(renderingGroupId, autoClearDepthStencil);
+        };
         RenderTargetTexture.prototype.clone = function () {
             var textureSize = this.getSize();
             var newTexture = new RenderTargetTexture(this.name, textureSize.width, this.getScene(), this._generateMipMaps);

+ 7 - 1
src/Materials/babylon.material.js

@@ -78,10 +78,15 @@ var BABYLON;
             */
             this.onDisposeObservable = new BABYLON.Observable();
             /**
-            * An event triggered when the material is compiled.
+            * An event triggered when the material is bound.
             * @type {BABYLON.Observable}
             */
             this.onBindObservable = new BABYLON.Observable();
+            /**
+            * An event triggered when the material is unbound.
+            * @type {BABYLON.Observable}
+            */
+            this.onUnBindObservable = new BABYLON.Observable();
             this.alphaMode = BABYLON.Engine.ALPHA_COMBINE;
             this.disableDepthWrite = false;
             this.fogEnabled = true;
@@ -241,6 +246,7 @@ var BABYLON;
         Material.prototype.bindOnlyWorldMatrix = function (world) {
         };
         Material.prototype.unbind = function () {
+            this.onUnBindObservable.notifyObservers(this);
             if (this.disableDepthWrite) {
                 var engine = this._scene.getEngine();
                 engine.setDepthWrite(this._cachedDepthWriteState);

+ 10 - 0
src/Math/babylon.math.js

@@ -396,6 +396,11 @@ var BABYLON;
             result.y = this.y + otherVector.y;
             return this;
         };
+        Vector2.prototype.addInPlace = function (otherVector) {
+            this.x += otherVector.x;
+            this.y += otherVector.y;
+            return this;
+        };
         Vector2.prototype.addVector3 = function (otherVector) {
             return new Vector2(this.x + otherVector.x, this.y + otherVector.y);
         };
@@ -566,6 +571,11 @@ var BABYLON;
             var y = value1.y - value2.y;
             return (x * x) + (y * y);
         };
+        Vector2.Center = function (value1, value2) {
+            var center = value1.add(value2);
+            center.scaleInPlace(0.5);
+            return center;
+        };
         Vector2.DistanceOfPointFromSegment = function (p, segA, segB) {
             var l2 = Vector2.DistanceSquared(segA, segB);
             if (l2 === 0.0) {

+ 12 - 4
src/PostProcess/babylon.postProcess.js

@@ -147,9 +147,17 @@ var BABYLON;
                 }
                 this.width = desiredWidth;
                 this.height = desiredHeight;
-                this._textures.push(this._engine.createRenderTargetTexture({ width: this.width, height: this.height }, { generateMipMaps: false, generateDepthBuffer: camera._postProcesses.indexOf(this) === 0, samplingMode: this.renderTargetSamplingMode, type: this._textureType }));
+                var textureSize = { width: this.width, height: this.height };
+                var textureOptions = {
+                    generateMipMaps: false,
+                    generateDepthBuffer: camera._postProcesses.indexOf(this) === 0,
+                    generateStencilBuffer: camera._postProcesses.indexOf(this) === 0 && this._engine.isStencilEnable,
+                    samplingMode: this.renderTargetSamplingMode,
+                    type: this._textureType
+                };
+                this._textures.push(this._engine.createRenderTargetTexture(textureSize, textureOptions));
                 if (this._reusable) {
-                    this._textures.push(this._engine.createRenderTargetTexture({ width: this.width, height: this.height }, { generateMipMaps: false, generateDepthBuffer: camera._postProcesses.indexOf(this) === 0, samplingMode: this.renderTargetSamplingMode, type: this._textureType }));
+                    this._textures.push(this._engine.createRenderTargetTexture(textureSize, textureOptions));
                 }
                 this.onSizeChangedObservable.notifyObservers(this);
             }
@@ -164,10 +172,10 @@ var BABYLON;
             this.onActivateObservable.notifyObservers(camera);
             // Clear
             if (this.clearColor) {
-                this._engine.clear(this.clearColor, true, true);
+                this._engine.clear(this.clearColor, true, true, true);
             }
             else {
-                this._engine.clear(scene.clearColor, scene.autoClear || scene.forceWireframe, true);
+                this._engine.clear(scene.clearColor, scene.autoClear || scene.forceWireframe, true, true);
             }
             if (this._reusable) {
                 this._currentRenderTextureInd = (this._currentRenderTextureInd + 1) % 2;

+ 1 - 1
src/Rendering/babylon.depthRenderer.js

@@ -19,7 +19,7 @@ var BABYLON;
             this._depthMap.renderList = null;
             // set default depth value to 1.0 (far away)
             this._depthMap.onClearObservable.add(function (engine) {
-                engine.clear(new BABYLON.Color4(1.0, 1.0, 1.0, 1.0), true, true);
+                engine.clear(new BABYLON.Color4(1.0, 1.0, 1.0, 1.0), true, true, true);
             });
             // Custom render function
             var renderSubMesh = function (subMesh) {

+ 188 - 43
src/Rendering/babylon.renderingGroup.js

@@ -1,13 +1,82 @@
 var BABYLON;
 (function (BABYLON) {
     var RenderingGroup = (function () {
-        function RenderingGroup(index, scene) {
+        /**
+         * Creates a new rendering group.
+         * @param index The rendering group index
+         * @param opaqueSortCompareFn The opaque sort comparison function. If null no order is applied
+         * @param alphaTestSortCompareFn The alpha test sort comparison function. If null no order is applied
+         * @param transparentSortCompareFn The transparent sort comparison function. If null back to front + alpha index sort is applied
+         */
+        function RenderingGroup(index, scene, opaqueSortCompareFn, alphaTestSortCompareFn, transparentSortCompareFn) {
+            if (opaqueSortCompareFn === void 0) { opaqueSortCompareFn = null; }
+            if (alphaTestSortCompareFn === void 0) { alphaTestSortCompareFn = null; }
+            if (transparentSortCompareFn === void 0) { transparentSortCompareFn = null; }
             this.index = index;
             this._opaqueSubMeshes = new BABYLON.SmartArray(256);
             this._transparentSubMeshes = new BABYLON.SmartArray(256);
             this._alphaTestSubMeshes = new BABYLON.SmartArray(256);
             this._scene = scene;
+            this.opaqueSortCompareFn = opaqueSortCompareFn;
+            this.alphaTestSortCompareFn = alphaTestSortCompareFn;
+            this.transparentSortCompareFn = transparentSortCompareFn;
         }
+        Object.defineProperty(RenderingGroup.prototype, "opaqueSortCompareFn", {
+            /**
+             * Set the opaque sort comparison function.
+             * If null the sub meshes will be render in the order they were created
+             */
+            set: function (value) {
+                this._opaqueSortCompareFn = value;
+                if (value) {
+                    this._renderOpaque = this.renderOpaqueSorted;
+                }
+                else {
+                    this._renderOpaque = RenderingGroup.renderUnsorted;
+                }
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(RenderingGroup.prototype, "alphaTestSortCompareFn", {
+            /**
+             * Set the alpha test sort comparison function.
+             * If null the sub meshes will be render in the order they were created
+             */
+            set: function (value) {
+                this._alphaTestSortCompareFn = value;
+                if (value) {
+                    this._renderAlphaTest = this.renderAlphaTestSorted;
+                }
+                else {
+                    this._renderAlphaTest = RenderingGroup.renderUnsorted;
+                }
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(RenderingGroup.prototype, "transparentSortCompareFn", {
+            /**
+             * Set the transparent sort comparison function.
+             * If null the sub meshes will be render in the order they were created
+             */
+            set: function (value) {
+                if (value) {
+                    this._transparentSortCompareFn = value;
+                }
+                else {
+                    this._transparentSortCompareFn = RenderingGroup.defaultTransparentSortCompare;
+                }
+                this._renderTransparent = this.renderTransparentSorted;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        /**
+         * Render all the sub meshes contained in the group.
+         * @param customRenderFunction Used to override the default render behaviour of the group.
+         * @returns true if rendered some submeshes.
+         */
         RenderingGroup.prototype.render = function (customRenderFunction) {
             if (customRenderFunction) {
                 customRenderFunction(this._opaqueSubMeshes, this._alphaTestSubMeshes, this._transparentSubMeshes);
@@ -21,62 +90,138 @@ var BABYLON;
             }
             var engine = this._scene.getEngine();
             // Opaque
-            var subIndex;
-            var submesh;
-            for (subIndex = 0; subIndex < this._opaqueSubMeshes.length; subIndex++) {
-                submesh = this._opaqueSubMeshes.data[subIndex];
-                submesh.render(false);
-            }
+            this._renderOpaque(this._opaqueSubMeshes);
             // Alpha test
             engine.setAlphaTesting(true);
-            for (subIndex = 0; subIndex < this._alphaTestSubMeshes.length; subIndex++) {
-                submesh = this._alphaTestSubMeshes.data[subIndex];
-                submesh.render(false);
-            }
+            this._renderAlphaTest(this._alphaTestSubMeshes);
             engine.setAlphaTesting(false);
             if (this.onBeforeTransparentRendering) {
                 this.onBeforeTransparentRendering();
             }
             // Transparent
-            if (this._transparentSubMeshes.length) {
-                // Sorting
-                for (subIndex = 0; subIndex < this._transparentSubMeshes.length; subIndex++) {
-                    submesh = this._transparentSubMeshes.data[subIndex];
-                    submesh._alphaIndex = submesh.getMesh().alphaIndex;
-                    submesh._distanceToCamera = submesh.getBoundingInfo().boundingSphere.centerWorld.subtract(this._scene.activeCamera.globalPosition).length();
-                }
-                var sortedArray = this._transparentSubMeshes.data.slice(0, this._transparentSubMeshes.length);
-                sortedArray.sort(function (a, b) {
-                    // Alpha index first
-                    if (a._alphaIndex > b._alphaIndex) {
-                        return 1;
-                    }
-                    if (a._alphaIndex < b._alphaIndex) {
-                        return -1;
-                    }
-                    // Then distance to camera
-                    if (a._distanceToCamera < b._distanceToCamera) {
-                        return 1;
-                    }
-                    if (a._distanceToCamera > b._distanceToCamera) {
-                        return -1;
-                    }
-                    return 0;
-                });
-                // Rendering                
-                for (subIndex = 0; subIndex < sortedArray.length; subIndex++) {
-                    submesh = sortedArray[subIndex];
-                    submesh.render(true);
-                }
-                engine.setAlphaMode(BABYLON.Engine.ALPHA_DISABLE);
-            }
+            this._renderTransparent(this._transparentSubMeshes);
             return true;
         };
+        /**
+         * Renders the opaque submeshes in the order from the opaqueSortCompareFn.
+         * @param subMeshes The submeshes to render
+         */
+        RenderingGroup.prototype.renderOpaqueSorted = function (subMeshes) {
+            return RenderingGroup.renderSorted(subMeshes, this._opaqueSortCompareFn, this._scene.activeCamera.globalPosition, false);
+        };
+        /**
+         * Renders the opaque submeshes in the order from the alphatestSortCompareFn.
+         * @param subMeshes The submeshes to render
+         */
+        RenderingGroup.prototype.renderAlphaTestSorted = function (subMeshes) {
+            return RenderingGroup.renderSorted(subMeshes, this._alphaTestSortCompareFn, this._scene.activeCamera.globalPosition, false);
+        };
+        /**
+         * Renders the opaque submeshes in the order from the transparentSortCompareFn.
+         * @param subMeshes The submeshes to render
+         */
+        RenderingGroup.prototype.renderTransparentSorted = function (subMeshes) {
+            return RenderingGroup.renderSorted(subMeshes, this._transparentSortCompareFn, this._scene.activeCamera.globalPosition, true);
+        };
+        /**
+         * Renders the submeshes in a specified order.
+         * @param subMeshes The submeshes to sort before render
+         * @param sortCompareFn The comparison function use to sort
+         * @param cameraPosition The camera position use to preprocess the submeshes to help sorting
+         * @param transparent Specifies to activate blending if true
+         */
+        RenderingGroup.renderSorted = function (subMeshes, sortCompareFn, cameraPosition, transparent) {
+            var subIndex = 0;
+            var subMesh;
+            for (; subIndex < subMeshes.length; subIndex++) {
+                subMesh = subMeshes.data[subIndex];
+                subMesh._alphaIndex = subMesh.getMesh().alphaIndex;
+                subMesh._distanceToCamera = subMesh.getBoundingInfo().boundingSphere.centerWorld.subtract(cameraPosition).length();
+            }
+            var sortedArray = subMeshes.data.slice(0, subMeshes.length);
+            sortedArray.sort(sortCompareFn);
+            for (subIndex = 0; subIndex < sortedArray.length; subIndex++) {
+                subMesh = sortedArray[subIndex];
+                subMesh.render(transparent);
+            }
+        };
+        /**
+         * Renders the submeshes in the order they were dispatched (no sort applied).
+         * @param subMeshes The submeshes to render
+         */
+        RenderingGroup.renderUnsorted = function (subMeshes) {
+            for (var subIndex = 0; subIndex < subMeshes.length; subIndex++) {
+                var submesh = subMeshes.data[subIndex];
+                submesh.render(false);
+            }
+        };
+        /**
+         * Build in function which can be applied to ensure meshes of a special queue (opaque, alpha test, transparent)
+         * are rendered back to front if in the same alpha index.
+         *
+         * @param a The first submesh
+         * @param b The second submesh
+         * @returns The result of the comparison
+         */
+        RenderingGroup.defaultTransparentSortCompare = function (a, b) {
+            // Alpha index first
+            if (a._alphaIndex > b._alphaIndex) {
+                return 1;
+            }
+            if (a._alphaIndex < b._alphaIndex) {
+                return -1;
+            }
+            // Then distance to camera
+            return RenderingGroup.backToFrontSortCompare(a, b);
+        };
+        /**
+         * Build in function which can be applied to ensure meshes of a special queue (opaque, alpha test, transparent)
+         * are rendered back to front.
+         *
+         * @param a The first submesh
+         * @param b The second submesh
+         * @returns The result of the comparison
+         */
+        RenderingGroup.backToFrontSortCompare = function (a, b) {
+            // Then distance to camera
+            if (a._distanceToCamera < b._distanceToCamera) {
+                return 1;
+            }
+            if (a._distanceToCamera > b._distanceToCamera) {
+                return -1;
+            }
+            return 0;
+        };
+        /**
+         * Build in function which can be applied to ensure meshes of a special queue (opaque, alpha test, transparent)
+         * are rendered front to back (prevent overdraw).
+         *
+         * @param a The first submesh
+         * @param b The second submesh
+         * @returns The result of the comparison
+         */
+        RenderingGroup.frontToBackSortCompare = function (a, b) {
+            // Then distance to camera
+            if (a._distanceToCamera < b._distanceToCamera) {
+                return -1;
+            }
+            if (a._distanceToCamera > b._distanceToCamera) {
+                return 1;
+            }
+            return 0;
+        };
+        /**
+         * Resets the different lists of submeshes to prepare a new frame.
+         */
         RenderingGroup.prototype.prepare = function () {
             this._opaqueSubMeshes.reset();
             this._transparentSubMeshes.reset();
             this._alphaTestSubMeshes.reset();
         };
+        /**
+         * Inserts the submesh in its correct queue depending on its material.
+         * @param subMesh The submesh to dispatch
+         */
         RenderingGroup.prototype.dispatch = function (subMesh) {
             var material = subMesh.getMaterial();
             var mesh = subMesh.getMesh();

+ 61 - 12
src/Rendering/babylon.renderingManager.js

@@ -3,7 +3,14 @@ var BABYLON;
     var RenderingManager = (function () {
         function RenderingManager(scene) {
             this._renderingGroups = new Array();
+            this._autoClearDepthStencil = {};
+            this._customOpaqueSortCompareFn = {};
+            this._customAlphaTestSortCompareFn = {};
+            this._customTransparentSortCompareFn = {};
             this._scene = scene;
+            for (var i = RenderingManager.MIN_RENDERINGGROUPS; i < RenderingManager.MAX_RENDERINGGROUPS; i++) {
+                this._autoClearDepthStencil[i] = true;
+            }
         }
         RenderingManager.prototype._renderParticles = function (index, activeMeshes) {
             if (this._scene._activeParticleSystems.length === 0) {
@@ -20,7 +27,7 @@ var BABYLON;
                 if ((activeCamera.layerMask & particleSystem.layerMask) === 0) {
                     continue;
                 }
-                this._clearDepthBuffer();
+                this._clearDepthStencilBuffer();
                 if (!particleSystem.emitter.position || !activeMeshes || activeMeshes.indexOf(particleSystem.emitter) !== -1) {
                     this._scene._activeParticles.addCount(particleSystem.render(), false);
                 }
@@ -37,18 +44,18 @@ var BABYLON;
             for (var id = 0; id < this._scene.spriteManagers.length; id++) {
                 var spriteManager = this._scene.spriteManagers[id];
                 if (spriteManager.renderingGroupId === index && ((activeCamera.layerMask & spriteManager.layerMask) !== 0)) {
-                    this._clearDepthBuffer();
+                    this._clearDepthStencilBuffer();
                     spriteManager.render();
                 }
             }
             this._scene._spritesDuration.endMonitoring(false);
         };
-        RenderingManager.prototype._clearDepthBuffer = function () {
-            if (this._depthBufferAlreadyCleaned) {
+        RenderingManager.prototype._clearDepthStencilBuffer = function () {
+            if (this._depthStencilBufferAlreadyCleaned) {
                 return;
             }
-            this._scene.getEngine().clear(0, false, true);
-            this._depthBufferAlreadyCleaned = true;
+            this._scene.getEngine().clear(0, false, true, true);
+            this._depthStencilBufferAlreadyCleaned = true;
         };
         RenderingManager.prototype._renderSpritesAndParticles = function () {
             if (this._currentRenderSprites) {
@@ -62,13 +69,15 @@ var BABYLON;
             this._currentActiveMeshes = activeMeshes;
             this._currentRenderParticles = renderParticles;
             this._currentRenderSprites = renderSprites;
-            for (var index = 0; index < RenderingManager.MAX_RENDERINGGROUPS; index++) {
-                this._depthBufferAlreadyCleaned = index === 0;
+            for (var index = RenderingManager.MIN_RENDERINGGROUPS; index < RenderingManager.MAX_RENDERINGGROUPS; index++) {
+                this._depthStencilBufferAlreadyCleaned = index === RenderingManager.MIN_RENDERINGGROUPS;
                 var renderingGroup = this._renderingGroups[index];
                 var needToStepBack = false;
                 this._currentIndex = index;
                 if (renderingGroup) {
-                    this._clearDepthBuffer();
+                    if (this._autoClearDepthStencil[index]) {
+                        this._clearDepthStencilBuffer();
+                    }
                     if (!renderingGroup.onBeforeTransparentRendering) {
                         renderingGroup.onBeforeTransparentRendering = this._renderSpritesAndParticles.bind(this);
                     }
@@ -87,21 +96,61 @@ var BABYLON;
             }
         };
         RenderingManager.prototype.reset = function () {
-            this._renderingGroups.forEach(function (renderingGroup, index, array) {
+            for (var index = RenderingManager.MIN_RENDERINGGROUPS; index < RenderingManager.MAX_RENDERINGGROUPS; index++) {
+                var renderingGroup = this._renderingGroups[index];
                 if (renderingGroup) {
                     renderingGroup.prepare();
                 }
-            });
+            }
         };
         RenderingManager.prototype.dispatch = function (subMesh) {
             var mesh = subMesh.getMesh();
             var renderingGroupId = mesh.renderingGroupId || 0;
             if (!this._renderingGroups[renderingGroupId]) {
-                this._renderingGroups[renderingGroupId] = new BABYLON.RenderingGroup(renderingGroupId, this._scene);
+                this._renderingGroups[renderingGroupId] = new BABYLON.RenderingGroup(renderingGroupId, this._scene, this._customOpaqueSortCompareFn[renderingGroupId], this._customAlphaTestSortCompareFn[renderingGroupId], this._customTransparentSortCompareFn[renderingGroupId]);
             }
             this._renderingGroups[renderingGroupId].dispatch(subMesh);
         };
+        /**
+         * Overrides the default sort function applied in the renderging group to prepare the meshes.
+         * This allowed control for front to back rendering or reversly depending of the special needs.
+         *
+         * @param renderingGroupId The rendering group id corresponding to its index
+         * @param opaqueSortCompareFn The opaque queue comparison function use to sort.
+         * @param alphaTestSortCompareFn The alpha test queue comparison function use to sort.
+         * @param transparentSortCompareFn The transparent queue comparison function use to sort.
+         */
+        RenderingManager.prototype.setRenderingOrder = function (renderingGroupId, opaqueSortCompareFn, alphaTestSortCompareFn, transparentSortCompareFn) {
+            if (opaqueSortCompareFn === void 0) { opaqueSortCompareFn = null; }
+            if (alphaTestSortCompareFn === void 0) { alphaTestSortCompareFn = null; }
+            if (transparentSortCompareFn === void 0) { transparentSortCompareFn = null; }
+            if (this._renderingGroups[renderingGroupId]) {
+                var group = this._renderingGroups[renderingGroupId];
+                group.opaqueSortCompareFn = this._customOpaqueSortCompareFn[renderingGroupId];
+                group.alphaTestSortCompareFn = this._customAlphaTestSortCompareFn[renderingGroupId];
+                group.transparentSortCompareFn = this._customTransparentSortCompareFn[renderingGroupId];
+            }
+            this._customOpaqueSortCompareFn[renderingGroupId] = opaqueSortCompareFn;
+            this._customAlphaTestSortCompareFn[renderingGroupId] = alphaTestSortCompareFn;
+            this._customTransparentSortCompareFn[renderingGroupId] = transparentSortCompareFn;
+        };
+        /**
+         * Specifies whether or not the stencil and depth buffer are cleared between two rendering groups.
+         *
+         * @param renderingGroupId The rendering group id corresponding to its index
+         * @param autoClearDepthStencil Automatically clears depth and stencil between groups if true.
+         */
+        RenderingManager.prototype.setRenderingAutoClearDepthStencil = function (renderingGroupId, autoClearDepthStencil) {
+            this._autoClearDepthStencil[renderingGroupId] = autoClearDepthStencil;
+        };
+        /**
+         * The max id used for rendering groups (not included)
+         */
         RenderingManager.MAX_RENDERINGGROUPS = 4;
+        /**
+         * The min id used for rendering groups (included)
+         */
+        RenderingManager.MIN_RENDERINGGROUPS = 0;
         return RenderingManager;
     })();
     BABYLON.RenderingManager = RenderingManager;

+ 1 - 1
src/States/babylon.stencilState.js

@@ -174,7 +174,7 @@ var BABYLON;
                 }
             };
             return _StencilState;
-        }());
+        })();
         Internals._StencilState = _StencilState;
     })(Internals = BABYLON.Internals || (BABYLON.Internals = {}));
 })(BABYLON || (BABYLON = {}));

+ 2 - 1
src/Tools/babylon.tools.js

@@ -325,7 +325,8 @@ var BABYLON;
                 request.onprogress = progressCallBack;
                 request.onreadystatechange = function () {
                     if (request.readyState === 4) {
-                        if (request.status === 200 || Tools.ValidateXHRData(request, !useArrayBuffer ? 1 : 6)) {
+                        request.onreadystatechange = null; //some browsers have issues where onreadystatechange can be called multiple times with the same value
+                        if (request.status >= 200 && request.status < 300 || (navigator.isCocoonJS && (request.status === 0))) {
                             callback(!useArrayBuffer ? request.responseText : request.response);
                         }
                         else {

+ 122 - 28
src/babylon.engine.js

@@ -149,6 +149,7 @@ var BABYLON;
             this.deltaTime = 0;
             // States
             this._depthCullingState = new BABYLON.Internals._DepthCullingState();
+            this._stencilState = new BABYLON.Internals._StencilState();
             this._alphaState = new BABYLON.Internals._AlphaState();
             this._alphaMode = Engine.ALPHA_DISABLE;
             // Cache
@@ -206,6 +207,7 @@ var BABYLON;
             this._hardwareScalingLevel = adaptToDeviceRatio ? 1.0 / Math.min(limitDeviceRatio, window.devicePixelRatio || 1.0) : 1.0;
             this.resize();
             // Caps
+            this._isStencilEnable = options.stencil;
             this._caps = new EngineCapabilities();
             this._caps.maxTexturesImageUnits = this._gl.getParameter(this._gl.MAX_TEXTURE_IMAGE_UNITS);
             this._caps.maxTextureSize = this._gl.getParameter(this._gl.MAX_TEXTURE_SIZE);
@@ -443,6 +445,16 @@ var BABYLON;
             enumerable: true,
             configurable: true
         });
+        Object.defineProperty(Engine.prototype, "isStencilEnable", {
+            /**
+             * Returns true if the stencil buffer has been enabled through the creation option of the context.
+             */
+            get: function () {
+                return this._isStencilEnable;
+            },
+            enumerable: true,
+            configurable: true
+        });
         Engine.prototype._prepareWorkingCanvas = function () {
             if (this._workingCanvas) {
                 return;
@@ -532,6 +544,54 @@ var BABYLON;
         Engine.prototype.setDepthFunctionToLessOrEqual = function () {
             this._depthCullingState.depthFunc = this._gl.LEQUAL;
         };
+        Engine.prototype.getStencilBuffer = function () {
+            return this._stencilState.stencilTest;
+        };
+        Engine.prototype.setStencilBuffer = function (enable) {
+            this._stencilState.stencilTest = enable;
+        };
+        Engine.prototype.getStencilMask = function () {
+            return this._stencilState.stencilMask;
+        };
+        Engine.prototype.setStencilMask = function (mask) {
+            this._stencilState.stencilMask = mask;
+        };
+        Engine.prototype.getStencilFunction = function () {
+            return this._stencilState.stencilFunc;
+        };
+        Engine.prototype.getStencilFunctionReference = function () {
+            return this._stencilState.stencilFuncRef;
+        };
+        Engine.prototype.getStencilFunctionMask = function () {
+            return this._stencilState.stencilFuncMask;
+        };
+        Engine.prototype.setStencilFunction = function (stencilFunc) {
+            this._stencilState.stencilFunc = stencilFunc;
+        };
+        Engine.prototype.setStencilFunctionReference = function (reference) {
+            this._stencilState.stencilFuncRef = reference;
+        };
+        Engine.prototype.setStencilFunctionMask = function (mask) {
+            this._stencilState.stencilFuncMask = mask;
+        };
+        Engine.prototype.getStencilOperationFail = function () {
+            return this._stencilState.stencilOpStencilFail;
+        };
+        Engine.prototype.getStencilOperationDepthFail = function () {
+            return this._stencilState.stencilOpDepthFail;
+        };
+        Engine.prototype.getStencilOperationPass = function () {
+            return this._stencilState.stencilOpStencilDepthPass;
+        };
+        Engine.prototype.setStencilOperationFail = function (operation) {
+            this._stencilState.stencilOpStencilFail = operation;
+        };
+        Engine.prototype.setStencilOperationDepthFail = function (operation) {
+            this._stencilState.stencilOpDepthFail = operation;
+        };
+        Engine.prototype.setStencilOperationPass = function (operation) {
+            this._stencilState.stencilOpStencilDepthPass = operation;
+        };
         /**
          * stop executing a render loop function and remove it from the execution array
          * @param {Function} [renderFunction] the function to be removed. If not provided all functions will be removed.
@@ -602,21 +662,22 @@ var BABYLON;
                 BABYLON.Tools.RequestFullscreen(this._renderingCanvas, options);
             }
         };
-        Engine.prototype.clear = function (color, backBuffer, depthStencil) {
+        Engine.prototype.clear = function (color, backBuffer, depth, stencil) {
+            if (stencil === void 0) { stencil = false; }
             this.applyStates();
-            if (backBuffer) {
-                this._gl.clearColor(color.r, color.g, color.b, color.a !== undefined ? color.a : 1.0);
-            }
-            if (depthStencil && this._depthCullingState.depthMask) {
-                this._gl.clearDepth(1.0);
-            }
             var mode = 0;
             if (backBuffer) {
+                this._gl.clearColor(color.r, color.g, color.b, color.a !== undefined ? color.a : 1.0);
                 mode |= this._gl.COLOR_BUFFER_BIT;
             }
-            if (depthStencil && this._depthCullingState.depthMask) {
+            if (depth) {
+                this._gl.clearDepth(1.0);
                 mode |= this._gl.DEPTH_BUFFER_BIT;
             }
+            if (stencil) {
+                this._gl.clearStencil(0);
+                mode |= this._gl.STENCIL_BUFFER_BIT;
+            }
             this._gl.clear(mode);
         };
         Engine.prototype.scissorClear = function (x, y, width, height, clearColor) {
@@ -628,7 +689,7 @@ var BABYLON;
             gl.enable(gl.SCISSOR_TEST);
             gl.scissor(x, y, width, height);
             // Clear
-            this.clear(clearColor, true, true);
+            this.clear(clearColor, true, true, true);
             // Restore state
             gl.scissor(curScissorBox[0], curScissorBox[1], curScissorBox[2], curScissorBox[3]);
             if (curScissor === true) {
@@ -705,15 +766,12 @@ var BABYLON;
         };
         Engine.prototype.bindFramebuffer = function (texture, faceIndex, requiredWidth, requiredHeight) {
             this._currentRenderTarget = texture;
-            var gl = this._gl;
             this.bindUnboundFramebuffer(texture._framebuffer);
+            var gl = this._gl;
             if (texture.isCube) {
                 gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex, texture, 0);
             }
-            else {
-                gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
-            }
-            this._gl.viewport(0, 0, requiredWidth || texture._width, requiredHeight || texture._height);
+            gl.viewport(0, 0, requiredWidth || texture._width, requiredHeight || texture._height);
             this.wipeCaches();
         };
         Engine.prototype.bindUnboundFramebuffer = function (framebuffer) {
@@ -997,6 +1055,7 @@ var BABYLON;
         };
         Engine.prototype.applyStates = function () {
             this._depthCullingState.apply(this._gl);
+            this._stencilState.apply(this._gl);
             this._alphaState.apply(this._gl);
         };
         Engine.prototype.draw = function (useTriangles, indexStart, indexCount, instancesCount) {
@@ -1340,6 +1399,7 @@ var BABYLON;
         Engine.prototype.wipeCaches = function () {
             this.resetTextureCache();
             this._currentEffect = null;
+            this._stencilState.reset();
             this._depthCullingState.reset();
             this.setDepthFunctionToLessOrEqual();
             this._alphaState.reset();
@@ -1624,11 +1684,13 @@ var BABYLON;
             // in the same way, generateDepthBuffer is defaulted to true
             var generateMipMaps = false;
             var generateDepthBuffer = true;
+            var generateStencilBuffer = false;
             var type = Engine.TEXTURETYPE_UNSIGNED_INT;
             var samplingMode = BABYLON.Texture.TRILINEAR_SAMPLINGMODE;
             if (options !== undefined) {
                 generateMipMaps = options.generateMipMaps === undefined ? options : options.generateMipMaps;
                 generateDepthBuffer = options.generateDepthBuffer === undefined ? true : options.generateDepthBuffer;
+                generateStencilBuffer = generateDepthBuffer && options.generateStencilBuffer;
                 type = options.type === undefined ? type : options.type;
                 if (options.samplingMode !== undefined) {
                     samplingMode = options.samplingMode;
@@ -1657,19 +1719,29 @@ var BABYLON;
             gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
             gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
             gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, getWebGLTextureType(gl, type), null);
-            var depthBuffer;
-            // Create the depth buffer
-            if (generateDepthBuffer) {
-                depthBuffer = gl.createRenderbuffer();
-                gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
+            var depthStencilBuffer;
+            // Create the depth/stencil buffer
+            if (generateStencilBuffer) {
+                depthStencilBuffer = gl.createRenderbuffer();
+                gl.bindRenderbuffer(gl.RENDERBUFFER, depthStencilBuffer);
+                gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, width, height);
+            }
+            else if (generateDepthBuffer) {
+                depthStencilBuffer = gl.createRenderbuffer();
+                gl.bindRenderbuffer(gl.RENDERBUFFER, depthStencilBuffer);
                 gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height);
             }
             // Create the framebuffer
             var framebuffer = gl.createFramebuffer();
             this.bindUnboundFramebuffer(framebuffer);
-            if (generateDepthBuffer) {
-                gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer);
+            // Manage attachments
+            if (generateStencilBuffer) {
+                gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, depthStencilBuffer);
             }
+            else if (generateDepthBuffer) {
+                gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthStencilBuffer);
+            }
+            gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
             if (generateMipMaps) {
                 this._gl.generateMipmap(this._gl.TEXTURE_2D);
             }
@@ -1679,7 +1751,7 @@ var BABYLON;
             this.bindUnboundFramebuffer(null);
             texture._framebuffer = framebuffer;
             if (generateDepthBuffer) {
-                texture._depthBuffer = depthBuffer;
+                texture._depthBuffer = depthStencilBuffer;
             }
             texture._baseWidth = width;
             texture._baseHeight = height;
@@ -1698,9 +1770,13 @@ var BABYLON;
             var gl = this._gl;
             var texture = gl.createTexture();
             var generateMipMaps = true;
+            var generateDepthBuffer = true;
+            var generateStencilBuffer = false;
             var samplingMode = BABYLON.Texture.TRILINEAR_SAMPLINGMODE;
             if (options !== undefined) {
                 generateMipMaps = options.generateMipMaps === undefined ? options : options.generateMipMaps;
+                generateDepthBuffer = options.generateDepthBuffer === undefined ? true : options.generateDepthBuffer;
+                generateStencilBuffer = generateDepthBuffer && options.generateStencilBuffer;
                 if (options.samplingMode !== undefined) {
                     samplingMode = options.samplingMode;
                 }
@@ -1720,13 +1796,28 @@ var BABYLON;
             gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
             gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
             // Create the depth buffer
-            var depthBuffer = gl.createRenderbuffer();
-            gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
-            gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, size, size);
+            var depthStencilBuffer;
+            // Create the depth/stencil buffer
+            if (generateStencilBuffer) {
+                depthStencilBuffer = gl.createRenderbuffer();
+                gl.bindRenderbuffer(gl.RENDERBUFFER, depthStencilBuffer);
+                gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, size, size);
+            }
+            else if (generateDepthBuffer) {
+                depthStencilBuffer = gl.createRenderbuffer();
+                gl.bindRenderbuffer(gl.RENDERBUFFER, depthStencilBuffer);
+                gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, size, size);
+            }
             // Create the framebuffer
             var framebuffer = gl.createFramebuffer();
             this.bindUnboundFramebuffer(framebuffer);
-            gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer);
+            // Manage attachments
+            if (generateStencilBuffer) {
+                gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, depthStencilBuffer);
+            }
+            else if (generateDepthBuffer) {
+                gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthStencilBuffer);
+            }
             // Mipmaps
             if (texture.generateMipMaps) {
                 this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture);
@@ -1737,11 +1828,14 @@ var BABYLON;
             gl.bindRenderbuffer(gl.RENDERBUFFER, null);
             this.bindUnboundFramebuffer(null);
             texture._framebuffer = framebuffer;
-            texture._depthBuffer = depthBuffer;
-            this.resetTextureCache();
+            if (generateDepthBuffer) {
+                texture._depthBuffer = depthStencilBuffer;
+            }
             texture._width = size;
             texture._height = size;
             texture.isReady = true;
+            this.resetTextureCache();
+            this._loadedTexturesCache.push(texture);
             return texture;
         };
         Engine.prototype.createCubeTexture = function (rootUrl, scene, files, noMipmap) {

+ 26 - 2
src/babylon.scene.js

@@ -1881,7 +1881,7 @@ var BABYLON;
                 BABYLON.Tools.EndPerformanceCounter("Procedural textures", this._proceduralTextures.length > 0);
             }
             // Clear
-            this._engine.clear(this.clearColor, this.autoClear || this.forceWireframe || this.forcePointsCloud, true);
+            this._engine.clear(this.clearColor, this.autoClear || this.forceWireframe || this.forcePointsCloud, true, true);
             // Shadows
             if (this.shadowsEnabled) {
                 for (var lightIndex = 0; lightIndex < this.lights.length; lightIndex++) {
@@ -1904,7 +1904,7 @@ var BABYLON;
                 for (var cameraIndex = 0; cameraIndex < this.activeCameras.length; cameraIndex++) {
                     this._renderId = currentRenderId;
                     if (cameraIndex > 0) {
-                        this._engine.clear(0, false, true);
+                        this._engine.clear(0, false, true, true);
                     }
                     this._processSubCameras(this.activeCameras[cameraIndex]);
                 }
@@ -2444,6 +2444,30 @@ var BABYLON;
         Scene.prototype.getMaterialByTags = function (tagsQuery, forEach) {
             return this._getByTags(this.materials, tagsQuery, forEach).concat(this._getByTags(this.multiMaterials, tagsQuery, forEach));
         };
+        /**
+         * Overrides the default sort function applied in the renderging group to prepare the meshes.
+         * This allowed control for front to back rendering or reversly depending of the special needs.
+         *
+         * @param renderingGroupId The rendering group id corresponding to its index
+         * @param opaqueSortCompareFn The opaque queue comparison function use to sort.
+         * @param alphaTestSortCompareFn The alpha test queue comparison function use to sort.
+         * @param transparentSortCompareFn The transparent queue comparison function use to sort.
+         */
+        Scene.prototype.setRenderingOrder = function (renderingGroupId, opaqueSortCompareFn, alphaTestSortCompareFn, transparentSortCompareFn) {
+            if (opaqueSortCompareFn === void 0) { opaqueSortCompareFn = null; }
+            if (alphaTestSortCompareFn === void 0) { alphaTestSortCompareFn = null; }
+            if (transparentSortCompareFn === void 0) { transparentSortCompareFn = null; }
+            this._renderingManager.setRenderingOrder(renderingGroupId, opaqueSortCompareFn, alphaTestSortCompareFn, transparentSortCompareFn);
+        };
+        /**
+         * Specifies whether or not the stencil and depth buffer are cleared between two rendering groups.
+         *
+         * @param renderingGroupId The rendering group id corresponding to its index
+         * @param autoClearDepthStencil Automatically clears depth and stencil between groups if true.
+         */
+        Scene.prototype.setRenderingAutoClearDepthStencil = function (renderingGroupId, autoClearDepthStencil) {
+            this._renderingManager.setRenderingAutoClearDepthStencil(renderingGroupId, autoClearDepthStencil);
+        };
         // Statics
         Scene._FOGMODE_NONE = 0;
         Scene._FOGMODE_EXP = 1;