Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
Alfakhori
Gengenbach
Commits
d2df398a
Commit
d2df398a
authored
Sep 29, 2026
by
Alfakhori
Browse files
optimisation
parent
6d8f61ae
Pipeline
#12467
passed with stage
in 26 seconds
Changes
2
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
public/ar_main.js
View file @
d2df398a
...
...
@@ -10,6 +10,10 @@ 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
menus
=
[
'
menu-bar
'
,
'
add-menu
'
,
...
...
@@ -18,6 +22,14 @@ const menus = [
'
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 */
...
...
@@ -158,11 +170,7 @@ function showMenu(menuId) {
const
isMapWindow
=
menuId
===
'
map-window
'
;
// ========================================================
// Check location before opening map
// ========================================================
if
(
isMapWindow
&&
!
geoLocation
)
{
console
.
log
(
"
Standort nicht geladen
"
);
...
...
@@ -175,14 +183,12 @@ function showMenu(menuId) {
}
// ========================================================
// Hide all menus
// ========================================================
// PERFORMANCE: Use cached menu elements instead of
// repeatedly querying the DOM.
menus
.
forEach
(
id
=>
{
const
element
=
document
.
get
Element
ById
(
id
)
;
menu
Element
s
[
id
]
;
if
(
element
)
{
...
...
@@ -194,17 +200,10 @@ function showMenu(menuId) {
});
// ========================================================
// Close dynamic menu
// ========================================================
closeDynamicMenu
();
// ========================================================
// Menu-specific actions
// ========================================================
if
(
menuId
===
'
menu-bar
'
)
{
clearSelectedModel
();
...
...
@@ -214,10 +213,8 @@ function showMenu(menuId) {
// Initialize map
init_map
();
// Leaflet needs to know the actual size of the
// visible map container.
setTimeout
(()
=>
{
if
(
...
...
@@ -239,29 +236,30 @@ function showMenu(menuId) {
function
closeMap
()
{
const
mapWindow
=
document
.
getElementById
(
"
map-window
"
);
menuElements
[
'
map-window
'
];
if
(
mapWindow
)
{
mapWindow
.
style
.
display
=
"
none
"
;
mapWindow
.
style
.
display
=
"
none
"
;
}
// Show the main AR menu again
const
menuBar
=
document
.
get
Element
ById
(
"
menu-bar
"
)
;
menu
Element
s
[
'
menu-bar
'
]
;
if
(
menuBar
)
{
menuBar
.
style
.
display
=
"
flex
"
;
menuBar
.
style
.
display
=
"
flex
"
;
}
// Stop the map update interval
if
(
typeof
intervalId
!==
'
undefined
'
)
{
if
(
typeof
intervalId
!==
'
undefined
'
)
{
if
(
intervalId
)
{
...
...
@@ -273,7 +271,6 @@ function closeMap() {
// Clear selected model
clearSelectedModel
();
}
...
...
@@ -287,7 +284,8 @@ function closeDynamicMenu() {
if
(
dynamicMenu
)
{
dynamicMenu
.
style
.
display
=
"
none
"
;
dynamicMenu
.
style
.
display
=
"
none
"
;
}
}
...
...
@@ -304,10 +302,11 @@ function showInfoDialog(message) {
"
info-text
"
);
infoText
.
textContent
=
message
;
infoText
.
textContent
=
message
;
infoDialog
.
style
.
display
=
"
flex
"
;
infoDialog
.
style
.
display
=
"
flex
"
;
}
...
...
@@ -318,7 +317,8 @@ function closeInfoDialog() {
"
info-dialog
"
);
infoDialog
.
style
.
display
=
"
none
"
;
infoDialog
.
style
.
display
=
"
none
"
;
}
...
...
@@ -342,7 +342,8 @@ function loadModel(filePath) {
reticle
.
position
,
(
model
)
=>
{
selectedPlacedModel
=
model
;
selectedPlacedModel
=
model
;
highlightSelectedModel
();
...
...
@@ -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
))
{
// Use the already loaded model
const
cachedModel
=
modelCache
.
get
(
filePath
);
const
model
=
cachedModel
.
clone
(
true
);
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
);
const
model
=
cachedModel
.
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
.
modelConfig
=
modelConfig
;
model
.
isPlacedModel
=
true
;
model
.
modelConfig
=
modelConfig
;
scene
.
add
(
model
);
scene
.
add
(
model
);
if
(
onPlace
)
{
onPlace
(
model
);
}
if
(
onPlace
)
{
onPlace
(
model
);
}
}
else
{
// Model has not been loaded yet
loader
.
load
(
filePath
,
(
gltf
)
=>
{
// Store the original model in the cache
modelCache
.
set
(
filePath
,
gltf
.
scene
);
// Model has not been loaded yet
gltfLoader
.
load
(
const
model
=
gltf
.
scene
.
clone
(
true
);
filePath
,
if
(
modelConfig
&&
modelConfig
.
scale
)
{
model
.
scale
.
set
(
modelConfig
.
scale
.
x
,
modelConfig
.
scale
.
y
,
modelConfig
.
scale
.
z
,
);
}
(
gltf
)
=>
{
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
)
{
onPlace
(
model
);
}
},
model
.
scale
.
set
(
modelConfig
.
scale
.
x
,
modelConfig
.
scale
.
y
,
modelConfig
.
scale
.
z
);
}
undefined
,
(
error
)
=>
{
console
.
error
(
"
Fehler beim Laden des Modells:
"
,
error
);
},
);
}
model
.
position
.
copy
(
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) {
model
.
file
)
{
loadModel
(
model
.
file
);
loadModel
(
model
.
file
);
showMenu
(
'
menu-bar
'
);
showMenu
(
'
menu-bar
'
);
}
}
...
...
@@ -485,7 +527,9 @@ function selectModelFromScene(event) {
);
if
(
intersects
.
length
>
0
)
{
if
(
intersects
.
length
>
0
)
{
let
selectedObject
=
intersects
[
0
].
object
;
...
...
@@ -502,7 +546,6 @@ function selectModelFromScene(event) {
// Reticle cannot be selected
if
(
selectedObject
===
reticle
)
{
...
...
@@ -521,7 +564,9 @@ function selectModelFromScene(event) {
highlightSelectedModel
();
showMenu
(
"
edit-menu
"
);
showMenu
(
"
edit-menu
"
);
}
}
...
...
@@ -536,13 +581,16 @@ function clearSelectedModel() {
if
(
child
.
isMesh
)
{
child
.
material
.
emissive
.
setHex
(
0x000000
);
.
setHex
(
0x000000
);
}
}
);
selectedPlacedModel
=
null
;
selectedPlacedModel
=
null
;
}
}
...
...
@@ -560,7 +608,9 @@ function highlightSelectedModel() {
if
(
child
.
isMesh
)
{
child
.
material
.
emissive
.
setHex
(
0xff0000
);
.
setHex
(
0xff0000
);
}
}
);
...
...
@@ -578,7 +628,9 @@ function removeHighlightFromSelectedModel() {
if
(
child
.
isMesh
)
{
child
.
material
.
emissive
.
setHex
(
0x000000
);
.
setHex
(
0x000000
);
}
}
);
...
...
@@ -598,7 +650,9 @@ function removeHighlightFromAllModels() {
)
{
child
.
material
.
emissive
.
setHex
(
0x000000
);
.
setHex
(
0x000000
);
}
}
);
...
...
@@ -628,7 +682,9 @@ function deleteModel() {
}
function
confirmDelete
(
shouldDelete
)
{
function
confirmDelete
(
shouldDelete
)
{
const
deleteDialog
=
document
.
getElementById
(
...
...
@@ -649,9 +705,12 @@ function confirmDelete(shouldDelete) {
selectedPlacedModel
);
selectedPlacedModel
=
null
;
selectedPlacedModel
=
null
;
showMenu
(
'
menu-bar
'
);
showMenu
(
'
menu-bar
'
);
}
}
...
...
@@ -662,15 +721,18 @@ function completeEditing() {
closeDynamicMenu
();
selectedPlacedModel
=
null
;
selectedPlacedModel
=
null
;
document
.
getElementById
(
'
edit-menu
'
).
style
.
display
=
'
none
'
;
).
style
.
display
=
'
none
'
;
document
.
getElementById
(
'
menu-bar
'
).
style
.
display
=
'
flex
'
;
).
style
.
display
=
'
flex
'
;
}
...
...
@@ -734,12 +796,16 @@ function openRotationMenu() {
}
function
updateRotation
(
axis
,
value
)
{
function
updateRotation
(
axis
,
value
)
{
if
(
selectedPlacedModel
)
{
const
radians
=
(
value
/
180
)
*
Math
.
PI
;
(
value
/
180
)
*
Math
.
PI
;
selectedPlacedModel
.
rotation
[
axis
]
=
...
...
@@ -786,7 +852,8 @@ function openScaleMenu() {
const
step
=
(
maxScale
-
minScale
)
/
100
;
(
maxScale
-
minScale
)
/
100
;
const
currentScalePercent
=
...
...
@@ -967,7 +1034,6 @@ function openMoveMenu() {
src="assets/icons/down-arrow.png"
alt="Down"
/>
</div>
</div>
...
...
@@ -979,7 +1045,9 @@ function openMoveMenu() {
}
function
updateMoveDelta
(
value
)
{
function
updateMoveDelta
(
value
)
{
moveDelta
=
parseFloat
(
value
);
...
...
@@ -1044,557 +1112,294 @@ function closeMapDialog() {
/* AR-HANDLING */
/* ========================= */
async
function
activateXR
(
sceneData
=
null
)
{
const
canvas
=
document
.
createElement
(
'
canvas
'
);
document
.
body
.
appendChild
(
canvas
);
const
gl
=
canvas
.
getContext
(
'
webgl
'
,
{
xrCompatible
:
true
}
);
const
renderer
=
new
THREE
.
WebGLRenderer
({
async
function
activateXR
(
sceneData
=
null
)
{
const
canvas
=
document
.
createElement
(
"
canvas
"
);
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
=
new
THREE
.
Scene
();
camera
.
matrixAutoUpdate
=
false
;
// ========================================================
// AR lighting
// ========================================================
camera
=
new
THREE
.
PerspectiveCamera
(
);
// Soft overall illumination
const
ambientLight
=
new
THREE
.
AmbientLight
(
0xffffff
,
2.5
);
scene
.
add
(
ambientLight
);
camera
.
matrixAutoUpdate
=
false
;
// Sky/ground illumination
const
hemisphereLight
=
new
THREE
.
HemisphereLight
(
0xffffff
,
0x888888
,
1.5
)
;
scene
.
add
(
hemisphereLight
);
// ========================================================
// AR lighting
// ========================================================
// Directional light for shadows and form
const
directionalLight
=
new
THREE
.
DirectionalLight
(
0xffffff
,
1.5
);
// Soft overall illumination
const
ambientLight
=
new
THREE
.
AmbientLight
(
0xffffff
,
2.5
);
directionalLight
.
position
.
set
(
10
,
20
,
10
);
scene
.
add
(
ambient
Light
);
scene
.
add
(
directional
Light
);
// ========================================================
// Reticle
// ========================================================
// Sky/ground illumination
const
hemisphereLight
=
new
THREE
.
HemisphereLight
(
0xffffff
,
0x888888
,
1.5
);
const
loader
=
new
THREE
.
GLTFLoader
();
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
const
directionalLight
=
new
THREE
.
DirectionalLight
(
0xffffff
,
1.5
);
directionalLight
.
position
.
set
(
10
,
20
,
10
);
scene
.
add
(
directionalLight
);
reticle
.
visible
=
false
;
scene
.
add
(
reticle
);
},
);
// ========================================================
// Reticle
// ========================================================
// ========================================================
// Start WebXR AR session
// ========================================================
c
onst
loader
=
new
THREE
.
GLTFLoader
();
c
urrentSession
=
await
navigator
.
xr
.
requestSession
(
"
immersive-ar
"
,
{
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
=
gltf
.
scene
;
const
viewerSpace
=
await
currentSession
.
requestReferenceSpace
(
"
viewer
"
);
reticle
.
visible
=
false
;
const
hitTestSource
=
await
currentSession
.
requestHitTestSource
({
space
:
viewerSpace
,
});
scene
.
add
(
reticle
);
}
);
// ========================================================
// Show main menu
// ========================================================
menuElements
[
"
menu-bar
"
].
style
.
display
=
"
flex
"
;
// ========================================================
//
Start WebXR
AR session
// ========================================================
// ========================================================
// AR session
ended
// ========================================================
currentSession
=
await
navigator
.
xr
.
requestSession
(
'
immersive-ar
'
,
{
optionalFeatures
:
[
"
dom-overlay
"
],
currentSession
.
addEventListener
(
"
end
"
,
()
=>
{
currentSession
=
null
;
domOverlay
:
{
root
:
document
.
body
},
// Hide dynamic menu
requiredFeatures
:
[
'
hit-test
'
]
}
);
document
.
getElementById
(
"
dynamic-menu
"
).
style
.
display
=
"
none
"
;
// Hide all application menus
currentSession
.
updateRenderState
({
menus
.
forEach
((
id
)
=>
{
const
element
=
menuElements
[
id
];
baseLayer
:
new
XRWebGLLayer
(
currentSession
,
gl
)
if
(
element
)
{
element
.
style
.
display
=
"
none
"
;
}
});
});
// ========================================================
// Select objects in AR
// ========================================================
const
referenceSpace
=
await
currentSession
.
requestReferenceSpace
(
'
local
'
);
const
viewerSpace
=
await
currentSession
.
requestReferenceSpace
(
'
viewer
'
);
canvas
.
addEventListener
(
"
pointerdown
"
,
selectModelFromScene
);
// ========================================================
// Geolocation
// ========================================================
const
hitTestSource
=
await
currentSession
.
requestHitTestSource
({
space
:
viewerSpace
});
if
(
navigator
.
geolocation
)
{
navigator
.
geolocation
.
getCurrentPosition
(
(
position
)
=>
{
geoLocation
=
{
latitude
:
roundTo
(
position
.
coords
.
latitude
,
5
),
longitude
:
roundTo
(
position
.
coords
.
longitude
,
5
),
};
// ========================================================
// Show main menu
// ========================================================
console
.
log
(
"
GeoLocation:
"
+
JSON
.
stringify
(
geoLocation
));
document
.
getElementById
(
'
menu-bar
'
).
style
.
display
=
'
flex
'
;
// =================================================
// Load saved scene models
// =================================================
if
(
sceneData
)
{
sceneData
.
models
.
forEach
((
model
)
=>
{
if
(
model
.
name
)
{
const
modelDefinition
=
Object
.
values
(
models
).
find
(
(
m
)
=>
model
.
name
===
m
.
name
,
);
// ========================================================
// AR session ended
// ========================================================
if
(
!
modelDefinition
)
{
console
.
error
(
"
Modell nicht gefunden:
"
,
model
.
name
);
currentSession
.
addEventListener
(
"
end
"
,
()
=>
{
return
;
}
currentSession
=
null
;
const
filePath
=
modelDefinition
.
file
;
const
{
x
,
z
}
=
leafletToThree
(
model
.
lat
,
model
.
lng
);
// Hide dynamic menu
const
positionVector
=
new
THREE
.
Vector3
(
x
,
model
.
position
.
y
,
z
);
document
.
getElementById
(
"
dynamic-menu
"
).
style
.
display
=
"
none
"
;
placeModel
(
filePath
,
positionVector
,
(
placed
)
=>
{
if
(
model
.
rotation
)
{
placed
.
rotation
.
_x
=
model
.
rotation
.
_x
;
placed
.
rotation
.
_y
=
model
.
rotation
.
_y
;
// Hide all application menus
menus
.
forEach
(
id
=>
{
placed
.
rotation
.
_z
=
model
.
rotation
.
_z
;
}
const
element
=
document
.
getElementById
(
id
)
;
if
(
model
.
scale
)
{
placed
.
scale
.
x
=
model
.
scale
.
x
;
if
(
element
)
{
placed
.
scale
.
y
=
model
.
scale
.
y
;
element
.
style
.
display
=
'
none
'
;
placed
.
scale
.
z
=
model
.
scale
.
z
;
}
});
});
}
});
}
);
},
(
error
)
=>
{
console
.
error
(
"
Fehler bei der Geolokalisierung:
"
,
JSON
.
stringify
(
error
),
);
},
// ========================================================
// Select objects in AR
// ========================================================
canvas
.
addEventListener
(
"
pointerdown
"
,
selectModelFromScene
{
enableHighAccuracy
:
true
,
maximumAge
:
2000
,
timeout
:
5000
,
},
);
}
// ========================================================
// XR render loop
// ========================================================
currentSession
.
requestAnimationFrame
(
function
onXRFrame
(
time
,
frame
)
{
currentSession
.
requestAnimationFrame
(
onXRFrame
);
// ========================================================
// Geolocation
// ========================================================
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
);
gl
.
bindFramebuffer
(
gl
.
FRAMEBUFFER
,
currentSession
.
renderState
.
baseLayer
.
framebuffer
,
);
const
pose
=
frame
.
getViewerPose
(
referenceSpace
);
placeModel
(
filePath
,
positionVector
,
(
placed
)
=>
{
if
(
pose
)
{
const
view
=
pose
.
views
[
0
];
if
(
model
.
rotation
)
{
const
viewport
=
currentSession
.
renderState
.
baseLayer
.
getViewport
(
view
);
placed
.
rotation
.
_x
=
model
.
rotation
.
_x
;
renderer
.
setSize
(
viewport
.
width
,
viewport
.
height
);
placed
.
rotation
.
_y
=
model
.
rotation
.
_y
;
camera
.
matrix
.
fromArray
(
view
.
transform
.
matrix
);
placed
.
rotation
.
_z
=
model
.
rotation
.
_z
;
}
camera
.
projectionMatrix
.
fromArray
(
view
.
projectionMatrix
);
camera
.
updateMatrixWorld
(
true
);
if
(
model
.
scale
)
{
// =================================================
// Hit testing
// =================================================
placed
.
scale
.
x
=
model
.
scale
.
x
;
const
hitTestResults
=
frame
.
getHitTestResults
(
hitTestSource
);
placed
.
scale
.
y
=
model
.
scale
.
y
;
if
(
hitTestResults
.
length
>
0
)
{
const
hitPose
=
hitTestResults
[
0
].
getPose
(
referenceSpace
)
;
placed
.
scale
.
z
=
model
.
scale
.
z
;
}
}
);
}
}
);
}
},
reticle
.
visible
=
true
;
error
=>
{
reticle
.
position
.
set
(
hitPose
.
transform
.
position
.
x
,
console
.
error
(
"
Fehler bei der Geolokalisierung:
"
,
JSON
.
stringify
(
error
)
);
},
hitPose
.
transform
.
position
.
y
,
{
enableHighAccuracy
:
true
,
maximumAge
:
2000
,
timeout
:
5000
}
hitPose
.
transform
.
position
.
z
,
);
}
// ========================================================
// 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
(
scene
,
camera
);
}
}
);
renderer
.
render
(
scene
,
camera
);
}
});
}
/* ========================= */
/* AR EXIT */
/* ========================= */
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
(
shouldExit
)
{
if
(
shouldExit
&&
currentSession
)
{
currentSession
.
end
();
}
document
.
getElementById
(
'
confirmation-dialog
'
).
style
.
display
=
'
none
'
;
document
.
getElementById
(
"
confirmation-dialog
"
).
style
.
display
=
"
none
"
;
}
/* ========================= */
/* BENUTZERINTERAKTIONEN */
/* ========================= */
let
soundTimeout
=
false
;
function
playButtonSound
()
{
if
(
!
soundTimeout
)
{
const
sound
=
document
.
getElementById
(
"
button-sound
"
);
if
(
!
soundTimeout
)
{
const
sound
=
document
.
getElementById
(
"
button-sound
"
);
sound
.
currentTime
=
0
;
sound
.
currentTime
=
0
;
sound
.
play
();
sound
.
play
()
;
sound
Timeout
=
true
;
soundTimeout
=
true
;
setTimeout
(
()
=>
{
soundTimeout
=
false
;
},
200
);
}
setTimeout
(()
=>
{
soundTimeout
=
false
;
},
200
);
}
}
\ No newline at end of file
public/ar_overviewmap.js
View file @
d2df398a
...
...
@@ -4,6 +4,18 @@ let userIcon;
let
intervalId
;
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
// ============================================================
...
...
@@ -24,476 +36,289 @@ let planVisible = false;
// Longitude = 8.010410216
const
planBounds
=
[
[
48.405858385
,
7.99708007
0
],
// Southwest / bottom-left
[
48.414966828
,
8.010410216
]
// Northeast / top-right
[
48.405858385
,
7.99708007
],
[
48.414966828
,
8.010410216
]
,
];
const
earthRadius
=
6378137
;
// Erdradius in Metern (WGS84)
const
earthRadius
=
6378137
;
// ============================================================
// Initialize map
// ============================================================
function
init_map
()
{
const
mapContainer
=
document
.
getElementById
(
"
map-container
"
);
const
mapContainer
=
document
.
getElementById
(
"
map-container
"
);
if
(
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
:
'
© <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
);
if
(
mapContainer
)
{
if
(
!
init
)
{
mymap
=
L
.
map
(
mapContainer
);
var
OpenStreetMap_DE
=
L
.
tileLayer
(
"
https://tile.openstreetmap.de/{z}/{x}/{y}.png
"
,
{
attribution
:
'
© <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
],
{
icon
:
L
.
icon
({
iconUrl
:
model
.
modelConfig
.
image
,
iconSize
:
[
16
,
16
],
iconAnchor
:
[
16
,
32
],
popupAnchor
:
[
0
,
-
32
]
})
}
)
.
addTo
(
mymap
)
.
bindPopup
(
model
.
modelConfig
?.
name
||
"
Unbenannt
"
);
// ========================================================
// Remove old model markers
// ========================================================
modeMarkers
.
forEach
((
marker
)
=>
{
marker
.
remove
();
});
modeMarkers
.
push
(
modelMarker
);
});
modeMarkers
=
[];
// ========================================================
// Add currently placed models to map
// ========================================================
// ========================================================
// Add proposed plan
// ========================================================
const
models
=
getAllPlacedModels
();
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
"
);
// ========================================================
// Make sure Leaflet knows the map container dimensions
// ========================================================
modeMarkers
.
push
(
modelMarker
);
});
setTimeout
(()
=>
{
// ========================================================
// Add proposed plan
// ========================================================
centerOnP
la
n
();
addPlanOver
la
y
();
},
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
// ============================================================
function
centerOnPlan
()
{
if
(
!
mymap
)
{
console
.
error
(
"
Map ist noch nicht initialisiert.
"
);
if
(
!
mymap
)
{
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
();
return
;
}
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
// ============================================================
//
// 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
()
{
if
(
!
mymap
)
{
console
.
error
(
"
Map ist noch nicht initialisiert.
"
);
if
(
!
mymap
)
{
console
.
error
(
"
Map ist noch nicht initialisiert.
"
);
return
;
}
// ========================================================
// If overlay already exists, show it
// ========================================================
if
(
planOverlay
)
{
planOverlay
.
addTo
(
mymap
);
planVisible
=
true
;
return
;
}
return
;
}
// ========================================================
// Create image overlay
// ========================================================
planOverlay
=
L
.
imageOverlay
(
'
assets/plans/proposed-plan.png
'
,
planBounds
,
{
// Transparency
opacity
:
0.7
,
if
(
planOverlay
)
{
planOverlay
.
addTo
(
mymap
);
// The plan does not capture mouse/touch events
interactive
:
false
}
);
planVisible
=
true
;
return
;
}
// ========================================================
// Add overlay to map
// ========================================================
planOverlay
=
L
.
imageOverlay
(
"
assets/plans/proposed-plan.png
"
,
planBounds
,
{
opacity
:
0.7
,
interactive
:
false
,
});
planOverlay
.
addTo
(
mymap
);
planOverlay
.
addTo
(
mymap
);
planVisible
=
true
;
planVisible
=
true
;
}
// ============================================================
// Update map
// ============================================================
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
(
document
.
getElementById
(
"
map-window
"
).
style
.
display
===
'
none
'
&&
intervalId
)
{
clearInterval
(
intervalId
);
intervalId
=
null
;
return
;
}
intervalId
=
null
;
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
(
userPosition
.
x
,
userPosition
.
z
);
lastMapX
=
cameraPosition
.
x
;
lastMapZ
=
cameraPosition
.
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
// ============================================================
function
threeToLeaflet
(
threeX
,
threeZ
)
{
if
(
!
geoLocation
)
{
console
.
error
(
"
GeoLocation ist noch nicht verfügbar
"
);
if
(
!
geoLocation
)
{
console
.
error
(
"
GeoLocation ist noch nicht verfügbar
"
);
return
;
}
const
originLat
=
geoLocation
.
latitude
;
const
originLng
=
geoLocation
.
longitude
;
const
deltaLat
=
(
threeZ
/
earthRadius
)
*
(
180
/
Math
.
PI
);
return
;
}
const
deltaLng
=
(
threeX
/
(
earthRadius
*
Math
.
cos
(
Math
.
PI
*
originLat
/
180
)
)
)
*
(
180
/
Math
.
PI
);
const
originLat
=
geoLocation
.
latitude
;
const
originLng
=
geoLocation
.
longitude
;
const
globalLat
=
originLat
+
deltaLat
;
const
deltaLat
=
(
threeZ
/
earthRadius
)
*
(
180
/
Math
.
PI
);
const
deltaLng
=
(
threeX
/
(
earthRadius
*
Math
.
cos
((
Math
.
PI
*
originLat
)
/
180
)))
*
(
180
/
Math
.
PI
);
const
globalLng
=
originLng
+
deltaLng
;
const
globalLat
=
originLat
+
deltaLat
;
const
globalLng
=
originLng
+
deltaLng
;
return
{
lat
:
globalLat
,
lng
:
globalLng
};
return
{
lat
:
globalLat
,
lng
:
globalLng
,
};
}
// ============================================================
// Convert Leaflet coordinates to Three.js coordinates
// ============================================================
function
leafletToThree
(
lat
,
lng
)
{
if
(
!
geoLocation
)
{
console
.
error
(
"
GeoLocation ist noch nicht verfügbar
"
);
if
(
!
geoLocation
)
{
console
.
error
(
"
GeoLocation ist noch nicht verfügbar
"
);
return
;
}
const
originLat
=
geoLocation
.
latitude
;
const
originLng
=
geoLocation
.
longitude
;
const
deltaLat
=
lat
-
originLat
;
return
;
}
const
originLat
=
geoLocation
.
latitude
;
const
deltaLng
=
lng
-
originLng
;
const
originLng
=
geoLocation
.
longitude
;
const
deltaLat
=
lat
-
originLat
;
const
threeZ
=
deltaLat
*
(
Math
.
PI
/
180
)
*
earthRadius
;
const
deltaLng
=
lng
-
originLng
;
const
threeZ
=
deltaLat
*
(
Math
.
PI
/
180
)
*
earthRadius
;
const
threeX
=
deltaLng
*
(
Math
.
PI
/
180
)
*
earthRadius
*
Math
.
cos
(
Math
.
PI
*
originLat
/
180
);
const
threeX
=
deltaLng
*
(
Math
.
PI
/
180
)
*
earthRadius
*
Math
.
cos
((
Math
.
PI
*
originLat
)
/
180
);
return
{
x
:
threeX
,
z
:
threeZ
};
return
{
x
:
threeX
,
z
:
threeZ
,
};
}
// ============================================================
// Round number
// ============================================================
function
roundTo
(
n
,
digits
)
{
var
negative
=
false
;
var
negative
=
false
;
if
(
digits
===
undefined
)
{
digits
=
0
;
}
if
(
digits
===
undefined
)
{
digits
=
0
;
}
if
(
n
<
0
)
{
negative
=
true
;
n
=
n
*
-
1
;
}
if
(
n
<
0
)
{
negative
=
true
;
n
=
n
*
-
1
;
}
var
multiplicator
=
Math
.
pow
(
10
,
digits
);
var
multiplicator
=
Math
.
pow
(
10
,
digits
);
n
=
parseFloat
((
n
*
multiplicator
).
toFixed
(
11
));
n
=
(
Math
.
round
(
n
)
/
multiplicator
).
toFixed
(
digits
);
n
=
parseFloat
(
(
n
*
multiplicator
).
toFixed
(
11
)
);
if
(
negative
)
{
n
=
(
n
*
-
1
).
toFixed
(
digits
);
}
n
=
(
Math
.
round
(
n
)
/
multiplicator
).
toFixed
(
digits
);
if
(
negative
)
{
n
=
(
n
*
-
1
).
toFixed
(
digits
);
}
return
parseFloat
(
n
);
}
\ No newline at end of file
return
parseFloat
(
n
);
}
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment