fix: load scene over HTTP so tracking no longer depends on the WebSocket; HUD shows scene state and detected-vs-usable crests
This commit is contained in:
+36
-9
@@ -52,8 +52,10 @@ let detector, poseEst, fuser, cvSolver;
|
|||||||
let gradients = {}, modelManifest = null, characters = { defaults: {}, byId: {} };
|
let gradients = {}, modelManifest = null, characters = { defaults: {}, byId: {} };
|
||||||
let ghostHeight = 4; // cm, from scene.ghostHeightCm
|
let ghostHeight = 4; // cm, from scene.ghostHeightCm
|
||||||
const activeGhosts = new Map(); // uid -> { rec, group }
|
const activeGhosts = new Map(); // uid -> { rec, group }
|
||||||
let tracking = { markers: 0, lastSeen: 0 };
|
let tracking = { markers: 0, raw: 0, rawIds: [], lastSeen: 0 };
|
||||||
let netRef = null;
|
let netRef = null;
|
||||||
|
let sceneLoaded = false;
|
||||||
|
let anchorCount = 0;
|
||||||
const rxLog = []; // last few WS message types, newest last
|
const rxLog = []; // last few WS message types, newest last
|
||||||
function rxTrace(t) { rxLog.push(t); if (rxLog.length > 6) rxLog.shift(); }
|
function rxTrace(t) { rxLog.push(t); if (rxLog.length > 6) rxLog.shift(); }
|
||||||
const clockOffsetSamples = [];
|
const clockOffsetSamples = [];
|
||||||
@@ -71,7 +73,7 @@ let dbgPose = '';
|
|||||||
function dbgStatus() {
|
function dbgStatus() {
|
||||||
const ws = netRef && netRef.ws ? ['conn','open','closing','closed'][netRef.ws.readyState] : 'none';
|
const ws = netRef && netRef.ws ? ['conn','open','closing','closed'][netRef.ws.readyState] : 'none';
|
||||||
return `ws ${ws} rx ${rxLog.join(',') || '-'}\n` +
|
return `ws ${ws} rx ${rxLog.join(',') || '-'}\n` +
|
||||||
`ghost recs ${activeGhosts.size}\n`;
|
`scene ${sceneLoaded ? anchorCount + ' anchors' : 'NOT LOADED'} ghost recs ${activeGhosts.size}\n`;
|
||||||
}
|
}
|
||||||
function paintDbg() { if (dbgEl) dbgEl.textContent = dbgStatus() + dbgPose + dbgAim; }
|
function paintDbg() { if (dbgEl) dbgEl.textContent = dbgStatus() + dbgPose + dbgAim; }
|
||||||
|
|
||||||
@@ -123,7 +125,7 @@ function updateDbg(fusedQuat, markerCount, extra) {
|
|||||||
dbgPose =
|
dbgPose =
|
||||||
`eng ${engine} ${freezeGhosts ? 'FROZEN' : 'moving'}\n` +
|
`eng ${engine} ${freezeGhosts ? 'FROZEN' : 'moving'}\n` +
|
||||||
`${modeLine}\n` +
|
`${modeLine}\n` +
|
||||||
`markers ${markerCount}${extra}\n${rawLine}\nview ${v3(view)}\nup ${v3(up)}\n` +
|
`markers ${markerCount}/${tracking.raw} seen [${tracking.rawIds.join(',')}]${extra}\n${rawLine}\nview ${v3(view)}\nup ${v3(up)}\n` +
|
||||||
`pos ${v3(camera3.position)}`;
|
`pos ${v3(camera3.position)}`;
|
||||||
paintDbg();
|
paintDbg();
|
||||||
}
|
}
|
||||||
@@ -222,6 +224,13 @@ async function start() {
|
|||||||
cvSolver = new CvBoardSolver();
|
cvSolver = new CvBoardSolver();
|
||||||
ensureOpenCV();
|
ensureOpenCV();
|
||||||
|
|
||||||
|
// scene over HTTP so tracking works even if the WebSocket is down
|
||||||
|
try {
|
||||||
|
applyScene(await (await fetch('/api/scene')).json());
|
||||||
|
} catch (e) {
|
||||||
|
reportError({ kind: 'scene', msg: `scene fetch failed: ${(e && e.message) || e}` });
|
||||||
|
}
|
||||||
|
|
||||||
// dev-mode pose readout + tap-to-cycle modes
|
// dev-mode pose readout + tap-to-cycle modes
|
||||||
if (info.mode === 'dev') {
|
if (info.mode === 'dev') {
|
||||||
freezeGhosts = true; // start static so tracking jitter is isolated from ghost float
|
freezeGhosts = true; // start static so tracking jitter is isolated from ghost float
|
||||||
@@ -266,11 +275,7 @@ async function start() {
|
|||||||
const net = new ExhibitNet();
|
const net = new ExhibitNet();
|
||||||
netRef = net;
|
netRef = net;
|
||||||
const on = (t, fn) => net.on(t, (m) => { rxTrace(t); return fn(m); });
|
const on = (t, fn) => net.on(t, (m) => { rxTrace(t); return fn(m); });
|
||||||
on('scene', (m) => {
|
on('scene', (m) => applyScene(m.scene));
|
||||||
fuser.setScene(m.scene); cvSolver.setScene(m.scene); buildOcclusion(m.scene);
|
|
||||||
ghostHeight = m.scene.ghostHeightCm || 4;
|
|
||||||
for (const e of activeGhosts.values()) e.group.userData.setHeight(ghostHeight);
|
|
||||||
});
|
|
||||||
on('characters', async (m) => {
|
on('characters', async (m) => {
|
||||||
characters = m.characters || characters;
|
characters = m.characters || characters;
|
||||||
const recs = [...activeGhosts.values()].map(e => e.rec);
|
const recs = [...activeGhosts.values()].map(e => e.rec);
|
||||||
@@ -301,6 +306,22 @@ function onResize() {
|
|||||||
camera3.updateProjectionMatrix();
|
camera3.updateProjectionMatrix();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Scene application. Tracking must NOT depend on the WebSocket: detection filters
|
||||||
|
* against the scene's marker IDs, so if the socket is slow or flapping the crest
|
||||||
|
* is detected and then silently discarded ("Point at a Newbury Crest" forever).
|
||||||
|
* We therefore load the scene over plain HTTP at start; the WS message just
|
||||||
|
* refreshes it when an operator edits the layout. */
|
||||||
|
function applyScene(scene) {
|
||||||
|
if (!scene) return;
|
||||||
|
sceneLoaded = true;
|
||||||
|
anchorCount = (scene.anchors || []).filter(a => a.enabled !== false).length;
|
||||||
|
fuser.setScene(scene);
|
||||||
|
cvSolver.setScene(scene);
|
||||||
|
buildOcclusion(scene);
|
||||||
|
ghostHeight = scene.ghostHeightCm || 4;
|
||||||
|
for (const e of activeGhosts.values()) e.group.userData.setHeight(ghostHeight);
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- occlusion: invisible depth-only building volumes ----------
|
// ---------- occlusion: invisible depth-only building volumes ----------
|
||||||
const occlusionGroup = new THREE.Group();
|
const occlusionGroup = new THREE.Group();
|
||||||
function buildOcclusion(scene) {
|
function buildOcclusion(scene) {
|
||||||
@@ -374,6 +395,8 @@ function loop(t) {
|
|||||||
|
|
||||||
const markers = detectMarkers(detector, img, fuser.knownIds());
|
const markers = detectMarkers(detector, img, fuser.knownIds());
|
||||||
tracking.markers = markers.length;
|
tracking.markers = markers.length;
|
||||||
|
tracking.raw = markers.rawCount || 0;
|
||||||
|
tracking.rawIds = markers.rawIds || [];
|
||||||
if (dbgEl) updateAim(markers, W, H);
|
if (dbgEl) updateAim(markers, W, H);
|
||||||
if (markers.length) {
|
if (markers.length) {
|
||||||
tracking.lastSeen = performance.now();
|
tracking.lastSeen = performance.now();
|
||||||
@@ -421,7 +444,11 @@ function loop(t) {
|
|||||||
if ((frameCount++ & 15) === 0) {
|
if ((frameCount++ & 15) === 0) {
|
||||||
paintDbg();
|
paintDbg();
|
||||||
const stale = performance.now() - tracking.lastSeen > 1500;
|
const stale = performance.now() - tracking.lastSeen > 1500;
|
||||||
$('#trk').textContent = stale ? 'Point at a Newbury Crest' : `Tracking ${tracking.markers} crest${tracking.markers === 1 ? '' : 's'}`;
|
$('#trk').textContent = !stale
|
||||||
|
? `Tracking ${tracking.markers} crest${tracking.markers === 1 ? '' : 's'}`
|
||||||
|
: (tracking.raw > 0
|
||||||
|
? `Crest ${tracking.rawIds.join(',')} not in this layout` // detected, but no anchor for it
|
||||||
|
: 'Point at a Newbury Crest');
|
||||||
$('#trk').classList.toggle('warn', stale || tracking.markers < 2);
|
$('#trk').classList.toggle('warn', stale || tracking.markers < 2);
|
||||||
$('#cnt').textContent = `${activeGhosts.size} ghost${activeGhosts.size === 1 ? '' : 's'} nearby`;
|
$('#cnt').textContent = `${activeGhosts.size} ghost${activeGhosts.size === 1 ? '' : 's'} nearby`;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user