package org.oscim.layers.tile; import static org.oscim.backend.GLAdapter.gl; import static org.oscim.layers.tile.MapTile.PROXY_GRAMPA; import static org.oscim.layers.tile.MapTile.PROXY_PARENT; import static org.oscim.layers.tile.MapTile.State.READY; import static org.oscim.renderer.MapRenderer.COORD_SCALE; import static org.oscim.renderer.bucket.RenderBucket.BITMAP; import static org.oscim.renderer.bucket.RenderBucket.HAIRLINE; import static org.oscim.renderer.bucket.RenderBucket.LINE; import static org.oscim.renderer.bucket.RenderBucket.MESH; import static org.oscim.renderer.bucket.RenderBucket.POLYGON; import static org.oscim.renderer.bucket.RenderBucket.TEXLINE; import org.oscim.backend.GL; import org.oscim.backend.canvas.Color; import org.oscim.core.MapPosition; import org.oscim.core.Tile; import org.oscim.renderer.GLMatrix; import org.oscim.renderer.GLViewport; import org.oscim.renderer.MapRenderer; import org.oscim.renderer.bucket.BitmapBucket; import org.oscim.renderer.bucket.HairLineBucket; import org.oscim.renderer.bucket.LineBucket; import org.oscim.renderer.bucket.LineTexBucket; import org.oscim.renderer.bucket.MeshBucket; import org.oscim.renderer.bucket.PolygonBucket; import org.oscim.renderer.bucket.RenderBucket; import org.oscim.renderer.bucket.RenderBuckets; import org.oscim.utils.FastMath; public class VectorTileRenderer extends TileRenderer { static final boolean debugOverdraw = false; protected int mClipMode; protected GLMatrix mClipProj = new GLMatrix(); protected GLMatrix mClipMVP = new GLMatrix(); /** * Current number of frames drawn, used to not draw a * tile twice per frame. */ protected int mDrawSerial; @Override public synchronized void render(GLViewport v) { /* discard depth projection from tilt, depth buffer * is used for clipping */ mClipProj.copy(v.proj); mClipProj.setValue(10, 0); mClipProj.setValue(14, 0); mClipProj.multiplyRhs(v.view); mClipMode = PolygonBucket.CLIP_STENCIL; int tileCnt = mDrawTiles.cnt + mProxyTileCnt; MapTile[] tiles = mDrawTiles.tiles; boolean drawProxies = false; mDrawSerial++; for (int i = 0; i < tileCnt; i++) { MapTile t = tiles[i]; if (t.isVisible && !t.state(READY)) { gl.depthMask(true); gl.clear(GL.DEPTH_BUFFER_BIT); /* always write depth for non-proxy tiles * this is used in drawProxies pass to not * draw where tiles were already drawn */ gl.depthFunc(GL.ALWAYS); mClipMode = PolygonBucket.CLIP_DEPTH; drawProxies = true; break; } } /* draw visible tiles */ for (int i = 0; i < tileCnt; i++) { MapTile t = tiles[i]; if (t.isVisible && t.state(READY)) drawTile(t, v, 0); } /* draw parent or children as proxy for visibile tiles that dont * have data yet. Proxies are clipped to the region where nothing * was drawn to depth buffer. * TODO draw proxies for placeholder */ if (!drawProxies) return; /* only draw where no other tile is drawn */ gl.depthFunc(GL.LESS); /* draw child or parent proxies */ boolean preferParent = (v.pos.getZoomScale() < 1.5) || (v.pos.zoomLevel < tiles[0].zoomLevel); if (preferParent) { for (int i = 0; i < tileCnt; i++) { MapTile t = tiles[i]; if ((!t.isVisible) || (t.lastDraw == mDrawSerial)) continue; if (!drawParent(t, v)) drawChildren(t, v); } } else { for (int i = 0; i < tileCnt; i++) { MapTile t = tiles[i]; if ((!t.isVisible) || (t.lastDraw == mDrawSerial)) continue; drawChildren(t, v); } for (int i = 0; i < tileCnt; i++) { MapTile t = tiles[i]; if ((!t.isVisible) || (t.lastDraw == mDrawSerial)) continue; drawParent(t, v); } } /* draw grandparents */ for (int i = 0; i < tileCnt; i++) { MapTile t = tiles[i]; if ((!t.isVisible) || (t.lastDraw == mDrawSerial)) continue; drawGrandParent(t, v); } gl.depthMask(false); /* make sure stencil buffer write is disabled */ //GL.stencilMask(0x00); } private void drawTile(MapTile tile, GLViewport v, int proxyLevel) { /* ensure to draw parents only once */ if (tile.lastDraw == mDrawSerial) return; tile.lastDraw = mDrawSerial; /* use holder proxy when it is set */ RenderBuckets buckets = (tile.holder == null) ? tile.getBuckets() : tile.holder.getBuckets(); if (buckets == null || buckets.vbo == null) { //log.debug("{} no buckets!", tile); return; } MapPosition pos = v.pos; /* place tile relative to map position */ int z = tile.zoomLevel; float div = FastMath.pow(z - pos.zoomLevel); double tileScale = Tile.SIZE * pos.scale; float x = (float) ((tile.x - pos.x) * tileScale); float y = (float) ((tile.y - pos.y) * tileScale); /* scale relative to zoom-level of this tile */ float scale = (float) (pos.scale / (1 << z)); v.mvp.setTransScale(x, y, scale / COORD_SCALE); v.mvp.multiplyLhs(v.viewproj); mClipMVP.setTransScale(x, y, scale / COORD_SCALE); mClipMVP.multiplyLhs(mClipProj); buckets.bind(); PolygonBucket.Renderer.clip(mClipMVP, mClipMode); boolean first = true; for (RenderBucket b = buckets.get(); b != null;) { switch (b.type) { case POLYGON: b = PolygonBucket.Renderer.draw(b, v, div, first); first = false; /* set test for clip to tile region */ gl.stencilFunc(GL.EQUAL, 0x80, 0x80); break; case LINE: b = LineBucket.Renderer.draw(b, v, scale, buckets); break; case TEXLINE: b = LineTexBucket.Renderer.draw(b, v, div, buckets); break; case MESH: b = MeshBucket.Renderer.draw(b, v); break; case HAIRLINE: b = HairLineBucket.Renderer.draw(b, v); break; case BITMAP: b = BitmapBucket.Renderer.draw(b, v, 1, mLayerAlpha); break; default: /* just in case */ log.error("unknown layer {}", b.type); b = b.next; break; } /* make sure buffers are bound again */ buckets.bind(); } if (debugOverdraw) { if (tile.zoomLevel > pos.zoomLevel) PolygonBucket.Renderer.drawOver(mClipMVP, Color.BLUE, 0.5f); else if (tile.zoomLevel < pos.zoomLevel) PolygonBucket.Renderer.drawOver(mClipMVP, Color.RED, 0.5f); else PolygonBucket.Renderer.drawOver(mClipMVP, Color.GREEN, 0.5f); return; } long fadeTime = tile.fadeTime; if (fadeTime == 0) { if (tile.holder == null) { fadeTime = getMinFade(tile, proxyLevel); } else { /* need to use time from original tile */ fadeTime = tile.holder.fadeTime; if (fadeTime == 0) fadeTime = getMinFade(tile.holder, proxyLevel); } tile.fadeTime = fadeTime; } long dTime = MapRenderer.frametime - fadeTime; if (mOverdrawColor == 0 || dTime > FADE_TIME) { PolygonBucket.Renderer.drawOver(mClipMVP, 0, 1); return; } float fade = 1 - dTime / FADE_TIME; PolygonBucket.Renderer.drawOver(mClipMVP, mOverdrawColor, fade * fade); MapRenderer.animate(); } protected boolean drawChildren(MapTile t, GLViewport v) { int drawn = 0; for (int i = 0; i < 4; i++) { MapTile c = t.getProxyChild(i, READY); if (c == null) continue; drawTile(c, v, 1); drawn++; } if (drawn == 4) { t.lastDraw = mDrawSerial; return true; } return false; } protected boolean drawParent(MapTile t, GLViewport v) { MapTile proxy = t.getProxy(PROXY_PARENT, READY); if (proxy != null) { drawTile(proxy, v, -1); t.lastDraw = mDrawSerial; return true; } return false; } protected void drawGrandParent(MapTile t, GLViewport v) { MapTile proxy = t.getProxy(PROXY_GRAMPA, READY); if (proxy != null) { drawTile(proxy, v, -2); t.lastDraw = mDrawSerial; } } }