Skip to main content

bevy_gizmos/
arrows.rs

1//! Additional [`GizmoBuffer`] Functions -- Arrows
2//!
3//! Includes the implementation of [`GizmoBuffer::arrow`] and [`GizmoBuffer::arrow_2d`],
4//! and assorted support items.
5
6use crate::{gizmos::GizmoBuffer, prelude::GizmoConfigGroup};
7use bevy_color::{
8    palettes::basic::{BLUE, GREEN, RED},
9    Color,
10};
11use bevy_math::{Quat, Vec2, Vec3, Vec3Swizzles};
12use bevy_transform::TransformPoint;
13
14/// A builder returned by [`GizmoBuffer::arrow`] and [`GizmoBuffer::arrow_2d`]
15pub struct ArrowBuilder<'a, Config, Clear>
16where
17    Config: GizmoConfigGroup,
18    Clear: 'static + Send + Sync,
19{
20    gizmos: &'a mut GizmoBuffer<Config, Clear>,
21    start: Vec3,
22    end: Vec3,
23    color: Color,
24    double_ended: bool,
25    tip_length: f32,
26}
27
28impl<Config, Clear> ArrowBuilder<'_, Config, Clear>
29where
30    Config: GizmoConfigGroup,
31    Clear: 'static + Send + Sync,
32{
33    /// Change the length of the tips to be `length`.
34    /// The default tip length is [length of the arrow]/10.
35    ///
36    /// # Example
37    /// ```
38    /// # use bevy_gizmos::prelude::*;
39    /// # use bevy_math::prelude::*;
40    /// # use bevy_color::palettes::basic::GREEN;
41    /// fn system(mut gizmos: Gizmos) {
42    ///     gizmos.arrow(Vec3::ZERO, Vec3::ONE, GREEN)
43    ///         .with_tip_length(3.);
44    /// }
45    /// # bevy_ecs::system::assert_is_system(system);
46    /// ```
47    #[doc(alias = "arrow_head_length")]
48    pub fn with_tip_length(mut self, length: f32) -> Self {
49        self.tip_length = length;
50        self
51    }
52
53    /// Adds another tip to the arrow, appended in the start point.
54    /// the default is only one tip at the end point.
55    pub fn with_double_end(mut self) -> Self {
56        self.double_ended = true;
57        self
58    }
59}
60
61impl<Config, Clear> Drop for ArrowBuilder<'_, Config, Clear>
62where
63    Config: GizmoConfigGroup,
64    Clear: 'static + Send + Sync,
65{
66    /// Draws the arrow, by drawing lines with the stored [`GizmoBuffer`]
67    fn drop(&mut self) {
68        if !self.gizmos.enabled {
69            return;
70        }
71
72        // first, draw the body (main line) of the arrow
73        if self.double_ended {
74            let center = (self.start + self.end) / 2.0;
75            // drawing two lines for double ended arrows looks better for animated gizmos
76            self.gizmos.line(center, self.start, self.color);
77            self.gizmos.line(center, self.end, self.color);
78        } else {
79            self.gizmos.line(self.start, self.end, self.color);
80        }
81        // now the hard part is to draw the head in a sensible way
82        // put us in a coordinate system where the arrow is pointing towards +x and ends at the origin
83        let pointing_end = (self.end - self.start).normalize();
84        let rotation_end = Quat::from_rotation_arc(Vec3::X, pointing_end);
85        let tips = [
86            Vec3::new(-1., 1., 0.),
87            Vec3::new(-1., 0., 1.),
88            Vec3::new(-1., -1., 0.),
89            Vec3::new(-1., 0., -1.),
90        ];
91        // - extend the vectors so their length is `tip_length`
92        // - rotate the world so +x is facing in the same direction as the arrow
93        // - translate over to the tip of the arrow
94        let tips_end = tips.map(|v| rotation_end * (v.normalize() * self.tip_length) + self.end);
95        for v in tips_end {
96            // then actually draw the tips
97            self.gizmos.line(self.end, v, self.color);
98        }
99        if self.double_ended {
100            let pointing_start = (self.start - self.end).normalize();
101            let rotation_start = Quat::from_rotation_arc(Vec3::X, pointing_start);
102            let tips_start =
103                tips.map(|v| rotation_start * (v.normalize() * self.tip_length) + self.start);
104            for v in tips_start {
105                // draw the start points tips
106                self.gizmos.line(self.start, v, self.color);
107            }
108        }
109    }
110}
111
112impl<Config, Clear> GizmoBuffer<Config, Clear>
113where
114    Config: GizmoConfigGroup,
115    Clear: 'static + Send + Sync,
116{
117    /// Draw an arrow in 3D, from `start` to `end`. Has four tips for convenient viewing from any direction.
118    ///
119    /// # Example
120    /// ```
121    /// # use bevy_gizmos::prelude::*;
122    /// # use bevy_math::prelude::*;
123    /// # use bevy_color::palettes::basic::GREEN;
124    /// fn system(mut gizmos: Gizmos) {
125    ///     gizmos.arrow(Vec3::ZERO, Vec3::ONE, GREEN);
126    /// }
127    /// # bevy_ecs::system::assert_is_system(system);
128    /// ```
129    pub fn arrow(
130        &mut self,
131        start: Vec3,
132        end: Vec3,
133        color: impl Into<Color>,
134    ) -> ArrowBuilder<'_, Config, Clear> {
135        let length = (end - start).length();
136        ArrowBuilder {
137            gizmos: self,
138            start,
139            end,
140            color: color.into(),
141            double_ended: false,
142            tip_length: length / 10.,
143        }
144    }
145
146    /// Draw an arrow in 2D (on the xy plane), from `start` to `end`.
147    ///
148    /// # Example
149    /// ```
150    /// # use bevy_gizmos::prelude::*;
151    /// # use bevy_math::prelude::*;
152    /// # use bevy_color::palettes::basic::GREEN;
153    /// fn system(mut gizmos: Gizmos) {
154    ///     gizmos.arrow_2d(Vec2::ZERO, Vec2::X, GREEN);
155    /// }
156    /// # bevy_ecs::system::assert_is_system(system);
157    /// ```
158    pub fn arrow_2d(
159        &mut self,
160        start: Vec2,
161        end: Vec2,
162        color: impl Into<Color>,
163    ) -> ArrowBuilder<'_, Config, Clear> {
164        self.arrow(start.extend(0.), end.extend(0.), color)
165    }
166}
167
168impl<Config, Clear> GizmoBuffer<Config, Clear>
169where
170    Config: GizmoConfigGroup,
171    Clear: 'static + Send + Sync,
172{
173    /// Draw a set of axes local to the given transform (`transform`), with length scaled by a factor
174    /// of `base_length`.
175    ///
176    /// X is red
177    /// Y is green
178    /// Z is blue
179    ///
180    /// # Example
181    /// ```
182    /// # use bevy_gizmos::prelude::*;
183    /// # use bevy_ecs::prelude::*;
184    /// # use bevy_transform::components::Transform;
185    /// # #[derive(Component)]
186    /// # struct MyComponent;
187    /// fn draw_axes(
188    ///     mut gizmos: Gizmos,
189    ///     query: Query<&Transform, With<MyComponent>>,
190    /// ) {
191    ///     for &transform in &query {
192    ///         gizmos.axes(transform, 1.);
193    ///     }
194    /// }
195    /// # bevy_ecs::system::assert_is_system(draw_axes);
196    /// ```
197    pub fn axes(&mut self, transform: impl TransformPoint, base_length: f32) {
198        let start = transform.transform_point(Vec3::ZERO);
199        let end_x = transform.transform_point(base_length * Vec3::X);
200        let end_y = transform.transform_point(base_length * Vec3::Y);
201        let end_z = transform.transform_point(base_length * Vec3::Z);
202
203        self.arrow(start, end_x, RED);
204        self.arrow(start, end_y, GREEN);
205        self.arrow(start, end_z, BLUE);
206    }
207
208    /// Draw a set of axes local to the given transform (`transform`), with length scaled by a factor
209    /// of `base_length`.
210    ///
211    /// # Example
212    /// ```
213    /// # use bevy_gizmos::prelude::*;
214    /// # use bevy_ecs::prelude::*;
215    /// # use bevy_transform::components::Transform;
216    /// # #[derive(Component)]
217    /// # struct AxesComponent;
218    /// fn draw_axes_2d(
219    ///     mut gizmos: Gizmos,
220    ///     query: Query<&Transform, With<AxesComponent>>,
221    /// ) {
222    ///     for &transform in &query {
223    ///         gizmos.axes_2d(transform, 1.);
224    ///     }
225    /// }
226    /// # bevy_ecs::system::assert_is_system(draw_axes_2d);
227    /// ```
228    pub fn axes_2d(&mut self, transform: impl TransformPoint, base_length: f32) {
229        let start = transform.transform_point(Vec3::ZERO);
230        let end_x = transform.transform_point(base_length * Vec3::X);
231        let end_y = transform.transform_point(base_length * Vec3::Y);
232
233        self.arrow_2d(start.xy(), end_x.xy(), RED);
234        self.arrow_2d(start.xy(), end_y.xy(), GREEN);
235    }
236}