123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995 |
- import BoundingRectangle from "../Core/BoundingRectangle.js";
- import Cartesian2 from "../Core/Cartesian2.js";
- import Color from "../Core/Color.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 Matrix4 from "../Core/Matrix4.js";
- import writeTextToCanvas from "../Core/writeTextToCanvas.js";
- import bitmapSDF from "bitmap-sdf";
- import BillboardCollection from "./BillboardCollection.js";
- import BlendOption from "./BlendOption.js";
- import HeightReference from "./HeightReference.js";
- import HorizontalOrigin from "./HorizontalOrigin.js";
- import Label from "./Label.js";
- import LabelStyle from "./LabelStyle.js";
- import SDFSettings from "./SDFSettings.js";
- import TextureAtlas from "./TextureAtlas.js";
- import VerticalOrigin from "./VerticalOrigin.js";
- import GraphemeSplitter from "grapheme-splitter";
- // A glyph represents a single character in a particular label. It may or may
- // not have a billboard, depending on whether the texture info has an index into
- // the the label collection's texture atlas. Invisible characters have no texture, and
- // no billboard. However, it always has a valid dimensions object.
- function Glyph() {
- this.textureInfo = undefined;
- this.dimensions = undefined;
- this.billboard = undefined;
- }
- // GlyphTextureInfo represents a single character, drawn in a particular style,
- // shared and reference counted across all labels. It may or may not have an
- // index into the label collection's texture atlas, depending on whether the character
- // has both width and height, but it always has a valid dimensions object.
- function GlyphTextureInfo(labelCollection, index, dimensions) {
- this.labelCollection = labelCollection;
- this.index = index;
- this.dimensions = dimensions;
- }
- // Traditionally, leading is %20 of the font size.
- const defaultLineSpacingPercent = 1.2;
- const whitePixelCanvasId = "ID_WHITE_PIXEL";
- const whitePixelSize = new Cartesian2(4, 4);
- const whitePixelBoundingRegion = new BoundingRectangle(1, 1, 1, 1);
- function addWhitePixelCanvas(textureAtlas) {
- const canvas = document.createElement("canvas");
- canvas.width = whitePixelSize.x;
- canvas.height = whitePixelSize.y;
- const context2D = canvas.getContext("2d");
- context2D.fillStyle = "#fff";
- context2D.fillRect(0, 0, canvas.width, canvas.height);
- // Canvas operations take a frame to draw. Use the asynchronous add function which resolves a promise and allows the draw to complete,
- // but there's no need to wait on the promise before operation can continue
- textureAtlas.addImage(whitePixelCanvasId, canvas);
- }
- // reusable object for calling writeTextToCanvas
- const writeTextToCanvasParameters = {};
- function createGlyphCanvas(
- character,
- font,
- fillColor,
- outlineColor,
- outlineWidth,
- style,
- verticalOrigin
- ) {
- writeTextToCanvasParameters.font = font;
- writeTextToCanvasParameters.fillColor = fillColor;
- writeTextToCanvasParameters.strokeColor = outlineColor;
- writeTextToCanvasParameters.strokeWidth = outlineWidth;
- // Setting the padding to something bigger is necessary to get enough space for the outlining.
- writeTextToCanvasParameters.padding = SDFSettings.PADDING;
- if (verticalOrigin === VerticalOrigin.CENTER) {
- writeTextToCanvasParameters.textBaseline = "middle";
- } else if (verticalOrigin === VerticalOrigin.TOP) {
- writeTextToCanvasParameters.textBaseline = "top";
- } else {
- // VerticalOrigin.BOTTOM and VerticalOrigin.BASELINE
- writeTextToCanvasParameters.textBaseline = "bottom";
- }
- writeTextToCanvasParameters.fill =
- style === LabelStyle.FILL || style === LabelStyle.FILL_AND_OUTLINE;
- writeTextToCanvasParameters.stroke =
- style === LabelStyle.OUTLINE || style === LabelStyle.FILL_AND_OUTLINE;
- writeTextToCanvasParameters.backgroundColor = Color.BLACK;
- return writeTextToCanvas(character, writeTextToCanvasParameters);
- }
- function unbindGlyph(labelCollection, glyph) {
- glyph.textureInfo = undefined;
- glyph.dimensions = undefined;
- const billboard = glyph.billboard;
- if (defined(billboard)) {
- billboard.show = false;
- billboard.image = undefined;
- if (defined(billboard._removeCallbackFunc)) {
- billboard._removeCallbackFunc();
- billboard._removeCallbackFunc = undefined;
- }
- labelCollection._spareBillboards.push(billboard);
- glyph.billboard = undefined;
- }
- }
- function addGlyphToTextureAtlas(textureAtlas, id, canvas, glyphTextureInfo) {
- glyphTextureInfo.index = textureAtlas.addImageSync(id, canvas);
- }
- const splitter = new GraphemeSplitter();
- function rebindAllGlyphs(labelCollection, label) {
- const text = label._renderedText;
- const graphemes = splitter.splitGraphemes(text);
- const textLength = graphemes.length;
- const glyphs = label._glyphs;
- const glyphsLength = glyphs.length;
- let glyph;
- let glyphIndex;
- let textIndex;
- // Compute a font size scale relative to the sdf font generated size.
- label._relativeSize = label._fontSize / SDFSettings.FONT_SIZE;
- // if we have more glyphs than needed, unbind the extras.
- if (textLength < glyphsLength) {
- for (glyphIndex = textLength; glyphIndex < glyphsLength; ++glyphIndex) {
- unbindGlyph(labelCollection, glyphs[glyphIndex]);
- }
- }
- // presize glyphs to match the new text length
- glyphs.length = textLength;
- const showBackground =
- label._showBackground && text.split("\n").join("").length > 0;
- let backgroundBillboard = label._backgroundBillboard;
- const backgroundBillboardCollection =
- labelCollection._backgroundBillboardCollection;
- if (!showBackground) {
- if (defined(backgroundBillboard)) {
- backgroundBillboardCollection.remove(backgroundBillboard);
- label._backgroundBillboard = backgroundBillboard = undefined;
- }
- } else {
- if (!defined(backgroundBillboard)) {
- backgroundBillboard = backgroundBillboardCollection.add({
- collection: labelCollection,
- image: whitePixelCanvasId,
- imageSubRegion: whitePixelBoundingRegion,
- });
- label._backgroundBillboard = backgroundBillboard;
- }
- backgroundBillboard.color = label._backgroundColor;
- backgroundBillboard.show = label._show;
- backgroundBillboard.position = label._position;
- backgroundBillboard.eyeOffset = label._eyeOffset;
- backgroundBillboard.pixelOffset = label._pixelOffset;
- backgroundBillboard.horizontalOrigin = HorizontalOrigin.LEFT;
- backgroundBillboard.verticalOrigin = label._verticalOrigin;
- backgroundBillboard.heightReference = label._heightReference;
- backgroundBillboard.scale = label.totalScale;
- backgroundBillboard.pickPrimitive = label;
- backgroundBillboard.id = label._id;
- backgroundBillboard.translucencyByDistance = label._translucencyByDistance;
- backgroundBillboard.pixelOffsetScaleByDistance =
- label._pixelOffsetScaleByDistance;
- backgroundBillboard.scaleByDistance = label._scaleByDistance;
- backgroundBillboard.distanceDisplayCondition =
- label._distanceDisplayCondition;
- backgroundBillboard.disableDepthTestDistance =
- label._disableDepthTestDistance;
- }
- const glyphTextureCache = labelCollection._glyphTextureCache;
- // walk the text looking for new characters (creating new glyphs for each)
- // or changed characters (rebinding existing glyphs)
- for (textIndex = 0; textIndex < textLength; ++textIndex) {
- const character = graphemes[textIndex];
- const verticalOrigin = label._verticalOrigin;
- const id = JSON.stringify([
- character,
- label._fontFamily,
- label._fontStyle,
- label._fontWeight,
- +verticalOrigin,
- ]);
- let glyphTextureInfo = glyphTextureCache[id];
- if (!defined(glyphTextureInfo)) {
- const glyphFont = `${label._fontStyle} ${label._fontWeight} ${SDFSettings.FONT_SIZE}px ${label._fontFamily}`;
- const canvas = createGlyphCanvas(
- character,
- glyphFont,
- Color.WHITE,
- Color.WHITE,
- 0.0,
- LabelStyle.FILL,
- verticalOrigin
- );
- glyphTextureInfo = new GlyphTextureInfo(
- labelCollection,
- -1,
- canvas.dimensions
- );
- glyphTextureCache[id] = glyphTextureInfo;
- if (canvas.width > 0 && canvas.height > 0) {
- const sdfValues = bitmapSDF(canvas, {
- cutoff: SDFSettings.CUTOFF,
- radius: SDFSettings.RADIUS,
- });
- // Context is originally created in writeTextToCanvas()
- const ctx = canvas.getContext("2d");
- const canvasWidth = canvas.width;
- const canvasHeight = canvas.height;
- const imgData = ctx.getImageData(0, 0, canvasWidth, canvasHeight);
- for (let i = 0; i < canvasWidth; i++) {
- for (let j = 0; j < canvasHeight; j++) {
- const baseIndex = j * canvasWidth + i;
- const alpha = sdfValues[baseIndex] * 255;
- const imageIndex = baseIndex * 4;
- imgData.data[imageIndex + 0] = alpha;
- imgData.data[imageIndex + 1] = alpha;
- imgData.data[imageIndex + 2] = alpha;
- imgData.data[imageIndex + 3] = alpha;
- }
- }
- ctx.putImageData(imgData, 0, 0);
- if (character !== " ") {
- addGlyphToTextureAtlas(
- labelCollection._textureAtlas,
- id,
- canvas,
- glyphTextureInfo
- );
- }
- }
- }
- glyph = glyphs[textIndex];
- if (defined(glyph)) {
- // clean up leftover information from the previous glyph
- if (glyphTextureInfo.index === -1) {
- // no texture, and therefore no billboard, for this glyph.
- // so, completely unbind glyph.
- unbindGlyph(labelCollection, glyph);
- } else if (defined(glyph.textureInfo)) {
- // we have a texture and billboard. If we had one before, release
- // our reference to that texture info, but reuse the billboard.
- glyph.textureInfo = undefined;
- }
- } else {
- // create a glyph object
- glyph = new Glyph();
- glyphs[textIndex] = glyph;
- }
- glyph.textureInfo = glyphTextureInfo;
- glyph.dimensions = glyphTextureInfo.dimensions;
- // if we have a texture, configure the existing billboard, or obtain one
- if (glyphTextureInfo.index !== -1) {
- let billboard = glyph.billboard;
- const spareBillboards = labelCollection._spareBillboards;
- if (!defined(billboard)) {
- if (spareBillboards.length > 0) {
- billboard = spareBillboards.pop();
- } else {
- billboard = labelCollection._billboardCollection.add({
- collection: labelCollection,
- });
- billboard._labelDimensions = new Cartesian2();
- billboard._labelTranslate = new Cartesian2();
- }
- glyph.billboard = billboard;
- }
- billboard.show = label._show;
- billboard.position = label._position;
- billboard.eyeOffset = label._eyeOffset;
- billboard.pixelOffset = label._pixelOffset;
- billboard.horizontalOrigin = HorizontalOrigin.LEFT;
- billboard.verticalOrigin = label._verticalOrigin;
- billboard.heightReference = label._heightReference;
- billboard.scale = label.totalScale;
- billboard.pickPrimitive = label;
- billboard.id = label._id;
- billboard.image = id;
- billboard.translucencyByDistance = label._translucencyByDistance;
- billboard.pixelOffsetScaleByDistance = label._pixelOffsetScaleByDistance;
- billboard.scaleByDistance = label._scaleByDistance;
- billboard.distanceDisplayCondition = label._distanceDisplayCondition;
- billboard.disableDepthTestDistance = label._disableDepthTestDistance;
- billboard._batchIndex = label._batchIndex;
- billboard.outlineColor = label.outlineColor;
- if (label.style === LabelStyle.FILL_AND_OUTLINE) {
- billboard.color = label._fillColor;
- billboard.outlineWidth = label.outlineWidth;
- } else if (label.style === LabelStyle.FILL) {
- billboard.color = label._fillColor;
- billboard.outlineWidth = 0.0;
- } else if (label.style === LabelStyle.OUTLINE) {
- billboard.color = Color.TRANSPARENT;
- billboard.outlineWidth = label.outlineWidth;
- }
- }
- }
- // changing glyphs will cause the position of the
- // glyphs to change, since different characters have different widths
- label._repositionAllGlyphs = true;
- }
- function calculateWidthOffset(lineWidth, horizontalOrigin, backgroundPadding) {
- if (horizontalOrigin === HorizontalOrigin.CENTER) {
- return -lineWidth / 2;
- } else if (horizontalOrigin === HorizontalOrigin.RIGHT) {
- return -(lineWidth + backgroundPadding.x);
- }
- return backgroundPadding.x;
- }
- // reusable Cartesian2 instances
- const glyphPixelOffset = new Cartesian2();
- const scratchBackgroundPadding = new Cartesian2();
- function repositionAllGlyphs(label) {
- const glyphs = label._glyphs;
- const text = label._renderedText;
- let glyph;
- let dimensions;
- let lastLineWidth = 0;
- let maxLineWidth = 0;
- const lineWidths = [];
- let maxGlyphDescent = Number.NEGATIVE_INFINITY;
- let maxGlyphY = 0;
- let numberOfLines = 1;
- let glyphIndex;
- const glyphLength = glyphs.length;
- const backgroundBillboard = label._backgroundBillboard;
- const backgroundPadding = Cartesian2.clone(
- defined(backgroundBillboard) ? label._backgroundPadding : Cartesian2.ZERO,
- scratchBackgroundPadding
- );
- // We need to scale the background padding, which is specified in pixels by the inverse of the relative size so it is scaled properly.
- backgroundPadding.x /= label._relativeSize;
- backgroundPadding.y /= label._relativeSize;
- for (glyphIndex = 0; glyphIndex < glyphLength; ++glyphIndex) {
- if (text.charAt(glyphIndex) === "\n") {
- lineWidths.push(lastLineWidth);
- ++numberOfLines;
- lastLineWidth = 0;
- } else {
- glyph = glyphs[glyphIndex];
- dimensions = glyph.dimensions;
- maxGlyphY = Math.max(maxGlyphY, dimensions.height - dimensions.descent);
- maxGlyphDescent = Math.max(maxGlyphDescent, dimensions.descent);
- //Computing the line width must also account for the kerning that occurs between letters.
- lastLineWidth += dimensions.width - dimensions.minx;
- if (glyphIndex < glyphLength - 1) {
- lastLineWidth += glyphs[glyphIndex + 1].dimensions.minx;
- }
- maxLineWidth = Math.max(maxLineWidth, lastLineWidth);
- }
- }
- lineWidths.push(lastLineWidth);
- const maxLineHeight = maxGlyphY + maxGlyphDescent;
- const scale = label.totalScale;
- const horizontalOrigin = label._horizontalOrigin;
- const verticalOrigin = label._verticalOrigin;
- let lineIndex = 0;
- let lineWidth = lineWidths[lineIndex];
- let widthOffset = calculateWidthOffset(
- lineWidth,
- horizontalOrigin,
- backgroundPadding
- );
- const lineSpacing =
- (defined(label._lineHeight)
- ? label._lineHeight
- : defaultLineSpacingPercent * label._fontSize) / label._relativeSize;
- const otherLinesHeight = lineSpacing * (numberOfLines - 1);
- let totalLineWidth = maxLineWidth;
- let totalLineHeight = maxLineHeight + otherLinesHeight;
- if (defined(backgroundBillboard)) {
- totalLineWidth += backgroundPadding.x * 2;
- totalLineHeight += backgroundPadding.y * 2;
- backgroundBillboard._labelHorizontalOrigin = horizontalOrigin;
- }
- glyphPixelOffset.x = widthOffset * scale;
- glyphPixelOffset.y = 0;
- let firstCharOfLine = true;
- let lineOffsetY = 0;
- for (glyphIndex = 0; glyphIndex < glyphLength; ++glyphIndex) {
- if (text.charAt(glyphIndex) === "\n") {
- ++lineIndex;
- lineOffsetY += lineSpacing;
- lineWidth = lineWidths[lineIndex];
- widthOffset = calculateWidthOffset(
- lineWidth,
- horizontalOrigin,
- backgroundPadding
- );
- glyphPixelOffset.x = widthOffset * scale;
- firstCharOfLine = true;
- } else {
- glyph = glyphs[glyphIndex];
- dimensions = glyph.dimensions;
- if (verticalOrigin === VerticalOrigin.TOP) {
- glyphPixelOffset.y =
- dimensions.height - maxGlyphY - backgroundPadding.y;
- glyphPixelOffset.y += SDFSettings.PADDING;
- } else if (verticalOrigin === VerticalOrigin.CENTER) {
- glyphPixelOffset.y =
- (otherLinesHeight + dimensions.height - maxGlyphY) / 2;
- } else if (verticalOrigin === VerticalOrigin.BASELINE) {
- glyphPixelOffset.y = otherLinesHeight;
- glyphPixelOffset.y -= SDFSettings.PADDING;
- } else {
- // VerticalOrigin.BOTTOM
- glyphPixelOffset.y =
- otherLinesHeight + maxGlyphDescent + backgroundPadding.y;
- glyphPixelOffset.y -= SDFSettings.PADDING;
- }
- glyphPixelOffset.y =
- (glyphPixelOffset.y - dimensions.descent - lineOffsetY) * scale;
- // Handle any offsets for the first character of the line since the bounds might not be right on the bottom left pixel.
- if (firstCharOfLine) {
- glyphPixelOffset.x -= SDFSettings.PADDING * scale;
- firstCharOfLine = false;
- }
- if (defined(glyph.billboard)) {
- glyph.billboard._setTranslate(glyphPixelOffset);
- glyph.billboard._labelDimensions.x = totalLineWidth;
- glyph.billboard._labelDimensions.y = totalLineHeight;
- glyph.billboard._labelHorizontalOrigin = horizontalOrigin;
- }
- //Compute the next x offset taking into account the kerning performed
- //on both the current letter as well as the next letter to be drawn
- //as well as any applied scale.
- if (glyphIndex < glyphLength - 1) {
- const nextGlyph = glyphs[glyphIndex + 1];
- glyphPixelOffset.x +=
- (dimensions.width - dimensions.minx + nextGlyph.dimensions.minx) *
- scale;
- }
- }
- }
- if (defined(backgroundBillboard) && text.split("\n").join("").length > 0) {
- if (horizontalOrigin === HorizontalOrigin.CENTER) {
- widthOffset = -maxLineWidth / 2 - backgroundPadding.x;
- } else if (horizontalOrigin === HorizontalOrigin.RIGHT) {
- widthOffset = -(maxLineWidth + backgroundPadding.x * 2);
- } else {
- widthOffset = 0;
- }
- glyphPixelOffset.x = widthOffset * scale;
- if (verticalOrigin === VerticalOrigin.TOP) {
- glyphPixelOffset.y = maxLineHeight - maxGlyphY - maxGlyphDescent;
- } else if (verticalOrigin === VerticalOrigin.CENTER) {
- glyphPixelOffset.y = (maxLineHeight - maxGlyphY) / 2 - maxGlyphDescent;
- } else if (verticalOrigin === VerticalOrigin.BASELINE) {
- glyphPixelOffset.y = -backgroundPadding.y - maxGlyphDescent;
- } else {
- // VerticalOrigin.BOTTOM
- glyphPixelOffset.y = 0;
- }
- glyphPixelOffset.y = glyphPixelOffset.y * scale;
- backgroundBillboard.width = totalLineWidth;
- backgroundBillboard.height = totalLineHeight;
- backgroundBillboard._setTranslate(glyphPixelOffset);
- backgroundBillboard._labelTranslate = Cartesian2.clone(
- glyphPixelOffset,
- backgroundBillboard._labelTranslate
- );
- }
- if (label.heightReference === HeightReference.CLAMP_TO_GROUND) {
- for (glyphIndex = 0; glyphIndex < glyphLength; ++glyphIndex) {
- glyph = glyphs[glyphIndex];
- const billboard = glyph.billboard;
- if (defined(billboard)) {
- billboard._labelTranslate = Cartesian2.clone(
- glyphPixelOffset,
- billboard._labelTranslate
- );
- }
- }
- }
- }
- function destroyLabel(labelCollection, label) {
- const glyphs = label._glyphs;
- for (let i = 0, len = glyphs.length; i < len; ++i) {
- unbindGlyph(labelCollection, glyphs[i]);
- }
- if (defined(label._backgroundBillboard)) {
- labelCollection._backgroundBillboardCollection.remove(
- label._backgroundBillboard
- );
- label._backgroundBillboard = undefined;
- }
- label._labelCollection = undefined;
- if (defined(label._removeCallbackFunc)) {
- label._removeCallbackFunc();
- }
- destroyObject(label);
- }
- /**
- * A renderable collection of labels. Labels are viewport-aligned text positioned in the 3D scene.
- * Each label can have a different font, color, scale, etc.
- * <br /><br />
- * <div align='center'>
- * <img src='Images/Label.png' width='400' height='300' /><br />
- * Example labels
- * </div>
- * <br /><br />
- * Labels are added and removed from the collection using {@link LabelCollection#add}
- * and {@link LabelCollection#remove}.
- *
- * @alias LabelCollection
- * @constructor
- *
- * @param {object} [options] Object with the following properties:
- * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms each label 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 labels that use the height reference property or will be depth tested against the globe.
- * @param {BlendOption} [options.blendOption=BlendOption.OPAQUE_AND_TRANSLUCENT] The label blending option. The default
- * is used for rendering both opaque and translucent labels. However, if either all of the labels are completely opaque or all are completely translucent,
- * setting the technique to BlendOption.OPAQUE or BlendOption.TRANSLUCENT can improve performance by up to 2x.
- * @param {boolean} [options.show=true] Determines if the labels in the collection will be shown.
- *
- * @performance For best performance, prefer a few collections, each with many labels, to
- * many collections with only a few labels each. Avoid having collections where some
- * labels change every frame and others do not; instead, create one or more collections
- * for static labels, and one or more collections for dynamic labels.
- *
- * @see LabelCollection#add
- * @see LabelCollection#remove
- * @see Label
- * @see BillboardCollection
- *
- * @demo {@link https://sandcastle.cesium.com/index.html?src=Labels.html|Cesium Sandcastle Labels Demo}
- *
- * @example
- * // Create a label collection with two labels
- * const labels = scene.primitives.add(new Cesium.LabelCollection());
- * labels.add({
- * position : new Cesium.Cartesian3(1.0, 2.0, 3.0),
- * text : 'A label'
- * });
- * labels.add({
- * position : new Cesium.Cartesian3(4.0, 5.0, 6.0),
- * text : 'Another label'
- * });
- */
- function LabelCollection(options) {
- options = defaultValue(options, defaultValue.EMPTY_OBJECT);
- this._scene = options.scene;
- this._batchTable = options.batchTable;
- this._textureAtlas = undefined;
- this._backgroundTextureAtlas = undefined;
- this._backgroundBillboardCollection = new BillboardCollection({
- scene: this._scene,
- });
- this._backgroundBillboardCollection.destroyTextureAtlas = false;
- this._billboardCollection = new BillboardCollection({
- scene: this._scene,
- batchTable: this._batchTable,
- });
- this._billboardCollection.destroyTextureAtlas = false;
- this._billboardCollection._sdf = true;
- this._spareBillboards = [];
- this._glyphTextureCache = {};
- this._labels = [];
- this._labelsToUpdate = [];
- this._totalGlyphCount = 0;
- this._highlightColor = Color.clone(Color.WHITE); // Only used by Vector3DTilePoints
- /**
- * Determines if labels in this collection will be shown.
- *
- * @type {boolean}
- * @default true
- */
- this.show = defaultValue(options.show, true);
- /**
- * The 4x4 transformation matrix that transforms each label in this collection from model to world coordinates.
- * When this is the identity matrix, the labels 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
- * const center = Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883);
- * labels.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(center);
- * labels.add({
- * position : new Cesium.Cartesian3(0.0, 0.0, 0.0),
- * text : 'Center'
- * });
- * labels.add({
- * position : new Cesium.Cartesian3(1000000.0, 0.0, 0.0),
- * text : 'East'
- * });
- * labels.add({
- * position : new Cesium.Cartesian3(0.0, 1000000.0, 0.0),
- * text : 'North'
- * });
- * labels.add({
- * position : new Cesium.Cartesian3(0.0, 0.0, 1000000.0),
- * text : 'Up'
- * });
- */
- this.modelMatrix = Matrix4.clone(
- defaultValue(options.modelMatrix, 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
- );
- /**
- * The label blending option. The default is used for rendering both opaque and translucent labels.
- * However, if either all of the labels 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
- );
- }
- Object.defineProperties(LabelCollection.prototype, {
- /**
- * Returns the number of labels in this collection. This is commonly used with
- * {@link LabelCollection#get} to iterate over all the labels
- * in the collection.
- * @memberof LabelCollection.prototype
- * @type {number}
- */
- length: {
- get: function () {
- return this._labels.length;
- },
- },
- });
- /**
- * Creates and adds a label with the specified initial properties to the collection.
- * The added label is returned so it can be modified or removed from the collection later.
- *
- * @param {object} [options] A template describing the label's properties as shown in Example 1.
- * @returns {Label} The label that was added to the collection.
- *
- * @performance Calling <code>add</code> is expected constant time. However, the collection's vertex buffer
- * is rewritten; this operations is <code>O(n)</code> and 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 label, specifying all the default values.
- * const l = labels.add({
- * show : true,
- * position : Cesium.Cartesian3.ZERO,
- * text : '',
- * font : '30px sans-serif',
- * fillColor : Cesium.Color.WHITE,
- * outlineColor : Cesium.Color.BLACK,
- * outlineWidth : 1.0,
- * showBackground : false,
- * backgroundColor : new Cesium.Color(0.165, 0.165, 0.165, 0.8),
- * backgroundPadding : new Cesium.Cartesian2(7, 5),
- * style : Cesium.LabelStyle.FILL,
- * pixelOffset : Cesium.Cartesian2.ZERO,
- * eyeOffset : Cesium.Cartesian3.ZERO,
- * horizontalOrigin : Cesium.HorizontalOrigin.LEFT,
- * verticalOrigin : Cesium.VerticalOrigin.BASELINE,
- * scale : 1.0,
- * translucencyByDistance : undefined,
- * pixelOffsetScaleByDistance : undefined,
- * heightReference : HeightReference.NONE,
- * distanceDisplayCondition : undefined
- * });
- *
- * @example
- * // Example 2: Specify only the label's cartographic position,
- * // text, and font.
- * const l = labels.add({
- * position : Cesium.Cartesian3.fromRadians(longitude, latitude, height),
- * text : 'Hello World',
- * font : '24px Helvetica',
- * });
- *
- * @see LabelCollection#remove
- * @see LabelCollection#removeAll
- */
- LabelCollection.prototype.add = function (options) {
- const label = new Label(options, this);
- this._labels.push(label);
- this._labelsToUpdate.push(label);
- return label;
- };
- /**
- * Removes a label from the collection. Once removed, a label is no longer usable.
- *
- * @param {Label} label The label to remove.
- * @returns {boolean} <code>true</code> if the label was removed; <code>false</code> if the label 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 labels as possible before calling <code>update</code>.
- * If you intend to temporarily hide a label, it is usually more efficient to call
- * {@link Label#show} instead of removing and re-adding the label.
- *
- * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
- *
- *
- * @example
- * const l = labels.add(...);
- * labels.remove(l); // Returns true
- *
- * @see LabelCollection#add
- * @see LabelCollection#removeAll
- * @see Label#show
- */
- LabelCollection.prototype.remove = function (label) {
- if (defined(label) && label._labelCollection === this) {
- const index = this._labels.indexOf(label);
- if (index !== -1) {
- this._labels.splice(index, 1);
- destroyLabel(this, label);
- return true;
- }
- }
- return false;
- };
- /**
- * Removes all labels from the collection.
- *
- * @performance <code>O(n)</code>. It is more efficient to remove all the labels
- * 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
- * labels.add(...);
- * labels.add(...);
- * labels.removeAll();
- *
- * @see LabelCollection#add
- * @see LabelCollection#remove
- */
- LabelCollection.prototype.removeAll = function () {
- const labels = this._labels;
- for (let i = 0, len = labels.length; i < len; ++i) {
- destroyLabel(this, labels[i]);
- }
- labels.length = 0;
- };
- /**
- * Check whether this collection contains a given label.
- *
- * @param {Label} label The label to check for.
- * @returns {boolean} true if this collection contains the label, false otherwise.
- *
- * @see LabelCollection#get
- *
- */
- LabelCollection.prototype.contains = function (label) {
- return defined(label) && label._labelCollection === this;
- };
- /**
- * Returns the label in the collection at the specified index. Indices are zero-based
- * and increase as labels are added. Removing a label shifts all labels after
- * it to the left, changing their indices. This function is commonly used with
- * {@link LabelCollection#length} to iterate over all the labels
- * in the collection.
- *
- * @param {number} index The zero-based index of the billboard.
- *
- * @returns {Label} The label at the specified index.
- *
- * @performance Expected constant time. If labels were removed from the collection and
- * {@link Scene#render} 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 label in the collection
- * const len = labels.length;
- * for (let i = 0; i < len; ++i) {
- * const l = billboards.get(i);
- * l.show = !l.show;
- * }
- *
- * @see LabelCollection#length
- */
- LabelCollection.prototype.get = function (index) {
- //>>includeStart('debug', pragmas.debug);
- if (!defined(index)) {
- throw new DeveloperError("index is required.");
- }
- //>>includeEnd('debug');
- return this._labels[index];
- };
- /**
- * @private
- *
- */
- LabelCollection.prototype.update = function (frameState) {
- if (!this.show) {
- return;
- }
- const billboardCollection = this._billboardCollection;
- const backgroundBillboardCollection = this._backgroundBillboardCollection;
- billboardCollection.modelMatrix = this.modelMatrix;
- billboardCollection.debugShowBoundingVolume = this.debugShowBoundingVolume;
- backgroundBillboardCollection.modelMatrix = this.modelMatrix;
- backgroundBillboardCollection.debugShowBoundingVolume = this.debugShowBoundingVolume;
- const context = frameState.context;
- if (!defined(this._textureAtlas)) {
- this._textureAtlas = new TextureAtlas({
- context: context,
- });
- billboardCollection.textureAtlas = this._textureAtlas;
- }
- if (!defined(this._backgroundTextureAtlas)) {
- this._backgroundTextureAtlas = new TextureAtlas({
- context: context,
- initialSize: whitePixelSize,
- });
- backgroundBillboardCollection.textureAtlas = this._backgroundTextureAtlas;
- addWhitePixelCanvas(this._backgroundTextureAtlas);
- }
- const len = this._labelsToUpdate.length;
- for (let i = 0; i < len; ++i) {
- const label = this._labelsToUpdate[i];
- if (label.isDestroyed()) {
- continue;
- }
- const preUpdateGlyphCount = label._glyphs.length;
- if (label._rebindAllGlyphs) {
- rebindAllGlyphs(this, label);
- label._rebindAllGlyphs = false;
- }
- if (label._repositionAllGlyphs) {
- repositionAllGlyphs(label);
- label._repositionAllGlyphs = false;
- }
- const glyphCountDifference = label._glyphs.length - preUpdateGlyphCount;
- this._totalGlyphCount += glyphCountDifference;
- }
- const blendOption =
- backgroundBillboardCollection.length > 0
- ? BlendOption.TRANSLUCENT
- : this.blendOption;
- billboardCollection.blendOption = blendOption;
- backgroundBillboardCollection.blendOption = blendOption;
- billboardCollection._highlightColor = this._highlightColor;
- backgroundBillboardCollection._highlightColor = this._highlightColor;
- this._labelsToUpdate.length = 0;
- backgroundBillboardCollection.update(frameState);
- billboardCollection.update(frameState);
- };
- /**
- * Returns true if this object was destroyed; otherwise, false.
- * <br /><br />
- * If this object was destroyed, it should not be used; calling any function other than
- * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
- *
- * @returns {boolean} True if this object was destroyed; otherwise, false.
- *
- * @see LabelCollection#destroy
- */
- LabelCollection.prototype.isDestroyed = function () {
- return false;
- };
- /**
- * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
- * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
- * <br /><br />
- * Once an object is destroyed, it should not be used; calling any function other than
- * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore,
- * assign the return value (<code>undefined</code>) to the object as done in the example.
- *
- * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
- *
- *
- * @example
- * labels = labels && labels.destroy();
- *
- * @see LabelCollection#isDestroyed
- */
- LabelCollection.prototype.destroy = function () {
- this.removeAll();
- this._billboardCollection = this._billboardCollection.destroy();
- this._textureAtlas = this._textureAtlas && this._textureAtlas.destroy();
- this._backgroundBillboardCollection = this._backgroundBillboardCollection.destroy();
- this._backgroundTextureAtlas =
- this._backgroundTextureAtlas && this._backgroundTextureAtlas.destroy();
- return destroyObject(this);
- };
- export default LabelCollection;
|