From 1bad7e46ab6726ec065fb2d336e1c7da37445a66 Mon Sep 17 00:00:00 2001 From: Evert Prants Date: Sun, 5 Jan 2020 17:13:55 +0200 Subject: [PATCH] simple voxel chunk --- src/engine/mesh/index.js | 17 +-- src/engine/voxel/index.js | 215 ++++++++++++++++++++++++++++++++++++++ src/index.js | 41 +++++--- 3 files changed, 251 insertions(+), 22 deletions(-) create mode 100644 src/engine/voxel/index.js diff --git a/src/engine/mesh/index.js b/src/engine/mesh/index.js index c8ef6f4..2c234a6 100644 --- a/src/engine/mesh/index.js +++ b/src/engine/mesh/index.js @@ -11,15 +11,20 @@ class Mesh { // VBO for model vertices let pos = Mesh.loadToBuffer(gl, gl.ARRAY_BUFFER, new Float32Array(vertices)) - // Indices Buffer - let ebo = Mesh.loadToBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices)) - // Create the mesh, as we have the most important data already buffered let mesh = new Mesh() mesh.posBuffer = pos - mesh.ebo = ebo mesh.vertices = vertices - mesh.indices = indices + + // Indices Buffer (EBO) + if (indices) { + let ebo = Mesh.loadToBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices)) + mesh.ebo = ebo + mesh.indices = indices + } else { + mesh.vertexCount = vertices.length / 3 + mesh.vertexLayout = 3 + } // VBO for model UVs if (uvs) { @@ -114,7 +119,7 @@ class Mesh { // Make sure no data floats around in memory dispose (gl) { gl.deleteBuffer(this.posBuffer) - gl.deleteBuffer(this.ebo) + this.ebo && gl.deleteBuffer(this.ebo) this.uvs && gl.deleteBuffer(this.uvs) this.nms && gl.deleteBuffer(this.nms) this.vertices = null diff --git a/src/engine/voxel/index.js b/src/engine/voxel/index.js new file mode 100644 index 0000000..422f874 --- /dev/null +++ b/src/engine/voxel/index.js @@ -0,0 +1,215 @@ +import { Mesh } from '../mesh' +import { MeshInstance } from '../components' +import { addv3, crossv3, subv3 } from '../utility' + +const FACE_VERTEX = [ + // Bottom + [ + [1.0, 0.0, 0.0], + [1.0, 0.0, 1.0], + [0.0, 0.0, 1.0], + [0.0, 0.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] + ], + // 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] + ], + // 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] + ], + // 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] + ], + // Back + [ + [0.0, 0.0, 0.0], + [0.0, 1.0, 0.0], + [1.0, 1.0, 0.0], + [1.0, 0.0, 0.0] + ] +] + +const FACE_BOTTOM = 0 +const FACE_TOP = 1 +const FACE_LEFT = 2 +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) + +class VoxelChunk extends MeshInstance { + constructor (pos, size = 16) { + super(null, pos) + this.size = size + this.array = {} + + this.mesh = null + this.dirty = true + } + + 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] + } + + generate (noise) { + this.array = {} + for (let x = 0; x < this.size; x++) { + if (!this.array[x]) this.array[x] = {} + for (let y = 0; y < this.size; y++) { + if (!this.array[x][y]) this.array[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) + } + } + } + } + + // Programmatically generate a voxel face + // Returns the position, normal and texture coordinates for each vertex in this face + createFace (verts, pos, face) { + let posi = [ + 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]]) + } + + createMesh (gl) { + this.dirty = false + + if (this.mesh) { + this.mesh.dispose(gl) + } + + let points = [] + + for (let x = 0; x < this.size; x++) { + for (let y = 0; y < this.size; y++) { + for (let z = 0; z < this.size; 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) { + this.createFace(points, cellPos, FACE_BOTTOM) + } + + if (faceTop) { + this.createFace(points, cellPos, FACE_TOP) + } + + if (faceLeft) { + this.createFace(points, cellPos, FACE_LEFT) + } + + if (faceRight) { + this.createFace(points, cellPos, FACE_RIGHT) + } + + if (faceFront) { + this.createFace(points, cellPos, FACE_FRONT) + } + + if (faceBack) { + this.createFace(points, cellPos, FACE_BACK) + } + } + } + } + + if (points.length === 0) { + return + } + + let vertices = [] + let normals = [] + let uvs = [] + for (let i in points) { + let vert = points[i] + vertices.push(vert[0][0]) + vertices.push(vert[0][1]) + vertices.push(vert[0][2]) + normals.push(vert[1][0]) + normals.push(vert[1][1]) + normals.push(vert[1][2]) + uvs.push(vert[2][0]) + uvs.push(vert[2][1]) + } + + this.mesh = Mesh.construct(gl, vertices, null, uvs, normals) + } + + update (dt) { + + } +} + +export { Voxel, VoxelChunk } diff --git a/src/index.js b/src/index.js index acfda58..1ce241b 100644 --- a/src/index.js +++ b/src/index.js @@ -13,6 +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' let game = Engine let env = new Environment() @@ -21,10 +22,10 @@ let fnt = new FontRenderer() let prt = new ParticleRenderer() async function pipeline () { - let entity = await loadMesh(game.gl, 'test') +// let entity = await loadMesh(game.gl, 'test') let terrainShader = await game.shaders.createShaderFromFiles(game.gl, 'terrain', false) let skyboxShader = await game.shaders.createShaderFromFiles(game.gl, 'skybox', false) - +/* entity.setRotation([0.0, 0.0, -90.0]) // Initialize water @@ -33,7 +34,7 @@ async function pipeline () { await waterRenderer.initialize(game) await waterRenderer.useDUDVMap(game.gl, 'dudv') await waterRenderer.useNormalMap(game.gl, 'normalmap') - +*/ let arialFont = await Font.fromFile('arial') await arialFont.loadTextures(game.gl) @@ -58,14 +59,14 @@ async function pipeline () { // Create a height map based on OpenSimplex noise let hmap = new SimplexHeightMap(1, 1, 256, 50) - +/* // Create a terrain instance let terrain = new LODTerrain([0.0, 0.0, 0.0], 1024, 1024, 850, 4) - +*/ // Terrain material let material = new Material(['grass-1024.jpg']) await material.loadTextures(game.gl) - +/* // test code for (let i in entity.children) { entity.children[i].mesh.material = material @@ -74,9 +75,9 @@ async function pipeline () { // Set generator and material for terrain terrain.setGenerator(hmap) terrain.setMaterial(material) - +*/ // Create and initialize the camera - let cam = new Camera([-32.0, 100.0, -32.0], [0.8, -0.6, 0.0]) + let cam = new Camera([-16.0, 25.0, -16.0], [0.8, -0.6, 0.0]) cam.updateProjection(game.gl) // Create skybox @@ -85,6 +86,12 @@ async function pipeline () { // Load textures and generate a mesh await skybox.initialize(game.gl) + // Voxel test + let chunk = new VoxelChunk([0.0, 0.0, 0.0]) + chunk.generate(hmap) + chunk.createMesh(game.gl) + chunk.mesh.material = material + // Update function for camera and terrain let fpsTimer = 0 game.addUpdateFunction(function (dt) { @@ -119,11 +126,11 @@ async function pipeline () { } // Update detail levels - terrain.update(game.gl, cam) - terrain.updateLODMesh(game.gl) + // terrain.update(game.gl, cam) + // terrain.updateLODMesh(game.gl) // Ripple water - waterRenderer.update(dt) + // waterRenderer.update(dt) // Set text to FPS fpsTimer++ @@ -147,19 +154,21 @@ async function pipeline () { // Set the viewport uniforms cam.draw(gl, terrainShader) // Draw terrain - terrain.draw(gl, terrainShader) + // terrain.draw(gl, terrainShader) - entity.draw(gl, terrainShader) + // entity.draw(gl, terrainShader) + + chunk.draw(gl, terrainShader) } // Render function for the triangle game.addRenderFunction(function (gl) { - waterRenderer.reflect(gl, cam, drawEverything) - waterRenderer.refract(gl, cam, drawEverything) + // waterRenderer.reflect(gl, cam, drawEverything) + // waterRenderer.refract(gl, cam, drawEverything) drawEverything(gl) - waterRenderer.draw(gl, [water], cam, env.sun) + // waterRenderer.draw(gl, [water], cam, env.sun) // Draw particles particleSystem.draw(gl, cam)