import { Cartesian2 } from "../../Source/Cesium.js"; import { Cartesian3 } from "../../Source/Cesium.js"; import { Cartographic } from "../../Source/Cesium.js"; import { Color } from "../../Source/Cesium.js"; import { CullingVolume } from "../../Source/Cesium.js"; import { defined } from "../../Source/Cesium.js"; import { getAbsoluteUri } from "../../Source/Cesium.js"; import { getJsonFromTypedArray } from "../../Source/Cesium.js"; import { HeadingPitchRange } from "../../Source/Cesium.js"; import { HeadingPitchRoll } from "../../Source/Cesium.js"; import { Intersect } from "../../Source/Cesium.js"; import { JulianDate } from "../../Source/Cesium.js"; import { Math as CesiumMath } from "../../Source/Cesium.js"; import { Matrix4 } from "../../Source/Cesium.js"; import { PerspectiveFrustum } from "../../Source/Cesium.js"; import { PrimitiveType } from "../../Source/Cesium.js"; import { Ray } from "../../Source/Cesium.js"; import { RequestScheduler } from "../../Source/Cesium.js"; import { Resource } from "../../Source/Cesium.js"; import { ResourceCache } from "../../Source/Cesium.js"; import { Transforms } from "../../Source/Cesium.js"; import { ClearCommand } from "../../Source/Cesium.js"; import { ContextLimits } from "../../Source/Cesium.js"; import { Camera } from "../../Source/Cesium.js"; import { Cesium3DTile } from "../../Source/Cesium.js"; import { Cesium3DTileColorBlendMode } from "../../Source/Cesium.js"; import { Cesium3DTileContentState } from "../../Source/Cesium.js"; import { Cesium3DTilePass } from "../../Source/Cesium.js"; import { Cesium3DTilePassState } from "../../Source/Cesium.js"; import { Cesium3DTileRefine } from "../../Source/Cesium.js"; import { Cesium3DTileset } from "../../Source/Cesium.js"; import { Cesium3DTileStyle } from "../../Source/Cesium.js"; import { ClippingPlane } from "../../Source/Cesium.js"; import { ClippingPlaneCollection } from "../../Source/Cesium.js"; import { CullFace } from "../../Source/Cesium.js"; import Cesium3DTilesTester from "../Cesium3DTilesTester.js"; import createScene from "../createScene.js"; import generateJsonBuffer from "../generateJsonBuffer.js"; import pollToPromise from "../pollToPromise.js"; import { when } from "../../Source/Cesium.js"; describe( "Scene/Cesium3DTileset", function () { // It's not easily possible to mock the most detailed pick functions // so don't run those tests when using the WebGL stub var webglStub = !!window.webglStub; var scene; var centerLongitude = -1.31968; var centerLatitude = 0.698874; var options; // Parent tile with content and four child tiles with content var tilesetUrl = "Data/Cesium3DTiles/Tilesets/Tileset/tileset.json"; // Parent tile with no content and four child tiles with content var tilesetEmptyRootUrl = "Data/Cesium3DTiles/Tilesets/TilesetEmptyRoot/tileset.json"; // Tileset with 3 levels of uniform subdivision var tilesetUniform = "Data/Cesium3DTiles/Tilesets/TilesetUniform/tileset.json"; var tilesetReplacement1Url = "Data/Cesium3DTiles/Tilesets/TilesetReplacement1/tileset.json"; var tilesetReplacement2Url = "Data/Cesium3DTiles/Tilesets/TilesetReplacement2/tileset.json"; var tilesetReplacement3Url = "Data/Cesium3DTiles/Tilesets/TilesetReplacement3/tileset.json"; // 3 level tree with mix of additive and replacement refinement var tilesetRefinementMix = "Data/Cesium3DTiles/Tilesets/TilesetRefinementMix/tileset.json"; // tileset.json : root content points to tiles2.json // tiles2.json: root with b3dm content, three children with b3dm content, one child points to tiles3.json // tiles3.json: root with b3dm content var tilesetOfTilesetsUrl = "Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json"; var withoutBatchTableUrl = "Data/Cesium3DTiles/Batched/BatchedWithoutBatchTable/tileset.json"; var withBatchTableUrl = "Data/Cesium3DTiles/Batched/BatchedWithBatchTable/tileset.json"; var noBatchIdsUrl = "Data/Cesium3DTiles/Batched/BatchedNoBatchIds/tileset.json"; var withBatchTableHierarchyUrl = "Data/Cesium3DTiles/Hierarchy/BatchTableHierarchy/tileset.json"; var withTransformBoxUrl = "Data/Cesium3DTiles/Batched/BatchedWithTransformBox/tileset.json"; var withTransformSphereUrl = "Data/Cesium3DTiles/Batched/BatchedWithTransformSphere/tileset.json"; var withTransformRegionUrl = "Data/Cesium3DTiles/Batched/BatchedWithTransformRegion/tileset.json"; var withBoundingSphereUrl = "Data/Cesium3DTiles/Batched/BatchedWithBoundingSphere/tileset.json"; var compositeUrl = "Data/Cesium3DTiles/Composite/Composite/tileset.json"; var instancedUrl = "Data/Cesium3DTiles/Instanced/InstancedWithBatchTable/tileset.json"; var instancedRedMaterialUrl = "Data/Cesium3DTiles/Instanced/InstancedRedMaterial/tileset.json"; var instancedAnimationUrl = "Data/Cesium3DTiles/Instanced/InstancedAnimated/tileset.json"; var gltfContentUrl = "Data/Cesium3DTiles/GltfContent/glTF/tileset.json"; // 1 tile where each feature is a different source color var colorsUrl = "Data/Cesium3DTiles/Batched/BatchedColors/tileset.json"; // 1 tile where each feature has a reddish texture var texturedUrl = "Data/Cesium3DTiles/Batched/BatchedTextured/tileset.json"; // 1 tile with translucent features var translucentUrl = "Data/Cesium3DTiles/Batched/BatchedTranslucent/tileset.json"; // 1 tile with opaque and translucent features var translucentOpaqueMixUrl = "Data/Cesium3DTiles/Batched/BatchedTranslucentOpaqueMix/tileset.json"; // Root tile is transformed from local space to wgs84, child tile is rotated, scaled, and translated locally var tilesetWithTransformsUrl = "Data/Cesium3DTiles/Tilesets/TilesetWithTransforms/tileset.json"; // Root tile with 4 b3dm children and 1 pnts child with a viewer request volume var tilesetWithViewerRequestVolumeUrl = "Data/Cesium3DTiles/Tilesets/TilesetWithViewerRequestVolume/tileset.json"; // Parent tile with content and four child tiles with content with viewer request volume for each child var tilesetReplacementWithViewerRequestVolumeUrl = "Data/Cesium3DTiles/Tilesets/TilesetReplacementWithViewerRequestVolume/tileset.json"; var tilesetWithExternalResourcesUrl = "Data/Cesium3DTiles/Tilesets/TilesetWithExternalResources/tileset.json"; var tilesetUrlWithContentUri = "Data/Cesium3DTiles/Batched/BatchedWithContentDataUri/tileset.json"; var tilesetSubtreeExpirationUrl = "Data/Cesium3DTiles/Tilesets/TilesetSubtreeExpiration/tileset.json"; var tilesetSubtreeUrl = "Data/Cesium3DTiles/Tilesets/TilesetSubtreeExpiration/subtree.json"; var batchedExpirationUrl = "Data/Cesium3DTiles/Batched/BatchedExpiration/tileset.json"; var batchedColorsB3dmUrl = "Data/Cesium3DTiles/Batched/BatchedColors/batchedColors.b3dm"; var batchedVertexColorsUrl = "Data/Cesium3DTiles/Batched/BatchedWithVertexColors/tileset.json"; var batchedAnimationUrl = "Data/Cesium3DTiles/Batched/BatchedAnimated/tileset.json"; var styleUrl = "Data/Cesium3DTiles/Style/style.json"; var pointCloudUrl = "Data/Cesium3DTiles/PointCloud/PointCloudRGB/tileset.json"; var pointCloudBatchedUrl = "Data/Cesium3DTiles/PointCloud/PointCloudBatched/tileset.json"; function endsWith(string, suffix) { var slicePoint = string.length - suffix.length; return string.slice(slicePoint) === suffix; } beforeAll(function () { scene = createScene(); }); afterAll(function () { scene.destroyForSpecs(); }); beforeEach(function () { RequestScheduler.clearForSpecs(); scene.morphTo3D(0.0); var camera = scene.camera; camera.frustum = new PerspectiveFrustum(); camera.frustum.aspectRatio = scene.drawingBufferWidth / scene.drawingBufferHeight; camera.frustum.fov = CesiumMath.toRadians(60.0); viewAllTiles(); options = { cullRequestsWhileMoving: false, }; }); afterEach(function () { scene.primitives.removeAll(); ResourceCache.clearForSpecs(); }); function setZoom(distance) { // Bird's eye view var center = Cartesian3.fromRadians(centerLongitude, centerLatitude); scene.camera.lookAt(center, new HeadingPitchRange(0.0, -1.57, distance)); } function viewAllTiles() { setZoom(15.0); } function viewRootOnly() { setZoom(100.0); } function viewNothing() { setZoom(200.0); } function viewSky() { var center = Cartesian3.fromRadians(centerLongitude, centerLatitude, 100); scene.camera.lookAt(center, new HeadingPitchRange(0.0, 1.57, 10.0)); } function viewBottomLeft() { viewAllTiles(); scene.camera.moveLeft(200.0); scene.camera.moveDown(200.0); } function viewInstances() { setZoom(30.0); } function viewPointCloud() { setZoom(5.0); } function viewGltfContent() { setZoom(100.0); } function isSelected(tileset, tile) { return tileset._selectedTiles.indexOf(tile) > -1; } it("throws with undefined url", function () { expect(function () { return new Cesium3DTileset(); }).toThrowDeveloperError(); }); it("rejects readyPromise with invalid tileset JSON fiile", function () { spyOn(Resource._Implementations, "loadWithXhr").and.callFake(function ( url, responseType, method, data, headers, deferred, overrideMimeType ) { deferred.reject(); }); options.url = "invalid.json"; var tileset = scene.primitives.add(new Cesium3DTileset(options)); return tileset.readyPromise .then(function () { fail("should not resolve"); }) .otherwise(function (error) { expect(tileset.ready).toEqual(false); }); }); it("loads json with static loadJson method", function () { var tilesetJson = { asset: { version: 2.0, }, }; var uri = "data:text/plain;base64," + btoa(JSON.stringify(tilesetJson)); Cesium3DTileset.loadJson(uri) .then(function (result) { expect(result).toEqual(tilesetJson); }) .otherwise(function (error) { fail("should not fail"); }); }); it("static method loadJson is used in Cesium3DTileset constructor", function () { var path = "Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json"; var originalLoadJson = Cesium3DTileset.loadJson; // override loadJson and replace incorrect url with correct url Cesium3DTileset.loadJson = function (tilesetUrl) { return originalLoadJson(path); }; // setup tileset with invalid url (overridden loadJson should replace invalid url with correct url) var tileset = new Cesium3DTileset({ url: "invalid.json", }); // restore original version Cesium3DTileset.loadJson = originalLoadJson; return tileset.readyPromise .then(function () { expect(tileset.ready).toEqual(true); }) .otherwise(function (error) { fail("should not fail"); }); }); it("Constructor works with promise to resource", function () { var resource = new Resource({ url: "Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json", }); // setup tileset with invalid url (overridden loadJson should replace invalid url with correct url) var tileset = new Cesium3DTileset({ url: when.resolve(resource), }); return tileset.readyPromise .then(function () { expect(tileset.ready).toEqual(true); }) .otherwise(function (error) { fail("should not fail"); }); }); it("Constructor works with file resource", function () { var resource = new Resource({ url: "Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json", }); // setup tileset with invalid url (overridden loadJson should replace invalid url with correct url) var tileset = new Cesium3DTileset({ url: resource, }); return tileset.readyPromise .then(function () { expect(tileset.ready).toEqual(true); }) .otherwise(function (error) { fail("should not fail"); }); }); it("rejects readyPromise with invalid tileset version", function () { var tilesetJson = { asset: { version: 2.0, }, }; var uri = "data:text/plain;base64," + btoa(JSON.stringify(tilesetJson)); options.url = uri; var tileset = scene.primitives.add(new Cesium3DTileset(options)); return tileset.readyPromise .then(function () { fail("should not resolve"); }) .otherwise(function (error) { expect(tileset.ready).toEqual(false); }); }); it("rejects readyPromise with unsupported extension", function () { var tilesetJson = { asset: { version: 1.0, }, extensionsUsed: ["unsupported_extension"], extensionsRequired: ["unsupported_extension"], }; var uri = "data:text/plain;base64," + btoa(JSON.stringify(tilesetJson)); options.url = uri; var tileset = scene.primitives.add(new Cesium3DTileset(options)); return tileset.readyPromise .then(function () { fail("should not resolve"); }) .otherwise(function (error) { expect(tileset.ready).toEqual(false); }); }); it("url and tilesetUrl set up correctly given tileset JSON filepath", function () { var path = "Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json"; var tileset = new Cesium3DTileset({ url: path, }); expect(tileset.resource.url).toEqual(path); }); it("url and tilesetUrl set up correctly given path with query string", function () { var path = "Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json"; var param = "?param1=1¶m2=2"; var tileset = new Cesium3DTileset({ url: path + param, }); expect(tileset.resource.url).toEqual(path + param); }); it("resolves readyPromise", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { return tileset.readyPromise.then(function (tileset) { expect(tileset.ready).toEqual(true); }); }); }); it("loads tileset JSON file", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { var asset = tileset.asset; expect(asset).toBeDefined(); expect(asset.version).toEqual("1.0"); expect(asset.tilesetVersion).toEqual("1.2.3"); var properties = tileset.properties; expect(properties).toBeDefined(); expect(properties.id).toBeDefined(); expect(properties.id.minimum).toEqual(0); expect(properties.id.maximum).toEqual(9); expect(tileset._geometricError).toEqual(240.0); expect(tileset.root).toBeDefined(); expect(tileset.resource.url).toEqual(tilesetUrl); }); }); it("loads tileset with extras", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { expect(tileset.extras).toEqual({ name: "Sample Tileset" }); expect(tileset.root.extras).toBeUndefined(); var length = tileset.root.children.length; var taggedChildren = 0; for (var i = 0; i < length; ++i) { if (defined(tileset.root.children[i].extras)) { expect(tileset.root.children[i].extras).toEqual({ id: "Special Tile", }); ++taggedChildren; } } expect(taggedChildren).toEqual(1); }); }); it("gets root tile", function () { options.url = tilesetUrl; var tileset = scene.primitives.add(new Cesium3DTileset(options)); expect(function () { return tileset.root; }).toThrowDeveloperError(); return tileset.readyPromise.then(function () { expect(tileset.root).toBeDefined(); }); }); it("hasExtension returns true if the tileset JSON file uses the specified extension", function () { return Cesium3DTilesTester.loadTileset( scene, withBatchTableHierarchyUrl ).then(function (tileset) { expect(tileset.hasExtension("3DTILES_batch_table_hierarchy")).toBe( true ); expect(tileset.hasExtension("3DTILES_nonexistant_extension")).toBe( false ); }); }); it("passes version in query string to tiles", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { expect(tileset.root.content._resource.url).toEqual( getAbsoluteUri( tilesetUrl.replace("tileset.json", "parent.b3dm?v=1.2.3") ) ); }); }); it("passes version in query string to all external resources", function () { // Spy on loadWithXhr so we can verify requested urls spyOn(Resource._Implementations, "loadWithXhr").and.callThrough(); var queryParams = "?a=1&b=boy"; var queryParamsWithVersion = "?a=1&b=boy&v=1.2.3"; return Cesium3DTilesTester.loadTileset( scene, tilesetWithExternalResourcesUrl + queryParams ).then(function (tileset) { var calls = Resource._Implementations.loadWithXhr.calls.all(); var callsLength = calls.length; for (var i = 0; i < callsLength; ++i) { var url = calls[0].args[0]; if (url.indexOf(tilesetWithExternalResourcesUrl) >= 0) { var query = url.slice(url.indexOf("?")); if (url.indexOf("tileset.json") >= 0) { // The initial tileset.json does not have a tileset version parameter expect(query).toBe(queryParams); } else { expect(query).toBe(queryParamsWithVersion); } } } }); }); it("throws when getting asset and tileset is not ready", function () { var tileset = new Cesium3DTileset({ url: tilesetUrl, }); expect(function () { return tileset.asset; }).toThrowDeveloperError(); }); it("throws when getting extensions and tileset is not ready", function () { var tileset = new Cesium3DTileset({ url: tilesetUrl, }); expect(function () { return tileset.extensions; }).toThrowDeveloperError(); }); it("throws when getting properties and tileset is not ready", function () { var tileset = new Cesium3DTileset({ url: tilesetUrl, }); expect(function () { return tileset.properties; }).toThrowDeveloperError(); }); it("throws when getting extras and tileset is not ready", function () { var tileset = new Cesium3DTileset({ url: tilesetUrl, }); expect(function () { return tileset.extras; }).toThrowDeveloperError(); }); it("requests tile with invalid magic", function () { var invalidMagicBuffer = Cesium3DTilesTester.generateBatchedTileBuffer({ magic: [120, 120, 120, 120], }); options.url = tilesetUrl; var tileset = scene.primitives.add(new Cesium3DTileset(options)); return tileset.readyPromise.then(function (tileset) { // Start spying after the tileset json has been loaded spyOn(Resource._Implementations, "loadWithXhr").and.callFake(function ( url, responseType, method, data, headers, deferred, overrideMimeType ) { deferred.resolve(invalidMagicBuffer); }); scene.renderForSpecs(); // Request root var root = tileset.root; return root.contentReadyPromise .then(function () { fail("should not resolve"); }) .otherwise(function (error) { expect(error.message).toBe("Invalid tile content."); expect(root._contentState).toEqual(Cesium3DTileContentState.FAILED); }); }); }); it("handles failed tile requests", function () { viewRootOnly(); options.url = tilesetUrl; var tileset = scene.primitives.add(new Cesium3DTileset(options)); return tileset.readyPromise.then(function (tileset) { // Start spying after the tileset json has been loaded spyOn(Resource._Implementations, "loadWithXhr").and.callFake(function ( url, responseType, method, data, headers, deferred, overrideMimeType ) { deferred.reject(); }); scene.renderForSpecs(); // Request root var root = tileset.root; return root.contentReadyPromise .then(function () { fail("should not resolve"); }) .otherwise(function (error) { expect(root._contentState).toEqual(Cesium3DTileContentState.FAILED); var statistics = tileset.statistics; expect(statistics.numberOfAttemptedRequests).toBe(0); expect(statistics.numberOfPendingRequests).toBe(0); expect(statistics.numberOfTilesProcessing).toBe(0); expect(statistics.numberOfTilesWithContentReady).toBe(0); }); }); }); it("handles failed tile processing", function () { viewRootOnly(); options.url = tilesetUrl; var tileset = scene.primitives.add(new Cesium3DTileset(options)); return tileset.readyPromise.then(function (tileset) { // Start spying after the tileset json has been loaded spyOn(Resource._Implementations, "loadWithXhr").and.callFake(function ( url, responseType, method, data, headers, deferred, overrideMimeType ) { deferred.resolve( Cesium3DTilesTester.generateBatchedTileBuffer({ version: 0, // Invalid version }) ); }); scene.renderForSpecs(); // Request root var root = tileset.root; return root.contentReadyPromise .then(function () { fail("should not resolve"); }) .otherwise(function (error) { expect(root._contentState).toEqual(Cesium3DTileContentState.FAILED); var statistics = tileset.statistics; expect(statistics.numberOfAttemptedRequests).toBe(0); expect(statistics.numberOfPendingRequests).toBe(0); expect(statistics.numberOfTilesProcessing).toBe(0); expect(statistics.numberOfTilesWithContentReady).toBe(0); }); }); }); it("renders tileset", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { var statistics = tileset._statistics; expect(statistics.visited).toEqual(5); expect(statistics.numberOfCommands).toEqual(5); }); }); function checkAnimation(url) { return Cesium3DTilesTester.loadTileset(scene, url).then(function ( tileset ) { var renderOptions = { scene: scene, time: new JulianDate(271.828), }; expect(renderOptions).toRenderAndCall(function (rgba) { var commandList = scene.frameState.commandList; var modelMatrix1 = Matrix4.clone(commandList[0].modelMatrix); // Check that the scene changes after .5 seconds. (it animates) renderOptions.time.secondsOfDay += 0.5; expect(renderOptions).toRenderAndCall(function (rgba) { var modelMatrix2 = Matrix4.clone(commandList[0].modelMatrix); expect(modelMatrix1).not.toEqual(modelMatrix2); }); }); }); } it("animates instanced tileset", function () { return checkAnimation(instancedAnimationUrl); }); it("animates batched tileset", function () { return checkAnimation(batchedAnimationUrl); }); it("renders tileset in CV", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { scene.morphToColumbusView(0.0); scene.renderForSpecs(); var statistics = tileset._statistics; expect(statistics.visited).toEqual(5); expect(statistics.numberOfCommands).toEqual(5); }); }); it("renders tileset in 2D", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { scene.morphTo2D(0.0); tileset.maximumScreenSpaceError = 3; scene.renderForSpecs(); var statistics = tileset._statistics; expect(statistics.visited).toEqual(5); expect(statistics.numberOfCommands).toEqual(10); }); }); it("does not render during morph", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { var commandList = scene.frameState.commandList; scene.renderForSpecs(); expect(commandList.length).toBeGreaterThan(0); scene.morphToColumbusView(1.0); scene.renderForSpecs(); expect(commandList.length).toBe(0); }); }); it("renders tileset with empty root tile", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetEmptyRootUrl).then( function (tileset) { var statistics = tileset._statistics; expect(statistics.visited).toEqual(5); expect(statistics.numberOfCommands).toEqual(4); // Empty tile doesn't issue a command } ); }); it("verify statistics", function () { options.url = tilesetUrl; var tileset = scene.primitives.add(new Cesium3DTileset(options)); // Verify initial values var statistics = tileset._statistics; expect(statistics.visited).toEqual(0); expect(statistics.numberOfCommands).toEqual(0); expect(statistics.numberOfPendingRequests).toEqual(0); expect(statistics.numberOfTilesProcessing).toEqual(0); return Cesium3DTilesTester.waitForReady(scene, tileset).then(function () { // Check that root and children are requested expect(statistics.visited).toEqual(5); expect(statistics.numberOfCommands).toEqual(0); expect(statistics.numberOfPendingRequests).toEqual(5); expect(statistics.numberOfTilesProcessing).toEqual(0); // Wait for all tiles to load and check that they are all visited and rendered return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( function () { expect(statistics.visited).toEqual(5); expect(statistics.numberOfCommands).toEqual(5); expect(statistics.numberOfPendingRequests).toEqual(0); expect(statistics.numberOfTilesProcessing).toEqual(0); } ); }); }); function checkPointAndFeatureCounts(tileset, features, points, triangles) { var statistics = tileset._statistics; expect(statistics.numberOfFeaturesSelected).toEqual(0); expect(statistics.numberOfFeaturesLoaded).toEqual(0); expect(statistics.numberOfPointsSelected).toEqual(0); expect(statistics.numberOfPointsLoaded).toEqual(0); expect(statistics.numberOfTrianglesSelected).toEqual(0); return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( function () { expect(statistics.numberOfFeaturesSelected).toEqual(features); expect(statistics.numberOfFeaturesLoaded).toEqual(features); expect(statistics.numberOfPointsSelected).toEqual(points); expect(statistics.numberOfPointsLoaded).toEqual(points); expect(statistics.numberOfTrianglesSelected).toEqual(triangles); viewNothing(); scene.renderForSpecs(); expect(statistics.numberOfFeaturesSelected).toEqual(0); expect(statistics.numberOfFeaturesLoaded).toEqual(features); expect(statistics.numberOfPointsSelected).toEqual(0); expect(statistics.numberOfPointsLoaded).toEqual(points); expect(statistics.numberOfTrianglesSelected).toEqual(0); tileset.trimLoadedTiles(); scene.renderForSpecs(); expect(statistics.numberOfFeaturesSelected).toEqual(0); expect(statistics.numberOfFeaturesLoaded).toEqual(0); expect(statistics.numberOfPointsSelected).toEqual(0); expect(statistics.numberOfPointsLoaded).toEqual(0); expect(statistics.numberOfTrianglesSelected).toEqual(0); } ); } it("verify batched features statistics", function () { options.url = withBatchTableUrl; var tileset = scene.primitives.add(new Cesium3DTileset(options)); return checkPointAndFeatureCounts(tileset, 10, 0, 120); }); it("verify no batch table features statistics", function () { options.url = noBatchIdsUrl; var tileset = scene.primitives.add(new Cesium3DTileset(options)); return checkPointAndFeatureCounts(tileset, 0, 0, 120); }); it("verify instanced features statistics", function () { options.url = instancedRedMaterialUrl; var tileset = scene.primitives.add(new Cesium3DTileset(options)); return checkPointAndFeatureCounts(tileset, 25, 0, 12); }); it("verify composite features statistics", function () { options.url = compositeUrl; var tileset = scene.primitives.add(new Cesium3DTileset(options)); return checkPointAndFeatureCounts(tileset, 35, 0, 132); }); it("verify tileset of tilesets features statistics", function () { options.url = tilesetOfTilesetsUrl; var tileset = scene.primitives.add(new Cesium3DTileset(options)); return checkPointAndFeatureCounts(tileset, 50, 0, 600); }); it("verify points statistics", function () { viewPointCloud(); options.url = pointCloudUrl; var tileset = scene.primitives.add(new Cesium3DTileset(options)); return checkPointAndFeatureCounts(tileset, 0, 1000, 0); }); it("verify triangle statistics", function () { options.url = tilesetEmptyRootUrl; var tileset = scene.primitives.add(new Cesium3DTileset(options)); return checkPointAndFeatureCounts(tileset, 40, 0, 480); }); it("verify batched points statistics", function () { viewPointCloud(); options.url = pointCloudBatchedUrl; var tileset = scene.primitives.add(new Cesium3DTileset(options)); return checkPointAndFeatureCounts(tileset, 8, 1000, 0); }); it("verify memory usage statistics", function () { // Calculations in Batched3DModel3DTileContentSpec, minus uvs var singleTileGeometryMemory = 7440; var singleTileTextureMemory = 0; var singleTileBatchTextureMemory = 40; var singleTilePickTextureMemory = 40; var tilesLength = 5; viewNothing(); return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { var statistics = tileset._statistics; // No tiles loaded expect(statistics.geometryByteLength).toEqual(0); expect(statistics.texturesByteLength).toEqual(0); expect(statistics.batchTableByteLength).toEqual(0); viewRootOnly(); return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( function () { // Root tile loaded expect(statistics.geometryByteLength).toEqual( singleTileGeometryMemory ); expect(statistics.texturesByteLength).toEqual( singleTileTextureMemory ); expect(statistics.batchTableByteLength).toEqual(0); viewAllTiles(); return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( function () { // All tiles loaded expect(statistics.geometryByteLength).toEqual( singleTileGeometryMemory * tilesLength ); expect(statistics.texturesByteLength).toEqual( singleTileTextureMemory * tilesLength ); expect(statistics.batchTableByteLength).toEqual(0); // One feature colored, the batch table memory is now higher tileset.root.content.getFeature(0).color = Color.RED; scene.renderForSpecs(); expect(statistics.geometryByteLength).toEqual( singleTileGeometryMemory * tilesLength ); expect(statistics.texturesByteLength).toEqual( singleTileTextureMemory * tilesLength ); expect(statistics.batchTableByteLength).toEqual( singleTileBatchTextureMemory ); // All tiles picked, the texture memory is now higher scene.pickForSpecs(); expect(statistics.geometryByteLength).toEqual( singleTileGeometryMemory * tilesLength ); expect(statistics.texturesByteLength).toEqual( singleTileTextureMemory * tilesLength ); expect(statistics.batchTableByteLength).toEqual( singleTileBatchTextureMemory + singleTilePickTextureMemory * tilesLength ); // Tiles are still in memory when zoomed out viewNothing(); scene.renderForSpecs(); expect(statistics.geometryByteLength).toEqual( singleTileGeometryMemory * tilesLength ); expect(statistics.texturesByteLength).toEqual( singleTileTextureMemory * tilesLength ); expect(statistics.batchTableByteLength).toEqual( singleTileBatchTextureMemory + singleTilePickTextureMemory * tilesLength ); // Trim loaded tiles, expect the memory statistics to be 0 tileset.trimLoadedTiles(); scene.renderForSpecs(); expect(statistics.geometryByteLength).toEqual(0); expect(statistics.texturesByteLength).toEqual(0); expect(statistics.batchTableByteLength).toEqual(0); } ); } ); }); }); it("verify memory usage statistics for shared resources", function () { // Six tiles total: // * Two b3dm tiles - no shared resources // * Two i3dm tiles with embedded glTF - no shared resources // * Two i3dm tiles with external glTF - shared resources // Expect to see some saving with memory usage since two of the tiles share resources // All tiles reference the same external texture but texture caching is not supported yet // TODO : tweak test when #5051 is in var b3dmGeometryMemory = 840; // Only one box in the tile, unlike most other test tiles var i3dmGeometryMemory = 840; // Texture is 128x128 RGBA bytes, not mipmapped var texturesByteLength = 65536; var expectedGeometryMemory = b3dmGeometryMemory * 2 + i3dmGeometryMemory * 3; var expectedTextureMemory = texturesByteLength * 5; return Cesium3DTilesTester.loadTileset( scene, tilesetWithExternalResourcesUrl ).then(function (tileset) { var statistics = tileset._statistics; expect(statistics.geometryByteLength).toBe(expectedGeometryMemory); expect(statistics.texturesByteLength).toBe(expectedTextureMemory); }); }); it("does not process tileset when screen space error is not met", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { var statistics = tileset._statistics; expect(statistics.visited).toEqual(5); expect(statistics.numberOfCommands).toEqual(5); // Set zoom far enough away to not meet sse viewNothing(); scene.renderForSpecs(); expect(statistics.visited).toEqual(0); expect(statistics.numberOfCommands).toEqual(0); }); }); it("does not select tiles when outside of view frustum", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { var statistics = tileset._statistics; expect(statistics.visited).toEqual(5); expect(statistics.numberOfCommands).toEqual(5); viewSky(); scene.renderForSpecs(); expect(statistics.visited).toEqual(0); expect(statistics.numberOfCommands).toEqual(0); expect( tileset.root.visibility( scene.frameState, CullingVolume.MASK_INDETERMINATE ) ).toEqual(CullingVolume.MASK_OUTSIDE); }); }); it("does not load additive tiles that are out of view", function () { viewBottomLeft(); return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { var statistics = tileset._statistics; expect(statistics.numberOfTilesWithContentReady).toEqual(2); }); }); it("culls with content box", function () { // Root tile has a content box that is half the extents of its box // Expect to cull root tile and three child tiles return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { var statistics = tileset._statistics; expect(statistics.visited).toEqual(5); expect(statistics.numberOfCommands).toEqual(5); viewBottomLeft(); scene.renderForSpecs(); expect(statistics.visited).toEqual(2); // Visits root, but does not render it expect(statistics.numberOfCommands).toEqual(1); expect(tileset._selectedTiles[0]).not.toBe(tileset.root); // Set contents box to undefined, and now root won't be culled tileset.root._contentBoundingVolume = undefined; scene.renderForSpecs(); expect(statistics.visited).toEqual(2); expect(statistics.numberOfCommands).toEqual(2); }); }); function findTileByUri(tiles, uri) { var length = tiles.length; for (var i = 0; i < length; ++i) { var tile = tiles[i]; var contentHeader = tile._header.content; if (defined(contentHeader)) { if (contentHeader.uri.indexOf(uri) >= 0) { return tile; } } } return undefined; } it("selects children in front to back order", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { // After moving the camera left by 1.0 and down by 0.5, the distance from the camera should be in the order: // 1. lower left // 2. upper left // 3. lower right // 4. upper right scene.camera.moveLeft(1.0); scene.camera.moveDown(0.5); scene.renderForSpecs(); var root = tileset.root; var llTile = findTileByUri(root.children, "ll.b3dm"); var lrTile = findTileByUri(root.children, "lr.b3dm"); var urTile = findTileByUri(root.children, "ur.b3dm"); var ulTile = findTileByUri(root.children, "ul.b3dm"); var selectedTiles = tileset._selectedTiles; expect(selectedTiles[0]).toBe(root); expect(selectedTiles[1]).toBe(llTile); expect(selectedTiles[2]).toBe(ulTile); expect(selectedTiles[3]).toBe(lrTile); expect(selectedTiles[4]).toBe(urTile); }); }); function testDynamicScreenSpaceError(url, distance) { return Cesium3DTilesTester.loadTileset(scene, url).then(function ( tileset ) { var statistics = tileset._statistics; // Horizon view, only root is visible var center = Cartesian3.fromRadians(centerLongitude, centerLatitude); scene.camera.lookAt(center, new HeadingPitchRange(0.0, 0.0, distance)); // Set dynamic SSE to false (default) tileset.dynamicScreenSpaceError = false; scene.renderForSpecs(); expect(statistics.visited).toEqual(1); expect(statistics.numberOfCommands).toEqual(1); // Set dynamic SSE to true, now the root is not rendered tileset.dynamicScreenSpaceError = true; tileset.dynamicScreenSpaceErrorDensity = 1.0; tileset.dynamicScreenSpaceErrorFactor = 10.0; scene.renderForSpecs(); expect(statistics.visited).toEqual(0); expect(statistics.numberOfCommands).toEqual(0); }); } function numberOfChildrenWithoutContent(tile) { var children = tile.children; var length = children.length; var count = 0; for (var i = 0; i < length; ++i) { var child = children[i]; if (!child.contentReady) { ++count; } } return count; } // Adjust distances for each test because the dynamic SSE takes the // bounding volume height into account, which differs for each bounding volume. it("uses dynamic screen space error for tileset with region", function () { return testDynamicScreenSpaceError(withTransformRegionUrl, 103.0); }); it("uses dynamic screen space error for tileset with bounding sphere", function () { return testDynamicScreenSpaceError(withBoundingSphereUrl, 137.0); }); it("uses dynamic screen space error for local tileset with box", function () { return testDynamicScreenSpaceError(withTransformBoxUrl, 103.0); }); it("uses dynamic screen space error for local tileset with sphere", function () { return testDynamicScreenSpaceError(withTransformSphereUrl, 144.0); }); it("additive refinement - selects root when sse is met", function () { viewRootOnly(); return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { // Meets screen space error, only root tile is rendered var statistics = tileset._statistics; expect(statistics.visited).toEqual(1); expect(statistics.numberOfCommands).toEqual(1); }); }); it("additive refinement - selects all tiles when sse is not met", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { // Does not meet screen space error, all tiles are visible var statistics = tileset._statistics; expect(statistics.visited).toEqual(5); expect(statistics.numberOfCommands).toEqual(5); }); }); it("additive refinement - use parent's geometric error on child's box for early refinement", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { var statistics = tileset._statistics; expect(statistics.visited).toEqual(5); expect(statistics.numberOfCommands).toEqual(5); // Both right tiles don't meet the SSE anymore scene.camera.moveLeft(50.0); scene.renderForSpecs(); expect(statistics.visited).toEqual(3); expect(statistics.numberOfCommands).toEqual(3); }); }); it("additive refinement - selects tile when inside viewer request volume", function () { return Cesium3DTilesTester.loadTileset( scene, tilesetWithViewerRequestVolumeUrl ).then(function (tileset) { var statistics = tileset._statistics; // Force root tile to always not meet SSE since this is just checking the request volume tileset.maximumScreenSpaceError = 0.0; // Renders all 5 tiles setZoom(20.0); scene.renderForSpecs(); expect(statistics.numberOfCommands).toEqual(5); // No longer renders the tile with a request volume setZoom(1500.0); scene.renderForSpecs(); expect(statistics.numberOfCommands).toEqual(4); }); }); it("replacement refinement - selects root when sse is met", function () { viewRootOnly(); return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { tileset.root.refine = Cesium3DTileRefine.REPLACE; // Meets screen space error, only root tile is rendered scene.renderForSpecs(); var statistics = tileset._statistics; expect(statistics.visited).toEqual(1); expect(statistics.numberOfCommands).toEqual(1); }); }); it("replacement refinement - selects children when sse is not met", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { tileset.root.refine = Cesium3DTileRefine.REPLACE; // Does not meet screen space error, child tiles replace root tile scene.renderForSpecs(); var statistics = tileset._statistics; expect(statistics.visited).toEqual(5); // Visits root, but does not render it expect(statistics.numberOfCommands).toEqual(4); }); }); it("replacement refinement - selects root when sse is not met and children are not ready", function () { viewRootOnly(); return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { var root = tileset.root; root.refine = Cesium3DTileRefine.REPLACE; // Set zoom to start loading child tiles viewAllTiles(); scene.renderForSpecs(); var statistics = tileset._statistics; // LOD skipping visits all visible expect(statistics.visited).toEqual(5); // no stencil clear command because only the root tile expect(statistics.numberOfCommands).toEqual(1); expect(statistics.numberOfPendingRequests).toEqual(4); expect(numberOfChildrenWithoutContent(root)).toEqual(4); }); }); it("replacement refinement - selects tile when inside viewer request volume", function () { return Cesium3DTilesTester.loadTileset( scene, tilesetWithViewerRequestVolumeUrl, { skipLevelOfDetail: false, } ).then(function (tileset) { var statistics = tileset._statistics; var root = tileset.root; root.refine = Cesium3DTileRefine.REPLACE; root.hasEmptyContent = false; // mock content tileset.maximumScreenSpaceError = 0.0; // Force root tile to always not meet SSE since this is just checking the request volume // Renders all 5 tiles setZoom(20.0); scene.renderForSpecs(); expect(statistics.numberOfCommands).toEqual(5); expect(isSelected(tileset, root)).toBe(false); // No longer renders the tile with a request volume setZoom(1500.0); scene.renderForSpecs(); expect(statistics.numberOfCommands).toEqual(4); expect(isSelected(tileset, root)).toBe(true); // one child is no longer selected. root is chosen instead }); }); it("replacement refinement - selects upwards when traversal stops at empty tile", function () { // No children have content, but all grandchildren have content // // C // E E // C C C C // return Cesium3DTilesTester.loadTileset( scene, tilesetReplacement1Url ).then(function (tileset) { tileset.root.geometricError = 90; setZoom(80); scene.renderForSpecs(); var statistics = tileset._statistics; expect(statistics.selected).toEqual(1); expect(statistics.visited).toEqual(3); expect(isSelected(tileset, tileset.root)).toBe(true); }); }); it("replacement refinement - selects root when sse is not met and subtree is not refinable (1)", function () { // No children have content, but all grandchildren have content // // C // E E // C C C C // viewRootOnly(); return Cesium3DTilesTester.loadTileset( scene, tilesetReplacement1Url ).then(function (tileset) { tileset.skipLevelOfDetail = false; viewAllTiles(); scene.renderForSpecs(); var statistics = tileset._statistics; var root = tileset.root; // Even though root's children are loaded, the grandchildren need to be loaded before it becomes refinable expect(numberOfChildrenWithoutContent(root)).toEqual(0); // Children are loaded expect(statistics.numberOfCommands).toEqual(1); // No stencil or backface commands; no mixed content expect(statistics.numberOfPendingRequests).toEqual(4); // Loading grandchildren return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( function () { scene.renderForSpecs(); expect(statistics.numberOfCommands).toEqual(4); // Render children } ); }); }); it("replacement refinement - selects root when sse is not met and subtree is not refinable (2)", function () { // Check that the root is refinable once its child is loaded // // C // E // C E // C (smaller geometric error) // viewRootOnly(); return Cesium3DTilesTester.loadTileset( scene, tilesetReplacement2Url ).then(function (tileset) { tileset.skipLevelOfDetail = false; var statistics = tileset._statistics; return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( function () { expect(statistics.numberOfCommands).toEqual(1); setZoom(5.0); // Zoom into the last tile, when it is ready the root is refinable scene.renderForSpecs(); return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( function () { expect(statistics.numberOfCommands).toEqual(2); // Renders two content tiles } ); } ); }); }); it("replacement refinement - selects root when sse is not met and subtree is not refinable (3)", function () { // Check that the root is refinable once its child is loaded // // C // T (external tileset ref) // E (root of external tileset) // C C C C // viewRootOnly(); return Cesium3DTilesTester.loadTileset( scene, tilesetReplacement3Url ).then(function (tileset) { tileset.skipLevelOfDetail = false; var statistics = tileset._statistics; var root = tileset.root; expect(statistics.numberOfCommands).toEqual(1); viewAllTiles(); scene.renderForSpecs(); return root.children[0].contentReadyPromise.then(function () { // The external tileset json is loaded, but the external tileset isn't. scene.renderForSpecs(); expect(statistics.numberOfCommands).toEqual(1); // root expect(statistics.numberOfPendingRequests).toEqual(4); // Loading child content tiles return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( function () { expect(isSelected(tileset, root)).toEqual(false); expect(statistics.numberOfCommands).toEqual(4); // Render child content tiles } ); }); }); }); it("replacement refinement - refines if descendant is empty leaf tile", function () { // Check that the root is refinable once its children with content are loaded // // C // C C C E // viewAllTiles(); var originalLoadJson = Cesium3DTileset.loadJson; spyOn(Cesium3DTileset, "loadJson").and.callFake(function (tilesetUrl) { return originalLoadJson(tilesetUrl).then(function (tilesetJson) { tilesetJson.root.refine = "REPLACE"; tilesetJson.root.children[3].content = undefined; return tilesetJson; }); }); return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { tileset.skipLevelOfDetail = false; var statistics = tileset._statistics; scene.renderForSpecs(); expect(statistics.numberOfCommands).toEqual(3); }); }); it("replacement and additive refinement", function () { // A // A R (not rendered) // R A R A // return Cesium3DTilesTester.loadTileset(scene, tilesetRefinementMix).then( function (tileset) { var statistics = tileset._statistics; expect(statistics.visited).toEqual(7); expect(statistics.numberOfCommands).toEqual(6); } ); }); describe("children bound union optimization", function () { it("does not select visible tiles with invisible children", function () { return Cesium3DTilesTester.loadTileset( scene, tilesetReplacementWithViewerRequestVolumeUrl ).then(function (tileset) { var center = Cartesian3.fromRadians( centerLongitude, centerLatitude, 22.0 ); scene.camera.lookAt(center, new HeadingPitchRange(0.0, 1.57, 1.0)); var root = tileset.root; var childRoot = root.children[0]; scene.renderForSpecs(); expect( childRoot.visibility( scene.frameState, CullingVolume.MASK_INDETERMINATE ) ).not.toEqual(CullingVolume.MASK_OUTSIDE); expect( childRoot.children[0].visibility( scene.frameState, CullingVolume.MASK_INDETERMINATE ) ).toEqual(CullingVolume.MASK_OUTSIDE); expect( childRoot.children[1].visibility( scene.frameState, CullingVolume.MASK_INDETERMINATE ) ).toEqual(CullingVolume.MASK_OUTSIDE); expect( childRoot.children[2].visibility( scene.frameState, CullingVolume.MASK_INDETERMINATE ) ).toEqual(CullingVolume.MASK_OUTSIDE); expect( childRoot.children[3].visibility( scene.frameState, CullingVolume.MASK_INDETERMINATE ) ).toEqual(CullingVolume.MASK_OUTSIDE); expect(tileset._selectedTiles.length).toEqual(0); expect(isSelected(tileset, childRoot)).toBe(false); }); }); it("does not select external tileset whose root has invisible children", function () { return Cesium3DTilesTester.loadTileset( scene, tilesetOfTilesetsUrl ).then(function (tileset) { var center = Cartesian3.fromRadians( centerLongitude, centerLatitude, 50.0 ); scene.camera.lookAt(center, new HeadingPitchRange(0.0, 1.57, 1.0)); var root = tileset.root; var externalRoot = root.children[0]; externalRoot.refine = Cesium3DTileRefine.REPLACE; scene.renderForSpecs(); expect(isSelected(tileset, root)).toBe(false); expect(isSelected(tileset, externalRoot)).toBe(false); expect(root._visible).toBe(false); expect(externalRoot._visible).toBe(false); expect(tileset.statistics.numberOfTilesCulledWithChildrenUnion).toBe( 1 ); }); }); it("does not select visible tiles not meeting SSE with visible children", function () { return Cesium3DTilesTester.loadTileset( scene, tilesetReplacementWithViewerRequestVolumeUrl ).then(function (tileset) { var root = tileset.root; var childRoot = root.children[0]; childRoot.geometricError = 240; scene.renderForSpecs(); expect( childRoot.visibility( scene.frameState, CullingVolume.MASK_INDETERMINATE ) ).not.toEqual(CullingVolume.MASK_OUTSIDE); expect( childRoot.children[0].visibility( scene.frameState, CullingVolume.MASK_INDETERMINATE ) ).not.toEqual(CullingVolume.MASK_OUTSIDE); expect( childRoot.children[1].visibility( scene.frameState, CullingVolume.MASK_INDETERMINATE ) ).not.toEqual(CullingVolume.MASK_OUTSIDE); expect( childRoot.children[2].visibility( scene.frameState, CullingVolume.MASK_INDETERMINATE ) ).not.toEqual(CullingVolume.MASK_OUTSIDE); expect( childRoot.children[3].visibility( scene.frameState, CullingVolume.MASK_INDETERMINATE ) ).not.toEqual(CullingVolume.MASK_OUTSIDE); expect(isSelected(tileset, childRoot)).toBe(false); }); }); it("does select visible tiles meeting SSE with visible children", function () { return Cesium3DTilesTester.loadTileset( scene, tilesetReplacementWithViewerRequestVolumeUrl ).then(function (tileset) { var root = tileset.root; var childRoot = root.children[0]; childRoot.geometricError = 0; // child root should meet SSE and children should not be drawn scene.renderForSpecs(); // wait for load because geometric error has changed return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( function (tileset) { expect( childRoot.visibility( scene.frameState, CullingVolume.MASK_INDETERMINATE ) ).not.toEqual(CullingVolume.MASK_OUTSIDE); expect( childRoot.children[0].visibility( scene.frameState, CullingVolume.MASK_INDETERMINATE ) ).not.toEqual(CullingVolume.MASK_OUTSIDE); expect( childRoot.children[1].visibility( scene.frameState, CullingVolume.MASK_INDETERMINATE ) ).not.toEqual(CullingVolume.MASK_OUTSIDE); expect( childRoot.children[2].visibility( scene.frameState, CullingVolume.MASK_INDETERMINATE ) ).not.toEqual(CullingVolume.MASK_OUTSIDE); expect( childRoot.children[3].visibility( scene.frameState, CullingVolume.MASK_INDETERMINATE ) ).not.toEqual(CullingVolume.MASK_OUTSIDE); expect(isSelected(tileset, childRoot)).toBe(true); } ); }); }); it("does select visible tiles with visible children failing request volumes", function () { viewRootOnly(); return Cesium3DTilesTester.loadTileset( scene, tilesetReplacementWithViewerRequestVolumeUrl, { cullWithChildrenBounds: false, } ).then(function (tileset) { var root = tileset.root; var childRoot = root.children[0]; expect( childRoot.visibility( scene.frameState, CullingVolume.MASK_INDETERMINATE ) ).not.toEqual(CullingVolume.MASK_OUTSIDE); expect( childRoot.children[0].visibility( scene.frameState, CullingVolume.MASK_INDETERMINATE ) ).not.toEqual(CullingVolume.MASK_OUTSIDE); expect( childRoot.children[1].visibility( scene.frameState, CullingVolume.MASK_INDETERMINATE ) ).not.toEqual(CullingVolume.MASK_OUTSIDE); expect( childRoot.children[2].visibility( scene.frameState, CullingVolume.MASK_INDETERMINATE ) ).not.toEqual(CullingVolume.MASK_OUTSIDE); expect( childRoot.children[3].visibility( scene.frameState, CullingVolume.MASK_INDETERMINATE ) ).not.toEqual(CullingVolume.MASK_OUTSIDE); expect(tileset._selectedTiles.length).toEqual(1); expect(isSelected(tileset, childRoot)).toBe(true); }); }); it("does select visible tiles with visible children passing request volumes", function () { return Cesium3DTilesTester.loadTileset( scene, tilesetReplacementWithViewerRequestVolumeUrl ).then(function (tileset) { var root = tileset.root; var childRoot = root.children[0]; childRoot.geometricError = 0; // wait for load because geometric error has changed return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( function (tileset) { expect( childRoot.visibility( scene.frameState, CullingVolume.MASK_INDETERMINATE ) ).not.toEqual(CullingVolume.MASK_OUTSIDE); expect( childRoot.children[0].visibility( scene.frameState, CullingVolume.MASK_INDETERMINATE ) ).not.toEqual(CullingVolume.MASK_OUTSIDE); expect( childRoot.children[1].visibility( scene.frameState, CullingVolume.MASK_INDETERMINATE ) ).not.toEqual(CullingVolume.MASK_OUTSIDE); expect( childRoot.children[2].visibility( scene.frameState, CullingVolume.MASK_INDETERMINATE ) ).not.toEqual(CullingVolume.MASK_OUTSIDE); expect( childRoot.children[3].visibility( scene.frameState, CullingVolume.MASK_INDETERMINATE ) ).not.toEqual(CullingVolume.MASK_OUTSIDE); expect(tileset._selectedTiles.length).toEqual(1); expect(isSelected(tileset, childRoot)).toBe(true); childRoot.geometricError = 200; scene.renderForSpecs(); expect(tileset._selectedTiles.length).toEqual(4); expect(isSelected(tileset, childRoot)).toBe(false); } ); }); }); }); it("loads tileset with external tileset JSON file", function () { // Set view so that no tiles are loaded initially viewNothing(); return Cesium3DTilesTester.loadTileset(scene, tilesetOfTilesetsUrl).then( function (tileset) { // Root points to an external tileset JSON file and has no children until it is requested var root = tileset.root; expect(root.children.length).toEqual(0); // Set view so that root's content is requested viewRootOnly(); scene.renderForSpecs(); return root.contentReadyPromise.then(function () { expect(root.hasTilesetContent).toEqual(true); // Root has one child now, the root of the external tileset expect(root.children.length).toEqual(1); // Check that headers are equal var subtreeRoot = root.children[0]; expect(root.refine).toEqual(subtreeRoot.refine); expect(root.contentBoundingVolume.boundingVolume).toEqual( subtreeRoot.contentBoundingVolume.boundingVolume ); // Check that subtree root has 4 children expect(subtreeRoot.hasTilesetContent).toEqual(false); expect(subtreeRoot.children.length).toEqual(4); }); } ); }); it("preserves query string with external tileset JSON file", function () { // Set view so that no tiles are loaded initially viewNothing(); //Spy on loadWithXhr so we can verify requested urls spyOn(Resource._Implementations, "loadWithXhr").and.callThrough(); var queryParams = "a=1&b=boy"; var expectedUrl = "Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json?" + queryParams; return Cesium3DTilesTester.loadTileset( scene, tilesetOfTilesetsUrl + "?" + queryParams ) .then(function (tileset) { //Make sure tileset JSON file was requested with query parameters expect( Resource._Implementations.loadWithXhr.calls.argsFor(0)[0] ).toEqual(expectedUrl); Resource._Implementations.loadWithXhr.calls.reset(); // Set view so that root's content is requested viewRootOnly(); scene.renderForSpecs(); return tileset.root.contentReadyPromise; }) .then(function () { //Make sure tileset2.json was requested with query parameters and does not use parent tilesetVersion expectedUrl = getAbsoluteUri( "Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset2.json?v=1.2.3&" + queryParams ); expect( Resource._Implementations.loadWithXhr.calls.argsFor(0)[0] ).toEqual(expectedUrl); }); }); it("renders tileset with external tileset JSON file", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetOfTilesetsUrl).then( function (tileset) { var statistics = tileset._statistics; expect(statistics.visited).toEqual(7); // Visits two tiles with tileset content, five tiles with b3dm content expect(statistics.numberOfCommands).toEqual(5); // Render the five tiles with b3dm content } ); }); it("always visits external tileset root", function () { viewRootOnly(); return Cesium3DTilesTester.loadTileset(scene, tilesetOfTilesetsUrl).then( function (tileset) { var statistics = tileset._statistics; expect(statistics.visited).toEqual(2); // Visits external tileset tile, and external tileset root expect(statistics.numberOfCommands).toEqual(1); // Renders external tileset root } ); }); it("set tile color", function () { return Cesium3DTilesTester.loadTileset(scene, noBatchIdsUrl).then( function (tileset) { // Get initial color var color; Cesium3DTilesTester.expectRender(scene, tileset, function (rgba) { color = rgba; }); // Check for color tileset.root.color = Color.RED; Cesium3DTilesTester.expectRender(scene, tileset, function (rgba) { expect(rgba).not.toEqual(color); }); } ); }); it("debugFreezeFrame", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { viewRootOnly(); scene.renderForSpecs(); var statistics = tileset._statistics; expect(statistics.visited).toEqual(1); expect(statistics.numberOfCommands).toEqual(1); tileset.debugFreezeFrame = true; viewAllTiles(); scene.renderForSpecs(); expect(statistics.visited).toEqual(0); // selectTiles returns early, so no tiles are visited expect(statistics.numberOfCommands).toEqual(1); // root tile is still in selectedTiles list }); }); function checkDebugColorizeTiles(url) { CesiumMath.setRandomNumberSeed(0); return Cesium3DTilesTester.loadTileset(scene, url).then(function ( tileset ) { // Get initial color var color; Cesium3DTilesTester.expectRender(scene, tileset, function (rgba) { color = rgba; }); // Check for debug color tileset.debugColorizeTiles = true; Cesium3DTilesTester.expectRender(scene, tileset, function (rgba) { expect(rgba).not.toEqual(color); }); // Check for original color tileset.debugColorizeTiles = false; Cesium3DTilesTester.expectRender(scene, tileset, function (rgba) { expect(rgba).toEqual(color); }); }); } it("debugColorizeTiles for b3dm with batch table", function () { return checkDebugColorizeTiles(withBatchTableUrl); }); it("debugColorizeTiles for b3dm without batch table", function () { return checkDebugColorizeTiles(noBatchIdsUrl); }); it("debugColorizeTiles for i3dm", function () { viewInstances(); return checkDebugColorizeTiles(instancedUrl); }); it("debugColorizeTiles for cmpt", function () { return checkDebugColorizeTiles(compositeUrl); }); it("debugColorizeTiles for pnts with batch table", function () { viewPointCloud(); return checkDebugColorizeTiles(pointCloudBatchedUrl); }); it("debugColorizeTiles for pnts without batch table", function () { viewPointCloud(); return checkDebugColorizeTiles(pointCloudUrl); }); it("debugColorizeTiles for glTF", function () { viewGltfContent(); return checkDebugColorizeTiles(gltfContentUrl); }); it("debugWireframe", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { viewRootOnly(); tileset.debugWireframe = true; scene.renderForSpecs(); var commands = scene.frameState.commandList; var length = commands.length; var i; for (i = 0; i < length; ++i) { expect(commands[i].primitiveType).toEqual(PrimitiveType.LINES); } tileset.debugWireframe = false; scene.renderForSpecs(); commands = scene.frameState.commandList; for (i = 0; i < length; ++i) { expect(commands[i].primitiveType).toEqual(PrimitiveType.TRIANGLES); } }); }); it("debugShowBoundingVolume", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { viewRootOnly(); tileset.debugShowBoundingVolume = true; scene.renderForSpecs(); var statistics = tileset._statistics; expect(statistics.visited).toEqual(1); expect(statistics.numberOfCommands).toEqual(2); // Tile command + bounding volume command tileset.debugShowBoundingVolume = false; scene.renderForSpecs(); expect(statistics.numberOfCommands).toEqual(1); }); }); it("debugShowContentBoundingVolume", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { viewRootOnly(); tileset.debugShowContentBoundingVolume = true; scene.renderForSpecs(); var statistics = tileset._statistics; expect(statistics.visited).toEqual(1); expect(statistics.numberOfCommands).toEqual(2); // Tile command + bounding volume command tileset.debugShowContentBoundingVolume = false; scene.renderForSpecs(); expect(statistics.numberOfCommands).toEqual(1); }); }); it("debugShowViewerRequestVolume", function () { return Cesium3DTilesTester.loadTileset( scene, tilesetWithViewerRequestVolumeUrl ).then(function (tileset) { tileset.debugShowViewerRequestVolume = true; scene.renderForSpecs(); var statistics = tileset._statistics; expect(statistics.visited).toEqual(6); // 1 empty root tile + 4 b3dm tiles + 1 pnts tile expect(statistics.numberOfCommands).toEqual(6); // 5 tile commands + viewer request volume command tileset.debugShowViewerRequestVolume = false; scene.renderForSpecs(); expect(statistics.numberOfCommands).toEqual(5); }); }); it("show tile debug labels with regions", function () { // tilesetUrl has bounding regions return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { tileset.debugShowGeometricError = true; scene.renderForSpecs(); expect(tileset._tileDebugLabels).toBeDefined(); expect(tileset._tileDebugLabels.length).toEqual(5); var root = tileset.root; expect(tileset._tileDebugLabels._labels[0].text).toEqual( "Geometric error: " + root.geometricError ); expect(tileset._tileDebugLabels._labels[1].text).toEqual( "Geometric error: " + root.children[0].geometricError ); expect(tileset._tileDebugLabels._labels[2].text).toEqual( "Geometric error: " + root.children[1].geometricError ); expect(tileset._tileDebugLabels._labels[3].text).toEqual( "Geometric error: " + root.children[2].geometricError ); expect(tileset._tileDebugLabels._labels[4].text).toEqual( "Geometric error: " + root.children[3].geometricError ); tileset.debugShowGeometricError = false; scene.renderForSpecs(); expect(tileset._tileDebugLabels).not.toBeDefined(); }); }); it("show tile debug labels with boxes", function () { // tilesetWithTransformsUrl has bounding boxes return Cesium3DTilesTester.loadTileset( scene, tilesetWithTransformsUrl ).then(function (tileset) { tileset.debugShowGeometricError = true; scene.renderForSpecs(); expect(tileset._tileDebugLabels).toBeDefined(); expect(tileset._tileDebugLabels.length).toEqual(2); var root = tileset.root; expect(tileset._tileDebugLabels._labels[0].text).toEqual( "Geometric error: " + root.geometricError ); expect(tileset._tileDebugLabels._labels[1].text).toEqual( "Geometric error: " + root.children[0].geometricError ); tileset.debugShowGeometricError = false; scene.renderForSpecs(); expect(tileset._tileDebugLabels).not.toBeDefined(); }); }); it("show tile debug labels with bounding spheres", function () { // tilesetWithViewerRequestVolumeUrl has bounding sphere return Cesium3DTilesTester.loadTileset( scene, tilesetWithViewerRequestVolumeUrl ).then(function (tileset) { tileset.debugShowGeometricError = true; scene.renderForSpecs(); var length = tileset._selectedTiles.length; expect(tileset._tileDebugLabels).toBeDefined(); expect(tileset._tileDebugLabels.length).toEqual(length); for (var i = 0; i < length; ++i) { expect(tileset._tileDebugLabels._labels[i].text).toEqual( "Geometric error: " + tileset._selectedTiles[i].geometricError ); } tileset.debugShowGeometricError = false; scene.renderForSpecs(); expect(tileset._tileDebugLabels).not.toBeDefined(); }); }); it("show tile debug labels with rendering statistics", function () { // tilesetUrl has bounding regions return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { tileset.debugShowRenderingStatistics = true; viewRootOnly(); scene.renderForSpecs(); expect(tileset._tileDebugLabels).toBeDefined(); expect(tileset._tileDebugLabels.length).toEqual(1); var content = tileset.root.content; var expected = "Commands: " + tileset.root.commandsLength + "\n" + "Triangles: " + content.trianglesLength + "\n" + "Features: " + content.featuresLength; expect(tileset._tileDebugLabels._labels[0].text).toEqual(expected); tileset.debugShowRenderingStatistics = false; scene.renderForSpecs(); expect(tileset._tileDebugLabels).not.toBeDefined(); }); }); it("show tile debug labels with memory usage", function () { // tilesetUrl has bounding regions return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { tileset.debugShowMemoryUsage = true; viewRootOnly(); scene.renderForSpecs(); expect(tileset._tileDebugLabels).toBeDefined(); expect(tileset._tileDebugLabels.length).toEqual(1); var expected = "Texture Memory: 0\n" + "Geometry Memory: 0.007"; expect(tileset._tileDebugLabels._labels[0].text).toEqual(expected); tileset.debugShowMemoryUsage = false; scene.renderForSpecs(); expect(tileset._tileDebugLabels).not.toBeDefined(); }); }); it("show tile debug labels with all statistics", function () { // tilesetUrl has bounding regions return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { tileset.debugShowGeometricError = true; tileset.debugShowRenderingStatistics = true; tileset.debugShowMemoryUsage = true; tileset.debugShowUrl = true; viewRootOnly(); scene.renderForSpecs(); expect(tileset._tileDebugLabels).toBeDefined(); var expected = "Geometric error: 70\n" + "Commands: 1\n" + "Triangles: 120\n" + "Features: 10\n" + "Texture Memory: 0\n" + "Geometry Memory: 0.007\n" + "Url: parent.b3dm"; expect(tileset._tileDebugLabels._labels[0].text).toEqual(expected); tileset.debugShowGeometricError = false; tileset.debugShowRenderingStatistics = false; tileset.debugShowMemoryUsage = false; tileset.debugShowUrl = false; scene.renderForSpecs(); expect(tileset._tileDebugLabels).not.toBeDefined(); }); }); it("show only picked tile debug label with all stats", function () { // tilesetUrl has bounding regions return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { tileset.debugShowGeometricError = true; tileset.debugShowRenderingStatistics = true; tileset.debugShowMemoryUsage = true; tileset.debugShowUrl = true; tileset.debugPickedTileLabelOnly = true; var scratchPosition = new Cartesian3(1.0, 1.0, 1.0); tileset.debugPickedTile = tileset.root; tileset.debugPickPosition = scratchPosition; scene.renderForSpecs(); expect(tileset._tileDebugLabels).toBeDefined(); var expected = "Geometric error: 70\n" + "Commands: 1\n" + "Triangles: 120\n" + "Features: 10\n" + "Texture Memory: 0\n" + "Geometry Memory: 0.007\n" + "Url: parent.b3dm"; expect(tileset._tileDebugLabels.get(0).text).toEqual(expected); expect(tileset._tileDebugLabels.get(0).position).toEqual( scratchPosition ); tileset.debugPickedTile = undefined; scene.renderForSpecs(); expect(tileset._tileDebugLabels.length).toEqual(0); }); }); it("does not request tiles when picking", function () { viewNothing(); return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { viewRootOnly(); scene.pickForSpecs(); expect(tileset._statistics.numberOfPendingRequests).toEqual(0); scene.renderForSpecs(); expect(tileset._statistics.numberOfPendingRequests).toEqual(1); }); }); it("does not process tiles when picking", function () { var spy = spyOn(Cesium3DTile.prototype, "process").and.callThrough(); viewNothing(); return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { viewRootOnly(); scene.renderForSpecs(); // Request root expect(tileset._statistics.numberOfPendingRequests).toEqual(1); return tileset.root.contentReadyToProcessPromise.then(function () { scene.pickForSpecs(); expect(spy).not.toHaveBeenCalled(); scene.renderForSpecs(); expect(spy).toHaveBeenCalled(); }); }); }); it("does not request tiles when the request scheduler is full", function () { viewRootOnly(); // Root tiles are loaded initially return Cesium3DTilesTester.loadTileset(scene, tilesetUrl, { skipLevelOfDetail: false, }).then(function (tileset) { // Try to load 4 children. Only 3 requests will go through, 1 will be attempted. var oldMaximumRequestsPerServer = RequestScheduler.maximumRequestsPerServer; RequestScheduler.maximumRequestsPerServer = 3; viewAllTiles(); scene.renderForSpecs(); expect(tileset._statistics.numberOfPendingRequests).toEqual(3); expect(tileset._statistics.numberOfAttemptedRequests).toEqual(1); RequestScheduler.maximumRequestsPerServer = oldMaximumRequestsPerServer; }); }); it("load progress events are raised", function () { // [numberOfPendingRequests, numberOfTilesProcessing] var results = [ [1, 0], [0, 1], [0, 0], ]; var spyUpdate = jasmine.createSpy("listener"); viewNothing(); return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { tileset.loadProgress.addEventListener(spyUpdate); viewRootOnly(); return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( function () { expect(spyUpdate.calls.count()).toEqual(3); expect(spyUpdate.calls.allArgs()).toEqual(results); } ); }); }); it("tilesLoaded", function () { options.url = tilesetUrl; var tileset = scene.primitives.add(new Cesium3DTileset(options)); expect(tileset.tilesLoaded).toBe(false); tileset.readyPromise.then(function () { expect(tileset.tilesLoaded).toBe(false); return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( function () { expect(tileset.tilesLoaded).toBe(true); } ); }); }); it("all tiles loaded event is raised", function () { // Called first when only the root is visible and it becomes loaded, and then again when // the rest of the tileset is visible and all tiles are loaded. var spyUpdate1 = jasmine.createSpy("listener"); var spyUpdate2 = jasmine.createSpy("listener"); viewRootOnly(); options.url = tilesetUrl; var tileset = scene.primitives.add(new Cesium3DTileset(options)); tileset.allTilesLoaded.addEventListener(spyUpdate1); tileset.initialTilesLoaded.addEventListener(spyUpdate2); return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( function () { viewAllTiles(); return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( function () { expect(spyUpdate1.calls.count()).toEqual(2); expect(spyUpdate2.calls.count()).toEqual(1); } ); } ); }); it("tile visible event is raised", function () { viewRootOnly(); return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { var spyUpdate = jasmine.createSpy("listener"); tileset.tileVisible.addEventListener(spyUpdate); scene.renderForSpecs(); expect( tileset.root.visibility( scene.frameState, CullingVolume.MASK_INDETERMINATE ) ).not.toEqual(CullingVolume.MASK_OUTSIDE); expect(spyUpdate.calls.count()).toEqual(1); expect(spyUpdate.calls.argsFor(0)[0]).toBe(tileset.root); }); }); it("tile load event is raised", function () { viewNothing(); return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { var spyUpdate = jasmine.createSpy("listener"); tileset.tileLoad.addEventListener(spyUpdate); tileset.maximumMemoryUsage = 0; viewRootOnly(); return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( function () { // Root is loaded expect(spyUpdate.calls.count()).toEqual(1); expect(spyUpdate.calls.argsFor(0)[0]).toBe(tileset.root); spyUpdate.calls.reset(); // Unload from cache viewNothing(); scene.renderForSpecs(); expect(tileset.statistics.numberOfTilesWithContentReady).toEqual(0); // Look at root again viewRootOnly(); return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( function () { expect(spyUpdate.calls.count()).toEqual(1); expect(spyUpdate.calls.argsFor(0)[0]).toBe(tileset.root); } ); } ); }); }); it("tile failed event is raised", function () { viewNothing(); return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { spyOn(Resource._Implementations, "loadWithXhr").and.callFake(function ( url, responseType, method, data, headers, deferred, overrideMimeType ) { deferred.reject("404"); }); var spyUpdate = jasmine.createSpy("listener"); tileset.tileFailed.addEventListener(spyUpdate); tileset.maximumMemoryUsage = 0; viewRootOnly(); return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( function () { expect(spyUpdate.calls.count()).toEqual(1); var arg = spyUpdate.calls.argsFor(0)[0]; expect(arg).toBeDefined(); expect(arg.url).toContain("parent.b3dm"); expect(arg.message).toBeDefined(); } ); }); }); it("destroys", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { var root = tileset.root; expect(tileset.isDestroyed()).toEqual(false); scene.primitives.remove(tileset); expect(tileset.isDestroyed()).toEqual(true); // Check that all tiles are destroyed expect(root.isDestroyed()).toEqual(true); expect(root.children[0].isDestroyed()).toEqual(true); expect(root.children[1].isDestroyed()).toEqual(true); expect(root.children[2].isDestroyed()).toEqual(true); expect(root.children[3].isDestroyed()).toEqual(true); }); }); it("destroys before external tileset JSON file finishes loading", function () { viewNothing(); return Cesium3DTilesTester.loadTileset(scene, tilesetOfTilesetsUrl).then( function (tileset) { var root = tileset.root; viewRootOnly(); scene.renderForSpecs(); // Request external tileset JSON file var statistics = tileset._statistics; expect(statistics.numberOfPendingRequests).toEqual(1); scene.primitives.remove(tileset); return root.contentReadyPromise .then(function (root) { fail("should not resolve"); }) .otherwise(function (error) { // Expect the root to not have added any children from the external tileset JSON file expect(root.children.length).toEqual(0); }); } ); }); it("destroys before tile finishes loading", function () { viewRootOnly(); options.url = tilesetUrl; var tileset = scene.primitives.add(new Cesium3DTileset(options)); return tileset.readyPromise.then(function (tileset) { var root = tileset.root; scene.renderForSpecs(); // Request root scene.primitives.remove(tileset); return root.contentReadyPromise .then(function (content) { fail("should not resolve"); }) .otherwise(function (error) { expect(root._contentState).toBe(Cesium3DTileContentState.FAILED); }); }); }); it("renders with imageBaseLightingFactor", function () { var renderOptions = { scene: scene, time: new JulianDate(2457522.154792), }; return Cesium3DTilesTester.loadTileset(scene, withoutBatchTableUrl).then( function (tileset) { expect(renderOptions).toRenderAndCall(function (rgba) { expect(rgba).not.toEqual([0, 0, 0, 255]); tileset.imageBasedLightingFactor = new Cartesian2(0.0, 0.0); expect(renderOptions).notToRender(rgba); }); } ); }); it("renders with lightColor", function () { var renderOptions = { scene: scene, time: new JulianDate(2457522.154792), }; return Cesium3DTilesTester.loadTileset(scene, withoutBatchTableUrl).then( function (tileset) { expect(renderOptions).toRenderAndCall(function (rgba) { expect(rgba).not.toEqual([0, 0, 0, 255]); tileset.imageBasedLightingFactor = new Cartesian2(0.0, 0.0); expect(renderOptions).toRenderAndCall(function (rgba2) { expect(rgba2).not.toEqual(rgba); tileset.lightColor = new Cartesian3(5.0, 5.0, 5.0); expect(renderOptions).notToRender(rgba2); }); }); } ); }); function testBackFaceCulling(url, setViewOptions) { var renderOptions = { scene: scene, time: new JulianDate(2457522.154792), }; return Cesium3DTilesTester.loadTileset(scene, url).then(function ( tileset ) { scene.camera.setView(setViewOptions); expect(renderOptions).toRenderAndCall(function (rgba) { expect(rgba).toEqual([0, 0, 0, 255]); tileset.backFaceCulling = false; expect(renderOptions).toRenderAndCall(function (rgba2) { expect(rgba2).not.toEqual(rgba); }); }); }); } it("renders b3dm tileset when back face culling is disabled", function () { var setViewOptions = { destination: new Cartesian3( 1215012.6853779217, -4736313.101374343, 4081603.4657718465 ), orientation: new HeadingPitchRoll( 6.283185307179584, -0.49999825387267993, 6.283185307179586 ), endTransform: Matrix4.IDENTITY, }; return testBackFaceCulling(withoutBatchTableUrl, setViewOptions); }); it("renders glTF tileset when back face culling is disabled", function () { var setViewOptions = { destination: new Cartesian3( 1215012.6853779217, -4736313.101374343, 4081603.4657718465 ), orientation: new HeadingPitchRoll( 6.283185307179584, -0.49999825387267993, 6.283185307179586 ), endTransform: Matrix4.IDENTITY, }; return testBackFaceCulling(gltfContentUrl, setViewOptions); }); it("renders i3dm tileset when back face culling is disabled", function () { var setViewOptions = { destination: new Cartesian3( 1215015.8599828142, -4736324.65638894, 4081609.967056947 ), orientation: new HeadingPitchRoll( 6.283185307179585, -0.5000006393986758, 6.283185307179586 ), endTransform: Matrix4.IDENTITY, }; return testBackFaceCulling(instancedUrl, setViewOptions); }); /////////////////////////////////////////////////////////////////////////// // Styling tests it("applies show style to a tileset", function () { return Cesium3DTilesTester.loadTileset(scene, withoutBatchTableUrl).then( function (tileset) { var hideStyle = new Cesium3DTileStyle({ show: "false" }); tileset.style = hideStyle; expect(tileset.style).toBe(hideStyle); expect(scene).toRender([0, 0, 0, 255]); tileset.style = new Cesium3DTileStyle({ show: "true" }); expect(scene).notToRender([0, 0, 0, 255]); } ); }); it("applies show style to a tileset without features", function () { return Cesium3DTilesTester.loadTileset(scene, noBatchIdsUrl).then( function (tileset) { var hideStyle = new Cesium3DTileStyle({ show: "false" }); tileset.style = hideStyle; expect(tileset.style).toBe(hideStyle); expect(scene).toRender([0, 0, 0, 255]); tileset.style = new Cesium3DTileStyle({ show: "true" }); expect(scene).notToRender([0, 0, 0, 255]); } ); }); it("applies style with complex show expression to a tileset", function () { return Cesium3DTilesTester.loadTileset(scene, withBatchTableUrl).then( function (tileset) { // Each feature in the b3dm file has an id property from 0 to 9 // ${id} >= 10 will always evaluate to false tileset.style = new Cesium3DTileStyle({ show: "${id} >= 50 * 2" }); expect(scene).toRender([0, 0, 0, 255]); // ${id} < 10 will always evaluate to true tileset.style = new Cesium3DTileStyle({ show: "${id} < 200 / 2" }); expect(scene).notToRender([0, 0, 0, 255]); } ); }); it("applies show style to a tileset with a composite tile", function () { return Cesium3DTilesTester.loadTileset(scene, compositeUrl).then( function (tileset) { tileset.style = new Cesium3DTileStyle({ show: "false" }); expect(scene).toRender([0, 0, 0, 255]); tileset.style = new Cesium3DTileStyle({ show: "true" }); expect(scene).notToRender([0, 0, 0, 255]); } ); }); it("applies show style to a tileset with glTF content", function () { return Cesium3DTilesTester.loadTileset(scene, gltfContentUrl).then( function (tileset) { viewGltfContent(); var hideStyle = new Cesium3DTileStyle({ show: "false" }); tileset.style = hideStyle; expect(tileset.style).toBe(hideStyle); expect(scene).toRender([0, 0, 0, 255]); tileset.style = new Cesium3DTileStyle({ show: "true" }); expect(scene).notToRender([0, 0, 0, 255]); } ); }); function expectColorStyle(tileset) { var color; expect(scene).toRenderAndCall(function (rgba) { color = rgba; }); tileset.style = new Cesium3DTileStyle({ color: 'color("blue")' }); expect(scene).toRenderAndCall(function (rgba) { expect(rgba[0]).toEqual(0); expect(rgba[1]).toEqual(0); expect(rgba[2]).toBeGreaterThan(0); expect(rgba[3]).toEqual(255); }); // set color to transparent tileset.style = new Cesium3DTileStyle({ color: 'color("blue", 0.0)' }); expect(scene).toRender([0, 0, 0, 255]); tileset.style = new Cesium3DTileStyle({ color: 'color("cyan")' }); expect(scene).toRenderAndCall(function (rgba) { expect(rgba[0]).toEqual(0); expect(rgba[1]).toBeGreaterThan(0); expect(rgba[2]).toBeGreaterThan(0); expect(rgba[3]).toEqual(255); }); // Remove style tileset.style = undefined; expect(scene).toRender(color); } it("applies color style to a tileset", function () { return Cesium3DTilesTester.loadTileset(scene, withoutBatchTableUrl).then( function (tileset) { expectColorStyle(tileset); } ); }); it("applies color style to a tileset with translucent tiles", function () { return Cesium3DTilesTester.loadTileset(scene, translucentUrl).then( function (tileset) { expectColorStyle(tileset); } ); }); it("applies color style to a tileset with translucent and opaque tiles", function () { return Cesium3DTilesTester.loadTileset( scene, translucentOpaqueMixUrl ).then(function (tileset) { expectColorStyle(tileset); }); }); it("applies color style to tileset without features", function () { return Cesium3DTilesTester.loadTileset(scene, noBatchIdsUrl).then( function (tileset) { expectColorStyle(tileset); } ); }); it("applies color style to tileset with glTF content", function () { return Cesium3DTilesTester.loadTileset(scene, gltfContentUrl).then( function (tileset) { viewGltfContent(); expectColorStyle(tileset); } ); }); it("applies style when feature properties change", function () { return Cesium3DTilesTester.loadTileset(scene, withBatchTableUrl).then( function (tileset) { // Initially, all feature ids are less than 10 tileset.style = new Cesium3DTileStyle({ show: "${id} < 10" }); expect(scene).notToRender([0, 0, 0, 255]); // Change feature ids so the show expression will evaluate to false var content = tileset.root.content; var length = content.featuresLength; var i; var feature; for (i = 0; i < length; ++i) { feature = content.getFeature(i); feature.setProperty("id", feature.getProperty("id") + 10); } expect(scene).toRender([0, 0, 0, 255]); // Change ids back for (i = 0; i < length; ++i) { feature = content.getFeature(i); feature.setProperty("id", feature.getProperty("id") - 10); } expect(scene).notToRender([0, 0, 0, 255]); } ); }); it("applies style when tile is selected after new style is applied", function () { return Cesium3DTilesTester.loadTileset(scene, withBatchTableUrl).then( function (tileset) { var feature = tileset.root.content.getFeature(0); tileset.style = new Cesium3DTileStyle({ color: 'color("red")' }); scene.renderForSpecs(); expect(feature.color).toEqual(Color.RED); tileset.style = new Cesium3DTileStyle({ color: 'color("blue")' }); scene.renderForSpecs(); expect(feature.color).toEqual(Color.BLUE); viewNothing(); tileset.style = new Cesium3DTileStyle({ color: 'color("lime")' }); scene.renderForSpecs(); expect(feature.color).toEqual(Color.BLUE); // Hasn't been selected yet viewAllTiles(); scene.renderForSpecs(); expect(feature.color).toEqual(Color.LIME); // Feature's show property is preserved if the style hasn't changed and the feature is newly selected feature.show = false; scene.renderForSpecs(); expect(feature.show).toBe(false); viewNothing(); scene.renderForSpecs(); expect(feature.show).toBe(false); viewAllTiles(); expect(feature.show).toBe(false); } ); }); it("does not reapply style during pick pass", function () { return Cesium3DTilesTester.loadTileset(scene, withBatchTableUrl).then( function (tileset) { tileset.style = new Cesium3DTileStyle({ color: 'color("red")' }); scene.renderForSpecs(); expect( tileset._statisticsPerPass[Cesium3DTilePass.RENDER] .numberOfTilesStyled ).toBe(1); scene.pickForSpecs(); expect( tileset._statisticsPerPass[Cesium3DTilePass.PICK] .numberOfTilesStyled ).toBe(0); } ); }); it("applies style with complex color expression to a tileset", function () { return Cesium3DTilesTester.loadTileset(scene, withBatchTableUrl).then( function (tileset) { // Each feature in the b3dm file has an id property from 0 to 9 // ${id} >= 10 will always evaluate to false tileset.style = new Cesium3DTileStyle({ color: '(${id} >= 50 * 2) ? color("red") : color("blue")', }); expect(scene).toRenderAndCall(function (rgba) { expect(rgba[0]).toEqual(0); expect(rgba[1]).toEqual(0); expect(rgba[2]).toBeGreaterThan(0); expect(rgba[3]).toEqual(255); }); // ${id} < 10 will always evaluate to true tileset.style = new Cesium3DTileStyle({ color: '(${id} < 50 * 2) ? color("red") : color("blue")', }); expect(scene).toRenderAndCall(function (rgba) { expect(rgba[0]).toBeGreaterThan(0); expect(rgba[1]).toEqual(0); expect(rgba[2]).toEqual(0); expect(rgba[3]).toEqual(255); }); } ); }); it("applies conditional color style to a tileset", function () { return Cesium3DTilesTester.loadTileset(scene, withBatchTableUrl).then( function (tileset) { // ${id} < 10 will always evaluate to true tileset.style = new Cesium3DTileStyle({ color: { conditions: [ ["${id} < 10", 'color("red")'], ["true", 'color("blue")'], ], }, }); expect(scene).toRenderAndCall(function (rgba) { expect(rgba[0]).toBeGreaterThan(0); expect(rgba[1]).toEqual(0); expect(rgba[2]).toEqual(0); expect(rgba[3]).toEqual(255); }); // ${id}>= 10 will always evaluate to false tileset.style = new Cesium3DTileStyle({ color: { conditions: [ ["${id} >= 10", 'color("red")'], ["true", 'color("blue")'], ], }, }); expect(scene).toRenderAndCall(function (rgba) { expect(rgba[0]).toEqual(0); expect(rgba[1]).toEqual(0); expect(rgba[2]).toBeGreaterThan(0); expect(rgba[3]).toEqual(255); }); } ); }); it("handle else case when applying conditional color style to a tileset", function () { return Cesium3DTilesTester.loadTileset(scene, withBatchTableUrl).then( function (tileset) { tileset.style = new Cesium3DTileStyle({ color: { conditions: [["${id} > 0", 'color("black")']], }, }); scene.renderForSpecs(); expect(tileset.root.content.getFeature(0).color).toEqual(Color.WHITE); expect(tileset.root.content.getFeature(1).color).toEqual(Color.BLACK); } ); }); it("handle else case when applying conditional show to a tileset", function () { return Cesium3DTilesTester.loadTileset(scene, withBatchTableUrl).then( function (tileset) { tileset.style = new Cesium3DTileStyle({ show: { conditions: [["${id} > 0", "true"]], }, }); scene.renderForSpecs(); expect(tileset.root.content.getFeature(0).show).toBe(true); expect(tileset.root.content.getFeature(1).show).toBe(true); tileset.style = new Cesium3DTileStyle({ show: { conditions: [["${id} > 0", "false"]], }, }); scene.renderForSpecs(); expect(tileset.root.content.getFeature(0).show).toBe(true); expect(tileset.root.content.getFeature(1).show).toBe(false); } ); }); it("loads style from uri", function () { return Cesium3DTilesTester.loadTileset(scene, withBatchTableUrl).then( function (tileset) { // ${id} < 10 will always evaluate to true tileset.style = new Cesium3DTileStyle(styleUrl); return tileset.style.readyPromise .then(function (style) { expect(scene).toRenderAndCall(function (rgba) { expect(rgba[0]).toBeGreaterThan(0); expect(rgba[1]).toEqual(0); expect(rgba[2]).toEqual(0); expect(rgba[3]).toEqual(255); }); }) .otherwise(function (error) { expect(error).not.toBeDefined(); }); } ); }); it("applies custom style to a tileset", function () { var style = new Cesium3DTileStyle(); style.show = { evaluate: function (feature) { return this._value; }, _value: false, }; style.color = { evaluateColor: function (feature, result) { return Color.clone(Color.WHITE, result); }, }; return Cesium3DTilesTester.loadTileset(scene, withoutBatchTableUrl).then( function (tileset) { tileset.style = style; expect(tileset.style).toBe(style); expect(scene).toRender([0, 0, 0, 255]); style.show._value = true; tileset.makeStyleDirty(); expect(scene).notToRender([0, 0, 0, 255]); } ); }); it("doesn't re-evaluate style during the next update", function () { return Cesium3DTilesTester.loadTileset(scene, withBatchTableUrl).then( function (tileset) { tileset.show = false; tileset.preloadWhenHidden = true; tileset.style = new Cesium3DTileStyle({ color: 'color("red")' }); scene.renderForSpecs(); var statistics = tileset._statisticsPerPass[Cesium3DTilePass.PRELOAD]; expect(statistics.numberOfTilesStyled).toBe(1); scene.renderForSpecs(); expect(statistics.numberOfTilesStyled).toBe(0); } ); }); it("doesn't re-evaluate style if the style being set is the same as the active style", function () { return Cesium3DTilesTester.loadTileset(scene, withBatchTableUrl).then( function (tileset) { var style = new Cesium3DTileStyle({ color: 'color("red")' }); tileset.style = style; scene.renderForSpecs(); var statistics = tileset._statisticsPerPass[Cesium3DTilePass.RENDER]; expect(statistics.numberOfTilesStyled).toBe(1); tileset.style = style; scene.renderForSpecs(); expect(statistics.numberOfTilesStyled).toBe(0); } ); }); function testColorBlendMode(url) { return Cesium3DTilesTester.loadTileset(scene, url).then(function ( tileset ) { tileset.luminanceAtZenith = undefined; // Check that the feature is red var sourceRed; var sourceGreen; var renderOptions = { scene: scene, time: new JulianDate(2457522.154792), }; expect(renderOptions).toRenderAndCall(function (rgba) { sourceRed = rgba[0]; sourceGreen = rgba[1]; }); expect(renderOptions).toRenderAndCall(function (rgba) { expect(rgba[0]).toBeGreaterThan(200); expect(rgba[1]).toBeLessThan(25); expect(rgba[2]).toBeLessThan(25); expect(rgba[3]).toEqual(255); }); // Use HIGHLIGHT blending tileset.colorBlendMode = Cesium3DTileColorBlendMode.HIGHLIGHT; // Style with dark yellow. Expect the red channel to be darker than before. tileset.style = new Cesium3DTileStyle({ color: "rgb(128, 128, 0)", }); expect(renderOptions).toRenderAndCall(function (rgba) { expect(rgba[0]).toBeGreaterThan(100); expect(rgba[0]).toBeLessThan(sourceRed); expect(rgba[1]).toBeLessThan(25); expect(rgba[2]).toBeLessThan(25); expect(rgba[3]).toEqual(255); }); // Style with yellow + alpha. Expect the red channel to be darker than before. tileset.style = new Cesium3DTileStyle({ color: "rgba(255, 255, 0, 0.5)", }); expect(renderOptions).toRenderAndCall(function (rgba) { expect(rgba[0]).toBeGreaterThan(100); expect(rgba[0]).toBeLessThan(sourceRed); expect(rgba[1]).toBeLessThan(25); expect(rgba[2]).toBeLessThan(25); expect(rgba[3]).toEqual(255); }); // Use REPLACE blending tileset.colorBlendMode = Cesium3DTileColorBlendMode.REPLACE; // Style with dark yellow. Expect the red and green channels to be roughly dark yellow. tileset.style = new Cesium3DTileStyle({ color: "rgb(128, 128, 0)", }); var replaceRed; var replaceGreen; expect(renderOptions).toRenderAndCall(function (rgba) { replaceRed = rgba[0]; replaceGreen = rgba[1]; expect(rgba[0]).toBeGreaterThan(100); expect(rgba[0]).toBeLessThan(255); expect(rgba[1]).toBeGreaterThan(100); expect(rgba[1]).toBeLessThan(255); expect(rgba[2]).toBeLessThan(25); expect(rgba[3]).toEqual(255); }); // Style with yellow + alpha. Expect the red and green channels to be a shade of yellow. tileset.style = new Cesium3DTileStyle({ color: "rgba(255, 255, 0, 0.5)", }); expect(renderOptions).toRenderAndCall(function (rgba) { expect(rgba[0]).toBeGreaterThan(100); expect(rgba[0]).toBeLessThan(255); expect(rgba[1]).toBeGreaterThan(100); expect(rgba[1]).toBeLessThan(255); expect(rgba[2]).toBeLessThan(25); expect(rgba[3]).toEqual(255); }); // Use MIX blending tileset.colorBlendMode = Cesium3DTileColorBlendMode.MIX; tileset.colorBlendAmount = 0.5; // Style with dark yellow. Expect color to be a mix of the source and style colors. tileset.style = new Cesium3DTileStyle({ color: "rgb(128, 128, 0)", }); var mixRed; var mixGreen; expect(renderOptions).toRenderAndCall(function (rgba) { mixRed = rgba[0]; mixGreen = rgba[1]; expect(rgba[0]).toBeGreaterThan(replaceRed); expect(rgba[0]).toBeLessThan(sourceRed); expect(rgba[1]).toBeGreaterThan(sourceGreen); expect(rgba[1]).toBeLessThan(replaceGreen); expect(rgba[2]).toBeLessThan(25); expect(rgba[3]).toEqual(255); }); // Set colorBlendAmount to 0.25. Expect color to be closer to the source color. tileset.colorBlendAmount = 0.25; expect(renderOptions).toRenderAndCall(function (rgba) { expect(rgba[0]).toBeGreaterThan(mixRed); expect(rgba[0]).toBeLessThan(sourceRed); expect(rgba[1]).toBeGreaterThan(0); expect(rgba[1]).toBeLessThan(mixGreen); expect(rgba[2]).toBeLessThan(25); expect(rgba[3]).toEqual(255); }); // Set colorBlendAmount to 0.0. Expect color to equal the source color tileset.colorBlendAmount = 0.0; expect(renderOptions).toRenderAndCall(function (rgba) { expect(rgba[0]).toEqual(sourceRed); expect(rgba[1]).toBeLessThan(25); expect(rgba[2]).toBeLessThan(25); expect(rgba[3]).toEqual(255); }); // Set colorBlendAmount to 1.0. Expect color to equal the style color tileset.colorBlendAmount = 1.0; expect(renderOptions).toRenderAndCall(function (rgba) { expect(rgba[0]).toEqual(replaceRed); expect(rgba[1]).toEqual(replaceGreen); expect(rgba[2]).toBeLessThan(25); expect(rgba[3]).toEqual(255); }); // Style with yellow + alpha. Expect color to be a mix of the source and style colors. tileset.colorBlendAmount = 0.5; tileset.style = new Cesium3DTileStyle({ color: "rgba(255, 255, 0, 0.5)", }); expect(renderOptions).toRenderAndCall(function (rgba) { expect(rgba[0]).toBeGreaterThan(0); expect(rgba[1]).toBeGreaterThan(0); expect(rgba[2]).toBeLessThan(25); expect(rgba[3]).toEqual(255); }); }); } it("sets colorBlendMode", function () { return testColorBlendMode(colorsUrl); }); it("sets colorBlendMode when vertex texture fetch is not supported", function () { // Disable VTF var maximumVertexTextureImageUnits = ContextLimits.maximumVertexTextureImageUnits; ContextLimits._maximumVertexTextureImageUnits = 0; return testColorBlendMode(colorsUrl).then(function () { // Re-enable VTF ContextLimits._maximumVertexTextureImageUnits = maximumVertexTextureImageUnits; }); }); it("sets colorBlendMode for textured tileset", function () { return testColorBlendMode(texturedUrl); }); it("sets colorBlendMode for instanced tileset", function () { viewInstances(); return testColorBlendMode(instancedRedMaterialUrl); }); it("sets colorBlendMode for vertex color tileset", function () { return testColorBlendMode(batchedVertexColorsUrl); }); /////////////////////////////////////////////////////////////////////////// // Cache replacement tests it("Unload all cached tiles not required to meet SSE using maximumMemoryUsage", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { tileset.maximumMemoryUsage = 0; // Render parent and four children (using additive refinement) viewAllTiles(); scene.renderForSpecs(); var statistics = tileset._statistics; expect(statistics.numberOfCommands).toEqual(5); expect(statistics.numberOfTilesWithContentReady).toEqual(5); // Five loaded tiles expect(tileset.totalMemoryUsageInBytes).toEqual(37200); // Specific to this tileset // Zoom out so only root tile is needed to meet SSE. This unloads // the four children since the maximum memory usage is zero. viewRootOnly(); scene.renderForSpecs(); expect(statistics.numberOfCommands).toEqual(1); expect(statistics.numberOfTilesWithContentReady).toEqual(1); expect(tileset.totalMemoryUsageInBytes).toEqual(7440); // Specific to this tileset // Zoom back in so all four children are re-requested. viewAllTiles(); return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( function () { expect(statistics.numberOfCommands).toEqual(5); expect(statistics.numberOfTilesWithContentReady).toEqual(5); // Five loaded tiles expect(tileset.totalMemoryUsageInBytes).toEqual(37200); // Specific to this tileset } ); }); }); it("Unload some cached tiles not required to meet SSE using maximumMemoryUsage", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { tileset.maximumMemoryUsage = 0.025; // Just enough memory to allow 3 tiles to remain // Render parent and four children (using additive refinement) viewAllTiles(); scene.renderForSpecs(); var statistics = tileset._statistics; expect(statistics.numberOfCommands).toEqual(5); expect(statistics.numberOfTilesWithContentReady).toEqual(5); // Five loaded tiles // Zoom out so only root tile is needed to meet SSE. This unloads // two of the four children so three tiles are still loaded (the // root and two children) since the maximum memory usage is sufficient. viewRootOnly(); scene.renderForSpecs(); expect(statistics.numberOfCommands).toEqual(1); expect(statistics.numberOfTilesWithContentReady).toEqual(3); // Zoom back in so the two children are re-requested. viewAllTiles(); return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( function () { expect(statistics.numberOfCommands).toEqual(5); expect(statistics.numberOfTilesWithContentReady).toEqual(5); // Five loaded tiles } ); }); }); it("Unloads cached tiles outside of the view frustum using maximumMemoryUsage", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { tileset.maximumMemoryUsage = 0; scene.renderForSpecs(); var statistics = tileset._statistics; expect(statistics.numberOfCommands).toEqual(5); expect(statistics.numberOfTilesWithContentReady).toEqual(5); viewSky(); // All tiles are unloaded scene.renderForSpecs(); expect(statistics.numberOfCommands).toEqual(0); expect(statistics.numberOfTilesWithContentReady).toEqual(0); // Reset camera so all tiles are reloaded viewAllTiles(); return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( function () { expect(statistics.numberOfCommands).toEqual(5); expect(statistics.numberOfTilesWithContentReady).toEqual(5); } ); }); }); it("Unloads cached tiles in a tileset with external tileset JSON file using maximumMemoryUsage", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetOfTilesetsUrl).then( function (tileset) { var statistics = tileset._statistics; var cacheList = tileset._cache._list; tileset.maximumMemoryUsage = 0.02; scene.renderForSpecs(); expect(statistics.numberOfCommands).toEqual(5); expect(statistics.numberOfTilesWithContentReady).toEqual(5); expect(cacheList.length - 1).toEqual(5); // Only tiles with content are on the replacement list. -1 for sentinel. // Zoom out so only root tile is needed to meet SSE. This unloads // all tiles except the root and one of the b3dm children viewRootOnly(); scene.renderForSpecs(); expect(statistics.numberOfCommands).toEqual(1); expect(statistics.numberOfTilesWithContentReady).toEqual(2); expect(cacheList.length - 1).toEqual(2); // Reset camera so all tiles are reloaded viewAllTiles(); return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( function () { expect(statistics.numberOfCommands).toEqual(5); expect(statistics.numberOfTilesWithContentReady).toEqual(5); expect(cacheList.length - 1).toEqual(5); } ); } ); }); it("Unloads cached tiles in a tileset with empty tiles using maximumMemoryUsage", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetEmptyRootUrl).then( function (tileset) { var statistics = tileset._statistics; tileset.maximumMemoryUsage = 0.02; scene.renderForSpecs(); expect(statistics.numberOfCommands).toEqual(4); expect(statistics.numberOfTilesWithContentReady).toEqual(4); // 4 children with b3dm content (does not include empty root) viewSky(); // Unload tiles to meet cache size scene.renderForSpecs(); expect(statistics.numberOfCommands).toEqual(0); expect(statistics.numberOfTilesWithContentReady).toEqual(2); // 2 children with b3dm content (does not include empty root) // Reset camera so all tiles are reloaded viewAllTiles(); return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( function () { expect(statistics.numberOfCommands).toEqual(4); expect(statistics.numberOfTilesWithContentReady).toEqual(4); } ); } ); }); it("Unload cached tiles when a tileset uses replacement refinement using maximumMemoryUsage", function () { // No children have content, but all grandchildren have content // // C // E E // C C C C // return Cesium3DTilesTester.loadTileset( scene, tilesetReplacement1Url ).then(function (tileset) { tileset.maximumMemoryUsage = 0; // Only root needs to be visible // Render parent and four children (using additive refinement) viewAllTiles(); scene.renderForSpecs(); var statistics = tileset._statistics; expect(statistics.numberOfCommands).toEqual(4); // 4 grandchildren. Root is replaced. expect(statistics.numberOfTilesWithContentReady).toEqual(5); // Root + four grandchildren (does not include empty children) // Zoom out so only root tile is needed to meet SSE. This unloads // all grandchildren since the max number of loaded tiles is one. viewRootOnly(); scene.renderForSpecs(); expect(statistics.numberOfCommands).toEqual(1); expect(statistics.numberOfTilesWithContentReady).toEqual(1); // Zoom back in so the four children are re-requested. viewAllTiles(); return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( function () { expect(statistics.numberOfCommands).toEqual(4); expect(statistics.numberOfTilesWithContentReady).toEqual(5); } ); }); }); it("Explicitly unloads cached tiles with trimLoadedTiles", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { tileset.maximumMemoryUsage = 0.05; // Render parent and four children (using additive refinement) viewAllTiles(); scene.renderForSpecs(); var statistics = tileset._statistics; expect(statistics.numberOfCommands).toEqual(5); expect(statistics.numberOfTilesWithContentReady).toEqual(5); // Five loaded tiles // Zoom out so only root tile is needed to meet SSE. The children // are not unloaded since max number of loaded tiles is five. viewRootOnly(); scene.renderForSpecs(); expect(statistics.numberOfCommands).toEqual(1); expect(statistics.numberOfTilesWithContentReady).toEqual(5); tileset.trimLoadedTiles(); scene.renderForSpecs(); expect(statistics.numberOfCommands).toEqual(1); expect(statistics.numberOfTilesWithContentReady).toEqual(1); }); }); it("tileUnload event is raised", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { tileset.maximumMemoryUsage = 0; // Render parent and four children (using additive refinement) viewAllTiles(); scene.renderForSpecs(); var statistics = tileset._statistics; expect(statistics.numberOfCommands).toEqual(5); expect(statistics.numberOfTilesWithContentReady).toEqual(5); // Five loaded tiles // Zoom out so only root tile is needed to meet SSE. All the // children are unloaded since max number of loaded tiles is one. viewRootOnly(); var spyUpdate = jasmine.createSpy("listener"); tileset.tileUnload.addEventListener(spyUpdate); scene.renderForSpecs(); expect( tileset.root.visibility( scene.frameState, CullingVolume.MASK_INDETERMINATE ) ).not.toEqual(CullingVolume.MASK_OUTSIDE); expect(spyUpdate.calls.count()).toEqual(4); expect(spyUpdate.calls.argsFor(0)[0]).toBe(tileset.root.children[0]); expect(spyUpdate.calls.argsFor(1)[0]).toBe(tileset.root.children[1]); expect(spyUpdate.calls.argsFor(2)[0]).toBe(tileset.root.children[2]); expect(spyUpdate.calls.argsFor(3)[0]).toBe(tileset.root.children[3]); }); }); it("maximumMemoryUsage throws when negative", function () { var tileset = new Cesium3DTileset({ url: tilesetUrl, }); expect(function () { tileset.maximumMemoryUsage = -1; }).toThrowDeveloperError(); }); it("maximumScreenSpaceError throws when negative", function () { var tileset = new Cesium3DTileset({ url: tilesetUrl, }); expect(function () { tileset.maximumScreenSpaceError = -1; }).toThrowDeveloperError(); }); it("propagates tile transform down the tree", function () { var b3dmCommands = 1; // when instancing is supported, there is a single draw command, // else each instance is a separate command. var i3dmCommands = scene.context.instancedArrays ? 1 : 25; var totalCommands = b3dmCommands + i3dmCommands; return Cesium3DTilesTester.loadTileset( scene, tilesetWithTransformsUrl ).then(function (tileset) { var statistics = tileset._statistics; var root = tileset.root; var rootTransform = Matrix4.unpack(root._header.transform); var child = root.children[0]; var childTransform = Matrix4.unpack(child._header.transform); var computedTransform = Matrix4.multiply( rootTransform, childTransform, new Matrix4() ); expect(statistics.numberOfCommands).toBe(totalCommands); expect(root.computedTransform).toEqual(rootTransform); expect(child.computedTransform).toEqual(computedTransform); // Set the tileset's modelMatrix var tilesetTransform = Matrix4.fromTranslation( new Cartesian3(0.0, 1.0, 0.0) ); tileset.modelMatrix = tilesetTransform; computedTransform = Matrix4.multiply( tilesetTransform, computedTransform, computedTransform ); scene.renderForSpecs(); expect(child.computedTransform).toEqual(computedTransform); // Set the modelMatrix somewhere off screen tileset.modelMatrix = Matrix4.fromTranslation( new Cartesian3(0.0, 100000.0, 0.0) ); scene.renderForSpecs(); expect(statistics.numberOfCommands).toBe(0); // Now bring it back tileset.modelMatrix = Matrix4.IDENTITY; scene.renderForSpecs(); expect(statistics.numberOfCommands).toBe(totalCommands); // Do the same steps for a tile transform child.transform = Matrix4.fromTranslation( new Cartesian3(0.0, 100000.0, 0.0) ); scene.renderForSpecs(); expect(statistics.numberOfCommands).toBe(1); child.transform = Matrix4.IDENTITY; scene.renderForSpecs(); expect(statistics.numberOfCommands).toBe(totalCommands); }); }); var skipLevelOfDetailOptions = { skipLevelOfDetail: true, }; it("does not mark tileset as refining when tiles have selection depth 0", function () { viewRootOnly(); return Cesium3DTilesTester.loadTileset( scene, tilesetUrl, skipLevelOfDetailOptions ).then(function (tileset) { viewAllTiles(); scene.renderForSpecs(); var statistics = tileset._statistics; expect(statistics.numberOfTilesWithContentReady).toEqual(1); expect(tileset._selectedTiles[0]._selectionDepth).toEqual(0); expect(tileset._hasMixedContent).toBe(false); return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( function (tileset) { expect(statistics.numberOfTilesWithContentReady).toEqual(5); expect(tileset._hasMixedContent).toBe(false); } ); }); }); it("marks tileset as mixed when tiles have nonzero selection depth", function () { return Cesium3DTilesTester.loadTileset( scene, tilesetReplacement3Url, skipLevelOfDetailOptions ).then(function (tileset) { var statistics = tileset._statistics; tileset.root.children[0].children[0].children[0].unloadContent(); tileset.root.children[0].children[0].children[1].unloadContent(); tileset.root.children[0].children[0].children[2].unloadContent(); statistics.numberOfTilesWithContentReady -= 3; scene.renderForSpecs(); expect(tileset._hasMixedContent).toBe(true); expect(statistics.numberOfTilesWithContentReady).toEqual(2); expect( tileset.root.children[0].children[0].children[3]._selectionDepth ).toEqual(1); expect(tileset.root._selectionDepth).toEqual(0); return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( function (tileset) { expect(statistics.numberOfTilesWithContentReady).toEqual(5); expect(tileset._hasMixedContent).toBe(false); } ); }); }); it("adds stencil clear command first when unresolved", function () { return Cesium3DTilesTester.loadTileset( scene, tilesetReplacement3Url, skipLevelOfDetailOptions ).then(function (tileset) { tileset.root.children[0].children[0].children[0].unloadContent(); tileset.root.children[0].children[0].children[1].unloadContent(); tileset.root.children[0].children[0].children[2].unloadContent(); scene.renderForSpecs(); var commandList = scene.frameState.commandList; expect(commandList[0]).toBeInstanceOf(ClearCommand); expect(commandList[0].stencil).toBe(0); }); }); it("creates duplicate backface commands", function () { return Cesium3DTilesTester.loadTileset( scene, tilesetReplacement3Url, skipLevelOfDetailOptions ).then(function (tileset) { var statistics = tileset._statistics; var root = tileset.root; tileset.root.children[0].children[0].children[0].unloadContent(); tileset.root.children[0].children[0].children[1].unloadContent(); tileset.root.children[0].children[0].children[2].unloadContent(); scene.renderForSpecs(); // 2 for root tile, 1 for child, 1 for stencil clear // Tiles that are marked as finalResolution, including leaves, do not create back face commands expect(statistics.numberOfCommands).toEqual(4); expect(isSelected(tileset, root)).toBe(true); expect(root._finalResolution).toBe(false); expect( isSelected(tileset, root.children[0].children[0].children[3]) ).toBe(true); expect(root.children[0].children[0].children[3]._finalResolution).toBe( true ); expect(tileset._hasMixedContent).toBe(true); var commandList = scene.frameState.commandList; var rs = commandList[1].renderState; expect(rs.cull.enabled).toBe(true); expect(rs.cull.face).toBe(CullFace.FRONT); expect(rs.polygonOffset.enabled).toBe(true); }); }); it("does not create duplicate backface commands if no selected descendants", function () { return Cesium3DTilesTester.loadTileset( scene, tilesetReplacement3Url, skipLevelOfDetailOptions ).then(function (tileset) { var statistics = tileset._statistics; var root = tileset.root; tileset.root.children[0].children[0].children[0].unloadContent(); tileset.root.children[0].children[0].children[1].unloadContent(); tileset.root.children[0].children[0].children[2].unloadContent(); tileset.root.children[0].children[0].children[3].unloadContent(); scene.renderForSpecs(); // 2 for root tile, 1 for child, 1 for stencil clear expect(statistics.numberOfCommands).toEqual(1); expect(isSelected(tileset, root)).toBe(true); expect(root._finalResolution).toBe(true); expect( isSelected(tileset, root.children[0].children[0].children[0]) ).toBe(false); expect( isSelected(tileset, root.children[0].children[0].children[1]) ).toBe(false); expect( isSelected(tileset, root.children[0].children[0].children[2]) ).toBe(false); expect( isSelected(tileset, root.children[0].children[0].children[3]) ).toBe(false); expect(tileset._hasMixedContent).toBe(false); }); }); it("does not add commands or stencil clear command with no selected tiles", function () { options.url = tilesetUrl; options.skipLevelOfDetail = true; var tileset = scene.primitives.add(new Cesium3DTileset(options)); scene.renderForSpecs(); var statistics = tileset._statistics; expect(tileset._selectedTiles.length).toEqual(0); expect(statistics.numberOfCommands).toEqual(0); }); it("does not add stencil clear command or backface commands when fully resolved", function () { viewAllTiles(); return Cesium3DTilesTester.loadTileset( scene, tilesetReplacement3Url, skipLevelOfDetailOptions ).then(function (tileset) { var statistics = tileset._statistics; expect(statistics.numberOfCommands).toEqual( tileset._selectedTiles.length ); var commandList = scene.frameState.commandList; var length = commandList.length; for (var i = 0; i < length; ++i) { var command = commandList[i]; expect(command).not.toBeInstanceOf(ClearCommand); expect(command.renderState.cull.face).not.toBe(CullFace.FRONT); } }); }); it("loadSiblings", function () { viewBottomLeft(); return Cesium3DTilesTester.loadTileset(scene, tilesetReplacement3Url, { skipLevelOfDetail: true, loadSiblings: false, foveatedTimeDelay: 0, }).then(function (tileset) { var statistics = tileset._statistics; expect(statistics.numberOfTilesWithContentReady).toBe(2); tileset.loadSiblings = true; scene.renderForSpecs(); return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( function (tileset) { expect(statistics.numberOfTilesWithContentReady).toBe(5); } ); }); }); it("immediatelyLoadDesiredLevelOfDetail", function () { viewNothing(); return Cesium3DTilesTester.loadTileset(scene, tilesetUrl, { skipLevelOfDetail: true, immediatelyLoadDesiredLevelOfDetail: true, }).then(function (tileset) { var root = tileset.root; var child = findTileByUri(root.children, "ll.b3dm"); tileset.root.refine = Cesium3DTileRefine.REPLACE; tileset._allTilesAdditive = false; viewBottomLeft(); return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( function (tileset) { expect(isSelected(tileset, child)); expect(!isSelected(tileset, root)); expect(root.contentUnloaded).toBe(true); // Renders child while parent loads viewRootOnly(); scene.renderForSpecs(); expect(isSelected(tileset, child)); expect(!isSelected(tileset, root)); return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( function (tileset) { expect(!isSelected(tileset, child)); expect(isSelected(tileset, root)); } ); } ); }); }); it("selects children if no ancestors available", function () { return Cesium3DTilesTester.loadTileset( scene, tilesetOfTilesetsUrl, skipLevelOfDetailOptions ).then(function (tileset) { var statistics = tileset._statistics; var parent = tileset.root.children[0]; var child = parent.children[3].children[0]; parent.refine = Cesium3DTileRefine.REPLACE; parent.unloadContent(); viewBottomLeft(); scene.renderForSpecs(); expect(child.contentReady).toBe(true); expect(parent.contentReady).toBe(false); expect(isSelected(tileset, child)).toBe(true); expect(isSelected(tileset, parent)).toBe(false); expect(statistics.numberOfCommands).toEqual(1); }); }); it("tile expires", function () { return Cesium3DTilesTester.loadTileset(scene, batchedExpirationUrl).then( function (tileset) { // Intercept the request and load content that produces more draw commands, to simulate fetching new content after the original expires spyOn(Resource._Implementations, "loadWithXhr").and.callFake( function ( url, responseType, method, data, headers, deferred, overrideMimeType ) { Resource._DefaultImplementations.loadWithXhr( batchedColorsB3dmUrl, responseType, method, data, headers, deferred, overrideMimeType ); } ); var tile = tileset.root; var statistics = tileset._statistics; var expiredContent; tileset.style = new Cesium3DTileStyle({ color: 'color("red")', }); // Check that expireDuration and expireDate are correctly set var expireDate = JulianDate.addSeconds( JulianDate.now(), 5.0, new JulianDate() ); expect( JulianDate.secondsDifference(tile.expireDate, expireDate) ).toEqualEpsilon(0.0, CesiumMath.EPSILON1); expect(tile.expireDuration).toBe(5.0); expect(tile.contentExpired).toBe(false); expect(tile.contentReady).toBe(true); expect(tile.contentAvailable).toBe(true); expect(tile._expiredContent).toBeUndefined(); // Check statistics expect(statistics.numberOfCommands).toBe(1); expect(statistics.numberOfTilesTotal).toBe(1); // Trigger expiration to happen next frame tile.expireDate = JulianDate.addSeconds( JulianDate.now(), -1.0, new JulianDate() ); // Stays in the expired state until the request goes through var originalMaxmimumRequests = RequestScheduler.maximumRequests; RequestScheduler.maximumRequests = 0; // Artificially limit Request Scheduler so the request won't go through scene.renderForSpecs(); RequestScheduler.maximumRequests = originalMaxmimumRequests; expiredContent = tile._expiredContent; expect(tile.contentExpired).toBe(true); expect(tile.contentAvailable).toBe(true); // Expired content now exists expect(expiredContent).toBeDefined(); // Expired content renders while new content loads in expect(statistics.numberOfCommands).toBe(1); expect(statistics.numberOfTilesTotal).toBe(1); // Request goes through, now in the LOADING state scene.renderForSpecs(); expect(tile.contentExpired).toBe(false); expect(tile.contentReady).toBe(false); expect(tile.contentAvailable).toBe(true); expect(tile._contentState).toBe(Cesium3DTileContentState.LOADING); expect(tile._expiredContent).toBeDefined(); // Still holds onto expired content until the content state is READY // Check that url contains a query param with the timestamp var url = Resource._Implementations.loadWithXhr.calls.first().args[0]; expect(url.indexOf("expired=") >= 0).toBe(true); // statistics are still the same expect(statistics.numberOfCommands).toBe(1); expect(statistics.numberOfTilesTotal).toBe(1); return pollToPromise(function () { expect(statistics.numberOfCommands).toBe(1); // Still renders expired content scene.renderForSpecs(); return tile.contentReady; }).then(function () { scene.renderForSpecs(); // Expired content is destroyed expect(tile._expiredContent).toBeUndefined(); expect(expiredContent.isDestroyed()).toBe(true); // Expect the style to be reapplied expect(tile.content.getFeature(0).color).toEqual(Color.RED); // statistics for new content expect(statistics.numberOfCommands).toBe(10); expect(statistics.numberOfTilesTotal).toBe(1); }); } ); }); function modifySubtreeBuffer(arrayBuffer) { var uint8Array = new Uint8Array(arrayBuffer); var json = getJsonFromTypedArray(uint8Array); json.root.children.splice(0, 1); return generateJsonBuffer(json).buffer; } it("tile with tileset content expires", function () { return Cesium3DTilesTester.loadTileset( scene, tilesetSubtreeExpirationUrl ).then(function (tileset) { // Intercept the request and load a subtree with one less child. Still want to make an actual request to simulate // real use cases instead of immediately returning a pre-created array buffer. spyOn(Resource._Implementations, "loadWithXhr").and.callFake(function ( url, responseType, method, data, headers, deferred, overrideMimeType ) { var newDeferred = when.defer(); Resource._DefaultImplementations.loadWithXhr( tilesetSubtreeUrl, responseType, method, data, headers, newDeferred, overrideMimeType ); newDeferred.promise.then(function (arrayBuffer) { deferred.resolve(modifySubtreeBuffer(arrayBuffer)); }); }); var subtreeRoot = tileset.root.children[0]; var subtreeChildren = subtreeRoot.children[0].children; var childrenLength = subtreeChildren.length; var statistics = tileset._statistics; // Check statistics expect(statistics.numberOfCommands).toBe(5); expect(statistics.numberOfTilesTotal).toBe(7); expect(statistics.numberOfTilesWithContentReady).toBe(5); // Trigger expiration to happen next frame subtreeRoot.expireDate = JulianDate.addSeconds( JulianDate.now(), -1.0, new JulianDate() ); // Listen to tile unload events var spyUpdate = jasmine.createSpy("listener"); tileset.tileUnload.addEventListener(spyUpdate); // Tiles in the subtree are removed from the cache and destroyed. scene.renderForSpecs(); // Becomes expired scene.renderForSpecs(); // Makes request expect(subtreeRoot.children).toEqual([]); for (var i = 0; i < childrenLength; ++i) { expect(subtreeChildren[0].isDestroyed()).toBe(true); } expect(spyUpdate.calls.count()).toEqual(4); // Remove the spy so new tiles load in normally Resource._Implementations.loadWithXhr = Resource._DefaultImplementations.loadWithXhr; // Wait for the new tileset content to come in with one less leaf return pollToPromise(function () { scene.renderForSpecs(); return subtreeRoot.contentReady && tileset.tilesLoaded; }).then(function () { scene.renderForSpecs(); expect(statistics.numberOfCommands).toBe(4); expect(statistics.numberOfTilesTotal).toBe(6); expect(statistics.numberOfTilesWithContentReady).toBe(4); }); }); }); it("tile expires and request fails", function () { return Cesium3DTilesTester.loadTileset(scene, batchedExpirationUrl).then( function (tileset) { spyOn(Resource._Implementations, "loadWithXhr").and.callFake( function ( url, responseType, method, data, headers, deferred, overrideMimeType ) { deferred.reject(); } ); var tile = tileset.root; var statistics = tileset._statistics; // Trigger expiration to happen next frame tile.expireDate = JulianDate.addSeconds( JulianDate.now(), -1.0, new JulianDate() ); // After update the tile is expired scene.renderForSpecs(); // Make request (it will fail) scene.renderForSpecs(); // Render scene scene.renderForSpecs(); expect(tile._contentState).toBe(Cesium3DTileContentState.FAILED); expect(statistics.numberOfCommands).toBe(0); expect(statistics.numberOfTilesTotal).toBe(1); } ); }); it("tile expiration date", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { var tile = tileset.root; // Trigger expiration to happen next frame tile.expireDate = JulianDate.addSeconds( JulianDate.now(), -1.0, new JulianDate() ); // Stays in the expired state until the request goes through var originalMaxmimumRequests = RequestScheduler.maximumRequests; RequestScheduler.maximumRequests = 0; // Artificially limit Request Scheduler so the request won't go through scene.renderForSpecs(); RequestScheduler.maximumRequests = originalMaxmimumRequests; expect(tile.contentExpired).toBe(true); return pollToPromise(function () { scene.renderForSpecs(); return tile.contentReady; }).then(function () { scene.renderForSpecs(); expect(tile._expiredContent).toBeUndefined(); expect(tile.expireDate).toBeUndefined(); }); }); }); it("supports content data URIs", function () { return Cesium3DTilesTester.loadTileset( scene, tilesetUrlWithContentUri ).then(function (tileset) { var statistics = tileset._statistics; expect(statistics.visited).toEqual(1); expect(statistics.numberOfCommands).toEqual(1); }); }); it("destroys attached ClippingPlaneCollections and ClippingPlaneCollections that have been detached", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { var clippingPlaneCollection1 = new ClippingPlaneCollection({ planes: [new ClippingPlane(Cartesian3.UNIT_Z, -100000000.0)], }); expect(clippingPlaneCollection1.owner).not.toBeDefined(); tileset.clippingPlanes = clippingPlaneCollection1; var clippingPlaneCollection2 = new ClippingPlaneCollection({ planes: [new ClippingPlane(Cartesian3.UNIT_Z, -100000000.0)], }); tileset.clippingPlanes = clippingPlaneCollection2; expect(clippingPlaneCollection1.isDestroyed()).toBe(true); scene.primitives.remove(tileset); expect(clippingPlaneCollection2.isDestroyed()).toBe(true); }); }); it("throws a DeveloperError when given a ClippingPlaneCollection attached to another Tileset", function () { var clippingPlanes; return Cesium3DTilesTester.loadTileset(scene, tilesetUrl) .then(function (tileset1) { clippingPlanes = new ClippingPlaneCollection({ planes: [new ClippingPlane(Cartesian3.UNIT_X, 0.0)], }); tileset1.clippingPlanes = clippingPlanes; return Cesium3DTilesTester.loadTileset(scene, tilesetUrl); }) .then(function (tileset2) { expect(function () { tileset2.clippingPlanes = clippingPlanes; }).toThrowDeveloperError(); }); }); it("clipping planes cull hidden tiles", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { var visibility = tileset.root.visibility( scene.frameState, CullingVolume.MASK_INSIDE ); expect(visibility).not.toBe(CullingVolume.MASK_OUTSIDE); var plane = new ClippingPlane(Cartesian3.UNIT_Z, -100000000.0); tileset.clippingPlanes = new ClippingPlaneCollection({ planes: [plane], }); visibility = tileset.root.visibility( scene.frameState, CullingVolume.MASK_INSIDE ); expect(visibility).toBe(CullingVolume.MASK_OUTSIDE); plane.distance = 0.0; visibility = tileset.root.visibility( scene.frameState, CullingVolume.MASK_INSIDE ); expect(visibility).not.toBe(CullingVolume.MASK_OUTSIDE); }); }); it("clipping planes cull hidden content", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { var visibility = tileset.root.contentVisibility(scene.frameState); expect(visibility).not.toBe(Intersect.OUTSIDE); var plane = new ClippingPlane(Cartesian3.UNIT_Z, -100000000.0); tileset.clippingPlanes = new ClippingPlaneCollection({ planes: [plane], }); visibility = tileset.root.contentVisibility(scene.frameState); expect(visibility).toBe(Intersect.OUTSIDE); plane.distance = 0.0; visibility = tileset.root.contentVisibility(scene.frameState); expect(visibility).not.toBe(Intersect.OUTSIDE); }); }); it("clipping planes cull tiles completely inside clipping region", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset ) { var statistics = tileset._statistics; var root = tileset.root; scene.renderForSpecs(); expect(statistics.numberOfCommands).toEqual(5); tileset.update(scene.frameState); var radius = 287.0736139905632; var plane = new ClippingPlane(Cartesian3.UNIT_X, radius); tileset.clippingPlanes = new ClippingPlaneCollection({ planes: [plane], }); tileset.update(scene.frameState); scene.renderForSpecs(); expect(statistics.numberOfCommands).toEqual(5); expect(root._isClipped).toBe(false); plane.distance = -1; tileset.update(scene.frameState); scene.renderForSpecs(); expect(statistics.numberOfCommands).toEqual(3); expect(root._isClipped).toBe(true); plane.distance = -radius; tileset.update(scene.frameState); scene.renderForSpecs(); expect(statistics.numberOfCommands).toEqual(0); expect(root._isClipped).toBe(true); }); }); it("clipping planes cull tiles completely inside clipping region for i3dm", function () { return Cesium3DTilesTester.loadTileset( scene, tilesetWithExternalResourcesUrl ).then(function (tileset) { var statistics = tileset._statistics; var root = tileset.root; scene.renderForSpecs(); expect(statistics.numberOfCommands).toEqual(6); tileset.update(scene.frameState); var radius = 142.19001637409772; var plane = new ClippingPlane(Cartesian3.UNIT_Z, radius); tileset.clippingPlanes = new ClippingPlaneCollection({ planes: [plane], }); tileset.update(scene.frameState); scene.renderForSpecs(); expect(statistics.numberOfCommands).toEqual(6); expect(root._isClipped).toBe(false); plane.distance = 0; tileset.update(scene.frameState); scene.renderForSpecs(); expect(statistics.numberOfCommands).toEqual(6); expect(root._isClipped).toBe(true); plane.distance = -radius; tileset.update(scene.frameState); scene.renderForSpecs(); expect(statistics.numberOfCommands).toEqual(0); expect(root._isClipped).toBe(true); }); }); it("clippingPlanesOriginMatrix has correct orientation", function () { return Cesium3DTilesTester.loadTileset(scene, withTransformBoxUrl).then( function (tileset) { // The bounding volume of this tileset puts it under the surface, so no // east-north-up should be applied. Check that it matches the orientation // of the original transform. var offsetMatrix = tileset.clippingPlanesOriginMatrix; expect( Matrix4.equals(offsetMatrix, tileset.root.computedTransform) ).toBe(true); return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then( function (tileset) { // The bounding volume of this tileset puts it on the surface, // so we want to apply east-north-up as our best guess. offsetMatrix = tileset.clippingPlanesOriginMatrix; // The clipping plane matrix is not the same as the original because we applied east-north-up. expect( Matrix4.equals(offsetMatrix, tileset.root.computedTransform) ).toBe(false); // But they have the same translation. var clippingPlanesOrigin = Matrix4.getTranslation( offsetMatrix, new Cartesian3() ); expect( Cartesian3.equals( tileset.root.boundingSphere.center, clippingPlanesOrigin ) ).toBe(true); } ); } ); }); it("clippingPlanesOriginMatrix matches root tile bounding sphere", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetOfTilesetsUrl).then( function (tileset) { var offsetMatrix = Matrix4.clone( tileset.clippingPlanesOriginMatrix, new Matrix4() ); var boundingSphereEastNorthUp = Transforms.eastNorthUpToFixedFrame( tileset.root.boundingSphere.center ); expect(Matrix4.equals(offsetMatrix, boundingSphereEastNorthUp)).toBe( true ); // Changing the model matrix should change the clipping planes matrix tileset.modelMatrix = Matrix4.fromTranslation( new Cartesian3(100, 0, 0) ); scene.renderForSpecs(); expect( Matrix4.equals(offsetMatrix, tileset.clippingPlanesOriginMatrix) ).toBe(false); boundingSphereEastNorthUp = Transforms.eastNorthUpToFixedFrame( tileset.root.boundingSphere.center ); offsetMatrix = tileset.clippingPlanesOriginMatrix; expect(offsetMatrix).toEqualEpsilon( boundingSphereEastNorthUp, CesiumMath.EPSILON3 ); } ); }); describe("updateForPass", function () { it("updates for pass", function () { viewAllTiles(); var passCamera = Camera.clone(scene.camera); var passCullingVolume = passCamera.frustum.computeCullingVolume( passCamera.positionWC, passCamera.directionWC, passCamera.upWC ); viewNothing(); // Main camera views nothing, pass camera views all tiles var preloadFlightPassState = new Cesium3DTilePassState({ pass: Cesium3DTilePass.PRELOAD_FLIGHT, camera: passCamera, cullingVolume: passCullingVolume, }); return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then( function (tileset) { expect(tileset.statistics.selected).toBe(0); tileset.updateForPass(scene.frameState, preloadFlightPassState); expect(tileset._requestedTiles.length).toBe(5); } ); }); it("uses custom command list", function () { var passCommandList = []; var renderPassState = new Cesium3DTilePassState({ pass: Cesium3DTilePass.RENDER, commandList: passCommandList, }); viewAllTiles(); return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then( function (tileset) { tileset.updateForPass(scene.frameState, renderPassState); expect(passCommandList.length).toBe(5); } ); }); it("throws if frameState is undefined", function () { var tileset = new Cesium3DTileset({ url: tilesetUrl, }); expect(function () { tileset.updateForPass(); }).toThrowDeveloperError(); }); it("throws if tilesetPassState is undefined", function () { var tileset = new Cesium3DTileset({ url: tilesetUrl, }); expect(function () { tileset.updateForPass(scene.frameState); }).toThrowDeveloperError(); }); }); function sampleHeightMostDetailed(cartographics, objectsToExclude) { var result; var completed = false; scene .sampleHeightMostDetailed(cartographics, objectsToExclude) .then(function (pickResult) { result = pickResult; completed = true; }); return pollToPromise(function () { // Scene requires manual updates in the tests to move along the promise scene.render(); return completed; }).then(function () { return result; }); } function drillPickFromRayMostDetailed(ray, limit, objectsToExclude) { var result; var completed = false; scene .drillPickFromRayMostDetailed(ray, limit, objectsToExclude) .then(function (pickResult) { result = pickResult; completed = true; }); return pollToPromise(function () { // Scene requires manual updates in the tests to move along the promise scene.render(); return completed; }).then(function () { return result; }); } describe("most detailed height queries", function () { it("tileset is offscreen", function () { if (webglStub) { return; } viewNothing(); // Tileset uses replacement refinement so only one tile should be loaded and selected during most detailed picking var centerCartographic = new Cartographic( -1.3196799798348215, 0.6988740001506679, 2.4683731133709323 ); var cartographics = [centerCartographic]; return Cesium3DTilesTester.loadTileset(scene, tilesetUniform).then( function (tileset) { return sampleHeightMostDetailed(cartographics).then(function () { expect(centerCartographic.height).toEqualEpsilon( 2.47, CesiumMath.EPSILON1 ); var statisticsMostDetailedPick = tileset._statisticsPerPass[Cesium3DTilePass.MOST_DETAILED_PICK]; var statisticsRender = tileset._statisticsPerPass[Cesium3DTilePass.RENDER]; expect(statisticsMostDetailedPick.numberOfCommands).toBe(1); expect( statisticsMostDetailedPick.numberOfTilesWithContentReady ).toBe(1); expect(statisticsMostDetailedPick.selected).toBe(1); expect(statisticsMostDetailedPick.visited).toBeGreaterThan(1); expect(statisticsMostDetailedPick.numberOfTilesTotal).toBe(21); expect(statisticsRender.selected).toBe(0); }); } ); }); it("tileset is onscreen", function () { if (webglStub) { return; } viewAllTiles(); // Tileset uses replacement refinement so only one tile should be loaded and selected during most detailed picking var centerCartographic = new Cartographic( -1.3196799798348215, 0.6988740001506679, 2.4683731133709323 ); var cartographics = [centerCartographic]; return Cesium3DTilesTester.loadTileset(scene, tilesetUniform).then( function (tileset) { return sampleHeightMostDetailed(cartographics).then(function () { expect(centerCartographic.height).toEqualEpsilon( 2.47, CesiumMath.EPSILON1 ); var statisticsMostDetailedPick = tileset._statisticsPerPass[Cesium3DTilePass.MOST_DETAILED_PICK]; var statisticsRender = tileset._statisticsPerPass[Cesium3DTilePass.RENDER]; expect(statisticsMostDetailedPick.numberOfCommands).toBe(1); expect( statisticsMostDetailedPick.numberOfTilesWithContentReady ).toBeGreaterThan(1); expect(statisticsMostDetailedPick.selected).toBe(1); expect(statisticsMostDetailedPick.visited).toBeGreaterThan(1); expect(statisticsMostDetailedPick.numberOfTilesTotal).toBe(21); expect(statisticsRender.selected).toBeGreaterThan(0); }); } ); }); it("tileset uses additive refinement", function () { if (webglStub) { return; } viewNothing(); var originalLoadJson = Cesium3DTileset.loadJson; spyOn(Cesium3DTileset, "loadJson").and.callFake(function (tilesetUrl) { return originalLoadJson(tilesetUrl).then(function (tilesetJson) { tilesetJson.root.refine = "ADD"; return tilesetJson; }); }); var offcenterCartographic = new Cartographic( -1.3196754112739246, 0.6988705057695633, 2.467395745774971 ); var cartographics = [offcenterCartographic]; return Cesium3DTilesTester.loadTileset(scene, tilesetUniform).then( function (tileset) { return sampleHeightMostDetailed(cartographics).then(function () { var statistics = tileset._statisticsPerPass[Cesium3DTilePass.MOST_DETAILED_PICK]; expect(offcenterCartographic.height).toEqualEpsilon( 7.407, CesiumMath.EPSILON1 ); expect(statistics.numberOfCommands).toBe(3); // One for each level of the tree expect( statistics.numberOfTilesWithContentReady ).toBeGreaterThanOrEqualTo(3); expect(statistics.selected).toBe(3); expect(statistics.visited).toBeGreaterThan(3); expect(statistics.numberOfTilesTotal).toBe(21); }); } ); }); it("drill picks multiple features when tileset uses additive refinement", function () { if (webglStub) { return; } viewNothing(); var ray = new Ray(scene.camera.positionWC, scene.camera.directionWC); var originalLoadJson = Cesium3DTileset.loadJson; spyOn(Cesium3DTileset, "loadJson").and.callFake(function (tilesetUrl) { return originalLoadJson(tilesetUrl).then(function (tilesetJson) { tilesetJson.root.refine = "ADD"; return tilesetJson; }); }); return Cesium3DTilesTester.loadTileset(scene, tilesetUniform).then( function (tileset) { return drillPickFromRayMostDetailed(ray).then(function (results) { expect(results.length).toBe(3); expect( results[0].object.content.url.indexOf("0_0_0.b3dm") > -1 ).toBe(true); expect( results[1].object.content.url.indexOf("1_1_1.b3dm") > -1 ).toBe(true); expect( results[2].object.content.url.indexOf("2_4_4.b3dm") > -1 ).toBe(true); }); } ); }); it("works when tileset cache is disabled", function () { if (webglStub) { return; } viewNothing(); var centerCartographic = new Cartographic( -1.3196799798348215, 0.6988740001506679, 2.4683731133709323 ); var cartographics = [centerCartographic]; return Cesium3DTilesTester.loadTileset(scene, tilesetUniform).then( function (tileset) { tileset.maximumMemoryUsage = 0; return sampleHeightMostDetailed(cartographics).then(function () { expect(centerCartographic.height).toEqualEpsilon( 2.47, CesiumMath.EPSILON1 ); }); } ); }); it("multiple samples", function () { if (webglStub) { return; } viewNothing(); var centerCartographic = new Cartographic( -1.3196799798348215, 0.6988740001506679 ); var offcenterCartographic = new Cartographic( -1.3196754112739246, 0.6988705057695633 ); var missCartographic = new Cartographic( -1.3196096042084076, 0.6988703290845706 ); var cartographics = [ centerCartographic, offcenterCartographic, missCartographic, ]; return Cesium3DTilesTester.loadTileset(scene, tilesetUniform).then( function (tileset) { return sampleHeightMostDetailed(cartographics).then(function () { expect(centerCartographic.height).toEqualEpsilon( 2.47, CesiumMath.EPSILON1 ); expect(offcenterCartographic.height).toEqualEpsilon( 2.47, CesiumMath.EPSILON1 ); expect(missCartographic.height).toBeUndefined(); var statistics = tileset._statisticsPerPass[Cesium3DTilePass.MOST_DETAILED_PICK]; expect(statistics.numberOfTilesWithContentReady).toBe(2); }); } ); }); }); it("cancels out-of-view tiles", function () { viewNothing(); return Cesium3DTilesTester.loadTileset(scene, tilesetUniform).then( function (tileset) { // Make requests viewAllTiles(); scene.renderForSpecs(); var requestedTilesInFlight = tileset._requestedTilesInFlight; var requestedTilesInFlightLength = requestedTilesInFlight.length; expect(requestedTilesInFlightLength).toBeGreaterThan(0); // Save off old requests var oldRequests = []; var i; for (i = 0; i < requestedTilesInFlightLength; i++) { oldRequests.push(requestedTilesInFlight[i]); } // Cancel requests viewNothing(); scene.renderForSpecs(); expect(requestedTilesInFlight.length).toBe(0); // Make sure old requests were marked for cancelling var allCancelled = true; var oldRequestsLength = oldRequests.length; for (i = 0; i < oldRequestsLength; i++) { var tile = oldRequests[i]; allCancelled = allCancelled && tile._request.cancelled; } expect(allCancelled).toBe(true); } ); }); it("sorts requests by priority", function () { viewNothing(); return Cesium3DTilesTester.loadTileset(scene, tilesetUniform).then( function (tileset) { // Make requests viewAllTiles(); scene.renderForSpecs(); var requestedTilesInFlight = tileset._requestedTilesInFlight; var requestedTilesInFlightLength = requestedTilesInFlight.length; expect(requestedTilesInFlightLength).toBeGreaterThan(0); // Verify sort var allSorted = true; var lastPriority = -Number.MAX_VALUE; var thisPriority; for (var i = 0; i < requestedTilesInFlightLength; i++) { thisPriority = requestedTilesInFlight[i]._priority; allSorted = allSorted && thisPriority >= lastPriority; lastPriority = thisPriority; } expect(allSorted).toBe(true); expect(lastPriority).not.toEqual(requestedTilesInFlight[0]._priority); // Not all the same value } ); }); it("defers requests when foveatedScreenSpaceError is true", function () { viewNothing(); return Cesium3DTilesTester.loadTileset(scene, tilesetRefinementMix).then( function (tileset) { tileset.foveatedScreenSpaceError = true; tileset.foveatedConeSize = 0; tileset.maximumScreenSpaceError = 8; tileset.foveatedTimeDelay = 0; // Position camera such that some tiles are outside the foveated cone but still on screen. viewAllTiles(); scene.camera.moveLeft(205.0); scene.camera.moveDown(205.0); scene.renderForSpecs(); // Verify deferred var requestedTilesInFlight = tileset._requestedTilesInFlight; expect(requestedTilesInFlight.length).toBe(1); expect(requestedTilesInFlight[0].priorityDeferred).toBe(true); } ); }); it("loads deferred requests only after time delay.", function () { viewNothing(); return Cesium3DTilesTester.loadTileset(scene, tilesetRefinementMix).then( function (tileset) { tileset.foveatedScreenSpaceError = true; tileset.foveatedConeSize = 0; tileset.maximumScreenSpaceError = 8; tileset.foveatedTimeDelay = 0.1; // Position camera such that some tiles are outside the foveated cone but still on screen. viewAllTiles(); scene.camera.moveLeft(205.0); scene.camera.moveDown(205.0); scene.renderForSpecs(); // Nothing should be loaded yet. expect(tileset._requestedTilesInFlight.length).toBe(0); // Eventually, a deferred tile should load. return pollToPromise(function () { scene.renderForSpecs(); return tileset._requestedTilesInFlight.length !== 0; }).then(function () { expect(tileset._requestedTilesInFlight[0].priorityDeferred).toBe( true ); }); } ); }); it("preloads tiles", function () { // Flight destination viewAllTiles(); scene.preloadFlightCamera = Camera.clone(scene.camera); scene.preloadFlightCullingVolume = scene.camera.frustum.computeCullingVolume( scene.camera.positionWC, scene.camera.directionWC, scene.camera.upWC ); // Reset view viewNothing(); return Cesium3DTilesTester.loadTileset(scene, tilesetUniform).then( function (tileset) { spyOn(Camera.prototype, "canPreloadFlight").and.callFake(function () { return true; }); scene.renderForSpecs(); expect(tileset._requestedTilesInFlight.length).toBeGreaterThan(0); } ); }); it("does not fetch tiles while camera is moving", function () { viewNothing(); return Cesium3DTilesTester.loadTileset(scene, tilesetUniform).then( function (tileset) { tileset.cullRequestsWhileMoving = true; viewAllTiles(); scene.renderForSpecs(); expect(tileset._requestedTilesInFlight.length).toEqual(0); // Big camera delta so no fetches should occur. } ); }); it("does not apply cullRequestWhileMoving optimization if tileset is moving", function () { viewNothing(); return Cesium3DTilesTester.loadTileset(scene, tilesetUniform).then( function (tileset) { tileset.cullRequestsWhileMoving = true; tileset.modelMatrix[12] += 1.0; viewAllTiles(); scene.renderForSpecs(); expect(tileset._requestedTilesInFlight.length).toEqual(2); } ); }); it("loads tiles when preloadWhenHidden is true", function () { var options = { show: false, preloadWhenHidden: true, }; return Cesium3DTilesTester.loadTileset(scene, tilesetUrl, options).then( function (tileset) { var selectedLength = tileset.statistics.selected; expect(selectedLength).toBeGreaterThan(0); tileset.show = true; scene.renderForSpecs(); expect(tileset.statistics.selected).toBe(selectedLength); expect(tileset.statistics.numberOfPendingRequests).toBe(0); } ); }); describe("3DTILES_implicit_tiling", function () { var implicitTilesetUrl = "Data/Cesium3DTiles/Implicit/ImplicitTileset/tileset.json"; var implicitRootUrl = "Data/Cesium3DTiles/Implicit/ImplicitRootTile/tileset.json"; var implicitChildUrl = "Data/Cesium3DTiles/Implicit/ImplicitChildTile/tileset.json"; it("renders tileset", function () { return Cesium3DTilesTester.loadTileset(scene, implicitTilesetUrl).then( function (tileset) { var statistics = tileset._statistics; // root + implicit placeholder + 4 child tiles expect(statistics.visited).toEqual(6); // the implicit placeholder tile is not rendered expect(statistics.numberOfCommands).toEqual(5); } ); }); it("detects and initializes an implicit tileset in root tile", function () { viewNothing(); return Cesium3DTilesTester.loadTileset(scene, implicitRootUrl).then( function (tileset) { var implicitTile = tileset.root; expect( endsWith( implicitTile._contentResource.url, "subtrees/0/0/0/0.subtree" ) ).toEqual(true); expect(implicitTile.implicitTileset).toBeDefined(); expect(implicitTile.implicitCoordinates).toBeDefined(); expect(implicitTile.implicitCoordinates.level).toEqual(0); expect(implicitTile.implicitCoordinates.x).toEqual(0); expect(implicitTile.implicitCoordinates.y).toEqual(0); expect(implicitTile.implicitCoordinates.z).toEqual(0); } ); }); it("detects and initializes an implicit tileset in child tile", function () { viewNothing(); return Cesium3DTilesTester.loadTileset(scene, implicitChildUrl).then( function (tileset) { var parentTile = tileset.root; var implicitTile = parentTile.children[0]; expect( endsWith( implicitTile._contentResource.url, "subtrees/0/0/0.subtree" ) ).toEqual(true); expect(implicitTile.implicitTileset).toBeDefined(); expect(implicitTile.implicitCoordinates).toBeDefined(); expect(implicitTile.implicitCoordinates.level).toEqual(0); expect(implicitTile.implicitCoordinates.x).toEqual(0); expect(implicitTile.implicitCoordinates.y).toEqual(0); } ); }); }); describe("3DTILES_multiple_contents", function () { var multipleContentsUrl = "Data/Cesium3DTiles/MultipleContents/MultipleContents/tileset.json"; var implicitMultipleContentsUrl = "Data/Cesium3DTiles/Implicit/ImplicitMultipleContents/tileset.json"; it("request statistics are updated correctly on success", function () { return Cesium3DTilesTester.loadTileset(scene, multipleContentsUrl).then( function (tileset) { var statistics = tileset.statistics; expect(statistics.numberOfAttemptedRequests).toBe(0); expect(statistics.numberOfPendingRequests).toBe(0); expect(statistics.numberOfTilesProcessing).toBe(0); expect(statistics.numberOfTilesWithContentReady).toBe(1); } ); }); it("request statistics are updated for partial success", function () { var originalLoadJson = Cesium3DTileset.loadJson; spyOn(Cesium3DTileset, "loadJson").and.callFake(function (tilesetUrl) { return originalLoadJson(tilesetUrl).then(function (tilesetJson) { var content = tilesetJson.root.extensions["3DTILES_multiple_contents"].content; var badTile = { uri: "nonexistent.b3dm", }; content.splice(1, 0, badTile); return tilesetJson; }); }); viewNothing(); return Cesium3DTilesTester.loadTileset(scene, multipleContentsUrl).then( function (tileset) { viewAllTiles(); scene.renderForSpecs(); var statistics = tileset.statistics; expect(statistics.numberOfAttemptedRequests).toBe(0); expect(statistics.numberOfPendingRequests).toBe(3); expect(statistics.numberOfTilesProcessing).toBe(0); expect(statistics.numberOfTilesWithContentReady).toBe(0); tileset.root.contentReadyToProcessPromise .then(function () { expect(statistics.numberOfAttemptedRequests).toBe(0); expect(statistics.numberOfPendingRequests).toBe(0); expect(statistics.numberOfTilesProcessing).toBe(1); expect(statistics.numberOfTilesWithContentReady).toBe(0); }) .otherwise(fail); return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( function () { expect(statistics.numberOfAttemptedRequests).toBe(0); expect(statistics.numberOfPendingRequests).toBe(0); expect(statistics.numberOfTilesProcessing).toBe(0); expect(statistics.numberOfTilesWithContentReady).toBe(1); } ); } ); }); it("request statistics are updated correctly if requests are not scheduled", function () { viewNothing(); return Cesium3DTilesTester.loadTileset(scene, multipleContentsUrl).then( function (tileset) { var oldMaximumRequestsPerServer = RequestScheduler.maximumRequestsPerServer; RequestScheduler.maximumRequestsPerServer = 1; viewAllTiles(); scene.renderForSpecs(); var statistics = tileset.statistics; expect(statistics.numberOfAttemptedRequests).toBe(2); expect(statistics.numberOfPendingRequests).toBe(0); expect(statistics.numberOfTilesProcessing).toBe(0); expect(statistics.numberOfTilesWithContentReady).toBe(0); RequestScheduler.maximumRequestsPerServer = oldMaximumRequestsPerServer; } ); }); it("statistics update correctly if tile is canceled", function () { viewNothing(); return Cesium3DTilesTester.loadTileset(scene, multipleContentsUrl).then( function (tileset) { var callCount = 0; tileset.tileFailed.addEventListener(function (event) { callCount++; }); viewAllTiles(); scene.renderForSpecs(); var statistics = tileset.statistics; expect(statistics.numberOfAttemptedRequests).toBe(0); expect(statistics.numberOfPendingRequests).toBe(2); expect(statistics.numberOfTilesProcessing).toBe(0); expect(statistics.numberOfTilesWithContentReady).toBe(0); var multipleContents = tileset.root.content; multipleContents.cancelRequests(); tileset.root.contentReadyToProcessPromise .then(function () { expect(statistics.numberOfAttemptedRequests).toBe(0); expect(statistics.numberOfPendingRequests).toBe(0); expect(statistics.numberOfTilesProcessing).toBe(1); expect(statistics.numberOfTilesWithContentReady).toBe(0); }) .otherwise(fail); return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( function () { // Resetting content should be handled gracefully; it should // not trigger the tileFailed event expect(callCount).toBe(0); expect(statistics.numberOfAttemptedRequests).toBe(0); expect(statistics.numberOfPendingRequests).toBe(0); expect(statistics.numberOfTilesProcessing).toBe(0); expect(statistics.numberOfTilesWithContentReady).toBe(1); } ); } ); }); it("verify multiple content statistics", function () { options.url = multipleContentsUrl; var tileset = scene.primitives.add(new Cesium3DTileset(options)); return checkPointAndFeatureCounts(tileset, 35, 0, 132); }); it("calls tileFailed for each content with errors", function () { var originalLoadJson = Cesium3DTileset.loadJson; spyOn(Cesium3DTileset, "loadJson").and.callFake(function (tilesetUrl) { return originalLoadJson(tilesetUrl).then(function (tilesetJson) { var extension = tilesetJson.root.extensions["3DTILES_multiple_contents"]; extension.content = [ { uri: "nonexistent1.b3dm", }, { uri: "nonexistent2.b3dm", }, { uri: "nonexistent3.b3dm", }, ]; return tilesetJson; }); }); var uris = []; viewNothing(); return Cesium3DTilesTester.loadTileset(scene, multipleContentsUrl).then( function (tileset) { tileset.tileFailed.addEventListener(function (event) { uris.push(event.url); }); viewAllTiles(); return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( function () { expect(uris.length).toBe(3); uris.sort(); var expected = [ "nonexistent1.b3dm", "nonexistent2.b3dm", "nonexistent3.b3dm", ]; for (var i = 0; i < expected.length; i++) { expect(endsWith(uris[i], expected[i])).toBe(true); } } ); } ); }); it("raises tileFailed for external tileset inside multiple contents", function () { var originalLoadJson = Cesium3DTileset.loadJson; spyOn(Cesium3DTileset, "loadJson").and.callFake(function (tilesetUrl) { return originalLoadJson(tilesetUrl).then(function (tilesetJson) { var extension = tilesetJson.root.extensions["3DTILES_multiple_contents"]; extension.content = [ { uri: "external.json", }, ]; return tilesetJson; }); }); spyOn(Resource.prototype, "fetchArrayBuffer").and.callFake(function () { var externalTileset = { asset: { version: "1.0", }, root: {}, }; var buffer = generateJsonBuffer(externalTileset).buffer; return when.resolve(buffer); }); viewNothing(); return Cesium3DTilesTester.loadTileset(scene, multipleContentsUrl).then( function (tileset) { var errorCount = 0; tileset.tileFailed.addEventListener(function (event) { errorCount++; expect(endsWith(event.url, "external.json")).toBe(true); expect(event.message).toEqual( "External tilesets are disallowed inside the 3DTILES_multiple_contents extension" ); }); viewAllTiles(); scene.renderForSpecs(); return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset).then( function () { expect(errorCount).toBe(1); } ); } ); }); it("debugColorizeTiles for multiple contents", function () { return checkDebugColorizeTiles(multipleContentsUrl); }); it("debugShowUrl lists each URI", function () { return Cesium3DTilesTester.loadTileset(scene, multipleContentsUrl).then( function (tileset) { tileset.debugShowUrl = true; scene.renderForSpecs(); expect(tileset._tileDebugLabels).toBeDefined(); var expected = "Urls:\n- batched.b3dm\n- instanced.i3dm"; expect(tileset._tileDebugLabels._labels[0].text).toEqual(expected); tileset.debugShowUrl = false; scene.renderForSpecs(); expect(tileset._tileDebugLabels).not.toBeDefined(); } ); }); it("renders tileset with multiple contents", function () { var b3dmCommands = 1; // when instancing is supported, there is a single draw command, // else,each instance is a separate command. var i3dmCommands = scene.context.instancedArrays ? 1 : 25; var totalCommands = b3dmCommands + i3dmCommands; return Cesium3DTilesTester.loadTileset(scene, multipleContentsUrl).then( function (tileset) { var statistics = tileset._statistics; expect(statistics.visited).toEqual(1); expect(statistics.numberOfCommands).toEqual(totalCommands); } ); }); it("renders implicit tileset with multiple contents", function () { return Cesium3DTilesTester.loadTileset( scene, implicitMultipleContentsUrl ).then(function (tileset) { var statistics = tileset._statistics; // implicit placeholder + transcoded root + 4 child tiles expect(statistics.visited).toEqual(6); // root content + 2 contents per child tile expect(statistics.numberOfCommands).toEqual(9); }); }); }); describe("3DTILES_metadata", function () { var tilesetMetadataUrl = "Data/Cesium3DTiles/Metadata/TilesetMetadata/tileset.json"; var tilesetWithExternalSchemaUrl = "Data/Cesium3DTiles/Metadata/ExternalSchema/tileset.json"; var tilesetWithGroupMetadataUrl = "Data/Cesium3DTiles/Metadata/GroupMetadata/tileset.json"; var tilesetWithImplicitTileMetadataUrl = "Data/Cesium3DTiles/Metadata/ImplicitTileMetadata/tileset.json"; var tilesetWithExplicitTileMetadataUrl = "Data/Cesium3DTiles/Metadata/TileMetadata/tileset.json"; var tilesetProperties = { author: "Cesium", date: "2021-03-23", centerCartographic: [ -1.3196816996258511, 0.6988767486400521, 45.78600543644279, ], tileCount: 5, }; it("loads tileset metadata", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetMetadataUrl).then( function (tileset) { var metadata = tileset.metadata; expect(metadata).toBeDefined(); var tilesetMetadata = metadata.tileset; expect(tilesetMetadata.getProperty("name")).not.toBeDefined(); expect(tilesetMetadata.getProperty("author")).toBe( tilesetProperties.author ); expect(tilesetMetadata.getPropertyBySemantic("DATE_ISO_8601")).toBe( tilesetProperties.date ); expect(tilesetMetadata.getProperty("centerCartographic")).toEqual( Cartesian3.unpack(tilesetProperties.centerCartographic) ); expect(tilesetMetadata.getProperty("tileCount")).toBe( tilesetProperties.tileCount ); } ); }); it("loads group metadata", function () { return Cesium3DTilesTester.loadTileset( scene, tilesetWithGroupMetadataUrl ).then(function (tileset) { var metadata = tileset.metadata; expect(metadata).toBeDefined(); var groups = metadata.groups; expect(groups).toBeDefined(); var group = groups.commercialDistrict; expect(group).toBeDefined(); expect(group.getProperty("businessCount")).toBe(143); group = groups.residentialDistrict; expect(group).toBeDefined(); expect(group.getProperty("population")).toBe(300000); expect(group.getProperty("neighborhoods")).toEqual([ "Hillside", "Middletown", "Western Heights", ]); group = groups.port; expect(group).not.toBeDefined(); }); }); it("can access group metadata through contents", function () { return Cesium3DTilesTester.loadTileset( scene, tilesetWithGroupMetadataUrl ).then(function (tileset) { var metadata = tileset.metadata; var commercialDistrict = metadata.groups.commercialDistrict; var residentialDistrict = metadata.groups.residentialDistrict; // the parent tile in this dataset does not have a group defined, // but its children do. var parent = tileset.root; var group = parent.content.groupMetadata; expect(group).not.toBeDefined(); var expected = { "ul.b3dm": residentialDistrict, "ll.b3dm": residentialDistrict, "ur.b3dm": commercialDistrict, "lr.b3dm": commercialDistrict, }; var childrenTiles = parent.children; childrenTiles.forEach(function (tile) { var uri = tile._header.content.uri; expect(tile.content.groupMetadata).toBe(expected[uri]); }); }); }); it("loads metadata with embedded schema", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetMetadataUrl).then( function (tileset) { var schema = tileset.metadata.schema; expect(schema).toBeDefined(); var classes = schema.classes; expect(classes.tileset).toBeDefined(); } ); }); it("loads metadata with external schema", function () { return Cesium3DTilesTester.loadTileset( scene, tilesetWithExternalSchemaUrl ).then(function (tileset) { var schema = tileset.metadata.schema; expect(schema).toBeDefined(); var classes = schema.classes; expect(classes.tileset).toBeDefined(); }); }); it("loads explicit tileset with tile metadata", function () { return Cesium3DTilesTester.loadTileset( scene, tilesetWithExplicitTileMetadataUrl ).then(function (tileset) { var expected = { "parent.b3dm": { color: new Cartesian3(0.5, 0.0, 1.0), population: 530, }, "ll.b3dm": { color: new Cartesian3(1.0, 1.0, 0.0), population: 50, }, "lr.b3dm": { color: new Cartesian3(1.0, 0.0, 0.5), population: 230, }, "ur.b3dm": { color: new Cartesian3(1.0, 0.5, 0.0), population: 150, }, "ul.b3dm": { color: new Cartesian3(1.0, 0.0, 0.0), population: 100, }, }; var parent = tileset.root; var tiles = [parent].concat(parent.children); tiles.forEach(function (tile) { var uri = tile._header.content.uri; var expectedValues = expected[uri]; var metadata = tile.metadata; expect(metadata.getProperty("color")).toEqual(expectedValues.color); expect(metadata.getProperty("population")).toEqual( expectedValues.population ); }); }); }); it("loads implicit tileset with tile metadata", function () { // this tileset is similar to other implicit tilesets, though // one tile is removed return Cesium3DTilesTester.loadTileset( scene, tilesetWithImplicitTileMetadataUrl ).then(function (tileset) { var placeholderTile = tileset.root; var transcodedRoot = placeholderTile.children[0]; var subtree = transcodedRoot.implicitSubtree; expect(subtree).toBeDefined(); var metadataTable = subtree.metadataTable; expect(metadataTable).toBeDefined(); expect(metadataTable.count).toBe(4); expect(metadataTable.hasProperty("color")).toBe(true); expect(metadataTable.hasProperty("quadrant")).toBe(true); var tileCount = 4; var expectedQuadrants = [ "None", "Southwest", "Northwest", "Northeast", ]; var expectedColors = [ new Cartesian3(255, 255, 255), new Cartesian3(255, 0, 0), new Cartesian3(0, 255, 0), new Cartesian3(0, 0, 255), ]; var tiles = [transcodedRoot].concat(transcodedRoot.children); expect(tiles.length).toBe(tileCount); var i; for (i = 0; i < tileCount; i++) { var tile = tiles[i]; var entityId = subtree.getEntityId(tile.implicitCoordinates); var metadata = tile.metadata; expect(metadata.getProperty("quadrant")).toBe( expectedQuadrants[entityId] ); expect(metadata.getProperty("color")).toEqual( expectedColors[entityId] ); } }); }); }); }, "WebGL" );