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}