/*
* 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.COORD_SCALE;
import org.oscim.backend.GL;
import org.oscim.backend.canvas.Color;
import org.oscim.core.GeometryBuffer;
import org.oscim.core.MapPosition;
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.renderer.MapRenderer;
import org.oscim.renderer.bucket.VertexData.Chunk;
import org.oscim.theme.styles.AreaStyle;
import org.oscim.utils.ColorUtil;
import org.oscim.utils.TessJNI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MeshBucket extends RenderBucket {
static final Logger log = LoggerFactory.getLogger(MeshBucket.class);
static final boolean dbgRender = false;
public AreaStyle area;
public float heightOffset;
private TessJNI tess;
private int numPoints;
public MeshBucket(int level) {
super(RenderBucket.MESH, true, false);
this.level = level;
}
public void addMesh(GeometryBuffer geom) {
numPoints += geom.pointPos;
if (tess == null)
tess = new TessJNI(8);
tess.addContour2D(geom.index, geom.points);
}
public void addConvexMesh(GeometryBuffer geom) {
short start = (short) numVertices;
if (numVertices >= (1 << 16)) {
return;
}
vertexItems.add(geom.points[0] * COORD_SCALE,
geom.points[1] * COORD_SCALE);
vertexItems.add(geom.points[2] * COORD_SCALE,
geom.points[3] * COORD_SCALE);
short prev = (short) (start + 1);
numVertices += 2;
for (int i = 4; i < geom.index[0]; i += 2) {
vertexItems.add(geom.points[i + 0] * COORD_SCALE,
geom.points[i + 1] * COORD_SCALE);
indiceItems.add(start, prev, ++prev);
numVertices++;
numIndices += 3;
}
//numPoints += geom.pointPos;
//tess.addContour2D(geom.index, geom.points);
}
protected void prepare() {
if (tess == null)
return;
if (numPoints == 0) {
tess.dispose();
return;
}
if (!tess.tesselate()) {
tess.dispose();
log.error("error in tessellation {}", numPoints);
return;
}
int nelems = tess.getElementCount() * 3;
//int startVertex = vertexItems.countSize();
for (int offset = indiceItems.countSize(); offset < nelems;) {
int size = nelems - offset;
if (size > VertexData.SIZE)
size = VertexData.SIZE;
Chunk chunk = indiceItems.obtainChunk();
tess.getElements(chunk.vertices, offset, size);
offset += size;
//if (startVertex != 0)
// FIXME
indiceItems.releaseChunk(size);
}
int nverts = tess.getVertexCount() * 2;
for (int offset = 0; offset < nverts;) {
int size = nverts - offset;
if (size > VertexData.SIZE)
size = VertexData.SIZE;
Chunk chunk = vertexItems.obtainChunk();
tess.getVertices(chunk.vertices, offset, size,
MapRenderer.COORD_SCALE);
offset += size;
vertexItems.releaseChunk(size);
}
this.numIndices += nelems;
this.numVertices += nverts >> 1;
tess.dispose();
}
public static class Renderer {
static Shader shader;
static boolean init() {
shader = new Shader("mesh_layer_2D");
return true;
}
static class Shader extends GLShader {
int uMVP, uColor, uHeight, aPos;
Shader(String shaderFile) {
if (!create(shaderFile))
return;
uMVP = getUniform("u_mvp");
uColor = getUniform("u_color");
uHeight = getUniform("u_height");
aPos = getAttrib("a_pos");
}
}
public static RenderBucket draw(RenderBucket l, GLViewport v) {
GLState.blend(true);
Shader s = shader;
s.useProgram();
GLState.enableVertexArrays(s.aPos, -1);
v.mvp.setAsUniform(s.uMVP);
float heightOffset = 0;
gl.uniform1f(s.uHeight, heightOffset);
for (; l != null && l.type == MESH; l = l.next) {
MeshBucket ml = (MeshBucket) l;
if (ml.heightOffset != heightOffset) {
heightOffset = ml.heightOffset;
gl.uniform1f(s.uHeight, heightOffset /
MercatorProjection.groundResolution(v.pos));
}
if (ml.area == null)
GLUtils.setColor(s.uColor, Color.BLUE, 0.4f);
else {
setColor(ml.area.current(), s, v.pos);
}
gl.vertexAttribPointer(s.aPos, 2, GL.SHORT,
false, 0, ml.vertexOffset);
gl.drawElements(GL.TRIANGLES,
ml.numIndices,
GL.UNSIGNED_SHORT,
ml.indiceOffset);
if (dbgRender) {
int c = (ml.area == null) ? Color.BLUE : ml.area.color;
gl.lineWidth(1);
//c = ColorUtil.shiftHue(c, 0.5);
c = ColorUtil.modHsv(c, 1.1, 1.0, 0.8, true);
GLUtils.setColor(s.uColor, c, 1);
gl.drawElements(GL.LINES,
ml.numIndices,
GL.UNSIGNED_SHORT,
ml.vertexOffset);
}
}
return l;
}
private static final int OPAQUE = 0xff000000;
static void setColor(AreaStyle a, Shader s, MapPosition pos) {
float fade = a.getFade(pos.scale);
float blend = a.getBlend(pos.scale);
if (fade < 1.0f) {
GLState.blend(true);
GLUtils.setColor(s.uColor, a.color, fade);
} else if (blend > 0.0f) {
if (blend == 1.0f)
GLUtils.setColor(s.uColor, a.blendColor, 1);
else
GLUtils.setColorBlend(s.uColor, a.color,
a.blendColor, blend);
} else {
/* test if color contains alpha */
GLState.blend((a.color & OPAQUE) != OPAQUE);
GLUtils.setColor(s.uColor, a.color, 1);
}
}
}
}