babylon.tools.ts 79 KB

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