feat: add zoom-to-object on double-click (#139)
Some checks failed
CI / validate (pull_request) Failing after 19s
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:
44
app.js
44
app.js
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user