import { BoundingSphere } from "../../Source/Cesium.js"; import { Color } from "../../Source/Cesium.js"; import { ColorGeometryInstanceAttribute } from "../../Source/Cesium.js"; import { combine } from "../../Source/Cesium.js"; import { destroyObject } from "../../Source/Cesium.js"; import { Ellipsoid } from "../../Source/Cesium.js"; import { GeometryInstance } from "../../Source/Cesium.js"; import { Math as CesiumMath } from "../../Source/Cesium.js"; import { Rectangle } from "../../Source/Cesium.js"; import { RectangleGeometry } from "../../Source/Cesium.js"; import { Pass } from "../../Source/Cesium.js"; import { RenderState } from "../../Source/Cesium.js"; import { Cesium3DTileBatchTable } from "../../Source/Cesium.js"; import { ClassificationType } from "../../Source/Cesium.js"; import { ColorBlendMode } from "../../Source/Cesium.js"; import { PerInstanceColorAppearance } from "../../Source/Cesium.js"; import { Primitive } from "../../Source/Cesium.js"; import { StencilConstants } from "../../Source/Cesium.js"; import { Vector3DTilePolygons } from "../../Source/Cesium.js"; import createContext from "../createContext.js"; import createScene from "../createScene.js"; import pollToPromise from "../pollToPromise.js"; // Testing of this feature in WebGL is currently disabled due to test // failures that started in https://github.com/CesiumGS/cesium/pull/8600. // Classification does NOT work reliably in WebGL2 anyway, see // https://github.com/CesiumGS/cesium/issues/8629 var testInWebGL2 = false; describe( "Scene/Vector3DTilePolygons", function () { createPolygonSpecs({}); if (testInWebGL2) { var c = createContext({ requestWebgl2: true }); // Don't repeat WebGL 1 tests when WebGL 2 is not supported if (c.webgl2) { createPolygonSpecs({ requestWebgl2: true }); } c.destroyForSpecs(); } function createPolygonSpecs(contextOptions) { var webglMessage = contextOptions.requestWebgl2 ? ": WebGL 2" : ""; var scene; var rectangle; var polygons; var globePrimitive; var tilesetPrimitive; var reusableGlobePrimitive; var reusableTilesetPrimitive; var ellipsoid = Ellipsoid.WGS84; var mockTileset = { _statistics: { texturesByteLength: 0, }, tileset: { _statistics: { batchTableByteLength: 0, }, colorBlendMode: ColorBlendMode.HIGHLIGHT, }, getFeature: function (id) { return { batchId: id }; }, }; function createPrimitive(rectangle, pass) { var renderState; if (pass === Pass.CESIUM_3D_TILE) { renderState = RenderState.fromCache({ stencilTest: StencilConstants.setCesium3DTileBit(), stencilMask: StencilConstants.CESIUM_3D_TILE_MASK, depthTest: { enabled: true, }, }); } var depthColorAttribute = ColorGeometryInstanceAttribute.fromColor( new Color(1.0, 0.0, 0.0, 1.0) ); return new Primitive({ geometryInstances: new GeometryInstance({ geometry: new RectangleGeometry({ ellipsoid: Ellipsoid.WGS84, rectangle: rectangle, }), id: "depth rectangle", attributes: { color: depthColorAttribute, }, }), appearance: new PerInstanceColorAppearance({ translucent: false, flat: true, renderState: renderState, }), asynchronous: false, }); } function MockPrimitive(primitive, pass) { this._primitive = primitive; this._pass = pass; this.show = true; } MockPrimitive.prototype.update = function (frameState) { if (!this.show) { return; } var commandList = frameState.commandList; var startLength = commandList.length; this._primitive.update(frameState); for (var i = startLength; i < commandList.length; ++i) { var command = commandList[i]; command.pass = this._pass; } }; MockPrimitive.prototype.isDestroyed = function () { return false; }; MockPrimitive.prototype.destroy = function () { return destroyObject(this); }; beforeAll(function () { scene = createScene({ contextOptions: contextOptions }); rectangle = Rectangle.fromDegrees(-40.0, -40.0, 40.0, 40.0); reusableGlobePrimitive = createPrimitive(rectangle, Pass.GLOBE); reusableTilesetPrimitive = createPrimitive( rectangle, Pass.CESIUM_3D_TILE ); }); afterAll(function () { reusableGlobePrimitive.destroy(); reusableTilesetPrimitive.destroy(); scene.destroyForSpecs(); }); beforeEach(function () { // wrap rectangle primitive so it gets executed during the globe pass and 3D Tiles pass to lay down depth globePrimitive = new MockPrimitive(reusableGlobePrimitive, Pass.GLOBE); tilesetPrimitive = new MockPrimitive( reusableTilesetPrimitive, Pass.CESIUM_3D_TILE ); }); afterEach(function () { scene.primitives.removeAll(); globePrimitive = globePrimitive && !globePrimitive.isDestroyed() && globePrimitive.destroy(); tilesetPrimitive = tilesetPrimitive && !tilesetPrimitive.isDestroyed() && tilesetPrimitive.destroy(); polygons = polygons && !polygons.isDestroyed() && polygons.destroy(); }); function loadPolygons(polygons) { var ready = false; polygons.readyPromise.then(function () { ready = true; }); return pollToPromise(function () { polygons.update(scene.frameState); scene.frameState.commandList.length = 0; return ready; }); } function zigZag(value) { return ((value << 1) ^ (value >> 15)) & 0xffff; } var maxShort = 32767; function encodePositions(rectangle, positions) { var length = positions.length; var buffer = new Uint16Array(length * 2); var lastU = 0; var lastV = 0; for (var i = 0; i < length; ++i) { var position = positions[i]; var u = (position.longitude - rectangle.west) / rectangle.width; var v = (position.latitude - rectangle.south) / rectangle.height; u = CesiumMath.clamp(u, 0.0, 1.0); v = CesiumMath.clamp(v, 0.0, 1.0); u = Math.floor(u * maxShort); v = Math.floor(v * maxShort); buffer[i] = zigZag(u - lastU); buffer[i + length] = zigZag(v - lastV); lastU = u; lastV = v; } return buffer; } function createPolygon(rectangle) { var cartographicPositions = [ Rectangle.northwest(rectangle), Rectangle.southwest(rectangle), Rectangle.southeast(rectangle), Rectangle.northeast(rectangle), ]; return { positions: encodePositions(rectangle, cartographicPositions), indices: new Uint16Array([0, 1, 2, 0, 2, 3]), counts: new Uint32Array([4]), indexCounts: new Uint32Array([6]), }; } it("renders a single polygon" + webglMessage, function () { var rectangle = Rectangle.fromDegrees(-1.0, -1.0, 1.0, 1.0); var polygonOptions = createPolygon(rectangle); var batchTable = new Cesium3DTileBatchTable(mockTileset, 1); batchTable.update(mockTileset, scene.frameState); scene.primitives.add(globePrimitive); var center = ellipsoid.cartographicToCartesian( Rectangle.center(rectangle) ); polygons = scene.primitives.add( new Vector3DTilePolygons( combine(polygonOptions, { minimumHeight: -10000.0, maximumHeight: 10000.0, center: center, rectangle: rectangle, boundingVolume: new BoundingSphere(center, 10000.0), batchTable: batchTable, batchIds: new Uint32Array([0]), isCartographic: true, }) ) ); return loadPolygons(polygons).then(function () { scene.camera.setView({ destination: rectangle, }); expect(scene).toRender([255, 255, 255, 255]); batchTable.setColor(0, Color.BLUE); polygons.updateCommands(0, Color.BLUE); batchTable.update(mockTileset, scene.frameState); expect(scene).toRender([0, 0, 255, 255]); }); }); it("renders multiple polygons" + webglMessage, function () { var rectangle1 = Rectangle.fromDegrees(-1.0, -1.0, 1.0, 1.0); var rectangle2 = Rectangle.fromDegrees(1.0, -1.0, 2.0, 1.0); var cartographicPositions = [ Rectangle.northwest(rectangle1), Rectangle.southwest(rectangle1), Rectangle.southeast(rectangle1), Rectangle.northeast(rectangle1), Rectangle.northwest(rectangle2), Rectangle.southwest(rectangle2), Rectangle.southeast(rectangle2), Rectangle.northeast(rectangle2), ]; var rectangle = Rectangle.fromDegrees(-1.0, -1.0, 2.0, 1.0); var batchTable = new Cesium3DTileBatchTable(mockTileset, 2); batchTable.update(mockTileset, scene.frameState); scene.primitives.add(globePrimitive); var center = ellipsoid.cartographicToCartesian( Rectangle.center(rectangle) ); polygons = scene.primitives.add( new Vector3DTilePolygons({ positions: encodePositions(rectangle, cartographicPositions), indices: new Uint16Array([0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7]), counts: new Uint32Array([4, 4]), indexCounts: new Uint32Array([6, 6]), minimumHeight: -10000.0, maximumHeight: 10000.0, center: center, rectangle: rectangle, boundingVolume: new BoundingSphere(center, 10000.0), batchTable: batchTable, batchIds: new Uint32Array([0, 1]), isCartographic: true, }) ); return loadPolygons(polygons).then(function () { scene.camera.setView({ destination: rectangle1, }); expect(scene).toRender([255, 255, 255, 255]); batchTable.setColor(0, Color.BLUE); polygons.updateCommands(0, Color.BLUE); batchTable.update(mockTileset, scene.frameState); expect(scene).toRender([0, 0, 255, 255]); scene.camera.setView({ destination: rectangle2, }); expect(scene).toRender([255, 255, 255, 255]); batchTable.setColor(1, Color.BLUE); polygons.updateCommands(1, Color.BLUE); batchTable.update(mockTileset, scene.frameState); expect(scene).toRender([0, 0, 255, 255]); }); }); it( "renders multiple polygons after re-batch" + webglMessage, function () { var rectangle1 = Rectangle.fromDegrees(-1.0, -1.0, 1.0, 1.0); var rectangle2 = Rectangle.fromDegrees(1.0, -1.0, 2.0, 1.0); var cartographicPositions = [ Rectangle.northwest(rectangle1), Rectangle.southwest(rectangle1), Rectangle.southeast(rectangle1), Rectangle.northeast(rectangle1), Rectangle.northwest(rectangle2), Rectangle.southwest(rectangle2), Rectangle.southeast(rectangle2), Rectangle.northeast(rectangle2), ]; var rectangle = Rectangle.fromDegrees(-1.0, -1.0, 2.0, 1.0); var batchTable = new Cesium3DTileBatchTable(mockTileset, 2); batchTable.update(mockTileset, scene.frameState); scene.primitives.add(globePrimitive); var center = ellipsoid.cartographicToCartesian( Rectangle.center(rectangle) ); polygons = scene.primitives.add( new Vector3DTilePolygons({ positions: encodePositions(rectangle, cartographicPositions), indices: new Uint16Array([0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7]), counts: new Uint32Array([4, 4]), indexCounts: new Uint32Array([6, 6]), minimumHeight: -10000.0, maximumHeight: 10000.0, center: center, rectangle: rectangle, boundingVolume: new BoundingSphere(center, 10000.0), batchTable: batchTable, batchIds: new Uint32Array([0, 1]), isCartographic: true, }) ); polygons.forceRebatch = true; return loadPolygons(polygons).then(function () { scene.camera.setView({ destination: rectangle1, }); expect(scene).toRender([255, 255, 255, 255]); batchTable.setColor(0, Color.BLUE); polygons.updateCommands(0, Color.BLUE); batchTable.update(mockTileset, scene.frameState); expect(scene).toRender([0, 0, 255, 255]); scene.camera.setView({ destination: rectangle2, }); expect(scene).toRender([255, 255, 255, 255]); batchTable.setColor(1, Color.BLUE); polygons.updateCommands(1, Color.BLUE); batchTable.update(mockTileset, scene.frameState); expect(scene).toRender([0, 0, 255, 255]); }); } ); it( "renders multiple polygons with different minimum and maximum heights" + webglMessage, function () { var rectangle1 = Rectangle.fromDegrees(-1.0, -1.0, 1.0, 1.0); var rectangle2 = Rectangle.fromDegrees(1.0, -1.0, 2.0, 1.0); var cartographicPositions = [ Rectangle.northwest(rectangle1), Rectangle.southwest(rectangle1), Rectangle.southeast(rectangle1), Rectangle.northeast(rectangle1), Rectangle.northwest(rectangle2), Rectangle.southwest(rectangle2), Rectangle.southeast(rectangle2), Rectangle.northeast(rectangle2), ]; var rectangle = Rectangle.fromDegrees(-1.0, -1.0, 2.0, 1.0); var batchTable = new Cesium3DTileBatchTable(mockTileset, 2); batchTable.update(mockTileset, scene.frameState); scene.primitives.add(globePrimitive); var center = ellipsoid.cartographicToCartesian( Rectangle.center(rectangle) ); polygons = scene.primitives.add( new Vector3DTilePolygons({ positions: encodePositions(rectangle, cartographicPositions), indices: new Uint16Array([0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7]), counts: new Uint32Array([4, 4]), indexCounts: new Uint32Array([6, 6]), minimumHeight: -10000.0, maximumHeight: 10000.0, polygonMinimumHeights: new Float32Array([-10000.0, 10.0]), polygonMaximumHeights: new Float32Array([10000.0, 100.0]), center: center, rectangle: rectangle, boundingVolume: new BoundingSphere(center, 10000.0), batchTable: batchTable, batchIds: new Uint32Array([0, 1]), isCartographic: true, }) ); polygons.forceRebatch = true; return loadPolygons(polygons).then(function () { scene.camera.setView({ destination: rectangle1, }); expect(scene).toRender([255, 255, 255, 255]); batchTable.setColor(0, Color.BLUE); polygons.updateCommands(0, Color.BLUE); batchTable.update(mockTileset, scene.frameState); expect(scene).toRender([0, 0, 255, 255]); scene.camera.setView({ destination: rectangle2, }); expect(scene).toRender([255, 0, 0, 255]); }); } ); it("renders with inverted classification" + webglMessage, function () { var rectangle = Rectangle.fromDegrees(-1.0, -1.0, 1.0, 1.0); var polygonOptions = createPolygon(rectangle); var batchTable = new Cesium3DTileBatchTable(mockTileset, 1); batchTable.update(mockTileset, scene.frameState); scene.primitives.add(tilesetPrimitive); var center = ellipsoid.cartographicToCartesian( Rectangle.center(rectangle) ); polygons = scene.primitives.add( new Vector3DTilePolygons( combine(polygonOptions, { minimumHeight: -10000.0, maximumHeight: 10000.0, center: center, rectangle: rectangle, boundingVolume: new BoundingSphere(center, 10000.0), batchTable: batchTable, batchIds: new Uint32Array([0]), isCartographic: true, }) ) ); return loadPolygons(polygons).then(function () { scene.camera.setView({ destination: Rectangle.fromDegrees(-2.0, -1.0, -1.0, 1.0), }); expect(scene).toRender([255, 0, 0, 255]); scene.invertClassification = true; scene.invertClassificationColor = new Color(0.25, 0.25, 0.25, 1.0); expect(scene).toRender([64, 0, 0, 255]); scene.camera.setView({ destination: rectangle, }); expect(scene).toRender([255, 255, 255, 255]); scene.invertClassification = false; }); }); it("renders wireframe" + webglMessage, function () { var rectangle = Rectangle.fromDegrees(-1.0, -1.0, 1.0, 1.0); var polygonOptions = createPolygon(rectangle); var batchTable = new Cesium3DTileBatchTable(mockTileset, 1); batchTable.update(mockTileset, scene.frameState); scene.primitives.add(globePrimitive); var center = ellipsoid.cartographicToCartesian( Rectangle.center(rectangle) ); polygons = scene.primitives.add( new Vector3DTilePolygons( combine(polygonOptions, { minimumHeight: -10000.0, maximumHeight: 10000.0, center: center, rectangle: rectangle, boundingVolume: new BoundingSphere(center, 10000.0), batchTable: batchTable, batchIds: new Uint32Array([0]), isCartographic: true, }) ) ); polygons.debugWireframe = true; return loadPolygons(polygons).then(function () { scene.camera.setView({ destination: rectangle, }); expect(scene).toRender([255, 255, 255, 255]); batchTable.setColor(0, Color.BLUE); polygons.updateCommands(0, Color.BLUE); batchTable.update(mockTileset, scene.frameState); expect(scene).toRender([0, 0, 255, 255]); }); }); it("renders based on classificationType" + webglMessage, function () { var rectangle = Rectangle.fromDegrees(-1.0, -1.0, 1.0, 1.0); var polygonOptions = createPolygon(rectangle); var batchTable = new Cesium3DTileBatchTable(mockTileset, 1); batchTable.update(mockTileset, scene.frameState); scene.primitives.add(globePrimitive); scene.primitives.add(tilesetPrimitive); var center = ellipsoid.cartographicToCartesian( Rectangle.center(rectangle) ); polygons = scene.primitives.add( new Vector3DTilePolygons( combine(polygonOptions, { minimumHeight: -10000.0, maximumHeight: 10000.0, center: center, rectangle: rectangle, boundingVolume: new BoundingSphere(center, 10000.0), batchTable: batchTable, batchIds: new Uint32Array([0]), isCartographic: true, }) ) ); return loadPolygons(polygons).then(function () { scene.camera.setView({ destination: rectangle, }); polygons.classificationType = ClassificationType.CESIUM_3D_TILE; globePrimitive.show = false; tilesetPrimitive.show = true; expect(scene).toRender([255, 255, 255, 255]); globePrimitive.show = true; tilesetPrimitive.show = false; expect(scene).toRender([255, 0, 0, 255]); polygons.classificationType = ClassificationType.TERRAIN; globePrimitive.show = false; tilesetPrimitive.show = true; expect(scene).toRender([255, 0, 0, 255]); globePrimitive.show = true; tilesetPrimitive.show = false; expect(scene).toRender([255, 255, 255, 255]); polygons.classificationType = ClassificationType.BOTH; globePrimitive.show = false; tilesetPrimitive.show = true; expect(scene).toRender([255, 255, 255, 255]); globePrimitive.show = true; tilesetPrimitive.show = false; expect(scene).toRender([255, 255, 255, 255]); globePrimitive.show = true; tilesetPrimitive.show = true; }); }); it("picks polygons" + webglMessage, function () { var rectangle = Rectangle.fromDegrees(-1.0, -1.0, 1.0, 1.0); var polygonOptions = createPolygon(rectangle); var batchTable = new Cesium3DTileBatchTable(mockTileset, 1); scene.primitives.add(globePrimitive); var center = ellipsoid.cartographicToCartesian( Rectangle.center(rectangle) ); polygons = scene.primitives.add( new Vector3DTilePolygons( combine(polygonOptions, { minimumHeight: -10000.0, maximumHeight: 10000.0, center: center, rectangle: rectangle, boundingVolume: new BoundingSphere(center, 10000.0), batchTable: batchTable, batchIds: new Uint32Array([0]), isCartographic: true, }) ) ); polygons.debugWireframe = true; return loadPolygons(polygons).then(function () { scene.camera.setView({ destination: rectangle, }); var features = []; polygons.createFeatures(mockTileset, features); var getFeature = mockTileset.getFeature; mockTileset.getFeature = function (index) { return features[index]; }; scene.frameState.passes.pick = true; batchTable.update(mockTileset, scene.frameState); expect(scene).toPickAndCall(function (result) { expect(result).toBe(features[0]); }); mockTileset.getFeature = getFeature; }); }); it("isDestroyed" + webglMessage, function () { polygons = new Vector3DTilePolygons({}); expect(polygons.isDestroyed()).toEqual(false); polygons.destroy(); expect(polygons.isDestroyed()).toEqual(true); }); } }, "WebGL" );