arc2bezier.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. //https://github.com/colinmeinke/svg-arc-to-cubic-bezier
  2. const TAU = Math.PI * 2;
  3. const mapToEllipse = ({ x, y }, rx, ry, cosphi, sinphi, centerx, centery ) => {
  4. x *= rx;
  5. y *= ry;
  6. const xp = cosphi * x - sinphi * y;
  7. const yp = sinphi * x + cosphi * y;
  8. return {
  9. x: xp + centerx,
  10. y: yp + centery,
  11. };
  12. };
  13. const approxUnitArc = ( ang1, ang2 ) => {
  14. const a = 4 / 3 * Math.tan( ang2 / 4 );
  15. const x1 = Math.cos( ang1 );
  16. const y1 = Math.sin( ang1 );
  17. const x2 = Math.cos( ang1 + ang2 );
  18. const y2 = Math.sin( ang1 + ang2 );
  19. return [
  20. {
  21. x: x1 - y1 * a,
  22. y: y1 + x1 * a,
  23. },
  24. {
  25. x: x2 + y2 * a,
  26. y: y2 - x2 * a,
  27. },
  28. {
  29. x: x2,
  30. y: y2,
  31. },
  32. ];
  33. };
  34. const vectorAngle = ( ux, uy, vx, vy ) => {
  35. const sign = ( ux * vy - uy * vx < 0 ) ? -1 : 1;
  36. const umag = Math.sqrt( ux * ux + uy * uy );
  37. const vmag = Math.sqrt( ux * ux + uy * uy );
  38. const dot = ux * vx + uy * vy;
  39. let div = dot / ( umag * vmag );
  40. if ( div > 1 ) {
  41. div = 1
  42. }
  43. if ( div < -1 ) {
  44. div = -1;
  45. }
  46. return sign * Math.acos( div );
  47. };
  48. const getArcCenter = (
  49. px,
  50. py,
  51. cx,
  52. cy,
  53. rx,
  54. ry,
  55. largeArcFlag,
  56. sweepFlag,
  57. sinphi,
  58. cosphi,
  59. pxp,
  60. pyp
  61. ) => {
  62. const rxsq = Math.pow( rx, 2 );
  63. const rysq = Math.pow( ry, 2 );
  64. const pxpsq = Math.pow( pxp, 2 );
  65. const pypsq = Math.pow( pyp, 2 );
  66. let radicant = ( rxsq * rysq ) - ( rxsq * pypsq ) - ( rysq * pxpsq );
  67. if ( radicant < 0 ) {
  68. radicant = 0;
  69. }
  70. radicant /= ( rxsq * pypsq ) + ( rysq * pxpsq );
  71. radicant = Math.sqrt( radicant ) * ( largeArcFlag === sweepFlag ? -1 : 1 );
  72. const centerxp = radicant * rx / ry * pyp;
  73. const centeryp = radicant * -ry / rx * pxp;
  74. const centerx = cosphi * centerxp - sinphi * centeryp + ( px + cx ) / 2;
  75. const centery = sinphi * centerxp + cosphi * centeryp + ( py + cy ) / 2;
  76. const vx1 = ( pxp - centerxp ) / rx;
  77. const vy1 = ( pyp - centeryp ) / ry;
  78. const vx2 = ( -pxp - centerxp ) / rx;
  79. const vy2 = ( -pyp - centeryp ) / ry;
  80. let ang1 = vectorAngle( 1, 0, vx1, vy1 );
  81. let ang2 = vectorAngle( vx1, vy1, vx2, vy2 );
  82. if ( sweepFlag === 0 && ang2 > 0 ) {
  83. ang2 -= TAU;
  84. }
  85. if ( sweepFlag === 1 && ang2 < 0 ) {
  86. ang2 += TAU;
  87. }
  88. return [ centerx, centery, ang1, ang2 ];
  89. };
  90. const arcToBezier = ({
  91. px,
  92. py,
  93. cx,
  94. cy,
  95. rx,
  96. ry,
  97. xAxisRotation = 0,
  98. largeArcFlag = 0,
  99. sweepFlag = 0
  100. }) => {
  101. const curves = [];
  102. if ( rx === 0 || ry === 0 ) {
  103. return [];
  104. }
  105. const sinphi = Math.sin( xAxisRotation * TAU / 360 );
  106. const cosphi = Math.cos( xAxisRotation * TAU / 360 );
  107. const pxp = cosphi * ( px - cx ) / 2 + sinphi * ( py - cy ) / 2;
  108. const pyp = -sinphi * ( px - cx ) / 2 + cosphi * ( py - cy ) / 2;
  109. if ( pxp === 0 && pyp === 0 ) {
  110. return [];
  111. }
  112. rx = Math.abs( rx );
  113. ry = Math.abs( ry );
  114. const lambda =
  115. Math.pow( pxp, 2 ) / Math.pow( rx, 2 ) +
  116. Math.pow( pyp, 2 ) / Math.pow( ry, 2 );
  117. if ( lambda > 1 ) {
  118. rx *= Math.sqrt( lambda );
  119. ry *= Math.sqrt( lambda );
  120. }
  121. let [ centerx, centery, ang1, ang2 ] = getArcCenter(
  122. px,
  123. py,
  124. cx,
  125. cy,
  126. rx,
  127. ry,
  128. largeArcFlag,
  129. sweepFlag,
  130. sinphi,
  131. cosphi,
  132. pxp,
  133. pyp
  134. );
  135. const segments = Math.max( Math.ceil( Math.abs( ang2 ) / ( TAU / 4 )), 1);
  136. ang2 /= segments;
  137. for ( let i = 0; i < segments; i++ ) {
  138. curves.push( approxUnitArc( ang1, ang2 ));
  139. ang1 += ang2;
  140. }
  141. return curves.map( curve => {
  142. const { x: x1, y: y1 } = mapToEllipse( curve[ 0 ], rx, ry, cosphi, sinphi, centerx, centery );
  143. const { x: x2, y: y2 } = mapToEllipse( curve[ 1 ], rx, ry, cosphi, sinphi, centerx, centery );
  144. const { x, y } = mapToEllipse( curve[ 2 ], rx, ry, cosphi, sinphi, centerx, centery );
  145. return { x1, y1, x2, y2, x, y };
  146. });
  147. };
  148. export default arcToBezier;