/* * 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.renderer.bucket; import static org.oscim.backend.GLAdapter.gl; import static org.oscim.renderer.MapRenderer.MAX_INDICES; import static org.oscim.renderer.MapRenderer.bindQuadIndicesVBO; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.ShortBuffer; import org.oscim.backend.GL; import org.oscim.core.GeometryBuffer; import org.oscim.renderer.GLShader; import org.oscim.renderer.GLState; import org.oscim.renderer.GLUtils; import org.oscim.renderer.GLViewport; import org.oscim.renderer.MapRenderer; import org.oscim.theme.styles.LineStyle; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * RenderElement for textured or stippled lines * * Interleave two segment quads in one block to be able to use * vertices twice. pos0 and pos1 use the same vertex array where * pos1 has an offset of one vertex. The vertex shader will use * pos0 when the vertexId is even, pos1 when the Id is odd. * * As there is no gl_VertexId in gles 2.0 an additional 'flip' * array is used. Depending on 'flip' extrusion is inverted. * * Indices and flip buffers can be static. * * <pre> * First pass: using even vertex array positions * (used vertices are in braces) * vertex id 0 1 2 3 4 5 6 7 * pos0 x (0) 1 (2) 3 (4) 5 (6) 7 x * pos1 x (0) 1 (2) 3 (4) 5 (6) 7 x * flip 0 1 0 1 0 1 0 1 * * Second pass: using odd vertex array positions * vertex id 0 1 2 3 4 5 6 7 * pos0 x 0 (1) 2 (3) 4 (5) 6 (7) x * pos1 x 0 (1) 2 (3) 4 (5) 6 (7) x * flip 0 1 0 1 0 1 0 1 * </pre> * * Vertex layout: * [2 short] position, * [2 short] extrusion, * [1 short] line length * [1 short] unused * * indices, for two blocks: * 0, 1, 2, * 2, 1, 3, * 4, 5, 6, * 6, 5, 7, * * BIG NOTE: renderer assumes to be able to offset vertex array position * so that in the first pass 'pos1' offset will be < 0 if no data precedes * - in our case there is always the polygon fill array at start * - see addLine hack otherwise. */ public final class LineTexBucket extends RenderBucket { static final Logger log = LoggerFactory.getLogger(LineTexBucket.class); private static final float COORD_SCALE = MapRenderer.COORD_SCALE; /* scale factor mapping extrusion vector to short values */ public static final float DIR_SCALE = 2048; public LineStyle line; public float width; public int evenQuads; public int oddQuads; private boolean evenSegment; protected boolean mRandomizeOffset = true; LineTexBucket(int level) { super(TEXLINE, false, true); this.level = level; this.evenSegment = true; } public void addLine(GeometryBuffer geom) { addLine(geom.points, geom.index); } public void addLine(float[] points, int[] index) { if (vertexItems.empty()) { /* HACK add one vertex offset when compiling * buffer otherwise one cant use the full * VertexItem (see Layers.compile) * add the two 'x' at front and end */ //numVertices = 2; /* the additional end vertex to make sure * not to read outside allocated memory */ numVertices = 1; } VertexData vi = vertexItems; boolean even = evenSegment; /* reset offset to last written position */ if (!even) vi.seek(-12); int n; int length = 0; if (index == null) { n = 1; length = points.length; } else { n = index.length; } for (int i = 0, pos = 0; i < n; i++) { if (index != null) length = index[i]; /* check end-marker in indices */ if (length < 0) break; /* need at least two points */ if (length < 4) { pos += length; continue; } int end = pos + length; float x = points[pos++] * COORD_SCALE; float y = points[pos++] * COORD_SCALE; /* randomize a bit */ float lineLength = mRandomizeOffset ? (x * x + y * y) % 80 : 0; while (pos < end) { float nx = points[pos++] * COORD_SCALE; float ny = points[pos++] * COORD_SCALE; /* Calculate triangle corners for the given width */ float vx = nx - x; float vy = ny - y; float a = (float) Math.sqrt(vx * vx + vy * vy); /* normal vector */ vx /= a; vy /= a; /* perpendicular to line segment */ float ux = -vy; float uy = vx; short dx = (short) (ux * DIR_SCALE); short dy = (short) (uy * DIR_SCALE); vi.add((short) x, (short) y, dx, dy, (short) lineLength, (short) 0); lineLength += a; vi.seek(6); vi.add((short) nx, (short) ny, dx, dy, (short) lineLength, (short) 0); x = nx; y = ny; if (even) { /* go to second segment */ vi.seek(-12); even = false; /* vertex 0 and 2 were added */ numVertices += 3; evenQuads++; } else { /* go to next block */ even = true; /* vertex 1 and 3 were added */ numVertices += 1; oddQuads++; } } } evenSegment = even; /* advance offset to last written position */ if (!even) vi.seek(12); } @Override protected void compile(ShortBuffer vboData, ShortBuffer iboData) { compileVertexItems(vboData); /* add additional vertex for interleaving, see TexLineLayer. */ vboData.position(vboData.position() + 6); } static class Shader extends GLShader { int uMVP, uColor, uWidth, uBgColor, uScale; int uPatternWidth, uPatternScale; int aPos0, aPos1, aLen0, aLen1, aFlip; Shader(String shaderFile) { if (!create(shaderFile)) return; uMVP = getUniform("u_mvp"); uScale = getUniform("u_scale"); uColor = getUniform("u_color"); uWidth = getUniform("u_width"); uBgColor = getUniform("u_bgcolor"); uPatternWidth = getUniform("u_pwidth"); uPatternScale = getUniform("u_pscale"); aPos0 = getAttrib("a_pos0"); aPos1 = getAttrib("a_pos1"); aLen0 = getAttrib("a_len0"); aLen1 = getAttrib("a_len1"); aFlip = getAttrib("a_flip"); } } public final static class Renderer { private static Shader shader; /* factor to normalize extrusion vector and scale to coord scale */ private final static float COORD_SCALE_BY_DIR_SCALE = MapRenderer.COORD_SCALE / LineBucket.DIR_SCALE; private static int mVertexFlipID; public static void init() { shader = new Shader("linetex_layer"); int[] vboIds = GLUtils.glGenBuffers(1); mVertexFlipID = vboIds[0]; /* bytes: 0, 1, 0, 1, 0, ... */ byte[] flip = new byte[MapRenderer.MAX_QUADS * 4]; for (int i = 0; i < flip.length; i++) flip[i] = (byte) (i % 2); ByteBuffer buf = ByteBuffer.allocateDirect(flip.length) .order(ByteOrder.nativeOrder()); buf.put(flip); buf.flip(); ShortBuffer sbuf = buf.asShortBuffer(); //GL.bindBuffer(GL20.ARRAY_BUFFER, mVertexFlipID); GLState.bindVertexBuffer(mVertexFlipID); gl.bufferData(GL.ARRAY_BUFFER, flip.length, sbuf, GL.STATIC_DRAW); GLState.bindVertexBuffer(0); // mTexID = new int[10]; // byte[] stipple = new byte[2]; // stipple[0] = 32; // stipple[1] = 32; // mTexID[0] = GlUtils.loadStippleTexture(stipple); } private final static int STRIDE = 12; private final static int LEN_OFFSET = 8; public static RenderBucket draw(RenderBucket b, GLViewport v, float div, RenderBuckets buckets) { //if (shader == 0) // return curLayer.next; GLState.blend(true); //GLState.useProgram(shader); shader.useProgram(); GLState.enableVertexArrays(-1, -1); int aLen0 = shader.aLen0; int aLen1 = shader.aLen1; int aPos0 = shader.aPos0; int aPos1 = shader.aPos1; int aFlip = shader.aFlip; gl.enableVertexAttribArray(aPos0); gl.enableVertexAttribArray(aPos1); gl.enableVertexAttribArray(aLen0); gl.enableVertexAttribArray(aLen1); gl.enableVertexAttribArray(aFlip); v.mvp.setAsUniform(shader.uMVP); bindQuadIndicesVBO(); GLState.bindVertexBuffer(mVertexFlipID); gl.vertexAttribPointer(shader.aFlip, 1, GL.BYTE, false, 0, 0); buckets.vbo.bind(); float scale = (float) v.pos.getZoomScale(); float s = scale / div; //GL.bindTexture(GL20.TEXTURE_2D, mTexID[0]); for (; b != null && b.type == TEXLINE; b = b.next) { LineTexBucket lb = (LineTexBucket) b; LineStyle line = lb.line.current(); GLUtils.setColor(shader.uColor, line.stippleColor, 1); GLUtils.setColor(shader.uBgColor, line.color, 1); float pScale = (int) (s + 0.5f); if (pScale < 1) pScale = 1; gl.uniform1f(shader.uPatternScale, (MapRenderer.COORD_SCALE * line.stipple) / pScale); gl.uniform1f(shader.uPatternWidth, line.stippleWidth); //GL.uniform1f(hScale, scale); /* keep line width fixed */ gl.uniform1f(shader.uWidth, lb.width / s * COORD_SCALE_BY_DIR_SCALE); /* add offset vertex */ int vOffset = -STRIDE; // TODO interleave 1. and 2. pass to improve vertex cache usage? /* first pass */ int allIndices = (lb.evenQuads * 6); for (int i = 0; i < allIndices; i += MAX_INDICES) { int numIndices = allIndices - i; if (numIndices > MAX_INDICES) numIndices = MAX_INDICES; /* i / 6 * (24 shorts per block * 2 short bytes) */ int add = (b.vertexOffset + i * 8) + vOffset; gl.vertexAttribPointer(aPos0, 4, GL.SHORT, false, STRIDE, add + STRIDE); gl.vertexAttribPointer(aLen0, 2, GL.SHORT, false, STRIDE, add + STRIDE + LEN_OFFSET); gl.vertexAttribPointer(aPos1, 4, GL.SHORT, false, STRIDE, add); gl.vertexAttribPointer(aLen1, 2, GL.SHORT, false, STRIDE, add + LEN_OFFSET); gl.drawElements(GL.TRIANGLES, numIndices, GL.UNSIGNED_SHORT, 0); } /* second pass */ allIndices = (lb.oddQuads * 6); for (int i = 0; i < allIndices; i += MAX_INDICES) { int numIndices = allIndices - i; if (numIndices > MAX_INDICES) numIndices = MAX_INDICES; /* i / 6 * (24 shorts per block * 2 short bytes) */ int add = (b.vertexOffset + i * 8) + vOffset; gl.vertexAttribPointer(aPos0, 4, GL.SHORT, false, STRIDE, add + 2 * STRIDE); gl.vertexAttribPointer(aLen0, 2, GL.SHORT, false, STRIDE, add + 2 * STRIDE + LEN_OFFSET); gl.vertexAttribPointer(aPos1, 4, GL.SHORT, false, STRIDE, add + STRIDE); gl.vertexAttribPointer(aLen1, 2, GL.SHORT, false, STRIDE, add + STRIDE + LEN_OFFSET); gl.drawElements(GL.TRIANGLES, numIndices, GL.UNSIGNED_SHORT, 0); } //GlUtils.checkGlError(TAG); } gl.disableVertexAttribArray(aPos0); gl.disableVertexAttribArray(aPos1); gl.disableVertexAttribArray(aLen0); gl.disableVertexAttribArray(aLen1); gl.disableVertexAttribArray(aFlip); //GL.bindTexture(GL20.TEXTURE_2D, 0); return b; } } }