resources/assets/js/visualizers/plane-mesh/index.ts

Summary

Maintainability
A
3 hrs
Test Coverage
import * as THREE from 'three'
import shaders from './shaders'
import planeMeshParameters from './planeMeshParameters'
import { audioService } from '@/services/audioService'

export const init = (container: HTMLElement) => {
  const uniforms = {
    u_time: {
      type: 'f',
      value: 2.0,
    },
    u_amplitude: {
      type: 'f',
      value: 4.0,
    },
    u_data_arr: {
      type: 'float[64]',
      value: new Uint8Array(),
    },
  }

  const analyser = audioService.analyzer

  analyser.fftSize = 1024
  const dataArray = new Uint8Array(analyser.frequencyBinCount)

  const width = container.clientWidth
  const height = container.clientHeight
  const scene = new THREE.Scene()

  const ambientLight = new THREE.AmbientLight(0xAAAAAA)
  ambientLight.castShadow = false

  const spotLight = new THREE.SpotLight(0xFFFFFF)
  spotLight.intensity = 0.9
  spotLight.position.set(-10, 40, 20)
  spotLight.castShadow = true

  const camera = new THREE.PerspectiveCamera(
    85,
    width / height,
    1,
    1000,
  )
  camera.position.z = 80

  const renderer = new THREE.WebGLRenderer()
  renderer.setSize(width, height)
  renderer.setClearAlpha(0)

  container.appendChild(renderer.domElement)

  const planeGeometry = new THREE.PlaneGeometry(64, 64, 64, 64)
  const planeMaterial = new THREE.ShaderMaterial({
    uniforms,
    vertexShader: shaders.vertex,
    fragmentShader: shaders.fragment,
    wireframe: true,
  })

  planeMeshParameters.forEach(item => {
    const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial)

    if (item.rotation.x === undefined) {
      planeMesh.rotation.y = item.rotation.y
    } else {
      planeMesh.rotation.x = item.rotation.x
    }

    planeMesh.scale.x = item.scale
    planeMesh.scale.y = item.scale
    planeMesh.scale.z = item.scale
    planeMesh.position.x = item.position.x
    planeMesh.position.y = item.position.y
    planeMesh.position.z = item.position.z

    scene.add(planeMesh)
  })

  scene.add(ambientLight)
  scene.add(spotLight)

  const render = () => {
    analyser.getByteFrequencyData(dataArray)
    uniforms.u_data_arr.value = dataArray
    camera.rotation.z += 0.001
    renderer.render(scene, camera)
  }

  const animate = () => {
    requestAnimationFrame(animate)
    render()
  }

  const windowResizeHandler = () => {
    const width = container.clientWidth
    const height = container.clientHeight
    renderer.setSize(width, height)
    camera.aspect = width / height
    camera.updateProjectionMatrix()
    renderer.domElement.width = width
    renderer.domElement.height = height
  }

  const wheelHandler = event => {
    const val = camera.position.z + event.deltaY / 100
    camera.position.z = Math.min(Math.max(val, 0), 256)
  }

  window.addEventListener('resize', windowResizeHandler)
  document.addEventListener('wheel', wheelHandler)

  animate()

  return () => {
    renderer.domElement.remove()
    renderer.dispose()
    window.removeEventListener('resize', windowResizeHandler)
    document.removeEventListener('wheel', wheelHandler)
  }
}