diff --git a/src/engine/voxel/index.js b/src/engine/voxel/index.js index 422f874..970221f 100644 --- a/src/engine/voxel/index.js +++ b/src/engine/voxel/index.js @@ -1,6 +1,6 @@ import { Mesh } from '../mesh' import { MeshInstance } from '../components' -import { addv3, crossv3, subv3 } from '../utility' +import { addv3 } from '../utility' const FACE_VERTEX = [ // Bottom @@ -8,34 +8,39 @@ const FACE_VERTEX = [ [1.0, 0.0, 0.0], [1.0, 0.0, 1.0], [0.0, 0.0, 1.0], - [0.0, 0.0, 0.0] + [0.0, 0.0, 0.0], + [0.0, -1.0, 0.0] ], // Top [ [0.0, 1.0, 0.0], [0.0, 1.0, 1.0], [1.0, 1.0, 1.0], - [1.0, 1.0, 0.0] + [1.0, 1.0, 0.0], + [0.0, 1.0, 0.0] ], // Left [ [0.0, 0.0, 1.0], [0.0, 1.0, 1.0], [0.0, 1.0, 0.0], - [0.0, 0.0, 0.0] + [0.0, 0.0, 0.0], + [-1.0, 0.0, 0.0] ], // Right [ [1.0, 0.0, 0.0], [1.0, 1.0, 0.0], [1.0, 1.0, 1.0], - [1.0, 0.0, 1.0] + [1.0, 0.0, 1.0], + [1.0, 0.0, 0.0] ], // Front [ [1.0, 0.0, 1.0], [1.0, 1.0, 1.0], [0.0, 1.0, 1.0], + [0.0, 0.0, 1.0], [0.0, 0.0, 1.0] ], // Back @@ -43,7 +48,8 @@ const FACE_VERTEX = [ [0.0, 0.0, 0.0], [0.0, 1.0, 0.0], [1.0, 1.0, 0.0], - [1.0, 0.0, 0.0] + [1.0, 0.0, 0.0], + [0.0, 0.0, -1.0] ] ] @@ -65,130 +71,123 @@ class Voxel { } const AIR_VOXEL = new Voxel(0) +const GROUND_VOXEL = new Voxel(1) class VoxelChunk extends MeshInstance { constructor (pos, size = 16) { super(null, pos) this.size = size - this.array = {} - this.mesh = null + + // Voxel data + this.data = {} + + // Set to true when this chunk mesh requires to be recreated this.dirty = true + + // Set to true when the generation has been finished + this.generated = false } getVoxel (x, y, z) { if (x < 0 || x >= this.size || y < 0 || y >= this.size || z < 0 || z >= this.size) return AIR_VOXEL - return this.array[x][y][z] + return this.data[x][y][z] } - generate (noise) { - this.array = {} + generate (generator) { + this.data = {} for (let x = 0; x < this.size; x++) { - if (!this.array[x]) this.array[x] = {} + if (!this.data[x]) this.data[x] = {} for (let y = 0; y < this.size; y++) { - if (!this.array[x][y]) this.array[x][y] = {} + if (!this.data[x][y]) this.data[x][y] = {} for (let z = 0; z < this.size; z++) { - if (!this.array[x][y][z]) this.array[x][y][z] = {} - let solid = y < (noise.getHeight(x + this.pos[0], z + this.pos[2]) + 10) - this.array[x][y][z] = new Voxel(solid ? 1 : 0) + if (!this.data[x][y][z]) this.data[x][y][z] = {} + let solid = y < (generator.getHeight(x + this.pos[0], z + this.pos[2]) + 10) + this.data[x][y][z] = solid ? GROUND_VOXEL : AIR_VOXEL } } } + this.generated = true } // Programmatically generate a voxel face // Returns the position, normal and texture coordinates for each vertex in this face - createFace (verts, pos, face) { - let posi = [ + createFace (points, pos, face) { + // Add the corresponding offsets for this face to the position + let corners = [ addv3(pos, FACE_VERTEX[face][0]), addv3(pos, FACE_VERTEX[face][1]), addv3(pos, FACE_VERTEX[face][2]), addv3(pos, FACE_VERTEX[face][3]) ] - let normal = crossv3(subv3(posi[2], posi[0]), subv3(posi[3], posi[1])) - verts.push([posi[0], normal, [0.0, 1.0]]) - verts.push([posi[1], normal, [0.0, 0.0]]) - verts.push([posi[2], normal, [1.0, 0.0]]) - verts.push([posi[0], normal, [0.0, 1.0]]) - verts.push([posi[2], normal, [1.0, 0.0]]) - verts.push([posi[3], normal, [1.0, 1.0]]) + + // Select the normal for this face + let normal = FACE_VERTEX[face][4] + + // Return the 6 vertices that make up this face (two triangles) + // They're named points because this function returns not only vertices, + // but corresponding texture coordinates and normals at the same time for convenience + points.push([corners[0], normal, [0.0, 1.0]]) + points.push([corners[1], normal, [0.0, 0.0]]) + points.push([corners[2], normal, [1.0, 0.0]]) + points.push([corners[0], normal, [0.0, 1.0]]) + points.push([corners[2], normal, [1.0, 0.0]]) + points.push([corners[3], normal, [1.0, 1.0]]) } createMesh (gl) { + // Makes sure the createMesh function is not called again while it is generating this.dirty = false + // If there is no generated chunk, we have nothing to base a mesh off of + if (!this.generated) return + + // If there already exists a mesh, dispose of it if (this.mesh) { this.mesh.dispose(gl) } + // Array of vertices with texture positions and normals let points = [] + // Generate face quads for each voxel in the chunk for (let x = 0; x < this.size; x++) { for (let y = 0; y < this.size; y++) { for (let z = 0; z < this.size; z++) { + let cellPos = [x, y, z] if (!this.getVoxel(x, y, z).solid) continue - let faceLeft = true - if (x > 0) { - faceLeft = !this.getVoxel(x - 1, y, z).solid - } - - let faceRight = true - if (x < this.size - 1) { - faceRight = !this.getVoxel(x + 1, y, z).solid - } - - let faceBottom = true - if (y > 0) { - faceBottom = !this.getVoxel(x, y - 1, z).solid - } - - let faceTop = true - if (y < this.size - 1) { - faceTop = !this.getVoxel(x, y + 1, z).solid - } - - let faceBack = true - if (z > 0) { - faceBack = !this.getVoxel(x, y, z - 1).solid - } - - let faceFront = true - if (z < this.size - 1) { - faceFront = !this.getVoxel(x, y, z + 1).solid - } - - let cellPos = [x, y, z] - - if (faceBottom) { + if (!this.getVoxel(x, y - 1, z).solid) { this.createFace(points, cellPos, FACE_BOTTOM) } - if (faceTop) { + if (!this.getVoxel(x, y + 1, z).solid) { this.createFace(points, cellPos, FACE_TOP) } - if (faceLeft) { + if (!this.getVoxel(x - 1, y, z).solid) { this.createFace(points, cellPos, FACE_LEFT) } - if (faceRight) { + if (!this.getVoxel(x + 1, y, z).solid) { this.createFace(points, cellPos, FACE_RIGHT) } - if (faceFront) { + if (!this.getVoxel(x, y, z + 1).solid) { this.createFace(points, cellPos, FACE_FRONT) } - if (faceBack) { + if (!this.getVoxel(x, y, z - 1).solid) { this.createFace(points, cellPos, FACE_BACK) } } } } + // Do not create a mesh when there are no faces in this chunk if (points.length === 0) { return } + // Flatten the points array to three separate arrays let vertices = [] let normals = [] let uvs = [] @@ -204,11 +203,15 @@ class VoxelChunk extends MeshInstance { uvs.push(vert[2][1]) } + // Create a new mesh without an element array buffer (TODO maybe?) this.mesh = Mesh.construct(gl, vertices, null, uvs, normals) + + // TODO: Temporary.. + if (this.material) this.mesh.material = this.material } - update (dt) { - + update (gl, dt) { + if (this.dirty) return this.createMesh(gl) } } diff --git a/src/index.js b/src/index.js index 1ce241b..5aa9b57 100644 --- a/src/index.js +++ b/src/index.js @@ -89,8 +89,7 @@ async function pipeline () { // Voxel test let chunk = new VoxelChunk([0.0, 0.0, 0.0]) chunk.generate(hmap) - chunk.createMesh(game.gl) - chunk.mesh.material = material + chunk.material = material // Update function for camera and terrain let fpsTimer = 0 @@ -132,6 +131,9 @@ async function pipeline () { // Ripple water // waterRenderer.update(dt) + // Update voxel chunk + chunk.update(game.gl, dt) + // Set text to FPS fpsTimer++ if (fpsTimer === 10) {