diff --git a/src/org/oscim/utils/Interpolation.java b/src/org/oscim/utils/Interpolation.java new file mode 100644 index 00000000..2053ac53 --- /dev/null +++ b/src/org/oscim/utils/Interpolation.java @@ -0,0 +1,420 @@ +/******************************************************************************* + * Copyright 2011 libgdx authors. + * + * 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 org.oscim.utils; + +/** + * Takes a linear value in the range of 0-1 and outputs a (usually) non-linear, + * interpolated value. + * + * @author Nathan Sweet + */ +public abstract class Interpolation { + /** @param a Alpha value between 0 and 1.*/ + abstract public float apply(float a); + + /** @param a Alpha value between 0 and 1. */ + public float apply(float start, float end, float a) { + return start + (end - start) * apply(a); + } + + static public final Interpolation linear = new Interpolation() { + @Override + public float apply(float a) { + return a; + } + }; + + static public final Interpolation fade = new Interpolation() { + @Override + public float apply(float a) { + return MathUtils.clamp(a * a * a * (a * (a * 6 - 15) + 10), 0, 1); + } + }; + + static public final Pow pow2 = new Pow(2); + static public final PowIn pow2In = new PowIn(2); + static public final PowOut pow2Out = new PowOut(2); + + static public final Pow pow3 = new Pow(3); + static public final PowIn pow3In = new PowIn(3); + static public final PowOut pow3Out = new PowOut(3); + + static public final Pow pow4 = new Pow(4); + static public final PowIn pow4In = new PowIn(4); + static public final PowOut pow4Out = new PowOut(4); + + static public final Pow pow5 = new Pow(5); + static public final PowIn pow5In = new PowIn(5); + static public final PowOut pow5Out = new PowOut(5); + + static public final Interpolation sine = new Interpolation() { + @Override + public float apply(float a) { + return (1 - MathUtils.cos(a * MathUtils.PI)) / 2; + } + }; + + static public final Interpolation sineIn = new Interpolation() { + @Override + public float apply(float a) { + return 1 - MathUtils.cos(a * MathUtils.PI / 2); + } + }; + + static public final Interpolation sineOut = new Interpolation() { + @Override + public float apply(float a) { + return MathUtils.sin(a * MathUtils.PI / 2); + } + }; + + static public final Interpolation exp10 = new Exp(2, 10); + static public final Interpolation exp10In = new ExpIn(2, 10); + static public final Interpolation exp10Out = new ExpOut(2, 10); + + static public final Interpolation exp5 = new Exp(2, 5); + static public final Interpolation exp5In = new ExpIn(2, 5); + static public final Interpolation exp5Out = new ExpOut(2, 5); + + static public final Interpolation circle = new Interpolation() { + @Override + public float apply(float a) { + if (a <= 0.5f) { + a *= 2; + return (1 - (float) Math.sqrt(1 - a * a)) / 2; + } + a--; + a *= 2; + return ((float) Math.sqrt(1 - a * a) + 1) / 2; + } + }; + + static public final Interpolation circleIn = new Interpolation() { + @Override + public float apply(float a) { + return 1 - (float) Math.sqrt(1 - a * a); + } + }; + + static public final Interpolation circleOut = new Interpolation() { + @Override + public float apply(float a) { + a--; + return (float) Math.sqrt(1 - a * a); + } + }; + + static public final Elastic elastic = new Elastic(2, 10); + static public final Elastic elasticIn = new ElasticIn(2, 10); + static public final Elastic elasticOut = new ElasticOut(2, 10); + + static public final Interpolation swing = new Swing(1.5f); + static public final Interpolation swingIn = new SwingIn(2f); + static public final Interpolation swingOut = new SwingOut(2f); + + static public final Interpolation bounce = new Bounce(4); + static public final Interpolation bounceIn = new BounceIn(4); + static public final Interpolation bounceOut = new BounceOut(4); + + // + + static public class Pow extends Interpolation { + final int power; + + public Pow(int power) { + this.power = power; + } + + @Override + public float apply(float a) { + if (a <= 0.5f) + return (float) Math.pow(a * 2, power) / 2; + return (float) Math.pow((a - 1) * 2, power) / (power % 2 == 0 ? -2 : 2) + 1; + } + } + + static public class PowIn extends Pow { + public PowIn(int power) { + super(power); + } + + @Override + public float apply(float a) { + return (float) Math.pow(a, power); + } + } + + static public class PowOut extends Pow { + public PowOut(int power) { + super(power); + } + + @Override + public float apply(float a) { + return (float) Math.pow(a - 1, power) * (power % 2 == 0 ? -1 : 1) + 1; + } + } + + // + + static public class Exp extends Interpolation { + final float value, power, min, scale; + + public Exp(float value, float power) { + this.value = value; + this.power = power; + min = (float) Math.pow(value, -power); + scale = 1 / (1 - min); + } + + @Override + public float apply(float a) { + if (a <= 0.5f) + return ((float) Math.pow(value, power * (a * 2 - 1)) - min) * scale / 2; + return (2 - ((float) Math.pow(value, -power * (a * 2 - 1)) - min) * scale) / 2; + } + } + + static public class ExpIn extends Exp { + public ExpIn(float value, float power) { + super(value, power); + } + + @Override + public float apply(float a) { + return ((float) Math.pow(value, power * (a - 1)) - min) * scale; + } + } + + static public class ExpOut extends Exp { + public ExpOut(float value, float power) { + super(value, power); + } + + @Override + public float apply(float a) { + return 1 - ((float) Math.pow(value, -power * a) - min) * scale; + } + } + + // + + static public class Elastic extends Interpolation { + final float value, power; + + public Elastic(float value, float power) { + this.value = value; + this.power = power; + } + + @Override + public float apply(float a) { + if (a <= 0.5f) { + a *= 2; + return (float) Math.pow(value, power * (a - 1)) * MathUtils.sin(a * 20) * 1.0955f + / 2; + } + a = 1 - a; + a *= 2; + return 1 - (float) Math.pow(value, power * (a - 1)) * MathUtils.sin((a) * 20) * 1.0955f + / 2; + } + } + + static public class ElasticIn extends Elastic { + public ElasticIn(float value, float power) { + super(value, power); + } + + @Override + public float apply(float a) { + return (float) Math.pow(value, power * (a - 1)) * MathUtils.sin(a * 20) * 1.0955f; + } + } + + static public class ElasticOut extends Elastic { + public ElasticOut(float value, float power) { + super(value, power); + } + + @Override + public float apply(float a) { + a = 1 - a; + return (1 - (float) Math.pow(value, power * (a - 1)) * MathUtils.sin(a * 20) * 1.0955f); + } + } + + // + + static public class Bounce extends BounceOut { + public Bounce(float[] widths, float[] heights) { + super(widths, heights); + } + + public Bounce(int bounces) { + super(bounces); + } + + private float out(float a) { + float test = a + widths[0] / 2; + if (test < widths[0]) + return test / (widths[0] / 2) - 1; + return super.apply(a); + } + + @Override + public float apply(float a) { + if (a <= 0.5f) + return (1 - out(1 - a * 2)) / 2; + return out(a * 2 - 1) / 2 + 0.5f; + } + } + + static public class BounceOut extends Interpolation { + final float[] widths, heights; + + public BounceOut(float[] widths, float[] heights) { + if (widths.length != heights.length) + throw new IllegalArgumentException("Must be the same number of widths and heights."); + this.widths = widths; + this.heights = heights; + } + + public BounceOut(int bounces) { + if (bounces < 2 || bounces > 5) + throw new IllegalArgumentException("bounces cannot be < 2 or > 5: " + bounces); + widths = new float[bounces]; + heights = new float[bounces]; + heights[0] = 1; + switch (bounces) { + case 2: + widths[0] = 0.6f; + widths[1] = 0.4f; + heights[1] = 0.33f; + break; + case 3: + widths[0] = 0.4f; + widths[1] = 0.4f; + widths[2] = 0.2f; + heights[1] = 0.33f; + heights[2] = 0.1f; + break; + case 4: + widths[0] = 0.34f; + widths[1] = 0.34f; + widths[2] = 0.2f; + widths[3] = 0.15f; + heights[1] = 0.26f; + heights[2] = 0.11f; + heights[3] = 0.03f; + break; + case 5: + widths[0] = 0.3f; + widths[1] = 0.3f; + widths[2] = 0.2f; + widths[3] = 0.1f; + widths[4] = 0.1f; + heights[1] = 0.45f; + heights[2] = 0.3f; + heights[3] = 0.15f; + heights[4] = 0.06f; + break; + } + widths[0] *= 2; + } + + @Override + public float apply(float a) { + a += widths[0] / 2; + float width = 0, height = 0; + for (int i = 0, n = widths.length; i < n; i++) { + width = widths[i]; + if (a <= width) { + height = heights[i]; + break; + } + a -= width; + } + a /= width; + float z = 4 / width * height * a; + return 1 - (z - z * a) * width; + } + } + + static public class BounceIn extends BounceOut { + public BounceIn(float[] widths, float[] heights) { + super(widths, heights); + } + + public BounceIn(int bounces) { + super(bounces); + } + + @Override + public float apply(float a) { + return 1 - super.apply(1 - a); + } + } + + // + + static public class Swing extends Interpolation { + private final float scale; + + public Swing(float scale) { + this.scale = scale * 2; + } + + @Override + public float apply(float a) { + if (a <= 0.5f) { + a *= 2; + return a * a * ((scale + 1) * a - scale) / 2; + } + a--; + a *= 2; + return a * a * ((scale + 1) * a + scale) / 2 + 1; + } + } + + static public class SwingOut extends Interpolation { + private final float scale; + + public SwingOut(float scale) { + this.scale = scale; + } + + @Override + public float apply(float a) { + a--; + return a * a * ((scale + 1) * a + scale) + 1; + } + } + + static public class SwingIn extends Interpolation { + private final float scale; + + public SwingIn(float scale) { + this.scale = scale; + } + + @Override + public float apply(float a) { + return a * a * ((scale + 1) * a - scale); + } + } +} diff --git a/src/org/oscim/utils/MathUtils.java b/src/org/oscim/utils/MathUtils.java new file mode 100644 index 00000000..f7a762fd --- /dev/null +++ b/src/org/oscim/utils/MathUtils.java @@ -0,0 +1,248 @@ +/******************************************************************************* + * Copyright 2011 libgdx authors. + * + * 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 org.oscim.utils; + +import java.util.Random; + +/** Utility and fast math functions. + *
+ * Thanks to Riven on JavaGaming.org for the basis of sin/cos/atan2/floor/ceil. + * @author Nathan Sweet */ +public class MathUtils { + static public final float nanoToSec = 1 / 1000000000f; + + // --- + + static public final float PI = 3.1415927f; + public static final float PI2 = PI * 2; + + static private final int SIN_BITS = 13; // Adjust for accuracy. + static private final int SIN_MASK = ~(-1 << SIN_BITS); + static private final int SIN_COUNT = SIN_MASK + 1; + + static private final float radFull = PI * 2; + static private final float degFull = 360; + static private final float radToIndex = SIN_COUNT / radFull; + static private final float degToIndex = SIN_COUNT / degFull; + + static public final float radiansToDegrees = 180f / PI; + static public final float radDeg = radiansToDegrees; + static public final float degreesToRadians = PI / 180; + static public final float degRad = degreesToRadians; + + static private class Sin { + static final float[] table = new float[SIN_COUNT]; + static { + for (int i = 0; i < SIN_COUNT; i++) + table[i] = (float)Math.sin((i + 0.5f) / SIN_COUNT * radFull); + for (int i = 0; i < 360; i += 90) + table[(int)(i * degToIndex) & SIN_MASK] = (float)Math.sin(i * degreesToRadians); + } + } + + static private class Cos { + static final float[] table = new float[SIN_COUNT]; + static { + for (int i = 0; i < SIN_COUNT; i++) + table[i] = (float)Math.cos((i + 0.5f) / SIN_COUNT * radFull); + for (int i = 0; i < 360; i += 90) + table[(int)(i * degToIndex) & SIN_MASK] = (float)Math.cos(i * degreesToRadians); + } + } + + /** Returns the sine in radians. */ + static public final float sin (float radians) { + return Sin.table[(int)(radians * radToIndex) & SIN_MASK]; + } + + /** Returns the cosine in radians. */ + static public final float cos (float radians) { + return Cos.table[(int)(radians * radToIndex) & SIN_MASK]; + } + + /** Returns the sine in radians. */ + static public final float sinDeg (float degrees) { + return Sin.table[(int)(degrees * degToIndex) & SIN_MASK]; + } + + /** Returns the cosine in radians. */ + static public final float cosDeg (float degrees) { + return Cos.table[(int)(degrees * degToIndex) & SIN_MASK]; + } + + // --- + + static private final int ATAN2_BITS = 7; // Adjust for accuracy. + static private final int ATAN2_BITS2 = ATAN2_BITS << 1; + static private final int ATAN2_MASK = ~(-1 << ATAN2_BITS2); + static private final int ATAN2_COUNT = ATAN2_MASK + 1; + static final int ATAN2_DIM = (int)Math.sqrt(ATAN2_COUNT); + static private final float INV_ATAN2_DIM_MINUS_1 = 1.0f / (ATAN2_DIM - 1); + + static private class Atan2 { + static final float[] table = new float[ATAN2_COUNT]; + static { + for (int i = 0; i < ATAN2_DIM; i++) { + for (int j = 0; j < ATAN2_DIM; j++) { + float x0 = (float)i / ATAN2_DIM; + float y0 = (float)j / ATAN2_DIM; + table[j * ATAN2_DIM + i] = (float)Math.atan2(y0, x0); + } + } + } + } + + /** Returns atan2 in radians from a lookup table. */ + static public final float atan2 (float y, float x) { + float add, mul; + if (x < 0) { + if (y < 0) { + y = -y; + mul = 1; + } else + mul = -1; + x = -x; + add = -PI; + } else { + if (y < 0) { + y = -y; + mul = -1; + } else + mul = 1; + add = 0; + } + float invDiv = 1 / ((x < y ? y : x) * INV_ATAN2_DIM_MINUS_1); + int xi = (int)(x * invDiv); + int yi = (int)(y * invDiv); + return (Atan2.table[yi * ATAN2_DIM + xi] + add) * mul; + } + + // --- + + static public Random random = new Random(); + + /** Returns a random number between 0 (inclusive) and the specified value (inclusive). */ + static public final int random (int range) { + return random.nextInt(range + 1); + } + + /** Returns a random number between start (inclusive) and end (inclusive). */ + static public final int random (int start, int end) { + return start + random.nextInt(end - start + 1); + } + + /** Returns a random boolean value. */ + static public final boolean randomBoolean () { + return random.nextBoolean(); + } + + /** Returns random number between 0.0 (inclusive) and 1.0 (exclusive). */ + static public final float random () { + return random.nextFloat(); + } + + /** Returns a random number between 0 (inclusive) and the specified value (exclusive). */ + static public final float random (float range) { + return random.nextFloat() * range; + } + + /** Returns a random number between start (inclusive) and end (exclusive). */ + static public final float random (float start, float end) { + return start + random.nextFloat() * (end - start); + } + + // --- + + /** Returns the next power of two. Returns the specified value if the value is already a power of two. */ + static public int nextPowerOfTwo (int value) { + if (value == 0) return 1; + value--; + value |= value >> 1; + value |= value >> 2; + value |= value >> 4; + value |= value >> 8; + value |= value >> 16; + return value + 1; + } + + static public boolean isPowerOfTwo (int value) { + return value != 0 && (value & value - 1) == 0; + } + + // --- + + static public int clamp (int value, int min, int max) { + if (value < min) return min; + if (value > max) return max; + return value; + } + + static public short clamp (short value, short min, short max) { + if (value < min) return min; + if (value > max) return max; + return value; + } + + static public float clamp (float value, float min, float max) { + if (value < min) return min; + if (value > max) return max; + return value; + } + + // --- + + static private final int BIG_ENOUGH_INT = 16 * 1024; + static private final double BIG_ENOUGH_FLOOR = BIG_ENOUGH_INT; + static private final double CEIL = 0.9999999; + static private final double BIG_ENOUGH_CEIL = Double.longBitsToDouble(Double.doubleToLongBits(BIG_ENOUGH_INT + 1) - 1); + static private final double BIG_ENOUGH_ROUND = BIG_ENOUGH_INT + 0.5f; + + /** Returns the largest integer less than or equal to the specified float. This method will only properly floor floats from + * -(2^14) to (Float.MAX_VALUE - 2^14). */ + static public int floor (float x) { + return (int)(x + BIG_ENOUGH_FLOOR) - BIG_ENOUGH_INT; + } + + /** Returns the largest integer less than or equal to the specified float. This method will only properly floor floats that are + * positive. Note this method simply casts the float to int. */ + static public int floorPositive (float x) { + return (int)x; + } + + /** Returns the smallest integer greater than or equal to the specified float. This method will only properly ceil floats from + * -(2^14) to (Float.MAX_VALUE - 2^14). */ + static public int ceil (float x) { + return (int)(x + BIG_ENOUGH_CEIL) - BIG_ENOUGH_INT; + } + + /** Returns the smallest integer greater than or equal to the specified float. This method will only properly ceil floats that + * are positive. */ + static public int ceilPositive (float x) { + return (int)(x + CEIL); + } + + /** Returns the closest integer to the specified float. This method will only properly round floats from -(2^14) to + * (Float.MAX_VALUE - 2^14). */ + static public int round (float x) { + return (int)(x + BIG_ENOUGH_ROUND) - BIG_ENOUGH_INT; + } + + /** Returns the closest integer to the specified float. This method will only properly round floats that are positive. */ + static public int roundPositive (float x) { + return (int)(x + 0.5f); + } +} diff --git a/src/org/oscim/view/MapViewPosition.java b/src/org/oscim/view/MapViewPosition.java index c09258e0..2788a9a3 100644 --- a/src/org/oscim/view/MapViewPosition.java +++ b/src/org/oscim/view/MapViewPosition.java @@ -25,6 +25,7 @@ import org.oscim.core.PointD; import org.oscim.core.PointF; import org.oscim.core.Tile; import org.oscim.utils.FastMath; +import org.oscim.utils.Interpolation; import org.oscim.utils.Matrix4; import android.opengl.Matrix; @@ -33,8 +34,6 @@ import android.os.Message; import android.os.SystemClock; import android.util.Log; import android.view.animation.AccelerateDecelerateInterpolator; -import android.view.animation.LinearInterpolator; -import android.widget.Scroller; /** * A MapPosition stores the latitude and longitude coordinate of a MapView @@ -78,7 +77,6 @@ public class MapViewPosition { public float mTilt; private final AnimationHandler mHandler; - private final Scroller mScroller; MapViewPosition(MapView mapView) { mMapView = mapView; @@ -89,13 +87,6 @@ public class MapViewPosition { mAbsScale = 1; mHandler = new AnimationHandler(this); - mScroller = new Scroller(mapView.getContext(), new LinearInterpolator() { - @Override - public float getInterpolation(float input) { - return (float) Math.sqrt(input); - - } - }); } private final Matrix4 mProjMatrix = new Matrix4(); @@ -739,20 +730,48 @@ public class MapViewPosition { mHandler.start(mDuration); } + + synchronized boolean fling(long millisLeft){ + + float delta = (mDuration - millisLeft) / mDuration; + float adv = Interpolation.exp5Out.apply(delta); + //adv *= Interpolation. + //float adv = delta; + float dx = mVelocityX * adv; + float dy = mVelocityY * adv; + + if (dx != 0 || dy != 0){ + moveMap((float)(dx - mScrollX), (float)(dy - mScrollY)); + + mMapView.redrawMap(true); + mScrollX = dx; + mScrollY = dy; + } + return true; +} + private float mVelocityX; + private float mVelocityY; + public synchronized void animateFling(int velocityX, int velocityY, int minX, int maxX, int minY, int maxY) { mScrollX = 0; mScrollY = 0; - mScroller.fling(0, 0, velocityX, velocityY, minX, maxX, minY, maxY); + mDuration = 300; + + mVelocityX = velocityX * (mDuration / 1000); + mVelocityY = velocityY * (mDuration / 1000); + FastMath.clamp(mVelocityX, minX, maxX); + FastMath.clamp(mVelocityY, minY, maxY); + + // mScroller.fling(0, 0, velocityX, velocityY, minX, maxX, minY, maxY); mAnimFling = true; mAnimMove = false; mAnimScale = false; //mMapView.mGLView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); - mDuration = 250; mHandler.start(mDuration); } @@ -770,26 +789,6 @@ public class MapViewPosition { //scroll(); } - synchronized boolean scroll() { - if (mScroller.isFinished()) { - return false; - } - mScroller.computeScrollOffset(); - - double dx = mScroller.getCurrX(); - double dy = mScroller.getCurrY(); - - int mx = (int) (dx - mScrollX); - int my = (int) (dy - mScrollY); - - if (mx >= 1 || my >= 1 || mx <= -1 || my <= -1) { - moveMap(mx, my); - mScrollX = dx; - mScrollY = dy; - } - return true; - } - void onTick(long millisLeft) { boolean changed = false; @@ -818,7 +817,7 @@ public class MapViewPosition { updatePosition(); } - if (mAnimFling && scroll()) + if (mAnimFling && fling(millisLeft)) changed = true; if (changed) diff --git a/src/org/oscim/view/TouchHandler.java b/src/org/oscim/view/TouchHandler.java index 6156e676..90b8937a 100644 --- a/src/org/oscim/view/TouchHandler.java +++ b/src/org/oscim/view/TouchHandler.java @@ -382,8 +382,8 @@ final class TouchHandler implements OnGestureListener, OnDoubleTapListener { if (mWasMulti) return true; - int w = Tile.TILE_SIZE * 6; - int h = Tile.TILE_SIZE * 6; + int w = Tile.TILE_SIZE * 3; + int h = Tile.TILE_SIZE * 3; //if (mMapView.enablePagedFling) { // double a = Math.sqrt(velocityX * velocityX + velocityY * velocityY); @@ -399,8 +399,10 @@ final class TouchHandler implements OnGestureListener, OnDoubleTapListener { //} else { float s = (200 / mMapView.dpi); - mMapPosition.animateFling(Math.round(velocityX * s), Math.round(velocityY * s), -w, w, -h, - h); + mMapPosition.animateFling( + Math.round(velocityX * s), + Math.round(velocityY * s), + -w, w, -h, h); return true; }