浏览代码

Remove tools coupling with engine

David Catuhe 6 年之前
父节点
当前提交
9d83e4b1c7

+ 6 - 5
Viewer/src/configuration/renderOnlyLoader.ts

@@ -3,9 +3,10 @@ import { ViewerConfiguration } from './configuration';
 import { processConfigurationCompatibility } from './configurationCompatibility';
 
 import { deepmerge } from '../helper';
-import { IFileRequest, Tools } from 'babylonjs/Misc/tools';
+import { Tools } from 'babylonjs/Misc/tools';
 import { extendedConfiguration } from './types/extended';
 import { renderOnlyDefaultConfiguration } from './types/renderOnlyDefault';
+import { IFileRequest } from 'babylonjs/Misc/fileRequest';
 
 /**
  * The configuration loader will load the configuration object from any source and will use the defined mapper to
@@ -23,7 +24,7 @@ export class RenderOnlyConfigurationLoader {
         this._loadRequests = [];
     }
 
-    private _getConfigurationTypeExcludeTemplate (types: string): ViewerConfiguration {
+    private _getConfigurationTypeExcludeTemplate(types: string): ViewerConfiguration {
         let config: ViewerConfiguration = {};
         let typesSeparated = types.split(",");
         typesSeparated.forEach((type) => {
@@ -45,7 +46,7 @@ export class RenderOnlyConfigurationLoader {
         return config;
     };
 
-    protected getExtendedConfig(type:string|undefined){
+    protected getExtendedConfig(type: string | undefined) {
         return this._getConfigurationTypeExcludeTemplate(type || "extended");
     }
 
@@ -61,7 +62,7 @@ export class RenderOnlyConfigurationLoader {
 
         let loadedConfig: ViewerConfiguration = deepmerge({}, initConfig);
         this._processInitialConfiguration(loadedConfig);
-        
+
         let extendedConfiguration = this.getExtendedConfig(loadedConfig.extends);
 
         if (loadedConfig.configuration) {
@@ -160,5 +161,5 @@ export class RenderOnlyConfigurationLoader {
             this._loadRequests.push(fileRequest);
         });
     }
-    
+
 }

+ 2 - 1
Viewer/src/templating/templateManager.ts

@@ -1,11 +1,12 @@
 import { Observable } from 'babylonjs/Misc/observable';
-import { IFileRequest, Tools } from 'babylonjs/Misc/tools';
+import { Tools } from 'babylonjs/Misc/tools';
 import { isUrl, camelToKebab, kebabToCamel } from '../helper';
 
 import * as Handlebars from 'handlebars/dist/handlebars';
 import { EventManager } from './eventManager';
 import { ITemplateConfiguration } from '../configuration/interfaces';
 import { deepmerge } from '../helper/';
+import { IFileRequest } from 'babylonjs/Misc/fileRequest';
 
 /**
  * The object sent when an event is triggered

+ 1 - 1
dist/preview release/packagesSizeBaseLine.json

@@ -1 +1 @@
-{"engineOnly":252171,"sceneOnly":510337,"minGridMaterial":639247,"minStandardMaterial":765316}
+{"engineOnly":166401,"sceneOnly":510917,"minGridMaterial":639821,"minStandardMaterial":765892}

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

@@ -100,3 +100,4 @@
 ## Breaking changes
 - Setting mesh.scaling to a new vector will no longer automatically call forceUpdate (this should be done manually when needed) ([TrevorDev](https://github.com/TrevorDev))
 - `Tools.ExtractMinAndMaxIndexed` and `Tools.ExtractMinAndMax` are now ambiant functions (available on `BABYLON.extractMinAndMaxIndexed` and `BABYLON.extractMinAndMax`) ([Deltakosh](https://github.com/deltakosh/))
+- `Tools.QueueNewFrame` was removed in favor of `Engine.QueueNewFrame` ([Deltakosh](https://github.com/deltakosh/))

+ 4 - 3
loaders/src/glTF/2.0/glTFLoader.ts

@@ -1,7 +1,9 @@
 import { IndicesArray, Nullable } from "babylonjs/types";
 import { Deferred } from "babylonjs/Misc/deferred";
 import { Quaternion, Color3, Vector3, Matrix } from "babylonjs/Maths/math";
-import { LoadFileError, IFileRequest, IAnimatable, Tools } from "babylonjs/Misc/tools";
+import { IAnimatable, Tools } from "babylonjs/Misc/tools";
+import { IFileRequest } from "babylonjs/Misc/fileRequest";
+import { LoadFileError } from "babylonjs/Misc/loadFileError";
 import { Camera } from "babylonjs/Cameras/camera";
 import { FreeCamera } from "babylonjs/Cameras/freeCamera";
 import { AnimationGroup } from "babylonjs/Animations/animationGroup";
@@ -1787,8 +1789,7 @@ export class GLTFLoader implements IGLTFLoader {
         const texture = ArrayItem.Get(`${context}/index`, this._gltf.textures, textureInfo.index);
         const promise = this._loadTextureAsync(`/textures/${textureInfo.index}`, texture, (babylonTexture) => {
             babylonTexture.coordinatesIndex = textureInfo.texCoord || 0;
-            if (texture.name)
-            {
+            if (texture.name) {
                 babylonTexture.name = texture.name;
             }
 

+ 3 - 3
src/Engines/Extensions/engine.cubeTexture.ts

@@ -5,7 +5,7 @@ import { Nullable } from '../../types';
 import { Scene } from '../../scene';
 import { IInternalTextureLoader } from '../../Materials/Textures/internalTextureLoader';
 import { WebRequest } from '../../Misc/webRequest';
-import { Tools } from '../../Misc/tools';
+import { FileTools } from '../../Misc/fileTools';
 
 declare module "../../Engines/engine" {
     export interface Engine {
@@ -191,7 +191,7 @@ Engine.prototype._partialLoadImg = function(url: string, index: number, loadedIm
         }
     };
 
-    img = Tools.LoadImage(url, onload, onerror, scene ? scene.offlineProvider : null);
+    img = FileTools.LoadImage(url, onload, onerror, scene ? scene.offlineProvider : null);
     if (scene) {
         scene._addPendingData(img);
     }
@@ -268,7 +268,7 @@ Engine.prototype.createCubeTexture = function(rootUrl: string, scene: Nullable<S
         }
 
         this._cascadeLoadImgs(scene, (imgs) => {
-            var width = this.needPOTTextures ? Tools.GetExponentOfTwo(imgs[0].width, this._caps.maxCubemapTextureSize) : imgs[0].width;
+            var width = this.needPOTTextures ? Engine.GetExponentOfTwo(imgs[0].width, this._caps.maxCubemapTextureSize) : imgs[0].width;
             var height = width;
 
             this._prepareWorkingCanvas();

+ 2 - 2
src/Engines/Extensions/engine.webVR.ts

@@ -133,7 +133,7 @@ Engine.prototype.initWebVRAsync = function(): Promise<IDisplayChangedEventArgs>
         this._onVrDisplayDisconnect = () => {
             this._vrDisplay.cancelAnimationFrame(this._frameHandler);
             this._vrDisplay = undefined;
-            this._frameHandler = Tools.QueueNewFrame(this._bindedRenderFunction);
+            this._frameHandler = Engine.QueueNewFrame(this._bindedRenderFunction);
             notifyObservers();
         };
         this._onVrDisplayPresentChange = () => {
@@ -266,5 +266,5 @@ Engine.prototype.isVRPresenting = function() {
 };
 
 Engine.prototype._requestVRFrame = function() {
-    this._frameHandler = Tools.QueueNewFrame(this._bindedRenderFunction, this._vrDisplay);
+    this._frameHandler = Engine.QueueNewFrame(this._bindedRenderFunction, this._vrDisplay);
 };

+ 4 - 4
src/Engines/Processors/shaderCodeNode.ts

@@ -1,5 +1,5 @@
 import { ProcessingOptions } from './shaderProcessingOptions';
-import { Tools } from '../../Misc/tools';
+import { StringTools } from '../../Misc/stringTools';
 
 /** @hidden */
 export class ShaderCodeNode {
@@ -18,11 +18,11 @@ export class ShaderCodeNode {
             let value: string = this.line;
             let processor = options.processor;
             if (processor) {
-                if (processor.attributeProcessor && Tools.StartsWith(this.line, "attribute")) {
+                if (processor.attributeProcessor && StringTools.StartsWith(this.line, "attribute")) {
                     value = processor.attributeProcessor(this.line);
-                } else if (processor.varyingProcessor && Tools.StartsWith(this.line, "varying")) {
+                } else if (processor.varyingProcessor && StringTools.StartsWith(this.line, "varying")) {
                     value = processor.varyingProcessor(this.line, options.isFragment);
-                } else if ((processor.uniformProcessor || processor.uniformBufferProcessor) && Tools.StartsWith(this.line, "uniform")) {
+                } else if ((processor.uniformProcessor || processor.uniformBufferProcessor) && StringTools.StartsWith(this.line, "uniform")) {
                     let regex = /uniform (.+) (.+)/;
 
                     if (regex.test(this.line)) { // uniform

+ 2 - 2
src/Engines/Processors/shaderProcessor.ts

@@ -1,4 +1,3 @@
-import { Tools } from '../../Misc/tools';
 import { ShaderCodeNode } from './shaderCodeNode';
 import { ShaderCodeCursor } from './shaderCodeCursor';
 import { ShaderCodeConditionNode } from './shaderCodeConditionNode';
@@ -9,6 +8,7 @@ import { ShaderDefineAndOperator } from './Expressions/Operators/shaderDefineAnd
 import { ShaderDefineExpression } from './Expressions/shaderDefineExpression';
 import { ShaderDefineArithmeticOperator } from './Expressions/Operators/shaderDefineArithmeticOperator';
 import { ProcessingOptions } from './shaderProcessingOptions';
+import { FileTools } from '../../Misc/fileTools';
 
 /** @hidden */
 export class ShaderProcessor {
@@ -331,7 +331,7 @@ export class ShaderProcessor {
             } else {
                 var includeShaderUrl = options.shadersRepository + "ShadersInclude/" + includeFile + ".fx";
 
-                Tools.LoadFile(includeShaderUrl, (fileContent) => {
+                FileTools.LoadFile(includeShaderUrl, (fileContent) => {
                     options.includesShadersStore[includeFile] = fileContent as string;
                     this._ProcessIncludes(<string>returnValue, options, callback);
                 });

+ 175 - 23
src/Engines/engine.ts

@@ -1,8 +1,6 @@
 import { Observer, Observable } from "../Misc/observable";
 import { PerformanceMonitor } from "../Misc/performanceMonitor";
 import { StringDictionary } from "../Misc/stringDictionary";
-import { PromisePolyfill } from "../Misc/promise";
-import { Tools, ICustomAnimationFrameRequester, IFileRequest } from "../Misc/tools";
 import { Nullable, FloatArray, DataArray, IndicesArray, float } from "../types";
 import { Scene } from "../scene";
 import { VertexBuffer } from "../Meshes/buffer";
@@ -30,6 +28,9 @@ import { WebGLDataBuffer } from '../Meshes/WebGL/webGLDataBuffer';
 import { IShaderProcessor } from './Processors/iShaderProcessor';
 import { WebGL2ShaderProcessor } from './WebGL/webGL2ShaderProcessors';
 import { PerfCounter } from '../Misc/perfCounter';
+import { IFileRequest } from '../Misc/fileRequest';
+import { ICustomAnimationFrameRequester } from '../Misc/customAnimationFrameRequester';
+import { FileTools } from '../Misc/fileTools';
 
 declare type Material = import("../Materials/material").Material;
 declare type PostProcess = import("../PostProcesses/postProcess").PostProcess;
@@ -996,9 +997,6 @@ export class Engine {
      */
     constructor(canvasOrContext: Nullable<HTMLCanvasElement | WebGLRenderingContext>, antialias?: boolean, options?: EngineOptions, adaptToDeviceRatio: boolean = false) {
 
-        // Register promises
-        PromisePolyfill.Apply();
-
         let canvas: Nullable<HTMLCanvasElement> = null;
         Engine.Instances.push(this);
 
@@ -1238,7 +1236,7 @@ export class Engine {
 
                 // Pointer lock
                 if (this.isFullscreen && this._pointerLockRequested && canvas) {
-                    Tools.RequestPointerlock(canvas);
+                    Engine._RequestPointerlock(canvas);
                 }
             };
 
@@ -2038,12 +2036,12 @@ export class Engine {
         if (this._activeRenderLoops.length > 0) {
             // Register new frame
             if (this.customAnimationFrameRequester) {
-                this.customAnimationFrameRequester.requestID = Tools.QueueNewFrame(this.customAnimationFrameRequester.renderFunction || this._bindedRenderFunction, this.customAnimationFrameRequester);
+                this.customAnimationFrameRequester.requestID = Engine.QueueNewFrame(this.customAnimationFrameRequester.renderFunction || this._bindedRenderFunction, this.customAnimationFrameRequester);
                 this._frameHandler = this.customAnimationFrameRequester.requestID;
             } else if (this.isVRPresenting()) {
                 this._requestVRFrame();
             } else {
-                this._frameHandler = Tools.QueueNewFrame(this._bindedRenderFunction);
+                this._frameHandler = Engine.QueueNewFrame(this._bindedRenderFunction);
             }
         } else {
             this._renderingQueueLaunched = false;
@@ -2064,7 +2062,7 @@ export class Engine {
         if (!this._renderingQueueLaunched) {
             this._renderingQueueLaunched = true;
             this._bindedRenderFunction = this._renderLoop.bind(this);
-            this._frameHandler = Tools.QueueNewFrame(this._bindedRenderFunction);
+            this._frameHandler = Engine.QueueNewFrame(this._bindedRenderFunction);
         }
     }
 
@@ -2088,7 +2086,7 @@ export class Engine {
         if (!this.isFullscreen) {
             this._pointerLockRequested = requestPointerLock;
             if (this._renderingCanvas) {
-                Tools.RequestFullscreen(this._renderingCanvas);
+                Engine._RequestFullscreen(this._renderingCanvas);
             }
         }
     }
@@ -2098,7 +2096,7 @@ export class Engine {
      */
     public exitFullscreen(): void {
         if (this.isFullscreen) {
-            Tools.ExitFullscreen();
+            Engine._ExitFullscreen();
         }
     }
 
@@ -2107,7 +2105,7 @@ export class Engine {
      */
     public enterPointerlock(): void {
         if (this._renderingCanvas) {
-            Tools.RequestPointerlock(this._renderingCanvas);
+            Engine._RequestPointerlock(this._renderingCanvas);
         }
     }
 
@@ -2115,7 +2113,7 @@ export class Engine {
      * Exits Pointerlock mode
      */
     public exitPointerlock(): void {
-        Tools.ExitPointerlock();
+        Engine._ExitPointerlock();
     }
 
     /**
@@ -4235,7 +4233,6 @@ export class Engine {
                     // Add Back
                     customFallback = true;
                     excludeLoaders.push(loader);
-                    Tools.Warn((loader.constructor as any).name + " failed when trying to load " + texture.url + ", falling back to the next supported loader");
                     this.createTexture(urlArg, noMipmap, texture.invertY, scene, samplingMode, null, onError, buffer, texture, undefined, undefined, excludeLoaders);
                     return;
                 }
@@ -4245,8 +4242,8 @@ export class Engine {
                 if (onLoadObserver) {
                     texture.onLoadedObservable.remove(onLoadObserver);
                 }
-                if (Tools.UseFallbackTexture) {
-                    this.createTexture(Tools.fallbackTexture, noMipmap, texture.invertY, scene, samplingMode, null, onError, buffer, texture);
+                if (EngineStore.UseFallbackTexture) {
+                    this.createTexture(EngineStore.FallbackTexture, noMipmap, texture.invertY, scene, samplingMode, null, onError, buffer, texture);
                     return;
                 }
             }
@@ -4336,11 +4333,11 @@ export class Engine {
                 if (buffer instanceof HTMLImageElement) {
                     onload(buffer);
                 } else {
-                    Tools.LoadImage(url, onload, onInternalError, scene ? scene.offlineProvider : null);
+                    FileTools.LoadImage(url, onload, onInternalError, scene ? scene.offlineProvider : null);
                 }
             }
             else if (typeof buffer === "string" || buffer instanceof ArrayBuffer || buffer instanceof Blob) {
-                Tools.LoadImage(buffer, onload, onInternalError, scene ? scene.offlineProvider : null);
+                FileTools.LoadImage(buffer, onload, onInternalError, scene ? scene.offlineProvider : null);
             }
             else {
                 onload(<HTMLImageElement>buffer);
@@ -4461,8 +4458,8 @@ export class Engine {
         texture.baseHeight = height;
 
         if (generateMipMaps) {
-            width = this.needPOTTextures ? Tools.GetExponentOfTwo(width, this._caps.maxTextureSize) : width;
-            height = this.needPOTTextures ? Tools.GetExponentOfTwo(height, this._caps.maxTextureSize) : height;
+            width = this.needPOTTextures ? Engine.GetExponentOfTwo(width, this._caps.maxTextureSize) : width;
+            height = this.needPOTTextures ? Engine.GetExponentOfTwo(height, this._caps.maxTextureSize) : height;
         }
 
         //  this.resetTextureCache();
@@ -5126,8 +5123,8 @@ export class Engine {
     private _prepareWebGLTexture(texture: InternalTexture, scene: Nullable<Scene>, width: number, height: number, invertY: boolean, noMipmap: boolean, isCompressed: boolean,
         processFunction: (width: number, height: number, continuationCallback: () => void) => boolean, samplingMode: number = Engine.TEXTURE_TRILINEAR_SAMPLINGMODE): void {
         var maxTextureSize = this.getCaps().maxTextureSize;
-        var potWidth = Math.min(maxTextureSize, this.needPOTTextures ? Tools.GetExponentOfTwo(width, maxTextureSize) : width);
-        var potHeight = Math.min(maxTextureSize, this.needPOTTextures ? Tools.GetExponentOfTwo(height, maxTextureSize) : height);
+        var potWidth = Math.min(maxTextureSize, this.needPOTTextures ? Engine.GetExponentOfTwo(width, maxTextureSize) : width);
+        var potHeight = Math.min(maxTextureSize, this.needPOTTextures ? Engine.GetExponentOfTwo(height, maxTextureSize) : height);
 
         var gl = this._gl;
         if (!gl) {
@@ -6380,7 +6377,7 @@ export class Engine {
 
     /** @hidden */
     public _loadFile(url: string, onSuccess: (data: string | ArrayBuffer, responseURL?: string) => void, onProgress?: (data: any) => void, offlineProvider?: IOfflineProvider, useArrayBuffer?: boolean, onError?: (request?: WebRequest, exception?: any) => void): IFileRequest {
-        let request = Tools.LoadFile(url, onSuccess, onProgress, offlineProvider, useArrayBuffer, onError);
+        let request = FileTools.LoadFile(url, onSuccess, onProgress, offlineProvider, useArrayBuffer, onError);
         this._activeRequests.push(request);
         request.onCompleteObservable.add((request) => {
             this._activeRequests.splice(this._activeRequests.indexOf(request), 1);
@@ -6416,4 +6413,159 @@ export class Engine {
             return false;
         }
     }
+
+    /**
+     * Find the next highest power of two.
+     * @param x Number to start search from.
+     * @return Next highest power of two.
+     */
+    public static CeilingPOT(x: number): number {
+        x--;
+        x |= x >> 1;
+        x |= x >> 2;
+        x |= x >> 4;
+        x |= x >> 8;
+        x |= x >> 16;
+        x++;
+        return x;
+    }
+
+    /**
+     * Find the next lowest power of two.
+     * @param x Number to start search from.
+     * @return Next lowest power of two.
+     */
+    public static FloorPOT(x: number): number {
+        x = x | (x >> 1);
+        x = x | (x >> 2);
+        x = x | (x >> 4);
+        x = x | (x >> 8);
+        x = x | (x >> 16);
+        return x - (x >> 1);
+    }
+
+    /**
+     * Find the nearest power of two.
+     * @param x Number to start search from.
+     * @return Next nearest power of two.
+     */
+    public static NearestPOT(x: number): number {
+        var c = Engine.CeilingPOT(x);
+        var f = Engine.FloorPOT(x);
+        return (c - x) > (x - f) ? f : c;
+    }
+
+    /**
+     * Get the closest exponent of two
+     * @param value defines the value to approximate
+     * @param max defines the maximum value to return
+     * @param mode defines how to define the closest value
+     * @returns closest exponent of two of the given value
+     */
+    public static GetExponentOfTwo(value: number, max: number, mode = Constants.SCALEMODE_NEAREST): number {
+        let pot;
+
+        switch (mode) {
+            case Constants.SCALEMODE_FLOOR:
+                pot = Engine.FloorPOT(value);
+                break;
+            case Constants.SCALEMODE_NEAREST:
+                pot = Engine.NearestPOT(value);
+                break;
+            case Constants.SCALEMODE_CEILING:
+            default:
+                pot = Engine.CeilingPOT(value);
+                break;
+        }
+
+        return Math.min(pot, max);
+    }
+
+    /**
+     * Queue a new function into the requested animation frame pool (ie. this function will be executed byt the browser for the next frame)
+     * @param func - the function to be called
+     * @param requester - the object that will request the next frame. Falls back to window.
+     * @returns frame number
+     */
+    public static QueueNewFrame(func: () => void, requester?: any): number {
+        if (!DomManagement.IsWindowObjectExist()) {
+            return setTimeout(func, 16);
+        }
+
+        if (!requester) {
+            requester = window;
+        }
+
+        if (requester.requestAnimationFrame) {
+            return requester.requestAnimationFrame(func);
+        }
+        else if (requester.msRequestAnimationFrame) {
+            return requester.msRequestAnimationFrame(func);
+        }
+        else if (requester.webkitRequestAnimationFrame) {
+            return requester.webkitRequestAnimationFrame(func);
+        }
+        else if (requester.mozRequestAnimationFrame) {
+            return requester.mozRequestAnimationFrame(func);
+        }
+        else if (requester.oRequestAnimationFrame) {
+            return requester.oRequestAnimationFrame(func);
+        }
+        else {
+            return window.setTimeout(func, 16);
+        }
+    }
+
+    /**
+     * Ask the browser to promote the current element to pointerlock mode
+     * @param element defines the DOM element to promote
+     */
+    static _RequestPointerlock(element: HTMLElement): void {
+        element.requestPointerLock = element.requestPointerLock || (<any>element).msRequestPointerLock || (<any>element).mozRequestPointerLock || (<any>element).webkitRequestPointerLock;
+        if (element.requestPointerLock) {
+            element.requestPointerLock();
+        }
+    }
+
+    /**
+     * Asks the browser to exit pointerlock mode
+     */
+    static _ExitPointerlock(): void {
+        let anyDoc = document as any;
+        document.exitPointerLock = document.exitPointerLock || anyDoc.msExitPointerLock || anyDoc.mozExitPointerLock || anyDoc.webkitExitPointerLock;
+
+        if (document.exitPointerLock) {
+            document.exitPointerLock();
+        }
+    }
+
+    /**
+     * Ask the browser to promote the current element to fullscreen rendering mode
+     * @param element defines the DOM element to promote
+     */
+    static _RequestFullscreen(element: HTMLElement): void {
+        var requestFunction = element.requestFullscreen || (<any>element).msRequestFullscreen || (<any>element).webkitRequestFullscreen || (<any>element).mozRequestFullScreen;
+        if (!requestFunction) { return; }
+        requestFunction.call(element);
+    }
+
+    /**
+     * Asks the browser to exit fullscreen mode
+     */
+    static _ExitFullscreen(): void {
+        let anyDoc = document as any;
+
+        if (document.exitFullscreen) {
+            document.exitFullscreen();
+        }
+        else if (anyDoc.mozCancelFullScreen) {
+            anyDoc.mozCancelFullScreen();
+        }
+        else if (anyDoc.webkitCancelFullScreen) {
+            anyDoc.webkitCancelFullScreen();
+        }
+        else if (anyDoc.msCancelFullScreen) {
+            anyDoc.msCancelFullScreen();
+        }
+    }
 }

+ 12 - 0
src/Engines/engineStore.ts

@@ -31,4 +31,16 @@ export class EngineStore {
     public static get LastCreatedScene(): Nullable<Scene> {
         return this._LastCreatedScene;
     }
+
+    /**
+     * Gets or sets a global variable indicating if fallback texture must be used when a texture cannot be loaded
+     * @ignorenaming
+     */
+    public static UseFallbackTexture = true;
+
+    /**
+     * Texture content used if a texture cannot loaded
+     * @ignorenaming
+     */
+    public static FallbackTexture = "";
 }

+ 2 - 2
src/Gamepads/gamepadManager.ts

@@ -1,4 +1,3 @@
-import { Tools } from "../Misc/tools";
 import { Observable } from "../Misc/observable";
 import { DomManagement } from "../Misc/domManagement";
 import { Nullable } from "../types";
@@ -9,6 +8,7 @@ import { _DepthCullingState, _StencilState, _AlphaState } from "../States/index"
 import { PoseEnabledControllerHelper } from "../Gamepads/Controllers/poseEnabledController";
 import { Xbox360Pad } from "./xboxGamepad";
 import { Gamepad, GenericPad } from "./gamepad";
+import { Engine } from '../Engines/engine';
 /**
  * Manager for handling gamepads
  */
@@ -211,7 +211,7 @@ export class GamepadManager {
         }
 
         if (this._isMonitoring && !this._scene) {
-            Tools.QueueNewFrame(() => { this._checkGamepadsStatus(); });
+            Engine.QueueNewFrame(() => { this._checkGamepadsStatus(); });
         }
     }
 

+ 2 - 2
src/Layers/effectLayer.ts

@@ -298,8 +298,8 @@ export abstract class EffectLayer {
             this._mainTextureDesiredSize.width = this._engine.getRenderWidth() * this._effectLayerOptions.mainTextureRatio;
             this._mainTextureDesiredSize.height = this._engine.getRenderHeight() * this._effectLayerOptions.mainTextureRatio;
 
-            this._mainTextureDesiredSize.width = this._engine.needPOTTextures ? Tools.GetExponentOfTwo(this._mainTextureDesiredSize.width, this._maxSize) : this._mainTextureDesiredSize.width;
-            this._mainTextureDesiredSize.height = this._engine.needPOTTextures ? Tools.GetExponentOfTwo(this._mainTextureDesiredSize.height, this._maxSize) : this._mainTextureDesiredSize.height;
+            this._mainTextureDesiredSize.width = this._engine.needPOTTextures ? Engine.GetExponentOfTwo(this._mainTextureDesiredSize.width, this._maxSize) : this._mainTextureDesiredSize.width;
+            this._mainTextureDesiredSize.height = this._engine.needPOTTextures ? Engine.GetExponentOfTwo(this._mainTextureDesiredSize.height, this._maxSize) : this._mainTextureDesiredSize.height;
         }
 
         this._mainTextureDesiredSize.width = Math.floor(this._mainTextureDesiredSize.width);

+ 3 - 3
src/Layers/glowLayer.ts

@@ -1,5 +1,4 @@
 import { serialize, SerializationHelper } from "../Misc/decorators";
-import { Tools } from "../Misc/tools";
 import { Nullable } from "../types";
 import { Camera } from "../Cameras/camera";
 import { Scene } from "../scene";
@@ -18,6 +17,7 @@ import { EffectLayer } from "./effectLayer";
 import { AbstractScene } from "../abstractScene";
 import { Constants } from "../Engines/constants";
 import { _TypeStore } from '../Misc/typeStore';
+import { Engine } from '../Engines/engine';
 
 import "../Shaders/glowMapMerge.fragment";
 import "../Shaders/glowMapMerge.vertex";
@@ -222,8 +222,8 @@ export class GlowLayer extends EffectLayer {
     protected _createTextureAndPostProcesses(): void {
         var blurTextureWidth = this._mainTextureDesiredSize.width;
         var blurTextureHeight = this._mainTextureDesiredSize.height;
-        blurTextureWidth = this._engine.needPOTTextures ? Tools.GetExponentOfTwo(blurTextureWidth, this._maxSize) : blurTextureWidth;
-        blurTextureHeight = this._engine.needPOTTextures ? Tools.GetExponentOfTwo(blurTextureHeight, this._maxSize) : blurTextureHeight;
+        blurTextureWidth = this._engine.needPOTTextures ? Engine.GetExponentOfTwo(blurTextureWidth, this._maxSize) : blurTextureWidth;
+        blurTextureHeight = this._engine.needPOTTextures ? Engine.GetExponentOfTwo(blurTextureHeight, this._maxSize) : blurTextureHeight;
 
         var textureType = 0;
         if (this._engine.getCaps().textureHalfFloatRender) {

+ 2 - 3
src/Layers/highlightLayer.ts

@@ -1,6 +1,5 @@
 import { serialize, SerializationHelper } from "../Misc/decorators";
 import { Observer, Observable } from "../Misc/observable";
-import { Tools } from "../Misc/tools";
 import { Nullable } from "../types";
 import { Camera } from "../Cameras/camera";
 import { Scene } from "../scene";
@@ -326,8 +325,8 @@ export class HighlightLayer extends EffectLayer {
     protected _createTextureAndPostProcesses(): void {
         var blurTextureWidth = this._mainTextureDesiredSize.width * this._options.blurTextureSizeRatio;
         var blurTextureHeight = this._mainTextureDesiredSize.height * this._options.blurTextureSizeRatio;
-        blurTextureWidth = this._engine.needPOTTextures ? Tools.GetExponentOfTwo(blurTextureWidth, this._maxSize) : blurTextureWidth;
-        blurTextureHeight = this._engine.needPOTTextures ? Tools.GetExponentOfTwo(blurTextureHeight, this._maxSize) : blurTextureHeight;
+        blurTextureWidth = this._engine.needPOTTextures ? Engine.GetExponentOfTwo(blurTextureWidth, this._maxSize) : blurTextureWidth;
+        blurTextureHeight = this._engine.needPOTTextures ? Engine.GetExponentOfTwo(blurTextureHeight, this._maxSize) : blurTextureHeight;
 
         var textureType = 0;
         if (this._engine.getCaps().textureHalfFloatRender) {

+ 3 - 2
src/Loading/Plugins/babylonFileLoader.ts

@@ -29,6 +29,7 @@ import { AmmoJSPlugin } from "../../Physics/Plugins/ammoJSPlugin";
 import { ReflectionProbe } from "../../Probes/reflectionProbe";
 import { _TypeStore } from '../../Misc/typeStore';
 import { Tools } from '../../Misc/tools';
+import { StringTools } from '../../Misc/stringTools';
 
 /** @hidden */
 export var _BabylonLoaderRegistered = true;
@@ -125,7 +126,7 @@ var loadAssetContainer = (scene: Scene, data: string, rootUrl: string, onError?:
                 }
                 scene.environmentTexture = hdrTexture;
             } else {
-                if (Tools.EndsWith(parsedData.environmentTexture, ".env")) {
+                if (StringTools.EndsWith(parsedData.environmentTexture, ".env")) {
                     var compressedTexture = new CubeTexture((parsedData.environmentTexture.match(/https?:\/\//g) ? "" : rootUrl) + parsedData.environmentTexture, scene);
                     if (parsedData.environmentTextureRotationY) {
                         compressedTexture.rotationY = parsedData.environmentTextureRotationY;
@@ -744,7 +745,7 @@ SceneLoader.RegisterPlugin({
                     }
                     scene.environmentTexture = hdrTexture;
                 } else {
-                    if (Tools.EndsWith(parsedData.environmentTexture, ".env")) {
+                    if (StringTools.EndsWith(parsedData.environmentTexture, ".env")) {
                         var compressedTexture = new CubeTexture(rootUrl + parsedData.environmentTexture, scene);
                         if (parsedData.environmentTextureRotationY) {
                             compressedTexture.rotationY = parsedData.environmentTextureRotationY;

+ 2 - 1
src/Loading/sceneLoader.ts

@@ -1,4 +1,4 @@
-import { IFileRequest, Tools } from "../Misc/tools";
+import { Tools } from "../Misc/tools";
 import { Observable } from "../Misc/observable";
 import { FilesInputStore } from "../Misc/filesInputStore";
 import { Nullable } from "../types";
@@ -16,6 +16,7 @@ import { Skeleton } from "../Bones/skeleton";
 import { Logger } from "../Misc/logger";
 import { Constants } from "../Engines/constants";
 import { SceneLoaderFlags } from "./sceneLoaderFlags";
+import { IFileRequest } from '../Misc/fileRequest';
 /**
  * Class used to represent data loading progression
  */

+ 3 - 4
src/Materials/Textures/renderTargetTexture.ts

@@ -17,8 +17,7 @@ import { Constants } from "../../Engines/constants";
 
 import "../../Engines/Extensions/engine.renderTarget";
 import { InstancedMesh } from '../../Meshes/instancedMesh';
-
-declare type Engine = import("../../Engines/engine").Engine;
+import { Engine } from '../../Engines/engine';
 
 /**
  * This Helps creating a texture that will be created from a camera in your scene.
@@ -734,10 +733,10 @@ export class RenderTargetTexture extends Texture {
     private _bestReflectionRenderTargetDimension(renderDimension: number, scale: number): number {
         let minimum = 128;
         let x = renderDimension * scale;
-        let curved = Tools.NearestPOT(x + (minimum * minimum / (minimum + x)));
+        let curved = Engine.NearestPOT(x + (minimum * minimum / (minimum + x)));
 
         // Ensure we don't exceed the render dimension (while staying POT)
-        return Math.min(Tools.FloorPOT(renderDimension), curved);
+        return Math.min(Engine.FloorPOT(renderDimension), curved);
     }
 
     /**

+ 20 - 0
src/Misc/customAnimationFrameRequester.ts

@@ -0,0 +1,20 @@
+
+/**
+ * Interface for any object that can request an animation frame
+ */
+export interface ICustomAnimationFrameRequester {
+    /**
+     * This function will be called when the render loop is ready. If this is not populated, the engine's renderloop function will be called
+     */
+    renderFunction?: Function;
+    /**
+     * Called to request the next frame to render to
+     * @see https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
+     */
+    requestAnimationFrame: Function;
+    /**
+     * You can pass this value to cancelAnimationFrame() to cancel the refresh callback request
+     * @see https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame#Return_value
+     */
+    requestID?: number;
+}

+ 16 - 0
src/Misc/fileRequest.ts

@@ -0,0 +1,16 @@
+import { Observable } from './observable';
+
+/**
+ * File request interface
+ */
+export interface IFileRequest {
+    /**
+     * Raised when the request is complete (success or error).
+     */
+    onCompleteObservable: Observable<IFileRequest>;
+
+    /**
+     * Aborts the request for a file.
+     */
+    abort: () => void;
+}

+ 370 - 0
src/Misc/fileTools.ts

@@ -0,0 +1,370 @@
+import { WebRequest } from './webRequest';
+import { LoadFileError } from './loadFileError';
+import { DomManagement } from './domManagement';
+import { Nullable } from '../types';
+import { IOfflineProvider } from '../Offline/IOfflineProvider';
+import { IFileRequest } from './fileRequest';
+import { Observable } from './observable';
+import { FilesInputStore } from './filesInputStore';
+import { RetryStrategy } from './retryStrategy';
+
+/**
+ * @hidden
+ */
+export class FileTools {
+    /**
+     * Gets or sets the retry strategy to apply when an error happens while loading an asset
+     */
+    public static DefaultRetryStrategy = RetryStrategy.ExponentialBackoff();
+
+    /**
+     * Gets or sets the base URL to use to load assets
+     */
+    public static BaseUrl = "";
+
+    /**
+     * Default behaviour for cors in the application.
+     * It can be a string if the expected behavior is identical in the entire app.
+     * Or a callback to be able to set it per url or on a group of them (in case of Video source for instance)
+     */
+    public static CorsBehavior: string | ((url: string | string[]) => string) = "anonymous";
+
+    /**
+     * Gets or sets a function used to pre-process url before using them to load assets
+     */
+    public static PreprocessUrl = (url: string) => {
+        return url;
+    }
+
+    /**
+     * Removes unwanted characters from an url
+     * @param url defines the url to clean
+     * @returns the cleaned url
+     */
+    private static _CleanUrl(url: string): string {
+        url = url.replace(/#/mg, "%23");
+        return url;
+    }
+
+    /**
+     * Sets the cors behavior on a dom element. This will add the required Tools.CorsBehavior to the element.
+     * @param url define the url we are trying
+     * @param element define the dom element where to configure the cors policy
+     */
+    public static SetCorsBehavior(url: string | string[], element: { crossOrigin: string | null }): void {
+        if (url && url.indexOf("data:") === 0) {
+            return;
+        }
+
+        if (this.CorsBehavior) {
+            if (typeof (this.CorsBehavior) === 'string' || this.CorsBehavior instanceof String) {
+                element.crossOrigin = <string>this.CorsBehavior;
+            }
+            else {
+                var result = this.CorsBehavior(url);
+                if (result) {
+                    element.crossOrigin = result;
+                }
+            }
+        }
+    }
+
+    /**
+     * Loads an image as an HTMLImageElement.
+     * @param input url string, ArrayBuffer, or Blob to load
+     * @param onLoad callback called when the image successfully loads
+     * @param onError callback called when the image fails to load
+     * @param offlineProvider offline provider for caching
+     * @returns the HTMLImageElement of the loaded image
+     */
+    public static LoadImage(input: string | ArrayBuffer | Blob, onLoad: (img: HTMLImageElement) => void, onError: (message?: string, exception?: any) => void, offlineProvider: Nullable<IOfflineProvider>): HTMLImageElement {
+        let url: string;
+        let usingObjectURL = false;
+
+        if (input instanceof ArrayBuffer) {
+            url = URL.createObjectURL(new Blob([input]));
+            usingObjectURL = true;
+        }
+        else if (input instanceof Blob) {
+            url = URL.createObjectURL(input);
+            usingObjectURL = true;
+        }
+        else {
+            url = this._CleanUrl(input);
+            url = this.PreprocessUrl(input);
+        }
+
+        var img = new Image();
+        this.SetCorsBehavior(url, img);
+
+        const loadHandler = () => {
+            img.removeEventListener("load", loadHandler);
+            img.removeEventListener("error", errorHandler);
+
+            onLoad(img);
+
+            // Must revoke the URL after calling onLoad to avoid security exceptions in
+            // certain scenarios (e.g. when hosted in vscode).
+            if (usingObjectURL && img.src) {
+                URL.revokeObjectURL(img.src);
+            }
+        };
+
+        const errorHandler = (err: any) => {
+            img.removeEventListener("load", loadHandler);
+            img.removeEventListener("error", errorHandler);
+
+            if (onError) {
+                onError("Error while trying to load image: " + input, err);
+            }
+
+            if (usingObjectURL && img.src) {
+                URL.revokeObjectURL(img.src);
+            }
+        };
+
+        img.addEventListener("load", loadHandler);
+        img.addEventListener("error", errorHandler);
+
+        var noOfflineSupport = () => {
+            img.src = url;
+        };
+
+        var loadFromOfflineSupport = () => {
+            if (offlineProvider) {
+                offlineProvider.loadImage(url, img);
+            }
+        };
+
+        if (url.substr(0, 5) !== "data:" && offlineProvider && offlineProvider.enableTexturesOffline) {
+            offlineProvider.open(loadFromOfflineSupport, noOfflineSupport);
+        }
+        else {
+            if (url.indexOf("file:") !== -1) {
+                var textureName = decodeURIComponent(url.substring(5).toLowerCase());
+                if (FilesInputStore.FilesToLoad[textureName]) {
+                    try {
+                        var blobURL;
+                        try {
+                            blobURL = URL.createObjectURL(FilesInputStore.FilesToLoad[textureName]);
+                        }
+                        catch (ex) {
+                            // Chrome doesn't support oneTimeOnly parameter
+                            blobURL = URL.createObjectURL(FilesInputStore.FilesToLoad[textureName]);
+                        }
+                        img.src = blobURL;
+                        usingObjectURL = true;
+                    }
+                    catch (e) {
+                        img.src = "";
+                    }
+                    return img;
+                }
+            }
+
+            noOfflineSupport();
+        }
+
+        return img;
+    }
+
+    /**
+     * Loads a file
+     * @param fileToLoad defines the file to load
+     * @param callback defines the callback to call when data is loaded
+     * @param progressCallBack defines the callback to call during loading process
+     * @param useArrayBuffer defines a boolean indicating that data must be returned as an ArrayBuffer
+     * @returns a file request object
+     */
+    public static ReadFile(fileToLoad: File, callback: (data: any) => void, progressCallBack?: (ev: ProgressEvent) => any, useArrayBuffer?: boolean): IFileRequest {
+        let reader = new FileReader();
+        let request: IFileRequest = {
+            onCompleteObservable: new Observable<IFileRequest>(),
+            abort: () => reader.abort(),
+        };
+
+        reader.onloadend = (e) => request.onCompleteObservable.notifyObservers(request);
+        reader.onerror = (e) => {
+            callback(JSON.stringify({ autoClear: true, clearColor: [1, 0, 0], ambientColor: [0, 0, 0], gravity: [0, -9.807, 0], meshes: [], cameras: [], lights: [] }));
+        };
+        reader.onload = (e) => {
+            //target doesn't have result from ts 1.3
+            callback((<any>e.target)['result']);
+        };
+        if (progressCallBack) {
+            reader.onprogress = progressCallBack;
+        }
+        if (!useArrayBuffer) {
+            // Asynchronous read
+            reader.readAsText(fileToLoad);
+        }
+        else {
+            reader.readAsArrayBuffer(fileToLoad);
+        }
+
+        return request;
+    }
+
+    /**
+     * Loads a file
+     * @param url url string, ArrayBuffer, or Blob to load
+     * @param onSuccess callback called when the file successfully loads
+     * @param onProgress callback called while file is loading (if the server supports this mode)
+     * @param offlineProvider defines the offline provider for caching
+     * @param useArrayBuffer defines a boolean indicating that date must be returned as ArrayBuffer
+     * @param onError callback called when the file fails to load
+     * @returns a file request object
+     */
+    public static LoadFile(url: string, onSuccess: (data: string | ArrayBuffer, responseURL?: string) => void, onProgress?: (data: any) => void, offlineProvider?: IOfflineProvider, useArrayBuffer?: boolean, onError?: (request?: WebRequest, exception?: any) => void): IFileRequest {
+        url = this._CleanUrl(url);
+
+        url = this.PreprocessUrl(url);
+
+        // If file and file input are set
+        if (url.indexOf("file:") !== -1) {
+            const fileName = decodeURIComponent(url.substring(5).toLowerCase());
+            if (FilesInputStore.FilesToLoad[fileName]) {
+                return this.ReadFile(FilesInputStore.FilesToLoad[fileName], onSuccess, onProgress, useArrayBuffer);
+            }
+        }
+
+        const loadUrl = this.BaseUrl + url;
+
+        let aborted = false;
+        const fileRequest: IFileRequest = {
+            onCompleteObservable: new Observable<IFileRequest>(),
+            abort: () => aborted = true,
+        };
+
+        const requestFile = () => {
+            let request = new WebRequest();
+            let retryHandle: Nullable<number> = null;
+
+            fileRequest.abort = () => {
+                aborted = true;
+
+                if (request.readyState !== (XMLHttpRequest.DONE || 4)) {
+                    request.abort();
+                }
+
+                if (retryHandle !== null) {
+                    clearTimeout(retryHandle);
+                    retryHandle = null;
+                }
+            };
+
+            const retryLoop = (retryIndex: number) => {
+                request.open('GET', loadUrl);
+
+                if (useArrayBuffer) {
+                    request.responseType = "arraybuffer";
+                }
+
+                if (onProgress) {
+                    request.addEventListener("progress", onProgress);
+                }
+
+                const onLoadEnd = () => {
+                    request.removeEventListener("loadend", onLoadEnd);
+                    fileRequest.onCompleteObservable.notifyObservers(fileRequest);
+                    fileRequest.onCompleteObservable.clear();
+                };
+
+                request.addEventListener("loadend", onLoadEnd);
+
+                const onReadyStateChange = () => {
+                    if (aborted) {
+                        return;
+                    }
+
+                    // In case of undefined state in some browsers.
+                    if (request.readyState === (XMLHttpRequest.DONE || 4)) {
+                        // Some browsers have issues where onreadystatechange can be called multiple times with the same value.
+                        request.removeEventListener("readystatechange", onReadyStateChange);
+
+                        if ((request.status >= 200 && request.status < 300) || (request.status === 0 && (!DomManagement.IsWindowObjectExist() || this.IsFileURL()))) {
+                            onSuccess(!useArrayBuffer ? request.responseText : <ArrayBuffer>request.response, request.responseURL);
+                            return;
+                        }
+
+                        let retryStrategy = this.DefaultRetryStrategy;
+                        if (retryStrategy) {
+                            let waitTime = retryStrategy(loadUrl, request, retryIndex);
+                            if (waitTime !== -1) {
+                                // Prevent the request from completing for retry.
+                                request.removeEventListener("loadend", onLoadEnd);
+                                request = new WebRequest();
+                                retryHandle = setTimeout(() => retryLoop(retryIndex + 1), waitTime);
+                                return;
+                            }
+                        }
+
+                        let e = new LoadFileError("Error status: " + request.status + " " + request.statusText + " - Unable to load " + loadUrl, request);
+                        if (onError) {
+                            onError(request, e);
+                        } else {
+                            throw e;
+                        }
+                    }
+                };
+
+                request.addEventListener("readystatechange", onReadyStateChange);
+
+                request.send();
+            };
+
+            retryLoop(0);
+        };
+
+        // Caching all files
+        if (offlineProvider && offlineProvider.enableSceneOffline) {
+            const noOfflineSupport = (request?: any) => {
+                if (request && request.status > 400) {
+                    if (onError) {
+                        onError(request);
+                    }
+                } else {
+                    if (!aborted) {
+                        requestFile();
+                    }
+                }
+            };
+
+            const loadFromOfflineSupport = () => {
+                // TODO: database needs to support aborting and should return a IFileRequest
+                if (aborted) {
+                    return;
+                }
+
+                if (offlineProvider) {
+                    offlineProvider.loadFile(url, (data) => {
+                        if (!aborted) {
+                            onSuccess(data);
+                        }
+
+                        fileRequest.onCompleteObservable.notifyObservers(fileRequest);
+                    }, onProgress ? (event) => {
+                        if (!aborted) {
+                            onProgress(event);
+                        }
+                    } : undefined, noOfflineSupport, useArrayBuffer);
+                }
+            };
+
+            offlineProvider.open(loadFromOfflineSupport, noOfflineSupport);
+        }
+        else {
+            requestFile();
+        }
+
+        return fileRequest;
+    }
+
+    /**
+     * Checks if the loaded document was accessed via `file:`-Protocol.
+     * @returns boolean
+     */
+    public static IsFileURL(): boolean {
+        return location.protocol === "file:";
+    }
+}

+ 4 - 0
src/Misc/index.ts

@@ -35,3 +35,7 @@ export * from "./iInspectable";
 export * from "./brdfTextureTools";
 export * from "./gradients";
 export * from "./perfCounter";
+export * from "./fileRequest";
+export * from "./customAnimationFrameRequester";
+export * from "./retryStrategy";
+export * from "./loadFileError";

+ 30 - 0
src/Misc/loadFileError.ts

@@ -0,0 +1,30 @@
+import { WebRequest } from './webRequest';
+
+/**
+ * @ignore
+ * Application error to support additional information when loading a file
+ */
+export class LoadFileError extends Error {
+    // See https://stackoverflow.com/questions/12915412/how-do-i-extend-a-host-object-e-g-error-in-typescript
+    // and https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work
+
+    // Polyfill for Object.setPrototypeOf if necessary.
+    private static _setPrototypeOf: (o: any, proto: object | null) => any =
+        (Object as any).setPrototypeOf || ((o, proto) => { o.__proto__ = proto; return o; });
+
+    /**
+     * Creates a new LoadFileError
+     * @param message defines the message of the error
+     * @param request defines the optional web request
+     */
+    constructor(
+        message: string,
+        /** defines the optional web request */
+        public request?: WebRequest
+    ) {
+        super(message);
+        this.name = "LoadFileError";
+
+        LoadFileError._setPrototypeOf(this, LoadFileError.prototype);
+    }
+}

+ 1 - 2
src/Misc/promise.ts

@@ -1,5 +1,4 @@
 import { Nullable } from "../types";
-import { Tools } from "./tools";
 
 enum PromiseStates {
     Pending,
@@ -70,7 +69,7 @@ class InternalPromise<T> {
         newPromise._parent = this;
 
         if (this._state !== PromiseStates.Pending) {
-            Tools.SetImmediate(() => {
+            setTimeout(() => {
                 if (this._state === PromiseStates.Fulfilled || this._rejectWasConsumed) {
                     let returnedValue = newPromise._resolve(this._result);
 

+ 22 - 0
src/Misc/retryStrategy.ts

@@ -0,0 +1,22 @@
+import { WebRequest } from './webRequest';
+
+/**
+ * Class used to define a retry strategy when error happens while loading assets
+ */
+export class RetryStrategy {
+    /**
+     * Function used to defines an exponential back off strategy
+     * @param maxRetries defines the maximum number of retries (3 by default)
+     * @param baseInterval defines the interval between retries
+     * @returns the strategy function to use
+     */
+    public static ExponentialBackoff(maxRetries = 3, baseInterval = 500) {
+        return (url: string, request: WebRequest, retryIndex: number): number => {
+            if (request.status !== 0 || retryIndex >= maxRetries || url.indexOf("file:") !== -1) {
+                return -1;
+            }
+
+            return Math.pow(2, retryIndex) * baseInterval;
+        };
+    }
+}

+ 24 - 0
src/Misc/stringTools.ts

@@ -0,0 +1,24 @@
+/**
+ * Helper to manipulate strings
+ */
+export class StringTools {
+    /**
+     * Checks for a matching suffix at the end of a string (for ES5 and lower)
+     * @param str Source string
+     * @param suffix Suffix to search for in the source string
+     * @returns Boolean indicating whether the suffix was found (true) or not (false)
+     */
+    public static EndsWith(str: string, suffix: string): boolean {
+        return str.indexOf(suffix, str.length - suffix.length) !== -1;
+    }
+
+    /**
+     * Checks for a matching suffix at the beginning of a string (for ES5 and lower)
+     * @param str Source string
+     * @param suffix Suffix to search for in the source string
+     * @returns Boolean indicating whether the suffix was found (true) or not (false)
+     */
+    public static StartsWith(str: string, suffix: string): boolean {
+        return str.indexOf(suffix) === 0;
+    }
+}

文件差异内容过多而无法显示
+ 57 - 559
src/Misc/tools.ts


+ 5 - 6
src/PostProcesses/postProcess.ts

@@ -1,5 +1,4 @@
 import { Nullable } from "../types";
-import { Tools } from "../Misc/tools";
 import { SmartArray } from "../Misc/smartArray";
 import { Observable, Observer } from "../Misc/observable";
 import { Color4, Vector2 } from "../Maths/math";
@@ -8,11 +7,11 @@ import { Effect } from "../Materials/effect";
 import { Constants } from "../Engines/constants";
 import "../Shaders/postprocess.vertex";
 import { IInspectable } from '../Misc/iInspectable';
+import { Engine } from '../Engines/engine';
 
-declare type Scene  = import("../scene").Scene;
+declare type Scene = import("../scene").Scene;
 declare type InternalTexture = import("../Materials/Textures/internalTexture").InternalTexture;
 declare type WebVRFreeCamera = import("../Cameras/VR/webVRCamera").WebVRFreeCamera;
-declare type Engine = import("../Engines/engine").Engine;
 declare type Animation = import("../Animations/animation").Animation;
 
 /**
@@ -98,7 +97,7 @@ export class PostProcess {
      * | 3     | SCALEMODE_CEILING                   | [engine.scalemode_ceiling](http://doc.babylonjs.com/api/classes/babylon.engine#scalemode_ceiling) |
      *
      */
-    public scaleMode = Constants.SCALEMODE_FLOOR;
+    public scaleMode = Engine.SCALEMODE_FLOOR;
     /**
     * Force textures to be a power of two (default: false)
     */
@@ -464,11 +463,11 @@ export class PostProcess {
 
             if (this.renderTargetSamplingMode === Constants.TEXTURE_TRILINEAR_SAMPLINGMODE || this.alwaysForcePOT) {
                 if (!(<PostProcessOptions>this._options).width) {
-                    desiredWidth = engine.needPOTTextures ? Tools.GetExponentOfTwo(desiredWidth, maxSize, this.scaleMode) : desiredWidth;
+                    desiredWidth = engine.needPOTTextures ? Engine.GetExponentOfTwo(desiredWidth, maxSize, this.scaleMode) : desiredWidth;
                 }
 
                 if (!(<PostProcessOptions>this._options).height) {
-                    desiredHeight = engine.needPOTTextures ? Tools.GetExponentOfTwo(desiredHeight, maxSize, this.scaleMode) : desiredHeight;
+                    desiredHeight = engine.needPOTTextures ? Engine.GetExponentOfTwo(desiredHeight, maxSize, this.scaleMode) : desiredHeight;
                 }
             }
 

+ 2 - 1
src/scene.ts

@@ -1,5 +1,5 @@
 import { Nullable } from "./types";
-import { IAnimatable, IFileRequest, Tools } from "./Misc/tools";
+import { IAnimatable, Tools } from "./Misc/tools";
 import { PrecisionDate } from "./Misc/precisionDate";
 import { Observable, Observer } from "./Misc/observable";
 import { SmartArrayNoDuplicate, SmartArray, ISmartArrayLike } from "./Misc/smartArray";
@@ -48,6 +48,7 @@ import { _DevTools } from './Misc/devTools';
 import { WebRequest } from './Misc/webRequest';
 import { InputManager } from './Inputs/scene.inputManager';
 import { PerfCounter } from './Misc/perfCounter';
+import { IFileRequest } from './Misc/fileRequest';
 
 declare type Ray = import("./Culling/ray").Ray;
 declare type TrianglePickingPredicate = import("./Culling/ray").TrianglePickingPredicate;