"Dynamic billboard" issues?

636 views
Skip to first unread message

gone...@gmail.com

unread,
Jun 29, 2015, 11:44:05 AM6/29/15
to cesiu...@googlegroups.com
Hi there,
I am working on a project where a billbard should change according to the zoom.
The event I am listening to is scene.prerender.
each time there is a zoom change - I add a new billboard to the billboard collection.

The problem I am facing is that after a few zoom in\zoom out - I get an exception of "height must be less than 16384".

As I understand - there is a problem for keeping all of the billboards in the billboards collection and after a while - it gets full. Seems that it is related to the textureAtlas.

Does anyone can think of a solution?

Mark Erikson

unread,
Jun 29, 2015, 12:20:09 PM6/29/15
to cesiu...@googlegroups.com, gone...@gmail.com
Well, first thing is that you're probably adding a new billboard way too often.  To start with, you probably want to debounce/throttle the "add a billboard" logic or check for a minimum amount of distance changed before you add a new billboard, because if you're doing it on every single render loop where the camera has moved, that's potentially a couple dozen+ per second.

A second approach would be to pre-add several specific billboards, and either change their visibility based on calculated zoom, or give them different values for scaleByDistance/translucencyByDistance.

The third option is to keep track of how many billboards you've added, and delete and recreate the BillboardCollection after a specified number of changes.

The ultimate solution would be for someone to actually go fix up Cesium's billboard and texture atlas handling so that it can reuse space, and submit those changes.

gone...@gmail.com

unread,
Jul 6, 2015, 8:13:08 AM7/6/15
to cesiu...@googlegroups.com, gone...@gmail.com
Regarding the third option - removeAll won't clean the texture atlas which will still remain full.
Is there a normal way to clean it?

Mark Erikson

unread,
Jul 6, 2015, 7:41:58 PM7/6/15
to cesiu...@googlegroups.com, gone...@gmail.com
I was suggesting actually removing the old BillboardCollection entirely (remove from scene primitives, remove references, let it get garbage-collected), and creating an entirely new one as a replacement.

Matthew Amato

unread,
Jul 6, 2015, 9:52:11 PM7/6/15
to cesiu...@googlegroups.com
Another option (depending on the use case) is to not use billboard at all and just render a canvas on the screen at the location the billboard is supposed to be.  You can still hook into the preRender event to do this.  The one downside of this approach is that the canvas would not be obscured by the earth, so rotating so it's behind a mountain or on the other side of the world would cause it to show through.  If you're interest, I can probably post a small sample proof of concept.

Obviously enhancing BillboardCollection to make it easy to replace a texture in place is the ideal solution, but I think there's some gotchas there that make it trickier than it sounds.  One day I'll get around to looking into it, but if someone gets there first we would be happy to look at a pull request.

--
You received this message because you are subscribed to the Google Groups "cesium-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cesium-dev+...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Mark Erikson

unread,
Jul 6, 2015, 10:34:43 PM7/6/15
to cesiu...@googlegroups.com
Any chance you could describe what you think those "gotchas" might be?  My skims through the BillboardCollection and TextureAtlas classes left me with the impression it was basically a matter of implementing the right bookkeeping for tracking what areas were used and what was available.  Certainly not trivial, but not unreasonable either.  Would be interested to hear what you think might make it difficult.

Matthew Amato

unread,
Jul 13, 2015, 11:14:41 PM7/13/15
to cesiu...@googlegroups.com
The nature of the texture atlas is that it may have several images that get used at different times, so you simply can't look at all billboards currently in use and rebuild the atlas (throwing away everything not in use).    Instead, you would want to add the ability to tag an item in the atlas as "dynamic" and then enhance the billboard setImage functionality so that if a dynamic image is updated it is replaced (the restriction would be that the new image would have to be the same size as the old one in order to avoid having to grow the atlas), if the image is a larger, you would have to grow it, if it's smaller you can get away with it.

That's at the primitive level.  At the Entity API level, you could then add a Billboard property that also specifies if the texture is dynamic and use that when the image is set.

It may not actually be all that bad, but I haven't tried it so I could be oversimplifying some things.  If you take a shot at implementing this, we would definitely be interested in a pull request.

gone...@gmail.com

unread,
Jul 14, 2015, 1:51:23 AM7/14/15
to Matthew Amato, cesiu...@googlegroups.com

Seems interesting.
I'll check it out next week.

You received this message because you are subscribed to a topic in the Google Groups "cesium-dev" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/cesium-dev/jFsmh7LOiTs/unsubscribe.
To unsubscribe from this group and all its topics, send an email to cesium-dev+...@googlegroups.com.

Alberto Acevedo

unread,
Oct 29, 2015, 3:54:55 PM10/29/15
to cesium-dev
Matthew,

I followed your hints to fix the issue when using dynamic billboards. I was successfully able to update 10,000 dynamic billboards (200 updates every 200 milliseconds). Before the fix the map was crashing after 10 seconds of updates. Please let me know if I missed something.

I did the following (see code below):

Entity billboard:
- Added a custom isDynamic boolean property when creating new billboard graphics to identify dynamic billboards. I'm tagging a billboard as dynamic when using image of type canvas (not for URL). The canvas are the ones my application constantly updates.

TextureAtlas class:
- Addded wrapper functions to handle the _idHash.
- Added an updateImage (imageId, image) function that is called for billboards tagged as dynamic. This function is the one that interacts with the _idHash to add indexes, check for the presence of indexes, and store new indexes. If an index is already present in the hash then that index is reused when calling the textureAtlas addImage(that, image, index);

Billboard:
- Modified the _loadImage() function to check is the billboard is dynamic. If it is dynamic the textureAtlas updateImage (imageId, image) is called, else the addImage((imageId, image) is called. Inside this  function I also have a modification to add a default icon when rendering KML containing icon URLs that are not accessible (like the X icon in google Earth).
 


texture atlas overflow fix:


TextureAtlas class:

/*global define*/
define([
        '../Core/BoundingRectangle',
        '../Core/Cartesian2',
        '../Core/createGuid',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/loadImage',
        '../Core/PixelFormat',
        '../Core/RuntimeError',
        '../Renderer/Framebuffer',
        '../Renderer/RenderState',
        '../Renderer/Texture',
        '../ThirdParty/when'
    ], function(
        BoundingRectangle,
        Cartesian2,
        createGuid,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        loadImage,
        PixelFormat,
        RuntimeError,
        Framebuffer,
        RenderState,
        Texture,
        when) {
    "use strict";

    // The atlas is made up of regions of space called nodes that contain images or child nodes.
    function TextureAtlasNode(bottomLeft, topRight, childNode1, childNode2, imageIndex) {
        this.bottomLeft = defaultValue(bottomLeft, Cartesian2.ZERO);
        this.topRight = defaultValue(topRight, Cartesian2.ZERO);
        this.childNode1 = childNode1;
        this.childNode2 = childNode2;
        this.imageIndex = imageIndex;
    }

    var defaultInitialSize = new Cartesian2(16.0, 16.0);

    /**
     * A TextureAtlas stores multiple images in one square texture and keeps
     * track of the texture coordinates for each image. TextureAtlas is dynamic,
     * meaning new images can be added at any point in time.
     * Texture coordinates are subject to change if the texture atlas resizes, so it is
     * important to check {@link TextureAtlas#getGUID} before using old values.
     *
     * @alias TextureAtlas
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {Scene} options.context The context in which the texture gets created.
     * @param {PixelFormat} [options.pixelFormat=PixelFormat.RGBA] The pixel format of the texture.
     * @param {Number} [options.borderWidthInPixels=1] The amount of spacing between adjacent images in pixels.
     * @param {Cartesian2} [options.initialSize=new Cartesian2(16.0, 16.0)] The initial side lengths of the texture.
     *
     * @exception {DeveloperError} borderWidthInPixels must be greater than or equal to zero.
     * @exception {DeveloperError} initialSize must be greater than zero.
     *
     * @private
     */
    var TextureAtlas = function(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        var borderWidthInPixels = defaultValue(options.borderWidthInPixels, 1.0);
        var initialSize = defaultValue(options.initialSize, defaultInitialSize);

        //>>includeStart('debug', pragmas.debug);
        if (!defined(options.context)) {
            throw new DeveloperError('context is required.');
        }
        if (borderWidthInPixels < 0) {
            throw new DeveloperError('borderWidthInPixels must be greater than or equal to zero.');
        }
        if (initialSize.x < 1 || initialSize.y < 1) {
            throw new DeveloperError('initialSize must be greater than zero.');
        }
        //>>includeEnd('debug');

        this._context = options.context;
        this._pixelFormat = defaultValue(options.pixelFormat, PixelFormat.RGBA);
        this._borderWidthInPixels = borderWidthInPixels;
        this._textureCoordinates = [];
        this._guid = createGuid();
        this._idHash = {};

    //use imageId as the key to store the atlas textureCoordinates index.
    // Storing the index based on imageId allows dynamic tagged images (canvas) to reuse the same space
    // in the atlas and solves the texture atlas overflow when updating the canvas image in the billboard.
    //start modification
    TextureAtlas.prototype.storeIdHashIndex = function (imageId, indexId) {
        this._idHash[imageId ] = indexId;
    };

    TextureAtlas.prototype.getIdHashIndex = function (imageId) {
        return this._idHash[imageId];
    };

    TextureAtlas.prototype.isIdHashIndexPresent = function ( imageId ) {
        if ( this._idHash.hasOwnProperty(imageId)) {
            return true;
        } else {
            return false;
        }
    };
    // end of modification

        // Create initial texture and root.
        this._texture = new Texture({
            context : this._context,
            width : initialSize.x,
            height : initialSize.y,
            pixelFormat : this._pixelFormat
        });
        this._root = new TextureAtlasNode(new Cartesian2(), new Cartesian2(initialSize.x, initialSize.y));

        var that = this;
        var uniformMap = {
            u_texture : function() {
                return that._texture;
            }
        };

        var fs =
            'uniform sampler2D u_texture;\n' +
            'varying vec2 v_textureCoordinates;\n' +
            'void main()\n' +
            '{\n' +
            '    gl_FragColor = texture2D(u_texture, v_textureCoordinates);\n' +
            '}\n';


        this._copyCommand = this._context.createViewportQuadCommand(fs, {
            uniformMap : uniformMap
        });
    };

    defineProperties(TextureAtlas.prototype, {
        /**
         * The amount of spacing between adjacent images in pixels.
         * @memberof TextureAtlas.prototype
         * @type {Number}
         */
        borderWidthInPixels : {
            get : function() {
                return this._borderWidthInPixels;
            }
        },

        /**
         * An array of {@link BoundingRectangle} texture coordinate regions for all the images in the texture atlas.
         * The x and y values of the rectangle correspond to the bottom-left corner of the texture coordinate.
         * The coordinates are in the order that the corresponding images were added to the atlas.
         * @memberof TextureAtlas.prototype
         * @type {BoundingRectangle[]}
         */
        textureCoordinates : {
            get : function() {
                return this._textureCoordinates;
            }
        },

        /**
         * The texture that all of the images are being written to.
         * @memberof TextureAtlas.prototype
         * @type {Texture}
         */
        texture : {
            get : function() {
                return this._texture;
            }
        },

        /**
         * The number of images in the texture atlas. This value increases
         * every time addImage or addImages is called.
         * Texture coordinates are subject to change if the texture atlas resizes, so it is
         * important to check {@link TextureAtlas#getGUID} before using old values.
         * @memberof TextureAtlas.prototype
         * @type {Number}
         */
        numberOfImages : {
            get : function() {
                return this._textureCoordinates.length;
            }
        },

        /**
         * The atlas' globally unique identifier (GUID).
         * The GUID changes whenever the texture atlas is modified.
         * Classes that use a texture atlas should check if the GUID
         * has changed before processing the atlas data.
         * @memberof TextureAtlas.prototype
         * @type {String}
         */
        guid : {
            get : function() {
                return this._guid;
            }
        }
    });

    // Builds a larger texture and copies the old texture into the new one.
    function resizeAtlas(textureAtlas, image) {
        var context = textureAtlas._context;
        var numImages = textureAtlas.numberOfImages;
        var scalingFactor = 2.0;
        if (numImages > 0) {
            var oldAtlasWidth = textureAtlas._texture.width;
            var oldAtlasHeight = textureAtlas._texture.height;
            var atlasWidth = scalingFactor * (oldAtlasWidth + image.width + textureAtlas._borderWidthInPixels);
            var atlasHeight = scalingFactor * (oldAtlasHeight + image.height + textureAtlas._borderWidthInPixels);
            var widthRatio = oldAtlasWidth / atlasWidth;
            var heightRatio = oldAtlasHeight / atlasHeight;

            // Create new node structure, putting the old root node in the bottom left.
            var nodeBottomRight = new TextureAtlasNode(new Cartesian2(oldAtlasWidth + textureAtlas._borderWidthInPixels, 0.0), new Cartesian2(atlasWidth, oldAtlasHeight));
            var nodeBottomHalf = new TextureAtlasNode(new Cartesian2(), new Cartesian2(atlasWidth, oldAtlasHeight), textureAtlas._root, nodeBottomRight);
            var nodeTopHalf = new TextureAtlasNode(new Cartesian2(0.0, oldAtlasHeight + textureAtlas._borderWidthInPixels), new Cartesian2(atlasWidth, atlasHeight));
            var nodeMain = new TextureAtlasNode(new Cartesian2(), new Cartesian2(atlasWidth, atlasHeight), nodeBottomHalf, nodeTopHalf);
            textureAtlas._root = nodeMain;

            // Resize texture coordinates.
            for (var i = 0; i < textureAtlas._textureCoordinates.length; i++) {
                var texCoord = textureAtlas._textureCoordinates[i];
                if (defined(texCoord)) {
                    texCoord.x *= widthRatio;
                    texCoord.y *= heightRatio;
                    texCoord.width *= widthRatio;
                    texCoord.height *= heightRatio;
                }
            }

            // Copy larger texture.
            var newTexture = new Texture({
                context : textureAtlas._context,
                width : atlasWidth,
                height : atlasHeight,
                pixelFormat : textureAtlas._pixelFormat
            });

            var framebuffer = new Framebuffer({
                context : context,
                colorTextures : [newTexture],
                destroyAttachments : false
            });

            var command = textureAtlas._copyCommand;
            var renderState = {
                viewport : new BoundingRectangle(0, 0, oldAtlasWidth, oldAtlasHeight)
            };
            command.renderState = RenderState.fromCache(renderState);

            // Copy by rendering a viewport quad, instead of using Texture.copyFromFramebuffer,
            // to workaround a Chrome 45 issue, https://github.com/AnalyticalGraphicsInc/cesium/issues/2997
            framebuffer._bind();
            command.execute(textureAtlas._context);
            framebuffer._unBind();
            framebuffer.destroy();
            textureAtlas._texture = newTexture;

            RenderState.removeFromCache(renderState);
            command.renderState = undefined;
        } else {
            // First image exceeds initialSize
            var initialWidth = scalingFactor * (image.width + textureAtlas._borderWidthInPixels);
            var initialHeight = scalingFactor * (image.height + textureAtlas._borderWidthInPixels);
            textureAtlas._texture = textureAtlas._texture && textureAtlas._texture.destroy();
            textureAtlas._texture = new Texture({
                context : textureAtlas._context,
                width : initialWidth,
                height : initialHeight,
                pixelFormat : textureAtlas._pixelFormat
            });
            textureAtlas._root = new TextureAtlasNode(new Cartesian2(), new Cartesian2(initialWidth, initialHeight));
        }
    }

    // A recursive function that finds the best place to insert
    // a new image based on existing image 'nodes'.
    // Inspired by: http://blackpawn.com/texts/lightmaps/default.html
    function findNode(textureAtlas, node, image) {
        if (!defined(node)) {
            return undefined;
        }

        // If a leaf node
        if (!defined(node.childNode1) &&
            !defined(node.childNode2)) {

            // Node already contains an image, don't add to it.
            if (defined(node.imageIndex)) {
                return undefined;
            }

            var nodeWidth = node.topRight.x - node.bottomLeft.x;
            var nodeHeight = node.topRight.y - node.bottomLeft.y;
            var widthDifference = nodeWidth - image.width;
            var heightDifference = nodeHeight - image.height;

            // Node is smaller than the image.
            if (widthDifference < 0 || heightDifference < 0) {
                return undefined;
            }

            // If the node is the same size as the image, return the node
            if (widthDifference === 0 && heightDifference === 0) {
                return node;
            }

            // Vertical split (childNode1 = left half, childNode2 = right half).
            if (widthDifference > heightDifference) {
                node.childNode1 = new TextureAtlasNode(new Cartesian2(node.bottomLeft.x, node.bottomLeft.y), new Cartesian2(node.bottomLeft.x + image.width, node.topRight.y));
                // Only make a second child if the border gives enough space.
                var childNode2BottomLeftX = node.bottomLeft.x + image.width + textureAtlas._borderWidthInPixels;
                if (childNode2BottomLeftX < node.topRight.x) {
                    node.childNode2 = new TextureAtlasNode(new Cartesian2(childNode2BottomLeftX, node.bottomLeft.y), new Cartesian2(node.topRight.x, node.topRight.y));
                }
            }
            // Horizontal split (childNode1 = bottom half, childNode2 = top half).
            else {
                node.childNode1 = new TextureAtlasNode(new Cartesian2(node.bottomLeft.x, node.bottomLeft.y), new Cartesian2(node.topRight.x, node.bottomLeft.y + image.height));
                // Only make a second child if the border gives enough space.
                var childNode2BottomLeftY = node.bottomLeft.y + image.height + textureAtlas._borderWidthInPixels;
                if (childNode2BottomLeftY < node.topRight.y) {
                    node.childNode2 = new TextureAtlasNode(new Cartesian2(node.bottomLeft.x, childNode2BottomLeftY), new Cartesian2(node.topRight.x, node.topRight.y));
                }
            }
            return findNode(textureAtlas, node.childNode1, image);
        }

        // If not a leaf node
        return findNode(textureAtlas, node.childNode1, image) ||
            findNode(textureAtlas, node.childNode2, image);
    }

    // Adds image of given index to the texture atlas. Called from addImage and addImages.
    function addImage(textureAtlas, image, index) {
        var node = findNode(textureAtlas, textureAtlas._root, image);
        if (defined(node)) {
            // Found a node that can hold the image.
            node.imageIndex = index;

            // Add texture coordinate and write to texture
            var atlasWidth = textureAtlas._texture.width;
            var atlasHeight = textureAtlas._texture.height;
            var nodeWidth = node.topRight.x - node.bottomLeft.x;
            var nodeHeight = node.topRight.y - node.bottomLeft.y;
            var x = node.bottomLeft.x / atlasWidth;
            var y = node.bottomLeft.y / atlasHeight;
            var w = nodeWidth / atlasWidth;
            var h = nodeHeight / atlasHeight;
            textureAtlas._textureCoordinates[index] = new BoundingRectangle(x, y, w, h);
            textureAtlas._texture.copyFrom(image, node.bottomLeft.x, node.bottomLeft.y);
        } else {
            // No node found, must resize the texture atlas.
            resizeAtlas(textureAtlas, image);
            addImage(textureAtlas, image, index);
        }

        textureAtlas._guid = createGuid();
    }

    /**
     * Adds an image to the atlas.  If the image is already in the atlas, the atlas is unchanged and
     * the existing index is used.
     *
     * @param {String} id An identifier to detect whether the image already exists in the atlas.
     * @param {Image|Canvas|String|Promise|TextureAtlas~CreateImageCallback} image An image or canvas to add to the texture atlas,
     *        or a URL to an Image, or a Promise for an image, or a function that creates an image.
     * @returns {Promise.<Number>} A Promise for the image index.
     */
    TextureAtlas.prototype.addImage = function(id, image) {
        //>>includeStart('debug', pragmas.debug);
        if (!defined(id)) {
            throw new DeveloperError('id is required.');
        }
        if (!defined(image)) {
            throw new DeveloperError('image is required.');
        }
        //>>includeEnd('debug');

        var indexPromise = this._idHash[id];
        if (defined(indexPromise)) {
            // we're already aware of this source
            return indexPromise;
        }

        // not in atlas, create the promise for the index

        if (typeof image === 'function') {
            // if image is a function, call it
            image = image(id);
            //>>includeStart('debug', pragmas.debug);
            if (!defined(image)) {
                throw new DeveloperError('image is required.');
            }
            //>>includeEnd('debug');
        } else if (typeof image === 'string') {
            // if image is a string, load it as an image
            image = loadImage(image);
        }

        var that = this;

        indexPromise = when(image, function(image) {
            if (that.isDestroyed()) {
                return -1;
            }

            var index = that.numberOfImages;

            addImage(that, image, index);

            return index;
        });

        // store the promise
        this._idHash[id] = indexPromise;

        return indexPromise;
    };
   
   
     /**
          
         * update an image in the atlas.  If the image is already in the atlas, the atlas image is replaced and
         * the existing index is used.
         *
         * @param {String} id An identifier to detect whether the image already exists in the atlas.
         * @param {Image|Canvas|String|Promise|TextureAtlas~CreateImageCallback} image An image or canvas to add to the texture atlas,
         *        or a URL to an Image, or a Promise for an image, or a function that creates an image.
         * @returns {Promise.<Number>} A Promise for the image index.
         */
        TextureAtlas.prototype.updateImage = function(imageId, image)// id is the imageId
        {
            var indexPromise = undefined;
                    if (!defined(imageId)) {
                throw new DeveloperError('id is required.');
            }
            if (!defined(image)) {
                throw new DeveloperError('image is required.');
            }
           
            if (typeof image === 'function') {
                // if image is a function, call it
                image = image(imageId);
                            if (!defined(image)) {
                    throw new DeveloperError('image is required.');
                }
                        } else if (typeof image === 'string') {
                // if image is a string, load it as an image
                image = loadImage(image);
            }
   
            var that = this;
   
            indexPromise = when(image, function(image) {
                if (that.isDestroyed()) {
                    return -1;
                }
   
                var index = that.numberOfImages;
                //acevedo
                // check if the image already has a space in the texture atlas
                if (that.isIdHashIndexPresent(imageId))
                {
                    //reuse space in texture
                    index = that.getIdHashIndex(imageId)
                    addImage(that, image, index);
                }
                else
                {
                   that.storeIdHashIndex(imageId,index);
                   addImage(that, image, index); 
                }
                return index;
            });
   
            return indexPromise;
    };

    /**
     * Add a sub-region of an existing atlas image as additional image indices.
     *
     * @param {String} id The identifier of the existing image.
     * @param {BoundingRectangle} subRegion An {@link BoundingRectangle} sub-region measured in pixels from the bottom-left.
     *
     * @returns {Promise.<Number>} A Promise for the image index.
     */
    TextureAtlas.prototype.addSubRegion = function(id, subRegion) {
        //>>includeStart('debug', pragmas.debug);
        if (!defined(id)) {
            throw new DeveloperError('id is required.');
        }
        if (!defined(subRegion)) {
            throw new DeveloperError('subRegion is required.');
        }
        //>>includeEnd('debug');

        var indexPromise = this._idHash[id];
        if (!defined(indexPromise)) {
            throw new RuntimeError('image with id "' + id + '" not found in the atlas.');
        }

        var that = this;
        return when(indexPromise, function(index) {
            if (index === -1) {
                // the atlas is destroyed
                return -1;
            }
            var atlasWidth = that._texture.width;
            var atlasHeight = that._texture.height;
            var numImages = that.numberOfImages;

            var baseRegion = that._textureCoordinates[index];
            var x = baseRegion.x + (subRegion.x / atlasWidth);
            var y = baseRegion.y + (subRegion.y / atlasHeight);
            var w = subRegion.width / atlasWidth;
            var h = subRegion.height / atlasHeight;
            that._textureCoordinates.push(new BoundingRectangle(x, y, w, h));
            that._guid = createGuid();

            return numImages;
        });
    };

    /**
     * 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 TextureAtlas#destroy
     */
    TextureAtlas.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.
     *
     * @returns {undefined}
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     * @see TextureAtlas#isDestroyed
     *
     * @example
     * atlas = atlas && atlas.destroy();
     */
    TextureAtlas.prototype.destroy = function() {
        this._texture = this._texture && this._texture.destroy();
        return destroyObject(this);
    };

    /**
     * A function that creates an image.
     * @callback TextureAtlas~CreateImageCallback
     * @param {String} id The identifier of the image to load.
     * @returns {Image|Promise} The image, or a promise that will resolve to an image.
     */

    return TextureAtlas;
});






/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



Inside Billboard class:





        // handle default emp icon and dynamic icons
              Billboard.prototype._loadImage = function() {
                      
                      var atlas = this._billboardCollection._textureAtlas;
                      var isDynamic =  false;
                      if (this._id && this._id._billboard && this._id._billboard.isDynamic)
                     {
                         this._imageId = this._id._id;
                           isDynamic = true;
                     }
                      var imageId = this._imageId;
                      var image = this._image;
                      var imageSubRegion = this._imageSubRegion;
                      var imageIndexPromise;
                      if (defined(image) && isDynamic)
                      {
                          imageIndexPromise = atlas.updateImage(imageId, image);
                      }
                      else if (defined(image) )
                      {
                           imageIndexPromise = atlas.addImage(imageId, image);
                      }
                      if (defined(imageSubRegion)) {
                          imageIndexPromise = atlas.addSubRegion(imageId, imageSubRegion);
                      }
             
                      this._imageIndexPromise = imageIndexPromise;
             
                      if (!defined(imageIndexPromise)) {
                          return;
                      }
             
                      var that = this;
                      that._id = this._id;
                      imageIndexPromise.then(function(index) {
                          if (that._imageId !== imageId || that._image !== image || !BoundingRectangle.equals(that._imageSubRegion, imageSubRegion)) {
                              // another load occurred before this one finished, ignore the index
                              return;
                          }
                          else if (that._imageId === empGlobe.getProxyUrl() + "?" + emp.utilities.getDefaultIcon().iconUrl)
                          {
                               return;
                          }
             
                          // fill in imageWidth and imageHeight
                          var textureCoordinates = atlas.textureCoordinates[index];
                          that._imageWidth = atlas.texture.width * textureCoordinates.width;
                          that._imageHeight = atlas.texture.height * textureCoordinates.height;
             
                          that._imageIndex = index;
                          that._ready = true;
                          that._image = undefined;
                          that._imageIndexPromise = undefined;
                          makeDirty(that, IMAGE_INDEX_INDEX);
                      }).otherwise(function(error) {
                          /*global console*/
                          var atlas = that._billboardCollection._textureAtlas;
                          var imageId = that._imageId;
                          var image = that._image;
                          var imageSubRegion = that._imageSubRegion;
                          var imageIndexPromise2;
                          //acevedo - next flag bilboard that failed to load image.
                          that.imageLoaded = false;
                          //stat acevedo edit
                          //add default icon when Cesium failed to load billboard icon
                          if (!(that._imageId === empGlobe.getProxyUrl() + "?" + emp.utilities.getDefaultIcon().iconUrl) )
                          {
                              storeUrlNotAccessible(that._imageId); // store original imageId
                              that._imageId = empGlobe.getProxyUrl() + "?" + emp.utilities.getDefaultIcon().iconUrl;
                              that._image = empDefaultIconCanvas;
                             
                              //that._image = new Cesium.ConstantProperty(empGlobe.getProxyUrl() + "?" + emp.utilities.getDefaultIcon().iconUrl);
                              that._imageWidth = emp.utilities.getDefaultIcon().offset.width;
                              that._imageHeight = emp.utilities.getDefaultIcon().offset.height;
                              that.pixelOffset = new Cesium.Cartesian2(isNaN(emp.utilities.getDefaultIcon().offset.x, emp.utilities.getDefaultIcon().offset.y));
                              //that._pixelOffset = new Cesium.Cartesian2(isNaN(that._actualPosition.x + emp.utilities.getDefaultIcon().offset.x, that._actualPosition.y + emp.utilities.getDefaultIcon().offset.y + 5000));
                              that._alignedAxis = Cesium.Cartesian3.ZERO;
                              that._verticalOrigin = Cesium.VerticalOrigin.BOTTOM;
                              that._imageIndexPromise = undefined;
                              //that._loadImage();
                          }
                          if (defined(image)) {
                          imageIndexPromise2 = atlas.addImage(that._imageId, that.image);
                      }
                      if (defined(imageSubRegion)) {
                          imageIndexPromise2 = atlas.addSubRegion(that._imageId, that._imageSubRegion);
                      }
             
                      that._imageIndexPromise = imageIndexPromise2;
             
                      if (!defined(imageIndexPromise2)) {
                          return;
                      }
             
                      var that2 = that;
                      that2._id = that._id;
                      imageIndexPromise2.then(function(index) {
                          var textureCoordinates = atlas.textureCoordinates[index];
                          that2._imageWidth = emp.utilities.getDefaultIcon().offset.width;
                          that2._imageHeight = emp.utilities.getDefaultIcon().offset.height;
                          that2.pixelOffset = new Cesium.Cartesian2(isNaN(emp.utilities.getDefaultIcon().offset.x, emp.utilities.getDefaultIcon().offset.y));
                          //that2._pixelOffset = new Cesium.Cartesian2(isNaN(that2._actualPosition.x + emp.utilities.getDefaultIcon().offset.x, that2._actualPosition.y + emp.utilities.getDefaultIcon().offset.y  + 5000));
                          that2.imageLoaded = true;
                          that2._alignedAxis = Cesium.Cartesian3.ZERO;
                          that2._verticalOrigin = Cesium.VerticalOrigin.BOTTOM;
                          that2._imageIndex = index;
                          that2._ready = true;
                          that2._image = undefined;
                          that2._imageIndexPromise = undefined;
                         
                          makeDirty(that2, IMAGE_INDEX_INDEX);
                      }).otherwise(function(error) {
                          /*global console*/
                          console.error('Error loading image for billboard: ' + error);
                          that2._imageIndexPromise = undefined;
                          that2.imageLoaded = false;
                      });
                         
                         
                      });
           };

Thanks,

Alberto

Mark Erikson

unread,
Oct 29, 2015, 10:41:44 PM10/29/15
to cesium-dev
Sounds useful.  Maybe go put together a pull request on Github?

André Borud

unread,
Nov 13, 2018, 3:34:39 AM11/13/18
to cesium-dev
Wow, three years have passed since the last message on this thread. 
I have implemented a working version of dynamic billboards based on Alberto's code above. Is this still wanted as a pull request?

Mark Erikson

unread,
Nov 13, 2018, 11:27:59 AM11/13/18
to cesium-dev
Yes, definitely!  This capability is still missing as far as I know.
Reply all
Reply to author
Forward
0 new messages