import { Cartesian3 } from "../../Source/Cesium.js"; import { Cartographic } from "../../Source/Cesium.js"; import { Color } from "../../Source/Cesium.js"; import { ColorGeometryInstanceAttribute } from "../../Source/Cesium.js"; import { destroyObject } from "../../Source/Cesium.js"; import { Ellipsoid } from "../../Source/Cesium.js"; import { GeometryInstance } from "../../Source/Cesium.js"; import { HeadingPitchRange } from "../../Source/Cesium.js"; import { Math as CesiumMath } from "../../Source/Cesium.js"; import { Matrix4 } from "../../Source/Cesium.js"; import { Rectangle } from "../../Source/Cesium.js"; import { RectangleGeometry } from "../../Source/Cesium.js"; import { Resource } from "../../Source/Cesium.js"; import { Pass } from "../../Source/Cesium.js"; import { RenderState } from "../../Source/Cesium.js"; import { ClassificationModel } from "../../Source/Cesium.js"; import { ClassificationType } from "../../Source/Cesium.js"; import { PerInstanceColorAppearance } from "../../Source/Cesium.js"; import { Primitive } from "../../Source/Cesium.js"; import { StencilConstants } from "../../Source/Cesium.js"; import createScene from "../createScene.js"; import pollToPromise from "../pollToPromise.js"; import { addDefaults } from "../../Source/Cesium.js"; import { parseGlb } from "../../Source/Cesium.js"; import { updateVersion } from "../../Source/Cesium.js"; describe( "Scene/ClassificationModel", function () { var scene; var modelMatrix; var centerLongitude = -1.31968; var centerLatitude = 0.698874; var batchedModel = "./Data/Models/Classification/batched.glb"; var quantizedModel = "./Data/Models/Classification/batchedQuantization.glb"; var globePrimitive; var tilesetPrimitive; var reusableGlobePrimitive; var reusableTilesetPrimitive; function setCamera(longitude, latitude) { // One feature is located at the center, point the camera there var center = Cartesian3.fromRadians(longitude, latitude); scene.camera.lookAt(center, new HeadingPitchRange(0.0, -1.57, 15.0)); } function createPrimitive(rectangle, pass) { var renderState; if (pass === Pass.CESIUM_3D_TILE) { renderState = RenderState.fromCache({ stencilTest: StencilConstants.setCesium3DTileBit(), stencilMask: StencilConstants.CESIUM_3D_TILE_MASK, depthTest: { enabled: true, }, }); } var depthColorAttribute = ColorGeometryInstanceAttribute.fromColor( new Color(0.0, 0.0, 0.0, 1.0) ); return new Primitive({ geometryInstances: new GeometryInstance({ geometry: new RectangleGeometry({ ellipsoid: Ellipsoid.WGS84, rectangle: rectangle, }), id: "depth rectangle", attributes: { color: depthColorAttribute, }, }), appearance: new PerInstanceColorAppearance({ translucent: false, flat: true, renderState: renderState, }), asynchronous: false, }); } function MockPrimitive(primitive, pass) { this._primitive = primitive; this._pass = pass; this.show = true; } MockPrimitive.prototype.update = function (frameState) { if (!this.show) { return; } var commandList = frameState.commandList; var startLength = commandList.length; this._primitive.update(frameState); for (var i = startLength; i < commandList.length; ++i) { var command = commandList[i]; command.pass = this._pass; } }; MockPrimitive.prototype.isDestroyed = function () { return false; }; MockPrimitive.prototype.destroy = function () { return destroyObject(this); }; beforeAll(function () { scene = createScene(); var translation = Ellipsoid.WGS84.geodeticSurfaceNormalCartographic( new Cartographic(centerLongitude, centerLatitude) ); Cartesian3.multiplyByScalar(translation, -5.0, translation); modelMatrix = Matrix4.fromTranslation(translation); var offset = CesiumMath.toRadians(0.01); var rectangle = new Rectangle( centerLongitude - offset, centerLatitude - offset, centerLongitude + offset, centerLatitude + offset ); reusableGlobePrimitive = createPrimitive(rectangle, Pass.GLOBE); reusableTilesetPrimitive = createPrimitive( rectangle, Pass.CESIUM_3D_TILE ); }); afterAll(function () { reusableGlobePrimitive.destroy(); reusableTilesetPrimitive.destroy(); scene.destroyForSpecs(); }); beforeEach(function () { setCamera(centerLongitude, centerLatitude); // wrap rectangle primitive so it gets executed during the globe pass and 3D Tiles pass to lay down depth globePrimitive = new MockPrimitive(reusableGlobePrimitive, Pass.GLOBE); tilesetPrimitive = new MockPrimitive( reusableTilesetPrimitive, Pass.CESIUM_3D_TILE ); scene.primitives.add(globePrimitive); scene.primitives.add(tilesetPrimitive); }); afterEach(function () { scene.primitives.removeAll(); globePrimitive = globePrimitive && !globePrimitive.isDestroyed() && globePrimitive.destroy(); tilesetPrimitive = tilesetPrimitive && !tilesetPrimitive.isDestroyed() && tilesetPrimitive.destroy(); }); function expectRender(scene, model) { model.show = false; expect(scene).toRender([0, 0, 0, 255]); model.show = true; expect(scene).toRenderAndCall(function (rgba) { expect(rgba).not.toEqual([0, 0, 0, 255]); }); } function expectRenderBlank(scene, model) { model.show = false; expect(scene).toRender([0, 0, 0, 255]); model.show = true; expect(scene).toRender([0, 0, 0, 255]); } function loadGltf(model) { return Resource.fetchArrayBuffer(model).then(function (arrayBuffer) { var gltf = new Uint8Array(arrayBuffer); gltf = parseGlb(gltf); updateVersion(gltf); addDefaults(gltf); return gltf; }); } function loadClassificationModel(url, classificationType) { return Resource.fetchArrayBuffer(url).then(function (arrayBuffer) { var model = scene.primitives.add( new ClassificationModel({ gltf: arrayBuffer, classificationType: classificationType, modelMatrix: modelMatrix, }) ); var ready = false; model.readyPromise.then(function () { ready = true; }); return pollToPromise(function () { scene.renderForSpecs(); return ready; }).then(function () { return model; }); }); } it("classifies 3D Tiles", function () { return loadClassificationModel( batchedModel, ClassificationType.CESIUM_3D_TILE ).then(function (model) { globePrimitive.show = false; tilesetPrimitive.show = true; expectRender(scene, model); globePrimitive.show = true; tilesetPrimitive.show = false; expectRenderBlank(scene, model); globePrimitive.show = true; tilesetPrimitive.show = true; }); }); it("classifies globe", function () { return loadClassificationModel( batchedModel, ClassificationType.TERRAIN ).then(function (model) { globePrimitive.show = false; tilesetPrimitive.show = true; expectRenderBlank(scene, model); globePrimitive.show = true; tilesetPrimitive.show = false; expectRender(scene, model); globePrimitive.show = true; tilesetPrimitive.show = true; }); }); it("classifies both 3D Tiles and globe", function () { return loadClassificationModel( batchedModel, ClassificationType.BOTH ).then(function (model) { globePrimitive.show = false; tilesetPrimitive.show = true; expectRender(scene, model); globePrimitive.show = true; tilesetPrimitive.show = false; expectRender(scene, model); globePrimitive.show = true; tilesetPrimitive.show = true; }); }); it("renders batched model", function () { return loadClassificationModel( batchedModel, ClassificationType.BOTH ).then(function (model) { expectRender(scene, model); }); }); it("renders batched model with quantization", function () { return loadClassificationModel( quantizedModel, ClassificationType.BOTH ).then(function (model) { expectRender(scene, model); }); }); it("throws with invalid number of nodes", function () { return loadGltf(batchedModel).then(function (gltf) { gltf.nodes.push({}); expect(function () { return new ClassificationModel({ gltf: gltf, }); }).toThrowRuntimeError(); }); }); it("throws with invalid number of meshes", function () { return loadGltf(batchedModel).then(function (gltf) { gltf.meshes.push({}); expect(function () { return new ClassificationModel({ gltf: gltf, }); }).toThrowRuntimeError(); }); }); it("throws with invalid number of primitives", function () { return loadGltf(batchedModel).then(function (gltf) { gltf.meshes[0].primitives.push({}); expect(function () { return new ClassificationModel({ gltf: gltf, }); }).toThrowRuntimeError(); }); }); it("throws with position semantic", function () { return loadGltf(batchedModel).then(function (gltf) { gltf.meshes[0].primitives[0].attributes.POSITION = undefined; expect(function () { return new ClassificationModel({ gltf: gltf, }); }).toThrowRuntimeError(); }); }); it("throws with batch id semantic", function () { return loadGltf(batchedModel).then(function (gltf) { gltf.meshes[0].primitives[0].attributes._BATCHID = undefined; expect(function () { return new ClassificationModel({ gltf: gltf, }); }).toThrowRuntimeError(); }); }); }, "WebGL" );