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