"...img/git@transfer.hft-stuttgart.de:zedflow/test-k1.git" did not exist on "dba2f0499c85b8fd7ee7aca308458afdf98d9fbb"
Commit d2df398a authored by Alfakhori's avatar Alfakhori
Browse files

optimisation

parent 6d8f61ae
Pipeline #12467 passed with stage
in 26 seconds
...@@ -10,6 +10,10 @@ let geoLocation; ...@@ -10,6 +10,10 @@ let geoLocation;
const modelCache = new Map(); 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 menus = [ const menus = [
'menu-bar', 'menu-bar',
'add-menu', 'add-menu',
...@@ -18,6 +22,14 @@ const menus = [ ...@@ -18,6 +22,14 @@ const menus = [
'map-window' '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);
});
/* ========================= */ /* ========================= */
/* MODELLE */ /* MODELLE */
...@@ -158,11 +170,7 @@ function showMenu(menuId) { ...@@ -158,11 +170,7 @@ function showMenu(menuId) {
const isMapWindow = const isMapWindow =
menuId === 'map-window'; 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");
...@@ -175,14 +183,12 @@ function showMenu(menuId) { ...@@ -175,14 +183,12 @@ function showMenu(menuId) {
} }
// ======================================================== // PERFORMANCE: Use cached menu elements instead of
// Hide all menus // repeatedly querying the DOM.
// ========================================================
menus.forEach(id => { menus.forEach(id => {
const element = const element =
document.getElementById(id); menuElements[id];
if (element) { if (element) {
...@@ -194,17 +200,10 @@ function showMenu(menuId) { ...@@ -194,17 +200,10 @@ function showMenu(menuId) {
}); });
// ========================================================
// Close dynamic menu
// ========================================================
closeDynamicMenu(); closeDynamicMenu();
// ========================================================
// Menu-specific actions // Menu-specific actions
// ========================================================
if (menuId === 'menu-bar') { if (menuId === 'menu-bar') {
clearSelectedModel(); clearSelectedModel();
...@@ -214,10 +213,8 @@ function showMenu(menuId) { ...@@ -214,10 +213,8 @@ function showMenu(menuId) {
// 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 ( if (
...@@ -239,29 +236,30 @@ function showMenu(menuId) { ...@@ -239,29 +236,30 @@ function showMenu(menuId) {
function closeMap() { function closeMap() {
const mapWindow = const mapWindow =
document.getElementById("map-window"); menuElements['map-window'];
if (mapWindow) { if (mapWindow) {
mapWindow.style.display = "none"; mapWindow.style.display =
"none";
} }
// Show the main AR menu again // Show the main AR menu again
const menuBar = const menuBar =
document.getElementById("menu-bar"); menuElements['menu-bar'];
if (menuBar) { if (menuBar) {
menuBar.style.display = "flex"; menuBar.style.display =
"flex";
} }
// Stop the map update interval // Stop the map update interval
if (
if (typeof intervalId !== 'undefined') { typeof intervalId !== 'undefined'
) {
if (intervalId) { if (intervalId) {
...@@ -273,7 +271,6 @@ function closeMap() { ...@@ -273,7 +271,6 @@ function closeMap() {
// Clear selected model // Clear selected model
clearSelectedModel(); clearSelectedModel();
} }
...@@ -287,7 +284,8 @@ function closeDynamicMenu() { ...@@ -287,7 +284,8 @@ function closeDynamicMenu() {
if (dynamicMenu) { if (dynamicMenu) {
dynamicMenu.style.display = "none"; dynamicMenu.style.display =
"none";
} }
} }
...@@ -304,10 +302,11 @@ function showInfoDialog(message) { ...@@ -304,10 +302,11 @@ function showInfoDialog(message) {
"info-text" "info-text"
); );
infoText.textContent =
message;
infoText.textContent = message; infoDialog.style.display =
"flex";
infoDialog.style.display = "flex";
} }
...@@ -318,7 +317,8 @@ function closeInfoDialog() { ...@@ -318,7 +317,8 @@ function closeInfoDialog() {
"info-dialog" "info-dialog"
); );
infoDialog.style.display = "none"; infoDialog.style.display =
"none";
} }
...@@ -342,7 +342,8 @@ function loadModel(filePath) { ...@@ -342,7 +342,8 @@ function loadModel(filePath) {
reticle.position, reticle.position,
(model) => { (model) => {
selectedPlacedModel = model; selectedPlacedModel =
model;
highlightSelectedModel(); highlightSelectedModel();
...@@ -366,72 +367,109 @@ function placeModel( ...@@ -366,72 +367,109 @@ function placeModel(
); );
const loader = new THREE.GLTFLoader(); // PERFORMANCE: Reuse the global GLTFLoader.
// Do not create a new loader for every model.
if (modelCache.has(filePath)) { if (modelCache.has(filePath)) {
// Use the already loaded model
const cachedModel = modelCache.get(filePath); // Use the already loaded model
const model = cachedModel.clone(true); const cachedModel =
modelCache.get(filePath);
if (modelConfig && modelConfig.scale) {
model.scale.set( const model =
modelConfig.scale.x, cachedModel.clone(true);
modelConfig.scale.y,
modelConfig.scale.z,
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.modelConfig =
modelConfig;
model.isPlacedModel = true; scene.add(model);
model.modelConfig = modelConfig;
scene.add(model);
if (onPlace) { if (onPlace) {
onPlace(model); onPlace(model);
} }
} else { } else {
// Model has not been loaded yet
loader.load(
filePath,
(gltf) => { // Model has not been loaded yet
// Store the original model in the cache gltfLoader.load(
modelCache.set(filePath, gltf.scene);
const model = gltf.scene.clone(true); filePath,
if (modelConfig && modelConfig.scale) { (gltf) => {
model.scale.set(
modelConfig.scale.x,
modelConfig.scale.y,
modelConfig.scale.z,
);
}
model.position.copy(position); // Store the original model in the cache
modelCache.set(
filePath,
gltf.scene
);
model.isPlacedModel = true; const model =
gltf.scene.clone(true);
model.modelConfig = modelConfig;
scene.add(model); if (
modelConfig &&
modelConfig.scale
) {
if (onPlace) { model.scale.set(
onPlace(model); modelConfig.scale.x,
} modelConfig.scale.y,
}, modelConfig.scale.z
);
}
undefined,
(error) => { model.position.copy(
console.error("Fehler beim Laden des Modells:", error); position
}, );
);
} model.isPlacedModel =
true;
model.modelConfig =
modelConfig;
scene.add(model);
if (onPlace) {
onPlace(model);
}
},
undefined,
(error) => {
console.error(
"Fehler beim Laden des Modells:",
error
);
}
);
}
} }
...@@ -446,9 +484,13 @@ function selectModel(modelId) { ...@@ -446,9 +484,13 @@ function selectModel(modelId) {
model.file model.file
) { ) {
loadModel(model.file); loadModel(
model.file
);
showMenu('menu-bar'); showMenu(
'menu-bar'
);
} }
} }
...@@ -485,7 +527,9 @@ function selectModelFromScene(event) { ...@@ -485,7 +527,9 @@ function selectModelFromScene(event) {
); );
if (intersects.length > 0) { if (
intersects.length > 0
) {
let selectedObject = let selectedObject =
intersects[0].object; intersects[0].object;
...@@ -502,7 +546,6 @@ function selectModelFromScene(event) { ...@@ -502,7 +546,6 @@ function selectModelFromScene(event) {
// Reticle cannot be selected // Reticle cannot be selected
if ( if (
selectedObject === reticle selectedObject === reticle
) { ) {
...@@ -521,7 +564,9 @@ function selectModelFromScene(event) { ...@@ -521,7 +564,9 @@ function selectModelFromScene(event) {
highlightSelectedModel(); highlightSelectedModel();
showMenu("edit-menu"); showMenu(
"edit-menu"
);
} }
} }
...@@ -536,13 +581,16 @@ function clearSelectedModel() { ...@@ -536,13 +581,16 @@ function clearSelectedModel() {
if (child.isMesh) { if (child.isMesh) {
child.material.emissive child.material.emissive
.setHex(0x000000); .setHex(
0x000000
);
} }
} }
); );
selectedPlacedModel = null; selectedPlacedModel =
null;
} }
} }
...@@ -560,7 +608,9 @@ function highlightSelectedModel() { ...@@ -560,7 +608,9 @@ function highlightSelectedModel() {
if (child.isMesh) { if (child.isMesh) {
child.material.emissive child.material.emissive
.setHex(0xff0000); .setHex(
0xff0000
);
} }
} }
); );
...@@ -578,7 +628,9 @@ function removeHighlightFromSelectedModel() { ...@@ -578,7 +628,9 @@ function removeHighlightFromSelectedModel() {
if (child.isMesh) { if (child.isMesh) {
child.material.emissive child.material.emissive
.setHex(0x000000); .setHex(
0x000000
);
} }
} }
); );
...@@ -598,7 +650,9 @@ function removeHighlightFromAllModels() { ...@@ -598,7 +650,9 @@ function removeHighlightFromAllModels() {
) { ) {
child.material.emissive child.material.emissive
.setHex(0x000000); .setHex(
0x000000
);
} }
} }
); );
...@@ -628,7 +682,9 @@ function deleteModel() { ...@@ -628,7 +682,9 @@ function deleteModel() {
} }
function confirmDelete(shouldDelete) { function confirmDelete(
shouldDelete
) {
const deleteDialog = const deleteDialog =
document.getElementById( document.getElementById(
...@@ -649,9 +705,12 @@ function confirmDelete(shouldDelete) { ...@@ -649,9 +705,12 @@ function confirmDelete(shouldDelete) {
selectedPlacedModel selectedPlacedModel
); );
selectedPlacedModel = null; selectedPlacedModel =
null;
showMenu('menu-bar'); showMenu(
'menu-bar'
);
} }
} }
...@@ -662,15 +721,18 @@ function completeEditing() { ...@@ -662,15 +721,18 @@ function completeEditing() {
closeDynamicMenu(); closeDynamicMenu();
selectedPlacedModel = null; selectedPlacedModel =
null;
document.getElementById( document.getElementById(
'edit-menu' 'edit-menu'
).style.display = 'none'; ).style.display =
'none';
document.getElementById( document.getElementById(
'menu-bar' 'menu-bar'
).style.display = 'flex'; ).style.display =
'flex';
} }
...@@ -734,12 +796,16 @@ function openRotationMenu() { ...@@ -734,12 +796,16 @@ function openRotationMenu() {
} }
function updateRotation(axis, value) { function updateRotation(
axis,
value
) {
if (selectedPlacedModel) { if (selectedPlacedModel) {
const radians = const radians =
(value / 180) * Math.PI; (value / 180) *
Math.PI;
selectedPlacedModel.rotation[axis] = selectedPlacedModel.rotation[axis] =
...@@ -786,7 +852,8 @@ function openScaleMenu() { ...@@ -786,7 +852,8 @@ function openScaleMenu() {
const step = const step =
(maxScale - minScale) / 100; (maxScale - minScale) /
100;
const currentScalePercent = const currentScalePercent =
...@@ -967,7 +1034,6 @@ function openMoveMenu() { ...@@ -967,7 +1034,6 @@ function openMoveMenu() {
src="assets/icons/down-arrow.png" src="assets/icons/down-arrow.png"
alt="Down" alt="Down"
/> />
</div> </div>
</div> </div>
...@@ -979,7 +1045,9 @@ function openMoveMenu() { ...@@ -979,7 +1045,9 @@ function openMoveMenu() {
} }
function updateMoveDelta(value) { function updateMoveDelta(
value
) {
moveDelta = moveDelta =
parseFloat(value); parseFloat(value);
...@@ -1044,557 +1112,294 @@ function closeMapDialog() { ...@@ -1044,557 +1112,294 @@ function closeMapDialog() {
/* AR-HANDLING */ /* AR-HANDLING */
/* ========================= */ /* ========================= */
async function activateXR( async function activateXR(sceneData = null) {
sceneData = null const canvas = document.createElement("canvas");
) {
const canvas =
document.createElement('canvas');
document.body.appendChild(
canvas
);
const gl =
canvas.getContext(
'webgl',
{
xrCompatible: true
}
);
const renderer =
new THREE.WebGLRenderer({
alpha: true, document.body.appendChild(canvas);
canvas, const gl = canvas.getContext("webgl", {
xrCompatible: true,
});
context: gl const renderer = new THREE.WebGLRenderer({
}); alpha: true,
canvas,
context: gl,
});
renderer.autoClear = false;
renderer.autoClear = false; scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera();
scene = camera.matrixAutoUpdate = false;
new THREE.Scene();
// ========================================================
// AR lighting
// ========================================================
camera = // Soft overall illumination
new THREE.PerspectiveCamera(); const ambientLight = new THREE.AmbientLight(0xffffff, 2.5);
scene.add(ambientLight);
camera.matrixAutoUpdate = // Sky/ground illumination
false; const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x888888, 1.5);
scene.add(hemisphereLight);
// ======================================================== // Directional light for shadows and form
// AR lighting const directionalLight = new THREE.DirectionalLight(0xffffff, 1.5);
// ========================================================
// Soft overall illumination directionalLight.position.set(10, 20, 10);
const ambientLight = new THREE.AmbientLight(
0xffffff,
2.5
);
scene.add(ambientLight); scene.add(directionalLight);
// ========================================================
// Reticle
// ========================================================
// Sky/ground illumination const loader = new THREE.GLTFLoader();
const hemisphereLight = new THREE.HemisphereLight(
0xffffff,
0x888888,
1.5
);
scene.add(hemisphereLight); loader.load(
"https://immersive-web.github.io/webxr-samples/media/gltf/reticle/reticle.gltf",
(gltf) => {
reticle = gltf.scene;
// Directional light for shadows and form reticle.visible = false;
const directionalLight = new THREE.DirectionalLight(
0xffffff,
1.5
);
directionalLight.position.set(
10,
20,
10
);
scene.add(directionalLight);
scene.add(reticle);
},
);
// ======================================================== // ========================================================
// Reticle // Start WebXR AR session
// ======================================================== // ========================================================
const loader = currentSession = await navigator.xr.requestSession("immersive-ar", {
new THREE.GLTFLoader(); optionalFeatures: ["dom-overlay"],
domOverlay: {
root: document.body,
},
loader.load( requiredFeatures: ["hit-test"],
});
"https://immersive-web.github.io/webxr-samples/media/gltf/reticle/reticle.gltf", currentSession.updateRenderState({
baseLayer: new XRWebGLLayer(currentSession, gl),
});
(gltf) => { const referenceSpace = await currentSession.requestReferenceSpace("local");
reticle = const viewerSpace = await currentSession.requestReferenceSpace("viewer");
gltf.scene;
reticle.visible = const hitTestSource = await currentSession.requestHitTestSource({
false; space: viewerSpace,
});
scene.add(reticle); // ========================================================
} // Show main menu
); // ========================================================
menuElements["menu-bar"].style.display = "flex";
// ======================================================== // ========================================================
// Start WebXR AR session // AR session ended
// ======================================================== // ========================================================
currentSession = currentSession.addEventListener("end", () => {
await navigator.xr.requestSession( currentSession = null;
'immersive-ar',
{
optionalFeatures: [
"dom-overlay"
],
domOverlay: { // Hide dynamic menu
root: document.body
},
requiredFeatures: [ document.getElementById("dynamic-menu").style.display = "none";
'hit-test'
]
}
);
// Hide all application menus
currentSession.updateRenderState({ menus.forEach((id) => {
const element = menuElements[id];
baseLayer: if (element) {
new XRWebGLLayer( element.style.display = "none";
currentSession, }
gl
)
}); });
});
// ========================================================
// Select objects in AR
// ========================================================
const referenceSpace = canvas.addEventListener("pointerdown", selectModelFromScene);
await currentSession.requestReferenceSpace(
'local'
);
const viewerSpace =
await currentSession.requestReferenceSpace(
'viewer'
);
// ========================================================
// Geolocation
// ========================================================
const hitTestSource = if (navigator.geolocation) {
await currentSession.requestHitTestSource({ navigator.geolocation.getCurrentPosition(
space: viewerSpace (position) => {
}); geoLocation = {
latitude: roundTo(position.coords.latitude, 5),
longitude: roundTo(position.coords.longitude, 5),
};
// ======================================================== console.log("GeoLocation: " + JSON.stringify(geoLocation));
// Show main menu
// ========================================================
document.getElementById( // =================================================
'menu-bar' // Load saved scene models
).style.display = 'flex'; // =================================================
if (sceneData) {
sceneData.models.forEach((model) => {
if (model.name) {
const modelDefinition = Object.values(models).find(
(m) => model.name === m.name,
);
// ======================================================== if (!modelDefinition) {
// AR session ended console.error("Modell nicht gefunden:", model.name);
// ========================================================
currentSession.addEventListener( return;
"end", }
() => {
currentSession = const filePath = modelDefinition.file;
null;
const { x, z } = leafletToThree(model.lat, model.lng);
// Hide dynamic menu const positionVector = new THREE.Vector3(x, model.position.y, z);
document.getElementById( placeModel(filePath, positionVector, (placed) => {
"dynamic-menu" if (model.rotation) {
).style.display = placed.rotation._x = model.rotation._x;
"none";
placed.rotation._y = model.rotation._y;
// Hide all application menus placed.rotation._z = model.rotation._z;
}
menus.forEach(id => {
const element = if (model.scale) {
document.getElementById(id); placed.scale.x = model.scale.x;
if (element) { placed.scale.y = model.scale.y;
element.style.display = placed.scale.z = model.scale.z;
'none';
} }
}); });
}
});
} }
); },
(error) => {
console.error(
"Fehler bei der Geolokalisierung:",
JSON.stringify(error),
);
},
// ======================================================== {
// Select objects in AR enableHighAccuracy: true,
// ======================================================== maximumAge: 2000,
timeout: 5000,
canvas.addEventListener( },
"pointerdown",
selectModelFromScene
); );
}
// ========================================================
// XR render loop
// ========================================================
currentSession.requestAnimationFrame(function onXRFrame(time, frame) {
currentSession.requestAnimationFrame(onXRFrame);
// ======================================================== gl.bindFramebuffer(
// Geolocation gl.FRAMEBUFFER,
// ======================================================== currentSession.renderState.baseLayer.framebuffer,
);
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
position => {
geoLocation = {
latitude:
roundTo(
position.coords.latitude,
5
),
longitude:
roundTo(
position.coords.longitude,
5
)
};
console.log(
"GeoLocation: " +
JSON.stringify(
geoLocation
)
);
// =================================================
// Load saved scene models
// =================================================
if (sceneData) {
sceneData.models.forEach(
(model) => {
if (model.name) {
const modelDefinition =
Object.values(models)
.find(
m =>
model.name ===
m.name
);
if (!modelDefinition) {
console.error(
"Modell nicht gefunden:",
model.name
);
return;
}
const filePath =
modelDefinition.file;
const {
x,
z
} =
leafletToThree(
model.lat,
model.lng
);
const positionVector =
new THREE.Vector3(
x,
model.position.y,
z
);
const pose = frame.getViewerPose(referenceSpace);
placeModel( if (pose) {
filePath, const view = pose.views[0];
positionVector,
(placed) => {
if ( const viewport = currentSession.renderState.baseLayer.getViewport(view);
model.rotation
) {
placed.rotation._x = renderer.setSize(viewport.width, viewport.height);
model.rotation._x;
placed.rotation._y = camera.matrix.fromArray(view.transform.matrix);
model.rotation._y;
placed.rotation._z = camera.projectionMatrix.fromArray(view.projectionMatrix);
model.rotation._z;
}
camera.updateMatrixWorld(true);
if ( // =================================================
model.scale // Hit testing
) { // =================================================
placed.scale.x = const hitTestResults = frame.getHitTestResults(hitTestSource);
model.scale.x;
placed.scale.y = if (hitTestResults.length > 0) {
model.scale.y; const hitPose = hitTestResults[0].getPose(referenceSpace);
placed.scale.z = reticle.visible = true;
model.scale.z;
}
}
);
}
}
);
}
},
error => { reticle.position.set(
hitPose.transform.position.x,
console.error( hitPose.transform.position.y,
"Fehler bei der Geolokalisierung:",
JSON.stringify(error)
);
},
{ hitPose.transform.position.z,
enableHighAccuracy: true,
maximumAge: 2000,
timeout: 5000
}
); );
}
// ========================================================
// 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
);
// =================================================
// Hit testing
// =================================================
const hitTestResults =
frame.getHitTestResults(
hitTestSource
);
if (
hitTestResults.length > 0
) {
const hitPose =
hitTestResults[0]
.getPose(
referenceSpace
);
reticle.visible =
true;
reticle.position.set(
hitPose.transform.position.x,
hitPose.transform.position.y,
hitPose.transform.position.z
);
reticle.updateMatrixWorld(
true
);
}
reticle.updateMatrixWorld(true);
}
// ================================================= // =================================================
// Render AR scene // Render AR scene
// ================================================= // =================================================
renderer.render( renderer.render(scene, camera);
scene, }
camera });
);
}
}
);
} }
/* ========================= */ /* ========================= */
/* AR EXIT */ /* AR EXIT */
/* ========================= */ /* ========================= */
function exitAR() { function exitAR() {
document.getElementById("confirmation-dialog").style.display = "flex";
document.getElementById(
'confirmation-dialog'
).style.display =
'flex';
} }
function confirmExit(shouldExit) {
if (shouldExit && currentSession) {
currentSession.end();
}
function confirmExit( document.getElementById("confirmation-dialog").style.display = "none";
shouldExit
) {
if (
shouldExit &&
currentSession
) {
currentSession.end();
}
document.getElementById(
'confirmation-dialog'
).style.display =
'none';
} }
/* ========================= */ /* ========================= */
/* BENUTZERINTERAKTIONEN */ /* BENUTZERINTERAKTIONEN */
/* ========================= */ /* ========================= */
let soundTimeout = false; let soundTimeout = false;
function playButtonSound() { function playButtonSound() {
if (!soundTimeout) {
const sound = document.getElementById("button-sound");
if (!soundTimeout) { sound.currentTime = 0;
const sound =
document.getElementById(
"button-sound"
);
sound.currentTime =
0;
sound.play();
sound.play(); soundTimeout = true;
setTimeout(() => {
soundTimeout = soundTimeout = false;
true; }, 200);
}
setTimeout(
() => {
soundTimeout =
false;
},
200
);
}
} }
\ No newline at end of file
...@@ -4,6 +4,18 @@ let userIcon; ...@@ -4,6 +4,18 @@ let userIcon;
let intervalId; let intervalId;
let modeMarkers = []; let modeMarkers = [];
// PERFORMANCE: Reuse one Vector3 instead of allocating a new
// Vector3 every time updateMap() runs.
let cameraPosition = new THREE.Vector3();
// PERFORMANCE: Cache the map window element once.
let mapWindow = document.getElementById("map-window");
// PERFORMANCE: Remember the last mapped camera position so Leaflet
// is updated only after the user has actually moved.
let lastMapX = null;
let lastMapZ = null;
// ============================================================ // ============================================================
// Proposed plan overlay // Proposed plan overlay
// ============================================================ // ============================================================
...@@ -24,476 +36,289 @@ let planVisible = false; ...@@ -24,476 +36,289 @@ let planVisible = false;
// Longitude = 8.010410216 // Longitude = 8.010410216
const planBounds = [ const planBounds = [
[48.405858385, 7.997080070], // Southwest / bottom-left [48.405858385, 7.99708007],
[48.414966828, 8.010410216] // Northeast / top-right [48.414966828, 8.010410216],
]; ];
const earthRadius = 6378137; // Erdradius in Metern (WGS84) const earthRadius = 6378137;
// ============================================================ // ============================================================
// Initialize map // Initialize map
// ============================================================ // ============================================================
function init_map() { function init_map() {
const mapContainer = document.getElementById("map-container");
const mapContainer = document.getElementById("map-container"); if (mapContainer) {
if (!init) {
if (mapContainer) { mymap = L.map(mapContainer);
if (!init) {
// ====================================================
// Create Leaflet map
// ====================================================
mymap = L.map(mapContainer);
// ====================================================
// OpenStreetMap Germany basemap
// ====================================================
var OpenStreetMap_DE = L.tileLayer(
'https://tile.openstreetmap.de/{z}/{x}/{y}.png',
{
//maxZoom: 20,
attribution:
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}
);
OpenStreetMap_DE.addTo(mymap);
// ====================================================
// User icon
// ====================================================
userIcon = L.icon({
iconUrl: 'assets/previewImages/map-user.png',
iconSize: [32, 32],
iconAnchor: [16, 32],
popupAnchor: [0, -32]
});
// ====================================================
// Initial map position
// ====================================================
//
// The map is centered on the proposed plan instead
// of the user's current GPS position.
//
// We use fitBounds() so the whole plan is visible.
//
mymap.fitBounds(planBounds, {
padding: [20, 20]
});
// ====================================================
// User position marker
// ====================================================
userIcon = L.marker(
[geoLocation.latitude, geoLocation.longitude],
{
icon: userIcon
}
)
.bindPopup('Ihre Position')
.addTo(mymap);
init = true;
}
// ========================================================
// Remove old model markers
// ========================================================
modeMarkers.forEach(marker => {
marker.remove();
});
// Clear marker array
modeMarkers = [];
// ========================================================
// Add currently placed models to map
// ========================================================
const models = getAllPlacedModels();
models.forEach(model => {
const { lat, lng } = threeToLeaflet(
model.position.x,
model.position.z
);
var OpenStreetMap_DE = L.tileLayer(
"https://tile.openstreetmap.de/{z}/{x}/{y}.png",
{
attribution:
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
},
);
OpenStreetMap_DE.addTo(mymap);
userIcon = L.icon({
iconUrl: "assets/previewImages/map-user.png",
iconSize: [32, 32],
iconAnchor: [16, 32],
popupAnchor: [0, -32],
});
mymap.fitBounds(planBounds, {
padding: [20, 20],
});
userIcon = L.marker([geoLocation.latitude, geoLocation.longitude], {
icon: userIcon,
})
.bindPopup("Ihre Position")
.addTo(mymap);
init = true;
}
const modelMarker = L.marker( // ========================================================
[lat, lng], // Remove old model markers
{ // ========================================================
icon: L.icon({
iconUrl: model.modelConfig.image,
iconSize: [16, 16],
iconAnchor: [16, 32],
popupAnchor: [0, -32]
})
}
)
.addTo(mymap)
.bindPopup(
model.modelConfig?.name || "Unbenannt"
);
modeMarkers.forEach((marker) => {
marker.remove();
});
modeMarkers.push(modelMarker); modeMarkers = [];
});
// ========================================================
// Add currently placed models to map
// ========================================================
// ======================================================== const models = getAllPlacedModels();
// Add proposed plan
// ========================================================
addPlanOverlay(); models.forEach((model) => {
const { lat, lng } = threeToLeaflet(model.position.x, model.position.z);
const modelMarker = L.marker([lat, lng], {
icon: L.icon({
iconUrl: model.modelConfig.image,
iconSize: [16, 16],
iconAnchor: [16, 32],
popupAnchor: [0, -32],
}),
})
.addTo(mymap)
.bindPopup(model.modelConfig?.name || "Unbenannt");
// ======================================================== modeMarkers.push(modelMarker);
// Make sure Leaflet knows the map container dimensions });
// ========================================================
setTimeout(() => { // ========================================================
// Add proposed plan
// ========================================================
centerOnPlan(); addPlanOverlay();
}, 100); // ========================================================
// Make sure Leaflet knows the map container dimensions
// ========================================================
setTimeout(() => {
centerOnPlan();
}, 100);
// ======================================================== // ========================================================
// Update map position continuously // Update map position continuously
// ======================================================== // ========================================================
intervalId = setInterval(updateMap, 500); // PERFORMANCE: Start only one map update interval.
if (!intervalId) {
intervalId = setInterval(updateMap, 500);
} }
}
} }
// ============================================================ // ============================================================
// Center map on proposed plan // Center map on proposed plan
// ============================================================ // ============================================================
function centerOnPlan() { function centerOnPlan() {
if (!mymap) {
console.error("Map ist noch nicht initialisiert.");
if (!mymap) { return;
}
console.error(
"Map ist noch nicht initialisiert."
);
return;
}
// Tell Leaflet to recalculate the map container size.
// This is important because map-window may initially
// be hidden when the map is initialized.
mymap.invalidateSize();
mymap.invalidateSize();
// Center and zoom the map on the proposed plan. mymap.fitBounds(planBounds, {
padding: [20, 20],
mymap.fitBounds( });
planBounds,
{
padding: [20, 20]
}
);
} }
// ============================================================ // ============================================================
// Add georeferenced proposed plan // Add georeferenced proposed plan
// ============================================================ // ============================================================
//
// Original coordinate system:
// DHDN / Gauss-Krüger Zone 3
// EPSG:31467
//
// Image extent:
//
// Southwest:
// X = 3425822.00
// Y = 5363602.00
//
// Northeast:
// X = 3426822.00
// Y = 5364602.00
//
// Converted to WGS84:
//
// Southwest:
// Latitude = 48.405858385
// Longitude = 7.997080070
//
// Northeast:
// Latitude = 48.414966828
// Longitude = 8.010410216
//
// Image:
//
// assets/plans/proposed-plan.png
// ============================================================
function addPlanOverlay() { function addPlanOverlay() {
if (!mymap) {
console.error("Map ist noch nicht initialisiert.");
if (!mymap) { return;
}
console.error(
"Map ist noch nicht initialisiert."
);
return;
}
// ========================================================
// If overlay already exists, show it
// ========================================================
if (planOverlay) {
planOverlay.addTo(mymap);
planVisible = true;
return;
}
// ======================================================== if (planOverlay) {
// Create image overlay planOverlay.addTo(mymap);
// ========================================================
planOverlay = L.imageOverlay(
'assets/plans/proposed-plan.png',
planBounds,
{
// Transparency
opacity: 0.7,
// The plan does not capture mouse/touch events planVisible = true;
interactive: false
}
);
return;
}
// ======================================================== planOverlay = L.imageOverlay("assets/plans/proposed-plan.png", planBounds, {
// Add overlay to map opacity: 0.7,
// ======================================================== interactive: false,
});
planOverlay.addTo(mymap); planOverlay.addTo(mymap);
planVisible = true; planVisible = true;
} }
// ============================================================ // ============================================================
// Update map // Update map
// ============================================================ // ============================================================
function updateMap() { function updateMap() {
// The interval is normally stopped by closeMap().
// This guard also handles cases where the map is hidden
// by another UI action.
if (mapWindow && mapWindow.style.display === "none" && intervalId) {
clearInterval(intervalId);
if ( intervalId = null;
document.getElementById("map-window").style.display === 'none'
&& intervalId
) {
clearInterval(intervalId);
intervalId = null;
return;
}
return;
}
let vec = new THREE.Vector3(); if (!camera || !userIcon) {
return;
}
const userPosition = camera.getWorldPosition(vec); // PERFORMANCE: Reuse the same Vector3 object.
camera.getWorldPosition(cameraPosition);
// PERFORMANCE: Do not update Leaflet if the user
// has not moved.
if (
lastMapX !== null &&
Math.abs(cameraPosition.x - lastMapX) < 0.1 &&
Math.abs(cameraPosition.z - lastMapZ) < 0.1
) {
return;
}
const { lat, lng } = threeToLeaflet( lastMapX = cameraPosition.x;
userPosition.x, lastMapZ = cameraPosition.z;
userPosition.z
);
const { lat, lng } = threeToLeaflet(cameraPosition.x, cameraPosition.z);
if (userIcon) { userIcon.setLatLng([lat, lng]);
userIcon.setLatLng([
lat,
lng
]);
}
} }
// ============================================================ // ============================================================
// Convert Three.js coordinates to Leaflet coordinates // Convert Three.js coordinates to Leaflet coordinates
// ============================================================ // ============================================================
function threeToLeaflet(threeX, threeZ) { function threeToLeaflet(threeX, threeZ) {
if (!geoLocation) {
console.error("GeoLocation ist noch nicht verfügbar");
if (!geoLocation) { return;
}
console.error(
"GeoLocation ist noch nicht verfügbar"
);
return;
}
const originLat = geoLocation.latitude;
const originLng = geoLocation.longitude;
const deltaLat =
(threeZ / earthRadius) *
(180 / Math.PI);
const deltaLng = const originLat = geoLocation.latitude;
(
threeX /
(
earthRadius *
Math.cos(
Math.PI * originLat / 180
)
)
) *
(180 / Math.PI);
const originLng = geoLocation.longitude;
const globalLat = const deltaLat = (threeZ / earthRadius) * (180 / Math.PI);
originLat + deltaLat;
const deltaLng =
(threeX / (earthRadius * Math.cos((Math.PI * originLat) / 180))) *
(180 / Math.PI);
const globalLng = const globalLat = originLat + deltaLat;
originLng + deltaLng;
const globalLng = originLng + deltaLng;
return { return {
lat: globalLat, lat: globalLat,
lng: globalLng lng: globalLng,
}; };
} }
// ============================================================ // ============================================================
// Convert Leaflet coordinates to Three.js coordinates // Convert Leaflet coordinates to Three.js coordinates
// ============================================================ // ============================================================
function leafletToThree(lat, lng) { function leafletToThree(lat, lng) {
if (!geoLocation) {
console.error("GeoLocation ist noch nicht verfügbar");
if (!geoLocation) { return;
}
console.error(
"GeoLocation ist noch nicht verfügbar"
);
return;
}
const originLat = geoLocation.latitude;
const originLng = geoLocation.longitude;
const deltaLat =
lat - originLat;
const originLat = geoLocation.latitude;
const deltaLng = const originLng = geoLocation.longitude;
lng - originLng;
const deltaLat = lat - originLat;
const threeZ = const deltaLng = lng - originLng;
deltaLat *
(Math.PI / 180) *
earthRadius;
const threeZ = deltaLat * (Math.PI / 180) * earthRadius;
const threeX = const threeX =
deltaLng * deltaLng *
(Math.PI / 180) * (Math.PI / 180) *
earthRadius * earthRadius *
Math.cos( Math.cos((Math.PI * originLat) / 180);
Math.PI * originLat / 180
);
return {
return { x: threeX,
x: threeX, z: threeZ,
z: threeZ };
};
} }
// ============================================================ // ============================================================
// Round number // Round number
// ============================================================ // ============================================================
function roundTo(n, digits) { function roundTo(n, digits) {
var negative = false;
var negative = false; if (digits === undefined) {
digits = 0;
}
if (digits === undefined) { if (n < 0) {
negative = true;
digits = 0;
}
n = n * -1;
}
if (n < 0) { var multiplicator = Math.pow(10, digits);
negative = true;
n = n * -1;
}
var multiplicator = n = parseFloat((n * multiplicator).toFixed(11));
Math.pow(10, digits);
n = (Math.round(n) / multiplicator).toFixed(digits);
n = parseFloat( if (negative) {
(n * multiplicator).toFixed(11) n = (n * -1).toFixed(digits);
); }
return parseFloat(n);
n = }
(
Math.round(n) /
multiplicator
).toFixed(digits);
if (negative) {
n =
(n * -1).toFixed(digits);
}
return parseFloat(n);
}
\ No newline at end of file
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