import arrayFill from '../Core/arrayFill.js'; import Cartesian2 from '../Core/Cartesian2.js'; import Cartesian4 from '../Core/Cartesian4.js'; import Check from '../Core/Check.js'; import clone from '../Core/clone.js'; import Color from '../Core/Color.js'; import combine from '../Core/combine.js'; import ComponentDatatype from '../Core/ComponentDatatype.js'; import defaultValue from '../Core/defaultValue.js'; import defined from '../Core/defined.js'; import defineProperties from '../Core/defineProperties.js'; import deprecationWarning from '../Core/deprecationWarning.js'; import destroyObject from '../Core/destroyObject.js'; import DeveloperError from '../Core/DeveloperError.js'; import CesiumMath from '../Core/Math.js'; import PixelFormat from '../Core/PixelFormat.js'; import RuntimeError from '../Core/RuntimeError.js'; import ContextLimits from '../Renderer/ContextLimits.js'; import DrawCommand from '../Renderer/DrawCommand.js'; import Pass from '../Renderer/Pass.js'; import PixelDatatype from '../Renderer/PixelDatatype.js'; import RenderState from '../Renderer/RenderState.js'; import Sampler from '../Renderer/Sampler.js'; import ShaderSource from '../Renderer/ShaderSource.js'; import Texture from '../Renderer/Texture.js'; import TextureMagnificationFilter from '../Renderer/TextureMagnificationFilter.js'; import TextureMinificationFilter from '../Renderer/TextureMinificationFilter.js'; import AttributeType from './AttributeType.js'; import BlendingState from './BlendingState.js'; import Cesium3DTileColorBlendMode from './Cesium3DTileColorBlendMode.js'; import CullFace from './CullFace.js'; import getBinaryAccessor from './getBinaryAccessor.js'; import StencilConstants from './StencilConstants.js'; import StencilFunction from './StencilFunction.js'; import StencilOperation from './StencilOperation.js'; var DEFAULT_COLOR_VALUE = Color.WHITE; var DEFAULT_SHOW_VALUE = true; /** * @private */ function Cesium3DTileBatchTable(content, featuresLength, batchTableJson, batchTableBinary, colorChangedCallback) { /** * @readonly */ this.featuresLength = featuresLength; this._translucentFeaturesLength = 0; // Number of features in the tile that are translucent var extensions; if (defined(batchTableJson)) { extensions = batchTableJson.extensions; } this._extensions = defaultValue(extensions, {}); var properties = initializeProperties(batchTableJson); this._properties = properties; this._batchTableHierarchy = initializeHierarchy(this, batchTableJson, batchTableBinary); this._batchTableBinaryProperties = getBinaryProperties(featuresLength, properties, batchTableBinary); // PERFORMANCE_IDEA: These parallel arrays probably generate cache misses in get/set color/show // and use A LOT of memory. How can we use less memory? this._showAlphaProperties = undefined; // [Show (0 or 255), Alpha (0 to 255)] property for each feature this._batchValues = undefined; // Per-feature RGBA (A is based on the color's alpha and feature's show property) this._batchValuesDirty = false; this._batchTexture = undefined; this._defaultTexture = undefined; this._pickTexture = undefined; this._pickIds = []; this._content = content; this._colorChangedCallback = colorChangedCallback; // Dimensions for batch and pick textures var textureDimensions; var textureStep; if (featuresLength > 0) { // PERFORMANCE_IDEA: this can waste memory in the last row in the uncommon case // when more than one row is needed (e.g., > 16K features in one tile) var width = Math.min(featuresLength, ContextLimits.maximumTextureSize); var height = Math.ceil(featuresLength / ContextLimits.maximumTextureSize); var stepX = 1.0 / width; var centerX = stepX * 0.5; var stepY = 1.0 / height; var centerY = stepY * 0.5; textureDimensions = new Cartesian2(width, height); textureStep = new Cartesian4(stepX, centerX, stepY, centerY); } this._textureDimensions = textureDimensions; this._textureStep = textureStep; } // This can be overridden for testing purposes Cesium3DTileBatchTable._deprecationWarning = deprecationWarning; defineProperties(Cesium3DTileBatchTable.prototype, { memorySizeInBytes : { get : function() { var memory = 0; if (defined(this._pickTexture)) { memory += this._pickTexture.sizeInBytes; } if (defined(this._batchTexture)) { memory += this._batchTexture.sizeInBytes; } return memory; } } }); function initializeProperties(jsonHeader) { var properties = {}; if (!defined(jsonHeader)) { return properties; } for (var propertyName in jsonHeader) { if (jsonHeader.hasOwnProperty(propertyName) && propertyName !== 'HIERARCHY' // Deprecated HIERARCHY property && propertyName !== 'extensions' && propertyName !== 'extras') { properties[propertyName] = clone(jsonHeader[propertyName], true); } } return properties; } function initializeHierarchy(batchTable, jsonHeader, binaryBody) { if (!defined(jsonHeader)) { return; } var hierarchy = batchTable._extensions['3DTILES_batch_table_hierarchy']; var legacyHierarchy = jsonHeader.HIERARCHY; if (defined(legacyHierarchy)) { Cesium3DTileBatchTable._deprecationWarning('batchTableHierarchyExtension', 'The batch table HIERARCHY property has been moved to an extension. Use extensions.3DTILES_batch_table_hierarchy instead.'); batchTable._extensions['3DTILES_batch_table_hierarchy'] = legacyHierarchy; hierarchy = legacyHierarchy; } if (!defined(hierarchy)) { return; } return initializeHierarchyValues(hierarchy, binaryBody); } function initializeHierarchyValues(hierarchyJson, binaryBody) { var i; var classId; var binaryAccessor; var instancesLength = hierarchyJson.instancesLength; var classes = hierarchyJson.classes; var classIds = hierarchyJson.classIds; var parentCounts = hierarchyJson.parentCounts; var parentIds = hierarchyJson.parentIds; var parentIdsLength = instancesLength; if (defined(classIds.byteOffset)) { classIds.componentType = defaultValue(classIds.componentType, ComponentDatatype.UNSIGNED_SHORT); classIds.type = AttributeType.SCALAR; binaryAccessor = getBinaryAccessor(classIds); classIds = binaryAccessor.createArrayBufferView(binaryBody.buffer, binaryBody.byteOffset + classIds.byteOffset, instancesLength); } var parentIndexes; if (defined(parentCounts)) { if (defined(parentCounts.byteOffset)) { parentCounts.componentType = defaultValue(parentCounts.componentType, ComponentDatatype.UNSIGNED_SHORT); parentCounts.type = AttributeType.SCALAR; binaryAccessor = getBinaryAccessor(parentCounts); parentCounts = binaryAccessor.createArrayBufferView(binaryBody.buffer, binaryBody.byteOffset + parentCounts.byteOffset, instancesLength); } parentIndexes = new Uint16Array(instancesLength); parentIdsLength = 0; for (i = 0; i < instancesLength; ++i) { parentIndexes[i] = parentIdsLength; parentIdsLength += parentCounts[i]; } } if (defined(parentIds) && defined(parentIds.byteOffset)) { parentIds.componentType = defaultValue(parentIds.componentType, ComponentDatatype.UNSIGNED_SHORT); parentIds.type = AttributeType.SCALAR; binaryAccessor = getBinaryAccessor(parentIds); parentIds = binaryAccessor.createArrayBufferView(binaryBody.buffer, binaryBody.byteOffset + parentIds.byteOffset, parentIdsLength); } var classesLength = classes.length; for (i = 0; i < classesLength; ++i) { var classInstancesLength = classes[i].length; var properties = classes[i].instances; var binaryProperties = getBinaryProperties(classInstancesLength, properties, binaryBody); classes[i].instances = combine(binaryProperties, properties); } var classCounts = arrayFill(new Array(classesLength), 0); var classIndexes = new Uint16Array(instancesLength); for (i = 0; i < instancesLength; ++i) { classId = classIds[i]; classIndexes[i] = classCounts[classId]; ++classCounts[classId]; } var hierarchy = { classes : classes, classIds : classIds, classIndexes : classIndexes, parentCounts : parentCounts, parentIndexes : parentIndexes, parentIds : parentIds }; //>>includeStart('debug', pragmas.debug); validateHierarchy(hierarchy); //>>includeEnd('debug'); return hierarchy; } //>>includeStart('debug', pragmas.debug); var scratchValidateStack = []; function validateHierarchy(hierarchy) { var stack = scratchValidateStack; stack.length = 0; var classIds = hierarchy.classIds; var instancesLength = classIds.length; for (var i = 0; i < instancesLength; ++i) { validateInstance(hierarchy, i, stack); } } function validateInstance(hierarchy, instanceIndex, stack) { var parentCounts = hierarchy.parentCounts; var parentIds = hierarchy.parentIds; var parentIndexes = hierarchy.parentIndexes; var classIds = hierarchy.classIds; var instancesLength = classIds.length; if (!defined(parentIds)) { // No need to validate if there are no parents return; } if (instanceIndex >= instancesLength) { throw new DeveloperError('Parent index ' + instanceIndex + ' exceeds the total number of instances: ' + instancesLength); } if (stack.indexOf(instanceIndex) > -1) { throw new DeveloperError('Circular dependency detected in the batch table hierarchy.'); } stack.push(instanceIndex); var parentCount = defined(parentCounts) ? parentCounts[instanceIndex] : 1; var parentIndex = defined(parentCounts) ? parentIndexes[instanceIndex] : instanceIndex; for (var i = 0; i < parentCount; ++i) { var parentId = parentIds[parentIndex + i]; // Stop the traversal when the instance has no parent (its parentId equals itself), else continue the traversal. if (parentId !== instanceIndex) { validateInstance(hierarchy, parentId, stack); } } stack.pop(instanceIndex); } //>>includeEnd('debug'); function getBinaryProperties(featuresLength, properties, binaryBody) { var binaryProperties; for (var name in properties) { if (properties.hasOwnProperty(name)) { var property = properties[name]; var byteOffset = property.byteOffset; if (defined(byteOffset)) { // This is a binary property var componentType = property.componentType; var type = property.type; if (!defined(componentType)) { throw new RuntimeError('componentType is required.'); } if (!defined(type)) { throw new RuntimeError('type is required.'); } if (!defined(binaryBody)) { throw new RuntimeError('Property ' + name + ' requires a batch table binary.'); } var binaryAccessor = getBinaryAccessor(property); var componentCount = binaryAccessor.componentsPerAttribute; var classType = binaryAccessor.classType; var typedArray = binaryAccessor.createArrayBufferView(binaryBody.buffer, binaryBody.byteOffset + byteOffset, featuresLength); if (!defined(binaryProperties)) { binaryProperties = {}; } // Store any information needed to access the binary data, including the typed array, // componentCount (e.g. a VEC4 would be 4), and the type used to pack and unpack (e.g. Cartesian4). binaryProperties[name] = { typedArray : typedArray, componentCount : componentCount, type : classType }; } } } return binaryProperties; } Cesium3DTileBatchTable.getBinaryProperties = function(featuresLength, batchTableJson, batchTableBinary) { return getBinaryProperties(featuresLength, batchTableJson, batchTableBinary); }; function getByteLength(batchTable) { var dimensions = batchTable._textureDimensions; return (dimensions.x * dimensions.y) * 4; } function getBatchValues(batchTable) { if (!defined(batchTable._batchValues)) { // Default batch texture to RGBA = 255: white highlight (RGB) and show/alpha = true/255 (A). var byteLength = getByteLength(batchTable); var bytes = new Uint8Array(byteLength); arrayFill(bytes, 255); batchTable._batchValues = bytes; } return batchTable._batchValues; } function getShowAlphaProperties(batchTable) { if (!defined(batchTable._showAlphaProperties)) { var byteLength = 2 * batchTable.featuresLength; var bytes = new Uint8Array(byteLength); // [Show = true, Alpha = 255] arrayFill(bytes, 255); batchTable._showAlphaProperties = bytes; } return batchTable._showAlphaProperties; } function checkBatchId(batchId, featuresLength) { if (!defined(batchId) || (batchId < 0) || (batchId > featuresLength)) { throw new DeveloperError('batchId is required and between zero and featuresLength - 1 (' + featuresLength - + ').'); } } Cesium3DTileBatchTable.prototype.setShow = function(batchId, show) { //>>includeStart('debug', pragmas.debug); checkBatchId(batchId, this.featuresLength); Check.typeOf.bool('show', show); //>>includeEnd('debug'); if (show && !defined(this._showAlphaProperties)) { // Avoid allocating since the default is show = true return; } var showAlphaProperties = getShowAlphaProperties(this); var propertyOffset = batchId * 2; var newShow = show ? 255 : 0; if (showAlphaProperties[propertyOffset] !== newShow) { showAlphaProperties[propertyOffset] = newShow; var batchValues = getBatchValues(this); // Compute alpha used in the shader based on show and color.alpha properties var offset = (batchId * 4) + 3; batchValues[offset] = show ? showAlphaProperties[propertyOffset + 1] : 0; this._batchValuesDirty = true; } }; Cesium3DTileBatchTable.prototype.setAllShow = function(show) { //>>includeStart('debug', pragmas.debug); Check.typeOf.bool('show', show); //>>includeEnd('debug'); var featuresLength = this.featuresLength; for (var i = 0; i < featuresLength; ++i) { this.setShow(i, show); } }; Cesium3DTileBatchTable.prototype.getShow = function(batchId) { //>>includeStart('debug', pragmas.debug); checkBatchId(batchId, this.featuresLength); //>>includeEnd('debug'); if (!defined(this._showAlphaProperties)) { // Avoid allocating since the default is show = true return true; } var offset = batchId * 2; return (this._showAlphaProperties[offset] === 255); }; var scratchColorBytes = new Array(4); Cesium3DTileBatchTable.prototype.setColor = function(batchId, color) { //>>includeStart('debug', pragmas.debug); checkBatchId(batchId, this.featuresLength); Check.typeOf.object('color', color); //>>includeEnd('debug'); if (Color.equals(color, DEFAULT_COLOR_VALUE) && !defined(this._batchValues)) { // Avoid allocating since the default is white return; } var newColor = color.toBytes(scratchColorBytes); var newAlpha = newColor[3]; var batchValues = getBatchValues(this); var offset = batchId * 4; var showAlphaProperties = getShowAlphaProperties(this); var propertyOffset = batchId * 2; if ((batchValues[offset] !== newColor[0]) || (batchValues[offset + 1] !== newColor[1]) || (batchValues[offset + 2] !== newColor[2]) || (showAlphaProperties[propertyOffset + 1] !== newAlpha)) { batchValues[offset] = newColor[0]; batchValues[offset + 1] = newColor[1]; batchValues[offset + 2] = newColor[2]; var wasTranslucent = (showAlphaProperties[propertyOffset + 1] !== 255); // Compute alpha used in the shader based on show and color.alpha properties var show = showAlphaProperties[propertyOffset] !== 0; batchValues[offset + 3] = show ? newAlpha : 0; showAlphaProperties[propertyOffset + 1] = newAlpha; // Track number of translucent features so we know if this tile needs // opaque commands, translucent commands, or both for rendering. var isTranslucent = (newAlpha !== 255); if (isTranslucent && !wasTranslucent) { ++this._translucentFeaturesLength; } else if (!isTranslucent && wasTranslucent) { --this._translucentFeaturesLength; } this._batchValuesDirty = true; if (defined(this._colorChangedCallback)) { this._colorChangedCallback(batchId, color); } } }; Cesium3DTileBatchTable.prototype.setAllColor = function(color) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object('color', color); //>>includeEnd('debug'); var featuresLength = this.featuresLength; for (var i = 0; i < featuresLength; ++i) { this.setColor(i, color); } }; Cesium3DTileBatchTable.prototype.getColor = function(batchId, result) { //>>includeStart('debug', pragmas.debug); checkBatchId(batchId, this.featuresLength); Check.typeOf.object('result', result); //>>includeEnd('debug'); if (!defined(this._batchValues)) { return Color.clone(DEFAULT_COLOR_VALUE, result); } var batchValues = this._batchValues; var offset = batchId * 4; var showAlphaProperties = this._showAlphaProperties; var propertyOffset = batchId * 2; return Color.fromBytes(batchValues[offset], batchValues[offset + 1], batchValues[offset + 2], showAlphaProperties[propertyOffset + 1], result); }; Cesium3DTileBatchTable.prototype.getPickColor = function(batchId) { //>>includeStart('debug', pragmas.debug); checkBatchId(batchId, this.featuresLength); //>>includeEnd('debug'); return this._pickIds[batchId]; }; var scratchColor = new Color(); Cesium3DTileBatchTable.prototype.applyStyle = function(style) { if (!defined(style)) { this.setAllColor(DEFAULT_COLOR_VALUE); this.setAllShow(DEFAULT_SHOW_VALUE); return; } var content = this._content; var length = this.featuresLength; for (var i = 0; i < length; ++i) { var feature = content.getFeature(i); var color = defined(style.color) ? style.color.evaluateColor(feature, scratchColor) : DEFAULT_COLOR_VALUE; var show = defined(style.show) ? style.show.evaluate(feature) : DEFAULT_SHOW_VALUE; this.setColor(i, color); this.setShow(i, show); } }; function getBinaryProperty(binaryProperty, index) { var typedArray = binaryProperty.typedArray; var componentCount = binaryProperty.componentCount; if (componentCount === 1) { return typedArray[index]; } return binaryProperty.type.unpack(typedArray, index * componentCount); } function setBinaryProperty(binaryProperty, index, value) { var typedArray = binaryProperty.typedArray; var componentCount = binaryProperty.componentCount; if (componentCount === 1) { typedArray[index] = value; } else { binaryProperty.type.pack(value, typedArray, index * componentCount); } } // The size of this array equals the maximum instance count among all loaded tiles, which has the potential to be large. var scratchVisited = []; var scratchStack = []; var marker = 0; function traverseHierarchyMultipleParents(hierarchy, instanceIndex, endConditionCallback) { var classIds = hierarchy.classIds; var parentCounts = hierarchy.parentCounts; var parentIds = hierarchy.parentIds; var parentIndexes = hierarchy.parentIndexes; var instancesLength = classIds.length; // Ignore instances that have already been visited. This occurs in diamond inheritance situations. // Use a marker value to indicate that an instance has been visited, which increments with each run. // This is more efficient than clearing the visited array every time. var visited = scratchVisited; visited.length = Math.max(visited.length, instancesLength); var visitedMarker = ++marker; var stack = scratchStack; stack.length = 0; stack.push(instanceIndex); while (stack.length > 0) { instanceIndex = stack.pop(); if (visited[instanceIndex] === visitedMarker) { // This instance has already been visited, stop traversal continue; } visited[instanceIndex] = visitedMarker; var result = endConditionCallback(hierarchy, instanceIndex); if (defined(result)) { // The end condition was met, stop the traversal and return the result return result; } var parentCount = parentCounts[instanceIndex]; var parentIndex = parentIndexes[instanceIndex]; for (var i = 0; i < parentCount; ++i) { var parentId = parentIds[parentIndex + i]; // Stop the traversal when the instance has no parent (its parentId equals itself) // else add the parent to the stack to continue the traversal. if (parentId !== instanceIndex) { stack.push(parentId); } } } } function traverseHierarchySingleParent(hierarchy, instanceIndex, endConditionCallback) { var hasParent = true; while (hasParent) { var result = endConditionCallback(hierarchy, instanceIndex); if (defined(result)) { // The end condition was met, stop the traversal and return the result return result; } var parentId = hierarchy.parentIds[instanceIndex]; hasParent = parentId !== instanceIndex; instanceIndex = parentId; } } function traverseHierarchy(hierarchy, instanceIndex, endConditionCallback) { // Traverse over the hierarchy and process each instance with the endConditionCallback. // When the endConditionCallback returns a value, the traversal stops and that value is returned. var parentCounts = hierarchy.parentCounts; var parentIds = hierarchy.parentIds; if (!defined(parentIds)) { return endConditionCallback(hierarchy, instanceIndex); } else if (defined(parentCounts)) { return traverseHierarchyMultipleParents(hierarchy, instanceIndex, endConditionCallback); } return traverseHierarchySingleParent(hierarchy, instanceIndex, endConditionCallback); } function hasPropertyInHierarchy(batchTable, batchId, name) { var hierarchy = batchTable._batchTableHierarchy; var result = traverseHierarchy(hierarchy, batchId, function(hierarchy, instanceIndex) { var classId = hierarchy.classIds[instanceIndex]; var instances = hierarchy.classes[classId].instances; if (defined(instances[name])) { return true; } }); return defined(result); } function getPropertyNamesInHierarchy(batchTable, batchId, results) { var hierarchy = batchTable._batchTableHierarchy; traverseHierarchy(hierarchy, batchId, function(hierarchy, instanceIndex) { var classId = hierarchy.classIds[instanceIndex]; var instances = hierarchy.classes[classId].instances; for (var name in instances) { if (instances.hasOwnProperty(name)) { if (results.indexOf(name) === -1) { results.push(name); } } } }); } function getHierarchyProperty(batchTable, batchId, name) { var hierarchy = batchTable._batchTableHierarchy; return traverseHierarchy(hierarchy, batchId, function(hierarchy, instanceIndex) { var classId = hierarchy.classIds[instanceIndex]; var instanceClass = hierarchy.classes[classId]; var indexInClass = hierarchy.classIndexes[instanceIndex]; var propertyValues = instanceClass.instances[name]; if (defined(propertyValues)) { if (defined(propertyValues.typedArray)) { return getBinaryProperty(propertyValues, indexInClass); } return clone(propertyValues[indexInClass], true); } }); } function setHierarchyProperty(batchTable, batchId, name, value) { var hierarchy = batchTable._batchTableHierarchy; var result = traverseHierarchy(hierarchy, batchId, function(hierarchy, instanceIndex) { var classId = hierarchy.classIds[instanceIndex]; var instanceClass = hierarchy.classes[classId]; var indexInClass = hierarchy.classIndexes[instanceIndex]; var propertyValues = instanceClass.instances[name]; if (defined(propertyValues)) { //>>includeStart('debug', pragmas.debug); if (instanceIndex !== batchId) { throw new DeveloperError('Inherited property "' + name + '" is read-only.'); } //>>includeEnd('debug'); if (defined(propertyValues.typedArray)) { setBinaryProperty(propertyValues, indexInClass, value); } else { propertyValues[indexInClass] = clone(value, true); } return true; } }); return defined(result); } Cesium3DTileBatchTable.prototype.isClass = function(batchId, className) { //>>includeStart('debug', pragmas.debug); checkBatchId(batchId, this.featuresLength); Check.typeOf.string('className', className); //>>includeEnd('debug'); // PERFORMANCE_IDEA : cache results in the ancestor classes to speed up this check if this area becomes a hotspot var hierarchy = this._batchTableHierarchy; if (!defined(hierarchy)) { return false; } // PERFORMANCE_IDEA : treat class names as integers for faster comparisons var result = traverseHierarchy(hierarchy, batchId, function(hierarchy, instanceIndex) { var classId = hierarchy.classIds[instanceIndex]; var instanceClass = hierarchy.classes[classId]; if (instanceClass.name === className) { return true; } }); return defined(result); }; Cesium3DTileBatchTable.prototype.isExactClass = function(batchId, className) { //>>includeStart('debug', pragmas.debug); Check.typeOf.string('className', className); //>>includeEnd('debug'); return (this.getExactClassName(batchId) === className); }; Cesium3DTileBatchTable.prototype.getExactClassName = function(batchId) { //>>includeStart('debug', pragmas.debug); checkBatchId(batchId, this.featuresLength); //>>includeEnd('debug'); var hierarchy = this._batchTableHierarchy; if (!defined(hierarchy)) { return undefined; } var classId = hierarchy.classIds[batchId]; var instanceClass = hierarchy.classes[classId]; return instanceClass.name; }; Cesium3DTileBatchTable.prototype.hasProperty = function(batchId, name) { //>>includeStart('debug', pragmas.debug); checkBatchId(batchId, this.featuresLength); Check.typeOf.string('name', name); //>>includeEnd('debug'); return (defined(this._properties[name])) || (defined(this._batchTableHierarchy) && hasPropertyInHierarchy(this, batchId, name)); }; Cesium3DTileBatchTable.prototype.getPropertyNames = function(batchId, results) { //>>includeStart('debug', pragmas.debug); checkBatchId(batchId, this.featuresLength); //>>includeEnd('debug'); results = defined(results) ? results : []; results.length = 0; var propertyNames = Object.keys(this._properties); results.push.apply(results, propertyNames); if (defined(this._batchTableHierarchy)) { getPropertyNamesInHierarchy(this, batchId, results); } return results; }; Cesium3DTileBatchTable.prototype.getProperty = function(batchId, name) { //>>includeStart('debug', pragmas.debug); checkBatchId(batchId, this.featuresLength); Check.typeOf.string('name', name); //>>includeEnd('debug'); if (defined(this._batchTableBinaryProperties)) { var binaryProperty = this._batchTableBinaryProperties[name]; if (defined(binaryProperty)) { return getBinaryProperty(binaryProperty, batchId); } } var propertyValues = this._properties[name]; if (defined(propertyValues)) { return clone(propertyValues[batchId], true); } if (defined(this._batchTableHierarchy)) { var hierarchyProperty = getHierarchyProperty(this, batchId, name); if (defined(hierarchyProperty)) { return hierarchyProperty; } } return undefined; }; Cesium3DTileBatchTable.prototype.setProperty = function(batchId, name, value) { var featuresLength = this.featuresLength; //>>includeStart('debug', pragmas.debug); checkBatchId(batchId, featuresLength); Check.typeOf.string('name', name); //>>includeEnd('debug'); if (defined(this._batchTableBinaryProperties)) { var binaryProperty = this._batchTableBinaryProperties[name]; if (defined(binaryProperty)) { setBinaryProperty(binaryProperty, batchId, value); return; } } if (defined(this._batchTableHierarchy)) { if (setHierarchyProperty(this, batchId, name, value)) { return; } } var propertyValues = this._properties[name]; if (!defined(propertyValues)) { // Property does not exist. Create it. this._properties[name] = new Array(featuresLength); propertyValues = this._properties[name]; } propertyValues[batchId] = clone(value, true); }; function getGlslComputeSt(batchTable) { // GLSL batchId is zero-based: [0, featuresLength - 1] if (batchTable._textureDimensions.y === 1) { return 'uniform vec4 tile_textureStep; \n' + 'vec2 computeSt(float batchId) \n' + '{ \n' + ' float stepX = tile_textureStep.x; \n' + ' float centerX = tile_textureStep.y; \n' + ' return vec2(centerX + (batchId * stepX), 0.5); \n' + '} \n'; } return 'uniform vec4 tile_textureStep; \n' + 'uniform vec2 tile_textureDimensions; \n' + 'vec2 computeSt(float batchId) \n' + '{ \n' + ' float stepX = tile_textureStep.x; \n' + ' float centerX = tile_textureStep.y; \n' + ' float stepY = tile_textureStep.z; \n' + ' float centerY = tile_textureStep.w; \n' + ' float xId = mod(batchId, tile_textureDimensions.x); \n' + ' float yId = floor(batchId / tile_textureDimensions.x); \n' + ' return vec2(centerX + (xId * stepX), centerY + (yId * stepY)); \n' + '} \n'; } Cesium3DTileBatchTable.prototype.getVertexShaderCallback = function(handleTranslucent, batchIdAttributeName, diffuseAttributeOrUniformName) { if (this.featuresLength === 0) { return; } var that = this; return function(source) { // If the color blend mode is HIGHLIGHT, the highlight color will always be applied in the fragment shader. // No need to apply the highlight color in the vertex shader as well. var renamedSource = modifyDiffuse(source, diffuseAttributeOrUniformName, false); var newMain; if (ContextLimits.maximumVertexTextureImageUnits > 0) { // When VTF is supported, perform per-feature show/hide in the vertex shader newMain = ''; if (handleTranslucent) { newMain += 'uniform bool tile_translucentCommand; \n'; } newMain += 'uniform sampler2D tile_batchTexture; \n' + 'varying vec4 tile_featureColor; \n' + 'varying vec2 tile_featureSt; \n' + 'void main() \n' + '{ \n' + ' vec2 st = computeSt(' + batchIdAttributeName + '); \n' + ' vec4 featureProperties = texture2D(tile_batchTexture, st); \n' + ' tile_color(featureProperties); \n' + ' float show = ceil(featureProperties.a); \n' + // 0 - false, non-zeo - true ' gl_Position *= show; \n'; // Per-feature show/hide if (handleTranslucent) { newMain += ' bool isStyleTranslucent = (featureProperties.a != 1.0); \n' + ' if (czm_pass == czm_passTranslucent) \n' + ' { \n' + ' if (!isStyleTranslucent && !tile_translucentCommand) \n' + // Do not render opaque features in the translucent pass ' { \n' + ' gl_Position *= 0.0; \n' + ' } \n' + ' } \n' + ' else \n' + ' { \n' + ' if (isStyleTranslucent) \n' + // Do not render translucent features in the opaque pass ' { \n' + ' gl_Position *= 0.0; \n' + ' } \n' + ' } \n'; } newMain += ' tile_featureColor = featureProperties; \n' + ' tile_featureSt = st; \n' + '}'; } else { // When VTF is not supported, color blend mode MIX will look incorrect due to the feature's color not being available in the vertex shader newMain = 'varying vec2 tile_featureSt; \n' + 'void main() \n' + '{ \n' + ' tile_color(vec4(1.0)); \n' + ' tile_featureSt = computeSt(' + batchIdAttributeName + '); \n' + '}'; } return renamedSource + '\n' + getGlslComputeSt(that) + newMain; }; }; function getDefaultShader(source, applyHighlight) { source = ShaderSource.replaceMain(source, 'tile_main'); if (!applyHighlight) { return source + 'void tile_color(vec4 tile_featureColor) \n' + '{ \n' + ' tile_main(); \n' + '} \n'; } // The color blend mode is intended for the RGB channels so alpha is always just multiplied. // gl_FragColor is multiplied by the tile color only when tile_colorBlend is 0.0 (highlight) return source + 'uniform float tile_colorBlend; \n' + 'void tile_color(vec4 tile_featureColor) \n' + '{ \n' + ' tile_main(); \n' + ' tile_featureColor = czm_gammaCorrect(tile_featureColor); \n' + ' gl_FragColor.a *= tile_featureColor.a; \n' + ' float highlight = ceil(tile_colorBlend); \n' + ' gl_FragColor.rgb *= mix(tile_featureColor.rgb, vec3(1.0), highlight); \n' + '} \n'; } function replaceDiffuseTextureCalls(source, diffuseAttributeOrUniformName) { var functionCall = 'texture2D(' + diffuseAttributeOrUniformName; var fromIndex = 0; var startIndex = source.indexOf(functionCall, fromIndex); var endIndex; while (startIndex > -1) { var nestedLevel = 0; for (var i = startIndex; i < source.length; ++i) { var character = source.charAt(i); if (character === '(') { ++nestedLevel; } else if (character === ')') { --nestedLevel; if (nestedLevel === 0) { endIndex = i + 1; break; } } } var extractedFunction = source.slice(startIndex, endIndex); var replacedFunction = 'tile_diffuse_final(' + extractedFunction + ', tile_diffuse)'; source = source.slice(0, startIndex) + replacedFunction + source.slice(endIndex); fromIndex = startIndex + replacedFunction.length; startIndex = source.indexOf(functionCall, fromIndex); } return source; } function modifyDiffuse(source, diffuseAttributeOrUniformName, applyHighlight) { // If the glTF does not specify the _3DTILESDIFFUSE semantic, return the default shader. // Otherwise if _3DTILESDIFFUSE is defined prefer the shader below that can switch the color mode at runtime. if (!defined(diffuseAttributeOrUniformName)) { return getDefaultShader(source, applyHighlight); } // Find the diffuse uniform. Examples matches: // uniform vec3 u_diffuseColor; // uniform sampler2D diffuseTexture; var regex = new RegExp('(uniform|attribute|in)\\s+(vec[34]|sampler2D)\\s+' + diffuseAttributeOrUniformName + ';'); var uniformMatch = source.match(regex); if (!defined(uniformMatch)) { // Could not find uniform declaration of type vec3, vec4, or sampler2D return getDefaultShader(source, applyHighlight); } var declaration = uniformMatch[0]; var type = uniformMatch[2]; source = ShaderSource.replaceMain(source, 'tile_main'); source = source.replace(declaration, ''); // Remove uniform declaration for now so the replace below doesn't affect it // If the tile color is white, use the source color. This implies the feature has not been styled. // Highlight: tile_colorBlend is 0.0 and the source color is used // Replace: tile_colorBlend is 1.0 and the tile color is used // Mix: tile_colorBlend is between 0.0 and 1.0, causing the source color and tile color to mix var finalDiffuseFunction = 'bool isWhite(vec3 color) \n' + '{ \n' + ' return all(greaterThan(color, vec3(1.0 - czm_epsilon3))); \n' + '} \n' + 'vec4 tile_diffuse_final(vec4 sourceDiffuse, vec4 tileDiffuse) \n' + '{ \n' + ' vec4 blendDiffuse = mix(sourceDiffuse, tileDiffuse, tile_colorBlend); \n' + ' vec4 diffuse = isWhite(tileDiffuse.rgb) ? sourceDiffuse : blendDiffuse; \n' + ' return vec4(diffuse.rgb, sourceDiffuse.a); \n' + '} \n'; // The color blend mode is intended for the RGB channels so alpha is always just multiplied. // gl_FragColor is multiplied by the tile color only when tile_colorBlend is 0.0 (highlight) var highlight = ' tile_featureColor = czm_gammaCorrect(tile_featureColor); \n' + ' gl_FragColor.a *= tile_featureColor.a; \n' + ' float highlight = ceil(tile_colorBlend); \n' + ' gl_FragColor.rgb *= mix(tile_featureColor.rgb, vec3(1.0), highlight); \n'; var setColor; if (type === 'vec3' || type === 'vec4') { var sourceDiffuse = (type === 'vec3') ? ('vec4(' + diffuseAttributeOrUniformName + ', 1.0)') : diffuseAttributeOrUniformName; var replaceDiffuse = (type === 'vec3') ? 'tile_diffuse.xyz' : 'tile_diffuse'; regex = new RegExp(diffuseAttributeOrUniformName, 'g'); source = source.replace(regex, replaceDiffuse); setColor = ' vec4 source = ' + sourceDiffuse + '; \n' + ' tile_diffuse = tile_diffuse_final(source, tile_featureColor); \n' + ' tile_main(); \n'; } else if (type === 'sampler2D') { // Handles any number of nested parentheses // E.g. texture2D(u_diffuse, uv) // E.g. texture2D(u_diffuse, computeUV(index)) source = replaceDiffuseTextureCalls(source, diffuseAttributeOrUniformName); setColor = ' tile_diffuse = tile_featureColor; \n' + ' tile_main(); \n'; } source = 'uniform float tile_colorBlend; \n' + 'vec4 tile_diffuse = vec4(1.0); \n' + finalDiffuseFunction + declaration + '\n' + source + '\n' + 'void tile_color(vec4 tile_featureColor) \n' + '{ \n' + setColor; if (applyHighlight) { source += highlight; } source += '} \n'; return source; } Cesium3DTileBatchTable.prototype.getFragmentShaderCallback = function(handleTranslucent, diffuseAttributeOrUniformName) { if (this.featuresLength === 0) { return; } return function(source) { source = modifyDiffuse(source, diffuseAttributeOrUniformName, true); if (ContextLimits.maximumVertexTextureImageUnits > 0) { // When VTF is supported, per-feature show/hide already happened in the fragment shader source += 'uniform sampler2D tile_pickTexture; \n' + 'varying vec2 tile_featureSt; \n' + 'varying vec4 tile_featureColor; \n' + 'void main() \n' + '{ \n' + ' tile_color(tile_featureColor); \n' + '}'; } else { if (handleTranslucent) { source += 'uniform bool tile_translucentCommand; \n'; } source += 'uniform sampler2D tile_pickTexture; \n' + 'uniform sampler2D tile_batchTexture; \n' + 'varying vec2 tile_featureSt; \n' + 'void main() \n' + '{ \n' + ' vec4 featureProperties = texture2D(tile_batchTexture, tile_featureSt); \n' + ' if (featureProperties.a == 0.0) { \n' + // show: alpha == 0 - false, non-zeo - true ' discard; \n' + ' } \n'; if (handleTranslucent) { source += ' bool isStyleTranslucent = (featureProperties.a != 1.0); \n' + ' if (czm_pass == czm_passTranslucent) \n' + ' { \n' + ' if (!isStyleTranslucent && !tile_translucentCommand) \n' + // Do not render opaque features in the translucent pass ' { \n' + ' discard; \n' + ' } \n' + ' } \n' + ' else \n' + ' { \n' + ' if (isStyleTranslucent) \n' + // Do not render translucent features in the opaque pass ' { \n' + ' discard; \n' + ' } \n' + ' } \n'; } source += ' tile_color(featureProperties); \n' + '} \n'; } return source; }; }; Cesium3DTileBatchTable.prototype.getClassificationFragmentShaderCallback = function() { if (this.featuresLength === 0) { return; } return function(source) { source = ShaderSource.replaceMain(source, 'tile_main'); if (ContextLimits.maximumVertexTextureImageUnits > 0) { // When VTF is supported, per-feature show/hide already happened in the fragment shader source += 'uniform sampler2D tile_pickTexture;\n' + 'varying vec2 tile_featureSt; \n' + 'varying vec4 tile_featureColor; \n' + 'void main() \n' + '{ \n' + ' tile_main(); \n' + ' gl_FragColor = tile_featureColor; \n' + '}'; } else { source += 'uniform sampler2D tile_batchTexture; \n' + 'uniform sampler2D tile_pickTexture;\n' + 'varying vec2 tile_featureSt; \n' + 'void main() \n' + '{ \n' + ' tile_main(); \n' + ' vec4 featureProperties = texture2D(tile_batchTexture, tile_featureSt); \n' + ' if (featureProperties.a == 0.0) { \n' + // show: alpha == 0 - false, non-zeo - true ' discard; \n' + ' } \n' + ' gl_FragColor = featureProperties; \n' + '} \n'; } return source; }; }; function getColorBlend(batchTable) { var tileset = batchTable._content.tileset; var colorBlendMode = tileset.colorBlendMode; var colorBlendAmount = tileset.colorBlendAmount; if (colorBlendMode === Cesium3DTileColorBlendMode.HIGHLIGHT) { return 0.0; } if (colorBlendMode === Cesium3DTileColorBlendMode.REPLACE) { return 1.0; } if (colorBlendMode === Cesium3DTileColorBlendMode.MIX) { // The value 0.0 is reserved for highlight, so clamp to just above 0.0. return CesiumMath.clamp(colorBlendAmount, CesiumMath.EPSILON4, 1.0); } //>>includeStart('debug', pragmas.debug); throw new DeveloperError('Invalid color blend mode "' + colorBlendMode + '".'); //>>includeEnd('debug'); } Cesium3DTileBatchTable.prototype.getUniformMapCallback = function() { if (this.featuresLength === 0) { return; } var that = this; return function(uniformMap) { var batchUniformMap = { tile_batchTexture : function() { // PERFORMANCE_IDEA: we could also use a custom shader that avoids the texture read. return defaultValue(that._batchTexture, that._defaultTexture); }, tile_textureDimensions : function() { return that._textureDimensions; }, tile_textureStep : function() { return that._textureStep; }, tile_colorBlend : function() { return getColorBlend(that); }, tile_pickTexture : function() { return that._pickTexture; } }; return combine(uniformMap, batchUniformMap); }; }; Cesium3DTileBatchTable.prototype.getPickId = function() { return 'texture2D(tile_pickTexture, tile_featureSt)'; }; /////////////////////////////////////////////////////////////////////////// var StyleCommandsNeeded = { ALL_OPAQUE : 0, ALL_TRANSLUCENT : 1, OPAQUE_AND_TRANSLUCENT : 2 }; Cesium3DTileBatchTable.prototype.addDerivedCommands = function(frameState, commandStart) { var commandList = frameState.commandList; var commandEnd = commandList.length; var tile = this._content._tile; var finalResolution = tile._finalResolution; var tileset = tile.tileset; var bivariateVisibilityTest = tileset._skipLevelOfDetail && tileset._hasMixedContent && frameState.context.stencilBuffer; var styleCommandsNeeded = getStyleCommandsNeeded(this); for (var i = commandStart; i < commandEnd; ++i) { var command = commandList[i]; var derivedCommands = command.derivedCommands.tileset; if (!defined(derivedCommands) || command.dirty) { derivedCommands = {}; command.derivedCommands.tileset = derivedCommands; derivedCommands.originalCommand = deriveCommand(command); command.dirty = false; } var originalCommand = derivedCommands.originalCommand; if (styleCommandsNeeded !== StyleCommandsNeeded.ALL_OPAQUE && command.pass !== Pass.TRANSLUCENT) { if (!defined(derivedCommands.translucent)) { derivedCommands.translucent = deriveTranslucentCommand(originalCommand); } } if (styleCommandsNeeded !== StyleCommandsNeeded.ALL_TRANSLUCENT && command.pass !== Pass.TRANSLUCENT) { if (!defined(derivedCommands.opaque)) { derivedCommands.opaque = deriveOpaqueCommand(originalCommand); } if (bivariateVisibilityTest) { if (!finalResolution) { if (!defined(derivedCommands.zback)) { derivedCommands.zback = deriveZBackfaceCommand(frameState.context, originalCommand); } tileset._backfaceCommands.push(derivedCommands.zback); } if (!defined(derivedCommands.stencil) || (tile._selectionDepth !== getLastSelectionDepth(derivedCommands.stencil))) { if (command.renderState.depthMask) { derivedCommands.stencil = deriveStencilCommand(originalCommand, tile._selectionDepth); } else { // Ignore if tile does not write depth derivedCommands.stencil = derivedCommands.opaque; } } } } var opaqueCommand = bivariateVisibilityTest ? derivedCommands.stencil : derivedCommands.opaque; var translucentCommand = derivedCommands.translucent; // If the command was originally opaque: // * If the styling applied to the tile is all opaque, use the opaque command // (with one additional uniform needed for the shader). // * If the styling is all translucent, use new (cached) derived commands (front // and back faces) with a translucent render state. // * If the styling causes both opaque and translucent features in this tile, // then use both sets of commands. if (command.pass !== Pass.TRANSLUCENT) { if (styleCommandsNeeded === StyleCommandsNeeded.ALL_OPAQUE) { commandList[i] = opaqueCommand; } if (styleCommandsNeeded === StyleCommandsNeeded.ALL_TRANSLUCENT) { commandList[i] = translucentCommand; } if (styleCommandsNeeded === StyleCommandsNeeded.OPAQUE_AND_TRANSLUCENT) { // PERFORMANCE_IDEA: if the tile has multiple commands, we do not know what features are in what // commands so this case may be overkill. commandList[i] = opaqueCommand; commandList.push(translucentCommand); } } else { // Command was originally translucent so no need to derive new commands; // as of now, a style can't change an originally translucent feature to // opaque since the style's alpha is modulated, not a replacement. When // this changes, we need to derive new opaque commands here. commandList[i] = originalCommand; } } }; function getStyleCommandsNeeded(batchTable) { var translucentFeaturesLength = batchTable._translucentFeaturesLength; if (translucentFeaturesLength === 0) { return StyleCommandsNeeded.ALL_OPAQUE; } else if (translucentFeaturesLength === batchTable.featuresLength) { return StyleCommandsNeeded.ALL_TRANSLUCENT; } return StyleCommandsNeeded.OPAQUE_AND_TRANSLUCENT; } function deriveCommand(command) { var derivedCommand = DrawCommand.shallowClone(command); // Add a uniform to indicate if the original command was translucent so // the shader knows not to cull vertices that were originally transparent // even though their style is opaque. var translucentCommand = (derivedCommand.pass === Pass.TRANSLUCENT); derivedCommand.uniformMap = defined(derivedCommand.uniformMap) ? derivedCommand.uniformMap : {}; derivedCommand.uniformMap.tile_translucentCommand = function() { return translucentCommand; }; return derivedCommand; } function deriveTranslucentCommand(command) { var derivedCommand = DrawCommand.shallowClone(command); derivedCommand.pass = Pass.TRANSLUCENT; derivedCommand.renderState = getTranslucentRenderState(command.renderState); return derivedCommand; } function deriveOpaqueCommand(command) { var derivedCommand = DrawCommand.shallowClone(command); derivedCommand.renderState = getOpaqueRenderState(command.renderState); return derivedCommand; } function getDisableLogDepthFragmentShaderProgram(context, shaderProgram) { var shader = context.shaderCache.getDerivedShaderProgram(shaderProgram, 'zBackfaceLogDepth'); if (!defined(shader)) { var fs = shaderProgram.fragmentShaderSource.clone(); fs.defines = defined(fs.defines) ? fs.defines.slice(0) : []; fs.defines.push('DISABLE_LOG_DEPTH_FRAGMENT_WRITE'); shader = context.shaderCache.createDerivedShaderProgram(shaderProgram, 'zBackfaceLogDepth', { vertexShaderSource : shaderProgram.vertexShaderSource, fragmentShaderSource : fs, attributeLocations : shaderProgram._attributeLocations }); } return shader; } function deriveZBackfaceCommand(context, command) { // Write just backface depth of unresolved tiles so resolved stenciled tiles do not appear in front var derivedCommand = DrawCommand.shallowClone(command); var rs = clone(derivedCommand.renderState, true); rs.cull.enabled = true; rs.cull.face = CullFace.FRONT; // Back faces do not need to write color. rs.colorMask = { red : false, green : false, blue : false, alpha : false }; // Push back face depth away from the camera so it is less likely that back faces and front faces of the same tile // intersect and overlap. This helps avoid flickering for very thin double-sided walls. rs.polygonOffset = { enabled : true, factor : 5.0, units : 5.0 }; // Set the 3D Tiles bit rs.stencilTest = StencilConstants.setCesium3DTileBit(); rs.stencilMask = StencilConstants.CESIUM_3D_TILE_MASK; derivedCommand.renderState = RenderState.fromCache(rs); derivedCommand.castShadows = false; derivedCommand.receiveShadows = false; // Disable the depth writes in the fragment shader. The back face commands were causing the higher resolution // tiles to disappear. derivedCommand.shaderProgram = getDisableLogDepthFragmentShaderProgram(context, command.shaderProgram); return derivedCommand; } function deriveStencilCommand(command, reference) { // Tiles only draw if their selection depth is >= the tile drawn already. They write their // selection depth to the stencil buffer to prevent ancestor tiles from drawing on top var derivedCommand = DrawCommand.shallowClone(command); var rs = clone(derivedCommand.renderState, true); // Stencil test is masked to the most significant 3 bits so the reference is shifted. Writes 0 for the terrain bit rs.stencilTest.enabled = true; rs.stencilTest.mask = StencilConstants.SKIP_LOD_MASK; rs.stencilTest.reference = StencilConstants.CESIUM_3D_TILE_MASK | (reference << StencilConstants.SKIP_LOD_BIT_SHIFT); rs.stencilTest.frontFunction = StencilFunction.GREATER_OR_EQUAL; rs.stencilTest.frontOperation.zPass = StencilOperation.REPLACE; rs.stencilTest.backFunction = StencilFunction.GREATER_OR_EQUAL; rs.stencilTest.backOperation.zPass = StencilOperation.REPLACE; rs.stencilMask = StencilConstants.CESIUM_3D_TILE_MASK | StencilConstants.SKIP_LOD_MASK; derivedCommand.renderState = RenderState.fromCache(rs); return derivedCommand; } function getLastSelectionDepth(stencilCommand) { // Isolate the selection depth from the stencil reference. var reference = stencilCommand.renderState.stencilTest.reference; return (reference & StencilConstants.SKIP_LOD_MASK) >>> StencilConstants.SKIP_LOD_BIT_SHIFT; } function getTranslucentRenderState(renderState) { var rs = clone(renderState, true); rs.cull.enabled = false; rs.depthTest.enabled = true; rs.depthMask = false; rs.blending = BlendingState.ALPHA_BLEND; return RenderState.fromCache(rs); } function getOpaqueRenderState(renderState) { var rs = clone(renderState, true); rs.stencilTest = StencilConstants.setCesium3DTileBit(); rs.stencilMask = StencilConstants.CESIUM_3D_TILE_MASK; return RenderState.fromCache(rs); } /////////////////////////////////////////////////////////////////////////// function createTexture(batchTable, context, bytes) { var dimensions = batchTable._textureDimensions; return new Texture({ context : context, pixelFormat : PixelFormat.RGBA, pixelDatatype : PixelDatatype.UNSIGNED_BYTE, source : { width : dimensions.x, height : dimensions.y, arrayBufferView : bytes }, flipY : false, sampler : new Sampler({ minificationFilter : TextureMinificationFilter.NEAREST, magnificationFilter : TextureMagnificationFilter.NEAREST }) }); } function createPickTexture(batchTable, context) { var featuresLength = batchTable.featuresLength; if (!defined(batchTable._pickTexture) && (featuresLength > 0)) { var pickIds = batchTable._pickIds; var byteLength = getByteLength(batchTable); var bytes = new Uint8Array(byteLength); var content = batchTable._content; // PERFORMANCE_IDEA: we could skip the pick texture completely by allocating // a continuous range of pickIds and then converting the base pickId + batchId // to RGBA in the shader. The only consider is precision issues, which might // not be an issue in WebGL 2. for (var i = 0; i < featuresLength; ++i) { var pickId = context.createPickId(content.getFeature(i)); pickIds.push(pickId); var pickColor = pickId.color; var offset = i * 4; bytes[offset] = Color.floatToByte(pickColor.red); bytes[offset + 1] = Color.floatToByte(pickColor.green); bytes[offset + 2] = Color.floatToByte(pickColor.blue); bytes[offset + 3] = Color.floatToByte(pickColor.alpha); } batchTable._pickTexture = createTexture(batchTable, context, bytes); content.tileset._statistics.batchTableByteLength += batchTable._pickTexture.sizeInBytes; } } function updateBatchTexture(batchTable) { var dimensions = batchTable._textureDimensions; // PERFORMANCE_IDEA: Instead of rewriting the entire texture, use fine-grained // texture updates when less than, for example, 10%, of the values changed. Or // even just optimize the common case when one feature show/color changed. batchTable._batchTexture.copyFrom({ width : dimensions.x, height : dimensions.y, arrayBufferView : batchTable._batchValues }); } Cesium3DTileBatchTable.prototype.update = function(tileset, frameState) { var context = frameState.context; this._defaultTexture = context.defaultTexture; var passes = frameState.passes; if (passes.pick || passes.postProcess) { createPickTexture(this, context); } if (this._batchValuesDirty) { this._batchValuesDirty = false; // Create batch texture on-demand if (!defined(this._batchTexture)) { this._batchTexture = createTexture(this, context, this._batchValues); tileset._statistics.batchTableByteLength += this._batchTexture.sizeInBytes; } updateBatchTexture(this); // Apply per-feature show/color updates } }; Cesium3DTileBatchTable.prototype.isDestroyed = function() { return false; }; Cesium3DTileBatchTable.prototype.destroy = function() { this._batchTexture = this._batchTexture && this._batchTexture.destroy(); this._pickTexture = this._pickTexture && this._pickTexture.destroy(); var pickIds = this._pickIds; var length = pickIds.length; for (var i = 0; i < length; ++i) { pickIds[i].destroy(); } return destroyObject(this); }; export default Cesium3DTileBatchTable;