Location texture renderer improvements #547
This commit is contained in:
@@ -8,6 +8,7 @@
|
|||||||
- vtm-mvt module with MVT tile decoder [#481](https://github.com/mapsforge/vtm/pull/481)
|
- vtm-mvt module with MVT tile decoder [#481](https://github.com/mapsforge/vtm/pull/481)
|
||||||
- Nextzen MVT / GeoJSON vector tiles [#498](https://github.com/mapsforge/vtm/issues/498)
|
- Nextzen MVT / GeoJSON vector tiles [#498](https://github.com/mapsforge/vtm/issues/498)
|
||||||
- OpenMapTiles MVT vector tiles [#482](https://github.com/mapsforge/vtm/issues/482)
|
- OpenMapTiles MVT vector tiles [#482](https://github.com/mapsforge/vtm/issues/482)
|
||||||
|
- Location texture renderer [#547](https://github.com/mapsforge/vtm/issues/547)
|
||||||
- Render themes: symbols on lines [#495](https://github.com/mapsforge/vtm/issues/495)
|
- Render themes: symbols on lines [#495](https://github.com/mapsforge/vtm/issues/495)
|
||||||
- Render themes: styles improvements [#479](https://github.com/mapsforge/vtm/pull/479)
|
- Render themes: styles improvements [#479](https://github.com/mapsforge/vtm/pull/479)
|
||||||
- Internal render themes improvements [#488](https://github.com/mapsforge/vtm/pull/488)
|
- Internal render themes improvements [#488](https://github.com/mapsforge/vtm/pull/488)
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import org.oscim.layers.LocationTextureLayer;
|
|||||||
import org.oscim.renderer.atlas.TextureAtlas;
|
import org.oscim.renderer.atlas.TextureAtlas;
|
||||||
import org.oscim.renderer.atlas.TextureRegion;
|
import org.oscim.renderer.atlas.TextureRegion;
|
||||||
import org.oscim.renderer.bucket.TextureItem;
|
import org.oscim.renderer.bucket.TextureItem;
|
||||||
|
import org.oscim.utils.IOUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@@ -42,28 +43,31 @@ public class LocationTextureActivity extends BitmapTileActivity implements Locat
|
|||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
//initial Android locationManager
|
|
||||||
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
|
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
|
||||||
|
|
||||||
//load a Bitmap/Svg from resources. (North/0° are on top
|
// load a Bitmap/SVG from resources
|
||||||
InputStream is = getResources().openRawResource(R.raw.arrow);
|
InputStream is = null;
|
||||||
Bitmap bmp = null;
|
Bitmap bmp = null;
|
||||||
try {
|
try {
|
||||||
bmp = CanvasAdapter.decodeSvgBitmap(is, 200, 200, 100);
|
is = getResources().openRawResource(R.raw.arrow);
|
||||||
|
bmp = CanvasAdapter.decodeSvgBitmap(is, 128, 128, 100);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(is);
|
||||||
}
|
}
|
||||||
//create TextureRegion from Bitmap
|
|
||||||
|
// create TextureRegion from Bitmap
|
||||||
TextureRegion textureRegion = new TextureRegion(new TextureItem(bmp), new TextureAtlas.Rect(0, 0, bmp.getWidth(), bmp.getHeight()));
|
TextureRegion textureRegion = new TextureRegion(new TextureItem(bmp), new TextureAtlas.Rect(0, 0, bmp.getWidth(), bmp.getHeight()));
|
||||||
|
|
||||||
//create LocationTexturLayer with created/loaded TextureRegion
|
// create LocationTextureLayer with created/loaded TextureRegion
|
||||||
locationLayer = new LocationTextureLayer(mMap, textureRegion);
|
locationLayer = new LocationTextureLayer(mMap, textureRegion);
|
||||||
|
|
||||||
// you can set the Color of Accuracy circle (Color.BLUE is default)
|
// set color of accuracy circle (Color.BLUE is default)
|
||||||
locationLayer.setAccuracyColor(Color.get(50, 50, 255));
|
locationLayer.locationRenderer.setAccuracyColor(Color.get(50, 50, 255));
|
||||||
|
|
||||||
// you can set the Color of Indicator circle (Color.RED is default)
|
// set color of indicator circle (Color.RED is default)
|
||||||
locationLayer.setIndicatorColor(Color.MAGENTA);
|
locationLayer.locationRenderer.setIndicatorColor(Color.MAGENTA);
|
||||||
|
|
||||||
locationLayer.setEnabled(false);
|
locationLayer.setEnabled(false);
|
||||||
mMap.layers().add(locationLayer);
|
mMap.layers().add(locationLayer);
|
||||||
@@ -72,6 +76,7 @@ public class LocationTextureActivity extends BitmapTileActivity implements Locat
|
|||||||
@Override
|
@Override
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
enableAvailableProviders();
|
enableAvailableProviders();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017-2018 Longri
|
* Copyright 2017-2018 Longri
|
||||||
|
* Copyright 2018 devemux86
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify it under the
|
* 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
|
* terms of the GNU Lesser General Public License as published by the Free Software
|
||||||
@@ -14,349 +15,28 @@
|
|||||||
*/
|
*/
|
||||||
package org.oscim.layers;
|
package org.oscim.layers;
|
||||||
|
|
||||||
import org.oscim.backend.CanvasAdapter;
|
|
||||||
import org.oscim.backend.GL;
|
|
||||||
import org.oscim.backend.canvas.Color;
|
|
||||||
import org.oscim.core.Box;
|
|
||||||
import org.oscim.core.MercatorProjection;
|
import org.oscim.core.MercatorProjection;
|
||||||
import org.oscim.core.Point;
|
|
||||||
import org.oscim.core.PointF;
|
|
||||||
import org.oscim.core.Tile;
|
|
||||||
import org.oscim.map.Map;
|
import org.oscim.map.Map;
|
||||||
import org.oscim.renderer.BucketRenderer;
|
import org.oscim.renderer.LocationTextureRenderer;
|
||||||
import org.oscim.renderer.GLShader;
|
|
||||||
import org.oscim.renderer.GLState;
|
|
||||||
import org.oscim.renderer.GLUtils;
|
|
||||||
import org.oscim.renderer.GLViewport;
|
|
||||||
import org.oscim.renderer.MapRenderer;
|
|
||||||
import org.oscim.renderer.atlas.TextureRegion;
|
import org.oscim.renderer.atlas.TextureRegion;
|
||||||
import org.oscim.renderer.bucket.SymbolBucket;
|
|
||||||
import org.oscim.renderer.bucket.SymbolItem;
|
|
||||||
import org.oscim.utils.FastMath;
|
|
||||||
import org.oscim.utils.geom.GeometryUtils;
|
|
||||||
import org.oscim.utils.math.Interpolation;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import static org.oscim.backend.GLAdapter.gl;
|
|
||||||
|
|
||||||
public class LocationTextureLayer extends Layer {
|
public class LocationTextureLayer extends Layer {
|
||||||
|
public final LocationTextureRenderer locationRenderer;
|
||||||
private static final PointF CENTER_OFFSET = new PointF(0.5f, 0.5f);
|
|
||||||
private static final long ANIM_RATE = 50;
|
|
||||||
private static final long INTERVAL = 2000;
|
|
||||||
private static final float CIRCLE_SIZE = 30;
|
|
||||||
private static final int SHOW_ACCURACY_ZOOM = 13;
|
|
||||||
private static final boolean IS_MAC = System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("mac");
|
|
||||||
|
|
||||||
private final static String V_SHADER = (""
|
|
||||||
+ "precision highp 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;"
|
|
||||||
+ "}").replace("precision highp float;", IS_MAC ? "" : "precision highp float;");
|
|
||||||
|
|
||||||
// only circle without direction
|
|
||||||
private static final String F_SHADER = (""
|
|
||||||
+ "precision highp float;"
|
|
||||||
+ "varying vec2 v_tex;"
|
|
||||||
+ "uniform float u_scale;"
|
|
||||||
+ "uniform float u_phase;"
|
|
||||||
+ "uniform vec4 u_fill;"
|
|
||||||
+ "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);"
|
|
||||||
/// - subtract inner from outer to create the outline
|
|
||||||
/// - multiply by viewshed
|
|
||||||
/// - add center point
|
|
||||||
+ " a = (a - (b + c)) + c;"
|
|
||||||
+ " gl_FragColor = u_fill * a;"
|
|
||||||
+ "}").replace("precision highp float;", IS_MAC ? "" : "precision highp float;");
|
|
||||||
|
|
||||||
|
|
||||||
private final LocationTextureRenderer locationRenderer;
|
|
||||||
|
|
||||||
public LocationTextureLayer(Map map, TextureRegion textureRegion) {
|
public LocationTextureLayer(Map map, TextureRegion textureRegion) {
|
||||||
super(map);
|
super(map);
|
||||||
|
|
||||||
mRenderer = locationRenderer = new LocationTextureRenderer(map);
|
mRenderer = locationRenderer = new LocationTextureRenderer(map);
|
||||||
setTextureRegion(textureRegion);
|
locationRenderer.setTextureRegion(textureRegion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setPosition(double latitude, double longitude, float bearing, float accuracy) {
|
||||||
public void setPosition(double latitude, double longitude, float heading, double accuracy) {
|
double x = MercatorProjection.longitudeToX(longitude);
|
||||||
locationRenderer.setPosition(latitude, longitude, heading, accuracy);
|
double y = MercatorProjection.latitudeToY(latitude);
|
||||||
|
bearing = -bearing;
|
||||||
|
while (bearing < 0)
|
||||||
|
bearing += 360;
|
||||||
|
double radius = accuracy / MercatorProjection.groundResolutionWithScale(latitude, 1);
|
||||||
|
locationRenderer.setLocation(x, y, bearing, radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAccuracyColor(int color) {
|
|
||||||
locationRenderer.setAccuracyColor(color);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIndicatorColor(int color) {
|
|
||||||
locationRenderer.setIndicatorColor(color);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTextureRegion(TextureRegion region) {
|
|
||||||
locationRenderer.setTextureRegion(region);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class LocationTextureRenderer extends BucketRenderer {
|
|
||||||
private final SymbolBucket symbolBucket;
|
|
||||||
private final float[] box = new float[8];
|
|
||||||
private final Point mapPoint = new Point();
|
|
||||||
private final Map map;
|
|
||||||
private boolean initialized;
|
|
||||||
private boolean locationIsVisible;
|
|
||||||
private int shaderProgramNumber;
|
|
||||||
private int hVertexPosition;
|
|
||||||
private int hMatrixPosition;
|
|
||||||
private int hScale;
|
|
||||||
private int hPhase;
|
|
||||||
private int uFill;
|
|
||||||
private double accuracyRadius;
|
|
||||||
|
|
||||||
private final Point indicatorPosition = new Point();
|
|
||||||
private final Point screenPoint = new Point();
|
|
||||||
private final Box boundingBox = new Box();
|
|
||||||
private boolean runAnim;
|
|
||||||
private long animStart;
|
|
||||||
private boolean update;
|
|
||||||
private float heading;
|
|
||||||
|
|
||||||
//properties
|
|
||||||
private TextureRegion textureRegion;
|
|
||||||
private int accuracyColor = Color.BLUE;
|
|
||||||
private int viewShedColor = Color.RED;
|
|
||||||
|
|
||||||
private LocationTextureRenderer(Map map) {
|
|
||||||
symbolBucket = new SymbolBucket();
|
|
||||||
this.map = map;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPosition(double latitude, double longitude, float heading, double accuracy) {
|
|
||||||
update = true;
|
|
||||||
this.heading = -heading;
|
|
||||||
while (this.heading < 0) this.heading += 360;
|
|
||||||
mapPoint.x = MercatorProjection.longitudeToX(longitude);
|
|
||||||
mapPoint.y = MercatorProjection.latitudeToY(latitude);
|
|
||||||
accuracyRadius = accuracy;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setAccuracyColor(int color) {
|
|
||||||
this.accuracyColor = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setIndicatorColor(int color) {
|
|
||||||
this.viewShedColor = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setTextureRegion(TextureRegion region) {
|
|
||||||
textureRegion = region;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void update(GLViewport v) {
|
|
||||||
if (!v.changed() && !update) return;
|
|
||||||
|
|
||||||
|
|
||||||
//accuracy
|
|
||||||
if (!initialized) {
|
|
||||||
init();
|
|
||||||
initialized = true;
|
|
||||||
}
|
|
||||||
setReady(true);
|
|
||||||
|
|
||||||
int width = map.getWidth();
|
|
||||||
int height = map.getHeight();
|
|
||||||
v.getBBox(boundingBox, 0);
|
|
||||||
|
|
||||||
double x = mapPoint.x;
|
|
||||||
double y = mapPoint.y;
|
|
||||||
|
|
||||||
if (!boundingBox.contains(mapPoint)) {
|
|
||||||
x = FastMath.clamp(x, boundingBox.xmin, boundingBox.xmax);
|
|
||||||
y = FastMath.clamp(y, boundingBox.ymin, boundingBox.ymax);
|
|
||||||
}
|
|
||||||
|
|
||||||
// get position of Location in pixel relative to
|
|
||||||
// screen center
|
|
||||||
v.toScreenPoint(x, y, screenPoint);
|
|
||||||
|
|
||||||
x = screenPoint.x + width / 2;
|
|
||||||
y = screenPoint.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++;
|
|
||||||
|
|
||||||
locationIsVisible = (visible == 2);
|
|
||||||
|
|
||||||
if (locationIsVisible) {
|
|
||||||
animate(false);
|
|
||||||
} else {
|
|
||||||
animate(true);
|
|
||||||
}
|
|
||||||
// set location indicator position
|
|
||||||
v.fromScreenPoint(x, y, indicatorPosition);
|
|
||||||
|
|
||||||
|
|
||||||
//Texture
|
|
||||||
mMapPosition.copy(v.pos);
|
|
||||||
|
|
||||||
double mx = v.pos.x;
|
|
||||||
double my = v.pos.y;
|
|
||||||
double scale = Tile.SIZE * v.pos.scale;
|
|
||||||
map.viewport().getMapExtents(box, 100);
|
|
||||||
long flip = (long) (Tile.SIZE * v.pos.scale) >> 1;
|
|
||||||
|
|
||||||
/* check visibility */
|
|
||||||
float symbolX = (float) ((mapPoint.x - mx) * scale);
|
|
||||||
float symbolY = (float) ((mapPoint.y - my) * scale);
|
|
||||||
|
|
||||||
if (symbolX > flip)
|
|
||||||
symbolX -= (flip << 1);
|
|
||||||
else if (symbolX < -flip)
|
|
||||||
symbolX += (flip << 1);
|
|
||||||
buckets.clear();
|
|
||||||
if (!GeometryUtils.pointInPoly(symbolX, symbolY, box, 8, 0)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mMapPosition.bearing = -mMapPosition.bearing;
|
|
||||||
if (textureRegion == null) return;
|
|
||||||
SymbolItem symbolItem = SymbolItem.pool.get();
|
|
||||||
symbolItem.set(symbolX, symbolY, textureRegion, this.heading, true);
|
|
||||||
symbolItem.offset = CENTER_OFFSET;
|
|
||||||
symbolBucket.pushSymbol(symbolItem);
|
|
||||||
|
|
||||||
buckets.set(symbolBucket);
|
|
||||||
buckets.prepare();
|
|
||||||
buckets.compile(true);
|
|
||||||
compile();
|
|
||||||
update = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void render(GLViewport v) {
|
|
||||||
renderAccuracyCircle(v);
|
|
||||||
super.render(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void init() {
|
|
||||||
int shader = GLShader.createProgram(V_SHADER, F_SHADER);
|
|
||||||
if (shader == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
shaderProgramNumber = 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");
|
|
||||||
uFill = gl.getUniformLocation(shader, "u_fill");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void renderAccuracyCircle(GLViewport v) {
|
|
||||||
GLState.useProgram(shaderProgramNumber);
|
|
||||||
GLState.blend(true);
|
|
||||||
GLState.test(false, false);
|
|
||||||
|
|
||||||
GLState.enableVertexArrays(hVertexPosition, -1);
|
|
||||||
MapRenderer.bindQuadVertexVBO(hVertexPosition/*, true*/);
|
|
||||||
|
|
||||||
float radius = 10;
|
|
||||||
boolean viewShed = false;
|
|
||||||
if (!locationIsVisible) {
|
|
||||||
radius = CIRCLE_SIZE * CanvasAdapter.getScale();
|
|
||||||
} else {
|
|
||||||
if (v.pos.zoomLevel >= SHOW_ACCURACY_ZOOM) {
|
|
||||||
radius = (float) (accuracyRadius / MercatorProjection.groundResolution(v.pos));
|
|
||||||
}
|
|
||||||
radius = Math.max(2, radius);
|
|
||||||
viewShed = true;
|
|
||||||
}
|
|
||||||
gl.uniform1f(hScale, radius);
|
|
||||||
|
|
||||||
double x = indicatorPosition.x - v.pos.x;
|
|
||||||
double y = indicatorPosition.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 && locationIsVisible) {
|
|
||||||
GLUtils.setColor(uFill, accuracyColor, 1);
|
|
||||||
} else {
|
|
||||||
GLUtils.setColor(uFill, viewShedColor, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
gl.drawArrays(GL.TRIANGLE_STRIP, 0, 4);
|
|
||||||
gl.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void animate(boolean enable) {
|
|
||||||
if (runAnim == enable)
|
|
||||||
return;
|
|
||||||
|
|
||||||
runAnim = enable;
|
|
||||||
if (!enable)
|
|
||||||
return;
|
|
||||||
|
|
||||||
final Runnable action = new Runnable() {
|
|
||||||
private long lastRun;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
if (!runAnim)
|
|
||||||
return;
|
|
||||||
|
|
||||||
long diff = System.currentTimeMillis() - lastRun;
|
|
||||||
map.postDelayed(this, Math.min(ANIM_RATE, diff));
|
|
||||||
map.render();
|
|
||||||
lastRun = System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
animStart = System.currentTimeMillis();
|
|
||||||
map.postDelayed(action, ANIM_RATE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private float animPhase() {
|
|
||||||
return (float) ((MapRenderer.frametime - animStart) % INTERVAL) / INTERVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
320
vtm/src/org/oscim/renderer/LocationTextureRenderer.java
Normal file
320
vtm/src/org/oscim/renderer/LocationTextureRenderer.java
Normal file
@@ -0,0 +1,320 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017-2018 Longri
|
||||||
|
* Copyright 2018 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.oscim.renderer;
|
||||||
|
|
||||||
|
import org.oscim.backend.CanvasAdapter;
|
||||||
|
import org.oscim.backend.GL;
|
||||||
|
import org.oscim.backend.canvas.Color;
|
||||||
|
import org.oscim.core.Box;
|
||||||
|
import org.oscim.core.Point;
|
||||||
|
import org.oscim.core.PointF;
|
||||||
|
import org.oscim.core.Tile;
|
||||||
|
import org.oscim.map.Map;
|
||||||
|
import org.oscim.renderer.atlas.TextureRegion;
|
||||||
|
import org.oscim.renderer.bucket.SymbolBucket;
|
||||||
|
import org.oscim.renderer.bucket.SymbolItem;
|
||||||
|
import org.oscim.utils.FastMath;
|
||||||
|
import org.oscim.utils.geom.GeometryUtils;
|
||||||
|
import org.oscim.utils.math.Interpolation;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import static org.oscim.backend.GLAdapter.gl;
|
||||||
|
|
||||||
|
public class LocationTextureRenderer extends BucketRenderer {
|
||||||
|
|
||||||
|
private static final PointF CENTER_OFFSET = new PointF(0.5f, 0.5f);
|
||||||
|
private static final long ANIM_RATE = 50;
|
||||||
|
private static final long INTERVAL = 2000;
|
||||||
|
private static final float CIRCLE_SIZE = 30;
|
||||||
|
private static final int SHOW_ACCURACY_ZOOM = 13;
|
||||||
|
private static final boolean IS_MAC = System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("mac");
|
||||||
|
|
||||||
|
private final static String V_SHADER = (""
|
||||||
|
+ "precision highp 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;"
|
||||||
|
+ "}").replace("precision highp float;", IS_MAC ? "" : "precision highp float;");
|
||||||
|
|
||||||
|
// only circle without direction
|
||||||
|
private static final String F_SHADER = (""
|
||||||
|
+ "precision highp float;"
|
||||||
|
+ "varying vec2 v_tex;"
|
||||||
|
+ "uniform float u_scale;"
|
||||||
|
+ "uniform float u_phase;"
|
||||||
|
+ "uniform vec4 u_fill;"
|
||||||
|
+ "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);"
|
||||||
|
/// - subtract inner from outer to create the outline
|
||||||
|
/// - multiply by viewshed
|
||||||
|
/// - add center point
|
||||||
|
+ " a = (a - (b + c)) + c;"
|
||||||
|
+ " gl_FragColor = u_fill * a;"
|
||||||
|
+ "}").replace("precision highp float;", IS_MAC ? "" : "precision highp float;");
|
||||||
|
|
||||||
|
private final SymbolBucket symbolBucket;
|
||||||
|
private final float[] box = new float[8];
|
||||||
|
private final Point mapPoint = new Point();
|
||||||
|
private final Map map;
|
||||||
|
private boolean initialized;
|
||||||
|
private boolean locationIsVisible;
|
||||||
|
private int shaderProgramNumber;
|
||||||
|
private int hVertexPosition;
|
||||||
|
private int hMatrixPosition;
|
||||||
|
private int hScale;
|
||||||
|
private int hPhase;
|
||||||
|
private int uFill;
|
||||||
|
private double radius;
|
||||||
|
|
||||||
|
private final Point indicatorPosition = new Point();
|
||||||
|
private final Point screenPoint = new Point();
|
||||||
|
private final Box boundingBox = new Box();
|
||||||
|
private boolean runAnim;
|
||||||
|
private long animStart;
|
||||||
|
private boolean update;
|
||||||
|
private float bearing;
|
||||||
|
|
||||||
|
// properties
|
||||||
|
private TextureRegion textureRegion;
|
||||||
|
private int accuracyColor = Color.BLUE;
|
||||||
|
private int viewShedColor = Color.RED;
|
||||||
|
|
||||||
|
public LocationTextureRenderer(Map map) {
|
||||||
|
this.map = map;
|
||||||
|
symbolBucket = new SymbolBucket();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAccuracyColor(int color) {
|
||||||
|
this.accuracyColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIndicatorColor(int color) {
|
||||||
|
this.viewShedColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocation(double x, double y, float bearing, double radius) {
|
||||||
|
update = true;
|
||||||
|
mapPoint.x = x;
|
||||||
|
mapPoint.y = y;
|
||||||
|
this.bearing = bearing;
|
||||||
|
this.radius = radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTextureRegion(TextureRegion region) {
|
||||||
|
textureRegion = region;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void animate(boolean enable) {
|
||||||
|
if (runAnim == enable)
|
||||||
|
return;
|
||||||
|
|
||||||
|
runAnim = enable;
|
||||||
|
if (!enable)
|
||||||
|
return;
|
||||||
|
|
||||||
|
final Runnable action = new Runnable() {
|
||||||
|
private long lastRun;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (!runAnim)
|
||||||
|
return;
|
||||||
|
|
||||||
|
long diff = System.currentTimeMillis() - lastRun;
|
||||||
|
map.postDelayed(this, Math.min(ANIM_RATE, diff));
|
||||||
|
map.render();
|
||||||
|
lastRun = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
animStart = System.currentTimeMillis();
|
||||||
|
map.postDelayed(action, ANIM_RATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private float animPhase() {
|
||||||
|
return (float) ((MapRenderer.frametime - animStart) % INTERVAL) / INTERVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void update(GLViewport v) {
|
||||||
|
if (!v.changed() && !update)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// accuracy
|
||||||
|
if (!initialized) {
|
||||||
|
init();
|
||||||
|
initialized = true;
|
||||||
|
}
|
||||||
|
setReady(true);
|
||||||
|
|
||||||
|
int width = map.getWidth();
|
||||||
|
int height = map.getHeight();
|
||||||
|
v.getBBox(boundingBox, 0);
|
||||||
|
|
||||||
|
double x = mapPoint.x;
|
||||||
|
double y = mapPoint.y;
|
||||||
|
|
||||||
|
if (!boundingBox.contains(mapPoint)) {
|
||||||
|
x = FastMath.clamp(x, boundingBox.xmin, boundingBox.xmax);
|
||||||
|
y = FastMath.clamp(y, boundingBox.ymin, boundingBox.ymax);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get position of Location in pixel relative to
|
||||||
|
// screen center
|
||||||
|
v.toScreenPoint(x, y, screenPoint);
|
||||||
|
|
||||||
|
x = screenPoint.x + width / 2;
|
||||||
|
y = screenPoint.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++;
|
||||||
|
|
||||||
|
locationIsVisible = (visible == 2);
|
||||||
|
|
||||||
|
if (locationIsVisible)
|
||||||
|
animate(false);
|
||||||
|
else
|
||||||
|
animate(true);
|
||||||
|
// set location indicator position
|
||||||
|
v.fromScreenPoint(x, y, indicatorPosition);
|
||||||
|
|
||||||
|
// Texture
|
||||||
|
mMapPosition.copy(v.pos);
|
||||||
|
|
||||||
|
double mx = v.pos.x;
|
||||||
|
double my = v.pos.y;
|
||||||
|
double scale = Tile.SIZE * v.pos.scale;
|
||||||
|
map.viewport().getMapExtents(box, 100);
|
||||||
|
long flip = (long) (Tile.SIZE * v.pos.scale) >> 1;
|
||||||
|
|
||||||
|
/* check visibility */
|
||||||
|
float symbolX = (float) ((mapPoint.x - mx) * scale);
|
||||||
|
float symbolY = (float) ((mapPoint.y - my) * scale);
|
||||||
|
|
||||||
|
if (symbolX > flip)
|
||||||
|
symbolX -= (flip << 1);
|
||||||
|
else if (symbolX < -flip)
|
||||||
|
symbolX += (flip << 1);
|
||||||
|
buckets.clear();
|
||||||
|
if (!GeometryUtils.pointInPoly(symbolX, symbolY, box, 8, 0))
|
||||||
|
return;
|
||||||
|
|
||||||
|
mMapPosition.bearing = -mMapPosition.bearing;
|
||||||
|
if (textureRegion == null)
|
||||||
|
return;
|
||||||
|
SymbolItem symbolItem = SymbolItem.pool.get();
|
||||||
|
symbolItem.set(symbolX, symbolY, textureRegion, this.bearing, true);
|
||||||
|
symbolItem.offset = CENTER_OFFSET;
|
||||||
|
symbolBucket.pushSymbol(symbolItem);
|
||||||
|
|
||||||
|
buckets.set(symbolBucket);
|
||||||
|
buckets.prepare();
|
||||||
|
buckets.compile(true);
|
||||||
|
compile();
|
||||||
|
update = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(GLViewport v) {
|
||||||
|
renderAccuracyCircle(v);
|
||||||
|
super.render(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
int shader = GLShader.createProgram(V_SHADER, F_SHADER);
|
||||||
|
if (shader == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
shaderProgramNumber = 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");
|
||||||
|
uFill = gl.getUniformLocation(shader, "u_fill");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderAccuracyCircle(GLViewport v) {
|
||||||
|
GLState.useProgram(shaderProgramNumber);
|
||||||
|
GLState.blend(true);
|
||||||
|
GLState.test(false, false);
|
||||||
|
|
||||||
|
GLState.enableVertexArrays(hVertexPosition, -1);
|
||||||
|
MapRenderer.bindQuadVertexVBO(hVertexPosition/*, true*/);
|
||||||
|
|
||||||
|
float radius = 10;
|
||||||
|
boolean viewShed = false;
|
||||||
|
if (!locationIsVisible)
|
||||||
|
radius = CIRCLE_SIZE * CanvasAdapter.getScale();
|
||||||
|
else {
|
||||||
|
if (v.pos.zoomLevel >= SHOW_ACCURACY_ZOOM)
|
||||||
|
radius = (float) (this.radius * v.pos.scale);
|
||||||
|
radius = Math.max(2, radius);
|
||||||
|
viewShed = true;
|
||||||
|
}
|
||||||
|
gl.uniform1f(hScale, radius);
|
||||||
|
|
||||||
|
double x = indicatorPosition.x - v.pos.x;
|
||||||
|
double y = indicatorPosition.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 && locationIsVisible)
|
||||||
|
GLUtils.setColor(uFill, accuracyColor, 1);
|
||||||
|
else
|
||||||
|
GLUtils.setColor(uFill, viewShedColor, 1);
|
||||||
|
|
||||||
|
gl.drawArrays(GL.TRIANGLE_STRIP, 0, 4);
|
||||||
|
gl.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user