tools.js 58 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294
  1. import { Observable } from "./observable.js";
  2. import { GetDOMTextContent, IsNavigatorAvailable, IsWindowObjectExist } from "./domManagement.js";
  3. import { Logger } from "./logger.js";
  4. import { DeepCopier } from "./deepCopier.js";
  5. import { PrecisionDate } from "./precisionDate.js";
  6. import { _WarnImport } from "./devTools.js";
  7. import { WebRequest } from "./webRequest.js";
  8. import { EngineStore } from "../Engines/engineStore.js";
  9. import { FileToolsOptions, DecodeBase64UrlToBinary, IsBase64DataUrl, LoadFile as FileToolsLoadFile, LoadImage as FileToolLoadImage, ReadFile as FileToolsReadFile, SetCorsBehavior, } from "./fileTools.js";
  10. import { TimingTools } from "./timingTools.js";
  11. import { InstantiationTools } from "./instantiationTools.js";
  12. import { RandomGUID } from "./guid.js";
  13. import { IsExponentOfTwo, Mix } from "./tools.functions.js";
  14. /**
  15. * Class containing a set of static utilities functions
  16. */
  17. export class Tools {
  18. /**
  19. * Gets or sets the base URL to use to load assets
  20. */
  21. static get BaseUrl() {
  22. return FileToolsOptions.BaseUrl;
  23. }
  24. static set BaseUrl(value) {
  25. FileToolsOptions.BaseUrl = value;
  26. }
  27. /**
  28. * This function checks whether a URL is absolute or not.
  29. * It will also detect data and blob URLs
  30. * @param url the url to check
  31. * @returns is the url absolute or relative
  32. */
  33. static IsAbsoluteUrl(url) {
  34. // See https://stackoverflow.com/a/38979205.
  35. // URL is protocol-relative (= absolute)
  36. if (url.indexOf("//") === 0) {
  37. return true;
  38. }
  39. // URL has no protocol (= relative)
  40. if (url.indexOf("://") === -1) {
  41. return false;
  42. }
  43. // URL does not contain a dot, i.e. no TLD (= relative, possibly REST)
  44. if (url.indexOf(".") === -1) {
  45. return false;
  46. }
  47. // URL does not contain a single slash (= relative)
  48. if (url.indexOf("/") === -1) {
  49. return false;
  50. }
  51. // The first colon comes after the first slash (= relative)
  52. if (url.indexOf(":") > url.indexOf("/")) {
  53. return false;
  54. }
  55. // Protocol is defined before first dot (= absolute)
  56. if (url.indexOf("://") < url.indexOf(".")) {
  57. return true;
  58. }
  59. if (url.indexOf("data:") === 0 || url.indexOf("blob:") === 0) {
  60. return true;
  61. }
  62. // Anything else must be relative
  63. return false;
  64. }
  65. /**
  66. * Sets the base URL to use to load scripts
  67. */
  68. static set ScriptBaseUrl(value) {
  69. FileToolsOptions.ScriptBaseUrl = value;
  70. }
  71. static get ScriptBaseUrl() {
  72. return FileToolsOptions.ScriptBaseUrl;
  73. }
  74. /**
  75. * Sets a preprocessing function to run on a source URL before importing it
  76. * Note that this function will execute AFTER the base URL is appended to the URL
  77. */
  78. static set ScriptPreprocessUrl(func) {
  79. FileToolsOptions.ScriptPreprocessUrl = func;
  80. }
  81. static get ScriptPreprocessUrl() {
  82. return FileToolsOptions.ScriptPreprocessUrl;
  83. }
  84. /**
  85. * Gets or sets the retry strategy to apply when an error happens while loading an asset
  86. */
  87. static get DefaultRetryStrategy() {
  88. return FileToolsOptions.DefaultRetryStrategy;
  89. }
  90. static set DefaultRetryStrategy(strategy) {
  91. FileToolsOptions.DefaultRetryStrategy = strategy;
  92. }
  93. /**
  94. * Default behavior for cors in the application.
  95. * It can be a string if the expected behavior is identical in the entire app.
  96. * Or a callback to be able to set it per url or on a group of them (in case of Video source for instance)
  97. */
  98. static get CorsBehavior() {
  99. return FileToolsOptions.CorsBehavior;
  100. }
  101. static set CorsBehavior(value) {
  102. FileToolsOptions.CorsBehavior = value;
  103. }
  104. /**
  105. * Gets or sets a global variable indicating if fallback texture must be used when a texture cannot be loaded
  106. * @ignorenaming
  107. */
  108. static get UseFallbackTexture() {
  109. return EngineStore.UseFallbackTexture;
  110. }
  111. static set UseFallbackTexture(value) {
  112. EngineStore.UseFallbackTexture = value;
  113. }
  114. /**
  115. * Use this object to register external classes like custom textures or material
  116. * to allow the loaders to instantiate them
  117. */
  118. static get RegisteredExternalClasses() {
  119. return InstantiationTools.RegisteredExternalClasses;
  120. }
  121. static set RegisteredExternalClasses(classes) {
  122. InstantiationTools.RegisteredExternalClasses = classes;
  123. }
  124. /**
  125. * Texture content used if a texture cannot loaded
  126. * @ignorenaming
  127. */
  128. // eslint-disable-next-line @typescript-eslint/naming-convention
  129. static get fallbackTexture() {
  130. return EngineStore.FallbackTexture;
  131. }
  132. // eslint-disable-next-line @typescript-eslint/naming-convention
  133. static set fallbackTexture(value) {
  134. EngineStore.FallbackTexture = value;
  135. }
  136. /**
  137. * Read the content of a byte array at a specified coordinates (taking in account wrapping)
  138. * @param u defines the coordinate on X axis
  139. * @param v defines the coordinate on Y axis
  140. * @param width defines the width of the source data
  141. * @param height defines the height of the source data
  142. * @param pixels defines the source byte array
  143. * @param color defines the output color
  144. */
  145. static FetchToRef(u, v, width, height, pixels, color) {
  146. const wrappedU = (Math.abs(u) * width) % width | 0;
  147. const wrappedV = (Math.abs(v) * height) % height | 0;
  148. const position = (wrappedU + wrappedV * width) * 4;
  149. color.r = pixels[position] / 255;
  150. color.g = pixels[position + 1] / 255;
  151. color.b = pixels[position + 2] / 255;
  152. color.a = pixels[position + 3] / 255;
  153. }
  154. /**
  155. * Interpolates between a and b via alpha
  156. * @param a The lower value (returned when alpha = 0)
  157. * @param b The upper value (returned when alpha = 1)
  158. * @param alpha The interpolation-factor
  159. * @returns The mixed value
  160. */
  161. static Mix(a, b, alpha) {
  162. return 0;
  163. }
  164. /**
  165. * Tries to instantiate a new object from a given class name
  166. * @param className defines the class name to instantiate
  167. * @returns the new object or null if the system was not able to do the instantiation
  168. */
  169. static Instantiate(className) {
  170. return InstantiationTools.Instantiate(className);
  171. }
  172. /**
  173. * Polyfill for setImmediate
  174. * @param action defines the action to execute after the current execution block
  175. */
  176. static SetImmediate(action) {
  177. TimingTools.SetImmediate(action);
  178. }
  179. /**
  180. * Function indicating if a number is an exponent of 2
  181. * @param value defines the value to test
  182. * @returns true if the value is an exponent of 2
  183. */
  184. static IsExponentOfTwo(value) {
  185. return true;
  186. }
  187. /**
  188. * Returns the nearest 32-bit single precision float representation of a Number
  189. * @param value A Number. If the parameter is of a different type, it will get converted
  190. * to a number or to NaN if it cannot be converted
  191. * @returns number
  192. */
  193. static FloatRound(value) {
  194. return Math.fround(value);
  195. }
  196. /**
  197. * Extracts the filename from a path
  198. * @param path defines the path to use
  199. * @returns the filename
  200. */
  201. static GetFilename(path) {
  202. const index = path.lastIndexOf("/");
  203. if (index < 0) {
  204. return path;
  205. }
  206. return path.substring(index + 1);
  207. }
  208. /**
  209. * Extracts the "folder" part of a path (everything before the filename).
  210. * @param uri The URI to extract the info from
  211. * @param returnUnchangedIfNoSlash Do not touch the URI if no slashes are present
  212. * @returns The "folder" part of the path
  213. */
  214. static GetFolderPath(uri, returnUnchangedIfNoSlash = false) {
  215. const index = uri.lastIndexOf("/");
  216. if (index < 0) {
  217. if (returnUnchangedIfNoSlash) {
  218. return uri;
  219. }
  220. return "";
  221. }
  222. return uri.substring(0, index + 1);
  223. }
  224. /**
  225. * Convert an angle in radians to degrees
  226. * @param angle defines the angle to convert
  227. * @returns the angle in degrees
  228. */
  229. static ToDegrees(angle) {
  230. return (angle * 180) / Math.PI;
  231. }
  232. /**
  233. * Convert an angle in degrees to radians
  234. * @param angle defines the angle to convert
  235. * @returns the angle in radians
  236. */
  237. static ToRadians(angle) {
  238. return (angle * Math.PI) / 180;
  239. }
  240. /**
  241. * Smooth angle changes (kind of low-pass filter), in particular for device orientation "shaking"
  242. * Use trigonometric functions to avoid discontinuity (0/360, -180/180)
  243. * @param previousAngle defines last angle value, in degrees
  244. * @param newAngle defines new angle value, in degrees
  245. * @param smoothFactor defines smoothing sensitivity; min 0: no smoothing, max 1: new data ignored
  246. * @returns the angle in degrees
  247. */
  248. static SmoothAngleChange(previousAngle, newAngle, smoothFactor = 0.9) {
  249. const previousAngleRad = this.ToRadians(previousAngle);
  250. const newAngleRad = this.ToRadians(newAngle);
  251. return this.ToDegrees(Math.atan2((1 - smoothFactor) * Math.sin(newAngleRad) + smoothFactor * Math.sin(previousAngleRad), (1 - smoothFactor) * Math.cos(newAngleRad) + smoothFactor * Math.cos(previousAngleRad)));
  252. }
  253. /**
  254. * Returns an array if obj is not an array
  255. * @param obj defines the object to evaluate as an array
  256. * @param allowsNullUndefined defines a boolean indicating if obj is allowed to be null or undefined
  257. * @returns either obj directly if obj is an array or a new array containing obj
  258. */
  259. static MakeArray(obj, allowsNullUndefined) {
  260. if (allowsNullUndefined !== true && (obj === undefined || obj == null)) {
  261. return null;
  262. }
  263. return Array.isArray(obj) ? obj : [obj];
  264. }
  265. /**
  266. * Gets the pointer prefix to use
  267. * @param engine defines the engine we are finding the prefix for
  268. * @returns "pointer" if touch is enabled. Else returns "mouse"
  269. */
  270. static GetPointerPrefix(engine) {
  271. let eventPrefix = "pointer";
  272. // Check if pointer events are supported
  273. if (IsWindowObjectExist() && !window.PointerEvent) {
  274. eventPrefix = "mouse";
  275. }
  276. // Special Fallback MacOS Safari...
  277. if (engine._badDesktopOS &&
  278. !engine._badOS &&
  279. // And not ipad pros who claim to be macs...
  280. !(document && "ontouchend" in document)) {
  281. eventPrefix = "mouse";
  282. }
  283. return eventPrefix;
  284. }
  285. /**
  286. * Sets the cors behavior on a dom element. This will add the required Tools.CorsBehavior to the element.
  287. * @param url define the url we are trying
  288. * @param element define the dom element where to configure the cors policy
  289. * @param element.crossOrigin
  290. */
  291. static SetCorsBehavior(url, element) {
  292. SetCorsBehavior(url, element);
  293. }
  294. /**
  295. * Sets the referrerPolicy behavior on a dom element.
  296. * @param referrerPolicy define the referrer policy to use
  297. * @param element define the dom element where to configure the referrer policy
  298. * @param element.referrerPolicy
  299. */
  300. static SetReferrerPolicyBehavior(referrerPolicy, element) {
  301. element.referrerPolicy = referrerPolicy;
  302. }
  303. // External files
  304. /**
  305. * Removes unwanted characters from an url
  306. * @param url defines the url to clean
  307. * @returns the cleaned url
  308. */
  309. static CleanUrl(url) {
  310. url = url.replace(/#/gm, "%23");
  311. return url;
  312. }
  313. /**
  314. * Gets or sets a function used to pre-process url before using them to load assets
  315. */
  316. static get PreprocessUrl() {
  317. return FileToolsOptions.PreprocessUrl;
  318. }
  319. static set PreprocessUrl(processor) {
  320. FileToolsOptions.PreprocessUrl = processor;
  321. }
  322. /**
  323. * Loads an image as an HTMLImageElement.
  324. * @param input url string, ArrayBuffer, or Blob to load
  325. * @param onLoad callback called when the image successfully loads
  326. * @param onError callback called when the image fails to load
  327. * @param offlineProvider offline provider for caching
  328. * @param mimeType optional mime type
  329. * @param imageBitmapOptions optional the options to use when creating an ImageBitmap
  330. * @returns the HTMLImageElement of the loaded image
  331. */
  332. static LoadImage(input, onLoad, onError, offlineProvider, mimeType, imageBitmapOptions) {
  333. return FileToolLoadImage(input, onLoad, onError, offlineProvider, mimeType, imageBitmapOptions);
  334. }
  335. /**
  336. * Loads a file from a url
  337. * @param url url string, ArrayBuffer, or Blob to load
  338. * @param onSuccess callback called when the file successfully loads
  339. * @param onProgress callback called while file is loading (if the server supports this mode)
  340. * @param offlineProvider defines the offline provider for caching
  341. * @param useArrayBuffer defines a boolean indicating that date must be returned as ArrayBuffer
  342. * @param onError callback called when the file fails to load
  343. * @returns a file request object
  344. */
  345. static LoadFile(url, onSuccess, onProgress, offlineProvider, useArrayBuffer, onError) {
  346. return FileToolsLoadFile(url, onSuccess, onProgress, offlineProvider, useArrayBuffer, onError);
  347. }
  348. /**
  349. * Loads a file from a url
  350. * @param url the file url to load
  351. * @param useArrayBuffer defines a boolean indicating that date must be returned as ArrayBuffer
  352. * @returns a promise containing an ArrayBuffer corresponding to the loaded file
  353. */
  354. static LoadFileAsync(url, useArrayBuffer = true) {
  355. return new Promise((resolve, reject) => {
  356. FileToolsLoadFile(url, (data) => {
  357. resolve(data);
  358. }, undefined, undefined, useArrayBuffer, (request, exception) => {
  359. reject(exception);
  360. });
  361. });
  362. }
  363. /**
  364. * Get a script URL including preprocessing
  365. * @param scriptUrl the script Url to process
  366. * @param forceAbsoluteUrl force the script to be an absolute url (adding the current base url if necessary)
  367. * @returns a modified URL to use
  368. */
  369. static GetBabylonScriptURL(scriptUrl, forceAbsoluteUrl) {
  370. if (!scriptUrl) {
  371. return "";
  372. }
  373. // if the base URL was set, and the script Url is an absolute path change the default path
  374. if (Tools.ScriptBaseUrl && scriptUrl.startsWith(Tools._DefaultCdnUrl)) {
  375. // change the default host, which is https://cdn.babylonjs.com with the one defined
  376. // make sure no trailing slash is present
  377. const baseUrl = Tools.ScriptBaseUrl[Tools.ScriptBaseUrl.length - 1] === "/" ? Tools.ScriptBaseUrl.substring(0, Tools.ScriptBaseUrl.length - 1) : Tools.ScriptBaseUrl;
  378. scriptUrl = scriptUrl.replace(Tools._DefaultCdnUrl, baseUrl);
  379. }
  380. // run the preprocessor
  381. scriptUrl = Tools.ScriptPreprocessUrl(scriptUrl);
  382. if (forceAbsoluteUrl) {
  383. scriptUrl = Tools.GetAbsoluteUrl(scriptUrl);
  384. }
  385. return scriptUrl;
  386. }
  387. /**
  388. * This function is used internally by babylon components to load a script (identified by an url). When the url returns, the
  389. * content of this file is added into a new script element, attached to the DOM (body element)
  390. * @param scriptUrl defines the url of the script to load
  391. * @param onSuccess defines the callback called when the script is loaded
  392. * @param onError defines the callback to call if an error occurs
  393. * @param scriptId defines the id of the script element
  394. */
  395. static LoadBabylonScript(scriptUrl, onSuccess, onError, scriptId) {
  396. scriptUrl = Tools.GetBabylonScriptURL(scriptUrl);
  397. Tools.LoadScript(scriptUrl, onSuccess, onError);
  398. }
  399. /**
  400. * Load an asynchronous script (identified by an url). When the url returns, the
  401. * content of this file is added into a new script element, attached to the DOM (body element)
  402. * @param scriptUrl defines the url of the script to laod
  403. * @returns a promise request object
  404. */
  405. static LoadBabylonScriptAsync(scriptUrl) {
  406. scriptUrl = Tools.GetBabylonScriptURL(scriptUrl);
  407. return Tools.LoadScriptAsync(scriptUrl);
  408. }
  409. /**
  410. * This function is used internally by babylon components to load a script (identified by an url). When the url returns, the
  411. * content of this file is added into a new script element, attached to the DOM (body element)
  412. * @param scriptUrl defines the url of the script to load
  413. * @param onSuccess defines the callback called when the script is loaded
  414. * @param onError defines the callback to call if an error occurs
  415. * @param scriptId defines the id of the script element
  416. */
  417. static LoadScript(scriptUrl, onSuccess, onError, scriptId) {
  418. if (typeof importScripts === "function") {
  419. try {
  420. importScripts(scriptUrl);
  421. onSuccess();
  422. }
  423. catch (e) {
  424. onError?.(`Unable to load script '${scriptUrl}' in worker`, e);
  425. }
  426. return;
  427. }
  428. else if (!IsWindowObjectExist()) {
  429. onError?.(`Cannot load script '${scriptUrl}' outside of a window or a worker`);
  430. return;
  431. }
  432. const head = document.getElementsByTagName("head")[0];
  433. const script = document.createElement("script");
  434. script.setAttribute("type", "text/javascript");
  435. script.setAttribute("src", scriptUrl);
  436. if (scriptId) {
  437. script.id = scriptId;
  438. }
  439. script.onload = () => {
  440. if (onSuccess) {
  441. onSuccess();
  442. }
  443. };
  444. script.onerror = (e) => {
  445. if (onError) {
  446. onError(`Unable to load script '${scriptUrl}'`, e);
  447. }
  448. };
  449. head.appendChild(script);
  450. }
  451. /**
  452. * Load an asynchronous 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. * @param scriptUrl defines the url of the script to load
  455. * @param scriptId defines the id of the script element
  456. * @returns a promise request object
  457. */
  458. static LoadScriptAsync(scriptUrl, scriptId) {
  459. return new Promise((resolve, reject) => {
  460. this.LoadScript(scriptUrl, () => {
  461. resolve();
  462. }, (message, exception) => {
  463. reject(exception || new Error(message));
  464. }, scriptId);
  465. });
  466. }
  467. /**
  468. * Loads a file from a blob
  469. * @param fileToLoad defines the blob to use
  470. * @param callback defines the callback to call when data is loaded
  471. * @param progressCallback defines the callback to call during loading process
  472. * @returns a file request object
  473. */
  474. static ReadFileAsDataURL(fileToLoad, callback, progressCallback) {
  475. const reader = new FileReader();
  476. const request = {
  477. onCompleteObservable: new Observable(),
  478. abort: () => reader.abort(),
  479. };
  480. reader.onloadend = () => {
  481. request.onCompleteObservable.notifyObservers(request);
  482. };
  483. reader.onload = (e) => {
  484. //target doesn't have result from ts 1.3
  485. callback(e.target["result"]);
  486. };
  487. reader.onprogress = progressCallback;
  488. reader.readAsDataURL(fileToLoad);
  489. return request;
  490. }
  491. /**
  492. * Reads a file from a File object
  493. * @param file defines the file to load
  494. * @param onSuccess defines the callback to call when data is loaded
  495. * @param onProgress defines the callback to call during loading process
  496. * @param useArrayBuffer defines a boolean indicating that data must be returned as an ArrayBuffer
  497. * @param onError defines the callback to call when an error occurs
  498. * @returns a file request object
  499. */
  500. static ReadFile(file, onSuccess, onProgress, useArrayBuffer, onError) {
  501. return FileToolsReadFile(file, onSuccess, onProgress, useArrayBuffer, onError);
  502. }
  503. /**
  504. * Creates a data url from a given string content
  505. * @param content defines the content to convert
  506. * @returns the new data url link
  507. */
  508. static FileAsURL(content) {
  509. const fileBlob = new Blob([content]);
  510. const url = window.URL;
  511. const link = url.createObjectURL(fileBlob);
  512. return link;
  513. }
  514. /**
  515. * Format the given number to a specific decimal format
  516. * @param value defines the number to format
  517. * @param decimals defines the number of decimals to use
  518. * @returns the formatted string
  519. */
  520. static Format(value, decimals = 2) {
  521. return value.toFixed(decimals);
  522. }
  523. /**
  524. * Tries to copy an object by duplicating every property
  525. * @param source defines the source object
  526. * @param destination defines the target object
  527. * @param doNotCopyList defines a list of properties to avoid
  528. * @param mustCopyList defines a list of properties to copy (even if they start with _)
  529. */
  530. static DeepCopy(source, destination, doNotCopyList, mustCopyList) {
  531. DeepCopier.DeepCopy(source, destination, doNotCopyList, mustCopyList);
  532. }
  533. /**
  534. * Gets a boolean indicating if the given object has no own property
  535. * @param obj defines the object to test
  536. * @returns true if object has no own property
  537. */
  538. static IsEmpty(obj) {
  539. for (const i in obj) {
  540. if (Object.prototype.hasOwnProperty.call(obj, i)) {
  541. return false;
  542. }
  543. }
  544. return true;
  545. }
  546. /**
  547. * Function used to register events at window level
  548. * @param windowElement defines the Window object to use
  549. * @param events defines the events to register
  550. */
  551. static RegisterTopRootEvents(windowElement, events) {
  552. for (let index = 0; index < events.length; index++) {
  553. const event = events[index];
  554. windowElement.addEventListener(event.name, event.handler, false);
  555. try {
  556. if (window.parent) {
  557. window.parent.addEventListener(event.name, event.handler, false);
  558. }
  559. }
  560. catch (e) {
  561. // Silently fails...
  562. }
  563. }
  564. }
  565. /**
  566. * Function used to unregister events from window level
  567. * @param windowElement defines the Window object to use
  568. * @param events defines the events to unregister
  569. */
  570. static UnregisterTopRootEvents(windowElement, events) {
  571. for (let index = 0; index < events.length; index++) {
  572. const event = events[index];
  573. windowElement.removeEventListener(event.name, event.handler);
  574. try {
  575. if (windowElement.parent) {
  576. windowElement.parent.removeEventListener(event.name, event.handler);
  577. }
  578. }
  579. catch (e) {
  580. // Silently fails...
  581. }
  582. }
  583. }
  584. /**
  585. * Dumps the current bound framebuffer
  586. * @param width defines the rendering width
  587. * @param height defines the rendering height
  588. * @param engine defines the hosting engine
  589. * @param successCallback defines the callback triggered once the data are available
  590. * @param mimeType defines the mime type of the result
  591. * @param fileName defines the filename to download. If present, the result will automatically be downloaded
  592. * @param quality The quality of the image if lossy mimeType is used (e.g. image/jpeg, image/webp). See {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob | HTMLCanvasElement.toBlob()}'s `quality` parameter.
  593. * @returns a void promise
  594. */
  595. static async DumpFramebuffer(width, height, engine, successCallback, mimeType = "image/png", fileName, quality) {
  596. throw _WarnImport("DumpTools");
  597. }
  598. /**
  599. * Dumps an array buffer
  600. * @param width defines the rendering width
  601. * @param height defines the rendering height
  602. * @param data the data array
  603. * @param successCallback defines the callback triggered once the data are available
  604. * @param mimeType defines the mime type of the result
  605. * @param fileName defines the filename to download. If present, the result will automatically be downloaded
  606. * @param invertY true to invert the picture in the Y dimension
  607. * @param toArrayBuffer true to convert the data to an ArrayBuffer (encoded as `mimeType`) instead of a base64 string
  608. * @param quality The quality of the image if lossy mimeType is used (e.g. image/jpeg, image/webp). See {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob | HTMLCanvasElement.toBlob()}'s `quality` parameter.
  609. */
  610. static DumpData(width, height, data, successCallback, mimeType = "image/png", fileName, invertY = false, toArrayBuffer = false, quality) {
  611. throw _WarnImport("DumpTools");
  612. }
  613. // eslint-disable-next-line jsdoc/require-returns-check
  614. /**
  615. * Dumps an array buffer
  616. * @param width defines the rendering width
  617. * @param height defines the rendering height
  618. * @param data the data array
  619. * @param mimeType defines the mime type of the result
  620. * @param fileName defines the filename to download. If present, the result will automatically be downloaded
  621. * @param invertY true to invert the picture in the Y dimension
  622. * @param toArrayBuffer true to convert the data to an ArrayBuffer (encoded as `mimeType`) instead of a base64 string
  623. * @param quality The quality of the image if lossy mimeType is used (e.g. image/jpeg, image/webp). See {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob | HTMLCanvasElement.toBlob()}'s `quality` parameter.
  624. * @returns a promise that resolve to the final data
  625. */
  626. static DumpDataAsync(width, height, data, mimeType = "image/png", fileName, invertY = false, toArrayBuffer = false, quality) {
  627. throw _WarnImport("DumpTools");
  628. }
  629. static _IsOffScreenCanvas(canvas) {
  630. return canvas.convertToBlob !== undefined;
  631. }
  632. /**
  633. * Converts the canvas data to blob.
  634. * This acts as a polyfill for browsers not supporting the to blob function.
  635. * @param canvas Defines the canvas to extract the data from (can be an offscreen canvas)
  636. * @param successCallback Defines the callback triggered once the data are available
  637. * @param mimeType Defines the mime type of the result
  638. * @param quality The quality of the image if lossy mimeType is used (e.g. image/jpeg, image/webp). See {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob | HTMLCanvasElement.toBlob()}'s `quality` parameter.
  639. */
  640. static ToBlob(canvas, successCallback, mimeType = "image/png", quality) {
  641. // We need HTMLCanvasElement.toBlob for HD screenshots
  642. if (!Tools._IsOffScreenCanvas(canvas) && !canvas.toBlob) {
  643. // low performance polyfill based on toDataURL (https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob)
  644. canvas.toBlob = function (callback, type, quality) {
  645. setTimeout(() => {
  646. const binStr = atob(this.toDataURL(type, quality).split(",")[1]), len = binStr.length, arr = new Uint8Array(len);
  647. for (let i = 0; i < len; i++) {
  648. arr[i] = binStr.charCodeAt(i);
  649. }
  650. callback(new Blob([arr]));
  651. });
  652. };
  653. }
  654. if (Tools._IsOffScreenCanvas(canvas)) {
  655. canvas
  656. .convertToBlob({
  657. type: mimeType,
  658. quality,
  659. })
  660. .then((blob) => successCallback(blob));
  661. }
  662. else {
  663. canvas.toBlob(function (blob) {
  664. successCallback(blob);
  665. }, mimeType, quality);
  666. }
  667. }
  668. /**
  669. * Download a Blob object
  670. * @param blob the Blob object
  671. * @param fileName the file name to download
  672. */
  673. static DownloadBlob(blob, fileName) {
  674. //Creating a link if the browser have the download attribute on the a tag, to automatically start download generated image.
  675. if ("download" in document.createElement("a")) {
  676. if (!fileName) {
  677. const date = new Date();
  678. const stringDate = (date.getFullYear() + "-" + (date.getMonth() + 1)).slice(2) + "-" + date.getDate() + "_" + date.getHours() + "-" + ("0" + date.getMinutes()).slice(-2);
  679. fileName = "screenshot_" + stringDate + ".png";
  680. }
  681. Tools.Download(blob, fileName);
  682. }
  683. else {
  684. if (blob && typeof URL !== "undefined") {
  685. const url = URL.createObjectURL(blob);
  686. const newWindow = window.open("");
  687. if (!newWindow) {
  688. return;
  689. }
  690. const img = newWindow.document.createElement("img");
  691. img.onload = function () {
  692. // no longer need to read the blob so it's revoked
  693. URL.revokeObjectURL(url);
  694. };
  695. img.src = url;
  696. newWindow.document.body.appendChild(img);
  697. }
  698. }
  699. }
  700. /**
  701. * Encodes the canvas data to base 64, or automatically downloads the result if `fileName` is defined.
  702. * @param canvas The canvas to get the data from, which can be an offscreen canvas.
  703. * @param successCallback The callback which is triggered once the data is available. If `fileName` is defined, the callback will be invoked after the download occurs, and the `data` argument will be an empty string.
  704. * @param mimeType The mime type of the result.
  705. * @param fileName The name of the file to download. If defined, the result will automatically be downloaded. If not defined, and `successCallback` is also not defined, the result will automatically be downloaded with an auto-generated file name.
  706. * @param quality The quality of the image if lossy mimeType is used (e.g. image/jpeg, image/webp). See {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob | HTMLCanvasElement.toBlob()}'s `quality` parameter.
  707. */
  708. static EncodeScreenshotCanvasData(canvas, successCallback, mimeType = "image/png", fileName, quality) {
  709. if (typeof fileName === "string" || !successCallback) {
  710. this.ToBlob(canvas, function (blob) {
  711. if (blob) {
  712. Tools.DownloadBlob(blob, fileName);
  713. }
  714. if (successCallback) {
  715. successCallback("");
  716. }
  717. }, mimeType, quality);
  718. }
  719. else if (successCallback) {
  720. if (Tools._IsOffScreenCanvas(canvas)) {
  721. canvas
  722. .convertToBlob({
  723. type: mimeType,
  724. quality,
  725. })
  726. .then((blob) => {
  727. const reader = new FileReader();
  728. reader.readAsDataURL(blob);
  729. reader.onloadend = () => {
  730. const base64data = reader.result;
  731. successCallback(base64data);
  732. };
  733. });
  734. return;
  735. }
  736. const base64Image = canvas.toDataURL(mimeType, quality);
  737. successCallback(base64Image);
  738. }
  739. }
  740. /**
  741. * Downloads a blob in the browser
  742. * @param blob defines the blob to download
  743. * @param fileName defines the name of the downloaded file
  744. */
  745. static Download(blob, fileName) {
  746. if (typeof URL === "undefined") {
  747. return;
  748. }
  749. const url = window.URL.createObjectURL(blob);
  750. const a = document.createElement("a");
  751. document.body.appendChild(a);
  752. a.style.display = "none";
  753. a.href = url;
  754. a.download = fileName;
  755. a.addEventListener("click", () => {
  756. if (a.parentElement) {
  757. a.parentElement.removeChild(a);
  758. }
  759. });
  760. a.click();
  761. window.URL.revokeObjectURL(url);
  762. }
  763. /**
  764. * Will return the right value of the noPreventDefault variable
  765. * Needed to keep backwards compatibility to the old API.
  766. *
  767. * @param args arguments passed to the attachControl function
  768. * @returns the correct value for noPreventDefault
  769. */
  770. static BackCompatCameraNoPreventDefault(args) {
  771. // is it used correctly?
  772. if (typeof args[0] === "boolean") {
  773. return args[0];
  774. }
  775. else if (typeof args[1] === "boolean") {
  776. return args[1];
  777. }
  778. return false;
  779. }
  780. /**
  781. * Captures a screenshot of the current rendering
  782. * @see https://doc.babylonjs.com/features/featuresDeepDive/scene/renderToPNG
  783. * @param engine defines the rendering engine
  784. * @param camera defines the source camera
  785. * @param size This parameter can be set to a single number or to an object with the
  786. * following (optional) properties: precision, width, height. If a single number is passed,
  787. * it will be used for both width and height. If an object is passed, the screenshot size
  788. * will be derived from the parameters. The precision property is a multiplier allowing
  789. * rendering at a higher or lower resolution
  790. * @param successCallback defines the callback receives a single parameter which contains the
  791. * screenshot as a string of base64-encoded characters. This string can be assigned to the
  792. * src parameter of an <img> to display it
  793. * @param mimeType defines the MIME type of the screenshot image (default: image/png).
  794. * Check your browser for supported MIME types
  795. * @param forceDownload force the system to download the image even if a successCallback is provided
  796. * @param quality The quality of the image if lossy mimeType is used (e.g. image/jpeg, image/webp). See {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob | HTMLCanvasElement.toBlob()}'s `quality` parameter.
  797. */
  798. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  799. static CreateScreenshot(engine, camera, size, successCallback, mimeType = "image/png", forceDownload = false, quality) {
  800. throw _WarnImport("ScreenshotTools");
  801. }
  802. // eslint-disable-next-line jsdoc/require-returns-check
  803. /**
  804. * Captures a screenshot of the current rendering
  805. * @see https://doc.babylonjs.com/features/featuresDeepDive/scene/renderToPNG
  806. * @param engine defines the rendering engine
  807. * @param camera defines the source camera
  808. * @param size This parameter can be set to a single number or to an object with the
  809. * following (optional) properties: precision, width, height. If a single number is passed,
  810. * it will be used for both width and height. If an object is passed, the screenshot size
  811. * will be derived from the parameters. The precision property is a multiplier allowing
  812. * rendering at a higher or lower resolution
  813. * @param mimeType defines the MIME type of the screenshot image (default: image/png).
  814. * Check your browser for supported MIME types
  815. * @param quality The quality of the image if lossy mimeType is used (e.g. image/jpeg, image/webp). See {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob | HTMLCanvasElement.toBlob()}'s `quality` parameter.
  816. * @returns screenshot as a string of base64-encoded characters. This string can be assigned
  817. * to the src parameter of an <img> to display it
  818. */
  819. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  820. static CreateScreenshotAsync(engine, camera, size, mimeType = "image/png", quality) {
  821. throw _WarnImport("ScreenshotTools");
  822. }
  823. /**
  824. * Generates an image screenshot from the specified camera.
  825. * @see https://doc.babylonjs.com/features/featuresDeepDive/scene/renderToPNG
  826. * @param engine The engine to use for rendering
  827. * @param camera The camera to use for rendering
  828. * @param size This parameter can be set to a single number or to an object with the
  829. * following (optional) properties: precision, width, height. If a single number is passed,
  830. * it will be used for both width and height. If an object is passed, the screenshot size
  831. * will be derived from the parameters. The precision property is a multiplier allowing
  832. * rendering at a higher or lower resolution
  833. * @param successCallback The callback receives a single parameter which contains the
  834. * screenshot as a string of base64-encoded characters. This string can be assigned to the
  835. * src parameter of an <img> to display it
  836. * @param mimeType The MIME type of the screenshot image (default: image/png).
  837. * Check your browser for supported MIME types
  838. * @param samples Texture samples (default: 1)
  839. * @param antialiasing Whether antialiasing should be turned on or not (default: false)
  840. * @param fileName A name for for the downloaded file.
  841. * @param renderSprites Whether the sprites should be rendered or not (default: false)
  842. * @param enableStencilBuffer Whether the stencil buffer should be enabled or not (default: false)
  843. * @param useLayerMask if the camera's layer mask should be used to filter what should be rendered (default: true)
  844. * @param quality The quality of the image if lossy mimeType is used (e.g. image/jpeg, image/webp). See {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob | HTMLCanvasElement.toBlob()}'s `quality` parameter.
  845. */
  846. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  847. static CreateScreenshotUsingRenderTarget(engine, camera, size, successCallback, mimeType = "image/png", samples = 1, antialiasing = false, fileName, renderSprites = false, enableStencilBuffer = false, useLayerMask = true, quality) {
  848. throw _WarnImport("ScreenshotTools");
  849. }
  850. // eslint-disable-next-line jsdoc/require-returns-check
  851. /**
  852. * Generates an image screenshot from the specified camera.
  853. * @see https://doc.babylonjs.com/features/featuresDeepDive/scene/renderToPNG
  854. * @param engine The engine to use for rendering
  855. * @param camera The camera to use for rendering
  856. * @param size This parameter can be set to a single number or to an object with the
  857. * following (optional) properties: precision, width, height. If a single number is passed,
  858. * it will be used for both width and height. If an object is passed, the screenshot size
  859. * will be derived from the parameters. The precision property is a multiplier allowing
  860. * rendering at a higher or lower resolution
  861. * @param mimeType The MIME type of the screenshot image (default: image/png).
  862. * Check your browser for supported MIME types
  863. * @param samples Texture samples (default: 1)
  864. * @param antialiasing Whether antialiasing should be turned on or not (default: false)
  865. * @param fileName A name for for the downloaded file.
  866. * @returns screenshot as a string of base64-encoded characters. This string can be assigned
  867. * @param renderSprites Whether the sprites should be rendered or not (default: false)
  868. * @param enableStencilBuffer Whether the stencil buffer should be enabled or not (default: false)
  869. * @param useLayerMask if the camera's layer mask should be used to filter what should be rendered (default: true)
  870. * @param quality The quality of the image if lossy mimeType is used (e.g. image/jpeg, image/webp). See {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob | HTMLCanvasElement.toBlob()}'s `quality` parameter.
  871. * to the src parameter of an <img> to display it
  872. */
  873. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  874. static CreateScreenshotUsingRenderTargetAsync(engine, camera, size, mimeType = "image/png", samples = 1, antialiasing = false, fileName, renderSprites = false, enableStencilBuffer = false, useLayerMask = true, quality) {
  875. throw _WarnImport("ScreenshotTools");
  876. }
  877. /**
  878. * Implementation from http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#answer-2117523
  879. * Be aware Math.random() could cause collisions, but:
  880. * "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"
  881. * @returns a pseudo random id
  882. */
  883. static RandomId() {
  884. return RandomGUID();
  885. }
  886. /**
  887. * Test if the given uri is a base64 string
  888. * @deprecated Please use FileTools.IsBase64DataUrl instead.
  889. * @param uri The uri to test
  890. * @returns True if the uri is a base64 string or false otherwise
  891. */
  892. static IsBase64(uri) {
  893. return IsBase64DataUrl(uri);
  894. }
  895. /**
  896. * Decode the given base64 uri.
  897. * @deprecated Please use FileTools.DecodeBase64UrlToBinary instead.
  898. * @param uri The uri to decode
  899. * @returns The decoded base64 data.
  900. */
  901. static DecodeBase64(uri) {
  902. return DecodeBase64UrlToBinary(uri);
  903. }
  904. /**
  905. * Gets a value indicating the number of loading errors
  906. * @ignorenaming
  907. */
  908. // eslint-disable-next-line @typescript-eslint/naming-convention
  909. static get errorsCount() {
  910. return Logger.errorsCount;
  911. }
  912. /**
  913. * Log a message to the console
  914. * @param message defines the message to log
  915. */
  916. static Log(message) {
  917. Logger.Log(message);
  918. }
  919. /**
  920. * Write a warning message to the console
  921. * @param message defines the message to log
  922. */
  923. static Warn(message) {
  924. Logger.Warn(message);
  925. }
  926. /**
  927. * Write an error message to the console
  928. * @param message defines the message to log
  929. */
  930. static Error(message) {
  931. Logger.Error(message);
  932. }
  933. /**
  934. * Gets current log cache (list of logs)
  935. */
  936. static get LogCache() {
  937. return Logger.LogCache;
  938. }
  939. /**
  940. * Clears the log cache
  941. */
  942. static ClearLogCache() {
  943. Logger.ClearLogCache();
  944. }
  945. /**
  946. * Sets the current log level (MessageLogLevel / WarningLogLevel / ErrorLogLevel)
  947. */
  948. static set LogLevels(level) {
  949. Logger.LogLevels = level;
  950. }
  951. /**
  952. * Sets the current performance log level
  953. */
  954. static set PerformanceLogLevel(level) {
  955. if ((level & Tools.PerformanceUserMarkLogLevel) === Tools.PerformanceUserMarkLogLevel) {
  956. Tools.StartPerformanceCounter = Tools._StartUserMark;
  957. Tools.EndPerformanceCounter = Tools._EndUserMark;
  958. return;
  959. }
  960. if ((level & Tools.PerformanceConsoleLogLevel) === Tools.PerformanceConsoleLogLevel) {
  961. Tools.StartPerformanceCounter = Tools._StartPerformanceConsole;
  962. Tools.EndPerformanceCounter = Tools._EndPerformanceConsole;
  963. return;
  964. }
  965. Tools.StartPerformanceCounter = Tools._StartPerformanceCounterDisabled;
  966. Tools.EndPerformanceCounter = Tools._EndPerformanceCounterDisabled;
  967. }
  968. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  969. static _StartPerformanceCounterDisabled(counterName, condition) { }
  970. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  971. static _EndPerformanceCounterDisabled(counterName, condition) { }
  972. static _StartUserMark(counterName, condition = true) {
  973. if (!Tools._Performance) {
  974. if (!IsWindowObjectExist()) {
  975. return;
  976. }
  977. Tools._Performance = window.performance;
  978. }
  979. if (!condition || !Tools._Performance.mark) {
  980. return;
  981. }
  982. Tools._Performance.mark(counterName + "-Begin");
  983. }
  984. static _EndUserMark(counterName, condition = true) {
  985. if (!condition || !Tools._Performance.mark) {
  986. return;
  987. }
  988. Tools._Performance.mark(counterName + "-End");
  989. Tools._Performance.measure(counterName, counterName + "-Begin", counterName + "-End");
  990. }
  991. static _StartPerformanceConsole(counterName, condition = true) {
  992. if (!condition) {
  993. return;
  994. }
  995. Tools._StartUserMark(counterName, condition);
  996. if (console.time) {
  997. console.time(counterName);
  998. }
  999. }
  1000. static _EndPerformanceConsole(counterName, condition = true) {
  1001. if (!condition) {
  1002. return;
  1003. }
  1004. Tools._EndUserMark(counterName, condition);
  1005. console.timeEnd(counterName);
  1006. }
  1007. /**
  1008. * Gets either window.performance.now() if supported or Date.now() else
  1009. */
  1010. static get Now() {
  1011. return PrecisionDate.Now;
  1012. }
  1013. /**
  1014. * This method will return the name of the class used to create the instance of the given object.
  1015. * It will works only on Javascript basic data types (number, string, ...) and instance of class declared with the @className decorator.
  1016. * @param object the object to get the class name from
  1017. * @param isType defines if the object is actually a type
  1018. * @returns the name of the class, will be "object" for a custom data type not using the @className decorator
  1019. */
  1020. static GetClassName(object, isType = false) {
  1021. let name = null;
  1022. if (!isType && object.getClassName) {
  1023. name = object.getClassName();
  1024. }
  1025. else {
  1026. if (object instanceof Object) {
  1027. const classObj = isType ? object : Object.getPrototypeOf(object);
  1028. name = classObj.constructor["__bjsclassName__"];
  1029. }
  1030. if (!name) {
  1031. name = typeof object;
  1032. }
  1033. }
  1034. return name;
  1035. }
  1036. /**
  1037. * Gets the first element of an array satisfying a given predicate
  1038. * @param array defines the array to browse
  1039. * @param predicate defines the predicate to use
  1040. * @returns null if not found or the element
  1041. */
  1042. static First(array, predicate) {
  1043. for (const el of array) {
  1044. if (predicate(el)) {
  1045. return el;
  1046. }
  1047. }
  1048. return null;
  1049. }
  1050. /**
  1051. * This method will return the name of the full name of the class, including its owning module (if any).
  1052. * 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).
  1053. * @param object the object to get the class name from
  1054. * @param isType defines if the object is actually a type
  1055. * @returns 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.
  1056. * @ignorenaming
  1057. */
  1058. // eslint-disable-next-line @typescript-eslint/naming-convention
  1059. static getFullClassName(object, isType = false) {
  1060. let className = null;
  1061. let moduleName = null;
  1062. if (!isType && object.getClassName) {
  1063. className = object.getClassName();
  1064. }
  1065. else {
  1066. if (object instanceof Object) {
  1067. const classObj = isType ? object : Object.getPrototypeOf(object);
  1068. className = classObj.constructor["__bjsclassName__"];
  1069. moduleName = classObj.constructor["__bjsmoduleName__"];
  1070. }
  1071. if (!className) {
  1072. className = typeof object;
  1073. }
  1074. }
  1075. if (!className) {
  1076. return null;
  1077. }
  1078. return (moduleName != null ? moduleName + "." : "") + className;
  1079. }
  1080. /**
  1081. * Returns a promise that resolves after the given amount of time.
  1082. * @param delay Number of milliseconds to delay
  1083. * @returns Promise that resolves after the given amount of time
  1084. */
  1085. static DelayAsync(delay) {
  1086. return new Promise((resolve) => {
  1087. setTimeout(() => {
  1088. resolve();
  1089. }, delay);
  1090. });
  1091. }
  1092. /**
  1093. * Utility function to detect if the current user agent is Safari
  1094. * @returns whether or not the current user agent is safari
  1095. */
  1096. static IsSafari() {
  1097. if (!IsNavigatorAvailable()) {
  1098. return false;
  1099. }
  1100. return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
  1101. }
  1102. }
  1103. /**
  1104. * Enable/Disable Custom HTTP Request Headers globally.
  1105. * default = false
  1106. * @see CustomRequestHeaders
  1107. */
  1108. Tools.UseCustomRequestHeaders = false;
  1109. /**
  1110. * Custom HTTP Request Headers to be sent with XMLHttpRequests
  1111. * i.e. when loading files, where the server/service expects an Authorization header
  1112. */
  1113. Tools.CustomRequestHeaders = WebRequest.CustomRequestHeaders;
  1114. /**
  1115. * Extracts text content from a DOM element hierarchy
  1116. * Back Compat only, please use GetDOMTextContent instead.
  1117. */
  1118. Tools.GetDOMTextContent = GetDOMTextContent;
  1119. /**
  1120. * @internal
  1121. */
  1122. Tools._DefaultCdnUrl = "https://cdn.babylonjs.com";
  1123. // eslint-disable-next-line jsdoc/require-returns-check, jsdoc/require-param
  1124. /**
  1125. * @returns the absolute URL of a given (relative) url
  1126. */
  1127. Tools.GetAbsoluteUrl = typeof document === "object"
  1128. ? (url) => {
  1129. const a = document.createElement("a");
  1130. a.href = url;
  1131. return a.href;
  1132. }
  1133. : typeof URL === "function" && typeof location === "object"
  1134. ? (url) => new URL(url, location.origin).href
  1135. : () => {
  1136. throw new Error("Unable to get absolute URL. Override BABYLON.Tools.GetAbsoluteUrl to a custom implementation for the current context.");
  1137. };
  1138. // Logs
  1139. /**
  1140. * No log
  1141. */
  1142. Tools.NoneLogLevel = Logger.NoneLogLevel;
  1143. /**
  1144. * Only message logs
  1145. */
  1146. Tools.MessageLogLevel = Logger.MessageLogLevel;
  1147. /**
  1148. * Only warning logs
  1149. */
  1150. Tools.WarningLogLevel = Logger.WarningLogLevel;
  1151. /**
  1152. * Only error logs
  1153. */
  1154. Tools.ErrorLogLevel = Logger.ErrorLogLevel;
  1155. /**
  1156. * All logs
  1157. */
  1158. Tools.AllLogLevel = Logger.AllLogLevel;
  1159. /**
  1160. * Checks if the window object exists
  1161. * Back Compat only, please use IsWindowObjectExist instead.
  1162. */
  1163. Tools.IsWindowObjectExist = IsWindowObjectExist;
  1164. // Performances
  1165. /**
  1166. * No performance log
  1167. */
  1168. Tools.PerformanceNoneLogLevel = 0;
  1169. /**
  1170. * Use user marks to log performance
  1171. */
  1172. Tools.PerformanceUserMarkLogLevel = 1;
  1173. /**
  1174. * Log performance to the console
  1175. */
  1176. Tools.PerformanceConsoleLogLevel = 2;
  1177. /**
  1178. * Starts a performance counter
  1179. */
  1180. Tools.StartPerformanceCounter = Tools._StartPerformanceCounterDisabled;
  1181. /**
  1182. * Ends a specific performance counter
  1183. */
  1184. Tools.EndPerformanceCounter = Tools._EndPerformanceCounterDisabled;
  1185. /**
  1186. * Use this className as a decorator on a given class definition to add it a name and optionally its module.
  1187. * You can then use the Tools.getClassName(obj) on an instance to retrieve its class name.
  1188. * This method is the only way to get it done in all cases, even if the .js file declaring the class is minified
  1189. * @param name The name of the class, case should be preserved
  1190. * @param module The name of the Module hosting the class, optional, but strongly recommended to specify if possible. Case should be preserved.
  1191. * @returns a decorator function to apply on the class definition.
  1192. */
  1193. export function className(name, module) {
  1194. return (target) => {
  1195. target["__bjsclassName__"] = name;
  1196. target["__bjsmoduleName__"] = module != null ? module : null;
  1197. };
  1198. }
  1199. /**
  1200. * An implementation of a loop for asynchronous functions.
  1201. */
  1202. export class AsyncLoop {
  1203. /**
  1204. * Constructor.
  1205. * @param iterations the number of iterations.
  1206. * @param func the function to run each iteration
  1207. * @param successCallback the callback that will be called upon successful execution
  1208. * @param offset starting offset.
  1209. */
  1210. constructor(
  1211. /**
  1212. * Defines the number of iterations for the loop
  1213. */
  1214. iterations, func, successCallback, offset = 0) {
  1215. this.iterations = iterations;
  1216. this.index = offset - 1;
  1217. this._done = false;
  1218. this._fn = func;
  1219. this._successCallback = successCallback;
  1220. }
  1221. /**
  1222. * Execute the next iteration. Must be called after the last iteration was finished.
  1223. */
  1224. executeNext() {
  1225. if (!this._done) {
  1226. if (this.index + 1 < this.iterations) {
  1227. ++this.index;
  1228. this._fn(this);
  1229. }
  1230. else {
  1231. this.breakLoop();
  1232. }
  1233. }
  1234. }
  1235. /**
  1236. * Break the loop and run the success callback.
  1237. */
  1238. breakLoop() {
  1239. this._done = true;
  1240. this._successCallback();
  1241. }
  1242. /**
  1243. * Create and run an async loop.
  1244. * @param iterations the number of iterations.
  1245. * @param fn the function to run each iteration
  1246. * @param successCallback the callback that will be called upon successful execution
  1247. * @param offset starting offset.
  1248. * @returns the created async loop object
  1249. */
  1250. static Run(iterations, fn, successCallback, offset = 0) {
  1251. const loop = new AsyncLoop(iterations, fn, successCallback, offset);
  1252. loop.executeNext();
  1253. return loop;
  1254. }
  1255. /**
  1256. * A for-loop that will run a given number of iterations synchronous and the rest async.
  1257. * @param iterations total number of iterations
  1258. * @param syncedIterations number of synchronous iterations in each async iteration.
  1259. * @param fn the function to call each iteration.
  1260. * @param callback a success call back that will be called when iterating stops.
  1261. * @param breakFunction a break condition (optional)
  1262. * @param timeout timeout settings for the setTimeout function. default - 0.
  1263. * @returns the created async loop object
  1264. */
  1265. static SyncAsyncForLoop(iterations, syncedIterations, fn, callback, breakFunction, timeout = 0) {
  1266. return AsyncLoop.Run(Math.ceil(iterations / syncedIterations), (loop) => {
  1267. if (breakFunction && breakFunction()) {
  1268. loop.breakLoop();
  1269. }
  1270. else {
  1271. setTimeout(() => {
  1272. for (let i = 0; i < syncedIterations; ++i) {
  1273. const iteration = loop.index * syncedIterations + i;
  1274. if (iteration >= iterations) {
  1275. break;
  1276. }
  1277. fn(iteration);
  1278. if (breakFunction && breakFunction()) {
  1279. loop.breakLoop();
  1280. break;
  1281. }
  1282. }
  1283. loop.executeNext();
  1284. }, timeout);
  1285. }
  1286. }, callback);
  1287. }
  1288. }
  1289. Tools.Mix = Mix;
  1290. Tools.IsExponentOfTwo = IsExponentOfTwo;
  1291. // Will only be define if Tools is imported freeing up some space when only engine is required
  1292. EngineStore.FallbackTexture =
  1293. "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";
  1294. //# sourceMappingURL=tools.js.map