diff --git a/web/pose_3d.js b/web/pose_3d.js index e0511c1..b631d4e 100644 --- a/web/pose_3d.js +++ b/web/pose_3d.js @@ -197,6 +197,27 @@ function updateRealWrists() { } } +function syncGhostsToTargetOrReal() { + for (const side of ['right', 'left']) { + const ghost = state.ghosts[side]; + if (!ghost) continue; + if (state.target[side]) { + // ghost segue il target locale (mosso dal gizmo) + ghost.visible = true; + } else if (side === state.active_side) { + // ghost allineato al reale (preparato per drag) + const real = state.current[side]; + if (real) { + ghost.position.set(real.position[0], real.position[1], real.position[2]); + ghost.quaternion.set(real.quaternion[0], real.quaternion[1], + real.quaternion[2], real.quaternion[3]); + } + } else { + ghost.visible = false; + } + } +} + function updateReadout() { const side = state.active_side; const pose = state.target[side] || state.current[side]; @@ -226,6 +247,7 @@ async function pollState() { state.saved_poses = s.poses || {}; state.live_max_step_m = s.live_max_step_m || 0.15; updateRealWrists(); + syncGhostsToTargetOrReal(); updateReadout(); // Aggiorna dropdown pose salvate refreshPoseSelect(); @@ -248,6 +270,99 @@ function refreshPoseSelect() { sel.value = cur; } +// ----- selezione polso via click ----- +function setupSelection() { + const { renderer, camera, transform } = state.three; + const raycaster = new THREE.Raycaster(); + const mouse = new THREE.Vector2(); + const canvas = renderer.domElement; + + canvas.addEventListener('pointerdown', (e) => { + // Se sta interagendo con il gizmo, non fare nulla + if (transform.dragging) return; + const rect = canvas.getBoundingClientRect(); + mouse.x = ((e.clientX - rect.left) / rect.width) * 2 - 1; + mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1; + raycaster.setFromCamera(mouse, camera); + // Verifica intersezione con sphere destra o sinistra + const candidates = [state.arms.right.userData.sphere, + state.arms.left.userData.sphere]; + const hits = raycaster.intersectObjects(candidates, false); + if (hits.length > 0) { + const sphere = hits[0].object; + const side = sphere.parent.userData.side; + activateSide(side); + } + }); +} + +function activateSide(side) { + state.active_side = side; + // sync radio buttons + for (const r of document.querySelectorAll('input[name=p3Side]')) { + r.checked = (r.value === side); + } + // Attacca gizmo al ghost (lo creiamo se non esiste, posizionandolo sul reale) + ensureGhostInit(side); + const { transform } = state.three; + transform.attach(state.ghosts[side]); + state.ghosts[side].visible = true; + updateReadout(); +} + +function ensureGhostInit(side) { + // Se non c'รจ target locale, allinea ghost al reale (preparato per drag) + if (!state.target[side]) { + const real = state.current[side]; + if (real) { + state.ghosts[side].position.set(real.position[0], real.position[1], real.position[2]); + state.ghosts[side].quaternion.set(real.quaternion[0], real.quaternion[1], + real.quaternion[2], real.quaternion[3]); + } + } +} + +// ----- gizmo mode toggle ----- +function bindGizmoControls() { + for (const r of document.querySelectorAll('input[name=p3Gizmo]')) { + r.addEventListener('change', () => { + state.gizmo_mode = r.value; + state.three.transform.setMode(r.value); + }); + } + // tasti t/r + window.addEventListener('keydown', (e) => { + if (!$('tab-pose-3d')?.classList.contains('active')) return; + if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return; + if (e.key === 't') { setGizmoMode('translate'); } + if (e.key === 'r') { setGizmoMode('rotate'); } + }); +} + +function setGizmoMode(mode) { + state.gizmo_mode = mode; + state.three.transform.setMode(mode); + for (const r of document.querySelectorAll('input[name=p3Gizmo]')) { + r.checked = (r.value === mode); + } +} + +// ----- target tracking durante drag gizmo ----- +function bindGizmoEvents() { + const { transform } = state.three; + transform.addEventListener('change', () => { + const side = state.active_side; + const ghost = state.ghosts[side]; + if (!ghost) return; + state.target[side] = { + position: [ghost.position.x, ghost.position.y, ghost.position.z], + quaternion: [ghost.quaternion.x, ghost.quaternion.y, + ghost.quaternion.z, ghost.quaternion.w], + }; + updateReadout(); + }); +} + // ----- side change listener ----- function bindSideControls() { for (const r of document.querySelectorAll('input[name=p3Side]')) { @@ -265,7 +380,10 @@ function ensureInit() { if (!$('p3Canvas')) return; inited = true; initScene(); + setupSelection(); bindSideControls(); + bindGizmoControls(); + bindGizmoEvents(); pollState(); pollTimer = setInterval(pollState, POLL_MS); }