tools.ts 78 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070
  1. import { FloatArray, IndicesArray, Nullable } from "../types";
  2. import { Color4, Color3, Vector2, Vector3 } from "../Maths/math";
  3. import { Scalar } from "../Maths/math.scalar";
  4. import { IOfflineProvider } from "../Offline/IOfflineProvider";
  5. import { Observable } from "./observable";
  6. import { FilesInputStore } from "./filesInputStore";
  7. import { Constants } from "../Engines/constants";
  8. import { DomManagement } from "./domManagement";
  9. import { Logger } from "./logger";
  10. import { _TypeStore } from "./typeStore";
  11. import { DeepCopier } from "./deepCopier";
  12. import { PrecisionDate } from './precisionDate';
  13. import { _DevTools } from './devTools';
  14. import { WebRequest } from './webRequest';
  15. declare type Camera = import("../Cameras/camera").Camera;
  16. declare type Engine = import("../Engines/engine").Engine;
  17. declare type Animation = import("../Animations/animation").Animation;
  18. declare type AnimationGroup = import("../Animations/animationGroup").AnimationGroup;
  19. /**
  20. * Interface for any object that can request an animation frame
  21. */
  22. export interface ICustomAnimationFrameRequester {
  23. /**
  24. * This function will be called when the render loop is ready. If this is not populated, the engine's renderloop function will be called
  25. */
  26. renderFunction?: Function;
  27. /**
  28. * Called to request the next frame to render to
  29. * @see https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
  30. */
  31. requestAnimationFrame: Function;
  32. /**
  33. * You can pass this value to cancelAnimationFrame() to cancel the refresh callback request
  34. * @see https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame#Return_value
  35. */
  36. requestID?: number;
  37. }
  38. /**
  39. * Interface containing an array of animations
  40. */
  41. export interface IAnimatable {
  42. /**
  43. * Array of animations
  44. */
  45. animations: Nullable<Array<Animation>>;
  46. animationGroups: Nullable<Array<AnimationGroup>>;
  47. }
  48. /** Interface used by value gradients (color, factor, ...) */
  49. export interface IValueGradient {
  50. /**
  51. * Gets or sets the gradient value (between 0 and 1)
  52. */
  53. gradient: number;
  54. }
  55. /** Class used to store color4 gradient */
  56. export class ColorGradient implements IValueGradient {
  57. /**
  58. * Gets or sets the gradient value (between 0 and 1)
  59. */
  60. public gradient: number;
  61. /**
  62. * Gets or sets first associated color
  63. */
  64. public color1: Color4;
  65. /**
  66. * Gets or sets second associated color
  67. */
  68. public color2?: Color4;
  69. /**
  70. * Will get a color picked randomly between color1 and color2.
  71. * If color2 is undefined then color1 will be used
  72. * @param result defines the target Color4 to store the result in
  73. */
  74. public getColorToRef(result: Color4) {
  75. if (!this.color2) {
  76. result.copyFrom(this.color1);
  77. return;
  78. }
  79. Color4.LerpToRef(this.color1, this.color2, Math.random(), result);
  80. }
  81. }
  82. /** Class used to store color 3 gradient */
  83. export class Color3Gradient implements IValueGradient {
  84. /**
  85. * Gets or sets the gradient value (between 0 and 1)
  86. */
  87. public gradient: number;
  88. /**
  89. * Gets or sets the associated color
  90. */
  91. public color: Color3;
  92. }
  93. /** Class used to store factor gradient */
  94. export class FactorGradient implements IValueGradient {
  95. /**
  96. * Gets or sets the gradient value (between 0 and 1)
  97. */
  98. public gradient: number;
  99. /**
  100. * Gets or sets first associated factor
  101. */
  102. public factor1: number;
  103. /**
  104. * Gets or sets second associated factor
  105. */
  106. public factor2?: number;
  107. /**
  108. * Will get a number picked randomly between factor1 and factor2.
  109. * If factor2 is undefined then factor1 will be used
  110. * @returns the picked number
  111. */
  112. public getFactor(): number {
  113. if (this.factor2 === undefined) {
  114. return this.factor1;
  115. }
  116. return Scalar.Lerp(this.factor1, this.factor2, Math.random());
  117. }
  118. }
  119. /**
  120. * @ignore
  121. * Application error to support additional information when loading a file
  122. */
  123. export class LoadFileError extends Error {
  124. // See https://stackoverflow.com/questions/12915412/how-do-i-extend-a-host-object-e-g-error-in-typescript
  125. // and https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work
  126. // Polyfill for Object.setPrototypeOf if necessary.
  127. private static _setPrototypeOf: (o: any, proto: object | null) => any =
  128. (Object as any).setPrototypeOf || ((o, proto) => { o.__proto__ = proto; return o; });
  129. /**
  130. * Creates a new LoadFileError
  131. * @param message defines the message of the error
  132. * @param request defines the optional web request
  133. */
  134. constructor(
  135. message: string,
  136. /** defines the optional web request */
  137. public request?: WebRequest
  138. ) {
  139. super(message);
  140. this.name = "LoadFileError";
  141. LoadFileError._setPrototypeOf(this, LoadFileError.prototype);
  142. }
  143. }
  144. /**
  145. * Class used to define a retry strategy when error happens while loading assets
  146. */
  147. export class RetryStrategy {
  148. /**
  149. * Function used to defines an exponential back off strategy
  150. * @param maxRetries defines the maximum number of retries (3 by default)
  151. * @param baseInterval defines the interval between retries
  152. * @returns the strategy function to use
  153. */
  154. public static ExponentialBackoff(maxRetries = 3, baseInterval = 500) {
  155. return (url: string, request: WebRequest, retryIndex: number): number => {
  156. if (request.status !== 0 || retryIndex >= maxRetries || url.indexOf("file:") !== -1) {
  157. return -1;
  158. }
  159. return Math.pow(2, retryIndex) * baseInterval;
  160. };
  161. }
  162. }
  163. /**
  164. * File request interface
  165. */
  166. export interface IFileRequest {
  167. /**
  168. * Raised when the request is complete (success or error).
  169. */
  170. onCompleteObservable: Observable<IFileRequest>;
  171. /**
  172. * Aborts the request for a file.
  173. */
  174. abort: () => void;
  175. }
  176. /**
  177. * Class containing a set of static utilities functions
  178. */
  179. export class Tools {
  180. /**
  181. * Gets or sets the base URL to use to load assets
  182. */
  183. public static BaseUrl = "";
  184. /**
  185. * Enable/Disable Custom HTTP Request Headers globally.
  186. * default = false
  187. * @see CustomRequestHeaders
  188. */
  189. public static UseCustomRequestHeaders: boolean = false;
  190. /**
  191. * Custom HTTP Request Headers to be sent with XMLHttpRequests
  192. * i.e. when loading files, where the server/service expects an Authorization header
  193. */
  194. public static CustomRequestHeaders = WebRequest.CustomRequestHeaders;
  195. /**
  196. * Gets or sets the retry strategy to apply when an error happens while loading an asset
  197. */
  198. public static DefaultRetryStrategy = RetryStrategy.ExponentialBackoff();
  199. /**
  200. * Default behaviour for cors in the application.
  201. * It can be a string if the expected behavior is identical in the entire app.
  202. * Or a callback to be able to set it per url or on a group of them (in case of Video source for instance)
  203. */
  204. public static CorsBehavior: string | ((url: string | string[]) => string) = "anonymous";
  205. /**
  206. * Gets or sets a global variable indicating if fallback texture must be used when a texture cannot be loaded
  207. * @ignorenaming
  208. */
  209. public static UseFallbackTexture = true;
  210. /**
  211. * Use this object to register external classes like custom textures or material
  212. * to allow the laoders to instantiate them
  213. */
  214. public static RegisteredExternalClasses: { [key: string]: Object } = {};
  215. /**
  216. * Texture content used if a texture cannot loaded
  217. * @ignorenaming
  218. */
  219. 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";
  220. /**
  221. * Read the content of a byte array at a specified coordinates (taking in account wrapping)
  222. * @param u defines the coordinate on X axis
  223. * @param v defines the coordinate on Y axis
  224. * @param width defines the width of the source data
  225. * @param height defines the height of the source data
  226. * @param pixels defines the source byte array
  227. * @param color defines the output color
  228. */
  229. public static FetchToRef(u: number, v: number, width: number, height: number, pixels: Uint8Array, color: Color4): void {
  230. let wrappedU = ((Math.abs(u) * width) % width) | 0;
  231. let wrappedV = ((Math.abs(v) * height) % height) | 0;
  232. let position = (wrappedU + wrappedV * width) * 4;
  233. color.r = pixels[position] / 255;
  234. color.g = pixels[position + 1] / 255;
  235. color.b = pixels[position + 2] / 255;
  236. color.a = pixels[position + 3] / 255;
  237. }
  238. /**
  239. * Interpolates between a and b via alpha
  240. * @param a The lower value (returned when alpha = 0)
  241. * @param b The upper value (returned when alpha = 1)
  242. * @param alpha The interpolation-factor
  243. * @return The mixed value
  244. */
  245. public static Mix(a: number, b: number, alpha: number): number {
  246. return a * (1 - alpha) + b * alpha;
  247. }
  248. /**
  249. * Tries to instantiate a new object from a given class name
  250. * @param className defines the class name to instantiate
  251. * @returns the new object or null if the system was not able to do the instantiation
  252. */
  253. public static Instantiate(className: string): any {
  254. if (Tools.RegisteredExternalClasses && Tools.RegisteredExternalClasses[className]) {
  255. return Tools.RegisteredExternalClasses[className];
  256. }
  257. const internalClass = _TypeStore.GetClass(className);
  258. if (internalClass) {
  259. return internalClass;
  260. }
  261. Logger.Warn(className + " not found, you may have missed an import.");
  262. var arr = className.split(".");
  263. var fn: any = (window || this);
  264. for (var i = 0, len = arr.length; i < len; i++) {
  265. fn = fn[arr[i]];
  266. }
  267. if (typeof fn !== "function") {
  268. return null;
  269. }
  270. return fn;
  271. }
  272. /**
  273. * Provides a slice function that will work even on IE
  274. * @param data defines the array to slice
  275. * @param start defines the start of the data (optional)
  276. * @param end defines the end of the data (optional)
  277. * @returns the new sliced array
  278. */
  279. public static Slice<T>(data: T, start?: number, end?: number): T {
  280. if ((data as any).slice) {
  281. return (data as any).slice(start, end);
  282. }
  283. return Array.prototype.slice.call(data, start, end);
  284. }
  285. /**
  286. * Polyfill for setImmediate
  287. * @param action defines the action to execute after the current execution block
  288. */
  289. public static SetImmediate(action: () => void) {
  290. if (DomManagement.IsWindowObjectExist() && window.setImmediate) {
  291. window.setImmediate(action);
  292. } else {
  293. setTimeout(action, 1);
  294. }
  295. }
  296. /**
  297. * Function indicating if a number is an exponent of 2
  298. * @param value defines the value to test
  299. * @returns true if the value is an exponent of 2
  300. */
  301. public static IsExponentOfTwo(value: number): boolean {
  302. var count = 1;
  303. do {
  304. count *= 2;
  305. } while (count < value);
  306. return count === value;
  307. }
  308. private static _tmpFloatArray = new Float32Array(1);
  309. /**
  310. * Returns the nearest 32-bit single precision float representation of a Number
  311. * @param value A Number. If the parameter is of a different type, it will get converted
  312. * to a number or to NaN if it cannot be converted
  313. * @returns number
  314. */
  315. public static FloatRound(value: number): number {
  316. if (Math.fround) {
  317. return Math.fround(value);
  318. }
  319. return (Tools._tmpFloatArray[0] = value);
  320. }
  321. /**
  322. * Find the next highest power of two.
  323. * @param x Number to start search from.
  324. * @return Next highest power of two.
  325. */
  326. public static CeilingPOT(x: number): number {
  327. x--;
  328. x |= x >> 1;
  329. x |= x >> 2;
  330. x |= x >> 4;
  331. x |= x >> 8;
  332. x |= x >> 16;
  333. x++;
  334. return x;
  335. }
  336. /**
  337. * Find the next lowest power of two.
  338. * @param x Number to start search from.
  339. * @return Next lowest power of two.
  340. */
  341. public static FloorPOT(x: number): number {
  342. x = x | (x >> 1);
  343. x = x | (x >> 2);
  344. x = x | (x >> 4);
  345. x = x | (x >> 8);
  346. x = x | (x >> 16);
  347. return x - (x >> 1);
  348. }
  349. /**
  350. * Find the nearest power of two.
  351. * @param x Number to start search from.
  352. * @return Next nearest power of two.
  353. */
  354. public static NearestPOT(x: number): number {
  355. var c = Tools.CeilingPOT(x);
  356. var f = Tools.FloorPOT(x);
  357. return (c - x) > (x - f) ? f : c;
  358. }
  359. /**
  360. * Get the closest exponent of two
  361. * @param value defines the value to approximate
  362. * @param max defines the maximum value to return
  363. * @param mode defines how to define the closest value
  364. * @returns closest exponent of two of the given value
  365. */
  366. public static GetExponentOfTwo(value: number, max: number, mode = Constants.SCALEMODE_NEAREST): number {
  367. let pot;
  368. switch (mode) {
  369. case Constants.SCALEMODE_FLOOR:
  370. pot = Tools.FloorPOT(value);
  371. break;
  372. case Constants.SCALEMODE_NEAREST:
  373. pot = Tools.NearestPOT(value);
  374. break;
  375. case Constants.SCALEMODE_CEILING:
  376. default:
  377. pot = Tools.CeilingPOT(value);
  378. break;
  379. }
  380. return Math.min(pot, max);
  381. }
  382. /**
  383. * Extracts the filename from a path
  384. * @param path defines the path to use
  385. * @returns the filename
  386. */
  387. public static GetFilename(path: string): string {
  388. var index = path.lastIndexOf("/");
  389. if (index < 0) {
  390. return path;
  391. }
  392. return path.substring(index + 1);
  393. }
  394. /**
  395. * Extracts the "folder" part of a path (everything before the filename).
  396. * @param uri The URI to extract the info from
  397. * @param returnUnchangedIfNoSlash Do not touch the URI if no slashes are present
  398. * @returns The "folder" part of the path
  399. */
  400. public static GetFolderPath(uri: string, returnUnchangedIfNoSlash = false): string {
  401. var index = uri.lastIndexOf("/");
  402. if (index < 0) {
  403. if (returnUnchangedIfNoSlash) {
  404. return uri;
  405. }
  406. return "";
  407. }
  408. return uri.substring(0, index + 1);
  409. }
  410. /**
  411. * Extracts text content from a DOM element hierarchy
  412. * Back Compat only, please use DomManagement.GetDOMTextContent instead.
  413. */
  414. public static GetDOMTextContent = DomManagement.GetDOMTextContent;
  415. /**
  416. * Convert an angle in radians to degrees
  417. * @param angle defines the angle to convert
  418. * @returns the angle in degrees
  419. */
  420. public static ToDegrees(angle: number): number {
  421. return angle * 180 / Math.PI;
  422. }
  423. /**
  424. * Convert an angle in degrees to radians
  425. * @param angle defines the angle to convert
  426. * @returns the angle in radians
  427. */
  428. public static ToRadians(angle: number): number {
  429. return angle * Math.PI / 180;
  430. }
  431. /**
  432. * Encode a buffer to a base64 string
  433. * @param buffer defines the buffer to encode
  434. * @returns the encoded string
  435. */
  436. public static EncodeArrayBufferTobase64(buffer: ArrayBuffer): string {
  437. var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
  438. var output = "";
  439. var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
  440. var i = 0;
  441. var bytes = new Uint8Array(buffer);
  442. while (i < bytes.length) {
  443. chr1 = bytes[i++];
  444. chr2 = i < bytes.length ? bytes[i++] : Number.NaN; // Not sure if the index
  445. chr3 = i < bytes.length ? bytes[i++] : Number.NaN; // checks are needed here
  446. enc1 = chr1 >> 2;
  447. enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
  448. enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
  449. enc4 = chr3 & 63;
  450. if (isNaN(chr2)) {
  451. enc3 = enc4 = 64;
  452. } else if (isNaN(chr3)) {
  453. enc4 = 64;
  454. }
  455. output += keyStr.charAt(enc1) + keyStr.charAt(enc2) +
  456. keyStr.charAt(enc3) + keyStr.charAt(enc4);
  457. }
  458. return "data:image/png;base64," + output;
  459. }
  460. /**
  461. * Extracts minimum and maximum values from a list of indexed positions
  462. * @param positions defines the positions to use
  463. * @param indices defines the indices to the positions
  464. * @param indexStart defines the start index
  465. * @param indexCount defines the end index
  466. * @param bias defines bias value to add to the result
  467. * @return minimum and maximum values
  468. */
  469. public static ExtractMinAndMaxIndexed(positions: FloatArray, indices: IndicesArray, indexStart: number, indexCount: number, bias: Nullable<Vector2> = null): { minimum: Vector3; maximum: Vector3 } {
  470. var minimum = new Vector3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
  471. var maximum = new Vector3(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE);
  472. for (var index = indexStart; index < indexStart + indexCount; index++) {
  473. const offset = indices[index] * 3;
  474. const x = positions[offset];
  475. const y = positions[offset + 1];
  476. const z = positions[offset + 2];
  477. minimum.minimizeInPlaceFromFloats(x, y, z);
  478. maximum.maximizeInPlaceFromFloats(x, y, z);
  479. }
  480. if (bias) {
  481. minimum.x -= minimum.x * bias.x + bias.y;
  482. minimum.y -= minimum.y * bias.x + bias.y;
  483. minimum.z -= minimum.z * bias.x + bias.y;
  484. maximum.x += maximum.x * bias.x + bias.y;
  485. maximum.y += maximum.y * bias.x + bias.y;
  486. maximum.z += maximum.z * bias.x + bias.y;
  487. }
  488. return {
  489. minimum: minimum,
  490. maximum: maximum
  491. };
  492. }
  493. /**
  494. * Extracts minimum and maximum values from a list of positions
  495. * @param positions defines the positions to use
  496. * @param start defines the start index in the positions array
  497. * @param count defines the number of positions to handle
  498. * @param bias defines bias value to add to the result
  499. * @param stride defines the stride size to use (distance between two positions in the positions array)
  500. * @return minimum and maximum values
  501. */
  502. public static ExtractMinAndMax(positions: FloatArray, start: number, count: number, bias: Nullable<Vector2> = null, stride?: number): { minimum: Vector3; maximum: Vector3 } {
  503. var minimum = new Vector3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
  504. var maximum = new Vector3(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE);
  505. if (!stride) {
  506. stride = 3;
  507. }
  508. for (var index = start, offset = start * stride; index < start + count; index++ , offset += stride) {
  509. const x = positions[offset];
  510. const y = positions[offset + 1];
  511. const z = positions[offset + 2];
  512. minimum.minimizeInPlaceFromFloats(x, y, z);
  513. maximum.maximizeInPlaceFromFloats(x, y, z);
  514. }
  515. if (bias) {
  516. minimum.x -= minimum.x * bias.x + bias.y;
  517. minimum.y -= minimum.y * bias.x + bias.y;
  518. minimum.z -= minimum.z * bias.x + bias.y;
  519. maximum.x += maximum.x * bias.x + bias.y;
  520. maximum.y += maximum.y * bias.x + bias.y;
  521. maximum.z += maximum.z * bias.x + bias.y;
  522. }
  523. return {
  524. minimum: minimum,
  525. maximum: maximum
  526. };
  527. }
  528. /**
  529. * Returns an array if obj is not an array
  530. * @param obj defines the object to evaluate as an array
  531. * @param allowsNullUndefined defines a boolean indicating if obj is allowed to be null or undefined
  532. * @returns either obj directly if obj is an array or a new array containing obj
  533. */
  534. public static MakeArray(obj: any, allowsNullUndefined?: boolean): Nullable<Array<any>> {
  535. if (allowsNullUndefined !== true && (obj === undefined || obj == null)) {
  536. return null;
  537. }
  538. return Array.isArray(obj) ? obj : [obj];
  539. }
  540. /**
  541. * Gets the pointer prefix to use
  542. * @returns "pointer" if touch is enabled. Else returns "mouse"
  543. */
  544. public static GetPointerPrefix(): string {
  545. var eventPrefix = "pointer";
  546. // Check if pointer events are supported
  547. if (DomManagement.IsWindowObjectExist() && !window.PointerEvent && !navigator.pointerEnabled) {
  548. eventPrefix = "mouse";
  549. }
  550. return eventPrefix;
  551. }
  552. /**
  553. * Queue a new function into the requested animation frame pool (ie. this function will be executed byt the browser for the next frame)
  554. * @param func - the function to be called
  555. * @param requester - the object that will request the next frame. Falls back to window.
  556. * @returns frame number
  557. */
  558. public static QueueNewFrame(func: () => void, requester?: any): number {
  559. if (!DomManagement.IsWindowObjectExist()) {
  560. return setTimeout(func, 16);
  561. }
  562. if (!requester) {
  563. requester = window;
  564. }
  565. if (requester.requestAnimationFrame) {
  566. return requester.requestAnimationFrame(func);
  567. }
  568. else if (requester.msRequestAnimationFrame) {
  569. return requester.msRequestAnimationFrame(func);
  570. }
  571. else if (requester.webkitRequestAnimationFrame) {
  572. return requester.webkitRequestAnimationFrame(func);
  573. }
  574. else if (requester.mozRequestAnimationFrame) {
  575. return requester.mozRequestAnimationFrame(func);
  576. }
  577. else if (requester.oRequestAnimationFrame) {
  578. return requester.oRequestAnimationFrame(func);
  579. }
  580. else {
  581. return window.setTimeout(func, 16);
  582. }
  583. }
  584. /**
  585. * Ask the browser to promote the current element to fullscreen rendering mode
  586. * @param element defines the DOM element to promote
  587. */
  588. public static RequestFullscreen(element: HTMLElement): void {
  589. var requestFunction = element.requestFullscreen || (<any>element).msRequestFullscreen || (<any>element).webkitRequestFullscreen || (<any>element).mozRequestFullScreen;
  590. if (!requestFunction) { return; }
  591. requestFunction.call(element);
  592. }
  593. /**
  594. * Asks the browser to exit fullscreen mode
  595. */
  596. public static ExitFullscreen(): void {
  597. let anyDoc = document as any;
  598. if (document.exitFullscreen) {
  599. document.exitFullscreen();
  600. }
  601. else if (anyDoc.mozCancelFullScreen) {
  602. anyDoc.mozCancelFullScreen();
  603. }
  604. else if (anyDoc.webkitCancelFullScreen) {
  605. anyDoc.webkitCancelFullScreen();
  606. }
  607. else if (anyDoc.msCancelFullScreen) {
  608. anyDoc.msCancelFullScreen();
  609. }
  610. }
  611. /**
  612. * Sets the cors behavior on a dom element. This will add the required Tools.CorsBehavior to the element.
  613. * @param url define the url we are trying
  614. * @param element define the dom element where to configure the cors policy
  615. */
  616. public static SetCorsBehavior(url: string | string[], element: { crossOrigin: string | null }): void {
  617. if (url && url.indexOf("data:") === 0) {
  618. return;
  619. }
  620. if (Tools.CorsBehavior) {
  621. if (typeof (Tools.CorsBehavior) === 'string' || Tools.CorsBehavior instanceof String) {
  622. element.crossOrigin = <string>Tools.CorsBehavior;
  623. }
  624. else {
  625. var result = Tools.CorsBehavior(url);
  626. if (result) {
  627. element.crossOrigin = result;
  628. }
  629. }
  630. }
  631. }
  632. // External files
  633. /**
  634. * Removes unwanted characters from an url
  635. * @param url defines the url to clean
  636. * @returns the cleaned url
  637. */
  638. public static CleanUrl(url: string): string {
  639. url = url.replace(/#/mg, "%23");
  640. return url;
  641. }
  642. /**
  643. * Gets or sets a function used to pre-process url before using them to load assets
  644. */
  645. public static PreprocessUrl = (url: string) => {
  646. return url;
  647. }
  648. /**
  649. * Loads an image as an HTMLImageElement.
  650. * @param input url string, ArrayBuffer, or Blob to load
  651. * @param onLoad callback called when the image successfully loads
  652. * @param onError callback called when the image fails to load
  653. * @param offlineProvider offline provider for caching
  654. * @returns the HTMLImageElement of the loaded image
  655. */
  656. public static LoadImage(input: string | ArrayBuffer | Blob, onLoad: (img: HTMLImageElement) => void, onError: (message?: string, exception?: any) => void, offlineProvider: Nullable<IOfflineProvider>): HTMLImageElement {
  657. let url: string;
  658. let usingObjectURL = false;
  659. if (input instanceof ArrayBuffer) {
  660. url = URL.createObjectURL(new Blob([input]));
  661. usingObjectURL = true;
  662. }
  663. else if (input instanceof Blob) {
  664. url = URL.createObjectURL(input);
  665. usingObjectURL = true;
  666. }
  667. else {
  668. url = Tools.CleanUrl(input);
  669. url = Tools.PreprocessUrl(input);
  670. }
  671. var img = new Image();
  672. Tools.SetCorsBehavior(url, img);
  673. const loadHandler = () => {
  674. img.removeEventListener("load", loadHandler);
  675. img.removeEventListener("error", errorHandler);
  676. onLoad(img);
  677. // Must revoke the URL after calling onLoad to avoid security exceptions in
  678. // certain scenarios (e.g. when hosted in vscode).
  679. if (usingObjectURL && img.src) {
  680. URL.revokeObjectURL(img.src);
  681. }
  682. };
  683. const errorHandler = (err: any) => {
  684. img.removeEventListener("load", loadHandler);
  685. img.removeEventListener("error", errorHandler);
  686. Logger.Error("Error while trying to load image: " + input);
  687. if (onError) {
  688. onError("Error while trying to load image: " + input, err);
  689. }
  690. if (usingObjectURL && img.src) {
  691. URL.revokeObjectURL(img.src);
  692. }
  693. };
  694. img.addEventListener("load", loadHandler);
  695. img.addEventListener("error", errorHandler);
  696. var noOfflineSupport = () => {
  697. img.src = url;
  698. };
  699. var loadFromOfflineSupport = () => {
  700. if (offlineProvider) {
  701. offlineProvider.loadImage(url, img);
  702. }
  703. };
  704. if (url.substr(0, 5) !== "data:" && offlineProvider && offlineProvider.enableTexturesOffline) {
  705. offlineProvider.open(loadFromOfflineSupport, noOfflineSupport);
  706. }
  707. else {
  708. if (url.indexOf("file:") !== -1) {
  709. var textureName = decodeURIComponent(url.substring(5).toLowerCase());
  710. if (FilesInputStore.FilesToLoad[textureName]) {
  711. try {
  712. var blobURL;
  713. try {
  714. blobURL = URL.createObjectURL(FilesInputStore.FilesToLoad[textureName]);
  715. }
  716. catch (ex) {
  717. // Chrome doesn't support oneTimeOnly parameter
  718. blobURL = URL.createObjectURL(FilesInputStore.FilesToLoad[textureName]);
  719. }
  720. img.src = blobURL;
  721. usingObjectURL = true;
  722. }
  723. catch (e) {
  724. img.src = "";
  725. }
  726. return img;
  727. }
  728. }
  729. noOfflineSupport();
  730. }
  731. return img;
  732. }
  733. /**
  734. * Loads a file
  735. * @param url url string, ArrayBuffer, or Blob to load
  736. * @param onSuccess callback called when the file successfully loads
  737. * @param onProgress callback called while file is loading (if the server supports this mode)
  738. * @param offlineProvider defines the offline provider for caching
  739. * @param useArrayBuffer defines a boolean indicating that date must be returned as ArrayBuffer
  740. * @param onError callback called when the file fails to load
  741. * @returns a file request object
  742. */
  743. 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 {
  744. url = Tools.CleanUrl(url);
  745. url = Tools.PreprocessUrl(url);
  746. // If file and file input are set
  747. if (url.indexOf("file:") !== -1) {
  748. const fileName = decodeURIComponent(url.substring(5).toLowerCase());
  749. if (FilesInputStore.FilesToLoad[fileName]) {
  750. return Tools.ReadFile(FilesInputStore.FilesToLoad[fileName], onSuccess, onProgress, useArrayBuffer);
  751. }
  752. }
  753. const loadUrl = Tools.BaseUrl + url;
  754. let aborted = false;
  755. const fileRequest: IFileRequest = {
  756. onCompleteObservable: new Observable<IFileRequest>(),
  757. abort: () => aborted = true,
  758. };
  759. const requestFile = () => {
  760. let request = new WebRequest();
  761. let retryHandle: Nullable<number> = null;
  762. fileRequest.abort = () => {
  763. aborted = true;
  764. if (request.readyState !== (XMLHttpRequest.DONE || 4)) {
  765. request.abort();
  766. }
  767. if (retryHandle !== null) {
  768. clearTimeout(retryHandle);
  769. retryHandle = null;
  770. }
  771. };
  772. const retryLoop = (retryIndex: number) => {
  773. request.open('GET', loadUrl);
  774. if (useArrayBuffer) {
  775. request.responseType = "arraybuffer";
  776. }
  777. if (onProgress) {
  778. request.addEventListener("progress", onProgress);
  779. }
  780. const onLoadEnd = () => {
  781. request.removeEventListener("loadend", onLoadEnd);
  782. fileRequest.onCompleteObservable.notifyObservers(fileRequest);
  783. fileRequest.onCompleteObservable.clear();
  784. };
  785. request.addEventListener("loadend", onLoadEnd);
  786. const onReadyStateChange = () => {
  787. if (aborted) {
  788. return;
  789. }
  790. // In case of undefined state in some browsers.
  791. if (request.readyState === (XMLHttpRequest.DONE || 4)) {
  792. // Some browsers have issues where onreadystatechange can be called multiple times with the same value.
  793. request.removeEventListener("readystatechange", onReadyStateChange);
  794. if ((request.status >= 200 && request.status < 300) || (request.status === 0 && (!DomManagement.IsWindowObjectExist() || Tools.IsFileURL()))) {
  795. onSuccess(!useArrayBuffer ? request.responseText : <ArrayBuffer>request.response, request.responseURL);
  796. return;
  797. }
  798. let retryStrategy = Tools.DefaultRetryStrategy;
  799. if (retryStrategy) {
  800. let waitTime = retryStrategy(loadUrl, request, retryIndex);
  801. if (waitTime !== -1) {
  802. // Prevent the request from completing for retry.
  803. request.removeEventListener("loadend", onLoadEnd);
  804. request = new WebRequest();
  805. retryHandle = setTimeout(() => retryLoop(retryIndex + 1), waitTime);
  806. return;
  807. }
  808. }
  809. let e = new LoadFileError("Error status: " + request.status + " " + request.statusText + " - Unable to load " + loadUrl, request);
  810. if (onError) {
  811. onError(request, e);
  812. } else {
  813. throw e;
  814. }
  815. }
  816. };
  817. request.addEventListener("readystatechange", onReadyStateChange);
  818. request.send();
  819. };
  820. retryLoop(0);
  821. };
  822. // Caching all files
  823. if (offlineProvider && offlineProvider.enableSceneOffline) {
  824. const noOfflineSupport = (request?: any) => {
  825. if (request && request.status > 400) {
  826. if (onError) {
  827. onError(request);
  828. }
  829. } else {
  830. if (!aborted) {
  831. requestFile();
  832. }
  833. }
  834. };
  835. const loadFromOfflineSupport = () => {
  836. // TODO: database needs to support aborting and should return a IFileRequest
  837. if (aborted) {
  838. return;
  839. }
  840. if (offlineProvider) {
  841. offlineProvider.loadFile(url, (data) => {
  842. if (!aborted) {
  843. onSuccess(data);
  844. }
  845. fileRequest.onCompleteObservable.notifyObservers(fileRequest);
  846. }, onProgress ? (event) => {
  847. if (!aborted) {
  848. onProgress(event);
  849. }
  850. } : undefined, noOfflineSupport, useArrayBuffer);
  851. }
  852. };
  853. offlineProvider.open(loadFromOfflineSupport, noOfflineSupport);
  854. }
  855. else {
  856. requestFile();
  857. }
  858. return fileRequest;
  859. }
  860. /**
  861. * Load a script (identified by an url). When the url returns, the
  862. * content of this file is added into a new script element, attached to the DOM (body element)
  863. * @param scriptUrl defines the url of the script to laod
  864. * @param onSuccess defines the callback called when the script is loaded
  865. * @param onError defines the callback to call if an error occurs
  866. * @param scriptId defines the id of the script element
  867. */
  868. public static LoadScript(scriptUrl: string, onSuccess: () => void, onError?: (message?: string, exception?: any) => void, scriptId?: string) {
  869. if (!DomManagement.IsWindowObjectExist()) {
  870. return;
  871. }
  872. var head = document.getElementsByTagName('head')[0];
  873. var script = document.createElement('script');
  874. script.setAttribute('type', 'text/javascript');
  875. script.setAttribute('src', scriptUrl);
  876. if (scriptId) {
  877. script.id = scriptId;
  878. }
  879. script.onload = () => {
  880. if (onSuccess) {
  881. onSuccess();
  882. }
  883. };
  884. script.onerror = (e) => {
  885. if (onError) {
  886. onError(`Unable to load script '${scriptUrl}'`, e);
  887. }
  888. };
  889. head.appendChild(script);
  890. }
  891. /**
  892. * Load an asynchronous script (identified by an url). When the url returns, the
  893. * content of this file is added into a new script element, attached to the DOM (body element)
  894. * @param scriptUrl defines the url of the script to laod
  895. * @param scriptId defines the id of the script element
  896. * @returns a promise request object
  897. */
  898. public static LoadScriptAsync(scriptUrl: string, scriptId?: string): Nullable<Promise<boolean>> {
  899. return new Promise<boolean>((resolve, reject) => {
  900. if (!DomManagement.IsWindowObjectExist()) {
  901. resolve(false);
  902. return;
  903. }
  904. var head = document.getElementsByTagName('head')[0];
  905. var script = document.createElement('script');
  906. script.setAttribute('type', 'text/javascript');
  907. script.setAttribute('src', scriptUrl);
  908. if (scriptId) {
  909. script.id = scriptId;
  910. }
  911. script.onload = () => {
  912. resolve(true);
  913. };
  914. script.onerror = (e) => {
  915. resolve(false);
  916. };
  917. head.appendChild(script);
  918. });
  919. }
  920. /**
  921. * Loads a file from a blob
  922. * @param fileToLoad defines the blob to use
  923. * @param callback defines the callback to call when data is loaded
  924. * @param progressCallback defines the callback to call during loading process
  925. * @returns a file request object
  926. */
  927. public static ReadFileAsDataURL(fileToLoad: Blob, callback: (data: any) => void, progressCallback: (ev: ProgressEvent) => any): IFileRequest {
  928. let reader = new FileReader();
  929. let request: IFileRequest = {
  930. onCompleteObservable: new Observable<IFileRequest>(),
  931. abort: () => reader.abort(),
  932. };
  933. reader.onloadend = (e) => {
  934. request.onCompleteObservable.notifyObservers(request);
  935. };
  936. reader.onload = (e) => {
  937. //target doesn't have result from ts 1.3
  938. callback((<any>e.target)['result']);
  939. };
  940. reader.onprogress = progressCallback;
  941. reader.readAsDataURL(fileToLoad);
  942. return request;
  943. }
  944. /**
  945. * Loads a file
  946. * @param fileToLoad defines the file to load
  947. * @param callback defines the callback to call when data is loaded
  948. * @param progressCallBack defines the callback to call during loading process
  949. * @param useArrayBuffer defines a boolean indicating that data must be returned as an ArrayBuffer
  950. * @returns a file request object
  951. */
  952. public static ReadFile(fileToLoad: File, callback: (data: any) => void, progressCallBack?: (ev: ProgressEvent) => any, useArrayBuffer?: boolean): IFileRequest {
  953. let reader = new FileReader();
  954. let request: IFileRequest = {
  955. onCompleteObservable: new Observable<IFileRequest>(),
  956. abort: () => reader.abort(),
  957. };
  958. reader.onloadend = (e) => request.onCompleteObservable.notifyObservers(request);
  959. reader.onerror = (e) => {
  960. Logger.Log("Error while reading file: " + fileToLoad.name);
  961. callback(JSON.stringify({ autoClear: true, clearColor: [1, 0, 0], ambientColor: [0, 0, 0], gravity: [0, -9.807, 0], meshes: [], cameras: [], lights: [] }));
  962. };
  963. reader.onload = (e) => {
  964. //target doesn't have result from ts 1.3
  965. callback((<any>e.target)['result']);
  966. };
  967. if (progressCallBack) {
  968. reader.onprogress = progressCallBack;
  969. }
  970. if (!useArrayBuffer) {
  971. // Asynchronous read
  972. reader.readAsText(fileToLoad);
  973. }
  974. else {
  975. reader.readAsArrayBuffer(fileToLoad);
  976. }
  977. return request;
  978. }
  979. /**
  980. * Creates a data url from a given string content
  981. * @param content defines the content to convert
  982. * @returns the new data url link
  983. */
  984. public static FileAsURL(content: string): string {
  985. var fileBlob = new Blob([content]);
  986. var url = window.URL || window.webkitURL;
  987. var link: string = url.createObjectURL(fileBlob);
  988. return link;
  989. }
  990. /**
  991. * Format the given number to a specific decimal format
  992. * @param value defines the number to format
  993. * @param decimals defines the number of decimals to use
  994. * @returns the formatted string
  995. */
  996. public static Format(value: number, decimals: number = 2): string {
  997. return value.toFixed(decimals);
  998. }
  999. /**
  1000. * Checks if a given vector is inside a specific range
  1001. * @param v defines the vector to test
  1002. * @param min defines the minimum range
  1003. * @param max defines the maximum range
  1004. */
  1005. public static CheckExtends(v: Vector3, min: Vector3, max: Vector3): void {
  1006. min.minimizeInPlace(v);
  1007. max.maximizeInPlace(v);
  1008. }
  1009. /**
  1010. * Tries to copy an object by duplicating every property
  1011. * @param source defines the source object
  1012. * @param destination defines the target object
  1013. * @param doNotCopyList defines a list of properties to avoid
  1014. * @param mustCopyList defines a list of properties to copy (even if they start with _)
  1015. */
  1016. public static DeepCopy(source: any, destination: any, doNotCopyList?: string[], mustCopyList?: string[]): void {
  1017. DeepCopier.DeepCopy(source, destination, doNotCopyList, mustCopyList);
  1018. }
  1019. /**
  1020. * Gets a boolean indicating if the given object has no own property
  1021. * @param obj defines the object to test
  1022. * @returns true if object has no own property
  1023. */
  1024. public static IsEmpty(obj: any): boolean {
  1025. for (var i in obj) {
  1026. if (obj.hasOwnProperty(i)) {
  1027. return false;
  1028. }
  1029. }
  1030. return true;
  1031. }
  1032. /**
  1033. * Checks for a matching suffix at the end of a string (for ES5 and lower)
  1034. * @param str Source string
  1035. * @param suffix Suffix to search for in the source string
  1036. * @returns Boolean indicating whether the suffix was found (true) or not (false)
  1037. */
  1038. public static EndsWith(str: string, suffix: string): boolean {
  1039. return str.indexOf(suffix, str.length - suffix.length) !== -1;
  1040. }
  1041. /**
  1042. * Function used to register events at window level
  1043. * @param events defines the events to register
  1044. */
  1045. public static RegisterTopRootEvents(events: { name: string; handler: Nullable<(e: FocusEvent) => any> }[]): void {
  1046. for (var index = 0; index < events.length; index++) {
  1047. var event = events[index];
  1048. window.addEventListener(event.name, <any>event.handler, false);
  1049. try {
  1050. if (window.parent) {
  1051. window.parent.addEventListener(event.name, <any>event.handler, false);
  1052. }
  1053. } catch (e) {
  1054. // Silently fails...
  1055. }
  1056. }
  1057. }
  1058. /**
  1059. * Function used to unregister events from window level
  1060. * @param events defines the events to unregister
  1061. */
  1062. public static UnregisterTopRootEvents(events: { name: string; handler: Nullable<(e: FocusEvent) => any> }[]): void {
  1063. for (var index = 0; index < events.length; index++) {
  1064. var event = events[index];
  1065. window.removeEventListener(event.name, <any>event.handler);
  1066. try {
  1067. if (window.parent) {
  1068. window.parent.removeEventListener(event.name, <any>event.handler);
  1069. }
  1070. } catch (e) {
  1071. // Silently fails...
  1072. }
  1073. }
  1074. }
  1075. /**
  1076. * @ignore
  1077. */
  1078. public static _ScreenshotCanvas: HTMLCanvasElement;
  1079. /**
  1080. * Dumps the current bound framebuffer
  1081. * @param width defines the rendering width
  1082. * @param height defines the rendering height
  1083. * @param engine defines the hosting engine
  1084. * @param successCallback defines the callback triggered once the data are available
  1085. * @param mimeType defines the mime type of the result
  1086. * @param fileName defines the filename to download. If present, the result will automatically be downloaded
  1087. */
  1088. public static DumpFramebuffer(width: number, height: number, engine: Engine, successCallback?: (data: string) => void, mimeType: string = "image/png", fileName?: string): void {
  1089. // Read the contents of the framebuffer
  1090. var numberOfChannelsByLine = width * 4;
  1091. var halfHeight = height / 2;
  1092. //Reading datas from WebGL
  1093. var data = engine.readPixels(0, 0, width, height);
  1094. //To flip image on Y axis.
  1095. for (var i = 0; i < halfHeight; i++) {
  1096. for (var j = 0; j < numberOfChannelsByLine; j++) {
  1097. var currentCell = j + i * numberOfChannelsByLine;
  1098. var targetLine = height - i - 1;
  1099. var targetCell = j + targetLine * numberOfChannelsByLine;
  1100. var temp = data[currentCell];
  1101. data[currentCell] = data[targetCell];
  1102. data[targetCell] = temp;
  1103. }
  1104. }
  1105. // Create a 2D canvas to store the result
  1106. if (!Tools._ScreenshotCanvas) {
  1107. Tools._ScreenshotCanvas = document.createElement('canvas');
  1108. }
  1109. Tools._ScreenshotCanvas.width = width;
  1110. Tools._ScreenshotCanvas.height = height;
  1111. var context = Tools._ScreenshotCanvas.getContext('2d');
  1112. if (context) {
  1113. // Copy the pixels to a 2D canvas
  1114. var imageData = context.createImageData(width, height);
  1115. var castData = <any>(imageData.data);
  1116. castData.set(data);
  1117. context.putImageData(imageData, 0, 0);
  1118. Tools.EncodeScreenshotCanvasData(successCallback, mimeType, fileName);
  1119. }
  1120. }
  1121. /**
  1122. * Converts the canvas data to blob.
  1123. * This acts as a polyfill for browsers not supporting the to blob function.
  1124. * @param canvas Defines the canvas to extract the data from
  1125. * @param successCallback Defines the callback triggered once the data are available
  1126. * @param mimeType Defines the mime type of the result
  1127. */
  1128. static ToBlob(canvas: HTMLCanvasElement, successCallback: (blob: Nullable<Blob>) => void, mimeType: string = "image/png"): void {
  1129. // We need HTMLCanvasElement.toBlob for HD screenshots
  1130. if (!canvas.toBlob) {
  1131. // low performance polyfill based on toDataURL (https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob)
  1132. canvas.toBlob = function(callback, type, quality) {
  1133. setTimeout(() => {
  1134. var binStr = atob(this.toDataURL(type, quality).split(',')[1]),
  1135. len = binStr.length,
  1136. arr = new Uint8Array(len);
  1137. for (var i = 0; i < len; i++) {
  1138. arr[i] = binStr.charCodeAt(i);
  1139. }
  1140. callback(new Blob([arr]));
  1141. });
  1142. };
  1143. }
  1144. canvas.toBlob(function(blob) {
  1145. successCallback(blob);
  1146. }, mimeType);
  1147. }
  1148. /**
  1149. * Encodes the canvas data to base 64 or automatically download the result if filename is defined
  1150. * @param successCallback defines the callback triggered once the data are available
  1151. * @param mimeType defines the mime type of the result
  1152. * @param fileName defines he filename to download. If present, the result will automatically be downloaded
  1153. */
  1154. static EncodeScreenshotCanvasData(successCallback?: (data: string) => void, mimeType: string = "image/png", fileName?: string): void {
  1155. if (successCallback) {
  1156. var base64Image = Tools._ScreenshotCanvas.toDataURL(mimeType);
  1157. successCallback(base64Image);
  1158. }
  1159. else {
  1160. this.ToBlob(Tools._ScreenshotCanvas, function(blob) {
  1161. //Creating a link if the browser have the download attribute on the a tag, to automatically start download generated image.
  1162. if (("download" in document.createElement("a"))) {
  1163. if (!fileName) {
  1164. var date = new Date();
  1165. var stringDate = (date.getFullYear() + "-" + (date.getMonth() + 1)).slice(2) + "-" + date.getDate() + "_" + date.getHours() + "-" + ('0' + date.getMinutes()).slice(-2);
  1166. fileName = "screenshot_" + stringDate + ".png";
  1167. }
  1168. Tools.Download(blob!, fileName);
  1169. }
  1170. else {
  1171. var url = URL.createObjectURL(blob);
  1172. var newWindow = window.open("");
  1173. if (!newWindow) { return; }
  1174. var img = newWindow.document.createElement("img");
  1175. img.onload = function() {
  1176. // no longer need to read the blob so it's revoked
  1177. URL.revokeObjectURL(url);
  1178. };
  1179. img.src = url;
  1180. newWindow.document.body.appendChild(img);
  1181. }
  1182. }, mimeType);
  1183. }
  1184. }
  1185. /**
  1186. * Downloads a blob in the browser
  1187. * @param blob defines the blob to download
  1188. * @param fileName defines the name of the downloaded file
  1189. */
  1190. public static Download(blob: Blob, fileName: string): void {
  1191. if (navigator && navigator.msSaveBlob) {
  1192. navigator.msSaveBlob(blob, fileName);
  1193. return;
  1194. }
  1195. var url = window.URL.createObjectURL(blob);
  1196. var a = document.createElement("a");
  1197. document.body.appendChild(a);
  1198. a.style.display = "none";
  1199. a.href = url;
  1200. a.download = fileName;
  1201. a.addEventListener("click", () => {
  1202. if (a.parentElement) {
  1203. a.parentElement.removeChild(a);
  1204. }
  1205. });
  1206. a.click();
  1207. window.URL.revokeObjectURL(url);
  1208. }
  1209. /**
  1210. * Captures a screenshot of the current rendering
  1211. * @see http://doc.babylonjs.com/how_to/render_scene_on_a_png
  1212. * @param engine defines the rendering engine
  1213. * @param camera defines the source camera
  1214. * @param size This parameter can be set to a single number or to an object with the
  1215. * following (optional) properties: precision, width, height. If a single number is passed,
  1216. * it will be used for both width and height. If an object is passed, the screenshot size
  1217. * will be derived from the parameters. The precision property is a multiplier allowing
  1218. * rendering at a higher or lower resolution
  1219. * @param successCallback defines the callback receives a single parameter which contains the
  1220. * screenshot as a string of base64-encoded characters. This string can be assigned to the
  1221. * src parameter of an <img> to display it
  1222. * @param mimeType defines the MIME type of the screenshot image (default: image/png).
  1223. * Check your browser for supported MIME types
  1224. */
  1225. public static CreateScreenshot(engine: Engine, camera: Camera, size: any, successCallback?: (data: string) => void, mimeType: string = "image/png"): void {
  1226. throw _DevTools.WarnImport("ScreenshotTools");
  1227. }
  1228. /**
  1229. * Generates an image screenshot from the specified camera.
  1230. * @see http://doc.babylonjs.com/how_to/render_scene_on_a_png
  1231. * @param engine The engine to use for rendering
  1232. * @param camera The camera to use for rendering
  1233. * @param size This parameter can be set to a single number or to an object with the
  1234. * following (optional) properties: precision, width, height. If a single number is passed,
  1235. * it will be used for both width and height. If an object is passed, the screenshot size
  1236. * will be derived from the parameters. The precision property is a multiplier allowing
  1237. * rendering at a higher or lower resolution
  1238. * @param successCallback The callback receives a single parameter which contains the
  1239. * screenshot as a string of base64-encoded characters. This string can be assigned to the
  1240. * src parameter of an <img> to display it
  1241. * @param mimeType The MIME type of the screenshot image (default: image/png).
  1242. * Check your browser for supported MIME types
  1243. * @param samples Texture samples (default: 1)
  1244. * @param antialiasing Whether antialiasing should be turned on or not (default: false)
  1245. * @param fileName A name for for the downloaded file.
  1246. */
  1247. 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 {
  1248. throw _DevTools.WarnImport("ScreenshotTools");
  1249. }
  1250. /**
  1251. * Implementation from http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#answer-2117523
  1252. * Be aware Math.random() could cause collisions, but:
  1253. * "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"
  1254. * @returns a pseudo random id
  1255. */
  1256. public static RandomId(): string {
  1257. return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
  1258. var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
  1259. return v.toString(16);
  1260. });
  1261. }
  1262. /**
  1263. * Test if the given uri is a base64 string
  1264. * @param uri The uri to test
  1265. * @return True if the uri is a base64 string or false otherwise
  1266. */
  1267. public static IsBase64(uri: string): boolean {
  1268. return uri.length < 5 ? false : uri.substr(0, 5) === "data:";
  1269. }
  1270. /**
  1271. * Decode the given base64 uri.
  1272. * @param uri The uri to decode
  1273. * @return The decoded base64 data.
  1274. */
  1275. public static DecodeBase64(uri: string): ArrayBuffer {
  1276. const decodedString = atob(uri.split(",")[1]);
  1277. const bufferLength = decodedString.length;
  1278. const bufferView = new Uint8Array(new ArrayBuffer(bufferLength));
  1279. for (let i = 0; i < bufferLength; i++) {
  1280. bufferView[i] = decodedString.charCodeAt(i);
  1281. }
  1282. return bufferView.buffer;
  1283. }
  1284. /**
  1285. * Gets the absolute url.
  1286. * @param url the input url
  1287. * @return the absolute url
  1288. */
  1289. public static GetAbsoluteUrl(url: string): string {
  1290. const a = document.createElement("a");
  1291. a.href = url;
  1292. return a.href;
  1293. }
  1294. // Logs
  1295. /**
  1296. * No log
  1297. */
  1298. public static readonly NoneLogLevel = Logger.NoneLogLevel;
  1299. /**
  1300. * Only message logs
  1301. */
  1302. public static readonly MessageLogLevel = Logger.MessageLogLevel;
  1303. /**
  1304. * Only warning logs
  1305. */
  1306. public static readonly WarningLogLevel = Logger.WarningLogLevel;
  1307. /**
  1308. * Only error logs
  1309. */
  1310. public static readonly ErrorLogLevel = Logger.ErrorLogLevel;
  1311. /**
  1312. * All logs
  1313. */
  1314. public static readonly AllLogLevel = Logger.AllLogLevel;
  1315. /**
  1316. * Gets a value indicating the number of loading errors
  1317. * @ignorenaming
  1318. */
  1319. public static get errorsCount(): number {
  1320. return Logger.errorsCount;
  1321. }
  1322. /**
  1323. * Callback called when a new log is added
  1324. */
  1325. public static OnNewCacheEntry: (entry: string) => void;
  1326. /**
  1327. * Log a message to the console
  1328. * @param message defines the message to log
  1329. */
  1330. public static Log(message: string): void {
  1331. Logger.Log(message);
  1332. }
  1333. /**
  1334. * Write a warning message to the console
  1335. * @param message defines the message to log
  1336. */
  1337. public static Warn(message: string): void {
  1338. Logger.Warn(message);
  1339. }
  1340. /**
  1341. * Write an error message to the console
  1342. * @param message defines the message to log
  1343. */
  1344. public static Error(message: string): void {
  1345. Logger.Error(message);
  1346. }
  1347. /**
  1348. * Gets current log cache (list of logs)
  1349. */
  1350. public static get LogCache(): string {
  1351. return Logger.LogCache;
  1352. }
  1353. /**
  1354. * Clears the log cache
  1355. */
  1356. public static ClearLogCache(): void {
  1357. Logger.ClearLogCache();
  1358. }
  1359. /**
  1360. * Sets the current log level (MessageLogLevel / WarningLogLevel / ErrorLogLevel)
  1361. */
  1362. public static set LogLevels(level: number) {
  1363. Logger.LogLevels = level;
  1364. }
  1365. /**
  1366. * Checks if the loaded document was accessed via `file:`-Protocol.
  1367. * @returns boolean
  1368. */
  1369. public static IsFileURL(): boolean {
  1370. return location.protocol === "file:";
  1371. }
  1372. /**
  1373. * Checks if the window object exists
  1374. * Back Compat only, please use DomManagement.IsWindowObjectExist instead.
  1375. */
  1376. public static IsWindowObjectExist = DomManagement.IsWindowObjectExist;
  1377. // Performances
  1378. /**
  1379. * No performance log
  1380. */
  1381. public static readonly PerformanceNoneLogLevel = 0;
  1382. /**
  1383. * Use user marks to log performance
  1384. */
  1385. public static readonly PerformanceUserMarkLogLevel = 1;
  1386. /**
  1387. * Log performance to the console
  1388. */
  1389. public static readonly PerformanceConsoleLogLevel = 2;
  1390. private static _performance: Performance;
  1391. /**
  1392. * Sets the current performance log level
  1393. */
  1394. public static set PerformanceLogLevel(level: number) {
  1395. if ((level & Tools.PerformanceUserMarkLogLevel) === Tools.PerformanceUserMarkLogLevel) {
  1396. Tools.StartPerformanceCounter = Tools._StartUserMark;
  1397. Tools.EndPerformanceCounter = Tools._EndUserMark;
  1398. return;
  1399. }
  1400. if ((level & Tools.PerformanceConsoleLogLevel) === Tools.PerformanceConsoleLogLevel) {
  1401. Tools.StartPerformanceCounter = Tools._StartPerformanceConsole;
  1402. Tools.EndPerformanceCounter = Tools._EndPerformanceConsole;
  1403. return;
  1404. }
  1405. Tools.StartPerformanceCounter = Tools._StartPerformanceCounterDisabled;
  1406. Tools.EndPerformanceCounter = Tools._EndPerformanceCounterDisabled;
  1407. }
  1408. private static _StartPerformanceCounterDisabled(counterName: string, condition?: boolean): void {
  1409. }
  1410. private static _EndPerformanceCounterDisabled(counterName: string, condition?: boolean): void {
  1411. }
  1412. private static _StartUserMark(counterName: string, condition = true): void {
  1413. if (!Tools._performance) {
  1414. if (!DomManagement.IsWindowObjectExist()) {
  1415. return;
  1416. }
  1417. Tools._performance = window.performance;
  1418. }
  1419. if (!condition || !Tools._performance.mark) {
  1420. return;
  1421. }
  1422. Tools._performance.mark(counterName + "-Begin");
  1423. }
  1424. private static _EndUserMark(counterName: string, condition = true): void {
  1425. if (!condition || !Tools._performance.mark) {
  1426. return;
  1427. }
  1428. Tools._performance.mark(counterName + "-End");
  1429. Tools._performance.measure(counterName, counterName + "-Begin", counterName + "-End");
  1430. }
  1431. private static _StartPerformanceConsole(counterName: string, condition = true): void {
  1432. if (!condition) {
  1433. return;
  1434. }
  1435. Tools._StartUserMark(counterName, condition);
  1436. if (console.time) {
  1437. console.time(counterName);
  1438. }
  1439. }
  1440. private static _EndPerformanceConsole(counterName: string, condition = true): void {
  1441. if (!condition) {
  1442. return;
  1443. }
  1444. Tools._EndUserMark(counterName, condition);
  1445. if (console.time) {
  1446. console.timeEnd(counterName);
  1447. }
  1448. }
  1449. /**
  1450. * Starts a performance counter
  1451. */
  1452. public static StartPerformanceCounter: (counterName: string, condition?: boolean) => void = Tools._StartPerformanceCounterDisabled;
  1453. /**
  1454. * Ends a specific performance coutner
  1455. */
  1456. public static EndPerformanceCounter: (counterName: string, condition?: boolean) => void = Tools._EndPerformanceCounterDisabled;
  1457. /**
  1458. * Gets either window.performance.now() if supported or Date.now() else
  1459. */
  1460. public static get Now(): number {
  1461. return PrecisionDate.Now;
  1462. }
  1463. /**
  1464. * This method will return the name of the class used to create the instance of the given object.
  1465. * It will works only on Javascript basic data types (number, string, ...) and instance of class declared with the @className decorator.
  1466. * @param object the object to get the class name from
  1467. * @param isType defines if the object is actually a type
  1468. * @returns the name of the class, will be "object" for a custom data type not using the @className decorator
  1469. */
  1470. public static GetClassName(object: any, isType: boolean = false): string {
  1471. let name = null;
  1472. if (!isType && object.getClassName) {
  1473. name = object.getClassName();
  1474. } else {
  1475. if (object instanceof Object) {
  1476. let classObj = isType ? object : Object.getPrototypeOf(object);
  1477. name = classObj.constructor["__bjsclassName__"];
  1478. }
  1479. if (!name) {
  1480. name = typeof object;
  1481. }
  1482. }
  1483. return name;
  1484. }
  1485. /**
  1486. * Gets the first element of an array satisfying a given predicate
  1487. * @param array defines the array to browse
  1488. * @param predicate defines the predicate to use
  1489. * @returns null if not found or the element
  1490. */
  1491. public static First<T>(array: Array<T>, predicate: (item: T) => boolean): Nullable<T> {
  1492. for (let el of array) {
  1493. if (predicate(el)) {
  1494. return el;
  1495. }
  1496. }
  1497. return null;
  1498. }
  1499. /**
  1500. * This method will return the name of the full name of the class, including its owning module (if any).
  1501. * 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).
  1502. * @param object the object to get the class name from
  1503. * @param isType defines if the object is actually a type
  1504. * @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.
  1505. * @ignorenaming
  1506. */
  1507. public static getFullClassName(object: any, isType: boolean = false): Nullable<string> {
  1508. let className = null;
  1509. let moduleName = null;
  1510. if (!isType && object.getClassName) {
  1511. className = object.getClassName();
  1512. } else {
  1513. if (object instanceof Object) {
  1514. let classObj = isType ? object : Object.getPrototypeOf(object);
  1515. className = classObj.constructor["__bjsclassName__"];
  1516. moduleName = classObj.constructor["__bjsmoduleName__"];
  1517. }
  1518. if (!className) {
  1519. className = typeof object;
  1520. }
  1521. }
  1522. if (!className) {
  1523. return null;
  1524. }
  1525. return ((moduleName != null) ? (moduleName + ".") : "") + className;
  1526. }
  1527. /**
  1528. * Returns a promise that resolves after the given amount of time.
  1529. * @param delay Number of milliseconds to delay
  1530. * @returns Promise that resolves after the given amount of time
  1531. */
  1532. public static DelayAsync(delay: number): Promise<void> {
  1533. return new Promise((resolve) => {
  1534. setTimeout(() => {
  1535. resolve();
  1536. }, delay);
  1537. });
  1538. }
  1539. /**
  1540. * Gets the current gradient from an array of IValueGradient
  1541. * @param ratio defines the current ratio to get
  1542. * @param gradients defines the array of IValueGradient
  1543. * @param updateFunc defines the callback function used to get the final value from the selected gradients
  1544. */
  1545. public static GetCurrentGradient(ratio: number, gradients: IValueGradient[], updateFunc: (current: IValueGradient, next: IValueGradient, scale: number) => void) {
  1546. for (var gradientIndex = 0; gradientIndex < gradients.length - 1; gradientIndex++) {
  1547. let currentGradient = gradients[gradientIndex];
  1548. let nextGradient = gradients[gradientIndex + 1];
  1549. if (ratio >= currentGradient.gradient && ratio <= nextGradient.gradient) {
  1550. let scale = (ratio - currentGradient.gradient) / (nextGradient.gradient - currentGradient.gradient);
  1551. updateFunc(currentGradient, nextGradient, scale);
  1552. return;
  1553. }
  1554. }
  1555. // Use last index if over
  1556. const lastIndex = gradients.length - 1;
  1557. updateFunc(gradients[lastIndex], gradients[lastIndex], 1.0);
  1558. }
  1559. }
  1560. /**
  1561. * This class is used to track a performance counter which is number based.
  1562. * The user has access to many properties which give statistics of different nature.
  1563. *
  1564. * The implementer can track two kinds of Performance Counter: time and count.
  1565. * 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.
  1566. * 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.
  1567. */
  1568. export class PerfCounter {
  1569. /**
  1570. * Gets or sets a global boolean to turn on and off all the counters
  1571. */
  1572. public static Enabled = true;
  1573. /**
  1574. * Returns the smallest value ever
  1575. */
  1576. public get min(): number {
  1577. return this._min;
  1578. }
  1579. /**
  1580. * Returns the biggest value ever
  1581. */
  1582. public get max(): number {
  1583. return this._max;
  1584. }
  1585. /**
  1586. * Returns the average value since the performance counter is running
  1587. */
  1588. public get average(): number {
  1589. return this._average;
  1590. }
  1591. /**
  1592. * Returns the average value of the last second the counter was monitored
  1593. */
  1594. public get lastSecAverage(): number {
  1595. return this._lastSecAverage;
  1596. }
  1597. /**
  1598. * Returns the current value
  1599. */
  1600. public get current(): number {
  1601. return this._current;
  1602. }
  1603. /**
  1604. * Gets the accumulated total
  1605. */
  1606. public get total(): number {
  1607. return this._totalAccumulated;
  1608. }
  1609. /**
  1610. * Gets the total value count
  1611. */
  1612. public get count(): number {
  1613. return this._totalValueCount;
  1614. }
  1615. /**
  1616. * Creates a new counter
  1617. */
  1618. constructor() {
  1619. this._startMonitoringTime = 0;
  1620. this._min = 0;
  1621. this._max = 0;
  1622. this._average = 0;
  1623. this._lastSecAverage = 0;
  1624. this._current = 0;
  1625. this._totalValueCount = 0;
  1626. this._totalAccumulated = 0;
  1627. this._lastSecAccumulated = 0;
  1628. this._lastSecTime = 0;
  1629. this._lastSecValueCount = 0;
  1630. }
  1631. /**
  1632. * Call this method to start monitoring a new frame.
  1633. * 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.
  1634. */
  1635. public fetchNewFrame() {
  1636. this._totalValueCount++;
  1637. this._current = 0;
  1638. this._lastSecValueCount++;
  1639. }
  1640. /**
  1641. * Call this method to monitor a count of something (e.g. mesh drawn in viewport count)
  1642. * @param newCount the count value to add to the monitored count
  1643. * @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.
  1644. */
  1645. public addCount(newCount: number, fetchResult: boolean) {
  1646. if (!PerfCounter.Enabled) {
  1647. return;
  1648. }
  1649. this._current += newCount;
  1650. if (fetchResult) {
  1651. this._fetchResult();
  1652. }
  1653. }
  1654. /**
  1655. * Start monitoring this performance counter
  1656. */
  1657. public beginMonitoring() {
  1658. if (!PerfCounter.Enabled) {
  1659. return;
  1660. }
  1661. this._startMonitoringTime = PrecisionDate.Now;
  1662. }
  1663. /**
  1664. * Compute the time lapsed since the previous beginMonitoring() call.
  1665. * @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
  1666. */
  1667. public endMonitoring(newFrame: boolean = true) {
  1668. if (!PerfCounter.Enabled) {
  1669. return;
  1670. }
  1671. if (newFrame) {
  1672. this.fetchNewFrame();
  1673. }
  1674. let currentTime = PrecisionDate.Now;
  1675. this._current = currentTime - this._startMonitoringTime;
  1676. if (newFrame) {
  1677. this._fetchResult();
  1678. }
  1679. }
  1680. private _fetchResult() {
  1681. this._totalAccumulated += this._current;
  1682. this._lastSecAccumulated += this._current;
  1683. // Min/Max update
  1684. this._min = Math.min(this._min, this._current);
  1685. this._max = Math.max(this._max, this._current);
  1686. this._average = this._totalAccumulated / this._totalValueCount;
  1687. // Reset last sec?
  1688. let now = PrecisionDate.Now;
  1689. if ((now - this._lastSecTime) > 1000) {
  1690. this._lastSecAverage = this._lastSecAccumulated / this._lastSecValueCount;
  1691. this._lastSecTime = now;
  1692. this._lastSecAccumulated = 0;
  1693. this._lastSecValueCount = 0;
  1694. }
  1695. }
  1696. private _startMonitoringTime: number;
  1697. private _min: number;
  1698. private _max: number;
  1699. private _average: number;
  1700. private _current: number;
  1701. private _totalValueCount: number;
  1702. private _totalAccumulated: number;
  1703. private _lastSecAverage: number;
  1704. private _lastSecAccumulated: number;
  1705. private _lastSecTime: number;
  1706. private _lastSecValueCount: number;
  1707. }
  1708. /**
  1709. * Use this className as a decorator on a given class definition to add it a name and optionally its module.
  1710. * You can then use the Tools.getClassName(obj) on an instance to retrieve its class name.
  1711. * This method is the only way to get it done in all cases, even if the .js file declaring the class is minified
  1712. * @param name The name of the class, case should be preserved
  1713. * @param module The name of the Module hosting the class, optional, but strongly recommended to specify if possible. Case should be preserved.
  1714. */
  1715. export function className(name: string, module?: string): (target: Object) => void {
  1716. return (target: Object) => {
  1717. (<any>target)["__bjsclassName__"] = name;
  1718. (<any>target)["__bjsmoduleName__"] = (module != null) ? module : null;
  1719. };
  1720. }
  1721. /**
  1722. * An implementation of a loop for asynchronous functions.
  1723. */
  1724. export class AsyncLoop {
  1725. /**
  1726. * Defines the current index of the loop.
  1727. */
  1728. public index: number;
  1729. private _done: boolean;
  1730. private _fn: (asyncLoop: AsyncLoop) => void;
  1731. private _successCallback: () => void;
  1732. /**
  1733. * Constructor.
  1734. * @param iterations the number of iterations.
  1735. * @param func the function to run each iteration
  1736. * @param successCallback the callback that will be called upon succesful execution
  1737. * @param offset starting offset.
  1738. */
  1739. constructor(
  1740. /**
  1741. * Defines the number of iterations for the loop
  1742. */
  1743. public iterations: number,
  1744. func: (asyncLoop: AsyncLoop) => void,
  1745. successCallback: () => void,
  1746. offset: number = 0) {
  1747. this.index = offset - 1;
  1748. this._done = false;
  1749. this._fn = func;
  1750. this._successCallback = successCallback;
  1751. }
  1752. /**
  1753. * Execute the next iteration. Must be called after the last iteration was finished.
  1754. */
  1755. public executeNext(): void {
  1756. if (!this._done) {
  1757. if (this.index + 1 < this.iterations) {
  1758. ++this.index;
  1759. this._fn(this);
  1760. } else {
  1761. this.breakLoop();
  1762. }
  1763. }
  1764. }
  1765. /**
  1766. * Break the loop and run the success callback.
  1767. */
  1768. public breakLoop(): void {
  1769. this._done = true;
  1770. this._successCallback();
  1771. }
  1772. /**
  1773. * Create and run an async loop.
  1774. * @param iterations the number of iterations.
  1775. * @param fn the function to run each iteration
  1776. * @param successCallback the callback that will be called upon succesful execution
  1777. * @param offset starting offset.
  1778. * @returns the created async loop object
  1779. */
  1780. public static Run(iterations: number, fn: (asyncLoop: AsyncLoop) => void, successCallback: () => void, offset: number = 0): AsyncLoop {
  1781. var loop = new AsyncLoop(iterations, fn, successCallback, offset);
  1782. loop.executeNext();
  1783. return loop;
  1784. }
  1785. /**
  1786. * A for-loop that will run a given number of iterations synchronous and the rest async.
  1787. * @param iterations total number of iterations
  1788. * @param syncedIterations number of synchronous iterations in each async iteration.
  1789. * @param fn the function to call each iteration.
  1790. * @param callback a success call back that will be called when iterating stops.
  1791. * @param breakFunction a break condition (optional)
  1792. * @param timeout timeout settings for the setTimeout function. default - 0.
  1793. * @returns the created async loop object
  1794. */
  1795. public static SyncAsyncForLoop(iterations: number, syncedIterations: number, fn: (iteration: number) => void, callback: () => void, breakFunction?: () => boolean, timeout: number = 0): AsyncLoop {
  1796. return AsyncLoop.Run(Math.ceil(iterations / syncedIterations), (loop: AsyncLoop) => {
  1797. if (breakFunction && breakFunction()) { loop.breakLoop(); }
  1798. else {
  1799. setTimeout(() => {
  1800. for (var i = 0; i < syncedIterations; ++i) {
  1801. var iteration = (loop.index * syncedIterations) + i;
  1802. if (iteration >= iterations) { break; }
  1803. fn(iteration);
  1804. if (breakFunction && breakFunction()) {
  1805. loop.breakLoop();
  1806. break;
  1807. }
  1808. }
  1809. loop.executeNext();
  1810. }, timeout);
  1811. }
  1812. }, callback);
  1813. }
  1814. }