Vue.jsでWebVR(A-Frame)を扱うととても便利

この度、作品投稿プラットフォームのFantasficでWebVRを使ったイラスト閲覧機能を[リリース](https://fantasfic.fun/info/21)しました。

この機能は投稿された画像作品をWebVR上で閲覧できるというシンプルなものです。
Vue.jsを介して[A-Frame](https://aframe.io/)を扱うことで実現しています。

A-FrameはDOMで表現した3DモデルをVRでレンダリングしてくれるライブラリです。
DOMで空間を表現するため、Vue.jsなどのフロントエンドフレームワークと親和性が高い作りになっています。

イベントもDOMの仕組みで処理できます。なので、3Dの平面オブジェクトを表すDOMに直接@clickでイベントリスナーを定義してイベントを受け取るといったことができます。下記がコード例です。

```vue
<template>
  <a-scene ref="scene">
    <!-- 平面を表示 -->
    <a-plane width="1" height="1" :position="planePosition" cursor @click="onClickPlane" />
  </a-scene>
</template>
<script>
export default {
  data() {
    return {
      planePositionSrc: {x: 0, y: 0, z: -1}
    }
  },
  computed: {
    planePosition () {
      const pos = this.planePositionSrc
      return `${pos.x} ${pos.y} ${pos.z}`
    }
  },
  methods: {
    // クリックすると平面が遠のく
    onClickPlane() {
      this.planePositionSrc.z += 0.5
    }
  }
}
</script>
```

フロントエンドフレームワークで実装するとコンポーネント化も簡単です。
下記はOculusのControllerを独自に拡張したコンポーネントの実装例です。
(若干きもちわるいことしてますが許してください)

```vue
<template>
  <a-entity
    id="controller"
    app-oculus-controls
    oculus-go-controls
    daydream-controls
    @triggerup="onTriggerUp"
    @triggerdown="onTriggerDown"
    @trackpadup="onTrackpadUp"
    @trackpaddown="onTrackpadDown"
    @touchstart="onTouchStart"
    @touchend="onTouchEnd"
    @axismove="onAxisMove"
  ></a-entity>
</template>
<script>
const handler = {
  get: function(target, name) {
    if (target.controller) {
      return target.controller[name]
    }
    return _ => null
  }
}
const target = {}
const proxy = new Proxy(target, handler)
window.AFRAME.registerComponent('app-oculus-controls', {
  init: function() {
    console.log('initialize controller')
  },
  tick: function() {
    proxy.onTick(this.el)
  }
})

export default {
  data() {
    return {
      trigger: false,
      touch: false
    }
  },
  mounted() {
    target.controller = this
  },
  destroy() {
    target.controller = null
  },
  methods: {
    onTriggerUp(e) {
      this.trigger = false
      this._startTriggerPos = null
      this.$emit('triggerup', e)
    },
    onTriggerDown(e) {
      this.trigger = true
      this._startTriggerPos = e.target.object3D.position.clone()
      this.$emit('triggerdown', e)
    },
    onTrackpadUp(e) {
      this.trackpad = false
      this._trackpadDownPos = null
      this.$emit('trackpadup', e)
    },
    onTrackpadDown(e) {
      this.trackpad = true
      this._trackpadDownPos = e.target.object3D.position.clone()
      this.$emit('trackpaddown', e)
    },
    onTouchStart(e) {
      this.touch = true
      this._startTouchPos = e.target.object3D.position.clone()
      this.$emit('touchstart', e)
    },
    onTouchEnd(e) {
      this.touch = false
      this._startTouchPos = null
      this._lastAxis = null
      this.$emit('touchend', e)
    },
    onAxisMove(e) {
      this.$emit('axismove', e)
      if (this.touch && !this.trackpad) {
        const axis = e.detail.axis
        if (this._lastAxis === null) {
          this._lastAxis = [].concat(axis)
        }
        if (this._lastAxis && this._lastAxis.length > 0) {
          const dx = axis[0] - this._lastAxis[0]
          const dy = axis[1] - this._lastAxis[1]
          this.$emit('axischange', { diff: { x: dx, y: dy } })
        }
      }
    },
    onTick(target) {
      if (this.trigger && this._startTriggerPos) {
        const c = target.object3D.position.clone()
        const s = this._startTriggerPos
        const diff = new window.THREE.Vector3(s.x - c.x, s.y - c.y, s.z - c.z)
        this.$emit('triggerpressmove', { diff })
        this._startTriggerPos = c
      }
      if (this.trackpad && this._trackpadDownPos) {
        const c = target.object3D.position.clone()
        const s = this._trackpadDownPos
        const diff = new window.THREE.Vector3(s.x - c.x, s.y - c.y, s.z - c.z)
        this.$emit('trackpadpressmove', { diff })
        this._trackpadDownPos = c
      }
    }
  }
}
</script>
```

いくらかThree.jsについて知っている必要があるものの、Vueで普通のWebフロントエンドを書くような感覚でVRアプリを実装できるのは助かりますね。
Oculus Questの販売も控えていますし、VRは今年も躍進すると信じています。VRアプリをネイティブで記述するのは敷居が高いと感じる方はVueやReactを使ってWebVRから挑戦してみると入りやすいかもしれません。