package ch.ethz.karto.map3d;
import com.jogamp.common.nio.Buffers;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import javax.media.opengl.GL;
import javax.media.opengl.GL2;
/**
* This vertex array model uses three large buffers to load vertices, normals,
* and texture coordinates. The buffers contain regular grids. The grids are
* assembled into a index buffer referencing the grids.
* This model is not as fast as VBOs in Map3DModelVBO, which should be
* preferred. Vertex arrays could be accelerated by compiling them into a display
* list, but this would increase the memory occupied on the GPU, as the
* triangle strips are converted to triangles.
*
* For vertex arrays, see:
* http://www.java-tips.org/other-api-tips/jogl/vertex-buffer-objects-nehe-tutorial-jogl-port-2.html
* @author jenny
*/
public class Map3DModelVertexArrays extends Map3DModel {
private FloatBuffer verticesBuffer = null;
private FloatBuffer normalsBuffer = null;
private FloatBuffer textureBuffer = null;
private Map3DTexture texture;
public Map3DModelVertexArrays() {
}
private void copyRowToBuffer(FloatBuffer verticesBuffer,
FloatBuffer normalsBuffer,
FloatBuffer textureBuffer,
int row) {
if (this.grid == null) {
return;
}
final int rows = this.grid.length;
final int cols = this.grid[0].length;
final int cols_1 = cols - 1;
final float s = 1.0f / (Math.max(cols, rows) - 1);
final float zScale = 1.0f / this.cellSize;
final int r1 = row;
final int r2 = Math.min(r1 + 1, rows - 1);
float[] row1 = this.grid[r1];
float[] row2 = this.grid[r2];
final float texY = (float) (row - 1) / rows;
final float widthInv = 1f / (cols - 1);
for (int c0 = 0; c0 < cols; ++c0) {
final int c1 = Math.min(c0 + 1, cols_1);
final float v10 = zScale * (row1[c0] - this.minValue);
final float v20 = zScale * (row2[c0] - this.minValue);
final float v11 = zScale * (row1[c1] - this.minValue);
// compute normal with the point to the right and the point below
final float nx = v10 - v11;
final float ny = v10 - v20;
final float len_inv = (float) (1. / Math.sqrt(nx * nx + ny * ny + 1.0f));
normalsBuffer.put(nx * len_inv);
normalsBuffer.put(ny * len_inv);
normalsBuffer.put(len_inv);
final float x = c0 * s;
final float y = r1 * s;
verticesBuffer.put(x);
verticesBuffer.put(y);
verticesBuffer.put(v10 * s);
if (textureBuffer != null) {
if (texture.is1D()) {
final float t = texture1DMapper.get1DTextureCoordinate(c0, r1);
textureBuffer.put(t);
} else {
textureBuffer.put(c0 * widthInv);
textureBuffer.put(texY);
}
}
}
}
@Override
public void loadModel(GL gl1, Map3DTexture texture) {
if (modelInitialized || grid == null) {
return;
}
GL2 gl = (GL2)gl1;
this.texture = texture;
texture.updateEnabledState(gl);
final int rows = this.getRows();
final int vertexCount = getCols() * rows;
gl.glTranslatef(0, 0, ZOFFSET);
if (verticesBuffer == null || verticesBuffer.capacity() != vertexCount * 3) {
verticesBuffer = Buffers.newDirectFloatBuffer(vertexCount * 3);
} else {
verticesBuffer.rewind();
}
if (normalsBuffer == null || normalsBuffer.capacity() != vertexCount * 3) {
normalsBuffer = Buffers.newDirectFloatBuffer(vertexCount * 3);
} else {
normalsBuffer.rewind();
}
if (texture.hasTexture()) {
int texCount = texture.getDim() * vertexCount;
if ((textureBuffer == null || textureBuffer.capacity() != texCount)) {
textureBuffer = Buffers.newDirectFloatBuffer(texCount);
} else {
textureBuffer.rewind();
}
} else {
textureBuffer = null;
}
// copy the rows
for (int r = 0; r < rows; r++) {
copyRowToBuffer(verticesBuffer, normalsBuffer, textureBuffer, r);
}
verticesBuffer.rewind();
normalsBuffer.rewind();
if (texture.hasTexture()) {
textureBuffer.rewind();
}
this.modelInitialized = true;
}
@Override
public void draw(GL gl1, boolean shading, boolean fog) {
if (grid == null) {
return;
}
GL2 gl = (GL2)gl1;
final int rows = this.getRows();
final int cols = this.getCols();
// Enable pointers
gl.glEnableClientState(GL2.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL2.GL_NORMAL_ARRAY);
if (texture.hasTexture()) {
gl.glEnableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
}
// create index buffer for triangle strip consisting of a single row
IntBuffer ib = Buffers.newDirectIntBuffer(cols * 2);
for (int x = 0; x < cols; x++) {
ib.put(x);
ib.put(x + cols);
}
for (int r = 0; r < rows - 1; r++) {
// set the vertex, normal and texture pointers to the data buffers
gl.glVertexPointer(3, GL2.GL_FLOAT, 0, verticesBuffer.position(r * cols * 3));
gl.glNormalPointer(GL2.GL_FLOAT, 0, normalsBuffer.position(r * cols * 3));
if (texture.hasTexture()) {
gl.glTexCoordPointer(texture.getDim(), GL2.GL_FLOAT, 0,
textureBuffer.position(r * cols * texture.getDim()));
}
ib.rewind();
gl.glDrawElements(GL2.GL_TRIANGLE_STRIP, cols * 2, GL2.GL_UNSIGNED_INT, ib);
}
// Disable Pointers
gl.glDisableClientState(GL2.GL_VERTEX_ARRAY);
gl.glDisableClientState(GL2.GL_NORMAL_ARRAY);
gl.glDisableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
}
@Override
public void releaseModel(GL gl) {
}
@Override
public boolean canRun() {
return Map3DGLCapabilities.hasOpenGLVersion(1, 1);
}
/*
* A variant that first computes two normals, one with the top-left neighbors and
* one with the bottom-right neighbours and then computes the mean of these
* two normals. This results in rounder mountain ridges with the black of
* the backside of mountains shimmering through.
@Override
protected void constructSurface(GL gl, int textureID) {
final int rows = this.grid.length;
final int cols = this.grid[0].length;
// Enable Pointers
gl.glEnableClientState(GL2.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL2.GL_NORMAL_ARRAY);
//gl.glEnableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
float s = 1.0f / (Math.max(cols, rows) - 1);
float zScale = 1.0f / this.cellSize;
// normals
float[] normal = new float[3];
FloatBuffer normalsBuffer = Buffers.newDirectFloatBuffer(cols * rows * 3);
for (int r = 0; r < rows; r++) {
final int r2 = Math.min(r + 1, rows - 1);
for (int c = 0; c < cols; ++c) {
final int c2 = Math.min(c + 1, cols - 1);
final float v10 = zScale * (this.grid[r][c] - this.minValue);
final float v20 = zScale * (this.grid[r2][c] - this.minValue);
final float v11 = zScale * (this.grid[r][c2] - this.minValue);
computeNormal(normal, v10, v11, v20);
normalsBuffer.put(normal);
}
}
normalsBuffer.position(0);
gl.glNormalPointer(GL2.GL_FLOAT, 0, normalsBuffer);
// vertices
FloatBuffer verticesBuffer = Buffers.newDirectFloatBuffer(cols * rows * 3);
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
final float x = c * s;
final float y = r * s;
verticesBuffer.put(x);
verticesBuffer.put(y + s);
verticesBuffer.put(zScale * (this.grid[r][c] - this.minValue) * s + ZOFFSET);
}
}
verticesBuffer.position(0);
gl.glVertexPointer(3, GL2.GL_FLOAT, 0, verticesBuffer);
for (int r = 0; r < rows - 1; r++) {
indices.position(r * cols * 2);
IntBuffer rowIndices = indices.slice();
gl.glDrawElements(GL2.GL_TRIANGLE_STRIP, cols * 2, GL2.GL_UNSIGNED_INT, rowIndices);
}
// Disable Pointers
gl.glDisableClientState(GL2.GL_VERTEX_ARRAY);
gl.glDisableClientState(GL2.GL_NORMAL_ARRAY);
//gl.glDisableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
}
*/
/*
@Override
protected void constructSurface(GL gl, int textureID) {
final int rows = this.grid.length;
final int cols = this.grid[0].length;
final int vertexCount = cols * 2;
// buffers that will hold two rows, forming a triangle strip.
FloatBuffer verticesBuffer = Buffers.newDirectFloatBuffer(vertexCount * 3);
FloatBuffer normalsBuffer = Buffers.newDirectFloatBuffer(vertexCount * 3);
// Enable Pointers
gl.glEnableClientState(GL2.GL_VERTEX_ARRAY); // Enable Vertex Arrays
gl.glEnableClientState(GL2.GL_NORMAL_ARRAY);
//gl.glEnableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
// copy the first row
this.copyRowToBuffer(verticesBuffer, normalsBuffer, 0);
// copy the following rows in alternating order
for (int r = 1; r < rows; r++) {
// jump over first vertex on odd rows
verticesBuffer.position((r % 2) * 3);
normalsBuffer.position((r % 2) * 3);
this.copyRowToBuffer(verticesBuffer, normalsBuffer, r);
verticesBuffer.position(0);
normalsBuffer.position(0);
// set the vertex, normal and texture pointers to our data buffers
gl.glNormalPointer(GL2.GL_FLOAT, 0, normalsBuffer);
gl.glVertexPointer(3, GL2.GL_FLOAT, 0, verticesBuffer);
//gl.glTexCoordPointer(2, GL2.GL_FLOAT, 0, mesh.texCoords);
// draw the row
gl.glDrawArrays(GL2.GL_TRIANGLE_STRIP, 0, vertexCount);
}
// Disable Pointers
gl.glDisableClientState(GL2.GL_VERTEX_ARRAY);
gl.glDisableClientState(GL2.GL_NORMAL_ARRAY);
//gl.glDisableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
}*/
}