Debug Flipping Fixes

This commit is contained in:
2026-07-08 19:19:26 +10:00
parent 199c3107f2
commit b865700fe8
2 changed files with 73 additions and 14 deletions
+60 -11
View File
@@ -77,10 +77,10 @@ function centredCorners(m, w, h) {
// (right) multiply by F fixes the rotation handedness while keeping translation
// correct (F*M*F's translation column is F*t, i.e. flipped exactly once).
const flipYZ = new THREE.Matrix4().makeScale(1, -1, -1);
function markerToCameraMatrix(m) {
const pose = posit.pose(centredCorners(m, grab.width, grab.height));
if (!pose) return null;
const R = pose.bestRotation, t = pose.bestTranslation;
// Convert one POS-IT (R,t) solution into a three.js marker->camera matrix via
// the F*M*F conjugation (see note above).
function poseToMatrix(R, t) {
const M = new THREE.Matrix4().set(
R[0][0], R[0][1], R[0][2], t[0],
R[1][0], R[1][1], R[1][2], t[1],
@@ -88,8 +88,24 @@ function markerToCameraMatrix(m) {
0, 0, 0, 1
);
M.premultiply(flipYZ); // F * M
M.multiply(flipYZ); // F * M * F (conjugation: fixes pitch handedness)
return { M, err: pose.bestError, dist: Math.hypot(t[0], t[1], t[2]) };
M.multiply(flipYZ); // F * M * F
return M;
}
// POS-IT returns TWO solutions for a planar marker (the real pose and a mirror
// twin). Up close they have near-equal error and the solver flip-flops between
// them -> "rotate 90deg, ghost spins 180deg". We return both; alignWorld picks
// the one whose resulting world orientation is closest to the previous frame.
function markerToCameraMatrix(m) {
const pose = posit.pose(centredCorners(m, grab.width, grab.height));
if (!pose) return null;
const best = { M: poseToMatrix(pose.bestRotation, pose.bestTranslation), err: pose.bestError };
let alt = null;
if (pose.alternativeRotation && pose.alternativeTranslation) {
alt = { M: poseToMatrix(pose.alternativeRotation, pose.alternativeTranslation), err: pose.alternativeError };
}
const t = pose.bestTranslation;
return { best, alt, dist: Math.hypot(t[0], t[1], t[2]) };
}
// anchor world placement -> matrix mapping the MARKER's local frame (as POS-IT
@@ -121,16 +137,49 @@ function anchorToWorldMatrix(anchor) {
// Given a detected marker with a known anchor, compute world->camera and apply
// it to the `world` group so everything in world coords renders correctly.
//
// Planar-ambiguity defeat: POS-IT gives two candidate poses. We compute the
// resulting world->camera for each, and keep whichever is closest to LAST
// frame's transform (temporal consistency). This stops the mirror-twin flip
// that made the ghost spin the wrong way. On first lock we take `best`.
const tmpAnchorInv = new THREE.Matrix4();
let lastWorldToCamera = null; // previous frame's chosen transform (for continuity)
function worldToCameraFor(mMatrix, anchorInv) {
return new THREE.Matrix4().multiplyMatrices(mMatrix, anchorInv);
}
// crude distance between two 4x4s: sum of squared element differences of the
// rotation part (enough to tell the real pose from its mirror twin).
function matDist(a, b) {
const ea = a.elements, eb = b.elements;
let s = 0;
for (const i of [0, 1, 2, 4, 5, 6, 8, 9, 10]) { const d = ea[i] - eb[i]; s += d * d; }
return s;
}
function alignWorld(m2c, anchor) {
// camera = markerToCamera * anchorToWorld^-1 (as a world->camera transform)
// world point -> anchor-local -> camera:
// worldToCamera = markerToCamera * (anchorToWorld)^-1
const aToW = anchorToWorldMatrix(anchor);
tmpAnchorInv.copy(aToW).invert();
const worldToCamera = new THREE.Matrix4().multiplyMatrices(m2c.M, tmpAnchorInv);
world.matrix.copy(worldToCamera);
const candBest = worldToCameraFor(m2c.best.M, tmpAnchorInv);
let chosen = candBest;
if (m2c.alt) {
const candAlt = worldToCameraFor(m2c.alt.M, tmpAnchorInv);
if (lastWorldToCamera) {
// pick the candidate closest to where the world was last frame
chosen = matDist(candAlt, lastWorldToCamera) < matDist(candBest, lastWorldToCamera)
? candAlt : candBest;
} else {
// first lock: trust the lower-error (best) solution
chosen = candBest;
}
}
world.matrix.copy(chosen);
world.matrixWorldNeedsUpdate = true;
lastWorldToCamera = chosen.clone();
}
// ---- Build occluders + anchor viz once scene arrives ----------------------