FINALLY some working LOD terrain!
This commit is contained in:
parent
8c43cd72bd
commit
cdafc652cf
@ -157,6 +157,17 @@ class Node {
|
|||||||
return this.pos
|
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 base
|
||||||
draw (gl, shader) {
|
draw (gl, shader) {
|
||||||
// Nothing to draw here, so just draw children
|
// Nothing to draw here, so just draw children
|
||||||
|
@ -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 }
|
|
207
src/engine/components/terrain/lod.js
Normal file
207
src/engine/components/terrain/lod.js
Normal file
@ -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 }
|
13
src/index.js
13
src/index.js
@ -3,7 +3,7 @@ import Camera from './engine/camera'
|
|||||||
import loadMesh from './engine/mesh/loader'
|
import loadMesh from './engine/mesh/loader'
|
||||||
|
|
||||||
import { Environment } from './engine/environment'
|
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 { SimplexHeightMap } from './engine/components/terrain/heightmap'
|
||||||
import { Material } from './engine/mesh/material'
|
import { Material } from './engine/mesh/material'
|
||||||
|
|
||||||
@ -22,13 +22,14 @@ async function pipeline () {
|
|||||||
let hmap = new SimplexHeightMap(1, 1, 256, 50)
|
let hmap = new SimplexHeightMap(1, 1, 256, 50)
|
||||||
|
|
||||||
// Create a terrain
|
// Create a terrain
|
||||||
let terrain = new Terrain([0.0, 0.0, 0.0], 256, 256)
|
let terrain = new LODTerrain([0.0, 0.0, 0.0], 1024, 1024, 850, 4)
|
||||||
terrain.createMesh(game.gl, hmap)
|
|
||||||
|
|
||||||
// Terrain material
|
// Terrain material
|
||||||
let material = new Material()
|
let material = new Material()
|
||||||
material.textures = ['grass-1024.jpg']
|
material.textures = ['grass-1024.jpg']
|
||||||
await material.loadTextures(game.gl)
|
await material.loadTextures(game.gl)
|
||||||
|
|
||||||
|
terrain.setGenerator(hmap)
|
||||||
terrain.setMaterial(material)
|
terrain.setMaterial(material)
|
||||||
|
|
||||||
// Create and initialize the camera
|
// Create and initialize the camera
|
||||||
@ -36,7 +37,6 @@ async function pipeline () {
|
|||||||
cam.updateProjection(game.gl)
|
cam.updateProjection(game.gl)
|
||||||
|
|
||||||
// Update function for camera
|
// Update function for camera
|
||||||
let face = 0
|
|
||||||
game.addUpdateFunction(function (dt) {
|
game.addUpdateFunction(function (dt) {
|
||||||
if (game.input.isDown('w')) {
|
if (game.input.isDown('w')) {
|
||||||
cam.processKeyboard(0, dt)
|
cam.processKeyboard(0, dt)
|
||||||
@ -54,10 +54,13 @@ async function pipeline () {
|
|||||||
cam.processMouseMove(game.input.mouseOffset)
|
cam.processMouseMove(game.input.mouseOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update LOD meshes
|
||||||
|
terrain.update(cam)
|
||||||
|
terrain.createMesh(game.gl)
|
||||||
|
|
||||||
// TESTING: Move model forward
|
// TESTING: Move model forward
|
||||||
// t = t + 0.1
|
// t = t + 0.1
|
||||||
// entity.setPosition([t, 0.0, 0.0])
|
// entity.setPosition([t, 0.0, 0.0])
|
||||||
if (face > 5) face = 0
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Render function for the triangle
|
// Render function for the triangle
|
||||||
|
Loading…
Reference in New Issue
Block a user