From 2ea276a20311a49d124b3a5f9d51146e065343dc Mon Sep 17 00:00:00 2001 From: Eugene Maksymenko Date: Sun, 11 Sep 2022 23:49:36 +0300 Subject: [PATCH] Remove WorldWindow dependency from Camera. Move transformations between Camera and LookAt to WorldWindow. --- apps/Explorer/Explorer.js | 2 +- apps/NEO/NEO.js | 2 +- apps/SentinelWMTS/SentinelWMTS.js | 2 +- apps/SubSurface/SubSurface.js | 2 +- apps/USGSSlabs/USGSSlabs.js | 2 +- apps/USGSWells/USGSWells.js | 2 +- examples/Canyon.js | 2 +- examples/Views.js | 10 +- performance/DeepPickingPerformance.js | 2 +- src/BasicWorldWindowController.js | 84 +++++----- src/WorldWindow.js | 161 ++++++++++++++++++- src/WorldWindowController.js | 8 - src/geom/Camera.js | 198 +----------------------- src/geom/LookAt.js | 24 --- src/layer/ViewControlsLayer.js | 16 +- src/navigate/LookAtNavigator.js | 12 +- src/navigate/Navigator.js | 18 +-- src/util/GoToAnimator.js | 6 +- src/util/KeyboardControls.js | 16 +- test/BasicWorldWindowController.test.js | 2 +- test/geom/Camera.test.js | 30 ++-- test/geom/LookAt.test.js | 2 +- test/util/TestUtils.test.js | 2 +- 23 files changed, 276 insertions(+), 329 deletions(-) diff --git a/apps/Explorer/Explorer.js b/apps/Explorer/Explorer.js index 77d38f8f29..bebd181162 100644 --- a/apps/Explorer/Explorer.js +++ b/apps/Explorer/Explorer.js @@ -67,7 +67,7 @@ define(['../../src/WorldWind', var lookAt = new WorldWind.LookAt(); lookAt.position.latitude = 30; lookAt.position.longitude = -(180 / 12) * ((new Date()).getTimezoneOffset() / 60); - this.wwd.camera.setFromLookAt(lookAt); + this.wwd.cameraFromLookAt(lookAt); this.goToBox = new GoToBox(this.wwd); this.layersPanel = new LayersPanel(this.wwd); diff --git a/apps/NEO/NEO.js b/apps/NEO/NEO.js index 7540fc6837..5ecd9185ee 100644 --- a/apps/NEO/NEO.js +++ b/apps/NEO/NEO.js @@ -64,7 +64,7 @@ define(['../../src/WorldWind', var lookAt = new WorldWind.LookAt(); lookAt.position.latitude = 30; lookAt.position.longitude = -(180 / 12) * ((new Date()).getTimezoneOffset() / 60); - this.wwd.camera.setFromLookAt(lookAt); + this.wwd.cameraFromLookAt(lookAt); this.timeSeriesPlayer = new TimeSeriesPlayer(this.wwd); this.projectionMenu = new ProjectionMenu(this.wwd); diff --git a/apps/SentinelWMTS/SentinelWMTS.js b/apps/SentinelWMTS/SentinelWMTS.js index 1bc5a9e74f..7d9428cf9d 100644 --- a/apps/SentinelWMTS/SentinelWMTS.js +++ b/apps/SentinelWMTS/SentinelWMTS.js @@ -84,7 +84,7 @@ define(['../../src/WorldWind', lookAt.position.latitude = 48.86; lookAt.position.longitude = 2.37; lookAt.range = 5e4; - this.wwd.camera.setFromLookAt(lookAt); + this.wwd.cameraFromLookAt(lookAt); // Create controllers for the user interface elements. new GoToBox(wwd); diff --git a/apps/SubSurface/SubSurface.js b/apps/SubSurface/SubSurface.js index 12416a67c3..c03bc713a5 100644 --- a/apps/SubSurface/SubSurface.js +++ b/apps/SubSurface/SubSurface.js @@ -67,7 +67,7 @@ define(['../../src/WorldWind', var lookAt = new WorldWind.LookAt(); lookAt.position.latitude = 30; lookAt.position.longitude = -(180 / 12) * ((new Date()).getTimezoneOffset() / 60); - this.wwd.camera.setFromLookAt(lookAt); + this.wwd.cameraFromLookAt(lookAt); this.goToBox = new GoToBox(this.wwd); this.layersPanel = new LayersPanel(this.wwd); diff --git a/apps/USGSSlabs/USGSSlabs.js b/apps/USGSSlabs/USGSSlabs.js index bb010f277d..d478fb6856 100644 --- a/apps/USGSSlabs/USGSSlabs.js +++ b/apps/USGSSlabs/USGSSlabs.js @@ -78,7 +78,7 @@ define(['../../src/WorldWind', var lookAt = new WorldWind.LookAt(); lookAt.position.latitude = 30; lookAt.position.longitude = -(180 / 12) * ((new Date()).getTimezoneOffset() / 60); - this.wwd.camera.setFromLookAt(lookAt); + this.wwd.cameraFromLookAt(lookAt); // Establish the shapes and the controllers to handle picking. this.setupPicking(); diff --git a/apps/USGSWells/USGSWells.js b/apps/USGSWells/USGSWells.js index ab5de679e7..c86644e5dc 100644 --- a/apps/USGSWells/USGSWells.js +++ b/apps/USGSWells/USGSWells.js @@ -91,7 +91,7 @@ define(['../../src/WorldWind', lookAt.range = 1400; lookAt.heading = 90; lookAt.tilt = 60; - this.wwd.camera.setFromLookAt(lookAt); + this.wwd.cameraFromLookAt(lookAt); // Establish the shapes and the controllers to handle picking. this.setupPicking(); diff --git a/examples/Canyon.js b/examples/Canyon.js index 9888f9f7b2..43174b1a20 100644 --- a/examples/Canyon.js +++ b/examples/Canyon.js @@ -22,7 +22,7 @@ requirejs(['./WorldWindShim'], var wwd = new WorldWind.WorldWindow("canvasOne"); var camera = wwd.camera; var lookAt = new WorldWind.LookAt(); - camera.getAsLookAt(lookAt); + wwd.cameraAsLookAt(lookAt); var layers = [ {layer: new WorldWind.BMNGLayer(), enabled: true}, {layer: new WorldWind.BingAerialWithLabelsLayer(null), enabled: true}, diff --git a/examples/Views.js b/examples/Views.js index d4326c06d5..ac3c862785 100644 --- a/examples/Views.js +++ b/examples/Views.js @@ -24,7 +24,7 @@ requirejs(['./WorldWindShim', var wwd = new WorldWind.WorldWindow("canvasOne"); var camera = wwd.camera; var lookAt = new WorldWind.LookAt(); - camera.getAsLookAt(lookAt); + wwd.cameraAsLookAt(lookAt); var layers = [ {layer: new WorldWind.BMNGLayer(), enabled: true}, {layer: new WorldWind.BingAerialWithLabelsLayer(null), enabled: true}, @@ -127,7 +127,7 @@ requirejs(['./WorldWindShim', rangeSlider.slider("disable"); altitudeSlider.slider("enable"); } else { - camera.getAsLookAt(lookAt); + wwd.cameraAsLookAt(lookAt); pos = lookAt.position; view = lookAt; rangeSlider.slider("enable"); @@ -138,7 +138,7 @@ requirejs(['./WorldWindShim', pos = camera.position; view = camera; } else { - camera.getAsLookAt(lookAt); + wwd.cameraAsLookAt(lookAt); pos = lookAt.position; view = lookAt; } @@ -150,7 +150,7 @@ requirejs(['./WorldWindShim', view.roll = rollSlider.slider("value"); if (selectedViewType === "LookAt") { lookAt.range = rangeSlider.slider("value"); - camera.setFromLookAt(lookAt); + wwd.cameraFromLookAt(lookAt); } } updateControls(pos, view); @@ -159,7 +159,7 @@ requirejs(['./WorldWindShim', window.setInterval(function () { var pos, view; - camera.getAsLookAt(lookAt); + wwd.cameraAsLookAt(lookAt); if (currentViewType === "Camera") { pos = camera.position; view = camera; diff --git a/performance/DeepPickingPerformance.js b/performance/DeepPickingPerformance.js index 9f2b5335a9..cd8f4e59f3 100644 --- a/performance/DeepPickingPerformance.js +++ b/performance/DeepPickingPerformance.js @@ -69,7 +69,7 @@ requirejs(['../src/WorldWind', var lookAt = new WorldWind.LookAt(); lookAt.position = new WorldWind.Position(44.2, -94.12, 0); lookAt.range = 625000; - wwd.camera.setFromLookAt(lookAt); + wwd.cameraFromLookAt(lookAt); // Satellite image footprints var footprints = []; diff --git a/src/BasicWorldWindowController.js b/src/BasicWorldWindowController.js index 1bb9c52226..48b519a1ee 100644 --- a/src/BasicWorldWindowController.js +++ b/src/BasicWorldWindowController.js @@ -131,6 +131,8 @@ define([ this.beginPoint = new Vec2(0, 0); this.lastPoint = new Vec2(0, 0); this.lastRotation = 0; + this.lastWheelEvent = 0; + this.activeGestures = 0; /** * Internal use only. @@ -251,9 +253,9 @@ define([ lookAt.position.latitude += forwardDegrees * cosHeading - sideDegrees * sinHeading; lookAt.position.longitude += forwardDegrees * sinHeading + sideDegrees * cosHeading; this.lastPoint.set(tx, ty); - this.applyLookAtLimits(lookAt); - this.wwd.camera.setFromLookAt(lookAt); - this.wwd.redraw(); + this.applyChanges(); + } else if (state === WorldWind.ENDED || state === WorldWind.CANCELLED) { + this.gestureDidEnd(); } }; @@ -295,7 +297,7 @@ define([ // Transform the original view's modelview matrix to account for the gesture's change. var modelview = Matrix.fromIdentity(); - lookAt.computeViewingTransform(globe, modelview); + this.wwd.lookAtToViewingTransform(lookAt, modelview); modelview.multiplyByTranslation(point2[0] - point1[0], point2[1] - point1[1], point2[2] - point1[2]); // Compute the globe point at the screen center from the perspective of the transformed view. @@ -313,9 +315,9 @@ define([ lookAt.heading = params.heading; lookAt.tilt = params.tilt; lookAt.roll = params.roll; - this.applyLookAtLimits(lookAt); - this.wwd.camera.setFromLookAt(lookAt); - this.wwd.redraw(); + this.applyChanges(); + } else if (state === WorldWind.ENDED || state === WorldWind.CANCELLED) { + this.gestureDidEnd(); } }; @@ -337,9 +339,9 @@ define([ // Apply the change in heading and tilt to this view's corresponding properties. lookAt.heading = this.beginLookAt.heading + headingDegrees; lookAt.tilt = this.beginLookAt.tilt + tiltDegrees; - this.applyLookAtLimits(lookAt); - this.wwd.camera.setFromLookAt(lookAt); - this.wwd.redraw(); + this.applyChanges(); + } else if (state === WorldWind.ENDED || state === WorldWind.CANCELLED) { + this.gestureDidEnd(); } }; @@ -356,10 +358,10 @@ define([ // began. var lookAt = this.lookAt; lookAt.range = this.beginLookAt.range / scale; - this.applyLookAtLimits(lookAt); - this.wwd.camera.setFromLookAt(lookAt); - this.wwd.redraw(); + this.applyChanges(); } + } else if (state === WorldWind.ENDED || state === WorldWind.CANCELLED) { + this.gestureDidEnd(); } }; @@ -378,9 +380,9 @@ define([ var lookAt = this.lookAt; lookAt.heading -= rotation - this.lastRotation; this.lastRotation = rotation; - this.applyLookAtLimits(lookAt); - this.wwd.camera.setFromLookAt(lookAt); - this.wwd.redraw(); + this.applyChanges(); + } else if (state === WorldWind.ENDED || state === WorldWind.CANCELLED) { + this.gestureDidEnd(); } }; @@ -397,16 +399,21 @@ define([ var tiltDegrees = -90 * ty / this.wwd.canvas.clientHeight; // Apply the change in heading and tilt to this view's corresponding properties. var lookAt = this.lookAt; - lookAt.tilt = this.beginTilt + tiltDegrees; - this.applyLookAtLimits(lookAt); - this.wwd.camera.setFromLookAt(lookAt); - this.wwd.redraw(); + lookAt.tilt = this.beginLookAt.tilt + tiltDegrees; + this.applyChanges(); + } else if (state === WorldWind.ENDED || state === WorldWind.CANCELLED) { + this.gestureDidEnd(); } }; // Intentionally not documented. BasicWorldWindowController.prototype.handleWheelEvent = function (event) { - var lookAt = this.wwd.camera.getAsLookAt(this.lookAt); + var lookAt = this.lookAt; + var timeStamp = event.timeStamp; + if (timeStamp - this.lastWheelEvent > 500) { + this.wwd.cameraAsLookAt(lookAt); + this.lastWheelEvent = timeStamp; + } // Normalize the wheel delta based on the wheel delta mode. This produces a roughly consistent delta across // browsers and input devices. var normalizedDelta; @@ -425,17 +432,17 @@ define([ // Apply the scale to this view's properties. lookAt.range *= scale; - this.applyLookAtLimits(lookAt); - this.wwd.camera.setFromLookAt(lookAt); - this.wwd.redraw(); + this.applyChanges(); }; /** * Internal use only. - * Limits the properties of a look at view to prevent unwanted navigation behaviour. + * Limits the properties of a look at view to prevent unwanted navigation behaviour and update camera view. * @ignore */ - BasicWorldWindowController.prototype.applyLookAtLimits = function (lookAt) { + BasicWorldWindowController.prototype.applyChanges = function () { + var lookAt = this.lookAt; + // Clamp latitude to between -90 and +90, and normalize longitude to between -180 and +180. lookAt.position.latitude = WWMath.clamp(lookAt.position.latitude, -90, 90); lookAt.position.longitude = Angle.normalizedDegreesLongitude(lookAt.position.longitude); @@ -463,16 +470,10 @@ define([ // Force tilt to 0 when in 2D mode to keep the viewer looking straight down. lookAt.tilt = 0; } - }; - /** - * Documented in super-class. - * @ignore - */ - BasicWorldWindowController.prototype.applyLimits = function () { - var lookAt = this.wwd.camera.getAsLookAt(this.lookAt); - this.applyLookAtLimits(lookAt); - this.wwd.camera.setFromLookAt(lookAt); + // Update camera view. + this.wwd.cameraFromLookAt(lookAt); + this.wwd.redraw(); }; /** @@ -481,8 +482,17 @@ define([ * @ignore */ BasicWorldWindowController.prototype.gestureDidBegin = function () { - this.wwd.camera.getAsLookAt(this.beginLookAt); - this.lookAt.copy(this.beginLookAt); + if (this.activeGestures++ === 0) { + this.wwd.cameraAsLookAt(this.beginLookAt); + this.lookAt.copy(this.beginLookAt); + } + }; + + BasicWorldWindowController.prototype.gestureDidEnd = function () { + // this should always be the case, but we check anyway + if (this.activeGestures > 0) { + this.activeGestures--; + } }; return BasicWorldWindowController; diff --git a/src/WorldWindow.js b/src/WorldWindow.js index 1ac8d94291..631e0eeb29 100644 --- a/src/WorldWindow.js +++ b/src/WorldWindow.js @@ -32,6 +32,7 @@ define([ './error/ArgumentError', './BasicWorldWindowController', './geom/Camera', + './geom/LookAt', './render/DrawContext', './globe/EarthElevationModel', './util/FrameStatistics', @@ -59,6 +60,7 @@ define([ function (ArgumentError, BasicWorldWindowController, Camera, + LookAt, DrawContext, EarthElevationModel, FrameStatistics, @@ -142,6 +144,15 @@ define([ // Internal. Intentionally not documented. this.scratchProjection = Matrix.fromIdentity(); + // Internal. Intentionally not documented. + this.scratchPoint = new Vec3(0, 0, 0); + + // Internal. Intentionally not documented. + this.scratchPosition = new Position(0, 0, 0); + + // Internal. Intentionally not documented. + this.scratchRay = new Line(new Vec3(0, 0, 0), new Vec3(0, 0, 0)); + // Internal. Intentionally not documented. this.hasStencilBuffer = gl.getContextAttributes().stencil; @@ -196,7 +207,7 @@ define([ * @type {Camera} * @default [Camera]{@link Camera} */ - this.camera = new Camera(this); + this.camera = new Camera(); /** * The controller used to manipulate the globe. @@ -498,6 +509,113 @@ define([ this.redrawRequested = true; // redraw during the next animation frame }; + /** + * Sets the properties of this Camera such that it mimics the supplied look at view. Note that repeated conversions + * between a look at and a camera view may result in view errors due to rounding. + * @param {LookAt} lookAt The look at view to mimic. + * @returns {Camera} This camera set to mimic the supplied look at view. + * @throws {ArgumentError} If the specified look at view is null or undefined. + */ + WorldWindow.prototype.cameraFromLookAt = function (lookAt) { + if (!lookAt) { + throw new ArgumentError( + Logger.logMessage(Logger.LEVEL_SEVERE, "Camera", "setFromLookAt", "missingLookAt")); + } + + var globe = this.globe, + ve = this.verticalExaggeration, + position = this.camera.position, + ray = this.scratchRay, + originPoint = this.scratchPoint, + modelview = this.scratchModelview, + origin = this.scratchProjection; + + this.lookAtToViewingTransform(lookAt, modelview); + modelview.extractEyePoint(originPoint); + + globe.computePositionFromPoint(originPoint[0], originPoint[1], originPoint[2], position); + origin.setToIdentity(); + origin.multiplyByLocalCoordinateTransform(originPoint, globe); + modelview.multiplyMatrix(origin); + + this.camera.heading = modelview.extractHeading(lookAt.roll); // disambiguate heading and roll + this.camera.tilt = modelview.extractTilt(); + this.camera.roll = lookAt.roll; // roll passes straight through + + // Check if camera altitude is not under the surface and correct tilt + var elevation = globe.elevationAtLocation(position.latitude, position.longitude) * ve + 10.0; // 10m above surface + if(elevation > position.altitude) { + // Set camera altitude above the surface + position.altitude = elevation; + // Compute new camera point + globe.computePointFromPosition(position.latitude, position.longitude, position.altitude, originPoint); + // Compute look at point + globe.computePointFromPosition(lookAt.position.latitude, lookAt.position.longitude, lookAt.position.altitude, ray.origin); + // Compute normal to globe in look at point + globe.surfaceNormalAtLocation(lookAt.position.latitude, lookAt.position.longitude, ray.direction); + // Calculate tilt angle between new camera point and look at point + originPoint.subtract(ray.origin).normalize(); + var dot = ray.direction.dot(originPoint); + if (dot >= -1 || dot <= 1) { + this.camera.tilt = Math.acos(dot) / Math.PI * 180; + } + } + + return this; + }; + + /** + * Converts the properties of this Camera to those of a look at view. Note that repeated conversions + * between a look at and a camera view may result in view errors due to rounding. + * @param {LookAt} result The look at view to hold the converted properties. + * @param {Position} terrainPosition Picked terrain position. + * @returns {LookAt} A reference to the result parameter. + * @throws {ArgumentError} If the specified result object is null or undefined. + */ + WorldWindow.prototype.cameraAsLookAt = function (result, terrainPosition = this.pick([this.viewport.width / 2, this.viewport.height / 2]).terrainObject()) { + if (!result) { + throw new ArgumentError( + Logger.logMessage(Logger.LEVEL_SEVERE, "Camera", "getAsLookAt", "missingResult")); + } + + var globe = this.globe, + forwardRay = this.scratchRay, + modelview = this.scratchModelview, + originPoint = this.scratchPoint, + originPos = this.scratchPosition, + origin = this.scratchProjection; + + this.cameraToViewingTransform(modelview); + + // Pick terrain located behind the viewport center point + if (terrainPosition) { + // Use picked terrain position including approximate rendered altitude + originPos.copy(terrainPosition); + globe.computePointFromPosition(originPos.latitude, originPos.longitude, originPos.altitude, originPoint); + } else { + // Center is outside the globe - use point on horizon + modelview.extractEyePoint(forwardRay.origin); + modelview.extractForwardVector(forwardRay.direction); + + var horizon = globe.horizonDistance(this.camera.position.altitude); + forwardRay.pointAt(horizon, originPoint); + + globe.computePositionFromPoint(originPoint[0], originPoint[1], originPoint[2], originPos); + } + + origin.setToIdentity(); + origin.multiplyByLocalCoordinateTransform(originPoint, globe); + modelview.multiplyMatrix(origin); + + result.position.copy(originPos); + result.range = -modelview[11]; + result.heading = modelview.extractHeading(this.camera.roll); // disambiguate heading and roll + result.tilt = modelview.extractTilt(); + result.roll = this.camera.roll; // roll passes straight through + + return result; + }; + /** * Requests the WorldWind objects displayed at a specified screen-coordinate point. * @@ -710,7 +828,7 @@ define([ Logger.logMessage(Logger.LEVEL_SEVERE, "WorldWindow", "computeViewingTransform", "missingModelview")); } - this.camera.computeViewingTransform(modelview); + this.cameraToViewingTransform(modelview); if (projection) { var eyePos = this.camera.position, @@ -753,6 +871,45 @@ define([ } }; + /** + * Internal use only. + * Computes the model view matrix for this camera. + * @ignore + */ + WorldWindow.prototype.cameraToViewingTransform = function (modelview) { + if (!modelview) { + throw new ArgumentError( + Logger.logMessage(Logger.LEVEL_SEVERE, "Camera", "cameraToViewingTransform", "missingModelview")); + } + + modelview.setToIdentity(); + modelview.multiplyByFirstPersonModelview(this.camera.position, this.camera.heading, this.camera.tilt, this.camera.roll, this.globe); + + return modelview; + }; + + /** + * Internal use only. + * Computes the model view matrix for this look at view. + * @ignore + */ + WorldWindow.prototype.lookAtToViewingTransform = function (lookAt, modelview) { + if (!lookAt) { + throw new ArgumentError( + Logger.logMessage(Logger.LEVEL_SEVERE, "WorldWindow", "lookAtToViewingTransform", "missingGlobe")); + } + + if (!modelview) { + throw new ArgumentError( + Logger.logMessage(Logger.LEVEL_SEVERE, "WorldWindow", "lookAtToViewingTransform", "missingModelview")); + } + + modelview.setToIdentity(); + modelview.multiplyByLookAtModelview(lookAt.position, lookAt.range, lookAt.heading, lookAt.tilt, lookAt.roll, this.globe); + + return modelview; + }; + /** * Computes the approximate size of a pixel at a specified distance from the eye point. *

diff --git a/src/WorldWindowController.js b/src/WorldWindowController.js index 2a6974616c..1e9f3c3d97 100644 --- a/src/WorldWindowController.js +++ b/src/WorldWindowController.js @@ -124,14 +124,6 @@ define([ } }; - /** - * Called by WorldWindow to allow the controller to enforce navigation limits. Implementation is not required by - * sub-classes. - */ - WorldWindowController.prototype.applyLimits = function () { - - }; - return WorldWindowController; } ); diff --git a/src/geom/Camera.js b/src/geom/Camera.js index 8276cb1da0..a4dc60fc3a 100644 --- a/src/geom/Camera.js +++ b/src/geom/Camera.js @@ -18,37 +18,15 @@ */ define([ '../error/ArgumentError', - '../geom/Line', '../util/Logger', - '../geom/LookAt', - '../geom/Matrix', - '../geom/Position', - '../geom/Vec3', - '../util/WWMath' + '../geom/Position' ], function (ArgumentError, - Line, Logger, - LookAt, - Matrix, - Position, - Vec3, - WWMath) { + Position) { "use strict"; - var Camera = function (worldWindow) { - if (!worldWindow) { - throw new ArgumentError( - Logger.logMessage(Logger.LEVEL_SEVERE, "Camera", "constructor", "missingWorldWindow")); - } - - /** - * The WorldWindow associated with this camera. - * @type {WorldWindow} - * @readonly - */ - this.wwd = worldWindow; - + var Camera = function () { /** * The geographic location of the camera. * @type {Location} @@ -81,63 +59,6 @@ define([ * @default 45 */ this.fieldOfView = 45; - - /** - * Internal use only. - * A temp variable used to hold model view matrices during calculations. Using an object level temp property - * negates the need for ad-hoc allocations and reduces load on the garbage collector. - * @ignore - */ - this.scratchModelview = Matrix.fromIdentity(); - - /** - * Internal use only. - * A temp variable used to hold points during calculations. Using an object level temp property - * negates the need for ad-hoc allocations and reduces load on the garbage collector. - * @ignore - */ - this.scratchPoint = new Vec3(0, 0, 0); - - /** - * Internal use only. - * A temp variable used to hold origin matrices during calculations. Using an object level temp property - * negates the need for ad-hoc allocations and reduces load on the garbage collector. - * @ignore - */ - this.scratchOrigin = Matrix.fromIdentity(); - - /** - * Internal use only. - * A temp variable used to hold positions during calculations. Using an object level temp property - * negates the need for ad-hoc allocations and reduces load on the garbage collector. - * @ignore - */ - this.scratchPosition = new Position(0, 0, 0); - - /** - * Internal use only. - * A temp variable used to hold lines during calculations. Using an object level temp property - * negates the need for ad-hoc allocations and reduces load on the garbage collector. - * @ignore - */ - this.scratchRay = new Line(new Vec3(0, 0, 0), new Vec3(0, 0, 0)); - }; - - /** - * Internal use only. - * Computes the model view matrix for this camera. - * @ignore - */ - Camera.prototype.computeViewingTransform = function (modelview) { - if (!modelview) { - throw new ArgumentError( - Logger.logMessage(Logger.LEVEL_SEVERE, "Camera", "computeViewingTransform", "missingModelview")); - } - - modelview.setToIdentity(); - modelview.multiplyByFirstPersonModelview(this.position, this.heading, this.tilt, this.roll, this.wwd.globe); - - return modelview; }; /** @@ -163,7 +84,7 @@ define([ * @returns {Camera} The new object. */ Camera.prototype.clone = function () { - var clone = new Camera(this.wwd); + var clone = new Camera(); clone.copy(this); return clone; @@ -181,7 +102,6 @@ define([ Logger.logMessage(Logger.LEVEL_SEVERE, "Camera", "copy", "missingObject")); } - this.wwd = copyObject.wwd; this.position.copy(copyObject.position); this.heading = copyObject.heading; this.tilt = copyObject.tilt; @@ -190,113 +110,6 @@ define([ return this; }; - /** - * Sets the properties of this Camera such that it mimics the supplied look at view. Note that repeated conversions - * between a look at and a camera view may result in view errors due to rounding. - * @param {LookAt} lookAt The look at view to mimic. - * @returns {Camera} This camera set to mimic the supplied look at view. - * @throws {ArgumentError} If the specified look at view is null or undefined. - */ - Camera.prototype.setFromLookAt = function (lookAt) { - if (!lookAt) { - throw new ArgumentError( - Logger.logMessage(Logger.LEVEL_SEVERE, "Camera", "setFromLookAt", "missingLookAt")); - } - - var globe = this.wwd.globe, - ve = this.wwd.verticalExaggeration, - ray = this.scratchRay, - originPoint = this.scratchPoint, - modelview = this.scratchModelview, - origin = this.scratchOrigin; - - lookAt.computeViewingTransform(globe, modelview); - modelview.extractEyePoint(originPoint); - - globe.computePositionFromPoint(originPoint[0], originPoint[1], originPoint[2], this.position); - origin.setToIdentity(); - origin.multiplyByLocalCoordinateTransform(originPoint, globe); - modelview.multiplyMatrix(origin); - - this.heading = modelview.extractHeading(lookAt.roll); // disambiguate heading and roll - this.tilt = modelview.extractTilt(); - this.roll = lookAt.roll; // roll passes straight through - - // Check if camera altitude is not under the surface and correct tilt - var elevation = globe.elevationAtLocation(this.position.latitude, this.position.longitude) * ve + 10.0; // 10m above surface - if(elevation > this.position.altitude) { - // Set camera altitude above the surface - this.position.altitude = elevation; - // Compute new camera point - globe.computePointFromPosition(this.position.latitude, this.position.longitude, this.position.altitude, originPoint); - // Compute look at point - globe.computePointFromPosition(lookAt.position.latitude, lookAt.position.longitude, lookAt.position.altitude, ray.origin); - // Compute normal to globe in look at point - globe.surfaceNormalAtLocation(lookAt.position.latitude, lookAt.position.longitude, ray.direction); - // Calculate tilt angle between new camera point and look at point - originPoint.subtract(ray.origin).normalize(); - var dot = ray.direction.dot(originPoint); - if (dot >= -1 || dot <= 1) { - this.tilt = Math.acos(dot) / Math.PI * 180; - } - } - - return this; - }; - - /** - * Converts the properties of this Camera to those of a look at view. Note that repeated conversions - * between a look at and a camera view may result in view errors due to rounding. - * @param {LookAt} result The look at view to hold the converted properties. - * @returns {LookAt} A reference to the result parameter. - * @throws {ArgumentError} If the specified result object is null or undefined. - */ - Camera.prototype.getAsLookAt = function (result) { - if (!result) { - throw new ArgumentError( - Logger.logMessage(Logger.LEVEL_SEVERE, "Camera", "getAsLookAt", "missingResult")); - } - - var globe = this.wwd.globe, - viewport = this.wwd.viewport, - forwardRay = this.scratchRay, - modelview = this.scratchModelview, - originPoint = this.scratchPoint, - originPos = this.scratchPosition, - origin = this.scratchOrigin; - - this.computeViewingTransform(modelview); - - // Pick terrain located behind the viewport center point - var terrainObject = this.wwd.pick([viewport.width / 2, viewport.height / 2]).terrainObject(); - if (terrainObject) { - // Use picked terrain position including approximate rendered altitude - originPos.copy(terrainObject.position); - globe.computePointFromPosition(originPos.latitude, originPos.longitude, originPos.altitude, originPoint); - } else { - // Center is outside the globe - use point on horizon - modelview.extractEyePoint(forwardRay.origin); - modelview.extractForwardVector(forwardRay.direction); - - var horizon = globe.horizonDistance(this.position.altitude); - forwardRay.pointAt(horizon, originPoint); - - globe.computePositionFromPoint(originPoint[0], originPoint[1], originPoint[2], originPos); - } - - origin.setToIdentity(); - origin.multiplyByLocalCoordinateTransform(originPoint, globe); - modelview.multiplyMatrix(origin); - - result.position.copy(originPos); - result.range = -modelview[11]; - result.heading = modelview.extractHeading(this.roll); // disambiguate heading and roll - result.tilt = modelview.extractTilt(); - result.roll = this.roll; // roll passes straight through - - return result; - }; - /** * Returns a string representation of this object. * @returns {String} @@ -306,5 +119,4 @@ define([ }; return Camera; - }); - + }); \ No newline at end of file diff --git a/src/geom/LookAt.js b/src/geom/LookAt.js index f023c15fb6..e6c6c63b03 100644 --- a/src/geom/LookAt.js +++ b/src/geom/LookAt.js @@ -19,12 +19,10 @@ define([ '../error/ArgumentError', '../util/Logger', - '../geom/Matrix', '../geom/Position' ], function (ArgumentError, Logger, - Matrix, Position) { "use strict"; @@ -64,28 +62,6 @@ define([ this.range = 10e6; // TODO: Compute initial range to fit globe in viewport. }; - /** - * Internal use only. - * Computes the model view matrix for this look at view. - * @ignore - */ - LookAt.prototype.computeViewingTransform = function (globe, modelview) { - if (!globe) { - throw new ArgumentError( - Logger.logMessage(Logger.LEVEL_SEVERE, "LookAt", "computeViewingTransform", "missingGlobe")); - } - - if (!modelview) { - throw new ArgumentError( - Logger.logMessage(Logger.LEVEL_SEVERE, "LookAt", "computeViewingTransform", "missingModelview")); - } - - modelview.setToIdentity(); - modelview.multiplyByLookAtModelview(this.position, this.range, this.heading, this.tilt, this.roll, globe); - - return modelview; - }; - /** * Indicates whether the components of this object are equal to those of a specified object. * @param {LookAt} otherLookAt The object to test equality with. May be null or undefined, in which case this diff --git a/src/layer/ViewControlsLayer.js b/src/layer/ViewControlsLayer.js index 631d2f6cf5..ae39bdf03b 100644 --- a/src/layer/ViewControlsLayer.js +++ b/src/layer/ViewControlsLayer.js @@ -697,7 +697,7 @@ define([ if (this.isPointerDown(e) || this.isTouchStart(e)) { this.activeControl = control; this.activeOperation = this.handlePan; - this.wwd.camera.getAsLookAt(this.lookAt); + this.wwd.cameraAsLookAt(this.lookAt); e.preventDefault(); if (this.isTouchStart(e)) { @@ -722,7 +722,7 @@ define([ Location.greatCircleLocation(lookAt.position, heading, -distance, lookAt.position); - thisLayer.wwd.camera.setFromLookAt(lookAt); + thisLayer.wwd.cameraFromLookAt(lookAt); thisLayer.wwd.redraw(); setTimeout(setLookAtLocation, 50); } @@ -743,7 +743,7 @@ define([ if (this.isPointerDown(e) || this.isTouchStart(e)) { this.activeControl = control; this.activeOperation = this.handleZoom; - this.wwd.camera.getAsLookAt(this.lookAt); + this.wwd.cameraAsLookAt(this.lookAt); e.preventDefault(); if (this.isTouchStart(e)) { @@ -760,7 +760,7 @@ define([ } else if (thisLayer.activeControl === thisLayer.zoomOutControl) { lookAt.range *= (1 + thisLayer.zoomIncrement); } - thisLayer.wwd.camera.setFromLookAt(lookAt); + thisLayer.wwd.cameraFromLookAt(lookAt); thisLayer.wwd.redraw(); setTimeout(setRange, 50); } @@ -781,7 +781,7 @@ define([ if (this.isPointerDown(e) || this.isTouchStart(e)) { this.activeControl = control; this.activeOperation = this.handleHeading; - this.wwd.camera.getAsLookAt(this.lookAt); + this.wwd.cameraAsLookAt(this.lookAt); e.preventDefault(); if (this.isTouchStart(e)) { @@ -798,7 +798,7 @@ define([ } else if (thisLayer.activeControl === thisLayer.headingRightControl) { lookAt.heading -= thisLayer.headingIncrement; } - thisLayer.wwd.camera.setFromLookAt(lookAt); + thisLayer.wwd.cameraFromLookAt(lookAt); thisLayer.wwd.redraw(); setTimeout(setHeading, 50); } @@ -818,7 +818,7 @@ define([ if (this.isPointerDown(e) || this.isTouchStart(e)) { this.activeControl = control; this.activeOperation = this.handleTilt; - this.wwd.camera.getAsLookAt(this.lookAt); + this.wwd.cameraAsLookAt(this.lookAt); e.preventDefault(); if (this.isTouchStart(e)) { @@ -837,7 +837,7 @@ define([ lookAt.tilt = Math.min(90, lookAt.tilt + thisLayer.tiltIncrement); } - thisLayer.wwd.camera.setFromLookAt(lookAt); + thisLayer.wwd.cameraFromLookAt(lookAt); thisLayer.wwd.redraw(); setTimeout(setTilt, 50); } diff --git a/src/navigate/LookAtNavigator.js b/src/navigate/LookAtNavigator.js index dfe80798c7..1f6198318d 100644 --- a/src/navigate/LookAtNavigator.js +++ b/src/navigate/LookAtNavigator.js @@ -71,12 +71,12 @@ define([ */ lookAtLocation: { get: function () { - this.wwd.camera.getAsLookAt(this.scratchLookAt); + this.wwd.cameraAsLookAt(this.scratchLookAt); this.scratchLookAtPositionProxy.position.copy(this.scratchLookAt.position); return this.scratchLookAtPositionProxy; }, set: function (value) { - var lookAt = this.wwd.camera.getAsLookAt(this.scratchLookAt); + var lookAt = this.wwd.cameraAsLookAt(this.scratchLookAt); lookAt.position.latitude = value.latitude; lookAt.position.longitude = value.longitude; if (value.altitude) { @@ -85,7 +85,7 @@ define([ else { lookAt.position.altitude = 0; } - this.wwd.camera.setFromLookAt(lookAt); + this.wwd.cameraFromLookAt(lookAt); } }, @@ -96,12 +96,12 @@ define([ */ range: { get: function () { - return this.wwd.camera.getAsLookAt(this.scratchLookAt).range; + return this.wwd.cameraAsLookAt(this.scratchLookAt).range; }, set: function (value) { - var lookAt = this.wwd.camera.getAsLookAt(this.scratchLookAt); + var lookAt = this.wwd.cameraAsLookAt(this.scratchLookAt); lookAt.range = value; - this.wwd.camera.setFromLookAt(lookAt); + this.wwd.cameraFromLookAt(lookAt); } } }); diff --git a/src/navigate/Navigator.js b/src/navigate/Navigator.js index 8319bf5c4a..a3a990b8ca 100644 --- a/src/navigate/Navigator.js +++ b/src/navigate/Navigator.js @@ -71,12 +71,12 @@ define(['../error/ArgumentError', */ heading: { get: function () { - return this.wwd.camera.getAsLookAt(this.scratchLookAt).heading; + return this.wwd.cameraAsLookAt(this.scratchLookAt).heading; }, set: function (value) { - var lookAt = this.wwd.camera.getAsLookAt(this.scratchLookAt); + var lookAt = this.wwd.cameraAsLookAt(this.scratchLookAt); lookAt.heading = value; - this.wwd.camera.setFromLookAt(lookAt); + this.wwd.cameraFromLookAt(lookAt); } }, @@ -87,12 +87,12 @@ define(['../error/ArgumentError', */ tilt: { get: function () { - return this.wwd.camera.getAsLookAt(this.scratchLookAt).tilt; + return this.wwd.cameraAsLookAt(this.scratchLookAt).tilt; }, set: function (value) { - var lookAt = this.wwd.camera.getAsLookAt(this.scratchLookAt); + var lookAt = this.wwd.cameraAsLookAt(this.scratchLookAt); lookAt.tilt = value; - this.wwd.camera.setFromLookAt(lookAt); + this.wwd.cameraFromLookAt(lookAt); } }, @@ -103,12 +103,12 @@ define(['../error/ArgumentError', */ roll: { get: function () { - return this.wwd.camera.getAsLookAt(this.scratchLookAt).roll; + return this.wwd.cameraAsLookAt(this.scratchLookAt).roll; }, set: function (value) { - var lookAt = this.wwd.camera.getAsLookAt(this.scratchLookAt); + var lookAt = this.wwd.cameraAsLookAt(this.scratchLookAt); lookAt.roll = value; - this.wwd.camera.setFromLookAt(lookAt); + this.wwd.cameraFromLookAt(lookAt); } } }); diff --git a/src/util/GoToAnimator.js b/src/util/GoToAnimator.js index 3feced89b1..60081ec968 100644 --- a/src/util/GoToAnimator.js +++ b/src/util/GoToAnimator.js @@ -121,7 +121,7 @@ define([ // Reset the cancellation flag. this.cancelled = false; - this.wwd.camera.getAsLookAt(this.lookAt); + this.wwd.cameraAsLookAt(this.lookAt); // Capture the target position and determine its altitude. this.targetPosition = new Position(position.latitude, position.longitude, position.altitude || this.lookAt.range); @@ -260,7 +260,7 @@ define([ continueAnimation = Math.abs(this.lookAt.range - this.targetPosition.altitude) > 1; } - this.wwd.camera.setFromLookAt(this.lookAt); + this.wwd.cameraFromLookAt(this.lookAt); return continueAnimation; }; @@ -280,7 +280,7 @@ define([ this.lookAt.position.latitude = nextLocation.latitude; this.lookAt.position.longitude = nextLocation.longitude; - this.wwd.camera.setFromLookAt(this.lookAt); + this.wwd.cameraFromLookAt(this.lookAt); // We're done if we're within a meter of the desired location. if (nextDistance < 1 / this.wwd.globe.equatorialRadius) { diff --git a/src/util/KeyboardControls.js b/src/util/KeyboardControls.js index 9e178594ec..cfa6a22a31 100644 --- a/src/util/KeyboardControls.js +++ b/src/util/KeyboardControls.js @@ -120,9 +120,9 @@ define([ * Reset the view to North up. */ KeyboardControls.prototype.resetHeading = function () { - this.wwd.camera.getAsLookAt(this.lookAt); + this.wwd.cameraAsLookAt(this.lookAt); this.lookAt.heading = Number(0); - this.wwd.camera.setFromLookAt(this.lookAt); + this.wwd.cameraFromLookAt(this.lookAt); this.wwd.redraw(); }; @@ -130,10 +130,10 @@ define([ * Reset the view to North up and nadir. */ KeyboardControls.prototype.resetHeadingAndTilt = function () { - this.wwd.camera.getAsLookAt(this.lookAt); + this.wwd.cameraAsLookAt(this.lookAt); this.lookAt.heading = 0; this.lookAt.tilt = 0; - this.wwd.camera.setFromLookAt(this.lookAt); + this.wwd.cameraFromLookAt(this.lookAt); this.wwd.redraw(); }; @@ -154,7 +154,7 @@ define([ */ KeyboardControls.prototype.handleZoom = function (operation) { this.activeOperation = this.handleZoom; - this.wwd.camera.getAsLookAt(this.lookAt); + this.wwd.cameraAsLookAt(this.lookAt); // This function is called by the timer to perform the operation. var self = this, // capture 'this' for use in the function @@ -165,7 +165,7 @@ define([ } else if (operation === "zoomOut") { self.lookAt.range *= (1 + self.zoomIncrement); } - self.wwd.camera.setFromLookAt(self.lookAt); + self.wwd.cameraFromLookAt(self.lookAt); self.wwd.redraw(); setTimeout(setRange, 50); } @@ -179,7 +179,7 @@ define([ */ KeyboardControls.prototype.handlePan = function (operation) { this.activeOperation = this.handlePan; - this.wwd.camera.getAsLookAt(this.lookAt); + this.wwd.cameraAsLookAt(this.lookAt); // This function is called by the timer to perform the operation. var self = this, // capture 'this' for use in the function @@ -207,7 +207,7 @@ define([ heading, distance, self.lookAt.position); - self.wwd.camera.setFromLookAt(self.lookAt); + self.wwd.cameraFromLookAt(self.lookAt); self.wwd.redraw(); setTimeout(setLookAtLocation, 50); } diff --git a/test/BasicWorldWindowController.test.js b/test/BasicWorldWindowController.test.js index 1a08bdb891..e8f9620495 100644 --- a/test/BasicWorldWindowController.test.js +++ b/test/BasicWorldWindowController.test.js @@ -48,7 +48,7 @@ define([ // wwd.worldWindowController.handlePanOrDrag2D(recognizer); // // var lookAt = new LookAt(); - // wwd.camera.getAsLookAt(lookAt); + // wwd.cameraAsLookAt(lookAt); // // expect(lookAt.range).toEqual(10000000); // expect(lookAt.tilt).toEqual(0); diff --git a/test/geom/Camera.test.js b/test/geom/Camera.test.js index 4d0d190294..45132ddd68 100644 --- a/test/geom/Camera.test.js +++ b/test/geom/Camera.test.js @@ -36,10 +36,10 @@ define([ // var lookAt = new LookAt(); // camera.position = new Position(30, -110, 10000000); // for (var a = 0; a < 90; a++) { - // camera.getAsLookAt(lookAt); + // wwd.cameraAsLookAt(lookAt); // console.log(lookAt.toString()); // lookAt.heading = a; - // camera.setFromLookAt(lookAt); + // wwd.cameraFromLookAt(lookAt); // console.log(camera.toString()); // console.log('==='); // } @@ -48,7 +48,7 @@ define([ // var testView = wwd.camera; // testView.position = new Position(30, -110, 10e6); // var result = Matrix.fromIdentity(); - // testView.computeViewingTransform(result); + // wwd.cameraToViewingTransform(result); // var expectedModelview = new Matrix( // -0.3420201433256687, 0.0, 0.9396926207859083, 0.0, // 0.46984631039295405, 0.8660254037844386, 0.17101007166283433, 18504.157, @@ -61,7 +61,7 @@ define([ // var testView = wwd.camera; // testView.position = new Position(0, 0, 10e6); // var result = Matrix.fromIdentity(); - // testView.computeViewingTransform(result); + // wwd.cameraToViewingTransform(result); // var expectedModelview = new Matrix( // 1.0, 0.0, 0.0, -0.0, // 0.0, 1.0, 0.0, -0.0, @@ -73,7 +73,7 @@ define([ // var testView = wwd.camera; // testView.position = new Position(30, 0, 10e6); // var result = Matrix.fromIdentity(); - // testView.computeViewingTransform(result); + // wwd.cameraToViewingTransform(result); // var expectedModelview = new Matrix( // 1.0,0.0,0.0,-0.0, // 0.0,0.8660254037844387,-0.5,18504.125313225202, @@ -87,7 +87,7 @@ define([ // var lookAt = new LookAt(); // lookAt.range = 1.131761199603698E7; // lookAt.position = new Position(30, -90, 0); - // camera.setFromLookAt(lookAt); + // wwd.cameraFromLookAt(lookAt); // expect(camera.position.latitude).toBeCloseTo(30.0, 6); // expect(camera.position.longitude).toBeCloseTo(-90.0, 6); // expect(camera.position.altitude).toBeCloseTo(1.131761199603698E7, 6); @@ -104,7 +104,7 @@ define([ // lookAt.roll = 5; // lookAt.heading = 15; // lookAt.position = new Position(30, -90, 0); - // camera.setFromLookAt(lookAt); + // wwd.cameraFromLookAt(lookAt); // expect(camera.position.latitude).toBeCloseTo(26.90254740059172, 6); // expect(camera.position.longitude).toBeCloseTo(-90.92754733364956, 6); // expect(camera.position.altitude).toBeCloseTo(11302122.347, 3); @@ -117,14 +117,14 @@ define([ describe("Indicates whether the components of two cameras are equal", function () { it("Equal cameras", function () { - var c1 = new Camera("test"); - var c2 = new Camera("test"); + var c1 = new Camera(); + var c2 = new Camera(); expect(c1.equals(c2)).toBe(true); }); it("Not equal cameras", function () { - var c1 = new Camera("test"); - var c2 = new Camera("test"); + var c1 = new Camera(); + var c2 = new Camera(); c2.heading = c1.heading + 1; expect(c1.equals(c2)).toBe(false); c2.heading = c1.heading; @@ -144,7 +144,7 @@ define([ }); it("Null comparison", function () { - var c1 = new Camera("test"); + var c1 = new Camera(); expect(c1.equals(null)).toBe(false); expect(c1.equals(undefined)).toBe(false); }); @@ -152,8 +152,8 @@ define([ describe("Camera cloning and copying", function () { it("Correctly copy cameras", function () { - var c1 = new Camera("test"); - var c2 = new Camera("test"); + var c1 = new Camera(); + var c2 = new Camera(); c2.heading = c1.heading + 1; c2.tilt = c1.tilt + 1; c2.roll = c1.roll + 1; @@ -163,7 +163,7 @@ define([ }); it("Correctly clones cameras", function () { - var c1 = new Camera("test"); + var c1 = new Camera(); c1.heading = c1.heading + 1; c1.tilt = c1.tilt + 1; c1.roll = c1.roll + 1; diff --git a/test/geom/LookAt.test.js b/test/geom/LookAt.test.js index c119c32ec8..a261612c95 100644 --- a/test/geom/LookAt.test.js +++ b/test/geom/LookAt.test.js @@ -34,7 +34,7 @@ define([ lookAt.position = new Position(30, -90, 0); lookAt.range = 1.131761199603698E7; var result = Matrix.fromIdentity(); - lookAt.computeViewingTransform(wwd.globe, result); + wwd.lookAtToViewingTransform(lookAt, result); var expectedModelview = new Matrix( 6.123233995736767E-17, -3.0814879110195774E-33, 1.0, 2.0679515313825692E-25, 0.5, 0.8660254037844387, -3.0616169978683836E-17, 18504.125313223805, diff --git a/test/util/TestUtils.test.js b/test/util/TestUtils.test.js index 525ce4a0a5..c3642b041a 100644 --- a/test/util/TestUtils.test.js +++ b/test/util/TestUtils.test.js @@ -81,7 +81,7 @@ define([ var wwd = new MockWorldWindow(); wwd.globe = mockGlobe; wwd.drawContext = dc; - wwd.camera = new Camera(wwd); + wwd.camera = new Camera(); wwd.worldWindowController = new BasicWorldWindowController(wwd); wwd.viewport = viewport; wwd.depthBits = 24;