最小構成のWebXR AR

WebXR AR Paint Advent Calendar3日目です（チガウ）。

前回の[エントリ](https://www.kabuku.co.jp/developers/webxrarpaint)で[WebXR AR Paint](https://threejs.org/examples/webxr_ar_paint.html)のソースコードについて雑に説明しました。ただ、改めて見ると、WebXRにとってはあまり本質ではない部分の説明が長くなってしまっていて混乱させるだけだったかもしれません。ということで、今回WebXR AR Paintのコードの`TubePainter`関連のコードをばっさり削って最小構成にしてみました。

https://technohippy.github.io/webxr-ar-sample/

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8717/c04d7a76-2f17-d203-6390-79c1f9f311ed.png" width="300px">

画面をタップすると、その時点での端末の位置と向きにあわせて円錐が表示されます。

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>three.js ar</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <link type="text/css" rel="stylesheet" href="main.css">
  </head>
  <body>
    <script type="module">
      import * as THREE from './jsm/three.module.js';
      import { ARButton } from './jsm/ARButton.js';

      let camera, scene, renderer;
      let controller;

      init();
      animate();

      function init() {

        // 1. レンダラーのalphaオプションをtrueにする
        renderer = new THREE.WebGLRenderer({antialias: true, alpha: true});

        renderer.setPixelRatio(window.devicePixelRatio);
        renderer.setSize(window.innerWidth, window.innerHeight);

        // 2. レンダラーのXR機能を有効にする
        renderer.xr.enabled = true;

        document.body.appendChild(renderer.domElement);

        // 3. AR表示を有効にするためのボタンを画面に追加する
        document.body.appendChild(ARButton.createButton(renderer));

        scene = new THREE.Scene();
        let light = new THREE.HemisphereLight(0xffffff, 0xbbbbff, 1);
        light.position.set(0, 1, 0);
        scene.add(light);

        camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 20);
        window.addEventListener('resize', () => {
          camera.aspect = window.innerWidth / window.innerHeight;
          camera.updateProjectionMatrix();
          renderer.setSize(window.innerWidth, window.innerHeight);
        }, false);

        // 4. XRセッションにアクセスするためコントローラーを取得する
        controller = renderer.xr.getController(0);
        controller.addEventListener('selectend', () => {
          controller.userData.isSelecting = true;
        });
      }

      function makeArrow(color) {
        const geometry = new THREE.ConeGeometry(0.03, 0.1, 32)
        const material = new THREE.MeshStandardMaterial({
          color: color,
          roughness: 0.9,
          metalness: 0.0,
          side: THREE.DoubleSide
        });
        const localMesh = new THREE.Mesh(geometry, material);
        localMesh.rotation.x = -Math.PI / 2
        const mesh = new THREE.Group()
        mesh.add(localMesh)
        return mesh
      }

      function handleController(controller) {
        if (!controller.userData.isSelecting) return;

        const mesh = makeArrow(Math.floor(Math.random() * 0xffffff))

        // 5. コントローラーのposition, rotationプロパティを使用して
        //    AR空間内での端末の姿勢を取得し、メッシュに適用する
        mesh.position.copy(controller.position)
        mesh.rotation.copy(controller.rotation)

        scene.add(mesh)

        controller.userData.isSelecting = false
      }

      function animate() {
        renderer.setAnimationLoop(render);
      }

      function render() {
        handleController(controller);
        renderer.render(scene, camera);
      }
    </script>
  </body>
</html>
```

要するに以下のような処理を追加すればARモードを実装できます。

1. レンダラーのalphaオプションをtrueにする
2. レンダラーのXR機能を有効にする
3. AR表示を有効にするためのボタンを画面に追加する
4. XRセッションにアクセスするためコントローラーを取得する
5. コントローラーのposition, rotationプロパティを使用してAR空間内での端末の姿勢を取得し、メッシュに適用する

割と簡単にARアプリが実装できて楽しいので、ぜひ試してみてください。