/*******************************************************************************
* Copyright 2014 Geoscience Australia
*
* 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.
******************************************************************************/
package au.gov.ga.earthsci.worldwind.common.view.orbit;
import gov.nasa.worldwind.View;
import gov.nasa.worldwind.WorldWind;
import gov.nasa.worldwind.animation.AngleAnimator;
import gov.nasa.worldwind.animation.AnimationController;
import gov.nasa.worldwind.animation.AnimationSupport;
import gov.nasa.worldwind.animation.Animator;
import gov.nasa.worldwind.animation.CompoundAnimator;
import gov.nasa.worldwind.animation.DoubleAnimator;
import gov.nasa.worldwind.animation.Interpolator;
import gov.nasa.worldwind.animation.MoveToDoubleAnimator;
import gov.nasa.worldwind.animation.MoveToPositionAnimator;
import gov.nasa.worldwind.animation.PositionAnimator;
import gov.nasa.worldwind.animation.RotateToAngleAnimator;
import gov.nasa.worldwind.animation.ScheduledInterpolator;
import gov.nasa.worldwind.animation.SmoothInterpolator;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.awt.AbstractViewInputHandler;
import gov.nasa.worldwind.awt.BasicViewInputHandler;
import gov.nasa.worldwind.awt.ViewInputActionHandler;
import gov.nasa.worldwind.awt.ViewInputAttributes;
import gov.nasa.worldwind.awt.ViewInputHandler;
import gov.nasa.worldwind.geom.Angle;
import gov.nasa.worldwind.geom.LatLon;
import gov.nasa.worldwind.geom.Position;
import gov.nasa.worldwind.geom.Vec4;
import gov.nasa.worldwind.util.Logging;
import gov.nasa.worldwind.util.PropertyAccessor;
import gov.nasa.worldwind.view.ViewPropertyAccessor;
import gov.nasa.worldwind.view.ViewUtil;
import gov.nasa.worldwind.view.orbit.BasicOrbitView;
import gov.nasa.worldwind.view.orbit.BasicOrbitViewLimits;
import gov.nasa.worldwind.view.orbit.OrbitView;
import gov.nasa.worldwind.view.orbit.OrbitViewInputHandler;
import gov.nasa.worldwind.view.orbit.OrbitViewLimits;
import gov.nasa.worldwind.view.orbit.OrbitViewPropertyAccessor;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.util.Date;
/**
* {@link ViewInputHandler} implementation based on the
* {@link OrbitViewInputHandler}, but only using methods from the
* {@link OrbitView} interface instead of assuming the view implementation is
* {@link BasicOrbitView}. This allows use with alternative {@link OrbitView}
* implementations.
*
* @author Michael de Hoog (michael.dehoog@ga.gov.au)
*/
public class BaseOrbitViewInputHandler extends BasicViewInputHandler
{
protected AnimationController gotoAnimControl = new AnimationController();
protected AnimationController uiAnimControl = new AnimationController();
protected static final String VIEW_ANIM_HEADING = "ViewAnimHeading"; //$NON-NLS-1$
protected static final String VIEW_ANIM_PITCH = "ViewAnimPitch"; //$NON-NLS-1$
protected static final String VIEW_ANIM_ROLL = "ViewAnimRoll"; //$NON-NLS-1$
protected static final String VIEW_ANIM_HEADING_PITCH = "ViewAnimHeadingPitch"; //$NON-NLS-1$
protected static final String VIEW_ANIM_POSITION = "ViewAnimPosition"; //$NON-NLS-1$
protected static final String VIEW_ANIM_CENTER = "ViewAnimCenter"; //$NON-NLS-1$
protected static final String VIEW_ANIM_ZOOM = "ViewAnimZoom"; //$NON-NLS-1$
protected static final String VIEW_ANIM_PAN = "ViewAnimPan"; //$NON-NLS-1$
protected static final String VIEW_ANIM_APP = "ViewAnimApp"; //$NON-NLS-1$
public static final String ORBITVIEW_RESET_ROLL = "gov.nasa.worldwind.ViewResetRoll"; //$NON-NLS-1$
/** Action handler to reset roll. */
public class ResetRollActionListener extends ViewInputActionHandler
{
@Override
public boolean inputActionPerformed(AbstractViewInputHandler inputHandler,
java.awt.event.MouseEvent mouseEvent, ViewInputAttributes.ActionAttributes viewAction)
{
onResetRoll(viewAction);
return true;
}
}
/**
* Create a new input handler.
*/
public BaseOrbitViewInputHandler()
{
this.initializeInputHandlers();
}
/**
* Initialize input handlers specific to ObitView.
*/
protected void initializeInputHandlers()
{
// OrbitView allows application controllers to set the view's roll, but it does not provide user controls to
// change the roll. Add an input handler that will reset the roll to zero when the user clicks the mouse so that
// the user can easily get back to normal roll state.
// Reset roll on mouse click
ViewInputAttributes.ActionAttributes.MouseAction[] resetRollMouseEvents =
{
new ViewInputAttributes.ActionAttributes.MouseAction(MouseEvent.BUTTON1_DOWN_MASK)
};
// Set up the input attributes for reset roll
this.getAttributes().setMouseActionAttributes(
ORBITVIEW_RESET_ROLL, // Action to map to mouse button
0, // Modifiers, none in this case
ViewInputAttributes.ActionAttributes.ActionTrigger.ON_PRESS, // The event that triggers the action
resetRollMouseEvents, // Input actions to map to the behavior
ViewInputAttributes.DEFAULT_KEY_ROLL_MIN_VALUE,
ViewInputAttributes.DEFAULT_KEY_ROLL_MAX_VALUE,
false, // Disable smoothing
0.0); // Smoothing value
// Add the action listener
ViewInputAttributes.ActionAttributes actionAttrs =
this.getAttributes().getActionMap(ViewInputAttributes.DEVICE_MOUSE).getActionAttributes(
ORBITVIEW_RESET_ROLL);
actionAttrs.setMouseActionListener(new ResetRollActionListener());
}
//**************************************************************//
//******************** View Change Events ********************//
//**************************************************************//
@Override
protected void onMoveTo(Position focalPosition, ViewInputAttributes.ActionAttributes actionAttribs)
{
}
@Override
protected void onMoveTo(Position focalPosition, ViewInputAttributes.DeviceAttributes deviceAttributes,
ViewInputAttributes.ActionAttributes actionAttribs)
{
this.stopAllAnimators();
View view = this.getView();
if (view == null) // include this test to ensure any derived implementation performs it
{
return;
}
if (view instanceof OrbitView)
{
// We're treating a speed parameter as smoothing here. A greater speed results in greater smoothing and
// slower response. Therefore the min speed used at lower altitudes ought to be *greater* than the max
// speed used at higher altitudes.
//double[] values = actionAttribs.getValues();
double smoothing = this.getScaleValueZoom(actionAttribs);
if (!actionAttribs.isEnableSmoothing())
{
smoothing = 0.0;
}
MoveToPositionAnimator centerAnimator = new MoveToPositionAnimator(
view.getEyePosition(), focalPosition, smoothing,
OrbitViewPropertyAccessor.createCenterPositionAccessor((OrbitView) view));
this.gotoAnimControl.put(VIEW_ANIM_CENTER, centerAnimator);
view.firePropertyChange(AVKey.VIEW, null, view);
}
}
@Override
protected void onHorizontalTranslateAbs(Angle latitudeChange, Angle longitudeChange,
ViewInputAttributes.ActionAttributes actionAttribs)
{
this.stopGoToAnimators();
this.stopUserInputAnimators(VIEW_ANIM_HEADING, VIEW_ANIM_PITCH, VIEW_ANIM_ZOOM);
OrbitView view = this.getView();
if (view == null) // include this test to ensure any derived implementation performs it
{
return;
}
if (latitudeChange.equals(Angle.ZERO) && longitudeChange.equals(Angle.ZERO))
{
return;
}
Position newPosition = view.getCenterPosition().add(new Position(
latitudeChange, longitudeChange, 0.0));
this.setCenterPosition(view, uiAnimControl, newPosition, actionAttribs);
}
@Override
protected void onHorizontalTranslateRel(double forwardInput, double sideInput,
double totalForwardInput, double totalSideInput,
ViewInputAttributes.DeviceAttributes deviceAttributes,
ViewInputAttributes.ActionAttributes actionAttributes)
{
this.stopGoToAnimators();
this.stopUserInputAnimators(VIEW_ANIM_HEADING, VIEW_ANIM_PITCH, VIEW_ANIM_ZOOM);
if (actionAttributes.getMouseActions() != null)
{
// Normalize the forward and right magnitudes.
double length = Math.sqrt(forwardInput * forwardInput + sideInput * sideInput);
if (length > 0.0)
{
forwardInput /= length;
sideInput /= length;
}
Point point = constrainToSourceBounds(getMousePoint(), getWorldWindow());
Point lastPoint = constrainToSourceBounds(getLastMousePoint(), getWorldWindow());
if (getSelectedPosition() == null)
{
// Compute the current selected position if none exists. This happens if the user starts dragging when
// the cursor is off the globe, then drags the cursor onto the globe.
setSelectedPosition(computeSelectedPosition());
}
else if (computeSelectedPosition() == null)
{
// User dragged the cursor off the globe. Clear the selected position to ensure a new one will be
// computed if the user drags the cursor back to the globe.
setSelectedPosition(null);
}
else if (computeSelectedPointAt(point) == null || computeSelectedPointAt(lastPoint) == null)
{
// User selected a position that is won't work for dragging. Probably the selected elevation is above the
// eye elevation, in which case dragging becomes unpredictable. Clear the selected position to ensure
// a new one will be computed if the user drags the cursor to a valid position.
setSelectedPosition(null);
}
Vec4 vec = computeSelectedPointAt(point);
Vec4 lastVec = computeSelectedPointAt(lastPoint);
// Cursor is on the globe, pan between the two positions.
if (vec != null && lastVec != null)
{
// Compute the change in view location given two screen points and corresponding world vectors.
LatLon latlon = getChangeInLocation(lastPoint, point, lastVec, vec);
onHorizontalTranslateAbs(latlon.getLatitude(), latlon.getLongitude(), actionAttributes);
return;
}
Point movement = ViewUtil.subtract(point, lastPoint);
forwardInput = movement.y;
sideInput = -movement.x;
}
// Cursor is off the globe, we potentially want to simulate globe dragging.
// or this is a keyboard event.
Angle forwardChange = Angle.fromDegrees(
forwardInput * getScaleValueHorizTransRel(deviceAttributes, actionAttributes));
Angle sideChange = Angle.fromDegrees(
sideInput * getScaleValueHorizTransRel(deviceAttributes, actionAttributes));
onHorizontalTranslateRel(forwardChange, sideChange, actionAttributes);
}
@Override
protected void onHorizontalTranslateRel(Angle forwardChange, Angle sideChange,
ViewInputAttributes.ActionAttributes actionAttribs)
{
OrbitView view = this.getView();
if (view == null) // include this test to ensure any derived implementation performs it
{
return;
}
if (forwardChange.equals(Angle.ZERO) && sideChange.equals(Angle.ZERO))
{
return;
}
double sinHeading = view.getHeading().sin();
double cosHeading = view.getHeading().cos();
double latChange = cosHeading * forwardChange.getDegrees() - sinHeading * sideChange.getDegrees();
double lonChange = sinHeading * forwardChange.getDegrees() + cosHeading * sideChange.getDegrees();
Position newPosition = view.getCenterPosition().add(
Position.fromDegrees(latChange, lonChange, 0.0));
this.setCenterPosition(view, this.uiAnimControl, newPosition, actionAttribs);
}
@Override
protected void onResetHeading(ViewInputAttributes.ActionAttributes actionAttribs)
{
this.stopAllAnimators();
View view = this.getView();
if (view == null) // include this test to ensure any derived implementation performs it
{
return;
}
this.addHeadingAnimator(view.getHeading(), Angle.ZERO);
}
/**
* Called when user input causes the roll to reset.
*
* @param actionAttribs
* input that caused the change.
*/
protected void onResetRoll(ViewInputAttributes.ActionAttributes actionAttribs)
{
View view = this.getView();
if (view == null) // include this test to ensure any derived implementation performs it
{
return;
}
if (Angle.ZERO.equals(view.getRoll())) // Don't need to reset if roll is already zero
{
return;
}
this.addRollAnimator(view.getRoll(), Angle.ZERO);
}
@Override
protected void onResetHeadingPitchRoll(ViewInputAttributes.ActionAttributes actionAttribs)
{
this.stopAllAnimators();
View view = this.getView();
if (view == null) // include this test to ensure any derived implementation performs it
{
return;
}
this.addHeadingPitchRollAnimator(view.getHeading(), Angle.ZERO, view.getPitch(), Angle.ZERO, view.getRoll(),
Angle.ZERO);
}
@Override
protected void onRotateView(double headingInput, double pitchInput,
double totalHeadingInput, double totalPitchInput,
ViewInputAttributes.DeviceAttributes deviceAttributes,
ViewInputAttributes.ActionAttributes actionAttributes)
{
this.stopGoToAnimators();
this.stopUserInputAnimators(VIEW_ANIM_CENTER, VIEW_ANIM_ZOOM);
if (actionAttributes.getMouseActions() != null)
{
// Switch the direction of heading change depending on whether the cursor is above or below
// the center of the screen.
if (getMousePoint().y < getView().getViewport().height / 2)
{
headingInput = -headingInput;
}
}
else
{
double length = Math.sqrt(headingInput * headingInput + pitchInput * pitchInput);
if (length > 0.0)
{
headingInput /= length;
pitchInput /= length;
}
}
Angle headingChange = Angle.fromDegrees(
headingInput * getScaleValueRotate(actionAttributes));
Angle pitchChange = Angle.fromDegrees(
pitchInput * getScaleValueRotate(actionAttributes));
onRotateView(headingChange, pitchChange, actionAttributes);
}
@Override
protected void onRotateView(Angle headingChange, Angle pitchChange,
ViewInputAttributes.ActionAttributes actionAttribs)
{
OrbitView view = this.getView();
if (view == null) // include this test to ensure any derived implementation performs it
{
return;
}
if (!headingChange.equals(Angle.ZERO))
{
this.changeHeading(view, uiAnimControl, headingChange, actionAttribs);
}
if (!pitchChange.equals(Angle.ZERO))
{
this.changePitch(view, uiAnimControl, pitchChange, actionAttribs);
}
}
@Override
protected void onVerticalTranslate(double translateChange, double totalTranslateChange,
ViewInputAttributes.DeviceAttributes deviceAttributes,
ViewInputAttributes.ActionAttributes actionAttributes)
{
this.stopGoToAnimators();
this.stopUserInputAnimators(VIEW_ANIM_CENTER, VIEW_ANIM_HEADING, VIEW_ANIM_PITCH);
double zoomChange = translateChange * getScaleValueRotate(actionAttributes);
onVerticalTranslate(zoomChange, actionAttributes);
}
@Override
protected void onVerticalTranslate(double translateChange, ViewInputAttributes.ActionAttributes actionAttribs)
{
OrbitView view = this.getView();
if (view == null) // include this test to ensure any derived implementation performs it
{
return;
}
if (translateChange == 0)
{
return;
}
this.changeZoom(view, uiAnimControl, translateChange, actionAttribs);
}
//**************************************************************//
//******************** **********************//
//**************************************************************//
/**
* Apply the changes prior to rendering a frame. The method will step
* animators, applying the results of those steps to the View, then if a
* focus on terrain is required, it will do that as well.
*
**/
@Override
public void apply()
{
super.apply();
View view = this.getView();
if (view == null)
{
return;
}
if (this.gotoAnimControl.stepAnimators())
{
view.firePropertyChange(AVKey.VIEW, null, view);
}
else
{
this.gotoAnimControl.clear();
}
if (this.uiAnimControl.stepAnimators())
{
view.firePropertyChange(AVKey.VIEW, null, view);
}
else
{
this.uiAnimControl.clear();
}
}
//**************************************************************//
//******************** Property Change Events ****************//
//**************************************************************//
@Override
protected void handlePropertyChange(java.beans.PropertyChangeEvent e)
{
super.handlePropertyChange(e);
//noinspection StringEquality
if (e.getPropertyName() == OrbitView.CENTER_STOPPED)
{
this.handleOrbitViewCenterStopped();
}
}
protected void stopAllAnimators()
{
// Explicitly stop all animators, then clear the data structure which holds them. If we remove an animator
// from this data structure without invoking stop(), the animator has no way of knowing it was forcibly stopped.
// An animator's owner - potentially an object other than this ViewInputHandler - may need to know if an
// animator has been forcibly stopped in order to react correctly to that event.
this.uiAnimControl.stopAnimations();
this.gotoAnimControl.stopAnimations();
this.uiAnimControl.clear();
this.gotoAnimControl.clear();
}
protected void stopGoToAnimators()
{
// Explicitly stop all 'go to' animators, then clear the data structure which holds them. If we remove an
// animator from this data structure without invoking stop(), the animator has no way of knowing it was forcibly
// stopped. An animator's owner - likely an application object other - may need to know if an animator has been
// forcibly stopped in order to react correctly to that event.
this.gotoAnimControl.stopAnimations();
this.gotoAnimControl.clear();
}
protected void stopUserInputAnimators(Object... names)
{
for (Object o : names)
{
if (this.uiAnimControl.get(o) != null)
{
// Explicitly stop the 'ui' animator, then clear it from the data structure which holds it. If we remove
// an animator from this data structure without invoking stop(), the animator has no way of knowing it
// was forcibly stopped. Though applications cannot access the 'ui' animator data structure, stopping
// the animators here is the correct action.
this.uiAnimControl.get(o).stop();
this.uiAnimControl.remove(o);
}
}
}
@Override
protected void handleViewStopped()
{
this.stopAllAnimators();
}
protected void handleOrbitViewCenterStopped()
{
// The "center stopped" message instructs components to stop modifying the OrbitView's center position.
// Therefore we stop any center position animations started by this view controller.
this.stopUserInputAnimators(VIEW_ANIM_CENTER);
}
//**************************************************************//
//******************** View State Change Utilities ***********//
//**************************************************************//
protected void setCenterPosition(OrbitView view,
AnimationController animControl,
Position position, ViewInputAttributes.ActionAttributes attrib)
{
double smoothing = attrib.getSmoothingValue();
if (!(attrib.isEnableSmoothing() && this.isEnableSmoothing()))
{
smoothing = 0.0;
}
if (smoothing == 0)
{
if (animControl.get(VIEW_ANIM_CENTER) != null)
{
animControl.remove(VIEW_ANIM_CENTER);
}
Position newPosition = BasicOrbitViewLimits.limitCenterPosition(position, view.getOrbitViewLimits());
view.setCenterPosition(newPosition);
}
else
{
MoveToPositionAnimator centerAnimator =
(MoveToPositionAnimator) animControl.get(VIEW_ANIM_CENTER);
Position cur = view.getCenterPosition();
if (centerAnimator == null || !centerAnimator.hasNext())
{
Position newPosition = computeNewPosition(position, view.getOrbitViewLimits());
centerAnimator = new MoveToPositionAnimator(
cur, newPosition, smoothing,
OrbitViewPropertyAccessor.createCenterPositionAccessor(view));
animControl.put(VIEW_ANIM_CENTER, centerAnimator);
}
else
{
Position newPosition = new Position(
centerAnimator.getEnd().getLatitude().add(
position.getLatitude()).subtract(cur.getLatitude()),
centerAnimator.getEnd().getLongitude().add(
position.getLongitude()).subtract(cur.getLongitude()),
centerAnimator.getEnd().getElevation() +
position.getElevation() - cur.getElevation());
newPosition = computeNewPosition(newPosition, view.getOrbitViewLimits());
centerAnimator.setEnd(newPosition);
}
centerAnimator.start();
}
view.firePropertyChange(AVKey.VIEW, null, view);
}
//protected void setHeading(BasicOrbitView view,
// AnimationController animControl,
// Angle heading)
//{
// view.computeAndSetViewCenterIfNeeded();
// RotateToAngleAnimator angleAnimator = new RotateToAngleAnimator(
// view.getHeading(), heading, .95,
// ViewPropertyAccessor.createHeadingAccessor(view));
// animControl.put(VIEW_ANIM_HEADING, angleAnimator);
//
// view.firePropertyChange(AVKey.VIEW, null, view);
//}
protected void changeHeading(OrbitView view,
AnimationController animControl,
Angle change, ViewInputAttributes.ActionAttributes attrib)
{
view.focusOnViewportCenter();
double smoothing = attrib.getSmoothingValue();
if (!(attrib.isEnableSmoothing() && this.isEnableSmoothing()))
{
smoothing = 0.0;
}
if (smoothing == 0)
{
if (animControl.get(VIEW_ANIM_HEADING) != null)
{
animControl.remove(VIEW_ANIM_HEADING);
}
Angle newHeading = computeNewHeading(view.getHeading().add(change), view.getOrbitViewLimits());
view.setHeading(newHeading);
}
else
{
RotateToAngleAnimator angleAnimator = (RotateToAngleAnimator)
animControl.get(VIEW_ANIM_HEADING);
if (angleAnimator == null || !angleAnimator.hasNext())
{
Angle newHeading = computeNewHeading(view.getHeading().add(change), view.getOrbitViewLimits());
angleAnimator = new RotateToAngleAnimator(
view.getHeading(), newHeading, smoothing,
ViewPropertyAccessor.createHeadingAccessor(view));
animControl.put(VIEW_ANIM_HEADING, angleAnimator);
}
else
{
Angle newHeading = computeNewHeading(angleAnimator.getEnd().add(change), view.getOrbitViewLimits());
angleAnimator.setEnd(newHeading);
}
angleAnimator.start();
}
view.firePropertyChange(AVKey.VIEW, null, view);
}
//protected void setPitch(BasicOrbitView view,
// AnimationController animControl,
// Angle pitch)
//{
// view.computeAndSetViewCenterIfNeeded();
// RotateToAngleAnimator angleAnimator = new RotateToAngleAnimator(
// view.getPitch(), pitch, .95,
// ViewPropertyAccessor.createPitchAccessor(view));
// animControl.put(VIEW_ANIM_PITCH, angleAnimator);
// view.firePropertyChange(AVKey.VIEW, null, view);
//}
protected void changePitch(OrbitView view,
AnimationController animControl,
Angle change, ViewInputAttributes.ActionAttributes attrib)
{
view.focusOnViewportCenter();
double smoothing = attrib.getSmoothingValue();
if (!(attrib.isEnableSmoothing() && this.isEnableSmoothing()))
{
smoothing = 0.0;
}
if (smoothing == 0.0)
{
if (animControl.get(VIEW_ANIM_PITCH) != null)
{
animControl.remove(VIEW_ANIM_PITCH);
}
Angle newPitch = computeNewPitch(view.getPitch().add(change), view.getOrbitViewLimits());
view.setPitch(newPitch);
}
else
{
RotateToAngleAnimator angleAnimator = (RotateToAngleAnimator) animControl.get(VIEW_ANIM_PITCH);
if (angleAnimator == null || !angleAnimator.hasNext())
{
// Create an angle animator which tilts the view to the specified new pitch. If this changes causes the
// view to collide with the surface, this animator is set to stop. We enable this behavior by using a
// {@link #CollisionAwarePitchAccessor} angle accessor and setting the animator's stopOnInvalidState
// property to 'true'.
Angle newPitch = computeNewPitch(view.getPitch().add(change), view.getOrbitViewLimits());
angleAnimator = new RotateToAngleAnimator(
view.getPitch(), newPitch, smoothing,
new CollisionAwarePitchAccessor(view));
angleAnimator.setStopOnInvalidState(true);
animControl.put(VIEW_ANIM_PITCH, angleAnimator);
}
else
{
Angle newPitch = computeNewPitch(angleAnimator.getEnd().add(change), view.getOrbitViewLimits());
angleAnimator.setEnd(newPitch);
}
angleAnimator.start();
}
view.firePropertyChange(AVKey.VIEW, null, view);
}
protected void changeZoom(OrbitView view,
AnimationController animControl,
double change, ViewInputAttributes.ActionAttributes attrib)
{
view.focusOnViewportCenter();
double smoothing = attrib.getSmoothingValue();
if (!(attrib.isEnableSmoothing() && this.isEnableSmoothing()))
{
smoothing = 0.0;
}
if (smoothing == 0.0)
{
if (animControl.get(VIEW_ANIM_ZOOM) != null)
{
animControl.remove(VIEW_ANIM_ZOOM);
}
view.setZoom(computeNewZoom(view.getZoom(), change, view.getOrbitViewLimits()));
}
else
{
double newZoom;
MoveToDoubleAnimator zoomAnimator =
(MoveToDoubleAnimator) animControl.get(VIEW_ANIM_ZOOM);
if (zoomAnimator == null || !zoomAnimator.hasNext())
{
newZoom = computeNewZoom(view.getZoom(), change, view.getOrbitViewLimits());
zoomAnimator = new MoveToDoubleAnimator(newZoom, smoothing,
OrbitViewPropertyAccessor.createZoomAccessor(view));
animControl.put(VIEW_ANIM_ZOOM, zoomAnimator);
}
else
{
newZoom = computeNewZoom(zoomAnimator.getEnd(), change, view.getOrbitViewLimits());
zoomAnimator.setEnd(newZoom);
}
zoomAnimator.start();
}
view.firePropertyChange(AVKey.VIEW, null, view);
}
protected static Position computeNewPosition(Position position, OrbitViewLimits limits)
{
Position newPosition = new Position(
Angle.normalizedLatitude(position.getLatitude()),
Angle.normalizedLongitude(position.getLongitude()),
position.getElevation());
return BasicOrbitViewLimits.limitCenterPosition(newPosition, limits);
}
protected static Angle computeNewHeading(Angle heading, OrbitViewLimits limits)
{
Angle newHeading = heading.normalizedLongitude();
return BasicOrbitViewLimits.limitHeading(newHeading, limits);
}
protected static Angle computeNewPitch(Angle pitch, OrbitViewLimits limits)
{
Angle newPitch = pitch.normalizedLongitude();
return BasicOrbitViewLimits.limitPitch(newPitch, limits);
}
protected static double computeNewZoom(double curZoom, double change, OrbitViewLimits limits)
{
double logCurZoom = curZoom != 0 ? Math.log(curZoom) : 0;
double newZoom = Math.exp(logCurZoom + change);
return BasicOrbitViewLimits.limitZoom(newZoom, limits);
}
//**************************************************************//
//******************** Input Handler Property Accessors ******//
//**************************************************************//
/**
* CollisionAwarePitchAccessor implements an
* {@link gov.nasa.worldwind.util.PropertyAccessor.AngleAccessor} interface
* onto the pitch property of an
* {@link gov.nasa.worldwind.view.orbit.OrbitView}. In addition to accessing
* the pitch property, this implementation is aware of view-surface
* collisions caused by setting the pitch property. If a call to
* {@link #setAngle(gov.nasa.worldwind.geom.Angle)} causes the view to
* collide with the surface, then the call returns false indicating to the
* caller that the set operation was not entirely successful.
*/
protected static class CollisionAwarePitchAccessor implements PropertyAccessor.AngleAccessor
{
protected OrbitView orbitView;
/**
* Creates a new CollisionAwarePitchAccessor with the specified
* OrbitView, but otherwise does nothing.
*
* @param orbitView
* the OrbitView who's pitch will be accessed.
*
* @throws IllegalArgumentException
* if the orbitView is null.
*/
public CollisionAwarePitchAccessor(OrbitView orbitView)
{
if (orbitView == null)
{
String message = Logging.getMessage("nullValue.OrbitViewIsNull"); //$NON-NLS-1$
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.orbitView = orbitView;
}
/**
* Returns the pitch property value from this accessor's view.
*
* @return the pitch from this accessor's view.
*/
@Override
public Angle getAngle()
{
return this.orbitView.getPitch();
}
/**
* Sets the pitch property of this accessor's view to the specified
* value. If the value is null, setting the view's pitch causes a
* surface collision, or setting the view's pitch causes an exception,
* this returns false. Otherwise this returns true.
*
* @param value
* the value to set as this view's pitch property.
*
* @return true if the pitch property was successfully set, and false
* otherwise.
*/
@Override
public boolean setAngle(Angle value)
{
if (value == null)
{
return false;
}
// If the view supports surface collision detection, then clear the view's collision flag prior to
// making any property changes.
if (this.orbitView.isDetectCollisions())
{
this.orbitView.hadCollisions();
}
try
{
this.orbitView.setPitch(value);
}
catch (Exception e)
{
String message = Logging.getMessage("generic.ExceptionWhileChangingView"); //$NON-NLS-1$
Logging.logger().log(java.util.logging.Level.SEVERE, message, e);
return false;
}
// If the view supports surface collision detection, then return false if the collision flag is set,
// otherwise return true.
return !(this.orbitView.isDetectCollisions() && this.orbitView.hadCollisions());
}
}
//**************************************************************//
//******************** Scaling Utilities *********************//
//**************************************************************//
protected double getScaleValueHorizTransRel(
ViewInputAttributes.DeviceAttributes deviceAttributes, ViewInputAttributes.ActionAttributes actionAttributes)
{
View view = this.getView();
if (view == null)
{
return 0.0;
}
if (view instanceof OrbitView)
{
double[] range = actionAttributes.getValues();
// If this is an OrbitView, we use the zoom value to set the scale
double radius = this.getWorldWindow().getModel().getGlobe().getRadius();
double t = getScaleValue(range[0], range[1],
((OrbitView) view).getZoom(), 3.0 * radius, true);
return (t);
}
else
{
// Any other view, use the base class scaling method
return (super.getScaleValueElevation(deviceAttributes, actionAttributes));
}
}
protected double getScaleValueRotate(
ViewInputAttributes.ActionAttributes actionAttributes)
{
View view = this.getView();
if (view == null)
{
return 0.0;
}
if (view instanceof OrbitView)
{
double[] range = actionAttributes.getValues();
// If this is an OrbitView, we use the zoom value to set the scale
double radius = this.getWorldWindow().getModel().getGlobe().getRadius();
double t = getScaleValue(range[0], range[1],
((OrbitView) view).getZoom(), 3.0 * radius, false);
return (t);
}
return (1.0);
}
protected double getScaleValueZoom(ViewInputAttributes.ActionAttributes actionAttributes)
{
View view = this.getView();
if (view == null)
{
return 0.0;
}
if (view instanceof OrbitView)
{
double[] range = actionAttributes.getValues();
// If this is an OrbitView, we use the zoom value to set the scale
double radius = this.getWorldWindow().getModel().getGlobe().getRadius();
double t = ((OrbitView) view).getZoom() / (3.0 * radius);
t = (t < 0 ? 0 : (t > 1 ? 1 : t));
return range[0] * (1.0 - t) + range[1] * t;
}
return (1.0);
}
public void addPanToAnimator(Position beginCenterPos, Position endCenterPos,
Angle beginHeading, Angle endHeading,
Angle beginPitch, Angle endPitch,
double beginZoom, double endZoom, long timeToMove, boolean endCenterOnSurface)
{
int altitudeMode = endCenterOnSurface ? WorldWind.CLAMP_TO_GROUND : WorldWind.ABSOLUTE;
OrbitView orbitView = this.getView();
FlyToOrbitViewAnimator panAnimator = FlyToOrbitViewAnimator.createFlyToOrbitViewAnimator(orbitView,
beginCenterPos, endCenterPos, beginHeading, endHeading, beginPitch, endPitch,
beginZoom, endZoom, timeToMove, altitudeMode);
this.gotoAnimControl.put(VIEW_ANIM_PAN, panAnimator);
this.getView().firePropertyChange(AVKey.VIEW, null, this.getView());
}
public void addPanToAnimator(Position beginCenterPos, Position endCenterPos,
Angle beginHeading, Angle endHeading,
Angle beginPitch, Angle endPitch,
double beginZoom, double endZoom, boolean endCenterOnSurface)
{
int altitudeMode = endCenterOnSurface ? WorldWind.CLAMP_TO_GROUND : WorldWind.ABSOLUTE;
// TODO: scale on mid-altitude?
final long MIN_LENGTH_MILLIS = 2000;
final long MAX_LENGTH_MILLIS = 10000;
long timeToMove = AnimationSupport.getScaledTimeMillisecs(
beginCenterPos, endCenterPos,
MIN_LENGTH_MILLIS, MAX_LENGTH_MILLIS);
OrbitView orbitView = this.getView();
FlyToOrbitViewAnimator panAnimator = FlyToOrbitViewAnimator.createFlyToOrbitViewAnimator(orbitView,
beginCenterPos, endCenterPos, beginHeading, endHeading, beginPitch, endPitch,
beginZoom, endZoom, timeToMove, altitudeMode);
this.gotoAnimControl.put(VIEW_ANIM_PAN, panAnimator);
this.getView().firePropertyChange(AVKey.VIEW, null, this.getView());
}
public void addPanToAnimator(Position centerPos, Angle heading, Angle pitch, double zoom,
long timeToMove, boolean endCenterOnSurface)
{
OrbitView view = this.getView();
addPanToAnimator(view.getCenterPosition(), centerPos,
view.getHeading(), heading,
view.getPitch(), pitch, view.getZoom(), zoom, timeToMove, endCenterOnSurface);
this.getView().firePropertyChange(AVKey.VIEW, null, this.getView());
}
public void addPanToAnimator(Position centerPos, Angle heading, Angle pitch, double zoom,
boolean endCenterOnSurface)
{
OrbitView view = this.getView();
addPanToAnimator(view.getCenterPosition(), centerPos,
view.getHeading(), heading,
view.getPitch(), pitch, view.getZoom(), zoom, endCenterOnSurface);
this.getView().firePropertyChange(AVKey.VIEW, null, this.getView());
}
public void addPanToAnimator(Position centerPos, Angle heading, Angle pitch, double zoom)
{
OrbitView view = this.getView();
addPanToAnimator(view.getCenterPosition(), centerPos,
view.getHeading(), heading,
view.getPitch(), pitch, view.getZoom(), zoom, false);
this.getView().firePropertyChange(AVKey.VIEW, null, this.getView());
}
public void addEyePositionAnimator(long timeToIterate, Position beginPosition, Position endPosition)
{
PositionAnimator eyePosAnimator = ViewUtil.createEyePositionAnimator(this.getView(),
timeToIterate, beginPosition, endPosition);
this.gotoAnimControl.put(VIEW_ANIM_POSITION, eyePosAnimator);
this.getView().firePropertyChange(AVKey.VIEW, null, this.getView());
}
public void addHeadingAnimator(Angle begin, Angle end)
{
this.gotoAnimControl.remove(VIEW_ANIM_HEADING_PITCH);
AngleAnimator headingAnimator = ViewUtil.createHeadingAnimator(this.getView(), begin, end);
this.gotoAnimControl.put(VIEW_ANIM_HEADING, headingAnimator);
this.getView().firePropertyChange(AVKey.VIEW, null, this.getView());
}
public void addPitchAnimator(Angle begin, Angle end)
{
this.gotoAnimControl.remove(VIEW_ANIM_HEADING_PITCH);
AngleAnimator pitchAnimator = ViewUtil.createPitchAnimator(this.getView(), begin, end);
this.gotoAnimControl.put(VIEW_ANIM_PITCH, pitchAnimator);
this.getView().firePropertyChange(AVKey.VIEW, null, this.getView());
}
/**
* Add an animator to animate roll.
*
* @param begin
* starting roll
* @param end
* final roll
*/
public void addRollAnimator(Angle begin, Angle end)
{
this.gotoAnimControl.remove(VIEW_ANIM_ROLL);
AngleAnimator rollAnimator = ViewUtil.createRollAnimator(this.getView(), begin, end);
this.gotoAnimControl.put(VIEW_ANIM_ROLL, rollAnimator);
this.getView().firePropertyChange(AVKey.VIEW, null, this.getView());
}
/**
* Add an animator to animate heading, pitch, and roll.
*
* @param beginHeading
* starting heading
* @param endHeading
* final heading
* @param beginPitch
* starting pitch
* @param endPitch
* final pitch
* @param beginRoll
* starting roll
* @param endRoll
* final roll
*/
public void addHeadingPitchRollAnimator(Angle beginHeading, Angle endHeading, Angle beginPitch, Angle endPitch,
Angle beginRoll, Angle endRoll)
{
this.gotoAnimControl.remove(VIEW_ANIM_PITCH);
this.gotoAnimControl.remove(VIEW_ANIM_HEADING);
CompoundAnimator headingPitchAnimator = ViewUtil.createHeadingPitchRollAnimator(this.getView(),
beginHeading, endHeading, beginPitch, endPitch, beginRoll, endRoll);
this.gotoAnimControl.put(VIEW_ANIM_HEADING_PITCH, headingPitchAnimator);
this.getView().firePropertyChange(AVKey.VIEW, null, this.getView());
}
public void addZoomAnimator(double zoomStart, double zoomEnd)
{
final long DEFAULT_LENGTH_MILLIS = 4000;
DoubleAnimator zoomAnimator = new DoubleAnimator(new ScheduledInterpolator(DEFAULT_LENGTH_MILLIS),
zoomStart, zoomEnd, OrbitViewPropertyAccessor.createZoomAccessor((this.getView())));
this.gotoAnimControl.put(VIEW_ANIM_ZOOM, zoomAnimator);
this.getView().firePropertyChange(AVKey.VIEW, null, this.getView());
}
public void addFlyToZoomAnimator(Angle heading, Angle pitch, double zoom)
{
if (heading == null || pitch == null)
{
String message = Logging.getMessage("nullValue.AngleIsNull"); //$NON-NLS-1$
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
View view = this.getView();
if (view instanceof OrbitView)
{
OrbitView orbitView = (OrbitView) view;
Angle beginHeading = orbitView.getHeading();
Angle beginPitch = orbitView.getPitch();
double beginZoom = orbitView.getZoom();
final long MIN_LENGTH_MILLIS = 1000;
final long MAX_LENGTH_MILLIS = 8000;
long lengthMillis = AnimationSupport.getScaledTimeMillisecs(
beginZoom, zoom,
MIN_LENGTH_MILLIS, MAX_LENGTH_MILLIS);
DoubleAnimator zoomAnimator = new DoubleAnimator(
new ScheduledInterpolator(lengthMillis), beginZoom, zoom,
OrbitViewPropertyAccessor.createZoomAccessor(orbitView));
AngleAnimator headingAnimator = new AngleAnimator(new ScheduledInterpolator(lengthMillis),
beginHeading, heading, ViewPropertyAccessor.createHeadingAccessor(orbitView));
AngleAnimator pitchAnimator = new AngleAnimator(new ScheduledInterpolator(lengthMillis),
beginPitch, pitch, ViewPropertyAccessor.createPitchAccessor(orbitView));
this.gotoAnimControl.put(VIEW_ANIM_ZOOM, zoomAnimator);
this.gotoAnimControl.put(VIEW_ANIM_HEADING, headingAnimator);
this.gotoAnimControl.put(VIEW_ANIM_PITCH, pitchAnimator);
orbitView.firePropertyChange(AVKey.VIEW, null, orbitView);
}
}
public void addCenterAnimator(Position begin, Position end, boolean smoothed)
{
if (begin == null || end == null)
{
String message = Logging.getMessage("nullValue.PositionIsNull"); //$NON-NLS-1$
Logging.logger().fine(message);
throw new IllegalArgumentException(message);
}
View view = this.getView();
if (view instanceof OrbitView)
{
// TODO: length-scaling factory function
final long DEFAULT_LENGTH_MILLIS = 4000;
this.addCenterAnimator(begin, end, DEFAULT_LENGTH_MILLIS, smoothed);
}
}
public void addCenterAnimator(Position begin, Position end, long lengthMillis, boolean smoothed)
{
if (begin == null || end == null)
{
String message = Logging.getMessage("nullValue.PositionIsNull"); //$NON-NLS-1$
Logging.logger().fine(message);
throw new IllegalArgumentException(message);
}
View view = this.getView();
if (view instanceof OrbitView)
{
OrbitView orbitView = (OrbitView) view;
Interpolator interpolator;
if (smoothed)
{
interpolator = new SmoothInterpolator(lengthMillis);
}
else
{
interpolator = new ScheduledInterpolator(lengthMillis);
}
Animator centerAnimator = new PositionAnimator(interpolator,
begin, end, OrbitViewPropertyAccessor.createCenterPositionAccessor(orbitView));
this.gotoAnimControl.put(VIEW_ANIM_CENTER, centerAnimator);
orbitView.firePropertyChange(AVKey.VIEW, null, orbitView);
}
}
@Override
public void goTo(Position lookAtPos, double distance)
{
OrbitView view = this.getView();
stopAnimators();
addPanToAnimator(lookAtPos, view.getHeading(), view.getPitch(), distance, true);
this.getView().firePropertyChange(AVKey.VIEW, null, this.getView());
}
@Override
public void stopAnimators()
{
this.uiAnimControl.stopAnimations();
this.gotoAnimControl.stopAnimations();
}
@Override
public boolean isAnimating()
{
return (this.uiAnimControl.hasActiveAnimation() || this.gotoAnimControl.hasActiveAnimation());
}
@Override
public void addAnimator(Animator animator)
{
long date = new Date().getTime();
this.gotoAnimControl.put(VIEW_ANIM_APP + date, animator);
}
@Override
protected OrbitView getView()
{
return (OrbitView) super.getView();
}
}