mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 02:14:01 +00:00
SCENES > VCAM pairs a phone through a QR code and turns it into a handheld camera, after Unreal's Live Link VCAM and Blender's VirtuCamera: - Android Chrome tracks 6DoF through WebXR immersive-ar; iPhone Safari, which has no WebXR AR, tracks rotation through DeviceOrientation, and thumb sticks dolly, truck, crane and pan. - A motion scale (1:1 walk to 1:10000 orbit), smoothing, height and horizon locks, hold/clutch and recenter, none of which make the camera jump. Focal length and scale-aware depth of field with tap-to-focus. - The phone shows the app's render as a monitor (newest-frame JPEG pull). - REC records a take as a version-7 track shot in the selected scene. - The rig claims the camera through the navigation seam (Cockpit refuses it) and holds on any other navigation, scene run or globe input. Pairing never exposes the app: a loopback-only session request starts a separate HTTPS listener (VCAM_PORT, default 4174) that serves only the static phone page and a token-paired relay, and stops after the last session. Its certificate comes from a local CA built with node:crypto, which an iPhone can trust once; without it the phone falls back from WebSocket to HTTPS requests, which iOS allows past a certificate warning. scripts/qa-vcam.mjs drives it end to end: the real phone page over the HTTPS fallback, a scripted 6DoF phone, a recorded and replayed take, depth of field and camera handoff. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_015H1iLusTzWpVKitGic2AW8
155 lines
6.1 KiB
HTML
155 lines
6.1 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta
|
||
name="viewport"
|
||
content="width=device-width, initial-scale=1, viewport-fit=cover, user-scalable=no"
|
||
/>
|
||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||
<meta name="theme-color" content="#0a0a0f" />
|
||
<title>GEV Virtual Camera</title>
|
||
<link rel="icon" href="icon.svg" />
|
||
<link rel="stylesheet" href="phone.css" />
|
||
</head>
|
||
<body>
|
||
<main id="app">
|
||
<section id="pair" class="screen">
|
||
<img class="logo" src="icon.svg" alt="" />
|
||
<h1>VIRTUAL CAMERA</h1>
|
||
<p class="lede">
|
||
Enter the code shown in God's Eye View under SCENES › VCAM.
|
||
</p>
|
||
<form id="pair-form" autocomplete="off">
|
||
<input
|
||
id="code"
|
||
inputmode="text"
|
||
autocapitalize="characters"
|
||
autocorrect="off"
|
||
spellcheck="false"
|
||
maxlength="6"
|
||
placeholder="CODE"
|
||
aria-label="Pairing code"
|
||
/>
|
||
<button id="join" type="submit" class="primary">PAIR</button>
|
||
</form>
|
||
<p id="pair-status" class="status" role="status"></p>
|
||
<details class="help">
|
||
<summary>Connection drops on iPhone?</summary>
|
||
<p>
|
||
Safari can load this page past a certificate warning but may still
|
||
refuse its live connection, so the camera falls back to slower
|
||
requests. To fix that once:
|
||
</p>
|
||
<ol>
|
||
<li>
|
||
<a id="ca-link" href="/ca.crt">Download the local certificate</a>
|
||
and allow the profile.
|
||
</li>
|
||
<li>Settings › General › VPN & Device Management: install it.</li>
|
||
<li>
|
||
Settings › General › About › Certificate Trust Settings: turn on
|
||
full trust for "God's Eye View Local VCam CA".
|
||
</li>
|
||
</ol>
|
||
<p>The certificate only lets your phone trust this computer.</p>
|
||
</details>
|
||
</section>
|
||
|
||
<section id="start" class="screen" hidden>
|
||
<img class="logo" src="icon.svg" alt="" />
|
||
<h1>PAIRED</h1>
|
||
<p id="support" class="lede"></p>
|
||
<button id="start-xr" class="primary" hidden>START AR CAMERA</button>
|
||
<button id="start-gyro" class="primary">START GYRO CAMERA</button>
|
||
<p class="hint">
|
||
AR tracks position and rotation (6DoF). Gyro tracks rotation only;
|
||
move with the on-screen sticks.
|
||
</p>
|
||
<p id="start-status" class="status" role="status"></p>
|
||
</section>
|
||
|
||
<section id="camera" class="screen camera" hidden>
|
||
<img id="monitor" alt="" draggable="false" />
|
||
<div id="focus-ring" hidden></div>
|
||
<div class="topbar">
|
||
<span id="link-dot" class="dot" title="Connection"></span>
|
||
<span id="mode-readout" class="chip">—</span>
|
||
<span id="rec-readout" class="chip rec" hidden>● 00:00</span>
|
||
<span class="spacer"></span>
|
||
<span id="scale-readout" class="chip">1:1</span>
|
||
<span id="focal-readout" class="chip">31mm</span>
|
||
</div>
|
||
<p id="camera-status" class="toast" role="status" hidden></p>
|
||
|
||
<div class="stick left" id="stick-left" aria-label="Move">
|
||
<div class="knob"></div>
|
||
<span>MOVE</span>
|
||
</div>
|
||
<div class="stick right" id="stick-right" aria-label="Crane and pan">
|
||
<div class="knob"></div>
|
||
<span>CRANE · PAN</span>
|
||
</div>
|
||
|
||
<div class="bottombar">
|
||
<button id="hold-btn" class="tool" aria-pressed="false">HOLD</button>
|
||
<button id="recenter-btn" class="tool">RECENTER</button>
|
||
<button id="record-btn" class="record" aria-pressed="false">
|
||
<span class="record-dot"></span><span class="sr">RECORD</span>
|
||
</button>
|
||
<button id="scale-btn" class="tool" aria-expanded="false">
|
||
SCALE
|
||
</button>
|
||
<button id="lens-btn" class="tool" aria-expanded="false">LENS</button>
|
||
</div>
|
||
|
||
<div id="scale-drawer" class="drawer" hidden>
|
||
<div class="presets" id="scale-presets">
|
||
<button data-scale="1">1:1<small>WALK</small></button>
|
||
<button data-scale="10">1:10<small>CRANE</small></button>
|
||
<button data-scale="100">1:100<small>DRONE</small></button>
|
||
<button data-scale="1000">1:1K<small>FLYOVER</small></button>
|
||
<button data-scale="10000">1:10K<small>ORBIT</small></button>
|
||
</div>
|
||
<label
|
||
>MOTION SCALE <output id="scale-value">1:1</output>
|
||
<input id="scale-range" type="range" min="0" max="5" step="0.01" value="0"
|
||
/></label>
|
||
<label
|
||
>SMOOTHING <output id="smooth-value">30%</output>
|
||
<input id="smooth-range" type="range" min="0" max="100" step="1" value="30"
|
||
/></label>
|
||
<div class="toggles">
|
||
<label><input id="height-lock" type="checkbox" /> HEIGHT LOCK</label>
|
||
<label><input id="horizon-lock" type="checkbox" /> HORIZON LOCK</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="lens-drawer" class="drawer" hidden>
|
||
<label
|
||
>FOCAL LENGTH <output id="focal-value">31mm</output>
|
||
<input id="focal-range" type="range" min="0" max="1" step="0.001" value="0.3"
|
||
/></label>
|
||
<div class="presets" id="focal-presets">
|
||
<button data-focal="14">14</button>
|
||
<button data-focal="24">24</button>
|
||
<button data-focal="35">35</button>
|
||
<button data-focal="50">50</button>
|
||
<button data-focal="85">85</button>
|
||
<button data-focal="200">200</button>
|
||
</div>
|
||
<div class="toggles">
|
||
<label><input id="dof-toggle" type="checkbox" /> DEPTH OF FIELD</label>
|
||
</div>
|
||
<label
|
||
>APERTURE <output id="fstop-value">f/2.8</output>
|
||
<input id="fstop-range" type="range" min="0" max="1" step="0.001" value="0.35"
|
||
/></label>
|
||
<p class="hint">Tap the picture to focus there.</p>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
<script type="module" src="phone.js"></script>
|
||
</body>
|
||
</html>
|