math.ts 14 KB

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