diff --git a/src/engine/components/index.js b/src/engine/components/index.js index 6493e1e..02c71a1 100644 --- a/src/engine/components/index.js +++ b/src/engine/components/index.js @@ -157,6 +157,17 @@ class Node { return this.pos } + get absolutePosition () { + let p + if (this.parent) { + p = this.parent.absolutePosition + p = [p[0] + this.pos[0], p[1] + this.pos[1], p[2] + this.pos[2]] + } else { + p = this.pos + } + return p + } + // Draw base draw (gl, shader) { // Nothing to draw here, so just draw children diff --git a/src/engine/components/terrain/index.js b/src/engine/components/terrain/basic.js similarity index 100% rename from src/engine/components/terrain/index.js rename to src/engine/components/terrain/basic.js diff --git a/src/engine/components/terrain/experimental.js b/src/engine/components/terrain/experimental.js deleted file mode 100644 index 3f8ba37..0000000 --- a/src/engine/components/terrain/experimental.js +++ /dev/null @@ -1,96 +0,0 @@ -import { Node } from '../' -import { Mesh } from '../../mesh' -import { vec3 } from 'gl-matrix' -import { subv3, mulv3, addv3, normalv3 } from '../../utility' - -class Terrain extends Node { - constructor (pos, sWidth, sHeight, face = [0.0, -1.0, 0.0]) { - super(pos) - - this.width = sWidth - this.height = sHeight - - this.mesh = null - - // Calculate left and forward vectors - this.left = [face[1], face[2], face[0]] || [1.0, 0.0, 0.0] - this.forward = [0.0, 0.0, -1.0] - vec3.cross(this.forward, face, this.left) - } - - createMesh (gl, heightMap) { - // Center the mesh - let cpoint = [0.0, 0.0, 0.0] - cpoint = subv3(cpoint, mulv3(this.left, heightMap.size / 2)) - cpoint = subv3(cpoint, mulv3(this.forward, heightMap.size / 2)) - - let VERTICES = heightMap.size - 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 vertexPointer = 0 - - for (let i = 0; i < VERTICES; i++) { - for (let j = 0; j < VERTICES; j++) { - let isize = j / (VERTICES - 1) * this.width / 2 - let jsize = i / (VERTICES - 1) * this.height / 2 - - // From the left and forward vectors, we can calculate an oriented vertex - let iv = mulv3(this.left, isize) - let jv = mulv3(this.forward, jsize) - - // Add the scaled left and forward to the centered origin - let pos = addv3(cpoint.slice(0), addv3(iv, jv)) - - vertices[vertexPointer * 3] = pos[0] - vertices[vertexPointer * 3 + 1] = pos[1] - vertices[vertexPointer * 3 + 2] = pos[2] - let normal = [0.0, 1.0, 0.0] // heightMap.getNormal(j, i) - normals[vertexPointer * 3] = normal[0] - normals[vertexPointer * 3 + 1] = normal[1] - normals[vertexPointer * 3 + 2] = normal[2] - textureCoords[vertexPointer * 2] = j / (VERTICES - 1) - textureCoords[vertexPointer * 2 + 1] = i / (VERTICES - 1) - vertexPointer++ - } - } - - let pointer = 0 - for (let gz = 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(gl, vertices, indices, textureCoords, normals) - } - - setMaterial (mat) { - this.mesh.material = mat - } - - draw (gl, shader) { - if (!this.mesh) return - // Set model transform matrix uniform - const transformLocation = shader.getUniformLocation(gl, 'uModelMatrix') - gl.uniformMatrix4fv(transformLocation, false, this.transform) - - this.mesh.prepare(gl, shader) - this.mesh.draw(gl, shader) - - super.draw(gl, shader) - } -} - -export { Terrain } diff --git a/src/engine/components/terrain/lod.js b/src/engine/components/terrain/lod.js new file mode 100644 index 0000000..4796871 --- /dev/null +++ b/src/engine/components/terrain/lod.js @@ -0,0 +1,207 @@ +import { Node } from '../' +import { BoundingBox } from '../../mesh/aabb' +import { Mesh } from '../../mesh' +import { vec3 } from 'gl-matrix' +import { addv3 } from '../../utility' + +class TerrainNode extends Node { + constructor (root, pos, level) { + super(pos) + + this.root = root + this.level = level + + this.mesh = null + } + + createMesh (gl) { + if (this.children.length) { + let generated = 0 + for (let i in this.children) { + if (generated >= this.root.genPerTick) break + let child = this.children[i] + if (!(child instanceof TerrainNode)) continue + generated += this.children[i].createMesh(gl) + } + return generated + } + + if (this.mesh) return 0 + + let VERTICES = this.root.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 vertexPointer = 0 + let divisionLevel = Math.pow(2, this.level) + + for (let i = 0; i < VERTICES; i++) { + for (let j = 0; j < VERTICES; j++) { + let vertDivj = j / (VERTICES - 1) + let vertDivi = i / (VERTICES - 1) + + let pj = vertDivj * this.root.width / divisionLevel + let pi = vertDivi * this.root.height / divisionLevel + + vertices[vertexPointer * 3] = pj + vertices[vertexPointer * 3 + 1] = this.root.generator.getHeight(this.absolutePosition[0] + pj, this.absolutePosition[2] + pi) + vertices[vertexPointer * 3 + 2] = pi + let normal = this.root.generator.getNormal(this.absolutePosition[0] + pj, this.absolutePosition[2] + pi) + normals[vertexPointer * 3] = normal[0] + normals[vertexPointer * 3 + 1] = normal[1] + normals[vertexPointer * 3 + 2] = normal[2] + textureCoords[vertexPointer * 2] = j / (VERTICES - 1) + textureCoords[vertexPointer * 2 + 1] = i / (VERTICES - 1) + vertexPointer++ + } + } + + let pointer = 0 + for (let gz = 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(gl, vertices, indices, textureCoords, normals) + this.mesh.material = this.root.material + this.bounds = BoundingBox.fromMesh(this.mesh) + + if (this.parent && this.parent.mesh) { + this.parent.dispose() + } + + return 1 + } + + draw (gl, shader) { + super.draw(gl, shader) + if (!this.mesh) return + // Set model transform matrix uniform + const transformLocation = shader.getUniformLocation(gl, 'uModelMatrix') + gl.uniformMatrix4fv(transformLocation, false, this.transform) + + this.mesh.prepare(gl, shader) + this.mesh.draw(gl, shader) + } + + get generated () { + return this.mesh != null + } + + dispose () { + this.mesh = null + } + + merge () { + if (this.children.length === 0) return + for (let i in this.children) { + this.children[i].merge() + this.children[i].dispose() + } + + this.children = [] + } + + subdivide () { + if (this.level === this.root.maxDetail) return + let lv = this.level + 1 + + let stepLeft = this.root.width / Math.pow(2, lv) + let stepForward = this.root.height / Math.pow(2, lv) + + this.addChild(new TerrainNode(this.root, [0, 0, 0], lv)) + this.addChild(new TerrainNode(this.root, [stepLeft, 0, 0], lv)) + this.addChild(new TerrainNode(this.root, [0, 0, stepForward], lv)) + this.addChild(new TerrainNode(this.root, [stepLeft, 0, stepForward], lv)) + } + + update (camera) { + if (!this.bounds) return false + let distCamera = vec3.distance(camera.pos, addv3(this.bounds.center, this.absolutePosition)) + let lodDistance = this.root.lodDistance - (Math.pow(2, this.level) * 16) + if (this.children.length) { + if (distCamera > lodDistance + 16 && this.level >= 1) { + this.merge() + return true + } + + let acted = false + for (let i in this.children) { + let child = this.children[i] + acted = child.update(camera) + } + return acted + } + + if (distCamera < lodDistance && this.level < this.root.maxDetail) { + this.subdivide() + + return true + } + + return false + } +} + +class LODTerrain extends Node { + constructor (pos, sWidth, sHeight, lodDistance = 1024, maxDetail = 4, resolution = 32, genPerTick = 4) { + super(pos) + + this.width = sWidth + this.height = sHeight + + this.genPerTick = genPerTick + this.lodDistance = lodDistance + this.maxDetail = maxDetail + this.resolution = resolution + + this.addChild(new TerrainNode(this, pos, 1)) + this.addChild(new TerrainNode(this, [pos[0] + sWidth / 2, pos[1], pos[2]], 1)) + this.addChild(new TerrainNode(this, [pos[0], pos[1], pos[2] + sHeight / 2], 1)) + this.addChild(new TerrainNode(this, [pos[0] + sWidth / 2, pos[1], pos[2] + sHeight / 2], 1)) + } + + setGenerator (generator) { + this.generator = generator + } + + setMaterial (mat) { + this.material = mat + } + + createMesh (gl) { + // Ensure only one mesh is generated every tick + let generated = 0 + for (let i in this.children) { + if (generated >= this.genPerTick) break + let child = this.children[i] + if (!(child instanceof TerrainNode)) continue + generated += this.children[i].createMesh(gl) + } + return generated + } + + update (camera) { + let acted = false + for (let i in this.children) { + if (acted) break + let child = this.children[i] + acted = child.update(camera) + } + return acted + } +} + +export { LODTerrain } diff --git a/src/index.js b/src/index.js index 0eb4dd9..818f804 100644 --- a/src/index.js +++ b/src/index.js @@ -3,7 +3,7 @@ import Camera from './engine/camera' import loadMesh from './engine/mesh/loader' import { Environment } from './engine/environment' -import { Terrain } from './engine/components/terrain' +import { LODTerrain } from './engine/components/terrain/lod' import { SimplexHeightMap } from './engine/components/terrain/heightmap' import { Material } from './engine/mesh/material' @@ -22,13 +22,14 @@ async function pipeline () { let hmap = new SimplexHeightMap(1, 1, 256, 50) // Create a terrain - let terrain = new Terrain([0.0, 0.0, 0.0], 256, 256) - terrain.createMesh(game.gl, hmap) + let terrain = new LODTerrain([0.0, 0.0, 0.0], 1024, 1024, 850, 4) // Terrain material let material = new Material() material.textures = ['grass-1024.jpg'] await material.loadTextures(game.gl) + + terrain.setGenerator(hmap) terrain.setMaterial(material) // Create and initialize the camera @@ -36,7 +37,6 @@ async function pipeline () { cam.updateProjection(game.gl) // Update function for camera - let face = 0 game.addUpdateFunction(function (dt) { if (game.input.isDown('w')) { cam.processKeyboard(0, dt) @@ -54,10 +54,13 @@ async function pipeline () { cam.processMouseMove(game.input.mouseOffset) } + // Update LOD meshes + terrain.update(cam) + terrain.createMesh(game.gl) + // TESTING: Move model forward // t = t + 0.1 // entity.setPosition([t, 0.0, 0.0]) - if (face > 5) face = 0 }) // Render function for the triangle