fix(photos): length dim snaps to wood footprint (#69)
This commit is contained in:
@@ -385,35 +385,55 @@ test('buildOverlayPlan: length axial span = tip span; inventory y stays within w
|
||||
`length bottom y=${lenMaxY.toFixed(1)} must not extend past wood AABB maxY=${mMaxY.toFixed(1)}`,
|
||||
);
|
||||
|
||||
// Measured axial span equals mask tip-to-tip (dim line parallel beside wood)
|
||||
// Length endpoints = robust tip pixels + common screen offset → tip-to-tip span
|
||||
const { start: aa, end: bb } = axisEndpoints(bbox);
|
||||
const ex = bb[0] - aa[0], ey = bb[1] - aa[1], elen = Math.hypot(ex, ey) || 1;
|
||||
const ux = ex / elen, uy = ey / elen;
|
||||
const cx = bbox.corners.reduce((s, c) => s + c[0], 0) / 4;
|
||||
const cy = bbox.corners.reduce((s, c) => s + c[1], 0) / 4;
|
||||
const cProj = cx * ux + cy * uy;
|
||||
let tipMin = Infinity, tipMax = -Infinity;
|
||||
const projs: number[] = [];
|
||||
for (let y = 0; y < H; y++) {
|
||||
for (let x = 0; x < W; x++) {
|
||||
if (!bitset[y * W + x]) continue;
|
||||
projs.push(x * ux + y * uy);
|
||||
}
|
||||
}
|
||||
projs.sort((p, q) => p - q);
|
||||
const lo = Math.min(Math.floor(projs.length * 0.002), projs.length - 1);
|
||||
const hi = Math.max(Math.floor(projs.length * 0.998), lo);
|
||||
const tipMin = projs[lo];
|
||||
const tipMax = projs[hi];
|
||||
const stationTol = Math.max(1.5, (tipMax - tipMin) * 0.001);
|
||||
let tipMinPt: [number, number] = [0, 0];
|
||||
let tipMaxPt: [number, number] = [0, 0];
|
||||
let bestStart = Infinity, bestEnd = Infinity;
|
||||
for (let y = 0; y < H; y++) {
|
||||
for (let x = 0; x < W; x++) {
|
||||
if (!bitset[y * W + x]) continue;
|
||||
const p = x * ux + y * uy;
|
||||
if (p < tipMin) tipMin = p;
|
||||
if (p > tipMax) tipMax = p;
|
||||
const across = Math.abs((x - cx) * -uy + (y - cy) * ux);
|
||||
if (Math.abs(p - tipMin) <= stationTol && across < bestStart) {
|
||||
bestStart = across;
|
||||
tipMinPt = [x, y];
|
||||
}
|
||||
if (Math.abs(p - tipMax) <= stationTol && across < bestEnd) {
|
||||
bestEnd = across;
|
||||
tipMaxPt = [x, y];
|
||||
}
|
||||
}
|
||||
}
|
||||
const tipStart: [number, number] = [cx + ux * (tipMin - cProj), cy + uy * (tipMin - cProj)];
|
||||
const tipEnd: [number, number] = [cx + ux * (tipMax - cProj), cy + uy * (tipMax - cProj)];
|
||||
const oriented = pickBottomTowardTop(tipStart, tipEnd);
|
||||
const mapBottom = inv.mapToInventory(oriented.bottom[0], oriented.bottom[1]);
|
||||
const mapTop = inv.mapToInventory(oriented.top[0], oriented.top[1]);
|
||||
const proj = (p: [number, number], o: [number, number]) =>
|
||||
(p[0] - o[0]) * oriented.ux + (p[1] - o[1]) * oriented.uy;
|
||||
const tipSpan = Math.abs(proj(mapTop, mapBottom));
|
||||
const lenSpan = Math.abs(proj(plan.length.b, plan.length.a));
|
||||
const tipOr = pickBottomTowardTop(tipMinPt, tipMaxPt);
|
||||
const mapTipBot = inv.mapToInventory(tipOr.bottom[0], tipOr.bottom[1]);
|
||||
const mapTipTop = inv.mapToInventory(tipOr.top[0], tipOr.top[1]);
|
||||
const tipEuclid = Math.hypot(mapTipTop[0] - mapTipBot[0], mapTipTop[1] - mapTipBot[1]);
|
||||
const lenEuclid = Math.hypot(plan.length.b[0] - plan.length.a[0], plan.length.b[1] - plan.length.a[1]);
|
||||
assert.ok(
|
||||
Math.abs(lenSpan - tipSpan) <= 2,
|
||||
`length axial span ${lenSpan.toFixed(1)} must equal tip span ${tipSpan.toFixed(1)}`,
|
||||
Math.abs(lenEuclid - tipEuclid) <= 2,
|
||||
`length span ${lenEuclid.toFixed(1)} must equal tip-to-tip ${tipEuclid.toFixed(1)}`,
|
||||
);
|
||||
// Arrowhead stations share tip screen-y (vertical-ish fixture)
|
||||
assert.ok(Math.abs(plan.length.a[1] - mapTipBot[1]) <= 1.5 || Math.abs(plan.length.a[1] - mapTipTop[1]) <= 1.5);
|
||||
assert.ok(Math.abs(plan.length.b[1] - mapTipBot[1]) <= 1.5 || Math.abs(plan.length.b[1] - mapTipTop[1]) <= 1.5);
|
||||
|
||||
// Length stays axis-right of wood (clearance to the right in inventory x for this fixture)
|
||||
const lenMidX = (plan.length.a[0] + plan.length.b[0]) / 2;
|
||||
|
||||
Reference in New Issue
Block a user