- started overlays

- started symbol layer
- move renderer and generator out of view package
  - hopefully the last big refactoring for a while...
- improve perspective, plane should be more far away to decrease foreshortening
This commit is contained in:
Hannes Janetzek
2012-10-09 13:23:15 +02:00
parent 2713f3bc6f
commit 33d8865d7b
128 changed files with 2360 additions and 1417 deletions

View File

@@ -0,0 +1,118 @@
/*
* Copyright 2012 Hannes Janetzek
*
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General 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 License for more details.
*
* You should have received a copy of the GNU Lesser General License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.oscim.renderer;
import android.opengl.GLES20;
class BufferObject {
private static BufferObject pool;
static synchronized BufferObject get() {
BufferObject bo;
if (pool == null) {
return null;
}
bo = pool;
pool = pool.next;
bo.next = null;
return bo;
}
// static synchronized BufferObject get(int size) {
// BufferObject bo, prev = null;
//
// if (pool == null) {
// return null;
// }
//
// int max = size * 4;
//
// for (bo = pool; bo != null; bo = bo.next) {
// if (bo.size > size && size < max)
// break;
//
// prev = bo;
// }
//
// if (prev != null && bo != null) {
// prev.next = bo.next;
// bo.next = null;
// return bo;
// }
//
// bo = pool;
// pool = pool.next;
// bo.next = null;
// return bo;
// }
static synchronized void release(BufferObject bo) {
bo.next = pool;
pool = bo;
}
// Note: only call from GL-Thread
static synchronized int limitUsage(int reduce) {
int vboIds[] = new int[10];
BufferObject[] tmp = new BufferObject[10];
int removed = 0;
int freed = 0;
for (BufferObject bo = pool; bo != null; bo = bo.next) {
if (bo.size > 0) {
freed += bo.size;
bo.size = 0;
vboIds[removed] = bo.id;
tmp[removed++] = bo;
if (removed == 10 || reduce < freed)
break;
}
}
if (removed > 0) {
GLES20.glDeleteBuffers(removed, vboIds, 0);
GLES20.glGenBuffers(removed, vboIds, 0);
for (int i = 0; i < removed; i++)
tmp[i].id = vboIds[i];
}
return freed;
}
static void init(int num) {
int[] mVboIds = new int[num];
GLES20.glGenBuffers(num, mVboIds, 0);
BufferObject bo;
for (int i = 1; i < num; i++) {
bo = new BufferObject(mVboIds[i]);
bo.next = pool;
pool = bo;
}
}
int id;
int size;
BufferObject next;
BufferObject(int id) {
this.id = id;
}
}

View File

@@ -0,0 +1,894 @@
/*
* Copyright 2012 Hannes Janetzek
*
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General 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 License for more details.
*
* You should have received a copy of the GNU Lesser General License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.oscim.renderer;
import static android.opengl.GLES20.GL_ARRAY_BUFFER;
import static android.opengl.GLES20.GL_BLEND;
import static android.opengl.GLES20.GL_DEPTH_TEST;
import static android.opengl.GLES20.GL_DYNAMIC_DRAW;
import static android.opengl.GLES20.GL_ONE;
import static android.opengl.GLES20.GL_ONE_MINUS_SRC_ALPHA;
import static android.opengl.GLES20.GL_POLYGON_OFFSET_FILL;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.concurrent.locks.ReentrantLock;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import org.oscim.core.MapPosition;
import org.oscim.core.Tile;
import org.oscim.renderer.MapRenderer.TilesData;
import org.oscim.renderer.layer.Layer;
import org.oscim.renderer.layer.Layers;
import org.oscim.theme.RenderTheme;
import org.oscim.view.MapView;
import org.oscim.view.MapViewPosition;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import android.os.SystemClock;
import android.util.FloatMath;
import android.util.Log;
public class GLRenderer implements GLSurfaceView.Renderer {
private static final String TAG = "SurfaceRenderer";
private static final int MB = 1024 * 1024;
private static final int SHORT_BYTES = 2;
private static final int CACHE_TILES_MAX = 250;
private static final int LIMIT_BUFFERS = 16 * MB;
public static final float COORD_MULTIPLIER = 8.0f;
static int CACHE_TILES = CACHE_TILES_MAX;
private final MapView mMapView;
private final MapViewPosition mMapViewPosition;
private static MapPosition mMapPosition;
// private static ArrayList<BufferObject> mVBOs;
static int mWidth, mHeight;
private static int rotateBuffers = 2;
private static ShortBuffer shortBuffer[];
private static short[] mFillCoords;
// bytes currently loaded in VBOs
private static int mBufferMemoryUsage;
private static float[] mMVPMatrix = new float[16];
private static float[] mProjMatrix = new float[16];
private static float[] mTmpMatrix = new float[16];
private static float[] mTileCoords = new float[8];
private static float[] mDebugCoords = new float[8];
// mNextTiles is set by TileLoader and swapped with
// mDrawTiles in onDrawFrame in GL thread.
private static TilesData mNextTiles;
/* package */static TilesData mDrawTiles;
// flag set by updateVisibleList when current visible tiles
// changed. used in onDrawFrame to flip mNextTiles/mDrawTiles
private static boolean mUpdateTiles;
private float[] mClearColor = null;
// number of tiles drawn in one frame
private static short mDrawCount = 0;
private static boolean mUpdateColor = false;
// drawlock to synchronize Main- and GL-Thread
static ReentrantLock tilelock = new ReentrantLock();
static ReentrantLock drawlock = new ReentrantLock();
// Add additional tiles that serve as placeholer when flipping
// over date-line.
// I dont really like this but cannot think of a better solution:
// the other option would be to run scanbox each time for upload,
// drawing, proxies and text layer. needing to add placeholder only
// happens rarely, unless you live on Fidschi
/* package */static int mHolderCount;
static boolean[] vertexArray = { false, false };
// TODO
final class GLState {
boolean blend = false;
boolean depth = false;
}
// scanline fill class used to check tile visibility
private static ScanBox mScanBox = new ScanBox() {
@Override
void setVisible(int y, int x1, int x2) {
int cnt = mDrawTiles.cnt;
MapTile[] tiles = mDrawTiles.tiles;
for (int i = 0; i < cnt; i++) {
MapTile t = tiles[i];
if (t.tileY == y && t.tileX >= x1 && t.tileX < x2)
t.isVisible = true;
}
int xmax = 1 << mZoom;
if (x1 >= 0 && x2 < xmax)
return;
// add placeholder tiles to show both sides
// of date line. a little too complicated...
for (int x = x1; x < x2; x++) {
MapTile holder = null;
MapTile tile = null;
boolean found = false;
if (x >= 0 && x < xmax)
continue;
int xx = x;
if (x < 0)
xx = xmax + x;
else
xx = x - xmax;
if (xx < 0 || xx >= xmax) {
// Log.d(TAG, "out of bounds " + y + " " + x + "/" + xx);
continue;
}
for (int i = cnt; i < cnt + mHolderCount; i++)
if (tiles[i].tileX == x && tiles[i].tileY == y) {
found = true;
break;
}
if (found) {
// Log.d(TAG, "already added " + y + " " + x + "/" + xx);
continue;
}
for (int i = 0; i < cnt; i++)
if (tiles[i].tileX == xx && tiles[i].tileY == y) {
tile = tiles[i];
break;
}
if (tile == null) {
// Log.d(TAG, "not found " + y + " " + x + "/" + xx);
continue;
}
holder = new MapTile(x, y, mZoom);
holder.isVisible = true;
holder.holder = tile;
tiles[cnt + mHolderCount++] = holder;
}
}
};
/**
* @param mapView
* the MapView
*/
public GLRenderer(MapView mapView) {
mMapView = mapView;
mMapViewPosition = mapView.getMapViewPosition();
mMapPosition = new MapPosition();
mMapPosition.init();
Matrix.setIdentityM(mMVPMatrix, 0);
// add half pixel to tile clip/fill coordinates to avoid rounding issues
short min = -4;
short max = (short) ((Tile.TILE_SIZE << 3) + 4);
mFillCoords = new short[8];
mFillCoords[0] = min;
mFillCoords[1] = max;
mFillCoords[2] = max;
mFillCoords[3] = max;
mFillCoords[4] = min;
mFillCoords[5] = min;
mFillCoords[6] = max;
mFillCoords[7] = min;
shortBuffer = new ShortBuffer[rotateBuffers];
for (int i = 0; i < rotateBuffers; i++) {
ByteBuffer bbuf = ByteBuffer.allocateDirect(MB >> 2)
.order(ByteOrder.nativeOrder());
shortBuffer[i] = bbuf.asShortBuffer();
shortBuffer[i].put(mFillCoords, 0, 8);
}
mUpdateTiles = false;
}
private static ArrayList<Overlay> mOverlays;
/**
* Called by TileLoader when list of active tiles changed. the list is
* copied to mNextTiles to be used in next call to onDrawFrame
*
* @param tiles
* active tiles
* @param overlays
* ...
*/
static void updateTiles(TilesData tiles, ArrayList<Overlay> overlays) {
MapTile[] newTiles = tiles.tiles;
// lock tiles (and their proxies) to not be removed from cache
for (int i = 0, n = tiles.cnt; i < n; i++)
newTiles[i].lock();
mOverlays = overlays;
// dont flip next/drawTiles while copying
GLRenderer.tilelock.lock();
MapTile[] nextTiles = mNextTiles.tiles;
// unlock previously active tiles
for (int i = 0, n = mNextTiles.cnt; i < n; i++)
nextTiles[i].unlock();
// copy newTiles to nextTiles
System.arraycopy(newTiles, 0, nextTiles, 0, tiles.cnt);
mNextTiles.cnt = tiles.cnt;
// flip next/drawTiles in next onDrawFrame
mUpdateTiles = true;
GLRenderer.tilelock.unlock();
}
void setRenderTheme(RenderTheme t) {
int bg = t.getMapBackground();
float[] c = new float[4];
c[3] = (bg >> 24 & 0xff) / 255.0f;
c[0] = (bg >> 16 & 0xff) / 255.0f;
c[1] = (bg >> 8 & 0xff) / 255.0f;
c[2] = (bg >> 0 & 0xff) / 255.0f;
mClearColor = c;
mUpdateColor = true;
}
private static int uploadCnt = 0;
private static boolean uploadLayers(Layers layers, BufferObject vbo, boolean addFill) {
int newSize = layers.getSize();
if (newSize == 0)
return false;
GLES20.glBindBuffer(GL_ARRAY_BUFFER, vbo.id);
// use multiple buffers to avoid overwriting buffer while current
// data is uploaded (or rather the blocking which is probably done to
// avoid overwriting)
if (uploadCnt >= rotateBuffers) {
uploadCnt = 0;
}
ShortBuffer sbuf = shortBuffer[uploadCnt];
// add fill coordinates
if (addFill)
newSize += 8;
// probably not a good idea to do this in gl thread...
if (sbuf.capacity() < newSize) {
sbuf = ByteBuffer
.allocateDirect(newSize * SHORT_BYTES)
.order(ByteOrder.nativeOrder())
.asShortBuffer();
shortBuffer[uploadCnt] = sbuf;
if (addFill)
sbuf.put(mFillCoords, 0, 8);
} else {
sbuf.clear();
if (addFill)
sbuf.put(mFillCoords, 0, 8);
// if (addFill)
// sbuf.position(8);
}
layers.compile(sbuf, addFill);
sbuf.flip();
if (newSize != sbuf.remaining()) {
Log.d(TAG, "wrong size: "
+ newSize + " "
+ sbuf.position() + " "
+ sbuf.limit() + " "
+ sbuf.remaining());
// tile.newData = false;
return false;
}
newSize *= SHORT_BYTES;
// reuse memory allocated for vbo when possible and allocated
// memory is less then four times the new data
if (vbo.size > newSize && vbo.size < newSize * 4
&& mBufferMemoryUsage < LIMIT_BUFFERS) {
GLES20.glBufferSubData(GL_ARRAY_BUFFER, 0, newSize, sbuf);
} else {
mBufferMemoryUsage -= vbo.size;
vbo.size = newSize;
GLES20.glBufferData(GL_ARRAY_BUFFER, vbo.size, sbuf, GL_DYNAMIC_DRAW);
mBufferMemoryUsage += vbo.size;
}
uploadCnt++;
return true;
}
private static boolean uploadTileData(MapTile tile) {
if (uploadLayers(tile.layers, tile.vbo, true))
tile.isReady = true;
tile.newData = false;
return tile.isReady;
}
private static boolean uploadOverlayData(Overlay overlay) {
if (uploadLayers(overlay.layers, overlay.vbo, true))
overlay.isReady = true;
overlay.newData = false;
return overlay.isReady;
}
private static void checkBufferUsage() {
// try to clear some unused vbo when exceding limit
if (mBufferMemoryUsage < LIMIT_BUFFERS) {
if (CACHE_TILES < CACHE_TILES_MAX)
CACHE_TILES += 50;
return;
}
Log.d(TAG, "buffer object usage: " + mBufferMemoryUsage / MB + "MB");
mBufferMemoryUsage -= BufferObject.limitUsage(2 * MB);
Log.d(TAG, "now: " + mBufferMemoryUsage / MB + "MB");
if (mBufferMemoryUsage > LIMIT_BUFFERS && CACHE_TILES > 100)
CACHE_TILES -= 50;
}
private static void setMatrix(float[] matrix, MapTile tile,
float div, boolean project) {
MapPosition mapPosition = mMapPosition;
float x = (float) (tile.pixelX - mapPosition.x * div);
float y = (float) (tile.pixelY - mapPosition.y * div);
float scale = mapPosition.scale / div;
Matrix.setIdentityM(matrix, 0);
// translate relative to map center
matrix[12] = x * scale;
matrix[13] = y * scale;
// scale to tile to world coordinates
scale /= COORD_MULTIPLIER;
matrix[0] = scale;
matrix[5] = scale;
Matrix.multiplyMM(matrix, 0, mapPosition.viewMatrix, 0, matrix, 0);
if (project)
Matrix.multiplyMM(matrix, 0, mProjMatrix, 0, matrix, 0);
}
private static float scaleDiv(MapTile t) {
float div = 1;
int diff = mMapPosition.zoomLevel - t.zoomLevel;
if (diff < 0)
div = (1 << -diff);
else if (diff > 0)
div = (1.0f / (1 << diff));
return div;
}
@Override
public void onDrawFrame(GL10 glUnused) {
long start = 0;
// prevent main thread recreating all tiles (updateMap)
// while rendering is going. not have seen this happen
// yet though
GLRenderer.drawlock.lock();
if (MapView.debugFrameTime)
start = SystemClock.uptimeMillis();
if (mUpdateColor) {
float cc[] = mClearColor;
GLES20.glClearColor(cc[0], cc[1], cc[2], cc[3]);
mUpdateColor = false;
}
// Note: it seems faster to also clear the stencil buffer even
// when not needed. probaly otherwise it is masked out from the
// depth buffer as they share the same memory region afaik
GLES20.glDepthMask(true);
GLES20.glStencilMask(0xFF);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT
| GLES20.GL_DEPTH_BUFFER_BIT
| GLES20.GL_STENCIL_BUFFER_BIT);
if (mUpdateTiles) {
// get current tiles to draw
GLRenderer.tilelock.lock();
mUpdateTiles = false;
TilesData tmp = mDrawTiles;
mDrawTiles = mNextTiles;
mNextTiles = tmp;
GLRenderer.tilelock.unlock();
// force update of mapPosition
mMapPosition.zoomLevel = -1;
}
if (mDrawTiles == null || mDrawTiles.cnt == 0) {
GLRenderer.drawlock.unlock();
return;
}
// get current MapPosition, set mTileCoords (mapping of screen to model
// coordinates)
MapPosition mapPosition = mMapPosition;
float[] coords = mTileCoords;
boolean changed = mMapViewPosition.getMapPosition(mapPosition, coords);
for (Overlay overlay : mOverlays) {
overlay.update(mMapView);
}
int tileCnt = mDrawTiles.cnt;
MapTile[] tiles = mDrawTiles.tiles;
if (changed) {
// get visible tiles
for (int i = 0; i < tileCnt; i++)
tiles[i].isVisible = false;
// relative zoom-level, 'tiles' could not have been updated after
// zoom-level changed.
float div = scaleDiv(tiles[0]);
float s = Tile.TILE_SIZE;
float scale = mapPosition.scale / div;
float px = (float) mapPosition.x * div;
float py = (float) mapPosition.y * div;
for (int i = 0; i < 8; i += 2) {
coords[i + 0] = (px + coords[i + 0] / scale) / s;
coords[i + 1] = (py + coords[i + 1] / scale) / s;
}
mHolderCount = 0;
mScanBox.scan(coords, tiles[0].zoomLevel);
}
tileCnt += mHolderCount;
// Log.d(TAG, "visible: " + tileCnt);
uploadCnt = 0;
int updateTextures = 0;
// check visible tiles, upload new vertex data
for (int i = 0; i < tileCnt; i++) {
MapTile tile = tiles[i];
// if (!isVisible(mapPosition, tile))
// continue;
if (!tile.isVisible)
continue;
if (MapView.staticLabeling) {
if (tile.texture == null && TextRenderer.drawToTexture(tile))
updateTextures++;
}
if (tile.newData) {
uploadTileData(tile);
continue;
}
if (tile.holder != null) {
// load tile that is referenced by this holder
if (tile.holder.newData)
uploadTileData(tile.holder);
tile.isReady = tile.holder.isReady;
} else if (!tile.isReady) {
// check near relatives than can serve as proxy
if ((tile.proxies & MapTile.PROXY_PARENT) != 0) {
MapTile rel = tile.rel.parent.tile;
if (rel.newData)
uploadTileData(rel);
continue;
}
for (int c = 0; c < 4; c++) {
if ((tile.proxies & 1 << c) == 0)
continue;
MapTile rel = tile.rel.child[c].tile;
if (rel != null && rel.newData)
uploadTileData(rel);
}
}
}
if (uploadCnt > 0)
checkBufferUsage();
if (MapView.staticLabeling) {
if (updateTextures > 0)
TextRenderer.compileTextures();
}
GLES20.glEnable(GL_DEPTH_TEST);
GLES20.glEnable(GL_POLYGON_OFFSET_FILL);
for (int i = 0; i < tileCnt; i++) {
MapTile t = tiles[i];
if (t.isVisible && t.isReady)
drawTile(t);
}
// proxies are clipped to the region where nothing was drawn to depth
// buffer.
// TODO draw all parent before grandparent
// TODO draw proxies for placeholder...
for (int i = 0; i < tileCnt; i++) {
MapTile t = tiles[i];
if (t.isVisible && !t.isReady && (t.holder == null))
drawProxyTile(t);
}
// GlUtils.checkGlError("end draw");
GLES20.glDisable(GL_POLYGON_OFFSET_FILL);
GLES20.glDisable(GL_DEPTH_TEST);
mDrawCount = 0;
mDrawSerial++;
if (MapView.staticLabeling) {
GLES20.glEnable(GL_BLEND);
int z = mapPosition.zoomLevel;
float s = mapPosition.scale;
int zoomLevelDiff = Math.max(z - TileGenerator.STROKE_MAX_ZOOM_LEVEL,
0);
float scale = (float) Math.pow(1.4, zoomLevelDiff);
if (scale < 1)
scale = 1;
if (z >= TileGenerator.STROKE_MAX_ZOOM_LEVEL)
TextRenderer.beginDraw(scale / FloatMath.sqrt(s), mProjMatrix);
else
TextRenderer.beginDraw(1f / s, mProjMatrix);
for (int i = 0; i < tileCnt; i++) {
MapTile t = tiles[i];
if (!t.isVisible)
continue;
if (t.holder == null) {
if (t.texture != null) {
setMatrix(mMVPMatrix, t, 1, false);
TextRenderer.drawTile(t, mMVPMatrix);
}
} else {
if (t.holder.texture != null) {
setMatrix(mMVPMatrix, t, 1, false);
TextRenderer.drawTile(t.holder, mMVPMatrix);
}
}
}
TextRenderer.endDraw();
}
// call overlay renderer
for (Overlay overlay : mOverlays) {
if (overlay.newData) {
if (overlay.vbo == null)
overlay.vbo = BufferObject.get();
if (overlay.vbo == null)
continue;
if (uploadOverlayData(overlay))
overlay.isReady = true;
}
if (!overlay.isReady)
continue;
// setMatrix(mMVPMatrix, overlay);
overlay.render(mMapPosition, mMVPMatrix, mProjMatrix);
}
if (MapView.debugFrameTime) {
GLES20.glFinish();
Log.d(TAG, "draw took " + (SystemClock.uptimeMillis() - start));
}
if (debugView) {
float mm = 0.5f;
float min = -mm;
float max = mm;
float ymax = mm * mHeight / mWidth;
mDebugCoords[0] = min;
mDebugCoords[1] = ymax;
mDebugCoords[2] = max;
mDebugCoords[3] = ymax;
mDebugCoords[4] = min;
mDebugCoords[5] = -ymax;
mDebugCoords[6] = max;
mDebugCoords[7] = -ymax;
PolygonRenderer.debugDraw(mProjMatrix, mDebugCoords, 0);
mapPosition.zoomLevel = -1;
mMapViewPosition.getMapPosition(mapPosition, mDebugCoords);
Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0,
mapPosition.viewMatrix, 0);
PolygonRenderer.debugDraw(mMVPMatrix, mDebugCoords, 1);
}
GLRenderer.drawlock.unlock();
}
// used to not draw a tile twice per frame.
private static int mDrawSerial = 0;
private static void drawTile(MapTile tile) {
// draw parents only once
if (tile.lastDraw == mDrawSerial)
return;
float div = scaleDiv(tile);
float[] mvp = mMVPMatrix;
MapPosition pos = mMapPosition;
tile.lastDraw = mDrawSerial;
setMatrix(mvp, tile, div, true);
if (tile.holder != null)
tile = tile.holder;
GLES20.glPolygonOffset(0, mDrawCount++);
GLES20.glBindBuffer(GL_ARRAY_BUFFER, tile.vbo.id);
boolean clipped = false;
int simpleShader = 0; // mRotate ? 0 : 1;
for (Layer l = tile.layers.layers; l != null;) {
switch (l.type) {
case Layer.POLYGON:
GLES20.glDisable(GL_BLEND);
l = PolygonRenderer.draw(pos, l, mvp, !clipped, true);
clipped = true;
break;
case Layer.LINE:
if (!clipped) {
PolygonRenderer.draw(pos, null, mvp, true, true);
clipped = true;
}
GLES20.glEnable(GL_BLEND);
l = LineRenderer.draw(pos, l, mvp, div, simpleShader,
tile.layers.lineOffset);
break;
}
}
if (tile.layers.symbolLayers != null) {
setMatrix(mvp, tile, div, false);
for (Layer l = tile.layers.symbolLayers; l != null;) {
l = TextureRenderer.draw(l, 1, mProjMatrix, mvp,
tile.layers.symbolOffset);
}
}
}
private static boolean drawProxyChild(MapTile tile) {
int drawn = 0;
for (int i = 0; i < 4; i++) {
if ((tile.proxies & 1 << i) == 0)
continue;
MapTile c = tile.rel.child[i].tile;
if (c.isReady) {
drawTile(c);
drawn++;
}
}
return drawn == 4;
}
private static void drawProxyTile(MapTile tile) {
int diff = mMapPosition.zoomLevel - tile.zoomLevel;
boolean drawn = false;
if (mMapPosition.scale > 1.5f || diff < 0) {
// prefer drawing children
if (!drawProxyChild(tile)) {
if ((tile.proxies & MapTile.PROXY_PARENT) != 0) {
MapTile t = tile.rel.parent.tile;
if (t.isReady) {
drawTile(t);
drawn = true;
}
}
if (!drawn && (tile.proxies & MapTile.PROXY_GRAMPA) != 0) {
MapTile t = tile.rel.parent.parent.tile;
if (t.isReady)
drawTile(t);
}
}
} else {
// prefer drawing parent
MapTile t = tile.rel.parent.tile;
if (t != null && t.isReady) {
drawTile(t);
} else if (!drawProxyChild(tile)) {
if ((tile.proxies & MapTile.PROXY_GRAMPA) != 0) {
t = tile.rel.parent.parent.tile;
if (t.isReady)
drawTile(t);
}
}
}
}
@Override
public void onSurfaceChanged(GL10 glUnused, int width, int height) {
Log.d(TAG, "SurfaceChanged:" + width + " " + height);
if (width <= 0 || height <= 0)
return;
boolean changed = true;
if (mWidth == width || mHeight == height)
changed = false;
mWidth = width;
mHeight = height;
float s = MapViewPosition.VIEW_SCALE;
float aspect = mHeight / (float) mWidth;
Matrix.frustumM(mProjMatrix, 0, -1 * s, 1 * s,
aspect * s, -aspect * s, MapViewPosition.VIEW_NEAR,
MapViewPosition.VIEW_FAR);
Matrix.setIdentityM(mTmpMatrix, 0);
Matrix.translateM(mTmpMatrix, 0, 0, 0, -MapViewPosition.VIEW_DISTANCE);
Matrix.multiplyMM(mProjMatrix, 0, mProjMatrix, 0, mTmpMatrix, 0);
if (debugView) {
// modify this to scale only the view, to see better which tiles are
// rendered
Matrix.setIdentityM(mMVPMatrix, 0);
Matrix.scaleM(mMVPMatrix, 0, 0.5f, 0.5f, 1);
Matrix.multiplyMM(mProjMatrix, 0, mMVPMatrix, 0, mProjMatrix, 0);
}
// set to zero: we modify the z value with polygon-offset for clipping
mProjMatrix[10] = 0;
mProjMatrix[14] = 0;
GLES20.glViewport(0, 0, width, height);
if (!changed && !mNewSurface) {
mMapView.redrawMap();
return;
}
mNewSurface = false;
mBufferMemoryUsage = 0;
int numTiles = (mWidth / (Tile.TILE_SIZE / 2) + 2)
* (mHeight / (Tile.TILE_SIZE / 2) + 2);
// Set up vertex buffer objects
int numVBO = (CACHE_TILES + (numTiles * 2));
BufferObject.init(numVBO);
// Set up textures
TextRenderer.setup(numTiles);
if (mClearColor != null)
mUpdateColor = true;
vertexArray[0] = false;
vertexArray[1] = false;
// FIXME this should be synchronized
mMapView.redrawMap();
}
// FIXME this is a bit too spaghetti
void clearTiles(int numTiles) {
mDrawTiles = new TilesData(numTiles);
mNextTiles = new TilesData(numTiles);
mMapPosition.zoomLevel = -1;
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// String ext = GLES20.glGetString(GLES20.GL_EXTENSIONS);
// Log.d(TAG, "Extensions: " + ext);
LineRenderer.init();
PolygonRenderer.init();
TextRenderer.init();
TextureRenderer.init();
TextureObject.init(10);
// glEnable(GL_SCISSOR_TEST);
// glScissor(0, 0, mWidth, mHeight);
GLES20.glClearStencil(0);
GLES20.glDisable(GLES20.GL_CULL_FACE);
GLES20.glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
mNewSurface = true;
}
private boolean mNewSurface;
private static final boolean debugView = false;
}

View File

@@ -0,0 +1,223 @@
/*
* Copyright 2012 Hannes Janetzek
*
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General 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 License for more details.
*
* You should have received a copy of the GNU Lesser General License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.oscim.renderer;
import org.oscim.core.MapPosition;
import org.oscim.renderer.layer.Layer;
import org.oscim.renderer.layer.LineLayer;
import org.oscim.theme.renderinstruction.Line;
import org.oscim.utils.GlUtils;
import android.graphics.Paint.Cap;
import android.opengl.GLES20;
import android.util.FloatMath;
import android.util.Log;
class LineRenderer {
private final static String TAG = "LineRenderer";
// private static int NUM_VERTEX_SHORTS = 4;
private static final int LINE_VERTICES_DATA_POS_OFFSET = 0;
private static final int LINE_VERTICES_DATA_TEX_OFFSET = 4;
// shader handles
private static int[] lineProgram = new int[2];
private static int[] hLineVertexPosition = new int[2];
private static int[] hLineTexturePosition = new int[2];
private static int[] hLineColor = new int[2];
private static int[] hLineMatrix = new int[2];
private static int[] hLineScale = new int[2];
private static int[] hLineWidth = new int[2];
private static int[] hLineMode = new int[2];
static boolean init() {
lineProgram[0] = GlUtils.createProgram(Shaders.lineVertexShader,
Shaders.lineFragmentShader);
if (lineProgram[0] == 0) {
Log.e(TAG, "Could not create line program.");
return false;
}
hLineMatrix[0] = GLES20.glGetUniformLocation(lineProgram[0], "u_mvp");
hLineScale[0] = GLES20.glGetUniformLocation(lineProgram[0], "u_wscale");
hLineWidth[0] = GLES20.glGetUniformLocation(lineProgram[0], "u_width");
hLineColor[0] = GLES20.glGetUniformLocation(lineProgram[0], "u_color");
hLineMode[0] = GLES20.glGetUniformLocation(lineProgram[0], "u_mode");
hLineVertexPosition[0] = GLES20.glGetAttribLocation(lineProgram[0], "a_position");
hLineTexturePosition[0] = GLES20.glGetAttribLocation(lineProgram[0], "a_st");
lineProgram[1] = GlUtils.createProgram(Shaders.lineVertexShader,
Shaders.lineSimpleFragmentShader);
if (lineProgram[1] == 0) {
Log.e(TAG, "Could not create simple line program.");
return false;
}
hLineMatrix[1] = GLES20.glGetUniformLocation(lineProgram[1], "u_mvp");
hLineScale[1] = GLES20.glGetUniformLocation(lineProgram[1], "u_wscale");
hLineWidth[1] = GLES20.glGetUniformLocation(lineProgram[1], "u_width");
hLineColor[1] = GLES20.glGetUniformLocation(lineProgram[1], "u_color");
hLineMode[1] = GLES20.glGetUniformLocation(lineProgram[1], "u_mode");
hLineVertexPosition[1] = GLES20.glGetAttribLocation(lineProgram[1], "a_position");
hLineTexturePosition[1] = GLES20.glGetAttribLocation(lineProgram[1], "a_st");
return true;
}
static Layer draw(MapPosition pos, Layer layer, float[] matrix, float div,
int mode, int bufferOffset) {
int zoom = pos.zoomLevel;
float scale = pos.scale;
if (layer == null)
return null;
GLES20.glUseProgram(lineProgram[mode]);
int va = hLineVertexPosition[mode];
if (!GLRenderer.vertexArray[va]) {
GLES20.glEnableVertexAttribArray(va);
GLRenderer.vertexArray[va] = true;
}
va = hLineTexturePosition[mode];
if (!GLRenderer.vertexArray[va]) {
GLES20.glEnableVertexAttribArray(va);
GLRenderer.vertexArray[va] = true;
}
// GLES20.glEnableVertexAttribArray(hLineVertexPosition[mode]);
// GLES20.glEnableVertexAttribArray(hLineTexturePosition[mode]);
GLES20.glVertexAttribPointer(hLineVertexPosition[mode], 2, GLES20.GL_SHORT,
false, 8, bufferOffset + LINE_VERTICES_DATA_POS_OFFSET);
GLES20.glVertexAttribPointer(hLineTexturePosition[mode], 2, GLES20.GL_SHORT,
false, 8, bufferOffset + LINE_VERTICES_DATA_TEX_OFFSET);
GLES20.glUniformMatrix4fv(hLineMatrix[mode], 1, false, matrix, 0);
// scale factor to map one pixel on tile to one pixel on screen:
// only works with orthographic projection
float s = scale / div;
float pixel = 0;
if (mode == 1)
pixel = 1.5f / s;
GLES20.glUniform1f(hLineScale[mode], pixel);
int lineMode = 0;
GLES20.glUniform1i(hLineMode[mode], lineMode);
// line scale factor (for non fixed lines)
float lineScale = FloatMath.sqrt(s);
float blurScale = pixel;
boolean blur = false;
// dont increase scale when max is reached
boolean strokeMaxZoom = zoom > TileGenerator.STROKE_MAX_ZOOM_LEVEL;
float width = 1;
Layer l = layer;
for (; l != null && l.type == Layer.LINE; l = l.next) {
LineLayer ll = (LineLayer) l;
Line line = ll.line;
if (line.fade != -1 && line.fade > zoom)
continue;
float alpha = 1.0f;
if (line.fade >= zoom)
alpha = (scale > 1.2f ? scale : 1.2f) - alpha;
GlUtils.setColor(hLineColor[mode], line.color, alpha);
if (blur && line.blur == 0) {
GLES20.glUniform1f(hLineScale[mode], pixel);
blur = false;
}
if (line.outline) {
for (LineLayer o = ll.outlines; o != null; o = o.outlines) {
if (o.line.fixed || strokeMaxZoom) {
width = (ll.width + o.width) / s;
} else {
width = ll.width / s + o.width / lineScale;
}
GLES20.glUniform1f(hLineWidth[mode], width);
if (line.blur != 0) {
blurScale = (ll.width + o.width) / s - (line.blur / s);
GLES20.glUniform1f(hLineScale[mode], blurScale);
blur = true;
}
if (line.cap == Cap.ROUND) {
if (lineMode != 1) {
lineMode = 1;
GLES20.glUniform1i(hLineMode[mode], lineMode);
}
} else if (lineMode != 0) {
lineMode = 0;
GLES20.glUniform1i(hLineMode[mode], lineMode);
}
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, o.offset, o.verticesCnt);
}
} else {
if (line.fixed || strokeMaxZoom) {
// invert scaling of extrusion vectors so that line width
// stays the same.
width = ll.width / s;
} else {
width = ll.width / lineScale;
}
GLES20.glUniform1f(hLineWidth[mode], width);
if (line.blur != 0) {
blurScale = (ll.width / lineScale) * line.blur;
GLES20.glUniform1f(hLineScale[mode], blurScale);
blur = true;
}
if (line.cap == Cap.ROUND) {
if (lineMode != 1) {
lineMode = 1;
GLES20.glUniform1i(hLineMode[mode], lineMode);
}
} else if (lineMode != 0) {
lineMode = 0;
GLES20.glUniform1i(hLineMode[mode], lineMode);
}
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, l.offset, l.verticesCnt);
}
}
// GLES20.glDisableVertexAttribArray(hLineVertexPosition[mode]);
// GLES20.glDisableVertexAttribArray(hLineTexturePosition[mode]);
return l;
}
}

View File

@@ -0,0 +1,610 @@
/*
* Copyright 2012 Hannes Janetzek
*
* 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 java.util.ArrayList;
import java.util.Collections;
import org.oscim.core.MapPosition;
import org.oscim.core.Tile;
import org.oscim.generator.JobTile;
import org.oscim.renderer.layer.VertexPool;
import org.oscim.theme.RenderTheme;
import org.oscim.utils.GlConfigChooser;
import org.oscim.view.MapView;
import org.oscim.view.MapViewPosition;
import android.content.Context;
import android.opengl.GLSurfaceView;
import android.util.FloatMath;
import android.util.Log;
// FIXME to many 'Renderer', this one needs a better name.. TileLoader?
public class MapRenderer extends GLSurfaceView {
private final static String TAG = "MapRenderer";
private GLRenderer mRenderer;
private static final int MAX_TILES_IN_QUEUE = 40;
private static final int CACHE_THRESHOLD = 10;
private static MapView mMapView;
private static final MapPosition mMapPosition = new MapPosition();
private final MapViewPosition mMapViewPosition;
// new jobs for the MapWorkers
private static ArrayList<JobTile> mJobList;
// all tiles currently referenced
private static ArrayList<MapTile> mTiles;
// tiles that have new data to upload, see passTile()
private static ArrayList<MapTile> mTilesLoaded;
private static ArrayList<Overlay> mOverlays;
// TODO current boundary tiles, values used to check if position has
// changed for updating current tile list
private static boolean mInitial;
// private static MapPosition mCurPosition, mDrawPosition;
private static int mWidth = 0, mHeight = 0;
// maps zoom-level to available zoom-levels in MapDatabase
// e.g. 16->16, 15->16, 14->13, 13->13, 12->13,....
// private static int[] mZoomLevels;
private static float[] mTileCoords = new float[8];
// private static int[] mBoundaryTiles = new int[8];
// used for passing tiles to be rendered from TileLoader(Main-Thread) to
// GLThread
static final class TilesData {
int cnt = 0;
final MapTile[] tiles;
TilesData(int numTiles) {
tiles = new MapTile[numTiles];
}
}
/* package */static TilesData mCurrentTiles;
private static ScanBox mScanBox = new ScanBox() {
@Override
void setVisible(int y, int x1, int x2) {
MapTile[] tiles = mCurrentTiles.tiles;
int cnt = mCurrentTiles.cnt;
int max = mCurrentTiles.tiles.length;
int xmax = 1 << mZoom;
for (int x = x1; x < x2; x++) {
MapTile tile = null;
if (cnt == max) {
Log.d(TAG, "reached maximum for currentTiles " + max);
break;
}
// NOTE to myself: do not modify x, argh !!!
int xx = x;
if (x < 0 || x >= xmax) {
// flip-around date line
if (x < 0)
xx = xmax + x;
else
xx = x - xmax;
if (xx < 0 || xx >= xmax)
continue;
}
// check if tile is already added
for (int i = 0; i < cnt; i++)
if (tiles[i].tileX == xx && tiles[i].tileY == y) {
tile = tiles[i];
break;
}
if (tile == null) {
tile = addTile(xx, y, mZoom, 0);
tiles[cnt++] = tile;
}
}
mCurrentTiles.cnt = cnt;
}
};
// why not try a pattern every now and then?
// but should do the same for GLRenderer
private static MapRenderer SINGLETON;
public static MapRenderer create(Context context, MapView mapView) {
if (SINGLETON != null)
throw new IllegalStateException();
return SINGLETON = new MapRenderer(context, mapView);
}
public void destroy() {
SINGLETON = null;
// mRenderer = null;
// mTiles = null;
// mTilesLoaded = null;
// mJobList = null;
// mOverlays = null;
// ... free pools
}
private MapRenderer(Context context, MapView mapView) {
super(context);
mMapView = mapView;
mMapViewPosition = mapView.getMapViewPosition();
Log.d(TAG, "init GLSurfaceLayer");
setEGLConfigChooser(new GlConfigChooser());
setEGLContextClientVersion(2);
// setDebugFlags(DEBUG_CHECK_GL_ERROR | DEBUG_LOG_GL_CALLS);
mRenderer = new GLRenderer(mMapView);
setRenderer(mRenderer);
// if (!debugFrameTime)
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
mJobList = new ArrayList<JobTile>();
mTiles = new ArrayList<MapTile>();
mTilesLoaded = new ArrayList<MapTile>(30);
mOverlays = new ArrayList<Overlay>(5);
VertexPool.init();
QuadTree.init();
mInitial = true;
}
/**
* Update list of visible tiles and passes them to MapRenderer, when not
* available tiles are created and added to JobQueue (mapView.addJobs) for
* loading by TileGenerator class
*
* @param clear
* whether to clear and reload all tiles
*/
public void updateMap(final boolean clear) {
boolean changedPos = false;
if (mMapView == null)
return;
if (clear || mInitial) {
// make sure onDrawFrame is not running
GLRenderer.drawlock.lock();
// remove all tiles references
Log.d(TAG, "CLEAR " + mInitial);
if (clear) {
for (MapTile t : mTiles)
clearTile(t);
} else {
VertexPool.init();
}
mTiles.clear();
mTilesLoaded.clear();
QuadTree.init();
// TODO clear overlay items data
mOverlays.clear();
mOverlays.add(new Overlay());
// set up TileData arrays that are passed to gl-thread
int num = mWidth;
if (mWidth < mHeight)
num = mHeight;
int size = Tile.TILE_SIZE >> 1;
int numTiles = (num * num) / (size * size) * 4;
mRenderer.clearTiles(numTiles);
mCurrentTiles = new TilesData(numTiles);
// MapInfo mapInfo = mMapView.getMapDatabase().getMapInfo();
// if (mapInfo != null)
// mZoomLevels = mapInfo.zoomLevel;
GLRenderer.drawlock.unlock();
// .. make sure mMapPosition will be updated
mMapPosition.zoomLevel = -1;
mInitial = false;
}
MapPosition mapPosition = mMapPosition;
float[] coords = mTileCoords;
changedPos = mMapViewPosition.getMapPosition(mapPosition, coords);
if (!changedPos)
return;
float s = Tile.TILE_SIZE;
// load some additional tiles more than currently visible
float scale = mapPosition.scale * 0.75f;
float px = (float) mapPosition.x;
float py = (float) mapPosition.y;
int zdir = 0;
for (int i = 0; i < 8; i += 2) {
coords[i + 0] = (px + coords[i + 0] / scale) / s;
coords[i + 1] = (py + coords[i + 1] / scale) / s;
}
// this does not work reloably with tilt and rotation
// changedPos = false;
// for (int i = 0; i < 8; i++)
// if (mBoundaryTiles[i] != (int) coords[i]) {
// changedPos = true;
// break;
// }
// for (int i = 0; i < 8; i++)
// mBoundaryTiles[i] = (int) coords[i];
// TODO all following should probably be done in an idler instead
// to drain queued events. need to check how android handles things.
boolean changed = updateVisibleList(mapPosition, zdir);
if (!MapView.debugFrameTime)
requestRender();
if (changed) {
int remove = mTiles.size() - GLRenderer.CACHE_TILES;
if (remove > CACHE_THRESHOLD)
limitCache(mapPosition, remove);
limitLoadQueue();
}
}
/**
* set mCurrentTiles for the visible tiles and pass it to GLRenderer, add
* jobs for not yet loaded tiles
*
* @param mapPosition
* the current MapPosition
* @param zdir
* zoom direction
* @return true if new tiles were loaded
*/
private static boolean updateVisibleList(MapPosition mapPosition, int zdir) {
mJobList.clear();
// set non processed tiles to isLoading == false
mMapView.addJobs(null);
mCurrentTiles.cnt = 0;
mScanBox.scan(mTileCoords, mapPosition.zoomLevel);
// Log.d(TAG, "visible: " + mCurrentTiles.cnt + "/" +
// mCurrentTiles.tiles.length);
GLRenderer.updateTiles(mCurrentTiles, mOverlays);
// note: this sets isLoading == true for all job tiles
if (mJobList.size() > 0) {
updateTileDistances(mJobList, mapPosition);
Collections.sort(mJobList);
mMapView.addJobs(mJobList);
return true;
}
return false;
}
/* package */
static MapTile addTile(int x, int y, byte zoomLevel, int zdir) {
MapTile tile;
tile = QuadTree.getTile(x, y, zoomLevel);
if (tile == null) {
tile = new MapTile(x, y, zoomLevel);
QuadTree.add(tile);
mTiles.add(tile);
}
// if (!fetchProxy && !tile.isActive()) {
if (!tile.isActive()) {
mJobList.add(tile);
}
// mCurrentTiles.tiles[tiles++] = tile;
// if (fetchChildren) {
// byte z = (byte) (zoomLevel + 1);
// for (int i = 0; i < 4; i++) {
// int cx = (xx << 1) + (i % 2);
// int cy = (yy << 1) + (i >> 1);
//
// MapTile c = QuadTree.getTile(cx, cy, z);
//
// if (c == null) {
// c = new MapTile(cx, cy, z);
//
// QuadTree.add(c);
// mTiles.add(c);
// }
//
// if (!c.isActive()) {
// mJobList.add(c);
// }
// }
// }
// if (fetchParent || (!fetchProxy && zdir > 0 && zoomLevel > 0)) {
if (zdir > 0 && zoomLevel > 0) {
// prefetch parent
MapTile p = tile.rel.parent.tile;
if (p == null) {
p = new MapTile(x >> 1, y >> 1, (byte) (zoomLevel - 1));
QuadTree.add(p);
mTiles.add(p);
mJobList.add(p);
} else if (!p.isActive()) {
if (!mJobList.contains(p))
mJobList.add(p);
}
}
return tile;
}
private static void clearTile(MapTile t) {
t.newData = false;
t.isLoading = false;
t.isReady = false;
if (t.layers != null) {
t.layers.clear();
t.layers = null;
}
t.labels = null;
if (t.vbo != null) {
BufferObject.release(t.vbo);
t.vbo = null;
}
if (t.texture != null)
t.texture.tile = null;
QuadTree.remove(t);
}
private static void updateTileDistances(ArrayList<?> tiles, MapPosition mapPosition) {
int h = (Tile.TILE_SIZE >> 1);
byte zoom = mapPosition.zoomLevel;
long x = (long) mapPosition.x;
long y = (long) mapPosition.y;
// long center = Tile.TILE_SIZE << (zoom - 1);
int diff;
long dx, dy;
// TODO this could need some fixing, and optimization
// to consider move/zoom direction
for (int i = 0, n = tiles.size(); i < n; i++) {
JobTile t = (JobTile) tiles.get(i);
diff = (t.zoomLevel - zoom);
if (diff == 0) {
dx = (t.pixelX + h) - x;
dy = (t.pixelY + h) - y;
// t.distance = ((dx > 0 ? dx : -dx) + (dy > 0 ? dy : -dy)) *
// 0.25f;
// dx %= center;
// dy %= center;
t.distance = FloatMath.sqrt((dx * dx + dy * dy)) * 0.25f;
} else if (diff > 0) {
// tile zoom level is child of current
if (diff < 3) {
dx = ((t.pixelX + h) >> diff) - x;
dy = ((t.pixelY + h) >> diff) - y;
}
else {
dx = ((t.pixelX + h) >> (diff >> 1)) - x;
dy = ((t.pixelY + h) >> (diff >> 1)) - y;
}
// dx %= center;
// dy %= center;
// t.distance = ((dx > 0 ? dx : -dx) + (dy > 0 ? dy : -dy));
t.distance = FloatMath.sqrt((dx * dx + dy * dy));
} else {
// tile zoom level is parent of current
dx = ((t.pixelX + h) << -diff) - x;
dy = ((t.pixelY + h) << -diff) - y;
// dx %= center;
// dy %= center;
// t.distance = ((dx > 0 ? dx : -dx) + (dy > 0 ? dy : -dy)) *
// (-diff * 0.5f);
t.distance = FloatMath.sqrt((dx * dx + dy * dy)) * (-diff * 0.5f);
}
}
}
private static void limitCache(MapPosition mapPosition, int remove) {
int size = mTiles.size();
// remove tiles that were never loaded
for (int i = 0; i < size;) {
MapTile t = mTiles.get(i);
// make sure tile cannot be used by GL or MapWorker Thread
if (t.isLocked() || t.isActive()) {
i++;
} else {
clearTile(t);
mTiles.remove(i);
remove--;
size--;
}
}
if (remove <= 0)
return;
updateTileDistances(mTiles, mapPosition);
Collections.sort(mTiles);
for (int i = 1; i < remove; i++) {
MapTile t = mTiles.remove(size - i);
if (t.isLocked()) {
// dont remove tile used by GLRenderer
Log.d(TAG, "X not removing " + t + " " + t.distance);
mTiles.add(t);
continue;
}
if (t.isLoading) {
// NOTE: if we add tile back and set loading=false, on next
// limitCache the tile will be removed. clearTile could
// interfere with TileGenerator. so clear in passTile()
// instead.
// ... no, this does not work either: when set loading to
// false tile could be added to load queue while still
// processed in TileGenerator => need tile.cancel flag.
// t.isLoading = false;
mTiles.add(t);
Log.d(TAG, "X cancel loading " + t + " " + t.distance);
continue;
}
clearTile(t);
}
}
private static void limitLoadQueue() {
int size = mTilesLoaded.size();
if (size < MAX_TILES_IN_QUEUE)
return;
synchronized (mTilesLoaded) {
// remove tiles already uploaded to vbo
for (int i = 0; i < size;) {
MapTile t = mTilesLoaded.get(i);
if (!t.newData) {
mTilesLoaded.remove(i);
size--;
continue;
}
i++;
}
if (size < MAX_TILES_IN_QUEUE)
return;
// clear loaded but not used tiles
for (int i = 0, n = size - MAX_TILES_IN_QUEUE / 2; i < n; n--) {
MapTile t = mTilesLoaded.get(i);
if (t.isLocked()) {
// Log.d(TAG, "keep unused tile data: " + t + " " +
// t.isActive);
i++;
continue;
}
// Log.d(TAG, "remove unused tile data: " + t);
mTilesLoaded.remove(i);
mTiles.remove(t);
clearTile(t);
}
}
}
/**
* called from MapWorker Thread when tile is loaded by TileGenerator
*
* @param jobTile
* ...
* @return ...
*/
public synchronized boolean passTile(JobTile jobTile) {
MapTile tile = (MapTile) jobTile;
if (!tile.isLoading) {
// no one should be able to use this tile now, TileGenerator passed
// it, GL-Thread does nothing until newdata is set.
Log.d(TAG, "passTile: canceled " + tile);
synchronized (mTilesLoaded) {
mTilesLoaded.add(tile);
}
return true;
}
tile.vbo = BufferObject.get();
if (tile.vbo == null) {
Log.d(TAG, "no VBOs left for " + tile);
tile.isLoading = false;
return false;
}
tile.newData = true;
tile.isLoading = false;
if (!MapView.debugFrameTime)
requestRender();
synchronized (mTilesLoaded) {
mTilesLoaded.add(tile);
}
return true;
}
public void setRenderTheme(RenderTheme t) {
if (mRenderer != null)
mRenderer.setRenderTheme(t);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
Log.d(TAG, "onSizeChanged" + w + " " + h);
mWidth = w;
mHeight = h;
if (mWidth > 0 && mHeight > 0)
mInitial = true;
super.onSizeChanged(w, h, oldw, oldh);
}
}

View File

@@ -0,0 +1,143 @@
/*
* Copyright 2012 Hannes Janetzek
*
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General 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 License for more details.
*
* You should have received a copy of the GNU Lesser General License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.oscim.renderer;
import org.oscim.generator.JobTile;
import org.oscim.renderer.layer.Layers;
class MapTile extends JobTile {
/**
* VBO layout: - 16 bytes fill coordinates, n bytes polygon vertices, m
* bytes lines vertices
*/
BufferObject vbo;
TextTexture texture;
/**
* Tile data set by TileGenerator:
*/
TextItem labels;
Layers layers;
/**
* tile has new data to upload to gl
*/
boolean newData;
/**
* tile is loaded and ready for drawing.
*/
boolean isReady;
/**
* tile is in view region.
*/
boolean isVisible;
/**
* pointer to access relatives in QuadTree
*/
QuadTree rel;
int lastDraw = 0;
// keep track which tiles are locked as proxy for this tile
final static int PROXY_PARENT = 16;
final static int PROXY_GRAMPA = 32;
final static int PROXY_HOLDER = 64;
// 1-8: children
byte proxies;
// counting the tiles that use this tile as proxy
byte refs;
byte locked;
// used when this tile sits in fo another tile.
// e.g. x:-1,y:0,z:1 for x:1,y:0
MapTile holder;
MapTile(int tileX, int tileY, byte zoomLevel) {
super(tileX, tileY, zoomLevel);
}
boolean isActive() {
return isLoading || newData || isReady;
}
boolean isLocked() {
return locked > 0 || refs > 0;
}
void lock() {
if (holder != null)
return;
locked++;
if (locked > 1 || isReady || newData)
return;
MapTile p = rel.parent.tile;
if (p != null && (p.isReady || p.newData || p.isLoading)) {
proxies |= PROXY_PARENT;
p.refs++;
}
p = rel.parent.parent.tile;
if (p != null && (p.isReady || p.newData || p.isLoading)) {
proxies |= PROXY_GRAMPA;
p.refs++;
}
for (int j = 0; j < 4; j++) {
if (rel.child[j] != null) {
p = rel.child[j].tile;
if (p != null && (p.isReady || p.newData || p.isLoading)) {
proxies |= (1 << j);
p.refs++;
}
}
}
}
void unlock() {
if (holder != null)
return;
locked--;
if (locked > 0 || proxies == 0)
return;
if ((proxies & (1 << 4)) != 0) {
MapTile p = rel.parent.tile;
p.refs--;
}
if ((proxies & (1 << 5)) != 0) {
MapTile p = rel.parent.parent.tile;
p.refs--;
}
for (int i = 0; i < 4; i++) {
if ((proxies & (1 << i)) != 0) {
rel.child[i].tile.refs--;
}
}
proxies = 0;
}
}

View File

@@ -0,0 +1,190 @@
/*
* Copyright 2012, Hannes Janetzek
*
* 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 java.io.IOException;
import org.oscim.core.MapPosition;
import org.oscim.core.Tile;
import org.oscim.renderer.layer.Layer;
import org.oscim.renderer.layer.Layers;
import org.oscim.renderer.layer.LineLayer;
import org.oscim.renderer.layer.SymbolItem;
import org.oscim.renderer.layer.SymbolLayer;
import org.oscim.theme.renderinstruction.BitmapUtils;
import org.oscim.theme.renderinstruction.Line;
import org.oscim.view.MapView;
import android.graphics.Color;
import android.graphics.Paint.Cap;
import android.opengl.GLES20;
import android.opengl.Matrix;
public class Overlay {
BufferObject vbo;
Layers layers;
TextItem labels;
TextTexture texture;
// flag to set when data is ready for (re)compilation.
boolean newData;
boolean isReady;
MapPosition mMapPosition;
float drawScale;
private boolean first = true;
Overlay() {
mMapPosition = new MapPosition();
layers = new Layers();
LineLayer ll = (LineLayer) layers.getLayer(1, Layer.LINE);
ll.line = new Line(Color.BLUE, 1.0f, Cap.BUTT);
ll.width = 2;
float[] points = { -100, -100, 100, -100, 100, 100, -100, 100, -100,
-100 };
short[] index = { (short) points.length };
ll.addLine(points, index, false);
//
// PolygonLayer pl = (PolygonLayer) layers.getLayer(0, Layer.POLYGON);
// pl.area = new Area(Color.argb(128, 255, 0, 0));
//
// float[] ppoints = {
// 0, 256,
// 0, 0,
// 256, 0,
// 256, 256,
// };
// short[] pindex = { (short) ppoints.length };
// pl.addPolygon(ppoints, pindex);
SymbolLayer sl = new SymbolLayer();
SymbolItem it = new SymbolItem();
it.x = 0;
it.y = 0;
// billboard always faces camera
it.billboard = true;
try {
it.bitmap = BitmapUtils.createBitmap("file:/sdcard/cheshire.png");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
sl.addSymbol(it);
SymbolItem it2 = new SymbolItem();
it2.bitmap = it.bitmap;
it2.x = 0;
it2.y = 0;
// billboard always faces camera
it2.billboard = false;
sl.addSymbol(it2);
layers.symbolLayers = sl;
}
synchronized boolean onTouch() {
return true;
}
// /////////////// called from GLRender Thread ////////////////////////
// use synchronized (this){} when updating 'layers' from another thread
synchronized void update(MapView mapView) {
// keep position constant (or update layer relative to new position)
// mapView.getMapViewPosition().getMapPosition(mMapPosition, null);
if (first) {
// fix at initial position
mapView.getMapViewPosition().getMapPosition(mMapPosition, null);
first = false;
// pass layers to be uploaded and drawn to GL Thread
// afterwards never modify 'layers' outside of this function!
newData = true;
}
}
float[] mvp = new float[16];
synchronized void render(MapPosition pos, float[] mv, float[] proj) {
float div = 1;
setMatrix(pos, mv);
Matrix.multiplyMM(mvp, 0, proj, 0, mv, 0);
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbo.id);
for (Layer l = layers.layers; l != null;) {
if (l.type == Layer.POLYGON) {
GLES20.glDisable(GLES20.GL_BLEND);
l = PolygonRenderer.draw(pos, l, mvp, true, false);
} else {
GLES20.glEnable(GLES20.GL_BLEND);
l = LineRenderer.draw(pos, l, mvp, div, 0, layers.lineOffset);
}
}
for (Layer l = layers.symbolLayers; l != null;) {
l = TextureRenderer.draw(l, 1, proj, mv, layers.symbolOffset);
}
}
private void setMatrix(MapPosition mPos, float[] matrix) {
MapPosition oPos = mMapPosition;
float div = 1;
byte z = oPos.zoomLevel;
int diff = mPos.zoomLevel - z;
if (diff < 0)
div = (1 << -diff);
else if (diff > 0)
div = (1.0f / (1 << diff));
float x = (float) (oPos.x - mPos.x * div);
float y = (float) (oPos.y - mPos.y * div);
// flip around date-line
float max = (Tile.TILE_SIZE << z);
if (x < -max / 2)
x = max + x;
else if (x > max / 2)
x = x - max;
float scale = mPos.scale / div;
Matrix.setIdentityM(matrix, 0);
// translate relative to map center
matrix[12] = x * scale;
matrix[13] = y * scale;
scale = (mPos.scale / oPos.scale) / div;
// scale to tile to world coordinates
scale /= GLRenderer.COORD_MULTIPLIER;
matrix[0] = scale;
matrix[5] = scale;
Matrix.multiplyMM(matrix, 0, mPos.viewMatrix, 0, matrix, 0);
}
}

View File

@@ -0,0 +1,351 @@
/*
* Copyright 2012 Hannes Janetzek
*
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General 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 License for more details.
*
* You should have received a copy of the GNU Lesser General License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.oscim.renderer;
import static android.opengl.GLES20.GL_BLEND;
import static android.opengl.GLES20.GL_EQUAL;
import static android.opengl.GLES20.GL_INVERT;
import static android.opengl.GLES20.GL_STENCIL_TEST;
import static android.opengl.GLES20.GL_TRIANGLE_FAN;
import static android.opengl.GLES20.GL_TRIANGLE_STRIP;
import static android.opengl.GLES20.GL_ZERO;
import static android.opengl.GLES20.glColorMask;
import static android.opengl.GLES20.glDisable;
import static android.opengl.GLES20.glDrawArrays;
import static android.opengl.GLES20.glEnable;
import static android.opengl.GLES20.glGetAttribLocation;
import static android.opengl.GLES20.glGetUniformLocation;
import static android.opengl.GLES20.glStencilFunc;
import static android.opengl.GLES20.glStencilMask;
import static android.opengl.GLES20.glStencilOp;
import static android.opengl.GLES20.glUniform4fv;
import static android.opengl.GLES20.glUniformMatrix4fv;
import static android.opengl.GLES20.glUseProgram;
import static android.opengl.GLES20.glVertexAttribPointer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import org.oscim.core.MapPosition;
import org.oscim.renderer.layer.Layer;
import org.oscim.renderer.layer.PolygonLayer;
import org.oscim.utils.GlUtils;
import android.opengl.GLES20;
class PolygonRenderer {
// private static final String TAG = "PolygonRenderer";
// private static final int NUM_VERTEX_SHORTS = 2;
private static final int POLYGON_VERTICES_DATA_POS_OFFSET = 0;
private static final int STENCIL_BITS = 8;
private static final float FADE_START = 1.3f;
private static PolygonLayer[] mFillPolys;
private static int polygonProgram;
private static int hPolygonVertexPosition;
private static int hPolygonMatrix;
private static int hPolygonColor;
static boolean init() {
// Set up the program for rendering polygons
polygonProgram = GlUtils.createProgram(Shaders.polygonVertexShader,
Shaders.polygonFragmentShader);
if (polygonProgram == 0) {
// Log.e(TAG, "Could not create polygon program.");
return false;
}
hPolygonMatrix = glGetUniformLocation(polygonProgram, "u_mvp");
hPolygonColor = glGetUniformLocation(polygonProgram, "u_color");
hPolygonVertexPosition = glGetAttribLocation(polygonProgram, "a_position");
mFillPolys = new PolygonLayer[STENCIL_BITS];
return true;
}
private static void fillPolygons(int zoom, float scale) {
boolean blend = false;
/* draw to framebuffer */
glColorMask(true, true, true, true);
/* do not modify stencil buffer */
glStencilMask(0);
for (int c = mStart; c < mCount; c++) {
PolygonLayer l = mFillPolys[c];
float f = 1.0f;
if (l.area.fade >= zoom || l.area.color[3] != 1.0) {
/* fade in/out || draw alpha color */
if (l.area.fade >= zoom) {
f = (scale > FADE_START ? scale : FADE_START) - f;
if (f > 1.0f)
f = 1.0f;
}
f *= l.area.color[3];
if (!blend) {
glEnable(GL_BLEND);
blend = true;
}
GlUtils.setColor(hPolygonColor, l.area.color, f);
} else if (l.area.blend == zoom) {
/* blend colors */
f = scale - 1.0f;
if (f > 1.0f)
f = 1.0f;
else if (f < 0)
f = 0;
GlUtils.setBlendColors(hPolygonColor,
l.area.color, l.area.blendColor, f);
} else {
/* draw solid */
if (blend) {
glDisable(GL_BLEND);
blend = false;
}
if (l.area.blend <= zoom && l.area.blend > 0)
glUniform4fv(hPolygonColor, 1, l.area.blendColor, 0);
else
glUniform4fv(hPolygonColor, 1, l.area.color, 0);
}
// if (alpha < 1) {
// if (!blend) {
// glEnable(GL_BLEND);
// blend = true;
// }
// } else if (blend) {
// glDisable(GL_BLEND);
// blend = false;
// }
/* set stencil buffer mask used to draw this layer */
glStencilFunc(GL_EQUAL, 0xff, 1 << c);
/* draw tile fill coordinates */
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
if (blend)
glDisable(GL_BLEND);
}
// layers to fill
private static int mCount;
// stencil buffer index to start fill
private static int mStart;
static Layer draw(MapPosition pos, Layer layer,
float[] matrix, boolean first, boolean clip) {
int zoom = pos.zoomLevel;
float scale = pos.scale;
glUseProgram(polygonProgram);
int va = hPolygonVertexPosition;
if (!GLRenderer.vertexArray[va]) {
GLES20.glEnableVertexAttribArray(va);
GLRenderer.vertexArray[va] = true;
}
va = va == 0 ? 1 : 0;
if (GLRenderer.vertexArray[va]) {
GLES20.glDisableVertexAttribArray(va);
GLRenderer.vertexArray[va] = false;
}
// GLES20.glEnableVertexAttribArray(hPolygonVertexPosition);
glVertexAttribPointer(hPolygonVertexPosition, 2, GLES20.GL_SHORT,
false, 0, POLYGON_VERTICES_DATA_POS_OFFSET);
glUniformMatrix4fv(hPolygonMatrix, 1, false, matrix, 0);
// use stencilbuffer method for polygon drawing
glEnable(GL_STENCIL_TEST);
if (first) {
mCount = 0;
mStart = 0;
} else {
mStart = mCount;
}
Layer l = layer;
for (; l != null && l.type == Layer.POLYGON; l = l.next) {
PolygonLayer pl = (PolygonLayer) l;
// fade out polygon layers (set in RederTheme)
if (pl.area.fade > 0 && pl.area.fade > zoom)
continue;
if (mCount == mStart) {
// clear stencilbuffer (tile region)
// disable drawing to framebuffer
glColorMask(false, false, false, false);
// never pass the test: always apply fail op
glStencilFunc(GLES20.GL_ALWAYS, 0, 0xFF);
glStencilMask(0xFF);
glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
if (clip) {
// draw clip-region into depth buffer:
// this is used for lines and polygons
// write to depth buffer
GLES20.glDepthMask(true);
// to prevent overdraw gl_less restricts
// the clip to the area where no other
// tile has drawn
GLES20.glDepthFunc(GLES20.GL_LESS);
}
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
if (clip) {
first = false;
clip = false;
// dont modify depth buffer
GLES20.glDepthMask(false);
// only draw to this tile
GLES20.glDepthFunc(GLES20.GL_EQUAL);
}
// stencil op for stencil method polygon drawing
glStencilOp(GL_INVERT, GL_INVERT, GL_INVERT);
// no need for depth test while drawing stencil
if (clip)
glDisable(GLES20.GL_DEPTH_TEST);
}
// else if (mCount == mStart) {
// // disable drawing to framebuffer
// glColorMask(false, false, false, false);
//
// // never pass the test: always apply fail op
// glStencilFunc(GLES20.GL_ALWAYS, 0, 0xFF);
//
// // stencil op for stencil method polygon drawing
// glStencilOp(GL_INVERT, GL_INVERT, GL_INVERT);
//
// // no need for depth test while drawing stencil
// if (clip)
// glDisable(GLES20.GL_DEPTH_TEST);
// }
mFillPolys[mCount] = pl;
// set stencil mask to draw to
glStencilMask(1 << mCount++);
glDrawArrays(GL_TRIANGLE_FAN, l.offset, l.verticesCnt);
// draw up to 8 layers into stencil buffer
if (mCount == STENCIL_BITS) {
/* only draw where nothing was drawn yet */
if (clip)
glEnable(GLES20.GL_DEPTH_TEST);
fillPolygons(zoom, scale);
mCount = 0;
mStart = 0;
}
}
if (mCount > 0) {
/* only draw where nothing was drawn yet */
if (clip)
glEnable(GLES20.GL_DEPTH_TEST);
fillPolygons(zoom, scale);
}
// maybe reset start when only few layers left in stencil buffer
// if (mCount > 5){
// mCount = 0;
// mStart = 0;
// }
glDisable(GL_STENCIL_TEST);
if (clip && first)
drawDepthClip();
// GLES20.glDisableVertexAttribArray(hPolygonVertexPosition);
return l;
}
private static float[] debugFillColor = { 0.3f, 0.0f, 0.0f, 0.3f };
private static float[] debugFillColor2 = { 0.0f, 0.3f, 0.0f, 0.3f };
private static ByteBuffer mDebugFill;
static void debugDraw(float[] matrix, float[] coords, int color) {
mDebugFill = ByteBuffer.allocateDirect(32).order(ByteOrder.nativeOrder());
FloatBuffer buf = mDebugFill.asFloatBuffer();
buf.put(coords);
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
mDebugFill.position(0);
glUseProgram(polygonProgram);
GLES20.glEnableVertexAttribArray(hPolygonVertexPosition);
glVertexAttribPointer(hPolygonVertexPosition, 2, GLES20.GL_FLOAT,
false, 0, mDebugFill);
glUniformMatrix4fv(hPolygonMatrix, 1, false, matrix, 0);
if (color == 0)
glUniform4fv(hPolygonColor, 1, debugFillColor, 0);
else
glUniform4fv(hPolygonColor, 1, debugFillColor2, 0);
glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
GlUtils.checkGlError("draw debug");
// GLES20.glDisableVertexAttribArray(hPolygonVertexPosition);
}
static void drawDepthClip() {
glColorMask(false, false, false, false);
GLES20.glDepthMask(true);
GLES20.glDepthFunc(GLES20.GL_LESS);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
GLES20.glDepthMask(false);
glColorMask(true, true, true, true);
GLES20.glDepthFunc(GLES20.GL_EQUAL);
}
}

View File

@@ -0,0 +1,147 @@
/*
* Copyright 2012 Hannes Janetzek
*
* 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 android.util.Log;
class QuadTree {
private static String TAG = "QuadTree";
// pointer to tile 0/0/0
private static QuadTree root;
// parent pointer is used to link pool items
private static QuadTree pool;
QuadTree parent;
// .... x y
// 0 => 0 0
// 1 => 1 0
// 2 => 0 1
// 3 => 1 1
final QuadTree[] child = new QuadTree[4];
int refs = 0;
byte id;
MapTile tile;
static void init() {
pool = null;
root = new QuadTree();
root.parent = root;
}
static boolean remove(MapTile t) {
if (t.rel == null) {
Log.d(TAG, "already removed " + t);
return true;
}
QuadTree cur = t.rel;
QuadTree next;
for (; cur != root;) {
// keep pointer to parent
next = cur.parent;
cur.refs--;
// if current node has no children
if (cur.refs == 0) {
// unhook from parent
next.child[cur.id] = null;
// add item back to pool
cur.parent = pool;
pool = cur;
}
cur = next;
}
root.refs--;
t.rel.tile = null;
t.rel = null;
return true;
}
static QuadTree add(MapTile tile) {
int x = tile.tileX;
int y = tile.tileY;
int z = tile.zoomLevel;
// if (x < 0 || x >= 1 << z) {
// Log.d(TAG, "invalid position");
// return null;
// }
// if (y < 0 || y >= 1 << z) {
// Log.d(TAG, "invalid position");
// return null;
// }
QuadTree leaf = root;
for (int level = z - 1; level >= 0; level--) {
int id = ((x >> level) & 1) | ((y >> level) & 1) << 1;
leaf.refs++;
QuadTree cur = leaf.child[id];
if (cur != null) {
leaf = cur;
continue;
}
if (pool != null) {
cur = pool;
pool = pool.parent;
} else {
cur = new QuadTree();
}
cur.refs = 0;
cur.id = (byte) id;
cur.parent = leaf;
cur.parent.child[id] = cur;
leaf = cur;
}
leaf.refs++;
leaf.tile = tile;
tile.rel = leaf;
return leaf;
}
static MapTile getTile(int x, int y, int z) {
QuadTree leaf = root;
for (int level = z - 1; level >= 0; level--) {
leaf = leaf.child[((x >> level) & 1) | ((y >> level) & 1) << 1];
if (leaf == null)
return null;
if (level == 0) {
return leaf.tile;
}
}
return null;
}
}

View File

@@ -0,0 +1,171 @@
/*
* Copyright 2012 Hannes Janetzek
*
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General 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 License for more details.
*
* You should have received a copy of the GNU Lesser General License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* ported from Polymaps: Layer.js Copyright (c) 2010, SimpleGeo and Stamen Design */
package org.oscim.renderer;
import android.util.FloatMath;
import android.util.Log;
public abstract class ScanBox {
static class Edge {
float x0, y0, x1, y1, dx, dy;
void set(float x0, float y0, float x1, float y1) {
if (y0 <= y1) {
this.x0 = x0;
this.y0 = y0;
this.x1 = x1;
this.y1 = y1;
dx = x1 - x0;
dy = y1 - y0;
} else {
this.x0 = x1;
this.y0 = y1;
this.x1 = x0;
this.y1 = y0;
dx = x0 - x1;
dy = y0 - y1;
}
}
}
private Edge ab = new Edge();
private Edge bc = new Edge();
private Edge ca = new Edge();
protected byte mZoom;
void scan(float[] coords, byte zoom) {
mZoom = zoom;
ab.set(coords[0], coords[1], coords[2], coords[3]);
bc.set(coords[2], coords[3], coords[4], coords[5]);
ca.set(coords[4], coords[5], coords[0], coords[1]);
scanTriangle();
ab.set(coords[4], coords[5], coords[6], coords[7]);
bc.set(coords[6], coords[7], coords[0], coords[1]);
ca.set(coords[0], coords[1], coords[4], coords[5]);
scanTriangle();
}
/**
* @param y
* ...
* @param x1
* ...
* @param x2
* ...
*/
void setVisible(int y, int x1, int x2) {
}
private void scanTriangle() {
if (ab.dy > bc.dy) {
Edge t = ab;
ab = bc;
bc = t;
}
if (ab.dy > ca.dy) {
Edge t = ab;
ab = ca;
ca = t;
}
if (bc.dy > ca.dy) {
Edge t = bc;
bc = ca;
ca = t;
}
// ca.dy > bc.dy > ab.dy
if (ca.dy == 0)
return;
if (ab.dy != 0)
scanSpans(ca, ab);
if (bc.dy != 0)
scanSpans(ca, bc);
}
// FIXME
private static final int MAX_SLOPE = 4;
private void scanSpans(Edge e0, Edge e1) {
int y0 = (int) Math.max(0, FloatMath.floor(e1.y0));
int y1 = (int) Math.min((1 << mZoom), FloatMath.ceil(e1.y1));
// sort edge by x-coordinate
if (e0.x0 == e1.x0 && e0.y0 == e1.y0) {
// bottom-flat
if (e0.x0 + e1.dy / e0.dy * e0.dx < e1.x1) {
Edge t = e0;
e0 = e1;
e1 = t;
}
} else {
// top-flat
if (e0.x1 - e1.dy / e0.dy * e0.dx < e1.x0) {
Edge t = e0;
e0 = e1;
e1 = t;
}
}
float m0 = e0.dx / e0.dy;
float m1 = e1.dx / e1.dy;
// still needed?
if (m0 > MAX_SLOPE)
m0 = MAX_SLOPE;
else if (m0 < -MAX_SLOPE)
m0 = -MAX_SLOPE;
if (m1 > MAX_SLOPE)
m1 = MAX_SLOPE;
else if (m1 < -MAX_SLOPE)
m1 = -MAX_SLOPE;
int d0 = e0.dx > 0 ? 1 : 0; // use y + 1 to compute x0
int d1 = e1.dx < 0 ? 1 : 0; // use y + 1 to compute x1
float x0, x1, dy;
for (int y = y0; y < y1; y++) {
dy = y + d0 - e0.y0;
if (e0.dy < dy)
dy = e0.dy;
x0 = e0.x0 + m0 * dy;
x0 = FloatMath.ceil(x0);
dy = y + d1 - e1.y0;
if (e1.dy < dy)
dy = e1.dy;
x1 = e1.x0 + m1 * dy;
x1 = FloatMath.floor(x1);
if (x1 > x0)
Log.d("...", "X set visible" + y + " " + x1 + "/" + x0);
setVisible(y, (int) x1, (int) x0);
}
}
}

View File

@@ -0,0 +1,303 @@
/*
* Copyright 2012 Hannes Janetzek
*
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General 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 License for more details.
*
* You should have received a copy of the GNU Lesser General License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.oscim.renderer;
public final class Shaders {
final static String lineVertexShader = ""
+ "precision mediump float;"
+ "uniform mat4 u_mvp;"
+ "uniform float u_width;"
+ "attribute vec2 a_position;"
+ "attribute vec2 a_st;"
+ "varying vec2 v_st;"
+ "const float dscale = 8.0/2048.0;"
+ "void main() {"
// scale extrusion to u_width pixel
// just ignore the two most insignificant bits of a_st :)
+ " vec2 dir = dscale * u_width * a_st;"
+ " gl_Position = u_mvp * vec4(a_position + dir, 0.0,1.0);"
// last two bits of a_st hold the texture coordinates
+ " v_st = u_width * (abs(mod(a_st,4.0)) - 1.0);"
// TODO use bit operations when available (gles 1.3)
// + " v_st = u_width * vec2(ivec2(a_st) & 3 - 1);"
+ "}";
// final static String lineVertexShader = ""
// + "precision mediump float;"
// + "uniform mat4 mvp;"
// + "attribute vec4 a_position;"
// + "attribute vec2 a_st;"
// + "varying vec2 v_st;"
// + "uniform float u_width;"
// + "const float dscale = 8.0/1000.0;"
// + "void main() {"
// + " vec2 dir = dscale * u_width * a_position.zw;"
// + " gl_Position = mvp * vec4(a_position.xy + dir, 0.0,1.0);"
// + " v_st = u_width * a_st;"
// + "}";
final static String lineSimpleFragmentShader = ""
+ "precision mediump float;"
+ "uniform float u_wscale;"
+ "uniform float u_width;"
+ "uniform int u_mode;"
+ "uniform vec4 u_color;"
+ "varying vec2 v_st;"
+ "const float zero = 0.0;"
+ "void main() {"
+ " float len;"
+ " if (u_mode == 0)"
+ " len = u_width - abs(v_st.s);"
+ " else "
+ " len = u_width - length(v_st);"
// fade to alpha. u_wscale is the width in pixel which should be
// faded, u_width - len the position of this fragment on the
// perpendicular to this line segment
+ " vec4 color = u_color;"
+ " if (len < u_wscale)"
+ " color *= len / u_wscale;"
+ " gl_FragColor = color;"
// smoothstep(zero, u_wscale, u_width - len) * u_color;"
+ "}";
// final static String lineFragmentShader = ""
// + "#extension GL_OES_standard_derivatives : enable\n"
// + "precision mediump float;\n"
// + "uniform float u_wscale;"
// + "uniform float u_width;"
// + "uniform vec4 u_color;"
// + "varying vec2 v_st;"
// + "const float zero = 0.0;"
// + "const vec4 col1 = vec4(0.5,0.0,0.0,0.5);"
// + "const vec4 col2 = vec4(0.0,0.0,0.5,0.5);"
// + "void main() {"
// + " vec4 color = u_color;"
// + " float width = u_width;"
// + " float len;"
// + " if (v_st.t == zero)"
// + " len = abs(v_st.s);"
// + " else "
// + " len = length(v_st);"
// + " vec2 st_width = fwidth(v_st);"
// + " float fuzz = max(st_width.s, st_width.t);"
// // + " if (v_st.s > 0.0){"
// // + " color = col1;"
// + " color *= (1.0 - len) / (fuzz + u_wscale);"
// // + " }else{"
// // + " color = col2;"
// // + " color *= 1.0 - (fuzz + u_wscale) / (len - 1.0);"
// // + " }"
// + " gl_FragColor = color;"
// + "}";
// final static String lineFragmentShader = ""
// + "#extension GL_OES_standard_derivatives : enable\n"
// + "precision mediump float;\n"
// + "uniform float u_wscale;"
// + "uniform float u_width;"
// + "uniform vec4 u_color;"
// + "varying vec2 v_st;"
// + "const float zero = 0.0;"
// + "const vec4 col1 = vec4(0.5,0.0,0.0,0.5);"
// + "const vec4 col2 = vec4(0.0,0.0,0.5,0.5);"
// + "void main() {"
// + " vec4 color = u_color;"
// + " float width = u_width;"
// + " float len;"
// + " if (v_st.t == zero)"
// + " len = abs(v_st.s);"
// + " else "
// + " len = length(v_st);"
// + " vec2 st_width = fwidth(v_st);"
// + " float fuzz = max(st_width.s, st_width.t);"
// + " if (u_width > 1.0) fuzz *= 1.2;"
// + " color *= (u_width - len) / (fuzz + u_wscale);"
// + " if (v_st.s > 0.0){"
// + " if (u_width - len < fuzz + u_wscale)"
// + " color = col1 * (u_width - len) / (fuzz + u_wscale);"
// + " }else{"
// + " if (u_width - len < fuzz + u_wscale)"
// + " color = col2 * (u_width - len) / (fuzz + u_wscale);"
// + "}"
// // + " color *= (fuzz + u_wscale);"
// // " color *= smoothstep(zero, fuzz + u_wscale, u_width - len);"
// + " gl_FragColor = color;"
// + "}";
final static String lineFragmentShader = ""
+ "#extension GL_OES_standard_derivatives : enable\n"
+ "precision mediump float;\n"
+ "uniform float u_wscale;"
+ "uniform float u_width;"
+ "uniform int u_mode;"
+ "uniform vec4 u_color;"
+ "varying vec2 v_st;"
// + "const vec4 col1 = vec4(0.5,0.0,0.0,0.5);"
// + "const vec4 col2 = vec4(0.0,0.0,0.5,0.5);"
+ "const float zero = 0.0;"
+ "void main() {"
+ " vec4 color = u_color;"
+ " float width = u_width;"
+ " float len;"
+ " if (u_mode == 0)"
+ " len = u_width - abs(v_st.s);"
+ " else "
+ " len = u_width - length(v_st);"
+ " vec2 st_width = fwidth(v_st);"
+ " float fuzz = max(st_width.s, st_width.t);"
// + " if (u_width > 1.0) fuzz *= 1.2;"
+ " fuzz += u_wscale;"
+ " if (len < fuzz){"
// + " if (v_st.s > zero)"
+ " color *= len / fuzz;"
// + " else"
// + " color = col2 * (u_width - len) / fuzz;"
+ " }"
+ " gl_FragColor = color;"
+ "}";
final static String polygonVertexShader = ""
+ "precision mediump float;"
+ "uniform mat4 u_mvp;"
+ "attribute vec4 a_position;"
+ "void main() {"
+ " gl_Position = u_mvp * a_position;"
+ "}";
final static String polygonFragmentShader = ""
+ "precision mediump float;"
+ "uniform vec4 u_color;"
+ "void main() {"
+ " gl_FragColor = u_color;"
+ "}";
final static String textVertexShader = ""
+ "precision highp float; "
+ "attribute vec4 vertex;"
+ "attribute vec2 tex_coord;"
+ "uniform mat4 u_mv;"
+ "uniform mat4 u_proj;"
+ "uniform float u_scale;"
+ "uniform float u_swidth;"
+ "varying vec2 tex_c;"
+ "const vec2 div = vec2(1.0/2048.0,1.0/2048.0);"
+ "const float coord_scale = 0.125;"
+ "void main() {"
+ " vec4 pos;"
+ " if (mod(vertex.x, 2.0) == 0.0){"
+ " pos = u_proj * (u_mv * vec4(vertex.xy + vertex.zw * u_scale, 0.0, 1.0));"
+ " } else {"
// // place as billboard
+ " vec4 dir = u_mv * vec4(vertex.xy, 0.0, 1.0);"
+ " pos = u_proj * (dir + vec4(vertex.zw * (coord_scale * u_swidth), 0.0, 0.0));"
+ " }"
+ " gl_Position = pos;"
+ " tex_c = tex_coord * div;"
+ "}";
// final static String textVertexShader = ""
// + "precision highp float; "
// + "attribute vec4 vertex;"
// + "attribute vec2 tex_coord;"
// + "uniform mat4 mvp;"
// + "uniform mat4 viewMatrix;"
// + "uniform float scale;"
// + "varying vec2 tex_c;"
// + "const vec2 div = vec2(1.0/4096.0,1.0/2048.0);"
// + "void main() {"
// + " vec4 pos;"
// + " if (mod(vertex.x, 2.0) == 0.0){"
// + " pos = mvp * vec4(vertex.xy + vertex.zw / scale, 0.0, 1.0);"
// + " } else {"
// + " vec4 dir = viewMatrix * vec4(vertex.zw / scale, 0.0, 1.0);"
// + " pos = mvp * vec4(vertex.xy + dir.xy, 0.0, 1.0);"
// + " }"
// + " pos.z = 0.0;"
// + " gl_Position = pos;"
// + " tex_c = tex_coord * div;"
// + "}";
// final static String textVertexShader = ""
// + "precision highp float; "
// + "attribute vec4 vertex;"
// + "attribute vec2 tex_coord;"
// + "uniform mat4 mvp;"
// + "uniform mat4 viewMatrix;"
// + "uniform float scale;"
// + "varying vec2 tex_c;"
// + "const vec2 div = vec2(1.0/4096.0,1.0/2048.0);"
// + "void main() {"
// + " if (mod(vertex.x, 2.0) == 0.0){"
// +
// " gl_Position = mvp * vec4(vertex.xy + vertex.zw / scale, 0.0, 1.0);"
// + " } else {"
// + " vec4 dir = viewMatrix * vec4(vertex.zw / scale, 0.0, 1.0);"
// + " gl_Position = mvp * vec4(vertex.xy + dir.xy, 0.0, 1.0);"
// + " }"
// + " tex_c = tex_coord * div;"
// + "}";
final static String textFragmentShader = ""
+ "precision highp float;"
+ "uniform sampler2D tex;"
+ "varying vec2 tex_c;"
+ "void main() {"
+ " gl_FragColor = texture2D(tex, tex_c.xy);"
+ "}";
// final static String lineVertexZigZagShader = ""
// + "precision mediump float;"
// + "uniform mat4 mvp;"
// + "attribute vec4 a_pos1;"
// + "attribute vec2 a_st1;"
// + "attribute vec4 a_pos2;"
// + "attribute vec2 a_st2;"
// + "varying vec2 v_st;"
// + "uniform vec2 u_mode;"
// + "const float dscale = 1.0/1000.0;"
// + "void main() {"
// + "if (gl_VertexID & 1 == 0) {"
// + " vec2 dir = dscale * u_mode[1] * a_pos1.zw;"
// + " gl_Position = mvp * vec4(a_pos1.xy + dir, 0.0,1.0);"
// + " v_st = u_mode[1] * a_st1;"
// + "} else {"
// + " vec2 dir = dscale * u_mode[1] * a_pos2.zw;"
// + " gl_Position = mvp * vec4( a_pos1.xy, dir, 0.0,1.0);"
// + " v_st = u_mode[1] * vec2(-a_st2.s , a_st2.t);"
// + "}";
// final static String lineFragmentShader = ""
// + "#extension GL_OES_standard_derivatives : enable\n"
// + "precision mediump float;"
// + "uniform vec2 u_mode;"
// + "uniform vec4 u_color;"
// + "varying vec2 v_st;"
// + "const float zero = 0.0;"
// + "void main() {"
// + " vec4 color = u_color;"
// + " float width = u_mode[1];"
// + " float len;"
// + " if (v_st.t == zero)"
// + " len = abs(v_st.s);"
// + " else "
// + " len = length(v_st);"
// + " vec2 st_width = fwidth(v_st);"
// + " float fuzz = max(st_width.s, st_width.t);"
// + " color.a *= smoothstep(-pixel, fuzz*pixel, width - (len * width));"
// + " gl_FragColor = color;"
// + "}";
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2012 Hannes Janetzek
*
* 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.theme.renderinstruction.Text;
public class TextItem {
TextItem next;
final float x, y;
final String string;
final Text text;
final float width;
short x1, y1, x2, y2;
public TextItem(float x, float y, String string, Text text) {
this.x = x;
this.y = y;
this.string = string;
this.text = text;
this.width = text.paint.measureText(string);
}
public TextItem(float x, float y, String string, Text text, float width) {
this.x = x;
this.y = y;
this.string = string;
this.text = text;
this.width = width;
}
}

View File

@@ -0,0 +1,455 @@
/*
* Copyright 2012 Hannes Janetzek
*
* 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 java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ShortBuffer;
import org.oscim.utils.GlUtils;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import android.util.FloatMath;
import android.util.Log;
public class TextRenderer {
private static String TAG = "TextRenderer";
private final static int TEXTURE_WIDTH = 256;
private final static int TEXTURE_HEIGHT = 256;
private final static float SCALE = 8.0f;
private final static int LBIT_MASK = 0xfffffffe;
// private final static int L2BIT_MASK = 0xfffffffc;
final static int INDICES_PER_SPRITE = 6;
final static int VERTICES_PER_SPRITE = 4;
final static int SHORTS_PER_VERTICE = 6;
final static int MAX_LABELS = 35;
private static Bitmap mBitmap;
private static Canvas mCanvas;
private static int mFontPadX = 1;
private static int mFontPadY = 1;
private static int mBitmapFormat;
private static int mBitmapType;
private static ShortBuffer mShortBuffer;
private static TextTexture[] mTextures;
private static int mIndicesVBO;
private static int mVerticesVBO;
private static int mTextProgram;
private static int hTextMVMatrix;
private static int hTextProjectionMatrix;
private static int hTextVertex;
private static int hTextScale;
private static int hTextScreenScale;
private static int hTextTextureCoord;
private static Paint mPaint = new Paint(Color.BLACK);
private static boolean debug = false;
private static short[] debugVertices = {
0, 0,
0, TEXTURE_HEIGHT * 4,
0, TEXTURE_HEIGHT - 1,
0, 0,
TEXTURE_WIDTH - 1, 0,
TEXTURE_WIDTH * 4, TEXTURE_HEIGHT * 4,
TEXTURE_WIDTH - 1, TEXTURE_HEIGHT - 1,
TEXTURE_WIDTH * 4, 0,
};
static void init() {
mTextProgram = GlUtils.createProgram(Shaders.textVertexShader,
Shaders.textFragmentShader);
hTextMVMatrix = GLES20.glGetUniformLocation(mTextProgram, "u_mv");
hTextProjectionMatrix = GLES20.glGetUniformLocation(mTextProgram, "u_proj");
hTextScale = GLES20.glGetUniformLocation(mTextProgram, "u_scale");
hTextScreenScale = GLES20.glGetUniformLocation(mTextProgram, "u_swidth");
hTextVertex = GLES20.glGetAttribLocation(mTextProgram, "vertex");
hTextTextureCoord = GLES20.glGetAttribLocation(mTextProgram, "tex_coord");
}
static boolean setup(int numTextures) {
int bufferSize = numTextures
* MAX_LABELS * VERTICES_PER_SPRITE
* SHORTS_PER_VERTICE * (Short.SIZE / 8);
// if (mBitmap == null) {
mBitmap = Bitmap.createBitmap(TEXTURE_WIDTH, TEXTURE_HEIGHT,
Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
mBitmapFormat = GLUtils.getInternalFormat(mBitmap);
mBitmapType = GLUtils.getType(mBitmap);
ByteBuffer buf = ByteBuffer.allocateDirect(bufferSize)
.order(ByteOrder.nativeOrder());
mShortBuffer = buf.asShortBuffer();
// }
int[] textureIds = new int[numTextures];
TextTexture[] textures = new TextTexture[numTextures];
GLES20.glGenTextures(numTextures, textureIds, 0);
for (int i = 0; i < numTextures; i++) {
// setup filters for texture
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureIds[i]);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
GLES20.GL_LINEAR);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,
GLES20.GL_LINEAR);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,
GLES20.GL_CLAMP_TO_EDGE); // Set U Wrapping
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,
GLES20.GL_CLAMP_TO_EDGE); // Set V Wrapping
// load the generated bitmap onto the texture
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, mBitmapFormat, mBitmap,
mBitmapType, 0);
textures[i] = new TextTexture(textureIds[i]);
}
GlUtils.checkGlError("init textures");
mTextures = textures;
// Setup triangle indices
short[] indices = new short[MAX_LABELS * INDICES_PER_SPRITE];
int len = indices.length;
short j = 0;
for (int i = 0; i < len; i += INDICES_PER_SPRITE, j += VERTICES_PER_SPRITE) {
indices[i + 0] = (short) (j + 0);
indices[i + 1] = (short) (j + 1);
indices[i + 2] = (short) (j + 2);
indices[i + 3] = (short) (j + 2);
indices[i + 4] = (short) (j + 3);
indices[i + 5] = (short) (j + 0);
}
mShortBuffer.clear();
mShortBuffer.put(indices, 0, len);
mShortBuffer.flip();
int[] mVboIds = new int[2];
GLES20.glGenBuffers(2, mVboIds, 0);
mIndicesVBO = mVboIds[0];
mVerticesVBO = mVboIds[1];
GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, mIndicesVBO);
GLES20.glBufferData(GLES20.GL_ELEMENT_ARRAY_BUFFER, len * (Short.SIZE / 8),
mShortBuffer, GLES20.GL_STATIC_DRAW);
GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0);
mShortBuffer.clear();
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVerticesVBO);
GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, bufferSize,
mShortBuffer, GLES20.GL_DYNAMIC_DRAW);
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
return true;
}
static boolean drawToTexture(MapTile tile) {
TextTexture tex = null;
if (tile.labels == null)
return false;
for (int i = 0; i < mTextures.length; i++) {
tex = mTextures[i];
if (tex.tile == null)
break;
if (!tex.tile.isLocked())
break;
tex = null;
}
if (tex == null) {
for (int i = 0; i < mTextures.length; i++) {
tex = mTextures[i];
if (!tex.tile.isVisible)
break;
tex = null;
}
}
if (tex == null) {
Log.d(TAG, "no textures left");
return false;
}
if (tex.tile != null)
tex.tile.texture = null;
mBitmap.eraseColor(Color.TRANSPARENT);
int pos = 0;
short[] buf = tex.vertices;
float y = 0;
float x = mFontPadX;
float width, height;
int max = MAX_LABELS;
if (debug) {
mCanvas.drawLine(debugVertices[0], debugVertices[1], debugVertices[4],
debugVertices[5], mPaint);
mCanvas.drawLine(debugVertices[0], debugVertices[1], debugVertices[8],
debugVertices[9], mPaint);
mCanvas.drawLine(debugVertices[12], debugVertices[13], debugVertices[4],
debugVertices[5], mPaint);
mCanvas.drawLine(debugVertices[12], debugVertices[13], debugVertices[8],
debugVertices[9], mPaint);
}
int advanceY = 0;
TextItem t = tile.labels;
float yy;
for (int i = 0; t != null && i < max; t = t.next, i++) {
height = (int) (t.text.fontHeight) + 2 * mFontPadY;
width = t.width + 2 * mFontPadX;
if (height > advanceY)
advanceY = (int) height;
if (x + width > TEXTURE_WIDTH) {
x = mFontPadX;
y += advanceY;
advanceY = (int) height;
}
yy = y + (height - 1) - t.text.fontDescent - mFontPadY;
if (yy > TEXTURE_HEIGHT) {
Log.d(TAG, "reached max labels");
break;
// continue;
}
if (t.text.stroke != null)
mCanvas.drawText(t.string, x + t.width / 2, yy, t.text.stroke);
mCanvas.drawText(t.string, x + t.width / 2, yy, t.text.paint);
if (width > TEXTURE_WIDTH)
width = TEXTURE_WIDTH;
float hw = width / 2.0f;
float hh = height / 2.0f;
short x1, x2, x3, x4, y1, y2, y3, y4;
if (t.text.caption) {
x1 = x3 = (short) (SCALE * (-hw));
y1 = y3 = (short) (SCALE * (hh));
x2 = x4 = (short) (SCALE * (hw));
y2 = y4 = (short) (SCALE * (-hh));
} else {
float vx = t.x1 - t.x2;
float vy = t.y1 - t.y2;
float a = FloatMath.sqrt(vx * vx + vy * vy);
vx = vx / a;
vy = vy / a;
float ux = -vy;
float uy = vx;
// int dx = (int) (vx * SCALE) & L2BIT_MASK;
// int dy = (int) (vy * SCALE) & L2BIT_MASK;
//
// x1 = (short) dx;
// y1 = (short) dy;
//
// x2 = (short) (dx | 1);
// y3 = (short) (dy | 1);
//
// x4 = (short) (dx | 3);
// y4 = (short) (dy | 3);
//
// x3 = (short) (dx | 2);
// y2 = (short) (dy | 2);
x1 = (short) (SCALE * (vx * hw - ux * hh));
y1 = (short) (SCALE * (vy * hw - uy * hh));
x2 = (short) (SCALE * (-vx * hw - ux * hh));
y3 = (short) (SCALE * (-vy * hw - uy * hh));
x4 = (short) (SCALE * (-vx * hw + ux * hh));
y4 = (short) (SCALE * (-vy * hw + uy * hh));
x3 = (short) (SCALE * (vx * hw + ux * hh));
y2 = (short) (SCALE * (vy * hw + uy * hh));
}
short u1 = (short) (SCALE * x);
short v1 = (short) (SCALE * y);
short u2 = (short) (SCALE * (x + width));
short v2 = (short) (SCALE * (y + height));
// pack caption/way-text info in lowest bit
int tmp = (int) (SCALE * t.x) & LBIT_MASK;
short tx = (short) (tmp | (t.text.caption ? 1 : 0));
short ty = (short) (SCALE * t.y);
// top-left
buf[pos++] = tx;
buf[pos++] = ty;
buf[pos++] = x1;
buf[pos++] = y1;
buf[pos++] = u1;
buf[pos++] = v2;
// top-right
buf[pos++] = tx;
buf[pos++] = ty;
buf[pos++] = x2;
buf[pos++] = y3;
buf[pos++] = u2;
buf[pos++] = v2;
// bot-right
buf[pos++] = tx;
buf[pos++] = ty;
buf[pos++] = x4;
buf[pos++] = y4;
buf[pos++] = u2;
buf[pos++] = v1;
// bot-left
buf[pos++] = tx;
buf[pos++] = ty;
buf[pos++] = x3;
buf[pos++] = y2;
buf[pos++] = u1;
buf[pos++] = v1;
x += width;
if (y > TEXTURE_HEIGHT) {
Log.d(TAG, "reached max labels: texture is full");
break;
}
}
tex.length = pos;
tile.texture = tex;
tex.tile = tile;
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, tex.id);
GLUtils.texSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, mBitmap,
mBitmapFormat, mBitmapType);
// FIXME shouldnt be needed here, still looking for sometimes corrupted
// labels..
GLES20.glFlush();
return true;
}
static void compileTextures() {
int offset = 0;
TextTexture tex;
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVerticesVBO);
mShortBuffer.clear();
for (int i = 0; i < mTextures.length; i++) {
tex = mTextures[i];
if (tex.tile == null) // || !tex.tile.isLocked)
continue;
mShortBuffer.put(tex.vertices, 0, tex.length);
tex.offset = offset;
offset += tex.length;
}
mShortBuffer.flip();
GLES20.glBufferSubData(GLES20.GL_ARRAY_BUFFER, 0, offset * (Short.SIZE / 8),
mShortBuffer);
}
static void beginDraw(float scale, float[] projection) {
GLES20.glUseProgram(mTextProgram);
// GLES20.glEnableVertexAttribArray(hTextTextureCoord);
// GLES20.glEnableVertexAttribArray(hTextVertex);
int va = hTextTextureCoord;
if (!GLRenderer.vertexArray[va]) {
GLES20.glEnableVertexAttribArray(va);
GLRenderer.vertexArray[va] = true;
}
va = hTextVertex;
if (!GLRenderer.vertexArray[va]) {
GLES20.glEnableVertexAttribArray(va);
GLRenderer.vertexArray[va] = true;
}
GLES20.glUniform1f(hTextScale, scale);
GLES20.glUniform1f(hTextScreenScale, 1f / GLRenderer.mWidth);
GLES20.glUniformMatrix4fv(hTextProjectionMatrix, 1, false, projection, 0);
GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, mIndicesVBO);
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVerticesVBO);
}
static void endDraw() {
GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0);
// GLES20.glDisableVertexAttribArray(hTextTextureCoord);
// GLES20.glDisableVertexAttribArray(hTextVertex);
}
static void drawTile(MapTile tile, float[] matrix) {
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, tile.texture.id);
GLES20.glUniformMatrix4fv(hTextMVMatrix, 1, false, matrix, 0);
GLES20.glVertexAttribPointer(hTextVertex, 4,
GLES20.GL_SHORT, false, 12, tile.texture.offset * (Short.SIZE / 8));
GLES20.glVertexAttribPointer(hTextTextureCoord, 2,
GLES20.GL_SHORT, false, 12, tile.texture.offset * (Short.SIZE / 8)
+ 8);
GLES20.glDrawElements(GLES20.GL_TRIANGLES, (tile.texture.length / 24) *
INDICES_PER_SPRITE, GLES20.GL_UNSIGNED_SHORT, 0);
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2012 Hannes Janetzek
*
* 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;
public class TextTexture {
final short[] vertices;
final int id;
int length;
int offset;
MapTile tile;
TextTexture(int textureID) {
vertices = new short[TextRenderer.MAX_LABELS *
TextRenderer.VERTICES_PER_SPRITE *
TextRenderer.SHORTS_PER_VERTICE];
id = textureID;
}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2012 Hannes Janetzek
*
* 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 android.graphics.Bitmap;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import android.util.Log;
public class TextureObject {
private static TextureObject pool;
public static synchronized TextureObject get() {
TextureObject to;
if (pool == null) {
init(10);
}
to = pool;
pool = pool.next;
to.next = null;
return to;
}
public static synchronized void release(TextureObject to) {
to.next = pool;
pool = to;
}
public static void uploadTexture(TextureObject to, Bitmap bitmap,
int format, int type, int w, int h) {
if (to == null) {
Log.d("...", "no fckn texture!");
return;
}
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, to.id);
if (to.width == w && to.height == h)
GLUtils.texSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, bitmap, format, type);
else {
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, format, bitmap, type, 0);
to.width = w;
to.height = h;
}
}
static void init(int num) {
TextureObject to;
int[] textureIds = new int[num];
GLES20.glGenTextures(num, textureIds, 0);
for (int i = 1; i < num; i++) {
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureIds[i]);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
GLES20.GL_LINEAR);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,
GLES20.GL_LINEAR);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,
GLES20.GL_CLAMP_TO_EDGE); // Set U Wrapping
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,
GLES20.GL_CLAMP_TO_EDGE); // Set V Wrapping
to = new TextureObject(textureIds[i]);
to.next = pool;
pool = to;
}
}
public TextureObject next;
int id;
int width;
int height;
// vertex offset from which this texture is referenced
// or store texture id with vertex?
int offset;
TextureObject(int id) {
this.id = id;
}
}

View File

@@ -0,0 +1,139 @@
/*
* Copyright 2012 Hannes Janetzek
*
* 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 java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ShortBuffer;
import org.oscim.renderer.layer.Layer;
import org.oscim.renderer.layer.TextureLayer;
import org.oscim.utils.GlUtils;
import android.opengl.GLES20;
public class TextureRenderer {
private static int mTextureProgram;
private static int hTextureMVMatrix;
private static int hTextureProjMatrix;
private static int hTextureVertex;
private static int hTextureScale;
private static int hTextureScreenScale;
private static int hTextureTexCoord;
private static int mIndicesVBO;
final static int INDICES_PER_SPRITE = 6;
final static int VERTICES_PER_SPRITE = 4;
final static int SHORTS_PER_VERTICE = 6;
// per texture
public final static int MAX_ITEMS = 40;
static void init() {
mTextureProgram = GlUtils.createProgram(Shaders.textVertexShader,
Shaders.textFragmentShader);
hTextureMVMatrix = GLES20.glGetUniformLocation(mTextureProgram, "u_mv");
hTextureProjMatrix = GLES20.glGetUniformLocation(mTextureProgram, "u_proj");
hTextureScale = GLES20.glGetUniformLocation(mTextureProgram, "u_scale");
hTextureScreenScale = GLES20.glGetUniformLocation(mTextureProgram, "u_swidth");
hTextureVertex = GLES20.glGetAttribLocation(mTextureProgram, "vertex");
hTextureTexCoord = GLES20.glGetAttribLocation(mTextureProgram, "tex_coord");
int bufferSize = MAX_ITEMS * VERTICES_PER_SPRITE
* SHORTS_PER_VERTICE * (Short.SIZE / 8);
ByteBuffer buf = ByteBuffer.allocateDirect(bufferSize)
.order(ByteOrder.nativeOrder());
ShortBuffer mShortBuffer = buf.asShortBuffer();
// Setup triangle indices
short[] indices = new short[MAX_ITEMS * INDICES_PER_SPRITE];
int len = indices.length;
short j = 0;
for (int i = 0; i < len; i += INDICES_PER_SPRITE, j += VERTICES_PER_SPRITE) {
indices[i + 0] = (short) (j + 0);
indices[i + 1] = (short) (j + 1);
indices[i + 2] = (short) (j + 2);
indices[i + 3] = (short) (j + 2);
indices[i + 4] = (short) (j + 3);
indices[i + 5] = (short) (j + 0);
}
mShortBuffer.clear();
mShortBuffer.put(indices, 0, len);
mShortBuffer.flip();
int[] mVboIds = new int[1];
GLES20.glGenBuffers(1, mVboIds, 0);
mIndicesVBO = mVboIds[0];
GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, mIndicesVBO);
GLES20.glBufferData(GLES20.GL_ELEMENT_ARRAY_BUFFER, len * (Short.SIZE / 8),
mShortBuffer, GLES20.GL_STATIC_DRAW);
GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0);
}
static Layer draw(Layer layer, float scale, float[] projection,
float matrix[], int offset) {
GLES20.glUseProgram(mTextureProgram);
GlUtils.checkGlError("draw texture1");
int va = hTextureTexCoord;
if (!GLRenderer.vertexArray[va]) {
GLES20.glEnableVertexAttribArray(va);
GLRenderer.vertexArray[va] = true;
}
va = hTextureVertex;
if (!GLRenderer.vertexArray[va]) {
GLES20.glEnableVertexAttribArray(va);
GLRenderer.vertexArray[va] = true;
}
TextureLayer tl = (TextureLayer) layer;
GlUtils.checkGlError("draw texture2.");
GLES20.glUniform1f(hTextureScale, scale);
GLES20.glUniform1f(hTextureScreenScale, 1f / GLRenderer.mWidth);
GLES20.glUniformMatrix4fv(hTextureProjMatrix, 1, false, projection, 0);
GLES20.glUniformMatrix4fv(hTextureMVMatrix, 1, false, matrix, 0);
GlUtils.checkGlError("draw texture2");
GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, mIndicesVBO);
GlUtils.checkGlError("draw texture3");
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, tl.textures.id);
GlUtils.checkGlError("draw texture4");
GlUtils.checkGlError("draw texture5");
GLES20.glVertexAttribPointer(hTextureVertex, 4,
GLES20.GL_SHORT, false, 12, offset);
GlUtils.checkGlError("draw texture..");
GLES20.glVertexAttribPointer(hTextureTexCoord, 2,
GLES20.GL_SHORT, false, 12, offset + 8);
GlUtils.checkGlError("draw texture...");
GLES20.glDrawElements(GLES20.GL_TRIANGLES, (tl.verticesCnt / 4)
* INDICES_PER_SPRITE, GLES20.GL_UNSIGNED_SHORT, 0);
GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0);
GlUtils.checkGlError("draw texture");
return layer.next;
}
}

View File

@@ -0,0 +1,569 @@
/*
* Copyright 2012 Hannes Janetzek
*
* 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.core.MercatorProjection;
import org.oscim.core.Tag;
import org.oscim.core.Tile;
import org.oscim.core.WebMercator;
import org.oscim.database.IMapDatabase;
import org.oscim.database.IMapDatabaseCallback;
import org.oscim.database.QueryResult;
import org.oscim.generator.JobTile;
import org.oscim.renderer.layer.Layer;
import org.oscim.renderer.layer.Layers;
import org.oscim.renderer.layer.LineLayer;
import org.oscim.renderer.layer.PolygonLayer;
import org.oscim.renderer.layer.SymbolItem;
import org.oscim.renderer.layer.SymbolLayer;
import org.oscim.theme.IRenderCallback;
import org.oscim.theme.RenderTheme;
import org.oscim.theme.renderinstruction.Area;
import org.oscim.theme.renderinstruction.Line;
import org.oscim.theme.renderinstruction.RenderInstruction;
import org.oscim.theme.renderinstruction.Text;
import org.oscim.view.DebugSettings;
import org.oscim.view.MapView;
import android.graphics.Bitmap;
import android.graphics.Paint;
import android.util.Log;
/**
*
*/
public class TileGenerator implements IRenderCallback, IMapDatabaseCallback {
private static String TAG = TileGenerator.class.getName();
private static final double PI180 = (Math.PI / 180) / 1000000.0;
private static final double PIx4 = Math.PI * 4;
private static final double STROKE_INCREASE = Math.sqrt(2);
private static final byte LAYERS = 11;
static final byte STROKE_MIN_ZOOM_LEVEL = 12;
static final byte STROKE_MAX_ZOOM_LEVEL = 17;
private static RenderTheme renderTheme;
private IMapDatabase mMapDatabase;
private MapTile mCurrentTile;
// coordinates of the currently processed way
private float[] mCoords;
private short[] mIndices;
// current line layer, will be added to outline layers
private LineLayer mCurLineLayer;
// layer data prepared for rendering
private Layers mLayers;
private TextItem mLabels;
private int mDrawingLayer;
private int mLevels;
private float mStrokeScale = 1.0f;
private boolean mProjected;
private float mSimplify;
private RenderInstruction[] mRenderInstructions = null;
private final String TAG_WATER = "water".intern();
private final MapView mMapView;
private final Tag[] debugTagBox = { new Tag("debug", "box") };
private final Tag[] debugTagWay = { new Tag("debug", "way") };
private final Tag[] debugTagArea = { new Tag("debug", "area") };
private final float[] debugBoxCoords = { 0, 0, 0, Tile.TILE_SIZE,
Tile.TILE_SIZE, Tile.TILE_SIZE, Tile.TILE_SIZE, 0, 0, 0 };
private final short[] debugBoxIndex = { 10 };
private float mProjectionScaleFactor;
/**
* @param mapView
* the MapView
*/
public TileGenerator(MapView mapView) {
Log.d(TAG, "init TileGenerator");
mMapView = mapView;
}
private float mPoiX = 256;
private float mPoiY = 256;
private Tag mTagEmptyName = new Tag(Tag.TAG_KEY_NAME, null, false);
private Tag mTagName;
private void filterTags(Tag[] tags) {
for (int i = 0; i < tags.length; i++) {
// Log.d(TAG, "check tag: " + tags[i]);
if (tags[i].key == Tag.TAG_KEY_NAME) {
mTagName = tags[i];
tags[i] = mTagEmptyName;
}
}
}
// private RenderInstruction[] mNodeRenderInstructions;
@Override
public void renderPointOfInterest(byte layer, Tag[] tags, float latitude,
float longitude) {
mTagName = null;
if (mMapProjection != null) {
long x = mCurrentTile.pixelX;
long y = mCurrentTile.pixelY + Tile.TILE_SIZE;
long z = Tile.TILE_SIZE << mCurrentTile.zoomLevel;
double divx, divy;
long dx = (x - (z >> 1));
long dy = (y - (z >> 1));
if (mMapProjection == WebMercator.NAME) {
double div = WebMercator.f900913 / (z >> 1);
// divy = f900913 / (z >> 1);
mPoiX = (float) (longitude / div - dx);
mPoiY = (float) (latitude / div + dy);
} else {
divx = 180000000.0 / (z >> 1);
divy = z / PIx4;
mPoiX = (float) (longitude / divx - dx);
double sinLat = Math.sin(latitude * PI180);
mPoiY = (float) (Math.log((1.0 + sinLat) / (1.0 - sinLat)) * divy + dy);
// TODO remove this, only used for mapsforge maps
if (mPoiX < -10 || mPoiX > Tile.TILE_SIZE + 10 || mPoiY < -10
|| mPoiY > Tile.TILE_SIZE + 10)
return;
}
} else {
mPoiX = longitude;
mPoiY = latitude;
}
// remove tags that should not be cached in Rendertheme
filterTags(tags);
// Log.d(TAG, "renderPointOfInterest: " + mTagName);
// mNodeRenderInstructions =
TileGenerator.renderTheme.matchNode(this, tags, mCurrentTile.zoomLevel);
}
@Override
public void renderWaterBackground() {
// TODO Auto-generated method stub
}
private boolean mClosed;
@Override
public void renderWay(byte layer, Tag[] tags, float[] coords, short[] indices,
boolean closed) {
mTagName = null;
mProjected = false;
mCurLineLayer = null;
mClosed = closed;
mDrawingLayer = getValidLayer(layer) * mLevels;
mSimplify = 0.5f;
if (closed) {
if (mCurrentTile.zoomLevel < 14)
mSimplify = 0.5f;
else
mSimplify = 0.2f;
if (tags.length == 1 && TAG_WATER == (tags[0].value))
mSimplify = 0;
}
mCoords = coords;
mIndices = indices;
// remove tags that should not be cached in Rendertheme
filterTags(tags);
// if (mRenderInstructions != null) {
// for (int i = 0, n = mRenderInstructions.length; i < n; i++)
// mRenderInstructions[i].renderWay(this, tags);
// }
mRenderInstructions = TileGenerator.renderTheme.matchWay(this, tags,
(byte) (mCurrentTile.zoomLevel + 0),
closed, true);
if (mRenderInstructions == null && mDebugDrawUnmatched)
debugUnmatched(closed, tags);
}
private void debugUnmatched(boolean closed, Tag[] tags) {
Log.d(TAG, "way not matched: " + tags[0] + " "
+ (tags.length > 1 ? tags[1] : "") + " " + closed);
mTagName = new Tag("name", tags[0].key + ":" + tags[0].value, false);
if (closed) {
mRenderInstructions = TileGenerator.renderTheme.matchWay(this, debugTagArea,
(byte) 0, true, true);
} else {
mRenderInstructions = TileGenerator.renderTheme.matchWay(this, debugTagWay,
(byte) 0, true, true);
}
}
@Override
public void renderAreaCaption(Text text) {
// Log.d(TAG, "renderAreaCaption: " + mTagName);
if (mTagName == null)
return;
if (text.textKey == mTagEmptyName.key) {
TextItem t = new TextItem(mCoords[0], mCoords[1], mTagName.value, text);
t.next = mLabels;
mLabels = t;
}
}
@Override
public void renderPointOfInterestCaption(Text text) {
// Log.d(TAG, "renderPointOfInterestCaption: " + mPoiX + " " + mPoiY +
// " " + mTagName);
if (mTagName == null)
return;
if (text.textKey == mTagEmptyName.key) {
TextItem t = new TextItem(mPoiX, mPoiY, mTagName.value, text);
t.next = mLabels;
mLabels = t;
}
}
@Override
public void renderWayText(Text text) {
// Log.d(TAG, "renderWayText: " + mTagName);
if (mTagName == null)
return;
if (text.textKey == mTagEmptyName.key && mTagName.value != null) {
mLabels = WayDecorator.renderText(mCoords, mTagName.value, text, 0,
mIndices[0], mLabels);
}
}
@Override
public void renderAreaSymbol(Bitmap symbol) {
// TODO Auto-generated method stub
}
@Override
public void renderPointOfInterestCircle(float radius, Paint fill, int level) {
// TODO Auto-generated method stub
}
@Override
public void renderPointOfInterestSymbol(Bitmap bitmap) {
// Log.d(TAG, "add symbol");
if (mLayers.symbolLayers == null)
mLayers.symbolLayers = new SymbolLayer();
SymbolLayer sl = (SymbolLayer) mLayers.symbolLayers;
SymbolItem it = new SymbolItem();
it.x = mPoiX;
it.y = mPoiY;
it.bitmap = bitmap;
it.billboard = true;
sl.addSymbol(it);
}
@Override
public void renderWay(Line line, int level) {
projectToTile();
if (line.outline && mCurLineLayer == null)
return;
int numLayer = (mDrawingLayer * 2) + level;
LineLayer lineLayer = (LineLayer) mLayers.getLayer(numLayer, Layer.LINE);
if (lineLayer == null)
return;
if (lineLayer.line == null) {
lineLayer.line = line;
float w = line.width;
if (!line.fixed) {
w *= mStrokeScale;
w *= mProjectionScaleFactor;
}
lineLayer.width = w;
}
if (line.outline) {
lineLayer.addOutline(mCurLineLayer);
return;
}
mCurLineLayer = lineLayer;
lineLayer.addLine(mCoords, mIndices, mClosed);
}
@Override
public void renderArea(Area area, int level) {
if (!mDebugDrawPolygons)
return;
if (!mProjected && !projectToTile())
return;
int numLayer = mDrawingLayer + level;
PolygonLayer layer = (PolygonLayer) mLayers.getLayer(numLayer, Layer.POLYGON);
if (layer == null)
return;
if (layer.area == null)
layer.area = area;
layer.addPolygon(mCoords, mIndices);
}
@Override
public void renderWaySymbol(Bitmap symbol, boolean alignCenter, boolean repeat) {
// TODO Auto-generated method stub
}
public void cleanup() {
// TODO Auto-generated method stub
}
private boolean mDebugDrawPolygons;
boolean mDebugDrawUnmatched;
public boolean executeJob(JobTile jobTile) {
MapTile tile;
if (mMapDatabase == null)
return false;
tile = mCurrentTile = (MapTile) jobTile;
DebugSettings debugSettings = mMapView.getDebugSettings();
mDebugDrawPolygons = !debugSettings.mDisablePolygons;
mDebugDrawUnmatched = debugSettings.mDrawUnmatchted;
if (tile.newData || tile.isReady) {
// should be fixed now.
Log.d(TAG, "XXX tile already loaded "
+ tile + " "
+ tile.newData + " "
+ tile.isReady + " ");
return false;
}
mLevels = TileGenerator.renderTheme.getLevels();
// limit stroke scale at z=17
if (tile.zoomLevel < STROKE_MAX_ZOOM_LEVEL)
setScaleStrokeWidth(tile.zoomLevel);
else
setScaleStrokeWidth(STROKE_MAX_ZOOM_LEVEL);
// acount for area changes with latitude
mProjectionScaleFactor = 0.5f + (float) (0.5 / Math.cos(MercatorProjection
.pixelYToLatitude(tile.pixelY, tile.zoomLevel)
* (Math.PI / 180)));
mLayers = new Layers();
if (mMapDatabase.executeQuery(tile, this) != QueryResult.SUCCESS) {
Log.d(TAG, "Failed loading: " + tile);
mLayers.clear();
mLayers = null;
mLabels = null;
mCurLineLayer = null;
tile.isLoading = false;
return false;
}
if (debugSettings.mDrawTileFrames) {
mTagName = new Tag("name", tile.toString(), false);
mPoiX = Tile.TILE_SIZE >> 1;
mPoiY = 10;
TileGenerator.renderTheme.matchNode(this, debugTagWay, (byte) 0);
mIndices = debugBoxIndex;
mCoords = debugBoxCoords;
mDrawingLayer = 10 * mLevels;
TileGenerator.renderTheme.matchWay(this, debugTagBox, (byte) 0, false, true);
}
tile.layers = mLayers;
tile.labels = mLabels;
mLayers = null;
mLabels = null;
mCurLineLayer = null;
return true;
}
private static byte getValidLayer(byte layer) {
if (layer < 0) {
return 0;
/**
* return instances of MapRenderer
*/
} else if (layer >= LAYERS) {
return LAYERS - 1;
} else {
return layer;
}
}
/**
* Sets the scale stroke factor for the given zoom level.
*
* @param zoomLevel
* the zoom level for which the scale stroke factor should be
* set.
*/
private void setScaleStrokeWidth(byte zoomLevel) {
int zoomLevelDiff = Math.max(zoomLevel - STROKE_MIN_ZOOM_LEVEL, 0);
mStrokeScale = (float) Math.pow(STROKE_INCREASE, zoomLevelDiff);
if (mStrokeScale < 1)
mStrokeScale = 1;
}
private String mMapProjection;
public void setMapDatabase(IMapDatabase mapDatabase) {
mMapDatabase = mapDatabase;
mMapProjection = mMapDatabase.getMapProjection();
}
public IMapDatabase getMapDatabase() {
return mMapDatabase;
}
public void setRenderTheme(RenderTheme theme) {
TileGenerator.renderTheme = theme;
}
@Override
public boolean checkWay(Tag[] tags, boolean closed) {
mRenderInstructions = TileGenerator.renderTheme.matchWay(this, tags,
(byte) (mCurrentTile.zoomLevel + 0), closed, false);
return mRenderInstructions != null;
}
// TODO move this to Projection classes
private boolean projectToTile() {
if (mProjected || mMapProjection == null)
return true;
boolean useWebMercator = false;
if (mMapProjection == WebMercator.NAME)
useWebMercator = true;
float[] coords = mCoords;
long x = mCurrentTile.pixelX;
long y = mCurrentTile.pixelY + Tile.TILE_SIZE;
long z = Tile.TILE_SIZE << mCurrentTile.zoomLevel;
float min = mSimplify;
double divx, divy = 0;
long dx = (x - (z >> 1));
long dy = (y - (z >> 1));
if (useWebMercator) {
divx = WebMercator.f900913 / (z >> 1);
} else {
divx = 180000000.0 / (z >> 1);
divy = z / PIx4;
}
for (int pos = 0, outPos = 0, i = 0, m = mIndices.length; i < m; i++) {
int len = mIndices[i];
if (len == 0)
continue;
if (len < 0)
break;
int cnt = 0;
float lat, lon, prevLon = 0, prevLat = 0;
for (int end = pos + len; pos < end; pos += 2) {
if (useWebMercator) {
lon = (float) (coords[pos] / divx - dx);
lat = (float) (coords[pos + 1] / divx + dy);
} else {
lon = (float) ((coords[pos]) / divx - dx);
double sinLat = Math.sin(coords[pos + 1] * PI180);
lat = (float) (Math.log((1.0 + sinLat) / (1.0 - sinLat)) * divy + dy);
}
if (cnt != 0) {
// drop small distance intermediate nodes
if (lat == prevLat && lon == prevLon)
continue;
if ((pos != end - 2) &&
!((lat > prevLat + min || lat < prevLat - min) ||
(lon > prevLon + min || lon < prevLon - min)))
continue;
}
coords[outPos++] = prevLon = lon;
coords[outPos++] = prevLat = lat;
cnt += 2;
}
mIndices[i] = (short) cnt;
}
mProjected = true;
// mProjectedResult = true;
return true;
}
}

View File

@@ -0,0 +1,286 @@
/*
* Copyright 2010, 2011, 2012 mapsforge.org
*
* 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.theme.renderinstruction.Text;
import org.oscim.utils.GeometryUtils;
import android.util.FloatMath;
final class WayDecorator {
// /**
// * Minimum distance in pixels before the symbol is repeated.
// */
// private static final int DISTANCE_BETWEEN_SYMBOLS = 200;
// /**
// * Minimum distance in pixels before the way name is repeated.
// */
// private static final int DISTANCE_BETWEEN_WAY_NAMES = 500;
// /**
// * Distance in pixels to skip from both ends of a segment.
// */
// private static final int SEGMENT_SAFETY_DISTANCE = 30;
// static void renderSymbol(Bitmap symbolBitmap, boolean alignCenter,
// boolean repeatSymbol, float[][] coordinates,
// List<SymbolContainer> waySymbols) {
// int skipPixels = SEGMENT_SAFETY_DISTANCE;
//
// // get the first way point coordinates
// float previousX = coordinates[0][0];
// float previousY = coordinates[0][1];
//
// // draw the symbol on each way segment
// float segmentLengthRemaining;
// float segmentSkipPercentage;
// float symbolAngle;
// for (int i = 2; i < coordinates[0].length; i += 2) {
// // get the current way point coordinates
// float currentX = coordinates[0][i];
// float currentY = coordinates[0][i + 1];
//
// // calculate the length of the current segment (Euclidian distance)
// float diffX = currentX - previousX;
// float diffY = currentY - previousY;
// double segmentLengthInPixel = Math.sqrt(diffX * diffX + diffY * diffY);
// segmentLengthRemaining = (float) segmentLengthInPixel;
//
// while (segmentLengthRemaining - skipPixels > SEGMENT_SAFETY_DISTANCE) {
// // calculate the percentage of the current segment to skip
// segmentSkipPercentage = skipPixels / segmentLengthRemaining;
//
// // move the previous point forward towards the current point
// previousX += diffX * segmentSkipPercentage;
// previousY += diffY * segmentSkipPercentage;
// symbolAngle = (float) Math.toDegrees(Math.atan2(currentY - previousY,
// currentX - previousX));
//
// waySymbols.add(new SymbolContainer(symbolBitmap, previousX, previousY,
// alignCenter, symbolAngle));
//
// // check if the symbol should only be rendered once
// if (!repeatSymbol) {
// return;
// }
//
// // recalculate the distances
// diffX = currentX - previousX;
// diffY = currentY - previousY;
//
// // recalculate the remaining length of the current segment
// segmentLengthRemaining -= skipPixels;
//
// // set the amount of pixels to skip before repeating the symbol
// skipPixels = DISTANCE_BETWEEN_SYMBOLS;
// }
//
// skipPixels -= segmentLengthRemaining;
// if (skipPixels < SEGMENT_SAFETY_DISTANCE) {
// skipPixels = SEGMENT_SAFETY_DISTANCE;
// }
//
// // set the previous way point coordinates for the next loop
// previousX = currentX;
// previousY = currentY;
// }
// }
static TextItem renderText(float[] coordinates, String string, Text text,
int pos, int len, TextItem textItems) {
TextItem items = textItems;
TextItem t = null;
// calculate the way name length plus some margin of safety
float wayNameWidth = -1;
float minWidth = 100;
int skipPixels = 0;
// get the first way point coordinates
int previousX = (int) coordinates[pos + 0];
int previousY = (int) coordinates[pos + 1];
// find way segments long enough to draw the way name on them
for (int i = pos + 2; i < pos + len; i += 2) {
// get the current way point coordinates
int currentX = (int) coordinates[i];
int currentY = (int) coordinates[i + 1];
// calculate the length of the current segment (Euclidian distance)
float diffX = currentX - previousX;
float diffY = currentY - previousY;
for (int j = i + 2; j < pos + len; j += 2) {
int nextX = (int) coordinates[j];
int nextY = (int) coordinates[j + 1];
if (diffY == 0) {
if ((currentY - nextY) != 0)
break;
currentX = nextX;
currentY = nextY;
continue;
} else if ((currentY - nextY) == 0)
break;
float diff = diffX / diffY -
(float) (currentX - nextX) / (currentY - nextY);
// skip segments with corners
if (diff >= 0.1f || diff <= -0.1f)
break;
currentX = nextX;
currentY = nextY;
}
diffX = currentX - previousX;
diffY = currentY - previousY;
if (diffX < 0)
diffX = -diffX;
if (diffY < 0)
diffY = -diffY;
if (diffX + diffY < minWidth) {
previousX = currentX;
previousY = currentY;
continue;
}
if (wayNameWidth > 0 && diffX + diffY < wayNameWidth) {
previousX = currentX;
previousY = currentY;
continue;
}
float segmentLengthInPixel = FloatMath.sqrt(diffX * diffX + diffY * diffY);
if (skipPixels > 0) {
skipPixels -= segmentLengthInPixel;
} else if (segmentLengthInPixel > minWidth) {
if (wayNameWidth < 0) {
wayNameWidth = text.paint.measureText(string);
}
if (segmentLengthInPixel > wayNameWidth + 25) {
float s = (wayNameWidth + 25) / segmentLengthInPixel;
int width, height;
int x1, y1, x2, y2;
if (previousX < currentX) {
x1 = previousX;
y1 = previousY;
x2 = currentX;
y2 = currentY;
} else {
x1 = currentX;
y1 = currentY;
x2 = previousX;
y2 = previousY;
}
// estimate position of text on path
width = (x2 - x1) / 2;
x2 = x2 - (int) (width - s * width);
x1 = x1 + (int) (width - s * width);
height = (y2 - y1) / 2;
y2 = y2 - (int) (height - s * height);
y1 = y1 + (int) (height - s * height);
short top = (short) (y1 < y2 ? y1 : y2);
short bot = (short) (y1 < y2 ? y2 : y1);
boolean intersects = false;
for (TextItem t2 = items; t2 != null; t2 = t2.next) {
// check crossings
if (GeometryUtils.lineIntersect(x1, y1, x2, y2, t2.x1, t2.y1,
t2.x2, t2.y2)) {
intersects = true;
break;
}
// check overlapping labels of road with more than one
// way
short top2 = t2.y1 < t2.y2 ? t2.y1 : t2.y2;
short bot2 = t2.y1 < t2.y2 ? t2.y2 : t2.y1;
if (x1 - 10 < t2.x2 && t2.x1 - 10 < x2 && top - 10 < bot2
&& top2 - 10 < bot) {
if (t2.string.equals(string)) {
intersects = true;
break;
}
}
}
if (intersects) {
previousX = (int) coordinates[pos + i];
previousY = (int) coordinates[pos + i + 1];
continue;
}
// Log.d("mapsforge", "add " + text + " " + first + " " +
// last);
if (previousX < currentX) {
x1 = previousX;
y1 = previousY;
x2 = currentX;
y2 = currentY;
} else {
x1 = currentX;
y1 = currentY;
x2 = previousX;
y2 = previousY;
}
// if (t == null)
t = new TextItem(x1 + (x2 - x1) / 2, y1 + (y2 - y1) / 2, string,
text, wayNameWidth);
t.x1 = (short) x1;
t.y1 = (short) y1;
t.x2 = (short) x2;
t.y2 = (short) y2;
t.next = items;
items = t;
// skipPixels = DISTANCE_BETWEEN_WAY_NAMES;
return items;
}
}
// store the previous way point coordinates
previousX = currentX;
previousY = currentY;
}
return items;
}
private WayDecorator() {
throw new IllegalStateException();
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2012 Hannes Janetzek
*
* 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.layer;
public class Layer {
public final static byte LINE = 0;
public final static byte POLYGON = 1;
public final static byte WAYTEXT = 2;
public final static byte POITEXT = 3;
public final static byte SYMBOL = 4;
public final static byte BITMAP = 5;
public byte type;
public Layer next;
int layer;
// number of vertices this layer holds
public int verticesCnt;
// vertices offset of this layer in VBO
public int offset;
VertexPoolItem pool;
protected VertexPoolItem curItem;
}

View File

@@ -0,0 +1,166 @@
/*
* Copyright 2012 Hannes Janetzek
*
* 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.layer;
import java.nio.ShortBuffer;
import org.oscim.renderer.TextureObject;
import android.util.Log;
public class Layers {
public Layer layers;
public int lineOffset;
public Layer symbolLayers;
public int symbolOffset;
private Layer mCurLayer;
public Layer getLayer(int level, byte type) {
Layer l = layers;
Layer ret = null;
if (mCurLayer != null && mCurLayer.layer == level) {
ret = mCurLayer;
} else if (l == null || l.layer > level) {
// insert new layer at start
l = null;
} else {
while (true) {
if (l.layer == level) {
// found layer
ret = l;
break;
}
if (l.next == null || l.next.layer > level) {
// insert new layer between current and next layer
break;
}
l = l.next;
}
}
if (ret == null) {
if (type == Layer.LINE)
ret = new LineLayer(level);
else
ret = new PolygonLayer(level);
if (l == null) {
ret.next = layers;
layers = ret;
} else {
ret.next = l.next;
l.next = ret;
}
} else if (ret.type != type) {
Log.d("...", "wrong layer type " + ret.type + " " + type);
// FIXME thorw exception
return null;
}
return ret;
}
private static int LINE_VERTEX_SHORTS = 4;
private static int POLY_VERTEX_SHORTS = 2;
private static int TEXTURE_VERTEX_SHORTS = 6;
public int getSize() {
int size = 0;
for (Layer l = layers; l != null; l = l.next) {
if (l.type == Layer.LINE)
size += l.verticesCnt * LINE_VERTEX_SHORTS;
else
size += l.verticesCnt * POLY_VERTEX_SHORTS;
}
for (Layer l = symbolLayers; l != null; l = l.next) {
size += l.verticesCnt * TEXTURE_VERTEX_SHORTS;
}
return size;
}
public void compile(ShortBuffer sbuf, boolean addFill) {
// offset from fill coordinates
int pos = 0;
if (addFill)
pos = 4;
// add polygons first, needed to get the offsets right...
addLayerItems(sbuf, layers, Layer.POLYGON, pos);
lineOffset = sbuf.position() * 2; // * short-bytes
addLayerItems(sbuf, layers, Layer.LINE, 0);
symbolOffset = sbuf.position() * 2; // * short-bytes
for (Layer l = symbolLayers; l != null; l = l.next) {
SymbolLayer sl = (SymbolLayer) l;
sl.compile(sbuf);
}
}
private static void addLayerItems(ShortBuffer sbuf, Layer l, byte type, int pos) {
VertexPoolItem last = null, items = null;
for (; l != null; l = l.next) {
if (l.type != type)
continue;
for (VertexPoolItem it = l.pool; it != null; it = it.next) {
if (it.next == null)
sbuf.put(it.vertices, 0, it.used);
else
sbuf.put(it.vertices);
last = it;
}
if (last == null)
continue;
l.offset = pos;
pos += l.verticesCnt;
last.next = items;
items = l.pool;
last = null;
l.pool = null;
l.curItem = null;
}
VertexPool.release(items);
}
public void clear() {
// FIXME collect pool and add as a whole
for (Layer l = layers; l != null; l = l.next) {
if (l.pool != null) {
VertexPool.release(l.pool);
l.pool = null;
l.curItem = null;
}
}
for (Layer l = symbolLayers; l != null; l = l.next) {
SymbolLayer sl = (SymbolLayer) l;
if (sl.textures != null)
TextureObject.release(sl.textures);
}
}
}

View File

@@ -0,0 +1,500 @@
/*
* Copyright 2012 Hannes Janetzek
*
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General 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 License for more details.
*
* You should have received a copy of the GNU Lesser General License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.oscim.renderer.layer;
import org.oscim.core.Tile;
import org.oscim.renderer.GLRenderer;
import org.oscim.theme.renderinstruction.Line;
import android.graphics.Paint.Cap;
import android.util.FloatMath;
public final class LineLayer extends Layer {
private static final float COORD_SCALE = GLRenderer.COORD_MULTIPLIER;
// scale factor mapping extrusion vector to short values
private static final float DIR_SCALE = 2048;
// mask for packing last two bits of extrusion vector with texture
// coordinates
private static final int DIR_MASK = 0xFFFFFFFC;
// lines referenced by this outline layer
public LineLayer outlines;
public Line line;
public float width;
// boolean isOutline;
LineLayer(int layer) {
this.layer = layer;
this.type = Layer.LINE;
}
// LineLayer(int layer, Line line, float width, boolean outline) {
// this.layer = layer;
// this.width = width;
// this.line = line;
// // this.isOutline = outline;
// }
public void addOutline(LineLayer link) {
for (LineLayer l = outlines; l != null; l = l.outlines)
if (link == l)
return;
link.outlines = outlines;
outlines = link;
}
/*
* line extrusion is based on code from GLMap
* (https://github.com/olofsj/GLMap/) by olofsj
*/
public void addLine(float[] points, short[] index, boolean closed) {
float x, y, nextX, nextY, prevX, prevY;
float a, ux, uy, vx, vy, wx, wy;
int tmax = Tile.TILE_SIZE + 10;
int tmin = -10;
boolean rounded = false;
boolean squared = false;
if (line.cap == Cap.ROUND)
rounded = true;
else if (line.cap == Cap.SQUARE)
squared = true;
if (pool == null) {
pool = curItem = VertexPool.get();
}
VertexPoolItem si = curItem;
short v[] = si.vertices;
int opos = si.used;
for (int i = 0, pos = 0, n = index.length; i < n; i++) {
int length = index[i];
if (length < 0)
break;
// save some vertices
if (rounded && i > 200)
rounded = false;
// need at least two points
if (length < 4) {
pos += length;
continue;
}
closed = false;
// amount of vertices used
// + 2 for drawing triangle-strip
// + 4 for round caps
// + 2 for closing polygons
verticesCnt += length + (rounded ? 6 : 2) + (closed ? 2 : 0);
int ipos = pos;
x = points[ipos++];
y = points[ipos++];
nextX = points[ipos++];
nextY = points[ipos++];
// Calculate triangle corners for the given width
vx = nextX - x;
vy = nextY - y;
a = FloatMath.sqrt(vx * vx + vy * vy);
vx = (vx / a);
vy = (vy / a);
ux = -vy;
uy = vx;
if (opos == VertexPoolItem.SIZE) {
si = si.next = VertexPool.get();
v = si.vertices;
opos = 0;
}
short ox, oy, dx, dy;
int ddx, ddy;
ox = (short) (x * COORD_SCALE);
oy = (short) (y * COORD_SCALE);
boolean outside = (x < tmin || x > tmax || y < tmin || y > tmax);
if (opos == VertexPoolItem.SIZE) {
si = si.next = VertexPool.get();
v = si.vertices;
opos = 0;
}
if (rounded && !outside) {
// add first vertex twice
ddx = (int) ((ux - vx) * DIR_SCALE);
ddy = (int) ((uy - vy) * DIR_SCALE);
// last two bit encode texture coord (-1)
dx = (short) (0 | ddx & DIR_MASK);
dy = (short) (2 | ddy & DIR_MASK);
v[opos++] = ox;
v[opos++] = oy;
v[opos++] = dx;
v[opos++] = dy;
if (opos == VertexPoolItem.SIZE) {
si = si.next = VertexPool.get();
v = si.vertices;
opos = 0;
}
v[opos++] = ox;
v[opos++] = oy;
v[opos++] = dx;
v[opos++] = dy;
if (opos == VertexPoolItem.SIZE) {
si = si.next = VertexPool.get();
v = si.vertices;
opos = 0;
}
ddx = (int) (-(ux + vx) * DIR_SCALE);
ddy = (int) (-(uy + vy) * DIR_SCALE);
v[opos++] = ox;
v[opos++] = oy;
v[opos++] = (short) (2 | ddx & DIR_MASK);
v[opos++] = (short) (2 | ddy & DIR_MASK);
if (opos == VertexPoolItem.SIZE) {
si = si.next = VertexPool.get();
v = si.vertices;
opos = 0;
}
// Start of line
ddx = (int) (ux * DIR_SCALE);
ddy = (int) (uy * DIR_SCALE);
v[opos++] = ox;
v[opos++] = oy;
v[opos++] = (short) (0 | ddx & DIR_MASK);
v[opos++] = (short) (1 | ddy & DIR_MASK);
if (opos == VertexPoolItem.SIZE) {
si = si.next = VertexPool.get();
v = si.vertices;
opos = 0;
}
v[opos++] = ox;
v[opos++] = oy;
v[opos++] = (short) (2 | -ddx & DIR_MASK);
v[opos++] = (short) (1 | -ddy & DIR_MASK);
} else {
// outside means line is probably clipped
// TODO should align ending with tile boundary
// for now, just extend the line a little
if (squared) {
vx = 0;
vy = 0;
} else if (!outside) {
vx *= 0.5;
vy *= 0.5;
}
if (rounded)
verticesCnt -= 2;
// add first vertex twice
ddx = (int) ((ux - vx) * DIR_SCALE);
ddy = (int) ((uy - vy) * DIR_SCALE);
dx = (short) (0 | ddx & DIR_MASK);
dy = (short) (1 | ddy & DIR_MASK);
v[opos++] = ox;
v[opos++] = oy;
v[opos++] = dx;
v[opos++] = dy;
if (opos == VertexPoolItem.SIZE) {
si = si.next = VertexPool.get();
v = si.vertices;
opos = 0;
}
v[opos++] = ox;
v[opos++] = oy;
v[opos++] = dx;
v[opos++] = dy;
if (opos == VertexPoolItem.SIZE) {
si = si.next = VertexPool.get();
v = si.vertices;
opos = 0;
}
ddx = (int) (-(ux + vx) * DIR_SCALE);
ddy = (int) (-(uy + vy) * DIR_SCALE);
v[opos++] = ox;
v[opos++] = oy;
v[opos++] = (short) (2 | ddx & DIR_MASK);
v[opos++] = (short) (1 | ddy & DIR_MASK);
}
prevX = x;
prevY = y;
x = nextX;
y = nextY;
for (;;) {
if (ipos < pos + length) {
nextX = points[ipos++];
nextY = points[ipos++];
} else if (closed && ipos < pos + length + 2) {
// add startpoint == endpoint
nextX = points[pos];
nextY = points[pos + 1];
ipos += 2;
} else
break;
// Unit vector pointing back to previous node
vx = prevX - x;
vy = prevY - y;
a = FloatMath.sqrt(vx * vx + vy * vy);
vx = (vx / a);
vy = (vy / a);
// Unit vector pointing forward to next node
wx = nextX - x;
wy = nextY - y;
a = FloatMath.sqrt(wx * wx + wy * wy);
wx = (wx / a);
wy = (wy / a);
// Sum of these two vectors points
ux = vx + wx;
uy = vy + wy;
a = -wy * ux + wx * uy;
// boolean split = false;
if (a < 0.01f && a > -0.01f) {
// Almost straight
ux = -wy;
uy = wx;
} else {
ux = (ux / a);
uy = (uy / a);
// hack to avoid miter going to infinity
if (ux > 2.0f || ux < -2.0f || uy > 2.0f || uy < -2.0f) {
ux = -wy;
uy = wx;
}
}
ox = (short) (x * COORD_SCALE);
oy = (short) (y * COORD_SCALE);
ddx = (int) (ux * DIR_SCALE);
ddy = (int) (uy * DIR_SCALE);
if (opos == VertexPoolItem.SIZE) {
si = si.next = VertexPool.get();
v = si.vertices;
opos = 0;
}
v[opos++] = ox;
v[opos++] = oy;
v[opos++] = (short) (0 | ddx & DIR_MASK);
v[opos++] = (short) (1 | ddy & DIR_MASK);
if (opos == VertexPoolItem.SIZE) {
si = si.next = VertexPool.get();
v = si.vertices;
opos = 0;
}
v[opos++] = ox;
v[opos++] = oy;
v[opos++] = (short) (2 | -ddx & DIR_MASK);
v[opos++] = (short) (1 | -ddy & DIR_MASK);
prevX = x;
prevY = y;
x = nextX;
y = nextY;
}
vx = prevX - x;
vy = prevY - y;
a = FloatMath.sqrt(vx * vx + vy * vy);
vx = (vx / a);
vy = (vy / a);
ux = vy;
uy = -vx;
outside = (x < tmin || x > tmax || y < tmin || y > tmax);
if (opos == VertexPoolItem.SIZE) {
si.next = VertexPool.get();
si = si.next;
opos = 0;
v = si.vertices;
}
ox = (short) (x * COORD_SCALE);
oy = (short) (y * COORD_SCALE);
if (rounded && !outside) {
ddx = (int) (ux * DIR_SCALE);
ddy = (int) (uy * DIR_SCALE);
v[opos++] = ox;
v[opos++] = oy;
v[opos++] = (short) (0 | ddx & DIR_MASK);
v[opos++] = (short) (1 | ddy & DIR_MASK);
if (opos == VertexPoolItem.SIZE) {
si = si.next = VertexPool.get();
v = si.vertices;
opos = 0;
}
v[opos++] = ox;
v[opos++] = oy;
v[opos++] = (short) (2 | -ddx & DIR_MASK);
v[opos++] = (short) (1 | -ddy & DIR_MASK);
if (opos == VertexPoolItem.SIZE) {
si = si.next = VertexPool.get();
v = si.vertices;
opos = 0;
}
// For rounded line edges
ddx = (int) ((ux - vx) * DIR_SCALE);
ddy = (int) ((uy - vy) * DIR_SCALE);
dx = (short) (0 | ddx & DIR_MASK);
dy = (short) (0 | ddy & DIR_MASK);
v[opos++] = ox;
v[opos++] = oy;
v[opos++] = dx;
v[opos++] = dy;
if (opos == VertexPoolItem.SIZE) {
si = si.next = VertexPool.get();
v = si.vertices;
opos = 0;
}
// add last vertex twice
ddx = (int) (-(ux + vx) * DIR_SCALE);
ddy = (int) (-(uy + vy) * DIR_SCALE);
dx = (short) (2 | ddx & DIR_MASK);
dy = (short) (0 | ddy & DIR_MASK);
v[opos++] = ox;
v[opos++] = oy;
v[opos++] = dx;
v[opos++] = dy;
if (opos == VertexPoolItem.SIZE) {
si = si.next = VertexPool.get();
v = si.vertices;
opos = 0;
}
v[opos++] = ox;
v[opos++] = oy;
v[opos++] = dx;
v[opos++] = dy;
} else {
if (squared) {
vx = 0;
vy = 0;
} else if (!outside) {
vx *= 0.5;
vy *= 0.5;
}
if (rounded)
verticesCnt -= 2;
ddx = (int) ((ux - vx) * DIR_SCALE);
ddy = (int) ((uy - vy) * DIR_SCALE);
v[opos++] = ox;
v[opos++] = oy;
v[opos++] = (short) (0 | ddx & DIR_MASK);
v[opos++] = (short) (1 | ddy & DIR_MASK);
if (opos == VertexPoolItem.SIZE) {
si = si.next = VertexPool.get();
v = si.vertices;
opos = 0;
}
// add last vertex twice
ddx = (int) (-(ux + vx) * DIR_SCALE);
ddy = (int) (-(uy + vy) * DIR_SCALE);
dx = (short) (2 | ddx & DIR_MASK);
dy = (short) (1 | ddy & DIR_MASK);
v[opos++] = ox;
v[opos++] = oy;
v[opos++] = dx;
v[opos++] = dy;
if (opos == VertexPoolItem.SIZE) {
si = si.next = VertexPool.get();
v = si.vertices;
opos = 0;
}
v[opos++] = ox;
v[opos++] = oy;
v[opos++] = dx;
v[opos++] = dy;
}
pos += length;
}
si.used = opos;
curItem = si;
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright 2012 Hannes Janetzek
*
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General 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 License for more details.
*
* You should have received a copy of the GNU Lesser General License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.oscim.renderer.layer;
import org.oscim.core.Tile;
import org.oscim.renderer.GLRenderer;
import org.oscim.theme.renderinstruction.Area;
public final class PolygonLayer extends Layer {
private static final float S = GLRenderer.COORD_MULTIPLIER;
public Area area;
PolygonLayer(int layer) {
this.layer = layer;
this.type = Layer.POLYGON;
curItem = VertexPool.get();
pool = curItem;
}
public void addPolygon(float[] points, short[] index) {
short center = (short) ((Tile.TILE_SIZE >> 1) * S);
VertexPoolItem si = curItem;
short[] v = si.vertices;
int outPos = si.used;
for (int i = 0, pos = 0, n = index.length; i < n; i++) {
int length = index[i];
if (length < 0)
break;
// need at least three points
if (length < 6) {
pos += length;
continue;
}
verticesCnt += length / 2 + 2;
int inPos = pos;
if (outPos == VertexPoolItem.SIZE) {
si = si.next = VertexPool.get();
v = si.vertices;
outPos = 0;
}
v[outPos++] = center;
v[outPos++] = center;
for (int j = 0; j < length; j += 2) {
if (outPos == VertexPoolItem.SIZE) {
si = si.next = VertexPool.get();
v = si.vertices;
outPos = 0;
}
v[outPos++] = (short) (points[inPos++] * S);
v[outPos++] = (short) (points[inPos++] * S);
}
if (outPos == VertexPoolItem.SIZE) {
si = si.next = VertexPool.get();
v = si.vertices;
outPos = 0;
}
v[outPos++] = (short) (points[pos + 0] * S);
v[outPos++] = (short) (points[pos + 1] * S);
pos += length;
}
si.used = outPos;
curItem = si;
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2012 Hannes Janetzek
*
* 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.layer;
import android.graphics.Bitmap;
public class SymbolItem {
SymbolItem next;
public Bitmap bitmap;
public float x;
public float y;
public boolean billboard;
// center, top, bottom, left, right, top-left...
byte placement;
}

View File

@@ -0,0 +1,200 @@
/*
* Copyright 2012 Hannes Janetzek
*
* 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.layer;
import java.nio.ShortBuffer;
import org.oscim.renderer.TextureObject;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.RectF;
import android.opengl.GLUtils;
import android.util.Log;
// TODO share one static texture for all poi map symabols
public final class SymbolLayer extends TextureLayer {
private static String TAG = SymbolLayer.class.getSimpleName();
private final static int TEXTURE_WIDTH = 256;
private final static int TEXTURE_HEIGHT = 256;
private final static float SCALE = 8.0f;
private static short[] mVertices;
private static Bitmap mBitmap;
private static Canvas mCanvas;
private static int mBitmapFormat;
private static int mBitmapType;
SymbolItem symbols;
public SymbolLayer() {
if (mBitmap == null) {
mBitmap = Bitmap.createBitmap(TEXTURE_WIDTH, TEXTURE_HEIGHT,
Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
mBitmapFormat = GLUtils.getInternalFormat(mBitmap);
mBitmapType = GLUtils.getType(mBitmap);
//
mVertices = new short[40 * 24];
}
}
public void addSymbol(SymbolItem item) {
verticesCnt += 4;
SymbolItem it = symbols;
for (; it != null; it = it.next) {
if (it.bitmap == item.bitmap) {
item.next = it.next;
it.next = item;
return;
}
}
item.next = symbols;
symbols = item;
}
private final static int LBIT_MASK = 0xfffffffe;
private final RectF mRect = new RectF();
// TODO ... reuse texture when only symbol position changed
public void compile(ShortBuffer sbuf) {
int pos = 0;
short buf[] = mVertices;
int advanceY = 0;
float x = 0;
float y = 0;
mBitmap.eraseColor(Color.TRANSPARENT);
for (SymbolItem it = symbols; it != null;) {
// add bitmap
float width = it.bitmap.getWidth();
float height = it.bitmap.getHeight();
if (height > advanceY)
advanceY = (int) height;
if (x + width > TEXTURE_WIDTH) {
x = 0;
y += advanceY;
advanceY = (int) (height + 0.5f);
if (y + height > TEXTURE_HEIGHT) {
Log.d(TAG, "reached max symbols");
// need to sync bitmap upload somehow???
TextureObject to = TextureObject.get();
TextureObject.uploadTexture(to, mBitmap,
mBitmapFormat, mBitmapType,
TEXTURE_WIDTH, TEXTURE_HEIGHT);
to.next = textures;
textures = to;
sbuf.put(buf, 0, pos);
pos = 0;
}
}
mRect.left = x;
mRect.top = y;
mRect.right = x + width;
mRect.bottom = y + height;
// Log.d("...", "draw " + x + " " + y + " " + width + " " + height);
mCanvas.drawBitmap(it.bitmap, null, mRect, null);
// mCanvas.drawBitmap(it.bitmap, x, y, null);
float hw = width / 2.0f;
float hh = height / 2.0f;
short x1, x2, x3, x4, y1, y2, y3, y4;
x1 = x3 = (short) (SCALE * (-hw));
x2 = x4 = (short) (SCALE * (hw));
y1 = y3 = (short) (SCALE * (hh));
y2 = y4 = (short) (SCALE * (-hh));
short u1 = (short) (SCALE * x);
short v1 = (short) (SCALE * y);
short u2 = (short) (SCALE * (x + width));
short v2 = (short) (SCALE * (y + height));
// add symbol items referencing the same bitmap
for (SymbolItem it2 = it;; it2 = it2.next) {
if (it2 == null || it2.bitmap != it.bitmap) {
it = it2;
break;
}
// add vertices
short tx = (short) ((int) (SCALE * it2.x) & LBIT_MASK | (it2.billboard ? 1 : 0));
short ty = (short) (SCALE * it2.y);
// top-left
buf[pos++] = tx;
buf[pos++] = ty;
buf[pos++] = x1;
buf[pos++] = y1;
buf[pos++] = u1;
buf[pos++] = v2;
// top-right
buf[pos++] = tx;
buf[pos++] = ty;
buf[pos++] = x2;
buf[pos++] = y3;
buf[pos++] = u2;
buf[pos++] = v2;
// bot-right
buf[pos++] = tx;
buf[pos++] = ty;
buf[pos++] = x4;
buf[pos++] = y4;
buf[pos++] = u2;
buf[pos++] = v1;
// bot-left
buf[pos++] = tx;
buf[pos++] = ty;
buf[pos++] = x3;
buf[pos++] = y2;
buf[pos++] = u1;
buf[pos++] = v1;
x += width + 1;
}
}
TextureObject to = TextureObject.get();
TextureObject.uploadTexture(to, mBitmap,
mBitmapFormat, mBitmapType,
TEXTURE_WIDTH, TEXTURE_HEIGHT);
to.next = textures;
textures = to;
sbuf.put(buf, 0, pos);
}
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2012 Hannes Janetzek
*
* 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.layer;
import org.oscim.renderer.TextItem;
public final class TextLayer extends TextureLayer {
TextItem labels;
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2012 Hannes Janetzek
*
* 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.layer;
import org.oscim.renderer.TextureObject;
public abstract class TextureLayer extends Layer {
public TextureObject textures;
}

View File

@@ -0,0 +1,112 @@
/*
* Copyright 2012 Hannes Janetzek
*
* 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.layer;
import android.util.Log;
public class VertexPool {
private static final int POOL_LIMIT = 6000;
static private VertexPoolItem pool = null;
static private int count = 0;
static private int countAll = 0;
public static synchronized void init() {
count = 0;
countAll = 0;
pool = null;
}
static synchronized VertexPoolItem get() {
if (pool == null && count > 0) {
Log.d("VertexPool", "XXX wrong count: " + count);
}
if (pool == null) {
countAll++;
return new VertexPoolItem();
}
count--;
if (count < 0) {
int c = 0;
for (VertexPoolItem tmp = pool; tmp != null; tmp = tmp.next)
c++;
Log.d("VertexPool", "XXX wrong count: " + count + " left" + c);
return new VertexPoolItem();
}
VertexPoolItem it = pool;
pool = pool.next;
it.used = 0;
it.next = null;
return it;
}
// private static float load = 1.0f;
// private static int loadCount = 0;
static synchronized void release(VertexPoolItem items) {
if (items == null)
return;
// int pall = countAll;
// int pcnt = count;
// limit pool items
if (countAll < POOL_LIMIT) {
VertexPoolItem last = items;
while (true) {
count++;
// load += (float) last.used / VertexPoolItem.SIZE;
// loadCount++;
if (last.next == null)
break;
last = last.next;
}
last.next = pool;
pool = items;
// Log.d("Pool", "added: " + (count - pcnt) + " " + count + " " +
// countAll
// + " load: " + (load / loadCount));
} else {
// int cleared = 0;
VertexPoolItem prev, tmp = items;
while (tmp != null) {
prev = tmp;
tmp = tmp.next;
countAll--;
// load += (float) prev.used / VertexPoolItem.SIZE;
// loadCount++;
prev.next = null;
}
// Log.d("Pool", "dropped: " + (pall - countAll) + " " + count + " "
// + countAll + " load: " + (load / loadCount));
}
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2012 Hannes Janetzek
*
* 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.layer;
public class VertexPoolItem {
final short[] vertices;
int used;
VertexPoolItem next;
VertexPoolItem() {
vertices = new short[SIZE];
used = 0;
}
// must be multiple of 4 (expected in LineLayer/PolygonLayer)
static final int SIZE = 256;
}