math.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  1. import { Vector2, ShapeUtils, Box2 } from "three";
  2. import { Transform } from "konva/lib/Util";
  3. import { round } from "./shared.ts";
  4. export type Pos = { x: number; y: number };
  5. export const vector = (pos: Pos = { x: 0, y: 0 }): Vector2 => {
  6. return new Vector2(pos.x, pos.y);
  7. // if (pos instanceof Vector2) {
  8. // return pos;
  9. // } else {
  10. // return new Vector2(pos.x, pos.y);
  11. // }
  12. };
  13. export const lVector = (line: Pos[]) => line.map(vector);
  14. export const zeroEq = (n: number) => Math.abs(n) < 0.0001;
  15. export const numEq = (p1: number, p2: number) => zeroEq(p1 - p2);
  16. export const vEq = (v1: Pos, v2: Pos) => numEq(v1.x, v2.x) && numEq(v1.y, v2.y);
  17. export const vsBound = (positions: Pos[]) => {
  18. const box = new Box2();
  19. box.setFromPoints(positions.map(vector));
  20. return box;
  21. };
  22. /**
  23. * 获取线段方向
  24. * @param line 线段
  25. * @returns 方向
  26. */
  27. export const lineVector = (line: Pos[]) =>
  28. vector(line[1]).sub(vector(line[0])).normalize();
  29. /**
  30. * 点是否相同
  31. * @param p1 点1
  32. * @param p2 点2
  33. * @returns 是否相等
  34. */
  35. export const eqPoint = vEq;
  36. /**
  37. * 方向是否相同
  38. * @param p1 点1
  39. * @param p2 点2
  40. * @returns 是否相等
  41. */
  42. export const eqNGDire = (p1: Pos, p2: Pos) =>
  43. eqPoint(p1, p2) || eqPoint(p1, vector(p2).multiplyScalar(-1));
  44. /**
  45. * 获取两点距离
  46. * @param p1 点1
  47. * @param p2 点2
  48. * @returns 距离
  49. */
  50. export const lineLen = (p1: Pos, p2: Pos) => vector(p1).distanceTo(p2);
  51. export const vectorLen = (dire: Pos) => vector(dire).length();
  52. /**
  53. * 获取向量的垂直向量
  54. * @param dire 原方向
  55. * @returns 垂直向量
  56. */
  57. export const verticalVector = (dire: Pos) =>
  58. vector({ x: -dire.y, y: dire.x }).normalize();
  59. /**
  60. * 获取旋转指定度数后的向量
  61. * @param pos 远向量
  62. * @param angleRad 旋转角度
  63. * @returns 旋转后向量
  64. */
  65. export const rotateVector = (pos: Pos, angleRad: number) =>
  66. new Transform().rotate(angleRad).point(pos);
  67. /**
  68. * 创建线段
  69. * @param dire 向量
  70. * @param start 起始点
  71. * @param dis 长度
  72. * @returns 线段
  73. */
  74. export const getVectorLine = (
  75. dire: Pos,
  76. start: Pos = { x: 0, y: 0 },
  77. dis: number = 1
  78. ) => [start, vector(dire).multiplyScalar(dis).add(start)];
  79. /**
  80. * 获取线段的垂直方向向量
  81. * @param line 原线段
  82. * @returns 垂直向量
  83. */
  84. export const lineVerticalVector = (line: Pos[]) =>
  85. verticalVector(lineVector(line));
  86. /**
  87. * 获取向量的垂直线段
  88. * @param dire 向量
  89. * @param start 线段原点
  90. * @param len 线段长度
  91. */
  92. export const verticalVectorLine = (
  93. dire: Pos,
  94. start: Pos = { x: 0, y: 0 },
  95. len: number = 1
  96. ) => getVectorLine(verticalVector(dire), start, len);
  97. /**
  98. * 获取两向量角度(从向量a出发)
  99. * @param v1 向量a
  100. * @param v2 向量b
  101. * @returns 两向量夹角弧度, 逆时针为正,顺时针为负
  102. */
  103. export const vector2IncludedAngle = (v1: Pos, v2: Pos) => {
  104. const start = vector(v1);
  105. const end = vector(v2);
  106. const angle = start.angleTo(end);
  107. return start.cross(end) > 0 ? angle : -angle;
  108. };
  109. /**
  110. * 获取两线段角度(从线段a出发)
  111. * @param line1 线段a
  112. * @param line2 线段b
  113. * @returns 两线段夹角弧度
  114. */
  115. export const line2IncludedAngle = (line1: Pos[], line2: Pos[]) =>
  116. vector2IncludedAngle(lineVector(line1), lineVector(line2));
  117. /**
  118. * 获取向量与X正轴角度
  119. * @param v 向量
  120. * @returns 夹角弧度
  121. */
  122. const nXAxis = vector({ x: 1, y: 0 });
  123. export const vectorAngle = (v: Pos) => {
  124. const start = vector(v);
  125. return start.angleTo(nXAxis);
  126. };
  127. /**
  128. * 获取线段与方向的夹角弧度
  129. * @param line 线段
  130. * @param dire 方向
  131. * @returns 线段与方向夹角弧度
  132. */
  133. export const lineAndVectorIncludedAngle = (line: Pos[], v: Pos) =>
  134. vector2IncludedAngle(lineVector(line), v);
  135. /**
  136. * 获取线段中心点
  137. * @param line
  138. * @returns
  139. */
  140. export const lineCenter = (line: Pos[]) =>
  141. vector(line[0]).add(line[1]).multiplyScalar(0.5);
  142. export const pointsCenter = (points: Pos[]) => {
  143. if (points.length === 0) return { x: 0, y: 0 };
  144. const v = vector(points[0]);
  145. for (let i = 1; i < points.length; i++) {
  146. v.add(points[i]);
  147. }
  148. return v.multiplyScalar(1 / points.length);
  149. };
  150. export const lineJoin = (l1: Pos[], l2: Pos[]) => {
  151. const checks = [
  152. [l1[0], l2[0]],
  153. [l1[0], l2[1]],
  154. [l1[1], l2[0]],
  155. [l1[1], l2[1]],
  156. ];
  157. const ndx = checks.findIndex((line) => eqPoint(line[0], line[1]));
  158. if (~ndx) {
  159. return checks[ndx];
  160. } else {
  161. return false;
  162. }
  163. };
  164. export const isLineEqual = (l1: Pos[], l2: Pos[]) =>
  165. eqPoint(l1[0], l2[0]) && eqPoint(l1[1], l2[1]);
  166. export const isLineReverseEqual = (l1: Pos[], l2: Pos[]) =>
  167. eqPoint(l1[0], l2[1]) && eqPoint(l1[1], l2[0]);
  168. export const isLineIntersect = (l1: Pos[], l2: Pos[]) => {
  169. const s1 = l2[1].y - l2[0].y;
  170. const s2 = l2[1].x - l2[0].x;
  171. const s3 = l1[1].x - l1[0].x;
  172. const s4 = l1[1].y - l1[0].y;
  173. const s5 = l1[0].y - l2[0].y;
  174. const s6 = l1[0].x - l2[0].x;
  175. const denominator = s1 * s3 - s2 * s4;
  176. const ua = round((s2 * s5 - s1 * s6) / denominator, 6);
  177. const ub = round((s3 * s5 - s4 * s6) / denominator, 6);
  178. if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) {
  179. return true;
  180. } else {
  181. return false;
  182. }
  183. };
  184. export const vectorParallel = (dire1: Pos, dire2: Pos) =>
  185. zeroEq(vector(dire1).cross(dire2));
  186. export const lineParallelRelationship = (l1: Pos[], l2: Pos[]) => {
  187. const dire1 = lineVector(l1);
  188. const dire2 = lineVector(l2);
  189. // 计算线段的法向量
  190. const normal1 = verticalVector(dire1);
  191. const normal2 = verticalVector(dire2);
  192. const startDire = lineVector([l1[0], l2[0]]);
  193. // 计算线段的参数方程
  194. const t1 = round(normal2.dot(startDire) / normal2.dot(dire1), 6);
  195. const t2 = round(normal1.dot(startDire) / normal1.dot(dire2), 6);
  196. if (t1 === 0 && t2 === 0) {
  197. return RelationshipEnum.Overlap;
  198. }
  199. if (eqPoint(normal1, normal2) || eqPoint(normal1, normal2.clone().negate())) {
  200. return lineJoin(l1, l2)
  201. ? RelationshipEnum.Overlap
  202. : RelationshipEnum.Parallel;
  203. }
  204. };
  205. export enum RelationshipEnum {
  206. // 重叠
  207. Overlap = "Overlap",
  208. // 相交
  209. Intersect = "Intersect",
  210. // 延长相交
  211. ExtendIntersect = "ExtendIntersect",
  212. // 平行
  213. Parallel = "Parallel",
  214. // 首尾连接
  215. Join = "Join",
  216. // 一样
  217. Equal = "Equal",
  218. // 反向
  219. ReverseEqual = "ReverseEqual",
  220. }
  221. /**
  222. * 获取两线段是什么关系,(重叠、相交、平行、首尾相接等)
  223. * @param l1
  224. * @param l2
  225. * @returns RelationshipEnum
  226. */
  227. export const lineRelationship = (l1: Pos[], l2: Pos[]) => {
  228. if (isLineEqual(l1, l2)) {
  229. return RelationshipEnum.Equal;
  230. } else if (isLineReverseEqual(l1, l2)) {
  231. return RelationshipEnum.ReverseEqual;
  232. }
  233. const parallelRelationship = lineParallelRelationship(l1, l2);
  234. if (parallelRelationship) {
  235. return parallelRelationship;
  236. } else if (lineJoin(l1, l2)) {
  237. return RelationshipEnum.Join;
  238. } else if (isLineIntersect(l1, l2)) {
  239. return RelationshipEnum.Intersect; // 两线段相交
  240. } else {
  241. return RelationshipEnum.ExtendIntersect; // 延长可相交
  242. }
  243. };
  244. export const createLine = (p: Pos, v: Pos, l?: number) => {
  245. const line = [p];
  246. if (l) {
  247. v = vector(v).multiplyScalar(l);
  248. }
  249. line[1] = vector(line[0]).add(v);
  250. return line;
  251. };
  252. /**
  253. * 获取两线段交点,可延长相交
  254. * @param l1 线段1
  255. * @param l2 线段2
  256. * @returns 交点坐标
  257. */
  258. export const lineIntersection = (l1: Pos[], l2: Pos[]) => {
  259. // 定义两条线段的起点和终点坐标
  260. const [line1Start, line1End] = lVector(l1);
  261. const [line2Start, line2End] = lVector(l2);
  262. // 计算线段的方向向量
  263. const dir1 = line1End.clone().sub(line1Start);
  264. const dir2 = line2End.clone().sub(line2Start);
  265. // 计算参数方程中的系数
  266. const a = dir1.x;
  267. const b = -dir2.x;
  268. const c = dir1.y;
  269. const d = -dir2.y;
  270. const e = line2Start.x - line1Start.x;
  271. const f = line2Start.y - line1Start.y;
  272. // 求解参数t和s
  273. const t = (d * e - b * f) / (a * d - b * c);
  274. // 计算交点坐标
  275. const p = line1Start.clone().add(dir1.clone().multiplyScalar(t));
  276. if (isNaN(p.x) || !isFinite(p.x) || isNaN(p.y) || !isFinite(p.y)) return null;
  277. return p;
  278. };
  279. /**
  280. * 获取点是否在线上
  281. * @param line 线段
  282. * @param position 点
  283. */
  284. export const lineInner = (line: Pos[], position: Pos) => {
  285. // 定义线段的起点和终点坐标
  286. const [A, B] = lVector(line);
  287. // 定义一个点的坐标
  288. const P = vector(position);
  289. // 计算向量 AP 和 AB
  290. const AP = P.clone().sub(A);
  291. const AB = B.clone().sub(A);
  292. // 计算叉积
  293. const crossProduct = AP.x * AB.y - AP.y * AB.x;
  294. // 如果叉积不为 0,说明点 P 不在直线 AB 上
  295. if (!zeroEq(crossProduct)) {
  296. return false;
  297. }
  298. // 检查点 P 的坐标是否在 A 和 B 的坐标范围内
  299. return (
  300. Math.min(A.x, B.x) <= P.x &&
  301. P.x <= Math.max(A.x, B.x) &&
  302. Math.min(A.y, B.y) <= P.y &&
  303. P.y <= Math.max(A.y, B.y)
  304. );
  305. };
  306. /**
  307. * 获取点在线段上的投影
  308. * @param line 线段
  309. * @param position 点
  310. * @returns 投影信息
  311. */
  312. export const linePointProjection = (line: Pos[], position: Pos) => {
  313. // 定义线段的起点和终点坐标
  314. const [lineStart, lineEnd] = lVector(line);
  315. // 定义一个点的坐标
  316. const point = vector(position);
  317. // 计算线段的方向向量
  318. const lineDir = lineEnd.clone().sub(lineStart);
  319. // 计算点到线段起点的向量
  320. const pointToLineStart = point.clone().sub(lineStart);
  321. // 计算点在线段方向上的投影长度
  322. const t = pointToLineStart.dot(lineDir.normalize());
  323. // 计算投影点的坐标
  324. return lineStart.add(lineDir.multiplyScalar(t));
  325. };
  326. /**
  327. * 获取点距离线段最近距离
  328. * @param line 直线
  329. * @param position 参考点
  330. * @returns 距离
  331. */
  332. export const linePointLen = (line: Pos[], position: Pos) =>
  333. lineLen(position, linePointProjection(line, position));
  334. /**
  335. * 计算多边形是否为逆时针
  336. * @param points 多边形顶点
  337. * @returns true | false
  338. */
  339. export const isPolygonCounterclockwise = (points: Pos[]) =>
  340. ShapeUtils.isClockWise(points.map(vector));
  341. /**
  342. * 切割线段,返回连段切割点
  343. * @param line 线段
  344. * @param amount 切割份量
  345. * @param unit 一份单位大小
  346. * @returns 点数组
  347. */
  348. export const lineSlice = (
  349. line: Pos[],
  350. amount: number,
  351. unit = lineLen(line[0], line[1]) / amount
  352. ) =>
  353. new Array(unit)
  354. .fill(0)
  355. .map((_, i) => linePointProjection(line, { x: i * unit, y: i * unit }));
  356. /**
  357. * 线段是否相交多边形
  358. * @param polygon 多边形
  359. * @param line 检测线段
  360. * @returns
  361. */
  362. export const isPolygonLineIntersect = (polygon: Pos[], line: Pos[]) => {
  363. for (let i = 0; i < polygon.length; i++) {
  364. if (isLineIntersect([polygon[i], polygon[i + 1]], line)) {
  365. return true;
  366. }
  367. }
  368. return false;
  369. };
  370. /**
  371. * 通过角度和两个点获取两者的连接点,
  372. * @param p1
  373. * @param p2
  374. * @param rad
  375. */
  376. export const joinPoint = (p1: Pos, p2: Pos, rad: number) => {
  377. const lvector = new Vector2()
  378. .subVectors(p1, p2)
  379. .rotateAround({ x: 0, y: 0 }, rad);
  380. return vector(p2).add(lvector);
  381. };
  382. /**
  383. * 要缩放多少才能到达目标
  384. * @param origin 缩放原点
  385. * @param scaleDirection 缩放方向
  386. * @param p1 当前点位
  387. * @param p2 目标点位
  388. * @returns
  389. */
  390. export function calculateScaleFactor(
  391. origin: Pos,
  392. scaleDirection: Pos,
  393. p1: Pos,
  394. p2: Pos
  395. ) {
  396. const op1 = vector(p1).sub(origin);
  397. const op2 = vector(p2).sub(origin);
  398. const xZero = zeroEq(op1.x);
  399. const yZero = zeroEq(op1.y);
  400. if (zeroEq(op1.x) || zeroEq(op2.y)) return;
  401. if (zeroEq(scaleDirection.x)) {
  402. if (zeroEq(p2.x - p1.x)) {
  403. return zeroEq(op1.y - op2.y) ? 1 : yZero ? null : op2.y / op1.y;
  404. } else {
  405. return;
  406. }
  407. }
  408. if (zeroEq(scaleDirection.y)) {
  409. if (zeroEq(p2.y - p1.y)) {
  410. return zeroEq(op1.x - op2.x) ? 1 : xZero ? null : op2.x / op1.x;
  411. } else {
  412. return;
  413. }
  414. }
  415. if (xZero && yZero) {
  416. return null;
  417. }
  418. const xScaleFactor = op2.x / (op1.x * scaleDirection.x);
  419. const yScaleFactor = op2.y / (op1.y * scaleDirection.y);
  420. if (xZero) {
  421. return yScaleFactor;
  422. } else if (yZero) {
  423. return xScaleFactor;
  424. }
  425. if (zeroEq(xScaleFactor - yScaleFactor)) {
  426. return xScaleFactor;
  427. }
  428. }
  429. // 获取两线段的矩阵关系
  430. export const getLineRelationMat = (l1: [Pos, Pos], l2: [Pos, Pos]) => {
  431. // 提取点
  432. const P1 = l1[0]; // l1 的起点
  433. const P1End = l1[1]; // l1 的终点
  434. const P2 = l2[0]; // l2 的起点
  435. const P2End = l2[1]; // l2 的终点
  436. // 计算方向向量
  437. const d1 = { x: P1End.x - P1.x, y: P1End.y - P1.y };
  438. const d2 = { x: P2End.x - P2.x, y: P2End.y - P2.y };
  439. // 计算方向向量的长度
  440. const lengthD1 = Math.sqrt(d1.x ** 2 + d1.y ** 2);
  441. const lengthD2 = Math.sqrt(d2.x ** 2 + d2.y ** 2);
  442. if (lengthD1 === 0 || lengthD2 === 0) return new Transform();
  443. // 归一化方向向量
  444. const unitD1 = { x: d1.x / lengthD1, y: d1.y / lengthD1 };
  445. const unitD2 = { x: d2.x / lengthD2, y: d2.y / lengthD2 };
  446. // 计算旋转角度
  447. const angle = Math.atan2(unitD2.y, unitD2.x) - Math.atan2(unitD1.y, unitD1.x);
  448. // 计算旋转矩阵
  449. // 计算缩放因子
  450. const scale = lengthD2 / lengthD1;
  451. // 计算平移向量
  452. const translation = [P2.x - P1.x, P2.y - P1.y];
  453. const mat = new Transform()
  454. .translate(translation[0], translation[1])
  455. .translate(P1.x, P1.y)
  456. .scale(scale, scale)
  457. .rotate(angle)
  458. .translate(-P1.x, -P1.y);
  459. if (!eqPoint(mat.point(P1), P2)) {
  460. console.error('对准不正确 旋转后P1', mat.point(P1), P2)
  461. }
  462. if (!eqPoint(mat.point(P1End), P2End)) {
  463. console.error('对准不正确 旋转后P2', mat.point(P1End), P1End)
  464. }
  465. return mat
  466. };