feat: add zoom-to-object on double-click (#139)
Some checks failed
CI / validate (pull_request) Failing after 19s

Double-clicking any star raycasts into the scene and smoothly flies
the camera to 8 units from the hit point over 1.4 s using an
ease-in-out curve. Camera faces the object throughout the animation.

Fixes #139
This commit is contained in:
Alexander Whitestone
2026-03-24 00:02:51 -04:00
parent 7eca0fba5d
commit e24d253bc6

44
app.js
View File

@@ -119,6 +119,39 @@ document.addEventListener('mousemove', (e) => {
mouseY = (e.clientY / window.innerHeight - 0.5) * 2;
});
// === ZOOM TO OBJECT (double-click to fly) ===
const raycaster = new THREE.Raycaster();
raycaster.params.Points.threshold = 3;
const mouse2D = new THREE.Vector2();
let zoomTarget = null;
let zoomOrigin = null;
let zoomLookAt = null;
let zoomStartTime = 0;
const ZOOM_DURATION = 1.4;
function easeInOutQuad(t) {
return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
}
renderer.domElement.addEventListener('dblclick', (e) => {
mouse2D.x = (e.clientX / window.innerWidth) * 2 - 1;
mouse2D.y = -(e.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse2D, camera);
const intersects = raycaster.intersectObject(stars);
if (intersects.length > 0) {
const hitPoint = intersects[0].point.clone();
// Approach from current camera direction, stop 8 units from hit
const approach = camera.position.clone().sub(hitPoint).normalize().multiplyScalar(8);
zoomOrigin = camera.position.clone();
zoomTarget = hitPoint.clone().add(approach);
zoomLookAt = hitPoint.clone();
zoomStartTime = clock.getElapsedTime();
}
});
// === RESIZE HANDLER ===
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
@@ -146,6 +179,17 @@ function animate() {
// Subtle pulse on constellation opacity
constellationLines.material.opacity = 0.12 + Math.sin(elapsed * 0.5) * 0.06;
// Zoom-to-object animation
if (zoomTarget) {
const t = Math.min((elapsed - zoomStartTime) / ZOOM_DURATION, 1);
const ease = easeInOutQuad(t);
camera.position.lerpVectors(zoomOrigin, zoomTarget, ease);
camera.lookAt(zoomLookAt);
if (t >= 1) {
zoomTarget = null;
}
}
renderer.render(scene, camera);
}