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(
return { model.position.x,
name: model.modelConfig.name, model.position.z,
position: model.position, );
rotation: model.rotation, return {
scale: model.scale, name: model.modelConfig.name,
lat: lat, position: model.position,
lng: lng, rotation: model.rotation,
} scale: model.scale,
}); lat: lat,
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;
} }
if (!geoLocation) { if (!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;
} }
const data = { const data = {
lat: geoLocation.latitude, lat: geoLocation.latitude,
lng: geoLocation.longitude, lng: geoLocation.longitude,
models: placedModels, models: placedModels,
} };
const sceneData = JSON.stringify(data, null, 1);
const blob = new Blob([sceneData], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "scene.json";
a.style.display = "none";
const sceneData = JSON.stringify(data, null, 1); document.body.appendChild(a);
const blob = new Blob([sceneData], { type: "application/json" }); a.click();
const url = URL.createObjectURL(blob);
const a = document.createElement("a"); URL.revokeObjectURL(url);
a.href = url; document.body.removeChild(a);
a.download = "scene.json";
a.style.display = "none";
document.body.appendChild(a);
a.click();
URL.revokeObjectURL(url);
document.body.removeChild(a);
} }
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);
} catch (error) { } catch (error) {
console.error("Fehler beim Verarbeiten der Datei:", error); console.error("Fehler beim Verarbeiten der Datei:", error);
} }
}; };
reader.readAsText(file); reader.readAsText(file);
}); });
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 */
/* ========================= */ /* ========================= */
...@@ -6,133 +9,122 @@ let selectedPlacedModel = null; ...@@ -6,133 +9,122 @@ 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: { name: "Trash bin",
name: "Trash bin", image: "assets/previewImages/trash_can.PNG",
image: "assets/previewImages/trash_can.PNG", 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: { name: "Lantern",
name: "Lantern", image: "assets/previewImages/park_light.png",
image: "assets/previewImages/park_light.png", 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: { name: "Telephone Box",
name: "Telephone Box", image: "assets/previewImages/telephone_box.PNG",
image: "assets/previewImages/telephone_box.PNG", 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: { name: "Fire Hydrant",
name: "Fire Hydrant", image: "assets/previewImages/hydrant.PNG",
image: "assets/previewImages/hydrant.PNG", 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: { name: "Statue",
name: "Statue", image: "assets/previewImages/statue.PNG",
image: "assets/previewImages/statue.PNG", 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: { name: "Fountain",
name: "Fountain", image: "assets/previewImages/fountain.PNG",
image: "assets/previewImages/fountain.PNG", 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
const buttons = document.querySelectorAll("button, .menu-item");
// Allen Buttons den Sound hinzufügen buttons.forEach((button) => {
const buttons = document.querySelectorAll( button.addEventListener("click", playButtonSound);
"button, .menu-item" });
);
buttons.forEach(button => {
button.addEventListener(
"click",
playButtonSound
);
});
}; };
function initializeAddMenu() { function initializeAddMenu() {
const addMenu = document.getElementById("add-menu");
const addMenu = addMenu.innerHTML =
document.getElementById('add-menu'); Object.entries(models)
.map(
addMenu.innerHTML = ([key, model]) => `
Object.entries(models)
.map(
([key, model]) => `
<div <div
class="menu-item" class="menu-item"
id="${key}-item" id="${key}-item"
...@@ -143,11 +135,10 @@ function initializeAddMenu() { ...@@ -143,11 +135,10 @@ function initializeAddMenu() {
alt="${model.name}" alt="${model.name}"
/> />
</div> </div>
` `,
) )
.join('') + .join("") +
`
`
<div <div
class="menu-item" class="menu-item"
onclick="showMenu('menu-bar')" onclick="showMenu('menu-bar')"
...@@ -160,617 +151,351 @@ function initializeAddMenu() { ...@@ -160,617 +151,351 @@ function initializeAddMenu() {
`; `;
} }
/* ========================= */ /* ========================= */
/* MENÜ-STEUERUNG */ /* MENÜ-STEUERUNG */
/* ========================= */ /* ========================= */
function showMenu(menuId) { function showMenu(menuId) {
const isMapWindow = menuId === "map-window";
const isMapWindow = // Check location before opening map
menuId === 'map-window'; if (isMapWindow && !geoLocation) {
console.log("Standort nicht geladen");
// Check location before opening map showInfoDialog("Ihr Standort wurde noch nicht geladen");
if (isMapWindow && !geoLocation) {
console.log("Standort nicht geladen"); return;
}
showInfoDialog( // PERFORMANCE: Use cached menu elements instead of
"Ihr Standort wurde noch nicht geladen" // repeatedly querying the DOM.
); menus.forEach((id) => {
const element = menuElements[id];
return; if (element) {
element.style.display = id === menuId ? "flex" : "none";
} }
});
closeDynamicMenu();
// PERFORMANCE: Use cached menu elements instead of // Menu-specific actions
// repeatedly querying the DOM. if (menuId === "menu-bar") {
menus.forEach(id => { clearSelectedModel();
} else if (isMapWindow) {
const element = // Initialize map
menuElements[id]; init_map();
if (element) {
element.style.display =
id === menuId
? 'flex'
: 'none';
}
});
closeDynamicMenu();
// Menu-specific actions
if (menuId === 'menu-bar') {
clearSelectedModel();
} else if (isMapWindow) {
// Initialize map
init_map();
// Leaflet needs to know the actual size of the
// visible map container.
setTimeout(() => {
if (
typeof centerOnPlan === 'function'
) {
centerOnPlan();
}
}, 100); // Leaflet needs to know the actual size of the
} // visible map container.
setTimeout(() => {
if (typeof centerOnPlan === "function") {
centerOnPlan();
}
}, 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 = if (mapWindow) {
menuElements['map-window']; mapWindow.style.display = "none";
}
if (mapWindow) {
mapWindow.style.display =
"none";
}
// Show the main AR menu again
const menuBar =
menuElements['menu-bar'];
if (menuBar) {
menuBar.style.display =
"flex";
}
// Stop the map update interval // Show the main AR menu again
if ( const menuBar = menuElements["menu-bar"];
typeof intervalId !== 'undefined'
) {
if (intervalId) { if (menuBar) {
menuBar.style.display = "flex";
}
clearInterval(intervalId); // Stop the map update interval
if (typeof intervalId !== "undefined") {
if (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 = if (dynamicMenu) {
document.getElementById( dynamicMenu.style.display = "none";
"dynamic-menu" }
);
if (dynamicMenu) {
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 = infoDialog.style.display = "flex";
message;
infoDialog.style.display =
"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) => {
selectedPlacedModel = model;
placeModel( highlightSelectedModel();
filePath,
reticle.position,
(model) => {
selectedPlacedModel =
model;
highlightSelectedModel();
showMenu('edit-menu'); showMenu("edit-menu");
} });
);
} }
function placeModel(filePath, position, onPlace = null) {
const modelConfig = Object.values(models).find(
(model) => model.file === filePath,
);
function placeModel( // PERFORMANCE: Reuse the global GLTFLoader.
filePath, // Do not create a new loader for every model.
position, if (modelCache.has(filePath)) {
onPlace = null // Use the already loaded model
) { const cachedModel = modelCache.get(filePath);
const modelConfig =
Object.values(models)
.find(
model =>
model.file === filePath
);
const model = cachedModel.clone(true);
// PERFORMANCE: Reuse the global GLTFLoader. if (modelConfig && modelConfig.scale) {
// Do not create a new loader for every model. model.scale.set(
if (modelCache.has(filePath)) { modelConfig.scale.x,
modelConfig.scale.y,
modelConfig.scale.z,
);
}
// Use the already loaded model model.position.copy(position);
const cachedModel =
modelCache.get(filePath);
const model = model.isPlacedModel = true;
cachedModel.clone(true);
model.modelConfig = modelConfig;
if ( scene.add(model);
modelConfig &&
modelConfig.scale
) {
model.scale.set( if (onPlace) {
modelConfig.scale.x, onPlace(model);
modelConfig.scale.y, }
modelConfig.scale.z } else {
); // Model has not been loaded yet
gltfLoader.load(
filePath,
(gltf) => {
// Store the original model in the cache
modelCache.set(filePath, gltf.scene);
const model = gltf.scene.clone(true);
if (modelConfig && modelConfig.scale) {
model.scale.set(
modelConfig.scale.x,
modelConfig.scale.y,
modelConfig.scale.z,
);
} }
model.position.copy(position);
model.position.copy( model.isPlacedModel = true;
position
);
model.isPlacedModel = model.modelConfig = modelConfig;
true;
model.modelConfig =
modelConfig;
scene.add(model); scene.add(model);
if (onPlace) { if (onPlace) {
onPlace(model); onPlace(model);
} }
},
} else { undefined,
// Model has not been loaded yet
gltfLoader.load(
filePath,
(gltf) => {
// Store the original model in the cache
modelCache.set(
filePath,
gltf.scene
);
const model =
gltf.scene.clone(true);
if (
modelConfig &&
modelConfig.scale
) {
model.scale.set(
modelConfig.scale.x,
modelConfig.scale.y,
modelConfig.scale.z
);
}
model.position.copy(
position
);
model.isPlacedModel =
true;
model.modelConfig =
modelConfig;
scene.add(model);
if (onPlace) {
onPlace(model);
}
},
undefined,
(error) => {
console.error( (error) => {
"Fehler beim Laden des Modells:", console.error("Fehler beim Laden des Modells:", error);
error },
); );
} }
);
}
} }
function selectModel(modelId) { function selectModel(modelId) {
const model = models[modelId];
const model = if (model && model.file) {
models[modelId]; loadModel(model.file);
if (
model &&
model.file
) {
loadModel( showMenu("menu-bar");
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();
raycaster.setFromCamera(
mouse,
camera
);
const intersects =
raycaster.intersectObjects(
scene.children,
true
);
if (
intersects.length > 0
) {
let selectedObject =
intersects[0].object;
const raycaster = new THREE.Raycaster();
while ( const rayCamera =
selectedObject.parent && xrRenderer && xrRenderer.xr.isPresenting
selectedObject.parent !== scene ? xrRenderer.xr.getCamera(camera)
) { : camera;
selectedObject = raycaster.setFromCamera(mouse, rayCamera);
selectedObject.parent;
}
const intersects = raycaster.intersectObjects(scene.children, true);
// Reticle cannot be selected if (intersects.length > 0) {
if ( let selectedObject = intersects[0].object;
selectedObject === reticle
) {
console.log( while (selectedObject.parent && selectedObject.parent !== scene) {
"Reticle kann nicht ausgewählt werden." selectedObject = selectedObject.parent;
); }
return;
}
// Reticle cannot be selected
if (selectedObject === reticle) {
console.log("Reticle kann nicht ausgewählt werden.");
selectedPlacedModel = return;
selectedObject; }
selectedPlacedModel = selectedObject;
highlightSelectedModel(); highlightSelectedModel();
showMenu( showMenu("edit-menu");
"edit-menu" }
);
}
} }
function clearSelectedModel() { function clearSelectedModel() {
if (selectedPlacedModel) {
selectedPlacedModel.traverse((child) => {
if (child.isMesh) {
child.material.emissive.setHex(0x000000);
}
});
if (selectedPlacedModel) { selectedPlacedModel = null;
}
selectedPlacedModel.traverse(
(child) => {
if (child.isMesh) {
child.material.emissive
.setHex(
0x000000
);
}
}
);
selectedPlacedModel =
null;
}
} }
function highlightSelectedModel() { function highlightSelectedModel() {
if (selectedPlacedModel) {
removeHighlightFromAllModels();
if (selectedPlacedModel) { selectedPlacedModel.traverse((child) => {
if (child.isMesh) {
removeHighlightFromAllModels(); child.material.emissive.setHex(0xff0000);
}
});
selectedPlacedModel.traverse( }
(child) => {
if (child.isMesh) {
child.material.emissive
.setHex(
0xff0000
);
}
}
);
}
} }
function removeHighlightFromSelectedModel() { function removeHighlightFromSelectedModel() {
if (selectedPlacedModel) {
if (selectedPlacedModel) { selectedPlacedModel.traverse((child) => {
if (child.isMesh) {
selectedPlacedModel.traverse( child.material.emissive.setHex(0x000000);
(child) => { }
});
if (child.isMesh) { }
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) {
console.log(
"Kein Modell ausgewählt. Bitte wählen Sie ein Modell aus, bevor Sie es löschen.",
);
if (!selectedPlacedModel) { return;
}
console.log(
"Kein Modell ausgewählt. Bitte wählen Sie ein Modell aus, bevor Sie es löschen."
);
return;
}
const deleteDialog =
document.getElementById(
'delete-confirmation-dialog'
);
const deleteDialog = document.getElementById("delete-confirmation-dialog");
deleteDialog.style.display = deleteDialog.style.display = "flex";
'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 = null;
selectedPlacedModel
);
selectedPlacedModel =
null;
showMenu( showMenu("menu-bar");
'menu-bar' }
);
}
} }
function completeEditing() { function completeEditing() {
removeHighlightFromSelectedModel();
removeHighlightFromSelectedModel(); closeDynamicMenu();
closeDynamicMenu(); selectedPlacedModel = null;
selectedPlacedModel = document.getElementById("edit-menu").style.display = "none";
null;
document.getElementById( document.getElementById("menu-bar").style.display = "flex";
'edit-menu'
).style.display =
'none';
document.getElementById(
'menu-bar'
).style.display =
'flex';
} }
/* ========================= */ /* ========================= */
/* BEARBEITUNGS-MENÜS */ /* BEARBEITUNGS-MENÜS */
/* ========================= */ /* ========================= */
function openRotationMenu() { function openRotationMenu() {
if (!selectedPlacedModel) {
console.log(
"Kein Modell ausgewählt. Bitte wählen Sie ein Modell aus, bevor Sie es bearbeiten.",
);
if (!selectedPlacedModel) { return;
}
console.log(
"Kein Modell ausgewählt. Bitte wählen Sie ein Modell aus, bevor Sie es bearbeiten."
);
return;
}
const currentRotation =
Math.round(
THREE.MathUtils.radToDeg(
selectedPlacedModel.rotation.y
)
);
const dynamicMenu =
document.getElementById(
"dynamic-menu"
);
const currentRotation = Math.round(
THREE.MathUtils.radToDeg(selectedPlacedModel.rotation.y),
);
dynamicMenu.style.display = const dynamicMenu = document.getElementById("dynamic-menu");
"flex";
dynamicMenu.style.display = "flex";
dynamicMenu.innerHTML = ` dynamicMenu.innerHTML = `
<h3>Rotation anpassen</h3> <h3>Rotation anpassen</h3>
...@@ -795,85 +520,45 @@ function openRotationMenu() { ...@@ -795,85 +520,45 @@ function openRotationMenu() {
`; `;
} }
function updateRotation(axis, value) {
if (selectedPlacedModel) {
const radians = (value / 180) * Math.PI;
function updateRotation( selectedPlacedModel.rotation[axis] = radians;
axis,
value
) {
if (selectedPlacedModel) { const currentRotationDisplay = document.getElementById("current-rotation");
const radians = if (currentRotationDisplay) {
(value / 180) * currentRotationDisplay.textContent = value;
Math.PI;
selectedPlacedModel.rotation[axis] =
radians;
const currentRotationDisplay =
document.getElementById(
"current-rotation"
);
if (currentRotationDisplay) {
currentRotationDisplay.textContent =
value;
}
} }
}
} }
function openScaleMenu() { function openScaleMenu() {
if (!selectedPlacedModel) {
console.log(
"Kein Modell ausgewählt. Bitte wählen Sie ein Modell aus, bevor Sie es bearbeiten.",
);
if (!selectedPlacedModel) { return;
}
console.log(
"Kein Modell ausgewählt. Bitte wählen Sie ein Modell aus, bevor Sie es bearbeiten."
);
return;
}
const currentScale =
selectedPlacedModel.scale.x;
const minScale =
selectedPlacedModel.modelConfig.minScale;
const maxScale =
selectedPlacedModel.modelConfig.maxScale;
const step =
(maxScale - minScale) /
100;
const currentScale = selectedPlacedModel.scale.x;
const currentScalePercent = const minScale = selectedPlacedModel.modelConfig.minScale;
(
(currentScale - minScale) /
(maxScale - minScale)
) * 100;
const maxScale = selectedPlacedModel.modelConfig.maxScale;
const dynamicMenu = const step = (maxScale - minScale) / 100;
document.getElementById(
"dynamic-menu"
);
const currentScalePercent =
((currentScale - minScale) / (maxScale - minScale)) * 100;
dynamicMenu.style.display = const dynamicMenu = document.getElementById("dynamic-menu");
"flex";
dynamicMenu.style.display = "flex";
dynamicMenu.innerHTML = ` dynamicMenu.innerHTML = `
<h3>Skalierung anpassen</h3> <h3>Skalierung anpassen</h3>
...@@ -903,69 +588,36 @@ function openScaleMenu() { ...@@ -903,69 +588,36 @@ function openScaleMenu() {
`; `;
} }
function updateScale(percentValue, minScale, maxScale) {
if (selectedPlacedModel) {
const scale = minScale + (percentValue / 100) * (maxScale - minScale);
function updateScale( selectedPlacedModel.scale.set(scale, scale, scale);
percentValue,
minScale,
maxScale
) {
if (selectedPlacedModel) {
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) {
console.log(
"Kein Modell ausgewählt. Bitte wählen Sie ein Modell aus, bevor Sie es bearbeiten.",
);
if (!selectedPlacedModel) { return;
}
console.log(
"Kein Modell ausgewählt. Bitte wählen Sie ein Modell aus, bevor Sie es bearbeiten."
);
return;
}
const dynamicMenu =
document.getElementById(
"dynamic-menu"
);
dynamicMenu.style.display = const dynamicMenu = document.getElementById("dynamic-menu");
"flex";
dynamicMenu.style.display = "flex";
dynamicMenu.innerHTML = ` dynamicMenu.innerHTML = `
<h3>Position anpassen</h3> <h3>Position anpassen</h3>
...@@ -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 = if (moveDeltaDisplay) {
document.getElementById( moveDeltaDisplay.textContent = moveDelta.toFixed(2);
"move-delta-display" }
);
if (moveDeltaDisplay) {
moveDeltaDisplay.textContent =
moveDelta.toFixed(2);
}
} }
function moveModelDynamic(axis, value) {
function moveModelDynamic( if (selectedPlacedModel) {
axis, selectedPlacedModel.position[axis] += value;
value }
) {
if (selectedPlacedModel) {
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);
}
}); });
} }
...@@ -1394,4 +1004,29 @@ function playButtonSound() { ...@@ -1394,4 +1004,29 @@ function playButtonSound() {
soundTimeout = false; soundTimeout = false;
}, 200); }, 200);
} }
} }
\ No newline at end of file
/* ============================================================
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