babylon.tools.ts 74 KB

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