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}