From 9f665fc403f8d8152636aa98ac6157f2494eb444 Mon Sep 17 00:00:00 2001 From: hypercross Date: Sun, 15 Mar 2026 11:45:35 +0800 Subject: [PATCH] chore: contour test --- src/plotcutter/contour.test.ts | 263 +++++++++++++++++++++++++++++++++ 1 file changed, 263 insertions(+) create mode 100644 src/plotcutter/contour.test.ts diff --git a/src/plotcutter/contour.test.ts b/src/plotcutter/contour.test.ts new file mode 100644 index 0000000..bdc710c --- /dev/null +++ b/src/plotcutter/contour.test.ts @@ -0,0 +1,263 @@ +import { describe, test, expect } from '@jest/globals'; +import { + getRoundedPolygonPoints, + getCardShapePoints, + getInscribedTrianglePoints, + getInscribedHexagonPoints +} from './contour'; + +/** + * 辅助函数:计算两点之间的距离 + */ +function distance(p1: [number, number], p2: [number, number]): number { + return Math.sqrt((p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2); +} + +/** + * 辅助函数:检查点是否接近预期值 + */ +function pointCloseTo(actual: [number, number], expected: [number, number], tolerance: number = 0.01): boolean { + return Math.abs(actual[0] - expected[0]) < tolerance && + Math.abs(actual[1] - expected[1]) < tolerance; +} + +describe('getRoundedPolygonPoints', () => { + describe('矩形圆角', () => { + test('应该生成正确的矩形圆角 (segmentsPerCorner=2)', () => { + const vertices: [number, number][] = [ + [0, 0], + [100, 0], + [100, 60], + [0, 60] + ]; + const cornerRadius = 10; + const segmentsPerCorner = 2; + + const points = getRoundedPolygonPoints(vertices, cornerRadius, segmentsPerCorner); + + // 每个角应该有:起点 + 2 个弧线点 = 3 个点,4 个角共 12 个点 + // 但由于结构是:起点 + 弧线点 1 + 弧线点 2,然后下一条边的起点就是上一个角的终点 + // 所以总点数应该是 4 * (1 + 2) = 12 个点 + expect(points.length).toBe(12); + + // 验证第一个角(左下角,顶点 [0, 0])的起点应该在 (0, 10) + expect(pointCloseTo(points[0], [0, 10])).toBe(true); + + // 验证第一个角的弧线点应该在圆上 + const center0: [number, number] = [10, 10]; + const arcPoint1: [number, number] = points[1]; + const arcPoint2: [number, number] = points[2]; + + // 检查弧线点到圆心的距离是否接近半径 + const dist1 = distance(arcPoint1, center0); + const dist2 = distance(arcPoint2, center0); + expect(Math.abs(dist1 - cornerRadius)).toBeLessThan(0.1); + expect(Math.abs(dist2 - cornerRadius)).toBeLessThan(0.1); + + // 验证第二个角(右下角,顶点 [100, 0])的起点应该在 (90, 0) + expect(pointCloseTo(points[3], [90, 0])).toBe(true); + }); + + test('应该生成闭合的轮廓', () => { + const vertices: [number, number][] = [ + [0, 0], + [100, 0], + [100, 60], + [0, 60] + ]; + const cornerRadius = 10; + const segmentsPerCorner = 4; + + const points = getRoundedPolygonPoints(vertices, cornerRadius, segmentsPerCorner); + + // 验证最后一个点和第一个点之间的距离应该合理(闭合路径) + const firstPoint = points[0]; + const lastPoint = points[points.length - 1]; + + // 最后一个点应该是最后一个角的弧线终点,接近第一个点 + // 实际上,由于每个角都从起点开始,最后一个点应该接近第一个角的起点 + const dist = distance(firstPoint, lastPoint); + expect(dist).toBeLessThan(cornerRadius * 0.5); // 应该比较接近 + }); + + test('圆角半径过大时应该被限制', () => { + const vertices: [number, number][] = [ + [0, 0], + [20, 0], + [20, 60], + [0, 60] + ]; + const cornerRadius = 50; // 远大于边长的一半 + + const points = getRoundedPolygonPoints(vertices, cornerRadius); + + // 验证所有点都在边界框内 + for (const [x, y] of points) { + expect(x).toBeGreaterThanOrEqual(0); + expect(x).toBeLessThanOrEqual(20); + expect(y).toBeGreaterThanOrEqual(0); + expect(y).toBeLessThanOrEqual(60); + } + }); + }); + + describe('三角形圆角', () => { + test('应该为三角形生成正确的圆角 (segmentsPerCorner=2)', () => { + const vertices = getInscribedTrianglePoints(100, 60); + const cornerRadius = 5; + const segmentsPerCorner = 2; + + const points = getRoundedPolygonPoints(vertices, cornerRadius, segmentsPerCorner); + + // 3 个角,每个角有 1 个起点 + 2 个弧线点 = 3 个点 + expect(points.length).toBe(9); + + // 验证所有弧线点到对应圆心的距离接近半径 + // (这里简化验证,只检查点数和合理性) + expect(points.length).toBeGreaterThan(0); + + // 验证所有点都在边界框内 + for (const [x, y] of points) { + expect(x).toBeGreaterThanOrEqual(0); + expect(x).toBeLessThanOrEqual(100); + expect(y).toBeGreaterThanOrEqual(0); + expect(y).toBeLessThanOrEqual(60); + } + }); + }); + + describe('六边形圆角', () => { + test('应该为六边形生成正确的圆角 (segmentsPerCorner=2)', () => { + const vertices = getInscribedHexagonPoints(100, 60); + const cornerRadius = 5; + const segmentsPerCorner = 2; + + const points = getRoundedPolygonPoints(vertices, cornerRadius, segmentsPerCorner); + + // 6 个角,每个角有 1 个起点 + 2 个弧线点 = 3 个点 + expect(points.length).toBe(18); + + // 验证所有点都在边界框内 + for (const [x, y] of points) { + expect(x).toBeGreaterThanOrEqual(0); + expect(x).toBeLessThanOrEqual(100); + expect(y).toBeGreaterThanOrEqual(0); + expect(y).toBeLessThanOrEqual(60); + } + }); + }); + + describe('边界情况', () => { + test('圆角半径为 0 时返回原始顶点', () => { + const vertices: [number, number][] = [ + [0, 0], + [100, 0], + [100, 60], + [0, 60] + ]; + + const points = getRoundedPolygonPoints(vertices, 0); + + expect(points).toEqual(vertices); + }); + + test('顶点数少于 3 个时返回原始顶点', () => { + const line: [number, number][] = [[0, 0], [100, 0]]; + const points = getRoundedPolygonPoints(line, 10); + expect(points).toEqual(line); + }); + }); +}); + +describe('getCardShapePoints', () => { + describe('矩形', () => { + test('无圆角矩形', () => { + const points = getCardShapePoints('rectangle', 100, 60, 0); + expect(points.length).toBe(4); + expect(points).toEqual([ + [0, 0], + [100, 0], + [100, 60], + [0, 60] + ]); + }); + + test('带圆角矩形 (segmentsPerCorner=2)', () => { + const points = getCardShapePoints('rectangle', 100, 60, 10, 2); + + // 4 个角,每个角有 1 个起点 + 2 个弧线点 = 3 个点 + expect(points.length).toBe(12); + + // 验证第一个点在左边,y 坐标为圆角半径 + expect(pointCloseTo(points[0], [0, 10])).toBe(true); + }); + + test('圆角矩形应该是对称的', () => { + const width = 100; + const height = 60; + const cornerRadius = 10; + const segmentsPerCorner = 4; + + const points = getCardShapePoints('rectangle', width, height, cornerRadius, segmentsPerCorner); + + // 验证左右对称 + const leftPoints = points.filter(([x]) => x < width / 2); + const rightPoints = points.filter(([x]) => x > width / 2); + + expect(leftPoints.length).toBe(rightPoints.length); + + // 验证上下对称 + const topPoints = points.filter(([, y]) => y < height / 2); + const bottomPoints = points.filter(([, y]) => y > height / 2); + + expect(topPoints.length).toBe(bottomPoints.length); + }); + }); + + describe('三角形', () => { + test('无圆角三角形', () => { + const points = getCardShapePoints('triangle', 100, 60, 0); + expect(points.length).toBe(3); + }); + + test('带圆角三角形 (segmentsPerCorner=2)', () => { + const points = getCardShapePoints('triangle', 100, 60, 10, 2); + + // 3 个角,每个角有 1 个起点 + 2 个弧线点 = 3 个点 + expect(points.length).toBe(9); + }); + }); + + describe('六边形', () => { + test('无圆角六边形', () => { + const points = getCardShapePoints('hexagon', 100, 60, 0); + expect(points.length).toBe(6); + }); + + test('带圆角六边形 (segmentsPerCorner=2)', () => { + const points = getCardShapePoints('hexagon', 100, 60, 10, 2); + + // 6 个角,每个角有 1 个起点 + 2 个弧线点 = 3 个点 + expect(points.length).toBe(18); + }); + }); + + describe('圆形', () => { + test('圆形轮廓', () => { + const points = getCardShapePoints('circle', 100, 60, 0); + + // 圆形固定生成 36 个点 + expect(points.length).toBe(36); + + // 验证所有点到中心的距离接近半径 + const centerX = 50; + const centerY = 30; + const radius = 30; // min(100, 60) / 2 + + for (const [x, y] of points) { + const dist = distance([x, y], [centerX, centerY]); + expect(Math.abs(dist - radius)).toBeLessThan(0.1); + } + }); + }); +});