Reputation: 155
I have an geometric arc defined by the two end points (P0 = x1,y1 and P4 = x2,y2) and either the radius R or the center of the arc (C = xc, yc). I need to calculate the two control points P2, P3 for the cubic Bézier curve in javascript. In my particular case the arc angle will be less than 90 degrees.
I have search the internet and stackoverflow and any solutions are incomplete, not generalized for an arc of indeterminate angle, or too complex for me to understand.
Does anyone have any javascript code or pseudo code that would help? I previously asked a similar question but improperly referred to the Bézier curve as quadratic when I need a cubic Bézier curve.
Upvotes: 2
Views: 3278
Reputation: 11295
VBA Implementation of @Mbo's code:
I have no idea how this works without PI, or any Tan, but here it is:
I use it to draw curves, or arc of a circle using VBA in Excel. I was not able to figure out how to place the control points, but the code above just did it! I'm not gona pretend I understand any of it, but I figured the VBA implementation might be valuable, expecially since the documentation on the topic of the .addCurve method is extremely scarce.
Note that I've noticed the arc will progressively difform when using large rotations, but it's the only piece of code I was able to implement successfully.
Private Type Coordinates
x As long
y As long
End Type
Sub BezierArcByPoints(x1 As Long, _
y1 As Long, _
x2 As Long, _
y2 As Long, _
cx As Long, _
cy As Long, _
R As Double)
Dim t1x As Double
Dim t1y As Double
Dim t2x As Double
Dim t2y As Double
Dim dx As Double
Dim dy As Double
Dim k As Double
Dim tx As Double
Dim ty As Double
Dim D As Double
Dim a As Double
Dim b As Double
Dim c As Double
t1x = cy - y1
t1y = x1 - cx
t2x = y2 - cy
t2y = cx - x2
dx = (x1 + x2) / 2 - cx
dy = (y1 + y2) / 2 - cy
tx = 3 / 8 * (t1x + t2x)
ty = 3 / 8 * (t1y + t2y)
a = tx * tx + ty * ty
b = dx * tx + dy * ty
c = dx * dx + dy * dy - R * R
D = b * b - a * c
Dim c1 As Coordinates
Dim c2 As Coordinates
If D > 0 Then
k = (D ^ 0.5 - b) / a
c1.x = x1 + Round(k * t1x)
c1.y = y1 + Round(k * t1y)
c2.x = x2 + Round(k * t2x)
c2.y = y2 + Round(k * t2y)
Dim Pts(1 To 4, 1 To 2) As Single
Pts(1, 1) = x1
Pts(1, 2) = y1
Pts(2, 1) = c1.x ' Bezier control point 1
Pts(2, 2) = c1.y
Pts(3, 1) = c2.x ' Bezier control point 2
Pts(3, 2) = c2.y
Pts(4, 1) = x2
Pts(4, 2) = y2
End If
ActiveSheet.Shapes.AddCurve(SafeArrayOfPoints:=Pts).Select
Selection.ShapeRange.Fill.Visible = msoFalse
Selection.name = "objArc"
With Selection.ShapeRange.Line
.Visible = msoTrue
.DashStyle = msoLineSysDash
.ForeColor.RGB = RGB(255, 0, 0)
.Transparency = 0
End With
' If you have little circles to display where the control points are located
Dim objAnchor As Shape
Dim objAnchor2 As Shape
Set objAnchor = ActiveSheet.Shapes("objAnchor")
Set objAnchor2 = ActiveSheet.Shapes("objAnchor2")
Call setObjCoord(objAnchor, c1, True)
Call setObjCoord(objAnchor2, c2, True)
End Sub
Note that this function is only able to draw clockwise. If you have a negative rotation, make sure to reverse x1, y1 and x2, y2.
Example:
If Rotation < 0 Then
Call BezierArcByPoints(pointA.x, pointA.y, pointB.x, pointB.y, Axis.x, Axis.y, Radius)
Else
Call BezierArcByPoints(pointB.x, pointB.y, pointA.x, pointA.y, Axis.x, Axis.y, Radius)
End If
Upvotes: 0
Reputation: 80325
Cubic Bezier approximation of the circle arc, defined by coordinates of starting and ending points, center and radius of the circle – (x1,y1) = P0, (x2,y2) = P3, C = (cx,cy), R
.
(Approximation of arс, defined by angles, could be found here)
Control points of Bezier should be on the tangents to given points on the circle. One possible approximation method – middle point of (symmetric) curve should lay on the circle.
(Note that for good approximation arc angle cannot be large)
Radius-vectors of given points:
V1 = (x1-cx, y1 - cy)
V2 = (x2-cx, y2 - cy)
Tangent vectors:
T1 = (cy – y1, x1 – cx)
T2 = (y2 - cy, cx – x2)
Coordinates of control points (k – unknown yet factor):
P1 = P0 + k * T1
P2 = P3 + k * T2
Middle point of Bezier:
MB = B(1/2) = P0 * 1/8 + P1 * 3/8 + P2 * 3/8 + P3 * 1/8 =
P0 * 1/8 + P0 * 3/8 + k * T1 * 3/8 + P3 * 3/8 + k * T2 * 3/8 + P3 * 1/8 =
(P0 + P3)/2 + k * 3/8 * (T1 +T2)
Now solve equation against k factor
(MB.X – cx)^2 + (MB.Y – cy)^2 = R^2
There are two solutions possible – we need positive one, if input points are in the right order (in common case for arcs < Pi - with the smallest magnitude)
Delphi code (doesn't check input data, doesn't treat extra cases) and it's result:
procedure BezierArcByPoints(x1, y1, x2, y2, cx, cy, R: Integer;
var Pts: array of TPoint);
var
t1x, t1y, t2x, t2y, dx, dy, k, tx, ty, D, a, b, c: Double;
begin
t1x := cy - y1;
t1y := x1 - cx;
t2x := y2 - cy;
t2y := cx - x2;
dx := (x1 + x2) / 2 - cx;
dy := (y1 + y2) / 2 - cy;
tx := 3 / 8 * (t1x + t2x);
ty := 3 / 8 * (t1y + t2y);
a := tx * tx + ty * ty;
b := dx * tx + dy * ty;
c := dx * dx + dy * dy - R * R;
D := b * b - a * c;
if D > 0 then begin
k := (Sqrt(D) - b) / a;
Pts[0] := Point(x1, y1);
Pts[3] := Point(x2, y2);
Pts[1] := Point(x1 + Round(k * t1x), y1 + Round(k * t1y));
Pts[2] := Point(x2 + Round(k * t2x), y2 + Round(k * t2y));
end;
end;
var
Pts: array [0 .. 3] of TPoint;
an1, an2: Double;
begin
an1 := 0;
an2 := Pi / 2;
Canvas.Pen.Color := clBlue;
Canvas.Pen.Width := 1;
Canvas.Ellipse(100, 100, 301, 301);
BezierArcByPoints(200 + Round(100 * Cos(an1)),
200 + Round(100 * Sin(an1)),
200 + Round(100 * Cos(an2)),
200 + Round(100 * Sin(an2)),
200, 200, 100, Pts);
Canvas.Pen.Color := clRed;
Canvas.Pen.Width := 3;
Canvas.PolyBezier(Pts);
end;
Upvotes: 6