From e0bf00fd5a70a03d839f34a08e31c90289a4c886 Mon Sep 17 00:00:00 2001 From: 0b5vr <0b5vr@0b5vr.com> Date: Fri, 21 Jul 2023 14:59:57 +0900 Subject: [PATCH 1/3] ui: update layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - レイアウト崩れの修正 - 再生・停止ボタン - プログレスバーの追加 また、自動再生を追加 --- src/components/Description.tsx | 8 +++-- src/components/VrmViewer.tsx | 61 ++++++++++++++++++++++++++++----- src/features/vrmViewer/Model.ts | 27 +++++++++++++-- src/icons/PlayAlt.svg | 1 + src/icons/index.ts | 12 +++++++ src/pages/index.tsx | 6 ++-- src/utils/useAnimationFrame.ts | 27 +++++++++++++++ 7 files changed, 125 insertions(+), 17 deletions(-) create mode 100644 src/icons/PlayAlt.svg create mode 100644 src/icons/index.ts create mode 100644 src/utils/useAnimationFrame.ts diff --git a/src/components/Description.tsx b/src/components/Description.tsx index aee30f0..d497992 100644 --- a/src/components/Description.tsx +++ b/src/components/Description.tsx @@ -6,9 +6,11 @@ interface DescriptionProps { const Description = (props: DescriptionProps) => { const state = props.state; return ( -
-
bvh to VRMA
-
+
+
+ bvh to VRMA +
+
あなたのbvhファイルをVRMAファイルに変換します。
diff --git a/src/components/VrmViewer.tsx b/src/components/VrmViewer.tsx index 9a25d4d..14169e0 100644 --- a/src/components/VrmViewer.tsx +++ b/src/components/VrmViewer.tsx @@ -2,7 +2,9 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { Viewer } from '@/features/vrmViewer/viewer'; import AvatarSample_A from '../assets/AvatarSample_A.vrm'; import { loadVRMAnimation } from '@/lib/VRMAnimation/loadVRMAnimation'; -import { Button } from '@charcoal-ui/react'; +import { IconButton } from '@charcoal-ui/react'; +import '@/icons'; +import { useAnimationFrame } from '@/utils/useAnimationFrame'; interface VRMViewerProps { blobURL: string | null; @@ -10,7 +12,10 @@ interface VRMViewerProps { export default function VrmViewer(props: VRMViewerProps) { const [viewer] = useState(new Viewer()); const [loadFlag, setLoadFlag] = useState(false); + const [isPlaying, setPlaying] = useState(false); + const refDivProgress = useRef(null); const blobURL = props.blobURL; + const canvasRef = useCallback( async (canvas: HTMLCanvasElement) => { if (canvas) { @@ -21,29 +26,69 @@ export default function VrmViewer(props: VRMViewerProps) { }, [viewer] ); + + const pauseAnimation = useCallback(() => { + if (viewer.model) { + viewer.model.pauseAction(); + setPlaying(false); + } + }, []); + + const playAnimation = useCallback(() => { + if (viewer.model) { + viewer.model.playAction(); + setPlaying(true); + } + }, []); + + const handlePointerDownProgress = useCallback((event: React.MouseEvent) => { + const target = event.nativeEvent.target as HTMLDivElement; + const rect = target.getBoundingClientRect(); + + const u = (event.clientX - rect.left) / rect.width; + console.log(event.clientX, rect.left, rect.width); + viewer.model?.setProgress(u); + }, []); + useEffect(() => { (async () => { if (blobURL) { const VRMAnimation = await loadVRMAnimation(blobURL); if (VRMAnimation && loadFlag && viewer.model) { await viewer.model.loadAnimation(VRMAnimation); + playAnimation(); } } })(); }, [blobURL, loadFlag, viewer.model]); - const playAnimation = () => { - if (viewer.model) { - viewer.model.playAction(); + useAnimationFrame(() => { + const divProgress = refDivProgress.current; + if (divProgress != null) { + divProgress.style.width = `${100.0 * (viewer.model?.progress ?? 0.0)}%`; } - }; + }, []); return ( -
-
+
+
- +
+ +
+
+
+
+
+
); } diff --git a/src/features/vrmViewer/Model.ts b/src/features/vrmViewer/Model.ts index 81e2799..c3bf32b 100644 --- a/src/features/vrmViewer/Model.ts +++ b/src/features/vrmViewer/Model.ts @@ -24,6 +24,16 @@ export class Model { this.mixer = new THREE.AnimationMixer(vrm.scene); } + public get progress(): number { + const action = this.currentAction; + + if (action != null) { + return action.time / action.getClip().duration; + } else { + return 0.0; + } + } + public async loadAnimation(vrmAnimation: VRMAnimation): Promise { const { vrm, mixer } = this; if (vrm == null || mixer == null) { @@ -39,10 +49,21 @@ export class Model { public playAction() { if (this.currentAction && this.mixer) { - this.mixer.addEventListener('loop', () => { - this.currentAction?.stop(); - }); this.currentAction.play(); + this.currentAction.paused = false; + } + } + + public pauseAction() { + if (this.currentAction && this.mixer) { + this.currentAction.paused = true; + } + } + + public setProgress(progress: number) { + if (this.currentAction && this.mixer) { + const duration = this.currentAction.getClip().duration; + this.currentAction.time = duration * progress; } } diff --git a/src/icons/PlayAlt.svg b/src/icons/PlayAlt.svg new file mode 100644 index 0000000..d0bbfa0 --- /dev/null +++ b/src/icons/PlayAlt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/icons/index.ts b/src/icons/index.ts new file mode 100644 index 0000000..5bce393 --- /dev/null +++ b/src/icons/index.ts @@ -0,0 +1,12 @@ +import { PixivIcon } from '@charcoal-ui/icons'; +import PlayAlt from './PlayAlt.svg'; + +PixivIcon.extend({ + '24/PlayAlt': PlayAlt.src, +}); + +declare module '@charcoal-ui/icons' { + export interface KnownIconType { + '24/PlayAlt': unknown, + } +} diff --git a/src/pages/index.tsx b/src/pages/index.tsx index ea4862b..f4618b6 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -11,14 +11,14 @@ export default function Home() { return (
-
-
+
+
{blobURL ? ( <>
-
+
diff --git a/src/utils/useAnimationFrame.ts b/src/utils/useAnimationFrame.ts new file mode 100644 index 0000000..5265ff3 --- /dev/null +++ b/src/utils/useAnimationFrame.ts @@ -0,0 +1,27 @@ +import { DependencyList, useEffect, useRef } from 'react'; + +export function useAnimationFrame( + callback: (delta: number) => void, + deps: DependencyList +): void { + const refPrev = useRef(0); + + useEffect(() => { + let halt = false; + const update = (): void => { + if (halt) { return; } + + const now = Date.now(); + callback(0.001 * (now - refPrev.current)); + refPrev.current = now; + + requestAnimationFrame(update); + }; + update(); + + return () => { + halt = true; + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ callback, ...deps ]); +} From 4137979249d5efb49e2eb15286eb63f9be6bb40c Mon Sep 17 00:00:00 2001 From: 0b5vr <0b5vr@0b5vr.com> Date: Fri, 21 Jul 2023 15:32:27 +0900 Subject: [PATCH 2/3] ui: tweak color of progress bar text4 -> text3-disabled --- src/components/VrmViewer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/VrmViewer.tsx b/src/components/VrmViewer.tsx index 14169e0..688d544 100644 --- a/src/components/VrmViewer.tsx +++ b/src/components/VrmViewer.tsx @@ -84,7 +84,7 @@ export default function VrmViewer(props: VRMViewerProps) { className="flex w-full h-full grow items-center cursor-pointer" onPointerDown={handlePointerDownProgress} > -
+
From 6fad8b410144badfd474376e6e2c8defc36bdfa1 Mon Sep 17 00:00:00 2001 From: 0b5vr <0b5vr@0b5vr.com> Date: Fri, 21 Jul 2023 15:33:40 +0900 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20Fix=20wording,=20=E5=A4=89=E6=9B=B4?= =?UTF-8?q?=E3=81=8C=E5=AE=8C=E4=BA=86=E3=81=97=E3=81=BE=E3=81=97=E3=81=9F?= =?UTF-8?q?=20=E2=86=92=20=E5=A4=89=E6=8F=9B=E3=81=8C=E5=AE=8C=E4=BA=86?= =?UTF-8?q?=E3=81=97=E3=81=BE=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/LoadBVH.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/LoadBVH.tsx b/src/components/LoadBVH.tsx index e156679..77b5069 100644 --- a/src/components/LoadBVH.tsx +++ b/src/components/LoadBVH.tsx @@ -108,7 +108,7 @@ const LoadBVH = (props: LoadBVHProps) => {
-
変更が完了しました
+
変換が完了しました