diff --git a/Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html b/Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html index ca5698c3faa5..df2f4e429d3e 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html +++ b/Apps/Sandcastle/gallery/3D Tiles Clipping Planes.html @@ -48,7 +48,6 @@ // Add a clipping plane, a plane geometry to show the representation of the // plane, and control the magnitude of the plane distance with the mouse. -// Clipping planes are not currently supported in Internet Explorer. var viewer = new Cesium.Viewer('cesiumContainer', { infoBox: false, @@ -103,7 +102,7 @@ } }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); -var scratchPlane = new Cesium.Plane(Cesium.Cartesian3.UNIT_X, 0.0); +var scratchPlane = new Cesium.ClippingPlane(Cesium.Cartesian3.UNIT_X, 0.0); function createPlaneUpdateFunction(plane, transform) { return function () { plane.distance = targetY; @@ -114,7 +113,7 @@ var tileset; function loadTileset(url) { var clippingPlanes = [ - new Cesium.Plane(new Cesium.Cartesian3(0.0, 0.0, -1.0), -100.0) + new Cesium.ClippingPlane(new Cesium.Cartesian3(0.0, 0.0, -1.0), -100.0) ]; tileset = viewer.scene.primitives.add(new Cesium.Cesium3DTileset({ @@ -156,7 +155,7 @@ var modelEntityClippingPlanes; function loadModel(url) { var clippingPlanes = [ - new Cesium.Plane(new Cesium.Cartesian3(0.0, 0.0, -1.0), -100.0) + new Cesium.ClippingPlane(new Cesium.Cartesian3(0.0, 0.0, -1.0), -100.0) ]; modelEntityClippingPlanes = new Cesium.ClippingPlaneCollection({ diff --git a/Apps/Sandcastle/gallery/Terrain Clipping Planes.html b/Apps/Sandcastle/gallery/Terrain Clipping Planes.html index faf4a6762e82..ecfa7394c8b0 100644 --- a/Apps/Sandcastle/gallery/Terrain Clipping Planes.html +++ b/Apps/Sandcastle/gallery/Terrain Clipping Planes.html @@ -40,7 +40,6 @@ 'use strict'; //Sandcastle_Begin // Use clipping planes to selectively hide parts of the globe surface. -// Clipping planes are not currently supported in Internet Explorer. var viewer = new Cesium.Viewer('cesiumContainer', { skyAtmosphere: false, @@ -74,10 +73,10 @@ globe.clippingPlanes = new Cesium.ClippingPlaneCollection({ modelMatrix : entity.computeModelMatrix(Cesium.JulianDate.now()), planes : [ - new Cesium.Plane(new Cesium.Cartesian3( 1.0, 0.0, 0.0), -700.0), - new Cesium.Plane(new Cesium.Cartesian3(-1.0, 0.0, 0.0), -700.0), - new Cesium.Plane(new Cesium.Cartesian3( 0.0, 1.0, 0.0), -700.0), - new Cesium.Plane(new Cesium.Cartesian3( 0.0, -1.0, 0.0), -700.0) + new Cesium.ClippingPlane(new Cesium.Cartesian3( 1.0, 0.0, 0.0), -700.0), + new Cesium.ClippingPlane(new Cesium.Cartesian3(-1.0, 0.0, 0.0), -700.0), + new Cesium.ClippingPlane(new Cesium.Cartesian3( 0.0, 1.0, 0.0), -700.0), + new Cesium.ClippingPlane(new Cesium.Cartesian3( 0.0, -1.0, 0.0), -700.0) ], edgeWidth: 1.0, edgeColor: Cesium.Color.WHITE diff --git a/Apps/Sandcastle/gallery/development/Many Clipping Planes.html b/Apps/Sandcastle/gallery/development/Many Clipping Planes.html new file mode 100644 index 000000000000..09bb82d8b791 --- /dev/null +++ b/Apps/Sandcastle/gallery/development/Many Clipping Planes.html @@ -0,0 +1,286 @@ + + +
+ + + + + +Distance | ++ + + | +Plane Count | ++ + + | +
distance
determines which side of the plane the origin
+ * is on. If distance
is positive, the origin is in the half-space
+ * in the direction of the normal; if negative, the origin is in the half-space
+ * opposite to the normal; if zero, the plane passes through the origin.
+ */
+ function ClippingPlane(normal, distance) {
+ this._distance = distance;
+ this._normal = new UpdateChangedCartesian3(normal, this);
+ this.onChangeCallback = undefined;
+ this.index = -1; // to be set by ClippingPlaneCollection
+ }
+
+ defineProperties(ClippingPlane.prototype, {
+ /**
+ * The shortest distance from the origin to the plane. The sign of
+ * distance
determines which side of the plane the origin
+ * is on. If distance
is positive, the origin is in the half-space
+ * in the direction of the normal; if negative, the origin is in the half-space
+ * opposite to the normal; if zero, the plane passes through the origin.
+ *
+ * @type {Number}
+ * @memberof ClippingPlane.prototype
+ */
+ distance : {
+ get : function() {
+ return this._distance;
+ },
+ set : function(value) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.number('value', value);
+ //>>includeEnd('debug');
+ if (defined(this.onChangeCallback) && value !== this._distance) {
+ this.onChangeCallback(this.index);
+ }
+ this._distance = value;
+ }
+ },
+ /**
+ * The plane's normal.
+ *
+ * @type {Cartesian3}
+ * @memberof ClippingPlane.prototype
+ */
+ normal : {
+ get : function() {
+ return this._normal;
+ },
+ set : function(value) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.object('value', value);
+ //>>includeEnd('debug');
+ if (defined(this.onChangeCallback) && !Cartesian3.equals(this._normal._cartesian3, value)) {
+ this.onChangeCallback(this.index);
+ }
+ // Set without firing callback again
+ Cartesian3.clone(value, this._normal._cartesian3);
+ }
+ }
+ });
+
+ /**
+ * Create a ClippingPlane from a Plane object.
+ *
+ * @param {Plane} plane The plane containing parameters to copy
+ * @param {ClippingPlane} [result] The object on which to store the result
+ * @returns {ClippingPlane} The ClippingPlane generated from the plane's parameters.
+ */
+ ClippingPlane.fromPlane = function(plane, result) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.object('plane', plane);
+ //>>includeEnd('debug');
+
+ if (!defined(result)) {
+ result = new ClippingPlane(plane.normal, plane.distance);
+ } else {
+ result.normal = plane.normal;
+ result.distance = plane.distance;
+ }
+ return result;
+ };
+
+ /**
+ * Clones the ClippingPlane without setting its ownership.
+ * @param {ClippingPlane} clippingPlane The ClippingPlane to be cloned
+ * @param {ClippingPlane} [result] The object on which to store the cloned parameters.
+ * @returns {ClippingPlane} a clone of the input ClippingPlane
+ */
+ ClippingPlane.clone = function(clippingPlane, result) {
+ if (!defined(result)) {
+ return new ClippingPlane(clippingPlane.normal, clippingPlane.distance);
+ }
+ result.normal = clippingPlane.normal;
+ result.distance = clippingPlane.distance;
+ return result;
+ };
+
+ /**
+ * Wrapper on Cartesian3 that allows detection of Plane changes from "members of members," for example:
+ *
+ * var clippingPlane = new ClippingPlane(...);
+ * clippingPlane.normal.z = -1.0;
+ *
+ * @private
+ */
+ function UpdateChangedCartesian3(normal, clippingPlane) {
+ this._clippingPlane = clippingPlane;
+ this._cartesian3 = Cartesian3.clone(normal);
+ }
+
+ function makeGetterStter(key) {
+ return {
+ get : function() {
+ return this._cartesian3[key];
+ },
+ set : function(value) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.number('value', value);
+ //>>includeEnd('debug');
+ if (defined(this._clippingPlane.onChangeCallback) && value !== this._cartesian3[key]) {
+ this._clippingPlane.onChangeCallback(this._clippingPlane.index);
+ }
+ this._cartesian3[key] = value;
+ }
+ };
+ }
+
+ defineProperties(UpdateChangedCartesian3.prototype, {
+ x : makeGetterStter('x'),
+ y : makeGetterStter('y'),
+ z : makeGetterStter('z')
+ });
+
+ return ClippingPlane;
+});
diff --git a/Source/Core/ClippingPlaneCollection.js b/Source/Core/ClippingPlaneCollection.js
index 56369d69202f..c694b0bbe61d 100644
--- a/Source/Core/ClippingPlaneCollection.js
+++ b/Source/Core/ClippingPlaneCollection.js
@@ -1,40 +1,66 @@
define([
+ './AttributeCompression',
+ './Cartesian2',
'./Cartesian3',
'./Cartesian4',
+ './Math',
'./Check',
+ './ClippingPlane',
'./Color',
'./defaultValue',
'./defined',
'./defineProperties',
+ './destroyObject',
'./DeveloperError',
'./FeatureDetection',
'./Intersect',
'./Matrix4',
- './Plane'
+ './PixelFormat',
+ './Plane',
+ '../Renderer/ContextLimits',
+ '../Renderer/PixelDatatype',
+ '../Renderer/Sampler',
+ '../Renderer/Texture',
+ '../Renderer/TextureMagnificationFilter',
+ '../Renderer/TextureMinificationFilter',
+ '../Renderer/TextureWrap'
], function(
+ AttributeCompression,
+ Cartesian2,
Cartesian3,
Cartesian4,
+ CesiumMath,
Check,
+ ClippingPlane,
Color,
defaultValue,
defined,
defineProperties,
+ destroyObject,
DeveloperError,
FeatureDetection,
Intersect,
Matrix4,
- Plane) {
+ PixelFormat,
+ Plane,
+ ContextLimits,
+ PixelDatatype,
+ Sampler,
+ Texture,
+ TextureMagnificationFilter,
+ TextureMinificationFilter,
+ TextureWrap) {
'use strict';
/**
* 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.
+ * outside of the specified list of {@link ClippingPlane} objects for a single gltf model, 3D Tileset, or the globe.
*
* @alias ClippingPlaneCollection
* @constructor
*
* @param {Object} [options] Object with the following properties:
- * @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 {ClippingPlane[]} [options.planes=[]] An array of {@link ClippingPlane} 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.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix specifying an additional transform relative to the clipping planes original coordinate system.
* @param {Boolean} [options.unionClippingRegions=false] If true, a region will be clipped if included in any plane in the collection. Otherwise, the region to be clipped must intersect the regions defined by all planes in this collection.
@@ -44,20 +70,26 @@ define([
function ClippingPlaneCollection(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
+ this._planes = [];
+ this._containsUntrackablePlanes = false;
+
+ // Do partial texture updates if just one plane is dirty.
+ // If many planes are dirty, refresh the entire texture.
+ this._dirtyIndex = -1;
+ this._manyDirtyPlanes = false;
+
+ // Add each plane to check if it's actually a Plane object instead of a ClippingPlane.
+ // Use of Plane objects will be deprecated.
var planes = options.planes;
if (defined(planes)) {
- this._planes = planes.slice(0);
- } else {
- this._planes = [];
+ var planesLength = planes.length;
+ for (var i = 0; i < planesLength; ++i) {
+ this.add(planes[i]);
+ }
}
- /**
- * Determines whether the clipping planes are active.
- *
- * @type {Boolean}
- * @default true
- */
- this.enabled = defaultValue(options.enabled, true);
+ this._enabled = false;
+ this.enabled = defaultValue(options.enabled, true); // set using setter
/**
* The 4x4 transformation matrix specifying an additional transform relative to the clipping planes
@@ -84,9 +116,18 @@ define([
*/
this.edgeWidth = defaultValue(options.edgeWidth, 0.0);
+ // If this ClippingPlaneCollection has an owner, only its owner should update or destroy it.
+ // This is because in a Cesium3DTileset multiple models may reference the tileset's ClippingPlaneCollection.
+ this._owner = undefined;
+
this._testIntersection = undefined;
this._unionClippingRegions = undefined;
- this.unionClippingRegions = defaultValue(options.unionClippingRegions, false);
+ this.unionClippingRegions = defaultValue(options.unionClippingRegions, false); // set using setter
+
+ this._uint8View = undefined;
+ this._float32View = undefined;
+
+ this._clippingPlanesTexture = undefined;
}
function unionIntersectFunction(value) {
@@ -126,34 +167,91 @@ define([
return this._unionClippingRegions;
},
set : function(value) {
- if (this._unionClippingRegions !== value) {
- this._unionClippingRegions = value;
- this._testIntersection = value ? unionIntersectFunction : defaultIntersectFunction;
+ if (this._unionClippingRegions === value) {
+ return;
}
+ this._unionClippingRegions = value;
+ this._testIntersection = value ? unionIntersectFunction : defaultIntersectFunction;
+ }
+ },
+
+ /**
+ * If true, clipping will be enabled.
+ *
+ * @memberof ClippingPlaneCollection.prototype
+ * @type {Boolean}
+ * @default false
+ */
+ enabled : {
+ get : function() {
+ return this._enabled;
+ },
+ set : function(value) {
+ if (this._enabled === value) {
+ return;
+ }
+ this._enabled = value;
+ }
+ },
+
+ /**
+ * Returns a texture containing packed, untransformed clipping planes.
+ *
+ * @memberof ClippingPlaneCollection.prototype
+ * @type {Texture}
+ * @readonly
+ * @private
+ */
+ texture : {
+ get : function() {
+ return this._clippingPlanesTexture;
+ }
+ },
+
+ /**
+ * A reference to the ClippingPlaneCollection's owner, if any.
+ *
+ * @memberof ClippingPlaneCollection.prototype
+ * @readonly
+ * @private
+ */
+ owner : {
+ get : function() {
+ return this._owner;
}
}
});
+ function setIndexDirty(collection, index) {
+ // If there's already a different _dirtyIndex set, more than one plane has changed since update.
+ // Entire texture must be reloaded
+ collection._manyDirtyPlanes = collection._manyDirtyPlanes || (collection._dirtyIndex !== -1 && collection._dirtyIndex !== index);
+ collection._dirtyIndex = index;
+ }
+
/**
- * Adds the specified {@link Plane} to the collection to be used to selectively disable rendering
+ * Adds the specified {@link ClippingPlane} to the collection to be used to selectively disable rendering
* on the outside of each plane. Use {@link ClippingPlaneCollection#unionClippingRegions} to modify
* how modify the clipping behavior of multiple planes.
*
- * @param {Plane} plane The plane to add to the collection.
- *
- * @exception {DeveloperError} The plane added exceeds the maximum number of supported clipping planes.
+ * @param {ClippingPlane} plane The ClippingPlane to add to the collection.
*
* @see ClippingPlaneCollection#unionClippingRegions
* @see ClippingPlaneCollection#remove
* @see ClippingPlaneCollection#removeAll
*/
ClippingPlaneCollection.prototype.add = function(plane) {
- //>>includeStart('debug', pragmas.debug);
- if (this.length >= ClippingPlaneCollection.MAX_CLIPPING_PLANES) {
- throw new DeveloperError('The maximum number of clipping planes supported is ' + ClippingPlaneCollection.MAX_CLIPPING_PLANES);
+ var newPlaneIndex = this._planes.length;
+ if (plane instanceof ClippingPlane) {
+ var that = this;
+ plane.onChangeCallback = function(index) {
+ setIndexDirty(that, index);
+ };
+ plane.index = newPlaneIndex;
+ } else {
+ this._containsUntrackablePlanes = true;
}
- //>>includeEnd('debug');
-
+ setIndexDirty(this, newPlaneIndex);
this._planes.push(plane);
};
@@ -165,7 +263,7 @@ define([
* in the collection.
*
* @param {Number} index The zero-based index of the plane.
- * @returns {Plane} The plane at the specified index.
+ * @returns {ClippingPlane} The ClippingPlane at the specified index.
*
* @see ClippingPlaneCollection#length
*/
@@ -189,39 +287,53 @@ define([
}
/**
- * Checks whether this collection contains the given plane.
+ * Checks whether this collection contains a ClippingPlane equal to the given ClippingPlane.
*
- * @param {Plane} [plane] The plane to check for.
- * @returns {Boolean} true if this collection contains the plane, false otherwise.
+ * @param {ClippingPlane} [clippingPlane] The ClippingPlane to check for.
+ * @returns {Boolean} true if this collection contains the ClippingPlane, false otherwise.
*
* @see ClippingPlaneCollection#get
*/
- ClippingPlaneCollection.prototype.contains = function(plane) {
- return indexOf(this._planes, plane) !== -1;
+ ClippingPlaneCollection.prototype.contains = function(clippingPlane) {
+ return indexOf(this._planes, clippingPlane) !== -1;
};
/**
- * Removes the first occurrence of the given plane from the collection.
+ * Removes the first occurrence of the given ClippingPlane from the collection.
*
- * @param {Plane} plane
+ * @param {ClippingPlane} clippingPlane
* @returns {Boolean} true
if the plane was removed; false
if the plane was not found in the collection.
*
* @see ClippingPlaneCollection#add
* @see ClippingPlaneCollection#contains
* @see ClippingPlaneCollection#removeAll
*/
- ClippingPlaneCollection.prototype.remove = function(plane) {
+ ClippingPlaneCollection.prototype.remove = function(clippingPlane) {
var planes = this._planes;
- var index = indexOf(planes, plane);
+ var index = indexOf(planes, clippingPlane);
if (index === -1) {
return false;
}
+ // Unlink this ClippingPlaneCollection from the ClippingPlane
+ if (clippingPlane instanceof ClippingPlane) {
+ clippingPlane.onChangeCallback = undefined;
+ clippingPlane.index = -1;
+ }
+
+ // Shift and update indices
var length = planes.length - 1;
for (var i = index; i < length; ++i) {
- planes[i] = planes[i + 1];
+ var planeToKeep = planes[i + 1];
+ planes[i] = planeToKeep;
+ if (planeToKeep instanceof ClippingPlane) {
+ planeToKeep.index = i;
+ }
}
+
+ // Indicate planes texture is dirty
+ this._manyDirtyPlanes = true;
planes.length = length;
return true;
@@ -234,53 +346,203 @@ define([
* @see ClippingPlaneCollection#remove
*/
ClippingPlaneCollection.prototype.removeAll = function() {
+ // Dereference this ClippingPlaneCollection from all ClippingPlanes
+ var planes = this._planes;
+ var planesCount = planes.length;
+ for (var i = 0; i < planesCount; ++i) {
+ var plane = planes[i];
+ if (plane instanceof ClippingPlane) {
+ plane.onChangeCallback = undefined;
+ plane.index = -1;
+ }
+ }
+ this._manyDirtyPlanes = true;
this._planes = [];
};
- var scratchPlane = new Plane(Cartesian3.UNIT_X, 0.0);
- var scratchMatrix = new Matrix4();
+ var octEncodeScratch = new Cartesian2();
+ var rightShift = 1.0 / 256.0;
/**
- * Applies the transformations to each plane and packs it into an array.
- * @private
- *
- * @param {Matrix4} viewMatrix The 4x4 matrix to transform the plane into eyespace.
- * @param {Cartesian4[]} [array] The array into which the planes will be packed.
- * @returns {Cartesian4[]} The array of packed planes.
+ * Encodes a normalized vector into 4 SNORM values in the range [0-255] following the 'oct' encoding.
+ * oct32 precision is higher than the default oct16, hence the additional 2 uint16 values.
*/
- ClippingPlaneCollection.prototype.transformAndPackPlanes = function(viewMatrix, array) {
- //>>includeStart('debug', pragmas.debug);
- Check.typeOf.object('viewMatrix', viewMatrix);
- //>>includeEnd('debug');
+ function oct32EncodeNormal(vector, result) {
+ AttributeCompression.octEncodeInRange(vector, 65535, octEncodeScratch);
+ result.x = octEncodeScratch.x * rightShift;
+ result.y = octEncodeScratch.x;
+ result.z = octEncodeScratch.y * rightShift;
+ result.w = octEncodeScratch.y;
+ return result;
+ }
- var planes = this._planes;
- var length = planes.length;
+ var distanceEncodeScratch = new Cartesian4();
+ var oct32EncodeScratch = new Cartesian4();
+ function packPlanesAsUint8(clippingPlaneCollection, startIndex, endIndex) {
+ var uint8View = clippingPlaneCollection._uint8View;
+ var planes = clippingPlaneCollection._planes;
+ var byteIndex = 0;
+ for (var i = startIndex; i < endIndex; ++i) {
+ var plane = planes[i];
- var index = 0;
- if (!defined(array)) {
- array = new Array(length);
- } else {
- index = array.length;
- array.length = length;
- }
+ var oct32Normal = oct32EncodeNormal(plane.normal, oct32EncodeScratch);
+ uint8View[byteIndex] = oct32Normal.x;
+ uint8View[byteIndex + 1] = oct32Normal.y;
+ uint8View[byteIndex + 2] = oct32Normal.z;
+ uint8View[byteIndex + 3] = oct32Normal.w;
- var i;
- for (i = index; i < length; ++i) {
- array[i] = new Cartesian4();
+ var encodedDistance = Cartesian4.packFloat(plane.distance, distanceEncodeScratch);
+ uint8View[byteIndex + 4] = encodedDistance.x;
+ uint8View[byteIndex + 5] = encodedDistance.y;
+ uint8View[byteIndex + 6] = encodedDistance.z;
+ uint8View[byteIndex + 7] = encodedDistance.w;
+
+ byteIndex += 8;
}
+ }
- var transform = Matrix4.multiply(viewMatrix, this.modelMatrix, scratchMatrix);
+ // Pack starting at the beginning of the buffer to allow partial update
+ function packPlanesAsFloats(clippingPlaneCollection, startIndex, endIndex) {
+ var float32View = clippingPlaneCollection._float32View;
+ var planes = clippingPlaneCollection._planes;
- for (i = 0; i < length; ++i) {
+ var floatIndex = 0;
+ for (var i = startIndex; i < endIndex; ++i) {
var plane = planes[i];
- var packedPlane = array[i];
+ var normal = plane.normal;
- Plane.transform(plane, transform, scratchPlane);
+ float32View[floatIndex] = normal.x;
+ float32View[floatIndex + 1] = normal.y;
+ float32View[floatIndex + 2] = normal.z;
+ float32View[floatIndex + 3] = plane.distance;
- Cartesian3.clone(scratchPlane.normal, packedPlane);
- packedPlane.w = scratchPlane.distance;
+ floatIndex += 4; // each plane is 4 floats
}
+ }
+
+ function computeTextureResolution(pixelsNeeded, result) {
+ var maxSize = ContextLimits.maximumTextureSize;
+ var width = Math.min(pixelsNeeded, maxSize);
+ var height = Math.ceil(pixelsNeeded / width);
+ result.x = width;
+ result.y = height;
+ return result;
+ }
- return array;
+ var textureResolutionScratch = new Cartesian2();
+ /**
+ * Called when {@link Viewer} or {@link CesiumWidget} render the scene to
+ * build the resources for clipping planes.
+ * + * Do not call this function directly. + *
+ */ + ClippingPlaneCollection.prototype.update = function(frameState) { + var clippingPlanesTexture = this._clippingPlanesTexture; + var context = frameState.context; + var useFloatTexture = ClippingPlaneCollection.useFloatTexture(context); + + // Compute texture requirements for current planes + // In RGBA FLOAT, A plane is 4 floats packed to a RGBA. + // In RGBA UNSIGNED_BYTE, A plane is a float in [0, 1) packed to RGBA and an Oct32 quantized normal, + // so 8 bits or 2 pixels in RGBA. + var pixelsNeeded = useFloatTexture ? this.length : this.length * 2; + var requiredResolution = computeTextureResolution(pixelsNeeded, textureResolutionScratch); + + if (defined(clippingPlanesTexture)) { + var currentPixelCount = clippingPlanesTexture.width * clippingPlanesTexture.height; + // Recreate the texture if it isn't big enough or is 4 times larger than it needs to be + if (currentPixelCount < pixelsNeeded || + pixelsNeeded < 0.25 * currentPixelCount) { + clippingPlanesTexture.destroy(); + clippingPlanesTexture = undefined; + } + } + + if (!defined(clippingPlanesTexture)) { + // Allocate twice as much space as needed to avoid frequent texture reallocation. + requiredResolution.x *= 2; + + var sampler = new Sampler({ + wrapS : TextureWrap.CLAMP_TO_EDGE, + wrapT : TextureWrap.CLAMP_TO_EDGE, + minificationFilter : TextureMinificationFilter.NEAREST, + magnificationFilter : TextureMagnificationFilter.NEAREST + }); + + if (useFloatTexture) { + clippingPlanesTexture = new Texture({ + context : context, + width : requiredResolution.x, + height : requiredResolution.y, + pixelFormat : PixelFormat.RGBA, + pixelDatatype : PixelDatatype.FLOAT, + sampler : sampler, + flipY : false + }); + this._float32View = new Float32Array(requiredResolution.x * requiredResolution.y * 4); + } else { + clippingPlanesTexture = new Texture({ + context : context, + width : requiredResolution.x, + height : requiredResolution.y, + pixelFormat : PixelFormat.RGBA, + pixelDatatype : PixelDatatype.UNSIGNED_BYTE, + sampler : sampler, + flipY : false + }); + this._uint8View = new Uint8Array(requiredResolution.x * requiredResolution.y * 4); + } + + this._clippingPlanesTexture = clippingPlanesTexture; + this._manyDirtyPlanes = true; + } + + // Use of Plane objects will be deprecated. + // But until then, we have no way of telling if they changed since last frame, so we have to do a full udpate. + var refreshFullTexture = this._manyDirtyPlanes || this._containsUntrackablePlanes; + var dirtyIndex = this._dirtyIndex; + + if (!refreshFullTexture && dirtyIndex === -1) { + return; + } + + if (!refreshFullTexture) { + // partial updates possible + var offsetY = Math.floor(dirtyIndex / clippingPlanesTexture.width); + var offsetX = Math.floor(dirtyIndex - offsetY * clippingPlanesTexture.width); + if (useFloatTexture) { + packPlanesAsFloats(this, dirtyIndex, dirtyIndex + 1); + clippingPlanesTexture.copyFrom({ + width : 1, + height : 1, + arrayBufferView : this._float32View + }, offsetX, offsetY); + } else { + packPlanesAsUint8(this, dirtyIndex, dirtyIndex + 1); + clippingPlanesTexture.copyFrom({ + width : 2, + height : 1, + arrayBufferView : this._uint8View + }, offsetX, offsetY); + } + } else if (useFloatTexture) { + packPlanesAsFloats(this, 0, this._planes.length); + clippingPlanesTexture.copyFrom({ + width : clippingPlanesTexture.width, + height : clippingPlanesTexture.height, + arrayBufferView : this._float32View + }); + } else { + packPlanesAsUint8(this, 0, this._planes.length); + clippingPlanesTexture.copyFrom({ + width : clippingPlanesTexture.width, + height : clippingPlanesTexture.height, + arrayBufferView : this._uint8View + }); + } + + this._manyDirtyPlanes = false; + this._dirtyIndex = -1; }; /** @@ -302,12 +564,17 @@ define([ planes.length = length; for (i = index; i < length; ++i) { - result._planes[i] = new Plane(Cartesian3.UNIT_X, 0.0); + result._planes[i] = new ClippingPlane(Cartesian3.UNIT_X, 0.0); } } for (i = 0; i < length; ++i) { - Plane.clone(this._planes[i], result._planes[i]); + var resultPlane = result._planes[i]; + resultPlane.index = i; + resultPlane.onChangeCallback = function(index) { + setIndexDirty(result, index); + }; + ClippingPlane.clone(this._planes[i], resultPlane); } result.enabled = this.enabled; @@ -319,18 +586,20 @@ define([ return result; }; + var scratchMatrix = new Matrix4(); + var scratchPlane = new Plane(Cartesian3.UNIT_X, 0.0); /** - * Determines the type intersection with the planes of this ClippingPlaneCollection instance and the specified {@link BoundingVolume}. + * Determines the type intersection with the planes of this ClippingPlaneCollection instance and the specified {@link TileBoundingVolume}. * @private * - * @param {Object} boundingVolume The volume to determine the intersection with the planes. + * @param {Object} tileBoundingVolume 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. */ - ClippingPlaneCollection.prototype.computeIntersectionWithBoundingVolume = function(boundingVolume, transform) { + ClippingPlaneCollection.prototype.computeIntersectionWithBoundingVolume = function(tileBoundingVolume, transform) { var planes = this._planes; var length = planes.length; @@ -351,9 +620,9 @@ define([ for (var i = 0; i < length; ++i) { var plane = planes[i]; - Plane.transform(plane, modelMatrix, scratchPlane); + Plane.transform(plane, modelMatrix, scratchPlane); // ClippingPlane can be used for Plane math - var value = boundingVolume.intersectPlane(scratchPlane); + var value = tileBoundingVolume.intersectPlane(scratchPlane); if (value === Intersect.INTERSECTING) { intersection = value; } else if (this._testIntersection(value)) { @@ -364,22 +633,103 @@ define([ return intersection; }; + /** + * Returns a Number encapsulating the state for this ClippingPlaneCollection. + * + * Clipping mode is encoded in the sign of the number, which is just the plane count. + * Used for checking if shader regeneration is necessary. + * + * @returns {Number} A Number that describes the ClippingPlaneCollection's state. + * @private + */ + ClippingPlaneCollection.prototype.clippingPlanesState = function() { + return this._unionClippingRegions ? this._planes.length : -this._planes.length; + }; + + /** + * Sets the owner for the input ClippingPlaneCollection if there wasn't another owner. + * Destroys the owner's previous ClippingPlaneCollection if setting is successful. + * + * @param {ClippingPlaneCollection} [clippingPlaneCollection] A ClippingPlaneCollection (or undefined) being attached to an object + * @param {Object} owner An Object that should receive the new ClippingPlaneCollection + * @param {String} key The Key for the Object to reference the ClippingPlaneCollection + * @private + */ + ClippingPlaneCollection.setOwnership = function(clippingPlaneCollection, owner, key) { + // Don't destroy the ClippingPlaneCollection if it is already owned by newOwner + if (clippingPlaneCollection === owner[key]) { + return; + } + // Destroy the existing ClippingPlaneCollection, if any + owner[key] = owner[key] && owner[key].destroy(); + if (defined(clippingPlaneCollection)) { + //>>includeStart('debug', pragmas.debug); + if (defined(clippingPlaneCollection._owner)) { + throw new DeveloperError('ClippingPlaneCollection should only be assigned to one object'); + } + //>>includeEnd('debug'); + clippingPlaneCollection._owner = owner; + owner[key] = clippingPlaneCollection; + } + }; + /** * Determines if rendering with clipping planes is supported. * - * @returns {Boolean}true
if ClippingPlaneCollections are supported; otherwise, returns false
+ * @returns {Boolean} true
if ClippingPlaneCollections are supported
+ * @deprecated
*/
ClippingPlaneCollection.isSupported = function() {
- return !FeatureDetection.isInternetExplorer();
+ return true;
+ };
+
+ /**
+ * Function for checking if the context will allow clipping planes with floating point textures.
+ *
+ * @param {Context} context The Context that will contain clipped objects and clipping textures.
+ * @returns {Boolean} true
if floating point textures can be used for clipping planes.
+ * @private
+ */
+ ClippingPlaneCollection.useFloatTexture = function(context) {
+ return context.floatingPointTexture;
+ };
+
+ /**
+ * Returns true if this object was destroyed; otherwise, false.
+ * isDestroyed
will result in a {@link DeveloperError} exception.
+ *
+ * @returns {Boolean} true
if this object was destroyed; otherwise, false
.
+ *
+ * @see ClippingPlaneCollection#destroy
+ */
+ ClippingPlaneCollection.prototype.isDestroyed = function() {
+ return false;
};
/**
- * The maximum number of supported clipping planes.
+ * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
+ * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
+ * isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
+ * assign the return value (undefined
) to the object as done in the example.
+ *
+ * @returns {undefined}
*
- * @type {number}
- * @constant
+ * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
+ *
+ *
+ * @example
+ * clippingPlanes = clippingPlanes && clippingPlanes .destroy();
+ *
+ * @see ClippingPlaneCollection#isDestroyed
*/
- ClippingPlaneCollection.MAX_CLIPPING_PLANES = 6;
+ ClippingPlaneCollection.prototype.destroy = function() {
+ this._clippingPlanesTexture = this._clippingPlanesTexture && this._clippingPlanesTexture.destroy();
+ return destroyObject(this);
+ };
return ClippingPlaneCollection;
});
diff --git a/Source/DataSources/ModelGraphics.js b/Source/DataSources/ModelGraphics.js
index 5dedd97c6f94..1b6c77f82b32 100644
--- a/Source/DataSources/ModelGraphics.js
+++ b/Source/DataSources/ModelGraphics.js
@@ -55,7 +55,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 the {@link ClippingPlaneCollection} used to selectively disable rendering the model. Clipping planes are not currently supported in Internet Explorer.
+ * @param {Property} [options.clippingPlanes] A property specifying the {@link ClippingPlaneCollection} used to selectively disable rendering the model.
*
* @see {@link https://cesiumjs.org/tutorials/3D-Models-Tutorial/|3D Models Tutorial}
* @demo {@link https://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=3D%20Models.html|Cesium Sandcastle 3D Models Demo}
diff --git a/Source/Renderer/AutomaticUniforms.js b/Source/Renderer/AutomaticUniforms.js
index 3e218afd105e..194af4c1a20e 100644
--- a/Source/Renderer/AutomaticUniforms.js
+++ b/Source/Renderer/AutomaticUniforms.js
@@ -912,6 +912,20 @@ define([
}
}),
+ /**
+ * An automatic GLSL uniform that indicates if the current camera is orthographic in 3D.
+ *
+ * @alias czm_orthographicIn3D
+ * @see UniformState#orthographicIn3D
+ */
+ czm_orthographicIn3D : new AutomaticUniform({
+ size : 1,
+ datatype : WebGLConstants.FLOAT,
+ getValue : function(uniformState) {
+ return uniformState.orthographicIn3D ? 1 : 0;
+ }
+ }),
+
/**
* An automatic GLSL uniform representing a 3x3 normal transformation matrix that
* transforms normal vectors in model coordinates to eye coordinates.
diff --git a/Source/Renderer/UniformState.js b/Source/Renderer/UniformState.js
index 89c87d7a88b8..00eddf4756f3 100644
--- a/Source/Renderer/UniformState.js
+++ b/Source/Renderer/UniformState.js
@@ -861,6 +861,18 @@ define([
get : function() {
return this._invertClassificationColor;
}
+ },
+
+ /**
+ * Whether or not the current projection is orthographic in 3D.
+ *
+ * @memberOf UniformState.prototype
+ * @type {Boolean}
+ */
+ orthographicIn3D : {
+ get : function() {
+ return this._orthographicIn3D;
+ }
}
});
diff --git a/Source/Scene/BatchTable.js b/Source/Scene/BatchTable.js
index e32799823b04..bbe799c529c9 100644
--- a/Source/Scene/BatchTable.js
+++ b/Source/Scene/BatchTable.js
@@ -16,7 +16,8 @@ define([
'../Renderer/Sampler',
'../Renderer/Texture',
'../Renderer/TextureMagnificationFilter',
- '../Renderer/TextureMinificationFilter'
+ '../Renderer/TextureMinificationFilter',
+ './getUnpackFloatFunction'
], function(
Cartesian2,
Cartesian3,
@@ -35,7 +36,8 @@ define([
Sampler,
Texture,
TextureMagnificationFilter,
- TextureMinificationFilter) {
+ TextureMinificationFilter,
+ getUnpackFloatFunction) {
'use strict';
/**
@@ -226,48 +228,18 @@ define([
var scratchPackedFloatCartesian4 = new Cartesian4();
- var SHIFT_LEFT_8 = 256.0;
- var SHIFT_LEFT_16 = 65536.0;
- var SHIFT_LEFT_24 = 16777216.0;
-
- var SHIFT_RIGHT_8 = 1.0 / SHIFT_LEFT_8;
- var SHIFT_RIGHT_16 = 1.0 / SHIFT_LEFT_16;
- var SHIFT_RIGHT_24 = 1.0 / SHIFT_LEFT_24;
-
- var BIAS = 38.0;
-
- function unpackFloat(value) {
- var temp = value.w / 2.0;
- var exponent = Math.floor(temp);
- var sign = (temp - exponent) * 2.0;
- exponent = exponent - BIAS;
-
- sign = sign * 2.0 - 1.0;
- sign = -sign;
-
- if (exponent >= BIAS) {
- return sign < 0.0 ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY;
- }
-
- var unpacked = sign * value.x * SHIFT_RIGHT_8;
- unpacked += sign * value.y * SHIFT_RIGHT_16;
- unpacked += sign * value.z * SHIFT_RIGHT_24;
-
- return unpacked * Math.pow(10.0, exponent);
- }
-
function getPackedFloat(array, index, result) {
var packed = Cartesian4.unpack(array, index, scratchPackedFloatCartesian4);
- var x = unpackFloat(packed);
+ var x = Cartesian4.unpackFloat(packed);
packed = Cartesian4.unpack(array, index + 4, scratchPackedFloatCartesian4);
- var y = unpackFloat(packed);
+ var y = Cartesian4.unpackFloat(packed);
packed = Cartesian4.unpack(array, index + 8, scratchPackedFloatCartesian4);
- var z = unpackFloat(packed);
+ var z = Cartesian4.unpackFloat(packed);
packed = Cartesian4.unpack(array, index + 12, scratchPackedFloatCartesian4);
- var w = unpackFloat(packed);
+ var w = Cartesian4.unpackFloat(packed);
return Cartesian4.fromElements(x, y, z, w, result);
}
@@ -275,50 +247,18 @@ define([
if (!FeatureDetection.supportsTypedArrays()) {
return;
}
- var scratchFloatArray = new Float32Array(1);
-
- function packFloat(value, result) {
- scratchFloatArray[0] = value;
- value = scratchFloatArray[0];
-
- if (value === 0.0) {
- return Cartesian4.clone(Cartesian4.ZERO, result);
- }
-
- var sign = value < 0.0 ? 1.0 : 0.0;
- var exponent;
-
- if (!isFinite(value)) {
- value = 0.1;
- exponent = BIAS;
- } else {
- value = Math.abs(value);
- exponent = Math.floor(CesiumMath.logBase(value, 10)) + 1.0;
- value = value / Math.pow(10.0, exponent);
- }
-
- var temp = value * SHIFT_LEFT_8;
- result.x = Math.floor(temp);
- temp = (temp - result.x) * SHIFT_LEFT_8;
- result.y = Math.floor(temp);
- temp = (temp - result.y) * SHIFT_LEFT_8;
- result.z = Math.floor(temp);
- result.w = (exponent + BIAS) * 2.0 + sign;
-
- return result;
- }
function setPackedAttribute(value, array, index) {
- var packed = packFloat(value.x, scratchPackedFloatCartesian4);
+ var packed = Cartesian4.packFloat(value.x, scratchPackedFloatCartesian4);
Cartesian4.pack(packed, array, index);
- packed = packFloat(value.y, packed);
+ packed = Cartesian4.packFloat(value.y, packed);
Cartesian4.pack(packed, array, index + 4);
- packed = packFloat(value.z, packed);
+ packed = Cartesian4.packFloat(value.z, packed);
Cartesian4.pack(packed, array, index + 8);
- packed = packFloat(value.w, packed);
+ packed = Cartesian4.packFloat(value.w, packed);
Cartesian4.pack(packed, array, index + 12);
}
@@ -527,20 +467,7 @@ define([
return '';
}
- return 'float unpackFloat(vec4 value) \n' +
- '{ \n' +
- ' value *= 255.0; \n' +
- ' float temp = value.w / 2.0; \n' +
- ' float exponent = floor(temp); \n' +
- ' float sign = (temp - exponent) * 2.0; \n' +
- ' exponent = exponent - float(' + BIAS + '); \n' +
- ' sign = sign * 2.0 - 1.0; \n' +
- ' sign = -sign; \n' +
- ' float unpacked = sign * value.x * float(' + SHIFT_RIGHT_8 + '); \n' +
- ' unpacked += sign * value.y * float(' + SHIFT_RIGHT_16 + '); \n' +
- ' unpacked += sign * value.z * float(' + SHIFT_RIGHT_24 + '); \n' +
- ' return unpacked * pow(10.0, exponent); \n' +
- '} \n';
+ return getUnpackFloatFunction('unpackFloat');
}
function getComponentType(componentsPerAttribute) {
diff --git a/Source/Scene/Batched3DModel3DTileContent.js b/Source/Scene/Batched3DModel3DTileContent.js
index 5ecb891e1a73..2f6f2a350dd0 100644
--- a/Source/Scene/Batched3DModel3DTileContent.js
+++ b/Source/Scene/Batched3DModel3DTileContent.js
@@ -1,5 +1,4 @@
define([
- '../Core/ClippingPlaneCollection',
'../Core/Color',
'../Core/defaultValue',
'../Core/defined',
@@ -20,7 +19,6 @@ define([
'./Model',
'./ModelUtility'
], function(
- ClippingPlaneCollection,
Color,
defaultValue,
defined,
@@ -69,6 +67,10 @@ define([
this._batchTable = undefined;
this._features = undefined;
+ // Populate from gltf when available
+ this._batchIdAttributeName = undefined;
+ this._diffuseAttributeOrUniformName = {};
+
/**
* @inheritdoc Cesium3DTileContent#featurePropertiesDirty
*/
@@ -206,11 +208,15 @@ define([
function getVertexShaderCallback(content) {
return function(vs, programId) {
var batchTable = content._batchTable;
- var gltf = content._model.gltf;
var handleTranslucent = !defined(content._tileset.classificationType);
- var batchIdAttributeName = getBatchIdAttributeName(gltf);
- var diffuseAttributeOrUniformName = ModelUtility.getDiffuseAttributeOrUniform(gltf, programId);
- var callback = batchTable.getVertexShaderCallback(handleTranslucent, batchIdAttributeName, diffuseAttributeOrUniformName);
+
+ var gltf = content._model.gltf;
+ if (defined(gltf)) {
+ content._batchIdAttributeName = getBatchIdAttributeName(gltf);
+ content._diffuseAttributeOrUniformName[programId] = ModelUtility.getDiffuseAttributeOrUniform(gltf, programId);
+ }
+
+ var callback = batchTable.getVertexShaderCallback(handleTranslucent, content._batchIdAttributeName, content._diffuseAttributeOrUniformName[programId]);
return defined(callback) ? callback(vs) : vs;
};
}
@@ -218,9 +224,13 @@ define([
function getPickVertexShaderCallback(content) {
return function(vs) {
var batchTable = content._batchTable;
+
var gltf = content._model.gltf;
- var batchIdAttributeName = getBatchIdAttributeName(gltf);
- var callback = batchTable.getPickVertexShaderCallback(batchIdAttributeName);
+ if (defined(gltf)) {
+ content._batchIdAttributeName = getBatchIdAttributeName(gltf);
+ }
+
+ var callback = batchTable.getPickVertexShaderCallback(content._batchIdAttributeName);
return defined(callback) ? callback(vs) : vs;
};
}
@@ -228,10 +238,13 @@ define([
function getFragmentShaderCallback(content) {
return function(fs, programId) {
var batchTable = content._batchTable;
- var gltf = content._model.gltf;
var handleTranslucent = !defined(content._tileset.classificationType);
- var diffuseAttributeOrUniformName = ModelUtility.getDiffuseAttributeOrUniform(gltf, programId);
- var callback = batchTable.getFragmentShaderCallback(handleTranslucent, diffuseAttributeOrUniformName);
+
+ var gltf = content._model.gltf;
+ if (defined(gltf)) {
+ content._diffuseAttributeOrUniformName[programId] = ModelUtility.getDiffuseAttributeOrUniform(gltf, programId);
+ }
+ var callback = batchTable.getFragmentShaderCallback(handleTranslucent, content._diffuseAttributeOrUniformName[programId]);
return defined(callback) ? callback(fs) : fs;
};
}
@@ -379,15 +392,6 @@ define([
};
if (!defined(tileset.classificationType)) {
- var clippingPlanes;
- if (defined(tileset.clippingPlanes)) {
- clippingPlanes = tileset.clippingPlanes.clone();
- } else {
- clippingPlanes = new ClippingPlaneCollection({
- enabled : false
- });
- }
-
// PERFORMANCE_IDEA: patch the shader on demand, e.g., the first time show/color changes.
// The pick shader still needs to be patched.
content._model = new Model({
@@ -409,8 +413,7 @@ 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 : clippingPlanes
+ pickObject : pickObject
});
} else {
// This transcodes glTF to an internal representation for geometry so we can take advantage of the re-batching of vector data.
@@ -503,12 +506,11 @@ define([
// Update clipping planes
var tilesetClippingPlanes = this._tileset.clippingPlanes;
- var modelClippingPlanes = this._model.clippingPlanes;
- if (defined(tilesetClippingPlanes)) {
- tilesetClippingPlanes.clone(modelClippingPlanes);
- modelClippingPlanes.enabled = tilesetClippingPlanes.enabled && this._tile._isClipped;
- } else if (defined(modelClippingPlanes) && modelClippingPlanes.enabled) {
- modelClippingPlanes.enabled = false;
+ if (this._tile.clippingPlanesDirty && defined(tilesetClippingPlanes)) {
+ // Dereference the clipping planes from the model if they are irrelevant.
+ // Link/Dereference directly to avoid ownership checks.
+ // This will also trigger synchronous shader regeneration to remove or add the clipping plane and color blending code.
+ this._model._clippingPlanes = (tilesetClippingPlanes.enabled && this._tile._isClipped) ? tilesetClippingPlanes : undefined;
}
this._model.update(frameState);
diff --git a/Source/Scene/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js
index 5ffb0114e887..e386a997586b 100644
--- a/Source/Scene/Cesium3DTile.js
+++ b/Source/Scene/Cesium3DTile.js
@@ -310,6 +310,16 @@ define([
*/
this._optimChildrenWithinParent = Cesium3DTileOptimizationHint.NOT_COMPUTED;
+ /**
+ * Tracks if the tile's relationship with a ClippingPlaneCollection has changed with regards
+ * to the ClippingPlaneCollection's state.
+ *
+ * @type {Boolean}
+ *
+ * @private
+ */
+ this.clippingPlanesDirty = false;
+
// Members that are updated every frame for tree traversal and rendering optimizations:
this._distanceToCamera = 0;
this._visibilityPlaneMask = 0;
@@ -329,6 +339,7 @@ define([
this._ancestorWithContent = undefined;
this._ancestorWithLoadedContent = undefined;
this._isClipped = true;
+ this._clippingPlanesState = 0; // encapsulates (_isClipped, clippingPlanes.enabled) and number/function
this._debugBoundingVolume = undefined;
this._debugContentBoundingVolume = undefined;
@@ -1048,6 +1059,23 @@ define([
content.update(tileset, frameState);
}
+ function updateClippingPlanes(tile, tileset) {
+ // Compute and compare ClippingPlanes state:
+ // - enabled-ness - are clipping planes enabled? is this tile clipped?
+ // - clipping plane count
+ // - clipping function (union v. intersection)
+ var clippingPlanes = tileset.clippingPlanes;
+ var currentClippingPlanesState = 0;
+ if (defined(clippingPlanes) && tile._isClipped && clippingPlanes.enabled) {
+ currentClippingPlanesState = clippingPlanes.clippingPlanesState();
+ }
+ // If clippingPlaneState for tile changed, mark clippingPlanesDirty so content can update
+ if (currentClippingPlanesState !== tile._clippingPlanesState) {
+ tile._clippingPlanesState = currentClippingPlanesState;
+ tile.clippingPlanesDirty = true;
+ }
+ }
+
/**
* Get the draw commands needed to render this tile.
*
@@ -1055,9 +1083,12 @@ define([
*/
Cesium3DTile.prototype.update = function(tileset, frameState) {
var initCommandLength = frameState.commandList.length;
+ updateClippingPlanes(this, tileset);
applyDebugSettings(this, tileset, frameState);
updateContent(this, tileset, frameState);
this._commandsLength = frameState.commandList.length - initCommandLength;
+
+ this.clippingPlanesDirty = false; // reset after content update
};
var scratchCommandList = [];
diff --git a/Source/Scene/Cesium3DTileBatchTable.js b/Source/Scene/Cesium3DTileBatchTable.js
index 345e5125cdc0..42c32407f0c9 100644
--- a/Source/Scene/Cesium3DTileBatchTable.js
+++ b/Source/Scene/Cesium3DTileBatchTable.js
@@ -1331,10 +1331,12 @@ define([
for (var i = commandStart; i < commandEnd; ++i) {
var command = commandList[i];
var derivedCommands = command.derivedCommands.tileset;
- if (!defined(derivedCommands)) {
+ // Command may be marked dirty from Model shader recompilation for clipping planes
+ if (!defined(derivedCommands) || command.dirty) {
derivedCommands = {};
command.derivedCommands.tileset = derivedCommands;
derivedCommands.originalCommand = deriveCommand(command);
+ command.dirty = false;
}
updateDerivedCommand(derivedCommands.originalCommand, command);
diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js
index 699fc5d41595..ddfbdcab4d21 100644
--- a/Source/Scene/Cesium3DTileset.js
+++ b/Source/Scene/Cesium3DTileset.js
@@ -3,6 +3,7 @@ define([
'../Core/Cartesian3',
'../Core/Cartographic',
'../Core/Check',
+ '../Core/ClippingPlaneCollection',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
@@ -46,6 +47,7 @@ define([
Cartesian3,
Cartographic,
Check,
+ ClippingPlaneCollection,
defaultValue,
defined,
defineProperties,
@@ -111,7 +113,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 ClippingPlaneCollection} used to selectively disable rendering the tileset. Clipping planes are not currently supported in Internet Explorer.
+ * @param {ClippingPlaneCollection} [options.clippingPlanes] The {@link ClippingPlaneCollection} used to selectively disable rendering the tileset.
* @param {ClassificationType} [options.classificationType] Determines whether terrain, 3D Tiles or both will be classified by this tileset. See {@link Cesium3DTileset#classificationType} for details about restrictions and limitations.
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid determining the size and shape of the globe.
* @param {Boolean} [options.debugFreezeFrame=false] For debugging only. Determines if only the tiles from last frame should be used for rendering.
@@ -547,11 +549,7 @@ define([
*/
this.loadSiblings = defaultValue(options.loadSiblings, false);
- /**
- * The {@link ClippingPlaneCollection} used to selectively disable rendering the tileset. Clipping planes are not currently supported in Internet Explorer.
- *
- * @type {ClippingPlaneCollection}
- */
+ this._clippingPlanes = undefined;
this.clippingPlanes = options.clippingPlanes;
/**
@@ -814,6 +812,19 @@ define([
return this._asset;
}
},
+ /**
+ * The {@link ClippingPlaneCollection} used to selectively disable rendering the tileset.
+ *
+ * @type {ClippingPlaneCollection}
+ */
+ clippingPlanes : {
+ get : function() {
+ return this._clippingPlanes;
+ },
+ set : function(value) {
+ ClippingPlaneCollection.setOwnership(value, this, '_clippingPlanes');
+ }
+ },
/**
* Gets the tileset's properties dictionary object, which contains metadata about per-feature properties.
@@ -1846,6 +1857,12 @@ define([
this._loadTimestamp = JulianDate.clone(frameState.time);
}
+ // Update clipping planes
+ var clippingPlanes = this._clippingPlanes;
+ if (defined(clippingPlanes) && clippingPlanes.enabled) {
+ clippingPlanes.update(frameState);
+ }
+
this._timeSinceLoad = Math.max(JulianDate.secondsDifference(frameState.time, this._loadTimestamp) * 1000, 0.0);
this._skipLevelOfDetail = this.skipLevelOfDetail && !defined(this._classificationType) && !this._disableSkipLevelOfDetail;
@@ -1930,6 +1947,9 @@ define([
// Destroy debug labels
this._tileDebugLabels = this._tileDebugLabels && this._tileDebugLabels.destroy();
+ // Destroy clipping plane collection
+ this._clippingPlanes = this._clippingPlanes && this._clippingPlanes.destroy();
+
// Traverse the tree and destroy all tiles
if (defined(this._root)) {
var stack = scratchStack;
diff --git a/Source/Scene/Globe.js b/Source/Scene/Globe.js
index 343295c50c1d..5d391dbebf31 100644
--- a/Source/Scene/Globe.js
+++ b/Source/Scene/Globe.js
@@ -266,7 +266,7 @@ define([
}
},
/**
- * A property specifying a {@link ClippingPlaneCollection} used to selectively disable rendering on the outside of each plane. Clipping planes are not currently supported in Internet Explorer.
+ * A property specifying a {@link ClippingPlaneCollection} used to selectively disable rendering on the outside of each plane.
*
* @memberof Globe.prototype
* @type {ClippingPlaneCollection}
diff --git a/Source/Scene/GlobeSurfaceShaderSet.js b/Source/Scene/GlobeSurfaceShaderSet.js
index c46fe650b6bf..85c775b1c00c 100644
--- a/Source/Scene/GlobeSurfaceShaderSet.js
+++ b/Source/Scene/GlobeSurfaceShaderSet.js
@@ -1,22 +1,27 @@
define([
+ '../Core/ClippingPlaneCollection',
'../Core/defined',
'../Core/destroyObject',
'../Core/TerrainQuantization',
'../Renderer/ShaderProgram',
- '../Scene/SceneMode'
+ '../Scene/SceneMode',
+ './getClippingFunction'
], function(
+ ClippingPlaneCollection,
defined,
destroyObject,
TerrainQuantization,
ShaderProgram,
- SceneMode) {
+ SceneMode,
+ getClippingFunction) {
'use strict';
- function GlobeSurfaceShader(numberOfDayTextures, flags, material, shaderProgram) {
+ function GlobeSurfaceShader(numberOfDayTextures, flags, material, shaderProgram, clippingShaderState) {
this.numberOfDayTextures = numberOfDayTextures;
this.flags = flags;
this.material = material;
this.shaderProgram = shaderProgram;
+ this.clippingShaderState = clippingShaderState;
}
/**
@@ -64,7 +69,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, unionClippingRegions) {
+ GlobeSurfaceShaderSet.prototype.getShaderProgram = function(frameState, surfaceTile, numberOfDayTextures, applyBrightness, applyContrast, applyHue, applySaturation, applyGamma, applyAlpha, applySplit, showReflectiveOcean, showOceanWaves, enableLighting, hasVertexNormals, useWebMercatorProjection, enableFog, enableClippingPlanes, clippingPlanes) {
var quantization = 0;
var quantizationDefine = '';
@@ -93,27 +98,36 @@ define([
(applySplit << 15) |
(enableClippingPlanes << 16);
+ var currentClippingShaderState = 0;
+ if (defined(clippingPlanes)) {
+ currentClippingShaderState = enableClippingPlanes ? clippingPlanes.clippingPlanesState() : 0;
+ }
var surfaceShader = surfaceTile.surfaceShader;
if (defined(surfaceShader) &&
surfaceShader.numberOfDayTextures === numberOfDayTextures &&
surfaceShader.flags === flags &&
- surfaceShader.material === this.material) {
+ surfaceShader.material === this.material &&
+ surfaceShader.clippingShaderState === currentClippingShaderState) {
return surfaceShader.shaderProgram;
}
- // New tile, or tile changed number of textures or flags.
+ // New tile, or tile changed number of textures, flags, or clipping planes
var shadersByFlags = this._shadersByTexturesFlags[numberOfDayTextures];
if (!defined(shadersByFlags)) {
shadersByFlags = this._shadersByTexturesFlags[numberOfDayTextures] = [];
}
surfaceShader = shadersByFlags[flags];
- if (!defined(surfaceShader) || surfaceShader.material !== this.material) {
+ if (!defined(surfaceShader) || surfaceShader.material !== this.material || surfaceShader.clippingShaderState !== currentClippingShaderState) {
// Cache miss - we've never seen this combination of numberOfDayTextures and flags before.
var vs = this.baseVertexShaderSource.clone();
var fs = this.baseFragmentShaderSource.clone();
+ if (currentClippingShaderState !== 0) {
+ fs.sources.unshift(getClippingFunction(clippingPlanes)); // Need to go before GlobeFS
+ }
+
vs.defines.push(quantizationDefine);
fs.defines.push('TEXTURE_UNITS ' + numberOfDayTextures);
@@ -167,10 +181,6 @@ define([
if (enableClippingPlanes) {
fs.defines.push('ENABLE_CLIPPING_PLANES');
-
- if (unionClippingRegions) {
- fs.defines.push('UNION_CLIPPING_REGIONS');
- }
}
var computeDayColor = '\
@@ -212,7 +222,7 @@ define([
attributeLocations : terrainEncoding.getAttributeLocations()
});
- surfaceShader = shadersByFlags[flags] = new GlobeSurfaceShader(numberOfDayTextures, flags, this.material, shader);
+ surfaceShader = shadersByFlags[flags] = new GlobeSurfaceShader(numberOfDayTextures, flags, this.material, shader, currentClippingShaderState);
}
surfaceTile.surfaceShader = surfaceShader;
diff --git a/Source/Scene/GlobeSurfaceTileProvider.js b/Source/Scene/GlobeSurfaceTileProvider.js
index ddf1fc10d641..9d9ea96e7e7b 100644
--- a/Source/Scene/GlobeSurfaceTileProvider.js
+++ b/Source/Scene/GlobeSurfaceTileProvider.js
@@ -172,7 +172,7 @@ define([
* @type {ClippingPlaneCollection}
* @private
*/
- this.clippingPlanes = undefined;
+ this._clippingPlanes = undefined;
}
defineProperties(GlobeSurfaceTileProvider.prototype, {
@@ -302,6 +302,21 @@ define([
this._quadtree.invalidateAllTiles();
}
}
+ },
+ /**
+ * The {@link ClippingPlaneCollection} used to selectively disable rendering the tileset.
+ *
+ * @type {ClippingPlaneCollection}
+ *
+ * @private
+ */
+ clippingPlanes : {
+ get : function() {
+ return this._clippingPlanes;
+ },
+ set : function(value) {
+ ClippingPlaneCollection.setOwnership(value, this, '_clippingPlanes');
+ }
}
});
@@ -396,7 +411,11 @@ define([
tiles.length = 0;
}
}
-
+ // update clipping planes
+ var clippingPlanes = this._clippingPlanes;
+ if (defined(clippingPlanes) && clippingPlanes.enabled) {
+ clippingPlanes.update(frameState);
+ }
this._usedDrawCommands = 0;
};
@@ -548,7 +567,7 @@ define([
}
}
- var clippingPlanes = this.clippingPlanes;
+ var clippingPlanes = this._clippingPlanes;
if (defined(clippingPlanes) && clippingPlanes.enabled) {
var planeIntersection = clippingPlanes.computeIntersectionWithBoundingVolume(boundingVolume);
tile.isClipped = (planeIntersection !== Intersect.INSIDE);
@@ -666,6 +685,8 @@ define([
*/
GlobeSurfaceTileProvider.prototype.destroy = function() {
this._tileProvider = this._tileProvider && this._tileProvider.destroy();
+ this._clippingPlanes = this._clippingPlanes && this._clippingPlanes.destroy();
+
return destroyObject(this);
};
@@ -826,7 +847,8 @@ define([
}
};
- function createTileUniformMap(frameState) {
+ var scratchClippingPlaneMatrix = new Matrix4();
+ function createTileUniformMap(frameState, globeSurfaceTileProvider) {
var uniformMap = {
u_initialColor : function() {
return this.properties.initialColor;
@@ -914,11 +936,17 @@ define([
u_dayTextureSplit : function() {
return this.properties.dayTextureSplit;
},
- u_clippingPlanesLength : function() {
- return this.properties.clippingPlanes.length;
- },
u_clippingPlanes : function() {
- return this.properties.clippingPlanes;
+ var clippingPlanes = globeSurfaceTileProvider._clippingPlanes;
+ if (defined(clippingPlanes) && defined(clippingPlanes.texture)) {
+ // Check in case clippingPlanes hasn't been updated yet.
+ return clippingPlanes.texture;
+ }
+ return frameState.context.defaultTexture;
+ },
+ u_clippingPlanesMatrix : function() {
+ var clippingPlanes = globeSurfaceTileProvider._clippingPlanes;
+ return defined(clippingPlanes) ? Matrix4.multiply(frameState.context.uniformState.view, clippingPlanes.modelMatrix, scratchClippingPlaneMatrix) : Matrix4.IDENTITY;
},
u_clippingPlanesEdgeStyle : function() {
var style = this.properties.clippingPlanesEdgeColor;
@@ -963,7 +991,6 @@ define([
minMaxHeight : new Cartesian2(),
scaleAndBias : new Matrix4(),
- clippingPlanes : [],
clippingPlanesEdgeColor : Color.clone(Color.WHITE),
clippingPlanesEdgeWidth : 0.0
}
@@ -1211,7 +1238,7 @@ define([
command.boundingVolume = new BoundingSphere();
command.orientedBoundingBox = undefined;
- uniformMap = createTileUniformMap(frameState);
+ uniformMap = createTileUniformMap(frameState, tileProvider);
tileProvider._drawCommands.push(command);
tileProvider._uniformMaps.push(uniformMap);
@@ -1342,37 +1369,18 @@ define([
Matrix4.clone(encoding.matrix, uniformMapProperties.scaleAndBias);
// update clipping planes
- var clippingPlanes = tileProvider.clippingPlanes;
- var length = 0;
-
- if (defined(clippingPlanes) && tile.isClipped) {
- length = clippingPlanes.length;
- }
-
- var packedPlanes = uniformMapProperties.clippingPlanes;
- var packedLength = packedPlanes.length;
- if (packedLength !== length) {
- packedPlanes.length = length;
-
- for (var i = packedLength; i < length; ++i) {
- packedPlanes[i] = new Cartesian4();
- }
- }
-
- if (defined(clippingPlanes) && clippingPlanes.enabled && tile.isClipped) {
- clippingPlanes.transformAndPackPlanes(context.uniformState.view, packedPlanes);
+ var clippingPlanes = tileProvider._clippingPlanes;
+ var clippingPlanesEnabled = defined(clippingPlanes) && clippingPlanes.enabled && tile.isClipped;
+ if (clippingPlanesEnabled) {
uniformMapProperties.clippingPlanesEdgeColor = Color.clone(clippingPlanes.edgeColor, uniformMapProperties.clippingPlanesEdgeColor);
uniformMapProperties.clippingPlanesEdgeWidth = clippingPlanes.edgeWidth;
}
- var clippingPlanesEnabled = defined(clippingPlanes) && clippingPlanes.enabled && (uniformMapProperties.clippingPlanes.length > 0) && ClippingPlaneCollection.isSupported();
- var unionClippingRegions = clippingPlanesEnabled ? clippingPlanes.unionClippingRegions : false;
-
if (defined(tileProvider.uniformMap)) {
uniformMap = combine(uniformMap, tileProvider.uniformMap);
}
- command.shaderProgram = tileProvider._surfaceShaderSet.getShaderProgram(frameState, surfaceTile, numberOfDayTextures, applyBrightness, applyContrast, applyHue, applySaturation, applyGamma, applyAlpha, applySplit, showReflectiveOcean, showOceanWaves, tileProvider.enableLighting, hasVertexNormals, useWebMercatorProjection, applyFog, clippingPlanesEnabled, unionClippingRegions);
+ command.shaderProgram = tileProvider._surfaceShaderSet.getShaderProgram(frameState, surfaceTile, numberOfDayTextures, applyBrightness, applyContrast, applyHue, applySaturation, applyGamma, applyAlpha, applySplit, showReflectiveOcean, showOceanWaves, tileProvider.enableLighting, hasVertexNormals, useWebMercatorProjection, applyFog, clippingPlanesEnabled, clippingPlanes);
command.castShadows = castShadows;
command.receiveShadows = receiveShadows;
command.renderState = renderState;
diff --git a/Source/Scene/Instanced3DModel3DTileContent.js b/Source/Scene/Instanced3DModel3DTileContent.js
index 1373144e21bb..01f65513809f 100644
--- a/Source/Scene/Instanced3DModel3DTileContent.js
+++ b/Source/Scene/Instanced3DModel3DTileContent.js
@@ -1,7 +1,6 @@
define([
'../Core/AttributeCompression',
'../Core/Cartesian3',
- '../Core/ClippingPlaneCollection',
'../Core/Color',
'../Core/ComponentDatatype',
'../Core/defaultValue',
@@ -29,7 +28,6 @@ define([
], function(
AttributeCompression,
Cartesian3,
- ClippingPlaneCollection,
Color,
ComponentDatatype,
defaultValue,
@@ -511,24 +509,21 @@ define([
this._modelInstanceCollection.modelMatrix = this._tile.computedTransform;
this._modelInstanceCollection.shadows = this._tileset.shadows;
this._modelInstanceCollection.debugWireframe = this._tileset.debugWireframe;
- this._modelInstanceCollection.update(frameState);
- // Update clipping planes
- var tilesetClippingPlanes = this._tileset.clippingPlanes;
var model = this._modelInstanceCollection._model;
- var modelClippingPlanes = model.clippingPlanes;
- if (defined(tilesetClippingPlanes)) {
- if (!defined(modelClippingPlanes)) {
- model.clippingPlanes = new ClippingPlaneCollection();
- modelClippingPlanes = model.clippingPlanes;
- }
- tilesetClippingPlanes.clone(modelClippingPlanes);
- modelClippingPlanes.enabled = tilesetClippingPlanes.enabled && this._tile._isClipped;
- } else if (defined(modelClippingPlanes) && modelClippingPlanes.enabled) {
- modelClippingPlanes.enabled = false;
+ if (defined(model)) {
+ // Update for clipping planes
+ var tilesetClippingPlanes = this._tileset.clippingPlanes;
+ if (this._tile.clippingPlanesDirty && defined(tilesetClippingPlanes)) {
+ // Dereference the clipping planes from the model if they are irrelevant - saves on shading
+ // Link/Dereference directly to avoid ownership checks.
+ model._clippingPlanes = (tilesetClippingPlanes.enabled && this._tile._isClipped) ? tilesetClippingPlanes : undefined;
+ }
}
+ this._modelInstanceCollection.update(frameState);
+
// If any commands were pushed, add derived commands
var commandEnd = frameState.commandList.length;
if ((commandStart < commandEnd) && frameState.passes.render) {
diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js
index 97c47808ac3b..efdc3966523e 100644
--- a/Source/Scene/Model.js
+++ b/Source/Scene/Model.js
@@ -64,6 +64,7 @@ define([
'./BlendingState',
'./ColorBlendMode',
'./DracoLoader',
+ './getClippingFunction',
'./HeightReference',
'./JobType',
'./ModelAnimationCache',
@@ -141,6 +142,7 @@ define([
BlendingState,
ColorBlendMode,
DracoLoader,
+ getClippingFunction,
HeightReference,
JobType,
ModelAnimationCache,
@@ -269,7 +271,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 {ClippingPlaneCollection} [options.clippingPlanes] The {@link ClippingPlaneCollection} used to selectively disable rendering the model. Clipping planes are not currently supported in Internet Explorer.
+ * @param {ClippingPlaneCollection} [options.clippingPlanes] The {@link ClippingPlaneCollection} 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.
@@ -492,6 +494,7 @@ define([
this.color = defaultValue(options.color, Color.WHITE);
this._color = new Color();
this._colorPreviousAlpha = 1.0;
+ this._hasPremultipliedAlpha = false;
/**
* Defines how the color blends with the model.
@@ -513,12 +516,12 @@ define([
*/
this.colorBlendAmount = defaultValue(options.colorBlendAmount, 0.5);
- /**
- * The {@link ClippingPlaneCollection} used to selectively disable rendering the model. Clipping planes are not currently supported in Internet Explorer.
- *
- * @type {ClippingPlaneCollection}
- */
+ this._colorShadingEnabled = isColorShadingEnabled(this);
+
+ this._clippingPlanes = undefined;
this.clippingPlanes = options.clippingPlanes;
+ // Used for checking if shaders need to be regenerated due to clipping plane changes.
+ this._clippingPlanesState = 0;
/**
* This property is for debugging only; it is not for production use nor is it optimized.
@@ -626,6 +629,12 @@ define([
this._texturesByteLength = 0;
this._trianglesLength = 0;
+ // Hold references to programs and shaders for shader reconstruction.
+ // Hold these separately because _cachedGltf may get released (this.releaseGltfJson)
+ this._sourcePrograms = undefined;
+ this._sourceShaders = undefined;
+ this._quantizedVertexShaders = {};
+
this._nodeCommands = [];
this._pickIds = [];
@@ -634,8 +643,6 @@ 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, {
@@ -1002,6 +1009,26 @@ define([
get : function() {
return this._cachedTexturesByteLength;
}
+ },
+
+ /**
+ * The {@link ClippingPlaneCollection} used to selectively disable rendering the model.
+ *
+ * @memberof Model.prototype
+ *
+ * @type {ClippingPlaneCollection}
+ */
+ clippingPlanes : {
+ get : function() {
+ return this._clippingPlanes;
+ },
+ set : function(value) {
+ if (value === this._clippingPlanes) {
+ return;
+ }
+ // Handle destroying, checking of unknown, checking for existing ownership
+ ClippingPlaneCollection.setOwnership(value, this, '_clippingPlanes');
+ }
}
});
@@ -1009,6 +1036,15 @@ define([
return context.stencilBuffer;
}
+ function isColorShadingEnabled(model) {
+ return !Color.equals(model.color, Color.WHITE) || (model.colorBlendMode !== ColorBlendMode.HIGHLIGHT);
+ }
+
+ function isClippingEnabled(model) {
+ var clippingPlanes = model._clippingPlanes;
+ return defined(clippingPlanes) && clippingPlanes.enabled;
+ }
+
/**
* Determines if silhouettes are supported.
*
@@ -1063,7 +1099,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 {ClippingPlaneCollection} [options.clippingPlanes] The {@link ClippingPlaneCollection} used to selectively disable rendering the model. Clipping planes are not currently supported in Internet Explorer.
+ * @param {ClippingPlaneCollection} [options.clippingPlanes] The {@link ClippingPlaneCollection} used to selectively disable rendering the model.
*
* @returns {Model} The newly created model.
*
@@ -1885,39 +1921,99 @@ define([
///////////////////////////////////////////////////////////////////////////
+ // When building programs for the first time, do not include modifiers for clipping planes and color
+ // since this is the version of the program that will be cached.
function createProgram(id, model, context) {
- var programs = model.gltf.programs;
- var shaders = model.gltf.shaders;
- var program = programs[id];
+ var program = model._sourcePrograms[id];
+ var shaders = model._sourceShaders;
+ var quantizedVertexShaders = model._quantizedVertexShaders;
- var attributeLocations = createAttributeLocations(model, program.attributes);
var vs = shaders[program.vertexShader].extras._pipeline.source;
var fs = shaders[program.fragmentShader].extras._pipeline.source;
- // Add pre-created attributes to attributeLocations
- var attributesLength = program.attributes.length;
- var precreatedAttributes = model._precreatedAttributes;
- if (defined(precreatedAttributes)) {
- for (var attrName in precreatedAttributes) {
- if (precreatedAttributes.hasOwnProperty(attrName)) {
- attributeLocations[attrName] = attributesLength++;
- }
+ if (model.extensionsUsed.WEB3D_quantized_attributes) {
+ var quantizedVS = quantizedVertexShaders[id];
+ if (!defined(quantizedVS)) {
+ quantizedVS = modifyShaderForQuantizedAttributes(vs, id, model);
+ quantizedVertexShaders[id] = quantizedVS;
}
+ vs = quantizedVS;
}
+ var drawVS = modifyShader(vs, id, model._vertexShaderLoaded);
+ var drawFS = modifyShader(fs, id, model._fragmentShaderLoaded);
+
+ var pickFS, pickVS;
+ if (model.allowPicking) {
+ // PERFORMANCE_IDEA: Can optimize this shader with a glTF hint. https://github.com/KhronosGroup/glTF/issues/181
+ pickVS = modifyShader(vs, id, model._pickVertexShaderLoaded);
+ pickFS = modifyShader(fs, id, model._pickFragmentShaderLoaded);
+
+ if (!model._pickFragmentShaderLoaded) {
+ pickFS = ShaderSource.createPickFragmentShaderSource(fs, 'uniform');
+ }
+ }
+ createAttributesAndProgram(id, drawFS, drawVS, pickFS, pickVS, model, context);
+ }
+
+ function recreateProgram(id, model, context) {
+ var program = model._sourcePrograms[id];
+ var shaders = model._sourceShaders;
+ var quantizedVertexShaders = model._quantizedVertexShaders;
+
+ var clippingPlaneCollection = model.clippingPlanes;
+ var addClippingPlaneCode = isClippingEnabled(model);
+
+ var vs = shaders[program.vertexShader].extras._pipeline.source;
+ var fs = shaders[program.fragmentShader].extras._pipeline.source;
+
if (model.extensionsUsed.WEB3D_quantized_attributes) {
- vs = modifyShaderForQuantizedAttributes(vs, id, model);
+ vs = quantizedVertexShaders[id];
}
- var premultipliedAlpha = hasPremultipliedAlpha(model);
- var finalFS = modifyShaderForColor(fs, premultipliedAlpha);
- if (ClippingPlaneCollection.isSupported()) {
- finalFS = modifyShaderForClippingPlanes(finalFS);
+ var finalFS = fs;
+ if (isColorShadingEnabled(model)) {
+ finalFS = Model._modifyShaderForColor(finalFS, model._hasPremultipliedAlpha);
+ }
+ if (addClippingPlaneCode) {
+ finalFS = modifyShaderForClippingPlanes(finalFS, clippingPlaneCollection);
}
var drawVS = modifyShader(vs, id, model._vertexShaderLoaded);
var drawFS = modifyShader(finalFS, id, model._fragmentShaderLoaded);
+ var pickFS, pickVS;
+ if (model.allowPicking) {
+ // PERFORMANCE_IDEA: Can optimize this shader with a glTF hint. https://github.com/KhronosGroup/glTF/issues/181
+ pickVS = modifyShader(vs, id, model._pickVertexShaderLoaded);
+ pickFS = modifyShader(fs, id, model._pickFragmentShaderLoaded);
+
+ if (!model._pickFragmentShaderLoaded) {
+ pickFS = ShaderSource.createPickFragmentShaderSource(fs, 'uniform');
+ }
+
+ if (addClippingPlaneCode) {
+ pickFS = modifyShaderForClippingPlanes(pickFS, clippingPlaneCollection);
+ }
+ }
+ createAttributesAndProgram(id, drawFS, drawVS, pickFS, pickVS, model, context);
+ }
+
+ function createAttributesAndProgram(id, drawFS, drawVS, pickFS, pickVS, model, context) {
+ var program = model._sourcePrograms[id];
+ var attributeLocations = createAttributeLocations(model, program.attributes);
+
+ // Add pre-created attributes to attributeLocations
+ var attributesLength = program.attributes.length;
+ var precreatedAttributes = model._precreatedAttributes;
+ if (defined(precreatedAttributes)) {
+ for (var attrName in precreatedAttributes) {
+ if (precreatedAttributes.hasOwnProperty(attrName)) {
+ attributeLocations[attrName] = attributesLength++;
+ }
+ }
+ }
+
model._rendererResources.programs[id] = ShaderProgram.fromCache({
context : context,
vertexShaderSource : drawVS,
@@ -1926,14 +2022,6 @@ define([
});
if (model.allowPicking) {
- // PERFORMANCE_IDEA: Can optimize this shader with a glTF hint. https://github.com/KhronosGroup/glTF/issues/181
- var pickVS = modifyShader(vs, id, model._pickVertexShaderLoaded);
- var pickFS = modifyShader(fs, id, model._pickFragmentShaderLoaded);
-
- if (!model._pickFragmentShaderLoaded) {
- pickFS = ShaderSource.createPickFragmentShaderSource(fs, 'uniform');
- }
-
model._rendererResources.pickPrograms[id] = ShaderProgram.fromCache({
context : context,
vertexShaderSource : pickVS,
@@ -2547,7 +2635,7 @@ define([
var polygonOffset = defaultValue(statesFunctions.polygonOffset, [0.0, 0.0]);
// Change the render state to use traditional alpha blending instead of premultiplied alpha blending
- if (booleanStates[WebGLConstants.BLEND] && hasPremultipliedAlpha(model)) {
+ if (booleanStates[WebGLConstants.BLEND] && model._hasPremultipliedAlpha) {
if ((blendFuncSeparate[0] === WebGLConstants.ONE) && (blendFuncSeparate[1] === WebGLConstants.ONE_MINUS_SRC_ALPHA)) {
blendFuncSeparate[0] = WebGLConstants.SRC_ALPHA;
blendFuncSeparate[1] = WebGLConstants.ONE_MINUS_SRC_ALPHA;
@@ -2832,33 +2920,21 @@ define([
};
}
- function createClippingPlanesLengthFunction(model) {
- return function() {
- return model._packedClippingPlanes.length;
- };
- }
-
- function createClippingPlanesUnionRegionsFunction(model) {
+ var scratchClippingPlaneMatrix = new Matrix4();
+ function createClippingPlanesMatrixFunction(model) {
return function() {
var clippingPlanes = model.clippingPlanes;
if (!defined(clippingPlanes)) {
- return false;
+ return Matrix4.IDENTITY;
}
-
- return clippingPlanes.unionClippingRegions;
+ return Matrix4.multiply(model._modelViewMatrix, clippingPlanes.modelMatrix, scratchClippingPlaneMatrix);
};
}
function createClippingPlanesFunction(model) {
return function() {
var clippingPlanes = model.clippingPlanes;
- var packedPlanes = model._packedClippingPlanes;
-
- if (defined(clippingPlanes) && clippingPlanes.enabled) {
- clippingPlanes.transformAndPackPlanes(model._modelViewMatrix, packedPlanes);
- }
-
- return packedPlanes;
+ return (!defined(clippingPlanes) || !clippingPlanes.enabled) ? model._defaultTexture : clippingPlanes.texture;
};
}
@@ -2977,10 +3053,9 @@ define([
uniformMap = combine(uniformMap, {
gltf_color : createColorFunction(model),
gltf_colorBlend : createColorBlendFunction(model),
- gltf_clippingPlanesLength: createClippingPlanesLengthFunction(model),
- gltf_clippingPlanesUnionRegions: createClippingPlanesUnionRegionsFunction(model),
- gltf_clippingPlanes: createClippingPlanesFunction(model, context),
- gltf_clippingPlanesEdgeStyle: createClippingPlanesEdgeStyleFunction(model)
+ gltf_clippingPlanes: createClippingPlanesFunction(model),
+ gltf_clippingPlanesEdgeStyle: createClippingPlanesEdgeStyleFunction(model),
+ gltf_clippingPlanesMatrix: createClippingPlanesMatrixFunction(model)
});
// Allow callback to modify the uniformMap
@@ -3020,7 +3095,7 @@ define([
vertexArray : vertexArray,
count : count,
offset : offset,
- shaderProgram : rendererPrograms[technique.program],
+ shaderProgram : rendererPrograms[programId],
castShadows : castShadows,
receiveShadows : receiveShadows,
uniformMap : uniformMap,
@@ -3061,7 +3136,7 @@ define([
vertexArray : vertexArray,
count : count,
offset : offset,
- shaderProgram : rendererPickPrograms[technique.program],
+ shaderProgram : rendererPickPrograms[programId],
uniformMap : pickUniformMap,
renderState : rs,
owner : owner,
@@ -3097,12 +3172,13 @@ define([
silhouetteColorCommand2D : undefined,
// Generated on demand when color alpha is less than 1.0
translucentCommand : undefined,
- translucentCommand2D : undefined
+ translucentCommand2D : undefined,
+ // For updating node commands on shader reconstruction
+ programId : programId
};
runtimeNode.commands.push(nodeCommand);
nodeCommands.push(nodeCommand);
}
-
}
function createRuntimeNodes(model, context, scene3DOnly) {
@@ -3231,6 +3307,11 @@ define([
var context = frameState.context;
var scene3DOnly = frameState.scene3DOnly;
+ // Retain references to updated source shaders and programs for rebuilding as needed
+ model._sourcePrograms = model.gltf.programs;
+ model._sourceShaders = model.gltf.shaders;
+ model._hasPremultipliedAlpha = hasPremultipliedAlpha(model);
+
ModelUtility.checkSupportedGlExtensions(model.gltf.glExtensionsUsed, context);
if (model._loadRendererResourcesFromCache) {
var resources = model._rendererResources;
@@ -3554,14 +3635,14 @@ define([
return translucentCommand;
}
- function updateColor(model, frameState) {
+ function updateColor(model, frameState, forceDerive) {
// Generate translucent commands when the blend color has an alpha in the range (0.0, 1.0) exclusive
var scene3DOnly = frameState.scene3DOnly;
var alpha = model.color.alpha;
if ((alpha > 0.0) && (alpha < 1.0)) {
var nodeCommands = model._nodeCommands;
var length = nodeCommands.length;
- if (!defined(nodeCommands[0].translucentCommand)) {
+ if (!defined(nodeCommands[0].translucentCommand) || forceDerive) {
for (var i = 0; i < length; ++i) {
var nodeCommand = nodeCommands[i];
var command = nodeCommand.command;
@@ -3774,42 +3855,29 @@ define([
}
}
- function modifyShaderForClippingPlanes(shader) {
+ function modifyShaderForClippingPlanes(shader, clippingPlaneCollection) {
shader = ShaderSource.replaceMain(shader, 'gltf_clip_main');
+ shader += Model._getClippingFunction(clippingPlaneCollection) + '\n';
shader +=
- 'uniform int gltf_clippingPlanesLength; \n' +
- 'uniform bool gltf_clippingPlanesUnionRegions; \n' +
- 'uniform vec4 gltf_clippingPlanes[czm_maxClippingPlanes]; \n' +
+ 'uniform sampler2D gltf_clippingPlanes; \n' +
'uniform vec4 gltf_clippingPlanesEdgeStyle; \n' +
+ 'uniform mat4 gltf_clippingPlanesMatrix; \n' +
'void main() \n' +
'{ \n' +
' gltf_clip_main(); \n' +
- ' if (gltf_clippingPlanesLength > 0) \n' +
+ ' float clipDistance = clip(gl_FragCoord, gltf_clippingPlanes, gltf_clippingPlanesMatrix);' +
+ ' 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' +
- ' float clipDistance; \n' +
- ' if (gltf_clippingPlanesUnionRegions) \n' +
- ' { \n' +
- ' clipDistance = czm_discardIfClippedWithUnion(gltf_clippingPlanes, gltf_clippingPlanesLength); \n' +
- ' } \n' +
- ' else \n' +
- ' { \n' +
- ' clipDistance = czm_discardIfClippedWithIntersect(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' +
+ ' gl_FragColor = clippingPlanesEdgeColor;\n' +
' } \n' +
'} \n';
-
return shader;
}
- function updateSilhouette(model, frameState) {
+ function updateSilhouette(model, frameState, force) {
// 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:
// 1. silhouetteModelCommand : render model normally while enabling stencil mask
@@ -3826,25 +3894,16 @@ define([
model._colorPreviousAlpha = model.color.alpha;
model._silhouetteColorPreviousAlpha = model.silhouetteColor.alpha;
- if (dirty) {
+ if (dirty || force) {
createSilhouetteCommands(model, frameState);
}
}
- function updateClippingPlanes(model) {
- var clippingPlanes = model.clippingPlanes;
- var length = 0;
- if (defined(clippingPlanes) && clippingPlanes.enabled) {
- length = clippingPlanes.length;
- }
-
- var packedPlanes = model._packedClippingPlanes;
- var packedLength = packedPlanes.length;
- if (packedLength !== length) {
- packedPlanes.length = length;
-
- for (var i = packedLength; i < length; ++i) {
- packedPlanes[i] = new Cartesian4();
+ function updateClippingPlanes(model, frameState) {
+ var clippingPlanes = model._clippingPlanes;
+ if (defined(clippingPlanes) && clippingPlanes.owner === model) {
+ if (clippingPlanes.enabled) {
+ clippingPlanes.update(frameState);
}
}
}
@@ -4076,7 +4135,7 @@ define([
// Use renderer resources from cache instead of loading/creating them?
var cachedRendererResources;
var cacheKey = this.cacheKey;
- if (defined(cacheKey)) {
+ if (defined(cacheKey)) { // cache key given? this model will pull from or contribute to context level cache
context.cache.modelRendererResourceCache = defaultValue(context.cache.modelRendererResourceCache, {});
var modelCaches = context.cache.modelRendererResourceCache;
@@ -4096,7 +4155,7 @@ define([
modelCaches[this.cacheKey] = cachedRendererResources;
}
this._cachedRendererResources = cachedRendererResources;
- } else {
+ } else { // cache key not given? this model doesn't care about context level cache at all. Cache is here to simplify freeing on destroy.
cachedRendererResources = new CachedRendererResources(context);
cachedRendererResources.count = 1;
this._cachedRendererResources = cachedRendererResources;
@@ -4280,11 +4339,6 @@ define([
}
}
- var clippingPlanes = this.clippingPlanes;
- if (defined(clippingPlanes) && clippingPlanes.enabled) {
- Matrix4.multiply(context.uniformState.view3D, modelMatrix, this._modelViewMatrix);
- }
-
// Update modelMatrix throughout the graph as needed
if (animated || modelTransformChanged || justLoaded) {
updateNodeHierarchyModelMatrix(this, modelTransformChanged, justLoaded, frameState.mapProjection);
@@ -4304,9 +4358,32 @@ define([
updateWireframe(this);
updateShowBoundingVolume(this);
updateShadows(this);
- updateColor(this, frameState);
- updateSilhouette(this, frameState);
updateClippingPlanes(this, frameState);
+
+ // Regnerate shaders if ClippingPlaneCollection state changed or it was removed
+ var clippingPlanes = this._clippingPlanes;
+ var currentClippingPlanesState = 0;
+ if (defined(clippingPlanes) && clippingPlanes.enabled) {
+ Matrix4.multiply(context.uniformState.view3D, modelMatrix, this._modelViewMatrix);
+ currentClippingPlanesState = clippingPlanes.clippingPlanesState();
+ }
+
+ var shouldRegenerateShaders = this._clippingPlanesState !== currentClippingPlanesState;
+ this._clippingPlanesState = currentClippingPlanesState;
+
+ // Regenerate shaders if color shading changed from last update
+ var currentlyColorShadingEnabled = isColorShadingEnabled(this);
+ if (currentlyColorShadingEnabled !== this._colorShadingEnabled) {
+ this._colorShadingEnabled = currentlyColorShadingEnabled;
+ shouldRegenerateShaders = true;
+ }
+
+ if (shouldRegenerateShaders) {
+ regenerateShaders(this, frameState);
+ } else {
+ updateColor(this, frameState, false);
+ updateSilhouette(this, frameState, false);
+ }
}
if (justLoaded) {
@@ -4385,6 +4462,83 @@ define([
}
};
+ function destroyIfNotCached(rendererResources, cachedRendererResources) {
+ if (rendererResources.programs !== cachedRendererResources.programs) {
+ destroy(rendererResources.programs);
+ }
+ if (rendererResources.pickPrograms !== cachedRendererResources.pickPrograms) {
+ destroy(rendererResources.pickPrograms);
+ }
+ if (rendererResources.silhouettePrograms !== cachedRendererResources.silhouettePrograms) {
+ destroy(rendererResources.silhouettePrograms);
+ }
+ }
+
+ // Run from update iff:
+ // - everything is loaded
+ // - clipping planes state change OR color state set
+ // Run this from destructor after removing color state and clipping plane state
+ function regenerateShaders(model, frameState) {
+ // In regards to _cachedRendererResources:
+ // Fair to assume that this is data that should just never get modified due to clipping planes or model color.
+ // So if clipping planes or model color active:
+ // - delink _rendererResources.*programs and create new dictionaries.
+ // - do NOT destroy any programs - might be used by copies of the model or by might be needed in the future if clipping planes/model color is deactivated
+
+ // If clipping planes and model color inactive:
+ // - destroy _rendererResources.*programs
+ // - relink _rendererResources.*programs to _cachedRendererResources
+
+ // In both cases, need to mark commands as dirty, re-run derived commands (elsewhere)
+
+ var rendererResources = model._rendererResources;
+ var cachedRendererResources = model._cachedRendererResources;
+ destroyIfNotCached(rendererResources, cachedRendererResources);
+
+ if (isClippingEnabled(model) || isColorShadingEnabled(model)) {
+ rendererResources.programs = {};
+ rendererResources.pickPrograms = {};
+ rendererResources.silhouettePrograms = {};
+
+ var sourcePrograms = model._sourcePrograms;
+
+ ForEach.object(sourcePrograms, function(program, id) {
+ recreateProgram(id, model, frameState.context);
+ });
+ } else {
+ rendererResources.programs = cachedRendererResources.programs;
+ rendererResources.pickPrograms = cachedRendererResources.pickPrograms;
+ rendererResources.silhouettePrograms = cachedRendererResources.silhouettePrograms;
+ }
+
+ // Fix all the commands, marking them as dirty so everything that derives will re-derive
+ var rendererPrograms = rendererResources.programs;
+ var rendererPickPrograms = rendererResources.pickPrograms;
+
+ var nodeCommands = model._nodeCommands;
+ var commandCount = nodeCommands.length;
+ for (var i = 0; i < commandCount; ++i) {
+ var nodeCommand = nodeCommands[i];
+ var programId = nodeCommand.programId;
+
+ var renderProgram = rendererPrograms[programId];
+ var pickProgram = rendererPickPrograms[programId];
+
+ nodeCommand.command.shaderProgram = renderProgram;
+ nodeCommand.pickCommand.shaderProgram = pickProgram;
+ if (defined(nodeCommand.command2D)) {
+ nodeCommand.command2D.shaderProgram = renderProgram;
+ }
+ if (defined(nodeCommand.pickCommand2D)) {
+ nodeCommand.pickCommand2D.shaderProgram = pickProgram;
+ }
+ }
+
+ // Force update silhouette commands/shaders
+ updateColor(model, frameState, true);
+ updateSilhouette(model, frameState, true);
+ }
+
/**
* Returns true if this object was destroyed; otherwise, false.
*