<template>
  <div class="canvas-backend" aria-hidden="true">
    <canvas ref="webgl" class="webgl"></canvas>
  </div>
</template>

<script>
import * as THREE from 'three'

export default {
  name: 'WebGLComponent',
  data() {
    return {
      mouseX: 0,
      mouseY: 0
    }
  },
  mounted() {
    this.initWebGL()
  },
  beforeUnmount() {
    document.removeEventListener('mousemove', this.onDocumentMouseMove, false)
    document.removeEventListener('touchstart', this.onDocumentTouchStart, false)
    document.removeEventListener('touchmove', this.onDocumentTouchMove, false)
  },
  methods: {
    initWebGL() {
      const canvas = this.$refs.webgl
      if (!canvas) return

      canvas.width = window.innerWidth
      canvas.height = window.innerHeight

      const gl = canvas.getContext('webgl')
      if (!gl) return

      const vertexShaderSource = `
        attribute vec4 position;
        attribute float scale;
        uniform mat4 modelViewMatrix;
        uniform mat4 projectionMatrix;
        void main() {
          vec4 mvPosition = modelViewMatrix * position;
          gl_PointSize = scale * 1.0 * (200.0 / - mvPosition.z);
          gl_Position = projectionMatrix * mvPosition;
        }
      `

      const fragShaderSource = `
        void main() {
          if (length(gl_PointCoord - vec2(0.5, 0.5)) > 0.49) discard;
          gl_FragColor = vec4(0.32, 0.72, 0.47, 0.35);
        }
      `

      const program = this.initShader(gl, vertexShaderSource, fragShaderSource)

      const aposLocation = gl.getAttribLocation(program, 'position')
      const scale = gl.getAttribLocation(program, 'scale')
      const modelViewMatrixLoc = gl.getUniformLocation(program, 'modelViewMatrix')
      const projectionMatrixLoc = gl.getUniformLocation(program, 'projectionMatrix')

      const SEPARATION = 100
      const AMOUNTX = 50
      const AMOUNTY = 50

      const positions = new Float32Array(AMOUNTX * AMOUNTY * 3)
      const scales = new Float32Array(AMOUNTX * AMOUNTY)

      let i = 0
      let j = 0
      for (let ix = 0; ix < AMOUNTX; ix++) {
        for (let iy = 0; iy < AMOUNTY; iy++) {
          positions[i] = ix * SEPARATION - (AMOUNTX * SEPARATION) / 2
          positions[i + 1] = 0
          positions[i + 2] = iy * SEPARATION - (AMOUNTY * SEPARATION) / 2
          scales[j] = 1
          i += 3
          j++
        }
      }

      const colorBuffer = gl.createBuffer()
      gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer)
      gl.bufferData(gl.ARRAY_BUFFER, scales, gl.STATIC_DRAW)
      gl.vertexAttribPointer(scale, 1, gl.FLOAT, false, 0, 0)
      gl.enableVertexAttribArray(scale)

      const buffer = gl.createBuffer()
      gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
      gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW)
      gl.vertexAttribPointer(aposLocation, 3, gl.FLOAT, false, 0, 0)
      gl.enableVertexAttribArray(aposLocation)

      gl.enable(gl.DEPTH_TEST)

      const width = window.innerWidth
      const height = window.innerHeight
      const camera = new THREE.PerspectiveCamera(60, width / height, 1, 10000)
      camera.position.set(944, 206, -262)
      camera.lookAt(new THREE.Vector3(0, 0, 0))
      camera.updateProjectionMatrix()
      camera.updateMatrixWorld(true)

      const mat4 = new THREE.Matrix4()
      mat4.copy(camera.projectionMatrix)
      gl.uniformMatrix4fv(projectionMatrixLoc, false, new Float32Array(mat4.elements))

      const mat4y = new THREE.Matrix4()
      mat4y.copy(camera.matrixWorldInverse)
      gl.uniformMatrix4fv(modelViewMatrixLoc, false, new Float32Array(mat4y.elements))

      let count = 0
      let rafId = null

      const draw = () => {
        camera.position.x += (this.mouseX - camera.position.x) * 0.01
        camera.updateMatrixWorld(true)
        mat4y.copy(camera.matrixWorldInverse)
        gl.uniformMatrix4fv(modelViewMatrixLoc, false, new Float32Array(mat4y.elements))

        let pi = 0
        let pj = 0
        for (let ix = 0; ix < AMOUNTX; ix++) {
          for (let iy = 0; iy < AMOUNTY; iy++) {
            positions[pi + 1] =
              Math.sin((ix + count) * 0.3) * 50 + Math.sin((iy + count) * 0.5) * 50
            scales[pj] =
              (Math.sin((ix + count) * 0.3) + 1.3) * 8 + (Math.sin((iy + count) * 0.5) + 1.3) * 8
            pi += 3
            pj++
          }
        }
        count += 0.1

        gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer)
        gl.bufferData(gl.ARRAY_BUFFER, scales, gl.STATIC_DRAW)
        gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
        gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW)

        rafId = requestAnimationFrame(draw)
        gl.drawArrays(gl.POINTS, 0, AMOUNTX * AMOUNTY)
      }

      draw()

      this._onResize = () => {
        canvas.width = window.innerWidth
        canvas.height = window.innerHeight
        gl.viewport(0, 0, window.innerWidth, window.innerHeight)
        camera.aspect = window.innerWidth / window.innerHeight
        camera.updateProjectionMatrix()
        mat4.copy(camera.projectionMatrix)
        gl.uniformMatrix4fv(projectionMatrixLoc, false, new Float32Array(mat4.elements))
      }

      window.addEventListener('resize', this._onResize)
      document.addEventListener('mousemove', this.onDocumentMouseMove, false)
    },

    initShader(gl, vertexShaderSource, fragmentShaderSource) {
      const vertexShader = gl.createShader(gl.VERTEX_SHADER)
      const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
      gl.shaderSource(vertexShader, vertexShaderSource)
      gl.shaderSource(fragmentShader, fragmentShaderSource)
      gl.compileShader(vertexShader)
      gl.compileShader(fragmentShader)
      const program = gl.createProgram()
      gl.attachShader(program, vertexShader)
      gl.attachShader(program, fragmentShader)
      gl.linkProgram(program)
      gl.useProgram(program)
      return program
    },

    onDocumentMouseMove(event) {
      this.mouseX = event.clientX - window.innerWidth / 2
      this.mouseY = event.clientY - window.innerHeight / 2
    },

    onDocumentTouchStart() {},

    onDocumentTouchMove() {}
  }
}
</script>

<style scoped>
.canvas-backend {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
}

.webgl {
  display: block;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
</style>
