Commit 78b89f59 authored by Alfakhori's avatar Alfakhori
Browse files

v0.3.0

parent 18535aa9
Pipeline #12492 passed with stage
in 28 seconds
# Gengenbach AR migration plan
## Audit baseline (2026-10-06)
Audited `D:\OneDrive\0_Projects\Gengenbach\Gengenbach\public` before application edits.
- The active app consists of `index.html`, `style.css`, and five classic scripts: `ar_start.js`, `ar_main.js`, `ar_overviewmap.js`, `ar_download.js`, and `ar_debug_console.js`.
- The tracked `public/new/` directory is deleted in the current working tree. `public/BK-Working/` is untracked and its seven files are byte-identical to the current active HTML/CSS/scripts. Both states are pre-existing user work and must remain untouched.
- There is no package manifest or bundler in the repository. The GitLab Pages config publishes `public/`; keep browser-native modules and no build system.
- Current runtime: Three.js r126 global scripts plus global `THREE.GLTFLoader`; Leaflet CSS/JS from unpinned unpkg URLs; OSM DE tiles; model URLs point to the existing HFT GitLab raw URLs; the reticle is loaded from the WebXR samples URL.
- `assets/models/` has seven GLTF model directories plus a separate street-lamp GLB, textures, plans, UI images/icons, and button audio. Each of the seven catalog GLTFs contains one skin and one animation; use `SkeletonUtils.clone()` for independent model copies. The catalog exposes seven named models (bench, trash bin, lantern, telephone box, hydrant, statue, fountain). Preserve the unlisted asset too.
- There are two predefined scene JSON files. Both use top-level `lat`, `lng`, and `models`; each model stores `name`, `position`, `rotation`, `scale`, `lat`, and `lng`. The importer identifies models by display name and rebuilds X/Z from geographic coordinates relative to the current GPS position.
- Existing behavior includes the start screen, upload and predefined-scene selection, AR start/exit/reload, hit testing/reticle, model placement/cache/selection/highlight/delete, rotation/scale/move controls, map with OSM tiles, model markers and georeferenced plan overlay, 500 ms map updates, scene download/upload, geolocation, button sounds, and the console/error/rejection debug panel.
- Coordinate conversion uses Earth radius 6,378,137 m and the current local tangent-plane equations. Preserve the equations, origin behavior, names, URLs, model defaults, HTML controls, styling, and scene schema.
- Current public inline/dynamic-handler API: `showMenu`, `selectModel`, `openRotationMenu`, `openScaleMenu`, `openMoveMenu`, `deleteModel`, `completeEditing`, `exitAR`, `downloadScene`, `confirmDelete`, `confirmExit`, `closeInfoDialog`, `closeDynamicMenu`, `updateRotation`, `updateScale`, `updateMoveDelta`, and `moveModelDynamic`. Export and attach only these required functions to `window`.
- Existing XR is a manual WebGL path: an XR-compatible canvas/context, explicit `XRWebGLLayer`, manual single-view camera matrices, and a session-owned `requestAnimationFrame` loop. The UI requests `immersive-ar`, requires `hit-test`, and requests `dom-overlay`. A modern renderer migration must replace these pieces together; retaining the old layer/matrix loop alongside `renderer.xr` would create two incompatible owners. Keep the features and hit-test source, while assigning rendering/camera/frame-loop ownership to Three.js WebXRManager.
- The current miss path leaves the reticle at its previous visible pose. Hide it when a frame has no hit, as required by the supplied migration acceptance criteria.
## Dependency outline
`index.html` loads Leaflet and the app entry point. The app entry imports the debug console, AR controller, start screen, map, and scene I/O. `ar_start.js` calls AR start and scene-file loading. `ar_main.js` owns AR/model/UI state and calls the map. The map reads the AR scene/camera/geolocation and serves coordinate conversion to the AR and scene-I/O code. Scene download reads placed models and map conversions. Keep Leaflet as its existing global dependency, with one Three.js import map shared by every module.
## Migration steps and checkpoints
1. Save this audit/plan before code edits; keep the existing user worktree changes and backup intact.
2. Establish a browser-native ES module entry and one import map pinned to Three.js r186 (`0.186.0`, the latest published stable release found during this audit); import addons with `three/addons/`. Keep Leaflet and its UI unchanged.
3. Convert the active app scripts to modules with explicit imports/exports. Preserve current function behavior and HTML; expose only the listed inline/dynamic handler API on `window`. Load the debug console before app initialization.
4. Replace global GLTFLoader usage with one imported shared loader and preserve model cache and external model/reticle URLs. All seven current catalog assets are skinned and animated, so clone each instance with `SkeletonUtils.clone()`.
5. Migrate XR as one coherent change: `renderer.xr.enabled`, `renderer.xr.setSession(session)`, one `renderer.setAnimationLoop`, frame-scoped hit-test processing from the supplied XRFrame, and Three.js-managed XR camera/rendering. Keep immersive AR, required hit-test, optional DOM overlay, viewer/local reference spaces, reticle placement, pointer selection, session end behavior, and DOM overlay.
6. Wire module dependencies for the start screen, map, geolocation, dialogs, debug console, and scene load/download; keep existing coordinate calculations and serialized JSON fields/values.
7. After each major phase, run targeted static checks for syntax/import resolution, old Three.js references, unresolved HTML/dynamic handler names, duplicate script/loop registration, asset/URL/schema preservation, and coordinate formula changes. Compare the feature/API inventory from this audit against the result.
8. Optimize only after functional parity: reuse frame objects, retain the 500 ms map cadence, and avoid unvalidated rendering changes.
9. Report checks separately from device-only behavior. A real mobile WebXR browser/device is required to claim camera passthrough, hit testing, actual placement/manipulation, Leaflet runtime, download/upload, and session exit verified.
## Guardrails
- Do not edit `public/new/` or `public/BK-Working/`, stage/commit them, or remove either working-tree state.
- Keep asset paths, model URLs, UI, map, equations, and JSON scene structure.
- If implementation requires changing a discovered behavior or schema, stop and explain it before doing so.
- Do not claim mobile WebXR acceptance criteria verified without an available device/browser run.
## Static checkpoint results
- The six active JS files pass Node syntax checks and local import/export resolution checks.
- The local module graph is acyclic. The app has one module entry and one Three.js import map; the active app path contains one `renderer.setAnimationLoop` and no explicit `XRWebGLLayer`, session-owned animation loop, or manual camera matrix overwrite.
- All 17 HTML/dynamically generated function-handler names resolve through the compatibility bridge. `moveDelta` is exposed through a read-only `window` accessor only because generated movement-button handlers refer to it.
- Both predefined JSON scenes parse and retain the expected fields. Original named functions, HTML IDs/inline handlers, model URLs/catalog, and coordinate equations remain present.
- The existing `BK-Working/` snapshot still matches the committed classic-script baseline. Existing `public/new/` deletions and the untracked backup were not changed.
- No real mobile WebXR device/browser was available for runtime validation. Camera passthrough, hit-test behavior, placement/manipulation, map behavior on-device, downloads/uploads, and XR exit remain to be checked manually.
import "./ar_debug_console.js";
import { initializeMainUI, installLegacyUI } from "./ar_main.js";
import { downloadScene } from "./ar_download.js";
import { showStartScreen } from "./ar_start.js";
if (navigator.xr) {
showStartScreen();
} else {
alert("WebXR wird nicht unterstützt.");
}
installLegacyUI(downloadScene);
initializeMainUI();
function downloadScene() { import { getAllPlacedModels, geoLocation, showInfoDialog } from "./ar_main.js";
import { threeToLeaflet } from "./ar_overviewmap.js";
export function downloadScene() {
const placedModels = getAllPlacedModels().map(model => { const placedModels = getAllPlacedModels().map(model => {
const { lat, lng } = threeToLeaflet(model.position.x, model.position.z); const { lat, lng } = threeToLeaflet(model.position.x, model.position.z);
return { return {
...@@ -44,7 +47,7 @@ function downloadScene() { ...@@ -44,7 +47,7 @@ function downloadScene() {
document.body.removeChild(a); document.body.removeChild(a);
} }
function loadSceneFromFile(loadedData) { export function loadSceneFromFile(loadedData) {
const fileInput = document.createElement('input'); const fileInput = document.createElement('input');
fileInput.type = 'file'; fileInput.type = 'file';
fileInput.accept = 'application/json'; fileInput.accept = 'application/json';
......
import * as THREE from "three";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
import { clone as cloneSkinned } from "three/addons/utils/SkeletonUtils.js";
import {
centerOnPlan,
configureMap,
init_map,
leafletToThree,
roundTo,
stopMapUpdates,
} from "./ar_overviewmap.js";
/* ========================= */ /* ========================= */
/* GLOBALE VARIABLEN */ /* SHARED AR STATE */
/* ========================= */ /* ========================= */
let selectedPlacedModel = null; export let selectedPlacedModel = null;
let currentSession = null; export let currentSession = null;
let reticle = null; export let reticle = null;
let scene, camera; export let scene;
let geoLocation; export let camera;
export let geoLocation;
const modelCache = new Map(); const modelCache = new Map();
const gltfLoader = new GLTFLoader();
// PERFORMANCE: Reuse one GLTFLoader instead of creating
// a new loader every time a model is placed.
const gltfLoader = new THREE.GLTFLoader();
const menus = [ const menus = [
'menu-bar', 'menu-bar',
...@@ -35,7 +45,7 @@ menus.forEach(id => { ...@@ -35,7 +45,7 @@ menus.forEach(id => {
/* MODELLE */ /* MODELLE */
/* ========================= */ /* ========================= */
let models = { export let models = {
bench: { bench: {
name: "Bench", name: "Bench",
...@@ -106,8 +116,12 @@ let models = { ...@@ -106,8 +116,12 @@ let models = {
/* INITIALISIERUNG */ /* INITIALISIERUNG */
/* ========================= */ /* ========================= */
window.onload = () => { export function initializeMainUI() {
configureMap({
getCamera: () => camera,
getGeoLocation: () => geoLocation,
getPlacedModels: getAllPlacedModels,
});
initializeAddMenu(); initializeAddMenu();
// Allen Buttons den Sound hinzufügen // Allen Buttons den Sound hinzufügen
...@@ -121,7 +135,7 @@ window.onload = () => { ...@@ -121,7 +135,7 @@ window.onload = () => {
playButtonSound playButtonSound
); );
}); });
}; }
function initializeAddMenu() { function initializeAddMenu() {
...@@ -165,7 +179,7 @@ function initializeAddMenu() { ...@@ -165,7 +179,7 @@ function initializeAddMenu() {
/* MENÜ-STEUERUNG */ /* MENÜ-STEUERUNG */
/* ========================= */ /* ========================= */
function showMenu(menuId) { export function showMenu(menuId) {
const isMapWindow = const isMapWindow =
menuId === 'map-window'; menuId === 'map-window';
...@@ -256,18 +270,7 @@ function closeMap() { ...@@ -256,18 +270,7 @@ function closeMap() {
} }
// Stop the map update interval stopMapUpdates();
if (
typeof intervalId !== 'undefined'
) {
if (intervalId) {
clearInterval(intervalId);
intervalId = null;
}
}
// Clear selected model // Clear selected model
...@@ -275,7 +278,7 @@ function closeMap() { ...@@ -275,7 +278,7 @@ function closeMap() {
} }
function closeDynamicMenu() { export function closeDynamicMenu() {
const dynamicMenu = const dynamicMenu =
document.getElementById( document.getElementById(
...@@ -290,7 +293,7 @@ function closeDynamicMenu() { ...@@ -290,7 +293,7 @@ function closeDynamicMenu() {
} }
function showInfoDialog(message) { export function showInfoDialog(message) {
const infoDialog = const infoDialog =
document.getElementById( document.getElementById(
...@@ -310,7 +313,7 @@ function showInfoDialog(message) { ...@@ -310,7 +313,7 @@ function showInfoDialog(message) {
} }
function closeInfoDialog() { export function closeInfoDialog() {
const infoDialog = const infoDialog =
document.getElementById( document.getElementById(
...@@ -326,7 +329,7 @@ function closeInfoDialog() { ...@@ -326,7 +329,7 @@ function closeInfoDialog() {
/* MODELL-HANDLING */ /* MODELL-HANDLING */
/* ========================= */ /* ========================= */
function getAllPlacedModels() { export function getAllPlacedModels() {
return scene.children.filter( return scene.children.filter(
child => child =>
...@@ -376,7 +379,7 @@ function placeModel( ...@@ -376,7 +379,7 @@ function placeModel(
modelCache.get(filePath); modelCache.get(filePath);
const model = const model =
cachedModel.clone(true); cloneSkinned(cachedModel);
if ( if (
...@@ -425,7 +428,7 @@ function placeModel( ...@@ -425,7 +428,7 @@ function placeModel(
); );
const model = const model =
gltf.scene.clone(true); cloneSkinned(gltf.scene);
if ( if (
...@@ -473,7 +476,7 @@ function placeModel( ...@@ -473,7 +476,7 @@ function placeModel(
} }
function selectModel(modelId) { export function selectModel(modelId) {
const model = const model =
models[modelId]; models[modelId];
...@@ -659,7 +662,7 @@ function removeHighlightFromAllModels() { ...@@ -659,7 +662,7 @@ function removeHighlightFromAllModels() {
} }
function deleteModel() { export function deleteModel() {
if (!selectedPlacedModel) { if (!selectedPlacedModel) {
...@@ -682,7 +685,7 @@ function deleteModel() { ...@@ -682,7 +685,7 @@ function deleteModel() {
} }
function confirmDelete( export function confirmDelete(
shouldDelete shouldDelete
) { ) {
...@@ -715,7 +718,7 @@ function confirmDelete( ...@@ -715,7 +718,7 @@ function confirmDelete(
} }
function completeEditing() { export function completeEditing() {
removeHighlightFromSelectedModel(); removeHighlightFromSelectedModel();
...@@ -740,7 +743,7 @@ function completeEditing() { ...@@ -740,7 +743,7 @@ function completeEditing() {
/* BEARBEITUNGS-MENÜS */ /* BEARBEITUNGS-MENÜS */
/* ========================= */ /* ========================= */
function openRotationMenu() { export function openRotationMenu() {
if (!selectedPlacedModel) { if (!selectedPlacedModel) {
...@@ -796,7 +799,7 @@ function openRotationMenu() { ...@@ -796,7 +799,7 @@ function openRotationMenu() {
} }
function updateRotation( export function updateRotation(
axis, axis,
value value
) { ) {
...@@ -827,7 +830,7 @@ function updateRotation( ...@@ -827,7 +830,7 @@ function updateRotation(
} }
function openScaleMenu() { export function openScaleMenu() {
if (!selectedPlacedModel) { if (!selectedPlacedModel) {
...@@ -904,7 +907,7 @@ function openScaleMenu() { ...@@ -904,7 +907,7 @@ function openScaleMenu() {
} }
function updateScale( export function updateScale(
percentValue, percentValue,
minScale, minScale,
maxScale maxScale
...@@ -943,7 +946,7 @@ function updateScale( ...@@ -943,7 +946,7 @@ function updateScale(
let moveDelta = 0.1; let moveDelta = 0.1;
function openMoveMenu() { export function openMoveMenu() {
if (!selectedPlacedModel) { if (!selectedPlacedModel) {
...@@ -1045,7 +1048,7 @@ function openMoveMenu() { ...@@ -1045,7 +1048,7 @@ function openMoveMenu() {
} }
function updateMoveDelta( export function updateMoveDelta(
value value
) { ) {
...@@ -1067,7 +1070,7 @@ function updateMoveDelta( ...@@ -1067,7 +1070,7 @@ function updateMoveDelta(
} }
function moveModelDynamic( export function moveModelDynamic(
axis, axis,
value value
) { ) {
...@@ -1112,172 +1115,127 @@ function closeMapDialog() { ...@@ -1112,172 +1115,127 @@ function closeMapDialog() {
/* AR-HANDLING */ /* AR-HANDLING */
/* ========================= */ /* ========================= */
async function activateXR(sceneData = null) { export async function activateXR(sceneData = null) {
const canvas = document.createElement("canvas"); const canvas = document.createElement("canvas");
document.body.appendChild(canvas);
const gl = canvas.getContext("webgl", {
xrCompatible: true,
});
const renderer = new THREE.WebGLRenderer({ const renderer = new THREE.WebGLRenderer({
alpha: true, alpha: true,
antialias: true,
canvas, canvas,
context: gl,
}); });
renderer.autoClear = false; document.body.appendChild(canvas);
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(); renderer.setSize(window.innerWidth, window.innerHeight);
renderer.xr.enabled = true;
renderer.xr.setReferenceSpaceType("local");
camera.matrixAutoUpdate = false; scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(
70,
window.innerWidth / window.innerHeight,
0.01,
20,
);
// ========================================================
// AR lighting // AR lighting
// ========================================================
// Soft overall illumination
const ambientLight = new THREE.AmbientLight(0xffffff, 2.5); const ambientLight = new THREE.AmbientLight(0xffffff, 2.5);
scene.add(ambientLight); scene.add(ambientLight);
// Sky/ground illumination
const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x888888, 1.5); const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x888888, 1.5);
scene.add(hemisphereLight); scene.add(hemisphereLight);
// Directional light for shadows and form
const directionalLight = new THREE.DirectionalLight(0xffffff, 1.5); const directionalLight = new THREE.DirectionalLight(0xffffff, 1.5);
directionalLight.position.set(10, 20, 10); directionalLight.position.set(10, 20, 10);
scene.add(directionalLight); scene.add(directionalLight);
// ========================================================
// Reticle // Reticle
// ======================================================== gltfLoader.load(
const loader = new THREE.GLTFLoader();
loader.load(
"https://immersive-web.github.io/webxr-samples/media/gltf/reticle/reticle.gltf", "https://immersive-web.github.io/webxr-samples/media/gltf/reticle/reticle.gltf",
(gltf) => { (gltf) => {
reticle = gltf.scene; reticle = gltf.scene;
reticle.visible = false; reticle.visible = false;
scene.add(reticle); scene.add(reticle);
}, },
undefined,
(error) => {
console.error("[GLTF] Failed to load reticle:", error);
},
); );
// ======================================================== try {
// Start WebXR AR session
// ========================================================
currentSession = await navigator.xr.requestSession("immersive-ar", { currentSession = await navigator.xr.requestSession("immersive-ar", {
requiredFeatures: ["hit-test"],
optionalFeatures: ["dom-overlay"], optionalFeatures: ["dom-overlay"],
domOverlay: { domOverlay: {
root: document.body, root: document.body,
}, },
requiredFeatures: ["hit-test"],
}); });
currentSession.updateRenderState({ await renderer.xr.setSession(currentSession);
baseLayer: new XRWebGLLayer(currentSession, gl), } catch (error) {
}); console.error("[XR] Failed to start session:", error);
renderer.dispose();
canvas.remove();
currentSession = null;
showInfoDialog("Die AR-Session konnte nicht gestartet werden.");
return;
}
const referenceSpace = await currentSession.requestReferenceSpace("local"); const referenceSpace = await currentSession.requestReferenceSpace("local");
const viewerSpace = await currentSession.requestReferenceSpace("viewer"); const viewerSpace = await currentSession.requestReferenceSpace("viewer");
const hitTestSource = await currentSession.requestHitTestSource({ const hitTestSource = await currentSession.requestHitTestSource({
space: viewerSpace, space: viewerSpace,
}); });
// ========================================================
// Show main menu
// ========================================================
document.getElementById("menu-bar").style.display = "flex"; document.getElementById("menu-bar").style.display = "flex";
// ========================================================
// AR session ended
// Reload the complete website
// ========================================================
currentSession.addEventListener("end", () => { currentSession.addEventListener("end", () => {
console.log("AR session ended"); console.log("AR session ended");
// Completely reload the website.
// This resets the AR canvas, menus, scene, map,
// variables, and all other application state.
window.location.reload(); window.location.reload();
}); });
// ========================================================
// Select objects in AR
// ========================================================
canvas.addEventListener("pointerdown", selectModelFromScene); canvas.addEventListener("pointerdown", selectModelFromScene);
// ========================================================
// Geolocation
// ========================================================
if (navigator.geolocation) { if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition( navigator.geolocation.getCurrentPosition(
(position) => { (position) => {
geoLocation = { geoLocation = {
latitude: roundTo(position.coords.latitude, 5), latitude: roundTo(position.coords.latitude, 5),
longitude: roundTo(position.coords.longitude, 5), longitude: roundTo(position.coords.longitude, 5),
}; };
console.log("GeoLocation: " + JSON.stringify(geoLocation)); console.log("GeoLocation: " + JSON.stringify(geoLocation));
// =================================================
// Load saved scene models
// =================================================
if (sceneData) { if (sceneData) {
sceneData.models.forEach((model) => { sceneData.models.forEach((model) => {
if (model.name) { if (model.name) {
const modelDefinition = Object.values(models).find( const modelDefinition = Object.values(models).find(
(m) => model.name === m.name, (candidate) => model.name === candidate.name,
); );
if (!modelDefinition) { if (!modelDefinition) {
console.error("Modell nicht gefunden:", model.name); console.error("[Scene] Model not found:", model.name);
return; return;
} }
const filePath = modelDefinition.file; const filePath = modelDefinition.file;
const { x, z } = leafletToThree(model.lat, model.lng); const { x, z } = leafletToThree(model.lat, model.lng);
const positionVector = new THREE.Vector3(
const positionVector = new THREE.Vector3(x, model.position.y, z); x,
model.position.y,
z,
);
placeModel(filePath, positionVector, (placed) => { placeModel(filePath, positionVector, (placed) => {
if (model.rotation) { if (model.rotation) {
placed.rotation._x = model.rotation._x; placed.rotation._x = model.rotation._x;
placed.rotation._y = model.rotation._y; placed.rotation._y = model.rotation._y;
placed.rotation._z = model.rotation._z; placed.rotation._z = model.rotation._z;
} }
if (model.scale) { if (model.scale) {
placed.scale.x = model.scale.x; placed.scale.x = model.scale.x;
placed.scale.y = model.scale.y; placed.scale.y = model.scale.y;
placed.scale.z = model.scale.z; placed.scale.z = model.scale.z;
} }
}); });
...@@ -1285,14 +1243,9 @@ async function activateXR(sceneData = null) { ...@@ -1285,14 +1243,9 @@ async function activateXR(sceneData = null) {
}); });
} }
}, },
(error) => { (error) => {
console.error( console.error("[Geo] Failed to get location:", error);
"Fehler bei der Geolokalisierung:",
JSON.stringify(error),
);
}, },
{ {
enableHighAccuracy: true, enableHighAccuracy: true,
maximumAge: 2000, maximumAge: 2000,
...@@ -1300,73 +1253,46 @@ async function activateXR(sceneData = null) { ...@@ -1300,73 +1253,46 @@ async function activateXR(sceneData = null) {
}, },
); );
} }
// ========================================================
// XR render loop
// ========================================================
currentSession.requestAnimationFrame(function onXRFrame(time, frame) {
currentSession.requestAnimationFrame(onXRFrame);
gl.bindFramebuffer(
gl.FRAMEBUFFER,
currentSession.renderState.baseLayer.framebuffer,
);
renderer.setAnimationLoop((time, frame) => {
if (frame) {
const pose = frame.getViewerPose(referenceSpace); const pose = frame.getViewerPose(referenceSpace);
if (pose) { 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);
// =================================================
// Hit testing
// =================================================
const hitTestResults = frame.getHitTestResults(hitTestSource); const hitTestResults = frame.getHitTestResults(hitTestSource);
if (hitTestResults.length > 0) { if (hitTestResults.length > 0) {
const hitPose = hitTestResults[0].getPose(referenceSpace); const hitPose = hitTestResults[0].getPose(referenceSpace);
if (reticle && hitPose) {
reticle.visible = true; reticle.visible = true;
reticle.position.set( reticle.position.set(
hitPose.transform.position.x, hitPose.transform.position.x,
hitPose.transform.position.y, hitPose.transform.position.y,
hitPose.transform.position.z, hitPose.transform.position.z,
); );
reticle.updateMatrixWorld(true); reticle.updateMatrixWorld(true);
} }
} else if (reticle) {
// ================================================= reticle.visible = false;
// Render AR scene }
// ================================================= } else if (reticle) {
reticle.visible = false;
}
}
renderer.render(scene, camera); renderer.render(scene, camera);
}
}); });
} }
/* ========================= */ /* ========================= */
/* AR EXIT */ /* AR EXIT */
/* ========================= */ /* ========================= */
function exitAR() { export function exitAR() {
document.getElementById("confirmation-dialog").style.display = "flex"; document.getElementById("confirmation-dialog").style.display = "flex";
} }
function confirmExit(shouldExit) { export function confirmExit(shouldExit) {
if (shouldExit && currentSession) { if (shouldExit && currentSession) {
currentSession.end(); currentSession.end();
} }
...@@ -1374,6 +1300,35 @@ function confirmExit(shouldExit) { ...@@ -1374,6 +1300,35 @@ function confirmExit(shouldExit) {
document.getElementById("confirmation-dialog").style.display = "none"; document.getElementById("confirmation-dialog").style.display = "none";
} }
export function installLegacyUI(downloadScene) {
Object.assign(window, {
showMenu,
selectModel,
showInfoDialog,
openRotationMenu,
openScaleMenu,
openMoveMenu,
deleteModel,
completeEditing,
exitAR,
downloadScene,
confirmDelete,
confirmExit,
closeInfoDialog,
closeDynamicMenu,
updateRotation,
updateScale,
updateMoveDelta,
moveModelDynamic,
});
Object.defineProperty(window, "moveDelta", {
configurable: true,
get: () => moveDelta,
});
}
/* ========================= */ /* ========================= */
/* BENUTZERINTERAKTIONEN */ /* BENUTZERINTERAKTIONEN */
/* ========================= */ /* ========================= */
......
import * as THREE from "three";
let init = false; let init = false;
let mymap; let mymap;
let userIcon; let userIcon;
let intervalId; let intervalId;
let modeMarkers = []; let modeMarkers = [];
let getCamera = () => null;
let getGeoLocation = () => null;
let getPlacedModels = () => [];
// PERFORMANCE: Reuse one Vector3 instead of allocating a new // PERFORMANCE: Reuse one Vector3 instead of allocating a new
// Vector3 every time updateMap() runs. // Vector3 every time updateMap() runs.
...@@ -42,12 +47,19 @@ const planBounds = [ ...@@ -42,12 +47,19 @@ const planBounds = [
const earthRadius = 6378137; const earthRadius = 6378137;
export function configureMap(dependencies) {
getCamera = dependencies.getCamera;
getGeoLocation = dependencies.getGeoLocation;
getPlacedModels = dependencies.getPlacedModels;
}
// ============================================================ // ============================================================
// Initialize map // Initialize map
// ============================================================ // ============================================================
function init_map() { export function init_map() {
const mapContainer = document.getElementById("map-container"); const mapContainer = document.getElementById("map-container");
const geoLocation = getGeoLocation();
if (mapContainer) { if (mapContainer) {
if (!init) { if (!init) {
...@@ -98,7 +110,7 @@ function init_map() { ...@@ -98,7 +110,7 @@ function init_map() {
// Add currently placed models to map // Add currently placed models to map
// ======================================================== // ========================================================
const models = getAllPlacedModels(); const models = getPlacedModels();
models.forEach((model) => { models.forEach((model) => {
const { lat, lng } = threeToLeaflet(model.position.x, model.position.z); const { lat, lng } = threeToLeaflet(model.position.x, model.position.z);
...@@ -142,11 +154,18 @@ function init_map() { ...@@ -142,11 +154,18 @@ function init_map() {
} }
} }
export function stopMapUpdates() {
if (intervalId) {
clearInterval(intervalId);
intervalId = null;
}
}
// ============================================================ // ============================================================
// Center map on proposed plan // Center map on proposed plan
// ============================================================ // ============================================================
function centerOnPlan() { export function centerOnPlan() {
if (!mymap) { if (!mymap) {
console.error("Map ist noch nicht initialisiert."); console.error("Map ist noch nicht initialisiert.");
...@@ -164,7 +183,7 @@ function centerOnPlan() { ...@@ -164,7 +183,7 @@ function centerOnPlan() {
// Add georeferenced proposed plan // Add georeferenced proposed plan
// ============================================================ // ============================================================
function addPlanOverlay() { export function addPlanOverlay() {
if (!mymap) { if (!mymap) {
console.error("Map ist noch nicht initialisiert."); console.error("Map ist noch nicht initialisiert.");
...@@ -193,7 +212,7 @@ function addPlanOverlay() { ...@@ -193,7 +212,7 @@ function addPlanOverlay() {
// Update map // Update map
// ============================================================ // ============================================================
function updateMap() { export function updateMap() {
// The interval is normally stopped by closeMap(). // The interval is normally stopped by closeMap().
// This guard also handles cases where the map is hidden // This guard also handles cases where the map is hidden
// by another UI action. // by another UI action.
...@@ -205,6 +224,8 @@ function updateMap() { ...@@ -205,6 +224,8 @@ function updateMap() {
return; return;
} }
const camera = getCamera();
if (!camera || !userIcon) { if (!camera || !userIcon) {
return; return;
} }
...@@ -234,7 +255,9 @@ function updateMap() { ...@@ -234,7 +255,9 @@ function updateMap() {
// Convert Three.js coordinates to Leaflet coordinates // Convert Three.js coordinates to Leaflet coordinates
// ============================================================ // ============================================================
function threeToLeaflet(threeX, threeZ) { export function threeToLeaflet(threeX, threeZ) {
const geoLocation = getGeoLocation();
if (!geoLocation) { if (!geoLocation) {
console.error("GeoLocation ist noch nicht verfügbar"); console.error("GeoLocation ist noch nicht verfügbar");
...@@ -265,7 +288,9 @@ function threeToLeaflet(threeX, threeZ) { ...@@ -265,7 +288,9 @@ function threeToLeaflet(threeX, threeZ) {
// Convert Leaflet coordinates to Three.js coordinates // Convert Leaflet coordinates to Three.js coordinates
// ============================================================ // ============================================================
function leafletToThree(lat, lng) { export function leafletToThree(lat, lng) {
const geoLocation = getGeoLocation();
if (!geoLocation) { if (!geoLocation) {
console.error("GeoLocation ist noch nicht verfügbar"); console.error("GeoLocation ist noch nicht verfügbar");
...@@ -298,7 +323,7 @@ function leafletToThree(lat, lng) { ...@@ -298,7 +323,7 @@ function leafletToThree(lat, lng) {
// Round number // Round number
// ============================================================ // ============================================================
function roundTo(n, digits) { export function roundTo(n, digits) {
var negative = false; var negative = false;
if (digits === undefined) { if (digits === undefined) {
......
import { activateXR } from "./ar_main.js";
import { loadSceneFromFile } from "./ar_download.js";
/* ========================= */ /* ========================= */
/* DEBUGGING UND START */ /* DEBUGGING UND START */
/* ========================= */ /* ========================= */
...@@ -17,7 +20,7 @@ let predefinedButton2 = null; ...@@ -17,7 +20,7 @@ let predefinedButton2 = null;
/* PREDEFINED SCENE */ /* PREDEFINED SCENE */
/* ========================= */ /* ========================= */
async function loadPredefinedScene(filePath, fileName) { export async function loadPredefinedScene(filePath, fileName) {
try { try {
const response = await fetch(filePath); const response = await fetch(filePath);
...@@ -44,7 +47,7 @@ async function loadPredefinedScene(filePath, fileName) { ...@@ -44,7 +47,7 @@ async function loadPredefinedScene(filePath, fileName) {
/* Create the Start Screen */ /* Create the Start Screen */
/* ============================================================ */ /* ============================================================ */
function showStartScreen() { export function showStartScreen() {
// Prevent duplicate start screens // Prevent duplicate start screens
if ( if (
startScreen || startScreen ||
...@@ -262,12 +265,3 @@ function showStartScreen() { ...@@ -262,12 +265,3 @@ function showStartScreen() {
}; };
} }
/* ============================================================ */
/* INITIAL START */
/* ============================================================ */
if (navigator.xr) {
showStartScreen();
} else {
alert("WebXR wird nicht unterstützt.");
}
...@@ -9,8 +9,14 @@ ...@@ -9,8 +9,14 @@
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<script src="https://unpkg.com/three@0.126.0/build/three.js"></script> <script type="importmap">
<script src="https://unpkg.com/three@0.126.0/examples/js/loaders/GLTFLoader.js"></script> {
"imports": {
"three": "https://unpkg.com/three@0.186.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.186.0/examples/jsm/"
}
}
</script>
</head> </head>
<body> <body>
...@@ -299,7 +305,7 @@ ...@@ -299,7 +305,7 @@
============================================================ --> ============================================================ -->
<div id="app-version"> <div id="app-version">
v0.2.0 v0.3.0
</div> </div>
...@@ -307,21 +313,13 @@ ...@@ -307,21 +313,13 @@
JAVASCRIPT JAVASCRIPT
============================================================ --> ============================================================ -->
<script src="ar_start.js"></script>
<script src="ar_main.js"></script>
<script src="ar_debug_console.js"></script>
<link <link
rel="stylesheet" rel="stylesheet"
href="https://unpkg.com/leaflet/dist/leaflet.css" /> href="https://unpkg.com/leaflet/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script> <script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
<script src="ar_overviewmap.js"></script> <script type="module" src="ar_app.js"></script>
<script src="ar_download.js"></script>
<!-- ============================================================ <!-- ============================================================
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment