![JAR search and dependency download from the Maven repository](/logo.png)
org.oscim.renderer.ExtrusionRenderer Maven / Gradle / Ivy
Show all versions of vtm Show documentation
/*
* Copyright 2013 Hannes Janetzek
* Copyright 2017 Izumi Kawashima
* Copyright 2017 devemux86
* Copyright 2018-2019 Gustl22
*
* 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 .
*/
package org.oscim.renderer;
import org.oscim.backend.GL;
import org.oscim.core.Tile;
import org.oscim.renderer.bucket.ExtrusionBucket;
import org.oscim.renderer.bucket.ExtrusionBuckets;
import org.oscim.renderer.bucket.RenderBuckets;
import org.oscim.renderer.light.ShadowRenderer;
import org.oscim.renderer.light.Sun;
import org.oscim.utils.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.oscim.backend.GLAdapter.gl;
import static org.oscim.renderer.MapRenderer.COORD_SCALE;
public abstract class ExtrusionRenderer extends LayerRenderer {
static final Logger log = LoggerFactory.getLogger(ExtrusionRenderer.class);
// Don't draw extrusions which are covered by others
private final boolean mTranslucent;
private final boolean mMesh;
private Shader mShader;
protected ExtrusionBuckets[] mExtrusionBucketSet = {};
protected int mBucketsCnt;
protected float mAlpha = 1;
private float mZLimit = Float.MAX_VALUE;
private Sun mSun;
private boolean mEnableCurrentSunPos;
private boolean mUseLight = true;
public ExtrusionRenderer(boolean mesh, boolean translucent) {
mMesh = mesh;
mTranslucent = translucent;
mSun = new Sun();
}
public static class Shader extends GLShader {
/**
* The vertex position as attribute.
*/
int aPos;
/**
* The normal of vertex's face as attribute.
*/
int aNormal;
/**
* The alpha value (e.g. for fading animation) as uniform.
*/
int uAlpha;
/**
* The extrusion color(s) as uniform.
*/
int uColor;
/**
* The lights position vector as uniform.
*/
int uLight;
/**
* The shader render mode as uniform.
*
* Extrusion shader:
* -1: translucent (depth buffer only)
* 0: draw roof
* 1: draw side one
* 2: draw side two
* 3: draw outline
*/
int uMode;
/**
* The model-view-projection matrix as uniform.
*/
int uMVP;
/**
* The height limit of extrusions as uniform.
*/
int uZLimit;
public Shader(String shader) {
this(shader, null);
}
public Shader(String shader, String directives) {
if (!createDirective(shader, directives))
return;
uMVP = getUniform("u_mvp");
uColor = getUniform("u_color");
uAlpha = getUniform("u_alpha");
uMode = getUniform("u_mode");
uZLimit = getUniform("u_zlimit");
aPos = getAttrib("a_pos");
aNormal = getAttrib("a_normal");
uLight = getUniform("u_light");
}
}
public void enableCurrentSunPos(boolean enableSunPos) {
mEnableCurrentSunPos = enableSunPos;
}
public Shader getShader() {
return mShader;
}
public Sun getSun() {
return mSun;
}
public boolean isMesh() {
return mMesh;
}
@Override
public boolean setup() {
if (!mMesh)
mShader = new Shader("extrusion_layer_ext");
else
mShader = new Shader("extrusion_layer_mesh");
return true;
}
@Override
public void update(GLViewport viewport) {
if (mEnableCurrentSunPos) {
float lat = (float) viewport.pos.getLatitude();
float lon = (float) viewport.pos.getLongitude();
if (FastMath.abs(mSun.getLatitude() - lat) > 0.2f
|| Math.abs(mSun.getLongitude() - lon) > 0.2f) {
// location is only updated if necessary (not every frame)
mSun.setCoordinates(lat, lon);
}
mSun.update();
}
}
private void renderCombined(int vertexPointer, ExtrusionBuckets ebs) {
for (ExtrusionBucket eb = ebs.buckets(); eb != null; eb = eb.next()) {
gl.vertexAttribPointer(vertexPointer, 3,
GL.SHORT, false, RenderBuckets.SHORT_BYTES * 4,
eb.getVertexOffset());
int sumIndices = eb.idx[0] + eb.idx[1] + eb.idx[2];
/* extrusion (mMesh == false) */
if (sumIndices > 0)
gl.drawElements(GL.TRIANGLES, sumIndices,
GL.UNSIGNED_SHORT, eb.off[0]);
/* mesh (mMesh == true) */
if (eb.idx[4] > 0) {
gl.drawElements(GL.TRIANGLES, eb.idx[4],
GL.UNSIGNED_SHORT, eb.off[4]);
}
}
}
@Override
public void render(GLViewport v) {
float[] currentColors = null;
float currentAlpha = 0;
gl.depthMask(true);
gl.clear(GL.DEPTH_BUFFER_BIT);
GLState.test(true, false);
Shader s = mShader;
s.useProgram();
GLState.enableVertexArrays(s.aPos, GLState.DISABLED);
/* only use face-culling when it's unlikely
* that one'moves through the building' */
if (v.pos.zoomLevel < 18)
gl.enable(GL.CULL_FACE);
gl.depthFunc(GL.LESS);
gl.uniform1f(s.uAlpha, mAlpha);
gl.uniform1f(s.uZLimit, mZLimit);
GLUtils.glUniform3fv(s.uLight, 1, mSun.getPosition());
ExtrusionBuckets[] ebs = mExtrusionBucketSet;
if (mTranslucent) {
/* only draw to depth buffer */
GLState.blend(false);
gl.colorMask(false, false, false, false);
gl.uniform1i(s.uMode, -1);
for (int i = 0; i < mBucketsCnt; i++) {
if (ebs[i] == null)
return;
if (ebs[i].ibo == null)
return;
ebs[i].ibo.bind();
ebs[i].vbo.bind();
setMatrix(s, v, ebs[i]);
float alpha = mAlpha * getFade(ebs[i]);
if (alpha != currentAlpha) {
gl.uniform1f(s.uAlpha, alpha);
currentAlpha = alpha;
}
renderCombined(s.aPos, ebs[i]);
}
/* only draw to color buffer */
gl.colorMask(true, true, true, true);
gl.depthMask(false);
gl.depthFunc(GL.EQUAL);
}
// Depth cannot be transparent (in GL20)
GLState.blend(mUseLight);
GLState.enableVertexArrays(s.aPos, s.aNormal);
for (int i = 0; i < mBucketsCnt; i++) {
if (ebs[i].ibo == null)
continue;
ebs[i].ibo.bind();
ebs[i].vbo.bind();
if (!mTranslucent)
setMatrix(s, v, ebs[i]);
float alpha = mAlpha * getFade(ebs[i]);
if (alpha != currentAlpha) {
gl.uniform1f(s.uAlpha, alpha);
currentAlpha = alpha;
}
ExtrusionBucket eb = ebs[i].buckets();
for (; eb != null; eb = eb.next()) {
if (eb.getColors() != currentColors) {
currentColors = eb.getColors();
GLUtils.glUniform4fv(s.uColor,
mMesh ? 1 : 4,
currentColors);
}
gl.vertexAttribPointer(s.aPos, 3, GL.SHORT,
false, RenderBuckets.SHORT_BYTES * 4, eb.getVertexOffset());
if (mUseLight)
gl.vertexAttribPointer(s.aNormal, 2, GL.UNSIGNED_BYTE,
false, RenderBuckets.SHORT_BYTES * 4, eb.getVertexOffset() + RenderBuckets.SHORT_BYTES * 3);
/* draw extruded outlines (mMesh == false) */
if (eb.idx[0] > 0) {
if (mTranslucent) {
gl.depthFunc(GL.EQUAL);
setMatrix(s, v, ebs[i]);
}
/* draw roof */
gl.uniform1i(s.uMode, 0);
gl.drawElements(GL.TRIANGLES, eb.idx[2],
GL.UNSIGNED_SHORT, eb.off[2]);
/* draw sides 1 */
gl.uniform1i(s.uMode, 1);
gl.drawElements(GL.TRIANGLES, eb.idx[0],
GL.UNSIGNED_SHORT, eb.off[0]);
/* draw sides 2 */
gl.uniform1i(s.uMode, 2);
gl.drawElements(GL.TRIANGLES, eb.idx[1],
GL.UNSIGNED_SHORT, eb.off[1]);
if (mTranslucent) {
/* drawing gl_lines with the same coordinates
* does not result in same depth values as
* polygons, so add offset and draw gl_lequal */
gl.depthFunc(GL.LEQUAL);
v.mvp.addDepthOffset(100);
v.mvp.setAsUniform(s.uMVP);
}
gl.uniform1i(s.uMode, 3);
gl.drawElements(GL.LINES, eb.idx[3],
GL.UNSIGNED_SHORT, eb.off[3]);
}
/* draw triangle meshes (mMesh == true) */
if (eb.idx[4] > 0) {
if (mTranslucent) {
gl.depthFunc(GL.EQUAL);
setMatrix(s, v, ebs[i]);
}
gl.drawElements(GL.TRIANGLES, eb.idx[4],
GL.UNSIGNED_SHORT, eb.off[4]);
}
}
/* just a temporary reference! */
/* But for shadows we use them multiple times */
//ebs[i] = null;
}
if (!mTranslucent)
gl.depthMask(false);
if (v.pos.zoomLevel < 18)
gl.disable(GL.CULL_FACE);
}
private float getFade(ExtrusionBuckets ebs) {
if (ebs.animTime == 0)
ebs.animTime = MapRenderer.frametime - 50;
return FastMath.clamp((float) (MapRenderer.frametime - ebs.animTime) / 300f, 0f, 1f);
}
private void setMatrix(Shader s, GLViewport v, ExtrusionBuckets l) {
int z = l.zoomLevel;
double curScale = Tile.SIZE * v.pos.scale;
float scale = (float) (v.pos.scale / (1 << z));
float x = (float) ((l.x - v.pos.x) * curScale);
float y = (float) ((l.y - v.pos.y) * curScale);
// Create model matrix
v.mvp.setTransScale(x, y, scale / COORD_SCALE);
v.mvp.setValue(10, scale / 10);
// Create shadow map converter
// TODO may code it cleaner
if (s instanceof ShadowRenderer.Shader)
((ShadowRenderer.Shader) s).setLightMVP(v.mvp);
// Apply model matrix to VP-Matrix
v.mvp.multiplyLhs(v.viewproj);
if (mTranslucent) {
/* should avoid z-fighting of overlapping
* building from different tiles */
int zoom = (1 << z);
int delta = (int) (l.x * zoom) % 4 + (int) (l.y * zoom) % 4 * 4;
v.mvp.addDepthOffset(delta);
}
v.mvp.setAsUniform(s.uMVP);
}
public void setShader(Shader shader) {
mShader = shader;
}
public void setZLimit(float zLimit) {
mZLimit = zLimit;
}
public void useLight(boolean useLight) {
mUseLight = useLight;
}
}