An error occurred while loading the file. Please try again.
index.html 10.33 KiB
<!doctype html>
<html lang="de">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>GeoVis AR Projekt</title>
  <style>
    body {
      margin: 0;
      font-family: Arial, sans-serif;
      background-color: #f0f0f0;
      color: #333;
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100vh;
      background-image: url('https://www.hft-stuttgart.de/fileadmin/Dateien/Hochschule/-_R_Juergen_Pollak_HFT_18.04.18-0091.jpg');
      background-size: cover;
      background-position: center;
    .container {
      text-align: center;
      background-color: rgba(0, 0, 0, 0.5);
      padding: 50px;
      border-radius: 10px;
      color: white;
      max-width: 80%;
      box-shadow: 0px 0px 15px rgba(0, 0, 0, 0.5);
    h1 {
      font-size: 3em;
      margin-bottom: 20px;
    p {
      font-size: 1.2em;
      margin-bottom: 30px;
    #menu-bar {
      position: absolute;
      bottom: 0;
      width: 100%;
      background: rgba(0, 0, 0, 0.4);
      color: white;
      display: none;
      justify-content: space-around;
      padding: 10px;
      z-index: 10;
    .menu-item {
      cursor: pointer;
      padding: 10px 20px;
      background: rgba(255, 255, 255, 0.1);
      border-radius: 5px;
      transition: background-color 0.3s;
    .menu-item:hover {
      background: rgba(255, 255, 255, 0.3);
    .menu-item.active {
      background: rgba(255, 255, 255, 0.5);
      font-weight: bold;
button { background-color: #4CAF50; color: white; font-size: 1.5em; padding: 15px 30px; border: none; border-radius: 5px; cursor: pointer; transition: background-color 0.3s; margin: 10px; } button:hover { background-color: #45a049; } button:active { background-color: #387a39; } #confirmation-dialog { position: fixed; top: 0; left: 0; width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; z-index: 20; } .dialog-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); } .dialog-box { position: relative; background: white; padding: 20px; border-radius: 8px; text-align: center; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2); } .dialog-box button { margin: 5px; padding: 10px 20px; font-size: 16px; border: none; border-radius: 5px; cursor: pointer; } .dialog-box button:first-child { background-color: #e74c3c; color: white; } .dialog-box button:last-child { background-color: #3498db; color: white; } </style>
<script src="https://unpkg.com/three@0.126.0/build/three.js"></script> <script src="https://unpkg.com/three@0.126.0/examples/js/loaders/GLTFLoader.js"></script> </head> <body> <div id="menu-bar"> <div class="menu-item active" id="robot-item" onclick="selectModel('robot')"> <img src="previewImages/robot.png" alt="Roboter" style="width: 50px; height: 50px;"> </div> <div class="menu-item" id="sunflower-item" onclick="selectModel('sunflower')"> <img src="previewImages/sunflower.png" alt="Sonnenblume" style="width: 50px; height: 50px;"> </div> <div class="menu-item" id="lantern-item" onclick="selectModel('tree')"> <img src="previewImages/tree.png" alt="Baum" style="width: 50px; height: 50px;"> </div> <div class="menu-item" id="exit-item" onclick="exitAR()"> <img src="previewImages/exit.png" alt="Exit" style="width: 50px; height: 50px;"> </div> </div> <div id="confirmation-dialog" style="display: none;"> <div class="dialog-overlay"></div> <div class="dialog-box"> <p>Möchten Sie die AR-Session wirklich verlassen?</p> <button onclick="confirmExit(true)">Ja</button> <button onclick="confirmExit(false)">Nein</button> </div> </div> <script> let selectedModel = 'robot'; // Start mit dem Roboter let models = {}; let reticle; let lastClickTime = 0; // Zeit des letzten Klicks const doubleClickThreshold = 300; // Zeitspanne für Doppelklick in Millisekunden let placedModel = null; let currentSession = null; // AR-Sitzung function updateMenu() { document.querySelectorAll('.menu-item').forEach(item => item.classList.remove('active')); document.getElementById(`${selectedModel}-item`).classList.add('active'); } function selectModel(modelId) { selectedModel = modelId; console.log(`Modell ausgewählt: ${selectedModel}`); updateMenu(); } async function activateXR() { const canvas = document.createElement('canvas'); document.body.appendChild(canvas); const gl = canvas.getContext('webgl', { xrCompatible: true }); const renderer = new THREE.WebGLRenderer({ alpha: true, canvas, context: gl }); renderer.autoClear = false; const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(); camera.matrixAutoUpdate = false; // Licht hinzufügen const light = new THREE.DirectionalLight(0xffffff, 1); light.position.set(10, 10, 10); scene.add(light); // Reticle (Cursor) const loader = new THREE.GLTFLoader(); loader.load("https://immersive-web.github.io/webxr-samples/media/gltf/reticle/reticle.gltf", (gltf) => { reticle = gltf.scene; reticle.visible = false;
scene.add(reticle); }); // Modelle laden loader.load("https://threejs.org/examples/models/gltf/RobotExpressive/RobotExpressive.glb", (gltf) => { models.robot = gltf.scene; }); //https://immersive-web.github.io/webxr-samples/media/gltf/sunflower/sunflower.gltf loader.load("https://transfer.hft-stuttgart.de/gitlab/geovistoogsi/ar/-/raw/master/public/assets/models/trash_model/scene.gltf", (gltf) => { models.sunflower = gltf.scene; models.sunflower.scale.set(0.07, 0.07, 0.07); }); loader.load("https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Assets/refs/heads/main/Models/Lantern/glTF/Lantern.gltf", (gltf) => { models.tree = gltf.scene; models.tree.scale.set(0.1, 0.1, 0.1); // Skaliert das Modell auf 50% der Originalgröße }); // AR-Session starten currentSession = await navigator.xr.requestSession('immersive-ar', { optionalFeatures: ["dom-overlay"], domOverlay: { root: document.body }, requiredFeatures: ['hit-test'] }); currentSession.updateRenderState({ baseLayer: new XRWebGLLayer(currentSession, gl) }); const referenceSpace = await currentSession.requestReferenceSpace('local'); const viewerSpace = await currentSession.requestReferenceSpace('viewer'); const hitTestSource = await currentSession.requestHitTestSource({ space: viewerSpace }); // Menü sichtbar machen document.getElementById('menu-bar').style.display = 'flex'; currentSession.requestAnimationFrame(onXRFrame); currentSession.addEventListener("select", (event) => { if (!reticle) return; // Prüfen, ob es ein Doppelklick ist const currentTime = Date.now(); const timeDiff = currentTime - lastClickTime; if (timeDiff < doubleClickThreshold) { // Doppelklick erkannt, Modell wechseln selectedModel = selectedModel === 'robot' ? 'sunflower' : selectedModel === 'sunflower' ? 'tree' : 'robot'; console.log(`${selectedModel.charAt(0).toUpperCase() + selectedModel.slice(1)} ausgewählt`); updateMenu(); } else { // Einzelner Klick – Modell platzieren if (models[selectedModel]) { const clone = models[selectedModel].clone(); clone.position.copy(reticle.position); scene.add(clone); placedModel = clone; console.log(`${selectedModel} platziert`); } } // Zeit des letzten Klicks aktualisieren lastClickTime = currentTime; }); currentSession.addEventListener("end", () => { console.log("AR-Session beendet"); currentSession = null; document.getElementById('menu-bar').style.display = 'none'; // Menü ausblenden }); // AR-Rendering function onXRFrame(time, frame) { currentSession.requestAnimationFrame(onXRFrame);
gl.bindFramebuffer(gl.FRAMEBUFFER, currentSession.renderState.baseLayer.framebuffer); const pose = frame.getViewerPose(referenceSpace); if (pose) { const view = pose.views[0]; const viewport = currentSession.renderState.baseLayer.getViewport(view); renderer.setSize(viewport.width, viewport.height); camera.matrix.fromArray(view.transform.matrix); camera.projectionMatrix.fromArray(view.projectionMatrix); camera.updateMatrixWorld(true); // Reticle Position const hitTestResults = frame.getHitTestResults(hitTestSource); if (hitTestResults.length > 0) { const hitPose = hitTestResults[0].getPose(referenceSpace); reticle.visible = true; reticle.position.set(hitPose.transform.position.x, hitPose.transform.position.y, hitPose.transform.position.z); reticle.updateMatrixWorld(true); } renderer.render(scene, camera); } } } function exitAR() { document.getElementById('confirmation-dialog').style.display = 'flex'; // Dialog anzeigen } function confirmExit(shouldExit) { if (shouldExit) { if (currentSession) { currentSession.end(); // Beende die AR-Session } } document.getElementById('confirmation-dialog').style.display = 'none'; // Dialog ausblenden } // AR starten if (navigator.xr) { const startButton = document.createElement('button'); startButton.textContent = 'Start AR'; startButton.style.cssText = "position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); padding: 15px; font-size: 18px;"; document.body.appendChild(startButton); startButton.onclick = () => { startButton.remove(); activateXR(); }; } else { alert('WebXR wird nicht unterstützt.'); } </script> </body> </html>