import { Vector2, ShapeUtils, Box2 } from "three"; import { Transform } from "konva/lib/Util"; import { round } from "./shared.ts"; export type Pos = { x: number; y: number }; export const vector = (pos: Pos = {x: 0, y: 0}): Vector2 => { return new Vector2(pos.x, pos.y); // if (pos instanceof Vector2) { // return pos; // } else { // return new Vector2(pos.x, pos.y); // } }; export const lVector = (line: Pos[]) => line.map(vector); export const zeroEq = (n: number) => Math.abs(n) < 0.0001; export const numEq = (p1: number, p2: number) => zeroEq(p1 - p2); export const vEq = (v1: Pos, v2: Pos) => numEq(v1.x, v2.x) && numEq(v2.y, v2.y); export const vsBound = (positions: Pos[]) => { const box = new Box2(); box.setFromPoints(positions.map(vector)); return box; }; /** * 获取线段方向 * @param line 线段 * @returns 方向 */ export const lineVector = (line: Pos[]) => vector(line[1]).sub(vector(line[0])).normalize(); const epsilon = 1e-6; // 误差范围 /** * 点是否相同 * @param p1 点1 * @param p2 点2 * @returns 是否相等 */ export const eqPoint = vEq /** * 方向是否相同 * @param p1 点1 * @param p2 点2 * @returns 是否相等 */ export const eqNGDire = (p1: Pos, p2: Pos) => eqPoint(p1, p2) || eqPoint(p1, vector(p2).multiplyScalar(-1)) /** * 获取两点距离 * @param p1 点1 * @param p2 点2 * @returns 距离 */ export const lineLen = (p1: Pos, p2: Pos) => vector(p1).distanceTo(p2); export const vectorLen = (dire: Pos) => vector(dire).length(); /** * 获取向量的垂直向量 * @param dire 原方向 * @returns 垂直向量 */ export const verticalVector = (dire: Pos) => vector({ x: -dire.y, y: dire.x }).normalize(); /** * 获取旋转指定度数后的向量 * @param pos 远向量 * @param angleRad 旋转角度 * @returns 旋转后向量 */ export const rotateVector = (pos: Pos, angleRad: number) => new Transform().rotate(angleRad).point(pos); /** * 创建线段 * @param dire 向量 * @param start 起始点 * @param dis 长度 * @returns 线段 */ export const getVectorLine = ( dire: Pos, start: Pos = { x: 0, y: 0 }, dis: number = 1 ) => [start, vector(dire).multiplyScalar(dis).add(start)]; /** * 获取线段的垂直方向向量 * @param line 原线段 * @returns 垂直向量 */ export const lineVerticalVector = (line: Pos[]) => verticalVector(lineVector(line)); /** * 获取向量的垂直线段 * @param dire 向量 * @param start 线段原点 * @param len 线段长度 */ export const verticalVectorLine = ( dire: Pos, start: Pos = { x: 0, y: 0 }, len: number = 1 ) => getVectorLine(verticalVector(dire), start, len); /** * 获取两向量角度(从向量a出发) * @param v1 向量a * @param v2 向量b * @returns 两向量夹角弧度, 逆时针为正,顺时针为负 */ export const vector2IncludedAngle = (v1: Pos, v2: Pos) => { const start = vector(v1); const end = vector(v2); const angle = start.angleTo(end); return start.cross(end) > 0 ? angle : -angle; }; /** * 获取两线段角度(从线段a出发) * @param line1 线段a * @param line2 线段b * @returns 两线段夹角弧度 */ export const line2IncludedAngle = (line1: Pos[], line2: Pos[]) => vector2IncludedAngle(lineVector(line1), lineVector(line2)); /** * 获取向量与X正轴角度 * @param v 向量 * @returns 夹角弧度 */ const nXAxis = vector({ x: 1, y: 0 }); export const vectorAngle = (v: Pos) => { const start = vector(v); return start.angleTo(nXAxis); }; /** * 获取线段与方向的夹角弧度 * @param line 线段 * @param dire 方向 * @returns 线段与方向夹角弧度 */ export const lineAndVectorIncludedAngle = (line: Pos[], v: Pos) => vector2IncludedAngle(lineVector(line), v); /** * 获取线段中心点 * @param line * @returns */ export const lineCenter = (line: Pos[]) => vector(line[0]).add(line[1]).multiplyScalar(0.5); export const pointsCenter = (points: Pos[]) => { if (points.length === 0) return { x: 0, y: 0 }; const v = vector(points[0]); for (let i = 1; i < points.length; i++) { v.add(points[i]); } return v.multiplyScalar(1 / points.length); }; export const lineJoin = (l1: Pos[], l2: Pos[]) => { const checks = [ [l1[0], l2[0]], [l1[0], l2[1]], [l1[1], l2[0]], [l1[1], l2[1]], ]; const ndx = checks.findIndex((line) => eqPoint(line[0], line[1])); if (~ndx) { return checks[ndx]; } else { return false; } }; export const isLineEqual = (l1: Pos[], l2: Pos[]) => eqPoint(l1[0], l2[0]) && eqPoint(l1[1], l2[1]); export const isLineReverseEqual = (l1: Pos[], l2: Pos[]) => eqPoint(l1[0], l2[1]) && eqPoint(l1[1], l2[0]); export const isLineIntersect = (l1: Pos[], l2: Pos[]) => { const s1 = l2[1].y - l2[0].y; const s2 = l2[1].x - l2[0].x; const s3 = l1[1].x - l1[0].x; const s4 = l1[1].y - l1[0].y; const s5 = l1[0].y - l2[0].y; const s6 = l1[0].x - l2[0].x; const denominator = s1 * s3 - s2 * s4; const ua = round((s2 * s5 - s1 * s6) / denominator, 6); const ub = round((s3 * s5 - s4 * s6) / denominator, 6); if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) { return true; } else { return false; } }; export const vectorParallel = (dire1: Pos, dire2: Pos) => zeroEq(vector(dire1).cross(dire2)); export const lineParallelRelationship = (l1: Pos[], l2: Pos[]) => { const dire1 = lineVector(l1); const dire2 = lineVector(l2); // 计算线段的法向量 const normal1 = verticalVector(dire1); const normal2 = verticalVector(dire2); const startDire = lineVector([l1[0], l2[0]]); // 计算线段的参数方程 const t1 = round(normal2.dot(startDire) / normal2.dot(dire1), 6); const t2 = round(normal1.dot(startDire) / normal1.dot(dire2), 6); if (t1 === 0 && t2 === 0) { return RelationshipEnum.Overlap; } if (eqPoint(normal1, normal2) || eqPoint(normal1, normal2.clone().negate())) { return lineJoin(l1, l2) ? RelationshipEnum.Overlap : RelationshipEnum.Parallel; } }; export enum RelationshipEnum { // 重叠 Overlap = "Overlap", // 相交 Intersect = "Intersect", // 延长相交 ExtendIntersect = "ExtendIntersect", // 平行 Parallel = "Parallel", // 首尾连接 Join = "Join", // 一样 Equal = "Equal", // 反向 ReverseEqual = "ReverseEqual", } /** * 获取两线段是什么关系,(重叠、相交、平行、首尾相接等) * @param l1 * @param l2 * @returns RelationshipEnum */ export const lineRelationship = (l1: Pos[], l2: Pos[]) => { if (isLineEqual(l1, l2)) { return RelationshipEnum.Equal; } else if (isLineReverseEqual(l1, l2)) { return RelationshipEnum.ReverseEqual; } const parallelRelationship = lineParallelRelationship(l1, l2); if (parallelRelationship) { return parallelRelationship; } else if (lineJoin(l1, l2)) { return RelationshipEnum.Join; } else if (isLineIntersect(l1, l2)) { return RelationshipEnum.Intersect; // 两线段相交 } else { return RelationshipEnum.ExtendIntersect; // 延长可相交 } }; export const createLine = (p: Pos, v: Pos, l?: number) => { const line = [p] if (l) { v = vector(v).multiplyScalar(l) } line[1] = vector(line[0]).add(v) return line } /** * 获取两线段交点,可延长相交 * @param l1 线段1 * @param l2 线段2 * @returns 交点坐标 */ export const lineIntersection = (l1: Pos[], l2: Pos[]) => { // 定义两条线段的起点和终点坐标 const [line1Start, line1End] = lVector(l1); const [line2Start, line2End] = lVector(l2); // 计算线段的方向向量 const dir1 = line1End.clone().sub(line1Start); const dir2 = line2End.clone().sub(line2Start); // 计算参数方程中的系数 const a = dir1.x; const b = -dir2.x; const c = dir1.y; const d = -dir2.y; const e = line2Start.x - line1Start.x; const f = line2Start.y - line1Start.y; // 求解参数t和s const t = (d * e - b * f) / (a * d - b * c); // 计算交点坐标 const p = line1Start.clone().add(dir1.clone().multiplyScalar(t)); if (isNaN(p.x) || !isFinite(p.x) || isNaN(p.y) || !isFinite(p.y)) return null; return p; }; /** * 获取点在线段上的投影 * @param line 线段 * @param position 点 * @returns 投影信息 */ export const linePointProjection = (line: Pos[], position: Pos) => { // 定义线段的起点和终点坐标 const [lineStart, lineEnd] = lVector(line); // 定义一个点的坐标 const point = vector(position); // 计算线段的方向向量 const lineDir = lineEnd.clone().sub(lineStart); // 计算点到线段起点的向量 const pointToLineStart = point.clone().sub(lineStart); // 计算点在线段方向上的投影长度 const t = pointToLineStart.dot(lineDir.normalize()); // 计算投影点的坐标 return lineStart.add(lineDir.multiplyScalar(t)); }; /** * 获取点距离线段最近距离 * @param line 直线 * @param position 参考点 * @returns 距离 */ export const linePointLen = (line: Pos[], position: Pos) => lineLen(position, linePointProjection(line, position)); /** * 计算多边形是否为逆时针 * @param points 多边形顶点 * @returns true | false */ export const isPolygonCounterclockwise = (points: Pos[]) => ShapeUtils.isClockWise(points.map(vector)); /** * 切割线段,返回连段切割点 * @param line 线段 * @param amount 切割份量 * @param unit 一份单位大小 * @returns 点数组 */ export const lineSlice = ( line: Pos[], amount: number, unit = lineLen(line[0], line[1]) / amount ) => new Array(unit) .fill(0) .map((_, i) => linePointProjection(line, { x: i * unit, y: i * unit })); /** * 线段是否相交多边形 * @param polygon 多边形 * @param line 检测线段 * @returns */ export const isPolygonLineIntersect = (polygon: Pos[], line: Pos[]) => { for (let i = 0; i < polygon.length; i++) { if (isLineIntersect([polygon[i], polygon[i + 1]], line)) { return true; } } return false; }; /** * 通过角度和两个点获取两者的连接点, * @param p1 * @param p2 * @param rad */ export const joinPoint = (p1: Pos, p2: Pos, rad: number) => { const lvector = new Vector2() .subVectors(p1, p2) .rotateAround({ x: 0, y: 0 }, rad); return vector(p2).add(lvector); }; /** * 要缩放多少才能到达目标 * @param origin 缩放原点 * @param scaleDirection 缩放方向 * @param p1 当前点位 * @param p2 目标点位 * @returns */ export function calculateScaleFactor( origin: Pos, scaleDirection: Pos, p1: Pos, p2: Pos ) { const op1 = vector(p1).sub(origin); const op2 = vector(p2).sub(origin); const xZero = zeroEq(op1.x) const yZero = zeroEq(op1.y) if (zeroEq(op1.x) || zeroEq(op2.y)) return; if (zeroEq(scaleDirection.x)) { if (zeroEq(p2.x - p1.x)) { return zeroEq(op1.y - op2.y) ? 1 : yZero ? null : op2.y / op1.y; } else { return; } } if (zeroEq(scaleDirection.y)) { if (zeroEq(p2.y - p1.y)) { return zeroEq(op1.x - op2.x) ? 1 : xZero ? null : op2.x / op1.x; } else { return; } } if (xZero && yZero) { return null; } const xScaleFactor = op2.x / (op1.x * scaleDirection.x); const yScaleFactor = op2.y / (op1.y * scaleDirection.y); if (xZero) { return yScaleFactor } else if (yZero) { return xScaleFactor } console.log(xScaleFactor - yScaleFactor) if (zeroEq(xScaleFactor - yScaleFactor)) { return xScaleFactor; } }