vtm/src/org/oscim/renderer/layer/ExtrusionLayer.java
2013-10-09 01:55:57 +02:00

473 lines
12 KiB
Java

/*
* Copyright 2012, 2013 Hannes Janetzek
*
* 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.layer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ShortBuffer;
import org.oscim.core.Tile;
import org.oscim.renderer.BufferObject;
import org.oscim.renderer.GLRenderer;
import org.oscim.utils.LineClipper;
import org.oscim.view.MapView;
import android.opengl.GLES20;
import android.util.Log;
/**
* @author Hannes Janetzek
* FIXME check if polygon has self intersections or 0/180 degree
* angles! or bad things might happen in Triangle
*/
public class ExtrusionLayer extends Layer {
private final static String TAG = ExtrusionLayer.class.getName();
private static final float S = GLRenderer.COORD_SCALE;
private final VertexPoolItem mVertices;
private VertexPoolItem mCurVertices;
private final VertexPoolItem mIndices[], mCurIndices[];
private LineClipper mClipper;
// indices for:
// 0. even sides, 1. odd sides, 2. roof, 3. roof outline
public int mIndiceCnt[] = { 0, 0, 0, 0 };
public int mNumIndices = 0;
public int mNumVertices = 0;
public int mIndicesBufferID;
public int mVertexBufferID;
private BufferObject mIndiceBO;
private BufferObject mVertexBO;
//private final static int IND_EVEN_SIDE = 0;
//private final static int IND_ODD_SIDE = 1;
private final static int IND_ROOF = 2;
private final static int IND_OUTLINE = 3;
public boolean compiled = false;
//private int[] mVboIds;
public ExtrusionLayer(int level) {
this.type = Layer.EXTRUSION;
this.level = level;
mVertices = mCurVertices = VertexPool.get();
mIndices = new VertexPoolItem[4];
mCurIndices = new VertexPoolItem[4];
for (int i = 0; i < 4; i++)
mIndices[i] = mCurIndices[i] = VertexPool.get();
mClipper = new LineClipper(0, 0, Tile.TILE_SIZE, Tile.TILE_SIZE);
}
public void addBuildings(float[] points, short[] index, int height) {
// start outer ring
int outer = 0;
boolean simple = true;
int startVertex = mNumVertices;
// just a guessing to make it look ok
if (height == 0)
height = 10;
height = (int) (height * -Math.log(height / 100000f) * 3.6f);
int length = 0;
for (int ipos = 0, ppos = 0, n = index.length; ipos < n; ipos++, ppos += length) {
length = index[ipos];
// end marker
if (length < 0)
break;
// start next polygon
if (length == 0) {
outer = ipos + 1;
startVertex = mNumVertices;
simple = true;
continue;
}
// check: drop last point from explicitly closed rings
int len = length;
if (!MapView.enableClosePolygons) {
len -= 2;
} else if (points[ppos] == points[ppos + len - 2]
&& points[ppos + 1] == points[ppos + len - 1]) {
// vector-tile-map does not produce implicty closed
// polygons (yet)
len -= 2;
}
// need at least three points
if (len < 6)
continue;
// check if polygon contains inner rings
if (simple && (ipos < n - 1) && (index[ipos + 1] > 0))
simple = false;
boolean convex = addOutline(points, ppos, len, height, simple);
if (simple && (convex || len <= 8))
addRoofSimple(startVertex, len);
else if (ipos == outer) { // add roof only once
addRoof(startVertex, index, ipos, points, ppos);
}
}
}
private void addRoofSimple(int startVertex, int len) {
// roof indices for convex shapes
int i = mCurIndices[IND_ROOF].used;
short[] indices = mCurIndices[IND_ROOF].vertices;
short first = (short) (startVertex + 1);
for (int k = 0; k < len - 4; k += 2) {
if (i == VertexPoolItem.SIZE) {
mCurIndices[IND_ROOF].used = VertexPoolItem.SIZE;
mCurIndices[IND_ROOF].next = VertexPool.get();
mCurIndices[IND_ROOF] = mCurIndices[2].next;
indices = mCurIndices[IND_ROOF].vertices;
i = 0;
}
indices[i++] = first;
indices[i++] = (short) (first + k + 2);
indices[i++] = (short) (first + k + 4);
}
mCurIndices[IND_ROOF].used = i;
}
private void addRoof(int startVertex, short[] index, int ipos, float[] points, int ppos) {
int len = 0;
int rings = 0;
// get sum of points in polygon
for (int i = ipos, n = index.length; i < n && index[i] > 0; i++) {
len += index[i];
rings++;
}
// triangulate up to 600 points (limited only by prepared buffers)
// some buildings in paris have even more...
if (len > 1200) {
Log.d(TAG, ">>> skip building : " + len + " <<<");
return;
}
int used = triangulate(points, ppos, len, index, ipos, rings,
startVertex + 1, mCurIndices[IND_ROOF]);
if (used > 0) {
// get back to the last item added..
VertexPoolItem it = mIndices[IND_ROOF];
while (it.next != null)
it = it.next;
mCurIndices[IND_ROOF] = it;
}
}
private boolean addOutline(float[] points, int pos, int len, float height,
boolean convex) {
// add two vertices for last face to make zigzag indices work
boolean addFace = (len % 4 != 0);
int vertexCnt = len + (addFace ? 2 : 0);
short h = (short) height;
float cx = points[pos + len - 2];
float cy = points[pos + len - 1];
float nx = points[pos + 0];
float ny = points[pos + 1];
// vector to next point
float vx = nx - cx;
float vy = ny - cy;
// vector from previous point
float ux, uy;
float a = (float) Math.sqrt(vx * vx + vy * vy);
short color1 = (short) ((1 + vx / a) * 127);
short fcolor = color1;
short color2 = 0;
int even = 0;
int changeX = 0;
int changeY = 0;
// vertex offset for all vertices in layer
int vOffset = mNumVertices;
short[] vertices = mCurVertices.vertices;
int v = mCurVertices.used;
mClipper.clipStart((int) nx, (int) ny);
for (int i = 2, n = vertexCnt + 2; i < n; i += 2, v += 8) {
cx = nx;
cy = ny;
ux = vx;
uy = vy;
/* add bottom and top vertex for each point */
if (v == VertexPoolItem.SIZE) {
mCurVertices.used = VertexPoolItem.SIZE;
mCurVertices.next = VertexPool.get();
mCurVertices = mCurVertices.next;
vertices = mCurVertices.vertices;
v = 0;
}
// set coordinate
vertices[v + 0] = vertices[v + 4] = (short) (cx * S);
vertices[v + 1] = vertices[v + 5] = (short) (cy * S);
// set height
vertices[v + 2] = 0;
vertices[v + 6] = h;
// get direction to next point
if (i < len) {
nx = points[pos + i + 0];
ny = points[pos + i + 1];
} else if (i == len) {
nx = points[pos + 0];
ny = points[pos + 1];
} else { // if (addFace)
short c = (short) (color1 | fcolor << 8);
vertices[v + 3] = vertices[v + 7] = c;
v += 8;
break;
}
vx = nx - cx;
vy = ny - cy;
// set lighting (by direction)
a = (float) Math.sqrt(vx * vx + vy * vy);
color2 = (short) ((1 + vx / a) * 127);
short c;
if (even == 0)
c = (short) (color1 | color2 << 8);
else
c = (short) (color2 | color1 << 8);
vertices[v + 3] = vertices[v + 7] = c;
color1 = color2;
/* check if polygon is convex */
if (convex) {
// TODO simple polys with only one concave arc
// could be handled without special triangulation
if ((ux < 0 ? 1 : -1) != (vx < 0 ? 1 : -1))
changeX++;
if ((uy < 0 ? 1 : -1) != (vy < 0 ? 1 : -1))
changeY++;
if (changeX > 2 || changeY > 2)
convex = false;
}
/* check if face is within tile */
if (mClipper.clipNext((int) nx, (int) ny) == 0) {
even = (even == 0 ? 1 : 0);
continue;
}
/* add ZigZagQuadIndices(tm) for sides */
short vert = (short) (vOffset + (i - 2));
short s0 = vert++;
short s1 = vert++;
short s2 = vert++;
short s3 = vert++;
// connect last to first (when number of faces is even)
if (!addFace && i == len) {
s2 -= len;
s3 -= len;
}
short[] indices = mCurIndices[even].vertices;
// index id relative to mCurIndices item
int ind = mCurIndices[even].used;
if (ind == VertexPoolItem.SIZE) {
mCurIndices[even].next = VertexPool.get();
mCurIndices[even] = mCurIndices[even].next;
indices = mCurIndices[even].vertices;
ind = 0;
}
indices[ind + 0] = s0;
indices[ind + 1] = s2;
indices[ind + 2] = s1;
indices[ind + 3] = s1;
indices[ind + 4] = s2;
indices[ind + 5] = s3;
mCurIndices[even].used += 6;
even = (even == 0 ? 1 : 0);
/* add roof outline indices */
VertexPoolItem it = mCurIndices[IND_OUTLINE];
if (it.used == VertexPoolItem.SIZE) {
it.next = VertexPool.get();
it = mCurIndices[IND_OUTLINE] = it.next;
}
it.vertices[it.used++] = s1;
it.vertices[it.used++] = s3;
}
mCurVertices.used = v;
mNumVertices += vertexCnt;
return convex;
}
@Override
public void compile(ShortBuffer sbuf) {
if (mNumVertices == 0 || compiled)
return;
mVertexBO = BufferObject.get(0);
mIndiceBO = BufferObject.get(0);
mIndicesBufferID = mIndiceBO.id;
mVertexBufferID = mVertexBO.id;
// upload indices
sbuf.clear();
mNumIndices = 0;
for (int i = 0; i < 4; i++) {
for (VertexPoolItem vi = mIndices[i]; vi != null; vi = vi.next) {
sbuf.put(vi.vertices, 0, vi.used);
mIndiceCnt[i] += vi.used;
}
mNumIndices += mIndiceCnt[i];
}
sbuf.flip();
mIndiceBO.size = mNumIndices * 2;
GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, mIndicesBufferID);
GLES20.glBufferData(GLES20.GL_ELEMENT_ARRAY_BUFFER,
mIndiceBO.size, sbuf, GLES20.GL_DYNAMIC_DRAW);
// upload vertices
sbuf.clear();
for (VertexPoolItem vi = mVertices; vi != null; vi = vi.next)
sbuf.put(vi.vertices, 0, vi.used);
sbuf.flip();
mVertexBO.size = mNumVertices * 4 * 2;
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVertexBufferID);
GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER,
mVertexBO.size, sbuf, GLES20.GL_DYNAMIC_DRAW);
GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0);
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
for (VertexPoolItem i : mIndices)
VertexPool.release(i);
VertexPool.release(mVertices);
mClipper = null;
compiled = true;
}
@Override
protected void clear() {
if (compiled) {
BufferObject.release(mIndiceBO);
BufferObject.release(mVertexBO);
mIndiceBO = null;
mVertexBO = null;
//GLES20.glDeleteBuffers(2, mVboIds, 0);
} else {
VertexPool.release(mVertices);
for (VertexPoolItem i : mIndices)
VertexPool.release(i);
}
}
private static boolean initialized = false;
private static ShortBuffer sBuf;
public static synchronized int triangulate(float[] points, int ppos, int plen, short[] index,
int ipos, int rings, int vertexOffset, VertexPoolItem item) {
if (!initialized) {
// FIXME also cleanup on shutdown!
sBuf = ByteBuffer.allocateDirect(1800 * 2).order(ByteOrder.nativeOrder())
.asShortBuffer();
initialized = true;
}
sBuf.clear();
sBuf.put(index, ipos, rings);
int numTris = triangulate(points, ppos, plen, rings, sBuf, vertexOffset);
int numIndices = numTris * 3;
sBuf.limit(numIndices);
sBuf.position(0);
for (int k = 0, cnt = 0; k < numIndices; k += cnt) {
if (item.used == VertexPoolItem.SIZE) {
item.next = VertexPool.get();
item = item.next;
}
cnt = VertexPoolItem.SIZE - item.used;
if (k + cnt > numIndices)
cnt = numIndices - k;
sBuf.get(item.vertices, item.used, cnt);
item.used += cnt;
}
return numIndices;
}
/**
* @param points an array of x,y coordinates
* @param pos position in points array
* @param len number of points * 2 (i.e. values to read)
* @param numRings number of rings in polygon == outer(1) + inner rings
* @param io input: number of points in rings - times 2!
* output: indices of triangles, 3 per triangle :) (indices use
* stride=2, i.e. 0,2,4...)
* @param ioffset offset used to add offset to indices
* @return number of triangles in io buffer
*/
public static native int triangulate(float[] points, int pos, int len, int numRings, ShortBuffer io,
int ioffset);
static {
System.loadLibrary("triangle-jni");
}
}