voxicity.ChunkNode.java Source code

Java tutorial

Introduction

Here is the source code for voxicity.ChunkNode.java

Source

/*
 * Copyright 2011, Erik Lund
 *
 * This file is part of Voxicity.
 *
 *  Voxicity 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.
 *
 *  Voxicity 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 Voxicity.  If not, see <http://www.gnu.org/licenses/>.
 */

package voxicity;

import java.io.BufferedReader;
import java.io.FileReader;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.ARBVertexBufferObject;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.OpenGLException;
import org.lwjgl.opengl.Util;
import org.lwjgl.util.vector.Vector3f;

public class ChunkNode {
    Vector3f pos = new Vector3f();

    int tex_buf;
    int vert_buf;

    List<Batch> batches = new ArrayList<Batch>();

    static int shader_prog;

    Chunk chunk;
    long last_update;

    boolean dirty = true;
    boolean empty = true;

    static FloatBuffer verts = BufferUtils.createFloatBuffer(3 * 24 * Constants.Chunk.block_number);
    static FloatBuffer tex_coords = BufferUtils.createFloatBuffer(2 * 24 * Constants.Chunk.block_number);

    public class Batch {
        public AABB box;
        public int indices;
        public int num_elements;
        public int shader;
        public int tex;
        public int tex_buf;
        public int vert_buf;

        public Batch(int tex, int indices, int num_elements, int shader, int tex_buf, int vert_buf, AABB box) {
            this.tex = tex;
            this.indices = indices;
            this.num_elements = num_elements;
            this.shader = shader;
            this.tex_buf = tex_buf;
            this.vert_buf = vert_buf;
            this.box = box;
        }
    }

    public ChunkNode(Chunk chunk) {
        this.chunk = chunk;
    }

    public void set_pos(float x, float y, float z) {
        pos.x = x;
        pos.y = y;
        pos.z = z;
    }

    public boolean is_clean() {
        return !dirty || (last_update >= chunk.get_timestamp());
    }

    public void clean() {
        last_update = Time.get_time_ms();

        vert_buf = GL15.glGenBuffers();
        tex_buf = GL15.glGenBuffers();

        if (shader_prog == 0)
            create_shader_prog();

        int offset = 0;

        verts.clear();
        tex_coords.clear();
        batches.clear();

        Map<Integer, IntBuffer> id_ind = new HashMap<Integer, IntBuffer>();

        BlockChunkLoc loc = new BlockChunkLoc(0, 0, 0, chunk);

        // Iterate through all blocks in the chunk
        for (int i = 0; i < Constants.Chunk.side_length; i++)
            for (int j = 0; j < Constants.Chunk.side_length; j++)
                for (int k = 0; k < Constants.Chunk.side_length; k++) {
                    // Get block id
                    int id = chunk.get_block(i, j, k);

                    // If air, do nothing, next block
                    if (id != Constants.Blocks.air) {
                        loc.x = i;
                        loc.y = j;
                        loc.z = k;

                        Block b = Blocks.get(id);

                        // If culled, do nothing, next block
                        if (!cull(loc) && !chunk_edge_cull(loc)) {
                            // Get the vertices for this block and put them in the verts buffer
                            verts.put(Coord.offset_coords(b.vertices(),
                                    new Vector3f(pos.x + loc.x, pos.y + loc.y, pos.z + loc.z)));

                            // Get the texture coords for this block and put them in the tex_coords buffer
                            tex_coords.put(b.texture_coords());

                            // Look up the texture for these vertices
                            int tex_id = TextureManager.get_texture(b.texture_string());

                            // Look up the index buffer for this texture and create it if needed
                            if (!id_ind.containsKey(tex_id))
                                id_ind.put(tex_id, BufferUtils.createIntBuffer(24 * Constants.Chunk.block_number));

                            // Get the index buffer for this texture
                            IntBuffer ind_buf = id_ind.get(tex_id);

                            // Get the indices for this block's vertices and put them in the ind_buf buffer after offsetting them
                            IntBuffer block_indices = b.indices();
                            while (block_indices.hasRemaining())
                                ind_buf.put(block_indices.get() + offset);

                            // Increase the offset by the number of indices
                            offset += block_indices.position();
                        }
                    }
                }

        if (verts.position() == 0) {
            empty = true;
            dirty = false;
            return;
        }

        verts.limit(verts.position()).rewind();
        tex_coords.limit(tex_coords.position()).rewind();
        System.out.println(verts.limit() + " " + tex_coords.limit());

        AABB box = new AABB(Constants.Chunk.side_length, Constants.Chunk.side_length, Constants.Chunk.side_length);
        box.center_on(pos.x + box.dimensions().x, pos.y + box.dimensions().y, pos.z + box.dimensions().z);

        // Pass the buffer to a VBO
        System.out.println("Binding vertex buffer");
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vert_buf);
        Util.checkGLError();
        System.out.println("Setting buffer data");
        GL15.glBufferData(GL15.GL_ARRAY_BUFFER, verts, GL15.GL_STATIC_DRAW);
        Util.checkGLError();
        System.out.println("Vertex buffer data set");

        System.out.println("Size of vertex buffer is: "
                + GL15.glGetBufferParameter(GL15.GL_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE));

        // Pass the buffer to a VBO
        System.out.println("Binding tex coord buffer");
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, tex_buf);
        Util.checkGLError();
        System.out.println("Setting buffer data");
        GL15.glBufferData(GL15.GL_ARRAY_BUFFER, tex_coords, GL15.GL_STATIC_DRAW);
        Util.checkGLError();

        System.out.println("Size of tex coord buffer is: "
                + GL15.glGetBufferParameter(GL15.GL_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE));

        for (Map.Entry<Integer, IntBuffer> entry : id_ind.entrySet()) {
            entry.getValue().limit(entry.getValue().position()).rewind();

            IntBuffer ibo = BufferUtils.createIntBuffer(1);
            GL15.glGenBuffers(ibo);

            if (ibo.get(0) == 0)
                System.out.println("Could not generate buffer object!");

            GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ibo.get(0));
            Util.checkGLError();
            GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, entry.getValue(), GL15.GL_STATIC_DRAW);
            Util.checkGLError();
            System.out.println("Size of index buffer is: "
                    + GL15.glGetBufferParameter(GL15.GL_ELEMENT_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE) + " with "
                    + entry.getValue().limit() + " indices");

            System.out.println(
                    "Creating batch: " + entry.getKey() + " " + ibo.get(0) + " " + entry.getValue().limit());
            batches.add(new Batch(entry.getKey(), ibo.get(0), entry.getValue().limit(), shader_prog, tex_buf,
                    vert_buf, box));

            GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
            GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);

        }

        empty = false;
        dirty = false;
    }

    public void render(Frustum camera) {
        if (empty)
            return;

        AABB chunk_box = new AABB(Constants.Chunk.side_length, Constants.Chunk.side_length,
                Constants.Chunk.side_length);

        chunk_box.center_on(chunk.get_x() + chunk_box.dimensions().x, chunk.get_y() + chunk_box.dimensions().y,
                chunk.get_z() + chunk_box.dimensions().z);

        if (!camera.collides(chunk_box))
            return;

        Renderer.draw_calls++;

        if (shader_prog != 0)
            GL20.glUseProgram(shader_prog);

        // Bind VBO to vertex pointer
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vert_buf);
        GL11.glVertexPointer(3, GL11.GL_FLOAT, 0, 0);

        // Bind the texture coord VBO to texture pointer
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, tex_buf);
        GL11.glTexCoordPointer(2, GL11.GL_FLOAT, 0, 0);

        int tex_bak = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);

        for (Batch batch : batches) {
            // Bind the texture for this batch
            GL11.glBindTexture(GL11.GL_TEXTURE_2D, batch.tex);

            // Bind index array
            GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, batch.indices);

            // Draw the block
            GL12.glDrawRangeElements(GL11.GL_QUADS, 0, Constants.Chunk.block_number * 24 - 1, batch.num_elements,
                    GL11.GL_UNSIGNED_INT, 0);

            Renderer.batch_draw_calls++;
            Renderer.quads += batch.num_elements;
        }

        // Unbind the texture
        GL11.glBindTexture(GL11.GL_TEXTURE_2D, tex_bak);

        // Unbind all buffers
        GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);

        // Disable the shader once more
        if (shader_prog != 0)
            GL20.glUseProgram(0);
    }

    void create_shader_prog() {
        shader_prog = GL20.glCreateProgram();

        int vert_shader = create_vert_shader("shader/block.vert");
        int frag_shader = create_frag_shader("shader/block.frag");

        GL20.glAttachShader(shader_prog, vert_shader);
        GL20.glAttachShader(shader_prog, frag_shader);
        GL20.glLinkProgram(shader_prog);

        if (check_shader_error(shader_prog)) {
            GL20.glDeleteProgram(shader_prog);
            shader_prog = 0;
        }

        GL20.glUseProgram(shader_prog);

        int uniform;
        if ((uniform = GL20.glGetUniformLocation(shader_prog, "textures")) != -1) {
            GL20.glUniform1i(uniform, 0);
        }

        System.out.println("Textures at: " + GL20.glGetUniformLocation(shader_prog, "textures"));

        GL20.glUseProgram(0);
    }

    int create_vert_shader(String filename) {
        int shader = GL20.glCreateShader(GL20.GL_VERTEX_SHADER);

        String code_text = "";
        String line;
        try {
            BufferedReader reader = new BufferedReader(new FileReader(filename));
            while ((line = reader.readLine()) != null) {
                code_text += line + "\n";
            }
        } catch (Exception e) {
            System.out.println("Reading vertex shader code failed");
            e.printStackTrace();
        }

        GL20.glShaderSource(shader, code_text);

        GL20.glCompileShader(shader);

        System.out.println("Compiling vertex shader: " + filename);
        if (GL20.glGetShader(shader, GL20.GL_COMPILE_STATUS) == GL11.GL_FALSE) {
            print_shader_log(shader);
        }

        if (check_shader_error(shader)) {
            GL20.glDeleteShader(shader);
            shader = 0;
        }

        return shader;
    }

    int create_frag_shader(String filename) {
        int shader = GL20.glCreateShader(GL20.GL_FRAGMENT_SHADER);

        String code_text = "";
        String line;
        try {
            BufferedReader reader = new BufferedReader(new FileReader(filename));
            while ((line = reader.readLine()) != null) {
                code_text += line + "\n";
            }
        } catch (Exception e) {
            System.out.println("Reading vertex shader code failed");
            e.printStackTrace();
        }

        GL20.glShaderSource(shader, code_text);

        System.out.println("Compiling fragment shader: " + filename);
        GL20.glCompileShader(shader);

        if (GL20.glGetShader(shader, GL20.GL_COMPILE_STATUS) == GL11.GL_FALSE) {
            print_shader_log(shader);
        }

        if (check_shader_error(shader)) {
            GL20.glDeleteShader(shader);
            shader = 0;
        }

        return shader;
    }

    boolean check_shader_error(int shader) {
        return false;
    }

    static void print_shader_log(int shader) {
        IntBuffer log_length = BufferUtils.createIntBuffer(1);
        GL20.glGetShader(shader, GL20.GL_INFO_LOG_LENGTH, log_length);

        if (log_length.get(0) > 1) {
            System.out.println("Shader log:\n" + GL20.glGetShaderInfoLog(shader, log_length.get(0)));

        }
    }

    boolean cull(BlockChunkLoc loc) {
        for (Direction dir : Direction.values()) {
            if (loc.get(dir).get() == Constants.Blocks.air)
                return false;
        }

        return true;
    }

    boolean chunk_edge_cull(BlockChunkLoc loc) {
        BlockLoc world_loc = new BlockLoc(loc);

        for (Direction dir : Direction.values()) {
            BlockLoc dir_loc = world_loc.get(dir);

            if (dir_loc.available() && dir_loc.get() == Constants.Blocks.air)
                return false;
        }

        return true;
    }
}