Fix: Deterministic camera transitions

- Refactored setCameraView to use precise lookAt and position calculation instead of accumulating quaternion errors.
- Forces camera UP vector (0,1,0) to prevent roll drift during isometric rotation.
This commit is contained in:
2025-12-23 12:56:09 +01:00
parent 21e85915e9
commit e47b2eeba0

View File

@@ -362,18 +362,16 @@ function setCameraView(direction, animate = true) {
} }
} }
// Calcular offset de la vista (diferencia entre posición y target) // Calcular offset de la vista (diferencia entre posición y target definidos en CAMERA_VIEWS)
const viewOffset = view.position.clone().sub(view.target); const viewOffset = view.position.clone().sub(view.target);
// Nueva posición de cámara centrada en el jugador // Nueva posición de cámara centrada en el jugador
const newPosition = playerPosition.clone().add(viewOffset); const targetPosition = playerPosition.clone().add(viewOffset);
const newTarget = playerPosition.clone(); const targetLookAt = playerPosition.clone();
if (animate && SESSION.currentView !== direction) { if (animate && SESSION.currentView !== direction) {
// Animación suave de transición const startPosition = camera.position.clone();
const startPos = camera.position.clone(); const startLookAt = controls.target.clone();
const startQuat = camera.quaternion.clone();
const startTarget = controls.target.clone();
const duration = 600; // ms const duration = 600; // ms
const startTime = Date.now(); const startTime = Date.now();
@@ -382,48 +380,47 @@ function setCameraView(direction, animate = true) {
const elapsed = Date.now() - startTime; const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1); const progress = Math.min(elapsed / duration, 1);
// Easing suave (ease-in-out) // Easing suave
const eased = progress < 0.5 const eased = progress < 0.5
? 2 * progress * progress ? 2 * progress * progress
: 1 - Math.pow(-2 * progress + 2, 2) / 2; : 1 - Math.pow(-2 * progress + 2, 2) / 2;
// Interpolar posición // Interpolación LINEAL de posición y target
camera.position.lerpVectors(startPos, newPosition, eased); const currentPos = new THREE.Vector3().lerpVectors(startPosition, targetPosition, eased);
const currentLookAt = new THREE.Vector3().lerpVectors(startLookAt, targetLookAt, eased);
// Interpolar quaternion (rotación suave) camera.position.copy(currentPos);
camera.quaternion.slerpQuaternions(startQuat, view.quaternion, eased); camera.up.set(0, 1, 0); // FORZAR UP VECTOR SIEMPRE
camera.lookAt(currentLookAt);
// Interpolar target controls.target.copy(currentLookAt);
controls.target.lerpVectors(startTarget, newTarget, eased);
camera.up.copy(view.up);
controls.update(); controls.update();
if (progress < 1) { if (progress < 1) {
requestAnimationFrame(animateTransition); requestAnimationFrame(animateTransition);
} else { } else {
// Asegurar valores finales exactos // Asegurar estado final perfecto
camera.position.copy(newPosition); camera.position.copy(targetPosition);
camera.quaternion.copy(view.quaternion); camera.up.set(0, 1, 0);
camera.up.copy(view.up); camera.lookAt(targetLookAt);
controls.target.copy(newTarget); controls.target.copy(targetLookAt);
controls.update(); controls.update();
} }
}; };
animateTransition(); animateTransition();
} else { } else {
// Sin animación (cambio instantáneo) // Cambio inmediato
camera.position.copy(newPosition); camera.position.copy(targetPosition);
camera.quaternion.copy(view.quaternion); camera.up.set(0, 1, 0); // FORZAR UP VECTOR
camera.up.copy(view.up); camera.lookAt(targetLookAt);
controls.target.copy(newTarget); controls.target.copy(targetLookAt);
controls.update(); controls.update();
} }
SESSION.currentView = direction; SESSION.currentView = direction;
updateCompassUI(); updateCompassUI();
updateWallOpacities(); // Actualizar opacidades de paredes según nueva vista updateWallOpacities();
} }
// Establecer vista inicial // Establecer vista inicial