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