Drive plane progress by mouse movement, not mouse position

Old behaviour: plane.x mapped 1:1 to cursor.x — moving the mouse left
made the plane reverse. New behaviour: every pixel of cursor travel
(any direction) increments a progress counter from 0 to 1, the plane
position is derived from progress, and progress saturates at 1 — so
once the plane has exited stage right, it stays gone.

FULL_DISTANCE = 3500 px of cursor travel for a full traversal. The
existing lerp (0.06) still smooths the rendered position. Background
parallax still uses raw cursor X/Y (independent of plane progress).
deviceorientation handler updated symmetrically — gamma+beta deltas
push progress forward on mobile.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
MVA Global Fret 2026-05-05 12:11:53 +02:00
parent ad9ad43487
commit 3829ab9af6

View File

@ -65,26 +65,45 @@ loader.load(
);
/* Souris
- mouseX, mouseY : 0..1 normalisés
- Sur écran sans souris (touch/mobile), valeur lente d'auto-scroll
La souris fait AVANCER l'avion sur sa trajectoire : on accumule la
distance parcourue par le curseur. Une fois que l'avion est sorti à
droite (progress = 1), il reste sorti la souris ne le ramène pas.
On garde aussi mouseX (0..1) pour la parallaxe légère de la photo.
*/
const mouse = { tx: 0.0, ty: 0.5, x: 0.0, y: 0.5 };
const FULL_DISTANCE = 3500; // pixels de souris pour traverser tout l'écran
const mouse = {
targetProgress: 0, // accumulé, croissant
progress: 0, // suit avec lerp
px: 0.5, py: 0.5 // dernier point connu (parallaxe fond)
};
let lastX = null, lastY = null;
window.addEventListener('mousemove', (e) => {
mouse.tx = Math.max(0, Math.min(1, e.clientX / window.innerWidth));
mouse.ty = Math.max(0, Math.min(1, e.clientY / window.innerHeight));
}, { passive: true });
/* Sur mobile : utilise l'orientation gamma (gauche-droite) si dispo */
window.addEventListener('deviceorientation', (e) => {
if (e.gamma == null) return;
mouse.tx = Math.max(0, Math.min(1, (e.gamma + 30) / 60));
if (e.beta != null) {
mouse.ty = Math.max(0, Math.min(1, (e.beta - 20) / 60));
if (lastX !== null) {
const dx = e.clientX - lastX;
const dy = e.clientY - lastY;
mouse.targetProgress = Math.min(1, mouse.targetProgress + Math.hypot(dx, dy) / FULL_DISTANCE);
}
lastX = e.clientX; lastY = e.clientY;
mouse.px = e.clientX / window.innerWidth;
mouse.py = e.clientY / window.innerHeight;
}, { passive: true });
/* Mobile : la rotation du device fait progresser l'avion */
let lastGamma = null, lastBeta = null;
window.addEventListener('deviceorientation', (e) => {
if (e.gamma == null || e.beta == null) return;
if (lastGamma !== null) {
const dg = e.gamma - lastGamma;
const db = e.beta - lastBeta;
mouse.targetProgress = Math.min(1, mouse.targetProgress + Math.hypot(dg, db) / 90);
}
lastGamma = e.gamma; lastBeta = e.beta;
mouse.px = Math.max(0, Math.min(1, (e.gamma + 30) / 60));
mouse.py = Math.max(0, Math.min(1, (e.beta - 20) / 60));
}, { passive: true });
/* Variable CSS pour la parallaxe douce de la photo de fond */
const root = document.documentElement;
/* ── Render loop ───────────────────────────────────────────────────────── */
@ -93,34 +112,32 @@ const clock = new THREE.Clock();
function tick() {
const t = clock.getElapsedTime();
/* Lerp doux vers la cible souris */
mouse.x += (mouse.tx - mouse.x) * 0.06;
mouse.y += (mouse.ty - mouse.y) * 0.06;
/* Lerp doux vers la cible (progrès cumulé) */
mouse.progress += (mouse.targetProgress - mouse.progress) * 0.06;
const p = mouse.progress;
/* Mappe sur les variables CSS (parallaxe légère du fond) */
root.style.setProperty('--mx', ((mouse.x - 0.5) * 2).toFixed(4));
root.style.setProperty('--my', ((mouse.y - 0.5) * 2).toFixed(4));
/* Variables CSS pour la parallaxe légère de la photo de fond */
root.style.setProperty('--mx', ((mouse.px - 0.5) * 2).toFixed(4));
root.style.setProperty('--my', ((mouse.py - 0.5) * 2).toFixed(4));
/* Position de l'avion :
- mouse.x = 0 arrive haut-gauche (hors champ)
- mouse.x = 0.5 centré
- mouse.x = 1 sortie en bas-droite (hors champ)
/* Trajectoire :
- p = 0 arrive haut-gauche (hors champ)
- p = 0.5 traverse au centre haut
- p = 1 sortie à droite (hors champ)
*/
const px = -16 + mouse.x * 32; // -16 à +16
const py = 7 - mouse.x * 5; // descend de +7 à +2 (haut de l'écran)
/* Léger bobbing autonome pour qu'il reste vivant même sans bouger la souris */
const px = -16 + p * 32;
const py = 7 - p * 5;
const bob = Math.sin(t * 0.9) * 0.12;
planeHolder.position.set(px, py + bob, 0);
/* Roulis : penche dans le sens du mouvement (mais tourne le nez vers la droite) */
const targetRoll = -0.18 - (mouse.x - 0.5) * 0.25; // léger pivot quand on traverse
const targetPitch = -0.18 - mouse.x * 0.10; // légèrement piqué
const targetYaw = (mouse.x - 0.5) * 0.10; // soupçon de yaw
planeHolder.rotation.z += (targetRoll - planeHolder.rotation.z) * 0.08;
/* Banking subtil — penche en pivotant */
const targetRoll = -0.18 - (p - 0.5) * 0.25;
const targetPitch = -0.18 - p * 0.10;
const targetYaw = (p - 0.5) * 0.10;
planeHolder.rotation.z += (targetRoll - planeHolder.rotation.z) * 0.08;
planeHolder.rotation.x += (targetPitch - planeHolder.rotation.x) * 0.08;
planeHolder.rotation.y += (targetYaw - planeHolder.rotation.y) * 0.08;
planeHolder.rotation.y += (targetYaw - planeHolder.rotation.y) * 0.08;
renderer.render(scene, camera);
requestAnimationFrame(tick);