diff --git a/Frontend/src/hooks/useCursorSender.ts b/Frontend/src/hooks/useCursorSender.ts new file mode 100644 index 0000000..edc4b3c --- /dev/null +++ b/Frontend/src/hooks/useCursorSender.ts @@ -0,0 +1,61 @@ +import { useEffect, useRef } from 'react'; + +/** + * Convert a viewport point to world coords in [0,1] × [0,1] using + * letterbox mapping (preserves world aspect ratio 1:1). + */ +export function viewportToWorld( + viewportPx: { x: number; y: number }, + viewportSize: { w: number; h: number }, +): { x: number; y: number } { + // World is 1×1. Fit inside viewport with black bars on the wider axis. + const scale = Math.min(viewportSize.w, viewportSize.h); + const offsetX = (viewportSize.w - scale) / 2; + const offsetY = (viewportSize.h - scale) / 2; + return { + x: Math.max(0, Math.min(1, (viewportPx.x - offsetX) / scale)), + y: Math.max(0, Math.min(1, (viewportPx.y - offsetY) / scale)), + }; +} + +const SEND_INTERVAL_MS = 20; // 50 Hz cap + +export function useCursorSender( + targetRef: React.RefObject, + onSend: (x: number, y: number) => void, +) { + const lastCursorRef = useRef<{ x: number; y: number } | null>(null); + const lastSentAtRef = useRef(0); + const rafRef = useRef(null); + + useEffect(() => { + const el = targetRef.current; + if (!el) return; + + const handleMove = (ev: MouseEvent) => { + const rect = el.getBoundingClientRect(); + const world = viewportToWorld( + { x: ev.clientX - rect.left, y: ev.clientY - rect.top }, + { w: rect.width, h: rect.height }, + ); + lastCursorRef.current = world; + }; + + const tick = (now: number) => { + const c = lastCursorRef.current; + if (c && now - lastSentAtRef.current >= SEND_INTERVAL_MS) { + onSend(c.x, c.y); + lastSentAtRef.current = now; + } + rafRef.current = requestAnimationFrame(tick); + }; + + el.addEventListener('mousemove', handleMove); + rafRef.current = requestAnimationFrame(tick); + + return () => { + el.removeEventListener('mousemove', handleMove); + if (rafRef.current !== null) cancelAnimationFrame(rafRef.current); + }; + }, [targetRef, onSend]); +}