Skip to content

Commit f2bbc50

Browse files
Add CircularSegment primitve as well.
1 parent 958310a commit f2bbc50

File tree

4 files changed

+261
-73
lines changed

4 files changed

+261
-73
lines changed

crates/bevy_math/src/bounding/bounded2d/primitive_impls.rs

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,13 @@ impl Bounded2d for Arc {
3636
self.radius * -Vec2::X,
3737
self.radius * -Vec2::Y,
3838
];
39-
if self.angle.is_sign_negative() {
39+
if self.half_angle.is_sign_negative() {
4040
// If we have a negative angle, we are going the opposite direction, so negate the Y-axis points.
4141
circle_bounds[2] = -circle_bounds[2];
4242
circle_bounds[4] = -circle_bounds[4];
4343
}
4444
// The number of quarter turns tells us how many extra points to include, between 0 and 3.
45-
let quarter_turns = f32::floor(self.angle.abs() / (PI / 2.0)).min(3.0) as usize;
45+
let quarter_turns = f32::floor(self.angle().abs() / (PI / 2.0)).min(3.0) as usize;
4646
Aabb2d::from_point_cloud(
4747
translation,
4848
rotation,
@@ -59,10 +59,8 @@ impl Bounded2d for Arc {
5959
} else {
6060
// Otherwise, the widest distance between two points is the chord,
6161
// so a circle of that diameter around the midpoint will contain the entire arc.
62-
let angle = self.angle + rotation;
63-
let center =
64-
Vec2::new(self.chord_midpoint_radius(), 0.0).rotate(Vec2::from_angle(angle));
65-
BoundingCircle::new(center, self.half_chord_length())
62+
let center = self.chord_midpoint().rotate(Vec2::from_angle(rotation));
63+
BoundingCircle::new(center + translation, self.half_chord_len())
6664
}
6765
}
6866
}
@@ -75,19 +73,19 @@ impl Bounded2d for CircularSector {
7573
// See comments above for discussion.
7674
let mut circle_bounds = [
7775
Vec2::ZERO,
78-
self.arc().end(),
79-
self.radius * Vec2::X,
80-
self.radius * Vec2::Y,
81-
self.radius * -Vec2::X,
82-
self.radius * -Vec2::Y,
76+
self.arc.end(),
77+
self.arc.radius * Vec2::X,
78+
self.arc.radius * Vec2::Y,
79+
self.arc.radius * -Vec2::X,
80+
self.arc.radius * -Vec2::Y,
8381
];
84-
if self.angle.is_sign_negative() {
82+
if self.arc.angle().is_sign_negative() {
8583
// If we have a negative angle, we are going the opposite direction, so negate the Y-axis points.
8684
circle_bounds[3] = -circle_bounds[3];
8785
circle_bounds[5] = -circle_bounds[5];
8886
}
8987
// The number of quarter turns tells us how many extra points to include, between 0 and 3.
90-
let quarter_turns = f32::floor(self.angle.abs() / (PI / 2.0)).min(3.0) as usize;
88+
let quarter_turns = f32::floor(self.arc.angle().abs() / (PI / 2.0)).min(3.0) as usize;
9189
Aabb2d::from_point_cloud(
9290
translation,
9391
rotation,
@@ -97,22 +95,22 @@ impl Bounded2d for CircularSector {
9795

9896
fn bounding_circle(&self, translation: Vec2, rotation: f32) -> BoundingCircle {
9997
// There are three possibilities for the bounding circle.
100-
if self.arc().is_major() {
98+
if self.arc.is_major() {
10199
// If the arc is major, then the widest distance between two points is a diameter of the arc's circle;
102100
// therefore, that circle is the bounding radius.
103-
BoundingCircle::new(translation, self.radius)
104-
} else if self.arc().chord_length() < self.radius {
101+
BoundingCircle::new(translation, self.arc.radius)
102+
} else if self.arc.chord_len() < self.arc.radius {
105103
// If the chord length is smaller than the radius, then the radius is the widest distance between two points,
106104
// so the radius is the diameter of the bounding circle.
107-
let angle = Vec2::from_angle(self.angle / 2.0 + rotation);
108-
let center = angle * self.radius / 2.0;
109-
BoundingCircle::new(center, self.radius / 2.0)
105+
let half_radius = self.arc.radius / 2.0;
106+
let angle = Vec2::from_angle(self.arc.half_angle + rotation);
107+
let center = half_radius * angle;
108+
BoundingCircle::new(center + translation, half_radius)
110109
} else {
111110
// Otherwise, the widest distance between two points is the chord,
112111
// so a circle of that diameter around the midpoint will contain the entire arc.
113-
let angle = Vec2::from_angle(self.angle / 2.0 + rotation);
114-
let center = angle * self.arc().chord_midpoint_radius();
115-
BoundingCircle::new(center, self.arc().half_chord_length())
112+
let center = self.arc.chord_midpoint().rotate(Vec2::from_angle(rotation));
113+
BoundingCircle::new(center + translation, self.arc.half_chord_len())
116114
}
117115
}
118116
}

crates/bevy_math/src/primitives/dim2.rs

Lines changed: 106 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -191,8 +191,8 @@ impl Circle {
191191
pub struct Arc {
192192
/// The radius of the circle
193193
pub radius: f32,
194-
/// The angle swept out by the arc.
195-
pub angle: f32,
194+
/// Half the angle swept out by the arc.
195+
pub half_angle: f32,
196196
}
197197
impl Primitive2d for Arc {}
198198

@@ -201,22 +201,31 @@ impl Default for Arc {
201201
fn default() -> Self {
202202
Self {
203203
radius: 0.5,
204-
angle: 1.0,
204+
half_angle: 0.5,
205205
}
206206
}
207207
}
208208

209209
impl Arc {
210210
/// Create a new [`Arc`] from a `radius`, and an `angle`
211211
#[inline(always)]
212-
pub const fn new(radius: f32, angle: f32) -> Self {
213-
Self { radius, angle }
212+
pub fn new(radius: f32, angle: f32) -> Self {
213+
Self {
214+
radius,
215+
half_angle: angle / 2.0,
216+
}
217+
}
218+
219+
/// Get the angle of the arc
220+
#[inline(always)]
221+
pub fn angle(&self) -> f32 {
222+
self.half_angle * 2.0
214223
}
215224

216225
/// Get the length of the arc
217226
#[inline(always)]
218227
pub fn length(&self) -> f32 {
219-
self.angle * self.radius
228+
self.angle() * self.radius
220229
}
221230

222231
/// Get the start point of the arc
@@ -228,7 +237,7 @@ impl Arc {
228237
/// Get the end point of the arc
229238
#[inline(always)]
230239
pub fn end(&self) -> Vec2 {
231-
self.radius * Vec2::from_angle(self.angle)
240+
self.radius * Vec2::from_angle(self.angle())
232241
}
233242

234243
/// Get the endpoints of the arc
@@ -237,34 +246,57 @@ impl Arc {
237246
[self.start(), self.end()]
238247
}
239248

249+
/// Get the midpoint of the arc
250+
#[inline]
251+
pub fn midpoint(&self) -> Vec2 {
252+
self.radius * Vec2::from_angle(self.half_angle)
253+
}
254+
240255
/// Get half the length of the chord subtended by the arc
241256
#[inline(always)]
242-
pub fn half_chord_length(&self) -> f32 {
243-
self.radius * f32::sin(self.angle / 2.0)
257+
pub fn half_chord_len(&self) -> f32 {
258+
self.radius * f32::sin(self.half_angle)
244259
}
245260

246261
/// Get the length of the chord subtended by the arc
247262
#[inline(always)]
248-
pub fn chord_length(&self) -> f32 {
249-
2.0 * self.half_chord_length()
263+
pub fn chord_len(&self) -> f32 {
264+
2.0 * self.half_chord_len()
250265
}
251266

252-
/// Get the distance from the center of the circle to the midpoint of the chord.
267+
/// Get the midpoint of the chord
253268
#[inline(always)]
254-
pub fn chord_midpoint_radius(&self) -> f32 {
255-
f32::sqrt(self.radius.powi(2) - self.half_chord_length().powi(2))
269+
pub fn chord_midpoint(&self) -> Vec2 {
270+
self.apothem_len() * Vec2::from_angle(self.half_angle)
256271
}
257272

258-
/// Get the midpoint of the chord
273+
/// Get the length of the apothem of this arc, that is,
274+
/// the distance from the center of the circle to the midpoint of the chord.
275+
/// Equivalently, the height of the triangle whose base is the chord and whose apex is the center of the circle.
259276
#[inline(always)]
260-
pub fn chord_midpoint(&self) -> Vec2 {
261-
Vec2::new(self.chord_midpoint_radius(), 0.0).rotate(Vec2::from_angle(self.angle))
277+
pub fn apothem_len(&self) -> f32 {
278+
f32::sqrt(self.radius.powi(2) - self.half_chord_len().powi(2))
279+
}
280+
281+
/// Get the legnth of the sagitta of this arc, that is,
282+
/// the length of the line between the midpoints of the arc and its chord.
283+
/// Equivalently, the height of the triangle whose base is the chord and whose apex is the midpoint of the arc.
284+
///
285+
/// If the arc is minor, i.e. less than half the circle, the this will be the difference of the [radius](Self::radius) and the [apothem](Self::apothem).
286+
/// If it is [major](Self::major), it will be their sum.
287+
#[inline(always)]
288+
pub fn sagitta_len(&self) -> f32 {
289+
if self.is_major() {
290+
self.radius + self.apothem_len()
291+
} else {
292+
self.radius - self.apothem_len()
293+
}
262294
}
263295

264296
/// Produces true if the arc is at least half a circle.
265297
#[inline(always)]
266298
pub fn is_major(&self) -> bool {
267-
self.angle >= PI
299+
self.angle() >= PI
268300
}
269301
}
270302

@@ -276,40 +308,80 @@ impl Arc {
276308
#[derive(Clone, Copy, Debug, PartialEq)]
277309
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
278310
pub struct CircularSector {
279-
/// The radius of the circle
280-
pub radius: f32,
281-
/// The angle swept out by the sector.
282-
pub angle: f32,
311+
/// The arc from which this sector is contructed.
312+
#[cfg_attr(feature = "seriealize", serde(flatten))]
313+
pub arc: Arc,
283314
}
284315
impl Primitive2d for CircularSector {}
285316

286317
impl Default for CircularSector {
287318
// Returns the default [`CircularSector`] with radius `0.5` and angle `1.0`.
288319
fn default() -> Self {
289-
Self {
290-
radius: 0.5,
291-
angle: 1.0,
292-
}
320+
Arc::default().into()
321+
}
322+
}
323+
324+
impl From<Arc> for CircularSector {
325+
fn from(arc: Arc) -> Self {
326+
Self { arc }
293327
}
294328
}
295329

296330
impl CircularSector {
297-
/// Create a new [`CircularSector`] from a `radius`, and an `angle`
331+
/// Create a new [CircularSector] from a `radius`, and an `angle`
298332
#[inline(always)]
299-
pub const fn new(radius: f32, angle: f32) -> Self {
300-
Self { radius, angle }
333+
pub fn new(radius: f32, angle: f32) -> Self {
334+
Arc::new(radius, angle).into()
301335
}
302336

303-
/// Produces the arc of this sector
337+
/// Returns the area of this sector
304338
#[inline(always)]
305-
pub fn arc(&self) -> Arc {
306-
Arc::new(self.radius, self.angle)
339+
pub fn area(&self) -> f32 {
340+
self.arc.radius.powi(2) * self.arc.half_angle
307341
}
342+
}
308343

309-
/// Returns the area of this sector
344+
/// A primitive representing a circular segment:
345+
/// the area enclosed by the arc of a circle and its chord (the line between its endpoints).
346+
///
347+
/// The segment is drawn starting from [Vec2::X], going counterclockwise.
348+
/// To orient the segment differently, apply a rotation.
349+
/// The segment is drawn with the center of its circle at the origin (0, 0).
350+
/// When positioning the segment, the [apothem_len](Self::apothem) and [sagitta_len](Sagitta) functions
351+
/// may be particularly useful.
352+
#[derive(Clone, Copy, Debug, PartialEq)]
353+
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
354+
pub struct CircularSegment {
355+
/// The arc from which this segment is contructed.
356+
#[cfg_attr(feature = "seriealize", serde(flatten))]
357+
pub arc: Arc,
358+
}
359+
impl Primitive2d for CircularSegment {}
360+
361+
impl Default for CircularSegment {
362+
// Returns the default [CircularSegment] with radius `0.5` and angle `1.0`.
363+
fn default() -> Self {
364+
Arc::default().into()
365+
}
366+
}
367+
368+
impl From<Arc> for CircularSegment {
369+
fn from(arc: Arc) -> Self {
370+
Self { arc }
371+
}
372+
}
373+
374+
impl CircularSegment {
375+
/// Create a new [CircularSegment] from a `radius`, and an `angle`
376+
#[inline(always)]
377+
pub fn new(radius: f32, angle: f32) -> Self {
378+
Arc::new(radius, angle).into()
379+
}
380+
381+
/// Returns the area of this segment
310382
#[inline(always)]
311383
pub fn area(&self) -> f32 {
312-
self.radius.powi(2) * self.angle / 2.0
384+
self.arc.radius.powi(2) * (self.arc.half_angle - self.arc.angle().sin())
313385
}
314386
}
315387

0 commit comments

Comments
 (0)