diff --git a/CHANGES.md b/CHANGES.md index 6c187ed161fc..dc95330bf8e8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,8 +2,11 @@ Change Log ========== ### 1.11 - 2015-07-01 + * Deprecated * The STK World Terrain url `cesiumjs.org/stk-terrain/world` has been deprecated, use `assets.agi.com/stk-terrain/world` instead. A redirect will be in place until 1.14. + * Deprecated `AxisAlignedBoundingBox.intersect` and `BoundingSphere.intersect`. These will be removed in 1.13. Use `.intersectPlane` and, if necessary, `Plane.fromCartesian4`. + * Deprecated the `ObjectOrientedBoundingBox` class. It will be removed in 1.12. Use `OrientedBoundingBox` instead. * Improved the algorithm that `Camera.viewRectangle` uses to select the position of the camera, so that the specified rectangle is now better centered on the screen [#2764](https://github.com/AnalyticalGraphicsInc/cesium/issues/2764). * The performance statistics displayed by setting `scene.debugShowFramesPerSecond` to `true` can now be styled using the `cesium-performanceDisplay` CSS classes in `shared.css` [#2779](https://github.com/AnalyticalGraphicsInc/cesium/issues/2779). * Fixed a crash when `viewer.zoomTo` or `viewer.flyTo` were called immediately before or during a scene morph [#2775](https://github.com/AnalyticalGraphicsInc/cesium/issues/2775). @@ -14,6 +17,17 @@ Change Log * The camera now zooms to the point under the mouse cursor. * Fixed a bug in `ImageryLayer` that could cause an exception and the render loop to stop when the base layer did not cover the entire globe. * Fixed flash/streak rendering artifacts when picking [#2790](https://github.com/AnalyticalGraphicsInc/cesium/issues/2790), [#2811](https://github.com/AnalyticalGraphicsInc/cesium/issues/2811). +* Added `Plane.fromCartesian4` to convert old `Cartesian4` plane representations to the new `Plane` format. +* Added `Plane.ORIGIN_XY_PLANE`/`ORIGIN_YZ_PLANE`/`ORIGIN_ZX_PLANE` constants for commonly-used planes. +* Added `Matrix2`/`Matrix3`/`Matrix4.ZERO` constants for zero matrices. +* Added `Matrix2`/`Matrix3.multiplyByScale` for multiplying against non-uniform scales. +* Added `projectPointToNearestOnPlane` and `projectPointsToNearestOnPlane` to `EllipsoidTangentPlane` to project 3D points to the nearest 2D point on an `EllipsoidTangentPlane`. +* Added `OrientedBoundingBox` class. +* Added `EllipsoidTangentPlane.plane` property to get the `Plane` for the tangent plane. +* Added `EllipsoidTangentPlane.xAxis`/`yAxis`/`zAxis` properties to get the local coordinate system of the tangent plane. +* Add `QuantizedMeshTerrainData` constructor argument `orientedBoundingBox`. +* Add `TerrainMesh.orientedBoundingBox` which holds the `OrientedBoundingBox` for the mesh for a single terrain tile. +* Use `OrientedBoundingBox` when rendering terrain and imagery to improve performance of rendering and loading (by up to 50% of terrain/imagery tiles, depending on camera view). ### 1.10 - 2015-06-01 diff --git a/Source/Core/AxisAlignedBoundingBox.js b/Source/Core/AxisAlignedBoundingBox.js index 072688aaa364..a169484752b5 100644 --- a/Source/Core/AxisAlignedBoundingBox.js +++ b/Source/Core/AxisAlignedBoundingBox.js @@ -3,14 +3,18 @@ define([ './Cartesian3', './defaultValue', './defined', + './deprecationWarning', './DeveloperError', - './Intersect' + './Intersect', + './Plane' ], function( Cartesian3, defaultValue, defined, + deprecationWarning, DeveloperError, - Intersect) { + Intersect, + Plane) { "use strict"; /** @@ -162,15 +166,13 @@ define([ * Determines which side of a plane a box is located. * * @param {AxisAlignedBoundingBox} box The bounding box to test. - * @param {Cartesian4} plane The coefficients of the plane in the form ax + by + cz + d = 0 - * where the coefficients a, b, c, and d are the components x, y, z, and w - * of the {@link Cartesian4}, respectively. + * @param {Plane} plane The plane to test against. * @returns {Intersect} {@link Intersect.INSIDE} if the entire box is on the side of the plane * the normal is pointing, {@link Intersect.OUTSIDE} if the entire box is * on the opposite side, and {@link Intersect.INTERSECTING} if the box * intersects the plane. */ - AxisAlignedBoundingBox.intersect = function(box, plane) { + AxisAlignedBoundingBox.intersectPlane = function(box, plane) { //>>includeStart('debug', pragmas.debug); if (!defined(box)) { throw new DeveloperError('box is required.'); @@ -182,8 +184,9 @@ define([ intersectScratch = Cartesian3.subtract(box.maximum, box.minimum, intersectScratch); var h = Cartesian3.multiplyByScalar(intersectScratch, 0.5, intersectScratch); //The positive half diagonal - var e = h.x * Math.abs(plane.x) + h.y * Math.abs(plane.y) + h.z * Math.abs(plane.z); - var s = Cartesian3.dot(box.center, plane) + plane.w; //signed distance from center + var normal = plane.normal; + var e = h.x * Math.abs(normal.x) + h.y * Math.abs(normal.y) + h.z * Math.abs(normal.z); + var s = Cartesian3.dot(box.center, normal) + plane.distance; //signed distance from center if (s - e > 0) { return Intersect.INSIDE; @@ -197,6 +200,26 @@ define([ return Intersect.INTERSECTING; }; + var scratchPlane = new Plane(new Cartesian3(), 0.0); + /** + * Determines which side of a plane a box is located. + * + * @deprecated + * @param {AxisAlignedBoundingBox} box The bounding box to test. + * @param {Cartesian4} plane The coefficients of the plane in the form ax + by + cz + d = 0 + * where the coefficients a, b, c, and d are the components x, y, z, and w + * of the {@link Cartesian4}, respectively. + * @returns {Intersect} {@link Intersect.INSIDE} if the entire box is on the side of the plane + * the normal is pointing, {@link Intersect.OUTSIDE} if the entire box is + * on the opposite side, and {@link Intersect.INTERSECTING} if the box + * intersects the plane. + */ + AxisAlignedBoundingBox.intersect = function(box, plane) { + deprecationWarning('AxisAlignedBoundingBox.intersect', 'AxisAlignedBoundingBox.intersect() was deprecated in Cesium 1.11. It will be removed in 1.12. Use AxisAlignedBoundingBox.intersectPlane() instead.'); + var p = Plane.fromCartesian4(plane, scratchPlane); + return AxisAlignedBoundingBox.intersectPlane(box, p); + }; + /** * Duplicates this AxisAlignedBoundingBox instance. * @@ -210,6 +233,20 @@ define([ /** * Determines which side of a plane this box is located. * + * @param {Plane} plane The plane to test against. + * @returns {Intersect} {@link Intersect.INSIDE} if the entire box is on the side of the plane + * the normal is pointing, {@link Intersect.OUTSIDE} if the entire box is + * on the opposite side, and {@link Intersect.INTERSECTING} if the box + * intersects the plane. + */ + AxisAlignedBoundingBox.prototype.intersectPlane = function(plane) { + return AxisAlignedBoundingBox.intersectPlane(this, plane); + }; + + /** + * Determines which side of a plane this box is located. + * + * @deprecated * @param {Cartesian4} plane The coefficients of the plane in the form ax + by + cz + d = 0 * where the coefficients a, b, c, and d are the components x, y, z, and w * of the {@link Cartesian4}, respectively. diff --git a/Source/Core/BoundingSphere.js b/Source/Core/BoundingSphere.js index 8a4b8cbcfede..09ca61880268 100644 --- a/Source/Core/BoundingSphere.js +++ b/Source/Core/BoundingSphere.js @@ -4,24 +4,28 @@ define([ './Cartographic', './defaultValue', './defined', + './deprecationWarning', './DeveloperError', './Ellipsoid', './GeographicProjection', './Intersect', './Interval', './Matrix4', + './Plane', './Rectangle' ], function( Cartesian3, Cartographic, defaultValue, defined, + deprecationWarning, DeveloperError, Ellipsoid, GeographicProjection, Intersect, Interval, Matrix4, + Plane, Rectangle) { "use strict"; @@ -742,16 +746,13 @@ define([ * Determines which side of a plane a sphere is located. * * @param {BoundingSphere} sphere The bounding sphere to test. - * @param {Cartesian4} plane The coefficients of the plane in Hessian Normal Form, as `ax + by + cz + d = 0`, - * where (a, b, c) must be a unit normal vector. - * The coefficients a, b, c, and d are the components x, y, z, - * and w of the {@link Cartesian4}, respectively. + * @param {Plane} plane The plane to test against. * @returns {Intersect} {@link Intersect.INSIDE} if the entire sphere is on the side of the plane * the normal is pointing, {@link Intersect.OUTSIDE} if the entire sphere is * on the opposite side, and {@link Intersect.INTERSECTING} if the sphere * intersects the plane. */ - BoundingSphere.intersect = function(sphere, plane) { + BoundingSphere.intersectPlane = function(sphere, plane) { //>>includeStart('debug', pragmas.debug); if (!defined(sphere)) { throw new DeveloperError('sphere is required.'); @@ -764,7 +765,8 @@ define([ var center = sphere.center; var radius = sphere.radius; - var distanceToPlane = Cartesian3.dot(plane, center) + plane.w; + var normal = plane.normal; + var distanceToPlane = Cartesian3.dot(normal, center) + plane.distance; if (distanceToPlane < -radius) { // The center point is negative side of the plane normal @@ -776,6 +778,27 @@ define([ return Intersect.INSIDE; }; + var scratchPlane = new Plane(new Cartesian3(), 0.0); + /** + * Determines which side of a plane a sphere is located. + * + * @deprecated + * @param {BoundingSphere} sphere The bounding sphere to test. + * @param {Cartesian4} plane The coefficients of the plane in Hessian Normal Form, as `ax + by + cz + d = 0`, + * where (a, b, c) must be a unit normal vector. + * The coefficients a, b, c, and d are the components x, y, z, + * and w of the {@link Cartesian4}, respectively. + * @returns {Intersect} {@link Intersect.INSIDE} if the entire sphere is on the side of the plane + * the normal is pointing, {@link Intersect.OUTSIDE} if the entire sphere is + * on the opposite side, and {@link Intersect.INTERSECTING} if the sphere + * intersects the plane. + */ + BoundingSphere.intersect = function(sphere, plane) { + deprecationWarning('BoundingSphere.intersect', 'BoundingSphere.intersect() was deprecated in Cesium 1.11. It will be removed in 1.12. Use BoundingSphere.intersectPlane() instead.'); + var p = Plane.fromCartesian4(plane, scratchPlane); + return BoundingSphere.intersectPlane(sphere, p); + }; + /** * Applies a 4x4 affine transformation matrix to a bounding sphere. * @@ -1042,6 +1065,20 @@ define([ /** * Determines which side of a plane the sphere is located. * + * @param {Plane} plane The plane to test against. + * @returns {Intersect} {@link Intersect.INSIDE} if the entire sphere is on the side of the plane + * the normal is pointing, {@link Intersect.OUTSIDE} if the entire sphere is + * on the opposite side, and {@link Intersect.INTERSECTING} if the sphere + * intersects the plane. + */ + BoundingSphere.prototype.intersectPlane = function(plane) { + return BoundingSphere.intersectPlane(this, plane); + }; + + /** + * Determines which side of a plane the sphere is located. + * + * @deprecated * @param {Cartesian4} plane The coefficients of the plane in the for ax + by + cz + d = 0 * where the coefficients a, b, c, and d are the components x, y, z, * and w of the {@link Cartesian4}, respectively. diff --git a/Source/Core/CesiumTerrainProvider.js b/Source/Core/CesiumTerrainProvider.js index 834a842d0421..0b15c98a3ee4 100644 --- a/Source/Core/CesiumTerrainProvider.js +++ b/Source/Core/CesiumTerrainProvider.js @@ -16,6 +16,9 @@ define([ './IndexDatatype', './loadArrayBuffer', './loadJson', + './Math', + './Matrix3', + './OrientedBoundingBox', './QuantizedMeshTerrainData', './RuntimeError', './TerrainProvider', @@ -38,6 +41,9 @@ define([ IndexDatatype, loadArrayBuffer, loadJson, + CesiumMath, + Matrix3, + OrientedBoundingBox, QuantizedMeshTerrainData, RuntimeError, TerrainProvider, @@ -417,11 +423,26 @@ define([ var skirtHeight = provider.getLevelMaximumGeometricError(level) * 5.0; + var rectangle = provider._tilingScheme.tileXYToRectangle(x, y, level); + var orientedBoundingBox; + if (rectangle.width < CesiumMath.PI_OVER_TWO + CesiumMath.EPSILON5) { + // Here, rectangle.width < pi/2, and rectangle.height < pi + // (though it would still work with rectangle.width up to pi) + + // The skirt is not included in the OBB computation. If this ever + // causes any rendering artifacts (cracks), they are expected to be + // minor and in the corners of the screen. It's possible that this + // might need to be changed - just change to `minimumHeight - skirtHeight` + // A similar change might also be needed in `upsampleQuantizedTerrainMesh.js`. + orientedBoundingBox = OrientedBoundingBox.fromRectangle(rectangle, minimumHeight, maximumHeight, provider._tilingScheme.ellipsoid); + } + return new QuantizedMeshTerrainData({ center : center, minimumHeight : minimumHeight, maximumHeight : maximumHeight, boundingSphere : boundingSphere, + orientedBoundingBox : orientedBoundingBox, horizonOcclusionPoint : horizonOcclusionPoint, quantizedVertices : encodedVertexBuffer, encodedNormals : encodedNormalBuffer, @@ -713,4 +734,4 @@ define([ }; return CesiumTerrainProvider; -}); \ No newline at end of file +}); diff --git a/Source/Core/EllipsoidTangentPlane.js b/Source/Core/EllipsoidTangentPlane.js index d00cdfbde2c4..a57adbbe9c83 100644 --- a/Source/Core/EllipsoidTangentPlane.js +++ b/Source/Core/EllipsoidTangentPlane.js @@ -10,6 +10,7 @@ define([ './DeveloperError', './Ellipsoid', './IntersectionTests', + './Matrix3', './Matrix4', './Plane', './Ray', @@ -25,6 +26,7 @@ define([ DeveloperError, Ellipsoid, IntersectionTests, + Matrix3, Matrix4, Plane, Ray, @@ -91,6 +93,54 @@ define([ get : function() { return this._origin; } + }, + + /** + * Gets the plane which is tangent to the ellipsoid. + * @memberof EllipsoidTangentPlane.prototype + * @readonly + * @type {Plane} + */ + plane : { + get : function() { + return this._plane; + } + }, + + /** + * Gets the local X-axis (east) of the tangent plane. + * @memberof EllipsoidTangentPlane.prototype + * @readonly + * @type {Cartesian3} + */ + xAxis : { + get : function() { + return this._xAxis; + } + }, + + /** + * Gets the local Y-axis (north) of the tangent plane. + * @memberof EllipsoidTangentPlane.prototype + * @readonly + * @type {Cartesian3} + */ + yAxis : { + get : function() { + return this._yAxis; + } + }, + + /** + * Gets the local Z-axis (up) of the tangent plane. + * @member EllipsoidTangentPlane.prototype + * @readonly + * @type {Cartesian3} + */ + zAxis : { + get : function() { + return this._plane.normal; + } } }); @@ -113,15 +163,15 @@ define([ return new EllipsoidTangentPlane(box.center, ellipsoid); }; - var projectPointOntoPlaneRay = new Ray(); - var projectPointOntoPlaneCartesian3 = new Cartesian3(); + var scratchProjectPointOntoPlaneRay = new Ray(); + var scratchProjectPointOntoPlaneCartesian3 = new Cartesian3(); /** - * Computes the projection of the provided 3D position onto the 2D plane. + * Computes the projection of the provided 3D position onto the 2D plane, radially outward from the {@link EllipsoidTangentPlane.ellipsoid} coordinate system origin. * * @param {Cartesian3} cartesian The point to project. * @param {Cartesian2} [result] The object onto which to store the result. - * @returns {Cartesian2} The modified result parameter or a new Cartesian2 instance if none was provided. + * @returns {Cartesian2} The modified result parameter or a new Cartesian2 instance if none was provided. Undefined if there is no intersection point */ EllipsoidTangentPlane.prototype.projectPointOntoPlane = function(cartesian, result) { //>>includeStart('debug', pragmas.debug); @@ -130,14 +180,14 @@ define([ } //>>includeEnd('debug'); - var ray = projectPointOntoPlaneRay; + var ray = scratchProjectPointOntoPlaneRay; ray.origin = cartesian; Cartesian3.normalize(cartesian, ray.direction); - var intersectionPoint = IntersectionTests.rayPlane(ray, this._plane, projectPointOntoPlaneCartesian3); + var intersectionPoint = IntersectionTests.rayPlane(ray, this._plane, scratchProjectPointOntoPlaneCartesian3); if (!defined(intersectionPoint)) { Cartesian3.negate(ray.direction, ray.direction); - intersectionPoint = IntersectionTests.rayPlane(ray, this._plane, projectPointOntoPlaneCartesian3); + intersectionPoint = IntersectionTests.rayPlane(ray, this._plane, scratchProjectPointOntoPlaneCartesian3); } if (defined(intersectionPoint)) { @@ -156,7 +206,10 @@ define([ }; /** - * Computes the projection of the provided 3D positions onto the 2D plane. + * Computes the projection of the provided 3D positions onto the 2D plane (where possible), radially outward from the global origin. + * The resulting array may be shorter than the input array - if a single projection is impossible it will not be included. + * + * @see EllipsoidTangentPlane.projectPointOntoPlane * * @param {Cartesian3[]} cartesians The array of points to project. * @param {Cartesian2[]} [result] The array of Cartesian2 instances onto which to store results. @@ -186,6 +239,70 @@ define([ return result; }; + /** + * Computes the projection of the provided 3D position onto the 2D plane, along the plane normal. + * + * @param {Cartesian3} cartesian The point to project. + * @param {Cartesian2} [result] The object onto which to store the result. + * @returns {Cartesian2} The modified result parameter or a new Cartesian2 instance if none was provided. + */ + EllipsoidTangentPlane.prototype.projectPointToNearestOnPlane = function(cartesian, result) { + //>>includeStart('debug', pragmas.debug); + if (!defined(cartesian)) { + throw new DeveloperError('cartesian is required.'); + } + //>>includeEnd('debug'); + + if (!defined(result)) { + result = new Cartesian2(); + } + + var ray = scratchProjectPointOntoPlaneRay; + ray.origin = cartesian; + Cartesian3.clone(this._plane.normal, ray.direction); + + var intersectionPoint = IntersectionTests.rayPlane(ray, this._plane, scratchProjectPointOntoPlaneCartesian3); + if (!defined(intersectionPoint)) { + Cartesian3.negate(ray.direction, ray.direction); + intersectionPoint = IntersectionTests.rayPlane(ray, this._plane, scratchProjectPointOntoPlaneCartesian3); + } + + var v = Cartesian3.subtract(intersectionPoint, this._origin, intersectionPoint); + var x = Cartesian3.dot(this._xAxis, v); + var y = Cartesian3.dot(this._yAxis, v); + + result.x = x; + result.y = y; + return result; + }; + + /** + * Computes the projection of the provided 3D positions onto the 2D plane, along the plane normal. + * + * @see EllipsoidTangentPlane.projectPointToNearestOnPlane + * + * @param {Cartesian3[]} cartesians The array of points to project. + * @param {Cartesian2[]} [result] The array of Cartesian2 instances onto which to store results. + * @returns {Cartesian2[]} The modified result parameter or a new array of Cartesian2 instances if none was provided. This will have the same length as cartesians. + */ + EllipsoidTangentPlane.prototype.projectPointsToNearestOnPlane = function(cartesians, result) { + //>>includeStart('debug', pragmas.debug); + if (!defined(cartesians)) { + throw new DeveloperError('cartesians is required.'); + } + //>>includeEnd('debug'); + + if (!defined(result)) { + result = []; + } + + var length = cartesians.length; + result.length = length; + for (var i = 0; i < length; i++) { + result[i] = this.projectPointToNearestOnPlane(cartesians[i], result[i]); + } + return result; + }; var projectPointsOntoEllipsoidScratch = new Cartesian3(); /** diff --git a/Source/Core/GeometryPipeline.js b/Source/Core/GeometryPipeline.js index 0121df21091c..7256c4b4c2c1 100644 --- a/Source/Core/GeometryPipeline.js +++ b/Source/Core/GeometryPipeline.js @@ -2402,7 +2402,7 @@ define([ var boundingSphere = geometry.boundingSphere; if (defined(boundingSphere)) { var minX = boundingSphere.center.x - boundingSphere.radius; - if (minX > 0 || BoundingSphere.intersect(boundingSphere, Cartesian4.UNIT_Y) !== Intersect.INTERSECTING) { + if (minX > 0 || BoundingSphere.intersectPlane(boundingSphere, Plane.ORIGIN_ZX_PLANE) !== Intersect.INTERSECTING) { return instance; } } diff --git a/Source/Core/HeightmapTerrainData.js b/Source/Core/HeightmapTerrainData.js index 35255264c8f0..8a6291febad7 100644 --- a/Source/Core/HeightmapTerrainData.js +++ b/Source/Core/HeightmapTerrainData.js @@ -211,7 +211,9 @@ define([ result.minimumHeight, result.maximumHeight, result.boundingSphere3D, - result.occludeePointInScaledSpace); + result.occludeePointInScaledSpace, + 6, + result.orientedBoundingBox); }); }; diff --git a/Source/Core/Matrix2.js b/Source/Core/Matrix2.js index 20c1a1e53a4e..fe2a84d530f2 100644 --- a/Source/Core/Matrix2.js +++ b/Source/Core/Matrix2.js @@ -671,6 +671,41 @@ define([ return result; }; + /** + * Computes the product of a matrix times a (non-uniform) scale, as if the scale were a scale matrix. + * + * @param {Matrix2} matrix The matrix on the left-hand side. + * @param {Cartesian2} scale The non-uniform scale on the right-hand side. + * @param {Matrix2} result The object onto which to store the result. + * @returns {Matrix2} The modified result parameter. + * + * @see Matrix2.fromScale + * @see Matrix2.multiplyByUniformScale + * + * @example + * // Instead of Cesium.Matrix2.multiply(m, Cesium.Matrix2.fromScale(scale), m); + * Cesium.Matrix2.multiplyByScale(m, scale, m); + */ + Matrix2.multiplyByScale = function(matrix, scale, result) { + //>>includeStart('debug', pragmas.debug); + if (!defined(matrix)) { + throw new DeveloperError('matrix is required'); + } + if (!defined(scale)) { + throw new DeveloperError('scale is required'); + } + if (!defined(result)) { + throw new DeveloperError('result is required'); + } + //>>includeEnd('debug'); + + result[0] = matrix[0] * scale.x; + result[1] = matrix[1] * scale.x; + result[2] = matrix[2] * scale.y; + result[3] = matrix[3] * scale.y; + return result; + }; + /** * Creates a negated copy of the provided matrix. * @@ -812,6 +847,15 @@ define([ Matrix2.IDENTITY = freezeObject(new Matrix2(1.0, 0.0, 0.0, 1.0)); + /** + * An immutable Matrix2 instance initialized to the zero matrix. + * + * @type {Matrix2} + * @constant + */ + Matrix2.ZERO = freezeObject(new Matrix2(0.0, 0.0, + 0.0, 0.0)); + /** * The index into Matrix2 for column 0, row 0. * diff --git a/Source/Core/Matrix3.js b/Source/Core/Matrix3.js index 5a4c770f73d8..1449b1eda217 100644 --- a/Source/Core/Matrix3.js +++ b/Source/Core/Matrix3.js @@ -961,6 +961,46 @@ define([ return result; }; + /** + * Computes the product of a matrix times a (non-uniform) scale, as if the scale were a scale matrix. + * + * @param {Matrix3} matrix The matrix on the left-hand side. + * @param {Cartesian3} scale The non-uniform scale on the right-hand side. + * @param {Matrix3} result The object onto which to store the result. + * @returns {Matrix3} The modified result parameter. + * + * @see Matrix3.fromScale + * @see Matrix3.multiplyByUniformScale + * + * @example + * // Instead of Cesium.Matrix3.multiply(m, Cesium.Matrix3.fromScale(scale), m); + * Cesium.Matrix3.multiplyByScale(m, scale, m); + */ + Matrix3.multiplyByScale = function(matrix, scale, result) { + //>>includeStart('debug', pragmas.debug); + if (!defined(matrix)) { + throw new DeveloperError('matrix is required'); + } + if (!defined(scale)) { + throw new DeveloperError('scale is required'); + } + if (!defined(result)) { + throw new DeveloperError('result is required'); + } + //>>includeEnd('debug'); + + result[0] = matrix[0] * scale.x; + result[1] = matrix[1] * scale.x; + result[2] = matrix[2] * scale.x; + result[3] = matrix[3] * scale.y; + result[4] = matrix[4] * scale.y; + result[5] = matrix[5] * scale.y; + result[6] = matrix[6] * scale.z; + result[7] = matrix[7] * scale.z; + result[8] = matrix[8] * scale.z; + return result; + }; + /** * Creates a negated copy of the provided matrix. * @@ -1354,6 +1394,16 @@ define([ 0.0, 1.0, 0.0, 0.0, 0.0, 1.0)); + /** + * An immutable Matrix3 instance initialized to the zero matrix. + * + * @type {Matrix3} + * @constant + */ + Matrix3.ZERO = freezeObject(new Matrix3(0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0)); + /** * The index into Matrix3 for column 0, row 0. * diff --git a/Source/Core/Matrix4.js b/Source/Core/Matrix4.js index 306d91f56061..80bc49d24329 100644 --- a/Source/Core/Matrix4.js +++ b/Source/Core/Matrix4.js @@ -2544,6 +2544,17 @@ define([ 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0)); + /** + * An immutable Matrix4 instance initialized to the zero matrix. + * + * @type {Matrix4} + * @constant + */ + Matrix4.ZERO = freezeObject(new Matrix4(0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0)); + /** * The index into Matrix4 for column 0, row 0. * diff --git a/Source/Core/ObjectOrientedBoundingBox.js b/Source/Core/ObjectOrientedBoundingBox.js index fae0d3605528..bbce966665d6 100644 --- a/Source/Core/ObjectOrientedBoundingBox.js +++ b/Source/Core/ObjectOrientedBoundingBox.js @@ -4,12 +4,14 @@ define([ './defaultValue', './defined', './DeveloperError', + './deprecationWarning', './Matrix3' ], function( Cartesian3, defaultValue, defined, DeveloperError, + deprecationWarning, Matrix3) { "use strict"; @@ -19,6 +21,7 @@ define([ * It is oriented, so it can provide an optimum fit, it can bound more tightly. * @alias ObjectOrientedBoundingBox * @constructor + * @deprecated * * @param {Matrix3} [rotation=Matrix3.IDENTITY] The transformation matrix, to rotate the box to the right position. * @param {Cartesian3} [translation=Cartesian3.ZERO] The position of the box. @@ -38,6 +41,8 @@ define([ * var oobb = new Cesium.ObjectOrientedBoundingBox(rotation, translation, scale); */ var ObjectOrientedBoundingBox = function(rotation, translation, scale) { + deprecationWarning('ObjectOrientedBoundingBox', 'ObjectOrientedBoundingBox was deprecated in Cesium 1.11. It will be removed in 1.12. Use OrientedBoundingBox instead.'); + /** * The transformation matrix, to rotate the box to the right position. * @type {Matrix3} @@ -362,9 +367,9 @@ define([ return (left === right) || ((defined(left)) && (defined(right)) && - Cartesian3.equals(left.transformedPosition, right.transformedPosition) && - Matrix3.equals(left.transformMatrix, right.transformMatrix) && - Cartesian3.equals(left.rectangle, right.rectangle)); + Cartesian3.equals(left.translation, right.translation) && + Matrix3.equals(left.rotation, right.rotation) && + Cartesian3.equals(left.scale, right.scale)); }; /** diff --git a/Source/Core/OrientedBoundingBox.js b/Source/Core/OrientedBoundingBox.js new file mode 100644 index 000000000000..e9bf50db32f6 --- /dev/null +++ b/Source/Core/OrientedBoundingBox.js @@ -0,0 +1,330 @@ +/*global define*/ +define([ + './Cartesian2', + './Cartesian3', + './Cartographic', + './defaultValue', + './defined', + './DeveloperError', + './Ellipsoid', + './EllipsoidTangentPlane', + './Intersect', + './Plane', + './Rectangle', + './Math', + './Matrix3' + ], function( + Cartesian2, + Cartesian3, + Cartographic, + defaultValue, + defined, + DeveloperError, + Ellipsoid, + EllipsoidTangentPlane, + Intersect, + Plane, + Rectangle, + CesiumMath, + Matrix3) { + "use strict"; + + /** + * Creates an instance of an OrientedBoundingBox. + * An OrientedBoundingBox of some object is a closed and convex cuboid. It can provide a tighter bounding volume than {@link BoundingSphere} or {@link AxisAlignedBoundingBox} in many cases. + * @alias OrientedBoundingBox + * @constructor + * + * @param {Cartesian3} [center=Cartesian3.ZERO] The center of the box. + * @param {Matrix3} [halfAxes=Matrix3.ZERO] The three orthogonal half-axes of the bounding box. + * Equivalently, the transformation matrix, to rotate and scale a 2x2x2 + * cube centered at the origin. + * + * @see BoundingSphere + * @see BoundingRectangle + * + * @example + * // Create an OrientedBoundingBox using a transformation matrix, a position where the box will be translated, and a scale. + * var center = new Cesium.Cartesian3(1.0, 0.0, 0.0); + * var halfAxes = Cesium.Matrix3.fromScale(new Cartesian3(1.0, 3.0, 2.0), new Matrix3()); + * + * var obb = new Cesium.OrientedBoundingBox(center, halfAxes); + */ + var OrientedBoundingBox = function(center, halfAxes) { + /** + * The center of the box. + * @type {Cartesian3} + * @default {@link Cartesian3.ZERO} + */ + this.center = Cartesian3.clone(defaultValue(center, Cartesian3.ZERO)); + /** + * The transformation matrix, to rotate the box to the right position. + * @type {Matrix3} + * @default {@link Matrix3.IDENTITY} + */ + this.halfAxes = Matrix3.clone(defaultValue(halfAxes, Matrix3.ZERO)); + }; + + var scratchOffset = new Cartesian3(); + var scratchScale = new Cartesian3(); + /** + * Computes an OrientedBoundingBox given extents in the east-north-up space of the tangent plane. + * + * @param {Number} minimumX Minimum X extent in tangent plane space. + * @param {Number} maximumX Maximum X extent in tangent plane space. + * @param {Number} minimumY Minimum Y extent in tangent plane space. + * @param {Number} maximumY Maximum Y extent in tangent plane space. + * @param {Number} minimumZ Minimum Z extent in tangent plane space. + * @param {Number} maximumZ Maximum Z extent in tangent plane space. + * @param {OrientedBoundingBox} [result] The object onto which to store the result. + * @returns {OrientedBoundingBox} The modified result parameter or a new OrientedBoundingBox instance if one was not provided. + */ + var fromTangentPlaneExtents = function(tangentPlane, minimumX, maximumX, minimumY, maximumY, minimumZ, maximumZ, result) { + //>>includeStart('debug', pragmas.debug); + if (!defined(minimumX) || + !defined(maximumX) || + !defined(minimumY) || + !defined(maximumY) || + !defined(minimumZ) || + !defined(maximumZ)) { + throw new DeveloperError('all extents (minimum/maximum X/Y/Z) are required.'); + } + //>>includeEnd('debug'); + + if (!defined(result)) { + result = new OrientedBoundingBox(); + } + + var halfAxes = result.halfAxes; + Matrix3.setColumn(halfAxes, 0, tangentPlane.xAxis, halfAxes); + Matrix3.setColumn(halfAxes, 1, tangentPlane.yAxis, halfAxes); + Matrix3.setColumn(halfAxes, 2, tangentPlane.zAxis, halfAxes); + + var centerOffset = scratchOffset; + centerOffset.x = (minimumX + maximumX) / 2.0; + centerOffset.y = (minimumY + maximumY) / 2.0; + centerOffset.z = (minimumZ + maximumZ) / 2.0; + + var scale = scratchScale; + scale.x = (maximumX - minimumX) / 2.0; + scale.y = (maximumY - minimumY) / 2.0; + scale.z = (maximumZ - minimumZ) / 2.0; + + var center = result.center; + centerOffset = Matrix3.multiplyByVector(halfAxes, centerOffset, centerOffset); + Cartesian3.add(tangentPlane.origin, centerOffset, center); + Matrix3.multiplyByScale(halfAxes, scale, halfAxes); + + return result; + }; + + var scratchRectangleCenterCartographic = new Cartographic(); + var scratchRectangleCenter = new Cartesian3(); + var perimeterCartographicScratch = [new Cartographic(), new Cartographic(), new Cartographic(), new Cartographic(), new Cartographic(), new Cartographic(), new Cartographic(), new Cartographic()]; + var perimeterCartesianScratch = [new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3()]; + var perimeterProjectedScratch = [new Cartesian2(), new Cartesian2(), new Cartesian2(), new Cartesian2(), new Cartesian2(), new Cartesian2(), new Cartesian2(), new Cartesian2()]; + /** + * Computes an OrientedBoundingBox that bounds a {@link Rectangle} on the surface of an {@link Ellipsoid}. + * There are no guarantees about the orientation of the bounding box. + * + * @param {Rectangle} rectangle The cartographic rectangle on the surface of the ellipsoid. + * @param {Number} [minimumHeight=0.0] The minimum height (elevation) within the tile. + * @param {Number} [maximumHeight=0.0] The maximum height (elevation) within the tile. + * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the rectangle is defined. + * @param {OrientedBoundingBox} [result] The object onto which to store the result. + * @returns {OrientedBoundingBox} The modified result parameter or a new OrientedBoundingBox instance if none was provided. + * + * @exception {DeveloperError} rectangle.width must be between 0 and pi. + * @exception {DeveloperError} rectangle.height must be between 0 and pi. + * @exception {DeveloperError} ellipsoid must be an ellipsoid of revolution (radii.x == radii.y) + */ + OrientedBoundingBox.fromRectangle = function(rectangle, minimumHeight, maximumHeight, ellipsoid, result) { + //>>includeStart('debug', pragmas.debug); + if (!defined(rectangle)) { + throw new DeveloperError('rectangle is required'); + } + if (rectangle.width < 0.0 || rectangle.width > CesiumMath.PI) { + throw new DeveloperError('Rectangle width must be between 0 and pi'); + } + if (rectangle.height < 0.0 || rectangle.height > CesiumMath.PI) { + throw new DeveloperError('Rectangle height must be between 0 and pi'); + } + if (defined(ellipsoid) && !CesiumMath.equalsEpsilon(ellipsoid.radii.x, ellipsoid.radii.y, CesiumMath.EPSILON15)) { + throw new DeveloperError('Ellipsoid must be an ellipsoid of revolution (radii.x == radii.y)'); + } + //>>includeEnd('debug'); + + minimumHeight = defaultValue(minimumHeight, 0.0); + maximumHeight = defaultValue(maximumHeight, 0.0); + ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); + + // The bounding box will be aligned with the tangent plane at the center of the rectangle. + var tangentPointCartographic = Rectangle.center(rectangle, scratchRectangleCenterCartographic); + var tangentPoint = ellipsoid.cartographicToCartesian(tangentPointCartographic, scratchRectangleCenter); + var tangentPlane = new EllipsoidTangentPlane(tangentPoint, ellipsoid); + var plane = tangentPlane.plane; + + // Corner arrangement: + // N/+y + // [0] [1] [2] + // W/-x [7] [3] E/+x + // [6] [5] [4] + // S/-y + // "C" refers to the central lat/long, which by default aligns with the tangent point (above). + // If the rectangle spans the equator, CW and CE are instead aligned with the equator. + var perimeterNW = perimeterCartographicScratch[0]; + var perimeterNC = perimeterCartographicScratch[1]; + var perimeterNE = perimeterCartographicScratch[2]; + var perimeterCE = perimeterCartographicScratch[3]; + var perimeterSE = perimeterCartographicScratch[4]; + var perimeterSC = perimeterCartographicScratch[5]; + var perimeterSW = perimeterCartographicScratch[6]; + var perimeterCW = perimeterCartographicScratch[7]; + + var lonCenter = tangentPointCartographic.longitude; + var latCenter = (rectangle.south < 0.0 && rectangle.north > 0.0) ? 0.0 : tangentPointCartographic.latitude; + perimeterSW.latitude = perimeterSC.latitude = perimeterSE.latitude = rectangle.south; + perimeterCW.latitude = perimeterCE.latitude = latCenter; + perimeterNW.latitude = perimeterNC.latitude = perimeterNE.latitude = rectangle.north; + perimeterSW.longitude = perimeterCW.longitude = perimeterNW.longitude = rectangle.west; + perimeterSC.longitude = perimeterNC.longitude = lonCenter; + perimeterSE.longitude = perimeterCE.longitude = perimeterNE.longitude = rectangle.east; + + // Compute XY extents using the rectangle at maximum height + perimeterNE.height = perimeterNC.height = perimeterNW.height = perimeterCW.height = perimeterSW.height = perimeterSC.height = perimeterSE.height = perimeterCE.height = maximumHeight; + + ellipsoid.cartographicArrayToCartesianArray(perimeterCartographicScratch, perimeterCartesianScratch); + tangentPlane.projectPointsToNearestOnPlane(perimeterCartesianScratch, perimeterProjectedScratch); + // See the `perimeterXX` definitions above for what these are + var minX = Math.min(perimeterProjectedScratch[6].x, perimeterProjectedScratch[7].x, perimeterProjectedScratch[0].x); + var maxX = Math.max(perimeterProjectedScratch[2].x, perimeterProjectedScratch[3].x, perimeterProjectedScratch[4].x); + var minY = Math.min(perimeterProjectedScratch[4].y, perimeterProjectedScratch[5].y, perimeterProjectedScratch[6].y); + var maxY = Math.max(perimeterProjectedScratch[0].y, perimeterProjectedScratch[1].y, perimeterProjectedScratch[2].y); + + // Compute minimum Z using the rectangle at minimum height + perimeterNE.height = perimeterNW.height = perimeterSE.height = perimeterSW.height = minimumHeight; + ellipsoid.cartographicArrayToCartesianArray(perimeterCartographicScratch, perimeterCartesianScratch); + var minZ = Math.min(Plane.getPointDistance(plane, perimeterCartesianScratch[0]), + Plane.getPointDistance(plane, perimeterCartesianScratch[2]), + Plane.getPointDistance(plane, perimeterCartesianScratch[4]), + Plane.getPointDistance(plane, perimeterCartesianScratch[6])); + var maxZ = maximumHeight; // Since the tangent plane touches the surface at height = 0, this is okay + + return fromTangentPlaneExtents(tangentPlane, minX, maxX, minY, maxY, minZ, maxZ, result); + }; + + /** + * Duplicates a OrientedBoundingBox instance. + * + * @param {OrientedBoundingBox} box The bounding box to duplicate. + * @param {OrientedBoundingBox} [result] The object onto which to store the result. + * @returns {OrientedBoundingBox} The modified result parameter or a new OrientedBoundingBox instance if none was provided. (Returns undefined if box is undefined) + */ + OrientedBoundingBox.clone = function(box, result) { + if (!defined(box)) { + return undefined; + } + + if (!defined(result)) { + return new OrientedBoundingBox(box.center, box.halfAxes); + } + + Cartesian3.clone(box.center, result.center); + Matrix3.clone(box.halfAxes, result.halfAxes); + + return result; + }; + + /** + * Determines which side of a plane the oriented bounding box is located. + * + * @param {OrientedBoundingBox} box The oriented bounding box to test. + * @param {Plane} plane The plane to test against. + * @returns {Intersect} {@link Intersect.INSIDE} if the entire box is on the side of the plane + * the normal is pointing, {@link Intersect.OUTSIDE} if the entire box is + * on the opposite side, and {@link Intersect.INTERSECTING} if the box + * intersects the plane. + */ + OrientedBoundingBox.intersectPlane = function(box, plane) { + //>>includeStart('debug', pragmas.debug); + if (!defined(box)) { + throw new DeveloperError('box is required.'); + } + + if (!defined(plane)) { + throw new DeveloperError('plane is required.'); + } + //>>includeEnd('debug'); + + var center = box.center; + var normal = plane.normal; + var halfAxes = box.halfAxes; + var normalX = normal.x, normalY = normal.y, normalZ = normal.z; + // plane is used as if it is its normal; the first three components are assumed to be normalized + var radEffective = Math.abs(normalX * halfAxes[Matrix3.COLUMN0ROW0] + normalY * halfAxes[Matrix3.COLUMN0ROW1] + normalZ * halfAxes[Matrix3.COLUMN0ROW2]) + + Math.abs(normalX * halfAxes[Matrix3.COLUMN1ROW0] + normalY * halfAxes[Matrix3.COLUMN1ROW1] + normalZ * halfAxes[Matrix3.COLUMN1ROW2]) + + Math.abs(normalX * halfAxes[Matrix3.COLUMN2ROW0] + normalY * halfAxes[Matrix3.COLUMN2ROW1] + normalZ * halfAxes[Matrix3.COLUMN2ROW2]); + var distanceToPlane = Cartesian3.dot(normal, center) + plane.distance; + + if (distanceToPlane <= -radEffective) { + // The entire box is on the negative side of the plane normal + return Intersect.OUTSIDE; + } else if (distanceToPlane >= radEffective) { + // The entire box is on the positive side of the plane normal + return Intersect.INSIDE; + } + return Intersect.INTERSECTING; + }; + + /** + * Determines which side of a plane the oriented bounding box is located. + * + * @param {Plane} plane The plane to test against. + * @returns {Intersect} {@link Intersect.INSIDE} if the entire box is on the side of the plane + * the normal is pointing, {@link Intersect.OUTSIDE} if the entire box is + * on the opposite side, and {@link Intersect.INTERSECTING} if the box + * intersects the plane. + */ + OrientedBoundingBox.prototype.intersectPlane = function(plane) { + return OrientedBoundingBox.intersectPlane(this, plane); + }; + + /** + * Compares the provided OrientedBoundingBox componentwise and returns + * true if they are equal, false otherwise. + * + * @param {OrientedBoundingBox} left The first OrientedBoundingBox. + * @param {OrientedBoundingBox} right The second OrientedBoundingBox. + * @returns {Boolean} true if left and right are equal, false otherwise. + */ + OrientedBoundingBox.equals = function(left, right) { + return (left === right) || + ((defined(left)) && + (defined(right)) && + Cartesian3.equals(left.center, right.center) && + Matrix3.equals(left.halfAxes, right.halfAxes)); + }; + + /** + * Duplicates this OrientedBoundingBox instance. + * + * @param {OrientedBoundingBox} [result] The object onto which to store the result. + * @returns {OrientedBoundingBox} The modified result parameter or a new OrientedBoundingBox instance if one was not provided. + */ + OrientedBoundingBox.prototype.clone = function(result) { + return OrientedBoundingBox.clone(this, result); + }; + + /** + * Compares this OrientedBoundingBox against the provided OrientedBoundingBox componentwise and returns + * true if they are equal, false otherwise. + * + * @param {OrientedBoundingBox} [right] The right hand side OrientedBoundingBox. + * @returns {Boolean} true if they are equal, false otherwise. + */ + OrientedBoundingBox.prototype.equals = function(right) { + return OrientedBoundingBox.equals(this, right); + }; + + return OrientedBoundingBox; +}); diff --git a/Source/Core/Plane.js b/Source/Core/Plane.js index 732f88e9a471..979e67ac97eb 100644 --- a/Source/Core/Plane.js +++ b/Source/Core/Plane.js @@ -2,11 +2,13 @@ define([ './Cartesian3', './defined', - './DeveloperError' + './DeveloperError', + './freezeObject' ], function( Cartesian3, defined, - DeveloperError) { + DeveloperError, + freezeObject) { "use strict"; /** @@ -95,6 +97,33 @@ define([ return result; }; + var scratchNormal = new Cartesian3(); + /** + * Creates a plane from the general equation + * + * @param {Cartesian4} coefficients The plane's normal (normalized). + * @param {Plane} [result] The object onto which to store the result. + * @returns {Plane} A new plane instance or the modified result parameter. + */ + Plane.fromCartesian4 = function(coefficients, result) { + //>>includeStart('debug', pragmas.debug); + if (!defined(coefficients)) { + throw new DeveloperError('coefficients is required.'); + } + //>>includeEnd('debug'); + + var normal = Cartesian3.fromCartesian4(coefficients, scratchNormal); + var distance = coefficients.w; + + if (!defined(result)) { + return new Plane(normal, distance); + } else { + Cartesian3.clone(normal, result.normal); + result.distance = distance; + return result; + } + }; + /** * Computes the signed shortest distance of a point to a plane. * The sign of the distance determines which side of the plane the point @@ -119,5 +148,29 @@ define([ return Cartesian3.dot(plane.normal, point) + plane.distance; }; + /** + * A constant initialized to the XY plane passing through the origin, with normal in positive Z. + * + * @type {Plane} + * @constant + */ + Plane.ORIGIN_XY_PLANE = freezeObject(new Plane(Cartesian3.UNIT_Z, 0.0)); + + /** + * A constant initialized to the YZ plane passing through the origin, with normal in positive X. + * + * @type {Plane} + * @constant + */ + Plane.ORIGIN_YZ_PLANE = freezeObject(new Plane(Cartesian3.UNIT_X, 0.0)); + + /** + * A constant initialized to the ZX plane passing through the origin, with normal in positive Y. + * + * @type {Plane} + * @constant + */ + Plane.ORIGIN_ZX_PLANE = freezeObject(new Plane(Cartesian3.UNIT_Y, 0.0)); + return Plane; }); diff --git a/Source/Core/QuantizedMeshTerrainData.js b/Source/Core/QuantizedMeshTerrainData.js index c0501d29cc41..5458bd0baea6 100644 --- a/Source/Core/QuantizedMeshTerrainData.js +++ b/Source/Core/QuantizedMeshTerrainData.js @@ -10,6 +10,7 @@ define([ './IndexDatatype', './Intersections2D', './Math', + './OrientedBoundingBox', './TaskProcessor', './TerrainMesh' ], function( @@ -23,6 +24,7 @@ define([ IndexDatatype, Intersections2D, CesiumMath, + OrientedBoundingBox, TaskProcessor, TerrainMesh) { "use strict"; @@ -44,6 +46,7 @@ define([ * @param {Number} options.minimumHeight The minimum terrain height within the tile, in meters above the ellipsoid. * @param {Number} options.maximumHeight The maximum terrain height within the tile, in meters above the ellipsoid. * @param {BoundingSphere} options.boundingSphere A sphere bounding all of the vertices in the mesh. + * @param {OrientedBoundingBox} [options.orientedBoundingBox] An OrientedBoundingBox bounding all of the vertices in the mesh. * @param {Cartesian3} options.horizonOcclusionPoint The horizon occlusion point of the mesh. If this point * is below the horizon, the entire tile is assumed to be below the horizon as well. * The point is expressed in ellipsoid-scaled coordinates. @@ -88,6 +91,7 @@ define([ * indices : new Uint16Array([0, 3, 1, * 0, 2, 3]), * boundingSphere : new Cesium.BoundingSphere(new Cesium.Cartesian3(1.0, 2.0, 3.0), 10000), + * orientedBoundingBox : new Cesium.OrientedBoundingBox(new Cesium.Cartesian3(1.0, 2.0, 3.0), Matrix3.fromRotationX(Cesium.Math.PI, new Matrix3())), * horizonOcclusionPoint : new Cesium.Cartesian3(3.0, 2.0, 1.0), * westIndices : [0, 1], * southIndices : [0, 1], @@ -154,6 +158,7 @@ define([ this._minimumHeight = options.minimumHeight; this._maximumHeight = options.maximumHeight; this._boundingSphere = options.boundingSphere; + this._orientedBoundingBox = options.orientedBoundingBox; this._horizonOcclusionPoint = options.horizonOcclusionPoint; var vertexCount = this._quantizedVertices.length / 3; @@ -291,7 +296,8 @@ define([ that._maximumHeight, that._boundingSphere, that._horizonOcclusionPoint, - defined(that._encodedNormals) ? 7 : 6); + defined(that._encodedNormals) ? 7 : 6, + that._orientedBoundingBox); }); }; @@ -388,6 +394,7 @@ define([ minimumHeight : result.minimumHeight, maximumHeight : result.maximumHeight, boundingSphere : BoundingSphere.clone(result.boundingSphere), + orientedBoundingBox : OrientedBoundingBox.clone(result.orientedBoundingBox), horizonOcclusionPoint : Cartesian3.clone(result.horizonOcclusionPoint), westIndices : result.westIndices, southIndices : result.southIndices, @@ -504,4 +511,4 @@ define([ }; return QuantizedMeshTerrainData; -}); \ No newline at end of file +}); diff --git a/Source/Core/TerrainMesh.js b/Source/Core/TerrainMesh.js index 3e60cb6a0ece..3432d5537279 100644 --- a/Source/Core/TerrainMesh.js +++ b/Source/Core/TerrainMesh.js @@ -25,8 +25,9 @@ define([ * scaled space, and used for horizon culling. If this point is below the horizon, * the tile is considered to be entirely below the horizon. * @param {Number} [vertexStride=6] The number of components in each vertex. + * @param {OrientedBoundingBox} [orientedBoundingBox] A bounding sphere that completely contains the tile. */ - var TerrainMesh = function TerrainMesh(center, vertices, indices, minimumHeight, maximumHeight, boundingSphere3D, occludeePointInScaledSpace, vertexStride) { + var TerrainMesh = function TerrainMesh(center, vertices, indices, minimumHeight, maximumHeight, boundingSphere3D, occludeePointInScaledSpace, vertexStride, orientedBoundingBox) { /** * The center of the tile. Vertex positions are specified relative to this center. * @type {Cartesian3} @@ -82,6 +83,12 @@ define([ * @type {Cartesian3} */ this.occludeePointInScaledSpace = occludeePointInScaledSpace; + + /** + * A bounding box that completely contains the tile. + * @type {OrientedBoundingBox} + */ + this.orientedBoundingBox = orientedBoundingBox; }; return TerrainMesh; diff --git a/Source/Renderer/DrawCommand.js b/Source/Renderer/DrawCommand.js index d7af2e289009..f8fdfcb972b6 100644 --- a/Source/Renderer/DrawCommand.js +++ b/Source/Renderer/DrawCommand.js @@ -31,6 +31,17 @@ define([ */ this.boundingVolume = options.boundingVolume; + /** + * The oriented bounding box of the geometry in world space. If this is defined, it is used instead of + * {@link DrawCommand#boundingVolume} for plane intersection testing. + * + * @type {OrientedBoundingBox} + * @default undefined + * + * @see DrawCommand#debugShowBoundingVolume + */ + this.orientedBoundingBox = options.orientedBoundingBox; + /** * When true, the renderer frustum and horizon culls the command based on its {@link DrawCommand#boundingVolume}. * If the command was already culled, set this to false for a performance improvement. @@ -186,4 +197,4 @@ define([ }; return DrawCommand; -}); \ No newline at end of file +}); diff --git a/Source/Scene/CullingVolume.js b/Source/Scene/CullingVolume.js index e053d9677cb4..09fc1aa0597e 100644 --- a/Source/Scene/CullingVolume.js +++ b/Source/Scene/CullingVolume.js @@ -2,13 +2,17 @@ define([ '../Core/defaultValue', '../Core/defined', + '../Core/Cartesian3', '../Core/DeveloperError', - '../Core/Intersect' + '../Core/Intersect', + '../Core/Plane' ], function( defaultValue, defined, + Cartesian3, DeveloperError, - Intersect) { + Intersect, + Plane) { "use strict"; /** @@ -30,6 +34,7 @@ define([ this.planes = defaultValue(planes, []); }; + var scratchPlane = new Plane(new Cartesian3(), 0.0); /** * Determines whether a bounding volume intersects the culling volume. * @@ -46,7 +51,7 @@ define([ var planes = this.planes; var intersecting = false; for (var k = 0, len = planes.length; k < len; ++k) { - var result = boundingVolume.intersect(planes[k]); + var result = boundingVolume.intersectPlane(Plane.fromCartesian4(planes[k], scratchPlane)); if (result === Intersect.OUTSIDE) { return Intersect.OUTSIDE; } else if (result === Intersect.INTERSECTING) { diff --git a/Source/Scene/GlobeSurfaceTile.js b/Source/Scene/GlobeSurfaceTile.js index a9a8eb8ebc1d..229a08ffee37 100644 --- a/Source/Scene/GlobeSurfaceTile.js +++ b/Source/Scene/GlobeSurfaceTile.js @@ -124,6 +124,7 @@ define([ this.maximumHeight = 0.0; this.boundingSphere3D = new BoundingSphere(); this.boundingSphere2D = new BoundingSphere(); + this.orientedBoundingBox = undefined; this.occludeePointInScaledSpace = new Cartesian3(); this.loadedTerrain = undefined; diff --git a/Source/Scene/GlobeSurfaceTileProvider.js b/Source/Scene/GlobeSurfaceTileProvider.js index ac40863482bd..12dc7f86570a 100644 --- a/Source/Scene/GlobeSurfaceTileProvider.js +++ b/Source/Scene/GlobeSurfaceTileProvider.js @@ -1,22 +1,28 @@ /*global define*/ define([ '../Core/BoundingSphere', + '../Core/BoxOutlineGeometry', '../Core/Cartesian2', '../Core/Cartesian3', '../Core/Cartesian4', '../Core/Color', + '../Core/ColorGeometryInstanceAttribute', + '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', '../Core/destroyObject', '../Core/DeveloperError', '../Core/Event', '../Core/FeatureDetection', + '../Core/GeometryInstance', '../Core/GeometryPipeline', '../Core/IndexDatatype', '../Core/Intersect', '../Core/Matrix4', + '../Core/OrientedBoundingBox', '../Core/PrimitiveType', '../Core/Rectangle', + '../Core/SphereOutlineGeometry', '../Core/Visibility', '../Core/WebMercatorProjection', '../Renderer/BufferUsage', @@ -24,6 +30,8 @@ define([ '../Scene/BlendingState', '../Scene/DepthFunction', '../Scene/Pass', + '../Scene/PerInstanceColorAppearance', + '../Scene/Primitive', '../ThirdParty/when', './GlobeSurfaceTile', './ImageryLayer', @@ -32,22 +40,28 @@ define([ './SceneMode' ], function( BoundingSphere, + BoxOutlineGeometry, Cartesian2, Cartesian3, Cartesian4, Color, + ColorGeometryInstanceAttribute, + defaultValue, defined, defineProperties, destroyObject, DeveloperError, Event, FeatureDetection, + GeometryInstance, GeometryPipeline, IndexDatatype, Intersect, Matrix4, + OrientedBoundingBox, PrimitiveType, Rectangle, + SphereOutlineGeometry, Visibility, WebMercatorProjection, BufferUsage, @@ -55,6 +69,8 @@ define([ BlendingState, DepthFunction, Pass, + PerInstanceColorAppearance, + Primitive, when, GlobeSurfaceTile, ImageryLayer, @@ -395,7 +411,7 @@ define([ var cullingVolume = frameState.cullingVolume; - var boundingVolume = surfaceTile.boundingSphere3D; + var boundingVolume = defaultValue(surfaceTile.orientedBoundingBox, surfaceTile.boundingSphere3D); if (frameState.mode !== SceneMode.SCENE3D) { boundingVolume = boundingSphereScratch; @@ -794,6 +810,74 @@ define([ return context.createVertexArray(vertexArray._attributes, wireframeIndexBuffer); } + var getDebugOrientedBoundingBox; + var getDebugBoundingSphere; + var debugDestroyPrimitive; + + (function() { + var instanceOBB = new GeometryInstance({ + geometry: BoxOutlineGeometry.fromDimensions({ dimensions: new Cartesian3(2.0, 2.0, 2.0) }) + }); + var instanceSphere = new GeometryInstance({ + geometry: new SphereOutlineGeometry({ radius: 1.0 }) + }); + var modelMatrix = new Matrix4(); + var previousVolume; + var primitive; + + function createDebugPrimitive(instance) { + return new Primitive({ + geometryInstances : instance, + appearance : new PerInstanceColorAppearance({ + translucent : false, + flat : true + }), + asynchronous : false + }); + } + + getDebugOrientedBoundingBox = function(obb, color) { + if (obb === previousVolume) { + return primitive; + } + debugDestroyPrimitive(); + + previousVolume = obb; + modelMatrix = Matrix4.fromRotationTranslation(obb.halfAxes, obb.center, modelMatrix); + + instanceOBB.modelMatrix = modelMatrix; + instanceOBB.attributes.color = ColorGeometryInstanceAttribute.fromColor(color); + + primitive = createDebugPrimitive(instanceOBB); + return primitive; + }; + + getDebugBoundingSphere = function(sphere, color) { + if (sphere === previousVolume) { + return primitive; + } + debugDestroyPrimitive(); + + previousVolume = sphere; + modelMatrix = Matrix4.fromTranslation(sphere.center, modelMatrix); + modelMatrix = Matrix4.multiplyByUniformScale(modelMatrix, sphere.radius, modelMatrix); + + instanceSphere.modelMatrix = modelMatrix; + instanceSphere.attributes.color = ColorGeometryInstanceAttribute.fromColor(color); + + primitive = createDebugPrimitive(instanceSphere); + return primitive; + }; + + debugDestroyPrimitive = function() { + if (defined(primitive)) { + primitive.destroy(); + primitive = undefined; + previousVolume = undefined; + } + }; + })(); + var otherPassesInitialColor = new Cartesian4(0.0, 0.0, 0.0, 0.0); function addDrawCommandsForTile(tileProvider, tile, context, frameState, commandList) { @@ -882,6 +966,10 @@ define([ var initialColor = tileProvider._firstPassInitialColor; + if (!defined(tileProvider._debug.boundingSphereTile)) { + debugDestroyPrimitive(); + } + do { var numberOfDayTextures = 0; @@ -893,6 +981,7 @@ define([ command.owner = tile; command.cull = false; command.boundingVolume = new BoundingSphere(); + command.orientedBoundingBox = undefined; uniformMap = createTileUniformMap(); @@ -907,7 +996,16 @@ define([ ++tileProvider._usedDrawCommands; - command.debugShowBoundingVolume = (tile === tileProvider._debug.boundingSphereTile); + if (tile === tileProvider._debug.boundingSphereTile) { + // If a debug primitive already exists for this tile, it will not be + // re-created, to avoid allocation every frame. If it were possible + // to have more than one selected tile, this would have to change. + if (defined(surfaceTile.orientedBoundingBox)) { + getDebugOrientedBoundingBox(surfaceTile.orientedBoundingBox, Color.RED).update(context, frameState, commandList); + } else if (defined(surfaceTile.boundingSphere3D)) { + getDebugBoundingSphere(surfaceTile.boundingSphere3D, Color.RED).update(context, frameState, commandList); + } + } Cartesian4.clone(initialColor, uniformMap.initialColor); uniformMap.oceanNormalMap = oceanNormalMap; @@ -1002,6 +1100,7 @@ define([ } var boundingVolume = command.boundingVolume; + var orientedBoundingBox = command.orientedBoundingBox; if (frameState.mode !== SceneMode.SCENE3D) { BoundingSphere.fromRectangleWithHeights2D(tile.rectangle, frameState.mapProjection, surfaceTile.minimumHeight, surfaceTile.maximumHeight, boundingVolume); @@ -1011,7 +1110,8 @@ define([ boundingVolume = BoundingSphere.union(surfaceTile.boundingSphere3D, boundingVolume, boundingVolume); } } else { - BoundingSphere.clone(surfaceTile.boundingSphere3D, boundingVolume); + command.boundingVolume = BoundingSphere.clone(surfaceTile.boundingSphere3D, boundingVolume); + command.orientedBoundingBox = OrientedBoundingBox.clone(surfaceTile.orientedBoundingBox, orientedBoundingBox); } commandList.push(command); diff --git a/Source/Scene/PolylineCollection.js b/Source/Scene/PolylineCollection.js index 0142d7ae1bab..25f8cae2bc12 100644 --- a/Source/Scene/PolylineCollection.js +++ b/Source/Scene/PolylineCollection.js @@ -16,6 +16,7 @@ define([ '../Core/Intersect', '../Core/Math', '../Core/Matrix4', + '../Core/Plane', '../Renderer/BufferUsage', '../Renderer/DrawCommand', '../Renderer/ShaderSource', @@ -44,6 +45,7 @@ define([ Intersect, CesiumMath, Matrix4, + Plane, BufferUsage, DrawCommand, ShaderSource, @@ -1046,7 +1048,7 @@ define([ function intersectsIDL(polyline) { return Cartesian3.dot(Cartesian3.UNIT_X, polyline._boundingVolume.center) < 0 || - polyline._boundingVolume.intersect(Cartesian4.UNIT_Y) === Intersect.INTERSECTING; + polyline._boundingVolume.intersectPlane(Plane.ORIGIN_ZX_PLANE) === Intersect.INTERSECTING; } PolylineBucket.prototype.getPolylinePositionsLength = function(polyline) { diff --git a/Source/Scene/TileTerrain.js b/Source/Scene/TileTerrain.js index df4d44027d56..0ba7fce95f97 100644 --- a/Source/Scene/TileTerrain.js +++ b/Source/Scene/TileTerrain.js @@ -6,6 +6,7 @@ define([ '../Core/defined', '../Core/DeveloperError', '../Core/IndexDatatype', + '../Core/OrientedBoundingBox', '../Core/TileProviderError', '../Renderer/BufferUsage', '../ThirdParty/when', @@ -18,6 +19,7 @@ define([ defined, DeveloperError, IndexDatatype, + OrientedBoundingBox, TileProviderError, BufferUsage, when, @@ -78,6 +80,7 @@ define([ surfaceTile.minimumHeight = mesh.minimumHeight; surfaceTile.maximumHeight = mesh.maximumHeight; surfaceTile.boundingSphere3D = BoundingSphere.clone(mesh.boundingSphere3D, surfaceTile.boundingSphere3D); + surfaceTile.orientedBoundingBox = OrientedBoundingBox.clone(mesh.orientedBoundingBox, surfaceTile.orientedBoundingBox); tile.data.occludeePointInScaledSpace = Cartesian3.clone(mesh.occludeePointInScaledSpace, surfaceTile.occludeePointInScaledSpace); diff --git a/Source/Widgets/CesiumInspector/CesiumInspector.js b/Source/Widgets/CesiumInspector/CesiumInspector.js index 0a2c415ecf36..b873546627d6 100644 --- a/Source/Widgets/CesiumInspector/CesiumInspector.js +++ b/Source/Widgets/CesiumInspector/CesiumInspector.js @@ -301,7 +301,7 @@ define([ tbsCheck.type = 'checkbox'; tbsCheck.setAttribute('data-bind', 'checked: tileBoundingSphere, enable: hasPickedTile, click: showTileBoundingSphere'); tileBoundingSphere.appendChild(tbsCheck); - tileBoundingSphere.appendChild(document.createTextNode('Show bounding sphere')); + tileBoundingSphere.appendChild(document.createTextNode('Show bounding volume')); var renderTile = document.createElement('div'); pickTileRequired.appendChild(renderTile); diff --git a/Source/Workers/createVerticesFromHeightmap.js b/Source/Workers/createVerticesFromHeightmap.js index 7fad38e31f49..48ed1d95c9b5 100644 --- a/Source/Workers/createVerticesFromHeightmap.js +++ b/Source/Workers/createVerticesFromHeightmap.js @@ -4,6 +4,8 @@ define([ '../Core/Ellipsoid', '../Core/EllipsoidalOccluder', '../Core/HeightmapTessellator', + '../Core/Math', + '../Core/OrientedBoundingBox', '../Core/Rectangle', './createTaskProcessorWorker' ], function( @@ -11,6 +13,8 @@ define([ Ellipsoid, EllipsoidalOccluder, HeightmapTessellator, + CesiumMath, + OrientedBoundingBox, Rectangle, createTaskProcessorWorker) { "use strict"; @@ -36,6 +40,12 @@ define([ var statistics = HeightmapTessellator.computeVertices(parameters); var boundingSphere3D = BoundingSphere.fromVertices(vertices, parameters.relativeToCenter, numberOfAttributes); + var orientedBoundingBox; + if (parameters.rectangle.width < CesiumMath.PI_OVER_TWO + CesiumMath.EPSILON5) { + // Here, rectangle.width < pi/2, and rectangle.height < pi + // (though it would still work with rectangle.width up to pi) + orientedBoundingBox = OrientedBoundingBox.fromRectangle(parameters.rectangle, statistics.minimumHeight, statistics.maximumHeight, parameters.ellipsoid); + } var ellipsoid = parameters.ellipsoid; var occluder = new EllipsoidalOccluder(ellipsoid); @@ -49,6 +59,7 @@ define([ gridWidth : arrayWidth, gridHeight : arrayHeight, boundingSphere3D : boundingSphere3D, + orientedBoundingBox : orientedBoundingBox, occludeePointInScaledSpace : occludeePointInScaledSpace }; } diff --git a/Source/Workers/upsampleQuantizedTerrainMesh.js b/Source/Workers/upsampleQuantizedTerrainMesh.js index 034d5f9c5a70..7dbcb81441ac 100644 --- a/Source/Workers/upsampleQuantizedTerrainMesh.js +++ b/Source/Workers/upsampleQuantizedTerrainMesh.js @@ -11,6 +11,7 @@ define([ '../Core/IndexDatatype', '../Core/Intersections2D', '../Core/Math', + '../Core/OrientedBoundingBox', './createTaskProcessorWorker' ], function( AttributeCompression, @@ -24,6 +25,7 @@ define([ IndexDatatype, Intersections2D, CesiumMath, + OrientedBoundingBox, createTaskProcessorWorker) { "use strict"; @@ -42,6 +44,7 @@ define([ var normalsScratch = []; var horizonOcclusionPointScratch = new Cartesian3(); var boundingSphereScratch = new BoundingSphere(); + var orientedBoundingBoxScratch = new OrientedBoundingBox(); function upsampleQuantizedTerrainMesh(parameters, transferableObjects) { var isEastChild = parameters.isEastChild; @@ -239,6 +242,7 @@ define([ } var boundingSphere = BoundingSphere.fromVertices(cartesianVertices, Cartesian3.ZERO, 3, boundingSphereScratch); + var orientedBoundingBox = OrientedBoundingBox.fromRectangle(rectangle, minimumHeight, maximumHeight, ellipsoid, orientedBoundingBoxScratch); var occluder = new EllipsoidalOccluder(ellipsoid); var horizonOcclusionPoint = occluder.computeHorizonCullingPointFromVertices(boundingSphere.center, cartesianVertices, 3, boundingSphere.center, horizonOcclusionPointScratch); @@ -285,6 +289,7 @@ define([ eastIndices : eastIndices, northIndices : northIndices, boundingSphere : boundingSphere, + orientedBoundingBox : orientedBoundingBox, horizonOcclusionPoint : horizonOcclusionPoint }; } diff --git a/Specs/Core/AxisAlignedBoundingBoxSpec.js b/Specs/Core/AxisAlignedBoundingBoxSpec.js index acfa831e21c3..f6ac5c5d7446 100644 --- a/Specs/Core/AxisAlignedBoundingBoxSpec.js +++ b/Specs/Core/AxisAlignedBoundingBoxSpec.js @@ -3,12 +3,14 @@ defineSuite([ 'Core/AxisAlignedBoundingBox', 'Core/Cartesian3', 'Core/Cartesian4', - 'Core/Intersect' + 'Core/Intersect', + 'Core/Plane' ], function( AxisAlignedBoundingBox, Cartesian3, Cartesian4, - Intersect) { + Intersect, + Plane) { "use strict"; /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn*/ @@ -115,45 +117,53 @@ defineSuite([ expect(box.center).toEqual(positions[0]); }); - it('intersect works with box on the positive side of a plane', function() { + it('intersectPlane works with box on the positive side of a plane', function() { var box = new AxisAlignedBoundingBox(Cartesian3.negate(Cartesian3.UNIT_X, new Cartesian3()), Cartesian3.ZERO); var normal = Cartesian3.negate(Cartesian3.UNIT_X, new Cartesian3()); var position = Cartesian3.UNIT_X; - var plane = new Cartesian4(normal.x, normal.y, normal.z, -Cartesian3.dot(normal, position)); - expect(box.intersect(plane)).toEqual(Intersect.INSIDE); + var plane = new Plane(normal, -Cartesian3.dot(normal, position)); + expect(box.intersectPlane(plane)).toEqual(Intersect.INSIDE); }); - it('intersect works with box on the negative side of a plane', function() { + it('intersectPlane works with box on the negative side of a plane', function() { var box = new AxisAlignedBoundingBox(Cartesian3.negate(Cartesian3.UNIT_X, new Cartesian3()), Cartesian3.ZERO); var normal = Cartesian3.UNIT_X; var position = Cartesian3.UNIT_X; - var plane = new Cartesian4(normal.x, normal.y, normal.z, -Cartesian3.dot(normal, position)); - expect(box.intersect(plane)).toEqual(Intersect.OUTSIDE); + var plane = new Plane(normal, -Cartesian3.dot(normal, position)); + expect(box.intersectPlane(plane)).toEqual(Intersect.OUTSIDE); }); - it('intersect works with box intersecting a plane', function() { + it('intersectPlane works with box intersecting a plane', function() { var box = new AxisAlignedBoundingBox(Cartesian3.ZERO, Cartesian3.multiplyByScalar(Cartesian3.UNIT_X, 2.0, new Cartesian3())); var normal = Cartesian3.UNIT_X; var position = Cartesian3.UNIT_X; - var plane = new Cartesian4(normal.x, normal.y, normal.z, -Cartesian3.dot(normal, position)); - expect(box.intersect(plane)).toEqual(Intersect.INTERSECTING); + var plane = new Plane(normal, -Cartesian3.dot(normal, position)); + expect(box.intersectPlane(plane)).toEqual(Intersect.INTERSECTING); + }); + + it('intersect works the same as intersectPlane in one case', function() { + var box = new AxisAlignedBoundingBox(Cartesian3.ZERO, Cartesian3.multiplyByScalar(Cartesian3.UNIT_X, 2.0, new Cartesian3())); + var normal = Cartesian3.UNIT_X; + var position = Cartesian3.UNIT_X; + var plane = new Plane(normal, -Cartesian3.dot(normal, position)); + expect(box.intersect(new Cartesian4(1.0, 0.0, 0.0, -1.0))).toEqual(box.intersectPlane(plane)); }); it('clone returns undefined with no parameter', function() { expect(AxisAlignedBoundingBox.clone()).toBeUndefined(); }); - it('intersect throws without a box', function() { - var plane = new Cartesian4(); + it('intersectPlane throws without a box', function() { + var plane = new Plane(Cartesian3.UNIT_X, 0.0); expect(function() { - AxisAlignedBoundingBox.intersect(undefined, plane); + AxisAlignedBoundingBox.intersectPlane(undefined, plane); }).toThrowDeveloperError(); }); - it('intersect throws without a plane', function() { + it('intersectPlane throws without a plane', function() { var box = new AxisAlignedBoundingBox(); expect(function() { - AxisAlignedBoundingBox.intersect(box, undefined); + AxisAlignedBoundingBox.intersectPlane(box, undefined); }).toThrowDeveloperError(); }); }); diff --git a/Specs/Core/BoundingSphereSpec.js b/Specs/Core/BoundingSphereSpec.js index 1ce8e61dd3b4..fb2971ac6068 100644 --- a/Specs/Core/BoundingSphereSpec.js +++ b/Specs/Core/BoundingSphereSpec.js @@ -10,6 +10,7 @@ defineSuite([ 'Core/Interval', 'Core/Math', 'Core/Matrix4', + 'Core/Plane', 'Core/Rectangle', 'Specs/createPackableSpecs' ], function( @@ -23,6 +24,7 @@ defineSuite([ Interval, CesiumMath, Matrix4, + Plane, Rectangle, createPackableSpecs) { "use strict"; @@ -384,28 +386,36 @@ defineSuite([ expect(sphere).toEqual(expected); }); - it('sphere on the positive side of a plane', function() { + it('intersectPlane with sphere on the positive side of a plane', function() { var sphere = new BoundingSphere(Cartesian3.ZERO, 0.5); var normal = Cartesian3.negate(Cartesian3.UNIT_X, new Cartesian3()); var position = Cartesian3.UNIT_X; - var plane = new Cartesian4(normal.x, normal.y, normal.z, -Cartesian3.dot(normal, position)); - expect(sphere.intersect(plane)).toEqual(Intersect.INSIDE); + var plane = new Plane(normal, -Cartesian3.dot(normal, position)); + expect(sphere.intersectPlane(plane)).toEqual(Intersect.INSIDE); }); - it('sphere on the negative side of a plane', function() { + it('intersectPlane with sphere on the negative side of a plane', function() { var sphere = new BoundingSphere(Cartesian3.ZERO, 0.5); var normal = Cartesian3.UNIT_X; var position = Cartesian3.UNIT_X; - var plane = new Cartesian4(normal.x, normal.y, normal.z, -Cartesian3.dot(normal, position)); - expect(sphere.intersect(plane)).toEqual(Intersect.OUTSIDE); + var plane = new Plane(normal, -Cartesian3.dot(normal, position)); + expect(sphere.intersectPlane(plane)).toEqual(Intersect.OUTSIDE); }); - it('sphere intersecting a plane', function() { + it('intersectPlane with sphere intersecting a plane', function() { var sphere = new BoundingSphere(Cartesian3.UNIT_X, 0.5); var normal = Cartesian3.UNIT_X; var position = Cartesian3.UNIT_X; - var plane = new Cartesian4(normal.x, normal.y, normal.z, -Cartesian3.dot(normal, position)); - expect(sphere.intersect(plane)).toEqual(Intersect.INTERSECTING); + var plane = new Plane(normal, -Cartesian3.dot(normal, position)); + expect(sphere.intersectPlane(plane)).toEqual(Intersect.INTERSECTING); + }); + + it('intersect works the same as intersectPlane in one case', function() { + var sphere = new BoundingSphere(Cartesian3.UNIT_X, 0.5); + var normal = Cartesian3.UNIT_X; + var position = Cartesian3.UNIT_X; + var plane = new Plane(normal, -Cartesian3.dot(normal, position)); + expect(sphere.intersect(new Cartesian4(1.0, 0.0, 0.0, -1.0))).toEqual(sphere.intersectPlane(plane)); }); it('expands to contain another sphere', function() { @@ -599,17 +609,17 @@ defineSuite([ }).toThrowDeveloperError(); }); - it('intersect throws without a sphere', function() { - var plane = new Cartesian4(); + it('intersectPlane throws without a sphere', function() { + var plane = new Plane(Cartesian3.UNIT_X, 0.0); expect(function() { - BoundingSphere.intersect(undefined, plane); + BoundingSphere.intersectPlane(undefined, plane); }).toThrowDeveloperError(); }); - it('intersect throws without a plane', function() { + it('intersectPlane throws without a plane', function() { var sphere = new BoundingSphere(); expect(function() { - BoundingSphere.intersect(sphere, undefined); + BoundingSphere.intersectPlane(sphere, undefined); }).toThrowDeveloperError(); }); diff --git a/Specs/Core/EllipsoidTangentPlaneSpec.js b/Specs/Core/EllipsoidTangentPlaneSpec.js index 175dd38fb696..bf88122417c1 100644 --- a/Specs/Core/EllipsoidTangentPlaneSpec.js +++ b/Specs/Core/EllipsoidTangentPlaneSpec.js @@ -3,12 +3,18 @@ defineSuite([ 'Core/EllipsoidTangentPlane', 'Core/Cartesian2', 'Core/Cartesian3', - 'Core/Ellipsoid' + 'Core/Ellipsoid', + 'Core/Math', + 'Core/Matrix3', + 'Core/OrientedBoundingBox' ], function( EllipsoidTangentPlane, Cartesian2, Cartesian3, - Ellipsoid) { + Ellipsoid, + CesiumMath, + Matrix3, + OrientedBoundingBox) { "use strict"; /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn*/ @@ -32,7 +38,7 @@ defineSuite([ expect(tangentPlane.origin).toEqual(Cartesian3.UNIT_X); }); - it('projectPointOntoPlane returns undefined for points not on the plane', function () { + it('projectPointOntoPlane returns undefined for unsolvable projections', function () { var ellipsoid = Ellipsoid.UNIT_SPHERE; var origin = new Cartesian3(1, 0, 0); var tangentPlane = new EllipsoidTangentPlane(origin, ellipsoid); @@ -92,7 +98,7 @@ defineSuite([ expect(returnedResults).toEqual(expectedResults); }); - it('projectPointsOntoPlane works when some points not on plane', function () { + it('projectPointsOntoPlane works when some points cannot be projected', function () { var ellipsoid = Ellipsoid.UNIT_SPHERE; var origin = new Cartesian3(1, 0, 0); var tangentPlane = new EllipsoidTangentPlane(origin, ellipsoid); @@ -129,6 +135,68 @@ defineSuite([ expect(returnedResults).toEqual(expectedResults); }); + it('projectPointToNearestOnPlane works without a result parameter', function () { + var ellipsoid = Ellipsoid.UNIT_SPHERE; + var origin = new Cartesian3(1, 0, 0); + var tangentPlane = new EllipsoidTangentPlane(origin, ellipsoid); + + var positions = new Cartesian3(1, 0, 1); + var expectedResult = new Cartesian2(0, 1); + var returnedResult = tangentPlane.projectPointToNearestOnPlane(positions); + expect(returnedResult).toEqual(expectedResult); + }); + + it('projectPointToNearestOnPlane works projecting from various distances', function () { + var ellipsoid = Ellipsoid.ZERO; + var origin = new Cartesian3(1, 0, 0); + var tangentPlane = new EllipsoidTangentPlane(origin, ellipsoid); + + expect(tangentPlane.projectPointToNearestOnPlane(new Cartesian3(2, 0, 0))).toEqual(new Cartesian2(0, 0)); + expect(tangentPlane.projectPointToNearestOnPlane(new Cartesian3(1, 0, 0))).toEqual(new Cartesian2(0, 0)); + expect(tangentPlane.projectPointToNearestOnPlane(new Cartesian3(0, 0, 0))).toEqual(new Cartesian2(0, 0)); + expect(tangentPlane.projectPointToNearestOnPlane(new Cartesian3(-1, 0, 0))).toEqual(new Cartesian2(0, 0)); + }); + + it('projectPointToNearestOnPlane works with a result parameter', function () { + var ellipsoid = Ellipsoid.UNIT_SPHERE; + var origin = new Cartesian3(1, 0, 0); + var tangentPlane = new EllipsoidTangentPlane(origin, ellipsoid); + + var positions = new Cartesian3(1, 0, 1); + var expectedResult = new Cartesian2(0, 1); + var result = new Cartesian2(); + var returnedResult = tangentPlane.projectPointToNearestOnPlane(positions, result); + expect(result).toBe(returnedResult); + expect(returnedResult).toEqual(expectedResult); + }); + + it('projectPointsToNearestOnPlane works without a result parameter', function () { + var ellipsoid = Ellipsoid.UNIT_SPHERE; + var origin = new Cartesian3(1, 0, 0); + var tangentPlane = new EllipsoidTangentPlane(origin, ellipsoid); + + var positions = [new Cartesian3(1, 0, 1), new Cartesian3(1, 0, 0), new Cartesian3(1, 1, 0)]; + var expectedResults = [new Cartesian2(0, 1), new Cartesian2(0, 0), new Cartesian2(1, 0)]; + var returnedResults = tangentPlane.projectPointsToNearestOnPlane(positions); + expect(returnedResults).toEqual(expectedResults); + }); + + it('projectPointsToNearestOnPlane works with a result parameter', function () { + var ellipsoid = Ellipsoid.UNIT_SPHERE; + var origin = new Cartesian3(1, 0, 0); + var tangentPlane = new EllipsoidTangentPlane(origin, ellipsoid); + + var positions = [new Cartesian3(1, 0, 1), new Cartesian3(1, 0, 0), new Cartesian3(1, 1, 0)]; + var expectedResults = [new Cartesian2(0, 1), new Cartesian2(0, 0), new Cartesian2(1, 0)]; + + var index0 = new Cartesian2(); + var result = [index0]; + var returnedResults = tangentPlane.projectPointsToNearestOnPlane(positions, result); + expect(result).toBe(returnedResults); + expect(result[0]).toBe(index0); + expect(returnedResults).toEqual(expectedResults); + }); + it('constructor throws without origin', function() { expect(function() { return new EllipsoidTangentPlane(undefined, Ellipsoid.WGS84); @@ -161,6 +229,20 @@ defineSuite([ }).toThrowDeveloperError(); }); + it('projectPointToNearestOnPlane throws without cartesian', function() { + var tangentPlane = new EllipsoidTangentPlane(Cartesian3.UNIT_X, Ellipsoid.UNIT_SPHERE); + expect(function() { + return tangentPlane.projectPointToNearestOnPlane(undefined); + }).toThrowDeveloperError(); + }); + + it('projectPointsToNearestOnPlane throws without cartesians', function() { + var tangentPlane = new EllipsoidTangentPlane(Cartesian3.UNIT_X, Ellipsoid.UNIT_SPHERE); + expect(function() { + return tangentPlane.projectPointsToNearestOnPlane(undefined); + }).toThrowDeveloperError(); + }); + it('projectPointsOntoEllipsoid throws without cartesians', function() { var tangentPlane = new EllipsoidTangentPlane(Cartesian3.UNIT_X, Ellipsoid.UNIT_SPHERE); expect(function() { diff --git a/Specs/Core/Matrix2Spec.js b/Specs/Core/Matrix2Spec.js index b4213e59856b..52331f275aeb 100644 --- a/Specs/Core/Matrix2Spec.js +++ b/Specs/Core/Matrix2Spec.js @@ -367,6 +367,25 @@ defineSuite([ expect(left).toEqual(expected); }); + it('multiplyByScale works', function() { + var m = new Matrix2(2, 3, 6, 7); + var scale = new Cartesian2(2.0, 3.0); + var expected = Matrix2.multiply(m, Matrix2.fromScale(scale), new Matrix2()); + var result = new Matrix2(); + var returnedResult = Matrix2.multiplyByScale(m, scale, result); + expect(returnedResult).toBe(result); + expect(result).toEqual(expected); + }); + + it('multiplyByScale works with "this" result parameter', function() { + var m = new Matrix2(1, 2, 5, 6); + var scale = new Cartesian2(1.0, 2.0); + var expected = Matrix2.multiply(m, Matrix2.fromScale(scale), new Matrix2()); + var returnedResult = Matrix2.multiplyByScale(m, scale, m); + expect(returnedResult).toBe(m); + expect(m).toEqual(expected); + }); + it('multiplyByVector works', function() { var left = new Matrix2(1, 2, 3, 4); var right = new Cartesian2(5, 6); @@ -656,6 +675,19 @@ defineSuite([ }).toThrowDeveloperError(); }); + it('multiplyByScale throws with no matrix parameter', function() { + expect(function() { + Matrix2.multiplyByScale(undefined, new Cartesian2()); + }).toThrowDeveloperError(); + }); + + it('multiplyByScale throws with no scale parameter', function() { + var m = new Matrix2(); + expect(function() { + Matrix2.multiplyByScale(m, undefined); + }).toThrowDeveloperError(); + }); + it('multiplyByVector throws with no matrix parameter', function() { var cartesian = new Cartesian2(); expect(function() { @@ -737,6 +769,12 @@ defineSuite([ }).toThrowDeveloperError(); }); + it('multiplyByScale throws without result parameter', function() { + expect(function() { + Matrix2.multiplyByScale(new Matrix2(), new Cartesian2()); + }).toThrowDeveloperError(); + }); + it('multiplyByVector throws without result parameter', function() { expect(function() { Matrix2.multiplyByVector(new Matrix2(), new Cartesian2()); diff --git a/Specs/Core/Matrix3Spec.js b/Specs/Core/Matrix3Spec.js index 17499aa7d289..b70a41bfb410 100644 --- a/Specs/Core/Matrix3Spec.js +++ b/Specs/Core/Matrix3Spec.js @@ -530,6 +530,25 @@ defineSuite([ expect(left).toEqual(expected); }); + it('multiplyByScale works', function() { + var m = new Matrix3(2, 3, 4, 6, 7, 8, 10, 11, 12); + var scale = new Cartesian3(2.0, 3.0, 4.0); + var expected = Matrix3.multiply(m, Matrix3.fromScale(scale), new Matrix3()); + var result = new Matrix3(); + var returnedResult = Matrix3.multiplyByScale(m, scale, result); + expect(returnedResult).toBe(result); + expect(result).toEqual(expected); + }); + + it('multiplyByScale works with "this" result parameter', function() { + var m = new Matrix3(1, 2, 3, 5, 6, 7, 9, 10, 11); + var scale = new Cartesian3(1.0, 2.0, 3.0); + var expected = Matrix3.multiply(m, Matrix3.fromScale(scale), new Matrix3()); + var returnedResult = Matrix3.multiplyByScale(m, scale, m); + expect(returnedResult).toBe(m); + expect(m).toEqual(expected); + }); + it('multiplyByVector works', function() { var left = new Matrix3(1, 2, 3, 4, 5, 6, 7, 8, 9); var right = new Cartesian3(10, 11, 12); @@ -937,6 +956,19 @@ defineSuite([ }).toThrowDeveloperError(); }); + it('multiplyByScale throws with no matrix parameter', function() { + expect(function() { + Matrix3.multiplyByScale(undefined, new Cartesian3()); + }).toThrowDeveloperError(); + }); + + it('multiplyByScale throws with no scale parameter', function() { + var m = new Matrix3(); + expect(function() { + Matrix3.multiplyByScale(m, undefined); + }).toThrowDeveloperError(); + }); + it('multiplyByVector throws with no matrix parameter', function() { var cartesian = new Cartesian3(); expect(function() { @@ -1054,6 +1086,12 @@ defineSuite([ }).toThrowDeveloperError(); }); + it('multiplyByScale throws without result parameter', function() { + expect(function() { + Matrix3.multiplyByScale(new Matrix3(), new Cartesian3()); + }).toThrowDeveloperError(); + }); + it('multiplyByVector throws without result parameter', function() { expect(function() { Matrix3.multiplyByVector(new Matrix3(), new Cartesian3()); diff --git a/Specs/Core/Matrix4Spec.js b/Specs/Core/Matrix4Spec.js index 2b95adf077a1..5dc0e0f0d458 100644 --- a/Specs/Core/Matrix4Spec.js +++ b/Specs/Core/Matrix4Spec.js @@ -662,6 +662,14 @@ defineSuite([ var returnedResult = Matrix4.multiplyByScale(m, scale, result); expect(returnedResult).toBe(result); expect(result).toEqual(expected); + + m = new Matrix4(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 0, 0, 0, 1); + scale = new Cartesian3(2.0, 3.0, 4.0); + expected = Matrix4.multiply(m, Matrix4.fromScale(scale), new Matrix4()); + result = new Matrix4(); + returnedResult = Matrix4.multiplyByScale(m, scale, result); + expect(returnedResult).toBe(result); + expect(result).toEqual(expected); }); it('multiplyByScale works with "this" result parameter', function() { diff --git a/Specs/Core/OrientedBoundingBoxSpec.js b/Specs/Core/OrientedBoundingBoxSpec.js new file mode 100644 index 000000000000..e55d78762cc3 --- /dev/null +++ b/Specs/Core/OrientedBoundingBoxSpec.js @@ -0,0 +1,487 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/OrientedBoundingBox', + 'Core/BoundingRectangle', + 'Core/Cartesian3', + 'Core/Cartesian4', + 'Core/Ellipsoid', + 'Core/EllipsoidTangentPlane', + 'Core/Intersect', + 'Core/Math', + 'Core/Matrix3', + 'Core/Plane', + 'Core/Quaternion', + 'Core/Rectangle' + ], function( + OrientedBoundingBox, + BoundingRectangle, + Cartesian3, + Cartesian4, + Ellipsoid, + EllipsoidTangentPlane, + Intersect, + CesiumMath, + Matrix3, + Plane, + Quaternion, + Rectangle) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn*/ + + var positions = [ + new Cartesian3(2.0, 0.0, 0.0), + new Cartesian3(0.0, 3.0, 0.0), + new Cartesian3(0.0, 0.0, 4.0), + new Cartesian3(-2.0, 0.0, 0.0), + new Cartesian3(0.0, -3.0, 0.0), + new Cartesian3(0.0, 0.0, -4.0) + ]; + + var positions2 = [ + new Cartesian3(4.0, 0.0, 0.0), + new Cartesian3(0.0, 3.0, 0.0), + new Cartesian3(0.0, 0.0, 2.0), + new Cartesian3(-4.0, 0.0, 0.0), + new Cartesian3(0.0, -3.0, 0.0), + new Cartesian3(0.0, 0.0, -2.0) + ]; + + function rotatePositions(positions, axis, angle) { + var points = []; + + var quaternion = Quaternion.fromAxisAngle(axis, angle); + var rotation = Matrix3.fromQuaternion(quaternion); + + for (var i = 0; i < positions.length; ++i) { + points.push(Matrix3.multiplyByVector(rotation, positions[i], new Cartesian3())); + } + + return { + points : points, + rotation : rotation + }; + } + + function translatePositions(positions, translation) { + var points = []; + for (var i = 0; i < positions.length; ++i) { + points.push(Cartesian3.add(translation, positions[i], new Cartesian3())); + } + + return points; + } + + it('constructor sets expected default values', function() { + var box = new OrientedBoundingBox(); + expect(box.center).toEqual(Cartesian3.ZERO); + expect(box.halfAxes).toEqual(Matrix3.ZERO); + }); + + it('fromRectangle sets correct default ellipsoid', function() { + var rectangle = new Rectangle(-0.9, -1.2, 0.5, 0.7); + var box1 = OrientedBoundingBox.fromRectangle(rectangle, 0.0, 0.0); + var box2 = OrientedBoundingBox.fromRectangle(rectangle, 0.0, 0.0, Ellipsoid.WGS84); + + expect(box1.center).toEqualEpsilon(box2.center, CesiumMath.EPSILON15); + + var rotScale = Matrix3.ZERO; + expect(box1.halfAxes).toEqualEpsilon(box2.halfAxes, CesiumMath.EPSILON15); + }); + + it('fromRectangle sets correct default heights', function() { + var rectangle = new Rectangle(0.0, 0.0, 0.0, 0.0); + var box = OrientedBoundingBox.fromRectangle(rectangle, undefined, undefined, Ellipsoid.UNIT_SPHERE); + + expect(box.center).toEqualEpsilon(new Cartesian3(1.0, 0.0, 0.0), CesiumMath.EPSILON15); + + var rotScale = Matrix3.ZERO; + expect(box.halfAxes).toEqualEpsilon(rotScale, CesiumMath.EPSILON15); + }); + + it('fromRectangle throws without rectangle', function() { + var ellipsoid = Ellipsoid.UNIT_SPHERE; + expect(function() { + OrientedBoundingBox.fromRectangle(undefined, 0.0, 0.0, ellipsoid); + }).toThrowDeveloperError(); + }); + + it('fromRectangle throws with invalid rectangles', function() { + var ellipsoid = Ellipsoid.UNIT_SPHERE; + expect(function() { return OrientedBoundingBox.fromRectangle(new Rectangle(1.0, -1.0, -1.0, 1.0), 0.0, 0.0, ellipsoid); }).toThrowDeveloperError(); + expect(function() { return OrientedBoundingBox.fromRectangle(new Rectangle(-1.0, 1.0, 1.0, -1.0), 0.0, 0.0, ellipsoid); }).toThrowDeveloperError(); + expect(function() { return OrientedBoundingBox.fromRectangle(new Rectangle(-1.0, 1.0, -2.0, 2.0), 0.0, 0.0, ellipsoid); }).toThrowDeveloperError(); + expect(function() { return OrientedBoundingBox.fromRectangle(new Rectangle(-2.0, 2.0, -1.0, 1.0), 0.0, 0.0, ellipsoid); }).toThrowDeveloperError(); + expect(function() { return OrientedBoundingBox.fromRectangle(new Rectangle(-2.0, -2.0, 2.0, 1.0), 0.0, 0.0, ellipsoid); }).toThrowDeveloperError(); + expect(function() { return OrientedBoundingBox.fromRectangle(new Rectangle(-2.0, -2.0, 1.0, 2.0), 0.0, 0.0, ellipsoid); }).toThrowDeveloperError(); + expect(function() { return OrientedBoundingBox.fromRectangle(new Rectangle(-1.0, -2.0, 2.0, 2.0), 0.0, 0.0, ellipsoid); }).toThrowDeveloperError(); + expect(function() { return OrientedBoundingBox.fromRectangle(new Rectangle(-2.0, -1.0, 2.0, 2.0), 0.0, 0.0, ellipsoid); }).toThrowDeveloperError(); + }); + + it('fromRectangle throws with non-revolution ellipsoids', function() { + var rectangle = new Rectangle(0.0, 0.0, 0.0, 0.0); + expect(function() { return OrientedBoundingBox.fromRectangle(rectangle, 0.0, 0.0, new Ellipsoid(1.01, 1.00, 1.01)); }).toThrowDeveloperError(); + expect(function() { return OrientedBoundingBox.fromRectangle(rectangle, 0.0, 0.0, new Ellipsoid(1.00, 1.01, 1.01)); }).toThrowDeveloperError(); + }); + + it('fromRectangle creates an OrientedBoundingBox without a result parameter', function() { + var ellipsoid = Ellipsoid.UNIT_SPHERE; + var rectangle = new Rectangle(0.0, 0.0, 0.0, 0.0); + var box = OrientedBoundingBox.fromRectangle(rectangle, 0.0, 0.0, ellipsoid); + + expect(box.center).toEqualEpsilon(new Cartesian3(1.0, 0.0, 0.0), CesiumMath.EPSILON15); + + var rotScale = Matrix3.ZERO; + expect(box.halfAxes).toEqualEpsilon(rotScale, CesiumMath.EPSILON15); + }); + + it('fromRectangle creates an OrientedBoundingBox with a result parameter', function() { + var ellipsoid = Ellipsoid.UNIT_SPHERE; + var rectangle = new Rectangle(0.0, 0.0, 0.0, 0.0); + var result = new OrientedBoundingBox(); + var box = OrientedBoundingBox.fromRectangle(rectangle, 0.0, 0.0, ellipsoid, result); + expect(box).toBe(result); + + expect(box.center).toEqualEpsilon(new Cartesian3(1.0, 0.0, 0.0), CesiumMath.EPSILON15); + + var rotScale = Matrix3.ZERO; + expect(box.halfAxes).toEqualEpsilon(rotScale, CesiumMath.EPSILON15); + }); + + it('fromRectangle for rectangles with heights', function() { + var d90 = CesiumMath.PI_OVER_TWO; + + var box; + + box = OrientedBoundingBox.fromRectangle(new Rectangle(0.0, 0.0, 0.0, 0.0), 1.0, 1.0, Ellipsoid.UNIT_SPHERE); + expect(box.center).toEqualEpsilon(new Cartesian3(2.0, 0.0, 0.0), CesiumMath.EPSILON15); + expect(box.halfAxes).toEqualEpsilon(Matrix3.ZERO, CesiumMath.EPSILON15); + + box = OrientedBoundingBox.fromRectangle(new Rectangle(0.0, 0.0, 0.0, 0.0), -1.0, -1.0, Ellipsoid.UNIT_SPHERE); + expect(box.center).toEqualEpsilon(new Cartesian3(0.0, 0.0, 0.0), CesiumMath.EPSILON15); + expect(box.halfAxes).toEqualEpsilon(Matrix3.ZERO, CesiumMath.EPSILON15); + + box = OrientedBoundingBox.fromRectangle(new Rectangle(0.0, 0.0, 0.0, 0.0), -1.0, 1.0, Ellipsoid.UNIT_SPHERE); + expect(box.center).toEqualEpsilon(new Cartesian3(1.0, 0.0, 0.0), CesiumMath.EPSILON15); + expect(box.halfAxes).toEqualEpsilon(new Matrix3(0, 0, 1, 0, 0, 0, 0, 0, 0), CesiumMath.EPSILON15); + + box = OrientedBoundingBox.fromRectangle(new Rectangle(-d90, -d90, d90, d90), 0.0, 1.0, Ellipsoid.UNIT_SPHERE); + expect(box.center).toEqualEpsilon(new Cartesian3(1.0, 0.0, 0.0), CesiumMath.EPSILON15); + expect(box.halfAxes).toEqualEpsilon(new Matrix3(0, 0, 1, 2, 0, 0, 0, 2, 0), CesiumMath.EPSILON15); + + box = OrientedBoundingBox.fromRectangle(new Rectangle(-d90, -d90, d90, d90), -1.0, -1.0, Ellipsoid.UNIT_SPHERE); + expect(box.center).toEqualEpsilon(new Cartesian3(0.0, 0.0, 0.0), CesiumMath.EPSILON15); + expect(box.halfAxes).toEqualEpsilon(Matrix3.ZERO, CesiumMath.EPSILON15); + + box = OrientedBoundingBox.fromRectangle(new Rectangle(-d90, -d90, d90, d90), -1.0, 0.0, Ellipsoid.UNIT_SPHERE); + expect(box.center).toEqualEpsilon(new Cartesian3(0.5, 0.0, 0.0), CesiumMath.EPSILON15); + expect(box.halfAxes).toEqualEpsilon(new Matrix3(0, 0, 0.5, 1, 0, 0, 0, 1, 0), CesiumMath.EPSILON15); + }); + + it('fromRectangle for interesting, degenerate, and edge-case rectangles', function() { + var d45 = CesiumMath.PI_OVER_FOUR; + var d30 = CesiumMath.PI_OVER_SIX; + var d90 = CesiumMath.PI_OVER_TWO; + var d135 = 3 * CesiumMath.PI_OVER_FOUR; + var d180 = CesiumMath.PI; + var sqrt3 = Math.sqrt(3.0); + + var box; + + box = OrientedBoundingBox.fromRectangle(new Rectangle(0.0, 0.0, 0.0, 0.0), 0.0, 0.0, Ellipsoid.UNIT_SPHERE); + expect(box.center).toEqualEpsilon(new Cartesian3(1.0, 0.0, 0.0), CesiumMath.EPSILON15); + expect(box.halfAxes).toEqualEpsilon(Matrix3.ZERO, CesiumMath.EPSILON15); + + box = OrientedBoundingBox.fromRectangle(new Rectangle(d180, 0.0, -d180, 0.0), 0.0, 0.0, Ellipsoid.UNIT_SPHERE); + expect(box.center).toEqualEpsilon(new Cartesian3(-1.0, 0.0, 0.0), CesiumMath.EPSILON15); + expect(box.halfAxes).toEqualEpsilon(Matrix3.ZERO, CesiumMath.EPSILON15); + + box = OrientedBoundingBox.fromRectangle(new Rectangle(d180, 0.0, d180, 0.0), 0.0, 0.0, Ellipsoid.UNIT_SPHERE); + expect(box.center).toEqualEpsilon(new Cartesian3(-1.0, 0.0, 0.0), CesiumMath.EPSILON15); + expect(box.halfAxes).toEqualEpsilon(Matrix3.ZERO, CesiumMath.EPSILON15); + + box = OrientedBoundingBox.fromRectangle(new Rectangle(0.0, d90, 0.0, d90), 0.0, 0.0, Ellipsoid.UNIT_SPHERE); + expect(box.center).toEqualEpsilon(new Cartesian3(0.0, 0.0, 1.0), CesiumMath.EPSILON15); + expect(box.halfAxes).toEqualEpsilon(Matrix3.ZERO, CesiumMath.EPSILON15); + + box = OrientedBoundingBox.fromRectangle(new Rectangle(0.0, 0.0, d180, 0.0), 0.0, 0.0, Ellipsoid.UNIT_SPHERE); + expect(box.center).toEqualEpsilon(new Cartesian3(0.0, 0.5, 0.0), CesiumMath.EPSILON15); + expect(box.halfAxes).toEqualEpsilon(new Matrix3(-1.0, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0), CesiumMath.EPSILON15); + + box = OrientedBoundingBox.fromRectangle(new Rectangle(-d90, -d90, d90, d90), 0.0, 0.0, Ellipsoid.UNIT_SPHERE); + expect(box.center).toEqualEpsilon(new Cartesian3(0.5, 0.0, 0.0), CesiumMath.EPSILON15); + expect(box.halfAxes).toEqualEpsilon(new Matrix3(0.0, 0.0, 0.5, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0), CesiumMath.EPSILON15); + + box = OrientedBoundingBox.fromRectangle(new Rectangle(-d90, -d30, d90, d90), 0.0, 0.0, Ellipsoid.UNIT_SPHERE); + expect(box.center).toEqualEpsilon(new Cartesian3(0.1875 * sqrt3, 0.0, 0.1875), CesiumMath.EPSILON15); + expect(box.halfAxes).toEqualEpsilon(new Matrix3(0, -sqrt3/4, 5*sqrt3/16, 1, 0, 0, 0, 3/4, 5/16), CesiumMath.EPSILON15); + + box = OrientedBoundingBox.fromRectangle(new Rectangle(-d90, -d90, d90, d30), 0.0, 0.0, Ellipsoid.UNIT_SPHERE); + expect(box.center).toEqualEpsilon(new Cartesian3(0.1875 * sqrt3, 0.0, -0.1875), CesiumMath.EPSILON15); + expect(box.halfAxes).toEqualEpsilon(new Matrix3(0, sqrt3/4, 5*sqrt3/16, 1, 0, 0, 0, 3/4, -5/16), CesiumMath.EPSILON15); + + box = OrientedBoundingBox.fromRectangle(new Rectangle(0.0, -d30, d180, d90), 0.0, 0.0, Ellipsoid.UNIT_SPHERE); + expect(box.center).toEqualEpsilon(new Cartesian3(0.0, 0.1875 * sqrt3, 0.1875), CesiumMath.EPSILON15); + expect(box.halfAxes).toEqualEpsilon(new Matrix3(-1, 0, 0, 0, -sqrt3/4, 5*sqrt3/16, 0, 3/4, 5/16), CesiumMath.EPSILON15); + + box = OrientedBoundingBox.fromRectangle(new Rectangle(0.0, -d90, d180, d30), 0.0, 0.0, Ellipsoid.UNIT_SPHERE); + expect(box.center).toEqualEpsilon(new Cartesian3(0.0, 0.1875 * sqrt3, -0.1875), CesiumMath.EPSILON15); + expect(box.halfAxes).toEqualEpsilon(new Matrix3(-1, 0, 0, 0, sqrt3/4, 5*sqrt3/16, 0, 3/4, -5/16), CesiumMath.EPSILON15); + + box = OrientedBoundingBox.fromRectangle(new Rectangle(-d45, 0.0, d45, 0.0), 0.0, 0.0, Ellipsoid.UNIT_SPHERE); + expect(box.center).toEqualEpsilon(new Cartesian3((1.0 + Math.SQRT1_2) / 2.0, 0.0, 0.0), CesiumMath.EPSILON15); + expect(box.halfAxes).toEqualEpsilon(new Matrix3(0.0, 0.0, 0.5 * (1.0 - Math.SQRT1_2), Math.SQRT1_2, 0.0, 0.0, 0.0, 0.0, 0.0), CesiumMath.EPSILON15); + + box = OrientedBoundingBox.fromRectangle(new Rectangle(d135, 0.0, -d135, 0.0), 0.0, 0.0, Ellipsoid.UNIT_SPHERE); + expect(box.center).toEqualEpsilon(new Cartesian3(-(1.0 + Math.SQRT1_2) / 2.0, 0.0, 0.0), CesiumMath.EPSILON15); + expect(box.halfAxes).toEqualEpsilon(new Matrix3(0.0, 0.0, -0.5 * (1.0 - Math.SQRT1_2), -Math.SQRT1_2, 0.0, 0.0, 0.0, 0.0, 0.0), CesiumMath.EPSILON15); + + box = OrientedBoundingBox.fromRectangle(new Rectangle(0.0, -d45, 0.0, d45), 0.0, 0.0, Ellipsoid.UNIT_SPHERE); + expect(box.center).toEqualEpsilon(new Cartesian3((1.0 + Math.SQRT1_2) / 2.0, 0.0, 0.0), CesiumMath.EPSILON15); + expect(box.halfAxes).toEqualEpsilon(new Matrix3(0.0, 0.0, 0.5 * (1.0 - Math.SQRT1_2), 0.0, 0.0, 0.0, 0.0, Math.SQRT1_2, 0.0), CesiumMath.EPSILON15); + + box = OrientedBoundingBox.fromRectangle(new Rectangle(-d90, 0.0, d90, 0.0), 0.0, 0.0, Ellipsoid.UNIT_SPHERE); + expect(box.center).toEqualEpsilon(new Cartesian3(0.5, 0.0, 0.0), CesiumMath.EPSILON15); + expect(box.halfAxes).toEqualEpsilon(new Matrix3(0.0, 0.0, 0.5, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0), CesiumMath.EPSILON15); + + box = OrientedBoundingBox.fromRectangle(new Rectangle(0.0, -d90, 0.0, d90), 0.0, 0.0, Ellipsoid.UNIT_SPHERE); + expect(box.center).toEqualEpsilon(new Cartesian3(0.5, 0.0, 0.0), CesiumMath.EPSILON15); + expect(box.halfAxes).toEqualEpsilon(new Matrix3(0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0), CesiumMath.EPSILON15); + }); + + var intersectPlaneTestCornersEdgesFaces = function(center, axes) { + var SQRT1_2 = Math.pow(1.0 / 2.0, 1 / 2.0); + var SQRT3_4 = Math.pow(3.0 / 4.0, 1 / 2.0); + + var box = new OrientedBoundingBox(center, Matrix3.multiplyByScalar(axes, 0.5, new Matrix3())); + + var planeNormXform = function(nx, ny, nz, dist) { + var n = new Cartesian3(nx, ny, nz); + var arb = new Cartesian3(357, 924, 258); + var p0 = Cartesian3.normalize(n, new Cartesian3()); + Cartesian3.multiplyByScalar(p0, -dist, p0); + var tang = Cartesian3.cross(n, arb, new Cartesian3()); + Cartesian3.normalize(tang, tang); + var binorm = Cartesian3.cross(n, tang, new Cartesian3()); + Cartesian3.normalize(binorm, binorm); + + Matrix3.multiplyByVector(axes, p0, p0); + Matrix3.multiplyByVector(axes, tang, tang); + Matrix3.multiplyByVector(axes, binorm, binorm); + Cartesian3.cross(tang, binorm, n); + Cartesian3.normalize(n, n); + + Cartesian3.add(p0, center, p0); + var d = -Cartesian3.dot(p0, n); + if (Math.abs(d) > 0.0001 && Cartesian3.magnitudeSquared(n) > 0.0001) { + return new Plane(n, d); + } else { + return undefined; + } + }; + + var pl; + + // Tests against faces + + pl = planeNormXform(+1.0, +0.0, +0.0, 0.50001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INSIDE); } + pl = planeNormXform(-1.0, +0.0, +0.0, 0.50001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INSIDE); } + pl = planeNormXform(+0.0, +1.0, +0.0, 0.50001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INSIDE); } + pl = planeNormXform(+0.0, -1.0, +0.0, 0.50001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INSIDE); } + pl = planeNormXform(+0.0, +0.0, +1.0, 0.50001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INSIDE); } + pl = planeNormXform(+0.0, +0.0, -1.0, 0.50001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INSIDE); } + + pl = planeNormXform(+1.0, +0.0, +0.0, 0.49999); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(-1.0, +0.0, +0.0, 0.49999); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(+0.0, +1.0, +0.0, 0.49999); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(+0.0, -1.0, +0.0, 0.49999); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(+0.0, +0.0, +1.0, 0.49999); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(+0.0, +0.0, -1.0, 0.49999); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + + pl = planeNormXform(+1.0, +0.0, +0.0, -0.49999); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(-1.0, +0.0, +0.0, -0.49999); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(+0.0, +1.0, +0.0, -0.49999); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(+0.0, -1.0, +0.0, -0.49999); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(+0.0, +0.0, +1.0, -0.49999); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(+0.0, +0.0, -1.0, -0.49999); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + + pl = planeNormXform(+1.0, +0.0, +0.0, -0.50001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.OUTSIDE); } + pl = planeNormXform(-1.0, +0.0, +0.0, -0.50001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.OUTSIDE); } + pl = planeNormXform(+0.0, +1.0, +0.0, -0.50001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.OUTSIDE); } + pl = planeNormXform(+0.0, -1.0, +0.0, -0.50001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.OUTSIDE); } + pl = planeNormXform(+0.0, +0.0, +1.0, -0.50001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.OUTSIDE); } + pl = planeNormXform(+0.0, +0.0, -1.0, -0.50001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.OUTSIDE); } + + // Tests against edges + + pl = planeNormXform(+1.0, +1.0, +0.0, SQRT1_2 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INSIDE); } + pl = planeNormXform(+1.0, -1.0, +0.0, SQRT1_2 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INSIDE); } + pl = planeNormXform(-1.0, +1.0, +0.0, SQRT1_2 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INSIDE); } + pl = planeNormXform(-1.0, -1.0, +0.0, SQRT1_2 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INSIDE); } + pl = planeNormXform(+1.0, +0.0, +1.0, SQRT1_2 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INSIDE); } + pl = planeNormXform(+1.0, +0.0, -1.0, SQRT1_2 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INSIDE); } + pl = planeNormXform(-1.0, +0.0, +1.0, SQRT1_2 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INSIDE); } + pl = planeNormXform(-1.0, +0.0, -1.0, SQRT1_2 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INSIDE); } + pl = planeNormXform(+0.0, +1.0, +1.0, SQRT1_2 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INSIDE); } + pl = planeNormXform(+0.0, +1.0, -1.0, SQRT1_2 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INSIDE); } + pl = planeNormXform(+0.0, -1.0, +1.0, SQRT1_2 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INSIDE); } + pl = planeNormXform(+0.0, -1.0, -1.0, SQRT1_2 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INSIDE); } + + pl = planeNormXform(+1.0, +1.0, +0.0, SQRT1_2 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(+1.0, -1.0, +0.0, SQRT1_2 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(-1.0, +1.0, +0.0, SQRT1_2 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(-1.0, -1.0, +0.0, SQRT1_2 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(+1.0, +0.0, +1.0, SQRT1_2 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(+1.0, +0.0, -1.0, SQRT1_2 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(-1.0, +0.0, +1.0, SQRT1_2 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(-1.0, +0.0, -1.0, SQRT1_2 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(+0.0, +1.0, +1.0, SQRT1_2 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(+0.0, +1.0, -1.0, SQRT1_2 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(+0.0, -1.0, +1.0, SQRT1_2 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(+0.0, -1.0, -1.0, SQRT1_2 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + + pl = planeNormXform(+1.0, +1.0, +0.0, -SQRT1_2 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(+1.0, -1.0, +0.0, -SQRT1_2 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(-1.0, +1.0, +0.0, -SQRT1_2 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(-1.0, -1.0, +0.0, -SQRT1_2 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(+1.0, +0.0, +1.0, -SQRT1_2 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(+1.0, +0.0, -1.0, -SQRT1_2 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(-1.0, +0.0, +1.0, -SQRT1_2 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(-1.0, +0.0, -1.0, -SQRT1_2 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(+0.0, +1.0, +1.0, -SQRT1_2 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(+0.0, +1.0, -1.0, -SQRT1_2 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(+0.0, -1.0, +1.0, -SQRT1_2 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(+0.0, -1.0, -1.0, -SQRT1_2 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + + pl = planeNormXform(+1.0, +1.0, +0.0, -SQRT1_2 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.OUTSIDE); } + pl = planeNormXform(+1.0, -1.0, +0.0, -SQRT1_2 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.OUTSIDE); } + pl = planeNormXform(-1.0, +1.0, +0.0, -SQRT1_2 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.OUTSIDE); } + pl = planeNormXform(-1.0, -1.0, +0.0, -SQRT1_2 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.OUTSIDE); } + pl = planeNormXform(+1.0, +0.0, +1.0, -SQRT1_2 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.OUTSIDE); } + pl = planeNormXform(+1.0, +0.0, -1.0, -SQRT1_2 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.OUTSIDE); } + pl = planeNormXform(-1.0, +0.0, +1.0, -SQRT1_2 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.OUTSIDE); } + pl = planeNormXform(-1.0, +0.0, -1.0, -SQRT1_2 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.OUTSIDE); } + pl = planeNormXform(+0.0, +1.0, +1.0, -SQRT1_2 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.OUTSIDE); } + pl = planeNormXform(+0.0, +1.0, -1.0, -SQRT1_2 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.OUTSIDE); } + pl = planeNormXform(+0.0, -1.0, +1.0, -SQRT1_2 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.OUTSIDE); } + pl = planeNormXform(+0.0, -1.0, -1.0, -SQRT1_2 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.OUTSIDE); } + + // Tests against corners + + pl = planeNormXform(+1.0, +1.0, +1.0, SQRT3_4 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INSIDE); } + pl = planeNormXform(+1.0, +1.0, -1.0, SQRT3_4 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INSIDE); } + pl = planeNormXform(+1.0, -1.0, +1.0, SQRT3_4 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INSIDE); } + pl = planeNormXform(+1.0, -1.0, -1.0, SQRT3_4 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INSIDE); } + pl = planeNormXform(-1.0, +1.0, +1.0, SQRT3_4 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INSIDE); } + pl = planeNormXform(-1.0, +1.0, -1.0, SQRT3_4 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INSIDE); } + pl = planeNormXform(-1.0, -1.0, +1.0, SQRT3_4 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INSIDE); } + pl = planeNormXform(-1.0, -1.0, -1.0, SQRT3_4 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INSIDE); } + + pl = planeNormXform(+1.0, +1.0, +1.0, SQRT3_4 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(+1.0, +1.0, -1.0, SQRT3_4 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(+1.0, -1.0, +1.0, SQRT3_4 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(+1.0, -1.0, -1.0, SQRT3_4 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(-1.0, +1.0, +1.0, SQRT3_4 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(-1.0, +1.0, -1.0, SQRT3_4 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(-1.0, -1.0, +1.0, SQRT3_4 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(-1.0, -1.0, -1.0, SQRT3_4 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + + pl = planeNormXform(+1.0, +1.0, +1.0, -SQRT3_4 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(+1.0, +1.0, -1.0, -SQRT3_4 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(+1.0, -1.0, +1.0, -SQRT3_4 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(+1.0, -1.0, -1.0, -SQRT3_4 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(-1.0, +1.0, +1.0, -SQRT3_4 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(-1.0, +1.0, -1.0, -SQRT3_4 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(-1.0, -1.0, +1.0, -SQRT3_4 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + pl = planeNormXform(-1.0, -1.0, -1.0, -SQRT3_4 + 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INTERSECTING); } + + pl = planeNormXform(+1.0, +1.0, +1.0, -SQRT3_4 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.OUTSIDE); } + pl = planeNormXform(+1.0, +1.0, -1.0, -SQRT3_4 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.OUTSIDE); } + pl = planeNormXform(+1.0, -1.0, +1.0, -SQRT3_4 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.OUTSIDE); } + pl = planeNormXform(+1.0, -1.0, -1.0, -SQRT3_4 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.OUTSIDE); } + pl = planeNormXform(-1.0, +1.0, +1.0, -SQRT3_4 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.OUTSIDE); } + pl = planeNormXform(-1.0, +1.0, -1.0, -SQRT3_4 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.OUTSIDE); } + pl = planeNormXform(-1.0, -1.0, +1.0, -SQRT3_4 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.OUTSIDE); } + pl = planeNormXform(-1.0, -1.0, -1.0, -SQRT3_4 - 0.00001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.OUTSIDE); } + + }; + + it('intersectPlane works with untransformed box', function() { + intersectPlaneTestCornersEdgesFaces(Cartesian3.ZERO, Matrix3.IDENTITY); + }); + + it('intersectPlane works with off-center box', function() { + intersectPlaneTestCornersEdgesFaces(new Cartesian3(1.0, 0.0, 0.0), Matrix3.IDENTITY); + intersectPlaneTestCornersEdgesFaces(new Cartesian3(0.7, -1.8, 12.0), Matrix3.IDENTITY); + }); + + it('intersectPlane works with rotated box', function() { + intersectPlaneTestCornersEdgesFaces(Cartesian3.ZERO, + Matrix3.fromQuaternion(Quaternion.fromAxisAngle(new Cartesian3(0.5, 1.5, -1.2), 1.2), new Matrix3())); + }); + + it('intersectPlane works with scaled box', function() { + var m = new Matrix3(); + intersectPlaneTestCornersEdgesFaces(Cartesian3.ZERO, Matrix3.fromScale(new Cartesian3(1.5, 0.4, 20.6), m)); + intersectPlaneTestCornersEdgesFaces(Cartesian3.ZERO, Matrix3.fromScale(new Cartesian3(0.0, 0.4, 20.6), m)); + intersectPlaneTestCornersEdgesFaces(Cartesian3.ZERO, Matrix3.fromScale(new Cartesian3(1.5, 0.0, 20.6), m)); + intersectPlaneTestCornersEdgesFaces(Cartesian3.ZERO, Matrix3.fromScale(new Cartesian3(1.5, 0.4, 0.0), m)); + intersectPlaneTestCornersEdgesFaces(Cartesian3.ZERO, Matrix3.fromScale(new Cartesian3(0.0, 0.0, 0.0), m)); + }); + + it('intersectPlane works with this arbitrary box', function() { + var m = Matrix3.fromScale(new Cartesian3(1.5, 80.4, 2.6), new Matrix3()); + var n = Matrix3.fromQuaternion(Quaternion.fromAxisAngle(new Cartesian3(0.5, 1.5, -1.2), 1.2), new Matrix3()); + Matrix3.multiply(m, n, n); + intersectPlaneTestCornersEdgesFaces(new Cartesian3(-5.1, 0.0, 0.1), n); + }); + + it('intersectPlane fails without box parameter', function() { + var plane = new Cartesian4(1.0, 0.0, 0.0, 0.0); + expect(function() { + OrientedBoundingBox.intersectPlane(undefined, plane); + }).toThrowDeveloperError(); + }); + + it('intersectPlane fails without plane parameter', function() { + var box = new OrientedBoundingBox(Cartesian3.IDENTITY, Matrix3.ZERO); + expect(function() { + OrientedBoundingBox.intersectPlane(box, undefined); + }).toThrowDeveloperError(); + }); + + it('clone without a result parameter', function() { + var box = new OrientedBoundingBox(); + var result = OrientedBoundingBox.clone(box); + expect(box).not.toBe(result); + expect(box).toEqual(result); + expect(box.clone()).toEqual(box); + }); + + it('clone with a result parameter', function() { + var box = new OrientedBoundingBox(); + var box2 = new OrientedBoundingBox(); + var result = new OrientedBoundingBox(); + var returnedResult = OrientedBoundingBox.clone(box, result); + expect(result).toBe(returnedResult); + expect(box).not.toBe(result); + expect(box).toEqual(result); + expect(box.clone(box2)).toBe(box2); + expect(box.clone(box2)).toEqual(box2); + }); + + it('clone undefined OBB with a result parameter', function() { + var box = new OrientedBoundingBox(); + var box2 = new OrientedBoundingBox(); + expect(OrientedBoundingBox.clone(undefined, box)).toBe(undefined); + }); + + it('clone undefined OBB without a result parameter', function() { + expect(OrientedBoundingBox.clone(undefined)).toBe(undefined); + }); + + it('equals works in all cases', function() { + var box = new OrientedBoundingBox(); + expect(box.equals(new OrientedBoundingBox())).toEqual(true); + expect(box.equals(undefined)).toEqual(false); + }); +}); diff --git a/Specs/Core/PlaneSpec.js b/Specs/Core/PlaneSpec.js index 5c92f1ea8f35..1d7d52dedbb6 100644 --- a/Specs/Core/PlaneSpec.js +++ b/Specs/Core/PlaneSpec.js @@ -1,10 +1,12 @@ /*global defineSuite*/ defineSuite([ 'Core/Plane', - 'Core/Cartesian3' + 'Core/Cartesian3', + 'Core/Cartesian4' ], function( Plane, - Cartesian3) { + Cartesian3, + Cartesian4) { "use strict"; /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn*/ @@ -47,6 +49,21 @@ defineSuite([ expect(plane.distance).toEqual(-Cartesian3.dot(normal, point)); }); + it('constructs from a Cartesian4 without result', function() { + var result = Plane.fromCartesian4(Cartesian4.UNIT_X); + + expect(result.normal).toEqual(Cartesian3.UNIT_X); + expect(result.distance).toEqual(0.0); + }); + + it('constructs from a Cartesian4 with result', function() { + var result = new Plane(Cartesian3.UNIT_X, 0.0); + Plane.fromCartesian4(Cartesian4.UNIT_X, result); + + expect(result.normal).toEqual(Cartesian3.UNIT_X); + expect(result.distance).toEqual(0.0); + }); + it('fromPointNormal throws without a point', function() { expect(function() { return Plane.fromPointNormal(undefined, Cartesian3.UNIT_X); @@ -59,6 +76,12 @@ defineSuite([ }).toThrowDeveloperError(); }); + it('fromCartesian4 throws without coefficients', function() { + expect(function() { + return Plane.fromCartesian4(undefined); + }).toThrowDeveloperError(); + }); + it('gets the distance to a point', function() { var plane = new Plane(new Cartesian3(1.0, 2.0, 3.0), 12.34); var point = new Cartesian3(4.0, 5.0, 6.0); @@ -66,10 +89,17 @@ defineSuite([ expect(Plane.getPointDistance(plane, point)).toEqual(Cartesian3.dot(plane.normal, point) + plane.distance); }); + it('getPointDistance throws without a plane', function() { + var point = Cartesian3.ZERO; + expect(function() { + return Plane.getPointDistance(undefined, point); + }).toThrowDeveloperError(); + }); + it('getPointDistance throws without a point', function() { var plane = new Plane(Cartesian3.UNIT_X, 0.0); expect(function() { - return Plane.getPointDistance(); + return Plane.getPointDistance(plane, undefined); }).toThrowDeveloperError(); }); });