quadratic.ts 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. import line from './line';
  2. import { distance, isNumberEqual, getBBoxByArray, piMod } from './util';
  3. import { nearestPoint } from './bezier';
  4. import { Point } from './types';
  5. // 差值公式
  6. function quadraticAt(p0: number, p1: number, p2: number, t: number) {
  7. const onet = 1 - t;
  8. return onet * onet * p0 + 2 * t * onet * p1 + t * t * p2;
  9. }
  10. // 求极值
  11. function extrema(p0: number, p1: number, p2: number) {
  12. const a = p0 + p2 - 2 * p1;
  13. if (isNumberEqual(a, 0)) {
  14. return [0.5];
  15. }
  16. const rst = (p0 - p1) / a;
  17. if (rst <= 1 && rst >= 0) {
  18. return [rst];
  19. }
  20. return [];
  21. }
  22. function derivativeAt(p0: number, p1: number, p2: number, t: number) {
  23. return 2 * (1 - t) * (p1 - p0) + 2 * t * (p2 - p1);
  24. }
  25. // 分割贝塞尔曲线
  26. function divideQuadratic(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, t: number) {
  27. // 划分点
  28. const xt = quadraticAt(x1, x2, x3, t);
  29. const yt = quadraticAt(y1, y2, y3, t);
  30. // 分割的第一条曲线的控制点
  31. const controlPoint1 = line.pointAt(x1, y1, x2, y2, t);
  32. // 分割的第二条曲线的控制点
  33. const controlPoint2 = line.pointAt(x2, y2, x3, y3, t);
  34. return [
  35. [x1, y1, controlPoint1.x, controlPoint1.y, xt, yt],
  36. [xt, yt, controlPoint2.x, controlPoint2.y, x3, y3],
  37. ];
  38. }
  39. // 使用迭代法取贝塞尔曲线的长度
  40. function quadraticLength(
  41. x1: number,
  42. y1: number,
  43. x2: number,
  44. y2: number,
  45. x3: number,
  46. y3: number,
  47. iterationCount: number
  48. ) {
  49. if (iterationCount === 0) {
  50. return (distance(x1, y1, x2, y2) + distance(x2, y2, x3, y3) + distance(x1, y1, x3, y3)) / 2;
  51. }
  52. const quadratics = divideQuadratic(x1, y1, x2, y2, x3, y3, 0.5);
  53. const left = quadratics[0];
  54. const right = quadratics[1];
  55. left.push(iterationCount - 1);
  56. right.push(iterationCount - 1);
  57. return quadraticLength.apply(null, left) + quadraticLength.apply(null, right);
  58. }
  59. export default {
  60. box(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number) {
  61. const xExtrema = extrema(x1, x2, x3)[0];
  62. const yExtrema = extrema(y1, y2, y3)[0];
  63. // 控制点不加入 box 的计算
  64. const xArr = [x1, x3];
  65. const yArr = [y1, y3];
  66. if (xExtrema !== undefined) {
  67. xArr.push(quadraticAt(x1, x2, x3, xExtrema));
  68. }
  69. if (yExtrema !== undefined) {
  70. yArr.push(quadraticAt(y1, y2, y3, yExtrema));
  71. }
  72. return getBBoxByArray(xArr, yArr);
  73. },
  74. length(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number) {
  75. return quadraticLength(x1, y1, x2, y2, x3, y3, 3);
  76. },
  77. nearestPoint(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, x0: number, y0: number) {
  78. return nearestPoint([x1, x2, x3], [y1, y2, y3], x0, y0, quadraticAt);
  79. },
  80. pointDistance(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, x0: number, y0: number) {
  81. const point = this.nearestPoint(x1, y1, x2, y2, x3, y3, x0, y0);
  82. return distance(point.x, point.y, x0, y0);
  83. },
  84. interpolationAt: quadraticAt,
  85. pointAt(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, t: number): Point {
  86. return {
  87. x: quadraticAt(x1, x2, x3, t),
  88. y: quadraticAt(y1, y2, y3, t),
  89. };
  90. },
  91. divide(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, t: number) {
  92. return divideQuadratic(x1, y1, x2, y2, x3, y3, t);
  93. },
  94. tangentAngle(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, t: number) {
  95. const dx = derivativeAt(x1, x2, x3, t);
  96. const dy = derivativeAt(y1, y2, y3, t);
  97. const angle = Math.atan2(dy, dx);
  98. return piMod(angle);
  99. },
  100. };