babylon.tools.ts 61 KB

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