|
@@ -0,0 +1,362 @@
|
|
|
|
+import { Vector2, ShapeUtils, Box2 } from "three";
|
|
|
|
+import { Transform } from "konva/lib/Util";
|
|
|
|
+import { round } from "./shared";
|
|
|
|
+
|
|
|
|
+export type Pos = { x: number; y: number };
|
|
|
|
+
|
|
|
|
+export const vector = (pos: Pos) => new Vector2(pos.x, pos.y);
|
|
|
|
+export const lVector = (line: Pos[]) => line.map(vector);
|
|
|
|
+
|
|
|
|
+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 = (p1: Pos, p2: Pos) =>
|
|
|
|
+ vector(p1).distanceTo(p2) < epsilon;
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * 获取两点距离
|
|
|
|
+ * @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));
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * 获取线段与方向的夹角弧度
|
|
|
|
+ * @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 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; // 延长可相交
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * 获取两线段交点,可延长相交
|
|
|
|
+ * @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);
|
|
|
|
+ // 计算交点坐标
|
|
|
|
+ return line1Start.clone().add(dir1.clone().multiplyScalar(t)).toArray();
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * 获取点在线段上的投影
|
|
|
|
+ * @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);
|
|
|
|
+};
|