diff --git a/public/index.html b/public/index.html index 2cb0e79f6b50428b6a55a5a77c6d80d675b84b2d..2e93c54b2706731eb5bfe590f95972f370786831 100644 --- a/public/index.html +++ b/public/index.html @@ -94,40 +94,43 @@ </div> </div> - - <script src="ar_debug_console.js"></script> <link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" /> <script src="https://unpkg.com/leaflet/dist/leaflet.js"></script> <!-- Leaflet einbinden --> <script src="ar_overviewmap.js"></script> - <!-- Audio-Element für Button-Klick --> - <audio id="button-sound" src="sounds/button-sound.mp3" preload="auto"></audio> + + <audio id="button-sound" src="sounds/button-sound.mp3" preload="auto"></audio> <!-- Audio-Element für Button-Klick --> <script> - // Variablen - let selectedModel = 'robot'; + /* ========================= */ + /* GLOBALE VARIABLEN */ + /* ========================= */ let selectedPlacedModel = null; let currentSession = null; let reticle = null; let scene, camera; + const menus = ['menu-bar', 'add-menu', 'edit-menu', 'options-menu', 'map-window']; + /* ========================= */ + /* MODELLE */ + /* ========================= */ let models = { bench: { name: "Bench", image: "previewImages/bench.PNG", file: "https://transfer.hft-stuttgart.de/gitlab/geovistoogsi/ar/-/raw/master/public/assets/models/bench_model/scene.gltf", scale: { x: 0.1, y: 0.1, z: 0.1 }, - minScale: 0.05, // 50% der aktuellen Größe - maxScale: 0.5 // 500% der aktuellen Größe + minScale: 0.05, + maxScale: 0.5 }, trashbin: { name: "Trash bin", image: "previewImages/trash_can.PNG", file: "https://transfer.hft-stuttgart.de/gitlab/geovistoogsi/ar/-/raw/master/public/assets/models/trash_model/scene.gltf", scale: { x: 0.03, y: 0.03, z: 0.03 }, - minScale: 0.01, // 50% der aktuellen Größe - maxScale: 0.1 // 500% der aktuellen Größe + minScale: 0.01, + maxScale: 0.1 }, lantern: { name: "Lantern", @@ -171,12 +174,14 @@ } }; - const menus = ['menu-bar', 'add-menu', 'edit-menu', 'options-menu', 'map-window']; + /* ========================= */ + /* INITIALISIERUNG */ + /* ========================= */ window.onload = () => { initializeAddMenu(); - // Fügt Sound zu allen Buttons hinzu + // Allen Buttons den Sound hinzufügen const buttons = document.querySelectorAll("button, .menu-item"); buttons.forEach(button => { button.addEventListener("click", playButtonSound); @@ -201,6 +206,10 @@ `; } + + /* ========================= */ + /* MENÜ-STEUERUNG */ + /* ========================= */ function showMenu(menuId) { menus.forEach(id => { document.getElementById(id).style.display = id === menuId ? 'flex' : 'none'; @@ -210,25 +219,15 @@ else if (menuId === 'map-window') init_map(); } - function clearSelectedModel() { - if (selectedPlacedModel) { - selectedPlacedModel.traverse((child) => { - if (child.isMesh) { - child.material.emissive.setHex(0x000000); // Markierung entfernen - } - }); - selectedPlacedModel = null; // Kein Modell mehr ausgewählt - } + function closeDynamicMenu() { + const dynamicMenu = document.getElementById("dynamic-menu"); + dynamicMenu.style.display = "none"; } - function selectModel(modelId) { - const model = models[modelId]; - if (model && model.file) { - loadModel(model.file); - showMenu('menu-bar'); - } - } + /* ========================= */ + /* MODELL-HANDLING */ + /* ========================= */ function loadModel(filePath) { const modelConfig = Object.values(models).find(model => model.file === filePath); const loader = new THREE.GLTFLoader(); @@ -256,30 +255,11 @@ ); } - function highlightSelectedModel() { - if (selectedPlacedModel) { - removeHighlightFromAllModels(); - selectedPlacedModel.traverse((child) => { - if (child.isMesh) { - child.material.emissive.setHex(0xff0000); // Rote Hervorhebung - } - }); - } - } - - function removeHighlightFromAllModels() { - scene.traverse((child) => { - if (child.isMesh && child.material && child.material.emissive) { - child.material.emissive.setHex(0x000000); // Markierung entfernen - } - }); - } - - function removeHighlightFromSelectedModel() { - if (selectedPlacedModel) { - selectedPlacedModel.traverse((child) => { - if (child.isMesh) child.material.emissive.setHex(0x000000); // Markierung entfernen - }); + function selectModel(modelId) { + const model = models[modelId]; + if (model && model.file) { + loadModel(model.file); + showMenu('menu-bar'); } } @@ -305,7 +285,7 @@ // Überprüfe, ob das ausgewählte Objekt das Reticle ist if (selectedObject === reticle) { console.log("Reticle kann nicht ausgewählt werden."); - return; // Auswahl ignorieren + return; } // Markiere das gesamte Modell als ausgewählt @@ -315,6 +295,56 @@ } } + function clearSelectedModel() { + if (selectedPlacedModel) { + selectedPlacedModel.traverse((child) => { + if (child.isMesh) { + child.material.emissive.setHex(0x000000); // Markierung entfernen + } + }); + selectedPlacedModel = null; + } + } + + function highlightSelectedModel() { + if (selectedPlacedModel) { + selectedPlacedModel.traverse((child) => { + if (child.isMesh) { + child.material.emissive.setHex(0xff0000); // Rote Hervorhebung + } + }); + } + } + + function removeHighlightFromSelectedModel() { + if (selectedPlacedModel) { + selectedPlacedModel.traverse((child) => { + if (child.isMesh) child.material.emissive.setHex(0x000000); // Markierung entfernen + }); + } + } + + function deleteModel() { + if (!selectedPlacedModel) { + console.log("Kein Modell ausgewählt. Bitte wählen Sie ein Modell aus, bevor Sie es löschen."); + return; + } + + const deleteDialog = document.getElementById('delete-confirmation-dialog'); + deleteDialog.style.display = 'flex'; + } + + function confirmDelete(shouldDelete) { + const deleteDialog = document.getElementById('delete-confirmation-dialog'); + deleteDialog.style.display = 'none'; + + if (shouldDelete && selectedPlacedModel) { + scene.remove(selectedPlacedModel); + selectedPlacedModel = null; + showMenu('menu-bar'); + } + } + function completeEditing() { removeHighlightFromSelectedModel(); closeDynamicMenu(); @@ -323,14 +353,17 @@ document.getElementById('menu-bar').style.display = 'flex'; } + + /* ========================= */ + /* BEARBEITUNGS-MENÜS */ + /* ========================= */ function openRotationMenu() { if (!selectedPlacedModel) { console.log("Kein Modell ausgewählt. Bitte wählen Sie ein Modell aus, bevor Sie es bearbeiten."); return; } - // Holen Sie die aktuelle Y-Rotation des Modells (in Grad) - const currentRotation = Math.round(THREE.MathUtils.radToDeg(selectedPlacedModel.rotation.y)); + const currentRotation = Math.round(THREE.MathUtils.radToDeg(selectedPlacedModel.rotation.y)); // Aktuelle Y-Rotation des Modells (in Grad) const dynamicMenu = document.getElementById("dynamic-menu"); dynamicMenu.style.display = "flex"; @@ -346,7 +379,7 @@ const radians = (value / 180) * Math.PI; selectedPlacedModel.rotation[axis] = radians; - // Update der aktuellen Rotation im Menü + // Anzeige der aktuellen Rotation im Dynamic-Menü aktualisieren const currentRotationDisplay = document.getElementById("current-rotation"); if (currentRotationDisplay) { currentRotationDisplay.textContent = value; // Zeige den aktuellen Wert in Grad an @@ -360,37 +393,40 @@ return; } - // Aktuelle Skalierung des Modells bestimmen + // Aktuelle Skalierung und Grenzen bestimmen const currentScale = selectedPlacedModel.scale.x; const minScale = selectedPlacedModel.modelConfig.minScale; const maxScale = selectedPlacedModel.modelConfig.maxScale; const step = (maxScale - minScale) / 100; // Dynamische Schrittgröße basierend auf Grenzen - console.log("Slider-Werte:", { minScale, maxScale, currentScale }); + const currentScalePercent = ((currentScale - minScale) / (maxScale - minScale)) * 100; // Umrechnung der Werte in Prozent const dynamicMenu = document.getElementById("dynamic-menu"); dynamicMenu.style.display = "flex"; dynamicMenu.innerHTML = ` - <h3>Skalierung anpassen</h3> - <label>Größe: <span id="scale-value">${currentScale.toFixed(2)}</span> - <input type="range" min="${minScale}" max="${maxScale}" step="${step}" value="${currentScale}" onchange="updateScale(this.value)"></label> - <button onclick="closeDynamicMenu()">Zurück</button> - `; + <h3>Skalierung anpassen</h3> + <label>Größe: <span id="scale-value">${currentScalePercent.toFixed(0)}%</span> + <input type="range" min="0" max="100" step="1" value="${currentScalePercent}" onchange="updateScale(this.value, ${minScale}, ${maxScale})"></label> + <button onclick="closeDynamicMenu()">Zurück</button> + `; } - function updateScale(value) { + function updateScale(percentValue, minScale, maxScale) { if (selectedPlacedModel) { - const scale = parseFloat(value); + // Berechnung der Skalierung basierend auf dem Prozentwert + const scale = minScale + (percentValue / 100) * (maxScale - minScale); selectedPlacedModel.scale.set(scale, scale, scale); - // Anzeige des aktuellen Wertes aktualisieren + // Anzeige des aktuellen Prozentsatzes im Dynamic-Menü aktualisieren const scaleValueDisplay = document.getElementById("scale-value"); if (scaleValueDisplay) { - scaleValueDisplay.textContent = `${scale.toFixed(2)}`; + scaleValueDisplay.textContent = `${parseInt(percentValue, 10)}%`; } } } + + let moveDelta = 0.1; // Standardwert für die Verschiebungsgröße, kann mit dem Slider geändert werden function openMoveMenu() { if (!selectedPlacedModel) { console.log("Kein Modell ausgewählt. Bitte wählen Sie ein Modell aus, bevor Sie es bewegen."); @@ -399,58 +435,56 @@ const dynamicMenu = document.getElementById("dynamic-menu"); dynamicMenu.style.display = "flex"; + dynamicMenu.innerHTML = ` <h3>Modell bewegen</h3> - <div> - <button onclick="moveModel('x', -0.1)">↠X</button> - <button onclick="moveModel('x', 0.1)">→ X</button> + <div id="position-info"> + <p>Aktuelle Position: X=${selectedPlacedModel.position.x.toFixed(2)}, Z=${selectedPlacedModel.position.z.toFixed(2)}</p> </div> <div> - <button onclick="moveModel('y', -0.1)">↓ Y</button> - <button onclick="moveModel('y', 0.1)">↑ Y</button> + <p>Verschiebungsgröße: <span id="move-delta-display">${moveDelta.toFixed(2)}</span></p> + <input type="range" min="0.01" max="1.0" step="0.01" value="${moveDelta}" + onchange="updateMoveDelta(this.value)"> </div> - <div> - <button onclick="moveModel('z', -0.1)">- Z</button> - <button onclick="moveModel('z', 0.1)">+ Z</button> + <div style="display: flex; flex-direction: column; align-items: center; gap: 10px;"> + <div> + <button onclick="moveModel('x', -moveDelta)">↠X</button> + <button onclick="moveModel('x', moveDelta)">→ X</button> + </div> + <div> + <button onclick="moveModel('z', -moveDelta)">- Z</button> + <button onclick="moveModel('z', moveDelta)">+ Z</button> + </div> </div> <button onclick="closeDynamicMenu()">Zurück</button> `; } - function moveModel(axis, delta) { - if (selectedPlacedModel) { - selectedPlacedModel.position[axis] += delta; + function updateMoveDelta(value) { + moveDelta = parseFloat(value); + const moveDeltaDisplay = document.getElementById("move-delta-display"); + if (moveDeltaDisplay) { + moveDeltaDisplay.textContent = moveDelta.toFixed(2); } } + function moveModel(axis, delta) { + if (selectedPlacedModel) { + selectedPlacedModel.position[axis] += delta; // Bewegung durchführen - function deleteModel() { - if (!selectedPlacedModel) { - console.log("Kein Modell ausgewählt. Bitte wählen Sie ein Modell aus, bevor Sie es löschen."); - return; - } - - // Dialog anzeigen - const deleteDialog = document.getElementById('delete-confirmation-dialog'); - deleteDialog.style.display = 'flex'; - } - - function confirmDelete(shouldDelete) { - const deleteDialog = document.getElementById('delete-confirmation-dialog'); - deleteDialog.style.display = 'none'; - - if (shouldDelete && selectedPlacedModel) { - scene.remove(selectedPlacedModel); - selectedPlacedModel = null; - showMenu('menu-bar'); + // Positionsanzeige im Dynamic-Menü aktualisieren + const positionInfo = document.getElementById("position-info"); + if (positionInfo) { + positionInfo.innerHTML = ` + <p>Aktuelle Position: X=${selectedPlacedModel.position.x.toFixed(2)}, Z=${selectedPlacedModel.position.z.toFixed(2)}</p>`; + } } } - function closeDynamicMenu() { - const dynamicMenu = document.getElementById("dynamic-menu"); - dynamicMenu.style.display = "none"; - } + /* ========================= */ + /* KARTENSTEUERUNG */ + /* ========================= */ function refreshMapDialog() { const mapDialog = document.getElementById('map-dialog'); mapDialog.style.display = 'flex'; @@ -461,6 +495,10 @@ mapDialog.style.display = 'none'; } + + /* ========================= */ + /* AR-HANDLING */ + /* ========================= */ async function activateXR() { const canvas = document.createElement('canvas'); document.body.appendChild(canvas); @@ -498,6 +536,7 @@ currentSession.addEventListener("end", () => { currentSession = null; + document.getElementById("dynamic-menu").style.display = "none"; menus.forEach(id => { document.getElementById(id).style.display = 'none'; }); @@ -537,14 +576,15 @@ } function confirmExit(shouldExit) { - if (shouldExit && currentSession) { - currentSession.end(); - } + if (shouldExit && currentSession) currentSession.end(); document.getElementById('confirmation-dialog').style.display = 'none'; } - let soundTimeout = false; + /* ========================= */ + /* BENUTZERINTERAKTIONEN */ + /* ========================= */ + let soundTimeout = false; function playButtonSound() { if (!soundTimeout) { const sound = document.getElementById("button-sound"); @@ -559,6 +599,9 @@ } + /* ========================= */ + /* DEBUGGING UND START */ + /* ========================= */ if (navigator.xr) { const startButton = document.createElement('button'); startButton.textContent = 'Start AR';