bezier.ts 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. import { distance } from './util';
  2. import { Point, PointTuple } from './types';
  3. const EPSILON = 0.0001;
  4. /**
  5. * 使用牛顿切割法求最近的点
  6. * @param {number[]} xArr 点的 x 数组
  7. * @param {number[]} yArr 点的 y 数组
  8. * @param {number} x 指定的点 x
  9. * @param {number} y 指定的点 y
  10. * @param {Function} tCallback 差值函数
  11. */
  12. export function nearestPoint(
  13. xArr: number[],
  14. yArr: number[],
  15. x: number,
  16. y: number,
  17. tCallback: (...arr: number[]) => number,
  18. length?: number
  19. ): Point {
  20. let t: number;
  21. let d = Infinity;
  22. const v0: PointTuple = [x, y];
  23. let segNum = 20;
  24. if (length && length > 200) {
  25. segNum = length / 10;
  26. }
  27. const increaseRate = 1 / segNum;
  28. let interval = increaseRate / 10;
  29. for (let i = 0; i <= segNum; i++) {
  30. const _t = i * increaseRate;
  31. const v1: PointTuple = [tCallback.apply(null, xArr.concat([_t])), tCallback.apply(null, yArr.concat([_t]))];
  32. const d1 = distance(v0[0], v0[1], v1[0], v1[1]);
  33. if (d1 < d) {
  34. t = _t;
  35. d = d1;
  36. }
  37. }
  38. // 提前终止
  39. if (t === 0) {
  40. return {
  41. x: xArr[0],
  42. y: yArr[0],
  43. };
  44. }
  45. if (t === 1) {
  46. const count = xArr.length;
  47. return {
  48. x: xArr[count - 1],
  49. y: yArr[count - 1],
  50. };
  51. }
  52. d = Infinity;
  53. for (let i = 0; i < 32; i++) {
  54. if (interval < EPSILON) {
  55. break;
  56. }
  57. const prev = t - interval;
  58. const next = t + interval;
  59. const v1 = [tCallback.apply(null, xArr.concat([prev])), tCallback.apply(null, yArr.concat([prev]))];
  60. const d1 = distance(v0[0], v0[1], v1[0], v1[1]);
  61. if (prev >= 0 && d1 < d) {
  62. t = prev;
  63. d = d1;
  64. } else {
  65. const v2 = [tCallback.apply(null, xArr.concat([next])), tCallback.apply(null, yArr.concat([next]))];
  66. const d2 = distance(v0[0], v0[1], v2[0], v2[1]);
  67. if (next <= 1 && d2 < d) {
  68. t = next;
  69. d = d2;
  70. } else {
  71. interval *= 0.5;
  72. }
  73. }
  74. }
  75. return {
  76. x: tCallback.apply(null, xArr.concat([t])),
  77. y: tCallback.apply(null, yArr.concat([t])),
  78. };
  79. }
  80. // 近似求解 https://community.khronos.org/t/3d-cubic-bezier-segment-length/62363/2
  81. export function snapLength(xArr: number[], yArr: number[]) {
  82. let totalLength = 0;
  83. const count = xArr.length;
  84. for (let i = 0; i < count; i++) {
  85. const x = xArr[i];
  86. const y = yArr[i];
  87. const nextX = xArr[(i + 1) % count];
  88. const nextY = yArr[(i + 1) % count];
  89. totalLength += distance(x, y, nextX, nextY);
  90. }
  91. return totalLength / 2;
  92. }