Commit 285b9b27 authored by Kang's avatar Kang
Browse files

Fix lable not showing issue

parent 6b774eda
...@@ -92,18 +92,25 @@ export const XRRenderer: React.FC = () => { ...@@ -92,18 +92,25 @@ export const XRRenderer: React.FC = () => {
addLightToScene(scene); addLightToScene(scene);
mountRef.current.appendChild(renderer.domElement); mountRef.current.appendChild(renderer.domElement);
// env map from RoomEnvironment (reuse same renderer) // env map
const pmremGenerator = new THREE.PMREMGenerator(renderer); const pmremGenerator = new THREE.PMREMGenerator(renderer);
scene.environment = pmremGenerator.fromScene(new RoomEnvironment(), 0.04).texture; scene.environment = pmremGenerator.fromScene(new RoomEnvironment(), 0.04).texture;
const button = createARButton(renderer, overlayRef); const button = createARButton(renderer, overlayRef);
mountRef.current.appendChild(button); 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 --- // --- 4️⃣ Block manager + reticle ---
const blockManager = new BlockManager(scene); const blockManager = new BlockManager(scene);
const reticle = createPlacementRing(scene, reticleRef); const reticle = createPlacementRing(scene, reticleRef);
// --- Load POIs and create label sprites --- // --- Load POIs and create label sprites (add to campusGroup) ---
async function loadPOIsAndCreateLabels() { async function loadPOIsAndCreateLabels() {
const res = await fetch("/coordinate/pois.json"); const res = await fetch("/coordinate/pois.json");
const pois: POI[] = await res.json(); const pois: POI[] = await res.json();
...@@ -138,14 +145,26 @@ export const XRRenderer: React.FC = () => { ...@@ -138,14 +145,26 @@ export const XRRenderer: React.FC = () => {
const sprite = makeLabel(p.name, p.icon); const sprite = makeLabel(p.name, p.icon);
const localXZ = toLocalMeters({ lat: p.lat, lon: p.lon }, ORIGIN_LLA); const localXZ = toLocalMeters({ lat: p.lat, lon: p.lon }, ORIGIN_LLA);
sprite.position.set(localXZ.x, LABEL_HEIGHT_Y, localXZ.z); sprite.position.set(localXZ.x, LABEL_HEIGHT_Y, localXZ.z);
sprite.visible = false; // visibility controlled per-frame sprite.visible = false; // controlled per-frame
scene.add(sprite); campusGroup.add(sprite); // ⬅ add to group
list.push({ poi: p, mesh: sprite, localXZ }); list.push({ poi: p, mesh: sprite, localXZ });
} }
poiRuntimeRef.current = list; poiRuntimeRef.current = list;
} }
loadPOIsAndCreateLabels().catch(console.error); 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) --- // --- Tap → start navigation (attach to overlay so it isn't blocked) ---
function ensureArrows() { function ensureArrows() {
if (arrowsRef.current.length) return; if (arrowsRef.current.length) return;
...@@ -159,8 +178,21 @@ export const XRRenderer: React.FC = () => { ...@@ -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) { function onTap(ev: MouseEvent | TouchEvent) {
// ignore UI controls inside overlay
const t = ev.target as HTMLElement | null; const t = ev.target as HTMLElement | null;
if (t && (t.closest("button") || t.closest("[data-ui='true']"))) return; if (t && (t.closest("button") || t.closest("[data-ui='true']"))) return;
...@@ -183,6 +215,10 @@ export const XRRenderer: React.FC = () => { ...@@ -183,6 +215,10 @@ export const XRRenderer: React.FC = () => {
const rt = poiRuntimeRef.current.find(p => p.mesh === hit || p.mesh === (hit as any).parent); const rt = poiRuntimeRef.current.find(p => p.mesh === hit || p.mesh === (hit as any).parent);
if (!rt) return; if (!rt) return;
// Calibrate heading
calibrateHeadingToPOI(rt.localXZ);
// Path: waypoints -> entrance -> main
const path: LLA[] = []; const path: LLA[] = [];
if (rt.poi.waypoints?.length) path.push(...rt.poi.waypoints); if (rt.poi.waypoints?.length) path.push(...rt.poi.waypoints);
if (rt.poi.entrance) path.push(rt.poi.entrance); if (rt.poi.entrance) path.push(rt.poi.entrance);
...@@ -206,6 +242,19 @@ export const XRRenderer: React.FC = () => { ...@@ -206,6 +242,19 @@ export const XRRenderer: React.FC = () => {
localRefSpace = await session.requestReferenceSpace("local-floor"); localRefSpace = await session.requestReferenceSpace("local-floor");
const viewerSpace = await session.requestReferenceSpace("viewer"); const viewerSpace = await session.requestReferenceSpace("viewer");
hitTestSource = await session.requestHitTestSource({ space: viewerSpace }); 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 = () => { const sessionEndHandler = () => {
...@@ -247,30 +296,36 @@ export const XRRenderer: React.FC = () => { ...@@ -247,30 +296,36 @@ export const XRRenderer: React.FC = () => {
} }
} }
// Common: camera position (reuse below) // Camera world position (reuse)
const camPos = new THREE.Vector3(); const camPos = new THREE.Vector3();
camera.getWorldPosition(camPos); camera.getWorldPosition(camPos);
// Labels within 500 m (always-on visibility rule) // --- Labels within 500 m (WORLD space) ---
const MAX_LABEL_DIST_M = 500; const MAX_LABEL_DIST_M = 500;
camera.getWorldPosition(camPos);
for (const item of poiRuntimeRef.current) { for (const item of poiRuntimeRef.current) {
const { mesh, localXZ } = item; const { mesh, localXZ } = item;
// keep label anchored at campus local coords
mesh.position.set(localXZ.x, LABEL_HEIGHT_Y, localXZ.z); 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; mesh.visible = d < MAX_LABEL_DIST_M;
if (mesh.visible) { if (mesh.visible) {
// billboard: face camera
mesh.quaternion.copy(camera.quaternion); mesh.quaternion.copy(camera.quaternion);
const e = new THREE.Euler().setFromQuaternion(mesh.quaternion, "YXZ"); const e = new THREE.Euler().setFromQuaternion(mesh.quaternion, "YXZ");
e.z = 0; e.z = 0; // keep upright
mesh.quaternion.setFromEuler(e); mesh.quaternion.setFromEuler(e);
} }
} }
// Guidance arrows (optional next step: add per-frame layout here) // --- Guidance arrows (WORLD space) ---
const nav = navigatingRef.current; const nav = navigatingRef.current;
if (nav?.active) { if (nav?.active) {
const ARROW_SPACING_M = 3.5; const ARROW_SPACING_M = 3.5;
...@@ -278,40 +333,44 @@ export const XRRenderer: React.FC = () => { ...@@ -278,40 +333,44 @@ export const XRRenderer: React.FC = () => {
const targetLLA = nav.pathLLA[nav.targetIndex]; const targetLLA = nav.pathLLA[nav.targetIndex];
if (targetLLA) { if (targetLLA) {
// target in campus local meters
const targetXZ = toLocalMeters(targetLLA, ORIGIN_LLA); const targetXZ = toLocalMeters(targetLLA, ORIGIN_LLA);
// Arrived at this waypoint? // convert campus-local → WORLD using group's transform
const distToTarget = Math.hypot(targetXZ.x - camPos.x, targetXZ.z - camPos.z); 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) { if (distToTarget < ARRIVAL_RADIUS_M) {
nav.targetIndex++; nav.targetIndex++;
if (nav.targetIndex >= nav.pathLLA.length) { if (nav.targetIndex >= nav.pathLLA.length) {
// finished: hide all arrows and stop
for (const a of arrowsRef.current) a.visible = false; for (const a of arrowsRef.current) a.visible = false;
navigatingRef.current = null; navigatingRef.current = null;
} }
} else { } else {
// Place arrows from camera toward target, spaced every ARROW_SPACING_M // direction from camera (WORLD) toward target (WORLD)
const dx = targetXZ.x - camPos.x; const dx = targetWorld.x - camPos.x;
const dz = targetXZ.z - camPos.z; const dz = targetWorld.z - camPos.z;
const len = Math.hypot(dx, dz); const len = Math.hypot(dx, dz);
const ux = dx / len; const ux = dx / len, uz = dz / len;
const uz = dz / len;
let used = 0; let used = 0;
for (let i = 1; i <= arrowsRef.current.length; i++) { for (let i = 1; i <= arrowsRef.current.length; i++) {
const dist = i * ARROW_SPACING_M; const dist = i * ARROW_SPACING_M;
if (dist >= len) break; if (dist >= len) break;
const ax = camPos.x + ux * dist; const ax = camPos.x + ux * dist;
const az = camPos.z + uz * dist; const az = camPos.z + uz * dist;
const arrow = arrowsRef.current[used++]; const arrow = arrowsRef.current[used++];
if (!arrow) break; if (!arrow) break;
arrow.position.set(ax, 1.6, az); // ~eye height arrow.position.set(ax, 1.6, az); // WORLD coords
const yaw = Math.atan2(ux, uz); // face along path const yaw = Math.atan2(ux, uz);
arrow.rotation.set(0, yaw, 0); arrow.rotation.set(0, yaw, 0);
arrow.visible = true; arrow.visible = true;
} }
// Hide any unused arrows // hide remaining arrows
for (let j = used; j < arrowsRef.current.length; j++) { for (let j = used; j < arrowsRef.current.length; j++) {
arrowsRef.current[j].visible = false; arrowsRef.current[j].visible = false;
} }
......
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