📖
React+TypeScript+R3Fのtutorial応用編1(annotations, GLTFSX, SVG)
Abstract
今回の参考はここ(House)。このソースをTypeScriptで実装しなおす
ポイント
- Drei annotations
- GLTFSXをつかう。
- SVG描画
- Tween.jsを使ってアニメーション
結論
今回の成果物はココ↓
前提
- React+Typescriptの開発環境は構築済 [環境構築]WindowsにVSCode+React+TypeScriptの開発環境を構築してみた。
- このスケルトンコードから始める。React-Ts-Template
手順
1.プロジェクト生成 -> VSCodeで開く
めんどいから、このスケルトンコードから始める。React-Ts-Template
で、下記コマンドでフォルダ名とか整備する。
フォルダリネームとか
$ D:
$ cd .\Products\React.js\ # ご自身の適当なフォルダで。
$ rd /q /s D:\Products\React.js\react-r3f-advanced001
$ git clone https://github.com/aaaa1597/React-Ts-Template.git
$ rd /q /s React-Ts-Template/.git
$ ren React-Ts-Template react-r3f-advanced001
$ cd react-r3f-advanced001
準備
コマンドプロンプト
$ npm install --save three
$ npm install --save @types/three
$ npm install --save @react-three/fiber
$ npm install --save @react-three/drei
$ npm install --save @tweenjs/tween.js
準備2
参考はここ(House)のgithubからDLしたhouse-water-transformed.glb をプロジェクトの"react-r3f-tutorial018/public/assets/"配下にコピー。
D:\Products\React.js\react-r3f-advanced001>dir public\assets
ドライブ D のボリューム ラベルは data です
ボリューム シリアル番号は 277E-64C0 です
D:\Products\React.js\react-r3f-advanced001\public\assets のディレクトリ
2023/12/30 22:02 <DIR> .
2023/12/30 21:32 <DIR> ..
2023/12/30 15:15 1,494,220 house-water-transformed.glb
1 個のファイル 1,494,220 バイト
2 個のディレクトリ 1,080,946,184,192 バイトの空き領域
npx gltfjsxコマンドを実行 -> TSXファイル生成
ここでハマった。windowsだとエラー吐いて実行できない。解決はここ
仕方ないので、ubuntuで実行した。
ubuntuで実行するときも、プロジェクト一式のpublic/assets配下に、house-water-transformed.glbをコピらないと失敗した。
原因不明。
コマンドプロンプト
$ cd ~/react-r3f-tutorial018
$ npx gltfjsx public/assets/house-water-transformed.glb --types --shadows
で、~/配下に出力されたHouse-water-transformed.tsxを自分のプロジェクトに持ってくることで解決した。
.eslintrc.jsを修正
エラーになるので、ignoreに追加
.eslintrc.js
+ "react/no-unknown-property": ['error', { ignore: ['dispose', "rotation", "castShadow", 'receiveShadow', 'geometry', 'material'] }],
App.tsx
まず全体。
App.tsx
-import React from 'react';
+import React, { Suspense, useState, useRef } from 'react';
+import { Canvas, useFrame, useThree } from '@react-three/fiber'
+import { OrbitControls, OrbitControlsProps, Environment, useProgress, Html, Stats } from '@react-three/drei'
+import { OrbitControls as OrbitControlsImpl } from "three-stdlib"
import './App.css';
+import { Model } from './House'
+import TWEEN from '@tweenjs/tween.js'
+import annotations from './annotations.json'
+const Annotations = (props: {controls: React.MutableRefObject<OrbitControlsImpl>}) => {
+ const { camera } = useThree()
+ const [selected, setSelected] = useState(-1)
+
+ return (
+ <>{annotations.map((value, idx) => {
+ return(
+ <Html key={idx} position={[value.lookAt.x, value.lookAt.y, value.lookAt.z]}>
+ <svg height="34" width="34" transform="translate(-16 -16)" style={{ cursor: 'pointer' }}>
+ <circle cx="17" cy="17" r="16" stroke="white" strokeWidth="2" fill="rgba(0,0,0,.66)"
+ onPointerDown={() => {
+ setSelected(idx)
+ // change target
+ new TWEEN.Tween(props.controls.current.target)
+ .to({x: value.lookAt.x, y: value.lookAt.y,z: value.lookAt.z }, 1000 )
+ .easing(TWEEN.Easing.Cubic.Out)
+ .start()
+ // change camera position
+ new TWEEN.Tween(camera.position)
+ .to({x: value.camPos.x, y: value.camPos.y, z: value.camPos.z }, 1000)
+ .easing(TWEEN.Easing.Cubic.Out)
+ .start()
+ }} />
+ <text x="12" y="22" fill="white" fontSize={17} fontFamily="monospace" style={{ pointerEvents: 'none' }}>
+ {idx + 1}
+ </text>
+ </svg>
+ {value.description && idx === selected &&
+ (<div id={'desc_' + idx} className="annotationDescription"
+ dangerouslySetInnerHTML={{ __html: value.description }} />
+ )
+ }
+ </Html>
+ )
+ })
+ }</>
+ )
+}
+
+const Tween = () => {
+ useFrame(() => {
+ TWEEN.update()
+ })
+ return(<></>)
+ }
+const Loader = () => {
+ const { progress } = useProgress()
+ return <Html center>{progress} % loaded</Html>
+}
-function App() {
+const App = () => {
+ const ref = useRef<OrbitControlsImpl>(null!)
+
return (
- <div className="App">
- hello world!!
+ <div style={{ width: "75vw", height: "75vh" }}>
+ <Canvas camera={{ position: [8, 2, 12] }}>
+ <OrbitControls ref={ref} target={[8, 2, 3]} />
+ <Suspense fallback={<Loader />}>
+ <Environment preset="forest" background blur={0.75} />
+ <Model />
+ <Annotations controls={ref} />
+ <Tween />
+ </Suspense>
+ <Stats />
+ </Canvas>
</div>
);
}
export default App;
npx gltfjsxで自動生成した分に、透過設定追加
House.tsx
export function Model(props: JSX.IntrinsicElements['group']) {
const { nodes, materials } = useGLTF('assets/house-water-transformed.glb') as GLTFResult
+ materials.ground_1.transparent = true
+ materials.ground_1.opacity = 0.2
+ materials.ground_1.depthWrite = false
+ materials.wall_1_2.transparent = true
+ materials.wall_1_2.opacity = 0.2
+ materials.wall_1_2.depthWrite = false
+ materials.room_58_344.transparent = true
+ materials.room_58_344.opacity = 0.2
+ materials.room_58_344.depthWrite = false
+ materials.grey.transparent = true
+ materials.grey.opacity = 0.2
+ materials.grey.depthWrite = false
+ materials.flltgrey.transparent = true
+ materials.flltgrey.opacity = 0.2
+ materials.flltgrey.depthWrite = false
+ materials.flltgrey_sweethome3d_window_pane_420.transparent = true
+ materials.flltgrey_sweethome3d_window_pane_420.opacity = 0.2
+ materials.flltgrey_sweethome3d_window_pane_420.depthWrite = false
+ materials['default'].depthWrite = true
+ materials['default'].opacity = 0.2
+ materials['default'].depthWrite = false
+ materials.Glass.transparent = true
+ materials.Glass.opacity = 0.2
+ materials.Glass.depthWrite = false
+ materials.flltgrey_sweethome3d_window_pane_420.transparent = true
+ materials.flltgrey_sweethome3d_window_pane_420.opacity = 0.2
+ materials.flltgrey_sweethome3d_window_pane_420.depthWrite = false
+ materials.white_Fenetre_480.transparent = true
+ materials.white_Fenetre_480.opacity = 0.2
+ materials.white_Fenetre_480.depthWrite = false
+ materials.white_13_526.transparent = true
+ materials.white_13_526.opacity = 0.2
+ materials.white_13_526.depthWrite = false
+ materials.wall_1_2.transparent = true
+ materials.wall_1_2.opacity = 0.2
+ materials.wall_1_2.depthWrite = false
+ materials.glassblutint.transparent = true
+ materials.glassblutint.opacity = 0.2
+ materials.glassblutint.depthWrite = false
+ materials.Aluminium_652.transparent = true
+ materials.Aluminium_652.opacity = 0.2
+ materials.Aluminium_652.depthWrite = false
+ materials.GLASS.transparent = true
+ materials.GLASS.opacity = 0.2
+ materials.GLASS.depthWrite = false
+ materials.Glass_sweethome3d_window_mirror_985.transparent = true
+ materials.Glass_sweethome3d_window_mirror_985.opacity = 0.2
+ materials.Glass_sweethome3d_window_mirror_985.depthWrite = false
で、実行。
出来た!!
ハマりポイント
javascript → typescriptに変更するのに、結構ハマったので備忘録かねて。
-
子要素つけろって怒られた!! 解決方法はココ。
-
型違いで、怒られた!! 解決方法はココ
-
propsを受け取る関数コンポーネントが分からんかった。
ポイント1. 関数コンポーネントに引数を追加するときは、Propsの型定義をする。
ポイント2. 関数コンポーネントを渡すときは、バラした状態になる。
ポイント3. useRefの型は、React.MutableRefObject<なんちゃら>。
App.tsx
import { OrbitControls as OrbitControlsImpl } from "three-stdlib"
// ↓ポイント1 ↓ポイント3
const Annotations = (props: {controls: React.MutableRefObject<OrbitControlsImpl>}) => {
return (
<>
</>
)
}
const ref = useRef<OrbitControlsImpl>(null!)
<Annotations controls={ref} />
// ↑ポイント2
- windows11だと、npx gltfjsxコマンドが失敗する。
windowsだとエラー吐いて実行できない。解決はここ
Discussion