Skip to content

Commit c128ef0

Browse files
Change the orientation of arc primitives to vertically symmetrical.
1 parent d2bfb2b commit c128ef0

File tree

4 files changed

+81
-75
lines changed

4 files changed

+81
-75
lines changed

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

Lines changed: 39 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
//! Contains [`Bounded2d`] implementations for [geometric primitives](crate::primitives).
22
3-
use std::f32::consts::PI;
4-
53
use glam::{Mat2, Vec2};
64

75
use crate::primitives::{
8-
Arc2d, BoxedPolygon, BoxedPolyline2d, Capsule2d, Circle, CircularSector, Direction2d, Ellipse,
9-
Line2d, Plane2d, Polygon, Polyline2d, Rectangle, RegularPolygon, Segment2d, Triangle2d,
6+
Arc2d, BoxedPolygon, BoxedPolyline2d, Capsule2d, Circle, CircularSector, CircularSegment,
7+
Direction2d, Ellipse, Line2d, Plane2d, Polygon, Polyline2d, Rectangle, RegularPolygon,
8+
Segment2d, Triangle2d,
109
};
1110

1211
use super::{Aabb2d, Bounded2d, BoundingCircle};
@@ -23,31 +22,23 @@ impl Bounded2d for Circle {
2322

2423
impl Bounded2d for Arc2d {
2524
fn aabb_2d(&self, translation: Vec2, rotation: f32) -> Aabb2d {
26-
// For a sufficiently wide arc, the bounding points in a given direction will be the outer
27-
// limits of a circle centered at the origin.
28-
// For smaller arcs, the two endpoints of the arc could also be bounding points,
29-
// but the start point is always axis-aligned so it's included as one of the circular limits.
25+
// Because our arcs are always symmetrical around Vec::Y, the uppermost point and the two endpoints will always be extrema.
26+
// For an arc that is greater than a semicircle, radii to the left and right will also be bounding points.
3027
// This gives five possible bounding points, so we will lay them out in an array and then
3128
// select the appropriate slice to compute the bounding box of.
32-
let mut circle_bounds = [
33-
self.end(),
34-
self.radius * Vec2::X,
29+
let all_bounds = [
30+
self.left_endpoint(),
31+
self.right_endpoint(),
3532
self.radius * Vec2::Y,
33+
self.radius * Vec2::X,
3634
self.radius * -Vec2::X,
37-
self.radius * -Vec2::Y,
3835
];
39-
if self.half_angle.is_sign_negative() {
40-
// If we have a negative angle, we are going the opposite direction, so negate the Y-axis points.
41-
circle_bounds[2] = -circle_bounds[2];
42-
circle_bounds[4] = -circle_bounds[4];
43-
}
44-
// 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;
46-
Aabb2d::from_point_cloud(
47-
translation,
48-
rotation,
49-
&circle_bounds[0..(2 + quarter_turns)],
50-
)
36+
let bounds = if self.is_major() {
37+
&all_bounds[0..5]
38+
} else {
39+
&all_bounds[0..3]
40+
};
41+
Aabb2d::from_point_cloud(translation, rotation, bounds)
5142
}
5243

5344
fn bounding_circle(&self, translation: Vec2, rotation: f32) -> BoundingCircle {
@@ -68,29 +59,23 @@ impl Bounded2d for Arc2d {
6859
impl Bounded2d for CircularSector {
6960
fn aabb_2d(&self, translation: Vec2, rotation: f32) -> Aabb2d {
7061
// This is identical to the implementation for Arc2d, above, with the additional possibility of the
71-
// origin point, the center of the arc, acting as a bounding point.
62+
// origin point, the center of the arc, acting as a bounding point when the arc is minor.
7263
//
73-
// See comments above for discussion.
74-
let mut circle_bounds = [
64+
// See comments above for an explanation of the logic.
65+
let all_bounds = [
7566
Vec2::ZERO,
76-
self.arc.end(),
77-
self.arc.radius * Vec2::X,
67+
self.arc.left_endpoint(),
68+
self.arc.right_endpoint(),
7869
self.arc.radius * Vec2::Y,
70+
self.arc.radius * Vec2::X,
7971
self.arc.radius * -Vec2::X,
80-
self.arc.radius * -Vec2::Y,
8172
];
82-
if self.arc.angle().is_sign_negative() {
83-
// If we have a negative angle, we are going the opposite direction, so negate the Y-axis points.
84-
circle_bounds[3] = -circle_bounds[3];
85-
circle_bounds[5] = -circle_bounds[5];
86-
}
87-
// The number of quarter turns tells us how many extra points to include, between 0 and 3.
88-
let quarter_turns = f32::floor(self.arc.angle().abs() / (PI / 2.0)).min(3.0) as usize;
89-
Aabb2d::from_point_cloud(
90-
translation,
91-
rotation,
92-
&circle_bounds[0..(3 + quarter_turns)],
93-
)
73+
let bounds = if self.arc.is_major() {
74+
&all_bounds[1..6]
75+
} else {
76+
&all_bounds[0..4]
77+
};
78+
Aabb2d::from_point_cloud(translation, rotation, bounds)
9479
}
9580

9681
fn bounding_circle(&self, translation: Vec2, rotation: f32) -> BoundingCircle {
@@ -101,10 +86,9 @@ impl Bounded2d for CircularSector {
10186
BoundingCircle::new(translation, self.arc.radius)
10287
} else if self.arc.chord_length() < self.arc.radius {
10388
// If the chord length is smaller than the radius, then the radius is the widest distance between two points,
104-
// so the radius is the diameter of the bounding circle.
89+
// so the bounding circle is centered on the midpoint of the radius.
10590
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;
91+
let center = half_radius * Vec2::Y;
10892
BoundingCircle::new(center + translation, half_radius)
10993
} else {
11094
// Otherwise, the widest distance between two points is the chord,
@@ -115,6 +99,16 @@ impl Bounded2d for CircularSector {
11599
}
116100
}
117101

102+
impl Bounded2d for CircularSegment {
103+
fn aabb_2d(&self, translation: Vec2, rotation: f32) -> Aabb2d {
104+
self.arc.aabb_2d(translation, rotation)
105+
}
106+
107+
fn bounding_circle(&self, translation: Vec2, rotation: f32) -> BoundingCircle {
108+
self.arc.bounding_circle(translation, rotation)
109+
}
110+
}
111+
118112
impl Bounded2d for Ellipse {
119113
fn aabb_2d(&self, translation: Vec2, rotation: f32) -> Aabb2d {
120114
// V = (hh * cos(beta), hh * sin(beta))

crates/bevy_math/src/primitives/dim2.rs

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -186,10 +186,9 @@ const HALF_PI: f32 = PI / 2.0;
186186
/// If you want to include only the space inside the convex hull of the arc,
187187
/// use [`CircularSegment`].
188188
///
189-
/// The arc is drawn starting from [`Vec2::X`], going counterclockwise.
190-
/// To orient the arc differently, apply a rotation.
191-
/// The arc is drawn with the center of its circle at the origin (0, 0),
192-
/// meaning that the center may not be inside its convex hull.
189+
/// The arc is drawn starting from [`Vec2::Y`], extending by `half_angle` radians on
190+
/// either side. The center of the circle is the origin [`Vec2::ZERO`]. Note that this
191+
/// means that the origin may not be within the `Arc2d`'s convex hull.
193192
#[derive(Clone, Copy, Debug, PartialEq)]
194193
#[doc(alias("CircularArc", "CircleArc"))]
195194
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
@@ -259,28 +258,28 @@ impl Arc2d {
259258
self.angle() * self.radius
260259
}
261260

262-
/// Get the start point of the arc
261+
/// Get the right-hand end point of the arc
263262
#[inline(always)]
264-
pub fn start(&self) -> Vec2 {
265-
Vec2::new(self.radius, 0.0)
263+
pub fn right_endpoint(&self) -> Vec2 {
264+
self.radius * Vec2::from_angle(HALF_PI - self.half_angle)
266265
}
267266

268-
/// Get the end point of the arc
267+
/// Get the left-hand end point of the arc
269268
#[inline(always)]
270-
pub fn end(&self) -> Vec2 {
271-
self.radius * Vec2::from_angle(self.angle())
269+
pub fn left_endpoint(&self) -> Vec2 {
270+
self.radius * Vec2::from_angle(HALF_PI + self.half_angle)
272271
}
273272

274273
/// Get the endpoints of the arc
275274
#[inline(always)]
276275
pub fn endpoints(&self) -> [Vec2; 2] {
277-
[self.start(), self.end()]
276+
[self.left_endpoint(), self.right_endpoint()]
278277
}
279278

280279
/// Get the midpoint of the arc
281280
#[inline]
282281
pub fn midpoint(&self) -> Vec2 {
283-
self.radius * Vec2::from_angle(self.half_angle)
282+
self.radius * Vec2::Y
284283
}
285284

286285
/// Get half the length of the chord subtended by the arc
@@ -298,7 +297,7 @@ impl Arc2d {
298297
/// Get the midpoint of the chord subtended by the arc
299298
#[inline(always)]
300299
pub fn chord_midpoint(&self) -> Vec2 {
301-
self.apothem() * Vec2::from_angle(self.half_angle)
300+
self.apothem() * Vec2::Y
302301
}
303302

304303
/// Get the length of the apothem of this arc, that is,
@@ -347,7 +346,7 @@ impl Arc2d {
347346

348347
/// A primitive representing a circular sector: a pie slice of a circle.
349348
///
350-
/// The sector is drawn starting from [`Vec2::X`], going counterclockwise.
349+
/// The segment is drawn starting from [`Vec2::Y`], extending equally on either side.
351350
/// To orient the sector differently, apply a rotation.
352351
/// The sector is drawn with the center of its circle at the origin [`Vec2::ZERO`].
353352
#[derive(Clone, Copy, Debug, PartialEq)]
@@ -409,7 +408,7 @@ impl CircularSector {
409408
/// A primitive representing a circular segment:
410409
/// the area enclosed by the arc of a circle and its chord (the line between its endpoints).
411410
///
412-
/// The segment is drawn starting from [`Vec2::X`], going counterclockwise.
411+
/// The segment is drawn starting from [`Vec2::Y`], extending equally on either side.
413412
/// To orient the segment differently, apply a rotation.
414413
/// The segment is drawn with the center of its circle at the origin [`Vec2::ZERO`].
415414
/// When positioning a segment, the [`apothem`](Arc2d::apothem) function may be particularly useful,

crates/bevy_render/src/mesh/primitives/dim2.rs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::f32::consts::PI;
2+
13
use crate::{
24
mesh::{Indices, Mesh},
35
render_asset::RenderAssetUsages,
@@ -81,6 +83,9 @@ impl From<CircleMeshBuilder> for Mesh {
8183
}
8284

8385
/// A builder used for creating a [`Mesh`] with a [`CircularSector`] shape.
86+
///
87+
/// The resulting mesh will have a UV-map such that the center of the circle is
88+
/// at the centure of the texture.
8489
#[derive(Clone, Copy, Debug)]
8590
pub struct CircularSectorMeshBuilder {
8691
/// The sector shape.
@@ -129,10 +134,12 @@ impl CircularSectorMeshBuilder {
129134
positions.push([0.0; 3]);
130135
uvs.push([0.5; 2]);
131136

132-
let last = (self.resolution - 1) as f32;
137+
let first_angle = PI / 2.0 - self.sector.arc.half_angle;
138+
let last_angle = PI / 2.0 + self.sector.arc.half_angle;
139+
let last_i = (self.resolution - 1) as f32;
133140
for i in 0..self.resolution {
134141
// Compute vertex position at angle theta
135-
let angle = Vec2::from_angle(f32::lerp(0.0, self.sector.arc.angle(), i as f32 / last));
142+
let angle = Vec2::from_angle(f32::lerp(first_angle, last_angle, i as f32 / last_i));
136143

137144
positions.push([
138145
angle.x * self.sector.arc.radius,
@@ -170,6 +177,9 @@ impl Meshable for CircularSector {
170177
}
171178

172179
impl From<CircularSector> for Mesh {
180+
/// Converts this sector into a [`Mesh`] using a default [`CircularSectorMeshBuilder`].
181+
///
182+
/// See the documentation of [`CircularSectorMeshBuilder`] for more details.
173183
fn from(sector: CircularSector) -> Self {
174184
sector.mesh().build()
175185
}
@@ -182,6 +192,9 @@ impl From<CircularSectorMeshBuilder> for Mesh {
182192
}
183193

184194
/// A builder used for creating a [`Mesh`] with a [`CircularSegment`] shape.
195+
///
196+
/// The resulting mesh will have a UV-map such that the center of the circle is
197+
/// at the centure of the texture.
185198
#[derive(Clone, Copy, Debug)]
186199
pub struct CircularSegmentMeshBuilder {
187200
/// The segment shape.
@@ -231,10 +244,12 @@ impl CircularSegmentMeshBuilder {
231244
positions.push([chord_midpoint.x, chord_midpoint.y, 0.0]);
232245
uvs.push([0.5 + chord_midpoint.x * 0.5, 0.5 + chord_midpoint.y * 0.5]);
233246

234-
let last = (self.resolution - 1) as f32;
247+
let first_angle = PI / 2.0 - self.segment.arc.half_angle;
248+
let last_angle = PI / 2.0 + self.segment.arc.half_angle;
249+
let last_i = (self.resolution - 1) as f32;
235250
for i in 0..self.resolution {
236251
// Compute vertex position at angle theta
237-
let angle = Vec2::from_angle(f32::lerp(0.0, self.segment.arc.angle(), i as f32 / last));
252+
let angle = Vec2::from_angle(f32::lerp(first_angle, last_angle, i as f32 / last_i));
238253

239254
positions.push([
240255
angle.x * self.segment.arc.radius,
@@ -272,6 +287,9 @@ impl Meshable for CircularSegment {
272287
}
273288

274289
impl From<CircularSegment> for Mesh {
290+
/// Converts this sector into a [`Mesh`] using a default [`CircularSegmentMeshBuilder`].
291+
///
292+
/// See the documentation of [`CircularSegmentMeshBuilder`] for more details.
275293
fn from(segment: CircularSegment) -> Self {
276294
segment.mesh().build()
277295
}

examples/2d/2d_shapes.rs

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,9 @@ fn setup(
2929
// Circular sector
3030
let sector = CircularSector::from_radians(50.0, 5.0);
3131
let mut sector_transform = Transform::from_translation(Vec3::new(-275.0, 0.0, 0.0));
32-
// A sector is drawn counterclockwise from the right.
33-
// To make it face left, rotate by negative half its angle.
34-
// To make it face right, rotate by an additional PI radians.
35-
sector_transform.rotate_z(-sector.arc.half_angle + PI);
32+
// A sector is drawn symmetrically from the top.
33+
// To make it face right, we must rotate it to the left by 90 degrees.
34+
sector_transform.rotate_z(PI / 2.0);
3635
commands.spawn(MaterialMesh2dBundle {
3736
mesh: meshes.add(sector).into(),
3837
material: materials.add(Color::YELLOW),
@@ -51,15 +50,11 @@ fn setup(
5150
});
5251

5352
// Circular segment
54-
let segment = CircularSegment::from_radians(50.0, 2.5);
55-
let mut segment_transform = Transform::from_translation(Vec3::new(-150.0, 0.0, 0.0));
56-
// A segment is drawn counterclockwise from the right.
57-
// To make it symmetrical about the X axis, rotate by negative half its angle.
58-
// To make it symmetrical about the Y axis, rotate by an additional PI/2 radians.
59-
segment_transform.rotate_z(-segment.arc.half_angle + PI / 2.0);
53+
let segment = CircularSegment::from_degrees(50.0, 135.0);
6054
// The segment is drawn with the center as the center of the circle.
6155
// By subtracting the apothem, we move the segment down so that it touches the line x = 0.
62-
segment_transform.translation.y -= segment.arc.apothem();
56+
let segment_transform =
57+
Transform::from_translation(Vec3::new(-150.0, -segment.arc.apothem(), 0.0));
6358
commands.spawn(MaterialMesh2dBundle {
6459
mesh: meshes.add(segment).into(),
6560
material: materials.add(Color::MIDNIGHT_BLUE),

0 commit comments

Comments
 (0)