Add specs for draped imagery visibility updates

This commit is contained in:
Marco Hutter 2025-07-16 19:15:47 +02:00
parent e7df345f36
commit 0cd17d8419
3 changed files with 208 additions and 75 deletions

View File

@ -0,0 +1,73 @@
import {
Cartesian3,
HeadingPitchRoll,
ImageryLayer,
TileCoordinatesImageryProvider,
Transforms,
WebMercatorTilingScheme,
} from "@cesium/engine";
import pollToPromise from "./pollToPromise";
import Cesium3DTilesTester from "./Cesium3DTilesTester";
// A currently hard-wired tileset to be loaded for imagery draping tests
const tileset_unitSquare_fourPrimitives_plain_url =
"./Data/Models/glTF-2.0/unitSquare/tileset_unitSquare_fourPrimitives_plain.json";
/**
* Wait until the root tile of the given tileset is loaded
*
* @param {Cesium3DTileset} tileset The tileset
* @param {Scene} scene The scene
*/
async function waitForRootLoaded(tileset, scene) {
scene.renderForSpecs();
const root = tileset.root;
await pollToPromise(() => {
scene.renderForSpecs();
return root.contentFailed || root.contentReady;
});
}
/**
* Load and return a test tileset that defines an imagery layer,
* waiting until the root of that tileset is loaded.
*
* This means that the resulting <code>tileset.root.content._model._modelImagery</code>
* (including the <code>ModelPrimitiveImagery</code> instances) will be defined and ready.
*
* @param {Scene} scene The scene
* @returns {Cesium3DTileset} The tileset
*/
async function loadTilesetWithImagery(scene) {
const url = tileset_unitSquare_fourPrimitives_plain_url;
const tileset = await Cesium3DTilesTester.loadTileset(scene, url);
// Create a non-trivial transform for the tileset
const transform = Transforms.eastNorthUpToFixedFrame(
Cartesian3.fromDegrees(-120.0, 40.0, 1.0),
);
tileset.modelMatrix = transform;
// Set a view that fully shows the tile content
// (a unit square at the position given above)
scene.camera.setView({
destination: new Cartesian3(
-2446354.452726738,
-4237211.248955036,
4077988.0921552004,
),
orientation: new HeadingPitchRoll(Math.PI * 2, -Math.PI / 2, 0),
});
const imageryProvider = new TileCoordinatesImageryProvider({
tilingScheme: new WebMercatorTilingScheme(),
});
const imageryLayer = new ImageryLayer(imageryProvider);
tileset.imageryLayers.add(imageryLayer);
await waitForRootLoaded(tileset, scene);
return tileset;
}
export default loadTilesetWithImagery;

View File

@ -0,0 +1,100 @@
import {
defined,
ResourceCache,
ShaderBuilder,
ImageryPipelineStage,
} from "../../../index.js";
import createScene from "../../../../../Specs/createScene.js";
import loadTilesetWithImagery from "../../../../../Specs/loadTilesetWithImagery.js";
describe("Scene/Model/ImageryPipelineStage", function () {
let scene;
beforeAll(function () {
scene = createScene();
});
afterAll(function () {
scene.destroyForSpecs();
});
afterEach(function () {
scene.primitives.removeAll();
ResourceCache.clearForSpecs();
});
// A mock `FrameState` object, extracted from other specs, for
// the ImageryPipelineStage.process call
const mockFrameState = {
context: {
defaultTexture: {},
defaultNormalTexture: {},
defaultEmissiveTexture: {},
},
};
// Create a mock `PrimitiveRenderResources` object, extracted from
// other specs, for the ImageryPipelineStage.process call
function mockRenderResources(model, primitive) {
const count = defined(primitive.indices)
? primitive.indices.count
: primitive.attributes[0].count;
return {
attributes: [],
shaderBuilder: new ShaderBuilder(),
attributeIndex: 1,
count: count,
model: model,
runtimeNode: {
node: {},
},
uniformMap: {},
runtimePrimitive: {},
};
}
it("does not create imagery inputs for imagery layers that are not shown", async function () {
// Part of a "regression test" test against https://github.com/CesiumGS/cesium/issues/12742
const tileset = await loadTilesetWithImagery(scene);
// Drill a hole through the loaded structures and create some
// mock data for the ImageryPipelineStage.process call
const imageryLayers = tileset.imageryLayers;
const root = tileset.root;
const content = root.content;
const model = content._model;
const modelImagery = model._modelImagery;
const modelPrimitiveImagery = modelImagery._modelPrimitiveImageries[0];
const primitive = model.sceneGraph.components.nodes[0].primitives[0];
const primitiveRenderResources = mockRenderResources(model, primitive);
const imageryTexCoordAttributeSetIndices = [0];
const frameState = mockFrameState;
ImageryPipelineStage.process(
primitiveRenderResources,
primitive,
frameState,
);
// Initially, the imagery layers are visible, as of show===true,
// and there should be at least one ImageryInput be generated
// to be sent to the shader
const imageryInputsA = ImageryPipelineStage._createImageryInputs(
imageryLayers,
modelPrimitiveImagery,
imageryTexCoordAttributeSetIndices,
);
expect(imageryInputsA.length).not.toBe(0);
// For specs: Hide the imagery layer by setting show = false
imageryLayers.get(0).show = false;
// No more ImageryInput objects should be generated now
const imageryInputsB = ImageryPipelineStage._createImageryInputs(
imageryLayers,
modelPrimitiveImagery,
imageryTexCoordAttributeSetIndices,
);
expect(imageryInputsB.length).toBe(0);
});
});

View File

@ -1,76 +1,7 @@
import {
Cartesian3,
ResourceCache,
Transforms,
ModelImagery,
ImageryLayer,
TileCoordinatesImageryProvider,
HeadingPitchRoll,
WebMercatorTilingScheme,
} from "../../../index.js";
import { ResourceCache, ModelImagery } from "../../../index.js";
import createScene from "../../../../../Specs/createScene.js";
import pollToPromise from "../../../../../Specs/pollToPromise.js";
import Cesium3DTilesTester from "../../../../../Specs/Cesium3DTilesTester.js";
const tileset_unitSquare_fourPrimitives_plain_url =
"./Data/Models/glTF-2.0/unitSquare/tileset_unitSquare_fourPrimitives_plain.json";
/**
* Wait until the root tile of the given tileset is loaded
*
* @param {Cesium3DTileset} tileset The tileset
* @param {Scene} scene The scene
*/
async function waitForRootLoaded(tileset, scene) {
scene.renderForSpecs();
const root = tileset.root;
await pollToPromise(() => {
scene.renderForSpecs();
return root.contentFailed || root.contentReady;
});
}
/**
* Load and return a test tileset that defines an imagery layer,
* waiting until the root of that tileset is loaded.
*
* This means that the resulting <code>tileset.root.content._model._modelImagery</code>
* (including the <code>ModelPrimitiveImagery</code> instances) will be defined and ready.
*
* @param {Scene} scene The scene
* @returns {Cesium3DTileset} The tileset
*/
async function loadTestTilesetWithImagery(scene) {
const url = tileset_unitSquare_fourPrimitives_plain_url;
const tileset = await Cesium3DTilesTester.loadTileset(scene, url);
// Create a non-trivial transform for the tileset
const transform = Transforms.eastNorthUpToFixedFrame(
Cartesian3.fromDegrees(-120.0, 40.0, 1.0),
);
tileset.modelMatrix = transform;
// Set a view that fully shows the tile content
// (a unit square at the position given above)
scene.camera.setView({
destination: new Cartesian3(
-2446354.452726738,
-4237211.248955036,
4077988.0921552004,
),
orientation: new HeadingPitchRoll(Math.PI * 2, -Math.PI / 2, 0),
});
const imageryProvider = new TileCoordinatesImageryProvider({
tilingScheme: new WebMercatorTilingScheme(),
});
const imageryLayer = new ImageryLayer(imageryProvider);
tileset.imageryLayers.add(imageryLayer);
await waitForRootLoaded(tileset, scene);
return tileset;
}
import loadTilesetWithImagery from "../../../../../Specs/loadTilesetWithImagery.js";
describe("Scene/Model/ModelImagery", function () {
let scene;
@ -96,7 +27,7 @@ describe("Scene/Model/ModelImagery", function () {
});
it("properly reports _hasImagery", async function () {
const tileset = await loadTestTilesetWithImagery(scene);
const tileset = await loadTilesetWithImagery(scene);
const root = tileset.root;
const content = root.content;
@ -114,7 +45,7 @@ describe("Scene/Model/ModelImagery", function () {
});
it("properly reports _allImageryLayersReady", async function () {
const tileset = await loadTestTilesetWithImagery(scene);
const tileset = await loadTilesetWithImagery(scene);
const root = tileset.root;
const content = root.content;
@ -137,7 +68,7 @@ describe("Scene/Model/ModelImagery", function () {
return;
}
const tileset = await loadTestTilesetWithImagery(scene);
const tileset = await loadTilesetWithImagery(scene);
const root = tileset.root;
const content = root.content;
@ -161,8 +92,37 @@ describe("Scene/Model/ModelImagery", function () {
expect(modelImagery._imageryConfigurationsModified()).toBeFalse();
});
it("considers the show flag as part of the imageryConfigurations", async function () {
if (!scene.context.webgl2) {
return;
}
const tileset = await loadTilesetWithImagery(scene);
const root = tileset.root;
const content = root.content;
const model = content._model;
const modelImagery = model._modelImagery;
const imageryLayer = tileset.imageryLayers.get(0);
// Initially, _imageryConfigurationsModified is false (it was just updated)
expect(modelImagery._imageryConfigurationsModified()).toBeFalse();
// For spec: Modify imagery configuration
imageryLayer.show = 0.5;
// Now, _imageryConfigurationsModified is true
expect(modelImagery._imageryConfigurationsModified()).toBeTrue();
// Trigger an update
modelImagery._checkForModifiedImageryConfigurations();
// Now, _imageryConfigurationsModified is false again
expect(modelImagery._imageryConfigurationsModified()).toBeFalse();
});
it("creates one ModelPrimitiveImagery for each primitive", async function () {
const tileset = await loadTestTilesetWithImagery(scene);
const tileset = await loadTilesetWithImagery(scene);
const root = tileset.root;
const content = root.content;