123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212 |
- import { Group, NodeMaterial, Mesh, PlaneGeometry, DoubleSide, CameraHelper } from 'three/webgpu';
- import { Fn, vec4, vec3, texture, uv, positionLocal, vec2, float, screenSize } from 'three/tsl';
- /**
- * Helper class to manage and display debug visuals for TileShadowNode.
- *
- * @augments Group
- * @three_import import { TileShadowNodeHelper } from 'three/addons/tsl/shadows/TileShadowNodeHelper.js';
- */
- class TileShadowNodeHelper extends Group {
- /**
- * @param {TileShadowNode} tileShadowNode The TileShadowNode instance to debug.
- */
- constructor( tileShadowNode ) {
- super();
- if ( ! tileShadowNode ) {
- throw new Error( 'TileShadowNode instance is required for TileShadowNodeHelper.' );
- }
- this.tileShadowNode = tileShadowNode;
- this.config = tileShadowNode.config;
- this.tiles = tileShadowNode.tiles;
- this._debugMeshes = [];
- this._shadowCamHelpers = [];
- this.initialized = false;
- }
- /**
- * Initializes the debug displays (planes and camera helpers).
- * Should be called after TileShadowNode has initialized its lights and shadow nodes.
- */
- init() {
- if ( this.tileShadowNode._shadowNodes.length !== this.tiles.length ) {
- console.error( 'Cannot initialize TileShadowNodeHelper: Shadow nodes not ready or mismatch count.' );
- return;
- }
- const tilesX = this.config.tilesX;
- const tilesY = this.config.tilesY;
- // Clear previous helpers if any (e.g., during a re-init)
- this.dispose();
- // Create a display for each shadow map tile
- for ( let i = 0; i < this.tiles.length; i ++ ) {
- // Create display plane
- const display = new Mesh( new PlaneGeometry( 1, 1 ), new NodeMaterial() );
- display.renderOrder = 9999999; // Ensure they appear on top
- display.material.transparent = true;
- display.frustumCulled = false;
- display.side = DoubleSide;
- display.material.depthTest = false; // Disable depth testing
- display.material.depthWrite = false; // Disable depth writing
- const col = i % tilesX;
- const row = Math.floor( i / tilesX );
- // Vertex shader logic for positioning the debug quad
- display.material.vertexNode = Fn( () => {
- const aspectRatio = screenSize.x.div( screenSize.y );
- const maxTiles = Math.max( tilesX, tilesY );
- const displaySize = float( 0.8 / maxTiles ); // Size adapts to number of tiles
- const margin = float( 0.01 );
- const cornerOffset = float( 0.05 );
- // Position tiles left-to-right, top-to-bottom
- const xBase = float( - 1.0 ).add( cornerOffset ).add(
- displaySize.div( 2 ).div( aspectRatio )
- ).add( float( col ).mul( displaySize.div( aspectRatio ).add( margin ) ) );
- const yBase = float( 1.0 ).sub( cornerOffset ).sub(
- displaySize.div( 2 )
- ).sub( float( row ).mul( displaySize.add( margin ) ) );
- const scaledPos = vec2(
- positionLocal.x.mul( displaySize.div( aspectRatio ) ),
- positionLocal.y.mul( displaySize )
- );
- const finalPos = vec2(
- scaledPos.x.add( xBase ),
- scaledPos.y.add( yBase )
- );
- return vec4( finalPos.x, finalPos.y, 0.0, 1.0 );
- } )();
- display.material.outputNode = Fn( () => {
- // Ensure shadowMap and depthTexture are available
- if ( ! this.tileShadowNode.shadowMap || ! this.tileShadowNode.shadowMap.depthTexture ) {
- return vec4( 1, 0, 1, 1 ); // Magenta error color
- }
- const sampledDepth = texture( this.tileShadowNode.shadowMap.depthTexture )
- .sample( uv().flipY() )
- .depth( float( i ) ) // Sample correct layer
- .compare( 0.9 ); // Example comparison value
- // Simple tint based on index for visual distinction
- const r = float( 0.5 + ( i % 3 ) * 0.16 );
- const g = float( 0.5 + ( i % 2 ) * 0.25 );
- const b = float( 0.7 + ( i % 4 ) * 0.075 );
- return vec4(
- vec3( r, g, b )
- .mul( sampledDepth )
- .saturate()
- .rgb,
- 1.0
- );
- } )();
- this.add( display );
- this._debugMeshes.push( display );
- if ( this.tileShadowNode._shadowNodes[ i ] && this.tileShadowNode._shadowNodes[ i ].shadow ) {
- const camHelper = new CameraHelper( this.tileShadowNode._shadowNodes[ i ].shadow.camera );
- camHelper.fog = false;
- this.add( camHelper );
- this._shadowCamHelpers.push( camHelper );
- } else {
- console.warn( `TileShadowNodeHelper: Could not create CameraHelper for tile index ${i}. Shadow node or camera missing.` );
- this._shadowCamHelpers.push( null );
- }
- }
- this.initialized = true;
- }
- /**
- * Updates the debug visuals (specifically camera helpers).
- * Should be called within TileShadowNode's update method.
- */
- update() {
- if ( this.initialized === false ) {
- this.init();
- }
- for ( const helper of this._shadowCamHelpers ) {
- if ( helper ) {
- helper.update(); // Update CameraHelper matrices
- helper.updateMatrixWorld( true ); // Ensure world matrix is current
- }
- }
- }
- /**
- * Removes all debug objects (planes and helpers) from the scene.
- */
- dispose() {
- if ( this.scene ) {
- for ( const mesh of this._debugMeshes ) {
- mesh.geometry.dispose();
- mesh.material.dispose();
- this.scene.remove( mesh );
- }
- for ( const helper of this._shadowCamHelpers ) {
- if ( helper ) {
- this.scene.remove( helper );
- }
- }
- }
- this._debugMeshes = [];
- this._shadowCamHelpers = [];
- }
- }
- export { TileShadowNodeHelper };
|