Comments, proper mesh disposal

This commit is contained in:
Evert Prants 2019-12-28 18:35:25 +02:00
parent cdafc652cf
commit a7d760858c
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
4 changed files with 82 additions and 24 deletions

View File

@ -40,6 +40,20 @@ class HeightMap {
vec3.normalize(normal, normal)
return normal
}
// Vertex position based getters with LOD subnode support
getHeightPlaneMesh (mesh, vx, vz, absolute) {
let pos = mesh.pos
if (absolute) pos = mesh.absolutePosition
return this.getHeight(pos[0] + vx, pos[2] + vz)
}
getNormalPlaneMesh (mesh, vx, vz, absolute) {
let pos = mesh.pos
if (absolute) pos = mesh.absolutePosition
return this.getNormal(pos[0] + vx, pos[2] + vz)
}
}
class SimplexHeightMap extends HeightMap {

View File

@ -4,6 +4,8 @@ import { Mesh } from '../../mesh'
import { vec3 } from 'gl-matrix'
import { addv3 } from '../../utility'
const LOD_SEPARATOR = 16
class TerrainNode extends Node {
constructor (root, pos, level) {
super(pos)
@ -14,18 +16,20 @@ class TerrainNode extends Node {
this.mesh = null
}
createMesh (gl) {
updateLODMesh (gl) {
// If this mesh has children (we're a branch), generate their meshes instead
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)
generated += this.children[i].updateLODMesh(gl)
}
return generated
}
// If we already have a mesh, we can skip
if (this.mesh) return 0
let VERTICES = this.root.resolution
@ -37,18 +41,22 @@ class TerrainNode extends Node {
let vertexPointer = 0
let divisionLevel = Math.pow(2, this.level)
// Create vertices dynamically
for (let i = 0; i < VERTICES; i++) {
for (let j = 0; j < VERTICES; j++) {
let vertDivj = j / (VERTICES - 1)
let vertDivi = i / (VERTICES - 1)
// Calculate relative resolution
let pj = vertDivj * this.root.width / divisionLevel
let pi = vertDivi * this.root.height / divisionLevel
// Generator takes meshes' absolute vertex position in the world when using height values
vertices[vertexPointer * 3] = pj
vertices[vertexPointer * 3 + 1] = this.root.generator.getHeight(this.absolutePosition[0] + pj, this.absolutePosition[2] + pi)
vertices[vertexPointer * 3 + 1] = this.root.generator.getHeightPlaneMesh(this, pj, pi, true)
vertices[vertexPointer * 3 + 2] = pi
let normal = this.root.generator.getNormal(this.absolutePosition[0] + pj, this.absolutePosition[2] + pi)
let normal = this.root.generator.getNormalPlaneMesh(this, pj, pi, true)
normals[vertexPointer * 3] = normal[0]
normals[vertexPointer * 3 + 1] = normal[1]
normals[vertexPointer * 3 + 2] = normal[2]
@ -58,6 +66,7 @@ class TerrainNode extends Node {
}
}
// Create indices dynamically
let pointer = 0
for (let gz = 0; gz < VERTICES - 1; gz++) {
for (let gx = 0; gx < VERTICES - 1; gx++) {
@ -78,14 +87,16 @@ class TerrainNode extends Node {
this.mesh.material = this.root.material
this.bounds = BoundingBox.fromMesh(this.mesh)
// Reduces flickering when subdividing
if (this.parent && this.parent.mesh) {
this.parent.dispose()
this.parent.dispose(gl)
}
return 1
}
draw (gl, shader) {
// Draw child nodes
super.draw(gl, shader)
if (!this.mesh) return
// Set model transform matrix uniform
@ -100,51 +111,69 @@ class TerrainNode extends Node {
return this.mesh != null
}
dispose () {
dispose (gl) {
if (!this.mesh) return
this.mesh.dispose(gl)
this.mesh = null
}
merge () {
merge (gl) {
// Can't merge if we have nothing to merge!
if (this.children.length === 0) return
// Merge children and dispose their meshes
for (let i in this.children) {
this.children[i].merge()
this.children[i].dispose()
this.children[i].merge(gl)
this.children[i].dispose(gl)
}
// Delete all children, (hopefully) they will be garbage collected
this.children = []
}
subdivide () {
// Do not divide when we're already at the limit
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)
// Child nodes take relative positions because they are parented
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) {
// Divide and merge meshes depending on camera position and lodDistance
update (gl, camera) {
if (!this.bounds) return false
// Get camera distance from the center of this mesh
// TODO: use edges
let distCamera = vec3.distance(camera.pos, addv3(this.bounds.center, this.absolutePosition))
let lodDistance = this.root.lodDistance - (Math.pow(2, this.level) * 16)
// Get LOD change distance based on current division level and a fixed distance
let lodDistance = this.root.lodDistance - (Math.pow(2, this.level) * LOD_SEPARATOR)
// If this node has children, either merge if too far away or update the children
if (this.children.length) {
if (distCamera > lodDistance + 16 && this.level >= 1) {
this.merge()
// Merge the node if the node is further than lodDistance, plus a set offset
if (distCamera > lodDistance + LOD_SEPARATOR && this.level >= 1) {
this.merge(gl)
return true
}
// Update the children
let acted = false
for (let i in this.children) {
let child = this.children[i]
acted = child.update(camera)
acted = child.update(gl, camera)
}
return acted
}
// If the camera is close enough to this node and we still have room to divide, divide!
if (distCamera < lodDistance && this.level < this.root.maxDetail) {
this.subdivide()
@ -162,11 +191,13 @@ class LODTerrain extends Node {
this.width = sWidth
this.height = sHeight
// LOD parameters
this.genPerTick = genPerTick
this.lodDistance = lodDistance
this.maxDetail = maxDetail
this.resolution = resolution
// Create four initial nodes
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))
@ -181,24 +212,27 @@ class LODTerrain extends Node {
this.material = mat
}
createMesh (gl) {
// Ensure only one mesh is generated every tick
// Update terrain meshes
updateLODMesh (gl) {
// Ensure only genPerTick mesh(es) is/are 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)
generated += this.children[i].updateLODMesh(gl)
}
return generated
}
update (camera) {
// Divide and merge meshes depending on camera position and lodDistance
update (gl, camera) {
// Ensure only one mesh update per tick
let acted = false
for (let i in this.children) {
if (acted) break
let child = this.children[i]
acted = child.update(camera)
acted = child.update(gl, camera)
}
return acted
}

View File

@ -3,6 +3,8 @@ import Screen from './screen'
import Input from './input'
import { ShaderManager } from './shader'
let gl
class Engine {
constructor () {
this.screen = new Screen()
@ -18,14 +20,22 @@ class Engine {
this.frameCount = 0
this.fps = 0
window.gl = this.screen.gl
gl = this.screen.gl
}
static get GL () {
return gl
}
static get gl () {
return gl
}
get gl () {
return this.screen.gl
}
render (gl) {
render () {
// Set clear color to black, fully opaque
gl.clearColor(0.0, 0.7, 1.0, 1.0)

View File

@ -54,9 +54,9 @@ async function pipeline () {
cam.processMouseMove(game.input.mouseOffset)
}
// Update LOD meshes
terrain.update(cam)
terrain.createMesh(game.gl)
// Update detail levels
terrain.update(game.gl, cam)
terrain.updateLODMesh(game.gl)
// TESTING: Move model forward
// t = t + 0.1