Commit af3bbe7d authored by Alfakhori's avatar Alfakhori
Browse files

return to 126

parent ae2a0557
Pipeline #12486 passed with stage
in 26 seconds
function downloadScene() { function downloadScene() {
const placedModels = window.getAllPlacedModels().map((model) => { const placedModels = getAllPlacedModels().map(model => {
const { lat, lng } = window.threeToLeaflet( const { lat, lng } = threeToLeaflet(model.position.x, model.position.z);
model.position.x,
model.position.z,
);
return { return {
name: model.modelConfig.name, name: model.modelConfig.name,
position: model.position, position: model.position,
...@@ -11,10 +8,10 @@ function downloadScene() { ...@@ -11,10 +8,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;
...@@ -30,7 +27,7 @@ function downloadScene() { ...@@ -30,7 +27,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" });
...@@ -48,20 +45,20 @@ function downloadScene() { ...@@ -48,20 +45,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);
...@@ -74,7 +71,3 @@ function loadSceneFromFile(loadedData) { ...@@ -74,7 +71,3 @@ function loadSceneFromFile(loadedData) {
}); });
fileInput.click(); fileInput.click();
} }
\ No newline at end of file
/* 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 */
/* ========================= */ /* ========================= */
...@@ -9,43 +6,44 @@ let selectedPlacedModel = null; ...@@ -9,43 +6,44 @@ 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 GLTFLoader(); const gltfLoader = new THREE.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: {
...@@ -54,7 +52,7 @@ let models = { ...@@ -54,7 +52,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: {
...@@ -63,7 +61,7 @@ let models = { ...@@ -63,7 +61,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: {
...@@ -72,7 +70,7 @@ let models = { ...@@ -72,7 +70,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: {
...@@ -81,7 +79,7 @@ let models = { ...@@ -81,7 +79,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: {
...@@ -90,7 +88,7 @@ let models = { ...@@ -90,7 +88,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: {
...@@ -99,27 +97,37 @@ let models = { ...@@ -99,27 +97,37 @@ 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("button, .menu-item"); const buttons = document.querySelectorAll(
"button, .menu-item"
);
buttons.forEach((button) => { buttons.forEach(button => {
button.addEventListener("click", playButtonSound); button.addEventListener(
"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)
...@@ -135,9 +143,10 @@ function initializeAddMenu() { ...@@ -135,9 +143,10 @@ function initializeAddMenu() {
alt="${model.name}" alt="${model.name}"
/> />
</div> </div>
`, `
) )
.join("") + .join('') +
` `
<div <div
class="menu-item" class="menu-item"
...@@ -151,183 +160,300 @@ function initializeAddMenu() { ...@@ -151,183 +160,300 @@ 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("Ihr Standort wurde noch nicht geladen"); showInfoDialog(
"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 = menuElements["menu-bar"]; const menuBar =
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 (typeof intervalId !== "undefined") { if (
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 infoText = document.getElementById("info-text"); const infoDialog =
document.getElementById(
"info-dialog"
);
infoText.textContent = message; const infoText =
document.getElementById(
"info-text"
);
infoText.textContent =
message;
infoDialog.style.display = "flex"; infoDialog.style.display =
"flex";
} }
function closeInfoDialog() { function closeInfoDialog() {
const infoDialog = document.getElementById("info-dialog");
infoDialog.style.display = "none"; const infoDialog =
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) => {
selectedPlacedModel = model; placeModel(
filePath,
reticle.position,
(model) => {
selectedPlacedModel =
model;
highlightSelectedModel(); highlightSelectedModel();
showMenu("edit-menu"); showMenu('edit-menu');
}); }
);
} }
function placeModel(filePath, position, onPlace = null) {
const modelConfig = Object.values(models).find( function placeModel(
(model) => model.file === filePath, 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 = modelCache.get(filePath); const cachedModel =
modelCache.get(filePath);
const model =
cachedModel.clone(true);
const model = cachedModel.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.isPlacedModel = true; model.position.copy(
position
);
model.isPlacedModel =
true;
model.modelConfig = modelConfig; model.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(filePath, gltf.scene); modelCache.set(
filePath,
gltf.scene
);
const model = gltf.scene.clone(true); const model =
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.isPlacedModel = true; model.position.copy(
position
);
model.isPlacedModel =
true;
model.modelConfig = modelConfig; model.modelConfig =
modelConfig;
scene.add(model); scene.add(model);
if (onPlace) { if (onPlace) {
onPlace(model); onPlace(model);
} }
...@@ -336,164 +462,313 @@ function placeModel(filePath, position, onPlace = null) { ...@@ -336,164 +462,313 @@ function placeModel(filePath, position, onPlace = null) {
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];
if (model && model.file) { const model =
loadModel(model.file); models[modelId];
showMenu("menu-bar"); if (
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,
-(event.clientY / window.innerHeight) * 2 + 1, const mouse =
new THREE.Vector2(
(event.clientX /
window.innerWidth) *
2 - 1,
-(event.clientY /
window.innerHeight) *
2 + 1
); );
const raycaster = new THREE.Raycaster();
const rayCamera = const raycaster =
xrRenderer && xrRenderer.xr.isPresenting new THREE.Raycaster();
? xrRenderer.xr.getCamera(camera)
: camera;
raycaster.setFromCamera(mouse, rayCamera);
const intersects = raycaster.intersectObjects(scene.children, true); raycaster.setFromCamera(
mouse,
camera
);
if (intersects.length > 0) {
let selectedObject = intersects[0].object;
while (selectedObject.parent && selectedObject.parent !== scene) { const intersects =
selectedObject = selectedObject.parent; raycaster.intersectObjects(
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 (selectedObject === reticle) { if (
console.log("Reticle kann nicht ausgewählt werden."); selectedObject === reticle
) {
console.log(
"Reticle kann nicht ausgewählt werden."
);
return; return;
} }
selectedPlacedModel = selectedObject;
selectedPlacedModel =
selectedObject;
highlightSelectedModel(); highlightSelectedModel();
showMenu("edit-menu"); showMenu(
"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 = null; selectedPlacedModel =
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) => {
if (child.isMesh && child.material && child.material.emissive) { scene.traverse(
child.material.emissive.setHex(0x000000); (child) => {
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");
deleteDialog.style.display = "flex"; 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"; function confirmDelete(
shouldDelete
) {
const deleteDialog =
document.getElementById(
'delete-confirmation-dialog'
);
deleteDialog.style.display =
'none';
if (shouldDelete && selectedPlacedModel) { if (
scene.remove(selectedPlacedModel); shouldDelete &&
selectedPlacedModel
) {
scene.remove(
selectedPlacedModel
);
selectedPlacedModel = null; selectedPlacedModel =
null;
showMenu("menu-bar"); showMenu(
'menu-bar'
);
} }
} }
function completeEditing() { function completeEditing() {
removeHighlightFromSelectedModel(); removeHighlightFromSelectedModel();
closeDynamicMenu(); closeDynamicMenu();
selectedPlacedModel = null; selectedPlacedModel =
null;
document.getElementById("edit-menu").style.display = "none"; document.getElementById(
'edit-menu'
).style.display =
'none';
document.getElementById("menu-bar").style.display = "flex"; document.getElementById(
'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(
THREE.MathUtils.radToDeg(selectedPlacedModel.rotation.y), const currentRotation =
Math.round(
THREE.MathUtils.radToDeg(
selectedPlacedModel.rotation.y
)
);
const dynamicMenu =
document.getElementById(
"dynamic-menu"
); );
const dynamicMenu = document.getElementById("dynamic-menu");
dynamicMenu.style.display = "flex"; dynamicMenu.style.display =
"flex";
dynamicMenu.innerHTML = ` dynamicMenu.innerHTML = `
...@@ -520,43 +795,83 @@ function openRotationMenu() { ...@@ -520,43 +795,83 @@ function openRotationMenu() {
`; `;
} }
function updateRotation(axis, value) {
function updateRotation(
axis,
value
) {
if (selectedPlacedModel) { if (selectedPlacedModel) {
const radians = (value / 180) * Math.PI;
selectedPlacedModel.rotation[axis] = radians; const 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 minScale = selectedPlacedModel.modelConfig.minScale; const currentScale =
selectedPlacedModel.scale.x;
const minScale =
selectedPlacedModel.modelConfig.minScale;
const maxScale =
selectedPlacedModel.modelConfig.maxScale;
const maxScale = selectedPlacedModel.modelConfig.maxScale; const step =
(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");
dynamicMenu.style.display = "flex"; const dynamicMenu =
document.getElementById(
"dynamic-menu"
);
dynamicMenu.style.display =
"flex";
dynamicMenu.innerHTML = ` dynamicMenu.innerHTML = `
...@@ -588,34 +903,67 @@ function openScaleMenu() { ...@@ -588,34 +903,67 @@ function openScaleMenu() {
`; `;
} }
function updateScale(percentValue, minScale, maxScale) {
function updateScale(
percentValue,
minScale,
maxScale
) {
if (selectedPlacedModel) { if (selectedPlacedModel) {
const scale = minScale + (percentValue / 100) * (maxScale - minScale);
selectedPlacedModel.scale.set(scale, scale, scale); const 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");
dynamicMenu.style.display = "flex"; const dynamicMenu =
document.getElementById(
"dynamic-menu"
);
dynamicMenu.style.display =
"flex";
dynamicMenu.innerHTML = ` dynamicMenu.innerHTML = `
...@@ -696,38 +1044,70 @@ function openMoveMenu() { ...@@ -696,38 +1044,70 @@ function openMoveMenu() {
`; `;
} }
function updateMoveDelta(value) {
moveDelta = parseFloat(value);
const moveDeltaDisplay = document.getElementById("move-delta-display"); function updateMoveDelta(
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");
mapDialog.style.display = "flex"; const mapDialog =
document.getElementById(
'map-dialog'
);
mapDialog.style.display =
'flex';
} }
function closeMapDialog() { function closeMapDialog() {
const mapDialog = document.getElementById("map-dialog");
mapDialog.style.display = "none"; const mapDialog =
document.getElementById(
'map-dialog'
);
mapDialog.style.display =
'none';
} }
/* ========================= */ /* ========================= */
/* AR-HANDLING */ /* AR-HANDLING */
/* ========================= */ /* ========================= */
...@@ -737,62 +1117,60 @@ async function activateXR(sceneData = null) { ...@@ -737,62 +1117,60 @@ async function activateXR(sceneData = null) {
document.body.appendChild(canvas); document.body.appendChild(canvas);
// ======================================================== const gl = canvas.getContext("webgl", {
// Three.js WebXR renderer 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; renderer.autoClear = false;
renderer.xr.enabled = true;
xrRenderer = renderer;
// Keep the same reference-space type used by the previous
// manual WebXR implementation.
renderer.xr.setReferenceSpaceType("local");
scene = new THREE.Scene(); 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 GLTFLoader(); const loader = new THREE.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;
scene.add(reticle);
},
undefined, reticle.visible = false;
(error) => { scene.add(reticle);
console.error("Fehler beim Laden des Reticles:", error);
}, },
); );
...@@ -810,14 +1188,11 @@ async function activateXR(sceneData = null) { ...@@ -810,14 +1188,11 @@ async function activateXR(sceneData = null) {
requiredFeatures: ["hit-test"], requiredFeatures: ["hit-test"],
}); });
// Let Three.js manage the XR render state / framebuffer. currentSession.updateRenderState({
await renderer.xr.setSession(currentSession); baseLayer: new XRWebGLLayer(currentSession, gl),
});
const referenceSpace = renderer.xr.getReferenceSpace(); const referenceSpace = await currentSession.requestReferenceSpace("local");
if (!referenceSpace) {
throw new Error("XR reference space konnte nicht erstellt werden.");
}
const viewerSpace = await currentSession.requestReferenceSpace("viewer"); const viewerSpace = await currentSession.requestReferenceSpace("viewer");
...@@ -859,8 +1234,9 @@ async function activateXR(sceneData = null) { ...@@ -859,8 +1234,9 @@ async function activateXR(sceneData = null) {
navigator.geolocation.getCurrentPosition( navigator.geolocation.getCurrentPosition(
(position) => { (position) => {
geoLocation = { geoLocation = {
latitude: window.roundTo(position.coords.latitude, 5), latitude: roundTo(position.coords.latitude, 5),
longitude: window.roundTo(position.coords.longitude, 5),
longitude: roundTo(position.coords.longitude, 5),
}; };
console.log("GeoLocation: " + JSON.stringify(geoLocation)); console.log("GeoLocation: " + JSON.stringify(geoLocation));
...@@ -869,7 +1245,7 @@ async function activateXR(sceneData = null) { ...@@ -869,7 +1245,7 @@ async function activateXR(sceneData = null) {
// Load saved scene models // Load saved scene models
// ================================================= // =================================================
if (sceneData && Array.isArray(sceneData.models)) { 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(
...@@ -884,27 +1260,25 @@ async function activateXR(sceneData = null) { ...@@ -884,27 +1260,25 @@ async function activateXR(sceneData = null) {
const filePath = modelDefinition.file; const filePath = modelDefinition.file;
// ar_overviewmap.js is loaded before user interaction, const { x, z } = leafletToThree(model.lat, model.lng);
// and exposes this conversion function on window.
const coordinates = window.leafletToThree(model.lat, model.lng);
const positionVector = new THREE.Vector3( const positionVector = new THREE.Vector3(x, model.position.y, z);
coordinates.x,
model.position.y,
coordinates.z,
);
placeModel(filePath, positionVector, (placed) => { placeModel(filePath, positionVector, (placed) => {
if (model.rotation) { if (model.rotation) {
placed.rotation.set( placed.rotation._x = model.rotation._x;
model.rotation._x ?? model.rotation.x ?? 0,
model.rotation._y ?? model.rotation.y ?? 0, placed.rotation._y = model.rotation._y;
model.rotation._z ?? model.rotation.z ?? 0,
); placed.rotation._z = model.rotation._z;
} }
if (model.scale) { if (model.scale) {
placed.scale.set(model.scale.x, model.scale.y, model.scale.z); placed.scale.x = model.scale.x;
placed.scale.y = model.scale.y;
placed.scale.z = model.scale.z;
} }
}); });
} }
...@@ -926,45 +1300,61 @@ async function activateXR(sceneData = null) { ...@@ -926,45 +1300,61 @@ async function activateXR(sceneData = null) {
}, },
); );
} }
// ======================================================== // ========================================================
// Modern Three.js XR render loop // XR render loop
// ======================================================== // ========================================================
renderer.setAnimationLoop((time, frame) => { currentSession.requestAnimationFrame(function onXRFrame(time, frame) {
if (!frame) { currentSession.requestAnimationFrame(onXRFrame);
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);
}
}); });
} }
...@@ -1005,36 +1395,3 @@ function playButtonSound() { ...@@ -1005,36 +1395,3 @@ function playButtonSound() {
}, 200); }, 200);
} }
} }
\ No newline at end of file
window.selectModel = selectModel;
window.updateRotation = updateRotation;
window.updateScale = updateScale;
window.updateMoveDelta = updateMoveDelta;
window.moveModelDynamic = moveModelDynamic;
window.closeDynamicMenu = closeDynamicMenu;
window.roundTo = window.roundTo;
/* ============================================================
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;
...@@ -100,7 +98,7 @@ function init_map() { ...@@ -100,7 +98,7 @@ function init_map() {
// Add currently placed models to map // Add currently placed models to map
// ======================================================== // ========================================================
const models = window.getAllPlacedModels(); const models = 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);
...@@ -212,12 +210,7 @@ function updateMap() { ...@@ -212,12 +210,7 @@ function updateMap() {
} }
// PERFORMANCE: Reuse the same Vector3 object. // PERFORMANCE: Reuse the same Vector3 object.
const activeCamera = camera.getWorldPosition(cameraPosition);
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.
...@@ -330,10 +323,3 @@ function roundTo(n, digits) { ...@@ -330,10 +323,3 @@ 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;
window.roundTo = roundTo;
...@@ -79,6 +79,7 @@ function showStartScreen() { ...@@ -79,6 +79,7 @@ function showStartScreen() {
startScreen.appendChild(hftLogo); startScreen.appendChild(hftLogo);
/* ========================================================== /* ==========================================================
TITLE TITLE
========================================================== */ ========================================================== */
...@@ -189,7 +190,7 @@ function showStartScreen() { ...@@ -189,7 +190,7 @@ function showStartScreen() {
predefinedButton2 = null; predefinedButton2 = null;
// Start AR // Start AR
window.window.activateXR(sceneData); activateXR(sceneData);
}; };
/* ========================================================== /* ==========================================================
...@@ -197,7 +198,7 @@ function showStartScreen() { ...@@ -197,7 +198,7 @@ function showStartScreen() {
========================================================== */ ========================================================== */
loadButton.onclick = () => { loadButton.onclick = () => {
window.window.loadSceneFromFile((data, fileName) => { loadSceneFromFile((data, fileName) => {
if (data && fileName) { if (data && fileName) {
sceneData = data; sceneData = data;
sceneFileName = fileName; sceneFileName = fileName;
......
...@@ -9,15 +9,8 @@ ...@@ -9,15 +9,8 @@
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<!-- Three.js (modern ES module setup) --> <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>
...@@ -306,7 +299,7 @@ ...@@ -306,7 +299,7 @@
============================================================ --> ============================================================ -->
<div id="app-version"> <div id="app-version">
v0.1.4 v0.1.3
</div> </div>
...@@ -314,14 +307,11 @@ ...@@ -314,14 +307,11 @@
JAVASCRIPT JAVASCRIPT
============================================================ --> ============================================================ -->
<!-- Debug console loads first so it can capture module errors/logs --> <script src="ar_start.js"></script>
<script src="ar_debug_console.js"></script>
<script src="ar_main.js"></script>
<!-- Application modules --> <script src="ar_debug_console.js"></script>
<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"
...@@ -329,7 +319,9 @@ ...@@ -329,7 +319,9 @@
<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