Commit 499216b7 authored by Alfakhori's avatar Alfakhori
Browse files

three v186

parent ebd6d6fc
Pipeline #12484 passed with stage
in 26 seconds
function downloadScene() { function downloadScene() {
const placedModels = getAllPlacedModels().map(model => { const placedModels = window.getAllPlacedModels().map((model) => {
const { lat, lng } = threeToLeaflet(model.position.x, model.position.z); const { lat, lng } = window.threeToLeaflet(
model.position.x,
model.position.z,
);
return { return {
name: model.modelConfig.name, name: model.modelConfig.name,
position: model.position, position: model.position,
...@@ -8,10 +11,10 @@ function downloadScene() { ...@@ -8,10 +11,10 @@ function downloadScene() {
scale: model.scale, scale: model.scale,
lat: lat, lat: lat,
lng: lng, lng: lng,
} };
}); });
console.log("Modelle: " + placedModels.length) console.log("Modelle: " + placedModels.length);
if (placedModels.length === 0) { if (placedModels.length === 0) {
showInfoDialog("Szene ist leer"); showInfoDialog("Szene ist leer");
return; return;
...@@ -27,7 +30,7 @@ function downloadScene() { ...@@ -27,7 +30,7 @@ function downloadScene() {
lat: geoLocation.latitude, lat: geoLocation.latitude,
lng: geoLocation.longitude, lng: geoLocation.longitude,
models: placedModels, models: placedModels,
} };
const sceneData = JSON.stringify(data, null, 1); const sceneData = JSON.stringify(data, null, 1);
const blob = new Blob([sceneData], { type: "application/json" }); const blob = new Blob([sceneData], { type: "application/json" });
...@@ -45,20 +48,20 @@ function downloadScene() { ...@@ -45,20 +48,20 @@ function downloadScene() {
} }
function loadSceneFromFile(loadedData) { 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";
fileInput.addEventListener('change', function(event) { fileInput.addEventListener("change", function (event) {
const file = event.target.files[0]; const file = event.target.files[0];
const fileName = file ? file.name : "Keine Datei ausgewählt" const fileName = file ? file.name : "Keine Datei ausgewählt";
if (!file) { if (!file) {
console.log("Keine Datei ausgewählt"); console.log("Keine Datei ausgewählt");
return; return;
} }
const reader = new FileReader(); const reader = new FileReader();
reader.onload = function(e) { reader.onload = function (e) {
try { try {
const sceneData = JSON.parse(e.target.result); const sceneData = JSON.parse(e.target.result);
loadedData(sceneData, fileName); loadedData(sceneData, fileName);
...@@ -71,3 +74,7 @@ function loadSceneFromFile(loadedData) { ...@@ -71,3 +74,7 @@ function loadSceneFromFile(loadedData) {
}); });
fileInput.click(); fileInput.click();
} }
/* Expose functions used by the existing HTML and ar_start.js. */
window.downloadScene = downloadScene;
window.loadSceneFromFile = loadSceneFromFile;
import * as THREE from "three";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
/* ========================= */ /* ========================= */
/* GLOBALE VARIABLEN */ /* GLOBALE VARIABLEN */
/* ========================= */ /* ========================= */
...@@ -6,44 +9,43 @@ let selectedPlacedModel = null; ...@@ -6,44 +9,43 @@ let selectedPlacedModel = null;
let currentSession = null; let currentSession = null;
let reticle = null; let reticle = null;
let scene, camera; let scene, camera;
let xrRenderer = null;
let geoLocation; let geoLocation;
const modelCache = new Map(); const modelCache = new Map();
// PERFORMANCE: Reuse one GLTFLoader instead of creating // PERFORMANCE: Reuse one GLTFLoader instead of creating
// a new loader every time a model is placed. // a new loader every time a model is placed.
const gltfLoader = new THREE.GLTFLoader(); const gltfLoader = new GLTFLoader();
const menus = [ const menus = [
'menu-bar', "menu-bar",
'add-menu', "add-menu",
'edit-menu', "edit-menu",
'options-menu', "options-menu",
'map-window' "map-window",
]; ];
// PERFORMANCE: Cache menu DOM elements once instead of // PERFORMANCE: Cache menu DOM elements once instead of
// repeatedly calling document.getElementById() in showMenu(). // repeatedly calling document.getElementById() in showMenu().
const menuElements = {}; const menuElements = {};
menus.forEach(id => { menus.forEach((id) => {
menuElements[id] = document.getElementById(id); menuElements[id] = document.getElementById(id);
}); });
/* ========================= */ /* ========================= */
/* MODELLE */ /* MODELLE */
/* ========================= */ /* ========================= */
let models = { let models = {
bench: { bench: {
name: "Bench", name: "Bench",
image: "assets/previewImages/bench.PNG", image: "assets/previewImages/bench.PNG",
file: "https://transfer.hft-stuttgart.de/gitlab/geovistoogsi/ar/-/raw/master/public/assets/models/bench_model/scene.gltf", 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 }, scale: { x: 0.1, y: 0.1, z: 0.1 },
minScale: 0.05, minScale: 0.05,
maxScale: 0.5 maxScale: 0.5,
}, },
trashbin: { trashbin: {
...@@ -52,7 +54,7 @@ let models = { ...@@ -52,7 +54,7 @@ let models = {
file: "https://transfer.hft-stuttgart.de/gitlab/geovistoogsi/ar/-/raw/master/public/assets/models/trash_model/scene.gltf", 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 }, scale: { x: 0.03, y: 0.03, z: 0.03 },
minScale: 0.01, minScale: 0.01,
maxScale: 0.1 maxScale: 0.1,
}, },
lantern: { lantern: {
...@@ -61,7 +63,7 @@ let models = { ...@@ -61,7 +63,7 @@ let models = {
file: "https://transfer.hft-stuttgart.de/gitlab/geovistoogsi/ar/-/raw/master/public/assets/models/park_light_model/scene.gltf", file: "https://transfer.hft-stuttgart.de/gitlab/geovistoogsi/ar/-/raw/master/public/assets/models/park_light_model/scene.gltf",
scale: { x: 0.5, y: 0.5, z: 0.5 }, scale: { x: 0.5, y: 0.5, z: 0.5 },
minScale: 0.2, minScale: 0.2,
maxScale: 5 maxScale: 5,
}, },
telephone_box: { telephone_box: {
...@@ -70,7 +72,7 @@ let models = { ...@@ -70,7 +72,7 @@ let models = {
file: "https://transfer.hft-stuttgart.de/gitlab/geovistoogsi/ar/-/raw/master/public/assets/models/telephone_box_model/scene.gltf", file: "https://transfer.hft-stuttgart.de/gitlab/geovistoogsi/ar/-/raw/master/public/assets/models/telephone_box_model/scene.gltf",
scale: { x: 0.5, y: 0.5, z: 0.5 }, scale: { x: 0.5, y: 0.5, z: 0.5 },
minScale: 0.05, minScale: 0.05,
maxScale: 1 maxScale: 1,
}, },
fire_hydrant_model: { fire_hydrant_model: {
...@@ -79,7 +81,7 @@ let models = { ...@@ -79,7 +81,7 @@ let models = {
file: "https://transfer.hft-stuttgart.de/gitlab/geovistoogsi/ar/-/raw/master/public/assets/models/fire_hydrant_model/scene.gltf", file: "https://transfer.hft-stuttgart.de/gitlab/geovistoogsi/ar/-/raw/master/public/assets/models/fire_hydrant_model/scene.gltf",
scale: { x: 0.3, y: 0.3, z: 0.3 }, scale: { x: 0.3, y: 0.3, z: 0.3 },
minScale: 0.1, minScale: 0.1,
maxScale: 1 maxScale: 1,
}, },
statue: { statue: {
...@@ -88,7 +90,7 @@ let models = { ...@@ -88,7 +90,7 @@ let models = {
file: "https://transfer.hft-stuttgart.de/gitlab/geovistoogsi/ar/-/raw/master/public/assets/models/statue_model/scene.gltf", file: "https://transfer.hft-stuttgart.de/gitlab/geovistoogsi/ar/-/raw/master/public/assets/models/statue_model/scene.gltf",
scale: { x: 0.5, y: 0.5, z: 0.5 }, scale: { x: 0.5, y: 0.5, z: 0.5 },
minScale: 0.05, minScale: 0.05,
maxScale: 2 maxScale: 2,
}, },
fountain: { fountain: {
...@@ -97,37 +99,27 @@ let models = { ...@@ -97,37 +99,27 @@ let models = {
file: "https://transfer.hft-stuttgart.de/gitlab/geovistoogsi/ar/-/raw/master/public/assets/models/fountain_model/scene.gltf", file: "https://transfer.hft-stuttgart.de/gitlab/geovistoogsi/ar/-/raw/master/public/assets/models/fountain_model/scene.gltf",
scale: { x: 0.001, y: 0.001, z: 0.001 }, scale: { x: 0.001, y: 0.001, z: 0.001 },
minScale: 0.0005, minScale: 0.0005,
maxScale: 0.005 maxScale: 0.005,
} },
}; };
/* ========================= */ /* ========================= */
/* INITIALISIERUNG */ /* INITIALISIERUNG */
/* ========================= */ /* ========================= */
window.onload = () => { window.onload = () => {
initializeAddMenu(); initializeAddMenu();
// Allen Buttons den Sound hinzufügen // Allen Buttons den Sound hinzufügen
const buttons = document.querySelectorAll( const buttons = document.querySelectorAll("button, .menu-item");
"button, .menu-item"
);
buttons.forEach(button => { buttons.forEach((button) => {
button.addEventListener( button.addEventListener("click", playButtonSound);
"click",
playButtonSound
);
}); });
}; };
function initializeAddMenu() { function initializeAddMenu() {
const addMenu = document.getElementById("add-menu");
const addMenu =
document.getElementById('add-menu');
addMenu.innerHTML = addMenu.innerHTML =
Object.entries(models) Object.entries(models)
...@@ -143,10 +135,9 @@ function initializeAddMenu() { ...@@ -143,10 +135,9 @@ function initializeAddMenu() {
alt="${model.name}" alt="${model.name}"
/> />
</div> </div>
` `,
) )
.join('') + .join("") +
` `
<div <div
class="menu-item" class="menu-item"
...@@ -160,300 +151,183 @@ function initializeAddMenu() { ...@@ -160,300 +151,183 @@ function initializeAddMenu() {
`; `;
} }
/* ========================= */ /* ========================= */
/* MENÜ-STEUERUNG */ /* MENÜ-STEUERUNG */
/* ========================= */ /* ========================= */
function showMenu(menuId) { function showMenu(menuId) {
const isMapWindow = menuId === "map-window";
const isMapWindow =
menuId === 'map-window';
// Check location before opening map // Check location before opening map
if (isMapWindow && !geoLocation) { if (isMapWindow && !geoLocation) {
console.log("Standort nicht geladen"); console.log("Standort nicht geladen");
showInfoDialog( showInfoDialog("Ihr Standort wurde noch nicht geladen");
"Ihr Standort wurde noch nicht geladen"
);
return; return;
} }
// PERFORMANCE: Use cached menu elements instead of // PERFORMANCE: Use cached menu elements instead of
// repeatedly querying the DOM. // repeatedly querying the DOM.
menus.forEach(id => { menus.forEach((id) => {
const element = menuElements[id];
const element =
menuElements[id];
if (element) { if (element) {
element.style.display = id === menuId ? "flex" : "none";
element.style.display =
id === menuId
? 'flex'
: 'none';
} }
}); });
closeDynamicMenu(); closeDynamicMenu();
// Menu-specific actions // Menu-specific actions
if (menuId === 'menu-bar') { if (menuId === "menu-bar") {
clearSelectedModel(); clearSelectedModel();
} else if (isMapWindow) { } else if (isMapWindow) {
// Initialize map // Initialize map
init_map(); init_map();
// Leaflet needs to know the actual size of the // Leaflet needs to know the actual size of the
// visible map container. // visible map container.
setTimeout(() => { setTimeout(() => {
if (typeof centerOnPlan === "function") {
if (
typeof centerOnPlan === 'function'
) {
centerOnPlan(); centerOnPlan();
} }
}, 100); }, 100);
} }
} }
/* ============================================================ */ /* ============================================================ */
/* Close map and return to AR scene */ /* Close map and return to AR scene */
/* ============================================================ */ /* ============================================================ */
function closeMap() { function closeMap() {
const mapWindow = menuElements["map-window"];
const mapWindow =
menuElements['map-window'];
if (mapWindow) { if (mapWindow) {
mapWindow.style.display = "none";
mapWindow.style.display =
"none";
} }
// Show the main AR menu again // Show the main AR menu again
const menuBar = const menuBar = menuElements["menu-bar"];
menuElements['menu-bar'];
if (menuBar) { if (menuBar) {
menuBar.style.display = "flex";
menuBar.style.display =
"flex";
} }
// Stop the map update interval // Stop the map update interval
if ( if (typeof intervalId !== "undefined") {
typeof intervalId !== 'undefined'
) {
if (intervalId) { if (intervalId) {
clearInterval(intervalId); clearInterval(intervalId);
intervalId = null; intervalId = null;
} }
} }
// Clear selected model // Clear selected model
clearSelectedModel(); clearSelectedModel();
} }
function closeDynamicMenu() { function closeDynamicMenu() {
const dynamicMenu = document.getElementById("dynamic-menu");
const dynamicMenu =
document.getElementById(
"dynamic-menu"
);
if (dynamicMenu) { if (dynamicMenu) {
dynamicMenu.style.display = "none";
dynamicMenu.style.display =
"none";
} }
} }
function showInfoDialog(message) { function showInfoDialog(message) {
const infoDialog = document.getElementById("info-dialog");
const infoDialog = const infoText = document.getElementById("info-text");
document.getElementById(
"info-dialog"
);
const infoText = infoText.textContent = message;
document.getElementById(
"info-text"
);
infoText.textContent =
message;
infoDialog.style.display = infoDialog.style.display = "flex";
"flex";
} }
function closeInfoDialog() { function closeInfoDialog() {
const infoDialog = document.getElementById("info-dialog");
const infoDialog = infoDialog.style.display = "none";
document.getElementById(
"info-dialog"
);
infoDialog.style.display =
"none";
} }
/* ========================= */ /* ========================= */
/* MODELL-HANDLING */ /* MODELL-HANDLING */
/* ========================= */ /* ========================= */
function getAllPlacedModels() { function getAllPlacedModels() {
return scene.children.filter((child) => child.isPlacedModel === true);
return scene.children.filter(
child =>
child.isPlacedModel === true
);
} }
function loadModel(filePath) { function loadModel(filePath) {
placeModel(filePath, reticle.position, (model) => {
placeModel( selectedPlacedModel = model;
filePath,
reticle.position,
(model) => {
selectedPlacedModel =
model;
highlightSelectedModel(); highlightSelectedModel();
showMenu('edit-menu'); showMenu("edit-menu");
} });
);
} }
function placeModel(filePath, position, onPlace = null) {
function placeModel( const modelConfig = Object.values(models).find(
filePath, (model) => model.file === filePath,
position,
onPlace = null
) {
const modelConfig =
Object.values(models)
.find(
model =>
model.file === filePath
); );
// PERFORMANCE: Reuse the global GLTFLoader. // PERFORMANCE: Reuse the global GLTFLoader.
// Do not create a new loader for every model. // Do not create a new loader for every model.
if (modelCache.has(filePath)) { if (modelCache.has(filePath)) {
// Use the already loaded model // Use the already loaded model
const cachedModel = const cachedModel = modelCache.get(filePath);
modelCache.get(filePath);
const model =
cachedModel.clone(true);
if ( const model = cachedModel.clone(true);
modelConfig &&
modelConfig.scale
) {
if (modelConfig && modelConfig.scale) {
model.scale.set( model.scale.set(
modelConfig.scale.x, modelConfig.scale.x,
modelConfig.scale.y, modelConfig.scale.y,
modelConfig.scale.z modelConfig.scale.z,
); );
} }
model.position.copy(position);
model.position.copy( model.isPlacedModel = true;
position
);
model.isPlacedModel =
true;
model.modelConfig = model.modelConfig = modelConfig;
modelConfig;
scene.add(model); scene.add(model);
if (onPlace) { if (onPlace) {
onPlace(model); onPlace(model);
} }
} else { } else {
// Model has not been loaded yet // Model has not been loaded yet
gltfLoader.load( gltfLoader.load(
filePath, filePath,
(gltf) => { (gltf) => {
// Store the original model in the cache // Store the original model in the cache
modelCache.set( modelCache.set(filePath, gltf.scene);
filePath,
gltf.scene
);
const model = const model = gltf.scene.clone(true);
gltf.scene.clone(true);
if (
modelConfig &&
modelConfig.scale
) {
if (modelConfig && modelConfig.scale) {
model.scale.set( model.scale.set(
modelConfig.scale.x, modelConfig.scale.x,
modelConfig.scale.y, modelConfig.scale.y,
modelConfig.scale.z modelConfig.scale.z,
); );
} }
model.position.copy(position);
model.position.copy( model.isPlacedModel = true;
position
);
model.isPlacedModel =
true;
model.modelConfig = model.modelConfig = modelConfig;
modelConfig;
scene.add(model); scene.add(model);
if (onPlace) { if (onPlace) {
onPlace(model); onPlace(model);
} }
...@@ -462,313 +336,164 @@ function placeModel( ...@@ -462,313 +336,164 @@ function placeModel(
undefined, undefined,
(error) => { (error) => {
console.error("Fehler beim Laden des Modells:", error);
console.error( },
"Fehler beim Laden des Modells:",
error
);
}
); );
} }
} }
function selectModel(modelId) { function selectModel(modelId) {
const model = models[modelId];
const model = if (model && model.file) {
models[modelId]; loadModel(model.file);
if ( showMenu("menu-bar");
model &&
model.file
) {
loadModel(
model.file
);
showMenu(
'menu-bar'
);
} }
} }
function selectModelFromScene(event) { function selectModelFromScene(event) {
const mouse = new THREE.Vector2(
(event.clientX / window.innerWidth) * 2 - 1,
const mouse = -(event.clientY / window.innerHeight) * 2 + 1,
new THREE.Vector2(
(event.clientX /
window.innerWidth) *
2 - 1,
-(event.clientY /
window.innerHeight) *
2 + 1
); );
const raycaster = new THREE.Raycaster();
const raycaster = const rayCamera =
new THREE.Raycaster(); xrRenderer && xrRenderer.xr.isPresenting
? xrRenderer.xr.getCamera(camera)
: camera;
raycaster.setFromCamera(mouse, rayCamera);
raycaster.setFromCamera( const intersects = raycaster.intersectObjects(scene.children, true);
mouse,
camera
);
if (intersects.length > 0) {
let selectedObject = intersects[0].object;
const intersects = while (selectedObject.parent && selectedObject.parent !== scene) {
raycaster.intersectObjects( selectedObject = selectedObject.parent;
scene.children,
true
);
if (
intersects.length > 0
) {
let selectedObject =
intersects[0].object;
while (
selectedObject.parent &&
selectedObject.parent !== scene
) {
selectedObject =
selectedObject.parent;
} }
// Reticle cannot be selected // Reticle cannot be selected
if ( if (selectedObject === reticle) {
selectedObject === reticle console.log("Reticle kann nicht ausgewählt werden.");
) {
console.log(
"Reticle kann nicht ausgewählt werden."
);
return; return;
} }
selectedPlacedModel = selectedObject;
selectedPlacedModel =
selectedObject;
highlightSelectedModel(); highlightSelectedModel();
showMenu( showMenu("edit-menu");
"edit-menu"
);
} }
} }
function clearSelectedModel() { function clearSelectedModel() {
if (selectedPlacedModel) { if (selectedPlacedModel) {
selectedPlacedModel.traverse((child) => {
selectedPlacedModel.traverse(
(child) => {
if (child.isMesh) { if (child.isMesh) {
child.material.emissive.setHex(0x000000);
child.material.emissive
.setHex(
0x000000
);
} }
} });
);
selectedPlacedModel = selectedPlacedModel = null;
null;
} }
} }
function highlightSelectedModel() { function highlightSelectedModel() {
if (selectedPlacedModel) { if (selectedPlacedModel) {
removeHighlightFromAllModels(); removeHighlightFromAllModels();
selectedPlacedModel.traverse((child) => {
selectedPlacedModel.traverse(
(child) => {
if (child.isMesh) { if (child.isMesh) {
child.material.emissive.setHex(0xff0000);
child.material.emissive
.setHex(
0xff0000
);
}
} }
); });
} }
} }
function removeHighlightFromSelectedModel() { function removeHighlightFromSelectedModel() {
if (selectedPlacedModel) { if (selectedPlacedModel) {
selectedPlacedModel.traverse((child) => {
selectedPlacedModel.traverse(
(child) => {
if (child.isMesh) { if (child.isMesh) {
child.material.emissive.setHex(0x000000);
child.material.emissive
.setHex(
0x000000
);
} }
} });
);
} }
} }
function removeHighlightFromAllModels() { function removeHighlightFromAllModels() {
scene.traverse((child) => {
scene.traverse( if (child.isMesh && child.material && child.material.emissive) {
(child) => { child.material.emissive.setHex(0x000000);
if (
child.isMesh &&
child.material &&
child.material.emissive
) {
child.material.emissive
.setHex(
0x000000
);
}
} }
); });
} }
function deleteModel() { function deleteModel() {
if (!selectedPlacedModel) { if (!selectedPlacedModel) {
console.log( console.log(
"Kein Modell ausgewählt. Bitte wählen Sie ein Modell aus, bevor Sie es löschen." "Kein Modell ausgewählt. Bitte wählen Sie ein Modell aus, bevor Sie es löschen.",
); );
return; return;
} }
const deleteDialog = document.getElementById("delete-confirmation-dialog");
const deleteDialog = deleteDialog.style.display = "flex";
document.getElementById(
'delete-confirmation-dialog'
);
deleteDialog.style.display =
'flex';
} }
function confirmDelete(shouldDelete) {
const deleteDialog = document.getElementById("delete-confirmation-dialog");
function confirmDelete( deleteDialog.style.display = "none";
shouldDelete
) {
const deleteDialog =
document.getElementById(
'delete-confirmation-dialog'
);
deleteDialog.style.display =
'none';
if ( if (shouldDelete && selectedPlacedModel) {
shouldDelete && scene.remove(selectedPlacedModel);
selectedPlacedModel
) {
scene.remove(
selectedPlacedModel
);
selectedPlacedModel = selectedPlacedModel = null;
null;
showMenu( showMenu("menu-bar");
'menu-bar'
);
} }
} }
function completeEditing() { function completeEditing() {
removeHighlightFromSelectedModel(); removeHighlightFromSelectedModel();
closeDynamicMenu(); closeDynamicMenu();
selectedPlacedModel = selectedPlacedModel = null;
null;
document.getElementById( document.getElementById("edit-menu").style.display = "none";
'edit-menu'
).style.display =
'none';
document.getElementById( document.getElementById("menu-bar").style.display = "flex";
'menu-bar'
).style.display =
'flex';
} }
/* ========================= */ /* ========================= */
/* BEARBEITUNGS-MENÜS */ /* BEARBEITUNGS-MENÜS */
/* ========================= */ /* ========================= */
function openRotationMenu() { function openRotationMenu() {
if (!selectedPlacedModel) { if (!selectedPlacedModel) {
console.log( console.log(
"Kein Modell ausgewählt. Bitte wählen Sie ein Modell aus, bevor Sie es bearbeiten." "Kein Modell ausgewählt. Bitte wählen Sie ein Modell aus, bevor Sie es bearbeiten.",
); );
return; return;
} }
const currentRotation = Math.round(
const currentRotation = THREE.MathUtils.radToDeg(selectedPlacedModel.rotation.y),
Math.round(
THREE.MathUtils.radToDeg(
selectedPlacedModel.rotation.y
)
);
const dynamicMenu =
document.getElementById(
"dynamic-menu"
); );
const dynamicMenu = document.getElementById("dynamic-menu");
dynamicMenu.style.display = dynamicMenu.style.display = "flex";
"flex";
dynamicMenu.innerHTML = ` dynamicMenu.innerHTML = `
...@@ -795,83 +520,43 @@ function openRotationMenu() { ...@@ -795,83 +520,43 @@ function openRotationMenu() {
`; `;
} }
function updateRotation(axis, value) {
function updateRotation(
axis,
value
) {
if (selectedPlacedModel) { if (selectedPlacedModel) {
const radians = (value / 180) * Math.PI;
const radians = selectedPlacedModel.rotation[axis] = radians;
(value / 180) *
Math.PI;
selectedPlacedModel.rotation[axis] =
radians;
const currentRotationDisplay =
document.getElementById(
"current-rotation"
);
const currentRotationDisplay = document.getElementById("current-rotation");
if (currentRotationDisplay) { if (currentRotationDisplay) {
currentRotationDisplay.textContent = value;
currentRotationDisplay.textContent =
value;
} }
} }
} }
function openScaleMenu() { function openScaleMenu() {
if (!selectedPlacedModel) { if (!selectedPlacedModel) {
console.log( console.log(
"Kein Modell ausgewählt. Bitte wählen Sie ein Modell aus, bevor Sie es bearbeiten." "Kein Modell ausgewählt. Bitte wählen Sie ein Modell aus, bevor Sie es bearbeiten.",
); );
return; return;
} }
const currentScale = selectedPlacedModel.scale.x;
const currentScale = const minScale = selectedPlacedModel.modelConfig.minScale;
selectedPlacedModel.scale.x;
const minScale =
selectedPlacedModel.modelConfig.minScale;
const maxScale =
selectedPlacedModel.modelConfig.maxScale;
const step = const maxScale = selectedPlacedModel.modelConfig.maxScale;
(maxScale - minScale) /
100;
const step = (maxScale - minScale) / 100;
const currentScalePercent = const currentScalePercent =
( ((currentScale - minScale) / (maxScale - minScale)) * 100;
(currentScale - minScale) /
(maxScale - minScale)
) * 100;
const dynamicMenu = document.getElementById("dynamic-menu");
const dynamicMenu = dynamicMenu.style.display = "flex";
document.getElementById(
"dynamic-menu"
);
dynamicMenu.style.display =
"flex";
dynamicMenu.innerHTML = ` dynamicMenu.innerHTML = `
...@@ -903,67 +588,34 @@ function openScaleMenu() { ...@@ -903,67 +588,34 @@ function openScaleMenu() {
`; `;
} }
function updateScale(percentValue, minScale, maxScale) {
function updateScale(
percentValue,
minScale,
maxScale
) {
if (selectedPlacedModel) { if (selectedPlacedModel) {
const scale = minScale + (percentValue / 100) * (maxScale - minScale);
const scale = selectedPlacedModel.scale.set(scale, scale, scale);
minScale +
(percentValue / 100) *
(maxScale - minScale);
selectedPlacedModel.scale.set(
scale,
scale,
scale
);
const scaleValueDisplay =
document.getElementById(
"scale-value"
);
const scaleValueDisplay = document.getElementById("scale-value");
if (scaleValueDisplay) { if (scaleValueDisplay) {
scaleValueDisplay.textContent = `${parseInt(percentValue, 10)}%`;
scaleValueDisplay.textContent =
`${parseInt(percentValue, 10)}%`;
} }
} }
} }
let moveDelta = 0.1; let moveDelta = 0.1;
function openMoveMenu() { function openMoveMenu() {
if (!selectedPlacedModel) { if (!selectedPlacedModel) {
console.log( console.log(
"Kein Modell ausgewählt. Bitte wählen Sie ein Modell aus, bevor Sie es bearbeiten." "Kein Modell ausgewählt. Bitte wählen Sie ein Modell aus, bevor Sie es bearbeiten.",
); );
return; return;
} }
const dynamicMenu = document.getElementById("dynamic-menu");
const dynamicMenu = dynamicMenu.style.display = "flex";
document.getElementById(
"dynamic-menu"
);
dynamicMenu.style.display =
"flex";
dynamicMenu.innerHTML = ` dynamicMenu.innerHTML = `
...@@ -1044,70 +696,38 @@ function openMoveMenu() { ...@@ -1044,70 +696,38 @@ function openMoveMenu() {
`; `;
} }
function updateMoveDelta(value) {
moveDelta = parseFloat(value);
function updateMoveDelta( const moveDeltaDisplay = document.getElementById("move-delta-display");
value
) {
moveDelta =
parseFloat(value);
const moveDeltaDisplay =
document.getElementById(
"move-delta-display"
);
if (moveDeltaDisplay) { if (moveDeltaDisplay) {
moveDeltaDisplay.textContent = moveDelta.toFixed(2);
moveDeltaDisplay.textContent =
moveDelta.toFixed(2);
} }
} }
function moveModelDynamic(axis, value) {
function moveModelDynamic(
axis,
value
) {
if (selectedPlacedModel) { if (selectedPlacedModel) {
selectedPlacedModel.position[axis] += value;
selectedPlacedModel.position[axis] +=
value;
} }
} }
/* ========================= */ /* ========================= */
/* KARTENSTEUERUNG */ /* KARTENSTEUERUNG */
/* ========================= */ /* ========================= */
function refreshMapDialog() { function refreshMapDialog() {
const mapDialog = document.getElementById("map-dialog");
const mapDialog = mapDialog.style.display = "flex";
document.getElementById(
'map-dialog'
);
mapDialog.style.display =
'flex';
} }
function closeMapDialog() { function closeMapDialog() {
const mapDialog = document.getElementById("map-dialog");
const mapDialog = mapDialog.style.display = "none";
document.getElementById(
'map-dialog'
);
mapDialog.style.display =
'none';
} }
/* ========================= */ /* ========================= */
/* AR-HANDLING */ /* AR-HANDLING */
/* ========================= */ /* ========================= */
...@@ -1117,61 +737,63 @@ async function activateXR(sceneData = null) { ...@@ -1117,61 +737,63 @@ async function activateXR(sceneData = null) {
document.body.appendChild(canvas); document.body.appendChild(canvas);
const gl = canvas.getContext("webgl", { // ========================================================
xrCompatible: true, // Three.js WebXR renderer
}); // ========================================================
const renderer = new THREE.WebGLRenderer({ const renderer = new THREE.WebGLRenderer({
alpha: true, alpha: true,
antialias: true,
canvas, canvas,
context: gl,
}); });
renderer.autoClear = false; renderer.autoClear = false;
renderer.xr.enabled = true;
xrRenderer = renderer;
scene = new THREE.Scene(); // Keep the same reference-space type used by the previous
// manual WebXR implementation.
renderer.xr.setReferenceSpaceType("local");
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(); camera = new THREE.PerspectiveCamera();
camera.matrixAutoUpdate = false;
// ======================================================== // ========================================================
// 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
// ======================================================== // ========================================================
const loader = new THREE.GLTFLoader(); const loader = new GLTFLoader();
loader.load( 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("Fehler beim Laden des Reticles:", error);
},
); );
// ======================================================== // ========================================================
...@@ -1188,11 +810,14 @@ async function activateXR(sceneData = null) { ...@@ -1188,11 +810,14 @@ async function activateXR(sceneData = null) {
requiredFeatures: ["hit-test"], requiredFeatures: ["hit-test"],
}); });
currentSession.updateRenderState({ // Let Three.js manage the XR render state / framebuffer.
baseLayer: new XRWebGLLayer(currentSession, gl), await renderer.xr.setSession(currentSession);
});
const referenceSpace = await currentSession.requestReferenceSpace("local"); const referenceSpace = renderer.xr.getReferenceSpace();
if (!referenceSpace) {
throw new Error("XR reference space konnte nicht erstellt werden.");
}
const viewerSpace = await currentSession.requestReferenceSpace("viewer"); const viewerSpace = await currentSession.requestReferenceSpace("viewer");
...@@ -1235,7 +860,6 @@ async function activateXR(sceneData = null) { ...@@ -1235,7 +860,6 @@ async function activateXR(sceneData = null) {
(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),
}; };
...@@ -1245,7 +869,7 @@ async function activateXR(sceneData = null) { ...@@ -1245,7 +869,7 @@ async function activateXR(sceneData = null) {
// Load saved scene models // Load saved scene models
// ================================================= // =================================================
if (sceneData) { if (sceneData && Array.isArray(sceneData.models)) {
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(
...@@ -1260,25 +884,27 @@ async function activateXR(sceneData = null) { ...@@ -1260,25 +884,27 @@ async function activateXR(sceneData = null) {
const filePath = modelDefinition.file; const filePath = modelDefinition.file;
const { x, z } = leafletToThree(model.lat, model.lng); // ar_overviewmap.js is loaded before user interaction,
// and exposes this conversion function on window.
const coordinates = window.leafletToThree(model.lat, model.lng);
const positionVector = new THREE.Vector3(x, model.position.y, z); const positionVector = new THREE.Vector3(
coordinates.x,
model.position.y,
coordinates.z,
);
placeModel(filePath, positionVector, (placed) => { placeModel(filePath, positionVector, (placed) => {
if (model.rotation) { if (model.rotation) {
placed.rotation._x = model.rotation._x; placed.rotation.set(
model.rotation._x ?? model.rotation.x ?? 0,
placed.rotation._y = model.rotation._y; model.rotation._y ?? model.rotation.y ?? 0,
model.rotation._z ?? model.rotation.z ?? 0,
placed.rotation._z = model.rotation._z; );
} }
if (model.scale) { if (model.scale) {
placed.scale.x = model.scale.x; placed.scale.set(model.scale.x, model.scale.y, model.scale.z);
placed.scale.y = model.scale.y;
placed.scale.z = model.scale.z;
} }
}); });
} }
...@@ -1300,61 +926,45 @@ async function activateXR(sceneData = null) { ...@@ -1300,61 +926,45 @@ async function activateXR(sceneData = null) {
}, },
); );
} }
// ======================================================== // ========================================================
// XR render loop // Modern Three.js XR render loop
// ======================================================== // ========================================================
currentSession.requestAnimationFrame(function onXRFrame(time, frame) { renderer.setAnimationLoop((time, frame) => {
currentSession.requestAnimationFrame(onXRFrame); if (!frame) {
return;
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);
// ================================================= // ======================================================
// Hit testing // 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 (hitPose && reticle) {
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 // Render AR scene
// ================================================= // ======================================================
renderer.render(scene, camera); renderer.render(scene, camera);
}
}); });
} }
...@@ -1395,3 +1005,28 @@ function playButtonSound() { ...@@ -1395,3 +1005,28 @@ function playButtonSound() {
}, 200); }, 200);
} }
} }
/* ============================================================
GLOBAL API BRIDGE
------------------------------------------------------------
ES modules have their own scope. The existing HTML UI uses
inline onclick handlers, so expose the existing application
functions explicitly on window.
============================================================ */
window.showMenu = showMenu;
window.openRotationMenu = openRotationMenu;
window.openScaleMenu = openScaleMenu;
window.openMoveMenu = openMoveMenu;
window.deleteModel = deleteModel;
window.completeEditing = completeEditing;
window.confirmDelete = confirmDelete;
window.exitAR = exitAR;
window.confirmExit = confirmExit;
window.closeInfoDialog = closeInfoDialog;
window.getAllPlacedModels = getAllPlacedModels;
window.activateXR = activateXR;
window.getXRRenderCamera = () =>
xrRenderer && xrRenderer.xr.isPresenting
? xrRenderer.xr.getCamera(camera)
: camera;
import * as THREE from "three";
let init = false; let init = false;
let mymap; let mymap;
let userIcon; let userIcon;
...@@ -98,7 +100,7 @@ function init_map() { ...@@ -98,7 +100,7 @@ function init_map() {
// Add currently placed models to map // Add currently placed models to map
// ======================================================== // ========================================================
const models = getAllPlacedModels(); const models = window.getAllPlacedModels();
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);
...@@ -210,7 +212,12 @@ function updateMap() { ...@@ -210,7 +212,12 @@ function updateMap() {
} }
// PERFORMANCE: Reuse the same Vector3 object. // PERFORMANCE: Reuse the same Vector3 object.
camera.getWorldPosition(cameraPosition); const activeCamera =
typeof window.getXRRenderCamera === "function"
? window.getXRRenderCamera()
: camera;
activeCamera.getWorldPosition(cameraPosition);
// PERFORMANCE: Do not update Leaflet if the user // PERFORMANCE: Do not update Leaflet if the user
// has not moved. // has not moved.
...@@ -323,3 +330,9 @@ function roundTo(n, digits) { ...@@ -323,3 +330,9 @@ function roundTo(n, digits) {
return parseFloat(n); return parseFloat(n);
} }
/* Expose map functions needed by the other application modules. */
window.init_map = init_map;
window.centerOnPlan = centerOnPlan;
window.threeToLeaflet = threeToLeaflet;
window.leafletToThree = leafletToThree;
...@@ -79,7 +79,6 @@ function showStartScreen() { ...@@ -79,7 +79,6 @@ function showStartScreen() {
startScreen.appendChild(hftLogo); startScreen.appendChild(hftLogo);
/* ========================================================== /* ==========================================================
TITLE TITLE
========================================================== */ ========================================================== */
...@@ -190,7 +189,7 @@ function showStartScreen() { ...@@ -190,7 +189,7 @@ function showStartScreen() {
predefinedButton2 = null; predefinedButton2 = null;
// Start AR // Start AR
activateXR(sceneData); window.activateXR(sceneData);
}; };
/* ========================================================== /* ==========================================================
...@@ -198,7 +197,7 @@ function showStartScreen() { ...@@ -198,7 +197,7 @@ function showStartScreen() {
========================================================== */ ========================================================== */
loadButton.onclick = () => { loadButton.onclick = () => {
loadSceneFromFile((data, fileName) => { window.loadSceneFromFile((data, fileName) => {
if (data && fileName) { if (data && fileName) {
sceneData = data; sceneData = data;
sceneFileName = fileName; sceneFileName = fileName;
......
...@@ -9,8 +9,15 @@ ...@@ -9,8 +9,15 @@
<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> <!-- Three.js (modern ES module setup) -->
<script src="https://unpkg.com/three@0.126.0/examples/js/loaders/GLTFLoader.js"></script> <script type="importmap">
{
"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>
...@@ -29,6 +36,7 @@ ...@@ -29,6 +36,7 @@
STANDARDMENÜ STANDARDMENÜ
============================================================ --> ============================================================ -->
<div id="menu-bar"> <div id="menu-bar">
<div <div
...@@ -299,7 +307,7 @@ ...@@ -299,7 +307,7 @@
============================================================ --> ============================================================ -->
<div id="app-version"> <div id="app-version">
v0.1.3 v0.1.186
</div> </div>
...@@ -307,21 +315,22 @@ ...@@ -307,21 +315,22 @@
JAVASCRIPT JAVASCRIPT
============================================================ --> ============================================================ -->
<script src="ar_start.js"></script> <!-- Debug console loads first so it can capture module errors/logs -->
<script src="ar_main.js"></script>
<script src="ar_debug_console.js"></script> <script src="ar_debug_console.js"></script>
<!-- Application modules -->
<script type="module" src="ar_main.js"></script>
<script type="module" src="ar_overviewmap.js"></script>
<script type="module" src="ar_download.js"></script>
<script type="module" src="ar_start.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 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