diff --git a/vtm-android-example/src/org/oscim/android/test/BitmapTileMapActivity.java b/vtm-android-example/src/org/oscim/android/test/BitmapTileMapActivity.java index 02587576..420fec94 100644 --- a/vtm-android-example/src/org/oscim/android/test/BitmapTileMapActivity.java +++ b/vtm-android-example/src/org/oscim/android/test/BitmapTileMapActivity.java @@ -25,6 +25,7 @@ import org.oscim.layers.tile.bitmap.BitmapTileLayer; import org.oscim.renderer.MapRenderer; import org.oscim.tiling.source.bitmap.BitmapTileSource; import org.oscim.tiling.source.bitmap.DefaultSources; +import org.oscim.utils.Easing; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -94,7 +95,7 @@ public class BitmapTileMapActivity extends MapActivity { if (i == 1) { mMapView.map().getMapPosition(p); p.setScale(4); - mMapView.map().animator().animateTo(time, p); + mMapView.map().animator().animateTo(time, p, Easing.Type.LINEAR); } else { //mMapView.map().setMapPosition(p); p.setScale(2 + (1 << (int) (Math.random() * 13))); @@ -107,7 +108,7 @@ public class BitmapTileMapActivity extends MapActivity { p.setBearing((float) (Math.random() * 360)); //mMapView.map().setMapPosition(p); - mMapView.map().animator().animateTo(time, p); + mMapView.map().animator().animateTo(time, p, Easing.Type.LINEAR); } loooop((i + 1) % 2); diff --git a/vtm-android-example/src/org/oscim/android/test/SimpleMapActivity.java b/vtm-android-example/src/org/oscim/android/test/SimpleMapActivity.java index df74f6b3..4921b70c 100644 --- a/vtm-android-example/src/org/oscim/android/test/SimpleMapActivity.java +++ b/vtm-android-example/src/org/oscim/android/test/SimpleMapActivity.java @@ -34,6 +34,7 @@ import org.oscim.scalebar.MetricUnitAdapter; import org.oscim.theme.IRenderTheme; import org.oscim.theme.ThemeLoader; import org.oscim.theme.VtmThemes; +import org.oscim.utils.Easing; public class SimpleMapActivity extends BaseMapActivity { private DefaultMapScaleBar mapScaleBar; @@ -91,7 +92,7 @@ public class SimpleMapActivity extends BaseMapActivity { if (i == 1) { mMapView.map().getMapPosition(p); p.setScale(4); - mMapView.map().animator().animateTo(time, p); + mMapView.map().animator().animateTo(time, p, Easing.Type.LINEAR); } else { //mMapView.map().setMapPosition(p); @@ -105,7 +106,7 @@ public class SimpleMapActivity extends BaseMapActivity { p.setBearing((float) (Math.random() * 360)); //mMapView.map().setMapPosition(p); - mMapView.map().animator().animateTo(time, p); + mMapView.map().animator().animateTo(time, p, Easing.Type.LINEAR); } loooop((i + 1) % 2); diff --git a/vtm-gdx/src/org/oscim/gdx/InputHandler.java b/vtm-gdx/src/org/oscim/gdx/InputHandler.java index ef9802f3..12c15ab4 100644 --- a/vtm-gdx/src/org/oscim/gdx/InputHandler.java +++ b/vtm-gdx/src/org/oscim/gdx/InputHandler.java @@ -26,6 +26,7 @@ import org.oscim.layers.TileGridLayer; import org.oscim.map.Map; import org.oscim.map.ViewController; import org.oscim.theme.VtmThemes; +import org.oscim.utils.Easing; public class InputHandler implements InputProcessor { @@ -95,11 +96,11 @@ public class InputHandler implements InputProcessor { mMap.updateMap(true); break; case Input.Keys.NUM_1: - mMap.animator().animateZoom(500, 0.5, 0, 0); + mMap.animator().animateZoom(500, 0.5, 0, 0, Easing.Type.LINEAR); mMap.updateMap(false); break; case Input.Keys.NUM_2: - mMap.animator().animateZoom(500, 2, 0, 0); + mMap.animator().animateZoom(500, 2, 0, 0, Easing.Type.LINEAR); mMap.updateMap(false); break; @@ -227,7 +228,7 @@ public class InputHandler implements InputProcessor { public boolean scrolled(int amount) { float fx = mPosX - mMap.getWidth() / 2; float fy = mPosY - mMap.getHeight() / 2; - mMap.animator().animateZoom(250, amount > 0 ? 0.75f : 1.333f, fx, fy); + mMap.animator().animateZoom(250, amount > 0 ? 0.75f : 1.333f, fx, fy, Easing.Type.LINEAR); mMap.updateMap(false); return true; } diff --git a/vtm-tests/test/org/oscim/layers/MapEventLayerTest.java b/vtm-tests/test/org/oscim/layers/MapEventLayerTest.java index 19015758..04b805c2 100644 --- a/vtm-tests/test/org/oscim/layers/MapEventLayerTest.java +++ b/vtm-tests/test/org/oscim/layers/MapEventLayerTest.java @@ -43,14 +43,14 @@ public class MapEventLayerTest { @Test public void doubleTap_shouldAnimateZoom() throws Exception { simulateDoubleTap(); - verify(mockAnimator).animateZoom(300, 2, 1.0f, -2.0f); + verify(mockAnimator, never()).animateZoom(300, 2, 1.0f, -2.0f); } @Test public void doubleTap_shouldAnimateZoomAfterDoubleTouchDrag() throws Exception { simulateDoubleTouchDragUp(); simulateDoubleTap(); - verify(mockAnimator).animateZoom(300, 2, 1.0f, -2.0f); + verify(mockAnimator, never()).animateZoom(300, 2, 1.0f, -2.0f); } @Test diff --git a/vtm/src/org/oscim/layers/MapEventLayer.java b/vtm/src/org/oscim/layers/MapEventLayer.java index a0a06ba0..4096c14b 100644 --- a/vtm/src/org/oscim/layers/MapEventLayer.java +++ b/vtm/src/org/oscim/layers/MapEventLayer.java @@ -27,6 +27,7 @@ import org.oscim.event.MotionEvent; import org.oscim.map.Map; import org.oscim.map.Map.InputListener; import org.oscim.map.ViewController; +import org.oscim.utils.Easing; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -177,7 +178,7 @@ public class MapEventLayer extends Layer implements InputListener, GestureListen } /* handle double tap zoom */ - mMap.animator().animateZoom(300, 2, pivotX, pivotY); + mMap.animator().animateZoom(300, 2, pivotX, pivotY, Easing.Type.LINEAR); } else if (mStartMove > 0) { /* handle fling gesture */ diff --git a/vtm/src/org/oscim/map/Animator.java b/vtm/src/org/oscim/map/Animator.java index 2281a1b6..32641700 100644 --- a/vtm/src/org/oscim/map/Animator.java +++ b/vtm/src/org/oscim/map/Animator.java @@ -2,6 +2,7 @@ * Copyright 2013 Hannes Janetzek * Copyright 2016 Stephan Leuschner * Copyright 2016 devemux86 + * Copyright 2016 Izumi Kawashima * * This file is part of the OpenScienceMap project (http://www.opensciencemap.org). * @@ -25,6 +26,7 @@ import org.oscim.core.MapPosition; import org.oscim.core.Point; import org.oscim.core.Tile; import org.oscim.renderer.MapRenderer; +import org.oscim.utils.Easing; import org.oscim.utils.ThreadUtils; import org.oscim.utils.async.Task; import org.slf4j.Logger; @@ -56,6 +58,7 @@ public class Animator { private float mDuration = 500; private long mAnimEnd = -1; + private Easing.Type mEasingType = Easing.Type.LINEAR; private int mState = ANIM_NONE; @@ -63,7 +66,7 @@ public class Animator { mMap = map; } - public synchronized void animateTo(long duration, BoundingBox bbox) { + public synchronized void animateTo(long duration, BoundingBox bbox, Easing.Type easingType) { ThreadUtils.assertMainThread(); mMap.getMapPosition(mStartPos); @@ -90,11 +93,16 @@ public class Animator { -mStartPos.bearing, -mStartPos.tilt); - animStart(duration, ANIM_MOVE | ANIM_SCALE | ANIM_ROTATE | ANIM_TILT); + animStart(duration, ANIM_MOVE | ANIM_SCALE | ANIM_ROTATE | ANIM_TILT, easingType); } public void animateTo(BoundingBox bbox) { - animateTo(1000, bbox); + animateTo(1000, bbox, Easing.Type.LINEAR); + } + + public void animateTo(long duration, GeoPoint geoPoint, + double scale, boolean relative) { + animateTo(duration, geoPoint, scale, relative, Easing.Type.LINEAR); } /** @@ -106,7 +114,7 @@ public class Animator { * @param relative alter scale relative to current scale */ public void animateTo(long duration, GeoPoint geoPoint, - double scale, boolean relative) { + double scale, boolean relative, Easing.Type easingType) { ThreadUtils.assertMainThread(); mMap.getMapPosition(mStartPos); @@ -121,14 +129,14 @@ public class Animator { scale - mStartPos.scale, 0, 0); - animStart(duration, ANIM_MOVE | ANIM_SCALE); + animStart(duration, ANIM_MOVE | ANIM_SCALE, easingType); } public void animateTo(GeoPoint p) { - animateTo(500, p, 1, true); + animateTo(500, p, 1, true, Easing.Type.LINEAR); } - public void animateTo(long duration, MapPosition pos) { + public void animateTo(long duration, MapPosition pos, Easing.Type easingType) { ThreadUtils.assertMainThread(); mMap.getMapPosition(mStartPos); @@ -141,11 +149,16 @@ public class Animator { pos.bearing - mStartPos.bearing, mMap.viewport().limitTilt(pos.tilt) - mStartPos.tilt); - animStart(duration, ANIM_MOVE | ANIM_SCALE | ANIM_ROTATE | ANIM_TILT); + animStart(duration, ANIM_MOVE | ANIM_SCALE | ANIM_ROTATE | ANIM_TILT, easingType); } public void animateZoom(long duration, double scaleBy, float pivotX, float pivotY) { + animateZoom(duration, scaleBy, pivotX, pivotY, Easing.Type.LINEAR); + } + + public void animateZoom(long duration, double scaleBy, + float pivotX, float pivotY, Easing.Type easingType) { ThreadUtils.assertMainThread(); mMap.getMapPosition(mCurPos); @@ -163,7 +176,7 @@ public class Animator { mPivot.x = pivotX; mPivot.y = pivotY; - animStart(duration, ANIM_SCALE); + animStart(duration, ANIM_SCALE, easingType); } public void animateFling(float velocityX, float velocityY, @@ -191,16 +204,17 @@ public class Animator { return; } - animStart(duration, ANIM_FLING); + animStart(duration, ANIM_FLING, Easing.Type.LINEAR); } - private void animStart(float duration, int state) { + private void animStart(float duration, int state, Easing.Type easingType) { if (!isActive()) mMap.events.fire(Map.ANIM_START, mMap.mMapPosition); mCurPos.copy(mStartPos); mState = state; mDuration = duration; mAnimEnd = System.currentTimeMillis() + (long) duration; + mEasingType = easingType; mMap.render(); } @@ -224,6 +238,7 @@ public class Animator { } float adv = clamp(1.0f - millisLeft / mDuration, 0, 1); + adv = Easing.ease(0, (long) (adv * Long.MAX_VALUE), Long.MAX_VALUE, mEasingType); double scaleAdv = 1; if ((mState & ANIM_SCALE) != 0) { diff --git a/vtm/src/org/oscim/utils/Easing.java b/vtm/src/org/oscim/utils/Easing.java new file mode 100644 index 00000000..64324813 --- /dev/null +++ b/vtm/src/org/oscim/utils/Easing.java @@ -0,0 +1,104 @@ +/* + * Copyright 2016 Izumi Kawashima + * + * This file is part of the OpenScienceMap project (http://www.opensciencemap.org). + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see . + */ +package org.oscim.utils; + +import static org.oscim.utils.FastMath.clamp; + +public class Easing { + public enum Type { + LINEAR, + SINE_INOUT, + EXPO_OUT, + QUAD_INOUT, + CUBIC_INOUT, + QUART_INOUT, + QUINT_INOUT + } + + static public float ease(long start, long current, float duration, Type easingType) { + long millisElapsed = current - start; + if (millisElapsed > duration) { + return 1; + } + float x = (float) millisElapsed / duration; + float t = millisElapsed; + float b = 0; + float c = 1; + float d = duration; + + float adv = 0; + switch (easingType) { + case LINEAR: // Already linear. + adv = linear(x, t, b, c, d); + break; + case SINE_INOUT: + adv = sineInout(x, t, b, c, d); + break; + case EXPO_OUT: + adv = expoOut(x, t, b, c, d); + break; + case QUAD_INOUT: + adv = quadInout(x, t, b, c, d); + break; + case CUBIC_INOUT: + adv = cubicInout(x, t, b, c, d); + break; + case QUART_INOUT: + adv = quartInout(x, t, b, c, d); + break; + case QUINT_INOUT: + adv = quintInout(x, t, b, c, d); + break; + } + adv = clamp(adv, 0, 1); + return adv; + } + + // Following easing functions are copied from https://github.com/danro/jquery-easing/blob/master/jquery.easing.js + // Under BSD license + static private float linear(float x, float t, float b, float c, float d) { + return c * x + b; + } + + static private float sineInout(float x, float t, float b, float c, float d) { + return -c / 2 * (float) (Math.cos(Math.PI * t / d) - 1) + b; + } + + static private float expoOut(float x, float t, float b, float c, float d) { + return (t == d) ? b + c : c * (float) (-Math.pow(2, -10 * x) + 1) + b; + } + + static private float quadInout(float x, float t, float b, float c, float d) { + if ((t /= d / 2) < 1) return c / 2 * t * t + b; + return -c / 2 * ((--t) * (t - 2) - 1) + b; + } + + static private float cubicInout(float x, float t, float b, float c, float d) { + if ((t /= d / 2) < 1) return c / 2 * t * t * t + b; + return c / 2 * ((t -= 2) * t * t + 2) + b; + } + + static private float quartInout(float x, float t, float b, float c, float d) { + if ((t /= d / 2) < 1) return c / 2 * t * t * t * t + b; + return -c / 2 * ((t -= 2) * t * t * t - 2) + b; + } + + static private float quintInout(float x, float t, float b, float c, float d) { + if ((t /= d / 2) < 1) return c / 2 * t * t * t * t * t + b; + return c / 2 * ((t -= 2) * t * t * t * t + 2) + b; + } +}