Java tutorial
/* * Copyright (C) 2013 Clemens-Alexander Brust IT-Dienstleistungen * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package de.ikosa.mars.viewer.glviewer; import de.ikosa.mars.ruleset.TerrainType; import de.ikosa.mars.session.world.World; import de.ikosa.mars.session.world.mesh.HeightMapMesh; import de.ikosa.mars.util.ML; import de.ikosa.mars.viewer.glviewer.engine.GLResources; import de.ikosa.mars.viewer.glviewer.engine.GLTextureArray; import org.lwjgl.BufferUtils; import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL15; import org.lwjgl.opengl.GL20; import org.lwjgl.opengl.GL30; import java.nio.FloatBuffer; import java.nio.IntBuffer; public class GLHeightMapMeshBuilder { private String name; private GLWorldVertex[][] vMatrix; private IntBuffer iBuffer; private int matrixSizeX, matrixSizeY, indices; public GLHeightMapMeshBuilder(String name) { this.name = name; } public void addHeightMapMesh(HeightMapMesh heightMapMesh) { float[][] heightMap = heightMapMesh.getHeightMap(); TerrainType[][] terrainMap = heightMapMesh.getTerrainMap(); float sampleSpacing = heightMapMesh.getGridDistance(); float originX = heightMapMesh.getOriginX(); float originY = heightMapMesh.getOriginY(); checkSampleCount(0, 0, heightMap.length - 1, heightMap.length - 1); prepareIndexBuffer(0, 0, heightMap.length - 1, heightMap.length - 1); prepareMatrix(0, 0, heightMap.length - 1, heightMap.length - 1); sampleHeightMapMeshData(heightMap, terrainMap, sampleSpacing, originX, originY); buildIndices(); if (GLSettings.MAP_FILTER) { filterWCoordinate(); } ML.d("Done."); } private void sampleHeightMapMeshData(float[][] heightMap, TerrainType[][] terrainMap, float sampleSpacing, float originX, float originY) { for (int y = 0; y < matrixSizeY; y++) { for (int x = 0; x < matrixSizeX; x++) { float px = originX + (float) x * sampleSpacing; float py = originY + (float) y * sampleSpacing; float pz = heightMap[x][y]; float nx = 0.0f; float ny = 0.0f; float nz = 1.0f; if (x > 0 && x < (matrixSizeX - 1) && y > 0 && y < (matrixSizeY - 1)) { nx = (heightMap[x - 1][y] - heightMap[x + 1][y]) / sampleSpacing; ny = (heightMap[x][y - 1] - heightMap[x][y + 1]) / sampleSpacing; } TerrainType terrainType = terrainMap[x][y]; float textureLayer = terrainType.getTextureLayer(); GLWorldVertex vertex = new GLWorldVertex(px, py, pz, px, py, textureLayer, nx, ny, nz); vMatrix[x][y] = vertex; } } } public void addWorld(World world) { HeightMapMesh mesh = world.getMesh(); addHeightMapMesh(mesh); } private void filterWCoordinate() { float kernel[][] = new float[][] { new float[] { 0, 1, 2, 1, 0 }, new float[] { 1, 4, 8, 4, 1 }, new float[] { 2, 8, 16, 8, 2 }, new float[] { 1, 4, 8, 4, 1 }, new float[] { 0, 1, 2, 1, 0 } }; int kx = kernel[0].length; int ky = kernel.length; int range = kx / 2; float kernel_sum = 0.0f; for (int x = 0; x < kx; x++) for (int y = 0; y < ky; y++) kernel_sum += kernel[x][y]; if (kernel_sum == 0.0f) kernel_sum = 1.0f; ML.d("Filtering vertex terrain info..."); for (int y = range; y < (matrixSizeY - range); y++) { for (int x = range; x < (matrixSizeX - range); x++) { float inner_sum = 0.0f; for (int inner_y = -range; inner_y <= range; inner_y++) for (int inner_x = -range; inner_x <= range; inner_x++) inner_sum += kernel[inner_x + range][inner_y + range] * vMatrix[x + inner_x][y + inner_y].tw; vMatrix[x][y].tw = inner_sum / kernel_sum; } } } private void buildIndices() { ML.d("Building indices..."); for (int y = 0; y < matrixSizeY - 1; y++) { for (int x = 0; x < matrixSizeX - 1; x++) { int indexAA = y * matrixSizeX + x; int indexAB = (y + 1) * matrixSizeX + x; int indexBA = y * matrixSizeX + x + 1; int indexBB = (y + 1) * matrixSizeX + x + 1; addFace(indexAA, indexBA, indexAB); addFace(indexBA, indexBB, indexAB); } } } private void prepareMatrix(int startX, int startY, int endX, int endY) { matrixSizeX = endX + 1 - startX; matrixSizeY = endY + 1 - startY; vMatrix = new GLWorldVertex[matrixSizeX][matrixSizeY]; } private void prepareIndexBuffer(int startX, int startY, int endX, int endY) { long indexCount = (endX - startX) * (endY - startY) * 6; if (indexCount > (long) Integer.MAX_VALUE) { ML.f(String.format("Too many indices (%d)!", (endX + 1) * (endY + 1))); } else { ML.d(String.format("Building height map with %d indices...", indexCount)); } indices = (int) indexCount; iBuffer = BufferUtils.createIntBuffer(indices); } private void checkSampleCount(int startX, int startY, int endX, int endY) { long totalSamples = (long) (endX + 1 - startX) * (long) (endY + 1 - startY); if (totalSamples > (long) Integer.MAX_VALUE) { ML.f(String.format("Too many samples (%d)!", (endX + 1) * (endY + 1))); } else { ML.d(String.format("Building height map with %d samples...", totalSamples)); } } public String getName() { return name; } public void addFace(int... index) { if (index.length > 3) ML.f("Face with more than three vertices."); // triangulate... else if (index.length < 3) { ML.e(String.format("Face with less than three vertices (%d)", index.length)); } else { iBuffer.put(index[0]); iBuffer.put(index[1]); iBuffer.put(index[2]); } } public GLHeightMapMesh createMesh() { ML.d(String.format("Writing mesh \"%s\" to buffer...", name)); // create VBO buffers FloatBuffer interleavedBuffer = BufferUtils.createFloatBuffer(matrixSizeX * matrixSizeY * 9); int stride = 12 + 12 + 12; int normalPosition = 12; int texCoordPosition = 24; // fill buffers for (int y = 0; y < matrixSizeY; y++) { for (int x = 0; x < matrixSizeX; x++) { GLWorldVertex vertex = vMatrix[x][y]; interleavedBuffer.put(vertex.px); interleavedBuffer.put(vertex.py); interleavedBuffer.put(vertex.pz); interleavedBuffer.put(vertex.nx); interleavedBuffer.put(vertex.ny); interleavedBuffer.put(vertex.nz); interleavedBuffer.put(vertex.tu); interleavedBuffer.put(vertex.tv); interleavedBuffer.put(vertex.tw); } } interleavedBuffer.flip(); ML.d("Done."); // create VAO int vaoId = GL30.glGenVertexArrays(); GL30.glBindVertexArray(vaoId); // create VBOs int interleavedVboId = GL15.glGenBuffers(); GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, interleavedVboId); GL15.glBufferData(GL15.GL_ARRAY_BUFFER, interleavedBuffer, GL15.GL_STATIC_DRAW); // attribs GL20.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, stride, 0); GL20.glVertexAttribPointer(1, 3, GL11.GL_FLOAT, false, stride, normalPosition); GL20.glVertexAttribPointer(2, 3, GL11.GL_FLOAT, false, stride, texCoordPosition); // unbind buffers GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); GL30.glBindVertexArray(0); // create index buffer iBuffer.flip(); int indexVboId = GL15.glGenBuffers(); GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, indexVboId); GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, iBuffer, GL15.GL_STATIC_DRAW); GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0); GLTextureArray terrainTexture = (GLTextureArray) GLResources.global.getTexture("terrain"); // create mesh object GLHeightMapMesh mesh = new GLHeightMapMesh(name, vaoId, interleavedVboId, indexVboId, indices, terrainTexture); return mesh; } public static class GLWorldVertex { public float px = 0.0f, py = 0.0f, pz = 0.0f, tu = 0.0f, tv = 0.0f, tw = 0.0f, nx = 0.0f, ny = 0.0f, nz = 0.0f; public GLWorldVertex(float px, float py, float pz, float tu, float tv, float tw, float nx, float ny, float nz) { this.px = px; this.py = py; this.pz = pz; this.tu = tu; this.tv = tv; this.tw = tw; this.nx = nx; this.ny = ny; this.nz = nz; } } }