/*
Copyright (C) 2001, 2007 United States Government as represented by
the Administrator of the National Aeronautics and Space Administration.
All Rights Reserved.
*/
package gov.nasa.worldwind.awt;
import gov.nasa.worldwind.View;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.geom.Angle;
import gov.nasa.worldwind.geom.LatLon;
import gov.nasa.worldwind.geom.Position;
import gov.nasa.worldwind.util.Logging;
import gov.nasa.worldwind.view.OrbitView;
/**
* @author dcollins
* @version $Id: OrbitViewInputSupport.java 5276 2008-05-02 04:33:57Z dcollins $
*/
class OrbitViewInputSupport implements java.beans.PropertyChangeListener
{
// OrbitView that will receive value changes.
private OrbitView orbitView;
private boolean viewChanged;
private boolean viewOutOfFocus;
private boolean targetStopped;
// Target OrbitView values.
private Position centerTarget; // Initially, do not change OrbitView's center position.
private Angle headingTarget; // Initially, do not change OrbitView's heading.
private Angle pitchTarget; // Initially, do not change OrbitView's pitch.
private double zoomTarget = -1; // Initially, do not change OrbitView's zoom.
// OrbitView value error thresholds.
private double centerMinEpsilon;
private double headingMinEpsilon;
private double pitchMinEpsilon;
private double zoomMinEpsilon;
// OrbitView value smoothing coefficients.
private double centerSmoothing;
private double headingSmoothing;
private double pitchSmoothing;
private double zoomSmoothing;
OrbitViewInputSupport()
{
this(null);
}
OrbitViewInputSupport(OrbitView orbitView)
{
setOrbitView(orbitView);
loadConfigurationValues();
// We don't know what state the view is in, so we assume it is "out of focus".
setViewOutOfFocus(true);
}
private void loadConfigurationValues()
{
// No configuration keys exist for these values yet (they may never).
setCenterMinEpsilon(1e-9);
setHeadingMinEpsilon(1e-4);
setPitchMinEpsilon(1e-4);
setZoomMinEpsilon(1e-3);
// No configuration keys exist for these values yet (they may never).
setCenterSmoothing(0.4);
setHeadingSmoothing(0.7);
setPitchSmoothing(0.7);
setZoomSmoothing(0.9);
}
public OrbitView getOrbitView()
{
return this.orbitView;
}
public void setOrbitView(OrbitView orbitView)
{
if (orbitView == this.orbitView)
return;
if (this.orbitView != null)
{
this.orbitView.removePropertyChangeListener(this);
}
this.orbitView = orbitView;
if (this.orbitView != null)
{
this.orbitView.addPropertyChangeListener(this);
}
}
public void propertyChange(java.beans.PropertyChangeEvent event)
{
if (event == null)
return;
if (event.getPropertyName().equals(View.VIEW_STOPPED))
{
clearTargets();
}
else if (event.getPropertyName().equalsIgnoreCase(OrbitView.CENTER_STOPPED))
{
this.centerTarget = null;
setViewOutOfFocus(false);
}
}
private boolean isViewChanged()
{
boolean result = this.viewChanged;
this.viewChanged = false;
return result;
}
private void flagViewChanged()
{
this.viewChanged = true;
}
private void refreshView()
{
if (isViewChanged() && this.orbitView != null)
this.orbitView.firePropertyChange(AVKey.VIEW, null, this);
}
private boolean isViewOutOfFocus()
{
return this.viewOutOfFocus;
}
private void setViewOutOfFocus(boolean b)
{
this.viewOutOfFocus = b;
}
private void focusView()
{
if (this.orbitView == null)
return;
try
{
// Update the View's focus.
if (this.orbitView.canFocusOnViewportCenter())
{
this.orbitView.focusOnViewportCenter();
setViewOutOfFocus(false);
flagViewChanged();
}
}
catch (Exception e)
{
String message = Logging.getMessage("generic.ExceptionWhileChangingView");
Logging.logger().log(java.util.logging.Level.SEVERE, message, e);
// If updating the View's focus failed, raise the flag again.
setViewOutOfFocus(true);
}
}
private boolean isTargetStopped()
{
boolean result = this.targetStopped;
this.targetStopped = false;
return result;
}
private void flagTargetStopped()
{
this.targetStopped = true;
}
public Position getCenterTarget()
{
return this.centerTarget;
}
public void setCenterTarget(Position centerTarget)
{
Position newTarget = clampedCenter(centerTarget);
// If smoothing is disabled, and centerTarget != null, then set center position directly.
if (this.centerSmoothing == 0 && newTarget != null && this.orbitView != null)
{
this.centerTarget = null;
this.orbitView.setCenterPosition(newTarget);
flagViewChanged();
setViewOutOfFocus(true);
}
// Otherwise, just set the target.
else
{
this.centerTarget = newTarget;
}
// Cancel heading, pitch, and zoom targets.
if (newTarget != null)
{
this.headingTarget = null;
this.pitchTarget = null;
this.zoomTarget = -1;
}
refreshView();
}
public Angle getHeadingTarget()
{
return this.headingTarget;
}
public void setHeadingTarget(Angle headingTarget)
{
Angle newTarget = normalizedHeading(headingTarget);
if (isViewOutOfFocus())
focusView();
// If smoothing is disabled, and headingTarget != null, then set heading directly.
if (this.headingSmoothing == 0 && newTarget != null && this.orbitView != null)
{
this.headingTarget = null;
this.orbitView.setHeading(newTarget);
flagViewChanged();
}
// Otherwise, just set the target.
else
{
this.headingTarget = newTarget;
}
// Cancel center and zoom targets.
if (newTarget != null)
{
this.centerTarget = null;
this.zoomTarget = -1;
}
refreshView();
}
public Angle getPitchTarget()
{
return this.pitchTarget;
}
public void setPitchTarget(Angle pitchTarget)
{
Angle newTarget = clampedPitch(pitchTarget);
if (isViewOutOfFocus())
focusView();
// If smoothing is disabled, and pitchTarget != null, then set pitch directly.
if (this.pitchSmoothing == 0 && newTarget != null && this.orbitView != null)
{
this.pitchTarget = null;
this.orbitView.setPitch(newTarget);
flagViewChanged();
}
// Otherwise, just set the target.
else
{
this.pitchTarget = newTarget;
}
// Cancel center and zoom targets,
if (newTarget != null)
{
this.centerTarget = null;
this.zoomTarget = -1;
}
refreshView();
}
public double getZoomTarget()
{
return this.zoomTarget;
}
public void setZoomTarget(double zoomTarget)
{
double newTarget = normalizedZoom(zoomTarget);
if (isViewOutOfFocus())
{
double beforeFocus, afterFocus;
beforeFocus = this.orbitView.getZoom();
focusView();
afterFocus = this.orbitView.getZoom();
newTarget = normalizedZoom(newTarget + (afterFocus - beforeFocus));
}
// If smoothing is disabled, and zoomTarget >= 0, then set zoom directly.
if (this.zoomSmoothing == 0 && newTarget >= 0 && this.orbitView != null)
{
this.zoomTarget = -1;
this.orbitView.setZoom(newTarget);
flagViewChanged();
}
// Otherwise, just set the target.
else
{
this.zoomTarget = newTarget;
}
// Cancel center, heading, and pitch targets.
if (newTarget >= 0)
{
this.centerTarget = null;
this.headingTarget = null;
this.pitchTarget = null;
}
refreshView();
}
public boolean hasTargets()
{
return this.centerTarget != null
|| this.headingTarget != null
|| this.pitchTarget != null
|| this.zoomTarget >= 0;
}
public void clearTargets()
{
this.centerTarget = null;
this.headingTarget = null;
this.pitchTarget = null;
this.zoomTarget = -1;
// Clear viewing flags.
this.viewChanged = false;
setViewOutOfFocus(false);
this.targetStopped = false;
}
private static Position clampedCenter(Position unclampedCenter)
{
if (unclampedCenter == null)
return null;
// Clamp latitude to the range [-90, 90],
// Normalize longitude to the range [-180, 180],
// Don't change elevation.
double lat = unclampedCenter.getLatitude().degrees;
double lon = unclampedCenter.getLongitude().degrees;
double elev = unclampedCenter.getElevation();
lon = lon % 360;
return Position.fromDegrees(
lat > 90 ? 90 : (lat < -90 ? -90 : lat),
lon > 180 ? lon - 360 : (lon < -180 ? 360 + lon : lon),
elev);
}
private static Angle normalizedHeading(Angle unnormalizedHeading)
{
if (unnormalizedHeading == null)
return null;
// Normalize heading to the range [-180, 180].
double degrees = unnormalizedHeading.degrees;
double heading = degrees % 360;
return Angle.fromDegrees(heading > 180 ? heading - 360 : (heading < -180 ? 360 + heading : heading));
}
private static Angle clampedPitch(Angle unclampedPitch)
{
if (unclampedPitch == null)
return null;
// Clamp pitch to the range [0, 90].
double pitch = unclampedPitch.degrees;
return Angle.fromDegrees(pitch > 90 ? 90 : (pitch < 0 ? 0 : pitch));
}
private static double normalizedZoom(double unnormalizedZoom)
{
return unnormalizedZoom < 0 ? -1 : unnormalizedZoom;
}
public double getCenterMinEpsilon()
{
return this.centerMinEpsilon;
}
public void setCenterMinEpsilon(double centerMinEpsilon)
{
if (centerMinEpsilon < 0)
{
String message = Logging.getMessage("generic.ArgumentOutOfRange", centerMinEpsilon);
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.centerMinEpsilon = centerMinEpsilon;
}
public double getHeadingMinEpsilon()
{
return headingMinEpsilon;
}
public void setHeadingMinEpsilon(double headingMinEpsilon)
{
if (headingMinEpsilon < 0)
{
String message = Logging.getMessage("generic.ArgumentOutOfRange", headingMinEpsilon);
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.headingMinEpsilon = headingMinEpsilon;
}
public double getPitchMinEpsilon()
{
return this.pitchMinEpsilon;
}
public void setPitchMinEpsilon(double pitchMinEpsilon)
{
if (pitchMinEpsilon < 0)
{
String message = Logging.getMessage("generic.ArgumentOutOfRange", pitchMinEpsilon);
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.pitchMinEpsilon = pitchMinEpsilon;
}
public double getZoomMinEpsilon()
{
return this.zoomMinEpsilon;
}
public void setZoomMinEpsilon(double zoomMinEpsilon)
{
if (zoomMinEpsilon < 0)
{
String message = Logging.getMessage("generic.ArgumentOutOfRange", zoomMinEpsilon);
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.zoomMinEpsilon = zoomMinEpsilon;
}
public double getCenterSmoothing()
{
return this.centerSmoothing;
}
public void setCenterSmoothing(double centerSmoothing)
{
if (centerSmoothing < 0 || centerSmoothing > 1)
{
String message = Logging.getMessage("generic.ArgumentOutOfRange", centerSmoothing);
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.centerSmoothing = centerSmoothing;
}
public double getHeadingSmoothing()
{
return this.headingSmoothing;
}
public void setHeadingSmoothing(double headingSmoothing)
{
if (headingSmoothing < 0 || headingSmoothing > 1)
{
String message = Logging.getMessage("generic.ArgumentOutOfRange", headingSmoothing);
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.headingSmoothing = headingSmoothing;
}
public double getPitchSmoothing()
{
return this.pitchSmoothing;
}
public void setPitchSmoothing(double pitchSmoothing)
{
if (pitchSmoothing < 0 || pitchSmoothing > 1)
{
String message = Logging.getMessage("generic.ArgumentOutOfRange", pitchSmoothing);
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.pitchSmoothing = pitchSmoothing;
}
public double getZoomSmoothing()
{
return this.zoomSmoothing;
}
public void setZoomSmoothing(double zoomSmoothing)
{
if (zoomSmoothing < 0 || zoomSmoothing > 1)
{
String message = Logging.getMessage("generic.ArgumentOutOfRange", zoomSmoothing);
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.zoomSmoothing = zoomSmoothing;
}
public void moveViewTowardTargets()
{
if (this.orbitView == null)
return;
// Clear any locally tracked view state.
isViewChanged();
isTargetStopped();
if (this.centerTarget != null)
moveTowardCenterTarget();
if (this.headingTarget != null)
moveTowardHeadingTarget();
if (this.pitchTarget != null)
moveTowardPitchTarget();
if (this.zoomTarget >= 0)
moveTowardZoomTarget();
refreshView();
if (isTargetStopped() && !hasTargets())
{
if (isViewOutOfFocus())
focusView();
clearTargets();
this.orbitView.firePropertyChange(AVKey.VIEW_QUIET, null, this.orbitView);
}
}
private void moveTowardCenterTarget()
{
Position nextCenter = this.centerTarget;
Position curCenter = this.orbitView.getCenterPosition();
double latlonDifference = LatLon.greatCircleDistance(nextCenter.getLatLon(), curCenter.getLatLon()).degrees;
double elevDifference = Math.abs(nextCenter.getElevation() - curCenter.getElevation());
boolean stopMoving = Math.max(latlonDifference, elevDifference) < this.centerMinEpsilon;
if (!stopMoving)
{
double interpolant = 1 - this.centerSmoothing;
nextCenter = new Position(
Angle.mix(interpolant, curCenter.getLatitude(), this.centerTarget.getLatitude()),
Angle.mix(interpolant, curCenter.getLongitude(), this.centerTarget.getLongitude()),
(1 - interpolant) * curCenter.getElevation() + interpolant * this.centerTarget.getElevation());
}
try
{
// Clear any previous collision state the view may have.
this.orbitView.hadCollisions();
this.orbitView.setCenterPosition(nextCenter);
// If the change caused a collision, update the target center position with the
// elevation that resolved the collision.
if (this.orbitView.hadCollisions())
this.centerTarget = new Position(
this.centerTarget.getLatLon(), this.orbitView.getCenterPosition().getElevation());
flagViewChanged();
setViewOutOfFocus(true);
}
catch (Exception e)
{
String message = Logging.getMessage("generic.ExceptionWhileChangingView");
Logging.logger().log(java.util.logging.Level.SEVERE, message, e);
stopMoving = true;
}
// If target is close, cancel future value changes.
if (stopMoving)
{
this.centerTarget = null;
flagTargetStopped();
}
}
private void moveTowardHeadingTarget()
{
Angle nextHeading = this.headingTarget;
Angle curHeading = this.orbitView.getHeading();
double difference = Math.abs(nextHeading.subtract(curHeading).degrees);
boolean stopMoving = difference < this.headingMinEpsilon;
if (!stopMoving)
{
double interpolant = 1 - this.headingSmoothing;
nextHeading = Angle.mix(interpolant, curHeading, this.headingTarget);
}
try
{
this.orbitView.setHeading(nextHeading);
flagViewChanged();
}
catch (Exception e)
{
String message = Logging.getMessage("generic.ExceptionWhileChangingView");
Logging.logger().log(java.util.logging.Level.SEVERE, message, e);
stopMoving = true;
}
// If target is close, cancel future value changes.
if (stopMoving)
{
this.headingTarget = null;
flagTargetStopped();
}
}
private void moveTowardPitchTarget()
{
Angle nextPitch = this.pitchTarget;
Angle curPitch = this.orbitView.getPitch();
double difference = Math.abs(nextPitch.subtract(curPitch).degrees);
boolean stopMoving = difference < this.pitchMinEpsilon;
if (!stopMoving)
{
double interpolant = 1 - this.pitchSmoothing;
nextPitch = Angle.mix(interpolant, curPitch, this.pitchTarget);
}
try
{
// Clear any previous collision state the view may have.
this.orbitView.hadCollisions();
this.orbitView.setPitch(nextPitch);
// If the change caused a collision, cancel future pitch changes.
if (this.orbitView.hadCollisions())
stopMoving = true;
flagViewChanged();
}
catch (Exception e)
{
String message = Logging.getMessage("generic.ExceptionWhileChangingView");
Logging.logger().log(java.util.logging.Level.SEVERE, message, e);
stopMoving = true;
}
// If target is close, cancel future value changes.
if (stopMoving)
{
this.pitchTarget = null;
flagTargetStopped();
}
}
private void moveTowardZoomTarget()
{
double nextZoom = this.zoomTarget;
double curZoom = this.orbitView.getZoom();
double difference = Math.abs(nextZoom - curZoom);
boolean stopMoving = difference < this.zoomMinEpsilon;
if (!stopMoving)
{
double interpolant = 1 - this.zoomSmoothing;
nextZoom = (1 - interpolant) * curZoom + interpolant * this.zoomTarget;
}
try
{
this.orbitView.setZoom(nextZoom);
flagViewChanged();
}
catch (Exception e)
{
String message = Logging.getMessage("generic.ExceptionWhileChangingView");
Logging.logger().log(java.util.logging.Level.SEVERE, message, e);
stopMoving = true;
}
// If target is close, cancel future value changes.
if (stopMoving)
{
this.zoomTarget = -1;
flagTargetStopped();
}
}
}