Skip to content

Commit

Permalink
Reorganize code around adding/removing/freezing terrain meshes.
Browse files Browse the repository at this point in the history
 - `noa` now emits `addingTerrainMesh`, `removingTerrainMesh`
 - these fire for both regular terrain chunks, and custom block meshes
 - intention is for client to use these for e.g. implementing BJS shadows
 - removes previous overwriteable hooks
 - also expose arrays of current terrain materials, object blocks,
   in case client needs to see stuff added before its listeners were made
  • Loading branch information
fenomas committed Apr 28, 2023
1 parent 239898b commit eeca89b
Show file tree
Hide file tree
Showing 7 changed files with 43 additions and 54 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ Recent changes:
* Terrain now supports texture atlases! See `registry.registerMaterial`.
* Added a fast way to specify that a worldgen chunk is entirely air/dirt/etc.
* Modernized keybinds to use [KeyboardEvent.code](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code) strings, and changed several binding state properties
* Changed default light to Directional, and updates related engine options
* Added hooks for clients to know when terrain meshes are added/removed
* Updated several lighting defaults to work well with Babylon shadows
* Engine now emits when adding/removing terrain meshes, so client can e.g. manage a ShadowGenerator

* `v0.32`: Fixes npm versioning issue - no code changes.
* `v0.31`:
Expand Down
8 changes: 4 additions & 4 deletions docs/history.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@ This is a summary of new features and breaking changes in recent `noa` versions.
* Changes default light to Directional, and updates related engine options:
* removes option `groundLightColor`, adds `lightVector`, and changes`ambientColor`
* Removes `noa.rendering.postMaterialCreationHook` - use mesh hooks instead
* Adds client hooks to know when meshes are added to or removed from the scene. This includes terrain meshes that noa manages.
* `noa.rendering.onMeshAddedToScene`
* `noa.rendering.onMeshRemovedFromScene`
* Adds `rendering.setMeshVisibility` for toggling the display of meshes that are added to the scene with `addMeshToScene`
* Engine now emits events when adding/removing terrain meshes that it magnages (static chunk terrain, or custom block meshes). Clients can listen to these to implement shadows.
* `noa#addingTerrainMesh`
* `noa#removingTerrainMesh`
* Adds `playerShadowComponent` option, defaulting to `true`
* Adds `rendering.setMeshVisibility` for toggling mesh displays

### 0.32.0
* Fixes npm versioning issue - no code changes.
Expand Down
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ var defaultOptions = {
* @emits beforeRender(dt)
* @emits afterRender(dt)
* @emits targetBlockChanged(blockDesc)
* @emits addingTerrainMesh(mesh)
* @emits removingTerrainMesh(mesh)
*/

export class Engine extends EventEmitter {
Expand Down
11 changes: 7 additions & 4 deletions src/lib/objectMesher.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/

import { TransformNode } from '@babylonjs/core/Meshes/transformNode'
import { makeProfileHook } from './util'
import '@babylonjs/core/Meshes/thinInstanceMesh'

export default ObjectMesher
Expand Down Expand Up @@ -202,7 +203,9 @@ function ObjectMesher(noa) {
*
*/

/** @param {import('../index').Engine} noa*/
function InstanceManager(noa, mesh) {
this.noa = noa
this.mesh = mesh
this.buffer = null
this.capacity = 0
Expand All @@ -216,8 +219,8 @@ function InstanceManager(noa, mesh) {
// prepare mesh for rendering
this.mesh.position.setAll(0)
this.mesh.parent = noa._objectMesher.rootNode
this.mesh.isVisible = true
noa.rendering.addMeshToScene(this.mesh, false)
this.noa.rendering.addMeshToScene(this.mesh, false)
this.noa.emit('addingTerrainMesh', this.mesh)
this.mesh.doNotSyncBoundingInfo = true
this.mesh.alwaysSelectAsActiveMesh = true
}
Expand All @@ -228,7 +231,8 @@ InstanceManager.prototype.dispose = function () {
if (this.disposed) return
this.mesh.thinInstanceCount = 0
this.setCapacity(0)
this.mesh.isVisible = false
this.noa.emit('removingTerrainMesh', this.mesh)
this.noa.rendering.setMeshVisibility(this.mesh, false)
this.mesh = null
this.keyToIndex = null
this.locToKey = null
Expand Down Expand Up @@ -347,6 +351,5 @@ function copyMatrixData(src, srcOff, dest, destOff) {



import { makeProfileHook } from './util'
var profile_hook = (PROFILE) ?
makeProfileHook(PROFILE, 'Object meshing') : () => { }
56 changes: 15 additions & 41 deletions src/lib/rendering.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ var hlpos = []


/**
* Add a mesh to the scene's octree setup so that it renders.
* Adds a mesh to the engine's selection/octree logic so that it renders.
*
* @param mesh the mesh to add to the scene
* @param isStatic pass in true if mesh never moves (i.e. change octree blocks)
Expand All @@ -252,35 +252,26 @@ var hlpos = []
Rendering.prototype.addMeshToScene = function (mesh, isStatic = false, pos = null, containingChunk = null) {
if (!mesh.metadata) mesh.metadata = {}

// exit silently if mesh has already been added and not removed
if (mesh.metadata[addedToSceneFlag]) return
// if mesh is already added, just make sure it's visisble
if (mesh.metadata[addedToSceneFlag]) {
this._octreeManager.setMeshVisibility(mesh, true)
return
}
mesh.metadata[addedToSceneFlag] = true

// find local position for mesh and move it there (unless it's parented)
if (!mesh.parent) {
if (!pos) pos = [mesh.position.x, mesh.position.y, mesh.position.z]
var lpos = []
this.noa.globalToLocal(pos, null, lpos)
mesh.position.copyFromFloats(lpos[0], lpos[1], lpos[2])
}

// save CPU by freezing terrain meshes
if (isStatic) {
mesh.freezeWorldMatrix()
if (mesh.freezeNormals) mesh.freezeNormals()
mesh.doNotSyncBoundingInfo = true
if (!pos) pos = mesh.position.asArray()
var lpos = this.noa.globalToLocal(pos, null, [])
mesh.position.fromArray(lpos)
}

// add to the octree, and add dispose handler to remove it
// add to the octree, and remove again on disposal
this._octreeManager.addMesh(mesh, isStatic, pos, containingChunk)
mesh.onDisposeObservable.add(() => {
this._octreeManager.removeMesh(mesh)
mesh.metadata[addedToSceneFlag] = false
this.onMeshRemovedFromScene(mesh, isStatic)
})

// call the post-creation hook for user logic
this.onMeshAddedToScene(mesh, isStatic)
}
var addedToSceneFlag = 'noa_added_to_scene'

Expand All @@ -296,32 +287,15 @@ var addedToSceneFlag = 'noa_added_to_scene'
*/
Rendering.prototype.setMeshVisibility = function (mesh, visible = false) {
if (!mesh.metadata) mesh.metadata = {}
if (!mesh.metadata[addedToSceneFlag]) return
this._octreeManager.setMeshVisibility(mesh, visible)
if (mesh.metadata[addedToSceneFlag]) {
this._octreeManager.setMeshVisibility(mesh, visible)
} else {
if (visible) this.addMeshToScene(mesh)
}
}



/**
* This hook is called whenever a mesh is added to the scene, either by the
* engine internally or because you called `noa.rendering.addMeshToScene`.
* You can override this to provide various custom logic - to support shadows,
* to to freeze or unfreeze materials, etc.
*
* @param {import('@babylonjs/core/Meshes').Mesh} mesh
* @param {boolean} isStaticTerrain
*/
Rendering.prototype.onMeshAddedToScene = function (mesh, isStaticTerrain = false) { }


/**
* Override this hook along with `noa.rendering.onMeshAddedToScene`.
* @param {import('@babylonjs/core/Meshes').Mesh} mesh
* @param {boolean} isStaticTerrain
*/
Rendering.prototype.onMeshRemovedFromScene = function (mesh, isStaticTerrain = false) { }





Expand Down
5 changes: 4 additions & 1 deletion src/lib/terrainMaterials.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export class TerrainMatManager {
this._defaultMat = noa.rendering.makeStandardMaterial('base-terrain')
this._defaultMat.freeze()

this.allMaterials = [this._defaultMat]

// internals
this.noa = noa
this._idCounter = 1000
Expand Down Expand Up @@ -54,6 +56,7 @@ export class TerrainMatManager {
// create a mat object for it, if needed
if (!(terrID in this._terrainIDtoMatObject)) {
var mat = createTerrainMat(this, blockMatID)
this.allMaterials.push(mat)
this._terrainIDtoMatObject[terrID] = mat
}
// cache results and done
Expand Down Expand Up @@ -158,7 +161,7 @@ function createTerrainMat(self, blockMatID = 0) {
tex.hasAlpha = true
}
}

mat.freeze()
return mat
}
Expand Down
11 changes: 9 additions & 2 deletions src/lib/terrainMesher.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export default function TerrainMesher(noa) {

// wrangles which block materials can be merged into the same mesh
var terrainMatManager = new TerrainMatManager(noa)
this.allTerrainMaterials = terrainMatManager.allMaterials

// internally expose the default flat material used for untextured terrain
this._defaultMaterial = terrainMatManager._defaultMat
Expand All @@ -57,7 +58,10 @@ export default function TerrainMesher(noa) {
}

this.disposeChunk = function (chunk) {
chunk._terrainMeshes.forEach(m => m.dispose())
chunk._terrainMeshes.forEach(mesh => {
noa.emit('removingTerrainMesh', mesh)
mesh.dispose()
})
chunk._terrainMeshes.length = 0
}

Expand Down Expand Up @@ -86,8 +90,11 @@ export default function TerrainMesher(noa) {

// add meshes to scene and finish
meshes.forEach((mesh) => {
noa.rendering.addMeshToScene(mesh, true, chunk.pos, this)
noa.emit('addingTerrainMesh', mesh)
mesh.cullingStrategy = Mesh.CULLINGSTRATEGY_BOUNDINGSPHERE_ONLY
noa.rendering.addMeshToScene(mesh, true, chunk.pos, this)
mesh.freezeNormals()
mesh.freezeWorldMatrix()
chunk._terrainMeshes.push(mesh)
})
}
Expand Down

0 comments on commit eeca89b

Please sign in to comment.