feat(pose_3d): selezione polso + gizmo translate/rotate sul ghost

This commit is contained in:
Adriano Dal Pastro committed 2026-05-21 15:58:41 +02:00
1 parent 7a561cfa67
commit 64500944d6
1 file changed
+118
+118
View File
@@ -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() { function updateReadout() {
const side = state.active_side; const side = state.active_side;
const pose = state.target[side] || state.current[side]; const pose = state.target[side] || state.current[side];
@@ -226,6 +247,7 @@ async function pollState() {
state.saved_poses = s.poses || {}; state.saved_poses = s.poses || {};
state.live_max_step_m = s.live_max_step_m || 0.15; state.live_max_step_m = s.live_max_step_m || 0.15;
updateRealWrists(); updateRealWrists();
syncGhostsToTargetOrReal();
updateReadout(); updateReadout();
// Aggiorna dropdown pose salvate // Aggiorna dropdown pose salvate
refreshPoseSelect(); refreshPoseSelect();
@@ -248,6 +270,99 @@ function refreshPoseSelect() {
sel.value = cur; 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 ----- // ----- side change listener -----
function bindSideControls() { function bindSideControls() {
for (const r of document.querySelectorAll('input[name=p3Side]')) { for (const r of document.querySelectorAll('input[name=p3Side]')) {
@@ -265,7 +380,10 @@ function ensureInit() {
if (!$('p3Canvas')) return; if (!$('p3Canvas')) return;
inited = true; inited = true;
initScene(); initScene();
setupSelection();
bindSideControls(); bindSideControls();
bindGizmoControls();
bindGizmoEvents();
pollState(); pollState();
pollTimer = setInterval(pollState, POLL_MS); pollTimer = setInterval(pollState, POLL_MS);
} }