Latest commit

History

41 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

ReverseProjection

reverse-projection.mp4

A live augmented-reality application that turns a webcam into a position sensor: hold your phone in front of the camera and an interactive map appears, perfectly aligned with your physical location. Touch the phone screen to draw annotations that appear on both the phone and the large display simultaneously.

Anyone who opens the phone page connects automatically.


How It Works

  1. The phone displays a single ArUco fiducial marker (ID 0) centred at the top of the screen so the webcam can detect and track it.
  2. The laptop accesses the webcam, detects the marker using jsartoolkit5, and derives the phone's position and rotation from the marker's four corner vertices.
  3. Both devices communicate over WebSockets (Socket.io) through a local Node.js server running on your network.
  4. The laptop map shows a bounding box representing the area currently visible on the phone's mini-map.
  5. Touching the phone screen sends lat/lng coordinates back to the laptop and draws a stroke on both maps simultaneously.

Mapping Model

The project uses three different coordinate spaces. Keeping them separate is the key to making the overlays line up.

  1. Camera space The laptop webcam sees the physical phone. Marker detection gives a phone centre (nx, ny) in normalised camera coordinates and a phone rotation θ.

  2. Phone viewport space The phone reports the size of the actually visible interactive area, not the whole page. This is the rectangle below the marker and above the bottom status bar. That reported width and height are the only dimensions that may be used to infer the visible viewport.

  3. Content space Each example maps the visible phone viewport into its own content space.

    • Map mode: geographic map bounds.
    • Screen mode: source video pixels from the captured desktop stream.
    • Whiteboard mode: whiteboard world coordinates.

The important rule is:

  • The blue overlay on the laptop must represent the phone's visible content viewport in content space.
  • It must not represent the physical phone body in camera space.

Screen mode mapping

Screen mode uses the exact same viewport transform on both devices.

  1. The phone renders the incoming desktop stream into its visible area using a cover transform:
    • base scale = max(phoneW / sourceW, phoneH / sourceH)
    • total scale = base scale * zoom
  2. The phone then translates the scaled source so that the tracked point (nx, ny) becomes the centre of the visible phone viewport.
  3. That translation is clamped so the source never exposes empty edges.
  4. The laptop overlay must compute the inverse of that same transform and draw the resulting source rectangle on top of the displayed preview.
  5. If rotation is enabled, the same viewport rectangle is rotated by the tracked phone angle. If rotation is disabled, both the phone and the overlay stay axis-aligned.

This means the laptop overlay for Screen mode must be derived from:

  • source video size
  • reported visible phone viewport size
  • zoom
  • tracked (nx, ny)
  • tracked rotation when enabled

It must not be derived from the marker's apparent size in the webcam image.

Map mode mapping

Map mode uses the reported visible phone viewport size to convert the tracked phone centre into a geographic rectangle.

  1. The laptop maps (nx, ny) into the current map bounds.
  2. The reported phone viewport width and height are converted to fractions of the camera frame using the detected physical scale.
  3. Those fractions are then converted into fractions of the current Leaflet map bounds.
  4. If rotation is enabled, the rectangle is rotated. Otherwise it stays upright.

The same visible viewport dimensions must be used consistently on the phone, in the laptop state, and in the laptop overlay.


Getting Started

Step 1 — Install Node.js

Node.js is the JavaScript runtime the server needs. Install it once; it stays on your machine.

macOS

The easiest way is Homebrew, a package manager for macOS. If you have never used it:

  1. Open Terminal (press Command+Space, type "Terminal", press Enter).

  2. Paste the following command and press Enter. It will ask for your Mac password:

    /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
    
  3. After Homebrew finishes, install Node.js:

    brew install node
    
  4. Verify the installation:

    node --version
    

    You should see something like v20.x.x.

Windows

  1. Open a web browser and go to https://nodejs.org.

  2. Download the LTS installer (the button labelled "LTS" — Long Term Support).

  3. Run the downloaded .msi file and follow the installer steps. Leave all options at their defaults.

  4. When it finishes, open Command Prompt (press Windows+R, type cmd, press Enter) and verify:

    node --version
    

Linux (Ubuntu / Debian)

sudo apt update
sudo apt install nodejs npm
node --version

For other distributions, see https://nodejs.org/en/download/package-manager.


Step 2 — Download the project

If you have Git installed:

git clone https://github.com/lemio/ReverseProjection.git
cd ReverseProjection

If you do not have Git, download the ZIP from GitHub (click the green "Code" button, then "Download ZIP"), unzip it, and open a terminal inside the folder.


Step 3 — Install dependencies and start the server

In your terminal, inside the project folder:

npm install
npm start

You should see:

ReverseProjection server running at http://localhost:3000

Leave this terminal window open — the server must keep running.


Step 4 — Open the laptop app

Open a browser (Chrome or Edge recommended for best webcam support) and go to:

http://localhost:3000

Allow camera access when the browser asks. The webcam feed will appear.


Step 5 — Connect a phone

The phone must be on the same Wi-Fi network as the laptop.

  1. On the laptop, click Copy Phone Link in the toolbar. This copies the URL to your clipboard.
  2. Open that URL on the phone's browser, or click Show QR Code and scan it with the phone camera.
  3. The phone will connect automatically and display an interactive map.

To find your laptop's local IP address (for typing the URL manually):

  • macOS / Linux: run ifconfig | grep "inet " in Terminal — look for a number like 192.168.x.x
  • Windows: run ipconfig in Command Prompt — look for "IPv4 Address"

Then open http://192.168.x.x:3000/phone on the phone.


Using the Application

  • Hold the phone face-up in front of the webcam. A single black-and-white marker centred at the top of the screen allows the webcam to track position and rotation.
  • The laptop map shows a blue rectangle representing the area currently visible on the phone.
  • Touch and drag on the phone to draw annotations. They appear on both screens simultaneously.
  • Use the toolbar buttons to invert controls or enable map rotation.

Toolbar controls

ButtonDescription
MapActivates the map example (the only built-in example)
InvertFlips the phone's position mapping so moving up moves the map north
No RotationWhen toggled to "Rotating", the phone's yaw rotates the mini-map
Copy Phone LinkCopies the LAN phone URL to the clipboard
Show QR CodeDisplays a QR code for the LAN phone URL

Lighting tips

  • Keep the phone screen brightness high — the marker needs clear contrast.
  • Avoid direct glare on the phone screen.
  • If detection is unreliable, reduce ambient light reflections.
  • When the phone is lost, the marker automatically grows to help re-acquisition.

File Structure

server.js Node.js / Express / Socket.io server
public/
index.html Laptop UI (webcam + overlay + map panel)
css/style.css Dark professional theme
js/
app.js Main orchestrator (webcam loop, detection, state)
jsarDetector.js Detects marker ID 0 via jsartoolkit5 — position and rotation from corners
homography.js Perspective-transform math (DLT algorithm)
vendor/
artoolkit.min.js jsartoolkit5 self-contained bundle
examples/
mapExample.js Leaflet map — phone position to geographic coordinate
phone/
index.html Phone PWA (auto-connects, no room code needed)
manifest.json
sw.js Service worker (offline cache)
css/phone.css
js/
phoneApp.js Auto-connection and example lifecycle
drawMarker.js Renders ArUco marker patterns onto canvas
examples/
mapPhone.js Leaflet mini-map tracking the phone's geographic position

Technical Notes

  • Detection uses jsartoolkit5 with a single 3x3 barcode marker (ID 0) centred at the top of the phone screen. The marker's four corners provide position and rotation.
  • The server detects the machine's LAN IP at startup and exposes it via /api/config so the laptop app can generate a correct phone URL for the QR code and copy-link button.
  • All devices share a single server session — no room codes or pairing required.
  • The phone mini-map renders at three zoom levels deeper than the laptop map and freezes during active drawing to keep strokes clean.
  • Drawn paths are placed in a dedicated Leaflet pane (drawPane) at z-index 650 with overflow: visible to prevent clipping at tile boundaries.

About

Project using a camera

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Latest commit

History

41 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

ReverseProjection

reverse-projection.mp4

A live augmented-reality application that turns a webcam into a position sensor: hold your phone in front of the camera and an interactive map appears, perfectly aligned with your physical location. Touch the phone screen to draw annotations that appear on both the phone and the large display simultaneously.

Anyone who opens the phone page connects automatically.


How It Works

  1. The phone displays a single ArUco fiducial marker (ID 0) centred at the top of the screen so the webcam can detect and track it.
  2. The laptop accesses the webcam, detects the marker using jsartoolkit5, and derives the phone's position and rotation from the marker's four corner vertices.
  3. Both devices communicate over WebSockets (Socket.io) through a local Node.js server running on your network.
  4. The laptop map shows a bounding box representing the area currently visible on the phone's mini-map.
  5. Touching the phone screen sends lat/lng coordinates back to the laptop and draws a stroke on both maps simultaneously.

Mapping Model

The project uses three different coordinate spaces. Keeping them separate is the key to making the overlays line up.

  1. Camera space The laptop webcam sees the physical phone. Marker detection gives a phone centre (nx, ny) in normalised camera coordinates and a phone rotation θ.

  2. Phone viewport space The phone reports the size of the actually visible interactive area, not the whole page. This is the rectangle below the marker and above the bottom status bar. That reported width and height are the only dimensions that may be used to infer the visible viewport.

  3. Content space Each example maps the visible phone viewport into its own content space.

    • Map mode: geographic map bounds.
    • Screen mode: source video pixels from the captured desktop stream.
    • Whiteboard mode: whiteboard world coordinates.

The important rule is:

  • The blue overlay on the laptop must represent the phone's visible content viewport in content space.
  • It must not represent the physical phone body in camera space.

Screen mode mapping

Screen mode uses the exact same viewport transform on both devices.

  1. The phone renders the incoming desktop stream into its visible area using a cover transform:
    • base scale = max(phoneW / sourceW, phoneH / sourceH)
    • total scale = base scale * zoom
  2. The phone then translates the scaled source so that the tracked point (nx, ny) becomes the centre of the visible phone viewport.
  3. That translation is clamped so the source never exposes empty edges.
  4. The laptop overlay must compute the inverse of that same transform and draw the resulting source rectangle on top of the displayed preview.
  5. If rotation is enabled, the same viewport rectangle is rotated by the tracked phone angle. If rotation is disabled, both the phone and the overlay stay axis-aligned.

This means the laptop overlay for Screen mode must be derived from:

  • source video size
  • reported visible phone viewport size
  • zoom
  • tracked (nx, ny)
  • tracked rotation when enabled

It must not be derived from the marker's apparent size in the webcam image.

Map mode mapping

Map mode uses the reported visible phone viewport size to convert the tracked phone centre into a geographic rectangle.

  1. The laptop maps (nx, ny) into the current map bounds.
  2. The reported phone viewport width and height are converted to fractions of the camera frame using the detected physical scale.
  3. Those fractions are then converted into fractions of the current Leaflet map bounds.
  4. If rotation is enabled, the rectangle is rotated. Otherwise it stays upright.

The same visible viewport dimensions must be used consistently on the phone, in the laptop state, and in the laptop overlay.


Getting Started

Step 1 — Install Node.js

Node.js is the JavaScript runtime the server needs. Install it once; it stays on your machine.

macOS

The easiest way is Homebrew, a package manager for macOS. If you have never used it:

  1. Open Terminal (press Command+Space, type "Terminal", press Enter).

  2. Paste the following command and press Enter. It will ask for your Mac password:

    /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
    
  3. After Homebrew finishes, install Node.js:

    brew install node
    
  4. Verify the installation:

    node --version
    

    You should see something like v20.x.x.

Windows

  1. Open a web browser and go to https://nodejs.org.

  2. Download the LTS installer (the button labelled "LTS" — Long Term Support).

  3. Run the downloaded .msi file and follow the installer steps. Leave all options at their defaults.

  4. When it finishes, open Command Prompt (press Windows+R, type cmd, press Enter) and verify:

    node --version
    

Linux (Ubuntu / Debian)

sudo apt update
sudo apt install nodejs npm
node --version

For other distributions, see https://nodejs.org/en/download/package-manager.


Step 2 — Download the project

If you have Git installed:

git clone https://github.com/lemio/ReverseProjection.git
cd ReverseProjection

If you do not have Git, download the ZIP from GitHub (click the green "Code" button, then "Download ZIP"), unzip it, and open a terminal inside the folder.


Step 3 — Install dependencies and start the server

In your terminal, inside the project folder:

npm install
npm start

You should see:

ReverseProjection server running at http://localhost:3000

Leave this terminal window open — the server must keep running.


Step 4 — Open the laptop app

Open a browser (Chrome or Edge recommended for best webcam support) and go to:

http://localhost:3000

Allow camera access when the browser asks. The webcam feed will appear.


Step 5 — Connect a phone

The phone must be on the same Wi-Fi network as the laptop.

  1. On the laptop, click Copy Phone Link in the toolbar. This copies the URL to your clipboard.
  2. Open that URL on the phone's browser, or click Show QR Code and scan it with the phone camera.
  3. The phone will connect automatically and display an interactive map.

To find your laptop's local IP address (for typing the URL manually):

  • macOS / Linux: run ifconfig | grep "inet " in Terminal — look for a number like 192.168.x.x
  • Windows: run ipconfig in Command Prompt — look for "IPv4 Address"

Then open http://192.168.x.x:3000/phone on the phone.


Using the Application

  • Hold the phone face-up in front of the webcam. A single black-and-white marker centred at the top of the screen allows the webcam to track position and rotation.
  • The laptop map shows a blue rectangle representing the area currently visible on the phone.
  • Touch and drag on the phone to draw annotations. They appear on both screens simultaneously.
  • Use the toolbar buttons to invert controls or enable map rotation.

Toolbar controls

ButtonDescription
MapActivates the map example (the only built-in example)
InvertFlips the phone's position mapping so moving up moves the map north
No RotationWhen toggled to "Rotating", the phone's yaw rotates the mini-map
Copy Phone LinkCopies the LAN phone URL to the clipboard
Show QR CodeDisplays a QR code for the LAN phone URL

Lighting tips

  • Keep the phone screen brightness high — the marker needs clear contrast.
  • Avoid direct glare on the phone screen.
  • If detection is unreliable, reduce ambient light reflections.
  • When the phone is lost, the marker automatically grows to help re-acquisition.

File Structure

server.js Node.js / Express / Socket.io server
public/
index.html Laptop UI (webcam + overlay + map panel)
css/style.css Dark professional theme
js/
app.js Main orchestrator (webcam loop, detection, state)
jsarDetector.js Detects marker ID 0 via jsartoolkit5 — position and rotation from corners
homography.js Perspective-transform math (DLT algorithm)
vendor/
artoolkit.min.js jsartoolkit5 self-contained bundle
examples/
mapExample.js Leaflet map — phone position to geographic coordinate
phone/
index.html Phone PWA (auto-connects, no room code needed)
manifest.json
sw.js Service worker (offline cache)
css/phone.css
js/
phoneApp.js Auto-connection and example lifecycle
drawMarker.js Renders ArUco marker patterns onto canvas
examples/
mapPhone.js Leaflet mini-map tracking the phone's geographic position

Technical Notes

  • Detection uses jsartoolkit5 with a single 3x3 barcode marker (ID 0) centred at the top of the phone screen. The marker's four corners provide position and rotation.
  • The server detects the machine's LAN IP at startup and exposes it via /api/config so the laptop app can generate a correct phone URL for the QR code and copy-link button.
  • All devices share a single server session — no room codes or pairing required.
  • The phone mini-map renders at three zoom levels deeper than the laptop map and freezes during active drawing to keep strokes clean.
  • Drawn paths are placed in a dedicated Leaflet pane (drawPane) at z-index 650 with overflow: visible to prevent clipping at tile boundaries.

About

Project using a camera

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

41 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

ReverseProjection

reverse-projection.mp4

A live augmented-reality application that turns a webcam into a position sensor: hold your phone in front of the camera and an interactive map appears, perfectly aligned with your physical location. Touch the phone screen to draw annotations that appear on both the phone and the large display simultaneously.

Anyone who opens the phone page connects automatically.


How It Works

  1. The phone displays a single ArUco fiducial marker (ID 0) centred at the top of the screen so the webcam can detect and track it.
  2. The laptop accesses the webcam, detects the marker using jsartoolkit5, and derives the phone's position and rotation from the marker's four corner vertices.
  3. Both devices communicate over WebSockets (Socket.io) through a local Node.js server running on your network.
  4. The laptop map shows a bounding box representing the area currently visible on the phone's mini-map.
  5. Touching the phone screen sends lat/lng coordinates back to the laptop and draws a stroke on both maps simultaneously.

Mapping Model

The project uses three different coordinate spaces. Keeping them separate is the key to making the overlays line up.

  1. Camera space The laptop webcam sees the physical phone. Marker detection gives a phone centre (nx, ny) in normalised camera coordinates and a phone rotation θ.

  2. Phone viewport space The phone reports the size of the actually visible interactive area, not the whole page. This is the rectangle below the marker and above the bottom status bar. That reported width and height are the only dimensions that may be used to infer the visible viewport.

  3. Content space Each example maps the visible phone viewport into its own content space.

    • Map mode: geographic map bounds.
    • Screen mode: source video pixels from the captured desktop stream.
    • Whiteboard mode: whiteboard world coordinates.

The important rule is:

  • The blue overlay on the laptop must represent the phone's visible content viewport in content space.
  • It must not represent the physical phone body in camera space.

Screen mode mapping

Screen mode uses the exact same viewport transform on both devices.

  1. The phone renders the incoming desktop stream into its visible area using a cover transform:
    • base scale = max(phoneW / sourceW, phoneH / sourceH)
    • total scale = base scale * zoom
  2. The phone then translates the scaled source so that the tracked point (nx, ny) becomes the centre of the visible phone viewport.
  3. That translation is clamped so the source never exposes empty edges.
  4. The laptop overlay must compute the inverse of that same transform and draw the resulting source rectangle on top of the displayed preview.
  5. If rotation is enabled, the same viewport rectangle is rotated by the tracked phone angle. If rotation is disabled, both the phone and the overlay stay axis-aligned.

This means the laptop overlay for Screen mode must be derived from:

  • source video size
  • reported visible phone viewport size
  • zoom
  • tracked (nx, ny)
  • tracked rotation when enabled

It must not be derived from the marker's apparent size in the webcam image.

Map mode mapping

Map mode uses the reported visible phone viewport size to convert the tracked phone centre into a geographic rectangle.

  1. The laptop maps (nx, ny) into the current map bounds.
  2. The reported phone viewport width and height are converted to fractions of the camera frame using the detected physical scale.
  3. Those fractions are then converted into fractions of the current Leaflet map bounds.
  4. If rotation is enabled, the rectangle is rotated. Otherwise it stays upright.

The same visible viewport dimensions must be used consistently on the phone, in the laptop state, and in the laptop overlay.


Getting Started

Step 1 — Install Node.js

Node.js is the JavaScript runtime the server needs. Install it once; it stays on your machine.

macOS

The easiest way is Homebrew, a package manager for macOS. If you have never used it:

  1. Open Terminal (press Command+Space, type "Terminal", press Enter).

  2. Paste the following command and press Enter. It will ask for your Mac password:

    /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
    
  3. After Homebrew finishes, install Node.js:

    brew install node
    
  4. Verify the installation:

    node --version
    

    You should see something like v20.x.x.

Windows

  1. Open a web browser and go to https://nodejs.org.

  2. Download the LTS installer (the button labelled "LTS" — Long Term Support).

  3. Run the downloaded .msi file and follow the installer steps. Leave all options at their defaults.

  4. When it finishes, open Command Prompt (press Windows+R, type cmd, press Enter) and verify:

    node --version
    

Linux (Ubuntu / Debian)

sudo apt update
sudo apt install nodejs npm
node --version

For other distributions, see https://nodejs.org/en/download/package-manager.


Step 2 — Download the project

If you have Git installed:

git clone https://github.com/lemio/ReverseProjection.git
cd ReverseProjection

If you do not have Git, download the ZIP from GitHub (click the green "Code" button, then "Download ZIP"), unzip it, and open a terminal inside the folder.


Step 3 — Install dependencies and start the server

In your terminal, inside the project folder:

npm install
npm start

You should see:

ReverseProjection server running at http://localhost:3000

Leave this terminal window open — the server must keep running.


Step 4 — Open the laptop app

Open a browser (Chrome or Edge recommended for best webcam support) and go to:

http://localhost:3000

Allow camera access when the browser asks. The webcam feed will appear.


Step 5 — Connect a phone

The phone must be on the same Wi-Fi network as the laptop.

  1. On the laptop, click Copy Phone Link in the toolbar. This copies the URL to your clipboard.
  2. Open that URL on the phone's browser, or click Show QR Code and scan it with the phone camera.
  3. The phone will connect automatically and display an interactive map.

To find your laptop's local IP address (for typing the URL manually):

  • macOS / Linux: run ifconfig | grep "inet " in Terminal — look for a number like 192.168.x.x
  • Windows: run ipconfig in Command Prompt — look for "IPv4 Address"

Then open http://192.168.x.x:3000/phone on the phone.


Using the Application

  • Hold the phone face-up in front of the webcam. A single black-and-white marker centred at the top of the screen allows the webcam to track position and rotation.
  • The laptop map shows a blue rectangle representing the area currently visible on the phone.
  • Touch and drag on the phone to draw annotations. They appear on both screens simultaneously.
  • Use the toolbar buttons to invert controls or enable map rotation.

Toolbar controls

ButtonDescription
MapActivates the map example (the only built-in example)
InvertFlips the phone's position mapping so moving up moves the map north
No RotationWhen toggled to "Rotating", the phone's yaw rotates the mini-map
Copy Phone LinkCopies the LAN phone URL to the clipboard
Show QR CodeDisplays a QR code for the LAN phone URL

Lighting tips

  • Keep the phone screen brightness high — the marker needs clear contrast.
  • Avoid direct glare on the phone screen.
  • If detection is unreliable, reduce ambient light reflections.
  • When the phone is lost, the marker automatically grows to help re-acquisition.

File Structure

server.js Node.js / Express / Socket.io server
public/
index.html Laptop UI (webcam + overlay + map panel)
css/style.css Dark professional theme
js/
app.js Main orchestrator (webcam loop, detection, state)
jsarDetector.js Detects marker ID 0 via jsartoolkit5 — position and rotation from corners
homography.js Perspective-transform math (DLT algorithm)
vendor/
artoolkit.min.js jsartoolkit5 self-contained bundle
examples/
mapExample.js Leaflet map — phone position to geographic coordinate
phone/
index.html Phone PWA (auto-connects, no room code needed)
manifest.json
sw.js Service worker (offline cache)
css/phone.css
js/
phoneApp.js Auto-connection and example lifecycle
drawMarker.js Renders ArUco marker patterns onto canvas
examples/
mapPhone.js Leaflet mini-map tracking the phone's geographic position

Technical Notes

  • Detection uses jsartoolkit5 with a single 3x3 barcode marker (ID 0) centred at the top of the phone screen. The marker's four corners provide position and rotation.
  • The server detects the machine's LAN IP at startup and exposes it via /api/config so the laptop app can generate a correct phone URL for the QR code and copy-link button.
  • All devices share a single server session — no room codes or pairing required.
  • The phone mini-map renders at three zoom levels deeper than the laptop map and freezes during active drawing to keep strokes clean.
  • Drawn paths are placed in a dedicated Leaflet pane (drawPane) at z-index 650 with overflow: visible to prevent clipping at tile boundaries.

About

Project using a camera

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

41 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

ReverseProjection

reverse-projection.mp4

A live augmented-reality application that turns a webcam into a position sensor: hold your phone in front of the camera and an interactive map appears, perfectly aligned with your physical location. Touch the phone screen to draw annotations that appear on both the phone and the large display simultaneously.

Anyone who opens the phone page connects automatically.


How It Works

  1. The phone displays a single ArUco fiducial marker (ID 0) centred at the top of the screen so the webcam can detect and track it.
  2. The laptop accesses the webcam, detects the marker using jsartoolkit5, and derives the phone's position and rotation from the marker's four corner vertices.
  3. Both devices communicate over WebSockets (Socket.io) through a local Node.js server running on your network.
  4. The laptop map shows a bounding box representing the area currently visible on the phone's mini-map.
  5. Touching the phone screen sends lat/lng coordinates back to the laptop and draws a stroke on both maps simultaneously.

Mapping Model

The project uses three different coordinate spaces. Keeping them separate is the key to making the overlays line up.

  1. Camera space The laptop webcam sees the physical phone. Marker detection gives a phone centre (nx, ny) in normalised camera coordinates and a phone rotation θ.

  2. Phone viewport space The phone reports the size of the actually visible interactive area, not the whole page. This is the rectangle below the marker and above the bottom status bar. That reported width and height are the only dimensions that may be used to infer the visible viewport.

  3. Content space Each example maps the visible phone viewport into its own content space.

    • Map mode: geographic map bounds.
    • Screen mode: source video pixels from the captured desktop stream.
    • Whiteboard mode: whiteboard world coordinates.

The important rule is:

  • The blue overlay on the laptop must represent the phone's visible content viewport in content space.
  • It must not represent the physical phone body in camera space.

Screen mode mapping

Screen mode uses the exact same viewport transform on both devices.

  1. The phone renders the incoming desktop stream into its visible area using a cover transform:
    • base scale = max(phoneW / sourceW, phoneH / sourceH)
    • total scale = base scale * zoom
  2. The phone then translates the scaled source so that the tracked point (nx, ny) becomes the centre of the visible phone viewport.
  3. That translation is clamped so the source never exposes empty edges.
  4. The laptop overlay must compute the inverse of that same transform and draw the resulting source rectangle on top of the displayed preview.
  5. If rotation is enabled, the same viewport rectangle is rotated by the tracked phone angle. If rotation is disabled, both the phone and the overlay stay axis-aligned.

This means the laptop overlay for Screen mode must be derived from:

  • source video size
  • reported visible phone viewport size
  • zoom
  • tracked (nx, ny)
  • tracked rotation when enabled

It must not be derived from the marker's apparent size in the webcam image.

Map mode mapping

Map mode uses the reported visible phone viewport size to convert the tracked phone centre into a geographic rectangle.

  1. The laptop maps (nx, ny) into the current map bounds.
  2. The reported phone viewport width and height are converted to fractions of the camera frame using the detected physical scale.
  3. Those fractions are then converted into fractions of the current Leaflet map bounds.
  4. If rotation is enabled, the rectangle is rotated. Otherwise it stays upright.

The same visible viewport dimensions must be used consistently on the phone, in the laptop state, and in the laptop overlay.


Getting Started

Step 1 — Install Node.js

Node.js is the JavaScript runtime the server needs. Install it once; it stays on your machine.

macOS

The easiest way is Homebrew, a package manager for macOS. If you have never used it:

  1. Open Terminal (press Command+Space, type "Terminal", press Enter).

  2. Paste the following command and press Enter. It will ask for your Mac password:

    /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
    
  3. After Homebrew finishes, install Node.js:

    brew install node
    
  4. Verify the installation:

    node --version
    

    You should see something like v20.x.x.

Windows

  1. Open a web browser and go to https://nodejs.org.

  2. Download the LTS installer (the button labelled "LTS" — Long Term Support).

  3. Run the downloaded .msi file and follow the installer steps. Leave all options at their defaults.

  4. When it finishes, open Command Prompt (press Windows+R, type cmd, press Enter) and verify:

    node --version
    

Linux (Ubuntu / Debian)

sudo apt update
sudo apt install nodejs npm
node --version

For other distributions, see https://nodejs.org/en/download/package-manager.


Step 2 — Download the project

If you have Git installed:

git clone https://github.com/lemio/ReverseProjection.git
cd ReverseProjection

If you do not have Git, download the ZIP from GitHub (click the green "Code" button, then "Download ZIP"), unzip it, and open a terminal inside the folder.


Step 3 — Install dependencies and start the server

In your terminal, inside the project folder:

npm install
npm start

You should see:

ReverseProjection server running at http://localhost:3000

Leave this terminal window open — the server must keep running.


Step 4 — Open the laptop app

Open a browser (Chrome or Edge recommended for best webcam support) and go to:

http://localhost:3000

Allow camera access when the browser asks. The webcam feed will appear.


Step 5 — Connect a phone

The phone must be on the same Wi-Fi network as the laptop.

  1. On the laptop, click Copy Phone Link in the toolbar. This copies the URL to your clipboard.
  2. Open that URL on the phone's browser, or click Show QR Code and scan it with the phone camera.
  3. The phone will connect automatically and display an interactive map.

To find your laptop's local IP address (for typing the URL manually):

  • macOS / Linux: run ifconfig | grep "inet " in Terminal — look for a number like 192.168.x.x
  • Windows: run ipconfig in Command Prompt — look for "IPv4 Address"

Then open http://192.168.x.x:3000/phone on the phone.


Using the Application

  • Hold the phone face-up in front of the webcam. A single black-and-white marker centred at the top of the screen allows the webcam to track position and rotation.
  • The laptop map shows a blue rectangle representing the area currently visible on the phone.
  • Touch and drag on the phone to draw annotations. They appear on both screens simultaneously.
  • Use the toolbar buttons to invert controls or enable map rotation.

Toolbar controls

ButtonDescription
MapActivates the map example (the only built-in example)
InvertFlips the phone's position mapping so moving up moves the map north
No RotationWhen toggled to "Rotating", the phone's yaw rotates the mini-map
Copy Phone LinkCopies the LAN phone URL to the clipboard
Show QR CodeDisplays a QR code for the LAN phone URL

Lighting tips

  • Keep the phone screen brightness high — the marker needs clear contrast.
  • Avoid direct glare on the phone screen.
  • If detection is unreliable, reduce ambient light reflections.
  • When the phone is lost, the marker automatically grows to help re-acquisition.

File Structure

server.js Node.js / Express / Socket.io server
public/
index.html Laptop UI (webcam + overlay + map panel)
css/style.css Dark professional theme
js/
app.js Main orchestrator (webcam loop, detection, state)
jsarDetector.js Detects marker ID 0 via jsartoolkit5 — position and rotation from corners
homography.js Perspective-transform math (DLT algorithm)
vendor/
artoolkit.min.js jsartoolkit5 self-contained bundle
examples/
mapExample.js Leaflet map — phone position to geographic coordinate
phone/
index.html Phone PWA (auto-connects, no room code needed)
manifest.json
sw.js Service worker (offline cache)
css/phone.css
js/
phoneApp.js Auto-connection and example lifecycle
drawMarker.js Renders ArUco marker patterns onto canvas
examples/
mapPhone.js Leaflet mini-map tracking the phone's geographic position

Technical Notes

  • Detection uses jsartoolkit5 with a single 3x3 barcode marker (ID 0) centred at the top of the phone screen. The marker's four corners provide position and rotation.
  • The server detects the machine's LAN IP at startup and exposes it via /api/config so the laptop app can generate a correct phone URL for the QR code and copy-link button.
  • All devices share a single server session — no room codes or pairing required.
  • The phone mini-map renders at three zoom levels deeper than the laptop map and freezes during active drawing to keep strokes clean.
  • Drawn paths are placed in a dedicated Leaflet pane (drawPane) at z-index 650 with overflow: visible to prevent clipping at tile boundaries.

About

Project using a camera

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Latest commit

History

41 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

ReverseProjection

reverse-projection.mp4

A live augmented-reality application that turns a webcam into a position sensor: hold your phone in front of the camera and an interactive map appears, perfectly aligned with your physical location. Touch the phone screen to draw annotations that appear on both the phone and the large display simultaneously.

Anyone who opens the phone page connects automatically.


How It Works

  1. The phone displays a single ArUco fiducial marker (ID 0) centred at the top of the screen so the webcam can detect and track it.
  2. The laptop accesses the webcam, detects the marker using jsartoolkit5, and derives the phone's position and rotation from the marker's four corner vertices.
  3. Both devices communicate over WebSockets (Socket.io) through a local Node.js server running on your network.
  4. The laptop map shows a bounding box representing the area currently visible on the phone's mini-map.
  5. Touching the phone screen sends lat/lng coordinates back to the laptop and draws a stroke on both maps simultaneously.

Mapping Model

The project uses three different coordinate spaces. Keeping them separate is the key to making the overlays line up.

  1. Camera space The laptop webcam sees the physical phone. Marker detection gives a phone centre (nx, ny) in normalised camera coordinates and a phone rotation θ.

  2. Phone viewport space The phone reports the size of the actually visible interactive area, not the whole page. This is the rectangle below the marker and above the bottom status bar. That reported width and height are the only dimensions that may be used to infer the visible viewport.

  3. Content space Each example maps the visible phone viewport into its own content space.

    • Map mode: geographic map bounds.
    • Screen mode: source video pixels from the captured desktop stream.
    • Whiteboard mode: whiteboard world coordinates.

The important rule is:

  • The blue overlay on the laptop must represent the phone's visible content viewport in content space.
  • It must not represent the physical phone body in camera space.

Screen mode mapping

Screen mode uses the exact same viewport transform on both devices.

  1. The phone renders the incoming desktop stream into its visible area using a cover transform:
    • base scale = max(phoneW / sourceW, phoneH / sourceH)
    • total scale = base scale * zoom
  2. The phone then translates the scaled source so that the tracked point (nx, ny) becomes the centre of the visible phone viewport.
  3. That translation is clamped so the source never exposes empty edges.
  4. The laptop overlay must compute the inverse of that same transform and draw the resulting source rectangle on top of the displayed preview.
  5. If rotation is enabled, the same viewport rectangle is rotated by the tracked phone angle. If rotation is disabled, both the phone and the overlay stay axis-aligned.

This means the laptop overlay for Screen mode must be derived from:

  • source video size
  • reported visible phone viewport size
  • zoom
  • tracked (nx, ny)
  • tracked rotation when enabled

It must not be derived from the marker's apparent size in the webcam image.

Map mode mapping

Map mode uses the reported visible phone viewport size to convert the tracked phone centre into a geographic rectangle.

  1. The laptop maps (nx, ny) into the current map bounds.
  2. The reported phone viewport width and height are converted to fractions of the camera frame using the detected physical scale.
  3. Those fractions are then converted into fractions of the current Leaflet map bounds.
  4. If rotation is enabled, the rectangle is rotated. Otherwise it stays upright.

The same visible viewport dimensions must be used consistently on the phone, in the laptop state, and in the laptop overlay.


Getting Started

Step 1 — Install Node.js

Node.js is the JavaScript runtime the server needs. Install it once; it stays on your machine.

macOS

The easiest way is Homebrew, a package manager for macOS. If you have never used it:

  1. Open Terminal (press Command+Space, type "Terminal", press Enter).

  2. Paste the following command and press Enter. It will ask for your Mac password:

    /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
    
  3. After Homebrew finishes, install Node.js:

    brew install node
    
  4. Verify the installation:

    node --version
    

    You should see something like v20.x.x.

Windows

  1. Open a web browser and go to https://nodejs.org.

  2. Download the LTS installer (the button labelled "LTS" — Long Term Support).

  3. Run the downloaded .msi file and follow the installer steps. Leave all options at their defaults.

  4. When it finishes, open Command Prompt (press Windows+R, type cmd, press Enter) and verify:

    node --version
    

Linux (Ubuntu / Debian)

sudo apt update
sudo apt install nodejs npm
node --version

For other distributions, see https://nodejs.org/en/download/package-manager.


Step 2 — Download the project

If you have Git installed:

git clone https://github.com/lemio/ReverseProjection.git
cd ReverseProjection

If you do not have Git, download the ZIP from GitHub (click the green "Code" button, then "Download ZIP"), unzip it, and open a terminal inside the folder.


Step 3 — Install dependencies and start the server

In your terminal, inside the project folder:

npm install
npm start

You should see:

ReverseProjection server running at http://localhost:3000

Leave this terminal window open — the server must keep running.


Step 4 — Open the laptop app

Open a browser (Chrome or Edge recommended for best webcam support) and go to:

http://localhost:3000

Allow camera access when the browser asks. The webcam feed will appear.


Step 5 — Connect a phone

The phone must be on the same Wi-Fi network as the laptop.

  1. On the laptop, click Copy Phone Link in the toolbar. This copies the URL to your clipboard.
  2. Open that URL on the phone's browser, or click Show QR Code and scan it with the phone camera.
  3. The phone will connect automatically and display an interactive map.

To find your laptop's local IP address (for typing the URL manually):

  • macOS / Linux: run ifconfig | grep "inet " in Terminal — look for a number like 192.168.x.x
  • Windows: run ipconfig in Command Prompt — look for "IPv4 Address"

Then open http://192.168.x.x:3000/phone on the phone.


Using the Application

  • Hold the phone face-up in front of the webcam. A single black-and-white marker centred at the top of the screen allows the webcam to track position and rotation.
  • The laptop map shows a blue rectangle representing the area currently visible on the phone.
  • Touch and drag on the phone to draw annotations. They appear on both screens simultaneously.
  • Use the toolbar buttons to invert controls or enable map rotation.

Toolbar controls

ButtonDescription
MapActivates the map example (the only built-in example)
InvertFlips the phone's position mapping so moving up moves the map north
No RotationWhen toggled to "Rotating", the phone's yaw rotates the mini-map
Copy Phone LinkCopies the LAN phone URL to the clipboard
Show QR CodeDisplays a QR code for the LAN phone URL

Lighting tips

  • Keep the phone screen brightness high — the marker needs clear contrast.
  • Avoid direct glare on the phone screen.
  • If detection is unreliable, reduce ambient light reflections.
  • When the phone is lost, the marker automatically grows to help re-acquisition.

File Structure

server.js Node.js / Express / Socket.io server
public/
index.html Laptop UI (webcam + overlay + map panel)
css/style.css Dark professional theme
js/
app.js Main orchestrator (webcam loop, detection, state)
jsarDetector.js Detects marker ID 0 via jsartoolkit5 — position and rotation from corners
homography.js Perspective-transform math (DLT algorithm)
vendor/
artoolkit.min.js jsartoolkit5 self-contained bundle
examples/
mapExample.js Leaflet map — phone position to geographic coordinate
phone/
index.html Phone PWA (auto-connects, no room code needed)
manifest.json
sw.js Service worker (offline cache)
css/phone.css
js/
phoneApp.js Auto-connection and example lifecycle
drawMarker.js Renders ArUco marker patterns onto canvas
examples/
mapPhone.js Leaflet mini-map tracking the phone's geographic position

Technical Notes

  • Detection uses jsartoolkit5 with a single 3x3 barcode marker (ID 0) centred at the top of the phone screen. The marker's four corners provide position and rotation.
  • The server detects the machine's LAN IP at startup and exposes it via /api/config so the laptop app can generate a correct phone URL for the QR code and copy-link button.
  • All devices share a single server session — no room codes or pairing required.
  • The phone mini-map renders at three zoom levels deeper than the laptop map and freezes during active drawing to keep strokes clean.
  • Drawn paths are placed in a dedicated Leaflet pane (drawPane) at z-index 650 with overflow: visible to prevent clipping at tile boundaries.

About

Project using a camera

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

41 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

ReverseProjection

reverse-projection.mp4

A live augmented-reality application that turns a webcam into a position sensor: hold your phone in front of the camera and an interactive map appears, perfectly aligned with your physical location. Touch the phone screen to draw annotations that appear on both the phone and the large display simultaneously.

Anyone who opens the phone page connects automatically.


How It Works

  1. The phone displays a single ArUco fiducial marker (ID 0) centred at the top of the screen so the webcam can detect and track it.
  2. The laptop accesses the webcam, detects the marker using jsartoolkit5, and derives the phone's position and rotation from the marker's four corner vertices.
  3. Both devices communicate over WebSockets (Socket.io) through a local Node.js server running on your network.
  4. The laptop map shows a bounding box representing the area currently visible on the phone's mini-map.
  5. Touching the phone screen sends lat/lng coordinates back to the laptop and draws a stroke on both maps simultaneously.

Mapping Model

The project uses three different coordinate spaces. Keeping them separate is the key to making the overlays line up.

  1. Camera space The laptop webcam sees the physical phone. Marker detection gives a phone centre (nx, ny) in normalised camera coordinates and a phone rotation θ.

  2. Phone viewport space The phone reports the size of the actually visible interactive area, not the whole page. This is the rectangle below the marker and above the bottom status bar. That reported width and height are the only dimensions that may be used to infer the visible viewport.

  3. Content space Each example maps the visible phone viewport into its own content space.

    • Map mode: geographic map bounds.
    • Screen mode: source video pixels from the captured desktop stream.
    • Whiteboard mode: whiteboard world coordinates.

The important rule is:

  • The blue overlay on the laptop must represent the phone's visible content viewport in content space.
  • It must not represent the physical phone body in camera space.

Screen mode mapping

Screen mode uses the exact same viewport transform on both devices.

  1. The phone renders the incoming desktop stream into its visible area using a cover transform:
    • base scale = max(phoneW / sourceW, phoneH / sourceH)
    • total scale = base scale * zoom
  2. The phone then translates the scaled source so that the tracked point (nx, ny) becomes the centre of the visible phone viewport.
  3. That translation is clamped so the source never exposes empty edges.
  4. The laptop overlay must compute the inverse of that same transform and draw the resulting source rectangle on top of the displayed preview.
  5. If rotation is enabled, the same viewport rectangle is rotated by the tracked phone angle. If rotation is disabled, both the phone and the overlay stay axis-aligned.

This means the laptop overlay for Screen mode must be derived from:

  • source video size
  • reported visible phone viewport size
  • zoom
  • tracked (nx, ny)
  • tracked rotation when enabled

It must not be derived from the marker's apparent size in the webcam image.

Map mode mapping

Map mode uses the reported visible phone viewport size to convert the tracked phone centre into a geographic rectangle.

  1. The laptop maps (nx, ny) into the current map bounds.
  2. The reported phone viewport width and height are converted to fractions of the camera frame using the detected physical scale.
  3. Those fractions are then converted into fractions of the current Leaflet map bounds.
  4. If rotation is enabled, the rectangle is rotated. Otherwise it stays upright.

The same visible viewport dimensions must be used consistently on the phone, in the laptop state, and in the laptop overlay.


Getting Started

Step 1 — Install Node.js

Node.js is the JavaScript runtime the server needs. Install it once; it stays on your machine.

macOS

The easiest way is Homebrew, a package manager for macOS. If you have never used it:

  1. Open Terminal (press Command+Space, type "Terminal", press Enter).

  2. Paste the following command and press Enter. It will ask for your Mac password:

    /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
    
  3. After Homebrew finishes, install Node.js:

    brew install node
    
  4. Verify the installation:

    node --version
    

    You should see something like v20.x.x.

Windows

  1. Open a web browser and go to https://nodejs.org.

  2. Download the LTS installer (the button labelled "LTS" — Long Term Support).

  3. Run the downloaded .msi file and follow the installer steps. Leave all options at their defaults.

  4. When it finishes, open Command Prompt (press Windows+R, type cmd, press Enter) and verify:

    node --version
    

Linux (Ubuntu / Debian)

sudo apt update
sudo apt install nodejs npm
node --version

For other distributions, see https://nodejs.org/en/download/package-manager.


Step 2 — Download the project

If you have Git installed:

git clone https://github.com/lemio/ReverseProjection.git
cd ReverseProjection

If you do not have Git, download the ZIP from GitHub (click the green "Code" button, then "Download ZIP"), unzip it, and open a terminal inside the folder.


Step 3 — Install dependencies and start the server

In your terminal, inside the project folder:

npm install
npm start

You should see:

ReverseProjection server running at http://localhost:3000

Leave this terminal window open — the server must keep running.


Step 4 — Open the laptop app

Open a browser (Chrome or Edge recommended for best webcam support) and go to:

http://localhost:3000

Allow camera access when the browser asks. The webcam feed will appear.


Step 5 — Connect a phone

The phone must be on the same Wi-Fi network as the laptop.

  1. On the laptop, click Copy Phone Link in the toolbar. This copies the URL to your clipboard.
  2. Open that URL on the phone's browser, or click Show QR Code and scan it with the phone camera.
  3. The phone will connect automatically and display an interactive map.

To find your laptop's local IP address (for typing the URL manually):

  • macOS / Linux: run ifconfig | grep "inet " in Terminal — look for a number like 192.168.x.x
  • Windows: run ipconfig in Command Prompt — look for "IPv4 Address"

Then open http://192.168.x.x:3000/phone on the phone.


Using the Application

  • Hold the phone face-up in front of the webcam. A single black-and-white marker centred at the top of the screen allows the webcam to track position and rotation.
  • The laptop map shows a blue rectangle representing the area currently visible on the phone.
  • Touch and drag on the phone to draw annotations. They appear on both screens simultaneously.
  • Use the toolbar buttons to invert controls or enable map rotation.

Toolbar controls

ButtonDescription
MapActivates the map example (the only built-in example)
InvertFlips the phone's position mapping so moving up moves the map north
No RotationWhen toggled to "Rotating", the phone's yaw rotates the mini-map
Copy Phone LinkCopies the LAN phone URL to the clipboard
Show QR CodeDisplays a QR code for the LAN phone URL

Lighting tips

  • Keep the phone screen brightness high — the marker needs clear contrast.
  • Avoid direct glare on the phone screen.
  • If detection is unreliable, reduce ambient light reflections.
  • When the phone is lost, the marker automatically grows to help re-acquisition.

File Structure

server.js Node.js / Express / Socket.io server
public/
index.html Laptop UI (webcam + overlay + map panel)
css/style.css Dark professional theme
js/
app.js Main orchestrator (webcam loop, detection, state)
jsarDetector.js Detects marker ID 0 via jsartoolkit5 — position and rotation from corners
homography.js Perspective-transform math (DLT algorithm)
vendor/
artoolkit.min.js jsartoolkit5 self-contained bundle
examples/
mapExample.js Leaflet map — phone position to geographic coordinate
phone/
index.html Phone PWA (auto-connects, no room code needed)
manifest.json
sw.js Service worker (offline cache)
css/phone.css
js/
phoneApp.js Auto-connection and example lifecycle
drawMarker.js Renders ArUco marker patterns onto canvas
examples/
mapPhone.js Leaflet mini-map tracking the phone's geographic position

Technical Notes

  • Detection uses jsartoolkit5 with a single 3x3 barcode marker (ID 0) centred at the top of the phone screen. The marker's four corners provide position and rotation.
  • The server detects the machine's LAN IP at startup and exposes it via /api/config so the laptop app can generate a correct phone URL for the QR code and copy-link button.
  • All devices share a single server session — no room codes or pairing required.
  • The phone mini-map renders at three zoom levels deeper than the laptop map and freezes during active drawing to keep strokes clean.
  • Drawn paths are placed in a dedicated Leaflet pane (drawPane) at z-index 650 with overflow: visible to prevent clipping at tile boundaries.

About

Project using a camera

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

41 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

ReverseProjection

reverse-projection.mp4

A live augmented-reality application that turns a webcam into a position sensor: hold your phone in front of the camera and an interactive map appears, perfectly aligned with your physical location. Touch the phone screen to draw annotations that appear on both the phone and the large display simultaneously.

Anyone who opens the phone page connects automatically.


How It Works

  1. The phone displays a single ArUco fiducial marker (ID 0) centred at the top of the screen so the webcam can detect and track it.
  2. The laptop accesses the webcam, detects the marker using jsartoolkit5, and derives the phone's position and rotation from the marker's four corner vertices.
  3. Both devices communicate over WebSockets (Socket.io) through a local Node.js server running on your network.
  4. The laptop map shows a bounding box representing the area currently visible on the phone's mini-map.
  5. Touching the phone screen sends lat/lng coordinates back to the laptop and draws a stroke on both maps simultaneously.

Mapping Model

The project uses three different coordinate spaces. Keeping them separate is the key to making the overlays line up.

  1. Camera space The laptop webcam sees the physical phone. Marker detection gives a phone centre (nx, ny) in normalised camera coordinates and a phone rotation θ.

  2. Phone viewport space The phone reports the size of the actually visible interactive area, not the whole page. This is the rectangle below the marker and above the bottom status bar. That reported width and height are the only dimensions that may be used to infer the visible viewport.

  3. Content space Each example maps the visible phone viewport into its own content space.

    • Map mode: geographic map bounds.
    • Screen mode: source video pixels from the captured desktop stream.
    • Whiteboard mode: whiteboard world coordinates.

The important rule is:

  • The blue overlay on the laptop must represent the phone's visible content viewport in content space.
  • It must not represent the physical phone body in camera space.

Screen mode mapping

Screen mode uses the exact same viewport transform on both devices.

  1. The phone renders the incoming desktop stream into its visible area using a cover transform:
    • base scale = max(phoneW / sourceW, phoneH / sourceH)
    • total scale = base scale * zoom
  2. The phone then translates the scaled source so that the tracked point (nx, ny) becomes the centre of the visible phone viewport.
  3. That translation is clamped so the source never exposes empty edges.
  4. The laptop overlay must compute the inverse of that same transform and draw the resulting source rectangle on top of the displayed preview.
  5. If rotation is enabled, the same viewport rectangle is rotated by the tracked phone angle. If rotation is disabled, both the phone and the overlay stay axis-aligned.

This means the laptop overlay for Screen mode must be derived from:

  • source video size
  • reported visible phone viewport size
  • zoom
  • tracked (nx, ny)
  • tracked rotation when enabled

It must not be derived from the marker's apparent size in the webcam image.

Map mode mapping

Map mode uses the reported visible phone viewport size to convert the tracked phone centre into a geographic rectangle.

  1. The laptop maps (nx, ny) into the current map bounds.
  2. The reported phone viewport width and height are converted to fractions of the camera frame using the detected physical scale.
  3. Those fractions are then converted into fractions of the current Leaflet map bounds.
  4. If rotation is enabled, the rectangle is rotated. Otherwise it stays upright.

The same visible viewport dimensions must be used consistently on the phone, in the laptop state, and in the laptop overlay.


Getting Started

Step 1 — Install Node.js

Node.js is the JavaScript runtime the server needs. Install it once; it stays on your machine.

macOS

The easiest way is Homebrew, a package manager for macOS. If you have never used it:

  1. Open Terminal (press Command+Space, type "Terminal", press Enter).

  2. Paste the following command and press Enter. It will ask for your Mac password:

    /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
    
  3. After Homebrew finishes, install Node.js:

    brew install node
    
  4. Verify the installation:

    node --version
    

    You should see something like v20.x.x.

Windows

  1. Open a web browser and go to https://nodejs.org.

  2. Download the LTS installer (the button labelled "LTS" — Long Term Support).

  3. Run the downloaded .msi file and follow the installer steps. Leave all options at their defaults.

  4. When it finishes, open Command Prompt (press Windows+R, type cmd, press Enter) and verify:

    node --version
    

Linux (Ubuntu / Debian)

sudo apt update
sudo apt install nodejs npm
node --version

For other distributions, see https://nodejs.org/en/download/package-manager.


Step 2 — Download the project

If you have Git installed:

git clone https://github.com/lemio/ReverseProjection.git
cd ReverseProjection

If you do not have Git, download the ZIP from GitHub (click the green "Code" button, then "Download ZIP"), unzip it, and open a terminal inside the folder.


Step 3 — Install dependencies and start the server

In your terminal, inside the project folder:

npm install
npm start

You should see:

ReverseProjection server running at http://localhost:3000

Leave this terminal window open — the server must keep running.


Step 4 — Open the laptop app

Open a browser (Chrome or Edge recommended for best webcam support) and go to:

http://localhost:3000

Allow camera access when the browser asks. The webcam feed will appear.


Step 5 — Connect a phone

The phone must be on the same Wi-Fi network as the laptop.

  1. On the laptop, click Copy Phone Link in the toolbar. This copies the URL to your clipboard.
  2. Open that URL on the phone's browser, or click Show QR Code and scan it with the phone camera.
  3. The phone will connect automatically and display an interactive map.

To find your laptop's local IP address (for typing the URL manually):

  • macOS / Linux: run ifconfig | grep "inet " in Terminal — look for a number like 192.168.x.x
  • Windows: run ipconfig in Command Prompt — look for "IPv4 Address"

Then open http://192.168.x.x:3000/phone on the phone.


Using the Application

  • Hold the phone face-up in front of the webcam. A single black-and-white marker centred at the top of the screen allows the webcam to track position and rotation.
  • The laptop map shows a blue rectangle representing the area currently visible on the phone.
  • Touch and drag on the phone to draw annotations. They appear on both screens simultaneously.
  • Use the toolbar buttons to invert controls or enable map rotation.

Toolbar controls

ButtonDescription
MapActivates the map example (the only built-in example)
InvertFlips the phone's position mapping so moving up moves the map north
No RotationWhen toggled to "Rotating", the phone's yaw rotates the mini-map
Copy Phone LinkCopies the LAN phone URL to the clipboard
Show QR CodeDisplays a QR code for the LAN phone URL

Lighting tips

  • Keep the phone screen brightness high — the marker needs clear contrast.
  • Avoid direct glare on the phone screen.
  • If detection is unreliable, reduce ambient light reflections.
  • When the phone is lost, the marker automatically grows to help re-acquisition.

File Structure

server.js Node.js / Express / Socket.io server
public/
index.html Laptop UI (webcam + overlay + map panel)
css/style.css Dark professional theme
js/
app.js Main orchestrator (webcam loop, detection, state)
jsarDetector.js Detects marker ID 0 via jsartoolkit5 — position and rotation from corners
homography.js Perspective-transform math (DLT algorithm)
vendor/
artoolkit.min.js jsartoolkit5 self-contained bundle
examples/
mapExample.js Leaflet map — phone position to geographic coordinate
phone/
index.html Phone PWA (auto-connects, no room code needed)
manifest.json
sw.js Service worker (offline cache)
css/phone.css
js/
phoneApp.js Auto-connection and example lifecycle
drawMarker.js Renders ArUco marker patterns onto canvas
examples/
mapPhone.js Leaflet mini-map tracking the phone's geographic position

Technical Notes

  • Detection uses jsartoolkit5 with a single 3x3 barcode marker (ID 0) centred at the top of the phone screen. The marker's four corners provide position and rotation.
  • The server detects the machine's LAN IP at startup and exposes it via /api/config so the laptop app can generate a correct phone URL for the QR code and copy-link button.
  • All devices share a single server session — no room codes or pairing required.
  • The phone mini-map renders at three zoom levels deeper than the laptop map and freezes during active drawing to keep strokes clean.
  • Drawn paths are placed in a dedicated Leaflet pane (drawPane) at z-index 650 with overflow: visible to prevent clipping at tile boundaries.

About

Project using a camera

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Latest commit

History

41 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

ReverseProjection

reverse-projection.mp4

A live augmented-reality application that turns a webcam into a position sensor: hold your phone in front of the camera and an interactive map appears, perfectly aligned with your physical location. Touch the phone screen to draw annotations that appear on both the phone and the large display simultaneously.

Anyone who opens the phone page connects automatically.


How It Works

  1. The phone displays a single ArUco fiducial marker (ID 0) centred at the top of the screen so the webcam can detect and track it.
  2. The laptop accesses the webcam, detects the marker using jsartoolkit5, and derives the phone's position and rotation from the marker's four corner vertices.
  3. Both devices communicate over WebSockets (Socket.io) through a local Node.js server running on your network.
  4. The laptop map shows a bounding box representing the area currently visible on the phone's mini-map.
  5. Touching the phone screen sends lat/lng coordinates back to the laptop and draws a stroke on both maps simultaneously.

Mapping Model

The project uses three different coordinate spaces. Keeping them separate is the key to making the overlays line up.

  1. Camera space The laptop webcam sees the physical phone. Marker detection gives a phone centre (nx, ny) in normalised camera coordinates and a phone rotation θ.

  2. Phone viewport space The phone reports the size of the actually visible interactive area, not the whole page. This is the rectangle below the marker and above the bottom status bar. That reported width and height are the only dimensions that may be used to infer the visible viewport.

  3. Content space Each example maps the visible phone viewport into its own content space.

    • Map mode: geographic map bounds.
    • Screen mode: source video pixels from the captured desktop stream.
    • Whiteboard mode: whiteboard world coordinates.

The important rule is:

  • The blue overlay on the laptop must represent the phone's visible content viewport in content space.
  • It must not represent the physical phone body in camera space.

Screen mode mapping

Screen mode uses the exact same viewport transform on both devices.

  1. The phone renders the incoming desktop stream into its visible area using a cover transform:
    • base scale = max(phoneW / sourceW, phoneH / sourceH)
    • total scale = base scale * zoom
  2. The phone then translates the scaled source so that the tracked point (nx, ny) becomes the centre of the visible phone viewport.
  3. That translation is clamped so the source never exposes empty edges.
  4. The laptop overlay must compute the inverse of that same transform and draw the resulting source rectangle on top of the displayed preview.
  5. If rotation is enabled, the same viewport rectangle is rotated by the tracked phone angle. If rotation is disabled, both the phone and the overlay stay axis-aligned.

This means the laptop overlay for Screen mode must be derived from:

  • source video size
  • reported visible phone viewport size
  • zoom
  • tracked (nx, ny)
  • tracked rotation when enabled

It must not be derived from the marker's apparent size in the webcam image.

Map mode mapping

Map mode uses the reported visible phone viewport size to convert the tracked phone centre into a geographic rectangle.

  1. The laptop maps (nx, ny) into the current map bounds.
  2. The reported phone viewport width and height are converted to fractions of the camera frame using the detected physical scale.
  3. Those fractions are then converted into fractions of the current Leaflet map bounds.
  4. If rotation is enabled, the rectangle is rotated. Otherwise it stays upright.

The same visible viewport dimensions must be used consistently on the phone, in the laptop state, and in the laptop overlay.


Getting Started

Step 1 — Install Node.js

Node.js is the JavaScript runtime the server needs. Install it once; it stays on your machine.

macOS

The easiest way is Homebrew, a package manager for macOS. If you have never used it:

  1. Open Terminal (press Command+Space, type "Terminal", press Enter).

  2. Paste the following command and press Enter. It will ask for your Mac password:

    /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
    
  3. After Homebrew finishes, install Node.js:

    brew install node
    
  4. Verify the installation:

    node --version
    

    You should see something like v20.x.x.

Windows

  1. Open a web browser and go to https://nodejs.org.

  2. Download the LTS installer (the button labelled "LTS" — Long Term Support).

  3. Run the downloaded .msi file and follow the installer steps. Leave all options at their defaults.

  4. When it finishes, open Command Prompt (press Windows+R, type cmd, press Enter) and verify:

    node --version
    

Linux (Ubuntu / Debian)

sudo apt update
sudo apt install nodejs npm
node --version

For other distributions, see https://nodejs.org/en/download/package-manager.


Step 2 — Download the project

If you have Git installed:

git clone https://github.com/lemio/ReverseProjection.git
cd ReverseProjection

If you do not have Git, download the ZIP from GitHub (click the green "Code" button, then "Download ZIP"), unzip it, and open a terminal inside the folder.


Step 3 — Install dependencies and start the server

In your terminal, inside the project folder:

npm install
npm start

You should see:

ReverseProjection server running at http://localhost:3000

Leave this terminal window open — the server must keep running.


Step 4 — Open the laptop app

Open a browser (Chrome or Edge recommended for best webcam support) and go to:

http://localhost:3000

Allow camera access when the browser asks. The webcam feed will appear.


Step 5 — Connect a phone

The phone must be on the same Wi-Fi network as the laptop.

  1. On the laptop, click Copy Phone Link in the toolbar. This copies the URL to your clipboard.
  2. Open that URL on the phone's browser, or click Show QR Code and scan it with the phone camera.
  3. The phone will connect automatically and display an interactive map.

To find your laptop's local IP address (for typing the URL manually):

  • macOS / Linux: run ifconfig | grep "inet " in Terminal — look for a number like 192.168.x.x
  • Windows: run ipconfig in Command Prompt — look for "IPv4 Address"

Then open http://192.168.x.x:3000/phone on the phone.


Using the Application

  • Hold the phone face-up in front of the webcam. A single black-and-white marker centred at the top of the screen allows the webcam to track position and rotation.
  • The laptop map shows a blue rectangle representing the area currently visible on the phone.
  • Touch and drag on the phone to draw annotations. They appear on both screens simultaneously.
  • Use the toolbar buttons to invert controls or enable map rotation.

Toolbar controls

ButtonDescription
MapActivates the map example (the only built-in example)
InvertFlips the phone's position mapping so moving up moves the map north
No RotationWhen toggled to "Rotating", the phone's yaw rotates the mini-map
Copy Phone LinkCopies the LAN phone URL to the clipboard
Show QR CodeDisplays a QR code for the LAN phone URL

Lighting tips

  • Keep the phone screen brightness high — the marker needs clear contrast.
  • Avoid direct glare on the phone screen.
  • If detection is unreliable, reduce ambient light reflections.
  • When the phone is lost, the marker automatically grows to help re-acquisition.

File Structure

server.js Node.js / Express / Socket.io server
public/
index.html Laptop UI (webcam + overlay + map panel)
css/style.css Dark professional theme
js/
app.js Main orchestrator (webcam loop, detection, state)
jsarDetector.js Detects marker ID 0 via jsartoolkit5 — position and rotation from corners
homography.js Perspective-transform math (DLT algorithm)
vendor/
artoolkit.min.js jsartoolkit5 self-contained bundle
examples/
mapExample.js Leaflet map — phone position to geographic coordinate
phone/
index.html Phone PWA (auto-connects, no room code needed)
manifest.json
sw.js Service worker (offline cache)
css/phone.css
js/
phoneApp.js Auto-connection and example lifecycle
drawMarker.js Renders ArUco marker patterns onto canvas
examples/
mapPhone.js Leaflet mini-map tracking the phone's geographic position

Technical Notes

  • Detection uses jsartoolkit5 with a single 3x3 barcode marker (ID 0) centred at the top of the phone screen. The marker's four corners provide position and rotation.
  • The server detects the machine's LAN IP at startup and exposes it via /api/config so the laptop app can generate a correct phone URL for the QR code and copy-link button.
  • All devices share a single server session — no room codes or pairing required.
  • The phone mini-map renders at three zoom levels deeper than the laptop map and freezes during active drawing to keep strokes clean.
  • Drawn paths are placed in a dedicated Leaflet pane (drawPane) at z-index 650 with overflow: visible to prevent clipping at tile boundaries.

About

Project using a camera

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages