Two-point fit
64-bitThe turn and move that carry two model points onto two site points, with the remaining error.
yaw = atan2(Δw) − atan2(Δm); t = mid(w) − R mid(m); residual = max(|R m₁ + t − w₁|, |R m₂ + t − w₂|); scale off = |Δw| ÷ |Δm| − 1
All 64-bit. The yaw is a difference of two angles, each from points at least 0.5 m apart. The scale mismatch subtracts 1 from a ratio near 1, so it carries fewer digits than the ratio; it is reported, not applied.
// ---- AR on site (0.8 EG.21, apps-mount_PART_5; spike docs/spikes/EG.21_PART_2.md and _PART_4.md). Maths and decisions only: startAR in xr-session.js drives them. ----
// arSupport(env) → { ok, route, reason, init } what this browser can do for AR (Android Chrome over https: 'webxr-ar'; iPhone/iPad: a 3D viewer link; an Android WebView:
// open the page in Chrome; else the reason). alignPoints(model[2], world[2]) → { yaw, tx, tz, scaleOff, residual, apply } a rigid fit (a turn about the vertical axis and a
// move, scale stays 1) from two reference points pointed at on site (world, metres, Y up) to the same two points in the model (Y up). saveAlignment/loadAlignment: the
// small record kept in the file. recheck(saved, modelPoint, worldPoint) → the second visit's one-point check: keep (< 5 cm), move (< 50 cm), again.
export function alignPoints(model, world) {
const [m1, m2] = model, [w1, w2] = world;
const dm = [m2[0] - m1[0], m2[2] - m1[2]], dw = [w2[0] - w1[0], w2[2] - w1[2]];
const lm = Math.hypot(dm[0], dm[1]), lw = Math.hypot(dw[0], dw[1]);
if (lm < 0.5 || lw < 0.5) throw Object.assign(new Error('The two points are too close to give a direction (under 0.5 m).'), { code: 'close' });
const yaw = Math.atan2(dw[1], dw[0]) - Math.atan2(dm[1], dm[0]);
const c = Math.cos(yaw), s = Math.sin(yaw);
const rot = (p) => [p[0] * c - p[2] * s, p[0] * s + p[2] * c];
// translation so the midpoints agree (spreads the pointing error over both points)
const mid = (a, b) => [(a[0] + b[0]) / 2, (a[2] + b[2]) / 2];
const rm = rot([mid(m1, m2)[0], 0, mid(m1, m2)[1]]), wm = mid(w1, w2);
const tx = wm[0] - rm[0], tz = wm[1] - rm[1];
const to = (p) => { const r = rot(p); return [r[0] + tx, r[1] + tz]; };
const e1 = Math.hypot(to(m1)[0] - w1[0], to(m1)[1] - w1[2]), e2 = Math.hypot(to(m2)[0] - w2[0], to(m2)[1] - w2[2]);
return { yaw, tx, tz, scaleOff: lw / lm - 1, residual: Math.max(e1, e2), apply: (p) => { const r = to(p); return [r[0], p[1], r[1]]; } };
}