diff --git a/lib/solvers/TraceCleanupSolver/simplifyPath.ts b/lib/solvers/TraceCleanupSolver/simplifyPath.ts index e17bfb52c..d5929f1b4 100644 --- a/lib/solvers/TraceCleanupSolver/simplifyPath.ts +++ b/lib/solvers/TraceCleanupSolver/simplifyPath.ts @@ -4,38 +4,46 @@ import { isVertical, } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions" -export const simplifyPath = (path: Point[]): Point[] => { - if (path.length < 3) return path - const newPath: Point[] = [path[0]] - for (let i = 1; i < path.length - 1; i++) { - const p1 = newPath[newPath.length - 1] - const p2 = path[i] - const p3 = path[i + 1] - if ( - (isVertical(p1, p2) && isVertical(p2, p3)) || - (isHorizontal(p1, p2) && isHorizontal(p2, p3)) - ) { +const EPS = 1e-9 + +const removeDuplicateConsecutivePoints = (path: Point[]): Point[] => { + if (path.length <= 1) return path + const result: Point[] = [path[0]] + for (let i = 1; i < path.length; i++) { + const prev = result[result.length - 1] + const cur = path[i] + if (Math.abs(prev.x - cur.x) < EPS && Math.abs(prev.y - cur.y) < EPS) { continue } - newPath.push(p2) + result.push(cur) } - newPath.push(path[path.length - 1]) + return result +} - if (newPath.length < 3) return newPath - const finalPath: Point[] = [newPath[0]] - for (let i = 1; i < newPath.length - 1; i++) { - const p1 = finalPath[finalPath.length - 1] - const p2 = newPath[i] - const p3 = newPath[i + 1] +const collapseCollinearPoints = (path: Point[]): Point[] => { + if (path.length < 3) return path + const result: Point[] = [path[0]] + for (let i = 1; i < path.length - 1; i++) { + const p1 = result[result.length - 1] + const p2 = path[i] + const p3 = path[i + 1] if ( (isVertical(p1, p2) && isVertical(p2, p3)) || (isHorizontal(p1, p2) && isHorizontal(p2, p3)) ) { continue } - finalPath.push(p2) + result.push(p2) } - finalPath.push(newPath[newPath.length - 1]) + result.push(path[path.length - 1]) + return result +} - return finalPath +export const simplifyPath = (path: Point[]): Point[] => { + let current = removeDuplicateConsecutivePoints(path) + current = collapseCollinearPoints(current) + current = removeDuplicateConsecutivePoints(current) + current = collapseCollinearPoints(current) + current = removeDuplicateConsecutivePoints(current) + return current } diff --git a/tests/solvers/TraceCleanupSolver/simplifyPath.test.ts b/tests/solvers/TraceCleanupSolver/simplifyPath.test.ts new file mode 100644 index 000000000..f58dee82f --- /dev/null +++ b/tests/solvers/TraceCleanupSolver/simplifyPath.test.ts @@ -0,0 +1,119 @@ +import { expect, test, describe } from "bun:test" +import { simplifyPath } from "lib/solvers/TraceCleanupSolver/simplifyPath" + +describe("simplifyPath duplicate point removal", () => { + test("removes consecutive duplicate points in the middle", () => { + const path = [ + { x: 0, y: 0 }, + { x: 1, y: 0 }, + { x: 1, y: 0 }, + { x: 2, y: 0 }, + ] + const result = simplifyPath(path) + expect(result).toEqual([ + { x: 0, y: 0 }, + { x: 2, y: 0 }, + ]) + }) + + test("removes consecutive duplicate points at the start", () => { + const path = [ + { x: 0, y: 0 }, + { x: 0, y: 0 }, + { x: 1, y: 0 }, + { x: 1, y: 1 }, + ] + const result = simplifyPath(path) + expect(result).toEqual([ + { x: 0, y: 0 }, + { x: 1, y: 0 }, + { x: 1, y: 1 }, + ]) + }) + + test("removes consecutive duplicate points at the end", () => { + const path = [ + { x: 0, y: 0 }, + { x: 1, y: 0 }, + { x: 1, y: 1 }, + { x: 1, y: 1 }, + ] + const result = simplifyPath(path) + expect(result).toEqual([ + { x: 0, y: 0 }, + { x: 1, y: 0 }, + { x: 1, y: 1 }, + ]) + }) + + test("removes multiple consecutive duplicates", () => { + const path = [ + { x: 0, y: 0 }, + { x: 1, y: 0 }, + { x: 1, y: 0 }, + { x: 1, y: 0 }, + { x: 1, y: 1 }, + ] + const result = simplifyPath(path) + expect(result).toEqual([ + { x: 0, y: 0 }, + { x: 1, y: 0 }, + { x: 1, y: 1 }, + ]) + }) + + test("handles zero-length two-point path (both points identical)", () => { + const path = [ + { x: 5, y: 5 }, + { x: 5, y: 5 }, + ] + const result = simplifyPath(path) + expect(result).toEqual([{ x: 5, y: 5 }]) + }) + + test("collapses collinear horizontal points", () => { + const path = [ + { x: 0, y: 0 }, + { x: 1, y: 0 }, + { x: 2, y: 0 }, + { x: 3, y: 0 }, + ] + const result = simplifyPath(path) + expect(result).toEqual([ + { x: 0, y: 0 }, + { x: 3, y: 0 }, + ]) + }) + + test("collapses collinear vertical points", () => { + const path = [ + { x: 0, y: 0 }, + { x: 0, y: 1 }, + { x: 0, y: 2 }, + { x: 0, y: 3 }, + ] + const result = simplifyPath(path) + expect(result).toEqual([ + { x: 0, y: 0 }, + { x: 0, y: 3 }, + ]) + }) + + test("handles path with duplicates at direction change", () => { + const path = [ + { x: 0, y: 0 }, + { x: 1, y: 0 }, + { x: 1, y: 0 }, + { x: 1, y: 1 }, + { x: 1, y: 1 }, + { x: 2, y: 1 }, + ] + const result = simplifyPath(path) + expect(result).toEqual([ + { x: 0, y: 0 }, + { x: 1, y: 0 }, + { x: 1, y: 1 }, + { x: 2, y: 1 }, + ]) + }) +})