// Copyright 2024 The Google Research Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * @fileoverview Input event handling. */ /** * We control the camera using either orbit controls... * @type {?THREE.OrbitControls} */ let gOrbitControls = null; /** * Map-controls, which are orbit controls with custom arguments, ... * @type {?THREE.OrbitControls} */ let gMapControls = null; /** * ...or for large scenes we use FPS-style controls. * @type {?THREE.PointerLockControls} */ let gPointerLockControls = null; // With PointerLockControls we have to track key states ourselves. /** @type {boolean} */ let gKeyW = false; /** @type {boolean} */ let gKeyA = false; /** @type {boolean} */ let gKeyS = false; /** @type {boolean} */ let gKeyD = false; /** @type {boolean} */ let gKeyQ = false; /** @type {boolean} */ let gKeyE = false; /** @type {boolean} */ let gKeyShift = false; /** * Keeps track of frame times for smooth camera motion. * @type {!THREE.Clock} */ const gClock = new THREE.Clock(); /** * Adds event listeners to UI. */ function addHandlers() { let shaderEditor = document.getElementById('shader-editor'); document.addEventListener('keypress', function (e) { if (document.activeElement.tagName.toLowerCase() === 'input' || document.activeElement.tagName.toLowerCase() === 'textarea') { return; // Ne pas interférer avec les champs d'input } if (document.activeElement === shaderEditor) { return; } if (e.keyCode === 32 || e.key === ' ' || e.key === 'Spacebar') { if (gDisplayMode == DisplayModeType.DISPLAY_NORMAL) { gDisplayMode = DisplayModeType.DISPLAY_DIFFUSE; console.log('Displaying DIFFUSE'); } else if (gDisplayMode == DisplayModeType.DISPLAY_DIFFUSE) { gDisplayMode = DisplayModeType.DISPLAY_FEATURES; console.log('Displaying DISPLAY_FEATURES'); } else if (gDisplayMode == DisplayModeType.DISPLAY_FEATURES) { gDisplayMode = DisplayModeType.DISPLAY_VIEW_DEPENDENT; console.log('Displaying DISPLAY_VIEW_DEPENDENT'); } else if (gDisplayMode == DisplayModeType.DISPLAY_VIEW_DEPENDENT) { gDisplayMode = DisplayModeType.DISPLAY_COARSE_GRID; console.log('Displaying DISPLAY_COARSE_GRID'); } else /* gDisplayModeType == DisplayModeType.DISPLAY_COARSE_GRID */ { gDisplayMode = DisplayModeType.DISPLAY_NORMAL; console.log('Displaying DISPLAY_NORMAL'); } e.preventDefault(); } if (e.key === 'r') { console.log('Recompile shader.'); let material = getRayMarchScene().children[0].material; material.fragmentShader = shaderEditor.value; material.needsUpdate = true; e.preventDefault(); } if (e.key === '?') { let position = gCamera.getWorldPosition(new THREE.Vector3(0., 0., 0.)); let direction = gCamera.getWorldQuaternion(new THREE.Quaternion()); console.log(` // Camera Info: gCamera.position.set(${position.x}, ${position.y}, ${position.z}); gCamera.quaternion.set(${direction.x}, ${direction.y}, ${direction.z}, ${ direction.w }); `); e.preventDefault(); } }); document.addEventListener('keydown', function (e) { if (document.activeElement.tagName.toLowerCase() === 'input' || document.activeElement.tagName.toLowerCase() === 'textarea') { return; // Ne pas interférer avec les champs d'input } if (document.activeElement === shaderEditor) { return; } let key = e.key.toLowerCase(); if (key === 'z') { // Avancer (AZERTY) gKeyW = true; e.preventDefault(); } if (key === 'q') { // Aller à gauche (AZERTY) gKeyA = true; } if (key === 's') { // Reculer gKeyS = true; e.preventDefault(); } if (key === 'd') { // Aller à droite gKeyD = true; e.preventDefault(); } if (key === 'a') { // Monter (remplace Q en QWERTY par A en AZERTY) gKeyQ = true; e.preventDefault(); } if (key === 'e') { // Descendre gKeyE = true; e.preventDefault(); } if (e.key === 'Shift') { // Boost vitesse gKeyShift = true; e.preventDefault(); } }); document.addEventListener('keyup', function (e) { if (document.activeElement.tagName.toLowerCase() === 'input' || document.activeElement.tagName.toLowerCase() === 'textarea') { return; // Ne pas interférer avec les champs d'input } if (document.activeElement === shaderEditor) { return; } let key = e.key.toLowerCase(); if (key === 'z') { gKeyW = false; e.preventDefault(); } if (key === 'q') { gKeyA = false; } if (key === 's') { gKeyS = false; e.preventDefault(); } if (key === 'd') { gKeyD = false; e.preventDefault(); } if (key === 'a') { gKeyQ = false; e.preventDefault(); } if (key === 'e') { gKeyE = false; e.preventDefault(); } if (e.key === 'Shift') { gKeyShift = false; e.preventDefault(); } }); } /** * Sets up the camera controls. * @param {string} mouseMode Either "orbit", "fps" or "map". * @param {!HTMLElement} view The view. */ function setupCameraControls(mouseMode, view) { if (mouseMode && mouseMode == 'fps') { gPointerLockControls = new THREE.PointerLockControls(gCamera, view); // Bouton "Commencer la visite" let startButton = document.createElement('button'); startButton.innerHTML = 'Commencer la visite !'; startButton.classList.add('mouse-navigation-button'); startButton.addEventListener('click', function () { gPointerLockControls.lock(); startButton.classList.add('hidden'); // Masque le bouton }); // Écoute pour la sortie du mode FPS et réaffiche le bouton si nécessaire gPointerLockControls.addEventListener('unlock', function() { startButton.classList.remove('hidden'); // Affiche le bouton lorsque la souris est débloquée (via "Échap") }); view.appendChild(startButton); } else if (mouseMode && mouseMode == 'map') { gMapControls = new THREE.OrbitControls(gCamera, view); gMapControls.panSpeed = 0.5 / gCamera.near; gMapControls.enableZoom = false; gMapControls.screenSpacePanning = false; gMapControls.mouseButtons = { LEFT: THREE.MOUSE.ROTATE, RIGHT: THREE.MOUSE.PAN, }; gMapControls.touches = { ONE: THREE.TOUCH.PAN, TWO: THREE.TOUCH.DOLLY_ROTATE, }; } else { // mouseMode == 'orbit' gOrbitControls = new THREE.OrbitControls(gCamera, view); gOrbitControls.screenSpacePanning = true; gOrbitControls.zoomSpeed = 0.5; } } // Variables globales pour FPS et clic gauche let isFPSActive = false; let gMouseDown = false; // Vérifier si l'événement cible est un élément interactif de l'interface function isClickOnUIElement(event) { const uiElements = ['BUTTON', 'INPUT', 'TEXTAREA', 'SELECT', 'LABEL']; return uiElements.includes(event.target.tagName) || event.target.closest('.info-tab') || event.target.closest('.assistant-tab'); } // Désactive le menu contextuel uniquement en mode FPS document.addEventListener('contextmenu', function (e) { if (gPointerLockControls && gPointerLockControls.isLocked) { e.preventDefault(); // Bloque le menu contextuel en mode FPS } }); // Gestion des clics de souris document.addEventListener('mousedown', function (e) { if (isClickOnUIElement(e)) { return; // Ignore les clics sur les éléments d'interface } if (e.button === 2) { // Clic droit pour quitter le mode FPS if (isFPSActive && gPointerLockControls) { isFPSActive = false; // Désactive le mode FPS gPointerLockControls.unlock(); // Désactive le verrouillage e.preventDefault(); // Empêche le menu contextuel natif } } if (e.button === 0 && !isFPSActive) { // Clic gauche pour réactiver le mode FPS if (gPointerLockControls) { isFPSActive = true; // Désactive le mode FPS gPointerLockControls.lock(); } } if (e.button === 0) { // Détecte le clic gauche maintenu gMouseDown = true; } }); document.addEventListener('mouseup', function (e) { if (e.button === 0) { gMouseDown = false; } }); // Variable globale pour suivre si Échap a été utilisé let isEscapePressed = false; document.addEventListener('keydown', function (e) { if (e.key === 'Escape') { // Touche Échap pour quitter le mode FPS if (gPointerLockControls && gPointerLockControls.isLocked) { // Indique que Échap a été pressé isEscapePressed = true; // Débloque la caméra immédiatement gPointerLockControls.unlock(); console.log("Mode FPS désactivé via Échap."); } // Réaffiche le bouton immédiatement const startButton = document.querySelector('.mouse-navigation-button'); if (startButton && startButton.classList.contains('hidden')) { startButton.classList.remove('hidden'); console.log("Bouton 'Commencer la visite' réaffiché."); } } }); // Synchronise l'état lorsque la caméra est effectivement déverrouillée if (gPointerLockControls) { gPointerLockControls.addEventListener('unlock', function () { // Si la sortie du mode FPS n'a pas été initiée par Échap, ne pas afficher le bouton if (!isEscapePressed) { console.log("Mode FPS désactivé via clic droit, bouton non affiché."); return; // Ne pas afficher le bouton } // Réinitialise la variable et affiche le bouton isEscapePressed = false; const startButton = document.querySelector('.mouse-navigation-button'); if (startButton && startButton.classList.contains('hidden')) { startButton.classList.remove('hidden'); console.log("Bouton 'Commencer la visite' réaffiché via Échap."); } }); } function updateCameraControls() { if (gOrbitControls) { gOrbitControls.update(); } else if (gMapControls) { gMapControls.update(); } else if (gPointerLockControls) { const elapsed = gClock.getDelta(); let movementSpeed = 0.1; if (gKeyShift) { movementSpeed = 0.2; } let camForward = gCamera.getWorldDirection(new THREE.Vector3(0., 0., 0.)); let upVec = new THREE.Vector3(0., 1., 0.); // Mouvement par clavier if (gKeyW) { // Avancer (Z en AZERTY) gCamera.position.addScaledVector(camForward, elapsed * movementSpeed); } if (gKeyA) { // Aller à gauche (Q en AZERTY) gPointerLockControls.moveRight(-elapsed * movementSpeed); } if (gKeyS) { // Reculer gCamera.position.addScaledVector(camForward, -elapsed * movementSpeed); } if (gKeyD) { // Aller à droite gPointerLockControls.moveRight(elapsed * movementSpeed); } if (gKeyQ) { // Monter (A en AZERTY) gCamera.position.addScaledVector(upVec, -elapsed * movementSpeed); } if (gKeyE) { // Descendre gCamera.position.addScaledVector(upVec, elapsed * movementSpeed); } // Mouvement par clic gauche maintenu if (gMouseDown) { gCamera.position.addScaledVector(camForward, elapsed * movementSpeed * 1.5); // Double la vitesse en cas de clic } } }