
# Objective - We introduce a gizmo that displays coordinate axes relative to a Transform*, primarily for debugging purposes. - See #9400 ## Solution A new method, `Gizmos::axes`, takes a `Transform`* as input and displays the standard coordinate axes, transformed according to it; its signature looks like this: ````rust pub fn axes(&mut self, transform: into TransformPoint, base_length: f32) { //... } ```` If my carefully placed asterisks hadn't already tipped you off, the argument here is not actually a `Transform` but instead anything which implements `TransformPoint`, which allows it to work also with `GlobalTransform` (and also `Mat4` and `Affine3A`, if the user happens to be hand-rolling transformations in some way). The `base_length` parameter is a scaling factor applied to the coordinate vectors before the transformation takes place; in other words, the caller can use this to help size the coordinate axes appropriately for the entity that they are attached to. An example invocation of this method looks something like this: ````rust fn draw_axes_system( mut gizmos: Gizmos, query: Query<&Transform, With<MyMarkerComponent>>, ) { for &transform in &query { gizmos.axes(transform, 2.); } } ```` The result is the three coordinate axes, X, Y, Z (colored red, green, and blue, respectively), drawn onto the entity: <img width="206" alt="Screenshot 2024-02-29 at 2 41 45 PM" src="https://github.com/bevyengine/bevy/assets/2975848/789d1703-29ae-4295-80ab-b87459cf8037"> Note that, if scaling was applied as part of the given transformation, it shows up in scaling on the axes as well: <img width="377" alt="Screenshot 2024-02-29 at 2 43 53 PM" src="https://github.com/bevyengine/bevy/assets/2975848/6dc1caf4-8b3e-47f7-a86a-8906d870fa72"> --- ## Changelog - Added `Gizmos::axes` in bevy_gizmos/src/arrows.rs - Fixed a minor issue with `ArrowBuilder::with_tip_length` not correctly implementing builder style (no external impact) --- ## Discussion ### Design considerations I feel pretty strongly that having no default length scale is for the best, at least for the time being, since it's very easy for the length scale to be too small, leading to the axes being hidden inside the body of the object they are associated with. That is, if the API instead looked like this: ````rust gizmos.axes(transform); // or gizmos.axes(transform).with_length_scale(3.0); ```` then I think it's a reasonable expectation that the first thing would "just work" for most applications, and it wouldn't, which would be kind of a footgun. ### Future steps There are a few directions that this might expand in the future: 1. Introduce additional options via the standard builder pattern; i.e. introducing `AxesBuilder<T: TransformPoint>` so that people can configure the axis colors, normalize all axes to a fixed length independent of scale deformations, etc. 2. Fold this functionality into a plugin (like AabbGizmoPlugin) so that the functionality becomes more-or-less automatic based on certain fixed marker components. This wouldn't be very hard to implement, and it has the benefit of making the axes more frictionless to use. Furthermore, if we coupled this to the AABB functionality we already have, we could also ensure that the plugin automatically sizes the axes (by coupling their size to the dimensions of the AABB, for example). 3. Implement something similar for 2d. Honestly, I have no idea if this is desired/useful, but I could probably just implement it in this PR if that's the case.
166 lines
5.4 KiB
Rust
166 lines
5.4 KiB
Rust
//! Additional [`Gizmos`] Functions -- Arrows
|
|
//!
|
|
//! Includes the implementation of [`Gizmos::arrow`] and [`Gizmos::arrow_2d`],
|
|
//! and assorted support items.
|
|
|
|
use crate::prelude::{GizmoConfigGroup, Gizmos};
|
|
use bevy_color::{
|
|
palettes::basic::{BLUE, GREEN, RED},
|
|
Color,
|
|
};
|
|
use bevy_math::{Quat, Vec2, Vec3};
|
|
use bevy_transform::TransformPoint;
|
|
|
|
/// A builder returned by [`Gizmos::arrow`] and [`Gizmos::arrow_2d`]
|
|
pub struct ArrowBuilder<'a, 'w, 's, T: GizmoConfigGroup> {
|
|
gizmos: &'a mut Gizmos<'w, 's, T>,
|
|
start: Vec3,
|
|
end: Vec3,
|
|
color: Color,
|
|
tip_length: f32,
|
|
}
|
|
|
|
impl<T: GizmoConfigGroup> ArrowBuilder<'_, '_, '_, T> {
|
|
/// Change the length of the tips to be `length`.
|
|
/// The default tip length is [length of the arrow]/10.
|
|
///
|
|
/// # Example
|
|
/// ```
|
|
/// # use bevy_gizmos::prelude::*;
|
|
/// # use bevy_render::prelude::*;
|
|
/// # use bevy_math::prelude::*;
|
|
/// # use bevy_color::palettes::basic::GREEN;
|
|
/// fn system(mut gizmos: Gizmos) {
|
|
/// gizmos.arrow(Vec3::ZERO, Vec3::ONE, GREEN)
|
|
/// .with_tip_length(3.);
|
|
/// }
|
|
/// # bevy_ecs::system::assert_is_system(system);
|
|
/// ```
|
|
#[doc(alias = "arrow_head_length")]
|
|
pub fn with_tip_length(mut self, length: f32) -> Self {
|
|
self.tip_length = length;
|
|
self
|
|
}
|
|
}
|
|
|
|
impl<T: GizmoConfigGroup> Drop for ArrowBuilder<'_, '_, '_, T> {
|
|
/// Draws the arrow, by drawing lines with the stored [`Gizmos`]
|
|
fn drop(&mut self) {
|
|
if !self.gizmos.enabled {
|
|
return;
|
|
}
|
|
// first, draw the body of the arrow
|
|
self.gizmos.line(self.start, self.end, self.color);
|
|
// now the hard part is to draw the head in a sensible way
|
|
// put us in a coordinate system where the arrow is pointing towards +x and ends at the origin
|
|
let pointing = (self.end - self.start).normalize();
|
|
let rotation = Quat::from_rotation_arc(Vec3::X, pointing);
|
|
let tips = [
|
|
Vec3::new(-1., 1., 0.),
|
|
Vec3::new(-1., 0., 1.),
|
|
Vec3::new(-1., -1., 0.),
|
|
Vec3::new(-1., 0., -1.),
|
|
];
|
|
// - extend the vectors so their length is `tip_length`
|
|
// - rotate the world so +x is facing in the same direction as the arrow
|
|
// - translate over to the tip of the arrow
|
|
let tips = tips.map(|v| rotation * (v.normalize() * self.tip_length) + self.end);
|
|
for v in tips {
|
|
// then actually draw the tips
|
|
self.gizmos.line(self.end, v, self.color);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> {
|
|
/// Draw an arrow in 3D, from `start` to `end`. Has four tips for convenient viewing from any direction.
|
|
///
|
|
/// This should be called for each frame the arrow needs to be rendered.
|
|
///
|
|
/// # Example
|
|
/// ```
|
|
/// # use bevy_gizmos::prelude::*;
|
|
/// # use bevy_render::prelude::*;
|
|
/// # use bevy_math::prelude::*;
|
|
/// # use bevy_color::palettes::basic::GREEN;
|
|
/// fn system(mut gizmos: Gizmos) {
|
|
/// gizmos.arrow(Vec3::ZERO, Vec3::ONE, GREEN);
|
|
/// }
|
|
/// # bevy_ecs::system::assert_is_system(system);
|
|
/// ```
|
|
pub fn arrow(
|
|
&mut self,
|
|
start: Vec3,
|
|
end: Vec3,
|
|
color: impl Into<Color>,
|
|
) -> ArrowBuilder<'_, 'w, 's, T> {
|
|
let length = (end - start).length();
|
|
ArrowBuilder {
|
|
gizmos: self,
|
|
start,
|
|
end,
|
|
color: color.into(),
|
|
tip_length: length / 10.,
|
|
}
|
|
}
|
|
|
|
/// Draw an arrow in 2D (on the xy plane), from `start` to `end`.
|
|
///
|
|
/// This should be called for each frame the arrow needs to be rendered.
|
|
///
|
|
/// # Example
|
|
/// ```
|
|
/// # use bevy_gizmos::prelude::*;
|
|
/// # use bevy_render::prelude::*;
|
|
/// # use bevy_math::prelude::*;
|
|
/// # use bevy_color::palettes::basic::GREEN;
|
|
/// fn system(mut gizmos: Gizmos) {
|
|
/// gizmos.arrow_2d(Vec2::ZERO, Vec2::X, GREEN);
|
|
/// }
|
|
/// # bevy_ecs::system::assert_is_system(system);
|
|
/// ```
|
|
pub fn arrow_2d(
|
|
&mut self,
|
|
start: Vec2,
|
|
end: Vec2,
|
|
color: impl Into<Color>,
|
|
) -> ArrowBuilder<'_, 'w, 's, T> {
|
|
self.arrow(start.extend(0.), end.extend(0.), color)
|
|
}
|
|
}
|
|
|
|
impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> {
|
|
/// Draw a set of axes local to the given transform (`transform`), with length scaled by a factor
|
|
/// of `base_length`.
|
|
///
|
|
/// This should be called for each frame the axes need to be rendered.
|
|
///
|
|
/// # Example
|
|
/// ```
|
|
/// # use bevy_gizmos::prelude::*;
|
|
/// # use bevy_ecs::prelude::*;
|
|
/// # use bevy_transform::components::Transform;
|
|
/// # #[derive(Component)]
|
|
/// # struct MyComponent;
|
|
/// fn draw_axes(
|
|
/// mut gizmos: Gizmos,
|
|
/// query: Query<&Transform, With<MyComponent>>,
|
|
/// ) {
|
|
/// for &transform in &query {
|
|
/// gizmos.axes(transform, 1.);
|
|
/// }
|
|
/// }
|
|
/// # bevy_ecs::system::assert_is_system(draw_axes);
|
|
/// ```
|
|
pub fn axes(&mut self, transform: impl TransformPoint, base_length: f32) {
|
|
let start = transform.transform_point(Vec3::ZERO);
|
|
let end_x = transform.transform_point(base_length * Vec3::X);
|
|
let end_y = transform.transform_point(base_length * Vec3::Y);
|
|
let end_z = transform.transform_point(base_length * Vec3::Z);
|
|
|
|
self.arrow(start, end_x, RED);
|
|
self.arrow(start, end_y, GREEN);
|
|
self.arrow(start, end_z, BLUE);
|
|
}
|
|
}
|