From 2d492baa7d4b0febddd6931d9ca0869ef0b93fb6 Mon Sep 17 00:00:00 2001 From: jessikitty Date: Wed, 8 Jul 2026 12:17:21 +1000 Subject: [PATCH] Calibration updates --- public/admin.html | 2 + public/calibrate.html | 217 ++++++++++++++++++++++++++++++++++++ public/js/admin.js | 10 ++ public/js/exhibit.js | 29 +++-- public/placement-plan.html | 222 +++++++++++++++++++++++++++++++++++++ 5 files changed, 473 insertions(+), 7 deletions(-) create mode 100644 public/calibrate.html create mode 100644 public/placement-plan.html diff --git a/public/admin.html b/public/admin.html index fd822cd..c6256e8 100644 --- a/public/admin.html +++ b/public/admin.html @@ -126,6 +126,8 @@ + + diff --git a/public/calibrate.html b/public/calibrate.html new file mode 100644 index 0000000..455bcaa --- /dev/null +++ b/public/calibrate.html @@ -0,0 +1,217 @@ + + + + + +Newbury Exhibit — calibrate + + + + + + + + + + +
+

Crest calibration

+

Measures the marker pose so ghost scale and placement are accurate. You'll need a tape measure.

+ +
+
+ + + + + + + + + diff --git a/public/js/admin.js b/public/js/admin.js index 9211042..6168139 100644 --- a/public/js/admin.js +++ b/public/js/admin.js @@ -464,6 +464,7 @@ function anchorEditor(a) { const box = document.createElement('div'); box.style.cssText = 'padding:4px 2px 12px'; box.appendChild(textRow('Label', a.label, (v) => { a.label = v; markDirty(); renderAnchorsSoft(); })); box.appendChild(selectRow('Marker', a.fiducialType || 'aruco', ['aruco', 'apriltag'], (v) => { a.fiducialType = v; markDirty(); })); + box.appendChild(selectRow('Mount', a.mount || 'flat', ['flat', 'wall'], (v) => { a.mount = v; markDirty(); })); box.appendChild(numRow('Marker id', a.fiducialId, (v) => { a.fiducialId = v | 0; markDirty(); renderAnchorsSoft(); }, { step: 1, min: 0 })); box.appendChild(numRow('Size (mm)', a.fiducialSizeMm, (v) => { a.fiducialSizeMm = v; rebuildAnchors(); markDirty(); }, { step: 1 })); box.appendChild(xyzRow('Pos (mm)', a.position, ['x', 'y', 'z'], () => { rebuildAnchors(); markDirty(); })); @@ -727,6 +728,15 @@ if (printAnchorsBtn) printAnchorsBtn.addEventListener('click', () => { window.open('anchors-print.html', '_blank'); }); +const planBtn = document.getElementById('planBtn'); +if (planBtn) planBtn.addEventListener('click', () => { + if (dirty) toast('Tip: save first so the plan matches your latest layout', ''); + window.open('placement-plan.html', '_blank'); +}); + +const calibrateBtn = document.getElementById('calibrateBtn'); +if (calibrateBtn) calibrateBtn.addEventListener('click', () => window.open('calibrate.html', '_blank')); + window.addEventListener('beforeunload', (e) => { if (dirty) { e.preventDefault(); e.returnValue = ''; } }); // =========================================================================== diff --git a/public/js/exhibit.js b/public/js/exhibit.js index 72e0f96..a4fc992 100644 --- a/public/js/exhibit.js +++ b/public/js/exhibit.js @@ -84,16 +84,31 @@ function markerToCameraMatrix(m) { return { M, err: pose.bestError, dist: Math.hypot(t[0], t[1], t[2]) }; } -// anchor world placement -> matrix (world coords of the marker's centre + yaw). -// The marker lies flat-ish on the build; we treat its plane with the anchor yaw -// about the vertical (Y) axis. Anchor.position is the marker centre in world mm. +// anchor world placement -> matrix mapping the MARKER's local frame (as POS-IT +// sees it: marker in its XY plane, +Z out of the printed face) into WORLD space. +// +// A crest lying flat on the table has its face pointing UP, so the marker's +// local +Z must map to world +Y. That's a -90 deg rotation about X. On top of +// that we apply the anchor's yaw (spin on the table) and its world position. +// +// mount 'flat' (default): marker lies on the table, face up -> rotX(-90) then yaw about Y +// mount 'wall': marker stands vertical, face outward -> yaw about Y only +// +// The order matters: worldFromMarker = T(pos) * Ry(yaw) * planeTilt. function anchorToWorldMatrix(anchor) { const p = anchor.position || { x: 0, y: 0, z: 0 }; const yaw = THREE.MathUtils.degToRad(anchor.rotationDeg?.y || 0); - const m = new THREE.Matrix4(); - m.makeRotationY(yaw); - m.setPosition(p.x, p.y, p.z); - return m; + const mount = anchor.mount || 'flat'; + + const planeTilt = new THREE.Matrix4(); + if (mount === 'wall') planeTilt.identity(); // face already points sideways + else planeTilt.makeRotationX(-Math.PI / 2); // flat: tip face up to +Y + + const spin = new THREE.Matrix4().makeRotationY(yaw); + const trans = new THREE.Matrix4().setPosition(p.x, p.y, p.z); + + // trans * spin * planeTilt + return trans.multiply(spin).multiply(planeTilt); } // Given a detected marker with a known anchor, compute world->camera and apply diff --git a/public/placement-plan.html b/public/placement-plan.html new file mode 100644 index 0000000..1b862db --- /dev/null +++ b/public/placement-plan.html @@ -0,0 +1,222 @@ + + + + + +Newbury Exhibit — placement plan + + + +
+

Newbury placement plan

+
+ + + + +
+ +
+
+

Top-down plan (looking down at the table)

+
loading…
+
+ anchor (crest) — arrow = facing/yaw + building footprint + ghost spawn / path +
+
Origin (0,0) is the front-left corner. X runs right (length), Z runs away from you (depth). Measure from that corner when placing pieces. Print at 100% for a to-scale reference, or just use the coordinate tables below.
+
+ +
+

Anchors — where to place each crest

+
+
+ +
+

Buildings — footprints

+
+
+ +
+

Ghost spawns

+
+
+
+ + + +