diff --git a/src/engine/voxel/index.js b/src/engine/voxel/index.js index 970221f..9b47a5b 100644 --- a/src/engine/voxel/index.js +++ b/src/engine/voxel/index.js @@ -1,5 +1,5 @@ import { Mesh } from '../mesh' -import { MeshInstance } from '../components' +import { Node, MeshInstance } from '../components' import { addv3 } from '../utility' const FACE_VERTEX = [ @@ -74,8 +74,9 @@ const AIR_VOXEL = new Voxel(0) const GROUND_VOXEL = new Voxel(1) class VoxelChunk extends MeshInstance { - constructor (pos, size = 16) { - super(null, pos) + constructor (origin, pos, size = 16) { + super(null, [origin[0] + pos[0] * size, origin[1] + pos[1] * size, origin[2] + pos[2] * size]) + this.relativePos = pos this.size = size this.mesh = null @@ -90,24 +91,77 @@ class VoxelChunk extends MeshInstance { } 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.data[x][y][z] + if (this.parent) { + let neighbor + if (x < 0) { + neighbor = this.parent.getChunk(this.relativePos[0] - 1, this.relativePos[1], this.relativePos[2]) + if (neighbor) { + return neighbor.getVoxel(this.size + x, y, z) + } + return AIR_VOXEL + } else if (x >= this.size) { + neighbor = this.parent.getChunk(this.relativePos[0] + 1, this.relativePos[1], this.relativePos[2]) + if (neighbor) { + return neighbor.getVoxel(x - this.size, y, z) + } + return AIR_VOXEL + } else if (y < 0) { + neighbor = this.parent.getChunk(this.relativePos[0], this.relativePos[1] - 1, this.relativePos[2]) + if (neighbor) { + return neighbor.getVoxel(x, this.size + y, z) + } + return AIR_VOXEL + } else if (y >= this.size) { + neighbor = this.parent.getChunk(this.relativePos[0], this.relativePos[1] + 1, this.relativePos[2]) + if (neighbor) { + return neighbor.getVoxel(x, y - this.size, z) + } + return AIR_VOXEL + } else if (z < 0) { + neighbor = this.parent.getChunk(this.relativePos[0], this.relativePos[1], this.relativePos[2] - 1) + if (neighbor) { + return neighbor.getVoxel(x, y, this.size + z) + } + return AIR_VOXEL + } else if (z >= this.size) { + neighbor = this.parent.getChunk(this.relativePos[0], this.relativePos[1], this.relativePos[2] + 1) + if (neighbor) { + return neighbor.getVoxel(x, y, z - this.size) + } + return AIR_VOXEL + } + } + + return this.data[x][z][y] + } + + getVoxelv (v) { + return this.getVoxel(v[0], v[1], v[2]) } generate (generator) { this.data = {} for (let x = 0; x < this.size; x++) { if (!this.data[x]) this.data[x] = {} - for (let y = 0; y < this.size; y++) { - if (!this.data[x][y]) this.data[x][y] = {} - for (let z = 0; z < this.size; z++) { - 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 + for (let z = 0; z < this.size; z++) { + if (!this.data[x][z]) this.data[x][z] = {} + let columnHeight = generator.getHeight(x + this.pos[0], z + this.pos[2]) + for (let y = 0; y < this.size; y++) { + let solid = this.pos[1] + y < columnHeight + this.data[x][z][y] = solid ? GROUND_VOXEL : AIR_VOXEL } } } + this.generated = true + + // Make sure the parent knows that we have generated everything + if (this.parent) { + this.parent.chunksLeft-- + if (this.parent.chunksLeft <= 0) { + this.parent.generated = true + } + } } // Programmatically generate a voxel face @@ -138,7 +192,7 @@ class VoxelChunk extends MeshInstance { this.dirty = false // If there is no generated chunk, we have nothing to base a mesh off of - if (!this.generated) return + if (!this.generated) return false // If there already exists a mesh, dispose of it if (this.mesh) { @@ -184,7 +238,7 @@ class VoxelChunk extends MeshInstance { // Do not create a mesh when there are no faces in this chunk if (points.length === 0) { - return + return false } // Flatten the points array to three separate arrays @@ -206,13 +260,108 @@ class VoxelChunk extends MeshInstance { // 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 + + return true } update (gl, dt) { if (this.dirty) return this.createMesh(gl) + return false + } + + destroy (gl) { + this.generated = false + this.mesh && this.mesh.dispose(gl) + this.data = {} } } -export { Voxel, VoxelChunk } +class VoxelMapBlock extends Node { + constructor (pos, chunkSize = 16, size = 8) { + super(pos) + this.chunkSize = chunkSize + this.size = size + this.chunks = {} + this.generated = false + this.chunksLeft = size * size * size + } + + getChunk (x, y, z) { + if (x < 0 || x >= this.size || y < 0 || y >= this.size || z < 0 || z >= this.size) return null + return this.chunks[x][z][y] + } + + getChunkv (v) { + return this.getChunk(v[0], v[1], v[2]) + } + + generate (generator) { + this.chunks = {} + for (let x = 0; x < this.size; x++) { + if (!this.chunks[x]) this.chunks[x] = {} + for (let z = 0; z < this.size; z++) { + if (!this.chunks[x][z]) this.chunks[x][z] = {} + for (let y = 0; y < this.size; y++) { + if (this.chunks[x][z][y]) continue + let chunk = new VoxelChunk(this.pos, [x, y, z], this.chunkSize) + this.chunks[x][z][y] = chunk + this.addChild(chunk) + generator.pushChunk(chunk) + } + } + } + } + + destroy (gl) { + this.generated = false + for (let i in this.children) { + let ch = this.children[i] + if (!(ch instanceof VoxelChunk)) continue + ch.destroy(gl) + } + this.children = [] + this.chunks = {} + } + + update (gl, dt) { + if (!this.generated) return + for (let i in this.children) { + let ch = this.children[i] + if (!(ch instanceof VoxelChunk)) continue + if (ch.update(gl, dt)) break + } + } +} + +class VoxelGenerator { + constructor (noise, material, groundHeight = 64) { + this.material = material + this.noise = noise + this.groundHeight = groundHeight + this.generateQueue = [] + } + + // Push a chunk into the generation queue + pushChunk (chunk) { + this.generateQueue.push(chunk) + } + + // Get chunk height + getHeight (x, z) { + return this.noise.getHeight(x, z) + this.groundHeight + } + + // Generate chunks in the queue + update (dt) { + for (let i in this.generateQueue) { + let chunk = this.generateQueue[i] + if (chunk.generated) continue + + chunk.material = this.material + chunk.generate(this) + } + } +} + +export { VoxelGenerator, Voxel, VoxelChunk, VoxelMapBlock } diff --git a/src/index.js b/src/index.js index 5aa9b57..62141e6 100644 --- a/src/index.js +++ b/src/index.js @@ -13,7 +13,7 @@ import { SimplexHeightMap } from './engine/components/terrain/heightmap' import { Material, Texture } from './engine/mesh/material' import { GUIRenderer, GUIImage, Dim4 } from './engine/gui' import { FontRenderer, GUIText, Font } from './engine/gui/font' -import { VoxelChunk } from './engine/voxel' +import { VoxelMapBlock, VoxelGenerator } from './engine/voxel' let game = Engine let env = new Environment() @@ -87,9 +87,9 @@ async function pipeline () { await skybox.initialize(game.gl) // Voxel test - let chunk = new VoxelChunk([0.0, 0.0, 0.0]) - chunk.generate(hmap) - chunk.material = material + let voxgen = new VoxelGenerator(hmap, material) + let block = new VoxelMapBlock([0.0, 0.0, 0.0]) + block.generate(voxgen) // Update function for camera and terrain let fpsTimer = 0 @@ -132,7 +132,8 @@ async function pipeline () { // waterRenderer.update(dt) // Update voxel chunk - chunk.update(game.gl, dt) + voxgen.update(dt) + block.update(game.gl, dt) // Set text to FPS fpsTimer++ @@ -160,7 +161,7 @@ async function pipeline () { // entity.draw(gl, terrainShader) - chunk.draw(gl, terrainShader) + block.draw(gl, terrainShader) } // Render function for the triangle