@@ -125,6 +125,92 @@ impl Ellipse {
125
125
}
126
126
}
127
127
128
+ /// A primitive shape formed by the region between two circles, also known as a ring.
129
+ #[ derive( Clone , Copy , Debug , PartialEq ) ]
130
+ #[ cfg_attr( feature = "serialize" , derive( serde:: Serialize , serde:: Deserialize ) ) ]
131
+ #[ doc( alias = "Ring" ) ]
132
+ pub struct Annulus {
133
+ /// The inner circle of the annulus
134
+ pub inner_circle : Circle ,
135
+ /// The outer circle of the annulus
136
+ pub outer_circle : Circle ,
137
+ }
138
+ impl Primitive2d for Annulus { }
139
+
140
+ impl Default for Annulus {
141
+ /// Returns the default [`Annulus`] with radii of `0.5` and `1.0`.
142
+ fn default ( ) -> Self {
143
+ Self {
144
+ inner_circle : Circle :: new ( 0.5 ) ,
145
+ outer_circle : Circle :: new ( 1.0 ) ,
146
+ }
147
+ }
148
+ }
149
+
150
+ impl Annulus {
151
+ /// Create a new [`Annulus`] from the radii of the inner and outer circle
152
+ #[ inline( always) ]
153
+ pub const fn new ( inner_radius : f32 , outer_radius : f32 ) -> Self {
154
+ Self {
155
+ inner_circle : Circle :: new ( inner_radius) ,
156
+ outer_circle : Circle :: new ( outer_radius) ,
157
+ }
158
+ }
159
+
160
+ /// Get the diameter of the annulus
161
+ #[ inline( always) ]
162
+ pub fn diameter ( & self ) -> f32 {
163
+ self . outer_circle . diameter ( )
164
+ }
165
+
166
+ /// Get the thickness of the annulus
167
+ #[ inline( always) ]
168
+ pub fn thickness ( & self ) -> f32 {
169
+ self . outer_circle . radius - self . inner_circle . radius
170
+ }
171
+
172
+ /// Get the area of the annulus
173
+ #[ inline( always) ]
174
+ pub fn area ( & self ) -> f32 {
175
+ PI * ( self . outer_circle . radius . powi ( 2 ) - self . inner_circle . radius . powi ( 2 ) )
176
+ }
177
+
178
+ /// Get the perimeter or circumference of the annulus,
179
+ /// which is the sum of the perimeters of the inner and outer circles.
180
+ #[ inline( always) ]
181
+ #[ doc( alias = "circumference" ) ]
182
+ pub fn perimeter ( & self ) -> f32 {
183
+ 2.0 * PI * ( self . outer_circle . radius + self . inner_circle . radius )
184
+ }
185
+
186
+ /// Finds the point on the annulus that is closest to the given `point`:
187
+ ///
188
+ /// - If the point is outside of the annulus completely, the returned point will be on the outer perimeter.
189
+ /// - If the point is inside of the inner circle (hole) of the annulus, the returned point will be on the inner perimeter.
190
+ /// - Otherwise, the returned point is overlapping the annulus and returned as is.
191
+ #[ inline( always) ]
192
+ pub fn closest_point ( & self , point : Vec2 ) -> Vec2 {
193
+ let distance_squared = point. length_squared ( ) ;
194
+
195
+ if self . inner_circle . radius . powi ( 2 ) <= distance_squared {
196
+ if distance_squared <= self . outer_circle . radius . powi ( 2 ) {
197
+ // The point is inside the annulus.
198
+ point
199
+ } else {
200
+ // The point is outside the annulus and closer to the outer perimeter.
201
+ // Find the closest point on the perimeter of the annulus.
202
+ let dir_to_point = point / distance_squared. sqrt ( ) ;
203
+ self . outer_circle . radius * dir_to_point
204
+ }
205
+ } else {
206
+ // The point is outside the annulus and closer to the inner perimeter.
207
+ // Find the closest point on the perimeter of the annulus.
208
+ let dir_to_point = point / distance_squared. sqrt ( ) ;
209
+ self . inner_circle . radius * dir_to_point
210
+ }
211
+ }
212
+ }
213
+
128
214
/// An unbounded plane in 2D space. It forms a separating surface through the origin,
129
215
/// stretching infinitely far
130
216
#[ derive( Clone , Copy , Debug , PartialEq ) ]
@@ -718,6 +804,20 @@ mod tests {
718
804
) ;
719
805
}
720
806
807
+ #[ test]
808
+ fn annulus_closest_point ( ) {
809
+ let annulus = Annulus :: new ( 1.5 , 2.0 ) ;
810
+ assert_eq ! ( annulus. closest_point( Vec2 :: X * 10.0 ) , Vec2 :: X * 2.0 ) ;
811
+ assert_eq ! (
812
+ annulus. closest_point( Vec2 :: NEG_ONE ) ,
813
+ Vec2 :: NEG_ONE . normalize( ) * 1.5
814
+ ) ;
815
+ assert_eq ! (
816
+ annulus. closest_point( Vec2 :: new( 1.55 , 0.85 ) ) ,
817
+ Vec2 :: new( 1.55 , 0.85 )
818
+ ) ;
819
+ }
820
+
721
821
#[ test]
722
822
fn circle_math ( ) {
723
823
let circle = Circle { radius : 3.0 } ;
@@ -726,6 +826,15 @@ mod tests {
726
826
assert_eq ! ( circle. perimeter( ) , 18.849556 , "incorrect perimeter" ) ;
727
827
}
728
828
829
+ #[ test]
830
+ fn annulus_math ( ) {
831
+ let annulus = Annulus :: new ( 2.5 , 3.5 ) ;
832
+ assert_eq ! ( annulus. diameter( ) , 7.0 , "incorrect diameter" ) ;
833
+ assert_eq ! ( annulus. thickness( ) , 1.0 , "incorrect thickness" ) ;
834
+ assert_eq ! ( annulus. area( ) , 18.849556 , "incorrect area" ) ;
835
+ assert_eq ! ( annulus. perimeter( ) , 37.699112 , "incorrect perimeter" ) ;
836
+ }
837
+
729
838
#[ test]
730
839
fn ellipse_math ( ) {
731
840
let ellipse = Ellipse :: new ( 3.0 , 1.0 ) ;
0 commit comments