📝

React+TypeScriptなWebアプリで、R3Fのtutorial20。(Lerpで簡単移動処理)

2024/01/03に公開

Abstract

今回の参考はここ(Lerp)
線形補完の意味なんだけど、目的地まで、カメラ位置とか、3Dモデル位置とかをだんだんゆっくりに移動させてくれるやつ。

結論

今回の成果物はココ↓
https://github.com/aaaa1597/react-r3f-tutorial020

Lerp使用箇所の要点 :

  1. 起動すぐのカメラ移動。(Rigタグ)
  2. 3Dオブジェクトタッチで、位置移動、色変更。

前提

手順

1.プロジェクト生成 -> VSCodeで開く

めんどいから、このスケルトンコードから始める。react-r3f-base-onebox
で、下記コマンドでフォルダ名とか整備する。

フォルダリネームとか
$ D:
$ cd .\Products\React.js\            # ご自身の適当なフォルダで。
$ rd /q /s react-r3f-base-onebox
$ git clone https://github.com/aaaa1597/react-r3f-base-onebox.git
$ rd /q /s react-r3f-base-onebox/.git
$ ren react-r3f-base-onebox react-r3f-tutorial020
$ cd react-r3f-tutorial020

2.必要ライブラリのインストール

$ npm install --save three
$ npm install --save @types/three
$ npm install --save @react-three/fiber
$ npm install --save @react-three/drei

.eslintrc.jsを修正

エラーになるので、ignoreに追加

.eslintrc.js
-        "react/no-unknown-property": ['error', { ignore: ['css', "args", 'wireframe', 'rotation-x', 'rotation'] }],
+        "react/no-unknown-property": ['error', { ignore: ['css', "args", 'wireframe', 'rotation-x', 'rotation', 'roughness', 'metalness', 'thickness', 'ior', 'transmission'] }],

tsconfig.jsonを修正

エラーになるので、downlevelIterationを追加

tsconfig.json
 {
   "compilerOptions": {
+    "downlevelIteration": true,
     "target": "es5",
     "lib": [
       "dom",

App.tsx

全体。

App.tsx
-import React, {useRef} from 'react';
+import React, {useMemo, useRef, useState} from 'react';
import './App.css';
-import { Canvas, useFrame, MeshProps  } from '@react-three/fiber'
+import { Canvas, useFrame, MeshProps } from '@react-three/fiber'
import * as THREE from 'three'
-import { OrbitControls, Environment } from '@react-three/drei'
+import { OrbitControls, Environment, Center, Stats } from '@react-three/drei'

-const Box = (props: MeshProps) => {
+const Button = (props: MeshProps) => {
  const ref = useRef<THREE.Mesh>(null!)
+  const refMat = useRef<THREE.MeshPhysicalMaterial>(null!)
+  const [hovered, setHovered] = useState(false)
+  const [selected, setSelected] = useState(false)
+  const colorTo = useMemo(() => new THREE.Color(Math.floor(Math.random() * 16777216)),[])

  useFrame((_, delta) => {
-    ref.current.rotation!.x += 1 * delta
-    ref.current.rotation!.y += 0.5 * delta
+    ref.current.rotation.x = hovered
+      ? THREE.MathUtils.lerp(ref.current.rotation.x, -Math.PI * 2, 0.025)
+      : THREE.MathUtils.lerp(ref.current.rotation.x, 0, 0.025)
+
+    ref.current.position.z = selected
+      ? THREE.MathUtils.lerp(ref.current.position.z, 0, 0.025)
+      : THREE.MathUtils.lerp(ref.current.position.z, -3, 0.025)
+
+      refMat.current.color.lerp(selected ? colorTo : new THREE.Color(0x000000), 0.025)
})

  return (
-    <mesh {...props} ref={ref}>
-      <boxGeometry />
-      <meshNormalMaterial />
+    <mesh {...props} ref={ref}
+      onPointerDown={() => {setSelected(!selected)}}
+      onPointerOver={() => {setHovered(true)}}
+      onPointerOut={() => {setHovered(false)}} >
+      <icosahedronGeometry />
+      <meshPhysicalMaterial ref={refMat}
+        roughness={0} metalness={0} thickness={3.12} ior={1.74} transmission={1.0}/>
     </mesh>
  )
}

+const vec = new THREE.Vector3()
+const Rig = () => {
+  return useFrame(({camera, mouse}) => {
+    vec.set(mouse.x * 2, mouse.y * 2, camera.position.z)
+    camera.position.lerp(vec, 0.025)
+    camera.lookAt(0, 0, 0)
+  })
+}
+

const App = () => {
  return (
-   <div style={{ width: "99vw", height: "75vh" }}>
+   <div style={{ width: "100vw", height: "75vh" }}>
-     <Canvas camera={{ position: [3, 1, 2] }}>
+     <Canvas camera={{ position: [0, 0, 8] }}>
        <Environment preset="forest" background />
+       <Center>
+         {[...Array(5).keys()].map((x) =>
+           [...Array(5).keys()].map((y) => {
+             return <Button key={y*5 + x} position={[x*2.5, y*2.5, 0]}/>
+           })
+         )}
+       </Center>
        <OrbitControls />
        <axesHelper args={[5]} />
        <gridHelper />
+       <Rig />
        <Stats/>
      </Canvas>
    </div>
  );
}

export default App;

で、実行。


出来た!!

ポイント

1. <Rig />関数コンポーネント

この関数で空間全体の姿勢を変更している。
実際は、全体Cameraの位置を変更-> (0,0,0)位置を向くように再計算している。

<Rig/>
camera.position.lerp(vec, 0.025)
camera.lookAt(0, 0, 0)

2. <Button />関数コンポーネント

meshタグにonPointerDown/onPointerOver/onPointerOutをくっつけて、イベント制御している。
各イベントで各変数を変更して、useFrame()の中で、各変数に合わせて自分の位置と色を変更している。
色を変更するためには、meshの参照でなく、meshPhysicalMaterialの参照を保持っとく必要があるので、refMatに設定している。

以上。


React+TypeScriptなWebアプリで、R3Fのtutorial19。(Annotationsで注釈表示)

Discussion