123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458 |
- 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;
- }
- }
|