Comments, proper mesh disposal
This commit is contained in:
parent
cdafc652cf
commit
a7d760858c
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user