fix(overlay): Photos-ready dims follow tape, not wood MAR

After rotateCalibration, map tape tick A→B through bakeTiltTransform into
baked cal coords, orient overlay bbox with orientedRect(..., tapeAngle), and
pass optional axis into buildOverlayPlan so length || tape and widths ⊥ tape.
This commit is contained in:
grokbot_ty
2026-09-26 08:08:21 -07:00
parent d7fc424b69
commit 4ea2618475
5 changed files with 257 additions and 18 deletions
+56
View File
@@ -0,0 +1,56 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { bakeTiltTransform } from './rotateCal.ts';
test('bakeTiltTransform: identity ≈0° leaves points and size unchanged', () => {
const x = bakeTiltTransform(800, 600, 0);
assert.equal(x.identity, true);
assert.equal(x.outW, 800);
assert.equal(x.outH, 600);
assert.deepEqual(x.mapPoint(100, 200), [100, 200]);
assert.deepEqual(x.mapPoint(0, 0), [0, 0]);
});
test('bakeTiltTransform: 360° folds to identity', () => {
const x = bakeTiltTransform(400, 300, 360);
assert.equal(x.identity, true);
assert.deepEqual(x.mapPoint(10, 20), [10, 20]);
});
test('bakeTiltTransform: 90° CW maps centre→centre; corner into AABB canvas', () => {
const srcW = 200, srcH = 100;
const x = bakeTiltTransform(srcW, srcH, 90);
assert.equal(x.identity, false);
// Rotated AABB of 200×100 about centre ≈ 100×200 (ceil may add 1px from float).
assert.ok(x.outW >= 100 && x.outW <= 101, `outW=${x.outW}`);
assert.ok(x.outH >= 200 && x.outH <= 201, `outH=${x.outH}`);
const [cx, cy] = x.mapPoint(srcW / 2, srcH / 2);
assert.ok(Math.abs(cx - x.outW / 2) < 1e-9);
assert.ok(Math.abs(cy - x.outH / 2) < 1e-9);
// Top-left (0,0) relative (−100,−50) → CW90 → (~50,~−100) → +out/2
const [tlx, tly] = x.mapPoint(0, 0);
assert.ok(Math.abs(tlx - (50 + x.outW / 2)) < 1e-4, `tlx=${tlx}`);
assert.ok(Math.abs(tly - (-100 + x.outH / 2)) < 1e-4, `tly=${tly}`);
});
test('bakeTiltTransform: maps a tape-like vertical segment; direction rotates with tilt', () => {
const srcW = 400, srcH = 600;
const start: [number, number] = [200, 100];
const end: [number, number] = [200, 500];
const preUx = 0, preUy = 1; // vertical down
for (const tilt of [15, 30, -20, 45]) {
const x = bakeTiltTransform(srcW, srcH, tilt);
const a = x.mapPoint(start[0], start[1]);
const b = x.mapPoint(end[0], end[1]);
const dx = b[0] - a[0], dy = b[1] - a[1];
const len = Math.hypot(dx, dy) || 1;
const ux = dx / len, uy = dy / len;
// Expected: rotate (preUx, preUy) clockwise by tilt
const rad = (((tilt % 360) + 360) % 360) * Math.PI / 180;
const cos = Math.cos(rad), sin = Math.sin(rad);
const expUx = preUx * cos - preUy * sin;
const expUy = preUx * sin + preUy * cos;
const dot = ux * expUx + uy * expUy;
assert.ok(dot > 0.999, `tilt=${tilt} direction mismatch dot=${dot}`);
}
});