diff --git a/.travis.yml b/.travis.yml index 024b8097b877..42139a1752fe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,7 @@ script: - echo 'makeZipFile' && echo -en 'travis_fold:start:script.makeZipFile\\r' - npm run clean - - npm run makeZipFile + - npm run makeZipFile -- --concurrency 2 - npm pack - echo -en 'travis_fold:end:script.makeZipFile\\r' diff --git a/Apps/Sandcastle/gallery/Classification.html b/Apps/Sandcastle/gallery/Classification.html new file mode 100644 index 000000000000..bafb8cf32ffa --- /dev/null +++ b/Apps/Sandcastle/gallery/Classification.html @@ -0,0 +1,232 @@ + + + + + + + + + Cesium Demo + + + + + + +
+

Loading...

+
+ + + diff --git a/Apps/Sandcastle/gallery/Classification.jpg b/Apps/Sandcastle/gallery/Classification.jpg new file mode 100644 index 000000000000..cea1adae8f22 Binary files /dev/null and b/Apps/Sandcastle/gallery/Classification.jpg differ diff --git a/Apps/Sandcastle/gallery/Polygon.html b/Apps/Sandcastle/gallery/Polygon.html index f1c86edafe76..56e7e95d5c3f 100644 --- a/Apps/Sandcastle/gallery/Polygon.html +++ b/Apps/Sandcastle/gallery/Polygon.html @@ -103,7 +103,7 @@ }] }, material : Cesium.Color.BLUE.withAlpha(0.5), - heigth : 0, + height : 0, outline : true // height is required for outline to display } }); diff --git a/Apps/Sandcastle/gallery/development/Frustum.html b/Apps/Sandcastle/gallery/development/Frustum.html new file mode 100644 index 000000000000..7f688433d8f6 --- /dev/null +++ b/Apps/Sandcastle/gallery/development/Frustum.html @@ -0,0 +1,102 @@ + + + + + + + + + Cesium Demo + + + + + + +
+

Loading...

+
+ + + diff --git a/CHANGES.md b/CHANGES.md index 78c2296e6f11..c7d169bad60b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,7 @@ Change Log * The function `Quaternion.fromHeadingPitchRoll(heading, pitch, roll, result)` was removed. Use `Quaternion.fromHeadingPitchRoll(hpr, result)` instead where `hpr` is a `HeadingPitchRoll`. * The function `Transforms.headingPitchRollToFixedFrame(origin, headingPitchRoll, ellipsoid, result)` was removed. Use `Transforms.headingPitchRollToFixedFrame(origin, headingPitchRoll, ellipsoid, fixedFrameTransform, result)` instead where `fixedFrameTransform` is a a 4x4 transformation matrix (see `Transforms.localFrameToFixedFrameGenerator`). * The function `Transforms.headingPitchRollQuaternion(origin, headingPitchRoll, ellipsoid, result)` was removed. Use `Transforms.headingPitchRollQuaternion(origin, headingPitchRoll, ellipsoid, fixedFrameTransform, result)` instead where `fixedFrameTransform` is a a 4x4 transformation matrix (see `Transforms.localFrameToFixedFrameGenerator`). + * The `color`, `show`, and `pointSize` properties of `Cesium3DTileStyle` are no longer initialized with default values. * Deprecated * `Scene/CullingVolume` is deprecated and will be removed in 1.38. Use `Core/CullingVolume`. * `Scene/OrthographicFrustum` is deprecated and will be removed in 1.38. Use `Core/OrthographicFrustum`. @@ -14,10 +15,23 @@ Change Log * `Scene/PerspectiveFrustum` is deprecated and will be removed in 1.38. Use `Core/PerspectiveFrustum`. * `Scene/PerspectiveOffCenterFrustum` is deprecated and will be removed in 1.38. Use `Core/PerspectiveOffCenterFrustum`. * Added ability to show tile urls in the 3D Tiles Inspector. [#5592](https://github.com/AnalyticalGraphicsInc/cesium/pull/5592) +* Fixed a bug when reading CRN compressed textures with multiple mip levels. [#5618](https://github.com/AnalyticalGraphicsInc/cesium/pull/5618) +* Fixed issue where composite 3D Tiles that contained instanced 3D Tiles with an external model reference would fail to download the model. * Added behavior to `Cesium3DTilesInspector` that selects the first tileset hovered over if no tilest is specified. [#5139](https://github.com/AnalyticalGraphicsInc/cesium/issues/5139) * Added ability to provide a `width` and `height` to `scene.pick`. [#5602](https://github.com/AnalyticalGraphicsInc/cesium/pull/5602) +* Added `Entity.computeModelMatrix` which returns the model matrix representing the entity's transformation. [#5584](https://github.com/AnalyticalGraphicsInc/cesium/pull/5584) +* Added ability to set a style's `color`, `show`, or `pointSize` with a string or object literal. `show` may also take a boolean and `pointSize` may take a number. [#5412](https://github.com/AnalyticalGraphicsInc/cesium/pull/5412) +* Added setter for `KmlDataSource.name` to specify a name for the datasource [#5660](https://github.com/AnalyticalGraphicsInc/cesium/pull/5660). +* Added setter for `GeoJsonDataSource.name` to specify a name for the datasource [#5653](https://github.com/AnalyticalGraphicsInc/cesium/issues/5653) * Fixed issue where scene would blink when labels were added. [#5537](https://github.com/AnalyticalGraphicsInc/cesium/issues/5537) +* Fixed label positioning when height reference changes [#5609](https://github.com/AnalyticalGraphicsInc/cesium/issues/5609) * Fixed crash when using the `Cesium3DTilesInspectorViewModel` and removing a tileset [#5607](https://github.com/AnalyticalGraphicsInc/cesium/issues/5607) +* Fixed polygon outline in Polygon Sandcastle demo [#5642](https://github.com/AnalyticalGraphicsInc/cesium/issues/5642) +* Fixed label positioning when using `HeightReference.CLAMP_TO_GROUND` and no position [#5648](https://github.com/AnalyticalGraphicsInc/cesium/pull/5648) +* Updated `Billboard`, `Label` and `PointPrimitive` constructors to clone `NearFarScale` parameters [#5654](https://github.com/AnalyticalGraphicsInc/cesium/pull/5654) +* Added `FrustumGeometry` and `FrustumOutlineGeometry`. [#5649](https://github.com/AnalyticalGraphicsInc/cesium/pull/5649) +* Added an `options` parameter to the constructors of `PerspectiveFrustum`, `PerspectiveOffCenterFrustum`, `OrthographicFrustum`, and `OrthographicOffCenterFrustum` to set properties. [#5649](https://github.com/AnalyticalGraphicsInc/cesium/pull/5649) +* Added `ClassificationPrimitive` which defines a volume and draws the intersection of the volume and terrain or 3D Tiles. [#5625](https://github.com/AnalyticalGraphicsInc/cesium/pull/5625) ### 1.35.2 - 2017-07-11 diff --git a/Source/Core/CorridorGeometry.js b/Source/Core/CorridorGeometry.js index eb44d140f0aa..3c7e24aeab6e 100644 --- a/Source/Core/CorridorGeometry.js +++ b/Source/Core/CorridorGeometry.js @@ -54,6 +54,13 @@ define([ var scratch1 = new Cartesian3(); var scratch2 = new Cartesian3(); + function scaleToSurface(positions, ellipsoid) { + for (var i = 0; i < positions.length; i++) { + positions[i] = ellipsoid.scaleToGeodeticSurface(positions[i], positions[i]); + } + return positions; + } + function addNormals(attr, normal, left, front, back, vertexFormat) { var normals = attr.normals; var tangents = attr.tangents; @@ -698,6 +705,7 @@ define([ var scratchCartographicMax = new Cartographic(); function computeRectangle(positions, ellipsoid, width, cornerType) { + positions = scaleToSurface(positions, ellipsoid); var cleanPositions = arrayRemoveDuplicates(positions, Cartesian3.equalsEpsilon); var length = cleanPositions.length; if (length < 2 || width <= 0) { @@ -964,14 +972,15 @@ define([ var width = corridorGeometry._width; var extrudedHeight = corridorGeometry._extrudedHeight; var extrude = (height !== extrudedHeight); + var ellipsoid = corridorGeometry._ellipsoid; + positions = scaleToSurface(positions, ellipsoid); var cleanPositions = arrayRemoveDuplicates(positions, Cartesian3.equalsEpsilon); if ((cleanPositions.length < 2) || (width <= 0)) { return; } - var ellipsoid = corridorGeometry._ellipsoid; var vertexFormat = corridorGeometry._vertexFormat; var params = { ellipsoid : ellipsoid, diff --git a/Source/Core/CorridorGeometryLibrary.js b/Source/Core/CorridorGeometryLibrary.js index dfa20ca7ac9e..c00ef5907de8 100644 --- a/Source/Core/CorridorGeometryLibrary.js +++ b/Source/Core/CorridorGeometryLibrary.js @@ -150,13 +150,6 @@ define([ } }; - function scaleToSurface(positions, ellipsoid) { - for (var i = 0; i < positions.length; i++) { - positions[i] = ellipsoid.scaleToGeodeticSurface(positions[i], positions[i]); - } - return positions; - } - var scratchForwardProjection = new Cartesian3(); var scratchBackwardProjection = new Cartesian3(); @@ -167,7 +160,6 @@ define([ var granularity = params.granularity; var positions = params.positions; var ellipsoid = params.ellipsoid; - positions = scaleToSurface(positions, ellipsoid); var width = params.width / 2; var cornerType = params.cornerType; var saveAttributes = params.saveAttributes; diff --git a/Source/Core/CorridorOutlineGeometry.js b/Source/Core/CorridorOutlineGeometry.js index 90fd307ba88d..b047c00f6d9a 100644 --- a/Source/Core/CorridorOutlineGeometry.js +++ b/Source/Core/CorridorOutlineGeometry.js @@ -40,6 +40,13 @@ define([ var cartesian2 = new Cartesian3(); var cartesian3 = new Cartesian3(); + function scaleToSurface(positions, ellipsoid) { + for (var i = 0; i < positions.length; i++) { + positions[i] = ellipsoid.scaleToGeodeticSurface(positions[i], positions[i]); + } + return positions; + } + function combine(computedPositions, cornerType) { var wallIndices = []; var positions = computedPositions.positions; @@ -462,14 +469,15 @@ define([ var width = corridorOutlineGeometry._width; var extrudedHeight = corridorOutlineGeometry._extrudedHeight; var extrude = (height !== extrudedHeight); + var ellipsoid = corridorOutlineGeometry._ellipsoid; + positions = scaleToSurface(positions, ellipsoid); var cleanPositions = arrayRemoveDuplicates(positions, Cartesian3.equalsEpsilon); if ((cleanPositions.length < 2) || (width <= 0)) { return; } - var ellipsoid = corridorOutlineGeometry._ellipsoid; var params = { ellipsoid : ellipsoid, positions : cleanPositions, diff --git a/Source/Core/FrustumGeometry.js b/Source/Core/FrustumGeometry.js new file mode 100644 index 000000000000..22713aa81e6b --- /dev/null +++ b/Source/Core/FrustumGeometry.js @@ -0,0 +1,503 @@ +define([ + './BoundingSphere', + './Cartesian3', + './Cartesian4', + './Check', + './ComponentDatatype', + './defaultValue', + './defined', + './Geometry', + './GeometryAttribute', + './GeometryAttributes', + './Matrix3', + './Matrix4', + './OrthographicFrustum', + './PerspectiveFrustum', + './PrimitiveType', + './Quaternion', + './VertexFormat' + ], function( + BoundingSphere, + Cartesian3, + Cartesian4, + Check, + ComponentDatatype, + defaultValue, + defined, + Geometry, + GeometryAttribute, + GeometryAttributes, + Matrix3, + Matrix4, + OrthographicFrustum, + PerspectiveFrustum, + PrimitiveType, + Quaternion, + VertexFormat) { + 'use strict'; + + var PERSPECTIVE = 0; + var ORTHOGRAPHIC = 1; + + /** + * Describes a frustum at the given the origin and orientation. + * + * @alias FrustumGeometry + * @constructor + * + * @param {Object} options Object with the following properties: + * @param {PerspectiveFrustum|OrthographicFrustum} options.frustum The frustum. + * @param {Cartesian3} options.origin The origin of the frustum. + * @param {Quaternion} options.orientation The orientation of the frustum. + * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed. + */ + function FrustumGeometry(options) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object('options', options); + Check.typeOf.object('options.frustum', options.frustum); + Check.typeOf.object('options.origin', options.origin); + Check.typeOf.object('options.orientation', options.orientation); + //>>includeEnd('debug'); + + var frustum = options.frustum; + var orientation = options.orientation; + var origin = options.origin; + var vertexFormat = defaultValue(options.vertexFormat, VertexFormat.DEFAULT); + + // This is private because it is used by DebugCameraPrimitive to draw a multi-frustum by + // creating multiple FrustumGeometrys. This way the near plane of one frustum doesn't overlap + // the far plane of another. + var drawNearPlane = defaultValue(options._drawNearPlane, true); + + var frustumType; + var frustumPackedLength; + if (frustum instanceof PerspectiveFrustum) { + frustumType = PERSPECTIVE; + frustumPackedLength = PerspectiveFrustum.packedLength; + } else if (frustum instanceof OrthographicFrustum) { + frustumType = ORTHOGRAPHIC; + frustumPackedLength = OrthographicFrustum.packedLength; + } + + this._frustumType = frustumType; + this._frustum = frustum.clone(); + this._origin = Cartesian3.clone(origin); + this._orientation = Quaternion.clone(orientation); + this._drawNearPlane = drawNearPlane; + this._vertexFormat = vertexFormat; + this._workerName = 'createFrustumGeometry'; + + /** + * The number of elements used to pack the object into an array. + * @type {Number} + */ + this.packedLength = 2 + frustumPackedLength + Cartesian3.packedLength + Quaternion.packedLength + VertexFormat.packedLength; + } + + /** + * Stores the provided instance into the provided array. + * + * @param {FrustumGeometry} value The value to pack. + * @param {Number[]} array The array to pack into. + * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements. + * + * @returns {Number[]} The array that was packed into + */ + FrustumGeometry.pack = function(value, array, startingIndex) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object('value', value); + Check.defined('array', array); + //>>includeEnd('debug'); + + startingIndex = defaultValue(startingIndex, 0); + + var frustumType = value._frustumType; + var frustum = value._frustum; + + array[startingIndex++] = frustumType; + + if (frustumType === PERSPECTIVE) { + PerspectiveFrustum.pack(frustum, array, startingIndex); + startingIndex += PerspectiveFrustum.packedLength; + } else { + OrthographicFrustum.pack(frustum, array, startingIndex); + startingIndex += OrthographicFrustum.packedLength; + } + + Cartesian3.pack(value._origin, array, startingIndex); + startingIndex += Cartesian3.packedLength; + Quaternion.pack(value._orientation, array, startingIndex); + startingIndex += Quaternion.packedLength; + VertexFormat.pack(value._vertexFormat, array, startingIndex); + startingIndex += VertexFormat.packedLength; + array[startingIndex] = value._drawNearPlane ? 1.0 : 0.0; + + return array; + }; + + var scratchPackPerspective = new PerspectiveFrustum(); + var scratchPackOrthographic = new OrthographicFrustum(); + var scratchPackQuaternion = new Quaternion(); + var scratchPackorigin = new Cartesian3(); + var scratchVertexFormat = new VertexFormat(); + + /** + * Retrieves an instance from a packed array. + * + * @param {Number[]} array The packed array. + * @param {Number} [startingIndex=0] The starting index of the element to be unpacked. + * @param {FrustumGeometry} [result] The object into which to store the result. + */ + FrustumGeometry.unpack = function(array, startingIndex, result) { + //>>includeStart('debug', pragmas.debug); + Check.defined('array', array); + //>>includeEnd('debug'); + + startingIndex = defaultValue(startingIndex, 0); + + var frustumType = array[startingIndex++]; + + var frustum; + if (frustumType === PERSPECTIVE) { + frustum = PerspectiveFrustum.unpack(array, startingIndex, scratchPackPerspective); + startingIndex += PerspectiveFrustum.packedLength; + } else { + frustum = OrthographicFrustum.unpack(array, startingIndex, scratchPackOrthographic); + startingIndex += OrthographicFrustum.packedLength; + } + + var origin = Cartesian3.unpack(array, startingIndex, scratchPackorigin); + startingIndex += Cartesian3.packedLength; + var orientation = Quaternion.unpack(array, startingIndex, scratchPackQuaternion); + startingIndex += Quaternion.packedLength; + var vertexFormat = VertexFormat.unpack(array, startingIndex, scratchVertexFormat); + startingIndex += VertexFormat.packedLength; + var drawNearPlane = array[startingIndex] === 1.0; + + if (!defined(result)) { + return new FrustumGeometry({ + frustum : frustum, + origin : origin, + orientation : orientation, + vertexFormat : vertexFormat, + _drawNearPlane : drawNearPlane + }); + } + + var frustumResult = frustumType === result._frustumType ? result._frustum : undefined; + result._frustum = frustum.clone(frustumResult); + + result._frustumType = frustumType; + result._origin = Cartesian3.clone(origin, result._origin); + result._orientation = Quaternion.clone(orientation, result._orientation); + result._vertexFormat = VertexFormat.clone(vertexFormat, result._vertexFormat); + result._drawNearPlane = drawNearPlane; + + return result; + }; + + function getAttributes(offset, normals, tangents, bitangents, st, normal, tangent, bitangent) { + var stOffset = offset / 3 * 2; + + for (var i = 0; i < 4; ++i) { + if (defined(normals)) { + normals[offset] = normal.x; + normals[offset + 1] = normal.y; + normals[offset + 2] = normal.z; + } + if (defined(tangents)) { + tangents[offset] = tangent.x; + tangents[offset + 1] = tangent.y; + tangents[offset + 2] = tangent.z; + } + if (defined(bitangents)) { + bitangents[offset] = bitangent.x; + bitangents[offset + 1] = bitangent.y; + bitangents[offset + 2] = bitangent.z; + } + offset += 3; + } + + st[stOffset] = 0.0; + st[stOffset + 1] = 0.0; + st[stOffset + 2] = 1.0; + st[stOffset + 3] = 0.0; + st[stOffset + 4] = 1.0; + st[stOffset + 5] = 1.0; + st[stOffset + 6] = 0.0; + st[stOffset + 7] = 1.0; + } + + var scratchRotationMatrix = new Matrix3(); + var scratchViewMatrix = new Matrix4(); + var scratchInverseMatrix = new Matrix4(); + + var scratchXDirection = new Cartesian3(); + var scratchYDirection = new Cartesian3(); + var scratchZDirection = new Cartesian3(); + var scratchNegativeX = new Cartesian3(); + var scratchNegativeY = new Cartesian3(); + var scratchNegativeZ = new Cartesian3(); + + var frustumSplits = new Array(3); + + var frustumCornersNDC = new Array(4); + frustumCornersNDC[0] = new Cartesian4(-1.0, -1.0, 1.0, 1.0); + frustumCornersNDC[1] = new Cartesian4(1.0, -1.0, 1.0, 1.0); + frustumCornersNDC[2] = new Cartesian4(1.0, 1.0, 1.0, 1.0); + frustumCornersNDC[3] = new Cartesian4(-1.0, 1.0, 1.0, 1.0); + + var scratchFrustumCorners = new Array(4); + for (var i = 0; i < 4; ++i) { + scratchFrustumCorners[i] = new Cartesian4(); + } + + FrustumGeometry._computeNearFarPlanes = function(origin, orientation, frustumType, frustum, positions, xDirection, yDirection, zDirection) { + var rotationMatrix = Matrix3.fromQuaternion(orientation, scratchRotationMatrix); + var x = defaultValue(xDirection, scratchXDirection); + var y = defaultValue(yDirection, scratchYDirection); + var z = defaultValue(zDirection, scratchZDirection); + + x = Matrix3.getColumn(rotationMatrix, 0, x); + y = Matrix3.getColumn(rotationMatrix, 1, y); + z = Matrix3.getColumn(rotationMatrix, 2, z); + + Cartesian3.normalize(x, x); + Cartesian3.normalize(y, y); + Cartesian3.normalize(z, z); + + Cartesian3.negate(x, x); + + var view = Matrix4.computeView(origin, z, y, x, scratchViewMatrix); + + var inverseView; + var inverseViewProjection; + if (frustumType === PERSPECTIVE) { + var projection = frustum.projectionMatrix; + var viewProjection = Matrix4.multiply(projection, view, scratchInverseMatrix); + inverseViewProjection = Matrix4.inverse(viewProjection, scratchInverseMatrix); + } else { + inverseView = Matrix4.inverseTransformation(view, scratchInverseMatrix); + } + + if (defined(inverseViewProjection)) { + frustumSplits[0] = frustum.near; + frustumSplits[1] = frustum.far; + } else { + frustumSplits[0] = 0.0; + frustumSplits[1] = frustum.near; + frustumSplits[2] = frustum.far; + } + + for (var i = 0; i < 2; ++i) { + for (var j = 0; j < 4; ++j) { + var corner = Cartesian4.clone(frustumCornersNDC[j], scratchFrustumCorners[j]); + + if (!defined(inverseViewProjection)) { + if (defined(frustum._offCenterFrustum)) { + frustum = frustum._offCenterFrustum; + } + + var near = frustumSplits[i]; + var far = frustumSplits[i + 1]; + + corner.x = (corner.x * (frustum.right - frustum.left) + frustum.left + frustum.right) * 0.5; + corner.y = (corner.y * (frustum.top - frustum.bottom) + frustum.bottom + frustum.top) * 0.5; + corner.z = (corner.z * (near - far) - near - far) * 0.5; + corner.w = 1.0; + + Matrix4.multiplyByVector(inverseView, corner, corner); + } else { + corner = Matrix4.multiplyByVector(inverseViewProjection, corner, corner); + + // Reverse perspective divide + var w = 1.0 / corner.w; + Cartesian3.multiplyByScalar(corner, w, corner); + + Cartesian3.subtract(corner, origin, corner); + Cartesian3.normalize(corner, corner); + + var fac = Cartesian3.dot(z, corner); + Cartesian3.multiplyByScalar(corner, frustumSplits[i] / fac, corner); + Cartesian3.add(corner, origin, corner); + } + + positions[12 * i + j * 3] = corner.x; + positions[12 * i + j * 3 + 1] = corner.y; + positions[12 * i + j * 3 + 2] = corner.z; + } + } + }; + + /** + * Computes the geometric representation of a frustum, including its vertices, indices, and a bounding sphere. + * + * @param {FrustumGeometry} frustumGeometry A description of the frustum. + * @returns {Geometry|undefined} The computed vertices and indices. + */ + FrustumGeometry.createGeometry = function(frustumGeometry) { + var frustumType = frustumGeometry._frustumType; + var frustum = frustumGeometry._frustum; + var origin = frustumGeometry._origin; + var orientation = frustumGeometry._orientation; + var drawNearPlane = frustumGeometry._drawNearPlane; + var vertexFormat = frustumGeometry._vertexFormat; + + var numberOfPlanes = drawNearPlane ? 6 : 5; + var positions = new Float64Array(3 * 4 * 6); + FrustumGeometry._computeNearFarPlanes(origin, orientation, frustumType, frustum, positions); + + // -x plane + var offset = 3 * 4 * 2; + positions[offset] = positions[3 * 4]; + positions[offset + 1] = positions[3 * 4 + 1]; + positions[offset + 2] = positions[3 * 4 + 2]; + positions[offset + 3] = positions[0]; + positions[offset + 4] = positions[1]; + positions[offset + 5] = positions[2]; + positions[offset + 6] = positions[3 * 3]; + positions[offset + 7] = positions[3 * 3 + 1]; + positions[offset + 8] = positions[3 * 3 + 2]; + positions[offset + 9] = positions[3 * 7]; + positions[offset + 10] = positions[3 * 7 + 1]; + positions[offset + 11] = positions[3 * 7 + 2]; + + // -y plane + offset += 3 * 4; + positions[offset] = positions[3 * 5]; + positions[offset + 1] = positions[3 * 5 + 1]; + positions[offset + 2] = positions[3 * 5 + 2]; + positions[offset + 3] = positions[3]; + positions[offset + 4] = positions[3 + 1]; + positions[offset + 5] = positions[3 + 2]; + positions[offset + 6] = positions[0]; + positions[offset + 7] = positions[1]; + positions[offset + 8] = positions[2]; + positions[offset + 9] = positions[3 * 4]; + positions[offset + 10] = positions[3 * 4 + 1]; + positions[offset + 11] = positions[3 * 4 + 2]; + + // +x plane + offset += 3 * 4; + positions[offset] = positions[3]; + positions[offset + 1] = positions[3 + 1]; + positions[offset + 2] = positions[3 + 2]; + positions[offset + 3] = positions[3 * 5]; + positions[offset + 4] = positions[3 * 5 + 1]; + positions[offset + 5] = positions[3 * 5 + 2]; + positions[offset + 6] = positions[3 * 6]; + positions[offset + 7] = positions[3 * 6 + 1]; + positions[offset + 8] = positions[3 * 6 + 2]; + positions[offset + 9] = positions[3 * 2]; + positions[offset + 10] = positions[3 * 2 + 1]; + positions[offset + 11] = positions[3 * 2 + 2]; + + // +y plane + offset += 3 * 4; + positions[offset] = positions[3 * 2]; + positions[offset + 1] = positions[3 * 2 + 1]; + positions[offset + 2] = positions[3 * 2 + 2]; + positions[offset + 3] = positions[3 * 6]; + positions[offset + 4] = positions[3 * 6 + 1]; + positions[offset + 5] = positions[3 * 6 + 2]; + positions[offset + 6] = positions[3 * 7]; + positions[offset + 7] = positions[3 * 7 + 1]; + positions[offset + 8] = positions[3 * 7 + 2]; + positions[offset + 9] = positions[3 * 3]; + positions[offset + 10] = positions[3 * 3 + 1]; + positions[offset + 11] = positions[3 * 3 + 2]; + + if (!drawNearPlane) { + positions = positions.subarray(3 * 4); + } + + var attributes = new GeometryAttributes({ + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : positions + }) + }); + + if (defined(vertexFormat.normal) || defined(vertexFormat.tangent) || defined(vertexFormat.bitangent) || defined(vertexFormat.st)) { + var normals = defined(vertexFormat.normal) ? new Float32Array(3 * 4 * numberOfPlanes) : undefined; + var tangents = defined(vertexFormat.tangent) ? new Float32Array(3 * 4 * numberOfPlanes) : undefined; + var bitangents = defined(vertexFormat.bitangent) ? new Float32Array(3 * 4 * numberOfPlanes) : undefined; + var st = defined(vertexFormat.st) ? new Float32Array(2 * 4 * numberOfPlanes) : undefined; + + var x = scratchXDirection; + var y = scratchYDirection; + var z = scratchZDirection; + + var negativeX = Cartesian3.negate(x, scratchNegativeX); + var negativeY = Cartesian3.negate(y, scratchNegativeY); + var negativeZ = Cartesian3.negate(z, scratchNegativeZ); + + offset = 0; + if (drawNearPlane) { + getAttributes(offset, normals, tangents, bitangents, st, negativeZ, x, y); // near + offset += 3 * 4; + } + getAttributes(offset, normals, tangents, bitangents, st, z, negativeX, y); // far + offset += 3 * 4; + getAttributes(offset, normals, tangents, bitangents, st, negativeX, negativeZ, y); // -x + offset += 3 * 4; + getAttributes(offset, normals, tangents, bitangents, st, negativeY, negativeZ, negativeX); // -y + offset += 3 * 4; + getAttributes(offset, normals, tangents, bitangents, st, x, z, y); // +x + offset += 3 * 4; + getAttributes(offset, normals, tangents, bitangents, st, y, z, negativeX); // +y + + if (defined(normals)) { + attributes.normal = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : normals + }); + } + if (defined(tangents)) { + attributes.tangent = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : tangents + }); + } + if (defined(bitangents)) { + attributes.bitangent = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : bitangents + }); + } + if (defined(st)) { + attributes.st = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 2, + values : st + }); + } + } + + var indices = new Uint16Array(6 * numberOfPlanes); + for (var i = 0; i < numberOfPlanes; ++i) { + var indexOffset = i * 6; + var index = i * 4; + + indices[indexOffset] = index; + indices[indexOffset + 1] = index + 1; + indices[indexOffset + 2] = index + 2; + indices[indexOffset + 3] = index; + indices[indexOffset + 4] = index + 2; + indices[indexOffset + 5] = index + 3; + } + + return new Geometry({ + attributes : attributes, + indices : indices, + primitiveType : PrimitiveType.TRIANGLES, + boundingSphere : BoundingSphere.fromVertices(positions) + }); + }; + + return FrustumGeometry; +}); diff --git a/Source/Core/FrustumOutlineGeometry.js b/Source/Core/FrustumOutlineGeometry.js new file mode 100644 index 000000000000..2740e2f98547 --- /dev/null +++ b/Source/Core/FrustumOutlineGeometry.js @@ -0,0 +1,259 @@ +define([ + './BoundingSphere', + './Cartesian3', + './Cartesian4', + './Check', + './ComponentDatatype', + './defaultValue', + './defined', + './FrustumGeometry', + './Geometry', + './GeometryAttribute', + './GeometryAttributes', + './Matrix3', + './Matrix4', + './OrthographicFrustum', + './PerspectiveFrustum', + './PrimitiveType', + './Quaternion' + ], function( + BoundingSphere, + Cartesian3, + Cartesian4, + Check, + ComponentDatatype, + defaultValue, + defined, + FrustumGeometry, + Geometry, + GeometryAttribute, + GeometryAttributes, + Matrix3, + Matrix4, + OrthographicFrustum, + PerspectiveFrustum, + PrimitiveType, + Quaternion) { + 'use strict'; + + var PERSPECTIVE = 0; + var ORTHOGRAPHIC = 1; + + /** + * A description of the outline of a frustum with the given the origin and orientation. + * + * @alias FrustumOutlineGeometry + * @constructor + * + * @param {Object} options Object with the following properties: + * @param {PerspectiveFrustum|OrthographicFrustum} options.frustum The frustum. + * @param {Cartesian3} options.origin The origin of the frustum. + * @param {Quaternion} options.orientation The orientation of the frustum. + */ + function FrustumOutlineGeometry(options) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object('options', options); + Check.typeOf.object('options.frustum', options.frustum); + Check.typeOf.object('options.origin', options.origin); + Check.typeOf.object('options.orientation', options.orientation); + //>>includeEnd('debug'); + + var frustum = options.frustum; + var orientation = options.orientation; + var origin = options.origin; + + // This is private because it is used by DebugCameraPrimitive to draw a multi-frustum by + // creating multiple FrustumOutlineGeometrys. This way the near plane of one frustum doesn't overlap + // the far plane of another. + var drawNearPlane = defaultValue(options._drawNearPlane, true); + + var frustumType; + var frustumPackedLength; + if (frustum instanceof PerspectiveFrustum) { + frustumType = PERSPECTIVE; + frustumPackedLength = PerspectiveFrustum.packedLength; + } else if (frustum instanceof OrthographicFrustum) { + frustumType = ORTHOGRAPHIC; + frustumPackedLength = OrthographicFrustum.packedLength; + } + + this._frustumType = frustumType; + this._frustum = frustum.clone(); + this._origin = Cartesian3.clone(origin); + this._orientation = Quaternion.clone(orientation); + this._drawNearPlane = drawNearPlane; + this._workerName = 'createFrustumOutlineGeometry'; + + /** + * The number of elements used to pack the object into an array. + * @type {Number} + */ + this.packedLength = 2 + frustumPackedLength + Cartesian3.packedLength + Quaternion.packedLength; + } + + /** + * Stores the provided instance into the provided array. + * + * @param {FrustumOutlineGeometry} value The value to pack. + * @param {Number[]} array The array to pack into. + * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements. + * + * @returns {Number[]} The array that was packed into + */ + FrustumOutlineGeometry.pack = function(value, array, startingIndex) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object('value', value); + Check.defined('array', array); + //>>includeEnd('debug'); + + startingIndex = defaultValue(startingIndex, 0); + + var frustumType = value._frustumType; + var frustum = value._frustum; + + array[startingIndex++] = frustumType; + + if (frustumType === PERSPECTIVE) { + PerspectiveFrustum.pack(frustum, array, startingIndex); + startingIndex += PerspectiveFrustum.packedLength; + } else { + OrthographicFrustum.pack(frustum, array, startingIndex); + startingIndex += OrthographicFrustum.packedLength; + } + + Cartesian3.pack(value._origin, array, startingIndex); + startingIndex += Cartesian3.packedLength; + Quaternion.pack(value._orientation, array, startingIndex); + startingIndex += Quaternion.packedLength; + array[startingIndex] = value._drawNearPlane ? 1.0 : 0.0; + + return array; + }; + + var scratchPackPerspective = new PerspectiveFrustum(); + var scratchPackOrthographic = new OrthographicFrustum(); + var scratchPackQuaternion = new Quaternion(); + var scratchPackorigin = new Cartesian3(); + + /** + * Retrieves an instance from a packed array. + * + * @param {Number[]} array The packed array. + * @param {Number} [startingIndex=0] The starting index of the element to be unpacked. + * @param {FrustumOutlineGeometry} [result] The object into which to store the result. + */ + FrustumOutlineGeometry.unpack = function(array, startingIndex, result) { + //>>includeStart('debug', pragmas.debug); + Check.defined('array', array); + //>>includeEnd('debug'); + + startingIndex = defaultValue(startingIndex, 0); + + var frustumType = array[startingIndex++]; + + var frustum; + if (frustumType === PERSPECTIVE) { + frustum = PerspectiveFrustum.unpack(array, startingIndex, scratchPackPerspective); + startingIndex += PerspectiveFrustum.packedLength; + } else { + frustum = OrthographicFrustum.unpack(array, startingIndex, scratchPackOrthographic); + startingIndex += OrthographicFrustum.packedLength; + } + + var origin = Cartesian3.unpack(array, startingIndex, scratchPackorigin); + startingIndex += Cartesian3.packedLength; + var orientation = Quaternion.unpack(array, startingIndex, scratchPackQuaternion); + startingIndex += Quaternion.packedLength; + var drawNearPlane = array[startingIndex] === 1.0; + + if (!defined(result)) { + return new FrustumOutlineGeometry({ + frustum : frustum, + origin : origin, + orientation : orientation, + _drawNearPlane : drawNearPlane + }); + } + + var frustumResult = frustumType === result._frustumType ? result._frustum : undefined; + result._frustum = frustum.clone(frustumResult); + + result._frustumType = frustumType; + result._origin = Cartesian3.clone(origin, result._origin); + result._orientation = Quaternion.clone(orientation, result._orientation); + result._drawNearPlane = drawNearPlane; + + return result; + }; + + /** + * Computes the geometric representation of a frustum outline, including its vertices, indices, and a bounding sphere. + * + * @param {FrustumOutlineGeometry} frustumGeometry A description of the frustum. + * @returns {Geometry|undefined} The computed vertices and indices. + */ + FrustumOutlineGeometry.createGeometry = function(frustumGeometry) { + var frustumType = frustumGeometry._frustumType; + var frustum = frustumGeometry._frustum; + var origin = frustumGeometry._origin; + var orientation = frustumGeometry._orientation; + var drawNearPlane = frustumGeometry._drawNearPlane; + + var positions = new Float64Array(3 * 4 * 2); + FrustumGeometry._computeNearFarPlanes(origin, orientation, frustumType, frustum, positions); + + var attributes = new GeometryAttributes({ + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : positions + }) + }); + + var offset; + var index; + + var numberOfPlanes = drawNearPlane ? 2 : 1; + var indices = new Uint16Array(8 * (numberOfPlanes + 1)); + + // Build the near/far planes + var i = drawNearPlane ? 0 : 1; + for (; i < 2; ++i) { + offset = drawNearPlane ? i * 8 : 0; + index = i * 4; + + indices[offset] = index; + indices[offset + 1] = index + 1; + indices[offset + 2] = index + 1; + indices[offset + 3] = index + 2; + indices[offset + 4] = index + 2; + indices[offset + 5] = index + 3; + indices[offset + 6] = index + 3; + indices[offset + 7] = index; + } + + // Build the sides of the frustums + for (i = 0; i < 2; ++i) { + offset = (numberOfPlanes + i) * 8; + index = i * 4; + + indices[offset] = index; + indices[offset + 1] = index + 4; + indices[offset + 2] = index + 1; + indices[offset + 3] = index + 5; + indices[offset + 4] = index + 2; + indices[offset + 5] = index + 6; + indices[offset + 6] = index + 3; + indices[offset + 7] = index + 7; + } + + return new Geometry({ + attributes : attributes, + indices : indices, + primitiveType : PrimitiveType.LINES, + boundingSphere : BoundingSphere.fromVertices(positions) + }); + }; + + return FrustumOutlineGeometry; +}); diff --git a/Source/Core/OrthographicFrustum.js b/Source/Core/OrthographicFrustum.js index 0c661db836f6..b3377b99fde1 100644 --- a/Source/Core/OrthographicFrustum.js +++ b/Source/Core/OrthographicFrustum.js @@ -1,9 +1,13 @@ define([ + './Check', + './defaultValue', './defined', './defineProperties', './DeveloperError', './OrthographicOffCenterFrustum' ], function( + Check, + defaultValue, defined, defineProperties, DeveloperError, @@ -19,6 +23,12 @@ define([ * @alias OrthographicFrustum * @constructor * + * @param {Object} [options] An object with the following properties: + * @param {Number} [options.width] The width of the frustum in meters. + * @param {Number} [options.aspectRatio] The aspect ratio of the frustum's width to it's height. + * @param {Number} [options.near=1.0] The distance of the near plane. + * @param {Number} [options.far=500000000.0] The distance of the far plane. + * * @example * var maxRadii = ellipsoid.maximumRadius; * @@ -26,7 +36,9 @@ define([ * frustum.near = 0.01 * maxRadii; * frustum.far = 50.0 * maxRadii; */ - function OrthographicFrustum() { + function OrthographicFrustum(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + this._offCenterFrustum = new OrthographicOffCenterFrustum(); /** @@ -34,7 +46,7 @@ define([ * @type {Number} * @default undefined */ - this.width = undefined; + this.width = options.width; this._width = undefined; /** @@ -42,7 +54,7 @@ define([ * @type {Number} * @default undefined */ - this.aspectRatio = undefined; + this.aspectRatio = options.aspectRatio; this._aspectRatio = undefined; /** @@ -50,7 +62,7 @@ define([ * @type {Number} * @default 1.0 */ - this.near = 1.0; + this.near = defaultValue(options.near, 1.0); this._near = this.near; /** @@ -58,10 +70,68 @@ define([ * @type {Number} * @default 500000000.0; */ - this.far = 500000000.0; + this.far = defaultValue(options.far, 500000000.0); this._far = this.far; } + /** + * The number of elements used to pack the object into an array. + * @type {Number} + */ + OrthographicFrustum.packedLength = 4; + + /** + * Stores the provided instance into the provided array. + * + * @param {OrthographicFrustum} value The value to pack. + * @param {Number[]} array The array to pack into. + * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements. + * + * @returns {Number[]} The array that was packed into + */ + OrthographicFrustum.pack = function(value, array, startingIndex) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object('value', value); + Check.defined('array', array); + //>>includeEnd('debug'); + + startingIndex = defaultValue(startingIndex, 0); + + array[startingIndex++] = value.width; + array[startingIndex++] = value.aspectRatio; + array[startingIndex++] = value.near; + array[startingIndex] = value.far; + + return array; + }; + + /** + * Retrieves an instance from a packed array. + * + * @param {Number[]} array The packed array. + * @param {Number} [startingIndex=0] The starting index of the element to be unpacked. + * @param {OrthographicFrustum} [result] The object into which to store the result. + * @returns {OrthographicFrustum} The modified result parameter or a new OrthographicFrustum instance if one was not provided. + */ + OrthographicFrustum.unpack = function(array, startingIndex, result) { + //>>includeStart('debug', pragmas.debug); + Check.defined('array', array); + //>>includeEnd('debug'); + + startingIndex = defaultValue(startingIndex, 0); + + if (!defined(result)) { + result = new OrthographicFrustum(); + } + + result.width = array[startingIndex++]; + result.aspectRatio = array[startingIndex++]; + result.near = array[startingIndex++]; + result.far = array[startingIndex]; + + return result; + }; + function update(frustum) { //>>includeStart('debug', pragmas.debug); if (!defined(frustum.width) || !defined(frustum.aspectRatio) || !defined(frustum.near) || !defined(frustum.far)) { diff --git a/Source/Core/OrthographicOffCenterFrustum.js b/Source/Core/OrthographicOffCenterFrustum.js index dff8404b5a3d..63dd0c40dfbf 100644 --- a/Source/Core/OrthographicOffCenterFrustum.js +++ b/Source/Core/OrthographicOffCenterFrustum.js @@ -2,6 +2,7 @@ define([ './Cartesian3', './Cartesian4', './CullingVolume', + './defaultValue', './defined', './defineProperties', './DeveloperError', @@ -10,6 +11,7 @@ define([ Cartesian3, Cartesian4, CullingVolume, + defaultValue, defined, defineProperties, DeveloperError, @@ -25,6 +27,14 @@ define([ * @alias OrthographicOffCenterFrustum * @constructor * + * @param {Object} [options] An object with the following properties: + * @param {Number} [options.left] The left clipping plane distance. + * @param {Number} [options.right] The right clipping plane distance. + * @param {Number} [options.top] The top clipping plane distance. + * @param {Number} [options.bottom] The bottom clipping plane distance. + * @param {Number} [options.near=1.0] The near clipping plane distance. + * @param {Number} [options.far=500000000.0] The far clipping plane distance. + * * @example * var maxRadii = ellipsoid.maximumRadius; * @@ -36,13 +46,15 @@ define([ * frustum.near = 0.01 * maxRadii; * frustum.far = 50.0 * maxRadii; */ - function OrthographicOffCenterFrustum() { + function OrthographicOffCenterFrustum(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + /** * The left clipping plane. * @type {Number} * @default undefined */ - this.left = undefined; + this.left = options.left; this._left = undefined; /** @@ -50,7 +62,7 @@ define([ * @type {Number} * @default undefined */ - this.right = undefined; + this.right = options.right; this._right = undefined; /** @@ -58,7 +70,7 @@ define([ * @type {Number} * @default undefined */ - this.top = undefined; + this.top = options.top; this._top = undefined; /** @@ -66,7 +78,7 @@ define([ * @type {Number} * @default undefined */ - this.bottom = undefined; + this.bottom = options.bottom; this._bottom = undefined; /** @@ -74,7 +86,7 @@ define([ * @type {Number} * @default 1.0 */ - this.near = 1.0; + this.near = defaultValue(options.near, 1.0); this._near = this.near; /** @@ -82,7 +94,7 @@ define([ * @type {Number} * @default 500000000.0; */ - this.far = 500000000.0; + this.far = defaultValue(options.far, 500000000.0); this._far = this.far; this._cullingVolume = new CullingVolume(); diff --git a/Source/Core/PerspectiveFrustum.js b/Source/Core/PerspectiveFrustum.js index b09c1f2d4da6..5926554f69dc 100644 --- a/Source/Core/PerspectiveFrustum.js +++ b/Source/Core/PerspectiveFrustum.js @@ -1,9 +1,13 @@ define([ + './Check', + './defaultValue', './defined', './defineProperties', './DeveloperError', './PerspectiveOffCenterFrustum' ], function( + Check, + defaultValue, defined, defineProperties, DeveloperError, @@ -19,17 +23,27 @@ define([ * @alias PerspectiveFrustum * @constructor * + * @param {Object} [options] An object with the following properties: + * @param {Number} [options.fov] The angle of the field of view (FOV), in radians. + * @param {Number} [options.aspectRatio] The aspect ratio of the frustum's width to it's height. + * @param {Number} [options.near=1.0] The distance of the near plane. + * @param {Number} [options.far=500000000.0] The distance of the far plane. + * @param {Number} [options.xOffset=0.0] The offset in the x direction. + * @param {Number} [options.yOffset=0.0] The offset in the y direction. * * @example - * var frustum = new Cesium.PerspectiveFrustum(); - * frustum.aspectRatio = canvas.clientWidth / canvas.clientHeight; - * frustum.fov = Cesium.Math.PI_OVER_THREE; - * frustum.near = 1.0; - * frustum.far = 2.0; + * var frustum = new Cesium.PerspectiveFrustum({ + * fov : Cesium.Math.PI_OVER_THREE, + * aspectRatio : canvas.clientWidth / canvas.clientHeight + * near : 1.0, + * far : 1000.0 + * }); * * @see PerspectiveOffCenterFrustum */ - function PerspectiveFrustum() { + function PerspectiveFrustum(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + this._offCenterFrustum = new PerspectiveOffCenterFrustum(); /** @@ -39,7 +53,7 @@ define([ * @type {Number} * @default undefined */ - this.fov = undefined; + this.fov = options.fov; this._fov = undefined; this._fovy = undefined; @@ -50,7 +64,7 @@ define([ * @type {Number} * @default undefined */ - this.aspectRatio = undefined; + this.aspectRatio = options.aspectRatio; this._aspectRatio = undefined; /** @@ -58,7 +72,7 @@ define([ * @type {Number} * @default 1.0 */ - this.near = 1.0; + this.near = defaultValue(options.near, 1.0); this._near = this.near; /** @@ -66,7 +80,7 @@ define([ * @type {Number} * @default 500000000.0 */ - this.far = 500000000.0; + this.far = defaultValue(options.far, 500000000.0); this._far = this.far; /** @@ -74,7 +88,7 @@ define([ * @type {Number} * @default 0.0 */ - this.xOffset = 0.0; + this.xOffset = defaultValue(options.xOffset, 0.0); this._xOffset = this.xOffset; /** @@ -82,10 +96,72 @@ define([ * @type {Number} * @default 0.0 */ - this.yOffset = 0.0; + this.yOffset = defaultValue(options.yOffset, 0.0); this._yOffset = this.yOffset; } + /** + * The number of elements used to pack the object into an array. + * @type {Number} + */ + PerspectiveFrustum.packedLength = 6; + + /** + * Stores the provided instance into the provided array. + * + * @param {PerspectiveFrustum} value The value to pack. + * @param {Number[]} array The array to pack into. + * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements. + * + * @returns {Number[]} The array that was packed into + */ + PerspectiveFrustum.pack = function(value, array, startingIndex) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object('value', value); + Check.defined('array', array); + //>>includeEnd('debug'); + + startingIndex = defaultValue(startingIndex, 0); + + array[startingIndex++] = value.fov; + array[startingIndex++] = value.aspectRatio; + array[startingIndex++] = value.near; + array[startingIndex++] = value.far; + array[startingIndex++] = value.xOffset; + array[startingIndex] = value.yOffset; + + return array; + }; + + /** + * Retrieves an instance from a packed array. + * + * @param {Number[]} array The packed array. + * @param {Number} [startingIndex=0] The starting index of the element to be unpacked. + * @param {PerspectiveFrustum} [result] The object into which to store the result. + * @returns {PerspectiveFrustum} The modified result parameter or a new PerspectiveFrustum instance if one was not provided. + */ + PerspectiveFrustum.unpack = function(array, startingIndex, result) { + //>>includeStart('debug', pragmas.debug); + Check.defined('array', array); + //>>includeEnd('debug'); + + startingIndex = defaultValue(startingIndex, 0); + + if (!defined(result)) { + result = new PerspectiveFrustum(); + } + + result.fov = array[startingIndex++]; + result.aspectRatio = array[startingIndex++]; + result.near = array[startingIndex++]; + result.far = array[startingIndex++]; + result.xOffset = array[startingIndex++]; + result.yOffset = array[startingIndex]; + + return result; + }; + function update(frustum) { //>>includeStart('debug', pragmas.debug); if (!defined(frustum.fov) || !defined(frustum.aspectRatio) || !defined(frustum.near) || !defined(frustum.far)) { diff --git a/Source/Core/PerspectiveOffCenterFrustum.js b/Source/Core/PerspectiveOffCenterFrustum.js index 6792faa4e98f..7b031d85e77c 100644 --- a/Source/Core/PerspectiveOffCenterFrustum.js +++ b/Source/Core/PerspectiveOffCenterFrustum.js @@ -2,6 +2,7 @@ define([ './Cartesian3', './Cartesian4', './CullingVolume', + './defaultValue', './defined', './defineProperties', './DeveloperError', @@ -10,6 +11,7 @@ define([ Cartesian3, Cartesian4, CullingVolume, + defaultValue, defined, defineProperties, DeveloperError, @@ -25,25 +27,35 @@ define([ * @alias PerspectiveOffCenterFrustum * @constructor * + * @param {Object} [options] An object with the following properties: + * @param {Number} [options.left] The left clipping plane distance. + * @param {Number} [options.right] The right clipping plane distance. + * @param {Number} [options.top] The top clipping plane distance. + * @param {Number} [options.bottom] The bottom clipping plane distance. + * @param {Number} [options.near=1.0] The near clipping plane distance. + * @param {Number} [options.far=500000000.0] The far clipping plane distance. * * @example - * var frustum = new Cesium.PerspectiveOffCenterFrustum(); - * frustum.right = 1.0; - * frustum.left = -1.0; - * frustum.top = 1.0; - * frustum.bottom = -1.0; - * frustum.near = 1.0; - * frustum.far = 2.0; + * var frustum = new Cesium.PerspectiveOffCenterFrustum({ + * left : -1.0, + * right : 1.0, + * top : 1.0, + * bottom : -1.0, + * near : 1.0, + * far : 100.0 + * }); * * @see PerspectiveFrustum */ - function PerspectiveOffCenterFrustum() { + function PerspectiveOffCenterFrustum(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + /** * Defines the left clipping plane. * @type {Number} * @default undefined */ - this.left = undefined; + this.left = options.left; this._left = undefined; /** @@ -51,7 +63,7 @@ define([ * @type {Number} * @default undefined */ - this.right = undefined; + this.right = options.right; this._right = undefined; /** @@ -59,7 +71,7 @@ define([ * @type {Number} * @default undefined */ - this.top = undefined; + this.top = options.top; this._top = undefined; /** @@ -67,7 +79,7 @@ define([ * @type {Number} * @default undefined */ - this.bottom = undefined; + this.bottom = options.bottom; this._bottom = undefined; /** @@ -75,7 +87,7 @@ define([ * @type {Number} * @default 1.0 */ - this.near = 1.0; + this.near = defaultValue(options.near, 1.0); this._near = this.near; /** @@ -83,7 +95,7 @@ define([ * @type {Number} * @default 500000000.0 */ - this.far = 500000000.0; + this.far = defaultValue(options.far, 500000000.0); this._far = this.far; this._cullingVolume = new CullingVolume(); diff --git a/Source/Core/VertexFormat.js b/Source/Core/VertexFormat.js index 64b93c9dced7..55627ea7364e 100644 --- a/Source/Core/VertexFormat.js +++ b/Source/Core/VertexFormat.js @@ -246,7 +246,7 @@ define([ array[startingIndex++] = value.st ? 1.0 : 0.0; array[startingIndex++] = value.tangent ? 1.0 : 0.0; array[startingIndex++] = value.bitangent ? 1.0 : 0.0; - array[startingIndex++] = value.color ? 1.0 : 0.0; + array[startingIndex] = value.color ? 1.0 : 0.0; return array; }; @@ -277,7 +277,7 @@ define([ result.st = array[startingIndex++] === 1.0; result.tangent = array[startingIndex++] === 1.0; result.bitangent = array[startingIndex++] === 1.0; - result.color = array[startingIndex++] === 1.0; + result.color = array[startingIndex] === 1.0; return result; }; diff --git a/Source/Core/loadKTX.js b/Source/Core/loadKTX.js index 39f90213ed19..f017a0ba1d05 100644 --- a/Source/Core/loadKTX.js +++ b/Source/Core/loadKTX.js @@ -189,9 +189,6 @@ define([ if (glFormat !== 0) { throw new RuntimeError('glFormat must be zero when the texture is compressed.'); } - if (numberOfMipmapLevels === 0) { - throw new RuntimeError('Generating mipmaps for a compressed texture is unsupported.'); - } } else if (glBaseInternalFormat !== glFormat) { throw new RuntimeError('The base internal format must be the same as the format for uncompressed textures.'); } @@ -208,9 +205,11 @@ define([ } // Only use the level 0 mipmap - if (PixelFormat.isCompressedFormat(glInternalFormat) && numberOfMipmapLevels > 1) { - var levelSize = PixelFormat.compressedTextureSizeInBytes(glInternalFormat, pixelWidth, pixelHeight); - texture = texture.slice(0, levelSize); + if (numberOfMipmapLevels > 1) { + var levelSize = PixelFormat.isCompressedFormat(glInternalFormat) ? + PixelFormat.compressedTextureSizeInBytes(glInternalFormat, pixelWidth, pixelHeight) : + PixelFormat.textureSizeInBytes(glInternalFormat, pixelWidth, pixelHeight); + texture = new Uint8Array(texture.buffer, texture.byteOffset, levelSize); } return new CompressedTextureBuffer(glInternalFormat, pixelWidth, pixelHeight, texture); diff --git a/Source/DataSources/BoxGeometryUpdater.js b/Source/DataSources/BoxGeometryUpdater.js index e216bc3cda29..638274b91e2a 100644 --- a/Source/DataSources/BoxGeometryUpdater.js +++ b/Source/DataSources/BoxGeometryUpdater.js @@ -362,7 +362,7 @@ define([ return new GeometryInstance({ id : entity, geometry : BoxGeometry.fromDimensions(this._options), - modelMatrix : entity._getModelMatrix(Iso8601.MINIMUM_VALUE), + modelMatrix : entity.computeModelMatrix(Iso8601.MINIMUM_VALUE), attributes : attributes }); }; @@ -394,7 +394,7 @@ define([ return new GeometryInstance({ id : entity, geometry : BoxOutlineGeometry.fromDimensions(this._options), - modelMatrix : entity._getModelMatrix(Iso8601.MINIMUM_VALUE), + modelMatrix : entity.computeModelMatrix(Iso8601.MINIMUM_VALUE), attributes : { show : new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time)), color : ColorGeometryInstanceAttribute.fromColor(outlineColor), @@ -555,7 +555,7 @@ define([ } var options = this._options; - var modelMatrix = entity._getModelMatrix(time); + var modelMatrix = entity.computeModelMatrix(time); var dimensions = Property.getValueOrUndefined(box.dimensions, time, options.dimensions); if (!defined(modelMatrix) || !defined(dimensions)) { return; diff --git a/Source/DataSources/CylinderGeometryUpdater.js b/Source/DataSources/CylinderGeometryUpdater.js index 45fa1736199a..426e8f4b0dd3 100644 --- a/Source/DataSources/CylinderGeometryUpdater.js +++ b/Source/DataSources/CylinderGeometryUpdater.js @@ -367,7 +367,7 @@ define([ return new GeometryInstance({ id : entity, geometry : new CylinderGeometry(this._options), - modelMatrix : entity._getModelMatrix(Iso8601.MINIMUM_VALUE), + modelMatrix : entity.computeModelMatrix(Iso8601.MINIMUM_VALUE), attributes : attributes }); }; @@ -399,7 +399,7 @@ define([ return new GeometryInstance({ id : entity, geometry : new CylinderOutlineGeometry(this._options), - modelMatrix : entity._getModelMatrix(Iso8601.MINIMUM_VALUE), + modelMatrix : entity.computeModelMatrix(Iso8601.MINIMUM_VALUE), attributes : { show : new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time)), color : ColorGeometryInstanceAttribute.fromColor(outlineColor), @@ -573,7 +573,7 @@ define([ } var options = this._options; - var modelMatrix = entity._getModelMatrix(time); + var modelMatrix = entity.computeModelMatrix(time); var length = Property.getValueOrUndefined(cylinder.length, time); var topRadius = Property.getValueOrUndefined(cylinder.topRadius, time); var bottomRadius = Property.getValueOrUndefined(cylinder.bottomRadius, time); diff --git a/Source/DataSources/EllipsoidGeometryUpdater.js b/Source/DataSources/EllipsoidGeometryUpdater.js index 00f93ca8f1fc..5334661ca1d7 100644 --- a/Source/DataSources/EllipsoidGeometryUpdater.js +++ b/Source/DataSources/EllipsoidGeometryUpdater.js @@ -374,7 +374,7 @@ define([ return new GeometryInstance({ id : entity, geometry : new EllipsoidGeometry(this._options), - modelMatrix : entity._getModelMatrix(Iso8601.MINIMUM_VALUE), + modelMatrix : entity.computeModelMatrix(Iso8601.MINIMUM_VALUE), attributes : attributes }); }; @@ -407,7 +407,7 @@ define([ return new GeometryInstance({ id : entity, geometry : new EllipsoidOutlineGeometry(this._options), - modelMatrix : entity._getModelMatrix(Iso8601.MINIMUM_VALUE), + modelMatrix : entity.computeModelMatrix(Iso8601.MINIMUM_VALUE), attributes : { show : new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time)), color : ColorGeometryInstanceAttribute.fromColor(outlineColor), @@ -589,7 +589,7 @@ define([ } var radii = Property.getValueOrUndefined(ellipsoid.radii, time, radiiScratch); - var modelMatrix = entity._getModelMatrix(time, this._modelMatrix); + var modelMatrix = entity.computeModelMatrix(time, this._modelMatrix); if (!defined(modelMatrix) || !defined(radii)) { if (defined(this._primitive)) { this._primitive.show = false; diff --git a/Source/DataSources/Entity.js b/Source/DataSources/Entity.js index abc2823c379a..42c634f3b1b5 100644 --- a/Source/DataSources/Entity.js +++ b/Source/DataSources/Entity.js @@ -1,5 +1,6 @@ define([ '../Core/Cartesian3', + '../Core/Check', '../Core/createGuid', '../Core/defaultValue', '../Core/defined', @@ -32,6 +33,7 @@ define([ './WallGraphics' ], function( Cartesian3, + Check, createGuid, defaultValue, defined, @@ -581,9 +583,16 @@ define([ var orientationScratch = new Quaternion(); /** - * @private + * Computes the model matrix for the entity's transform at specified time. Returns undefined if orientation or position + * are undefined. + * + * @param {JulianDate} time The time to retrieve model matrix for. + * @param {Matrix4} [result] The object onto which to store the result. + * + * @returns {Matrix4} The modified result parameter or a new Matrix4 instance if one was not provided. Result is undefined if position or orientation are undefined. */ - Entity.prototype._getModelMatrix = function(time, result) { + Entity.prototype.computeModelMatrix = function(time, result) { + Check.typeOf.object('time', time); var position = Property.getValueOrUndefined(this._position, time, positionScratch); if (!defined(position)) { return undefined; diff --git a/Source/DataSources/GeoJsonDataSource.js b/Source/DataSources/GeoJsonDataSource.js index 2123486fcb1c..854760323b76 100644 --- a/Source/DataSources/GeoJsonDataSource.js +++ b/Source/DataSources/GeoJsonDataSource.js @@ -683,13 +683,19 @@ define([ defineProperties(GeoJsonDataSource.prototype, { /** - * Gets a human-readable name for this instance. + * Gets or sets a human-readable name for this instance. * @memberof GeoJsonDataSource.prototype * @type {String} */ name : { get : function() { return this._name; + }, + set : function(value) { + if (this._name !== value) { + this._name = value; + this._changed.raiseEvent(this); + } } }, /** diff --git a/Source/DataSources/KmlDataSource.js b/Source/DataSources/KmlDataSource.js index ded2f89e5fcc..c236e71955aa 100644 --- a/Source/DataSources/KmlDataSource.js +++ b/Source/DataSources/KmlDataSource.js @@ -2344,7 +2344,7 @@ define([ defineProperties(KmlDataSource.prototype, { /** - * Gets a human-readable name for this instance. + * Gets or sets a human-readable name for this instance. * This will be automatically be set to the KML document name on load. * @memberof KmlDataSource.prototype * @type {String} @@ -2352,6 +2352,12 @@ define([ name : { get : function() { return this._name; + }, + set : function(value) { + if (this._name !== value) { + this._name = value; + this._changed.raiseEvent(this); + } } }, /** diff --git a/Source/DataSources/ModelVisualizer.js b/Source/DataSources/ModelVisualizer.js index fd24f13cc280..bf6e48d2dd2f 100644 --- a/Source/DataSources/ModelVisualizer.js +++ b/Source/DataSources/ModelVisualizer.js @@ -100,7 +100,7 @@ define([ var modelMatrix; if (show) { - modelMatrix = entity._getModelMatrix(time, modelMatrixScratch); + modelMatrix = entity.computeModelMatrix(time, modelMatrixScratch); uri = Property.getValueOrUndefined(modelGraphics._uri, time); show = defined(modelMatrix) && defined(uri); } diff --git a/Source/Renderer/Context.js b/Source/Renderer/Context.js index 7eff7dc3fa52..5ab984919331 100644 --- a/Source/Renderer/Context.js +++ b/Source/Renderer/Context.js @@ -269,12 +269,15 @@ define([ // Query and initialize extensions this._standardDerivatives = !!getExtension(gl, ['OES_standard_derivatives']); + this._blendMinmax = !!getExtension(gl, ['EXT_blend_minmax']); this._elementIndexUint = !!getExtension(gl, ['OES_element_index_uint']); this._depthTexture = !!getExtension(gl, ['WEBGL_depth_texture', 'WEBKIT_WEBGL_depth_texture']); this._textureFloat = !!getExtension(gl, ['OES_texture_float']); this._fragDepth = !!getExtension(gl, ['EXT_frag_depth']); this._debugShaders = getExtension(gl, ['WEBGL_debug_shaders']); + this._colorBufferFloat = this._webgl2 && !!getExtension(gl, ['EXT_color_buffer_float']); + this._s3tc = !!getExtension(gl, ['WEBGL_compressed_texture_s3tc', 'MOZ_WEBGL_compressed_texture_s3tc', 'WEBKIT_WEBGL_compressed_texture_s3tc']); this._pvrtc = !!getExtension(gl, ['WEBGL_compressed_texture_pvrtc', 'WEBKIT_WEBGL_compressed_texture_pvrtc']); this._etc1 = !!getExtension(gl, ['WEBGL_compressed_texture_etc1']); @@ -483,7 +486,21 @@ define([ */ standardDerivatives : { get : function() { - return this._standardDerivatives; + return this._standardDerivatives || this._webgl2; + } + }, + + /** + * true if the EXT_blend_minmax extension is supported. This + * extension extends blending capabilities by adding two new blend equations: + * the minimum or maximum color components of the source and destination colors. + * @memberof Context.prototype + * @type {Boolean} + * @see {@link https://www.khronos.org/registry/webgl/extensions/EXT_blend_minmax/} + */ + blendMinmax : { + get : function() { + return this._blendMinmax || this._webgl2; } }, @@ -510,7 +527,7 @@ define([ */ depthTexture : { get : function() { - return this._depthTexture; + return this._depthTexture || this._webgl2; } }, @@ -523,7 +540,7 @@ define([ */ floatingPointTexture : { get : function() { - return this._textureFloat; + return this._textureFloat || this._colorBufferFloat; } }, @@ -597,7 +614,7 @@ define([ */ fragmentDepth : { get : function() { - return this._fragDepth; + return this._fragDepth || this._webgl2; } }, @@ -614,6 +631,20 @@ define([ } }, + /** + * true if the EXT_color_buffer_float extension is supported. This + * extension makes the formats gl.R16F, gl.RG16F, gl.RGBA16F, gl.R32F, gl.RG32F, + * gl.RGBA32F, gl.R11F_G11F_B10F color renderable. + * @memberof Context.prototype + * @type {Boolean} + * @see {@link https://www.khronos.org/registry/webgl/extensions/EXT_color_buffer_float/} + */ + colorBufferFloat : { + get : function() { + return this._colorBufferFloat; + } + }, + /** * true if the WEBGL_draw_buffers extension is supported. This * extensions provides support for multiple render targets. The framebuffer object can have mutiple @@ -1076,7 +1107,8 @@ define([ }), uniformMap : overrides.uniformMap, owner : overrides.owner, - framebuffer : overrides.framebuffer + framebuffer : overrides.framebuffer, + pass : overrides.pass }); }; diff --git a/Source/Renderer/RenderState.js b/Source/Renderer/RenderState.js index 3b30bebbd290..07ab7633fe12 100644 --- a/Source/Renderer/RenderState.js +++ b/Source/Renderer/RenderState.js @@ -21,7 +21,9 @@ define([ function validateBlendEquation(blendEquation) { return ((blendEquation === WebGLConstants.FUNC_ADD) || (blendEquation === WebGLConstants.FUNC_SUBTRACT) || - (blendEquation === WebGLConstants.FUNC_REVERSE_SUBTRACT)); + (blendEquation === WebGLConstants.FUNC_REVERSE_SUBTRACT) || + (blendEquation === WebGLConstants.MIN) || + (blendEquation === WebGLConstants.MAX)); } function validateBlendFunction(blendFunction) { diff --git a/Source/Renderer/ShaderCache.js b/Source/Renderer/ShaderCache.js index d8635213de6d..a2a9e86d7358 100644 --- a/Source/Renderer/ShaderCache.js +++ b/Source/Renderer/ShaderCache.js @@ -96,8 +96,8 @@ define([ }); } - var vertexShaderText = vertexShaderSource.createCombinedVertexShader(); - var fragmentShaderText = fragmentShaderSource.createCombinedFragmentShader(); + var vertexShaderText = vertexShaderSource.createCombinedVertexShader(this._context); + var fragmentShaderText = fragmentShaderSource.createCombinedFragmentShader(this._context); var keyword = vertexShaderText + fragmentShaderText + JSON.stringify(attributeLocations); var cachedShader; @@ -169,10 +169,11 @@ define([ }); } - var vertexShaderText = vertexShaderSource.createCombinedVertexShader(); - var fragmentShaderText = fragmentShaderSource.createCombinedFragmentShader(); - var context = this._context; + + var vertexShaderText = vertexShaderSource.createCombinedVertexShader(context); + var fragmentShaderText = fragmentShaderSource.createCombinedFragmentShader(context); + var derivedShaderProgram = new ShaderProgram({ gl : context._gl, logShaderCompilation : context.logShaderCompilation, diff --git a/Source/Renderer/ShaderSource.js b/Source/Renderer/ShaderSource.js index 36bb4637145b..b1f4684b93df 100644 --- a/Source/Renderer/ShaderSource.js +++ b/Source/Renderer/ShaderSource.js @@ -2,12 +2,14 @@ define([ '../Core/defaultValue', '../Core/defined', '../Core/DeveloperError', + '../Renderer/modernizeShader', '../Shaders/Builtin/CzmBuiltins', './AutomaticUniforms' ], function( defaultValue, defined, DeveloperError, + modernizeShader, CzmBuiltins, AutomaticUniforms) { 'use strict'; @@ -151,7 +153,7 @@ define([ return builtinsSource.replace(root.glslSource, ''); } - function combineShader(shaderSource, isFragmentShader) { + function combineShader(shaderSource, isFragmentShader, context) { var i; var length; @@ -186,6 +188,17 @@ define([ return '\n'; }); + // Extract shader extensions from sources + var extensions = []; + combinedSources = combinedSources.replace(/#extension.*\n/gm, function(match) { + // Extract extension to put at the top + extensions.push(match); + + // Replace original #extension directive with a new line so the line numbers + // are not off by one. + return '\n'; + }); + // Remove precision qualifier combinedSources = combinedSources.replace(/precision\s(lowp|mediump|highp)\s(float|int);/, ''); @@ -204,6 +217,11 @@ define([ result = '#version ' + version + '\n'; } + var extensionsLength = extensions.length; + for (i = 0; i < extensionsLength; i++) { + result += extensions[i]; + } + if (isFragmentShader) { result += '\ #ifdef GL_FRAGMENT_PRECISION_HIGH\n\ @@ -224,6 +242,12 @@ define([ } } + // GLSLModernizer inserts its own layout qualifiers + // at this position in the source + if (context.webgl2) { + result += '#define OUTPUT_DECLARATION\n\n'; + } + // append built-ins if (shaderSource.includeBuiltIns) { result += getBuiltinsAndAutomaticUniforms(combinedSources); @@ -235,6 +259,11 @@ define([ // append actual source result += combinedSources; + // modernize the source + if (context.webgl2) { + result = modernizeShader(result, isFragmentShader, true); + } + return result; } @@ -297,19 +326,23 @@ define([ /** * Create a single string containing the full, combined vertex shader with all dependencies and defines. * + * @param {Context} context The current rendering context + * * @returns {String} The combined shader string. */ - ShaderSource.prototype.createCombinedVertexShader = function() { - return combineShader(this, false); + ShaderSource.prototype.createCombinedVertexShader = function(context) { + return combineShader(this, false, context); }; /** * Create a single string containing the full, combined fragment shader with all dependencies and defines. * + * @param {Context} context The current rendering context + * * @returns {String} The combined shader string. */ - ShaderSource.prototype.createCombinedFragmentShader = function() { - return combineShader(this, true); + ShaderSource.prototype.createCombinedFragmentShader = function(context) { + return combineShader(this, true, context); }; /** diff --git a/Source/Renderer/Texture.js b/Source/Renderer/Texture.js index 2de068ea749e..34d71a6d56a6 100644 --- a/Source/Renderer/Texture.js +++ b/Source/Renderer/Texture.js @@ -71,6 +71,23 @@ define([ internalFormat = WebGLConstants.DEPTH_COMPONENT24; } } + + if (pixelDatatype === PixelDatatype.FLOAT) { + switch (pixelFormat) { + case PixelFormat.RGBA: + internalFormat = WebGLConstants.RGBA32F; + break; + case PixelFormat.RGB: + internalFormat = WebGLConstants.RGB32F; + break; + case PixelFormat.RG: + internalFormat = WebGLConstants.RG32F; + break; + case PixelFormat.R: + internalFormat = WebGLConstants.R32F; + break; + } + } } //>>includeStart('debug', pragmas.debug); diff --git a/Source/Renderer/modernizeShader.js b/Source/Renderer/modernizeShader.js new file mode 100644 index 000000000000..b2accdf7e62a --- /dev/null +++ b/Source/Renderer/modernizeShader.js @@ -0,0 +1,227 @@ +define([ + '../Core/defined', + '../Core/DeveloperError', + ], function( + defined, + DeveloperError) { + 'use strict'; + + /** + * A function to port GLSL shaders from GLSL ES 1.00 to GLSL ES 3.00 + * + * This function is nowhere near comprehensive or complete. It just + * handles some common cases. + * + * Note that this function requires the presence of the + * "#define OUTPUT_DECLARATION" line that is appended + * by ShaderSource. + * + * @private + */ + function modernizeShader(source, isFragmentShader) { + var outputDeclarationRegex = /#define OUTPUT_DECLARATION/; + var splitSource = source.split('\n'); + + if (/#version 300 es/g.test(source)) { + return source; + } + + var outputDeclarationLine = -1; + var i, line; + for (i = 0; i < splitSource.length; ++i) { + line = splitSource[i]; + if (outputDeclarationRegex.test(line)) { + outputDeclarationLine = i; + break; + } + } + + if (outputDeclarationLine === -1) { + throw new DeveloperError('Could not find a #define OUTPUT_DECLARATION!'); + } + + var outputVariables = []; + + for (i = 0; i < 10; i++) { + var fragDataString = 'gl_FragData\\[' + i + '\\]'; + var newOutput = 'czm_out' + i; + var regex = new RegExp(fragDataString, 'g'); + if (regex.test(source)) { + setAdd(newOutput, outputVariables); + replaceInSourceString(fragDataString, newOutput, splitSource); + splitSource.splice(outputDeclarationLine, 0, 'layout(location = ' + i + ') out vec4 ' + newOutput + ';'); + outputDeclarationLine += 1; + } + } + + var czmFragColor = 'czm_fragColor'; + if (findInSource('gl_FragColor', splitSource)) { + setAdd(czmFragColor, outputVariables); + replaceInSourceString('gl_FragColor', czmFragColor, splitSource); + splitSource.splice(outputDeclarationLine, 0, 'layout(location = 0) out vec4 czm_fragColor;'); + outputDeclarationLine += 1; + } + + var variableMap = getVariablePreprocessorBranch(outputVariables, splitSource); + var lineAdds = {}; + for (i = 0; i < splitSource.length; i++) { + line = splitSource[i]; + for (var variable in variableMap) { + if (variableMap.hasOwnProperty(variable)) { + var matchVar = new RegExp('(layout)[^]+(out)[^]+(' + variable + ')[^]+', 'g'); + if (matchVar.test(line)) { + lineAdds[line] = variable; + } + } + } + } + + for (var layoutDeclaration in lineAdds) { + if (lineAdds.hasOwnProperty(layoutDeclaration)) { + var variableName = lineAdds[layoutDeclaration]; + var lineNumber = splitSource.indexOf(layoutDeclaration); + var entry = variableMap[variableName]; + var depth = entry.length; + var d; + for (d = 0; d < depth; d++) { + splitSource.splice(lineNumber, 0, entry[d]); + } + lineNumber += depth + 1; + for (d = depth - 1; d >= 0; d--) { + splitSource.splice(lineNumber, 0, '#endif //' + entry[d]); + } + } + } + + var versionThree = '#version 300 es'; + var foundVersion = false; + for (i = 0; i < splitSource.length; i++) { + if (/#version/.test(splitSource[i])) { + splitSource[i] = versionThree; + foundVersion = true; + } + } + + if (!foundVersion) { + splitSource.splice(0, 0, versionThree); + } + + removeExtension('EXT_draw_buffers', splitSource); + removeExtension('EXT_frag_depth', splitSource); + + replaceInSourceString('texture2D', 'texture', splitSource); + replaceInSourceString('texture3D', 'texture', splitSource); + replaceInSourceString('textureCube', 'texture', splitSource); + replaceInSourceString('gl_FragDepthEXT', 'gl_FragDepth', splitSource); + + if (isFragmentShader) { + replaceInSourceString('varying', 'in', splitSource); + } else { + replaceInSourceString('attribute', 'in', splitSource); + replaceInSourceString('varying', 'out', splitSource); + } + + return compileSource(splitSource); + } + + // Note that this fails if your string looks like + // searchString[singleCharacter]searchString + function replaceInSourceString(str, replacement, splitSource) { + var regexStr = '(^|[^\\w])(' + str + ')($|[^\\w])'; + var regex = new RegExp(regexStr, 'g'); + + var splitSourceLength = splitSource.length; + for (var i = 0; i < splitSourceLength; ++i) { + var line = splitSource[i]; + splitSource[i] = line.replace(regex, '$1' + replacement + '$3'); + } + } + + function replaceInSourceRegex(regex, replacement, splitSource) { + var splitSourceLength = splitSource.length; + for (var i = 0; i < splitSourceLength; ++i) { + var line = splitSource[i]; + splitSource[i] = line.replace(regex, replacement); + } + } + + function findInSource(str, splitSource) { + var regexStr = '(^|[^\\w])(' + str + ')($|[^\\w])'; + var regex = new RegExp(regexStr, 'g'); + + var splitSourceLength = splitSource.length; + for (var i = 0; i < splitSourceLength; ++i) { + var line = splitSource[i]; + if (regex.test(line)) { + return true; + } + } + return false; + } + + function compileSource(splitSource) { + var wholeSource = ''; + + var splitSourceLength = splitSource.length; + for (var i = 0; i < splitSourceLength; ++i) { + wholeSource += splitSource[i] + '\n'; + } + return wholeSource; + } + + function setAdd(variable, set) { + if (set.indexOf(variable) === -1) { + set.push(variable); + } + } + + function getVariablePreprocessorBranch(layoutVariables, splitSource) { + var variableMap = {}; + + var numLayoutVariables = layoutVariables.length; + + var stack = []; + for (var i = 0; i < splitSource.length; ++i) { + var line = splitSource[i]; + var hasIF = /(#ifdef|#if)/g.test(line); + var hasELSE = /#else/g.test(line); + var hasENDIF = /#endif/g.test(line); + + if (hasIF) { + stack.push(line); + } else if (hasELSE) { + var top = stack[stack.length - 1]; + var op = top.replace('ifdef', 'ifndef'); + if (/if/g.test(op)) { + op = op.replace(/(#if\s+)(\S*)([^]*)/, '$1!($2)$3'); + } + stack.pop(); + stack.push(op); + } else if (hasENDIF) { + stack.pop(); + } else if (!/layout/g.test(line)) { + for (var varIndex = 0; varIndex < numLayoutVariables; ++varIndex) { + var varName = layoutVariables[varIndex]; + if (line.indexOf(varName) !== -1) { + if (!defined(variableMap[varName])) { + variableMap[varName] = stack.slice(); + } else { + variableMap[varName] = variableMap[varName].filter(function(x) { + return stack.indexOf(x) >= 0; + }); + } + } + } + } + } + + return variableMap; + } + + function removeExtension(name, splitSource) { + var regex = '#extension\\s+GL_' + name + '\\s+:\\s+[a-zA-Z0-9]+\\s*$'; + replaceInSourceRegex(new RegExp(regex, 'g'), '', splitSource); + } + + return modernizeShader; +}); diff --git a/Source/Scene/Batched3DModel3DTileContent.js b/Source/Scene/Batched3DModel3DTileContent.js index fd2a50198a5f..0bcd5f7e3181 100644 --- a/Source/Scene/Batched3DModel3DTileContent.js +++ b/Source/Scene/Batched3DModel3DTileContent.js @@ -13,6 +13,7 @@ define([ '../Core/getStringFromTypedArray', '../Core/RequestType', '../Core/RuntimeError', + '../Renderer/Pass', './Cesium3DTileBatchTable', './Cesium3DTileFeature', './Cesium3DTileFeatureTable', @@ -33,6 +34,7 @@ define([ getStringFromTypedArray, RequestType, RuntimeError, + Pass, Cesium3DTileBatchTable, Cesium3DTileFeature, Cesium3DTileFeatureTable, @@ -352,6 +354,7 @@ define([ gltf : gltfView, cull : false, // The model is already culled by 3D Tiles releaseGltfJson : true, // Models are unique and will not benefit from caching so save memory + opaquePass : Pass.CESIUM_3D_TILE, // Draw opaque portions of the model during the 3D Tiles pass basePath : basePath, requestType : RequestType.TILES3D, modelMatrix : tile.computedTransform, diff --git a/Source/Scene/Billboard.js b/Source/Scene/Billboard.js index 88e8d1870597..56d0fef92103 100644 --- a/Source/Scene/Billboard.js +++ b/Source/Scene/Billboard.js @@ -76,22 +76,47 @@ define([ options = defaultValue(options, defaultValue.EMPTY_OBJECT); //>>includeStart('debug', pragmas.debug); - if (defined(options.scaleByDistance) && options.scaleByDistance.far <= options.scaleByDistance.near) { - throw new DeveloperError('scaleByDistance.far must be greater than scaleByDistance.near.'); + if (defined(options.disableDepthTestDistance) && options.disableDepthTestDistance < 0.0) { + throw new DeveloperError('disableDepthTestDistance must be greater than or equal to 0.0.'); } - if (defined(options.translucencyByDistance) && options.translucencyByDistance.far <= options.translucencyByDistance.near) { - throw new DeveloperError('translucencyByDistance.far must be greater than translucencyByDistance.near.'); + //>>includeEnd('debug'); + + var translucencyByDistance = options.translucencyByDistance; + var pixelOffsetScaleByDistance = options.pixelOffsetScaleByDistance; + var scaleByDistance = options.scaleByDistance; + var distanceDisplayCondition = options.distanceDisplayCondition; + if (defined(translucencyByDistance)) { + //>>includeStart('debug', pragmas.debug); + if (translucencyByDistance.far <= translucencyByDistance.near) { + throw new DeveloperError('translucencyByDistance.far must be greater than translucencyByDistance.near.'); + } + //>>includeEnd('debug'); + translucencyByDistance = NearFarScalar.clone(translucencyByDistance); } - if (defined(options.pixelOffsetScaleByDistance) && options.pixelOffsetScaleByDistance.far <= options.pixelOffsetScaleByDistance.near) { - throw new DeveloperError('pixelOffsetScaleByDistance.far must be greater than pixelOffsetScaleByDistance.near.'); + if (defined(pixelOffsetScaleByDistance)) { + //>>includeStart('debug', pragmas.debug); + if (pixelOffsetScaleByDistance.far <= pixelOffsetScaleByDistance.near) { + throw new DeveloperError('pixelOffsetScaleByDistance.far must be greater than pixelOffsetScaleByDistance.near.'); + } + //>>includeEnd('debug'); + pixelOffsetScaleByDistance = NearFarScalar.clone(pixelOffsetScaleByDistance); } - if (defined(options.distanceDisplayCondition) && options.distanceDisplayCondition.far <= options.distanceDisplayCondition.near) { - throw new DeveloperError('distanceDisplayCondition.far must be greater than distanceDisplayCondition.near'); + if (defined(scaleByDistance)) { + //>>includeStart('debug', pragmas.debug); + if (scaleByDistance.far <= scaleByDistance.near) { + throw new DeveloperError('scaleByDistance.far must be greater than scaleByDistance.near.'); + } + //>>includeEnd('debug'); + scaleByDistance = NearFarScalar.clone(scaleByDistance); } - if (defined(options.disableDepthTestDistance) && options.disableDepthTestDistance < 0.0) { - throw new DeveloperError('disableDepthTestDistance must be greater than or equal to 0.0.'); + if (defined(distanceDisplayCondition)) { + //>>includeStart('debug', pragmas.debug); + if (distanceDisplayCondition.far <= distanceDisplayCondition.near) { + throw new DeveloperError('distanceDisplayCondition.far must be greater than distanceDisplayCondition.near.'); + } + //>>includeEnd('debug'); + distanceDisplayCondition = DistanceDisplayCondition.clone(distanceDisplayCondition); } - //>>includeEnd('debug'); this._show = defaultValue(options.show, true); this._position = Cartesian3.clone(defaultValue(options.position, Cartesian3.ZERO)); @@ -108,11 +133,11 @@ define([ this._alignedAxis = Cartesian3.clone(defaultValue(options.alignedAxis, Cartesian3.ZERO)); this._width = options.width; this._height = options.height; - this._scaleByDistance = options.scaleByDistance; - this._translucencyByDistance = options.translucencyByDistance; - this._pixelOffsetScaleByDistance = options.pixelOffsetScaleByDistance; + this._scaleByDistance = scaleByDistance; + this._translucencyByDistance = translucencyByDistance; + this._pixelOffsetScaleByDistance = pixelOffsetScaleByDistance; this._sizeInMeters = defaultValue(options.sizeInMeters, false); - this._distanceDisplayCondition = options.distanceDisplayCondition; + this._distanceDisplayCondition = distanceDisplayCondition; this._disableDepthTestDistance = defaultValue(options.disableDepthTestDistance, 0.0); this._id = options.id; this._collection = defaultValue(options.collection, billboardCollection); diff --git a/Source/Scene/BlendEquation.js b/Source/Scene/BlendEquation.js index 27bf019d21eb..e589beeae418 100644 --- a/Source/Scene/BlendEquation.js +++ b/Source/Scene/BlendEquation.js @@ -34,9 +34,27 @@ define([ * @type {Number} * @constant */ - REVERSE_SUBTRACT : WebGLConstants.FUNC_REVERSE_SUBTRACT + REVERSE_SUBTRACT : WebGLConstants.FUNC_REVERSE_SUBTRACT, - // No min and max like in ColladaFX GLES2 profile + /** + * Pixel values are given to the minimum function (min(source, destination)). + * + * This equation operates on each pixel color component. + * + * @type {Number} + * @constant + */ + MIN : WebGLConstants.MIN, + + /** + * Pixel values are given to the maximum function (max(source, destination)). + * + * This equation operates on each pixel color component. + * + * @type {Number} + * @constant + */ + MAX : WebGLConstants.MAX }; return freezeObject(BlendEquation); diff --git a/Source/Scene/Cesium3DTileBatchTable.js b/Source/Scene/Cesium3DTileBatchTable.js index 79401ae40314..f2b10bc06534 100644 --- a/Source/Scene/Cesium3DTileBatchTable.js +++ b/Source/Scene/Cesium3DTileBatchTable.js @@ -68,6 +68,9 @@ define([ StencilOperation) { 'use strict'; + var DEFAULT_COLOR_VALUE = Color.WHITE; + var DEFAULT_SHOW_VALUE = true; + /** * @private */ @@ -414,7 +417,7 @@ define([ Check.typeOf.object('color', color); //>>includeEnd('debug'); - if (Color.equals(color, Color.WHITE) && !defined(this._batchValues)) { + if (Color.equals(color, DEFAULT_COLOR_VALUE) && !defined(this._batchValues)) { // Avoid allocating since the default is white return; } @@ -475,7 +478,7 @@ define([ //>>includeEnd('debug'); if (!defined(this._batchValues)) { - return Color.clone(Color.WHITE, result); + return Color.clone(DEFAULT_COLOR_VALUE, result); } var batchValues = this._batchValues; @@ -495,7 +498,7 @@ define([ Cesium3DTileBatchTable.prototype.applyStyle = function(frameState, style) { if (!defined(style)) { - this.setAllColor(Color.WHITE); + this.setAllColor(DEFAULT_COLOR_VALUE); this.setAllShow(true); return; } @@ -504,8 +507,8 @@ define([ var length = this.featuresLength; for (var i = 0; i < length; ++i) { var feature = content.getFeature(i); - var color = style.color.evaluateColor(frameState, feature, scratchColor); - var show = style.show.evaluate(frameState, feature); + var color = defined(style.color) ? style.color.evaluateColor(frameState, feature, scratchColor) : DEFAULT_COLOR_VALUE; + var show = defined(style.show) ? style.show.evaluate(frameState, feature) : DEFAULT_SHOW_VALUE; this.setColor(i, color); this.setShow(i, show); } @@ -1305,10 +1308,6 @@ define([ // even though their style is opaque. var translucentCommand = (derivedCommand.pass === Pass.TRANSLUCENT); - if (!translucentCommand) { - derivedCommand.pass = Pass.CESIUM_3D_TILE; - } - derivedCommand.uniformMap = defined(derivedCommand.uniformMap) ? derivedCommand.uniformMap : {}; derivedCommand.uniformMap.tile_translucentCommand = function() { return translucentCommand; diff --git a/Source/Scene/Cesium3DTileStyle.js b/Source/Scene/Cesium3DTileStyle.js index 02b4d55947ae..a21770cf06ea 100644 --- a/Source/Scene/Cesium3DTileStyle.js +++ b/Source/Scene/Cesium3DTileStyle.js @@ -22,10 +22,6 @@ define([ Expression) { 'use strict'; - var DEFAULT_JSON_COLOR_EXPRESSION = 'color("#ffffff")'; - var DEFAULT_JSON_BOOLEAN_EXPRESSION = true; - var DEFAULT_JSON_NUMBER_EXPRESSION = 1.0; - /** * A style that is applied to a {@link Cesium3DTileset}. *

@@ -95,49 +91,13 @@ define([ styleJson = defaultValue(styleJson, defaultValue.EMPTY_OBJECT); - that._colorShaderFunctionReady = !defined(styleJson.color); - that._showShaderFunctionReady = !defined(styleJson.show); - that._pointSizeShaderFunctionReady = !defined(styleJson.pointSize); - - var colorExpression = defaultValue(styleJson.color, DEFAULT_JSON_COLOR_EXPRESSION); - var showExpression = defaultValue(styleJson.show, DEFAULT_JSON_BOOLEAN_EXPRESSION); - var pointSizeExpression = defaultValue(styleJson.pointSize, DEFAULT_JSON_NUMBER_EXPRESSION); - - var defines = styleJson.defines; - - var color; - if (typeof colorExpression === 'string') { - color = new Expression(colorExpression, defines); - } else if (defined(colorExpression.conditions)) { - color = new ConditionsExpression(colorExpression, defines); - } - - that._color = color; - - var show; - if (typeof showExpression === 'boolean') { - show = new Expression(String(showExpression), defines); - } else if (typeof showExpression === 'string') { - show = new Expression(showExpression, defines); - } else if (defined(showExpression.conditions)) { - show = new ConditionsExpression(showExpression, defines); - } - - that._show = show; - - var pointSize; - if (typeof pointSizeExpression === 'number') { - pointSize = new Expression(String(pointSizeExpression), defines); - } else if (typeof pointSizeExpression === 'string') { - pointSize = new Expression(pointSizeExpression, defines); - } else if (defined(pointSizeExpression.conditions)) { - pointSize = new ConditionsExpression(pointSizeExpression, defines); - } - - that._pointSize = pointSize; + that.color = styleJson.color; + that.show = styleJson.show; + that.pointSize = styleJson.pointSize; var meta = {}; if (defined(styleJson.meta)) { + var defines = styleJson.defines; var metaJson = defaultValue(styleJson.meta, defaultValue.EMPTY_OBJECT); for (var property in metaJson) { if (metaJson.hasOwnProperty(property)) { @@ -209,7 +169,8 @@ define([ }, /** - * Gets or sets the {@link StyleExpression} object used to evaluate the style's show property. + * Gets or sets the {@link StyleExpression} object used to evaluate the style's show property. Alternatively a boolean, string, or object defining a show style can be used. + * The getter will return the internal {@link Expression} or {@link ConditionsExpression}, which may differ from the value provided to the setter. *

* The expression must return or convert to a Boolean. *

@@ -234,6 +195,28 @@ define([ * return true; * } * }; + * + * @example + * var style = new Cesium.Cesium3DTileStyle(); + * // Override show expression with a boolean + * style.show = true; + * }; + * + * @example + * var style = new Cesium.Cesium3DTileStyle(); + * // Override show expression with a string + * style.show = '${Height} > 0'; + * }; + * + * @example + * var style = new Cesium.Cesium3DTileStyle(); + * // Override show expression with a condition + * style.show = { + * conditions: [ + * ['${height} > 2', 'false'], + * ['true', 'true'] + * ]; + * }; */ show : { get : function() { @@ -246,13 +229,25 @@ define([ return this._show; }, set : function(value) { + var defines = defaultValue(this._style, defaultValue.EMPTY_OBJECT).defines; + if (!defined(value)) { + this._show = undefined; + } else if (typeof value === 'boolean') { + this._show = new Expression(String(value)); + } else if (typeof value === 'string') { + this._show = new Expression(value, defines); + } else if (defined(value.conditions)) { + this._show = new ConditionsExpression(value, defines); + } else { + this._show = value; + } this._showShaderFunctionReady = false; - this._show = value; } }, /** - * Gets or sets the {@link StyleExpression} object used to evaluate the style's color property. + * Gets or sets the {@link StyleExpression} object used to evaluate the style's color property. Alternatively a string or object defining a color style can be used. + * The getter will return the internal {@link Expression} or {@link ConditionsExpression}, which may differ from the value provided to the setter. *

* The expression must return a Color. *

@@ -277,6 +272,21 @@ define([ * return Cesium.Color.clone(Cesium.Color.WHITE, result); * } * }; + * + * @example + * var style = new Cesium.Cesium3DTileStyle(); + * // Override color expression with a string + * style.color = 'color("blue")'; + * + * @example + * var style = new Cesium.Cesium3DTileStyle(); + * // Override color expression with a condition + * style.color = { + * conditions : [ + * ['${height} > 2', 'color("cyan")'], + * ['true', 'color("blue")'] + * ] + * }; */ color : { get : function() { @@ -289,13 +299,23 @@ define([ return this._color; }, set : function(value) { + var defines = defaultValue(this._style, defaultValue.EMPTY_OBJECT).defines; + if (!defined(value)) { + this._color = undefined; + } else if (typeof value === 'string') { + this._color = new Expression(value, defines); + } else if (defined(value.conditions)) { + this._color = new ConditionsExpression(value, defines); + } else { + this._color = value; + } this._colorShaderFunctionReady = false; - this._color = value; } }, /** - * Gets or sets the {@link StyleExpression} object used to evaluate the style's pointSize property. + * Gets or sets the {@link StyleExpression} object used to evaluate the style's pointSize property. Alternatively a number, string, or object defining a pointSize style can be used. + * The getter will return the internal {@link Expression} or {@link ConditionsExpression}, which may differ from the value provided to the setter. *

* The expression must return or convert to a Number. *

@@ -320,6 +340,26 @@ define([ * return 1.0; * } * }; + * + * @example + * var style = new Cesium.Cesium3DTileStyle(); + * // Override pointSize expression with a number + * style.pointSize = 1.0; + * + * @example + * var style = new Cesium.Cesium3DTileStyle(); + * // Override pointSize expression with a string + * style.pointSize = '${height} / 10'; + * + * @example + * var style = new Cesium.Cesium3DTileStyle(); + * // Override pointSize expression with a condition + * style.pointSize = { + * conditions : [ + * ['${height} > 2', '1.0'], + * ['true', '2.0'] + * ] + * }; */ pointSize : { get : function() { @@ -332,8 +372,19 @@ define([ return this._pointSize; }, set : function(value) { + var defines = defaultValue(this._style, defaultValue.EMPTY_OBJECT).defines; + if (!defined(value)) { + this._pointSize = undefined; + } else if (typeof value === 'number') { + this._pointSize = new Expression(String(value)); + } else if (typeof value === 'string') { + this._pointSize = new Expression(value, defines); + } else if (defined(value.conditions)) { + this._pointSize = new ConditionsExpression(value, defines); + } else { + this._pointSize = value; + } this._pointSizeShaderFunctionReady = false; - this._pointSize = value; } }, @@ -389,7 +440,7 @@ define([ } this._colorShaderFunctionReady = true; - this._colorShaderFunction = this.color.getShaderFunction(functionName, attributePrefix, shaderState, 'vec4'); + this._colorShaderFunction = defined(this.color) ? this.color.getShaderFunction(functionName, attributePrefix, shaderState, 'vec4') : undefined; return this._colorShaderFunction; }; @@ -411,7 +462,7 @@ define([ } this._showShaderFunctionReady = true; - this._showShaderFunction = this.show.getShaderFunction(functionName, attributePrefix, shaderState, 'bool'); + this._showShaderFunction = defined(this.show) ? this.show.getShaderFunction(functionName, attributePrefix, shaderState, 'bool') : undefined; return this._showShaderFunction; }; @@ -433,7 +484,7 @@ define([ } this._pointSizeShaderFunctionReady = true; - this._pointSizeShaderFunction = this.pointSize.getShaderFunction(functionName, attributePrefix, shaderState, 'float'); + this._pointSizeShaderFunction = defined(this.pointSize) ? this.pointSize.getShaderFunction(functionName, attributePrefix, shaderState, 'float') : undefined; return this._pointSizeShaderFunction; }; diff --git a/Source/Scene/ClassificationPrimitive.js b/Source/Scene/ClassificationPrimitive.js new file mode 100644 index 000000000000..86440fcecf86 --- /dev/null +++ b/Source/Scene/ClassificationPrimitive.js @@ -0,0 +1,918 @@ +/*global define*/ +define([ + '../Core/ColorGeometryInstanceAttribute', + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/destroyObject', + '../Core/DeveloperError', + '../Core/GeometryInstance', + '../Core/isArray', + '../Renderer/DrawCommand', + '../Renderer/Pass', + '../Renderer/RenderState', + '../Renderer/ShaderProgram', + '../Renderer/ShaderSource', + '../Shaders/ShadowVolumeFS', + '../Shaders/ShadowVolumeVS', + '../ThirdParty/when', + './BlendingState', + './DepthFunction', + './PerInstanceColorAppearance', + './Primitive', + './SceneMode', + './StencilFunction', + './StencilOperation' + ], function( + ColorGeometryInstanceAttribute, + defaultValue, + defined, + defineProperties, + destroyObject, + DeveloperError, + GeometryInstance, + isArray, + DrawCommand, + Pass, + RenderState, + ShaderProgram, + ShaderSource, + ShadowVolumeFS, + ShadowVolumeVS, + when, + BlendingState, + DepthFunction, + PerInstanceColorAppearance, + Primitive, + SceneMode, + StencilFunction, + StencilOperation) { + 'use strict'; + + var ClassificationPrimitiveReadOnlyInstanceAttributes = ['color']; + + /** + * A classification primitive represents a volume enclosing geometry in the {@link Scene} to be highlighted. The geometry must be from a single {@link GeometryInstance}. + * Batching multiple geometries is not yet supported. + *

+ * A primitive combines the geometry instance with an {@link Appearance} that describes the full shading, including + * {@link Material} and {@link RenderState}. Roughly, the geometry instance defines the structure and placement, + * and the appearance defines the visual characteristics. Decoupling geometry and appearance allows us to mix + * and match most of them and add a new geometry or appearance independently of each other. Only the {@link PerInstanceColorAppearance} + * is supported at this time. + *

+ *

+ * For correct rendering, this feature requires the EXT_frag_depth WebGL extension. For hardware that do not support this extension, there + * will be rendering artifacts for some viewing angles. + *

+ *

+ * Valid geometries are {@link BoxGeometry}, {@link CylinderGeometry}, {@link EllipsoidGeometry}, {@link PolylineVolumeGeometry}, and {@link SphereGeometry}. + *

+ *

+ * Geometries that follow the surface of the ellipsoid, such as {@link CircleGeometry}, {@link CorridorGeometry}, {@link EllipseGeometry}, {@link PolygonGeometry}, and {@link RectangleGeometry}, + * are also valid if they are extruded volumes; otherwise, they will not be rendered. + *

+ * + * @alias ClassificationPrimitive + * @constructor + * + * @param {Object} [options] Object with the following properties: + * @param {Array|GeometryInstance} [options.geometryInstances] The geometry instances to render. This can either be a single instance or an array of length one. + * @param {Boolean} [options.show=true] Determines if this primitive will be shown. + * @param {Boolean} [options.vertexCacheOptimize=false] When true, geometry vertices are optimized for the pre and post-vertex-shader caches. + * @param {Boolean} [options.interleave=false] When true, geometry vertex attributes are interleaved, which can slightly improve rendering performance but increases load time. + * @param {Boolean} [options.compressVertices=true] When true, the geometry vertices are compressed, which will save memory. + * @param {Boolean} [options.releaseGeometryInstances=true] When true, the primitive does not keep a reference to the input geometryInstances to save memory. + * @param {Boolean} [options.allowPicking=true] When true, each geometry instance will only be pickable with {@link Scene#pick}. When false, GPU memory is saved. + * @param {Boolean} [options.asynchronous=true] Determines if the primitive will be created asynchronously or block until ready. If false initializeTerrainHeights() must be called first. + * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown. + * @param {Boolean} [options.debugShowShadowVolume=false] For debugging only. Determines if the shadow volume for each geometry in the primitive is drawn. Must be true on + * creation for the volumes to be created before the geometry is released or options.releaseGeometryInstance must be false. + * + * @see Primitive + * @see GroundPrimitive + * @see GeometryInstance + * @see Appearance + */ + function ClassificationPrimitive(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + /** + * The geometry instance rendered with this primitive. This may + * be undefined if options.releaseGeometryInstances + * is true when the primitive is constructed. + *

+ * Changing this property after the primitive is rendered has no effect. + *

+ *

+ * Because of the rendering technique used, all geometry instances must be the same color. + * If there is an instance with a differing color, a DeveloperError will be thrown + * on the first attempt to render. + *

+ * + * @readonly + * @type {Array|GeometryInstance} + * + * @default undefined + */ + this.geometryInstances = options.geometryInstances; + /** + * Determines if the primitive will be shown. This affects all geometry + * instances in the primitive. + * + * @type {Boolean} + * + * @default true + */ + this.show = defaultValue(options.show, true); + /** + * This property is for debugging only; it is not for production use nor is it optimized. + *

+ * Draws the bounding sphere for each draw command in the primitive. + *

+ * + * @type {Boolean} + * + * @default false + */ + this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false); + /** + * This property is for debugging only; it is not for production use nor is it optimized. + *

+ * Draws the shadow volume for each geometry in the primitive. + *

+ * + * @type {Boolean} + * + * @default false + */ + this.debugShowShadowVolume = defaultValue(options.debugShowShadowVolume, false); + this._debugShowShadowVolume = false; + + // These are used by GroundPrimitive to augment the shader and uniform map. + this._extruded = defaultValue(options._extruded, false); + this._uniformMap = options._uniformMap; + + this._sp = undefined; + this._spPick = undefined; + + this._rsStencilPreloadPass = undefined; + this._rsStencilDepthPass = undefined; + this._rsColorPass = undefined; + this._rsPickPass = undefined; + + this._ready = false; + this._readyPromise = when.defer(); + + this._primitive = undefined; + this._pickPrimitive = options._pickPrimitive; + + var appearance = new PerInstanceColorAppearance({ + flat : true + }); + + var readOnlyAttributes; + if (defined(this.geometryInstances) && isArray(this.geometryInstances) && this.geometryInstances.length > 1) { + readOnlyAttributes = ClassificationPrimitiveReadOnlyInstanceAttributes; + } + + this._createBoundingVolumeFunction = options._createBoundingVolumeFunction; + this._updateAndQueueCommandsFunction = options._updateAndQueueCommandsFunction; + + this._primitiveOptions = { + geometryInstances : undefined, + appearance : appearance, + vertexCacheOptimize : defaultValue(options.vertexCacheOptimize, false), + interleave : defaultValue(options.interleave, false), + releaseGeometryInstances : defaultValue(options.releaseGeometryInstances, true), + allowPicking : defaultValue(options.allowPicking, true), + asynchronous : defaultValue(options.asynchronous, true), + compressVertices : defaultValue(options.compressVertices, true), + _readOnlyInstanceAttributes : readOnlyAttributes, + _createBoundingVolumeFunction : undefined, + _createRenderStatesFunction : undefined, + _createShaderProgramFunction : undefined, + _createCommandsFunction : undefined, + _updateAndQueueCommandsFunction : undefined, + _createPickOffsets : true + }; + } + + defineProperties(ClassificationPrimitive.prototype, { + /** + * When true, geometry vertices are optimized for the pre and post-vertex-shader caches. + * + * @memberof ClassificationPrimitive.prototype + * + * @type {Boolean} + * @readonly + * + * @default true + */ + vertexCacheOptimize : { + get : function() { + return this._primitiveOptions.vertexCacheOptimize; + } + }, + + /** + * Determines if geometry vertex attributes are interleaved, which can slightly improve rendering performance. + * + * @memberof ClassificationPrimitive.prototype + * + * @type {Boolean} + * @readonly + * + * @default false + */ + interleave : { + get : function() { + return this._primitiveOptions.interleave; + } + }, + + /** + * When true, the primitive does not keep a reference to the input geometryInstances to save memory. + * + * @memberof ClassificationPrimitive.prototype + * + * @type {Boolean} + * @readonly + * + * @default true + */ + releaseGeometryInstances : { + get : function() { + return this._primitiveOptions.releaseGeometryInstances; + } + }, + + /** + * When true, each geometry instance will only be pickable with {@link Scene#pick}. When false, GPU memory is saved. + * + * @memberof ClassificationPrimitive.prototype + * + * @type {Boolean} + * @readonly + * + * @default true + */ + allowPicking : { + get : function() { + return this._primitiveOptions.allowPicking; + } + }, + + /** + * Determines if the geometry instances will be created and batched on a web worker. + * + * @memberof ClassificationPrimitive.prototype + * + * @type {Boolean} + * @readonly + * + * @default true + */ + asynchronous : { + get : function() { + return this._primitiveOptions.asynchronous; + } + }, + + /** + * When true, geometry vertices are compressed, which will save memory. + * + * @memberof ClassificationPrimitive.prototype + * + * @type {Boolean} + * @readonly + * + * @default true + */ + compressVertices : { + get : function() { + return this._primitiveOptions.compressVertices; + } + }, + + /** + * Determines if the primitive is complete and ready to render. If this property is + * true, the primitive will be rendered the next time that {@link ClassificationPrimitive#update} + * is called. + * + * @memberof ClassificationPrimitive.prototype + * + * @type {Boolean} + * @readonly + */ + ready : { + get : function() { + return this._ready; + } + }, + + /** + * Gets a promise that resolves when the primitive is ready to render. + * @memberof ClassificationPrimitive.prototype + * @type {Promise.} + * @readonly + */ + readyPromise : { + get : function() { + return this._readyPromise.promise; + } + } + }); + + /** + * Determines if ClassificationPrimitive rendering is supported. + * + * @param {Scene} scene The scene. + * @returns {Boolean} true if ClassificationPrimitives are supported; otherwise, returns false + */ + ClassificationPrimitive.isSupported = function(scene) { + return scene.context.stencilBuffer; + }; + + function getStencilPreloadRenderState(enableStencil) { + return { + colorMask : { + red : false, + green : false, + blue : false, + alpha : false + }, + stencilTest : { + enabled : enableStencil, + frontFunction : StencilFunction.ALWAYS, + frontOperation : { + fail : StencilOperation.KEEP, + zFail : StencilOperation.DECREMENT_WRAP, + zPass : StencilOperation.DECREMENT_WRAP + }, + backFunction : StencilFunction.ALWAYS, + backOperation : { + fail : StencilOperation.KEEP, + zFail : StencilOperation.INCREMENT_WRAP, + zPass : StencilOperation.INCREMENT_WRAP + }, + reference : 0, + mask : ~0 + }, + depthTest : { + enabled : false + }, + depthMask : false + }; + } + + function getStencilDepthRenderState(enableStencil) { + return { + colorMask : { + red : false, + green : false, + blue : false, + alpha : false + }, + stencilTest : { + enabled : enableStencil, + frontFunction : StencilFunction.ALWAYS, + frontOperation : { + fail : StencilOperation.KEEP, + zFail : StencilOperation.KEEP, + zPass : StencilOperation.INCREMENT_WRAP + }, + backFunction : StencilFunction.ALWAYS, + backOperation : { + fail : StencilOperation.KEEP, + zFail : StencilOperation.KEEP, + zPass : StencilOperation.DECREMENT_WRAP + }, + reference : 0, + mask : ~0 + }, + depthTest : { + enabled : true, + func : DepthFunction.LESS_OR_EQUAL + }, + depthMask : false + }; + } + + + function getColorRenderState(enableStencil) { + return { + stencilTest : { + enabled : enableStencil, + frontFunction : StencilFunction.NOT_EQUAL, + frontOperation : { + fail : StencilOperation.KEEP, + zFail : StencilOperation.KEEP, + zPass : StencilOperation.DECREMENT_WRAP + }, + backFunction : StencilFunction.NOT_EQUAL, + backOperation : { + fail : StencilOperation.KEEP, + zFail : StencilOperation.KEEP, + zPass : StencilOperation.DECREMENT_WRAP + }, + reference : 0, + mask : ~0 + }, + depthTest : { + enabled : false + }, + depthMask : false, + blending : BlendingState.ALPHA_BLEND + }; + } + + var pickRenderState = { + stencilTest : { + enabled : true, + frontFunction : StencilFunction.NOT_EQUAL, + frontOperation : { + fail : StencilOperation.KEEP, + zFail : StencilOperation.KEEP, + zPass : StencilOperation.DECREMENT_WRAP + }, + backFunction : StencilFunction.NOT_EQUAL, + backOperation : { + fail : StencilOperation.KEEP, + zFail : StencilOperation.KEEP, + zPass : StencilOperation.DECREMENT_WRAP + }, + reference : 0, + mask : ~0 + }, + depthTest : { + enabled : false + }, + depthMask : false + }; + + function createRenderStates(classificationPrimitive, context, appearance, twoPasses) { + if (defined(classificationPrimitive._rsStencilPreloadPass)) { + return; + } + var stencilEnabled = !classificationPrimitive.debugShowShadowVolume; + + classificationPrimitive._rsStencilPreloadPass = RenderState.fromCache(getStencilPreloadRenderState(stencilEnabled)); + classificationPrimitive._rsStencilDepthPass = RenderState.fromCache(getStencilDepthRenderState(stencilEnabled)); + classificationPrimitive._rsColorPass = RenderState.fromCache(getColorRenderState(stencilEnabled)); + classificationPrimitive._rsPickPass = RenderState.fromCache(pickRenderState); + } + + function modifyForEncodedNormals(primitive, vertexShaderSource) { + if (!primitive.compressVertices) { + return vertexShaderSource; + } + + if (vertexShaderSource.search(/attribute\s+vec3\s+extrudeDirection;/g) !== -1) { + var attributeName = 'compressedAttributes'; + + //only shadow volumes use extrudeDirection, and shadow volumes use vertexFormat: POSITION_ONLY so we don't need to check other attributes + var attributeDecl = 'attribute vec2 ' + attributeName + ';'; + + var globalDecl = 'vec3 extrudeDirection;\n'; + var decode = ' extrudeDirection = czm_octDecode(' + attributeName + ', 65535.0);\n'; + + var modifiedVS = vertexShaderSource; + modifiedVS = modifiedVS.replace(/attribute\s+vec3\s+extrudeDirection;/g, ''); + modifiedVS = ShaderSource.replaceMain(modifiedVS, 'czm_non_compressed_main'); + var compressedMain = + 'void main() \n' + + '{ \n' + + decode + + ' czm_non_compressed_main(); \n' + + '}'; + + return [attributeDecl, globalDecl, modifiedVS, compressedMain].join('\n'); + } + } + + function createShaderProgram(classificationPrimitive, frameState, appearance) { + if (defined(classificationPrimitive._sp)) { + return; + } + + var context = frameState.context; + var primitive = classificationPrimitive._primitive; + var vs = ShadowVolumeVS; + vs = classificationPrimitive._primitive._batchTable.getVertexShaderCallback()(vs); + vs = Primitive._appendShowToShader(primitive, vs); + vs = Primitive._appendDistanceDisplayConditionToShader(primitive, vs); + vs = Primitive._modifyShaderPosition(classificationPrimitive, vs, frameState.scene3DOnly); + vs = Primitive._updateColorAttribute(primitive, vs); + + if (classificationPrimitive._extruded) { + vs = modifyForEncodedNormals(primitive, vs); + } + + var extrudedDefine = classificationPrimitive._extruded ? 'EXTRUDED_GEOMETRY' : ''; + + var vsSource = new ShaderSource({ + defines : [extrudedDefine], + sources : [vs] + }); + var fsSource = new ShaderSource({ + sources : [ShadowVolumeFS] + }); + var attributeLocations = classificationPrimitive._primitive._attributeLocations; + + classificationPrimitive._sp = ShaderProgram.replaceCache({ + context : context, + shaderProgram : classificationPrimitive._sp, + vertexShaderSource : vsSource, + fragmentShaderSource : fsSource, + attributeLocations : attributeLocations + }); + + if (classificationPrimitive._primitive.allowPicking) { + var vsPick = ShaderSource.createPickVertexShaderSource(vs); + vsPick = Primitive._updatePickColorAttribute(vsPick); + + var pickVS = new ShaderSource({ + defines : [extrudedDefine], + sources : [vsPick] + }); + + var pickFS = new ShaderSource({ + sources : [ShadowVolumeFS], + pickColorQualifier : 'varying' + }); + + classificationPrimitive._spPick = ShaderProgram.replaceCache({ + context : context, + shaderProgram : classificationPrimitive._spPick, + vertexShaderSource : pickVS, + fragmentShaderSource : pickFS, + attributeLocations : attributeLocations + }); + } else { + classificationPrimitive._spPick = ShaderProgram.fromCache({ + context : context, + vertexShaderSource : vsSource, + fragmentShaderSource : fsSource, + attributeLocations : attributeLocations + }); + } + } + + function createColorCommands(classificationPrimitive, colorCommands) { + var primitive = classificationPrimitive._primitive; + var length = primitive._va.length * 3; + colorCommands.length = length; + + var vaIndex = 0; + var uniformMap = primitive._batchTable.getUniformMapCallback()(classificationPrimitive._uniformMap); + + for (var i = 0; i < length; i += 3) { + var vertexArray = primitive._va[vaIndex++]; + + // stencil preload command + var command = colorCommands[i]; + if (!defined(command)) { + command = colorCommands[i] = new DrawCommand({ + owner : classificationPrimitive, + primitiveType : primitive._primitiveType + }); + } + + command.vertexArray = vertexArray; + command.renderState = classificationPrimitive._rsStencilPreloadPass; + command.shaderProgram = classificationPrimitive._sp; + command.uniformMap = uniformMap; + command.pass = Pass.GROUND; + + // stencil depth command + command = colorCommands[i + 1]; + if (!defined(command)) { + command = colorCommands[i + 1] = new DrawCommand({ + owner : classificationPrimitive, + primitiveType : primitive._primitiveType + }); + } + + command.vertexArray = vertexArray; + command.renderState = classificationPrimitive._rsStencilDepthPass; + command.shaderProgram = classificationPrimitive._sp; + command.uniformMap = uniformMap; + command.pass = Pass.GROUND; + + // color command + command = colorCommands[i + 2]; + if (!defined(command)) { + command = colorCommands[i + 2] = new DrawCommand({ + owner : classificationPrimitive, + primitiveType : primitive._primitiveType + }); + } + + command.vertexArray = vertexArray; + command.renderState = classificationPrimitive._rsColorPass; + command.shaderProgram = classificationPrimitive._sp; + command.uniformMap = uniformMap; + command.pass = Pass.GROUND; + } + } + + function createPickCommands(classificationPrimitive, pickCommands) { + var primitive = classificationPrimitive._primitive; + var pickOffsets = primitive._pickOffsets; + var length = pickOffsets.length * 3; + pickCommands.length = length; + + var pickIndex = 0; + var uniformMap = primitive._batchTable.getUniformMapCallback()(classificationPrimitive._uniformMap); + + for (var j = 0; j < length; j += 3) { + var pickOffset = pickOffsets[pickIndex++]; + + var offset = pickOffset.offset; + var count = pickOffset.count; + var vertexArray = primitive._va[pickOffset.index]; + + // stencil preload command + var command = pickCommands[j]; + if (!defined(command)) { + command = pickCommands[j] = new DrawCommand({ + owner : classificationPrimitive, + primitiveType : primitive._primitiveType + }); + } + + command.vertexArray = vertexArray; + command.offset = offset; + command.count = count; + command.renderState = classificationPrimitive._rsStencilPreloadPass; + command.shaderProgram = classificationPrimitive._sp; + command.uniformMap = uniformMap; + command.pass = Pass.GROUND; + + // stencil depth command + command = pickCommands[j + 1]; + if (!defined(command)) { + command = pickCommands[j + 1] = new DrawCommand({ + owner : classificationPrimitive, + primitiveType : primitive._primitiveType + }); + } + + command.vertexArray = vertexArray; + command.offset = offset; + command.count = count; + command.renderState = classificationPrimitive._rsStencilDepthPass; + command.shaderProgram = classificationPrimitive._sp; + command.uniformMap = uniformMap; + command.pass = Pass.GROUND; + + // color command + command = pickCommands[j + 2]; + if (!defined(command)) { + command = pickCommands[j + 2] = new DrawCommand({ + owner : classificationPrimitive, + primitiveType : primitive._primitiveType + }); + } + + command.vertexArray = vertexArray; + command.offset = offset; + command.count = count; + command.renderState = classificationPrimitive._rsPickPass; + command.shaderProgram = classificationPrimitive._spPick; + command.uniformMap = uniformMap; + command.pass = Pass.GROUND; + } + } + + function createCommands(classificationPrimitive, appearance, material, translucent, twoPasses, colorCommands, pickCommands) { + createColorCommands(classificationPrimitive, colorCommands); + createPickCommands(classificationPrimitive, pickCommands); + } + + function updateAndQueueCommands(classificationPrimitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses) { + var primitive = classificationPrimitive._primitive; + Primitive._updateBoundingVolumes(primitive, frameState, modelMatrix); + + var boundingVolumes; + if (frameState.mode === SceneMode.SCENE3D) { + boundingVolumes = primitive._boundingSphereWC; + } else if (frameState.mode === SceneMode.COLUMBUS_VIEW) { + boundingVolumes = primitive._boundingSphereCV; + } else if (frameState.mode === SceneMode.SCENE2D && defined(primitive._boundingSphere2D)) { + boundingVolumes = primitive._boundingSphere2D; + } else if (defined(primitive._boundingSphereMorph)) { + boundingVolumes = primitive._boundingSphereMorph; + } + + var commandList = frameState.commandList; + var passes = frameState.passes; + if (passes.render) { + var colorLength = colorCommands.length; + for (var i = 0; i < colorLength; ++i) { + var colorCommand = colorCommands[i]; + colorCommand.modelMatrix = modelMatrix; + colorCommand.boundingVolume = boundingVolumes[Math.floor(i / 3)]; + colorCommand.cull = cull; + colorCommand.debugShowBoundingVolume = debugShowBoundingVolume; + + commandList.push(colorCommand); + } + } + + if (passes.pick) { + var pickOffsets = primitive._pickOffsets; + var length = pickOffsets.length * 3; + pickCommands.length = length; + + for (var j = 0; j < length; ++j) { + var pickOffset = pickOffsets[Math.floor(j / 3)]; + var pickCommand = pickCommands[j]; + pickCommand.modelMatrix = modelMatrix; + pickCommand.boundingVolume = boundingVolumes[pickOffset.index]; + pickCommand.cull = cull; + + commandList.push(pickCommand); + } + } + } + + /** + * Called when {@link Viewer} or {@link CesiumWidget} render the scene to + * get the draw commands needed to render this primitive. + *

+ * Do not call this function directly. This is documented just to + * list the exceptions that may be propagated when the scene is rendered: + *

+ * + * @exception {DeveloperError} All instance geometries must have the same primitiveType. + * @exception {DeveloperError} Appearance and material have a uniform with the same name. + * @exception {DeveloperError} Not all of the geometry instances have the same color attribute. + */ + ClassificationPrimitive.prototype.update = function(frameState) { + if (!this.show || (!defined(this._primitive) && !defined(this.geometryInstances))) { + return; + } + + var that = this; + var primitiveOptions = this._primitiveOptions; + + if (!defined(this._primitive)) { + var instances = isArray(this.geometryInstances) ? this.geometryInstances : [this.geometryInstances]; + var length = instances.length; + + var i; + var instance; + //>>includeStart('debug', pragmas.debug); + var color; + for (i = 0; i < length; ++i) { + instance = instances[i]; + var attributes = instance.attributes; + if (!defined(attributes) || !defined(attributes.color)) { + throw new DeveloperError('Not all of the geometry instances have the same color attribute.'); + } else if (defined(color) && !ColorGeometryInstanceAttribute.equals(color, attributes.color)) { + throw new DeveloperError('Not all of the geometry instances have the same color attribute.'); + } else if (!defined(color)) { + color = attributes.color; + } + } + //>>includeEnd('debug'); + + var geometryInstances = new Array(length); + for (i = 0; i < length; ++i) { + instance = instances[i]; + geometryInstances[i] = new GeometryInstance({ + geometry : instance.geometry, + attributes : instance.attributes, + modelMatrix : instance.modelMatrix, + id : instance.id, + pickPrimitive : defaultValue(this._pickPrimitive, that) + }); + } + + primitiveOptions.geometryInstances = geometryInstances; + + if (defined(this._createBoundingVolumeFunction)) { + primitiveOptions._createBoundingVolumeFunction = function(frameState, geometry) { + that._createBoundingVolumeFunction(frameState, geometry); + }; + } + + primitiveOptions._createRenderStatesFunction = function(primitive, context, appearance, twoPasses) { + createRenderStates(that, context); + }; + primitiveOptions._createShaderProgramFunction = function(primitive, frameState, appearance) { + createShaderProgram(that, frameState); + }; + primitiveOptions._createCommandsFunction = function(primitive, appearance, material, translucent, twoPasses, colorCommands, pickCommands) { + createCommands(that, undefined, undefined, true, false, colorCommands, pickCommands); + }; + + if (defined(this._updateAndQueueCommandsFunction)) { + primitiveOptions._updateAndQueueCommandsFunction = function(primitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses) { + that._updateAndQueueCommandsFunction(primitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses); + }; + } else { + primitiveOptions._updateAndQueueCommandsFunction = function(primitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses) { + updateAndQueueCommands(that, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses); + }; + } + + this._primitive = new Primitive(primitiveOptions); + this._primitive.readyPromise.then(function(primitive) { + that._ready = true; + + if (that.releaseGeometryInstances) { + that.geometryInstances = undefined; + } + + var error = primitive._error; + if (!defined(error)) { + that._readyPromise.resolve(that); + } else { + that._readyPromise.reject(error); + } + }); + } + + if (this.debugShowShadowVolume && !this._debugShowShadowVolume && this._ready) { + this._debugShowShadowVolume = true; + this._rsStencilPreloadPass = RenderState.fromCache(getStencilPreloadRenderState(false)); + this._rsStencilDepthPass = RenderState.fromCache(getStencilDepthRenderState(false)); + this._rsColorPass = RenderState.fromCache(getColorRenderState(false)); + } else if (!this.debugShowShadowVolume && this._debugShowShadowVolume) { + this._debugShowShadowVolume = false; + this._rsStencilPreloadPass = RenderState.fromCache(getStencilPreloadRenderState(true)); + this._rsStencilDepthPass = RenderState.fromCache(getStencilDepthRenderState(true)); + this._rsColorPass = RenderState.fromCache(getColorRenderState(true)); + } + + this._primitive.debugShowBoundingVolume = this.debugShowBoundingVolume; + this._primitive.update(frameState); + }; + + /** + * Returns the modifiable per-instance attributes for a {@link GeometryInstance}. + * + * @param {Object} id The id of the {@link GeometryInstance}. + * @returns {Object} The typed array in the attribute's format or undefined if the is no instance with id. + * + * @exception {DeveloperError} must call update before calling getGeometryInstanceAttributes. + * + * @example + * var attributes = primitive.getGeometryInstanceAttributes('an id'); + * attributes.color = Cesium.ColorGeometryInstanceAttribute.toValue(Cesium.Color.AQUA); + * attributes.show = Cesium.ShowGeometryInstanceAttribute.toValue(true); + */ + ClassificationPrimitive.prototype.getGeometryInstanceAttributes = function(id) { + //>>includeStart('debug', pragmas.debug); + if (!defined(this._primitive)) { + throw new DeveloperError('must call update before calling getGeometryInstanceAttributes'); + } + //>>includeEnd('debug'); + return this._primitive.getGeometryInstanceAttributes(id); + }; + + /** + * Returns true if this object was destroyed; otherwise, false. + *

+ * If this object was destroyed, it should not be used; calling any function other than + * isDestroyed will result in a {@link DeveloperError} exception. + *

+ * + * @returns {Boolean} true if this object was destroyed; otherwise, false. + * + * @see ClassificationPrimitive#destroy + */ + ClassificationPrimitive.prototype.isDestroyed = function() { + return false; + }; + + /** + * 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. + *

+ * Once an object is destroyed, it should not be used; calling any function other than + * isDestroyed will result in a {@link DeveloperError} exception. Therefore, + * assign the return value (undefined) to the object as done in the example. + *

+ * + * @returns {undefined} + * + * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. + * + * @example + * e = e && e.destroy(); + * + * @see ClassificationPrimitive#isDestroyed + */ + ClassificationPrimitive.prototype.destroy = function() { + this._primitive = this._primitive && this._primitive.destroy(); + this._sp = this._sp && this._sp.destroy(); + this._spPick = this._spPick && this._spPick.destroy(); + return destroyObject(this); + }; + + return ClassificationPrimitive; +}); diff --git a/Source/Scene/DebugCameraPrimitive.js b/Source/Scene/DebugCameraPrimitive.js index 32a8b5681362..02f990da1353 100644 --- a/Source/Scene/DebugCameraPrimitive.js +++ b/Source/Scene/DebugCameraPrimitive.js @@ -1,37 +1,39 @@ define([ - '../Core/BoundingSphere', '../Core/Cartesian3', - '../Core/Cartesian4', '../Core/Color', '../Core/ColorGeometryInstanceAttribute', - '../Core/ComponentDatatype', '../Core/defaultValue', '../Core/defined', '../Core/destroyObject', '../Core/DeveloperError', - '../Core/GeometryAttribute', - '../Core/GeometryAttributes', + '../Core/FrustumGeometry', + '../Core/FrustumOutlineGeometry', '../Core/GeometryInstance', - '../Core/Matrix4', - '../Core/PrimitiveType', + '../Core/Matrix3', + '../Core/OrthographicFrustum', + '../Core/OrthographicOffCenterFrustum', + '../Core/PerspectiveFrustum', + '../Core/PerspectiveOffCenterFrustum', + '../Core/Quaternion', './PerInstanceColorAppearance', './Primitive' ], function( - BoundingSphere, Cartesian3, - Cartesian4, Color, ColorGeometryInstanceAttribute, - ComponentDatatype, defaultValue, defined, destroyObject, DeveloperError, - GeometryAttribute, - GeometryAttributes, + FrustumGeometry, + FrustumOutlineGeometry, GeometryInstance, - Matrix4, - PrimitiveType, + Matrix3, + OrthographicFrustum, + OrthographicOffCenterFrustum, + PerspectiveFrustum, + PerspectiveOffCenterFrustum, + Quaternion, PerInstanceColorAppearance, Primitive) { 'use strict'; @@ -87,24 +89,21 @@ define([ this.id = options.id; this._id = undefined; - this._outlinePrimitive = undefined; - this._planesPrimitive = undefined; + this._outlinePrimitives = []; + this._planesPrimitives = []; } - var frustumCornersNDC = new Array(4); - frustumCornersNDC[0] = new Cartesian4(-1.0, -1.0, 1.0, 1.0); - frustumCornersNDC[1] = new Cartesian4(1.0, -1.0, 1.0, 1.0); - frustumCornersNDC[2] = new Cartesian4(1.0, 1.0, 1.0, 1.0); - frustumCornersNDC[3] = new Cartesian4(-1.0, 1.0, 1.0, 1.0); - - var scratchMatrix = new Matrix4(); - var scratchFrustumCorners = new Array(4); - for (var i = 0; i < 4; ++i) { - scratchFrustumCorners[i] = new Cartesian4(); - } + var scratchRight = new Cartesian3(); + var scratchRotation = new Matrix3(); + var scratchOrientation = new Quaternion(); + var scratchPerspective = new PerspectiveFrustum(); + var scratchPerspectiveOffCenter = new PerspectiveOffCenterFrustum(); + var scratchOrthographic = new OrthographicFrustum(); + var scratchOrthographicOffCenter = new OrthographicOffCenterFrustum(); var scratchColor = new Color(); var scratchSplits = [1.0, 100000.0]; + /** * @private */ @@ -113,15 +112,36 @@ define([ return; } + var planesPrimitives = this._planesPrimitives; + var outlinePrimitives = this._outlinePrimitives; + var i; + var length; + if (this._updateOnChange) { // Recreate the primitive every frame - this._outlinePrimitive = this._outlinePrimitive && this._outlinePrimitive.destroy(); - this._planesPrimitive = this._planesPrimitive && this._planesPrimitive.destroy(); + length = planesPrimitives.length; + for (i = 0; i < length; ++i) { + outlinePrimitives[i] = outlinePrimitives[i] && outlinePrimitives[i].destroy(); + planesPrimitives[i] = planesPrimitives[i] && planesPrimitives[i].destroy(); + } + planesPrimitives.length = 0; + outlinePrimitives.length = 0; } - if (!defined(this._outlinePrimitive)) { + if (planesPrimitives.length === 0) { var camera = this._camera; - var frustum = camera.frustum; + var cameraFrustum = camera.frustum; + var frustum; + if (cameraFrustum instanceof PerspectiveFrustum) { + frustum = scratchPerspective; + } else if (cameraFrustum instanceof PerspectiveOffCenterFrustum) { + frustum = scratchPerspectiveOffCenter; + } else if (cameraFrustum instanceof OrthographicFrustum) { + frustum = scratchOrthographic; + } else { + frustum = scratchOrthographicOffCenter; + } + frustum = cameraFrustum.clone(frustum); var frustumSplits = frameState.frustumSplits; var numFrustums = frustumSplits.length - 1; @@ -132,200 +152,74 @@ define([ numFrustums = 1; } - var view = this._camera.viewMatrix; - var inverseView; - var inverseViewProjection; - if (defined(camera.frustum.fovy)) { - var projection = this._camera.frustum.projectionMatrix; - var viewProjection = Matrix4.multiply(projection, view, scratchMatrix); - inverseViewProjection = Matrix4.inverse(viewProjection, scratchMatrix); - } else { - inverseView = Matrix4.inverseTransformation(view, scratchMatrix); - } - - - var positions = new Float64Array(3 * 4 * (numFrustums + 1)); - var f; - for (f = 0; f < numFrustums + 1; ++f) { - for (var i = 0; i < 4; ++i) { - var corner = Cartesian4.clone(frustumCornersNDC[i], scratchFrustumCorners[i]); - - if (!defined(inverseViewProjection)) { - if (defined(frustum._offCenterFrustum)) { - frustum = frustum._offCenterFrustum; - } - - var near; - var far; - if (f === numFrustums) { - near = frustumSplits[f - 1]; - far = frustumSplits[f]; - } else { - near = frustumSplits[f]; - far = frustumSplits[f + 1]; - } - corner.x = (corner.x * (frustum.right - frustum.left) + frustum.left + frustum.right) * 0.5; - corner.y = (corner.y * (frustum.top - frustum.bottom) + frustum.bottom + frustum.top) * 0.5; - corner.z = (corner.z * (near - far) - near - far) * 0.5; - corner.w = 1.0; - - Matrix4.multiplyByVector(inverseView, corner, corner); - } else { - corner = Matrix4.multiplyByVector(inverseViewProjection, corner, corner); - - // Reverse perspective divide - var w = 1.0 / corner.w; - Cartesian3.multiplyByScalar(corner, w, corner); - - Cartesian3.subtract(corner, this._camera.positionWC, corner); - Cartesian3.normalize(corner, corner); - - var fac = Cartesian3.dot(this._camera.directionWC, corner); - Cartesian3.multiplyByScalar(corner, frustumSplits[f] / fac, corner); - Cartesian3.add(corner, this._camera.positionWC, corner); - } - - positions[12 * f + i * 3] = corner.x; - positions[12 * f + i * 3 + 1] = corner.y; - positions[12 * f + i * 3 + 2] = corner.z; - } - } - - var boundingSphere = new BoundingSphere.fromVertices(positions); - - var attributes = new GeometryAttributes(); - attributes.position = new GeometryAttribute({ - componentDatatype : ComponentDatatype.DOUBLE, - componentsPerAttribute : 3, - values : positions - }); - - var offset, index; - - // Create the outline primitive - var outlineIndices = new Uint16Array(8 * (2 * numFrustums + 1)); - // Build the far planes - for (f = 0; f < numFrustums + 1; ++f) { - offset = f * 8; - index = f * 4; - - outlineIndices[offset] = index; - outlineIndices[offset + 1] = index + 1; - outlineIndices[offset + 2] = index + 1; - outlineIndices[offset + 3] = index + 2; - outlineIndices[offset + 4] = index + 2; - outlineIndices[offset + 5] = index + 3; - outlineIndices[offset + 6] = index + 3; - outlineIndices[offset + 7] = index; - } - // Build the sides of the frustums - for (f = 0; f < numFrustums; ++f) { - offset = (numFrustums + 1 + f) * 8; - index = f * 4; - - outlineIndices[offset] = index; - outlineIndices[offset + 1] = index + 4; - outlineIndices[offset + 2] = index + 1; - outlineIndices[offset + 3] = index + 5; - outlineIndices[offset + 4] = index + 2; - outlineIndices[offset + 5] = index + 6; - outlineIndices[offset + 6] = index + 3; - outlineIndices[offset + 7] = index + 7; + var position = camera.positionWC; + var direction = camera.directionWC; + var up = camera.upWC; + var right = camera.rightWC; + right = Cartesian3.negate(right, scratchRight); + + var rotation = scratchRotation; + Matrix3.setColumn(rotation, 0, right, rotation); + Matrix3.setColumn(rotation, 1, up, rotation); + Matrix3.setColumn(rotation, 2, direction, rotation); + + var orientation = Quaternion.fromRotationMatrix(rotation, scratchOrientation); + + planesPrimitives.length = outlinePrimitives.length = numFrustums; + + for (i = 0; i < numFrustums; ++i) { + frustum.near = frustumSplits[i]; + frustum.far = frustumSplits[i + 1]; + + planesPrimitives[i] = new Primitive({ + geometryInstances : new GeometryInstance({ + geometry : new FrustumGeometry({ + origin : position, + orientation : orientation, + frustum : frustum, + _drawNearPlane : i === 0 + }), + attributes : { + color : ColorGeometryInstanceAttribute.fromColor(Color.fromAlpha(this._color, 0.1, scratchColor)) + }, + id : this.id, + pickPrimitive : this + }), + appearance : new PerInstanceColorAppearance({ + translucent : true, + flat : true + }), + asynchronous : false + }); + + outlinePrimitives[i] = new Primitive({ + geometryInstances : new GeometryInstance({ + geometry : new FrustumOutlineGeometry({ + origin : position, + orientation : orientation, + frustum : frustum, + _drawNearPlane : i === 0 + }), + attributes : { + color : ColorGeometryInstanceAttribute.fromColor(this._color) + }, + id : this.id, + pickPrimitive : this + }), + appearance : new PerInstanceColorAppearance({ + translucent : false, + flat : true + }), + asynchronous : false + }); } - - this._outlinePrimitive = new Primitive({ - geometryInstances : new GeometryInstance({ - geometry : { - attributes : attributes, - indices : outlineIndices, - primitiveType : PrimitiveType.LINES, - boundingSphere : boundingSphere - }, - attributes : { - color : ColorGeometryInstanceAttribute.fromColor(this._color) - }, - id : this.id, - pickPrimitive : this - }), - appearance : new PerInstanceColorAppearance({ - translucent : false, - flat : true - }), - asynchronous : false - }); - - // Create the planes primitive - var planesIndices = new Uint16Array(6 * (5 * numFrustums + 1)); - // Build the far planes - for (f = 0; f < numFrustums + 1; ++f) { - offset = f * 6; - index = f * 4; - - planesIndices[offset] = index; - planesIndices[offset + 1] = index + 1; - planesIndices[offset + 2] = index + 2; - planesIndices[offset + 3] = index; - planesIndices[offset + 4] = index + 2; - planesIndices[offset + 5] = index + 3; - } - // Build the sides of the frustums - for (f = 0; f < numFrustums; ++f) { - offset = (numFrustums + 1 + 4 * f) * 6; - index = f * 4; - - planesIndices[offset] = index + 4; - planesIndices[offset + 1] = index; - planesIndices[offset + 2] = index + 3; - planesIndices[offset + 3] = index + 4; - planesIndices[offset + 4] = index + 3; - planesIndices[offset + 5] = index + 7; - - planesIndices[offset + 6] = index + 4; - planesIndices[offset + 7] = index; - planesIndices[offset + 8] = index + 1; - planesIndices[offset + 9] = index + 4; - planesIndices[offset + 10] = index + 1; - planesIndices[offset + 11] = index + 5; - - planesIndices[offset + 12] = index + 7; - planesIndices[offset + 13] = index + 3; - planesIndices[offset + 14] = index + 2; - planesIndices[offset + 15] = index + 7; - planesIndices[offset + 16] = index + 2; - planesIndices[offset + 17] = index + 6; - - planesIndices[offset + 18] = index + 6; - planesIndices[offset + 19] = index + 2; - planesIndices[offset + 20] = index + 1; - planesIndices[offset + 21] = index + 6; - planesIndices[offset + 22] = index + 1; - planesIndices[offset + 23] = index + 5; - } - - this._planesPrimitive = new Primitive({ - geometryInstances : new GeometryInstance({ - geometry : { - attributes : attributes, - indices : planesIndices, - primitiveType : PrimitiveType.TRIANGLES, - boundingSphere : boundingSphere - }, - attributes : { - color : ColorGeometryInstanceAttribute.fromColor(Color.fromAlpha(this._color, 0.1, scratchColor)) - }, - id : this.id, - pickPrimitive : this - }), - appearance : new PerInstanceColorAppearance({ - translucent : true, - flat : true - }), - asynchronous : false - }); } - this._outlinePrimitive.update(frameState); - this._planesPrimitive.update(frameState); + length = planesPrimitives.length; + for (i = 0; i < length; ++i) { + outlinePrimitives[i].update(frameState); + planesPrimitives[i].update(frameState); + } }; /** @@ -362,8 +256,11 @@ define([ * @see DebugCameraPrimitive#isDestroyed */ DebugCameraPrimitive.prototype.destroy = function() { - this._outlinePrimitive = this._outlinePrimitive && this._outlinePrimitive.destroy(); - this._planesPrimitive = this._planesPrimitive && this._planesPrimitive.destroy(); + var length = this._planesPrimitives.length; + for (var i = 0; i < length; ++i) { + this._outlinePrimitives[i] = this._outlinePrimitives[i] && this._outlinePrimitives[i].destroy(); + this._planesPrimitives[i] = this._planesPrimitives[i] && this._planesPrimitives[i].destroy(); + } return destroyObject(this); }; diff --git a/Source/Scene/GroundPrimitive.js b/Source/Scene/GroundPrimitive.js index 6deff85dbfad..9a46f35d16c4 100644 --- a/Source/Scene/GroundPrimitive.js +++ b/Source/Scene/GroundPrimitive.js @@ -4,7 +4,6 @@ define([ '../Core/Cartesian2', '../Core/Cartesian3', '../Core/Cartographic', - '../Core/ColorGeometryInstanceAttribute', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', @@ -17,28 +16,15 @@ define([ '../Core/Math', '../Core/OrientedBoundingBox', '../Core/Rectangle', - '../Renderer/DrawCommand', - '../Renderer/Pass', - '../Renderer/RenderState', - '../Renderer/ShaderProgram', - '../Renderer/ShaderSource', - '../Shaders/ShadowVolumeFS', - '../Shaders/ShadowVolumeVS', '../ThirdParty/when', - './BlendingState', - './DepthFunction', - './PerInstanceColorAppearance', - './Primitive', - './SceneMode', - './StencilFunction', - './StencilOperation' + './ClassificationPrimitive', + './SceneMode' ], function( BoundingSphere, buildModuleUrl, Cartesian2, Cartesian3, Cartographic, - ColorGeometryInstanceAttribute, defaultValue, defined, defineProperties, @@ -51,23 +37,17 @@ define([ CesiumMath, OrientedBoundingBox, Rectangle, - DrawCommand, - Pass, - RenderState, - ShaderProgram, - ShaderSource, - ShadowVolumeFS, - ShadowVolumeVS, when, - BlendingState, - DepthFunction, - PerInstanceColorAppearance, - Primitive, - SceneMode, - StencilFunction, - StencilOperation) { + ClassificationPrimitive, + SceneMode) { 'use strict'; + var GroundPrimitiveUniformMap = { + u_globeMinimumAltitude: function() { + return 55000.0; + } + }; + /** * A ground primitive represents geometry draped over the terrain in the {@link Scene}. The geometry must be from a single {@link GeometryInstance}. * Batching multiple geometries is not yet supported. @@ -144,6 +124,7 @@ define([ * })); * * @see Primitive + * @see ClassificationPrimitive * @see GeometryInstance * @see Appearance */ @@ -163,6 +144,7 @@ define([ * on the first attempt to render. *

* + * @readonly * @type {Array|GeometryInstance} * * @default undefined @@ -200,21 +182,6 @@ define([ * @default false */ this.debugShowShadowVolume = defaultValue(options.debugShowShadowVolume, false); - this._debugShowShadowVolume = false; - - this._sp = undefined; - this._spPick = undefined; - - this._rsStencilPreloadPass = undefined; - this._rsStencilDepthPass = undefined; - this._rsColorPass = undefined; - this._rsPickPass = undefined; - - this._uniformMap = { - u_globeMinimumAltitude: function() { - return 55000.0; - } - }; this._boundingVolumes = []; this._boundingVolumes2D = []; @@ -233,31 +200,20 @@ define([ this._boundingSpheresKeys = []; this._boundingSpheres = []; - var appearance = new PerInstanceColorAppearance({ - flat : true - }); - - var readOnlyAttributes; - var readOnlyInstanceAttributesScratch = ['color']; - - if (defined(this.geometryInstances) && isArray(this.geometryInstances) && this.geometryInstances.length > 1) { - readOnlyAttributes = readOnlyInstanceAttributesScratch; - } - + var that = this; this._primitiveOptions = { geometryInstances : undefined, - appearance : appearance, vertexCacheOptimize : defaultValue(options.vertexCacheOptimize, false), interleave : defaultValue(options.interleave, false), releaseGeometryInstances : defaultValue(options.releaseGeometryInstances, true), allowPicking : defaultValue(options.allowPicking, true), asynchronous : defaultValue(options.asynchronous, true), compressVertices : defaultValue(options.compressVertices, true), - _readOnlyInstanceAttributes : readOnlyAttributes, - _createRenderStatesFunction : undefined, - _createShaderProgramFunction : undefined, - _createCommandsFunction : undefined, - _createPickOffsets : true + _createBoundingVolumeFunction : undefined, + _updateAndQueueCommandsFunction : undefined, + _pickPrimitive : that, + _extruded : true, + _uniformMap : GroundPrimitiveUniformMap }; } @@ -393,9 +349,7 @@ define([ * @param {Scene} scene The scene. * @returns {Boolean} true if GroundPrimitives are supported; otherwise, returns false */ - GroundPrimitive.isSupported = function(scene) { - return scene.context.stencilBuffer; - }; + GroundPrimitive.isSupported = ClassificationPrimitive.isSupported; GroundPrimitive._defaultMaxTerrainHeight = 9000.0; GroundPrimitive._defaultMinTerrainHeight = -100000.0; @@ -417,123 +371,6 @@ define([ }; } - function getStencilPreloadRenderState(enableStencil) { - return { - colorMask : { - red : false, - green : false, - blue : false, - alpha : false - }, - stencilTest : { - enabled : enableStencil, - frontFunction : StencilFunction.ALWAYS, - frontOperation : { - fail : StencilOperation.KEEP, - zFail : StencilOperation.DECREMENT_WRAP, - zPass : StencilOperation.DECREMENT_WRAP - }, - backFunction : StencilFunction.ALWAYS, - backOperation : { - fail : StencilOperation.KEEP, - zFail : StencilOperation.INCREMENT_WRAP, - zPass : StencilOperation.INCREMENT_WRAP - }, - reference : 0, - mask : ~0 - }, - depthTest : { - enabled : false - }, - depthMask : false - }; - } - - function getStencilDepthRenderState(enableStencil) { - return { - colorMask : { - red : false, - green : false, - blue : false, - alpha : false - }, - stencilTest : { - enabled : enableStencil, - frontFunction : StencilFunction.ALWAYS, - frontOperation : { - fail : StencilOperation.KEEP, - zFail : StencilOperation.KEEP, - zPass : StencilOperation.INCREMENT_WRAP - }, - backFunction : StencilFunction.ALWAYS, - backOperation : { - fail : StencilOperation.KEEP, - zFail : StencilOperation.KEEP, - zPass : StencilOperation.DECREMENT_WRAP - }, - reference : 0, - mask : ~0 - }, - depthTest : { - enabled : true, - func : DepthFunction.LESS_OR_EQUAL - }, - depthMask : false - }; - } - - - function getColorRenderState(enableStencil) { - return { - stencilTest : { - enabled : enableStencil, - frontFunction : StencilFunction.NOT_EQUAL, - frontOperation : { - fail : StencilOperation.KEEP, - zFail : StencilOperation.KEEP, - zPass : StencilOperation.DECREMENT_WRAP - }, - backFunction : StencilFunction.NOT_EQUAL, - backOperation : { - fail : StencilOperation.KEEP, - zFail : StencilOperation.KEEP, - zPass : StencilOperation.DECREMENT_WRAP - }, - reference : 0, - mask : ~0 - }, - depthTest : { - enabled : false - }, - depthMask : false, - blending : BlendingState.ALPHA_BLEND - }; - } - - var pickRenderState = { - stencilTest : { - enabled : true, - frontFunction : StencilFunction.NOT_EQUAL, - frontOperation : { - fail : StencilOperation.KEEP, - zFail : StencilOperation.KEEP, - zPass : StencilOperation.DECREMENT_WRAP - }, - backFunction : StencilFunction.NOT_EQUAL, - backOperation : { - fail : StencilOperation.KEEP, - zFail : StencilOperation.KEEP, - zPass : StencilOperation.DECREMENT_WRAP - }, - reference : 0, - mask : ~0 - }, - depthTest : { - enabled : false - }, - depthMask : false - }; - var scratchBVCartesianHigh = new Cartesian3(); var scratchBVCartesianLow = new Cartesian3(); var scratchBVCartesian = new Cartesian3(); @@ -717,229 +554,6 @@ define([ } } - function createRenderStates(groundPrimitive, context, appearance, twoPasses) { - if (defined(groundPrimitive._rsStencilPreloadPass)) { - return; - } - var stencilEnabled = !groundPrimitive.debugShowShadowVolume; - - groundPrimitive._rsStencilPreloadPass = RenderState.fromCache(getStencilPreloadRenderState(stencilEnabled)); - groundPrimitive._rsStencilDepthPass = RenderState.fromCache(getStencilDepthRenderState(stencilEnabled)); - groundPrimitive._rsColorPass = RenderState.fromCache(getColorRenderState(stencilEnabled)); - groundPrimitive._rsPickPass = RenderState.fromCache(pickRenderState); - } - - function modifyForEncodedNormals(primitive, vertexShaderSource) { - if (!primitive.compressVertices) { - return vertexShaderSource; - } - - if (vertexShaderSource.search(/attribute\s+vec3\s+extrudeDirection;/g) !== -1) { - var attributeName = 'compressedAttributes'; - - //only shadow volumes use extrudeDirection, and shadow volumes use vertexFormat: POSITION_ONLY so we don't need to check other attributes - var attributeDecl = 'attribute vec2 ' + attributeName + ';'; - - var globalDecl = 'vec3 extrudeDirection;\n'; - var decode = ' extrudeDirection = czm_octDecode(' + attributeName + ', 65535.0);\n'; - - var modifiedVS = vertexShaderSource; - modifiedVS = modifiedVS.replace(/attribute\s+vec3\s+extrudeDirection;/g, ''); - modifiedVS = ShaderSource.replaceMain(modifiedVS, 'czm_non_compressed_main'); - var compressedMain = - 'void main() \n' + - '{ \n' + - decode + - ' czm_non_compressed_main(); \n' + - '}'; - - return [attributeDecl, globalDecl, modifiedVS, compressedMain].join('\n'); - } - } - - function createShaderProgram(groundPrimitive, frameState, appearance) { - if (defined(groundPrimitive._sp)) { - return; - } - - var context = frameState.context; - var primitive = groundPrimitive._primitive; - var vs = ShadowVolumeVS; - vs = groundPrimitive._primitive._batchTable.getVertexShaderCallback()(vs); - vs = Primitive._appendShowToShader(primitive, vs); - vs = Primitive._appendDistanceDisplayConditionToShader(primitive, vs); - vs = Primitive._modifyShaderPosition(groundPrimitive, vs, frameState.scene3DOnly); - vs = Primitive._updateColorAttribute(primitive, vs); - vs = modifyForEncodedNormals(primitive, vs); - - var fs = ShadowVolumeFS; - var attributeLocations = groundPrimitive._primitive._attributeLocations; - - groundPrimitive._sp = ShaderProgram.replaceCache({ - context : context, - shaderProgram : groundPrimitive._sp, - vertexShaderSource : vs, - fragmentShaderSource : fs, - attributeLocations : attributeLocations - }); - - if (groundPrimitive._primitive.allowPicking) { - var vsPick = ShaderSource.createPickVertexShaderSource(vs); - vsPick = Primitive._updatePickColorAttribute(vsPick); - - var pickFS = new ShaderSource({ - sources : [fs], - pickColorQualifier : 'varying' - }); - groundPrimitive._spPick = ShaderProgram.replaceCache({ - context : context, - shaderProgram : groundPrimitive._spPick, - vertexShaderSource : vsPick, - fragmentShaderSource : pickFS, - attributeLocations : attributeLocations - }); - } else { - groundPrimitive._spPick = ShaderProgram.fromCache({ - context : context, - vertexShaderSource : vs, - fragmentShaderSource : fs, - attributeLocations : attributeLocations - }); - } - } - - function createColorCommands(groundPrimitive, colorCommands) { - var primitive = groundPrimitive._primitive; - var length = primitive._va.length * 3; - colorCommands.length = length; - - var vaIndex = 0; - var uniformMap = primitive._batchTable.getUniformMapCallback()(groundPrimitive._uniformMap); - - for (var i = 0; i < length; i += 3) { - var vertexArray = primitive._va[vaIndex++]; - - // stencil preload command - var command = colorCommands[i]; - if (!defined(command)) { - command = colorCommands[i] = new DrawCommand({ - owner : groundPrimitive, - primitiveType : primitive._primitiveType - }); - } - - command.vertexArray = vertexArray; - command.renderState = groundPrimitive._rsStencilPreloadPass; - command.shaderProgram = groundPrimitive._sp; - command.uniformMap = uniformMap; - command.pass = Pass.GROUND; - - // stencil depth command - command = colorCommands[i + 1]; - if (!defined(command)) { - command = colorCommands[i + 1] = new DrawCommand({ - owner : groundPrimitive, - primitiveType : primitive._primitiveType - }); - } - - command.vertexArray = vertexArray; - command.renderState = groundPrimitive._rsStencilDepthPass; - command.shaderProgram = groundPrimitive._sp; - command.uniformMap = uniformMap; - command.pass = Pass.GROUND; - - // color command - command = colorCommands[i + 2]; - if (!defined(command)) { - command = colorCommands[i + 2] = new DrawCommand({ - owner : groundPrimitive, - primitiveType : primitive._primitiveType - }); - } - - command.vertexArray = vertexArray; - command.renderState = groundPrimitive._rsColorPass; - command.shaderProgram = groundPrimitive._sp; - command.uniformMap = uniformMap; - command.pass = Pass.GROUND; - } - } - - function createPickCommands(groundPrimitive, pickCommands) { - var primitive = groundPrimitive._primitive; - var pickOffsets = primitive._pickOffsets; - var length = pickOffsets.length * 3; - pickCommands.length = length; - - var pickIndex = 0; - var uniformMap = primitive._batchTable.getUniformMapCallback()(groundPrimitive._uniformMap); - - for (var j = 0; j < length; j += 3) { - var pickOffset = pickOffsets[pickIndex++]; - - var offset = pickOffset.offset; - var count = pickOffset.count; - var vertexArray = primitive._va[pickOffset.index]; - - // stencil preload command - var command = pickCommands[j]; - if (!defined(command)) { - command = pickCommands[j] = new DrawCommand({ - owner : groundPrimitive, - primitiveType : primitive._primitiveType - }); - } - - command.vertexArray = vertexArray; - command.offset = offset; - command.count = count; - command.renderState = groundPrimitive._rsStencilPreloadPass; - command.shaderProgram = groundPrimitive._sp; - command.uniformMap = uniformMap; - command.pass = Pass.GROUND; - - // stencil depth command - command = pickCommands[j + 1]; - if (!defined(command)) { - command = pickCommands[j + 1] = new DrawCommand({ - owner : groundPrimitive, - primitiveType : primitive._primitiveType - }); - } - - command.vertexArray = vertexArray; - command.offset = offset; - command.count = count; - command.renderState = groundPrimitive._rsStencilDepthPass; - command.shaderProgram = groundPrimitive._sp; - command.uniformMap = uniformMap; - command.pass = Pass.GROUND; - - // color command - command = pickCommands[j + 2]; - if (!defined(command)) { - command = pickCommands[j + 2] = new DrawCommand({ - owner : groundPrimitive, - primitiveType : primitive._primitiveType - }); - } - - command.vertexArray = vertexArray; - command.offset = offset; - command.count = count; - command.renderState = groundPrimitive._rsPickPass; - command.shaderProgram = groundPrimitive._spPick; - command.uniformMap = uniformMap; - command.pass = Pass.GROUND; - } - } - - function createCommands(groundPrimitive, appearance, material, translucent, twoPasses, colorCommands, pickCommands) { - createColorCommands(groundPrimitive, colorCommands); - createPickCommands(groundPrimitive, pickCommands); - } - function updateAndQueueCommands(groundPrimitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses) { var boundingVolumes; if (frameState.mode === SceneMode.SCENE3D) { @@ -952,40 +566,35 @@ define([ var passes = frameState.passes; if (passes.render) { var colorLength = colorCommands.length; - for (var j = 0; j < colorLength; ++j) { - colorCommands[j].modelMatrix = modelMatrix; - colorCommands[j].boundingVolume = boundingVolumes[Math.floor(j / 3)]; - colorCommands[j].cull = cull; - colorCommands[j].debugShowBoundingVolume = debugShowBoundingVolume; - - commandList.push(colorCommands[j]); + for (var i = 0; i < colorLength; ++i) { + var colorCommand = colorCommands[i]; + colorCommand.owner = groundPrimitive; + colorCommand.modelMatrix = modelMatrix; + colorCommand.boundingVolume = boundingVolumes[Math.floor(i / 3)]; + colorCommand.cull = cull; + colorCommand.debugShowBoundingVolume = debugShowBoundingVolume; + + commandList.push(colorCommand); } } if (passes.pick) { - var primitive = groundPrimitive._primitive; + var primitive = groundPrimitive._primitive._primitive; var pickOffsets = primitive._pickOffsets; var length = pickOffsets.length * 3; pickCommands.length = length; - var pickIndex = 0; - for (var k = 0; k < length; k += 3) { - var pickOffset = pickOffsets[pickIndex++]; + for (var j = 0; j < length; ++j) { + var pickOffset = pickOffsets[Math.floor(j / 3)]; var bv = boundingVolumes[pickOffset.index]; - pickCommands[k].modelMatrix = modelMatrix; - pickCommands[k].boundingVolume = bv; - pickCommands[k].cull = cull; - - pickCommands[k + 1].modelMatrix = modelMatrix; - pickCommands[k + 1].boundingVolume = bv; - pickCommands[k + 1].cull = cull; - - pickCommands[k + 2].modelMatrix = modelMatrix; - pickCommands[k + 2].boundingVolume = bv; - pickCommands[k + 2].cull = cull; + var pickCommand = pickCommands[j]; + pickCommand.owner = groundPrimitive; + pickCommand.modelMatrix = modelMatrix; + pickCommand.boundingVolume = bv; + pickCommand.cull = cull; - commandList.push(pickCommands[k], pickCommands[k + 1], pickCommands[k + 2]); + commandList.push(pickCommand); } } } @@ -1057,7 +666,6 @@ define([ var groundInstances = new Array(length); var i; - var color; var rectangle; for (i = 0; i < length; ++i) { instance = instances[i]; @@ -1077,19 +685,7 @@ define([ } instanceType = geometry.constructor; - if (defined(instanceType) && defined(instanceType.createShadowVolume)) { - var attributes = instance.attributes; - - //>>includeStart('debug', pragmas.debug); - if (!defined(attributes) || !defined(attributes.color)) { - throw new DeveloperError('Not all of the geometry instances have the same color attribute.'); - } else if (defined(color) && !ColorGeometryInstanceAttribute.equals(color, attributes.color)) { - throw new DeveloperError('Not all of the geometry instances have the same color attribute.'); - } else if (!defined(color)) { - color = attributes.color; - } - //>>includeEnd('debug'); - } else { + if (!defined(instanceType) || !defined(instanceType.createShadowVolume)) { //>>includeStart('debug', pragmas.debug); throw new DeveloperError('Not all of the geometry instances have GroundPrimitive support.'); //>>includeEnd('debug'); @@ -1110,8 +706,7 @@ define([ geometry : instanceType.createShadowVolume(geometry, getComputeMinimumHeightFunction(this), getComputeMaximumHeightFunction(this)), attributes : instance.attributes, - id : instance.id, - pickPrimitive : this + id : instance.id }); } @@ -1120,20 +715,11 @@ define([ primitiveOptions._createBoundingVolumeFunction = function(frameState, geometry) { createBoundingVolume(that, frameState, geometry); }; - primitiveOptions._createRenderStatesFunction = function(primitive, context, appearance, twoPasses) { - createRenderStates(that, context); - }; - primitiveOptions._createShaderProgramFunction = function(primitive, frameState, appearance) { - createShaderProgram(that, frameState); - }; - primitiveOptions._createCommandsFunction = function(primitive, appearance, material, translucent, twoPasses, colorCommands, pickCommands) { - createCommands(that, undefined, undefined, true, false, colorCommands, pickCommands); - }; primitiveOptions._updateAndQueueCommandsFunction = function(primitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses) { updateAndQueueCommands(that, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses); }; - this._primitive = new Primitive(primitiveOptions); + this._primitive = new ClassificationPrimitive(primitiveOptions); this._primitive.readyPromise.then(function(primitive) { that._ready = true; @@ -1150,18 +736,7 @@ define([ }); } - if (this.debugShowShadowVolume && !this._debugShowShadowVolume && this._ready) { - this._debugShowShadowVolume = true; - this._rsStencilPreloadPass = RenderState.fromCache(getStencilPreloadRenderState(false)); - this._rsStencilDepthPass = RenderState.fromCache(getStencilDepthRenderState(false)); - this._rsColorPass = RenderState.fromCache(getColorRenderState(false)); - } else if (!this.debugShowShadowVolume && this._debugShowShadowVolume) { - this._debugShowShadowVolume = false; - this._rsStencilPreloadPass = RenderState.fromCache(getStencilPreloadRenderState(true)); - this._rsStencilDepthPass = RenderState.fromCache(getStencilDepthRenderState(true)); - this._rsColorPass = RenderState.fromCache(getColorRenderState(true)); - } - + this._primitive.debugShowShadowVolume = this.debugShowShadowVolume; this._primitive.debugShowBoundingVolume = this.debugShowBoundingVolume; this._primitive.update(frameState); }; @@ -1235,8 +810,6 @@ define([ */ GroundPrimitive.prototype.destroy = function() { this._primitive = this._primitive && this._primitive.destroy(); - this._sp = this._sp && this._sp.destroy(); - this._spPick = this._spPick && this._spPick.destroy(); return destroyObject(this); }; diff --git a/Source/Scene/Instanced3DModel3DTileContent.js b/Source/Scene/Instanced3DModel3DTileContent.js index df1de7ca0593..429f51d35200 100644 --- a/Source/Scene/Instanced3DModel3DTileContent.js +++ b/Source/Scene/Instanced3DModel3DTileContent.js @@ -21,6 +21,7 @@ define([ '../Core/RuntimeError', '../Core/Transforms', '../Core/TranslationRotationScale', + '../Renderer/Pass', './Cesium3DTileBatchTable', './Cesium3DTileFeature', './Cesium3DTileFeatureTable', @@ -48,6 +49,7 @@ define([ RuntimeError, Transforms, TranslationRotationScale, + Pass, Cesium3DTileBatchTable, Cesium3DTileFeature, Cesium3DTileFeatureTable, @@ -303,11 +305,16 @@ define([ gltf : undefined, basePath : undefined, incrementallyLoadTextures : false, - upAxis : content._tileset._gltfUpAxis + upAxis : content._tileset._gltfUpAxis, + opaquePass : Pass.CESIUM_3D_TILE // Draw opaque portions during the 3D Tiles pass }; if (gltfFormat === 0) { var gltfUrl = getStringFromTypedArray(gltfView); + + // We need to remove padding from the end of the model URL in case this tile was part of a composite tile. + // This removes all white space and null characters from the end of the string. + gltfUrl = gltfUrl.replace(/[\s\0]+$/, ''); collectionOptions.url = getAbsoluteUri(joinUrls(getBaseUri(content._url, true), gltfUrl)); } else { collectionOptions.gltf = gltfView; diff --git a/Source/Scene/Label.js b/Source/Scene/Label.js index 54865f47fde8..813522182a0e 100644 --- a/Source/Scene/Label.js +++ b/Source/Scene/Label.js @@ -68,22 +68,47 @@ define([ options = defaultValue(options, defaultValue.EMPTY_OBJECT); //>>includeStart('debug', pragmas.debug); - if (defined(options.translucencyByDistance) && options.translucencyByDistance.far <= options.translucencyByDistance.near) { - throw new DeveloperError('translucencyByDistance.far must be greater than translucencyByDistance.near.'); + if (defined(options.disableDepthTestDistance) && options.disableDepthTestDistance < 0.0) { + throw new DeveloperError('disableDepthTestDistance must be greater than 0.0.'); } - if (defined(options.pixelOffsetScaleByDistance) && options.pixelOffsetScaleByDistance.far <= options.pixelOffsetScaleByDistance.near) { - throw new DeveloperError('pixelOffsetScaleByDistance.far must be greater than pixelOffsetScaleByDistance.near.'); + //>>includeEnd('debug'); + + var translucencyByDistance = options.translucencyByDistance; + var pixelOffsetScaleByDistance = options.pixelOffsetScaleByDistance; + var scaleByDistance = options.scaleByDistance; + var distanceDisplayCondition = options.distanceDisplayCondition; + if (defined(translucencyByDistance)) { + //>>includeStart('debug', pragmas.debug); + if (translucencyByDistance.far <= translucencyByDistance.near) { + throw new DeveloperError('translucencyByDistance.far must be greater than translucencyByDistance.near.'); + } + //>>includeEnd('debug'); + translucencyByDistance = NearFarScalar.clone(translucencyByDistance); } - if (defined(options.scaleByDistance) && options.scaleByDistance.far <= options.scaleByDistance.near) { - throw new DeveloperError('scaleByDistance.far must be greater than scaleByDistance.near.'); + if (defined(pixelOffsetScaleByDistance)) { + //>>includeStart('debug', pragmas.debug); + if (pixelOffsetScaleByDistance.far <= pixelOffsetScaleByDistance.near) { + throw new DeveloperError('pixelOffsetScaleByDistance.far must be greater than pixelOffsetScaleByDistance.near.'); + } + //>>includeEnd('debug'); + pixelOffsetScaleByDistance = NearFarScalar.clone(pixelOffsetScaleByDistance); } - if (defined(options.distanceDisplayCondition) && options.distanceDisplayCondition.far <= options.distanceDisplayCondition.near) { - throw new DeveloperError('distanceDisplayCondition.far must be greater than distanceDisplayCondition.near'); + if (defined(scaleByDistance)) { + //>>includeStart('debug', pragmas.debug); + if (scaleByDistance.far <= scaleByDistance.near) { + throw new DeveloperError('scaleByDistance.far must be greater than scaleByDistance.near.'); + } + //>>includeEnd('debug'); + scaleByDistance = NearFarScalar.clone(scaleByDistance); } - if (defined(options.disableDepthTestDistance) && options.disableDepthTestDistance < 0.0) { - throw new DeveloperError('disableDepthTestDistance must be greater than 0.0.'); + if (defined(distanceDisplayCondition)) { + //>>includeStart('debug', pragmas.debug); + if (distanceDisplayCondition.far <= distanceDisplayCondition.near) { + throw new DeveloperError('distanceDisplayCondition.far must be greater than distanceDisplayCondition.near.'); + } + //>>includeEnd('debug'); + distanceDisplayCondition = DistanceDisplayCondition.clone(distanceDisplayCondition); } - //>>includeEnd('debug'); this._text = defaultValue(options.text, ''); this._show = defaultValue(options.show, true); @@ -102,11 +127,11 @@ define([ this._position = Cartesian3.clone(defaultValue(options.position, Cartesian3.ZERO)); this._scale = defaultValue(options.scale, 1.0); this._id = options.id; - this._translucencyByDistance = options.translucencyByDistance; - this._pixelOffsetScaleByDistance = options.pixelOffsetScaleByDistance; - this._scaleByDistance = options.scaleByDistance; + this._translucencyByDistance = translucencyByDistance; + this._pixelOffsetScaleByDistance = pixelOffsetScaleByDistance; + this._scaleByDistance = scaleByDistance; this._heightReference = defaultValue(options.heightReference, HeightReference.NONE); - this._distanceDisplayCondition = options.distanceDisplayCondition; + this._distanceDisplayCondition = distanceDisplayCondition; this._disableDepthTestDistance = defaultValue(options.disableDepthTestDistance, 0.0); this._labelCollection = labelCollection; @@ -194,9 +219,7 @@ define([ backgroundBillboard.position = value; } - if (this._heightReference !== HeightReference.NONE) { - this._updateClamping(); - } + this._updateClamping(); } } }, @@ -964,22 +987,17 @@ define([ this._actualClampedPosition = Cartesian3.clone(value, this._actualClampedPosition); var glyphs = this._glyphs; - value = defaultValue(value, this._position); for (var i = 0, len = glyphs.length; i < len; i++) { var glyph = glyphs[i]; if (defined(glyph.billboard)) { // Set all the private values here, because we already clamped to ground // so we don't want to do it again for every glyph glyph.billboard._clampedPosition = value; - Cartesian3.clone(value, glyph.billboard._position); - Cartesian3.clone(value, glyph.billboard._actualPosition); } } var backgroundBillboard = this._backgroundBillboard; if (defined(backgroundBillboard)) { backgroundBillboard._clampedPosition = value; - Cartesian3.clone(value, backgroundBillboard._position); - Cartesian3.clone(value, backgroundBillboard._actualPosition); } } }, diff --git a/Source/Scene/LabelCollection.js b/Source/Scene/LabelCollection.js index c93803a6c255..efb9f4fd11b0 100644 --- a/Source/Scene/LabelCollection.js +++ b/Source/Scene/LabelCollection.js @@ -106,6 +106,10 @@ define([ if (defined(billboard)) { billboard.show = false; billboard.image = undefined; + if (defined(billboard._removeCallbackFunc)) { + billboard._removeCallbackFunc(); + billboard._removeCallbackFunc = undefined; + } labelCollection._spareBillboards.push(billboard); glyph.billboard = undefined; } diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js index 06425037b1f8..0bd2a4d638bf 100644 --- a/Source/Scene/Model.js +++ b/Source/Scene/Model.js @@ -644,6 +644,12 @@ define([ */ this.cull = defaultValue(options.cull, true); + /** + * @private + * @readonly + */ + this.opaquePass = defaultValue(options.opaquePass, Pass.OPAQUE); + this._computedModelMatrix = new Matrix4(); // Derived from modelMatrix and scale this._initialRadius = undefined; // Radius without model's scale property, model-matrix scale, animations, or skins this._boundingSphere = undefined; @@ -3483,7 +3489,7 @@ define([ uniformMap : uniformMap, renderState : rs, owner : owner, - pass : isTranslucent ? Pass.TRANSLUCENT : Pass.OPAQUE + pass : isTranslucent ? Pass.TRANSLUCENT : model.opaquePass }); var pickCommand; @@ -3522,7 +3528,7 @@ define([ uniformMap : pickUniformMap, renderState : rs, owner : owner, - pass : isTranslucent ? Pass.TRANSLUCENT : Pass.OPAQUE + pass : isTranslucent ? Pass.TRANSLUCENT : model.opaquePass }); } @@ -4764,6 +4770,11 @@ define([ destroy(this._rendererResources.vertexArrays); } + if (defined(this._removeUpdateHeightCallback)) { + this._removeUpdateHeightCallback(); + this._removeUpdateHeightCallback = undefined; + } + this._rendererResources = undefined; this._cachedRendererResources = this._cachedRendererResources && this._cachedRendererResources.release(); diff --git a/Source/Scene/ModelInstanceCollection.js b/Source/Scene/ModelInstanceCollection.js index 86f7586b6b50..514e34323921 100644 --- a/Source/Scene/ModelInstanceCollection.js +++ b/Source/Scene/ModelInstanceCollection.js @@ -16,6 +16,7 @@ define([ '../Renderer/Buffer', '../Renderer/BufferUsage', '../Renderer/DrawCommand', + '../Renderer/Pass', '../Renderer/ShaderSource', '../ThirdParty/when', './getAttributeOrUniformBySemantic', @@ -41,6 +42,7 @@ define([ Buffer, BufferUsage, DrawCommand, + Pass, ShaderSource, when, getAttributeOrUniformBySemantic, @@ -107,12 +109,15 @@ define([ this._instancingSupported = false; this._dynamic = defaultValue(options.dynamic, false); this._allowPicking = defaultValue(options.allowPicking, true); - this._cull = defaultValue(options.cull, true); // Undocumented option this._ready = false; this._readyPromise = when.defer(); this._state = LoadState.NEEDS_LOAD; this._dirty = false; + // Undocumented options + this._cull = defaultValue(options.cull, true); + this._opaquePass = defaultValue(options.opaquePass, Pass.OPAQUE); + this._instances = createInstances(this, options.instances); // When the model instance collection is backed by an i3dm tile, @@ -603,7 +608,8 @@ define([ pickVertexShaderLoaded : undefined, pickFragmentShaderLoaded : undefined, pickUniformMapLoaded : undefined, - ignoreCommands : true + ignoreCommands : true, + opaquePass : collection._opaquePass }; if (allowPicking && !usesBatchTable) { diff --git a/Source/Scene/OrthographicFrustum.js b/Source/Scene/OrthographicFrustum.js index 057b9d80fa11..bbb36ec006ac 100644 --- a/Source/Scene/OrthographicFrustum.js +++ b/Source/Scene/OrthographicFrustum.js @@ -6,9 +6,9 @@ define([ OrthographicFrustum) { 'use strict'; - function DeprecatedOrthographicFrustum() { + function DeprecatedOrthographicFrustum(options) { deprecationWarning('OrthographicFrustum', 'Scene/OrthographicFrustum is deprecated. It has moved to Core/OrthographicFrustum in 1.36. Scene/OrthographicFrustum will be removed in 1.38.'); - return new OrthographicFrustum(); + return new OrthographicFrustum(options); } return DeprecatedOrthographicFrustum; diff --git a/Source/Scene/OrthographicOffCenterFrustum.js b/Source/Scene/OrthographicOffCenterFrustum.js index 93a9f14edf45..54d53cf14321 100644 --- a/Source/Scene/OrthographicOffCenterFrustum.js +++ b/Source/Scene/OrthographicOffCenterFrustum.js @@ -6,9 +6,9 @@ define([ OrthographicOffCenterFrustum) { 'use strict'; - function DeprecatedOrthographicOffCenterFrustum() { + function DeprecatedOrthographicOffCenterFrustum(options) { deprecationWarning('OrthographicOffCenterFrustum', 'Scene/OrthographicOffCenterFrustum is deprecated. It has moved to Core/OrthographicOffCenterFrustum in 1.36. Scene/OrthographicOffCenterFrustum will be removed in 1.38.'); - return new OrthographicOffCenterFrustum(); + return new OrthographicOffCenterFrustum(options); } return DeprecatedOrthographicOffCenterFrustum; diff --git a/Source/Scene/PerspectiveFrustum.js b/Source/Scene/PerspectiveFrustum.js index c2ae3b511fa5..5030940f5c5c 100644 --- a/Source/Scene/PerspectiveFrustum.js +++ b/Source/Scene/PerspectiveFrustum.js @@ -6,9 +6,9 @@ define([ PerspectiveFrustum) { 'use strict'; - function DeprecatedPerspectiveFrustum() { + function DeprecatedPerspectiveFrustum(options) { deprecationWarning('PerspectiveFrustum', 'Scene/PerspectiveFrustum is deprecated. It has moved to Core/PerspectiveFrustum in 1.36. Scene/PerspectiveFrustum will be removed in 1.38.'); - return new PerspectiveFrustum(); + return new PerspectiveFrustum(options); } return DeprecatedPerspectiveFrustum; diff --git a/Source/Scene/PerspectiveOffCenterFrustum.js b/Source/Scene/PerspectiveOffCenterFrustum.js index 4953638ef85d..e8496b4ef925 100644 --- a/Source/Scene/PerspectiveOffCenterFrustum.js +++ b/Source/Scene/PerspectiveOffCenterFrustum.js @@ -6,9 +6,9 @@ define([ PerspectiveOffCenterFrustum) { 'use strict'; - function DeprecatedPerspectiveOffCenterFrustum() { + function DeprecatedPerspectiveOffCenterFrustum(options) { deprecationWarning('PerspectiveOffCenterFrustum', 'Scene/PerspectiveOffCenterFrustum is deprecated. It has moved to Core/PerspectiveOffCenterFrustum in 1.36. Scene/PerspectiveOffCenterFrustum will be removed in 1.38.'); - return new PerspectiveOffCenterFrustum(); + return new PerspectiveOffCenterFrustum(options); } return DeprecatedPerspectiveOffCenterFrustum; diff --git a/Source/Scene/PointPrimitive.js b/Source/Scene/PointPrimitive.js index 088f03b61dfa..b4e18e8eed9a 100644 --- a/Source/Scene/PointPrimitive.js +++ b/Source/Scene/PointPrimitive.js @@ -59,20 +59,39 @@ define([ options = defaultValue(options, defaultValue.EMPTY_OBJECT); //>>includeStart('debug', pragmas.debug); - if (defined(options.scaleByDistance) && options.scaleByDistance.far <= options.scaleByDistance.near) { - throw new DeveloperError('scaleByDistance.far must be greater than scaleByDistance.near.'); - } - if (defined(options.translucencyByDistance) && options.translucencyByDistance.far <= options.translucencyByDistance.near) { - throw new DeveloperError('translucencyByDistance.far must be greater than translucencyByDistance.near.'); - } - if (defined(options.distanceDisplayCondition) && options.distanceDisplayCondition.far <= options.distanceDisplayCondition.near) { - throw new DeveloperError('distanceDisplayCondition.far must be greater than distanceDisplayCondition.near'); - } if (defined(options.disableDepthTestDistance) && options.disableDepthTestDistance < 0.0) { throw new DeveloperError('disableDepthTestDistance must be greater than or equal to 0.0.'); } //>>includeEnd('debug'); + var translucencyByDistance = options.translucencyByDistance; + var scaleByDistance = options.scaleByDistance; + var distanceDisplayCondition = options.distanceDisplayCondition; + if (defined(translucencyByDistance)) { + //>>includeStart('debug', pragmas.debug); + if (translucencyByDistance.far <= translucencyByDistance.near) { + throw new DeveloperError('translucencyByDistance.far must be greater than translucencyByDistance.near.'); + } + //>>includeEnd('debug'); + translucencyByDistance = NearFarScalar.clone(translucencyByDistance); + } + if (defined(scaleByDistance)) { + //>>includeStart('debug', pragmas.debug); + if (scaleByDistance.far <= scaleByDistance.near) { + throw new DeveloperError('scaleByDistance.far must be greater than scaleByDistance.near.'); + } + //>>includeEnd('debug'); + scaleByDistance = NearFarScalar.clone(scaleByDistance); + } + if (defined(distanceDisplayCondition)) { + //>>includeStart('debug', pragmas.debug); + if (distanceDisplayCondition.far <= distanceDisplayCondition.near) { + throw new DeveloperError('distanceDisplayCondition.far must be greater than distanceDisplayCondition.near.'); + } + //>>includeEnd('debug'); + distanceDisplayCondition = DistanceDisplayCondition.clone(distanceDisplayCondition); + } + this._show = defaultValue(options.show, true); this._position = Cartesian3.clone(defaultValue(options.position, Cartesian3.ZERO)); this._actualPosition = Cartesian3.clone(this._position); // For columbus view and 2D @@ -80,9 +99,9 @@ define([ this._outlineColor = Color.clone(defaultValue(options.outlineColor, Color.TRANSPARENT)); this._outlineWidth = defaultValue(options.outlineWidth, 0.0); this._pixelSize = defaultValue(options.pixelSize, 10.0); - this._scaleByDistance = options.scaleByDistance; - this._translucencyByDistance = options.translucencyByDistance; - this._distanceDisplayCondition = options.distanceDisplayCondition; + this._scaleByDistance = scaleByDistance; + this._translucencyByDistance = translucencyByDistance; + this._distanceDisplayCondition = distanceDisplayCondition; this._disableDepthTestDistance = defaultValue(options.disableDepthTestDistance, 0.0); this._id = options.id; this._collection = defaultValue(options.collection, pointPrimitiveCollection); diff --git a/Source/Scene/Primitive.js b/Source/Scene/Primitive.js index a872bef41470..0484e51bff28 100644 --- a/Source/Scene/Primitive.js +++ b/Source/Scene/Primitive.js @@ -189,6 +189,8 @@ define([ * * @see GeometryInstance * @see Appearance + * @see ClassificationPrimitive + * @see GroundPrimitive */ function Primitive(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); @@ -201,6 +203,7 @@ define([ * Changing this property after the primitive is rendered has no effect. *

* + * @readonly * @type GeometryInstance[]|GeometryInstance * * @default undefined @@ -1622,36 +1625,30 @@ define([ } } - function updateBoundingVolumes(primitive, frameState) { + Primitive._updateBoundingVolumes = function(primitive, frameState, modelMatrix) { + var i; + var length; + var boundingSphere; + // Update bounding volumes for primitives that are sized in pixels. // The pixel size in meters varies based on the distance from the camera. var pixelSize = primitive.appearance.pixelSize; if (defined(pixelSize)) { - var length = primitive._boundingSpheres.length; - for (var i = 0; i < length; ++i) { - var boundingSphere = primitive._boundingSpheres[i]; + length = primitive._boundingSpheres.length; + for (i = 0; i < length; ++i) { + boundingSphere = primitive._boundingSpheres[i]; var boundingSphereWC = primitive._boundingSphereWC[i]; var pixelSizeInMeters = frameState.camera.getPixelSize(boundingSphere, frameState.context.drawingBufferWidth, frameState.context.drawingBufferHeight); var sizeInMeters = pixelSizeInMeters * pixelSize; boundingSphereWC.radius = boundingSphere.radius + sizeInMeters; } } - } - - function updateAndQueueCommands(primitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses) { - //>>includeStart('debug', pragmas.debug); - if (frameState.mode !== SceneMode.SCENE3D && !Matrix4.equals(modelMatrix, Matrix4.IDENTITY)) { - throw new DeveloperError('Primitive.modelMatrix is only supported in 3D mode.'); - } - //>>includeEnd('debug'); - - updateBoundingVolumes(primitive, frameState); if (!Matrix4.equals(modelMatrix, primitive._modelMatrix)) { Matrix4.clone(modelMatrix, primitive._modelMatrix); - var length = primitive._boundingSpheres.length; - for (var i = 0; i < length; ++i) { - var boundingSphere = primitive._boundingSpheres[i]; + length = primitive._boundingSpheres.length; + for (i = 0; i < length; ++i) { + boundingSphere = primitive._boundingSpheres[i]; if (defined(boundingSphere)) { primitive._boundingSphereWC[i] = BoundingSphere.transform(boundingSphere, modelMatrix, primitive._boundingSphereWC[i]); if (!frameState.scene3DOnly) { @@ -1662,6 +1659,16 @@ define([ } } } + }; + + function updateAndQueueCommands(primitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses) { + //>>includeStart('debug', pragmas.debug); + if (frameState.mode !== SceneMode.SCENE3D && !Matrix4.equals(modelMatrix, Matrix4.IDENTITY)) { + throw new DeveloperError('Primitive.modelMatrix is only supported in 3D mode.'); + } + //>>includeEnd('debug'); + + Primitive._updateBoundingVolumes(primitive, frameState, modelMatrix); var boundingSpheres; if (frameState.mode === SceneMode.SCENE3D) { diff --git a/Source/Scene/Scene.js b/Source/Scene/Scene.js index 67193b1ca034..f99853f1ac4e 100644 --- a/Source/Scene/Scene.js +++ b/Source/Scene/Scene.js @@ -1901,6 +1901,17 @@ define([ executeCommand(commands[j], scene, context, passState); } + us.updatePass(Pass.CESIUM_3D_TILE); + commands = frustumCommands.commands[Pass.CESIUM_3D_TILE]; + length = frustumCommands.indices[Pass.CESIUM_3D_TILE]; + for (j = 0; j < length; ++j) { + executeCommand(commands[j], scene, context, passState); + } + + if (length > 0 && context.stencilBuffer) { + scene._stencilClearCommand.execute(context, passState); + } + if (defined(globeDepth) && environmentState.useGlobeDepthFramebuffer && (scene.copyGlobeDepth || scene.debugShowGlobeDepth)) { globeDepth.update(context, passState); globeDepth.executeCopyDepth(context, passState); @@ -1929,13 +1940,6 @@ define([ } } - us.updatePass(Pass.CESIUM_3D_TILE); - commands = frustumCommands.commands[Pass.CESIUM_3D_TILE]; - length = frustumCommands.indices[Pass.CESIUM_3D_TILE]; - for (j = 0; j < length; ++j) { - executeCommand(commands[j], scene, context, passState); - } - // Execute commands in order by pass up to the translucent pass. // Translucent geometry needs special handling (sorting/OIT). var startPass = Pass.GROUND + 1; diff --git a/Source/Shaders/GlobeFS.glsl b/Source/Shaders/GlobeFS.glsl index c36fc9737286..34f8bc02c4ca 100644 --- a/Source/Shaders/GlobeFS.glsl +++ b/Source/Shaders/GlobeFS.glsl @@ -66,7 +66,7 @@ varying vec3 v_mieColor; vec4 sampleAndBlend( vec4 previousColor, - sampler2D texture, + sampler2D textureToSample, vec2 tileTextureCoordinates, vec4 textureCoordinateRectangle, vec4 textureCoordinateTranslationAndScale, @@ -94,7 +94,7 @@ vec4 sampleAndBlend( vec2 translation = textureCoordinateTranslationAndScale.xy; vec2 scale = textureCoordinateTranslationAndScale.zw; vec2 textureCoordinates = tileTextureCoordinates * scale + translation; - vec4 value = texture2D(texture, textureCoordinates); + vec4 value = texture2D(textureToSample, textureCoordinates); vec3 color = value.rgb; float alpha = value.a; diff --git a/Source/Shaders/ShadowVolumeVS.glsl b/Source/Shaders/ShadowVolumeVS.glsl index 809cf0be80a3..a85fc04a548e 100644 --- a/Source/Shaders/ShadowVolumeVS.glsl +++ b/Source/Shaders/ShadowVolumeVS.glsl @@ -1,10 +1,13 @@ attribute vec3 position3DHigh; attribute vec3 position3DLow; -attribute vec3 extrudeDirection; attribute vec4 color; attribute float batchId; +#ifdef EXTRUDED_GEOMETRY +attribute vec3 extrudeDirection; + uniform float u_globeMinimumAltitude; +#endif // emulated noperspective varying float v_WindowZ; @@ -22,10 +25,14 @@ void main() v_color = color; vec4 position = czm_computePosition(); + +#ifdef EXTRUDED_GEOMETRY float delta = min(u_globeMinimumAltitude, czm_geometricToleranceOverMeter * length(position.xyz)); delta *= czm_sceneMode == czm_sceneMode3D ? 1.0 : 0.0; //extrudeDirection is zero for the top layer position = position + vec4(extrudeDirection * delta, 0.0); +#endif + gl_Position = depthClampFarPlane(czm_modelViewProjectionRelativeToEye * position); } diff --git a/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js b/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js index b58d82ec377d..6fff82c67a4a 100644 --- a/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js +++ b/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js @@ -1014,7 +1014,7 @@ define([ // Restore original color to feature that is no longer selected var frameState = this._scene.frameState; if (!this.colorize && defined(this._style)) { - currentFeature.color = this._style.color.evaluateColor(frameState, currentFeature, scratchColor); + currentFeature.color = defined(this._style.color) ? this._style.color.evaluateColor(frameState, currentFeature, scratchColor) : Color.WHITE; } else { currentFeature.color = oldColor; } diff --git a/Source/Widgets/InfoBox/InfoBox.js b/Source/Widgets/InfoBox/InfoBox.js index da8d533b05c8..10eed5e586cb 100644 --- a/Source/Widgets/InfoBox/InfoBox.js +++ b/Source/Widgets/InfoBox/InfoBox.js @@ -1,21 +1,21 @@ define([ '../../Core/buildModuleUrl', + '../../Core/Check', '../../Core/Color', '../../Core/defined', '../../Core/defineProperties', '../../Core/destroyObject', - '../../Core/DeveloperError', '../../ThirdParty/knockout', '../getElement', '../subscribeAndEvaluate', './InfoBoxViewModel' ], function( buildModuleUrl, + Check, Color, defined, defineProperties, destroyObject, - DeveloperError, knockout, getElement, subscribeAndEvaluate, @@ -34,9 +34,7 @@ define([ */ function InfoBox(container) { //>>includeStart('debug', pragmas.debug); - if (!defined(container)) { - throw new DeveloperError('container is required.'); - } + Check.defined('container', container); //>>includeEnd('debug') container = getElement(container); diff --git a/Source/Workers/createFrustumGeometry.js b/Source/Workers/createFrustumGeometry.js new file mode 100644 index 000000000000..3f1c141e6896 --- /dev/null +++ b/Source/Workers/createFrustumGeometry.js @@ -0,0 +1,17 @@ +define([ + '../Core/defined', + '../Core/FrustumGeometry' + ], function( + defined, + FrustumGeometry) { + 'use strict'; + + function createFrustumGeometry(frustumGeometry, offset) { + if (defined(offset)) { + frustumGeometry = FrustumGeometry.unpack(frustumGeometry, offset); + } + return FrustumGeometry.createGeometry(frustumGeometry); + } + + return createFrustumGeometry; +}); diff --git a/Source/Workers/createFrustumOutlineGeometry.js b/Source/Workers/createFrustumOutlineGeometry.js new file mode 100644 index 000000000000..4e6a84370bcf --- /dev/null +++ b/Source/Workers/createFrustumOutlineGeometry.js @@ -0,0 +1,17 @@ +define([ + '../Core/defined', + '../Core/FrustumOutlineGeometry' + ], function( + defined, + FrustumOutlineGeometry) { + 'use strict'; + + function createFrustumOutlineGeometry(frustumGeometry, offset) { + if (defined(offset)) { + frustumGeometry = FrustumOutlineGeometry.unpack(frustumGeometry, offset); + } + return FrustumOutlineGeometry.createGeometry(frustumGeometry); + } + + return createFrustumOutlineGeometry; +}); diff --git a/Source/Workers/transcodeCRNToDXT.js b/Source/Workers/transcodeCRNToDXT.js index e5a28e1297cd..2d058b700e30 100644 --- a/Source/Workers/transcodeCRNToDXT.js +++ b/Source/Workers/transcodeCRNToDXT.js @@ -132,8 +132,12 @@ define([ // Mipmaps are unsupported, so copy the level 0 texture // When mipmaps are supported, a copy will still be necessary as dxtData is a view on the heap. var length = PixelFormat.compressedTextureSizeInBytes(format, width, height); + + // Get a copy of the 0th mip level. dxtData will exceed length when there are more mip levels. + // Equivalent to dxtData.slice(0, length), which is not supported in IE11 + var level0DXTDataView = dxtData.subarray(0, length); var level0DXTData = new Uint8Array(length); - level0DXTData.set(dxtData, 0); + level0DXTData.set(level0DXTDataView, 0); transferableObjects.push(level0DXTData.buffer); return new CompressedTextureBuffer(format, width, height, level0DXTData); diff --git a/Specs/Core/CorridorGeometrySpec.js b/Specs/Core/CorridorGeometrySpec.js index 129575e85781..4d4f0ac14780 100644 --- a/Specs/Core/CorridorGeometrySpec.js +++ b/Specs/Core/CorridorGeometrySpec.js @@ -41,6 +41,13 @@ defineSuite([ width: 10000 })); expect(geometry).toBeUndefined(); + + geometry = CorridorGeometry.createGeometry(new CorridorGeometry({ + positions : [new Cartesian3(-1349511.388149118, -5063973.22857992, 3623141.6372688496), //same lon/lat, different height + new Cartesian3(-1349046.4811926484, -5062228.688739784, 3621885.0521561056)], + width: 10000 + })); + expect(geometry).toBeUndefined(); }); it('computes positions', function() { diff --git a/Specs/Core/CorridorOutlineGeometrySpec.js b/Specs/Core/CorridorOutlineGeometrySpec.js index f3317faaf0c4..4392d7b89468 100644 --- a/Specs/Core/CorridorOutlineGeometrySpec.js +++ b/Specs/Core/CorridorOutlineGeometrySpec.js @@ -35,6 +35,13 @@ defineSuite([ width: 10000 })); expect(geometry).toBeUndefined(); + + geometry = CorridorOutlineGeometry.createGeometry(new CorridorOutlineGeometry({ + positions : [new Cartesian3(-1349511.388149118, -5063973.22857992, 3623141.6372688496), //same lon/lat, different height + new Cartesian3(-1349046.4811926484, -5062228.688739784, 3621885.0521561056)], + width: 10000 + })); + expect(geometry).toBeUndefined(); }); it('computes positions', function() { diff --git a/Specs/Core/FrustumGeometrySpec.js b/Specs/Core/FrustumGeometrySpec.js new file mode 100644 index 000000000000..0b9f5669fc6d --- /dev/null +++ b/Specs/Core/FrustumGeometrySpec.js @@ -0,0 +1,93 @@ +defineSuite([ + 'Core/FrustumGeometry', + 'Core/Cartesian3', + 'Core/Math', + 'Core/PerspectiveFrustum', + 'Core/Quaternion', + 'Core/VertexFormat', + 'Specs/createPackableSpecs' + ], function( + FrustumGeometry, + Cartesian3, + CesiumMath, + PerspectiveFrustum, + Quaternion, + VertexFormat, + createPackableSpecs) { + 'use strict'; + + it('constructor throws without options', function() { + expect(function() { + return new FrustumGeometry(); + }).toThrowDeveloperError(); + }); + + it('constructor throws without frustum', function() { + expect(function() { + return new FrustumGeometry({ + origin : Cartesian3.ZERO, + orientation : Quaternion.IDENTITY + }); + }).toThrowDeveloperError(); + }); + + it('constructor throws without position', function() { + expect(function() { + return new FrustumGeometry({ + frustum : new PerspectiveFrustum(), + orientation : Quaternion.IDENTITY + }); + }).toThrowDeveloperError(); + }); + + it('constructor throws without orientation', function() { + expect(function() { + return new FrustumGeometry({ + frustum : new PerspectiveFrustum(), + origin : Cartesian3.ZERO + }); + }).toThrowDeveloperError(); + }); + + it('constructor computes all vertex attributes', function() { + var frustum = new PerspectiveFrustum(); + frustum.fov = CesiumMath.toRadians(30.0); + frustum.aspectRatio = 1920.0 / 1080.0; + frustum.near = 1.0; + frustum.far = 3.0; + + var m = FrustumGeometry.createGeometry(new FrustumGeometry({ + frustum : frustum, + origin : Cartesian3.ZERO, + orientation : Quaternion.IDENTITY, + vertexFormat : VertexFormat.ALL + })); + + var numVertices = 24; //3 components x 8 corners + var numTriangles = 12; //6 sides x 2 triangles per side + expect(m.attributes.position.values.length).toEqual(numVertices * 3); + expect(m.attributes.normal.values.length).toEqual(numVertices * 3); + expect(m.attributes.tangent.values.length).toEqual(numVertices * 3); + expect(m.attributes.bitangent.values.length).toEqual(numVertices * 3); + expect(m.attributes.st.values.length).toEqual(numVertices * 2); + + expect(m.indices.length).toEqual(numTriangles * 3); + + expect(m.boundingSphere.center).toEqual(new Cartesian3(0.0, 0.0, 2.0)); + expect(m.boundingSphere.radius).toBeGreaterThan(1.0); + expect(m.boundingSphere.radius).toBeLessThan(2.0); + }); + + var packableFrustum = new PerspectiveFrustum(); + packableFrustum.fov = 1.0; + packableFrustum.aspectRatio = 2.0; + packableFrustum.near = 3.0; + packableFrustum.far = 4.0; + + createPackableSpecs(FrustumGeometry, new FrustumGeometry({ + frustum : packableFrustum, + origin : Cartesian3.ZERO, + orientation : Quaternion.IDENTITY, + vertexFormat : VertexFormat.POSITION_ONLY + }), [0.0, 1.0, 2.0, 3.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0]); +}); diff --git a/Specs/Core/FrustumOutlineGeometrySpec.js b/Specs/Core/FrustumOutlineGeometrySpec.js new file mode 100644 index 000000000000..71a46b75da84 --- /dev/null +++ b/Specs/Core/FrustumOutlineGeometrySpec.js @@ -0,0 +1,87 @@ +defineSuite([ + 'Core/FrustumOutlineGeometry', + 'Core/Cartesian3', + 'Core/Math', + 'Core/PerspectiveFrustum', + 'Core/Quaternion', + 'Core/VertexFormat', + 'Specs/createPackableSpecs' + ], function( + FrustumOutlineGeometry, + Cartesian3, + CesiumMath, + PerspectiveFrustum, + Quaternion, + VertexFormat, + createPackableSpecs) { + 'use strict'; + + it('constructor throws without options', function() { + expect(function() { + return new FrustumOutlineGeometry(); + }).toThrowDeveloperError(); + }); + + it('constructor throws without frustum', function() { + expect(function() { + return new FrustumOutlineGeometry({ + origin : Cartesian3.ZERO, + orientation : Quaternion.IDENTITY + }); + }).toThrowDeveloperError(); + }); + + it('constructor throws without position', function() { + expect(function() { + return new FrustumOutlineGeometry({ + frustum : new PerspectiveFrustum(), + orientation : Quaternion.IDENTITY + }); + }).toThrowDeveloperError(); + }); + + it('constructor throws without orientation', function() { + expect(function() { + return new FrustumOutlineGeometry({ + frustum : new PerspectiveFrustum(), + origin : Cartesian3.ZERO + }); + }).toThrowDeveloperError(); + }); + + it('constructor computes all vertex attributes', function() { + var frustum = new PerspectiveFrustum(); + frustum.fov = CesiumMath.toRadians(30.0); + frustum.aspectRatio = 1920.0 / 1080.0; + frustum.near = 1.0; + frustum.far = 3.0; + + var m = FrustumOutlineGeometry.createGeometry(new FrustumOutlineGeometry({ + frustum : frustum, + origin : Cartesian3.ZERO, + orientation : Quaternion.IDENTITY + })); + + var numVertices = 8; + var numLines = 12; + expect(m.attributes.position.values.length).toEqual(numVertices * 3); + expect(m.indices.length).toEqual(numLines * 2); + + expect(m.boundingSphere.center).toEqual(new Cartesian3(0.0, 0.0, 2.0)); + expect(m.boundingSphere.radius).toBeGreaterThan(1.0); + expect(m.boundingSphere.radius).toBeLessThan(2.0); + }); + + var packableFrustum = new PerspectiveFrustum(); + packableFrustum.fov = 1.0; + packableFrustum.aspectRatio = 2.0; + packableFrustum.near = 3.0; + packableFrustum.far = 4.0; + + createPackableSpecs(FrustumOutlineGeometry, new FrustumOutlineGeometry({ + frustum : packableFrustum, + origin : Cartesian3.ZERO, + orientation : Quaternion.IDENTITY, + vertexFormat : VertexFormat.POSITION_ONLY + }), [0.0, 1.0, 2.0, 3.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0]); +}); diff --git a/Specs/Core/OrthographicFrustumSpec.js b/Specs/Core/OrthographicFrustumSpec.js index f40c2ca60fe0..4ac99bd4e0ec 100644 --- a/Specs/Core/OrthographicFrustumSpec.js +++ b/Specs/Core/OrthographicFrustumSpec.js @@ -1,17 +1,19 @@ defineSuite([ - 'Core/OrthographicFrustum', - 'Core/Cartesian2', - 'Core/Cartesian3', - 'Core/Cartesian4', - 'Core/Math', - 'Core/Matrix4' -], function( - OrthographicFrustum, - Cartesian2, - Cartesian3, - Cartesian4, - CesiumMath, - Matrix4) { + 'Core/OrthographicFrustum', + 'Core/Cartesian2', + 'Core/Cartesian3', + 'Core/Cartesian4', + 'Core/Math', + 'Core/Matrix4', + 'Specs/createPackableSpecs' + ], function( + OrthographicFrustum, + Cartesian2, + Cartesian3, + Cartesian4, + CesiumMath, + Matrix4, + createPackableSpecs) { 'use strict'; var frustum, planes; @@ -25,6 +27,30 @@ defineSuite([ planes = frustum.computeCullingVolume(new Cartesian3(), Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3()), Cartesian3.UNIT_Y).planes; }); + it('constructs', function() { + var options = { + width : 1.0, + aspectRatio : 2.0, + near : 3.0, + far : 4.0, + xOffset : 5.0, + yOffset : 6.0 + }; + var f = new OrthographicFrustum(options); + expect(f.width).toEqual(options.width); + expect(f.aspectRatio).toEqual(options.aspectRatio); + expect(f.near).toEqual(options.near); + expect(f.far).toEqual(options.far); + }); + + it('default constructs', function() { + var f = new OrthographicFrustum(); + expect(f.width).toBeUndefined(); + expect(f.aspectRatio).toBeUndefined(); + expect(f.near).toEqual(1.0); + expect(f.far).toEqual(500000000.0); + }); + it('undefined width causes an exception', function() { frustum.width = undefined; expect(function() { @@ -167,4 +193,11 @@ defineSuite([ expect(frustum2).toBe(result); expect(frustum).toEqual(frustum2); }); + + createPackableSpecs(OrthographicFrustum, new OrthographicFrustum({ + width : 1.0, + aspectRatio : 2.0, + near : 3.0, + far : 4.0 + }), [1.0, 2.0, 3.0, 4.0]); }); diff --git a/Specs/Core/OrthographicOffCenterFrustumSpec.js b/Specs/Core/OrthographicOffCenterFrustumSpec.js index 176024388cba..a81c88f087dc 100644 --- a/Specs/Core/OrthographicOffCenterFrustumSpec.js +++ b/Specs/Core/OrthographicOffCenterFrustumSpec.js @@ -27,6 +27,32 @@ defineSuite([ planes = frustum.computeCullingVolume(new Cartesian3(), Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3()), Cartesian3.UNIT_Y).planes; }); + it('constructs', function() { + var options = { + left : -1.0, + right : 2.0, + top : 5.0, + bottom : -1.0, + near : 3.0, + far : 4.0 + }; + var f = new OrthographicOffCenterFrustum(options); + expect(f.width).toEqual(options.width); + expect(f.aspectRatio).toEqual(options.aspectRatio); + expect(f.near).toEqual(options.near); + expect(f.far).toEqual(options.far); + }); + + it('default constructs', function() { + var f = new OrthographicOffCenterFrustum(); + expect(f.left).toBeUndefined(); + expect(f.right).toBeUndefined(); + expect(f.top).toBeUndefined(); + expect(f.bottom).toBeUndefined(); + expect(f.near).toEqual(1.0); + expect(f.far).toEqual(500000000.0); + }); + it('left greater than right causes an exception', function() { frustum.left = frustum.right + 1.0; expect(function() { diff --git a/Specs/Core/PerspectiveFrustumSpec.js b/Specs/Core/PerspectiveFrustumSpec.js index 4a3f37c80085..7682b09d6ffc 100644 --- a/Specs/Core/PerspectiveFrustumSpec.js +++ b/Specs/Core/PerspectiveFrustumSpec.js @@ -4,14 +4,16 @@ defineSuite([ 'Core/Cartesian3', 'Core/Cartesian4', 'Core/Math', - 'Core/Matrix4' + 'Core/Matrix4', + 'Specs/createPackableSpecs' ], function( PerspectiveFrustum, Cartesian2, Cartesian3, Cartesian4, CesiumMath, - Matrix4) { + Matrix4, + createPackableSpecs) { 'use strict'; var frustum, planes; @@ -25,6 +27,34 @@ defineSuite([ planes = frustum.computeCullingVolume(new Cartesian3(), Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3()), Cartesian3.UNIT_Y).planes; }); + it('constructs', function() { + var options = { + fov : 1.0, + aspectRatio : 2.0, + near : 3.0, + far : 4.0, + xOffset : 5.0, + yOffset : 6.0 + }; + var f = new PerspectiveFrustum(options); + expect(f.fov).toEqual(options.fov); + expect(f.aspectRatio).toEqual(options.aspectRatio); + expect(f.near).toEqual(options.near); + expect(f.far).toEqual(options.far); + expect(f.xOffset).toEqual(options.xOffset); + expect(f.yOffset).toEqual(options.yOffset); + }); + + it('default constructs', function() { + var f = new PerspectiveFrustum(); + expect(f.fov).toBeUndefined(); + expect(f.aspectRatio).toBeUndefined(); + expect(f.near).toEqual(1.0); + expect(f.far).toEqual(500000000.0); + expect(f.xOffset).toEqual(0.0); + expect(f.yOffset).toEqual(0.0); + }); + it('out of range fov causes an exception', function() { frustum.fov = -1.0; expect(function() { @@ -172,4 +202,13 @@ defineSuite([ expect(frustum2).toBe(result); expect(frustum).toEqual(frustum2); }); + + createPackableSpecs(PerspectiveFrustum, new PerspectiveFrustum({ + fov : 1.0, + aspectRatio : 2.0, + near : 3.0, + far : 4.0, + xOffset : 5.0, + yOffset : 6.0 + }), [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]); }); diff --git a/Specs/Core/PerspectiveOffCenterFrustumSpec.js b/Specs/Core/PerspectiveOffCenterFrustumSpec.js index 01df32c6eec0..937f0d2a499f 100644 --- a/Specs/Core/PerspectiveOffCenterFrustumSpec.js +++ b/Specs/Core/PerspectiveOffCenterFrustumSpec.js @@ -27,6 +27,32 @@ defineSuite([ planes = frustum.computeCullingVolume(new Cartesian3(), Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3()), Cartesian3.UNIT_Y).planes; }); + it('constructs', function() { + var options = { + left : -1.0, + right : 2.0, + top : 5.0, + bottom : -1.0, + near : 3.0, + far : 4.0 + }; + var f = new PerspectiveOffCenterFrustum(options); + expect(f.width).toEqual(options.width); + expect(f.aspectRatio).toEqual(options.aspectRatio); + expect(f.near).toEqual(options.near); + expect(f.far).toEqual(options.far); + }); + + it('default constructs', function() { + var f = new PerspectiveOffCenterFrustum(); + expect(f.left).toBeUndefined(); + expect(f.right).toBeUndefined(); + expect(f.top).toBeUndefined(); + expect(f.bottom).toBeUndefined(); + expect(f.near).toEqual(1.0); + expect(f.far).toEqual(500000000.0); + }); + it('out of range near plane throws an exception', function() { frustum.near = -1.0; expect(function() { diff --git a/Specs/Core/loadCRNSpec.js b/Specs/Core/loadCRNSpec.js index ce187d90187f..b5d53cf2d066 100644 --- a/Specs/Core/loadCRNSpec.js +++ b/Specs/Core/loadCRNSpec.js @@ -13,6 +13,7 @@ defineSuite([ 'use strict'; var validCompressed = new Uint8Array([72, 120, 0, 74, 227, 123, 0, 0, 0, 138, 92, 167, 0, 4, 0, 4, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 74, 0, 0, 22, 0, 1, 0, 0, 96, 0, 0, 12, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 108, 0, 0, 0, 137, 0, 10, 96, 0, 0, 0, 0, 0, 0, 16, 4, 9, 130, 0, 0, 0, 0, 0, 0, 109, 4, 0, 0, 198, 96, 128, 0, 0, 0, 0, 0, 26, 80, 0, 0, 6, 96, 0, 0, 0, 0, 0, 0, 16, 0, 51, 0, 0, 0, 0, 0, 0, 0, 128, 1, 152, 0, 0, 0, 0, 0, 0, 4, 0]); + var validCompressedMipmap = new Uint8Array([72, 120, 0, 82, 183, 141, 0, 0, 0, 148, 151, 24, 0, 4, 0, 4, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 0, 0, 22, 0, 1, 0, 0, 104, 0, 0, 12, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 116, 0, 0, 0, 145, 0, 0, 0, 146, 0, 0, 0, 147, 0, 130, 97, 0, 0, 0, 0, 0, 4, 35, 37, 0, 3, 48, 0, 0, 0, 0, 0, 0, 8, 200, 0, 198, 96, 128, 0, 0, 0, 0, 0, 26, 80, 0, 0, 6, 96, 0, 0, 0, 0, 0, 0, 16, 0, 51, 0, 0, 0, 0, 0, 0, 0, 128, 1, 152, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0]); var fakeXHR; beforeEach(function() { @@ -119,7 +120,7 @@ defineSuite([ expect(rejectedError.response).toEqual(error); }); - it('returns a promise that resolves to an compressed texture when the request loads', function() { + it('returns a promise that resolves to a compressed texture when the request loads', function() { var testUrl = 'http://example.invalid/testuri'; var promise = loadCRN(testUrl); @@ -149,6 +150,36 @@ defineSuite([ }); }); + it('returns a promise that resolves to a compressed texture containing the first mip level of the original texture', function() { + var testUrl = 'http://example.invalid/testuri'; + var promise = loadCRN(testUrl); + + expect(promise).toBeDefined(); + + var resolvedValue; + var rejectedError; + var newPromise = promise.then(function(value) { + resolvedValue = value; + }, function(error) { + rejectedError = error; + }); + + expect(resolvedValue).toBeUndefined(); + expect(rejectedError).toBeUndefined(); + + var response = validCompressedMipmap.buffer; + fakeXHR.simulateLoad(response); + + return newPromise.then(function() { + expect(resolvedValue).toBeDefined(); + expect(resolvedValue.width).toEqual(4); + expect(resolvedValue.height).toEqual(4); + expect(PixelFormat.isCompressedFormat(resolvedValue.internalFormat)).toEqual(true); + expect(resolvedValue.bufferView).toBeDefined(); + expect(rejectedError).toBeUndefined(); + }); + }); + it('returns undefined if the request is throttled', function() { var oldMaximumRequests = RequestScheduler.maximumRequests; RequestScheduler.maximumRequests = 0; diff --git a/Specs/Core/loadKTXSpec.js b/Specs/Core/loadKTXSpec.js index 745c22c1a488..0f2ebcd3fe30 100644 --- a/Specs/Core/loadKTXSpec.js +++ b/Specs/Core/loadKTXSpec.js @@ -15,7 +15,9 @@ defineSuite([ 'use strict'; var validCompressed = new Uint8Array([171, 75, 84, 88, 32, 49, 49, 187, 13, 10, 26, 10, 1, 2, 3, 4, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 241, 131, 0, 0, 8, 25, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 224, 7, 224, 7, 0, 0, 0, 0]); + var validCompressedMipmap = new Uint8Array([171, 75, 84, 88, 32, 49, 49, 187, 13, 10, 26, 10, 1, 2, 3, 4, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 100, 141, 0, 0, 7, 25, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 52, 0, 0, 0, 19, 0, 0, 0, 67, 82, 78, 76, 73, 66, 95, 70, 79, 85, 82, 67, 67, 0, 69, 84, 67, 49, 0, 0, 23, 0, 0, 0, 75, 84, 88, 111, 114, 105, 101, 110, 116, 97, 116, 105, 111, 110, 0, 83, 61, 114, 44, 84, 61, 100, 0, 0, 8, 0, 0, 0, 255, 0, 0, 0, 255, 255, 0, 0, 8, 0, 0, 0, 255, 0, 0, 0, 255, 255, 0, 0, 8, 0, 0, 0, 255, 0, 0, 0, 255, 255, 0, 0]); var validUncompressed = new Uint8Array([171, 75, 84, 88, 32, 49, 49, 187, 13, 10, 26, 10, 1, 2, 3, 4, 1, 20, 0, 0, 1, 0, 0, 0, 8, 25, 0, 0, 88, 128, 0, 0, 8, 25, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 32, 0, 0, 0, 27, 0, 0, 0, 75, 84, 88, 79, 114, 105, 101, 110, 116, 97, 116, 105, 111, 110, 0, 83, 61, 114, 44, 84, 61, 100, 44, 82, 61, 105, 0, 0, 64, 0, 0, 0, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255]); + var validUncompressedMipmap = new Uint8Array([171, 75, 84, 88, 32, 49, 49, 187, 13, 10, 26, 10, 1, 2, 3, 4, 1, 20, 0, 0, 1, 0, 0, 0, 7, 25, 0, 0, 81, 128, 0, 0, 7, 25, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 52, 0, 0, 0, 19, 0, 0, 0, 67, 82, 78, 76, 73, 66, 95, 70, 79, 85, 82, 67, 67, 0, 82, 71, 66, 120, 0, 0, 23, 0, 0, 0, 75, 84, 88, 111, 114, 105, 101, 110, 116, 97, 116, 105, 111, 110, 0, 83, 61, 114, 44, 84, 61, 100, 0, 0, 48, 0, 0, 0, 255, 0, 0, 255, 0, 0, 255, 0, 0, 255, 0, 0, 255, 0, 0, 255, 0, 0, 255, 0, 0, 255, 0, 0, 255, 0, 0, 255, 0, 0, 255, 0, 0, 255, 0, 0, 255, 0, 0, 255, 0, 0, 255, 0, 0, 255, 0, 0, 12, 0, 0, 0, 255, 0, 0, 255, 0, 0, 255, 0, 0, 255, 0, 0, 3, 0, 0, 0, 255, 0, 0, 0]); var fakeXHR; @@ -150,7 +152,34 @@ defineSuite([ expect(rejectedError).toBeUndefined(); }); - it('returns a promise that resolves to an compressed texture when the request loads', function() { + it('returns a promise that resolves to an uncompressed texture containing the first mip level of the original texture', function() { + var testUrl = 'http://example.invalid/testuri'; + var promise = loadKTX(testUrl); + + expect(promise).toBeDefined(); + + var resolvedValue; + var rejectedError; + promise.then(function(value) { + resolvedValue = value; + }, function(error) { + rejectedError = error; + }); + + expect(resolvedValue).toBeUndefined(); + expect(rejectedError).toBeUndefined(); + + var response = validUncompressedMipmap.buffer; + fakeXHR.simulateLoad(response); + expect(resolvedValue).toBeDefined(); + expect(resolvedValue.width).toEqual(4); + expect(resolvedValue.height).toEqual(4); + expect(PixelFormat.isCompressedFormat(resolvedValue.internalFormat)).toEqual(false); + expect(resolvedValue.bufferView).toBeDefined(); + expect(rejectedError).toBeUndefined(); + }); + + it('returns a promise that resolves to a compressed texture when the request loads', function() { var testUrl = 'http://example.invalid/testuri'; var promise = loadKTX(testUrl); @@ -177,6 +206,34 @@ defineSuite([ expect(rejectedError).toBeUndefined(); }); + it('returns a promise that resolves to a compressed texture containing the first mip level of the original texture', function() { + var testUrl = 'http://example.invalid/testuri'; + var promise = loadKTX(testUrl); + + expect(promise).toBeDefined(); + + var resolvedValue; + var rejectedError; + promise.then(function(value) { + resolvedValue = value; + }, function(error) { + rejectedError = error; + }); + + expect(resolvedValue).toBeUndefined(); + expect(rejectedError).toBeUndefined(); + + var response = validCompressedMipmap.buffer; + fakeXHR.simulateLoad(response); + expect(resolvedValue).toBeDefined(); + expect(resolvedValue.width).toEqual(4); + expect(resolvedValue.height).toEqual(4); + expect(PixelFormat.isCompressedFormat(resolvedValue.internalFormat)).toEqual(true); + expect(resolvedValue.bufferView).toBeDefined(); + expect(rejectedError).toBeUndefined(); + }); + + it('cannot parse invalid KTX buffer', function() { var invalidKTX = new Uint8Array(validCompressed); invalidKTX[0] = 0; @@ -276,26 +333,6 @@ defineSuite([ expect(rejectedError.message).toEqual('The type size for compressed textures must be 1.'); }); - it('cannot parse KTX buffer with compressed texture and needs to generate mipmaps', function() { - var reinterprestBuffer = new Uint32Array(validCompressed.buffer); - var invalidKTX = new Uint32Array(reinterprestBuffer); - invalidKTX[14] = 0; - - var promise = loadKTX(invalidKTX.buffer); - - var resolvedValue; - var rejectedError; - promise.then(function(value) { - resolvedValue = value; - }, function(error) { - rejectedError = error; - }); - - expect(resolvedValue).toBeUndefined(); - expect(rejectedError instanceof RuntimeError).toEqual(true); - expect(rejectedError.message).toEqual('Generating mipmaps for a compressed texture is unsupported.'); - }); - it('cannot parse KTX buffer with uncompressed texture and base format is not the same as format', function() { var reinterprestBuffer = new Uint32Array(validUncompressed.buffer); var invalidKTX = new Uint32Array(reinterprestBuffer); diff --git a/Specs/Data/Cesium3DTiles/Batched/BatchedTextured/batchedTextured.b3dm b/Specs/Data/Cesium3DTiles/Batched/BatchedTextured/batchedTextured.b3dm index d5636e9602d7..b21138f99ab9 100644 Binary files a/Specs/Data/Cesium3DTiles/Batched/BatchedTextured/batchedTextured.b3dm and b/Specs/Data/Cesium3DTiles/Batched/BatchedTextured/batchedTextured.b3dm differ diff --git a/Specs/Data/Cesium3DTiles/Composite/CompositeOfInstanced/box.glb b/Specs/Data/Cesium3DTiles/Composite/CompositeOfInstanced/box.glb new file mode 100644 index 000000000000..2bde3c4ee98e Binary files /dev/null and b/Specs/Data/Cesium3DTiles/Composite/CompositeOfInstanced/box.glb differ diff --git a/Specs/Data/Cesium3DTiles/Composite/CompositeOfInstanced/compositeOfInstanced.cmpt b/Specs/Data/Cesium3DTiles/Composite/CompositeOfInstanced/compositeOfInstanced.cmpt new file mode 100644 index 000000000000..99f84f85f092 Binary files /dev/null and b/Specs/Data/Cesium3DTiles/Composite/CompositeOfInstanced/compositeOfInstanced.cmpt differ diff --git a/Specs/Data/Cesium3DTiles/Composite/CompositeOfInstanced/tileset.json b/Specs/Data/Cesium3DTiles/Composite/CompositeOfInstanced/tileset.json new file mode 100644 index 000000000000..9609546a000c --- /dev/null +++ b/Specs/Data/Cesium3DTiles/Composite/CompositeOfInstanced/tileset.json @@ -0,0 +1,29 @@ +{ + "asset": { + "version": "0.0" + }, + "properties": { + "Height": { + "minimum": 20, + "maximum": 20 + } + }, + "geometricError": 70, + "root": { + "refine": "ADD", + "boundingVolume": { + "region": [ + -1.3197004795898053, + 0.6988582109, + -1.3196595204101946, + 0.6988897891, + 0, + 30 + ] + }, + "geometricError": 0, + "content": { + "url": "compositeOfInstanced.cmpt" + } + } +} diff --git a/Specs/DataSources/EntitySpec.js b/Specs/DataSources/EntitySpec.js index 85b1444e2915..5a4a4eb4738a 100644 --- a/Specs/DataSources/EntitySpec.js +++ b/Specs/DataSources/EntitySpec.js @@ -241,13 +241,20 @@ defineSuite([ }).toThrowDeveloperError(); }); - it('_getModelMatrix returns undefined when position is undefined.', function() { + it('computeModelMatrix throws if no time specified.', function() { + var entity = new Entity(); + expect(function() { + entity.computeModelMatrix(); + }).toThrowDeveloperError(); + }); + + it('computeModelMatrix returns undefined when position is undefined.', function() { var entity = new Entity(); entity.orientation = new ConstantProperty(Quaternion.IDENTITY); - expect(entity._getModelMatrix(new JulianDate())).toBeUndefined(); + expect(entity.computeModelMatrix(new JulianDate())).toBeUndefined(); }); - it('_getModelMatrix returns correct value.', function() { + it('computeModelMatrix returns correct value.', function() { var entity = new Entity(); var position = new Cartesian3(123456, 654321, 123456); @@ -257,28 +264,28 @@ defineSuite([ entity.position = new ConstantProperty(position); entity.orientation = new ConstantProperty(orientation); - var modelMatrix = entity._getModelMatrix(new JulianDate()); + var modelMatrix = entity.computeModelMatrix(new JulianDate()); var expected = Matrix4.fromRotationTranslation(Matrix3.fromQuaternion(orientation), position); expect(modelMatrix).toEqual(expected); }); - it('_getModelMatrix returns ENU when quaternion is undefined.', function() { + it('computeModelMatrix returns ENU when quaternion is undefined.', function() { var entity = new Entity(); var position = new Cartesian3(123456, 654321, 123456); entity.position = new ConstantProperty(position); - var modelMatrix = entity._getModelMatrix(new JulianDate()); + var modelMatrix = entity.computeModelMatrix(new JulianDate()); var expected = Transforms.eastNorthUpToFixedFrame(position); expect(modelMatrix).toEqual(expected); }); - it('_getModelMatrix works with result parameter.', function() { + it('computeModelMatrix works with result parameter.', function() { var entity = new Entity(); var position = new Cartesian3(123456, 654321, 123456); entity.position = new ConstantProperty(position); var result = new Matrix4(); - var modelMatrix = entity._getModelMatrix(new JulianDate(), result); + var modelMatrix = entity.computeModelMatrix(new JulianDate(), result); var expected = Transforms.eastNorthUpToFixedFrame(position); expect(modelMatrix).toBe(result); expect(modelMatrix).toEqual(expected); diff --git a/Specs/DataSources/GeoJsonDataSourceSpec.js b/Specs/DataSources/GeoJsonDataSourceSpec.js index a19097e85205..18fd511998bd 100644 --- a/Specs/DataSources/GeoJsonDataSourceSpec.js +++ b/Specs/DataSources/GeoJsonDataSourceSpec.js @@ -279,6 +279,19 @@ defineSuite([ expect(dataSource.show).toBe(true); }); + it('setting name raises changed event', function() { + var dataSource = new GeoJsonDataSource(); + + var spy = jasmine.createSpy('changedEvent'); + dataSource.changedEvent.addEventListener(spy); + + var newName = 'chester'; + dataSource.name = newName; + expect(dataSource.name).toEqual(newName); + expect(spy.calls.count()).toEqual(1); + expect(spy).toHaveBeenCalledWith(dataSource); + }); + it('show sets underlying entity collection show.', function() { var dataSource = new GeoJsonDataSource(); diff --git a/Specs/DataSources/KmlDataSourceSpec.js b/Specs/DataSources/KmlDataSourceSpec.js index 17198cd83648..4d1a3636d5d9 100644 --- a/Specs/DataSources/KmlDataSourceSpec.js +++ b/Specs/DataSources/KmlDataSourceSpec.js @@ -150,6 +150,19 @@ defineSuite([ expect(dataSource.show).toBe(true); }); + it('setting name raises changed event', function() { + var dataSource = new KmlDataSource(options); + + var spy = jasmine.createSpy('changedEvent'); + dataSource.changedEvent.addEventListener(spy); + + var newName = 'garfield'; + dataSource.name = newName; + expect(dataSource.name).toEqual(newName); + expect(spy.calls.count()).toEqual(1); + expect(spy).toHaveBeenCalledWith(dataSource); + }); + it('show sets underlying entity collection show.', function() { var dataSource = new KmlDataSource(options); diff --git a/Specs/Renderer/ShaderProgramSpec.js b/Specs/Renderer/ShaderProgramSpec.js index e2934ed94b66..0fe33e0acc0b 100644 --- a/Specs/Renderer/ShaderProgramSpec.js +++ b/Specs/Renderer/ShaderProgramSpec.js @@ -65,13 +65,13 @@ defineSuite([ var expectedVSText = new ShaderSource({ sources : [vs] - }).createCombinedVertexShader(); + }).createCombinedVertexShader(context); expect(sp._vertexShaderText).toEqual(expectedVSText); var expectedFSText = new ShaderSource({ sources : [fs] - }).createCombinedFragmentShader(); + }).createCombinedFragmentShader(context); expect(sp._fragmentShaderText).toEqual(expectedFSText); }); diff --git a/Specs/Renderer/ShaderSourceSpec.js b/Specs/Renderer/ShaderSourceSpec.js index b9105e4b28ed..80b4f4fbe9c4 100644 --- a/Specs/Renderer/ShaderSourceSpec.js +++ b/Specs/Renderer/ShaderSourceSpec.js @@ -4,12 +4,16 @@ defineSuite([ ShaderSource) { 'use strict'; + var mockContext = { + webgl2 : false + }; + it('combines #defines', function() { var source = new ShaderSource({ defines : ['A', 'B', ''] }); - var shaderText = source.createCombinedVertexShader(); + var shaderText = source.createCombinedVertexShader(mockContext); expect(shaderText).toContain('#define A'); expect(shaderText).toContain('#define B'); expect(shaderText.match(/#define/g).length).toEqual(2); @@ -19,7 +23,7 @@ defineSuite([ var source = new ShaderSource({ sources : ['void func() {}', 'void main() {}'] }); - var shaderText = source.createCombinedVertexShader(); + var shaderText = source.createCombinedVertexShader(mockContext); expect(shaderText).toContain('#line 0\nvoid func() {}'); expect(shaderText).toContain('#line 0\nvoid main() {}'); }); @@ -29,7 +33,7 @@ defineSuite([ defines : ['A', 'B', ''], sources : ['void func() {}', 'void main() {}'] }); - var shaderText = source.createCombinedVertexShader(); + var shaderText = source.createCombinedVertexShader(mockContext); expect(shaderText).toContain('#define A'); expect(shaderText).toContain('#define B'); expect(shaderText.match(/#define/g).length).toEqual(2); @@ -42,7 +46,7 @@ defineSuite([ sources : ['void main() { gl_FragColor = vec4(1.0); }'], pickColorQualifier : 'uniform' }); - var shaderText = source.createCombinedVertexShader(); + var shaderText = source.createCombinedVertexShader(mockContext); expect(shaderText).toContain('uniform vec4 czm_pickColor;'); expect(shaderText).toContain('gl_FragColor = czm_pickColor;'); }); @@ -52,7 +56,7 @@ defineSuite([ sources : ['void main() { gl_FragColor = vec4(1.0); }'], pickColorQualifier : 'varying' }); - var shaderText = source.createCombinedVertexShader(); + var shaderText = source.createCombinedVertexShader(mockContext); expect(shaderText).toContain('varying vec4 czm_pickColor;'); expect(shaderText).toContain('gl_FragColor = czm_pickColor;'); }); @@ -69,7 +73,7 @@ defineSuite([ var source = new ShaderSource({ sources : ['#version 300 es\nvoid main() {gl_FragColor = vec4(1.0); }'] }); - var shaderText = source.createCombinedVertexShader(); + var shaderText = source.createCombinedVertexShader(mockContext); expect(shaderText).toStartWith('#version 300 es\n'); }); }); diff --git a/Specs/Renderer/modernizeShaderSpec.js b/Specs/Renderer/modernizeShaderSpec.js new file mode 100644 index 000000000000..b915fd6461e7 --- /dev/null +++ b/Specs/Renderer/modernizeShaderSpec.js @@ -0,0 +1,317 @@ +defineSuite([ + 'Renderer/modernizeShader' + ], function( + modernizeShader) { + 'use strict'; + + it('adds version string', function() { + var simple = + '#define OUTPUT_DECLARATION \n' + + 'void main() \n' + + '{ \n' + + '} \n'; + var output = modernizeShader(simple, true); + var expected = '#version 300 es'; + expect(output.split('\n')[0]).toEqual(expected); + }); + + it('removes extensions', function() { + var extensions = + '#define OUTPUT_DECLARATION \n' + + '#extension GL_EXT_draw_buffers : enable \n' + + 'void main() \n' + + '{ \n' + + '} \n'; + var output = modernizeShader(extensions, true); + var notExpected = '#extension GL_EXT_draw_buffers : enable \n'; + expect(output).not.toContain(notExpected); + }); + + it('throws exception if no output declaration', function() { + var noOutputDeclaration = + 'void main() \n' + + '{ \n' + + '} \n'; + var runFunc = function() { + modernizeShader(noOutputDeclaration, true); + }; + expect(runFunc).toThrowDeveloperError(); + }); + + it('creates layout qualifier for gl_FragColor', function() { + var noQualifiers = + '#define OUTPUT_DECLARATION \n' + + 'void main() \n' + + '{ \n' + + ' gl_FragColor = vec4(0.0); \n' + + '} \n'; + var output = modernizeShader(noQualifiers, true); + var expected = 'layout(location = 0) out vec4 czm_fragColor;'; + expect(output).toContain(expected); + }); + + it('creates layout qualifier for gl_FragData', function() { + var noQualifiers = + '#define OUTPUT_DECLARATION \n' + + 'void main() \n' + + '{ \n' + + ' gl_FragData[0] = vec4(0.0); \n' + + '} \n'; + var output = modernizeShader(noQualifiers, true); + var expected = 'layout(location = 0) out vec4 czm_out0;'; + expect(output).toContain(expected); + }); + + it('creates layout qualifier for MRT', function() { + var noQualifiers = + '#define OUTPUT_DECLARATION \n' + + '#extension GL_EXT_draw_buffers : enable \n' + + 'void main() \n' + + '{ \n' + + ' gl_FragData[0] = vec4(0.0); \n' + + ' gl_FragData[1] = vec4(1.0); \n' + + '} \n'; + var output = modernizeShader(noQualifiers, true); + var expected0 = 'layout(location = 0) out vec4 czm_out0;'; + var expected1 = 'layout(location = 1) out vec4 czm_out1;'; + expect(output).toContain(expected0); + expect(output).toContain(expected1); + }); + + it('does not create layout qualifier for reserved word lookalike variables', function() { + var noQualifiers = + '#define OUTPUT_DECLARATION \n' + + 'uniform sampler2D example; \n' + + 'void main() \n' + + '{ \n' + + ' vec4 gl_FragData_ = vec4(0.0); \n' + + ' vec4 a_gl_FragData = vec4(0.0); \n' + + ' vec2 thisIsNotAGoodNameForAtexture2D = vec2(0.0); \n' + + ' vec4 gl_FragColor = texture2D(example, thisIsNotAGoodNameForAtexture2D); \n' + + '} \n'; + var output = modernizeShader(noQualifiers, true); + var expectedBadName = 'vec2 thisIsNotAGoodNameForAtexture2D'; + var expectedVariable = 'vec4 a_gl_FragData = vec4(0.0);'; + var expectedTextureCall = 'texture(example, thisIsNotAGoodNameForAtexture2D)'; + var notExpectedLayout = 'layout(location = 0) out czm_out'; + expect(output).toContain(expectedBadName); + expect(output).toContain(expectedVariable); + expect(output).toContain(expectedTextureCall); + expect(output).not.toContain(notExpectedLayout); + }); + + it('creates layout qualifier with swizzle', function() { + var noQualifiers = + '#define OUTPUT_DECLARATION \n' + + '#extension GL_EXT_draw_buffers : enable \n' + + 'void main() \n' + + '{ \n' + + ' gl_FragData[0] = vec4(0.0); \n' + + ' gl_FragData[1].xyz = vec3(1.0); \n' + + '} \n'; + var output = modernizeShader(noQualifiers, true); + var expected0 = 'layout(location = 0) out vec4 czm_out0;'; + var expected1 = 'layout(location = 1) out vec4 czm_out1;'; + expect(output).toContain(expected0); + expect(output).toContain(expected1); + }); + + it('removes old functions/variables from fragment shader', function() { + var old_fragment = + '#define OUTPUT_DECLARATION \n' + + '#extension GL_EXT_draw_buffers : enable \n' + + 'uniform sampler2D example; \n' + + 'uniform sampler2D exampleCube; \n' + + 'uniform sampler2D example3D; \n' + + 'varying vec2 v_textureCoordinates; \n' + + 'void main() \n' + + '{ \n' + + ' gl_FragData[0] = texture2D(example, v_textureCoordinates); \n' + + ' gl_FragData[1] = textureCube(exampleCube, v_textureCoordinates); \n' + + ' gl_FragData[2] = texture3D(example3D, v_textureCoordinates); \n' + + ' gl_FragDepthEXT = 0.0; \n' + + '} \n'; + + var output = modernizeShader(old_fragment, true); + + var expectedDepth = 'gl_FragDepth = 0.0;'; + var expectedTexture2D = 'texture(example, v_textureCoordinates);'; + var expectedTextureCube = 'texture(exampleCube, v_textureCoordinates);'; + var expectedTexture3D = 'texture(example3D, v_textureCoordinates);'; + var expectedIn = 'in vec2 v_textureCoordinates;'; + + var notExpectedDepth = 'gl_FragDepthEXT = 0.0;'; + var notExpectedTexture2D = 'texture2D(example, v_textureCoordinates);'; + var notExpectedTextureCube = 'textureCube(exampleCube, v_textureCoordinates);'; + var notExpectedTexture3D = 'texture3D(example3D, v_textureCoordinates);'; + var notExpectedVarying = 'varying vec2 v_textureCoordinates;'; + + expect(output).toContain(expectedDepth); + expect(output).toContain(expectedTexture2D); + expect(output).toContain(expectedTextureCube); + expect(output).toContain(expectedTexture3D); + expect(output).toContain(expectedIn); + + expect(output).not.toContain(notExpectedDepth); + expect(output).not.toContain(notExpectedTexture2D); + expect(output).not.toContain(notExpectedTextureCube); + expect(output).not.toContain(notExpectedTexture3D); + expect(output).not.toContain(notExpectedVarying); + }); + + it('removes old functions/variables from vertex shader', function() { + var old_vertex = + '#define OUTPUT_DECLARATION \n' + + 'attribute vec4 position; \n' + + 'varying vec4 varyingVariable; \n' + + 'void main() \n' + + '{ \n' + + ' gl_Position = position; \n' + + ' varyingVariable = position.wzyx; \n' + + '} \n'; + + var output = modernizeShader(old_vertex, false); + + var expectedOut = 'out vec4 varyingVariable;'; + var expectedIn = 'in vec4 position;'; + + var notExpectedAttribute = 'attribute vec4 varyingVariable;'; + var notExpectedVarying = 'varying vec2 varyingVariable;'; + + expect(output).toContain(expectedOut); + expect(output).toContain(expectedIn); + + expect(output).not.toContain(notExpectedAttribute); + expect(output).not.toContain(notExpectedVarying); + }); + + it('creates single layout qualifier under single branch, single condition', function() { + var noQualifiers = + '#define OUTPUT_DECLARATION \n' + + '#define EXAMPLE_BRANCH \n' + + 'void main() \n' + + '{ \n' + + ' #ifdef EXAMPLE_BRANCH \n' + + ' gl_FragData[0] = vec4(0.0); \n' + + ' #endif //EXAMPLE_BRANCH \n' + + '} \n'; + var output = modernizeShader(noQualifiers, true); + var expected = '#ifdef EXAMPLE_BRANCH \nlayout(location = 0) out vec4 czm_out0;\n#endif'; + expect(output).toContain(expected); + }); + + it('creates multiple layout qualifiers under single branch, single condition', function() { + var noQualifiers = + '#define OUTPUT_DECLARATION \n' + + '#define EXAMPLE_BRANCH \n' + + 'void main() \n' + + '{ \n' + + ' #ifdef EXAMPLE_BRANCH \n' + + ' gl_FragData[0] = vec4(0.0); \n' + + ' gl_FragData[1] = vec4(1.0); \n' + + ' #endif //EXAMPLE_BRANCH \n' + + '} \n'; + var output = modernizeShader(noQualifiers, true); + var expected0 = '#ifdef EXAMPLE_BRANCH \nlayout(location = 0) out vec4 czm_out0;\n#endif'; + var expected1 = '#ifdef EXAMPLE_BRANCH \nlayout(location = 1) out vec4 czm_out1;\n#endif'; + expect(output).toContain(expected0); + expect(output).toContain(expected1); + }); + + it('creates multiple layout qualifiers under multiple branches, single condition (cancels)', function() { + var noQualifiers = + '#define OUTPUT_DECLARATION \n' + + '#define EXAMPLE_BRANCH \n' + + 'void main() \n' + + '{ \n' + + ' #ifdef EXAMPLE_BRANCH \n' + + ' gl_FragData[0] = vec4(0.0); \n' + + ' gl_FragData[1] = vec4(1.0); \n' + + ' #endif //EXAMPLE_BRANCH \n' + + ' #ifndef EXAMPLE_BRANCH \n' + + ' gl_FragData[0] = vec4(0.0); \n' + + ' gl_FragData[1] = vec4(1.0); \n' + + ' #endif //!EXAMPLE_BRANCH \n' + + '} \n'; + var output = modernizeShader(noQualifiers, true); + var notExpected = '#ifdef EXAMPLE_BRANCH \nlayout(location = 0) out vec4 czm_out0;\n#endif'; + expect(output).not.toContain(notExpected); + }); + + it('creates single layout qualifier under multiple branches, multiple conditions (cancels)', function() { + var noQualifiers = + '#define OUTPUT_DECLARATION \n' + + '#define EXAMPLE_BRANCH \n' + + '#define EXAMPLE_BRANCH1 \n' + + 'void main() \n' + + '{ \n' + + ' #ifdef EXAMPLE_BRANCH \n' + + ' gl_FragData[0] = vec4(0.0); \n' + + ' #endif //EXAMPLE_BRANCH \n' + + ' #ifdef EXAMPLE_BRANCH1 \n' + + ' gl_FragData[0] = vec4(0.0); \n' + + ' #endif //EXAMPLE_BRANCH1 \n' + + '} \n'; + var output = modernizeShader(noQualifiers, true); + var notExpected = '#ifdef EXAMPLE_BRANCH \nlayout(location = 0) out vec4 czm_out0;\n#endif'; + expect(output).not.toContain(notExpected); + }); + + it('creates multiple layout qualifiers under multiple branches, multiple conditions (cascades)', function() { + var noQualifiers = + '#define OUTPUT_DECLARATION \n' + + '#define EXAMPLE_BRANCH \n' + + '#define EXAMPLE_BRANCH1 \n' + + 'void main() \n' + + '{ \n' + + ' #ifdef EXAMPLE_BRANCH \n' + + ' gl_FragData[0] = vec4(0.0); \n' + + ' #ifdef EXAMPLE_BRANCH1 \n' + + ' gl_FragData[1] = vec4(1.0); \n' + + ' #endif //EXAMPLE_BRANCH1 \n' + + ' #endif //EXAMPLE_BRANCH \n' + + '} \n'; + var output = modernizeShader(noQualifiers, true); + var expected0 = '#ifdef EXAMPLE_BRANCH \nlayout(location = 0) out vec4 czm_out0;\n#endif'; + var expected1 = /#ifdef (EXAMPLE_BRANCH|EXAMPLE_BRANCH1)\s*\n\s*#ifdef (EXAMPLE_BRANCH1|EXAMPLE_BRANCH)\s*\n\s*layout\(location = 1\) out vec4 czm_out1;/g; + var containsExpected0 = expected1.test(output); + expect(output).toContain(expected0); + expect(containsExpected0).toBe(true); + }); + + it('creates single layout qualifier under multiple branches, single condition (else)', function() { + var noQualifiers = + '#define OUTPUT_DECLARATION \n' + + '#define EXAMPLE_BRANCH \n' + + 'void main() \n' + + '{ \n' + + ' #ifdef EXAMPLE_BRANCH \n' + + ' gl_FragData[0] = vec4(0.0); \n' + + ' #else \n' + + ' gl_FragData[0] = vec4(1.0); \n' + + ' #endif //EXAMPLE_BRANCH \n' + + '} \n'; + var output = modernizeShader(noQualifiers, true); + var notExpected = '#ifdef EXAMPLE_BRANCH \nlayout(location = 0) out vec4 czm_out0;\n#endif'; + expect(output).not.toContain(notExpected); + }); + + it('creates branched layout qualifiers for gl_FragColor and gl_FragData', function() { + var noQualifiers = + '#define OUTPUT_DECLARATION \n' + + '#define EXAMPLE_BRANCH \n' + + 'void main() \n' + + '{ \n' + + ' #ifdef EXAMPLE_BRANCH \n' + + ' gl_FragData[0] = vec4(0.0); \n' + + ' #else \n' + + ' gl_FragColor = vec4(1.0); \n' + + ' #endif //EXAMPLE_BRANCH \n' + + '} \n'; + var output = modernizeShader(noQualifiers, true); + var expected0 = '#ifdef EXAMPLE_BRANCH \nlayout(location = 0) out vec4 czm_out0;\n#endif'; + var expected1 = '#ifndef EXAMPLE_BRANCH \nlayout(location = 0) out vec4 czm_fragColor;\n#endif'; + expect(output).toContain(expected0); + expect(output).toContain(expected1); + }); +}); diff --git a/Specs/Scene/Cesium3DTileStyleSpec.js b/Specs/Scene/Cesium3DTileStyleSpec.js index e1dccae26621..43fab6e103b1 100644 --- a/Specs/Scene/Cesium3DTileStyleSpec.js +++ b/Specs/Scene/Cesium3DTileStyleSpec.js @@ -82,28 +82,28 @@ defineSuite([ }); }); - it('sets show value to default expression', function() { + it('sets show value to undefined if value not present', function() { var style = new Cesium3DTileStyle({}); - expect(style.show).toEqual(new Expression('true')); + expect(style.show).toBeUndefined(); style = new Cesium3DTileStyle(); - expect(style.show).toEqual(new Expression('true')); + expect(style.show).toBeUndefined(); }); - it('sets color value to default expression', function() { + it('sets color value to undefined if value not present', function() { var style = new Cesium3DTileStyle({}); - expect(style.color).toEqual(new Expression('color("#ffffff")')); + expect(style.color).toBeUndefined(); style = new Cesium3DTileStyle(); - expect(style.color).toEqual(new Expression('color("#ffffff")')); + expect(style.color).toBeUndefined(); }); - it('sets pointSize value to default expression', function() { + it('sets pointSize value to undefined if value not present', function() { var style = new Cesium3DTileStyle({}); - expect(style.pointSize).toEqual(new Expression('1')); + expect(style.pointSize).toBeUndefined(); style = new Cesium3DTileStyle(); - expect(style.pointSize).toEqual(new Expression('1')); + expect(style.pointSize).toBeUndefined(); }); it('sets show value to expression', function() { @@ -147,11 +147,48 @@ defineSuite([ expect(style.show).toEqual(new ConditionsExpression(jsonExp)); }); - it('sets show to undefined if not a string, boolean, or conditional', function() { - var style = new Cesium3DTileStyle({ - show : 1 + it('sets show expressions in setter', function() { + var style = new Cesium3DTileStyle(); + + var condExp = new ConditionsExpression({ + conditions : [ + ['${height} > 2', 'false'], + ['true', 'true'] + ] }); - expect(style.show).toEqual(undefined); + + style.show = condExp; + expect(style.show).toEqual(condExp); + + var exp = new Expression('false'); + style.show = exp; + expect(style.show).toEqual(exp); + }); + + it('sets show values in setter', function() { + var defines = { + 'showFactor': 10 + }; + var style = new Cesium3DTileStyle({ 'defines': defines }); + + style.show = '${height} * ${showFactor} >= 1000'; + expect(style.show).toEqual(new Expression('${height} * ${showFactor} >= 1000', defines)); + + style.show = false; + expect(style.show).toEqual(new Expression('false')); + + var jsonExp = { + conditions : [ + ['${height} > ${showFactor}', 'false'], + ['true', 'true'] + ] + }; + + style.show = jsonExp; + expect(style.show).toEqual(new ConditionsExpression(jsonExp, defines)); + + style.show = undefined; + expect(style.show).toBeUndefined(); }); it('sets color value to expression', function() { @@ -185,11 +222,45 @@ defineSuite([ expect(style.color).toEqual(new ConditionsExpression(jsonExp)); }); - it('sets color to undefined if not a string or conditional', function() { - var style = new Cesium3DTileStyle({ - color : 1 + it('sets color expressions in setter', function() { + var style = new Cesium3DTileStyle(); + + var exp = new Expression('color("red")'); + style.color = exp; + expect(style.color).toEqual(exp); + + var condExp = new ConditionsExpression({ + conditions : [ + ['${height} > 2', 'color("cyan")'], + ['true', 'color("blue")'] + ] }); - expect(style.color).toEqual(undefined); + + style.color = condExp; + expect(style.color).toEqual(condExp); + + style.color = undefined; + expect(style.color).toBeUndefined(); + }); + + it('sets color values in setter', function() { + var defines = { + 'targetColor': 'red' + }; + var style = new Cesium3DTileStyle({ 'defines': defines }); + + style.color = 'color("${targetColor}")'; + expect(style.color).toEqual(new Expression('color("${targetColor}")', defines)); + + var jsonExp = { + conditions : [ + ['${height} > 2', 'color("cyan")'], + ['true', 'color("${targetColor}")'] + ] + }; + + style.color = jsonExp; + expect(style.color).toEqual(new ConditionsExpression(jsonExp, defines)); }); it('sets pointSize value to expression', function() { @@ -223,11 +294,51 @@ defineSuite([ expect(style.pointSize).toEqual(new ConditionsExpression(jsonExp)); }); - it('sets pointSize to undefined if not a number, string, or conditional', function() { - var style = new Cesium3DTileStyle({ - pointSize : true + it('sets pointSize expressions in setter', function() { + var style = new Cesium3DTileStyle(); + + style.pointSize = 2; + expect(style.pointSize).toEqual(new Expression('2')); + + var exp = new Expression('2'); + style.pointSize = exp; + expect(style.pointSize).toEqual(exp); + + var condExp = new ConditionsExpression({ + conditions : [ + ['${height} > 2', '1.0'], + ['true', '2.0'] + ] }); - expect(style.pointSize).toEqual(undefined); + + style.pointSize = condExp; + expect(style.pointSize).toEqual(condExp); + + style.pointSize = undefined; + expect(style.pointSize).toBeUndefined(); + }); + + it('sets pointSize values in setter', function() { + var defines = { + 'targetPointSize': '2.0' + }; + var style = new Cesium3DTileStyle({ 'defines': defines }); + + style.pointSize = 2; + expect(style.pointSize).toEqual(new Expression('2')); + + style.pointSize = '${targetPointSize} + 1.0'; + expect(style.pointSize).toEqual(new Expression('${targetPointSize} + 1.0', defines)); + + var jsonExp = { + conditions : [ + ['${height} > 2', '1.0'], + ['true', '${targetPointSize}'] + ] + }; + + style.pointSize = jsonExp; + expect(style.pointSize).toEqual(new ConditionsExpression(jsonExp, defines)); }); it('throws on accessing style if not ready', function() { @@ -334,7 +445,6 @@ defineSuite([ expect(style.show.evaluate(frameState, feature1)).toEqual(true); expect(style.show.evaluate(frameState, feature2)).toEqual(false); - expect(style.color.evaluateColor(frameState, undefined)).toEqual(Color.WHITE); }); it('applies show style with regexp and variables', function() { @@ -344,7 +454,6 @@ defineSuite([ expect(style.show.evaluate(frameState, feature1)).toEqual(true); expect(style.show.evaluate(frameState, feature2)).toEqual(false); - expect(style.color.evaluateColor(frameState, undefined)).toEqual(Color.WHITE); }); it('applies show style with conditional', function() { @@ -368,7 +477,6 @@ defineSuite([ var style = new Cesium3DTileStyle({ "color" : "(${Temperature} > 90) ? color('red') : color('white')" }); - expect(style.show.evaluate(frameState, feature1)).toEqual(true); expect(style.color.evaluateColor(frameState, feature1)).toEqual(Color.WHITE); expect(style.color.evaluateColor(frameState, feature2)).toEqual(Color.RED); }); @@ -377,7 +485,6 @@ defineSuite([ var style = new Cesium3DTileStyle({ "color" : "rgba(${red}, ${green}, ${blue}, (${volume} > 100 ? 0.5 : 1.0))" }); - expect(style.show.evaluate(frameState, feature1)).toEqual(true); expect(style.color.evaluateColor(frameState, feature1)).toEqual(new Color(38/255, 255/255, 82/255, 0.5)); expect(style.color.evaluateColor(frameState, feature2)).toEqual(new Color(255/255, 30/255, 30/255, 1.0)); }); @@ -395,7 +502,6 @@ defineSuite([ ] } }); - expect(style.show.evaluate(frameState, feature1)).toEqual(true); expect(style.color.evaluateColor(frameState, feature1)).toEqual(Color.RED); expect(style.color.evaluateColor(frameState, feature2)).toEqual(Color.LIME); }); @@ -413,7 +519,6 @@ defineSuite([ ] } }); - expect(style.show.evaluate(frameState, feature1)).toEqual(true); expect(style.color.evaluateColor(frameState, feature1)).toEqual(Color.BLUE); expect(style.color.evaluateColor(frameState, feature2)).toEqual(Color.YELLOW); }); diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js index 307a07e4c7dd..d791f597de56 100644 --- a/Specs/Scene/Cesium3DTilesetSpec.js +++ b/Specs/Scene/Cesium3DTilesetSpec.js @@ -1880,11 +1880,15 @@ defineSuite([ return Cesium3DTilesTester.loadTileset(scene, url).then(function(tileset) { // Check that the feature is red var sourceRed; - expect(scene).toRenderAndCall(function(rgba) { + var renderOptions = { + scene : scene, + time : new JulianDate(2457522.154792) + }; + expect(renderOptions).toRenderAndCall(function(rgba) { sourceRed = rgba[0]; }); - expect(scene).toRenderAndCall(function(rgba) { + expect(renderOptions).toRenderAndCall(function(rgba) { expect(rgba[0]).toBeGreaterThan(0); expect(rgba[1]).toEqual(0); expect(rgba[2]).toEqual(0); @@ -1898,7 +1902,7 @@ defineSuite([ tileset.style = new Cesium3DTileStyle({ color : 'rgb(128, 128, 0)' }); - expect(scene).toRenderAndCall(function(rgba) { + expect(renderOptions).toRenderAndCall(function(rgba) { expect(rgba[0]).toBeGreaterThan(0); expect(rgba[0]).toBeLessThan(sourceRed); expect(rgba[1]).toEqual(0); @@ -1910,7 +1914,7 @@ defineSuite([ tileset.style = new Cesium3DTileStyle({ color : 'rgba(255, 255, 0, 0.5)' }); - expect(scene).toRenderAndCall(function(rgba) { + expect(renderOptions).toRenderAndCall(function(rgba) { expect(rgba[0]).toBeGreaterThan(0); expect(rgba[0]).toBeLessThan(sourceRed); expect(rgba[1]).toEqual(0); @@ -1927,7 +1931,7 @@ defineSuite([ }); var replaceRed; var replaceGreen; - expect(scene).toRenderAndCall(function(rgba) { + expect(renderOptions).toRenderAndCall(function(rgba) { replaceRed = rgba[0]; replaceGreen = rgba[1]; expect(rgba[0]).toBeGreaterThan(0); @@ -1942,7 +1946,7 @@ defineSuite([ tileset.style = new Cesium3DTileStyle({ color : 'rgba(255, 255, 0, 0.5)' }); - expect(scene).toRenderAndCall(function(rgba) { + expect(renderOptions).toRenderAndCall(function(rgba) { expect(rgba[0]).toBeGreaterThan(0); expect(rgba[0]).toBeLessThan(255); expect(rgba[1]).toBeGreaterThan(0); @@ -1961,7 +1965,7 @@ defineSuite([ }); var mixRed; var mixGreen; - expect(scene).toRenderAndCall(function(rgba) { + expect(renderOptions).toRenderAndCall(function(rgba) { mixRed = rgba[0]; mixGreen = rgba[1]; expect(rgba[0]).toBeGreaterThan(replaceRed); @@ -1974,7 +1978,7 @@ defineSuite([ // Set colorBlendAmount to 0.25. Expect color to be closer to the source color. tileset.colorBlendAmount = 0.25; - expect(scene).toRenderAndCall(function(rgba) { + expect(renderOptions).toRenderAndCall(function(rgba) { expect(rgba[0]).toBeGreaterThan(mixRed); expect(rgba[0]).toBeLessThan(sourceRed); expect(rgba[1]).toBeGreaterThan(0); @@ -1985,7 +1989,7 @@ defineSuite([ // Set colorBlendAmount to 0.0. Expect color to equal the source color tileset.colorBlendAmount = 0.0; - expect(scene).toRenderAndCall(function(rgba) { + expect(renderOptions).toRenderAndCall(function(rgba) { expect(rgba[0]).toEqual(sourceRed); expect(rgba[1]).toEqual(0); expect(rgba[2]).toEqual(0); @@ -1994,7 +1998,7 @@ defineSuite([ // Set colorBlendAmount to 1.0. Expect color to equal the style color tileset.colorBlendAmount = 1.0; - expect(scene).toRenderAndCall(function(rgba) { + expect(renderOptions).toRenderAndCall(function(rgba) { expect(rgba[0]).toEqual(replaceRed); expect(rgba[1]).toEqual(replaceGreen); expect(rgba[2]).toEqual(0); @@ -2006,7 +2010,7 @@ defineSuite([ tileset.style = new Cesium3DTileStyle({ color : 'rgba(255, 255, 0, 0.5)' }); - expect(scene).toRenderAndCall(function(rgba) { + expect(renderOptions).toRenderAndCall(function(rgba) { expect(rgba[0]).toBeGreaterThan(0); expect(rgba[1]).toBeGreaterThan(0); expect(rgba[2]).toEqual(0); diff --git a/Specs/Scene/ClassificationPrimitiveSpec.js b/Specs/Scene/ClassificationPrimitiveSpec.js new file mode 100644 index 000000000000..0bbbba8058ad --- /dev/null +++ b/Specs/Scene/ClassificationPrimitiveSpec.js @@ -0,0 +1,909 @@ +defineSuite([ + 'Scene/ClassificationPrimitive', + 'Core/BoxGeometry', + 'Core/Cartesian3', + 'Core/Color', + 'Core/ColorGeometryInstanceAttribute', + 'Core/destroyObject', + 'Core/DistanceDisplayConditionGeometryInstanceAttribute', + 'Core/Ellipsoid', + 'Core/GeometryInstance', + 'Core/HeadingPitchRange', + 'Core/Math', + 'Core/Matrix4', + 'Core/PolygonGeometry', + 'Core/Rectangle', + 'Core/RectangleGeometry', + 'Core/ShowGeometryInstanceAttribute', + 'Core/Transforms', + 'Renderer/Pass', + 'Scene/PerInstanceColorAppearance', + 'Scene/Primitive', + 'Specs/createScene', + 'Specs/pollToPromise' + ], function( + ClassificationPrimitive, + BoxGeometry, + Cartesian3, + Color, + ColorGeometryInstanceAttribute, + destroyObject, + DistanceDisplayConditionGeometryInstanceAttribute, + Ellipsoid, + GeometryInstance, + HeadingPitchRange, + CesiumMath, + Matrix4, + PolygonGeometry, + Rectangle, + RectangleGeometry, + ShowGeometryInstanceAttribute, + Transforms, + Pass, + PerInstanceColorAppearance, + Primitive, + createScene, + pollToPromise) { + 'use strict'; + + var scene; + + var ellipsoid; + var rectangle; + + var depthColor; + var boxColor; + + var boxInstance; + var primitive; + var depthPrimitive; + + beforeAll(function() { + scene = createScene(); + scene.fxaa = false; + + ellipsoid = Ellipsoid.WGS84; + }); + + afterAll(function() { + scene.destroyForSpecs(); + }); + + function MockGlobePrimitive(primitive) { + this._primitive = primitive; + } + MockGlobePrimitive.prototype.update = function(frameState) { + var commandList = frameState.commandList; + var startLength = commandList.length; + this._primitive.update(frameState); + + for (var i = startLength; i < commandList.length; ++i) { + var command = commandList[i]; + command.pass = Pass.GLOBE; + } + }; + + MockGlobePrimitive.prototype.isDestroyed = function() { + return false; + }; + + MockGlobePrimitive.prototype.destroy = function() { + this._primitive.destroy(); + return destroyObject(this); + }; + + beforeEach(function() { + scene.morphTo3D(0); + + rectangle = Rectangle.fromDegrees(-80.0, 20.0, -70.0, 30.0); + + var depthColorAttribute = ColorGeometryInstanceAttribute.fromColor(new Color(0.0, 0.0, 1.0, 1.0)); + depthColor = depthColorAttribute.value; + var primitive = new Primitive({ + geometryInstances : new GeometryInstance({ + geometry : new RectangleGeometry({ + ellipsoid : ellipsoid, + rectangle : rectangle + }), + id : 'depth rectangle', + attributes : { + color : depthColorAttribute + } + }), + appearance : new PerInstanceColorAppearance({ + translucent : false, + flat : true + }), + asynchronous : false + }); + + // wrap rectangle primitive so it gets executed during the globe pass to lay down depth + depthPrimitive = new MockGlobePrimitive(primitive); + + var center = Rectangle.center(rectangle); + var origin = ellipsoid.cartographicToCartesian(center); + var modelMatrix = Transforms.eastNorthUpToFixedFrame(origin); + + var dimensions = new Cartesian3(1000000.0, 1000000.0, 1000000.0); + + var boxColorAttribute = ColorGeometryInstanceAttribute.fromColor(new Color(1.0, 1.0, 0.0, 1.0)); + boxColor = boxColorAttribute.value; + boxInstance = new GeometryInstance({ + geometry : BoxGeometry.fromDimensions({ + dimensions : dimensions + }), + modelMatrix : modelMatrix, + id : 'box', + attributes : { + color : boxColorAttribute + } + }); + }); + + afterEach(function() { + scene.groundPrimitives.removeAll(); + primitive = primitive && !primitive.isDestroyed() && primitive.destroy(); + depthPrimitive = depthPrimitive && !depthPrimitive.isDestroyed() && depthPrimitive.destroy(); + }); + + it('default constructs', function() { + primitive = new ClassificationPrimitive(); + expect(primitive.geometryInstances).not.toBeDefined(); + expect(primitive.show).toEqual(true); + expect(primitive.vertexCacheOptimize).toEqual(false); + expect(primitive.interleave).toEqual(false); + expect(primitive.compressVertices).toEqual(true); + expect(primitive.releaseGeometryInstances).toEqual(true); + expect(primitive.allowPicking).toEqual(true); + expect(primitive.asynchronous).toEqual(true); + expect(primitive.debugShowBoundingVolume).toEqual(false); + expect(primitive.debugShowShadowVolume).toEqual(false); + }); + + it('constructs with options', function() { + var geometryInstances = []; + + primitive = new ClassificationPrimitive({ + geometryInstances : geometryInstances, + show : false, + vertexCacheOptimize : true, + interleave : true, + compressVertices : false, + releaseGeometryInstances : false, + allowPicking : false, + asynchronous : false, + debugShowBoundingVolume : true, + debugShowShadowVolume : true + }); + + expect(primitive.geometryInstances).toEqual(geometryInstances); + expect(primitive.show).toEqual(false); + expect(primitive.vertexCacheOptimize).toEqual(true); + expect(primitive.interleave).toEqual(true); + expect(primitive.compressVertices).toEqual(false); + expect(primitive.releaseGeometryInstances).toEqual(false); + expect(primitive.allowPicking).toEqual(false); + expect(primitive.asynchronous).toEqual(false); + expect(primitive.debugShowBoundingVolume).toEqual(true); + expect(primitive.debugShowShadowVolume).toEqual(true); + }); + + it('releases geometry instances when releaseGeometryInstances is true', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + releaseGeometryInstances : true, + asynchronous : false + }); + + expect(primitive.geometryInstances).toBeDefined(); + scene.groundPrimitives.add(primitive); + scene.renderForSpecs(); + expect(primitive.geometryInstances).not.toBeDefined(); + }); + + it('does not release geometry instances when releaseGeometryInstances is false', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + releaseGeometryInstances : false, + asynchronous : false + }); + + expect(primitive.geometryInstances).toBeDefined(); + scene.groundPrimitives.add(primitive); + scene.renderForSpecs(); + expect(primitive.geometryInstances).toBeDefined(); + }); + + it('adds afterRender promise to frame state', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + releaseGeometryInstances : false, + asynchronous : false + }); + + scene.groundPrimitives.add(primitive); + scene.renderForSpecs(); + + return primitive.readyPromise.then(function(param) { + expect(param.ready).toBe(true); + }); + }); + + it('does not render when geometryInstances is undefined', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : undefined, + appearance : new PerInstanceColorAppearance(), + asynchronous : false + }); + + var frameState = scene.frameState; + frameState.commandList.length = 0; + + primitive.update(frameState); + expect(frameState.commandList.length).toEqual(0); + }); + + it('does not render when show is false', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + asynchronous : false + }); + + var frameState = scene.frameState; + + frameState.commandList.length = 0; + primitive.update(frameState); + expect(frameState.afterRender.length).toEqual(1); + + frameState.afterRender[0](); + frameState.commandList.length = 0; + primitive.update(frameState); + expect(frameState.commandList.length).toBeGreaterThan(0); + + frameState.commandList.length = 0; + primitive.show = false; + primitive.update(frameState); + expect(frameState.commandList.length).toEqual(0); + }); + + it('does not render other than for the color or pick pass', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + asynchronous : false + }); + + var frameState = scene.frameState; + frameState.passes.render = false; + frameState.passes.pick = false; + + primitive.update(frameState); + expect(frameState.commandList.length).toEqual(0); + }); + + function verifyClassificationPrimitiveRender(primitive, color) { + scene.camera.setView({ destination : rectangle }); + + scene.groundPrimitives.add(depthPrimitive); + expect(scene).toRenderAndCall(function(rgba) { + expect(rgba).not.toEqual([0, 0, 0, 255]); + expect(rgba[0]).toEqual(0); + }); + + scene.groundPrimitives.add(primitive); + expect(scene).toRender(color); + } + + it('renders in 3D', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + asynchronous : false + }); + + verifyClassificationPrimitiveRender(primitive, boxColor); + }); + + it('renders in Columbus view when scene3DOnly is false', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + asynchronous : false + }); + + scene.morphToColumbusView(0); + verifyClassificationPrimitiveRender(primitive, boxColor); + }); + + it('renders in 2D when scene3DOnly is false', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + asynchronous : false + }); + + scene.morphTo2D(0); + verifyClassificationPrimitiveRender(primitive, boxColor); + }); + + it('renders batched instances', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + var neCarto = Rectangle.northeast(rectangle); + var nwCarto = Rectangle.northwest(rectangle); + + var ne = ellipsoid.cartographicToCartesian(neCarto); + var nw = ellipsoid.cartographicToCartesian(nwCarto); + + var direction = Cartesian3.subtract(ne, nw, new Cartesian3()); + var distance = Cartesian3.magnitude(direction) * 0.25; + Cartesian3.normalize(direction, direction); + Cartesian3.multiplyByScalar(direction, distance, direction); + + var center = Rectangle.center(rectangle); + var origin = ellipsoid.cartographicToCartesian(center); + + var origin1 = Cartesian3.add(origin, direction, new Cartesian3()); + var modelMatrix = Transforms.eastNorthUpToFixedFrame(origin1); + + var dimensions = new Cartesian3(500000.0, 1000000.0, 1000000.0); + + var boxColorAttribute = ColorGeometryInstanceAttribute.fromColor(new Color(0.0, 1.0, 1.0, 1.0)); + var boxInstance1 = new GeometryInstance({ + geometry : BoxGeometry.fromDimensions({ + dimensions : dimensions + }), + modelMatrix : modelMatrix, + id : 'box1', + attributes : { + color : boxColorAttribute + } + }); + + Cartesian3.negate(direction, direction); + var origin2 = Cartesian3.add(origin, direction, new Cartesian3()); + modelMatrix = Transforms.eastNorthUpToFixedFrame(origin2); + + var boxInstance2 = new GeometryInstance({ + geometry : BoxGeometry.fromDimensions({ + dimensions : dimensions + }), + modelMatrix : modelMatrix, + id : 'box2', + attributes : { + color : boxColorAttribute + } + }); + + primitive = new ClassificationPrimitive({ + geometryInstances : [boxInstance1, boxInstance2], + asynchronous : false + }); + verifyClassificationPrimitiveRender(primitive, boxColorAttribute.value); + }); + + it('renders bounding volume with debugShowBoundingVolume', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + asynchronous : false, + debugShowBoundingVolume : true + }); + + scene.groundPrimitives.add(primitive); + scene.camera.setView({ destination : rectangle }); + expect(scene).toRenderAndCall(function(rgba) { + expect(rgba[1]).toBeGreaterThanOrEqualTo(0); + expect(rgba[1]).toBeGreaterThanOrEqualTo(0); + expect(rgba[2]).toBeGreaterThanOrEqualTo(0); + expect(rgba[3]).toEqual(255); + }); + }); + + it('renders shadow volume with debugShowShadowVolume', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + asynchronous : false, + debugShowShadowVolume : true + }); + + scene.groundPrimitives.add(primitive); + scene.camera.setView({ destination : rectangle }); + expect(scene).toRenderAndCall(function(rgba) { + expect(rgba[1]).toBeGreaterThanOrEqualTo(0); + expect(rgba[1]).toBeGreaterThanOrEqualTo(0); + expect(rgba[2]).toBeGreaterThanOrEqualTo(0); + expect(rgba[3]).toEqual(255); + }); + }); + + it('get per instance attributes', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + asynchronous : false + }); + + verifyClassificationPrimitiveRender(primitive, boxColor); + + var attributes = primitive.getGeometryInstanceAttributes('box'); + expect(attributes.color).toBeDefined(); + }); + + it('modify color instance attribute', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + asynchronous : false + }); + + verifyClassificationPrimitiveRender(primitive, boxColor); + + scene.groundPrimitives.destroyPrimitives = false; + scene.groundPrimitives.removeAll(); + scene.groundPrimitives.destroyPrimitives = true; + + var newColor = [255, 255, 255, 255]; + var attributes = primitive.getGeometryInstanceAttributes('box'); + expect(attributes.color).toBeDefined(); + attributes.color = newColor; + + verifyClassificationPrimitiveRender(primitive, newColor); + }); + + it('modify show instance attribute', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + boxInstance.attributes.show = new ShowGeometryInstanceAttribute(true); + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + asynchronous : false + }); + + verifyClassificationPrimitiveRender(primitive, boxColor); + + scene.groundPrimitives.destroyPrimitives = false; + scene.groundPrimitives.removeAll(); + scene.groundPrimitives.destroyPrimitives = true; + + var attributes = primitive.getGeometryInstanceAttributes('box'); + expect(attributes.show).toBeDefined(); + attributes.show = [0]; + + verifyClassificationPrimitiveRender(primitive, depthColor); + }); + + it('get bounding sphere from per instance attribute', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + asynchronous : false + }); + + verifyClassificationPrimitiveRender(primitive, boxColor); + + var attributes = primitive.getGeometryInstanceAttributes('box'); + expect(attributes.boundingSphere).toBeDefined(); + }); + + it('getGeometryInstanceAttributes returns same object each time', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + asynchronous : false + }); + + verifyClassificationPrimitiveRender(primitive, boxColor); + + var attributes = primitive.getGeometryInstanceAttributes('box'); + var attributes2 = primitive.getGeometryInstanceAttributes('box'); + expect(attributes).toBe(attributes2); + }); + + it('picking', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + asynchronous : false + }); + + verifyClassificationPrimitiveRender(primitive, boxColor); + + expect(scene).toPickAndCall(function(result) { + expect(result.id).toEqual('box'); + }); + }); + + it('does not pick when allowPicking is false', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + allowPicking : false, + asynchronous : false + }); + + verifyClassificationPrimitiveRender(primitive, boxColor); + + expect(scene).notToPick(); + }); + + it('internally invalid asynchronous geometry resolves promise and sets ready', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : new GeometryInstance({ + geometry : PolygonGeometry.fromPositions({ + positions : [] + }), + attributes: { + color: ColorGeometryInstanceAttribute.fromColor(Color.RED) + } + }), + compressVertices : false + }); + + var frameState = scene.frameState; + frameState.afterRender.length = 0; + return pollToPromise(function() { + if (frameState.afterRender.length > 0) { + frameState.afterRender[0](); + return true; + } + primitive.update(frameState); + return false; + }).then(function() { + return primitive.readyPromise.then(function(arg) { + expect(arg).toBe(primitive); + expect(primitive.ready).toBe(true); + }); + }); + }); + + it('internally invalid synchronous geometry resolves promise and sets ready', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : new GeometryInstance({ + geometry : PolygonGeometry.fromPositions({ + positions : [] + }), + attributes: { + color: ColorGeometryInstanceAttribute.fromColor(Color.RED) + } + }), + asynchronous : false, + compressVertices : false + }); + + var frameState = scene.frameState; + frameState.afterRender.length = 0; + return pollToPromise(function() { + if (frameState.afterRender.length > 0) { + frameState.afterRender[0](); + return true; + } + primitive.update(frameState); + return false; + }).then(function() { + return primitive.readyPromise.then(function(arg) { + expect(arg).toBe(primitive); + expect(primitive.ready).toBe(true); + }); + }); + }); + + it('update throws when batched instance colors are different', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + var neCarto = Rectangle.northeast(rectangle); + var nwCarto = Rectangle.northwest(rectangle); + + var ne = ellipsoid.cartographicToCartesian(neCarto); + var nw = ellipsoid.cartographicToCartesian(nwCarto); + + var direction = Cartesian3.subtract(ne, nw, new Cartesian3()); + var distance = Cartesian3.magnitude(direction) * 0.25; + Cartesian3.normalize(direction, direction); + Cartesian3.multiplyByScalar(direction, distance, direction); + + var center = Rectangle.center(rectangle); + var origin = ellipsoid.cartographicToCartesian(center); + + var origin1 = Cartesian3.add(origin, direction, new Cartesian3()); + var modelMatrix = Transforms.eastNorthUpToFixedFrame(origin1); + + var dimensions = new Cartesian3(500000.0, 1000000.0, 1000000.0); + + var boxColorAttribute = ColorGeometryInstanceAttribute.fromColor(new Color(0.0, 1.0, 1.0, 1.0)); + var boxInstance1 = new GeometryInstance({ + geometry : BoxGeometry.fromDimensions({ + dimensions : dimensions + }), + modelMatrix : modelMatrix, + id : 'box1', + attributes : { + color : ColorGeometryInstanceAttribute.fromColor(new Color(0.0, 1.0, 1.0, 1.0)) + } + }); + + Cartesian3.negate(direction, direction); + var origin2 = Cartesian3.add(origin, direction, new Cartesian3()); + modelMatrix = Transforms.eastNorthUpToFixedFrame(origin2); + + var boxInstance2 = new GeometryInstance({ + geometry : BoxGeometry.fromDimensions({ + dimensions : dimensions + }), + modelMatrix : modelMatrix, + id : 'box2', + attributes : { + color : ColorGeometryInstanceAttribute.fromColor(new Color(1.0, 0.0, 1.0, 1.0)) + } + }); + + primitive = new ClassificationPrimitive({ + geometryInstances : [boxInstance1, boxInstance2], + asynchronous : false + }); + + expect(function() { + verifyClassificationPrimitiveRender(primitive, boxColorAttribute.value); + }).toThrowDeveloperError(); + }); + + it('update throws when one batched instance color is undefined', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + var neCarto = Rectangle.northeast(rectangle); + var nwCarto = Rectangle.northwest(rectangle); + + var ne = ellipsoid.cartographicToCartesian(neCarto); + var nw = ellipsoid.cartographicToCartesian(nwCarto); + + var direction = Cartesian3.subtract(ne, nw, new Cartesian3()); + var distance = Cartesian3.magnitude(direction) * 0.25; + Cartesian3.normalize(direction, direction); + Cartesian3.multiplyByScalar(direction, distance, direction); + + var center = Rectangle.center(rectangle); + var origin = ellipsoid.cartographicToCartesian(center); + + var origin1 = Cartesian3.add(origin, direction, new Cartesian3()); + var modelMatrix = Transforms.eastNorthUpToFixedFrame(origin1); + + var dimensions = new Cartesian3(500000.0, 1000000.0, 1000000.0); + + var boxColorAttribute = ColorGeometryInstanceAttribute.fromColor(new Color(0.0, 1.0, 1.0, 1.0)); + var boxInstance1 = new GeometryInstance({ + geometry : BoxGeometry.fromDimensions({ + dimensions : dimensions + }), + modelMatrix : modelMatrix, + id : 'box1', + attributes : { + color : ColorGeometryInstanceAttribute.fromColor(new Color(0.0, 1.0, 1.0, 1.0)) + } + }); + + Cartesian3.negate(direction, direction); + var origin2 = Cartesian3.add(origin, direction, new Cartesian3()); + modelMatrix = Transforms.eastNorthUpToFixedFrame(origin2); + + var boxInstance2 = new GeometryInstance({ + geometry : BoxGeometry.fromDimensions({ + dimensions : dimensions + }), + modelMatrix : modelMatrix, + id : 'box2' + }); + + primitive = new ClassificationPrimitive({ + geometryInstances : [boxInstance1, boxInstance2], + asynchronous : false + }); + + expect(function() { + verifyClassificationPrimitiveRender(primitive, boxColorAttribute.value); + }).toThrowDeveloperError(); + }); + + it('setting per instance attribute throws when value is undefined', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + asynchronous : false + }); + + verifyClassificationPrimitiveRender(primitive, boxColor); + + var attributes = primitive.getGeometryInstanceAttributes('box'); + + expect(function() { + attributes.color = undefined; + }).toThrowDeveloperError(); + }); + + it('can disable picking when asynchronous', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + asynchronous : true, + allowPicking : false + }); + + var frameState = scene.frameState; + + return pollToPromise(function() { + primitive.update(frameState); + if (frameState.afterRender.length > 0) { + frameState.afterRender[0](); + } + return primitive.ready; + }).then(function() { + var attributes = primitive.getGeometryInstanceAttributes('box'); + expect(function() { + attributes.color = undefined; + }).toThrowDeveloperError(); + }); + }); + + it('getGeometryInstanceAttributes throws without id', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + asynchronous : false + }); + + verifyClassificationPrimitiveRender(primitive, boxColor); + + expect(function() { + primitive.getGeometryInstanceAttributes(); + }).toThrowDeveloperError(); + }); + + it('getGeometryInstanceAttributes throws if update was not called', function() { + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + asynchronous : false + }); + + expect(function() { + primitive.getGeometryInstanceAttributes('box'); + }).toThrowDeveloperError(); + }); + + it('getGeometryInstanceAttributes returns undefined if id does not exist', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + asynchronous : false + }); + + verifyClassificationPrimitiveRender(primitive, boxColor); + + expect(primitive.getGeometryInstanceAttributes('unknown')).not.toBeDefined(); + }); + + it('isDestroyed', function() { + primitive = new ClassificationPrimitive(); + expect(primitive.isDestroyed()).toEqual(false); + primitive.destroy(); + expect(primitive.isDestroyed()).toEqual(true); + }); + + it('renders when using asynchronous pipeline', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance + }); + + var frameState = scene.frameState; + + return pollToPromise(function() { + primitive.update(frameState); + if (frameState.afterRender.length > 0) { + frameState.afterRender[0](); + } + return primitive.ready; + }).then(function() { + verifyClassificationPrimitiveRender(primitive, boxColor); + }); + }); + + it('destroy before asynchonous pipeline is complete', function() { + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance + }); + + var frameState = scene.frameState; + primitive.update(frameState); + + primitive.destroy(); + expect(primitive.isDestroyed()).toEqual(true); + }); +}, 'WebGL'); diff --git a/Specs/Scene/Composite3DTileContentSpec.js b/Specs/Scene/Composite3DTileContentSpec.js index 103ad89ac774..6eb79d187840 100644 --- a/Specs/Scene/Composite3DTileContentSpec.js +++ b/Specs/Scene/Composite3DTileContentSpec.js @@ -20,12 +20,13 @@ defineSuite([ var compositeUrl = './Data/Cesium3DTiles/Composite/Composite/'; var compositeOfComposite = './Data/Cesium3DTiles/Composite/CompositeOfComposite/'; + var compositeOfInstanced = './Data/Cesium3DTiles/Composite/CompositeOfInstanced/'; beforeAll(function() { scene = createScene(); // One item in each data set is always located in the center, so point the camera there var center = Cartesian3.fromRadians(centerLongitude, centerLatitude); - scene.camera.lookAt(center, new HeadingPitchRange(0.0, -1.57, 30.0)); + scene.camera.lookAt(center, new HeadingPitchRange(0.0, -1.57, 26.0)); }); afterAll(function() { @@ -121,6 +122,10 @@ defineSuite([ return Cesium3DTilesTester.loadTileset(scene, compositeOfComposite).then(expectRenderComposite); }); + it('renders multiple instanced tilesets', function() { + return Cesium3DTilesTester.loadTileset(scene, compositeOfInstanced).then(expectRenderComposite); + }); + it('destroys', function() { return Cesium3DTilesTester.tileDestroys(scene, compositeUrl); }); diff --git a/Specs/Scene/DebugCameraPrimitiveSpec.js b/Specs/Scene/DebugCameraPrimitiveSpec.js index 9634f622b567..90fe211053bb 100644 --- a/Specs/Scene/DebugCameraPrimitiveSpec.js +++ b/Specs/Scene/DebugCameraPrimitiveSpec.js @@ -16,16 +16,18 @@ defineSuite([ var camera; beforeAll(function() { - scene = createScene(); - - camera = new Camera(scene); - camera.position = new Cartesian3(0.0, 0.0, 0.0); - camera.direction = Cartesian3.negate(Cartesian3.UNIT_X, new Cartesian3()); - camera.up = Cartesian3.clone(Cartesian3.UNIT_Z); + scene = createScene({ + scene3DOnly : true + }); scene.camera.position = new Cartesian3(0.0, 0.0, 0.0); scene.camera.direction = Cartesian3.negate(Cartesian3.UNIT_X, new Cartesian3()); scene.camera.up = Cartesian3.clone(Cartesian3.UNIT_Z); + scene.camera.frustum.near = 1.0; + scene.camera.frustum.far = 500.0; + + camera = Camera.clone(scene.camera); + scene.camera.zoomOut(1.0); }); @@ -85,9 +87,9 @@ defineSuite([ camera : camera })); scene.renderForSpecs(); - var primitive = p._outlinePrimitive; + var primitive = p._outlinePrimitives[0]; scene.renderForSpecs(); - expect(p._outlinePrimitive).not.toBe(primitive); + expect(p._outlinePrimitives[0]).not.toBe(primitive); }); it('does not update when updateOnChange is false', function() { diff --git a/Specs/Scene/GroundPrimitiveSpec.js b/Specs/Scene/GroundPrimitiveSpec.js index 54c22f83c35a..f80c0fcdb7fa 100644 --- a/Specs/Scene/GroundPrimitiveSpec.js +++ b/Specs/Scene/GroundPrimitiveSpec.js @@ -152,6 +152,7 @@ defineSuite([ expect(primitive.allowPicking).toEqual(true); expect(primitive.asynchronous).toEqual(true); expect(primitive.debugShowBoundingVolume).toEqual(false); + expect(primitive.debugShowShadowVolume).toEqual(false); }); it('constructs with options', function() { @@ -166,7 +167,8 @@ defineSuite([ releaseGeometryInstances : false, allowPicking : false, asynchronous : false, - debugShowBoundingVolume : true + debugShowBoundingVolume : true, + debugShowShadowVolume : true }); expect(primitive.geometryInstances).toEqual(geometryInstances); @@ -178,6 +180,7 @@ defineSuite([ expect(primitive.allowPicking).toEqual(false); expect(primitive.asynchronous).toEqual(false); expect(primitive.debugShowBoundingVolume).toEqual(true); + expect(primitive.debugShowShadowVolume).toEqual(true); }); it('releases geometry instances when releaseGeometryInstances is true', function() { diff --git a/Specs/Scene/LabelCollectionSpec.js b/Specs/Scene/LabelCollectionSpec.js index 1b10f96f4c5f..2f999ca1ea9d 100644 --- a/Specs/Scene/LabelCollectionSpec.js +++ b/Specs/Scene/LabelCollectionSpec.js @@ -2210,6 +2210,40 @@ defineSuite([ cartographic = scene.globe.ellipsoid.cartesianToCartographic(l._clampedPosition); expect(cartographic.height).toEqualEpsilon(100.0, CesiumMath.EPSILON9); }); + + it('resets the clamped position when HeightReference.NONE', function() { + scene.globe = createGlobe(); + spyOn(scene.camera, 'update'); + var l = labelsWithHeight.add({ + heightReference : HeightReference.CLAMP_TO_GROUND, + text: 't', + position : Cartesian3.fromDegrees(-72.0, 40.0) + }); + scene.renderForSpecs(); + expect(l._clampedPosition).toBeDefined(); + expect(l._glyphs[0].billboard._clampedPosition).toBeDefined(); + + l.heightReference = HeightReference.NONE; + expect(l._clampedPosition).toBeUndefined(); + expect(l._glyphs[0].billboard._clampedPosition).toBeUndefined(); + }); + + it('clears the billboard height reference callback when the label is removed', function() { + scene.globe = createGlobe(); + spyOn(scene.camera, 'update'); + var l = labelsWithHeight.add({ + heightReference : HeightReference.CLAMP_TO_GROUND, + text: 't', + position : Cartesian3.fromDegrees(-72.0, 40.0) + }); + scene.renderForSpecs(); + var billboard = l._glyphs[0].billboard; + expect(billboard._removeCallbackFunc).toBeDefined(); + var spy = spyOn(billboard, '_removeCallbackFunc'); + labelsWithHeight.remove(l); + expect(spy).toHaveBeenCalled(); + expect(labelsWithHeight._spareBillboards[0]._removeCallbackFunc).not.toBeDefined(); + }); }); }, 'WebGL'); diff --git a/Specs/Scene/ModelSpec.js b/Specs/Scene/ModelSpec.js index 87f7a76c5346..c7dd2dfec270 100644 --- a/Specs/Scene/ModelSpec.js +++ b/Specs/Scene/ModelSpec.js @@ -2521,6 +2521,23 @@ defineSuite([ }); }); + it('removes the callback on destroy', function() { + scene.globe = createMockGlobe(); + return loadModelJson(texturedBoxModel.gltf, { + heightReference : HeightReference.CLAMP_TO_GROUND, + position : Cartesian3.fromDegrees(-72.0, 40.0), + scene : scene, + show : true + }).then(function(model) { + expect(scene.globe.callback).toBeDefined(); + scene.renderForSpecs(); + + primitives.remove(model); + scene.renderForSpecs(); + expect(scene.globe.callback).toBeUndefined(); + }); + }); + it('changing the terrain provider', function() { scene.globe = createMockGlobe(); return loadModelJson(texturedBoxModel.gltf, { @@ -2566,5 +2583,4 @@ defineSuite([ }); }); }); - }, 'WebGL'); diff --git a/Specs/createGlobe.js b/Specs/createGlobe.js index aaa05130dca2..4ad23aec8b51 100644 --- a/Specs/createGlobe.js +++ b/Specs/createGlobe.js @@ -17,6 +17,8 @@ define([ callback : undefined, removedCallback : false, ellipsoid : ellipsoid, + beginFrame: function() {}, + endFrame: function() {}, update : function() {}, getHeight : function() { return 0.0; diff --git a/Specs/karma.conf.js b/Specs/karma.conf.js index 374f114dded8..a697e0767b27 100644 --- a/Specs/karma.conf.js +++ b/Specs/karma.conf.js @@ -6,6 +6,9 @@ module.exports = function(config) { // base path that will be used to resolve all patterns (eg. files, exclude) basePath : '..', + // Disable module load timeout + waitSeconds : 0, + // frameworks to use // available frameworks: https://npmjs.org/browse/keyword/karma-adapter frameworks : ['jasmine', 'requirejs', 'detectBrowsers'], diff --git a/gulpfile.js b/gulpfile.js index eb0a95c34a6a..6be3722bd14b 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -46,8 +46,10 @@ var noDevelopmentGallery = taskName === 'release' || taskName === 'makeZipFile'; var buildingRelease = noDevelopmentGallery; var minifyShaders = taskName === 'minify' || taskName === 'minifyRelease' || taskName === 'release' || taskName === 'makeZipFile' || taskName === 'buildApps'; -//travis reports 32 cores but only has 3GB of memory, which causes the VM to run out. Limit to 8 cores instead. -var concurrency = Math.min(os.cpus().length, 8); +var concurrency = yargs.argv.concurrency; +if (!concurrency) { + concurrency = os.cpus().length; +} //Since combine and minify run in parallel already, split concurrency in half when building both. //This can go away when gulp 4 comes out because it allows for synchronous tasks. @@ -123,6 +125,10 @@ gulp.task('clean', function(done) { gulp.task('requirejs', function(done) { var config = JSON.parse(new Buffer(process.argv[3].substring(2), 'base64').toString('utf8')); + + // Disable module load timeout + config.waitSeconds = 0; + requirejs.optimize(config, function() { done(); }, done);