Skip to content

Commit

Permalink
Merge pull request #8 from vrm-c/layout
Browse files Browse the repository at this point in the history
ui: update layout
  • Loading branch information
0b5vr committed Jul 21, 2023
2 parents b05150c + 6fad8b4 commit 07c4c06
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 18 deletions.
8 changes: 5 additions & 3 deletions src/components/Description.tsx
Expand Up @@ -6,9 +6,11 @@ interface DescriptionProps {
const Description = (props: DescriptionProps) => {
const state = props.state;
return (
<div className="p-40">
<div className="pb-40 typography-32 text-brand font-bold text-center">bvh to VRMA</div>
<div className="text-center typography-14 sm:typography-16 pb-8">
<div className="pb-40">
<div className="pb-40 text-[32px] text-[--charcoal-brand] font-bold text-center">
bvh to VRMA
</div>
<div className="text-center text-[14px] sm:text-[16px] pb-[6px]">
あなたのbvhファイルをVRMAファイルに変換します。
</div>
<div className="text-center typography-14 sm:typography-16">
Expand Down
2 changes: 1 addition & 1 deletion src/components/LoadBVH.tsx
Expand Up @@ -108,7 +108,7 @@ const LoadBVH = (props: LoadBVHProps) => {
<div className="flex justify-center pb-16">
<pixiv-icon class="text-text4" name="24/Check" scale="2"></pixiv-icon>
</div>
<div className="text-center font-bold text-text3 pb-24">変更が完了しました</div>
<div className="text-center font-bold text-text3 pb-24">変換が完了しました</div>
<div className="pb-8">
<Button onClick={fileDownload} variant="Primary">
ファイルをダウンロード
Expand Down
61 changes: 53 additions & 8 deletions src/components/VrmViewer.tsx
Expand Up @@ -2,15 +2,20 @@ 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;
}
export default function VrmViewer(props: VRMViewerProps) {
const [viewer] = useState<Viewer>(new Viewer());
const [loadFlag, setLoadFlag] = useState(false);
const [isPlaying, setPlaying] = useState(false);
const refDivProgress = useRef<HTMLDivElement>(null);
const blobURL = props.blobURL;

const canvasRef = useCallback(
async (canvas: HTMLCanvasElement) => {
if (canvas) {
Expand All @@ -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<HTMLDivElement>) => {
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 (
<div>
<div className={'lg:w-[390px] w-[358px] sm:w-[60vw] h-[583px] sm:h-[635px]'}>
<div className="flex flex-col">
<div className={'sm:w-[390px] w-[60vw] sm:h-[480px] h-[583px]'}>
<canvas ref={canvasRef} className={'h-full w-full rounded-24 bg-[--charcoal-surface3]'}></canvas>
</div>
<Button onClick={playAnimation}>Play</Button>
<div className="mt-[16px] flex gap-[16px] grow-0 w-full">
<IconButton
icon={isPlaying ? '24/PauseAlt' : '24/PlayAlt'}
onClick={isPlaying ? pauseAnimation : playAnimation}
className="grow-0 shrink-0"
/>
<div
className="flex w-full h-full grow items-center cursor-pointer"
onPointerDown={handlePointerDownProgress}
>
<div className="bg-text3-disabled w-full h-[8px] rounded-4 pointer-events-none">
<div ref={refDivProgress} className="bg-brand h-full rounded-4 pointer-events-none" />
</div>
</div>
</div>
</div>
);
}
27 changes: 24 additions & 3 deletions src/features/vrmViewer/Model.ts
Expand Up @@ -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<void> {
const { vrm, mixer } = this;
if (vrm == null || mixer == null) {
Expand All @@ -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;
}
}

Expand Down
1 change: 1 addition & 0 deletions src/icons/PlayAlt.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions 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,
}
}
6 changes: 3 additions & 3 deletions src/pages/index.tsx
Expand Up @@ -11,14 +11,14 @@ export default function Home() {
return (
<main className={`h-screen flex justify-center lg:items-center font-sans`}>
<Modal state={modalState} />
<div className="rounded-24 lg:h-[80vh] lg:w-[80vw] flex justify-center lg:items-center lg:bg-[--charcoal-background1]">
<div className="lg:w-[60vw] lg:flex lg:flex-row-reverse lg:justify-around">
<div className="rounded-24 lg:h-[80vh] lg:w-[80vw] flex justify-center lg:items-center lg:bg-[--charcoal-background1] lg:mt-0 mt-40">
<div className="lg:w-[60vw] lg:flex lg:flex-row-reverse lg:justify-around items-center gap-32">
{blobURL ? (
<>
<div>
<Description state={modalState} />
<div className="flex justify-center">
<div className="lg:w-[496px] lg:h-[296px] h-[268px] w-[358px] sm:w-[60vw]">
<div className="w-full lg:h-[296px] h-[268px]">
<LoadBVH setBlobURL={setBlobURL} />
</div>
</div>
Expand Down
27 changes: 27 additions & 0 deletions 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<number>(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 ]);
}

0 comments on commit 07c4c06

Please sign in to comment.