From c70de7c881ed011fa185dc90c07bda0926872536 Mon Sep 17 00:00:00 2001 From: Dan Bagnell Date: Fri, 1 Feb 2013 19:08:47 -0500 Subject: [PATCH 1/2] Fixed PolylinePipeline.wrapLongitude and made it more efficient. Added IntersectionTests.lineSegmentPlane method. --- CHANGES.md | 2 + Source/Core/IntersectionTests.js | 60 +++++++++++ Source/Core/PolylinePipeline.js | 148 ++++++++++++++++------------ Source/Scene/Polyline.js | 4 +- Source/Scene/PolylineCollection.js | 4 +- Specs/Core/IntersectionTestsSpec.js | 73 ++++++++++++++ Specs/Core/PolylinePipelineSpec.js | 25 ++++- 7 files changed, 247 insertions(+), 69 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index aae081655747..39297554b04a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -18,10 +18,12 @@ Beta Releases * Added `Transforms.computeIcrfToFixedMatrix` and `computeFixedToIcrfMatrix`. * Added `EarthOrientationParameters`, `EarthOrientationParametersSample`, `Iau2006XysData`, and `Iau2006XysDataSample` classes to `Core`. * CZML now supports the ability to specify positions in the International Celestial Reference Frame (ICRF), and inertial reference frame. +* Added a line segment-ray intersection test to `IntersectionTests`. * Fixed globe rendering on the Nexus 4 running Google Chrome Beta. * `ViewportQuad` now supports the material system. See the [Fabric](https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric) wiki page. * Fixed rendering artifacts in `EllipsoidPrimitive`. * Fixed an issue where streaming CZML would fail when changing material types. +* Fixed an issue where a `PolylineCollection` with a model matrix other than the identity would be incorrectly rendered in 2D and Columbus view. * Updated Dojo from 1.7.2 to 1.8.4. Reminder: Cesium does not depend on Dojo but uses it for reference applications. ### b12a - 2013-01-18 diff --git a/Source/Core/IntersectionTests.js b/Source/Core/IntersectionTests.js index d51ec9727fee..480303418b88 100644 --- a/Source/Core/IntersectionTests.js +++ b/Source/Core/IntersectionTests.js @@ -355,5 +355,65 @@ define([ return undefined; }; + var lineSegmentPlaneDifference = new Cartesian3(); + + /** + * Computes the intersection of a line segment and a plane. + * @memberof IntersectionTests + * + * @param {Cartesian3} endPoint0 An end point of the line segment. + * @param {Cartesian3} endPoint1 The other end point of the line segment. + * @param {Cartesian3} planeNormal The plane normal. + * @param {Number} planeD The distance from the plane to the origin. + * @param {Cartesian3} [result] The object onto which to store the result. + * @returns {Cartesian3} The intersection point or undefined if there is no intersection. + * + * @exception {DeveloperError} endPoint0 is required. + * @exception {DeveloperError} endPoint1 is required. + * @exception {DeveloperError} planeNormal is required. + * @exception {DeveloperError} planeD is required. + */ + IntersectionTests.lineSegmentPlane = function(endPoint0, endPoint1, planeNormal, planeD, result) { + if (typeof endPoint0 === 'undefined') { + throw new DeveloperError('endPoint0 is required.'); + } + + if (typeof endPoint1 === 'undefined') { + throw new DeveloperError('endPoint1 is required.'); + } + + if (typeof planeNormal === 'undefined') { + throw new DeveloperError('planeNormal is required.'); + } + + if (typeof planeD === 'undefined') { + throw new DeveloperError('planeD is required.'); + } + + var difference = Cartesian3.subtract(endPoint1, endPoint0, lineSegmentPlaneDifference); + var nDotDiff = Cartesian3.dot(planeNormal, difference); + + // check if the segment and plane are parallel + if (Math.abs(nDotDiff) < CesiumMath.EPSILON6) { + return undefined; + } + + var nDotP0 = Cartesian3.dot(planeNormal, endPoint0); + var t = -(planeD + nDotP0) / nDotDiff; + + // intersection only if t is in [0, 1] + if (t < 0.0 || t > 1.0) { + return undefined; + } + + // intersection is endPoint0 + t * (endPoint1 - endPoint0) + if (typeof result === 'undefined') { + result = new Cartesian3(); + } + Cartesian3.multiplyByScalar(difference, t, result); + Cartesian3.add(endPoint0, result, result); + return result; + }; + return IntersectionTests; }); diff --git a/Source/Core/PolylinePipeline.js b/Source/Core/PolylinePipeline.js index 633a4b6b93e2..e5e14177dae4 100644 --- a/Source/Core/PolylinePipeline.js +++ b/Source/Core/PolylinePipeline.js @@ -1,9 +1,16 @@ /*global define*/ -define(['./Cartographic', - './Cartesian3' +define([ + './defaultValue', + './Cartesian3', + './Cartesian4', + './IntersectionTests', + './Matrix4' ], function( - Cartographic, - Cartesian3) { + defaultValue, + Cartesian3, + Cartesian4, + IntersectionTests, + Matrix4) { "use strict"; /** @@ -11,79 +18,98 @@ define(['./Cartographic', * * @exports PolylinePipeline */ - var PolylinePipeline = { - /** - * Breaks a {@link Polyline} into segments such that it does not cross the ±180 degree meridian of an ellipsoid. - * - * @param {Ellipsoid} ellipsoid The ellipsoid to wrap around. - * @param {Array} positions The polyline's Cartesian positions. - * - * @returns An array of polyline segment objects containing the Cartesian and {@link Cartographic} positions and indices. - * - * @see Polyline - * @see PolylineCollection - * - * @example - * var polylines = new PolylineCollection(); - * polylines.add(...); - * var positions = polylines.get(0).getPositions(); - * var segments = PolylinePipeline.wrapLongitude(ellipsoid, positions); - */ - wrapLongitude : function(ellipsoid, positions) { - var segments = []; - - if (positions && (positions.length > 0)) { - var length = positions.length; - - var currentSegment = [{ - cartesian : Cartesian3.clone(positions[0]), - cartographic : ellipsoid.cartesianToCartographic(positions[0]), - index : 0 - }]; - - var prev = currentSegment[0].cartographic; - - for ( var i = 1; i < length; ++i) { - var cur = ellipsoid.cartesianToCartographic(positions[i]); - - if (Math.abs(prev.longitude - cur.longitude) > Math.PI) { - var interpolatedLongitude = prev.longitude < 0.0 ? -Math.PI : Math.PI; - var longitude = cur.longitude + (2.0 * interpolatedLongitude); - var ratio = (interpolatedLongitude - prev.longitude) / (longitude - prev.longitude); - var interpolatedLatitude = prev.latitude + (cur.latitude - prev.latitude) * ratio; - var interpolatedHeight = prev.height + (cur.height - prev.height) * ratio; + var PolylinePipeline = {}; + + var wrapLongitudeInversMatrix = new Matrix4(); + var wrapLongitudeOrigin = new Cartesian4(); + var wrapLongitudeXZNormal = new Cartesian4(); + var wrapLongitudeYZNormal = new Cartesian4(); + var wrapLongitudeIntersection = new Cartesian3(); + var wrapLongitudeOffset = new Cartesian3(); + + /** + * Breaks a {@link Polyline} into segments such that it does not cross the ±180 degree meridian of an ellipsoid. + * @memberof PolylinePipeline + * + * @param {Array} positions The polyline's Cartesian positions. + * @param {Matrix4} [modelMatrix=Matrix4.IDENTITY] The polyline's model matrix. + * + * @returns An array of polyline segment objects containing the Cartesian position and indices. + * + * @see Polyline + * @see PolylineCollection + * + * @example + * var polylines = new PolylineCollection(); + * polylines.add(...); + * var positions = polylines.get(0).getPositions(); + * var modelMatrix = polylines.modelMatrix; + * var segments = PolylinePipeline.wrapLongitude(positions, modelMatrix); + */ + PolylinePipeline.wrapLongitude = function(positions, modelMatrix) { + var segments = []; + + if (typeof positions !== 'undefined' && positions.length > 0) { + modelMatrix = defaultValue(modelMatrix, Matrix4.IDENTITY); + var inverseModelMatrix = Matrix4.inverseTransformation(modelMatrix, wrapLongitudeInversMatrix); + + var origin = Matrix4.multiplyByPoint(inverseModelMatrix, Cartesian3.ZERO, wrapLongitudeOrigin); + var xzNormal = Matrix4.multiplyByVector(inverseModelMatrix, Cartesian4.UNIT_Y, wrapLongitudeXZNormal); + var xzConstant = -Cartesian3.dot(xzNormal, origin); + var yzNormal = Matrix4.multiplyByVector(inverseModelMatrix, Cartesian4.UNIT_X, wrapLongitudeYZNormal); + var yzConstant = -Cartesian3.dot(yzNormal, origin); + + var currentSegment = [{ + cartesian : Cartesian3.clone(positions[0]), + index : 0 + }]; + var prev = currentSegment[0].cartesian; + + var length = positions.length; + for ( var i = 1; i < length; ++i) { + var cur = positions[i]; + + // intersects the IDL if either endpoint is on the negative side of the yz-plane + if (Cartesian3.dot(prev, yzNormal) + yzConstant < 0.0 || Cartesian3.dot(cur, yzNormal) + yzNormal < 0.0) { + // and intersects the xz-plane + var intersection = IntersectionTests.lineSegmentPlane(prev, cur, xzNormal, xzConstant, wrapLongitudeIntersection); + if (typeof intersection !== 'undefined') { + // move point on the xz-plane slightly away from the plane + var offset = Cartesian3.multiplyByScalar(xzNormal, 5.0e-9, wrapLongitudeOffset); + if (Cartesian3.dot(prev, xzNormal) + xzConstant < 0.0) { + Cartesian3.negate(offset, offset); + } currentSegment.push({ - cartesian : ellipsoid.cartographicToCartesian(new Cartographic(interpolatedLongitude, interpolatedLatitude, interpolatedHeight)), - cartographic : new Cartographic(interpolatedLongitude, interpolatedLatitude, interpolatedHeight), + cartesian : Cartesian3.add(intersection, offset), index : i }); segments.push(currentSegment); + Cartesian3.negate(offset, offset); + currentSegment = []; currentSegment.push({ - cartesian : ellipsoid.cartographicToCartesian(new Cartographic(-interpolatedLongitude, interpolatedLatitude, interpolatedHeight)), - cartographic : new Cartographic(-interpolatedLongitude, interpolatedLatitude, interpolatedHeight), + cartesian : Cartesian3.add(intersection, offset), index : i }); } - - currentSegment.push({ - cartesian : Cartesian3.clone(positions[i]), - cartographic : ellipsoid.cartesianToCartographic(positions[i]), - index : i - }); - - prev = cur.clone(); } - if (currentSegment.length > 1) { - segments.push(currentSegment); - } + currentSegment.push({ + cartesian : Cartesian3.clone(positions[i]), + index : i + }); + + prev = cur; } - return segments; + if (currentSegment.length > 1) { + segments.push(currentSegment); + } } + + return segments; }; return PolylinePipeline; diff --git a/Source/Scene/Polyline.js b/Source/Scene/Polyline.js index b8bdaebd1d96..576c9ba0f0ec 100644 --- a/Source/Scene/Polyline.js +++ b/Source/Scene/Polyline.js @@ -361,8 +361,8 @@ define([ return positions; }; - Polyline.prototype._createSegments = function(ellipsoid) { - return PolylinePipeline.wrapLongitude(ellipsoid, this.getPositions()); + Polyline.prototype._createSegments = function(modelMatrix) { + return PolylinePipeline.wrapLongitude(this.getPositions(), modelMatrix); }; Polyline.prototype._setSegments = function(segments) { diff --git a/Source/Scene/PolylineCollection.js b/Source/Scene/PolylineCollection.js index 929ec235fccc..c668a36fa810 100644 --- a/Source/Scene/PolylineCollection.js +++ b/Source/Scene/PolylineCollection.js @@ -407,7 +407,7 @@ define([ var changedProperties = polyline._propertiesChanged; if (changedProperties[POSITION_INDEX]) { if (intersectsIDL(polyline)) { - var newSegments = polyline._createSegments(this._projection._ellipsoid); + var newSegments = polyline._createSegments(this.modelMatrix); if (polyline._segmentsLengthChanged(newSegments)) { createVertexArrays = true; break; @@ -1129,7 +1129,7 @@ define([ if (this.mode === SceneMode.SCENE3D || !intersectsIDL(polyline)) { return polyline.getPositions().length; } - var segments = polyline._createSegments(this.ellipsoid); + var segments = polyline._createSegments(this.modelMatrix); return polyline._setSegments(segments); }; diff --git a/Specs/Core/IntersectionTestsSpec.js b/Specs/Core/IntersectionTestsSpec.js index ba98c684cb15..7ce60c5d8fe6 100644 --- a/Specs/Core/IntersectionTestsSpec.js +++ b/Specs/Core/IntersectionTestsSpec.js @@ -255,4 +255,77 @@ defineSuite([ var ray = new Ray(Cartesian3.ZERO, Cartesian3.UNIT_Z); expect(IntersectionTests.grazingAltitudeLocation(ray, ellipsoid)).not.toBeDefined(); }); + + it('lineSegmentPlane intersects', function() { + var planeNormal = Cartesian3.UNIT_Y.clone(); + var pointOnPlane = new Cartesian3(0.0, 2.0, 0.0); + var planeConstant = -Cartesian3.dot(planeNormal, pointOnPlane); + + var endPoint0 = new Cartesian3(1.0, 1.0, 0.0); + var endPoint1 = new Cartesian3(1.0, 3.0, 0.0); + + var intersectionPoint = IntersectionTests.lineSegmentPlane(endPoint0, endPoint1, planeNormal, planeConstant); + + expect(intersectionPoint).toEqual(new Cartesian3(1.0, 2.0, 0.0)); + }); + + it('lineSegmentPlane misses (entire segment behind plane)', function() { + var planeNormal = new Cartesian3(1.0, 0.0, 0.0); + var planeConstant = 0.0; + + var endPoint0 = new Cartesian3(-2.0, 0.0, 0.0); + var endPoint1 = new Cartesian3(-5.0, 0.0, 0.0); + + var intersectionPoint = IntersectionTests.lineSegmentPlane(endPoint0, endPoint1, planeNormal, planeConstant); + + expect(intersectionPoint).not.toBeDefined(); + }); + + it('lineSegmentPlane misses (entire segment in front of plane)', function() { + var planeNormal = new Cartesian3(1.0, 0.0, 0.0); + var planeConstant = 0.0; + + var endPoint0 = new Cartesian3(5.0, 0.0, 0.0); + var endPoint1 = new Cartesian3(2.0, 0.0, 0.0); + + var intersectionPoint = IntersectionTests.lineSegmentPlane(endPoint0, endPoint1, planeNormal, planeConstant); + + expect(intersectionPoint).not.toBeDefined(); + }); + + it('lineSegmentPlane misses (parallel)', function() { + var planeNormal = new Cartesian3(1.0, 0.0, 0.0); + var planeConstant = 0.0; + + var endPoint0 = new Cartesian3(0.0, -1.0, 0.0); + var endPoint1 = new Cartesian3(0.0, 1.0, 0.0); + + var intersectionPoint = IntersectionTests.lineSegmentPlane(endPoint0, endPoint1, planeNormal, planeConstant); + + expect(intersectionPoint).not.toBeDefined(); + }); + + it('lineSegmentPlane throws without endPoint0', function() { + expect(function() { + IntersectionTests.lineSegmentPlane(); + }).toThrow(); + }); + + it('lineSegmentPlane throws without endPoint1', function() { + expect(function() { + IntersectionTests.lineSegmentPlane(new Cartesian3()); + }).toThrow(); + }); + + it('lineSegmentPlane throws without planeNormal', function() { + expect(function() { + IntersectionTests.lineSegmentPlane(new Cartesian3(), new Cartesian3()); + }).toThrow(); + }); + + it('lineSegmentPlane throws without planeD', function() { + expect(function() { + IntersectionTests.lineSegmentPlane(new Cartesian3(), new Cartesian3(), new Cartesian3()); + }).toThrow(); + }); }); diff --git a/Specs/Core/PolylinePipelineSpec.js b/Specs/Core/PolylinePipelineSpec.js index 57e0d3c9833a..afd9b90f6dae 100644 --- a/Specs/Core/PolylinePipelineSpec.js +++ b/Specs/Core/PolylinePipelineSpec.js @@ -1,12 +1,16 @@ /*global defineSuite*/ defineSuite([ 'Core/PolylinePipeline', + 'Core/Cartesian3', 'Core/Cartographic', - 'Core/Ellipsoid' + 'Core/Ellipsoid', + 'Core/Transforms' ], function( PolylinePipeline, + Cartesian3, Cartographic, - Ellipsoid) { + Ellipsoid, + Transforms) { "use strict"; /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ @@ -16,7 +20,7 @@ defineSuite([ var p2 = new Cartographic.fromDegrees(-80.2264393, 25.7889689); // Miami, FL var positions = [ellipsoid.cartographicToCartesian(p1), ellipsoid.cartographicToCartesian(p2)]; - var segments = PolylinePipeline.wrapLongitude(ellipsoid, positions); + var segments = PolylinePipeline.wrapLongitude(positions); expect(segments.length).toEqual(1); expect(segments[0].length).toEqual(2); }); @@ -27,7 +31,20 @@ defineSuite([ var p2 = new Cartographic.fromDegrees(2.0, 25.0); var positions = [ellipsoid.cartographicToCartesian(p1), ellipsoid.cartographicToCartesian(p2)]; - var segments = PolylinePipeline.wrapLongitude(ellipsoid, positions); + var segments = PolylinePipeline.wrapLongitude(positions); + expect(segments.length).toEqual(2); + expect(segments[0].length).toEqual(2); + expect(segments[1].length).toEqual(2); + }); + + it('wrapLongitude breaks polyline into segments with model matrix', function() { + var ellipsoid = Ellipsoid.WGS84; + var center = ellipsoid.cartographicToCartesian(new Cartographic.fromDegrees(-179.0, 39.0)); + var matrix = Transforms.eastNorthUpToFixedFrame(center, ellipsoid); + + var positions = [ new Cartesian3(0.0, 0.0, 0.0), + new Cartesian3(0.0, 100000000.0, 0.0)]; + var segments = PolylinePipeline.wrapLongitude(positions, matrix); expect(segments.length).toEqual(2); expect(segments[0].length).toEqual(2); expect(segments[1].length).toEqual(2); From 77fd8d2842859d55ed39fe776bae836f6c1c0e2e Mon Sep 17 00:00:00 2001 From: Dan Bagnell Date: Fri, 1 Feb 2013 19:38:32 -0500 Subject: [PATCH 2/2] Changes based on review. --- CHANGES.md | 6 ++++-- Source/Core/IntersectionTests.js | 11 +++++++++++ Source/Core/PolylinePipeline.js | 9 ++++++--- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index aed54984676c..8b0af05574d8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,7 +6,11 @@ Beta Releases ### b14 - 2013-xx-xx +* Added a line segment-ray intersection test to `IntersectionTests`. +* Fixed an issue where a `PolylineCollection` with a model matrix other than the identity would be incorrectly rendered in 2D and Columbus view. + ### b13 - 2013-02-01 + * Breaking changes: * The combined `Cesium.js` file and other required files are now created in `Build/Cesium` and `Build/CesiumUnminified` folders. * The Web Worker files needed when using the combined `Cesium.js` file are now in a `Workers` subdirectory. @@ -20,12 +24,10 @@ Beta Releases * Added `Transforms.computeIcrfToFixedMatrix` and `computeFixedToIcrfMatrix`. * Added `EarthOrientationParameters`, `EarthOrientationParametersSample`, `Iau2006XysData`, and `Iau2006XysDataSample` classes to `Core`. * CZML now supports the ability to specify positions in the International Celestial Reference Frame (ICRF), and inertial reference frame. -* Added a line segment-ray intersection test to `IntersectionTests`. * Fixed globe rendering on the Nexus 4 running Google Chrome Beta. * `ViewportQuad` now supports the material system. See the [Fabric](https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric) wiki page. * Fixed rendering artifacts in `EllipsoidPrimitive`. * Fixed an issue where streaming CZML would fail when changing material types. -* Fixed an issue where a `PolylineCollection` with a model matrix other than the identity would be incorrectly rendered in 2D and Columbus view. * Updated Dojo from 1.7.2 to 1.8.4. Reminder: Cesium does not depend on Dojo but uses it for reference applications. ### b12a - 2013-01-18 diff --git a/Source/Core/IntersectionTests.js b/Source/Core/IntersectionTests.js index 480303418b88..0a11c11ae73b 100644 --- a/Source/Core/IntersectionTests.js +++ b/Source/Core/IntersectionTests.js @@ -372,6 +372,17 @@ define([ * @exception {DeveloperError} endPoint1 is required. * @exception {DeveloperError} planeNormal is required. * @exception {DeveloperError} planeD is required. + * + * @example + * var origin = ellipsoid.cartographicToCartesian(Cartographic.fromDegrees(-75.59777, 40.03883, 0.0)); + * var normal = ellipsoid.geodeticSurfaceNormal(origin); + * var constant = -Cartesian3.dot(normal, origin); + * + * var p0 = new Cartesian3(...); + * var p1 = new Cartesian3(...); + * + * // find the intersection of the line segment from p0 to p1 and the tangent plane at origin. + * var intersection = IntersectionTests.lineSegmentPlane(p0, p1, normal, constant); */ IntersectionTests.lineSegmentPlane = function(endPoint0, endPoint1, planeNormal, planeD, result) { if (typeof endPoint0 === 'undefined') { diff --git a/Source/Core/PolylinePipeline.js b/Source/Core/PolylinePipeline.js index e5e14177dae4..c02731aff695 100644 --- a/Source/Core/PolylinePipeline.js +++ b/Source/Core/PolylinePipeline.js @@ -32,7 +32,10 @@ define([ * @memberof PolylinePipeline * * @param {Array} positions The polyline's Cartesian positions. - * @param {Matrix4} [modelMatrix=Matrix4.IDENTITY] The polyline's model matrix. + * @param {Matrix4} [modelMatrix=Matrix4.IDENTITY] The polyline's model matrix. Assumed to be an affine + * transformation matrix, where the upper left 3x3 elements are a rotation matrix, and + * the upper three elements in the fourth column are the translation. The bottom row is assumed to be [0, 0, 0, 1]. + * The matrix is not verified to be in the proper form. * * @returns An array of polyline segment objects containing the Cartesian position and indices. * @@ -41,8 +44,8 @@ define([ * * @example * var polylines = new PolylineCollection(); - * polylines.add(...); - * var positions = polylines.get(0).getPositions(); + * var polyline = polylines.add(...); + * var positions = polyline.getPositions(); * var modelMatrix = polylines.modelMatrix; * var segments = PolylinePipeline.wrapLongitude(positions, modelMatrix); */