/*
* Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports BasicWorldWindowController
*/
define([
'./geom/Angle',
'./ArcBallCamera',
'./error/ArgumentError',
'./gesture/ClickRecognizer',
'./gesture/DragRecognizer',
'./gesture/FlingRecognizer',
'./gesture/GestureRecognizer',
'./geom/Line',
'./geom/Location',
'./util/Logger',
'./geom/Matrix',
'./util/measure/MeasurerUtils',
'./gesture/PanRecognizer',
'./gesture/PinchRecognizer',
'./geom/Position',
'./gesture/RotationRecognizer',
'./gesture/TapRecognizer',
'./gesture/TiltRecognizer',
'./geom/Vec2',
'./geom/Vec3',
'./WorldWindowController',
'./util/WWMath'
],
function (Angle,
ArcBallCamera,
ArgumentError,
ClickRecognizer,
DragRecognizer,
FlingRecognizer,
GestureRecognizer,
Line,
Location,
Logger,
Matrix,
MeasurerUtils,
PanRecognizer,
PinchRecognizer,
Position,
RotationRecognizer,
TapRecognizer,
TiltRecognizer,
Vec2,
Vec3,
WorldWindowController,
WWMath) {
"use strict";
/**
* Constructs a window controller with basic capabilities.
* @alias BasicWorldWindowController
* @constructor
* @augments WorldWindowController
* @classDesc This class provides the default window controller for WorldWind for controlling the globe via user interaction.
* @param {WorldWindow} worldWindow The WorldWindow associated with this layer.
*/
var BasicWorldWindowController = function (worldWindow) {
WorldWindowController.call(this, worldWindow); // base class checks for a valid worldWindow
/**
* Enables/disables the zoom to mouse effect.
* When used on a touch device the location of the mouse is the center between the touch points.
*
* When zooming in, the location of the mouse will move towards the center of the screen.
* When zooming out, the location of the mouse will away from the center of the screen.
*
* @type {Boolean}
* @default true
*/
this.zoomToMouseEnabled = true;
// Intentionally not documented.
this.primaryDragRecognizer = new DragRecognizer(this.wwd, null);
this.primaryDragRecognizer.addListener(this);
// Intentionally not documented.
this.secondaryDragRecognizer = new DragRecognizer(this.wwd, null);
this.secondaryDragRecognizer.addListener(this);
this.secondaryDragRecognizer.button = 2; // secondary mouse button
// Intentionally not documented.
this.panRecognizer = new PanRecognizer(this.wwd, null);
this.panRecognizer.addListener(this);
// Intentionally not documented.
this.pinchRecognizer = new PinchRecognizer(this.wwd, null);
this.pinchRecognizer.addListener(this);
// Intentionally not documented.
this.rotationRecognizer = new RotationRecognizer(this.wwd, null);
this.rotationRecognizer.addListener(this);
// Intentionally not documented.
this.tiltRecognizer = new TiltRecognizer(this.wwd, null);
this.tiltRecognizer.addListener(this);
// Establish the dependencies between gesture recognizers. The pan, pinch and rotate gesture may recognize
// simultaneously with each other.
this.panRecognizer.recognizeSimultaneouslyWith(this.pinchRecognizer);
this.panRecognizer.recognizeSimultaneouslyWith(this.rotationRecognizer);
this.pinchRecognizer.recognizeSimultaneouslyWith(this.rotationRecognizer);
// Since the tilt gesture is a subset of the pan gesture, pan will typically recognize before tilt,
// effectively suppressing tilt. Establish a dependency between the other touch gestures and tilt to provide
// tilt an opportunity to recognize.
this.panRecognizer.requireRecognizerToFail(this.tiltRecognizer);
this.pinchRecognizer.requireRecognizerToFail(this.tiltRecognizer);
this.rotationRecognizer.requireRecognizerToFail(this.tiltRecognizer);
// Intentionally not documented.
// this.tapRecognizer = new TapRecognizer(this.wwd, null);
// this.tapRecognizer.addListener(this);
// Intentionally not documented.
// this.clickRecognizer = new ClickRecognizer(this.wwd, null);
// this.clickRecognizer.addListener(this);
// Intentionally not documented.
this.flingRecognizer = new FlingRecognizer(this.wwd, null);
this.flingRecognizer.addListener(this);
this.flingRecognizer.recognizeSimultaneouslyWith(this.primaryDragRecognizer);
this.flingRecognizer.recognizeSimultaneouslyWith(this.panRecognizer);
this.flingRecognizer.recognizeSimultaneouslyWith(this.pinchRecognizer);
this.flingRecognizer.recognizeSimultaneouslyWith(this.rotationRecognizer);
// Intentionally not documented.
this.beginPoint = new Vec2(0, 0);
this.lastPoint = new Vec2(0, 0);
this.beginHeading = 0;
this.beginTilt = 0;
this.beginRange = 0;
this.lastRotation = 0;
this.pointerLocation = null;
this.dragDelta = new Vec2(0, 0);
this.dragLastLocation = new Location(0, 0);
this.flingAnimationId = -1;
this.beginIntersectionPoint = new Vec3(0, 0, 0);
this.lastIntersectionPoint = new Vec3(0, 0, 0);
this.beginIntersectionPosition = new Position(0, 0, 0);
this.lastIntersectionPosition = new Position(0, 0, 0);
this.rotationVector = new Vec3(0, 0, 0);
this.scratchRay = new Line(new Vec3(0, 0, 0), new Vec3(0, 0, 0));
this.scratchMatrix = Matrix.fromIdentity();
};
BasicWorldWindowController.prototype = Object.create(WorldWindowController.prototype);
// Intentionally not documented.
BasicWorldWindowController.prototype.onGestureEvent = function (e) {
var handled = WorldWindowController.prototype.onGestureEvent.call(this, e);
if (e.type === 'mousemove' || (e.pointerType === 'mouse' && e.type === 'pointermove')) {
this.pointerLocation = null;
}
if (!handled) {
if (e.type === "wheel") {
handled = true;
this.handleWheelEvent(e);
}
else {
for (var i = 0, len = GestureRecognizer.allRecognizers.length; i < len; i++) {
var recognizer = GestureRecognizer.allRecognizers[i];
if (recognizer.target === this.wwd) {
handled |= recognizer.onGestureEvent(e); // use or-assignment to indicate if any recognizer handled the event
}
}
}
}
return handled;
};
// Intentionally not documented.
BasicWorldWindowController.prototype.gestureStateChanged = function (recognizer) {
if (recognizer.state === WorldWind.BEGAN || recognizer.state === WorldWind.RECOGNIZED) {
this.cancelFlingAnimation();
}
var isArcBall = this.wwd.navigator.camera instanceof ArcBallCamera;
if (recognizer === this.primaryDragRecognizer || recognizer === this.panRecognizer) {
if (isArcBall) {
this.handlePanOrDrag(recognizer);
}
else {
this.handleSecondaryDrag(recognizer);
}
}
else if (recognizer === this.secondaryDragRecognizer) {
if (isArcBall) {
this.handleSecondaryDrag(recognizer);
}
}
else if (recognizer === this.pinchRecognizer) {
this.handlePinch(recognizer);
}
else if (recognizer === this.rotationRecognizer) {
this.handleRotation(recognizer);
}
else if (recognizer === this.tiltRecognizer) {
this.handleTilt(recognizer);
}
// else if (recognizer === this.clickRecognizer || recognizer === this.tapRecognizer) {
// this.handleClickOrTap(recognizer);
// }
else if (recognizer === this.flingRecognizer) {
this.handleFling(recognizer);
}
};
// Intentionally not documented.
// BasicWorldWindowController.prototype.handleClickOrTap = function (recognizer) {
// if (recognizer.state === WorldWind.RECOGNIZED) {
// var pickPoint = this.wwd.canvasCoordinates(recognizer.clientX, recognizer.clientY);
//
// // Identify if the top picked object contains a URL for hyperlinking
// var pickList = this.wwd.pick(pickPoint);
// var topObject = pickList.topPickedObject();
// // If the url object was appended, open the hyperlink
// if (topObject &&
// topObject.userObject &&
// topObject.userObject.userProperties &&
// topObject.userObject.userProperties.url) {
// window.open(topObject.userObject.userProperties.url, "_blank");
// }
// }
// };
// Intentionally not documented.
BasicWorldWindowController.prototype.handlePanOrDrag = function (recognizer) {
if (this.wwd.globe.is2D()) {
this.handlePanOrDrag2D(recognizer);
} else {
this.handlePanOrDrag3D(recognizer);
}
};
// Intentionally not documented.
BasicWorldWindowController.prototype.handlePanOrDrag3D = function (recognizer) {
var wwd = this.wwd;
var state = recognizer.state;
var x = recognizer.clientX;
var y = recognizer.clientY;
if (state === WorldWind.BEGAN) {
var ray = wwd.rayThroughScreenPoint(wwd.canvasCoordinates(x, y));
if (!wwd.globe.intersectsLine(ray, this.beginIntersectionPoint)) {
return;
}
wwd.globe.computePositionFromPoint(this.beginIntersectionPoint[0], this.beginIntersectionPoint[1], this.beginIntersectionPoint[2], this.beginIntersectionPosition);
this.beginPoint.set(x, y);
this.lastPoint.set(x, y);
}
else if (state === WorldWind.CHANGED) {
var didMove = this.move3D(x, y);
if (didMove) {
this.beginPoint.copy(this.lastPoint);
this.lastPoint.set(x, y);
this.applyLimits();
this.dragDelta.set(
this.dragLastLocation.latitude - wwd.navigator.lookAtLocation.latitude,
Angle.normalizedDegreesLongitude(
this.dragLastLocation.longitude - wwd.navigator.lookAtLocation.longitude
)
);
this.dragLastLocation.copy(wwd.navigator.lookAtLocation);
wwd.redraw();
}
}
};
// Intentionally not documented.
BasicWorldWindowController.prototype.move3D = function(x, y) {
var wwd = this.wwd;
var ray = wwd.rayThroughScreenPoint(wwd.canvasCoordinates(x, y));
if (!wwd.globe.intersectsLine(ray, this.lastIntersectionPoint)) {
return false;
}
wwd.globe.computePositionFromPoint(this.lastIntersectionPoint[0], this.lastIntersectionPoint[1], this.lastIntersectionPoint[2], this.lastIntersectionPosition);
if (this.isSphereRotation(this.lastIntersectionPosition)) {
var rotationAngle = this.computeRotationVectorAndAngle(this.beginIntersectionPoint, this.lastIntersectionPoint, this.rotationVector);
var isFling = false;
return this.rotateShpere(this.rotationVector, rotationAngle, isFling);
}
else {
var deltaLat = this.lastIntersectionPosition.latitude - this.beginIntersectionPosition.latitude;
var deltaLon = this.lastIntersectionPosition.longitude - this.beginIntersectionPosition.longitude;
var lookAtLocation = wwd.navigator.lookAtLocation;
lookAtLocation.latitude -= deltaLat;
lookAtLocation.longitude -= deltaLon;
return true;
}
};
// Intentionally not documented.
BasicWorldWindowController.prototype.isSphereRotation = function (lastIntersectionPosition) {
var looAtLatitude = this.wwd.navigator.lookAtLocation.latitude;
var heading = this.wwd.navigator.heading;
return (heading !== 0 || Math.abs(looAtLatitude) > 75 || Math.abs(lastIntersectionPosition.latitude) > 75);
};
// Intentionally not documented.
BasicWorldWindowController.prototype.computeRotationVectorAndAngle = function (vec1, vec2, rotationVector) {
var angleRad = MeasurerUtils.angleBetweenVectors(vec1, vec2);
var angle = angleRad * Angle.RADIANS_TO_DEGREES;
rotationVector.copy(vec1);
rotationVector.cross(vec2);
rotationVector.normalize();
return angle;
};
// Intentionally not documented.
BasicWorldWindowController.prototype.rotateShpere = function (rotationVector, angle, isFling) {
if (!isFinite(angle) || !isFinite(rotationVector[0]) || !isFinite(rotationVector[1]) || !isFinite(rotationVector[2])) {
return false;
}
var wwd = this.wwd;
var navigator = wwd.navigator;
var viewMatrix = this.scratchMatrix;
var altitude = navigator.lookAtLocation.altitude;
var tilt = navigator.tilt;
navigator.tilt = 0;
wwd.computeViewingTransform(null, viewMatrix);
viewMatrix.multiplyByRotation(rotationVector[0], rotationVector[1], rotationVector[2], angle);
viewMatrix.extractEyePoint(this.scratchRay.origin);
viewMatrix.extractForwardVector(this.scratchRay.direction);
if (!wwd.globe.intersectsLine(this.scratchRay, this.lastIntersectionPoint)) {
navigator.tilt = tilt;
return false;
}
var params = viewMatrix.extractViewingParameters(this.lastIntersectionPoint, navigator.roll, wwd.globe, {});
if (!isFling && Math.abs(navigator.heading) < 5 && Math.abs(navigator.lookAtLocation.latitude < 70) && Math.abs(this.lastIntersectionPosition.latitude) < 70) {
navigator.heading = Math.round(params.heading);
}
else {
navigator.heading = params.heading;
}
navigator.lookAtLocation.copy(params.origin);
navigator.lookAtLocation.altitude = altitude;
navigator.tilt = tilt;
return true;
}
// Intentionally not documented.
BasicWorldWindowController.prototype.handlePanOrDrag2D = function (recognizer) {
var state = recognizer.state,
x = recognizer.clientX,
y = recognizer.clientY,
tx = recognizer.translationX,
ty = recognizer.translationY;
var navigator = this.wwd.navigator;
if (state === WorldWind.BEGAN) {
this.beginPoint.set(x, y);
this.lastPoint.set(x, y);
} else if (state === WorldWind.CHANGED) {
this.move2D(tx, ty);
}
};
BasicWorldWindowController.prototype.move2D = function (tx, ty) {
var navigator = this.wwd.navigator;
var x1 = this.lastPoint[0],
y1 = this.lastPoint[1],
x2 = this.beginPoint[0] + tx,
y2 = this.beginPoint[1] + ty;
this.lastPoint.set(x2, y2);
var globe = this.wwd.globe,
ray = this.wwd.rayThroughScreenPoint(this.wwd.canvasCoordinates(x1, y1)),
point1 = new Vec3(0, 0, 0),
point2 = new Vec3(0, 0, 0),
origin = new Vec3(0, 0, 0);
if (!globe.intersectsLine(ray, point1)) {
return;
}
ray = this.wwd.rayThroughScreenPoint(this.wwd.canvasCoordinates(x2, y2));
if (!globe.intersectsLine(ray, point2)) {
return;
}
// Transform the original navigator state's modelview matrix to account for the gesture's change.
var modelview = Matrix.fromIdentity();
this.wwd.computeViewingTransform(null, 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 navigator state.
modelview.extractEyePoint(ray.origin);
modelview.extractForwardVector(ray.direction);
if (!globe.intersectsLine(ray, origin)) {
return;
}
// Convert the transformed modelview matrix to a set of navigator properties, then apply those
// properties to this navigator.
var params = modelview.extractViewingParameters(origin, navigator.roll, globe, {});
navigator.lookAtLocation.copy(params.origin);
navigator.range = params.range;
navigator.heading = params.heading;
navigator.tilt = params.tilt;
navigator.roll = params.roll;
this.applyLimits();
this.wwd.redraw();
this.dragDelta.set(x1 - x2, y1 - y2);
this.dragLastLocation.copy(navigator.lookAtLocation);
};
// Intentionally not documented.
BasicWorldWindowController.prototype.handleFling = function (recognizer) {
if (this.wwd.globe.is2D()) {
this.handleFling2D(recognizer);
} else {
this.handleFling3D(recognizer);
}
};
BasicWorldWindowController.prototype.handleFling2D = function (recognizer) {
if (recognizer.state === WorldWind.RECOGNIZED) {
var wwd = this.wwd;
var navigator = wwd.navigator;
var animationDuration = 1500; // ms
this.beginPoint.copy(this.lastPoint);
// Last location set by this animation
var lastLocation = new Location();
lastLocation.copy(this.dragLastLocation);
// Start time of this animation
var startTime = new Date();
var beginTx = this.dragDelta[0];
var beginTy = this.dragDelta[1];
var tx = beginTx;
var ty = beginTy;
// Animation Loop
var controller = this;
var animate = function () {
controller.flingAnimationId = -1;
if (!lastLocation.equals(navigator.lookAtLocation)) {
// The navigator was changed externally. Aborting the animation.
return;
}
// Compute the delta to apply using a sinusoidal out easing
var elapsed = (new Date() - startTime) / animationDuration;
elapsed = elapsed > 1 ? 1 : elapsed;
var value = Math.sin(elapsed * Math.PI / 2);
tx -= beginTx - beginTx * value;
ty -= beginTy - beginTy * value;
controller.move2D(tx, ty);
// Save the new current lookAt location
lastLocation.copy(navigator.lookAtLocation);
// If we haven't reached the animation duration, request a new frame
if (elapsed < 1) {
controller.flingAnimationId = requestAnimationFrame(animate);
}
};
this.flingAnimationId = requestAnimationFrame(animate);
}
};
// Intentionally not documented.
BasicWorldWindowController.prototype.handleFling3D = function (recognizer) {
if (recognizer.state === WorldWind.RECOGNIZED) {
var navigator = this.wwd.navigator;
var animationDuration = 1500; // ms
// Initial delta at the beginning of this animation
var initialDelta = new Vec2();
initialDelta.copy(this.dragDelta);
// Last location set by this animation
var lastLocation = new Location();
lastLocation.copy(this.dragLastLocation);
var wwd = this.wwd;
var rotationAngle = 0;
var ray = wwd.rayThroughScreenPoint(wwd.canvasCoordinates(this.lastPoint[0], this.lastPoint[1]));
if (!wwd.globe.intersectsLine(ray, this.lastIntersectionPoint)) {
return;
}
wwd.globe.computePositionFromPoint(this.lastIntersectionPoint[0], this.lastIntersectionPoint[1], this.lastIntersectionPoint[2], this.lastIntersectionPosition);
var shouldUseSphereRotation = this.isSphereRotation(this.lastIntersectionPosition);
if (shouldUseSphereRotation) {
var ray = wwd.rayThroughScreenPoint(wwd.canvasCoordinates(this.beginPoint[0], this.beginPoint[1]));
if (!wwd.globe.intersectsLine(ray, this.beginIntersectionPoint)) {
return;
}
rotationAngle = this.computeRotationVectorAndAngle(this.beginIntersectionPoint, this.lastIntersectionPoint, this.rotationVector);
if (!isFinite(rotationAngle) || !isFinite(this.rotationVector[0]) || !isFinite(this.rotationVector[1]) || !isFinite(this.rotationVector[2])) {
return;
}
}
// Start time of this animation
var startTime = new Date();
// Animation Loop
var controller = this;
var animate = function () {
controller.flingAnimationId = -1;
if (!lastLocation.equals(navigator.lookAtLocation)) {
// The navigator was changed externally. Aborting the animation.
return;
}
// Compute the delta to apply using a sinusoidal out easing
var elapsed = (new Date() - startTime) / animationDuration;
elapsed = elapsed > 1 ? 1 : elapsed;
var value = Math.sin(elapsed * Math.PI / 2);
if (shouldUseSphereRotation) {
var angle = rotationAngle * (1 - value);
var isFling = true;
controller.rotateShpere(controller.rotationVector, angle, isFling);
}
else {
var deltaLatitude = initialDelta[0] - initialDelta[0] * value;
var deltaLongitude = initialDelta[1] - initialDelta[1] * value;
navigator.lookAtLocation.latitude -= deltaLatitude;
navigator.lookAtLocation.longitude -= deltaLongitude;
}
controller.applyLimits();
controller.wwd.redraw();
// Save the new current lookAt location
lastLocation.copy(navigator.lookAtLocation);
// If we haven't reached the animation duration, request a new frame
if (elapsed < 1) {
controller.flingAnimationId = requestAnimationFrame(animate);
}
};
this.flingAnimationId = requestAnimationFrame(animate);
}
};
// Intentionally not documented.
BasicWorldWindowController.prototype.cancelFlingAnimation = function () {
if (this.flingAnimationId !== -1) {
cancelAnimationFrame(this.flingAnimationId);
this.flingAnimationId = -1;
}
};
// Intentionally not documented.
BasicWorldWindowController.prototype.handleSecondaryDrag = function (recognizer) {
var state = recognizer.state,
tx = recognizer.translationX,
ty = recognizer.translationY;
var navigator = this.wwd.navigator;
if (state === WorldWind.BEGAN) {
this.beginHeading = navigator.heading;
this.beginTilt = navigator.tilt;
} else if (state === WorldWind.CHANGED) {
// Compute the current translation from screen coordinates to degrees. Use the canvas dimensions as a
// metric for converting the gesture translation to a fraction of an angle.
var headingDegrees = 180 * tx / this.wwd.canvas.clientWidth,
tiltDegrees = 90 * ty / this.wwd.canvas.clientHeight;
// Apply the change in heading and tilt to this navigator's corresponding properties.
navigator.heading = this.beginHeading + headingDegrees;
navigator.tilt = this.beginTilt + tiltDegrees;
this.applyLimits();
this.wwd.redraw();
}
};
// Intentionally not documented.
BasicWorldWindowController.prototype.handlePinch = function (recognizer) {
var navigator = this.wwd.navigator;
var state = recognizer.state,
scale = recognizer.scale;
if (state === WorldWind.BEGAN) {
this.beginRange = navigator.range;
this.pointerLocation = null;
} else if (state === WorldWind.CHANGED) {
if (scale !== 0) {
var newRange = this.beginRange / scale;
var amount = newRange / navigator.range;
this.moveZoom(recognizer.clientX, recognizer.clientY, amount);
// Apply the change in pinch scale to this navigator's range, relative to the range when the gesture
// began.
navigator.range = newRange;
this.applyLimits();
this.wwd.redraw();
}
}
};
// Intentionally not documented.
BasicWorldWindowController.prototype.handleRotation = function (recognizer) {
var navigator = this.wwd.navigator;
var state = recognizer.state,
rotation = recognizer.rotation;
if (state === WorldWind.BEGAN) {
this.lastRotation = 0;
} else if (state === WorldWind.CHANGED) {
// Apply the change in gesture rotation to this navigator's current heading. We apply relative to the
// current heading rather than the heading when the gesture began in order to work simultaneously with
// pan operations that also modify the current heading.
navigator.heading -= rotation - this.lastRotation;
this.lastRotation = rotation;
this.applyLimits();
this.wwd.redraw();
}
};
// Intentionally not documented.
BasicWorldWindowController.prototype.handleTilt = function (recognizer) {
var navigator = this.wwd.navigator;
var state = recognizer.state,
ty = recognizer.translationY;
if (state === WorldWind.BEGAN) {
this.beginTilt = navigator.tilt;
} else if (state === WorldWind.CHANGED) {
// Compute the gesture translation from screen coordinates to degrees. Use the canvas dimensions as a
// metric for converting the translation to a fraction of an angle.
var tiltDegrees = -90 * ty / this.wwd.canvas.clientHeight;
// Apply the change in heading and tilt to this navigator's corresponding properties.
navigator.tilt = this.beginTilt + tiltDegrees;
this.applyLimits();
this.wwd.redraw();
}
};
// Intentionally not documented.
BasicWorldWindowController.prototype.handleWheelEvent = function (event) {
this.cancelFlingAnimation();
var navigator = this.wwd.navigator;
// Normalize the wheel delta based on the wheel delta mode. This produces a roughly consistent delta across
// browsers and input devices.
var normalizedDelta;
if (event.deltaMode === WheelEvent.DOM_DELTA_PIXEL) {
normalizedDelta = event.deltaY;
} else if (event.deltaMode === WheelEvent.DOM_DELTA_LINE) {
normalizedDelta = event.deltaY * 40;
} else if (event.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
normalizedDelta = event.deltaY * 400;
}
// Compute a zoom scale factor by adding a fraction of the normalized delta to 1. When multiplied by the
// navigator's range, this has the effect of zooming out or zooming in depending on whether the delta is
// positive or negative, respectfully.
var scale = 1 + (normalizedDelta / 1000);
this.moveZoom(event.clientX, event.clientY, scale);
// Apply the scale to this navigator's properties.
navigator.range *= scale;
this.applyLimits();
this.wwd.redraw();
};
// Intentionally not documented.
BasicWorldWindowController.prototype.locationAtPickPoint = function (x, y) {
var coordinates = this.wwd.canvasCoordinates(x, y);
var pickList = this.wwd.pickTerrain(coordinates);
for (var i = 0; i < pickList.objects.length; i++) {
var pickedObject = pickList.objects[i];
if (pickedObject.isTerrain) {
var pickedPosition = pickedObject.position;
if (pickedPosition) {
return new Location(pickedPosition.latitude, pickedPosition.longitude);
}
}
}
};
// Intentionally not documented.
BasicWorldWindowController.prototype.moveZoom = function (x, y, amount) {
if (!this.zoomToMouseEnabled) {
return;
}
if (amount === 1) {
return;
}
if (!this.pointerLocation) {
this.pointerLocation = this.locationAtPickPoint(x, y);
}
if (!this.pointerLocation) {
return;
}
var lookAtLocation = this.wwd.navigator.lookAtLocation;
var location;
if (amount < 1) {
var distanceRemaining = Location.greatCircleDistance(lookAtLocation,
this.pointerLocation) * this.wwd.globe.equatorialRadius;
if (distanceRemaining <= 50000) {
location = this.pointerLocation;
}
else {
location = Location.interpolateGreatCircle(amount, this.pointerLocation,
lookAtLocation, new Location(0, 0));
}
}
else {
var intermediateLocation = Location.interpolateGreatCircle(1 / amount, this.pointerLocation,
lookAtLocation, new Location(0, 0));
var distanceRadians = Location.greatCircleDistance(lookAtLocation, intermediateLocation);
var greatCircleAzimuthDegrees = Location.greatCircleAzimuth(lookAtLocation, intermediateLocation);
location = Location.greatCircleLocation(lookAtLocation, greatCircleAzimuthDegrees - 180,
distanceRadians, new Location(0, 0));
}
lookAtLocation.latitude = location.latitude;
lookAtLocation.longitude = location.longitude;
};
// Documented in super-class.
BasicWorldWindowController.prototype.applyLimits = function () {
this.wwd.navigator.camera.applyLimits();
};
return BasicWorldWindowController;
}
);