You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

vec.ts 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520
  1. // A big collection of vector utilities. Collected into a class to improve logging / packaging.
  2. /* ----------------- Start Copy Here ---------------- */
  3. export default class Vec {
  4. /**
  5. * Clamp a value into a range.
  6. * @param n
  7. * @param min
  8. */
  9. static clamp(n: number, min: number): number
  10. static clamp(n: number, min: number, max: number): number
  11. static clamp(n: number, min: number, max?: number): number {
  12. return Math.max(min, typeof max !== 'undefined' ? Math.min(n, max) : n)
  13. }
  14. /**
  15. * Negate a vector.
  16. * @param A
  17. */
  18. static neg = (A: number[]): number[] => {
  19. return [-A[0], -A[1]]
  20. }
  21. /**
  22. * Add vectors.
  23. * @param A
  24. * @param B
  25. */
  26. static add = (A: number[], B: number[]): number[] => {
  27. return [A[0] + B[0], A[1] + B[1]]
  28. }
  29. /**
  30. * Add scalar to vector.
  31. * @param A
  32. * @param B
  33. */
  34. static addScalar = (A: number[], n: number): number[] => {
  35. return [A[0] + n, A[1] + n]
  36. }
  37. /**
  38. * Subtract vectors.
  39. * @param A
  40. * @param B
  41. */
  42. static sub = (A: number[], B: number[]): number[] => {
  43. return [A[0] - B[0], A[1] - B[1]]
  44. }
  45. /**
  46. * Subtract scalar from vector.
  47. * @param A
  48. * @param B
  49. */
  50. static subScalar = (A: number[], n: number): number[] => {
  51. return [A[0] - n, A[1] - n]
  52. }
  53. /**
  54. * Get the vector from vectors A to B.
  55. * @param A
  56. * @param B
  57. */
  58. static vec = (A: number[], B: number[]): number[] => {
  59. // A, B as vectors get the vector from A to B
  60. return [B[0] - A[0], B[1] - A[1]]
  61. }
  62. /**
  63. * Vector multiplication by scalar
  64. * @param A
  65. * @param n
  66. */
  67. static mul = (A: number[], n: number): number[] => {
  68. return [A[0] * n, A[1] * n]
  69. }
  70. static mulV = (A: number[], B: number[]): number[] => {
  71. return [A[0] * B[0], A[1] * B[1]]
  72. }
  73. /**
  74. * Vector division by scalar.
  75. * @param A
  76. * @param n
  77. */
  78. static div = (A: number[], n: number): number[] => {
  79. return [A[0] / n, A[1] / n]
  80. }
  81. /**
  82. * Vector division by vector.
  83. * @param A
  84. * @param n
  85. */
  86. static divV = (A: number[], B: number[]): number[] => {
  87. return [A[0] / B[0], A[1] / B[1]]
  88. }
  89. /**
  90. * Perpendicular rotation of a vector A
  91. * @param A
  92. */
  93. static per = (A: number[]): number[] => {
  94. return [A[1], -A[0]]
  95. }
  96. /**
  97. * Dot product
  98. * @param A
  99. * @param B
  100. */
  101. static dpr = (A: number[], B: number[]): number => {
  102. return A[0] * B[0] + A[1] * B[1]
  103. }
  104. /**
  105. * Cross product (outer product) | A X B |
  106. * @param A
  107. * @param B
  108. */
  109. static cpr = (A: number[], B: number[]): number => {
  110. return A[0] * B[1] - B[0] * A[1]
  111. }
  112. /**
  113. * Length of the vector squared
  114. * @param A
  115. */
  116. static len2 = (A: number[]): number => {
  117. return A[0] * A[0] + A[1] * A[1]
  118. }
  119. /**
  120. * Length of the vector
  121. * @param A
  122. */
  123. static len = (A: number[]): number => {
  124. return Math.hypot(A[0], A[1])
  125. }
  126. /**
  127. * Project A over B
  128. * @param A
  129. * @param B
  130. */
  131. static pry = (A: number[], B: number[]): number => {
  132. return Vec.dpr(A, B) / Vec.len(B)
  133. }
  134. /**
  135. * Get normalized / unit vector.
  136. * @param A
  137. */
  138. static uni = (A: number[]): number[] => {
  139. return Vec.div(A, Vec.len(A))
  140. }
  141. /**
  142. * Get normalized / unit vector.
  143. * @param A
  144. */
  145. static normalize = (A: number[]): number[] => {
  146. return Vec.uni(A)
  147. }
  148. /**
  149. * Get the tangent between two vectors.
  150. * @param A
  151. * @param B
  152. * @returns
  153. */
  154. static tangent = (A: number[], B: number[]): number[] => {
  155. return Vec.normalize(Vec.sub(A, B))
  156. }
  157. /**
  158. * Dist length from A to B squared.
  159. * @param A
  160. * @param B
  161. */
  162. static dist2 = (A: number[], B: number[]): number => {
  163. return Vec.len2(Vec.sub(A, B))
  164. }
  165. /**
  166. * Dist length from A to B
  167. * @param A
  168. * @param B
  169. */
  170. static dist = (A: number[], B: number[]): number => {
  171. return Math.hypot(A[1] - B[1], A[0] - B[0])
  172. }
  173. /**
  174. * A faster, though less accurate method for testing distances. Maybe faster?
  175. * @param A
  176. * @param B
  177. * @returns
  178. */
  179. static fastDist = (A: number[], B: number[]): number[] => {
  180. const V = [B[0] - A[0], B[1] - A[1]]
  181. const aV = [Math.abs(V[0]), Math.abs(V[1])]
  182. let r = 1 / Math.max(aV[0], aV[1])
  183. r = r * (1.29289 - (aV[0] + aV[1]) * r * 0.29289)
  184. return [V[0] * r, V[1] * r]
  185. }
  186. /**
  187. * Angle between vector A and vector B in radians
  188. * @param A
  189. * @param B
  190. */
  191. static ang = (A: number[], B: number[]): number => {
  192. return Math.atan2(Vec.cpr(A, B), Vec.dpr(A, B))
  193. }
  194. /**
  195. * Angle between vector A and vector B in radians
  196. * @param A
  197. * @param B
  198. */
  199. static angle = (A: number[], B: number[]): number => {
  200. return Math.atan2(B[1] - A[1], B[0] - A[0])
  201. }
  202. /**
  203. * Mean between two vectors or mid vector between two vectors
  204. * @param A
  205. * @param B
  206. */
  207. static med = (A: number[], B: number[]): number[] => {
  208. return Vec.mul(Vec.add(A, B), 0.5)
  209. }
  210. /**
  211. * Vector rotation by r (radians)
  212. * @param A
  213. * @param r rotation in radians
  214. */
  215. static rot = (A: number[], r: number): number[] => {
  216. return [
  217. A[0] * Math.cos(r) - A[1] * Math.sin(r),
  218. A[0] * Math.sin(r) + A[1] * Math.cos(r),
  219. ]
  220. }
  221. /**
  222. * Rotate a vector around another vector by r (radians)
  223. * @param A vector
  224. * @param C center
  225. * @param r rotation in radians
  226. */
  227. static rotWith = (A: number[], C: number[], r: number): number[] => {
  228. if (r === 0) return A
  229. const s = Math.sin(r)
  230. const c = Math.cos(r)
  231. const px = A[0] - C[0]
  232. const py = A[1] - C[1]
  233. const nx = px * c - py * s
  234. const ny = px * s + py * c
  235. return [nx + C[0], ny + C[1]]
  236. }
  237. /**
  238. * Check of two vectors are identical.
  239. * @param A
  240. * @param B
  241. */
  242. static isEqual = (A: number[], B: number[]): boolean => {
  243. return A[0] === B[0] && A[1] === B[1]
  244. }
  245. /**
  246. * Interpolate vector A to B with a scalar t
  247. * @param A
  248. * @param B
  249. * @param t scalar
  250. */
  251. static lrp = (A: number[], B: number[], t: number): number[] => {
  252. return Vec.add(A, Vec.mul(Vec.vec(A, B), t))
  253. }
  254. /**
  255. * Interpolate from A to B when curVAL goes fromVAL: number[] => to
  256. * @param A
  257. * @param B
  258. * @param from Starting value
  259. * @param to Ending value
  260. * @param s Strength
  261. */
  262. static int = (
  263. A: number[],
  264. B: number[],
  265. from: number,
  266. to: number,
  267. s = 1
  268. ): number[] => {
  269. const t = (Vec.clamp(from, to) - from) / (to - from)
  270. return Vec.add(Vec.mul(A, 1 - t), Vec.mul(B, s))
  271. }
  272. /**
  273. * Get the angle between the three vectors A, B, and C.
  274. * @param p1
  275. * @param pc
  276. * @param p2
  277. */
  278. static ang3 = (p1: number[], pc: number[], p2: number[]): number => {
  279. // this,
  280. const v1 = Vec.vec(pc, p1)
  281. const v2 = Vec.vec(pc, p2)
  282. return Vec.ang(v1, v2)
  283. }
  284. /**
  285. * Absolute value of a vector.
  286. * @param A
  287. * @returns
  288. */
  289. static abs = (A: number[]): number[] => {
  290. return [Math.abs(A[0]), Math.abs(A[1])]
  291. }
  292. static rescale = (a: number[], n: number): number[] => {
  293. const l = Vec.len(a)
  294. return [(n * a[0]) / l, (n * a[1]) / l]
  295. }
  296. /**
  297. * Get whether p1 is left of p2, relative to pc.
  298. * @param p1
  299. * @param pc
  300. * @param p2
  301. */
  302. static isLeft = (p1: number[], pc: number[], p2: number[]): number => {
  303. // isLeft: >0 for counterclockwise
  304. // =0 for none (degenerate)
  305. // <0 for clockwise
  306. return (pc[0] - p1[0]) * (p2[1] - p1[1]) - (p2[0] - p1[0]) * (pc[1] - p1[1])
  307. }
  308. static clockwise = (p1: number[], pc: number[], p2: number[]): boolean => {
  309. return Vec.isLeft(p1, pc, p2) > 0
  310. }
  311. static round = (a: number[], d = 5): number[] => {
  312. return a.map((v) => Number(v.toPrecision(d)))
  313. }
  314. /**
  315. * Get the minimum distance from a point P to a line with a segment AB.
  316. * @param A The start of the line.
  317. * @param B The end of the line.
  318. * @param P A point.
  319. * @returns
  320. */
  321. // static distanceToLine(A: number[], B: number[], P: number[]) {
  322. // const delta = sub(B, A)
  323. // const angle = Math.atan2(delta[1], delta[0])
  324. // const dir = rot(sub(P, A), -angle)
  325. // return dir[1]
  326. // }
  327. /**
  328. * Get the nearest point on a line segment AB.
  329. * @param A The start of the line.
  330. * @param B The end of the line.
  331. * @param P A point.
  332. * @param clamp Whether to clamp the resulting point to the segment.
  333. * @returns
  334. */
  335. // static nearestPointOnLine(
  336. // A: number[],
  337. // B: number[],
  338. // P: number[],
  339. // clamp = true
  340. // ) {
  341. // const delta = sub(B, A)
  342. // const length = len(delta)
  343. // const angle = Math.atan2(delta[1], delta[0])
  344. // const dir = rot(sub(P, A), -angle)
  345. // if (clamp) {
  346. // if (dir[0] < 0) return A
  347. // if (dir[0] > length) return B
  348. // }
  349. // return add(A, div(mul(delta, dir[0]), length))
  350. // }
  351. /**
  352. * Get the nearest point on a line with a known unit vector that passes through point A
  353. * @param A Any point on the line
  354. * @param u The unit vector for the line.
  355. * @param P A point not on the line to test.
  356. * @returns
  357. */
  358. static nearestPointOnLineThroughPoint = (
  359. A: number[],
  360. u: number[],
  361. P: number[]
  362. ): number[] => {
  363. return Vec.add(A, Vec.mul(u, Vec.pry(Vec.sub(P, A), u)))
  364. }
  365. /**
  366. * Distance between a point and a line with a known unit vector that passes through a point.
  367. * @param A Any point on the line
  368. * @param u The unit vector for the line.
  369. * @param P A point not on the line to test.
  370. * @returns
  371. */
  372. static distanceToLineThroughPoint = (
  373. A: number[],
  374. u: number[],
  375. P: number[]
  376. ): number => {
  377. return Vec.dist(P, Vec.nearestPointOnLineThroughPoint(A, u, P))
  378. }
  379. /**
  380. * Get the nearest point on a line segment between A and B
  381. * @param A The start of the line segment
  382. * @param B The end of the line segment
  383. * @param P The off-line point
  384. * @param clamp Whether to clamp the point between A and B.
  385. * @returns
  386. */
  387. static nearestPointOnLineSegment = (
  388. A: number[],
  389. B: number[],
  390. P: number[],
  391. clamp = true
  392. ): number[] => {
  393. const delta = Vec.sub(B, A)
  394. const length = Vec.len(delta)
  395. const u = Vec.div(delta, length)
  396. const pt = Vec.add(A, Vec.mul(u, Vec.pry(Vec.sub(P, A), u)))
  397. if (clamp) {
  398. const da = Vec.dist(A, pt)
  399. const db = Vec.dist(B, pt)
  400. if (db < da && da > length) return B
  401. if (da < db && db > length) return A
  402. }
  403. return pt
  404. }
  405. /**
  406. * Distance between a point and the nearest point on a line segment between A and B
  407. * @param A The start of the line segment
  408. * @param B The end of the line segment
  409. * @param P The off-line point
  410. * @param clamp Whether to clamp the point between A and B.
  411. * @returns
  412. */
  413. static distanceToLineSegment = (
  414. A: number[],
  415. B: number[],
  416. P: number[],
  417. clamp = true
  418. ): number => {
  419. return Vec.dist(P, Vec.nearestPointOnLineSegment(A, B, P, clamp))
  420. }
  421. /**
  422. * Push a point A towards point B by a given distance.
  423. * @param A
  424. * @param B
  425. * @param d
  426. * @returns
  427. */
  428. static nudge = (A: number[], B: number[], d: number): number[] => {
  429. return Vec.add(A, Vec.mul(Vec.uni(Vec.vec(A, B)), d))
  430. }
  431. /**
  432. * Push a point in a given angle by a given distance.
  433. * @param A
  434. * @param B
  435. * @param d
  436. */
  437. static nudgeAtAngle = (A: number[], a: number, d: number): number[] => {
  438. return [Math.cos(a) * d + A[0], Math.sin(a) * d + A[1]]
  439. }
  440. /**
  441. * Round a vector to a precision length.
  442. * @param a
  443. * @param n
  444. */
  445. static toPrecision = (a: number[], n = 4): number[] => {
  446. return [+a[0].toPrecision(n), +a[1].toPrecision(n)]
  447. }
  448. /**
  449. * Get a number of points between two points.
  450. * @param a
  451. * @param b
  452. * @param steps
  453. */
  454. static pointsBetween = (a: number[], b: number[], steps = 6): number[][] => {
  455. return Array.from(Array(steps))
  456. .map((_, i) => {
  457. const t = i / steps
  458. return t * t * t
  459. })
  460. .map((t) => [...Vec.lrp(a, b, t), (1 - t) / 2])
  461. }
  462. }