bevy_gizmos/
circles.rs

1//! Additional [`GizmoBuffer`] Functions -- Circles
2//!
3//! Includes the implementation of [`GizmoBuffer::circle`] and [`GizmoBuffer::circle_2d`],
4//! and assorted support items.
5
6use crate::{gizmos::GizmoBuffer, prelude::GizmoConfigGroup};
7use bevy_color::Color;
8use bevy_math::{ops, Isometry2d, Isometry3d, Quat, Vec2, Vec3};
9use core::f32::consts::TAU;
10
11pub(crate) const DEFAULT_CIRCLE_RESOLUTION: u32 = 32;
12
13fn ellipse_inner(half_size: Vec2, resolution: u32) -> impl Iterator<Item = Vec2> {
14    (0..resolution + 1).map(move |i| {
15        let angle = i as f32 * TAU / resolution as f32;
16        let (x, y) = ops::sin_cos(angle);
17        Vec2::new(x, y) * half_size
18    })
19}
20
21impl<Config, Clear> GizmoBuffer<Config, Clear>
22where
23    Config: GizmoConfigGroup,
24    Clear: 'static + Send + Sync,
25{
26    /// Draw an ellipse in 3D with the given `isometry` applied.
27    ///
28    /// If `isometry == Isometry3d::IDENTITY` then
29    ///
30    /// - the center is at `Vec3::ZERO`
31    /// - the `half_sizes` are aligned with the `Vec3::X` and `Vec3::Y` axes.
32    ///
33    /// # Example
34    /// ```
35    /// # use bevy_gizmos::prelude::*;
36    /// # use bevy_math::prelude::*;
37    /// # use bevy_color::palettes::basic::{RED, GREEN};
38    /// fn system(mut gizmos: Gizmos) {
39    ///     gizmos.ellipse(Isometry3d::IDENTITY, Vec2::new(1., 2.), GREEN);
40    ///
41    ///     // Ellipses have 32 line-segments by default.
42    ///     // You may want to increase this for larger ellipses.
43    ///     gizmos
44    ///         .ellipse(Isometry3d::IDENTITY, Vec2::new(5., 1.), RED)
45    ///         .resolution(64);
46    /// }
47    /// # bevy_ecs::system::assert_is_system(system);
48    /// ```
49    #[inline]
50    pub fn ellipse(
51        &mut self,
52        isometry: impl Into<Isometry3d>,
53        half_size: Vec2,
54        color: impl Into<Color>,
55    ) -> EllipseBuilder<'_, Config, Clear> {
56        EllipseBuilder {
57            gizmos: self,
58            isometry: isometry.into(),
59            half_size,
60            color: color.into(),
61            resolution: DEFAULT_CIRCLE_RESOLUTION,
62        }
63    }
64
65    /// Draw an ellipse in 2D with the given `isometry` applied.
66    ///
67    /// If `isometry == Isometry2d::IDENTITY` then
68    ///
69    /// - the center is at `Vec2::ZERO`
70    /// - the `half_sizes` are aligned with the `Vec2::X` and `Vec2::Y` axes.
71    ///
72    /// # Example
73    /// ```
74    /// # use bevy_gizmos::prelude::*;
75    /// # use bevy_math::prelude::*;
76    /// # use bevy_color::palettes::basic::{RED, GREEN};
77    /// fn system(mut gizmos: Gizmos) {
78    ///     gizmos.ellipse_2d(Isometry2d::from_rotation(Rot2::degrees(180.0)), Vec2::new(2., 1.), GREEN);
79    ///
80    ///     // Ellipses have 32 line-segments by default.
81    ///     // You may want to increase this for larger ellipses.
82    ///     gizmos
83    ///         .ellipse_2d(Isometry2d::from_rotation(Rot2::degrees(180.0)), Vec2::new(5., 1.), RED)
84    ///         .resolution(64);
85    /// }
86    /// # bevy_ecs::system::assert_is_system(system);
87    /// ```
88    #[inline]
89    pub fn ellipse_2d(
90        &mut self,
91        isometry: impl Into<Isometry2d>,
92        half_size: Vec2,
93        color: impl Into<Color>,
94    ) -> Ellipse2dBuilder<'_, Config, Clear> {
95        Ellipse2dBuilder {
96            gizmos: self,
97            isometry: isometry.into(),
98            half_size,
99            color: color.into(),
100            resolution: DEFAULT_CIRCLE_RESOLUTION,
101        }
102    }
103
104    /// Draw a circle in 3D with the given `isometry` applied.
105    ///
106    /// If `isometry == Isometry3d::IDENTITY` then
107    ///
108    /// - the center is at `Vec3::ZERO`
109    /// - the radius is aligned with the `Vec3::X` and `Vec3::Y` axes.
110    ///
111    /// # Example
112    /// ```
113    /// # use bevy_gizmos::prelude::*;
114    /// # use bevy_math::prelude::*;
115    /// # use bevy_color::palettes::basic::{RED, GREEN};
116    /// fn system(mut gizmos: Gizmos) {
117    ///     gizmos.circle(Isometry3d::IDENTITY, 1., GREEN);
118    ///
119    ///     // Circles have 32 line-segments by default.
120    ///     // You may want to increase this for larger circles.
121    ///     gizmos
122    ///         .circle(Isometry3d::IDENTITY, 5., RED)
123    ///         .resolution(64);
124    /// }
125    /// # bevy_ecs::system::assert_is_system(system);
126    /// ```
127    #[inline]
128    pub fn circle(
129        &mut self,
130        isometry: impl Into<Isometry3d>,
131        radius: f32,
132        color: impl Into<Color>,
133    ) -> EllipseBuilder<'_, Config, Clear> {
134        EllipseBuilder {
135            gizmos: self,
136            isometry: isometry.into(),
137            half_size: Vec2::splat(radius),
138            color: color.into(),
139            resolution: DEFAULT_CIRCLE_RESOLUTION,
140        }
141    }
142
143    /// Draw a circle in 2D with the given `isometry` applied.
144    ///
145    /// If `isometry == Isometry2d::IDENTITY` then
146    ///
147    /// - the center is at `Vec2::ZERO`
148    /// - the radius is aligned with the `Vec2::X` and `Vec2::Y` axes.
149    ///
150    /// # Example
151    /// ```
152    /// # use bevy_gizmos::prelude::*;
153    /// # use bevy_math::prelude::*;
154    /// # use bevy_color::palettes::basic::{RED, GREEN};
155    /// fn system(mut gizmos: Gizmos) {
156    ///     gizmos.circle_2d(Isometry2d::IDENTITY, 1., GREEN);
157    ///
158    ///     // Circles have 32 line-segments by default.
159    ///     // You may want to increase this for larger circles.
160    ///     gizmos
161    ///         .circle_2d(Isometry2d::IDENTITY, 5., RED)
162    ///         .resolution(64);
163    /// }
164    /// # bevy_ecs::system::assert_is_system(system);
165    /// ```
166    #[inline]
167    pub fn circle_2d(
168        &mut self,
169        isometry: impl Into<Isometry2d>,
170        radius: f32,
171        color: impl Into<Color>,
172    ) -> Ellipse2dBuilder<'_, Config, Clear> {
173        Ellipse2dBuilder {
174            gizmos: self,
175            isometry: isometry.into(),
176            half_size: Vec2::splat(radius),
177            color: color.into(),
178            resolution: DEFAULT_CIRCLE_RESOLUTION,
179        }
180    }
181
182    /// Draw a wireframe sphere in 3D made out of 3 circles around the axes with the given
183    /// `isometry` applied.
184    ///
185    /// If `isometry == Isometry3d::IDENTITY` then
186    ///
187    /// - the center is at `Vec3::ZERO`
188    /// - the 3 circles are in the XY, YZ and XZ planes.
189    ///
190    /// # Example
191    /// ```
192    /// # use bevy_gizmos::prelude::*;
193    /// # use bevy_math::prelude::*;
194    /// # use bevy_color::Color;
195    /// fn system(mut gizmos: Gizmos) {
196    ///     gizmos.sphere(Isometry3d::IDENTITY, 1., Color::BLACK);
197    ///
198    ///     // Each circle has 32 line-segments by default.
199    ///     // You may want to increase this for larger spheres.
200    ///     gizmos
201    ///         .sphere(Isometry3d::IDENTITY, 5., Color::BLACK)
202    ///         .resolution(64);
203    /// }
204    /// # bevy_ecs::system::assert_is_system(system);
205    /// ```
206    #[inline]
207    pub fn sphere(
208        &mut self,
209        isometry: impl Into<Isometry3d>,
210        radius: f32,
211        color: impl Into<Color>,
212    ) -> SphereBuilder<'_, Config, Clear> {
213        SphereBuilder {
214            gizmos: self,
215            radius,
216            isometry: isometry.into(),
217            color: color.into(),
218            resolution: DEFAULT_CIRCLE_RESOLUTION,
219        }
220    }
221}
222
223/// A builder returned by [`GizmoBuffer::ellipse`].
224pub struct EllipseBuilder<'a, Config, Clear>
225where
226    Config: GizmoConfigGroup,
227    Clear: 'static + Send + Sync,
228{
229    gizmos: &'a mut GizmoBuffer<Config, Clear>,
230    isometry: Isometry3d,
231    half_size: Vec2,
232    color: Color,
233    resolution: u32,
234}
235
236impl<Config, Clear> EllipseBuilder<'_, Config, Clear>
237where
238    Config: GizmoConfigGroup,
239    Clear: 'static + Send + Sync,
240{
241    /// Set the number of lines used to approximate the geometry of this ellipse.
242    pub fn resolution(mut self, resolution: u32) -> Self {
243        self.resolution = resolution;
244        self
245    }
246}
247
248impl<Config, Clear> Drop for EllipseBuilder<'_, Config, Clear>
249where
250    Config: GizmoConfigGroup,
251    Clear: 'static + Send + Sync,
252{
253    fn drop(&mut self) {
254        if !self.gizmos.enabled {
255            return;
256        }
257
258        let positions = ellipse_inner(self.half_size, self.resolution)
259            .map(|vec2| self.isometry * vec2.extend(0.));
260        self.gizmos.linestrip(positions, self.color);
261    }
262}
263
264/// A builder returned by [`GizmoBuffer::ellipse_2d`].
265pub struct Ellipse2dBuilder<'a, Config, Clear>
266where
267    Config: GizmoConfigGroup,
268    Clear: 'static + Send + Sync,
269{
270    gizmos: &'a mut GizmoBuffer<Config, Clear>,
271    isometry: Isometry2d,
272    half_size: Vec2,
273    color: Color,
274    resolution: u32,
275}
276
277impl<Config, Clear> Ellipse2dBuilder<'_, Config, Clear>
278where
279    Config: GizmoConfigGroup,
280    Clear: 'static + Send + Sync,
281{
282    /// Set the number of line-segments used to approximate the geometry of this ellipse.
283    pub fn resolution(mut self, resolution: u32) -> Self {
284        self.resolution = resolution;
285        self
286    }
287}
288
289impl<Config, Clear> Drop for Ellipse2dBuilder<'_, Config, Clear>
290where
291    Config: GizmoConfigGroup,
292    Clear: 'static + Send + Sync,
293{
294    /// Set the number of line-segments for this ellipse.
295    fn drop(&mut self) {
296        if !self.gizmos.enabled {
297            return;
298        };
299
300        let positions =
301            ellipse_inner(self.half_size, self.resolution).map(|vec2| self.isometry * vec2);
302        self.gizmos.linestrip_2d(positions, self.color);
303    }
304}
305
306/// A builder returned by [`GizmoBuffer::sphere`].
307pub struct SphereBuilder<'a, Config, Clear>
308where
309    Config: GizmoConfigGroup,
310    Clear: 'static + Send + Sync,
311{
312    gizmos: &'a mut GizmoBuffer<Config, Clear>,
313
314    // Radius of the sphere
315    radius: f32,
316
317    isometry: Isometry3d,
318    // Color of the sphere
319    color: Color,
320
321    // Number of line-segments used to approximate the sphere geometry
322    resolution: u32,
323}
324
325impl<Config, Clear> SphereBuilder<'_, Config, Clear>
326where
327    Config: GizmoConfigGroup,
328    Clear: 'static + Send + Sync,
329{
330    /// Set the number of line-segments used to approximate the sphere geometry.
331    pub fn resolution(mut self, resolution: u32) -> Self {
332        self.resolution = resolution;
333        self
334    }
335}
336
337impl<Config, Clear> Drop for SphereBuilder<'_, Config, Clear>
338where
339    Config: GizmoConfigGroup,
340    Clear: 'static + Send + Sync,
341{
342    fn drop(&mut self) {
343        if !self.gizmos.enabled {
344            return;
345        }
346
347        // draws one great circle around each of the local axes
348        Vec3::AXES.into_iter().for_each(|axis| {
349            let axis_rotation = Isometry3d::from_rotation(Quat::from_rotation_arc(Vec3::Z, axis));
350            self.gizmos
351                .circle(self.isometry * axis_rotation, self.radius, self.color)
352                .resolution(self.resolution);
353        });
354    }
355}