From dfebc1265d03821adbd1c0aaa9330c0786c99bce Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Tue, 9 Jun 2015 15:24:30 -0400 Subject: [PATCH 01/28] Replace .intersect(Cartesian4) with .intersectPlane(Plane) This affects bounding volumes AxisAlignedBoundingBox and BoundingSphere. --- CHANGES.md | 2 + Source/Core/AxisAlignedBoundingBox.js | 50 +++++++++++++++++---- Source/Core/BoundingSphere.js | 46 ++++++++++++++++--- Source/Core/GeometryPipeline.js | 2 +- Source/Core/Plane.js | 57 +++++++++++++++++++++++- Source/Scene/CullingVolume.js | 11 +++-- Source/Scene/PolylineCollection.js | 4 +- Specs/Core/AxisAlignedBoundingBoxSpec.js | 42 ++++++++++------- Specs/Core/BoundingSphereSpec.js | 38 ++++++++++------ Specs/Core/PlaneSpec.js | 36 +++++++++++++-- 10 files changed, 234 insertions(+), 54 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 56472ab48b3e..49aa6dd9bd25 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,8 @@ Change Log ### 1.11 - 2015-07-01 +* Deprecated + * Deprecated `AxisAlignedBoundingBox.intersect` and `BoundingSphere.intersect`. These will be removed in 1.13. Use `.intersectPlane` and, if necessary, `Plane.fromCartesian4`. * 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). diff --git a/Source/Core/AxisAlignedBoundingBox.js b/Source/Core/AxisAlignedBoundingBox.js index 072688aaa364..2acf4fb2563e 100644 --- a/Source/Core/AxisAlignedBoundingBox.js +++ b/Source/Core/AxisAlignedBoundingBox.js @@ -4,13 +4,15 @@ define([ './defaultValue', './defined', './DeveloperError', - './Intersect' + './Intersect', + './Plane' ], function( Cartesian3, defaultValue, defined, DeveloperError, - Intersect) { + Intersect, + Plane) { "use strict"; /** @@ -162,15 +164,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 +182,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 +198,25 @@ 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) { + var p = Plane.fromCartesian4(plane, scratchPlane); + return AxisAlignedBoundingBox.intersectPlane(box, p); + }; + /** * Duplicates this AxisAlignedBoundingBox instance. * @@ -210,6 +230,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..bb973e3f6665 100644 --- a/Source/Core/BoundingSphere.js +++ b/Source/Core/BoundingSphere.js @@ -10,6 +10,7 @@ define([ './Intersect', './Interval', './Matrix4', + './Plane', './Rectangle' ], function( Cartesian3, @@ -22,6 +23,7 @@ define([ Intersect, Interval, Matrix4, + Plane, Rectangle) { "use strict"; @@ -742,16 +744,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 +763,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 +776,26 @@ 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) { + var p = Plane.fromCartesian4(plane, scratchPlane); + return BoundingSphere.intersectPlane(sphere, p); + }; + /** * Applies a 4x4 affine transformation matrix to a bounding sphere. * @@ -1042,6 +1062,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/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/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/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/PolylineCollection.js b/Source/Scene/PolylineCollection.js index c06f5d47736d..3103934d92e5 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/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/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(); }); }); From 2e81e951891282428a9e64864444c913c9dc8305 Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Wed, 3 Jun 2015 11:45:07 -0400 Subject: [PATCH 02/28] Fix for ObjectOrientedBoundingBox.equals --- Source/Core/ObjectOrientedBoundingBox.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Core/ObjectOrientedBoundingBox.js b/Source/Core/ObjectOrientedBoundingBox.js index fae0d3605528..d9cc1c736be6 100644 --- a/Source/Core/ObjectOrientedBoundingBox.js +++ b/Source/Core/ObjectOrientedBoundingBox.js @@ -362,9 +362,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)); }; /** From f2800c408f550f7321629ddba73c7bf4b4c3ac22 Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Wed, 3 Jun 2015 11:58:47 -0400 Subject: [PATCH 03/28] Add Matrix2/3/4.ZERO for zero matrices --- CHANGES.md | 1 + Source/Core/Matrix2.js | 9 +++++++++ Source/Core/Matrix3.js | 10 ++++++++++ Source/Core/Matrix4.js | 11 +++++++++++ 4 files changed, 31 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 49aa6dd9bd25..8fcd3ff07609 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,6 +11,7 @@ Change Log * Fixed an issue where `Camera` functions would throw an exception if used from within a `Scene.morphComplete` callback [#2776](https://github.com/AnalyticalGraphicsInc/cesium/issues/2776). * `Model` can now load Binary glTF from a Uint8Array. * Added a new camera mode for horizon views. When the camera is looking at the horizon and a point on terrain above the camera is picked, the camera moves in the plane containing the camera position, up and right vectors. +* Added `Matrix2`/`Matrix3`/`Matrix4.ZERO` constants for zero matrices. ### 1.10 - 2015-06-01 diff --git a/Source/Core/Matrix2.js b/Source/Core/Matrix2.js index 20c1a1e53a4e..6d302d8ea652 100644 --- a/Source/Core/Matrix2.js +++ b/Source/Core/Matrix2.js @@ -812,6 +812,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..ca0a2eba77df 100644 --- a/Source/Core/Matrix3.js +++ b/Source/Core/Matrix3.js @@ -1354,6 +1354,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. * From 4d686eecd4cbf671ccde5319de3e7eec7fd41477 Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Mon, 8 Jun 2015 16:53:26 -0400 Subject: [PATCH 04/28] Add Matrix2/3.multiplyByScale and a Matrix4.multiplyByScale test --- Source/Core/Matrix2.js | 35 ++++++++++++++++++++++++++++++++++ Source/Core/Matrix3.js | 40 +++++++++++++++++++++++++++++++++++++++ Specs/Core/Matrix2Spec.js | 38 +++++++++++++++++++++++++++++++++++++ Specs/Core/Matrix3Spec.js | 38 +++++++++++++++++++++++++++++++++++++ Specs/Core/Matrix4Spec.js | 8 ++++++++ 5 files changed, 159 insertions(+) diff --git a/Source/Core/Matrix2.js b/Source/Core/Matrix2.js index 6d302d8ea652..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. * diff --git a/Source/Core/Matrix3.js b/Source/Core/Matrix3.js index ca0a2eba77df..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. * 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() { From 33a16c273d25e8a1514b30da8be887e4615bdebe Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Wed, 10 Jun 2015 14:51:42 -0400 Subject: [PATCH 05/28] Add EllipsoidTangentPlane.projectPoint(s)ToNearestOnPlane --- Source/Core/EllipsoidTangentPlane.js | 73 ++++++++++++++++++++++++- Specs/Core/EllipsoidTangentPlaneSpec.js | 14 +++++ 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/Source/Core/EllipsoidTangentPlane.js b/Source/Core/EllipsoidTangentPlane.js index d00cdfbde2c4..ab4e53bf2a4c 100644 --- a/Source/Core/EllipsoidTangentPlane.js +++ b/Source/Core/EllipsoidTangentPlane.js @@ -117,7 +117,7 @@ define([ var projectPointOntoPlaneCartesian3 = 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 global origin. * * @param {Cartesian3} cartesian The point to project. * @param {Cartesian2} [result] The object onto which to store the result. @@ -156,7 +156,7 @@ 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, radially outward from the global origin. * * @param {Cartesian3[]} cartesians The array of points to project. * @param {Cartesian2[]} [result] The array of Cartesian2 instances onto which to store results. @@ -186,6 +186,75 @@ 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'); + + var ray = projectPointOntoPlaneRay; + ray.origin = cartesian; + Cartesian3.normalize(this._plane.normal, ray.direction); + + var intersectionPoint = IntersectionTests.rayPlane(ray, this._plane, projectPointOntoPlaneCartesian3); + if (!defined(intersectionPoint)) { + Cartesian3.negate(ray.direction, ray.direction); + intersectionPoint = IntersectionTests.rayPlane(ray, this._plane, projectPointOntoPlaneCartesian3); + } + + if (defined(intersectionPoint)) { + var v = Cartesian3.subtract(intersectionPoint, this._origin, intersectionPoint); + var x = Cartesian3.dot(this._xAxis, v); + var y = Cartesian3.dot(this._yAxis, v); + + if (!defined(result)) { + return new Cartesian2(x, y); + } + result.x = x; + result.y = y; + return result; + } + return undefined; + }; + + /** + * Computes the projection of the provided 3D positions onto the 2D plane, along the plane normal. + * + * @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. + */ + 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 count = 0; + var length = cartesians.length; + for ( var i = 0; i < length; i++) { + var p = this.projectPointToNearestOnPlane(cartesians[i], result[count]); + if (defined(p)) { + result[count] = p; + count++; + } + } + result.length = count; + return result; + }; var projectPointsOntoEllipsoidScratch = new Cartesian3(); /** diff --git a/Specs/Core/EllipsoidTangentPlaneSpec.js b/Specs/Core/EllipsoidTangentPlaneSpec.js index 175dd38fb696..aef9f4175654 100644 --- a/Specs/Core/EllipsoidTangentPlaneSpec.js +++ b/Specs/Core/EllipsoidTangentPlaneSpec.js @@ -161,6 +161,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() { From 0195ffa0a82037cfca331a9f0de4b64826d130fa Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Wed, 3 Jun 2015 11:43:59 -0400 Subject: [PATCH 06/28] Implement/test new OrientedBoundingBox --- Source/Core/OrientedBoundingBox.js | 305 +++++++++++++++++ Specs/Core/OrientedBoundingBoxSpec.js | 457 ++++++++++++++++++++++++++ 2 files changed, 762 insertions(+) create mode 100644 Source/Core/OrientedBoundingBox.js create mode 100644 Specs/Core/OrientedBoundingBoxSpec.js diff --git a/Source/Core/OrientedBoundingBox.js b/Source/Core/OrientedBoundingBox.js new file mode 100644 index 000000000000..808a4b223a5e --- /dev/null +++ b/Source/Core/OrientedBoundingBox.js @@ -0,0 +1,305 @@ +/*global define*/ +define([ + './Cartesian2', + './Cartesian3', + './Cartographic', + './defaultValue', + './defined', + './DeveloperError', + './Ellipsoid', + './Intersect', + './Plane', + './Rectangle', + './Math', + './Matrix3' + ], function( + Cartesian2, + Cartesian3, + Cartographic, + defaultValue, + defined, + DeveloperError, + Ellipsoid, + Intersect, + Plane, + Rectangle, + CesiumMath, + Matrix3) { + "use strict"; + + /** + * Creates an instance of an OrientedBoundingBox. + * An OrientedBoundingBox model of an object or set of objects, is a closed volume (a cuboid), which completely contains the object or the set of objects. + * It is oriented, so it can provide an optimum fit, it can bound more tightly. + * @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 OrientedBoundingBox.fromBoundingRectangle + * @see BoundingSphere + * @see BoundingRectangle + * @see ObjectOrientedBoundingBox + * + * @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); + * var halfAxes = Cesium.Matrix3.clone(Cesium.Matrix3.fromScale(new Cartesian3(1.0, 3.0, 2.0))); + * + * 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 scratchCartesian1 = new Cartesian3(); + var scratchCartesian2 = new Cartesian3(); + var scratchCartesian3 = new Cartesian3(); + + var scratchRotation = new Matrix3(); + /** + * Computes an OrientedBoundingBox from a BoundingRectangle. + * The BoundingRectangle is placed on the XY plane. + * + * @param {BoundingRectangle} boundingRectangle A bounding rectangle. + * @param {Number} [rotation=0.0] The rotation of the bounding box in radians. + * @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. + * + * @example + * // Compute an object oriented bounding box enclosing two points. + * var box = Cesium.OrientedBoundingBox.fromBoundingRectangle(boundingRectangle, 0.0); + */ + OrientedBoundingBox.fromBoundingRectangle = function(boundingRectangle, rotation, result) { + //>>includeStart('debug', pragmas.debug); + if (!defined(boundingRectangle)) { + throw new DeveloperError('boundingRectangle is required'); + } + //>>includeEnd('debug'); + + if (!defined(result)) { + result = new OrientedBoundingBox(); + } + + var rotMat; + if (defined(rotation)) { + rotMat = Matrix3.fromRotationZ(rotation, scratchRotation); + } else { + rotMat = Matrix3.clone(Matrix3.IDENTITY, scratchRotation); + } + + var scale = scratchCartesian1; + scale.x = boundingRectangle.width * 0.5; + scale.y = boundingRectangle.height * 0.5; + scale.z = 0.0; + Matrix3.multiplyByScale(rotMat, scale, result.halfAxes); + + var translation = Matrix3.multiplyByVector(rotMat, scale, result.center); + translation.x += boundingRectangle.x; + translation.y += boundingRectangle.y; + + return result; + }; + + var cornersCartographicScratch = [new Cartographic(), new Cartographic(), new Cartographic(), new Cartographic(), new Cartographic(), new Cartographic()]; + var cornersCartesianScratch = [new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3()]; + var cornersProjectedScratch = [new Cartesian2(), new Cartesian2(), new Cartesian2(), new Cartesian2(), new Cartesian2(), new Cartesian2()]; + /** + * Computes an OrientedBoundingBox from a surface {@link Rectangle} aligned with a specified {@link EllipsoidTangentPlane}. + * + * @param {Rectangle} rectangle The valid rectangle used to create a bounding box. + * @param {EllipsoidTangentPlane} tangentPlane A tangent plane with which the bounding box will be aligned. + * @param {Number} [minHeight=0.0] The minimum height (elevation) within the tile. + * @param {Number} [maxHeight=0.0] The maximum height (elevation) within the tile. + * @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. + */ + OrientedBoundingBox.fromRectangleTangentPlane = function(rectangle, tangentPlane, minHeight, maxHeight, result) { + //>>includeStart('debug', pragmas.debug); + if (!defined(rectangle)) { + throw new DeveloperError('rectangle is required'); + } + if (!defined(tangentPlane)) { + throw new DeveloperError('tangentPlane is required'); + } + (function() { + var rw = rectangle.width; + var rh = rectangle.height; + if (rw < 0.0 || rw > CesiumMath.PI) { + throw new DeveloperError('Rectangle width must be between 0 and pi') + } + if (rh < 0.0 || rh > CesiumMath.PI) { + throw new DeveloperError('Rectangle height must be between 0 and pi') + } + })(); + //>>includeEnd('debug'); + + minHeight = defaultValue(minHeight, 0.0); + maxHeight = defaultValue(maxHeight, 0.0); + + var plane = tangentPlane.plane; + + // Rectangle at maximum height + var cornerNE = cornersCartographicScratch[0]; + var cornerNW = cornersCartographicScratch[1]; + var cornerSE = cornersCartographicScratch[2]; + var cornerSW = cornersCartographicScratch[3]; + var cornerNC = cornersCartographicScratch[4]; + var cornerSC = cornersCartographicScratch[5]; + + var lonWest = rectangle.west; + var lonEast = rectangle.east; + var lonCenter = lonWest + 0.5 * rectangle.width; + cornerSW.latitude = cornerSC.latitude = cornerSE.latitude = rectangle.south; + cornerNW.latitude = cornerNC.latitude = cornerNE.latitude = rectangle.north; + cornerSW.longitude = cornerNW.longitude = lonWest; + cornerSC.longitude = cornerNC.longitude = lonCenter; + cornerSE.longitude = cornerNE.longitude = lonEast; + + cornerNE.height = cornerNW.height = cornerSE.height = cornerSW.height = cornerNC.height = cornerSC.height = maxHeight; + + tangentPlane.ellipsoid.cartographicArrayToCartesianArray(cornersCartographicScratch, cornersCartesianScratch); + tangentPlane.projectPointsToNearestOnPlane(cornersCartesianScratch, cornersProjectedScratch); + var minX = Math.min(cornersProjectedScratch[1].x, cornersProjectedScratch[3].x); + var maxX = Math.max(cornersProjectedScratch[0].x, cornersProjectedScratch[2].x); + var minY = Math.min(cornersProjectedScratch[2].y, cornersProjectedScratch[3].y, cornersProjectedScratch[5].y); + var maxY = Math.max(cornersProjectedScratch[0].y, cornersProjectedScratch[1].y, cornersProjectedScratch[4].y); + + cornerNE.height = cornerNW.height = cornerSE.height = cornerSW.height = minHeight; + tangentPlane.ellipsoid.cartographicArrayToCartesianArray(cornersCartographicScratch, cornersCartesianScratch); + var minZ = Math.min(Plane.getPointDistance(plane, cornersCartesianScratch[0]), + Plane.getPointDistance(plane, cornersCartesianScratch[1]), + Plane.getPointDistance(plane, cornersCartesianScratch[2]), + Plane.getPointDistance(plane, cornersCartesianScratch[3])); + var maxZ = maxHeight; // Since the tangent plane touches the surface at height = 0, this is okay + + return tangentPlane.extentsToOrientedBoundingBox(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; + // plane is used as if it is its normal; the first three components are assumed to be normalized + var radEffective = Math.abs(Cartesian3.dot(normal, Matrix3.getColumn(box.halfAxes, 0, scratchCartesian1))) + + Math.abs(Cartesian3.dot(normal, Matrix3.getColumn(box.halfAxes, 1, scratchCartesian2))) + + Math.abs(Cartesian3.dot(normal, Matrix3.getColumn(box.halfAxes, 2, scratchCartesian3))); + 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/Specs/Core/OrientedBoundingBoxSpec.js b/Specs/Core/OrientedBoundingBoxSpec.js new file mode 100644 index 000000000000..100ffafb61a7 --- /dev/null +++ b/Specs/Core/OrientedBoundingBoxSpec.js @@ -0,0 +1,457 @@ +/*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('fromBoundingRectangle throws without rectangle', function() { + expect(function() { + OrientedBoundingBox.fromBoundingRectangle(); + }).toThrowDeveloperError(); + }); + + it('fromBoundingRectangle returns zero-size OrientedBoundingBox given a zero-size BoundingRectangle', function() { + var box = OrientedBoundingBox.fromBoundingRectangle(new BoundingRectangle()); + expect(box.center).toEqual(Cartesian3.ZERO); + expect(box.halfAxes).toEqual(Matrix3.ZERO); + }); + + it('fromBoundingRectangle creates an OrientedBoundingBox without a result parameter', function() { + var rect = new BoundingRectangle(1.0, 2.0, 3.0, 4.0); + var angle = CesiumMath.PI_OVER_TWO; + var box = OrientedBoundingBox.fromBoundingRectangle(rect, angle); + var rotation = Matrix3.fromRotationZ(angle); + expect(box.center).toEqual(new Cartesian3(-1.0, 3.5, 0.0)); + + var rotScale = new Matrix3(); + Matrix3.multiply(rotation, Matrix3.fromScale(new Cartesian3(1.5, 2.0, 0.0)), rotScale); + expect(box.halfAxes).toEqualEpsilon(rotScale, CesiumMath.EPSILON15); + }); + + it('fromBoundingRectangle creates an OrientedBoundingBox with a result parameter', function() { + var rect = new BoundingRectangle(1.0, 2.0, 3.0, 4.0); + var angle = CesiumMath.PI_OVER_TWO; + var result = new OrientedBoundingBox(); + var box = OrientedBoundingBox.fromBoundingRectangle(rect, angle, result); + expect(box).toBe(result); + var rotation = Matrix3.fromRotationZ(angle); + + expect(box.center).toEqual(new Cartesian3(-1.0, 3.5, 0.0)); + + var rotScale = new Matrix3(); + Matrix3.multiply(rotation, Matrix3.fromScale(new Cartesian3(1.5, 2.0, 0.0)), rotScale); + expect(box.halfAxes).toEqualEpsilon(rotScale, CesiumMath.EPSILON15); + }); + + it('fromRectangleTangentPlane throws without rectangle', function() { + var pl = new EllipsoidTangentPlane(Cartesian3.UNIT_X, Ellipsoid.UNIT_SPHERE); + expect(function() { + return OrientedBoundingBox.fromRectangleTangentPlane(undefined, pl, 0.0, 0.0); + }).toThrowDeveloperError(); + }); + + it('fromRectangleTangentPlane throws without tangentPlane', function() { + var rect = new BoundingRectangle(1.0, 2.0, 3.0, 4.0); + expect(function() { + return OrientedBoundingBox.fromRectangleTangentPlane(rect, undefined, 0.0, 0.0); + }).toThrowDeveloperError(); + }); + + it('fromRectangleTangentPlane creates an OrientedBoundingBox without a result parameter', function() { + var rect = new Rectangle(0.0, 0.0, 0.0, 0.0); + var pl = new EllipsoidTangentPlane(Cartesian3.UNIT_X, Ellipsoid.UNIT_SPHERE); + var box = OrientedBoundingBox.fromRectangleTangentPlane(rect, pl, 0.0, 0.0); + + 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('fromRectangleTangentPlane creates an OrientedBoundingBox with a result parameter', function() { + var rect = new Rectangle(0.0, 0.0, 0.0, 0.0); + var pl = new EllipsoidTangentPlane(Cartesian3.UNIT_X, Ellipsoid.UNIT_SPHERE); + var result = new OrientedBoundingBox(); + var box = OrientedBoundingBox.fromRectangleTangentPlane(rect, pl, 0.0, 0.0, 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('fromRectangleTangentPlane from a degenerate rectangle from (-45, 0) to (45, 0)', function() { + var d45 = CesiumMath.PI_OVER_FOUR; + var rect = new Rectangle(-d45, 0.0, d45, 0.0); + var pl = new EllipsoidTangentPlane(Cartesian3.UNIT_X, Ellipsoid.UNIT_SPHERE); + var result = new OrientedBoundingBox(); + var box = OrientedBoundingBox.fromRectangleTangentPlane(rect, pl, 0.0, 0.0, result); + expect(box).toBe(result); + + expect(box.center).toEqualEpsilon(new Cartesian3((1.0 + Math.SQRT1_2) / 2.0, 0.0, 0.0), CesiumMath.EPSILON15); + + var rotScale = 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); + expect(box.halfAxes).toEqualEpsilon(rotScale, CesiumMath.EPSILON15); + }); + + it('fromRectangleTangentPlane from a degenerate rectangle from (135, 0) to (-135, 0)', function() { + var d135 = 3 * CesiumMath.PI_OVER_FOUR; + var rect = new Rectangle(d135, 0.0, -d135, 0.0); + var pl = new EllipsoidTangentPlane(new Cartesian3(-1.0, 0.0, 0.0), Ellipsoid.UNIT_SPHERE); + var result = new OrientedBoundingBox(); + var box = OrientedBoundingBox.fromRectangleTangentPlane(rect, pl, 0.0, 0.0, result); + expect(box).toBe(result); + + expect(box.center).toEqualEpsilon(new Cartesian3(-(1.0 + Math.SQRT1_2) / 2.0, 0.0, 0.0), CesiumMath.EPSILON15); + + var rotScale = 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); + expect(box.halfAxes).toEqualEpsilon(rotScale, CesiumMath.EPSILON15); + }); + + it('fromRectangleTangentPlane from a degenerate rectangle from (0, -45) to (0, 45)', function() { + var d45 = CesiumMath.PI_OVER_FOUR; + var rect = new Rectangle(0.0, -d45, 0.0, d45); + var pl = new EllipsoidTangentPlane(Cartesian3.UNIT_X, Ellipsoid.UNIT_SPHERE); + var result = new OrientedBoundingBox(); + var box = OrientedBoundingBox.fromRectangleTangentPlane(rect, pl, 0.0, 0.0, result); + expect(box).toBe(result); + + expect(box.center).toEqualEpsilon(new Cartesian3((1.0 + Math.SQRT1_2) / 2.0, 0.0, 0.0), CesiumMath.EPSILON15); + + var rotScale = 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); + expect(box.halfAxes).toEqualEpsilon(rotScale, CesiumMath.EPSILON15); + }); + + it('fromRectangleTangentPlane from a degenerate rectangle from (0, 135) to (0, -135)', function() { + var d135 = 3 * CesiumMath.PI_OVER_FOUR; + var rect = new Rectangle(0.0, d135, 0.0, -d135); + var pl = new EllipsoidTangentPlane(new Cartesian3(-1.0, 0.0, 0.0), Ellipsoid.UNIT_SPHERE); + var result = new OrientedBoundingBox(); + var box = OrientedBoundingBox.fromRectangleTangentPlane(rect, pl, 0.0, 0.0, result); + expect(box).toBe(result); + + expect(box.center).toEqualEpsilon(new Cartesian3(-(1.0 + Math.SQRT1_2) / 2.0, 0.0, 0.0), CesiumMath.EPSILON15); + + var rotScale = 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); + expect(box.halfAxes).toEqualEpsilon(rotScale, CesiumMath.EPSILON15); + }); + + /** + * @param {Cartesian3} center + * @param {Matrix3} axes + */ + var intersectPlaneTestCornersEdgesFaces = function(center, axes) { + var SQRT1_2 = Math.pow(1.0 / 2.0, 1 / 2.0); + var SQRT1_3 = Math.pow(1.0 / 3.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); + }); +}); From 69a9ab776763b020f79de72364003d3b88c009d1 Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Wed, 3 Jun 2015 15:47:19 -0400 Subject: [PATCH 07/28] Deprecate ObjectOrientedBoundingBox --- CHANGES.md | 1 + Source/Core/ObjectOrientedBoundingBox.js | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 8fcd3ff07609..0bbbbaa7b13f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ Change Log * Deprecated * 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. If possible, `OrientedBoundingBox`, which is very similar. * 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). diff --git a/Source/Core/ObjectOrientedBoundingBox.js b/Source/Core/ObjectOrientedBoundingBox.js index d9cc1c736be6..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} From 3e68548c9b5a78c82d974622d269594453352aa5 Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Wed, 10 Jun 2015 08:25:33 -0400 Subject: [PATCH 08/28] Add EllipsoidTangentPlane.extentsToOrientedBoundingBox --- Source/Core/EllipsoidTangentPlane.js | 66 ++++++++++++++++++++++ Specs/Core/EllipsoidTangentPlaneSpec.js | 75 ++++++++++++++++++++++++- 2 files changed, 139 insertions(+), 2 deletions(-) diff --git a/Source/Core/EllipsoidTangentPlane.js b/Source/Core/EllipsoidTangentPlane.js index ab4e53bf2a4c..20e61b19d3fc 100644 --- a/Source/Core/EllipsoidTangentPlane.js +++ b/Source/Core/EllipsoidTangentPlane.js @@ -10,7 +10,9 @@ define([ './DeveloperError', './Ellipsoid', './IntersectionTests', + './Matrix3', './Matrix4', + './OrientedBoundingBox', './Plane', './Ray', './Transforms' @@ -25,7 +27,9 @@ define([ DeveloperError, Ellipsoid, IntersectionTests, + Matrix3, Matrix4, + OrientedBoundingBox, Plane, Ray, Transforms) { @@ -91,6 +95,17 @@ define([ get : function() { return this._origin; } + }, + + /** + * Gets the plane which is tangent to the ellipsoid. + * @memberof EllipsoidTangentPlane.prototype + * @type {Plane} + */ + plane : { + get : function() { + return this._plane; + } } }); @@ -299,5 +314,56 @@ define([ return result; }; + 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} minX Minimum x extent in tangent plane space. + * @param {Number} maxX Maximum x extent in tangent plane space. + * @param {Number} minY Minimum y extent in tangent plane space. + * @param {Number} maxY Maximum y extent in tangent plane space. + * @param {Number} minZ Minimum z extent in tangent plane space. + * @param {Number} maxZ 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. + */ + EllipsoidTangentPlane.prototype.extentsToOrientedBoundingBox = function(minX, maxX, minY, maxY, minZ, maxZ, result) { + //>>includeStart('debug', pragmas.debug); + if (!defined(minX)) { throw new DeveloperError('minX is required.'); } + if (!defined(maxX)) { throw new DeveloperError('maxX is required.'); } + if (!defined(minY)) { throw new DeveloperError('minY is required.'); } + if (!defined(maxY)) { throw new DeveloperError('maxY is required.'); } + if (!defined(minZ)) { throw new DeveloperError('minZ is required.'); } + if (!defined(maxZ)) { throw new DeveloperError('maxZ is required.'); } + //>>includeEnd('debug'); + + if (!defined(result)) { + result = new OrientedBoundingBox(); + } + + var halfAxes = result.halfAxes; + Matrix3.setColumn(halfAxes, 0, this._xAxis, halfAxes); + Matrix3.setColumn(halfAxes, 1, this._yAxis, halfAxes); + Matrix3.setColumn(halfAxes, 2, this._plane.normal, halfAxes); + + var centerOffset = scratchOffset; + centerOffset.x = (minX + maxX) / 2.0; + centerOffset.y = (minY + maxY) / 2.0; + centerOffset.z = (minZ + maxZ) / 2.0; + + var scale = scratchScale; + scale.x = (maxX - minX) / 2.0; + scale.y = (maxY - minY) / 2.0; + scale.z = (maxZ - minZ) / 2.0; + + var center = result.center; + centerOffset = Matrix3.multiplyByVector(halfAxes, centerOffset, centerOffset); + Cartesian3.add(this._origin, centerOffset, center); + Matrix3.multiplyByScale(halfAxes, scale, halfAxes); + + return result; + }; + return EllipsoidTangentPlane; }); diff --git a/Specs/Core/EllipsoidTangentPlaneSpec.js b/Specs/Core/EllipsoidTangentPlaneSpec.js index aef9f4175654..915a93309d17 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*/ @@ -129,6 +135,27 @@ defineSuite([ expect(returnedResults).toEqual(expectedResults); }); + it('extentsToOrientedBoundingBox works with a result parameter', function() { + var ellipsoid = Ellipsoid.UNIT_SPHERE; + var origin = Cartesian3.UNIT_X; + var pl = new EllipsoidTangentPlane(origin, ellipsoid); + var result = new OrientedBoundingBox(); + result = pl.extentsToOrientedBoundingBox(-0.5, 0.5, -0.5, 0.5, -0.5, 0.5, result); + var expected = new OrientedBoundingBox(origin, + new Matrix3(0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0, 0.5, 0.0)); + expect(result).toEqual(expected); + }); + + it('extentsToOrientedBoundingBox works without a result parameter', function() { + var ellipsoid = Ellipsoid.UNIT_SPHERE; + var origin = Cartesian3.UNIT_X; + var pl = new EllipsoidTangentPlane(origin, ellipsoid); + var result = pl.extentsToOrientedBoundingBox(-0.5, 0.5, -0.5, 0.5, -0.5, 0.5); + var expected = new OrientedBoundingBox(origin, + new Matrix3(0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0, 0.5, 0.0)); + expect(result).toEqual(expected); + }); + it('constructor throws without origin', function() { expect(function() { return new EllipsoidTangentPlane(undefined, Ellipsoid.WGS84); @@ -182,6 +209,19 @@ defineSuite([ }).toThrowDeveloperError(); }); + it('extentsToOrientedBoundingBox throws missing any of the dimensional parameters', function() { + var ellipsoid = Ellipsoid.UNIT_SPHERE; + var origin = new Cartesian3(1.0, 0.0, 0.0); + var pl = new EllipsoidTangentPlane(origin, ellipsoid); + var result = new OrientedBoundingBox(); + expect(function() { pl.extentsToOrientedBoundingBox(undefined, 0.5, -0.5, 0.5, -0.5, 0.5, result); }).toThrowDeveloperError(); + expect(function() { pl.extentsToOrientedBoundingBox(-0.5, undefined, -0.5, 0.5, -0.5, 0.5, result); }).toThrowDeveloperError(); + expect(function() { pl.extentsToOrientedBoundingBox(-0.5, 0.5, undefined, 0.5, -0.5, 0.5, result); }).toThrowDeveloperError(); + expect(function() { pl.extentsToOrientedBoundingBox(-0.5, 0.5, -0.5, undefined, -0.5, 0.5, result); }).toThrowDeveloperError(); + expect(function() { pl.extentsToOrientedBoundingBox(-0.5, 0.5, -0.5, 0.5, undefined, 0.5, result); }).toThrowDeveloperError(); + expect(function() { pl.extentsToOrientedBoundingBox(-0.5, 0.5, -0.5, 0.5, -0.5, undefined, result); }).toThrowDeveloperError(); + }); + it('projectPointsOntoEllipsoid works with an arbitrary ellipsoid using fromPoints', function () { var points = Cartesian3.fromDegreesArray([ -72.0, 40.0, @@ -199,4 +239,35 @@ defineSuite([ expect(positionsBack[0].y).toBeCloseTo(points[0].y); expect(positionsBack[0].z).toBeCloseTo(points[0].z); }); + + it('extentsToOrientedBoundingBox works with some edge-ish cases', function() { + var res, exp; + var result = new OrientedBoundingBox(); + var ellipsoid; + + ellipsoid = Ellipsoid.UNIT_SPHERE; + res = new EllipsoidTangentPlane(Cartesian3.UNIT_X, ellipsoid).extentsToOrientedBoundingBox(-0.3, 0.3, -0.3, 0.3, -0.3, 0.3, res); + exp = new OrientedBoundingBox(Cartesian3.UNIT_X, + new Matrix3(0.0, 0.0, 0.3, 0.3, 0.0, 0.0, 0.0, 0.3, 0.0)); + expect(res.center).toEqualEpsilon(exp.center, CesiumMath.EPSILON15); + expect(res.halfAxes).toEqualEpsilon(exp.halfAxes, CesiumMath.EPSILON15); + + ellipsoid = Ellipsoid.UNIT_SPHERE; + res = new EllipsoidTangentPlane(Cartesian3.UNIT_Z, ellipsoid).extentsToOrientedBoundingBox(-0.3, 0.3, -0.3, 0.3, -0.3, 0.3, res); + expect(res.center).toEqualEpsilon(Cartesian3.UNIT_Z, CesiumMath.EPSILON15); + + ellipsoid = Ellipsoid.UNIT_SPHERE; + res = new EllipsoidTangentPlane(Cartesian3.UNIT_Y, ellipsoid).extentsToOrientedBoundingBox(-0.3, 0.3, -0.3, 0.3, -0.3, 0.3, res); + exp = new OrientedBoundingBox(Cartesian3.UNIT_Y, + new Matrix3(-0.3, 0.0, 0.0, 0.0, 0.0, 0.3, 0.0, 0.3, 0.0)); + expect(res.center).toEqualEpsilon(exp.center, CesiumMath.EPSILON15); + expect(res.halfAxes).toEqualEpsilon(exp.halfAxes, CesiumMath.EPSILON15); + + ellipsoid = Ellipsoid.UNIT_SPHERE; + res = new EllipsoidTangentPlane(Cartesian3.UNIT_X, ellipsoid).extentsToOrientedBoundingBox(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, res); + exp = new OrientedBoundingBox(Cartesian3.UNIT_X, + new Matrix3(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)); + expect(res.center).toEqualEpsilon(exp.center, CesiumMath.EPSILON15); + expect(res.halfAxes).toEqualEpsilon(exp.halfAxes, CesiumMath.EPSILON15); + }); }); From 09a847bd5c038d5f248d103656848e037664e777 Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Wed, 10 Jun 2015 09:14:52 -0400 Subject: [PATCH 09/28] [WIP] Frustum-OBB culling on terrain - TODO: testing, benchmarks --- Source/Core/CesiumTerrainProvider.js | 16 +++ Source/Core/QuantizedMeshTerrainData.js | 9 +- Source/Core/TerrainMesh.js | 9 +- Source/Renderer/DrawCommand.js | 11 ++ Source/Scene/GlobeSurfaceTile.js | 3 + Source/Scene/GlobeSurfaceTileProvider.js | 11 +- Source/Scene/Scene.js | 104 +++++++++++------- Source/Scene/TileTerrain.js | 3 + .../CesiumInspector/CesiumInspector.js | 2 +- 9 files changed, 123 insertions(+), 45 deletions(-) diff --git a/Source/Core/CesiumTerrainProvider.js b/Source/Core/CesiumTerrainProvider.js index 834a842d0421..a1652d7f73aa 100644 --- a/Source/Core/CesiumTerrainProvider.js +++ b/Source/Core/CesiumTerrainProvider.js @@ -10,12 +10,16 @@ define([ './defined', './defineProperties', './DeveloperError', + './EllipsoidTangentPlane', './Event', './GeographicTilingScheme', './HeightmapTerrainData', './IndexDatatype', './loadArrayBuffer', './loadJson', + './Math', + './Matrix3', + './OrientedBoundingBox', './QuantizedMeshTerrainData', './RuntimeError', './TerrainProvider', @@ -32,12 +36,16 @@ define([ defined, defineProperties, DeveloperError, + EllipsoidTangentPlane, Event, GeographicTilingScheme, HeightmapTerrainData, IndexDatatype, loadArrayBuffer, loadJson, + CesiumMath, + Matrix3, + OrientedBoundingBox, QuantizedMeshTerrainData, RuntimeError, TerrainProvider, @@ -417,11 +425,19 @@ define([ var skirtHeight = provider.getLevelMaximumGeometricError(level) * 5.0; + var rectangle = provider._tilingScheme.tileXYToRectangle(x, y, level); + var boundingOBB; + if (rectangle.width < CesiumMath.PI_OVER_TWO + 0.001) { + var tangentPlane = new EllipsoidTangentPlane(center, provider._tilingScheme.ellipsoid); + boundingOBB = OrientedBoundingBox.fromRectangleTangentPlane(rectangle, tangentPlane, minimumHeight, maximumHeight); + } + return new QuantizedMeshTerrainData({ center : center, minimumHeight : minimumHeight, maximumHeight : maximumHeight, boundingSphere : boundingSphere, + boundingOBB : boundingOBB, horizonOcclusionPoint : horizonOcclusionPoint, quantizedVertices : encodedVertexBuffer, encodedNormals : encodedNormalBuffer, diff --git a/Source/Core/QuantizedMeshTerrainData.js b/Source/Core/QuantizedMeshTerrainData.js index c0501d29cc41..018ceae16b83 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.boundingOBB] 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), + * boundingOBB : 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._boundingOBB = options.boundingOBB; 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._boundingOBB); }); }; @@ -388,6 +394,7 @@ define([ minimumHeight : result.minimumHeight, maximumHeight : result.maximumHeight, boundingSphere : BoundingSphere.clone(result.boundingSphere), + boundingOBB : OrientedBoundingBox.clone(result.boundingOBB), horizonOcclusionPoint : Cartesian3.clone(result.horizonOcclusionPoint), westIndices : result.westIndices, southIndices : result.southIndices, diff --git a/Source/Core/TerrainMesh.js b/Source/Core/TerrainMesh.js index 3e60cb6a0ece..6c282f2849c1 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} [boundingOBB=undefined] 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, boundingOBB) { /** * 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.boundingOBB = boundingOBB; }; return TerrainMesh; diff --git a/Source/Renderer/DrawCommand.js b/Source/Renderer/DrawCommand.js index d7af2e289009..27b4a6431e23 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.boundingOBB = options.boundingOBB; + /** * 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. diff --git a/Source/Scene/GlobeSurfaceTile.js b/Source/Scene/GlobeSurfaceTile.js index a9a8eb8ebc1d..52e1264bea5f 100644 --- a/Source/Scene/GlobeSurfaceTile.js +++ b/Source/Scene/GlobeSurfaceTile.js @@ -8,6 +8,7 @@ define([ '../Core/defined', '../Core/defineProperties', '../Core/IntersectionTests', + '../Core/OrientedBoundingBox', '../Core/PixelFormat', '../Core/Rectangle', '../Renderer/PixelDatatype', @@ -28,6 +29,7 @@ define([ defined, defineProperties, IntersectionTests, + OrientedBoundingBox, PixelFormat, Rectangle, PixelDatatype, @@ -124,6 +126,7 @@ define([ this.maximumHeight = 0.0; this.boundingSphere3D = new BoundingSphere(); this.boundingSphere2D = new BoundingSphere(); + this.boundingOBB = undefined; this.occludeePointInScaledSpace = new Cartesian3(); this.loadedTerrain = undefined; diff --git a/Source/Scene/GlobeSurfaceTileProvider.js b/Source/Scene/GlobeSurfaceTileProvider.js index ac40863482bd..c7abbd1f5c62 100644 --- a/Source/Scene/GlobeSurfaceTileProvider.js +++ b/Source/Scene/GlobeSurfaceTileProvider.js @@ -5,6 +5,7 @@ define([ '../Core/Cartesian3', '../Core/Cartesian4', '../Core/Color', + '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', '../Core/destroyObject', @@ -15,6 +16,7 @@ define([ '../Core/IndexDatatype', '../Core/Intersect', '../Core/Matrix4', + '../Core/OrientedBoundingBox', '../Core/PrimitiveType', '../Core/Rectangle', '../Core/Visibility', @@ -36,6 +38,7 @@ define([ Cartesian3, Cartesian4, Color, + defaultValue, defined, defineProperties, destroyObject, @@ -46,6 +49,7 @@ define([ IndexDatatype, Intersect, Matrix4, + OrientedBoundingBox, PrimitiveType, Rectangle, Visibility, @@ -395,7 +399,7 @@ define([ var cullingVolume = frameState.cullingVolume; - var boundingVolume = surfaceTile.boundingSphere3D; + var boundingVolume = defaultValue(surfaceTile.boundingOBB, surfaceTile.boundingSphere3D); if (frameState.mode !== SceneMode.SCENE3D) { boundingVolume = boundingSphereScratch; @@ -893,6 +897,7 @@ define([ command.owner = tile; command.cull = false; command.boundingVolume = new BoundingSphere(); + command.boundingOBB = undefined; uniformMap = createTileUniformMap(); @@ -1002,6 +1007,7 @@ define([ } var boundingVolume = command.boundingVolume; + var boundingOBB = command.boundingOBB; if (frameState.mode !== SceneMode.SCENE3D) { BoundingSphere.fromRectangleWithHeights2D(tile.rectangle, frameState.mapProjection, surfaceTile.minimumHeight, surfaceTile.maximumHeight, boundingVolume); @@ -1011,7 +1017,8 @@ define([ boundingVolume = BoundingSphere.union(surfaceTile.boundingSphere3D, boundingVolume, boundingVolume); } } else { - BoundingSphere.clone(surfaceTile.boundingSphere3D, boundingVolume); + command.boundingVolume = BoundingSphere.clone(surfaceTile.boundingSphere3D, boundingVolume); + command.boundingOBB = OrientedBoundingBox.clone(surfaceTile.boundingOBB, boundingOBB); } commandList.push(command); diff --git a/Source/Scene/Scene.js b/Source/Scene/Scene.js index f92ed359b657..268cb47f1842 100644 --- a/Source/Scene/Scene.js +++ b/Source/Scene/Scene.js @@ -2,6 +2,7 @@ define([ '../Core/BoundingRectangle', '../Core/BoundingSphere', + '../Core/BoxOutlineGeometry', '../Core/Cartesian2', '../Core/Cartesian3', '../Core/Cartesian4', @@ -57,6 +58,7 @@ define([ ], function( BoundingRectangle, BoundingSphere, + BoxOutlineGeometry, Cartesian2, Cartesian3, Cartesian4, @@ -1196,58 +1198,80 @@ define([ command.execute(context, passState, renderState, shaderProgram); } - if (command.debugShowBoundingVolume && (defined(command.boundingVolume))) { + if (command.debugShowBoundingVolume) { // Debug code to draw bounding volume for command. Not optimized! - // Assumes bounding volume is a bounding sphere. + if (defined(scene._debugSphere)) { scene._debugSphere.destroy(); } var frameState = scene._frameState; - var boundingVolume = command.boundingVolume; - var radius = boundingVolume.radius; - var center = boundingVolume.center; - - var geometry = GeometryPipeline.toWireframe(EllipsoidGeometry.createGeometry(new EllipsoidGeometry({ - radii : new Cartesian3(radius, radius, radius), - vertexFormat : PerInstanceColorAppearance.FLAT_VERTEX_FORMAT - }))); - - if (frameState.mode !== SceneMode.SCENE3D) { - center = Matrix4.multiplyByPoint(transformFrom2D, center, center); - var projection = frameState.mapProjection; - var centerCartographic = projection.unproject(center); - center = projection.ellipsoid.cartographicToCartesian(centerCartographic); - } - scene._debugSphere = new Primitive({ - geometryInstances : new GeometryInstance({ - geometry : geometry, - modelMatrix : Matrix4.multiplyByTranslation(Matrix4.IDENTITY, center, new Matrix4()), - attributes : { - color : new ColorGeometryInstanceAttribute(1.0, 0.0, 0.0, 1.0) - } - }), - appearance : new PerInstanceColorAppearance({ - flat : true, - translucent : false - }), - asynchronous : false - }); + var modelMatrix = new Matrix4(); - var commandList = []; - scene._debugSphere.update(context, frameState, commandList); + var geometry; + if (defined(command.boundingOBB)) { + // Assumes bounding volume is an OrientedBoundingBox. + var boundingOBB = command.boundingOBB; - var framebuffer; - if (defined(debugFramebuffer)) { - framebuffer = passState.framebuffer; - passState.framebuffer = debugFramebuffer; + geometry = BoxOutlineGeometry.createGeometry(BoxOutlineGeometry.fromDimensions({ + dimensions: new Cartesian3(2.0, 2.0, 2.0), + vertexFormat: PerInstanceColorAppearance.FLAT_VERTEX_FORMAT + })); + + Matrix4.fromRotationTranslation(boundingOBB.halfAxes, boundingOBB.center, modelMatrix); + } else if (defined(command.boundingVolume)) { + // Assumes bounding volume is a bounding sphere. + + var boundingVolume = command.boundingVolume; + var radius = boundingVolume.radius; + var center = boundingVolume.center; + + geometry = GeometryPipeline.toWireframe(EllipsoidGeometry.createGeometry(new EllipsoidGeometry({ + radii: new Cartesian3(radius, radius, radius), + vertexFormat: PerInstanceColorAppearance.FLAT_VERTEX_FORMAT + }))); + + if (frameState.mode !== SceneMode.SCENE3D) { + center = Matrix4.multiplyByPoint(transformFrom2D, center, center); + var projection = frameState.mapProjection; + var centerCartographic = projection.unproject(center); + center = projection.ellipsoid.cartographicToCartesian(centerCartographic); + } + + Matrix4.multiplyByTranslation(Matrix4.IDENTITY, center, modelMatrix); } - commandList[0].execute(context, passState); + if (defined(geometry)) { + scene._debugSphere = new Primitive({ + geometryInstances: new GeometryInstance({ + geometry: geometry, + modelMatrix: modelMatrix, + attributes: { + color: new ColorGeometryInstanceAttribute(1.0, 0.0, 0.0, 1.0) + } + }), + appearance: new PerInstanceColorAppearance({ + flat: true, + translucent: false + }), + asynchronous: false + }); + + var commandList = []; + scene._debugSphere.update(context, frameState, commandList); - if (defined(framebuffer)) { - passState.framebuffer = framebuffer; + var framebuffer; + if (defined(debugFramebuffer)) { + framebuffer = passState.framebuffer; + passState.framebuffer = debugFramebuffer; + } + + commandList[0].execute(context, passState); + + if (defined(framebuffer)) { + passState.framebuffer = framebuffer; + } } } } diff --git a/Source/Scene/TileTerrain.js b/Source/Scene/TileTerrain.js index df4d44027d56..117f375318bf 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.boundingOBB = OrientedBoundingBox.clone(mesh.boundingOBB, surfaceTile.boundingOBB); 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); From 3f1a58e9481f161fcc8ea793aceabfd4b64fb317 Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Wed, 10 Jun 2015 15:34:36 -0400 Subject: [PATCH 10/28] Fixups for pull request --- CHANGES.md | 12 +- Source/Core/AxisAlignedBoundingBox.js | 3 + Source/Core/BoundingSphere.js | 3 + Source/Core/CesiumTerrainProvider.js | 15 +- Source/Core/EllipsoidTangentPlane.js | 154 +++++++--------- Source/Core/OrientedBoundingBox.js | 205 ++++++++++++--------- Source/Core/QuantizedMeshTerrainData.js | 12 +- Source/Core/TerrainMesh.js | 6 +- Source/Renderer/DrawCommand.js | 4 +- Source/Scene/GlobeSurfaceTile.js | 4 +- Source/Scene/GlobeSurfaceTileProvider.js | 8 +- Source/Scene/Scene.js | 6 +- Source/Scene/TileTerrain.js | 2 +- Specs/Core/EllipsoidTangentPlaneSpec.js | 119 ++++++------ Specs/Core/OrientedBoundingBoxSpec.js | 224 +++++++++++++---------- 15 files changed, 413 insertions(+), 364 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 0bbbbaa7b13f..ffedf90c6136 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,14 +5,24 @@ Change Log * Deprecated * 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. If possible, `OrientedBoundingBox`, which is very similar. + * 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). * Fixed an issue where `Camera` functions would throw an exception if used from within a `Scene.morphComplete` callback [#2776](https://github.com/AnalyticalGraphicsInc/cesium/issues/2776). * `Model` can now load Binary glTF from a Uint8Array. * Added a new camera mode for horizon views. When the camera is looking at the horizon and a point on terrain above the camera is picked, the camera moves in the plane containing the camera position, up and right vectors. +* 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 to improve performance of terrain rendering (by **TODO**%) and loading (by **TODO**%). ### 1.10 - 2015-06-01 diff --git a/Source/Core/AxisAlignedBoundingBox.js b/Source/Core/AxisAlignedBoundingBox.js index 2acf4fb2563e..a169484752b5 100644 --- a/Source/Core/AxisAlignedBoundingBox.js +++ b/Source/Core/AxisAlignedBoundingBox.js @@ -3,6 +3,7 @@ define([ './Cartesian3', './defaultValue', './defined', + './deprecationWarning', './DeveloperError', './Intersect', './Plane' @@ -10,6 +11,7 @@ define([ Cartesian3, defaultValue, defined, + deprecationWarning, DeveloperError, Intersect, Plane) { @@ -213,6 +215,7 @@ define([ * 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); }; diff --git a/Source/Core/BoundingSphere.js b/Source/Core/BoundingSphere.js index bb973e3f6665..09ca61880268 100644 --- a/Source/Core/BoundingSphere.js +++ b/Source/Core/BoundingSphere.js @@ -4,6 +4,7 @@ define([ './Cartographic', './defaultValue', './defined', + './deprecationWarning', './DeveloperError', './Ellipsoid', './GeographicProjection', @@ -17,6 +18,7 @@ define([ Cartographic, defaultValue, defined, + deprecationWarning, DeveloperError, Ellipsoid, GeographicProjection, @@ -792,6 +794,7 @@ define([ * 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); }; diff --git a/Source/Core/CesiumTerrainProvider.js b/Source/Core/CesiumTerrainProvider.js index a1652d7f73aa..55f4c166674d 100644 --- a/Source/Core/CesiumTerrainProvider.js +++ b/Source/Core/CesiumTerrainProvider.js @@ -10,7 +10,6 @@ define([ './defined', './defineProperties', './DeveloperError', - './EllipsoidTangentPlane', './Event', './GeographicTilingScheme', './HeightmapTerrainData', @@ -36,7 +35,6 @@ define([ defined, defineProperties, DeveloperError, - EllipsoidTangentPlane, Event, GeographicTilingScheme, HeightmapTerrainData, @@ -426,10 +424,11 @@ define([ var skirtHeight = provider.getLevelMaximumGeometricError(level) * 5.0; var rectangle = provider._tilingScheme.tileXYToRectangle(x, y, level); - var boundingOBB; - if (rectangle.width < CesiumMath.PI_OVER_TWO + 0.001) { - var tangentPlane = new EllipsoidTangentPlane(center, provider._tilingScheme.ellipsoid); - boundingOBB = OrientedBoundingBox.fromRectangleTangentPlane(rectangle, tangentPlane, minimumHeight, maximumHeight); + 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) + orientedBoundingBox = OrientedBoundingBox.fromEllipsoidRectangle(provider._tilingScheme.ellipsoid, rectangle, minimumHeight, maximumHeight); } return new QuantizedMeshTerrainData({ @@ -437,7 +436,7 @@ define([ minimumHeight : minimumHeight, maximumHeight : maximumHeight, boundingSphere : boundingSphere, - boundingOBB : boundingOBB, + orientedBoundingBox : orientedBoundingBox, horizonOcclusionPoint : horizonOcclusionPoint, quantizedVertices : encodedVertexBuffer, encodedNormals : encodedNormalBuffer, @@ -729,4 +728,4 @@ define([ }; return CesiumTerrainProvider; -}); \ No newline at end of file +}); diff --git a/Source/Core/EllipsoidTangentPlane.js b/Source/Core/EllipsoidTangentPlane.js index 20e61b19d3fc..a57adbbe9c83 100644 --- a/Source/Core/EllipsoidTangentPlane.js +++ b/Source/Core/EllipsoidTangentPlane.js @@ -12,7 +12,6 @@ define([ './IntersectionTests', './Matrix3', './Matrix4', - './OrientedBoundingBox', './Plane', './Ray', './Transforms' @@ -29,7 +28,6 @@ define([ IntersectionTests, Matrix3, Matrix4, - OrientedBoundingBox, Plane, Ray, Transforms) { @@ -100,12 +98,49 @@ define([ /** * 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; + } } }); @@ -128,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, radially outward from the global origin. + * 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); @@ -145,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)) { @@ -171,7 +206,10 @@ define([ }; /** - * Computes the projection of the provided 3D positions onto the 2D plane, radially outward from the global origin. + * 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. @@ -215,37 +253,37 @@ define([ } //>>includeEnd('debug'); - var ray = projectPointOntoPlaneRay; + if (!defined(result)) { + result = new Cartesian2(); + } + + var ray = scratchProjectPointOntoPlaneRay; ray.origin = cartesian; - Cartesian3.normalize(this._plane.normal, ray.direction); + Cartesian3.clone(this._plane.normal, 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)) { - var v = Cartesian3.subtract(intersectionPoint, this._origin, intersectionPoint); - var x = Cartesian3.dot(this._xAxis, v); - var y = Cartesian3.dot(this._yAxis, v); + var v = Cartesian3.subtract(intersectionPoint, this._origin, intersectionPoint); + var x = Cartesian3.dot(this._xAxis, v); + var y = Cartesian3.dot(this._yAxis, v); - if (!defined(result)) { - return new Cartesian2(x, y); - } - result.x = x; - result.y = y; - return result; - } - return undefined; + 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. + * @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); @@ -258,16 +296,11 @@ define([ result = []; } - var count = 0; var length = cartesians.length; - for ( var i = 0; i < length; i++) { - var p = this.projectPointToNearestOnPlane(cartesians[i], result[count]); - if (defined(p)) { - result[count] = p; - count++; - } + result.length = length; + for (var i = 0; i < length; i++) { + result[i] = this.projectPointToNearestOnPlane(cartesians[i], result[i]); } - result.length = count; return result; }; @@ -314,56 +347,5 @@ define([ return result; }; - 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} minX Minimum x extent in tangent plane space. - * @param {Number} maxX Maximum x extent in tangent plane space. - * @param {Number} minY Minimum y extent in tangent plane space. - * @param {Number} maxY Maximum y extent in tangent plane space. - * @param {Number} minZ Minimum z extent in tangent plane space. - * @param {Number} maxZ 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. - */ - EllipsoidTangentPlane.prototype.extentsToOrientedBoundingBox = function(minX, maxX, minY, maxY, minZ, maxZ, result) { - //>>includeStart('debug', pragmas.debug); - if (!defined(minX)) { throw new DeveloperError('minX is required.'); } - if (!defined(maxX)) { throw new DeveloperError('maxX is required.'); } - if (!defined(minY)) { throw new DeveloperError('minY is required.'); } - if (!defined(maxY)) { throw new DeveloperError('maxY is required.'); } - if (!defined(minZ)) { throw new DeveloperError('minZ is required.'); } - if (!defined(maxZ)) { throw new DeveloperError('maxZ is required.'); } - //>>includeEnd('debug'); - - if (!defined(result)) { - result = new OrientedBoundingBox(); - } - - var halfAxes = result.halfAxes; - Matrix3.setColumn(halfAxes, 0, this._xAxis, halfAxes); - Matrix3.setColumn(halfAxes, 1, this._yAxis, halfAxes); - Matrix3.setColumn(halfAxes, 2, this._plane.normal, halfAxes); - - var centerOffset = scratchOffset; - centerOffset.x = (minX + maxX) / 2.0; - centerOffset.y = (minY + maxY) / 2.0; - centerOffset.z = (minZ + maxZ) / 2.0; - - var scale = scratchScale; - scale.x = (maxX - minX) / 2.0; - scale.y = (maxY - minY) / 2.0; - scale.z = (maxZ - minZ) / 2.0; - - var center = result.center; - centerOffset = Matrix3.multiplyByVector(halfAxes, centerOffset, centerOffset); - Cartesian3.add(this._origin, centerOffset, center); - Matrix3.multiplyByScale(halfAxes, scale, halfAxes); - - return result; - }; - return EllipsoidTangentPlane; }); diff --git a/Source/Core/OrientedBoundingBox.js b/Source/Core/OrientedBoundingBox.js index 808a4b223a5e..e389f8d4e60f 100644 --- a/Source/Core/OrientedBoundingBox.js +++ b/Source/Core/OrientedBoundingBox.js @@ -7,6 +7,7 @@ define([ './defined', './DeveloperError', './Ellipsoid', + './EllipsoidTangentPlane', './Intersect', './Plane', './Rectangle', @@ -20,6 +21,7 @@ define([ defined, DeveloperError, Ellipsoid, + EllipsoidTangentPlane, Intersect, Plane, Rectangle, @@ -29,8 +31,7 @@ define([ /** * Creates an instance of an OrientedBoundingBox. - * An OrientedBoundingBox model of an object or set of objects, is a closed volume (a cuboid), which completely contains the object or the set of objects. - * It is oriented, so it can provide an optimum fit, it can bound more tightly. + * 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 * @@ -39,15 +40,13 @@ define([ * Equivalently, the transformation matrix, to rotate and scale a 2x2x2 * cube centered at the origin. * - * @see OrientedBoundingBox.fromBoundingRectangle * @see BoundingSphere * @see BoundingRectangle - * @see ObjectOrientedBoundingBox * * @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); - * var halfAxes = Cesium.Matrix3.clone(Cesium.Matrix3.fromScale(new Cartesian3(1.0, 3.0, 2.0))); + * 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); */ @@ -66,28 +65,29 @@ define([ this.halfAxes = Matrix3.clone(defaultValue(halfAxes, Matrix3.ZERO)); }; - var scratchCartesian1 = new Cartesian3(); - var scratchCartesian2 = new Cartesian3(); - var scratchCartesian3 = new Cartesian3(); - - var scratchRotation = new Matrix3(); + var scratchOffset = new Cartesian3(); + var scratchScale = new Cartesian3(); /** - * Computes an OrientedBoundingBox from a BoundingRectangle. - * The BoundingRectangle is placed on the XY plane. + * Computes an OrientedBoundingBox given extents in the east-north-up space of the tangent plane. * - * @param {BoundingRectangle} boundingRectangle A bounding rectangle. - * @param {Number} [rotation=0.0] The rotation of the bounding box in radians. + * @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. - * - * @example - * // Compute an object oriented bounding box enclosing two points. - * var box = Cesium.OrientedBoundingBox.fromBoundingRectangle(boundingRectangle, 0.0); */ - OrientedBoundingBox.fromBoundingRectangle = function(boundingRectangle, rotation, result) { + var fromTangentPlaneExtents = function(tangentPlane, minimumX, maximumX, minimumY, maximumY, minimumZ, maximumZ, result) { //>>includeStart('debug', pragmas.debug); - if (!defined(boundingRectangle)) { - throw new DeveloperError('boundingRectangle is required'); + 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'); @@ -95,99 +95,123 @@ define([ result = new OrientedBoundingBox(); } - var rotMat; - if (defined(rotation)) { - rotMat = Matrix3.fromRotationZ(rotation, scratchRotation); - } else { - rotMat = Matrix3.clone(Matrix3.IDENTITY, scratchRotation); - } + 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 = scratchCartesian1; - scale.x = boundingRectangle.width * 0.5; - scale.y = boundingRectangle.height * 0.5; - scale.z = 0.0; - Matrix3.multiplyByScale(rotMat, scale, result.halfAxes); + var scale = scratchScale; + scale.x = (maximumX - minimumX) / 2.0; + scale.y = (maximumY - minimumY) / 2.0; + scale.z = (maximumZ - minimumZ) / 2.0; - var translation = Matrix3.multiplyByVector(rotMat, scale, result.center); - translation.x += boundingRectangle.x; - translation.y += boundingRectangle.y; + var center = result.center; + centerOffset = Matrix3.multiplyByVector(halfAxes, centerOffset, centerOffset); + Cartesian3.add(tangentPlane.origin, centerOffset, center); + Matrix3.multiplyByScale(halfAxes, scale, halfAxes); return result; }; - var cornersCartographicScratch = [new Cartographic(), new Cartographic(), new Cartographic(), new Cartographic(), new Cartographic(), new Cartographic()]; - var cornersCartesianScratch = [new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3()]; - var cornersProjectedScratch = [new Cartesian2(), new Cartesian2(), new Cartesian2(), new Cartesian2(), new Cartesian2(), new Cartesian2()]; + 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 from a surface {@link Rectangle} aligned with a specified {@link EllipsoidTangentPlane}. + * 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 valid rectangle used to create a bounding box. - * @param {EllipsoidTangentPlane} tangentPlane A tangent plane with which the bounding box will be aligned. - * @param {Number} [minHeight=0.0] The minimum height (elevation) within the tile. - * @param {Number} [maxHeight=0.0] The maximum height (elevation) within the tile. + * @param {Ellipsoid} ellipsoid The ellipsoid on which the rectangle is defined. + * @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 {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. */ - OrientedBoundingBox.fromRectangleTangentPlane = function(rectangle, tangentPlane, minHeight, maxHeight, result) { + OrientedBoundingBox.fromEllipsoidRectangle = function(ellipsoid, rectangle, minimumHeight, maximumHeight, result) { //>>includeStart('debug', pragmas.debug); + if (!defined(ellipsoid)) { + throw new DeveloperError('ellipsoid is required'); + } if (!defined(rectangle)) { throw new DeveloperError('rectangle is required'); } - if (!defined(tangentPlane)) { - throw new DeveloperError('tangentPlane 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'); } - (function() { - var rw = rectangle.width; - var rh = rectangle.height; - if (rw < 0.0 || rw > CesiumMath.PI) { - throw new DeveloperError('Rectangle width must be between 0 and pi') - } - if (rh < 0.0 || rh > CesiumMath.PI) { - throw new DeveloperError('Rectangle height must be between 0 and pi') - } - })(); //>>includeEnd('debug'); - minHeight = defaultValue(minHeight, 0.0); - maxHeight = defaultValue(maxHeight, 0.0); + minimumHeight = defaultValue(minimumHeight, 0.0); + maximumHeight = defaultValue(maximumHeight, 0.0); + // If the rectangle does not span the equator, then the bounding box will be aligned with the tangent plane at the center of the rectangle. + // If the rectangle does span the equator, then the bounding box will be aligned with the tangent plane to the equator at the longitudinal center of the rectangle. + var tangentPointCartographic = Rectangle.center(rectangle, scratchRectangleCenterCartographic); + if (rectangle.south < 0.0 && rectangle.north > 0.0) { + // The rectangle spans the equator + tangentPointCartographic.latitude = 0.0; + } + var tangentPoint = ellipsoid.cartographicToCartesian(tangentPointCartographic, scratchRectangleCenter); + var tangentPlane = new EllipsoidTangentPlane(tangentPoint, ellipsoid); var plane = tangentPlane.plane; - // Rectangle at maximum height - var cornerNE = cornersCartographicScratch[0]; - var cornerNW = cornersCartographicScratch[1]; - var cornerSE = cornersCartographicScratch[2]; - var cornerSW = cornersCartographicScratch[3]; - var cornerNC = cornersCartographicScratch[4]; - var cornerSC = cornersCartographicScratch[5]; + // Corner arrangement: + // N/+y + // [0] [1] [2] + // W/-x [7] [3] E/+x + // [6] [5] [4] + // S/-y + 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 lonWest = rectangle.west; - var lonEast = rectangle.east; - var lonCenter = lonWest + 0.5 * rectangle.width; - cornerSW.latitude = cornerSC.latitude = cornerSE.latitude = rectangle.south; - cornerNW.latitude = cornerNC.latitude = cornerNE.latitude = rectangle.north; - cornerSW.longitude = cornerNW.longitude = lonWest; - cornerSC.longitude = cornerNC.longitude = lonCenter; - cornerSE.longitude = cornerNE.longitude = lonEast; + var lonCenter = tangentPointCartographic.longitude; + var latCenter = 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; - cornerNE.height = cornerNW.height = cornerSE.height = cornerSW.height = cornerNC.height = cornerSC.height = maxHeight; + // Compute XY extents using the rectangle at maximum height + perimeterNE.height = perimeterNW.height = perimeterSE.height = perimeterSW.height = perimeterNC.height = perimeterSC.height = maximumHeight; - tangentPlane.ellipsoid.cartographicArrayToCartesianArray(cornersCartographicScratch, cornersCartesianScratch); - tangentPlane.projectPointsToNearestOnPlane(cornersCartesianScratch, cornersProjectedScratch); - var minX = Math.min(cornersProjectedScratch[1].x, cornersProjectedScratch[3].x); - var maxX = Math.max(cornersProjectedScratch[0].x, cornersProjectedScratch[2].x); - var minY = Math.min(cornersProjectedScratch[2].y, cornersProjectedScratch[3].y, cornersProjectedScratch[5].y); - var maxY = Math.max(cornersProjectedScratch[0].y, cornersProjectedScratch[1].y, cornersProjectedScratch[4].y); + 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[6].y, perimeterProjectedScratch[6].y); + var maxY = Math.max(perimeterProjectedScratch[0].y, perimeterProjectedScratch[1].y, perimeterProjectedScratch[2].y); - cornerNE.height = cornerNW.height = cornerSE.height = cornerSW.height = minHeight; - tangentPlane.ellipsoid.cartographicArrayToCartesianArray(cornersCartographicScratch, cornersCartesianScratch); - var minZ = Math.min(Plane.getPointDistance(plane, cornersCartesianScratch[0]), - Plane.getPointDistance(plane, cornersCartesianScratch[1]), - Plane.getPointDistance(plane, cornersCartesianScratch[2]), - Plane.getPointDistance(plane, cornersCartesianScratch[3])); - var maxZ = maxHeight; // Since the tangent plane touches the surface at height = 0, this is okay + // 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[1]), + Plane.getPointDistance(plane, perimeterCartesianScratch[2]), + Plane.getPointDistance(plane, perimeterCartesianScratch[3])); + var maxZ = maximumHeight; // Since the tangent plane touches the surface at height = 0, this is okay - return tangentPlane.extentsToOrientedBoundingBox(minX, maxX, minY, maxY, minZ, maxZ, result); + return fromTangentPlaneExtents(tangentPlane, minX, maxX, minY, maxY, minZ, maxZ, result); }; /** @@ -212,6 +236,9 @@ define([ return result; }; + var scratchCartesian1 = new Cartesian3(); + var scratchCartesian2 = new Cartesian3(); + var scratchCartesian3 = new Cartesian3(); /** * Determines which side of a plane the oriented bounding box is located. * diff --git a/Source/Core/QuantizedMeshTerrainData.js b/Source/Core/QuantizedMeshTerrainData.js index 018ceae16b83..5458bd0baea6 100644 --- a/Source/Core/QuantizedMeshTerrainData.js +++ b/Source/Core/QuantizedMeshTerrainData.js @@ -46,7 +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.boundingOBB] An OrientedBoundingBox 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. @@ -91,7 +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), - * boundingOBB : new Cesium.OrientedBoundingBox(new Cesium.Cartesian3(1.0, 2.0, 3.0), Matrix3.fromRotationX(Cesium.Math.PI, new Matrix3())), + * 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], @@ -158,7 +158,7 @@ define([ this._minimumHeight = options.minimumHeight; this._maximumHeight = options.maximumHeight; this._boundingSphere = options.boundingSphere; - this._boundingOBB = options.boundingOBB; + this._orientedBoundingBox = options.orientedBoundingBox; this._horizonOcclusionPoint = options.horizonOcclusionPoint; var vertexCount = this._quantizedVertices.length / 3; @@ -297,7 +297,7 @@ define([ that._boundingSphere, that._horizonOcclusionPoint, defined(that._encodedNormals) ? 7 : 6, - that._boundingOBB); + that._orientedBoundingBox); }); }; @@ -394,7 +394,7 @@ define([ minimumHeight : result.minimumHeight, maximumHeight : result.maximumHeight, boundingSphere : BoundingSphere.clone(result.boundingSphere), - boundingOBB : OrientedBoundingBox.clone(result.boundingOBB), + orientedBoundingBox : OrientedBoundingBox.clone(result.orientedBoundingBox), horizonOcclusionPoint : Cartesian3.clone(result.horizonOcclusionPoint), westIndices : result.westIndices, southIndices : result.southIndices, @@ -511,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 6c282f2849c1..3432d5537279 100644 --- a/Source/Core/TerrainMesh.js +++ b/Source/Core/TerrainMesh.js @@ -25,9 +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} [boundingOBB=undefined] A bounding sphere that completely contains the tile. + * @param {OrientedBoundingBox} [orientedBoundingBox] A bounding sphere that completely contains the tile. */ - var TerrainMesh = function TerrainMesh(center, vertices, indices, minimumHeight, maximumHeight, boundingSphere3D, occludeePointInScaledSpace, vertexStride, boundingOBB) { + 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} @@ -88,7 +88,7 @@ define([ * A bounding box that completely contains the tile. * @type {OrientedBoundingBox} */ - this.boundingOBB = boundingOBB; + this.orientedBoundingBox = orientedBoundingBox; }; return TerrainMesh; diff --git a/Source/Renderer/DrawCommand.js b/Source/Renderer/DrawCommand.js index 27b4a6431e23..f8fdfcb972b6 100644 --- a/Source/Renderer/DrawCommand.js +++ b/Source/Renderer/DrawCommand.js @@ -40,7 +40,7 @@ define([ * * @see DrawCommand#debugShowBoundingVolume */ - this.boundingOBB = options.boundingOBB; + this.orientedBoundingBox = options.orientedBoundingBox; /** * When true, the renderer frustum and horizon culls the command based on its {@link DrawCommand#boundingVolume}. @@ -197,4 +197,4 @@ define([ }; return DrawCommand; -}); \ No newline at end of file +}); diff --git a/Source/Scene/GlobeSurfaceTile.js b/Source/Scene/GlobeSurfaceTile.js index 52e1264bea5f..229a08ffee37 100644 --- a/Source/Scene/GlobeSurfaceTile.js +++ b/Source/Scene/GlobeSurfaceTile.js @@ -8,7 +8,6 @@ define([ '../Core/defined', '../Core/defineProperties', '../Core/IntersectionTests', - '../Core/OrientedBoundingBox', '../Core/PixelFormat', '../Core/Rectangle', '../Renderer/PixelDatatype', @@ -29,7 +28,6 @@ define([ defined, defineProperties, IntersectionTests, - OrientedBoundingBox, PixelFormat, Rectangle, PixelDatatype, @@ -126,7 +124,7 @@ define([ this.maximumHeight = 0.0; this.boundingSphere3D = new BoundingSphere(); this.boundingSphere2D = new BoundingSphere(); - this.boundingOBB = undefined; + this.orientedBoundingBox = undefined; this.occludeePointInScaledSpace = new Cartesian3(); this.loadedTerrain = undefined; diff --git a/Source/Scene/GlobeSurfaceTileProvider.js b/Source/Scene/GlobeSurfaceTileProvider.js index c7abbd1f5c62..1e59fcf10ae6 100644 --- a/Source/Scene/GlobeSurfaceTileProvider.js +++ b/Source/Scene/GlobeSurfaceTileProvider.js @@ -399,7 +399,7 @@ define([ var cullingVolume = frameState.cullingVolume; - var boundingVolume = defaultValue(surfaceTile.boundingOBB, surfaceTile.boundingSphere3D); + var boundingVolume = defaultValue(surfaceTile.orientedBoundingBox, surfaceTile.boundingSphere3D); if (frameState.mode !== SceneMode.SCENE3D) { boundingVolume = boundingSphereScratch; @@ -897,7 +897,7 @@ define([ command.owner = tile; command.cull = false; command.boundingVolume = new BoundingSphere(); - command.boundingOBB = undefined; + command.orientedBoundingBox = undefined; uniformMap = createTileUniformMap(); @@ -1007,7 +1007,7 @@ define([ } var boundingVolume = command.boundingVolume; - var boundingOBB = command.boundingOBB; + var orientedBoundingBox = command.orientedBoundingBox; if (frameState.mode !== SceneMode.SCENE3D) { BoundingSphere.fromRectangleWithHeights2D(tile.rectangle, frameState.mapProjection, surfaceTile.minimumHeight, surfaceTile.maximumHeight, boundingVolume); @@ -1018,7 +1018,7 @@ define([ } } else { command.boundingVolume = BoundingSphere.clone(surfaceTile.boundingSphere3D, boundingVolume); - command.boundingOBB = OrientedBoundingBox.clone(surfaceTile.boundingOBB, boundingOBB); + command.orientedBoundingBox = OrientedBoundingBox.clone(surfaceTile.orientedBoundingBox, orientedBoundingBox); } commandList.push(command); diff --git a/Source/Scene/Scene.js b/Source/Scene/Scene.js index 268cb47f1842..8ab9cfa4c6ed 100644 --- a/Source/Scene/Scene.js +++ b/Source/Scene/Scene.js @@ -1210,16 +1210,16 @@ define([ var modelMatrix = new Matrix4(); var geometry; - if (defined(command.boundingOBB)) { + if (defined(command.orientedBoundingBox)) { // Assumes bounding volume is an OrientedBoundingBox. - var boundingOBB = command.boundingOBB; + var orientedBoundingBox = command.orientedBoundingBox; geometry = BoxOutlineGeometry.createGeometry(BoxOutlineGeometry.fromDimensions({ dimensions: new Cartesian3(2.0, 2.0, 2.0), vertexFormat: PerInstanceColorAppearance.FLAT_VERTEX_FORMAT })); - Matrix4.fromRotationTranslation(boundingOBB.halfAxes, boundingOBB.center, modelMatrix); + Matrix4.fromRotationTranslation(orientedBoundingBox.halfAxes, orientedBoundingBox.center, modelMatrix); } else if (defined(command.boundingVolume)) { // Assumes bounding volume is a bounding sphere. diff --git a/Source/Scene/TileTerrain.js b/Source/Scene/TileTerrain.js index 117f375318bf..0ba7fce95f97 100644 --- a/Source/Scene/TileTerrain.js +++ b/Source/Scene/TileTerrain.js @@ -80,7 +80,7 @@ define([ surfaceTile.minimumHeight = mesh.minimumHeight; surfaceTile.maximumHeight = mesh.maximumHeight; surfaceTile.boundingSphere3D = BoundingSphere.clone(mesh.boundingSphere3D, surfaceTile.boundingSphere3D); - surfaceTile.boundingOBB = OrientedBoundingBox.clone(mesh.boundingOBB, surfaceTile.boundingOBB); + surfaceTile.orientedBoundingBox = OrientedBoundingBox.clone(mesh.orientedBoundingBox, surfaceTile.orientedBoundingBox); tile.data.occludeePointInScaledSpace = Cartesian3.clone(mesh.occludeePointInScaledSpace, surfaceTile.occludeePointInScaledSpace); diff --git a/Specs/Core/EllipsoidTangentPlaneSpec.js b/Specs/Core/EllipsoidTangentPlaneSpec.js index 915a93309d17..bf88122417c1 100644 --- a/Specs/Core/EllipsoidTangentPlaneSpec.js +++ b/Specs/Core/EllipsoidTangentPlaneSpec.js @@ -38,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); @@ -98,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); @@ -135,25 +135,66 @@ defineSuite([ expect(returnedResults).toEqual(expectedResults); }); - it('extentsToOrientedBoundingBox works with a result parameter', function() { + it('projectPointToNearestOnPlane works without a result parameter', function () { var ellipsoid = Ellipsoid.UNIT_SPHERE; - var origin = Cartesian3.UNIT_X; - var pl = new EllipsoidTangentPlane(origin, ellipsoid); - var result = new OrientedBoundingBox(); - result = pl.extentsToOrientedBoundingBox(-0.5, 0.5, -0.5, 0.5, -0.5, 0.5, result); - var expected = new OrientedBoundingBox(origin, - new Matrix3(0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0, 0.5, 0.0)); - expect(result).toEqual(expected); + 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('extentsToOrientedBoundingBox works without a result parameter', function() { + 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 = Cartesian3.UNIT_X; - var pl = new EllipsoidTangentPlane(origin, ellipsoid); - var result = pl.extentsToOrientedBoundingBox(-0.5, 0.5, -0.5, 0.5, -0.5, 0.5); - var expected = new OrientedBoundingBox(origin, - new Matrix3(0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0, 0.5, 0.0)); - expect(result).toEqual(expected); + 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() { @@ -209,19 +250,6 @@ defineSuite([ }).toThrowDeveloperError(); }); - it('extentsToOrientedBoundingBox throws missing any of the dimensional parameters', function() { - var ellipsoid = Ellipsoid.UNIT_SPHERE; - var origin = new Cartesian3(1.0, 0.0, 0.0); - var pl = new EllipsoidTangentPlane(origin, ellipsoid); - var result = new OrientedBoundingBox(); - expect(function() { pl.extentsToOrientedBoundingBox(undefined, 0.5, -0.5, 0.5, -0.5, 0.5, result); }).toThrowDeveloperError(); - expect(function() { pl.extentsToOrientedBoundingBox(-0.5, undefined, -0.5, 0.5, -0.5, 0.5, result); }).toThrowDeveloperError(); - expect(function() { pl.extentsToOrientedBoundingBox(-0.5, 0.5, undefined, 0.5, -0.5, 0.5, result); }).toThrowDeveloperError(); - expect(function() { pl.extentsToOrientedBoundingBox(-0.5, 0.5, -0.5, undefined, -0.5, 0.5, result); }).toThrowDeveloperError(); - expect(function() { pl.extentsToOrientedBoundingBox(-0.5, 0.5, -0.5, 0.5, undefined, 0.5, result); }).toThrowDeveloperError(); - expect(function() { pl.extentsToOrientedBoundingBox(-0.5, 0.5, -0.5, 0.5, -0.5, undefined, result); }).toThrowDeveloperError(); - }); - it('projectPointsOntoEllipsoid works with an arbitrary ellipsoid using fromPoints', function () { var points = Cartesian3.fromDegreesArray([ -72.0, 40.0, @@ -239,35 +267,4 @@ defineSuite([ expect(positionsBack[0].y).toBeCloseTo(points[0].y); expect(positionsBack[0].z).toBeCloseTo(points[0].z); }); - - it('extentsToOrientedBoundingBox works with some edge-ish cases', function() { - var res, exp; - var result = new OrientedBoundingBox(); - var ellipsoid; - - ellipsoid = Ellipsoid.UNIT_SPHERE; - res = new EllipsoidTangentPlane(Cartesian3.UNIT_X, ellipsoid).extentsToOrientedBoundingBox(-0.3, 0.3, -0.3, 0.3, -0.3, 0.3, res); - exp = new OrientedBoundingBox(Cartesian3.UNIT_X, - new Matrix3(0.0, 0.0, 0.3, 0.3, 0.0, 0.0, 0.0, 0.3, 0.0)); - expect(res.center).toEqualEpsilon(exp.center, CesiumMath.EPSILON15); - expect(res.halfAxes).toEqualEpsilon(exp.halfAxes, CesiumMath.EPSILON15); - - ellipsoid = Ellipsoid.UNIT_SPHERE; - res = new EllipsoidTangentPlane(Cartesian3.UNIT_Z, ellipsoid).extentsToOrientedBoundingBox(-0.3, 0.3, -0.3, 0.3, -0.3, 0.3, res); - expect(res.center).toEqualEpsilon(Cartesian3.UNIT_Z, CesiumMath.EPSILON15); - - ellipsoid = Ellipsoid.UNIT_SPHERE; - res = new EllipsoidTangentPlane(Cartesian3.UNIT_Y, ellipsoid).extentsToOrientedBoundingBox(-0.3, 0.3, -0.3, 0.3, -0.3, 0.3, res); - exp = new OrientedBoundingBox(Cartesian3.UNIT_Y, - new Matrix3(-0.3, 0.0, 0.0, 0.0, 0.0, 0.3, 0.0, 0.3, 0.0)); - expect(res.center).toEqualEpsilon(exp.center, CesiumMath.EPSILON15); - expect(res.halfAxes).toEqualEpsilon(exp.halfAxes, CesiumMath.EPSILON15); - - ellipsoid = Ellipsoid.UNIT_SPHERE; - res = new EllipsoidTangentPlane(Cartesian3.UNIT_X, ellipsoid).extentsToOrientedBoundingBox(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, res); - exp = new OrientedBoundingBox(Cartesian3.UNIT_X, - new Matrix3(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)); - expect(res.center).toEqualEpsilon(exp.center, CesiumMath.EPSILON15); - expect(res.halfAxes).toEqualEpsilon(exp.halfAxes, CesiumMath.EPSILON15); - }); }); diff --git a/Specs/Core/OrientedBoundingBoxSpec.js b/Specs/Core/OrientedBoundingBoxSpec.js index 100ffafb61a7..b41b02238383 100644 --- a/Specs/Core/OrientedBoundingBoxSpec.js +++ b/Specs/Core/OrientedBoundingBoxSpec.js @@ -77,63 +77,32 @@ defineSuite([ expect(box.halfAxes).toEqual(Matrix3.ZERO); }); - it('fromBoundingRectangle throws without rectangle', function() { + it('fromEllipsoidRectangle throws without ellipsoid', function() { + var rectangle = new Rectangle(0.0, 0.0, 0.0, 0.0); expect(function() { - OrientedBoundingBox.fromBoundingRectangle(); + OrientedBoundingBox.fromEllipsoidRectangle(undefined, rectangle, 0.0, 0.0); }).toThrowDeveloperError(); }); - it('fromBoundingRectangle returns zero-size OrientedBoundingBox given a zero-size BoundingRectangle', function() { - var box = OrientedBoundingBox.fromBoundingRectangle(new BoundingRectangle()); - expect(box.center).toEqual(Cartesian3.ZERO); - expect(box.halfAxes).toEqual(Matrix3.ZERO); - }); - - it('fromBoundingRectangle creates an OrientedBoundingBox without a result parameter', function() { - var rect = new BoundingRectangle(1.0, 2.0, 3.0, 4.0); - var angle = CesiumMath.PI_OVER_TWO; - var box = OrientedBoundingBox.fromBoundingRectangle(rect, angle); - var rotation = Matrix3.fromRotationZ(angle); - expect(box.center).toEqual(new Cartesian3(-1.0, 3.5, 0.0)); - - var rotScale = new Matrix3(); - Matrix3.multiply(rotation, Matrix3.fromScale(new Cartesian3(1.5, 2.0, 0.0)), rotScale); - expect(box.halfAxes).toEqualEpsilon(rotScale, CesiumMath.EPSILON15); - }); - - it('fromBoundingRectangle creates an OrientedBoundingBox with a result parameter', function() { - var rect = new BoundingRectangle(1.0, 2.0, 3.0, 4.0); - var angle = CesiumMath.PI_OVER_TWO; - var result = new OrientedBoundingBox(); - var box = OrientedBoundingBox.fromBoundingRectangle(rect, angle, result); - expect(box).toBe(result); - var rotation = Matrix3.fromRotationZ(angle); - - expect(box.center).toEqual(new Cartesian3(-1.0, 3.5, 0.0)); - - var rotScale = new Matrix3(); - Matrix3.multiply(rotation, Matrix3.fromScale(new Cartesian3(1.5, 2.0, 0.0)), rotScale); - expect(box.halfAxes).toEqualEpsilon(rotScale, CesiumMath.EPSILON15); - }); - - it('fromRectangleTangentPlane throws without rectangle', function() { - var pl = new EllipsoidTangentPlane(Cartesian3.UNIT_X, Ellipsoid.UNIT_SPHERE); + it('fromEllipsoidRectangle throws without rectangle', function() { + var ellipsoid = Ellipsoid.UNIT_SPHERE; expect(function() { - return OrientedBoundingBox.fromRectangleTangentPlane(undefined, pl, 0.0, 0.0); + OrientedBoundingBox.fromEllipsoidRectangle(ellipsoid, undefined, 0.0, 0.0); }).toThrowDeveloperError(); }); - it('fromRectangleTangentPlane throws without tangentPlane', function() { - var rect = new BoundingRectangle(1.0, 2.0, 3.0, 4.0); - expect(function() { - return OrientedBoundingBox.fromRectangleTangentPlane(rect, undefined, 0.0, 0.0); - }).toThrowDeveloperError(); + it('fromEllipsoidRectangle throws with invalid rectangles', function() { + var ellipsoid = Ellipsoid.UNIT_SPHERE; + expect(function() { return OrientedBoundingBox.fromEllipsoidRectangle(ellipsoid, new Rectangle(1.0, -1.0, -1.0, 1.0), 0.0, 0.0); }).toThrowDeveloperError(); + expect(function() { return OrientedBoundingBox.fromEllipsoidRectangle(ellipsoid, new Rectangle(-1.0, 1.0, 1.0, -1.0), 0.0, 0.0); }).toThrowDeveloperError(); + expect(function() { return OrientedBoundingBox.fromEllipsoidRectangle(ellipsoid, new Rectangle(-1.0, 1.0, -2.0, 2.0), 0.0, 0.0); }).toThrowDeveloperError(); + expect(function() { return OrientedBoundingBox.fromEllipsoidRectangle(ellipsoid, new Rectangle(-2.0, 2.0, -1.0, 1.0), 0.0, 0.0); }).toThrowDeveloperError(); }); - it('fromRectangleTangentPlane creates an OrientedBoundingBox without a result parameter', function() { - var rect = new Rectangle(0.0, 0.0, 0.0, 0.0); - var pl = new EllipsoidTangentPlane(Cartesian3.UNIT_X, Ellipsoid.UNIT_SPHERE); - var box = OrientedBoundingBox.fromRectangleTangentPlane(rect, pl, 0.0, 0.0); + it('fromEllipsoidRectangle 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.fromEllipsoidRectangle(ellipsoid, rectangle, 0.0, 0.0); expect(box.center).toEqualEpsilon(new Cartesian3(1.0, 0.0, 0.0), CesiumMath.EPSILON15); @@ -141,11 +110,11 @@ defineSuite([ expect(box.halfAxes).toEqualEpsilon(rotScale, CesiumMath.EPSILON15); }); - it('fromRectangleTangentPlane creates an OrientedBoundingBox with a result parameter', function() { - var rect = new Rectangle(0.0, 0.0, 0.0, 0.0); - var pl = new EllipsoidTangentPlane(Cartesian3.UNIT_X, Ellipsoid.UNIT_SPHERE); + it('fromEllipsoidRectangle 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.fromRectangleTangentPlane(rect, pl, 0.0, 0.0, result); + var box = OrientedBoundingBox.fromEllipsoidRectangle(ellipsoid, rectangle, 0.0, 0.0, result); expect(box).toBe(result); expect(box.center).toEqualEpsilon(new Cartesian3(1.0, 0.0, 0.0), CesiumMath.EPSILON15); @@ -154,66 +123,130 @@ defineSuite([ expect(box.halfAxes).toEqualEpsilon(rotScale, CesiumMath.EPSILON15); }); - it('fromRectangleTangentPlane from a degenerate rectangle from (-45, 0) to (45, 0)', function() { + it('fromEllipsoidRectangle for interesting, degenerate, and edge-case rectangles', function() { var d45 = CesiumMath.PI_OVER_FOUR; - var rect = new Rectangle(-d45, 0.0, d45, 0.0); - var pl = new EllipsoidTangentPlane(Cartesian3.UNIT_X, Ellipsoid.UNIT_SPHERE); - var result = new OrientedBoundingBox(); - var box = OrientedBoundingBox.fromRectangleTangentPlane(rect, pl, 0.0, 0.0, result); - expect(box).toBe(result); + var d90 = CesiumMath.PI_OVER_TWO; + var d135 = 3 * CesiumMath.PI_OVER_FOUR; + var d180 = CesiumMath.PI; - expect(box.center).toEqualEpsilon(new Cartesian3((1.0 + Math.SQRT1_2) / 2.0, 0.0, 0.0), CesiumMath.EPSILON15); + var box; - var rotScale = 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); - expect(box.halfAxes).toEqualEpsilon(rotScale, CesiumMath.EPSILON15); - }); + box = OrientedBoundingBox.fromEllipsoidRectangle(Ellipsoid.UNIT_SPHERE,new Rectangle(0.0, 0.0, 0.0, 0.0), 0.0, 0.0); + expect(box.center).toEqualEpsilon(new Cartesian3(1.0, 0.0, 0.0), CesiumMath.EPSILON15); + expect(box.halfAxes).toEqualEpsilon(Matrix3.ZERO, CesiumMath.EPSILON15); - it('fromRectangleTangentPlane from a degenerate rectangle from (135, 0) to (-135, 0)', function() { - var d135 = 3 * CesiumMath.PI_OVER_FOUR; - var rect = new Rectangle(d135, 0.0, -d135, 0.0); - var pl = new EllipsoidTangentPlane(new Cartesian3(-1.0, 0.0, 0.0), Ellipsoid.UNIT_SPHERE); - var result = new OrientedBoundingBox(); - var box = OrientedBoundingBox.fromRectangleTangentPlane(rect, pl, 0.0, 0.0, result); - expect(box).toBe(result); + box = OrientedBoundingBox.fromEllipsoidRectangle(Ellipsoid.UNIT_SPHERE, new Rectangle(d180, 0.0, -d180, 0.0), 0.0, 0.0); + expect(box.center).toEqualEpsilon(new Cartesian3(-1.0, 0.0, 0.0), CesiumMath.EPSILON15); + expect(box.halfAxes).toEqualEpsilon(Matrix3.ZERO, CesiumMath.EPSILON15); - expect(box.center).toEqualEpsilon(new Cartesian3(-(1.0 + Math.SQRT1_2) / 2.0, 0.0, 0.0), CesiumMath.EPSILON15); + box = OrientedBoundingBox.fromEllipsoidRectangle(Ellipsoid.UNIT_SPHERE, new Rectangle(d180, 0.0, d180, 0.0), 0.0, 0.0); + expect(box.center).toEqualEpsilon(new Cartesian3(-1.0, 0.0, 0.0), CesiumMath.EPSILON15); + expect(box.halfAxes).toEqualEpsilon(Matrix3.ZERO, CesiumMath.EPSILON15); - var rotScale = 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); - expect(box.halfAxes).toEqualEpsilon(rotScale, CesiumMath.EPSILON15); - }); + box = OrientedBoundingBox.fromEllipsoidRectangle(Ellipsoid.UNIT_SPHERE, new Rectangle(0.0, d90, 0.0, d90), 0.0, 0.0); + expect(box.center).toEqualEpsilon(new Cartesian3(0.0, 0.0, 1.0), CesiumMath.EPSILON15); + expect(box.halfAxes).toEqualEpsilon(Matrix3.ZERO, CesiumMath.EPSILON15); - it('fromRectangleTangentPlane from a degenerate rectangle from (0, -45) to (0, 45)', function() { - var d45 = CesiumMath.PI_OVER_FOUR; - var rect = new Rectangle(0.0, -d45, 0.0, d45); - var pl = new EllipsoidTangentPlane(Cartesian3.UNIT_X, Ellipsoid.UNIT_SPHERE); - var result = new OrientedBoundingBox(); - var box = OrientedBoundingBox.fromRectangleTangentPlane(rect, pl, 0.0, 0.0, result); - expect(box).toBe(result); + box = OrientedBoundingBox.fromEllipsoidRectangle(Ellipsoid.UNIT_SPHERE, new Rectangle(0.0, 0.0, d180, 0.0), 0.0, 0.0); + 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.fromEllipsoidRectangle(Ellipsoid.UNIT_SPHERE, new Rectangle(-d90, -d90, d90, d90), 0.0, 0.0); + 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.fromEllipsoidRectangle(Ellipsoid.UNIT_SPHERE, new Rectangle(-d90, -d45, d90, d90), 0.0, 0.0); + expect(box.center).toEqualEpsilon(new Cartesian3(0.5, 0.0, 0.5 * (1.0 - Math.SQRT1_2)), CesiumMath.EPSILON15); + expect(box.halfAxes).toEqualEpsilon(new Matrix3(0.0, 0.0, 0.5, 1.0, 0.0, 0.0, 0.0, 0.5 * (1.0 + Math.SQRT1_2), 0.0), CesiumMath.EPSILON15); + + box = OrientedBoundingBox.fromEllipsoidRectangle(Ellipsoid.UNIT_SPHERE, new Rectangle(-d45, 0.0, d45, 0.0), 0.0, 0.0); 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); - var rotScale = 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); - expect(box.halfAxes).toEqualEpsilon(rotScale, CesiumMath.EPSILON15); + box = OrientedBoundingBox.fromEllipsoidRectangle(Ellipsoid.UNIT_SPHERE, new Rectangle(d135, 0.0, -d135, 0.0), 0.0, 0.0); + 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.fromEllipsoidRectangle(Ellipsoid.UNIT_SPHERE, new Rectangle(0.0, -d45, 0.0, d45), 0.0, 0.0); + 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.fromEllipsoidRectangle(Ellipsoid.UNIT_SPHERE, new Rectangle(-d90, 0.0, d90, 0.0), 0.0, 0.0); + 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.fromEllipsoidRectangle(Ellipsoid.UNIT_SPHERE, new Rectangle(0.0, -d90, 0.0, d90), 0.0, 0.0); + 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); }); - it('fromRectangleTangentPlane from a degenerate rectangle from (0, 135) to (0, -135)', function() { - var d135 = 3 * CesiumMath.PI_OVER_FOUR; - var rect = new Rectangle(0.0, d135, 0.0, -d135); - var pl = new EllipsoidTangentPlane(new Cartesian3(-1.0, 0.0, 0.0), Ellipsoid.UNIT_SPHERE); + /* + it('extentsToOrientedBoundingBox works with a result parameter', function() { + var ellipsoid = Ellipsoid.UNIT_SPHERE; + var origin = Cartesian3.UNIT_X; + var pl = new EllipsoidTangentPlane(origin, ellipsoid); var result = new OrientedBoundingBox(); - var box = OrientedBoundingBox.fromRectangleTangentPlane(rect, pl, 0.0, 0.0, result); - expect(box).toBe(result); + result = pl.extentsToOrientedBoundingBox(-0.5, 0.5, -0.5, 0.5, -0.5, 0.5, result); + var expected = new OrientedBoundingBox(origin, + new Matrix3(0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0, 0.5, 0.0)); + expect(result).toEqual(expected); + }); - expect(box.center).toEqualEpsilon(new Cartesian3(-(1.0 + Math.SQRT1_2) / 2.0, 0.0, 0.0), CesiumMath.EPSILON15); + it('extentsToOrientedBoundingBox works without a result parameter', function() { + var ellipsoid = Ellipsoid.UNIT_SPHERE; + var origin = Cartesian3.UNIT_X; + var pl = new EllipsoidTangentPlane(origin, ellipsoid); + var result = pl.extentsToOrientedBoundingBox(-0.5, 0.5, -0.5, 0.5, -0.5, 0.5); + var expected = new OrientedBoundingBox(origin, + new Matrix3(0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0, 0.5, 0.0)); + expect(result).toEqual(expected); + }); - var rotScale = 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); - expect(box.halfAxes).toEqualEpsilon(rotScale, CesiumMath.EPSILON15); + it('extentsToOrientedBoundingBox throws missing any of the dimensional parameters', function() { + var ellipsoid = Ellipsoid.UNIT_SPHERE; + var origin = new Cartesian3(1.0, 0.0, 0.0); + var pl = new EllipsoidTangentPlane(origin, ellipsoid); + var result = new OrientedBoundingBox(); + expect(function() { pl.extentsToOrientedBoundingBox(undefined, 0.5, -0.5, 0.5, -0.5, 0.5, result); }).toThrowDeveloperError(); + expect(function() { pl.extentsToOrientedBoundingBox(-0.5, undefined, -0.5, 0.5, -0.5, 0.5, result); }).toThrowDeveloperError(); + expect(function() { pl.extentsToOrientedBoundingBox(-0.5, 0.5, undefined, 0.5, -0.5, 0.5, result); }).toThrowDeveloperError(); + expect(function() { pl.extentsToOrientedBoundingBox(-0.5, 0.5, -0.5, undefined, -0.5, 0.5, result); }).toThrowDeveloperError(); + expect(function() { pl.extentsToOrientedBoundingBox(-0.5, 0.5, -0.5, 0.5, undefined, 0.5, result); }).toThrowDeveloperError(); + expect(function() { pl.extentsToOrientedBoundingBox(-0.5, 0.5, -0.5, 0.5, -0.5, undefined, result); }).toThrowDeveloperError(); }); - /** - * @param {Cartesian3} center - * @param {Matrix3} axes - */ + it('extentsToOrientedBoundingBox works with some edge-ish cases', function() { + var res, exp; + var result = new OrientedBoundingBox(); + var ellipsoid; + + ellipsoid = Ellipsoid.UNIT_SPHERE; + res = new EllipsoidTangentPlane(Cartesian3.UNIT_X, ellipsoid).extentsToOrientedBoundingBox(-0.3, 0.3, -0.3, 0.3, -0.3, 0.3, res); + exp = new OrientedBoundingBox(Cartesian3.UNIT_X, + new Matrix3(0.0, 0.0, 0.3, 0.3, 0.0, 0.0, 0.0, 0.3, 0.0)); + expect(res.center).toEqualEpsilon(exp.center, CesiumMath.EPSILON15); + expect(res.halfAxes).toEqualEpsilon(exp.halfAxes, CesiumMath.EPSILON15); + + ellipsoid = Ellipsoid.UNIT_SPHERE; + res = new EllipsoidTangentPlane(Cartesian3.UNIT_Z, ellipsoid).extentsToOrientedBoundingBox(-0.3, 0.3, -0.3, 0.3, -0.3, 0.3, res); + expect(res.center).toEqualEpsilon(Cartesian3.UNIT_Z, CesiumMath.EPSILON15); + + ellipsoid = Ellipsoid.UNIT_SPHERE; + res = new EllipsoidTangentPlane(Cartesian3.UNIT_Y, ellipsoid).extentsToOrientedBoundingBox(-0.3, 0.3, -0.3, 0.3, -0.3, 0.3, res); + exp = new OrientedBoundingBox(Cartesian3.UNIT_Y, + new Matrix3(-0.3, 0.0, 0.0, 0.0, 0.0, 0.3, 0.0, 0.3, 0.0)); + expect(res.center).toEqualEpsilon(exp.center, CesiumMath.EPSILON15); + expect(res.halfAxes).toEqualEpsilon(exp.halfAxes, CesiumMath.EPSILON15); + + ellipsoid = Ellipsoid.UNIT_SPHERE; + res = new EllipsoidTangentPlane(Cartesian3.UNIT_X, ellipsoid).extentsToOrientedBoundingBox(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, res); + exp = new OrientedBoundingBox(Cartesian3.UNIT_X, + new Matrix3(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)); + expect(res.center).toEqualEpsilon(exp.center, CesiumMath.EPSILON15); + expect(res.halfAxes).toEqualEpsilon(exp.halfAxes, CesiumMath.EPSILON15); + }); + */ + var intersectPlaneTestCornersEdgesFaces = function(center, axes) { var SQRT1_2 = Math.pow(1.0 / 2.0, 1 / 2.0); var SQRT1_3 = Math.pow(1.0 / 3.0, 1 / 2.0); @@ -248,7 +281,6 @@ defineSuite([ var pl; - // Tests against faces pl = planeNormXform(+1.0, +0.0, +0.0, 0.50001); if (pl) { expect(box.intersectPlane(pl)).toEqual(Intersect.INSIDE); } @@ -279,7 +311,6 @@ defineSuite([ 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); } @@ -334,7 +365,6 @@ defineSuite([ 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); } From 931aa625eb876cefba5dbcfec5a6f7fff085a55f Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Fri, 12 Jun 2015 21:52:09 -0400 Subject: [PATCH 11/28] Move terrain bounding volume debug draw from Scene.js to GlobeSurfaceTileProvider.js --- Source/Scene/GlobeSurfaceTileProvider.js | 58 ++++++++++++- Source/Scene/Scene.js | 104 +++++++++-------------- 2 files changed, 97 insertions(+), 65 deletions(-) diff --git a/Source/Scene/GlobeSurfaceTileProvider.js b/Source/Scene/GlobeSurfaceTileProvider.js index 1e59fcf10ae6..837666c27322 100644 --- a/Source/Scene/GlobeSurfaceTileProvider.js +++ b/Source/Scene/GlobeSurfaceTileProvider.js @@ -1,10 +1,12 @@ /*global define*/ define([ '../Core/BoundingSphere', + '../Core/BoxOutlineGeometry', '../Core/Cartesian2', '../Core/Cartesian3', '../Core/Cartesian4', '../Core/Color', + '../Core/ColorGeometryInstanceAttribute', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', @@ -12,6 +14,7 @@ define([ '../Core/DeveloperError', '../Core/Event', '../Core/FeatureDetection', + '../Core/GeometryInstance', '../Core/GeometryPipeline', '../Core/IndexDatatype', '../Core/Intersect', @@ -19,6 +22,7 @@ define([ '../Core/OrientedBoundingBox', '../Core/PrimitiveType', '../Core/Rectangle', + '../Core/SphereOutlineGeometry', '../Core/Visibility', '../Core/WebMercatorProjection', '../Renderer/BufferUsage', @@ -26,6 +30,8 @@ define([ '../Scene/BlendingState', '../Scene/DepthFunction', '../Scene/Pass', + '../Scene/PerInstanceColorAppearance', + '../Scene/Primitive', '../ThirdParty/when', './GlobeSurfaceTile', './ImageryLayer', @@ -34,10 +40,12 @@ define([ './SceneMode' ], function( BoundingSphere, + BoxOutlineGeometry, Cartesian2, Cartesian3, Cartesian4, Color, + ColorGeometryInstanceAttribute, defaultValue, defined, defineProperties, @@ -45,6 +53,7 @@ define([ DeveloperError, Event, FeatureDetection, + GeometryInstance, GeometryPipeline, IndexDatatype, Intersect, @@ -52,6 +61,7 @@ define([ OrientedBoundingBox, PrimitiveType, Rectangle, + SphereOutlineGeometry, Visibility, WebMercatorProjection, BufferUsage, @@ -59,6 +69,8 @@ define([ BlendingState, DepthFunction, Pass, + PerInstanceColorAppearance, + Primitive, when, GlobeSurfaceTile, ImageryLayer, @@ -798,6 +810,44 @@ define([ return context.createVertexArray(vertexArray._attributes, wireframeIndexBuffer); } + function createDebugPrimitive(geometry, color, modelMatrix) { + var instance = new GeometryInstance({ + geometry : geometry, + modelMatrix : modelMatrix, + attributes : { + color : ColorGeometryInstanceAttribute.fromColor(color) + } + }); + + return new Primitive({ + geometryInstances : instance, + appearance : new PerInstanceColorAppearance({ + translucent : false, + flat : true + }), + asynchronous : false + }); + } + + var scratchBVMatrix = new Matrix4(); + var debugOrientedBoundingBox = BoxOutlineGeometry.fromDimensions({ + dimensions: new Cartesian3(2.0, 2.0, 2.0), + vertexFormat: PerInstanceColorAppearance.FLAT_VERTEX_FORMAT + }); + var createDebugOrientedBoundingBox = function(obb, color) { + var modelMatrix = Matrix4.fromRotationTranslation(obb.halfAxes, obb.center, scratchBVMatrix); + return createDebugPrimitive(debugOrientedBoundingBox, color, modelMatrix); + }; + + var debugBoundingSphere = new SphereOutlineGeometry({ + radius: 1.0 + }); + var createDebugSphere = function(sphere, color) { + var modelMatrix = Matrix4.fromTranslation(sphere.center); + Matrix4.multiplyByUniformScale(modelMatrix, sphere.radius, modelMatrix); + return createDebugPrimitive(debugBoundingSphere, color, modelMatrix); + }; + var otherPassesInitialColor = new Cartesian4(0.0, 0.0, 0.0, 0.0); function addDrawCommandsForTile(tileProvider, tile, context, frameState, commandList) { @@ -912,7 +962,13 @@ define([ ++tileProvider._usedDrawCommands; - command.debugShowBoundingVolume = (tile === tileProvider._debug.boundingSphereTile); + if (tile === tileProvider._debug.boundingSphereTile) { + if (defined(surfaceTile.orientedBoundingBox)) { + createDebugOrientedBoundingBox(surfaceTile.orientedBoundingBox, Color.RED).update(context, frameState, commandList); + } else if (defined(surfaceTile.boundingSphere3D)) { + createDebugSphere(surfaceTile.boundingSphere3D, Color.RED).update(context, frameState, commandList); + } + } Cartesian4.clone(initialColor, uniformMap.initialColor); uniformMap.oceanNormalMap = oceanNormalMap; diff --git a/Source/Scene/Scene.js b/Source/Scene/Scene.js index 8ab9cfa4c6ed..f92ed359b657 100644 --- a/Source/Scene/Scene.js +++ b/Source/Scene/Scene.js @@ -2,7 +2,6 @@ define([ '../Core/BoundingRectangle', '../Core/BoundingSphere', - '../Core/BoxOutlineGeometry', '../Core/Cartesian2', '../Core/Cartesian3', '../Core/Cartesian4', @@ -58,7 +57,6 @@ define([ ], function( BoundingRectangle, BoundingSphere, - BoxOutlineGeometry, Cartesian2, Cartesian3, Cartesian4, @@ -1198,80 +1196,58 @@ define([ command.execute(context, passState, renderState, shaderProgram); } - if (command.debugShowBoundingVolume) { + if (command.debugShowBoundingVolume && (defined(command.boundingVolume))) { // Debug code to draw bounding volume for command. Not optimized! - + // Assumes bounding volume is a bounding sphere. if (defined(scene._debugSphere)) { scene._debugSphere.destroy(); } var frameState = scene._frameState; + var boundingVolume = command.boundingVolume; + var radius = boundingVolume.radius; + var center = boundingVolume.center; + + var geometry = GeometryPipeline.toWireframe(EllipsoidGeometry.createGeometry(new EllipsoidGeometry({ + radii : new Cartesian3(radius, radius, radius), + vertexFormat : PerInstanceColorAppearance.FLAT_VERTEX_FORMAT + }))); + + if (frameState.mode !== SceneMode.SCENE3D) { + center = Matrix4.multiplyByPoint(transformFrom2D, center, center); + var projection = frameState.mapProjection; + var centerCartographic = projection.unproject(center); + center = projection.ellipsoid.cartographicToCartesian(centerCartographic); + } - var modelMatrix = new Matrix4(); - - var geometry; - if (defined(command.orientedBoundingBox)) { - // Assumes bounding volume is an OrientedBoundingBox. - var orientedBoundingBox = command.orientedBoundingBox; - - geometry = BoxOutlineGeometry.createGeometry(BoxOutlineGeometry.fromDimensions({ - dimensions: new Cartesian3(2.0, 2.0, 2.0), - vertexFormat: PerInstanceColorAppearance.FLAT_VERTEX_FORMAT - })); + scene._debugSphere = new Primitive({ + geometryInstances : new GeometryInstance({ + geometry : geometry, + modelMatrix : Matrix4.multiplyByTranslation(Matrix4.IDENTITY, center, new Matrix4()), + attributes : { + color : new ColorGeometryInstanceAttribute(1.0, 0.0, 0.0, 1.0) + } + }), + appearance : new PerInstanceColorAppearance({ + flat : true, + translucent : false + }), + asynchronous : false + }); - Matrix4.fromRotationTranslation(orientedBoundingBox.halfAxes, orientedBoundingBox.center, modelMatrix); - } else if (defined(command.boundingVolume)) { - // Assumes bounding volume is a bounding sphere. + var commandList = []; + scene._debugSphere.update(context, frameState, commandList); - var boundingVolume = command.boundingVolume; - var radius = boundingVolume.radius; - var center = boundingVolume.center; - - geometry = GeometryPipeline.toWireframe(EllipsoidGeometry.createGeometry(new EllipsoidGeometry({ - radii: new Cartesian3(radius, radius, radius), - vertexFormat: PerInstanceColorAppearance.FLAT_VERTEX_FORMAT - }))); - - if (frameState.mode !== SceneMode.SCENE3D) { - center = Matrix4.multiplyByPoint(transformFrom2D, center, center); - var projection = frameState.mapProjection; - var centerCartographic = projection.unproject(center); - center = projection.ellipsoid.cartographicToCartesian(centerCartographic); - } - - Matrix4.multiplyByTranslation(Matrix4.IDENTITY, center, modelMatrix); + var framebuffer; + if (defined(debugFramebuffer)) { + framebuffer = passState.framebuffer; + passState.framebuffer = debugFramebuffer; } - if (defined(geometry)) { - scene._debugSphere = new Primitive({ - geometryInstances: new GeometryInstance({ - geometry: geometry, - modelMatrix: modelMatrix, - attributes: { - color: new ColorGeometryInstanceAttribute(1.0, 0.0, 0.0, 1.0) - } - }), - appearance: new PerInstanceColorAppearance({ - flat: true, - translucent: false - }), - asynchronous: false - }); - - var commandList = []; - scene._debugSphere.update(context, frameState, commandList); - - var framebuffer; - if (defined(debugFramebuffer)) { - framebuffer = passState.framebuffer; - passState.framebuffer = debugFramebuffer; - } + commandList[0].execute(context, passState); - commandList[0].execute(context, passState); - - if (defined(framebuffer)) { - passState.framebuffer = framebuffer; - } + if (defined(framebuffer)) { + passState.framebuffer = framebuffer; } } } From d46b5266ddddc0199e259d7c4d19e6ca5c2b6d4d Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Fri, 12 Jun 2015 22:12:28 -0400 Subject: [PATCH 12/28] Additional OrientedBoundingBox.fromEllipsoidRectangle test --- Specs/Core/OrientedBoundingBoxSpec.js | 78 ++++----------------------- 1 file changed, 11 insertions(+), 67 deletions(-) diff --git a/Specs/Core/OrientedBoundingBoxSpec.js b/Specs/Core/OrientedBoundingBoxSpec.js index b41b02238383..7175ece5e4ad 100644 --- a/Specs/Core/OrientedBoundingBoxSpec.js +++ b/Specs/Core/OrientedBoundingBoxSpec.js @@ -77,6 +77,17 @@ defineSuite([ expect(box.halfAxes).toEqual(Matrix3.ZERO); }); + it('fromEllipsoidRectangle sets correct default heights', function() { + var ellipsoid = Ellipsoid.UNIT_SPHERE; + var rectangle = new Rectangle(0.0, 0.0, 0.0, 0.0); + var box = OrientedBoundingBox.fromEllipsoidRectangle(ellipsoid, rectangle); + + 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('fromEllipsoidRectangle throws without ellipsoid', function() { var rectangle = new Rectangle(0.0, 0.0, 0.0, 0.0); expect(function() { @@ -180,73 +191,6 @@ defineSuite([ 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); }); - /* - it('extentsToOrientedBoundingBox works with a result parameter', function() { - var ellipsoid = Ellipsoid.UNIT_SPHERE; - var origin = Cartesian3.UNIT_X; - var pl = new EllipsoidTangentPlane(origin, ellipsoid); - var result = new OrientedBoundingBox(); - result = pl.extentsToOrientedBoundingBox(-0.5, 0.5, -0.5, 0.5, -0.5, 0.5, result); - var expected = new OrientedBoundingBox(origin, - new Matrix3(0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0, 0.5, 0.0)); - expect(result).toEqual(expected); - }); - - it('extentsToOrientedBoundingBox works without a result parameter', function() { - var ellipsoid = Ellipsoid.UNIT_SPHERE; - var origin = Cartesian3.UNIT_X; - var pl = new EllipsoidTangentPlane(origin, ellipsoid); - var result = pl.extentsToOrientedBoundingBox(-0.5, 0.5, -0.5, 0.5, -0.5, 0.5); - var expected = new OrientedBoundingBox(origin, - new Matrix3(0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0, 0.5, 0.0)); - expect(result).toEqual(expected); - }); - - it('extentsToOrientedBoundingBox throws missing any of the dimensional parameters', function() { - var ellipsoid = Ellipsoid.UNIT_SPHERE; - var origin = new Cartesian3(1.0, 0.0, 0.0); - var pl = new EllipsoidTangentPlane(origin, ellipsoid); - var result = new OrientedBoundingBox(); - expect(function() { pl.extentsToOrientedBoundingBox(undefined, 0.5, -0.5, 0.5, -0.5, 0.5, result); }).toThrowDeveloperError(); - expect(function() { pl.extentsToOrientedBoundingBox(-0.5, undefined, -0.5, 0.5, -0.5, 0.5, result); }).toThrowDeveloperError(); - expect(function() { pl.extentsToOrientedBoundingBox(-0.5, 0.5, undefined, 0.5, -0.5, 0.5, result); }).toThrowDeveloperError(); - expect(function() { pl.extentsToOrientedBoundingBox(-0.5, 0.5, -0.5, undefined, -0.5, 0.5, result); }).toThrowDeveloperError(); - expect(function() { pl.extentsToOrientedBoundingBox(-0.5, 0.5, -0.5, 0.5, undefined, 0.5, result); }).toThrowDeveloperError(); - expect(function() { pl.extentsToOrientedBoundingBox(-0.5, 0.5, -0.5, 0.5, -0.5, undefined, result); }).toThrowDeveloperError(); - }); - - it('extentsToOrientedBoundingBox works with some edge-ish cases', function() { - var res, exp; - var result = new OrientedBoundingBox(); - var ellipsoid; - - ellipsoid = Ellipsoid.UNIT_SPHERE; - res = new EllipsoidTangentPlane(Cartesian3.UNIT_X, ellipsoid).extentsToOrientedBoundingBox(-0.3, 0.3, -0.3, 0.3, -0.3, 0.3, res); - exp = new OrientedBoundingBox(Cartesian3.UNIT_X, - new Matrix3(0.0, 0.0, 0.3, 0.3, 0.0, 0.0, 0.0, 0.3, 0.0)); - expect(res.center).toEqualEpsilon(exp.center, CesiumMath.EPSILON15); - expect(res.halfAxes).toEqualEpsilon(exp.halfAxes, CesiumMath.EPSILON15); - - ellipsoid = Ellipsoid.UNIT_SPHERE; - res = new EllipsoidTangentPlane(Cartesian3.UNIT_Z, ellipsoid).extentsToOrientedBoundingBox(-0.3, 0.3, -0.3, 0.3, -0.3, 0.3, res); - expect(res.center).toEqualEpsilon(Cartesian3.UNIT_Z, CesiumMath.EPSILON15); - - ellipsoid = Ellipsoid.UNIT_SPHERE; - res = new EllipsoidTangentPlane(Cartesian3.UNIT_Y, ellipsoid).extentsToOrientedBoundingBox(-0.3, 0.3, -0.3, 0.3, -0.3, 0.3, res); - exp = new OrientedBoundingBox(Cartesian3.UNIT_Y, - new Matrix3(-0.3, 0.0, 0.0, 0.0, 0.0, 0.3, 0.0, 0.3, 0.0)); - expect(res.center).toEqualEpsilon(exp.center, CesiumMath.EPSILON15); - expect(res.halfAxes).toEqualEpsilon(exp.halfAxes, CesiumMath.EPSILON15); - - ellipsoid = Ellipsoid.UNIT_SPHERE; - res = new EllipsoidTangentPlane(Cartesian3.UNIT_X, ellipsoid).extentsToOrientedBoundingBox(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, res); - exp = new OrientedBoundingBox(Cartesian3.UNIT_X, - new Matrix3(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)); - expect(res.center).toEqualEpsilon(exp.center, CesiumMath.EPSILON15); - expect(res.halfAxes).toEqualEpsilon(exp.halfAxes, CesiumMath.EPSILON15); - }); - */ - var intersectPlaneTestCornersEdgesFaces = function(center, axes) { var SQRT1_2 = Math.pow(1.0 / 2.0, 1 / 2.0); var SQRT1_3 = Math.pow(1.0 / 3.0, 1 / 2.0); From 4408e9fa15c3020f8ad16c3357486cecc2def41a Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Mon, 15 Jun 2015 11:14:23 -0400 Subject: [PATCH 13/28] fromEllipsoidRectangle fixup --- Source/Core/OrientedBoundingBox.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/Core/OrientedBoundingBox.js b/Source/Core/OrientedBoundingBox.js index e389f8d4e60f..d3fa8766d48f 100644 --- a/Source/Core/OrientedBoundingBox.js +++ b/Source/Core/OrientedBoundingBox.js @@ -173,6 +173,8 @@ define([ // W/-x [7] [3] E/+x // [6] [5] [4] // S/-y + // "C" refers to the central lat/long which always align with the tangent point (above). + // If the rectangle spans the equator, CW and CE are aligned with the equator; otherwise, they're centered in latitude. var perimeterNW = perimeterCartographicScratch[0]; var perimeterNC = perimeterCartographicScratch[1]; var perimeterNE = perimeterCartographicScratch[2]; @@ -206,9 +208,9 @@ define([ 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[1]), Plane.getPointDistance(plane, perimeterCartesianScratch[2]), - Plane.getPointDistance(plane, perimeterCartesianScratch[3])); + 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); From bc1c9e667c6f8528bf300f827998b751fab3fa75 Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Mon, 15 Jun 2015 14:13:49 -0400 Subject: [PATCH 14/28] Rename fromEllipsoidRectangle to fromRectangle and reorder arguments --- Source/Core/CesiumTerrainProvider.js | 2 +- Source/Core/OrientedBoundingBox.js | 8 ++- Specs/Core/OrientedBoundingBoxSpec.js | 71 ++++++++++++++------------- 3 files changed, 41 insertions(+), 40 deletions(-) diff --git a/Source/Core/CesiumTerrainProvider.js b/Source/Core/CesiumTerrainProvider.js index 55f4c166674d..fb4cdddcf01f 100644 --- a/Source/Core/CesiumTerrainProvider.js +++ b/Source/Core/CesiumTerrainProvider.js @@ -428,7 +428,7 @@ define([ 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) - orientedBoundingBox = OrientedBoundingBox.fromEllipsoidRectangle(provider._tilingScheme.ellipsoid, rectangle, minimumHeight, maximumHeight); + orientedBoundingBox = OrientedBoundingBox.fromRectangle(rectangle, minimumHeight, maximumHeight, provider._tilingScheme.ellipsoid); } return new QuantizedMeshTerrainData({ diff --git a/Source/Core/OrientedBoundingBox.js b/Source/Core/OrientedBoundingBox.js index d3fa8766d48f..8c4bbc92c46b 100644 --- a/Source/Core/OrientedBoundingBox.js +++ b/Source/Core/OrientedBoundingBox.js @@ -127,21 +127,18 @@ define([ * 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 {Ellipsoid} ellipsoid The ellipsoid on which the rectangle is defined. * @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. */ - OrientedBoundingBox.fromEllipsoidRectangle = function(ellipsoid, rectangle, minimumHeight, maximumHeight, result) { + OrientedBoundingBox.fromRectangle = function(rectangle, minimumHeight, maximumHeight, ellipsoid, result) { //>>includeStart('debug', pragmas.debug); - if (!defined(ellipsoid)) { - throw new DeveloperError('ellipsoid is required'); - } if (!defined(rectangle)) { throw new DeveloperError('rectangle is required'); } @@ -155,6 +152,7 @@ define([ minimumHeight = defaultValue(minimumHeight, 0.0); maximumHeight = defaultValue(maximumHeight, 0.0); + ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); // If the rectangle does not span the equator, then the bounding box will be aligned with the tangent plane at the center of the rectangle. // If the rectangle does span the equator, then the bounding box will be aligned with the tangent plane to the equator at the longitudinal center of the rectangle. diff --git a/Specs/Core/OrientedBoundingBoxSpec.js b/Specs/Core/OrientedBoundingBoxSpec.js index 7175ece5e4ad..95ec4fd979c2 100644 --- a/Specs/Core/OrientedBoundingBoxSpec.js +++ b/Specs/Core/OrientedBoundingBoxSpec.js @@ -77,43 +77,46 @@ defineSuite([ expect(box.halfAxes).toEqual(Matrix3.ZERO); }); - it('fromEllipsoidRectangle sets correct default heights', function() { - var ellipsoid = Ellipsoid.UNIT_SPHERE; - var rectangle = new Rectangle(0.0, 0.0, 0.0, 0.0); - var box = OrientedBoundingBox.fromEllipsoidRectangle(ellipsoid, rectangle); + 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(box.center).toEqualEpsilon(new Cartesian3(1.0, 0.0, 0.0), CesiumMath.EPSILON15); + expect(box1.center).toEqualEpsilon(box2.center, CesiumMath.EPSILON15); var rotScale = Matrix3.ZERO; - expect(box.halfAxes).toEqualEpsilon(rotScale, CesiumMath.EPSILON15); + expect(box1.halfAxes).toEqualEpsilon(box2.halfAxes, CesiumMath.EPSILON15); }); - it('fromEllipsoidRectangle throws without ellipsoid', function() { + it('fromRectangle sets correct default heights', function() { var rectangle = new Rectangle(0.0, 0.0, 0.0, 0.0); - expect(function() { - OrientedBoundingBox.fromEllipsoidRectangle(undefined, rectangle, 0.0, 0.0); - }).toThrowDeveloperError(); + 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('fromEllipsoidRectangle throws without rectangle', function() { + it('fromRectangle throws without rectangle', function() { var ellipsoid = Ellipsoid.UNIT_SPHERE; expect(function() { - OrientedBoundingBox.fromEllipsoidRectangle(ellipsoid, undefined, 0.0, 0.0); + OrientedBoundingBox.fromRectangle(undefined, 0.0, 0.0, ellipsoid); }).toThrowDeveloperError(); }); - it('fromEllipsoidRectangle throws with invalid rectangles', function() { + it('fromRectangle throws with invalid rectangles', function() { var ellipsoid = Ellipsoid.UNIT_SPHERE; - expect(function() { return OrientedBoundingBox.fromEllipsoidRectangle(ellipsoid, new Rectangle(1.0, -1.0, -1.0, 1.0), 0.0, 0.0); }).toThrowDeveloperError(); - expect(function() { return OrientedBoundingBox.fromEllipsoidRectangle(ellipsoid, new Rectangle(-1.0, 1.0, 1.0, -1.0), 0.0, 0.0); }).toThrowDeveloperError(); - expect(function() { return OrientedBoundingBox.fromEllipsoidRectangle(ellipsoid, new Rectangle(-1.0, 1.0, -2.0, 2.0), 0.0, 0.0); }).toThrowDeveloperError(); - expect(function() { return OrientedBoundingBox.fromEllipsoidRectangle(ellipsoid, new Rectangle(-2.0, 2.0, -1.0, 1.0), 0.0, 0.0); }).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, 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(); }); - it('fromEllipsoidRectangle creates an OrientedBoundingBox without a result parameter', function() { + 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.fromEllipsoidRectangle(ellipsoid, rectangle, 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); @@ -121,11 +124,11 @@ defineSuite([ expect(box.halfAxes).toEqualEpsilon(rotScale, CesiumMath.EPSILON15); }); - it('fromEllipsoidRectangle creates an OrientedBoundingBox with a result parameter', function() { + 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.fromEllipsoidRectangle(ellipsoid, rectangle, 0.0, 0.0, result); + 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); @@ -134,7 +137,7 @@ defineSuite([ expect(box.halfAxes).toEqualEpsilon(rotScale, CesiumMath.EPSILON15); }); - it('fromEllipsoidRectangle for interesting, degenerate, and edge-case rectangles', function() { + it('fromRectangle for interesting, degenerate, and edge-case rectangles', function() { var d45 = CesiumMath.PI_OVER_FOUR; var d90 = CesiumMath.PI_OVER_TWO; var d135 = 3 * CesiumMath.PI_OVER_FOUR; @@ -142,51 +145,51 @@ defineSuite([ var box; - box = OrientedBoundingBox.fromEllipsoidRectangle(Ellipsoid.UNIT_SPHERE,new Rectangle(0.0, 0.0, 0.0, 0.0), 0.0, 0.0); + 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.fromEllipsoidRectangle(Ellipsoid.UNIT_SPHERE, new Rectangle(d180, 0.0, -d180, 0.0), 0.0, 0.0); + 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.fromEllipsoidRectangle(Ellipsoid.UNIT_SPHERE, new Rectangle(d180, 0.0, d180, 0.0), 0.0, 0.0); + 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.fromEllipsoidRectangle(Ellipsoid.UNIT_SPHERE, new Rectangle(0.0, d90, 0.0, d90), 0.0, 0.0); + 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.fromEllipsoidRectangle(Ellipsoid.UNIT_SPHERE, new Rectangle(0.0, 0.0, d180, 0.0), 0.0, 0.0); + 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.fromEllipsoidRectangle(Ellipsoid.UNIT_SPHERE, new Rectangle(-d90, -d90, d90, d90), 0.0, 0.0); + 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.fromEllipsoidRectangle(Ellipsoid.UNIT_SPHERE, new Rectangle(-d90, -d45, d90, d90), 0.0, 0.0); + box = OrientedBoundingBox.fromRectangle(new Rectangle(-d90, -d45, d90, d90), 0.0, 0.0, Ellipsoid.UNIT_SPHERE); expect(box.center).toEqualEpsilon(new Cartesian3(0.5, 0.0, 0.5 * (1.0 - Math.SQRT1_2)), CesiumMath.EPSILON15); expect(box.halfAxes).toEqualEpsilon(new Matrix3(0.0, 0.0, 0.5, 1.0, 0.0, 0.0, 0.0, 0.5 * (1.0 + Math.SQRT1_2), 0.0), CesiumMath.EPSILON15); - box = OrientedBoundingBox.fromEllipsoidRectangle(Ellipsoid.UNIT_SPHERE, new Rectangle(-d45, 0.0, d45, 0.0), 0.0, 0.0); + 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.fromEllipsoidRectangle(Ellipsoid.UNIT_SPHERE, new Rectangle(d135, 0.0, -d135, 0.0), 0.0, 0.0); + 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.fromEllipsoidRectangle(Ellipsoid.UNIT_SPHERE, new Rectangle(0.0, -d45, 0.0, d45), 0.0, 0.0); + 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.fromEllipsoidRectangle(Ellipsoid.UNIT_SPHERE, new Rectangle(-d90, 0.0, d90, 0.0), 0.0, 0.0); + 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.fromEllipsoidRectangle(Ellipsoid.UNIT_SPHERE, new Rectangle(0.0, -d90, 0.0, d90), 0.0, 0.0); + 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); }); From c1c6a666bfb5bf947ef53b43c3bc6d43e1f83f52 Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Mon, 15 Jun 2015 14:14:54 -0400 Subject: [PATCH 15/28] OBB intersectPlane: lower-level computation for perf --- Source/Core/OrientedBoundingBox.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Source/Core/OrientedBoundingBox.js b/Source/Core/OrientedBoundingBox.js index 8c4bbc92c46b..feda605bb7cf 100644 --- a/Source/Core/OrientedBoundingBox.js +++ b/Source/Core/OrientedBoundingBox.js @@ -262,10 +262,11 @@ define([ var center = box.center; var normal = plane.normal; + var halfAxes = box.halfAxes; // plane is used as if it is its normal; the first three components are assumed to be normalized - var radEffective = Math.abs(Cartesian3.dot(normal, Matrix3.getColumn(box.halfAxes, 0, scratchCartesian1))) + - Math.abs(Cartesian3.dot(normal, Matrix3.getColumn(box.halfAxes, 1, scratchCartesian2))) + - Math.abs(Cartesian3.dot(normal, Matrix3.getColumn(box.halfAxes, 2, scratchCartesian3))); + var radEffective = Math.abs(normal.x * halfAxes[Matrix3.COLUMN0ROW0] + normal.y * halfAxes[Matrix3.COLUMN0ROW1] + normal.z * halfAxes[Matrix3.COLUMN0ROW2]) + + Math.abs(normal.x * halfAxes[Matrix3.COLUMN1ROW0] + normal.y * halfAxes[Matrix3.COLUMN1ROW1] + normal.z * halfAxes[Matrix3.COLUMN1ROW2]) + + Math.abs(normal.x * halfAxes[Matrix3.COLUMN2ROW0] + normal.y * halfAxes[Matrix3.COLUMN2ROW1] + normal.z * halfAxes[Matrix3.COLUMN2ROW2]); var distanceToPlane = Cartesian3.dot(normal, center) + plane.distance; if (distanceToPlane <= -radEffective) { From 1c23c4b58ef5f9a0cdb86285ad494408a058e510 Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Mon, 15 Jun 2015 14:50:08 -0400 Subject: [PATCH 16/28] OBB fromRectangle: more invalid rectangle tests --- Specs/Core/OrientedBoundingBoxSpec.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Specs/Core/OrientedBoundingBoxSpec.js b/Specs/Core/OrientedBoundingBoxSpec.js index 95ec4fd979c2..8d3b9c45366c 100644 --- a/Specs/Core/OrientedBoundingBoxSpec.js +++ b/Specs/Core/OrientedBoundingBoxSpec.js @@ -111,6 +111,10 @@ defineSuite([ 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 creates an OrientedBoundingBox without a result parameter', function() { From fc0ab82ca0212850068327854c06dc05efe976d0 Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Tue, 16 Jun 2015 16:37:24 -0400 Subject: [PATCH 17/28] OBB fromRectangle: require ellipsoid of revolution, at least for now --- Source/Core/OrientedBoundingBox.js | 4 ++++ Specs/Core/OrientedBoundingBoxSpec.js | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/Source/Core/OrientedBoundingBox.js b/Source/Core/OrientedBoundingBox.js index feda605bb7cf..546f2eb43e45 100644 --- a/Source/Core/OrientedBoundingBox.js +++ b/Source/Core/OrientedBoundingBox.js @@ -136,6 +136,7 @@ define([ * * @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); @@ -148,6 +149,9 @@ define([ 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); diff --git a/Specs/Core/OrientedBoundingBoxSpec.js b/Specs/Core/OrientedBoundingBoxSpec.js index 8d3b9c45366c..516c9f0643ac 100644 --- a/Specs/Core/OrientedBoundingBoxSpec.js +++ b/Specs/Core/OrientedBoundingBoxSpec.js @@ -117,6 +117,12 @@ defineSuite([ 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); From 360448582b11f7909d42a51584d213351d7bf365 Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Tue, 16 Jun 2015 16:39:03 -0400 Subject: [PATCH 18/28] OBB fromRectangle: change tangent plane for equator-spanning case, add a test, and fix a typo --- Source/Core/OrientedBoundingBox.js | 15 +++++---------- Specs/Core/OrientedBoundingBoxSpec.js | 10 ++++++---- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/Source/Core/OrientedBoundingBox.js b/Source/Core/OrientedBoundingBox.js index 546f2eb43e45..5516aa2fe7e0 100644 --- a/Source/Core/OrientedBoundingBox.js +++ b/Source/Core/OrientedBoundingBox.js @@ -158,13 +158,8 @@ define([ maximumHeight = defaultValue(maximumHeight, 0.0); ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); - // If the rectangle does not span the equator, then the bounding box will be aligned with the tangent plane at the center of the rectangle. - // If the rectangle does span the equator, then the bounding box will be aligned with the tangent plane to the equator at the longitudinal center of the rectangle. + // The bounding box will be aligned with the tangent plane at the center of the rectangle. var tangentPointCartographic = Rectangle.center(rectangle, scratchRectangleCenterCartographic); - if (rectangle.south < 0.0 && rectangle.north > 0.0) { - // The rectangle spans the equator - tangentPointCartographic.latitude = 0.0; - } var tangentPoint = ellipsoid.cartographicToCartesian(tangentPointCartographic, scratchRectangleCenter); var tangentPlane = new EllipsoidTangentPlane(tangentPoint, ellipsoid); var plane = tangentPlane.plane; @@ -175,8 +170,8 @@ define([ // W/-x [7] [3] E/+x // [6] [5] [4] // S/-y - // "C" refers to the central lat/long which always align with the tangent point (above). - // If the rectangle spans the equator, CW and CE are aligned with the equator; otherwise, they're centered in latitude. + // "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]; @@ -187,7 +182,7 @@ define([ var perimeterCW = perimeterCartographicScratch[7]; var lonCenter = tangentPointCartographic.longitude; - var latCenter = tangentPointCartographic.latitude; + 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; @@ -203,7 +198,7 @@ define([ // 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[6].y, perimeterProjectedScratch[6].y); + 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 diff --git a/Specs/Core/OrientedBoundingBoxSpec.js b/Specs/Core/OrientedBoundingBoxSpec.js index 516c9f0643ac..60fe27d5f1f8 100644 --- a/Specs/Core/OrientedBoundingBoxSpec.js +++ b/Specs/Core/OrientedBoundingBoxSpec.js @@ -149,9 +149,12 @@ defineSuite([ 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 d60 = CesiumMath.PI_OVER_THREE; var d135 = 3 * CesiumMath.PI_OVER_FOUR; var d180 = CesiumMath.PI; + var sqrt3 = Math.sqrt(3.0); var box; @@ -179,9 +182,9 @@ defineSuite([ 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, -d45, d90, d90), 0.0, 0.0, Ellipsoid.UNIT_SPHERE); - expect(box.center).toEqualEpsilon(new Cartesian3(0.5, 0.0, 0.5 * (1.0 - Math.SQRT1_2)), CesiumMath.EPSILON15); - expect(box.halfAxes).toEqualEpsilon(new Matrix3(0.0, 0.0, 0.5, 1.0, 0.0, 0.0, 0.0, 0.5 * (1.0 + Math.SQRT1_2), 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(-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); @@ -206,7 +209,6 @@ defineSuite([ var intersectPlaneTestCornersEdgesFaces = function(center, axes) { var SQRT1_2 = Math.pow(1.0 / 2.0, 1 / 2.0); - var SQRT1_3 = Math.pow(1.0 / 3.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())); From d908de2cc970968c50ca32219ee461d52977716c Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Tue, 16 Jun 2015 17:00:59 -0400 Subject: [PATCH 19/28] OBB fromRectangle: More similar tests --- Specs/Core/OrientedBoundingBoxSpec.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Specs/Core/OrientedBoundingBoxSpec.js b/Specs/Core/OrientedBoundingBoxSpec.js index 60fe27d5f1f8..5a7eea40f3a2 100644 --- a/Specs/Core/OrientedBoundingBoxSpec.js +++ b/Specs/Core/OrientedBoundingBoxSpec.js @@ -186,6 +186,18 @@ defineSuite([ 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); From 58d06b976d60e47d24b1d3003e30b9a437b8d118 Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Tue, 16 Jun 2015 17:25:05 -0400 Subject: [PATCH 20/28] OBB fromRectangle: more tests and fix another typo --- Source/Core/OrientedBoundingBox.js | 2 +- Specs/Core/OrientedBoundingBoxSpec.js | 31 ++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/Source/Core/OrientedBoundingBox.js b/Source/Core/OrientedBoundingBox.js index 5516aa2fe7e0..3a2e0e8e6fd8 100644 --- a/Source/Core/OrientedBoundingBox.js +++ b/Source/Core/OrientedBoundingBox.js @@ -191,7 +191,7 @@ define([ perimeterSE.longitude = perimeterCE.longitude = perimeterNE.longitude = rectangle.east; // Compute XY extents using the rectangle at maximum height - perimeterNE.height = perimeterNW.height = perimeterSE.height = perimeterSW.height = perimeterNC.height = perimeterSC.height = maximumHeight; + 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); diff --git a/Specs/Core/OrientedBoundingBoxSpec.js b/Specs/Core/OrientedBoundingBoxSpec.js index 5a7eea40f3a2..e55d78762cc3 100644 --- a/Specs/Core/OrientedBoundingBoxSpec.js +++ b/Specs/Core/OrientedBoundingBoxSpec.js @@ -147,11 +147,40 @@ defineSuite([ 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 d60 = CesiumMath.PI_OVER_THREE; var d135 = 3 * CesiumMath.PI_OVER_FOUR; var d180 = CesiumMath.PI; var sqrt3 = Math.sqrt(3.0); From e183089db787ef492cb7d2e4905ad82966ec6ec8 Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Wed, 17 Jun 2015 17:06:58 -0400 Subject: [PATCH 21/28] Hopefully-better debug volume render code --- Source/Scene/GlobeSurfaceTileProvider.js | 100 +++++++++++++++-------- 1 file changed, 66 insertions(+), 34 deletions(-) diff --git a/Source/Scene/GlobeSurfaceTileProvider.js b/Source/Scene/GlobeSurfaceTileProvider.js index 837666c27322..bd1457e2b2cf 100644 --- a/Source/Scene/GlobeSurfaceTileProvider.js +++ b/Source/Scene/GlobeSurfaceTileProvider.js @@ -810,43 +810,71 @@ define([ return context.createVertexArray(vertexArray._attributes, wireframeIndexBuffer); } - function createDebugPrimitive(geometry, color, modelMatrix) { - var instance = new GeometryInstance({ - geometry : geometry, - modelMatrix : modelMatrix, - attributes : { - color : ColorGeometryInstanceAttribute.fromColor(color) - } - }); + var createDebugOrientedBoundingBox; + var createDebugBoundingSphere; + var debugDestroyPrimitive; - return new Primitive({ - geometryInstances : instance, - appearance : new PerInstanceColorAppearance({ - translucent : false, - flat : true - }), - asynchronous : false + (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 + }); + } - var scratchBVMatrix = new Matrix4(); - var debugOrientedBoundingBox = BoxOutlineGeometry.fromDimensions({ - dimensions: new Cartesian3(2.0, 2.0, 2.0), - vertexFormat: PerInstanceColorAppearance.FLAT_VERTEX_FORMAT - }); - var createDebugOrientedBoundingBox = function(obb, color) { - var modelMatrix = Matrix4.fromRotationTranslation(obb.halfAxes, obb.center, scratchBVMatrix); - return createDebugPrimitive(debugOrientedBoundingBox, color, modelMatrix); - }; + createDebugOrientedBoundingBox = function(obb, color) { + if (obb === previousVolume) { + return primitive; + } + debugDestroyPrimitive(); - var debugBoundingSphere = new SphereOutlineGeometry({ - radius: 1.0 - }); - var createDebugSphere = function(sphere, color) { - var modelMatrix = Matrix4.fromTranslation(sphere.center); - Matrix4.multiplyByUniformScale(modelMatrix, sphere.radius, modelMatrix); - return createDebugPrimitive(debugBoundingSphere, color, modelMatrix); - }; + previousVolume = obb; + modelMatrix = Matrix4.fromRotationTranslation(obb.halfAxes, obb.center, modelMatrix); + + instanceOBB.modelMatrix = modelMatrix; + instanceOBB.attributes.color = ColorGeometryInstanceAttribute.fromColor(color); + + primitive = createDebugPrimitive(instanceOBB); + return primitive; + }; + + createDebugBoundingSphere = 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(); + } + }; + })(); var otherPassesInitialColor = new Cartesian4(0.0, 0.0, 0.0, 0.0); @@ -936,6 +964,10 @@ define([ var initialColor = tileProvider._firstPassInitialColor; + if (!defined(tileProvider._debug.boundingSphereTile)) { + debugDestroyPrimitive(); + } + do { var numberOfDayTextures = 0; @@ -966,7 +998,7 @@ define([ if (defined(surfaceTile.orientedBoundingBox)) { createDebugOrientedBoundingBox(surfaceTile.orientedBoundingBox, Color.RED).update(context, frameState, commandList); } else if (defined(surfaceTile.boundingSphere3D)) { - createDebugSphere(surfaceTile.boundingSphere3D, Color.RED).update(context, frameState, commandList); + createDebugBoundingSphere(surfaceTile.boundingSphere3D, Color.RED).update(context, frameState, commandList); } } From 8148cc1429f525ef36e2dbab8ed5db4996362e09 Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Wed, 17 Jun 2015 17:50:16 -0400 Subject: [PATCH 22/28] Compute OrientedBoundingBoxes for upsampled terrain --- Source/Workers/upsampleQuantizedTerrainMesh.js | 5 +++++ 1 file changed, 5 insertions(+) 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 }; } From 2f520c6caf364aec45de9fb3d4d3d2a51deaa871 Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Thu, 18 Jun 2015 10:51:44 -0400 Subject: [PATCH 23/28] Fix crash for debug drawing code --- Source/Scene/GlobeSurfaceTileProvider.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Scene/GlobeSurfaceTileProvider.js b/Source/Scene/GlobeSurfaceTileProvider.js index bd1457e2b2cf..90232dcad503 100644 --- a/Source/Scene/GlobeSurfaceTileProvider.js +++ b/Source/Scene/GlobeSurfaceTileProvider.js @@ -872,6 +872,8 @@ define([ debugDestroyPrimitive = function() { if (defined(primitive)) { primitive.destroy(); + primitive = undefined; + previousVolume = undefined; } }; })(); From fb646145e85b34410bf30aa2bb0f0092814bee35 Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Thu, 18 Jun 2015 17:26:14 -0400 Subject: [PATCH 24/28] Add a comment about excluding skirts from OBBs --- Source/Core/CesiumTerrainProvider.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Source/Core/CesiumTerrainProvider.js b/Source/Core/CesiumTerrainProvider.js index fb4cdddcf01f..0b15c98a3ee4 100644 --- a/Source/Core/CesiumTerrainProvider.js +++ b/Source/Core/CesiumTerrainProvider.js @@ -428,6 +428,12 @@ define([ 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); } From 0a712f6ad15bc2d5156cfa2eba4207afdd4bad66 Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Fri, 19 Jun 2015 13:55:12 -0400 Subject: [PATCH 25/28] Possible minor optimization for OBB.intersectPlane --- Source/Core/OrientedBoundingBox.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Source/Core/OrientedBoundingBox.js b/Source/Core/OrientedBoundingBox.js index 3a2e0e8e6fd8..e9bf50db32f6 100644 --- a/Source/Core/OrientedBoundingBox.js +++ b/Source/Core/OrientedBoundingBox.js @@ -235,9 +235,6 @@ define([ return result; }; - var scratchCartesian1 = new Cartesian3(); - var scratchCartesian2 = new Cartesian3(); - var scratchCartesian3 = new Cartesian3(); /** * Determines which side of a plane the oriented bounding box is located. * @@ -262,10 +259,11 @@ define([ 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(normal.x * halfAxes[Matrix3.COLUMN0ROW0] + normal.y * halfAxes[Matrix3.COLUMN0ROW1] + normal.z * halfAxes[Matrix3.COLUMN0ROW2]) + - Math.abs(normal.x * halfAxes[Matrix3.COLUMN1ROW0] + normal.y * halfAxes[Matrix3.COLUMN1ROW1] + normal.z * halfAxes[Matrix3.COLUMN1ROW2]) + - Math.abs(normal.x * halfAxes[Matrix3.COLUMN2ROW0] + normal.y * halfAxes[Matrix3.COLUMN2ROW1] + normal.z * halfAxes[Matrix3.COLUMN2ROW2]); + 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) { From 1cd0747d6c8301843ed3ef8a7d2233fa9ab128d2 Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Fri, 19 Jun 2015 13:56:14 -0400 Subject: [PATCH 26/28] Tile debug volume display: add comment and rename functions to clarify semantics --- Source/Scene/GlobeSurfaceTileProvider.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Source/Scene/GlobeSurfaceTileProvider.js b/Source/Scene/GlobeSurfaceTileProvider.js index 90232dcad503..12dc7f86570a 100644 --- a/Source/Scene/GlobeSurfaceTileProvider.js +++ b/Source/Scene/GlobeSurfaceTileProvider.js @@ -810,8 +810,8 @@ define([ return context.createVertexArray(vertexArray._attributes, wireframeIndexBuffer); } - var createDebugOrientedBoundingBox; - var createDebugBoundingSphere; + var getDebugOrientedBoundingBox; + var getDebugBoundingSphere; var debugDestroyPrimitive; (function() { @@ -836,7 +836,7 @@ define([ }); } - createDebugOrientedBoundingBox = function(obb, color) { + getDebugOrientedBoundingBox = function(obb, color) { if (obb === previousVolume) { return primitive; } @@ -852,7 +852,7 @@ define([ return primitive; }; - createDebugBoundingSphere = function(sphere, color) { + getDebugBoundingSphere = function(sphere, color) { if (sphere === previousVolume) { return primitive; } @@ -997,10 +997,13 @@ define([ ++tileProvider._usedDrawCommands; 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)) { - createDebugOrientedBoundingBox(surfaceTile.orientedBoundingBox, Color.RED).update(context, frameState, commandList); + getDebugOrientedBoundingBox(surfaceTile.orientedBoundingBox, Color.RED).update(context, frameState, commandList); } else if (defined(surfaceTile.boundingSphere3D)) { - createDebugBoundingSphere(surfaceTile.boundingSphere3D, Color.RED).update(context, frameState, commandList); + getDebugBoundingSphere(surfaceTile.boundingSphere3D, Color.RED).update(context, frameState, commandList); } } From 85fc36c3ac6851ef3ef4c1b3c1eec7e3586b1959 Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Fri, 19 Jun 2015 14:14:30 -0400 Subject: [PATCH 27/28] Add OBB performance note to CHANGES.md --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 30e90f575d0d..f9d698753e3d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -25,7 +25,7 @@ Change Log * 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 to improve performance of terrain rendering (by **TODO**%) and loading (by **TODO**%). +* Use `OrientedBoundingBox` when rendering terrain to improve performance of terrain rendering and loading (by up to 50% of terrain tiles, depending on camera view). ### 1.10 - 2015-06-01 From d6a73897173108400032a90aa0089b9295fc470b Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Fri, 19 Jun 2015 16:33:45 -0400 Subject: [PATCH 28/28] Use bounding boxes for heightmap terrain (including no terrain). --- CHANGES.md | 2 +- Source/Core/HeightmapTerrainData.js | 4 +++- Source/Workers/createVerticesFromHeightmap.js | 11 +++++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index f9d698753e3d..deee341f81b4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -25,7 +25,7 @@ Change Log * 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 to improve performance of terrain rendering and loading (by up to 50% of terrain tiles, depending on camera view). +* 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/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/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 }; }