Skip to content

Commit 2afac10

Browse files
committed
Circle implements Interface IHasArcLength
1 parent 7309fc7 commit 2afac10

File tree

4 files changed

+323
-4
lines changed

4 files changed

+323
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
- `Bezier` now inherits from `BoundedCurve`.
3838
- `Polyline` is now parameterized 0->length.
3939
- `Circle` is now parameterized 0->2Pi.
40-
- `Line` is now parameterized 0->length.
40+
- `Circle` now implements the `IHasArcLength` interface
4141
- `Vector3.DistanceTo(Ray ray)` now returns positive infinity instead of throwing.
4242
- `Message`: removed obsolete `FromLine` method.
4343
- `AdaptiveGrid`: removed obsolete `TryGetVertexIndex` with `tolerance` parameter.

Elements/src/Geometry/Circle.cs

Lines changed: 181 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using Elements.Validators;
12
using System;
23
using System.Collections.Generic;
34
using Elements.Geometry.Interfaces;
@@ -6,10 +7,10 @@
67
namespace Elements.Geometry
78
{
89
/// <summary>
9-
/// A circle.
10+
/// A circle.
1011
/// Parameterization of the circle is 0 -> 2PI.
1112
/// </summary>
12-
public class Circle : Curve, IConic
13+
public class Circle : Curve, IConic, IHasArcLength
1314
{
1415
/// <summary>The center of the circle.</summary>
1516
[JsonProperty("Center", Required = Required.AllowNull)]
@@ -26,6 +27,17 @@ public Vector3 Center
2627
[System.ComponentModel.DataAnnotations.Range(0.0D, double.MaxValue)]
2728
public double Radius { get; protected set; }
2829

30+
/// <summary>The circumference of the circle.</summary>
31+
[JsonIgnore]
32+
[System.ComponentModel.DataAnnotations.Range(0.0D, double.MaxValue)]
33+
public double Circumference { get; protected set; }
34+
35+
/// <summary>
36+
/// The domain of the curve.
37+
/// </summary>
38+
[JsonIgnore]
39+
public Domain1d Domain => new Domain1d(0, 2 * Math.PI);
40+
2941
/// <summary>
3042
/// The coordinate system of the plane containing the circle.
3143
/// </summary>
@@ -39,7 +51,15 @@ public Vector3 Center
3951
[JsonConstructor]
4052
public Circle(Vector3 center, double radius = 1.0)
4153
{
54+
if (!Validator.DisableValidationOnConstruction)
55+
{
56+
if (Math.Abs(radius - 0.0) < double.Epsilon ? true : false)
57+
{
58+
throw new ArgumentException($"The circle could not be created. The radius of the circle cannot be the zero: radius {radius}");
59+
}
60+
}
4261
this.Radius = radius;
62+
this.Circumference = 2 * Math.PI * this.Radius;
4363
this.Transform = new Transform(center);
4464
}
4565

@@ -49,7 +69,15 @@ public Circle(Vector3 center, double radius = 1.0)
4969
/// <param name="radius">The radius of the circle.</param>
5070
public Circle(double radius = 1.0)
5171
{
72+
if (!Validator.DisableValidationOnConstruction)
73+
{
74+
if (Math.Abs(radius - 0.0) < double.Epsilon ? true : false)
75+
{
76+
throw new ArgumentException($"The circle could not be created. The radius of the circle cannot be the zero: radius {radius}");
77+
}
78+
}
5279
this.Radius = radius;
80+
this.Circumference = 2 * Math.PI * this.Radius;
5381
this.Transform = new Transform();
5482
}
5583

@@ -58,8 +86,65 @@ public Circle(double radius = 1.0)
5886
/// </summary>
5987
public Circle(Transform transform, double radius = 1.0)
6088
{
89+
if (!Validator.DisableValidationOnConstruction)
90+
{
91+
if (Math.Abs(radius - 0.0) < double.Epsilon ? true : false)
92+
{
93+
throw new ArgumentException($"The circle could not be created. The radius of the circle cannot be the zero: radius {radius}");
94+
}
95+
}
6196
this.Transform = transform;
6297
this.Radius = radius;
98+
this.Circumference = 2 * Math.PI * this.Radius;
99+
}
100+
101+
/// <summary>
102+
/// Calculate the length of the circle between two parameters.
103+
/// </summary>
104+
public double ArcLength(double start, double end)
105+
{
106+
// Convert start and end parameters from radians to degrees
107+
double _startAngle = start * 180.0 / Math.PI;
108+
double _endAngle = end * 180.0 / Math.PI;
109+
110+
// Ensure the start angle is within the valid domain range of 0 to 360 degrees
111+
double startAngle = _startAngle % 360;
112+
if (startAngle < 0)
113+
{
114+
startAngle += 360;
115+
}
116+
117+
// Ensure the end angle is within the valid domain range of 0 to 360 degrees
118+
double endAngle = _endAngle % 360;
119+
if (endAngle < 0)
120+
{
121+
endAngle += 360;
122+
}
123+
else if (endAngle == 0 && Math.Abs(_endAngle) >= 2 * Math.PI)
124+
{
125+
endAngle = 360;
126+
}
127+
128+
// Calculate the difference in angles
129+
double angleDifference = endAngle - startAngle;
130+
131+
// Adjust the angle difference if it crosses the 360-degree boundary
132+
if (angleDifference < 0)
133+
{
134+
angleDifference += 360;
135+
}
136+
else if (angleDifference >= 2 * Math.PI)
137+
{
138+
return Circumference; // Full circle, return circumference
139+
}
140+
141+
// Convert the angle difference back to radians
142+
double angleDifferenceRadians = angleDifference * Math.PI / 180.0;
143+
144+
// Calculate the arc length using the formula: arc length = radius * angle
145+
double arcLength = Radius * angleDifferenceRadians;
146+
147+
return arcLength;
63148
}
64149

65150
/// <summary>
@@ -83,6 +168,14 @@ public Polygon ToPolygon(int divisions = 10)
83168
return new Polygon(pts, true);
84169
}
85170

171+
/// <summary>
172+
/// Are the two circles almost equal?
173+
/// </summary>
174+
public bool IsAlmostEqualTo(Circle other, double tolerance = Vector3.EPSILON)
175+
{
176+
return (Center.IsAlmostEqualTo(other.Center, tolerance) && Math.Abs(Radius - other.Radius) < tolerance ? true : false);
177+
}
178+
86179
/// <summary>
87180
/// Convert a circle to a circular arc.
88181
/// </summary>
@@ -94,6 +187,15 @@ public Polygon ToPolygon(int divisions = 10)
94187
/// <param name="c">The bounded curve to convert.</param>
95188
public static implicit operator ModelCurve(Circle c) => new ModelCurve(c);
96189

190+
/// <summary>
191+
/// Calculates and returns the midpoint of the circle.
192+
/// </summary>
193+
/// <returns>The midpoint of the circle.</returns>
194+
public Vector3 MidPoint()
195+
{
196+
return PointAt(Math.PI);
197+
}
198+
97199
/// <summary>
98200
/// Return the point at parameter u on the arc.
99201
/// </summary>
@@ -111,6 +213,46 @@ private Vector3 PointAtUntransformed(double u)
111213
return new Vector3(x, y);
112214
}
113215

216+
/// <summary>
217+
/// Calculates and returns the point on the circle at a specific arc length.
218+
/// </summary>
219+
/// <param name="length">The arc length along the circumference of the circle.</param>
220+
/// <returns>The point on the circle at the specified arc length.</returns>
221+
public Vector3 PointAtLength(double length)
222+
{
223+
double parameter = (length / Circumference) * 2 * Math.PI;
224+
return PointAt(parameter);
225+
}
226+
227+
/// <summary>
228+
/// Calculates and returns the point on the circle at a normalized arc length.
229+
/// </summary>
230+
/// <param name="normalizedLength">The normalized arc length between 0 and 1.</param>
231+
/// <returns>The point on the circle at the specified normalized arc length.</returns>
232+
public Vector3 PointAtNormalizedLength(double normalizedLength)
233+
{
234+
double parameter = normalizedLength * 2 * Math.PI;
235+
return PointAt(parameter);
236+
}
237+
238+
/// <summary>
239+
/// Calculates the parameter within the range of 0 to 2π at a given point on the circle.
240+
/// </summary>
241+
/// <param name="point">The point on the circle.</param>
242+
/// <returns>The parameter within the range of 0 to 2π at the given point on the circle.</returns>
243+
public double GetParameterAt(Vector3 point)
244+
{
245+
Vector3 relativePoint = point - Center;
246+
247+
double theta = Math.Atan2(relativePoint.Y, relativePoint.X);
248+
249+
if (theta < 0)
250+
{
251+
theta += 2 * Math.PI;
252+
}
253+
return theta;
254+
}
255+
114256
/// <summary>
115257
/// Return transform on the arc at parameter u.
116258
/// </summary>
@@ -148,5 +290,42 @@ public override double ParameterAtDistanceFromParameter(double distance, double
148290

149291
return start + theta;
150292
}
293+
294+
/// <summary>
295+
/// Divides the circle into segments of the specified length and returns a list of points representing the division.
296+
/// </summary>
297+
/// <param name="length">The length of each segment.</param>
298+
/// <returns>A list of points representing the division of the circle.</returns>
299+
public Vector3[] DivideByLength(double length)
300+
{
301+
List<Vector3> points = new List<Vector3>();
302+
double circumference = 2 * Math.PI * Radius;
303+
int segmentCount = (int)Math.Ceiling(circumference / length);
304+
double segmentLength = circumference / segmentCount;
305+
306+
for (int i = 0; i < segmentCount; i++)
307+
{
308+
double parameter = i * segmentLength / circumference;
309+
points.Add(PointAtNormalizedLength(parameter));
310+
}
311+
312+
return points.ToArray();
313+
}
314+
315+
/// <summary>
316+
/// Checks if a given point lies on a circle within a specified tolerance.
317+
/// </summary>
318+
/// <param name="point">The point to be checked.</param>
319+
/// <param name="circle">The circle to check against.</param>
320+
/// <param name="tolerance">The tolerance value (optional). Default is 1E-05.</param>
321+
/// <returns>True if the point lies on the circle within the tolerance, otherwise false.</returns>
322+
public static bool PointOnCircle(Vector3 point, Circle circle, double tolerance = 1E-05)
323+
{
324+
Vector3 centerToPoint = point - circle.Center;
325+
double distanceToCenter = centerToPoint.Length();
326+
327+
// Check if the distance from the point to the center is within the tolerance of the circle's radius
328+
return Math.Abs(distanceToCenter - circle.Radius) < tolerance;
329+
}
151330
}
152331
}

0 commit comments

Comments
 (0)