From f9193eaa3fc64aa66a423e82b67354d70c685056 Mon Sep 17 00:00:00 2001 From: Emux Date: Fri, 1 Jun 2018 12:31:04 +0300 Subject: [PATCH] Location texture renderer improvements #547 --- docs/Changelog.md | 1 + .../android/test/LocationTextureActivity.java | 25 +- .../oscim/layers/LocationTextureLayer.java | 346 +----------------- .../renderer/LocationTextureRenderer.java | 320 ++++++++++++++++ 4 files changed, 349 insertions(+), 343 deletions(-) create mode 100644 vtm/src/org/oscim/renderer/LocationTextureRenderer.java diff --git a/docs/Changelog.md b/docs/Changelog.md index 8440375b..8646f851 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -8,6 +8,7 @@ - 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) - 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: styles improvements [#479](https://github.com/mapsforge/vtm/pull/479) - Internal render themes improvements [#488](https://github.com/mapsforge/vtm/pull/488) diff --git a/vtm-android-example/src/org/oscim/android/test/LocationTextureActivity.java b/vtm-android-example/src/org/oscim/android/test/LocationTextureActivity.java index 64784cc4..421f05e8 100644 --- a/vtm-android-example/src/org/oscim/android/test/LocationTextureActivity.java +++ b/vtm-android-example/src/org/oscim/android/test/LocationTextureActivity.java @@ -29,6 +29,7 @@ import org.oscim.layers.LocationTextureLayer; import org.oscim.renderer.atlas.TextureAtlas; import org.oscim.renderer.atlas.TextureRegion; import org.oscim.renderer.bucket.TextureItem; +import org.oscim.utils.IOUtils; import java.io.IOException; import java.io.InputStream; @@ -42,28 +43,31 @@ public class LocationTextureActivity extends BitmapTileActivity implements Locat public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - //initial Android locationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); - //load a Bitmap/Svg from resources. (North/0° are on top - InputStream is = getResources().openRawResource(R.raw.arrow); + // load a Bitmap/SVG from resources + InputStream is = null; Bitmap bmp = null; try { - bmp = CanvasAdapter.decodeSvgBitmap(is, 200, 200, 100); + is = getResources().openRawResource(R.raw.arrow); + bmp = CanvasAdapter.decodeSvgBitmap(is, 128, 128, 100); } catch (IOException e) { 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())); - //create LocationTexturLayer with created/loaded TextureRegion + // create LocationTextureLayer with created/loaded TextureRegion locationLayer = new LocationTextureLayer(mMap, textureRegion); - // you can set the Color of Accuracy circle (Color.BLUE is default) - locationLayer.setAccuracyColor(Color.get(50, 50, 255)); + // set color of accuracy circle (Color.BLUE is default) + locationLayer.locationRenderer.setAccuracyColor(Color.get(50, 50, 255)); - // you can set the Color of Indicator circle (Color.RED is default) - locationLayer.setIndicatorColor(Color.MAGENTA); + // set color of indicator circle (Color.RED is default) + locationLayer.locationRenderer.setIndicatorColor(Color.MAGENTA); locationLayer.setEnabled(false); mMap.layers().add(locationLayer); @@ -72,6 +76,7 @@ public class LocationTextureActivity extends BitmapTileActivity implements Locat @Override protected void onResume() { super.onResume(); + enableAvailableProviders(); } diff --git a/vtm/src/org/oscim/layers/LocationTextureLayer.java b/vtm/src/org/oscim/layers/LocationTextureLayer.java index d8922157..34731769 100644 --- a/vtm/src/org/oscim/layers/LocationTextureLayer.java +++ b/vtm/src/org/oscim/layers/LocationTextureLayer.java @@ -1,5 +1,6 @@ /* * 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 @@ -14,349 +15,28 @@ */ 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.Point; -import org.oscim.core.PointF; -import org.oscim.core.Tile; import org.oscim.map.Map; -import org.oscim.renderer.BucketRenderer; -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.LocationTextureRenderer; 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 { - - 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 final LocationTextureRenderer locationRenderer; public LocationTextureLayer(Map map, TextureRegion textureRegion) { super(map); + mRenderer = locationRenderer = new LocationTextureRenderer(map); - setTextureRegion(textureRegion); + locationRenderer.setTextureRegion(textureRegion); } - - public void setPosition(double latitude, double longitude, float heading, double accuracy) { - locationRenderer.setPosition(latitude, longitude, heading, accuracy); + public void setPosition(double latitude, double longitude, float bearing, float accuracy) { + double x = MercatorProjection.longitudeToX(longitude); + 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; - } - - } - } diff --git a/vtm/src/org/oscim/renderer/LocationTextureRenderer.java b/vtm/src/org/oscim/renderer/LocationTextureRenderer.java new file mode 100644 index 00000000..8e2eb63e --- /dev/null +++ b/vtm/src/org/oscim/renderer/LocationTextureRenderer.java @@ -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 . + */ +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(); + } +}