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.

utils.ts 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751
  1. import { Bounds } from 'types'
  2. import vec from 'utils/vec'
  3. /**
  4. * ## Utils
  5. */
  6. export default class Utils {
  7. /**
  8. * Linear interpolation betwen two numbers.
  9. * @param y1
  10. * @param y2
  11. * @param mu
  12. */
  13. static lerp(y1: number, y2: number, mu: number): number {
  14. mu = Utils.clamp(mu, 0, 1)
  15. return y1 * (1 - mu) + y2 * mu
  16. }
  17. /**
  18. * Modulate a value between two ranges.
  19. * @param value
  20. * @param rangeA from [low, high]
  21. * @param rangeB to [low, high]
  22. * @param clamp
  23. */
  24. static modulate(
  25. value: number,
  26. rangeA: number[],
  27. rangeB: number[],
  28. clamp = false
  29. ): number {
  30. const [fromLow, fromHigh] = rangeA
  31. const [v0, v1] = rangeB
  32. const result = v0 + ((value - fromLow) / (fromHigh - fromLow)) * (v1 - v0)
  33. return clamp
  34. ? v0 < v1
  35. ? Math.max(Math.min(result, v1), v0)
  36. : Math.max(Math.min(result, v0), v1)
  37. : result
  38. }
  39. /**
  40. * Clamp a value into a range.
  41. * @param n
  42. * @param min
  43. */
  44. static clamp(n: number, min: number): number
  45. static clamp(n: number, min: number, max: number): number
  46. static clamp(n: number, min: number, max?: number): number {
  47. return Math.max(min, typeof max !== 'undefined' ? Math.min(n, max) : n)
  48. }
  49. // TODO: replace with a string compression algorithm
  50. static compress(s: string): string {
  51. return s
  52. }
  53. // TODO: replace with a string decompression algorithm
  54. static decompress(s: string): string {
  55. return s
  56. }
  57. /**
  58. * Recursively clone an object or array.
  59. * @param obj
  60. */
  61. static deepClone<T>(obj: T): T {
  62. if (obj === null) return null
  63. const clone: any = { ...obj }
  64. Object.keys(obj).forEach(
  65. (key) =>
  66. (clone[key] =
  67. typeof obj[key] === 'object' ? Utils.deepClone(obj[key]) : obj[key])
  68. )
  69. if (Array.isArray(obj)) {
  70. clone.length = obj.length
  71. return Array.from(clone) as any as T
  72. }
  73. return clone as T
  74. }
  75. /**
  76. * Seeded random number generator, using [xorshift](https://en.wikipedia.org/wiki/Xorshift).
  77. * The result will always be betweeen -1 and 1.
  78. *
  79. * Adapted from [seedrandom](https://github.com/davidbau/seedrandom).
  80. */
  81. static rng(seed = ''): () => number {
  82. let x = 0
  83. let y = 0
  84. let z = 0
  85. let w = 0
  86. function next() {
  87. const t = x ^ (x << 11)
  88. ;(x = y), (y = z), (z = w)
  89. w ^= ((w >>> 19) ^ t ^ (t >>> 8)) >>> 0
  90. return w / 0x100000000
  91. }
  92. for (let k = 0; k < seed.length + 64; k++) {
  93. ;(x ^= seed.charCodeAt(k) | 0), next()
  94. }
  95. return next
  96. }
  97. /**
  98. * Shuffle the contents of an array.
  99. * @param arr
  100. * @param offset
  101. */
  102. static shuffleArr<T>(arr: T[], offset: number): T[] {
  103. return arr.map((_, i) => arr[(i + offset) % arr.length])
  104. }
  105. /**
  106. * Deep compare two arrays.
  107. * @param a
  108. * @param b
  109. */
  110. static deepCompareArrays<T>(a: T[], b: T[]): boolean {
  111. if (a?.length !== b?.length) return false
  112. return Utils.deepCompare(a, b)
  113. }
  114. /**
  115. * Deep compare any values.
  116. * @param a
  117. * @param b
  118. */
  119. static deepCompare<T>(a: T, b: T): boolean {
  120. return a === b || JSON.stringify(a) === JSON.stringify(b)
  121. }
  122. /**
  123. * Find whether two arrays intersect.
  124. * @param a
  125. * @param b
  126. * @param fn An optional function to apply to the items of a; will check if b includes the result.
  127. */
  128. static arrsIntersect<T, K>(a: T[], b: K[], fn?: (item: K) => T): boolean
  129. static arrsIntersect<T>(a: T[], b: T[]): boolean
  130. static arrsIntersect<T>(
  131. a: T[],
  132. b: unknown[],
  133. fn?: (item: unknown) => T
  134. ): boolean {
  135. return a.some((item) => b.includes(fn ? fn(item) : item))
  136. }
  137. /**
  138. * Get the unique values from an array of strings or numbers.
  139. * @param items
  140. */
  141. static uniqueArray<T extends string | number>(...items: T[]): T[] {
  142. return Array.from(new Set(items).values())
  143. }
  144. /**
  145. * Convert a set to an array.
  146. * @param set
  147. */
  148. static setToArray<T>(set: Set<T>): T[] {
  149. return Array.from(set.values())
  150. }
  151. /**
  152. * Get the outer of between a circle and a point.
  153. * @param C The circle's center.
  154. * @param r The circle's radius.
  155. * @param P The point.
  156. * @param side
  157. */
  158. static getCircleTangentToPoint(
  159. C: number[],
  160. r: number,
  161. P: number[],
  162. side: number
  163. ): number[] {
  164. const B = vec.lrp(C, P, 0.5),
  165. r1 = vec.dist(C, B),
  166. delta = vec.sub(B, C),
  167. d = vec.len(delta)
  168. if (!(d <= r + r1 && d >= Math.abs(r - r1))) {
  169. return
  170. }
  171. const a = (r * r - r1 * r1 + d * d) / (2.0 * d),
  172. n = 1 / d,
  173. p = vec.add(C, vec.mul(delta, a * n)),
  174. h = Math.sqrt(r * r - a * a),
  175. k = vec.mul(vec.per(delta), h * n)
  176. return side === 0 ? vec.add(p, k) : vec.sub(p, k)
  177. }
  178. /**
  179. * Get outer tangents of two circles.
  180. * @param x0
  181. * @param y0
  182. * @param r0
  183. * @param x1
  184. * @param y1
  185. * @param r1
  186. * @returns [lx0, ly0, lx1, ly1, rx0, ry0, rx1, ry1]
  187. */
  188. static getOuterTangentsOfCircles(
  189. C0: number[],
  190. r0: number,
  191. C1: number[],
  192. r1: number
  193. ): number[][] {
  194. const a0 = vec.angle(C0, C1)
  195. const d = vec.dist(C0, C1)
  196. // Circles are overlapping, no tangents
  197. if (d < Math.abs(r1 - r0)) return
  198. const a1 = Math.acos((r0 - r1) / d),
  199. t0 = a0 + a1,
  200. t1 = a0 - a1
  201. return [
  202. [C0[0] + r0 * Math.cos(t1), C0[1] + r0 * Math.sin(t1)],
  203. [C1[0] + r1 * Math.cos(t1), C1[1] + r1 * Math.sin(t1)],
  204. [C0[0] + r0 * Math.cos(t0), C0[1] + r0 * Math.sin(t0)],
  205. [C1[0] + r1 * Math.cos(t0), C1[1] + r1 * Math.sin(t0)],
  206. ]
  207. }
  208. /**
  209. * Get the closest point on the perimeter of a circle to a given point.
  210. * @param C The circle's center.
  211. * @param r The circle's radius.
  212. * @param P The point.
  213. */
  214. static getClosestPointOnCircle(
  215. C: number[],
  216. r: number,
  217. P: number[]
  218. ): number[] {
  219. const v = vec.sub(C, P)
  220. return vec.sub(C, vec.mul(vec.div(v, vec.len(v)), r))
  221. }
  222. static det(
  223. a: number,
  224. b: number,
  225. c: number,
  226. d: number,
  227. e: number,
  228. f: number,
  229. g: number,
  230. h: number,
  231. i: number
  232. ): number {
  233. return a * e * i + b * f * g + c * d * h - a * f * h - b * d * i - c * e * g
  234. }
  235. /**
  236. * Get a circle from three points.
  237. * @param A
  238. * @param B
  239. * @param C
  240. * @returns [x, y, r]
  241. */
  242. static circleFromThreePoints(
  243. A: number[],
  244. B: number[],
  245. C: number[]
  246. ): number[] {
  247. const a = Utils.det(A[0], A[1], 1, B[0], B[1], 1, C[0], C[1], 1)
  248. const bx = -Utils.det(
  249. A[0] * A[0] + A[1] * A[1],
  250. A[1],
  251. 1,
  252. B[0] * B[0] + B[1] * B[1],
  253. B[1],
  254. 1,
  255. C[0] * C[0] + C[1] * C[1],
  256. C[1],
  257. 1
  258. )
  259. const by = Utils.det(
  260. A[0] * A[0] + A[1] * A[1],
  261. A[0],
  262. 1,
  263. B[0] * B[0] + B[1] * B[1],
  264. B[0],
  265. 1,
  266. C[0] * C[0] + C[1] * C[1],
  267. C[0],
  268. 1
  269. )
  270. const c = -Utils.det(
  271. A[0] * A[0] + A[1] * A[1],
  272. A[0],
  273. A[1],
  274. B[0] * B[0] + B[1] * B[1],
  275. B[0],
  276. B[1],
  277. C[0] * C[0] + C[1] * C[1],
  278. C[0],
  279. C[1]
  280. )
  281. const x = -bx / (2 * a)
  282. const y = -by / (2 * a)
  283. const r = Math.sqrt(bx * bx + by * by - 4 * a * c) / (2 * Math.abs(a))
  284. return [x, y, r]
  285. }
  286. /**
  287. * Find the approximate perimeter of an ellipse.
  288. * @param rx
  289. * @param ry
  290. */
  291. static perimeterOfEllipse(rx: number, ry: number): number {
  292. const h = Math.pow(rx - ry, 2) / Math.pow(rx + ry, 2)
  293. const p = Math.PI * (rx + ry) * (1 + (3 * h) / (10 + Math.sqrt(4 - 3 * h)))
  294. return p
  295. }
  296. /**
  297. * Get the short angle distance between two angles.
  298. * @param a0
  299. * @param a1
  300. */
  301. static shortAngleDist(a0: number, a1: number): number {
  302. const max = Math.PI * 2
  303. const da = (a1 - a0) % max
  304. return ((2 * da) % max) - da
  305. }
  306. /**
  307. * Get the long angle distance between two angles.
  308. * @param a0
  309. * @param a1
  310. */
  311. static longAngleDist(a0: number, a1: number): number {
  312. return Math.PI * 2 - Utils.shortAngleDist(a0, a1)
  313. }
  314. /**
  315. * Interpolate an angle between two angles.
  316. * @param a0
  317. * @param a1
  318. * @param t
  319. */
  320. static lerpAngles(a0: number, a1: number, t: number): number {
  321. return a0 + Utils.shortAngleDist(a0, a1) * t
  322. }
  323. /**
  324. * Get the short distance between two angles.
  325. * @param a0
  326. * @param a1
  327. */
  328. static angleDelta(a0: number, a1: number): number {
  329. return Utils.shortAngleDist(a0, a1)
  330. }
  331. /**
  332. * Get the "sweep" or short distance between two points on a circle's perimeter.
  333. * @param C
  334. * @param A
  335. * @param B
  336. */
  337. static getSweep(C: number[], A: number[], B: number[]): number {
  338. return Utils.angleDelta(vec.angle(C, A), vec.angle(C, B))
  339. }
  340. /**
  341. * Rotate a point around a center.
  342. * @param x The x-axis coordinate of the point.
  343. * @param y The y-axis coordinate of the point.
  344. * @param cx The x-axis coordinate of the point to rotate round.
  345. * @param cy The y-axis coordinate of the point to rotate round.
  346. * @param angle The distance (in radians) to rotate.
  347. */
  348. static rotatePoint(A: number[], B: number[], angle: number): number[] {
  349. const s = Math.sin(angle)
  350. const c = Math.cos(angle)
  351. const px = A[0] - B[0]
  352. const py = A[1] - B[1]
  353. const nx = px * c - py * s
  354. const ny = px * s + py * c
  355. return [nx + B[0], ny + B[1]]
  356. }
  357. /**
  358. * Clamp radians within 0 and 2PI
  359. * @param r
  360. */
  361. static clampRadians(r: number): number {
  362. return (Math.PI * 2 + r) % (Math.PI * 2)
  363. }
  364. /**
  365. * Clamp rotation to even segments.
  366. * @param r
  367. * @param segments
  368. */
  369. static clampToRotationToSegments(r: number, segments: number): number {
  370. const seg = (Math.PI * 2) / segments
  371. return Math.floor((Utils.clampRadians(r) + seg / 2) / seg) * seg
  372. }
  373. /**
  374. * Is angle c between angles a and b?
  375. * @param a
  376. * @param b
  377. * @param c
  378. */
  379. static isAngleBetween(a: number, b: number, c: number): boolean {
  380. if (c === a || c === b) return true
  381. const PI2 = Math.PI * 2
  382. const AB = (b - a + PI2) % PI2
  383. const AC = (c - a + PI2) % PI2
  384. return AB <= Math.PI !== AC > AB
  385. }
  386. /**
  387. * Convert degrees to radians.
  388. * @param d
  389. */
  390. static degreesToRadians(d: number): number {
  391. return (d * Math.PI) / 180
  392. }
  393. /**
  394. * Convert radians to degrees.
  395. * @param r
  396. */
  397. static radiansToDegrees(r: number): number {
  398. return (r * 180) / Math.PI
  399. }
  400. /**
  401. * Get the length of an arc between two points on a circle's perimeter.
  402. * @param C
  403. * @param r
  404. * @param A
  405. * @param B
  406. */
  407. static getArcLength(
  408. C: number[],
  409. r: number,
  410. A: number[],
  411. B: number[]
  412. ): number {
  413. const sweep = Utils.getSweep(C, A, B)
  414. return r * (2 * Math.PI) * (sweep / (2 * Math.PI))
  415. }
  416. /**
  417. * Get a dash offset for an arc, based on its length.
  418. * @param C
  419. * @param r
  420. * @param A
  421. * @param B
  422. * @param step
  423. */
  424. static getArcDashOffset(
  425. C: number[],
  426. r: number,
  427. A: number[],
  428. B: number[],
  429. step: number
  430. ): number {
  431. const del0 = Utils.getSweep(C, A, B)
  432. const len0 = Utils.getArcLength(C, r, A, B)
  433. const off0 = del0 < 0 ? len0 : 2 * Math.PI * C[2] - len0
  434. return -off0 / 2 + step
  435. }
  436. /**
  437. * Get a dash offset for an ellipse, based on its length.
  438. * @param A
  439. * @param step
  440. */
  441. static getEllipseDashOffset(A: number[], step: number): number {
  442. const c = 2 * Math.PI * A[2]
  443. return -c / 2 + -step
  444. }
  445. /**
  446. * Get an array of points between two points.
  447. * @param a
  448. * @param b
  449. * @param options
  450. */
  451. static getPointsBetween(
  452. a: number[],
  453. b: number[],
  454. options = {} as {
  455. steps?: number
  456. ease?: (t: number) => number
  457. }
  458. ): number[][] {
  459. const { steps = 6, ease = (t) => t * t * t } = options
  460. return Array.from(Array(steps))
  461. .map((_, i) => ease(i / steps))
  462. .map((t) => [...vec.lrp(a, b, t), (1 - t) / 2])
  463. }
  464. static getRayRayIntersection(
  465. p0: number[],
  466. n0: number[],
  467. p1: number[],
  468. n1: number[]
  469. ): number[] {
  470. const p0e = vec.add(p0, n0),
  471. p1e = vec.add(p1, n1),
  472. m0 = (p0e[1] - p0[1]) / (p0e[0] - p0[0]),
  473. m1 = (p1e[1] - p1[1]) / (p1e[0] - p1[0]),
  474. b0 = p0[1] - m0 * p0[0],
  475. b1 = p1[1] - m1 * p1[0],
  476. x = (b1 - b0) / (m0 - m1),
  477. y = m0 * x + b0
  478. return [x, y]
  479. }
  480. static bez1d(a: number, b: number, c: number, d: number, t: number): number {
  481. return (
  482. a * (1 - t) * (1 - t) * (1 - t) +
  483. 3 * b * t * (1 - t) * (1 - t) +
  484. 3 * c * t * t * (1 - t) +
  485. d * t * t * t
  486. )
  487. }
  488. static getCubicBezierBounds(
  489. p0: number[],
  490. c0: number[],
  491. c1: number[],
  492. p1: number[]
  493. ): Bounds {
  494. // solve for x
  495. let a = 3 * p1[0] - 9 * c1[0] + 9 * c0[0] - 3 * p0[0]
  496. let b = 6 * p0[0] - 12 * c0[0] + 6 * c1[0]
  497. let c = 3 * c0[0] - 3 * p0[0]
  498. let disc = b * b - 4 * a * c
  499. let xl = p0[0]
  500. let xh = p0[0]
  501. if (p1[0] < xl) xl = p1[0]
  502. if (p1[0] > xh) xh = p1[0]
  503. if (disc >= 0) {
  504. const t1 = (-b + Math.sqrt(disc)) / (2 * a)
  505. if (t1 > 0 && t1 < 1) {
  506. const x1 = Utils.bez1d(p0[0], c0[0], c1[0], p1[0], t1)
  507. if (x1 < xl) xl = x1
  508. if (x1 > xh) xh = x1
  509. }
  510. const t2 = (-b - Math.sqrt(disc)) / (2 * a)
  511. if (t2 > 0 && t2 < 1) {
  512. const x2 = Utils.bez1d(p0[0], c0[0], c1[0], p1[0], t2)
  513. if (x2 < xl) xl = x2
  514. if (x2 > xh) xh = x2
  515. }
  516. }
  517. // Solve for y
  518. a = 3 * p1[1] - 9 * c1[1] + 9 * c0[1] - 3 * p0[1]
  519. b = 6 * p0[1] - 12 * c0[1] + 6 * c1[1]
  520. c = 3 * c0[1] - 3 * p0[1]
  521. disc = b * b - 4 * a * c
  522. let yl = p0[1]
  523. let yh = p0[1]
  524. if (p1[1] < yl) yl = p1[1]
  525. if (p1[1] > yh) yh = p1[1]
  526. if (disc >= 0) {
  527. const t1 = (-b + Math.sqrt(disc)) / (2 * a)
  528. if (t1 > 0 && t1 < 1) {
  529. const y1 = Utils.bez1d(p0[1], c0[1], c1[1], p1[1], t1)
  530. if (y1 < yl) yl = y1
  531. if (y1 > yh) yh = y1
  532. }
  533. const t2 = (-b - Math.sqrt(disc)) / (2 * a)
  534. if (t2 > 0 && t2 < 1) {
  535. const y2 = Utils.bez1d(p0[1], c0[1], c1[1], p1[1], t2)
  536. if (y2 < yl) yl = y2
  537. if (y2 > yh) yh = y2
  538. }
  539. }
  540. return {
  541. minX: xl,
  542. minY: yl,
  543. maxX: xh,
  544. maxY: yh,
  545. width: Math.abs(xl - xh),
  546. height: Math.abs(yl - yh),
  547. }
  548. }
  549. static getExpandedBounds(a: Bounds, b: Bounds): Bounds {
  550. const minX = Math.min(a.minX, b.minX),
  551. minY = Math.min(a.minY, b.minY),
  552. maxX = Math.max(a.maxX, b.maxX),
  553. maxY = Math.max(a.maxY, b.maxY),
  554. width = Math.abs(maxX - minX),
  555. height = Math.abs(maxY - minY)
  556. return { minX, minY, maxX, maxY, width, height }
  557. }
  558. static getCommonBounds(...b: Bounds[]): Bounds {
  559. if (b.length < 2) return b[0]
  560. let bounds = b[0]
  561. for (let i = 1; i < b.length; i++) {
  562. bounds = Utils.getExpandedBounds(bounds, b[i])
  563. }
  564. return bounds
  565. }
  566. /**
  567. * Get a bezier curve data for a spline that fits an array of points.
  568. * @param pts
  569. * @param tension
  570. * @param isClosed
  571. * @param numOfSegments
  572. */
  573. static getCurvePoints(
  574. pts: number[][],
  575. tension = 0.5,
  576. isClosed = false,
  577. numOfSegments = 3
  578. ): number[][] {
  579. const _pts = [...pts],
  580. len = pts.length,
  581. res: number[][] = [] // results
  582. let t1x: number, // tension vectors
  583. t2x: number,
  584. t1y: number,
  585. t2y: number,
  586. c1: number, // cardinal points
  587. c2: number,
  588. c3: number,
  589. c4: number,
  590. st: number,
  591. st2: number,
  592. st3: number
  593. // The algorithm require a previous and next point to the actual point array.
  594. // Check if we will draw closed or open curve.
  595. // If closed, copy end points to beginning and first points to end
  596. // If open, duplicate first points to befinning, end points to end
  597. if (isClosed) {
  598. _pts.unshift(_pts[len - 1])
  599. _pts.push(_pts[0])
  600. } else {
  601. //copy 1. point and insert at beginning
  602. _pts.unshift(_pts[0])
  603. _pts.push(_pts[len - 1])
  604. // _pts.push(_pts[len - 1])
  605. }
  606. // For each point, calculate a segment
  607. for (let i = 1; i < _pts.length - 2; i++) {
  608. // Calculate points along segment and add to results
  609. for (let t = 0; t <= numOfSegments; t++) {
  610. // Step
  611. st = t / numOfSegments
  612. st2 = Math.pow(st, 2)
  613. st3 = Math.pow(st, 3)
  614. // Cardinals
  615. c1 = 2 * st3 - 3 * st2 + 1
  616. c2 = -(2 * st3) + 3 * st2
  617. c3 = st3 - 2 * st2 + st
  618. c4 = st3 - st2
  619. // Tension
  620. t1x = (_pts[i + 1][0] - _pts[i - 1][0]) * tension
  621. t2x = (_pts[i + 2][0] - _pts[i][0]) * tension
  622. t1y = (_pts[i + 1][1] - _pts[i - 1][1]) * tension
  623. t2y = (_pts[i + 2][1] - _pts[i][1]) * tension
  624. // Control points
  625. res.push([
  626. c1 * _pts[i][0] + c2 * _pts[i + 1][0] + c3 * t1x + c4 * t2x,
  627. c1 * _pts[i][1] + c2 * _pts[i + 1][1] + c3 * t1y + c4 * t2y,
  628. ])
  629. }
  630. }
  631. res.push(pts[pts.length - 1])
  632. return res
  633. }
  634. /**
  635. * Simplify a line (using Ramer-Douglas-Peucker algorithm).
  636. * @param points An array of points as [x, y, ...][]
  637. * @param tolerance The minimum line distance (also called epsilon).
  638. * @returns Simplified array as [x, y, ...][]
  639. */
  640. static simplify(points: number[][], tolerance = 1): number[][] {
  641. const len = points.length,
  642. a = points[0],
  643. b = points[len - 1],
  644. [x1, y1] = a,
  645. [x2, y2] = b
  646. if (len > 2) {
  647. let distance = 0
  648. let index = 0
  649. const max = Math.hypot(y2 - y1, x2 - x1)
  650. for (let i = 1; i < len - 1; i++) {
  651. const [x0, y0] = points[i],
  652. d =
  653. Math.abs((y2 - y1) * x0 - (x2 - x1) * y0 + x2 * y1 - y2 * x1) / max
  654. if (distance > d) continue
  655. distance = d
  656. index = i
  657. }
  658. if (distance > tolerance) {
  659. const l0 = Utils.simplify(points.slice(0, index + 1), tolerance)
  660. const l1 = Utils.simplify(points.slice(index + 1), tolerance)
  661. return l0.concat(l1.slice(1))
  662. }
  663. }
  664. return [a, b]
  665. }
  666. }