cesium
Version:
CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.
1,203 lines (1,056 loc) • 83.2 kB
JavaScript
import AttributeCompression from '../Core/AttributeCompression.js';
import BoundingSphere from '../Core/BoundingSphere.js';
import Cartesian2 from '../Core/Cartesian2.js';
import Cartesian3 from '../Core/Cartesian3.js';
import Color from '../Core/Color.js';
import ComponentDatatype from '../Core/ComponentDatatype.js';
import defaultValue from '../Core/defaultValue.js';
import defined from '../Core/defined.js';
import destroyObject from '../Core/destroyObject.js';
import DeveloperError from '../Core/DeveloperError.js';
import EncodedCartesian3 from '../Core/EncodedCartesian3.js';
import IndexDatatype from '../Core/IndexDatatype.js';
import CesiumMath from '../Core/Math.js';
import Matrix4 from '../Core/Matrix4.js';
import WebGLConstants from '../Core/WebGLConstants.js';
import Buffer from '../Renderer/Buffer.js';
import BufferUsage from '../Renderer/BufferUsage.js';
import ContextLimits from '../Renderer/ContextLimits.js';
import DrawCommand from '../Renderer/DrawCommand.js';
import Pass from '../Renderer/Pass.js';
import RenderState from '../Renderer/RenderState.js';
import ShaderProgram from '../Renderer/ShaderProgram.js';
import ShaderSource from '../Renderer/ShaderSource.js';
import VertexArrayFacade from '../Renderer/VertexArrayFacade.js';
import BillboardCollectionFS from '../Shaders/BillboardCollectionFS.js';
import BillboardCollectionVS from '../Shaders/BillboardCollectionVS.js';
import Billboard from './Billboard.js';
import BlendingState from './BlendingState.js';
import BlendOption from './BlendOption.js';
import HeightReference from './HeightReference.js';
import HorizontalOrigin from './HorizontalOrigin.js';
import SceneMode from './SceneMode.js';
import SDFSettings from './SDFSettings.js';
import TextureAtlas from './TextureAtlas.js';
import VerticalOrigin from './VerticalOrigin.js';
var SHOW_INDEX = Billboard.SHOW_INDEX;
var POSITION_INDEX = Billboard.POSITION_INDEX;
var PIXEL_OFFSET_INDEX = Billboard.PIXEL_OFFSET_INDEX;
var EYE_OFFSET_INDEX = Billboard.EYE_OFFSET_INDEX;
var HORIZONTAL_ORIGIN_INDEX = Billboard.HORIZONTAL_ORIGIN_INDEX;
var VERTICAL_ORIGIN_INDEX = Billboard.VERTICAL_ORIGIN_INDEX;
var SCALE_INDEX = Billboard.SCALE_INDEX;
var IMAGE_INDEX_INDEX = Billboard.IMAGE_INDEX_INDEX;
var COLOR_INDEX = Billboard.COLOR_INDEX;
var ROTATION_INDEX = Billboard.ROTATION_INDEX;
var ALIGNED_AXIS_INDEX = Billboard.ALIGNED_AXIS_INDEX;
var SCALE_BY_DISTANCE_INDEX = Billboard.SCALE_BY_DISTANCE_INDEX;
var TRANSLUCENCY_BY_DISTANCE_INDEX = Billboard.TRANSLUCENCY_BY_DISTANCE_INDEX;
var PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX = Billboard.PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX;
var DISTANCE_DISPLAY_CONDITION_INDEX = Billboard.DISTANCE_DISPLAY_CONDITION;
var DISABLE_DEPTH_DISTANCE = Billboard.DISABLE_DEPTH_DISTANCE;
var TEXTURE_COORDINATE_BOUNDS = Billboard.TEXTURE_COORDINATE_BOUNDS;
var SDF_INDEX = Billboard.SDF_INDEX;
var NUMBER_OF_PROPERTIES = Billboard.NUMBER_OF_PROPERTIES;
var attributeLocations;
var attributeLocationsBatched = {
positionHighAndScale : 0,
positionLowAndRotation : 1,
compressedAttribute0 : 2, // pixel offset, translate, horizontal origin, vertical origin, show, direction, texture coordinates
compressedAttribute1 : 3, // aligned axis, translucency by distance, image width
compressedAttribute2 : 4, // image height, color, pick color, size in meters, valid aligned axis, 13 bits free
eyeOffset : 5, // 4 bytes free
scaleByDistance : 6,
pixelOffsetScaleByDistance : 7,
compressedAttribute3 : 8,
textureCoordinateBoundsOrLabelTranslate : 9,
a_batchId : 10,
sdf: 11
};
var attributeLocationsInstanced = {
direction : 0,
positionHighAndScale : 1,
positionLowAndRotation : 2, // texture offset in w
compressedAttribute0 : 3,
compressedAttribute1 : 4,
compressedAttribute2 : 5,
eyeOffset : 6, // texture range in w
scaleByDistance : 7,
pixelOffsetScaleByDistance : 8,
compressedAttribute3 : 9,
textureCoordinateBoundsOrLabelTranslate : 10,
a_batchId : 11,
sdf: 12
};
/**
* A renderable collection of billboards. Billboards are viewport-aligned
* images positioned in the 3D scene.
* <br /><br />
* <div align='center'>
* <img src='Images/Billboard.png' width='400' height='300' /><br />
* Example billboards
* </div>
* <br /><br />
* Billboards are added and removed from the collection using {@link BillboardCollection#add}
* and {@link BillboardCollection#remove}. Billboards in a collection automatically share textures
* for images with the same identifier.
*
* @alias BillboardCollection
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms each billboard from model to world coordinates.
* @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown.
* @param {Scene} [options.scene] Must be passed in for billboards that use the height reference property or will be depth tested against the globe.
* @param {BlendOption} [options.blendOption=BlendOption.OPAQUE_AND_TRANSLUCENT] The billboard blending option. The default
* is used for rendering both opaque and translucent billboards. However, if either all of the billboards are completely opaque or all are completely translucent,
* setting the technique to BlendOption.OPAQUE or BlendOption.TRANSLUCENT can improve performance by up to 2x.
*
* @performance For best performance, prefer a few collections, each with many billboards, to
* many collections with only a few billboards each. Organize collections so that billboards
* with the same update frequency are in the same collection, i.e., billboards that do not
* change should be in one collection; billboards that change every frame should be in another
* collection; and so on.
*
* @see BillboardCollection#add
* @see BillboardCollection#remove
* @see Billboard
* @see LabelCollection
*
* @demo {@link https://sandcastle.cesium.com/index.html?src=Billboards.html|Cesium Sandcastle Billboard Demo}
*
* @example
* // Create a billboard collection with two billboards
* var billboards = scene.primitives.add(new Cesium.BillboardCollection());
* billboards.add({
* position : new Cesium.Cartesian3(1.0, 2.0, 3.0),
* image : 'url/to/image'
* });
* billboards.add({
* position : new Cesium.Cartesian3(4.0, 5.0, 6.0),
* image : 'url/to/another/image'
* });
*/
function BillboardCollection(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
this._scene = options.scene;
this._batchTable = options.batchTable;
this._textureAtlas = undefined;
this._textureAtlasGUID = undefined;
this._destroyTextureAtlas = true;
this._sp = undefined;
this._spTranslucent = undefined;
this._rsOpaque = undefined;
this._rsTranslucent = undefined;
this._vaf = undefined;
this._billboards = [];
this._billboardsToUpdate = [];
this._billboardsToUpdateIndex = 0;
this._billboardsRemoved = false;
this._createVertexArray = false;
this._shaderRotation = false;
this._compiledShaderRotation = false;
this._shaderAlignedAxis = false;
this._compiledShaderAlignedAxis = false;
this._shaderScaleByDistance = false;
this._compiledShaderScaleByDistance = false;
this._shaderTranslucencyByDistance = false;
this._compiledShaderTranslucencyByDistance = false;
this._shaderPixelOffsetScaleByDistance = false;
this._compiledShaderPixelOffsetScaleByDistance = false;
this._shaderDistanceDisplayCondition = false;
this._compiledShaderDistanceDisplayCondition = false;
this._shaderDisableDepthDistance = false;
this._compiledShaderDisableDepthDistance = false;
this._shaderClampToGround = false;
this._compiledShaderClampToGround = false;
this._propertiesChanged = new Uint32Array(NUMBER_OF_PROPERTIES);
this._maxSize = 0.0;
this._maxEyeOffset = 0.0;
this._maxScale = 1.0;
this._maxPixelOffset = 0.0;
this._allHorizontalCenter = true;
this._allVerticalCenter = true;
this._allSizedInMeters = true;
this._baseVolume = new BoundingSphere();
this._baseVolumeWC = new BoundingSphere();
this._baseVolume2D = new BoundingSphere();
this._boundingVolume = new BoundingSphere();
this._boundingVolumeDirty = false;
this._colorCommands = [];
/**
* The 4x4 transformation matrix that transforms each billboard in this collection from model to world coordinates.
* When this is the identity matrix, the billboards are drawn in world coordinates, i.e., Earth's WGS84 coordinates.
* Local reference frames can be used by providing a different transformation matrix, like that returned
* by {@link Transforms.eastNorthUpToFixedFrame}.
*
* @type {Matrix4}
* @default {@link Matrix4.IDENTITY}
*
*
* @example
* var center = Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883);
* billboards.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(center);
* billboards.add({
* image : 'url/to/image',
* position : new Cesium.Cartesian3(0.0, 0.0, 0.0) // center
* });
* billboards.add({
* image : 'url/to/image',
* position : new Cesium.Cartesian3(1000000.0, 0.0, 0.0) // east
* });
* billboards.add({
* image : 'url/to/image',
* position : new Cesium.Cartesian3(0.0, 1000000.0, 0.0) // north
* });
* billboards.add({
* image : 'url/to/image',
* position : new Cesium.Cartesian3(0.0, 0.0, 1000000.0) // up
* });
*
* @see Transforms.eastNorthUpToFixedFrame
*/
this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY));
this._modelMatrix = Matrix4.clone(Matrix4.IDENTITY);
/**
* This property is for debugging only; it is not for production use nor is it optimized.
* <p>
* Draws the bounding sphere for each draw command in the primitive.
* </p>
*
* @type {Boolean}
*
* @default false
*/
this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false);
/**
* This property is for debugging only; it is not for production use nor is it optimized.
* <p>
* Draws the texture atlas for this BillboardCollection as a fullscreen quad.
* </p>
*
* @type {Boolean}
*
* @default false
*/
this.debugShowTextureAtlas = defaultValue(options.debugShowTextureAtlas, false);
/**
* The billboard blending option. The default is used for rendering both opaque and translucent billboards.
* However, if either all of the billboards are completely opaque or all are completely translucent,
* setting the technique to BlendOption.OPAQUE or BlendOption.TRANSLUCENT can improve
* performance by up to 2x.
* @type {BlendOption}
* @default BlendOption.OPAQUE_AND_TRANSLUCENT
*/
this.blendOption = defaultValue(options.blendOption, BlendOption.OPAQUE_AND_TRANSLUCENT);
this._blendOption = undefined;
this._mode = SceneMode.SCENE3D;
// The buffer usage for each attribute is determined based on the usage of the attribute over time.
this._buffersUsage = [
BufferUsage.STATIC_DRAW, // SHOW_INDEX
BufferUsage.STATIC_DRAW, // POSITION_INDEX
BufferUsage.STATIC_DRAW, // PIXEL_OFFSET_INDEX
BufferUsage.STATIC_DRAW, // EYE_OFFSET_INDEX
BufferUsage.STATIC_DRAW, // HORIZONTAL_ORIGIN_INDEX
BufferUsage.STATIC_DRAW, // VERTICAL_ORIGIN_INDEX
BufferUsage.STATIC_DRAW, // SCALE_INDEX
BufferUsage.STATIC_DRAW, // IMAGE_INDEX_INDEX
BufferUsage.STATIC_DRAW, // COLOR_INDEX
BufferUsage.STATIC_DRAW, // ROTATION_INDEX
BufferUsage.STATIC_DRAW, // ALIGNED_AXIS_INDEX
BufferUsage.STATIC_DRAW, // SCALE_BY_DISTANCE_INDEX
BufferUsage.STATIC_DRAW, // TRANSLUCENCY_BY_DISTANCE_INDEX
BufferUsage.STATIC_DRAW, // PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX
BufferUsage.STATIC_DRAW, // DISTANCE_DISPLAY_CONDITION_INDEX
BufferUsage.STATIC_DRAW // TEXTURE_COORDINATE_BOUNDS
];
this._highlightColor = Color.clone(Color.WHITE); // Only used by Vector3DTilePoints
var that = this;
this._uniforms = {
u_atlas : function() {
return that._textureAtlas.texture;
},
u_highlightColor : function() {
return that._highlightColor;
}
};
var scene = this._scene;
if (defined(scene) && defined(scene.terrainProviderChanged)) {
this._removeCallbackFunc = scene.terrainProviderChanged.addEventListener(function() {
var billboards = this._billboards;
var length = billboards.length;
for (var i = 0; i < length; ++i) {
billboards[i]._updateClamping();
}
}, this);
}
}
Object.defineProperties(BillboardCollection.prototype, {
/**
* Returns the number of billboards in this collection. This is commonly used with
* {@link BillboardCollection#get} to iterate over all the billboards
* in the collection.
* @memberof BillboardCollection.prototype
* @type {Number}
*/
length : {
get : function() {
removeBillboards(this);
return this._billboards.length;
}
},
/**
* Gets or sets the textureAtlas.
* @memberof BillboardCollection.prototype
* @type {TextureAtlas}
* @private
*/
textureAtlas : {
get : function() {
return this._textureAtlas;
},
set : function(value) {
if (this._textureAtlas !== value) {
this._textureAtlas = this._destroyTextureAtlas && this._textureAtlas && this._textureAtlas.destroy();
this._textureAtlas = value;
this._createVertexArray = true; // New per-billboard texture coordinates
}
}
},
/**
* Gets or sets a value which determines if the texture atlas is
* destroyed when the collection is destroyed.
*
* If the texture atlas is used by more than one collection, set this to <code>false</code>,
* and explicitly destroy the atlas to avoid attempting to destroy it multiple times.
*
* @memberof BillboardCollection.prototype
* @type {Boolean}
* @private
*
* @example
* // Set destroyTextureAtlas
* // Destroy a billboard collection but not its texture atlas.
*
* var atlas = new TextureAtlas({
* scene : scene,
* images : images
* });
* billboards.textureAtlas = atlas;
* billboards.destroyTextureAtlas = false;
* billboards = billboards.destroy();
* console.log(atlas.isDestroyed()); // False
*/
destroyTextureAtlas : {
get : function() {
return this._destroyTextureAtlas;
},
set : function(value) {
this._destroyTextureAtlas = value;
}
}
});
function destroyBillboards(billboards) {
var length = billboards.length;
for (var i = 0; i < length; ++i) {
if (billboards[i]) {
billboards[i]._destroy();
}
}
}
/**
* Creates and adds a billboard with the specified initial properties to the collection.
* The added billboard is returned so it can be modified or removed from the collection later.
*
* @param {Object}[options] A template describing the billboard's properties as shown in Example 1.
* @returns {Billboard} The billboard that was added to the collection.
*
* @performance Calling <code>add</code> is expected constant time. However, the collection's vertex buffer
* is rewritten - an <code>O(n)</code> operation that also incurs CPU to GPU overhead. For
* best performance, add as many billboards as possible before calling <code>update</code>.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* // Example 1: Add a billboard, specifying all the default values.
* var b = billboards.add({
* show : true,
* position : Cesium.Cartesian3.ZERO,
* pixelOffset : Cesium.Cartesian2.ZERO,
* eyeOffset : Cesium.Cartesian3.ZERO,
* heightReference : Cesium.HeightReference.NONE,
* horizontalOrigin : Cesium.HorizontalOrigin.CENTER,
* verticalOrigin : Cesium.VerticalOrigin.CENTER,
* scale : 1.0,
* image : 'url/to/image',
* imageSubRegion : undefined,
* color : Cesium.Color.WHITE,
* id : undefined,
* rotation : 0.0,
* alignedAxis : Cesium.Cartesian3.ZERO,
* width : undefined,
* height : undefined,
* scaleByDistance : undefined,
* translucencyByDistance : undefined,
* pixelOffsetScaleByDistance : undefined,
* sizeInMeters : false,
* distanceDisplayCondition : undefined
* });
*
* @example
* // Example 2: Specify only the billboard's cartographic position.
* var b = billboards.add({
* position : Cesium.Cartesian3.fromDegrees(longitude, latitude, height)
* });
*
* @see BillboardCollection#remove
* @see BillboardCollection#removeAll
*/
BillboardCollection.prototype.add = function(options) {
var b = new Billboard(options, this);
b._index = this._billboards.length;
this._billboards.push(b);
this._createVertexArray = true;
return b;
};
/**
* Removes a billboard from the collection.
*
* @param {Billboard} billboard The billboard to remove.
* @returns {Boolean} <code>true</code> if the billboard was removed; <code>false</code> if the billboard was not found in the collection.
*
* @performance Calling <code>remove</code> is expected constant time. However, the collection's vertex buffer
* is rewritten - an <code>O(n)</code> operation that also incurs CPU to GPU overhead. For
* best performance, remove as many billboards as possible before calling <code>update</code>.
* If you intend to temporarily hide a billboard, it is usually more efficient to call
* {@link Billboard#show} instead of removing and re-adding the billboard.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* var b = billboards.add(...);
* billboards.remove(b); // Returns true
*
* @see BillboardCollection#add
* @see BillboardCollection#removeAll
* @see Billboard#show
*/
BillboardCollection.prototype.remove = function(billboard) {
if (this.contains(billboard)) {
this._billboards[billboard._index] = null; // Removed later
this._billboardsRemoved = true;
this._createVertexArray = true;
billboard._destroy();
return true;
}
return false;
};
/**
* Removes all billboards from the collection.
*
* @performance <code>O(n)</code>. It is more efficient to remove all the billboards
* from a collection and then add new ones than to create a new collection entirely.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* billboards.add(...);
* billboards.add(...);
* billboards.removeAll();
*
* @see BillboardCollection#add
* @see BillboardCollection#remove
*/
BillboardCollection.prototype.removeAll = function() {
destroyBillboards(this._billboards);
this._billboards = [];
this._billboardsToUpdate = [];
this._billboardsToUpdateIndex = 0;
this._billboardsRemoved = false;
this._createVertexArray = true;
};
function removeBillboards(billboardCollection) {
if (billboardCollection._billboardsRemoved) {
billboardCollection._billboardsRemoved = false;
var newBillboards = [];
var billboards = billboardCollection._billboards;
var length = billboards.length;
for (var i = 0, j = 0; i < length; ++i) {
var billboard = billboards[i];
if (billboard) {
billboard._index = j++;
newBillboards.push(billboard);
}
}
billboardCollection._billboards = newBillboards;
}
}
BillboardCollection.prototype._updateBillboard = function(billboard, propertyChanged) {
if (!billboard._dirty) {
this._billboardsToUpdate[this._billboardsToUpdateIndex++] = billboard;
}
++this._propertiesChanged[propertyChanged];
};
/**
* Check whether this collection contains a given billboard.
*
* @param {Billboard} [billboard] The billboard to check for.
* @returns {Boolean} true if this collection contains the billboard, false otherwise.
*
* @see BillboardCollection#get
*/
BillboardCollection.prototype.contains = function(billboard) {
return defined(billboard) && billboard._billboardCollection === this;
};
/**
* Returns the billboard in the collection at the specified index. Indices are zero-based
* and increase as billboards are added. Removing a billboard shifts all billboards after
* it to the left, changing their indices. This function is commonly used with
* {@link BillboardCollection#length} to iterate over all the billboards
* in the collection.
*
* @param {Number} index The zero-based index of the billboard.
* @returns {Billboard} The billboard at the specified index.
*
* @performance Expected constant time. If billboards were removed from the collection and
* {@link BillboardCollection#update} was not called, an implicit <code>O(n)</code>
* operation is performed.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* // Toggle the show property of every billboard in the collection
* var len = billboards.length;
* for (var i = 0; i < len; ++i) {
* var b = billboards.get(i);
* b.show = !b.show;
* }
*
* @see BillboardCollection#length
*/
BillboardCollection.prototype.get = function(index) {
//>>includeStart('debug', pragmas.debug);
if (!defined(index)) {
throw new DeveloperError('index is required.');
}
//>>includeEnd('debug');
removeBillboards(this);
return this._billboards[index];
};
var getIndexBuffer;
function getIndexBufferBatched(context) {
var sixteenK = 16 * 1024;
var indexBuffer = context.cache.billboardCollection_indexBufferBatched;
if (defined(indexBuffer)) {
return indexBuffer;
}
// Subtract 6 because the last index is reserverd for primitive restart.
// https://www.khronos.org/registry/webgl/specs/latest/2.0/#5.18
var length = sixteenK * 6 - 6;
var indices = new Uint16Array(length);
for (var i = 0, j = 0; i < length; i += 6, j += 4) {
indices[i] = j;
indices[i + 1] = j + 1;
indices[i + 2] = j + 2;
indices[i + 3] = j + 0;
indices[i + 4] = j + 2;
indices[i + 5] = j + 3;
}
// PERFORMANCE_IDEA: Should we reference count billboard collections, and eventually delete this?
// Is this too much memory to allocate up front? Should we dynamically grow it?
indexBuffer = Buffer.createIndexBuffer({
context : context,
typedArray : indices,
usage : BufferUsage.STATIC_DRAW,
indexDatatype : IndexDatatype.UNSIGNED_SHORT
});
indexBuffer.vertexArrayDestroyable = false;
context.cache.billboardCollection_indexBufferBatched = indexBuffer;
return indexBuffer;
}
function getIndexBufferInstanced(context) {
var indexBuffer = context.cache.billboardCollection_indexBufferInstanced;
if (defined(indexBuffer)) {
return indexBuffer;
}
indexBuffer = Buffer.createIndexBuffer({
context : context,
typedArray : new Uint16Array([0, 1, 2, 0, 2, 3]),
usage : BufferUsage.STATIC_DRAW,
indexDatatype : IndexDatatype.UNSIGNED_SHORT
});
indexBuffer.vertexArrayDestroyable = false;
context.cache.billboardCollection_indexBufferInstanced = indexBuffer;
return indexBuffer;
}
function getVertexBufferInstanced(context) {
var vertexBuffer = context.cache.billboardCollection_vertexBufferInstanced;
if (defined(vertexBuffer)) {
return vertexBuffer;
}
vertexBuffer = Buffer.createVertexBuffer({
context : context,
typedArray : new Float32Array([0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0]),
usage : BufferUsage.STATIC_DRAW
});
vertexBuffer.vertexArrayDestroyable = false;
context.cache.billboardCollection_vertexBufferInstanced = vertexBuffer;
return vertexBuffer;
}
BillboardCollection.prototype.computeNewBuffersUsage = function() {
var buffersUsage = this._buffersUsage;
var usageChanged = false;
var properties = this._propertiesChanged;
for (var k = 0; k < NUMBER_OF_PROPERTIES; ++k) {
var newUsage = (properties[k] === 0) ? BufferUsage.STATIC_DRAW : BufferUsage.STREAM_DRAW;
usageChanged = usageChanged || (buffersUsage[k] !== newUsage);
buffersUsage[k] = newUsage;
}
return usageChanged;
};
function createVAF(context, numberOfBillboards, buffersUsage, instanced, batchTable, sdf) {
var attributes = [{
index : attributeLocations.positionHighAndScale,
componentsPerAttribute : 4,
componentDatatype : ComponentDatatype.FLOAT,
usage : buffersUsage[POSITION_INDEX]
}, {
index : attributeLocations.positionLowAndRotation,
componentsPerAttribute : 4,
componentDatatype : ComponentDatatype.FLOAT,
usage : buffersUsage[POSITION_INDEX]
}, {
index : attributeLocations.compressedAttribute0,
componentsPerAttribute : 4,
componentDatatype : ComponentDatatype.FLOAT,
usage : buffersUsage[PIXEL_OFFSET_INDEX]
}, {
index : attributeLocations.compressedAttribute1,
componentsPerAttribute : 4,
componentDatatype : ComponentDatatype.FLOAT,
usage : buffersUsage[TRANSLUCENCY_BY_DISTANCE_INDEX]
}, {
index : attributeLocations.compressedAttribute2,
componentsPerAttribute : 4,
componentDatatype : ComponentDatatype.FLOAT,
usage : buffersUsage[COLOR_INDEX]
}, {
index : attributeLocations.eyeOffset,
componentsPerAttribute : 4,
componentDatatype : ComponentDatatype.FLOAT,
usage : buffersUsage[EYE_OFFSET_INDEX]
}, {
index : attributeLocations.scaleByDistance,
componentsPerAttribute : 4,
componentDatatype : ComponentDatatype.FLOAT,
usage : buffersUsage[SCALE_BY_DISTANCE_INDEX]
}, {
index : attributeLocations.pixelOffsetScaleByDistance,
componentsPerAttribute : 4,
componentDatatype : ComponentDatatype.FLOAT,
usage : buffersUsage[PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX]
}, {
index : attributeLocations.compressedAttribute3,
componentsPerAttribute : 4,
componentDatatype : ComponentDatatype.FLOAT,
usage : buffersUsage[DISTANCE_DISPLAY_CONDITION_INDEX]
}, {
index : attributeLocations.textureCoordinateBoundsOrLabelTranslate,
componentsPerAttribute : 4,
componentDatatype : ComponentDatatype.FLOAT,
usage : buffersUsage[TEXTURE_COORDINATE_BOUNDS]
}];
// Instancing requires one non-instanced attribute.
if (instanced) {
attributes.push({
index : attributeLocations.direction,
componentsPerAttribute : 2,
componentDatatype : ComponentDatatype.FLOAT,
vertexBuffer : getVertexBufferInstanced(context)
});
}
if (defined(batchTable)) {
attributes.push({
index : attributeLocations.a_batchId,
componentsPerAttribute : 1,
componentDatatyps : ComponentDatatype.FLOAT,
bufferUsage : BufferUsage.STATIC_DRAW
});
}
if (sdf) {
attributes.push({
index : attributeLocations.sdf,
componentsPerAttribute : 2,
componentDatatype : ComponentDatatype.FLOAT,
usage : buffersUsage[SDF_INDEX]
});
}
// When instancing is enabled, only one vertex is needed for each billboard.
var sizeInVertices = instanced ? numberOfBillboards : 4 * numberOfBillboards;
return new VertexArrayFacade(context, attributes, sizeInVertices, instanced);
}
///////////////////////////////////////////////////////////////////////////
// Four vertices per billboard. Each has the same position, etc., but a different screen-space direction vector.
// PERFORMANCE_IDEA: Save memory if a property is the same for all billboards, use a latched attribute state,
// instead of storing it in a vertex buffer.
var writePositionScratch = new EncodedCartesian3();
function writePositionScaleAndRotation(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) {
var i;
var positionHighWriter = vafWriters[attributeLocations.positionHighAndScale];
var positionLowWriter = vafWriters[attributeLocations.positionLowAndRotation];
var position = billboard._getActualPosition();
if (billboardCollection._mode === SceneMode.SCENE3D) {
BoundingSphere.expand(billboardCollection._baseVolume, position, billboardCollection._baseVolume);
billboardCollection._boundingVolumeDirty = true;
}
EncodedCartesian3.fromCartesian(position, writePositionScratch);
var scale = billboard.scale;
var rotation = billboard.rotation;
if (rotation !== 0.0) {
billboardCollection._shaderRotation = true;
}
billboardCollection._maxScale = Math.max(billboardCollection._maxScale, scale);
var high = writePositionScratch.high;
var low = writePositionScratch.low;
if (billboardCollection._instanced) {
i = billboard._index;
positionHighWriter(i, high.x, high.y, high.z, scale);
positionLowWriter(i, low.x, low.y, low.z, rotation);
} else {
i = billboard._index * 4;
positionHighWriter(i + 0, high.x, high.y, high.z, scale);
positionHighWriter(i + 1, high.x, high.y, high.z, scale);
positionHighWriter(i + 2, high.x, high.y, high.z, scale);
positionHighWriter(i + 3, high.x, high.y, high.z, scale);
positionLowWriter(i + 0, low.x, low.y, low.z, rotation);
positionLowWriter(i + 1, low.x, low.y, low.z, rotation);
positionLowWriter(i + 2, low.x, low.y, low.z, rotation);
positionLowWriter(i + 3, low.x, low.y, low.z, rotation);
}
}
var scratchCartesian2 = new Cartesian2();
var UPPER_BOUND = 32768.0; // 2^15
var LEFT_SHIFT16 = 65536.0; // 2^16
var LEFT_SHIFT12 = 4096.0; // 2^12
var LEFT_SHIFT8 = 256.0; // 2^8
var LEFT_SHIFT7 = 128.0;
var LEFT_SHIFT5 = 32.0;
var LEFT_SHIFT3 = 8.0;
var LEFT_SHIFT2 = 4.0;
var RIGHT_SHIFT8 = 1.0 / 256.0;
var LOWER_LEFT = 0.0;
var LOWER_RIGHT = 2.0;
var UPPER_RIGHT = 3.0;
var UPPER_LEFT = 1.0;
function writeCompressedAttrib0(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) {
var i;
var writer = vafWriters[attributeLocations.compressedAttribute0];
var pixelOffset = billboard.pixelOffset;
var pixelOffsetX = pixelOffset.x;
var pixelOffsetY = pixelOffset.y;
var translate = billboard._translate;
var translateX = translate.x;
var translateY = translate.y;
billboardCollection._maxPixelOffset = Math.max(billboardCollection._maxPixelOffset, Math.abs(pixelOffsetX + translateX), Math.abs(-pixelOffsetY + translateY));
var horizontalOrigin = billboard.horizontalOrigin;
var verticalOrigin = billboard._verticalOrigin;
var show = billboard.show && billboard.clusterShow;
// If the color alpha is zero, do not show this billboard. This lets us avoid providing
// color during the pick pass and also eliminates a discard in the fragment shader.
if (billboard.color.alpha === 0.0) {
show = false;
}
// Raw billboards don't distinguish between BASELINE and BOTTOM, only LabelCollection does that.
if (verticalOrigin === VerticalOrigin.BASELINE) {
verticalOrigin = VerticalOrigin.BOTTOM;
}
billboardCollection._allHorizontalCenter = billboardCollection._allHorizontalCenter && horizontalOrigin === HorizontalOrigin.CENTER;
billboardCollection._allVerticalCenter = billboardCollection._allVerticalCenter && verticalOrigin === VerticalOrigin.CENTER;
var bottomLeftX = 0;
var bottomLeftY = 0;
var width = 0;
var height = 0;
var index = billboard._imageIndex;
if (index !== -1) {
var imageRectangle = textureAtlasCoordinates[index];
//>>includeStart('debug', pragmas.debug);
if (!defined(imageRectangle)) {
throw new DeveloperError('Invalid billboard image index: ' + index);
}
//>>includeEnd('debug');
bottomLeftX = imageRectangle.x;
bottomLeftY = imageRectangle.y;
width = imageRectangle.width;
height = imageRectangle.height;
}
var topRightX = bottomLeftX + width;
var topRightY = bottomLeftY + height;
var compressed0 = Math.floor(CesiumMath.clamp(pixelOffsetX, -UPPER_BOUND, UPPER_BOUND) + UPPER_BOUND) * LEFT_SHIFT7;
compressed0 += (horizontalOrigin + 1.0) * LEFT_SHIFT5;
compressed0 += (verticalOrigin + 1.0) * LEFT_SHIFT3;
compressed0 += (show ? 1.0 : 0.0) * LEFT_SHIFT2;
var compressed1 = Math.floor(CesiumMath.clamp(pixelOffsetY, -UPPER_BOUND, UPPER_BOUND) + UPPER_BOUND) * LEFT_SHIFT8;
var compressed2 = Math.floor(CesiumMath.clamp(translateX, -UPPER_BOUND, UPPER_BOUND) + UPPER_BOUND) * LEFT_SHIFT8;
var tempTanslateY = (CesiumMath.clamp(translateY, -UPPER_BOUND, UPPER_BOUND) + UPPER_BOUND) * RIGHT_SHIFT8;
var upperTranslateY = Math.floor(tempTanslateY);
var lowerTranslateY = Math.floor((tempTanslateY - upperTranslateY) * LEFT_SHIFT8);
compressed1 += upperTranslateY;
compressed2 += lowerTranslateY;
scratchCartesian2.x = bottomLeftX;
scratchCartesian2.y = bottomLeftY;
var compressedTexCoordsLL = AttributeCompression.compressTextureCoordinates(scratchCartesian2);
scratchCartesian2.x = topRightX;
var compressedTexCoordsLR = AttributeCompression.compressTextureCoordinates(scratchCartesian2);
scratchCartesian2.y = topRightY;
var compressedTexCoordsUR = AttributeCompression.compressTextureCoordinates(scratchCartesian2);
scratchCartesian2.x = bottomLeftX;
var compressedTexCoordsUL = AttributeCompression.compressTextureCoordinates(scratchCartesian2);
if (billboardCollection._instanced) {
i = billboard._index;
writer(i, compressed0, compressed1, compressed2, compressedTexCoordsLL);
} else {
i = billboard._index * 4;
writer(i + 0, compressed0 + LOWER_LEFT, compressed1, compressed2, compressedTexCoordsLL);
writer(i + 1, compressed0 + LOWER_RIGHT, compressed1, compressed2, compressedTexCoordsLR);
writer(i + 2, compressed0 + UPPER_RIGHT, compressed1, compressed2, compressedTexCoordsUR);
writer(i + 3, compressed0 + UPPER_LEFT, compressed1, compressed2, compressedTexCoordsUL);
}
}
function writeCompressedAttrib1(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) {
var i;
var writer = vafWriters[attributeLocations.compressedAttribute1];
var alignedAxis = billboard.alignedAxis;
if (!Cartesian3.equals(alignedAxis, Cartesian3.ZERO)) {
billboardCollection._shaderAlignedAxis = true;
}
var near = 0.0;
var nearValue = 1.0;
var far = 1.0;
var farValue = 1.0;
var translucency = billboard.translucencyByDistance;
if (defined(translucency)) {
near = translucency.near;
nearValue = translucency.nearValue;
far = translucency.far;
farValue = translucency.farValue;
if (nearValue !== 1.0 || farValue !== 1.0) {
// translucency by distance calculation in shader need not be enabled
// until a billboard with near and far !== 1.0 is found
billboardCollection._shaderTranslucencyByDistance = true;
}
}
var width = 0;
var index = billboard._imageIndex;
if (index !== -1) {
var imageRectangle = textureAtlasCoordinates[index];
//>>includeStart('debug', pragmas.debug);
if (!defined(imageRectangle)) {
throw new DeveloperError('Invalid billboard image index: ' + index);
}
//>>includeEnd('debug');
width = imageRectangle.width;
}
var textureWidth = billboardCollection._textureAtlas.texture.width;
var imageWidth = Math.round(defaultValue(billboard.width, textureWidth * width));
billboardCollection._maxSize = Math.max(billboardCollection._maxSize, imageWidth);
var compressed0 = CesiumMath.clamp(imageWidth, 0.0, LEFT_SHIFT16);
var compressed1 = 0.0;
if (Math.abs(Cartesian3.magnitudeSquared(alignedAxis) - 1.0) < CesiumMath.EPSILON6) {
compressed1 = AttributeCompression.octEncodeFloat(alignedAxis);
}
nearValue = CesiumMath.clamp(nearValue, 0.0, 1.0);
nearValue = nearValue === 1.0 ? 255.0 : (nearValue * 255.0) | 0;
compressed0 = compressed0 * LEFT_SHIFT8 + nearValue;
farValue = CesiumMath.clamp(farValue, 0.0, 1.0);
farValue = farValue === 1.0 ? 255.0 : (farValue * 255.0) | 0;
compressed1 = compressed1 * LEFT_SHIFT8 + farValue;
if (billboardCollection._instanced) {
i = billboard._index;
writer(i, compressed0, compressed1, near, far);
} else {
i = billboard._index * 4;
writer(i + 0, compressed0, compressed1, near, far);
writer(i + 1, compressed0, compressed1, near, far);
writer(i + 2, compressed0, compressed1, near, far);
writer(i + 3, compressed0, compressed1, near, far);
}
}
function writeCompressedAttrib2(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) {
var i;
var writer = vafWriters[attributeLocations.compressedAttribute2];
var color = billboard.color;
var pickColor = !defined(billboardCollection._batchTable) ? billboard.getPickId(context).color : Color.WHITE;
var sizeInMeters = billboard.sizeInMeters ? 1.0 : 0.0;
var validAlignedAxis = Math.abs(Cartesian3.magnitudeSquared(billboard.alignedAxis) - 1.0) < CesiumMath.EPSILON6 ? 1.0 : 0.0;
billboardCollection._allSizedInMeters = billboardCollection._allSizedInMeters && sizeInMeters === 1.0;
var height = 0;
var index = billboard._imageIndex;
if (index !== -1) {
var imageRectangle = textureAtlasCoordinates[index];
//>>includeStart('debug', pragmas.debug);
if (!defined(imageRectangle)) {
throw new DeveloperError('Invalid billboard image index: ' + index);
}
//>>includeEnd('debug');
height = imageRectangle.height;
}
var dimensions = billboardCollection._textureAtlas.texture.dimensions;
var imageHeight = Math.round(defaultValue(billboard.height, dimensions.y * height));
billboardCollection._maxSize = Math.max(billboardCollection._maxSize, imageHeight);
var labelHorizontalOrigin = defaultValue(billboard._labelHorizontalOrigin, -2);
labelHorizontalOrigin += 2;
var compressed3 = imageHeight * LEFT_SHIFT2 + labelHorizontalOrigin;
var red = Color.floatToByte(color.red);
var green = Color.floatToByte(color.green);
var blue = Color.floatToByte(color.blue);
var compressed0 = red * LEFT_SHIFT16 + green * LEFT_SHIFT8 + blue;
red = Color.floatToByte(pickColor.red);
green = Color.floatToByte(pickColor.green);
blue = Color.floatToByte(pickColor.blue);
var compressed1 = red * LEFT_SHIFT16 + green * LEFT_SHIFT8 + blue;
var compressed2 = Color.floatToByte(color.alpha) * LEFT_SHIFT16 + Color.floatToByte(pickColor.alpha) * LEFT_SHIFT8;
compressed2 += sizeInMeters * 2.0 + validAlignedAxis;
if (billboardCollection._instanced) {
i = billboard._index;
writer(i, compressed0, compressed1, compressed2, compressed3);
} else {
i = billboard._index * 4;
writer(i + 0, compressed0, compressed1, compressed2, compressed3);
writer(i + 1, compressed0, compressed1, compressed2, compressed3);
writer(i + 2, compressed0, compressed1, compressed2, compressed3);
writer(i + 3, compressed0, compressed1, compressed2, compressed3);
}
}
function writeEyeOffset(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) {
var i;
var writer = vafWriters[attributeLocations.eyeOffset];
var eyeOffset = billboard.eyeOffset;
// For billboards that are clamped to ground, move it slightly closer to the camera
var eyeOffsetZ = eyeOffset.z;
if (billboard._heightReference !== HeightReference.NONE) {
eyeOffsetZ *= 1.005;
}
billboardCollection._maxEyeOffset = Math.max(billboardCollection._maxEyeOffset, Math.abs(eyeOffset.x), Math.abs(eyeOffset.y), Math.abs(eyeOffsetZ));
if (billboardCollection._instanced) {
var width = 0;
var height = 0;
var index = billboard._imageIndex;
if (index !== -1) {
var imageRectangle = textureAtlasCoordinates[index];
//>>includeStart('debug', pragmas.debug);
if (!defined(imageRectangle)) {
throw new DeveloperError('Invalid billboard image index: ' + index);
}
//>>includeEnd('debug');
width = imageRectangle.width;
height = imageRectangle.height;
}
scratchCartesian2.x = width;
scratchCartesian2.y = height;
var compressedTexCoordsRange = AttributeCompression.compressTextureCoordinates(scratchCartesian2);
i = billboard._index;
writer(i, eyeOffset.x, eyeOffset.y, eyeOffsetZ, compressedTexCoordsRange);
} else {
i = billboard._index * 4;
writer(i + 0, eyeOffset.x, eyeOffset.y, eyeOffsetZ, 0.0);
writer(i + 1, eyeOffset.x, eyeOffset.y, eyeOffsetZ, 0.0);
writer(i + 2, eyeOffset.x, eyeOffset.y, eyeOffsetZ, 0.0);
writer(i + 3, eyeOffset.x, eyeOffset.y, eyeOffsetZ, 0.0);
}
}
function writeScaleByDistance(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) {
var i;
var writer = vafWriters[attributeLocations.scaleByDistance];
var near = 0.0;
var nearValue = 1.0;
var far = 1.0;
var farValue = 1.0;
var scale = billboard.scaleByDistance;
if (defined(scale)) {
near = scale.near;
nearValue = scale.nearValue;
far = scale.far;
farValue = scale.farValue;
if (nearValue !== 1.0 || farValue !== 1.0) {
// scale by distance calculation in shader need not be enabled
// until a billboard with near and far !== 1.0 is found
billboardCollection._shaderScaleByDistance = true;
}
}
if (billboardCollection._instanced) {
i = billboard._index;
writer(i, near, nearValue, far, farValue);
} else {
i = billboard._index * 4;
writer(i + 0, near, nearValue, far, farValue);
writer(i + 1, near, nearValue, far, farValue);
writer(i + 2, near, nearValue, far, farValue);
writer(i + 3, near, nearValue, far, farValue);
}
}
function writePixelOffsetScaleByDistance(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) {
var i;
var writer = vafWriters[attributeLocations.pixelOffsetScaleByDistance];
var near = 0.0;
var nearValue = 1.0;
var far = 1.0;
var farValue = 1.0;
var pixelOffsetScale = billboard.pixelOffsetScaleByDistance;
if (defined(pixelOffsetScale)) {
near = pixelOffsetScale.near;
nearValue = pixelOffsetScale.nearValue;
far = pixelOffsetScale.far;
farValue = pixelOffsetScale.farValue;
if (nearValue !== 1.0 || farValue !== 1.0) {
// pixelOffsetScale by distance calculation in shader need not be enabled
// until a billboard with near and far !== 1.0 is found
billboardCollection._shaderPixelOffsetScaleByDistance = true;
}
}
if (billboardCollection._instanced) {
i = billboard._index;
writer(i, near, nearValue, far, farValue);
} else {
i = billboard._index * 4;
writer(i + 0, near, nearValue, far, farValue);
writer(i + 1, near, nearValue, far, farValue);
writer(i + 2, near, nearValue, far, farValue);
writer(i + 3, near, nearValue, far, farValue);
}
}
function writeCompressedAttribute3(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) {
var i;
var writer = vafWriters[attributeLocations.compressedAttribute3];
var near = 0.0;
var far = Number.MAX_VALUE;
var distanceDisplayCondition = billboard.distanceDisplayCondition;
if (defined(distanceDisplayCondition)) {
near = distanceDisplayCondition.near;
far = distanceDisplayCondition.far;
near *= near;
far *= far;
billboardCollection._shaderDistanceDisplayCondition = true;
}
var disableDepthTestDistance = billboard.disableDepthTestDistance;
var clampToGround = billboard.heightReference === HeightReference.CLAMP_TO_GROUND && billboardCollection._scene.context.depthTexture;
if (!defined(disableDepthTestDistance)) {
disableDepthTestDistance = clampToGround ? 5000.0 : 0.0;
}
disableDepthTestDistance *= disableDepthTestDistance;
if (clampToGround || disableDepthTestDistance > 0.0) {
billboardCollection._shaderDisableDepthDistance = true;
if (disableDepthTestDistance === Number.POSITIVE_INFINITY) {
disableDepthTestDistance = -1.0;
}
}
var imageHeight;
var imageWidth;
if (!defined(billboard._labelDimensions)) {
var height = 0;
var width = 0;
var index = billboard._imageIndex;
if (index !== -1) {