123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533 |
- 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(v1.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();
- /**
- * 点是否相同
- * @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 点
- */
- export const lineInner = (line: Pos[], position: Pos) => {
- // 定义线段的起点和终点坐标
- const [A, B] = lVector(line);
- // 定义一个点的坐标
- const P = vector(position);
- // 计算向量 AP 和 AB
- const AP = P.clone().sub(A);
- const AB = B.clone().sub(A);
- // 计算叉积
- const crossProduct = AP.x * AB.y - AP.y * AB.x;
- // 如果叉积不为 0,说明点 P 不在直线 AB 上
- if (!zeroEq(crossProduct)) {
- return false;
- }
- // 检查点 P 的坐标是否在 A 和 B 的坐标范围内
- return (
- Math.min(A.x, B.x) <= P.x &&
- P.x <= Math.max(A.x, B.x) &&
- Math.min(A.y, B.y) <= P.y &&
- P.y <= Math.max(A.y, B.y)
- );
- };
- /**
- * 获取点在线段上的投影
- * @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;
- }
- if (zeroEq(xScaleFactor - yScaleFactor)) {
- return xScaleFactor;
- }
- }
- // 获取两线段的矩阵关系
- export const getLineRelationMat = (l1: [Pos, Pos], l2: [Pos, Pos]) => {
- // 提取点
- const P1 = l1[0]; // l1 的起点
- const P1End = l1[1]; // l1 的终点
- const P2 = l2[0]; // l2 的起点
- const P2End = l2[1]; // l2 的终点
- // 计算方向向量
- const d1 = { x: P1End.x - P1.x, y: P1End.y - P1.y };
- const d2 = { x: P2End.x - P2.x, y: P2End.y - P2.y };
- // 计算方向向量的长度
- const lengthD1 = Math.sqrt(d1.x ** 2 + d1.y ** 2);
- const lengthD2 = Math.sqrt(d2.x ** 2 + d2.y ** 2);
- if (lengthD1 === 0 || lengthD2 === 0) return new Transform();
- // 归一化方向向量
- const unitD1 = { x: d1.x / lengthD1, y: d1.y / lengthD1 };
- const unitD2 = { x: d2.x / lengthD2, y: d2.y / lengthD2 };
- // 计算旋转角度
- const angle = Math.atan2(unitD2.y, unitD2.x) - Math.atan2(unitD1.y, unitD1.x);
- // 计算旋转矩阵
- // 计算缩放因子
- const scale = lengthD2 / lengthD1;
- // 计算平移向量
- const translation = [P2.x - P1.x, P2.y - P1.y];
- const mat = new Transform()
- .translate(translation[0], translation[1])
- .translate(P1.x, P1.y)
- .scale(scale, scale)
- .rotate(angle)
- .translate(-P1.x, -P1.y);
-
- if (!eqPoint(mat.point(P1), P2)) {
- console.error('对准不正确 旋转后P1', mat.point(P1), P2)
- }
- if (!eqPoint(mat.point(P1End), P2End)) {
- console.error('对准不正确 旋转后P2', mat.point(P1End), P1End)
- }
- return mat
- };
|