cesium
Version:
CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.
1,191 lines (1,033 loc) • 65.3 kB
JavaScript
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 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;
Object.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;
}