diff --git a/Apps/Sandcastle/gallery/Clipping Planes.html b/Apps/Sandcastle/gallery/Clipping Planes.html new file mode 100644 index 000000000000..5637666be683 --- /dev/null +++ b/Apps/Sandcastle/gallery/Clipping Planes.html @@ -0,0 +1,191 @@ + + + + + + + + + Cesium Demo + + + + + + +
+

Loading...

+
+ + + + + + + + + + + + + + + + + +
Type
x + + +
y + + +
z + + +
+
+ + + + diff --git a/Apps/Sandcastle/gallery/Clipping Planes.jpg b/Apps/Sandcastle/gallery/Clipping Planes.jpg new file mode 100644 index 000000000000..b8b63c0307b4 Binary files /dev/null and b/Apps/Sandcastle/gallery/Clipping Planes.jpg differ diff --git a/CHANGES.md b/CHANGES.md index 432dff7bc1a0..4ef4adbcc2d6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -18,6 +18,7 @@ Change Log * Fixed a 3D Tiles point cloud bug causing a stray point to appear at the center of the screen on certain hardware. [#5599](https://github.com/AnalyticalGraphicsInc/cesium/issues/5599) * Fixed removing multiple event listeners within event callbacks. [#5827](https://github.com/AnalyticalGraphicsInc/cesium/issues/5827) * Running `buildApps` now creates a built version of Sandcastle which uses the built version of Cesium for better performance. +* Added `clippingPlanes` property to models and 3D Tilesets, which specify an array of planes to clip the object. [TODO]() * Fixed a tileset traversal bug when the `skipLevelOfDetail` optimization is off. [#5869](https://github.com/AnalyticalGraphicsInc/cesium/issues/5869) ### 1.37 - 2017-09-01 diff --git a/Source/DataSources/ModelGraphics.js b/Source/DataSources/ModelGraphics.js index db69b5d8df4f..f0f258248d5b 100644 --- a/Source/DataSources/ModelGraphics.js +++ b/Source/DataSources/ModelGraphics.js @@ -54,6 +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 {@link Plane} used to clip 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} @@ -91,6 +92,8 @@ define([ this._colorBlendModeSubscription = undefined; this._colorBlendAmount = undefined; this._colorBlendAmountSubscription = undefined; + this._clippingPlanes = undefined; + this._clippingPlanesSubscription = undefined; this._definitionChanged = new Event(); this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); @@ -242,7 +245,15 @@ define([ * @type {Property} * @default 0.5 */ - colorBlendAmount : createPropertyDescriptor('colorBlendAmount') + colorBlendAmount : createPropertyDescriptor('colorBlendAmount'), + + /** + * A property specifying an array of {@link Plane} used to clip the model. + * @memberof ModelGraphics.prototype + * @type {Property} + * @default [] + */ + clippingPlanes: createPropertyDescriptor('clippingPlanes') }); /** @@ -271,6 +282,7 @@ define([ result.color = this.color; result.colorBlendMode = this.colorBlendMode; result.colorBlendAmount = this.colorBlendAmount; + result.clippingPlanes = this.clippingPlanes; return result; }; @@ -303,6 +315,7 @@ define([ this.color = defaultValue(this.color, source.color); this.colorBlendMode = defaultValue(this.colorBlendMode, source.colorBlendMode); this.colorBlendAmount = defaultValue(this.colorBlendAmount, source.colorBlendAmount); + this.clippingPlanes = defaultValue(this.clippingPlanes, source.clippingPlanes); var sourceNodeTransformations = source.nodeTransformations; if (defined(sourceNodeTransformations)) { diff --git a/Source/DataSources/ModelVisualizer.js b/Source/DataSources/ModelVisualizer.js index bf6e48d2dd2f..f878277f2983 100644 --- a/Source/DataSources/ModelVisualizer.js +++ b/Source/DataSources/ModelVisualizer.js @@ -40,6 +40,7 @@ define([ var defaultColor = Color.WHITE; var defaultColorBlendMode = ColorBlendMode.HIGHLIGHT; var defaultColorBlendAmount = 0.5; + var defaultClippingPlanes = []; var modelMatrixScratch = new Matrix4(); var nodeMatrixScratch = new Matrix4(); @@ -152,6 +153,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); if (model.ready) { var runAnimations = Property.getValueOrDefault(modelGraphics._runAnimations, time, true); diff --git a/Source/Scene/Batched3DModel3DTileContent.js b/Source/Scene/Batched3DModel3DTileContent.js index 7db1c61386cb..34fdb2db1f3c 100644 --- a/Source/Scene/Batched3DModel3DTileContent.js +++ b/Source/Scene/Batched3DModel3DTileContent.js @@ -377,7 +377,8 @@ 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 : tileset.clippingPlanes }); } @@ -447,6 +448,7 @@ 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.update(frameState); // If any commands were pushed, add derived commands diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index cca0bd85e6f7..80183bc431f6 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -105,6 +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=[]] An array of {@link Plane} used to clip 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. @@ -522,6 +523,14 @@ define([ */ this.loadSiblings = defaultValue(options.loadSiblings, false); + /** + * An array of {@link Plane} used to clip the tileset. + * + * @type {Plane[]} + * @default [] + */ + this.clippingPlanes = defaultValue(options.clippingPlanes, []); + /** * This property is for debugging only; it is not optimized for production use. *

diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js index 5001d1c5c14d..455b64046bec 100644 --- a/Source/Scene/Model.js +++ b/Source/Scene/Model.js @@ -339,6 +339,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. * * @exception {DeveloperError} bgltf is not a valid Binary glTF file. * @exception {DeveloperError} Only glTF Binary version 1 is supported. @@ -576,6 +577,15 @@ define([ */ this.colorBlendAmount = defaultValue(options.colorBlendAmount, 0.5); + /** + * An array of {@link Plane} used to clip the model. + * + * @type {Plane[]} + * + * @default [] + */ + this.clippingPlanes = defaultValue(options.clippingPlanes, []); + /** * This property is for debugging only; it is not for production use nor is it optimized. *

@@ -688,6 +698,8 @@ define([ this._rtcCenterEye = undefined; // in eye coordinates this._rtcCenter3D = undefined; // in world coordinates this._rtcCenter2D = undefined; // in projected world coordinates + + this._packedClippingPlanes = []; } defineProperties(Model.prototype, { @@ -2096,9 +2108,10 @@ define([ var premultipliedAlpha = hasPremultipliedAlpha(model); var blendFS = modifyShaderForColor(fs, premultipliedAlpha); + var clippingFS = modifyShaderForClippingPlanes(blendFS); var drawVS = modifyShader(vs, id, model._vertexShaderLoaded); - var drawFS = modifyShader(blendFS, id, model._fragmentShaderLoaded); + var drawFS = modifyShader(clippingFS, id, model._fragmentShaderLoaded); model._rendererResources.programs[id] = ShaderProgram.fromCache({ context : context, @@ -3293,6 +3306,37 @@ define([ }; } + function createClippingPlanesLengthFunction(model) { + return function() { + return model.clippingPlanes.length; + }; + } + + var scratchCartesian = new Cartesian3(); + var scratchMatrix = new Matrix4(); + + function createClippingPlanesFunction(model, context) { + return function() { + var scale = getScale(model, context.uniformState.frameState); + Matrix4.multiplyByUniformScale(model.modelMatrix, scale, scratchMatrix); + Matrix4.multiply(context.uniformState.view3D, scratchMatrix, scratchMatrix); + + var planes = model.clippingPlanes; + var length = planes.length; + var packedPlanes = model._packedClippingPlanes; + for (var i = 0; i < length; ++i) { + var plane = planes[i]; + var packedPlane = packedPlanes[i]; + Matrix4.multiplyByPointAsVector(scratchMatrix, plane.normal, packedPlane); + Cartesian3.normalize(packedPlane, packedPlane); + Cartesian3.multiplyByScalar(plane.normal, plane.distance, scratchCartesian); + Matrix4.multiplyByPoint(scratchMatrix, scratchCartesian, scratchCartesian); + packedPlane.w = Cartesian3.dot(packedPlane, scratchCartesian); + } + return packedPlanes; + }; + } + function createColorBlendFunction(model) { return function() { return ColorBlendMode.getColorBlend(model.colorBlendMode, model.colorBlendAmount); @@ -3387,7 +3431,9 @@ define([ uniformMap = combine(uniformMap, { gltf_color : createColorFunction(model), - gltf_colorBlend : createColorBlendFunction(model) + gltf_colorBlend : createColorBlendFunction(model), + gltf_clippingPlanesLength: createClippingPlanesLengthFunction(model), + gltf_clippingPlanes: createClippingPlanesFunction(model, context) }); // Allow callback to modify the uniformMap @@ -4181,6 +4227,20 @@ define([ } } + function modifyShaderForClippingPlanes(shader) { + shader = ShaderSource.replaceMain(shader, 'gltf_clip_main'); + shader += + 'uniform int gltf_clippingPlanesLength; \n' + + 'uniform vec4 gltf_clippingPlanes[czm_maxClippingPlanes]; \n' + + 'void main() \n' + + '{ \n' + + ' gltf_clip_main(); \n' + + ' czm_clipPlanes(gltf_clippingPlanes, gltf_clippingPlanesLength); \n' + + '} \n'; + + return shader; + } + function updateSilhouette(model, frameState) { // Generate silhouette commands when the silhouette size is greater than 0.0 and the alpha is greater than 0.0 // There are two silhouette commands: @@ -4203,6 +4263,18 @@ define([ } } + function updateClippingPlanes(model) { + var length = model.clippingPlanes.length; + + if (model._packedClippingPlanes.length !== length) { + model._packedClippingPlanes = new Array(length); + + for (var i = 0; i < length; ++i) { + model._packedClippingPlanes[i] = new Cartesian4(); + } + } + } + var scratchBoundingSphere = new BoundingSphere(); function scaleInPixels(positionWC, radius, frameState) { @@ -4667,6 +4739,7 @@ define([ updateShadows(this); updateColor(this, frameState); updateSilhouette(this, frameState); + updateClippingPlanes(this); } if (justLoaded) { diff --git a/Source/Scene/PointCloud3DTileContent.js b/Source/Scene/PointCloud3DTileContent.js index d82a88667bb4..4280deb069a1 100644 --- a/Source/Scene/PointCloud3DTileContent.js +++ b/Source/Scene/PointCloud3DTileContent.js @@ -12,6 +12,7 @@ define([ '../Core/DeveloperError', '../Core/FeatureDetection', '../Core/getStringFromTypedArray', + '../Core/Matrix3', '../Core/Matrix4', '../Core/oneTimeWarning', '../Core/PrimitiveType', @@ -46,6 +47,7 @@ define([ DeveloperError, FeatureDetection, getStringFromTypedArray, + Matrix3, Matrix4, oneTimeWarning, PrimitiveType, @@ -134,6 +136,8 @@ define([ this._features = undefined; + this._packedClippingPlanes = []; + /** * @inheritdoc Cesium3DTileContent#featurePropertiesDirty */ @@ -510,6 +514,8 @@ define([ } } + var scratchCartesian = new Cartesian3(); + var uniformMap = { u_pointSizeAndTilesetTime : function() { scratchPointSizeAndTilesetTime.x = content._pointSize; @@ -521,6 +527,24 @@ define([ }, u_constantColor : function() { return content._constantColor; + }, + u_clippingPlanesLength : function() { + return content._tileset.clippingPlanes.length; + }, + u_clippingPlanes : function() { + var planes = content._tileset.clippingPlanes; + var length = planes.length; + var packedPlanes = content._packedClippingPlanes; + for (var i = 0; i < length; ++i) { + var plane = planes[i]; + var packedPlane = packedPlanes[i]; + + Matrix3.multiplyByVector(context.uniformState.normal, plane.normal, packedPlane); + Cartesian3.multiplyByScalar(plane.normal, plane.distance, scratchCartesian); + Matrix4.multiplyByPoint(context.uniformState.modelView3D, scratchCartesian, scratchCartesian); + packedPlane.w = Cartesian3.dot(packedPlane, scratchCartesian); + } + return packedPlanes; } }; @@ -1030,8 +1054,11 @@ define([ vs += '} \n'; var fs = 'varying vec4 v_color; \n' + + 'uniform int u_clippingPlanesLength;' + + 'uniform vec4 u_clippingPlanes[czm_maxClippingPlanes]; \n' + 'void main() \n' + '{ \n' + + ' czm_clipPlanes(u_clippingPlanes, u_clippingPlanesLength); \n' + ' gl_FragColor = v_color; \n' + '} \n'; @@ -1166,6 +1193,20 @@ define([ this._mode = frameState.mode; + // update clipping planes + var length = 0; + if (defined(this._tileset.clippingPlanes)) { + length = this._tileset.clippingPlanes.length; + } + + if (this._packedClippingPlanes.length !== length) { + this._packedClippingPlanes = new Array(length); + + for (var i = 0; i < length; ++i) { + this._packedClippingPlanes[i] = new Cartesian4(); + } + } + if (!defined(this._drawCommand)) { createResources(this, frameState); createShaders(this, frameState, tileset.style); diff --git a/Source/Shaders/Builtin/Constants/maxClippingPlanes.glsl b/Source/Shaders/Builtin/Constants/maxClippingPlanes.glsl new file mode 100644 index 000000000000..30c377df1668 --- /dev/null +++ b/Source/Shaders/Builtin/Constants/maxClippingPlanes.glsl @@ -0,0 +1,8 @@ +/** + * The maximum amount of planes that can be clipped by czm_clipPlanes. + * + * @name czm_maxClippingPlanes + * @glslConstant + * @see czm_clipPlanes + */ +const int czm_maxClippingPlanes = 6; diff --git a/Source/Shaders/Builtin/Functions/clipPlanes.glsl b/Source/Shaders/Builtin/Functions/clipPlanes.glsl new file mode 100644 index 000000000000..086f505d3ef7 --- /dev/null +++ b/Source/Shaders/Builtin/Functions/clipPlanes.glsl @@ -0,0 +1,36 @@ +/** + * Clip a fragment by an array of clipping planes. + * + * @name czm_clipPlanes + * @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. + */ +void czm_clipPlanes (vec4[czm_maxClippingPlanes] clippingPlanes, int clippingPlanesLength) +{ + if (clippingPlanesLength > 0) + { + bool clipped = false; + vec4 position = czm_windowToEyeCoordinates(gl_FragCoord); + vec3 clipNormal = vec3(0.0, 0.0, 0.0); + vec3 clipPosition = vec3(0.0, 0.0, 0.0); + + for (int i = 0; i < czm_maxClippingPlanes; ++i) + { + if (i >= clippingPlanesLength) + { + break; + } + + clipNormal = clippingPlanes[i].xyz; + clipPosition = clippingPlanes[i].w * clipNormal; + clipped = clipped || (dot(clipNormal, (position.xyz - clipPosition)) > czm_epsilon7); + } + + if (clipped) + { + discard; + } + } +}