ModelExperimentalUtility.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. import BoundingSphere from "../../Core/BoundingSphere.js";
  2. import Cartesian3 from "../../Core/Cartesian3.js";
  3. import defined from "../../Core/defined.js";
  4. import Matrix4 from "../../Core/Matrix4.js";
  5. import Quaternion from "../../Core/Quaternion.js";
  6. import RuntimeError from "../../Core/RuntimeError.js";
  7. import Axis from "../Axis.js";
  8. import AttributeType from "../AttributeType.js";
  9. import VertexAttributeSemantic from "../VertexAttributeSemantic.js";
  10. /**
  11. * Utility functions for {@link ModelExperimental}.
  12. *
  13. * @private
  14. */
  15. export default function ModelExperimentalUtility() {}
  16. /**
  17. * Create a function for reporting when a model fails to load
  18. *
  19. * @param {ModelExperimental} model The model to report about
  20. * @param {String} type The type of object to report about
  21. * @param {String} path The URI of the model file
  22. * @return {Function} An error function that throws an error for the failed model
  23. *
  24. * @private
  25. */
  26. ModelExperimentalUtility.getFailedLoadFunction = function (model, type, path) {
  27. return function (error) {
  28. let message = `Failed to load ${type}: ${path}`;
  29. if (defined(error)) {
  30. message += `\n${error.message}`;
  31. }
  32. model._readyPromise.reject(new RuntimeError(message));
  33. };
  34. };
  35. /**
  36. * Get a transformation matrix from a node in the model.
  37. *
  38. * @param {ModelComponents.Node} node The node components
  39. * @return {Matrix4} The computed transformation matrix. If no transformation matrix or parameters are specified, this will be the identity matrix.
  40. *
  41. * @private
  42. */
  43. ModelExperimentalUtility.getNodeTransform = function (node) {
  44. if (defined(node.matrix)) {
  45. return node.matrix;
  46. }
  47. return Matrix4.fromTranslationQuaternionRotationScale(
  48. defined(node.translation) ? node.translation : Cartesian3.ZERO,
  49. defined(node.rotation) ? node.rotation : Quaternion.IDENTITY,
  50. defined(node.scale) ? node.scale : Cartesian3.ONE
  51. );
  52. };
  53. /**
  54. * Find an attribute by semantic such as POSITION or TANGENT.
  55. *
  56. * @param {ModelComponents.Primitive|ModelComponents.Instances} object The primitive components or instances object
  57. * @param {VertexAttributeSemantic|InstanceAttributeSemantic} semantic The semantic to search for
  58. * @param {Number} setIndex The set index of the semantic. May be undefined for some semantics (POSITION, NORMAL, TRANSLATION, ROTATION, for example)
  59. * @return {ModelComponents.Attribute} The selected attribute, or undefined if not found.
  60. *
  61. * @private
  62. */
  63. ModelExperimentalUtility.getAttributeBySemantic = function (
  64. object,
  65. semantic,
  66. setIndex
  67. ) {
  68. const attributes = object.attributes;
  69. const attributesLength = attributes.length;
  70. for (let i = 0; i < attributesLength; ++i) {
  71. const attribute = attributes[i];
  72. const matchesSetIndex = defined(setIndex)
  73. ? attribute.setIndex === setIndex
  74. : true;
  75. if (attribute.semantic === semantic && matchesSetIndex) {
  76. return attribute;
  77. }
  78. }
  79. };
  80. /**
  81. * Similar to getAttributeBySemantic, but search using the name field only,
  82. * as custom attributes do not have a semantic.
  83. *
  84. * @param {ModelComponents.Primitive|ModelComponents.Instances} object The primitive components or instances object
  85. * @param {String} name The name of the attribute as it appears in the model file.
  86. * @return {ModelComponents.Attribute} The selected attribute, or undefined if not found.
  87. *
  88. * @private
  89. */
  90. ModelExperimentalUtility.getAttributeByName = function (object, name) {
  91. const attributes = object.attributes;
  92. const attributesLength = attributes.length;
  93. for (let i = 0; i < attributesLength; ++i) {
  94. const attribute = attributes[i];
  95. if (attribute.name === name) {
  96. return attribute;
  97. }
  98. }
  99. };
  100. /**
  101. * Find a feature ID from an array with label or positionalLabel matching the
  102. * given label
  103. * @param {Array.<ModelComponents.FeatureIdAttribute|ModelComponents.FeatureIdImplicitRange|ModelComponents.FeatureIdTexture>} featureIds
  104. * @param {String} label the label to search for
  105. * @return {ModelComponents.FeatureIdAttribute|ModelComponents.FeatureIdImplicitRange|ModelComponents.FeatureIdTexture} The feature ID set if found, otherwise <code>undefined</code>
  106. *
  107. * @private
  108. */
  109. ModelExperimentalUtility.getFeatureIdsByLabel = function (featureIds, label) {
  110. for (let i = 0; i < featureIds.length; i++) {
  111. const featureIdSet = featureIds[i];
  112. if (
  113. featureIdSet.positionalLabel === label ||
  114. featureIdSet.label === label
  115. ) {
  116. return featureIdSet;
  117. }
  118. }
  119. return undefined;
  120. };
  121. ModelExperimentalUtility.hasQuantizedAttributes = function (attributes) {
  122. if (!defined(attributes)) {
  123. return false;
  124. }
  125. for (let i = 0; i < attributes.length; i++) {
  126. const attribute = attributes[i];
  127. if (defined(attribute.quantization)) {
  128. return true;
  129. }
  130. }
  131. return false;
  132. };
  133. /**
  134. * @param {ModelComponents.Attribute} attribute
  135. *
  136. * @private
  137. */
  138. ModelExperimentalUtility.getAttributeInfo = function (attribute) {
  139. const semantic = attribute.semantic;
  140. const setIndex = attribute.setIndex;
  141. let variableName;
  142. let hasSemantic = false;
  143. if (defined(semantic)) {
  144. variableName = VertexAttributeSemantic.getVariableName(semantic, setIndex);
  145. hasSemantic = true;
  146. } else {
  147. variableName = attribute.name;
  148. // According to the glTF 2.0 spec, custom attributes must be prepended with
  149. // an underscore.
  150. variableName = variableName.replace(/^_/, "");
  151. variableName = variableName.toLowerCase();
  152. }
  153. const isVertexColor = /^color_\d+$/.test(variableName);
  154. const attributeType = attribute.type;
  155. let glslType = AttributeType.getGlslType(attributeType);
  156. // color_n can be either a vec3 or a vec4. But in GLSL we can always use
  157. // attribute vec4 since GLSL promotes vec3 attribute data to vec4 with
  158. // the .a channel set to 1.0.
  159. if (isVertexColor) {
  160. glslType = "vec4";
  161. }
  162. const isQuantized = defined(attribute.quantization);
  163. let quantizedGlslType;
  164. if (isQuantized) {
  165. // The quantized color_n attribute also is promoted to a vec4 in the shader
  166. quantizedGlslType = isVertexColor
  167. ? "vec4"
  168. : AttributeType.getGlslType(attribute.quantization.type);
  169. }
  170. return {
  171. attribute: attribute,
  172. isQuantized: isQuantized,
  173. variableName: variableName,
  174. hasSemantic: hasSemantic,
  175. glslType: glslType,
  176. quantizedGlslType: quantizedGlslType,
  177. };
  178. };
  179. const cartesianMaxScratch = new Cartesian3();
  180. const cartesianMinScratch = new Cartesian3();
  181. /**
  182. * Create a bounding sphere from a primitive's POSITION attribute and model
  183. * matrix.
  184. *
  185. * @param {ModelComponents.Primitive} primitive The primitive components.
  186. * @param {Matrix4} modelMatrix The primitive's model matrix.
  187. * @param {Cartesian3} [instancingTranslationMax] The component-wise maximum value of the instancing translation attribute.
  188. * @param {Cartesian3} [instancingTranslationMin] The component-wise minimum value of the instancing translation attribute.
  189. */
  190. ModelExperimentalUtility.createBoundingSphere = function (
  191. primitive,
  192. modelMatrix,
  193. instancingTranslationMax,
  194. instancingTranslationMin
  195. ) {
  196. const positionGltfAttribute = ModelExperimentalUtility.getAttributeBySemantic(
  197. primitive,
  198. "POSITION"
  199. );
  200. const positionMax = positionGltfAttribute.max;
  201. const positionMin = positionGltfAttribute.min;
  202. let boundingSphere;
  203. if (defined(instancingTranslationMax) && defined(instancingTranslationMin)) {
  204. const computedMin = Cartesian3.add(
  205. positionMin,
  206. instancingTranslationMin,
  207. cartesianMinScratch
  208. );
  209. const computedMax = Cartesian3.add(
  210. positionMax,
  211. instancingTranslationMax,
  212. cartesianMaxScratch
  213. );
  214. boundingSphere = BoundingSphere.fromCornerPoints(computedMin, computedMax);
  215. } else {
  216. boundingSphere = BoundingSphere.fromCornerPoints(positionMin, positionMax);
  217. }
  218. BoundingSphere.transform(boundingSphere, modelMatrix, boundingSphere);
  219. return boundingSphere;
  220. };
  221. /**
  222. * Model matrices in a model file (e.g. glTF) are typically in a different
  223. * coordinate system, such as with y-up instead of z-up in 3D Tiles.
  224. * This function returns a matrix that will correct this such that z is up,
  225. * and x is forward.
  226. *
  227. * @param {Axis} upAxis The original up direction
  228. * @param {Axis} forwardAxis The original forward direction
  229. * @param {Matrix4} result The matrix in which to store the result.
  230. * @return {Matrix4} The axis correction matrix
  231. *
  232. * @private
  233. */
  234. ModelExperimentalUtility.getAxisCorrectionMatrix = function (
  235. upAxis,
  236. forwardAxis,
  237. result
  238. ) {
  239. result = Matrix4.clone(Matrix4.IDENTITY, result);
  240. if (upAxis === Axis.Y) {
  241. result = Matrix4.clone(Axis.Y_UP_TO_Z_UP, result);
  242. } else if (upAxis === Axis.X) {
  243. result = Matrix4.clone(Axis.X_UP_TO_Z_UP, result);
  244. }
  245. if (forwardAxis === Axis.Z) {
  246. // glTF 2.0 has a Z-forward convention that must be adapted here to X-forward.
  247. result = Matrix4.multiplyTransformation(result, Axis.Z_UP_TO_X_UP, result);
  248. }
  249. return result;
  250. };