import BoundingSphere from "../Core/BoundingSphere.js"; import Cartesian2 from "../Core/Cartesian2.js"; import Cartesian3 from "../Core/Cartesian3.js"; import Cartesian4 from "../Core/Cartesian4.js"; import clone from "../Core/clone.js"; import defined from "../Core/defined.js"; import Matrix2 from "../Core/Matrix2.js"; import Matrix3 from "../Core/Matrix3.js"; import Matrix4 from "../Core/Matrix4.js"; import Quaternion from "../Core/Quaternion.js"; import RuntimeError from "../Core/RuntimeError.js"; import WebGLConstants from "../Core/WebGLConstants.js"; import ShaderSource from "../Renderer/ShaderSource.js"; import addToArray from "../ThirdParty/GltfPipeline/addToArray.js"; import ForEach from "../ThirdParty/GltfPipeline/ForEach.js"; import hasExtension from "../ThirdParty/GltfPipeline/hasExtension.js"; import AttributeType from "./AttributeType.js"; import Axis from "./Axis.js"; /** * @private */ var ModelUtility = {}; /** * Updates the model's forward axis if the model is not a 2.0 model. * * @param {Object} model The model to update. */ ModelUtility.updateForwardAxis = function (model) { var cachedSourceVersion = model.gltf.extras.sourceVersion; if ( (defined(cachedSourceVersion) && cachedSourceVersion !== "2.0") || ModelUtility.getAssetVersion(model.gltf) !== "2.0" ) { model._gltfForwardAxis = Axis.X; } }; /** * Gets the string representing the glTF asset version. * * @param {Object} gltf A javascript object containing a glTF asset. * @returns {String} The glTF asset version string. */ ModelUtility.getAssetVersion = function (gltf) { // In glTF 1.0 it was valid to omit the version number. if (!defined(gltf.asset) || !defined(gltf.asset.version)) { return "1.0"; } return gltf.asset.version; }; /** * Splits primitive materials with values incompatible for generating techniques. * * @param {Object} gltf A javascript object containing a glTF asset. * @returns {Object} The glTF asset with modified materials. */ ModelUtility.splitIncompatibleMaterials = function (gltf) { var accessors = gltf.accessors; var materials = gltf.materials; var primitiveInfoByMaterial = {}; ForEach.mesh(gltf, function (mesh) { ForEach.meshPrimitive(mesh, function (primitive) { var materialIndex = primitive.material; var material = materials[materialIndex]; var jointAccessorId = primitive.attributes.JOINTS_0; var componentType; var accessorType; if (defined(jointAccessorId)) { var jointAccessor = accessors[jointAccessorId]; componentType = jointAccessor.componentType; accessorType = jointAccessor.type; } var isSkinned = defined(jointAccessorId) && accessorType === "VEC4"; var hasVertexColors = defined(primitive.attributes.COLOR_0); var hasMorphTargets = defined(primitive.targets); var hasNormals = defined(primitive.attributes.NORMAL); var hasTangents = defined(primitive.attributes.TANGENT); var hasTexCoords = defined(primitive.attributes.TEXCOORD_0); var hasTexCoord1 = hasTexCoords && defined(primitive.attributes.TEXCOORD_1); var hasOutline = defined(primitive.extensions) && defined(primitive.extensions.CESIUM_primitive_outline); var primitiveInfo = primitiveInfoByMaterial[materialIndex]; if (!defined(primitiveInfo)) { primitiveInfoByMaterial[materialIndex] = { skinning: { skinned: isSkinned, componentType: componentType, }, hasVertexColors: hasVertexColors, hasMorphTargets: hasMorphTargets, hasNormals: hasNormals, hasTangents: hasTangents, hasTexCoords: hasTexCoords, hasTexCoord1: hasTexCoord1, hasOutline: hasOutline, }; } else if ( primitiveInfo.skinning.skinned !== isSkinned || primitiveInfo.hasVertexColors !== hasVertexColors || primitiveInfo.hasMorphTargets !== hasMorphTargets || primitiveInfo.hasNormals !== hasNormals || primitiveInfo.hasTangents !== hasTangents || primitiveInfo.hasTexCoords !== hasTexCoords || primitiveInfo.hasTexCoord1 !== hasTexCoord1 || primitiveInfo.hasOutline !== hasOutline ) { // This primitive uses the same material as another one that either: // * Isn't skinned // * Uses a different type to store joints and weights // * Doesn't have vertex colors, morph targets, normals, tangents, or texCoords // * Doesn't have a CESIUM_primitive_outline extension. var clonedMaterial = clone(material, true); // Split this off as a separate material materialIndex = addToArray(materials, clonedMaterial); primitive.material = materialIndex; primitiveInfoByMaterial[materialIndex] = { skinning: { skinned: isSkinned, componentType: componentType, }, hasVertexColors: hasVertexColors, hasMorphTargets: hasMorphTargets, hasNormals: hasNormals, hasTangents: hasTangents, hasTexCoords: hasTexCoords, hasTexCoord1: hasTexCoord1, hasOutline: hasOutline, }; } }); }); return primitiveInfoByMaterial; }; ModelUtility.getShaderVariable = function (type) { if (type === "SCALAR") { return "float"; } return type.toLowerCase(); }; ModelUtility.ModelState = { NEEDS_LOAD: 0, LOADING: 1, LOADED: 2, // Renderable, but textures can still be pending when incrementallyLoadTextures is true. FAILED: 3, }; ModelUtility.getFailedLoadFunction = function (model, type, path) { return function (error) { model._state = ModelUtility.ModelState.FAILED; var message = "Failed to load " + type + ": " + path; if (defined(error)) { message += "\n" + error.message; } model._readyPromise.reject(new RuntimeError(message)); }; }; ModelUtility.parseBuffers = function (model, bufferLoad) { var loadResources = model._loadResources; ForEach.buffer(model.gltf, function (buffer, bufferViewId) { if (defined(buffer.extras._pipeline.source)) { loadResources.buffers[bufferViewId] = buffer.extras._pipeline.source; } else if (defined(bufferLoad)) { var bufferResource = model._resource.getDerivedResource({ url: buffer.uri, }); ++loadResources.pendingBufferLoads; bufferResource .fetchArrayBuffer() .then(bufferLoad(model, bufferViewId)) .otherwise( ModelUtility.getFailedLoadFunction( model, "buffer", bufferResource.url ) ); } }); }; var aMinScratch = new Cartesian3(); var aMaxScratch = new Cartesian3(); ModelUtility.computeBoundingSphere = function (model) { var gltf = model.gltf; var gltfNodes = gltf.nodes; var gltfMeshes = gltf.meshes; var rootNodes = gltf.scenes[gltf.scene].nodes; var rootNodesLength = rootNodes.length; var nodeStack = []; var min = new Cartesian3( Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE ); var max = new Cartesian3( -Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE ); for (var i = 0; i < rootNodesLength; ++i) { var n = gltfNodes[rootNodes[i]]; n._transformToRoot = ModelUtility.getTransform(n); nodeStack.push(n); while (nodeStack.length > 0) { n = nodeStack.pop(); var transformToRoot = n._transformToRoot; var meshId = n.mesh; if (defined(meshId)) { var mesh = gltfMeshes[meshId]; var primitives = mesh.primitives; var primitivesLength = primitives.length; for (var m = 0; m < primitivesLength; ++m) { var positionAccessor = primitives[m].attributes.POSITION; if (defined(positionAccessor)) { var minMax = ModelUtility.getAccessorMinMax(gltf, positionAccessor); if (defined(minMax.min) && defined(minMax.max)) { var aMin = Cartesian3.fromArray(minMax.min, 0, aMinScratch); var aMax = Cartesian3.fromArray(minMax.max, 0, aMaxScratch); Matrix4.multiplyByPoint(transformToRoot, aMin, aMin); Matrix4.multiplyByPoint(transformToRoot, aMax, aMax); Cartesian3.minimumByComponent(min, aMin, min); Cartesian3.maximumByComponent(max, aMax, max); } } } } var children = n.children; if (defined(children)) { var childrenLength = children.length; for (var k = 0; k < childrenLength; ++k) { var child = gltfNodes[children[k]]; child._transformToRoot = ModelUtility.getTransform(child); Matrix4.multiplyTransformation( transformToRoot, child._transformToRoot, child._transformToRoot ); nodeStack.push(child); } } delete n._transformToRoot; } } var boundingSphere = BoundingSphere.fromCornerPoints(min, max); if (model._forwardAxis === Axis.Z) { // glTF 2.0 has a Z-forward convention that must be adapted here to X-forward. BoundingSphere.transformWithoutScale( boundingSphere, Axis.Z_UP_TO_X_UP, boundingSphere ); } if (model._upAxis === Axis.Y) { BoundingSphere.transformWithoutScale( boundingSphere, Axis.Y_UP_TO_Z_UP, boundingSphere ); } else if (model._upAxis === Axis.X) { BoundingSphere.transformWithoutScale( boundingSphere, Axis.X_UP_TO_Z_UP, boundingSphere ); } return boundingSphere; }; function techniqueAttributeForSemantic(technique, semantic) { return ForEach.techniqueAttribute(technique, function ( attribute, attributeName ) { if (attribute.semantic === semantic) { return attributeName; } }); } function ensureSemanticExistenceForPrimitive(gltf, primitive) { var accessors = gltf.accessors; var materials = gltf.materials; var techniquesWebgl = gltf.extensions.KHR_techniques_webgl; var techniques = techniquesWebgl.techniques; var programs = techniquesWebgl.programs; var shaders = techniquesWebgl.shaders; var targets = primitive.targets; var attributes = primitive.attributes; for (var target in targets) { if (targets.hasOwnProperty(target)) { var targetAttributes = targets[target]; for (var attribute in targetAttributes) { if (attribute !== "extras") { attributes[attribute + "_" + target] = targetAttributes[attribute]; } } } } var material = materials[primitive.material]; var technique = techniques[material.extensions.KHR_techniques_webgl.technique]; var program = programs[technique.program]; var vertexShader = shaders[program.vertexShader]; for (var semantic in attributes) { if (attributes.hasOwnProperty(semantic)) { if (!defined(techniqueAttributeForSemantic(technique, semantic))) { var accessorId = attributes[semantic]; var accessor = accessors[accessorId]; var lowerCase = semantic.toLowerCase(); if (lowerCase.charAt(0) === "_") { lowerCase = lowerCase.slice(1); } var attributeName = "a_" + lowerCase; technique.attributes[attributeName] = { semantic: semantic, type: accessor.componentType, }; var pipelineExtras = vertexShader.extras._pipeline; var shaderText = pipelineExtras.source; shaderText = "attribute " + ModelUtility.getShaderVariable(accessor.type) + " " + attributeName + ";\n" + shaderText; pipelineExtras.source = shaderText; } } } } /** * Ensures all attributes present on the primitive are present in the technique and * vertex shader. * * @param {Object} gltf A javascript object containing a glTF asset. * @returns {Object} The glTF asset, including any additional attributes. */ ModelUtility.ensureSemanticExistence = function (gltf) { ForEach.mesh(gltf, function (mesh) { ForEach.meshPrimitive(mesh, function (primitive) { ensureSemanticExistenceForPrimitive(gltf, primitive); }); }); return gltf; }; /** * Creates attribute location for all attributes required by a technique. * * @param {Object} technique A glTF KHR_techniques_webgl technique object. * @param {Object} precreatedAttributes A dictionary object of pre-created attributes for which to also create locations. * @returns {Object} A dictionary object containing attribute names and their locations. */ ModelUtility.createAttributeLocations = function ( technique, precreatedAttributes ) { var attributeLocations = {}; var hasIndex0 = false; var i = 1; ForEach.techniqueAttribute(technique, function (attribute, attributeName) { // Set the position attribute to the 0th index. In some WebGL implementations the shader // will not work correctly if the 0th attribute is not active. For example, some glTF models // list the normal attribute first but derived shaders like the cast-shadows shader do not use // the normal attribute. if (/pos/i.test(attributeName) && !hasIndex0) { attributeLocations[attributeName] = 0; hasIndex0 = true; } else { attributeLocations[attributeName] = i++; } }); if (defined(precreatedAttributes)) { for (var attributeName in precreatedAttributes) { if (precreatedAttributes.hasOwnProperty(attributeName)) { attributeLocations[attributeName] = i++; } } } return attributeLocations; }; ModelUtility.getAccessorMinMax = function (gltf, accessorId) { var accessor = gltf.accessors[accessorId]; var extensions = accessor.extensions; var accessorMin = accessor.min; var accessorMax = accessor.max; // If this accessor is quantized, we should use the decoded min and max if (defined(extensions)) { var quantizedAttributes = extensions.WEB3D_quantized_attributes; if (defined(quantizedAttributes)) { accessorMin = quantizedAttributes.decodedMin; accessorMax = quantizedAttributes.decodedMax; } } return { min: accessorMin, max: accessorMax, }; }; function getTechniqueAttributeOrUniformFunction( gltf, technique, semantic, ignoreNodes ) { if (hasExtension(gltf, "KHR_techniques_webgl")) { return function (attributeOrUniform, attributeOrUniformName) { if ( attributeOrUniform.semantic === semantic && (!ignoreNodes || !defined(attributeOrUniform.node)) ) { return attributeOrUniformName; } }; } return function (parameterName, attributeOrUniformName) { var attributeOrUniform = technique.parameters[parameterName]; if ( attributeOrUniform.semantic === semantic && (!ignoreNodes || !defined(attributeOrUniform.node)) ) { return attributeOrUniformName; } }; } ModelUtility.getAttributeOrUniformBySemantic = function ( gltf, semantic, programId, ignoreNodes ) { return ForEach.technique(gltf, function (technique) { if (defined(programId) && technique.program !== programId) { return; } var value = ForEach.techniqueAttribute( technique, getTechniqueAttributeOrUniformFunction( gltf, technique, semantic, ignoreNodes ) ); if (defined(value)) { return value; } return ForEach.techniqueUniform( technique, getTechniqueAttributeOrUniformFunction( gltf, technique, semantic, ignoreNodes ) ); }); }; ModelUtility.getDiffuseAttributeOrUniform = function (gltf, programId) { var diffuseUniformName = ModelUtility.getAttributeOrUniformBySemantic( gltf, "COLOR_0", programId ); if (!defined(diffuseUniformName)) { diffuseUniformName = ModelUtility.getAttributeOrUniformBySemantic( gltf, "_3DTILESDIFFUSE", programId ); } return diffuseUniformName; }; var nodeTranslationScratch = new Cartesian3(); var nodeQuaternionScratch = new Quaternion(); var nodeScaleScratch = new Cartesian3(); ModelUtility.getTransform = function (node, result) { if (defined(node.matrix)) { return Matrix4.fromColumnMajorArray(node.matrix, result); } return Matrix4.fromTranslationQuaternionRotationScale( Cartesian3.fromArray(node.translation, 0, nodeTranslationScratch), Quaternion.unpack(node.rotation, 0, nodeQuaternionScratch), Cartesian3.fromArray(node.scale, 0, nodeScaleScratch), result ); }; ModelUtility.getUsedExtensions = function (gltf) { var extensionsUsed = gltf.extensionsUsed; var cachedExtensionsUsed = {}; if (defined(extensionsUsed)) { var extensionsUsedLength = extensionsUsed.length; for (var i = 0; i < extensionsUsedLength; i++) { var extension = extensionsUsed[i]; cachedExtensionsUsed[extension] = true; } } return cachedExtensionsUsed; }; ModelUtility.getRequiredExtensions = function (gltf) { var extensionsRequired = gltf.extensionsRequired; var cachedExtensionsRequired = {}; if (defined(extensionsRequired)) { var extensionsRequiredLength = extensionsRequired.length; for (var i = 0; i < extensionsRequiredLength; i++) { var extension = extensionsRequired[i]; cachedExtensionsRequired[extension] = true; } } return cachedExtensionsRequired; }; ModelUtility.supportedExtensions = { AGI_articulations: true, CESIUM_RTC: true, EXT_texture_webp: true, KHR_blend: true, KHR_binary_glTF: true, KHR_draco_mesh_compression: true, KHR_materials_common: true, KHR_techniques_webgl: true, KHR_materials_unlit: true, KHR_materials_pbrSpecularGlossiness: true, KHR_texture_transform: true, WEB3D_quantized_attributes: true, }; ModelUtility.checkSupportedExtensions = function ( extensionsRequired, browserSupportsWebp ) { for (var extension in extensionsRequired) { if (extensionsRequired.hasOwnProperty(extension)) { if (!ModelUtility.supportedExtensions[extension]) { throw new RuntimeError("Unsupported glTF Extension: " + extension); } if (extension === "EXT_texture_webp" && browserSupportsWebp === false) { throw new RuntimeError( "Loaded model requires WebP but browser does not support it." ); } } } }; ModelUtility.checkSupportedGlExtensions = function (extensionsUsed, context) { if (defined(extensionsUsed)) { var glExtensionsUsedLength = extensionsUsed.length; for (var i = 0; i < glExtensionsUsedLength; i++) { var extension = extensionsUsed[i]; if (extension !== "OES_element_index_uint") { throw new RuntimeError("Unsupported WebGL Extension: " + extension); } else if (!context.elementIndexUint) { throw new RuntimeError( "OES_element_index_uint WebGL extension is not enabled." ); } } } }; function replaceAllButFirstInString(string, find, replace) { // Limit search to strings that are not a subset of other tokens. find += "(?!\\w)"; find = new RegExp(find, "g"); var index = string.search(find); return string.replace(find, function (match, offset) { return index === offset ? match : replace; }); } function getQuantizedAttributes(gltf, accessorId) { var accessor = gltf.accessors[accessorId]; var extensions = accessor.extensions; if (defined(extensions)) { return extensions.WEB3D_quantized_attributes; } return undefined; } function getAttributeVariableName(gltf, primitive, attributeSemantic) { var materialId = primitive.material; var material = gltf.materials[materialId]; if ( !hasExtension(gltf, "KHR_techniques_webgl") || !defined(material.extensions) || !defined(material.extensions.KHR_techniques_webgl) ) { return; } var techniqueId = material.extensions.KHR_techniques_webgl.technique; var techniquesWebgl = gltf.extensions.KHR_techniques_webgl; var technique = techniquesWebgl.techniques[techniqueId]; return ForEach.techniqueAttribute(technique, function ( attribute, attributeName ) { var semantic = attribute.semantic; if (semantic === attributeSemantic) { return attributeName; } }); } ModelUtility.modifyShaderForDracoQuantizedAttributes = function ( gltf, primitive, shader, decodedAttributes ) { var quantizedUniforms = {}; for (var attributeSemantic in decodedAttributes) { if (decodedAttributes.hasOwnProperty(attributeSemantic)) { var attribute = decodedAttributes[attributeSemantic]; var quantization = attribute.quantization; if (!defined(quantization)) { continue; } var attributeVarName = getAttributeVariableName( gltf, primitive, attributeSemantic ); if (attributeSemantic.charAt(0) === "_") { attributeSemantic = attributeSemantic.substring(1); } var decodeUniformVarName = "gltf_u_dec_" + attributeSemantic.toLowerCase(); if (!defined(quantizedUniforms[decodeUniformVarName])) { var newMain = "gltf_decoded_" + attributeSemantic; var decodedAttributeVarName = attributeVarName.replace( "a_", "gltf_a_dec_" ); var size = attribute.componentsPerAttribute; // replace usages of the original attribute with the decoded version, but not the declaration shader = replaceAllButFirstInString( shader, attributeVarName, decodedAttributeVarName ); // declare decoded attribute var variableType; if (quantization.octEncoded) { variableType = "vec3"; } else if (size > 1) { variableType = "vec" + size; } else { variableType = "float"; } shader = variableType + " " + decodedAttributeVarName + ";\n" + shader; // The gltf 2.0 COLOR_0 vertex attribute can be VEC4 or VEC3 var vec3Color = size === 3 && attributeSemantic === "COLOR_0"; if (vec3Color) { shader = replaceAllButFirstInString( shader, decodedAttributeVarName, "vec4(" + decodedAttributeVarName + ", 1.0)" ); } // splice decode function into the shader var decode = ""; if (quantization.octEncoded) { var decodeUniformVarNameRangeConstant = decodeUniformVarName + "_rangeConstant"; shader = "uniform float " + decodeUniformVarNameRangeConstant + ";\n" + shader; decode = "\n" + "void main() {\n" + // Draco oct-encoding decodes to zxy order " " + decodedAttributeVarName + " = czm_octDecode(" + attributeVarName + ".xy, " + decodeUniformVarNameRangeConstant + ").zxy;\n" + " " + newMain + "();\n" + "}\n"; } else { var decodeUniformVarNameNormConstant = decodeUniformVarName + "_normConstant"; var decodeUniformVarNameMin = decodeUniformVarName + "_min"; shader = "uniform float " + decodeUniformVarNameNormConstant + ";\n" + "uniform " + variableType + " " + decodeUniformVarNameMin + ";\n" + shader; var attributeVarAccess = vec3Color ? ".xyz" : ""; decode = "\n" + "void main() {\n" + " " + decodedAttributeVarName + " = " + decodeUniformVarNameMin + " + " + attributeVarName + attributeVarAccess + " * " + decodeUniformVarNameNormConstant + ";\n" + " " + newMain + "();\n" + "}\n"; } shader = ShaderSource.replaceMain(shader, newMain); shader += decode; } } } return { shader: shader, }; }; ModelUtility.modifyShaderForQuantizedAttributes = function ( gltf, primitive, shader ) { var quantizedUniforms = {}; var attributes = primitive.attributes; for (var attributeSemantic in attributes) { if (attributes.hasOwnProperty(attributeSemantic)) { var attributeVarName = getAttributeVariableName( gltf, primitive, attributeSemantic ); var accessorId = primitive.attributes[attributeSemantic]; if (attributeSemantic.charAt(0) === "_") { attributeSemantic = attributeSemantic.substring(1); } var decodeUniformVarName = "gltf_u_dec_" + attributeSemantic.toLowerCase(); var decodeUniformVarNameScale = decodeUniformVarName + "_scale"; var decodeUniformVarNameTranslate = decodeUniformVarName + "_translate"; if ( !defined(quantizedUniforms[decodeUniformVarName]) && !defined(quantizedUniforms[decodeUniformVarNameScale]) ) { var quantizedAttributes = getQuantizedAttributes(gltf, accessorId); if (defined(quantizedAttributes)) { var decodeMatrix = quantizedAttributes.decodeMatrix; var newMain = "gltf_decoded_" + attributeSemantic; var decodedAttributeVarName = attributeVarName.replace( "a_", "gltf_a_dec_" ); var size = Math.floor(Math.sqrt(decodeMatrix.length)); // replace usages of the original attribute with the decoded version, but not the declaration shader = replaceAllButFirstInString( shader, attributeVarName, decodedAttributeVarName ); // declare decoded attribute var variableType; if (size > 2) { variableType = "vec" + (size - 1); } else { variableType = "float"; } shader = variableType + " " + decodedAttributeVarName + ";\n" + shader; // splice decode function into the shader - attributes are pre-multiplied with the decode matrix // uniform in the shader (32-bit floating point) var decode = ""; if (size === 5) { // separate scale and translate since glsl doesn't have mat5 shader = "uniform mat4 " + decodeUniformVarNameScale + ";\n" + shader; shader = "uniform vec4 " + decodeUniformVarNameTranslate + ";\n" + shader; decode = "\n" + "void main() {\n" + " " + decodedAttributeVarName + " = " + decodeUniformVarNameScale + " * " + attributeVarName + " + " + decodeUniformVarNameTranslate + ";\n" + " " + newMain + "();\n" + "}\n"; quantizedUniforms[decodeUniformVarNameScale] = { mat: 4 }; quantizedUniforms[decodeUniformVarNameTranslate] = { vec: 4 }; } else { shader = "uniform mat" + size + " " + decodeUniformVarName + ";\n" + shader; decode = "\n" + "void main() {\n" + " " + decodedAttributeVarName + " = " + variableType + "(" + decodeUniformVarName + " * vec" + size + "(" + attributeVarName + ",1.0));\n" + " " + newMain + "();\n" + "}\n"; quantizedUniforms[decodeUniformVarName] = { mat: size }; } shader = ShaderSource.replaceMain(shader, newMain); shader += decode; } } } } return { shader: shader, uniforms: quantizedUniforms, }; }; function getScalarUniformFunction(value) { var that = { value: value, clone: function (source, result) { return source; }, func: function () { return that.value; }, }; return that; } function getVec2UniformFunction(value) { var that = { value: Cartesian2.fromArray(value), clone: Cartesian2.clone, func: function () { return that.value; }, }; return that; } function getVec3UniformFunction(value) { var that = { value: Cartesian3.fromArray(value), clone: Cartesian3.clone, func: function () { return that.value; }, }; return that; } function getVec4UniformFunction(value) { var that = { value: Cartesian4.fromArray(value), clone: Cartesian4.clone, func: function () { return that.value; }, }; return that; } function getMat2UniformFunction(value) { var that = { value: Matrix2.fromColumnMajorArray(value), clone: Matrix2.clone, func: function () { return that.value; }, }; return that; } function getMat3UniformFunction(value) { var that = { value: Matrix3.fromColumnMajorArray(value), clone: Matrix3.clone, func: function () { return that.value; }, }; return that; } function getMat4UniformFunction(value) { var that = { value: Matrix4.fromColumnMajorArray(value), clone: Matrix4.clone, func: function () { return that.value; }, }; return that; } /////////////////////////////////////////////////////////////////////////// function DelayLoadedTextureUniform(value, textures, defaultTexture) { this._value = undefined; this._textureId = value.index; this._textures = textures; this._defaultTexture = defaultTexture; } Object.defineProperties(DelayLoadedTextureUniform.prototype, { value: { get: function () { // Use the default texture (1x1 white) until the model's texture is loaded if (!defined(this._value)) { var texture = this._textures[this._textureId]; if (defined(texture)) { this._value = texture; } else { return this._defaultTexture; } } return this._value; }, set: function (value) { this._value = value; }, }, }); DelayLoadedTextureUniform.prototype.clone = function (source) { return source; }; DelayLoadedTextureUniform.prototype.func = undefined; /////////////////////////////////////////////////////////////////////////// function getTextureUniformFunction(value, textures, defaultTexture) { var uniform = new DelayLoadedTextureUniform(value, textures, defaultTexture); // Define function here to access closure since 'this' can't be // used when the Renderer sets uniforms. uniform.func = function () { return uniform.value; }; return uniform; } var gltfUniformFunctions = {}; gltfUniformFunctions[WebGLConstants.FLOAT] = getScalarUniformFunction; gltfUniformFunctions[WebGLConstants.FLOAT_VEC2] = getVec2UniformFunction; gltfUniformFunctions[WebGLConstants.FLOAT_VEC3] = getVec3UniformFunction; gltfUniformFunctions[WebGLConstants.FLOAT_VEC4] = getVec4UniformFunction; gltfUniformFunctions[WebGLConstants.INT] = getScalarUniformFunction; gltfUniformFunctions[WebGLConstants.INT_VEC2] = getVec2UniformFunction; gltfUniformFunctions[WebGLConstants.INT_VEC3] = getVec3UniformFunction; gltfUniformFunctions[WebGLConstants.INT_VEC4] = getVec4UniformFunction; gltfUniformFunctions[WebGLConstants.BOOL] = getScalarUniformFunction; gltfUniformFunctions[WebGLConstants.BOOL_VEC2] = getVec2UniformFunction; gltfUniformFunctions[WebGLConstants.BOOL_VEC3] = getVec3UniformFunction; gltfUniformFunctions[WebGLConstants.BOOL_VEC4] = getVec4UniformFunction; gltfUniformFunctions[WebGLConstants.FLOAT_MAT2] = getMat2UniformFunction; gltfUniformFunctions[WebGLConstants.FLOAT_MAT3] = getMat3UniformFunction; gltfUniformFunctions[WebGLConstants.FLOAT_MAT4] = getMat4UniformFunction; gltfUniformFunctions[WebGLConstants.SAMPLER_2D] = getTextureUniformFunction; // GLTF_SPEC: Support SAMPLER_CUBE. https://github.com/KhronosGroup/glTF/issues/40 ModelUtility.createUniformFunction = function ( type, value, textures, defaultTexture ) { return gltfUniformFunctions[type](value, textures, defaultTexture); }; function scaleFromMatrix5Array(matrix) { return [ matrix[0], matrix[1], matrix[2], matrix[3], matrix[5], matrix[6], matrix[7], matrix[8], matrix[10], matrix[11], matrix[12], matrix[13], matrix[15], matrix[16], matrix[17], matrix[18], ]; } function translateFromMatrix5Array(matrix) { return [matrix[20], matrix[21], matrix[22], matrix[23]]; } ModelUtility.createUniformsForDracoQuantizedAttributes = function ( decodedAttributes ) { var uniformMap = {}; for (var attribute in decodedAttributes) { if (decodedAttributes.hasOwnProperty(attribute)) { var decodedData = decodedAttributes[attribute]; var quantization = decodedData.quantization; if (!defined(quantization)) { continue; } if (attribute.charAt(0) === "_") { attribute = attribute.substring(1); } var uniformVarName = "gltf_u_dec_" + attribute.toLowerCase(); if (quantization.octEncoded) { var uniformVarNameRangeConstant = uniformVarName + "_rangeConstant"; var rangeConstant = (1 << quantization.quantizationBits) - 1.0; uniformMap[uniformVarNameRangeConstant] = getScalarUniformFunction( rangeConstant ).func; continue; } var uniformVarNameNormConstant = uniformVarName + "_normConstant"; var normConstant = quantization.range / (1 << quantization.quantizationBits); uniformMap[uniformVarNameNormConstant] = getScalarUniformFunction( normConstant ).func; var uniformVarNameMin = uniformVarName + "_min"; switch (decodedData.componentsPerAttribute) { case 1: uniformMap[uniformVarNameMin] = getScalarUniformFunction( quantization.minValues ).func; break; case 2: uniformMap[uniformVarNameMin] = getVec2UniformFunction( quantization.minValues ).func; break; case 3: uniformMap[uniformVarNameMin] = getVec3UniformFunction( quantization.minValues ).func; break; case 4: uniformMap[uniformVarNameMin] = getVec4UniformFunction( quantization.minValues ).func; break; } } } return uniformMap; }; ModelUtility.createUniformsForQuantizedAttributes = function ( gltf, primitive, quantizedUniforms ) { var accessors = gltf.accessors; var setUniforms = {}; var uniformMap = {}; var attributes = primitive.attributes; for (var attribute in attributes) { if (attributes.hasOwnProperty(attribute)) { var accessorId = attributes[attribute]; var a = accessors[accessorId]; var extensions = a.extensions; if (attribute.charAt(0) === "_") { attribute = attribute.substring(1); } if (defined(extensions)) { var quantizedAttributes = extensions.WEB3D_quantized_attributes; if (defined(quantizedAttributes)) { var decodeMatrix = quantizedAttributes.decodeMatrix; var uniformVariable = "gltf_u_dec_" + attribute.toLowerCase(); switch (a.type) { case AttributeType.SCALAR: uniformMap[uniformVariable] = getMat2UniformFunction( decodeMatrix ).func; setUniforms[uniformVariable] = true; break; case AttributeType.VEC2: uniformMap[uniformVariable] = getMat3UniformFunction( decodeMatrix ).func; setUniforms[uniformVariable] = true; break; case AttributeType.VEC3: uniformMap[uniformVariable] = getMat4UniformFunction( decodeMatrix ).func; setUniforms[uniformVariable] = true; break; case AttributeType.VEC4: // VEC4 attributes are split into scale and translate because there is no mat5 in GLSL var uniformVariableScale = uniformVariable + "_scale"; var uniformVariableTranslate = uniformVariable + "_translate"; uniformMap[uniformVariableScale] = getMat4UniformFunction( scaleFromMatrix5Array(decodeMatrix) ).func; uniformMap[uniformVariableTranslate] = getVec4UniformFunction( translateFromMatrix5Array(decodeMatrix) ).func; setUniforms[uniformVariableScale] = true; setUniforms[uniformVariableTranslate] = true; break; } } } } } // If there are any unset quantized uniforms in this program, they should be set to the identity for (var quantizedUniform in quantizedUniforms) { if (quantizedUniforms.hasOwnProperty(quantizedUniform)) { if (!setUniforms[quantizedUniform]) { var properties = quantizedUniforms[quantizedUniform]; if (defined(properties.mat)) { if (properties.mat === 2) { uniformMap[quantizedUniform] = getMat2UniformFunction( Matrix2.IDENTITY ).func; } else if (properties.mat === 3) { uniformMap[quantizedUniform] = getMat3UniformFunction( Matrix3.IDENTITY ).func; } else if (properties.mat === 4) { uniformMap[quantizedUniform] = getMat4UniformFunction( Matrix4.IDENTITY ).func; } } if (defined(properties.vec)) { if (properties.vec === 4) { uniformMap[quantizedUniform] = getVec4UniformFunction([ 0, 0, 0, 0, ]).func; } } } } } return uniformMap; }; // This doesn't support LOCAL, which we could add if it is ever used. var scratchTranslationRtc = new Cartesian3(); var gltfSemanticUniforms = { MODEL: function (uniformState, model) { return function () { return uniformState.model; }; }, VIEW: function (uniformState, model) { return function () { return uniformState.view; }; }, PROJECTION: function (uniformState, model) { return function () { return uniformState.projection; }; }, MODELVIEW: function (uniformState, model) { return function () { return uniformState.modelView; }; }, CESIUM_RTC_MODELVIEW: function (uniformState, model) { // CESIUM_RTC extension var mvRtc = new Matrix4(); return function () { if (defined(model._rtcCenter)) { Matrix4.getTranslation(uniformState.model, scratchTranslationRtc); Cartesian3.add( scratchTranslationRtc, model._rtcCenter, scratchTranslationRtc ); Matrix4.multiplyByPoint( uniformState.view, scratchTranslationRtc, scratchTranslationRtc ); return Matrix4.setTranslation( uniformState.modelView, scratchTranslationRtc, mvRtc ); } return uniformState.modelView; }; }, MODELVIEWPROJECTION: function (uniformState, model) { return function () { return uniformState.modelViewProjection; }; }, MODELINVERSE: function (uniformState, model) { return function () { return uniformState.inverseModel; }; }, VIEWINVERSE: function (uniformState, model) { return function () { return uniformState.inverseView; }; }, PROJECTIONINVERSE: function (uniformState, model) { return function () { return uniformState.inverseProjection; }; }, MODELVIEWINVERSE: function (uniformState, model) { return function () { return uniformState.inverseModelView; }; }, MODELVIEWPROJECTIONINVERSE: function (uniformState, model) { return function () { return uniformState.inverseModelViewProjection; }; }, MODELINVERSETRANSPOSE: function (uniformState, model) { return function () { return uniformState.inverseTransposeModel; }; }, MODELVIEWINVERSETRANSPOSE: function (uniformState, model) { return function () { return uniformState.normal; }; }, VIEWPORT: function (uniformState, model) { return function () { return uniformState.viewportCartesian4; }; }, // JOINTMATRIX created in createCommand() }; ModelUtility.getGltfSemanticUniforms = function () { return gltfSemanticUniforms; }; export default ModelUtility;