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