babylon.tools.ts 71 KB

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