// @ts-check import defined from "../Core/defined.js"; import BufferPrimitiveCollection from "./BufferPrimitiveCollection.js"; import BufferPolygon from "./BufferPolygon.js"; import Frozen from "../Core/Frozen.js"; import assert from "../Core/assert.js"; import IndexDatatype from "../Core/IndexDatatype.js"; import renderPolygons from "./renderBufferPolygonCollection.js"; import BufferPolygonMaterial from "./BufferPolygonMaterial.js"; /** @import BlendOption from "./BlendOption.js"; */ /** @import BoundingSphere from "../Core/BoundingSphere.js"; */ /** @import { TypedArray } from "../Core/globalTypes.js"; */ /** @import Matrix4 from "../Core/Matrix4.js"; */ /** @import FrameState from "./FrameState.js" */ /** @import ComponentDatatype from "../Core/ComponentDatatype.js"; */ const { ERR_CAPACITY } = BufferPrimitiveCollection.Error; /** * @typedef {object} BufferPolygonOptions * @property {Matrix4} [modelMatrix=Matrix4.IDENTITY] Transforms geometry from model to world coordinates. * @property {boolean} [show=true] * @property {BufferPolygonMaterial} [material=BufferPolygonMaterial.DEFAULT_MATERIAL] * @property {number} [featureId] * @property {object} [pickObject] * @property {TypedArray} [positions] * @property {TypedArray} [holes] * @property {TypedArray} [triangles] * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy. */ /** * Collection of polygons held in ArrayBuffer storage for performance and memory optimization. * *
Default buffer memory allocation is arbitrary, and collections cannot be resized, * so specific per-buffer capacities should be provided in the collection * constructor when available.
* * @example * import earcut from "earcut"; * * const collection = new BufferPolygonCollection({ * primitiveCountMax: 1024, * vertexCountMax: 4096, * holeCountMax: 1024, * triangleCountMax: 2048, * }); * * const polygon = new BufferPolygon(); * const positions = [ ... ]; * const holes = [ ... ]; * const material = new BufferPolygonMaterial({color: Color.WHITE}); * * // Create a new polygon, temporarily bound to 'polygon' local variable. * collection.add({ * positions: new Float64Array(positions), * holes: new Uint32Array(holes), * triangles: new Uint32Array(earcut(positions, holes, 3)), * material * }, polygon); * * // Iterate over all polygons in collection, temporarily binding 'polygon' * // local variable to each, and updating polygon material. * for (let i = 0; i < collection.primitiveCount; i++) { * collection.get(i, polygon); * polygon.setMaterial(material); * } * * @see BufferPolygon * @see BufferPolygonMaterial * @see BufferPrimitiveCollection * @extends BufferPrimitiveCollectiontrue, primitives are pickable with {@link Scene#pick}. When false, memory and initialization cost are lower.
* @param {BoundingSphere} [options.boundingVolume] Bounding volume, in world space, for the collection. When
* unspecified, a bounding volume is computed automatically and updated when primitive positions change. When
* specified, users are responsible for updating bounding volume as needed. Pre-computing the bounding volume
* manually, and updating it only as needed, will improve performance for larger dynamic collections.
* @param {boolean} [options.debugShowBoundingVolume=false]
* @param {BlendOption} [options.blendOption=BlendOption.TRANSLUCENT]
*/
constructor(options = Frozen.EMPTY_OBJECT) {
super(options);
/**
* @type {number}
* @ignore
*/
this._holeCount = 0;
/**
* @type {number}
* @protected
* @ignore
*/
this._holeCountMax =
options.holeCountMax ?? BufferPrimitiveCollection.DEFAULT_CAPACITY;
/**
* @type {TypedArray}
* @ignore
*/
this._holeIndexView = null;
/**
* @type {number}
* @ignore
*/
this._triangleCount = 0;
/**
* @type {number}
* @protected
* @ignore
*/
this._triangleCountMax =
options.triangleCountMax ?? BufferPrimitiveCollection.DEFAULT_CAPACITY;
/**
* @type {TypedArray}
* @ignore
*/
this._triangleIndexView = null;
this._allocateHoleIndexBuffer();
this._allocateTriangleIndexBuffer();
}
_getCollectionClass() {
return BufferPolygonCollection;
}
_getPrimitiveClass() {
return BufferPolygon;
}
_getMaterialClass() {
return BufferPolygonMaterial;
}
/////////////////////////////////////////////////////////////////////////////
// COLLECTION LIFECYCLE
/**
* @private
* @ignore
*/
_allocateHoleIndexBuffer() {
// @ts-expect-error Requires https://github.com/CesiumGS/cesium/pull/13203.
this._holeIndexView = IndexDatatype.createTypedArray(
this._positionCountMax,
this._holeCountMax,
);
}
/**
* @private
* @ignore
*/
_allocateTriangleIndexBuffer() {
// @ts-expect-error Requires https://github.com/CesiumGS/cesium/pull/13203.
this._triangleIndexView = IndexDatatype.createTypedArray(
this._positionCountMax,
this._triangleCountMax * 3,
);
}
/**
* Duplicates the contents of this collection into the result collection.
* Result collection is not resized, and must contain enough space for all
* primitives in the source collection. Existing polygons in the result
* collection will be overwritten.
*
* Useful when allocating more space for a collection that has reached its * capacity, and efficiently transferring polygons to the new collection.
* * @example * const result = new BufferPolygonCollection({ ... }); // allocate larger 'result' collection * BufferPolygonCollection.clone(collection, result); // copy polygons from 'collection' into 'result' * * @param {BufferPolygonCollection} collection * @param {BufferPolygonCollection} result * @returns {BufferPolygonCollection} */ static clone(collection, result) { super.clone(collection, result); //>>includeStart('debug', pragmas.debug); assert(collection.holeCount <= result.holeCountMax, ERR_CAPACITY); assert(collection.triangleCount <= result.triangleCountMax, ERR_CAPACITY); //>>includeEnd('debug'); this._copySubArray( collection._holeIndexView, result._holeIndexView, collection.holeCount, ); this._copySubArray( collection._triangleIndexView, result._triangleIndexView, collection._triangleCount * 3, ); result._holeCount = collection._holeCount; result._triangleCount = collection._triangleCount; return result; } /** * @param {BufferPolygonCollection} collection * @returns {BufferPolygonCollection} * @override * @ignore */ static _cloneEmpty(collection) { return new BufferPolygonCollection({ primitiveCountMax: collection.primitiveCountMax, vertexCountMax: collection.vertexCountMax, holeCountMax: collection.holeCountMax, triangleCountMax: collection.triangleCountMax, positionDatatype: collection.positionDatatype, positionNormalized: collection.positionNormalized, }); } /** * @param {BufferPolygonCollection} src * @param {BufferPolygonCollection} dst * @override * @ignore */ static _replaceBuffers(src, dst) { super._replaceBuffers(src, dst); dst._holeIndexView = src._holeIndexView; dst._triangleIndexView = src._triangleIndexView; } ///////////////////////////////////////////////////////////////////////////// // PRIMITIVE LIFECYCLE /** * Adds a new polygon to the collection, with the specified options. A * {@link BufferPolygon} instance is linked to the new polygon, using * the 'result' argument if given, or a new instance if not. For repeated * calls, prefer to reuse a single BufferPolygon instance rather than * allocating a new instance on each call. * * @param {BufferPolygonOptions} options * @param {BufferPolygon} result * @returns {BufferPolygon} * @override */ add(options, result = new BufferPolygon()) { super.add(options, result); const vertexOffset = this._positionCount; result._setUint32(BufferPolygon.Layout.POSITION_OFFSET_U32, vertexOffset); result._setUint32(BufferPolygon.Layout.POSITION_COUNT_U32, 0); const holeOffset = this._holeCount; result._setUint32(BufferPolygon.Layout.HOLE_OFFSET_U32, holeOffset); result._setUint32(BufferPolygon.Layout.HOLE_COUNT_U32, 0); const triangleOffset = this._triangleCount; result._setUint32(BufferPolygon.Layout.TRIANGLE_OFFSET_U32, triangleOffset); result._setUint32(BufferPolygon.Layout.TRIANGLE_COUNT_U32, 0); if (defined(options.positions)) { result.setPositions(options.positions); } if (defined(options.holes)) { result.setHoles(options.holes); } if (defined(options.triangles)) { result.setTriangles(options.triangles); } return result; } ///////////////////////////////////////////////////////////////////////////// // RENDER /** * @param {FrameState} frameState * @ignore */ update(frameState) { super.update(frameState); const passes = frameState.passes; if (this.show && (passes.render || passes.pick)) { this._renderContext = renderPolygons( this, frameState, this._renderContext, ); } } ///////////////////////////////////////////////////////////////////////////// // ACCESSORS /** * Total byte length of buffers owned by this collection. Includes any unused * space allocated by {@link primitiveCountMax}, even if no polygons have * yet been added in that space. * * @type {number} * @readonly * @override */ get byteLength() { return ( super.byteLength + this._holeIndexView.byteLength + this._triangleIndexView.byteLength ); } /** * Number of holes in collection. Must be <= {@link holeCountMax}. * * @type {number} * @readonly */ get holeCount() { return this._holeCount; } /** * Maximum number of holes in collection. Must be >= {@link holeCount}. * * @type {number} * @readonly * @default {@link BufferPrimitiveCollection.DEFAULT_CAPACITY} */ get holeCountMax() { return this._holeCountMax; } /** * Number of triangles in collection. Must be <= {@link triangleCountMax}. * * @type {number} * @readonly */ get triangleCount() { return this._triangleCount; } /** * Maximum number of triangles in collection. Must be >= {@link triangleCount}. * * @type {number} * @readonly * @default {@link BufferPrimitiveCollection.DEFAULT_CAPACITY} */ get triangleCountMax() { return this._triangleCountMax; } } export default BufferPolygonCollection;