| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400 |
- import Check from "../Core/Check.js";
- import defined from "../Core/defined.js";
- import BillboardLoadState from "./BillboardLoadState.js";
-
- /**
- * Tracks a reference to an image and it's loading state, as used in a BillboardCollection and stored in a texture atlas.
- * @constructor
- * @private
- * @see BillboardCollection
- * @see Billboard#image
- * @alias BillboardTexture
- * @param {BillboardCollection} billboardCollection The associated billboard collecion.
- */
- function BillboardTexture(billboardCollection) {
- //>>includeStart('debug', pragmas.debug);
- Check.defined("billboardCollection", billboardCollection);
- //>>includeEnd('debug');
-
- this._billboardCollection = billboardCollection;
-
- this._id = undefined;
- this._loadState = BillboardLoadState.NONE;
- this._loadError = undefined;
-
- this._index = -1;
- this._width = undefined;
- this._height = undefined;
-
- this._hasSubregion = false;
-
- /**
- * Used by billboardCollection to track whcih billboards to update.
- * @type {boolean}
- * @private
- */
- this.dirty = false;
- }
-
- Object.defineProperties(BillboardTexture.prototype, {
- /**
- * If defined, this error was encountered during the loading process.
- * @memberof BillboardTexture.prototype
- * @type {Error|undefined}
- * @readonly
- * @private
- */
- loadError: {
- get: function () {
- return this._loadError;
- },
- },
-
- /**
- * The current status of the image load. When <code>BillboardLoadState.LOADED</code>, this billboard is ready to render, i.e., the image
- * has been downloaded and the WebGL resources are created.
- * @memberof BillboardTexture.prototype
- * @type {BillboardLoadState}
- * @readonly
- * @default BillboardLoadState.NONE
- * @private
- */
- loadState: {
- get: function () {
- return this._loadState;
- },
- },
-
- /**
- * When <code>true</code>, this texture is ready to render, i.e., the image
- * has been downloaded and the WebGL resources are created.
- * @memberof BillboardTexture.prototype
- * @type {boolean}
- * @readonly
- * @default false
- * @private
- */
- ready: {
- get: function () {
- return this._loadState === BillboardLoadState.LOADED;
- },
- },
-
- /**
- * Returns <code>true</code> if there is image data associated with this instance.
- * @memberof BillboardTexture.prototype
- * @type {boolean}
- * @readonly
- * @private
- */
- hasImage: {
- get: function () {
- return this._loadState !== BillboardLoadState.NONE;
- },
- },
-
- /**
- * A unique identifier for the image, or undefined if no image data has been associated with this instance.
- * @memberof BillboardTexture.prototype
- * @type {string|undefined}
- * @readonly
- * @private
- */
- id: {
- get: function () {
- return this._id;
- },
- },
-
- /**
- * The width of the associated image. Before the instance is <code>ready</code>, this will be <code>undefined</code>.
- * @memberof BillboardTexture.prototype
- * @type {number|undefined}
- * @readonly
- * @private
- */
- width: {
- get: function () {
- return this._width;
- },
- },
-
- /**
- * The height of the associated image. Before the instance is <code>ready</code>, this will be <code>undefined</code>.
- * @memberof BillboardTexture.prototype
- * @type {number|undefined}
- * @readonly
- * @private
- */
- height: {
- get: function () {
- return this._height;
- },
- },
- });
-
- /**
- * Releases reference to any associated image data.
- * @private
- */
- BillboardTexture.prototype.unload = async function () {
- if (this._loadState === BillboardLoadState.NONE) {
- return;
- }
-
- this._id = undefined;
- this._loadError = undefined;
- this._loadState = BillboardLoadState.NONE;
-
- this._index = -1;
- this._width = undefined;
- this._height = undefined;
-
- this.dirty = true;
- };
-
- /**
- * Starts loading an image into the texture atlas.
- * @see {TextureAtlas#addImage}
- * @private
- * @param {string} id An identifier to detect whether the image already exists in the atlas.
- * @param {HTMLImageElement|HTMLCanvasElement|string|Resource|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.
- * @param {number} width A number specifying the width of the texture. If undefined, the image width will be used.
- * @param {number} height A number specifying the height of the texture. If undefined, the image height will be used.
- */
- BillboardTexture.prototype.loadImage = async function (
- id,
- image,
- width,
- height,
- ) {
- if (this._id === id) {
- // This image has already been loaded
- return;
- }
-
- const collection = this._billboardCollection;
- const cache = collection.billboardTextureCache;
- let billboardTexture = cache.get(id);
- if (
- (defined(billboardTexture) &&
- image.loadState === BillboardLoadState.LOADING) ||
- image.loadState === BillboardLoadState.LOADED
- ) {
- // Use the cached texture if it is in progress or successful.
- BillboardTexture.clone(billboardTexture, this);
- return;
- }
- // Otherwise, load if not yet assigned an image, and try the load again if anything failed during the last billboard creation
- if (!defined(billboardTexture)) {
- billboardTexture = new BillboardTexture(collection);
- cache.set(id, billboardTexture);
- }
-
- billboardTexture._id = this._id = id;
- billboardTexture._loadState = this._loadState = BillboardLoadState.LOADING;
- billboardTexture._loadError = this._loadError = undefined;
-
- let index;
- const atlas = this._billboardCollection.textureAtlas;
- try {
- index = atlas.addImage(id, image, width, height);
- if (index instanceof Promise) {
- index = await index;
- }
- } catch (error) {
- // There was an error loading the image
- billboardTexture._loadState = BillboardLoadState.ERROR;
- billboardTexture._loadError = error;
-
- if (this._id !== id) {
- // Another load was initiated and resolved resolved before this one. This operation is cancelled.
- return;
- }
-
- this._loadState = BillboardLoadState.ERROR;
- this._loadError = error;
- return;
- }
-
- if (!defined(index) || index === -1) {
- // Resources destroyed or otherwise
- billboardTexture._loadState = BillboardLoadState.FAILED;
- billboardTexture._index = -1;
-
- if (this._id !== id) {
- // Another load was initiated and resolved resolved before this one. This operation is cancelled.
- return;
- }
-
- this._loadState = BillboardLoadState.FAILED;
- this._index = -1;
-
- return;
- }
-
- billboardTexture._index = index;
- billboardTexture._loadState = BillboardLoadState.LOADED;
-
- const rectangle = atlas.rectangles[index];
- billboardTexture._width = rectangle.width;
- billboardTexture._height = rectangle.height;
-
- if (this._id !== id) {
- // Another load was initiated and resolved resolved before this one. This operation is cancelled.
- return;
- }
-
- this._index = index;
- this._loadState = BillboardLoadState.LOADED;
- this._width = rectangle.width;
- this._height = rectangle.height;
-
- this.dirty = true;
- };
-
- /**
- * Track a reference to a sub-region of an existing image.
- * @see {TextureAtlas#addImageSubRegion}
- * @private
- * @param {string} id An identifier to detect whether the image already exists in the atlas.
- * @param {BoundingRectangle} subRegion An {@link BoundingRectangle} defining a region of an existing image, measured in pixels from the bottom-left of the image.
- */
- BillboardTexture.prototype.addImageSubRegion = function (id, subRegion) {
- this._id = id;
- this._loadError = undefined;
- this._hasSubregion = true;
-
- const atlas = this._billboardCollection.textureAtlas;
- const indexOrPromise = atlas.addImageSubRegion(id, subRegion);
-
- if (typeof indexOrPromise === "number") {
- this.setImageSubRegion(indexOrPromise, subRegion);
- return;
- }
-
- this.loadImageSubRegion(id, subRegion, indexOrPromise);
- };
-
- /**
- * @see {TextureAtlas#addImageSubRegion}
- * @private
- * @param {string} id An identifier to detect whether the image already exists in the atlas.
- * @param {BoundingRectangle} subRegion An {@link BoundingRectangle} defining a region of an existing image, measured in pixels from the bottom-left of the image.
- * @param {Promise<number>} indexPromise A promise that resolves to the image region index.
- */
- BillboardTexture.prototype.loadImageSubRegion = async function (
- id,
- subRegion,
- indexPromise,
- ) {
- let index;
- try {
- this._loadState = BillboardLoadState.LOADING;
- index = await indexPromise;
- } catch (error) {
- // There was an error loading the referenced image
- this._loadState = BillboardLoadState.ERROR;
- this._loadError = error;
- return;
- }
-
- if (this._id !== id) {
- // Another load was initiated and resolved resolved before this one. This operation is cancelled.
- return;
- }
-
- this._loadState = BillboardLoadState.LOADED;
-
- this.setImageSubRegion(index, subRegion);
- };
-
- /**
- * @see {TextureAtlas#addImageSubRegion}
- * @private
- * @param {number} index The resolved index in the {@link TextureAtlas}
- * @param {BoundingRectangle} subRegion An {@link BoundingRectangle} defining a region of an existing image, measured in pixels from the bottom-left of the image.
- */
- BillboardTexture.prototype.setImageSubRegion = function (index, subRegion) {
- if (this._index === index) {
- return;
- }
-
- if (!defined(index) || index === -1) {
- this._loadState = BillboardLoadState.FAILED;
- this._index = -1;
- this._width = undefined;
- this._height = undefined;
- return;
- }
-
- this._width = subRegion.width;
- this._height = subRegion.height;
-
- this._index = index;
-
- this.dirty = true;
- };
-
- /**
- * Get the texture coordinates for reading the loaded texture in shaders.
- * @private
- * @param {BoundingRectangle} [result] The modified result parameter or a new BoundingRectangle instance if one was not provided.
- * @return {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided.
- */
- BillboardTexture.prototype.computeTextureCoordinates = function (result) {
- const atlas = this._billboardCollection.textureAtlas;
- return atlas.computeTextureCoordinates(this._index, result);
- };
-
- /**
- * Clones an existing billboard texture, inlcuding any in-flight tracking, into the target billboard texture.
- * @param {BillboardTexture} billboardTexture
- * @param {BillboardTexture} target
- * @returns {BillboardTexture} target
- */
- BillboardTexture.clone = function (billboardTexture, target) {
- target._id = billboardTexture._id;
- target._loadState = billboardTexture._loadState;
- target._loadError = undefined;
- target._index = billboardTexture._index;
- target._width = billboardTexture._width;
- target._height = billboardTexture._height;
- target._hasSubregion = billboardTexture._hasSubregion;
-
- if (billboardTexture.ready) {
- target.dirty = true;
- return;
- }
-
- const completeLoad = async () => {
- const id = billboardTexture._id;
- const atlas = billboardTexture._billboardCollection.textureAtlas;
- await atlas._indexPromiseById.get(id);
-
- // Any errors should have already been handled
- if (target._id !== id) {
- // Another load was initiated and resolved resolved before this one. This operation is cancelled.
- return;
- }
-
- if (billboardTexture._hasSubregion) {
- // Subregions must wait an additional frame to be ready
- await Promise.resolve();
- }
-
- target._id = id;
- target._loadState = billboardTexture._loadState;
- target._loadError = billboardTexture._loadError;
- target._index = billboardTexture._index;
- target._width = billboardTexture._width;
- target._height = billboardTexture._height;
- target.dirty = true;
- };
-
- completeLoad();
- return target;
- };
-
- export default BillboardTexture;
|