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) vec3.normalize(normal, normal)
return 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 { class SimplexHeightMap extends HeightMap {

View File

@ -4,6 +4,8 @@ import { Mesh } from '../../mesh'
import { vec3 } from 'gl-matrix' import { vec3 } from 'gl-matrix'
import { addv3 } from '../../utility' import { addv3 } from '../../utility'
const LOD_SEPARATOR = 16
class TerrainNode extends Node { class TerrainNode extends Node {
constructor (root, pos, level) { constructor (root, pos, level) {
super(pos) super(pos)
@ -14,18 +16,20 @@ class TerrainNode extends Node {
this.mesh = null this.mesh = null
} }
createMesh (gl) { updateLODMesh (gl) {
// If this mesh has children (we're a branch), generate their meshes instead
if (this.children.length) { if (this.children.length) {
let generated = 0 let generated = 0
for (let i in this.children) { for (let i in this.children) {
if (generated >= this.root.genPerTick) break if (generated >= this.root.genPerTick) break
let child = this.children[i] let child = this.children[i]
if (!(child instanceof TerrainNode)) continue if (!(child instanceof TerrainNode)) continue
generated += this.children[i].createMesh(gl) generated += this.children[i].updateLODMesh(gl)
} }
return generated return generated
} }
// If we already have a mesh, we can skip
if (this.mesh) return 0 if (this.mesh) return 0
let VERTICES = this.root.resolution let VERTICES = this.root.resolution
@ -37,18 +41,22 @@ class TerrainNode extends Node {
let vertexPointer = 0 let vertexPointer = 0
let divisionLevel = Math.pow(2, this.level) let divisionLevel = Math.pow(2, this.level)
// Create vertices dynamically
for (let i = 0; i < VERTICES; i++) { for (let i = 0; i < VERTICES; i++) {
for (let j = 0; j < VERTICES; j++) { for (let j = 0; j < VERTICES; j++) {
let vertDivj = j / (VERTICES - 1) let vertDivj = j / (VERTICES - 1)
let vertDivi = i / (VERTICES - 1) let vertDivi = i / (VERTICES - 1)
// Calculate relative resolution
let pj = vertDivj * this.root.width / divisionLevel let pj = vertDivj * this.root.width / divisionLevel
let pi = vertDivi * this.root.height / 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] = 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 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] = normal[0]
normals[vertexPointer * 3 + 1] = normal[1] normals[vertexPointer * 3 + 1] = normal[1]
normals[vertexPointer * 3 + 2] = normal[2] normals[vertexPointer * 3 + 2] = normal[2]
@ -58,6 +66,7 @@ class TerrainNode extends Node {
} }
} }
// Create indices dynamically
let pointer = 0 let pointer = 0
for (let gz = 0; gz < VERTICES - 1; gz++) { for (let gz = 0; gz < VERTICES - 1; gz++) {
for (let gx = 0; gx < VERTICES - 1; gx++) { for (let gx = 0; gx < VERTICES - 1; gx++) {
@ -78,14 +87,16 @@ class TerrainNode extends Node {
this.mesh.material = this.root.material this.mesh.material = this.root.material
this.bounds = BoundingBox.fromMesh(this.mesh) this.bounds = BoundingBox.fromMesh(this.mesh)
// Reduces flickering when subdividing
if (this.parent && this.parent.mesh) { if (this.parent && this.parent.mesh) {
this.parent.dispose() this.parent.dispose(gl)
} }
return 1 return 1
} }
draw (gl, shader) { draw (gl, shader) {
// Draw child nodes
super.draw(gl, shader) super.draw(gl, shader)
if (!this.mesh) return if (!this.mesh) return
// Set model transform matrix uniform // Set model transform matrix uniform
@ -100,51 +111,69 @@ class TerrainNode extends Node {
return this.mesh != null return this.mesh != null
} }
dispose () { dispose (gl) {
if (!this.mesh) return
this.mesh.dispose(gl)
this.mesh = null this.mesh = null
} }
merge () { merge (gl) {
// Can't merge if we have nothing to merge!
if (this.children.length === 0) return if (this.children.length === 0) return
// Merge children and dispose their meshes
for (let i in this.children) { for (let i in this.children) {
this.children[i].merge() this.children[i].merge(gl)
this.children[i].dispose() this.children[i].dispose(gl)
} }
// Delete all children, (hopefully) they will be garbage collected
this.children = [] this.children = []
} }
subdivide () { subdivide () {
// Do not divide when we're already at the limit
if (this.level === this.root.maxDetail) return if (this.level === this.root.maxDetail) return
let lv = this.level + 1 let lv = this.level + 1
let stepLeft = this.root.width / Math.pow(2, lv) let stepLeft = this.root.width / Math.pow(2, lv)
let stepForward = this.root.height / 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, [0, 0, 0], lv))
this.addChild(new TerrainNode(this.root, [stepLeft, 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, [0, 0, stepForward], lv))
this.addChild(new TerrainNode(this.root, [stepLeft, 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 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 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 (this.children.length) {
if (distCamera > lodDistance + 16 && this.level >= 1) { // Merge the node if the node is further than lodDistance, plus a set offset
this.merge() if (distCamera > lodDistance + LOD_SEPARATOR && this.level >= 1) {
this.merge(gl)
return true return true
} }
// Update the children
let acted = false let acted = false
for (let i in this.children) { for (let i in this.children) {
let child = this.children[i] let child = this.children[i]
acted = child.update(camera) acted = child.update(gl, camera)
} }
return acted 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) { if (distCamera < lodDistance && this.level < this.root.maxDetail) {
this.subdivide() this.subdivide()
@ -162,11 +191,13 @@ class LODTerrain extends Node {
this.width = sWidth this.width = sWidth
this.height = sHeight this.height = sHeight
// LOD parameters
this.genPerTick = genPerTick this.genPerTick = genPerTick
this.lodDistance = lodDistance this.lodDistance = lodDistance
this.maxDetail = maxDetail this.maxDetail = maxDetail
this.resolution = resolution this.resolution = resolution
// Create four initial nodes
this.addChild(new TerrainNode(this, pos, 1)) 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] + 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], pos[1], pos[2] + sHeight / 2], 1))
@ -181,24 +212,27 @@ class LODTerrain extends Node {
this.material = mat this.material = mat
} }
createMesh (gl) { // Update terrain meshes
// Ensure only one mesh is generated every tick updateLODMesh (gl) {
// Ensure only genPerTick mesh(es) is/are generated every tick
let generated = 0 let generated = 0
for (let i in this.children) { for (let i in this.children) {
if (generated >= this.genPerTick) break if (generated >= this.genPerTick) break
let child = this.children[i] let child = this.children[i]
if (!(child instanceof TerrainNode)) continue if (!(child instanceof TerrainNode)) continue
generated += this.children[i].createMesh(gl) generated += this.children[i].updateLODMesh(gl)
} }
return generated 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 let acted = false
for (let i in this.children) { for (let i in this.children) {
if (acted) break if (acted) break
let child = this.children[i] let child = this.children[i]
acted = child.update(camera) acted = child.update(gl, camera)
} }
return acted return acted
} }

View File

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

View File

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