glTFValidation.ts 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. import * as GLTF2 from 'babylonjs-gltf2interface';
  2. import { Tools } from 'babylonjs/Misc/tools';
  3. declare var GLTFValidator: GLTF2.IGLTFValidator;
  4. // WorkerGlobalScope
  5. declare function importScripts(...urls: string[]): void;
  6. declare function postMessage(message: any, transfer?: any[]): void;
  7. function validateAsync(data: string | ArrayBuffer, rootUrl: string, fileName: string, getExternalResource: (uri: string) => Promise<ArrayBuffer>): Promise<GLTF2.IGLTFValidationResults> {
  8. const options: GLTF2.IGLTFValidationOptions = {
  9. externalResourceFunction: (uri) => getExternalResource(uri).then((value) => new Uint8Array(value))
  10. };
  11. if (fileName) {
  12. options.uri = (rootUrl === "file:" ? fileName : rootUrl + fileName);
  13. }
  14. return (data instanceof ArrayBuffer)
  15. ? GLTFValidator.validateBytes(new Uint8Array(data), options)
  16. : GLTFValidator.validateString(data, options);
  17. }
  18. /**
  19. * The worker function that gets converted to a blob url to pass into a worker.
  20. */
  21. function workerFunc(): void {
  22. const pendingExternalResources: Array<{ resolve: (data: any) => void, reject: (reason: any) => void }> = [];
  23. onmessage = (message) => {
  24. const data = message.data;
  25. switch (data.id) {
  26. case "init": {
  27. importScripts(data.url);
  28. break;
  29. }
  30. case "validate": {
  31. validateAsync(data.data, data.rootUrl, data.fileName, (uri) => new Promise((resolve, reject) => {
  32. const index = pendingExternalResources.length;
  33. pendingExternalResources.push({ resolve, reject });
  34. postMessage({ id: "getExternalResource", index: index, uri: uri });
  35. })).then((value) => {
  36. postMessage({ id: "validate.resolve", value: value });
  37. }, (reason) => {
  38. postMessage({ id: "validate.reject", reason: reason });
  39. });
  40. break;
  41. }
  42. case "getExternalResource.resolve": {
  43. pendingExternalResources[data.index].resolve(data.value);
  44. break;
  45. }
  46. case "getExternalResource.reject": {
  47. pendingExternalResources[data.index].reject(data.reason);
  48. break;
  49. }
  50. }
  51. };
  52. }
  53. /**
  54. * Configuration for glTF validation
  55. */
  56. export interface IGLTFValidationConfiguration {
  57. /**
  58. * The url of the glTF validator.
  59. */
  60. url: string;
  61. }
  62. /**
  63. * glTF validation
  64. */
  65. export class GLTFValidation {
  66. /**
  67. * The configuration. Defaults to `{ url: "https://preview.babylonjs.com/gltf_validator.js" }`.
  68. */
  69. public static Configuration: IGLTFValidationConfiguration = {
  70. url: "https://preview.babylonjs.com/gltf_validator.js"
  71. };
  72. private static _LoadScriptPromise: Promise<void>;
  73. /**
  74. * Validate a glTF asset using the glTF-Validator.
  75. * @param data The JSON of a glTF or the array buffer of a binary glTF
  76. * @param rootUrl The root url for the glTF
  77. * @param fileName The file name for the glTF
  78. * @param getExternalResource The callback to get external resources for the glTF validator
  79. * @returns A promise that resolves with the glTF validation results once complete
  80. */
  81. public static ValidateAsync(data: string | ArrayBuffer, rootUrl: string, fileName: string, getExternalResource: (uri: string) => Promise<ArrayBuffer>): Promise<GLTF2.IGLTFValidationResults>
  82. {
  83. if (typeof Worker === "function") {
  84. return new Promise((resolve, reject) => {
  85. const workerContent = `${validateAsync}(${workerFunc})()`;
  86. const workerBlobUrl = URL.createObjectURL(new Blob([workerContent], { type: "application/javascript" }));
  87. const worker = new Worker(workerBlobUrl);
  88. const onError = (error: ErrorEvent) => {
  89. worker.removeEventListener("error", onError);
  90. worker.removeEventListener("message", onMessage);
  91. reject(error);
  92. };
  93. const onMessage = (message: MessageEvent) => {
  94. const data = message.data;
  95. switch (data.id) {
  96. case "getExternalResource": {
  97. getExternalResource(data.uri).then((value) => {
  98. worker.postMessage({ id: "getExternalResource.resolve", index: data.index, value: value }, [value]);
  99. }, (reason) => {
  100. worker.postMessage({ id: "getExternalResource.reject", index: data.index, reason: reason });
  101. });
  102. break;
  103. }
  104. case "validate.resolve": {
  105. worker.removeEventListener("error", onError);
  106. worker.removeEventListener("message", onMessage);
  107. resolve(data.value);
  108. break;
  109. }
  110. case "validate.reject": {
  111. worker.removeEventListener("error", onError);
  112. worker.removeEventListener("message", onMessage);
  113. reject(data.reason);
  114. }
  115. }
  116. };
  117. worker.addEventListener("error", onError);
  118. worker.addEventListener("message", onMessage);
  119. worker.postMessage({ id: "init", url: Tools.GetAbsoluteUrl(this.Configuration.url) });
  120. worker.postMessage({ id: "validate", data: data, rootUrl: rootUrl, fileName: fileName });
  121. });
  122. }
  123. else {
  124. if (!this._LoadScriptPromise) {
  125. this._LoadScriptPromise = Tools.LoadScriptAsync(this.Configuration.url);
  126. }
  127. return this._LoadScriptPromise.then(() => {
  128. return validateAsync(data, rootUrl, fileName, getExternalResource);
  129. });
  130. }
  131. }
  132. }