cesium
Version:
CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.
1,114 lines (996 loc) • 48.3 kB
JavaScript
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 type;
if (defined(jointAccessorId)) {
var jointAccessor = accessors[jointAccessorId];
componentType = jointAccessor.componentType;
type = jointAccessor.type;
}
var isSkinned = defined(jointAccessorId);
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 primitiveInfo = primitiveInfoByMaterial[materialIndex];
if (!defined(primitiveInfo)) {
primitiveInfoByMaterial[materialIndex] = {
skinning: {
skinned: isSkinned,
componentType: componentType,
type: type
},
hasVertexColors: hasVertexColors,
hasMorphTargets: hasMorphTargets,
hasNormals: hasNormals,
hasTangents: hasTangents,
hasTexCoords: hasTexCoords
};
} else if ((primitiveInfo.skinning.skinned !== isSkinned) ||
(primitiveInfo.skinning.type !== type) ||
(primitiveInfo.hasVertexColors !== hasVertexColors) ||
(primitiveInfo.hasMorphTargets !== hasMorphTargets) ||
(primitiveInfo.hasNormals !== hasNormals) ||
(primitiveInfo.hasTangents !== hasTangents) ||
(primitiveInfo.hasTexCoords !== hasTexCoords)) {
// 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
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,
type: type
},
hasVertexColors: hasVertexColors,
hasMorphTargets: hasMorphTargets,
hasNormals: hasNormals,
hasTangents: hasTangents,
hasTexCoords: hasTexCoords
};
}
});
});
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);
var aMin = Cartesian3.fromArray(minMax.min, 0, aMinScratch);
var aMax = Cartesian3.fromArray(minMax.max, 0, aMaxScratch);
if (defined(min) && defined(max)) {
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
};
};
ModelUtility.toClipCoordinatesGLSL = function(gltf, shader) {
var positionName = ModelUtility.getAttributeOrUniformBySemantic(gltf, 'POSITION');
var decodedPositionName = positionName.replace('a_', 'gltf_a_dec_');
if (shader.indexOf(decodedPositionName) !== -1) {
positionName = decodedPositionName;
}
var modelViewProjectionName = ModelUtility.getAttributeOrUniformBySemantic(gltf, 'MODELVIEWPROJECTION', undefined, true);
if (!defined(modelViewProjectionName) || shader.indexOf(modelViewProjectionName) === -1) {
var projectionName = ModelUtility.getAttributeOrUniformBySemantic(gltf, 'PROJECTION', undefined, true);
var modelViewName = ModelUtility.getAttributeOrUniformBySemantic(gltf, 'MODELVIEW', undefined, true);
if (shader.indexOf('czm_instanced_modelView ') !== -1) {
modelViewName = 'czm_instanced_modelView';
} else if (!defined(modelViewName)) {
modelViewName = ModelUtility.getAttributeOrUniformBySemantic(gltf, 'CESIUM_RTC_MODELVIEW', undefined, true);
}
modelViewProjectionName = projectionName + ' * ' + modelViewName;
}
return modelViewProjectionName + ' * vec4(' + positionName + '.xyz, 1.0)';
};
ModelUtility.modifyFragmentShaderForLogDepth = function(shader) {
shader = ShaderSource.replaceMain(shader, 'czm_depth_main');
shader +=
'\n' +
'void main() \n' +
'{ \n' +
' czm_depth_main(); \n' +
' czm_writeLogDepth(); \n' +
'} \n';
return shader;
};
ModelUtility.modifyVertexShaderForLogDepth = function(shader, toClipCoordinatesGLSL) {
shader = ShaderSource.replaceMain(shader, 'czm_depth_main');
shader +=
'\n' +
'void main() \n' +
'{ \n' +
' czm_depth_main(); \n' +
' czm_vertexLogDepth(' + toClipCoordinatesGLSL + '); \n' +
'} \n';
return shader;
};
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;