From 4f77643321975800648156ab8c59730ef9673b77 Mon Sep 17 00:00:00 2001 From: Hannes Janetzek Date: Sat, 22 Mar 2014 06:13:23 +0100 Subject: [PATCH] refactor: GdxMap --- vtm-gdx/src/org/oscim/gdx/GdxMap.java | 566 +++---------------- vtm-gdx/src/org/oscim/gdx/InputHandler.java | 189 +++++++ vtm-gdx/src/org/oscim/gdx/MapController.java | 233 ++++++++ 3 files changed, 489 insertions(+), 499 deletions(-) create mode 100644 vtm-gdx/src/org/oscim/gdx/InputHandler.java create mode 100644 vtm-gdx/src/org/oscim/gdx/MapController.java diff --git a/vtm-gdx/src/org/oscim/gdx/GdxMap.java b/vtm-gdx/src/org/oscim/gdx/GdxMap.java index ceddd5d2..b21f23a1 100644 --- a/vtm-gdx/src/org/oscim/gdx/GdxMap.java +++ b/vtm-gdx/src/org/oscim/gdx/GdxMap.java @@ -16,15 +16,12 @@ */ package org.oscim.gdx; -import org.oscim.core.Tile; -import org.oscim.layers.GenericLayer; import org.oscim.layers.TileGridLayer; import org.oscim.layers.tile.vector.BuildingLayer; import org.oscim.layers.tile.vector.VectorTileLayer; import org.oscim.layers.tile.vector.labeling.LabelLayer; import org.oscim.map.Layers; import org.oscim.map.Map; -import org.oscim.map.ViewController; import org.oscim.renderer.MapRenderer; import org.oscim.theme.VtmThemes; import org.oscim.tiling.TileSource; @@ -32,97 +29,22 @@ import org.oscim.tiling.TileSource; import com.badlogic.gdx.Application; import com.badlogic.gdx.ApplicationListener; import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.Input; -import com.badlogic.gdx.Input.Buttons; import com.badlogic.gdx.InputMultiplexer; -import com.badlogic.gdx.InputProcessor; import com.badlogic.gdx.input.GestureDetector; -import com.badlogic.gdx.input.GestureDetector.GestureListener; -import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.Timer; import com.badlogic.gdx.utils.Timer.Task; public abstract class GdxMap implements ApplicationListener { protected final Map mMap; + protected final MapAdapter mMapAdapter; + + VectorTileLayer mMapLayer; private final MapRenderer mMapRenderer; - boolean mRenderRequest; - public GdxMap() { - - mMap = new Map() { - @Override - public int getWidth() { - return mWidth; - } - - @Override - public int getHeight() { - return mHeight; - } - - @Override - public void updateMap(boolean forceRender) { - if (!mWaitRedraw) { - mWaitRedraw = true; - Gdx.app.postRunnable(mRedrawRequest); - } - } - - @Override - public void render() { - mRenderRequest = true; - if (mClearMap) - updateMap(false); - else - Gdx.graphics.requestRendering(); - } - - @Override - public boolean post(Runnable runnable) { - Gdx.app.postRunnable(runnable); - return true; - } - - @Override - public boolean postDelayed(final Runnable action, long delay) { - Timer.schedule(new Task() { - @Override - public void run() { - action.run(); - } - }, delay / 1000f); - return true; - } - - /** - * Update all Layers on Main thread. - * - * @param forceRedraw - * also render frame FIXME (does nothing atm) - */ - private void redrawMapInternal(boolean forceRedraw) { - - updateLayers(); - - mRenderRequest = true; - Gdx.graphics.requestRendering(); - } - - /* private */boolean mWaitRedraw; - private final Runnable mRedrawRequest = new Runnable() { - @Override - public void run() { - mWaitRedraw = false; - redrawMapInternal(false); - } - }; - - }; - + mMap = mMapAdapter = new MapAdapter(); mMapRenderer = new MapRenderer(mMap); - } protected void initDefaultLayers(TileSource tileSource, boolean tileGrid, boolean labels, @@ -144,15 +66,6 @@ public abstract class GdxMap implements ApplicationListener { layers.add(new TileGridLayer(mMap)); } - // Stage ui; - // Label fps; - // BitmapFont font; - - VectorTileLayer mMapLayer; - GenericLayer mGridLayer; - - int mHeight, mWidth; - @Override public void create() { @@ -161,33 +74,17 @@ public abstract class GdxMap implements ApplicationListener { int w = Gdx.graphics.getWidth(); int h = Gdx.graphics.getHeight(); - mWidth = w; - mHeight = h; mMap.viewport().setScreenSize(w, h); - - //MapPosition p = new MapPosition(); - //p.setZoomLevel(14); - //p.setPosition(53.08, 8.83); - //p.setPosition(0.0, 0.0); - //mMap.setMapPosition(p); - mMapRenderer.onSurfaceCreated(); mMapRenderer.onSurfaceChanged(w, h); InputMultiplexer mux = new InputMultiplexer(); - MapController controller = new MapController(); - GestureDetector gestureDetector = new GestureDetector(20, 0.5f, 2, 0.05f, controller); - mux.addProcessor(new TouchHandler()); - mux.addProcessor(gestureDetector); + mux.addProcessor(new InputHandler(mMap)); + mux.addProcessor(new GestureDetector(20, 0.5f, 2, 0.05f, + new MapController(mMap))); Gdx.input.setInputProcessor(mux); - // ui = new Stage(w, h, false); - // font = new BitmapFont(false); - // fps = new Label("fps: 0", new Label.LabelStyle(font, Color.WHITE)); - // fps.setPosition(10, 30); - // fps.setColor(0, 1, 0, 1); - // ui.addActor(fps); createLayers(); } @@ -200,26 +97,13 @@ public abstract class GdxMap implements ApplicationListener { @Override public void render() { - // GLState.enableVertexArrays(-1, -1); - // GLState.blend(false); - // GLState.test(false, false); - - if (mRenderRequest) { - mRenderRequest = false; + if (mMapAdapter.needsRedraw()) { mMapRenderer.onDrawFrame(); } - // Gdx.gl20.glBindBuffer(GL20.GL_ARRAY_BUFFER, 0); - // Gdx.gl20.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, 0); - - // fps.setText("fps: " + Gdx.graphics.getFramesPerSecond()); - // ui.draw(); } @Override public void resize(int w, int h) { - mWidth = w; - mHeight = h; - mMap.viewport().setScreenSize(w, h); mMapRenderer.onSurfaceChanged(w, h); mMap.render(); @@ -237,402 +121,86 @@ public abstract class GdxMap implements ApplicationListener { return false; } - class TouchHandler implements InputProcessor { + public Map getMap() { + return mMap; + } - private ViewController mViewport; - - public TouchHandler() { - mViewport = mMap.viewport(); - } - - private boolean mActiveScale; - //private boolean mActiveTilt; - private boolean mActiveRotate; - - private int mPosX, mPosY; + static class MapAdapter extends Map { + boolean mRenderRequest; @Override - public boolean keyDown(int keycode) { - if (onKeyDown(keycode)) - return true; + public int getWidth() { + return Gdx.graphics.getWidth(); + } - switch (keycode) { - case Input.Keys.ESCAPE: - Gdx.app.exit(); - break; + @Override + public int getHeight() { + return Gdx.graphics.getHeight(); + } - case Input.Keys.UP: - mViewport.moveMap(0, -50); - mMap.updateMap(true); - break; - case Input.Keys.DOWN: - mViewport.moveMap(0, 50); - mMap.updateMap(true); - break; - case Input.Keys.LEFT: - mViewport.moveMap(-50, 0); - mMap.updateMap(true); - break; - case Input.Keys.RIGHT: - mViewport.moveMap(50, 0); - mMap.updateMap(true); - break; - case Input.Keys.M: - mViewport.scaleMap(1.05f, 0, 0); - mMap.updateMap(true); - break; - case Input.Keys.N: - mViewport.scaleMap(0.95f, 0, 0); - mMap.updateMap(true); - break; - case Input.Keys.NUM_1: - mMap.animator().animateZoom(500, 0.5, 0, 0); - break; - case Input.Keys.NUM_2: - mMap.animator().animateZoom(500, 2, 0, 0); - break; - - case Input.Keys.D: - mMap.setTheme(VtmThemes.DEFAULT); - mMap.updateMap(false); - break; - - case Input.Keys.T: - mMap.setTheme(VtmThemes.TRONRENDER); - mMap.updateMap(false); - break; - - case Input.Keys.R: - mMap.setTheme(VtmThemes.OSMARENDER); - mMap.updateMap(false); - break; - - case Input.Keys.G: - if (mGridLayer == null) { - mGridLayer = new TileGridLayer(mMap); - mGridLayer.setEnabled(true); - mMap.layers().add(mGridLayer); - } else { - if (mGridLayer.isEnabled()) { - mGridLayer.setEnabled(false); - mMap.layers().remove(mGridLayer); - } else { - mGridLayer.setEnabled(true); - mMap.layers().add(mGridLayer); - } - } - mMap.render(); - break; + @Override + public void updateMap(boolean forceRender) { + if (!mWaitRedraw) { + mWaitRedraw = true; + Gdx.app.postRunnable(mRedrawRequest); } - return false; } @Override - public boolean keyUp(int keycode) { - return false; + public void render() { + mRenderRequest = true; + if (mClearMap) + updateMap(false); + else + Gdx.graphics.requestRendering(); } @Override - public boolean keyTyped(char character) { - return false; + public boolean post(Runnable runnable) { + Gdx.app.postRunnable(runnable); + return true; } @Override - public boolean touchDown(int screenX, int screenY, int pointer, int button) { - if (button == Buttons.MIDDLE) { - mActiveScale = true; - mPosY = screenY; - } else if (button == Buttons.RIGHT) { - mActiveRotate = true; - mPosX = screenX; - mPosY = screenY; - return true; + public boolean postDelayed(final Runnable action, long delay) { + Timer.schedule(new Task() { + @Override + public void run() { + action.run(); + } + }, delay / 1000f); + return true; + } + + /** + * Update all Layers on Main thread. + * + * @param forceRedraw + * also render frame FIXME (does nothing atm) + */ + private void redrawMapInternal(boolean forceRedraw) { + updateLayers(); + + mRenderRequest = true; + Gdx.graphics.requestRendering(); + } + + /* private */boolean mWaitRedraw; + private final Runnable mRedrawRequest = new Runnable() { + @Override + public void run() { + mWaitRedraw = false; + redrawMapInternal(false); } - return false; - } + }; - @Override - public boolean touchUp(int screenX, int screenY, int pointer, int button) { - mActiveScale = false; - mActiveRotate = false; - - return false; - } - - @Override - public boolean touchDragged(int screenX, int screenY, int pointer) { - boolean changed = false; - - if (!(mActiveScale || mActiveRotate)) + public boolean needsRedraw() { + if (!mRenderRequest) return false; - if (mActiveScale) { - // changed = mMapPosition.tilt((screenY - mStartY) / 5f); - changed = mViewport.scaleMap(1 - (screenY - mPosY) / 100f, 0, 0); - mPosY = screenY; - } - - if (mActiveRotate) { - mViewport.rotateMap((screenX - mPosX) / 500f, 0, 0); - mPosX = screenX; - mViewport.tiltMap((screenY - mPosY) / 10f); - mPosY = screenY; - changed = true; - } - - if (changed) { - mMap.updateMap(true); - } + mRenderRequest = false; return true; } - @Override - public boolean mouseMoved(int screenX, int screenY) { - mPosX = screenX; - mPosY = screenY; - return false; - } - - @Override - public boolean scrolled(int amount) { - - if (amount > 0) { - - mMap.animator().animateZoom(250, 0.75f, 0, 0); - } else { - float fx = mPosX - mMap.getWidth() / 2; - float fy = mPosY - mMap.getHeight() / 2; - - mMap.animator().animateZoom(250, 1.333f, fx, fy); - } - mMap.updateMap(false); - - return true; - } - } - - class MapController implements GestureListener { - - private boolean mayFling = true; - - private boolean mPinch; - - private boolean mBeginScale; - private float mSumScale; - private float mSumRotate; - - private boolean mBeginRotate; - private boolean mBeginTilt; - - private float mPrevX; - private float mPrevY; - - private float mPrevX2; - private float mPrevY2; - - private float mFocusX; - private float mFocusY; - - private double mAngle; - protected double mPrevPinchWidth = -1; - - protected static final int JUMP_THRESHOLD = 100; - protected static final double PINCH_ZOOM_THRESHOLD = 5; - protected static final double PINCH_ROTATE_THRESHOLD = 0.02; - protected static final float PINCH_TILT_THRESHOLD = 1f; - - private ViewController mViewport; - - public MapController() { - mViewport = mMap.viewport(); - } - - @Override - public boolean touchDown(float x, float y, int pointer, int button) { - mayFling = true; - mPinch = false; - - return false; - } - - @Override - public boolean tap(float x, float y, int count, int button) { - return false; - } - - @Override - public boolean longPress(float x, float y) { - return false; - } - - @Override - public boolean fling(final float velocityX, final float velocityY, - int button) { - //log.debug("fling " + button + " " + velocityX + "/" + velocityY); - if (mayFling && button == Buttons.LEFT) { - int m = Tile.SIZE * 4; - mMap.animator().animateFling((int) velocityX, (int) velocityY, -m, m, -m, m); - return true; - } - return false; - } - - @Override - public boolean pan(float x, float y, float deltaX, float deltaY) { - if (mPinch) - return true; - - mViewport.moveMap(deltaX, deltaY); - mMap.updateMap(true); - - return false; - } - - @Override - public boolean zoom(float initialDistance, float distance) { - return false; - } - - @Override - public boolean pinch(Vector2 initialPointer1, Vector2 initialPointer2, - Vector2 pointer1, Vector2 pointer2) { - mayFling = false; - - if (!mPinch) { - mPrevX = pointer1.x; - mPrevY = pointer1.y; - mPrevX2 = pointer2.x; - mPrevY2 = pointer2.y; - - double dx = mPrevX - mPrevX2; - double dy = mPrevY - mPrevY2; - - mAngle = Math.atan2(dy, dx); - mPrevPinchWidth = Math.sqrt(dx * dx + dy * dy); - - mPinch = true; - - mBeginTilt = false; - mBeginRotate = false; - mBeginScale = false; - - return true; - } - - float x1 = pointer1.x; - float y1 = pointer1.y; - - //float mx = x1 - mPrevX; - float my = y1 - mPrevY; - - float x2 = pointer2.x; - float y2 = pointer2.y; - - float dx = (x1 - x2); - float dy = (y1 - y2); - float slope = 0; - - if (dx != 0) - slope = dy / dx; - - double pinchWidth = Math.sqrt(dx * dx + dy * dy); - - final double deltaPinchWidth = pinchWidth - mPrevPinchWidth; - - double rad = Math.atan2(dy, dx); - double r = rad - mAngle; - - boolean startScale = (Math.abs(deltaPinchWidth) > PINCH_ZOOM_THRESHOLD); - - boolean changed = false; - - if (!mBeginTilt && (mBeginScale || startScale)) { - mBeginScale = true; - - float scale = (float) (pinchWidth / mPrevPinchWidth); - - // decrease change of scale by the change of rotation - // * 20 is just arbitrary - if (mBeginRotate) - scale = 1 + ((scale - 1) * Math.max((1 - (float) Math.abs(r) * 20), 0)); - - mSumScale *= scale; - - if ((mSumScale < 0.99 || mSumScale > 1.01) - && mSumRotate < Math.abs(0.02)) - mBeginRotate = false; - - float fx = (x2 + x1) / 2 - mWidth / 2; - float fy = (y2 + y1) / 2 - mHeight / 2; - - // log.debug("zoom " + deltaPinchWidth + " " + scale + " " + - // mSumScale); - changed = mViewport.scaleMap(scale, fx, fy); - } - - if (!mBeginRotate && Math.abs(slope) < 1) { - float my2 = y2 - mPrevY2; - float threshold = PINCH_TILT_THRESHOLD; - - // log.debug(r + " " + slope + " m1:" + my + " m2:" + my2); - - if ((my > threshold && my2 > threshold) - || (my < -threshold && my2 < -threshold)) { - mBeginTilt = true; - changed = mViewport.tiltMap(my / 5); - } - } - - if (!mBeginTilt - && (mBeginRotate || (Math.abs(slope) > 1 && Math.abs(r) > PINCH_ROTATE_THRESHOLD))) { - // log.debug("rotate: " + mBeginRotate + " " + - // Math.toDegrees(rad)); - if (!mBeginRotate) { - mAngle = rad; - - mSumScale = 1; - mSumRotate = 0; - - mBeginRotate = true; - - mFocusX = (x1 + x2) / 2 - (mWidth / 2); - mFocusY = (y1 + y2) / 2 - (mHeight / 2); - } else { - double da = rad - mAngle; - mSumRotate += da; - - if (Math.abs(da) > 0.001) { - double rsin = Math.sin(r); - double rcos = Math.cos(r); - float x = (float) (mFocusX * rcos + mFocusY * -rsin - mFocusX); - float y = (float) (mFocusX * rsin + mFocusY * rcos - mFocusY); - - mViewport.rotateMap(da, x, y); - changed = true; - } - } - mAngle = rad; - } - - if (changed) { - mMap.updateMap(true); - mPrevPinchWidth = pinchWidth; - mPrevY2 = y2; - - } - - mPrevX = x1; - mPrevY = y1; - mPrevX2 = x2; - - return true; - } - - @Override - public boolean panStop(float x, float y, int pointer, int button) { - return false; - } - } - } diff --git a/vtm-gdx/src/org/oscim/gdx/InputHandler.java b/vtm-gdx/src/org/oscim/gdx/InputHandler.java new file mode 100644 index 00000000..2af30570 --- /dev/null +++ b/vtm-gdx/src/org/oscim/gdx/InputHandler.java @@ -0,0 +1,189 @@ +package org.oscim.gdx; + +import org.oscim.layers.GenericLayer; +import org.oscim.layers.TileGridLayer; +import org.oscim.map.Map; +import org.oscim.map.ViewController; +import org.oscim.theme.VtmThemes; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Input; +import com.badlogic.gdx.Input.Buttons; +import com.badlogic.gdx.InputProcessor; + +public class InputHandler implements InputProcessor { + + private ViewController mViewport; + private final Map mMap; + private GenericLayer mGridLayer; + + public InputHandler(Map map) { + mViewport = map.viewport(); + mMap = map; + } + + private boolean mActiveScale; + //private boolean mActiveTilt; + private boolean mActiveRotate; + + private int mPosX, mPosY; + + @Override + public boolean keyDown(int keycode) { + //if (onKeyDown(keycode)) + // return true; + + switch (keycode) { + case Input.Keys.ESCAPE: + Gdx.app.exit(); + break; + + case Input.Keys.UP: + mViewport.moveMap(0, -50); + mMap.updateMap(true); + break; + case Input.Keys.DOWN: + mViewport.moveMap(0, 50); + mMap.updateMap(true); + break; + case Input.Keys.LEFT: + mViewport.moveMap(-50, 0); + mMap.updateMap(true); + break; + case Input.Keys.RIGHT: + mViewport.moveMap(50, 0); + mMap.updateMap(true); + break; + case Input.Keys.M: + mViewport.scaleMap(1.05f, 0, 0); + mMap.updateMap(true); + break; + case Input.Keys.N: + mViewport.scaleMap(0.95f, 0, 0); + mMap.updateMap(true); + break; + case Input.Keys.NUM_1: + mMap.animator().animateZoom(500, 0.5, 0, 0); + break; + case Input.Keys.NUM_2: + mMap.animator().animateZoom(500, 2, 0, 0); + break; + + case Input.Keys.D: + mMap.setTheme(VtmThemes.DEFAULT); + mMap.updateMap(false); + break; + + case Input.Keys.T: + mMap.setTheme(VtmThemes.TRONRENDER); + mMap.updateMap(false); + break; + + case Input.Keys.R: + mMap.setTheme(VtmThemes.OSMARENDER); + mMap.updateMap(false); + break; + + case Input.Keys.G: + if (mGridLayer == null) { + mGridLayer = new TileGridLayer(mMap); + mGridLayer.setEnabled(true); + mMap.layers().add(mGridLayer); + } else { + if (mGridLayer.isEnabled()) { + mGridLayer.setEnabled(false); + mMap.layers().remove(mGridLayer); + } else { + mGridLayer.setEnabled(true); + mMap.layers().add(mGridLayer); + } + } + mMap.render(); + break; + } + return false; + } + + @Override + public boolean keyUp(int keycode) { + return false; + } + + @Override + public boolean keyTyped(char character) { + return false; + } + + @Override + public boolean touchDown(int screenX, int screenY, int pointer, int button) { + if (button == Buttons.MIDDLE) { + mActiveScale = true; + mPosY = screenY; + } else if (button == Buttons.RIGHT) { + mActiveRotate = true; + mPosX = screenX; + mPosY = screenY; + return true; + } + return false; + } + + @Override + public boolean touchUp(int screenX, int screenY, int pointer, int button) { + mActiveScale = false; + mActiveRotate = false; + + return false; + } + + @Override + public boolean touchDragged(int screenX, int screenY, int pointer) { + boolean changed = false; + + if (!(mActiveScale || mActiveRotate)) + return false; + + if (mActiveScale) { + // changed = mMapPosition.tilt((screenY - mStartY) / 5f); + changed = mViewport.scaleMap(1 - (screenY - mPosY) / 100f, 0, 0); + mPosY = screenY; + } + + if (mActiveRotate) { + mViewport.rotateMap((screenX - mPosX) / 500f, 0, 0); + mPosX = screenX; + mViewport.tiltMap((screenY - mPosY) / 10f); + mPosY = screenY; + changed = true; + } + + if (changed) { + mMap.updateMap(true); + } + return true; + } + + @Override + public boolean mouseMoved(int screenX, int screenY) { + mPosX = screenX; + mPosY = screenY; + return false; + } + + @Override + public boolean scrolled(int amount) { + + if (amount > 0) { + + mMap.animator().animateZoom(250, 0.75f, 0, 0); + } else { + float fx = mPosX - mMap.getWidth() / 2; + float fy = mPosY - mMap.getHeight() / 2; + + mMap.animator().animateZoom(250, 1.333f, fx, fy); + } + mMap.updateMap(false); + + return true; + } +} diff --git a/vtm-gdx/src/org/oscim/gdx/MapController.java b/vtm-gdx/src/org/oscim/gdx/MapController.java new file mode 100644 index 00000000..405460a1 --- /dev/null +++ b/vtm-gdx/src/org/oscim/gdx/MapController.java @@ -0,0 +1,233 @@ +package org.oscim.gdx; + +import org.oscim.core.Tile; +import org.oscim.map.Map; + +import com.badlogic.gdx.Input.Buttons; +import com.badlogic.gdx.input.GestureDetector.GestureListener; +import com.badlogic.gdx.math.Vector2; + +public class MapController implements GestureListener { + private boolean mayFling = true; + + private boolean mPinch; + + private boolean mBeginScale; + private float mSumScale; + private float mSumRotate; + + private boolean mBeginRotate; + private boolean mBeginTilt; + + private float mPrevX; + private float mPrevY; + + private float mPrevX2; + private float mPrevY2; + + private float mFocusX; + private float mFocusY; + + private double mAngle; + protected double mPrevPinchWidth = -1; + + protected static final int JUMP_THRESHOLD = 100; + protected static final double PINCH_ZOOM_THRESHOLD = 5; + protected static final double PINCH_ROTATE_THRESHOLD = 0.02; + protected static final float PINCH_TILT_THRESHOLD = 1f; + + //private ViewController mViewport; + private final Map mMap; + + public MapController(Map map) { + //mViewport = mMap.viewport(); + mMap = map; + } + + @Override + public boolean touchDown(float x, float y, int pointer, int button) { + mayFling = true; + mPinch = false; + + return false; + } + + @Override + public boolean tap(float x, float y, int count, int button) { + return false; + } + + @Override + public boolean longPress(float x, float y) { + return false; + } + + @Override + public boolean fling(final float velocityX, final float velocityY, + int button) { + //log.debug("fling " + button + " " + velocityX + "/" + velocityY); + if (mayFling && button == Buttons.LEFT) { + int m = Tile.SIZE * 4; + mMap.animator().animateFling((int) velocityX, (int) velocityY, -m, m, -m, m); + return true; + } + return false; + } + + @Override + public boolean pan(float x, float y, float deltaX, float deltaY) { + if (mPinch) + return true; + + mMap.viewport().moveMap(deltaX, deltaY); + mMap.updateMap(true); + + return false; + } + + @Override + public boolean zoom(float initialDistance, float distance) { + return false; + } + + @Override + public boolean pinch(Vector2 initialPointer1, Vector2 initialPointer2, + Vector2 pointer1, Vector2 pointer2) { + mayFling = false; + + if (!mPinch) { + mPrevX = pointer1.x; + mPrevY = pointer1.y; + mPrevX2 = pointer2.x; + mPrevY2 = pointer2.y; + + double dx = mPrevX - mPrevX2; + double dy = mPrevY - mPrevY2; + + mAngle = Math.atan2(dy, dx); + mPrevPinchWidth = Math.sqrt(dx * dx + dy * dy); + + mPinch = true; + + mBeginTilt = false; + mBeginRotate = false; + mBeginScale = false; + + return true; + } + + float x1 = pointer1.x; + float y1 = pointer1.y; + + //float mx = x1 - mPrevX; + float my = y1 - mPrevY; + + float x2 = pointer2.x; + float y2 = pointer2.y; + + float dx = (x1 - x2); + float dy = (y1 - y2); + float slope = 0; + + if (dx != 0) + slope = dy / dx; + + double pinchWidth = Math.sqrt(dx * dx + dy * dy); + + final double deltaPinchWidth = pinchWidth - mPrevPinchWidth; + + double rad = Math.atan2(dy, dx); + double r = rad - mAngle; + + boolean startScale = (Math.abs(deltaPinchWidth) > PINCH_ZOOM_THRESHOLD); + + boolean changed = false; + + if (!mBeginTilt && (mBeginScale || startScale)) { + mBeginScale = true; + + float scale = (float) (pinchWidth / mPrevPinchWidth); + + // decrease change of scale by the change of rotation + // * 20 is just arbitrary + if (mBeginRotate) + scale = 1 + ((scale - 1) * Math.max((1 - (float) Math.abs(r) * 20), 0)); + + mSumScale *= scale; + + if ((mSumScale < 0.99 || mSumScale > 1.01) + && mSumRotate < Math.abs(0.02)) + mBeginRotate = false; + + float fx = (x2 + x1) / 2 - mMap.getWidth() / 2; + float fy = (y2 + y1) / 2 - mMap.getHeight() / 2; + + // log.debug("zoom " + deltaPinchWidth + " " + scale + " " + + // mSumScale); + changed = mMap.viewport().scaleMap(scale, fx, fy); + } + + if (!mBeginRotate && Math.abs(slope) < 1) { + float my2 = y2 - mPrevY2; + float threshold = PINCH_TILT_THRESHOLD; + + // log.debug(r + " " + slope + " m1:" + my + " m2:" + my2); + + if ((my > threshold && my2 > threshold) + || (my < -threshold && my2 < -threshold)) { + mBeginTilt = true; + changed = mMap.viewport().tiltMap(my / 5); + } + } + + if (!mBeginTilt + && (mBeginRotate || (Math.abs(slope) > 1 && Math.abs(r) > PINCH_ROTATE_THRESHOLD))) { + // log.debug("rotate: " + mBeginRotate + " " + + // Math.toDegrees(rad)); + if (!mBeginRotate) { + mAngle = rad; + + mSumScale = 1; + mSumRotate = 0; + + mBeginRotate = true; + + mFocusX = (x1 + x2) / 2 - (mMap.getWidth() / 2); + mFocusY = (y1 + y2) / 2 - (mMap.getHeight() / 2); + } else { + double da = rad - mAngle; + mSumRotate += da; + + if (Math.abs(da) > 0.001) { + double rsin = Math.sin(r); + double rcos = Math.cos(r); + float x = (float) (mFocusX * rcos + mFocusY * -rsin - mFocusX); + float y = (float) (mFocusX * rsin + mFocusY * rcos - mFocusY); + + mMap.viewport().rotateMap(da, x, y); + changed = true; + } + } + mAngle = rad; + } + + if (changed) { + mMap.updateMap(true); + mPrevPinchWidth = pinchWidth; + mPrevY2 = y2; + + } + + mPrevX = x1; + mPrevY = y1; + mPrevX2 = x2; + + return true; + } + + @Override + public boolean panStop(float x, float y, int pointer, int button) { + return false; + } + +}