import React, { useState, useRef, useEffect } from 'react'; import { Rect } from '../types'; interface RiggingEditorProps { imageUrl: string; initialData?: { leftEye: Rect; rightEye: Rect; mouth: Rect; skinColor: string }; onComplete: (data: { leftEye: Rect; rightEye: Rect; mouth: Rect; skinColor: string; textureClosedEye: Rect; textureOpenMouth: Rect; mainBody: Rect; chromaKeyColor: string; }) => void; } type ActiveFeature = 'leftEye' | 'rightEye' | 'mouth' | 'textureClosedEye' | 'textureOpenMouth' | 'mainBody' | null; const ResizableBox: React.FC<{ rect: Rect; color: string; label: string; isActive: boolean; onUpdate: (rect: Rect) => void; onActivate: () => void; }> = ({ rect, color, label, isActive, onUpdate, onActivate }) => { const boxRef = useRef(null); const [isDragging, setIsDragging] = useState(false); const [isResizing, setIsResizing] = useState(false); const startPos = useRef({ x: 0, y: 0 }); const startRect = useRef({ x: 0, y: 0, w: 0, h: 0 }); const handleMouseDown = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); onActivate(); setIsDragging(true); startPos.current = { x: e.clientX, y: e.clientY }; startRect.current = { ...rect }; }; const handleResizeDown = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); onActivate(); setIsResizing(true); startPos.current = { x: e.clientX, y: e.clientY }; startRect.current = { ...rect }; }; useEffect(() => { const handleMouseMove = (e: MouseEvent) => { if (!isDragging && !isResizing) return; const parent = boxRef.current?.parentElement; if (!parent) return; const parentRect = parent.getBoundingClientRect(); const deltaX = (e.clientX - startPos.current.x) / parentRect.width; const deltaY = (e.clientY - startPos.current.y) / parentRect.height; if (isDragging) { onUpdate({ ...rect, x: startRect.current.x + deltaX, y: startRect.current.y + deltaY, }); } else if (isResizing) { onUpdate({ ...rect, w: Math.max(0.01, startRect.current.w + deltaX), h: Math.max(0.01, startRect.current.h + deltaY), }); } }; const handleMouseUp = () => { setIsDragging(false); setIsResizing(false); }; if (isDragging || isResizing) { window.addEventListener('mousemove', handleMouseMove); window.addEventListener('mouseup', handleMouseUp); } return () => { window.removeEventListener('mousemove', handleMouseMove); window.removeEventListener('mouseup', handleMouseUp); }; }, [isDragging, isResizing, rect, onUpdate]); return (
{/* Label */}
{label}
{/* Resize Handle */}
); }; const RiggingEditor: React.FC = ({ imageUrl, initialData, onComplete }) => { // Targets (Left side of image usually) const [leftEye, setLeftEye] = useState(initialData?.leftEye || { x: 0.25, y: 0.4, w: 0.1, h: 0.1 }); const [rightEye, setRightEye] = useState(initialData?.rightEye || { x: 0.45, y: 0.4, w: 0.1, h: 0.1 }); const [mouth, setMouth] = useState(initialData?.mouth || { x: 0.35, y: 0.55, w: 0.1, h: 0.05 }); // Main Body (Default to left 70%) const [mainBody, setMainBody] = useState({ x: 0.05, y: 0.05, w: 0.65, h: 0.9 }); // Sources (Right side of image usually) const [textureClosedEye, setTextureClosedEye] = useState({ x: 0.7, y: 0.1, w: 0.2, h: 0.2 }); const [textureOpenMouth, setTextureOpenMouth] = useState({ x: 0.7, y: 0.5, w: 0.2, h: 0.2 }); const [skinColor, setSkinColor] = useState(initialData?.skinColor || '#fcd3bf'); // Use this simply as a boolean flag now, passing 'AI_AUTO' if enabled const [useAiBackground, setUseAiBackground] = useState(true); const [activeFeature, setActiveFeature] = useState(null); return (

Rig Your Character

1. Adjust the Main Body (Yellow) to frame your character.
2. Match the Targets (Red/Blue/Green) to the face features.
3. Match the Sources (Purple/Orange) to the assets on the right.

{/* Editor Area */}
Rigging Target {/* Aspect ratio container to map percentage boxes correctly */}
{/* Main Body */} setActiveFeature('mainBody')} /> {/* Targets */} setActiveFeature('leftEye')} /> setActiveFeature('rightEye')} /> setActiveFeature('mouth')} /> {/* Sources */} setActiveFeature('textureClosedEye')} /> setActiveFeature('textureOpenMouth')} />
{/* Sidebar Controls */}
AI Magic Removal
setSkinColor(e.target.value)} className="w-8 h-8 rounded cursor-pointer border-0 p-0" /> Fallback
Composition
setActiveFeature('mainBody')}>
Main Body Crop
Targets (Main Face)
setActiveFeature('leftEye')}>
Left Eye
setActiveFeature('rightEye')}>
Right Eye
setActiveFeature('mouth')}>
Mouth
Sources (Right Side)
setActiveFeature('textureClosedEye')}>
Closed Eye Texture
setActiveFeature('textureOpenMouth')}>
Open Mouth Texture
); }; export default RiggingEditor;