Introduction
A voxel engine is a 3D graphics engine that uses volumetric pixels (voxels) as the fundamental building blocks for creating and rendering 3D worlds. This prototype demonstrates core voxel engine concepts including chunk-based world management, mesh generation, and basic rendering.
Core Architecture
Voxel Data Structure
public class Voxel { public static final byte TYPE_AIR = 0; public static final byte TYPE_GRASS = 1; public static final byte TYPE_DIRT = 2; public static final byte TYPE_STONE = 3; public static final byte TYPE_WATER = 4; public static final byte TYPE_WOOD = 5; public static final byte TYPE_LEAVES = 6; private byte type; private boolean transparent; private boolean solid; public Voxel(byte type) { this.type = type; updateProperties(); } private void updateProperties() { switch (type) { case TYPE_AIR: transparent = true; solid = false; break; case TYPE_WATER: transparent = true; solid = false; break; case TYPE_LEAVES: transparent = true; solid = true; break; default: transparent = false; solid = true; break; } } // Getters public byte getType() { return type; } public boolean isTransparent() { return transparent; } public boolean isSolid() { return solid; } public void setType(byte type) { this.type = type; updateProperties(); } // Static factory methods public static Voxel air() { return new Voxel(TYPE_AIR); } public static Voxel grass() { return new Voxel(TYPE_GRASS); } public static Voxel dirt() { return new Voxel(TYPE_DIRT); } public static Voxel stone() { return new Voxel(TYPE_STONE); } public static Voxel water() { return new Voxel(TYPE_WATER); } public static Voxel wood() { return new Voxel(TYPE_WOOD); } public static Voxel leaves() { return new Voxel(TYPE_LEAVES); } } Chunk System
public class Chunk { public static final int CHUNK_SIZE = 16; public static final int CHUNK_HEIGHT = 256; private final Voxel[][][] voxels; private final int chunkX, chunkZ; private boolean needsRebuild; private ChunkMesh mesh; public Chunk(int chunkX, int chunkZ) { this.chunkX = chunkX; this.chunkZ = chunkZ; this.voxels = new Voxel[CHUNK_SIZE][CHUNK_HEIGHT][CHUNK_SIZE]; this.needsRebuild = true; initializeVoxels(); } private void initializeVoxels() { // Fill with air initially for (int x = 0; x < CHUNK_SIZE; x++) { for (int y = 0; y < CHUNK_HEIGHT; y++) { for (int z = 0; z < CHUNK_SIZE; z++) { voxels[x][y][z] = Voxel.air(); } } } } public Voxel getVoxel(int x, int y, int z) { if (isWithinBounds(x, y, z)) { return voxels[x][y][z]; } return Voxel.air(); } public void setVoxel(int x, int y, int z, Voxel voxel) { if (isWithinBounds(x, y, z)) { voxels[x][y][z] = voxel; needsRebuild = true; // Notify neighboring chunks if on border if (x == 0) notifyNeighbor(-1, 0); if (x == CHUNK_SIZE - 1) notifyNeighbor(1, 0); if (z == 0) notifyNeighbor(0, -1); if (z == CHUNK_SIZE - 1) notifyNeighbor(0, 1); } } private void notifyNeighbor(int dx, int dz) { // This would notify the world to mark neighboring chunk for rebuild // Implementation depends on world management } private boolean isWithinBounds(int x, int y, int z) { return x >= 0 && x < CHUNK_SIZE && y >= 0 && y < CHUNK_HEIGHT && z >= 0 && z < CHUNK_SIZE; } public boolean isSolid(int x, int y, int z) { Voxel voxel = getVoxel(x, y, z); return voxel != null && voxel.isSolid(); } public boolean isTransparent(int x, int y, int z) { Voxel voxel = getVoxel(x, y, z); return voxel != null && voxel.isTransparent(); } public void generateMesh() { this.mesh = new ChunkMesh(this); this.needsRebuild = false; } public ChunkMesh getMesh() { if (needsRebuild) { generateMesh(); } return mesh; } // World position conversion public int getWorldX(int localX) { return chunkX * CHUNK_SIZE + localX; } public int getWorldZ(int localZ) { return chunkZ * CHUNK_SIZE + localZ; } // Getters public int getChunkX() { return chunkX; } public int getChunkZ() { return chunkZ; } public boolean needsRebuild() { return needsRebuild; } } Mesh Generation
Chunk Mesh Generation
public class ChunkMesh { private static final int VERTEX_SIZE = 8; // x, y, z, u, v, r, g, b private static final int INDICES_PER_FACE = 6; private FloatBuffer vertexBuffer; private IntBuffer indexBuffer; private int vertexCount; private int indexCount; public ChunkMesh(Chunk chunk) { generateMesh(chunk); } private void generateMesh(Chunk chunk) { List<Float> vertices = new ArrayList<>(); List<Integer> indices = new ArrayList<>(); for (int x = 0; x < Chunk.CHUNK_SIZE; x++) { for (int y = 0; y < Chunk.CHUNK_HEIGHT; y++) { for (int z = 0; z < Chunk.CHUNK_SIZE; z++) { Voxel voxel = chunk.getVoxel(x, y, z); if (voxel.isSolid()) { generateVoxelMesh(chunk, x, y, z, voxel, vertices, indices); } } } } createBuffers(vertices, indices); } private void generateVoxelMesh(Chunk chunk, int x, int y, int z, Voxel voxel, List<Float> vertices, List<Integer> indices) { // Check each face for visibility if (shouldRenderFace(chunk, x, y - 1, z)) { // Bottom face addFace(x, y, z, Face.BOTTOM, voxel, vertices, indices); } if (shouldRenderFace(chunk, x, y + 1, z)) { // Top face addFace(x, y, z, Face.TOP, voxel, vertices, indices); } if (shouldRenderFace(chunk, x - 1, y, z)) { // West face addFace(x, y, z, Face.WEST, voxel, vertices, indices); } if (shouldRenderFace(chunk, x + 1, y, z)) { // East face addFace(x, y, z, Face.EAST, voxel, vertices, indices); } if (shouldRenderFace(chunk, x, y, z - 1)) { // North face addFace(x, y, z, Face.NORTH, voxel, vertices, indices); } if (shouldRenderFace(chunk, x, y, z + 1)) { // South face addFace(x, y, z, Face.SOUTH, voxel, vertices, indices); } } private boolean shouldRenderFace(Chunk chunk, int x, int y, int z) { // Check if the neighboring voxel exists and is transparent if (x < 0 || x >= Chunk.CHUNK_SIZE || z < 0 || z >= Chunk.CHUNK_SIZE || y < 0 || y >= Chunk.CHUNK_HEIGHT) { return true; // Face at chunk border is always visible } Voxel neighbor = chunk.getVoxel(x, y, z); return !neighbor.isSolid() || neighbor.isTransparent(); } private void addFace(int x, int y, int z, Face face, Voxel voxel, List<Float> vertices, List<Integer> indices) { int baseVertex = vertexCount; float[] faceVertices = face.getVertices(x, y, z); float[] texCoords = getTextureCoordinates(voxel, face); float[] color = getColor(voxel); // Add vertices with texture coordinates and color for (int i = 0; i < 4; i++) { // Position vertices.add(faceVertices[i * 3]); vertices.add(faceVertices[i * 3 + 1]); vertices.add(faceVertices[i * 3 + 2]); // Texture coordinates vertices.add(texCoords[i * 2]); vertices.add(texCoords[i * 2 + 1]); // Color vertices.add(color[0]); vertices.add(color[1]); vertices.add(color[2]); } // Add indices (two triangles) int[] faceIndices = face.getIndices(); for (int index : faceIndices) { indices.add(baseVertex + index); } vertexCount += 4; indexCount += 6; } private float[] getTextureCoordinates(Voxel voxel, Face face) { // Simple texture coordinates - in real implementation, // this would look up from a texture atlas switch (voxel.getType()) { case Voxel.TYPE_GRASS: if (face == Face.TOP) return new float[]{0, 0, 1, 0, 1, 1, 0, 1}; else if (face == Face.BOTTOM) return new float[]{0, 0, 1, 0, 1, 1, 0, 1}; else return new float[]{0, 0, 1, 0, 1, 1, 0, 1}; default: return new float[]{0, 0, 1, 0, 1, 1, 0, 1}; } } private float[] getColor(Voxel voxel) { // Simple color mapping switch (voxel.getType()) { case Voxel.TYPE_GRASS: return new float[]{0.2f, 0.8f, 0.2f}; case Voxel.TYPE_DIRT: return new float[]{0.5f, 0.3f, 0.1f}; case Voxel.TYPE_STONE: return new float[]{0.5f, 0.5f, 0.5f}; case Voxel.TYPE_WATER: return new float[]{0.2f, 0.2f, 0.8f}; case Voxel.TYPE_WOOD: return new float[]{0.5f, 0.3f, 0.1f}; case Voxel.TYPE_LEAVES: return new float[]{0.1f, 0.6f, 0.1f}; default: return new float[]{1.0f, 1.0f, 1.0f}; } } private void createBuffers(List<Float> vertices, List<Integer> indices) { vertexBuffer = BufferUtils.createFloatBuffer(vertices.size()); for (Float v : vertices) { vertexBuffer.put(v); } vertexBuffer.flip(); indexBuffer = BufferUtils.createIntBuffer(indices.size()); for (Integer i : indices) { indexBuffer.put(i); } indexBuffer.flip(); } public void render() { if (vertexCount == 0) return; // Enable vertex arrays glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glEnableClientState(GL_COLOR_ARRAY); // Set pointers glVertexPointer(3, GL_FLOAT, VERTEX_SIZE * Float.BYTES, vertexBuffer); glTexCoordPointer(2, GL_FLOAT, VERTEX_SIZE * Float.BYTES, vertexBuffer.position(3)); glColorPointer(3, GL_FLOAT, VERTEX_SIZE * Float.BYTES, vertexBuffer.position(5)); // Draw glDrawElements(GL_TRIANGLES, indexBuffer); // Disable vertex arrays glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glDisableClientState(GL_COLOR_ARRAY); } public void cleanup() { // Clean up buffers if (vertexBuffer != null) { vertexBuffer.clear(); } if (indexBuffer != null) { indexBuffer.clear(); } } } enum Face { TOP, BOTTOM, NORTH, SOUTH, EAST, WEST; public float[] getVertices(float x, float y, float z) { switch (this) { case TOP: return new float[]{ x, y + 1, z, // 0 x + 1, y + 1, z, // 1 x + 1, y + 1, z + 1,// 2 x, y + 1, z + 1 // 3 }; case BOTTOM: return new float[]{ x, y, z, // 0 x, y, z + 1, // 1 x + 1, y, z + 1, // 2 x + 1, y, z // 3 }; case NORTH: return new float[]{ x, y, z, // 0 x, y + 1, z, // 1 x + 1, y + 1, z, // 2 x + 1, y, z // 3 }; case SOUTH: return new float[]{ x, y, z + 1, // 0 x + 1, y, z + 1, // 1 x + 1, y + 1, z + 1,// 2 x, y + 1, z + 1 // 3 }; case EAST: return new float[]{ x + 1, y, z, // 0 x + 1, y + 1, z, // 1 x + 1, y + 1, z + 1,// 2 x + 1, y, z + 1 // 3 }; case WEST: return new float[]{ x, y, z, // 0 x, y, z + 1, // 1 x, y + 1, z + 1, // 2 x, y + 1, z // 3 }; default: return new float[12]; } } public int[] getIndices() { return new int[]{0, 1, 2, 0, 2, 3}; } } World Management
World Class
public class VoxelWorld { private final Map<Long, Chunk> chunks; private final int renderDistance; private final WorldGenerator worldGenerator; public VoxelWorld(int renderDistance) { this.chunks = new ConcurrentHashMap<>(); this.renderDistance = renderDistance; this.worldGenerator = new WorldGenerator(); } public Chunk getChunk(int chunkX, int chunkZ) { long key = getChunkKey(chunkX, chunkZ); return chunks.computeIfAbsent(key, k -> { Chunk chunk = new Chunk(chunkX, chunkZ); worldGenerator.generateChunk(chunk); return chunk; }); } public Voxel getVoxel(int worldX, int worldY, int worldZ) { int chunkX = worldX >> 4; // Divide by 16 int chunkZ = worldZ >> 4; int localX = worldX & 0xF; // Modulo 16 int localZ = worldZ & 0xF; Chunk chunk = getChunk(chunkX, chunkZ); return chunk.getVoxel(localX, worldY, localZ); } public void setVoxel(int worldX, int worldY, int worldZ, Voxel voxel) { int chunkX = worldX >> 4; int chunkZ = worldZ >> 4; int localX = worldX & 0xF; int localZ = worldZ & 0xF; Chunk chunk = getChunk(chunkX, chunkZ); chunk.setVoxel(localX, worldY, localZ, voxel); } public void updateChunks(int playerChunkX, int playerChunkZ) { // Load chunks within render distance for (int x = -renderDistance; x <= renderDistance; x++) { for (int z = -renderDistance; z <= renderDistance; z++) { int chunkX = playerChunkX + x; int chunkZ = playerChunkZ + z; getChunk(chunkX, chunkZ); // This will generate if needed } } // Unload chunks outside render distance chunks.entrySet().removeIf(entry -> { long key = entry.getKey(); int chunkX = (int)(key >> 32); int chunkZ = (int)(key & 0xFFFFFFFF); int distX = Math.abs(chunkX - playerChunkX); int distZ = Math.abs(chunkZ - playerChunkZ); return distX > renderDistance || distZ > renderDistance; }); } public void render() { for (Chunk chunk : chunks.values()) { ChunkMesh mesh = chunk.getMesh(); if (mesh != null) { glPushMatrix(); glTranslatef(chunk.getChunkX() * Chunk.CHUNK_SIZE, 0, chunk.getChunkZ() * Chunk.CHUNK_SIZE); mesh.render(); glPopMatrix(); } } } private long getChunkKey(int x, int z) { return ((long)x << 32) | (z & 0xFFFFFFFFL); } public void cleanup() { for (Chunk chunk : chunks.values()) { ChunkMesh mesh = chunk.getMesh(); if (mesh != null) { mesh.cleanup(); } } chunks.clear(); } } World Generation
Procedural World Generator
public class WorldGenerator { private final SimplexNoise terrainNoise; private final SimplexNoise caveNoise; private final Random random; public WorldGenerator() { this.random = new Random(); this.terrainNoise = new SimplexNoise(12345); this.caveNoise = new SimplexNoise(67890); } public void generateChunk(Chunk chunk) { int chunkX = chunk.getChunkX(); int chunkZ = chunk.getChunkZ(); for (int x = 0; x < Chunk.CHUNK_SIZE; x++) { for (int z = 0; z < Chunk.CHUNK_SIZE; z++) { int worldX = chunk.getWorldX(x); int worldZ = chunk.getWorldZ(z); generateColumn(chunk, x, z, worldX, worldZ); } } } private void generateColumn(Chunk chunk, int x, int z, int worldX, int worldZ) { // Generate terrain height double noise = terrainNoise.noise(worldX * 0.01, worldZ * 0.01); double roughness = terrainNoise.noise(worldX * 0.05, worldZ * 0.05) * 0.3; int height = (int)((noise * 0.5 + 0.5) * 30 + roughness * 10 + 50); // Generate caves boolean hasCave = caveNoise.noise(worldX * 0.1, worldZ * 0.1) > 0.2; for (int y = 0; y < Chunk.CHUNK_HEIGHT; y++) { Voxel voxel = determineVoxel(y, height, hasCave, worldX, worldZ, y); chunk.setVoxel(x, y, z, voxel); } } private Voxel determineVoxel(int y, int surfaceHeight, boolean hasCave, int worldX, int worldZ, int localY) { if (y > surfaceHeight) { if (y < 60) return Voxel.water(); return Voxel.air(); } // Generate caves if (hasCave && y < surfaceHeight - 5 && y > 10) { double caveNoise = caveNoise.noise(worldX * 0.2, localY * 0.2, worldZ * 0.2); if (caveNoise > 0.1) { return Voxel.air(); } } // Surface layers if (y == surfaceHeight) { return Voxel.grass(); } else if (y > surfaceHeight - 5) { return Voxel.dirt(); } else if (y > surfaceHeight - 15) { // Occasionally generate ore veins if (random.nextFloat() < 0.02f) { return Voxel.stone(); // In real implementation, this would be an ore type } return Voxel.stone(); } else { return Voxel.stone(); } } public void generateTree(Chunk chunk, int x, int y, int z) { // Generate trunk int trunkHeight = 4 + random.nextInt(3); for (int i = 0; i < trunkHeight; i++) { if (y + i < Chunk.CHUNK_HEIGHT) { chunk.setVoxel(x, y + i, z, Voxel.wood()); } } // Generate leaves int leafStart = y + trunkHeight - 2; for (int dx = -2; dx <= 2; dx++) { for (int dz = -2; dz <= 2; dz++) { for (int dy = 0; dy < 3; dy++) { if (Math.abs(dx) + Math.abs(dz) + Math.abs(dy) <= 3) { int leafX = x + dx; int leafY = leafStart + dy; int leafZ = z + dz; if (leafX >= 0 && leafX < Chunk.CHUNK_SIZE && leafZ >= 0 && leafZ < Chunk.CHUNK_SIZE && leafY < Chunk.CHUNK_HEIGHT) { chunk.setVoxel(leafX, leafY, leafZ, Voxel.leaves()); } } } } } } } // Simple Simplex Noise implementation public class SimplexNoise { private final PermutationTable permutation; public SimplexNoise(long seed) { this.permutation = new PermutationTable(seed); } public double noise(double x, double y) { // 2D Simplex noise implementation // Simplified version - full implementation would be more complex double s = (x + y) * 0.366025403784439; // F2 int i = (int)Math.floor(x + s); int j = (int)Math.floor(y + s); double t = (i + j) * 0.211324865405187; // G2 double X0 = i - t; double Y0 = j - t; double x0 = x - X0; double y0 = y - Y0; // Determine which simplex we're in int i1, j1; if (x0 > y0) { i1 = 1; j1 = 0; } else { i1 = 0; j1 = 1; } double x1 = x0 - i1 + 0.211324865405187; double y1 = y0 - j1 + 0.211324865405187; double x2 = x0 - 1.0 + 0.422649730810374; double y2 = y0 - 1.0 + 0.422649730810374; // Calculate contributions double n0, n1, n2; double t0 = 0.5 - x0 * x0 - y0 * y0; if (t0 < 0) n0 = 0; else { t0 *= t0; n0 = t0 * t0 * grad(permutation.perm(i, j), x0, y0); } double t1 = 0.5 - x1 * x1 - y1 * y1; if (t1 < 0) n1 = 0; else { t1 *= t1; n1 = t1 * t1 * grad(permutation.perm(i + i1, j + j1), x1, y1); } double t2 = 0.5 - x2 * x2 - y2 * y2; if (t2 < 0) n2 = 0; else { t2 *= t2; n2 = t2 * t2 * grad(permutation.perm(i + 1, j + 1), x2, y2); } return 70.0 * (n0 + n1 + n2); } public double noise(double x, double y, double z) { // 3D Simplex noise - similar structure but more complex // Implementation would follow similar pattern to 2D but with 3 dimensions return noise(x, y); // Simplified for prototype } private double grad(int hash, double x, double y) { int h = hash & 7; double u = h < 4 ? x : y; double v = h < 4 ? y : x; return ((h & 1) != 0 ? -u : u) + ((h & 2) != 0 ? -2.0 * v : 2.0 * v); } private static class PermutationTable { private final int[] perm; public PermutationTable(long seed) { perm = new int[512]; Random random = new Random(seed); int[] p = new int[256]; for (int i = 0; i < 256; i++) p[i] = i; // Shuffle array for (int i = 0; i < 256; i++) { int j = random.nextInt(256 - i) + i; int temp = p[i]; p[i] = p[j]; p[j] = temp; } // Duplicate for overflow for (int i = 0; i < 512; i++) { perm[i] = p[i & 255]; } } public int perm(int x, int y) { return perm[perm[x] + y]; } public int perm(int x, int y, int z) { return perm[perm[perm[x] + y] + z]; } } } Player and Camera System
First-Person Camera
public class Player { private Vector3f position; private Vector3f velocity; private float yaw, pitch; private boolean onGround; private final AABB boundingBox; public Player() { this.position = new Vector3f(0, 70, 0); this.velocity = new Vector3f(0, 0, 0); this.boundingBox = new AABB(-0.3f, 0, -0.3f, 0.3f, 1.8f, 0.3f); } public void update(float deltaTime, VoxelWorld world) { // Apply gravity if (!onGround) { velocity.y -= 20.0f * deltaTime; // Gravity } // Update position Vector3f newPosition = new Vector3f(position); newPosition.x += velocity.x * deltaTime; newPosition.y += velocity.y * deltaTime; newPosition.z += velocity.z * deltaTime; // Check collisions handleCollisions(world, newPosition); // Apply friction velocity.x *= 0.9f; velocity.z *= 0.9f; } private void handleCollisions(VoxelWorld world, Vector3f newPosition) { AABB newBoundingBox = boundingBox.translate(newPosition); // Check Y collision (vertical) if (velocity.y < 0) { // Falling if (checkCollision(world, newBoundingBox.offset(0, -0.1f, 0))) { velocity.y = 0; onGround = true; newPosition.y = (float)Math.floor(newPosition.y); } else { onGround = false; } } else if (velocity.y > 0) { // Jumping if (checkCollision(world, newBoundingBox.offset(0, 0.1f, 0))) { velocity.y = 0; } } // Check X collision if (checkCollision(world, newBoundingBox.offset(velocity.x * 0.1f, 0, 0))) { velocity.x = 0; } // Check Z collision if (checkCollision(world, newBoundingBox.offset(0, 0, velocity.z * 0.1f))) { velocity.z = 0; } position.set(newPosition); } private boolean checkCollision(VoxelWorld world, AABB box) { int minX = (int)Math.floor(box.minX); int maxX = (int)Math.ceil(box.maxX); int minY = (int)Math.floor(box.minY); int maxY = (int)Math.ceil(box.maxY); int minZ = (int)Math.floor(box.minZ); int maxZ = (int)Math.ceil(box.maxZ); for (int x = minX; x <= maxX; x++) { for (int y = minY; y <= maxY; y++) { for (int z = minZ; z <= maxZ; z++) { Voxel voxel = world.getVoxel(x, y, z); if (voxel.isSolid()) { AABB voxelBox = new AABB(x, y, z, x + 1, y + 1, z + 1); if (box.intersects(voxelBox)) { return true; } } } } } return false; } public void move(float forward, float strafe) { float cosYaw = (float)Math.cos(Math.toRadians(yaw)); float sinYaw = (float)Math.sin(Math.toRadians(yaw)); velocity.x = forward * sinYaw + strafe * cosYaw; velocity.z = -forward * cosYaw + strafe * sinYaw; // Normalize if moving diagonally if (forward != 0 && strafe != 0) { velocity.x *= 0.707f; velocity.z *= 0.707f; } // Apply speed float speed = 5.0f; velocity.x *= speed; velocity.z *= speed; } public void jump() { if (onGround) { velocity.y = 8.0f; onGround = false; } } public void setOrientation(float yaw, float pitch) { this.yaw = yaw; this.pitch = Math.max(-89, Math.min(89, pitch)); } public Matrix4f getViewMatrix() { Matrix4f matrix = new Matrix4f(); matrix.setIdentity(); matrix.rotate((float)Math.toRadians(pitch), new Vector3f(1, 0, 0)); matrix.rotate((float)Math.toRadians(yaw), new Vector3f(0, 1, 0)); matrix.translate(new Vector3f(-position.x, -position.y, -position.z)); return matrix; } // Getters public Vector3f getPosition() { return position; } public float getYaw() { return yaw; } public float getPitch() { return pitch; } } public class AABB { public final float minX, minY, minZ; public final float maxX, maxY, maxZ; public AABB(float minX, float minY, float minZ, float maxX, float maxY, float maxZ) { this.minX = minX; this.minY = minY; this.minZ = minZ; this.maxX = maxX; this.maxY = maxY; this.maxZ = maxZ; } public AABB translate(Vector3f translation) { return new AABB( minX + translation.x, minY + translation.y, minZ + translation.z, maxX + translation.x, maxY + translation.y, maxZ + translation.z ); } public AABB offset(float dx, float dy, float dz) { return new AABB(minX + dx, minY + dy, minZ + dz, maxX + dx, maxY + dy, maxZ + dz); } public boolean intersects(AABB other) { return minX < other.maxX && maxX > other.minX && minY < other.maxY && maxY > other.minY && minZ < other.maxZ && maxZ > other.minZ; } } Main Game Loop
Voxel Engine Main Class
public class VoxelEngine { private VoxelWorld world; private Player player; private boolean running; private int windowWidth = 800; private int windowHeight = 600; public VoxelEngine() { try { initialize(); run(); } finally { cleanup(); } } private void initialize() { // Initialize GLFW if (!glfwInit()) { throw new IllegalStateException("Unable to initialize GLFW"); } // Create window long window = glfwCreateWindow(windowWidth, windowHeight, "Voxel Engine", 0, 0); if (window == 0) { throw new RuntimeException("Failed to create GLFW window"); } glfwMakeContextCurrent(window); GL.createCapabilities(); // Initialize systems world = new VoxelWorld(8); // 8 chunk render distance player = new Player(); // Set up OpenGL glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); glCullFace(GL_BACK); // Set up callbacks setupCallbacks(window); running = true; } private void setupCallbacks(long window) { glfwSetKeyCallback(window, (win, key, scancode, action, mods) -> { if (key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE) { running = false; } }); glfwSetCursorPosCallback(window, (win, xpos, ypos) -> { handleMouseInput(xpos, ypos); }); glfwSetMouseButtonCallback(window, (win, button, action, mods) -> { handleMouseClick(button, action); }); } private void handleMouseInput(double xpos, double ypos) { // Calculate mouse movement float deltaX = (float)(xpos - windowWidth / 2); float deltaY = (float)(ypos - windowHeight / 2); // Update player orientation player.setOrientation( player.getYaw() + deltaX * 0.1f, player.getPitch() + deltaY * 0.1f ); // Reset cursor to center glfwSetCursorPos(glfwGetCurrentContext(), windowWidth / 2, windowHeight / 2); } private void handleMouseClick(int button, int action) { if (action == GLFW_PRESS) { if (button == GLFW_MOUSE_BUTTON_LEFT) { // Break block breakBlock(); } else if (button == GLFW_MOUSE_BUTTON_RIGHT) { // Place block placeBlock(); } } } private void breakBlock() { Vector3f position = player.getPosition(); Vector3f direction = getLookDirection(); // Raycast to find targeted block for (float t = 0; t < 5; t += 0.1f) { int x = (int)Math.floor(position.x + direction.x * t); int y = (int)Math.floor(position.y + direction.y * t); int z = (int)Math.floor(position.z + direction.z * t); Voxel voxel = world.getVoxel(x, y, z); if (voxel.isSolid()) { world.setVoxel(x, y, z, Voxel.air()); break; } } } private void placeBlock() { Vector3f position = player.getPosition(); Vector3f direction = getLookDirection(); // Raycast to find placement position Integer lastX = null, lastY = null, lastZ = null; for (float t = 0; t < 5; t += 0.1f) { int x = (int)Math.floor(position.x + direction.x * t); int y = (int)Math.floor(position.y + direction.y * t); int z = (int)Math.floor(position.z + direction.z * t); Voxel voxel = world.getVoxel(x, y, z); if (voxel.isSolid()) { if (lastX != null) { // Place block at previous position (adjacent to hit block) world.setVoxel(lastX, lastY, lastZ, Voxel.stone()); } break; } lastX = x; lastY = y; lastZ = z; } } private Vector3f getLookDirection() { float yaw = (float)Math.toRadians(player.getYaw()); float pitch = (float)Math.toRadians(player.getPitch()); return new Vector3f( (float)(Math.sin(yaw) * Math.cos(pitch)), (float)(-Math.sin(pitch)), (float)(-Math.cos(yaw) * Math.cos(pitch)) ); } private void run() { long window = glfwGetCurrentContext(); long lastTime = System.nanoTime(); while (running && !glfwWindowShouldClose(window)) { long currentTime = System.nanoTime(); float deltaTime = (currentTime - lastTime) / 1_000_000_000.0f; lastTime = currentTime; // Handle input handleKeyboardInput(window, deltaTime); // Update update(deltaTime); // Render render(); glfwSwapBuffers(window); glfwPollEvents(); } } private void handleKeyboardInput(long window, float deltaTime) { float forward = 0, strafe = 0; if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) forward += 1; if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) forward -= 1; if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) strafe -= 1; if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) strafe += 1; if (glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_PRESS) player.jump(); player.move(forward, strafe); } private void update(float deltaTime) { player.update(deltaTime, world); // Update chunks around player Vector3f playerPos = player.getPosition(); int playerChunkX = (int)Math.floor(playerPos.x) >> 4; int playerChunkZ = (int)Math.floor(playerPos.z) >> 4; world.updateChunks(playerChunkX, playerChunkZ); } private void render() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Set up projection matrix Matrix4f projection = new Matrix4f(); projection.perspective((float)Math.toRadians(70), (float)windowWidth / windowHeight, 0.1f, 1000.0f); // Set up view matrix from player Matrix4f view = player.getViewMatrix(); // Set up model-view-projection matrix Matrix4f mvp = new Matrix4f(projection); mvp.mul(view); // Render world glLoadMatrixf(mvp.get(new float[16])); world.render(); } private void cleanup() { if (world != null) { world.cleanup(); } glfwTerminate(); } public static void main(String[] args) { new VoxelEngine(); } } Performance Optimizations
Frustum Culling and LOD
public class ChunkManager { private final VoxelWorld world; private final Map<Long, Chunk> loadedChunks; private final List<Chunk> visibleChunks; public ChunkManager(VoxelWorld world) { this.world = world; this.loadedChunks = new HashMap<>(); this.visibleChunks = new ArrayList<>(); } public void updateVisibility(Frustum frustum, Vector3f cameraPos) { visibleChunks.clear(); for (Chunk chunk : loadedChunks.values()) { AABB chunkBounds = getChunkAABB(chunk); if (frustum.intersects(chunkBounds)) { // Calculate distance for LOD float distance = distanceToChunk(cameraPos, chunk); // Simple LOD: skip meshing for distant chunks if (distance < 100) { // Only mesh chunks within 100 units visibleChunks.add(chunk); } } } } private AABB getChunkAABB(Chunk chunk) { float minX = chunk.getChunkX() * Chunk.CHUNK_SIZE; float minZ = chunk.getChunkZ() * Chunk.CHUNK_SIZE; float maxX = minX + Chunk.CHUNK_SIZE; float maxZ = minZ + Chunk.CHUNK_SIZE; return new AABB(minX, 0, minZ, maxX, Chunk.CHUNK_HEIGHT, maxZ); } private float distanceToChunk(Vector3f cameraPos, Chunk chunk) { float chunkCenterX = (chunk.getChunkX() + 0.5f) * Chunk.CHUNK_SIZE; float chunkCenterZ = (chunk.getChunkZ() + 0.5f) * Chunk.CHUNK_SIZE; float dx = cameraPos.x - chunkCenterX; float dz = cameraPos.z - chunkCenterZ; return (float)Math.sqrt(dx * dx + dz * dz); } } public class Frustum { private final Plane[] planes; public Frustum() { this.planes = new Plane[6]; for (int i = 0; i < 6; i++) { planes[i] = new Plane(); } } public void update(Matrix4f mvp) { // Extract frustum planes from model-view-projection matrix // Left plane planes[0].set(mvp.m03 + mvp.m00, mvp.m13 + mvp.m10, mvp.m23 + mvp.m20, mvp.m33 + mvp.m30); // Right plane planes[1].set(mvp.m03 - mvp.m00, mvp.m13 - mvp.m10, mvp.m23 - mvp.m20, mvp.m33 - mvp.m30); // Bottom plane planes[2].set(mvp.m03 + mvp.m01, mvp.m13 + mvp.m11, mvp.m23 + mvp.m21, mvp.m33 + mvp.m31); // Top plane planes[3].set(mvp.m03 - mvp.m01, mvp.m13 - mvp.m11, mvp.m23 - mvp.m21, mvp.m33 - mvp.m31); // Near plane planes[4].set(mvp.m03 + mvp.m02, mvp.m13 + mvp.m12, mvp.m23 + mvp.m22, mvp.m33 + mvp.m32); // Far plane planes[5].set(mvp.m03 - mvp.m02, mvp.m13 - mvp.m12, mvp.m23 - mvp.m22, mvp.m33 - mvp.m32); // Normalize all planes for (Plane plane : planes) { plane.normalize(); } } public boolean intersects(AABB aabb) { for (Plane plane : planes) { if (plane.distanceToPoint(getPositiveVertex(aabb, plane)) < 0) { return false; } } return true; } private Vector3f getPositiveVertex(AABB aabb, Plane plane) { Vector3f vertex = new Vector3f(); vertex.x = plane.normal.x > 0 ? aabb.maxX : aabb.minX; vertex.y = plane.normal.y > 0 ? aabb.maxY : aabb.minY; vertex.z = plane.normal.z > 0 ? aabb.maxZ : aabb.minZ; return vertex; } } public class Plane { public float a, b, c, d; public void set(float a, float b, float c, float d) { this.a = a; this.b = b; this.c = c; this.d = d; } public void normalize() { float length = (float)Math.sqrt(a * a + b * b + c * c); if (length > 0) { float invLength = 1.0f / length; a *= invLength; b *= invLength; c *= invLength; d *= invLength; } } public float distanceToPoint(Vector3f point) { return a * point.x + b * point.y + c * point.z + d; } } This voxel engine prototype provides a solid foundation for building Minecraft-like games or other voxel-based applications. Key features include chunk-based world management, greedy meshing for efficient rendering, procedural world generation, collision detection, and basic player interaction. The architecture is designed to be extensible for adding features like lighting, fluids, and more advanced world generation.