export type CardShape = 'rectangle' | 'circle' | 'triangle' | 'hexagon'; export interface ContourPoint { x: number; y: number; } export interface ContourBounds { minX: number; minY: number; maxX: number; maxY: number; } /** * 生成带圆角的矩形轮廓点 * @param width 矩形宽度 * @param height 矩形高度 * @param cornerRadius 圆角半径(mm) * @param segmentsPerCorner 每个圆角的分段数 */ export function getRoundedRectPoints( width: number, height: number, cornerRadius: number, segmentsPerCorner: number = 4 ): [number, number][] { const points: [number, number][] = []; const r = Math.min(cornerRadius, width / 2, height / 2); if (r <= 0) { // 无圆角,返回普通矩形 points.push([0, 0]); points.push([width, 0]); points.push([width, height]); points.push([0, height]); return points; } // 左上角圆角(从顶部开始,顺时针) for (let i = 0; i < segmentsPerCorner; i++) { const angle = (Math.PI / 2) * (i / segmentsPerCorner); points.push([ r + r * Math.cos(angle - Math.PI / 2), r + r * Math.sin(angle - Math.PI / 2) ]); } // 右上角圆角 for (let i = 0; i < segmentsPerCorner; i++) { const angle = (Math.PI / 2) * (i / segmentsPerCorner); points.push([ width - r + r * Math.cos(angle), r + r * Math.sin(angle) ]); } // 右下角圆角 for (let i = 0; i < segmentsPerCorner; i++) { const angle = (Math.PI / 2) * (i / segmentsPerCorner) + Math.PI / 2; points.push([ width - r + r * Math.cos(angle), height - r + r * Math.sin(angle) ]); } // 左下角圆角 for (let i = 0; i < segmentsPerCorner; i++) { const angle = (Math.PI / 2) * (i / segmentsPerCorner) + Math.PI; points.push([ r + r * Math.cos(angle), height - r + r * Math.sin(angle) ]); } return points; } /** * 根据形状生成卡片轮廓点(单位:mm,相对于卡片左下角) */ export function getCardShapePoints( shape: CardShape, width: number, height: number, cornerRadius: number = 0 ): [number, number][] { if (shape === 'rectangle' && cornerRadius > 0) { return getRoundedRectPoints(width, height, cornerRadius); } const points: [number, number][] = []; switch (shape) { case 'circle': { const radius = Math.min(width, height) / 2; const centerX = width / 2; const centerY = height / 2; for (let i = 0; i < 36; i++) { const angle = (i / 36) * Math.PI * 2; points.push([ centerX + radius * Math.cos(angle), centerY + radius * Math.sin(angle) ]); } break; } case 'triangle': { points.push([width / 2, 0]); points.push([0, height]); points.push([width, height]); break; } case 'hexagon': { const halfW = width / 2; const quarterH = height / 4; points.push([halfW, 0]); points.push([width, quarterH]); points.push([width, height - quarterH]); points.push([halfW, height]); points.push([0, height - quarterH]); points.push([0, quarterH]); break; } case 'rectangle': default: { points.push([0, 0]); points.push([width, 0]); points.push([width, height]); points.push([0, height]); break; } } return points; } /** * 计算多边形的中心点 */ export function calculateCenter(points: [number, number][]): { x: number; y: number } { let sumX = 0; let sumY = 0; for (const [x, y] of points) { sumX += x; sumY += y; } return { x: sumX / points.length, y: sumY / points.length }; } /** * 计算轮廓的边界框 */ export function calculateBounds(points: [number, number][]): ContourBounds { if (points.length === 0) { return { minX: 0, minY: 0, maxX: 0, maxY: 0 }; } let minX = Infinity; let minY = Infinity; let maxX = -Infinity; let maxY = -Infinity; for (const [x, y] of points) { minX = Math.min(minX, x); minY = Math.min(minY, y); maxX = Math.max(maxX, x); maxY = Math.max(maxY, y); } return { minX, minY, maxX, maxY }; } /** * 根据进度计算点在路径上的位置 */ export function getPointOnPath(points: [number, number][], progress: number): [number, number] { if (points.length === 0) return [0, 0]; if (points.length === 1) return points[0]; const totalSegments = points.length; const scaledProgress = progress * totalSegments; const segmentIndex = Math.floor(scaledProgress); const segmentProgress = scaledProgress - segmentIndex; const currentIndex = Math.min(segmentIndex, points.length - 1); const nextIndex = (currentIndex + 1) % points.length; const [x1, y1] = points[currentIndex]; const [x2, y2] = points[nextIndex]; return [ x1 + (x2 - x1) * segmentProgress, y1 + (y2 - y1) * segmentProgress ]; } /** * 将轮廓点转换为 SVG path 命令 * @param points 轮廓点数组 * @param closed 是否闭合路径 */ export function contourToSvgPath(points: [number, number][], closed = true): string { if (points.length === 0) return ''; const [startX, startY] = points[0]; let d = `M ${startX} ${startY}`; for (let i = 1; i < points.length; i++) { const [x, y] = points[i]; d += ` L ${x} ${y}`; } if (closed) { d += ' Z'; } return d; } /** * 平移轮廓点(添加偏移量) */ export function translateContour( points: [number, number][], offsetX: number, offsetY: number ): [number, number][] { return points.map(([x, y]) => [x + offsetX, y + offsetY] as [number, number]); } /** * 翻转轮廓(用于 SVG 坐标转换,Y 轴翻转) * @param points 轮廓点 * @param height 画布高度 */ export function flipContourY( points: [number, number][], height: number ): [number, number][] { return points.map(([x, y]) => [x, height - y] as [number, number]); }