math.ts 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870
  1. import { Vector2, ShapeUtils, Box2 } from "three";
  2. import { Transform } from "konva/lib/Util";
  3. import { rangMod, round } from "./shared.ts";
  4. import { IRect } from "konva/lib/types";
  5. export type Pos = { x: number; y: number };
  6. export type Size = { width: number; height: number };
  7. export const vector = (pos: Pos = { x: 0, y: 0 }): Vector2 => {
  8. return new Vector2(pos.x, pos.y);
  9. // if (pos instanceof Vector2) {
  10. // return pos;
  11. // } else {
  12. // return new Vector2(pos.x, pos.y);
  13. // }
  14. };
  15. export const lVector = (line: Pos[]) => line.map(vector);
  16. export const zeroEq = (n: number) => Math.abs(n) < 0.0001;
  17. export const numEq = (p1: number, p2: number) => zeroEq(p1 - p2);
  18. export const vEq = (v1: Pos, v2: Pos) => numEq(v1.x, v2.x) && numEq(v1.y, v2.y);
  19. export const vsBound = (positions: Pos[]) => {
  20. const box = new Box2();
  21. box.setFromPoints(positions.map(vector));
  22. return box;
  23. };
  24. /**
  25. * 获取线段方向
  26. * @param line 线段
  27. * @returns 方向
  28. */
  29. export const lineVector = (line: Pos[]) =>
  30. vector(line[1]).sub(vector(line[0])).normalize();
  31. /**
  32. * 点是否相同
  33. * @param p1 点1
  34. * @param p2 点2
  35. * @returns 是否相等
  36. */
  37. export const eqPoint = vEq;
  38. /**
  39. * 方向是否相同
  40. * @param p1 点1
  41. * @param p2 点2
  42. * @returns 是否相等
  43. */
  44. export const eqNGDire = (p1: Pos, p2: Pos) =>
  45. eqPoint(p1, p2) || eqPoint(p1, vector(p2).multiplyScalar(-1));
  46. /**
  47. * 获取两点距离
  48. * @param p1 点1
  49. * @param p2 点2
  50. * @returns 距离
  51. */
  52. export const lineLen = (p1: Pos, p2: Pos) => vector(p1).distanceTo(p2);
  53. export const vectorLen = (dire: Pos) => vector(dire).length();
  54. /**
  55. * 获取向量的垂直向量
  56. * @param dire 原方向
  57. * @returns 垂直向量
  58. */
  59. export const verticalVector = (dire: Pos) =>
  60. vector({ x: -dire.y, y: dire.x }).normalize();
  61. /**
  62. * 获取旋转指定度数后的向量
  63. * @param pos 远向量
  64. * @param angleRad 旋转角度
  65. * @returns 旋转后向量
  66. */
  67. export const rotateVector = (pos: Pos, angleRad: number) =>
  68. new Transform().rotate(angleRad).point(pos);
  69. /**
  70. * 创建线段
  71. * @param dire 向量
  72. * @param start 起始点
  73. * @param dis 长度
  74. * @returns 线段
  75. */
  76. export const getVectorLine = (
  77. dire: Pos,
  78. start: Pos = { x: 0, y: 0 },
  79. dis: number = 1
  80. ) => [start, vector(dire).multiplyScalar(dis).add(start)];
  81. /**
  82. * 获取线段的垂直方向向量
  83. * @param line 原线段
  84. * @returns 垂直向量
  85. */
  86. export const lineVerticalVector = (line: Pos[]) =>
  87. verticalVector(lineVector(line));
  88. /**
  89. * 获取向量的垂直线段
  90. * @param dire 向量
  91. * @param start 线段原点
  92. * @param len 线段长度
  93. */
  94. export const verticalVectorLine = (
  95. dire: Pos,
  96. start: Pos = { x: 0, y: 0 },
  97. len: number = 1
  98. ) => getVectorLine(verticalVector(dire), start, len);
  99. /**
  100. * 获取两向量角度(从向量a出发)
  101. * @param v1 向量a
  102. * @param v2 向量b
  103. * @returns 两向量夹角弧度, 逆时针为正,顺时针为负
  104. */
  105. export const vector2IncludedAngle = (v1: Pos, v2: Pos) => {
  106. const start = vector(v1);
  107. const end = vector(v2);
  108. const angle = start.angleTo(end);
  109. return start.cross(end) > 0 ? angle : -angle;
  110. };
  111. // 判断多边形方向(Shoelace Formula)
  112. export function getPolygonDirection(points: Pos[]) {
  113. let area = 0;
  114. const numPoints = points.length;
  115. for (let i = 0; i < numPoints; i++) {
  116. const p1 = points[i];
  117. const p2 = points[(i + 1) % numPoints];
  118. area += (p2.x - p1.x) * (p2.y + p1.y);
  119. }
  120. // 如果面积为正,是逆时针;否则是顺时针
  121. return area;
  122. }
  123. /**
  124. * 获取两线段角度(从线段a出发)
  125. * @param line1 线段a
  126. * @param line2 线段b
  127. * @returns 两线段夹角弧度
  128. */
  129. export const line2IncludedAngle = (line1: Pos[], line2: Pos[]) =>
  130. vector2IncludedAngle(lineVector(line1), lineVector(line2));
  131. /**
  132. * 获取向量与X正轴角度
  133. * @param v 向量
  134. * @returns 夹角弧度
  135. */
  136. const nXAxis = vector({ x: 1, y: 0 });
  137. export const vectorAngle = (v: Pos) => {
  138. const start = vector(v);
  139. return start.angleTo(nXAxis);
  140. };
  141. /**
  142. * 获取线段与方向的夹角弧度
  143. * @param line 线段
  144. * @param dire 方向
  145. * @returns 线段与方向夹角弧度
  146. */
  147. export const lineAndVectorIncludedAngle = (line: Pos[], v: Pos) =>
  148. vector2IncludedAngle(lineVector(line), v);
  149. /**
  150. * 获取线段中心点
  151. * @param line
  152. * @returns
  153. */
  154. export const lineCenter = (line: Pos[]) => {
  155. const start = vector(line[0]);
  156. for (let i = 1; i < line.length; i++) {
  157. start.add(line[i]);
  158. }
  159. return start.multiplyScalar(1 / line.length);
  160. };
  161. export const lineSpeed = (line: Pos[], step: number) => {
  162. const p = vector(line[0]);
  163. const v = vector(line[1]).sub(line[0]);
  164. return p.add(v.multiplyScalar(step));
  165. };
  166. export const pointsCenter = (points: Pos[]) => {
  167. if (points.length === 0) return { x: 0, y: 0 };
  168. const v = vector(points[0]);
  169. for (let i = 1; i < points.length; i++) {
  170. v.add(points[i]);
  171. }
  172. return v.multiplyScalar(1 / points.length);
  173. };
  174. export const lineJoin = (l1: Pos[], l2: Pos[]) => {
  175. const checks = [
  176. [l1[0], l2[0]],
  177. [l1[0], l2[1]],
  178. [l1[1], l2[0]],
  179. [l1[1], l2[1]],
  180. ];
  181. const ndx = checks.findIndex((line) => eqPoint(line[0], line[1]));
  182. if (~ndx) {
  183. return checks[ndx];
  184. } else {
  185. return false;
  186. }
  187. };
  188. export const isLineEqual = (l1: Pos[], l2: Pos[]) =>
  189. eqPoint(l1[0], l2[0]) && eqPoint(l1[1], l2[1]);
  190. export const isLineReverseEqual = (l1: Pos[], l2: Pos[]) =>
  191. eqPoint(l1[0], l2[1]) && eqPoint(l1[1], l2[0]);
  192. export const isLineIntersect = (l1: Pos[], l2: Pos[]) => {
  193. const s1 = l2[1].y - l2[0].y;
  194. const s2 = l2[1].x - l2[0].x;
  195. const s3 = l1[1].x - l1[0].x;
  196. const s4 = l1[1].y - l1[0].y;
  197. const s5 = l1[0].y - l2[0].y;
  198. const s6 = l1[0].x - l2[0].x;
  199. const denominator = s1 * s3 - s2 * s4;
  200. const ua = round((s2 * s5 - s1 * s6) / denominator, 6);
  201. const ub = round((s3 * s5 - s4 * s6) / denominator, 6);
  202. if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) {
  203. return true;
  204. } else {
  205. return false;
  206. }
  207. };
  208. export const vectorParallel = (dire1: Pos, dire2: Pos) =>
  209. zeroEq(vector(dire1).cross(dire2));
  210. export const lineParallelRelationship = (l1: Pos[], l2: Pos[]) => {
  211. const dire1 = lineVector(l1);
  212. const dire2 = lineVector(l2);
  213. // 计算线段的法向量
  214. const normal1 = verticalVector(dire1);
  215. const normal2 = verticalVector(dire2);
  216. const startDire = lineVector([l1[0], l2[0]]);
  217. // 计算线段的参数方程
  218. const t1 = round(normal2.dot(startDire) / normal2.dot(dire1), 6);
  219. const t2 = round(normal1.dot(startDire) / normal1.dot(dire2), 6);
  220. if (t1 === 0 && t2 === 0) {
  221. return RelationshipEnum.Overlap;
  222. }
  223. if (eqPoint(normal1, normal2) || eqPoint(normal1, normal2.clone().negate())) {
  224. return lineJoin(l1, l2)
  225. ? RelationshipEnum.Overlap
  226. : RelationshipEnum.Parallel;
  227. }
  228. };
  229. export enum RelationshipEnum {
  230. // 重叠
  231. Overlap = "Overlap",
  232. // 相交
  233. Intersect = "Intersect",
  234. // 延长相交
  235. ExtendIntersect = "ExtendIntersect",
  236. // 平行
  237. Parallel = "Parallel",
  238. // 首尾连接
  239. Join = "Join",
  240. // 一样
  241. Equal = "Equal",
  242. // 反向
  243. ReverseEqual = "ReverseEqual",
  244. }
  245. /**
  246. * 获取两线段是什么关系,(重叠、相交、平行、首尾相接等)
  247. * @param l1
  248. * @param l2
  249. * @returns RelationshipEnum
  250. */
  251. export const lineRelationship = (l1: Pos[], l2: Pos[]) => {
  252. if (isLineEqual(l1, l2)) {
  253. return RelationshipEnum.Equal;
  254. } else if (isLineReverseEqual(l1, l2)) {
  255. return RelationshipEnum.ReverseEqual;
  256. }
  257. const parallelRelationship = lineParallelRelationship(l1, l2);
  258. if (parallelRelationship) {
  259. return parallelRelationship;
  260. } else if (lineJoin(l1, l2)) {
  261. return RelationshipEnum.Join;
  262. } else if (isLineIntersect(l1, l2)) {
  263. return RelationshipEnum.Intersect; // 两线段相交
  264. } else {
  265. return RelationshipEnum.ExtendIntersect; // 延长可相交
  266. }
  267. };
  268. export const createLine = (p: Pos, v: Pos, l?: number) => {
  269. const line = [p];
  270. if (l) {
  271. v = vector(v).multiplyScalar(l);
  272. }
  273. line[1] = vector(line[0]).add(v);
  274. return line;
  275. };
  276. /**
  277. * 获取两线段交点,可延长相交
  278. * @param l1 线段1
  279. * @param l2 线段2
  280. * @returns 交点坐标
  281. */
  282. export const lineIntersection = (l1: Pos[], l2: Pos[]) => {
  283. // 定义两条线段的起点和终点坐标
  284. const [line1Start, line1End] = lVector(l1);
  285. const [line2Start, line2End] = lVector(l2);
  286. // 计算线段的方向向量
  287. const dir1 = line1End.clone().sub(line1Start);
  288. const dir2 = line2End.clone().sub(line2Start);
  289. // 计算参数方程中的系数
  290. const a = dir1.x;
  291. const b = -dir2.x;
  292. const c = dir1.y;
  293. const d = -dir2.y;
  294. const e = line2Start.x - line1Start.x;
  295. const f = line2Start.y - line1Start.y;
  296. // 求解参数t和s
  297. const t = (d * e - b * f) / (a * d - b * c);
  298. // 计算交点坐标
  299. const p = line1Start.clone().add(dir1.clone().multiplyScalar(t));
  300. if (isNaN(p.x) || !isFinite(p.x) || isNaN(p.y) || !isFinite(p.y)) return null;
  301. return p;
  302. };
  303. /**
  304. * 获取点是否在线上
  305. * @param line 线段
  306. * @param position 点
  307. */
  308. export const lineInner = (line: Pos[], position: Pos) => {
  309. // 定义线段的起点和终点坐标
  310. const [A, B] = lVector(line);
  311. // 定义一个点的坐标
  312. const P = vector(position);
  313. // 计算向量 AP 和 AB
  314. const AP = P.clone().sub(A);
  315. const AB = B.clone().sub(A);
  316. // 计算叉积
  317. const crossProduct = AP.x * AB.y - AP.y * AB.x;
  318. // 如果叉积不为 0,说明点 P 不在直线 AB 上
  319. if (!zeroEq(crossProduct)) {
  320. return false;
  321. }
  322. // 检查点 P 的坐标是否在 A 和 B 的坐标范围内
  323. const minX = Math.min(A.x, B.x);
  324. const maxX = Math.max(A.x, B.x);
  325. const minY = Math.min(A.y, B.y);
  326. const maxY = Math.max(A.y, B.y);
  327. return (
  328. (minX < P.x || numEq(minX, P.x)) &&
  329. (P.x < maxX || numEq(maxX, P.x)) &&
  330. (minY < P.y || numEq(minY, P.y)) &&
  331. (P.y < maxY || numEq(maxY, P.y))
  332. );
  333. };
  334. /**
  335. * 获取点在线段上的投影
  336. * @param line 线段
  337. * @param position 点
  338. * @returns 投影信息
  339. */
  340. export const linePointProjection = (line: Pos[], position: Pos) => {
  341. // 定义线段的起点和终点坐标
  342. const [lineStart, lineEnd] = lVector(line);
  343. // 定义一个点的坐标
  344. const point = vector(position);
  345. // 计算线段的方向向量
  346. const lineDir = lineEnd.clone().sub(lineStart);
  347. // 计算点到线段起点的向量
  348. const pointToLineStart = point.clone().sub(lineStart);
  349. // 计算点在线段方向上的投影长度
  350. const t = pointToLineStart.dot(lineDir.normalize());
  351. // 计算投影点的坐标
  352. return lineStart.add(lineDir.multiplyScalar(t));
  353. };
  354. /**
  355. * 获取点距离线段最近距离
  356. * @param line 直线
  357. * @param position 参考点
  358. * @returns 距离
  359. */
  360. export const linePointLen = (line: Pos[], position: Pos) =>
  361. lineLen(position, linePointProjection(line, position));
  362. /**
  363. * 计算多边形是否为逆时针
  364. * @param points 多边形顶点
  365. * @returns true | false
  366. */
  367. export const isPolygonCounterclockwise = (points: Pos[]) =>
  368. ShapeUtils.isClockWise(points.map(vector));
  369. /**
  370. * 切割线段,返回连段切割点
  371. * @param line 线段
  372. * @param amount 切割份量
  373. * @param unit 一份单位大小
  374. * @returns 点数组
  375. */
  376. export const lineSlice = (
  377. line: Pos[],
  378. amount: number,
  379. unit = lineLen(line[0], line[1]) / amount
  380. ) =>
  381. new Array(unit)
  382. .fill(0)
  383. .map((_, i) => linePointProjection(line, { x: i * unit, y: i * unit }));
  384. /**
  385. * 线段是否相交多边形
  386. * @param polygon 多边形
  387. * @param line 检测线段
  388. * @returns
  389. */
  390. export const isPolygonLineIntersect = (polygon: Pos[], line: Pos[]) => {
  391. for (let i = 0; i < polygon.length; i++) {
  392. if (isLineIntersect([polygon[i], polygon[i + 1]], line)) {
  393. return true;
  394. }
  395. }
  396. return false;
  397. };
  398. /**
  399. * 线段与多边形交点
  400. * @param polygon 多边形
  401. * @param line 检测线段
  402. * @returns
  403. */
  404. export const polygonLineIntersect = (polygon: Pos[], line: Pos[]) => {
  405. const ps: Pos[] =[]
  406. for (let i = 0; i < polygon.length; i++) {
  407. const line2 = [polygon[i], polygon[(i + 1) % polygon.length]]
  408. const p = lineIntersection(line2, line)
  409. if (p && lineInner(line, p) && lineInner(line2, p)) {
  410. ps.push(p)
  411. }
  412. }
  413. return ps
  414. };
  415. /**
  416. * 判断点是否在多边形内部
  417. * @param polygon 多边形顶点数组,按顺时针或逆时针顺序排列
  418. * @param pos 要判断的点
  419. * @returns 点在多边形内返回true,否则返回false
  420. */
  421. export const isPolygonPointInner = (polygon: Pos[], pos: Pos): boolean => {
  422. // 如果多边形少于3个点,直接返回false
  423. if (polygon.length < 3) return false;
  424. let inside = false;
  425. const x = pos.x;
  426. const y = pos.y;
  427. // 使用射线法判断
  428. for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
  429. const xi = polygon[i].x;
  430. const yi = polygon[i].y;
  431. const xj = polygon[j].x;
  432. const yj = polygon[j].y;
  433. // 检查点是否在多边形的顶点上
  434. if ((numEq(xi, x) && numEq(yi, y)) || (numEq(xj, x) && numEq(yj, y))) {
  435. return true;
  436. }
  437. // 检查点是否在边的水平射线上
  438. const intersect =
  439. yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;
  440. if (intersect) {
  441. inside = !inside;
  442. }
  443. }
  444. return inside;
  445. };
  446. /**
  447. * 通过角度和两个点获取两者的连接点,
  448. * @param p1
  449. * @param p2
  450. * @param rad
  451. */
  452. export const joinPoint = (p1: Pos, p2: Pos, rad: number) => {
  453. const lvector = new Vector2()
  454. .subVectors(p1, p2)
  455. .rotateAround({ x: 0, y: 0 }, rad);
  456. return vector(p2).add(lvector);
  457. };
  458. /**
  459. * 要缩放多少才能到达目标
  460. * @param origin 缩放原点
  461. * @param scaleDirection 缩放方向
  462. * @param p1 当前点位
  463. * @param p2 目标点位
  464. * @returns
  465. */
  466. export function calculateScaleFactor(
  467. origin: Pos,
  468. scaleDirection: Pos,
  469. p1: Pos,
  470. p2: Pos
  471. ) {
  472. const op1 = vector(p1).sub(origin);
  473. const op2 = vector(p2).sub(origin);
  474. const xZero = zeroEq(op1.x);
  475. const yZero = zeroEq(op1.y);
  476. if (zeroEq(op1.x) || zeroEq(op2.y)) return;
  477. if (zeroEq(scaleDirection.x)) {
  478. if (zeroEq(p2.x - p1.x)) {
  479. return zeroEq(op1.y - op2.y) ? 1 : yZero ? null : op2.y / op1.y;
  480. } else {
  481. return;
  482. }
  483. }
  484. if (zeroEq(scaleDirection.y)) {
  485. if (zeroEq(p2.y - p1.y)) {
  486. return zeroEq(op1.x - op2.x) ? 1 : xZero ? null : op2.x / op1.x;
  487. } else {
  488. return;
  489. }
  490. }
  491. if (xZero && yZero) {
  492. return null;
  493. }
  494. const xScaleFactor = op2.x / (op1.x * scaleDirection.x);
  495. const yScaleFactor = op2.y / (op1.y * scaleDirection.y);
  496. if (xZero) {
  497. return yScaleFactor;
  498. } else if (yZero) {
  499. return xScaleFactor;
  500. }
  501. if (zeroEq(xScaleFactor - yScaleFactor)) {
  502. return xScaleFactor;
  503. }
  504. }
  505. // 获取两线段的矩阵关系
  506. export const getLineRelationMat = (l1: [Pos, Pos], l2: [Pos, Pos]) => {
  507. // 提取点
  508. const P1 = l1[0]; // l1 的起点
  509. const P1End = l1[1]; // l1 的终点
  510. const P2 = l2[0]; // l2 的起点
  511. const P2End = l2[1]; // l2 的终点
  512. // 计算方向向量
  513. const d1 = { x: P1End.x - P1.x, y: P1End.y - P1.y };
  514. const d2 = { x: P2End.x - P2.x, y: P2End.y - P2.y };
  515. // 计算方向向量的长度
  516. const lengthD1 = Math.sqrt(d1.x ** 2 + d1.y ** 2);
  517. const lengthD2 = Math.sqrt(d2.x ** 2 + d2.y ** 2);
  518. if (lengthD1 === 0 || lengthD2 === 0) return new Transform();
  519. // 归一化方向向量
  520. const unitD1 = { x: d1.x / lengthD1, y: d1.y / lengthD1 };
  521. const unitD2 = { x: d2.x / lengthD2, y: d2.y / lengthD2 };
  522. // 计算旋转角度
  523. const angle = Math.atan2(unitD2.y, unitD2.x) - Math.atan2(unitD1.y, unitD1.x);
  524. // 计算旋转矩阵
  525. // 计算缩放因子
  526. const scale = lengthD2 / lengthD1;
  527. // 计算平移向量
  528. const translation = [P2.x - P1.x, P2.y - P1.y];
  529. const mat = new Transform()
  530. .translate(translation[0], translation[1])
  531. .translate(P1.x, P1.y)
  532. .scale(scale, scale)
  533. .rotate(angle)
  534. .translate(-P1.x, -P1.y);
  535. if (!eqPoint(mat.point(P1), P2)) {
  536. console.error("对准不正确 旋转后P1", mat.point(P1), P2);
  537. }
  538. if (!eqPoint(mat.point(P1End), P2End)) {
  539. console.error("对准不正确 旋转后P2", mat.point(P1End), P1End);
  540. }
  541. return mat;
  542. };
  543. // 判断两向量是否垂直
  544. export const isVertical = (v1: Pos, v2: Pos) => {
  545. return zeroEq(vector(v1).dot(v2));
  546. };
  547. /**
  548. * 判断rect1是否完整包含rect2
  549. * @param rect1
  550. * @param rect2
  551. * @returns
  552. */
  553. export const isRectContained = (rect1: IRect, rect2: IRect) => {
  554. // 计算 rect1 的左右边界
  555. const rect1Left = Math.min(rect1.x, rect1.x + rect1.width);
  556. const rect1Right = Math.max(rect1.x, rect1.x + rect1.width);
  557. // 计算 rect1 的上下边界
  558. const rect1Top = Math.min(rect1.y, rect1.y + rect1.height);
  559. const rect1Bottom = Math.max(rect1.y, rect1.y + rect1.height);
  560. // 计算 rect2 的左右边界
  561. const rect2Left = Math.min(rect2.x, rect2.x + rect2.width);
  562. const rect2Right = Math.max(rect2.x, rect2.x + rect2.width);
  563. // 计算 rect2 的上下边界
  564. const rect2Top = Math.min(rect2.y, rect2.y + rect2.height);
  565. const rect2Bottom = Math.max(rect2.y, rect2.y + rect2.height);
  566. // 检查 rect2 是否完全在 rect1 内
  567. return (
  568. rect2Left >= rect1Left &&
  569. rect2Right <= rect1Right &&
  570. rect2Top >= rect1Top &&
  571. rect2Bottom <= rect1Bottom
  572. );
  573. };
  574. export const getLineEdges = (points: Pos[], strokeWidth: number) => {
  575. const v = lineVector(points);
  576. const vv = verticalVector(v);
  577. const offset = vv.clone().multiplyScalar(strokeWidth / 2);
  578. const top = points.map((p) => offset.clone().add(p));
  579. offset.multiplyScalar(-1);
  580. const bottom = points.map((p) => offset.clone().add(p));
  581. return [...top, bottom[1], bottom[0]];
  582. };
  583. export type LEJLine = { points: Pos[]; width: number };
  584. export type LEJInfo = { rep: number; points: Pos[] }[];
  585. export const getLEJJoinNdxs = (originLine: Pos[], targetLine: Pos[]) => {
  586. let originNdx = -1,
  587. targetNdx = -1;
  588. for (let i = 0; i < originLine.length; i++) {
  589. targetNdx = targetLine.findIndex((p) => eqPoint(originLine[i], p));
  590. if (~targetNdx) {
  591. originNdx = i;
  592. break;
  593. }
  594. }
  595. return { originNdx, targetNdx };
  596. };
  597. export const getLEJLineAngle = (originLine: Pos[], targetLine: Pos[]) => {
  598. const { originNdx, targetNdx } = getLEJJoinNdxs(originLine, targetLine);
  599. const targetInvFlag = originNdx === targetNdx;
  600. const targetPoints = targetInvFlag ? [...targetLine].reverse() : targetLine;
  601. const originVector = lineVector(originLine);
  602. const targetVector = lineVector(targetPoints).multiplyScalar(-1);
  603. let angle;
  604. if (originNdx === targetNdx && originNdx === 0) {
  605. angle = vector2IncludedAngle(targetVector, originVector);
  606. } else {
  607. angle = vector2IncludedAngle(originVector, targetVector);
  608. }
  609. return {
  610. angle,
  611. norAngle: rangMod(Math.abs(angle), Math.PI),
  612. originNdx,
  613. targetNdx,
  614. targetPoints,
  615. targetInvFlag,
  616. };
  617. };
  618. const getLinePolygonOverdo = (polygon: Pos[], slideLine: Pos[]) => {
  619. const sideJoins = polygonLineIntersect(polygon, slideLine);
  620. // 完全穿插而过
  621. if (sideJoins.length > 1) {
  622. return -1;
  623. // 完全包含在多边形内
  624. } else if (
  625. sideJoins.length === 0 &&
  626. slideLine.some((p) => isPolygonPointInner(polygon, p))
  627. ) {
  628. return -1;
  629. } else if (sideJoins.length === 1) {
  630. return sideJoins[0];
  631. } else {
  632. return 1;
  633. }
  634. };
  635. /**
  636. * 获取两变短延伸后的平湖处理
  637. * @param origin
  638. * @param target
  639. * @param minAngle
  640. * @param palAngle
  641. * @returns 平滑信息,需要结合延伸使用
  642. */
  643. export const getLineEdgeJoinInfo = (
  644. origin: LEJLine,
  645. target: LEJLine,
  646. minAngle: number,
  647. palAngle: number
  648. ) => {
  649. const { originNdx, targetPoints, norAngle } = getLEJLineAngle(
  650. origin.points,
  651. target.points
  652. );
  653. // 最小可平滑处理角度
  654. if (norAngle < minAngle || norAngle > Math.PI - minAngle) {
  655. return;
  656. }
  657. const targetEdges = getLineEdges(targetPoints, target.width);
  658. const originEdges = getLineEdges(origin.points, origin.width);
  659. const center = lineCenter([...origin.points, ...target.points]);
  660. const originEdgesInv =
  661. lineLen(center, originEdges[0]) > lineLen(center, originEdges[3]);
  662. let originInner, originOuter;
  663. if (originEdgesInv) {
  664. originOuter = [originEdges[0], originEdges[1]];
  665. originInner = [originEdges[3], originEdges[2]];
  666. } else {
  667. originInner = [originEdges[0], originEdges[1]];
  668. originOuter = [originEdges[3], originEdges[2]];
  669. }
  670. let innerJoin: Pos | null = null;
  671. // 如果交点边缘在另一条线中,则交点更改为另一条线上
  672. const targetJoinNdxs = originNdx === 0 ? [0, 3] : [1, 2];
  673. const originJoinNdxs = originNdx === 0 ? [1, 2] : [0, 3];
  674. // let innerJoinFlag = getLinePolygonOverdo(
  675. // originEdges,
  676. // targetJoinNdxs.map((i) => targetEdges[i])
  677. // );
  678. // if (innerJoinFlag === -1) {
  679. // return;
  680. // } else if (innerJoinFlag !== 1) {
  681. // innerJoin = innerJoinFlag as Pos;
  682. // }
  683. // if (!innerJoin) {
  684. // innerJoinFlag = getLinePolygonOverdo(
  685. // targetEdges,
  686. // originJoinNdxs.map((i) => originEdges[i])
  687. // );
  688. // if (innerJoinFlag === -1) {
  689. // return;
  690. // } else if (innerJoinFlag !== 1) {
  691. // innerJoin = innerJoinFlag as Pos;
  692. // }
  693. // }
  694. if (
  695. originJoinNdxs.some((i) =>
  696. isPolygonPointInner(targetEdges, originEdges[i])
  697. ) ||
  698. targetJoinNdxs.some((i) => isPolygonPointInner(originEdges, targetEdges[i]))
  699. ) {
  700. return;
  701. }
  702. const targetEdgesInv =
  703. lineLen(center, targetEdges[0]) > lineLen(center, targetEdges[3]);
  704. let targetInner, targetOuter;
  705. if (targetEdgesInv) {
  706. targetOuter = [targetEdges[0], targetEdges[1]];
  707. targetInner = [targetEdges[3], targetEdges[2]];
  708. } else {
  709. targetInner = [targetEdges[0], targetEdges[1]];
  710. targetOuter = [targetEdges[3], targetEdges[2]];
  711. }
  712. let outerJoin = lineIntersection(targetOuter, originOuter);
  713. if (!outerJoin) {
  714. return;
  715. }
  716. if (!innerJoin) {
  717. innerJoin = lineIntersection(targetInner, originInner);
  718. if (!innerJoin) {
  719. return;
  720. }
  721. }
  722. // 如果内交点均不在两条内线上则不处理
  723. if (
  724. !lineInner(targetInner, innerJoin) &&
  725. !lineInner(originInner, innerJoin)
  726. ) {
  727. return;
  728. }
  729. let originInnerPoints: Pos[] = [innerJoin];
  730. let originOuterPoints: Pos[] = [outerJoin];
  731. const join = origin.points[originNdx];
  732. // 如果角度过于尖锐则使用平行线
  733. if (norAngle < palAngle) {
  734. const pal = getVectorLine(lineVerticalVector([join, outerJoin]), join);
  735. originOuterPoints = [lineIntersection(pal, originOuter)!, join];
  736. }
  737. const originRepInfos: LEJInfo = [];
  738. if (originNdx === 0) {
  739. originRepInfos.push(
  740. {
  741. rep: 0,
  742. points: originEdgesInv
  743. ? originOuterPoints.reverse()
  744. : originInnerPoints,
  745. },
  746. {
  747. rep: 3,
  748. points: originEdgesInv ? originInnerPoints : originOuterPoints,
  749. }
  750. );
  751. } else {
  752. originRepInfos.push(
  753. {
  754. rep: 1,
  755. points: originEdgesInv ? originOuterPoints : originInnerPoints,
  756. },
  757. {
  758. rep: 2,
  759. points: originEdgesInv
  760. ? originInnerPoints
  761. : originOuterPoints.reverse(),
  762. }
  763. );
  764. }
  765. return originRepInfos;
  766. };