diff --git a/docs/Changelog.md b/docs/Changelog.md index b4e0264db..d00410267 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -11,7 +11,8 @@ - Native libraries for all platforms [#14](https://github.com/mapsforge/vtm/issues/14) - Line stipple and texture rendering [#105](https://github.com/mapsforge/vtm/issues/105) - Layer groups [#99](https://github.com/mapsforge/vtm/issues/99) [#103](https://github.com/mapsforge/vtm/issues/103) -- Map scale bar multi-platform [#84](https://github.com/mapsforge/vtm/issues/84) +- Location renderer [#171](https://github.com/mapsforge/vtm/issues/171) +- Map scale bar [#84](https://github.com/mapsforge/vtm/issues/84) - libGDX layer gestures [#151](https://github.com/mapsforge/vtm/issues/151) - LWJGL desktop libGDX backend [#129](https://github.com/mapsforge/vtm/issues/129) - Render theme area tessellation option [#37](https://github.com/mapsforge/vtm/issues/37) diff --git a/vtm-app/src/org/oscim/app/location/Compass.java b/vtm-app/src/org/oscim/app/location/Compass.java index cdfbd8df9..4d5dcdd1e 100644 --- a/vtm-app/src/org/oscim/app/location/Compass.java +++ b/vtm-app/src/org/oscim/app/location/Compass.java @@ -1,6 +1,7 @@ /* * Copyright 2013 Ahmad Saleem * Copyright 2013 Hannes Janetzek + * Copyright 2016 devemux86 * * 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 @@ -13,7 +14,6 @@ * You should have received a copy of the GNU Lesser General Public License along with * this program. If not, see . */ - package org.oscim.app.location; import android.content.Context; @@ -31,10 +31,11 @@ import org.oscim.event.Event; import org.oscim.layers.Layer; import org.oscim.map.Map; +import org.oscim.renderer.LocationRenderer; @SuppressWarnings("deprecation") -public class Compass extends Layer implements SensorEventListener, - Map.UpdateListener { +public class Compass extends Layer implements SensorEventListener, Map.UpdateListener, + LocationRenderer.Callback { // final static Logger log = LoggerFactory.getLogger(Compass.class); @@ -84,6 +85,7 @@ public Compass(Context context, Map map) { setEnabled(false); } + @Override public synchronized float getRotation() { return mCurRotation; } diff --git a/vtm-app/src/org/oscim/app/location/LocationOverlay.java b/vtm-app/src/org/oscim/app/location/LocationOverlay.java index c3a4dd2d1..cf63f662a 100644 --- a/vtm-app/src/org/oscim/app/location/LocationOverlay.java +++ b/vtm-app/src/org/oscim/app/location/LocationOverlay.java @@ -16,44 +16,29 @@ */ package org.oscim.app.location; -import android.os.SystemClock; - -import org.oscim.backend.GL; -import org.oscim.core.Box; import org.oscim.core.MercatorProjection; -import org.oscim.core.Point; -import org.oscim.core.Tile; import org.oscim.layers.Layer; import org.oscim.map.Map; -import org.oscim.renderer.GLShader; -import org.oscim.renderer.GLState; -import org.oscim.renderer.GLViewport; -import org.oscim.renderer.LayerRenderer; -import org.oscim.renderer.MapRenderer; -import org.oscim.utils.FastMath; -import org.oscim.utils.math.Interpolation; - -import static org.oscim.backend.GLAdapter.gl; +import org.oscim.renderer.LocationRenderer; public class LocationOverlay extends Layer { - private final int SHOW_ACCURACY_ZOOM = 16; - - private final Point mLocation = new Point(); - private double mRadius; - private final Compass mCompass; + private final LocationRenderer mLocationRenderer; public LocationOverlay(Map map, Compass compass) { super(map); - mRenderer = new LocationIndicator(map); mCompass = compass; + + mRenderer = mLocationRenderer = new LocationRenderer(mMap, this); + mLocationRenderer.setCallback(compass); } public void setPosition(double latitude, double longitude, double accuracy) { - mLocation.x = MercatorProjection.longitudeToX(longitude); - mLocation.y = MercatorProjection.latitudeToY(latitude); - mRadius = accuracy / MercatorProjection.groundResolution(latitude, 1); - ((LocationIndicator) mRenderer).animate(true); + double x = MercatorProjection.longitudeToX(longitude); + double y = MercatorProjection.latitudeToY(latitude); + double radius = accuracy / MercatorProjection.groundResolution(latitude, 1); + mLocationRenderer.setLocation(x, y, radius); + mLocationRenderer.animate(true); } @Override @@ -64,268 +49,8 @@ public void setEnabled(boolean enabled) { super.setEnabled(enabled); if (!enabled) - ((LocationIndicator) mRenderer).animate(false); + mLocationRenderer.animate(false); mCompass.setEnabled(enabled); } - - public class LocationIndicator extends LayerRenderer { - private int mShaderProgram; - private int hVertexPosition; - private int hMatrixPosition; - private int hScale; - private int hPhase; - private int hDirection; - - private final float CIRCLE_SIZE = 60; - - private final static long ANIM_RATE = 50; - private final static long INTERVAL = 2000; - - private final Point mIndicatorPosition = new Point(); - - private final Point mScreenPoint = new Point(); - private final Box mBBox = new Box(); - - private boolean mInitialized; - - private boolean mLocationIsVisible; - - private boolean mRunAnim; - private long mAnimStart; - - public LocationIndicator(final Map map) { - super(); - } - - private void animate(boolean enable) { - if (mRunAnim == enable) - return; - - mRunAnim = enable; - if (!enable) - return; - - final Runnable action = new Runnable() { - private long lastRun; - - @Override - public void run() { - if (!mRunAnim) - return; - - long diff = SystemClock.elapsedRealtime() - lastRun; - mMap.postDelayed(this, Math.min(ANIM_RATE, diff)); - mMap.render(); - } - }; - - mAnimStart = SystemClock.elapsedRealtime(); - mMap.postDelayed(action, ANIM_RATE); - } - - private float animPhase() { - return (float) ((MapRenderer.frametime - mAnimStart) % INTERVAL) / INTERVAL; - } - - @Override - public void update(GLViewport v) { - - if (!mInitialized) { - init(); - mInitialized = true; - } - - if (!isEnabled()) { - setReady(false); - return; - } - - if (!v.changed() && isReady()) - return; - - setReady(true); - - int width = mMap.getWidth(); - int height = mMap.getHeight(); - - // clamp location to a position that can be - // savely translated to screen coordinates - v.getBBox(mBBox, 0); - - double x = mLocation.x; - double y = mLocation.y; - - if (!mBBox.contains(mLocation)) { - x = FastMath.clamp(x, mBBox.xmin, mBBox.xmax); - y = FastMath.clamp(y, mBBox.ymin, mBBox.ymax); - } - - // get position of Location in pixel relative to - // screen center - v.toScreenPoint(x, y, mScreenPoint); - - x = mScreenPoint.x + width / 2; - y = mScreenPoint.y + height / 2; - - // clip position to screen boundaries - int visible = 0; - - if (x > width - 5) - x = width; - else if (x < 5) - x = 0; - else - visible++; - - if (y > height - 5) - y = height; - else if (y < 5) - y = 0; - else - visible++; - - mLocationIsVisible = (visible == 2); - - // set location indicator position - v.fromScreenPoint(x, y, mIndicatorPosition); - } - - @Override - public void render(GLViewport v) { - - GLState.useProgram(mShaderProgram); - GLState.blend(true); - GLState.test(false, false); - - GLState.enableVertexArrays(hVertexPosition, -1); - MapRenderer.bindQuadVertexVBO(hVertexPosition/*, true*/); - - float radius = CIRCLE_SIZE; - - animate(true); - boolean viewShed = false; - if (!mLocationIsVisible /* || pos.zoomLevel < SHOW_ACCURACY_ZOOM */) { - //animate(true); - } else { - if (v.pos.zoomLevel >= SHOW_ACCURACY_ZOOM) - radius = (float) (mRadius * v.pos.scale); - - viewShed = true; - //animate(false); - } - gl.uniform1f(hScale, radius); - - double x = mIndicatorPosition.x - v.pos.x; - double y = mIndicatorPosition.y - v.pos.y; - double tileScale = Tile.SIZE * v.pos.scale; - - v.mvp.setTransScale((float) (x * tileScale), (float) (y * tileScale), 1); - v.mvp.multiplyMM(v.viewproj, v.mvp); - v.mvp.setAsUniform(hMatrixPosition); - - if (!viewShed) { - float phase = Math.abs(animPhase() - 0.5f) * 2; - //phase = Interpolation.fade.apply(phase); - phase = Interpolation.swing.apply(phase); - - gl.uniform1f(hPhase, 0.8f + phase * 0.2f); - } else { - gl.uniform1f(hPhase, 1); - } - - if (viewShed && mLocationIsVisible) { - float rotation = mCompass.getRotation() - 90; - gl.uniform2f(hDirection, - (float) Math.cos(Math.toRadians(rotation)), - (float) Math.sin(Math.toRadians(rotation))); - } else { - gl.uniform2f(hDirection, 0, 0); - } - - gl.drawArrays(GL.TRIANGLE_STRIP, 0, 4); - } - - private boolean init() { - int shader = GLShader.createProgram(vShaderStr, fShaderStr); - if (shader == 0) - return false; - - mShaderProgram = shader; - hVertexPosition = gl.getAttribLocation(shader, "a_pos"); - hMatrixPosition = gl.getUniformLocation(shader, "u_mvp"); - hPhase = gl.getUniformLocation(shader, "u_phase"); - hScale = gl.getUniformLocation(shader, "u_scale"); - hDirection = gl.getUniformLocation(shader, "u_dir"); - - return true; - } - - private final static String vShaderStr = "" - + "precision mediump float;" - + "uniform mat4 u_mvp;" - + "uniform float u_phase;" - + "uniform float u_scale;" - + "attribute vec2 a_pos;" - + "varying vec2 v_tex;" - + "void main() {" - + " gl_Position = u_mvp * vec4(a_pos * u_scale * u_phase, 0.0, 1.0);" - + " v_tex = a_pos;" - + "}"; - - private final static String fShaderStr = "" - + "precision mediump float;" - + "varying vec2 v_tex;" - + "uniform float u_scale;" - + "uniform float u_phase;" - + "uniform vec2 u_dir;" - - + "void main() {" - + " float len = 1.0 - length(v_tex);" - + " if (u_dir.x == 0.0 && u_dir.y == 0.0){" - + " gl_FragColor = vec4(0.2, 0.2, 0.8, 1.0) * len;" - + " } else {" - /// outer ring - + " float a = smoothstep(0.0, 2.0 / u_scale, len);" - /// inner ring - + " float b = 0.5 * smoothstep(4.0 / u_scale, 5.0 / u_scale, len);" - /// center point - + " float c = 0.5 * (1.0 - smoothstep(14.0 / u_scale, 16.0 / u_scale, 1.0 - len));" - + " vec2 dir = normalize(v_tex);" - + " float d = 1.0 - dot(dir, u_dir); " - /// 0.5 width of viewshed - + " d = clamp(step(0.5, d), 0.4, 0.7);" - /// - subtract inner from outer to create the outline - /// - multiply by viewshed - /// - add center point - + " a = d * (a - (b + c)) + c;" - + " gl_FragColor = vec4(0.2, 0.2, 0.8, 1.0) * a;" - + "}}"; - - //private final static String fShaderStr = "" - // + "precision mediump float;" - // + "varying vec2 v_tex;" - // + "uniform float u_scale;" - // + "uniform float u_phase;" - // + "uniform vec2 u_dir;" - // + "void main() {" - // + " float len = 1.0 - length(v_tex);" - // /// outer ring - // + " float a = smoothstep(0.0, 2.0 / u_scale, len);" - // /// inner ring - // + " float b = 0.8 * smoothstep(3.0 / u_scale, 4.0 / u_scale, len);" - // /// center point - // + " float c = 0.5 * (1.0 - smoothstep(14.0 / u_scale, 16.0 / u_scale, 1.0 - len));" - // + " vec2 dir = normalize(v_tex);" - // + " float d = dot(dir, u_dir); " - // /// 0.5 width of viewshed - // + " d = clamp(smoothstep(0.7, 0.7 + 2.0/u_scale, d) * len, 0.0, 1.0);" - // /// - subtract inner from outer to create the outline - // /// - multiply by viewshed - // /// - add center point - // + " a = max(d, (a - (b + c)) + c);" - // + " gl_FragColor = vec4(0.2, 0.2, 0.8, 1.0) * a;" - // + "}"; - - } } diff --git a/vtm/src/org/oscim/renderer/LocationRenderer.java b/vtm/src/org/oscim/renderer/LocationRenderer.java new file mode 100644 index 000000000..7b9bc2964 --- /dev/null +++ b/vtm/src/org/oscim/renderer/LocationRenderer.java @@ -0,0 +1,320 @@ +/* + * Copyright 2013 Ahmad Saleem + * Copyright 2013 Hannes Janetzek + * Copyright 2016 devemux86 + * + * 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.renderer; + +import org.oscim.backend.GL; +import org.oscim.core.Box; +import org.oscim.core.Point; +import org.oscim.core.Tile; +import org.oscim.layers.Layer; +import org.oscim.map.Map; +import org.oscim.utils.FastMath; +import org.oscim.utils.math.Interpolation; + +import static org.oscim.backend.GLAdapter.gl; + +public class LocationRenderer extends LayerRenderer { + private static final int SHOW_ACCURACY_ZOOM = 16; + + private final Map mMap; + private final Layer mLayer; + + private int mShaderProgram; + private int hVertexPosition; + private int hMatrixPosition; + private int hScale; + private int hPhase; + private int hDirection; + + private static final float CIRCLE_SIZE = 60; + + private static final long ANIM_RATE = 50; + private static final long INTERVAL = 2000; + + private final Point mIndicatorPosition = new Point(); + + private final Point mScreenPoint = new Point(); + private final Box mBBox = new Box(); + + private boolean mInitialized; + + private boolean mLocationIsVisible; + + private boolean mRunAnim; + private long mAnimStart; + + private Callback mCallback; + private final Point mLocation = new Point(Double.NaN, Double.NaN); + private double mRadius; + private int mShowAccuracyZoom = SHOW_ACCURACY_ZOOM; + + public LocationRenderer(Map map, Layer layer) { + mMap = map; + mLayer = layer; + } + + public void setCallback(Callback callback) { + mCallback = callback; + } + + public void setLocation(double x, double y, double radius) { + mLocation.x = x; + mLocation.y = y; + mRadius = radius; + } + + public void setShowAccuracyZoom(int showAccuracyZoom) { + mShowAccuracyZoom = showAccuracyZoom; + } + + public void animate(boolean enable) { + if (mRunAnim == enable) + return; + + mRunAnim = enable; + if (!enable) + return; + + final Runnable action = new Runnable() { + private long lastRun; + + @Override + public void run() { + if (!mRunAnim) + return; + + long diff = System.currentTimeMillis() - lastRun; + mMap.postDelayed(this, Math.min(ANIM_RATE, diff)); + mMap.render(); + lastRun = System.currentTimeMillis(); + } + }; + + mAnimStart = System.currentTimeMillis(); + mMap.postDelayed(action, ANIM_RATE); + } + + private float animPhase() { + return (float) ((MapRenderer.frametime - mAnimStart) % INTERVAL) / INTERVAL; + } + + @Override + public void update(GLViewport v) { + + if (!mInitialized) { + init(); + mInitialized = true; + } + + if (!mLayer.isEnabled()) { + setReady(false); + return; + } + + /*if (!v.changed() && isReady()) + return;*/ + + setReady(true); + + int width = mMap.getWidth(); + int height = mMap.getHeight(); + + // clamp location to a position that can be + // savely translated to screen coordinates + v.getBBox(mBBox, 0); + + double x = mLocation.x; + double y = mLocation.y; + + if (!mBBox.contains(mLocation)) { + x = FastMath.clamp(x, mBBox.xmin, mBBox.xmax); + y = FastMath.clamp(y, mBBox.ymin, mBBox.ymax); + } + + // get position of Location in pixel relative to + // screen center + v.toScreenPoint(x, y, mScreenPoint); + + x = mScreenPoint.x + width / 2; + y = mScreenPoint.y + height / 2; + + // clip position to screen boundaries + int visible = 0; + + if (x > width - 5) + x = width; + else if (x < 5) + x = 0; + else + visible++; + + if (y > height - 5) + y = height; + else if (y < 5) + y = 0; + else + visible++; + + mLocationIsVisible = (visible == 2); + + // set location indicator position + v.fromScreenPoint(x, y, mIndicatorPosition); + } + + @Override + public void render(GLViewport v) { + + GLState.useProgram(mShaderProgram); + GLState.blend(true); + GLState.test(false, false); + + GLState.enableVertexArrays(hVertexPosition, -1); + MapRenderer.bindQuadVertexVBO(hVertexPosition/*, true*/); + + float radius = CIRCLE_SIZE; + + animate(true); + boolean viewShed = false; + if (!mLocationIsVisible /* || pos.zoomLevel < SHOW_ACCURACY_ZOOM */) { + //animate(true); + } else { + if (v.pos.zoomLevel >= mShowAccuracyZoom && mRadius > 0) + radius = (float) (mRadius * v.pos.scale); + + viewShed = true; + //animate(false); + } + gl.uniform1f(hScale, radius); + + double x = mIndicatorPosition.x - v.pos.x; + double y = mIndicatorPosition.y - v.pos.y; + double tileScale = Tile.SIZE * v.pos.scale; + + v.mvp.setTransScale((float) (x * tileScale), (float) (y * tileScale), 1); + v.mvp.multiplyMM(v.viewproj, v.mvp); + v.mvp.setAsUniform(hMatrixPosition); + + if (!viewShed) { + float phase = Math.abs(animPhase() - 0.5f) * 2; + //phase = Interpolation.fade.apply(phase); + phase = Interpolation.swing.apply(phase); + + gl.uniform1f(hPhase, 0.8f + phase * 0.2f); + } else { + gl.uniform1f(hPhase, 1); + } + + if (viewShed && mLocationIsVisible) { + float rotation = 0; + if (mCallback != null) + rotation = mCallback.getRotation(); + rotation -= 90; + gl.uniform2f(hDirection, + (float) Math.cos(Math.toRadians(rotation)), + (float) Math.sin(Math.toRadians(rotation))); + } else { + gl.uniform2f(hDirection, 0, 0); + } + + gl.drawArrays(GL.TRIANGLE_STRIP, 0, 4); + } + + private boolean init() { + int shader = GLShader.createProgram(vShaderStr, fShaderStr); + if (shader == 0) + return false; + + mShaderProgram = shader; + hVertexPosition = gl.getAttribLocation(shader, "a_pos"); + hMatrixPosition = gl.getUniformLocation(shader, "u_mvp"); + hPhase = gl.getUniformLocation(shader, "u_phase"); + hScale = gl.getUniformLocation(shader, "u_scale"); + hDirection = gl.getUniformLocation(shader, "u_dir"); + + return true; + } + + private final static String vShaderStr = "" + + "precision mediump float;" + + "uniform mat4 u_mvp;" + + "uniform float u_phase;" + + "uniform float u_scale;" + + "attribute vec2 a_pos;" + + "varying vec2 v_tex;" + + "void main() {" + + " gl_Position = u_mvp * vec4(a_pos * u_scale * u_phase, 0.0, 1.0);" + + " v_tex = a_pos;" + + "}"; + + private final static String fShaderStr = "" + + "precision mediump float;" + + "varying vec2 v_tex;" + + "uniform float u_scale;" + + "uniform float u_phase;" + + "uniform vec2 u_dir;" + + + "void main() {" + + " float len = 1.0 - length(v_tex);" + + " if (u_dir.x == 0.0 && u_dir.y == 0.0){" + + " gl_FragColor = vec4(0.2, 0.2, 0.8, 1.0) * len;" + + " } else {" + /// outer ring + + " float a = smoothstep(0.0, 2.0 / u_scale, len);" + /// inner ring + + " float b = 0.5 * smoothstep(4.0 / u_scale, 5.0 / u_scale, len);" + /// center point + + " float c = 0.5 * (1.0 - smoothstep(14.0 / u_scale, 16.0 / u_scale, 1.0 - len));" + + " vec2 dir = normalize(v_tex);" + + " float d = 1.0 - dot(dir, u_dir); " + /// 0.5 width of viewshed + + " d = clamp(step(0.5, d), 0.4, 0.7);" + /// - subtract inner from outer to create the outline + /// - multiply by viewshed + /// - add center point + + " a = d * (a - (b + c)) + c;" + + " gl_FragColor = vec4(0.2, 0.2, 0.8, 1.0) * a;" + + "}}"; + + //private final static String fShaderStr = "" + // + "precision mediump float;" + // + "varying vec2 v_tex;" + // + "uniform float u_scale;" + // + "uniform float u_phase;" + // + "uniform vec2 u_dir;" + // + "void main() {" + // + " float len = 1.0 - length(v_tex);" + // /// outer ring + // + " float a = smoothstep(0.0, 2.0 / u_scale, len);" + // /// inner ring + // + " float b = 0.8 * smoothstep(3.0 / u_scale, 4.0 / u_scale, len);" + // /// center point + // + " float c = 0.5 * (1.0 - smoothstep(14.0 / u_scale, 16.0 / u_scale, 1.0 - len));" + // + " vec2 dir = normalize(v_tex);" + // + " float d = dot(dir, u_dir); " + // /// 0.5 width of viewshed + // + " d = clamp(smoothstep(0.7, 0.7 + 2.0/u_scale, d) * len, 0.0, 1.0);" + // /// - subtract inner from outer to create the outline + // /// - multiply by viewshed + // /// - add center point + // + " a = max(d, (a - (b + c)) + c);" + // + " gl_FragColor = vec4(0.2, 0.2, 0.8, 1.0) * a;" + // + "}"; + + public interface Callback { + float getRotation(); + } +}