123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386 |
- import Cartesian3 from "../../Core/Cartesian3.js";
- import defined from "../../Core/defined.js";
- import Matrix4 from "../../Core/Matrix4.js";
- import Quaternion from "../../Core/Quaternion.js";
- import RuntimeError from "../../Core/RuntimeError.js";
- import Axis from "../Axis.js";
- import AttributeType from "../AttributeType.js";
- import VertexAttributeSemantic from "../VertexAttributeSemantic.js";
- import CullFace from "../CullFace.js";
- import PrimitiveType from "../../Core/PrimitiveType.js";
- import Matrix3 from "../../Core/Matrix3.js";
- /**
- * Utility functions for {@link Model}.
- *
- * @private
- */
- function ModelUtility() {}
- /**
- * Create a function for reporting when a model fails to load
- *
- * @param {string} type The type of object to report about
- * @param {string} path The URI of the model file
- * @param {Error} [error] The error which caused the failure
- * @returns {RuntimeError} An error for the failed model
- *
- * @private
- */
- ModelUtility.getError = function (type, path, error) {
- let message = `Failed to load ${type}: ${path}`;
- if (defined(error) && defined(error.message)) {
- message += `\n${error.message}`;
- }
- const runtimeError = new RuntimeError(message);
- if (defined(error)) {
- // the original call stack is often more useful than the new error's stack,
- // so add the information here
- runtimeError.stack = `Original stack:\n${error.stack}\nHandler stack:\n${runtimeError.stack}`;
- }
- return runtimeError;
- };
- /**
- * Get a transformation matrix from a node in the model.
- *
- * @param {ModelComponents.Node} node The node components
- * @returns {Matrix4} The computed transformation matrix. If no transformation matrix or parameters are specified, this will be the identity matrix.
- *
- * @private
- */
- ModelUtility.getNodeTransform = function (node) {
- if (defined(node.matrix)) {
- return node.matrix;
- }
- return Matrix4.fromTranslationQuaternionRotationScale(
- defined(node.translation) ? node.translation : Cartesian3.ZERO,
- defined(node.rotation) ? node.rotation : Quaternion.IDENTITY,
- defined(node.scale) ? node.scale : Cartesian3.ONE
- );
- };
- /**
- * Find an attribute by semantic such as POSITION or TANGENT.
- *
- * @param {ModelComponents.Primitive|ModelComponents.Instances} object The primitive components or instances object
- * @param {VertexAttributeSemantic|InstanceAttributeSemantic} semantic The semantic to search for
- * @param {number} [setIndex] The set index of the semantic. May be undefined for some semantics (POSITION, NORMAL, TRANSLATION, ROTATION, for example)
- * @return {ModelComponents.Attribute} The selected attribute, or undefined if not found.
- *
- * @private
- */
- ModelUtility.getAttributeBySemantic = function (object, semantic, setIndex) {
- const attributes = object.attributes;
- const attributesLength = attributes.length;
- for (let i = 0; i < attributesLength; ++i) {
- const attribute = attributes[i];
- const matchesSetIndex = defined(setIndex)
- ? attribute.setIndex === setIndex
- : true;
- if (attribute.semantic === semantic && matchesSetIndex) {
- return attribute;
- }
- }
- return undefined;
- };
- /**
- * Similar to getAttributeBySemantic, but search using the name field only,
- * as custom attributes do not have a semantic.
- *
- * @param {ModelComponents.Primitive|ModelComponents.Instances} object The primitive components or instances object
- * @param {string} name The name of the attribute as it appears in the model file.
- * @return {ModelComponents.Attribute} The selected attribute, or undefined if not found.
- *
- * @private
- */
- ModelUtility.getAttributeByName = function (object, name) {
- const attributes = object.attributes;
- const attributesLength = attributes.length;
- for (let i = 0; i < attributesLength; ++i) {
- const attribute = attributes[i];
- if (attribute.name === name) {
- return attribute;
- }
- }
- return undefined;
- };
- /**
- * Find a feature ID from an array with label or positionalLabel matching the
- * given label
- * @param {ModelComponents.FeatureIdAttribute[]|ModelComponents.FeatureIdImplicitRange[]|ModelComponents.FeatureIdTexture[]} featureIds
- * @param {string} label the label to search for
- * @returns {ModelComponents.FeatureIdAttribute|ModelComponents.FeatureIdImplicitRange|ModelComponents.FeatureIdTexture} The feature ID set if found, otherwise <code>undefined</code>
- *
- * @private
- */
- ModelUtility.getFeatureIdsByLabel = function (featureIds, label) {
- for (let i = 0; i < featureIds.length; i++) {
- const featureIdSet = featureIds[i];
- if (
- featureIdSet.positionalLabel === label ||
- featureIdSet.label === label
- ) {
- return featureIdSet;
- }
- }
- return undefined;
- };
- ModelUtility.hasQuantizedAttributes = function (attributes) {
- if (!defined(attributes)) {
- return false;
- }
- for (let i = 0; i < attributes.length; i++) {
- const attribute = attributes[i];
- if (defined(attribute.quantization)) {
- return true;
- }
- }
- return false;
- };
- /**
- * @param {ModelComponents.Attribute} attribute
- *
- * @private
- */
- ModelUtility.getAttributeInfo = function (attribute) {
- const semantic = attribute.semantic;
- const setIndex = attribute.setIndex;
- let variableName;
- let hasSemantic = false;
- if (defined(semantic)) {
- variableName = VertexAttributeSemantic.getVariableName(semantic, setIndex);
- hasSemantic = true;
- } else {
- variableName = attribute.name;
- // According to the glTF 2.0 spec, custom attributes must be prepended with
- // an underscore.
- variableName = variableName.replace(/^_/, "");
- variableName = variableName.toLowerCase();
- }
- const isVertexColor = /^color_\d+$/.test(variableName);
- const attributeType = attribute.type;
- let glslType = AttributeType.getGlslType(attributeType);
- // color_n can be either a vec3 or a vec4. But in GLSL we can always use
- // attribute vec4 since GLSL promotes vec3 attribute data to vec4 with
- // the .a channel set to 1.0.
- if (isVertexColor) {
- glslType = "vec4";
- }
- const isQuantized = defined(attribute.quantization);
- let quantizedGlslType;
- if (isQuantized) {
- // The quantized color_n attribute also is promoted to a vec4 in the shader
- quantizedGlslType = isVertexColor
- ? "vec4"
- : AttributeType.getGlslType(attribute.quantization.type);
- }
- return {
- attribute: attribute,
- isQuantized: isQuantized,
- variableName: variableName,
- hasSemantic: hasSemantic,
- glslType: glslType,
- quantizedGlslType: quantizedGlslType,
- };
- };
- const cartesianMaxScratch = new Cartesian3();
- const cartesianMinScratch = new Cartesian3();
- /**
- * Get the minimum and maximum values for a primitive's POSITION attribute.
- * This is used to compute the bounding sphere of the primitive, as well as
- * the bounding sphere of the whole model.
- *
- * @param {ModelComponents.Primitive} primitive The primitive components.
- * @param {Cartesian3} [instancingTranslationMin] The component-wise minimum value of the instancing translation attribute.
- * @param {Cartesian3} [instancingTranslationMax] The component-wise maximum value of the instancing translation attribute.
- *
- * @returns {object} An object containing the minimum and maximum position values.
- *
- * @private
- */
- ModelUtility.getPositionMinMax = function (
- primitive,
- instancingTranslationMin,
- instancingTranslationMax
- ) {
- const positionGltfAttribute = ModelUtility.getAttributeBySemantic(
- primitive,
- "POSITION"
- );
- let positionMax = positionGltfAttribute.max;
- let positionMin = positionGltfAttribute.min;
- if (defined(instancingTranslationMax) && defined(instancingTranslationMin)) {
- positionMin = Cartesian3.add(
- positionMin,
- instancingTranslationMin,
- cartesianMinScratch
- );
- positionMax = Cartesian3.add(
- positionMax,
- instancingTranslationMax,
- cartesianMaxScratch
- );
- }
- return {
- min: positionMin,
- max: positionMax,
- };
- };
- /**
- * Model matrices in a model file (e.g. glTF) are typically in a different
- * coordinate system, such as with y-up instead of z-up in 3D Tiles.
- * This function returns a matrix that will correct this such that z is up,
- * and x is forward.
- *
- * @param {Axis} upAxis The original up direction
- * @param {Axis} forwardAxis The original forward direction
- * @param {Matrix4} result The matrix in which to store the result.
- * @returns {Matrix4} The axis correction matrix
- *
- * @private
- */
- ModelUtility.getAxisCorrectionMatrix = function (upAxis, forwardAxis, result) {
- result = Matrix4.clone(Matrix4.IDENTITY, result);
- if (upAxis === Axis.Y) {
- result = Matrix4.clone(Axis.Y_UP_TO_Z_UP, result);
- } else if (upAxis === Axis.X) {
- result = Matrix4.clone(Axis.X_UP_TO_Z_UP, result);
- }
- if (forwardAxis === Axis.Z) {
- // glTF 2.0 has a Z-forward convention that must be adapted here to X-forward.
- result = Matrix4.multiplyTransformation(result, Axis.Z_UP_TO_X_UP, result);
- }
- return result;
- };
- const scratchMatrix3 = new Matrix3();
- /**
- * Get the cull face to use in the command's render state.
- * <p>
- * From the glTF spec section 3.7.4:
- * When a mesh primitive uses any triangle-based topology (i.e., triangles,
- * triangle strip, or triangle fan), the determinant of the node’s global
- * transform defines the winding order of that primitive. If the determinant
- * is a positive value, the winding order triangle faces is counterclockwise;
- * in the opposite case, the winding order is clockwise.
- * </p>
- *
- * @param {Matrix4} modelMatrix The model matrix
- * @param {PrimitiveType} primitiveType The primitive type
- * @returns {CullFace} The cull face
- *
- * @private
- */
- ModelUtility.getCullFace = function (modelMatrix, primitiveType) {
- if (!PrimitiveType.isTriangles(primitiveType)) {
- return CullFace.BACK;
- }
- const matrix3 = Matrix4.getMatrix3(modelMatrix, scratchMatrix3);
- return Matrix3.determinant(matrix3) < 0.0 ? CullFace.FRONT : CullFace.BACK;
- };
- /**
- * Sanitize the identifier to be used in a GLSL shader. The identifier
- * is sanitized as follows:
- * - Replace all sequences of non-alphanumeric characters with a single `_`.
- * - If the gl_ prefix is present, remove it. The prefix is reserved in GLSL.
- * - If the identifier starts with a digit, prefix it with an underscore.
- *
- * @example
- * // Returns "customProperty"
- * ModelUtility.sanitizeGlslIdentifier("gl_customProperty");
- *
- * @example
- * // Returns "_1234"
- * ModelUtility.sanitizeGlslIdentifier("1234");
- *
- * @param {string} identifier The original identifier.
- *
- * @returns {string} The sanitized version of the identifier.
- */
- ModelUtility.sanitizeGlslIdentifier = function (identifier) {
- // Remove non-alphanumeric characters and replace with a single underscore.
- // This regex is designed so that the result won't have multiple underscores
- // in a row.
- let sanitizedIdentifier = identifier.replaceAll(/[^A-Za-z0-9]+/g, "_");
- // Remove the gl_ prefix if present.
- sanitizedIdentifier = sanitizedIdentifier.replace(/^gl_/, "");
- // Add an underscore if first character is a digit.
- if (/^\d/.test(sanitizedIdentifier)) {
- sanitizedIdentifier = `_${sanitizedIdentifier}`;
- }
- return sanitizedIdentifier;
- };
- ModelUtility.supportedExtensions = {
- AGI_articulations: true,
- CESIUM_primitive_outline: true,
- CESIUM_RTC: true,
- EXT_feature_metadata: true,
- EXT_instance_features: true,
- EXT_mesh_features: true,
- EXT_mesh_gpu_instancing: true,
- EXT_meshopt_compression: true,
- EXT_structural_metadata: true,
- EXT_texture_webp: true,
- KHR_blend: true,
- KHR_draco_mesh_compression: true,
- KHR_techniques_webgl: true,
- KHR_materials_common: true,
- KHR_materials_pbrSpecularGlossiness: true,
- KHR_materials_unlit: true,
- KHR_mesh_quantization: true,
- KHR_texture_basisu: true,
- KHR_texture_transform: true,
- WEB3D_quantized_attributes: true,
- };
- /**
- * Checks whether or not the extensions required by the glTF are
- * supported. If an unsupported extension is found, this throws
- * a {@link RuntimeError} with the extension name.
- *
- * @param {string[]} extensionsRequired The extensionsRequired array in the glTF.
- *
- * @exception {RuntimeError} Unsupported glTF Extension
- */
- ModelUtility.checkSupportedExtensions = function (extensionsRequired) {
- const length = extensionsRequired.length;
- for (let i = 0; i < length; i++) {
- const extension = extensionsRequired[i];
- if (!ModelUtility.supportedExtensions[extension]) {
- throw new RuntimeError(`Unsupported glTF Extension: ${extension}`);
- }
- }
- };
- export default ModelUtility;
|