1
+ using Elements . Validators ;
1
2
using System ;
2
3
using System . Collections . Generic ;
3
4
using Elements . Geometry . Interfaces ;
6
7
namespace Elements . Geometry
7
8
{
8
9
/// <summary>
9
- /// A circle.
10
+ /// A circle.
10
11
/// Parameterization of the circle is 0 -> 2PI.
11
12
/// </summary>
12
- public class Circle : Curve , IConic
13
+ public class Circle : Curve , IConic , IHasArcLength
13
14
{
14
15
/// <summary>The center of the circle.</summary>
15
16
[ JsonProperty ( "Center" , Required = Required . AllowNull ) ]
@@ -26,6 +27,17 @@ public Vector3 Center
26
27
[ System . ComponentModel . DataAnnotations . Range ( 0.0D , double . MaxValue ) ]
27
28
public double Radius { get ; protected set ; }
28
29
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
+
29
41
/// <summary>
30
42
/// The coordinate system of the plane containing the circle.
31
43
/// </summary>
@@ -39,7 +51,15 @@ public Vector3 Center
39
51
[ JsonConstructor ]
40
52
public Circle ( Vector3 center , double radius = 1.0 )
41
53
{
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
+ }
42
61
this . Radius = radius ;
62
+ this . Circumference = 2 * Math . PI * this . Radius ;
43
63
this . Transform = new Transform ( center ) ;
44
64
}
45
65
@@ -49,7 +69,15 @@ public Circle(Vector3 center, double radius = 1.0)
49
69
/// <param name="radius">The radius of the circle.</param>
50
70
public Circle ( double radius = 1.0 )
51
71
{
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
+ }
52
79
this . Radius = radius ;
80
+ this . Circumference = 2 * Math . PI * this . Radius ;
53
81
this . Transform = new Transform ( ) ;
54
82
}
55
83
@@ -58,8 +86,65 @@ public Circle(double radius = 1.0)
58
86
/// </summary>
59
87
public Circle ( Transform transform , double radius = 1.0 )
60
88
{
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
+ }
61
96
this . Transform = transform ;
62
97
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 ;
63
148
}
64
149
65
150
/// <summary>
@@ -83,6 +168,14 @@ public Polygon ToPolygon(int divisions = 10)
83
168
return new Polygon ( pts , true ) ;
84
169
}
85
170
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
+
86
179
/// <summary>
87
180
/// Convert a circle to a circular arc.
88
181
/// </summary>
@@ -94,6 +187,15 @@ public Polygon ToPolygon(int divisions = 10)
94
187
/// <param name="c">The bounded curve to convert.</param>
95
188
public static implicit operator ModelCurve ( Circle c ) => new ModelCurve ( c ) ;
96
189
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
+
97
199
/// <summary>
98
200
/// Return the point at parameter u on the arc.
99
201
/// </summary>
@@ -111,6 +213,46 @@ private Vector3 PointAtUntransformed(double u)
111
213
return new Vector3 ( x , y ) ;
112
214
}
113
215
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
+
114
256
/// <summary>
115
257
/// Return transform on the arc at parameter u.
116
258
/// </summary>
@@ -148,5 +290,42 @@ public override double ParameterAtDistanceFromParameter(double distance, double
148
290
149
291
return start + theta ;
150
292
}
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
+ }
151
330
}
152
331
}
0 commit comments