package org.osm2world.core.target.jogl;
import static java.lang.Math.PI;
import static java.lang.Math.abs;
import static org.osm2world.core.target.common.rendering.OrthoTilesUtil.CardinalDirection.closestCardinal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.osm2world.core.math.VectorXYZ;
import org.osm2world.core.target.common.Primitive;
import org.osm2world.core.target.common.material.Material;
import org.osm2world.core.target.common.material.Material.Transparency;
import org.osm2world.core.target.common.rendering.Camera;
import org.osm2world.core.target.common.rendering.OrthoTilesUtil.CardinalDirection;
import org.osm2world.core.target.common.rendering.Projection;
/**
* Base class for renderer that use vertex buffer objects (VBO) to speed up the process.
*
* If you don't need the renderer anymore, it's recommended to manually call
* {@link #freeResources()} to delete the VBOs and other resources.
*/
public abstract class JOGLRendererVBO extends JOGLRenderer {
protected static final boolean DOUBLE_PRECISION_RENDERING = false;
/** VBOs with static, non-alphablended geometry for each material */
protected List<VBOData<?>> vbos = new ArrayList<VBOData<?>>();
/** alphablended primitives, need to be sorted by distance from camera */
protected List<PrimitiveWithMaterial> transparentPrimitives =
new ArrayList<PrimitiveWithMaterial>();
/**
* the camera direction that was the basis for the previous sorting
* of {@link #transparentPrimitives}.
*/
private CardinalDirection currentPrimitiveSortDirection = null;
protected static final class PrimitiveWithMaterial {
public final Primitive primitive;
public final Material material;
public final VBOData<?> vbo;
private PrimitiveWithMaterial(Primitive primitive, Material material, VBOData<?>vbo) {
this.primitive = primitive;
this.material = material;
this.vbo = vbo;
}
}
/**
* returns the number of values for each vertex
* in the vertex buffer layout appropriate for a given material.
*/
public static int getValuesPerVertex(Material material) {
int numValues = 6; // vertex coordinates and normals
if (material.getTextureDataList() != null) {
numValues += 2 * material.getTextureDataList().size();
}
if (material.hasBumpMap()) {
numValues += 4; // tangent vectors are 4D
}
return numValues;
}
JOGLRendererVBO(JOGLTextureManager textureManager) {
super(textureManager);
}
/**
* Create the VBOs from a {@link PrimitiveBuffer}.
* @param primitiveBuffer the source for the VBOs
*/
protected void init(PrimitiveBuffer primitiveBuffer) {
for (Material material : primitiveBuffer.getMaterials()) {
if (material.getTransparency() == Transparency.TRUE) {
for (Primitive primitive : primitiveBuffer.getPrimitives(material)) {
transparentPrimitives.add(new PrimitiveWithMaterial(
primitive, material, this.createVBOData(
textureManager, material,
Arrays.asList(primitive))));
}
} else {
Collection<Primitive> primitives = primitiveBuffer.getPrimitives(material);
vbos.add(this.createVBOData(textureManager, material, primitives));
}
}
}
/**
* Sort all transparent primitives back to front relative to the camera.
* The projection can be used to speed up sorting if it is orthographic.
*/
protected void sortPrimitivesBackToFront(final Camera camera,
final Projection projection) {
if (projection.isOrthographic() &&
abs(camera.getViewDirection().xz().angle() % (PI/2)) < 0.01 ) {
/* faster sorting for cardinal directions */
CardinalDirection closestCardinal = closestCardinal(camera.getViewDirection().xz().angle());
if (closestCardinal.isOppositeOf(currentPrimitiveSortDirection)) {
Collections.reverse(transparentPrimitives);
} else if (closestCardinal != currentPrimitiveSortDirection) {
Comparator<PrimitiveWithMaterial> comparator = null;
switch(closestCardinal) {
case N:
comparator = new Comparator<PrimitiveWithMaterial>() {
@Override
public int compare(PrimitiveWithMaterial p1, PrimitiveWithMaterial p2) {
return Double.compare(primitivePos(p2).z, primitivePos(p1).z);
}
};
break;
case E:
comparator = new Comparator<PrimitiveWithMaterial>() {
@Override
public int compare(PrimitiveWithMaterial p1, PrimitiveWithMaterial p2) {
return Double.compare(primitivePos(p2).x, primitivePos(p1).x);
}
};
break;
case S:
comparator = new Comparator<PrimitiveWithMaterial>() {
@Override
public int compare(PrimitiveWithMaterial p1, PrimitiveWithMaterial p2) {
return Double.compare(primitivePos(p1).z, primitivePos(p2).z);
}
};
break;
case W:
comparator = new Comparator<PrimitiveWithMaterial>() {
@Override
public int compare(PrimitiveWithMaterial p1, PrimitiveWithMaterial p2) {
return Double.compare(primitivePos(p1).x, primitivePos(p2).x);
}
};
break;
}
Collections.sort(transparentPrimitives, comparator);
}
currentPrimitiveSortDirection = closestCardinal;
} else {
/* sort based on distance to camera */
Collections.sort(transparentPrimitives, new Comparator<PrimitiveWithMaterial>() {
@Override
public int compare(PrimitiveWithMaterial p1, PrimitiveWithMaterial p2) {
return Double.compare(
distanceToCameraSq(camera, p2),
distanceToCameraSq(camera, p1));
}
});
currentPrimitiveSortDirection = null;
}
}
private double distanceToCameraSq(Camera camera, PrimitiveWithMaterial p) {
return primitivePos(p).distanceToSquared(camera.getPos());
}
private VectorXYZ primitivePos(PrimitiveWithMaterial p) {
double sumX = 0, sumY = 0, sumZ = 0;
for (VectorXYZ v : p.primitive.vertices) {
sumX += v.x;
sumY += v.y;
sumZ += v.z;
}
return new VectorXYZ(sumX / p.primitive.vertices.size(),
sumY / p.primitive.vertices.size(),
sumZ / p.primitive.vertices.size());
}
@Override
public void freeResources() {
if (vbos != null) {
for (VBOData<?> vbo : vbos) {
vbo.delete();
}
vbos = null;
}
super.freeResources();
}
/**
* Create a new vertex buffer object for a bunch of primitives with the same material.
* @param textureManager the texture manager used if the material contains texture layers.
* @param material the material that applies to all primitives
* @param primitives the primitives to create the VBO for
* @return a vertex buffer object matching the primitives
*/
abstract VBOData<?> createVBOData(JOGLTextureManager textureManager, Material material, Collection<Primitive> primitives);
}