React-Three-Fiverを用いてReactで3D表現する

React-three-fiber(この記事では、r3fと呼称する。)とは、ReactでThree.jsを扱うためのラッパーライブラリで、Reactの宣言的な記述に合わせてWebGLシーンを構築することができます。

https://github.com/pmndrs/react-three-fiber
## 対象者
- r3fを使ったことがない方
- three.jsの知識をお持ちの方
- Reactで3Dのサイトを作りたい方
## インストール
```console
$ npm install three @react-three/fiber
```
また、`TypeScript`をお使いの場合は下記に記載しているモジュールもインストールします。
```console
$ npm install @types/three
```
## Canvas
r3fを使う時に**必ず**ラップする。
```tsx
import React from 'react'
import { Canvas } from '@react-three/fiber'

const App = () => (
  <Canvas>
    ...
  </Canvas>
)

export default App;
```
Canvasコンポーネントでは**camera**、**renderer**、**scene**を指定した事になります。

**camera**の位置はデフォルトでは`{ fov: 75, near: 0.1, far: 1000, position: [0, 0, 5] }`
もし、**camera**位置を変更したい時には下記のようにpropsで指定します。
```tsx
import React from 'react'
import { Canvas } from '@react-three/fiber'

const App = () => (
  <Canvas camera={ { fov: カメラの錐台の垂直視野, near: 平面近くのカメラ錐台, far: カメラの錐台の遠方平面, position: [x座標, y座標, z座標] } }>
    ...
  </Canvas>
)

export default App;
```
## Mesh
いよいよ物体を表示するのですが、物体を表示させる為には**メッシュ**と呼ばれるものが必要でした。
r3fでは、meshコンポーネントがあります。`<mesh />`

一方で、**メッシュ**を作成には**形状(ジオメトリ)** と **材質(マテリアル)** が必要でした。
なので、最終的には下記のようなコードになるはずです。
```tsx
import React from 'react'
import { Canvas } from '@react-three/fiber'

const App = () => (
  <Canvas>
    <mesh>
      <Geometry />
      <Material />
    </mesh>
  </Canvas>
)

export default App;
```
### 形状(ジオメトリ)
基本はthree.jsのインスタンスメソッドがコンポーネントになったと思ってください。
例えば、直方体を指定したい時には下記のインスタンスメソッドをメソッドを作成していました。
```ts
new THREE.BoxGeometry(1, 1, 1)
```
これが、r3fだと下記のようになります。
```tsx
<boxGeometry args={[1, 1, 1]} />
```
このように、r3fではインスタンスメソッドがそのままコンポーネントになっています。

### 材質(マテリアル)
**Material**の場合も同様に、three.jsのインスタンスメソッドがコンポーネントになっています。
例えば、`MeshBasicMaterial`を使いたい場合、three.jsでは下記のように指定していました。
```ts
new THREE.MeshBasicMaterial({ color: "hotpink" })
```
これが、r3fだと下記のようになります。
```tsx
<meshBasicMaterial color={"hotpink"} />
```
### 光源(ライト)
使用する**Material**によっては光源(ライト)を照らしてあげないといけない場合があります。　ここでは、光源(ライト)の照らし方を紹介します。
因みに余談なのですが、光源(ライト)を照らさなくても大丈夫な**Material**を紹介します。
- `MeshBasicMaterial`
- `MeshNormalMaterial`

ここでは、平行光源(DirectionalLight)の当て方を紹介します。
```tsx
<directionalLight color={"white"} intensity={0.5} position={[0, 0, 0]} />
// color : 光の色
// intensity : 光の強さ
// position : 平行光源のポジショニング
```
## アニメーション
JavaScriptでアニメーションをさせるには、時間経過で関数を呼び続ける必要があります。そのためには、`requestAnimationFrame()`というグローバルメソッドを使用します。

r3fでは`useFrame()`というメソッドが用意されています。
では、サンプルコードを交えて`useFrame()`を使っていきます。
```tsx:App.tsx
import { Canvas, useFrame } from "@react-three/fiber";
import React, { useRef } from "react";
import { Mesh } from "three";

function App() {
  const ref = useRef({} as Mesh);
  useFrame(() => (ref.current.rotation.x += 0.01));
  return (
    <>
      <Canvas
        camera={{ fov: 70, near: 0.1, far: 2000 }}
        style={{ width: "100vw", height: "100vh" }}
      >
        <color args={["#5bbee5"]} attach={"background"} />
        <ambientLight intensity={0.5} />
        <directionalLight position={[10, 10, 20]} />
        <mesh ref={ref} position={[-2, 0, 0]}>
          <boxGeometry args={[1, 1, 1]} />
          <meshPhongMaterial color={"hotpink"} />
        </mesh>
        <mesh ref={ref} position={[0, 0, 0]}>
          <boxGeometry args={[1, 1, 1]} />
          <meshPhongMaterial color={"hotpink"} />
        </mesh>
        <mesh ref={ref} position={[2, 0, 0]}>
          <boxGeometry args={[1, 1, 1]} />
          <meshPhongMaterial color={"hotpink"} />
        </mesh>
      </Canvas>
    </>
  );
}

export default App;
```
3つのBoxGeometryが縦方向に少しずつ回転していくアニメーションを行なっていく処理を記述していきましたが、下記のようなエラーが出ました。

```
R3F: hooks can only be used within the Canvas component
```
`useFrame()`は`<Canvas />`の中でしか使えないよ〜と言ってきます。
なので、以下のように**メッシュを子コンポーネントで管理**します。
```tsx:App.tsx
import { Canvas } from "@react-three/fiber";
import Box from "./compornents/Box";

function App() {
  return (
    <>
      <Canvas
        camera={{ fov: 70, near: 0.1, far: 2000 }}
        style={{ width: "100vw", height: "100vh" }}
      >
        <color args={["#5bbee5"]} attach={"background"} />
        <ambientLight intensity={0.5} />
        <directionalLight position={[10, 10, 20]} />
        <Box position={[-2.4, 0, 0]} />
        <Box position={[0, 0, 0]} />
        <Box position={[2.4, 0, 0]} />
      </Canvas>
    </>
  );
}

export default App;
```
```tsx:Box.tsx
import { useFrame } from "@react-three/fiber";
import React, { useRef } from "react";
import { Mesh } from "three";

const Box = (props: JSX.IntrinsicElements["mesh"]) => {
  const ref = useRef({} as Mesh);
  useFrame(() => (ref.current.rotation.x += 0.01));
  return (
    <mesh ref={ref} {...props}>
      <boxGeometry args={[1, 1, 1]} />
      <meshPhongMaterial color={"hotpink"} />
    </mesh>
  );
};

export default Box;
```
## チュートリアルっぽく仕上げる
最後に公式にあるチュートリアルっぽく、BoxGeometryをクリックすると拡大・縮小したり、hoverしたら色が変わる処理を追加し今回のブログを締めようと思います。
### 拡大・縮小
まず最初にクリックしたら拡大・縮小する処理を実装します。実装するには拡大・縮小を管理するstateが必要になるので用意します。
```tsx
 const [clicked, setClicked] = useState<boolean>(false);
```
useStateの値が`false`なら縮小・`true`なら拡大してあげます。
また、大きさを変化させる時に用いられるプロパティは`scale`です。
```tsx
<mesh
  onClick={() => setClicked(!clicked)}
  scale={clicked ? 2 : 1}
>
```
### hoverしたら色が変わる
クリックしたら拡大・縮小する処理と同様に色を管理するstateを用意します。
```tsx
const [hovered, setHovered] = useState<boolean>(false);
```
useStateの値が`false`ならhotpink・`true`ならorangeに変わるという処理を記述していきます。

以下のプロパティを付けることでマウスがhoverした時の処理、マウスが離れた時の処理を実装することができます。
```tsx
<mesh
  onPointerOver={() => setHovered(true)} // マウスがhoverした時の処理
  onPointerOut={() => setHovered(false)} // マウスが離れた時の処理
>
  ...
  <meshPhongMaterial color={hovered ? "orange" : "hotpink"} />
</mesh>
```

https://codesandbox.io/embed/r3f-test-pzt759?fontsize=14&hidenavigation=1&theme=dark

## おわりに
いかがだったでしょうか？僕自身も今r3fにハマっていてこれからどんどんキャッチアップしていきたいなと思っていて、次回まだ未定なのですが`@react-three/drei`を用いて色々やる記事を書こうかな？と思っています。

今回使用した、`@react-three/fiber`と`@react-three/drei`を組み合わせる事により、`@react-three/fiber`だけではできなかった3D表現ができるようになるのでお楽しみに。ということで最後まで目を通していただきありがとうございました。

https://docs.pmnd.rs/react-three-fiber/getting-started/introduction

https://qiita.com/watataku8911/items/8cdb5ab91dc2f75b7b73