Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
HFT-informatics-weekend
AR - Urban Planner
Commits
285b9b27
Commit
285b9b27
authored
Oct 12, 2025
by
Kang
Browse files
Fix lable not showing issue
parent
6b774eda
Changes
1
Hide whitespace changes
Inline
Side-by-side
webxr-cube/src/components/XRRenderer.tsx
View file @
285b9b27
...
...
@@ -92,18 +92,25 @@ export const XRRenderer: React.FC = () => {
addLightToScene
(
scene
);
mountRef
.
current
.
appendChild
(
renderer
.
domElement
);
// env map
from RoomEnvironment (reuse same renderer)
// env map
const
pmremGenerator
=
new
THREE
.
PMREMGenerator
(
renderer
);
scene
.
environment
=
pmremGenerator
.
fromScene
(
new
RoomEnvironment
(),
0.04
).
texture
;
const
button
=
createARButton
(
renderer
,
overlayRef
);
mountRef
.
current
.
appendChild
(
button
);
// 🔹 Group for campus (lets us translate/rotate all POIs together)
const
campusGroup
=
new
THREE
.
Group
();
scene
.
add
(
campusGroup
);
// User local (meters from ORIGIN_LLA), set on session start
const
userLocalRef
=
{
x
:
0
,
z
:
0
};
// --- 4️⃣ Block manager + reticle ---
const
blockManager
=
new
BlockManager
(
scene
);
const
reticle
=
createPlacementRing
(
scene
,
reticleRef
);
// --- Load POIs and create label sprites ---
// --- Load POIs and create label sprites
(add to campusGroup)
---
async
function
loadPOIsAndCreateLabels
()
{
const
res
=
await
fetch
(
"
/coordinate/pois.json
"
);
const
pois
:
POI
[]
=
await
res
.
json
();
...
...
@@ -138,14 +145,26 @@ export const XRRenderer: React.FC = () => {
const
sprite
=
makeLabel
(
p
.
name
,
p
.
icon
);
const
localXZ
=
toLocalMeters
({
lat
:
p
.
lat
,
lon
:
p
.
lon
},
ORIGIN_LLA
);
sprite
.
position
.
set
(
localXZ
.
x
,
LABEL_HEIGHT_Y
,
localXZ
.
z
);
sprite
.
visible
=
false
;
//
visibility
controlled per-frame
scene
.
add
(
sprite
);
sprite
.
visible
=
false
;
// controlled per-frame
campusGroup
.
add
(
sprite
);
// ⬅ add to group
list
.
push
({
poi
:
p
,
mesh
:
sprite
,
localXZ
});
}
poiRuntimeRef
.
current
=
list
;
}
loadPOIsAndCreateLabels
().
catch
(
console
.
error
);
// --- Geolocation helper (HTTPS or localhost needed) ---
function
getUserLLA
():
Promise
<
LLA
>
{
return
new
Promise
((
resolve
,
reject
)
=>
{
if
(
!
navigator
.
geolocation
)
return
reject
(
new
Error
(
"
Geolocation not available
"
));
navigator
.
geolocation
.
getCurrentPosition
(
(
pos
)
=>
resolve
({
lat
:
pos
.
coords
.
latitude
,
lon
:
pos
.
coords
.
longitude
}),
(
err
)
=>
reject
(
err
),
{
enableHighAccuracy
:
true
,
maximumAge
:
5000
,
timeout
:
8000
}
);
});
}
// --- Tap → start navigation (attach to overlay so it isn't blocked) ---
function
ensureArrows
()
{
if
(
arrowsRef
.
current
.
length
)
return
;
...
...
@@ -159,8 +178,21 @@ export const XRRenderer: React.FC = () => {
}
}
// One-tap heading calibration: rotate campus so tapped POI is straight ahead
function
calibrateHeadingToPOI
(
poiLocalXZ
:
{
x
:
number
;
z
:
number
})
{
const
vx
=
poiLocalXZ
.
x
-
userLocalRef
.
x
;
const
vz
=
poiLocalXZ
.
z
-
userLocalRef
.
z
;
const
mapYaw
=
Math
.
atan2
(
vx
,
vz
);
// radians
const
fwd
=
new
THREE
.
Vector3
();
camera
.
getWorldDirection
(
fwd
);
const
camYaw
=
Math
.
atan2
(
fwd
.
x
,
fwd
.
z
);
// radians
const
offset
=
camYaw
-
mapYaw
;
campusGroup
.
rotation
.
y
=
offset
;
}
function
onTap
(
ev
:
MouseEvent
|
TouchEvent
)
{
// ignore UI controls inside overlay
const
t
=
ev
.
target
as
HTMLElement
|
null
;
if
(
t
&&
(
t
.
closest
(
"
button
"
)
||
t
.
closest
(
"
[data-ui='true']
"
)))
return
;
...
...
@@ -183,6 +215,10 @@ export const XRRenderer: React.FC = () => {
const
rt
=
poiRuntimeRef
.
current
.
find
(
p
=>
p
.
mesh
===
hit
||
p
.
mesh
===
(
hit
as
any
).
parent
);
if
(
!
rt
)
return
;
// Calibrate heading
calibrateHeadingToPOI
(
rt
.
localXZ
);
// Path: waypoints -> entrance -> main
const
path
:
LLA
[]
=
[];
if
(
rt
.
poi
.
waypoints
?.
length
)
path
.
push
(...
rt
.
poi
.
waypoints
);
if
(
rt
.
poi
.
entrance
)
path
.
push
(
rt
.
poi
.
entrance
);
...
...
@@ -206,6 +242,19 @@ export const XRRenderer: React.FC = () => {
localRefSpace
=
await
session
.
requestReferenceSpace
(
"
local-floor
"
);
const
viewerSpace
=
await
session
.
requestReferenceSpace
(
"
viewer
"
);
hitTestSource
=
await
session
.
requestHitTestSource
({
space
:
viewerSpace
});
// Align campus translation to user's GPS
try
{
const
userLLA
=
await
getUserLLA
();
const
userLocal
=
toLocalMeters
(
userLLA
,
ORIGIN_LLA
);
userLocalRef
.
x
=
userLocal
.
x
;
userLocalRef
.
z
=
userLocal
.
z
;
// Move entire campus so your GPS position sits at AR origin
campusGroup
.
position
.
set
(
-
userLocal
.
x
,
0
,
-
userLocal
.
z
);
}
catch
(
e
)
{
console
.
warn
(
"
Geolocation failed; labels may be out of range / rotation off.
"
,
e
);
}
};
const
sessionEndHandler
=
()
=>
{
...
...
@@ -247,30 +296,36 @@ export const XRRenderer: React.FC = () => {
}
}
// C
ommon: camera
position (reuse
below
)
// C
amera world
position (reuse)
const
camPos
=
new
THREE
.
Vector3
();
camera
.
getWorldPosition
(
camPos
);
// Labels within 500 m (
always-on visibility rule)
//
---
Labels within 500 m (
WORLD space) ---
const
MAX_LABEL_DIST_M
=
500
;
camera
.
getWorldPosition
(
camPos
);
for
(
const
item
of
poiRuntimeRef
.
current
)
{
const
{
mesh
,
localXZ
}
=
item
;
// keep label anchored at campus local coords
mesh
.
position
.
set
(
localXZ
.
x
,
LABEL_HEIGHT_Y
,
localXZ
.
z
);
const
d
=
Math
.
hypot
(
localXZ
.
x
-
camPos
.
x
,
localXZ
.
z
-
camPos
.
z
);
// get WORLD position of label (because it's under campusGroup)
const
worldPos
=
new
THREE
.
Vector3
();
mesh
.
getWorldPosition
(
worldPos
);
// distance in WORLD space
const
d
=
Math
.
hypot
(
worldPos
.
x
-
camPos
.
x
,
worldPos
.
z
-
camPos
.
z
);
mesh
.
visible
=
d
<
MAX_LABEL_DIST_M
;
if
(
mesh
.
visible
)
{
// billboard: face camera
mesh
.
quaternion
.
copy
(
camera
.
quaternion
);
const
e
=
new
THREE
.
Euler
().
setFromQuaternion
(
mesh
.
quaternion
,
"
YXZ
"
);
e
.
z
=
0
;
e
.
z
=
0
;
// keep upright
mesh
.
quaternion
.
setFromEuler
(
e
);
}
}
// Guidance arrows (
optional next step: add per-frame layout here)
//
---
Guidance arrows (
WORLD space) ---
const
nav
=
navigatingRef
.
current
;
if
(
nav
?.
active
)
{
const
ARROW_SPACING_M
=
3.5
;
...
...
@@ -278,40 +333,44 @@ export const XRRenderer: React.FC = () => {
const
targetLLA
=
nav
.
pathLLA
[
nav
.
targetIndex
];
if
(
targetLLA
)
{
// target in campus local meters
const
targetXZ
=
toLocalMeters
(
targetLLA
,
ORIGIN_LLA
);
// Arrived at this waypoint?
const
distToTarget
=
Math
.
hypot
(
targetXZ
.
x
-
camPos
.
x
,
targetXZ
.
z
-
camPos
.
z
);
// convert campus-local → WORLD using group's transform
const
targetWorld
=
new
THREE
.
Vector3
(
targetXZ
.
x
,
0
,
targetXZ
.
z
);
targetWorld
.
applyMatrix4
(
campusGroup
.
matrixWorld
);
// Arrived?
const
distToTarget
=
Math
.
hypot
(
targetWorld
.
x
-
camPos
.
x
,
targetWorld
.
z
-
camPos
.
z
);
if
(
distToTarget
<
ARRIVAL_RADIUS_M
)
{
nav
.
targetIndex
++
;
if
(
nav
.
targetIndex
>=
nav
.
pathLLA
.
length
)
{
// finished: hide all arrows and stop
for
(
const
a
of
arrowsRef
.
current
)
a
.
visible
=
false
;
navigatingRef
.
current
=
null
;
}
}
else
{
//
Place arrows
from camera toward target
, spaced every ARROW_SPACING_M
const
dx
=
target
XZ
.
x
-
camPos
.
x
;
const
dz
=
target
XZ
.
z
-
camPos
.
z
;
//
direction
from camera
(WORLD)
toward target
(WORLD)
const
dx
=
target
World
.
x
-
camPos
.
x
;
const
dz
=
target
World
.
z
-
camPos
.
z
;
const
len
=
Math
.
hypot
(
dx
,
dz
);
const
ux
=
dx
/
len
;
const
uz
=
dz
/
len
;
const
ux
=
dx
/
len
,
uz
=
dz
/
len
;
let
used
=
0
;
for
(
let
i
=
1
;
i
<=
arrowsRef
.
current
.
length
;
i
++
)
{
const
dist
=
i
*
ARROW_SPACING_M
;
if
(
dist
>=
len
)
break
;
const
ax
=
camPos
.
x
+
ux
*
dist
;
const
az
=
camPos
.
z
+
uz
*
dist
;
const
arrow
=
arrowsRef
.
current
[
used
++
];
if
(
!
arrow
)
break
;
arrow
.
position
.
set
(
ax
,
1.6
,
az
);
//
~eye height
const
yaw
=
Math
.
atan2
(
ux
,
uz
);
// face along path
arrow
.
position
.
set
(
ax
,
1.6
,
az
);
//
WORLD coords
const
yaw
=
Math
.
atan2
(
ux
,
uz
);
arrow
.
rotation
.
set
(
0
,
yaw
,
0
);
arrow
.
visible
=
true
;
}
//
H
ide
any unused
arrows
//
h
ide
remaining
arrows
for
(
let
j
=
used
;
j
<
arrowsRef
.
current
.
length
;
j
++
)
{
arrowsRef
.
current
[
j
].
visible
=
false
;
}
...
...
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