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