de.ikosa.mars.viewer.glviewer.GLHeightMapMeshBuilder.java Source code

Java tutorial

Introduction

Here is the source code for de.ikosa.mars.viewer.glviewer.GLHeightMapMeshBuilder.java

Source

/*
 * 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;
        }
    }
}