Last active
March 26, 2020 22:36
-
-
Save arce/9dd11df69ad73e422c7a7b106f54c78e to your computer and use it in GitHub Desktop.
Arc by bezier
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
I stumbled upon this problem recently. I compiled a solution from the articles mentioned here in the form of a module. | |
It accepts start angle, end angle, center and radius as input. | |
It approximates small arcs (<= PI/2) pretty well. If you need to approximate something arcs from PI/2 to 2*PI you can always break them in parts < PI/2, calculate the according curves and join them afterward. | |
This solution is start and end angle order agnostic - it always picks the minor arc. | |
As a result you get all four points you need to define a cubic bezier curve in absolute coordinates. | |
I think this is best explained in code and comments: | |
'use strict'; | |
module.exports = function (angleStart, angleEnd, center, radius) { | |
// assuming angleStart and angleEnd are in degrees | |
const angleStartRadians = angleStart * Math.PI / 180; | |
const angleEndRadians = angleEnd * Math.PI / 180; | |
// Finding the coordinates of the control points in a simplified case where the center of the circle is at [0,0] | |
const relControlPoints = getRelativeControlPoints(angleStartRadians, angleEndRadians, radius); | |
return { | |
pointStart: getPointAtAngle(angleStartRadians, center, radius), | |
pointEnd: getPointAtAngle(angleEndRadians, center, radius), | |
// To get the absolute control point coordinates we just translate by the center coordinates | |
controlPoint1: { | |
x: center.x + relControlPoints[0].x, | |
y: center.y + relControlPoints[0].y | |
}, | |
controlPoint2: { | |
x: center.x + relControlPoints[1].x, | |
y: center.y + relControlPoints[1].y | |
} | |
}; | |
}; | |
function getRelativeControlPoints(angleStart, angleEnd, radius) { | |
// factor is the commonly reffered parameter K in the articles about arc to cubic bezier approximation | |
const factor = getApproximationFactor(angleStart, angleEnd); | |
// Distance from [0, 0] to each of the control points. Basically this is the hypotenuse of the triangle [0,0], a control point and the projection of the point on Ox | |
const distToCtrPoint = Math.sqrt(radius * radius * (1 + factor * factor)); | |
// Angle between the hypotenuse and Ox for control point 1. | |
const angle1 = angleStart + Math.atan(factor); | |
// Angle between the hypotenuse and Ox for control point 2. | |
const angle2 = angleEnd - Math.atan(factor); | |
return [ | |
{ | |
x: Math.cos(angle1) * distToCtrPoint, | |
y: Math.sin(angle1) * distToCtrPoint | |
}, | |
{ | |
x: Math.cos(angle2) * distToCtrPoint, | |
y: Math.sin(angle2) * distToCtrPoint | |
} | |
]; | |
} | |
function getPointAtAngle(angle, center, radius) { | |
return { | |
x: center.x + radius * Math.cos(angle), | |
y: center.y + radius * Math.sin(angle) | |
}; | |
} | |
// Calculating K as done in https://pomax.github.io/bezierinfo/#circles_cubic | |
function getApproximationFactor(angleStart, angleEnd) { | |
let arc = angleEnd - angleStart; | |
// Always choose the smaller arc | |
if (Math.abs(arc) > Math.PI) { | |
arc -= Math.PI * 2; | |
arc %= Math.PI * 2; | |
} | |
return (4 / 3) * Math.tan(arc / 4); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment