text: import {
    Group,
    Mesh,
    LineSegments,
    BufferGeometry,
    LineBasicMaterial,
    Box3Helper,
    Box3,
    PlaneGeometry,
    MeshBasicMaterial,
    BufferAttribute,
    DoubleSide,
  } from 'three'
  
  class CSMHelper extends Group {
    constructor(csm) {
      super()
      this.csm = csm
      this.displayFrustum = true
      this.displayPlanes = true
      this.displayShadowBounds = true
  
      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])
      const positions = new Float32Array(24)
      const frustumGeometry = new BufferGeometry()
      frustumGeometry.setIndex(new BufferAttribute(indices, 1))
      frustumGeometry.setAttribute('position', new BufferAttribute(positions, 3, false))
      const frustumLines = new LineSegments(frustumGeometry, new LineBasicMaterial())
      this.add(frustumLines)
  
      this.frustumLines = frustumLines
      this.cascadeLines = []
      this.cascadePlanes = []
      this.shadowLines = []
    }
  
    updateVisibility() {
      const displayFrustum = this.displayFrustum
      const displayPlanes = this.displayPlanes
      const displayShadowBounds = this.displayShadowBounds
  
      const frustumLines = this.frustumLines
      const cascadeLines = this.cascadeLines
      const cascadePlanes = this.cascadePlanes
      const shadowLines = this.shadowLines
      for (let i = 0, l = cascadeLines.length; i < l; i++) {
        const cascadeLine = cascadeLines[i]
        const cascadePlane = cascadePlanes[i]
        const shadowLineGroup = shadowLines[i]
  
        cascadeLine.visible = displayFrustum
        cascadePlane.visible = displayFrustum && displayPlanes
        shadowLineGroup.visible = displayShadowBounds
      }
  
      frustumLines.visible = displayFrustum
    }
  
    update() {
      const csm = this.csm
      const camera = csm.camera
      const cascades = csm.cascades
      const mainFrustum = csm.mainFrustum
      const frustums = csm.frustums
      const lights = csm.lights
  
      const frustumLines = this.frustumLines
      const frustumLinePositions = frustumLines.geometry.getAttribute('position')
      const cascadeLines = this.cascadeLines
      const cascadePlanes = this.cascadePlanes
      const shadowLines = this.shadowLines
  
      this.position.copy(camera.position)
      this.quaternion.copy(camera.quaternion)
      this.scale.copy(camera.scale)
      this.updateMatrixWorld(true)
  
      while (cascadeLines.length > cascades) {
        this.remove(cascadeLines.pop())
        this.remove(cascadePlanes.pop())
        this.remove(shadowLines.pop())
      }
  
      while (cascadeLines.length < cascades) {
        const cascadeLine = new Box3Helper(new Box3(), 0xffffff)
        const planeMat = new MeshBasicMaterial({
          transparent: true,
          opacity: 0.1,
          depthWrite: false,
          side: DoubleSide,
        })
        const cascadePlane = new Mesh(new PlaneGeometry(), planeMat)
        const shadowLineGroup = new Group()
        const shadowLine = new Box3Helper(new Box3(), 0xffff00)
        shadowLineGroup.add(shadowLine)
  
        this.add(cascadeLine)
        this.add(cascadePlane)
        this.add(shadowLineGroup)
  
        cascadeLines.push(cascadeLine)
        cascadePlanes.push(cascadePlane)
        shadowLines.push(shadowLineGroup)
      }
  
      for (let i = 0; i < cascades; i++) {
        const frustum = frustums[i]
        const light = lights[i]
        const shadowCam = light.shadow.camera
        const farVerts = frustum.vertices.far
  
        const cascadeLine = cascadeLines[i]
        const cascadePlane = cascadePlanes[i]
        const shadowLineGroup = shadowLines[i]
        const shadowLine = shadowLineGroup.children[0]
  
        cascadeLine.box.min.copy(farVerts[2])
        cascadeLine.box.max.copy(farVerts[0])
        cascadeLine.box.max.z += 1e-4
  
        cascadePlane.position.addVectors(farVerts[0], farVerts[2])
        cascadePlane.position.multiplyScalar(0.5)
        cascadePlane.scale.subVectors(farVerts[0], farVerts[2])
        cascadePlane.scale.z = 1e-4
  
        this.remove(shadowLineGroup)
        shadowLineGroup.position.copy(shadowCam.position)
        shadowLineGroup.quaternion.copy(shadowCam.quaternion)
        shadowLineGroup.scale.copy(shadowCam.scale)
        shadowLineGroup.updateMatrixWorld(true)
        this.attach(shadowLineGroup)
  
        shadowLine.box.min.set(shadowCam.bottom, shadowCam.left, -shadowCam.far)
        shadowLine.box.max.set(shadowCam.top, shadowCam.right, -shadowCam.near)
      }
  
      const nearVerts = mainFrustum.vertices.near
      const farVerts = mainFrustum.vertices.far
      frustumLinePositions.setXYZ(0, farVerts[0].x, farVerts[0].y, farVerts[0].z)
      frustumLinePositions.setXYZ(1, farVerts[3].x, farVerts[3].y, farVerts[3].z)
      frustumLinePositions.setXYZ(2, farVerts[2].x, farVerts[2].y, farVerts[2].z)
      frustumLinePositions.setXYZ(3, farVerts[1].x, farVerts[1].y, farVerts[1].z)
  
      frustumLinePositions.setXYZ(4, nearVerts[0].x, nearVerts[0].y, nearVerts[0].z)
      frustumLinePositions.setXYZ(5, nearVerts[3].x, nearVerts[3].y, nearVerts[3].z)
      frustumLinePositions.setXYZ(6, nearVerts[2].x, nearVerts[2].y, nearVerts[2].z)
      frustumLinePositions.setXYZ(7, nearVerts[1].x, nearVerts[1].y, nearVerts[1].z)
      frustumLinePositions.needsUpdate = true
    }
  }
  
  export { CSMHelper }
  