React-Three-Fiber でアイコンをグリングリン回したい

## react-three-fiber でアイコンをクルクルする
したいよね？
なので、cubeGeometry に png画像を貼り付けてグルグルさせてみました。

### 準備
Typescript 版の React プロジェクト作成
react-three/fiber のインストール
```bash
  % npx create-react-app cube-icon
  % cd cube-icon
  % npm install three @react-three/fiber
```

アイコン用ファイル( icon.png )を `cube-icon/public` 以下に配置します。

今回は以下の画像ファイルを使用しました。
大人気 AppleWatch アプリの [男は読経！女も読経！](https://apps.apple.com/us/app/id1574287143) 用に作ったアイコンです。

アートですねぇ...

![icon.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/481708/a339dcf3-6e68-c50d-3ac9-da38552ebc92.png)


### コード

App.js, App.css の２ファイル、Index.jsは省略

#### App.css
この設定がないと表示が小さくなってしまう
```CSS:App.css
.App {
  width: 100vw;
  height: 100vh;
}
```


#### App.js

`function IconBoard` 内で 1x1x0.05の薄い板を作って
マテリアルのマップにテキスチャを貼り付けてます。

`IconBoard` の引数でテキスチャとポジションを指定できるようにしてます。

`useFrame()` 内に
マウスが `mesh` の上に乗るとアイコンが捻り回転、離れると戻る。
クリックされると縦回転、一回転すると止まる。
の処理を記述。

```React:App.js
import { useState, useRef } from 'react';
import { Canvas, useFrame } from '@react-three/fiber';
import { TextureLoader } from 'three';
import './App.css';

function IconBoard(props) {
  const iconRef = useRef();
  const [hover, setHover] = useState(false);
  const [click, setClick] = useState(false);

  const maxRad = Math.PI*2;

  useFrame(()=>{
    const rotZ = iconRef.current.rotation.z;
    if (hover) {
      iconRef.current.rotation.z = rotZ < maxRad? rotZ + 0.03: maxRad;
    } else {
      iconRef.current.rotation.z = rotZ > 0.0? rotZ - 0.05: 0.0;
    }
    if (click) {
      iconRef.current.rotation.y += 0.04;
      if (iconRef.current.rotation.y > maxRad) {
        iconRef.current.rotation.y = 0;
        setClick(false);
      }
    }
  });

  return (
        <mesh 
        position={props.position}
        ref={iconRef} 
        onPointerOver={()=>setHover(true)} 
        onPointerLeave={()=>setHover(false)}
        onClick={()=>setClick(true)}
        >
          <boxGeometry args={[1,1,0.05]} />
          <meshBasicMaterial map={props.map} />
        </mesh>
  );
}

function App() {
  const texture = new TextureLoader().load("./icon.png");
  return (
    <div className="App">
      <Canvas>
        <IconBoard map={texture} position={[0,0,0]}/>
      </Canvas>
    </div>
  );
}

export default App;

```
### 実行画面

神々しいですね
![スクリーンショット 0004-05-06 21.11.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/481708/4780a34e-976e-ba26-4584-3e474e8cd1fb.png)

クリックすると回ります
![スクリーンショット 0004-05-06 21.12.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/481708/361a50af-1502-0c4c-d61b-4d1fb63922ce.png)

### 複数表示

`function App()` 内で '&lt; IconBoard />' を並べると複数のアイコンボードを表示できます。

``` React:App
function App() {
  const texture = new TextureLoader().load("./icon.png");
  return (
    <div className="App">
      <Canvas>
        {[...Array(3)].map((_,x)=><IconBoard map={texture} position={[(x-1)*1.5,1.5,0]}/>)}
        <IconBoard map={texture} position={[0,0,0]}/>
        {[...Array(3)].map((_,x)=><IconBoard map={texture} position={[(x-1)*1.5,-1.5,0]}/>)}
      </Canvas>
    </div>
  );
}
```

こんな感じで、さらに神々しさが増しました。

![スクリーンショット 0004-05-06 21.20.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/481708/2ed25bc0-43f3-2843-70b3-2119b644aa1f.png)

### 最後に
今年のGWは、Three.js と React-Three で苦手な3Dの勉強を頑張りましたよ。
まだまだ最低限の機能しか使えないけど、なかなか楽しく学べたのでヨシ！