vtm/src/org/mapsforge/android/swrenderer/MapRenderer.java
Hannes Janetzek 1a27f56313 - making oscimap default backend
- added about-screen
- added TreeTile for Tile lookup, dropping that HashMap
- using simple line-shader instead of std-derivatives one,
  about twice as faster here
- use distance calculation from MapRenderer - removing TileScheduler
- no need for MapGeneratorJob, pass MapTile directly to MapWorkers
- added two-finger tap gestures for zoom-in/out
- added tub/tron rendertheme
- started caching for oscimap
- add x/y coordinates to MapPosition, using it in MapRenderer
- create tag hash when needed
- no need for long tile coordinates max zoomlevel 31 should suffice
2013-10-09 01:17:34 +02:00

584 lines
16 KiB
Java

/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapsforge.android.swrenderer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.Collections;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import org.mapsforge.android.MapView;
import org.mapsforge.android.mapgenerator.IMapGenerator;
import org.mapsforge.android.mapgenerator.MapTile;
import org.mapsforge.android.mapgenerator.TileDistanceSort;
import org.mapsforge.android.rendertheme.RenderTheme;
import org.mapsforge.android.utils.GlUtils;
import org.mapsforge.core.MapPosition;
import org.mapsforge.core.MercatorProjection;
import org.mapsforge.core.Tile;
import android.opengl.GLES20;
import android.opengl.Matrix;
/**
*
*/
public class MapRenderer implements org.mapsforge.android.IMapRenderer {
// private static String TAG = "MapRenderer";
private static final int FLOAT_SIZE_BYTES = 4;
private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
private int mProgram;
private int muMVPMatrixHandle;
private int maPositionHandle;
private int maTextureHandle;
private int muScaleHandle;
private FloatBuffer mVertices;
private float[] mMatrix = new float[16];
private int mWidth, mHeight;
private double mDrawX, mDrawY;
private long mTileX, mTileY;
private float mMapScale;
// private DebugSettings mDebugSettings;
// private JobParameters mJobParameter;
private MapPosition mMapPosition, mPrevMapPosition;
private ArrayList<MapTile> mJobList;
ArrayList<Integer> mTextures;
MapView mMapView;
GLMapTile[] currentTiles;
GLMapTile[] newTiles;
int currentTileCnt = 0;
// private TileCacheKey mTileCacheKey;
// private LinkedHashMap<TileCacheKey, GLMapTile> mTiles;
private ArrayList<GLMapTile> mTileList;
private boolean processedTile = true;
private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
private boolean mInitial;
private final TileDistanceSort tileDistanceSort = new TileDistanceSort();
/**
* @param mapView
* the MapView
*/
public MapRenderer(MapView mapView) {
mMapView = mapView;
// mDebugSettings = mapView.getDebugSettings();
mMapScale = 1;
float[] vertices = {
0, 0, 0, 0, 0.5f,
0, 1, 0, 0, 0,
1, 0, 0, 0.5f, 0.5f,
1, 1, 0, 0.5f, 0 };
mVertices = ByteBuffer.allocateDirect(4 * TRIANGLE_VERTICES_DATA_STRIDE_BYTES)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mVertices.put(vertices);
mTextures = new ArrayList<Integer>();
mJobList = new ArrayList<MapTile>();
// mTiles = new LinkedHashMap<TileCacheKey, GLMapTile>(100);
mTileList = new ArrayList<GLMapTile>();
// mTileCacheKey = new TileCacheKey();
mInitial = true;
}
private void limitCache(byte zoom, int remove) {
long x = mTileX;
long y = mTileY;
int diff;
for (GLMapTile t : mTileList) {
diff = (t.zoomLevel - zoom);
if (diff != 0)
{
float z = (diff > 0) ? (1 << diff) : 1.0f / (1 << -diff);
t.distance = (long) (Math.abs((t.tileX) * z - x) + Math.abs((t.tileY) * z
- y));
t.distance *= 2 * diff * diff;
} else {
t.distance = (Math.abs(t.tileX - x) + Math.abs(t.tileY - y));
}
}
Collections.sort(mTileList, tileDistanceSort);
for (int j = mTileList.size() - 1, cnt = 0; cnt < remove; j--, cnt++) {
GLMapTile t = mTileList.remove(j);
// mTileCacheKey.set(t.tileX, t.tileY, t.zoomLevel);
// mTiles.remove(mTileCacheKey);
for (int i = 0; i < 4; i++) {
if (t.child[i] != null)
t.child[i].parent = null;
}
if (t.parent != null) {
for (int i = 0; i < 4; i++) {
if (t.parent.child[i] == t)
t.parent.child[i] = null;
}
}
if (t.hasTexture()) {
synchronized (mTextures) {
mTextures.add(Integer.valueOf(t.getTexture()));
}
}
}
}
private boolean updateVisibleList(long x, long y, byte zoomLevel) {
float scale = mMapPosition.scale;
double add = 1.0f / scale;
int offsetX = (int) ((mWidth >> 1) * add);
int offsetY = (int) ((mHeight >> 1) * add);
long pixelRight = x + offsetX;
long pixelBottom = y + offsetY;
long pixelLeft = x - offsetX;
long pixelTop = y - offsetY;
int tileLeft = MercatorProjection.pixelXToTileX(pixelLeft, zoomLevel);
int tileTop = MercatorProjection.pixelYToTileY(pixelTop, zoomLevel);
int tileRight = MercatorProjection.pixelXToTileX(pixelRight, zoomLevel);
int tileBottom = MercatorProjection.pixelYToTileY(pixelBottom, zoomLevel);
mJobList.clear();
// IMapGenerator mapGenerator = mMapView.getMapGenerator();
int tiles = 0;
for (int tileY = tileTop - 1; tileY <= tileBottom + 1; tileY++) {
for (int tileX = tileLeft - 1; tileX <= tileRight + 1; tileX++) {
// GLMapTile tile = mTiles.get(mTileCacheKey.set(tileX, tileY, zoomLevel));
//
// if (tile == null) {
// tile = new GLMapTile(tileX, tileY, zoomLevel);
// TileCacheKey key = new TileCacheKey(mTileCacheKey);
// mTiles.put(key, tile);
//
// mTileCacheKey.set((tileX >> 1), (tileY >> 1), (byte) (zoomLevel - 1));
// tile.parent = mTiles.get(mTileCacheKey);
//
// long xx = tileX << 1;
// long yy = tileY << 1;
// byte z = (byte) (zoomLevel + 1);
//
// tile.child[0] = mTiles.get(mTileCacheKey.set(xx, yy, z));
// tile.child[1] = mTiles.get(mTileCacheKey.set(xx + 1, yy, z));
// tile.child[2] = mTiles.get(mTileCacheKey.set(xx, yy + 1, z));
// tile.child[3] = mTiles.get(mTileCacheKey.set(xx + 1, yy + 1, z));
//
// mTileList.add(tile);
// }
// newTiles[tiles++] = tile;
//
// if (!tile.isReady || (tile.getScale() != scale)) {
// // tile.isLoading = true;
// // approximation for TileScheduler
// if (tileY < tileTop || tileY > tileBottom || tileX < tileLeft
// || tileX > tileRight)
// tile.isVisible = false;
// else
// tile.isVisible = true;
//
// MapGeneratorJob job = new MapGeneratorJob(tile, mJobParameter,
// mDebugSettings);
// job.setScale(scale);
// mJobList.add(job);
// }
}
}
synchronized (this) {
limitCache(zoomLevel, (mTileList.size() - 200));
for (int i = 0; i < tiles; i++)
currentTiles[i] = newTiles[i];
currentTileCnt = tiles;
mDrawX = x;
mDrawY = y;
mMapScale = scale;
}
// if (mJobList.size() > 0) {
// mMapView.addJobs(mJobList);
// }
return true;
}
/**
*
*/
@Override
public synchronized void redrawTiles(boolean clear) {
boolean update = false;
mMapPosition = mMapView.getMapPosition().getMapPosition();
long x = (long) MercatorProjection.longitudeToPixelX(mMapPosition);
long y = (long) MercatorProjection.latitudeToPixelY(mMapPosition);
long tileX = MercatorProjection.pixelXToTileX(x, mMapPosition.zoomLevel);
long tileY = MercatorProjection.pixelYToTileY(y, mMapPosition.zoomLevel);
float scale = mMapPosition.scale;
if (mInitial) {
mInitial = false;
mPrevMapPosition = mMapPosition;
mTileX = tileX;
mTileY = tileY;
update = true;
} else if (mPrevMapPosition.zoomLevel != mMapPosition.zoomLevel) {
update = true;
} else if (mMapScale != scale) {
update = true;
} else if (tileX != mTileX || tileY != mTileY) {
update = true;
}
mTileX = tileX;
mTileY = tileY;
if (update) {
// do not change list while drawing
// synchronized (this) {
mPrevMapPosition = mMapPosition;
updateVisibleList(x, y, mMapPosition.zoomLevel);
}
else {
synchronized (this) {
mDrawX = x;
mDrawY = y;
}
}
mMapView.requestRender();
}
// private MapGeneratorJob mMapGeneratorJob = null;
@Override
public boolean passTile(MapTile mapTile) {
// mMapGeneratorJob = mapGeneratorJob;
processedTile = false;
mMapView.requestRender();
return true;
}
private boolean drawTile(GLMapTile tile, int level, float height) {
// do not recurse more than two parents
if (level > 2)
return true;
if (!tile.hasTexture()) {
// draw parent below current zoom level tiles
float h = height > 0 ? height * 2 : 0.1f;
if (level <= 2 && tile.parent != null)
return drawTile(tile.parent, level + 1, h);
return false;
}
float z = 1;
double drawX = mDrawX;
double drawY = mDrawY;
// translate all pixel coordinates * 'zoom factor difference'
// TODO clip tile when drawing parent
int diff = tile.zoomLevel - mMapPosition.zoomLevel;
if (diff != 0) {
if (diff > 0) {
z = (1 << diff);
} else {
z = 1.0f / (1 << -diff);
}
// drawX = MercatorProjection
// .longitudeToPixelX(mMapPosition.geoPoint.getLongitude(),
// tile.zoomLevel);
// drawY = MercatorProjection
// .latitudeToPixelY(mMapPosition.geoPoint.getLatitude(), tile.zoomLevel);
}
float mapScale = mMapScale / z;
int tileSize = Tile.TILE_SIZE;
float size = tileSize * mapScale;
float x = (float) ((tile.pixelX) - drawX) * mapScale;
float y = (float) ((tile.pixelY + tileSize) - drawY) * mapScale;
if (x + size < -mWidth / 2 || x > mWidth / 2) {
// Log.i(TAG, tile + " skip X " + x + " " + y);
tile.isVisible = false;
return true;
}
if (y < -mHeight / 2 || y - size > mHeight / 2) {
// Log.i(TAG, tile + " skip Y " + x + " " + y);
tile.isVisible = false;
return true;
}
// Log.i(TAG, tile + " draw " + x + " " + y);
tile.isVisible = true;
// set drawn tile scale (texture size)
GLES20.glUniform1f(muScaleHandle, tile.getScale());
Matrix.setIdentityM(mMatrix, 0);
// map tile GL coordinates to screen coordinates
Matrix.scaleM(mMatrix, 0, 2.0f * (tileSize * z) / mWidth, 2.0f * (tileSize * z)
/ mHeight, 1);
// scale tile
Matrix.scaleM(mMatrix, 0, mapScale / z, mapScale / z, 1);
// translate tile
Matrix.translateM(mMatrix, 0, (x / size), -(y / size), height);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, tile.getTexture());
// GlUtils.checkGlError("glBindTexture");
GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMatrix, 0);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
return true;
}
@Override
public void onDrawFrame(GL10 glUnused) {
// boolean loadedTexture = false;
GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
GLES20.glClearColor(0.95f, 0.95f, 0.94f, 1.0f);
// GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glUseProgram(mProgram);
GlUtils.checkGlError("glUseProgram");
mVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false,
TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mVertices);
GlUtils.checkGlError("glVertexAttribPointer maPosition");
GLES20.glEnableVertexAttribArray(maPositionHandle);
GlUtils.checkGlError("glEnableVertexAttribArray maPositionHandle");
mVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
GLES20.glVertexAttribPointer(maTextureHandle, 2, GLES20.GL_FLOAT, false,
TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mVertices);
GlUtils.checkGlError("glVertexAttribPointer maTextureHandle");
GLES20.glEnableVertexAttribArray(maTextureHandle);
GlUtils.checkGlError("glEnableVertexAttribArray maTextureHandle");
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLMapTile tile, child, child2;
GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
// lock position and currentTiles while drawing
// synchronized (this) {
// if (mMapGeneratorJob != null) {
//
// tile = (GLMapTile) mMapGeneratorJob.tile;
// // TODO tile bitmaps texture to smaller parts avoiding uploading full
// // bitmap when not necessary
// if (tile.getTexture() >= 0) {
// // reuse tile texture
// GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, tile.getTexture());
// GLUtils.texSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0,
// mMapGeneratorJob.getBitmap());
// } else if (mTextures.size() > 0) {
// // reuse texture from previous tiles
// Integer texture;
// texture = mTextures.remove(mTextures.size() - 1);
//
// GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture.intValue());
// GLUtils.texSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0,
// mMapGeneratorJob.getBitmap());
// tile.setTexture(texture.intValue());
// } else {
// // create texture
// tile.setTexture(GlUtils.loadTextures(mMapGeneratorJob.getBitmap()));
// }
//
// tile.setScale(mMapGeneratorJob.getScale());
// tile.isReady = true;
// tile.isLoading = false;
//
// mMapGeneratorJob = null;
// processedTile = true;
// // loadedTexture = true;
// }
// int tileSize = (int) (Tile.TILE_SIZE * mMapScale);
// int hWidth = mWidth >> 1;
// int hHeight = mHeight >> 1;
// for (int i = 0, n = currentTileCnt; i < n; i++) {
// tile = currentTiles[i];
//
// float x = (float) (tile.pixelX - mDrawX);
// float y = (float) (tile.pixelY - mDrawY);
//
// // clip rendering to tile boundaries
// GLES20.glScissor(
// hWidth + (int) (x * mMapScale) - 2,
// hHeight - (int) (y * mMapScale) - tileSize - 2,
// tileSize + 4, tileSize + 4);
//
// if (drawTile(tile, 0, 0.0f))
// continue;
//
// // or two zoom level above
// for (int k = 0; k < 4; k++) {
// if (((child = tile.child[k]) != null)) {
//
// if (drawTile(child, 2, 0.1f))
// continue;
//
// for (int j = 0; j < 4; j++)
// if ((child2 = child.child[j]) != null)
// drawTile(child2, 2, 0.1f);
// }
// }
// }
// }
}
@Override
public void onSurfaceChanged(GL10 glUnused, int width, int height) {
mWidth = width;
mHeight = height;
int tiles = (mWidth / Tile.TILE_SIZE + 4) * (mHeight / Tile.TILE_SIZE + 4);
currentTiles = new GLMapTile[tiles];
newTiles = new GLMapTile[tiles];
GLES20.glViewport(0, 0, width, height);
// mDebugSettings = mMapView.getDebugSettings();
// mJobParameter = mMapView.getJobParameters();
// mTiles.clear();
mTileList.clear();
mTextures.clear();
mInitial = true;
mMapView.redrawTiles();
}
@Override
public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
mProgram = GlUtils.createProgram(mVertexShader, mFragmentShader);
if (mProgram == 0) {
return;
}
maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
GlUtils.checkGlError("glGetAttribLocation aPosition");
if (maPositionHandle == -1) {
throw new RuntimeException("Could not get attrib location for aPosition");
}
maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord");
GlUtils.checkGlError("glGetAttribLocation aTextureCoord");
if (maTextureHandle == -1) {
throw new RuntimeException("Could not get attrib location for aTextureCoord");
}
muScaleHandle = GLES20.glGetUniformLocation(mProgram, "uScale");
GlUtils.checkGlError("glGetAttribLocation uScale");
if (muScaleHandle == -1) {
throw new RuntimeException("Could not get attrib location for uScale");
}
muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
GlUtils.checkGlError("glGetUniformLocation uMVPMatrix");
if (muMVPMatrixHandle == -1) {
throw new RuntimeException("Could not get attrib location for uMVPMatrix");
}
// GLES20.glEnable(GLES20.GL_DEPTH_TEST);
// GLES20.glDepthFunc(GLES20.GL_LEQUAL);
GLES20.glDisable(GLES20.GL_DEPTH_TEST);
GLES20.glCullFace(GLES20.GL_BACK);
GLES20.glFrontFace(GLES20.GL_CW);
GLES20.glEnable(GLES20.GL_CULL_FACE);
}
private final String mVertexShader = "precision highp float;\n" +
"uniform float uScale;\n" +
"uniform mat4 uMVPMatrix;\n" + "attribute vec4 aPosition;\n" +
"attribute vec2 aTextureCoord;\n" +
"varying vec2 vTextureCoord;\n" + "void main() {\n" +
" gl_Position = uMVPMatrix * aPosition;\n" +
" vTextureCoord = aTextureCoord * uScale;\n" +
"}\n";
private final String mFragmentShader = "precision highp float;\n" +
"uniform float uScale;\n" +
"varying vec2 vTextureCoord;\n" +
"uniform sampler2D sTexture;\n" +
"void main() {\n" +
" gl_FragColor = texture2D(sTexture, vTextureCoord); \n" +
"}\n";
@Override
public boolean processedTile() {
return processedTile;
}
@Override
public IMapGenerator createMapGenerator() {
return new MapGenerator(mMapView);
}
@Override
public void setRenderTheme(RenderTheme t) {
// TODO Auto-generated method stub
}
}