Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829
  1. import { Data } from "types"
  2. import * as svg from "./svg"
  3. import * as vec from "./vec"
  4. export function screenToWorld(point: number[], data: Data) {
  5. return vec.add(vec.div(point, data.camera.zoom), data.camera.point)
  6. }
  7. // A helper for getting tangents.
  8. export function getCircleTangentToPoint(
  9. A: number[],
  10. r0: number,
  11. P: number[],
  12. side: number
  13. ) {
  14. const B = vec.lrp(A, P, 0.5),
  15. r1 = vec.dist(A, B),
  16. delta = vec.sub(B, A),
  17. d = vec.len(delta)
  18. if (!(d <= r0 + r1 && d >= Math.abs(r0 - r1))) {
  19. return
  20. }
  21. const a = (r0 * r0 - r1 * r1 + d * d) / (2.0 * d),
  22. n = 1 / d,
  23. p = vec.add(A, vec.mul(delta, a * n)),
  24. h = Math.sqrt(r0 * r0 - a * a),
  25. k = vec.mul(vec.per(delta), h * n)
  26. return side === 0 ? vec.add(p, k) : vec.sub(p, k)
  27. }
  28. export function circleCircleIntersections(a: number[], b: number[]) {
  29. const R = a[2],
  30. r = b[2]
  31. let dx = b[0] - a[0],
  32. dy = b[1] - a[1]
  33. const d = Math.sqrt(dx * dx + dy * dy),
  34. x = (d * d - r * r + R * R) / (2 * d),
  35. y = Math.sqrt(R * R - x * x)
  36. dx /= d
  37. dy /= d
  38. return [
  39. [a[0] + dx * x - dy * y, a[1] + dy * x + dx * y],
  40. [a[0] + dx * x + dy * y, a[1] + dy * x - dx * y],
  41. ]
  42. }
  43. export function getClosestPointOnCircle(
  44. C: number[],
  45. r: number,
  46. P: number[],
  47. padding = 0
  48. ) {
  49. const v = vec.sub(C, P)
  50. return vec.sub(C, vec.mul(vec.div(v, vec.len(v)), r + padding))
  51. }
  52. export function projectPoint(p0: number[], a: number, d: number) {
  53. return [Math.cos(a) * d + p0[0], Math.sin(a) * d + p0[1]]
  54. }
  55. function shortAngleDist(a0: number, a1: number) {
  56. const max = Math.PI * 2
  57. const da = (a1 - a0) % max
  58. return ((2 * da) % max) - da
  59. }
  60. export function lerpAngles(a0: number, a1: number, t: number) {
  61. return a0 + shortAngleDist(a0, a1) * t
  62. }
  63. export function getBezierCurveSegments(points: number[][], tension = 0.4) {
  64. const len = points.length,
  65. cpoints: number[][] = [...points]
  66. if (len < 2) {
  67. throw Error("Curve must have at least two points.")
  68. }
  69. for (let i = 1; i < len - 1; i++) {
  70. const p0 = points[i - 1],
  71. p1 = points[i],
  72. p2 = points[i + 1]
  73. const pdx = p2[0] - p0[0],
  74. pdy = p2[1] - p0[1],
  75. pd = Math.hypot(pdx, pdy),
  76. nx = pdx / pd, // normalized x
  77. ny = pdy / pd, // normalized y
  78. dp = Math.hypot(p1[0] - p0[0], p1[1] - p0[1]), // Distance to previous
  79. dn = Math.hypot(p1[0] - p2[0], p1[1] - p2[1]) // Distance to next
  80. cpoints[i] = [
  81. // tangent start
  82. p1[0] - nx * dp * tension,
  83. p1[1] - ny * dp * tension,
  84. // tangent end
  85. p1[0] + nx * dn * tension,
  86. p1[1] + ny * dn * tension,
  87. // normal
  88. nx,
  89. ny,
  90. ]
  91. }
  92. // TODO: Reflect the nearest control points, not average them
  93. const d0 = Math.hypot(points[0][0] + cpoints[1][0])
  94. cpoints[0][2] = (points[0][0] + cpoints[1][0]) / 2
  95. cpoints[0][3] = (points[0][1] + cpoints[1][1]) / 2
  96. cpoints[0][4] = (cpoints[1][0] - points[0][0]) / d0
  97. cpoints[0][5] = (cpoints[1][1] - points[0][1]) / d0
  98. const d1 = Math.hypot(points[len - 1][1] + cpoints[len - 1][1])
  99. cpoints[len - 1][0] = (points[len - 1][0] + cpoints[len - 2][2]) / 2
  100. cpoints[len - 1][1] = (points[len - 1][1] + cpoints[len - 2][3]) / 2
  101. cpoints[len - 1][4] = (cpoints[len - 2][2] - points[len - 1][0]) / -d1
  102. cpoints[len - 1][5] = (cpoints[len - 2][3] - points[len - 1][1]) / -d1
  103. const results: {
  104. start: number[]
  105. tangentStart: number[]
  106. normalStart: number[]
  107. pressureStart: number
  108. end: number[]
  109. tangentEnd: number[]
  110. normalEnd: number[]
  111. pressureEnd: number
  112. }[] = []
  113. for (let i = 1; i < cpoints.length; i++) {
  114. results.push({
  115. start: points[i - 1].slice(0, 2),
  116. tangentStart: cpoints[i - 1].slice(2, 4),
  117. normalStart: cpoints[i - 1].slice(4, 6),
  118. pressureStart: 2 + ((i - 1) % 2 === 0 ? 1.5 : 0),
  119. end: points[i].slice(0, 2),
  120. tangentEnd: cpoints[i].slice(0, 2),
  121. normalEnd: cpoints[i].slice(4, 6),
  122. pressureEnd: 2 + (i % 2 === 0 ? 1.5 : 0),
  123. })
  124. }
  125. return results
  126. }
  127. export function cubicBezier(
  128. tx: number,
  129. x1: number,
  130. y1: number,
  131. x2: number,
  132. y2: number
  133. ) {
  134. // Inspired by Don Lancaster's two articles
  135. // http://www.tinaja.com/glib/cubemath.pdf
  136. // http://www.tinaja.com/text/bezmath.html
  137. // Set start and end point
  138. const x0 = 0,
  139. y0 = 0,
  140. x3 = 1,
  141. y3 = 1,
  142. // Convert the coordinates to equation space
  143. A = x3 - 3 * x2 + 3 * x1 - x0,
  144. B = 3 * x2 - 6 * x1 + 3 * x0,
  145. C = 3 * x1 - 3 * x0,
  146. D = x0,
  147. E = y3 - 3 * y2 + 3 * y1 - y0,
  148. F = 3 * y2 - 6 * y1 + 3 * y0,
  149. G = 3 * y1 - 3 * y0,
  150. H = y0,
  151. // Variables for the loop below
  152. iterations = 5
  153. let i: number,
  154. slope: number,
  155. x: number,
  156. t = tx
  157. // Loop through a few times to get a more accurate time value, according to the Newton-Raphson method
  158. // http://en.wikipedia.org/wiki/Newton's_method
  159. for (i = 0; i < iterations; i++) {
  160. // The curve's x equation for the current time value
  161. x = A * t * t * t + B * t * t + C * t + D
  162. // The slope we want is the inverse of the derivate of x
  163. slope = 1 / (3 * A * t * t + 2 * B * t + C)
  164. // Get the next estimated time value, which will be more accurate than the one before
  165. t -= (x - tx) * slope
  166. t = t > 1 ? 1 : t < 0 ? 0 : t
  167. }
  168. // Find the y value through the curve's y equation, with the now more accurate time value
  169. return Math.abs(E * t * t * t + F * t * t + G * t * H)
  170. }
  171. export function copyToClipboard(string: string) {
  172. let textarea: HTMLTextAreaElement
  173. let result: boolean
  174. try {
  175. navigator.clipboard.writeText(string)
  176. } catch (e) {
  177. try {
  178. textarea = document.createElement("textarea")
  179. textarea.setAttribute("position", "fixed")
  180. textarea.setAttribute("top", "0")
  181. textarea.setAttribute("readonly", "true")
  182. textarea.setAttribute("contenteditable", "true")
  183. textarea.style.position = "fixed" // prevent scroll from jumping to the bottom when focus is set.
  184. textarea.value = string
  185. document.body.appendChild(textarea)
  186. textarea.focus()
  187. textarea.select()
  188. const range = document.createRange()
  189. range.selectNodeContents(textarea)
  190. const sel = window.getSelection()
  191. sel.removeAllRanges()
  192. sel.addRange(range)
  193. textarea.setSelectionRange(0, textarea.value.length)
  194. result = document.execCommand("copy")
  195. } catch (err) {
  196. result = null
  197. } finally {
  198. document.body.removeChild(textarea)
  199. }
  200. }
  201. return !!result
  202. }
  203. /**
  204. * Get a bezier curve data to for a spline that fits an array of points.
  205. * @param points An array of points formatted as [x, y]
  206. * @param k Tension
  207. * @returns An array of points as [cp1x, cp1y, cp2x, cp2y, px, py].
  208. */
  209. export function getSpline(pts: number[][], k = 0.5) {
  210. let p0: number[],
  211. [p1, p2, p3] = pts
  212. const results: number[][] = []
  213. for (let i = 1, len = pts.length; i < len; i++) {
  214. p0 = p1
  215. p1 = p2
  216. p2 = p3
  217. p3 = pts[i + 2] ? pts[i + 2] : p2
  218. results.push([
  219. p1[0] + ((p2[0] - p0[0]) / 6) * k,
  220. p1[1] + ((p2[1] - p0[1]) / 6) * k,
  221. p2[0] - ((p3[0] - p1[0]) / 6) * k,
  222. p2[1] - ((p3[1] - p1[1]) / 6) * k,
  223. pts[i][0],
  224. pts[i][1],
  225. ])
  226. }
  227. return results
  228. }
  229. export function getCurvePoints(
  230. pts: number[][],
  231. tension = 0.5,
  232. isClosed = false,
  233. numOfSegments = 3
  234. ) {
  235. const _pts = [...pts],
  236. len = pts.length,
  237. res: number[][] = [] // results
  238. let t1x: number, // tension vectors
  239. t2x: number,
  240. t1y: number,
  241. t2y: number,
  242. c1: number, // cardinal points
  243. c2: number,
  244. c3: number,
  245. c4: number,
  246. st: number,
  247. st2: number,
  248. st3: number
  249. // The algorithm require a previous and next point to the actual point array.
  250. // Check if we will draw closed or open curve.
  251. // If closed, copy end points to beginning and first points to end
  252. // If open, duplicate first points to befinning, end points to end
  253. if (isClosed) {
  254. _pts.unshift(_pts[len - 1])
  255. _pts.push(_pts[0])
  256. } else {
  257. //copy 1. point and insert at beginning
  258. _pts.unshift(_pts[0])
  259. _pts.push(_pts[len - 1])
  260. // _pts.push(_pts[len - 1])
  261. }
  262. // For each point, calculate a segment
  263. for (let i = 1; i < _pts.length - 2; i++) {
  264. // Calculate points along segment and add to results
  265. for (let t = 0; t <= numOfSegments; t++) {
  266. // Step
  267. st = t / numOfSegments
  268. st2 = Math.pow(st, 2)
  269. st3 = Math.pow(st, 3)
  270. // Cardinals
  271. c1 = 2 * st3 - 3 * st2 + 1
  272. c2 = -(2 * st3) + 3 * st2
  273. c3 = st3 - 2 * st2 + st
  274. c4 = st3 - st2
  275. // Tension
  276. t1x = (_pts[i + 1][0] - _pts[i - 1][0]) * tension
  277. t2x = (_pts[i + 2][0] - _pts[i][0]) * tension
  278. t1y = (_pts[i + 1][1] - _pts[i - 1][1]) * tension
  279. t2y = (_pts[i + 2][1] - _pts[i][1]) * tension
  280. // Control points
  281. res.push([
  282. c1 * _pts[i][0] + c2 * _pts[i + 1][0] + c3 * t1x + c4 * t2x,
  283. c1 * _pts[i][1] + c2 * _pts[i + 1][1] + c3 * t1y + c4 * t2y,
  284. ])
  285. }
  286. }
  287. res.push(pts[pts.length - 1])
  288. return res
  289. }
  290. export function angleDelta(a0: number, a1: number) {
  291. return shortAngleDist(a0, a1)
  292. }
  293. /**
  294. * Rotate a point around a center.
  295. * @param x The x-axis coordinate of the point.
  296. * @param y The y-axis coordinate of the point.
  297. * @param cx The x-axis coordinate of the point to rotate round.
  298. * @param cy The y-axis coordinate of the point to rotate round.
  299. * @param angle The distance (in radians) to rotate.
  300. */
  301. export function rotatePoint(A: number[], B: number[], angle: number) {
  302. const s = Math.sin(angle)
  303. const c = Math.cos(angle)
  304. const px = A[0] - B[0]
  305. const py = A[1] - B[1]
  306. const nx = px * c - py * s
  307. const ny = px * s + py * c
  308. return [nx + B[0], ny + B[1]]
  309. }
  310. export function degreesToRadians(d: number) {
  311. return (d * Math.PI) / 180
  312. }
  313. export function radiansToDegrees(r: number) {
  314. return (r * 180) / Math.PI
  315. }
  316. export function getArcLength(C: number[], r: number, A: number[], B: number[]) {
  317. const sweep = getSweep(C, A, B)
  318. return r * (2 * Math.PI) * (sweep / (2 * Math.PI))
  319. }
  320. export function getArcDashOffset(
  321. C: number[],
  322. r: number,
  323. A: number[],
  324. B: number[],
  325. step: number
  326. ) {
  327. const del0 = getSweep(C, A, B)
  328. const len0 = getArcLength(C, r, A, B)
  329. const off0 = del0 < 0 ? len0 : 2 * Math.PI * C[2] - len0
  330. return -off0 / 2 + step
  331. }
  332. export function getEllipseDashOffset(A: number[], step: number) {
  333. const c = 2 * Math.PI * A[2]
  334. return -c / 2 + -step
  335. }
  336. export function getSweep(C: number[], A: number[], B: number[]) {
  337. return angleDelta(vec.angle(C, A), vec.angle(C, B))
  338. }
  339. export function deepCompareArrays<T>(a: T[], b: T[]) {
  340. if (a?.length !== b?.length) return false
  341. return deepCompare(a, b)
  342. }
  343. export function deepCompare<T>(a: T, b: T) {
  344. return a === b || JSON.stringify(a) === JSON.stringify(b)
  345. }
  346. /**
  347. * Get outer tangents of two circles.
  348. * @param x0
  349. * @param y0
  350. * @param r0
  351. * @param x1
  352. * @param y1
  353. * @param r1
  354. * @returns [lx0, ly0, lx1, ly1, rx0, ry0, rx1, ry1]
  355. */
  356. export function getOuterTangents(
  357. C0: number[],
  358. r0: number,
  359. C1: number[],
  360. r1: number
  361. ) {
  362. const a0 = vec.angle(C0, C1)
  363. const d = vec.dist(C0, C1)
  364. // Circles are overlapping, no tangents
  365. if (d < Math.abs(r1 - r0)) return
  366. const a1 = Math.acos((r0 - r1) / d),
  367. t0 = a0 + a1,
  368. t1 = a0 - a1
  369. return [
  370. [C0[0] + r0 * Math.cos(t1), C0[1] + r0 * Math.sin(t1)],
  371. [C1[0] + r1 * Math.cos(t1), C1[1] + r1 * Math.sin(t1)],
  372. [C0[0] + r0 * Math.cos(t0), C0[1] + r0 * Math.sin(t0)],
  373. [C1[0] + r1 * Math.cos(t0), C1[1] + r1 * Math.sin(t0)],
  374. ]
  375. }
  376. export function arrsIntersect<T, K>(
  377. a: T[],
  378. b: K[],
  379. fn?: (item: K) => T
  380. ): boolean
  381. export function arrsIntersect<T>(a: T[], b: T[]): boolean
  382. export function arrsIntersect<T>(
  383. a: T[],
  384. b: unknown[],
  385. fn?: (item: unknown) => T
  386. ) {
  387. return a.some((item) => b.includes(fn ? fn(item) : item))
  388. }
  389. // /**
  390. // * Will mutate an array to remove items.
  391. // * @param arr
  392. // * @param item
  393. // */
  394. // export function pull<T>(arr: T[], ...items: T[]) {
  395. // for (let item of items) {
  396. // arr.splice(arr.indexOf(item), 1)
  397. // }
  398. // return arr
  399. // }
  400. // /**
  401. // * Will mutate an array to remove items, based on a function
  402. // * @param arr
  403. // * @param fn
  404. // * @returns
  405. // */
  406. // export function pullWith<T>(arr: T[], fn: (item: T) => boolean) {
  407. // pull(arr, ...arr.filter((item) => fn(item)))
  408. // return arr
  409. // }
  410. // export function rectContainsRect(
  411. // x0: number,
  412. // y0: number,
  413. // x1: number,
  414. // y1: number,
  415. // box: { x: number; y: number; width: number; height: number }
  416. // ) {
  417. // return !(
  418. // x0 > box.x ||
  419. // x1 < box.x + box.width ||
  420. // y0 > box.y ||
  421. // y1 < box.y + box.height
  422. // )
  423. // }
  424. export function getTouchDisplay() {
  425. return (
  426. "ontouchstart" in window ||
  427. navigator.maxTouchPoints > 0 ||
  428. navigator.msMaxTouchPoints > 0
  429. )
  430. }
  431. const rounds = [1, 10, 100, 1000]
  432. export function round(n: number, p = 2) {
  433. return Math.floor(n * rounds[p]) / rounds[p]
  434. }
  435. /**
  436. * Linear interpolation betwen two numbers.
  437. * @param y1
  438. * @param y2
  439. * @param mu
  440. */
  441. export function lerp(y1: number, y2: number, mu: number) {
  442. mu = clamp(mu, 0, 1)
  443. return y1 * (1 - mu) + y2 * mu
  444. }
  445. /**
  446. * Modulate a value between two ranges.
  447. * @param value
  448. * @param rangeA from [low, high]
  449. * @param rangeB to [low, high]
  450. * @param clamp
  451. */
  452. export function modulate(
  453. value: number,
  454. rangeA: number[],
  455. rangeB: number[],
  456. clamp = false
  457. ) {
  458. const [fromLow, fromHigh] = rangeA
  459. const [v0, v1] = rangeB
  460. const result = v0 + ((value - fromLow) / (fromHigh - fromLow)) * (v1 - v0)
  461. return clamp
  462. ? v0 < v1
  463. ? Math.max(Math.min(result, v1), v0)
  464. : Math.max(Math.min(result, v0), v1)
  465. : result
  466. }
  467. /**
  468. * Clamp a value into a range.
  469. * @param n
  470. * @param min
  471. */
  472. export function clamp(n: number, min: number): number
  473. export function clamp(n: number, min: number, max: number): number
  474. export function clamp(n: number, min: number, max?: number): number {
  475. return Math.max(min, typeof max !== "undefined" ? Math.min(n, max) : n)
  476. }
  477. // CURVES
  478. // Mostly adapted from https://github.com/Pomax/bezierjs
  479. export function computePointOnCurve(t: number, points: number[][]) {
  480. // shortcuts
  481. if (t === 0) {
  482. return points[0]
  483. }
  484. const order = points.length - 1
  485. if (t === 1) {
  486. return points[order]
  487. }
  488. const mt = 1 - t
  489. let p = points // constant?
  490. if (order === 0) {
  491. return points[0]
  492. } // linear?
  493. if (order === 1) {
  494. return [mt * p[0][0] + t * p[1][0], mt * p[0][1] + t * p[1][1]]
  495. } // quadratic/cubic curve?
  496. if (order < 4) {
  497. const mt2 = mt * mt,
  498. t2 = t * t
  499. let a: number,
  500. b: number,
  501. c: number,
  502. d = 0
  503. if (order === 2) {
  504. p = [p[0], p[1], p[2], [0, 0]]
  505. a = mt2
  506. b = mt * t * 2
  507. c = t2
  508. } else if (order === 3) {
  509. a = mt2 * mt
  510. b = mt2 * t * 3
  511. c = mt * t2 * 3
  512. d = t * t2
  513. }
  514. return [
  515. a * p[0][0] + b * p[1][0] + c * p[2][0] + d * p[3][0],
  516. a * p[0][1] + b * p[1][1] + c * p[2][1] + d * p[3][1],
  517. ]
  518. } // higher order curves: use de Casteljau's computation
  519. }
  520. function distance2(p: DOMPoint, point: number[]) {
  521. const dx = p.x - point[0],
  522. dy = p.y - point[1]
  523. return dx * dx + dy * dy
  524. }
  525. /**
  526. * Find the closest point on a path to an off-path point.
  527. * @param pathNode
  528. * @param point
  529. * @returns
  530. */
  531. export function getClosestPointOnPath(
  532. pathNode: SVGPathElement,
  533. point: number[]
  534. ) {
  535. const pathLen = pathNode.getTotalLength()
  536. let p = 8,
  537. best: DOMPoint,
  538. bestLen: number,
  539. bestDist = Infinity,
  540. bl: number,
  541. al: number
  542. // linear scan for coarse approximation
  543. for (
  544. let scan: DOMPoint, scanLen = 0, scanDist: number;
  545. scanLen <= pathLen;
  546. scanLen += p
  547. ) {
  548. if (
  549. (scanDist = distance2(
  550. (scan = pathNode.getPointAtLength(scanLen)),
  551. point
  552. )) < bestDist
  553. ) {
  554. ;(best = scan), (bestLen = scanLen), (bestDist = scanDist)
  555. }
  556. }
  557. // binary search for precise estimate
  558. p /= 2
  559. while (p > 0.5) {
  560. let before: DOMPoint, after: DOMPoint, bd: number, ad: number
  561. if (
  562. (bl = bestLen - p) >= 0 &&
  563. (bd = distance2((before = pathNode.getPointAtLength(bl)), point)) <
  564. bestDist
  565. ) {
  566. ;(best = before), (bestLen = bl), (bestDist = bd)
  567. } else if (
  568. (al = bestLen + p) <= pathLen &&
  569. (ad = distance2((after = pathNode.getPointAtLength(al)), point)) <
  570. bestDist
  571. ) {
  572. ;(best = after), (bestLen = al), (bestDist = ad)
  573. } else {
  574. p /= 2
  575. }
  576. }
  577. return {
  578. point: [best.x, best.y],
  579. distance: bestDist,
  580. length: (bl + al) / 2,
  581. t: (bl + al) / 2 / pathLen,
  582. }
  583. }
  584. export function det(
  585. a: number,
  586. b: number,
  587. c: number,
  588. d: number,
  589. e: number,
  590. f: number,
  591. g: number,
  592. h: number,
  593. i: number
  594. ) {
  595. return a * e * i + b * f * g + c * d * h - a * f * h - b * d * i - c * e * g
  596. }
  597. /**
  598. * Get a circle from three points.
  599. * @param p0
  600. * @param p1
  601. * @param center
  602. * @returns
  603. */
  604. export function circleFromThreePoints(A: number[], B: number[], C: number[]) {
  605. const a = det(A[0], A[1], 1, B[0], B[1], 1, C[0], C[1], 1)
  606. const bx = -det(
  607. A[0] * A[0] + A[1] * A[1],
  608. A[1],
  609. 1,
  610. B[0] * B[0] + B[1] * B[1],
  611. B[1],
  612. 1,
  613. C[0] * C[0] + C[1] * C[1],
  614. C[1],
  615. 1
  616. )
  617. const by = det(
  618. A[0] * A[0] + A[1] * A[1],
  619. A[0],
  620. 1,
  621. B[0] * B[0] + B[1] * B[1],
  622. B[0],
  623. 1,
  624. C[0] * C[0] + C[1] * C[1],
  625. C[0],
  626. 1
  627. )
  628. const c = -det(
  629. A[0] * A[0] + A[1] * A[1],
  630. A[0],
  631. A[1],
  632. B[0] * B[0] + B[1] * B[1],
  633. B[0],
  634. B[1],
  635. C[0] * C[0] + C[1] * C[1],
  636. C[0],
  637. C[1]
  638. )
  639. return [
  640. -bx / (2 * a),
  641. -by / (2 * a),
  642. Math.sqrt(bx * bx + by * by - 4 * a * c) / (2 * Math.abs(a)),
  643. ]
  644. }
  645. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  646. export function throttle<P extends any[], T extends (...args: P) => any>(
  647. fn: T,
  648. wait: number,
  649. preventDefault?: boolean
  650. ) {
  651. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  652. let inThrottle: boolean, lastFn: any, lastTime: number
  653. return function(...args: P) {
  654. if (preventDefault) args[0].preventDefault()
  655. // eslint-disable-next-line @typescript-eslint/no-this-alias
  656. const context = this
  657. if (!inThrottle) {
  658. fn.apply(context, args)
  659. lastTime = Date.now()
  660. inThrottle = true
  661. } else {
  662. clearTimeout(lastFn)
  663. lastFn = setTimeout(function() {
  664. if (Date.now() - lastTime >= wait) {
  665. fn.apply(context, args)
  666. lastTime = Date.now()
  667. }
  668. }, Math.max(wait - (Date.now() - lastTime), 0))
  669. }
  670. }
  671. }
  672. export function pointInRect(
  673. point: number[],
  674. minX: number,
  675. minY: number,
  676. maxX: number,
  677. maxY: number
  678. ) {
  679. return !(
  680. point[0] < minX ||
  681. point[0] > maxX ||
  682. point[1] < minY ||
  683. point[1] > maxY
  684. )
  685. }
  686. /**
  687. * Get the intersection of two rays, with origin points p0 and p1, and direction vectors n0 and n1.
  688. * @param p0 The origin point of the first ray
  689. * @param n0 The direction vector of the first ray
  690. * @param p1 The origin point of the second ray
  691. * @param n1 The direction vector of the second ray
  692. * @returns
  693. */
  694. export function getRayRayIntersection(
  695. p0: number[],
  696. n0: number[],
  697. p1: number[],
  698. n1: number[]
  699. ) {
  700. const p0e = vec.add(p0, n0),
  701. p1e = vec.add(p1, n1),
  702. m0 = (p0e[1] - p0[1]) / (p0e[0] - p0[0]),
  703. m1 = (p1e[1] - p1[1]) / (p1e[0] - p1[0]),
  704. b0 = p0[1] - m0 * p0[0],
  705. b1 = p1[1] - m1 * p1[0],
  706. x = (b1 - b0) / (m0 - m1),
  707. y = m0 * x + b0
  708. return [x, y]
  709. }
  710. export async function postJsonToEndpoint(
  711. endpoint: string,
  712. data: { [key: string]: unknown }
  713. ) {
  714. const d = await fetch(
  715. `${process.env.NEXT_PUBLIC_BASE_API_URL}/api/${endpoint}`,
  716. {
  717. method: "POST",
  718. headers: { "Content-Type": "application/json" },
  719. body: JSON.stringify(data),
  720. }
  721. )
  722. return await d.json()
  723. }