22import * as THREE from "three" ;
33import {
44 Actor ,
5- ActorComponent
5+ ActorComponent ,
6+ Systems
67} from "@jolly-pixel/engine" ;
78
89// Import Internal Dependencies
@@ -124,6 +125,12 @@ export interface VoxelRendererOptions {
124125 * @default 0.1
125126 */
126127 alphaTest ?: number ;
128+
129+ /**
130+ * Optional logger instance for debug output.
131+ * Uses the engine's default logger if not provided.
132+ */
133+ logger ?: Systems . Logger ;
127134}
128135
129136/**
@@ -156,11 +163,16 @@ export class VoxelRenderer extends ActorComponent {
156163 /**
157164 * One material per tileset ID. Created lazily; disposed on tileset reload or destroy.
158165 */
159- #materials = new Map < string , THREE . MeshLambertMaterial | THREE . MeshStandardMaterial > ( ) ;
166+ #materials = new Map <
167+ string ,
168+ THREE . MeshLambertMaterial | THREE . MeshStandardMaterial
169+ > ( ) ;
160170 #materialCustomizer?: MaterialCustomizerFn ;
161171 #materialType: "lambert" | "standard" ;
162172 #alphaTest: number ;
163173
174+ #logger: Systems . Logger ;
175+
164176 constructor (
165177 actor : Actor < any > ,
166178 options : VoxelRendererOptions = { }
@@ -178,21 +190,28 @@ export class VoxelRenderer extends ActorComponent {
178190 rapier,
179191 blocks = [ ] ,
180192 shapes = [ ] ,
181- alphaTest = 0.1
193+ alphaTest = 0.1 ,
194+ logger = actor . world . logger
182195 } = options ;
183196
184197 this . #materialType = material ;
185198 this . #materialCustomizer = materialCustomizer ;
186199 this . #alphaTest = alphaTest ;
200+ this . #logger = logger . child ( {
201+ namespace : "VoxelRenderer"
202+ } ) ;
187203
188204 this . world = new VoxelWorld ( chunkSize ) ;
189205 if ( layers . length > 0 ) {
190206 layers . forEach ( ( name ) => this . addLayer ( name ) ) ;
191207 }
192208
193209 this . blockRegistry = new BlockRegistry ( blocks ) ;
194- this . shapeRegistry = BlockShapeRegistry . createDefault ( ) ;
195- shapes . forEach ( ( shape ) => this . shapeRegistry . register ( shape ) ) ;
210+ this . shapeRegistry = BlockShapeRegistry
211+ . createDefault ( ) ;
212+ shapes . forEach (
213+ ( shape ) => this . shapeRegistry . register ( shape )
214+ ) ;
196215
197216 this . tilesetManager = new TilesetManager ( ) ;
198217 this . serializer = new VoxelSerializer ( ) ;
@@ -219,7 +238,7 @@ export class VoxelRenderer extends ActorComponent {
219238 // --- Lifecycle --- //
220239 awake ( ) : void {
221240 // Build initial meshes for all existing chunks (e.g. after deserialize).
222- this . #rebuildAllChunks( ) ;
241+ this . #rebuildAllChunks( "awake" ) ;
223242 }
224243
225244 update (
@@ -231,12 +250,17 @@ export class VoxelRenderer extends ActorComponent {
231250 continue ;
232251 }
233252
253+ this . #logger. debug (
254+ `Layer with name '${ layer . name } ' is dirty and will be rebuilt.` ,
255+ { source : "update" }
256+ ) ;
234257 this . #rebuildChunk( layer , chunk ) ;
235258 chunk . dirty = false ;
236259 }
237260 }
238261
239262 override destroy ( ) : void {
263+ this . #logger. debug ( "Destroying VoxelRenderer." ) ;
240264 // Remove and dispose all chunk meshes individually (we own the geometries
241265 // but share materials per tileset, so we must NOT call removeChildren).
242266 for ( const mesh of this . #chunkMeshes. values ( ) ) {
@@ -369,6 +393,7 @@ export class VoxelRenderer extends ActorComponent {
369393 ) ;
370394
371395 this . tilesetManager . registerTexture ( def , texture ) ;
396+ this . #logger. debug ( `Loaded tileset '${ def . id } ' from '${ def . src } '` ) ;
372397
373398 // Invalidate the cached material for this tileset so it is recreated
374399 // with the new texture.
@@ -377,11 +402,13 @@ export class VoxelRenderer extends ActorComponent {
377402 this . #materials. delete ( def . id ) ;
378403
379404 // Force all chunks to rebuild geometry (UV offsets may have changed).
380- this . #markAllChunksDirty( ) ;
405+ this . #markAllChunksDirty( "loadTileset" ) ;
381406 }
382407
383408 // --- Serialization --- //
384409 save ( ) : VoxelWorldJSON {
410+ this . #logger. debug ( "Serializing world to JSON..." ) ;
411+
385412 return this . serializer . serialize (
386413 this . world ,
387414 this . tilesetManager
@@ -397,6 +424,7 @@ export class VoxelRenderer extends ActorComponent {
397424 mesh . geometry . dispose ( ) ;
398425 }
399426 this . #chunkMeshes. clear ( ) ;
427+ this . #logger. debug ( "Cleared existing chunk meshes while loading new world." ) ;
400428
401429 // Register block definitions embedded by a converter, if present.
402430 // Skips IDs already registered so callers can pre-register overrides.
@@ -426,12 +454,15 @@ export class VoxelRenderer extends ActorComponent {
426454 mat . dispose ( ) ;
427455 }
428456 this . #materials. clear ( ) ;
429- this . #rebuildAllChunks( ) ;
457+
458+ this . #rebuildAllChunks( "load" ) ;
430459 }
431460
432461 #getMaterial(
433462 tilesetId : string
434463 ) : THREE . MeshLambertMaterial | THREE . MeshStandardMaterial {
464+ this . #logger. debug ( `Getting material for tileset '${ tilesetId } '` ) ;
465+
435466 let material = this . #materials. get ( tilesetId ) ;
436467 if ( material ) {
437468 return material ;
@@ -466,16 +497,17 @@ export class VoxelRenderer extends ActorComponent {
466497 layer : VoxelLayer ,
467498 chunk : VoxelChunk
468499 ) : void {
469- const chunkKeyBase = `${ layer . id } :${ chunk . cx } ,${ chunk . cy } ,${ chunk . cz } ` ;
500+ const chunkKeyBase = `${ layer . id } :${ chunk . toString ( ) } ` ;
501+ this . #logger. debug (
502+ `Rebuilding chunk '${ chunkKeyBase } ' with layer name '${ layer . name } '`
503+ ) ;
470504
471505 // Remove all existing meshes for this chunk (rebuilt per tileset below).
472- const keysToRemove : string [ ] = [ ] ;
473506 for ( const key of this . #chunkMeshes. keys ( ) ) {
474- if ( key . startsWith ( `${ chunkKeyBase } :` ) ) {
475- keysToRemove . push ( key ) ;
507+ if ( ! key . startsWith ( `${ chunkKeyBase } :` ) ) {
508+ continue ;
476509 }
477- }
478- for ( const key of keysToRemove ) {
510+
479511 const mesh = this . #chunkMeshes. get ( key ) ! ;
480512 this . actor . object3D . remove ( mesh ) ;
481513 mesh . geometry . dispose ( ) ;
@@ -502,8 +534,18 @@ export class VoxelRenderer extends ActorComponent {
502534
503535 // Rebuild collision collider if physics is enabled.
504536 if ( this . #colliderBuilder) {
505- const collider = this . #buildColliderFromGeometries( chunk , geometries , layer . offset ) ;
537+ const offset = layer . offset ;
538+ this . #logger. debug (
539+ `Rebuilding chunk geometries collider '${ chunkKeyBase } ' with layer name '${ layer . name } '` ,
540+ {
541+ offset
542+ }
543+ ) ;
544+
545+ const collider = this . #buildColliderFromGeometries( chunk , geometries , offset ) ;
506546 if ( collider ) {
547+ this . #logger. debug ( `Successfully built collider for chunk '${ chunkKeyBase } '` ) ;
548+
507549 this . #chunkColliders. set ( chunkKeyBase , collider ) ;
508550 }
509551 }
@@ -518,11 +560,16 @@ export class VoxelRenderer extends ActorComponent {
518560 geometries : Map < string , THREE . BufferGeometry > ,
519561 layerOffset : { x : number ; y : number ; z : number ; }
520562 ) : RapierCollider | null {
563+ const colliderBuilder = this . #colliderBuilder;
564+ if ( ! colliderBuilder ) {
565+ return null ;
566+ }
567+
521568 if ( geometries . size === 1 ) {
522569 // Fast path: single tileset — pass the geometry directly.
523570 const [ geometry ] = geometries . values ( ) ;
524571
525- return this . # colliderBuilder! . buildChunkCollider ( chunk , geometry , layerOffset ) ;
572+ return colliderBuilder . buildChunkCollider ( chunk , geometry , layerOffset ) ;
526573 }
527574
528575 // Merge position/index arrays from all tileset geometries.
@@ -557,20 +604,28 @@ export class VoxelRenderer extends ActorComponent {
557604 ) ;
558605 combinedGeo . setIndex ( combinedIndices ) ;
559606
560- const collider = this . # colliderBuilder! . buildChunkCollider ( chunk , combinedGeo , layerOffset ) ;
607+ const collider = colliderBuilder . buildChunkCollider ( chunk , combinedGeo , layerOffset ) ;
561608 combinedGeo . dispose ( ) ;
562609
563610 return collider ;
564611 }
565612
566- #rebuildAllChunks( ) : void {
613+ #rebuildAllChunks(
614+ source ?: string
615+ ) : void {
616+ this . #logger. debug ( "Rebuilding all chunks..." , { source } ) ;
617+
567618 for ( const { layer, chunk } of this . world . getAllChunks ( ) ) {
568619 this . #rebuildChunk( layer , chunk ) ;
569620 chunk . dirty = false ;
570621 }
571622 }
572623
573- #markAllChunksDirty( ) : void {
624+ #markAllChunksDirty(
625+ source ?: string
626+ ) : void {
627+ this . #logger. debug ( "Marking all chunks dirty..." , { source } ) ;
628+
574629 for ( const { chunk } of this . world . getAllChunks ( ) ) {
575630 chunk . dirty = true ;
576631 }
0 commit comments