bevy_gizmos/
arcs.rs

1//! Additional [`GizmoBuffer`] Functions -- Arcs
2//!
3//! Includes the implementation of [`GizmoBuffer::arc_2d`],
4//! and assorted support items.
5
6use crate::{circles::DEFAULT_CIRCLE_RESOLUTION, gizmos::GizmoBuffer, prelude::GizmoConfigGroup};
7use bevy_color::Color;
8use bevy_math::{Isometry2d, Isometry3d, Quat, Rot2, Vec2, Vec3};
9use core::f32::consts::{FRAC_PI_2, TAU};
10
11// === 2D ===
12
13impl<Config, Clear> GizmoBuffer<Config, Clear>
14where
15    Config: GizmoConfigGroup,
16    Clear: 'static + Send + Sync,
17{
18    /// Draw an arc, which is a part of the circumference of a circle, in 2D.
19    ///
20    /// # Arguments
21    /// - `isometry` defines the translation and rotation of the arc.
22    ///   - the translation specifies the center of the arc
23    ///   - the rotation is counter-clockwise starting from `Vec2::Y`
24    /// - `arc_angle` sets the length of this arc, in radians.
25    /// - `radius` controls the distance from `position` to this arc, and thus its curvature.
26    /// - `color` sets the color to draw the arc.
27    ///
28    /// # Example
29    /// ```
30    /// # use bevy_gizmos::prelude::*;
31    /// # use bevy_math::prelude::*;
32    /// # use std::f32::consts::FRAC_PI_4;
33    /// # use bevy_color::palettes::basic::{GREEN, RED};
34    /// fn system(mut gizmos: Gizmos) {
35    ///     gizmos.arc_2d(Isometry2d::IDENTITY, FRAC_PI_4, 1., GREEN);
36    ///
37    ///     // Arcs have 32 line-segments by default.
38    ///     // You may want to increase this for larger arcs.
39    ///     gizmos
40    ///         .arc_2d(Isometry2d::IDENTITY, FRAC_PI_4, 5., RED)
41    ///         .resolution(64);
42    /// }
43    /// # bevy_ecs::system::assert_is_system(system);
44    /// ```
45    #[inline]
46    pub fn arc_2d(
47        &mut self,
48        isometry: impl Into<Isometry2d>,
49        arc_angle: f32,
50        radius: f32,
51        color: impl Into<Color>,
52    ) -> Arc2dBuilder<'_, Config, Clear> {
53        Arc2dBuilder {
54            gizmos: self,
55            isometry: isometry.into(),
56            arc_angle,
57            radius,
58            color: color.into(),
59            resolution: None,
60        }
61    }
62}
63
64/// A builder returned by [`GizmoBuffer::arc_2d`].
65pub struct Arc2dBuilder<'a, Config, Clear>
66where
67    Config: GizmoConfigGroup,
68    Clear: 'static + Send + Sync,
69{
70    gizmos: &'a mut GizmoBuffer<Config, Clear>,
71    isometry: Isometry2d,
72    arc_angle: f32,
73    radius: f32,
74    color: Color,
75    resolution: Option<u32>,
76}
77
78impl<Config, Clear> Arc2dBuilder<'_, Config, Clear>
79where
80    Config: GizmoConfigGroup,
81    Clear: 'static + Send + Sync,
82{
83    /// Set the number of lines used to approximate the geometry of this arc.
84    pub fn resolution(mut self, resolution: u32) -> Self {
85        self.resolution.replace(resolution);
86        self
87    }
88}
89
90impl<Config, Clear> Drop for Arc2dBuilder<'_, Config, Clear>
91where
92    Config: GizmoConfigGroup,
93    Clear: 'static + Send + Sync,
94{
95    fn drop(&mut self) {
96        if !self.gizmos.enabled {
97            return;
98        }
99
100        let resolution = self
101            .resolution
102            .unwrap_or_else(|| resolution_from_angle(self.arc_angle));
103
104        let positions =
105            arc_2d_inner(self.arc_angle, self.radius, resolution).map(|vec2| self.isometry * vec2);
106        self.gizmos.linestrip_2d(positions, self.color);
107    }
108}
109
110fn arc_2d_inner(arc_angle: f32, radius: f32, resolution: u32) -> impl Iterator<Item = Vec2> {
111    (0..=resolution)
112        .map(move |n| arc_angle * n as f32 / resolution as f32)
113        .map(|angle| angle + FRAC_PI_2)
114        .map(Vec2::from_angle)
115        .map(move |vec2| vec2 * radius)
116}
117
118// === 3D ===
119
120impl<Config, Clear> GizmoBuffer<Config, Clear>
121where
122    Config: GizmoConfigGroup,
123    Clear: 'static + Send + Sync,
124{
125    /// Draw an arc, which is a part of the circumference of a circle, in 3D. For default values
126    /// this is drawing a standard arc. A standard arc is defined as
127    ///
128    /// - an arc with a center at `Vec3::ZERO`
129    /// - starting at `Vec3::X`
130    /// - embedded in the XZ plane
131    /// - rotates counterclockwise
132    ///
133    /// # Arguments
134    /// - `angle`: sets how much of a circle circumference is passed, e.g. PI is half a circle. This
135    ///   value should be in the range (-2 * PI..=2 * PI)
136    /// - `radius`: distance between the arc and its center point
137    /// - `isometry` defines the translation and rotation of the arc.
138    ///   - the translation specifies the center of the arc
139    ///   - the rotation is counter-clockwise starting from `Vec3::Y`
140    /// - `color`: color of the arc
141    ///
142    /// # Builder methods
143    /// The resolution of the arc (i.e. the level of detail) can be adjusted with the
144    /// `.resolution(...)` method.
145    ///
146    /// # Example
147    /// ```
148    /// # use bevy_gizmos::prelude::*;
149    /// # use bevy_math::prelude::*;
150    /// # use std::f32::consts::PI;
151    /// # use bevy_color::palettes::css::ORANGE;
152    /// fn system(mut gizmos: Gizmos) {
153    ///     // rotation rotates normal to point in the direction of `Vec3::NEG_ONE`
154    ///     let rotation = Quat::from_rotation_arc(Vec3::Y, Vec3::NEG_ONE.normalize());
155    ///
156    ///     gizmos
157    ///        .arc_3d(
158    ///          270.0_f32.to_radians(),
159    ///          0.25,
160    ///          Isometry3d::new(Vec3::ONE, rotation),
161    ///          ORANGE
162    ///          )
163    ///          .resolution(100);
164    /// }
165    /// # bevy_ecs::system::assert_is_system(system);
166    /// ```
167    #[inline]
168    pub fn arc_3d(
169        &mut self,
170        angle: f32,
171        radius: f32,
172        isometry: impl Into<Isometry3d>,
173        color: impl Into<Color>,
174    ) -> Arc3dBuilder<'_, Config, Clear> {
175        Arc3dBuilder {
176            gizmos: self,
177            start_vertex: Vec3::X,
178            isometry: isometry.into(),
179            angle,
180            radius,
181            color: color.into(),
182            resolution: None,
183        }
184    }
185
186    /// Draws the shortest arc between two points (`from` and `to`) relative to a specified `center` point.
187    ///
188    /// # Arguments
189    ///
190    /// - `center`: The center point around which the arc is drawn.
191    /// - `from`: The starting point of the arc.
192    /// - `to`: The ending point of the arc.
193    /// - `color`: color of the arc
194    ///
195    /// # Builder methods
196    /// The resolution of the arc (i.e. the level of detail) can be adjusted with the
197    /// `.resolution(...)` method.
198    ///
199    /// # Examples
200    /// ```
201    /// # use bevy_gizmos::prelude::*;
202    /// # use bevy_math::prelude::*;
203    /// # use bevy_color::palettes::css::ORANGE;
204    /// fn system(mut gizmos: Gizmos) {
205    ///     gizmos.short_arc_3d_between(
206    ///        Vec3::ONE,
207    ///        Vec3::ONE + Vec3::NEG_ONE,
208    ///        Vec3::ZERO,
209    ///        ORANGE
210    ///        )
211    ///        .resolution(100);
212    /// }
213    /// # bevy_ecs::system::assert_is_system(system);
214    /// ```
215    ///
216    /// # Notes
217    /// - This method assumes that the points `from` and `to` are distinct from `center`. If one of
218    ///   the points is coincident with `center`, nothing is rendered.
219    /// - The arc is drawn as a portion of a circle with a radius equal to the distance from the
220    ///   `center` to `from`. If the distance from `center` to `to` is not equal to the radius, then
221    ///   the results will behave as if this were the case
222    #[inline]
223    pub fn short_arc_3d_between(
224        &mut self,
225        center: Vec3,
226        from: Vec3,
227        to: Vec3,
228        color: impl Into<Color>,
229    ) -> Arc3dBuilder<'_, Config, Clear> {
230        self.arc_from_to(center, from, to, color, |x| x)
231    }
232
233    /// Draws the longest arc between two points (`from` and `to`) relative to a specified `center` point.
234    ///
235    /// # Arguments
236    /// - `center`: The center point around which the arc is drawn.
237    /// - `from`: The starting point of the arc.
238    /// - `to`: The ending point of the arc.
239    /// - `color`: color of the arc
240    ///
241    /// # Builder methods
242    /// The resolution of the arc (i.e. the level of detail) can be adjusted with the
243    /// `.resolution(...)` method.
244    ///
245    /// # Examples
246    /// ```
247    /// # use bevy_gizmos::prelude::*;
248    /// # use bevy_math::prelude::*;
249    /// # use bevy_color::palettes::css::ORANGE;
250    /// fn system(mut gizmos: Gizmos) {
251    ///     gizmos.long_arc_3d_between(
252    ///        Vec3::ONE,
253    ///        Vec3::ONE + Vec3::NEG_ONE,
254    ///        Vec3::ZERO,
255    ///        ORANGE
256    ///        )
257    ///        .resolution(100);
258    /// }
259    /// # bevy_ecs::system::assert_is_system(system);
260    /// ```
261    ///
262    /// # Notes
263    /// - This method assumes that the points `from` and `to` are distinct from `center`. If one of
264    ///   the points is coincident with `center`, nothing is rendered.
265    /// - The arc is drawn as a portion of a circle with a radius equal to the distance from the
266    ///   `center` to `from`. If the distance from `center` to `to` is not equal to the radius, then
267    ///   the results will behave as if this were the case.
268    #[inline]
269    pub fn long_arc_3d_between(
270        &mut self,
271        center: Vec3,
272        from: Vec3,
273        to: Vec3,
274        color: impl Into<Color>,
275    ) -> Arc3dBuilder<'_, Config, Clear> {
276        self.arc_from_to(center, from, to, color, |angle| {
277            if angle > 0.0 {
278                TAU - angle
279            } else if angle < 0.0 {
280                -TAU - angle
281            } else {
282                0.0
283            }
284        })
285    }
286
287    #[inline]
288    fn arc_from_to(
289        &mut self,
290        center: Vec3,
291        from: Vec3,
292        to: Vec3,
293        color: impl Into<Color>,
294        angle_fn: impl Fn(f32) -> f32,
295    ) -> Arc3dBuilder<'_, Config, Clear> {
296        // `from` and `to` can be the same here since in either case nothing gets rendered and the
297        // orientation ambiguity of `up` doesn't matter
298        let from_axis = (from - center).normalize_or_zero();
299        let to_axis = (to - center).normalize_or_zero();
300        let (up, angle) = Quat::from_rotation_arc(from_axis, to_axis).to_axis_angle();
301
302        let angle = angle_fn(angle);
303        let radius = center.distance(from);
304        let rotation = Quat::from_rotation_arc(Vec3::Y, up);
305
306        let start_vertex = rotation.inverse() * from_axis;
307
308        Arc3dBuilder {
309            gizmos: self,
310            start_vertex,
311            isometry: Isometry3d::new(center, rotation),
312            angle,
313            radius,
314            color: color.into(),
315            resolution: None,
316        }
317    }
318
319    /// Draws the shortest arc between two points (`from` and `to`) relative to a specified `center` point.
320    ///
321    /// # Arguments
322    ///
323    /// - `center`: The center point around which the arc is drawn.
324    /// - `from`: The starting point of the arc.
325    /// - `to`: The ending point of the arc.
326    /// - `color`: color of the arc
327    ///
328    /// # Builder methods
329    /// The resolution of the arc (i.e. the level of detail) can be adjusted with the
330    /// `.resolution(...)` method.
331    ///
332    /// # Examples
333    /// ```
334    /// # use bevy_gizmos::prelude::*;
335    /// # use bevy_math::prelude::*;
336    /// # use bevy_color::palettes::css::ORANGE;
337    /// fn system(mut gizmos: Gizmos) {
338    ///     gizmos.short_arc_2d_between(
339    ///        Vec2::ZERO,
340    ///        Vec2::X,
341    ///        Vec2::Y,
342    ///        ORANGE
343    ///        )
344    ///        .resolution(100);
345    /// }
346    /// # bevy_ecs::system::assert_is_system(system);
347    /// ```
348    ///
349    /// # Notes
350    /// - This method assumes that the points `from` and `to` are distinct from `center`. If one of
351    ///   the points is coincident with `center`, nothing is rendered.
352    /// - The arc is drawn as a portion of a circle with a radius equal to the distance from the
353    ///   `center` to `from`. If the distance from `center` to `to` is not equal to the radius, then
354    ///   the results will behave as if this were the case
355    #[inline]
356    pub fn short_arc_2d_between(
357        &mut self,
358        center: Vec2,
359        from: Vec2,
360        to: Vec2,
361        color: impl Into<Color>,
362    ) -> Arc2dBuilder<'_, Config, Clear> {
363        self.arc_2d_from_to(center, from, to, color, core::convert::identity)
364    }
365
366    /// Draws the longest arc between two points (`from` and `to`) relative to a specified `center` point.
367    ///
368    /// # Arguments
369    /// - `center`: The center point around which the arc is drawn.
370    /// - `from`: The starting point of the arc.
371    /// - `to`: The ending point of the arc.
372    /// - `color`: color of the arc
373    ///
374    /// # Builder methods
375    /// The resolution of the arc (i.e. the level of detail) can be adjusted with the
376    /// `.resolution(...)` method.
377    ///
378    /// # Examples
379    /// ```
380    /// # use bevy_gizmos::prelude::*;
381    /// # use bevy_math::prelude::*;
382    /// # use bevy_color::palettes::css::ORANGE;
383    /// fn system(mut gizmos: Gizmos) {
384    ///     gizmos.long_arc_2d_between(
385    ///        Vec2::ZERO,
386    ///        Vec2::X,
387    ///        Vec2::Y,
388    ///        ORANGE
389    ///        )
390    ///        .resolution(100);
391    /// }
392    /// # bevy_ecs::system::assert_is_system(system);
393    /// ```
394    ///
395    /// # Notes
396    /// - This method assumes that the points `from` and `to` are distinct from `center`. If one of
397    ///   the points is coincident with `center`, nothing is rendered.
398    /// - The arc is drawn as a portion of a circle with a radius equal to the distance from the
399    ///   `center` to `from`. If the distance from `center` to `to` is not equal to the radius, then
400    ///   the results will behave as if this were the case.
401    #[inline]
402    pub fn long_arc_2d_between(
403        &mut self,
404        center: Vec2,
405        from: Vec2,
406        to: Vec2,
407        color: impl Into<Color>,
408    ) -> Arc2dBuilder<'_, Config, Clear> {
409        self.arc_2d_from_to(center, from, to, color, |angle| angle - TAU)
410    }
411
412    #[inline]
413    fn arc_2d_from_to(
414        &mut self,
415        center: Vec2,
416        from: Vec2,
417        to: Vec2,
418        color: impl Into<Color>,
419        angle_fn: impl Fn(f32) -> f32,
420    ) -> Arc2dBuilder<'_, Config, Clear> {
421        // `from` and `to` can be the same here since in either case nothing gets rendered and the
422        // orientation ambiguity of `up` doesn't matter
423        let from_axis = (from - center).normalize_or_zero();
424        let to_axis = (to - center).normalize_or_zero();
425        let rotation = Vec2::Y.angle_to(from_axis);
426        let arc_angle_raw = from_axis.angle_to(to_axis);
427
428        let arc_angle = angle_fn(arc_angle_raw);
429        let radius = center.distance(from);
430
431        Arc2dBuilder {
432            gizmos: self,
433            isometry: Isometry2d::new(center, Rot2::radians(rotation)),
434            arc_angle,
435            radius,
436            color: color.into(),
437            resolution: None,
438        }
439    }
440}
441
442/// A builder returned by [`GizmoBuffer::arc_2d`].
443pub struct Arc3dBuilder<'a, Config, Clear>
444where
445    Config: GizmoConfigGroup,
446    Clear: 'static + Send + Sync,
447{
448    gizmos: &'a mut GizmoBuffer<Config, Clear>,
449    // this is the vertex the arc starts on in the XZ plane. For the normal arc_3d method this is
450    // always starting at Vec3::X. For the short/long arc methods we actually need a way to start
451    // at the from position and this is where this internal field comes into play. Some implicit
452    // assumptions:
453    //
454    // 1. This is always in the XZ plane
455    // 2. This is always normalized
456    //
457    // DO NOT expose this field to users as it is easy to mess this up
458    start_vertex: Vec3,
459    isometry: Isometry3d,
460    angle: f32,
461    radius: f32,
462    color: Color,
463    resolution: Option<u32>,
464}
465
466impl<Config, Clear> Arc3dBuilder<'_, Config, Clear>
467where
468    Config: GizmoConfigGroup,
469    Clear: 'static + Send + Sync,
470{
471    /// Set the number of lines for this arc.
472    pub fn resolution(mut self, resolution: u32) -> Self {
473        self.resolution.replace(resolution);
474        self
475    }
476}
477
478impl<Config, Clear> Drop for Arc3dBuilder<'_, Config, Clear>
479where
480    Config: GizmoConfigGroup,
481    Clear: 'static + Send + Sync,
482{
483    fn drop(&mut self) {
484        if !self.gizmos.enabled {
485            return;
486        }
487
488        let resolution = self
489            .resolution
490            .unwrap_or_else(|| resolution_from_angle(self.angle));
491
492        let positions = arc_3d_inner(
493            self.start_vertex,
494            self.isometry,
495            self.angle,
496            self.radius,
497            resolution,
498        );
499        self.gizmos.linestrip(positions, self.color);
500    }
501}
502
503fn arc_3d_inner(
504    start_vertex: Vec3,
505    isometry: Isometry3d,
506    angle: f32,
507    radius: f32,
508    resolution: u32,
509) -> impl Iterator<Item = Vec3> {
510    // drawing arcs bigger than TAU degrees or smaller than -TAU degrees makes no sense since
511    // we won't see the overlap and we would just decrease the level of details since the resolution
512    // would be larger
513    let angle = angle.clamp(-TAU, TAU);
514    (0..=resolution)
515        .map(move |frac| frac as f32 / resolution as f32)
516        .map(move |percentage| angle * percentage)
517        .map(move |frac_angle| Quat::from_axis_angle(Vec3::Y, frac_angle) * start_vertex)
518        .map(move |vec3| vec3 * radius)
519        .map(move |vec3| isometry * vec3)
520}
521
522// helper function for getting a default value for the resolution parameter
523fn resolution_from_angle(angle: f32) -> u32 {
524    ((angle.abs() / TAU) * DEFAULT_CIRCLE_RESOLUTION as f32).ceil() as u32
525}