Skip to content

Commit d367065

Browse files
committed
feat: more dances and VRM controls!
1 parent 08491c7 commit d367065

File tree

4 files changed

+100
-37
lines changed

4 files changed

+100
-37
lines changed

next.config.mjs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import createNextIntlPlugin from "next-intl/plugin";
44

55
const withNextIntl = createNextIntlPlugin();
66

7-
const nextConfig = {
8-
};
7+
const nextConfig = {};
98

109
export default withNextIntl(nextConfig);

src/app/[locale]/page.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,7 @@ export default function Home() {
7373
</div>
7474
</div>
7575
<div className="">
76-
<Link
77-
href={"https://www.youtube.com/@mikndotdev"}
78-
target={"_blank"}
79-
>
80-
<VRM />
81-
</Link>
76+
<VRM />
8277
</div>
8378
</div>
8479

src/components/nUI/Header.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -263,15 +263,15 @@ export const Header = forwardRef<HTMLDivElement, HeaderProps>(
263263
key={buttonProps.title}
264264
>
265265
<button
266-
className="btn btn-primary text-white"
266+
className="btn btn-secondary text-white"
267267
{...buttonProps}
268268
>
269269
{buttonProps.title}
270270
</button>
271271
</Link>
272272
) : (
273273
<button
274-
className="btn btn-primary text-white"
274+
className="btn btn-secondary text-white"
275275
{...buttonProps}
276276
key={buttonProps.title}
277277
>
@@ -334,15 +334,15 @@ export const Header = forwardRef<HTMLDivElement, HeaderProps>(
334334
key={buttonProps.title}
335335
>
336336
<button
337-
className="btn btn-primary text-white"
337+
className="btn btn-secondary text-white"
338338
{...buttonProps}
339339
>
340340
{buttonProps.title}
341341
</button>
342342
</Link>
343343
) : (
344344
<button
345-
className="btn btn-primary text-white"
345+
className="btn btn-secondary text-white"
346346
{...buttonProps}
347347
key={buttonProps.title}
348348
>

src/components/vrm.tsx

Lines changed: 94 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,47 @@
11
"use client";
2-
import { AiOutlineLoading3Quarters } from "react-icons/ai";
3-
import React, { useState, useEffect } from "react";
2+
import { useState, useEffect, useRef, FC } from "react";
43
import { Canvas, useFrame } from "@react-three/fiber";
54
// @ts-ignore
65
import { GLTF, GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
6+
import { AnimationAction } from "three";
77
import { VRMLoaderPlugin } from "@pixiv/three-vrm";
88
import {
99
VRMAnimationLoaderPlugin,
1010
createVRMAnimationClip,
1111
} from "@pixiv/three-vrm-animation";
1212
import { AnimationMixer, LoopOnce, LoopRepeat } from "three";
13+
import { PiPlayPause, PiRepeat } from "react-icons/pi";
1314

1415
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+
},
1942
];
2043

21-
export const VRMModel: React.FC<{
44+
export const VRMModel: FC<{
2245
vrm: import("@pixiv/three-vrm").VRM | null;
2346
mixer: AnimationMixer | null;
2447
}> = ({ vrm, mixer }) => {
@@ -43,6 +66,7 @@ export function VRM() {
4366
const [vrm, setVrm] = useState<import("@pixiv/three-vrm").VRM | null>(null);
4467
const [mixer, setMixer] = useState<AnimationMixer | null>(null);
4568
const [isLoaded, setIsLoaded] = useState(false);
69+
const actionRef = useRef<AnimationAction | null>(null);
4670

4771
useEffect(() => {
4872
const loader = new GLTFLoader();
@@ -73,7 +97,10 @@ export function VRM() {
7397
};
7498

7599
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+
);
77104
const rand = Math.random() * total;
78105
let acc = 0;
79106
for (const anim of animations) {
@@ -96,8 +123,11 @@ export function VRM() {
96123
vrmAnimations[0],
97124
loadedVrm,
98125
);
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);
101131

102132
if (loop) {
103133
action.setLoop(LoopRepeat, Infinity);
@@ -108,14 +138,20 @@ export function VRM() {
108138

109139
action.play();
110140
setMixer(animationMixer);
141+
actionRef.current = action;
111142
} 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+
);
113146
}
114147
}
115148
},
116149
undefined,
117150
(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+
);
119155
},
120156
);
121157
};
@@ -131,19 +167,52 @@ export function VRM() {
131167
}, []);
132168

133169
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>
147216
</div>
148217
);
149218
}

0 commit comments

Comments
 (0)