/*
* Copyright 2013 Hannes Janetzek
*
* This file is part of the OpenScienceMap project (http://www.opensciencemap.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.layers.tile;
import static org.oscim.layers.tile.MapTile.PROXY_PARENT;
import static org.oscim.layers.tile.MapTile.State.NEW_DATA;
import static org.oscim.layers.tile.MapTile.State.READY;
import org.oscim.layers.tile.MapTile.TileNode;
import org.oscim.renderer.BufferObject;
import org.oscim.renderer.GLViewport;
import org.oscim.renderer.LayerRenderer;
import org.oscim.renderer.MapRenderer;
import org.oscim.renderer.bucket.RenderBuckets;
import org.oscim.utils.ScanBox;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class TileRenderer extends LayerRenderer {
static final Logger log = LoggerFactory.getLogger(TileRenderer.class);
/** fade-in time */
protected static final float FADE_TIME = 500;
protected static final int MAX_TILE_LOAD = 8;
private TileManager mTileManager;
protected final TileSet mDrawTiles;
protected int mProxyTileCnt;
private int mOverdraw = 0;
private float mAlpha = 1;
protected int mOverdrawColor;
protected float mLayerAlpha;
private int mUploadSerial;
public TileRenderer() {
mUploadSerial = 0;
mDrawTiles = new TileSet();
}
protected void setTileManager(TileManager tileManager) {
mTileManager = tileManager;
}
/**
* Threadsafe
*/
public synchronized void setOverdrawColor(int color) {
mOverdraw = color;
}
/**
* Threadsafe
*/
public synchronized void setBitmapAlpha(float alpha) {
mAlpha = alpha;
}
/**
* synced with clearTiles, setOverdrawColor and setBitmapAlpha
*/
@Override
public synchronized void update(GLViewport v) {
/* count placeholder tiles */
if (mAlpha == 0) {
mDrawTiles.releaseTiles();
setReady(false);
return;
}
/* keep constant while rendering frame */
mLayerAlpha = mAlpha;
mOverdrawColor = mOverdraw;
/* get current tiles to draw */
synchronized (tilelock) {
boolean tilesChanged = mTileManager.getActiveTiles(mDrawTiles);
if (mDrawTiles.cnt == 0) {
setReady(false);
mProxyTileCnt = 0;
return;
}
/* update isVisible flag true for tiles that intersect view */
if (tilesChanged || v.changed()) {
/* lock tiles while updating isVisible state */
mProxyTileCnt = 0;
MapTile[] tiles = mDrawTiles.tiles;
int tileZoom = tiles[0].zoomLevel;
for (int i = 0; i < mDrawTiles.cnt; i++)
tiles[i].isVisible = false;
/* check visibile tiles */
mScanBox.scan(v.pos.x, v.pos.y, v.pos.scale, tileZoom, v.plane);
}
}
/* prepare tiles for rendering */
if (compileTileLayers(mDrawTiles.tiles, mDrawTiles.cnt + mProxyTileCnt) > 0) {
mUploadSerial++;
BufferObject.checkBufferUsage(false);
}
setReady(true);
}
public void clearTiles() {
synchronized (tilelock) {
/* Clear all references to MapTiles as all current
* tiles will also be removed from TileManager. */
mDrawTiles.releaseTiles();
mDrawTiles.tiles = new MapTile[1];
mDrawTiles.cnt = 0;
}
}
/** compile tile layer data and upload to VBOs */
private static int compileTileLayers(MapTile[] tiles, int tileCnt) {
int uploadCnt = 0;
for (int i = 0; i < tileCnt; i++) {
MapTile tile = tiles[i];
if (!tile.isVisible)
continue;
if (tile.state(READY))
continue;
if (tile.state(NEW_DATA)) {
uploadCnt += uploadTileData(tile);
continue;
}
/* load tile that is referenced by this holder */
MapTile proxy = tile.holder;
if (proxy != null && proxy.state(NEW_DATA)) {
uploadCnt += uploadTileData(proxy);
tile.state = proxy.state;
continue;
}
/* check near relatives than can serve as proxy */
proxy = tile.getProxy(PROXY_PARENT, NEW_DATA);
if (proxy != null) {
uploadCnt += uploadTileData(proxy);
/* dont load child proxies */
continue;
}
for (int c = 0; c < 4; c++) {
proxy = tile.getProxyChild(c, NEW_DATA);
if (proxy != null)
uploadCnt += uploadTileData(proxy);
}
if (uploadCnt >= MAX_TILE_LOAD)
break;
}
return uploadCnt;
}
private static int uploadTileData(MapTile tile) {
tile.setState(READY);
RenderBuckets buckets = tile.getBuckets();
/* tile might only contain label layers */
if (buckets == null)
return 0;
if (!buckets.compile(true)) {
buckets.clear();
return 0;
}
return 1;
}
private final Object tilelock = new Object();
/**
* Update tileSet with currently visible tiles get a TileSet of currently
* visible tiles
*/
public boolean getVisibleTiles(TileSet tileSet) {
if (tileSet == null)
return false;
if (mDrawTiles == null) {
releaseTiles(tileSet);
return false;
}
int prevSerial = tileSet.serial;
/* ensure tiles keep visible state */
synchronized (tilelock) {
MapTile[] newTiles = mDrawTiles.tiles;
int cnt = mDrawTiles.cnt;
/* ensure same size */
if (tileSet.tiles.length != newTiles.length) {
tileSet.tiles = new MapTile[newTiles.length];
}
/* lock tiles to not be removed from cache */
tileSet.cnt = 0;
for (int i = 0; i < cnt; i++) {
MapTile t = newTiles[i];
if (t.isVisible && t.state(READY))
t.lock();
}
/* unlock previous tiles */
tileSet.releaseTiles();
for (int i = 0; i < cnt; i++) {
MapTile t = newTiles[i];
if (t.isVisible && t.state(READY))
tileSet.tiles[tileSet.cnt++] = t;
}
tileSet.serial = mUploadSerial;
}
return prevSerial != tileSet.serial;
}
public void releaseTiles(TileSet tileSet) {
tileSet.releaseTiles();
}
/** scanline fill class used to check tile visibility */
private final ScanBox mScanBox = new ScanBox() {
@Override
protected void setVisible(int y, int x1, int x2) {
MapTile[] tiles = mDrawTiles.tiles;
int proxyOffset = mDrawTiles.cnt;
for (int i = 0; i < proxyOffset; i++) {
MapTile t = tiles[i];
if (t.tileY == y && t.tileX >= x1 && t.tileX < x2)
t.isVisible = true;
}
/* add placeholder tiles to show both sides
* of date line. a little too complicated... */
int xmax = 1 << mZoom;
if (x1 >= 0 && x2 < xmax)
return;
O: for (int x = x1; x < x2; x++) {
if (x >= 0 && x < xmax)
continue;
int xx = x;
if (x < 0)
xx = xmax + x;
else
xx = x - xmax;
if (xx < 0 || xx >= xmax)
continue;
for (int i = proxyOffset; i < proxyOffset + mProxyTileCnt; i++)
if (tiles[i].tileX == x && tiles[i].tileY == y)
continue O;
MapTile tile = null;
for (int i = 0; i < proxyOffset; i++)
if (tiles[i].tileX == xx && tiles[i].tileY == y) {
tile = tiles[i];
break;
}
if (tile == null)
continue;
if (proxyOffset + mProxyTileCnt >= tiles.length) {
//log.error(" + mNumTileHolder");
break;
}
MapTile holder = new MapTile(null, x, y, (byte) mZoom);
holder.isVisible = true;
holder.holder = tile;
holder.state = tile.state;
tile.isVisible = true;
tiles[proxyOffset + mProxyTileCnt++] = holder;
}
}
};
/**
* @param proxyLevel zoom-level of tile relative to current TileSet
*/
public static long getMinFade(MapTile tile, int proxyLevel) {
long minFade = MapRenderer.frametime - 50;
/* check children for grandparent, parent or current */
if (proxyLevel <= 0) {
for (int c = 0; c < 4; c++) {
MapTile ci = tile.node.child(c);
if (ci == null)
continue;
if (ci.fadeTime > 0 && ci.fadeTime < minFade)
minFade = ci.fadeTime;
/* when drawing the parent of the current level
* we also check if the children of current level
* are visible */
if (proxyLevel >= -1) {
long m = getMinFade(ci, proxyLevel - 1);
if (m < minFade)
minFade = m;
}
}
}
/* check parents for child, current or parent */
TileNode p = tile.node.parent;
for (int i = proxyLevel; i >= -1; i--) {
if (p == null)
break;
if (p.item != null && p.item.fadeTime > 0 && p.item.fadeTime < minFade)
minFade = p.item.fadeTime;
p = p.parent;
}
return minFade;
}
}