Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@ Three.js renders:
- scene bounds and box/cylinder/sphere zones, with live presence labels shown below occupied zones and slider-volume fill on round zones;
- a Pleiades-style orbit camera, 1 × 1 m floor grid and axes.

Mouse navigation follows the Pleiades viewer philosophy: left-drag orbits, right-drag pans parallel to the floor, middle-drag/wheel zooms, and the orbit is constrained above the floor plane. The camera view is persisted after navigation and restored on refresh; setup updates do not overwrite a restored/user-controlled view. **Reset camera** explicitly returns the viewer to automatic framing, so a later full setup can frame its new bounds normally. Without a saved/user-controlled view, a successful connection setup initializes the same centered framing used by **Reset camera**. **Simulate data**, **Reset camera**, or a left-button double-click reframes the displayed setup. The sidebar uses two lightweight levels of disclosure: each main section can be folded to a compact status row, and each section can expose a denser **Advanced** subsection for less-used controls. In **Connection**, the server address, Connect/Simulate controls, and live connection log stay primary while Port, Protocol and Point downsample live under Advanced. **Display** keeps the Scene plus common visibility toggles primary, with a lightweight **Reset camera** action above them and Velocity vectors under Advanced. **Live debug data** stays readable as the main content with a lightweight **Clear data** action above the inspector. While the Live debug data section is folded, only its compact summary is refreshed; the detailed debug HTML is not rebuilt until the section is opened again. While interacting with Frame / Objects / Zones / Control disclosures, debug DOM refreshes briefly pause so a live 4 Hz update cannot replace the clicked disclosure between pointer-down and click. The Display panel exposes **All scenes** plus every Scene received in the current World, with independent **Scenes** and **Zones** visibility toggles. On desktop, the translucent panel overlays the 3D view: use the edge arrow to hide/show it, or drag its left edge to resize it. On phone/touch layouts, the same control becomes a small horizontal handle at the viewer/menu boundary; folding the menu expands the viewer to the full viewport while keeping the handle available to reopen it. **Escape** folds the panel. **H** toggles the viewer title/subtitle as a transient display shortcut; unlike the other UI/display preferences, this title visibility is intentionally not persisted. After 3 seconds without mouse movement or keyboard input, the handle fades whether the panel is unfolded or folded; activity brings it back quickly. Folded sidebar/QR content is removed from keyboard navigation while hidden. The camera projection follows the panel width so the orbit target remains centered in the unobscured part of the view.
Mouse navigation follows the Pleiades viewer philosophy: left-drag orbits, right-drag pans parallel to the floor, middle-drag/wheel zooms, and the orbit is constrained above the floor plane. The camera view is persisted after navigation and restored on refresh; setup updates do not overwrite a restored/user-controlled view. **Reset camera** explicitly returns the viewer to automatic framing, so a later full setup can frame its new bounds normally. Without a saved/user-controlled view, a successful connection setup initializes the same centered framing used by **Reset camera**. **Simulate data**, **Reset camera**, or a left-button double-click reframes the displayed setup. The sidebar uses two lightweight levels of disclosure: each main section can be folded to a compact status row, and each section can expose a denser **Advanced** subsection for less-used controls. In **Connection**, the server address, Connect/Simulate controls, and live connection log stay primary while Port, Protocol and Point downsample live under Advanced. **Display** keeps the Scene plus common visibility toggles primary, with **Reset camera** beside the Scene label and Velocity vectors under Advanced. **Live debug data** stays readable as the main content with a lightweight **Clear data** action above the inspector. While the Live debug data section is folded, only its compact summary is refreshed; the detailed debug HTML is not rebuilt until the section is opened again. While interacting with Frame / Objects / Zones / Control disclosures, debug DOM refreshes briefly pause so a live 4 Hz update cannot replace the clicked disclosure between pointer-down and click. The Display panel exposes **All scenes** plus every Scene received in the current World, with independent **Scenes** and **Zones** visibility toggles. On desktop, the translucent panel overlays the 3D view: use the edge arrow to hide/show it, or drag its left edge to resize it. On phone/touch layouts, the same control becomes a small horizontal handle at the viewer/menu boundary; folding the menu expands the viewer to the full viewport while keeping the handle available to reopen it. **Escape** folds the panel. **H** toggles the viewer title/subtitle as a transient display shortcut; unlike the other UI/display preferences, this title visibility is intentionally not persisted. On desktop, after 3 seconds without mouse movement or keyboard input, the handle fades whether the panel is unfolded or folded; activity brings it back quickly. On phone/touch layouts, the handle stays visible so the folded menu can always be reopened. When folded, the sidebar is removed from keyboard navigation; on desktop the hidden QR is inert as well, while on phone/touch layouts the visible QR remains usable. The camera projection follows the panel width so the orbit target remains centered in the unobscured part of the view.

Partial setup updates are merged into the cached hierarchy before rendering, so a Scene update that omits its Zone children does not erase them. Updates that race ahead of the initial full setup are ignored rather than promoted to an incomplete root. When a specific Scene is selected, live setup updates still render from the World root so ancestor transforms remain intact. Zone-event caches are pruned against the latest setup hierarchy so renamed/removed addresses do not accumulate stale visual state. Scene selection and scene-size debug information therefore always use the latest valid merged setup.
Partial setup updates are merged into the cached hierarchy before rendering, so a Scene update that omits its Zone children does not erase them. Updates that race ahead of the initial full setup are ignored rather than promoted to an incomplete root. When a specific Scene is selected, live setup updates still render from the World root so ancestor transforms remain intact. Zone-event caches are pruned against the latest setup hierarchy so renamed/removed addresses do not accumulate stale visual state. A zone stops rendering 50 ms after its live event heartbeat disappears, so disabled zones vanish quickly while their setup definition remains available. Scene selection and scene-size debug information therefore always use the latest valid merged setup.

The live debug inspector is throttled and preserves the user's open/closed section state across refreshes. Cluster debug values include the received OBB quaternion and `lookAt` vector so the rendered direction marker can be checked against the wire data. Using **Clear** preserves the inspector layout and sidebar scroll position while the live stream repopulates, so clearing data does not jump the panel. The JavaScript SDK is included as a **Git submodule** in `vendor/AugmentaClientSDK-JS` and pinned to SDK `1.0.0-beta.1` commit `115b77c1` for reproducible builds. The SDK lockfile is committed, so CI and local reproducible builds use `npm ci`. The pinned SDK includes bulk point-cloud parsing, packet-boundary validation, protocol V2/V3 guards and stale-WebSocket-event protection. The inspector exposes the main fields useful for this example; raw point arrays are rendered in full in Three.js while the text panel is throttled and point-intensity statistics are bounded to a representative sample to stay responsive with large clouds.
The live debug inspector is throttled and preserves the user's open/closed section state across refreshes. Cluster debug values include the received OBB quaternion and `lookAt` vector so the rendered direction marker can be checked against the wire data. Using **Clear data** preserves the inspector layout and sidebar scroll position while the live stream repopulates, so clearing data does not jump the panel. The JavaScript SDK is included as a **Git submodule** in `vendor/AugmentaClientSDK-JS` and pinned to SDK `1.0.0-beta.1` commit `115b77c1` for reproducible builds. The SDK lockfile is committed, so CI and local reproducible builds use `npm ci`. The pinned SDK includes bulk point-cloud parsing, packet-boundary validation, protocol V2/V3 guards and stale-WebSocket-event protection. The inspector exposes the main fields useful for this example; raw point arrays are rendered in full in Three.js while the text panel is throttled and point-intensity statistics are bounded to a representative sample to stay responsive with large clouds.

## Clone and run locally

Expand Down Expand Up @@ -230,4 +230,4 @@ Augmenta-authored example code is distributed under the Augmenta SDK license in

## Live viewer behavior

If the server-side WebSocket connection disappears unexpectedly, live clusters, point clouds, velocity vectors and debug data are cleared after 500 ms while the application keeps retrying. The static scene/zones remain visible. Clicking **Connected** to disconnect manually stops retries and immediately removes only live tracking/debug data; the scene/zones remain visible. The **Clear** button only clears the debug inspector.
If the server-side WebSocket connection disappears unexpectedly, live clusters, point clouds, velocity vectors and debug data are cleared after 500 ms while the application keeps retrying. The static scene/zones remain visible. Clicking **Connected** to disconnect manually stops retries and immediately removes only live tracking/debug data; the scene/zones remain visible. The **Clear data** button only clears the debug inspector.
9 changes: 4 additions & 5 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ <h1>Augmenta × Three.js</h1>
></div>

<aside id="sidebar">
<details id="connection-section" class="panel panel-section connection-panel" open>
<details id="connection-section" class="panel-section" open>
<summary class="section-summary">
<span class="section-chevron" aria-hidden="true"></span>
<span class="panel-heading-copy">
Expand All @@ -85,7 +85,7 @@ <h1>Augmenta × Three.js</h1>
</summary>

<div class="panel-content">
<div class="server-address-field">
<div>
<label for="server-address">Augmenta Server Address</label>
<input id="server-address" type="text" value="127.0.0.1" spellcheck="false" autocomplete="off" placeholder="192.168.1.42 or augmenta-WA12031.local">
<p class="field-subtitle">IP address or hostname</p>
Expand Down Expand Up @@ -127,7 +127,7 @@ <h1>Augmenta × Three.js</h1>
</div>
</details>

<details id="display-section" class="panel panel-section display-panel" open>
<details id="display-section" class="panel-section" open>
<summary class="section-summary">
<span class="section-chevron" aria-hidden="true"></span>
<span class="panel-heading-copy">
Expand Down Expand Up @@ -169,7 +169,7 @@ <h1>Augmenta × Three.js</h1>
</div>
</details>

<details id="debug-section" class="panel panel-section debug-panel" open>
<details id="debug-section" class="panel-section debug-panel" open>
<summary class="section-summary">
<span class="section-chevron" aria-hidden="true"></span>
<span class="panel-heading-copy">
Expand Down Expand Up @@ -211,7 +211,6 @@ <h1>Augmenta × Three.js</h1>
</aside>
</main>

<script type="module" src="./src/qr.js"></script>
<script type="module" src="./src/main.js"></script>
</body>
</html>
47 changes: 30 additions & 17 deletions scripts/assemble-site.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ function requirePath(path, label) {
}
}

function replaceRequired(source, search, replacement) {
if (!source.includes(search)) {
throw new Error(`Missing expected build token: ${search}`);
}
return source.replaceAll(search, replacement);
}

requirePath(join(root, 'vendor', 'AugmentaClientSDK-JS', 'dist', 'esm'), 'built Augmenta SDK');
requirePath(join(root, 'vendor', 'qrcode-generator', 'qrcode.js'), 'vendored QR generator');
requirePath(join(threeRoot, 'build', 'three.module.js'), 'Three.js runtime');
Expand Down Expand Up @@ -71,23 +78,29 @@ cpSync(

const indexPath = join(out, 'index.html');
let html = readFileSync(indexPath, 'utf8');
html = html
.replace(' <link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>\n', '')
.replace(
'https://cdn.jsdelivr.net/npm/three@0.186.1/build/three.module.js',
`./vendor/three/${rev}/build/three.module.js`
)
.replace(
'https://cdn.jsdelivr.net/npm/three@0.186.1/examples/jsm/',
`./vendor/three/${rev}/examples/jsm/`
)
.replace(
'./vendor/AugmentaClientSDK-JS/dist/esm/index.js',
`./vendor/AugmentaClientSDK-JS/${rev}/dist/esm/index.js`
)
.replaceAll('./src/styles.css', `./src/styles.css?v=${rev}`)
.replaceAll('./src/qr.js', `./src/qr.js?v=${rev}`)
.replaceAll('./src/main.js', `./src/main.js?v=${rev}`);
html = replaceRequired(
html,
' <link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>\n',
''
);
html = replaceRequired(
html,
'https://cdn.jsdelivr.net/npm/three@0.186.1/build/three.module.js',
`./vendor/three/${rev}/build/three.module.js`
);
html = replaceRequired(
html,
'https://cdn.jsdelivr.net/npm/three@0.186.1/examples/jsm/',
`./vendor/three/${rev}/examples/jsm/`
);
html = replaceRequired(
html,
'./vendor/AugmentaClientSDK-JS/dist/esm/index.js',
`./vendor/AugmentaClientSDK-JS/${rev}/dist/esm/index.js`
);
html = replaceRequired(html, './src/styles.css', `./src/styles.css?v=${rev}`);
html = replaceRequired(html, './src/qr.js', `./src/qr.js?v=${rev}`);
html = replaceRequired(html, './src/main.js', `./src/main.js?v=${rev}`);

if (html.includes('cdn.jsdelivr.net')) {
throw new Error('Built Pages index still contains a runtime CDN dependency.');
Expand Down
14 changes: 9 additions & 5 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,13 @@ import { refreshConnectionQr } from './qr.js';
const DISCONNECT_CLEANUP_DELAY_MS = 500;
const CAMERA_PREFERENCE_SAVE_DELAY_MS = 250;
const SIDEBAR_HANDLE_IDLE_DELAY_MS = 3000;
const FPS_WINDOW_MS = 1000;
const DEMO_FRAME_INTERVAL_MS = 33;
const SIDEBAR_MIN_WIDTH = 320;
const SIDEBAR_MAX_WIDTH = 450;
const SIDEBAR_VIEWPORT_MARGIN = 160;
const SIDEBAR_RESIZE_STEP_PX = 16;
const SIDEBAR_RESIZE_LARGE_STEP_PX = 40;
const MOBILE_MEDIA_QUERY = '(max-width: 900px), (pointer: coarse) and (max-width: 1100px)';
const SETTINGS_STORAGE_KEY = 'augmenta-threejs-settings:v1';

Expand Down Expand Up @@ -285,7 +289,7 @@ function trackFrame(frame) {
lastFrame = frame;
const now = performance.now();
frameTimes.push(now);
frameTimes = frameTimes.filter((time) => time >= now - 1000);
frameTimes = frameTimes.filter((time) => time >= now - FPS_WINDOW_MS);
viewer.renderFrame(frame);
renderDebug();
}
Expand Down Expand Up @@ -487,7 +491,7 @@ function startSimulation() {
const start = performance.now();
const tick = () => trackFrame(makeDemoFrame((performance.now() - start) / 1000));
tick();
demoTimer = window.setInterval(tick, 33);
demoTimer = window.setInterval(tick, DEMO_FRAME_INTERVAL_MS);
updateSimulationButton();
}

Expand Down Expand Up @@ -619,12 +623,12 @@ function setSidebarWidth(width) {
}

function resizeSidebar(event) {
if (window.matchMedia(MOBILE_MEDIA_QUERY).matches) return;
if (isMobileLayout()) return;
setSidebarWidth(window.innerWidth - event.clientX);
}

ui.sidebarResizer.addEventListener('pointerdown', (event) => {
if (window.matchMedia(MOBILE_MEDIA_QUERY).matches || ui.app.classList.contains('sidebar-hidden')) return;
if (isMobileLayout() || ui.app.classList.contains('sidebar-hidden')) return;
event.preventDefault();
ui.sidebarResizer.setPointerCapture(event.pointerId);
ui.app.classList.add('sidebar-resizing');
Expand All @@ -647,7 +651,7 @@ ui.sidebarResizer.addEventListener('pointercancel', stopSidebarResize);
ui.sidebarResizer.addEventListener('keydown', (event) => {
if (!['ArrowLeft', 'ArrowRight'].includes(event.key)) return;
event.preventDefault();
const step = event.shiftKey ? 40 : 16;
const step = event.shiftKey ? SIDEBAR_RESIZE_LARGE_STEP_PX : SIDEBAR_RESIZE_STEP_PX;
const current = ui.sidebar.getBoundingClientRect().width;
setSidebarWidth(current + (event.key === 'ArrowLeft' ? step : -step));
savePreferences();
Expand Down
4 changes: 1 addition & 3 deletions src/qr.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ const ui = {

let qrCopyFeedbackTimer;
let qrHovering = false;
let initialized = false;

function getConnectionSettings() {
return {
Expand Down Expand Up @@ -118,8 +117,7 @@ async function handleConnectionQrClick(event) {
}

function initializeConnectionQr() {
if (initialized || !ui.connectionQr) return;
initialized = true;
if (!ui.connectionQr) return;

ui.connectionQr.addEventListener('click', handleConnectionQrClick);
ui.connectionQr.addEventListener('mouseenter', () => {
Expand Down
7 changes: 0 additions & 7 deletions src/setup-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,7 @@ export function createSetupStore() {
return root;
}

function clear() {
root = undefined;
scenes = [];
byAddress = new Map();
}

return {
clear,
setRoot,
applyUpdate,
getRoot: () => root,
Expand Down
Loading
Loading