From d1880142fa075331109234ea028d71681ed71c38 Mon Sep 17 00:00:00 2001 From: ggetz Date: Mon, 6 Nov 2017 11:34:26 -0500 Subject: [PATCH 01/37] Add plane geometry --- Source/Core/PlaneGeometry.js | 462 ++++++++++++++++++++++++++ Source/Workers/createPlaneGeometry.js | 15 + 2 files changed, 477 insertions(+) create mode 100644 Source/Core/PlaneGeometry.js create mode 100644 Source/Workers/createPlaneGeometry.js diff --git a/Source/Core/PlaneGeometry.js b/Source/Core/PlaneGeometry.js new file mode 100644 index 000000000000..deab47408d95 --- /dev/null +++ b/Source/Core/PlaneGeometry.js @@ -0,0 +1,462 @@ +define([ + './BoundingSphere', + './Cartesian3', + './Check', + './ComponentDatatype', + './defaultValue', + './defined', + './Geometry', + './GeometryAttribute', + './GeometryAttributes', + './Matrix4', + './Plane', + './PrimitiveType', + './Quaternion', + './TranslationRotationScale', + './VertexFormat' +], function( + BoundingSphere, + Cartesian3, + Check, + ComponentDatatype, + defaultValue, + defined, + Geometry, + GeometryAttribute, + GeometryAttributes, + Matrix4, + Plane, + PrimitiveType, + Quaternion, + TranslationRotationScale, + VertexFormat) { + 'use strict'; + + var scratchCartesian = new Cartesian3(); + var scratchMatrix = new Matrix4(); + + /** + * Describes a plane by a normal and distance from the origin in world coordinates. + * + * @alias Plane + * @constructor + * + * @param {Object} options Object with the following properties: + * @param {Cartesian3} options.minimum The minimum x, y, and z coordinates of the box. + * @param {Cartesian3} options.maximum The maximum x, y, and z coordinates of the box. + * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed. + * + * @see Plane + * @see PlaneGeometry.fromDimensions + * @see PlaneGeometry.createGeometry + * @see Packable + * + * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Box.html|Cesium Sandcastle Box Demo} + * + * @example + * var planeGeometry = new Cesium.PlaneGeometry({ + * vertexFormat : Cesium.VertexFormat.POSITION_ONLY, + * maximum : new Cesium.Cartesian3(250000.0, 250000.0, 250000.0), + * minimum : new Cesium.Cartesian3(-250000.0, -250000.0, -250000.0) + * }); + * var geometry = Cesium.PlaneGeometry.createGeometry(plane); + */ + function PlaneGeometry(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + var min = options.minimum; + var max = options.maximum; + + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object('min', min); + Check.typeOf.object('max', max); + //>>includeEnd('debug'); + + var vertexFormat = defaultValue(options.vertexFormat, VertexFormat.DEFAULT); + + // only need a min and max, but they must be planar + this._minimum = min; + this._maximum = max; + this._vertexFormat = vertexFormat; + this._workerName = 'createPlaneGeometry'; + } + + PlaneGeometry.fromPlane = function (options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + var plane = options.plane; + var scale = defaultValue(options.scale, new Cartesian3(1.0, 1.0, 1.0)); + + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object('plane', plane); + Check.typeOf.object('scale', scale); + //>>includeEnd('debug'); + + var min = new Cartesian3(-0.5, -0.5, 0.0); + var max = new Cartesian3( 0.5, 0.5, 0.0); + + var location = Cartesian3.multiplyByScalar(plane.normal, plane.distance, scratchCartesian); + var orientation = Quaternion.IDENTITY; // orient z-axis to plane normal + + var transformation = Matrix4.fromTranslationRotationScale(new TranslationRotationScale( + location, orientation, scale + ), scratchMatrix); + + min = Matrix4.multiplyByPoint(transformation, min, min); + max = Matrix4.multiplyByPoint(transformation, max, max); + return new PlaneGeometry({ + minimum : min, + maximum : max, + vertexFormat : options.vertexFormat + }); + }; + + /** + * The number of elements used to pack the object into an array. + * @type {Number} + */ + PlaneGeometry.packedLength = 2 * Cartesian3.packedLength + VertexFormat.packedLength; + + /** + * Stores the provided instance into the provided array. + * + * @param {PlaneGeometry} value The value to pack. + * @param {Number[]} array The array to pack into. + * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements. + * + * @returns {Number[]} The array that was packed into + */ + PlaneGeometry.pack = function(value, array, startingIndex) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object('value', value); + Check.defined('array', array); + //>>includeEnd('debug'); + + startingIndex = defaultValue(startingIndex, 0); + + Cartesian3.pack(value._minimum, array, startingIndex); + Cartesian3.pack(value._maximum, array, startingIndex + Cartesian3.packedLength); + VertexFormat.pack(value._vertexFormat, array, startingIndex + 2 * Cartesian3.packedLength); + + return array; + }; + + var scratchMin = new Cartesian3(); + var scratchMax = new Cartesian3(); + var scratchVertexFormat = new VertexFormat(); + var scratchOptions = { + minimum: scratchMin, + maximum: scratchMax, + vertexFormat: scratchVertexFormat + }; + + /** + * Retrieves an instance from a packed array. + * + * @param {Number[]} array The packed array. + * @param {Number} [startingIndex=0] The starting index of the element to be unpacked. + * @param {PlaneGeometry} [result] The object into which to store the result. + * @returns {PlaneGeometry} The modified result parameter or a new BoxGeometry instance if one was not provided. + */ + PlaneGeometry.unpack = function(array, startingIndex, result) { + //>>includeStart('debug', pragmas.debug); + Check.defined('array', array); + //>>includeEnd('debug'); + + startingIndex = defaultValue(startingIndex, 0); + + var min = Cartesian3.unpack(array, startingIndex, scratchMin); + var max = Cartesian3.unpack(array, startingIndex + Cartesian3.packedLength, scratchMax); + var vertexFormat = VertexFormat.unpack(array, startingIndex + 2 * Cartesian3.packedLength, scratchVertexFormat); + + if (!defined(result)) { + return new PlaneGeometry(scratchOptions); + } + + result._minimum = Cartesian3.clone(min, result._minimum); + result._maximum = Cartesian3.clone(max, result._maximum); + result._vertexFormat = VertexFormat.clone(vertexFormat, result._vertexFormat); + + return result; + }; + + /** + * Computes the geometric representation of a plane, including its vertices, indices, and a bounding sphere. + * + * @param {PlaneGeometry} planeGeometry A description of the plane. + * @returns {Geometry|undefined} The computed vertices and indices. + */ + PlaneGeometry.createGeometry = function(planeGeometry) { + var min = planeGeometry._minimum; + var max = planeGeometry._maximum; + var vertexFormat = planeGeometry._vertexFormat; + + if (Cartesian3.equals(min, max)) { + return; + } + + var attributes = new GeometryAttributes(); + var indices; + var positions; + + // TODO: tangents should be the plane normal + + if (vertexFormat.position && + (vertexFormat.st || vertexFormat.normal || vertexFormat.tangent || vertexFormat.bitangent)) { + if (vertexFormat.position) { + // 4 corner points. Duplicated 3 times each for each incident edge/face. + positions = new Float64Array(2 * 4 * 3); + + // +z face + positions[0] = min.x; + positions[1] = min.y; + positions[2] = max.z; + positions[3] = max.x; + positions[4] = min.y; + positions[5] = max.z; + positions[6] = max.x; + positions[7] = max.y; + positions[8] = max.z; + positions[9] = min.x; + positions[10] = max.y; + positions[11] = max.z; + + // -z face + positions[12] = min.x; + positions[13] = min.y; + positions[14] = min.z; + positions[15] = max.x; + positions[16] = min.y; + positions[17] = min.z; + positions[18] = max.x; + positions[19] = max.y; + positions[20] = min.z; + positions[21] = min.x; + positions[22] = max.y; + positions[23] = min.z; + + attributes.position = new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : positions + }); + } + + if (vertexFormat.normal) { + var normals = new Float32Array(2 * 4 * 3); + + // +z face + normals[0] = 0.0; + normals[1] = 0.0; + normals[2] = 1.0; + normals[3] = 0.0; + normals[4] = 0.0; + normals[5] = 1.0; + normals[6] = 0.0; + normals[7] = 0.0; + normals[8] = 1.0; + normals[9] = 0.0; + normals[10] = 0.0; + normals[11] = 1.0; + + // -z face + normals[12] = 0.0; + normals[13] = 0.0; + normals[14] = -1.0; + normals[15] = 0.0; + normals[16] = 0.0; + normals[17] = -1.0; + normals[18] = 0.0; + normals[19] = 0.0; + normals[20] = -1.0; + normals[21] = 0.0; + normals[22] = 0.0; + normals[23] = -1.0; + + attributes.normal = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : normals + }); + } + + if (vertexFormat.st) { + var texCoords = new Float32Array(2 * 4 * 2); + + // +z face + texCoords[0] = 0.0; + texCoords[1] = 0.0; + texCoords[2] = 1.0; + texCoords[3] = 0.0; + texCoords[4] = 1.0; + texCoords[5] = 1.0; + texCoords[6] = 0.0; + texCoords[7] = 1.0; + + // -z face + texCoords[8] = 1.0; + texCoords[9] = 0.0; + texCoords[10] = 0.0; + texCoords[11] = 0.0; + texCoords[12] = 0.0; + texCoords[13] = 1.0; + texCoords[14] = 1.0; + texCoords[15] = 1.0; + + attributes.st = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 2, + values : texCoords + }); + } + + if (vertexFormat.tangent) { + var tangents = new Float32Array(2 * 4 * 3); + + // +z face + tangents[0] = 1.0; + tangents[1] = 0.0; + tangents[2] = 0.0; + tangents[3] = 1.0; + tangents[4] = 0.0; + tangents[5] = 0.0; + tangents[6] = 1.0; + tangents[7] = 0.0; + tangents[8] = 0.0; + tangents[9] = 1.0; + tangents[10] = 0.0; + tangents[11] = 0.0; + + // -z face + tangents[12] = -1.0; + tangents[13] = 0.0; + tangents[14] = 0.0; + tangents[15] = -1.0; + tangents[16] = 0.0; + tangents[17] = 0.0; + tangents[18] = -1.0; + tangents[19] = 0.0; + tangents[20] = 0.0; + tangents[21] = -1.0; + tangents[22] = 0.0; + tangents[23] = 0.0; + + attributes.tangent = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : tangents + }); + } + + if (vertexFormat.bitangent) { + var bitangents = new Float32Array(2 * 4 * 3); + + // +z face + bitangents[0] = 0.0; + bitangents[1] = 1.0; + bitangents[2] = 0.0; + bitangents[3] = 0.0; + bitangents[4] = 1.0; + bitangents[5] = 0.0; + bitangents[6] = 0.0; + bitangents[7] = 1.0; + bitangents[8] = 0.0; + bitangents[9] = 0.0; + bitangents[10] = 1.0; + bitangents[11] = 0.0; + + // -z face + bitangents[12] = 0.0; + bitangents[13] = 1.0; + bitangents[14] = 0.0; + bitangents[15] = 0.0; + bitangents[16] = 1.0; + bitangents[17] = 0.0; + bitangents[18] = 0.0; + bitangents[19] = 1.0; + bitangents[20] = 0.0; + bitangents[21] = 0.0; + bitangents[22] = 1.0; + bitangents[23] = 0.0; + + attributes.bitangent = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : bitangents + }); + } + + // 4 triangles: 2 faces, 2 triangles each. + indices = new Uint16Array(2 * 2 * 3); + + // +z face + indices[0] = 0; + indices[1] = 1; + indices[2] = 2; + indices[3] = 0; + indices[4] = 2; + indices[5] = 3; + + // -z face + indices[6] = 4 + 2; + indices[7] = 4 + 1; + indices[8] = 4 + 0; + indices[9] = 4 + 3; + indices[10] = 4 + 2; + indices[11] = 4 + 0; + } else { + // Positions only - no need to duplicate corner points + positions = new Float64Array(4 * 3); + + positions[0] = min.x; + positions[1] = min.y; + positions[2] = min.z; + positions[3] = max.x; + positions[4] = min.y; + positions[5] = min.z; + positions[6] = max.x; + positions[7] = max.y; + positions[8] = min.z; + positions[9] = min.x; + positions[10] = max.y; + positions[11] = min.z; + + attributes.position = new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : positions + }); + + // 12 triangles: 2 faces, 2 triangles each. + indices = new Uint16Array(2 * 2 * 3); + + // plane z = corner.Z + indices[0] = 4; + indices[1] = 5; + indices[2] = 6; + indices[3] = 4; + indices[4] = 6; + indices[5] = 7; + + // plane z = -corner.Z + indices[6] = 1; + indices[7] = 0; + indices[8] = 3; + indices[9] = 1; + indices[10] = 3; + indices[11] = 2; + } + + var diff = Cartesian3.subtract(max, min, scratchCartesian); + var radius = Cartesian3.magnitude(diff) * 0.5; + + return new Geometry({ + attributes : attributes, + indices : indices, + primitiveType : PrimitiveType.TRIANGLES, + boundingSphere : new BoundingSphere(Cartesian3.ZERO, radius) + }); + }; + + return PlaneGeometry; +}); diff --git a/Source/Workers/createPlaneGeometry.js b/Source/Workers/createPlaneGeometry.js new file mode 100644 index 000000000000..23785ac71d65 --- /dev/null +++ b/Source/Workers/createPlaneGeometry.js @@ -0,0 +1,15 @@ +define([ + '../Core/PlaneGeometry', + '../Core/defined' + ], function( + PlaneGeometry, + defined) { + 'use strict'; + + return function(planeGeometry, offset) { + if (defined(offset)) { + planeGeometry = PlaneGeometry.unpack(planeGeometry, offset); + } + return PlaneGeometry.createGeometry(planeGeometry); + }; +}); From d142b0f640ce518808f7b50cbdbeb3c7b0549a1d Mon Sep 17 00:00:00 2001 From: ggetz Date: Thu, 9 Nov 2017 17:20:27 -0500 Subject: [PATCH 02/37] Added plane graphics and geometries, update clipping planes example --- Apps/Sandcastle/gallery/Clipping Planes.html | 164 ++--- CHANGES.md | 2 + Source/Core/Plane.js | 4 - Source/Core/PlaneGeometry.js | 23 +- Source/Core/PlaneOutlineGeometry.js | 143 ++++ Source/DataSources/DataSourceDisplay.js | 3 + Source/DataSources/Entity.js | 13 +- Source/DataSources/PlaneGeometryUpdater.js | 723 +++++++++++++++++++ Source/DataSources/PlaneGraphics.js | 199 +++++ Source/Workers/createPlaneOutlineGeometry.js | 15 + 10 files changed, 1166 insertions(+), 123 deletions(-) create mode 100644 Source/Core/PlaneOutlineGeometry.js create mode 100644 Source/DataSources/PlaneGeometryUpdater.js create mode 100644 Source/DataSources/PlaneGraphics.js create mode 100644 Source/Workers/createPlaneOutlineGeometry.js diff --git a/Apps/Sandcastle/gallery/Clipping Planes.html b/Apps/Sandcastle/gallery/Clipping Planes.html index 16ff8c79c49b..8f993364ea75 100644 --- a/Apps/Sandcastle/gallery/Clipping Planes.html +++ b/Apps/Sandcastle/gallery/Clipping Planes.html @@ -36,34 +36,6 @@

Loading...

- - - - - - - - - - - - - - - - - -
Type
x - - -
y - - -
z - - -
- Enable clipping planes Show debug boundingVolume
@@ -71,7 +43,13 @@ function startup(Cesium) { 'use strict'; //Sandcastle_Begin -var viewer = new Cesium.Viewer('cesiumContainer'); +var viewer = new Cesium.Viewer('cesiumContainer', { + infoBox: false, + selectionIndicator: false +}); +var scene = viewer.scene; + +var planeScale = new Cesium.Cartesian2(300.0, 300.0); var clippingPlanes; var defaultClippingPlanes = [ @@ -81,15 +59,46 @@ ]; var viewModel = { - exampleType : 'BIM', - exampleTypes : ['BIM', 'Model', 'Point Cloud'], - clippingPlanesEnabled: true, - xOffset : 0.0, - yOffset: -100.0, - zOffset: -100.0, debugBoundingVolumesEnabled: false }; +var targetX = 0.0; +var planeEntities = []; +var selectedPlane; + +// Select plane when mouse down +var downHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas); +downHandler.setInputAction(function(movement) { + var pickedObject = scene.pick(movement.position); + if (Cesium.defined(pickedObject) && Cesium.defined(pickedObject.id.plane)) { + selectedPlane = pickedObject.id.plane; + selectedPlane.material = Cesium.Color.WHITE.withAlpha(0.05); + selectedPlane.outlineColor = Cesium.Color.WHITE; + scene.screenSpaceCameraController.enableInputs = false; + } +}, Cesium.ScreenSpaceEventType.LEFT_DOWN); + +// Release plane on mouse up +var upHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas); +upHandler.setInputAction(function() { + if (Cesium.defined(selectedPlane)) { + selectedPlane.material = Cesium.Color.CYAN.withAlpha(0.1); + selectedPlane.outlineColor = Cesium.Color.CYAN; + selectedPlane = undefined; + } + + scene.screenSpaceCameraController.enableInputs = true; +}, Cesium.ScreenSpaceEventType.LEFT_UP); + +// Update plane on mouse move +var moveHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas); +moveHandler.setInputAction(function(movement) { + if (Cesium.defined(selectedPlane)) { + var deltaX = movement.endPosition.x - movement.startPosition.x; + targetX += deltaX; + } +}, Cesium.ScreenSpaceEventType.MOUSE_MOVE); + var tileset; function loadTileset(url) { reset(); @@ -102,6 +111,26 @@ var boundingSphere = tileset.boundingSphere; viewer.camera.viewBoundingSphere(boundingSphere, new Cesium.HeadingPitchRange(0.5, -0.2, boundingSphere.radius * 4.0)); viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY); + + for (var i = 0; i < 1; ++i) { + var plane = clippingPlanes[i]; + var entity = viewer.entities.add({ + position : boundingSphere.center, + plane : { + dimensions : planeScale, + material : Cesium.Color.CYAN.withAlpha(0.1), + normal: plane.normal, + distance: new Cesium.CallbackProperty(function() { + plane.distance = Cesium.Math.lerp(plane.distance, targetX, 0.1); + return plane.distance; + }, false), + outline: true, + outlineColor: Cesium.Color.CYAN + } + }); + + planeEntities.push(entity); + } }).otherwise(function(error) { throw(error); }); @@ -111,38 +140,8 @@ clippingPlanes = tileset.clippingPlanes = defaultClippingPlanes.slice(); } -var model; -function loadModel(url) { - reset(); - - var position = Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706, 100.0); - var heading = Cesium.Math.toRadians(135); - var pitch = 0; - var roll = 0; - var hpr = new Cesium.HeadingPitchRoll(heading, pitch, roll); - var orientation = Cesium.Transforms.headingPitchRollQuaternion(position, hpr); - - var entity = viewer.entities.add({ - name : url, - position : position, - orientation : orientation, - model : { - uri : url, - scale: 8, - minimumPixelSize: 100.0 - } - }); - - viewer.trackedEntity = entity; - model = entity.model; - model.clippingPlanesEnabled = viewModel.clippingPlanesEnabled; - clippingPlanes = model.clippingPlanes = defaultClippingPlanes.slice(); -} - // Power Plant design model provided by Bentley Systems var bimUrl = 'https://beta.cesium.com/api/assets/1459?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIzNjUyM2I5Yy01YmRhLTQ0MjktOGI0Zi02MDdmYzBjMmY0MjYiLCJpZCI6NDQsImFzc2V0cyI6WzE0NTldLCJpYXQiOjE0OTkyNjQ3ODF9.SW_rwY-ic0TwQBeiweXNqFyywoxnnUBtcVjeCmDGef4'; -var pointCloudUrl = 'https://beta.cesium.com/api/assets/1460?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIyMzk2YzJiOS1jZGFmLTRlZmYtYmQ4MS00NTA3NjEwMzViZTkiLCJpZCI6NDQsImFzc2V0cyI6WzE0NjBdLCJpYXQiOjE0OTkyNjQ3NTV9.oWjvN52CRQ-dk3xtvD4e8ZnOHZhoWSpJLlw115mbQJM'; -var modelUrl = '../../SampleData/models/CesiumAir/Cesium_Air.glb'; loadTileset(bimUrl); @@ -151,45 +150,16 @@ Cesium.knockout.track(viewModel); Cesium.knockout.applyBindings(viewModel, toolbar); -Cesium.knockout.getObservable(viewModel, 'xOffset').subscribe(function(newValue) { - clippingPlanes[0].distance = parseFloat(newValue); -}); - -Cesium.knockout.getObservable(viewModel, 'yOffset').subscribe(function(newValue) { - clippingPlanes[1].distance = parseFloat(newValue); -}); - -Cesium.knockout.getObservable(viewModel, 'zOffset').subscribe(function(newValue) { - clippingPlanes[2].distance = parseFloat(newValue); -}); - Cesium.knockout.getObservable(viewModel, 'debugBoundingVolumesEnabled').subscribe(function(newValue) { if (Cesium.defined(tileset)) { tileset.debugShowBoundingVolume = newValue; } }); - -Cesium.knockout.getObservable(viewModel, 'clippingPlanesEnabled').subscribe(function(newValue) { - if (Cesium.defined(tileset)) { - tileset.clippingPlanesEnabled = newValue; - } -}); - -Cesium.knockout.getObservable(viewModel, 'exampleType').subscribe( - function(newValue) { - if (newValue === 'BIM') { - loadTileset(bimUrl); - } else if (newValue === 'Point Cloud') { - loadTileset(pointCloudUrl); - } else { - loadModel(modelUrl); - } - } -); - -function reset () { + +function reset() { viewer.entities.removeAll(); viewer.scene.primitives.removeAll(); + planeEntities = []; if (Cesium.defined(viewModel)) { viewModel.clippingPlanesEnabled = true; diff --git a/CHANGES.md b/CHANGES.md index 8dbc228e74bc..67d968efe6d8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,8 @@ Change Log * Added `clippingPlanes` property to `ModelGraphics` and `Cesium3DTileset`, which specifies an array of planes to clip the object. [TODO]() * Added `Plane.transformPlane` function to apply a transformation to a plane. [TODO]() +* Added `PlaneGeometry` and `PlaneOutlineGeometry` primitives +* Added `PlaneGraphics` and `plane` property to `Entity`. ### 1.38 - 2017-10-02 diff --git a/Source/Core/Plane.js b/Source/Core/Plane.js index 824ecb9eb7c0..cec682af4071 100644 --- a/Source/Core/Plane.js +++ b/Source/Core/Plane.js @@ -183,10 +183,6 @@ define([ return Plane.fromPointNormal(scratchPosition, scratchNormal, result); }; - Plane.prototype.origin = function (result) { - Cartesian3.multiplyByScalar(this.normal, this.distance, result); - }; - /** * A constant initialized to the XY plane passing through the origin, with normal in positive Z. * diff --git a/Source/Core/PlaneGeometry.js b/Source/Core/PlaneGeometry.js index 43c59793c366..9a494d60415b 100644 --- a/Source/Core/PlaneGeometry.js +++ b/Source/Core/PlaneGeometry.js @@ -1,6 +1,5 @@ define([ './BoundingSphere', - './Cartesian2', './Cartesian3', './Check', './ComponentDatatype', @@ -9,15 +8,10 @@ define([ './Geometry', './GeometryAttribute', './GeometryAttributes', - './Matrix4', - './Plane', './PrimitiveType', - './Quaternion', - './TranslationRotationScale', './VertexFormat' ], function( BoundingSphere, - Cartesian2, Cartesian3, Check, ComponentDatatype, @@ -26,11 +20,7 @@ define([ Geometry, GeometryAttribute, GeometryAttributes, - Matrix4, - Plane, PrimitiveType, - Quaternion, - TranslationRotationScale, VertexFormat) { 'use strict'; @@ -41,22 +31,15 @@ define([ * @constructor * * @param {Object} options Object with the following properties: - * @param {Cartesian3} options.minimum The minimum x, y, and z coordinates of the box. - * @param {Cartesian3} options.maximum The maximum x, y, and z coordinates of the box. * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed. * * @see Plane - * @see PlaneGeometry.fromDimensions * @see PlaneGeometry.createGeometry * @see Packable * - * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Box.html|Cesium Sandcastle Box Demo} - * * @example * var planeGeometry = new Cesium.PlaneGeometry({ - * vertexFormat : Cesium.VertexFormat.POSITION_ONLY, - * maximum : new Cesium.Cartesian3(250000.0, 250000.0, 250000.0), - * minimum : new Cesium.Cartesian3(-250000.0, -250000.0, -250000.0) + * vertexFormat : Cesium.VertexFormat.POSITION_ONLY * }); * var geometry = Cesium.PlaneGeometry.createGeometry(plane); */ @@ -389,13 +372,11 @@ define([ indices[11] = 2; } - var radius = 0.5; - return new Geometry({ attributes : attributes, indices : indices, primitiveType : PrimitiveType.TRIANGLES, - boundingSphere : new BoundingSphere(Cartesian3.ZERO, radius) + boundingSphere : new BoundingSphere(Cartesian3.ZERO, 0.5) }); }; diff --git a/Source/Core/PlaneOutlineGeometry.js b/Source/Core/PlaneOutlineGeometry.js new file mode 100644 index 000000000000..f3f0aaf55ff7 --- /dev/null +++ b/Source/Core/PlaneOutlineGeometry.js @@ -0,0 +1,143 @@ +define([ + './BoundingSphere', + './Cartesian3', + './Check', + './ComponentDatatype', + './defaultValue', + './defined', + './Geometry', + './GeometryAttribute', + './GeometryAttributes', + './PrimitiveType' +], function( + BoundingSphere, + Cartesian3, + Check, + ComponentDatatype, + defaultValue, + defined, + Geometry, + GeometryAttribute, + GeometryAttributes, + PrimitiveType) { + 'use strict'; + + /** + * A description of the outline of a cube centered at the origin. + * + * @alias PlaneOutlineGeometry + * @constructor + * + * @param {Object} options Object with the following properties: + * @param {Cartesian3} options.minimum The minimum x, y, and z coordinates of the box. + * @param {Cartesian3} options.maximum The maximum x, y, and z coordinates of the box. + * + * @see PlaneOutlineGeometry.fromDimensions + * @see PlaneOutlineGeometry.createGeometry + * @see Packable + * + * @example + * var box = new Cesium.PlaneOutlineGeometry({ + * maximum : new Cesium.Cartesian3(250000.0, 250000.0, 250000.0), + * minimum : new Cesium.Cartesian3(-250000.0, -250000.0, -250000.0) + * }); + * var geometry = Cesium.PlaneOutlineGeometry.createGeometry(box); + */ + function PlaneOutlineGeometry(options) { + this._workerName = 'createPlaneOutlineGeometry'; + } + /** + * The number of elements used to pack the object into an array. + * @type {Number} + */ + PlaneOutlineGeometry.packedLength = 0; + + /** + * Stores the provided instance into the provided array. + * + * @param {PlaneOutlineGeometry} value The value to pack. + * @param {Number[]} array The array to pack into. + * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements. + * + * @returns {Number[]} The array that was packed into + */ + PlaneOutlineGeometry.pack = function(value, array, startingIndex) { + //>>includeStart('debug', pragmas.debug); + Check.defined('array', array); + //>>includeEnd('debug'); + + return array; + }; + + /** + * Retrieves an instance from a packed array. + * + * @param {Number[]} array The packed array. + * @param {Number} [startingIndex=0] The starting index of the element to be unpacked. + * @param {PlaneOutlineGeometry} [result] The object into which to store the result. + * @returns {PlaneOutlineGeometry} The modified result parameter or a new PlaneOutlineGeometry instance if one was not provided. + */ + PlaneOutlineGeometry.unpack = function(array, startingIndex, result) { + //>>includeStart('debug', pragmas.debug); + Check.defined('array', array); + //>>includeEnd('debug'); + + if (!defined(result)) { + return new PlaneOutlineGeometry(); + } + + return result; + }; + + /** + * Computes the geometric representation of an outline of a box, including its vertices, indices, and a bounding sphere. + * + * @param {PlaneOutlineGeometry} boxGeometry A description of the box outline. + * @returns {Geometry|undefined} The computed vertices and indices. + */ + PlaneOutlineGeometry.createGeometry = function(boxGeometry) { + var min = new Cartesian3(-0.5, -0.5, 0.0); + var max = new Cartesian3( 0.5, 0.5, 0.0); + + var attributes = new GeometryAttributes(); + var indices = new Uint16Array(4 * 2); + var positions = new Float64Array(4 * 3); + + positions[0] = min.x; + positions[1] = min.y; + positions[2] = min.z; + positions[3] = max.x; + positions[4] = min.y; + positions[5] = min.z; + positions[6] = max.x; + positions[7] = max.y; + positions[8] = min.z; + positions[9] = min.x; + positions[10] = max.y; + positions[11] = min.z; + + attributes.position = new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : positions + }); + + indices[0] = 0; + indices[1] = 1; + indices[2] = 1; + indices[3] = 2; + indices[4] = 2; + indices[5] = 3; + indices[6] = 3; + indices[7] = 0; + + return new Geometry({ + attributes : attributes, + indices : indices, + primitiveType : PrimitiveType.LINES, + boundingSphere : new BoundingSphere(Cartesian3.ZERO, 0.5) + }); + }; + + return PlaneOutlineGeometry; +}); diff --git a/Source/DataSources/DataSourceDisplay.js b/Source/DataSources/DataSourceDisplay.js index 98d243669d77..977845700c24 100644 --- a/Source/DataSources/DataSourceDisplay.js +++ b/Source/DataSources/DataSourceDisplay.js @@ -20,6 +20,7 @@ define([ './LabelVisualizer', './ModelVisualizer', './PathVisualizer', + './PlaneGeometryUpdater', './PointVisualizer', './PolygonGeometryUpdater', './PolylineGeometryUpdater', @@ -48,6 +49,7 @@ define([ LabelVisualizer, ModelVisualizer, PathVisualizer, + PlaneGeometryUpdater, PointVisualizer, PolygonGeometryUpdater, PolylineGeometryUpdater, @@ -114,6 +116,7 @@ define([ new GeometryVisualizer(CorridorGeometryUpdater, scene, entities), new GeometryVisualizer(EllipseGeometryUpdater, scene, entities), new GeometryVisualizer(EllipsoidGeometryUpdater, scene, entities), + new GeometryVisualizer(PlaneGeometryUpdater, scene, entities), new GeometryVisualizer(PolygonGeometryUpdater, scene, entities), new GeometryVisualizer(PolylineGeometryUpdater, scene, entities), new GeometryVisualizer(PolylineVolumeGeometryUpdater, scene, entities), diff --git a/Source/DataSources/Entity.js b/Source/DataSources/Entity.js index 42c634f3b1b5..b487bdece44c 100644 --- a/Source/DataSources/Entity.js +++ b/Source/DataSources/Entity.js @@ -23,6 +23,7 @@ define([ './LabelGraphics', './ModelGraphics', './PathGraphics', + './PlaneGraphics', './PointGraphics', './PolygonGraphics', './PolylineGraphics', @@ -56,6 +57,7 @@ define([ LabelGraphics, ModelGraphics, PathGraphics, + PlaneGraphics, PointGraphics, PolygonGraphics, PolylineGraphics, @@ -109,6 +111,7 @@ define([ * @param {LabelGraphics} [options.label] A options.label to associate with this entity. * @param {ModelGraphics} [options.model] A model to associate with this entity. * @param {PathGraphics} [options.path] A path to associate with this entity. + * @param {PlaneGraphics} [options.plane] A plane to associate with this entity. * @param {PointGraphics} [options.point] A point to associate with this entity. * @param {PolygonGraphics} [options.polygon] A polygon to associate with this entity. * @param {PolylineGraphics} [options.polyline] A polyline to associate with this entity. @@ -134,7 +137,7 @@ define([ this._show = defaultValue(options.show, true); this._parent = undefined; this._propertyNames = ['billboard', 'box', 'corridor', 'cylinder', 'description', 'ellipse', // - 'ellipsoid', 'label', 'model', 'orientation', 'path', 'point', 'polygon', // + 'ellipsoid', 'label', 'model', 'orientation', 'path', 'plane', 'point', 'polygon', // 'polyline', 'polylineVolume', 'position', 'properties', 'rectangle', 'viewFrom', 'wall']; this._billboard = undefined; @@ -159,6 +162,8 @@ define([ this._orientationSubscription = undefined; this._path = undefined; this._pathSubscription = undefined; + this._plane = undefined; + this._planeSubscription = undefined; this._point = undefined; this._pointSubscription = undefined; this._polygon = undefined; @@ -398,6 +403,12 @@ define([ * @type {PathGraphics} */ path : createPropertyTypeDescriptor('path', PathGraphics), + /** + * Gets or sets the plane. + * @memberof Entity.prototype + * @type {PlaneGraphics} + */ + plane : createPropertyTypeDescriptor('plane', PlaneGraphics), /** * Gets or sets the point graphic. * @memberof Entity.prototype diff --git a/Source/DataSources/PlaneGeometryUpdater.js b/Source/DataSources/PlaneGeometryUpdater.js new file mode 100644 index 000000000000..6ee862b5e7aa --- /dev/null +++ b/Source/DataSources/PlaneGeometryUpdater.js @@ -0,0 +1,723 @@ +define([ + '../Core/PlaneGeometry', + '../Core/PlaneOutlineGeometry', + '../Core/Cartesian2', + '../Core/Cartesian3', + '../Core/Color', + '../Core/ColorGeometryInstanceAttribute', + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/destroyObject', + '../Core/DeveloperError', + '../Core/DistanceDisplayCondition', + '../Core/DistanceDisplayConditionGeometryInstanceAttribute', + '../Core/Event', + '../Core/GeometryInstance', + '../Core/Iso8601', + '../Core/Matrix4', + '../Core/ShowGeometryInstanceAttribute', + '../Core/Quaternion', + '../Scene/MaterialAppearance', + '../Scene/PerInstanceColorAppearance', + '../Scene/Primitive', + '../Scene/ShadowMode', + './ColorMaterialProperty', + './ConstantProperty', + './dynamicGeometryGetBoundingSphere', + './MaterialProperty', + './Property' +], function( + PlaneGeometry, + PlaneOutlineGeometry, + Cartesian2, + Cartesian3, + Color, + ColorGeometryInstanceAttribute, + defaultValue, + defined, + defineProperties, + destroyObject, + DeveloperError, + DistanceDisplayCondition, + DistanceDisplayConditionGeometryInstanceAttribute, + Event, + GeometryInstance, + Iso8601, + Matrix4, + ShowGeometryInstanceAttribute, + Quaternion, + MaterialAppearance, + PerInstanceColorAppearance, + Primitive, + ShadowMode, + ColorMaterialProperty, + ConstantProperty, + dynamicGeometryGetBoundingSphere, + MaterialProperty, + Property) { + 'use strict'; + + var defaultMaterial = new ColorMaterialProperty(Color.WHITE); + var defaultShow = new ConstantProperty(true); + var defaultFill = new ConstantProperty(true); + var defaultOutline = new ConstantProperty(false); + var defaultOutlineColor = new ConstantProperty(Color.BLACK); + var defaultShadows = new ConstantProperty(ShadowMode.DISABLED); + var defaultDistanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); + var scratchColor = new Color(); + + function GeometryOptions(entity) { + this.id = entity; + this.vertexFormat = undefined; + this.normal = undefined; + this.distance = 0.0; + this.dimensions = undefined; + } + + /** + * A {@link GeometryUpdater} for planes. + * Clients do not normally create this class directly, but instead rely on {@link DataSourceDisplay}. + * @alias PlaneGeometryUpdater + * @constructor + * + * @param {Entity} entity The entity containing the geometry to be visualized. + * @param {Scene} scene The scene where visualization is taking place. + */ + function PlaneGeometryUpdater(entity, scene) { + //>>includeStart('debug', pragmas.debug); + if (!defined(entity)) { + throw new DeveloperError('entity is required'); + } + if (!defined(scene)) { + throw new DeveloperError('scene is required'); + } + //>>includeEnd('debug'); + + this._entity = entity; + this._scene = scene; + this._entitySubscription = entity.definitionChanged.addEventListener(PlaneGeometryUpdater.prototype._onEntityPropertyChanged, this); + this._fillEnabled = false; + this._dynamic = false; + this._outlineEnabled = false; + this._geometryChanged = new Event(); + this._showProperty = undefined; + this._materialProperty = undefined; + this._hasConstantOutline = true; + this._showOutlineProperty = undefined; + this._outlineColorProperty = undefined; + this._outlineWidth = 1.0; + this._shadowsProperty = undefined; + this._distanceDisplayConditionProperty = undefined; + this._options = new GeometryOptions(entity); + this._onEntityPropertyChanged(entity, 'plane', entity.plane, undefined); + } + + defineProperties(PlaneGeometryUpdater, { + /** + * Gets the type of Appearance to use for simple color-based geometry. + * @memberof PlaneGeometryUpdater + * @type {Appearance} + */ + perInstanceColorAppearanceType : { + value : PerInstanceColorAppearance + }, + /** + * Gets the type of Appearance to use for material-based geometry. + * @memberof PlaneGeometryUpdater + * @type {Appearance} + */ + materialAppearanceType : { + value : MaterialAppearance + } + }); + + defineProperties(PlaneGeometryUpdater.prototype, { + /** + * Gets the entity associated with this geometry. + * @memberof PlaneGeometryUpdater.prototype + * + * @type {Entity} + * @readonly + */ + entity : { + get : function() { + return this._entity; + } + }, + /** + * Gets a value indicating if the geometry has a fill component. + * @memberof PlaneGeometryUpdater.prototype + * + * @type {Boolean} + * @readonly + */ + fillEnabled : { + get : function() { + return this._fillEnabled; + } + }, + /** + * Gets a value indicating if fill visibility varies with simulation time. + * @memberof PlaneGeometryUpdater.prototype + * + * @type {Boolean} + * @readonly + */ + hasConstantFill : { + get : function() { + return !this._fillEnabled || + (!defined(this._entity.availability) && + Property.isConstant(this._showProperty) && + Property.isConstant(this._fillProperty)); + } + }, + /** + * Gets the material property used to fill the geometry. + * @memberof PlaneGeometryUpdater.prototype + * + * @type {MaterialProperty} + * @readonly + */ + fillMaterialProperty : { + get : function() { + return this._materialProperty; + } + }, + /** + * Gets a value indicating if the geometry has an outline component. + * @memberof PlaneGeometryUpdater.prototype + * + * @type {Boolean} + * @readonly + */ + outlineEnabled : { + get : function() { + return this._outlineEnabled; + } + }, + /** + * Gets a value indicating if the geometry has an outline component. + * @memberof PlaneGeometryUpdater.prototype + * + * @type {Boolean} + * @readonly + */ + hasConstantOutline : { + get : function() { + return !this._outlineEnabled || + (!defined(this._entity.availability) && + Property.isConstant(this._showProperty) && + Property.isConstant(this._showOutlineProperty)); + } + }, + /** + * Gets the {@link Color} property for the geometry outline. + * @memberof PlaneGeometryUpdater.prototype + * + * @type {Property} + * @readonly + */ + outlineColorProperty : { + get : function() { + return this._outlineColorProperty; + } + }, + /** + * Gets the constant with of the geometry outline, in pixels. + * This value is only valid if isDynamic is false. + * @memberof PlaneGeometryUpdater.prototype + * + * @type {Number} + * @readonly + */ + outlineWidth : { + get : function() { + return this._outlineWidth; + } + }, + /** + * Gets the property specifying whether the geometry + * casts or receives shadows from each light source. + * @memberof PlaneGeometryUpdater.prototype + * + * @type {Property} + * @readonly + */ + shadowsProperty : { + get : function() { + return this._shadowsProperty; + } + }, + /** + * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this geometry will be displayed. + * @memberof PlaneGeometryUpdater.prototype + * + * @type {Property} + * @readonly + */ + distanceDisplayConditionProperty : { + get : function() { + return this._distanceDisplayConditionProperty; + } + }, + /** + * Gets a value indicating if the geometry is time-varying. + * If true, all visualization is delegated to the {@link DynamicGeometryUpdater} + * returned by GeometryUpdater#createDynamicUpdater. + * @memberof PlaneGeometryUpdater.prototype + * + * @type {Boolean} + * @readonly + */ + isDynamic : { + get : function() { + return this._dynamic; + } + }, + /** + * Gets a value indicating if the geometry is closed. + * This property is only valid for static geometry. + * @memberof PlaneGeometryUpdater.prototype + * + * @type {Boolean} + * @readonly + */ + isClosed : { + value : true + }, + /** + * Gets an event that is raised whenever the public properties + * of this updater change. + * @memberof PlaneGeometryUpdater.prototype + * + * @type {Boolean} + * @readonly + */ + geometryChanged : { + get : function() { + return this._geometryChanged; + } + } + }); + + /** + * Checks if the geometry is outlined at the provided time. + * + * @param {JulianDate} time The time for which to retrieve visibility. + * @returns {Boolean} true if geometry is outlined at the provided time, false otherwise. + */ + PlaneGeometryUpdater.prototype.isOutlineVisible = function(time) { + var entity = this._entity; + return this._outlineEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time); + }; + + /** + * Checks if the geometry is filled at the provided time. + * + * @param {JulianDate} time The time for which to retrieve visibility. + * @returns {Boolean} true if geometry is filled at the provided time, false otherwise. + */ + PlaneGeometryUpdater.prototype.isFilled = function(time) { + var entity = this._entity; + return this._fillEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._fillProperty.getValue(time); + }; + + /** + * Creates the geometry instance which represents the fill of the geometry. + * + * @param {JulianDate} time The time to use when retrieving initial attribute values. + * @returns {GeometryInstance} The geometry instance representing the filled portion of the geometry. + * + * @exception {DeveloperError} This instance does not represent a filled geometry. + */ + PlaneGeometryUpdater.prototype.createFillGeometryInstance = function(time) { + //>>includeStart('debug', pragmas.debug); + if (!defined(time)) { + throw new DeveloperError('time is required.'); + } + + if (!this._fillEnabled) { + throw new DeveloperError('This instance does not represent a filled geometry.'); + } + //>>includeEnd('debug'); + + var entity = this._entity; + var isAvailable = entity.isAvailable(time); + + var attributes; + + var color; + var show = new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._fillProperty.getValue(time)); + var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time); + var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition); + if (this._materialProperty instanceof ColorMaterialProperty) { + var currentColor = Color.WHITE; + if (defined(this._materialProperty.color) && (this._materialProperty.color.isConstant || isAvailable)) { + currentColor = this._materialProperty.color.getValue(time); + } + color = ColorGeometryInstanceAttribute.fromColor(currentColor); + attributes = { + show : show, + distanceDisplayCondition : distanceDisplayConditionAttribute, + color : color + }; + } else { + attributes = { + show : show, + distanceDisplayCondition : distanceDisplayConditionAttribute + }; + } + + var options = this._options; + var normal = options.normal; + var distance = options.distance; + var dimensions = options.dimensions; + + var modelMatrix = entity.computeModelMatrix(Iso8601.MINIMUM_VALUE); + //modelMatrix = createPrimitiveMatrix(normal, distance, dimensions, modelMatrix, modelMatrix); + + return new GeometryInstance({ + id : entity, + geometry : new PlaneGeometry(), + modelMatrix : modelMatrix, + attributes : attributes + }); + }; + + /** + * Creates the geometry instance which represents the outline of the geometry. + * + * @param {JulianDate} time The time to use when retrieving initial attribute values. + * @returns {GeometryInstance} The geometry instance representing the outline portion of the geometry. + * + * @exception {DeveloperError} This instance does not represent an outlined geometry. + */ + PlaneGeometryUpdater.prototype.createOutlineGeometryInstance = function(time) { + //>>includeStart('debug', pragmas.debug); + if (!defined(time)) { + throw new DeveloperError('time is required.'); + } + + if (!this._outlineEnabled) { + throw new DeveloperError('This instance does not represent an outlined geometry.'); + } + //>>includeEnd('debug'); + + var entity = this._entity; + var isAvailable = entity.isAvailable(time); + var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK); + var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time); + + var options = this._options; + var normal = options.normal; + var distance = options.distance; + var dimensions = options.dimensions; + + var modelMatrix = entity.computeModelMatrix(Iso8601.MINIMUM_VALUE); + //modelMatrix = createPrimitiveMatrix(normal, distance, dimensions, modelMatrix, modelMatrix); + + return new GeometryInstance({ + id : entity, + geometry : new PlaneOutlineGeometry(), + modelMatrix : modelMatrix, + attributes : { + show : new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time)), + color : ColorGeometryInstanceAttribute.fromColor(outlineColor), + distanceDisplayCondition : DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition) + } + }); + }; + + /** + * Returns true if this object was destroyed; otherwise, false. + * + * @returns {Boolean} True if this object was destroyed; otherwise, false. + */ + PlaneGeometryUpdater.prototype.isDestroyed = function() { + return false; + }; + + /** + * Destroys and resources used by the object. Once an object is destroyed, it should not be used. + * + * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. + */ + PlaneGeometryUpdater.prototype.destroy = function() { + this._entitySubscription(); + destroyObject(this); + }; + + PlaneGeometryUpdater.prototype._onEntityPropertyChanged = function(entity, propertyName, newValue, oldValue) { + if (!(propertyName === 'availability' || propertyName === 'position' || propertyName === 'orientation' || propertyName === 'plane')) { + return; + } + + var plane = this._entity.plane; + + + + if (!defined(plane)) { + if (this._fillEnabled || this._outlineEnabled) { + this._fillEnabled = false; + this._outlineEnabled = false; + this._geometryChanged.raiseEvent(this); + } + return; + } + + var fillProperty = plane.fill; + var fillEnabled = defined(fillProperty) && fillProperty.isConstant ? fillProperty.getValue(Iso8601.MINIMUM_VALUE) : true; + + var outlineProperty = plane.outline; + var outlineEnabled = defined(outlineProperty); + if (outlineEnabled && outlineProperty.isConstant) { + outlineEnabled = outlineProperty.getValue(Iso8601.MINIMUM_VALUE); + } + + if (!fillEnabled && !outlineEnabled) { + if (this._fillEnabled || this._outlineEnabled) { + this._fillEnabled = false; + this._outlineEnabled = false; + this._geometryChanged.raiseEvent(this); + } + return; + } + + var normal = plane.normal; + var distance = plane.distance; + var dimensions = plane.dimensions; + var position = entity.position; + + var show = plane.show; + if (!defined(normal) || !defined(distance) || !defined(position) || (defined(show) && show.isConstant && !show.getValue(Iso8601.MINIMUM_VALUE))) { + if (this._fillEnabled || this._outlineEnabled) { + this._fillEnabled = false; + this._outlineEnabled = false; + this._geometryChanged.raiseEvent(this); + } + return; + } + + var material = defaultValue(plane.material, defaultMaterial); + var isColorMaterial = material instanceof ColorMaterialProperty; + this._materialProperty = material; + this._fillProperty = defaultValue(fillProperty, defaultFill); + this._showProperty = defaultValue(show, defaultShow); + this._showOutlineProperty = defaultValue(plane.outline, defaultOutline); + this._outlineColorProperty = outlineEnabled ? defaultValue(plane.outlineColor, defaultOutlineColor) : undefined; + this._shadowsProperty = defaultValue(plane.shadows, defaultShadows); + this._distanceDisplayConditionProperty = defaultValue(plane.distanceDisplayCondition, defaultDistanceDisplayCondition); + + var outlineWidth = plane.outlineWidth; + + this._fillEnabled = fillEnabled; + this._outlineEnabled = outlineEnabled; + + if (!position.isConstant || // + !Property.isConstant(entity.orientation) || // + !normal.isConstant || // + !distance.isConstant || // + !dimensions.isConstant || // + !Property.isConstant(outlineWidth)) { + if (!this._dynamic) { + this._dynamic = true; + this._geometryChanged.raiseEvent(this); + } + } else { + var options = this._options; + options.vertexFormat = isColorMaterial ? PerInstanceColorAppearance.VERTEX_FORMAT : MaterialAppearance.MaterialSupport.TEXTURED.vertexFormat; + options.normal = normal.getValue(Iso8601.MINIMUM_VALUE, options.normal); + options.distance = distance.getValue(Iso8601.MINIMUM_VALUE, options.distance); + options.dimensions = dimensions.getValue(Iso8601.MINIMUM_VALUE, options.dimensions); + this._outlineWidth = defined(outlineWidth) ? outlineWidth.getValue(Iso8601.MINIMUM_VALUE) : 1.0; + this._dynamic = false; + this._geometryChanged.raiseEvent(this); + } + }; + + /** + * Creates the dynamic updater to be used when GeometryUpdater#isDynamic is true. + * + * @param {PrimitiveCollection} primitives The primitive collection to use. + * @returns {DynamicGeometryUpdater} The dynamic updater used to update the geometry each frame. + * + * @exception {DeveloperError} This instance does not represent dynamic geometry. + */ + PlaneGeometryUpdater.prototype.createDynamicUpdater = function(primitives) { + //>>includeStart('debug', pragmas.debug); + if (!this._dynamic) { + throw new DeveloperError('This instance does not represent dynamic geometry.'); + } + + if (!defined(primitives)) { + throw new DeveloperError('primitives is required.'); + } + //>>includeEnd('debug'); + + return new DynamicGeometryUpdater(primitives, this); + }; + + /** + * @private + */ + function DynamicGeometryUpdater(primitives, geometryUpdater) { + this._primitives = primitives; + this._primitive = undefined; + this._outlinePrimitive = undefined; + this._geometryUpdater = geometryUpdater; + this._options = new GeometryOptions(geometryUpdater._entity); + } + DynamicGeometryUpdater.prototype.update = function(time) { + //>>includeStart('debug', pragmas.debug); + if (!defined(time)) { + throw new DeveloperError('time is required.'); + } + //>>includeEnd('debug'); + + var primitives = this._primitives; + primitives.removeAndDestroy(this._primitive); + primitives.removeAndDestroy(this._outlinePrimitive); + this._primitive = undefined; + this._outlinePrimitive = undefined; + + var geometryUpdater = this._geometryUpdater; + var entity = geometryUpdater._entity; + var plane = entity.plane; + if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(plane.show, time, true)) { + return; + } + + var options = this._options; + var modelMatrix = entity.computeModelMatrix(time); + var normal = Property.getValueOrDefault(plane.normal, time, options.normal); + var distance = Property.getValueOrUndefined(plane.distance, time, options.distance); + var dimensions = Property.getValueOrUndefined(plane.dimensions, time, options.dimensions); + if (!defined(modelMatrix) || !defined(normal) || !defined(distance) || !defined(dimensions)) { + return; + } + + options.normal = normal; + options.distance = distance; + options.dimensions = dimensions; + + modelMatrix = createPrimitiveMatrix(normal, distance, dimensions, modelMatrix, modelMatrix); + + var shadows = this._geometryUpdater.shadowsProperty.getValue(time); + + var distanceDisplayConditionProperty = this._geometryUpdater.distanceDisplayConditionProperty; + var distanceDisplayCondition = distanceDisplayConditionProperty.getValue(time); + var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition); + + if (Property.getValueOrDefault(plane.fill, time, true)) { + var material = MaterialProperty.getValue(time, geometryUpdater.fillMaterialProperty, this._material); + this._material = material; + + var appearance = new MaterialAppearance({ + material : material, + translucent : material.isTranslucent(), + closed : true + }); + options.vertexFormat = appearance.vertexFormat; + + this._primitive = primitives.add(new Primitive({ + geometryInstances : new GeometryInstance({ + id : entity, + geometry : new PlaneGeometry(), + modelMatrix : modelMatrix, + attributes : { + distanceDisplayCondition : distanceDisplayConditionAttribute + } + }), + appearance : appearance, + asynchronous : false, + shadows : shadows + })); + } + + if (Property.getValueOrDefault(plane.outline, time, false)) { + options.vertexFormat = PerInstanceColorAppearance.VERTEX_FORMAT; + + var outlineColor = Property.getValueOrClonedDefault(plane.outlineColor, time, Color.BLACK, scratchColor); + var outlineWidth = Property.getValueOrDefault(plane.outlineWidth, time, 1.0); + var translucent = outlineColor.alpha !== 1.0; + + this._outlinePrimitive = primitives.add(new Primitive({ + geometryInstances : new GeometryInstance({ + id : entity, + geometry : new PlaneOutlineGeometry(), + modelMatrix : modelMatrix, + attributes : { + color : ColorGeometryInstanceAttribute.fromColor(outlineColor), + distanceDisplayCondition : distanceDisplayConditionAttribute + } + }), + appearance : new PerInstanceColorAppearance({ + flat : true, + translucent : translucent, + renderState : { + lineWidth : geometryUpdater._scene.clampLineWidth(outlineWidth) + } + }), + asynchronous : false, + shadows : shadows + })); + } + }; + + var scratchTranslation = new Cartesian3(); + var scratchNormal = new Cartesian3(); + var scratchScale = new Cartesian3(); + var defaultDimensions = new Cartesian2(1.0, 1.0); + function createPrimitiveMatrix (normal, distance, dimensions, modelMatrix, result) { + if (!defined(normal)) { + normal = Cartesian3.UNIT_X; + } + if (!defined(distance)) { + distance = 0.0; + } + if (!defined(dimensions)) { + dimensions = defaultDimensions; + } + + var translation = Cartesian3.multiplyByScalar(normal, distance, scratchTranslation); + translation = Matrix4.multiplyByPoint(modelMatrix, translation, translation); + + var transformedNormal = Matrix4.multiplyByPointAsVector(modelMatrix, normal, scratchNormal); + Cartesian3.normalize(transformedNormal, transformedNormal); + var rotation = getRotationMatrix(transformedNormal, Cartesian3.UNIT_Z); + + var scale = Cartesian2.clone(dimensions, scratchScale); + scale.z = 1.0; + + return Matrix4.fromTranslationQuaternionRotationScale(translation, rotation, scale, result); + } + + // get a rotation according to a normal + function getRotationMatrix(direction, up) { + var angle = Cartesian3.angleBetween(direction, up); + if (angle === 0.0) { + return Quaternion.IDENTITY; + } + + var axis = Cartesian3.cross(up, direction, new Cartesian3()); + return Quaternion.fromAxisAngle(axis, angle); + } + + DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) { + return dynamicGeometryGetBoundingSphere(entity, this._primitive, this._outlinePrimitive, result); + }; + + DynamicGeometryUpdater.prototype.isDestroyed = function() { + return false; + }; + + DynamicGeometryUpdater.prototype.destroy = function() { + var primitives = this._primitives; + primitives.removeAndDestroy(this._primitive); + primitives.removeAndDestroy(this._outlinePrimitive); + destroyObject(this); + }; + + return PlaneGeometryUpdater; +}); diff --git a/Source/DataSources/PlaneGraphics.js b/Source/DataSources/PlaneGraphics.js new file mode 100644 index 000000000000..3aac0ea91e95 --- /dev/null +++ b/Source/DataSources/PlaneGraphics.js @@ -0,0 +1,199 @@ +define([ + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/DeveloperError', + '../Core/Event', + './createMaterialPropertyDescriptor', + './createPropertyDescriptor' +], function( + defaultValue, + defined, + defineProperties, + DeveloperError, + Event, + createMaterialPropertyDescriptor, + createPropertyDescriptor) { + 'use strict'; + + /** + * Describes a plane. The center position and orientation are determined by the containing {@link Entity}. + * + * @alias PlaneGraphics + * @constructor + * + * @param {Object} [options] Object with the following properties: + * @param {Property} [options.dimensions] A {@link Cartesian3} Property specifying the length, width, and height of the box. + * @param {Property} [options.show=true] A boolean Property specifying the visibility of the box. + * @param {Property} [options.fill=true] A boolean Property specifying whether the box is filled with the provided material. + * @param {MaterialProperty} [options.material=Color.WHITE] A Property specifying the material used to fill the box. + * @param {Property} [options.outline=false] A boolean Property specifying whether the box is outlined. + * @param {Property} [options.outlineColor=Color.BLACK] A Property specifying the {@link Color} of the outline. + * @param {Property} [options.outlineWidth=1.0] A numeric Property specifying the width of the outline. + * @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the box casts or receives shadows from each light source. + * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this box will be displayed. + * + * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Box.html|Cesium Sandcastle Box Demo} + */ + function PlaneGraphics(options) { + this._normal = undefined; + this._normalSubscription = undefined; + this._distance = undefined; + this._distanceSubscription = undefined; + this._dimensions = undefined; + this._dimensionsSubscription = undefined; + this._show = undefined; + this._showSubscription = undefined; + this._fill = undefined; + this._fillSubscription = undefined; + this._material = undefined; + this._materialSubscription = undefined; + this._outline = undefined; + this._outlineSubscription = undefined; + this._outlineColor = undefined; + this._outlineColorSubscription = undefined; + this._outlineWidth = undefined; + this._outlineWidthSubscription = undefined; + this._shadows = undefined; + this._shadowsSubscription = undefined; + this._distanceDisplayCondition = undefined; + this._distanceDisplayConditionSubscription = undefined; + this._definitionChanged = new Event(); + + this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); + } + + defineProperties(PlaneGraphics.prototype, { + /** + * Gets the event that is raised whenever a property or sub-property is changed or modified. + * @memberof PlaneGraphics.prototype + * @type {Event} + * @readonly + */ + definitionChanged : { + get : function() { + return this._definitionChanged; + } + }, + + /** + * Gets or sets the boolean Property specifying the visibility of the box. + * @memberof PlaneGraphics.prototype + * @type {Property} + * @default true + */ + show : createPropertyDescriptor('show'), + + normal : createPropertyDescriptor('normal'), + distance : createPropertyDescriptor('distance'), + dimensions : createPropertyDescriptor('dimensions'), + + /** + * Gets or sets the material used to fill the box. + * @memberof PlaneGraphics.prototype + * @type {MaterialProperty} + * @default Color.WHITE + */ + material : createMaterialPropertyDescriptor('material'), + + /** + * Gets or sets the boolean Property specifying whether the box is filled with the provided material. + * @memberof PlaneGraphics.prototype + * @type {Property} + * @default true + */ + fill : createPropertyDescriptor('fill'), + + /** + * Gets or sets the Property specifying whether the box is outlined. + * @memberof PlaneGraphics.prototype + * @type {Property} + * @default false + */ + outline : createPropertyDescriptor('outline'), + + /** + * Gets or sets the Property specifying the {@link Color} of the outline. + * @memberof PlaneGraphics.prototype + * @type {Property} + * @default Color.BLACK + */ + outlineColor : createPropertyDescriptor('outlineColor'), + + /** + * Gets or sets the numeric Property specifying the width of the outline. + * @memberof PlaneGraphics.prototype + * @type {Property} + * @default 1.0 + */ + outlineWidth : createPropertyDescriptor('outlineWidth'), + + /** + * Get or sets the enum Property specifying whether the box + * casts or receives shadows from each light source. + * @memberof PlaneGraphics.prototype + * @type {Property} + * @default ShadowMode.DISABLED + */ + shadows : createPropertyDescriptor('shadows'), + + /** + * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this box will be displayed. + * @memberof PlaneGraphics.prototype + * @type {Property} + */ + distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition') + }); + + /** + * Duplicates this instance. + * + * @param {PlaneGraphics} [result] The object onto which to store the result. + * @returns {PlaneGraphics} The modified result parameter or a new instance if one was not provided. + */ + PlaneGraphics.prototype.clone = function(result) { + if (!defined(result)) { + return new PlaneGraphics(this); + } + result.normal = this.normal; + result.distance = this.distance; + result.dimensions = this.dimensions; + result.show = this.show; + result.material = this.material; + result.fill = this.fill; + result.outline = this.outline; + result.outlineColor = this.outlineColor; + result.outlineWidth = this.outlineWidth; + result.shadows = this.shadows; + result.distanceDisplayCondition = this.distanceDisplayCondition; + return result; + }; + + /** + * Assigns each unassigned property on this object to the value + * of the same property on the provided source object. + * + * @param {PlaneGraphics} source The object to be merged into this object. + */ + PlaneGraphics.prototype.merge = function(source) { + //>>includeStart('debug', pragmas.debug); + if (!defined(source)) { + throw new DeveloperError('source is required.'); + } + //>>includeEnd('debug'); + + this.normal = defaultValue(this.normal, source.normal); + this.distance = defaultValue(this.distance, source.distance); + this.dimensions = defaultValue(this.dimensions, source.dimensions); + this.show = defaultValue(this.show, source.show); + this.material = defaultValue(this.material, source.material); + this.fill = defaultValue(this.fill, source.fill); + this.outline = defaultValue(this.outline, source.outline); + this.outlineColor = defaultValue(this.outlineColor, source.outlineColor); + this.outlineWidth = defaultValue(this.outlineWidth, source.outlineWidth); + this.shadows = defaultValue(this.shadows, source.shadows); + this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition); + }; + + return PlaneGraphics; +}); diff --git a/Source/Workers/createPlaneOutlineGeometry.js b/Source/Workers/createPlaneOutlineGeometry.js new file mode 100644 index 000000000000..7f91ee8b6e9e --- /dev/null +++ b/Source/Workers/createPlaneOutlineGeometry.js @@ -0,0 +1,15 @@ +define([ + '../Core/PlaneOutlineGeometry', + '../Core/defined' +], function( + PlaneOutlineGeometry, + defined) { + 'use strict'; + + return function(planeGeometry, offset) { + if (defined(offset)) { + planeGeometry = PlaneOutlineGeometry.unpack(planeGeometry, offset); + } + return PlaneOutlineGeometry.createGeometry(planeGeometry); + }; +}); From 4dfea3a406c1631f9a8a62d5acbbb15a9682748e Mon Sep 17 00:00:00 2001 From: ggetz Date: Thu, 9 Nov 2017 17:49:44 -0500 Subject: [PATCH 03/37] Cleanup --- Apps/Sandcastle/gallery/Clipping Planes.html | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Apps/Sandcastle/gallery/Clipping Planes.html b/Apps/Sandcastle/gallery/Clipping Planes.html index 8f993364ea75..5b407826b35c 100644 --- a/Apps/Sandcastle/gallery/Clipping Planes.html +++ b/Apps/Sandcastle/gallery/Clipping Planes.html @@ -70,7 +70,9 @@ var downHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas); downHandler.setInputAction(function(movement) { var pickedObject = scene.pick(movement.position); - if (Cesium.defined(pickedObject) && Cesium.defined(pickedObject.id.plane)) { + if (Cesium.defined(pickedObject) && + Cesium.defined(pickedObject.id) && + Cesium.defined(pickedObject.id.plane)) { selectedPlane = pickedObject.id.plane; selectedPlane.material = Cesium.Color.WHITE.withAlpha(0.05); selectedPlane.outlineColor = Cesium.Color.WHITE; @@ -155,7 +157,7 @@ tileset.debugShowBoundingVolume = newValue; } }); - + function reset() { viewer.entities.removeAll(); viewer.scene.primitives.removeAll(); @@ -163,9 +165,6 @@ if (Cesium.defined(viewModel)) { viewModel.clippingPlanesEnabled = true; - viewModel.xOffset = 0.0; - viewModel.yOffset = -100.0; - viewModel.zOffset = -100.0; } } From ee8b97aabb791eac300e5cb6aa6f598c27e6867e Mon Sep 17 00:00:00 2001 From: ggetz Date: Fri, 10 Nov 2017 13:19:48 -0500 Subject: [PATCH 04/37] Update sandcastle demo, colors and scenario --- Apps/Sandcastle/gallery/Clipping Planes.html | 11 +++++------ Source/Scene/Model.js | 7 ++++++- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Apps/Sandcastle/gallery/Clipping Planes.html b/Apps/Sandcastle/gallery/Clipping Planes.html index 5b407826b35c..cbd1716033a4 100644 --- a/Apps/Sandcastle/gallery/Clipping Planes.html +++ b/Apps/Sandcastle/gallery/Clipping Planes.html @@ -53,8 +53,6 @@ var clippingPlanes; var defaultClippingPlanes = [ - new Cesium.Plane(new Cesium.Cartesian3(1.0, 0.0, 0.0), 0.0), - new Cesium.Plane(new Cesium.Cartesian3(0.0, 1.0, 0.0), -100.0), new Cesium.Plane(new Cesium.Cartesian3(0.0, 0.0,-1.0), -100.0) ]; @@ -62,7 +60,7 @@ debugBoundingVolumesEnabled: false }; -var targetX = 0.0; +var targetY = 0.0; var planeEntities = []; var selectedPlane; @@ -96,8 +94,9 @@ var moveHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas); moveHandler.setInputAction(function(movement) { if (Cesium.defined(selectedPlane)) { - var deltaX = movement.endPosition.x - movement.startPosition.x; - targetX += deltaX; + var deltaY = movement.endPosition.y - movement.startPosition.y; + var screenScale = scene.camera.getPixelSize(tileset.boundingSphere, scene.drawingBufferWidth, scene.drawingBufferHeight); + targetY += deltaY * screenScale; } }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); @@ -123,7 +122,7 @@ material : Cesium.Color.CYAN.withAlpha(0.1), normal: plane.normal, distance: new Cesium.CallbackProperty(function() { - plane.distance = Cesium.Math.lerp(plane.distance, targetX, 0.1); + plane.distance = Cesium.Math.lerp(plane.distance, targetY, 0.1); return plane.distance; }, false), outline: true, diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js index dd6a2f99811f..bf8687cc4f21 100644 --- a/Source/Scene/Model.js +++ b/Source/Scene/Model.js @@ -3332,10 +3332,15 @@ define([ } var scratchPlane = new Plane(Cartesian3.UNIT_X, 0.0); - function createClippingPlanesFunction(model, context) { + function createClippingPlanesFunction(model) { return function() { var planes = model.clippingPlanes; var packedPlanes = model._packedClippingPlanes; + + if (!model.clippingPlanesEnabled) { + return packedPlanes; + } + var length = packedPlanes.length; for (var i = 0; i < length; ++i) { var plane = planes[i]; From 33eb86a61aec28f0e373c9e11e44c4eb585e7330 Mon Sep 17 00:00:00 2001 From: ggetz Date: Sun, 12 Nov 2017 15:32:46 -0500 Subject: [PATCH 05/37] Clipping planes on terrain --- Source/Scene/Globe.js | 13 +++++++ Source/Scene/GlobeSurfaceShaderSet.js | 9 ++++- Source/Scene/GlobeSurfaceTileProvider.js | 49 +++++++++++++++++++++++- Source/Shaders/GlobeFS.glsl | 9 +++++ 4 files changed, 76 insertions(+), 4 deletions(-) diff --git a/Source/Scene/Globe.js b/Source/Scene/Globe.js index 8a351098c7b9..2c853c9e70f2 100644 --- a/Source/Scene/Globe.js +++ b/Source/Scene/Globe.js @@ -234,6 +234,19 @@ define([ this._surface.tileProvider.baseColor = value; } }, + /** + * A property specifying an array of up to 6 {@link Plane} used to selectively disable rendering on the outside of each plane. + * + * @type {Plane[]} + */ + terrainClippingPlanes : { + get : function() { + return this._surface.tileProvider.clippingPlanes; + }, + set : function(value) { + this._surface.tileProvider.clippingPlanes = value; + } + }, /** * The terrain provider providing surface geometry for this globe. * @type {TerrainProvider} diff --git a/Source/Scene/GlobeSurfaceShaderSet.js b/Source/Scene/GlobeSurfaceShaderSet.js index 2442df1a5efb..a6f7068a8b61 100644 --- a/Source/Scene/GlobeSurfaceShaderSet.js +++ b/Source/Scene/GlobeSurfaceShaderSet.js @@ -61,7 +61,7 @@ define([ return useWebMercatorProjection ? get2DYPositionFractionMercatorProjection : get2DYPositionFractionGeographicProjection; } - GlobeSurfaceShaderSet.prototype.getShaderProgram = function(frameState, surfaceTile, numberOfDayTextures, applyBrightness, applyContrast, applyHue, applySaturation, applyGamma, applyAlpha, applySplit, showReflectiveOcean, showOceanWaves, enableLighting, hasVertexNormals, useWebMercatorProjection, enableFog) { + GlobeSurfaceShaderSet.prototype.getShaderProgram = function(frameState, surfaceTile, numberOfDayTextures, applyBrightness, applyContrast, applyHue, applySaturation, applyGamma, applyAlpha, applySplit, showReflectiveOcean, showOceanWaves, enableLighting, hasVertexNormals, useWebMercatorProjection, enableFog, enableClippingPlanes) { var quantization = 0; var quantizationDefine = ''; @@ -87,7 +87,8 @@ define([ (useWebMercatorProjection << 12) | (enableFog << 13) | (quantization << 14) | - (applySplit << 15); + (applySplit << 15) | + (enableClippingPlanes << 16); var surfaceShader = surfaceTile.surfaceShader; if (defined(surfaceShader) && @@ -160,6 +161,10 @@ define([ fs.defines.push('APPLY_SPLIT'); } + if (enableClippingPlanes) { + fs.defines.push('ENABLE_CLIPPING_PLANES'); + } + var computeDayColor = '\ vec4 computeDayColor(vec4 initialColor, vec3 textureCoordinates)\n\ {\n\ diff --git a/Source/Scene/GlobeSurfaceTileProvider.js b/Source/Scene/GlobeSurfaceTileProvider.js index 00c047e2b83d..2665bfc37ec9 100644 --- a/Source/Scene/GlobeSurfaceTileProvider.js +++ b/Source/Scene/GlobeSurfaceTileProvider.js @@ -20,6 +20,7 @@ define([ '../Core/Matrix4', '../Core/OrientedBoundingBox', '../Core/OrthographicFrustum', + '../Core/Plane', '../Core/PrimitiveType', '../Core/Rectangle', '../Core/SphereOutlineGeometry', @@ -64,6 +65,7 @@ define([ Matrix4, OrientedBoundingBox, OrthographicFrustum, + Plane, PrimitiveType, Rectangle, SphereOutlineGeometry, @@ -158,6 +160,13 @@ define([ this._baseColor = undefined; this._firstPassInitialColor = undefined; this.baseColor = new Color(0.0, 0.0, 0.5, 1.0); + + /** + * Gets or sets the array of clipping planes, defined in world space; + * Each clipping plane selectively disables rendering contents on the outside of the plane. + * @type {Plane[]} + */ + this.clippingPlanes = undefined; } defineProperties(GlobeSurfaceTileProvider.prototype, { @@ -849,6 +858,12 @@ define([ u_dayTextureSplit : function() { return this.properties.dayTextureSplit; }, + u_clippingPlanesLength : function() { + return this.properties.clippingPlanes.length; + }, + u_clippingPlanes : function() { + return this.properties.clippingPlanes; + }, // make a separate object so that changes to the properties are seen on // derived commands that combine another uniform map with this one. @@ -883,7 +898,8 @@ define([ waterMaskTranslationAndScale : new Cartesian4(), minMaxHeight : new Cartesian2(), - scaleAndBias : new Matrix4() + scaleAndBias : new Matrix4(), + clippingPlanes : [] } }; @@ -1006,6 +1022,7 @@ define([ })(); var otherPassesInitialColor = new Cartesian4(0.0, 0.0, 0.0, 0.0); + var scratchPlane = new Plane(Cartesian3.UNIT_X, 0.0); function addDrawCommandsForTile(tileProvider, tile, frameState) { var surfaceTile = tile.data; @@ -1259,7 +1276,35 @@ define([ uniformMapProperties.minMaxHeight.y = encoding.maximumHeight; Matrix4.clone(encoding.matrix, uniformMapProperties.scaleAndBias); - command.shaderProgram = tileProvider._surfaceShaderSet.getShaderProgram(frameState, surfaceTile, numberOfDayTextures, applyBrightness, applyContrast, applyHue, applySaturation, applyGamma, applyAlpha, applySplit, showReflectiveOcean, showOceanWaves, tileProvider.enableLighting, hasVertexNormals, useWebMercatorProjection, applyFog); + // update clipping planes + var length = 0; + var clippingPlanes = tileProvider.clippingPlanes; + if (defined(clippingPlanes)) { + length = clippingPlanes.length; + } + if (length !== uniformMapProperties.clippingPlanes.length) { + uniformMapProperties.clippingPlanes = new Array(length); + + for (var i = 0; i < length; ++i) { + uniformMapProperties.clippingPlanes[i] = new Cartesian4(); + } + } + + var packedPlanes = uniformMapProperties.clippingPlanes; + for (var j = 0; j < length; ++j) { + var plane = clippingPlanes[j]; + var packedPlane = packedPlanes[j]; + + Plane.transform(plane, context.uniformState.view, scratchPlane); + + Cartesian3.clone(scratchPlane.normal, packedPlane); + packedPlane.w = scratchPlane.distance; + } + + // TODO: Optimization, determine if this tile is entirely clipped or entirely visible + var clippingPlanesEnabled = (uniformMapProperties.clippingPlanes.length > 0); + + command.shaderProgram = tileProvider._surfaceShaderSet.getShaderProgram(frameState, surfaceTile, numberOfDayTextures, applyBrightness, applyContrast, applyHue, applySaturation, applyGamma, applyAlpha, applySplit, showReflectiveOcean, showOceanWaves, tileProvider.enableLighting, hasVertexNormals, useWebMercatorProjection, applyFog, clippingPlanesEnabled); command.castShadows = castShadows; command.receiveShadows = receiveShadows; command.renderState = renderState; diff --git a/Source/Shaders/GlobeFS.glsl b/Source/Shaders/GlobeFS.glsl index 34f8bc02c4ca..e1925a224f96 100644 --- a/Source/Shaders/GlobeFS.glsl +++ b/Source/Shaders/GlobeFS.glsl @@ -52,6 +52,11 @@ uniform sampler2D u_oceanNormalMap; uniform vec2 u_lightingFadeDistance; #endif +#ifdef ENABLE_CLIPPING_PLANES +uniform int u_clippingPlanesLength; +uniform vec4 u_clippingPlanes[czm_maxClippingPlanes]; +#endif + varying vec3 v_positionMC; varying vec3 v_positionEC; varying vec3 v_textureCoordinates; @@ -141,6 +146,10 @@ vec4 computeWaterColor(vec3 positionEyeCoordinates, vec2 textureCoordinates, mat void main() { +#ifdef ENABLE_CLIPPING_PLANES + czm_discardIfClipped(u_clippingPlanes, u_clippingPlanesLength); +#endif + // The clamp below works around an apparent bug in Chrome Canary v23.0.1241.0 // where the fragment shader sees textures coordinates < 0.0 and > 1.0 for the // fragments on the edges of tiles even though the vertex shader is outputting From 951a33bb2182b6079a59903a5ca4462b7d2b1531 Mon Sep 17 00:00:00 2001 From: ggetz Date: Mon, 13 Nov 2017 09:35:41 -0500 Subject: [PATCH 06/37] Modified shaders to highlight clipping edge --- Source/Scene/Model.js | 3 ++- .../Shaders/Builtin/Functions/discardIfClipped.glsl | 13 +++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js index bf8687cc4f21..d37a32ba80ff 100644 --- a/Source/Scene/Model.js +++ b/Source/Scene/Model.js @@ -4256,7 +4256,8 @@ define([ '{ \n' + ' gltf_clip_main(); \n' + ' if (gltf_clippingPlanesEnabled) { \n' + - ' czm_discardIfClipped(gltf_clippingPlanes, gltf_clippingPlanesLength); \n' + + ' float amount = czm_discardIfClipped(gltf_clippingPlanes, gltf_clippingPlanesLength); \n' + + ' if (amount > 0.0 && amount < 1.0) gl_FragColor = vec4(1.0); \n' + ' } \n' + '} \n'; diff --git a/Source/Shaders/Builtin/Functions/discardIfClipped.glsl b/Source/Shaders/Builtin/Functions/discardIfClipped.glsl index 0a59ae5f7e64..04b60f9a1ef7 100644 --- a/Source/Shaders/Builtin/Functions/discardIfClipped.glsl +++ b/Source/Shaders/Builtin/Functions/discardIfClipped.glsl @@ -7,7 +7,7 @@ * @param {vec4[]} clippingPlanes The array of planes used to clip, defined in eyespace. * @param {int} clippingPlanesLength The number of planes in the array of clipping planes. */ -void czm_discardIfClipped (vec4[czm_maxClippingPlanes] clippingPlanes, int clippingPlanesLength) +float czm_discardIfClipped (vec4[czm_maxClippingPlanes] clippingPlanes, int clippingPlanesLength) { if (clippingPlanesLength > 0) { @@ -15,6 +15,7 @@ void czm_discardIfClipped (vec4[czm_maxClippingPlanes] clippingPlanes, int clipp vec4 position = czm_windowToEyeCoordinates(gl_FragCoord); vec3 clipNormal = vec3(0.0); vec3 clipPosition = vec3(0.0); + float clipAmount = 100.0; for (int i = 0; i < czm_maxClippingPlanes; ++i) { @@ -25,12 +26,20 @@ void czm_discardIfClipped (vec4[czm_maxClippingPlanes] clippingPlanes, int clipp clipNormal = clippingPlanes[i].xyz; clipPosition = -clippingPlanes[i].w * clipNormal; - clipped = dot(clipNormal, (position.xyz - clipPosition)) <= 0.0; + float amount = dot(clipNormal, (position.xyz - clipPosition)); + clipped = (amount <= 0.0); + if (abs(amount) < clipAmount) { + clipAmount = abs(amount); + } + } if (clipped) { discard; + return 0.0; } + + return clipAmount; } } From 8981779ec93aeea7ff4c05274ebf78c4672946ac Mon Sep 17 00:00:00 2001 From: ggetz Date: Mon, 13 Nov 2017 10:21:09 -0500 Subject: [PATCH 07/37] Add demo --- Apps/Sandcastle/gallery/Terrain Clipping.html | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 Apps/Sandcastle/gallery/Terrain Clipping.html diff --git a/Apps/Sandcastle/gallery/Terrain Clipping.html b/Apps/Sandcastle/gallery/Terrain Clipping.html new file mode 100644 index 000000000000..00270f2cc6d2 --- /dev/null +++ b/Apps/Sandcastle/gallery/Terrain Clipping.html @@ -0,0 +1,61 @@ + + + + + + + + + Cesium Demo + + + + + + +
+

Loading...

+
+ + + From c0ec2c4b6859663688d04e8ca90693ccee21c15e Mon Sep 17 00:00:00 2001 From: ggetz Date: Mon, 13 Nov 2017 13:07:01 -0500 Subject: [PATCH 08/37] Add highlighting to terrain edges --- Source/Scene/Model.js | 5 ++++- Source/Shaders/GlobeFS.glsl | 7 ++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js index d37a32ba80ff..dd5312cbab42 100644 --- a/Source/Scene/Model.js +++ b/Source/Scene/Model.js @@ -4246,6 +4246,9 @@ define([ } } + var edgeColor = 'vec4(1.0)'; + var edgeWidth = '0.7'; + function modifyShaderForClippingPlanes(shader) { shader = ShaderSource.replaceMain(shader, 'gltf_clip_main'); shader += @@ -4257,7 +4260,7 @@ define([ ' gltf_clip_main(); \n' + ' if (gltf_clippingPlanesEnabled) { \n' + ' float amount = czm_discardIfClipped(gltf_clippingPlanes, gltf_clippingPlanesLength); \n' + - ' if (amount > 0.0 && amount < 1.0) gl_FragColor = vec4(1.0); \n' + + ' if (amount > 0.0 && amount < '+ edgeWidth + ') gl_FragColor = ' + edgeColor + '; \n' + ' } \n' + '} \n'; diff --git a/Source/Shaders/GlobeFS.glsl b/Source/Shaders/GlobeFS.glsl index e1925a224f96..1d6be70cd4b1 100644 --- a/Source/Shaders/GlobeFS.glsl +++ b/Source/Shaders/GlobeFS.glsl @@ -147,7 +147,7 @@ vec4 computeWaterColor(vec3 positionEyeCoordinates, vec2 textureCoordinates, mat void main() { #ifdef ENABLE_CLIPPING_PLANES - czm_discardIfClipped(u_clippingPlanes, u_clippingPlanesLength); + float clippingEdgeAmount = czm_discardIfClipped(u_clippingPlanes, u_clippingPlanesLength); #endif // The clamp below works around an apparent bug in Chrome Canary v23.0.1241.0 @@ -204,6 +204,11 @@ void main() vec4 finalColor = color; #endif +#ifdef ENABLE_CLIPPING_PLANES + if (clippingEdgeAmount > 0.0 && clippingEdgeAmount < 0.5) { // Edge width + finalColor = vec4(1.0); // Edgecolor + } +#endif #ifdef FOG const float fExposure = 2.0; From 5ec1058cb5df71f7d6850e55765c725e17d939c9 Mon Sep 17 00:00:00 2001 From: ggetz Date: Tue, 14 Nov 2017 13:15:01 -0500 Subject: [PATCH 09/37] Multiple clipping planes && together, define terrain clipping plane origin --- Apps/Sandcastle/gallery/Terrain Clipping.html | 8 +++++--- Source/Scene/Globe.js | 13 +++++++++++++ Source/Scene/GlobeSurfaceTileProvider.js | 6 +++++- .../Shaders/Builtin/Functions/discardIfClipped.glsl | 6 +++--- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/Apps/Sandcastle/gallery/Terrain Clipping.html b/Apps/Sandcastle/gallery/Terrain Clipping.html index 00270f2cc6d2..4d330677d4cf 100644 --- a/Apps/Sandcastle/gallery/Terrain Clipping.html +++ b/Apps/Sandcastle/gallery/Terrain Clipping.html @@ -39,13 +39,15 @@ viewer.terrainProvider = cesiumTerrainProviderMeshes; var globe = viewer.scene.globe; - +globe.terrainClippingPlanesOrigin = new Cesium.Cartesian3(0.0, 0.0, 3000000.0); // Defines the "center" from which the planes' normals and offsets will orginate var planes = globe.terrainClippingPlanes = [ - new Cesium.Plane(new Cesium.Cartesian3(0.0, 0.0, -1.0), 1000000.0) + new Cesium.Plane(new Cesium.Cartesian3(0.0, 0.0, -1.0), 0.0), + new Cesium.Plane(new Cesium.Cartesian3(0.0, 0.0, 1.0), 100000.0) ]; window.setInterval(function () { - planes[0].distance -= 5.0; + planes[0].distance -= 50.0; + planes[1].distance += 50.0; }, 30); //Sandcastle_End diff --git a/Source/Scene/Globe.js b/Source/Scene/Globe.js index 2c853c9e70f2..51dfa8c6c273 100644 --- a/Source/Scene/Globe.js +++ b/Source/Scene/Globe.js @@ -234,6 +234,19 @@ define([ this._surface.tileProvider.baseColor = value; } }, + /** + * A property specifying the reference location that each clipping plane treats as the origin. + * + * @type {Cartesian3} + */ + terrainClippingPlanesOrigin : { + get : function() { + return this._surface.tileProvider.clippingPlanesOrigin; + }, + set : function(value) { + this._surface.tileProvider.clippingPlanesOrigin = value; + } + }, /** * A property specifying an array of up to 6 {@link Plane} used to selectively disable rendering on the outside of each plane. * diff --git a/Source/Scene/GlobeSurfaceTileProvider.js b/Source/Scene/GlobeSurfaceTileProvider.js index 2665bfc37ec9..c73c59899db1 100644 --- a/Source/Scene/GlobeSurfaceTileProvider.js +++ b/Source/Scene/GlobeSurfaceTileProvider.js @@ -167,6 +167,7 @@ define([ * @type {Plane[]} */ this.clippingPlanes = undefined; + this.clippingPlanesOrigin = new Cartesian3(); } defineProperties(GlobeSurfaceTileProvider.prototype, { @@ -1023,6 +1024,7 @@ define([ var otherPassesInitialColor = new Cartesian4(0.0, 0.0, 0.0, 0.0); var scratchPlane = new Plane(Cartesian3.UNIT_X, 0.0); + var scratchMatrix = new Matrix4(); function addDrawCommandsForTile(tileProvider, tile, frameState) { var surfaceTile = tile.data; @@ -1291,11 +1293,13 @@ define([ } var packedPlanes = uniformMapProperties.clippingPlanes; + var origin = Matrix4.fromTranslation(tileProvider.clippingPlanesOrigin, scratchMatrix); + var transform = Matrix4.multiply(context.uniformState.view, origin, scratchMatrix); for (var j = 0; j < length; ++j) { var plane = clippingPlanes[j]; var packedPlane = packedPlanes[j]; - Plane.transform(plane, context.uniformState.view, scratchPlane); + Plane.transform(plane, transform, scratchPlane); Cartesian3.clone(scratchPlane.normal, packedPlane); packedPlane.w = scratchPlane.distance; diff --git a/Source/Shaders/Builtin/Functions/discardIfClipped.glsl b/Source/Shaders/Builtin/Functions/discardIfClipped.glsl index 0a59ae5f7e64..3ba6bb00c9fa 100644 --- a/Source/Shaders/Builtin/Functions/discardIfClipped.glsl +++ b/Source/Shaders/Builtin/Functions/discardIfClipped.glsl @@ -11,21 +11,21 @@ void czm_discardIfClipped (vec4[czm_maxClippingPlanes] clippingPlanes, int clipp { if (clippingPlanesLength > 0) { - bool clipped = false; + bool clipped = true; vec4 position = czm_windowToEyeCoordinates(gl_FragCoord); vec3 clipNormal = vec3(0.0); vec3 clipPosition = vec3(0.0); for (int i = 0; i < czm_maxClippingPlanes; ++i) { - if (i == clippingPlanesLength || clipped) + if (i == clippingPlanesLength) { break; } clipNormal = clippingPlanes[i].xyz; clipPosition = -clippingPlanes[i].w * clipNormal; - clipped = dot(clipNormal, (position.xyz - clipPosition)) <= 0.0; + clipped = clipped && (dot(clipNormal, (position.xyz - clipPosition)) <= 0.0); } if (clipped) From 443f73a8a60e3eb921902985bb8eb0d6d53c0c2e Mon Sep 17 00:00:00 2001 From: ggetz Date: Fri, 17 Nov 2017 10:15:02 -0500 Subject: [PATCH 10/37] Add ClippingPlaneCollection --- Apps/Sandcastle/gallery/Clipping Planes.html | 28 ++-- Apps/Sandcastle/gallery/Terrain Clipping.html | 13 +- Source/DataSources/ModelGraphics.js | 2 +- Source/Scene/Batched3DModel3DTileContent.js | 12 +- Source/Scene/Cesium3DTile.js | 5 +- Source/Scene/Cesium3DTileset.js | 14 +- Source/Scene/ClippingPlanesCollection.js | 141 ++++++++++++++++++ Source/Scene/Globe.js | 15 +- Source/Scene/GlobeSurfaceTileProvider.js | 38 +++-- Source/Scene/Model.js | 89 ++++++----- .../Builtin/Functions/discardIfClipped.glsl | 18 ++- Source/Shaders/GlobeFS.glsl | 9 +- 12 files changed, 274 insertions(+), 110 deletions(-) create mode 100644 Source/Scene/ClippingPlanesCollection.js diff --git a/Apps/Sandcastle/gallery/Clipping Planes.html b/Apps/Sandcastle/gallery/Clipping Planes.html index cbd1716033a4..5ca87ed1957f 100644 --- a/Apps/Sandcastle/gallery/Clipping Planes.html +++ b/Apps/Sandcastle/gallery/Clipping Planes.html @@ -51,7 +51,6 @@ var planeScale = new Cesium.Cartesian2(300.0, 300.0); -var clippingPlanes; var defaultClippingPlanes = [ new Cesium.Plane(new Cesium.Cartesian3(0.0, 0.0,-1.0), -100.0) ]; @@ -100,12 +99,24 @@ } }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); +function createPlaneUpdateFunction(plane) { + return function () { + plane.distance = Cesium.Math.lerp(plane.distance, targetY, 0.1); + return plane.distance; + }; +} + var tileset; function loadTileset(url) { reset(); tileset = viewer.scene.primitives.add(new Cesium.Cesium3DTileset({ - url : url + url : url, + clippingPlanes : new Cesium.ClippingPlanesCollection({ + planes : defaultClippingPlanes, + edgeWidth: 10.0, + edgeColor: Cesium.Color.CYAN + }) })); tileset.readyPromise.then(function() { @@ -114,17 +125,14 @@ viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY); for (var i = 0; i < 1; ++i) { - var plane = clippingPlanes[i]; + var plane = tileset.clippingPlanes.planes[i]; var entity = viewer.entities.add({ position : boundingSphere.center, plane : { dimensions : planeScale, material : Cesium.Color.CYAN.withAlpha(0.1), normal: plane.normal, - distance: new Cesium.CallbackProperty(function() { - plane.distance = Cesium.Math.lerp(plane.distance, targetY, 0.1); - return plane.distance; - }, false), + distance: new Cesium.CallbackProperty(createPlaneUpdateFunction(plane), false), outline: true, outlineColor: Cesium.Color.CYAN } @@ -137,8 +145,6 @@ }); tileset.debugShowBoundingVolume = viewModel.debugBoundingVolumesEnabled; - tileset.clippingPlanesEnabled = viewModel.clippingPlanesEnabled; - clippingPlanes = tileset.clippingPlanes = defaultClippingPlanes.slice(); } // Power Plant design model provided by Bentley Systems @@ -161,10 +167,6 @@ viewer.entities.removeAll(); viewer.scene.primitives.removeAll(); planeEntities = []; - - if (Cesium.defined(viewModel)) { - viewModel.clippingPlanesEnabled = true; - } } //Sandcastle_End diff --git a/Apps/Sandcastle/gallery/Terrain Clipping.html b/Apps/Sandcastle/gallery/Terrain Clipping.html index 4d330677d4cf..ed2a15cd94ac 100644 --- a/Apps/Sandcastle/gallery/Terrain Clipping.html +++ b/Apps/Sandcastle/gallery/Terrain Clipping.html @@ -39,12 +39,15 @@ viewer.terrainProvider = cesiumTerrainProviderMeshes; var globe = viewer.scene.globe; -globe.terrainClippingPlanesOrigin = new Cesium.Cartesian3(0.0, 0.0, 3000000.0); // Defines the "center" from which the planes' normals and offsets will orginate -var planes = globe.terrainClippingPlanes = [ - new Cesium.Plane(new Cesium.Cartesian3(0.0, 0.0, -1.0), 0.0), - new Cesium.Plane(new Cesium.Cartesian3(0.0, 0.0, 1.0), 100000.0) -]; +globe.terrainClippingPlanes = new Cesium.ClippingPlanesCollection({ + transformationMatrix : Cesium.Matrix4.fromTranslation(new Cesium.Cartesian3(0.0, 0.0, 3000000.0)), + planes : [ + new Cesium.Plane(new Cesium.Cartesian3(0.0, 0.0, -1.0), 0.0), + new Cesium.Plane(new Cesium.Cartesian3(0.0, 0.0, 1.0), 100000.0) + ] +}); +var planes = globe.terrainClippingPlanes.planes; window.setInterval(function () { planes[0].distance -= 50.0; planes[1].distance += 50.0; diff --git a/Source/DataSources/ModelGraphics.js b/Source/DataSources/ModelGraphics.js index 566b1249dc2e..90af4ab0a59a 100644 --- a/Source/DataSources/ModelGraphics.js +++ b/Source/DataSources/ModelGraphics.js @@ -54,7 +54,7 @@ define([ * @param {Property} [options.color=Color.WHITE] A Property specifying the {@link Color} that blends with the model's rendered color. * @param {Property} [options.colorBlendMode=ColorBlendMode.HIGHLIGHT] An enum Property specifying how the color blends with the model. * @param {Property} [options.colorBlendAmount=0.5] A numeric Property specifying the color strength when the colorBlendMode is MIX. A value of 0.0 results in the model's rendered color while a value of 1.0 results in a solid color, with any value in-between resulting in a mix of the two. - * @param {Property} [options.clippingPlanes=[]] A property specifying an array of up to 6 {@link Plane} used to selectively disable rendering on the outside of each plane. + * @param {Property} [options.clippingPlanes] A property specifying an array of up to 6 {@link Plane} used to selectively disable rendering on the outside of each plane. * * @see {@link http://cesiumjs.org/2014/03/03/Cesium-3D-Models-Tutorial/|3D Models Tutorial} * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=3D%20Models.html|Cesium Sandcastle 3D Models Demo} diff --git a/Source/Scene/Batched3DModel3DTileContent.js b/Source/Scene/Batched3DModel3DTileContent.js index 95ce75a3ab86..86bed8370b29 100644 --- a/Source/Scene/Batched3DModel3DTileContent.js +++ b/Source/Scene/Batched3DModel3DTileContent.js @@ -378,8 +378,7 @@ define([ pickUniformMapLoaded : batchTable.getPickUniformMapCallback(), addBatchIdToGeneratedShaders : (batchLength > 0), // If the batch table has values in it, generated shaders will need a batchId attribute pickObject : pickObject, - clippingPlanes : tileset.clippingPlanes, - clippingPlanesEnabled : tileset.clippingPlanesEnabled && tile._isClipped + clippingPlanes : tileset.clippingPlanes }); } @@ -449,8 +448,13 @@ define([ this._model.modelMatrix = this._tile.computedTransform; this._model.shadows = this._tileset.shadows; this._model.debugWireframe = this._tileset.debugWireframe; - this._model.clippingPlanes = this._tileset.clippingPlanes; - this._model.clippingPlanesEnabled = this._tileset.clippingPlanesEnabled && this._tile._isClipped; + + // Update clipping planes + var modelClippingPlanes = this._model.clippingPlanes.planes; + var tilesetClippingPlanes = this._tileset.clippingPlanes; + tilesetClippingPlanes.clone(modelClippingPlanes); + modelClippingPlanes.enabled = tilesetClippingPlanes.enabled && this._tile._isClipped; + this._model.update(frameState); // If any commands were pushed, add derived commands diff --git a/Source/Scene/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js index a09d67affac0..dfe40de97a62 100644 --- a/Source/Scene/Cesium3DTile.js +++ b/Source/Scene/Cesium3DTile.js @@ -743,8 +743,9 @@ define([ var scratchPlane = new Plane(Cartesian3.UNIT_X, 0.0); function checkTileClipped(tile, boundingVolume) { - var planes = tile._tileset.clippingPlanes; - if (defined(planes)) { + var clippingPlanes = tile._tileset.clippingPlanes; + if (defined(clippingPlanes)) { + var planes = clippingPlanes.planes; var length = planes.length; var rootTransform = tile._tileset._root.computedTransform; for (var i = 0; i < length; ++i) { diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index f5ffbe16cfa4..3194f7463051 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -105,8 +105,7 @@ define([ * @param {Number} [options.skipLevels=1] When skipLevelOfDetail is true, a constant defining the minimum number of levels to skip when loading tiles. When it is 0, no levels are skipped. Used in conjunction with skipScreenSpaceErrorFactor to determine which tiles to load. * @param {Boolean} [options.immediatelyLoadDesiredLevelOfDetail=false] When skipLevelOfDetail is true, only tiles that meet the maximum screen space error will ever be downloaded. Skipping factors are ignored and just the desired tiles are loaded. * @param {Boolean} [options.loadSiblings=false] When skipLevelOfDetail is true, determines whether siblings of visible tiles are always downloaded during traversal. - * @param {Plane[]} [options.clippingPlanes] A property specifying an array of up to 6 {@link Plane} used to selectively disable rendering on the outside of each plane. - * @param [Boolean} [options.clippingPlanesEnabled=true] Optimization option. If set to false, the tileset will not perform clipping operations. + * @param {ClippingPlaneCollection} [options.clippingPlanes] A property specifying an array of up to 6 {@link Plane} used to selectively disable rendering on the outside of each plane. * @param {Boolean} [options.debugFreezeFrame=false] For debugging only. Determines if only the tiles from last frame should be used for rendering. * @param {Boolean} [options.debugColorizeTiles=false] For debugging only. When true, assigns a random color to each tile. * @param {Boolean} [options.debugWireframe=false] For debugging only. When true, render's each tile's content as a wireframe. @@ -527,19 +526,10 @@ define([ /** * A property specifying an array of up to 6 {@link Plane} used to selectively disable rendering on the outside of each plane. * - * @type {Plane[]} + * @type {ClippingPlanesCollection} */ this.clippingPlanes = options.clippingPlanes; - /** - * Optimization option. If set to false, the tileset will not perform clipping operations. - * - * @type {Boolean} - * @default {undefined} - * @see Cesium3DTileset.clippingPlanes - */ - this.clippingPlanesEnabled = defaultValue(options.clippingPlanesEnabled, true); - /** * This property is for debugging only; it is not optimized for production use. *

diff --git a/Source/Scene/ClippingPlanesCollection.js b/Source/Scene/ClippingPlanesCollection.js new file mode 100644 index 000000000000..8e7b954809a7 --- /dev/null +++ b/Source/Scene/ClippingPlanesCollection.js @@ -0,0 +1,141 @@ +define([ + '../Core/BoundingSphere', + '../Core/buildModuleUrl', + '../Core/Cartesian3', + '../Core/Cartographic', + '../Core/Color', + '../Core/defaultValue', + '../Core/defined', + '../Core/Matrix4', + '../Core/Plane' +], function( + BoundingSphere, + buildModuleUrl, + Cartesian3, + Cartographic, + Color, + defaultValue, + defined, + Matrix4, + Plane) { + 'use strict'; + + /** + * The globe rendered in the scene, including its terrain ({@link ClippingPlanesCollection#terrainProvider}) + * and imagery layers ({@link ClippingPlanesCollection#imageryLayers}). Access the globe using {@link Scene#globe}. + * + * @alias ClippingPlanesCollection + * @constructor + * + * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] Determines the size and shape of the + * globe. + */ + function ClippingPlanesCollection(options) { + var options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + /** + * An array of up to 6 {@link Plane} used to selectively disable rendering on the outside of each plane. + * + * @type {Plane} + * @default [] + */ + this.planes = defaultValue(options.planes, []); // TODO don't instantiate? + + /** + * Determines whether the clipping planes are active. + * + * @type {Boolean} + * @default true + */ + this.enabled = defaultValue(options.enabled, true); + + /** + * A transformation matrix specifying the coordinate system for this collection. + * + * @type {Matrix4} + * @default Matrix4.IDENTITY + */ + this.transformationMatrix = defaultValue(options.transformationMatrix, Matrix4.IDENTITY); + + /** + * If true, the region to be clipped must be included in all planes in this collection. + * Otherwise, a region will be clipped if included in any plane in the collection. + * + * @type {Boolean} + * @default true + */ + this.inclusive = defaultValue(options.inclusive, true); + + /** + * The color of the highlighted clipped edge. + * + * @type {Color} + * @default Color.WHITE + */ + this.edgeColor = defaultValue(options.edgeColor, Color.WHITE); + + /** + * The width of the clipped edge to highlight. + * + * @type {Number} + * @default 0.0 + */ + this.edgeWidth = defaultValue(options.edgeWidth, 0.0); + + } + + var scratchPlane = new Plane(Cartesian3.UNIT_X, 0.0); + var scratchMatrix = new Matrix4(); + /** + * Applies the transformations to each plane and packs it into an array. + * + * @param viewMatrix + * @param [array] + * @returns {Cartesian4[]} The array of packed planes. + */ + ClippingPlanesCollection.prototype.transformAndPackPlanes = function (viewMatrix, array) { + var planes = this.planes; + var length = planes.length; + + if (!defined(array)) { + array = new Array(length); + } + + var transform = Matrix4.multiply(viewMatrix, this.transformationMatrix, scratchMatrix); + + for (var j = 0; j < length; ++j) { + var plane = planes[j]; + var packedPlane = array[j]; + + Plane.transform(plane, transform, scratchPlane); + + Cartesian3.clone(scratchPlane.normal, packedPlane); + packedPlane.w = scratchPlane.distance; + } + + return array; + }; + + /** + * Duplicates this ClippingPlanesCollection instance. + * + * @param {ClippingPlanesCollection} [result] The object onto which to store the result. + * @returns he modified result parameter or a new ClippingPlanesCollection instance if one was not provided. + */ + ClippingPlanesCollection.prototype.clone = function (result) { + if (!defined(result)) { + result = new ClippingPlanesCollection(); + } + + result.planes = Array.from(this.planes); + result.enabled = this.enabled; + Matrix4.clone(this.transformationMatrix, result.transformationMatrix); + result.inclusive = this.inclusive; + Color.clone(this.edgeColor, result.edgeColor); + result.edgeWidth = this.edgeWidth; + + return result; + }; + + return ClippingPlanesCollection; +}); diff --git a/Source/Scene/Globe.js b/Source/Scene/Globe.js index 51dfa8c6c273..41f6b6e5b17e 100644 --- a/Source/Scene/Globe.js +++ b/Source/Scene/Globe.js @@ -234,23 +234,10 @@ define([ this._surface.tileProvider.baseColor = value; } }, - /** - * A property specifying the reference location that each clipping plane treats as the origin. - * - * @type {Cartesian3} - */ - terrainClippingPlanesOrigin : { - get : function() { - return this._surface.tileProvider.clippingPlanesOrigin; - }, - set : function(value) { - this._surface.tileProvider.clippingPlanesOrigin = value; - } - }, /** * A property specifying an array of up to 6 {@link Plane} used to selectively disable rendering on the outside of each plane. * - * @type {Plane[]} + * @type {ClippingPlaneCollection} */ terrainClippingPlanes : { get : function() { diff --git a/Source/Scene/GlobeSurfaceTileProvider.js b/Source/Scene/GlobeSurfaceTileProvider.js index c73c59899db1..009c4f61a769 100644 --- a/Source/Scene/GlobeSurfaceTileProvider.js +++ b/Source/Scene/GlobeSurfaceTileProvider.js @@ -164,10 +164,9 @@ define([ /** * Gets or sets the array of clipping planes, defined in world space; * Each clipping plane selectively disables rendering contents on the outside of the plane. - * @type {Plane[]} + * @type {ClippingPlanesCollection} */ this.clippingPlanes = undefined; - this.clippingPlanesOrigin = new Cartesian3(); } defineProperties(GlobeSurfaceTileProvider.prototype, { @@ -865,6 +864,15 @@ define([ u_clippingPlanes : function() { return this.properties.clippingPlanes; }, + u_clippingPlanesInclusive : function() { + return this.properties.clippingPlanesInclusive; + }, + u_clippingPlanesEdgeColor : function() { + return this.properties.clippingPlanesEdgeColor; + }, + u_clippingPlanesEdgeWidth : function() { + return this.properties.clippingPlanesEdgeWidth; + }, // make a separate object so that changes to the properties are seen on // derived commands that combine another uniform map with this one. @@ -900,7 +908,10 @@ define([ minMaxHeight : new Cartesian2(), scaleAndBias : new Matrix4(), - clippingPlanes : [] + clippingPlanes : [], + clippingPlanesInclusive : true, + clippingPlanesEdgeColor : new Cartesian4(1.0, 1.0, 1.0, 1.0), + clippingPlanesEdgeWidth : 0.0 } }; @@ -1281,8 +1292,9 @@ define([ // update clipping planes var length = 0; var clippingPlanes = tileProvider.clippingPlanes; + if (defined(clippingPlanes)) { - length = clippingPlanes.length; + length = clippingPlanes.planes.length; } if (length !== uniformMapProperties.clippingPlanes.length) { uniformMapProperties.clippingPlanes = new Array(length); @@ -1292,21 +1304,15 @@ define([ } } - var packedPlanes = uniformMapProperties.clippingPlanes; - var origin = Matrix4.fromTranslation(tileProvider.clippingPlanesOrigin, scratchMatrix); - var transform = Matrix4.multiply(context.uniformState.view, origin, scratchMatrix); - for (var j = 0; j < length; ++j) { - var plane = clippingPlanes[j]; - var packedPlane = packedPlanes[j]; - - Plane.transform(plane, transform, scratchPlane); - - Cartesian3.clone(scratchPlane.normal, packedPlane); - packedPlane.w = scratchPlane.distance; + if (defined(clippingPlanes) && clippingPlanes.enabled) { + clippingPlanes.transformAndPackPlanes(context.uniformState.view, uniformMapProperties.clippingPlanes); + uniformMapProperties.clippingPlanesInclusive = clippingPlanes.inclusive; + uniformMapProperties.clippingPlanesEdgeColor = Cartesian4.fromColor(clippingPlanes.edgeColor, uniformMapProperties.clippingPlanesEdgeColor); + uniformMapProperties.clippingPlanesEdgeWidth = clippingPlanes.edgeWidth; } // TODO: Optimization, determine if this tile is entirely clipped or entirely visible - var clippingPlanesEnabled = (uniformMapProperties.clippingPlanes.length > 0); + var clippingPlanesEnabled = defined(clippingPlanes) && clippingPlanes.enabled && (uniformMapProperties.clippingPlanes.length > 0); command.shaderProgram = tileProvider._surfaceShaderSet.getShaderProgram(frameState, surfaceTile, numberOfDayTextures, applyBrightness, applyContrast, applyHue, applySaturation, applyGamma, applyAlpha, applySplit, showReflectiveOcean, showOceanWaves, tileProvider.enableLighting, hasVertexNormals, useWebMercatorProjection, applyFog, clippingPlanesEnabled); command.castShadows = castShadows; diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js index dd5312cbab42..84038881c48e 100644 --- a/Source/Scene/Model.js +++ b/Source/Scene/Model.js @@ -341,8 +341,7 @@ define([ * @param {Number} [options.colorBlendAmount=0.5] Value used to determine the color strength when the colorBlendMode is MIX. A value of 0.0 results in the model's rendered color while a value of 1.0 results in a solid color, with any value in-between resulting in a mix of the two. * @param {Color} [options.silhouetteColor=Color.RED] The silhouette color. If more than 256 models have silhouettes enabled, there is a small chance that overlapping models will have minor artifacts. * @param {Number} [options.silhouetteSize=0.0] The size of the silhouette in pixels. - * @param {Plane[]} [options.clippingPlanes=[]] An array of {@link Plane} used to clip the model. - * @param {Boolean} [options.clippingPlanesEnabled=true] Optimization option. If set to false, the model will not perform clipping operations. + * @param {ClippingPlanesCollection} [options.clippingPlanes] An array of {@link Plane} used to clip the model. * * @exception {DeveloperError} bgltf is not a valid Binary glTF file. * @exception {DeveloperError} Only glTF Binary version 1 is supported. @@ -583,21 +582,10 @@ define([ /** * A property specifying an array of up to 6 {@link Plane} used to selectively disable rendering on the outside of each plane. * - * @type {Plane[]} + * @type {ClippingPlanesCollection} */ this.clippingPlanes = options.clippingPlanes; - /** - * Optimization option. If set to false, the model will not perform clipping operations. - * - * @see Model.clippingPlanes - * - * @type {Boolean} - * - * @default false - */ - this.clippingPlanesEnabled = defaultValue(options.clippingPlanesEnabled, true); - /** * This property is for debugging only; it is not for production use nor is it optimized. *

@@ -3321,7 +3309,12 @@ define([ function createClippingPlanesEnabledFunction(model) { return function() { - return model.clippingPlanesEnabled; + var clippingPlanes = model.clippingPlanes; + if (!defined(clippingPlanes)) { + return false; + } + + return clippingPlanes.enabled; }; } @@ -3331,27 +3324,47 @@ define([ }; } - var scratchPlane = new Plane(Cartesian3.UNIT_X, 0.0); function createClippingPlanesFunction(model) { return function() { - var planes = model.clippingPlanes; + var clippingPlanes = model.clippingPlanes; var packedPlanes = model._packedClippingPlanes; - if (!model.clippingPlanesEnabled) { - return packedPlanes; + if (defined(clippingPlanes) && clippingPlanes.enabled) { + clippingPlanes.transformAndPackPlanes(model._modelViewMatrix, packedPlanes); } - var length = packedPlanes.length; - for (var i = 0; i < length; ++i) { - var plane = planes[i]; - var packedPlane = packedPlanes[i]; + return packedPlanes; + }; + } + + function createClippingPlanesInclusiveFunction(model) { + return function() { + if (!defined(model.clippingPlanes)) { + return true; + } - Plane.transform(plane, model._modelViewMatrix, scratchPlane); + return model.clippingPlanes.inclusive; + }; + } - Cartesian3.clone(scratchPlane.normal, packedPlane); - packedPlane.w = scratchPlane.distance; + function createClippingPlanesEdgeWidthFunction(model) { + return function() { + if (!defined(model.clippingPlanes)) { + return 0.0; } - return packedPlanes; + + return model.clippingPlanes.edgeWidth; + }; + } + + var scratchCartesian = new Cartesian4(); + function createClippingPlanesEdgeColorFunction(model) { + return function() { + if (!defined(model.clippingPlanes)) { + return Cartesian4(); + } + + return Cartesian4.fromColor(model.clippingPlanes.edgeColor, scratchCartesian); }; } @@ -3452,7 +3465,10 @@ define([ gltf_colorBlend : createColorBlendFunction(model), gltf_clippingPlanesEnabled: createClippingPlanesEnabledFunction(model), gltf_clippingPlanesLength: createClippingPlanesLengthFunction(model), - gltf_clippingPlanes: createClippingPlanesFunction(model, context) + gltf_clippingPlanes: createClippingPlanesFunction(model, context), + gltf_clippingPlanesInclusive: createClippingPlanesInclusiveFunction(model), + gltf_clippingPlanesEdgeColor: createClippingPlanesEdgeColorFunction(model), + gltf_clippingPlanesEdgeWidth: createClippingPlanesEdgeWidthFunction(model) }); // Allow callback to modify the uniformMap @@ -4246,21 +4262,23 @@ define([ } } - var edgeColor = 'vec4(1.0)'; - var edgeWidth = '0.7'; - function modifyShaderForClippingPlanes(shader) { shader = ShaderSource.replaceMain(shader, 'gltf_clip_main'); shader += 'uniform bool gltf_clippingPlanesEnabled; \n' + 'uniform int gltf_clippingPlanesLength; \n' + 'uniform vec4 gltf_clippingPlanes[czm_maxClippingPlanes]; \n' + + 'uniform bool gltf_clippingPlanesInclusive; \n' + + 'uniform vec4 gltf_clippingPlanesEdgeColor; \n' + + 'uniform float gltf_clippingPlanesEdgeWidth; \n' + 'void main() \n' + '{ \n' + ' gltf_clip_main(); \n' + ' if (gltf_clippingPlanesEnabled) { \n' + - ' float amount = czm_discardIfClipped(gltf_clippingPlanes, gltf_clippingPlanesLength); \n' + - ' if (amount > 0.0 && amount < '+ edgeWidth + ') gl_FragColor = ' + edgeColor + '; \n' + + ' float clipDistance = czm_discardIfClipped(gltf_clippingPlanes, gltf_clippingPlanesLength, gltf_clippingPlanesInclusive); \n' + + ' if (clipDistance < gltf_clippingPlanesEdgeWidth) { \n' + + ' gl_FragColor = gltf_clippingPlanesEdgeColor; \n' + + ' } \n' + ' } \n' + '} \n'; @@ -4293,7 +4311,7 @@ define([ var length = 0; var clippingPlanes = model.clippingPlanes; if (defined(clippingPlanes)) { - length = clippingPlanes.length; + length = clippingPlanes.planes.length; } if (model._packedClippingPlanes.length !== length) { @@ -4751,7 +4769,8 @@ define([ } } - if (this.clippingPlanesEnabled && modelViewChanged) { + var clippingPlanes = this.clippingPlanes; + if (defined(clippingPlanes) && clippingPlanes.enabled && modelViewChanged) { Matrix4.multiply(context.uniformState.view3D, this._computedModelMatrix, this._modelViewMatrix); } diff --git a/Source/Shaders/Builtin/Functions/discardIfClipped.glsl b/Source/Shaders/Builtin/Functions/discardIfClipped.glsl index b99f7e371e79..1f3bd125008f 100644 --- a/Source/Shaders/Builtin/Functions/discardIfClipped.glsl +++ b/Source/Shaders/Builtin/Functions/discardIfClipped.glsl @@ -6,12 +6,14 @@ * * @param {vec4[]} clippingPlanes The array of planes used to clip, defined in eyespace. * @param {int} clippingPlanesLength The number of planes in the array of clipping planes. + * @param {bool} inclusive + * @returns {float} The distance away from a clipped fragment, in eyespace */ -float czm_discardIfClipped (vec4[czm_maxClippingPlanes] clippingPlanes, int clippingPlanesLength) +float czm_discardIfClipped (vec4[czm_maxClippingPlanes] clippingPlanes, int clippingPlanesLength, bool inclusive) { if (clippingPlanesLength > 0) { - bool clipped = true; + bool clipped = inclusive; vec4 position = czm_windowToEyeCoordinates(gl_FragCoord); vec3 clipNormal = vec3(0.0); vec3 clipPosition = vec3(0.0); @@ -26,16 +28,22 @@ float czm_discardIfClipped (vec4[czm_maxClippingPlanes] clippingPlanes, int clip clipNormal = clippingPlanes[i].xyz; clipPosition = -clippingPlanes[i].w * clipNormal; + float amount = dot(clipNormal, (position.xyz - clipPosition)); - clipAmount += amount; + if (amount > clipAmount) { + clipAmount = amount; + } - clipped = clipped && (amount <= 0.0); + if (inclusive) { + clipped = clipped && (amount <= 0.0); + } else { + clipped = clipped || (amount <= 0.0); + } } if (clipped) { discard; - return 0.0; } return clipAmount; diff --git a/Source/Shaders/GlobeFS.glsl b/Source/Shaders/GlobeFS.glsl index 1d6be70cd4b1..be076f396704 100644 --- a/Source/Shaders/GlobeFS.glsl +++ b/Source/Shaders/GlobeFS.glsl @@ -55,6 +55,9 @@ uniform vec2 u_lightingFadeDistance; #ifdef ENABLE_CLIPPING_PLANES uniform int u_clippingPlanesLength; uniform vec4 u_clippingPlanes[czm_maxClippingPlanes]; +uniform bool u_clippingPlanesInclusive; +uniform vec4 u_clippingPlanesEdgeColor; +uniform float u_clippingPlanesEdgeWidth; #endif varying vec3 v_positionMC; @@ -147,7 +150,7 @@ vec4 computeWaterColor(vec3 positionEyeCoordinates, vec2 textureCoordinates, mat void main() { #ifdef ENABLE_CLIPPING_PLANES - float clippingEdgeAmount = czm_discardIfClipped(u_clippingPlanes, u_clippingPlanesLength); + float clipDistance = czm_discardIfClipped(u_clippingPlanes, u_clippingPlanesLength, u_clippingPlanesInclusive); #endif // The clamp below works around an apparent bug in Chrome Canary v23.0.1241.0 @@ -205,8 +208,8 @@ void main() #endif #ifdef ENABLE_CLIPPING_PLANES - if (clippingEdgeAmount > 0.0 && clippingEdgeAmount < 0.5) { // Edge width - finalColor = vec4(1.0); // Edgecolor + if (clipDistance < u_clippingPlanesEdgeWidth) { + finalColor = u_clippingPlanesEdgeColor; } #endif From 910e1c6d490c52d30a9443ff17a80f7ea2a381d1 Mon Sep 17 00:00:00 2001 From: ggetz Date: Fri, 17 Nov 2017 17:11:42 -0500 Subject: [PATCH 11/37] Fixed scaling in demo --- Apps/Sandcastle/gallery/Clipping Planes.html | 23 +++--- Source/DataSources/PlaneGeometryUpdater.js | 74 ++++++++------------ Source/DataSources/PlaneGraphics.js | 32 +++++---- Source/Scene/Batched3DModel3DTileContent.js | 8 ++- Source/Scene/ClippingPlanesCollection.js | 3 +- Source/Scene/Model.js | 2 +- 6 files changed, 70 insertions(+), 72 deletions(-) diff --git a/Apps/Sandcastle/gallery/Clipping Planes.html b/Apps/Sandcastle/gallery/Clipping Planes.html index 5ca87ed1957f..425dab024016 100644 --- a/Apps/Sandcastle/gallery/Clipping Planes.html +++ b/Apps/Sandcastle/gallery/Clipping Planes.html @@ -49,7 +49,7 @@ }); var scene = viewer.scene; -var planeScale = new Cesium.Cartesian2(300.0, 300.0); +var planeScale = new Cesium.Cartesian2(500.0, 500.0); var defaultClippingPlanes = [ new Cesium.Plane(new Cesium.Cartesian3(0.0, 0.0,-1.0), -100.0) @@ -99,10 +99,14 @@ } }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); -function createPlaneUpdateFunction(plane) { +var scratchPlane = new Cesium.Plane(Cesium.Cartesian3.UNIT_X, 0.0); +function createPlaneUpdateFunction(tileset, plane) { return function () { plane.distance = Cesium.Math.lerp(plane.distance, targetY, 0.1); - return plane.distance; + + var transformedPlane = Cesium.Plane.transform(plane, tileset.modelMatrix, scratchPlane); + transformedPlane.distance = -transformedPlane.distance; + return transformedPlane; }; } @@ -114,9 +118,10 @@ url : url, clippingPlanes : new Cesium.ClippingPlanesCollection({ planes : defaultClippingPlanes, - edgeWidth: 10.0, + edgeWidth: 1.0, edgeColor: Cesium.Color.CYAN - }) + }), + modelMatrix : Cesium.Matrix4.fromUniformScale(2.0) })); tileset.readyPromise.then(function() { @@ -124,22 +129,22 @@ viewer.camera.viewBoundingSphere(boundingSphere, new Cesium.HeadingPitchRange(0.5, -0.2, boundingSphere.radius * 4.0)); viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY); - for (var i = 0; i < 1; ++i) { + for (var i = 0; i < defaultClippingPlanes.length; ++i) { var plane = tileset.clippingPlanes.planes[i]; var entity = viewer.entities.add({ position : boundingSphere.center, plane : { dimensions : planeScale, material : Cesium.Color.CYAN.withAlpha(0.1), - normal: plane.normal, - distance: new Cesium.CallbackProperty(createPlaneUpdateFunction(plane), false), + plane: new Cesium.CallbackProperty(createPlaneUpdateFunction(tileset, plane), false), outline: true, outlineColor: Cesium.Color.CYAN - } + }, }); planeEntities.push(entity); } + }).otherwise(function(error) { throw(error); }); diff --git a/Source/DataSources/PlaneGeometryUpdater.js b/Source/DataSources/PlaneGeometryUpdater.js index 6ee862b5e7aa..02117d691b1f 100644 --- a/Source/DataSources/PlaneGeometryUpdater.js +++ b/Source/DataSources/PlaneGeometryUpdater.js @@ -70,8 +70,7 @@ define([ function GeometryOptions(entity) { this.id = entity; this.vertexFormat = undefined; - this.normal = undefined; - this.distance = 0.0; + this.plane = undefined; this.dimensions = undefined; } @@ -370,12 +369,8 @@ define([ } var options = this._options; - var normal = options.normal; - var distance = options.distance; - var dimensions = options.dimensions; var modelMatrix = entity.computeModelMatrix(Iso8601.MINIMUM_VALUE); - //modelMatrix = createPrimitiveMatrix(normal, distance, dimensions, modelMatrix, modelMatrix); return new GeometryInstance({ id : entity, @@ -409,13 +404,7 @@ define([ var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK); var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time); - var options = this._options; - var normal = options.normal; - var distance = options.distance; - var dimensions = options.dimensions; - var modelMatrix = entity.computeModelMatrix(Iso8601.MINIMUM_VALUE); - //modelMatrix = createPrimitiveMatrix(normal, distance, dimensions, modelMatrix, modelMatrix); return new GeometryInstance({ id : entity, @@ -453,11 +442,9 @@ define([ return; } - var plane = this._entity.plane; - - + var planeGraphics = this._entity.plane; - if (!defined(plane)) { + if (!defined(planeGraphics)) { if (this._fillEnabled || this._outlineEnabled) { this._fillEnabled = false; this._outlineEnabled = false; @@ -466,10 +453,10 @@ define([ return; } - var fillProperty = plane.fill; + var fillProperty = planeGraphics.fill; var fillEnabled = defined(fillProperty) && fillProperty.isConstant ? fillProperty.getValue(Iso8601.MINIMUM_VALUE) : true; - var outlineProperty = plane.outline; + var outlineProperty = planeGraphics.outline; var outlineEnabled = defined(outlineProperty); if (outlineEnabled && outlineProperty.isConstant) { outlineEnabled = outlineProperty.getValue(Iso8601.MINIMUM_VALUE); @@ -484,13 +471,12 @@ define([ return; } - var normal = plane.normal; - var distance = plane.distance; - var dimensions = plane.dimensions; + var plane = planeGraphics.plane; + var dimensions = planeGraphics.dimensions; var position = entity.position; - var show = plane.show; - if (!defined(normal) || !defined(distance) || !defined(position) || (defined(show) && show.isConstant && !show.getValue(Iso8601.MINIMUM_VALUE))) { + var show = planeGraphics.show; + if (!defined(plane) || !defined(position) || (defined(show) && show.isConstant && !show.getValue(Iso8601.MINIMUM_VALUE))) { if (this._fillEnabled || this._outlineEnabled) { this._fillEnabled = false; this._outlineEnabled = false; @@ -499,25 +485,24 @@ define([ return; } - var material = defaultValue(plane.material, defaultMaterial); + var material = defaultValue(planeGraphics.material, defaultMaterial); var isColorMaterial = material instanceof ColorMaterialProperty; this._materialProperty = material; this._fillProperty = defaultValue(fillProperty, defaultFill); this._showProperty = defaultValue(show, defaultShow); - this._showOutlineProperty = defaultValue(plane.outline, defaultOutline); - this._outlineColorProperty = outlineEnabled ? defaultValue(plane.outlineColor, defaultOutlineColor) : undefined; - this._shadowsProperty = defaultValue(plane.shadows, defaultShadows); - this._distanceDisplayConditionProperty = defaultValue(plane.distanceDisplayCondition, defaultDistanceDisplayCondition); + this._showOutlineProperty = defaultValue(planeGraphics.outline, defaultOutline); + this._outlineColorProperty = outlineEnabled ? defaultValue(planeGraphics.outlineColor, defaultOutlineColor) : undefined; + this._shadowsProperty = defaultValue(planeGraphics.shadows, defaultShadows); + this._distanceDisplayConditionProperty = defaultValue(planeGraphics.distanceDisplayCondition, defaultDistanceDisplayCondition); - var outlineWidth = plane.outlineWidth; + var outlineWidth = planeGraphics.outlineWidth; this._fillEnabled = fillEnabled; this._outlineEnabled = outlineEnabled; if (!position.isConstant || // !Property.isConstant(entity.orientation) || // - !normal.isConstant || // - !distance.isConstant || // + !plane.isConstant || // !dimensions.isConstant || // !Property.isConstant(outlineWidth)) { if (!this._dynamic) { @@ -527,8 +512,7 @@ define([ } else { var options = this._options; options.vertexFormat = isColorMaterial ? PerInstanceColorAppearance.VERTEX_FORMAT : MaterialAppearance.MaterialSupport.TEXTURED.vertexFormat; - options.normal = normal.getValue(Iso8601.MINIMUM_VALUE, options.normal); - options.distance = distance.getValue(Iso8601.MINIMUM_VALUE, options.distance); + options.plane = plane.getValue(Iso8601.MINIMUM_VALUE, options.plane); options.dimensions = dimensions.getValue(Iso8601.MINIMUM_VALUE, options.dimensions); this._outlineWidth = defined(outlineWidth) ? outlineWidth.getValue(Iso8601.MINIMUM_VALUE) : 1.0; this._dynamic = false; @@ -583,25 +567,23 @@ define([ var geometryUpdater = this._geometryUpdater; var entity = geometryUpdater._entity; - var plane = entity.plane; - if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(plane.show, time, true)) { + var planeGraphics = entity.plane; + if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(planeGraphics.show, time, true)) { return; } var options = this._options; var modelMatrix = entity.computeModelMatrix(time); - var normal = Property.getValueOrDefault(plane.normal, time, options.normal); - var distance = Property.getValueOrUndefined(plane.distance, time, options.distance); - var dimensions = Property.getValueOrUndefined(plane.dimensions, time, options.dimensions); - if (!defined(modelMatrix) || !defined(normal) || !defined(distance) || !defined(dimensions)) { + var plane = Property.getValueOrDefault(planeGraphics.plane, time, options.plane); + var dimensions = Property.getValueOrUndefined(planeGraphics.dimensions, time, options.dimensions); + if (!defined(modelMatrix) || !defined(plane) || !defined(dimensions)) { return; } - options.normal = normal; - options.distance = distance; + options.plane = plane; options.dimensions = dimensions; - modelMatrix = createPrimitiveMatrix(normal, distance, dimensions, modelMatrix, modelMatrix); + modelMatrix = createPrimitiveMatrix(plane.normal, plane.distance, dimensions, modelMatrix, modelMatrix); var shadows = this._geometryUpdater.shadowsProperty.getValue(time); @@ -609,7 +591,7 @@ define([ var distanceDisplayCondition = distanceDisplayConditionProperty.getValue(time); var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition); - if (Property.getValueOrDefault(plane.fill, time, true)) { + if (Property.getValueOrDefault(planeGraphics.fill, time, true)) { var material = MaterialProperty.getValue(time, geometryUpdater.fillMaterialProperty, this._material); this._material = material; @@ -635,11 +617,11 @@ define([ })); } - if (Property.getValueOrDefault(plane.outline, time, false)) { + if (Property.getValueOrDefault(planeGraphics.outline, time, false)) { options.vertexFormat = PerInstanceColorAppearance.VERTEX_FORMAT; - var outlineColor = Property.getValueOrClonedDefault(plane.outlineColor, time, Color.BLACK, scratchColor); - var outlineWidth = Property.getValueOrDefault(plane.outlineWidth, time, 1.0); + var outlineColor = Property.getValueOrClonedDefault(planeGraphics.outlineColor, time, Color.BLACK, scratchColor); + var outlineWidth = Property.getValueOrDefault(planeGraphics.outlineWidth, time, 1.0); var translucent = outlineColor.alpha !== 1.0; this._outlinePrimitive = primitives.add(new Primitive({ diff --git a/Source/DataSources/PlaneGraphics.js b/Source/DataSources/PlaneGraphics.js index 3aac0ea91e95..59348494fea5 100644 --- a/Source/DataSources/PlaneGraphics.js +++ b/Source/DataSources/PlaneGraphics.js @@ -23,7 +23,8 @@ define([ * @constructor * * @param {Object} [options] Object with the following properties: - * @param {Property} [options.dimensions] A {@link Cartesian3} Property specifying the length, width, and height of the box. + * @param {Property} [options.plane] A {@link Plane} Property specifying the normal and distance for the plane. + * @param {Property} [options.dimensions] A {@link Cartesian2} Property specifying the width and height of the plane. * @param {Property} [options.show=true] A boolean Property specifying the visibility of the box. * @param {Property} [options.fill=true] A boolean Property specifying whether the box is filled with the provided material. * @param {MaterialProperty} [options.material=Color.WHITE] A Property specifying the material used to fill the box. @@ -36,11 +37,8 @@ define([ * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Box.html|Cesium Sandcastle Box Demo} */ function PlaneGraphics(options) { - this._normal = undefined; - this._normalSubscription = undefined; - this._distance = undefined; - this._distanceSubscription = undefined; - this._dimensions = undefined; + this._plane = undefined; + this._planeSubscription = undefined; this._dimensionsSubscription = undefined; this._show = undefined; this._showSubscription = undefined; @@ -84,8 +82,20 @@ define([ */ show : createPropertyDescriptor('show'), - normal : createPropertyDescriptor('normal'), - distance : createPropertyDescriptor('distance'), + /** + * Gets or sets the {@link Plane} Property specifying the normal and distance of the plane. + * + * @memberof PlaneGraphics.prototype + * @type {Property} + */ + plane : createPropertyDescriptor('plane'), + + /** + * Gets or sets the {@link Cartesian2} Property specifying the width and height of the plane. + * + * @memberof PlaneGraphics.prototype + * @type {Property} + */ dimensions : createPropertyDescriptor('dimensions'), /** @@ -155,8 +165,7 @@ define([ if (!defined(result)) { return new PlaneGraphics(this); } - result.normal = this.normal; - result.distance = this.distance; + result.plane = this.plane; result.dimensions = this.dimensions; result.show = this.show; result.material = this.material; @@ -182,8 +191,7 @@ define([ } //>>includeEnd('debug'); - this.normal = defaultValue(this.normal, source.normal); - this.distance = defaultValue(this.distance, source.distance); + this.plane = defaultValue(this.plane, source.plane); this.dimensions = defaultValue(this.dimensions, source.dimensions); this.show = defaultValue(this.show, source.show); this.material = defaultValue(this.material, source.material); diff --git a/Source/Scene/Batched3DModel3DTileContent.js b/Source/Scene/Batched3DModel3DTileContent.js index 86bed8370b29..27e1c0403b77 100644 --- a/Source/Scene/Batched3DModel3DTileContent.js +++ b/Source/Scene/Batched3DModel3DTileContent.js @@ -450,10 +450,12 @@ define([ this._model.debugWireframe = this._tileset.debugWireframe; // Update clipping planes - var modelClippingPlanes = this._model.clippingPlanes.planes; var tilesetClippingPlanes = this._tileset.clippingPlanes; - tilesetClippingPlanes.clone(modelClippingPlanes); - modelClippingPlanes.enabled = tilesetClippingPlanes.enabled && this._tile._isClipped; + if (defined(tilesetClippingPlanes)) { + var modelClippingPlanes = this._model.clippingPlanes.planes; + tilesetClippingPlanes.clone(modelClippingPlanes); + modelClippingPlanes.enabled = tilesetClippingPlanes.enabled && this._tile._isClipped; + } this._model.update(frameState); diff --git a/Source/Scene/ClippingPlanesCollection.js b/Source/Scene/ClippingPlanesCollection.js index 8e7b954809a7..038d6b1e1415 100644 --- a/Source/Scene/ClippingPlanesCollection.js +++ b/Source/Scene/ClippingPlanesCollection.js @@ -39,7 +39,7 @@ define([ * @type {Plane} * @default [] */ - this.planes = defaultValue(options.planes, []); // TODO don't instantiate? + this.planes = defaultValue(options.planes, []); /** * Determines whether the clipping planes are active. @@ -82,6 +82,7 @@ define([ */ this.edgeWidth = defaultValue(options.edgeWidth, 0.0); + this._geometries = undefined; } var scratchPlane = new Plane(Cartesian3.UNIT_X, 0.0); diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js index 84038881c48e..7f163d211d91 100644 --- a/Source/Scene/Model.js +++ b/Source/Scene/Model.js @@ -3361,7 +3361,7 @@ define([ function createClippingPlanesEdgeColorFunction(model) { return function() { if (!defined(model.clippingPlanes)) { - return Cartesian4(); + return scratchCartesian; } return Cartesian4.fromColor(model.clippingPlanes.edgeColor, scratchCartesian); From 6d67925820dbfe3ac2af0428e241766682d60d82 Mon Sep 17 00:00:00 2001 From: ggetz Date: Mon, 20 Nov 2017 13:13:23 -0500 Subject: [PATCH 12/37] Optimize globe tile loading with clipping planes --- Source/Scene/Cesium3DTile.js | 26 ++++++++----- Source/Scene/ClippingPlanesCollection.js | 47 +++++++++++++++++++++++- Source/Scene/GlobeSurfaceTile.js | 1 + Source/Scene/GlobeSurfaceTileProvider.js | 30 ++++++++++----- 4 files changed, 82 insertions(+), 22 deletions(-) diff --git a/Source/Scene/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js index dfe40de97a62..6eb6ad2af9b7 100644 --- a/Source/Scene/Cesium3DTile.js +++ b/Source/Scene/Cesium3DTile.js @@ -744,10 +744,10 @@ define([ var scratchPlane = new Plane(Cartesian3.UNIT_X, 0.0); function checkTileClipped(tile, boundingVolume) { var clippingPlanes = tile._tileset.clippingPlanes; - if (defined(clippingPlanes)) { + if (defined(clippingPlanes) && clippingPlanes.enabled) { var planes = clippingPlanes.planes; var length = planes.length; - var rootTransform = tile._tileset._root.computedTransform; + var rootTransform = tile._tileset._root.computedTransform; // TODO, factor in inclusive/exclusive, transform for (var i = 0; i < length; ++i) { var plane = planes[i]; Plane.transform(plane, rootTransform, scratchPlane); @@ -775,10 +775,13 @@ define([ var cullingVolume = frameState.cullingVolume; var boundingVolume = getBoundingVolume(this, frameState); - if (this._tileset.clippingPlanesEnabled) { - var clipped = checkTileClipped(this, boundingVolume); - this._isClipped = (clipped !== Intersect.INSIDE); - if (clipped === Intersect.OUTSIDE) { + var tileset = this._tileset; + var clippingPlanes = tileset.clippingPlanes; + if (defined(clippingPlanes) && clippingPlanes.enabled) { + var tileTransform = tileset._root.computedTransform; + var intersection = clippingPlanes.computeIntersectionWithBoundingVolume(boundingVolume, tileTransform); + this._isClipped = (intersection !== Intersect.INSIDE); + if (intersection === Intersect.OUTSIDE) { return CullingVolume.MASK_OUTSIDE; } } @@ -807,10 +810,13 @@ define([ var cullingVolume = frameState.cullingVolume; var boundingVolume = getContentBoundingVolume(this, frameState); - if (this._tileset.clippingPlanesEnabled) { - var clipped = checkTileClipped(this, boundingVolume); - this._isClipped = (clipped !== Intersect.INSIDE); - if (clipped === Intersect.OUTSIDE) { + var tileset = this._tileset; + var clippingPlanes = tileset.clippingPlanes; + if (defined(clippingPlanes) && clippingPlanes.enabled) { + var tileTransform = tileset._root.computedTransform; + var intersection = clippingPlanes.computeIntersectionWithBoundingVolume(boundingVolume, tileTransform); + this._isClipped = (intersection !== Intersect.INSIDE); + if (intersection === Intersect.OUTSIDE) { return Intersect.OUTSIDE; } } diff --git a/Source/Scene/ClippingPlanesCollection.js b/Source/Scene/ClippingPlanesCollection.js index 038d6b1e1415..6d7bdafe241c 100644 --- a/Source/Scene/ClippingPlanesCollection.js +++ b/Source/Scene/ClippingPlanesCollection.js @@ -6,6 +6,7 @@ define([ '../Core/Color', '../Core/defaultValue', '../Core/defined', + '../Core/Intersect', '../Core/Matrix4', '../Core/Plane' ], function( @@ -16,6 +17,7 @@ define([ Color, defaultValue, defined, + Intersect, Matrix4, Plane) { 'use strict'; @@ -81,8 +83,6 @@ define([ * @default 0.0 */ this.edgeWidth = defaultValue(options.edgeWidth, 0.0); - - this._geometries = undefined; } var scratchPlane = new Plane(Cartesian3.UNIT_X, 0.0); @@ -138,5 +138,48 @@ define([ return result; }; + /** + * Determines the type intersection with the planes of this bounding collection and the specified {@link BoundingVolume}. + * + * @param {BoundingVolume} boundingVolume The volume to determine the intersection with the planes. + * @param {Matrix4} [parentTransform] An additional matrix to transform the plane to world coordinates. + * @returns {Intersect} {@link Intersect.INSIDE} if the entire volume is on the side of the planes + * the normal is pointing and should be entirely rendered, {@link Intersect.OUTSIDE} + * if the entire volume is on the opposite side and should be clipped, and + * {@link Intersect.INTERSECTING} if the volume intersects the planes. + */ + ClippingPlanesCollection.prototype.computeIntersectionWithBoundingVolume = function (boundingVolume, parentTransform) { + var planes = this.planes; + var length = planes.length; + + var transformation = this.transformationMatrix; + if (defined(parentTransform)) { + transformation = Matrix4.multiply(transformation, parentTransform, scratchMatrix); + } + + // If the clipping planes are inclusive, the volume must be outside of all planes to be considered completely + // clipped. Otherwise, if the volume can be outside any the planes, it is considered completely clipped. + // Lastly, if not completely clipped, if any plane is intersecting, more calculations must be performed. + var intersection = Intersect.INSIDE; + if (this.inclusive && length > 0) { + intersection = Intersect.OUTSIDE; + } + for (var i = 0; i < length; ++i) { + var plane = planes[i]; + + Plane.transform(plane, transformation, scratchPlane); + + var value = boundingVolume.intersectPlane(scratchPlane); + if (value === Intersect.INTERSECTING) { + intersection = value; + } else if ((this.inclusive && value === Intersect.INSIDE) || + (!this.inclusive && value === Intersect.OUTSIDE)) { + return value; + } + } + + return intersection; + }; + return ClippingPlanesCollection; }); diff --git a/Source/Scene/GlobeSurfaceTile.js b/Source/Scene/GlobeSurfaceTile.js index a976e41f53c2..22aad038b2b4 100644 --- a/Source/Scene/GlobeSurfaceTile.js +++ b/Source/Scene/GlobeSurfaceTile.js @@ -80,6 +80,7 @@ define([ this.pickTerrain = undefined; this.surfaceShader = undefined; + this.isClipped = true; } defineProperties(GlobeSurfaceTile.prototype, { diff --git a/Source/Scene/GlobeSurfaceTileProvider.js b/Source/Scene/GlobeSurfaceTileProvider.js index 009c4f61a769..ce6c2fce8548 100644 --- a/Source/Scene/GlobeSurfaceTileProvider.js +++ b/Source/Scene/GlobeSurfaceTileProvider.js @@ -507,6 +507,15 @@ define([ } } + var clippingPlanes = this.clippingPlanes; + if (defined(clippingPlanes) && clippingPlanes.enabled) { + var planeIntersection = clippingPlanes.computeIntersectionWithBoundingVolume(boundingVolume); + tile.isClipped = (planeIntersection !== Intersect.INSIDE); + if (planeIntersection === Intersect.OUTSIDE) { + return Intersect.OUTSIDE; + } + } + var intersection = cullingVolume.computeVisibility(boundingVolume); if (intersection === Intersect.OUTSIDE) { return Visibility.NONE; @@ -1290,17 +1299,19 @@ define([ Matrix4.clone(encoding.matrix, uniformMapProperties.scaleAndBias); // update clipping planes - var length = 0; - var clippingPlanes = tileProvider.clippingPlanes; + if (tile.isClipped) { + var length = 0; + var clippingPlanes = tileProvider.clippingPlanes; - if (defined(clippingPlanes)) { - length = clippingPlanes.planes.length; - } - if (length !== uniformMapProperties.clippingPlanes.length) { - uniformMapProperties.clippingPlanes = new Array(length); + if (defined(clippingPlanes)) { + length = clippingPlanes.planes.length; + } + if (length !== uniformMapProperties.clippingPlanes.length) { + uniformMapProperties.clippingPlanes = new Array(length); - for (var i = 0; i < length; ++i) { - uniformMapProperties.clippingPlanes[i] = new Cartesian4(); + for (var i = 0; i < length; ++i) { + uniformMapProperties.clippingPlanes[i] = new Cartesian4(); + } } } @@ -1311,7 +1322,6 @@ define([ uniformMapProperties.clippingPlanesEdgeWidth = clippingPlanes.edgeWidth; } - // TODO: Optimization, determine if this tile is entirely clipped or entirely visible var clippingPlanesEnabled = defined(clippingPlanes) && clippingPlanes.enabled && (uniformMapProperties.clippingPlanes.length > 0); command.shaderProgram = tileProvider._surfaceShaderSet.getShaderProgram(frameState, surfaceTile, numberOfDayTextures, applyBrightness, applyContrast, applyHue, applySaturation, applyGamma, applyAlpha, applySplit, showReflectiveOcean, showOceanWaves, tileProvider.enableLighting, hasVertexNormals, useWebMercatorProjection, applyFog, clippingPlanesEnabled); From 086a03b1b90bda88f29ac32577293d74e9cd15b8 Mon Sep 17 00:00:00 2001 From: ggetz Date: Tue, 21 Nov 2017 13:09:05 -0500 Subject: [PATCH 13/37] Cleanup docs --- Source/DataSources/ModelGraphics.js | 5 ++--- Source/Scene/Cesium3DTileset.js | 4 ++-- Source/Scene/ClippingPlanesCollection.js | 17 +++++++++++------ Source/Scene/Model.js | 4 ++-- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/Source/DataSources/ModelGraphics.js b/Source/DataSources/ModelGraphics.js index 90af4ab0a59a..e0e4ec7d2b10 100644 --- a/Source/DataSources/ModelGraphics.js +++ b/Source/DataSources/ModelGraphics.js @@ -54,7 +54,7 @@ define([ * @param {Property} [options.color=Color.WHITE] A Property specifying the {@link Color} that blends with the model's rendered color. * @param {Property} [options.colorBlendMode=ColorBlendMode.HIGHLIGHT] An enum Property specifying how the color blends with the model. * @param {Property} [options.colorBlendAmount=0.5] A numeric Property specifying the color strength when the colorBlendMode is MIX. A value of 0.0 results in the model's rendered color while a value of 1.0 results in a solid color, with any value in-between resulting in a mix of the two. - * @param {Property} [options.clippingPlanes] A property specifying an array of up to 6 {@link Plane} used to selectively disable rendering on the outside of each plane. + * @param {Property} [options.clippingPlanes] A property specifying the {@link ClippingPlanesCollection} used to selectively disable rendering the model. * * @see {@link http://cesiumjs.org/2014/03/03/Cesium-3D-Models-Tutorial/|3D Models Tutorial} * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=3D%20Models.html|Cesium Sandcastle 3D Models Demo} @@ -248,10 +248,9 @@ define([ colorBlendAmount : createPropertyDescriptor('colorBlendAmount'), /** - * A property specifying an array of up to 6 {@link Plane} used to selectively disable rendering on the outside of each plane. + * A property specifying the {@link ClippingPlanesCollection} used to selectively disable rendering the model. * @memberof ModelGraphics.prototype * @type {Property} - * @default [] */ clippingPlanes: createPropertyDescriptor('clippingPlanes') }); diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index 3194f7463051..2a88231f0dc1 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -105,7 +105,7 @@ define([ * @param {Number} [options.skipLevels=1] When skipLevelOfDetail is true, a constant defining the minimum number of levels to skip when loading tiles. When it is 0, no levels are skipped. Used in conjunction with skipScreenSpaceErrorFactor to determine which tiles to load. * @param {Boolean} [options.immediatelyLoadDesiredLevelOfDetail=false] When skipLevelOfDetail is true, only tiles that meet the maximum screen space error will ever be downloaded. Skipping factors are ignored and just the desired tiles are loaded. * @param {Boolean} [options.loadSiblings=false] When skipLevelOfDetail is true, determines whether siblings of visible tiles are always downloaded during traversal. - * @param {ClippingPlaneCollection} [options.clippingPlanes] A property specifying an array of up to 6 {@link Plane} used to selectively disable rendering on the outside of each plane. + * @param {ClippingPlaneCollection} [options.clippingPlanes] The {@link ClippingPlanesCollection} used to selectively disable rendering the tileset. * @param {Boolean} [options.debugFreezeFrame=false] For debugging only. Determines if only the tiles from last frame should be used for rendering. * @param {Boolean} [options.debugColorizeTiles=false] For debugging only. When true, assigns a random color to each tile. * @param {Boolean} [options.debugWireframe=false] For debugging only. When true, render's each tile's content as a wireframe. @@ -524,7 +524,7 @@ define([ this.loadSiblings = defaultValue(options.loadSiblings, false); /** - * A property specifying an array of up to 6 {@link Plane} used to selectively disable rendering on the outside of each plane. + * The {@link ClippingPlanesCollection} used to selectively disable rendering the tileset. * * @type {ClippingPlanesCollection} */ diff --git a/Source/Scene/ClippingPlanesCollection.js b/Source/Scene/ClippingPlanesCollection.js index 6d7bdafe241c..2a813aa706d3 100644 --- a/Source/Scene/ClippingPlanesCollection.js +++ b/Source/Scene/ClippingPlanesCollection.js @@ -29,8 +29,13 @@ define([ * @alias ClippingPlanesCollection * @constructor * - * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] Determines the size and shape of the - * globe. + * @param {Object} [options] Object with the following properties: + * @param {Plane[]} [options.planes=[]] An array of up to 6 {@link Plane} used to selectively disable rendering on the outside of each plane. + * @param {Boolean} [options.enabled=true] Determines whether the clipping planes are active. + * @param {Matrix4} [options.transformationMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix specifying an additional transform relative to the clipping planes original coordinate system. + * @param {Boolean} [options.inclusive=true] If true, the region to be clipped must be included in all planes in this collection. Otherwise, a region will be clipped if included in any plane in the collection. + * @param {Color} [options.edgeColor=Color.WHITE] The color applied to hughlight the edge along which an object is clipped. + * @param {Number} [options.edgeWidth=0.0] The width of the highlight applied to the edge along which an object is clipped. */ function ClippingPlanesCollection(options) { var options = defaultValue(options, defaultValue.EMPTY_OBJECT); @@ -52,7 +57,7 @@ define([ this.enabled = defaultValue(options.enabled, true); /** - * A transformation matrix specifying the coordinate system for this collection. + * The 4x4 transformation matrix specifying an additional transform relative to the clipping planes original coordinate system. * * @type {Matrix4} * @default Matrix4.IDENTITY @@ -69,7 +74,7 @@ define([ this.inclusive = defaultValue(options.inclusive, true); /** - * The color of the highlighted clipped edge. + * The color applied to hughlight the edge along which an object is clipped. * * @type {Color} * @default Color.WHITE @@ -77,7 +82,7 @@ define([ this.edgeColor = defaultValue(options.edgeColor, Color.WHITE); /** - * The width of the clipped edge to highlight. + * The width of the highlight applied to the edge along which an object is clipped. * * @type {Number} * @default 0.0 @@ -142,7 +147,7 @@ define([ * Determines the type intersection with the planes of this bounding collection and the specified {@link BoundingVolume}. * * @param {BoundingVolume} boundingVolume The volume to determine the intersection with the planes. - * @param {Matrix4} [parentTransform] An additional matrix to transform the plane to world coordinates. + * @param {Matrix4} [parentTransform] An optional, additional matrix to transform the plane to world coordinates. * @returns {Intersect} {@link Intersect.INSIDE} if the entire volume is on the side of the planes * the normal is pointing and should be entirely rendered, {@link Intersect.OUTSIDE} * if the entire volume is on the opposite side and should be clipped, and diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js index 7f163d211d91..37602006092c 100644 --- a/Source/Scene/Model.js +++ b/Source/Scene/Model.js @@ -341,7 +341,7 @@ define([ * @param {Number} [options.colorBlendAmount=0.5] Value used to determine the color strength when the colorBlendMode is MIX. A value of 0.0 results in the model's rendered color while a value of 1.0 results in a solid color, with any value in-between resulting in a mix of the two. * @param {Color} [options.silhouetteColor=Color.RED] The silhouette color. If more than 256 models have silhouettes enabled, there is a small chance that overlapping models will have minor artifacts. * @param {Number} [options.silhouetteSize=0.0] The size of the silhouette in pixels. - * @param {ClippingPlanesCollection} [options.clippingPlanes] An array of {@link Plane} used to clip the model. + * @param {ClippingPlanesCollection} [options.clippingPlanes] The {@link ClippingPlanesCollection} used to selectively disable rendering the model. * * @exception {DeveloperError} bgltf is not a valid Binary glTF file. * @exception {DeveloperError} Only glTF Binary version 1 is supported. @@ -580,7 +580,7 @@ define([ this.colorBlendAmount = defaultValue(options.colorBlendAmount, 0.5); /** - * A property specifying an array of up to 6 {@link Plane} used to selectively disable rendering on the outside of each plane. + * The {@link ClippingPlanesCollection} used to selectively disable rendering the model. * * @type {ClippingPlanesCollection} */ From af21899f9940a08114b01eeb960dd39234f7c720 Mon Sep 17 00:00:00 2001 From: ggetz Date: Tue, 21 Nov 2017 13:12:43 -0500 Subject: [PATCH 14/37] Updated CHANGES.md --- CHANGES.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 67d968efe6d8..918d68eb3014 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,7 +3,8 @@ Change Log ## TODO release -* Added `clippingPlanes` property to `ModelGraphics` and `Cesium3DTileset`, which specifies an array of planes to clip the object. [TODO]() +* Added `clippingPlanes` property to `ModelGraphics` and `Cesium3DTileset`, which specifies a `ClippingPlanesCollection` to clip the object. [TODO]() +* Added `terrainClippingPlanes` property to `Globe` which specifies a `ClippingPlanesCollection` to clip the terrain. * Added `Plane.transformPlane` function to apply a transformation to a plane. [TODO]() * Added `PlaneGeometry` and `PlaneOutlineGeometry` primitives * Added `PlaneGraphics` and `plane` property to `Entity`. From 748784749f2f98f9bbabf95804eabc579aa20f65 Mon Sep 17 00:00:00 2001 From: ggetz Date: Tue, 21 Nov 2017 13:25:47 -0500 Subject: [PATCH 15/37] Tweak doc --- Source/Scene/ClippingPlanesCollection.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/Scene/ClippingPlanesCollection.js b/Source/Scene/ClippingPlanesCollection.js index 2a813aa706d3..8b06f0969089 100644 --- a/Source/Scene/ClippingPlanesCollection.js +++ b/Source/Scene/ClippingPlanesCollection.js @@ -23,8 +23,7 @@ define([ 'use strict'; /** - * The globe rendered in the scene, including its terrain ({@link ClippingPlanesCollection#terrainProvider}) - * and imagery layers ({@link ClippingPlanesCollection#imageryLayers}). Access the globe using {@link Scene#globe}. + * Contains the options to specify a set of clipping planes. Clipping planes selectively disable rendering on an object on the outside of the specified list of {@link Plane}. * * @alias ClippingPlanesCollection * @constructor From 227698cc3d1a8421deb029837e8b751ffadaedf7 Mon Sep 17 00:00:00 2001 From: ggetz Date: Wed, 22 Nov 2017 15:31:43 -0500 Subject: [PATCH 16/37] Update Sandcastle example, fix sandcastle images --- Apps/Sandcastle/gallery/Clipping Planes.html | 103 +++++++++++++++--- Apps/Sandcastle/gallery/Clipping Planes.jpg | Bin 20989 -> 31284 bytes Apps/Sandcastle/gallery/Terrain Clipping.html | 8 +- Apps/Sandcastle/gallery/Terrain Clipping.jpg | Bin 0 -> 32991 bytes Source/DataSources/ModelVisualizer.js | 3 +- Source/Scene/ClippingPlanesCollection.js | 10 +- Source/Scene/Model.js | 33 ++---- Source/Scene/PointCloud3DTileContent.js | 92 +++++++++++----- 8 files changed, 165 insertions(+), 84 deletions(-) create mode 100644 Apps/Sandcastle/gallery/Terrain Clipping.jpg diff --git a/Apps/Sandcastle/gallery/Clipping Planes.html b/Apps/Sandcastle/gallery/Clipping Planes.html index 425dab024016..73e04286bf59 100644 --- a/Apps/Sandcastle/gallery/Clipping Planes.html +++ b/Apps/Sandcastle/gallery/Clipping Planes.html @@ -36,6 +36,7 @@

Loading...

+ Show debug boundingVolume
@@ -49,14 +50,15 @@ }); var scene = viewer.scene; -var planeScale = new Cesium.Cartesian2(500.0, 500.0); - var defaultClippingPlanes = [ new Cesium.Plane(new Cesium.Cartesian3(0.0, 0.0,-1.0), -100.0) ]; +var clipObjects = ['BIM', 'Point Cloud', 'Model']; var viewModel = { - debugBoundingVolumesEnabled: false + debugBoundingVolumesEnabled : false, + exampleTypes : clipObjects, + currentExampleType : clipObjects[0] }; var targetY = 0.0; @@ -94,17 +96,17 @@ moveHandler.setInputAction(function(movement) { if (Cesium.defined(selectedPlane)) { var deltaY = movement.endPosition.y - movement.startPosition.y; - var screenScale = scene.camera.getPixelSize(tileset.boundingSphere, scene.drawingBufferWidth, scene.drawingBufferHeight); - targetY += deltaY * screenScale; + //var screenScale = scene.camera.getPixelSize(tileset.boundingSphere, scene.drawingBufferWidth, scene.drawingBufferHeight); + targetY += deltaY;// * screenScale; } }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); var scratchPlane = new Cesium.Plane(Cesium.Cartesian3.UNIT_X, 0.0); -function createPlaneUpdateFunction(tileset, plane) { +function createPlaneUpdateFunction(plane, transform) { return function () { plane.distance = Cesium.Math.lerp(plane.distance, targetY, 0.1); - var transformedPlane = Cesium.Plane.transform(plane, tileset.modelMatrix, scratchPlane); + var transformedPlane = Cesium.Plane.transform(plane, transform, scratchPlane); transformedPlane.distance = -transformedPlane.distance; return transformedPlane; }; @@ -112,37 +114,37 @@ var tileset; function loadTileset(url) { - reset(); - tileset = viewer.scene.primitives.add(new Cesium.Cesium3DTileset({ url : url, clippingPlanes : new Cesium.ClippingPlanesCollection({ planes : defaultClippingPlanes, edgeWidth: 1.0, edgeColor: Cesium.Color.CYAN - }), - modelMatrix : Cesium.Matrix4.fromUniformScale(2.0) + }) })); tileset.readyPromise.then(function() { var boundingSphere = tileset.boundingSphere; - viewer.camera.viewBoundingSphere(boundingSphere, new Cesium.HeadingPitchRange(0.5, -0.2, boundingSphere.radius * 4.0)); + var radius = boundingSphere.radius; + + viewer.camera.viewBoundingSphere(boundingSphere, new Cesium.HeadingPitchRange(0.5, -0.2, radius * 4.0)); viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY); for (var i = 0; i < defaultClippingPlanes.length; ++i) { - var plane = tileset.clippingPlanes.planes[i]; - var entity = viewer.entities.add({ + var clippingPlanes = tileset.clippingPlanes; + var plane = clippingPlanes.planes[i]; + var planeEntity = viewer.entities.add({ position : boundingSphere.center, plane : { - dimensions : planeScale, + dimensions : new Cesium.Cartesian2(radius * 2.5, radius * 2.5), material : Cesium.Color.CYAN.withAlpha(0.1), - plane: new Cesium.CallbackProperty(createPlaneUpdateFunction(tileset, plane), false), + plane: new Cesium.CallbackProperty(createPlaneUpdateFunction(plane, tileset.modelMatrix), false), outline: true, outlineColor: Cesium.Color.CYAN - }, + } }); - planeEntities.push(entity); + planeEntities.push(planeEntity); } }).otherwise(function(error) { @@ -152,8 +154,58 @@ tileset.debugShowBoundingVolume = viewModel.debugBoundingVolumesEnabled; } +var modelEntity; +function loadModel(url) { + var position = Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706, 100.0); + var heading = Cesium.Math.toRadians(135.0); + var pitch = 0.0; + var roll = 0.0; + var hpr = new Cesium.HeadingPitchRoll(heading, pitch, roll); + var orientation = Cesium.Transforms.headingPitchRollQuaternion(position, hpr); + var entity = viewer.entities.add({ + name : url, + position : position, + orientation : orientation, + model : { + uri : url, + scale: 8, + minimumPixelSize: 100.0, + clippingPlanes : new Cesium.ClippingPlanesCollection({ + planes : defaultClippingPlanes, + edgeWidth: 1.0, + edgeColor: Cesium.Color.CYAN, + transformationMatrix: Cesium.Matrix4.fromRotationTranslation(Cesium.Matrix3.fromRotationX(-Cesium.Math.PI_OVER_TWO)) + }) + } + }); + + viewer.trackedEntity = entity; + modelEntity = entity.model; + + console.log(modelEntity); + + for (var i = 0; i < defaultClippingPlanes.length; ++i) { + var clippingPlanes = tileset.clippingPlanes; + var plane = clippingPlanes.planes[i]; + var planeEntity = viewer.entities.add({ + position : position, + plane : { + dimensions : new Cesium.Cartesian2(300.0, 300.0), + material : Cesium.Color.CYAN.withAlpha(0.1), + plane: new Cesium.CallbackProperty(createPlaneUpdateFunction(plane, Cesium.Matrix4.IDENTITY), false), + outline: true, + outlineColor: Cesium.Color.CYAN + } + }); + + planeEntities.push(planeEntity); + } +} + // Power Plant design model provided by Bentley Systems var bimUrl = 'https://beta.cesium.com/api/assets/1459?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIzNjUyM2I5Yy01YmRhLTQ0MjktOGI0Zi02MDdmYzBjMmY0MjYiLCJpZCI6NDQsImFzc2V0cyI6WzE0NTldLCJpYXQiOjE0OTkyNjQ3ODF9.SW_rwY-ic0TwQBeiweXNqFyywoxnnUBtcVjeCmDGef4'; +var pointCloudUrl = 'https://beta.cesium.com/api/assets/1460?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIyMzk2YzJiOS1jZGFmLTRlZmYtYmQ4MS00NTA3NjEwMzViZTkiLCJpZCI6NDQsImFzc2V0cyI6WzE0NjBdLCJpYXQiOjE0OTkyNjQ3NTV9.oWjvN52CRQ-dk3xtvD4e8ZnOHZhoWSpJLlw115mbQJM'; +var modelUrl = '../../SampleData/models/CesiumAir/Cesium_Air.glb'; loadTileset(bimUrl); @@ -162,6 +214,21 @@ Cesium.knockout.track(viewModel); Cesium.knockout.applyBindings(viewModel, toolbar); +Cesium.knockout.getObservable(viewModel, 'currentExampleType').subscribe(function(newValue) { + reset(); + + if (newValue === clipObjects[0]) { + loadTileset(bimUrl); + } else if (newValue === clipObjects[1]) { + loadTileset(pointCloudUrl); + tileset.readyPromise.then(function () { + tileset.clippingPlanes.transformationMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(tileset.boundingSphere.center); + }); + } else { + loadModel(modelUrl); + } +}); + Cesium.knockout.getObservable(viewModel, 'debugBoundingVolumesEnabled').subscribe(function(newValue) { if (Cesium.defined(tileset)) { tileset.debugShowBoundingVolume = newValue; diff --git a/Apps/Sandcastle/gallery/Clipping Planes.jpg b/Apps/Sandcastle/gallery/Clipping Planes.jpg index b8b63c0307b4379fb9d5e39d76e43f74155e99c3..a5098bed32e4437941cd6bfe2e69efefd13bb3bd 100644 GIT binary patch delta 30805 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 delta 20429 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 ao2$tRvXt#=smdzWcU-cIPfzBzk^k9Zc5%i4 diff --git a/Apps/Sandcastle/gallery/Terrain Clipping.html b/Apps/Sandcastle/gallery/Terrain Clipping.html index ed2a15cd94ac..4c382a1c3d04 100644 --- a/Apps/Sandcastle/gallery/Terrain Clipping.html +++ b/Apps/Sandcastle/gallery/Terrain Clipping.html @@ -43,16 +43,10 @@ transformationMatrix : Cesium.Matrix4.fromTranslation(new Cesium.Cartesian3(0.0, 0.0, 3000000.0)), planes : [ new Cesium.Plane(new Cesium.Cartesian3(0.0, 0.0, -1.0), 0.0), - new Cesium.Plane(new Cesium.Cartesian3(0.0, 0.0, 1.0), 100000.0) + new Cesium.Plane(new Cesium.Cartesian3(0.0, 0.0, 1.0), 1000000.0) ] }); -var planes = globe.terrainClippingPlanes.planes; -window.setInterval(function () { - planes[0].distance -= 50.0; - planes[1].distance += 50.0; -}, 30); - //Sandcastle_End Sandcastle.finishedLoading(); } diff --git a/Apps/Sandcastle/gallery/Terrain Clipping.jpg b/Apps/Sandcastle/gallery/Terrain Clipping.jpg new file mode 100644 index 0000000000000000000000000000000000000000..cb1bd06060d048db08ae0109ec7c89a98838cef8 GIT binary patch literal 32991 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 Of#<@!&Y+@zKmXa-8YwIQ literal 0 HcmV?d00001 diff --git a/Source/DataSources/ModelVisualizer.js b/Source/DataSources/ModelVisualizer.js index f878277f2983..e8055bf1af71 100644 --- a/Source/DataSources/ModelVisualizer.js +++ b/Source/DataSources/ModelVisualizer.js @@ -40,7 +40,6 @@ define([ var defaultColor = Color.WHITE; var defaultColorBlendMode = ColorBlendMode.HIGHLIGHT; var defaultColorBlendAmount = 0.5; - var defaultClippingPlanes = []; var modelMatrixScratch = new Matrix4(); var nodeMatrixScratch = new Matrix4(); @@ -153,7 +152,7 @@ define([ model.color = Property.getValueOrDefault(modelGraphics._color, time, defaultColor, model._color); model.colorBlendMode = Property.getValueOrDefault(modelGraphics._colorBlendMode, time, defaultColorBlendMode); model.colorBlendAmount = Property.getValueOrDefault(modelGraphics._colorBlendAmount, time, defaultColorBlendAmount); - model.clippingPlanes = Property.getValueOrDefault(modelGraphics._clippingPlanes, time, defaultClippingPlanes); + model.clippingPlanes = Property.getValueOrUndefined(modelGraphics._clippingPlanes, time); if (model.ready) { var runAnimations = Property.getValueOrDefault(modelGraphics._runAnimations, time, true); diff --git a/Source/Scene/ClippingPlanesCollection.js b/Source/Scene/ClippingPlanesCollection.js index 8b06f0969089..6e198a7615ae 100644 --- a/Source/Scene/ClippingPlanesCollection.js +++ b/Source/Scene/ClippingPlanesCollection.js @@ -61,7 +61,7 @@ define([ * @type {Matrix4} * @default Matrix4.IDENTITY */ - this.transformationMatrix = defaultValue(options.transformationMatrix, Matrix4.IDENTITY); + this.transformationMatrix = defaultValue(options.transformationMatrix, Matrix4.clone(Matrix4.IDENTITY)); /** * If true, the region to be clipped must be included in all planes in this collection. @@ -78,7 +78,7 @@ define([ * @type {Color} * @default Color.WHITE */ - this.edgeColor = defaultValue(options.edgeColor, Color.WHITE); + this.edgeColor = defaultValue(options.edgeColor, Color.clone(Color.WHITE)); /** * The width of the highlight applied to the edge along which an object is clipped. @@ -108,9 +108,9 @@ define([ var transform = Matrix4.multiply(viewMatrix, this.transformationMatrix, scratchMatrix); - for (var j = 0; j < length; ++j) { - var plane = planes[j]; - var packedPlane = array[j]; + for (var i = 0; i < length; ++i) { + var plane = planes[i]; + var packedPlane = array[i]; Plane.transform(plane, transform, scratchPlane); diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js index d43064e571e6..9007b8bd7ff6 100644 --- a/Source/Scene/Model.js +++ b/Source/Scene/Model.js @@ -2108,11 +2108,15 @@ define([ } var premultipliedAlpha = hasPremultipliedAlpha(model); - var blendFS = modifyShaderForColor(fs, premultipliedAlpha); - var clippingFS = modifyShaderForClippingPlanes(blendFS); + var finalFS = modifyShaderForColor(fs, premultipliedAlpha); + + var clippingPlanes = model.clippingPlanes; + if (defined(clippingPlanes) && clippingPlanes.enabled) { + finalFS = modifyShaderForClippingPlanes(finalFS); + } var drawVS = modifyShader(vs, id, model._vertexShaderLoaded); - var drawFS = modifyShader(clippingFS, id, model._fragmentShaderLoaded); + var drawFS = modifyShader(finalFS, id, model._fragmentShaderLoaded); model._rendererResources.programs[id] = ShaderProgram.fromCache({ context : context, @@ -3307,23 +3311,12 @@ define([ }; } - function createClippingPlanesEnabledFunction(model) { - return function() { - var clippingPlanes = model.clippingPlanes; - if (!defined(clippingPlanes)) { - return false; - } - - return clippingPlanes.enabled; - }; - } - function createClippingPlanesLengthFunction(model) { return function() { return model._packedClippingPlanes.length; }; } - + function createClippingPlanesFunction(model) { return function() { var clippingPlanes = model.clippingPlanes; @@ -3463,7 +3456,6 @@ define([ uniformMap = combine(uniformMap, { gltf_color : createColorFunction(model), gltf_colorBlend : createColorBlendFunction(model), - gltf_clippingPlanesEnabled: createClippingPlanesEnabledFunction(model), gltf_clippingPlanesLength: createClippingPlanesLengthFunction(model), gltf_clippingPlanes: createClippingPlanesFunction(model, context), gltf_clippingPlanesInclusive: createClippingPlanesInclusiveFunction(model), @@ -4265,7 +4257,6 @@ define([ function modifyShaderForClippingPlanes(shader) { shader = ShaderSource.replaceMain(shader, 'gltf_clip_main'); shader += - 'uniform bool gltf_clippingPlanesEnabled; \n' + 'uniform int gltf_clippingPlanesLength; \n' + 'uniform vec4 gltf_clippingPlanes[czm_maxClippingPlanes]; \n' + 'uniform bool gltf_clippingPlanesInclusive; \n' + @@ -4274,11 +4265,9 @@ define([ 'void main() \n' + '{ \n' + ' gltf_clip_main(); \n' + - ' if (gltf_clippingPlanesEnabled) { \n' + - ' float clipDistance = czm_discardIfClipped(gltf_clippingPlanes, gltf_clippingPlanesLength, gltf_clippingPlanesInclusive); \n' + - ' if (clipDistance < gltf_clippingPlanesEdgeWidth) { \n' + - ' gl_FragColor = gltf_clippingPlanesEdgeColor; \n' + - ' } \n' + + ' float clipDistance = czm_discardIfClipped(gltf_clippingPlanes, gltf_clippingPlanesLength, gltf_clippingPlanesInclusive); \n' + + ' if (clipDistance < gltf_clippingPlanesEdgeWidth) { \n' + + ' gl_FragColor = gltf_clippingPlanesEdgeColor; \n' + ' } \n' + '} \n'; diff --git a/Source/Scene/PointCloud3DTileContent.js b/Source/Scene/PointCloud3DTileContent.js index 72cbcff5e7f9..207048fe97fe 100644 --- a/Source/Scene/PointCloud3DTileContent.js +++ b/Source/Scene/PointCloud3DTileContent.js @@ -138,7 +138,8 @@ define([ this._features = undefined; - this._packedClippingPlanes = []; + this._packedClippingPlanes = []; + this._modelViewMatrix = Matrix4.clone(Matrix4.IDENTITY); /** * @inheritdoc Cesium3DTileContent#featurePropertiesDirty @@ -516,8 +517,8 @@ define([ } } - var scratchPlane = new Plane(Cartesian3.UNIT_X, 0.0); - var scratchMatrix = new Matrix4(); + var clippingPlanes = content._tileset.clippingPlanes; + var scratchCartesian = new Cartesian4(); var uniformMap = { u_pointSizeAndTilesetTime : function() { scratchPointSizeAndTilesetTime.x = content._pointSize; @@ -530,28 +531,38 @@ define([ u_constantColor : function() { return content._constantColor; }, - u_clippingPlanesEnabled : function() { - return content._tileset.clippingPlanesEnabled && content._tile._isClipped; - }, u_clippingPlanesLength : function() { return content._packedClippingPlanes.length; }, u_clippingPlanes : function() { - Matrix4.multiply(context.uniformState.view3D, content._modelMatrix, scratchMatrix); - - var planes = content._tileset.clippingPlanes; var packedPlanes = content._packedClippingPlanes; - var length = packedPlanes.length; - for (var i = 0; i < length; ++i) { - var plane = planes[i]; - var packedPlane = packedPlanes[i]; - - Plane.transform(plane, scratchMatrix, scratchPlane); - Cartesian3.clone(scratchPlane.normal, packedPlane); - packedPlane.w = scratchPlane.distance; + if (defined(clippingPlanes) && clippingPlanes.enabled) { + clippingPlanes.transformAndPackPlanes(content._modelViewMatrix, packedPlanes); } + return packedPlanes; + }, + u_clippingPlanesInclusive : function() { + if (!defined(clippingPlanes)) { + return true; + } + + return clippingPlanes.inclusive; + }, + u_clippingPlanesEdgeWidth : function() { + if (!defined(clippingPlanes)) { + return 0.0; + } + + return clippingPlanes.edgeWidth; + }, + u_clippingPlanesEdgeColor : function() { + if (!defined(clippingPlanes)) { + return scratchCartesian; + } + + return Cartesian4.fromColor(clippingPlanes.edgeColor, scratchCartesian); } }; @@ -818,6 +829,7 @@ define([ var hasBatchIds = content._hasBatchIds; var backFaceCulling = content._backFaceCulling; var vertexArray = content._drawCommand.vertexArray; + var clippingPlanes = content._tileset.clippingPlanes; var colorStyleFunction; var showStyleFunction; @@ -846,6 +858,7 @@ define([ var hasColorStyle = defined(colorStyleFunction); var hasShowStyle = defined(showStyleFunction); var hasPointSizeStyle = defined(pointSizeStyleFunction); + var hasClippingPlanes = defined(clippingPlanes) && clippingPlanes.enabled && content._tile._isClipped; // Get the properties in use by the style var styleableProperties = []; @@ -1060,17 +1073,28 @@ define([ vs += '} \n'; - var fs = 'varying vec4 v_color; \n' + - 'uniform bool u_clippingPlanesEnabled; \n' + - 'uniform int u_clippingPlanesLength;' + - 'uniform vec4 u_clippingPlanes[czm_maxClippingPlanes]; \n' + - 'void main() \n' + - '{ \n' + - ' if (u_clippingPlanesEnabled) { \n' + - ' czm_discardIfClipped(u_clippingPlanes, u_clippingPlanesLength); \n' + - ' } \n' + - ' gl_FragColor = v_color; \n' + - '} \n'; + var fs = 'varying vec4 v_color; \n'; + + if (hasClippingPlanes) { + fs += 'uniform int u_clippingPlanesLength;' + + 'uniform vec4 u_clippingPlanes[czm_maxClippingPlanes]; \n' + + 'uniform bool u_clippingPlanesInclusive; \n' + + 'uniform vec4 u_clippingPlanesEdgeColor; \n' + + 'uniform float u_clippingPlanesEdgeWidth; \n'; + } + + fs += 'void main() \n' + + '{ \n' + + ' gl_FragColor = v_color; \n'; + + if (hasClippingPlanes) { + fs += ' float clipDistance = czm_discardIfClipped(u_clippingPlanes, u_clippingPlanesLength, u_clippingPlanesInclusive); \n' + + ' if (clipDistance < u_clippingPlanesEdgeWidth) { \n' + + ' gl_FragColor = u_clippingPlanesEdgeColor; \n' + + ' } \n'; + } + + fs += '} \n'; var drawVS = vs; var drawFS = fs; @@ -1203,10 +1227,18 @@ define([ this._mode = frameState.mode; + var context = frameState.context; + // update clipping planes + var clippingPlanes = this._tileset.clippingPlanes; + var modelViewChanged = context.uniformState._modelViewDirty || modelMatrixChanged; + if (defined(clippingPlanes) && clippingPlanes.enabled && modelViewChanged) { + Matrix4.multiply(context.uniformState.view3D, modelMatrix, this._modelViewMatrix); + } + var length = 0; - if (defined(this._tileset.clippingPlanes)) { - length = this._tileset.clippingPlanes.length; + if (defined(clippingPlanes)) { + length = clippingPlanes.planes.length; } if (this._packedClippingPlanes.length !== length) { From e8e14f2263689c206e93791ee4d5db7bc885c8bf Mon Sep 17 00:00:00 2001 From: ggetz Date: Mon, 27 Nov 2017 10:03:58 -0500 Subject: [PATCH 17/37] Refactor clipping planes --- ...nes.html => 3D Tiles Clipping Planes.html} | 39 ++++---- ...lanes.jpg => 3D Tiles Clipping Planes.jpg} | Bin ...ping.html => Terrain Clipping Planes.html} | 11 +- ...ipping.jpg => Terrain Clipping Planes.jpg} | Bin Source/Core/Plane.js | 2 - Source/Scene/Cesium3DTile.js | 22 ---- Source/Scene/ClippingPlanesCollection.js | 94 ++++++++++++------ Source/Scene/Globe.js | 2 +- Source/Scene/GlobeSurfaceShaderSet.js | 6 +- Source/Scene/GlobeSurfaceTileProvider.js | 36 +++---- Source/Scene/Model.js | 20 +--- Source/Scene/PointCloud3DTileContent.js | 11 +- .../Builtin/Functions/discardIfClipped.glsl | 19 ++-- .../discardIfClippedCombineRegions.glsl | 49 +++++++++ Source/Shaders/GlobeFS.glsl | 7 +- 15 files changed, 173 insertions(+), 145 deletions(-) rename Apps/Sandcastle/gallery/{Clipping Planes.html => 3D Tiles Clipping Planes.html} (85%) rename Apps/Sandcastle/gallery/{Clipping Planes.jpg => 3D Tiles Clipping Planes.jpg} (100%) rename Apps/Sandcastle/gallery/{Terrain Clipping.html => Terrain Clipping Planes.html} (78%) rename Apps/Sandcastle/gallery/{Terrain Clipping.jpg => Terrain Clipping Planes.jpg} (100%) create mode 100644 Source/Shaders/Builtin/Functions/discardIfClippedCombineRegions.glsl diff --git a/Apps/Sandcastle/gallery/Clipping Planes.html b/Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html similarity index 85% rename from Apps/Sandcastle/gallery/Clipping Planes.html rename to Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html index 73e04286bf59..c7af66f4ef25 100644 --- a/Apps/Sandcastle/gallery/Clipping Planes.html +++ b/Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html @@ -37,7 +37,7 @@

- Show debug boundingVolume + Show bounding volume
@@ -31,16 +31,15 @@ terrainExaggeration : 3.0 }); -var cesiumTerrainProviderMeshes = new Cesium.CesiumTerrainProvider({ +viewer.terrainProvider = new Cesium.CesiumTerrainProvider({ url : 'https://assets.agi.com/stk-terrain/v1/tilesets/world/tiles', requestWaterMask : true, requestVertexNormals : true }); -viewer.terrainProvider = cesiumTerrainProviderMeshes; var globe = viewer.scene.globe; -globe.terrainClippingPlanes = new Cesium.ClippingPlanesCollection({ - transformationMatrix : Cesium.Matrix4.fromTranslation(new Cesium.Cartesian3(0.0, 0.0, 3000000.0)), +globe.clippingPlanes = new Cesium.ClippingPlanesCollection({ + modelMatrix : Cesium.Matrix4.fromTranslation(new Cesium.Cartesian3(0.0, 0.0, 3000000.0)), planes : [ new Cesium.Plane(new Cesium.Cartesian3(0.0, 0.0, -1.0), 0.0), new Cesium.Plane(new Cesium.Cartesian3(0.0, 0.0, 1.0), 1000000.0) diff --git a/Apps/Sandcastle/gallery/Terrain Clipping.jpg b/Apps/Sandcastle/gallery/Terrain Clipping Planes.jpg similarity index 100% rename from Apps/Sandcastle/gallery/Terrain Clipping.jpg rename to Apps/Sandcastle/gallery/Terrain Clipping Planes.jpg diff --git a/Source/Core/Plane.js b/Source/Core/Plane.js index cec682af4071..cc7f5532937e 100644 --- a/Source/Core/Plane.js +++ b/Source/Core/Plane.js @@ -1,7 +1,6 @@ define([ './Cartesian3', './Check', - './defaultValue', './defined', './DeveloperError', './freezeObject', @@ -10,7 +9,6 @@ define([ ], function( Cartesian3, Check, - defaultValue, defined, DeveloperError, freezeObject, diff --git a/Source/Scene/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js index 6eb6ad2af9b7..d7af1391ed35 100644 --- a/Source/Scene/Cesium3DTile.js +++ b/Source/Scene/Cesium3DTile.js @@ -740,28 +740,6 @@ define([ return frameState.mode !== SceneMode.SCENE3D ? tile._contentBoundingVolume2D : tile._contentBoundingVolume; } - - var scratchPlane = new Plane(Cartesian3.UNIT_X, 0.0); - function checkTileClipped(tile, boundingVolume) { - var clippingPlanes = tile._tileset.clippingPlanes; - if (defined(clippingPlanes) && clippingPlanes.enabled) { - var planes = clippingPlanes.planes; - var length = planes.length; - var rootTransform = tile._tileset._root.computedTransform; // TODO, factor in inclusive/exclusive, transform - for (var i = 0; i < length; ++i) { - var plane = planes[i]; - Plane.transform(plane, rootTransform, scratchPlane); - var value = boundingVolume.intersectPlane(scratchPlane); - if (value !== Intersect.INSIDE) { - return value; - } - } - } - - return Intersect.INSIDE; - } - - /** * Determines whether the tile's bounding volume intersects the culling volume. * diff --git a/Source/Scene/ClippingPlanesCollection.js b/Source/Scene/ClippingPlanesCollection.js index 6e198a7615ae..e6d7bf1be8f1 100644 --- a/Source/Scene/ClippingPlanesCollection.js +++ b/Source/Scene/ClippingPlanesCollection.js @@ -6,6 +6,7 @@ define([ '../Core/Color', '../Core/defaultValue', '../Core/defined', + '../Core/defineProperties', '../Core/Intersect', '../Core/Matrix4', '../Core/Plane' @@ -17,30 +18,31 @@ define([ Color, defaultValue, defined, + defineProperties, Intersect, Matrix4, Plane) { 'use strict'; /** - * Contains the options to specify a set of clipping planes. Clipping planes selectively disable rendering on an object on the outside of the specified list of {@link Plane}. + * Specifies a set of clipping planes. Clipping planes selectively disable rendering in a region on the outside of the specified list of {@link Plane} objects. * * @alias ClippingPlanesCollection * @constructor * * @param {Object} [options] Object with the following properties: - * @param {Plane[]} [options.planes=[]] An array of up to 6 {@link Plane} used to selectively disable rendering on the outside of each plane. + * @param {Plane[]} [options.planes=[]] An array of up to 6 {@link Plane} objects used to selectively disable rendering on the outside of each plane. * @param {Boolean} [options.enabled=true] Determines whether the clipping planes are active. - * @param {Matrix4} [options.transformationMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix specifying an additional transform relative to the clipping planes original coordinate system. - * @param {Boolean} [options.inclusive=true] If true, the region to be clipped must be included in all planes in this collection. Otherwise, a region will be clipped if included in any plane in the collection. - * @param {Color} [options.edgeColor=Color.WHITE] The color applied to hughlight the edge along which an object is clipped. + * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix specifying an additional transform relative to the clipping planes original coordinate system. + * @param {Boolean} [options.combineClippingRegions=true] If true, the region to be clipped must be included in all planes in this collection. Otherwise, a region will be clipped if included in any plane in the collection. + * @param {Color} [options.edgeColor=Color.WHITE] The color applied to highlight the edge along which an object is clipped. * @param {Number} [options.edgeWidth=0.0] The width of the highlight applied to the edge along which an object is clipped. */ function ClippingPlanesCollection(options) { - var options = defaultValue(options, defaultValue.EMPTY_OBJECT); + options = defaultValue(options, defaultValue.EMPTY_OBJECT); /** - * An array of up to 6 {@link Plane} used to selectively disable rendering on the outside of each plane. + * An array of up to 6 {@link Plane} objects used to selectively disable rendering on the outside of each plane. * * @type {Plane} * @default [] @@ -61,19 +63,10 @@ define([ * @type {Matrix4} * @default Matrix4.IDENTITY */ - this.transformationMatrix = defaultValue(options.transformationMatrix, Matrix4.clone(Matrix4.IDENTITY)); + this.modelMatrix = defaultValue(options.modelMatrix, Matrix4.clone(Matrix4.IDENTITY)); /** - * If true, the region to be clipped must be included in all planes in this collection. - * Otherwise, a region will be clipped if included in any plane in the collection. - * - * @type {Boolean} - * @default true - */ - this.inclusive = defaultValue(options.inclusive, true); - - /** - * The color applied to hughlight the edge along which an object is clipped. + * The color applied to highlight the edge along which an object is clipped. * * @type {Color} * @default Color.WHITE @@ -87,6 +80,43 @@ define([ * @default 0.0 */ this.edgeWidth = defaultValue(options.edgeWidth, 0.0); + + this._testIntersection = undefined; + this.combineClippingRegions = defaultValue(options.combineClippingRegions, true); + } + + defineProperties(ClippingPlanesCollection.prototype, { + /** + * If true, the region to be clipped must be included in all planes in this collection. + * Otherwise, a region will be clipped if included in any plane in the collection. + * + * @memberof ClippingPlanesCollection.prototype + * @type {Boolean} + * @default true + */ + combineClippingRegions : { + get : function() { + return this._combineClippingRegions; + }, + set : function(value) { + if (this._combineClippingRegions !== value) { + this._combineClippingRegions = value; + this._testIntersection = getTestIntersectionFunction(value); + } + } + } + }); + + function getTestIntersectionFunction(combineClippingRegions) { + if (combineClippingRegions) { + return function(value) { + return (value === Intersect.INSIDE); + }; + } + + return function(value) { + return (value === Intersect.OUTSIDE); + }; } var scratchPlane = new Plane(Cartesian3.UNIT_X, 0.0); @@ -98,7 +128,7 @@ define([ * @param [array] * @returns {Cartesian4[]} The array of packed planes. */ - ClippingPlanesCollection.prototype.transformAndPackPlanes = function (viewMatrix, array) { + ClippingPlanesCollection.prototype.transformAndPackPlanes = function(viewMatrix, array) { var planes = this.planes; var length = planes.length; @@ -106,7 +136,7 @@ define([ array = new Array(length); } - var transform = Matrix4.multiply(viewMatrix, this.transformationMatrix, scratchMatrix); + var transform = Matrix4.multiply(viewMatrix, this.modelMatrix, scratchMatrix); for (var i = 0; i < length; ++i) { var plane = planes[i]; @@ -127,15 +157,15 @@ define([ * @param {ClippingPlanesCollection} [result] The object onto which to store the result. * @returns he modified result parameter or a new ClippingPlanesCollection instance if one was not provided. */ - ClippingPlanesCollection.prototype.clone = function (result) { + ClippingPlanesCollection.prototype.clone = function(result) { if (!defined(result)) { result = new ClippingPlanesCollection(); } result.planes = Array.from(this.planes); result.enabled = this.enabled; - Matrix4.clone(this.transformationMatrix, result.transformationMatrix); - result.inclusive = this.inclusive; + Matrix4.clone(this.modelMatrix, result.modelMatrix); + result.combineClippingRegions = this.combineClippingRegions; Color.clone(this.edgeColor, result.edgeColor); result.edgeWidth = this.edgeWidth; @@ -152,32 +182,32 @@ define([ * if the entire volume is on the opposite side and should be clipped, and * {@link Intersect.INTERSECTING} if the volume intersects the planes. */ - ClippingPlanesCollection.prototype.computeIntersectionWithBoundingVolume = function (boundingVolume, parentTransform) { + ClippingPlanesCollection.prototype.computeIntersectionWithBoundingVolume = function(boundingVolume, parentTransform) { var planes = this.planes; var length = planes.length; - var transformation = this.transformationMatrix; + var transform = this.modelMatrix; if (defined(parentTransform)) { - transformation = Matrix4.multiply(transformation, parentTransform, scratchMatrix); + transform = Matrix4.multiply(transform, parentTransform, scratchMatrix); } - // If the clipping planes are inclusive, the volume must be outside of all planes to be considered completely - // clipped. Otherwise, if the volume can be outside any the planes, it is considered completely clipped. + // If the clipping planes are using combineClippingRegions, the volume must be outside of all planes to be considered + // completely clipped. Otherwise, if the volume can be outside any the planes, it is considered completely clipped. // Lastly, if not completely clipped, if any plane is intersecting, more calculations must be performed. var intersection = Intersect.INSIDE; - if (this.inclusive && length > 0) { + if (this.combineClippingRegions && length > 0) { intersection = Intersect.OUTSIDE; } + for (var i = 0; i < length; ++i) { var plane = planes[i]; - Plane.transform(plane, transformation, scratchPlane); + Plane.transform(plane, transform, scratchPlane); var value = boundingVolume.intersectPlane(scratchPlane); if (value === Intersect.INTERSECTING) { intersection = value; - } else if ((this.inclusive && value === Intersect.INSIDE) || - (!this.inclusive && value === Intersect.OUTSIDE)) { + } else if (this._testIntersection(value)) { return value; } } diff --git a/Source/Scene/Globe.js b/Source/Scene/Globe.js index 41f6b6e5b17e..3fd3e79f92f7 100644 --- a/Source/Scene/Globe.js +++ b/Source/Scene/Globe.js @@ -239,7 +239,7 @@ define([ * * @type {ClippingPlaneCollection} */ - terrainClippingPlanes : { + clippingPlanes : { get : function() { return this._surface.tileProvider.clippingPlanes; }, diff --git a/Source/Scene/GlobeSurfaceShaderSet.js b/Source/Scene/GlobeSurfaceShaderSet.js index a6f7068a8b61..7de0b451d118 100644 --- a/Source/Scene/GlobeSurfaceShaderSet.js +++ b/Source/Scene/GlobeSurfaceShaderSet.js @@ -61,7 +61,7 @@ define([ return useWebMercatorProjection ? get2DYPositionFractionMercatorProjection : get2DYPositionFractionGeographicProjection; } - GlobeSurfaceShaderSet.prototype.getShaderProgram = function(frameState, surfaceTile, numberOfDayTextures, applyBrightness, applyContrast, applyHue, applySaturation, applyGamma, applyAlpha, applySplit, showReflectiveOcean, showOceanWaves, enableLighting, hasVertexNormals, useWebMercatorProjection, enableFog, enableClippingPlanes) { + GlobeSurfaceShaderSet.prototype.getShaderProgram = function(frameState, surfaceTile, numberOfDayTextures, applyBrightness, applyContrast, applyHue, applySaturation, applyGamma, applyAlpha, applySplit, showReflectiveOcean, showOceanWaves, enableLighting, hasVertexNormals, useWebMercatorProjection, enableFog, enableClippingPlanes, combineClippingRegions) { var quantization = 0; var quantizationDefine = ''; @@ -163,6 +163,10 @@ define([ if (enableClippingPlanes) { fs.defines.push('ENABLE_CLIPPING_PLANES'); + + if (combineClippingRegions) { + fs.defines.push('COMBINE_CLIPPING_REGIONS'); + } } var computeDayColor = '\ diff --git a/Source/Scene/GlobeSurfaceTileProvider.js b/Source/Scene/GlobeSurfaceTileProvider.js index ce6c2fce8548..a785bca7fb5d 100644 --- a/Source/Scene/GlobeSurfaceTileProvider.js +++ b/Source/Scene/GlobeSurfaceTileProvider.js @@ -873,9 +873,6 @@ define([ u_clippingPlanes : function() { return this.properties.clippingPlanes; }, - u_clippingPlanesInclusive : function() { - return this.properties.clippingPlanesInclusive; - }, u_clippingPlanesEdgeColor : function() { return this.properties.clippingPlanesEdgeColor; }, @@ -918,7 +915,6 @@ define([ minMaxHeight : new Cartesian2(), scaleAndBias : new Matrix4(), clippingPlanes : [], - clippingPlanesInclusive : true, clippingPlanesEdgeColor : new Cartesian4(1.0, 1.0, 1.0, 1.0), clippingPlanesEdgeWidth : 0.0 } @@ -1043,8 +1039,6 @@ define([ })(); var otherPassesInitialColor = new Cartesian4(0.0, 0.0, 0.0, 0.0); - var scratchPlane = new Plane(Cartesian3.UNIT_X, 0.0); - var scratchMatrix = new Matrix4(); function addDrawCommandsForTile(tileProvider, tile, frameState) { var surfaceTile = tile.data; @@ -1299,32 +1293,32 @@ define([ Matrix4.clone(encoding.matrix, uniformMapProperties.scaleAndBias); // update clipping planes - if (tile.isClipped) { - var length = 0; - var clippingPlanes = tileProvider.clippingPlanes; + var clippingPlanes = tileProvider.clippingPlanes; + var length = 0; - if (defined(clippingPlanes)) { - length = clippingPlanes.planes.length; - } - if (length !== uniformMapProperties.clippingPlanes.length) { - uniformMapProperties.clippingPlanes = new Array(length); + if (defined(clippingPlanes) && tile.isClipped) { + length = clippingPlanes.planes.length; + } - for (var i = 0; i < length; ++i) { - uniformMapProperties.clippingPlanes[i] = new Cartesian4(); - } + var clippingPlanesProperty = uniformMapProperties.clippingPlanes; + if (length !== clippingPlanesProperty.length) { + clippingPlanesProperty = uniformMapProperties.clippingPlanes = new Array(length); + + for (var i = 0; i < length; ++i) { + clippingPlanesProperty[i] = new Cartesian4(); } } - if (defined(clippingPlanes) && clippingPlanes.enabled) { - clippingPlanes.transformAndPackPlanes(context.uniformState.view, uniformMapProperties.clippingPlanes); - uniformMapProperties.clippingPlanesInclusive = clippingPlanes.inclusive; + if (defined(clippingPlanes) && clippingPlanes.enabled && tile.isClipped) { + clippingPlanes.transformAndPackPlanes(context.uniformState.view, clippingPlanesProperty); uniformMapProperties.clippingPlanesEdgeColor = Cartesian4.fromColor(clippingPlanes.edgeColor, uniformMapProperties.clippingPlanesEdgeColor); uniformMapProperties.clippingPlanesEdgeWidth = clippingPlanes.edgeWidth; } var clippingPlanesEnabled = defined(clippingPlanes) && clippingPlanes.enabled && (uniformMapProperties.clippingPlanes.length > 0); + var combineClippingRegions = clippingPlanesEnabled ? clippingPlanes.combineClippingRegions : true; - command.shaderProgram = tileProvider._surfaceShaderSet.getShaderProgram(frameState, surfaceTile, numberOfDayTextures, applyBrightness, applyContrast, applyHue, applySaturation, applyGamma, applyAlpha, applySplit, showReflectiveOcean, showOceanWaves, tileProvider.enableLighting, hasVertexNormals, useWebMercatorProjection, applyFog, clippingPlanesEnabled); + command.shaderProgram = tileProvider._surfaceShaderSet.getShaderProgram(frameState, surfaceTile, numberOfDayTextures, applyBrightness, applyContrast, applyHue, applySaturation, applyGamma, applyAlpha, applySplit, showReflectiveOcean, showOceanWaves, tileProvider.enableLighting, hasVertexNormals, useWebMercatorProjection, applyFog, clippingPlanesEnabled, combineClippingRegions); command.castShadows = castShadows; command.receiveShadows = receiveShadows; command.renderState = renderState; diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js index 9007b8bd7ff6..e6d270601222 100644 --- a/Source/Scene/Model.js +++ b/Source/Scene/Model.js @@ -2112,7 +2112,7 @@ define([ var clippingPlanes = model.clippingPlanes; if (defined(clippingPlanes) && clippingPlanes.enabled) { - finalFS = modifyShaderForClippingPlanes(finalFS); + finalFS = modifyShaderForClippingPlanes(finalFS, model.clippingPlanes.combineClippingRegions); } var drawVS = modifyShader(vs, id, model._vertexShaderLoaded); @@ -3330,16 +3330,6 @@ define([ }; } - function createClippingPlanesInclusiveFunction(model) { - return function() { - if (!defined(model.clippingPlanes)) { - return true; - } - - return model.clippingPlanes.inclusive; - }; - } - function createClippingPlanesEdgeWidthFunction(model) { return function() { if (!defined(model.clippingPlanes)) { @@ -3458,7 +3448,6 @@ define([ gltf_colorBlend : createColorBlendFunction(model), gltf_clippingPlanesLength: createClippingPlanesLengthFunction(model), gltf_clippingPlanes: createClippingPlanesFunction(model, context), - gltf_clippingPlanesInclusive: createClippingPlanesInclusiveFunction(model), gltf_clippingPlanesEdgeColor: createClippingPlanesEdgeColorFunction(model), gltf_clippingPlanesEdgeWidth: createClippingPlanesEdgeWidthFunction(model) }); @@ -4254,18 +4243,19 @@ define([ } } - function modifyShaderForClippingPlanes(shader) { + function modifyShaderForClippingPlanes(shader, combineClippingRegions) { shader = ShaderSource.replaceMain(shader, 'gltf_clip_main'); + + var clippingFunction = combineClippingRegions ? 'czm_discardIfClippedCombineRegions' : 'czm_discardIfClipped'; shader += 'uniform int gltf_clippingPlanesLength; \n' + 'uniform vec4 gltf_clippingPlanes[czm_maxClippingPlanes]; \n' + - 'uniform bool gltf_clippingPlanesInclusive; \n' + 'uniform vec4 gltf_clippingPlanesEdgeColor; \n' + 'uniform float gltf_clippingPlanesEdgeWidth; \n' + 'void main() \n' + '{ \n' + ' gltf_clip_main(); \n' + - ' float clipDistance = czm_discardIfClipped(gltf_clippingPlanes, gltf_clippingPlanesLength, gltf_clippingPlanesInclusive); \n' + + ' float clipDistance = ' + clippingFunction + '(gltf_clippingPlanes, gltf_clippingPlanesLength); \n' + ' if (clipDistance < gltf_clippingPlanesEdgeWidth) { \n' + ' gl_FragColor = gltf_clippingPlanesEdgeColor; \n' + ' } \n' + diff --git a/Source/Scene/PointCloud3DTileContent.js b/Source/Scene/PointCloud3DTileContent.js index 207048fe97fe..64b1791c208f 100644 --- a/Source/Scene/PointCloud3DTileContent.js +++ b/Source/Scene/PointCloud3DTileContent.js @@ -543,13 +543,6 @@ define([ return packedPlanes; }, - u_clippingPlanesInclusive : function() { - if (!defined(clippingPlanes)) { - return true; - } - - return clippingPlanes.inclusive; - }, u_clippingPlanesEdgeWidth : function() { if (!defined(clippingPlanes)) { return 0.0; @@ -1078,7 +1071,6 @@ define([ if (hasClippingPlanes) { fs += 'uniform int u_clippingPlanesLength;' + 'uniform vec4 u_clippingPlanes[czm_maxClippingPlanes]; \n' + - 'uniform bool u_clippingPlanesInclusive; \n' + 'uniform vec4 u_clippingPlanesEdgeColor; \n' + 'uniform float u_clippingPlanesEdgeWidth; \n'; } @@ -1088,7 +1080,8 @@ define([ ' gl_FragColor = v_color; \n'; if (hasClippingPlanes) { - fs += ' float clipDistance = czm_discardIfClipped(u_clippingPlanes, u_clippingPlanesLength, u_clippingPlanesInclusive); \n' + + var clippingFunction = clippingPlanes.combineClippingRegions ? 'czm_discardIfClippedCombineRegions' : 'czm_discardIfClipped'; + fs += ' float clipDistance = ' + clippingFunction + '(u_clippingPlanes, u_clippingPlanesLength); \n' + ' if (clipDistance < u_clippingPlanesEdgeWidth) { \n' + ' gl_FragColor = u_clippingPlanesEdgeColor; \n' + ' } \n'; diff --git a/Source/Shaders/Builtin/Functions/discardIfClipped.glsl b/Source/Shaders/Builtin/Functions/discardIfClipped.glsl index 1f3bd125008f..6f9e740175e7 100644 --- a/Source/Shaders/Builtin/Functions/discardIfClipped.glsl +++ b/Source/Shaders/Builtin/Functions/discardIfClipped.glsl @@ -1,19 +1,18 @@ /** - * Clip a fragment by an array of clipping planes. + * Clip a fragment by an array of clipping planes. Clipping plane regions are not combined, therefore if a fragment is + * clipped by any of the planes, it is discarded. * * @name czm_discardIfClipped * @glslFunction * * @param {vec4[]} clippingPlanes The array of planes used to clip, defined in eyespace. * @param {int} clippingPlanesLength The number of planes in the array of clipping planes. - * @param {bool} inclusive * @returns {float} The distance away from a clipped fragment, in eyespace */ -float czm_discardIfClipped (vec4[czm_maxClippingPlanes] clippingPlanes, int clippingPlanesLength, bool inclusive) +float czm_discardIfClipped (vec4[czm_maxClippingPlanes] clippingPlanes, int clippingPlanesLength) { if (clippingPlanesLength > 0) { - bool clipped = inclusive; vec4 position = czm_windowToEyeCoordinates(gl_FragCoord); vec3 clipNormal = vec3(0.0); vec3 clipPosition = vec3(0.0); @@ -34,18 +33,12 @@ float czm_discardIfClipped (vec4[czm_maxClippingPlanes] clippingPlanes, int clip clipAmount = amount; } - if (inclusive) { - clipped = clipped && (amount <= 0.0); - } else { - clipped = clipped || (amount <= 0.0); + if (amount <= 0.0) { + discard; + return amount; } } - if (clipped) - { - discard; - } - return clipAmount; } diff --git a/Source/Shaders/Builtin/Functions/discardIfClippedCombineRegions.glsl b/Source/Shaders/Builtin/Functions/discardIfClippedCombineRegions.glsl new file mode 100644 index 000000000000..638b835ba378 --- /dev/null +++ b/Source/Shaders/Builtin/Functions/discardIfClippedCombineRegions.glsl @@ -0,0 +1,49 @@ +/** + * Clip a fragment by an array of clipping planes. Clipping plane regions are combined, so a fragment must be clipped + * by all of the planes to be discarded. + * + * @name czm_discardIfClipped + * @glslFunction + * + * @param {vec4[]} clippingPlanes The array of planes used to clip, defined in eyespace. + * @param {int} clippingPlanesLength The number of planes in the array of clipping planes. + * @returns {float} The distance away from a clipped fragment, in eyespace + */ +float czm_discardIfClippedCombineRegions (vec4[czm_maxClippingPlanes] clippingPlanes, int clippingPlanesLength) +{ + if (clippingPlanesLength > 0) + { + bool clipped = true; + vec4 position = czm_windowToEyeCoordinates(gl_FragCoord); + vec3 clipNormal = vec3(0.0); + vec3 clipPosition = vec3(0.0); + float clipAmount = 0.0; + + for (int i = 0; i < czm_maxClippingPlanes; ++i) + { + if (i == clippingPlanesLength) + { + break; + } + + clipNormal = clippingPlanes[i].xyz; + clipPosition = -clippingPlanes[i].w * clipNormal; + + float amount = dot(clipNormal, (position.xyz - clipPosition)); + if (amount > clipAmount) { + clipAmount = amount; + } + + clipped = clipped && (amount <= 0.0); + } + + if (clipped) + { + discard; + } + + return clipAmount; + } + + return 0.0; +} diff --git a/Source/Shaders/GlobeFS.glsl b/Source/Shaders/GlobeFS.glsl index be076f396704..aa47300d6301 100644 --- a/Source/Shaders/GlobeFS.glsl +++ b/Source/Shaders/GlobeFS.glsl @@ -55,7 +55,6 @@ uniform vec2 u_lightingFadeDistance; #ifdef ENABLE_CLIPPING_PLANES uniform int u_clippingPlanesLength; uniform vec4 u_clippingPlanes[czm_maxClippingPlanes]; -uniform bool u_clippingPlanesInclusive; uniform vec4 u_clippingPlanesEdgeColor; uniform float u_clippingPlanesEdgeWidth; #endif @@ -150,7 +149,11 @@ vec4 computeWaterColor(vec3 positionEyeCoordinates, vec2 textureCoordinates, mat void main() { #ifdef ENABLE_CLIPPING_PLANES - float clipDistance = czm_discardIfClipped(u_clippingPlanes, u_clippingPlanesLength, u_clippingPlanesInclusive); + #ifdef COMBINE_CLIPPING_REGIONS + float clipDistance = czm_discardIfClippedCombineRegions(u_clippingPlanes, u_clippingPlanesLength); + #else + float clipDistance = czm_discardIfClipped(u_clippingPlanes, u_clippingPlanesLength); + #endif #endif // The clamp below works around an apparent bug in Chrome Canary v23.0.1241.0 From 4a376901322e064cb38a3f8ea7c5a139915e3670 Mon Sep 17 00:00:00 2001 From: ggetz Date: Mon, 27 Nov 2017 12:21:50 -0500 Subject: [PATCH 18/37] Fix Plane geometry classes --- CHANGES.md | 10 ++++---- Source/Core/PlaneGeometry.js | 12 +++------ Source/Core/PlaneOutlineGeometry.js | 29 ++++++---------------- Source/DataSources/PlaneGeometryUpdater.js | 2 -- 4 files changed, 17 insertions(+), 36 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 918d68eb3014..6efaddfb0013 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,11 +3,11 @@ Change Log ## TODO release -* Added `clippingPlanes` property to `ModelGraphics` and `Cesium3DTileset`, which specifies a `ClippingPlanesCollection` to clip the object. [TODO]() -* Added `terrainClippingPlanes` property to `Globe` which specifies a `ClippingPlanesCollection` to clip the terrain. -* Added `Plane.transformPlane` function to apply a transformation to a plane. [TODO]() -* Added `PlaneGeometry` and `PlaneOutlineGeometry` primitives -* Added `PlaneGraphics` and `plane` property to `Entity`. +* Added `clippingPlanes` property to `ModelGraphics` and `Cesium3DTileset`, which specifies a `ClippingPlanesCollection` to clip the object. [#5913](https://github.com/AnalyticalGraphicsInc/cesium/pull/5913) +* Added `terrainClippingPlanes` property to `Globe` which specifies a `ClippingPlanesCollection` to clip the terrain. [#5996](https://github.com/AnalyticalGraphicsInc/cesium/pull/5996) +* Added `Plane.transformPlane` function to apply a transformation to a plane. [#5966](https://github.com/AnalyticalGraphicsInc/cesium/pull/5966) +* Added `PlaneGeometry`, `PlaneOutlineGeometry`, and `PlaneOutlineGeometryUpdater` classes to render plane primitives. [#5996](https://github.com/AnalyticalGraphicsInc/cesium/pull/5996) +* Added `PlaneGraphics` class and `plane` property to `Entity`. [#5996](https://github.com/AnalyticalGraphicsInc/cesium/pull/5996) ### 1.38 - 2017-10-02 diff --git a/Source/Core/PlaneGeometry.js b/Source/Core/PlaneGeometry.js index 9a494d60415b..412b21ecf68f 100644 --- a/Source/Core/PlaneGeometry.js +++ b/Source/Core/PlaneGeometry.js @@ -25,23 +25,18 @@ define([ 'use strict'; /** - * Describes a plane by a normal and distance from the origin in world coordinates. + * Describes geometry representing a plane centered at the origin, with a unit width and length. * - * @alias Plane + * @alias PlaneGeometry * @constructor * * @param {Object} options Object with the following properties: * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed. * - * @see Plane - * @see PlaneGeometry.createGeometry - * @see Packable - * * @example * var planeGeometry = new Cesium.PlaneGeometry({ * vertexFormat : Cesium.VertexFormat.POSITION_ONLY * }); - * var geometry = Cesium.PlaneGeometry.createGeometry(plane); */ function PlaneGeometry(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); @@ -110,6 +105,7 @@ define([ return result; }; + /** * Computes the geometric representation of a plane, including its vertices, indices, and a bounding sphere. * @@ -376,7 +372,7 @@ define([ attributes : attributes, indices : indices, primitiveType : PrimitiveType.TRIANGLES, - boundingSphere : new BoundingSphere(Cartesian3.ZERO, 0.5) + boundingSphere : new BoundingSphere(Cartesian3.ZERO, Math.sqrt(2.0)) }); }; diff --git a/Source/Core/PlaneOutlineGeometry.js b/Source/Core/PlaneOutlineGeometry.js index f3f0aaf55ff7..751c13bf5558 100644 --- a/Source/Core/PlaneOutlineGeometry.js +++ b/Source/Core/PlaneOutlineGeometry.js @@ -23,29 +23,16 @@ define([ 'use strict'; /** - * A description of the outline of a cube centered at the origin. + * Describes geometry representing the outline of a plane centered at the origin, with a unit width and length. * * @alias PlaneOutlineGeometry * @constructor * - * @param {Object} options Object with the following properties: - * @param {Cartesian3} options.minimum The minimum x, y, and z coordinates of the box. - * @param {Cartesian3} options.maximum The maximum x, y, and z coordinates of the box. - * - * @see PlaneOutlineGeometry.fromDimensions - * @see PlaneOutlineGeometry.createGeometry - * @see Packable - * - * @example - * var box = new Cesium.PlaneOutlineGeometry({ - * maximum : new Cesium.Cartesian3(250000.0, 250000.0, 250000.0), - * minimum : new Cesium.Cartesian3(-250000.0, -250000.0, -250000.0) - * }); - * var geometry = Cesium.PlaneOutlineGeometry.createGeometry(box); */ - function PlaneOutlineGeometry(options) { + function PlaneOutlineGeometry() { this._workerName = 'createPlaneOutlineGeometry'; } + /** * The number of elements used to pack the object into an array. * @type {Number} @@ -61,7 +48,7 @@ define([ * * @returns {Number[]} The array that was packed into */ - PlaneOutlineGeometry.pack = function(value, array, startingIndex) { + PlaneOutlineGeometry.pack = function(value, array) { //>>includeStart('debug', pragmas.debug); Check.defined('array', array); //>>includeEnd('debug'); @@ -90,12 +77,12 @@ define([ }; /** - * Computes the geometric representation of an outline of a box, including its vertices, indices, and a bounding sphere. + * Computes the geometric representation of an outline of a plane, including its vertices, indices, and a bounding sphere. * - * @param {PlaneOutlineGeometry} boxGeometry A description of the box outline. + * @param {PlaneOutlineGeometry} planeGeometry A description of the plane outline. * @returns {Geometry|undefined} The computed vertices and indices. */ - PlaneOutlineGeometry.createGeometry = function(boxGeometry) { + PlaneOutlineGeometry.createGeometry = function() { var min = new Cartesian3(-0.5, -0.5, 0.0); var max = new Cartesian3( 0.5, 0.5, 0.0); @@ -135,7 +122,7 @@ define([ attributes : attributes, indices : indices, primitiveType : PrimitiveType.LINES, - boundingSphere : new BoundingSphere(Cartesian3.ZERO, 0.5) + boundingSphere : new BoundingSphere(Cartesian3.ZERO, Math.sqrt(2.0)) }); }; diff --git a/Source/DataSources/PlaneGeometryUpdater.js b/Source/DataSources/PlaneGeometryUpdater.js index 02117d691b1f..94013f39d01c 100644 --- a/Source/DataSources/PlaneGeometryUpdater.js +++ b/Source/DataSources/PlaneGeometryUpdater.js @@ -368,8 +368,6 @@ define([ }; } - var options = this._options; - var modelMatrix = entity.computeModelMatrix(Iso8601.MINIMUM_VALUE); return new GeometryInstance({ From 93a29409024efba3e3e923d311e1518b94775d5f Mon Sep 17 00:00:00 2001 From: ggetz Date: Mon, 27 Nov 2017 12:23:40 -0500 Subject: [PATCH 19/37] Fix doc --- Source/Core/PlaneOutlineGeometry.js | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/Core/PlaneOutlineGeometry.js b/Source/Core/PlaneOutlineGeometry.js index 751c13bf5558..1f24a5876876 100644 --- a/Source/Core/PlaneOutlineGeometry.js +++ b/Source/Core/PlaneOutlineGeometry.js @@ -44,7 +44,6 @@ define([ * * @param {PlaneOutlineGeometry} value The value to pack. * @param {Number[]} array The array to pack into. - * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements. * * @returns {Number[]} The array that was packed into */ From 8d41a56186aeb95df21c3b63bd42ad20c64de95b Mon Sep 17 00:00:00 2001 From: ggetz Date: Mon, 27 Nov 2017 13:49:21 -0500 Subject: [PATCH 20/37] Pixel width for edge highlighting, fix model transforms --- .../gallery/3D Tiles Clipping Planes.html | 32 +++++++++---------- Source/Scene/ClippingPlanesCollection.js | 4 +-- Source/Scene/Model.js | 2 +- .../Builtin/Functions/discardIfClipped.glsl | 3 +- .../discardIfClippedCombineRegions.glsl | 3 +- 5 files changed, 23 insertions(+), 21 deletions(-) diff --git a/Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html b/Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html index c7af66f4ef25..1c6e8e6d9dc9 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html +++ b/Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html @@ -50,10 +50,6 @@ }); var scene = viewer.scene; -var defaultClippingPlanes = [ - new Cesium.Plane(new Cesium.Cartesian3(0.0, 0.0,-1.0), -100.0) -]; - var clipObjects = ['BIM', 'Point Cloud', 'Model']; var viewModel = { debugBoundingVolumesEnabled : false, @@ -113,10 +109,14 @@ var tileset; function loadTileset(url) { + var clippingPlanes = [ + new Cesium.Plane(new Cesium.Cartesian3(0.0, 0.0, -1.0), -100.0) + ]; + tileset = viewer.scene.primitives.add(new Cesium.Cesium3DTileset({ url : url, clippingPlanes : new Cesium.ClippingPlanesCollection({ - planes : defaultClippingPlanes, + planes : clippingPlanes, edgeWidth : 1.0 }) })); @@ -128,9 +128,8 @@ viewer.camera.viewBoundingSphere(boundingSphere, new Cesium.HeadingPitchRange(0.5, -0.2, radius * 4.0)); viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY); - for (var i = 0; i < defaultClippingPlanes.length; ++i) { - var clippingPlanes = tileset.clippingPlanes; - var plane = clippingPlanes.planes[i]; + for (var i = 0; i < clippingPlanes.length; ++i) { + var plane = clippingPlanes[i]; var planeEntity = viewer.entities.add({ position : boundingSphere.center, plane : { @@ -154,6 +153,10 @@ var modelEntity; function loadModel(url) { + var clippingPlanes = [ + new Cesium.Plane(new Cesium.Cartesian3(0.0, 0.0, -1.0), -100.0) + ]; + var position = Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706, 100.0); var heading = Cesium.Math.toRadians(135.0); var pitch = 0.0; @@ -169,9 +172,8 @@ scale : 8, minimumPixelSize : 100.0, clippingPlanes : new Cesium.ClippingPlanesCollection({ - planes : defaultClippingPlanes, - edgeWidth : 1.0, - modelMatrix : Cesium.Matrix4.fromRotationTranslation(Cesium.Matrix3.fromRotationX(-Cesium.Math.PI_OVER_TWO)) + planes : clippingPlanes, + edgeWidth : 1.0 }) } }); @@ -179,11 +181,8 @@ viewer.trackedEntity = entity; modelEntity = entity.model; - console.log(modelEntity); - - for (var i = 0; i < defaultClippingPlanes.length; ++i) { - var clippingPlanes = tileset.clippingPlanes; - var plane = clippingPlanes.planes[i]; + for (var i = 0; i < clippingPlanes.length; ++i) { + var plane = clippingPlanes[i]; var planeEntity = viewer.entities.add({ position : position, plane : { @@ -236,6 +235,7 @@ viewer.entities.removeAll(); viewer.scene.primitives.removeAll(); planeEntities = []; + targetY = 0.0; } //Sandcastle_End diff --git a/Source/Scene/ClippingPlanesCollection.js b/Source/Scene/ClippingPlanesCollection.js index e6d7bf1be8f1..5583ef81ceba 100644 --- a/Source/Scene/ClippingPlanesCollection.js +++ b/Source/Scene/ClippingPlanesCollection.js @@ -36,7 +36,7 @@ define([ * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix specifying an additional transform relative to the clipping planes original coordinate system. * @param {Boolean} [options.combineClippingRegions=true] If true, the region to be clipped must be included in all planes in this collection. Otherwise, a region will be clipped if included in any plane in the collection. * @param {Color} [options.edgeColor=Color.WHITE] The color applied to highlight the edge along which an object is clipped. - * @param {Number} [options.edgeWidth=0.0] The width of the highlight applied to the edge along which an object is clipped. + * @param {Number} [options.edgeWidth=0.0] The width, in pixels, of the highlight applied to the edge along which an object is clipped. */ function ClippingPlanesCollection(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); @@ -74,7 +74,7 @@ define([ this.edgeColor = defaultValue(options.edgeColor, Color.clone(Color.WHITE)); /** - * The width of the highlight applied to the edge along which an object is clipped. + * The width, in pixels, of the highlight applied to the edge along which an object is clipped. * * @type {Number} * @default 0.0 diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js index e6d270601222..61194c4cbbbb 100644 --- a/Source/Scene/Model.js +++ b/Source/Scene/Model.js @@ -4750,7 +4750,7 @@ define([ var clippingPlanes = this.clippingPlanes; if (defined(clippingPlanes) && clippingPlanes.enabled && modelViewChanged) { - Matrix4.multiply(context.uniformState.view3D, this._computedModelMatrix, this._modelViewMatrix); + Matrix4.multiply(context.uniformState.view3D, modelMatrix, this._modelViewMatrix); } // Update modelMatrix throughout the graph as needed diff --git a/Source/Shaders/Builtin/Functions/discardIfClipped.glsl b/Source/Shaders/Builtin/Functions/discardIfClipped.glsl index 6f9e740175e7..33896e5cbfc6 100644 --- a/Source/Shaders/Builtin/Functions/discardIfClipped.glsl +++ b/Source/Shaders/Builtin/Functions/discardIfClipped.glsl @@ -17,6 +17,7 @@ float czm_discardIfClipped (vec4[czm_maxClippingPlanes] clippingPlanes, int clip vec3 clipNormal = vec3(0.0); vec3 clipPosition = vec3(0.0); float clipAmount = 0.0; + float pixelWidth = czm_metersPerPixel(position); for (int i = 0; i < czm_maxClippingPlanes; ++i) { @@ -28,7 +29,7 @@ float czm_discardIfClipped (vec4[czm_maxClippingPlanes] clippingPlanes, int clip clipNormal = clippingPlanes[i].xyz; clipPosition = -clippingPlanes[i].w * clipNormal; - float amount = dot(clipNormal, (position.xyz - clipPosition)); + float amount = dot(clipNormal, (position.xyz - clipPosition)) / pixelWidth; if (amount > clipAmount) { clipAmount = amount; } diff --git a/Source/Shaders/Builtin/Functions/discardIfClippedCombineRegions.glsl b/Source/Shaders/Builtin/Functions/discardIfClippedCombineRegions.glsl index 638b835ba378..18b007c4c0e7 100644 --- a/Source/Shaders/Builtin/Functions/discardIfClippedCombineRegions.glsl +++ b/Source/Shaders/Builtin/Functions/discardIfClippedCombineRegions.glsl @@ -18,6 +18,7 @@ float czm_discardIfClippedCombineRegions (vec4[czm_maxClippingPlanes] clippingPl vec3 clipNormal = vec3(0.0); vec3 clipPosition = vec3(0.0); float clipAmount = 0.0; + float pixelWidth = czm_metersPerPixel(position); for (int i = 0; i < czm_maxClippingPlanes; ++i) { @@ -29,7 +30,7 @@ float czm_discardIfClippedCombineRegions (vec4[czm_maxClippingPlanes] clippingPl clipNormal = clippingPlanes[i].xyz; clipPosition = -clippingPlanes[i].w * clipNormal; - float amount = dot(clipNormal, (position.xyz - clipPosition)); + float amount = dot(clipNormal, (position.xyz - clipPosition)) / pixelWidth; if (amount > clipAmount) { clipAmount = amount; } From b4c9a8d72bb69073831de0dccfd2493f19ffacb8 Mon Sep 17 00:00:00 2001 From: ggetz Date: Mon, 27 Nov 2017 14:50:21 -0500 Subject: [PATCH 21/37] Point cloud clipping fix --- Source/Scene/PointCloud3DTileContent.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Source/Scene/PointCloud3DTileContent.js b/Source/Scene/PointCloud3DTileContent.js index 64b1791c208f..7a1de03680c4 100644 --- a/Source/Scene/PointCloud3DTileContent.js +++ b/Source/Scene/PointCloud3DTileContent.js @@ -117,6 +117,9 @@ define([ this._hasNormals = false; this._hasBatchIds = false; + // Used to regenerate shader when clipping on this tile changes + this._isClipped = true; + // Use per-point normals to hide back-facing points. this.backFaceCulling = false; this._backFaceCulling = false; @@ -518,6 +521,7 @@ define([ } var clippingPlanes = content._tileset.clippingPlanes; + var contentClipped = defined(clippingPlanes) && clippingPlanes.enabled;// && content._tile._isClipped; var scratchCartesian = new Cartesian4(); var uniformMap = { u_pointSizeAndTilesetTime : function() { @@ -537,7 +541,7 @@ define([ u_clippingPlanes : function() { var packedPlanes = content._packedClippingPlanes; - if (defined(clippingPlanes) && clippingPlanes.enabled) { + if (contentClipped) { clippingPlanes.transformAndPackPlanes(content._modelViewMatrix, packedPlanes); } @@ -1251,6 +1255,12 @@ define([ this._parsedContent = undefined; // Unload } + var isClipped = this._tile._isClipped; + if (this._isClipped !== isClipped) { + this._isClipped = isClipped; + createShaders(this, frameState, tileset.style); + } + if (updateModelMatrix) { Matrix4.clone(modelMatrix, this._modelMatrix); if (defined(this._rtcCenter)) { From 627b8d435c2608f249c3b3adb6a038e7a8506acb Mon Sep 17 00:00:00 2001 From: ggetz Date: Mon, 27 Nov 2017 15:54:02 -0500 Subject: [PATCH 22/37] Update clipping planes example --- .../gallery/Terrain Clipping Planes.html | 63 ++++++++++++++++-- .../gallery/Terrain Clipping Planes.jpg | Bin 32991 -> 39605 bytes 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/Apps/Sandcastle/gallery/Terrain Clipping Planes.html b/Apps/Sandcastle/gallery/Terrain Clipping Planes.html index c925f37a2206..0570579157cc 100644 --- a/Apps/Sandcastle/gallery/Terrain Clipping Planes.html +++ b/Apps/Sandcastle/gallery/Terrain Clipping Planes.html @@ -19,16 +19,28 @@

Loading...

-
+
+ Globe clipping planes enabled +
+ + + + + +
+

Loading...

+
+ + + diff --git a/Apps/Sandcastle/gallery/Plane.jpg b/Apps/Sandcastle/gallery/Plane.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4fcb0355e641ece73baf7c0c7e0c205e094f2280 GIT binary patch literal 39003 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 zBr{2HE+A>includeStart('debug', pragmas.debug); @@ -372,7 +372,7 @@ define([ attributes : attributes, indices : indices, primitiveType : PrimitiveType.TRIANGLES, - boundingSphere : new BoundingSphere(Cartesian3.ZERO, Math.sqrt(2.0)) + boundingSphere : new BoundingSphere(Cartesian3.ZERO, Math.sqrt(2.0)/2.0) }); }; diff --git a/Source/Core/PlaneOutlineGeometry.js b/Source/Core/PlaneOutlineGeometry.js index a83ac781782f..d4cb7245b300 100644 --- a/Source/Core/PlaneOutlineGeometry.js +++ b/Source/Core/PlaneOutlineGeometry.js @@ -78,7 +78,6 @@ define([ /** * Computes the geometric representation of an outline of a plane, including its vertices, indices, and a bounding sphere. * - * @param {PlaneOutlineGeometry} planeGeometry A description of the plane outline. * @returns {Geometry|undefined} The computed vertices and indices. */ PlaneOutlineGeometry.createGeometry = function() { diff --git a/Source/DataSources/ModelGraphics.js b/Source/DataSources/ModelGraphics.js index e0e4ec7d2b10..eddda212b0ec 100644 --- a/Source/DataSources/ModelGraphics.js +++ b/Source/DataSources/ModelGraphics.js @@ -252,7 +252,7 @@ define([ * @memberof ModelGraphics.prototype * @type {Property} */ - clippingPlanes: createPropertyDescriptor('clippingPlanes') + clippingPlanes : createPropertyDescriptor('clippingPlanes') }); /** diff --git a/Source/DataSources/PlaneGeometryUpdater.js b/Source/DataSources/PlaneGeometryUpdater.js index 314877f467c8..14877ad8aff2 100644 --- a/Source/DataSources/PlaneGeometryUpdater.js +++ b/Source/DataSources/PlaneGeometryUpdater.js @@ -368,7 +368,19 @@ define([ }; } - var modelMatrix = entity.computeModelMatrix(Iso8601.MINIMUM_VALUE); + var planeGraphics = entity.plane; + var options = this._options; + var modelMatrix = entity.computeModelMatrix(time); + var plane = Property.getValueOrDefault(planeGraphics.plane, time, options.plane); + var dimensions = Property.getValueOrUndefined(planeGraphics.dimensions, time, options.dimensions); + if (!defined(modelMatrix) || !defined(plane) || !defined(dimensions)) { + return; + } + + options.plane = plane; + options.dimensions = dimensions; + + modelMatrix = createPrimitiveMatrix(plane, dimensions, modelMatrix, modelMatrix); return new GeometryInstance({ id : entity, @@ -402,7 +414,19 @@ define([ var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK); var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time); - var modelMatrix = entity.computeModelMatrix(Iso8601.MINIMUM_VALUE); + var planeGraphics = entity.plane; + var options = this._options; + var modelMatrix = entity.computeModelMatrix(time); + var plane = Property.getValueOrDefault(planeGraphics.plane, time, options.plane); + var dimensions = Property.getValueOrUndefined(planeGraphics.dimensions, time, options.dimensions); + if (!defined(modelMatrix) || !defined(plane) || !defined(dimensions)) { + return; + } + + options.plane = plane; + options.dimensions = dimensions; + + modelMatrix = createPrimitiveMatrix(plane, dimensions, modelMatrix, modelMatrix); return new GeometryInstance({ id : entity, @@ -439,7 +463,6 @@ define([ if (!(propertyName === 'availability' || propertyName === 'position' || propertyName === 'orientation' || propertyName === 'plane')) { return; } - var planeGraphics = this._entity.plane; if (!defined(planeGraphics)) { @@ -581,7 +604,7 @@ define([ options.plane = plane; options.dimensions = dimensions; - modelMatrix = createPrimitiveMatrix(plane.normal, plane.distance, dimensions, modelMatrix, modelMatrix); + modelMatrix = createPrimitiveMatrix(plane, dimensions, modelMatrix, modelMatrix); var shadows = this._geometryUpdater.shadowsProperty.getValue(time); @@ -648,16 +671,19 @@ define([ var scratchTranslation = new Cartesian3(); var scratchNormal = new Cartesian3(); var scratchScale = new Cartesian3(); - var defaultDimensions = new Cartesian2(1.0, 1.0); - function createPrimitiveMatrix (normal, distance, dimensions, modelMatrix, result) { - if (!defined(normal)) { - normal = Cartesian3.UNIT_X; - } - if (!defined(distance)) { + function createPrimitiveMatrix (plane, dimensions, modelMatrix, result) { + var normal; + var distance; + if (defined(plane)) { + normal = plane.normal; + distance = plane.distance; + } else { + normal = Cartesian3.clone(Cartesian3.UNIT_X, scratchNormal); distance = 0.0; } + if (!defined(dimensions)) { - dimensions = defaultDimensions; + dimensions = new Cartesian2(1.0, 1.0); } var translation = Cartesian3.multiplyByScalar(normal, distance, scratchTranslation); @@ -674,14 +700,16 @@ define([ } // get a rotation according to a normal + var scratchAxis = new Cartesian3(); + var scratchQuaternion = new Quaternion(); function getRotationMatrix(direction, up) { var angle = Cartesian3.angleBetween(direction, up); if (angle === 0.0) { - return Quaternion.IDENTITY; + return Quaternion.clone(Quaternion.IDENTITY, scratchQuaternion); } - var axis = Cartesian3.cross(up, direction, new Cartesian3()); - return Quaternion.fromAxisAngle(axis, angle); + var axis = Cartesian3.cross(up, direction, scratchAxis); + return Quaternion.fromAxisAngle(axis, angle, scratchQuaternion); } DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) { diff --git a/Source/DataSources/PlaneGraphics.js b/Source/DataSources/PlaneGraphics.js index 38636f7d9406..5e5cf3dab481 100644 --- a/Source/DataSources/PlaneGraphics.js +++ b/Source/DataSources/PlaneGraphics.js @@ -25,20 +25,21 @@ define([ * @param {Object} [options] Object with the following properties: * @param {Property} [options.plane] A {@link Plane} Property specifying the normal and distance for the plane. * @param {Property} [options.dimensions] A {@link Cartesian2} Property specifying the width and height of the plane. - * @param {Property} [options.show=true] A boolean Property specifying the visibility of the box. - * @param {Property} [options.fill=true] A boolean Property specifying whether the box is filled with the provided material. - * @param {MaterialProperty} [options.material=Color.WHITE] A Property specifying the material used to fill the box. - * @param {Property} [options.outline=false] A boolean Property specifying whether the box is outlined. + * @param {Property} [options.show=true] A boolean Property specifying the visibility of the plane. + * @param {Property} [options.fill=true] A boolean Property specifying whether the plane is filled with the provided material. + * @param {MaterialProperty} [options.material=Color.WHITE] A Property specifying the material used to fill the plane. + * @param {Property} [options.outline=false] A boolean Property specifying whether the plane is outlined. * @param {Property} [options.outlineColor=Color.BLACK] A Property specifying the {@link Color} of the outline. * @param {Property} [options.outlineWidth=1.0] A numeric Property specifying the width of the outline. - * @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the box casts or receives shadows from each light source. - * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this box will be displayed. + * @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the plane casts or receives shadows from each light source. + * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this plane will be displayed. * - * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Box.html|Cesium Sandcastle Box Demo} + * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Plane.html|Cesium Sandcastle Plane Demo} */ function PlaneGraphics(options) { this._plane = undefined; this._planeSubscription = undefined; + this._dimensions = undefined; this._dimensionsSubscription = undefined; this._show = undefined; this._showSubscription = undefined; @@ -75,7 +76,7 @@ define([ }, /** - * Gets or sets the boolean Property specifying the visibility of the box. + * Gets or sets the boolean Property specifying the visibility of the plane. * @memberof PlaneGraphics.prototype * @type {Property} * @default true @@ -99,7 +100,7 @@ define([ dimensions : createPropertyDescriptor('dimensions'), /** - * Gets or sets the material used to fill the box. + * Gets or sets the material used to fill the plane. * @memberof PlaneGraphics.prototype * @type {MaterialProperty} * @default Color.WHITE @@ -107,7 +108,7 @@ define([ material : createMaterialPropertyDescriptor('material'), /** - * Gets or sets the boolean Property specifying whether the box is filled with the provided material. + * Gets or sets the boolean Property specifying whether the plane is filled with the provided material. * @memberof PlaneGraphics.prototype * @type {Property} * @default true @@ -115,7 +116,7 @@ define([ fill : createPropertyDescriptor('fill'), /** - * Gets or sets the Property specifying whether the box is outlined. + * Gets or sets the Property specifying whether the plane is outlined. * @memberof PlaneGraphics.prototype * @type {Property} * @default false @@ -139,7 +140,7 @@ define([ outlineWidth : createPropertyDescriptor('outlineWidth'), /** - * Get or sets the enum Property specifying whether the box + * Get or sets the enum Property specifying whether the plane * casts or receives shadows from each light source. * @memberof PlaneGraphics.prototype * @type {Property} @@ -148,7 +149,7 @@ define([ shadows : createPropertyDescriptor('shadows'), /** - * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this box will be displayed. + * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this plane will be displayed. * @memberof PlaneGraphics.prototype * @type {Property} */ diff --git a/Source/Scene/Batched3DModel3DTileContent.js b/Source/Scene/Batched3DModel3DTileContent.js index 27e1c0403b77..42deb3e41ccb 100644 --- a/Source/Scene/Batched3DModel3DTileContent.js +++ b/Source/Scene/Batched3DModel3DTileContent.js @@ -378,7 +378,7 @@ define([ pickUniformMapLoaded : batchTable.getPickUniformMapCallback(), addBatchIdToGeneratedShaders : (batchLength > 0), // If the batch table has values in it, generated shaders will need a batchId attribute pickObject : pickObject, - clippingPlanes : tileset.clippingPlanes + clippingPlanes : tileset.clippingPlanes.clone() }); } @@ -452,7 +452,7 @@ define([ // Update clipping planes var tilesetClippingPlanes = this._tileset.clippingPlanes; if (defined(tilesetClippingPlanes)) { - var modelClippingPlanes = this._model.clippingPlanes.planes; + var modelClippingPlanes = this._model.clippingPlanes; tilesetClippingPlanes.clone(modelClippingPlanes); modelClippingPlanes.enabled = tilesetClippingPlanes.enabled && this._tile._isClipped; } diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index 2a88231f0dc1..adcadce80fc7 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -105,7 +105,7 @@ define([ * @param {Number} [options.skipLevels=1] When skipLevelOfDetail is true, a constant defining the minimum number of levels to skip when loading tiles. When it is 0, no levels are skipped. Used in conjunction with skipScreenSpaceErrorFactor to determine which tiles to load. * @param {Boolean} [options.immediatelyLoadDesiredLevelOfDetail=false] When skipLevelOfDetail is true, only tiles that meet the maximum screen space error will ever be downloaded. Skipping factors are ignored and just the desired tiles are loaded. * @param {Boolean} [options.loadSiblings=false] When skipLevelOfDetail is true, determines whether siblings of visible tiles are always downloaded during traversal. - * @param {ClippingPlaneCollection} [options.clippingPlanes] The {@link ClippingPlanesCollection} used to selectively disable rendering the tileset. + * @param {ClippingPlanesCollection} [options.clippingPlanes] The {@link ClippingPlanesCollection} used to selectively disable rendering the tileset. * @param {Boolean} [options.debugFreezeFrame=false] For debugging only. Determines if only the tiles from last frame should be used for rendering. * @param {Boolean} [options.debugColorizeTiles=false] For debugging only. When true, assigns a random color to each tile. * @param {Boolean} [options.debugWireframe=false] For debugging only. When true, render's each tile's content as a wireframe. diff --git a/Source/Scene/ClippingPlanesCollection.js b/Source/Scene/ClippingPlanesCollection.js index f905131a8925..b43cd5f9c4c1 100644 --- a/Source/Scene/ClippingPlanesCollection.js +++ b/Source/Scene/ClippingPlanesCollection.js @@ -1,27 +1,25 @@ define([ - '../Core/BoundingSphere', - '../Core/buildModuleUrl', - '../Core/Cartesian3', - '../Core/Cartographic', - '../Core/Color', - '../Core/defaultValue', - '../Core/defined', - '../Core/defineProperties', - '../Core/Intersect', - '../Core/Matrix4', - '../Core/Plane' - ], function( - BoundingSphere, - buildModuleUrl, - Cartesian3, - Cartographic, - Color, - defaultValue, - defined, - defineProperties, - Intersect, - Matrix4, - Plane) { + '../Core/Cartesian3', + '../Core/Cartesian4', + '../Core/Check', + '../Core/Color', + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/Intersect', + '../Core/Matrix4', + '../Core/Plane' +], function( + Cartesian3, + Cartesian4, + Check, + Color, + defaultValue, + defined, + defineProperties, + Intersect, + Matrix4, + Plane) { 'use strict'; /** @@ -123,22 +121,32 @@ define([ var scratchMatrix = new Matrix4(); /** * Applies the transformations to each plane and packs it into an array. + * @private * - * @param viewMatrix - * @param [array] + * @param viewMatrix The 4x4 matrix to transform the plane into eyespace. + * @param [array] The array into which the planes will be packed. * @returns {Cartesian4[]} The array of packed planes. */ ClippingPlanesCollection.prototype.transformAndPackPlanes = function(viewMatrix, array) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object('viewMatrix', viewMatrix); + //>>includeEnd('debug'); + var planes = this.planes; var length = planes.length; + var i; if (!defined(array)) { array = new Array(length); + + for (i = 0; i < length; ++i) { + array[i] = new Cartesian4(); + } } var transform = Matrix4.multiply(viewMatrix, this.modelMatrix, scratchMatrix); - for (var i = 0; i < length; ++i) { + for (i = 0; i < length; ++i) { var plane = planes[i]; var packedPlane = array[i]; @@ -155,14 +163,20 @@ define([ * Duplicates this ClippingPlanesCollection instance. * * @param {ClippingPlanesCollection} [result] The object onto which to store the result. - * @returns he modified result parameter or a new ClippingPlanesCollection instance if one was not provided. + * @returns {ClippingPlanesCollection} The modified result parameter or a new ClippingPlanesCollection instance if one was not provided. */ ClippingPlanesCollection.prototype.clone = function(result) { if (!defined(result)) { result = new ClippingPlanesCollection(); } - result.planes = Array.from(this.planes); + var length = this.planes.length; + result.planes = new Array(length); + for (var i = 0; i < length; ++i) { + var plane = this.planes[i]; + result.planes[i] = new Plane(plane.normal, plane.distance); + } + result.enabled = this.enabled; Matrix4.clone(this.modelMatrix, result.modelMatrix); result.combineClippingRegions = this.combineClippingRegions; @@ -173,22 +187,23 @@ define([ }; /** - * Determines the type intersection with the planes of this bounding collection and the specified {@link BoundingVolume}. + * Determines the type intersection with the planes of this ClippingPlanesCollection instance and the specified {@link BoundingVolume}. + * @private * - * @param {BoundingVolume} boundingVolume The volume to determine the intersection with the planes. - * @param {Matrix4} [parentTransform] An optional, additional matrix to transform the plane to world coordinates. + * @param {Object} boundingVolume The volume to determine the intersection with the planes. + * @param {Matrix4} [transform] An optional, additional matrix to transform the plane to world coordinates. * @returns {Intersect} {@link Intersect.INSIDE} if the entire volume is on the side of the planes * the normal is pointing and should be entirely rendered, {@link Intersect.OUTSIDE} * if the entire volume is on the opposite side and should be clipped, and * {@link Intersect.INTERSECTING} if the volume intersects the planes. */ - ClippingPlanesCollection.prototype.computeIntersectionWithBoundingVolume = function(boundingVolume, parentTransform) { + ClippingPlanesCollection.prototype.computeIntersectionWithBoundingVolume = function(boundingVolume, transform) { var planes = this.planes; var length = planes.length; - var transform = this.modelMatrix; - if (defined(parentTransform)) { - transform = Matrix4.multiply(transform, parentTransform, scratchMatrix); + var modelMatrix = this.modelMatrix; + if (defined(transform)) { + modelMatrix = Matrix4.multiply(modelMatrix, transform, scratchMatrix); } // If the clipping planes are using combineClippingRegions, the volume must be outside of all planes to be considered @@ -202,7 +217,7 @@ define([ for (var i = 0; i < length; ++i) { var plane = planes[i]; - Plane.transform(plane, transform, scratchPlane); + Plane.transform(plane, modelMatrix, scratchPlane); var value = boundingVolume.intersectPlane(scratchPlane); if (value === Intersect.INTERSECTING) { diff --git a/Source/Scene/Globe.js b/Source/Scene/Globe.js index 3fd3e79f92f7..a15bba2f77a2 100644 --- a/Source/Scene/Globe.js +++ b/Source/Scene/Globe.js @@ -237,6 +237,7 @@ define([ /** * A property specifying an array of up to 6 {@link Plane} used to selectively disable rendering on the outside of each plane. * + * @memberof Globe.prototype * @type {ClippingPlaneCollection} */ clippingPlanes : { diff --git a/Source/Scene/GlobeSurfaceTileProvider.js b/Source/Scene/GlobeSurfaceTileProvider.js index a785bca7fb5d..a7646e76bde8 100644 --- a/Source/Scene/GlobeSurfaceTileProvider.js +++ b/Source/Scene/GlobeSurfaceTileProvider.js @@ -873,11 +873,10 @@ define([ u_clippingPlanes : function() { return this.properties.clippingPlanes; }, - u_clippingPlanesEdgeColor : function() { - return this.properties.clippingPlanesEdgeColor; - }, - u_clippingPlanesEdgeWidth : function() { - return this.properties.clippingPlanesEdgeWidth; + u_clippingPlanesEdgeStyle : function() { + var style = this.properties.clippingPlanesEdgeColor; + style.alpha = this.properties.clippingPlanesEdgeWidth; + return style; }, // make a separate object so that changes to the properties are seen on @@ -915,7 +914,7 @@ define([ minMaxHeight : new Cartesian2(), scaleAndBias : new Matrix4(), clippingPlanes : [], - clippingPlanesEdgeColor : new Cartesian4(1.0, 1.0, 1.0, 1.0), + clippingPlanesEdgeColor : Color.clone(Color.WHITE), clippingPlanesEdgeWidth : 0.0 } }; @@ -1311,7 +1310,7 @@ define([ if (defined(clippingPlanes) && clippingPlanes.enabled && tile.isClipped) { clippingPlanes.transformAndPackPlanes(context.uniformState.view, clippingPlanesProperty); - uniformMapProperties.clippingPlanesEdgeColor = Cartesian4.fromColor(clippingPlanes.edgeColor, uniformMapProperties.clippingPlanesEdgeColor); + uniformMapProperties.clippingPlanesEdgeColor = Color.clone(clippingPlanes.edgeColor, uniformMapProperties.clippingPlanesEdgeColor); uniformMapProperties.clippingPlanesEdgeWidth = clippingPlanes.edgeWidth; } diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js index 61194c4cbbbb..41ec0cb64115 100644 --- a/Source/Scene/Model.js +++ b/Source/Scene/Model.js @@ -586,6 +586,10 @@ define([ */ this.clippingPlanes = options.clippingPlanes; + // Used to determine if the shader needs to be recompiled + this._clippingPlanesEnabled = false; + this._clippingPlanesCombined = true; + /** * This property is for debugging only; it is not for production use nor is it optimized. *

@@ -2109,11 +2113,7 @@ define([ var premultipliedAlpha = hasPremultipliedAlpha(model); var finalFS = modifyShaderForColor(fs, premultipliedAlpha); - - var clippingPlanes = model.clippingPlanes; - if (defined(clippingPlanes) && clippingPlanes.enabled) { - finalFS = modifyShaderForClippingPlanes(finalFS, model.clippingPlanes.combineClippingRegions); - } + finalFS = modifyShaderForClippingPlanes(finalFS); var drawVS = modifyShader(vs, id, model._vertexShaderLoaded); var drawFS = modifyShader(finalFS, id, model._fragmentShaderLoaded); @@ -3317,37 +3317,40 @@ define([ }; } - function createClippingPlanesFunction(model) { + function createClippingPlanesCombineRegionsFunction(model) { return function() { var clippingPlanes = model.clippingPlanes; - var packedPlanes = model._packedClippingPlanes; - - if (defined(clippingPlanes) && clippingPlanes.enabled) { - clippingPlanes.transformAndPackPlanes(model._modelViewMatrix, packedPlanes); + if (!defined(clippingPlanes)) { + return true; } - return packedPlanes; + return clippingPlanes.combineClippingRegions; }; } - function createClippingPlanesEdgeWidthFunction(model) { + function createClippingPlanesFunction(model) { return function() { - if (!defined(model.clippingPlanes)) { - return 0.0; + var clippingPlanes = model.clippingPlanes; + var packedPlanes = model._packedClippingPlanes; + + if (defined(clippingPlanes) && clippingPlanes.enabled) { + clippingPlanes.transformAndPackPlanes(model._modelViewMatrix, packedPlanes); } - return model.clippingPlanes.edgeWidth; + return packedPlanes; }; } - var scratchCartesian = new Cartesian4(); - function createClippingPlanesEdgeColorFunction(model) { + function createClippingPlanesEdgeStyleFunction(model) { return function() { - if (!defined(model.clippingPlanes)) { - return scratchCartesian; + var clippingPlanes = model.clippingPlanes; + if (!defined(clippingPlanes)) { + return Color.WHITE.withAlpha(0.0); } - return Cartesian4.fromColor(model.clippingPlanes.edgeColor, scratchCartesian); + var style = Color.clone(clippingPlanes.edgeColor); + style.alpha = clippingPlanes.edgeWidth; + return style; }; } @@ -3447,9 +3450,9 @@ define([ gltf_color : createColorFunction(model), gltf_colorBlend : createColorBlendFunction(model), gltf_clippingPlanesLength: createClippingPlanesLengthFunction(model), + gltf_clippingPlanesCombineRegions: createClippingPlanesCombineRegionsFunction(model), gltf_clippingPlanes: createClippingPlanesFunction(model, context), - gltf_clippingPlanesEdgeColor: createClippingPlanesEdgeColorFunction(model), - gltf_clippingPlanesEdgeWidth: createClippingPlanesEdgeWidthFunction(model) + gltf_clippingPlanesEdgeStyle: createClippingPlanesEdgeStyleFunction(model) }); // Allow callback to modify the uniformMap @@ -4243,21 +4246,35 @@ define([ } } - function modifyShaderForClippingPlanes(shader, combineClippingRegions) { + function modifyShaderForClippingPlanes(shader) { shader = ShaderSource.replaceMain(shader, 'gltf_clip_main'); - - var clippingFunction = combineClippingRegions ? 'czm_discardIfClippedCombineRegions' : 'czm_discardIfClipped'; shader += 'uniform int gltf_clippingPlanesLength; \n' + + 'uniform bool gltf_clippingPlanesCombineRegions; \n' + 'uniform vec4 gltf_clippingPlanes[czm_maxClippingPlanes]; \n' + - 'uniform vec4 gltf_clippingPlanesEdgeColor; \n' + - 'uniform float gltf_clippingPlanesEdgeWidth; \n' + + 'uniform vec4 gltf_clippingPlanesEdgeStyle; \n' + 'void main() \n' + '{ \n' + ' gltf_clip_main(); \n' + - ' float clipDistance = ' + clippingFunction + '(gltf_clippingPlanes, gltf_clippingPlanesLength); \n' + - ' if (clipDistance < gltf_clippingPlanesEdgeWidth) { \n' + - ' gl_FragColor = gltf_clippingPlanesEdgeColor; \n' + + ' if (gltf_clippingPlanesLength > 0) \n' + + ' { \n' + + ' float clipDistance; \n' + + ' if (gltf_clippingPlanesCombineRegions) \n' + + ' { \n' + + ' clipDistance = czm_discardIfClippedCombineRegions(gltf_clippingPlanes, gltf_clippingPlanesLength); \n' + + ' } \n' + + ' else \n' + + ' { \n' + + ' clipDistance = czm_discardIfClipped(gltf_clippingPlanes, gltf_clippingPlanesLength); \n' + + ' } \n' + + ' \n' + + ' vec4 clippingPlanesEdgeColor = vec4(1.0); \n' + + ' clippingPlanesEdgeColor.rgb = gltf_clippingPlanesEdgeStyle.rgb; \n' + + ' float clippingPlanesEdgeWidth = gltf_clippingPlanesEdgeStyle.a; \n' + + ' if (clipDistance > 0.0 && clipDistance < clippingPlanesEdgeWidth) \n' + + ' { \n' + + ' gl_FragColor = clippingPlanesEdgeColor; \n' + + ' } \n' + ' } \n' + '} \n'; @@ -4287,9 +4304,9 @@ define([ } function updateClippingPlanes(model) { - var length = 0; var clippingPlanes = model.clippingPlanes; - if (defined(clippingPlanes)) { + var length = 0; + if (defined(clippingPlanes) && clippingPlanes.enabled) { length = clippingPlanes.planes.length; } @@ -4708,7 +4725,6 @@ define([ this._cesiumAnimationsDirty = false; this._dirty = false; var modelMatrix = this.modelMatrix; - var modelViewChanged = context.uniformState._modelViewDirty; var modeChanged = frameState.mode !== this._mode; this._mode = frameState.mode; @@ -4722,8 +4738,6 @@ define([ modeChanged; if (modelTransformChanged || justLoaded) { - modelViewChanged = true; - Matrix4.clone(modelMatrix, this._modelMatrix); updateClamping(this); @@ -4749,7 +4763,7 @@ define([ } var clippingPlanes = this.clippingPlanes; - if (defined(clippingPlanes) && clippingPlanes.enabled && modelViewChanged) { + if (defined(clippingPlanes) && clippingPlanes.enabled) { Matrix4.multiply(context.uniformState.view3D, modelMatrix, this._modelViewMatrix); } @@ -4774,7 +4788,7 @@ define([ updateShadows(this); updateColor(this, frameState); updateSilhouette(this, frameState); - updateClippingPlanes(this); + updateClippingPlanes(this, frameState); } if (justLoaded) { diff --git a/Source/Scene/PointCloud3DTileContent.js b/Source/Scene/PointCloud3DTileContent.js index 7a1de03680c4..5221b2876674 100644 --- a/Source/Scene/PointCloud3DTileContent.js +++ b/Source/Scene/PointCloud3DTileContent.js @@ -521,8 +521,7 @@ define([ } var clippingPlanes = content._tileset.clippingPlanes; - var contentClipped = defined(clippingPlanes) && clippingPlanes.enabled;// && content._tile._isClipped; - var scratchCartesian = new Cartesian4(); + var hasClippedContent = defined(clippingPlanes) && clippingPlanes.enabled && content._tile._isClipped; var uniformMap = { u_pointSizeAndTilesetTime : function() { scratchPointSizeAndTilesetTime.x = content._pointSize; @@ -541,25 +540,20 @@ define([ u_clippingPlanes : function() { var packedPlanes = content._packedClippingPlanes; - if (contentClipped) { + if (hasClippedContent) { clippingPlanes.transformAndPackPlanes(content._modelViewMatrix, packedPlanes); } return packedPlanes; }, - u_clippingPlanesEdgeWidth : function() { + u_clippingPlanesEdgeStyle : function() { if (!defined(clippingPlanes)) { - return 0.0; + return Color.WHITE.withAlpha(0.0); } - return clippingPlanes.edgeWidth; - }, - u_clippingPlanesEdgeColor : function() { - if (!defined(clippingPlanes)) { - return scratchCartesian; - } - - return Cartesian4.fromColor(clippingPlanes.edgeColor, scratchCartesian); + var style = Color.clone(clippingPlanes.edgeColor); + style.alpha = clippingPlanes.edgeWidth; + return style; } }; @@ -855,7 +849,7 @@ define([ var hasColorStyle = defined(colorStyleFunction); var hasShowStyle = defined(showStyleFunction); var hasPointSizeStyle = defined(pointSizeStyleFunction); - var hasClippingPlanes = defined(clippingPlanes) && clippingPlanes.enabled && content._tile._isClipped; + var hasClippedContent = defined(clippingPlanes) && clippingPlanes.enabled && content._tile._isClipped; // Get the properties in use by the style var styleableProperties = []; @@ -1072,22 +1066,25 @@ define([ var fs = 'varying vec4 v_color; \n'; - if (hasClippingPlanes) { + if (hasClippedContent) { fs += 'uniform int u_clippingPlanesLength;' + 'uniform vec4 u_clippingPlanes[czm_maxClippingPlanes]; \n' + - 'uniform vec4 u_clippingPlanesEdgeColor; \n' + - 'uniform float u_clippingPlanesEdgeWidth; \n'; + 'uniform vec4 u_clippingPlanesEdgeStyle; \n'; } fs += 'void main() \n' + '{ \n' + ' gl_FragColor = v_color; \n'; - if (hasClippingPlanes) { + if (hasClippedContent) { var clippingFunction = clippingPlanes.combineClippingRegions ? 'czm_discardIfClippedCombineRegions' : 'czm_discardIfClipped'; fs += ' float clipDistance = ' + clippingFunction + '(u_clippingPlanes, u_clippingPlanesLength); \n' + - ' if (clipDistance < u_clippingPlanesEdgeWidth) { \n' + - ' gl_FragColor = u_clippingPlanesEdgeColor; \n' + + ' vec4 clippingPlanesEdgeColor = vec4(1.0); \n' + + ' clippingPlanesEdgeColor.rgb = u_clippingPlanesEdgeStyle.rgb; \n' + + ' float clippingPlanesEdgeWidth = u_clippingPlanesEdgeStyle.a; \n' + + ' if (clipDistance > 0.0 && clipDistance < clippingPlanesEdgeWidth) \n' + + ' { \n' + + ' gl_FragColor = clippingPlanesEdgeColor; \n' + ' } \n'; } @@ -1228,8 +1225,7 @@ define([ // update clipping planes var clippingPlanes = this._tileset.clippingPlanes; - var modelViewChanged = context.uniformState._modelViewDirty || modelMatrixChanged; - if (defined(clippingPlanes) && clippingPlanes.enabled && modelViewChanged) { + if (defined(clippingPlanes) && clippingPlanes.enabled) { Matrix4.multiply(context.uniformState.view3D, modelMatrix, this._modelViewMatrix); } diff --git a/Source/Shaders/Builtin/Functions/discardIfClipped.glsl b/Source/Shaders/Builtin/Functions/discardIfClipped.glsl index 33896e5cbfc6..74d0c51dc01f 100644 --- a/Source/Shaders/Builtin/Functions/discardIfClipped.glsl +++ b/Source/Shaders/Builtin/Functions/discardIfClipped.glsl @@ -30,13 +30,11 @@ float czm_discardIfClipped (vec4[czm_maxClippingPlanes] clippingPlanes, int clip clipPosition = -clippingPlanes[i].w * clipNormal; float amount = dot(clipNormal, (position.xyz - clipPosition)) / pixelWidth; - if (amount > clipAmount) { - clipAmount = amount; - } + clipAmount = max(amount, clipAmount); - if (amount <= 0.0) { + if (amount <= 0.0) + { discard; - return amount; } } diff --git a/Source/Shaders/Builtin/Functions/discardIfClippedCombineRegions.glsl b/Source/Shaders/Builtin/Functions/discardIfClippedCombineRegions.glsl index 18b007c4c0e7..4709159fddd1 100644 --- a/Source/Shaders/Builtin/Functions/discardIfClippedCombineRegions.glsl +++ b/Source/Shaders/Builtin/Functions/discardIfClippedCombineRegions.glsl @@ -31,9 +31,7 @@ float czm_discardIfClippedCombineRegions (vec4[czm_maxClippingPlanes] clippingPl clipPosition = -clippingPlanes[i].w * clipNormal; float amount = dot(clipNormal, (position.xyz - clipPosition)) / pixelWidth; - if (amount > clipAmount) { - clipAmount = amount; - } + clipAmount = max(amount, clipAmount); clipped = clipped && (amount <= 0.0); } diff --git a/Source/Shaders/GlobeFS.glsl b/Source/Shaders/GlobeFS.glsl index aa47300d6301..056d6a2bf8d5 100644 --- a/Source/Shaders/GlobeFS.glsl +++ b/Source/Shaders/GlobeFS.glsl @@ -55,8 +55,7 @@ uniform vec2 u_lightingFadeDistance; #ifdef ENABLE_CLIPPING_PLANES uniform int u_clippingPlanesLength; uniform vec4 u_clippingPlanes[czm_maxClippingPlanes]; -uniform vec4 u_clippingPlanesEdgeColor; -uniform float u_clippingPlanesEdgeWidth; +uniform vec4 u_clippingPlanesEdgeStyle; #endif varying vec3 v_positionMC; @@ -211,8 +210,12 @@ void main() #endif #ifdef ENABLE_CLIPPING_PLANES - if (clipDistance < u_clippingPlanesEdgeWidth) { - finalColor = u_clippingPlanesEdgeColor; + vec4 clippingPlanesEdgeColor = vec4(1.0); + clippingPlanesEdgeColor.rgb = u_clippingPlanesEdgeStyle.rgb; + float clippingPlanesEdgeWidth = u_clippingPlanesEdgeStyle.a; + + if (clipDistance < clippingPlanesEdgeWidth) { + finalColor = clippingPlanesEdgeColor; } #endif From 92fc2bb6a3cb50aeb1bee37aaca82f834337cd33 Mon Sep 17 00:00:00 2001 From: ggetz Date: Thu, 30 Nov 2017 16:14:55 -0500 Subject: [PATCH 25/37] Fix failing tests --- Source/Scene/Batched3DModel3DTileContent.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Source/Scene/Batched3DModel3DTileContent.js b/Source/Scene/Batched3DModel3DTileContent.js index 42deb3e41ccb..0d083631b10a 100644 --- a/Source/Scene/Batched3DModel3DTileContent.js +++ b/Source/Scene/Batched3DModel3DTileContent.js @@ -377,9 +377,12 @@ define([ pickFragmentShaderLoaded : batchTable.getPickFragmentShaderCallback(), pickUniformMapLoaded : batchTable.getPickUniformMapCallback(), addBatchIdToGeneratedShaders : (batchLength > 0), // If the batch table has values in it, generated shaders will need a batchId attribute - pickObject : pickObject, - clippingPlanes : tileset.clippingPlanes.clone() + pickObject : pickObject }); + + if (defined(tileset.clippingPlanes)) { + content._model.clippingPlanes = tileset.clippingPlanes.clone(); + } } function createFeatures(content) { From 0af0db6c46cfa906f43081be8c64781ff4e26b01 Mon Sep 17 00:00:00 2001 From: ggetz Date: Thu, 30 Nov 2017 17:07:36 -0500 Subject: [PATCH 26/37] Specs --- Source/Core/PlaneOutlineGeometry.js | 1 + Source/DataSources/PlaneGeometryUpdater.js | 2 +- Source/Scene/Batched3DModel3DTileContent.js | 7 +- Source/Scene/ClippingPlanesCollection.js | 1 + Source/Scene/PointCloud3DTileContent.js | 8 +- Specs/Core/PlaneGeometrySpec.js | 44 ++ Specs/Core/PlaneOutlineGeometrySpec.js | 19 + Specs/Core/PlaneSpec.js | 38 +- Specs/DataSources/EntitySpec.js | 5 + Specs/DataSources/ModelGraphicsSpec.js | 13 + Specs/DataSources/ModelVisualizerSpec.js | 12 + Specs/DataSources/PlaneGeometryUpdaterSpec.js | 491 ++++++++++++++++++ .../Scene/Batched3DModel3DTileContentSpec.js | 32 ++ Specs/Scene/Cesium3DTilesetSpec.js | 57 ++ Specs/Scene/ClippingPlanesCollectionSpec.js | 175 +++++++ Specs/Scene/GlobeSurfaceTileProviderSpec.js | 120 +++++ Specs/Scene/PointCloud3DTileContentSpec.js | 119 +++++ 17 files changed, 1135 insertions(+), 9 deletions(-) create mode 100644 Specs/Core/PlaneGeometrySpec.js create mode 100644 Specs/Core/PlaneOutlineGeometrySpec.js create mode 100644 Specs/DataSources/PlaneGeometryUpdaterSpec.js create mode 100644 Specs/Scene/ClippingPlanesCollectionSpec.js diff --git a/Source/Core/PlaneOutlineGeometry.js b/Source/Core/PlaneOutlineGeometry.js index d4cb7245b300..29f6cf0d3654 100644 --- a/Source/Core/PlaneOutlineGeometry.js +++ b/Source/Core/PlaneOutlineGeometry.js @@ -49,6 +49,7 @@ define([ */ PlaneOutlineGeometry.pack = function(value, array) { //>>includeStart('debug', pragmas.debug); + Check.defined('value', value); Check.defined('array', array); //>>includeEnd('debug'); diff --git a/Source/DataSources/PlaneGeometryUpdater.js b/Source/DataSources/PlaneGeometryUpdater.js index 14877ad8aff2..6e18f19bdec4 100644 --- a/Source/DataSources/PlaneGeometryUpdater.js +++ b/Source/DataSources/PlaneGeometryUpdater.js @@ -497,7 +497,7 @@ define([ var position = entity.position; var show = planeGraphics.show; - if (!defined(plane) || !defined(position) || (defined(show) && show.isConstant && !show.getValue(Iso8601.MINIMUM_VALUE))) { + if (!defined(plane) || !defined(dimensions) || !defined(position) || (defined(show) && show.isConstant && !show.getValue(Iso8601.MINIMUM_VALUE))) { if (this._fillEnabled || this._outlineEnabled) { this._fillEnabled = false; this._outlineEnabled = false; diff --git a/Source/Scene/Batched3DModel3DTileContent.js b/Source/Scene/Batched3DModel3DTileContent.js index 0d083631b10a..6013c32f9727 100644 --- a/Source/Scene/Batched3DModel3DTileContent.js +++ b/Source/Scene/Batched3DModel3DTileContent.js @@ -14,6 +14,7 @@ define([ '../Core/RequestType', '../Core/RuntimeError', '../Renderer/Pass', + '../Scene/ClippingPlanesCollection', './Cesium3DTileBatchTable', './Cesium3DTileFeature', './Cesium3DTileFeatureTable', @@ -35,6 +36,7 @@ define([ RequestType, RuntimeError, Pass, + ClippingPlanesCollections, Cesium3DTileBatchTable, Cesium3DTileFeature, Cesium3DTileFeatureTable, @@ -377,7 +379,10 @@ define([ pickFragmentShaderLoaded : batchTable.getPickFragmentShaderCallback(), pickUniformMapLoaded : batchTable.getPickUniformMapCallback(), addBatchIdToGeneratedShaders : (batchLength > 0), // If the batch table has values in it, generated shaders will need a batchId attribute - pickObject : pickObject + pickObject : pickObject, + clippingPlanes : new ClippingPlanesCollections({ + enabled : false + }) }); if (defined(tileset.clippingPlanes)) { diff --git a/Source/Scene/ClippingPlanesCollection.js b/Source/Scene/ClippingPlanesCollection.js index b43cd5f9c4c1..b3fa262dd030 100644 --- a/Source/Scene/ClippingPlanesCollection.js +++ b/Source/Scene/ClippingPlanesCollection.js @@ -80,6 +80,7 @@ define([ this.edgeWidth = defaultValue(options.edgeWidth, 0.0); this._testIntersection = undefined; + this._combineClippingRegions = undefined; this.combineClippingRegions = defaultValue(options.combineClippingRegions, true); } diff --git a/Source/Scene/PointCloud3DTileContent.js b/Source/Scene/PointCloud3DTileContent.js index 5221b2876674..69aa39d970ad 100644 --- a/Source/Scene/PointCloud3DTileContent.js +++ b/Source/Scene/PointCloud3DTileContent.js @@ -1225,13 +1225,11 @@ define([ // update clipping planes var clippingPlanes = this._tileset.clippingPlanes; - if (defined(clippingPlanes) && clippingPlanes.enabled) { - Matrix4.multiply(context.uniformState.view3D, modelMatrix, this._modelViewMatrix); - } - + var clippingEnabled = defined(clippingPlanes) && clippingPlanes.enabled && this._tile._isClipped; var length = 0; - if (defined(clippingPlanes)) { + if (clippingEnabled) { length = clippingPlanes.planes.length; + Matrix4.multiply(context.uniformState.view3D, modelMatrix, this._modelViewMatrix); } if (this._packedClippingPlanes.length !== length) { diff --git a/Specs/Core/PlaneGeometrySpec.js b/Specs/Core/PlaneGeometrySpec.js new file mode 100644 index 000000000000..0a5561fb4389 --- /dev/null +++ b/Specs/Core/PlaneGeometrySpec.js @@ -0,0 +1,44 @@ +defineSuite([ + 'Core/PlaneGeometry', + 'Core/Cartesian3', + 'Core/VertexFormat', + 'Specs/createPackableSpecs' + ], function( + PlaneGeometry, + Cartesian3, + VertexFormat, + createPackableSpecs) { + 'use strict'; + + it('constructor creates optimized number of positions for VertexFormat.POSITIONS_ONLY', function() { + var m = PlaneGeometry.createGeometry(new PlaneGeometry({ + vertexFormat : VertexFormat.POSITION_ONLY + })); + + expect(m.attributes.position.values.length).toEqual(4 * 3); // 4 corners + expect(m.indices.length).toEqual(4 * 3); // 2 sides x 2 triangles per side + }); + + it('constructor computes all vertex attributes', function() { + var m = PlaneGeometry.createGeometry(new PlaneGeometry({ + vertexFormat : VertexFormat.ALL + })); + + var numVertices = 8; //2 sides x 4 corners + var numTriangles = 4; //2 sides x 2 triangles per side + expect(m.attributes.position.values.length).toEqual(numVertices * 3); + expect(m.attributes.normal.values.length).toEqual(numVertices * 3); + expect(m.attributes.tangent.values.length).toEqual(numVertices * 3); + expect(m.attributes.bitangent.values.length).toEqual(numVertices * 3); + expect(m.attributes.st.values.length).toEqual(numVertices * 2); + + expect(m.indices.length).toEqual(numTriangles * 3); + + expect(m.boundingSphere.center).toEqual(Cartesian3.ZERO); + expect(m.boundingSphere.radius).toEqual(Math.sqrt(2.0)/2.0); + }); + + createPackableSpecs(PlaneGeometry, new PlaneGeometry({ + vertexFormat : VertexFormat.POSITION_AND_NORMAL + }), [1.0, 1.0, 0.0, 0.0, 0.0, 0.0]); +}); diff --git a/Specs/Core/PlaneOutlineGeometrySpec.js b/Specs/Core/PlaneOutlineGeometrySpec.js new file mode 100644 index 000000000000..f1f1d49f6dc0 --- /dev/null +++ b/Specs/Core/PlaneOutlineGeometrySpec.js @@ -0,0 +1,19 @@ +defineSuite([ + 'Core/PlaneOutlineGeometry', + 'Core/Cartesian3', + 'Specs/createPackableSpecs' + ], function( + PlaneOutlineGeometry, + Cartesian3, + createPackableSpecs) { + 'use strict'; + + it('constructor creates positions', function() { + var m = PlaneOutlineGeometry.createGeometry(new PlaneOutlineGeometry()); + + expect(m.attributes.position.values.length).toEqual(4 * 3); + expect(m.indices.length).toEqual(4 * 2); + }); + + createPackableSpecs(PlaneOutlineGeometry, new PlaneOutlineGeometry(), []); +}); diff --git a/Specs/Core/PlaneSpec.js b/Specs/Core/PlaneSpec.js index 8e28801b179f..94c851016ba4 100644 --- a/Specs/Core/PlaneSpec.js +++ b/Specs/Core/PlaneSpec.js @@ -1,11 +1,17 @@ defineSuite([ 'Core/Plane', 'Core/Cartesian3', - 'Core/Cartesian4' + 'Core/Cartesian4', + 'Core/Math', + 'Core/Matrix3', + 'Core/Matrix4' ], function( Plane, Cartesian3, - Cartesian4) { + Cartesian4, + CesiumMath, + Matrix3, + Matrix4) { 'use strict'; it('constructs', function() { @@ -122,4 +128,32 @@ defineSuite([ return Plane.getPointDistance(plane, undefined); }).toThrowDeveloperError(); }); + + it('transforms a plane according to a transform', function() { + var normal = new Cartesian3(1.0, 2.0, 3.0); + normal = Cartesian3.normalize(normal, normal); + var plane = new Plane(normal, 12.34); + + var transform = Matrix4.fromUniformScale(2.0); + transform = Matrix4.multiplyByMatrix3(transform, Matrix3.fromRotationY(Math.PI), transform); + var transformedPlane = Plane.transform(plane, transform); + expect(transformedPlane.distance).toEqual(-plane.distance * 2.0); + expect(transformedPlane.normal.x).toEqualEpsilon(-plane.normal.x, CesiumMath.EPSILON10); + expect(transformedPlane.normal.y).toEqual(plane.normal.y); + expect(transformedPlane.normal.z).toEqual(-plane.normal.z); + }); + + it('transform throws without a plane', function() { + var point = Cartesian3.ZERO; + expect(function() { + return Plane.transform(undefined, point); + }).toThrowDeveloperError(); + }); + + it('transform throws without a point', function() { + var plane = new Plane(Cartesian3.UNIT_X, 0.0); + expect(function() { + return Plane.transform(plane, undefined); + }).toThrowDeveloperError(); + }); }); diff --git a/Specs/DataSources/EntitySpec.js b/Specs/DataSources/EntitySpec.js index 5a4a4eb4738a..3ff931561312 100644 --- a/Specs/DataSources/EntitySpec.js +++ b/Specs/DataSources/EntitySpec.js @@ -19,6 +19,7 @@ defineSuite([ 'DataSources/LabelGraphics', 'DataSources/ModelGraphics', 'DataSources/PathGraphics', + 'DataSources/PlaneGraphics', 'DataSources/PointGraphics', 'DataSources/PolygonGraphics', 'DataSources/PolylineGraphics', @@ -46,6 +47,7 @@ defineSuite([ LabelGraphics, ModelGraphics, PathGraphics, + PlaneGraphics, PointGraphics, PolygonGraphics, PolylineGraphics, @@ -69,6 +71,7 @@ defineSuite([ expect(entity.model).toBeUndefined(); expect(entity.orientation).toBeUndefined(); expect(entity.path).toBeUndefined(); + expect(entity.plane).toBeUndefined(); expect(entity.point).toBeUndefined(); expect(entity.polygon).toBeUndefined(); expect(entity.polyline).toBeUndefined(); @@ -97,6 +100,7 @@ defineSuite([ model : {}, orientation : new Quaternion(1, 2, 3, 4), path : {}, + plane : {}, point : {}, polygon : {}, polyline : {}, @@ -126,6 +130,7 @@ defineSuite([ expect(entity.model).toBeInstanceOf(ModelGraphics); expect(entity.orientation).toBeInstanceOf(ConstantProperty); expect(entity.path).toBeInstanceOf(PathGraphics); + expect(entity.plane).toBeInstanceOf(PlaneGraphics); expect(entity.point).toBeInstanceOf(PointGraphics); expect(entity.polygon).toBeInstanceOf(PolygonGraphics); expect(entity.polyline).toBeInstanceOf(PolylineGraphics); diff --git a/Specs/DataSources/ModelGraphicsSpec.js b/Specs/DataSources/ModelGraphicsSpec.js index c5db38a7d99c..bda0e8cb9c73 100644 --- a/Specs/DataSources/ModelGraphicsSpec.js +++ b/Specs/DataSources/ModelGraphicsSpec.js @@ -9,6 +9,7 @@ defineSuite([ 'DataSources/NodeTransformationProperty', 'DataSources/PropertyBag', 'Scene/ColorBlendMode', + 'Scene/ClippingPlanesCollection', 'Scene/HeightReference', 'Scene/ShadowMode' ], function( @@ -22,6 +23,7 @@ defineSuite([ NodeTransformationProperty, PropertyBag, ColorBlendMode, + ClippingPlanesCollection, HeightReference, ShadowMode) { 'use strict'; @@ -43,6 +45,7 @@ defineSuite([ color : new Color(0.0, 1.0, 0.0, 0.2), colorBlendMode : ColorBlendMode.HIGHLIGHT, colorBlendAmount : 0.5, + clippingPlanes : new ClippingPlanesCollection(), nodeTransformations : { node1 : { translation : Cartesian3.UNIT_Y, @@ -67,6 +70,7 @@ defineSuite([ expect(model.color).toBeInstanceOf(ConstantProperty); expect(model.colorBlendMode).toBeInstanceOf(ConstantProperty); expect(model.colorBlendAmount).toBeInstanceOf(ConstantProperty); + expect(model.clippingPlanes).toBeInstanceOf(ConstantProperty); expect(model.runAnimations).toBeInstanceOf(ConstantProperty); expect(model.nodeTransformations).toBeInstanceOf(PropertyBag); @@ -85,6 +89,7 @@ defineSuite([ expect(model.color.getValue()).toEqual(options.color); expect(model.colorBlendMode.getValue()).toEqual(options.colorBlendMode); expect(model.colorBlendAmount.getValue()).toEqual(options.colorBlendAmount); + expect(model.clippingPlanes.getValue().planes).toEqual(options.clippingPlanes.planes); expect(model.runAnimations.getValue()).toEqual(options.runAnimations); var actualNodeTransformations = model.nodeTransformations.getValue(new JulianDate()); @@ -112,6 +117,7 @@ defineSuite([ source.color = new ConstantProperty(new Color(0.0, 1.0, 0.0, 0.2)); source.colorBlendMode = new ConstantProperty(ColorBlendMode.HIGHLIGHT); source.colorBlendAmount = new ConstantProperty(0.5); + source.clippingPlanes = new ConstantProperty(new ClippingPlanesCollection()); source.runAnimations = new ConstantProperty(true); source.nodeTransformations = { node1 : new NodeTransformationProperty({ @@ -141,6 +147,7 @@ defineSuite([ expect(target.color).toBe(source.color); expect(target.colorBlendMode).toBe(source.colorBlendMode); expect(target.colorBlendAmount).toBe(source.colorBlendAmount); + expect(target.clippingPlanes).toBe(source.clippingPlanes); expect(target.runAnimations).toBe(source.runAnimations); expect(target.nodeTransformations).toEqual(source.nodeTransformations); }); @@ -161,6 +168,7 @@ defineSuite([ source.color = new ConstantProperty(new Color(0.0, 1.0, 0.0, 0.2)); source.colorBlendMode = new ConstantProperty(ColorBlendMode.HIGHLIGHT); source.colorBlendAmount = new ConstantProperty(0.5); + source.clippingPlanes = new ConstantProperty(new ClippingPlanesCollection()); source.runAnimations = new ConstantProperty(true); source.nodeTransformations = { transform : new NodeTransformationProperty() @@ -180,6 +188,7 @@ defineSuite([ var color = new ConstantProperty(new Color(0.0, 1.0, 0.0, 0.2)); var colorBlendMode = new ConstantProperty(ColorBlendMode.HIGHLIGHT); var colorBlendAmount = new ConstantProperty(0.5); + var clippingPlanes = new ConstantProperty(new ClippingPlanesCollection()); var runAnimations = new ConstantProperty(true); var nodeTransformations = new PropertyBag({ transform : new NodeTransformationProperty() @@ -200,6 +209,7 @@ defineSuite([ target.color = color; target.colorBlendMode = colorBlendMode; target.colorBlendAmount = colorBlendAmount; + target.clippingPlanes = clippingPlanes; target.runAnimations = runAnimations; target.nodeTransformations = nodeTransformations; @@ -219,6 +229,7 @@ defineSuite([ expect(target.color).toBe(color); expect(target.colorBlendMode).toBe(colorBlendMode); expect(target.colorBlendAmount).toBe(colorBlendAmount); + expect(target.clippingPlanes).toBe(clippingPlanes); expect(target.runAnimations).toBe(runAnimations); expect(target.nodeTransformations).toBe(nodeTransformations); }); @@ -239,6 +250,7 @@ defineSuite([ source.color = new ConstantProperty(new Color(0.0, 1.0, 0.0, 0.2)); source.colorBlendMode = new ConstantProperty(ColorBlendMode.HIGHLIGHT); source.colorBlendAmount = new ConstantProperty(0.5); + source.clippingPlanes = new ConstantProperty(new ClippingPlanesCollection()); source.runAnimations = new ConstantProperty(true); source.nodeTransformations = { node1 : new NodeTransformationProperty(), @@ -260,6 +272,7 @@ defineSuite([ expect(result.color).toBe(source.color); expect(result.colorBlendMode).toBe(source.colorBlendMode); expect(result.colorBlendAmount).toBe(source.colorBlendAmount); + expect(result.clippingPlanes).toBe(source.clippingPlanes); expect(result.runAnimations).toBe(source.runAnimations); expect(result.nodeTransformations).toEqual(source.nodeTransformations); }); diff --git a/Specs/DataSources/ModelVisualizerSpec.js b/Specs/DataSources/ModelVisualizerSpec.js index 61fa33d7598d..d76cbb795e46 100644 --- a/Specs/DataSources/ModelVisualizerSpec.js +++ b/Specs/DataSources/ModelVisualizerSpec.js @@ -6,6 +6,7 @@ defineSuite([ 'Core/DistanceDisplayCondition', 'Core/JulianDate', 'Core/Matrix4', + 'Core/Plane', 'Core/Quaternion', 'Core/Transforms', 'DataSources/BoundingSphereState', @@ -14,6 +15,7 @@ defineSuite([ 'DataSources/EntityCollection', 'DataSources/ModelGraphics', 'DataSources/NodeTransformationProperty', + 'Scene/ClippingPlanesCollection', 'Scene/Globe', 'Specs/createScene', 'Specs/pollToPromise' @@ -25,6 +27,7 @@ defineSuite([ DistanceDisplayCondition, JulianDate, Matrix4, + Plane, Quaternion, Transforms, BoundingSphereState, @@ -33,6 +36,7 @@ defineSuite([ EntityCollection, ModelGraphics, NodeTransformationProperty, + ClippingPlanesCollection, Globe, createScene, pollToPromise) { @@ -136,6 +140,13 @@ defineSuite([ }; model.nodeTransformations = nodeTransforms; + var clippingPlanes = new ClippingPlanesCollection({ + planes: [ + new Plane(Cartesian3.UNIT_X, 0.0) + ] + }); + model.clippingPlanes = new ConstantProperty(clippingPlanes); + var testObject = entityCollection.getOrCreateEntity('test'); testObject.position = new ConstantPositionProperty(Cartesian3.fromDegrees(1, 2, 3)); testObject.model = model; @@ -151,6 +162,7 @@ defineSuite([ expect(primitive.minimumPixelSize).toEqual(24.0); expect(primitive.modelMatrix).toEqual(Transforms.eastNorthUpToFixedFrame(Cartesian3.fromDegrees(1, 2, 3), scene.globe.ellipsoid)); expect(primitive.distanceDisplayCondition).toEqual(new DistanceDisplayCondition(10.0, 100.0)); + expect(primitive.clippingPlanes.planes).toEqual(clippingPlanes.planes); // wait till the model is loaded before we can check node transformations return pollToPromise(function() { diff --git a/Specs/DataSources/PlaneGeometryUpdaterSpec.js b/Specs/DataSources/PlaneGeometryUpdaterSpec.js new file mode 100644 index 000000000000..4e548da1d8f1 --- /dev/null +++ b/Specs/DataSources/PlaneGeometryUpdaterSpec.js @@ -0,0 +1,491 @@ +defineSuite([ + 'DataSources/PlaneGeometryUpdater', + 'Core/Cartesian2', + 'Core/Cartesian3', + 'Core/Color', + 'Core/ColorGeometryInstanceAttribute', + 'Core/DistanceDisplayCondition', + 'Core/DistanceDisplayConditionGeometryInstanceAttribute', + 'Core/JulianDate', + 'Core/Plane', + 'Core/ShowGeometryInstanceAttribute', + 'Core/TimeInterval', + 'Core/TimeIntervalCollection', + 'DataSources/PlaneGraphics', + 'DataSources/ColorMaterialProperty', + 'DataSources/ConstantPositionProperty', + 'DataSources/ConstantProperty', + 'DataSources/Entity', + 'DataSources/GridMaterialProperty', + 'DataSources/SampledProperty', + 'DataSources/TimeIntervalCollectionProperty', + 'Scene/PrimitiveCollection', + 'Scene/ShadowMode', + 'Specs/createDynamicGeometryBoundingSphereSpecs', + 'Specs/createDynamicProperty', + 'Specs/createScene' +], function( + PlaneGeometryUpdater, + Cartesian2, + Cartesian3, + Color, + ColorGeometryInstanceAttribute, + DistanceDisplayCondition, + DistanceDisplayConditionGeometryInstanceAttribute, + JulianDate, + Plane, + ShowGeometryInstanceAttribute, + TimeInterval, + TimeIntervalCollection, + PlaneGraphics, + ColorMaterialProperty, + ConstantPositionProperty, + ConstantProperty, + Entity, + GridMaterialProperty, + SampledProperty, + TimeIntervalCollectionProperty, + PrimitiveCollection, + ShadowMode, + createDynamicGeometryBoundingSphereSpecs, + createDynamicProperty, + createScene) { + 'use strict'; + + var scene; + var time; + + beforeAll(function() { + scene = createScene(); + time = JulianDate.now(); + }); + + afterAll(function() { + scene.destroyForSpecs(); + }); + + function createBasicPlane() { + var planeGraphics = new PlaneGraphics(); + planeGraphics.plane = new ConstantProperty(new Plane(Cartesian3.UNIT_X, 0.0)); + planeGraphics.dimensions = new ConstantProperty(new Cartesian2(1.0, 2.0)); + var entity = new Entity(); + entity.position = new ConstantPositionProperty(Cartesian3.fromDegrees(0, 0, 0)); + entity.plane = planeGraphics; + return entity; + } + + it('Constructor sets expected defaults', function() { + var entity = new Entity(); + var updater = new PlaneGeometryUpdater(entity, scene); + + expect(updater.isDestroyed()).toBe(false); + expect(updater.entity).toBe(entity); + expect(updater.isClosed).toBe(true); + expect(updater.fillEnabled).toBe(false); + expect(updater.fillMaterialProperty).toBe(undefined); + expect(updater.outlineEnabled).toBe(false); + expect(updater.hasConstantFill).toBe(true); + expect(updater.hasConstantOutline).toBe(true); + expect(updater.outlineColorProperty).toBe(undefined); + expect(updater.outlineWidth).toBe(1.0); + expect(updater.shadowsProperty).toBe(undefined); + expect(updater.distanceDisplayConditionProperty).toBe(undefined); + expect(updater.isDynamic).toBe(false); + expect(updater.isOutlineVisible(time)).toBe(false); + expect(updater.isFilled(time)).toBe(false); + updater.destroy(); + expect(updater.isDestroyed()).toBe(true); + }); + + it('No geometry available when plane is undefined ', function() { + var entity = createBasicPlane(); + var updater = new PlaneGeometryUpdater(entity, scene); + entity.plane = undefined; + + expect(updater.fillEnabled).toBe(false); + expect(updater.outlineEnabled).toBe(false); + expect(updater.isDynamic).toBe(false); + }); + + it('No geometry available when not filled or outline.', function() { + var entity = createBasicPlane(); + var updater = new PlaneGeometryUpdater(entity, scene); + entity.plane.fill = new ConstantProperty(false); + entity.plane.outline = new ConstantProperty(false); + + expect(updater.fillEnabled).toBe(false); + expect(updater.outlineEnabled).toBe(false); + expect(updater.isDynamic).toBe(false); + }); + + it('Values correct when using default graphics', function() { + var entity = createBasicPlane(); + var updater = new PlaneGeometryUpdater(entity, scene); + + expect(updater.isClosed).toBe(true); + expect(updater.fillEnabled).toBe(true); + expect(updater.fillMaterialProperty).toEqual(new ColorMaterialProperty(Color.WHITE)); + expect(updater.outlineEnabled).toBe(false); + expect(updater.hasConstantFill).toBe(true); + expect(updater.hasConstantOutline).toBe(true); + expect(updater.outlineColorProperty).toBe(undefined); + expect(updater.outlineWidth).toBe(1.0); + expect(updater.shadowsProperty).toEqual(new ConstantProperty(ShadowMode.DISABLED)); + expect(updater.distanceDisplayConditionProperty).toEqual(new ConstantProperty(new DistanceDisplayCondition())); + expect(updater.isDynamic).toBe(false); + }); + + it('Plane material is correctly exposed.', function() { + var entity = createBasicPlane(); + var updater = new PlaneGeometryUpdater(entity, scene); + entity.plane.material = new GridMaterialProperty(Color.BLUE); + expect(updater.fillMaterialProperty).toBe(entity.plane.material); + }); + + it('A time-varying outlineWidth causes geometry to be dynamic', function() { + var entity = createBasicPlane(); + var updater = new PlaneGeometryUpdater(entity, scene); + entity.plane.outlineWidth = createDynamicProperty(); + expect(updater.isDynamic).toBe(true); + }); + + it('A time-varying plane causes geometry to be dynamic', function() { + var entity = createBasicPlane(); + var updater = new PlaneGeometryUpdater(entity, scene); + entity.plane.plane = createDynamicProperty(); + + expect(updater.isDynamic).toBe(true); + }); + + it('A time-varying dimensions causes geometry to be dynamic', function() { + var entity = createBasicPlane(); + var updater = new PlaneGeometryUpdater(entity, scene); + entity.plane.dimensions = createDynamicProperty(); + + expect(updater.isDynamic).toBe(true); + }); + + function validateGeometryInstance(options) { + var entity = createBasicPlane(); + + var plane = entity.plane; + plane.show = new ConstantProperty(options.show); + plane.fill = new ConstantProperty(options.fill); + plane.material = options.material; + plane.outline = new ConstantProperty(options.outline); + plane.outlineColor = new ConstantProperty(options.outlineColor); + plane.plane = new ConstantProperty(options.plane); + plane.dimensions = new ConstantProperty(options.dimensions); + plane.distanceDisplayCondition = options.distanceDisplayCondition; + + var updater = new PlaneGeometryUpdater(entity, scene); + + var instance; + var geometry; + var attributes; + if (options.fill) { + instance = updater.createFillGeometryInstance(time); + + attributes = instance.attributes; + if (options.material instanceof ColorMaterialProperty) { + expect(attributes.color.value).toEqual(ColorGeometryInstanceAttribute.toValue(options.material.color.getValue(time))); + } else { + expect(attributes.color).toBeUndefined(); + } + expect(attributes.show.value).toEqual(ShowGeometryInstanceAttribute.toValue(options.fill)); + if (options.distanceDisplayCondition) { + expect(attributes.distanceDisplayCondition.value).toEqual(DistanceDisplayConditionGeometryInstanceAttribute.toValue(options.distanceDisplayCondition)); + } + } + + if (options.outline) { + instance = updater.createOutlineGeometryInstance(time); + + attributes = instance.attributes; + expect(attributes.color.value).toEqual(ColorGeometryInstanceAttribute.toValue(options.outlineColor)); + expect(attributes.show.value).toEqual(ShowGeometryInstanceAttribute.toValue(options.fill)); + if (options.distanceDisplayCondition) { + expect(attributes.distanceDisplayCondition.value).toEqual(DistanceDisplayConditionGeometryInstanceAttribute.toValue(options.distanceDisplayCondition)); + } + } + } + + it('Creates expected per-color geometry', function() { + validateGeometryInstance({ + show : true, + material : new ColorMaterialProperty(Color.RED), + fill : true, + outline : true, + outlineColor : Color.BLUE, + plane : new Plane(Cartesian3.UNIT_X, 0.0), + dimensions : new Cartesian2(1.0, 2.0) + }); + }); + + it('Creates expected per-material geometry', function() { + validateGeometryInstance({ + show : true, + material : new GridMaterialProperty(), + fill : true, + outline : true, + outlineColor : Color.BLUE, + plane : new Plane(Cartesian3.UNIT_X, 0.0), + dimensions : new Cartesian2(1.0, 2.0) + }); + }); + + it('Creates expected distance display condition geometry', function() { + validateGeometryInstance({ + show : true, + material : new ColorMaterialProperty(Color.RED), + fill : true, + outline : true, + outlineColor : Color.BLUE, + plane : new Plane(Cartesian3.UNIT_X, 0.0), + dimensions : new Cartesian2(1.0, 2.0), + distanceDisplayCondition : new DistanceDisplayCondition(10.0, 100.0) + }); + }); + + it('Correctly exposes outlineWidth', function() { + var entity = createBasicPlane(); + entity.plane.outlineWidth = new ConstantProperty(8); + var updater = new PlaneGeometryUpdater(entity, scene); + expect(updater.outlineWidth).toBe(8); + }); + + it('Attributes have expected values at creation time', function() { + var time1 = new JulianDate(0, 0); + var time2 = new JulianDate(10, 0); + var time3 = new JulianDate(20, 0); + + var fill = new TimeIntervalCollectionProperty(); + fill.intervals.addInterval(new TimeInterval({ + start : time1, + stop : time2, + data : false + })); + fill.intervals.addInterval(new TimeInterval({ + start : time2, + stop : time3, + isStartIncluded : false, + data : true + })); + + var colorMaterial = new ColorMaterialProperty(); + colorMaterial.color = new SampledProperty(Color); + colorMaterial.color.addSample(time, Color.YELLOW); + colorMaterial.color.addSample(time2, Color.BLUE); + colorMaterial.color.addSample(time3, Color.RED); + + var outline = new TimeIntervalCollectionProperty(); + outline.intervals.addInterval(new TimeInterval({ + start : time1, + stop : time2, + data : false + })); + outline.intervals.addInterval(new TimeInterval({ + start : time2, + stop : time3, + isStartIncluded : false, + data : true + })); + + var outlineColor = new SampledProperty(Color); + outlineColor.addSample(time, Color.BLUE); + outlineColor.addSample(time2, Color.RED); + outlineColor.addSample(time3, Color.YELLOW); + + var entity = createBasicPlane(); + entity.plane.fill = fill; + entity.plane.material = colorMaterial; + entity.plane.outline = outline; + entity.plane.outlineColor = outlineColor; + + var updater = new PlaneGeometryUpdater(entity, scene); + + var instance = updater.createFillGeometryInstance(time2); + var attributes = instance.attributes; + expect(attributes.color.value).toEqual(ColorGeometryInstanceAttribute.toValue(colorMaterial.color.getValue(time2))); + expect(attributes.show.value).toEqual(ShowGeometryInstanceAttribute.toValue(fill.getValue(time2))); + + instance = updater.createOutlineGeometryInstance(time2); + attributes = instance.attributes; + expect(attributes.color.value).toEqual(ColorGeometryInstanceAttribute.toValue(outlineColor.getValue(time2))); + expect(attributes.show.value).toEqual(ShowGeometryInstanceAttribute.toValue(outline.getValue(time2))); + }); + + it('createFillGeometryInstance obeys Entity.show is false.', function() { + var entity = createBasicPlane(); + entity.show = false; + entity.plane.fill = true; + var updater = new PlaneGeometryUpdater(entity, scene); + var instance = updater.createFillGeometryInstance(new JulianDate()); + var attributes = instance.attributes; + expect(attributes.show.value).toEqual(ShowGeometryInstanceAttribute.toValue(false)); + }); + + it('createOutlineGeometryInstance obeys Entity.show is false.', function() { + var entity = createBasicPlane(); + entity.show = false; + entity.plane.outline = true; + var updater = new PlaneGeometryUpdater(entity, scene); + var instance = updater.createFillGeometryInstance(new JulianDate()); + var attributes = instance.attributes; + expect(attributes.show.value).toEqual(ShowGeometryInstanceAttribute.toValue(false)); + }); + + it('dynamic updater sets properties', function() { + var entity = new Entity(); + entity.position = new ConstantPositionProperty(Cartesian3.fromDegrees(0, 0, 0)); + var planeGraphics = new PlaneGraphics(); + entity.plane = planeGraphics; + + planeGraphics.show = createDynamicProperty(true); + planeGraphics.plane = createDynamicProperty(new Plane(Cartesian3.UNIT_X, 0.0)); + planeGraphics.dimensions = createDynamicProperty(new Cartesian3(1.0, 2.0)); + planeGraphics.outline = createDynamicProperty(true); + planeGraphics.fill = createDynamicProperty(true); + + var updater = new PlaneGeometryUpdater(entity, scene); + var primitives = new PrimitiveCollection(); + var dynamicUpdater = updater.createDynamicUpdater(primitives); + expect(primitives.length).toBe(0); + + dynamicUpdater.update(JulianDate.now()); + expect(primitives.length).toBe(2); + expect(dynamicUpdater.isDestroyed()).toBe(false); + + expect(dynamicUpdater._options.id).toBe(entity); + expect(dynamicUpdater._options.plane).toEqual(planeGraphics.plane.getValue()); + expect(dynamicUpdater._options.dimensions).toEqual(planeGraphics.dimensions.getValue()); + + entity.show = false; + dynamicUpdater.update(JulianDate.now()); + expect(primitives.length).toBe(0); + entity.show = true; + + planeGraphics.show.setValue(false); + dynamicUpdater.update(JulianDate.now()); + expect(primitives.length).toBe(0); + + planeGraphics.show.setValue(true); + planeGraphics.fill.setValue(false); + dynamicUpdater.update(JulianDate.now()); + expect(primitives.length).toBe(1); + + planeGraphics.fill.setValue(true); + planeGraphics.outline.setValue(false); + dynamicUpdater.update(JulianDate.now()); + expect(primitives.length).toBe(1); + + dynamicUpdater.destroy(); + expect(primitives.length).toBe(0); + updater.destroy(); + }); + + it('geometryChanged event is raised when expected', function() { + var entity = createBasicPlane(); + var updater = new PlaneGeometryUpdater(entity, scene); + var listener = jasmine.createSpy('listener'); + updater.geometryChanged.addEventListener(listener); + + entity.plane.dimensions = new ConstantProperty(); + expect(listener.calls.count()).toEqual(1); + + entity.availability = new TimeIntervalCollection(); + expect(listener.calls.count()).toEqual(2); + + entity.plane.dimensions = undefined; + expect(listener.calls.count()).toEqual(3); + + //Since there's no valid geometry, changing another property should not raise the event. + entity.plane.height = undefined; + + //Modifying an unrelated property should not have any effect. + entity.viewFrom = new ConstantProperty(Cartesian3.UNIT_X); + expect(listener.calls.count()).toEqual(3); + }); + + it('createFillGeometryInstance throws if object is not filled', function() { + var entity = new Entity(); + var updater = new PlaneGeometryUpdater(entity, scene); + expect(function() { + return updater.createFillGeometryInstance(time); + }).toThrowDeveloperError(); + }); + + it('createFillGeometryInstance throws if no time provided', function() { + var entity = createBasicPlane(); + var updater = new PlaneGeometryUpdater(entity, scene); + expect(function() { + return updater.createFillGeometryInstance(undefined); + }).toThrowDeveloperError(); + }); + + it('createOutlineGeometryInstance throws if object is not outlined', function() { + var entity = new Entity(); + var updater = new PlaneGeometryUpdater(entity, scene); + expect(function() { + return updater.createOutlineGeometryInstance(time); + }).toThrowDeveloperError(); + }); + + it('createOutlineGeometryInstance throws if no time provided', function() { + var entity = createBasicPlane(); + entity.plane.outline = new ConstantProperty(true); + var updater = new PlaneGeometryUpdater(entity, scene); + expect(function() { + return updater.createOutlineGeometryInstance(undefined); + }).toThrowDeveloperError(); + }); + + it('createDynamicUpdater throws if not dynamic', function() { + var entity = createBasicPlane(); + var updater = new PlaneGeometryUpdater(entity, scene); + expect(function() { + return updater.createDynamicUpdater(new PrimitiveCollection()); + }).toThrowDeveloperError(); + }); + + it('createDynamicUpdater throws if primitives undefined', function() { + var entity = createBasicPlane(); + entity.plane.dimensions = createDynamicProperty(new Cartesian2(1.0, 2.0 + )); + var updater = new PlaneGeometryUpdater(entity, scene); + expect(updater.isDynamic).toBe(true); + expect(function() { + return updater.createDynamicUpdater(undefined); + }).toThrowDeveloperError(); + }); + + it('dynamicUpdater.update throws if no time specified', function() { + var entity = createBasicPlane(); + entity.plane.dimensions = createDynamicProperty(new Cartesian2(1.0, 2.0)); + var updater = new PlaneGeometryUpdater(entity, scene); + var dynamicUpdater = updater.createDynamicUpdater(new PrimitiveCollection()); + expect(function() { + dynamicUpdater.update(undefined); + }).toThrowDeveloperError(); + }); + + it('Constructor throws if no Entity supplied', function() { + expect(function() { + return new PlaneGeometryUpdater(undefined, scene); + }).toThrowDeveloperError(); + }); + + it('Constructor throws if no scene supplied', function() { + var entity = createBasicPlane(); + expect(function() { + return new PlaneGeometryUpdater(entity, undefined); + }).toThrowDeveloperError(); + }); + + var entity = createBasicPlane(); + entity.plane.plane = createDynamicProperty(new Plane(Cartesian3.UNIT_X, 0.0)); + entity.plane.dimensions = createDynamicProperty(new Cartesian2(1.0, 2.0)); + createDynamicGeometryBoundingSphereSpecs(PlaneGeometryUpdater, entity, entity.plane, function() { + return scene; + }); +}, 'WebGL'); diff --git a/Specs/Scene/Batched3DModel3DTileContentSpec.js b/Specs/Scene/Batched3DModel3DTileContentSpec.js index c5e948a855ed..1f77b3fba8ff 100644 --- a/Specs/Scene/Batched3DModel3DTileContentSpec.js +++ b/Specs/Scene/Batched3DModel3DTileContentSpec.js @@ -5,7 +5,9 @@ defineSuite([ 'Core/deprecationWarning', 'Core/HeadingPitchRange', 'Core/HeadingPitchRoll', + 'Core/Plane', 'Core/Transforms', + 'Scene/ClippingPlanesCollection', 'Specs/Cesium3DTilesTester', 'Specs/createScene' ], function( @@ -15,7 +17,9 @@ defineSuite([ deprecationWarning, HeadingPitchRange, HeadingPitchRoll, + Plane, Transforms, + ClippingPlanesCollection, Cesium3DTilesTester, createScene) { 'use strict'; @@ -288,6 +292,34 @@ defineSuite([ }); }); + it('Updates model\'s clipping planes', function() { + return Cesium3DTilesTester.loadTileset(scene, withBatchTableUrl).then(function(tileset) { + var tile = tileset._root; + var content = tile.content; + var model = content._model; + + expect(model.clippingPlanes).toBeDefined(); + expect(model.clippingPlanes.planes.length).toBe(0); + expect(model.clippingPlanes.enabled).toBe(false); + + tileset.clippingPlanes = new ClippingPlanesCollection({ + planes : [ + new Plane(Cartesian3.UNIT_X, 0.0) + ] + }); + content.update(tileset, scene.frameState); + + expect(model.clippingPlanes).toBeDefined(); + expect(model.clippingPlanes.planes.length).toBe(1); + expect(model.clippingPlanes.enabled).toBe(true); + + tile._isClipped = false; + content.update(tileset, scene.frameState); + + expect(model.clippingPlanes.enabled).toBe(false); + }); + }); + it('destroys', function() { return Cesium3DTilesTester.tileDestroys(scene, withoutBatchTableUrl); }); diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js index c6f802b8f315..772c0ad0bde4 100644 --- a/Specs/Scene/Cesium3DTilesetSpec.js +++ b/Specs/Scene/Cesium3DTilesetSpec.js @@ -7,11 +7,13 @@ defineSuite([ 'Core/defined', 'Core/getStringFromTypedArray', 'Core/HeadingPitchRange', + 'Core/Intersect', 'Core/JulianDate', 'Core/loadWithXhr', 'Core/Math', 'Core/Matrix4', 'Core/PerspectiveFrustum', + 'Core/Plane', 'Core/PrimitiveType', 'Core/RequestScheduler', 'Renderer/ClearCommand', @@ -22,6 +24,7 @@ defineSuite([ 'Scene/Cesium3DTileOptimizations', 'Scene/Cesium3DTileRefine', 'Scene/Cesium3DTileStyle', + 'Scene/ClippingPlanesCollection', 'Scene/CullFace', 'Specs/Cesium3DTilesTester', 'Specs/createScene', @@ -36,11 +39,13 @@ defineSuite([ defined, getStringFromTypedArray, HeadingPitchRange, + Intersect, JulianDate, loadWithXhr, CesiumMath, Matrix4, PerspectiveFrustum, + Plane, PrimitiveType, RequestScheduler, ClearCommand, @@ -51,6 +56,7 @@ defineSuite([ Cesium3DTileOptimizations, Cesium3DTileRefine, Cesium3DTileStyle, + ClippingPlanesCollection, CullFace, Cesium3DTilesTester, createScene, @@ -2807,4 +2813,55 @@ defineSuite([ }); }); + it('clipping planes cull hidden tiles', 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 visibility = tileset._root.visibility(scene.frameState, CullingVolume.MASK_INSIDE); + + expect(visibility).not.toBe(CullingVolume.MASK_OUTSIDE); + + var plane = new Plane(Cartesian3.UNIT_Z, 100000000.0); + tileset.clippingPlanes = new ClippingPlanesCollection({ + 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() { + // 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 visibility = tileset._root.contentVisibility(scene.frameState); + + expect(visibility).not.toBe(Intersect.OUTSIDE); + + var plane = new Plane(Cartesian3.UNIT_Z, 100000000.0); + tileset.clippingPlanes = new ClippingPlanesCollection({ + 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); + }); + }); }, 'WebGL'); diff --git a/Specs/Scene/ClippingPlanesCollectionSpec.js b/Specs/Scene/ClippingPlanesCollectionSpec.js new file mode 100644 index 000000000000..eb640a7c7557 --- /dev/null +++ b/Specs/Scene/ClippingPlanesCollectionSpec.js @@ -0,0 +1,175 @@ +defineSuite([ + 'Scene/ClippingPlanesCollection', + 'Core/BoundingSphere', + 'Core/Cartesian3', + 'Core/Cartesian4', + 'Core/Color', + 'Core/Intersect', + 'Core/Matrix4', + 'Core/Plane' +], function( + ClippingPlanesCollection, + BoundingSphere, + Cartesian3, + Cartesian4, + Color, + Intersect, + Matrix4, + Plane) { + 'use strict'; + + var clippingPlanes; + var planes = [ + new Plane(Cartesian3.UNIT_X, 1.0), + new Plane(Cartesian3.UNIT_Y, 2.0) + ]; + + var transform = new Matrix4.fromTranslation(new Cartesian3(1.0, 2.0, 3.0)); + var boundingVolume = new BoundingSphere(Cartesian3.ZERO, 1.0); + + it('default constructor', function() { + clippingPlanes = new ClippingPlanesCollection(); + expect(clippingPlanes.planes).toEqual([]); + expect(clippingPlanes.enabled).toEqual(true); + expect(clippingPlanes.modelMatrix).toEqual(Matrix4.IDENTITY); + expect(clippingPlanes.edgeColor).toEqual(Color.WHITE); + expect(clippingPlanes.edgeWidth).toEqual(0.0); + expect(clippingPlanes.combineClippingRegions).toEqual(true); + expect(clippingPlanes._testIntersection).not.toBeUndefined(); + }); + + it('transforms and packs planes into result paramter', function() { + clippingPlanes = new ClippingPlanesCollection({ + planes : planes + }); + + var result = clippingPlanes.transformAndPackPlanes(transform); + expect(result.length).toEqual(2); + expect(result[0]).toEqual(new Cartesian4(1.0, 0.0, 0.0, -2.0)); + expect(result[1]).toEqual(new Cartesian4(0.0, 1.0, 0.0, -4.0)); + }); + + it('transforms and packs planes with no result parameter creates new array', function() { + clippingPlanes = new ClippingPlanesCollection({ + planes : planes + }); + + var result = clippingPlanes.transformAndPackPlanes(transform); + expect(result.length).toEqual(2); + expect(result[0] instanceof Cartesian4).toBe(true); + expect(result[1] instanceof Cartesian4).toBe(true); + }); + + it('clone without a result parameter returns new identical copy', function() { + clippingPlanes = new ClippingPlanesCollection({ + planes : planes, + enabled : false, + edgeColor : Color.RED, + modelMatrix : transform + }); + + var result = clippingPlanes.clone(); + expect(result).not.toBe(clippingPlanes); + expect(result.planes[0]).toEqual(planes[0]); + expect(result.planes[1]).toEqual(planes[1]); + expect(result.enabled).toEqual(false); + expect(result.modelMatrix).toEqual(transform); + expect(result.edgeColor).toEqual(Color.RED); + expect(result.edgeWidth).toEqual(0.0); + expect(result.combineClippingRegions).toEqual(true); + expect(result._testIntersection).not.toBeUndefined(); + }); + + it('clone stores copy in result parameter', function() { + clippingPlanes = new ClippingPlanesCollection({ + planes : planes, + enabled : false, + edgeColor : Color.RED, + modelMatrix : transform + }); + var result = new ClippingPlanesCollection(); + var copy = clippingPlanes.clone(result); + expect(copy).toBe(result); + expect(result.planes[0]).toEqual(planes[0]); + expect(result.planes[1]).toEqual(planes[1]); + expect(result.enabled).toEqual(false); + expect(result.modelMatrix).toEqual(transform); + expect(result.edgeColor).toEqual(Color.RED); + expect(result.edgeWidth).toEqual(0.0); + expect(result.combineClippingRegions).toEqual(true); + expect(result._testIntersection).not.toBeUndefined(); + }); + + it('setting combineClippingRegions updates testIntersection function', function() { + clippingPlanes = new ClippingPlanesCollection(); + var originalIntersectFunction = clippingPlanes._testIntersection; + + expect(clippingPlanes._testIntersection).not.toBeUndefined(); + + clippingPlanes.combineClippingRegions = false; + + expect(clippingPlanes._testIntersection).not.toBe(originalIntersectFunction); + }); + + it('computes intersections with bounding volumes when combining clipping regions', function() { + clippingPlanes = new ClippingPlanesCollection(); + + var intersect = clippingPlanes.computeIntersectionWithBoundingVolume(boundingVolume); + expect(intersect).toEqual(Intersect.INSIDE); + + clippingPlanes.planes.push(new Plane(Cartesian3.UNIT_X, 2.0)); + intersect = clippingPlanes.computeIntersectionWithBoundingVolume(boundingVolume); + expect(intersect).toEqual(Intersect.OUTSIDE); + + clippingPlanes.planes.push(new Plane(Cartesian3.UNIT_Y, 0.0)); + intersect = clippingPlanes.computeIntersectionWithBoundingVolume(boundingVolume); + expect(intersect).toEqual(Intersect.INTERSECTING); + + clippingPlanes.planes.push(new Plane(Cartesian3.UNIT_Z, -1.0)); + intersect = clippingPlanes.computeIntersectionWithBoundingVolume(boundingVolume); + expect(intersect).toEqual(Intersect.INSIDE); + + clippingPlanes.planes.push(new Plane(Cartesian3.UNIT_Z, 0.0)); + intersect = clippingPlanes.computeIntersectionWithBoundingVolume(boundingVolume); + expect(intersect).toEqual(Intersect.INSIDE); + }); + + it('computes intersections with bounding volumes when not combining clipping regions', function() { + clippingPlanes = new ClippingPlanesCollection({ + combineClippingRegions : false + }); + + var intersect = clippingPlanes.computeIntersectionWithBoundingVolume(boundingVolume); + expect(intersect).toEqual(Intersect.INSIDE); + + clippingPlanes.planes.push(new Plane(Cartesian3.UNIT_Z, -1.0)); + intersect = clippingPlanes.computeIntersectionWithBoundingVolume(boundingVolume); + expect(intersect).toEqual(Intersect.INSIDE); + + clippingPlanes.planes.push(new Plane(Cartesian3.UNIT_Y, 2.0)); + intersect = clippingPlanes.computeIntersectionWithBoundingVolume(boundingVolume); + expect(intersect).toEqual(Intersect.OUTSIDE); + + clippingPlanes.planes.push(new Plane(Cartesian3.UNIT_X, 0.0)); + intersect = clippingPlanes.computeIntersectionWithBoundingVolume(boundingVolume); + expect(intersect).toEqual(Intersect.OUTSIDE); + + var plane = clippingPlanes.planes.pop(); + clippingPlanes.planes.pop(); + + clippingPlanes.planes.push(plane); + intersect = clippingPlanes.computeIntersectionWithBoundingVolume(boundingVolume); + expect(intersect).toEqual(Intersect.INTERSECTING); + }); + + it('computes intersections applies optional transform to planes', function() { + clippingPlanes = new ClippingPlanesCollection(); + + var intersect = clippingPlanes.computeIntersectionWithBoundingVolume(boundingVolume, transform); + expect(intersect).toEqual(Intersect.INSIDE); + + clippingPlanes.planes.push(new Plane(Cartesian3.UNIT_X, -1.0)); + intersect = clippingPlanes.computeIntersectionWithBoundingVolume(boundingVolume, transform); + expect(intersect).not.toEqual(Intersect.INSIDE); + }); +}); diff --git a/Specs/Scene/GlobeSurfaceTileProviderSpec.js b/Specs/Scene/GlobeSurfaceTileProviderSpec.js index 40b865886b56..9bf9a81abc0a 100644 --- a/Specs/Scene/GlobeSurfaceTileProviderSpec.js +++ b/Specs/Scene/GlobeSurfaceTileProviderSpec.js @@ -8,11 +8,14 @@ defineSuite([ 'Core/Ellipsoid', 'Core/EllipsoidTerrainProvider', 'Core/GeographicProjection', + 'Core/Intersect', + 'Core/Plane', 'Core/Rectangle', 'Core/WebMercatorProjection', 'Renderer/ContextLimits', 'Renderer/RenderState', 'Scene/BlendingState', + 'Scene/ClippingPlanesCollection', 'Scene/Fog', 'Scene/Globe', 'Scene/GlobeSurfaceShaderSet', @@ -35,11 +38,14 @@ defineSuite([ Ellipsoid, EllipsoidTerrainProvider, GeographicProjection, + Intersect, + Plane, Rectangle, WebMercatorProjection, ContextLimits, RenderState, BlendingState, + ClippingPlanesCollection, Fog, Globe, GlobeSurfaceShaderSet, @@ -692,4 +698,118 @@ defineSuite([ }).toThrowDeveloperError(); }); + it('clipping planes selectively disable rendering globe surface', function() { + expect(scene).toRender([0, 0, 0, 255]); + + switchViewMode(SceneMode.SCENE3D, new GeographicProjection(Ellipsoid.WGS84)); + + return updateUntilDone(scene.globe).then(function() { + expect(scene).notToRender([0, 0, 0, 255]); + + var result; + expect(scene).toRenderAndCall(function(rgba) { + result = rgba; + expect(rgba).not.toEqual([0, 0, 0, 255]); + }); + + var clipPlane = new Plane(Cartesian3.UNIT_Z, 10000.0); + scene.globe.clippingPlanes = new ClippingPlanesCollection ({ + planes : [ + clipPlane + ] + }); + + expect(scene).notToRender(result); + + clipPlane.distance = 0.0; + + expect(scene).toRender(result); + + scene.globe.clippingPlanes = undefined; + }); + }); + + it('clipping planes apply edge styling to globe surface', function() { + expect(scene).toRender([0, 0, 0, 255]); + + switchViewMode(SceneMode.SCENE3D, new GeographicProjection(Ellipsoid.WGS84)); + + return updateUntilDone(scene.globe).then(function() { + expect(scene).notToRender([0, 0, 0, 255]); + + var result; + expect(scene).toRenderAndCall(function(rgba) { + result = rgba; + expect(rgba).not.toEqual([0, 0, 0, 255]); + }); + + var clipPlane = new Plane(Cartesian3.UNIT_Z, 1000.0); + scene.globe.clippingPlanes = new ClippingPlanesCollection ({ + planes : [ + clipPlane + ], + edgeWidth : 20.0, + edgeColor : Color.RED + }); + + expect(scene).notToRender(result); + + clipPlane.distance = 0.0; + + expect(scene).toRender([255, 0, 0, 255]); + + scene.globe.clippingPlanes = undefined; + }); + }); + + it('clipping planes combine regions', function() { + expect(scene).toRender([0, 0, 0, 255]); + + switchViewMode(SceneMode.SCENE3D, new GeographicProjection(Ellipsoid.WGS84)); + + return updateUntilDone(scene.globe).then(function() { + expect(scene).notToRender([0, 0, 0, 255]); + + var result; + expect(scene).toRenderAndCall(function(rgba) { + result = rgba; + expect(rgba).not.toEqual([0, 0, 0, 255]); + }); + + scene.globe.clippingPlanes = new ClippingPlanesCollection ({ + planes : [ + new Plane(Cartesian3.UNIT_Z, 10000.0), + new Plane(Cartesian3.UNIT_X, 1000.0) + ], + combineClippingRegions: false + }); + + expect(scene).notToRender(result); + + scene.globe.clippingPlanes.combineClippingRegions = true; + + expect(scene).toRender(result); + + scene.globe.clippingPlanes = undefined; + }); + }); + + it('computes tile visibility culls tiles when clipped', function() { + scene.globe.clippingPlanes = new ClippingPlanesCollection ({ + planes : [ + new Plane(Cartesian3.UNIT_X, 10000000.0) + ] + }); + + var surface = scene.globe._surface; + + scene.renderForSpecs(); + + var tile = surface._levelZeroTiles[0]; + expect(surface.tileProvider.computeTileVisibility(tile, scene.frameState)).toBe(Intersect.OUTSIDE); + expect(tile.isClipped).toBe(true); + + scene.globe.clippingPlanes = undefined; + }); + }, 'WebGL'); diff --git a/Specs/Scene/PointCloud3DTileContentSpec.js b/Specs/Scene/PointCloud3DTileContentSpec.js index f70b614c718c..b327f6a85651 100644 --- a/Specs/Scene/PointCloud3DTileContentSpec.js +++ b/Specs/Scene/PointCloud3DTileContentSpec.js @@ -7,9 +7,12 @@ defineSuite([ 'Core/HeadingPitchRange', 'Core/HeadingPitchRoll', 'Core/Math', + 'Core/Matrix4', 'Core/PerspectiveFrustum', + 'Core/Plane', 'Core/Transforms', 'Scene/Cesium3DTileStyle', + 'Scene/ClippingPlanesCollection', 'Scene/Expression', 'Specs/Cesium3DTilesTester', 'Specs/createScene', @@ -23,9 +26,12 @@ defineSuite([ HeadingPitchRange, HeadingPitchRoll, CesiumMath, + Matrix4, PerspectiveFrustum, + Plane, Transforms, Cesium3DTileStyle, + ClippingPlanesCollection, Expression, Cesium3DTilesTester, createScene, @@ -685,6 +691,119 @@ defineSuite([ }); }); + it('Updates clipping planes when clipping planes are enabled', function () { + return Cesium3DTilesTester.loadTileset(scene, pointCloudRGBUrl).then(function(tileset) { + var content = tileset._root.content; + + expect(content._packedClippingPlanes).toBeDefined(); + expect(content._packedClippingPlanes.length).toBe(0); + expect(content._modelViewMatrix).toEqual(Matrix4.IDENTITY); + + tileset.clippingPlanes = new ClippingPlanesCollection({ + planes : [ + new Plane(Cartesian3.UNIT_X, 0.0) + ] + }); + + content.update(tileset, scene.frameState); + + expect(content._packedClippingPlanes.length).toBe(1); + expect(content._modelViewMatrix).not.toEqual(Matrix4.IDENTITY); + }); + }); + + it('Does not update clipping planes when tile is not clipped', function () { + return Cesium3DTilesTester.loadTileset(scene, pointCloudRGBUrl).then(function(tileset) { + var tile = tileset._root; + tile._isClipped = false; + var content = tile.content; + + expect(content._packedClippingPlanes).toBeDefined(); + expect(content._packedClippingPlanes.length).toBe(0); + expect(content._modelViewMatrix).toEqual(Matrix4.IDENTITY); + + tileset.clippingPlanes = new ClippingPlanesCollection({ + planes : [ + new Plane(Cartesian3.UNIT_X, 0.0) + ] + }); + + content.update(tileset, scene.frameState); + + expect(content._packedClippingPlanes.length).toBe(0); + expect(content._modelViewMatrix).toEqual(Matrix4.IDENTITY); + }); + }); + + it('Clipping planes selectively disable rendering', function () { + return Cesium3DTilesTester.loadTileset(scene, pointCloudRGBUrl).then(function(tileset) { + var color; + expect(scene).toRenderAndCall(function(rgba) { + color = rgba; + }); + + var clipPlane = new Plane(Cartesian3.UNIT_Z, 10.0); + tileset.clippingPlanes = new ClippingPlanesCollection({ + planes : [ + clipPlane + ], + modelMatrix : Transforms.eastNorthUpToFixedFrame(tileset.boundingSphere.center) + }); + + expect(scene).notToRender(color); + + clipPlane.distance = 0.0; + + expect(scene).toRender(color); + }); + }); + + it('clipping planes apply edge styling', function() { + return Cesium3DTilesTester.loadTileset(scene, pointCloudRGBUrl).then(function(tileset) { + var color; + expect(scene).toRenderAndCall(function(rgba) { + color = rgba; + }); + + var clipPlane = new Plane(Cartesian3.UNIT_Z, 10.0); + tileset.clippingPlanes = new ClippingPlanesCollection ({ + planes : [ + clipPlane + ], + modelMatrix : Transforms.eastNorthUpToFixedFrame(tileset.boundingSphere.center), + edgeWidth : 20.0, + edgeColor : Color.RED + }); + + expect(scene).notToRender(color); + }); + }); + + it('clipping planes combine regions', function() { + return Cesium3DTilesTester.loadTileset(scene, pointCloudRGBUrl).then(function(tileset) { + var color; + expect(scene).toRenderAndCall(function(rgba) { + color = rgba; + }); + + tileset.clippingPlanes = new ClippingPlanesCollection ({ + planes : [ + new Plane(Cartesian3.UNIT_Z, 10.0), + new Plane(Cartesian3.UNIT_X, 0.0) + ], + modelMatrix : Transforms.eastNorthUpToFixedFrame(tileset.boundingSphere.center), + combineClippingRegions: false + }); + + expect(scene).notToRender(color); + + tileset.clippingPlanes.combineClippingRegions = true; + + expect(scene).toRender(color); + }); + }); + + it('destroys', function() { return Cesium3DTilesTester.tileDestroys(scene, pointCloudRGBUrl); }); From 77457f59d830199986ae22d89687ad7f46731054 Mon Sep 17 00:00:00 2001 From: ggetz Date: Thu, 30 Nov 2017 17:49:26 -0500 Subject: [PATCH 27/37] Fix errors --- Source/Scene/Model.js | 2 +- Specs/DataSources/PlaneGeometryUpdaterSpec.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js index 41ec0cb64115..e5a570a58d63 100644 --- a/Source/Scene/Model.js +++ b/Source/Scene/Model.js @@ -645,7 +645,7 @@ define([ this.opaquePass = defaultValue(options.opaquePass, Pass.OPAQUE); this._computedModelMatrix = new Matrix4(); // Derived from modelMatrix and scale - this._modelViewMatrix = new Matrix4(); // Derived from modelMatrix, scale, and the current view matrix + this._modelViewMatrix = Matrix4.clone(Matrix4.IDENTITY); // Derived from modelMatrix, scale, and the current view matrix this._initialRadius = undefined; // Radius without model's scale property, model-matrix scale, animations, or skins this._boundingSphere = undefined; this._scaledBoundingSphere = new BoundingSphere(); diff --git a/Specs/DataSources/PlaneGeometryUpdaterSpec.js b/Specs/DataSources/PlaneGeometryUpdaterSpec.js index 4e548da1d8f1..9ac0070e680c 100644 --- a/Specs/DataSources/PlaneGeometryUpdaterSpec.js +++ b/Specs/DataSources/PlaneGeometryUpdaterSpec.js @@ -181,7 +181,6 @@ defineSuite([ var updater = new PlaneGeometryUpdater(entity, scene); var instance; - var geometry; var attributes; if (options.fill) { instance = updater.createFillGeometryInstance(time); From 47e0ec3794dc49b05a51f4dfe1a871f7e85e881e Mon Sep 17 00:00:00 2001 From: ggetz Date: Fri, 1 Dec 2017 11:03:49 -0500 Subject: [PATCH 28/37] Cleanup, update specs --- Source/Scene/ClippingPlanesCollection.js | 46 ++++---- Source/Scene/Globe.js | 2 +- Source/Scene/PointCloud3DTileContent.js | 13 +-- .../Builtin/Functions/discardIfClipped.glsl | 2 +- .../discardIfClippedCombineRegions.glsl | 2 +- Specs/Core/PlaneSpec.js | 7 +- Specs/DataSources/PlaneGeometryUpdaterSpec.js | 102 +++++++++--------- Specs/Scene/ClippingPlanesCollectionSpec.js | 45 ++++---- Specs/Scene/GlobeSurfaceTileProviderSpec.js | 40 +++++-- 9 files changed, 144 insertions(+), 115 deletions(-) diff --git a/Source/Scene/ClippingPlanesCollection.js b/Source/Scene/ClippingPlanesCollection.js index b3fa262dd030..d1b9bf411386 100644 --- a/Source/Scene/ClippingPlanesCollection.js +++ b/Source/Scene/ClippingPlanesCollection.js @@ -1,25 +1,25 @@ define([ - '../Core/Cartesian3', - '../Core/Cartesian4', - '../Core/Check', - '../Core/Color', - '../Core/defaultValue', - '../Core/defined', - '../Core/defineProperties', - '../Core/Intersect', - '../Core/Matrix4', - '../Core/Plane' -], function( - Cartesian3, - Cartesian4, - Check, - Color, - defaultValue, - defined, - defineProperties, - Intersect, - Matrix4, - Plane) { + '../Core/Cartesian3', + '../Core/Cartesian4', + '../Core/Check', + '../Core/Color', + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/Intersect', + '../Core/Matrix4', + '../Core/Plane' + ], function( + Cartesian3, + Cartesian4, + Check, + Color, + defaultValue, + defined, + defineProperties, + Intersect, + Matrix4, + Plane) { 'use strict'; /** @@ -172,7 +172,9 @@ define([ } var length = this.planes.length; - result.planes = new Array(length); + if (result.planes.length !== length) { + result.planes = new Array(length); + } for (var i = 0; i < length; ++i) { var plane = this.planes[i]; result.planes[i] = new Plane(plane.normal, plane.distance); diff --git a/Source/Scene/Globe.js b/Source/Scene/Globe.js index a15bba2f77a2..150e780c1d01 100644 --- a/Source/Scene/Globe.js +++ b/Source/Scene/Globe.js @@ -238,7 +238,7 @@ define([ * A property specifying an array of up to 6 {@link Plane} used to selectively disable rendering on the outside of each plane. * * @memberof Globe.prototype - * @type {ClippingPlaneCollection} + * @type {ClippingPlanesCollection} */ clippingPlanes : { get : function() { diff --git a/Source/Scene/PointCloud3DTileContent.js b/Source/Scene/PointCloud3DTileContent.js index 69aa39d970ad..d22a7049085d 100644 --- a/Source/Scene/PointCloud3DTileContent.js +++ b/Source/Scene/PointCloud3DTileContent.js @@ -521,7 +521,6 @@ define([ } var clippingPlanes = content._tileset.clippingPlanes; - var hasClippedContent = defined(clippingPlanes) && clippingPlanes.enabled && content._tile._isClipped; var uniformMap = { u_pointSizeAndTilesetTime : function() { scratchPointSizeAndTilesetTime.x = content._pointSize; @@ -538,13 +537,7 @@ define([ return content._packedClippingPlanes.length; }, u_clippingPlanes : function() { - var packedPlanes = content._packedClippingPlanes; - - if (hasClippedContent) { - clippingPlanes.transformAndPackPlanes(content._modelViewMatrix, packedPlanes); - } - - return packedPlanes; + return content._packedClippingPlanes; }, u_clippingPlanesEdgeStyle : function() { if (!defined(clippingPlanes)) { @@ -1249,6 +1242,10 @@ define([ this._parsedContent = undefined; // Unload } + if (clippingEnabled) { + clippingPlanes.transformAndPackPlanes(this._modelViewMatrix, this._packedClippingPlanes); + } + var isClipped = this._tile._isClipped; if (this._isClipped !== isClipped) { this._isClipped = isClipped; diff --git a/Source/Shaders/Builtin/Functions/discardIfClipped.glsl b/Source/Shaders/Builtin/Functions/discardIfClipped.glsl index 74d0c51dc01f..dab7cae02566 100644 --- a/Source/Shaders/Builtin/Functions/discardIfClipped.glsl +++ b/Source/Shaders/Builtin/Functions/discardIfClipped.glsl @@ -9,7 +9,7 @@ * @param {int} clippingPlanesLength The number of planes in the array of clipping planes. * @returns {float} The distance away from a clipped fragment, in eyespace */ -float czm_discardIfClipped (vec4[czm_maxClippingPlanes] clippingPlanes, int clippingPlanesLength) +float czm_discardIfClipped(vec4[czm_maxClippingPlanes] clippingPlanes, int clippingPlanesLength) { if (clippingPlanesLength > 0) { diff --git a/Source/Shaders/Builtin/Functions/discardIfClippedCombineRegions.glsl b/Source/Shaders/Builtin/Functions/discardIfClippedCombineRegions.glsl index 4709159fddd1..9d69e3d49838 100644 --- a/Source/Shaders/Builtin/Functions/discardIfClippedCombineRegions.glsl +++ b/Source/Shaders/Builtin/Functions/discardIfClippedCombineRegions.glsl @@ -9,7 +9,7 @@ * @param {int} clippingPlanesLength The number of planes in the array of clipping planes. * @returns {float} The distance away from a clipped fragment, in eyespace */ -float czm_discardIfClippedCombineRegions (vec4[czm_maxClippingPlanes] clippingPlanes, int clippingPlanesLength) +float czm_discardIfClippedCombineRegions(vec4[czm_maxClippingPlanes] clippingPlanes, int clippingPlanesLength) { if (clippingPlanesLength > 0) { diff --git a/Specs/Core/PlaneSpec.js b/Specs/Core/PlaneSpec.js index 94c851016ba4..9c9419457b30 100644 --- a/Specs/Core/PlaneSpec.js +++ b/Specs/Core/PlaneSpec.js @@ -136,6 +136,7 @@ defineSuite([ var transform = Matrix4.fromUniformScale(2.0); transform = Matrix4.multiplyByMatrix3(transform, Matrix3.fromRotationY(Math.PI), transform); + var transformedPlane = Plane.transform(plane, transform); expect(transformedPlane.distance).toEqual(-plane.distance * 2.0); expect(transformedPlane.normal.x).toEqualEpsilon(-plane.normal.x, CesiumMath.EPSILON10); @@ -144,13 +145,13 @@ defineSuite([ }); it('transform throws without a plane', function() { - var point = Cartesian3.ZERO; + var transform = Matrix4.IDENTITY; expect(function() { - return Plane.transform(undefined, point); + return Plane.transform(undefined, transform); }).toThrowDeveloperError(); }); - it('transform throws without a point', function() { + it('transform throws without a transform', function() { var plane = new Plane(Cartesian3.UNIT_X, 0.0); expect(function() { return Plane.transform(plane, undefined); diff --git a/Specs/DataSources/PlaneGeometryUpdaterSpec.js b/Specs/DataSources/PlaneGeometryUpdaterSpec.js index 9ac0070e680c..9f01ae6abc4c 100644 --- a/Specs/DataSources/PlaneGeometryUpdaterSpec.js +++ b/Specs/DataSources/PlaneGeometryUpdaterSpec.js @@ -1,55 +1,55 @@ defineSuite([ - 'DataSources/PlaneGeometryUpdater', - 'Core/Cartesian2', - 'Core/Cartesian3', - 'Core/Color', - 'Core/ColorGeometryInstanceAttribute', - 'Core/DistanceDisplayCondition', - 'Core/DistanceDisplayConditionGeometryInstanceAttribute', - 'Core/JulianDate', - 'Core/Plane', - 'Core/ShowGeometryInstanceAttribute', - 'Core/TimeInterval', - 'Core/TimeIntervalCollection', - 'DataSources/PlaneGraphics', - 'DataSources/ColorMaterialProperty', - 'DataSources/ConstantPositionProperty', - 'DataSources/ConstantProperty', - 'DataSources/Entity', - 'DataSources/GridMaterialProperty', - 'DataSources/SampledProperty', - 'DataSources/TimeIntervalCollectionProperty', - 'Scene/PrimitiveCollection', - 'Scene/ShadowMode', - 'Specs/createDynamicGeometryBoundingSphereSpecs', - 'Specs/createDynamicProperty', - 'Specs/createScene' -], function( - PlaneGeometryUpdater, - Cartesian2, - Cartesian3, - Color, - ColorGeometryInstanceAttribute, - DistanceDisplayCondition, - DistanceDisplayConditionGeometryInstanceAttribute, - JulianDate, - Plane, - ShowGeometryInstanceAttribute, - TimeInterval, - TimeIntervalCollection, - PlaneGraphics, - ColorMaterialProperty, - ConstantPositionProperty, - ConstantProperty, - Entity, - GridMaterialProperty, - SampledProperty, - TimeIntervalCollectionProperty, - PrimitiveCollection, - ShadowMode, - createDynamicGeometryBoundingSphereSpecs, - createDynamicProperty, - createScene) { + 'DataSources/PlaneGeometryUpdater', + 'Core/Cartesian2', + 'Core/Cartesian3', + 'Core/Color', + 'Core/ColorGeometryInstanceAttribute', + 'Core/DistanceDisplayCondition', + 'Core/DistanceDisplayConditionGeometryInstanceAttribute', + 'Core/JulianDate', + 'Core/Plane', + 'Core/ShowGeometryInstanceAttribute', + 'Core/TimeInterval', + 'Core/TimeIntervalCollection', + 'DataSources/PlaneGraphics', + 'DataSources/ColorMaterialProperty', + 'DataSources/ConstantPositionProperty', + 'DataSources/ConstantProperty', + 'DataSources/Entity', + 'DataSources/GridMaterialProperty', + 'DataSources/SampledProperty', + 'DataSources/TimeIntervalCollectionProperty', + 'Scene/PrimitiveCollection', + 'Scene/ShadowMode', + 'Specs/createDynamicGeometryBoundingSphereSpecs', + 'Specs/createDynamicProperty', + 'Specs/createScene' + ], function( + PlaneGeometryUpdater, + Cartesian2, + Cartesian3, + Color, + ColorGeometryInstanceAttribute, + DistanceDisplayCondition, + DistanceDisplayConditionGeometryInstanceAttribute, + JulianDate, + Plane, + ShowGeometryInstanceAttribute, + TimeInterval, + TimeIntervalCollection, + PlaneGraphics, + ColorMaterialProperty, + ConstantPositionProperty, + ConstantProperty, + Entity, + GridMaterialProperty, + SampledProperty, + TimeIntervalCollectionProperty, + PrimitiveCollection, + ShadowMode, + createDynamicGeometryBoundingSphereSpecs, + createDynamicProperty, + createScene) { 'use strict'; var scene; diff --git a/Specs/Scene/ClippingPlanesCollectionSpec.js b/Specs/Scene/ClippingPlanesCollectionSpec.js index eb640a7c7557..0645adaa6fd4 100644 --- a/Specs/Scene/ClippingPlanesCollectionSpec.js +++ b/Specs/Scene/ClippingPlanesCollectionSpec.js @@ -1,21 +1,21 @@ defineSuite([ - 'Scene/ClippingPlanesCollection', - 'Core/BoundingSphere', - 'Core/Cartesian3', - 'Core/Cartesian4', - 'Core/Color', - 'Core/Intersect', - 'Core/Matrix4', - 'Core/Plane' -], function( - ClippingPlanesCollection, - BoundingSphere, - Cartesian3, - Cartesian4, - Color, - Intersect, - Matrix4, - Plane) { + 'Scene/ClippingPlanesCollection', + 'Core/BoundingSphere', + 'Core/Cartesian3', + 'Core/Cartesian4', + 'Core/Color', + 'Core/Intersect', + 'Core/Matrix4', + 'Core/Plane' + ], function( + ClippingPlanesCollection, + BoundingSphere, + Cartesian3, + Cartesian4, + Color, + Intersect, + Matrix4, + Plane) { 'use strict'; var clippingPlanes; @@ -56,8 +56,8 @@ defineSuite([ var result = clippingPlanes.transformAndPackPlanes(transform); expect(result.length).toEqual(2); - expect(result[0] instanceof Cartesian4).toBe(true); - expect(result[1] instanceof Cartesian4).toBe(true); + expect(result[0]).toBeInstanceOf(Cartesian4); + expect(result[1]).toBeInstanceOf(Cartesian4); }); it('clone without a result parameter returns new identical copy', function() { @@ -90,6 +90,7 @@ defineSuite([ var result = new ClippingPlanesCollection(); var copy = clippingPlanes.clone(result); expect(copy).toBe(result); + expect(result.planes).not.toBe(planes); expect(result.planes[0]).toEqual(planes[0]); expect(result.planes[1]).toEqual(planes[1]); expect(result.enabled).toEqual(false); @@ -98,8 +99,14 @@ defineSuite([ expect(result.edgeWidth).toEqual(0.0); expect(result.combineClippingRegions).toEqual(true); expect(result._testIntersection).not.toBeUndefined(); + + // Only allocate a new array if needed + var previousPlanes = result.planes; + clippingPlanes.clone(result); + expect(result.planes).toBe(previousPlanes); }); + it('setting combineClippingRegions updates testIntersection function', function() { clippingPlanes = new ClippingPlanesCollection(); var originalIntersectFunction = clippingPlanes._testIntersection; diff --git a/Specs/Scene/GlobeSurfaceTileProviderSpec.js b/Specs/Scene/GlobeSurfaceTileProviderSpec.js index 9bf9a81abc0a..8a91459632ea 100644 --- a/Specs/Scene/GlobeSurfaceTileProviderSpec.js +++ b/Specs/Scene/GlobeSurfaceTileProviderSpec.js @@ -794,22 +794,44 @@ defineSuite([ }); }); - it('computes tile visibility culls tiles when clipped', function() { - scene.globe.clippingPlanes = new ClippingPlanesCollection ({ + it('computesTileVisibility culls tiles when they are entirely inside the clipped region', function() { + var globe = scene.globe; + var plane = new Plane(Cartesian3.UNIT_Z, 10000000.0); + globe.clippingPlanes = new ClippingPlanesCollection ({ planes : [ - new Plane(Cartesian3.UNIT_X, 10000000.0) + plane ] }); - var surface = scene.globe._surface; + switchViewMode(SceneMode.SCENE3D, new GeographicProjection(Ellipsoid.WGS84)); + + return updateUntilDone(globe).then(function() { + var surface = globe._surface; + var tile = surface._levelZeroTiles[0]; + + expect(surface.tileProvider.computeTileVisibility(tile, scene.frameState)).toBe(Intersect.OUTSIDE); + expect(tile.isClipped).toBe(true); - scene.renderForSpecs(); + plane.distance = 0.0; + scene.renderForSpecs(); + + surface = scene.globe._surface; + tile = surface._levelZeroTiles[0]; - var tile = surface._levelZeroTiles[0]; - expect(surface.tileProvider.computeTileVisibility(tile, scene.frameState)).toBe(Intersect.OUTSIDE); - expect(tile.isClipped).toBe(true); + expect(surface.tileProvider.computeTileVisibility(tile, scene.frameState, scene.globe._surface._occluders)).toBe(Intersect.INTERSECTING); + expect(tile.isClipped).toBe(true); - scene.globe.clippingPlanes = undefined; + plane.distance = -10000000.0; + scene.renderForSpecs(); + + surface = scene.globe._surface; + tile = surface._levelZeroTiles[0]; + + expect(surface.tileProvider.computeTileVisibility(tile, scene.frameState, scene.globe._surface._occluders)).toBe(Intersect.INTERSECTING); + expect(tile.isClipped).toBe(false); + + scene.globe.clippingPlanes = undefined; + }); }); }, 'WebGL'); From 5680c34ba631cf493e99010fb36f245539fb6e69 Mon Sep 17 00:00:00 2001 From: ggetz Date: Fri, 1 Dec 2017 13:07:04 -0500 Subject: [PATCH 29/37] Fix plane geometry surface --- Source/DataSources/PlaneGeometryUpdater.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/DataSources/PlaneGeometryUpdater.js b/Source/DataSources/PlaneGeometryUpdater.js index 6e18f19bdec4..bf2b4915665b 100644 --- a/Source/DataSources/PlaneGeometryUpdater.js +++ b/Source/DataSources/PlaneGeometryUpdater.js @@ -384,7 +384,7 @@ define([ return new GeometryInstance({ id : entity, - geometry : new PlaneGeometry(), + geometry : new PlaneGeometry(this._options), modelMatrix : modelMatrix, attributes : attributes }); From 21c96e9ce5551fdee69d1ed7ad9923c9b0a1379b Mon Sep 17 00:00:00 2001 From: ggetz Date: Fri, 1 Dec 2017 14:43:46 -0500 Subject: [PATCH 30/37] Draw commands in specs --- Specs/Scene/Cesium3DTilesetSpec.js | 91 ++++++++++++++++++++- Specs/Scene/GlobeSurfaceTileProviderSpec.js | 61 +++++++++----- 2 files changed, 129 insertions(+), 23 deletions(-) diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js index 772c0ad0bde4..f1487346875e 100644 --- a/Specs/Scene/Cesium3DTilesetSpec.js +++ b/Specs/Scene/Cesium3DTilesetSpec.js @@ -2814,8 +2814,6 @@ defineSuite([ }); it('clipping planes cull hidden tiles', 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 visibility = tileset._root.visibility(scene.frameState, CullingVolume.MASK_INSIDE); @@ -2840,8 +2838,6 @@ defineSuite([ }); it('clipping planes cull hidden content', 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 visibility = tileset._root.contentVisibility(scene.frameState); @@ -2864,4 +2860,91 @@ defineSuite([ expect(visibility).not.toBe(Intersect.OUTSIDE); }); }); + + it('clipping planes cull tiles completely inside', 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 plane = new Plane(Cartesian3.UNIT_Z, 0.0); + tileset.clippingPlanes = new ClippingPlanesCollection({ + planes : [ + plane + ] + }); + + tileset.update(scene.frameState); + scene.renderForSpecs(); + + expect(statistics.numberOfCommands).toEqual(5); + expect(root._isClipped).toBe(false); + + plane.distance = 4081630.311150717; // center + + tileset.update(scene.frameState); + scene.renderForSpecs(); + + expect(statistics.numberOfCommands).toEqual(3); + expect(root._isClipped).toBe(true); + + plane.distance = 4081630.31115071 + 287.0736139905632; // center + radius + + tileset.update(scene.frameState); + scene.renderForSpecs(); + + expect(statistics.numberOfCommands).toEqual(0); + expect(root._isClipped).toBe(true); + }); + }); + + it('clipping planes cull tiles completely inside for i3dm', function() { + return Cesium3DTilesTester.loadTileset(scene, instancedUrl).then(function(tileset) { + + console.log(tileset); + + var statistics = tileset._statistics; + var root = tileset._root; + + scene.renderForSpecs(); + + expect(statistics.numberOfCommands).toEqual(1); + + tileset.update(scene.frameState); + + var plane = new Plane(Cartesian3.UNIT_Z, 0.0); + tileset.clippingPlanes = new ClippingPlanesCollection({ + planes : [ + plane + ] + }); + + tileset.update(scene.frameState); + scene.renderForSpecs(); + + expect(statistics.numberOfCommands).toEqual(1); + expect(root._isClipped).toBe(false); + + plane.distance = 4081611.654572015; // center + + tileset.update(scene.frameState); + scene.renderForSpecs(); + + expect(statistics.numberOfCommands).toEqual(1); + expect(root._isClipped).toBe(true); + + plane.distance = 4081611.654572015 + 142.6291406685467; // center + radius + + tileset.update(scene.frameState); + scene.renderForSpecs(); + + expect(statistics.numberOfCommands).toEqual(0); + expect(root._isClipped).toBe(true); + }); + }); }, 'WebGL'); diff --git a/Specs/Scene/GlobeSurfaceTileProviderSpec.js b/Specs/Scene/GlobeSurfaceTileProviderSpec.js index 8a91459632ea..e21ddc426df1 100644 --- a/Specs/Scene/GlobeSurfaceTileProviderSpec.js +++ b/Specs/Scene/GlobeSurfaceTileProviderSpec.js @@ -729,7 +729,7 @@ defineSuite([ }); }); - it('clipping planes apply edge styling to globe surface', function() { + it('renders with clipping planes edge styling on globe surface', function() { expect(scene).toRender([0, 0, 0, 255]); switchViewMode(SceneMode.SCENE3D, new GeographicProjection(Ellipsoid.WGS84)); @@ -762,7 +762,7 @@ defineSuite([ }); }); - it('clipping planes combine regions', function() { + it('renders with multiple clipping planes and combined regions', function() { expect(scene).toRender([0, 0, 0, 255]); switchViewMode(SceneMode.SCENE3D, new GeographicProjection(Ellipsoid.WGS84)); @@ -794,12 +794,20 @@ defineSuite([ }); }); - it('computesTileVisibility culls tiles when they are entirely inside the clipped region', function() { + it('No extra tiles culled with no clipping planes', function() { + var globe = scene.globe; + switchViewMode(SceneMode.SCENE3D, new GeographicProjection(Ellipsoid.WGS84)); + + return updateUntilDone(globe).then(function() { + expect(scene.frameState.commandList.length).toBe(4); + }); + }); + + it('Culls tiles when completely inside clipping region', function() { var globe = scene.globe; - var plane = new Plane(Cartesian3.UNIT_Z, 10000000.0); globe.clippingPlanes = new ClippingPlanesCollection ({ planes : [ - plane + new Plane(Cartesian3.UNIT_Z, 1000000.0) ] }); @@ -808,29 +816,44 @@ defineSuite([ return updateUntilDone(globe).then(function() { var surface = globe._surface; var tile = surface._levelZeroTiles[0]; - - expect(surface.tileProvider.computeTileVisibility(tile, scene.frameState)).toBe(Intersect.OUTSIDE); expect(tile.isClipped).toBe(true); + expect(scene.frameState.commandList.length).toBe(2); + }); + }); - plane.distance = 0.0; - scene.renderForSpecs(); + it('Doesn\'t cull, but clips tiles when intersecting clipping plane', function() { + var globe = scene.globe; + globe.clippingPlanes = new ClippingPlanesCollection ({ + planes : [ + new Plane(Cartesian3.UNIT_Z, 0.0) + ] + }); - surface = scene.globe._surface; - tile = surface._levelZeroTiles[0]; + switchViewMode(SceneMode.SCENE3D, new GeographicProjection(Ellipsoid.WGS84)); - expect(surface.tileProvider.computeTileVisibility(tile, scene.frameState, scene.globe._surface._occluders)).toBe(Intersect.INTERSECTING); + return updateUntilDone(globe).then(function() { + var surface = globe._surface; + var tile = surface._levelZeroTiles[0]; expect(tile.isClipped).toBe(true); + expect(scene.frameState.commandList.length).toBe(4); + }); + }); - plane.distance = -10000000.0; - scene.renderForSpecs(); + it('Doesn\'t cull or clip tiles when completely outside clipping region', function() { + var globe = scene.globe; + globe.clippingPlanes = new ClippingPlanesCollection ({ + planes : [ + new Plane(Cartesian3.UNIT_Z, -10000000.0) + ] + }); - surface = scene.globe._surface; - tile = surface._levelZeroTiles[0]; + switchViewMode(SceneMode.SCENE3D, new GeographicProjection(Ellipsoid.WGS84)); - expect(surface.tileProvider.computeTileVisibility(tile, scene.frameState, scene.globe._surface._occluders)).toBe(Intersect.INTERSECTING); + return updateUntilDone(globe).then(function() { + var surface = globe._surface; + var tile = surface._levelZeroTiles[0]; expect(tile.isClipped).toBe(false); - - scene.globe.clippingPlanes = undefined; + expect(scene.frameState.commandList.length).toBe(4); }); }); From 3b069caedf0be6eb3a36b8cde95e355fb4bbabfa Mon Sep 17 00:00:00 2001 From: ggetz Date: Fri, 1 Dec 2017 15:47:06 -0500 Subject: [PATCH 31/37] Model and PlaneGeometry Specs --- Specs/DataSources/PlaneGraphicsSpec.js | 179 +++++++++++++++++++++ Specs/Scene/ModelSpec.js | 144 ++++++++++++++++- Specs/Scene/PointCloud3DTileContentSpec.js | 1 - 3 files changed, 320 insertions(+), 4 deletions(-) create mode 100644 Specs/DataSources/PlaneGraphicsSpec.js diff --git a/Specs/DataSources/PlaneGraphicsSpec.js b/Specs/DataSources/PlaneGraphicsSpec.js new file mode 100644 index 000000000000..ba79c4cf1dad --- /dev/null +++ b/Specs/DataSources/PlaneGraphicsSpec.js @@ -0,0 +1,179 @@ +defineSuite([ + 'DataSources/PlaneGraphics', + 'Core/Cartesian2', + 'Core/Cartesian3', + 'Core/Color', + 'Core/DistanceDisplayCondition', + 'Core/Plane', + 'DataSources/ColorMaterialProperty', + 'DataSources/ConstantProperty', + 'Scene/ShadowMode', + 'Specs/testDefinitionChanged', + 'Specs/testMaterialDefinitionChanged' + ], function( + PlaneGraphics, + Cartesian2, + Cartesian3, + Color, + DistanceDisplayCondition, + Plane, + ColorMaterialProperty, + ConstantProperty, + ShadowMode, + testDefinitionChanged, + testMaterialDefinitionChanged) { + 'use strict'; + + it('creates expected instance from raw assignment and construction', function() { + var options = { + material : Color.BLUE, + show : true, + fill : false, + outline : false, + outlineColor : Color.RED, + outlineWidth : 1, + plane : new Plane(Cartesian3.UNIT_X, 0.0), + dimensions : new Cartesian2(2.0, 3.0), + shadows : ShadowMode.DISABLED, + distanceDisplayCondition : new DistanceDisplayCondition(10.0, 100.0) + }; + + var plane = new PlaneGraphics(options); + expect(plane.material).toBeInstanceOf(ColorMaterialProperty); + expect(plane.show).toBeInstanceOf(ConstantProperty); + expect(plane.fill).toBeInstanceOf(ConstantProperty); + expect(plane.outline).toBeInstanceOf(ConstantProperty); + expect(plane.outlineColor).toBeInstanceOf(ConstantProperty); + expect(plane.outlineWidth).toBeInstanceOf(ConstantProperty); + expect(plane.plane).toBeInstanceOf(ConstantProperty); + expect(plane.dimensions).toBeInstanceOf(ConstantProperty); + expect(plane.shadows).toBeInstanceOf(ConstantProperty); + expect(plane.distanceDisplayCondition).toBeInstanceOf(ConstantProperty); + + expect(plane.material.color.getValue()).toEqual(options.material); + expect(plane.show.getValue()).toEqual(options.show); + expect(plane.fill.getValue()).toEqual(options.fill); + expect(plane.outline.getValue()).toEqual(options.outline); + expect(plane.outlineColor.getValue()).toEqual(options.outlineColor); + expect(plane.outlineWidth.getValue()).toEqual(options.outlineWidth); + expect(plane.plane.getValue()).toEqual(options.plane); + expect(plane.dimensions.getValue()).toEqual(options.dimensions); + expect(plane.shadows.getValue()).toEqual(options.shadows); + expect(plane.distanceDisplayCondition.getValue()).toEqual(options.distanceDisplayCondition); + }); + + it('merge assigns unassigned properties', function() { + var source = new PlaneGraphics(); + source.material = new ColorMaterialProperty(); + source.show = new ConstantProperty(); + source.fill = new ConstantProperty(); + source.outline = new ConstantProperty(); + source.outlineColor = new ConstantProperty(); + source.outlineWidth = new ConstantProperty(); + source.plane = new ConstantProperty(); + source.dimensions = new ConstantProperty(); + source.shadows = new ConstantProperty(ShadowMode.ENABLED); + source.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition(10.0, 100.0)); + + var target = new PlaneGraphics(); + target.merge(source); + + expect(target.material).toBe(source.material); + expect(target.show).toBe(source.show); + expect(target.fill).toBe(source.fill); + expect(target.outline).toBe(source.outline); + expect(target.outlineColor).toBe(source.outlineColor); + expect(target.outlineWidth).toBe(source.outlineWidth); + expect(target.plane).toBe(source.plane); + expect(target.dimensions).toBe(source.dimensions); + expect(target.shadows).toBe(source.shadows); + expect(target.distanceDisplayCondition).toBe(source.distanceDisplayCondition); + }); + + it('merge does not assign assigned properties', function() { + var source = new PlaneGraphics(); + + var material = new ColorMaterialProperty(); + var show = new ConstantProperty(); + var fill = new ConstantProperty(); + var outline = new ConstantProperty(); + var outlineColor = new ConstantProperty(); + var outlineWidth = new ConstantProperty(); + var plane = new ConstantProperty(); + var dimensions = new ConstantProperty(); + var shadows = new ConstantProperty(); + var distanceDisplayCondition = new ConstantProperty(); + + var target = new PlaneGraphics(); + target.material = material; + target.show = show; + target.fill = fill; + target.outline = outline; + target.outlineColor = outlineColor; + target.outlineWidth = outlineWidth; + target.plane = plane; + target.dimensions = dimensions; + target.shadows = shadows; + target.distanceDisplayCondition = distanceDisplayCondition; + + target.merge(source); + + expect(target.material).toBe(material); + expect(target.show).toBe(show); + expect(target.fill).toBe(fill); + expect(target.outline).toBe(outline); + expect(target.outlineColor).toBe(outlineColor); + expect(target.outlineWidth).toBe(outlineWidth); + expect(target.plane).toBe(plane); + expect(target.dimensions).toBe(dimensions); + expect(target.shadows).toBe(shadows); + expect(target.distanceDisplayCondition).toBe(distanceDisplayCondition); + }); + + it('clone works', function() { + var source = new PlaneGraphics(); + source.material = new ColorMaterialProperty(); + source.show = new ConstantProperty(); + source.fill = new ConstantProperty(); + source.outline = new ConstantProperty(); + source.outlineColor = new ConstantProperty(); + source.outlineWidth = new ConstantProperty(); + source.plane = new ConstantProperty(); + source.dimensions = new ConstantProperty(); + source.shadows = new ConstantProperty(); + source.distanceDisplayCondition = new ConstantProperty(); + + var result = source.clone(); + expect(result.material).toBe(source.material); + expect(result.show).toBe(source.show); + expect(result.fill).toBe(source.fill); + expect(result.outline).toBe(source.outline); + expect(result.outlineColor).toBe(source.outlineColor); + expect(result.outlineWidth).toBe(source.outlineWidth); + expect(result.plane).toBe(source.plane); + expect(result.dimensions).toBe(source.dimensions); + expect(result.shadows).toBe(source.shadows); + expect(result.distanceDisplayCondition).toBe(source.distanceDisplayCondition); + }); + + it('merge throws if source undefined', function() { + var target = new PlaneGraphics(); + expect(function() { + target.merge(undefined); + }).toThrowDeveloperError(); + }); + + it('raises definitionChanged when a property is assigned or modified', function() { + var property = new PlaneGraphics(); + testMaterialDefinitionChanged(property, 'material', Color.RED, Color.BLUE); + testDefinitionChanged(property, 'show', true, false); + testDefinitionChanged(property, 'fill', false, true); + testDefinitionChanged(property, 'outline', true, false); + testDefinitionChanged(property, 'outlineColor', Color.RED, Color.BLUE); + testDefinitionChanged(property, 'outlineWidth', 2, 3); + testDefinitionChanged(property, 'plane', new Plane(Cartesian3.UNIT_X, 0.0), new Plane(Cartesian3.UNIT_Z, 1.0)); + testDefinitionChanged(property, 'dimensions', new Cartesian2(0.0, 0.0), new Cartesian2(1.0, 1.0)); + testDefinitionChanged(property, 'shadows', ShadowMode.ENABLED, ShadowMode.DISABLED); + testDefinitionChanged(property, 'distanceDisplayCondition', new DistanceDisplayCondition(), new DistanceDisplayCondition(10.0, 100.0)); + }); +}); diff --git a/Specs/Scene/ModelSpec.js b/Specs/Scene/ModelSpec.js index 7ae7ba9e3385..c52fdb0a614b 100644 --- a/Specs/Scene/ModelSpec.js +++ b/Specs/Scene/ModelSpec.js @@ -22,6 +22,7 @@ defineSuite([ 'Core/Matrix3', 'Core/Matrix4', 'Core/PerspectiveFrustum', + 'Core/Plane', 'Core/PrimitiveType', 'Core/Transforms', 'Core/WebGLConstants', @@ -29,6 +30,7 @@ defineSuite([ 'Renderer/RenderState', 'Renderer/ShaderSource', 'Scene/Axis', + 'Scene/ClippingPlanesCollection', 'Scene/ColorBlendMode', 'Scene/HeightReference', 'Scene/ModelAnimationLoop', @@ -59,6 +61,7 @@ defineSuite([ Matrix3, Matrix4, PerspectiveFrustum, + Plane, PrimitiveType, Transforms, WebGLConstants, @@ -66,6 +69,7 @@ defineSuite([ RenderState, ShaderSource, Axis, + ClippingPlanesCollection, ColorBlendMode, HeightReference, ModelAnimationLoop, @@ -2397,6 +2401,8 @@ defineSuite([ scene.renderForSpecs(); var commands = scene.frameState.commandList; expect(commands.length).toBe(0); + + primitives.remove(model); }); }); @@ -2503,9 +2509,9 @@ defineSuite([ model.silhouetteColor = Color.GREEN; // Load a second model - return loadModel(boxUrl).then(function(model) { - model.show = true; - model.silhouetteSize = 1.0; + return loadModel(boxUrl).then(function(model2) { + model2.show = true; + model2.silhouetteSize = 1.0; scene.renderForSpecs(); expect(commands.length).toBe(4); expect(commands[0].renderState.stencilTest.enabled).toBe(true); @@ -2520,7 +2526,139 @@ defineSuite([ var reference1 = commands[0].renderState.stencilTest.reference; var reference2 = commands[2].renderState.stencilTest.reference; expect(reference2).toEqual(reference1 + 1); + + primitives.remove(model); + primitives.remove(model2); + }); + }); + }); + + it('Updates clipping planes when clipping planes are enabled', function () { + return loadModel(boxUrl).then(function(model) { + model.show = true; + model.zoomTo(); + + scene.renderForSpecs(); + + expect(model._packedClippingPlanes).toBeDefined(); + expect(model._packedClippingPlanes.length).toBe(0); + expect(model._modelViewMatrix).toEqual(Matrix4.IDENTITY); + + model.clippingPlanes = new ClippingPlanesCollection({ + planes : [ + new Plane(Cartesian3.UNIT_X, 0.0) + ] }); + + model.update(scene.frameState); + scene.renderForSpecs(); + + expect(model._packedClippingPlanes.length).toBe(1); + expect(model._modelViewMatrix).not.toEqual(Matrix4.IDENTITY); + + primitives.remove(model); + }); + }); + + it('Clipping planes selectively disable rendering', function () { + return loadModel(boxUrl).then(function(model) { + model.show = true; + model.zoomTo(); + + var modelColor; + model.update(scene.frameState); + expect(scene).toRenderAndCall(function(rgba) { + modelColor = rgba; + }); + + var plane = new Plane(Cartesian3.UNIT_X, 0.0); + model.clippingPlanes = new ClippingPlanesCollection({ + planes : [ + plane + ] + }); + + model.update(scene.frameState); + expect(scene).toRenderAndCall(function(rgba) { + expect(rgba).not.toEqual(modelColor); + }); + + plane.distance = -10.0; + model.update(scene.frameState); + expect(scene).toRenderAndCall(function(rgba) { + expect(rgba).toEqual(modelColor); + }); + + primitives.remove(model); + }); + }); + + it('Clipping planes apply edge styling', function () { + return loadModel(boxUrl).then(function(model) { + model.show = true; + model.zoomTo(); + + var modelColor; + model.update(scene.frameState); + expect(scene).toRenderAndCall(function(rgba) { + modelColor = rgba; + }); + + var plane = new Plane(Cartesian3.UNIT_X, 0.0); + model.clippingPlanes = new ClippingPlanesCollection({ + planes : [ + plane + ], + edgeWidth : 5.0, + edgeColor : Color.BLUE + }); + + model.update(scene.frameState); + expect(scene).toRenderAndCall(function(rgba) { + expect(rgba).not.toEqual(modelColor); + }); + + plane.distance = -5.0; + model.update(scene.frameState); + expect(scene).toRenderAndCall(function(rgba) { + expect(rgba).toEqual([0, 0, 255, 255]); + }); + + primitives.remove(model); + }); + }); + + it('Clipping planes combien regions', function () { + return loadModel(boxUrl).then(function(model) { + model.show = true; + model.zoomTo(); + + var modelColor; + model.update(scene.frameState); + expect(scene).toRenderAndCall(function(rgba) { + modelColor = rgba; + }); + + model.clippingPlanes = new ClippingPlanesCollection({ + planes : [ + new Plane(Cartesian3.UNIT_Z, -5.0), + new Plane(Cartesian3.UNIT_X, 0.0) + ], + combineClippingRegions: false + }); + + model.update(scene.frameState); + expect(scene).toRenderAndCall(function(rgba) { + expect(rgba).not.toEqual(modelColor); + }); + + model.clippingPlanes._combineClippingRegions = true; + model.update(scene.frameState); + expect(scene).toRenderAndCall(function(rgba) { + expect(rgba).toEqual(modelColor); + }); + + primitives.remove(model); }); }); diff --git a/Specs/Scene/PointCloud3DTileContentSpec.js b/Specs/Scene/PointCloud3DTileContentSpec.js index b327f6a85651..b45c5c706682 100644 --- a/Specs/Scene/PointCloud3DTileContentSpec.js +++ b/Specs/Scene/PointCloud3DTileContentSpec.js @@ -803,7 +803,6 @@ defineSuite([ }); }); - it('destroys', function() { return Cesium3DTilesTester.tileDestroys(scene, pointCloudRGBUrl); }); From 82a2780d9ecce10167fca9eb22707308ea27ba02 Mon Sep 17 00:00:00 2001 From: ggetz Date: Fri, 1 Dec 2017 16:00:07 -0500 Subject: [PATCH 32/37] Cleanup --- Specs/Scene/Cesium3DTilesetSpec.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js index f1487346875e..841adfd5861e 100644 --- a/Specs/Scene/Cesium3DTilesetSpec.js +++ b/Specs/Scene/Cesium3DTilesetSpec.js @@ -2861,7 +2861,7 @@ defineSuite([ }); }); - it('clipping planes cull tiles completely inside', function() { + 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; @@ -2903,11 +2903,8 @@ defineSuite([ }); }); - it('clipping planes cull tiles completely inside for i3dm', function() { + it('clipping planes cull tiles completely inside clipping region for i3dm', function() { return Cesium3DTilesTester.loadTileset(scene, instancedUrl).then(function(tileset) { - - console.log(tileset); - var statistics = tileset._statistics; var root = tileset._root; From ca40033736046f1e577555aee4f4f1f623bde8d7 Mon Sep 17 00:00:00 2001 From: ggetz Date: Fri, 1 Dec 2017 16:07:19 -0500 Subject: [PATCH 33/37] i3dm tileset in specs --- Specs/Scene/Cesium3DTilesetSpec.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js index 841adfd5861e..1c87581a8a78 100644 --- a/Specs/Scene/Cesium3DTilesetSpec.js +++ b/Specs/Scene/Cesium3DTilesetSpec.js @@ -2904,13 +2904,13 @@ defineSuite([ }); it('clipping planes cull tiles completely inside clipping region for i3dm', function() { - return Cesium3DTilesTester.loadTileset(scene, instancedUrl).then(function(tileset) { + return Cesium3DTilesTester.loadTileset(scene, tilesetWithExternalResourcesUrl).then(function(tileset) { var statistics = tileset._statistics; var root = tileset._root; scene.renderForSpecs(); - expect(statistics.numberOfCommands).toEqual(1); + expect(statistics.numberOfCommands).toEqual(6); tileset.update(scene.frameState); @@ -2924,18 +2924,18 @@ defineSuite([ tileset.update(scene.frameState); scene.renderForSpecs(); - expect(statistics.numberOfCommands).toEqual(1); + expect(statistics.numberOfCommands).toEqual(6); expect(root._isClipped).toBe(false); - plane.distance = 4081611.654572015; // center + plane.distance = 4081608.4377916814; // center tileset.update(scene.frameState); scene.renderForSpecs(); - expect(statistics.numberOfCommands).toEqual(1); + expect(statistics.numberOfCommands).toEqual(6); expect(root._isClipped).toBe(true); - plane.distance = 4081611.654572015 + 142.6291406685467; // center + radius + plane.distance = 4081608.4377916814 + 142.19001637409772; // center + radius tileset.update(scene.frameState); scene.renderForSpecs(); From 31079f72bad6ead15800aab8dd9c04a537fc83bf Mon Sep 17 00:00:00 2001 From: ggetz Date: Fri, 1 Dec 2017 17:10:24 -0500 Subject: [PATCH 34/37] Added i3dm tilesets --- Source/Scene/Batched3DModel3DTileContent.js | 6 +++--- Source/Scene/ClippingPlanesCollection.js | 12 ++++++++++-- Source/Scene/Instanced3DModel3DTileContent.js | 15 ++++++++++++++- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/Source/Scene/Batched3DModel3DTileContent.js b/Source/Scene/Batched3DModel3DTileContent.js index 6013c32f9727..ec9df91a15e1 100644 --- a/Source/Scene/Batched3DModel3DTileContent.js +++ b/Source/Scene/Batched3DModel3DTileContent.js @@ -14,10 +14,10 @@ define([ '../Core/RequestType', '../Core/RuntimeError', '../Renderer/Pass', - '../Scene/ClippingPlanesCollection', './Cesium3DTileBatchTable', './Cesium3DTileFeature', './Cesium3DTileFeatureTable', + './ClippingPlanesCollection', './getAttributeOrUniformBySemantic', './Model' ], function( @@ -36,10 +36,10 @@ define([ RequestType, RuntimeError, Pass, - ClippingPlanesCollections, Cesium3DTileBatchTable, Cesium3DTileFeature, Cesium3DTileFeatureTable, + ClippingPlanesCollection, getAttributeOrUniformBySemantic, Model) { 'use strict'; @@ -380,7 +380,7 @@ define([ pickUniformMapLoaded : batchTable.getPickUniformMapCallback(), addBatchIdToGeneratedShaders : (batchLength > 0), // If the batch table has values in it, generated shaders will need a batchId attribute pickObject : pickObject, - clippingPlanes : new ClippingPlanesCollections({ + clippingPlanes : new ClippingPlanesCollection({ enabled : false }) }); diff --git a/Source/Scene/ClippingPlanesCollection.js b/Source/Scene/ClippingPlanesCollection.js index d1b9bf411386..8200382e642c 100644 --- a/Source/Scene/ClippingPlanesCollection.js +++ b/Source/Scene/ClippingPlanesCollection.js @@ -172,12 +172,20 @@ define([ } var length = this.planes.length; + var i; if (result.planes.length !== length) { result.planes = new Array(length); + + for (i = 0; i < length; ++i) { + result.planes[i] = new Plane(Cartesian3.UNIT_X, 0.0); + } } - for (var i = 0; i < length; ++i) { + + for (i = 0; i < length; ++i) { var plane = this.planes[i]; - result.planes[i] = new Plane(plane.normal, plane.distance); + var resultPlane = result.planes[i]; + resultPlane.normal = plane.normal; + resultPlane.distance = plane.distance; } result.enabled = this.enabled; diff --git a/Source/Scene/Instanced3DModel3DTileContent.js b/Source/Scene/Instanced3DModel3DTileContent.js index 2bd40a0ae6db..0e66cfc5989b 100644 --- a/Source/Scene/Instanced3DModel3DTileContent.js +++ b/Source/Scene/Instanced3DModel3DTileContent.js @@ -26,6 +26,7 @@ define([ './Cesium3DTileBatchTable', './Cesium3DTileFeature', './Cesium3DTileFeatureTable', + './ClippingPlanesCollection', './ModelInstanceCollection' ], function( AttributeCompression, @@ -55,6 +56,7 @@ define([ Cesium3DTileBatchTable, Cesium3DTileFeature, Cesium3DTileFeatureTable, + ClippingPlanesCollection, ModelInstanceCollection) { 'use strict'; @@ -443,7 +445,6 @@ define([ var modelMatrix = instanceTransform.clone(); instances[i] = { modelMatrix : modelMatrix, - batchId : batchId }; } @@ -514,6 +515,18 @@ define([ this._modelInstanceCollection.debugWireframe = this._tileset.debugWireframe; this._modelInstanceCollection.update(frameState); + // Update clipping planes + var tilesetClippingPlanes = this._tileset.clippingPlanes; + if (defined(tilesetClippingPlanes)) { + var model = this._modelInstanceCollection._model; + if (!defined(model.clippingPlanes)) { + model.clippingPlanes = new ClippingPlanesCollection(); + } + + tilesetClippingPlanes.clone(model.clippingPlanes); + model.clippingPlanes.enabled = tilesetClippingPlanes.enabled && this._tile._isClipped; + } + // If any commands were pushed, add derived commands var commandEnd = frameState.commandList.length; if ((commandStart < commandEnd) && frameState.passes.render) { From b83e288b743c3035c39dfca106f06db2dee66001 Mon Sep 17 00:00:00 2001 From: ggetz Date: Fri, 1 Dec 2017 17:15:57 -0500 Subject: [PATCH 35/37] i3dm specs --- .../Instanced3DModel3DTileContentSpec.js | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/Specs/Scene/Instanced3DModel3DTileContentSpec.js b/Specs/Scene/Instanced3DModel3DTileContentSpec.js index ab03463ac95e..4c00160251a6 100644 --- a/Specs/Scene/Instanced3DModel3DTileContentSpec.js +++ b/Specs/Scene/Instanced3DModel3DTileContentSpec.js @@ -4,8 +4,10 @@ defineSuite([ 'Core/Color', 'Core/HeadingPitchRange', 'Core/HeadingPitchRoll', + 'Core/Plane', 'Core/Transforms', 'Scene/Cesium3DTileContentState', + 'Scene/ClippingPlanesCollection', 'Scene/TileBoundingSphere', 'Specs/Cesium3DTilesTester', 'Specs/createScene' @@ -15,8 +17,10 @@ defineSuite([ Color, HeadingPitchRange, HeadingPitchRoll, + Plane, Transforms, Cesium3DTileContentState, + ClippingPlanesCollection, TileBoundingSphere, Cesium3DTilesTester, createScene) { @@ -305,6 +309,32 @@ defineSuite([ }); }); + it('Updates model\'s clipping planes', function() { + return Cesium3DTilesTester.loadTileset(scene, withBatchTableUrl).then(function(tileset) { + var tile = tileset._root; + var content = tile.content; + var model = content._modelInstanceCollection._model; + + expect(model.clippingPlanes).toBeUndefined(); + + tileset.clippingPlanes = new ClippingPlanesCollection({ + planes : [ + new Plane(Cartesian3.UNIT_X, 0.0) + ] + }); + content.update(tileset, scene.frameState); + + expect(model.clippingPlanes).toBeDefined(); + expect(model.clippingPlanes.planes.length).toBe(1); + expect(model.clippingPlanes.enabled).toBe(true); + + tile._isClipped = false; + content.update(tileset, scene.frameState); + + expect(model.clippingPlanes.enabled).toBe(false); + }); + }); + it('destroys', function() { return Cesium3DTilesTester.tileDestroys(scene, withoutBatchTableUrl); }); From 2d855b8c79031b7c8a8c26ef73c6fda4a79bfecb Mon Sep 17 00:00:00 2001 From: ggetz Date: Fri, 1 Dec 2017 22:15:22 -0500 Subject: [PATCH 36/37] Add instanced tileset to sandcastle example --- Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html | 8 +++++++- Source/Scene/ClippingPlanesCollection.js | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html b/Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html index 22a3fb7d3632..a790e19678dc 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html +++ b/Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html @@ -55,7 +55,7 @@ }); var scene = viewer.scene; -var clipObjects = ['BIM', 'Point Cloud', 'Model']; +var clipObjects = ['BIM', 'Point Cloud', 'Instanced', 'Model',]; var viewModel = { debugBoundingVolumesEnabled : false, edgeStylingEnabled : true, @@ -211,6 +211,7 @@ // Power Plant design model provided by Bentley Systems var bimUrl = 'https://beta.cesium.com/api/assets/1459?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIzNjUyM2I5Yy01YmRhLTQ0MjktOGI0Zi02MDdmYzBjMmY0MjYiLCJpZCI6NDQsImFzc2V0cyI6WzE0NTldLCJpYXQiOjE0OTkyNjQ3ODF9.SW_rwY-ic0TwQBeiweXNqFyywoxnnUBtcVjeCmDGef4'; var pointCloudUrl = 'https://beta.cesium.com/api/assets/1460?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIyMzk2YzJiOS1jZGFmLTRlZmYtYmQ4MS00NTA3NjEwMzViZTkiLCJpZCI6NDQsImFzc2V0cyI6WzE0NjBdLCJpYXQiOjE0OTkyNjQ3NTV9.oWjvN52CRQ-dk3xtvD4e8ZnOHZhoWSpJLlw115mbQJM'; +var instancedUrl = '../../../Specs/Data/Cesium3DTiles/Instanced/InstancedOrientation/'; var modelUrl = '../../SampleData/models/CesiumAir/Cesium_Air.glb'; loadTileset(bimUrl); @@ -230,6 +231,11 @@ tileset.readyPromise.then(function () { tileset.clippingPlanes.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(tileset.boundingSphere.center); }); + } else if (newValue === clipObjects[2]) { + loadTileset(instancedUrl); + tileset.readyPromise.then(function () { + tileset.clippingPlanes.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(tileset.boundingSphere.center); + }); } else { loadModel(modelUrl); } diff --git a/Source/Scene/ClippingPlanesCollection.js b/Source/Scene/ClippingPlanesCollection.js index 8200382e642c..33d21eec26d8 100644 --- a/Source/Scene/ClippingPlanesCollection.js +++ b/Source/Scene/ClippingPlanesCollection.js @@ -137,7 +137,7 @@ define([ var length = planes.length; var i; - if (!defined(array)) { + if (!defined(array) || array.length !== length) { array = new Array(length); for (i = 0; i < length; ++i) { From c8cd6fa82c10feb882593d6ee7dd1ddf9c35bb89 Mon Sep 17 00:00:00 2001 From: ggetz Date: Mon, 4 Dec 2017 09:27:11 -0500 Subject: [PATCH 37/37] Tweak sandcastle example --- Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html b/Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html index a790e19678dc..659492fb649f 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html +++ b/Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html @@ -55,7 +55,7 @@ }); var scene = viewer.scene; -var clipObjects = ['BIM', 'Point Cloud', 'Instanced', 'Model',]; +var clipObjects = ['BIM', 'Point Cloud', 'Instanced', 'Model']; var viewModel = { debugBoundingVolumesEnabled : false, edgeStylingEnabled : true, @@ -105,7 +105,7 @@ var scratchPlane = new Cesium.Plane(Cesium.Cartesian3.UNIT_X, 0.0); function createPlaneUpdateFunction(plane, transform) { return function () { - plane.distance = Cesium.Math.lerp(plane.distance, targetY, 0.1); + plane.distance = targetY; var transformedPlane = Cesium.Plane.transform(plane, transform, scratchPlane); transformedPlane.distance = -transformedPlane.distance;