/* Copyright (C) 2001, 2006 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. */ package gov.nasa.worldwind.view; import gov.nasa.worldwind.geom.*; import gov.nasa.worldwind.globes.Globe; import gov.nasa.worldwind.util.Logging; /** * @author dcollins * @version $Id: FlyToOrbitViewStateIterator.java 4169 2008-01-14 19:26:23Z dcollins $ */ public class FlyToOrbitViewStateIterator extends BasicOrbitViewStateIterator { protected FlyToOrbitViewStateIterator(long lengthMillis, OrbitViewAnimator animator) { super(false, new ScheduledOrbitViewInterpolator(lengthMillis), animator); } // ============== "Pan To" ======================= // // ============== "Pan To" ======================= // // ============== "Pan To" ======================= // private static class PanAnimator extends BasicOrbitViewAnimator { private final Globe globe; private final OrbitViewAnimator centerAnimator; private final OrbitViewAnimator zoomAnimator; private final OrbitViewAnimator headingAnimator; private final OrbitViewAnimator pitchAnimator; private final OrbitViewAnimator beginToMidZoomAnimator, endToMidZoomAnimator; private final boolean useMidZoom; private final boolean endCenterOnSurface; private PanAnimator( Globe globe, Position beginCenter, Position endCenter, Angle beginHeading, Angle endHeading, Angle beginPitch, Angle endPitch, double beginZoom, double endZoom) { this(globe, beginCenter, endCenter, beginHeading, endHeading, beginPitch, endPitch, beginZoom, endZoom, false); } private PanAnimator( Globe globe, Position beginCenter, Position endCenter, Angle beginHeading, Angle endHeading, Angle beginPitch, Angle endPitch, double beginZoom, double endZoom, boolean endCenterOnSurface) { this.globe = globe; this.endCenterOnSurface = endCenterOnSurface; // Center position. this.centerAnimator = createPositionAnimator(beginCenter, endCenter); // Zoom. this.zoomAnimator = new DoubleAnimator( beginZoom, endZoom, OrbitViewPropertyAccessor.createZoomAccessor()); // Heading. this.headingAnimator = new AngleAnimator( beginHeading, endHeading, OrbitViewPropertyAccessor.createHeadingAccessor()); // Pitch. this.pitchAnimator = new AngleAnimator( beginPitch, endPitch, OrbitViewPropertyAccessor.createPitchAccessor()); // Mid-zoom logic. double midZoom = computeMidZoom( this.globe, beginCenter.getLatLon(), endCenter.getLatLon(), beginZoom, endZoom); this.useMidZoom = useMidZoom( beginZoom, endZoom, midZoom); this.beginToMidZoomAnimator = new DoubleAnimator( beginZoom, midZoom, OrbitViewPropertyAccessor.createZoomAccessor()); this.endToMidZoomAnimator = new DoubleAnimator( endZoom, midZoom, OrbitViewPropertyAccessor.createZoomAccessor()); } private PositionAnimator createPositionAnimator(Position beginCenter, Position endCenter) { return new PositionAnimator( beginCenter, endCenter, OrbitViewPropertyAccessor.createCenterPositionAccessor()) { public Position nextPosition(double interpolant, OrbitView orbitView) { // Invoke the standard next position functionality. Position pos = super.nextPosition(interpolant, orbitView); // If the caller has flagged endCenterOnSurface, then we override endPosition's elevation with // the surface elevation. if (endCenterOnSurface) { // Use interpolated lat/lon. LatLon ll = pos.getLatLon(); // Override end position elevation with surface elevation at end lat/lon. double e1 = getBegin().getElevation(); double e2 = globe.getElevation(getEnd().getLatitude(), getEnd().getLongitude()); pos = new Position(ll, (1 - interpolant) * e1 + interpolant * e2); } return pos; } }; } private static double computeMidZoom( Globe globe, LatLon beginLatLon, LatLon endLatLon, double beginZoom, double endZoom) { // Scale factor is angular distance over 180 degrees. Angle sphericalDistance = LatLon.greatCircleDistance(beginLatLon, endLatLon); double scaleFactor = angularRatio(sphericalDistance, Angle.POS180); // Mid-point zoom is interpolated value between minimum and maximum zoom. final double MIN_ZOOM = Math.min(beginZoom, endZoom); final double MAX_ZOOM = 3.0 * globe.getRadius(); return mixDouble(scaleFactor, MIN_ZOOM, MAX_ZOOM); } private static boolean useMidZoom(double beginZoom, double endZoom, double midZoom) { double a = Math.abs(endZoom - beginZoom); double b = Math.abs(midZoom - Math.max(beginZoom, endZoom)); return a < b; } protected void doNextStateImpl(double interpolant, OrbitView orbitView, BasicOrbitViewStateIterator stateIterator) { if (orbitView == null) { String message = Logging.getMessage("nullValue.OrbitViewIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (stateIterator == null) { String message = Logging.getMessage("nullValue.OrbitViewStateIteratorIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.nextCenterState(interpolant, orbitView, stateIterator); this.nextZoomState(interpolant, orbitView, stateIterator); this.nextHeadingState(interpolant, orbitView, stateIterator); this.nextPitchState(interpolant, orbitView, stateIterator); } private void nextCenterState(double interpolant, OrbitView orbitView, BasicOrbitViewStateIterator stateIterator) { final int MAX_SMOOTHING = 1; final double CENTER_START = this.useMidZoom ? 0.2 : 0.0; final double CENTER_STOP = this.useMidZoom ? 0.8 : 0.8; double latLonInterpolant = basicInterpolant(interpolant, CENTER_START, CENTER_STOP, MAX_SMOOTHING); this.centerAnimator.doNextState(latLonInterpolant, orbitView, stateIterator); } private void nextHeadingState(double interpolant, OrbitView orbitView, BasicOrbitViewStateIterator stateIterator) { final int MAX_SMOOTHING = 1; final double HEADING_START = this.useMidZoom ? 0.0 : 0.6; final double HEADING_STOP = 1.0; double headingInterpolant = basicInterpolant(interpolant, HEADING_START, HEADING_STOP, MAX_SMOOTHING); this.headingAnimator.doNextState(headingInterpolant, orbitView, stateIterator); } private void nextPitchState(double interpolant, OrbitView orbitView, BasicOrbitViewStateIterator stateIterator) { final int MAX_SMOOTHING = 1; final double PITCH_START = 0.0; final double PITCH_STOP = 0.8; double pitchInterpolant = basicInterpolant(interpolant, PITCH_START, PITCH_STOP, MAX_SMOOTHING); this.pitchAnimator.doNextState(pitchInterpolant, orbitView, stateIterator); } private void nextZoomState(double interpolant, OrbitView orbitView, BasicOrbitViewStateIterator stateIterator) { final int MAX_SMOOTHING = 1; if (this.useMidZoom) { final double ZOOM_START = 0.0; final double ZOOM_STOP = 1.0; double zoomInterpolant = this.zoomInterpolant(interpolant, ZOOM_START, ZOOM_STOP, MAX_SMOOTHING); if (interpolant <= 0.5) this.beginToMidZoomAnimator.doNextState(zoomInterpolant, orbitView, stateIterator); else this.endToMidZoomAnimator.doNextState(zoomInterpolant, orbitView, stateIterator); } else { final double ZOOM_START = 0.0; final double ZOOM_STOP = 1.0; double zoomInterpolant = basicInterpolant(interpolant, ZOOM_START, ZOOM_STOP, MAX_SMOOTHING); this.zoomAnimator.doNextState(zoomInterpolant, orbitView, stateIterator); } } private double zoomInterpolant(double interpolant, double startInterpolant, double stopInterpolant, int maxSmoothing) { // Map interpolant in to range [start, stop]. double normalizedInterpolant = interpolantNormalized(interpolant, startInterpolant, stopInterpolant); // During first half of iteration, zoom increases from begin to mid, // and decreases from mid to end during second half. if (normalizedInterpolant <= 0.5) { normalizedInterpolant = 2.0 * normalizedInterpolant; } else { normalizedInterpolant = 1.0 - (2.0 * normalizedInterpolant - 1.0); } return interpolantSmoothed(normalizedInterpolant, maxSmoothing); } } // ============== "Zoom To" ======================= // // ============== "Zoom To" ======================= // // ============== "Zoom To" ======================= // private static class ZoomAnimator extends BasicOrbitViewAnimator { private final OrbitViewAnimator headingAnimator; private final OrbitViewAnimator pitchAnimator; private final OrbitViewAnimator zoomAnimator; private ZoomAnimator( Angle beginHeading, Angle endHeading, Angle beginPitch, Angle endPitch, double beginZoom, double endZoom) { // Heading. this.headingAnimator = new AngleAnimator( beginHeading, endHeading, OrbitViewPropertyAccessor.createHeadingAccessor()); // Pitch. this.pitchAnimator = new AngleAnimator( beginPitch, endPitch, OrbitViewPropertyAccessor.createPitchAccessor()); // Zoom. this.zoomAnimator = new DoubleAnimator( beginZoom, endZoom, OrbitViewPropertyAccessor.createZoomAccessor()); } protected void doNextStateImpl(double interpolant, OrbitView orbitView, BasicOrbitViewStateIterator stateIterator) { if (orbitView == null) { String message = Logging.getMessage("nullValue.OrbitViewIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (stateIterator == null) { String message = Logging.getMessage("nullValue.OrbitViewStateIteratorIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.nextZoomState(interpolant, orbitView, stateIterator); this.nextHeadingState(interpolant, orbitView, stateIterator); this.nextPitchState(interpolant, orbitView, stateIterator); } private void nextHeadingState(double interpolant, OrbitView orbitView, BasicOrbitViewStateIterator stateIterator) { final int MAX_SMOOTHING = 1; final double HEADING_START = 0.0; final double HEADING_STOP = 0.6; double headingInterpolant = basicInterpolant(interpolant, HEADING_START, HEADING_STOP, MAX_SMOOTHING); this.headingAnimator.doNextState(headingInterpolant, orbitView, stateIterator); } private void nextPitchState(double interpolant, OrbitView orbitView, BasicOrbitViewStateIterator stateIterator) { final int MAX_SMOOTHING = 1; final double PITCH_START = 0.0; final double PITCH_STOP = 0.6; double pitchInterpolant = basicInterpolant(interpolant, PITCH_START, PITCH_STOP, MAX_SMOOTHING); this.pitchAnimator.doNextState(pitchInterpolant, orbitView, stateIterator); } private void nextZoomState(double interpolant, OrbitView orbitView, BasicOrbitViewStateIterator stateIterator) { final int MAX_SMOOTHING = 1; final double ZOOM_START = 0.0; final double ZOOM_STOP = 1.0; double zoomInterpolant = basicInterpolant(interpolant, ZOOM_START, ZOOM_STOP, MAX_SMOOTHING); this.zoomAnimator.doNextState(zoomInterpolant, orbitView, stateIterator); } } // ============== Factory Functions ======================= // // ============== Factory Functions ======================= // // ============== Factory Functions ======================= // public static FlyToOrbitViewStateIterator createPanToIterator( OrbitView orbitView, Globe globe, Position center, Angle heading, Angle pitch, double zoom) { if (orbitView == null) { String message = Logging.getMessage("nullValue.ViewIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (globe == null) { String message = Logging.getMessage("nullValue.GlobeIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (center == null) { String message = Logging.getMessage("nullValue.PositionIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (heading == null || pitch == null) { String message = Logging.getMessage("nullValue.AngleIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } Position beginCenter = orbitView.getCenterPosition(); Angle beginHeading = orbitView.getHeading(); Angle beginPitch = orbitView.getPitch(); double beginZoom = orbitView.getZoom(); return createPanToIterator( globe, beginCenter, center, beginHeading, heading, beginPitch, pitch, beginZoom, zoom); } public static FlyToOrbitViewStateIterator createPanToIterator( OrbitView orbitView, Globe globe, Position center, Angle heading, Angle pitch, double zoom, boolean endCenterOnSurface) { if (orbitView == null) { String message = Logging.getMessage("nullValue.ViewIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (globe == null) { String message = Logging.getMessage("nullValue.GlobeIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (center == null) { String message = Logging.getMessage("nullValue.PositionIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (heading == null || pitch == null) { String message = Logging.getMessage("nullValue.AngleIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } Position beginCenter = orbitView.getCenterPosition(); Angle beginHeading = orbitView.getHeading(); Angle beginPitch = orbitView.getPitch(); double beginZoom = orbitView.getZoom(); return createPanToIterator( globe, beginCenter, center, beginHeading, heading, beginPitch, pitch, beginZoom, zoom, endCenterOnSurface); } public static FlyToOrbitViewStateIterator createPanToIterator( Globe globe, Position beginCenter, Position endCenter, Angle beginHeading, Angle endHeading, Angle beginPitch, Angle endPitch, double beginZoom, double endZoom) { if (globe == null) { String message = Logging.getMessage("nullValue.GlobeIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (beginCenter == null || endCenter == null) { String message = Logging.getMessage("nullValue.PositionIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (beginHeading == null || endHeading == null || beginPitch == null || endPitch == null) { String message = Logging.getMessage("nullValue.AngleIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } // TODO: scale on mid-altitude? final long MIN_LENGTH_MILLIS = 4000; final long MAX_LENGTH_MILLIS = 16000; long lengthMillis = getScaledLengthMillis( beginCenter.getLatLon(), endCenter.getLatLon(), MIN_LENGTH_MILLIS, MAX_LENGTH_MILLIS); return createPanToIterator( globe, beginCenter, endCenter, beginHeading, endHeading, beginPitch, endPitch, beginZoom, endZoom, lengthMillis); } public static FlyToOrbitViewStateIterator createPanToIterator( Globe globe, Position beginCenter, Position endCenter, Angle beginHeading, Angle endHeading, Angle beginPitch, Angle endPitch, double beginZoom, double endZoom, boolean endCenterOnSurface) { if (globe == null) { String message = Logging.getMessage("nullValue.GlobeIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (beginCenter == null || endCenter == null) { String message = Logging.getMessage("nullValue.PositionIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (beginHeading == null || endHeading == null || beginPitch == null || endPitch == null) { String message = Logging.getMessage("nullValue.AngleIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } // TODO: scale on mid-altitude? final long MIN_LENGTH_MILLIS = 4000; final long MAX_LENGTH_MILLIS = 16000; long lengthMillis = getScaledLengthMillis( beginCenter.getLatLon(), endCenter.getLatLon(), MIN_LENGTH_MILLIS, MAX_LENGTH_MILLIS); return createPanToIterator( globe, beginCenter, endCenter, beginHeading, endHeading, beginPitch, endPitch, beginZoom, endZoom, lengthMillis, endCenterOnSurface); } public static FlyToOrbitViewStateIterator createPanToIterator( Globe globe, Position beginCenter, Position endCenter, Angle beginHeading, Angle endHeading, Angle beginPitch, Angle endPitch, double beginZoom, double endZoom, long lengthMillis) { if (globe == null) { String message = Logging.getMessage("nullValue.GlobeIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (beginCenter == null || endCenter == null) { String message = Logging.getMessage("nullValue.PositionIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (beginHeading == null || endHeading == null || beginPitch == null || endPitch == null) { String message = Logging.getMessage("nullValue.AngleIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (lengthMillis < 0) { String message = Logging.getMessage("generic.ArgumentOutOfRange", lengthMillis); Logging.logger().severe(message); throw new IllegalArgumentException(message); } OrbitViewAnimator animator = new PanAnimator( globe, beginCenter, endCenter, beginHeading, endHeading, beginPitch, endPitch, beginZoom, endZoom); return new FlyToOrbitViewStateIterator(lengthMillis, animator); } public static FlyToOrbitViewStateIterator createPanToIterator( Globe globe, Position beginCenter, Position endCenter, Angle beginHeading, Angle endHeading, Angle beginPitch, Angle endPitch, double beginZoom, double endZoom, long lengthMillis, boolean endCenterOnSurface) { if (globe == null) { String message = Logging.getMessage("nullValue.GlobeIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (beginCenter == null || endCenter == null) { String message = Logging.getMessage("nullValue.PositionIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (beginHeading == null || endHeading == null || beginPitch == null || endPitch == null) { String message = Logging.getMessage("nullValue.AngleIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (lengthMillis < 0) { String message = Logging.getMessage("generic.ArgumentOutOfRange", lengthMillis); Logging.logger().severe(message); throw new IllegalArgumentException(message); } OrbitViewAnimator animator = new PanAnimator( globe, beginCenter, endCenter, beginHeading, endHeading, beginPitch, endPitch, beginZoom, endZoom, endCenterOnSurface); return new FlyToOrbitViewStateIterator(lengthMillis, animator); } public static FlyToOrbitViewStateIterator createZoomToIterator( OrbitView orbitView, Angle heading, Angle pitch, double zoom) { if (orbitView == null) { String message = Logging.getMessage("nullValue.ViewIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (heading == null || pitch == null) { String message = Logging.getMessage("nullValue.AngleIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } Angle beginHeading = orbitView.getHeading(); Angle beginPitch = orbitView.getPitch(); double beginZoom = orbitView.getZoom(); return createZoomToIterator( beginHeading, heading, beginPitch, pitch, beginZoom, zoom); } public static FlyToOrbitViewStateIterator createZoomToIterator( Angle beginHeading, Angle endHeading, Angle beginPitch, Angle endPitch, double beginZoom, double endZoom) { if (beginHeading == null || endHeading == null || beginPitch == null || endPitch == null) { String message = Logging.getMessage("nullValue.AngleIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } final long MIN_LENGTH_MILLIS = 1000; final long MAX_LENGTH_MILLIS = 8000; long lengthMillis = getScaledLengthMillis( beginZoom, endZoom, MIN_LENGTH_MILLIS, MAX_LENGTH_MILLIS); return createZoomToIterator( beginHeading, endHeading, beginPitch, endPitch, beginZoom, endZoom, lengthMillis); } public static FlyToOrbitViewStateIterator createZoomToIterator( Angle beginHeading, Angle endHeading, Angle beginPitch, Angle endPitch, double beginZoom, double endZoom, long lengthMillis) { if (beginHeading == null || endHeading == null || beginPitch == null || endPitch == null) { String message = Logging.getMessage("nullValue.AngleIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (lengthMillis < 0) { String message = Logging.getMessage("generic.ArgumentOutOfRange", lengthMillis); Logging.logger().severe(message); throw new IllegalArgumentException(message); } OrbitViewAnimator animator = new ZoomAnimator( beginHeading, endHeading, beginPitch, endPitch, beginZoom, endZoom); return new FlyToOrbitViewStateIterator(lengthMillis, animator); } private static long getScaledLengthMillis( double beginZoom, double endZoom, long minLengthMillis, long maxLengthMillis) { double scaleFactor = Math.abs(endZoom - beginZoom) / Math.max(endZoom, beginZoom); // Clamp scaleFactor to range [0, 1]. scaleFactor = clampDouble(scaleFactor, 0.0, 1.0); // Iteration time is interpolated value between minumum and maximum lengths. return (long) mixDouble(scaleFactor, minLengthMillis, maxLengthMillis); } private static long getScaledLengthMillis( LatLon beginLatLon, LatLon endLatLon, long minLengthMillis, long maxLengthMillis) { Angle sphericalDistance = LatLon.greatCircleDistance(beginLatLon, endLatLon); double scaleFactor = angularRatio(sphericalDistance, Angle.POS180); return (long) mixDouble(scaleFactor, minLengthMillis, maxLengthMillis); } // ============== Helper Functions ======================= // // ============== Helper Functions ======================= // // ============== Helper Functions ======================= // // Map amount range [startAmount, stopAmount] to [0, 1] when amount is inside range. private static double interpolantNormalized(double amount, double startAmount, double stopAmount) { if (amount < startAmount) return 0.0; else if (amount > stopAmount) return 1.0; return (amount - startAmount) / (stopAmount - startAmount); } private static double interpolantSmoothed(double interpolant, int smoothingIterations) { // Apply iterative hermite smoothing. double smoothed = interpolant; for (int i = 0; i < smoothingIterations; i++) { smoothed = smoothed * smoothed * (3.0 - 2.0 * smoothed); } return smoothed; } private static double basicInterpolant(double interpolant, double startInterpolant, double stopInterpolant, int maxSmoothing) { double normalizedInterpolant = interpolantNormalized(interpolant, startInterpolant, stopInterpolant); return interpolantSmoothed(normalizedInterpolant, maxSmoothing); } private static double angularRatio(Angle x, Angle y) { if (x == null || y == null) { String message = Logging.getMessage("nullValue.AngleIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } double unclampedRatio = x.divide(y); return clampDouble(unclampedRatio, 0, 1); } private static double clampDouble(double value, double min, double max) { return value < min ? min : (value > max ? max : value); } private static double mixDouble(double amount, double value1, double value2) { if (amount < 0) return value1; else if (amount > 1) return value2; return value1 * (1.0 - amount) + value2 * amount; } }