1
1
"use client" ;
2
- import { AiOutlineLoading3Quarters } from "react-icons/ai" ;
3
- import React , { useState , useEffect } from "react" ;
2
+ import { useState , useEffect , useRef , FC } from "react" ;
4
3
import { Canvas , useFrame } from "@react-three/fiber" ;
5
4
// @ts -ignore
6
5
import { GLTF , GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader" ;
6
+ import { AnimationAction } from "three" ;
7
7
import { VRMLoaderPlugin } from "@pixiv/three-vrm" ;
8
8
import {
9
9
VRMAnimationLoaderPlugin ,
10
10
createVRMAnimationClip ,
11
11
} from "@pixiv/three-vrm-animation" ;
12
12
import { AnimationMixer , LoopOnce , LoopRepeat } from "three" ;
13
+ import { PiPlayPause , PiRepeat } from "react-icons/pi" ;
13
14
14
15
const animations = [
15
- { url : "https://cdn.mikn.dev/vroid/shikanoko.vrma" , loop : true , percentage : 10 } ,
16
- { url : "https://cdn.mikn.dev/vroid/hi.vrma" , loop : false , percentage : 70 } ,
17
- { url : "https://cdn.mikn.dev/vroid/uishig.vrma" , loop : true , percentage : 10 } ,
18
- { url : "https://cdn.mikn.dev/vroid/tetoris.vrma" , loop : false , percentage : 10 } ,
16
+ {
17
+ url : "https://cdn.mikn.dev/vroid/shikanoko.vrma" ,
18
+ loop : true ,
19
+ percentage : 10 ,
20
+ } ,
21
+ { url : "https://cdn.mikn.dev/vroid/hi.vrma" , loop : false , percentage : 50 } ,
22
+ {
23
+ url : "https://cdn.mikn.dev/vroid/uishig.vrma" ,
24
+ loop : false ,
25
+ percentage : 10 ,
26
+ } ,
27
+ {
28
+ url : "https://cdn.mikn.dev/vroid/tetoris.vrma" ,
29
+ loop : false ,
30
+ percentage : 10 ,
31
+ } ,
32
+ {
33
+ url : "https://cdn.mikn.dev/vroid/telepathy.vrma" ,
34
+ loop : false ,
35
+ percentage : 10 ,
36
+ } ,
37
+ {
38
+ url : "https://cdn.mikn.dev/vroid/soware.vrma" ,
39
+ loop : false ,
40
+ percentage : 10 ,
41
+ } ,
19
42
] ;
20
43
21
- export const VRMModel : React . FC < {
44
+ export const VRMModel : FC < {
22
45
vrm : import ( "@pixiv/three-vrm" ) . VRM | null ;
23
46
mixer : AnimationMixer | null ;
24
47
} > = ( { vrm, mixer } ) => {
@@ -43,6 +66,7 @@ export function VRM() {
43
66
const [ vrm , setVrm ] = useState < import ( "@pixiv/three-vrm" ) . VRM | null > ( null ) ;
44
67
const [ mixer , setMixer ] = useState < AnimationMixer | null > ( null ) ;
45
68
const [ isLoaded , setIsLoaded ] = useState ( false ) ;
69
+ const actionRef = useRef < AnimationAction | null > ( null ) ;
46
70
47
71
useEffect ( ( ) => {
48
72
const loader = new GLTFLoader ( ) ;
@@ -73,7 +97,10 @@ export function VRM() {
73
97
} ;
74
98
75
99
function pickAnimation ( ) {
76
- const total = animations . reduce ( ( sum , anim ) => sum + anim . percentage , 0 ) ;
100
+ const total = animations . reduce (
101
+ ( sum , anim ) => sum + anim . percentage ,
102
+ 0 ,
103
+ ) ;
77
104
const rand = Math . random ( ) * total ;
78
105
let acc = 0 ;
79
106
for ( const anim of animations ) {
@@ -96,8 +123,11 @@ export function VRM() {
96
123
vrmAnimations [ 0 ] ,
97
124
loadedVrm ,
98
125
) ;
99
- const animationMixer = new AnimationMixer ( loadedVrm . scene ) ;
100
- const action = animationMixer . clipAction ( animationClip ) ;
126
+ const animationMixer = new AnimationMixer (
127
+ loadedVrm . scene ,
128
+ ) ;
129
+ const action =
130
+ animationMixer . clipAction ( animationClip ) ;
101
131
102
132
if ( loop ) {
103
133
action . setLoop ( LoopRepeat , Infinity ) ;
@@ -108,14 +138,20 @@ export function VRM() {
108
138
109
139
action . play ( ) ;
110
140
setMixer ( animationMixer ) ;
141
+ actionRef . current = action ;
111
142
} else {
112
- console . error ( "VRM model or humanoid is not loaded correctly." ) ;
143
+ console . error (
144
+ "VRM model or humanoid is not loaded correctly." ,
145
+ ) ;
113
146
}
114
147
}
115
148
} ,
116
149
undefined ,
117
150
( error : Error ) => {
118
- console . error ( "An error occurred while loading the animation:" , error ) ;
151
+ console . error (
152
+ "An error occurred while loading the animation:" ,
153
+ error ,
154
+ ) ;
119
155
} ,
120
156
) ;
121
157
} ;
@@ -131,19 +167,52 @@ export function VRM() {
131
167
} , [ ] ) ;
132
168
133
169
return (
134
- < div className = "flex justify-center items-center w-96 h-96" >
135
- { ! isLoaded ? (
136
- < div className = "flex items-center justify-center p-4" >
137
- < span
138
- className = "loading loading-xl loading-spinner text-primary"
139
- />
140
- </ div >
141
- ) : (
142
- < Canvas camera = { { position : [ 0 , 0 , 3 ] } } >
143
- < ambientLight intensity = { 1.7 } />
144
- < VRMModel vrm = { vrm } mixer = { mixer } />
145
- </ Canvas >
146
- ) }
170
+ < div >
171
+ < div className = "flex flex-col justify-center items-center w-96 h-96" >
172
+ { ! isLoaded ? (
173
+ < div className = "flex items-center justify-center p-4" >
174
+ < span className = "loading loading-xl loading-spinner text-primary" />
175
+ </ div >
176
+ ) : (
177
+ < a
178
+ href = { "https://youtube.com/@mikndotdev" }
179
+ target = "_blank"
180
+ className = { "block w-full h-full" }
181
+ >
182
+ < Canvas camera = { { position : [ 0 , 0 , 3 ] } } >
183
+ < ambientLight intensity = { 1.7 } />
184
+ < VRMModel vrm = { vrm } mixer = { mixer } />
185
+ </ Canvas >
186
+ </ a >
187
+ ) }
188
+ </ div >
189
+ < div className = "flex gap-2 mt-5 w-full justify-center" >
190
+ < PiPlayPause
191
+ className = "text-primary w-10 h-10 cursor-pointer"
192
+ onClick = { ( ) => {
193
+ if ( ! actionRef . current ) return ;
194
+ if ( actionRef . current ?. paused ) {
195
+ actionRef . current . paused = false ;
196
+ } else {
197
+ actionRef . current . paused = true ;
198
+ }
199
+ } }
200
+ >
201
+ Pause
202
+ </ PiPlayPause >
203
+ < PiRepeat
204
+ className = "text-primary w-10 h-10 cursor-pointer"
205
+ onClick = { ( ) => {
206
+ if ( actionRef . current ) {
207
+ actionRef . current . reset ( ) ;
208
+ actionRef . current . paused = false ;
209
+ actionRef . current . play ( ) ;
210
+ }
211
+ } }
212
+ >
213
+ Restart
214
+ </ PiRepeat >
215
+ </ div >
147
216
</ div >
148
217
) ;
149
218
}
0 commit comments