Open10

Next.jsで3D

kodukakoduka

https://r3f.docs.pmnd.rs/getting-started/introduction#eco-system

3 ファイバーの周りには、ライブラリ、ヘルパー、抽象化が満載の活気に満ちた広範なエコ システムがあります。

@react-three/drei– 役に立つヘルパー、それ自体がエコシステムです
@react-three/gltfjsx– GLTFをJSXコンポーネントに変換します
@react-three/postprocessing– 後処理効果
@react-three/test-renderer– ノードのユニットテスト用
@react-three/flex– react-three-fiber 用の flexbox
@react-three/xr– VR/ARコントローラーとイベント
@react-three/csg– 構成的立体幾何学
@react-three/rapier– Rapierを使用した3D物理
@react-three/cannon– キャノンを使った3D物理
@react-three/p2– P2を使用した2D物理学
@react-three/a11y– あなたのシーンのための本当のa11y
@react-three/gpu-pathtracer– リアルなパストレース
create-r3f-app next– nextjs スターター
lamina– レイヤーベースのシェーダーマテリアル
zustand– フラックスベースの状態管理
jotai– アトムベースの状態管理
valtio– プロキシベースの状態管理
react-spring– スプリング物理ベースのアニメーションライブラリ
framer-motion-3d– 人気のアニメーションライブラリ、フレーマーモーション
use-gesture– マウス/タッチジェスチャー
leva– 数秒でGUIコントロールを作成
maath– 数学の助けとなるキッチンシンク
miniplex– ECS(エンティティ管理システム)
composer-suite– シェーダー、パーティクル、エフェクト、ゲームメカニクスの作成

kodukakoduka

Canvasのcameraプロパティの意味は以下の通りらしい。

  • fov:カメラの視野の垂直方向、下から上への度数。
  • near:平面付近のカメラのフラストレーション。
  • far:カメラの距方平面。(nearより大きい値じゃないとダメらしい)
  • position:オブジェクトのローカル位置。

nearfarがイマイチわからんな。

Canvas3D.tsx
"use client";

import type { CanvasProps } from "@react-three/fiber";

import { Canvas } from "@react-three/fiber";

export function Canvas3D(props: CanvasProps) {
  return <Canvas {...props} />;
}
page.tsx
<Canvas3D camera={{ fov: 75, near: 0.1, far: 1000, position: [0, 0, 1] }}>
// ...
</Canvas3D>
kodukakoduka

ユーザーからの操作イベント

https://r3f.docs.pmnd.rs/tutorials/events-and-interaction#user-interaction

<mesh
  onClick={(e) => console.log('click')}
  onContextMenu={(e) => console.log('context menu')}
  onDoubleClick={(e) => console.log('double click')}
  onWheel={(e) => console.log('wheel spins')}
  onPointerUp={(e) => console.log('up')}
  onPointerDown={(e) => console.log('down')}
  onPointerOver={(e) => console.log('over')}
  onPointerOut={(e) => console.log('out')}
  onPointerEnter={(e) => console.log('enter')}
  onPointerLeave={(e) => console.log('leave')}
  onPointerMove={(e) => console.log('move')}
  onPointerMissed={() => console.log('missed')}
  onUpdate={(self) => console.log('props have been updated')}
/>
kodukakoduka
  const { actions, mixer } = useAnimations(animations, group)

  useEffect(() => {
    actions.spin?.setLoop(THREE.LoopRepeat, 2).play()
    mixer.addEventListener('loop', (e) => {
      // アニメーションがループされた時に発火
      console.log('loop', e)
    })
    mixer.addEventListener('finished', (e) => {
      // アニメーションが終了した時に発火
      console.log('finished', e)
    })
  }, [actions, mixer])
kodukakoduka

framer-motionを利用して、コンポーネントのアンマウント時にアニメーションを再生するようにしようと考えた時に、Suspenseとの兼ね合いがどうなるのかが気になる。
試した感じ、うまくいかなかった。
そのため、ここにあるようにContext使った実装にしても良いかもしれない。

https://github.com/framer/motion/issues/1193#issuecomment-1816150859

kodukakoduka

3Dモデルにframer-motion-3dで透過アニメーションを適用するには、<mesh>タグにアニメーションを適応するのではなく、<mesh>タグの子に<material>タグを作成して、そこに透過アニメーションを適応する必要がある。なぜなら、<mesh>タグにはopacityというプロパティがないからである。
また、transparentをtrueにしないと<material>opacityが反映されないので、これは必須である。

Earch.tsx(修正前)
    <group ref={group} {...props}>
      <group name="Scene" dispose={null}>
        <motion.mesh
          name="earth"
          geometry={nodes.earth.geometry}
          material={materials.material}
          material-transparent={true}
          animate={{
            opacity: [0, 1, 0], // 点滅のために透明度を1から0に変化させる
          }}
          transition={{
            duration: 1, // 1秒かけて点滅
            repeat: Infinity, // 永遠に繰り返す
            repeatType: 'reverse', // 透明度を1から0、そして0から1に戻す
          }}
        />
      </group>
    </group>
Earth.tsx(修正後)
    <group ref={group} {...props}>
      <group name="Scene" dispose={null}>
        <motion.mesh
          name="earth"
          geometry={nodes.earth.geometry}
        >
          <motion.material
            {...materials.material}
            transparent
            animate={{
              opacity: [0, 1, 0], // 点滅のために透明度を1から0に変化させる
            }}
            transition={{
              duration: 1, // 1秒かけて点滅
              repeat: Infinity, // 永遠に繰り返す
              repeatType: 'reverse', // 透明度を1から0、そして0から1に戻す
            }}
          />
        </motion.mesh>
      </group>
    </group>
kodukakoduka

Three.jsで3Dモデルをキャッシュするには、サービスワーカーを使ってfetchイベントを監視する必要があるのか。

https://discourse.threejs.org/t/is-there-anyway-i-can-cache-models-in-the-users-browser-cache/9132/15

self.addEventListener("fetch", (event) => {
  if (event.request.method !== "GET") return;
  event.respondWith(
    (async () => {
      const url = event.request.url;
      const isGlb = url.endsWith(".glb");
      if (isGlb) {
        // Try to get the response from a cache.
        const cache = await caches.open(CACHE_NAME);
        const cachedResponse = await cache.match(event.request);

        if (cachedResponse) {
          // If we found a match in the cache, return it, but also
          // update the entry in the cache in the background.
          event.waitUntil(cache.add(event.request));
          return cachedResponse;
        }
      }

      // If we didn't find a match in the cache, use the network.
      const res = await fetch(event.request).catch(() =>
        caches.match(event.request).then((res) => res)
      );
      if (isGlb) {
        const copyCache = res.clone();
        caches.open(CACHE_NAME).then((cache) => {
          cache.put(event.request, copyCache);
        });
      }
      return res;
    })()
  );
});

でも、Three.jsのChacheを有効にすれば、サイトを開いている間はキャッシュ化できるから、いいか?

import type * as THREE from 'three'
THREE.Cache.enabled = true