math.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  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(v2.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. const epsilon = 1e-6; // 误差范围
  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. /**
  111. * 获取两线段角度(从线段a出发)
  112. * @param line1 线段a
  113. * @param line2 线段b
  114. * @returns 两线段夹角弧度
  115. */
  116. export const line2IncludedAngle = (line1: Pos[], line2: Pos[]) =>
  117. vector2IncludedAngle(lineVector(line1), lineVector(line2));
  118. /**
  119. * 获取向量与X正轴角度
  120. * @param v 向量
  121. * @returns 夹角弧度
  122. */
  123. const nXAxis = vector({ x: 1, y: 0 });
  124. export const vectorAngle = (v: Pos) => {
  125. const start = vector(v);
  126. return start.angleTo(nXAxis);
  127. };
  128. /**
  129. * 获取线段与方向的夹角弧度
  130. * @param line 线段
  131. * @param dire 方向
  132. * @returns 线段与方向夹角弧度
  133. */
  134. export const lineAndVectorIncludedAngle = (line: Pos[], v: Pos) =>
  135. vector2IncludedAngle(lineVector(line), v);
  136. /**
  137. * 获取线段中心点
  138. * @param line
  139. * @returns
  140. */
  141. export const lineCenter = (line: Pos[]) =>
  142. vector(line[0]).add(line[1]).multiplyScalar(0.5);
  143. export const pointsCenter = (points: Pos[]) => {
  144. if (points.length === 0) return { x: 0, y: 0 };
  145. const v = vector(points[0]);
  146. for (let i = 1; i < points.length; i++) {
  147. v.add(points[i]);
  148. }
  149. return v.multiplyScalar(1 / points.length);
  150. };
  151. export const lineJoin = (l1: Pos[], l2: Pos[]) => {
  152. const checks = [
  153. [l1[0], l2[0]],
  154. [l1[0], l2[1]],
  155. [l1[1], l2[0]],
  156. [l1[1], l2[1]],
  157. ];
  158. const ndx = checks.findIndex((line) => eqPoint(line[0], line[1]));
  159. if (~ndx) {
  160. return checks[ndx];
  161. } else {
  162. return false;
  163. }
  164. };
  165. export const isLineEqual = (l1: Pos[], l2: Pos[]) =>
  166. eqPoint(l1[0], l2[0]) && eqPoint(l1[1], l2[1]);
  167. export const isLineReverseEqual = (l1: Pos[], l2: Pos[]) =>
  168. eqPoint(l1[0], l2[1]) && eqPoint(l1[1], l2[0]);
  169. export const isLineIntersect = (l1: Pos[], l2: Pos[]) => {
  170. const s1 = l2[1].y - l2[0].y;
  171. const s2 = l2[1].x - l2[0].x;
  172. const s3 = l1[1].x - l1[0].x;
  173. const s4 = l1[1].y - l1[0].y;
  174. const s5 = l1[0].y - l2[0].y;
  175. const s6 = l1[0].x - l2[0].x;
  176. const denominator = s1 * s3 - s2 * s4;
  177. const ua = round((s2 * s5 - s1 * s6) / denominator, 6);
  178. const ub = round((s3 * s5 - s4 * s6) / denominator, 6);
  179. if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) {
  180. return true;
  181. } else {
  182. return false;
  183. }
  184. };
  185. export const vectorParallel = (dire1: Pos, dire2: Pos) =>
  186. zeroEq(vector(dire1).cross(dire2));
  187. export const lineParallelRelationship = (l1: Pos[], l2: Pos[]) => {
  188. const dire1 = lineVector(l1);
  189. const dire2 = lineVector(l2);
  190. // 计算线段的法向量
  191. const normal1 = verticalVector(dire1);
  192. const normal2 = verticalVector(dire2);
  193. const startDire = lineVector([l1[0], l2[0]]);
  194. // 计算线段的参数方程
  195. const t1 = round(normal2.dot(startDire) / normal2.dot(dire1), 6);
  196. const t2 = round(normal1.dot(startDire) / normal1.dot(dire2), 6);
  197. if (t1 === 0 && t2 === 0) {
  198. return RelationshipEnum.Overlap;
  199. }
  200. if (eqPoint(normal1, normal2) || eqPoint(normal1, normal2.clone().negate())) {
  201. return lineJoin(l1, l2)
  202. ? RelationshipEnum.Overlap
  203. : RelationshipEnum.Parallel;
  204. }
  205. };
  206. export enum RelationshipEnum {
  207. // 重叠
  208. Overlap = "Overlap",
  209. // 相交
  210. Intersect = "Intersect",
  211. // 延长相交
  212. ExtendIntersect = "ExtendIntersect",
  213. // 平行
  214. Parallel = "Parallel",
  215. // 首尾连接
  216. Join = "Join",
  217. // 一样
  218. Equal = "Equal",
  219. // 反向
  220. ReverseEqual = "ReverseEqual",
  221. }
  222. /**
  223. * 获取两线段是什么关系,(重叠、相交、平行、首尾相接等)
  224. * @param l1
  225. * @param l2
  226. * @returns RelationshipEnum
  227. */
  228. export const lineRelationship = (l1: Pos[], l2: Pos[]) => {
  229. if (isLineEqual(l1, l2)) {
  230. return RelationshipEnum.Equal;
  231. } else if (isLineReverseEqual(l1, l2)) {
  232. return RelationshipEnum.ReverseEqual;
  233. }
  234. const parallelRelationship = lineParallelRelationship(l1, l2);
  235. if (parallelRelationship) {
  236. return parallelRelationship;
  237. } else if (lineJoin(l1, l2)) {
  238. return RelationshipEnum.Join;
  239. } else if (isLineIntersect(l1, l2)) {
  240. return RelationshipEnum.Intersect; // 两线段相交
  241. } else {
  242. return RelationshipEnum.ExtendIntersect; // 延长可相交
  243. }
  244. };
  245. export const createLine = (p: Pos, v: Pos, l?: number) => {
  246. const line = [p]
  247. if (l) {
  248. v = vector(v).multiplyScalar(l)
  249. }
  250. line[1] = vector(line[0]).add(v)
  251. return line
  252. }
  253. /**
  254. * 获取两线段交点,可延长相交
  255. * @param l1 线段1
  256. * @param l2 线段2
  257. * @returns 交点坐标
  258. */
  259. export const lineIntersection = (l1: Pos[], l2: Pos[]) => {
  260. // 定义两条线段的起点和终点坐标
  261. const [line1Start, line1End] = lVector(l1);
  262. const [line2Start, line2End] = lVector(l2);
  263. // 计算线段的方向向量
  264. const dir1 = line1End.clone().sub(line1Start);
  265. const dir2 = line2End.clone().sub(line2Start);
  266. // 计算参数方程中的系数
  267. const a = dir1.x;
  268. const b = -dir2.x;
  269. const c = dir1.y;
  270. const d = -dir2.y;
  271. const e = line2Start.x - line1Start.x;
  272. const f = line2Start.y - line1Start.y;
  273. // 求解参数t和s
  274. const t = (d * e - b * f) / (a * d - b * c);
  275. // 计算交点坐标
  276. const p = line1Start.clone().add(dir1.clone().multiplyScalar(t));
  277. if (isNaN(p.x) || !isFinite(p.x) || isNaN(p.y) || !isFinite(p.y)) return null;
  278. return p;
  279. };
  280. /**
  281. * 获取点在线段上的投影
  282. * @param line 线段
  283. * @param position 点
  284. * @returns 投影信息
  285. */
  286. export const linePointProjection = (line: Pos[], position: Pos) => {
  287. // 定义线段的起点和终点坐标
  288. const [lineStart, lineEnd] = lVector(line);
  289. // 定义一个点的坐标
  290. const point = vector(position);
  291. // 计算线段的方向向量
  292. const lineDir = lineEnd.clone().sub(lineStart);
  293. // 计算点到线段起点的向量
  294. const pointToLineStart = point.clone().sub(lineStart);
  295. // 计算点在线段方向上的投影长度
  296. const t = pointToLineStart.dot(lineDir.normalize());
  297. // 计算投影点的坐标
  298. return lineStart.add(lineDir.multiplyScalar(t));
  299. };
  300. /**
  301. * 获取点距离线段最近距离
  302. * @param line 直线
  303. * @param position 参考点
  304. * @returns 距离
  305. */
  306. export const linePointLen = (line: Pos[], position: Pos) =>
  307. lineLen(position, linePointProjection(line, position));
  308. /**
  309. * 计算多边形是否为逆时针
  310. * @param points 多边形顶点
  311. * @returns true | false
  312. */
  313. export const isPolygonCounterclockwise = (points: Pos[]) =>
  314. ShapeUtils.isClockWise(points.map(vector));
  315. /**
  316. * 切割线段,返回连段切割点
  317. * @param line 线段
  318. * @param amount 切割份量
  319. * @param unit 一份单位大小
  320. * @returns 点数组
  321. */
  322. export const lineSlice = (
  323. line: Pos[],
  324. amount: number,
  325. unit = lineLen(line[0], line[1]) / amount
  326. ) =>
  327. new Array(unit)
  328. .fill(0)
  329. .map((_, i) => linePointProjection(line, { x: i * unit, y: i * unit }));
  330. /**
  331. * 线段是否相交多边形
  332. * @param polygon 多边形
  333. * @param line 检测线段
  334. * @returns
  335. */
  336. export const isPolygonLineIntersect = (polygon: Pos[], line: Pos[]) => {
  337. for (let i = 0; i < polygon.length; i++) {
  338. if (isLineIntersect([polygon[i], polygon[i + 1]], line)) {
  339. return true;
  340. }
  341. }
  342. return false;
  343. };
  344. /**
  345. * 通过角度和两个点获取两者的连接点,
  346. * @param p1
  347. * @param p2
  348. * @param rad
  349. */
  350. export const joinPoint = (p1: Pos, p2: Pos, rad: number) => {
  351. const lvector = new Vector2()
  352. .subVectors(p1, p2)
  353. .rotateAround({ x: 0, y: 0 }, rad);
  354. return vector(p2).add(lvector);
  355. };
  356. /**
  357. * 要缩放多少才能到达目标
  358. * @param origin 缩放原点
  359. * @param scaleDirection 缩放方向
  360. * @param p1 当前点位
  361. * @param p2 目标点位
  362. * @returns
  363. */
  364. export function calculateScaleFactor(
  365. origin: Pos,
  366. scaleDirection: Pos,
  367. p1: Pos,
  368. p2: Pos
  369. ) {
  370. const op1 = vector(p1).sub(origin);
  371. const op2 = vector(p2).sub(origin);
  372. const xZero = zeroEq(op1.x)
  373. const yZero = zeroEq(op1.y)
  374. if (zeroEq(op1.x) || zeroEq(op2.y)) return;
  375. if (zeroEq(scaleDirection.x)) {
  376. if (zeroEq(p2.x - p1.x)) {
  377. return zeroEq(op1.y - op2.y) ? 1 : yZero ? null : op2.y / op1.y;
  378. } else {
  379. return;
  380. }
  381. }
  382. if (zeroEq(scaleDirection.y)) {
  383. if (zeroEq(p2.y - p1.y)) {
  384. return zeroEq(op1.x - op2.x) ? 1 : xZero ? null : op2.x / op1.x;
  385. } else {
  386. return;
  387. }
  388. }
  389. if (xZero && yZero) {
  390. return null;
  391. }
  392. const xScaleFactor = op2.x / (op1.x * scaleDirection.x);
  393. const yScaleFactor = op2.y / (op1.y * scaleDirection.y);
  394. if (xZero) {
  395. return yScaleFactor
  396. } else if (yZero) {
  397. return xScaleFactor
  398. }
  399. console.log(xScaleFactor - yScaleFactor)
  400. if (zeroEq(xScaleFactor - yScaleFactor)) {
  401. return xScaleFactor;
  402. }
  403. }