import type { CardShape, ContourPoint, ContourBounds } from './types'; import {getRoundedPolygonPoints} from "./rounded"; // 重新导出类型以兼容旧导入路径 export type { CardShape, ContourPoint, ContourBounds }; /** * 生成内接正三角形的轮廓点(无圆角版本) * @param width 外框宽度 * @param height 外框高度 * @returns 正三角形轮廓点(相对于外框左下角,顺时针) */ export function getInscribedTrianglePoints( width: number, height: number ): [number, number][] { // 以短边为基准计算内接正三角形的边长 const minDim = Math.min(width, height / Math.sqrt(3) * 2); // 正三角形的高 = 边长 * sqrt(3) / 2 const triangleHeight = minDim * Math.sqrt(3) / 2; const sideLength = minDim; // 计算居中偏移 const offsetX = (width - sideLength) / 2; const offsetY = (height - triangleHeight) / 2; // 正三角形三个顶点(底边在下,顶点在上,顺时针:左上→右上→下) const points: [number, number][] = [ [offsetX, offsetY + triangleHeight], // 左下顶点 [offsetX + sideLength, offsetY + triangleHeight], // 右下顶点 [offsetX + sideLength / 2, offsetY] // 顶部顶点 ]; return points; } /** * 生成内接正六边形的轮廓点(无圆角版本) * @param width 外框宽度 * @param height 外框高度 * @returns 正六边形轮廓点(相对于外框左下角,顺时针) */ export function getInscribedHexagonPoints( width: number, height: number ): [number, number][] { const minDim = Math.min(width, height / Math.sqrt(3) * 2); const radius = minDim / 2; // 中心点 const centerX = width / 2; const centerY = height / 2; // 正六边形六个顶点(平顶,从左上角开始顺时针) // 角度:210°, 270°, 330°, 30°, 90°, 150° (数学坐标系,Y向上) // 在屏幕坐标系 (Y向下),我们直接按顺时针计算 const points: [number, number][] = []; for (let i = 0; i < 6; i++) { // 从 -120度开始,每 60 度一个点,实现平顶且顺时针 const angle = (-2 * Math.PI / 3) + (i * Math.PI / 3); points.push([ centerX + radius * Math.cos(angle), centerY + radius * Math.sin(angle) ]); } return points; } /** * 根据形状生成卡片轮廓点(单位:mm,相对于卡片左下角) */ export function getCardShapePoints( shape: CardShape, width: number, height: number, cornerRadius: number = 0, segmentsPerCorner: number = 4 ): [number, number][] { // 处理带圆角的情况 - 统一使用 getRoundedPolygonPoints if (cornerRadius > 0) { if (shape === 'rectangle') { const vertices: [number, number][] = [ [0, 0], [width, 0], [width, height], [0, height] ]; return getRoundedPolygonPoints(vertices, cornerRadius, segmentsPerCorner); } if (shape === 'triangle') { const vertices = getInscribedTrianglePoints(width, height); return getRoundedPolygonPoints(vertices, cornerRadius, segmentsPerCorner); } if (shape === 'hexagon') { const vertices = getInscribedHexagonPoints(width, height); return getRoundedPolygonPoints(vertices, cornerRadius, segmentsPerCorner); } } 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': { return getInscribedTrianglePoints(width, height); } case 'hexagon': { return getInscribedHexagonPoints(width, height); } 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 = false): 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]); }