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

three v186

parent ebd6d6fc
Pipeline #12484 passed with stage
in 26 seconds
function downloadScene() {
const placedModels = getAllPlacedModels().map(model => {
const { lat, lng } = threeToLeaflet(model.position.x, model.position.z);
return {
name: model.modelConfig.name,
position: model.position,
rotation: model.rotation,
scale: model.scale,
lat: lat,
lng: lng,
}
});
const placedModels = window.getAllPlacedModels().map((model) => {
const { lat, lng } = window.threeToLeaflet(
model.position.x,
model.position.z,
);
return {
name: model.modelConfig.name,
position: model.position,
rotation: model.rotation,
scale: model.scale,
lat: lat,
lng: lng,
};
});
console.log("Modelle: " + placedModels.length)
if (placedModels.length === 0) {
showInfoDialog("Szene ist leer");
return;
}
console.log("Modelle: " + placedModels.length);
if (placedModels.length === 0) {
showInfoDialog("Szene ist leer");
return;
}
if (!geoLocation) {
console.log("Standort nicht geladen");
showInfoDialog("Ihr Standort wurde noch nicht geladen");
return;
}
if (!geoLocation) {
console.log("Standort nicht geladen");
showInfoDialog("Ihr Standort wurde noch nicht geladen");
return;
}
const data = {
lat: geoLocation.latitude,
lng: geoLocation.longitude,
models: placedModels,
}
const data = {
lat: geoLocation.latitude,
lng: geoLocation.longitude,
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);
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";
document.body.appendChild(a);
a.click();
URL.revokeObjectURL(url);
document.body.removeChild(a);
document.body.appendChild(a);
a.click();
URL.revokeObjectURL(url);
document.body.removeChild(a);
}
function loadSceneFromFile(loadedData) {
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = 'application/json';
const fileInput = document.createElement("input");
fileInput.type = "file";
fileInput.accept = "application/json";
fileInput.addEventListener('change', function(event) {
const file = event.target.files[0];
const fileName = file ? file.name : "Keine Datei ausgewählt"
if (!file) {
console.log("Keine Datei ausgewählt");
return;
}
fileInput.addEventListener("change", function (event) {
const file = event.target.files[0];
const fileName = file ? file.name : "Keine Datei ausgewählt";
if (!file) {
console.log("Keine Datei ausgewählt");
return;
}
const reader = new FileReader();
reader.onload = function(e) {
try {
const sceneData = JSON.parse(e.target.result);
loadedData(sceneData, fileName);
} catch (error) {
console.error("Fehler beim Verarbeiten der Datei:", error);
}
};
const reader = new FileReader();
reader.onload = function (e) {
try {
const sceneData = JSON.parse(e.target.result);
loadedData(sceneData, fileName);
} catch (error) {
console.error("Fehler beim Verarbeiten der Datei:", error);
}
};
reader.readAsText(file);
reader.readAsText(file);
});
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 */
/* ========================= */
......@@ -6,133 +9,122 @@ let selectedPlacedModel = null;
let currentSession = null;
let reticle = null;
let scene, camera;
let xrRenderer = null;
let geoLocation;
const modelCache = new Map();
// PERFORMANCE: Reuse one GLTFLoader instead of creating
// a new loader every time a model is placed.
const gltfLoader = new THREE.GLTFLoader();
const gltfLoader = new GLTFLoader();
const menus = [
'menu-bar',
'add-menu',
'edit-menu',
'options-menu',
'map-window'
"menu-bar",
"add-menu",
"edit-menu",
"options-menu",
"map-window",
];
// PERFORMANCE: Cache menu DOM elements once instead of
// repeatedly calling document.getElementById() in showMenu().
const menuElements = {};
menus.forEach(id => {
menuElements[id] = document.getElementById(id);
menus.forEach((id) => {
menuElements[id] = document.getElementById(id);
});
/* ========================= */
/* MODELLE */
/* ========================= */
let models = {
bench: {
name: "Bench",
image: "assets/previewImages/bench.PNG",
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 },
minScale: 0.05,
maxScale: 0.5
},
trashbin: {
name: "Trash bin",
image: "assets/previewImages/trash_can.PNG",
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 },
minScale: 0.01,
maxScale: 0.1
},
lantern: {
name: "Lantern",
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",
scale: { x: 0.5, y: 0.5, z: 0.5 },
minScale: 0.2,
maxScale: 5
},
telephone_box: {
name: "Telephone Box",
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",
scale: { x: 0.5, y: 0.5, z: 0.5 },
minScale: 0.05,
maxScale: 1
},
fire_hydrant_model: {
name: "Fire Hydrant",
image: "assets/previewImages/hydrant.PNG",
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 },
minScale: 0.1,
maxScale: 1
},
statue: {
name: "Statue",
image: "assets/previewImages/statue.PNG",
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 },
minScale: 0.05,
maxScale: 2
},
fountain: {
name: "Fountain",
image: "assets/previewImages/fountain.PNG",
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 },
minScale: 0.0005,
maxScale: 0.005
}
bench: {
name: "Bench",
image: "assets/previewImages/bench.PNG",
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 },
minScale: 0.05,
maxScale: 0.5,
},
trashbin: {
name: "Trash bin",
image: "assets/previewImages/trash_can.PNG",
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 },
minScale: 0.01,
maxScale: 0.1,
},
lantern: {
name: "Lantern",
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",
scale: { x: 0.5, y: 0.5, z: 0.5 },
minScale: 0.2,
maxScale: 5,
},
telephone_box: {
name: "Telephone Box",
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",
scale: { x: 0.5, y: 0.5, z: 0.5 },
minScale: 0.05,
maxScale: 1,
},
fire_hydrant_model: {
name: "Fire Hydrant",
image: "assets/previewImages/hydrant.PNG",
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 },
minScale: 0.1,
maxScale: 1,
},
statue: {
name: "Statue",
image: "assets/previewImages/statue.PNG",
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 },
minScale: 0.05,
maxScale: 2,
},
fountain: {
name: "Fountain",
image: "assets/previewImages/fountain.PNG",
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 },
minScale: 0.0005,
maxScale: 0.005,
},
};
/* ========================= */
/* INITIALISIERUNG */
/* ========================= */
window.onload = () => {
initializeAddMenu();
initializeAddMenu();
// Allen Buttons den Sound hinzufügen
const buttons = document.querySelectorAll("button, .menu-item");
// Allen Buttons den Sound hinzufügen
const buttons = document.querySelectorAll(
"button, .menu-item"
);
buttons.forEach(button => {
button.addEventListener(
"click",
playButtonSound
);
});
buttons.forEach((button) => {
button.addEventListener("click", playButtonSound);
});
};
function initializeAddMenu() {
const addMenu = document.getElementById("add-menu");
const addMenu =
document.getElementById('add-menu');
addMenu.innerHTML =
Object.entries(models)
.map(
([key, model]) => `
addMenu.innerHTML =
Object.entries(models)
.map(
([key, model]) => `
<div
class="menu-item"
id="${key}-item"
......@@ -143,11 +135,10 @@ function initializeAddMenu() {
alt="${model.name}"
/>
</div>
`
)
.join('') +
`
`,
)
.join("") +
`
<div
class="menu-item"
onclick="showMenu('menu-bar')"
......@@ -160,617 +151,351 @@ function initializeAddMenu() {
`;
}
/* ========================= */
/* MENÜ-STEUERUNG */
/* ========================= */
function showMenu(menuId) {
const isMapWindow = menuId === "map-window";
const isMapWindow =
menuId === 'map-window';
// Check location before opening map
if (isMapWindow && !geoLocation) {
console.log("Standort nicht geladen");
// Check location before opening map
if (isMapWindow && !geoLocation) {
showInfoDialog("Ihr Standort wurde noch nicht geladen");
console.log("Standort nicht geladen");
return;
}
showInfoDialog(
"Ihr Standort wurde noch nicht geladen"
);
// PERFORMANCE: Use cached menu elements instead of
// 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
// repeatedly querying the DOM.
menus.forEach(id => {
const element =
menuElements[id];
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();
}
// Menu-specific actions
if (menuId === "menu-bar") {
clearSelectedModel();
} else if (isMapWindow) {
// Initialize map
init_map();
}, 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 */
/* ============================================================ */
function closeMap() {
const mapWindow = menuElements["map-window"];
const mapWindow =
menuElements['map-window'];
if (mapWindow) {
mapWindow.style.display =
"none";
}
// Show the main AR menu again
const menuBar =
menuElements['menu-bar'];
if (menuBar) {
menuBar.style.display =
"flex";
}
if (mapWindow) {
mapWindow.style.display = "none";
}
// Stop the map update interval
if (
typeof intervalId !== 'undefined'
) {
// Show the main AR menu again
const menuBar = menuElements["menu-bar"];
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
clearSelectedModel();
// Clear selected model
clearSelectedModel();
}
function closeDynamicMenu() {
const dynamicMenu = document.getElementById("dynamic-menu");
const dynamicMenu =
document.getElementById(
"dynamic-menu"
);
if (dynamicMenu) {
dynamicMenu.style.display =
"none";
}
if (dynamicMenu) {
dynamicMenu.style.display = "none";
}
}
function showInfoDialog(message) {
const infoDialog = document.getElementById("info-dialog");
const infoDialog =
document.getElementById(
"info-dialog"
);
const infoText = document.getElementById("info-text");
const infoText =
document.getElementById(
"info-text"
);
infoText.textContent = message;
infoText.textContent =
message;
infoDialog.style.display =
"flex";
infoDialog.style.display = "flex";
}
function closeInfoDialog() {
const infoDialog = document.getElementById("info-dialog");
const infoDialog =
document.getElementById(
"info-dialog"
);
infoDialog.style.display =
"none";
infoDialog.style.display = "none";
}
/* ========================= */
/* MODELL-HANDLING */
/* ========================= */
function getAllPlacedModels() {
return scene.children.filter(
child =>
child.isPlacedModel === true
);
return scene.children.filter((child) => child.isPlacedModel === true);
}
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(
(model) => model.file === filePath,
);
function placeModel(
filePath,
position,
onPlace = null
) {
const modelConfig =
Object.values(models)
.find(
model =>
model.file === filePath
);
// PERFORMANCE: Reuse the global GLTFLoader.
// Do not create a new loader for every model.
if (modelCache.has(filePath)) {
// Use the already loaded model
const cachedModel = modelCache.get(filePath);
const model = cachedModel.clone(true);
// PERFORMANCE: Reuse the global GLTFLoader.
// Do not create a new loader for every model.
if (modelCache.has(filePath)) {
if (modelConfig && modelConfig.scale) {
model.scale.set(
modelConfig.scale.x,
modelConfig.scale.y,
modelConfig.scale.z,
);
}
// Use the already loaded model
const cachedModel =
modelCache.get(filePath);
model.position.copy(position);
const model =
cachedModel.clone(true);
model.isPlacedModel = true;
model.modelConfig = modelConfig;
if (
modelConfig &&
modelConfig.scale
) {
scene.add(model);
model.scale.set(
modelConfig.scale.x,
modelConfig.scale.y,
modelConfig.scale.z
);
if (onPlace) {
onPlace(model);
}
} 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(
position
);
model.isPlacedModel = true;
model.isPlacedModel =
true;
model.modelConfig =
modelConfig;
model.modelConfig = modelConfig;
scene.add(model);
if (onPlace) {
onPlace(model);
onPlace(model);
}
},
} 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.isPlacedModel =
true;
model.modelConfig =
modelConfig;
scene.add(model);
if (onPlace) {
onPlace(model);
}
},
undefined,
(error) => {
undefined,
console.error(
"Fehler beim Laden des Modells:",
error
);
}
);
}
(error) => {
console.error("Fehler beim Laden des Modells:", error);
},
);
}
}
function selectModel(modelId) {
const model = models[modelId];
const model =
models[modelId];
if (
model &&
model.file
) {
if (model && model.file) {
loadModel(model.file);
loadModel(
model.file
);
showMenu(
'menu-bar'
);
}
showMenu("menu-bar");
}
}
function selectModelFromScene(event) {
const mouse = new THREE.Vector2(
(event.clientX / window.innerWidth) * 2 - 1,
const mouse =
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;
-(event.clientY / window.innerHeight) * 2 + 1,
);
const raycaster = new THREE.Raycaster();
while (
selectedObject.parent &&
selectedObject.parent !== scene
) {
const rayCamera =
xrRenderer && xrRenderer.xr.isPresenting
? xrRenderer.xr.getCamera(camera)
: camera;
selectedObject =
selectedObject.parent;
}
raycaster.setFromCamera(mouse, rayCamera);
const intersects = raycaster.intersectObjects(scene.children, true);
// Reticle cannot be selected
if (
selectedObject === reticle
) {
if (intersects.length > 0) {
let selectedObject = intersects[0].object;
console.log(
"Reticle kann nicht ausgewählt werden."
);
return;
}
while (selectedObject.parent && selectedObject.parent !== scene) {
selectedObject = selectedObject.parent;
}
// Reticle cannot be selected
if (selectedObject === reticle) {
console.log("Reticle kann nicht ausgewählt werden.");
selectedPlacedModel =
selectedObject;
return;
}
selectedPlacedModel = selectedObject;
highlightSelectedModel();
highlightSelectedModel();
showMenu(
"edit-menu"
);
}
showMenu("edit-menu");
}
}
function clearSelectedModel() {
if (selectedPlacedModel) {
selectedPlacedModel.traverse((child) => {
if (child.isMesh) {
child.material.emissive.setHex(0x000000);
}
});
if (selectedPlacedModel) {
selectedPlacedModel.traverse(
(child) => {
if (child.isMesh) {
child.material.emissive
.setHex(
0x000000
);
}
}
);
selectedPlacedModel =
null;
}
selectedPlacedModel = null;
}
}
function highlightSelectedModel() {
if (selectedPlacedModel) {
removeHighlightFromAllModels();
if (selectedPlacedModel) {
removeHighlightFromAllModels();
selectedPlacedModel.traverse(
(child) => {
if (child.isMesh) {
child.material.emissive
.setHex(
0xff0000
);
}
}
);
}
selectedPlacedModel.traverse((child) => {
if (child.isMesh) {
child.material.emissive.setHex(0xff0000);
}
});
}
}
function removeHighlightFromSelectedModel() {
if (selectedPlacedModel) {
selectedPlacedModel.traverse(
(child) => {
if (child.isMesh) {
child.material.emissive
.setHex(
0x000000
);
}
}
);
}
if (selectedPlacedModel) {
selectedPlacedModel.traverse((child) => {
if (child.isMesh) {
child.material.emissive.setHex(0x000000);
}
});
}
}
function removeHighlightFromAllModels() {
scene.traverse(
(child) => {
if (
child.isMesh &&
child.material &&
child.material.emissive
) {
child.material.emissive
.setHex(
0x000000
);
}
}
);
scene.traverse((child) => {
if (child.isMesh && child.material && child.material.emissive) {
child.material.emissive.setHex(0x000000);
}
});
}
function deleteModel() {
if (!selectedPlacedModel) {
console.log(
"Kein Modell ausgewählt. Bitte wählen Sie ein Modell aus, bevor Sie es löschen.",
);
if (!selectedPlacedModel) {
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'
);
return;
}
const deleteDialog = document.getElementById("delete-confirmation-dialog");
deleteDialog.style.display =
'flex';
deleteDialog.style.display = "flex";
}
function confirmDelete(shouldDelete) {
const deleteDialog = document.getElementById("delete-confirmation-dialog");
function confirmDelete(
shouldDelete
) {
const deleteDialog =
document.getElementById(
'delete-confirmation-dialog'
);
deleteDialog.style.display =
'none';
deleteDialog.style.display = "none";
if (
shouldDelete &&
selectedPlacedModel
) {
if (shouldDelete && selectedPlacedModel) {
scene.remove(selectedPlacedModel);
scene.remove(
selectedPlacedModel
);
selectedPlacedModel =
null;
selectedPlacedModel = null;
showMenu(
'menu-bar'
);
}
showMenu("menu-bar");
}
}
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 */
/* ========================= */
function openRotationMenu() {
if (!selectedPlacedModel) {
console.log(
"Kein Modell ausgewählt. Bitte wählen Sie ein Modell aus, bevor Sie es bearbeiten.",
);
if (!selectedPlacedModel) {
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"
);
return;
}
const currentRotation = Math.round(
THREE.MathUtils.radToDeg(selectedPlacedModel.rotation.y),
);
dynamicMenu.style.display =
"flex";
const dynamicMenu = document.getElementById("dynamic-menu");
dynamicMenu.style.display = "flex";
dynamicMenu.innerHTML = `
dynamicMenu.innerHTML = `
<h3>Rotation anpassen</h3>
......@@ -795,85 +520,45 @@ function openRotationMenu() {
`;
}
function updateRotation(axis, value) {
if (selectedPlacedModel) {
const radians = (value / 180) * Math.PI;
function updateRotation(
axis,
value
) {
selectedPlacedModel.rotation[axis] = radians;
if (selectedPlacedModel) {
const currentRotationDisplay = document.getElementById("current-rotation");
const radians =
(value / 180) *
Math.PI;
selectedPlacedModel.rotation[axis] =
radians;
const currentRotationDisplay =
document.getElementById(
"current-rotation"
);
if (currentRotationDisplay) {
currentRotationDisplay.textContent =
value;
}
if (currentRotationDisplay) {
currentRotationDisplay.textContent = value;
}
}
}
function openScaleMenu() {
if (!selectedPlacedModel) {
console.log(
"Kein Modell ausgewählt. Bitte wählen Sie ein Modell aus, bevor Sie es bearbeiten.",
);
if (!selectedPlacedModel) {
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;
return;
}
const currentScale = selectedPlacedModel.scale.x;
const currentScalePercent =
(
(currentScale - minScale) /
(maxScale - minScale)
) * 100;
const minScale = selectedPlacedModel.modelConfig.minScale;
const maxScale = selectedPlacedModel.modelConfig.maxScale;
const dynamicMenu =
document.getElementById(
"dynamic-menu"
);
const step = (maxScale - minScale) / 100;
const currentScalePercent =
((currentScale - minScale) / (maxScale - minScale)) * 100;
dynamicMenu.style.display =
"flex";
const dynamicMenu = document.getElementById("dynamic-menu");
dynamicMenu.style.display = "flex";
dynamicMenu.innerHTML = `
dynamicMenu.innerHTML = `
<h3>Skalierung anpassen</h3>
......@@ -903,69 +588,36 @@ function openScaleMenu() {
`;
}
function updateScale(percentValue, minScale, maxScale) {
if (selectedPlacedModel) {
const scale = minScale + (percentValue / 100) * (maxScale - minScale);
function updateScale(
percentValue,
minScale,
maxScale
) {
if (selectedPlacedModel) {
const scale =
minScale +
(percentValue / 100) *
(maxScale - minScale);
selectedPlacedModel.scale.set(
scale,
scale,
scale
);
const scaleValueDisplay =
document.getElementById(
"scale-value"
);
selectedPlacedModel.scale.set(scale, scale, scale);
const scaleValueDisplay = document.getElementById("scale-value");
if (scaleValueDisplay) {
scaleValueDisplay.textContent =
`${parseInt(percentValue, 10)}%`;
}
if (scaleValueDisplay) {
scaleValueDisplay.textContent = `${parseInt(percentValue, 10)}%`;
}
}
}
let moveDelta = 0.1;
function openMoveMenu() {
if (!selectedPlacedModel) {
console.log(
"Kein Modell ausgewählt. Bitte wählen Sie ein Modell aus, bevor Sie es bearbeiten.",
);
if (!selectedPlacedModel) {
console.log(
"Kein Modell ausgewählt. Bitte wählen Sie ein Modell aus, bevor Sie es bearbeiten."
);
return;
}
const dynamicMenu =
document.getElementById(
"dynamic-menu"
);
return;
}
dynamicMenu.style.display =
"flex";
const dynamicMenu = document.getElementById("dynamic-menu");
dynamicMenu.style.display = "flex";
dynamicMenu.innerHTML = `
dynamicMenu.innerHTML = `
<h3>Position anpassen</h3>
......@@ -1044,70 +696,38 @@ function openMoveMenu() {
`;
}
function updateMoveDelta(value) {
moveDelta = parseFloat(value);
function updateMoveDelta(
value
) {
moveDelta =
parseFloat(value);
const moveDeltaDisplay = document.getElementById("move-delta-display");
const moveDeltaDisplay =
document.getElementById(
"move-delta-display"
);
if (moveDeltaDisplay) {
moveDeltaDisplay.textContent =
moveDelta.toFixed(2);
}
if (moveDeltaDisplay) {
moveDeltaDisplay.textContent = moveDelta.toFixed(2);
}
}
function moveModelDynamic(
axis,
value
) {
if (selectedPlacedModel) {
selectedPlacedModel.position[axis] +=
value;
}
function moveModelDynamic(axis, value) {
if (selectedPlacedModel) {
selectedPlacedModel.position[axis] += value;
}
}
/* ========================= */
/* KARTENSTEUERUNG */
/* ========================= */
function refreshMapDialog() {
const mapDialog = document.getElementById("map-dialog");
const mapDialog =
document.getElementById(
'map-dialog'
);
mapDialog.style.display =
'flex';
mapDialog.style.display = "flex";
}
function closeMapDialog() {
const mapDialog = document.getElementById("map-dialog");
const mapDialog =
document.getElementById(
'map-dialog'
);
mapDialog.style.display =
'none';
mapDialog.style.display = "none";
}
/* ========================= */
/* AR-HANDLING */
/* ========================= */
......@@ -1117,61 +737,63 @@ async function activateXR(sceneData = null) {
document.body.appendChild(canvas);
const gl = canvas.getContext("webgl", {
xrCompatible: true,
});
// ========================================================
// Three.js WebXR renderer
// ========================================================
const renderer = new THREE.WebGLRenderer({
alpha: true,
antialias: true,
canvas,
context: gl,
});
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.matrixAutoUpdate = false;
// ========================================================
// AR lighting
// ========================================================
// Soft overall illumination
const ambientLight = new THREE.AmbientLight(0xffffff, 2.5);
scene.add(ambientLight);
// Sky/ground illumination
const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x888888, 1.5);
scene.add(hemisphereLight);
// Directional light for shadows and form
const directionalLight = new THREE.DirectionalLight(0xffffff, 1.5);
directionalLight.position.set(10, 20, 10);
scene.add(directionalLight);
// ========================================================
// Reticle
// ========================================================
const loader = new THREE.GLTFLoader();
const loader = new GLTFLoader();
loader.load(
"https://immersive-web.github.io/webxr-samples/media/gltf/reticle/reticle.gltf",
(gltf) => {
reticle = gltf.scene;
reticle.visible = false;
scene.add(reticle);
},
undefined,
(error) => {
console.error("Fehler beim Laden des Reticles:", error);
},
);
// ========================================================
......@@ -1188,11 +810,14 @@ async function activateXR(sceneData = null) {
requiredFeatures: ["hit-test"],
});
currentSession.updateRenderState({
baseLayer: new XRWebGLLayer(currentSession, gl),
});
// Let Three.js manage the XR render state / framebuffer.
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");
......@@ -1235,7 +860,6 @@ async function activateXR(sceneData = null) {
(position) => {
geoLocation = {
latitude: roundTo(position.coords.latitude, 5),
longitude: roundTo(position.coords.longitude, 5),
};
......@@ -1245,7 +869,7 @@ async function activateXR(sceneData = null) {
// Load saved scene models
// =================================================
if (sceneData) {
if (sceneData && Array.isArray(sceneData.models)) {
sceneData.models.forEach((model) => {
if (model.name) {
const modelDefinition = Object.values(models).find(
......@@ -1260,25 +884,27 @@ async function activateXR(sceneData = null) {
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) => {
if (model.rotation) {
placed.rotation._x = model.rotation._x;
placed.rotation._y = model.rotation._y;
placed.rotation._z = model.rotation._z;
placed.rotation.set(
model.rotation._x ?? model.rotation.x ?? 0,
model.rotation._y ?? model.rotation.y ?? 0,
model.rotation._z ?? model.rotation.z ?? 0,
);
}
if (model.scale) {
placed.scale.x = model.scale.x;
placed.scale.y = model.scale.y;
placed.scale.z = model.scale.z;
placed.scale.set(model.scale.x, model.scale.y, model.scale.z);
}
});
}
......@@ -1300,61 +926,45 @@ async function activateXR(sceneData = null) {
},
);
}
// ========================================================
// XR render loop
// Modern Three.js XR render loop
// ========================================================
currentSession.requestAnimationFrame(function onXRFrame(time, frame) {
currentSession.requestAnimationFrame(onXRFrame);
gl.bindFramebuffer(
gl.FRAMEBUFFER,
currentSession.renderState.baseLayer.framebuffer,
);
const pose = frame.getViewerPose(referenceSpace);
if (pose) {
const view = pose.views[0];
const viewport = currentSession.renderState.baseLayer.getViewport(view);
renderer.setSize(viewport.width, viewport.height);
camera.matrix.fromArray(view.transform.matrix);
camera.projectionMatrix.fromArray(view.projectionMatrix);
camera.updateMatrixWorld(true);
renderer.setAnimationLoop((time, frame) => {
if (!frame) {
return;
}
// =================================================
// Hit testing
// =================================================
// ======================================================
// Hit testing
// ======================================================
const hitTestResults = frame.getHitTestResults(hitTestSource);
const hitTestResults = frame.getHitTestResults(hitTestSource);
if (hitTestResults.length > 0) {
const hitPose = hitTestResults[0].getPose(referenceSpace);
if (hitTestResults.length > 0) {
const hitPose = hitTestResults[0].getPose(referenceSpace);
if (hitPose && reticle) {
reticle.visible = true;
reticle.position.set(
hitPose.transform.position.x,
hitPose.transform.position.y,
hitPose.transform.position.z,
);
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() {
soundTimeout = false;
}, 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 mymap;
let userIcon;
......@@ -98,7 +100,7 @@ function init_map() {
// Add currently placed models to map
// ========================================================
const models = getAllPlacedModels();
const models = window.getAllPlacedModels();
models.forEach((model) => {
const { lat, lng } = threeToLeaflet(model.position.x, model.position.z);
......@@ -210,7 +212,12 @@ function updateMap() {
}
// 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
// has not moved.
......@@ -323,3 +330,9 @@ function roundTo(n, digits) {
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() {
startScreen.appendChild(hftLogo);
/* ==========================================================
TITLE
========================================================== */
......@@ -190,7 +189,7 @@ function showStartScreen() {
predefinedButton2 = null;
// Start AR
activateXR(sceneData);
window.activateXR(sceneData);
};
/* ==========================================================
......@@ -198,7 +197,7 @@ function showStartScreen() {
========================================================== */
loadButton.onclick = () => {
loadSceneFromFile((data, fileName) => {
window.loadSceneFromFile((data, fileName) => {
if (data && fileName) {
sceneData = data;
sceneFileName = fileName;
......
......@@ -9,8 +9,15 @@
<link rel="stylesheet" href="style.css">
<script src="https://unpkg.com/three@0.126.0/build/three.js"></script>
<script src="https://unpkg.com/three@0.126.0/examples/js/loaders/GLTFLoader.js"></script>
<!-- Three.js (modern ES module setup) -->
<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>
<body>
......@@ -29,6 +36,7 @@
STANDARDMENÜ
============================================================ -->
<div id="menu-bar">
<div
......@@ -299,7 +307,7 @@
============================================================ -->
<div id="app-version">
v0.1.3
v0.1.186
</div>
......@@ -307,21 +315,22 @@
JAVASCRIPT
============================================================ -->
<script src="ar_start.js"></script>
<script src="ar_main.js"></script>
<!-- Debug console loads first so it can capture module errors/logs -->
<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
rel="stylesheet"
href="https://unpkg.com/leaflet/dist/leaflet.css" />
<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