Skip to content

Draping Imagery on 3D Tiles #12567

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 17 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion packages/engine/Source/Scene/Cesium3DTileset.js
Original file line number Diff line number Diff line change
Expand Up @@ -1097,6 +1097,25 @@ Object.defineProperties(Cesium3DTileset.prototype, {
},
},

/**
* The {@link ImageryLayerCollection} that should be draped on the contents of this tileset.
*
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before this PR is merged, let's make sure to document any known limitations here. For example, IIUC, imagery layers will not be applied to tilesets with content other than model meshes.

* @memberof Cesium3DTileset.prototype
*
* @type {ImageryLayerCollection}
*/
imageryLayers: {
// TODO_DRAPING Attach a listener that will set some
// `tileset._imageryLayersDirty` flag that is passed to the
// models, so that the models can be updated for new imagery
get: function () {
return this._imageryLayers;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to initialize value as an empty ImageryLayerCollection? That would make the API a bit cleaner, i.e.

const imageryProvider = await ...;
const layer = new Cesium.ImageryLayer(imageryProvider);
tileset.imageryLayers.add(layer)

Copy link
Contributor Author

@javagl javagl Apr 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There could be certain patterns or "ways how things are usually done" (and maybe there are patterns and I overlooked them...).

Here, I think that this could make sense. It will require some fixes, with changes from
if (defined(imageryLayers)) doSomethingCostly()
to
if (defined(imageryLayers) && imageryLayers.length > 0) doSomethingCostly();

If we do this, then I'd rather mark the imageryLayers as readonly (i.e. remove the setter). This way, the "wiring" of the listeners that is mentioned there could be done once, and we wouldn't have to handle someone doing a tileset.imageryLayers = completelyNewOne.

Maybe throwing in some clear()/removeAll() and addAll(...) functions for convenience...

},
set: function (value) {
this._imageryLayers = value;
},
},

/**
* Gets the tileset's properties dictionary object, which contains metadata about per-feature properties.
* <p>
Expand Down Expand Up @@ -2811,7 +2830,8 @@ function addTileDebugLabel(tile, tileset, position) {
let attributes = 0;

if (tileset.debugShowGeometricError) {
labelString += `\nGeometric error: ${tile.geometricError}`;
// XXX_DRAPING Show SSE as well....
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of scope for this PR, but Would it be helpful to add this functionality to the tileset inspector? I could certainly see value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll leave this open, and during a "cleanup pass" where all the _DRAPING comments are handled, this can be moved into an issue (there might be other "minor TODOs" ... and some larger ones as well ... coming out of all this)

labelString += `\nGeometric error: ${tile.geometricError} SSE ${tile._screenSpaceError}`;
attributes++;
}

Expand Down
9 changes: 4 additions & 5 deletions packages/engine/Source/Scene/GlobeSurfaceShaderSet.js
Original file line number Diff line number Diff line change
Expand Up @@ -409,17 +409,16 @@ GlobeSurfaceShaderSet.prototype.getShaderProgram = function (options) {
u_dayTextureTexCoordsRectangle[${i}],\n\
u_dayTextureTranslationAndScale[${i}],\n\
${applyAlpha ? `u_dayTextureAlpha[${i}]` : "1.0"},\n\
${applyDayNightAlpha ? `u_dayTextureNightAlpha[${i}]` : "1.0"},\n${
applyDayNightAlpha ? `u_dayTextureDayAlpha[${i}]` : "1.0"
},\n${applyBrightness ? `u_dayTextureBrightness[${i}]` : "0.0"},\n\
${applyDayNightAlpha ? `u_dayTextureNightAlpha[${i}]` : "1.0"},\n\
${applyDayNightAlpha ? `u_dayTextureDayAlpha[${i}]` : "1.0"},\n\
${applyBrightness ? `u_dayTextureBrightness[${i}]` : "0.0"},\n\
${applyContrast ? `u_dayTextureContrast[${i}]` : "0.0"},\n\
${applyHue ? `u_dayTextureHue[${i}]` : "0.0"},\n\
${applySaturation ? `u_dayTextureSaturation[${i}]` : "0.0"},\n\
${applyGamma ? `u_dayTextureOneOverGamma[${i}]` : "0.0"},\n\
${applySplit ? `u_dayTextureSplit[${i}]` : "0.0"},\n\
${colorToAlpha ? `u_colorsToAlpha[${i}]` : "vec4(0.0)"},\n\
nightBlend\
);\n`;
nightBlend\);\n`;
if (hasImageryLayerCutout) {
computeDayColor +=
"\
Expand Down
19 changes: 17 additions & 2 deletions packages/engine/Source/Scene/ImageryLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -663,7 +663,7 @@ ImageryLayer.prototype.getImageryRectangle = function () {
*
* @private
*
* @param {Tile} tile The terrain tile.
* @param {QuadtreeTile} tile The terrain tile.
* @param {TerrainProvider|undefined} terrainProvider The terrain provider associated with the terrain tile.
* @param {number} insertionPoint The position to insert new skeletons before in the tile's imagery list.
* @returns {boolean} true if this layer overlaps any portion of the terrain tile; otherwise, false.
Expand Down Expand Up @@ -1068,13 +1068,28 @@ ImageryLayer.prototype._calculateTextureTranslationAndScale = function (

const scaleX = terrainWidth / imageryRectangle.width;
const scaleY = terrainHeight / imageryRectangle.height;
return new Cartesian4(
const result = new Cartesian4(
(scaleX * (terrainRectangle.west - imageryRectangle.west)) / terrainWidth,
(scaleY * (terrainRectangle.south - imageryRectangle.south)) /
terrainHeight,
scaleX,
scaleY,
);

/*
// XXX_DRAPING: The above is just a convoluted way of writing this:
const invImageryWidth = 1.0 / imageryRectangle.width;
const invImageryHeight = 1.0 / imageryRectangle.height;
const deltaWest = terrainRectangle.west - imageryRectangle.west;
const deltaSouth = terrainRectangle.south - imageryRectangle.south;
const offsetX = deltaWest * invImageryWidth;
const offsetY = deltaSouth * invImageryHeight;
const scaleX = terrainRectangle.width * invImageryWidth;
const scaleY = terrainRectangle.height * invImageryHeight;
// which, in turn, could be a generic "Rectangle.relativize" function
*/

return result;
};

/**
Expand Down
23 changes: 23 additions & 0 deletions packages/engine/Source/Scene/Model/ImageryConfiguration.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* A class containing a the values that affect the appearance of
* an <code>ImageryLayer</code>.
*
* This is used in the <code>ModelImagery</code> to detect changes in
* the imagery settings: The <code>ModelImagery</code> stores one
* instance per imagery layer. During the <code>update</code>
* call, it checks whether any of the settings was changed.
* If this is the case, the draw commands of the model are reset.
*/
class ImageryConfiguration {
constructor(imageryLayer) {
this.alpha = imageryLayer.alpha;
this.brightness = imageryLayer.brightness;
this.contrast = imageryLayer.contrast;
this.hue = imageryLayer.hue;
this.saturation = imageryLayer.saturation;
this.gamma = imageryLayer.gamma;
this.colorToAlpha = imageryLayer.colorToAlpha;
}
}

export default ImageryConfiguration;
70 changes: 70 additions & 0 deletions packages/engine/Source/Scene/Model/ImageryCoverage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* A structure containing information about a piece of imagery.
*
* This represents the result of computing the imagery tiles that
* are covered by a given <code>Rectangle</code> (and which part
* of that imagery is covered, in terms of texture coordinates).
*
* It is used by the <code>ModelPrimitiveImagery</code>, to
* represent the imagery tiles that are covered by the cartographic
* bounding rectangle of the primitive positions.
*
* TODO_DRAPING: Implementation note for ImageryCoverage:
* This roughly corresponds to the <code>TileImagery</code> that is
* created in my favorite draping-related time sink, namely in
* <code>ImageryLayer._createTileImagerySkeletons</code>.
* But in contrast to the <code>TileImagery</code>, this describes
* <i>which</i> imagery tile is used, does not store the ("loading"
* and "ready"...) imagery <i>itself</i>.
*/
class ImageryCoverage {
/**
* Creates a new instance
*
* @param {number} x x-coordinate of the imagery tile
* @param {number} y y-coordinate of the imagery tile
* @param {number} level level of the imagery tile
* @param {Cartesian4} textureCoordinateRectangle The texture coordinate
* rectangle from the imagery tile that is covered, i.e. the
* (minU, minV, maxU, maxV) coordinate range.
*/
constructor(x, y, level, textureCoordinateRectangle) {
/**
* The x-coordinate of the imagery tile
*
* @type {number}
* @readonly
*/
this.x = x;

/**
* The x-coordinate of the imagery tile
*
* @type {number}
* @readonly
*/
this.y = y;

/**
* The x-coordinate of the imagery tile
*
* @type {number}
* @readonly
*/
this.level = level;

/**
* The texture coordinate range that is covered from the
* imagery tile.
*
* This is a <code>Cartesian4</code> that contains the
* (minU, minV, maxU, maxV) coordinate range.
*
* @type {Cartesian4}
* @readonly
*/
this.textureCoordinateRectangle = textureCoordinateRectangle;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BoundingRectangle could be a useful type here. We use it in other places to describe texture coordinates.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I've occasionally been meandering between Rectangle, BoundingRectangle, Cartesian4 (and added a local CartesianRectangle), because they serve different purposes in different contexts, with different constraints. Here, the main purpose was to ~"have something that can be passed to a shader via a uniform", but that translation may also happen elsewhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The specific "source" of that was

const texCoordsRectangle = new Cartesian4(minU, minV, maxU, maxV);

}
}

export default ImageryCoverage;
Loading
Loading