From eeca89b7b0033ec1c41fb1ba4616e5505a2d6ac2 Mon Sep 17 00:00:00 2001 From: andyhall Date: Fri, 28 Apr 2023 15:51:10 +0900 Subject: [PATCH] Reorganize code around adding/removing/freezing terrain meshes. - `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 --- README.md | 4 +-- docs/history.md | 8 +++--- src/index.js | 2 ++ src/lib/objectMesher.js | 11 +++++--- src/lib/rendering.js | 56 ++++++++++--------------------------- src/lib/terrainMaterials.js | 5 +++- src/lib/terrainMesher.js | 11 ++++++-- 7 files changed, 43 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index da5c4d93..6b0e5ffa 100644 --- a/README.md +++ b/README.md @@ -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`: diff --git a/docs/history.md b/docs/history.md index d3b002ea..51b70d09 100644 --- a/docs/history.md +++ b/docs/history.md @@ -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. diff --git a/src/index.js b/src/index.js index 994e9182..3185393a 100644 --- a/src/index.js +++ b/src/index.js @@ -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 { diff --git a/src/lib/objectMesher.js b/src/lib/objectMesher.js index 84d57fae..09a9a341 100644 --- a/src/lib/objectMesher.js +++ b/src/lib/objectMesher.js @@ -4,6 +4,7 @@ */ import { TransformNode } from '@babylonjs/core/Meshes/transformNode' +import { makeProfileHook } from './util' import '@babylonjs/core/Meshes/thinInstanceMesh' export default ObjectMesher @@ -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 @@ -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 } @@ -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 @@ -347,6 +351,5 @@ function copyMatrixData(src, srcOff, dest, destOff) { -import { makeProfileHook } from './util' var profile_hook = (PROFILE) ? makeProfileHook(PROFILE, 'Object meshing') : () => { } diff --git a/src/lib/rendering.js b/src/lib/rendering.js index 043b47a9..27d2469b 100644 --- a/src/lib/rendering.js +++ b/src/lib/rendering.js @@ -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) @@ -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' @@ -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) { } - - diff --git a/src/lib/terrainMaterials.js b/src/lib/terrainMaterials.js index 0aa7dee9..ca2704e6 100644 --- a/src/lib/terrainMaterials.js +++ b/src/lib/terrainMaterials.js @@ -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 @@ -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 @@ -158,7 +161,7 @@ function createTerrainMat(self, blockMatID = 0) { tex.hasAlpha = true } } - + mat.freeze() return mat } diff --git a/src/lib/terrainMesher.js b/src/lib/terrainMesher.js index fa6d66d1..5580ac54 100644 --- a/src/lib/terrainMesher.js +++ b/src/lib/terrainMesher.js @@ -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 @@ -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 } @@ -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) }) }