defineSuite([ 'Scene/Primitive', 'Core/BoundingSphere', 'Core/BoxGeometry', 'Core/Cartesian3', 'Core/ColorGeometryInstanceAttribute', 'Core/ComponentDatatype', 'Core/CylinderGeometry', 'Core/defined', 'Core/DistanceDisplayConditionGeometryInstanceAttribute', 'Core/Ellipsoid', 'Core/Geometry', 'Core/GeometryAttribute', 'Core/GeometryInstance', 'Core/GeometryInstanceAttribute', 'Core/HeadingPitchRange', 'Core/Math', 'Core/Matrix4', 'Core/PerspectiveFrustum', 'Core/PolygonGeometry', 'Core/PrimitiveType', 'Core/Rectangle', 'Core/RectangleGeometry', 'Core/ShowGeometryInstanceAttribute', 'Core/Transforms', 'Scene/Camera', 'Scene/MaterialAppearance', 'Scene/PerInstanceColorAppearance', 'Scene/SceneMode', 'Specs/BadGeometry', 'Specs/createContext', 'Specs/createFrameState', 'Specs/createScene', 'Specs/pollToPromise' ], function( Primitive, BoundingSphere, BoxGeometry, Cartesian3, ColorGeometryInstanceAttribute, ComponentDatatype, CylinderGeometry, defined, DistanceDisplayConditionGeometryInstanceAttribute, Ellipsoid, Geometry, GeometryAttribute, GeometryInstance, GeometryInstanceAttribute, HeadingPitchRange, CesiumMath, Matrix4, PerspectiveFrustum, PolygonGeometry, PrimitiveType, Rectangle, RectangleGeometry, ShowGeometryInstanceAttribute, Transforms, Camera, MaterialAppearance, PerInstanceColorAppearance, SceneMode, BadGeometry, createContext, createFrameState, createScene, pollToPromise) { 'use strict'; var scene; var context; var frameStateContext; var frameState; var ellipsoid; var rectangle1; var rectangle2; var rectangleInstance1; var rectangleInstance2; var primitive; beforeAll(function() { scene = createScene(); scene.primitives.destroyPrimitives = false; context = scene.context; ellipsoid = Ellipsoid.WGS84; frameStateContext = createContext(); }); afterAll(function() { scene.destroyForSpecs(); frameStateContext.destroyForSpecs(); }); beforeEach(function() { scene.morphTo3D(0); var camera = scene.camera; camera.frustum = new PerspectiveFrustum(); camera.frustum.aspectRatio = scene.drawingBufferWidth / scene.drawingBufferHeight; camera.frustum.fov = CesiumMath.toRadians(60.0); scene.frameState.passes.render = true; scene.frameState.passes.pick = false; // Mock frameState, separate from scene.frameState, used for test that call primitive.update directly frameState = createFrameState(frameStateContext); rectangle1 = Rectangle.fromDegrees(-80.0, 20.0, -70.0, 30.0); rectangle2 = Rectangle.fromDegrees(70.0, 20.0, 80.0, 30.0); var translation = Cartesian3.multiplyByScalar(Cartesian3.normalize(ellipsoid.cartographicToCartesian(Rectangle.center(rectangle1)), new Cartesian3()), 2.0, new Cartesian3()); rectangleInstance1 = new GeometryInstance({ geometry : new RectangleGeometry({ vertexFormat : PerInstanceColorAppearance.VERTEX_FORMAT, ellipsoid : ellipsoid, rectangle : rectangle1 }), modelMatrix : Matrix4.fromTranslation(translation, new Matrix4()), id : 'rectangle1', attributes : { color : new ColorGeometryInstanceAttribute(1.0, 1.0, 0.0, 1.0), show : new ShowGeometryInstanceAttribute(true) } }); translation = Cartesian3.multiplyByScalar(Cartesian3.normalize(ellipsoid.cartographicToCartesian(Rectangle.center(rectangle2)), new Cartesian3()), 3.0, new Cartesian3()); rectangleInstance2 = new GeometryInstance({ geometry : new RectangleGeometry({ vertexFormat : PerInstanceColorAppearance.VERTEX_FORMAT, ellipsoid : ellipsoid, rectangle : rectangle2 }), modelMatrix : Matrix4.fromTranslation(translation, new Matrix4()), id : 'rectangle2', attributes : { color : new ColorGeometryInstanceAttribute(0.0, 1.0, 1.0, 1.0), show : new ShowGeometryInstanceAttribute(true) } }); }); afterEach(function() { scene.primitives.removeAll(); primitive = primitive && !primitive.isDestroyed() && primitive.destroy(); }); it('default constructs', function() { primitive = new Primitive(); expect(primitive.geometryInstances).not.toBeDefined(); expect(primitive.appearance).not.toBeDefined(); expect(primitive.depthFailAppearance).not.toBeDefined(); expect(primitive.modelMatrix).toEqual(Matrix4.IDENTITY); expect(primitive.show).toEqual(true); expect(primitive.vertexCacheOptimize).toEqual(false); expect(primitive.interleave).toEqual(false); expect(primitive.compressVertices).toEqual(true); expect(primitive.releaseGeometryInstances).toEqual(true); expect(primitive.allowPicking).toEqual(true); expect(primitive.cull).toEqual(true); expect(primitive.asynchronous).toEqual(true); expect(primitive.debugShowBoundingVolume).toEqual(false); }); it('Constructs with options', function() { var geometryInstances = {}; var appearance = {}; var depthFailAppearance = {}; var modelMatrix = Matrix4.fromUniformScale(5.0); primitive = new Primitive({ geometryInstances : geometryInstances, appearance : appearance, depthFailAppearance : depthFailAppearance, modelMatrix : modelMatrix, show : false, vertexCacheOptimize : true, interleave : true, compressVertices : false, releaseGeometryInstances : false, allowPicking : false, cull : false, asynchronous : false, debugShowBoundingVolume : true }); expect(primitive.geometryInstances).toEqual(geometryInstances); expect(primitive.appearance).toEqual(appearance); expect(primitive.depthFailAppearance).toEqual(depthFailAppearance); expect(primitive.modelMatrix).toEqual(modelMatrix); expect(primitive.show).toEqual(false); expect(primitive.vertexCacheOptimize).toEqual(true); expect(primitive.interleave).toEqual(true); expect(primitive.compressVertices).toEqual(false); expect(primitive.releaseGeometryInstances).toEqual(false); expect(primitive.allowPicking).toEqual(false); expect(primitive.cull).toEqual(false); expect(primitive.asynchronous).toEqual(false); expect(primitive.debugShowBoundingVolume).toEqual(true); }); it('releases geometry instances when releaseGeometryInstances is true', function() { primitive = new Primitive({ geometryInstances : [rectangleInstance1, rectangleInstance2], appearance : new PerInstanceColorAppearance(), releaseGeometryInstances : true, asynchronous : false }); expect(primitive.geometryInstances).toBeDefined(); scene.primitives.add(primitive); scene.renderForSpecs(); expect(primitive.geometryInstances).not.toBeDefined(); }); it('does not release geometry instances when releaseGeometryInstances is false', function() { primitive = new Primitive({ geometryInstances : [rectangleInstance1, rectangleInstance2], appearance : new PerInstanceColorAppearance(), releaseGeometryInstances : false, asynchronous : false }); expect(primitive.geometryInstances).toBeDefined(); scene.primitives.add(primitive); scene.renderForSpecs(); expect(primitive.geometryInstances).toBeDefined(); }); it('adds afterRender promise to frame state', function() { primitive = new Primitive({ geometryInstances : [rectangleInstance1, rectangleInstance2], appearance : new PerInstanceColorAppearance(), releaseGeometryInstances : false, asynchronous : false }); scene.primitives.add(primitive); scene.renderForSpecs(); return primitive.readyPromise.then(function(param) { expect(param.ready).toBe(true); }); }); it('does not render when geometryInstances is an empty array', function() { primitive = new Primitive({ geometryInstances : [], appearance : new PerInstanceColorAppearance(), asynchronous : false }); frameState.commandList.length = 0; primitive.update(frameState); expect(frameState.commandList.length).toEqual(0); }); it('does not render when show is false', function() { primitive = new Primitive({ geometryInstances : [rectangleInstance1, rectangleInstance2], appearance : new PerInstanceColorAppearance(), asynchronous : false }); primitive.update(frameState); expect(frameState.commandList.length).toBeGreaterThan(0); frameState.commandList.length = 0; primitive.show = false; primitive.update(frameState); expect(frameState.commandList.length).toEqual(0); }); it('does not render other than for the color or pick pass', function() { primitive = new Primitive({ geometryInstances : [rectangleInstance1, rectangleInstance2], appearance : new PerInstanceColorAppearance(), asynchronous : false }); frameState.passes.render = false; frameState.passes.pick = false; primitive.update(frameState); expect(frameState.commandList.length).toEqual(0); }); it('does not render when scene3DOnly is true and the scene mode is SCENE2D', function() { primitive = new Primitive({ geometryInstances : [rectangleInstance1, rectangleInstance2], appearance : new PerInstanceColorAppearance(), asynchronous : false }); frameState.mode = SceneMode.SCENE2D; frameState.scene3DOnly = true; primitive.update(frameState); expect(frameState.commandList.length).toEqual(0); }); it('does not render when scene3DOnly is true and the scene mode is COLUMBUS_VIEW', function() { primitive = new Primitive({ geometryInstances : [rectangleInstance1, rectangleInstance2], appearance : new PerInstanceColorAppearance(), asynchronous : false }); frameState.mode = SceneMode.COLUMBUS_VIEW; frameState.scene3DOnly = true; primitive.update(frameState); expect(frameState.commandList.length).toEqual(0); }); it('renders in two passes for closed, translucent geometry', function() { primitive = new Primitive({ geometryInstances : new GeometryInstance({ geometry : BoxGeometry.fromDimensions({ vertexFormat : PerInstanceColorAppearance.VERTEX_FORMAT, dimensions : new Cartesian3(500000.0, 500000.0, 500000.0) }), id : 'box', attributes : { color : new ColorGeometryInstanceAttribute(1.0, 1.0, 0.0, 0.5) } }), appearance : new PerInstanceColorAppearance({ closed : true, translucent : true }), asynchronous : false }); var frameState = scene.frameState; frameState.commandList.length = 0; // set scene3DOnly to true so that the geometry is not split due to the IDL frameState.scene3DOnly = true; scene.primitives.add(primitive); scene.render(); expect(frameState.commandList.length).toEqual(2); }); function verifyPrimitiveRender(primitive, rectangle) { scene.primitives.removeAll(); if (defined(rectangle)){ scene.camera.setView({ destination : rectangle }); } expect(scene).toRender([0, 0, 0, 255]); scene.primitives.add(primitive); expect(scene).notToRender([0, 0, 0, 255]); } it('renders in Columbus view when scene3DOnly is false', function() { scene.frameState.scene3DOnly = false; primitive = new Primitive({ geometryInstances : [rectangleInstance1, rectangleInstance2], appearance : new PerInstanceColorAppearance(), asynchronous : false }); scene.morphToColumbusView(0); verifyPrimitiveRender(primitive, rectangle1); verifyPrimitiveRender(primitive, rectangle2); }); it('renders in 2D when scene3DOnly is false', function() { scene.frameState.scene3DOnly = false; primitive = new Primitive({ geometryInstances : [rectangleInstance1, rectangleInstance2], appearance : new PerInstanceColorAppearance(), asynchronous : false }); scene.morphTo2D(0); verifyPrimitiveRender(primitive, rectangle1); verifyPrimitiveRender(primitive, rectangle2); }); it('renders RTC', function() { var dimensions = new Cartesian3(400.0, 300.0, 500.0); var positionOnEllipsoid = Cartesian3.fromDegrees(-105.0, 45.0); var boxModelMatrix = Matrix4.multiplyByTranslation( Transforms.eastNorthUpToFixedFrame(positionOnEllipsoid), new Cartesian3(0.0, 0.0, dimensions.z * 0.5), new Matrix4()); var boxGeometry = BoxGeometry.createGeometry(BoxGeometry.fromDimensions({ vertexFormat : PerInstanceColorAppearance.VERTEX_FORMAT, dimensions : dimensions })); var positions = boxGeometry.attributes.position.values; var newPositions = new Float32Array(positions.length); for (var i = 0; i < positions.length; ++i) { newPositions[i] = positions[i]; } boxGeometry.attributes.position.values = newPositions; boxGeometry.attributes.position.componentDatatype = ComponentDatatype.FLOAT; BoundingSphere.transform(boxGeometry.boundingSphere, boxModelMatrix, boxGeometry.boundingSphere); var boxGeometryInstance = new GeometryInstance({ geometry : boxGeometry, attributes : { color : new ColorGeometryInstanceAttribute(1.0, 0.0, 0.0, 0.5) } }); var primitive = new Primitive({ geometryInstances : boxGeometryInstance, appearance : new PerInstanceColorAppearance({ closed: true }), asynchronous : false, allowPicking : false, rtcCenter : boxGeometry.boundingSphere.center }); // create test camera var camera = scene.camera; var testCamera = new Camera(scene); testCamera.viewBoundingSphere(boxGeometry.boundingSphere); scene.camera = testCamera; scene.frameState.scene3DOnly = true; verifyPrimitiveRender(primitive); scene.camera = camera; }); it('renders with depth fail appearance', function() { var rect = Rectangle.fromDegrees(-1.0, -1.0, 1.0, 1.0); var translation = Cartesian3.multiplyByScalar(Cartesian3.normalize(ellipsoid.cartographicToCartesian(Rectangle.center(rect)), new Cartesian3()), 100.0, new Cartesian3()); var rectInstance = new GeometryInstance({ geometry : new RectangleGeometry({ vertexFormat : PerInstanceColorAppearance.VERTEX_FORMAT, ellipsoid : ellipsoid, rectangle : rect }), modelMatrix : Matrix4.fromTranslation(translation, new Matrix4()), id : 'rect', attributes : { color : new ColorGeometryInstanceAttribute(1.0, 1.0, 0.0, 1.0) } }); var p0 = new Primitive({ geometryInstances : rectInstance, appearance : new PerInstanceColorAppearance({ translucent : false }), asynchronous : false }); var rectInstance2 = new GeometryInstance({ geometry : new RectangleGeometry({ vertexFormat : PerInstanceColorAppearance.VERTEX_FORMAT, ellipsoid : ellipsoid, rectangle : rect }), id : 'rect2', attributes : { color : new ColorGeometryInstanceAttribute(1.0, 0.0, 0.0, 1.0), depthFailColor : new ColorGeometryInstanceAttribute(1.0, 0.0, 1.0, 1.0) } }); var p1 = new Primitive({ geometryInstances : rectInstance2, appearance : new PerInstanceColorAppearance({ translucent : false }), depthFailAppearance : new PerInstanceColorAppearance({ translucent : false }), asynchronous : false }); scene.primitives.add(p0); scene.primitives.add(p1); scene.camera.setView({ destination : rect }); scene.renderForSpecs(); expect(scene).toRender([255, 0, 255, 255]); }); it('pick with depth fail appearance', function() { var rect = Rectangle.fromDegrees(-1.0, -1.0, 1.0, 1.0); var translation = Cartesian3.multiplyByScalar(Cartesian3.normalize(ellipsoid.cartographicToCartesian(Rectangle.center(rect)), new Cartesian3()), 100.0, new Cartesian3()); var rectInstance = new GeometryInstance({ geometry : new RectangleGeometry({ vertexFormat : PerInstanceColorAppearance.VERTEX_FORMAT, ellipsoid : ellipsoid, rectangle : rect }), modelMatrix : Matrix4.fromTranslation(translation, new Matrix4()), id : 'rect', attributes : { color : new ColorGeometryInstanceAttribute(1.0, 1.0, 0.0, 1.0) } }); var p0 = new Primitive({ geometryInstances : rectInstance, appearance : new PerInstanceColorAppearance({ translucent : false }), asynchronous : false }); var rectInstance2 = new GeometryInstance({ geometry : new RectangleGeometry({ vertexFormat : PerInstanceColorAppearance.VERTEX_FORMAT, ellipsoid : ellipsoid, rectangle : rect }), id : 'rect2', attributes : { color : new ColorGeometryInstanceAttribute(1.0, 0.0, 0.0, 1.0), depthFailColor : new ColorGeometryInstanceAttribute(1.0, 0.0, 1.0, 1.0) } }); var p1 = new Primitive({ geometryInstances : rectInstance2, appearance : new PerInstanceColorAppearance({ translucent : false }), depthFailAppearance : new PerInstanceColorAppearance({ translucent : false }), asynchronous : false }); scene.primitives.add(p0); scene.primitives.add(p1); scene.camera.setView({ destination : rect }); scene.renderForSpecs(); expect(scene).toPickAndCall(function(result) { expect(result.primitive).toEqual(p1); expect(result.id).toEqual('rect2'); }); }); it('RTC throws with more than one instance', function() { expect(function() { return new Primitive({ geometryInstances : [rectangleInstance1, rectangleInstance2], appearance : new PerInstanceColorAppearance({ closed: true }), asynchronous : false, allowPicking : false, rtcCenter : Cartesian3.ZERO }); }).toThrowDeveloperError(); }); it('RTC throws if the scene is not 3D only', function() { scene.frameState.scene3DOnly = false; var primitive = new Primitive({ geometryInstances : rectangleInstance1, appearance : new PerInstanceColorAppearance({ closed: true }), asynchronous : false, allowPicking : false, rtcCenter : Cartesian3.ZERO }); expect(function() { verifyPrimitiveRender(primitive); }).toThrowDeveloperError(); }); it('updates model matrix for one instance in 3D', function() { primitive = new Primitive({ geometryInstances : rectangleInstance1, appearance : new PerInstanceColorAppearance(), asynchronous : false }); primitive.update(frameState); var commands = frameState.commandList; expect(commands.length).toEqual(1); expect(commands[0].modelMatrix).toEqual(primitive.modelMatrix); var modelMatrix = Matrix4.fromUniformScale(10.0); primitive.modelMatrix = modelMatrix; commands.length = 0; primitive.update(frameState); expect(commands.length).toEqual(1); expect(commands[0].modelMatrix).toEqual(modelMatrix); }); it('updates model matrix for more than one instance in 3D with equal model matrices in 3D only scene', function() { var modelMatrix = Matrix4.fromUniformScale(2.0); rectangleInstance1.modelMatrix = modelMatrix; rectangleInstance2.modelMatrix = modelMatrix; primitive = new Primitive({ geometryInstances : [rectangleInstance1, rectangleInstance2], appearance : new PerInstanceColorAppearance(), asynchronous : false }); frameState.scene3DOnly = true; var commands = frameState.commandList; commands.length = 0; primitive.update(frameState); expect(commands.length).toEqual(1); expect(commands[0].modelMatrix).toEqual(modelMatrix); modelMatrix = Matrix4.fromUniformScale(10.0); primitive.modelMatrix = modelMatrix; commands.length = 0; primitive.update(frameState); expect(commands.length).toEqual(1); expect(commands[0].modelMatrix).toEqual(modelMatrix); }); it('computes model matrix when given one for a single instance and for the primitive in 3D only', function() { var instanceModelMatrix = Matrix4.fromUniformScale(2.0); var dimensions = new Cartesian3(400000.0, 300000.0, 500000.0); var positionOnEllipsoid = Cartesian3.fromDegrees(-105.0, 45.0); var primitiveModelMatrix = Matrix4.multiplyByTranslation( Transforms.eastNorthUpToFixedFrame(positionOnEllipsoid), new Cartesian3(0.0, 0.0, dimensions.z * 0.5), new Matrix4()); var boxGeometry = BoxGeometry.fromDimensions({ vertexFormat : PerInstanceColorAppearance.VERTEX_FORMAT, dimensions : dimensions }); var boxGeometryInstance = new GeometryInstance({ geometry : boxGeometry, modelMatrix : instanceModelMatrix, attributes : { color : new ColorGeometryInstanceAttribute(1.0, 0.0, 0.0, 1.0) } }); primitive = new Primitive({ geometryInstances : boxGeometryInstance, modelMatrix : primitiveModelMatrix, appearance : new PerInstanceColorAppearance({ translucent : false, closed: true }), asynchronous : false }); var expectedModelMatrix = Matrix4.multiplyTransformation(primitiveModelMatrix, instanceModelMatrix, new Matrix4()); frameState.scene3DOnly = true; var commands = frameState.commandList; commands.length = 0; primitive.update(frameState); expect(commands.length).toEqual(1); expect(commands[0].modelMatrix).toEqual(expectedModelMatrix); }); it('update model matrix throws in Columbus view', function() { primitive = new Primitive({ geometryInstances : [rectangleInstance1, rectangleInstance2], appearance : new PerInstanceColorAppearance(), asynchronous : false }); frameState.mode = SceneMode.COLUMBUS_VIEW; frameState.scene3DOnly = false; var commands = frameState.commandList; commands.length = 0; primitive.update(frameState); expect(commands.length).toEqual(1); expect(commands[0].modelMatrix).toEqual(Matrix4.IDENTITY); var modelMatrix = Matrix4.fromUniformScale(10.0); primitive.modelMatrix = modelMatrix; commands.length = 0; expect(function() { primitive.update(frameState); }).toThrowDeveloperError(); }); it('update model matrix throws in 2D', function() { primitive = new Primitive({ geometryInstances : [rectangleInstance1, rectangleInstance2], appearance : new PerInstanceColorAppearance(), asynchronous : false }); frameState.mode = SceneMode.SCENE2D; frameState.scene3DOnly = false; var commands = frameState.commandList; primitive.update(frameState); expect(commands.length).toEqual(1); expect(commands[0].modelMatrix).toEqual(Matrix4.IDENTITY); var modelMatrix = Matrix4.fromUniformScale(10.0); primitive.modelMatrix = modelMatrix; commands.length = 0; expect(function() { primitive.update(frameState); }).toThrowDeveloperError(); }); it('renders bounding volume with debugShowBoundingVolume', function() { primitive = new Primitive({ geometryInstances : rectangleInstance1, appearance : new PerInstanceColorAppearance(), asynchronous : false, debugShowBoundingVolume : true }); scene.primitives.add(primitive); scene.camera.setView({ destination : rectangle1 }); expect(scene).toRenderAndCall(function(rgba) { expect(rgba[0]).not.toEqual(0); expect(rgba[1]).toBeGreaterThanOrEqualTo(0); expect(rgba[2]).toBeGreaterThanOrEqualTo(0); expect(rgba[3]).toEqual(255); }); }); it('transforms to world coordinates', function() { primitive = new Primitive({ geometryInstances : [rectangleInstance1, rectangleInstance2], appearance : new PerInstanceColorAppearance(), asynchronous : false }); verifyPrimitiveRender(primitive, rectangle1); verifyPrimitiveRender(primitive, rectangle2); expect(primitive.modelMatrix).toEqual(Matrix4.IDENTITY); }); it('does not transform to world coordinates', function() { rectangleInstance2.modelMatrix = Matrix4.clone(rectangleInstance1.modelMatrix); primitive = new Primitive({ geometryInstances : [rectangleInstance1, rectangleInstance2], appearance : new PerInstanceColorAppearance(), asynchronous : false }); scene.frameState.scene3DOnly = true; verifyPrimitiveRender(primitive, rectangle1); verifyPrimitiveRender(primitive, rectangle2); expect(primitive.modelMatrix).not.toEqual(Matrix4.IDENTITY); scene.frameState.scene3DOnly = true; }); it('get common per instance attributes', function() { rectangleInstance2.attributes.not_used = new GeometryInstanceAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 1, value : [0.5] }); primitive = new Primitive({ geometryInstances : [rectangleInstance1, rectangleInstance2], appearance : new PerInstanceColorAppearance(), asynchronous : false }); scene.primitives.add(primitive); scene.renderForSpecs(); var attributes = primitive.getGeometryInstanceAttributes('rectangle1'); expect(attributes.color).toBeDefined(); expect(attributes.show).toBeDefined(); attributes = primitive.getGeometryInstanceAttributes('rectangle2'); expect(attributes.color).toBeDefined(); expect(attributes.show).toBeDefined(); expect(attributes.not_used).not.toBeDefined(); }); it('modify color instance attribute', function() { primitive = new Primitive({ geometryInstances : rectangleInstance1, appearance : new PerInstanceColorAppearance(), asynchronous : false }); scene.camera.setView({ destination : rectangle1 }); scene.primitives.add(primitive); var pixels; expect(scene).toRenderAndCall(function(rgba) { pixels = rgba; expect(rgba).not.toEqual([0, 0, 0, 255]); }); var attributes = primitive.getGeometryInstanceAttributes('rectangle1'); expect(attributes.color).toBeDefined(); attributes.color = [255, 255, 255, 255]; expect(scene).toRenderAndCall(function(rgba) { expect(rgba).not.toEqual([0, 0, 0, 255]); expect(rgba).not.toEqual(pixels); }); }); it('modify show instance attribute', function() { primitive = new Primitive({ geometryInstances : rectangleInstance1, appearance : new PerInstanceColorAppearance(), asynchronous : false }); scene.primitives.add(primitive); scene.camera.setView({ destination : rectangle1 }); expect(scene).notToRender([0, 0, 0, 255]); var attributes = primitive.getGeometryInstanceAttributes('rectangle1'); expect(attributes.show).toBeDefined(); attributes.show = [0]; expect(scene).toRender([0, 0, 0, 255]); }); it('get bounding sphere from per instance attribute', function() { primitive = new Primitive({ geometryInstances : rectangleInstance1, appearance : new PerInstanceColorAppearance(), asynchronous : false }); verifyPrimitiveRender(primitive, rectangle1); var attributes = primitive.getGeometryInstanceAttributes('rectangle1'); expect(attributes.boundingSphere).toBeDefined(); }); it('renders with distance display condition per instance attribute', function() { if (!context.floatingPointTexture) { return; } var near = 10000.0; var far = 1000000.0; var rect = Rectangle.fromDegrees(-1.0, -1.0, 1.0, 1.0); var translation = Cartesian3.multiplyByScalar(Cartesian3.normalize(ellipsoid.cartographicToCartesian(Rectangle.center(rect)), new Cartesian3()), 2.0, new Cartesian3()); var rectInstance = new GeometryInstance({ geometry : new RectangleGeometry({ vertexFormat : PerInstanceColorAppearance.VERTEX_FORMAT, ellipsoid : ellipsoid, rectangle : rect }), modelMatrix : Matrix4.fromTranslation(translation, new Matrix4()), id : 'rect', attributes : { color : new ColorGeometryInstanceAttribute(1.0, 1.0, 0.0, 1.0), distanceDisplayCondition : new DistanceDisplayConditionGeometryInstanceAttribute(near, far) } }); primitive = new Primitive({ geometryInstances : rectInstance, appearance : new PerInstanceColorAppearance(), asynchronous : false }); scene.primitives.add(primitive); scene.camera.setView({ destination : rect }); scene.renderForSpecs(); var boundingSphere = primitive.getGeometryInstanceAttributes('rect').boundingSphere; var center = boundingSphere.center; var radius = boundingSphere.radius; scene.camera.lookAt(center, new HeadingPitchRange(0.0, -CesiumMath.PI_OVER_TWO, radius)); expect(scene).toRender([0, 0, 0, 255]); scene.camera.lookAt(center, new HeadingPitchRange(0.0, -CesiumMath.PI_OVER_TWO, radius + near + 1.0)); expect(scene).notToRender([0, 0, 0, 255]); scene.camera.lookAt(center, new HeadingPitchRange(0.0, -CesiumMath.PI_OVER_TWO, radius + far + 1.0)); expect(scene).toRender([0, 0, 0, 255]); }); it('primitive with display condition properly transforms boundingSphere', function() { var near = 10000.0; var far = 1000000.0; var translation = new Cartesian3(10, 20, 30); var cylinder = new GeometryInstance({ id : 'cylinder', vertexFormat : PerInstanceColorAppearance.VERTEX_FORMAT, geometry : new CylinderGeometry({ length : 10, topRadius : 10, bottomRadius : 10 }), attributes : { color : new ColorGeometryInstanceAttribute(1.0, 1.0, 0.0, 1.0), show : new ShowGeometryInstanceAttribute(true), distanceDisplayCondition : new DistanceDisplayConditionGeometryInstanceAttribute(near, far) } }); primitive = new Primitive({ geometryInstances : cylinder, appearance : new PerInstanceColorAppearance(), modelMatrix : Matrix4.fromTranslation(translation, new Matrix4()), asynchronous : false }); scene.primitives.add(primitive); scene.frameState.scene3DOnly = true; scene.renderForSpecs(); var boundingSphere = primitive.getGeometryInstanceAttributes('cylinder').boundingSphere; var center = boundingSphere.center; expect(center).toEqual(translation); }); it('primitive without display condition properly transforms boundingSphere', function() { var translation = new Cartesian3(10, 20, 30); var cylinder = new GeometryInstance({ id : 'cylinder', vertexFormat : PerInstanceColorAppearance.VERTEX_FORMAT, geometry : new CylinderGeometry({ length : 10, topRadius : 10, bottomRadius : 10 }), attributes : { color : new ColorGeometryInstanceAttribute(1.0, 1.0, 0.0, 1.0), show : new ShowGeometryInstanceAttribute(true) } }); primitive = new Primitive({ geometryInstances : cylinder, appearance : new PerInstanceColorAppearance(), modelMatrix : Matrix4.fromTranslation(translation, new Matrix4()), asynchronous : false }); scene.primitives.add(primitive); scene.frameState.scene3DOnly = true; scene.renderForSpecs(); var boundingSphere = primitive.getGeometryInstanceAttributes('cylinder').boundingSphere; var center = boundingSphere.center; expect(center).toEqual(translation); }); it('getGeometryInstanceAttributes returns same object each time', function() { primitive = new Primitive({ geometryInstances : rectangleInstance1, appearance : new PerInstanceColorAppearance(), asynchronous : false }); verifyPrimitiveRender(primitive, rectangle1); var attributes = primitive.getGeometryInstanceAttributes('rectangle1'); var attributes2 = primitive.getGeometryInstanceAttributes('rectangle1'); expect(attributes).toBe(attributes2); }); it('picking', function() { primitive = new Primitive({ geometryInstances : [rectangleInstance1, rectangleInstance2], appearance : new PerInstanceColorAppearance(), asynchronous : false }); verifyPrimitiveRender(primitive, rectangle1); expect(scene).toPickAndCall(function(result) { expect(result.primitive).toEqual(primitive); expect(result.id).toEqual('rectangle1'); }); verifyPrimitiveRender(primitive, rectangle2); expect(scene).toPickAndCall(function(result) { expect(result.primitive).toEqual(primitive); expect(result.id).toEqual('rectangle2'); }); }); it('does not pick when allowPicking is false', function() { primitive = new Primitive({ geometryInstances : [rectangleInstance1], appearance : new PerInstanceColorAppearance(), allowPicking : false, asynchronous : false }); verifyPrimitiveRender(primitive, rectangle1); expect(scene).notToPick(); }); it('does not cull when cull is false', function() { primitive = new Primitive({ geometryInstances : rectangleInstance1, appearance : new PerInstanceColorAppearance(), asynchronous : false, cull : false }); frameState.commandList.length = 0; primitive.update(frameState); expect(frameState.commandList[0].cull).toEqual(false); }); it('update throws when geometry primitive types are different', function() { primitive = new Primitive({ geometryInstances : [ new GeometryInstance({ geometry : new Geometry({ attributes : { position : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 3, values : new Float32Array([1.0, 2.0, 3.0, 4.0]) }) }, primitiveType : PrimitiveType.LINES }) }), new GeometryInstance({ geometry : new Geometry({ attributes : { position : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 3, values : new Float32Array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0]) }) }, primitiveType : PrimitiveType.TRIANGLES }) }) ], appearance : new PerInstanceColorAppearance(), asynchronous : false }); expect(function() { primitive.update(frameState); }).toThrowDeveloperError(); }); it('failed geometry rejects promise and throws on next update', function() { primitive = new Primitive({ geometryInstances : [new GeometryInstance({ geometry : new BadGeometry() })], appearance : new MaterialAppearance({ materialSupport : MaterialAppearance.MaterialSupport.ALL }), compressVertices : false }); scene.frameState.afterRender.length = 0; scene.primitives.add(primitive); return pollToPromise(function() { for (var i = 0; i < frameState.afterRender.length; ++i) { frameState.afterRender[i](); return true; } primitive.update(frameState); return false; }).then(function() { return primitive.readyPromise.then(function() { fail('should not be called'); }).otherwise(function(e) { expect(e).toBe(primitive._error); // Use toThrow since the error is thrown by RequireJS for the web worker import script expect(function() { scene.render(); }).toThrow(); }); }); }); it('internally invalid asynchronous geometry resolves promise and sets ready', function() { primitive = new Primitive({ geometryInstances : [new GeometryInstance({ geometry : PolygonGeometry.fromPositions({ positions : [] }) })], appearance : new MaterialAppearance({ materialSupport : MaterialAppearance.MaterialSupport.ALL }), compressVertices : false }); scene.frameState.afterRender.length = 0; return pollToPromise(function() { for (var i = 0; i < frameState.afterRender.length; ++i) { frameState.afterRender[i](); return true; } primitive.update(frameState); return false; }).then(function() { return primitive.readyPromise.then(function(arg) { expect(arg).toBe(primitive); expect(primitive.ready).toBe(true); }); }); }); it('internally invalid synchronous geometry resolves promise and sets ready', function() { primitive = new Primitive({ geometryInstances : [new GeometryInstance({ geometry : PolygonGeometry.fromPositions({ positions : [] }) })], appearance : new MaterialAppearance({ materialSupport : MaterialAppearance.MaterialSupport.ALL }), asynchronous : false, compressVertices : false }); scene.frameState.afterRender.length = 0; return pollToPromise(function() { if (scene.frameState.afterRender.length > 0) { scene.frameState.afterRender[0](); return true; } primitive.update(scene.frameState); return false; }).then(function() { return primitive.readyPromise.then(function(arg) { expect(arg).toBe(primitive); expect(primitive.ready).toBe(true); }); }); }); it('can mix valid and invalid geometry', function() { var instances = []; instances.push(rectangleInstance1); instances.push(new GeometryInstance({ geometry : PolygonGeometry.fromPositions({ positions : [] }), attributes : { color : new ColorGeometryInstanceAttribute(1.0, 0.0, 1.0, 1.0) }, id : 'invalid' })); instances.push(rectangleInstance2); primitive = new Primitive({ geometryInstances : instances, appearance : new PerInstanceColorAppearance({ flat : true }) }); return pollToPromise(function() { primitive.update(frameState); if (frameState.afterRender.length > 0) { frameState.afterRender[0](); } return primitive.ready; }).then(function() { expect(primitive.getGeometryInstanceAttributes('rectangle1').boundingSphere).toBeDefined(); expect(primitive.getGeometryInstanceAttributes('rectangle2').boundingSphere).toBeDefined(); expect(primitive.getGeometryInstanceAttributes('invalid').boundingSphere).not.toBeDefined(); }); }); it('shader validation', function() { if (!!window.webglStub) { return; } primitive = new Primitive({ geometryInstances : [rectangleInstance1, rectangleInstance2], appearance : new MaterialAppearance({ materialSupport : MaterialAppearance.MaterialSupport.ALL }), asynchronous : false, compressVertices : false }); expect(function() { primitive.update(frameState); }).toThrowDeveloperError(); }); it('setting per instance attribute throws when value is undefined', function() { primitive = new Primitive({ geometryInstances : rectangleInstance1, appearance : new PerInstanceColorAppearance(), asynchronous : false }); primitive.update(frameState); var attributes = primitive.getGeometryInstanceAttributes('rectangle1'); expect(function() { attributes.color = undefined; }).toThrowDeveloperError(); }); it('can disable picking when asynchronous', function() { primitive = new Primitive({ geometryInstances : rectangleInstance1, appearance : new PerInstanceColorAppearance(), asynchronous : true, allowPicking : false }); frameState.afterRender.length = 0; scene.primitives.add(primitive); return pollToPromise(function() { if (frameState.afterRender.length > 0) { frameState.afterRender[0](); } scene.render(); return primitive.ready; }).then(function() { var attributes = primitive.getGeometryInstanceAttributes('rectangle1'); expect(function() { attributes.color = undefined; }).toThrowDeveloperError(); }); }); it('getGeometryInstanceAttributes throws without id', function() { primitive = new Primitive({ geometryInstances : rectangleInstance1, appearance : new PerInstanceColorAppearance(), asynchronous : false }); scene.primitives.add(primitive); scene.renderForSpecs(); expect(function() { primitive.getGeometryInstanceAttributes(); }).toThrowDeveloperError(); }); it('getGeometryInstanceAttributes throws if update was not called', function() { primitive = new Primitive({ geometryInstances : rectangleInstance1, appearance : new PerInstanceColorAppearance(), asynchronous : false }); expect(function() { primitive.getGeometryInstanceAttributes('rectangle1'); }).toThrowDeveloperError(); }); it('getGeometryInstanceAttributes returns undefined if id does not exist', function() { primitive = new Primitive({ geometryInstances : rectangleInstance1, appearance : new PerInstanceColorAppearance(), asynchronous : false }); scene.primitives.add(primitive); scene.renderForSpecs(); expect(primitive.getGeometryInstanceAttributes('unknown')).not.toBeDefined(); }); it('isDestroyed', function() { primitive = new Primitive(); expect(primitive.isDestroyed()).toEqual(false); primitive.destroy(); expect(primitive.isDestroyed()).toEqual(true); }); it('renders when using asynchronous pipeline', function() { primitive = new Primitive({ geometryInstances : rectangleInstance1, appearance : new PerInstanceColorAppearance({ flat : true }) }); var frameState = scene.frameState; return pollToPromise(function() { primitive.update(frameState); for (var i = 0; i < frameState.afterRender.length; ++i) { frameState.afterRender[i](); } return primitive.ready; }).then(function() { verifyPrimitiveRender(primitive, rectangle1); }); }); it('destroy before asynchonous pipeline is complete', function() { primitive = new Primitive({ geometryInstances : rectangleInstance1, appearance : new PerInstanceColorAppearance() }); primitive.update(frameState); primitive.destroy(); expect(primitive.isDestroyed()).toEqual(true); }); }, 'WebGL');