12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288 |
- module BABYLON {
- /**
- * 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;
- }
- /**
- * Interface containing an array of animations
- */
- export interface IAnimatable {
- /**
- * Array of animations
- */
- animations: Array<Animation>;
- }
- /** Interface used by value gradients (color, factor, ...) */
- export interface IValueGradient {
- /**
- * Gets or sets the gradient value (between 0 and 1)
- */
- gradient: number;
- }
- /** Class used to store color4 gradient */
- export class ColorGradient implements IValueGradient {
- /**
- * Gets or sets the gradient value (between 0 and 1)
- */
- public gradient: number;
- /**
- * Gets or sets first associated color
- */
- public color1: Color4;
- /**
- * Gets or sets second associated color
- */
- public color2?: Color4;
- /**
- * Will get a color picked randomly between color1 and color2.
- * If color2 is undefined then color1 will be used
- * @param result defines the target Color4 to store the result in
- */
- public getColorToRef(result: Color4) {
- if (!this.color2) {
- result.copyFrom(this.color1);
- return;
- }
- Color4.LerpToRef(this.color1, this.color2, Math.random(), result);
- }
- }
- /** Class used to store color 3 gradient */
- export class Color3Gradient implements IValueGradient {
- /**
- * Gets or sets the gradient value (between 0 and 1)
- */
- public gradient: number;
- /**
- * Gets or sets the associated color
- */
- public color: Color3;
- }
- /** Class used to store factor gradient */
- export class FactorGradient implements IValueGradient {
- /**
- * Gets or sets the gradient value (between 0 and 1)
- */
- public gradient: number;
- /**
- * Gets or sets first associated factor
- */
- public factor1: number;
- /**
- * Gets or sets second associated factor
- */
- public factor2?: number;
- /**
- * Will get a number picked randomly between factor1 and factor2.
- * If factor2 is undefined then factor1 will be used
- * @returns the picked number
- */
- public getFactor(): number {
- if (this.factor2 === undefined) {
- return this.factor1;
- }
- return Scalar.Lerp(this.factor1, this.factor2, Math.random());
- }
- }
- /**
- * @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 XHR request
- */
- constructor(
- message: string,
- /** defines the optional XHR request */
- public request?: XMLHttpRequest
- ) {
- super(message);
- this.name = "LoadFileError";
- LoadFileError._setPrototypeOf(this, LoadFileError.prototype);
- }
- }
- /**
- * 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: XMLHttpRequest, retryIndex: number): number => {
- if (request.status !== 0 || retryIndex >= maxRetries || url.indexOf("file:") !== -1) {
- return -1;
- }
- return Math.pow(2, retryIndex) * baseInterval;
- };
- }
- }
- /**
- * 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;
- }
- // Screenshots
- var screenshotCanvas: HTMLCanvasElement;
- var cloneValue = (source: any, destinationObject: any) => {
- if (!source) {
- return null;
- }
- if (source instanceof Mesh) {
- return null;
- }
- if (source instanceof SubMesh) {
- return source.clone(destinationObject);
- } else if (source.clone) {
- return source.clone();
- }
- return null;
- };
- /**
- * Class containing a set of static utilities functions
- */
- export class Tools {
- /**
- * Gets or sets the base URL to use to load assets
- */
- public static BaseUrl = "";
- /**
- * Gets or sets the retry strategy to apply when an error happens while loading an asset
- */
- public static DefaultRetryStrategy = RetryStrategy.ExponentialBackoff();
- /**
- * 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 global variable indicating if fallback texture must be used when a texture cannot be loaded
- * @ignorenaming
- */
- public static UseFallbackTexture = true;
- /**
- * Use this object to register external classes like custom textures or material
- * to allow the laoders to instantiate them
- */
- public static RegisteredExternalClasses: { [key: string]: Object } = {};
- /**
- * Texture content used if a texture cannot loaded
- * @ignorenaming
- */
- public static fallbackTexture = "data:image/jpg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/4QBmRXhpZgAATU0AKgAAAAgABAEaAAUAAAABAAAAPgEbAAUAAAABAAAARgEoAAMAAAABAAIAAAExAAIAAAAQAAAATgAAAAAAAABgAAAAAQAAAGAAAAABcGFpbnQubmV0IDQuMC41AP/bAEMABAIDAwMCBAMDAwQEBAQFCQYFBQUFCwgIBgkNCw0NDQsMDA4QFBEODxMPDAwSGBITFRYXFxcOERkbGRYaFBYXFv/bAEMBBAQEBQUFCgYGChYPDA8WFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFv/AABEIAQABAAMBIgACEQEDEQH/xAAfAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgv/xAC1EAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp6vHy8/T19vf4+fr/xAAfAQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgv/xAC1EQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/APH6KKK+FP76Pl+iiivuj+BT6gooor4U/vo+X6KKK+6P4FPqCiiivhT++j5fooor7o/gU+oKKKK+FP76Pl+iiivuj+BT6gooor4U/vo+X6KKK+6P4FPqCiiivhT++j5fooor7o/gU+oKKKK+FP76Pl+iiivuj+BT6gooor4U/vo+X6KKK+6P4FCiiigD6gooor4U/vo+X6KKK+6P4FPqCiiivhT++j5fooor7o/gU+oKKKK+FP76Pl+iiivuj+BT6gooor4U/vo+X6KKK+6P4FPqCiiivhT++j5fooor7o/gU+oKKKK+FP76Pl+iiivuj+BT6gooor4U/vo+X6KKK+6P4FPqCiiivhT++gooooA+X6KKK+6P4FPqCiiivhT++j5fooor7o/gU+oKKKK+FP76Pl+iiivuj+BT6gooor4U/vo+X6KKK+6P4FPqCiiivhT++j5fooor7o/gU+oKKKK+FP76Pl+iiivuj+BT6gooor4U/vo+X6KKK+6P4FPqCiiivhT++j5fooor7o/gUKKKKAPqCiiivhT++j5fooor7o/gU+oKKKK+FP76Pl+iiivuj+BT6gooor4U/vo+X6KKK+6P4FPqCiiivhT++j5fooor7o/gU+oKKKK+FP76Pl+iiivuj+BT6gooor4U/vo+X6KKK+6P4FPqCiiivhT++j5fooor7o/gU+oKKKK+FP76CiiigD5fooor7o/gU+oKKKK+FP76Pl+iiivuj+BT6gooor4U/vo+X6KKK+6P4FPqCiiivhT++j5fooor7o/gU+oKKKK+FP76Pl+iiivuj+BT6gooor4U/vo+X6KKK+6P4FPqCiiivhT++j5fooor7o/gU+oKKKK+FP76Pl+iiivuj+BQooooA+oKKKK+FP76Pl+iiivuj+BT6gooor4U/vo+X6KKK+6P4FPqCiiivhT++j5fooor7o/gU+oKKKK+FP76Pl+iiivuj+BT6gooor4U/vo+X6KKK+6P4FPqCiiivhT++j5fooor7o/gU+oKKKK+FP76Pl+iiivuj+BT6gooor4U/voKKKKAPl+iiivuj+BT6gooor4U/vo+X6KKK+6P4FPqCiiivhT++j5fooor7o/gU+oKKKK+FP76Pl+iiivuj+BT6gooor4U/vo+X6KKK+6P4FPqCiiivhT++j5fooor7o/gU+oKKKK+FP76Pl+iiivuj+BT6gooor4U/vo+X6KKK+6P4FCiiigD6gooor4U/vo+X6KKK+6P4FPqCiiivhT++j5fooor7o/gU+oKKKK+FP76Pl+iiivuj+BT6gooor4U/vo+X6KKK+6P4FPqCiiivhT++j5fooor7o/gU+oKKKK+FP76Pl+iiivuj+BT6gooor4U/vo+X6KKK+6P4FPqCiiivhT++gooooA+X6KKK+6P4FPqCiiivhT++j5fooor7o/gU+oKKKK+FP76Pl+iiivuj+BT6gooor4U/vo+X6KKK+6P4FPqCiiivhT++j5fooor7o/gU+oKKKK+FP76Pl+iiivuj+BT6gooor4U/vo+X6KKK+6P4FPqCiiivhT++j5fooor7o/gUKKKKAPqCiiivhT++j5fooor7o/gU+oKKKK+FP76Pl+iiivuj+BT6gooor4U/vo+X6KKK+6P4FPqCiiivhT++j5fooor7o/gU+oKKKK+FP76Pl+iiivuj+BT6gooor4U/vo+X6KKK+6P4FPqCiiivhT++j5fooor7o/gU+oKKKK+FP76CiiigD5fooor7o/gU+oKKKK+FP76Pl+iiivuj+BT6gooor4U/vo+X6KKK+6P4FPqCiiivhT++j5fooor7o/gU+oKKKK+FP76Pl+iiivuj+BT6gooor4U/vo+X6KKK+6P4FPqCiiivhT++j5fooor7o/gU+oKKKK+FP76Pl+iiivuj+BQooooA+oKKKK+FP76Pl+iiivuj+BT6gooor4U/vo+X6KKK+6P4FPqCiiivhT++j5fooor7o/gU+oKKKK+FP76Pl+iiivuj+BT6gooor4U/vo+X6KKK+6P4FPqCiiivhT++j5fooor7o/gU+oKKKK+FP76Pl+iiivuj+BT6gooor4U/voKKKKAPl+iiivuj+BT6gooor4U/vo+X6KKK+6P4FPqCiiivhT++j5fooor7o/gU+oKKKK+FP76Pl+iiivuj+BT6gooor4U/vo+X6KKK+6P4FPqCiiivhT++j5fooor7o/gU+oKKKK+FP76Pl+iiivuj+BT6gooor4U/vo+X6KKK+6P4FCiiigD6gooor4U/vo+X6KKK+6P4FPqCiiivhT++j5fooor7o/gU+oKKKK+FP76Pl+iiivuj+BT6gooor4U/vo+X6KKK+6P4FPqCiiivhT++j5fooor7o/gU+oKKKK+FP76Pl+iiivuj+BT6gooor4U/vo+X6KKK+6P4FPqCiiivhT++gooooA+X6KKK+6P4FPqCiiivhT++j5fooor7o/gU+oKKKK+FP76Pl+iiivuj+BT6gooor4U/vo+X6KKK+6P4FPqCiiivhT++j5fooor7o/gU+oKKKK+FP76Pl+iiivuj+BT6gooor4U/vo+X6KKK+6P4FPqCiiivhT++j5fooor7o/gUKKKKAPqCiiivhT++j5fooor7o/gU+oKKKK+FP76Pl+iiivuj+BT6gooor4U/vo+X6KKK+6P4FPqCiiivhT++j5fooor7o/gU+oKKKK+FP76Pl+iiivuj+BT6gooor4U/vo+X6KKK+6P4FPqCiiivhT++j5fooor7o/gU+oKKKK+FP76P//Z";
- /**
- * Read the content of a byte array at a specified coordinates (taking in account wrapping)
- * @param u defines the coordinate on X axis
- * @param v defines the coordinate on Y axis
- * @param width defines the width of the source data
- * @param height defines the height of the source data
- * @param pixels defines the source byte array
- * @param color defines the output color
- */
- public static FetchToRef(u: number, v: number, width: number, height: number, pixels: Uint8Array, color: Color4): void {
- let wrappedU = ((Math.abs(u) * width) % width) | 0;
- let wrappedV = ((Math.abs(v) * height) % height) | 0;
- let position = (wrappedU + wrappedV * width) * 4;
- color.r = pixels[position] / 255;
- color.g = pixels[position + 1] / 255;
- color.b = pixels[position + 2] / 255;
- color.a = pixels[position + 3] / 255;
- }
- /**
- * Interpolates between a and b via alpha
- * @param a The lower value (returned when alpha = 0)
- * @param b The upper value (returned when alpha = 1)
- * @param alpha The interpolation-factor
- * @return The mixed value
- */
- public static Mix(a: number, b: number, alpha: number): number {
- return a * (1 - alpha) + b * alpha;
- }
- /**
- * Tries to instantiate a new object from a given class name
- * @param className defines the class name to instantiate
- * @returns the new object or null if the system was not able to do the instantiation
- */
- public static Instantiate(className: string): any {
- if (Tools.RegisteredExternalClasses && Tools.RegisteredExternalClasses[className]) {
- return Tools.RegisteredExternalClasses[className];
- }
- var arr = className.split(".");
- var fn: any = (window || this);
- for (var i = 0, len = arr.length; i < len; i++) {
- fn = fn[arr[i]];
- }
- if (typeof fn !== "function") {
- return null;
- }
- return fn;
- }
- /**
- * Provides a slice function that will work even on IE
- * @param data defines the array to slice
- * @param start defines the start of the data (optional)
- * @param end defines the end of the data (optional)
- * @returns the new sliced array
- */
- public static Slice<T>(data: T, start?: number, end?: number): T {
- if ((data as any).slice) {
- return (data as any).slice(start, end);
- }
- return Array.prototype.slice.call(data, start, end);
- }
- /**
- * Polyfill for setImmediate
- * @param action defines the action to execute after the current execution block
- */
- public static SetImmediate(action: () => void) {
- if (Tools.IsWindowObjectExist() && window.setImmediate) {
- window.setImmediate(action);
- } else {
- setTimeout(action, 1);
- }
- }
- /**
- * Function indicating if a number is an exponent of 2
- * @param value defines the value to test
- * @returns true if the value is an exponent of 2
- */
- public static IsExponentOfTwo(value: number): boolean {
- var count = 1;
- do {
- count *= 2;
- } while (count < value);
- return count === value;
- }
- private static _tmpFloatArray = new Float32Array(1);
- /**
- * Returns the nearest 32-bit single precision float representation of a Number
- * @param value A Number. If the parameter is of a different type, it will get converted
- * to a number or to NaN if it cannot be converted
- * @returns number
- */
- public static FloatRound(value: number): number {
- if (Math.fround) {
- return Math.fround(value);
- }
- return (Tools._tmpFloatArray[0] = value);
- }
- /**
- * 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 = Tools.CeilingPOT(x);
- var f = Tools.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 = Engine.SCALEMODE_NEAREST): number {
- let pot;
- switch (mode) {
- case Engine.SCALEMODE_FLOOR:
- pot = Tools.FloorPOT(value);
- break;
- case Engine.SCALEMODE_NEAREST:
- pot = Tools.NearestPOT(value);
- break;
- case Engine.SCALEMODE_CEILING:
- default:
- pot = Tools.CeilingPOT(value);
- break;
- }
- return Math.min(pot, max);
- }
- /**
- * Extracts the filename from a path
- * @param path defines the path to use
- * @returns the filename
- */
- public static GetFilename(path: string): string {
- var index = path.lastIndexOf("/");
- if (index < 0) {
- return path;
- }
- return path.substring(index + 1);
- }
- /**
- * Extracts the "folder" part of a path (everything before the filename).
- * @param uri The URI to extract the info from
- * @param returnUnchangedIfNoSlash Do not touch the URI if no slashes are present
- * @returns The "folder" part of the path
- */
- public static GetFolderPath(uri: string, returnUnchangedIfNoSlash = false): string {
- var index = uri.lastIndexOf("/");
- if (index < 0) {
- if (returnUnchangedIfNoSlash) {
- return uri;
- }
- return "";
- }
- return uri.substring(0, index + 1);
- }
- /**
- * Extracts text content from a DOM element hierarchy
- * @param element defines the root element
- * @returns a string
- */
- public static GetDOMTextContent(element: HTMLElement): string {
- var result = "";
- var child = element.firstChild;
- while (child) {
- if (child.nodeType === 3) {
- result += child.textContent;
- }
- child = child.nextSibling;
- }
- return result;
- }
- /**
- * Convert an angle in radians to degrees
- * @param angle defines the angle to convert
- * @returns the angle in degrees
- */
- public static ToDegrees(angle: number): number {
- return angle * 180 / Math.PI;
- }
- /**
- * Convert an angle in degrees to radians
- * @param angle defines the angle to convert
- * @returns the angle in radians
- */
- public static ToRadians(angle: number): number {
- return angle * Math.PI / 180;
- }
- /**
- * Encode a buffer to a base64 string
- * @param buffer defines the buffer to encode
- * @returns the encoded string
- */
- public static EncodeArrayBufferTobase64(buffer: ArrayBuffer): string {
- var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
- var output = "";
- var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
- var i = 0;
- var bytes = new Uint8Array(buffer);
- while (i < bytes.length) {
- chr1 = bytes[i++];
- chr2 = i < bytes.length ? bytes[i++] : Number.NaN; // Not sure if the index
- chr3 = i < bytes.length ? bytes[i++] : Number.NaN; // checks are needed here
- enc1 = chr1 >> 2;
- enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
- enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
- enc4 = chr3 & 63;
- if (isNaN(chr2)) {
- enc3 = enc4 = 64;
- } else if (isNaN(chr3)) {
- enc4 = 64;
- }
- output += keyStr.charAt(enc1) + keyStr.charAt(enc2) +
- keyStr.charAt(enc3) + keyStr.charAt(enc4);
- }
- return "data:image/png;base64," + output;
- }
- /**
- * Extracts minimum and maximum values from a list of indexed positions
- * @param positions defines the positions to use
- * @param indices defines the indices to the positions
- * @param indexStart defines the start index
- * @param indexCount defines the end index
- * @param bias defines bias value to add to the result
- * @return minimum and maximum values
- */
- public static ExtractMinAndMaxIndexed(positions: FloatArray, indices: IndicesArray, indexStart: number, indexCount: number, bias: Nullable<Vector2> = null): { minimum: Vector3; maximum: Vector3 } {
- var minimum = new Vector3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
- var maximum = new Vector3(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE);
- for (var index = indexStart; index < indexStart + indexCount; index++) {
- const offset = indices[index] * 3;
- const x = positions[offset];
- const y = positions[offset + 1];
- const z = positions[offset + 2];
- minimum.minimizeInPlaceFromFloats(x, y, z);
- maximum.maximizeInPlaceFromFloats(x, y, z);
- }
- if (bias) {
- minimum.x -= minimum.x * bias.x + bias.y;
- minimum.y -= minimum.y * bias.x + bias.y;
- minimum.z -= minimum.z * bias.x + bias.y;
- maximum.x += maximum.x * bias.x + bias.y;
- maximum.y += maximum.y * bias.x + bias.y;
- maximum.z += maximum.z * bias.x + bias.y;
- }
- return {
- minimum: minimum,
- maximum: maximum
- };
- }
- /**
- * Extracts minimum and maximum values from a list of positions
- * @param positions defines the positions to use
- * @param start defines the start index in the positions array
- * @param count defines the number of positions to handle
- * @param bias defines bias value to add to the result
- * @param stride defines the stride size to use (distance between two positions in the positions array)
- * @return minimum and maximum values
- */
- public static ExtractMinAndMax(positions: FloatArray, start: number, count: number, bias: Nullable<Vector2> = null, stride?: number): { minimum: Vector3; maximum: Vector3 } {
- var minimum = new Vector3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
- var maximum = new Vector3(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE);
- if (!stride) {
- stride = 3;
- }
- for (var index = start, offset = start * stride; index < start + count; index++ , offset += stride) {
- const x = positions[offset];
- const y = positions[offset + 1];
- const z = positions[offset + 2];
- minimum.minimizeInPlaceFromFloats(x, y, z);
- maximum.maximizeInPlaceFromFloats(x, y, z);
- }
- if (bias) {
- minimum.x -= minimum.x * bias.x + bias.y;
- minimum.y -= minimum.y * bias.x + bias.y;
- minimum.z -= minimum.z * bias.x + bias.y;
- maximum.x += maximum.x * bias.x + bias.y;
- maximum.y += maximum.y * bias.x + bias.y;
- maximum.z += maximum.z * bias.x + bias.y;
- }
- return {
- minimum: minimum,
- maximum: maximum
- };
- }
- /**
- * Returns an array if obj is not an array
- * @param obj defines the object to evaluate as an array
- * @param allowsNullUndefined defines a boolean indicating if obj is allowed to be null or undefined
- * @returns either obj directly if obj is an array or a new array containing obj
- */
- public static MakeArray(obj: any, allowsNullUndefined?: boolean): Nullable<Array<any>> {
- if (allowsNullUndefined !== true && (obj === undefined || obj == null)) {
- return null;
- }
- return Array.isArray(obj) ? obj : [obj];
- }
- /**
- * Returns an array of the given size filled with element built from the given constructor and the paramters
- * @param size the number of element to construct and put in the array
- * @param itemBuilder a callback responsible for creating new instance of item. Called once per array entry.
- * @returns a new array filled with new objects
- */
- public static BuildArray<T>(size: number, itemBuilder: () => T): Array<T> {
- const a: T[] = [];
- for (let i = 0; i < size; ++i) {
- a.push(itemBuilder());
- }
- return a;
- }
- /**
- * Gets the pointer prefix to use
- * @returns "pointer" if touch is enabled. Else returns "mouse"
- */
- public static GetPointerPrefix(): string {
- var eventPrefix = "pointer";
- // Check if pointer events are supported
- if (Tools.IsWindowObjectExist() && !window.PointerEvent && !navigator.pointerEnabled) {
- eventPrefix = "mouse";
- }
- return eventPrefix;
- }
- /**
- * 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 (!Tools.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 fullscreen rendering mode
- * @param element defines the DOM element to promote
- */
- public 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
- */
- public static ExitFullscreen(): void {
- if (document.exitFullscreen) {
- document.exitFullscreen();
- }
- else if (document.mozCancelFullScreen) {
- document.mozCancelFullScreen();
- }
- else if (document.webkitCancelFullScreen) {
- document.webkitCancelFullScreen();
- }
- else if (document.msCancelFullScreen) {
- document.msCancelFullScreen();
- }
- }
- /**
- * 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 (Tools.CorsBehavior) {
- if (typeof (Tools.CorsBehavior) === 'string' || Tools.CorsBehavior instanceof String) {
- element.crossOrigin = <string>Tools.CorsBehavior;
- }
- else {
- var result = Tools.CorsBehavior(url);
- if (result) {
- element.crossOrigin = result;
- }
- }
- }
- }
- // External files
- /**
- * Removes unwanted characters from an url
- * @param url defines the url to clean
- * @returns the cleaned url
- */
- public static CleanUrl(url: string): string {
- url = url.replace(/#/mg, "%23");
- return url;
- }
- /**
- * Gets or sets a function used to pre-process url before using them to load assets
- */
- public static PreprocessUrl = (url: string) => {
- return url;
- }
- /**
- * 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 = Tools.CleanUrl(input);
- url = Tools.PreprocessUrl(input);
- }
- var img = new Image();
- Tools.SetCorsBehavior(url, img);
- const loadHandler = () => {
- if (usingObjectURL && img.src) {
- URL.revokeObjectURL(img.src);
- }
- img.removeEventListener("load", loadHandler);
- img.removeEventListener("error", errorHandler);
- onLoad(img);
- };
- const errorHandler = (err: any) => {
- if (usingObjectURL && img.src) {
- URL.revokeObjectURL(img.src);
- }
- img.removeEventListener("load", loadHandler);
- img.removeEventListener("error", errorHandler);
- Tools.Error("Error while trying to load image: " + input);
- if (onError) {
- onError("Error while trying to load image: " + input, err);
- }
- };
- 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 (FilesInput.FilesToLoad[textureName]) {
- try {
- var blobURL;
- try {
- blobURL = URL.createObjectURL(FilesInput.FilesToLoad[textureName]);
- }
- catch (ex) {
- // Chrome doesn't support oneTimeOnly parameter
- blobURL = URL.createObjectURL(FilesInput.FilesToLoad[textureName]);
- }
- img.src = blobURL;
- usingObjectURL = true;
- }
- catch (e) {
- img.src = "";
- }
- return img;
- }
- }
- noOfflineSupport();
- }
- return img;
- }
- /**
- * 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?: XMLHttpRequest, exception?: any) => void): IFileRequest {
- url = Tools.CleanUrl(url);
- url = Tools.PreprocessUrl(url);
- // If file and file input are set
- if (url.indexOf("file:") !== -1) {
- const fileName = decodeURIComponent(url.substring(5).toLowerCase());
- if (FilesInput.FilesToLoad[fileName]) {
- return Tools.ReadFile(FilesInput.FilesToLoad[fileName], onSuccess, onProgress, useArrayBuffer);
- }
- }
- const loadUrl = Tools.BaseUrl + url;
- let aborted = false;
- const fileRequest: IFileRequest = {
- onCompleteObservable: new Observable<IFileRequest>(),
- abort: () => aborted = true,
- };
- const requestFile = () => {
- let request = new XMLHttpRequest();
- 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, true);
- 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 && (!Tools.IsWindowObjectExist() || Tools.IsFileURL()))) {
- onSuccess(!useArrayBuffer ? request.responseText : <ArrayBuffer>request.response, request.responseURL);
- return;
- }
- let retryStrategy = Tools.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 XMLHttpRequest();
- 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;
- }
- /**
- * Load a script (identified by an url). When the url returns, the
- * content of this file is added into a new script element, attached to the DOM (body element)
- * @param scriptUrl defines the url of the script to laod
- * @param onSuccess defines the callback called when the script is loaded
- * @param onError defines the callback to call if an error occurs
- */
- public static LoadScript(scriptUrl: string, onSuccess: () => void, onError?: (message?: string, exception?: any) => void) {
- if (!Tools.IsWindowObjectExist()) {
- return;
- }
- var head = document.getElementsByTagName('head')[0];
- var script = document.createElement('script');
- script.type = 'text/javascript';
- script.src = scriptUrl;
- script.onload = () => {
- if (onSuccess) {
- onSuccess();
- }
- };
- script.onerror = (e) => {
- if (onError) {
- onError(`Unable to load script '${scriptUrl}'`, e);
- }
- };
- head.appendChild(script);
- }
- /**
- * Loads a file from a blob
- * @param fileToLoad defines the blob to use
- * @param callback defines the callback to call when data is loaded
- * @param progressCallback defines the callback to call during loading process
- * @returns a file request object
- */
- public static ReadFileAsDataURL(fileToLoad: Blob, callback: (data: any) => void, progressCallback: (ev: ProgressEvent) => any): IFileRequest {
- let reader = new FileReader();
- let request: IFileRequest = {
- onCompleteObservable: new Observable<IFileRequest>(),
- abort: () => reader.abort(),
- };
- reader.onloadend = (e) => {
- request.onCompleteObservable.notifyObservers(request);
- };
- reader.onload = (e) => {
- //target doesn't have result from ts 1.3
- callback((<any>e.target)['result']);
- };
- reader.onprogress = progressCallback;
- reader.readAsDataURL(fileToLoad);
- return request;
- }
- /**
- * 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) => {
- Tools.Log("Error while reading file: " + fileToLoad.name);
- 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;
- }
- /**
- * Creates a data url from a given string content
- * @param content defines the content to convert
- * @returns the new data url link
- */
- public static FileAsURL(content: string): string {
- var fileBlob = new Blob([content]);
- var url = window.URL || window.webkitURL;
- var link: string = url.createObjectURL(fileBlob);
- return link;
- }
- /**
- * Format the given number to a specific decimal format
- * @param value defines the number to format
- * @param decimals defines the number of decimals to use
- * @returns the formatted string
- */
- public static Format(value: number, decimals: number = 2): string {
- return value.toFixed(decimals);
- }
- /**
- * Checks if a given vector is inside a specific range
- * @param v defines the vector to test
- * @param min defines the minimum range
- * @param max defines the maximum range
- */
- public static CheckExtends(v: Vector3, min: Vector3, max: Vector3): void {
- min.minimizeInPlace(v);
- max.maximizeInPlace(v);
- }
- /**
- * Tries to copy an object by duplicating every property
- * @param source defines the source object
- * @param destination defines the target object
- * @param doNotCopyList defines a list of properties to avoid
- * @param mustCopyList defines a list of properties to copy (even if they start with _)
- */
- public static DeepCopy(source: any, destination: any, doNotCopyList?: string[], mustCopyList?: string[]): void {
- for (var prop in source) {
- if (prop[0] === "_" && (!mustCopyList || mustCopyList.indexOf(prop) === -1)) {
- continue;
- }
- if (doNotCopyList && doNotCopyList.indexOf(prop) !== -1) {
- continue;
- }
- var sourceValue = source[prop];
- var typeOfSourceValue = typeof sourceValue;
- if (typeOfSourceValue === "function") {
- continue;
- }
- try {
- if (typeOfSourceValue === "object") {
- if (sourceValue instanceof Array) {
- destination[prop] = [];
- if (sourceValue.length > 0) {
- if (typeof sourceValue[0] == "object") {
- for (var index = 0; index < sourceValue.length; index++) {
- var clonedValue = cloneValue(sourceValue[index], destination);
- if (destination[prop].indexOf(clonedValue) === -1) { // Test if auto inject was not done
- destination[prop].push(clonedValue);
- }
- }
- } else {
- destination[prop] = sourceValue.slice(0);
- }
- }
- } else {
- destination[prop] = cloneValue(sourceValue, destination);
- }
- } else {
- destination[prop] = sourceValue;
- }
- }
- catch (e) {
- // Just ignore error (it could be because of a read-only property)
- }
- }
- }
- /**
- * Gets a boolean indicating if the given object has no own property
- * @param obj defines the object to test
- * @returns true if object has no own property
- */
- public static IsEmpty(obj: any): boolean {
- for (var i in obj) {
- if (obj.hasOwnProperty(i)) {
- return false;
- }
- }
- return true;
- }
- /**
- * Function used to register events at window level
- * @param events defines the events to register
- */
- public static RegisterTopRootEvents(events: { name: string; handler: Nullable<(e: FocusEvent) => any> }[]): void {
- for (var index = 0; index < events.length; index++) {
- var event = events[index];
- window.addEventListener(event.name, <any>event.handler, false);
- try {
- if (window.parent) {
- window.parent.addEventListener(event.name, <any>event.handler, false);
- }
- } catch (e) {
- // Silently fails...
- }
- }
- }
- /**
- * Function used to unregister events from window level
- * @param events defines the events to unregister
- */
- public static UnregisterTopRootEvents(events: { name: string; handler: Nullable<(e: FocusEvent) => any> }[]): void {
- for (var index = 0; index < events.length; index++) {
- var event = events[index];
- window.removeEventListener(event.name, <any>event.handler);
- try {
- if (window.parent) {
- window.parent.removeEventListener(event.name, <any>event.handler);
- }
- } catch (e) {
- // Silently fails...
- }
- }
- }
- /**
- * Dumps the current bound framebuffer
- * @param width defines the rendering width
- * @param height defines the rendering height
- * @param engine defines the hosting engine
- * @param successCallback defines the callback triggered once the data are available
- * @param mimeType defines the mime type of the result
- * @param fileName defines the filename to download. If present, the result will automatically be downloaded
- */
- public static DumpFramebuffer(width: number, height: number, engine: Engine, successCallback?: (data: string) => void, mimeType: string = "image/png", fileName?: string): void {
- // Read the contents of the framebuffer
- var numberOfChannelsByLine = width * 4;
- var halfHeight = height / 2;
- //Reading datas from WebGL
- var data = engine.readPixels(0, 0, width, height);
- //To flip image on Y axis.
- for (var i = 0; i < halfHeight; i++) {
- for (var j = 0; j < numberOfChannelsByLine; j++) {
- var currentCell = j + i * numberOfChannelsByLine;
- var targetLine = height - i - 1;
- var targetCell = j + targetLine * numberOfChannelsByLine;
- var temp = data[currentCell];
- data[currentCell] = data[targetCell];
- data[targetCell] = temp;
- }
- }
- // Create a 2D canvas to store the result
- if (!screenshotCanvas) {
- screenshotCanvas = document.createElement('canvas');
- }
- screenshotCanvas.width = width;
- screenshotCanvas.height = height;
- var context = screenshotCanvas.getContext('2d');
- if (context) {
- // Copy the pixels to a 2D canvas
- var imageData = context.createImageData(width, height);
- var castData = <any>(imageData.data);
- castData.set(data);
- context.putImageData(imageData, 0, 0);
- Tools.EncodeScreenshotCanvasData(successCallback, mimeType, fileName);
- }
- }
- /**
- * Converts the canvas data to blob.
- * This acts as a polyfill for browsers not supporting the to blob function.
- * @param canvas Defines the canvas to extract the data from
- * @param successCallback Defines the callback triggered once the data are available
- * @param mimeType Defines the mime type of the result
- */
- static ToBlob(canvas: HTMLCanvasElement, successCallback: (blob: Nullable<Blob>) => void, mimeType: string = "image/png"): void {
- // We need HTMLCanvasElement.toBlob for HD screenshots
- if (!canvas.toBlob) {
- // low performance polyfill based on toDataURL (https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob)
- canvas.toBlob = function(callback, type, quality) {
- setTimeout(() => {
- var binStr = atob(this.toDataURL(type, quality).split(',')[1]),
- len = binStr.length,
- arr = new Uint8Array(len);
- for (var i = 0; i < len; i++) {
- arr[i] = binStr.charCodeAt(i);
- }
- callback(new Blob([arr]));
- });
- };
- }
- canvas.toBlob(function(blob) {
- successCallback(blob);
- }, mimeType);
- }
- /**
- * Encodes the canvas data to base 64 or automatically download the result if filename is defined
- * @param successCallback defines the callback triggered once the data are available
- * @param mimeType defines the mime type of the result
- * @param fileName defines he filename to download. If present, the result will automatically be downloaded
- */
- static EncodeScreenshotCanvasData(successCallback?: (data: string) => void, mimeType: string = "image/png", fileName?: string): void {
- if (successCallback) {
- var base64Image = screenshotCanvas.toDataURL(mimeType);
- successCallback(base64Image);
- }
- else {
- this.ToBlob(screenshotCanvas, function(blob) {
- //Creating a link if the browser have the download attribute on the a tag, to automatically start download generated image.
- if (("download" in document.createElement("a"))) {
- if (!fileName) {
- var date = new Date();
- var stringDate = (date.getFullYear() + "-" + (date.getMonth() + 1)).slice(2) + "-" + date.getDate() + "_" + date.getHours() + "-" + ('0' + date.getMinutes()).slice(-2);
- fileName = "screenshot_" + stringDate + ".png";
- }
- Tools.Download(blob!, fileName);
- }
- else {
- var url = URL.createObjectURL(blob);
- var newWindow = window.open("");
- if (!newWindow) { return; }
- var img = newWindow.document.createElement("img");
- img.onload = function() {
- // no longer need to read the blob so it's revoked
- URL.revokeObjectURL(url);
- };
- img.src = url;
- newWindow.document.body.appendChild(img);
- }
- }, mimeType);
- }
- }
- /**
- * Downloads a blob in the browser
- * @param blob defines the blob to download
- * @param fileName defines the name of the downloaded file
- */
- public static Download(blob: Blob, fileName: string): void {
- if (navigator && navigator.msSaveBlob) {
- navigator.msSaveBlob(blob, fileName);
- return;
- }
- var url = window.URL.createObjectURL(blob);
- var a = document.createElement("a");
- document.body.appendChild(a);
- a.style.display = "none";
- a.href = url;
- a.download = fileName;
- a.addEventListener("click", () => {
- if (a.parentElement) {
- a.parentElement.removeChild(a);
- }
- });
- a.click();
- window.URL.revokeObjectURL(url);
- }
- /**
- * Captures a screenshot of the current rendering
- * @see http://doc.babylonjs.com/how_to/render_scene_on_a_png
- * @param engine defines the rendering engine
- * @param camera defines the source camera
- * @param size This parameter can be set to a single number or to an object with the
- * following (optional) properties: precision, width, height. If a single number is passed,
- * it will be used for both width and height. If an object is passed, the screenshot size
- * will be derived from the parameters. The precision property is a multiplier allowing
- * rendering at a higher or lower resolution
- * @param successCallback defines the callback receives a single parameter which contains the
- * screenshot as a string of base64-encoded characters. This string can be assigned to the
- * src parameter of an <img> to display it
- * @param mimeType defines the MIME type of the screenshot image (default: image/png).
- * Check your browser for supported MIME types
- */
- public static CreateScreenshot(engine: Engine, camera: Camera, size: any, successCallback?: (data: string) => void, mimeType: string = "image/png"): void {
- var width: number;
- var height: number;
- // If a precision value is specified
- if (size.precision) {
- width = Math.round(engine.getRenderWidth() * size.precision);
- height = Math.round(width / engine.getAspectRatio(camera));
- }
- else if (size.width && size.height) {
- width = size.width;
- height = size.height;
- }
- //If passing only width, computing height to keep display canvas ratio.
- else if (size.width && !size.height) {
- width = size.width;
- height = Math.round(width / engine.getAspectRatio(camera));
- }
- //If passing only height, computing width to keep display canvas ratio.
- else if (size.height && !size.width) {
- height = size.height;
- width = Math.round(height * engine.getAspectRatio(camera));
- }
- //Assuming here that "size" parameter is a number
- else if (!isNaN(size)) {
- height = size;
- width = size;
- }
- else {
- Tools.Error("Invalid 'size' parameter !");
- return;
- }
- if (!screenshotCanvas) {
- screenshotCanvas = document.createElement('canvas');
- }
- screenshotCanvas.width = width;
- screenshotCanvas.height = height;
- var renderContext = screenshotCanvas.getContext("2d");
- var ratio = engine.getRenderWidth() / engine.getRenderHeight();
- var newWidth = width;
- var newHeight = newWidth / ratio;
- if (newHeight > height) {
- newHeight = height;
- newWidth = newHeight * ratio;
- }
- var offsetX = Math.max(0, width - newWidth) / 2;
- var offsetY = Math.max(0, height - newHeight) / 2;
- var renderingCanvas = engine.getRenderingCanvas();
- if (renderContext && renderingCanvas) {
- renderContext.drawImage(renderingCanvas, offsetX, offsetY, newWidth, newHeight);
- }
- Tools.EncodeScreenshotCanvasData(successCallback, mimeType);
- }
- /**
- * Generates an image screenshot from the specified camera.
- * @see http://doc.babylonjs.com/how_to/render_scene_on_a_png
- * @param engine The engine to use for rendering
- * @param camera The camera to use for rendering
- * @param size This parameter can be set to a single number or to an object with the
- * following (optional) properties: precision, width, height. If a single number is passed,
- * it will be used for both width and height. If an object is passed, the screenshot size
- * will be derived from the parameters. The precision property is a multiplier allowing
- * rendering at a higher or lower resolution
- * @param successCallback The callback receives a single parameter which contains the
- * screenshot as a string of base64-encoded characters. This string can be assigned to the
- * src parameter of an <img> to display it
- * @param mimeType The MIME type of the screenshot image (default: image/png).
- * Check your browser for supported MIME types
- * @param samples Texture samples (default: 1)
- * @param antialiasing Whether antialiasing should be turned on or not (default: false)
- * @param fileName A name for for the downloaded file.
- */
- public static CreateScreenshotUsingRenderTarget(engine: Engine, camera: Camera, size: any, successCallback?: (data: string) => void, mimeType: string = "image/png", samples: number = 1, antialiasing: boolean = false, fileName?: string): void {
- var width: number;
- var height: number;
- //If a precision value is specified
- if (size.precision) {
- width = Math.round(engine.getRenderWidth() * size.precision);
- height = Math.round(width / engine.getAspectRatio(camera));
- size = { width: width, height: height };
- }
- else if (size.width && size.height) {
- width = size.width;
- height = size.height;
- }
- //If passing only width, computing height to keep display canvas ratio.
- else if (size.width && !size.height) {
- width = size.width;
- height = Math.round(width / engine.getAspectRatio(camera));
- size = { width: width, height: height };
- }
- //If passing only height, computing width to keep display canvas ratio.
- else if (size.height && !size.width) {
- height = size.height;
- width = Math.round(height * engine.getAspectRatio(camera));
- size = { width: width, height: height };
- }
- //Assuming here that "size" parameter is a number
- else if (!isNaN(size)) {
- height = size;
- width = size;
- }
- else {
- Tools.Error("Invalid 'size' parameter !");
- return;
- }
- var scene = camera.getScene();
- var previousCamera: Nullable<Camera> = null;
- if (scene.activeCamera !== camera) {
- previousCamera = scene.activeCamera;
- scene.activeCamera = camera;
- }
- // At this point size can be a number, or an object (according to engine.prototype.createRenderTargetTexture method)
- var texture = new RenderTargetTexture("screenShot", size, scene, false, false, Engine.TEXTURETYPE_UNSIGNED_INT, false, Texture.NEAREST_SAMPLINGMODE);
- texture.renderList = null;
- texture.samples = samples;
- if (antialiasing) {
- texture.addPostProcess(new FxaaPostProcess('antialiasing', 1.0, scene.activeCamera));
- }
- texture.onAfterRenderObservable.add(() => {
- Tools.DumpFramebuffer(width, height, engine, successCallback, mimeType, fileName);
- });
- scene.incrementRenderId();
- scene.resetCachedMaterial();
- texture.render(true);
- texture.dispose();
- if (previousCamera) {
- scene.activeCamera = previousCamera;
- }
- camera.getProjectionMatrix(true); // Force cache refresh;
- }
- /**
- * Validates if xhr data is correct
- * @param xhr defines the request to validate
- * @param dataType defines the expected data type
- * @returns true if data is correct
- */
- public static ValidateXHRData(xhr: XMLHttpRequest, dataType = 7): boolean {
- // 1 for text (.babylon, manifest and shaders), 2 for TGA, 4 for DDS, 7 for all
- try {
- if (dataType & 1) {
- if (xhr.responseText && xhr.responseText.length > 0) {
- return true;
- } else if (dataType === 1) {
- return false;
- }
- }
- if (dataType & 2) {
- // Check header width and height since there is no "TGA" magic number
- var tgaHeader = TGATools.GetTGAHeader(xhr.response);
- if (tgaHeader.width && tgaHeader.height && tgaHeader.width > 0 && tgaHeader.height > 0) {
- return true;
- } else if (dataType === 2) {
- return false;
- }
- }
- if (dataType & 4) {
- // Check for the "DDS" magic number
- var ddsHeader = new Uint8Array(xhr.response, 0, 3);
- if (ddsHeader[0] === 68 && ddsHeader[1] === 68 && ddsHeader[2] === 83) {
- return true;
- } else {
- return false;
- }
- }
- } catch (e) {
- // Global protection
- }
- return false;
- }
- /**
- * Implementation from http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#answer-2117523
- * Be aware Math.random() could cause collisions, but:
- * "All but 6 of the 128 bits of the ID are randomly generated, which means that for any two ids, there's a 1 in 2^^122 (or 5.3x10^^36) chance they'll collide"
- * @returns a pseudo random id
- */
- public static RandomId(): string {
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
- var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
- return v.toString(16);
- });
- }
- /**
- * Test if the given uri is a base64 string
- * @param uri The uri to test
- * @return True if the uri is a base64 string or false otherwise
- */
- public static IsBase64(uri: string): boolean {
- return uri.length < 5 ? false : uri.substr(0, 5) === "data:";
- }
- /**
- * Decode the given base64 uri.
- * @param uri The uri to decode
- * @return The decoded base64 data.
- */
- public static DecodeBase64(uri: string): ArrayBuffer {
- const decodedString = atob(uri.split(",")[1]);
- const bufferLength = decodedString.length;
- const bufferView = new Uint8Array(new ArrayBuffer(bufferLength));
- for (let i = 0; i < bufferLength; i++) {
- bufferView[i] = decodedString.charCodeAt(i);
- }
- return bufferView.buffer;
- }
- // Logs
- /**
- * No log
- */
- public static readonly NoneLogLevel = 0;
- /**
- * Only message logs
- */
- public static readonly MessageLogLevel = 1;
- /**
- * Only warning logs
- */
- public static readonly WarningLogLevel = 2;
- /**
- * Only error logs
- */
- public static readonly ErrorLogLevel = 4;
- /**
- * All logs
- */
- public static readonly AllLogLevel = 7;
- private static _LogCache = "";
- /**
- * Gets a value indicating the number of loading errors
- * @ignorenaming
- */
- public static errorsCount = 0;
- /**
- * Callback called when a new log is added
- */
- public static OnNewCacheEntry: (entry: string) => void;
- private static _AddLogEntry(entry: string) {
- Tools._LogCache = entry + Tools._LogCache;
- if (Tools.OnNewCacheEntry) {
- Tools.OnNewCacheEntry(entry);
- }
- }
- private static _FormatMessage(message: string): string {
- var padStr = (i: number) => (i < 10) ? "0" + i : "" + i;
- var date = new Date();
- return "[" + padStr(date.getHours()) + ":" + padStr(date.getMinutes()) + ":" + padStr(date.getSeconds()) + "]: " + message;
- }
- private static _LogDisabled(message: string): void {
- // nothing to do
- }
- private static _LogEnabled(message: string): void {
- var formattedMessage = Tools._FormatMessage(message);
- console.log("BJS - " + formattedMessage);
- var entry = "<div style='color:white'>" + formattedMessage + "</div><br>";
- Tools._AddLogEntry(entry);
- }
- private static _WarnDisabled(message: string): void {
- // nothing to do
- }
- private static _WarnEnabled(message: string): void {
- var formattedMessage = Tools._FormatMessage(message);
- console.warn("BJS - " + formattedMessage);
- var entry = "<div style='color:orange'>" + formattedMessage + "</div><br>";
- Tools._AddLogEntry(entry);
- }
- private static _ErrorDisabled(message: string): void {
- // nothing to do
- }
- private static _ErrorEnabled(message: string): void {
- Tools.errorsCount++;
- var formattedMessage = Tools._FormatMessage(message);
- console.error("BJS - " + formattedMessage);
- var entry = "<div style='color:red'>" + formattedMessage + "</div><br>";
- Tools._AddLogEntry(entry);
- }
- /**
- * Log a message to the console
- */
- public static Log: (message: string) => void = Tools._LogEnabled;
- /**
- * Write a warning message to the console
- */
- public static Warn: (message: string) => void = Tools._WarnEnabled;
- /**
- * Write an error message to the console
- */
- public static Error: (message: string) => void = Tools._ErrorEnabled;
- /**
- * Gets current log cache (list of logs)
- */
- public static get LogCache(): string {
- return Tools._LogCache;
- }
- /**
- * Clears the log cache
- */
- public static ClearLogCache(): void {
- Tools._LogCache = "";
- Tools.errorsCount = 0;
- }
- /**
- * Sets the current log level (MessageLogLevel / WarningLogLevel / ErrorLogLevel)
- */
- public static set LogLevels(level: number) {
- if ((level & Tools.MessageLogLevel) === Tools.MessageLogLevel) {
- Tools.Log = Tools._LogEnabled;
- }
- else {
- Tools.Log = Tools._LogDisabled;
- }
- if ((level & Tools.WarningLogLevel) === Tools.WarningLogLevel) {
- Tools.Warn = Tools._WarnEnabled;
- }
- else {
- Tools.Warn = Tools._WarnDisabled;
- }
- if ((level & Tools.ErrorLogLevel) === Tools.ErrorLogLevel) {
- Tools.Error = Tools._ErrorEnabled;
- }
- else {
- Tools.Error = Tools._ErrorDisabled;
- }
- }
- /**
- * Checks if the loaded document was accessed via `file:`-Protocol.
- * @returns boolean
- */
- public static IsFileURL(): boolean {
- return location.protocol === "file:";
- }
- /**
- * Checks if the window object exists
- * @returns true if the window object exists
- */
- public static IsWindowObjectExist(): boolean {
- return (typeof window) !== "undefined";
- }
- // Performances
- /**
- * No performance log
- */
- public static readonly PerformanceNoneLogLevel = 0;
- /**
- * Use user marks to log performance
- */
- public static readonly PerformanceUserMarkLogLevel = 1;
- /**
- * Log performance to the console
- */
- public static readonly PerformanceConsoleLogLevel = 2;
- private static _performance: Performance;
- /**
- * Sets the current performance log level
- */
- public static set PerformanceLogLevel(level: number) {
- if ((level & Tools.PerformanceUserMarkLogLevel) === Tools.PerformanceUserMarkLogLevel) {
- Tools.StartPerformanceCounter = Tools._StartUserMark;
- Tools.EndPerformanceCounter = Tools._EndUserMark;
- return;
- }
- if ((level & Tools.PerformanceConsoleLogLevel) === Tools.PerformanceConsoleLogLevel) {
- Tools.StartPerformanceCounter = Tools._StartPerformanceConsole;
- Tools.EndPerformanceCounter = Tools._EndPerformanceConsole;
- return;
- }
- Tools.StartPerformanceCounter = Tools._StartPerformanceCounterDisabled;
- Tools.EndPerformanceCounter = Tools._EndPerformanceCounterDisabled;
- }
- private static _StartPerformanceCounterDisabled(counterName: string, condition?: boolean): void {
- }
- private static _EndPerformanceCounterDisabled(counterName: string, condition?: boolean): void {
- }
- private static _StartUserMark(counterName: string, condition = true): void {
- if (!Tools._performance) {
- if (!Tools.IsWindowObjectExist()) {
- return;
- }
- Tools._performance = window.performance;
- }
- if (!condition || !Tools._performance.mark) {
- return;
- }
- Tools._performance.mark(counterName + "-Begin");
- }
- private static _EndUserMark(counterName: string, condition = true): void {
- if (!condition || !Tools._performance.mark) {
- return;
- }
- Tools._performance.mark(counterName + "-End");
- Tools._performance.measure(counterName, counterName + "-Begin", counterName + "-End");
- }
- private static _StartPerformanceConsole(counterName: string, condition = true): void {
- if (!condition) {
- return;
- }
- Tools._StartUserMark(counterName, condition);
- if (console.time) {
- console.time(counterName);
- }
- }
- private static _EndPerformanceConsole(counterName: string, condition = true): void {
- if (!condition) {
- return;
- }
- Tools._EndUserMark(counterName, condition);
- if (console.time) {
- console.timeEnd(counterName);
- }
- }
- /**
- * Starts a performance counter
- */
- public static StartPerformanceCounter: (counterName: string, condition?: boolean) => void = Tools._StartPerformanceCounterDisabled;
- /**
- * Ends a specific performance coutner
- */
- public static EndPerformanceCounter: (counterName: string, condition?: boolean) => void = Tools._EndPerformanceCounterDisabled;
- /**
- * Gets either window.performance.now() if supported or Date.now() else
- */
- public static get Now(): number {
- if (Tools.IsWindowObjectExist() && window.performance && window.performance.now) {
- return window.performance.now();
- }
- return Date.now();
- }
- /**
- * This method will return the name of the class used to create the instance of the given object.
- * It will works only on Javascript basic data types (number, string, ...) and instance of class declared with the @className decorator.
- * @param object the object to get the class name from
- * @param isType defines if the object is actually a type
- * @returns the name of the class, will be "object" for a custom data type not using the @className decorator
- */
- public static GetClassName(object: any, isType: boolean = false): string {
- let name = null;
- if (!isType && object.getClassName) {
- name = object.getClassName();
- } else {
- if (object instanceof Object) {
- let classObj = isType ? object : Object.getPrototypeOf(object);
- name = classObj.constructor["__bjsclassName__"];
- }
- if (!name) {
- name = typeof object;
- }
- }
- return name;
- }
- /**
- * Gets the first element of an array satisfying a given predicate
- * @param array defines the array to browse
- * @param predicate defines the predicate to use
- * @returns null if not found or the element
- */
- public static First<T>(array: Array<T>, predicate: (item: T) => boolean): Nullable<T> {
- for (let el of array) {
- if (predicate(el)) {
- return el;
- }
- }
- return null;
- }
- /**
- * This method will return the name of the full name of the class, including its owning module (if any).
- * It will works only on Javascript basic data types (number, string, ...) and instance of class declared with the @className decorator or implementing a method getClassName():string (in which case the module won't be specified).
- * @param object the object to get the class name from
- * @param isType defines if the object is actually a type
- * @return a string that can have two forms: "moduleName.className" if module was specified when the class' Name was registered or "className" if there was not module specified.
- * @ignorenaming
- */
- public static getFullClassName(object: any, isType: boolean = false): Nullable<string> {
- let className = null;
- let moduleName = null;
- if (!isType && object.getClassName) {
- className = object.getClassName();
- } else {
- if (object instanceof Object) {
- let classObj = isType ? object : Object.getPrototypeOf(object);
- className = classObj.constructor["__bjsclassName__"];
- moduleName = classObj.constructor["__bjsmoduleName__"];
- }
- if (!className) {
- className = typeof object;
- }
- }
- if (!className) {
- return null;
- }
- return ((moduleName != null) ? (moduleName + ".") : "") + className;
- }
- /**
- * Returns a promise that resolves after the given amount of time.
- * @param delay Number of milliseconds to delay
- * @returns Promise that resolves after the given amount of time
- */
- public static DelayAsync(delay: number): Promise<void> {
- return new Promise((resolve) => {
- setTimeout(() => {
- resolve();
- }, delay);
- });
- }
- /**
- * Gets the current gradient from an array of IValueGradient
- * @param ratio defines the current ratio to get
- * @param gradients defines the array of IValueGradient
- * @param updateFunc defines the callback function used to get the final value from the selected gradients
- */
- public static GetCurrentGradient(ratio: number, gradients: IValueGradient[], updateFunc: (current: IValueGradient, next: IValueGradient, scale: number) => void) {
- for (var gradientIndex = 0; gradientIndex < gradients.length - 1; gradientIndex++) {
- let currentGradient = gradients[gradientIndex];
- let nextGradient = gradients[gradientIndex + 1];
- if (ratio >= currentGradient.gradient && ratio <= nextGradient.gradient) {
- let scale = (ratio - currentGradient.gradient) / (nextGradient.gradient - currentGradient.gradient);
- updateFunc(currentGradient, nextGradient, scale);
- return;
- }
- }
- // Use last index if over
- const lastIndex = gradients.length - 1;
- updateFunc(gradients[lastIndex], gradients[lastIndex], 1.0);
- }
- }
- /**
- * This class is used to track a performance counter which is number based.
- * The user has access to many properties which give statistics of different nature.
- *
- * The implementer can track two kinds of Performance Counter: time and count.
- * For time you can optionally call fetchNewFrame() to notify the start of a new frame to monitor, then call beginMonitoring() to start and endMonitoring() to record the lapsed time. endMonitoring takes a newFrame parameter for you to specify if the monitored time should be set for a new frame or accumulated to the current frame being monitored.
- * For count you first have to call fetchNewFrame() to notify the start of a new frame to monitor, then call addCount() how many time required to increment the count value you monitor.
- */
- export class PerfCounter {
- /**
- * Gets or sets a global boolean to turn on and off all the counters
- */
- public static Enabled = true;
- /**
- * Returns the smallest value ever
- */
- public get min(): number {
- return this._min;
- }
- /**
- * Returns the biggest value ever
- */
- public get max(): number {
- return this._max;
- }
- /**
- * Returns the average value since the performance counter is running
- */
- public get average(): number {
- return this._average;
- }
- /**
- * Returns the average value of the last second the counter was monitored
- */
- public get lastSecAverage(): number {
- return this._lastSecAverage;
- }
- /**
- * Returns the current value
- */
- public get current(): number {
- return this._current;
- }
- /**
- * Gets the accumulated total
- */
- public get total(): number {
- return this._totalAccumulated;
- }
- /**
- * Gets the total value count
- */
- public get count(): number {
- return this._totalValueCount;
- }
- /**
- * Creates a new counter
- */
- constructor() {
- this._startMonitoringTime = 0;
- this._min = 0;
- this._max = 0;
- this._average = 0;
- this._lastSecAverage = 0;
- this._current = 0;
- this._totalValueCount = 0;
- this._totalAccumulated = 0;
- this._lastSecAccumulated = 0;
- this._lastSecTime = 0;
- this._lastSecValueCount = 0;
- }
- /**
- * Call this method to start monitoring a new frame.
- * This scenario is typically used when you accumulate monitoring time many times for a single frame, you call this method at the start of the frame, then beginMonitoring to start recording and endMonitoring(false) to accumulated the recorded time to the PerfCounter or addCount() to accumulate a monitored count.
- */
- public fetchNewFrame() {
- this._totalValueCount++;
- this._current = 0;
- this._lastSecValueCount++;
- }
- /**
- * Call this method to monitor a count of something (e.g. mesh drawn in viewport count)
- * @param newCount the count value to add to the monitored count
- * @param fetchResult true when it's the last time in the frame you add to the counter and you wish to update the statistics properties (min/max/average), false if you only want to update statistics.
- */
- public addCount(newCount: number, fetchResult: boolean) {
- if (!PerfCounter.Enabled) {
- return;
- }
- this._current += newCount;
- if (fetchResult) {
- this._fetchResult();
- }
- }
- /**
- * Start monitoring this performance counter
- */
- public beginMonitoring() {
- if (!PerfCounter.Enabled) {
- return;
- }
- this._startMonitoringTime = Tools.Now;
- }
- /**
- * Compute the time lapsed since the previous beginMonitoring() call.
- * @param newFrame true by default to fetch the result and monitor a new frame, if false the time monitored will be added to the current frame counter
- */
- public endMonitoring(newFrame: boolean = true) {
- if (!PerfCounter.Enabled) {
- return;
- }
- if (newFrame) {
- this.fetchNewFrame();
- }
- let currentTime = Tools.Now;
- this._current = currentTime - this._startMonitoringTime;
- if (newFrame) {
- this._fetchResult();
- }
- }
- private _fetchResult() {
- this._totalAccumulated += this._current;
- this._lastSecAccumulated += this._current;
- // Min/Max update
- this._min = Math.min(this._min, this._current);
- this._max = Math.max(this._max, this._current);
- this._average = this._totalAccumulated / this._totalValueCount;
- // Reset last sec?
- let now = Tools.Now;
- if ((now - this._lastSecTime) > 1000) {
- this._lastSecAverage = this._lastSecAccumulated / this._lastSecValueCount;
- this._lastSecTime = now;
- this._lastSecAccumulated = 0;
- this._lastSecValueCount = 0;
- }
- }
- private _startMonitoringTime: number;
- private _min: number;
- private _max: number;
- private _average: number;
- private _current: number;
- private _totalValueCount: number;
- private _totalAccumulated: number;
- private _lastSecAverage: number;
- private _lastSecAccumulated: number;
- private _lastSecTime: number;
- private _lastSecValueCount: number;
- }
- /**
- * Use this className as a decorator on a given class definition to add it a name and optionally its module.
- * You can then use the Tools.getClassName(obj) on an instance to retrieve its class name.
- * This method is the only way to get it done in all cases, even if the .js file declaring the class is minified
- * @param name The name of the class, case should be preserved
- * @param module The name of the Module hosting the class, optional, but strongly recommended to specify if possible. Case should be preserved.
- */
- export function className(name: string, module?: string): (target: Object) => void {
- return (target: Object) => {
- (<any>target)["__bjsclassName__"] = name;
- (<any>target)["__bjsmoduleName__"] = (module != null) ? module : null;
- };
- }
- /**
- * An implementation of a loop for asynchronous functions.
- */
- export class AsyncLoop {
- /**
- * Defines the current index of the loop.
- */
- public index: number;
- private _done: boolean;
- private _fn: (asyncLoop: AsyncLoop) => void;
- private _successCallback: () => void;
- /**
- * Constructor.
- * @param iterations the number of iterations.
- * @param func the function to run each iteration
- * @param successCallback the callback that will be called upon succesful execution
- * @param offset starting offset.
- */
- constructor(
- /**
- * Defines the number of iterations for the loop
- */
- public iterations: number,
- func: (asyncLoop: AsyncLoop) => void,
- successCallback: () => void,
- offset: number = 0) {
- this.index = offset - 1;
- this._done = false;
- this._fn = func;
- this._successCallback = successCallback;
- }
- /**
- * Execute the next iteration. Must be called after the last iteration was finished.
- */
- public executeNext(): void {
- if (!this._done) {
- if (this.index + 1 < this.iterations) {
- ++this.index;
- this._fn(this);
- } else {
- this.breakLoop();
- }
- }
- }
- /**
- * Break the loop and run the success callback.
- */
- public breakLoop(): void {
- this._done = true;
- this._successCallback();
- }
- /**
- * Create and run an async loop.
- * @param iterations the number of iterations.
- * @param fn the function to run each iteration
- * @param successCallback the callback that will be called upon succesful execution
- * @param offset starting offset.
- * @returns the created async loop object
- */
- public static Run(iterations: number, fn: (asyncLoop: AsyncLoop) => void, successCallback: () => void, offset: number = 0): AsyncLoop {
- var loop = new AsyncLoop(iterations, fn, successCallback, offset);
- loop.executeNext();
- return loop;
- }
- /**
- * A for-loop that will run a given number of iterations synchronous and the rest async.
- * @param iterations total number of iterations
- * @param syncedIterations number of synchronous iterations in each async iteration.
- * @param fn the function to call each iteration.
- * @param callback a success call back that will be called when iterating stops.
- * @param breakFunction a break condition (optional)
- * @param timeout timeout settings for the setTimeout function. default - 0.
- * @returns the created async loop object
- */
- public static SyncAsyncForLoop(iterations: number, syncedIterations: number, fn: (iteration: number) => void, callback: () => void, breakFunction?: () => boolean, timeout: number = 0): AsyncLoop {
- return AsyncLoop.Run(Math.ceil(iterations / syncedIterations), (loop: AsyncLoop) => {
- if (breakFunction && breakFunction()) { loop.breakLoop(); }
- else {
- setTimeout(() => {
- for (var i = 0; i < syncedIterations; ++i) {
- var iteration = (loop.index * syncedIterations) + i;
- if (iteration >= iterations) { break; }
- fn(iteration);
- if (breakFunction && breakFunction()) {
- loop.breakLoop();
- break;
- }
- }
- loop.executeNext();
- }, timeout);
- }
- }, callback);
- }
- }
- }
|