Circles and arcs: 3-point arcs and tangents
Describe arcs by centre, radius and angles; build an arc through three points; get arc length, sagitta and bulge; and find the tangent points from a point to a circle.
| Form | Stored as | Where you meet it |
|---|---|---|
Arc.Create(plane, radius, start, end), DXF ARC entities | ||
A point on a circle
Arc length = r × sweep, with the sweep in radians. In degrees, multiply by π ÷ 180 first. The tangent at θ is (−sin θ, cos θ) for counterclockwise travel: at right angles to the radius.
An arc through three points
var p1 = new V2(0, 0);var p2 = new V2(4, 2);var p3 = new V2(8, 0); var (c, r) = Circumcircle(p1, p2, p3);double a1 = Math.Atan2(p1.Y - c.Y, p1.X - c.X);double a3 = Math.Atan2(p3.Y - c.Y, p3.X - c.X);bool ccw = V2.Cross(p2 - p1, p3 - p2) > 0; // does the path turn left?double sweep = ccw ? Wrap(a3 - a1) : -Wrap(a1 - a3); Console.WriteLine($"Centre {c}, radius {r}");Console.WriteLine($"Sweep {sweep * 180 / Math.PI:F2} deg ({(ccw ? "counterclockwise" : "clockwise")})");Console.WriteLine($"Arc length {r * Math.Abs(sweep):F3} m, chord {(p3 - p1).Length} m"); static double Wrap(double a) => a < 0 ? a + 2 * Math.PI : a; // into [0, 2 pi) static (V2 Centre, double Radius) Circumcircle(V2 a, V2 b, V2 c){ V2 ab = b - a, ac = c - a; double d = 2 * V2.Cross(ab, ac); if (Math.Abs(d) < 1e-12) throw new ArgumentException("The points are collinear: no arc"); double ab2 = V2.Dot(ab, ab), ac2 = V2.Dot(ac, ac); var centre = new V2(ac.Y * ab2 - ab.Y * ac2, ab.X * ac2 - ac.X * ab2) * (1 / d); return (a + centre, centre.Length);} readonly record struct V2(double X, double Y){ public static V2 operator +(V2 a, V2 b) => new(a.X + b.X, a.Y + b.Y); public static V2 operator -(V2 a, V2 b) => new(a.X - b.X, a.Y - b.Y); public static V2 operator *(V2 v, double k) => new(v.X * k, v.Y * k); public static double Dot(V2 a, V2 b) => a.X * b.X + a.Y * b.Y; public static double Cross(V2 a, V2 b) => a.X * b.Y - a.Y * b.X; public double Length => Math.Sqrt(X * X + Y * Y); public override string ToString() => $"({X:0.###}, {Y:0.###})";}Centre (4, -3), radius 5 Sweep -106.26 deg (clockwise) Arc length 9.273 m, chord 8 m
Sagitta, bulge and radius from a chord
double width = 2.4, rise = 0.4; // an arched openingdouble h = width / 2; double r = (h * h + rise * rise) / (2 * rise);double bulge = rise / h;double sweep = 4 * Math.Atan(bulge); // tan(sweep / 4) = bulgeConsole.WriteLine($"Radius {r:F3} m");Console.WriteLine($"Sweep {sweep * 180 / Math.PI:F2} deg, bulge {bulge:F4}");Console.WriteLine($"Arc length {r * sweep:F3} m (the chord is {width} m)");Radius 2.000 m Sweep 73.74 deg, bulge 0.3333 Arc length 2.574 m (the chord is 2.4 m)
Tangents from a point
var c = new V2(0, 0);double r = 3; // a round tankvar p = new V2(5, 0); // a valve outside it V2 cp = p - c;double dist = cp.Length;if (dist <= r) { Console.WriteLine("The point is inside the circle: no tangents"); return; } double alpha = Math.Acos(r / dist); // angle at the centredouble toP = Math.Atan2(cp.Y, cp.X);foreach (double sign in new[] { 1.0, -1.0 }){ double angle = toP + sign * alpha; Console.WriteLine($"Tangent point {c + new V2(Math.Cos(angle), Math.Sin(angle)) * r}");}Console.WriteLine($"Tangent length {Math.Sqrt(dist * dist - r * r)} m"); readonly record struct V2(double X, double Y){ public static V2 operator +(V2 a, V2 b) => new(a.X + b.X, a.Y + b.Y); public static V2 operator -(V2 a, V2 b) => new(a.X - b.X, a.Y - b.Y); public static V2 operator *(V2 v, double k) => new(v.X * k, v.Y * k); public static double Dot(V2 a, V2 b) => a.X * b.X + a.Y * b.Y; public static double Cross(V2 a, V2 b) => a.X * b.Y - a.Y * b.X; public double Length => Math.Sqrt(X * X + Y * Y); public override string ToString() => $"({X:0.###}, {Y:0.###})";}Tangent point (1.8, 2.4) Tangent point (1.8, -2.4) Tangent length 4 m
Show a hint
Show the solution
var c = new V2(10, 0);double r = 10, start = 90 * Math.PI / 180, end = 180 * Math.PI / 180; V2 At(double angle) => c + new V2(Math.Cos(angle), Math.Sin(angle)) * r; Console.WriteLine($"Start {At(start)}, end {At(end)}");Console.WriteLine($"Length {r * (end - start):F3} m");for (int i = 1; i <= 3; i++) Console.WriteLine($"Window {i}: {At(start + (end - start) * i / 4)}"); readonly record struct V2(double X, double Y){ public static V2 operator +(V2 a, V2 b) => new(a.X + b.X, a.Y + b.Y); public static V2 operator -(V2 a, V2 b) => new(a.X - b.X, a.Y - b.Y); public static V2 operator *(V2 v, double k) => new(v.X * k, v.Y * k); public static double Dot(V2 a, V2 b) => a.X * b.X + a.Y * b.Y; public static double Cross(V2 a, V2 b) => a.X * b.Y - a.Y * b.X; public double Length => Math.Sqrt(X * X + Y * Y); public override string ToString() => $"({X:0.###}, {Y:0.###})";}Start (10, 10), end (0, 0) Length 15.708 m Window 1: (6.173, 9.239) Window 2: (2.929, 7.071) Window 3: (0.761, 3.827)
Remember
A point on a circle is C + r(cos θ, sin θ); an arc limits θ to a range. Arc length is r × sweep in radians; the tangent is at right angles to the radius. The centre of a 3-point arc is where the perpendicular bisectors cross; collinear points have no arc. From a chord and a rise: r = (h² + s²) ÷ (2s); the DXF bulge is tan(sweep ÷ 4) = s ÷ h. Tangents from an outside point P touch the circle at an angle acos(r ÷ |P − C|) either side of CP.