You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1737 lines
55 KiB
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 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
* @constructor
*/
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,
hasPremultipliedAlpha
) {
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";
if (hasPremultipliedAlpha) {
source += " gl_FragColor.rgb *= gl_FragColor.a; \n";
}
source += "}";
} 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";
if (hasPremultipliedAlpha) {
source += " gl_FragColor.rgb *= gl_FragColor.a; \n";
}
source += "} \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" +
" gl_FragColor.rgb *= gl_FragColor.a; \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" +
" gl_FragColor.rgb *= gl_FragColor.a; \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 getLogDepthPolygonOffsetFragmentShaderProgram(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("POLYGON_OFFSET");
fs.sources.unshift(
"#ifdef GL_OES_standard_derivatives\n#extension GL_OES_standard_derivatives : enable\n#endif\n"
);
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;
derivedCommand.uniformMap = clone(command.uniformMap);
var polygonOffset = new Cartesian2(5.0, 5.0);
derivedCommand.uniformMap.u_polygonOffset = function () {
return polygonOffset;
};
// Make the log depth depth fragment write account for the polygon offset, too.
// Otherwise, the back face commands will cause the higher resolution
// tiles to disappear.
derivedCommand.shaderProgram = getLogDepthPolygonOffsetFragmentShaderProgram(
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;
rs.stencilTest = StencilConstants.setCesium3DTileBit();
rs.stencilMask = StencilConstants.CESIUM_3D_TILE_MASK;
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: Sampler.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;