org.oscim.renderer.bucket.LineBucket Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of vtm Show documentation
Show all versions of vtm Show documentation
OpenGL vector map library written in Java - running on Android, iOS, Desktop and within the browser.
/*
* Copyright 2013 Hannes Janetzek
* Copyright 2016-2019 devemux86
*
* 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 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 .
*/
package org.oscim.renderer.bucket;
import org.oscim.backend.GL;
import org.oscim.backend.GLAdapter;
import org.oscim.backend.canvas.Paint.Cap;
import org.oscim.core.GeometryBuffer;
import org.oscim.core.MercatorProjection;
import org.oscim.renderer.GLShader;
import org.oscim.renderer.GLState;
import org.oscim.renderer.GLUtils;
import org.oscim.renderer.GLViewport;
import org.oscim.theme.styles.LineStyle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.oscim.backend.GLAdapter.gl;
import static org.oscim.renderer.MapRenderer.COORD_SCALE;
/**
* Note:
* Coordinates must be in range +/- (Short.MAX_VALUE / COORD_SCALE) if using GL.SHORT.
* The maximum resolution for coordinates is 0.25 as points will be converted
* to fixed point values.
*/
public class LineBucket extends RenderBucket {
static final Logger log = LoggerFactory.getLogger(LineBucket.class);
/**
* scale factor mapping extrusion vector to short values
*/
public static final float DIR_SCALE = 2048;
/**
* maximal resolution
*/
private static final float MIN_DIST = 1 / 8f;
/**
* not quite right.. need to go back so that additional
* bevel vertices are at least MIN_DIST apart
*/
private static final float BEVEL_MIN = MIN_DIST * 4;
/**
* 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 LineBucket outlines;
public LineStyle line;
public float scale = 1;
public boolean roundCap;
private float mMinDist = MIN_DIST;
public float heightOffset;
private int tmin = Integer.MIN_VALUE, tmax = Integer.MAX_VALUE;
public LineBucket(int layer) {
super(RenderBucket.LINE, false, false);
this.level = layer;
}
LineBucket(byte type, boolean indexed, boolean quads) {
super(type, indexed, quads);
}
public void addOutline(LineBucket link) {
for (LineBucket l = outlines; l != null; l = l.outlines)
if (link == l)
return;
link.outlines = outlines;
outlines = link;
}
public void setExtents(int min, int max) {
tmin = min;
tmax = max;
}
/**
* For point reduction by minimal distance. Default is 1/8.
*/
public void setDropDistance(float minDist) {
mMinDist = Math.max(minDist, MIN_DIST);
}
public void addLine(GeometryBuffer geom) {
if (geom.isPoly())
addLine(geom.points, geom.index, -1, true);
else if (geom.isLine())
addLine(geom.points, geom.index, -1, false);
else
log.debug("geometry must be LINE or POLYGON");
}
public void addLine(float[] points, int numPoints, boolean closed) {
if (numPoints >= 4)
addLine(points, null, numPoints, closed);
}
void addLine(float[] points, int[] index, int numPoints, boolean closed) {
boolean rounded = false;
boolean squared = false;
if (line.cap == Cap.ROUND)
rounded = true;
else if (line.cap == Cap.SQUARE)
squared = true;
/* Note: just a hack to save some vertices, when there are
* more than 200 lines per type. FIXME make optional! */
if (rounded && index != null) {
int cnt = 0;
for (int i = 0, n = index.length; i < n; i++, cnt++) {
if (index[i] < 0)
break;
if (cnt > 400) {
rounded = false;
break;
}
}
}
roundCap = rounded;
int n;
int length = 0;
if (index == null) {
n = 1;
if (numPoints > 0) {
length = numPoints;
} else {
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;
int ipos = pos;
pos += length;
/* need at least two points */
if (length < 4)
continue;
/* start an enpoint are equal */
if (length == 4 &&
points[ipos] == points[ipos + 2] &&
points[ipos + 1] == points[ipos + 3])
continue;
/* avoid simple 180 degree angles */
if (length == 6 &&
points[ipos] == points[ipos + 4] &&
points[ipos + 1] == points[ipos + 5])
length -= 2;
addLine(vertexItems, points, ipos, length, rounded, squared, closed);
}
}
private void addVertex(VertexData vi,
float x, float y,
float vNextX, float vNextY,
float vPrevX, float vPrevY) {
float ux = vNextX + vPrevX;
float uy = vNextY + vPrevY;
/* vPrev times perpendicular of sum(vNext, vPrev) */
double a = uy * vPrevX - ux * vPrevY;
if (a < 0.01 && a > -0.01) {
ux = -vPrevY;
uy = vPrevX;
} else {
ux /= a;
uy /= a;
}
short ox = (short) (x * COORD_SCALE);
short oy = (short) (y * COORD_SCALE);
int ddx = (int) (ux * DIR_SCALE);
int ddy = (int) (uy * DIR_SCALE);
vi.add(ox, oy,
(short) (0 | ddx & DIR_MASK),
(short) (1 | ddy & DIR_MASK));
vi.add(ox, oy,
(short) (2 | -ddx & DIR_MASK),
(short) (1 | -ddy & DIR_MASK));
}
private void addLine(VertexData vertices, float[] points, int start, int length,
boolean rounded, boolean squared, boolean closed) {
float ux, uy;
float vPrevX, vPrevY;
float vNextX, vNextY;
float curX, curY;
float nextX, nextY;
double a;
/* amount of vertices used
* + 2 for drawing triangle-strip
* + 4 for round caps
* + 2 for closing polygons */
numVertices += length + (rounded ? 6 : 2) + (closed ? 2 : 0);
int ipos = start;
curX = points[ipos++];
curY = points[ipos++];
nextX = points[ipos++];
nextY = points[ipos++];
/* Unit vector to next node */
vPrevX = nextX - curX;
vPrevY = nextY - curY;
a = (float) Math.sqrt(vPrevX * vPrevX + vPrevY * vPrevY);
vPrevX /= a;
vPrevY /= a;
/* perpendicular on the first segment */
ux = -vPrevY;
uy = vPrevX;
int ddx, ddy;
/* vertex point coordinate */
short ox = (short) (curX * COORD_SCALE);
short oy = (short) (curY * COORD_SCALE);
/* vertex extrusion vector, last two bit
* encode texture coord. */
short dx, dy;
/* when the endpoint is outside the tile region omit round caps. */
boolean outside = (curX < tmin || curX > tmax || curY < tmin || curY > tmax);
if (rounded && !outside) {
ddx = (int) ((ux - vPrevX) * DIR_SCALE);
ddy = (int) ((uy - vPrevY) * DIR_SCALE);
dx = (short) (0 | ddx & DIR_MASK);
dy = (short) (2 | ddy & DIR_MASK);
vertices.add(ox, oy, (short) dx, (short) dy);
vertices.add(ox, oy, (short) dx, (short) dy);
ddx = (int) (-(ux + vPrevX) * DIR_SCALE);
ddy = (int) (-(uy + vPrevY) * DIR_SCALE);
vertices.add(ox, oy,
(short) (2 | ddx & DIR_MASK),
(short) (2 | ddy & DIR_MASK));
/* Start of line */
ddx = (int) (ux * DIR_SCALE);
ddy = (int) (uy * DIR_SCALE);
vertices.add(ox, oy,
(short) (0 | ddx & DIR_MASK),
(short) (1 | ddy & DIR_MASK));
vertices.add(ox, oy,
(short) (2 | -ddx & DIR_MASK),
(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 */
float tx = vPrevX;
float ty = vPrevY;
if (!rounded && !squared) {
tx = 0;
ty = 0;
} else if (rounded) {
tx *= 0.5;
ty *= 0.5;
}
if (rounded)
numVertices -= 2;
/* add first vertex twice */
ddx = (int) ((ux - tx) * DIR_SCALE);
ddy = (int) ((uy - ty) * DIR_SCALE);
dx = (short) (0 | ddx & DIR_MASK);
dy = (short) (1 | ddy & DIR_MASK);
vertices.add(ox, oy, (short) dx, (short) dy);
vertices.add(ox, oy, (short) dx, (short) dy);
ddx = (int) (-(ux + tx) * DIR_SCALE);
ddy = (int) (-(uy + ty) * DIR_SCALE);
vertices.add(ox, oy,
(short) (2 | ddx & DIR_MASK),
(short) (1 | ddy & DIR_MASK));
}
curX = nextX;
curY = nextY;
/* Unit vector pointing back to previous node */
vPrevX *= -1;
vPrevY *= -1;
// vertexItem.used = opos + 4;
for (int end = start + length; ; ) {
if (ipos < end) {
nextX = points[ipos++];
nextY = points[ipos++];
} else if (closed && ipos < end + 2) {
/* add startpoint == endpoint */
nextX = points[start];
nextY = points[start + 1];
ipos += 2;
} else
break;
/* unit vector pointing forward to next node */
vNextX = nextX - curX;
vNextY = nextY - curY;
a = Math.sqrt(vNextX * vNextX + vNextY * vNextY);
/* skip two vertex segments */
if (a < mMinDist) {
numVertices -= 2;
continue;
}
vNextX /= a;
vNextY /= a;
double dotp = (vNextX * vPrevX + vNextY * vPrevY);
//log.debug("acos " + dotp);
if (dotp > 0.65) {
/* add bevel join to avoid miter going to infinity */
numVertices += 2;
//dotp = FastMath.clamp(dotp, -1, 1);
//double cos = Math.acos(dotp);
//log.debug("cos " + Math.toDegrees(cos));
//log.debug("back " + (mMinDist * 2 / Math.sin(cos + Math.PI / 2)));
float px, py;
if (dotp > 0.999) {
/* 360 degree angle, set points aside */
ux = vPrevX + vNextX;
uy = vPrevY + vNextY;
a = vNextX * uy - vNextY * ux;
if (a < 0.1 && a > -0.1) {
/* Almost straight */
ux = -vNextY;
uy = vNextX;
} else {
ux /= a;
uy /= a;
}
//log.debug("aside " + a + " " + ux + " " + uy);
px = curX - ux * BEVEL_MIN;
py = curY - uy * BEVEL_MIN;
curX = curX + ux * BEVEL_MIN;
curY = curY + uy * BEVEL_MIN;
} else {
//log.debug("back");
/* go back by min dist */
px = curX + vPrevX * BEVEL_MIN;
py = curY + vPrevY * BEVEL_MIN;
/* go forward by min dist */
curX = curX + vNextX * BEVEL_MIN;
curY = curY + vNextY * BEVEL_MIN;
}
/* unit vector pointing forward to next node */
vNextX = curX - px;
vNextY = curY - py;
a = Math.sqrt(vNextX * vNextX + vNextY * vNextY);
vNextX /= a;
vNextY /= a;
addVertex(vertices, px, py, vPrevX, vPrevY, vNextX, vNextY);
/* flip unit vector to point back */
vPrevX = -vNextX;
vPrevY = -vNextY;
/* unit vector pointing forward to next node */
vNextX = nextX - curX;
vNextY = nextY - curY;
a = Math.sqrt(vNextX * vNextX + vNextY * vNextY);
vNextX /= a;
vNextY /= a;
}
addVertex(vertices, curX, curY, vPrevX, vPrevY, vNextX, vNextY);
curX = nextX;
curY = nextY;
/* flip vector to point back */
vPrevX = -vNextX;
vPrevY = -vNextY;
}
ux = vPrevY;
uy = -vPrevX;
outside = (curX < tmin || curX > tmax || curY < tmin || curY > tmax);
ox = (short) (curX * COORD_SCALE);
oy = (short) (curY * COORD_SCALE);
if (rounded && !outside) {
ddx = (int) (ux * DIR_SCALE);
ddy = (int) (uy * DIR_SCALE);
vertices.add(ox, oy,
(short) (0 | ddx & DIR_MASK),
(short) (1 | ddy & DIR_MASK));
vertices.add(ox, oy,
(short) (2 | -ddx & DIR_MASK),
(short) (1 | -ddy & DIR_MASK));
/* For rounded line edges */
ddx = (int) ((ux - vPrevX) * DIR_SCALE);
ddy = (int) ((uy - vPrevY) * DIR_SCALE);
vertices.add(ox, oy,
(short) (0 | ddx & DIR_MASK),
(short) (0 | ddy & DIR_MASK));
/* last vertex */
ddx = (int) (-(ux + vPrevX) * DIR_SCALE);
ddy = (int) (-(uy + vPrevY) * DIR_SCALE);
dx = (short) (2 | ddx & DIR_MASK);
dy = (short) (0 | ddy & DIR_MASK);
} else {
if (!rounded && !squared) {
vPrevX = 0;
vPrevY = 0;
} else if (rounded) {
vPrevX *= 0.5;
vPrevY *= 0.5;
}
if (rounded)
numVertices -= 2;
ddx = (int) ((ux - vPrevX) * DIR_SCALE);
ddy = (int) ((uy - vPrevY) * DIR_SCALE);
vertices.add(ox, oy,
(short) (0 | ddx & DIR_MASK),
(short) (1 | ddy & DIR_MASK));
/* last vertex */
ddx = (int) (-(ux + vPrevX) * DIR_SCALE);
ddy = (int) (-(uy + vPrevY) * DIR_SCALE);
dx = (short) (2 | ddx & DIR_MASK);
dy = (short) (1 | ddy & DIR_MASK);
}
/* add last vertex twice */
vertices.add(ox, oy, (short) dx, (short) dy);
vertices.add(ox, oy, (short) dx, (short) dy);
}
static class Shader extends GLShader {
int uMVP, uFade, uWidth, uColor, uMode, uHeight, aPos;
Shader(String shaderFile) {
if (!create(shaderFile))
return;
uMVP = getUniform("u_mvp");
uFade = getUniform("u_fade");
uWidth = getUniform("u_width");
uColor = getUniform("u_color");
uMode = getUniform("u_mode");
uHeight = getUniform("u_height");
aPos = getAttrib("a_pos");
}
@Override
public boolean useProgram() {
if (super.useProgram()) {
GLState.enableVertexArrays(aPos, GLState.DISABLED);
return true;
}
return false;
}
}
public static final class Renderer {
/* TODO:
* http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter22.html */
/* factor to normalize extrusion vector and scale to coord scale */
private static final float COORD_SCALE_BY_DIR_SCALE =
COORD_SCALE / LineBucket.DIR_SCALE;
private static final int CAP_THIN = 0;
private static final int CAP_BUTT = 1;
private static final int CAP_ROUND = 2;
private static final int SHADER_FLAT = 1;
private static final int SHADER_PROJ = 0;
public static int mTexID;
private static Shader[] shaders = {null, null};
static boolean init() {
shaders[0] = new Shader("line_aa_proj");
shaders[1] = new Shader("line_aa");
/* create lookup table as texture for 'length(0..1,0..1)'
* using mirrored wrap mode for 'length(-1..1,-1..1)' */
byte[] pixel = new byte[128 * 128];
for (int x = 0; x < 128; x++) {
float xx = x * x;
for (int y = 0; y < 128; y++) {
float yy = y * y;
int color = (int) (Math.sqrt(xx + yy) * 2);
if (color > 255)
color = 255;
pixel[x + y * 128] = (byte) color;
}
}
mTexID = GLUtils.loadTexture(pixel, 128, 128, GL.ALPHA,
GL.NEAREST, GL.NEAREST,
GL.MIRRORED_REPEAT,
GL.MIRRORED_REPEAT);
return true;
}
public static RenderBucket draw(RenderBucket b, GLViewport v,
float scale, RenderBuckets buckets) {
/* simple line shader does not take forward shortening into
* account. only used when tilt is 0. */
int mode = v.pos.tilt < 1 ? 1 : 0;
Shader s = shaders[mode];
s.useProgram();
GLState.blend(true);
/* Somehow we loose the texture after an indefinite
* time, when label/symbol textures are used.
* Debugging gl on Desktop is most fun imaginable,
* so for now: */
if (!GLAdapter.GDX_DESKTOP_QUIRKS)
GLState.bindTex2D(mTexID);
int uLineFade = s.uFade;
int uLineMode = s.uMode;
int uLineColor = s.uColor;
int uLineWidth = s.uWidth;
int uLineHeight = s.uHeight;
gl.vertexAttribPointer(s.aPos, 4, GL.SHORT, false, 0,
buckets.offset[LINE]);
v.mvp.setAsUniform(s.uMVP);
/* Line scale factor for non fixed lines: Within a zoom-
* level lines would be scaled by the factor 2 by view-matrix.
* Though lines should only scale by sqrt(2). This is achieved
* by inverting scaling of extrusion vector with: width/sqrt(s). */
double variableScale = Math.sqrt(scale);
/* scale factor to map one pixel on tile to one pixel on screen:
* used with orthographic projection, (shader mode == 1) */
double pixel = (mode == SHADER_PROJ) ? 0.0001 : 1.5 / scale;
gl.uniform1f(uLineFade, (float) pixel);
int capMode = 0;
gl.uniform1i(uLineMode, capMode);
boolean blur = false;
double width;
float heightOffset = 0;
gl.uniform1f(uLineHeight, heightOffset);
// if (1 == 1)
// return b.next;
//
for (; b != null && b.type == RenderBucket.LINE; b = b.next) {
LineBucket lb = (LineBucket) b;
LineStyle line = lb.line.current();
if (line.heightOffset != lb.heightOffset)
lb.heightOffset = line.heightOffset;
if (lb.heightOffset != heightOffset) {
heightOffset = lb.heightOffset;
gl.uniform1f(uLineHeight, (float) (heightOffset / MercatorProjection.groundResolution(v.pos)));
}
if (line.fadeScale < v.pos.zoomLevel) {
GLUtils.setColor(uLineColor, line.color, 1);
} else if (line.fadeScale > v.pos.zoomLevel) {
continue;
} else {
float alpha = (float) (scale > 1.2 ? scale : 1.2) - 1;
GLUtils.setColor(uLineColor, line.color, alpha);
}
if (mode == SHADER_PROJ && blur && line.blur == 0) {
gl.uniform1f(uLineFade, (float) pixel);
blur = false;
}
/* draw LineLayer */
if (!line.outline) {
/* invert scaling of extrusion vectors so that line
* width stays the same. */
if (line.fixed) {
width = Math.max(line.width, 1) / scale;
} else {
width = lb.scale * line.width / variableScale;
}
gl.uniform1f(uLineWidth,
(float) (width * COORD_SCALE_BY_DIR_SCALE));
/* Line-edge fade */
if (line.blur > 0) {
gl.uniform1f(uLineFade, line.blur);
blur = true;
} else if (mode == SHADER_FLAT) {
gl.uniform1f(uLineFade, (float) (pixel / width));
//GL.uniform1f(uLineScale, (float)(pixel / (ll.width / s)));
}
/* Cap mode */
if (lb.scale < 1.0) {
if (capMode != CAP_THIN) {
capMode = CAP_THIN;
gl.uniform1i(uLineMode, capMode);
}
} else if (lb.roundCap) {
if (capMode != CAP_ROUND) {
capMode = CAP_ROUND;
gl.uniform1i(uLineMode, capMode);
}
} else if (capMode != CAP_BUTT) {
capMode = CAP_BUTT;
gl.uniform1i(uLineMode, capMode);
}
gl.drawArrays(GL.TRIANGLE_STRIP,
b.vertexOffset, b.numVertices);
continue;
}
/* draw LineLayers references by this outline */
for (LineBucket ref = lb.outlines; ref != null; ref = ref.outlines) {
LineStyle core = ref.line.current();
// core width
if (core.fixed) {
width = Math.max(core.width, 1) / scale;
} else {
width = ref.scale * core.width / variableScale;
}
// add outline width
if (line.fixed) {
width += line.width / scale;
} else {
width += lb.scale * line.width / variableScale;
}
gl.uniform1f(uLineWidth,
(float) (width * COORD_SCALE_BY_DIR_SCALE));
/* Line-edge fade */
if (line.blur > 0) {
gl.uniform1f(uLineFade, line.blur);
blur = true;
} else if (mode == SHADER_FLAT) {
gl.uniform1f(uLineFade, (float) (pixel / width));
}
/* Cap mode */
if (ref.roundCap) {
if (capMode != CAP_ROUND) {
capMode = CAP_ROUND;
gl.uniform1i(uLineMode, capMode);
}
} else if (capMode != CAP_BUTT) {
capMode = CAP_BUTT;
gl.uniform1i(uLineMode, capMode);
}
gl.drawArrays(GL.TRIANGLE_STRIP,
ref.vertexOffset, ref.numVertices);
}
}
return b;
}
}
}