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 = () => {
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 = () => {
}
}
// Common: camera position (reuse below)
// Camera 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 = targetXZ.x - camPos.x;
const dz = targetXZ.z - camPos.z;
// direction from camera (WORLD) toward target (WORLD)
const dx = targetWorld.x - camPos.x;
const dz = targetWorld.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;
}
// Hide any unused arrows
// hide remaining arrows
for (let j = used; j < arrowsRef.current.length; j++) {
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