import { Cartesian3 } from "../../Source/Cesium.js"; import { clone } from "../../Source/Cesium.js"; import { HeadingPitchRoll } from "../../Source/Cesium.js"; import { Math as CesiumMath } from "../../Source/Cesium.js"; import { Matrix3 } from "../../Source/Cesium.js"; import { Matrix4 } from "../../Source/Cesium.js"; import { Rectangle } from "../../Source/Cesium.js"; import { Transforms } from "../../Source/Cesium.js"; import { Cesium3DTile } from "../../Source/Cesium.js"; import { Cesium3DTilePass } from "../../Source/Cesium.js"; import { Cesium3DTileRefine } from "../../Source/Cesium.js"; import { Cesium3DTilesetHeatmap } from "../../Source/Cesium.js"; import { TileBoundingRegion } from "../../Source/Cesium.js"; import { TileOrientedBoundingBox } from "../../Source/Cesium.js"; import createScene from "../createScene.js"; describe( "Scene/Cesium3DTile", function () { var tileWithBoundingSphere = { geometricError: 1, refine: "REPLACE", children: [], boundingVolume: { sphere: [0.0, 0.0, 0.0, 5.0], }, }; var tileWithContentBoundingSphere = { geometricError: 1, refine: "REPLACE", content: { url: "0/0.b3dm", boundingVolume: { sphere: [0.0, 0.0, 1.0, 5.0], }, }, children: [], boundingVolume: { sphere: [0.0, 0.0, 1.0, 5.0], }, }; var tileWithBoundingRegion = { geometricError: 1, refine: "REPLACE", children: [], boundingVolume: { region: [-1.2, -1.2, 0.0, 0.0, -30, -34], }, }; var tileWithContentBoundingRegion = { geometricError: 1, refine: "REPLACE", children: [], content: { url: "0/0.b3dm", boundingVolume: { region: [-1.2, -1.2, 0, 0, -30, -34], }, }, boundingVolume: { region: [-1.2, -1.2, 0, 0, -30, -34], }, }; var tileWithBoundingBox = { geometricError: 1, refine: "REPLACE", children: [], boundingVolume: { box: [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0], }, }; var tileWithContentBoundingBox = { geometricError: 1, refine: "REPLACE", children: [], content: { url: "0/0.b3dm", boundingVolume: { box: [0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 2.0], }, }, boundingVolume: { box: [0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 2.0], }, }; var tileWithViewerRequestVolume = { geometricError: 1, refine: "REPLACE", children: [], boundingVolume: { box: [0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 2.0], }, viewerRequestVolume: { box: [0.0, 0.0, 1.0, 2.0, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 2.0], }, }; var mockTileset = { debugShowBoundingVolume: true, debugShowViewerRequestVolume: true, modelMatrix: Matrix4.IDENTITY, _geometricError: 2, _heatmap: new Cesium3DTilesetHeatmap(), }; var centerLongitude = -1.31968; var centerLatitude = 0.698874; function getTileTransform(longitude, latitude) { var transformCenter = Cartesian3.fromRadians(longitude, latitude, 0.0); var hpr = new HeadingPitchRoll(); var transformMatrix = Transforms.headingPitchRollToFixedFrame( transformCenter, hpr ); return Matrix4.pack(transformMatrix, new Array(16)); } it("destroys", function () { var tile = new Cesium3DTile( mockTileset, "/some_url", tileWithBoundingSphere, undefined ); expect(tile.isDestroyed()).toEqual(false); tile.destroy(); expect(tile.isDestroyed()).toEqual(true); }); it("throws if boundingVolume is undefined", function () { var tileWithoutBoundingVolume = clone(tileWithBoundingSphere, true); delete tileWithoutBoundingVolume.boundingVolume; expect(function () { return new Cesium3DTile( mockTileset, "/some_url", tileWithoutBoundingVolume, undefined ); }).toThrowRuntimeError(); }); it("throws if boundingVolume does not contain a sphere, region, or box", function () { var tileWithoutBoundingVolume = clone(tileWithBoundingSphere, true); delete tileWithoutBoundingVolume.boundingVolume.sphere; expect(function () { return new Cesium3DTile( mockTileset, "/some_url", tileWithoutBoundingVolume, undefined ); }).toThrowRuntimeError(); }); it("logs deprecation warning if refine is lowercase", function () { spyOn(Cesium3DTile, "_deprecationWarning"); var header = clone(tileWithBoundingSphere, true); header.refine = "replace"; var tile = new Cesium3DTile(mockTileset, "/some_url", header, undefined); expect(tile.refine).toBe(Cesium3DTileRefine.REPLACE); expect(Cesium3DTile._deprecationWarning).toHaveBeenCalled(); }); it("logs deprecation warning if geometric error is undefined", function () { spyOn(Cesium3DTile, "_deprecationWarning"); var geometricErrorMissing = clone(tileWithBoundingSphere, true); delete geometricErrorMissing.geometricError; var parent = new Cesium3DTile( mockTileset, "/some_url", tileWithBoundingSphere, undefined ); var child = new Cesium3DTile( mockTileset, "/some_url", geometricErrorMissing, parent ); expect(child.geometricError).toBe(parent.geometricError); expect(child.geometricError).toBe(1); var tile = new Cesium3DTile( mockTileset, "/some_url", geometricErrorMissing, undefined ); expect(tile.geometricError).toBe(mockTileset._geometricError); expect(tile.geometricError).toBe(2); expect(Cesium3DTile._deprecationWarning.calls.count()).toBe(2); }); describe("bounding volumes", function () { it("returns the tile bounding volume if the content bounding volume is undefined", function () { var tile = new Cesium3DTile( mockTileset, "/some_url", tileWithBoundingSphere, undefined ); expect(tile.boundingVolume).toBeDefined(); expect(tile.contentBoundingVolume).toBe(tile.boundingVolume); }); it("can have a bounding sphere", function () { var tile = new Cesium3DTile( mockTileset, "/some_url", tileWithBoundingSphere, undefined ); var radius = tileWithBoundingSphere.boundingVolume.sphere[3]; expect(tile.boundingVolume).toBeDefined(); expect(tile.boundingVolume.boundingVolume.radius).toEqual(radius); expect(tile.boundingVolume.boundingVolume.center).toEqual( Cartesian3.ZERO ); }); it("can have a content bounding sphere", function () { var tile = new Cesium3DTile( mockTileset, "/some_url", tileWithContentBoundingSphere, undefined ); var radius = tileWithContentBoundingSphere.content.boundingVolume.sphere[3]; expect(tile.contentBoundingVolume).toBeDefined(); expect(tile.contentBoundingVolume.boundingVolume.radius).toEqual( radius ); expect(tile.contentBoundingVolume.boundingVolume.center).toEqual( new Cartesian3(0.0, 0.0, 1.0) ); }); it("can have a bounding region", function () { var box = tileWithBoundingRegion.boundingVolume.region; var rectangle = new Rectangle(box[0], box[1], box[2], box[3]); var minimumHeight = tileWithBoundingRegion.boundingVolume.region[4]; var maximumHeight = tileWithBoundingRegion.boundingVolume.region[5]; var tile = new Cesium3DTile( mockTileset, "/some_url", tileWithBoundingRegion, undefined ); var tbr = new TileBoundingRegion({ rectangle: rectangle, minimumHeight: minimumHeight, maximumHeight: maximumHeight, }); expect(tile.boundingVolume).toBeDefined(); expect(tile.boundingVolume).toEqual(tbr); }); it("can have a content bounding region", function () { var region = tileWithContentBoundingRegion.content.boundingVolume.region; var tile = new Cesium3DTile( mockTileset, "/some_url", tileWithContentBoundingRegion, undefined ); expect(tile.contentBoundingVolume).toBeDefined(); var tbb = new TileBoundingRegion({ rectangle: new Rectangle(region[0], region[1], region[2], region[3]), minimumHeight: region[4], maximumHeight: region[5], }); expect(tile.contentBoundingVolume).toEqual(tbb); }); it("can have an oriented bounding box", function () { var box = tileWithBoundingBox.boundingVolume.box; var tile = new Cesium3DTile( mockTileset, "/some_url", tileWithBoundingBox, undefined ); expect(tile.boundingVolume).toBeDefined(); var center = new Cartesian3(box[0], box[1], box[2]); var halfAxes = Matrix3.fromArray(box, 3); var obb = new TileOrientedBoundingBox(center, halfAxes); expect(tile.boundingVolume).toEqual(obb); }); it("does not crash for bounding box with 0 volume", function () { // Create a copy of the tile with bounding box. var tileWithBoundingBox0Volume = JSON.parse( JSON.stringify(tileWithBoundingBox) ); // Generate all the combinations of missing axes. var boxes = []; for (var x = 0; x < 2; ++x) { for (var y = 0; y < 2; ++y) { for (var z = 0; z < 2; ++z) { boxes.push([ 0.0, 0.0, 0.0, x, 0.0, 0.0, 0.0, y, 0.0, 0.0, 0.0, z, ]); } } } for (var i = 0; i < boxes.length; ++i) { var box = boxes[i]; tileWithBoundingBox0Volume.boundingVolume.box = box; var tile = new Cesium3DTile( mockTileset, "/some_url", tileWithBoundingBox0Volume, undefined ); expect(tile.boundingVolume).toBeDefined(); var center = new Cartesian3(box[0], box[1], box[2]); var halfAxes = Matrix3.fromArray(box, 3); var obb = new TileOrientedBoundingBox(center, halfAxes); expect(tile.boundingVolume).toEqual(obb); } }); it("can have a content oriented bounding box", function () { var box = tileWithContentBoundingBox.boundingVolume.box; var tile = new Cesium3DTile( mockTileset, "/some_url", tileWithContentBoundingBox, undefined ); expect(tile.contentBoundingVolume).toBeDefined(); var center = new Cartesian3(box[0], box[1], box[2]); var halfAxes = Matrix3.fromArray(box, 3); var obb = new TileOrientedBoundingBox(center, halfAxes); expect(tile.contentBoundingVolume).toEqual(obb); }); it("tile transform affects bounding sphere", function () { var header = clone(tileWithContentBoundingSphere, true); header.transform = getTileTransform(centerLongitude, centerLatitude); var tile = new Cesium3DTile( mockTileset, "/some_url", header, undefined ); var boundingSphere = tile.boundingVolume.boundingVolume; var contentBoundingSphere = tile.contentBoundingVolume.boundingVolume; var boundingVolumeCenter = Cartesian3.fromRadians( centerLongitude, centerLatitude, 1.0 ); expect(boundingSphere.center).toEqualEpsilon( boundingVolumeCenter, CesiumMath.EPSILON4 ); expect(boundingSphere.radius).toEqual(5.0); // No change expect(contentBoundingSphere.center).toEqualEpsilon( boundingVolumeCenter, CesiumMath.EPSILON4 ); expect(contentBoundingSphere.radius).toEqual(5.0); // No change }); it("tile transform affects oriented bounding box", function () { var header = clone(tileWithContentBoundingBox, true); header.transform = getTileTransform(centerLongitude, centerLatitude); var tile = new Cesium3DTile( mockTileset, "/some_url", header, undefined ); var boundingBox = tile.boundingVolume.boundingVolume; var contentBoundingBox = tile.contentBoundingVolume.boundingVolume; var boundingVolumeCenter = Cartesian3.fromRadians( centerLongitude, centerLatitude, 1.0 ); expect(boundingBox.center).toEqualEpsilon( boundingVolumeCenter, CesiumMath.EPSILON7 ); expect(contentBoundingBox.center).toEqualEpsilon( boundingVolumeCenter, CesiumMath.EPSILON7 ); }); it("tile transform does not affect bounding region", function () { var header = clone(tileWithContentBoundingRegion, true); header.transform = getTileTransform(centerLongitude, centerLatitude); var tile = new Cesium3DTile( mockTileset, "/some_url", header, undefined ); var boundingRegion = tile.boundingVolume; var contentBoundingRegion = tile.contentBoundingVolume; var region = header.boundingVolume.region; var rectangle = Rectangle.unpack(region); expect(boundingRegion.rectangle).toEqual(rectangle); expect(contentBoundingRegion.rectangle).toEqual(rectangle); }); it("tile transform affects viewer request volume", function () { var header = clone(tileWithViewerRequestVolume, true); header.transform = getTileTransform(centerLongitude, centerLatitude); var tile = new Cesium3DTile( mockTileset, "/some_url", header, undefined ); var requestVolume = tile._viewerRequestVolume.boundingVolume; var requestVolumeCenter = Cartesian3.fromRadians( centerLongitude, centerLatitude, 1.0 ); expect(requestVolume.center).toEqualEpsilon( requestVolumeCenter, CesiumMath.EPSILON7 ); }); it("tile transform changes", function () { var mockTileset = { modelMatrix: Matrix4.IDENTITY, }; var header = clone(tileWithBoundingSphere, true); header.transform = getTileTransform(centerLongitude, centerLatitude); var tile = new Cesium3DTile( mockTileset, "/some_url", header, undefined ); var boundingSphere = tile.boundingVolume.boundingVolume; // Check the original transform var boundingVolumeCenter = Cartesian3.fromRadians( centerLongitude, centerLatitude ); expect(boundingSphere.center).toEqualEpsilon( boundingVolumeCenter, CesiumMath.EPSILON7 ); // Change the transform var newLongitude = -1.012; var newLatitude = 0.698874; tile.transform = getTileTransform(newLongitude, newLatitude); tile.updateTransform(); // Check the new transform var newCenter = Cartesian3.fromRadians(newLongitude, newLatitude); expect(boundingSphere.center).toEqualEpsilon( newCenter, CesiumMath.EPSILON7 ); }); }); describe("debug bounding volumes", function () { var scene; beforeEach(function () { scene = createScene(); scene.frameState.passes.render = true; }); afterEach(function () { scene.destroyForSpecs(); }); it("can be a bounding region", function () { var tile = new Cesium3DTile( mockTileset, "/some_url", tileWithBoundingRegion, undefined ); var passOptions = Cesium3DTilePass.getPassOptions( Cesium3DTilePass.RENDER ); tile.update(mockTileset, scene.frameState, passOptions); expect(tile._debugBoundingVolume).toBeDefined(); }); it("can be an oriented bounding box", function () { var tile = new Cesium3DTile( mockTileset, "/some_url", tileWithBoundingBox, undefined ); var passOptions = Cesium3DTilePass.getPassOptions( Cesium3DTilePass.RENDER ); tile.update(mockTileset, scene.frameState, passOptions); expect(tile._debugBoundingVolume).toBeDefined(); }); it("can be a bounding sphere", function () { var tile = new Cesium3DTile( mockTileset, "/some_url", tileWithBoundingSphere, undefined ); var passOptions = Cesium3DTilePass.getPassOptions( Cesium3DTilePass.RENDER ); tile.update(mockTileset, scene.frameState, passOptions); expect(tile._debugBoundingVolume).toBeDefined(); }); it("creates debug bounding volume for viewer request volume", function () { var tile = new Cesium3DTile( mockTileset, "/some_url", tileWithViewerRequestVolume, undefined ); var passOptions = Cesium3DTilePass.getPassOptions( Cesium3DTilePass.RENDER ); tile.update(mockTileset, scene.frameState, passOptions); expect(tile._debugViewerRequestVolume).toBeDefined(); }); }); it("updates priority", function () { var tile1 = new Cesium3DTile( mockTileset, "/some_url", tileWithBoundingSphere, undefined ); tile1._priorityHolder = tile1; tile1._foveatedFactor = 0.0; tile1._distanceToCamera = 1.0; tile1._depth = 0.0; tile1._priorityProgressiveResolution = true; var tile2 = new Cesium3DTile( mockTileset, "/some_url", tileWithBoundingSphere, undefined ); tile2._priorityHolder = tile1; tile2._foveatedFactor = 1.0; // foveatedFactor (when considered for priority in certain modes) is actually 0 since its linked up to tile1 tile2._distanceToCamera = 0.0; tile2._depth = 1.0; tile2._priorityProgressiveResolution = true; mockTileset._minimumPriority = { depth: 0.0, distance: 0.0, foveatedFactor: 0.0, }; mockTileset._maximumPriority = { depth: 1.0, distance: 1.0, foveatedFactor: 1.0, }; tile1.updatePriority(); tile2.updatePriority(); var nonPreloadFlightPenalty = 10000000000.0; var tile1ExpectedPriority = nonPreloadFlightPenalty + 0.0; var tile2ExpectedPriority = nonPreloadFlightPenalty + 1.0; expect( CesiumMath.equalsEpsilon( tile1._priority, tile1ExpectedPriority, CesiumMath.EPSILON2 ) ).toBe(true); expect( CesiumMath.equalsEpsilon( tile2._priority, tile2ExpectedPriority, CesiumMath.EPSILON2 ) ).toBe(true); // Penalty for not being a progressive resolution tile2._priorityProgressiveResolution = false; tile2.updatePriority(); var nonProgressiveResoutionPenalty = 100000000.0; expect(tile2._priority).toBeGreaterThan(nonProgressiveResoutionPenalty); tile2._priorityProgressiveResolution = true; // Penalty for being a foveated deferral tile2.priorityDeferred = true; tile2.updatePriority(); var foveatedDeferralPenalty = 10000000.0; expect(tile2._priority).toBeGreaterThanOrEqualTo(foveatedDeferralPenalty); tile2._priorityDeferred = false; }); }, "WebGL" );