Skip to content

Commit a6e0226

Browse files
authored
Merge pull request #12958 from Beilinson/fix-billboard-subregion
fix billboard subregion not displayed
2 parents edceb42 + e6ac78f commit a6e0226

File tree

5 files changed

+111
-31
lines changed

5 files changed

+111
-31
lines changed

CHANGES.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Change Log
22

3+
## 1.136
4+
5+
### @cesium/engine
6+
7+
#### Fixes :wrench:
8+
9+
- Billboards using `imageSubRegion` now render as expected. [#12585](https://github.com/CesiumGS/cesium/issues/12585)
10+
311
## 1.135 - 2025-11-03
412

513
### @cesium/engine

packages/engine/Source/Renderer/TextureAtlas.js

Lines changed: 54 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,16 @@ function TextureAtlas(options) {
5959
this._initialSize = initialSize;
6060

6161
this._texturePacker = undefined;
62+
/** @type {BoundingRectangle[]} */
6263
this._rectangles = [];
64+
/** @type {Map<number, number>} */
6365
this._subRegions = new Map();
6466
this._guid = createGuid();
6567

6668
this._imagesToAddQueue = [];
69+
/** @type {Map<string, number>} */
6770
this._indexById = new Map();
71+
/** @type {Map<string, Promise<number>>} */
6872
this._indexPromiseById = new Map();
6973
this._nextIndex = 0;
7074
}
@@ -644,7 +648,7 @@ async function resolveImage(image, id) {
644648
* @param {string} id An identifier to detect whether the image already exists in the atlas.
645649
* @param {HTMLImageElement|HTMLCanvasElement|string|Resource|Promise|TextureAtlas.CreateImageCallback} image An image or canvas to add to the texture atlas,
646650
* or a URL to an Image, or a Promise for an image, or a function that creates an image.
647-
* @returns {Promise<number>} A Promise that resolves to the image region index. -1 is returned if resouces are in the process of being destroyed.
651+
* @returns {Promise<number>} A Promise that resolves to the image region index, or -1 if resources are in the process of being destroyed.
648652
*/
649653
TextureAtlas.prototype.addImage = function (id, image) {
650654
//>>includeStart('debug', pragmas.debug);
@@ -653,12 +657,17 @@ TextureAtlas.prototype.addImage = function (id, image) {
653657
//>>includeEnd('debug');
654658

655659
let promise = this._indexPromiseById.get(id);
660+
let index = this._indexById.get(id);
656661
if (defined(promise)) {
657-
// This image has already been added
662+
// This image is already being added
658663
return promise;
659664
}
665+
if (defined(index)) {
666+
// This image has already been added and resolved
667+
return Promise.resolve(index);
668+
}
660669

661-
const index = this._nextIndex++;
670+
index = this._nextIndex++;
662671
this._indexById.set(id, index);
663672

664673
const resolveAndAddImage = async () => {
@@ -668,57 +677,79 @@ TextureAtlas.prototype.addImage = function (id, image) {
668677
//>>includeEnd('debug');
669678

670679
if (this.isDestroyed() || !defined(image)) {
680+
this._indexPromiseById.delete(id);
671681
return -1;
672682
}
673683

674-
return this._addImage(index, image);
684+
const imageIndex = await this._addImage(index, image);
685+
this._indexPromiseById.delete(id);
686+
return imageIndex;
675687
};
676688

677689
promise = resolveAndAddImage();
678690
this._indexPromiseById.set(id, promise);
679691
return promise;
680692
};
681693

694+
/**
695+
* Get an existing sub-region of an existing atlas image as additional image indices.
696+
* @private
697+
* @param {string} id The identifier of the existing image.
698+
* @param {BoundingRectangle} subRegion An {@link BoundingRectangle} defining a region of an existing image, measured in pixels from the bottom-left of the image.
699+
* @param {number} imageIndex The index of the image.
700+
* @returns {Promise<number> | number | undefined} The existing subRegion index, or undefined if not yet added.
701+
*/
702+
TextureAtlas.prototype.getCachedImageSubRegion = function (
703+
id,
704+
subRegion,
705+
imageIndex,
706+
) {
707+
const imagePromise = this._indexPromiseById.get(id);
708+
for (const [index, parentIndex] of this._subRegions.entries()) {
709+
if (imageIndex === parentIndex) {
710+
const boundingRegion = this._rectangles[index];
711+
if (boundingRegion.equals(subRegion)) {
712+
// The subregion is already being tracked
713+
if (imagePromise) {
714+
return imagePromise.then((resolvedImageIndex) =>
715+
resolvedImageIndex === -1 ? -1 : index,
716+
);
717+
}
718+
return index;
719+
}
720+
}
721+
}
722+
};
723+
682724
/**
683725
* Add a sub-region of an existing atlas image as additional image indices.
684726
* @private
685727
* @param {string} id The identifier of the existing image.
686728
* @param {BoundingRectangle} subRegion An {@link BoundingRectangle} defining a region of an existing image, measured in pixels from the bottom-left of the image.
687-
* @returns {Promise<number>} A Promise that resolves to the image region index. -1 is returned if resouces are in the process of being destroyed.
729+
* @returns {number | Promise<number>} The resolved image region index, or a Promise that resolves to it. -1 is returned if resources are in the process of being destroyed.
688730
*/
689731
TextureAtlas.prototype.addImageSubRegion = function (id, subRegion) {
690732
//>>includeStart('debug', pragmas.debug);
691733
Check.typeOf.string("id", id);
692734
Check.defined("subRegion", subRegion);
693735
//>>includeEnd('debug');
694-
695736
const imageIndex = this._indexById.get(id);
696737
if (!defined(imageIndex)) {
697738
throw new RuntimeError(`image with id "${id}" not found in the atlas.`);
698739
}
699740

700-
const indexPromise = this._indexPromiseById.get(id);
701-
for (const [index, parentIndex] of this._subRegions.entries()) {
702-
if (imageIndex === parentIndex) {
703-
const boundingRegion = this._rectangles[index];
704-
if (boundingRegion.equals(subRegion)) {
705-
// The subregion is already being tracked
706-
return indexPromise.then((resolvedImageIndex) => {
707-
if (resolvedImageIndex === -1) {
708-
// The atlas has been destroyed
709-
return -1;
710-
}
711-
712-
return index;
713-
});
714-
}
715-
}
741+
let index = this.getCachedImageSubRegion(id, subRegion, imageIndex);
742+
if (defined(index)) {
743+
return index;
716744
}
717745

718-
const index = this._nextIndex++;
746+
index = this._nextIndex++;
719747
this._subRegions.set(index, imageIndex);
720748
this._rectangles[index] = subRegion.clone();
721749

750+
const indexPromise =
751+
this._indexPromiseById.get(id) ?? Promise.resolve(imageIndex);
752+
722753
return indexPromise.then((imageIndex) => {
723754
if (imageIndex === -1) {
724755
// The atlas has been destroyed

packages/engine/Source/Scene/Billboard.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,6 @@ function Billboard(options, billboardCollection) {
189189
this._batchIndex = undefined; // Used only by Vector3DTilePoints and BillboardCollection
190190

191191
this._imageTexture = new BillboardTexture(billboardCollection);
192-
this._imageWidth = undefined;
193-
this._imageHeight = undefined;
194192

195193
this._labelDimensions = undefined;
196194
this._labelHorizontalOrigin = undefined;

packages/engine/Source/Scene/BillboardTexture.js

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -251,23 +251,66 @@ BillboardTexture.prototype.loadImage = async function (id, image) {
251251
* @param {string} id An identifier to detect whether the image already exists in the atlas.
252252
* @param {BoundingRectangle} subRegion An {@link BoundingRectangle} defining a region of an existing image, measured in pixels from the bottom-left of the image.
253253
*/
254-
BillboardTexture.prototype.addImageSubRegion = async function (id, subRegion) {
254+
BillboardTexture.prototype.addImageSubRegion = function (id, subRegion) {
255255
this._id = id;
256-
this._loadState = BillboardLoadState.LOADING;
257256
this._loadError = undefined;
258257
this._hasSubregion = true;
259258

260-
let index;
261259
const atlas = this._billboardCollection.textureAtlas;
260+
const indexOrPromise = atlas.addImageSubRegion(id, subRegion);
261+
262+
if (typeof indexOrPromise === "number") {
263+
this.setImageSubRegion(indexOrPromise, subRegion);
264+
return;
265+
}
266+
267+
this.loadImageSubRegion(id, subRegion, indexOrPromise);
268+
};
269+
270+
/**
271+
* @see {TextureAtlas#addImageSubRegion}
272+
* @private
273+
* @param {string} id An identifier to detect whether the image already exists in the atlas.
274+
* @param {BoundingRectangle} subRegion An {@link BoundingRectangle} defining a region of an existing image, measured in pixels from the bottom-left of the image.
275+
* @param {Promise<number>} indexPromise A promise that resolves to the image region index.
276+
*/
277+
BillboardTexture.prototype.loadImageSubRegion = async function (
278+
id,
279+
subRegion,
280+
indexPromise,
281+
) {
282+
let index;
262283
try {
263-
index = await atlas.addImageSubRegion(id, subRegion);
284+
this._loadState = BillboardLoadState.LOADING;
285+
index = await indexPromise;
264286
} catch (error) {
265287
// There was an error loading the referenced image
266288
this._loadState = BillboardLoadState.ERROR;
267289
this._loadError = error;
268290
return;
269291
}
270292

293+
if (this._id !== id) {
294+
// Another load was initiated and resolved resolved before this one. This operation is cancelled.
295+
return;
296+
}
297+
298+
this._loadState = BillboardLoadState.LOADED;
299+
300+
this.setImageSubRegion(index, subRegion);
301+
};
302+
303+
/**
304+
* @see {TextureAtlas#addImageSubRegion}
305+
* @private
306+
* @param {number} index The resolved index in the {@link TextureAtlas}
307+
* @param {BoundingRectangle} subRegion An {@link BoundingRectangle} defining a region of an existing image, measured in pixels from the bottom-left of the image.
308+
*/
309+
BillboardTexture.prototype.setImageSubRegion = function (index, subRegion) {
310+
if (this._index === index) {
311+
return;
312+
}
313+
271314
if (!defined(index) || index === -1) {
272315
this._loadState = BillboardLoadState.FAILED;
273316
this._index = -1;
@@ -280,7 +323,6 @@ BillboardTexture.prototype.addImageSubRegion = async function (id, subRegion) {
280323
this._height = subRegion.height;
281324

282325
this._index = index;
283-
this._loadState = BillboardLoadState.LOADED;
284326

285327
this.dirty = true;
286328
};

packages/engine/Specs/DataSources/BillboardVisualizerSpec.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,9 +178,10 @@ describe(
178178
const bb = billboardCollection.get(0);
179179

180180
await pollToPromise(function () {
181+
entityCluster.update(scene.frameState);
181182
scene.renderForSpecs();
182183
visualizer.update(time);
183-
return bb.show;
184+
return bb.ready;
184185
});
185186

186187
expect(bb.position).toEqual(testObject.position.getValue(time));

0 commit comments

Comments
 (0)