ModelUtility.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. import Cartesian3 from "../../Core/Cartesian3.js";
  2. import defined from "../../Core/defined.js";
  3. import Matrix4 from "../../Core/Matrix4.js";
  4. import Quaternion from "../../Core/Quaternion.js";
  5. import RuntimeError from "../../Core/RuntimeError.js";
  6. import Axis from "../Axis.js";
  7. import AttributeType from "../AttributeType.js";
  8. import VertexAttributeSemantic from "../VertexAttributeSemantic.js";
  9. import CullFace from "../CullFace.js";
  10. import PrimitiveType from "../../Core/PrimitiveType.js";
  11. import Matrix3 from "../../Core/Matrix3.js";
  12. /**
  13. * Utility functions for {@link Model}.
  14. *
  15. * @private
  16. */
  17. function ModelUtility() {}
  18. /**
  19. * Create a function for reporting when a model fails to load
  20. *
  21. * @param {string} type The type of object to report about
  22. * @param {string} path The URI of the model file
  23. * @param {Error} [error] The error which caused the failure
  24. * @returns {RuntimeError} An error for the failed model
  25. *
  26. * @private
  27. */
  28. ModelUtility.getError = function (type, path, error) {
  29. let message = `Failed to load ${type}: ${path}`;
  30. if (defined(error) && defined(error.message)) {
  31. message += `\n${error.message}`;
  32. }
  33. const runtimeError = new RuntimeError(message);
  34. if (defined(error)) {
  35. // the original call stack is often more useful than the new error's stack,
  36. // so add the information here
  37. runtimeError.stack = `Original stack:\n${error.stack}\nHandler stack:\n${runtimeError.stack}`;
  38. }
  39. return runtimeError;
  40. };
  41. /**
  42. * Get a transformation matrix from a node in the model.
  43. *
  44. * @param {ModelComponents.Node} node The node components
  45. * @returns {Matrix4} The computed transformation matrix. If no transformation matrix or parameters are specified, this will be the identity matrix.
  46. *
  47. * @private
  48. */
  49. ModelUtility.getNodeTransform = function (node) {
  50. if (defined(node.matrix)) {
  51. return node.matrix;
  52. }
  53. return Matrix4.fromTranslationQuaternionRotationScale(
  54. defined(node.translation) ? node.translation : Cartesian3.ZERO,
  55. defined(node.rotation) ? node.rotation : Quaternion.IDENTITY,
  56. defined(node.scale) ? node.scale : Cartesian3.ONE
  57. );
  58. };
  59. /**
  60. * Find an attribute by semantic such as POSITION or TANGENT.
  61. *
  62. * @param {ModelComponents.Primitive|ModelComponents.Instances} object The primitive components or instances object
  63. * @param {VertexAttributeSemantic|InstanceAttributeSemantic} semantic The semantic to search for
  64. * @param {number} [setIndex] The set index of the semantic. May be undefined for some semantics (POSITION, NORMAL, TRANSLATION, ROTATION, for example)
  65. * @return {ModelComponents.Attribute} The selected attribute, or undefined if not found.
  66. *
  67. * @private
  68. */
  69. ModelUtility.getAttributeBySemantic = function (object, semantic, setIndex) {
  70. const attributes = object.attributes;
  71. const attributesLength = attributes.length;
  72. for (let i = 0; i < attributesLength; ++i) {
  73. const attribute = attributes[i];
  74. const matchesSetIndex = defined(setIndex)
  75. ? attribute.setIndex === setIndex
  76. : true;
  77. if (attribute.semantic === semantic && matchesSetIndex) {
  78. return attribute;
  79. }
  80. }
  81. return undefined;
  82. };
  83. /**
  84. * Similar to getAttributeBySemantic, but search using the name field only,
  85. * as custom attributes do not have a semantic.
  86. *
  87. * @param {ModelComponents.Primitive|ModelComponents.Instances} object The primitive components or instances object
  88. * @param {string} name The name of the attribute as it appears in the model file.
  89. * @return {ModelComponents.Attribute} The selected attribute, or undefined if not found.
  90. *
  91. * @private
  92. */
  93. ModelUtility.getAttributeByName = function (object, name) {
  94. const attributes = object.attributes;
  95. const attributesLength = attributes.length;
  96. for (let i = 0; i < attributesLength; ++i) {
  97. const attribute = attributes[i];
  98. if (attribute.name === name) {
  99. return attribute;
  100. }
  101. }
  102. return undefined;
  103. };
  104. /**
  105. * Find a feature ID from an array with label or positionalLabel matching the
  106. * given label
  107. * @param {ModelComponents.FeatureIdAttribute[]|ModelComponents.FeatureIdImplicitRange[]|ModelComponents.FeatureIdTexture[]} featureIds
  108. * @param {string} label the label to search for
  109. * @returns {ModelComponents.FeatureIdAttribute|ModelComponents.FeatureIdImplicitRange|ModelComponents.FeatureIdTexture} The feature ID set if found, otherwise <code>undefined</code>
  110. *
  111. * @private
  112. */
  113. ModelUtility.getFeatureIdsByLabel = function (featureIds, label) {
  114. for (let i = 0; i < featureIds.length; i++) {
  115. const featureIdSet = featureIds[i];
  116. if (
  117. featureIdSet.positionalLabel === label ||
  118. featureIdSet.label === label
  119. ) {
  120. return featureIdSet;
  121. }
  122. }
  123. return undefined;
  124. };
  125. ModelUtility.hasQuantizedAttributes = function (attributes) {
  126. if (!defined(attributes)) {
  127. return false;
  128. }
  129. for (let i = 0; i < attributes.length; i++) {
  130. const attribute = attributes[i];
  131. if (defined(attribute.quantization)) {
  132. return true;
  133. }
  134. }
  135. return false;
  136. };
  137. /**
  138. * @param {ModelComponents.Attribute} attribute
  139. *
  140. * @private
  141. */
  142. ModelUtility.getAttributeInfo = function (attribute) {
  143. const semantic = attribute.semantic;
  144. const setIndex = attribute.setIndex;
  145. let variableName;
  146. let hasSemantic = false;
  147. if (defined(semantic)) {
  148. variableName = VertexAttributeSemantic.getVariableName(semantic, setIndex);
  149. hasSemantic = true;
  150. } else {
  151. variableName = attribute.name;
  152. // According to the glTF 2.0 spec, custom attributes must be prepended with
  153. // an underscore.
  154. variableName = variableName.replace(/^_/, "");
  155. variableName = variableName.toLowerCase();
  156. }
  157. const isVertexColor = /^color_\d+$/.test(variableName);
  158. const attributeType = attribute.type;
  159. let glslType = AttributeType.getGlslType(attributeType);
  160. // color_n can be either a vec3 or a vec4. But in GLSL we can always use
  161. // attribute vec4 since GLSL promotes vec3 attribute data to vec4 with
  162. // the .a channel set to 1.0.
  163. if (isVertexColor) {
  164. glslType = "vec4";
  165. }
  166. const isQuantized = defined(attribute.quantization);
  167. let quantizedGlslType;
  168. if (isQuantized) {
  169. // The quantized color_n attribute also is promoted to a vec4 in the shader
  170. quantizedGlslType = isVertexColor
  171. ? "vec4"
  172. : AttributeType.getGlslType(attribute.quantization.type);
  173. }
  174. return {
  175. attribute: attribute,
  176. isQuantized: isQuantized,
  177. variableName: variableName,
  178. hasSemantic: hasSemantic,
  179. glslType: glslType,
  180. quantizedGlslType: quantizedGlslType,
  181. };
  182. };
  183. const cartesianMaxScratch = new Cartesian3();
  184. const cartesianMinScratch = new Cartesian3();
  185. /**
  186. * Get the minimum and maximum values for a primitive's POSITION attribute.
  187. * This is used to compute the bounding sphere of the primitive, as well as
  188. * the bounding sphere of the whole model.
  189. *
  190. * @param {ModelComponents.Primitive} primitive The primitive components.
  191. * @param {Cartesian3} [instancingTranslationMin] The component-wise minimum value of the instancing translation attribute.
  192. * @param {Cartesian3} [instancingTranslationMax] The component-wise maximum value of the instancing translation attribute.
  193. *
  194. * @returns {object} An object containing the minimum and maximum position values.
  195. *
  196. * @private
  197. */
  198. ModelUtility.getPositionMinMax = function (
  199. primitive,
  200. instancingTranslationMin,
  201. instancingTranslationMax
  202. ) {
  203. const positionGltfAttribute = ModelUtility.getAttributeBySemantic(
  204. primitive,
  205. "POSITION"
  206. );
  207. let positionMax = positionGltfAttribute.max;
  208. let positionMin = positionGltfAttribute.min;
  209. if (defined(instancingTranslationMax) && defined(instancingTranslationMin)) {
  210. positionMin = Cartesian3.add(
  211. positionMin,
  212. instancingTranslationMin,
  213. cartesianMinScratch
  214. );
  215. positionMax = Cartesian3.add(
  216. positionMax,
  217. instancingTranslationMax,
  218. cartesianMaxScratch
  219. );
  220. }
  221. return {
  222. min: positionMin,
  223. max: positionMax,
  224. };
  225. };
  226. /**
  227. * Model matrices in a model file (e.g. glTF) are typically in a different
  228. * coordinate system, such as with y-up instead of z-up in 3D Tiles.
  229. * This function returns a matrix that will correct this such that z is up,
  230. * and x is forward.
  231. *
  232. * @param {Axis} upAxis The original up direction
  233. * @param {Axis} forwardAxis The original forward direction
  234. * @param {Matrix4} result The matrix in which to store the result.
  235. * @returns {Matrix4} The axis correction matrix
  236. *
  237. * @private
  238. */
  239. ModelUtility.getAxisCorrectionMatrix = function (upAxis, forwardAxis, result) {
  240. result = Matrix4.clone(Matrix4.IDENTITY, result);
  241. if (upAxis === Axis.Y) {
  242. result = Matrix4.clone(Axis.Y_UP_TO_Z_UP, result);
  243. } else if (upAxis === Axis.X) {
  244. result = Matrix4.clone(Axis.X_UP_TO_Z_UP, result);
  245. }
  246. if (forwardAxis === Axis.Z) {
  247. // glTF 2.0 has a Z-forward convention that must be adapted here to X-forward.
  248. result = Matrix4.multiplyTransformation(result, Axis.Z_UP_TO_X_UP, result);
  249. }
  250. return result;
  251. };
  252. const scratchMatrix3 = new Matrix3();
  253. /**
  254. * Get the cull face to use in the command's render state.
  255. * <p>
  256. * From the glTF spec section 3.7.4:
  257. * When a mesh primitive uses any triangle-based topology (i.e., triangles,
  258. * triangle strip, or triangle fan), the determinant of the node’s global
  259. * transform defines the winding order of that primitive. If the determinant
  260. * is a positive value, the winding order triangle faces is counterclockwise;
  261. * in the opposite case, the winding order is clockwise.
  262. * </p>
  263. *
  264. * @param {Matrix4} modelMatrix The model matrix
  265. * @param {PrimitiveType} primitiveType The primitive type
  266. * @returns {CullFace} The cull face
  267. *
  268. * @private
  269. */
  270. ModelUtility.getCullFace = function (modelMatrix, primitiveType) {
  271. if (!PrimitiveType.isTriangles(primitiveType)) {
  272. return CullFace.BACK;
  273. }
  274. const matrix3 = Matrix4.getMatrix3(modelMatrix, scratchMatrix3);
  275. return Matrix3.determinant(matrix3) < 0.0 ? CullFace.FRONT : CullFace.BACK;
  276. };
  277. /**
  278. * Sanitize the identifier to be used in a GLSL shader. The identifier
  279. * is sanitized as follows:
  280. * - Replace all sequences of non-alphanumeric characters with a single `_`.
  281. * - If the gl_ prefix is present, remove it. The prefix is reserved in GLSL.
  282. * - If the identifier starts with a digit, prefix it with an underscore.
  283. *
  284. * @example
  285. * // Returns "customProperty"
  286. * ModelUtility.sanitizeGlslIdentifier("gl_customProperty");
  287. *
  288. * @example
  289. * // Returns "_1234"
  290. * ModelUtility.sanitizeGlslIdentifier("1234");
  291. *
  292. * @param {string} identifier The original identifier.
  293. *
  294. * @returns {string} The sanitized version of the identifier.
  295. */
  296. ModelUtility.sanitizeGlslIdentifier = function (identifier) {
  297. // Remove non-alphanumeric characters and replace with a single underscore.
  298. // This regex is designed so that the result won't have multiple underscores
  299. // in a row.
  300. let sanitizedIdentifier = identifier.replaceAll(/[^A-Za-z0-9]+/g, "_");
  301. // Remove the gl_ prefix if present.
  302. sanitizedIdentifier = sanitizedIdentifier.replace(/^gl_/, "");
  303. // Add an underscore if first character is a digit.
  304. if (/^\d/.test(sanitizedIdentifier)) {
  305. sanitizedIdentifier = `_${sanitizedIdentifier}`;
  306. }
  307. return sanitizedIdentifier;
  308. };
  309. ModelUtility.supportedExtensions = {
  310. AGI_articulations: true,
  311. CESIUM_primitive_outline: true,
  312. CESIUM_RTC: true,
  313. EXT_feature_metadata: true,
  314. EXT_instance_features: true,
  315. EXT_mesh_features: true,
  316. EXT_mesh_gpu_instancing: true,
  317. EXT_meshopt_compression: true,
  318. EXT_structural_metadata: true,
  319. EXT_texture_webp: true,
  320. KHR_blend: true,
  321. KHR_draco_mesh_compression: true,
  322. KHR_techniques_webgl: true,
  323. KHR_materials_common: true,
  324. KHR_materials_pbrSpecularGlossiness: true,
  325. KHR_materials_unlit: true,
  326. KHR_mesh_quantization: true,
  327. KHR_texture_basisu: true,
  328. KHR_texture_transform: true,
  329. WEB3D_quantized_attributes: true,
  330. };
  331. /**
  332. * Checks whether or not the extensions required by the glTF are
  333. * supported. If an unsupported extension is found, this throws
  334. * a {@link RuntimeError} with the extension name.
  335. *
  336. * @param {string[]} extensionsRequired The extensionsRequired array in the glTF.
  337. *
  338. * @exception {RuntimeError} Unsupported glTF Extension
  339. */
  340. ModelUtility.checkSupportedExtensions = function (extensionsRequired) {
  341. const length = extensionsRequired.length;
  342. for (let i = 0; i < length; i++) {
  343. const extension = extensionsRequired[i];
  344. if (!ModelUtility.supportedExtensions[extension]) {
  345. throw new RuntimeError(`Unsupported glTF Extension: ${extension}`);
  346. }
  347. }
  348. };
  349. export default ModelUtility;