feat: alignment + projection pipeline
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
87d579eb47
commit
4a554e2057
2 changed files with 63 additions and 0 deletions
36
src/geometry/transform.test.ts
Normal file
36
src/geometry/transform.test.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { applyAlignment, projectSegments, imageToMachine, alignmentFromOrigin } from './transform';
|
||||||
|
import type { Vec2, Mat3, Segment, Alignment } from '../types';
|
||||||
|
|
||||||
|
const close = (a: Vec2, b: Vec2, eps = 1e-9) =>
|
||||||
|
Math.abs(a[0] - b[0]) < eps && Math.abs(a[1] - b[1]) < eps;
|
||||||
|
|
||||||
|
const IDENT: Mat3 = [1, 0, 0, 0, 1, 0, 0, 0, 1];
|
||||||
|
|
||||||
|
describe('transform', () => {
|
||||||
|
it('applyAlignment rotates then translates', () => {
|
||||||
|
const a: Alignment = { tx: 5, ty: 1, rot: Math.PI / 2 };
|
||||||
|
// (1,0) rotated 90° → (0,1), then +(5,1) → (5,2)
|
||||||
|
expect(close(applyAlignment(a, [1, 0]), [5, 2])).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('projectSegments applies alignment then homography to every point', () => {
|
||||||
|
const segs: Segment[] = [{ kind: 'cut', points: [[0, 0], [10, 0]] }];
|
||||||
|
const a: Alignment = { tx: 2, ty: 3, rot: 0 };
|
||||||
|
const out = projectSegments(segs, a, IDENT);
|
||||||
|
expect(out[0]!.kind).toBe('cut');
|
||||||
|
expect(close(out[0]!.points[0]!, [2, 3])).toBe(true);
|
||||||
|
expect(close(out[0]!.points[1]!, [12, 3])).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('imageToMachine is the inverse of the homography', () => {
|
||||||
|
const H: Mat3 = [2, 0, 10, 0, 2, 20, 0, 0, 1];
|
||||||
|
// machine (5,5) → image (20,30); invert should return (5,5)
|
||||||
|
expect(close(imageToMachine(H, [20, 30]), [5, 5])).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('alignmentFromOrigin places work-origin at the given machine point', () => {
|
||||||
|
const a = alignmentFromOrigin([7, 9], 0);
|
||||||
|
expect(close(applyAlignment(a, [0, 0]), [7, 9])).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
27
src/geometry/transform.ts
Normal file
27
src/geometry/transform.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import type { Vec2, Mat3, Segment, Alignment } from '../types';
|
||||||
|
import { applyHomography, invertMat3 } from './homography';
|
||||||
|
|
||||||
|
/** Apply per-job alignment to a point: rotate by rot, then translate by (tx,ty). */
|
||||||
|
export function applyAlignment(a: Alignment, p: Vec2): Vec2 {
|
||||||
|
const c = Math.cos(a.rot);
|
||||||
|
const s = Math.sin(a.rot);
|
||||||
|
return [a.tx + c * p[0] - s * p[1], a.ty + s * p[0] + c * p[1]];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Project work-coordinate segments to image pixels: work → (alignment) → machine → (H) → image. */
|
||||||
|
export function projectSegments(segments: Segment[], a: Alignment, H: Mat3): Segment[] {
|
||||||
|
return segments.map((seg) => ({
|
||||||
|
kind: seg.kind,
|
||||||
|
points: seg.points.map((p) => applyHomography(H, applyAlignment(a, p))),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convert an image-pixel point to machine mm using the inverse homography. */
|
||||||
|
export function imageToMachine(H: Mat3, imagePoint: Vec2): Vec2 {
|
||||||
|
return applyHomography(invertMat3(H), imagePoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Build an alignment that places the work origin (0,0) at the given machine point. */
|
||||||
|
export function alignmentFromOrigin(machinePoint: Vec2, rot: number): Alignment {
|
||||||
|
return { tx: machinePoint[0], ty: machinePoint[1], rot };
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue