From ab35890f1bb99f3fe795cf0dd9ebc45590788d74 Mon Sep 17 00:00:00 2001 From: Evert Prants Date: Thu, 9 Apr 2020 15:57:00 +0300 Subject: [PATCH] some voxel bs --- src/engine/components/terrain/heightmap.js | 2 +- src/engine/utility.js | 14 + src/engine/voxel/index.js | 289 ++++++++------------- src/engine/voxel/voxeldata.js | 27 ++ src/engine/voxel/voxeltexture.js | 9 + 5 files changed, 160 insertions(+), 181 deletions(-) create mode 100644 src/engine/voxel/voxeldata.js create mode 100644 src/engine/voxel/voxeltexture.js diff --git a/src/engine/components/terrain/heightmap.js b/src/engine/components/terrain/heightmap.js index 5f1857a..a21d222 100644 --- a/src/engine/components/terrain/heightmap.js +++ b/src/engine/components/terrain/heightmap.js @@ -83,7 +83,7 @@ class SimplexHeightMap extends HeightMap { let amplitude = this.amplitude for (let i = 0; i < o; i++) { - output += (amplitude * this.osn.noise3D(x * frequency, y * frequency)) + output += (amplitude * this.osn.noise2D(x * frequency, y * frequency)) denom += amplitude frequency *= this.lacunarity diff --git a/src/engine/utility.js b/src/engine/utility.js index 498c8e3..02816cb 100644 --- a/src/engine/utility.js +++ b/src/engine/utility.js @@ -8,6 +8,20 @@ export function clamp (num, min, max) { return Math.min(Math.max(num, min), max) } +export function dim3to1 (x, y, z, xMax, yMax) { + if (xMax && !yMax) yMax = xMax + return (z * xMax * yMax) + (y * xMax) + x +} + +export function dim1to3 (idx, xMax, yMax) { + if (xMax && !yMax) yMax = xMax + const z = idx / (yMax * xMax) + idx -= (z * xMax * yMax) + const y = idx / xMax + const x = idx % xMax + return { x, y, z } +} + export function addv3 (one, two) { if (one.length !== 3) return null if (typeof two !== 'object') { diff --git a/src/engine/voxel/index.js b/src/engine/voxel/index.js index e14eefc..f970199 100644 --- a/src/engine/voxel/index.js +++ b/src/engine/voxel/index.js @@ -1,6 +1,9 @@ import { Mesh } from '../mesh' import { Node, MeshInstance } from '../components' -import { addv3, mulv3 } from '../utility' +import { vec3 } from 'gl-matrix' +import { addv3, subv3, mulv3, dim3to1, dim1to3 } from '../utility' +import { BoundingBox } from '../mesh/aabb' +import VoxelData from './voxeldata' const FACE_VERTEX = [ // Bottom @@ -41,7 +44,7 @@ const FACE_VERTEX = [ [1.0, 1.0, 1.0], [0.0, 1.0, 1.0], [0.0, 0.0, 1.0], - [0.0, 0.0, 1.0] + [0.0, 0.0, -1.0] ], // Back [ @@ -49,7 +52,7 @@ const FACE_VERTEX = [ [0.0, 1.0, 0.0], [1.0, 1.0, 0.0], [1.0, 0.0, 0.0], - [0.0, 0.0, -1.0] + [0.0, 0.0, 1.0] ] ] @@ -60,71 +63,65 @@ const FACE_RIGHT = 3 const FACE_FRONT = 4 const FACE_BACK = 5 -class Voxel { - constructor (id) { - this.id = id - } - - get solid () { - return this.id !== 0 - } -} - -const AIR_VOXEL = new Voxel(0) -const GROUND_VOXEL = new Voxel(1) +const AIR_VOXEL = VoxelData.register('air', { solid: false }) +const GROUND_VOXEL = VoxelData.register('ground', { solid: true }) class VoxelChunk extends MeshInstance { - constructor (origin, pos, size = 16) { - super(null, [origin[0] + pos[0] * size, origin[1] + pos[1] * size, origin[2] + pos[2] * size]) + constructor (world, pos, size = 16) { + super(null, [pos[0] * size, pos[1] * size, pos[2] * size]) + this.world = world this.relativePos = pos this.size = size this.mesh = null // Voxel data - this.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 + + // If the chunk is outside of the view, make it inactive + this.active = true } getVoxel (x, y, z) { - if (this.parent) { + if (this.world) { let neighbor if (x < 0) { - neighbor = this.parent.getChunk(this.relativePos[0] - 1, this.relativePos[1], this.relativePos[2]) + neighbor = this.world.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]) + neighbor = this.world.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]) + neighbor = this.world.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]) + neighbor = this.world.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) + neighbor = this.world.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) + neighbor = this.world.getChunk(this.relativePos[0], this.relativePos[1], this.relativePos[2] + 1) if (neighbor) { return neighbor.getVoxel(x, y, z - this.size) } @@ -132,7 +129,7 @@ class VoxelChunk extends MeshInstance { } } - return this.data[x][z][y] + return this.data[dim3to1(x, y, z, this.size)] || AIR_VOXEL } getVoxelv (v) { @@ -140,33 +137,24 @@ class VoxelChunk extends MeshInstance { } generate (generator) { - this.data = {} + this.material = generator.material for (let x = 0; x < this.size; x++) { - if (!this.data[x]) this.data[x] = {} for (let z = 0; z < this.size; z++) { - if (!this.data[x][z]) this.data[x][z] = {} const columnHeight = generator.getHeight(x + this.pos[0], z + this.pos[2]) for (let y = 0; y < this.size; y++) { const solid = this.pos[1] + y < columnHeight - this.data[x][z][y] = solid ? GROUND_VOXEL : AIR_VOXEL + this.data[dim3to1(x, y, z, this.size)] = 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 - } - } + return true } // Programmatically generate a voxel face // Returns the position, normal and texture coordinates for each vertex in this face - createFace (points, pos, face) { + createFace (indices, points, pos, face) { // Add the corresponding offsets for this face to the position const corners = [ addv3(pos, FACE_VERTEX[face][0]), addv3(pos, FACE_VERTEX[face][1]), @@ -176,15 +164,22 @@ class VoxelChunk extends MeshInstance { // Select the normal for this face const normal = FACE_VERTEX[face][4] - // Return the 6 vertices that make up this face (two triangles) + // Create the 4 vertices that make up this face // 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]]) + + // Create the face + const inx = points.length - 4 + indices.push(inx) + indices.push(inx + 1) + indices.push(inx + 2) + indices.push(inx) + indices.push(inx + 2) + indices.push(inx + 3) } createMesh (gl) { @@ -201,36 +196,37 @@ class VoxelChunk extends MeshInstance { // Array of vertices with texture positions and normals const points = [] + const indices = [] // 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++) { const cellPos = [x, y, z] - if (!this.getVoxel(x, y, z).solid) continue + if (!VoxelData.isSolid(this.getVoxel(x, y, z))) continue - if (!this.getVoxel(x, y - 1, z).solid) { - this.createFace(points, cellPos, FACE_BOTTOM) + if (!VoxelData.isSolid(this.getVoxel(x, y - 1, z))) { + this.createFace(indices, points, cellPos, FACE_BOTTOM) } - if (!this.getVoxel(x, y + 1, z).solid) { - this.createFace(points, cellPos, FACE_TOP) + if (!VoxelData.isSolid(this.getVoxel(x, y + 1, z))) { + this.createFace(indices, points, cellPos, FACE_TOP) } - if (!this.getVoxel(x - 1, y, z).solid) { - this.createFace(points, cellPos, FACE_LEFT) + if (!VoxelData.isSolid(this.getVoxel(x - 1, y, z))) { + this.createFace(indices, points, cellPos, FACE_LEFT) } - if (!this.getVoxel(x + 1, y, z).solid) { - this.createFace(points, cellPos, FACE_RIGHT) + if (!VoxelData.isSolid(this.getVoxel(x + 1, y, z))) { + this.createFace(indices, points, cellPos, FACE_RIGHT) } - if (!this.getVoxel(x, y, z + 1).solid) { - this.createFace(points, cellPos, FACE_FRONT) + if (!VoxelData.isSolid(this.getVoxel(x, y, z + 1))) { + this.createFace(indices, points, cellPos, FACE_FRONT) } - if (!this.getVoxel(x, y, z - 1).solid) { - this.createFace(points, cellPos, FACE_BACK) + if (!VoxelData.isSolid(this.getVoxel(x, y, z - 1))) { + this.createFace(indices, points, cellPos, FACE_BACK) } } } @@ -257,15 +253,21 @@ 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) + // Create a new mesh with an element array buffer + this.mesh = Mesh.construct(gl, vertices, indices, uvs, normals) if (this.material) this.mesh.material = this.material return true } - update (gl, dt) { + update (gl, dt, camera) { + this.active = camera.frustum.containsBox( + vec3.transformMat4([], [0.0, 0.0, 0.0], this.transform), + vec3.transformMat4([], [this.size, this.size, this.size], this.transform) + ) + if (!this.active) return false + if (!this.generated) return this.generate(this.world.generator) if (this.dirty) return this.createMesh(gl) return false } @@ -275,132 +277,76 @@ class VoxelChunk extends MeshInstance { this.mesh && this.mesh.dispose(gl) this.data = {} } + + draw (gl, shader, camera) { + if (this.active) super.draw(gl, shader) + } } -class VoxelMapBlock extends Node { - constructor (pos, chunkSize = 16, size = 8) { - super(pos) +class VoxelWorld { + constructor (generator, origin, chunkSize = 16, renderDistance = 5) { + this.generator = generator + this.origin = origin this.chunkSize = chunkSize - this.size = size - this.chunks = {} - this.generated = false - this.chunksLeft = size * size * size + this.renderDistance = renderDistance + this.chunks = [] } 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] + for (const i in this.chunks) { + const c = this.chunks[i] + if (c.relativePos[0] === x && c.relativePos[1] === y && c.relativePos[2] === z) return c + } + return null } 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 - const 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 (const i in this.children) { - const ch = this.children[i] + for (const i in this.chunks) { + const ch = this.chunks[i] if (!(ch instanceof VoxelChunk)) continue ch.destroy(gl) } - this.children = [] - this.chunks = {} + this.chunks = [] } - update (gl, dt) { - if (!this.generated) return false - for (const i in this.children) { - const ch = this.children[i] - if (!(ch instanceof VoxelChunk)) continue - if (ch.update(gl, dt)) return true - } - } -} + update (gl, dt, camera) { + const slgrid = [ + Math.floor(camera.pos[0] / this.chunkSize), + Math.floor(camera.pos[1] / this.chunkSize), + Math.floor(camera.pos[2] / this.chunkSize) + ] -class VoxelWorld extends Node { - constructor (generator, renderDistance = 2, worldSize = 64, blockSize = 8, chunkSize = 16) { - super([0.0, 0.0, 0.0]) - this.generator = generator - this.renderDistance = renderDistance - this.generator = generator - this.worldSize = worldSize - this.blockSize = blockSize - this.chunkSize = chunkSize - this.blocks = {} - this.mapblockqueue = [] - } - - queueMapblock (pos) { - this.mapblockqueue.push(pos) - } - - getBlock (x, y, z) { - if (!this.blocks[x] || !this.blocks[x][z] || !this.blocks[x][z][y]) return null - return this.blocks[x][z][y] - } - - update (gl, cam, dt) { - // Generate queued mapblocks before adding new ones to queue - if (this.mapblockqueue.length) { - const leftover = [] - let generated = false - for (const i in this.mapblockqueue) { - const pos = this.mapblockqueue[i] - const check = this.getBlock(pos[0], pos[1], pos[2]) - - if (generated || check) { - leftover.push(pos) - continue - } - - const absPos = mulv3(pos, this.blockSize * this.chunkSize) - const block = new VoxelMapBlock(absPos, this.chunkSize, this.blockSize) - block.generate(this.generator) - - if (!this.blocks[pos[0]]) this.blocks[pos[0]] = {} - if (!this.blocks[pos[0]][pos[2]]) this.blocks[pos[0]][pos[2]] = {} - if (!this.blocks[pos[0]][pos[2]][pos[1]]) this.blocks[pos[0]][pos[2]][pos[1]] = {} - - this.blocks[pos[0]][pos[2]][pos[1]] = block - this.addChild(block) - generated = true - } - this.mapblockqueue = leftover - return - } - - // Calculate a fixed grid - const total = this.blockSize * this.chunkSize - const slgrid = [Math.floor(cam.pos[0] / total), Math.floor(cam.pos[1] / total), Math.floor(cam.pos[2] / total)] - - for (let x = slgrid[0] - this.renderDistance / 2; x < slgrid[0] + this.renderDistance / 2; x++) { - for (let z = slgrid[2] - this.renderDistance / 2; z < slgrid[2] + this.renderDistance / 2; z++) { - for (let y = slgrid[1] - this.renderDistance / 2; y < slgrid[1] + this.renderDistance / 2; y++) { - if (this.getBlock(x, y, z)) continue - this.queueMapblock([x, y, z]) + for (let x = slgrid[0] - this.renderDistance; x < slgrid[0] + this.renderDistance; x++) { + for (let z = slgrid[2] - this.renderDistance; z < slgrid[2] + this.renderDistance; z++) { + for (let y = slgrid[1] + this.renderDistance / 2; y > slgrid[1] - this.renderDistance / 2; y--) { + if (this.getChunk(x, y, z)) continue + const chunk = new VoxelChunk(this, [x, y, z], this.chunkSize) + this.chunks.push(chunk) } } } - for (const i in this.children) { - if (this.children[i].update(gl, dt)) break + const resp = [] + for (const i in this.chunks) { + const chunk = this.chunks[i] + const dist = vec3.length(subv3(chunk.relativePos, slgrid)) + if (dist < this.renderDistance) resp.push(chunk) + } + this.chunks = resp + + for (const i in this.chunks) { + const ch = this.chunks[i] + if (ch.update(gl, dt, camera)) break + } + } + + draw (gl, shader) { + for (const i in this.chunks) { + this.chunks[i].draw(gl, shader) } } } @@ -410,29 +356,12 @@ class VoxelGenerator { 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 (const i in this.generateQueue) { - const chunk = this.generateQueue[i] - if (chunk.generated) continue - - chunk.material = this.material - chunk.generate(this) - } + return this.noise.getHeight(x / 100, z / 100) * this.noise.amplitude + this.groundHeight } } -export { VoxelGenerator, Voxel, VoxelChunk, VoxelMapBlock, VoxelWorld } +export { VoxelGenerator, VoxelChunk, VoxelWorld } diff --git a/src/engine/voxel/voxeldata.js b/src/engine/voxel/voxeldata.js new file mode 100644 index 0000000..66be7e7 --- /dev/null +++ b/src/engine/voxel/voxeldata.js @@ -0,0 +1,27 @@ + +const VoxelData = { + index: 0, + registeredVoxels: {}, + indexCache: [], + texture: null, + register: (name, def) => { + def.index = VoxelData.index++ + VoxelData.registeredVoxels[name] = def + VoxelData.indexCache.push(def) + return def.index + }, + get: (name) => { + return VoxelData.registeredVoxels[name] + }, + afterRegistration: () => { + + }, + isSolid: (id) => { + return VoxelData.indexCache[id] ? VoxelData.indexCache[id].solid : false + }, + textureIndex: (id, face) => { + + } +} + +export default VoxelData diff --git a/src/engine/voxel/voxeltexture.js b/src/engine/voxel/voxeltexture.js new file mode 100644 index 0000000..38d4d5a --- /dev/null +++ b/src/engine/voxel/voxeltexture.js @@ -0,0 +1,9 @@ +import Screen from '../screen' + +function generateTextureMap (registeredVoxels) { + // body... +} + +const VoxelTexture = { + +}