CSMHelper.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. import {
  2. Group,
  3. Mesh,
  4. LineSegments,
  5. BufferGeometry,
  6. LineBasicMaterial,
  7. Box3Helper,
  8. Box3,
  9. PlaneGeometry,
  10. MeshBasicMaterial,
  11. BufferAttribute,
  12. DoubleSide
  13. } from 'three';
  14. /**
  15. * A helper for visualizing the cascades of a CSM instance.
  16. *
  17. * @augments Group
  18. * @three_import import { CSMHelper } from 'three/addons/csm/CSMHelper.js';
  19. */
  20. class CSMHelper extends Group {
  21. /**
  22. * Constructs a new CSM helper.
  23. *
  24. * @param {CSM|CSMShadowNode} csm - The CSM instance to visualize.
  25. */
  26. constructor( csm ) {
  27. super();
  28. /**
  29. * The CSM instance to visualize.
  30. *
  31. * @type {CSM|CSMShadowNode}
  32. */
  33. this.csm = csm;
  34. /**
  35. * Whether to display the CSM frustum or not.
  36. *
  37. * @type {boolean}
  38. * @default true
  39. */
  40. this.displayFrustum = true;
  41. /**
  42. * Whether to display the cascade planes or not.
  43. *
  44. * @type {boolean}
  45. * @default true
  46. */
  47. this.displayPlanes = true;
  48. /**
  49. * Whether to display the shadow bounds or not.
  50. *
  51. * @type {boolean}
  52. * @default true
  53. */
  54. this.displayShadowBounds = true;
  55. const indices = new Uint16Array( [ 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 ] );
  56. const positions = new Float32Array( 24 );
  57. const frustumGeometry = new BufferGeometry();
  58. frustumGeometry.setIndex( new BufferAttribute( indices, 1 ) );
  59. frustumGeometry.setAttribute( 'position', new BufferAttribute( positions, 3, false ) );
  60. const frustumLines = new LineSegments( frustumGeometry, new LineBasicMaterial() );
  61. this.add( frustumLines );
  62. this.frustumLines = frustumLines;
  63. this.cascadeLines = [];
  64. this.cascadePlanes = [];
  65. this.shadowLines = [];
  66. }
  67. /**
  68. * This method must be called if one of the `display*` properties is changed at runtime.
  69. */
  70. updateVisibility() {
  71. const displayFrustum = this.displayFrustum;
  72. const displayPlanes = this.displayPlanes;
  73. const displayShadowBounds = this.displayShadowBounds;
  74. const frustumLines = this.frustumLines;
  75. const cascadeLines = this.cascadeLines;
  76. const cascadePlanes = this.cascadePlanes;
  77. const shadowLines = this.shadowLines;
  78. for ( let i = 0, l = cascadeLines.length; i < l; i ++ ) {
  79. const cascadeLine = cascadeLines[ i ];
  80. const cascadePlane = cascadePlanes[ i ];
  81. const shadowLineGroup = shadowLines[ i ];
  82. cascadeLine.visible = displayFrustum;
  83. cascadePlane.visible = displayFrustum && displayPlanes;
  84. shadowLineGroup.visible = displayShadowBounds;
  85. }
  86. frustumLines.visible = displayFrustum;
  87. }
  88. /**
  89. * Updates the helper. This method should be called in the app's animation loop.
  90. */
  91. update() {
  92. const csm = this.csm;
  93. const camera = csm.camera;
  94. const cascades = csm.cascades;
  95. const mainFrustum = csm.mainFrustum;
  96. const frustums = csm.frustums;
  97. const lights = csm.lights;
  98. const frustumLines = this.frustumLines;
  99. const frustumLinePositions = frustumLines.geometry.getAttribute( 'position' );
  100. const cascadeLines = this.cascadeLines;
  101. const cascadePlanes = this.cascadePlanes;
  102. const shadowLines = this.shadowLines;
  103. if ( camera === null ) return;
  104. this.position.copy( camera.position );
  105. this.quaternion.copy( camera.quaternion );
  106. this.scale.copy( camera.scale );
  107. this.updateMatrixWorld( true );
  108. while ( cascadeLines.length > cascades ) {
  109. this.remove( cascadeLines.pop() );
  110. this.remove( cascadePlanes.pop() );
  111. this.remove( shadowLines.pop() );
  112. }
  113. while ( cascadeLines.length < cascades ) {
  114. const cascadeLine = new Box3Helper( new Box3(), 0xffffff );
  115. const planeMat = new MeshBasicMaterial( { transparent: true, opacity: 0.1, depthWrite: false, side: DoubleSide } );
  116. const cascadePlane = new Mesh( new PlaneGeometry(), planeMat );
  117. const shadowLineGroup = new Group();
  118. const shadowLine = new Box3Helper( new Box3(), 0xffff00 );
  119. shadowLineGroup.add( shadowLine );
  120. this.add( cascadeLine );
  121. this.add( cascadePlane );
  122. this.add( shadowLineGroup );
  123. cascadeLines.push( cascadeLine );
  124. cascadePlanes.push( cascadePlane );
  125. shadowLines.push( shadowLineGroup );
  126. }
  127. for ( let i = 0; i < cascades; i ++ ) {
  128. const frustum = frustums[ i ];
  129. const light = lights[ i ];
  130. const shadowCam = light.shadow.camera;
  131. const farVerts = frustum.vertices.far;
  132. const cascadeLine = cascadeLines[ i ];
  133. const cascadePlane = cascadePlanes[ i ];
  134. const shadowLineGroup = shadowLines[ i ];
  135. const shadowLine = shadowLineGroup.children[ 0 ];
  136. cascadeLine.box.min.copy( farVerts[ 2 ] );
  137. cascadeLine.box.max.copy( farVerts[ 0 ] );
  138. cascadeLine.box.max.z += 1e-4;
  139. cascadePlane.position.addVectors( farVerts[ 0 ], farVerts[ 2 ] );
  140. cascadePlane.position.multiplyScalar( 0.5 );
  141. cascadePlane.scale.subVectors( farVerts[ 0 ], farVerts[ 2 ] );
  142. cascadePlane.scale.z = 1e-4;
  143. this.remove( shadowLineGroup );
  144. shadowLineGroup.position.copy( shadowCam.position );
  145. shadowLineGroup.quaternion.copy( shadowCam.quaternion );
  146. shadowLineGroup.scale.copy( shadowCam.scale );
  147. shadowLineGroup.updateMatrixWorld( true );
  148. this.attach( shadowLineGroup );
  149. shadowLine.box.min.set( shadowCam.bottom, shadowCam.left, - shadowCam.far );
  150. shadowLine.box.max.set( shadowCam.top, shadowCam.right, - shadowCam.near );
  151. }
  152. const nearVerts = mainFrustum.vertices.near;
  153. const farVerts = mainFrustum.vertices.far;
  154. frustumLinePositions.setXYZ( 0, farVerts[ 0 ].x, farVerts[ 0 ].y, farVerts[ 0 ].z );
  155. frustumLinePositions.setXYZ( 1, farVerts[ 3 ].x, farVerts[ 3 ].y, farVerts[ 3 ].z );
  156. frustumLinePositions.setXYZ( 2, farVerts[ 2 ].x, farVerts[ 2 ].y, farVerts[ 2 ].z );
  157. frustumLinePositions.setXYZ( 3, farVerts[ 1 ].x, farVerts[ 1 ].y, farVerts[ 1 ].z );
  158. frustumLinePositions.setXYZ( 4, nearVerts[ 0 ].x, nearVerts[ 0 ].y, nearVerts[ 0 ].z );
  159. frustumLinePositions.setXYZ( 5, nearVerts[ 3 ].x, nearVerts[ 3 ].y, nearVerts[ 3 ].z );
  160. frustumLinePositions.setXYZ( 6, nearVerts[ 2 ].x, nearVerts[ 2 ].y, nearVerts[ 2 ].z );
  161. frustumLinePositions.setXYZ( 7, nearVerts[ 1 ].x, nearVerts[ 1 ].y, nearVerts[ 1 ].z );
  162. frustumLinePositions.needsUpdate = true;
  163. }
  164. /**
  165. * Frees the GPU-related resources allocated by this instance. Call this
  166. * method whenever this instance is no longer used in your app.
  167. */
  168. dispose() {
  169. const frustumLines = this.frustumLines;
  170. const cascadeLines = this.cascadeLines;
  171. const cascadePlanes = this.cascadePlanes;
  172. const shadowLines = this.shadowLines;
  173. frustumLines.geometry.dispose();
  174. frustumLines.material.dispose();
  175. const cascades = this.csm.cascades;
  176. for ( let i = 0; i < cascades; i ++ ) {
  177. const cascadeLine = cascadeLines[ i ];
  178. const cascadePlane = cascadePlanes[ i ];
  179. const shadowLineGroup = shadowLines[ i ];
  180. const shadowLine = shadowLineGroup.children[ 0 ];
  181. cascadeLine.dispose(); // Box3Helper
  182. cascadePlane.geometry.dispose();
  183. cascadePlane.material.dispose();
  184. shadowLine.dispose(); // Box3Helper
  185. }
  186. }
  187. }
  188. export { CSMHelper };