import React, { useEffect, useRef } from 'react'; import * as skinview3d from 'skinview3d'; const STEVE_SKIN_BASE64 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAEwElEQVR4Xu1av2sUURgMqCABQQVBBK0SCdooMQQD5jSFkNgpKdIEwSZoZ6GYJohNUmlhqrSxsUlhYZM/If/TmdncrLOz3+7drcneXtyB4f36bvNm3vd2l32ZmOiD+3eudMHZqWtJyTrbn189LqVfb+xAwZ17N1LxqLsBH17MZniuDFDBXn5b6+TEkxjz640dXLSmP8r/wgDd+74VILJoC5wLA3TFNRsgHqRQv/mh7+v64vkwwO8BSoiE4KLSrzd2UNF+QwQpdnn6aobs9+s1DpraFDZ183Im5ZkFSjVBf6PX0WtEcSh9PrUjmiBIIVpX8V7yd5Foxvn10efzqR0qwifMvr03y91fn9a7v7+8S8qf79eSvkfT13OxINvs0zpNYJ/Pp3ZwQjpRXUkIhWAIp3gSY5Go6JqsM55tn0/t0ImxxCMM4iACQoGXT3ZS0agDaCMGffhNdC0aGhnUCAN8hShIyRV/+/RBQs0AJUX5yuv9QQ1B2+dTOzgRlFx5PsO/v15MX2jwaPu4NJdw6fZkJoaPPGaCXtMN8LbPp3bwrU3FuwkoIRomsKRoGqS/5fX0zfDurckM2e/zGTkWHu50lfPz8wlnZmYSenwOR0fp9lBj0Ycxv77TL5fDwUE34f5+Znt6WGX4hCicRnh8DsciVbyacNoGqNEeVhk+oSoZcNYGTB4epgY0MgPOdAscC9ct8M8Z8Gxurwvij6N8vvAjJftYkjp+cXc3Q0/PVHxv4kXxYKafQpW9uLS9tZWlxg4KChzUAB0HMdlL29sJU0E9oUxP74/iMyK9j/2RARsbJ6QBHB8UZQI9O3wchJALm5sJUdcURel9Hu8muFkZ8WJGMlZkADgoygQWGaB1iiH7CfL4UJi0c2mupHg1gRwULpDtqE/HSBeUWXEzQFc/MiC39/f/3uh4s9N6RI67zkK4Ab7CkSHa9pRODegJcQM83jMkFd9r66OON1Rtq+hKj8XIAN8GZeMURXoK66p6bBTvv3VhKj4ygKXrLASFFQnsZwAmqWKSDNAVZTb0BHl8aIDED2oAykoZ4OmtwpUeR1KQiqNwF8cYj8sYIOIR4yvrBqgRlQxYWVnpgn5zczLOqaIiM1Sgx6YGiGgVD6rgoizQLTC0AXi97XQ6GVGrq6s5oYjxWNR1i6hhmjk0gsI8RrePxiATIwM0K9wAlq6zEHzHpzgXSOFRHOpqAKnbiCVX1OP9XsMY9pcJ1/3vRrjOFi1atGjRosX4AR9Ai5h5fT44+ViajrVo0aJFixYtWtQN/5Q29OGqfELTDyEe1li4AUMfr4sBmZPlcYEbUCUD+JrLDBhrAypnwHFZ6airbujXX//I6WM6zpjcgaefEbDtbAoGEcls8DEwPOJWA9wEtpsCNwClG6Dj/QxID0vcCK83BS7QRfq49iUGlJ3xl5HQTOmZ498NnKdqoItnO+rTMdLP9/XG5wce2u/zGBmKBPtq67jGu1h99Hm7kY/FSGC/LaB0A4pEs5+lz2NkcIEoIwMic8DofM/Fq/DGZYCnv6+09kfbJNrnkXDv93mMDDxJpsAi+nE7WSY+Et44A3hcrqKG+f+CyAAV7Qaw7fMYGaL/G1CBFB7FoQ5BRfeByAiO+zyq4g8lK5z2I+oYkQAAAABJRU5ErkJggg=="; // 3D 스킨 뷰어 컴포넌트 const SkinViewer = ({ skinUrl }) => { const containerRef = useRef(null); const canvasRef = useRef(null); const viewerRef = useRef(null); const isDraggingRef = useRef(false); const lastXRef = useRef(0); useEffect(() => { if (!canvasRef.current || !containerRef.current) return; const viewer = new skinview3d.SkinViewer({ canvas: canvasRef.current, width: containerRef.current.clientWidth, height: containerRef.current.clientHeight, skin: STEVE_SKIN_BASE64, }); // 설정 viewer.fov = 70; viewer.zoom = 0.9; viewer.autoRotate = false; viewer.animation = new skinview3d.IdleAnimation(); viewer.playerObject.rotation.y = Math.PI / 6; viewer.playerObject.position.y = -0.5; viewerRef.current = viewer; // 수동 드래그 회전 구현 const canvas = canvasRef.current; const handleMouseDown = (e) => { isDraggingRef.current = true; lastXRef.current = e.clientX; canvas.style.cursor = 'grabbing'; }; const handleMouseMove = (e) => { if (!isDraggingRef.current || !viewerRef.current) return; const deltaX = e.clientX - lastXRef.current; viewerRef.current.playerObject.rotation.y += deltaX * 0.01; lastXRef.current = e.clientX; }; const handleMouseUp = () => { isDraggingRef.current = false; canvas.style.cursor = 'grab'; }; // 터치 이벤트 지원 const handleTouchStart = (e) => { if (e.touches.length === 1) { isDraggingRef.current = true; lastXRef.current = e.touches[0].clientX; } }; const handleTouchMove = (e) => { if (!isDraggingRef.current || !viewerRef.current || e.touches.length !== 1) return; e.preventDefault(); const deltaX = e.touches[0].clientX - lastXRef.current; viewerRef.current.playerObject.rotation.y += deltaX * 0.01; lastXRef.current = e.touches[0].clientX; }; const handleTouchEnd = () => { isDraggingRef.current = false; }; canvas.addEventListener('mousedown', handleMouseDown); window.addEventListener('mousemove', handleMouseMove); window.addEventListener('mouseup', handleMouseUp); canvas.addEventListener('touchstart', handleTouchStart, { passive: false }); canvas.addEventListener('touchmove', handleTouchMove, { passive: false }); canvas.addEventListener('touchend', handleTouchEnd); // Resize Observer for responsive sizing const resizeObserver = new ResizeObserver(entries => { for (let entry of entries) { const { width, height } = entry.contentRect; viewer.setSize(width, height); } }); resizeObserver.observe(containerRef.current); return () => { resizeObserver.disconnect(); canvas.removeEventListener('mousedown', handleMouseDown); window.removeEventListener('mousemove', handleMouseMove); window.removeEventListener('mouseup', handleMouseUp); canvas.removeEventListener('touchstart', handleTouchStart); canvas.removeEventListener('touchmove', handleTouchMove); canvas.removeEventListener('touchend', handleTouchEnd); viewer.dispose(); }; }, []); useEffect(() => { if (viewerRef.current) { // 로딩 상태를 보여주기 위해 스티브 스킨으로 초기화 (동기/즉시) viewerRef.current.loadSkin(STEVE_SKIN_BASE64); const targetSkin = skinUrl || STEVE_SKIN_BASE64; if (targetSkin !== STEVE_SKIN_BASE64) { // 그 다음 실제 스킨 로드 viewerRef.current.loadSkin(targetSkin); } } }, [skinUrl]); return (
); }; export default SkinViewer;