arc.ts 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. import { distance, piMod } from './util';
  2. import ellipse from './ellipse';
  3. import { Point, BBox } from './types';
  4. // 偏导数 x
  5. function derivativeXAt(
  6. cx: number,
  7. cy: number,
  8. rx: number,
  9. ry: number,
  10. xRotation: number,
  11. startAngle: number,
  12. endAngle: number,
  13. angle: number
  14. ) {
  15. return -1 * rx * Math.cos(xRotation) * Math.sin(angle) - ry * Math.sin(xRotation) * Math.cos(angle);
  16. }
  17. // 偏导数 y
  18. function derivativeYAt(
  19. cx: number,
  20. cy: number,
  21. rx: number,
  22. ry: number,
  23. xRotation: number,
  24. startAngle: number,
  25. endAngle: number,
  26. angle: number
  27. ) {
  28. return -1 * rx * Math.sin(xRotation) * Math.sin(angle) + ry * Math.cos(xRotation) * Math.cos(angle);
  29. }
  30. // x 的极值
  31. function xExtrema(rx: number, ry: number, xRotation: number) {
  32. return Math.atan((-ry / rx) * Math.tan(xRotation));
  33. }
  34. // y 的极值
  35. function yExtrema(rx: number, ry: number, xRotation: number) {
  36. return Math.atan(ry / (rx * Math.tan(xRotation)));
  37. }
  38. // 根据角度求 x 坐标
  39. function xAt(cx: number, cy: number, rx: number, ry: number, xRotation: number, angle: number) {
  40. return rx * Math.cos(xRotation) * Math.cos(angle) - ry * Math.sin(xRotation) * Math.sin(angle) + cx;
  41. }
  42. // 根据角度求 y 坐标
  43. function yAt(cx: number, cy: number, rx: number, ry: number, xRotation: number, angle: number) {
  44. return rx * Math.sin(xRotation) * Math.cos(angle) + ry * Math.cos(xRotation) * Math.sin(angle) + cy;
  45. }
  46. // 获取点在椭圆上的角度
  47. function getAngle(rx: number, ry: number, x0: number, y0: number) {
  48. const angle = Math.atan2(y0 * rx, x0 * ry);
  49. // 转换到 0 - 2PI 内
  50. return (angle + Math.PI * 2) % (Math.PI * 2);
  51. }
  52. // 根据角度获取,x,y
  53. function getPoint(rx: number, ry: number, angle: number): Point {
  54. return {
  55. x: rx * Math.cos(angle),
  56. y: ry * Math.sin(angle),
  57. };
  58. }
  59. // 旋转
  60. function rotate(x: number, y: number, angle: number) {
  61. const cos = Math.cos(angle);
  62. const sin = Math.sin(angle);
  63. return [x * cos - y * sin, x * sin + y * cos];
  64. }
  65. export default {
  66. /**
  67. * 计算包围盒
  68. * @param {number} cx 圆心 x
  69. * @param {number} cy 圆心 y
  70. * @param {number} rx x 轴方向的半径
  71. * @param {number} ry y 轴方向的半径
  72. * @param {number} xRotation 旋转角度
  73. * @param {number} startAngle 起始角度
  74. * @param {number} endAngle 结束角度
  75. * @return {object} 包围盒对象
  76. */
  77. box(cx: number, cy: number, rx: number, ry: number, xRotation: number, startAngle: number, endAngle: number): BBox {
  78. const xDim = xExtrema(rx, ry, xRotation);
  79. let minX = Infinity;
  80. let maxX = -Infinity;
  81. const xs = [startAngle, endAngle];
  82. for (let i = -Math.PI * 2; i <= Math.PI * 2; i += Math.PI) {
  83. const xAngle = xDim + i;
  84. if (startAngle < endAngle) {
  85. if (startAngle < xAngle && xAngle < endAngle) {
  86. xs.push(xAngle);
  87. }
  88. } else {
  89. if (endAngle < xAngle && xAngle < startAngle) {
  90. xs.push(xAngle);
  91. }
  92. }
  93. }
  94. for (let i = 0; i < xs.length; i++) {
  95. const x = xAt(cx, cy, rx, ry, xRotation, xs[i]);
  96. if (x < minX) {
  97. minX = x;
  98. }
  99. if (x > maxX) {
  100. maxX = x;
  101. }
  102. }
  103. const yDim = yExtrema(rx, ry, xRotation);
  104. let minY = Infinity;
  105. let maxY = -Infinity;
  106. const ys = [startAngle, endAngle];
  107. for (let i = -Math.PI * 2; i <= Math.PI * 2; i += Math.PI) {
  108. const yAngle = yDim + i;
  109. if (startAngle < endAngle) {
  110. if (startAngle < yAngle && yAngle < endAngle) {
  111. ys.push(yAngle);
  112. }
  113. } else {
  114. if (endAngle < yAngle && yAngle < startAngle) {
  115. ys.push(yAngle);
  116. }
  117. }
  118. }
  119. for (let i = 0; i < ys.length; i++) {
  120. const y = yAt(cx, cy, rx, ry, xRotation, ys[i]);
  121. if (y < minY) {
  122. minY = y;
  123. }
  124. if (y > maxY) {
  125. maxY = y;
  126. }
  127. }
  128. return {
  129. x: minX,
  130. y: minY,
  131. width: maxX - minX,
  132. height: maxY - minY,
  133. };
  134. },
  135. /**
  136. * 获取圆弧的长度,计算圆弧长度时不考虑旋转角度,
  137. * 仅跟 rx, ry, startAngle, endAngle 相关
  138. * @param {number} cx 圆心 x
  139. * @param {number} cy 圆心 y
  140. * @param {number} rx x 轴方向的半径
  141. * @param {number} ry y 轴方向的半径
  142. * @param {number} xRotation 旋转角度
  143. * @param {number} startAngle 起始角度
  144. * @param {number} endAngle 结束角度
  145. */
  146. length(cx: number, cy: number, rx: number, ry: number, xRotation: number, startAngle: number, endAngle: number) {},
  147. /**
  148. * 获取指定点到圆弧的最近距离的点
  149. * @param {number} cx 圆心 x
  150. * @param {number} cy 圆心 y
  151. * @param {number} rx x 轴方向的半径
  152. * @param {number} ry y 轴方向的半径
  153. * @param {number} xRotation 旋转角度
  154. * @param {number} startAngle 起始角度
  155. * @param {number} endAngle 结束角度
  156. * @param {number} x0 指定点的 x
  157. * @param {number} y0 指定点的 y
  158. * @return {object} 到指定点最近距离的点
  159. */
  160. nearestPoint(
  161. cx: number,
  162. cy: number,
  163. rx: number,
  164. ry: number,
  165. xRotation: number,
  166. startAngle: number,
  167. endAngle: number,
  168. x0: number,
  169. y0: number
  170. ) {
  171. // 将最近距离问题转换成到椭圆中心 0,0 没有旋转的椭圆问题
  172. const relativeVector = rotate(x0 - cx, y0 - cy, -xRotation);
  173. const [x1, y1] = relativeVector;
  174. // 计算点到椭圆的最近的点
  175. let relativePoint = ellipse.nearestPoint(0, 0, rx, ry, x1, y1);
  176. // 获取点在椭圆上的角度
  177. const angle = getAngle(rx, ry, relativePoint.x, relativePoint.y);
  178. // 点没有在圆弧上
  179. if (angle < startAngle) {
  180. // 小于起始圆弧
  181. relativePoint = getPoint(rx, ry, startAngle);
  182. } else if (angle > endAngle) {
  183. // 大于结束圆弧
  184. relativePoint = getPoint(rx, ry, endAngle);
  185. }
  186. // 旋转到 xRotation 的角度
  187. const vector = rotate(relativePoint.x, relativePoint.y, xRotation);
  188. return {
  189. x: vector[0] + cx,
  190. y: vector[1] + cy,
  191. };
  192. },
  193. pointDistance(
  194. cx: number,
  195. cy: number,
  196. rx: number,
  197. ry: number,
  198. xRotation: number,
  199. startAngle: number,
  200. endAngle: number,
  201. x0: number,
  202. y0: number
  203. ) {
  204. const nearestPoint = this.nearestPoint(cx, cy, rx, ry, x0, y0);
  205. return distance(nearestPoint.x, nearestPoint.y, x0, y0);
  206. },
  207. pointAt(
  208. cx: number,
  209. cy: number,
  210. rx: number,
  211. ry: number,
  212. xRotation: number,
  213. startAngle: number,
  214. endAngle: number,
  215. t: number
  216. ): Point {
  217. const angle = (endAngle - startAngle) * t + startAngle;
  218. return {
  219. x: xAt(cx, cy, rx, ry, xRotation, angle),
  220. y: yAt(cx, cy, rx, ry, xRotation, angle),
  221. };
  222. },
  223. tangentAngle(
  224. cx: number,
  225. cy: number,
  226. rx: number,
  227. ry: number,
  228. xRotation: number,
  229. startAngle: number,
  230. endAngle: number,
  231. t: number
  232. ) {
  233. const angle = (endAngle - startAngle) * t + startAngle;
  234. const dx = derivativeXAt(cx, cy, rx, ry, xRotation, startAngle, endAngle, angle);
  235. const dy = derivativeYAt(cx, cy, rx, ry, xRotation, startAngle, endAngle, angle);
  236. return piMod(Math.atan2(dy, dx));
  237. },
  238. };