見出し画像

CodePenでesm.shでお手軽にReact Three Fiberを使用してみるテスト4 3Dモデルアニメーション切り替え表示

 前回からのつづきです。 esm.shを使用したCodePenのコードで、React Three Fiberによる3Dモデルアニメーションの切り替え表示を行ってみました。 切り替え用3DモデルアニメーションはglTFファイル形式のものを使用しています。


概要

 前回の、3Dモデルアニメーションを1つだけ再生するコードから、3Dモデルによる複数のアニメーションを切り替えるコードに進化?させてみました。 

 作成したコードのうち、Test01Test04Test05、といったあたりが自然に本来のアニメーションの再生・切替ができているように自分には見受けられました。 
 また、Test02Test03のようにテクスチャがうまく貼れずに表示に失敗してしまっているコードもあります。 
 Test06はアニメーションは動いているようですが、本来は効果音も含むファイルを読み込んでいるのでその分の読込データが大きくなってしまっているかもです(元々Test03含めBabylon.js用のファイルをgithack.com経由で使わせてもらっていたものだったかもしれません  Babylon.jsでは効果音も再生できていました  参考noteです)。

自分のコードを含む、以下のサイトを参考にさせていただきました(アニメーション切替コードとして一番うまくいったと思うTest05のCodeSandboxコードに残っていたコメントより作成しています ←作成したのが1年以上前なので自分でも記憶があいまいですw)。

How to extract and play animation in react-three-fiber
reactjs - react-three-fiberでアニメーションを抽出して再生する方法((現在サイトはなくなってしまっているようです))
Three.js r110でglTF 3Dモデルのアニメーション切り替え その03
How to Perform Inplace Animations in React Three Fiber
【React】react-three-fibarで3D表現をする(Mixamoを使ったアニメーションモデル)
Controlling Multiple Character Animations In React Three Fiber 


参考:import文そのままになりますが、使用したライブラリ等のバージョンは以下になります

import ReactDOM from 'https://esm.sh/react-dom@18.2.0'
import React, { useState, useEffect, useRef, Suspense } from 'https://esm.sh/react@18.2.0'
import { Canvas, useFrame } from 'https://esm.sh/@react-three/fiber@8.16.1'
import { Html, useProgress, useGLTF, useAnimations } from 'https://esm.sh/@react-three/drei@9.105.4'

このnoteのそれぞれのコードの下にある参考サイトとして引用している自分noteは、参考にした自作CodeSandboxコードへのリンクをまとめただけのものです。 それらCodeSandboxコードはこのnoteのコード作成時に参考にしましたが、重いのでコードへの直接のリンクはやめておきました。

以下作成したコードのリストとなります。 各コードにて、タイトル文字によるリンクはコード付きCodePenへのリンク、サムネイル画像によるリンクはコードなしのコード実行結果の全画面表示へのリンク、となります。 

あと、JavaScriptのコードをそのまま載せているので、冗長な感じでこのnoteエントリけっこう長いです、ご注意ください。


React Three Fiber 8.16.1 glTF Animation Change Test01

React Three Fiber 8.16.1 glTF Animation Change Test01

HTML

<div id="root"></div>

CSS

* {
  box-sizing: border-box;
}

html,
body,
#root {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
}

body {
  background: #30ffff;
}

JS(JavaScript)

import ReactDOM from 'https://esm.sh/react-dom@18.2.0'
import React, { useState, useEffect, useRef, Suspense } from 'https://esm.sh/react@18.2.0'
import { Canvas, useFrame } from 'https://esm.sh/@react-three/fiber@8.16.1'
import { Html, useProgress, useGLTF, useAnimations } from 'https://esm.sh/@react-three/drei@9.105.4'

const ModelPath = 'https://rawcdn.githack.com/KhronosGroup/glTF-Sample-Models/8e9a5a6ad1a2790e2333e3eb48a1ee39f9e0e31b/2.0/Fox/glTF-Binary/Fox.glb'

const Loader = () => {
  const { progress } = useProgress()
  return (
    <Html center>
      {progress} % loaded
    </Html>
  )
}

const Model = (props) => {
  const group = useRef()

  const gltf = useGLTF(ModelPath)

  const { actions } = useAnimations(gltf.animations, group)

  useEffect(() => {
    //console.log(actions) // find out the name of your action
    if (props.previousAction) {
      actions[props.previousAction].fadeOut(0.2)
      actions[props.previousAction].stop()
    }

    actions[props.action].play()
    actions[props.action].fadeIn(0.2)
  }, [props.action, actions])

  useFrame((state) => {
    gltf.scene.rotation.x += 0.02
    gltf.scene.rotation.y += 0.02
    //gltf.scene.rotation.x = state.mouse.y * -2.5
    //gltf.scene.rotation.y = state.mouse.x * 7.5
    //gltf.scene.rotation.z = (state.mouse.x + state.mouse.y) / 2  * 5
  }, [])

  return (
    <>
      <group ref={group} dispose={null}>
        <group scale={props.scale}>
          <primitive object={gltf.scene} position={props.position} rotation={props.rotation} scale={props.scale} />
        </group>
      </group>
    </>
  )

}

const App = () => {

  const [action, setAction] = useState('Walk')
  const [previousAction, setPreviousAction] = useState('')

  return (

    <Canvas>

      <Html fullscreen>
        <button
          onClick={() => {
            setPreviousAction(action)
            setAction('Survey')
          }}>
          Survey
        </button>
        <button
          onClick={() => {
            setPreviousAction(action)
            setAction('Walk')
          }}>
          Walk
        </button>
        <button
          onClick={() => {
            setPreviousAction(action)
            setAction('Run')
          }}>
          Run
        </button>
      </Html>

      <ambientLight intensity={Math.PI / 2} />
      <spotLight position={[10, 10, 10]} angle={0.15} penumbra={1} decay={0} intensity={Math.PI} />
      <pointLight position={[-10, -10, -10]} decay={0} intensity={Math.PI} />

      <Suspense fallback={<Loader />}>
        <Model
          position={[0, -2, 0]}
          rotation={[0, 0, 0]}
          scale={0.18}
          action={action}
          previousAction={previousAction}
        />
      </Suspense>

    </Canvas>

  )

}

ReactDOM.render(
  <App/>,
  document.getElementById("root")
);

前に自分で作成した以下のサイトの記録を参考にして作成してみました
CodeSandboxでReact Three Fiber実験その5 glTF 3Dモデルアニメーション切り替え表示 > React Three Fiber Practice31 glTF Model Multiple Animations Change04


React Three Fiber 8.16.1 glTF Animation Change Test02

React Three Fiber 8.16.1 glTF Animation Change Test02

HTML Test01と同じです

CSS Test01と同じです

JS(JavaScript)

import ReactDOM from 'https://esm.sh/react-dom@18.2.0'
import React, { useState, useEffect, useRef, Suspense } from 'https://esm.sh/react@18.2.0'
import { Canvas, useFrame } from 'https://esm.sh/@react-three/fiber@8.16.1'
import { Html, useProgress, useGLTF, useAnimations } from 'https://esm.sh/@react-three/drei@9.105.4'

const ModelPath = 'https://rawcdn.githack.com/BabylonJS/MeshesLibrary/55f475726670be2e7e4017b5f88c5762a90508c2/shark.glb'

const Loader = () => {
  const { progress } = useProgress()
  return (
    <Html center>
      {progress} % loaded
    </Html>
  )
}

const Model = (props) => {
  const group = useRef()

  const gltf = useGLTF(ModelPath)

  const { actions } = useAnimations(gltf.animations, group)

  useEffect(() => {
    //console.log(actions) // find out the name of your action
    if (props.previousAction) {
      actions[props.previousAction].fadeOut(0.2)
      actions[props.previousAction].stop()
    }

    actions[props.action].play()
    actions[props.action].fadeIn(0.2)
  }, [props.action, actions])

  useFrame((state) => {
    //gltf.scene.rotation.x += 0.02
    //gltf.scene.rotation.x = state.mouse.y * -2.5
    gltf.scene.rotation.y = state.mouse.x * 7.5
    //gltf.scene.rotation.z = (state.mouse.x + state.mouse.y) / 2  * 5
  }, [])

  return (
    <>
      <group ref={group} dispose={null}>
        <group scale={props.scale}>
          <primitive object={gltf.scene} position={props.position} rotation={props.rotation} scale={props.scale} />
        </group>
      </group>
    </>
  )

}

const App = () => {

  const [action, setAction] = useState('swimming')
  const [previousAction, setPreviousAction] = useState('')

  return (

    <Canvas>

      <Html fullscreen>
        <button
          onClick={() => {
            setPreviousAction(action)
            setAction('swimming')
          }}>
          swimming
        </button>
        <button
          onClick={() => {
            setPreviousAction(action)
            setAction('circling')
          }}>
          circling
        </button>
        <button
          onClick={() => {
            setPreviousAction(action)
            setAction('bite')
          }}>
          bite
        </button>
      </Html>

      <ambientLight intensity={Math.PI / 2} />
      <spotLight position={[10, 10, 10]} angle={0.15} penumbra={1} decay={0} intensity={Math.PI} />
      <pointLight position={[-10, -10, -10]} decay={0} intensity={Math.PI} />

      <Suspense fallback={<Loader />}>
        <Model
          position={[0, -2, 0]}
          rotation={[0, 0, 0]}
          scale={0.65}
          action={action}
          previousAction={previousAction}
        />
      </Suspense>

    </Canvas>

  )

}

ReactDOM.render(
  <App/>,
  document.getElementById("root")
);

前に自分で作成した以下のサイトの記録を参考にして作成してみました
CodeSandboxでReact Three Fiber実験その5 glTF 3Dモデルアニメーション切り替え表示 > React Three Fiber Practice37 glTF Model Multiple Animations Change05


React Three Fiber 8.16.1 glTF Animation Change Test03

React Three Fiber 8.16.1 glTF Animation Change Test03

HTML Test01と同じです

CSS Test01と同じです

JS(JavaScript)

import ReactDOM from 'https://esm.sh/react-dom@18.2.0'
import React, { useState, useEffect, useRef, Suspense } from 'https://esm.sh/react@18.2.0'
import { Canvas, useFrame } from 'https://esm.sh/@react-three/fiber@8.16.1'
import { Html, useProgress, useGLTF, useAnimations } from 'https://esm.sh/@react-three/drei@9.105.4'

const ModelPath = 'https://rawcdn.githack.com/BabylonJS/Babylon.js/e38e3cc4f2f91c7da05b45aa08103eaaf201cb58/Playground/scenes/ufo.glb'

const Loader = () => {
  const { progress } = useProgress()
  return (
    <Html center>
      {progress} % loaded
    </Html>
  )
}

const Model = (props) => {
  const group = useRef()

  const gltf = useGLTF(ModelPath)

  const { actions } = useAnimations(gltf.animations, group)

  useEffect(() => {
    //console.log(actions) // find out the name of your action
    if (props.previousAction) {
      actions[props.previousAction].fadeOut(0.2)
      actions[props.previousAction].stop()
    }

    actions[props.action].play()
    actions[props.action].fadeIn(0.2)
  }, [props.action, actions])

  useFrame((state) => {
    //gltf.scene.rotation.x += 0.02
    //gltf.scene.rotation.x = state.mouse.y * -2.5
    //gltf.scene.rotation.y = state.mouse.x * 7.5
    //gltf.scene.rotation.z = (state.mouse.x + state.mouse.y) / 2  * 5
  }, [])

  return (
    <>
      <group ref={group} dispose={null}>
        <group scale={props.scale}>
          <primitive object={gltf.scene} position={props.position} rotation={props.rotation} scale={props.scale} />
        </group>
      </group>
    </>
  )

}

const App = () => {

  const [action, setAction] = useState('hover')
  const [previousAction, setPreviousAction] = useState('')

  return (

    <Canvas>

      <Html fullscreen>
        <button
          onClick={() => {
            setPreviousAction(action)
            setAction('hover')
          }}>
          hover
        </button>
        <button
          onClick={() => {
            setPreviousAction(action)
            setAction('flight')
          }}>
          flight
        </button>
        <button
          onClick={() => {
            setPreviousAction(action)
            setAction('abduction_rings')
          }}>
          abduction_rings
        </button>
      </Html>

      <ambientLight intensity={Math.PI / 2} />
      <spotLight position={[10, 10, 10]} angle={0.15} penumbra={1} decay={0} intensity={Math.PI} />
      <pointLight position={[-10, -10, -10]} decay={0} intensity={Math.PI} />

      <Suspense fallback={<Loader />}>
        <Model
          position={[0, -1.5, 0]}
          rotation={[0, 0, 0]}
          scale={2}
          action={action}
          previousAction={previousAction}
        />
      </Suspense>

    </Canvas>

  )

}

ReactDOM.render(
  <App/>,
  document.getElementById("root")
);

前に自分で作成した以下のサイトの記録を参考にして作成してみました
CodeSandboxでReact Three Fiber実験その5 glTF 3Dモデルアニメーション切り替え表示 > React Three Fiber Practice30 glTF Model Multiple Animations Change03


React Three Fiber 8.16.1 glTF Animation Change Test04

React Three Fiber 8.16.1 glTF Animation Change Test04

HTML Test01と同じです

CSS Test01と同じです

JS(JavaScript)

import ReactDOM from 'https://esm.sh/react-dom@18.2.0'
import React, { useState, useEffect, useRef, Suspense } from 'https://esm.sh/react@18.2.0'
import { Canvas, useFrame } from 'https://esm.sh/@react-three/fiber@8.16.1'
import { Html, useProgress, useGLTF, useAnimations } from 'https://esm.sh/@react-three/drei@9.105.4'

const ModelPath = 'https://cdn.jsdelivr.net/gh/mrdoob/three.js@r110/examples/models/gltf/Soldier.glb'

const Loader = () => {
  const { progress } = useProgress()
  return (
    <Html center>
      {progress} % loaded
    </Html>
  )
}

const Model = (props) => {
  const group = useRef()

  const gltf = useGLTF(ModelPath)

  const { actions } = useAnimations(gltf.animations, group)

  useEffect(() => {
    //console.log(actions) // find out the name of your action
    if (props.previousAction) {
      actions[props.previousAction].fadeOut(0.2)
      actions[props.previousAction].stop()
    }

    actions[props.action].play()
    actions[props.action].fadeIn(0.2)
  }, [props.action, actions])

  useFrame((state) => {
    //gltf.scene.rotation.x += 0.02
    //gltf.scene.rotation.x = state.mouse.y * -2.5
    gltf.scene.rotation.y = state.mouse.x * 7.5
    //gltf.scene.rotation.z = (state.mouse.x + state.mouse.y) / 2  * 5
  }, [])

  return (
    <>
      <group ref={group} dispose={null}>
        <group scale={props.scale}>
          <primitive object={gltf.scene} position={props.position} rotation={props.rotation} scale={props.scale} />
        </group>
      </group>
    </>
  )

}

const App = () => {

  const [action, setAction] = useState('Walk')
  const [previousAction, setPreviousAction] = useState('')

  return (

    <Canvas>

      <Html fullscreen>
        <button
          onClick={() => {
            setPreviousAction(action)
            setAction('Idle')
          }}>
          Idle
        </button>
        <button
          onClick={() => {
            setPreviousAction(action)
            setAction('Run')
          }}>
          Run
        </button>
        <button
          onClick={() => {
            setPreviousAction(action)
            setAction('TPose')
          }}>
          TPose
        </button>
        <button
          onClick={() => {
            setPreviousAction(action)
            setAction('Walk')
          }}>
          Walk
        </button>
      </Html>

      <ambientLight intensity={Math.PI / 2} />
      <spotLight position={[10, 10, 10]} angle={0.15} penumbra={1} decay={0} intensity={Math.PI} />
      <pointLight position={[-10, -10, -10]} decay={0} intensity={Math.PI} />

      <Suspense fallback={<Loader />}>
        <Model
          position={[0, -1.5, 0]}
          rotation={[0, Math.PI, 0]}
          scale={1.7}
          action={action}
          previousAction={previousAction}
        />
      </Suspense>

    </Canvas>

  )

}

ReactDOM.render(
  <App/>,
  document.getElementById("root")
);

前に自分で作成した以下のサイトの記録を参考にして作成してみました
CodeSandboxでReact Three Fiber実験その5 glTF 3Dモデルアニメーション切り替え表示 > React Three Fiber Practice29 glTF Model Multiple Animations Change02


React Three Fiber 8.16.1 glTF Animation Change Test05

React Three Fiber 8.16.1 glTF Animation Change Test05

HTML Test01と同じです

CSS Test01と同じです

JS(JavaScript)

import ReactDOM from 'https://esm.sh/react-dom@18.2.0'
import React, { useState, useEffect, useRef, Suspense } from 'https://esm.sh/react@18.2.0'
import { Canvas, useFrame } from 'https://esm.sh/@react-three/fiber@8.16.1'
import { Html, useProgress, useGLTF, useAnimations } from 'https://esm.sh/@react-three/drei@9.105.4'

const ModelPath = 'https://rawcdn.githack.com/mrdoob/three.js/36f9f34752a985359e2556c68a52234436cefdfa/examples/models/gltf/RobotExpressive/RobotExpressive.glb'

const Loader = () => {
  const { progress } = useProgress()
  return (
    <Html center>
      {progress} % loaded
    </Html>
  )
}

const Model = (props) => {
  const group = useRef()

  const gltf = useGLTF(ModelPath)

  const { actions } = useAnimations(gltf.animations, group)

  useEffect(() => {
    //console.log(actions) // find out the name of your action
    if (props.previousAction) {
      actions[props.previousAction].fadeOut(0.2)
      actions[props.previousAction].stop()
    }

    actions[props.action].play()
    actions[props.action].fadeIn(0.2)
  }, [props.action, actions])

  useFrame((state) => {
    //gltf.scene.rotation.x += 0.02
    //gltf.scene.rotation.x = state.mouse.y * -2.5
    gltf.scene.rotation.y = state.mouse.x * 7.5
    //gltf.scene.rotation.z = (state.mouse.x + state.mouse.y) / 2  * 5
  }, [])

  return (
    <>
      <group ref={group} dispose={null}>
        <group scale={props.scale}>
          <primitive object={gltf.scene} position={props.position} rotation={props.rotation} scale={props.scale} />
        </group>
      </group>
    </>
  )

}

const App = () => {

  const [action, setAction] = useState('Walking')
  const [previousAction, setPreviousAction] = useState('')

  return (

    <Canvas>

      <Html fullscreen>
        <button
          onClick={() => {
            setPreviousAction(action)
            setAction('Idle')
          }}>
          Idle
        </button>
        <button
          onClick={() => {
            setPreviousAction(action)
            setAction('Walking')
          }}>
          Walking
        </button>
        <button
          onClick={() => {
            setPreviousAction(action)
            setAction('Running')
          }}>
          Running
        </button>
        <button
          onClick={() => {
            setPreviousAction(action)
            setAction('Dance')
          }}>
          Dance
        </button>
        <button
          onClick={() => {
            setPreviousAction(action)
            setAction('Death')
          }}>
          Death
        </button>
        <button
          onClick={() => {
            setPreviousAction(action)
            setAction('Sitting')
          }}>
          Sitting
        </button>
        <button
          onClick={() => {
            setPreviousAction(action)
            setAction('Standing')
          }}>
          Standing
        </button>
        <button
          onClick={() => {
            setPreviousAction(action)
            setAction('Jump')
          }}>
          Jump
        </button>
        <button
          onClick={() => {
            setPreviousAction(action)
            setAction('Yes')
          }}>
          Yes
        </button>
        <button
          onClick={() => {
            setPreviousAction(action)
            setAction('No')
          }}>
          No
        </button>
        <button
          onClick={() => {
            setPreviousAction(action)
            setAction('Wave')
          }}>
          Wave
        </button>
        <button
          onClick={() => {
            setPreviousAction(action)
            setAction('Punch')
          }}>
          Punch
        </button>
        <button
          onClick={() => {
            setPreviousAction(action)
            setAction('ThumbsUp')
          }}>
          ThumbsUp
        </button>
      </Html>

      <ambientLight intensity={Math.PI / 2} />
      <spotLight position={[10, 10, 10]} angle={0.15} penumbra={1} decay={0} intensity={Math.PI} />
      <pointLight position={[-10, -10, -10]} decay={0} intensity={Math.PI} />

      <Suspense fallback={<Loader />}>
        <Model
          position={[0, -2.5, 0]}
          scale={1}
          action={action}
          previousAction={previousAction}
        />
      </Suspense>

    </Canvas>

  )

}

ReactDOM.render(
  <App/>,
  document.getElementById("root")
);

前に自分で作成した以下のサイトの記録を参考にして作成してみました
CodeSandboxでReact Three Fiber実験その5 glTF 3Dモデルアニメーション切り替え表示 > React Three Fiber Practice28 glTF Model Multiple Animations Change01


React Three Fiber 8.16.1 glTF Animation Change Test06

React Three Fiber 8.16.1 glTF Animation Change Test06

HTML Test01と同じです

CSS Test01と同じです

JS(JavaScript)

import ReactDOM from 'https://esm.sh/react-dom@18.2.0'
import React, { useState, useEffect, useRef, Suspense } from 'https://esm.sh/react@18.2.0'
import { Canvas, useFrame } from 'https://esm.sh/@react-three/fiber@8.16.1'
import { Html, useProgress, useGLTF, useAnimations } from 'https://esm.sh/@react-three/drei@9.105.4'

const ModelPath = 'https://rawcdn.githack.com/najadojo/glTF/8109e86a26c9210814f8d236589ce92a2f9fa8f8/extensions/2.0/Vendor/MSFT_audio_emitter/samples/skateboard_character/glTF/skateboard_character_audio.gltf'

const Loader = () => {
  const { progress } = useProgress()
  return (
    <Html center>
      {progress} % loaded
    </Html>
  )
}

const Model = (props) => {
  const group = useRef()

  const gltf = useGLTF(ModelPath)

  const { actions } = useAnimations(gltf.animations, group)

  useEffect(() => {
    //console.log(actions) // find out the name of your action
    if (props.previousAction) {
      actions[props.previousAction].fadeOut(0.2)
      actions[props.previousAction].stop()
    }

    actions[props.action].play()
    actions[props.action].fadeIn(0.2)
  }, [props.action, actions])

  useFrame((state) => {
    //gltf.scene.rotation.x += 0.02
    //gltf.scene.rotation.x = state.mouse.y * -2.5
    gltf.scene.rotation.y = state.mouse.x * 7.5
    //gltf.scene.rotation.z = (state.mouse.x + state.mouse.y) / 2  * 5
  }, [])

  return (
    <>
      <group ref={group} dispose={null}>
        <group scale={props.scale}>
          <primitive object={gltf.scene} position={props.position} rotation={props.rotation} scale={props.scale} />
        </group>
      </group>
    </>
  )

}

const App = () => {

  const [action, setAction] = useState('idle_and_push')
  const [previousAction, setPreviousAction] = useState('')

  return (

    <Canvas>

      <Html fullscreen>
        <button
          onClick={() => {
            setPreviousAction(action)
            setAction('idle_and_push')
          }}>
          idle_and_push
        </button>
        <button
          onClick={() => {
            setPreviousAction(action)
            setAction('tricks_1')
          }}>
          tricks_1
        </button>
        <button
          onClick={() => {
            setPreviousAction(action)
            setAction('tricks_2')
          }}>
          tricks_2
        </button>
        <button
          onClick={() => {
            setPreviousAction(action)
            setAction('walk')
          }}>
          walk
        </button>
        <button
          onClick={() => {
            setPreviousAction(action)
            setAction('jump')
          }}>
          jump
        </button>

      </Html>

      <ambientLight intensity={Math.PI / 2} />
      <spotLight position={[10, 10, 10]} angle={0.15} penumbra={1} decay={0} intensity={Math.PI} />
      <pointLight position={[-10, -10, -10]} decay={0} intensity={Math.PI} />

      <Suspense fallback={<Loader />}>
        <Model
          position={[0, -0.35, -0.25]}
          scale={10}
          action={action}
          previousAction={previousAction}
        />
      </Suspense>

    </Canvas>

  )

}

ReactDOM.render(
  <App/>,
  document.getElementById("root")
);

前に自分で作成した以下のサイトの記録を参考にして作成してみました
Three.js r110でglTF形式の3Dモデルアニメーションを切り替えるCodePenへのリンク集


まとめ


この記事が気に入ったらサポートをしてみませんか?