diff --git a/package.json b/package.json index 314ea11..2bf195e 100644 --- a/package.json +++ b/package.json @@ -16,19 +16,19 @@ "author": "Evert \"Diamond\" Prants ", "license": "LGPL-3.0-or-later", "devDependencies": { - "@babel/core": "^7.7.4", - "@babel/plugin-transform-runtime": "^7.7.4", - "@babel/preset-env": "^7.7.4", - "babel-loader": "^8.0.6", + "@babel/core": "^7.9.0", + "@babel/plugin-transform-runtime": "^7.9.0", + "@babel/preset-env": "^7.9.0", + "babel-loader": "^8.1.0", "express": "^4.17.1", - "html-webpack-plugin": "^3.2.0", - "standard": "^12.0.1", - "webpack": "^4.41.2", - "webpack-command": "^0.4.2" + "html-webpack-plugin": "^4.0.3", + "standard": "^14.3.3", + "webpack": "^4.42.1", + "webpack-command": "^0.5.0" }, "dependencies": { - "@babel/runtime": "^7.7.4", - "gl-matrix": "^2.8.1", + "@babel/runtime": "^7.9.2", + "gl-matrix": "^3.2.1", "open-simplex-noise": "^1.7.0" } } diff --git a/src/engine/components/planet/index.js b/src/engine/components/planet/index.js new file mode 100644 index 0000000..8480e85 --- /dev/null +++ b/src/engine/components/planet/index.js @@ -0,0 +1,220 @@ +import { Mesh } from '../../mesh' +import { BoundingBox } from '../../mesh/aabb' +import { mat4, vec3 } from 'gl-matrix' +import { subv3, mulv3, addv3, divv3, normalv3, crossv3 } from '../../utility' +import Screen from '../../screen' + +const lodMax = 8 + +class PlanetGenerator { + constructor (resolution, radius, noise) { + this.resolution = resolution + this.radius = radius + this.noise = noise + } +} + +class CubeFace { + constructor (parent, level, pos, normal, generator) { + this.parent = parent + this.children = [] + + this.normal = normal + + this.level = level + + this.generated = false + this.generator = generator + + // Calculate left (x) and forward (z) vectors from the normal (y) + this.left = [normal[1], normal[2], normal[0]] + this.forward = crossv3(normal, this.left) + + this.position = pos + + // Center the face + if (level === 0) { + this.position = subv3(this.position, mulv3(this.left, generator.radius / 2)) + this.position = subv3(this.position, mulv3(this.forward, generator.radius / 2)) + } + + this.generate() + } + + generate () { + if (this.generated) return + + let VERTICES = this.generator.resolution + let count = VERTICES * VERTICES + let vertices = new Array(count * 3) + let normals = new Array(count * 3) + let textureCoords = new Array(count * 2) + let indices = new Array(6 * (VERTICES - 1) * (VERTICES - 1)) + + let radius = this.generator.radius + let divisionLevel = Math.pow(2, this.level) + + for (let i = 0, vertexPointer = 0; i < VERTICES; i++) { + for (let j = 0; j < VERTICES; j++, vertexPointer++) { + // Vertex index (0 - 1) + let iindex = i / (VERTICES - 1) + let jindex = j / (VERTICES - 1) + + // From the left and forward vectors, we can calculate an oriented vertex + let iv = divv3(mulv3(mulv3(this.left, iindex), radius), divisionLevel) + let jv = divv3(mulv3(mulv3(this.forward, jindex), radius), divisionLevel) + + // Add the scaled left and forward to the centered origin + let vertex = addv3(this.position, addv3(iv, jv)) + + // Normalize and multiply by radius to create a spherical mesh + let normal = normalv3(vertex) + let pos = mulv3(normal, (radius)) + + vertices[vertexPointer * 3] = pos[0] + vertices[vertexPointer * 3 + 1] = pos[1] + vertices[vertexPointer * 3 + 2] = pos[2] + normals[vertexPointer * 3] = normal[0] + normals[vertexPointer * 3 + 1] = normal[1] + normals[vertexPointer * 3 + 2] = normal[2] + textureCoords[vertexPointer * 2] = j * (1 / VERTICES) + textureCoords[vertexPointer * 2 + 1] = i * (1 / VERTICES) + + if (i === VERTICES / 2 && j === VERTICES / 2) { + this.center = pos + } + } + } + + for (let gz = 0, pointer = 0; gz < VERTICES - 1; gz++) { + for (let gx = 0; gx < VERTICES - 1; gx++) { + let topLeft = (gz * VERTICES) + gx + let topRight = topLeft + 1 + let bottomLeft = ((gz + 1) * VERTICES) + gx + let bottomRight = bottomLeft + 1 + indices[pointer++] = topLeft + indices[pointer++] = bottomLeft + indices[pointer++] = topRight + indices[pointer++] = topRight + indices[pointer++] = bottomLeft + indices[pointer++] = bottomRight + } + } + + this.mesh = Mesh.construct(Screen.gl, vertices, indices, textureCoords, normals) + this.generated = true + } + + dispose () { + this.generated = false + if (this.mesh) { + // this.mesh.dispose(Screen.gl) + this.mesh = null + } + } + + merge () { + if (!this.children.length) return + + for (let i in this.children) { + let ch = this.children[i] + + ch.merge() + ch.dispose() + } + + this.children = [] + this.generate() + } + + subdivide () { + if (this.level === lodMax) return + + let lv = this.level + 1 + let stepLeft = mulv3(this.left, this.generator.radius / Math.pow(2, lv)) + let stepForward = mulv3(this.forward, this.generator.radius / Math.pow(2, lv)) + + this.children = [ + new CubeFace(this, lv, this.position, this.normal, this.generator), + new CubeFace(this, lv, addv3(this.position, stepForward), this.normal, this.generator), + new CubeFace(this, lv, addv3(this.position, stepLeft), this.normal, this.generator), + new CubeFace(this, lv, addv3(this.position, addv3(stepLeft, stepForward)), this.normal, this.generator) + ] + + this.dispose() + } + + draw (gl, shader) { + if (!this.mesh) { + for (let i in this.children) { + this.children[i].draw(gl, shader) + } + return + } + + this.mesh.prepare(gl, shader) + this.mesh.draw(gl, shader) + } + + update (camera, dt) { + if (!this.center) return + let camToOrigin = vec3.distance(camera.pos, this.center) + let divisionLevel = Math.pow(2, this.level) + let splitDistance = this.generator.radius / divisionLevel + + if (camToOrigin < splitDistance * 1.5 && this.children.length === 0) { + this.subdivide() + return + } else if (camToOrigin > splitDistance * 2 && this.children.length > 0) { + this.merge() + return + } + + if (this.children.length > 0) { + for (let i in this.children) { + this.children[i].update(camera, dt) + } + } + } +} + +class CubePlanet { + constructor (origin, generator) { + this.origin = origin + this.generator = generator + + this.transform = mat4.create() + mat4.fromTranslation(this.transform, origin) + + let hs = generator.radius / 2 + + this.faces = [ + new CubeFace(this, 0, [0, 0, -hs], [0, 0, -1], generator), // front + new CubeFace(this, 0, [0, 0, hs], [0, 0, 1], generator), // back + + new CubeFace(this, 0, [-hs, 0, 0], [-1, 0, 0], generator), // left + new CubeFace(this, 0, [hs, 0, 0], [1, 0, 0], generator), // right + + new CubeFace(this, 0, [0, hs, 0], [0, 1, 0], generator), // top + new CubeFace(this, 0, [0, -hs, 0], [0, -1, 0], generator) // bottom + ] + } + + update (camera, dt) { + for (let i in this.faces) { + this.faces[i].update(camera, dt) + } + } + + draw (gl, shader) { + // Set model transform matrix uniform + const transformLocation = shader.getUniformLocation(gl, 'uModelMatrix') + gl.uniformMatrix4fv(transformLocation, false, this.transform) + + for (let i in this.faces) { + this.faces[i].draw(gl, shader) + } + } +} + +export { CubePlanet, CubeFace, PlanetGenerator } diff --git a/src/engine/environment.js b/src/engine/environment.js index 7c884f0..44061cf 100644 --- a/src/engine/environment.js +++ b/src/engine/environment.js @@ -42,7 +42,7 @@ class Environment { this.fogEnd = 0 this.fogColor = [0.8, 0.8, 0.8] - this.sun = new DirectionalLight([0.0, 1000.0, 2000.0], [-1.0, -1.0, 0.0], [1.0, 1.0, 1.0]) + this.sun = new DirectionalLight([0.0, 1000.0, -2000.0], [-1.0, -1.0, 0.0], [1.0, 1.0, 1.0]) this.lights = [ this.sun ] this.maxEnvironmentLights = ENV_MAX_LIGHTS diff --git a/src/engine/voxel/index.js b/src/engine/voxel/index.js index 09dedd8..76f7748 100644 --- a/src/engine/voxel/index.js +++ b/src/engine/voxel/index.js @@ -325,11 +325,11 @@ class VoxelMapBlock extends Node { } update (gl, dt) { - if (!this.generated) return + if (!this.generated) return false for (let i in this.children) { let ch = this.children[i] if (!(ch instanceof VoxelChunk)) continue - if (ch.update(gl, dt)) break + if (ch.update(gl, dt)) return true } } } @@ -370,7 +370,7 @@ class VoxelWorld extends Node { continue } - let absPos = mulv3(pos, this.blockSize * (this.chunkSize / 2)) + let absPos = mulv3(pos, this.blockSize * this.chunkSize) let block = new VoxelMapBlock(absPos, this.chunkSize, this.blockSize) block.generate(this.generator) @@ -391,8 +391,8 @@ class VoxelWorld extends Node { let 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[1] - this.renderDistance / 2; z < slgrid[1] + this.renderDistance / 2; z++) { - for (let y = slgrid[2] - this.renderDistance / 2; y < slgrid[2] + this.renderDistance / 2; y++) { + 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]) } @@ -400,7 +400,7 @@ class VoxelWorld extends Node { } for (let i in this.children) { - this.children[i].update(gl, dt) + if (this.children[i].update(gl, dt)) break } } } diff --git a/src/index.js b/src/index.js index cb88405..46e55d5 100644 --- a/src/index.js +++ b/src/index.js @@ -15,6 +15,8 @@ import { GUIRenderer, GUIImage, Dim4 } from './engine/gui' import { FontRenderer, GUIText, Font } from './engine/gui/font' import { VoxelWorld, VoxelGenerator } from './engine/voxel' +import { CubePlanet, PlanetGenerator } from './engine/components/planet' + let game = Engine let env = new Environment() let gui = new GUIRenderer() @@ -29,11 +31,11 @@ async function pipeline () { entity.setRotation([0.0, 0.0, -90.0]) // Initialize water - let waterRenderer = new WaterRenderer() - let water = new WaterTile([100.0, 0.0, 100.0], 100.0) - await waterRenderer.initialize(game) - await waterRenderer.useDUDVMap(game.gl, 'dudv') - await waterRenderer.useNormalMap(game.gl, 'normalmap') + // let waterRenderer = new WaterRenderer() + // let water = new WaterTile([100.0, 0.0, 100.0], 100.0) + // 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) @@ -60,7 +62,7 @@ async function pipeline () { 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) + // let terrain = new LODTerrain([0.0, 0.0, 0.0], 1024, 1024, 850, 4) // Terrain material let material = new Material(['grass-1024.jpg']) @@ -72,11 +74,11 @@ async function pipeline () { } // Set generator and material for terrain - terrain.setGenerator(hmap) - terrain.setMaterial(material) + // terrain.setGenerator(hmap) + // terrain.setMaterial(material) // Create and initialize the camera - let cam = new Camera([-16.0, 25.0, -16.0], [0.8, -0.6, 0.0]) + let cam = new Camera([-1300.0, 1325.0, -1300.0], [0.8, -0.6, 0.0]) cam.updateProjection(game.gl) // Create skybox @@ -89,6 +91,9 @@ async function pipeline () { // let voxgen = new VoxelGenerator(hmap, material) // let block = new VoxelWorld(voxgen) + // Planet test + let planet = new CubePlanet([0.0, 0.0, 0.0], new PlanetGenerator(16, 1000, hmap)) + // Update function for camera and terrain let fpsTimer = 0 game.addUpdateFunction(function (dt) { @@ -123,11 +128,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) // Update voxel chunk // voxgen.update(dt) @@ -139,6 +144,9 @@ async function pipeline () { itms[0].children[0].setText(game.fps + ' fps') fpsTimer = 0 } + + // Update planet LOD + planet.update(cam, dt) }) function drawEverything (gl) { @@ -155,21 +163,24 @@ 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) // block.draw(gl, terrainShader) + + material.apply(gl, terrainShader) + planet.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)