React (TypeScript) で WebAR する

## model-viewer

簡単に WebAR する Google 製のライブラリの model-viewer を React で使いたかったが、思いの外まとまった資料が少なく苦労した。ので、メモ。

https://modelviewer.dev/

## 結論

```typescript:ModelViewer.tsx
/** @jsxImportSource @emotion/react */
import { FC } from 'react';
import { css } from '@emotion/react';
import '@google/model-viewer';
declare global {
  namespace JSX {
    interface IntrinsicElements {
      'model-viewer': MyElementAttributes;
    }
    interface MyElementAttributes {
      src: string;
      poster: string;
      alt: string;
      ar: boolean;
      scale: string;
    }
  }
}

type Props = {
  poster: string;
  glb: string;
  usdz: string;
  alt: string;
  scale?: number;
  height?: string;
};
export const ModelViewer: FC<Props> = (props) => {
  const { poster, glb, usdz, alt, scale = 1, height = '80vh' } = props;

  const container = css`
    width: 100%;
    model-viewer {
      width: 100%;
      height: ${height};
    }
  `;

  return (
    <div css={container}>
      <model-viewer
        src={glb}
        ios-src={usdz}
        poster={poster}
        alt={alt}
        shadow-intensity="1"
        camera-controls
        ar
        scale={`${scale} ${scale} ${scale}`}
      ></model-viewer>
    </div>
  );
};
```

![Screen Shot 2022-12-20 at 16.25.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245908/6b911b81-b691-cf0f-5c78-29257276250c.png)
<img width=400 src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245908/78a49aa7-484c-e36f-170c-5b88343e601f.png alt="SP Browser"> <img width=400 src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245908/36cd4fa1-79ac-3912-2740-89f24c454d3a.png alt="AR Mode">

## 環境

- react: 18.2.0
- @google/model-viewer: 2.1.1
- @emotion/react: 11.10.5
- node: 16.15.1

## mode-viewer を埋め込む

```<model-viewer></model-viewer>``` を埋め込もうとすると、```Property 'model-viewer' does not exist on type 'JSX.IntrinsicElements'.``` と怒られてしまう。そこで、

```typescript
declare global {
  namespace JSX {
    interface IntrinsicElements {
      'model-viewer': MyElementAttributes;
    }
    interface MyElementAttributes {
      src: string;
      poster: string;
      alt: string;
      ar: boolean;
      scale: string;
    }
  }
}
```

こんな風に宣言してあげる必要がある。MyElementAttributes に書いているのは、```-``` の入っていないパラメータだ。人によって設定は違うと思うので、怒られながら設定すれば良い。

## 表示がちっちゃい

デフォルトだと Canvas が、ものすごく小さく表示されてしまうので、画面いっぱいに大きくする。

```css
    model-viewer {
      width: 100%;
      height: ${height};
    }
```

CSS の与え方は何でも良いが、```<model-viewer>``` という要素に、幅と高さを付ける。今回、幅も高さも画面いっぱい使いたかったのだが、スマートフォンで見た時に、下の方がちょっと切れちゃって、AR ボタンが押せなかったので、```80vh``` になっている。が、表示したいサイズに合わせて設定。

## 表示がでっかい

<img width=400 src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245908/e789df61-70f3-64c0-90c4-87208164cc60.jpeg alt="AR Mode">

読み込む 3D Model のサイズによるんだろうと思いつつ、AR Mode にした時に、表示される大きさがでかすぎて使いづらい。
そこで、```model-viewer``` に ```scale``` を設定する。

```typescript
      <model-viewer
        src={glb}
        ios-src={usdz}
        poster={poster}
        alt={alt}
        shadow-intensity="1"
        camera-controls
        ar
        scale={`${scale} ${scale} ${scale}`}
      ></model-viewer>
```

```ar-scale="fixed"``` にしていると ```scale``` は1（100%）固定のようだ。android と iOS で大きさが違うので、なんだか微妙だが、WebAR で使う 3D Model のポリゴン数には制限があって、それ用に作ると思うので、元を良い感じのサイズで作ってくれ。ということで。

ちょうどカメラのフレーム内に収まるように表示する方法はないものか…


## Local のテストでスマホのカメラにアクセスする

```bash
$ HTTPS=true yarn start
---
Compiled successfully!

You can now view model3d in the browser.

  Local:            https://localhost:3000
  On Your Network:  https://192.168.1.8:3000

Note that the development build is not optimized.
To create a production build, use yarn build.

webpack compiled successfully
Files successfully emitted, waiting for typecheck results...
Issues checking in progress...
No issues found.
```

https でないと、カメラアクセスはできないので、```HTTPS=true``` で立ち上げ、```On Your Network: ``` に表示されている 192.168... にアクセスする。


## おしまい

最近、3D Model を表示して、WebAR したいみたいな案件は、結構多い。
```Three.js``` は、そのうち勉強する。