fuzzyworld

north-star / commit

pasta time with north orientation

73446732d55de45d470f82b8e5b2d4dfc450f184 · Pavel Safronov · 2026-03-14 19:50:30 -0700 · parents e70d53bd13bb

pasta time with north orientation
 src/app.js | 76 ++++++++++++++++++++++++++------------------------------------
 1 file changed, 32 insertions(+), 44 deletions(-)

diff --git a/src/app.js b/src/app.js
index 5952b78..3daec94 100644
--- a/src/app.js
+++ b/src/app.js
@@ -27,6 +27,7 @@ const getShortestDelta = (fromAngle, toAngle) => ((toAngle - fromAngle + 540) %
 const normalizeAngle = (angle) => ((angle % 360) + 360) % 360;
 const RENDER_INTERVAL_MS = 180;
 const LOCATION_SYNC_INTERVAL_MS = 3000;
+const HEADING_CORRECTION_DEG = 0;
 
 /**
  * @typedef {Object} Position
@@ -391,61 +392,48 @@ navigator.geolocation.watchPosition(async (pos) => {
 }, (err) => {
     console.error('Error getting geolocation', err);
 });
-window.addEventListener('deviceorientationabsolute', (event) => {
-    // Some browsers expose absolute compass heading as webkitCompassHeading.
-    // Others expose alpha that needs conversion and screen-orientation compensation.
-    const getScreenOrientationAngle = () => {
-        if (window.screen?.orientation && typeof window.screen.orientation.angle === 'number') {
-            return window.screen.orientation.angle;
-        }
-        if (typeof window.orientation === 'number') {
-            return window.orientation;
-        }
-        return 0;
-    };
+const getScreenOrientationAngle = () => {
+    if (window.screen?.orientation && typeof window.screen.orientation.angle === 'number') {
+        return window.screen.orientation.angle;
+    }
+    if (typeof window.orientation === 'number') {
+        return window.orientation;
+    }
+    return 0;
+};
 
-    const getHeadingFromEvent = (orientationEvent) => {
-        if (typeof orientationEvent.webkitCompassHeading === 'number') {
-            return normalizeAngle(orientationEvent.webkitCompassHeading);
-        }
-        if (orientationEvent.alpha === null) {
-            return null;
-        }
-        // Convert alpha to compass-style heading (clockwise from North).
-        return normalizeAngle(360 - orientationEvent.alpha);
-    };
+const getHeadingFromEvent = (orientationEvent) => {
+    // iOS Safari: already compass heading of the top of the screen.
+    if (typeof orientationEvent.webkitCompassHeading === 'number') {
+        return normalizeAngle(orientationEvent.webkitCompassHeading);
+    }
 
+    if (orientationEvent.alpha === null) {
+        return null;
+    }
+
+    // Generic alpha -> compass heading, then compensate screen rotation.
+    const alphaHeading = normalizeAngle(360 - orientationEvent.alpha);
+    return normalizeAngle(alphaHeading + getScreenOrientationAngle());
+};
+
+let hasAbsoluteOrientation = false;
+
+window.addEventListener('deviceorientationabsolute', (event) => {
     const heading = getHeadingFromEvent(event);
     if (heading === null) return;
 
-    selfHeading = normalizeAngle(heading + getScreenOrientationAngle());
+    hasAbsoluteOrientation = true;
+    selfHeading = normalizeAngle(heading + HEADING_CORRECTION_DEG);
 });
 
 window.addEventListener('deviceorientation', (event) => {
-    const getScreenOrientationAngle = () => {
-        if (window.screen?.orientation && typeof window.screen.orientation.angle === 'number') {
-            return window.screen.orientation.angle;
-        }
-        if (typeof window.orientation === 'number') {
-            return window.orientation;
-        }
-        return 0;
-    };
-
-    const getHeadingFromEvent = (orientationEvent) => {
-        if (typeof orientationEvent.webkitCompassHeading === 'number') {
-            return normalizeAngle(orientationEvent.webkitCompassHeading);
-        }
-        if (orientationEvent.alpha === null) {
-            return null;
-        }
-        return normalizeAngle(360 - orientationEvent.alpha);
-    };
+    if (hasAbsoluteOrientation) return;
 
     const heading = getHeadingFromEvent(event);
     if (heading === null) return;
 
-    selfHeading = normalizeAngle(heading + getScreenOrientationAngle());
+    selfHeading = normalizeAngle(heading + HEADING_CORRECTION_DEG);
 });
 
 const participantsRef = ref(db, `rooms/${roomId}/participants`);
@@ -620,7 +608,7 @@ const render = () => {
         // Rotate the arrow toward the other participant and use their color.
         const dx = target.position.lon - selfParticipant.position.lon;
         const dy = target.position.lat - selfParticipant.position.lat;
-        const rawAngle = Math.atan2(dy, dx) * (180 / Math.PI) + 90;
+        const rawAngle = Math.atan2(dx, dy) * (180 / Math.PI);
 
         // Consider the heading of the device to rotate the arrow accordingly
         const targetAngle = normalizeAngle(rawAngle - selfHeading);