From ee0fa7d1c22adf5bf4cbac2553e1055b7f3cc579 Mon Sep 17 00:00:00 2001
From: Lynn <62256001+solis-lumine-vorago@users.noreply.github.com>
Date: Wed, 13 Mar 2024 19:51:53 +0100
Subject: [PATCH] Gizmo 3d grids (#12430)
# Objective
- Adds 3d grids, suggestion of #9400
## Solution
- Added 3d grids (grids spanning all three dimensions, not flat grids)
to bevy_gizmos
---
## Changelog
- `gizmos.grid(...)` and `gizmos.grid_2d(...)` now return a
`GridBuilder2d`.
- Added `gizmos.grid_3d(...)` which returns a `GridBuilder3d`.
- The difference between them is basically only that `GridBuilder3d`
exposes some methods for configuring the z axis while the 2d version
doesn't.
- Allowed for drawing the outer edges along a specific axis by calling
`.outer_edges_x()`, etc. on the builder.
## Additional information
Please note that I have not added the 3d grid to any example as not to
clutter them.
Here is an image of what the 3d grid looks like:
---------
Co-authored-by: Alice Cecile
---
crates/bevy_gizmos/src/grid.rs | 333 ++++++++++++++++++++++++++-------
examples/gizmos/2d_gizmos.rs | 2 +-
2 files changed, 264 insertions(+), 71 deletions(-)
diff --git a/crates/bevy_gizmos/src/grid.rs b/crates/bevy_gizmos/src/grid.rs
index c7b007746849c..a00a8c4fa19bc 100644
--- a/crates/bevy_gizmos/src/grid.rs
+++ b/crates/bevy_gizmos/src/grid.rs
@@ -5,29 +5,91 @@
use crate::prelude::{GizmoConfigGroup, Gizmos};
use bevy_color::LinearRgba;
-use bevy_math::{Quat, UVec2, Vec2, Vec3};
+use bevy_math::{Quat, UVec2, UVec3, Vec2, Vec3};
+/// A builder returned by [`Gizmos::grid_3d`]
+pub struct GridBuilder3d<'a, 'w, 's, T: GizmoConfigGroup> {
+ gizmos: &'a mut Gizmos<'w, 's, T>,
+ position: Vec3,
+ rotation: Quat,
+ spacing: Vec3,
+ cell_count: UVec3,
+ skew: Vec3,
+ outer_edges: [bool; 3],
+ color: LinearRgba,
+}
/// A builder returned by [`Gizmos::grid`] and [`Gizmos::grid_2d`]
-pub struct GridBuilder<'a, 'w, 's, T: GizmoConfigGroup> {
+pub struct GridBuilder2d<'a, 'w, 's, T: GizmoConfigGroup> {
gizmos: &'a mut Gizmos<'w, 's, T>,
position: Vec3,
rotation: Quat,
spacing: Vec2,
cell_count: UVec2,
skew: Vec2,
- outer_edges: bool,
+ outer_edges: [bool; 2],
color: LinearRgba,
}
-impl GridBuilder<'_, '_, '_, T> {
+impl GridBuilder3d<'_, '_, '_, T> {
/// Skews the grid by `tan(skew)` in the x direction.
/// `skew` is in radians
pub fn skew_x(mut self, skew: f32) -> Self {
self.skew.x = skew;
self
}
+ /// Skews the grid by `tan(skew)` in the y direction.
+ /// `skew` is in radians
+ pub fn skew_y(mut self, skew: f32) -> Self {
+ self.skew.y = skew;
+ self
+ }
+ /// Skews the grid by `tan(skew)` in the z direction.
+ /// `skew` is in radians
+ pub fn skew_z(mut self, skew: f32) -> Self {
+ self.skew.z = skew;
+ self
+ }
+ /// Skews the grid by `tan(skew)` in the x, y and z directions.
+ /// `skew` is in radians
+ pub fn skew(mut self, skew: Vec3) -> Self {
+ self.skew = skew;
+ self
+ }
+
+ /// Declare that the outer edges of the grid along the x axis should be drawn.
+ /// By default, the outer edges will not be drawn.
+ pub fn outer_edges_x(mut self) -> Self {
+ self.outer_edges[0] = true;
+ self
+ }
+ /// Declare that the outer edges of the grid along the y axis should be drawn.
+ /// By default, the outer edges will not be drawn.
+ pub fn outer_edges_y(mut self) -> Self {
+ self.outer_edges[1] = true;
+ self
+ }
+ /// Declare that the outer edges of the grid along the z axis should be drawn.
+ /// By default, the outer edges will not be drawn.
+ pub fn outer_edges_z(mut self) -> Self {
+ self.outer_edges[2] = true;
+ self
+ }
+ /// Declare that all outer edges of the grid should be drawn.
+ /// By default, the outer edges will not be drawn.
+ pub fn outer_edges(mut self) -> Self {
+ self.outer_edges.fill(true);
+ self
+ }
+}
+impl GridBuilder2d<'_, '_, '_, T> {
/// Skews the grid by `tan(skew)` in the x direction.
/// `skew` is in radians
+ pub fn skew_x(mut self, skew: f32) -> Self {
+ self.skew.x = skew;
+ self
+ }
+ /// Skews the grid by `tan(skew)` in the y direction.
+ /// `skew` is in radians
pub fn skew_y(mut self, skew: f32) -> Self {
self.skew.y = skew;
self
@@ -39,70 +101,54 @@ impl GridBuilder<'_, '_, '_, T> {
self
}
- /// Toggle whether the outer edges of the grid should be drawn.
+ /// Declare that the outer edges of the grid along the x axis should be drawn.
+ /// By default, the outer edges will not be drawn.
+ pub fn outer_edges_x(mut self) -> Self {
+ self.outer_edges[0] = true;
+ self
+ }
+ /// Declare that the outer edges of the grid along the y axis should be drawn.
/// By default, the outer edges will not be drawn.
- pub fn outer_edges(mut self, outer_edges: bool) -> Self {
- self.outer_edges = outer_edges;
+ pub fn outer_edges_y(mut self) -> Self {
+ self.outer_edges[1] = true;
+ self
+ }
+ /// Declare that all outer edges of the grid should be drawn.
+ /// By default, the outer edges will not be drawn.
+ pub fn outer_edges(mut self) -> Self {
+ self.outer_edges.fill(true);
self
}
}
-impl Drop for GridBuilder<'_, '_, '_, T> {
- /// Draws a grid, by drawing lines with the stored [`Gizmos`]
+impl Drop for GridBuilder3d<'_, '_, '_, T> {
fn drop(&mut self) {
- if !self.gizmos.enabled {
- return;
- }
-
- // Offset between two adjacent grid cells along the x/y-axis and accounting for skew.
- let dx = Vec3::new(self.spacing.x, self.spacing.x * self.skew.y.tan(), 0.);
- let dy = Vec3::new(self.spacing.y * self.skew.x.tan(), self.spacing.y, 0.);
-
- // Bottom-left corner of the grid
- let grid_start = self.position
- - self.cell_count.x as f32 / 2.0 * dx
- - self.cell_count.y as f32 / 2.0 * dy;
-
- let (line_count, vertical_start, horizontal_start) = if self.outer_edges {
- (self.cell_count + UVec2::ONE, grid_start, grid_start)
- } else {
- (
- self.cell_count.saturating_sub(UVec2::ONE),
- grid_start + dx,
- grid_start + dy,
- )
- };
-
- // Vertical lines
- let dline = dy * self.cell_count.y as f32;
- for i in 0..line_count.x {
- let i = i as f32;
- let line_start = vertical_start + i * dx;
- let line_end = line_start + dline;
-
- self.gizmos.line(
- self.rotation * line_start,
- self.rotation * line_end,
- self.color,
- );
- }
-
- // Horizontal lines
- let dline = dx * self.cell_count.x as f32;
- for i in 0..line_count.y {
- let i = i as f32;
- let line_start = horizontal_start + i * dy;
- let line_end = line_start + dline;
-
- self.gizmos.line(
- self.rotation * line_start,
- self.rotation * line_end,
- self.color,
- );
- }
+ draw_grid(
+ self.gizmos,
+ self.position,
+ self.rotation,
+ self.spacing,
+ self.cell_count,
+ self.skew,
+ self.outer_edges,
+ self.color,
+ );
+ }
+}
+impl Drop for GridBuilder2d<'_, '_, '_, T> {
+ fn drop(&mut self) {
+ draw_grid(
+ self.gizmos,
+ self.position,
+ self.rotation,
+ self.spacing.extend(0.),
+ self.cell_count.extend(0),
+ self.skew.extend(0.),
+ [self.outer_edges[0], self.outer_edges[1], true],
+ self.color,
+ );
}
}
-
impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> {
/// Draw a 2D grid in 3D.
///
@@ -119,7 +165,7 @@ impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> {
/// # Builder methods
///
/// - The skew of the grid can be adjusted using the `.skew(...)`, `.skew_x(...)` or `.skew_y(...)` methods. They behave very similar to their CSS equivalents.
- /// - The outer edges can be toggled on or off using `.outer_edges(...)`.
+ /// - All outer edges can be toggled on or off using `.outer_edges(...)`. Alternatively you can use `.outer_edges_x(...)` or `.outer_edges_y(...)` to toggle the outer edges along an axis.
///
/// # Example
/// ```
@@ -136,7 +182,7 @@ impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> {
/// GREEN
/// )
/// .skew_x(0.25)
- /// .outer_edges(true);
+ /// .outer_edges();
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
@@ -147,15 +193,71 @@ impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> {
cell_count: UVec2,
spacing: Vec2,
color: impl Into,
- ) -> GridBuilder<'_, 'w, 's, T> {
- GridBuilder {
+ ) -> GridBuilder2d<'_, 'w, 's, T> {
+ GridBuilder2d {
gizmos: self,
position,
rotation,
spacing,
cell_count,
skew: Vec2::ZERO,
- outer_edges: false,
+ outer_edges: [false, false],
+ color: color.into(),
+ }
+ }
+
+ /// Draw a 3D grid of voxel-like cells.
+ ///
+ /// This should be called for each frame the grid needs to be rendered.
+ ///
+ /// # Arguments
+ ///
+ /// - `position`: The center point of the grid.
+ /// - `rotation`: defines the orientation of the grid, by default we assume the grid is contained in a plane parallel to the XY plane.
+ /// - `cell_count`: defines the amount of cells in the x, y and z axes
+ /// - `spacing`: defines the distance between cells along the x, y and z axes
+ /// - `color`: color of the grid
+ ///
+ /// # Builder methods
+ ///
+ /// - The skew of the grid can be adjusted using the `.skew(...)`, `.skew_x(...)`, `.skew_y(...)` or `.skew_z(...)` methods. They behave very similar to their CSS equivalents.
+ /// - All outer edges can be toggled on or off using `.outer_edges(...)`. Alternatively you can use `.outer_edges_x(...)`, `.outer_edges_y(...)` or `.outer_edges_z(...)` to toggle the outer edges along an axis.
+ ///
+ /// # 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.grid_3d(
+ /// Vec3::ZERO,
+ /// Quat::IDENTITY,
+ /// UVec3::new(10, 2, 10),
+ /// Vec3::splat(2.),
+ /// GREEN
+ /// )
+ /// .skew_x(0.25)
+ /// .outer_edges();
+ /// }
+ /// # bevy_ecs::system::assert_is_system(system);
+ /// ```
+ pub fn grid_3d(
+ &mut self,
+ position: Vec3,
+ rotation: Quat,
+ cell_count: UVec3,
+ spacing: Vec3,
+ color: impl Into,
+ ) -> GridBuilder3d<'_, 'w, 's, T> {
+ GridBuilder3d {
+ gizmos: self,
+ position,
+ rotation,
+ spacing,
+ cell_count,
+ skew: Vec3::ZERO,
+ outer_edges: [false, false, false],
color: color.into(),
}
}
@@ -175,7 +277,7 @@ impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> {
/// # Builder methods
///
/// - The skew of the grid can be adjusted using the `.skew(...)`, `.skew_x(...)` or `.skew_y(...)` methods. They behave very similar to their CSS equivalents.
- /// - The outer edges can be toggled on or off using `.outer_edges(...)`.
+ /// - All outer edges can be toggled on or off using `.outer_edges(...)`. Alternatively you can use `.outer_edges_x(...)` or `.outer_edges_y(...)` to toggle the outer edges along an axis.
///
/// # Example
/// ```
@@ -192,7 +294,7 @@ impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> {
/// GREEN
/// )
/// .skew_x(0.25)
- /// .outer_edges(true);
+ /// .outer_edges();
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
@@ -203,16 +305,107 @@ impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> {
cell_count: UVec2,
spacing: Vec2,
color: impl Into,
- ) -> GridBuilder<'_, 'w, 's, T> {
- GridBuilder {
+ ) -> GridBuilder2d<'_, 'w, 's, T> {
+ GridBuilder2d {
gizmos: self,
position: position.extend(0.),
rotation: Quat::from_rotation_z(rotation),
spacing,
cell_count,
skew: Vec2::ZERO,
- outer_edges: false,
+ outer_edges: [false, false],
color: color.into(),
}
}
}
+
+#[allow(clippy::too_many_arguments)]
+fn draw_grid(
+ gizmos: &mut Gizmos<'_, '_, T>,
+ position: Vec3,
+ rotation: Quat,
+ spacing: Vec3,
+ cell_count: UVec3,
+ skew: Vec3,
+ outer_edges: [bool; 3],
+ color: LinearRgba,
+) {
+ if !gizmos.enabled {
+ return;
+ }
+
+ // Offset between two adjacent grid cells along the x/y-axis and accounting for skew.
+ let dx = spacing.x
+ * Vec3::new(1., skew.y.tan(), skew.z.tan())
+ * if cell_count.x != 0 { 1. } else { 0. };
+ let dy = spacing.y
+ * Vec3::new(skew.x.tan(), 1., skew.z.tan())
+ * if cell_count.y != 0 { 1. } else { 0. };
+ let dz = spacing.z
+ * Vec3::new(skew.x.tan(), skew.y.tan(), 1.)
+ * if cell_count.z != 0 { 1. } else { 0. };
+
+ // Bottom-left-front corner of the grid
+ let grid_start = position
+ - cell_count.x as f32 / 2.0 * dx
+ - cell_count.y as f32 / 2.0 * dy
+ - cell_count.z as f32 / 2.0 * dz;
+
+ let line_count = UVec3::new(
+ if outer_edges[0] {
+ cell_count.x + 1
+ } else {
+ cell_count.x.saturating_sub(1)
+ },
+ if outer_edges[1] {
+ cell_count.y + 1
+ } else {
+ cell_count.y.saturating_sub(1)
+ },
+ if outer_edges[2] {
+ cell_count.z + 1
+ } else {
+ cell_count.z.saturating_sub(1)
+ },
+ );
+ let x_start = grid_start + if outer_edges[0] { Vec3::ZERO } else { dy + dz };
+ let y_start = grid_start + if outer_edges[1] { Vec3::ZERO } else { dx + dz };
+ let z_start = grid_start + if outer_edges[2] { Vec3::ZERO } else { dx + dy };
+
+ // Lines along the x direction
+ let dline = dx * cell_count.x as f32;
+ for iy in 0..line_count.y {
+ let iy = iy as f32;
+ for iz in 0..line_count.z {
+ let iz = iz as f32;
+ let line_start = x_start + iy * dy + iz * dz;
+ let line_end = line_start + dline;
+
+ gizmos.line(rotation * line_start, rotation * line_end, color);
+ }
+ }
+ // Lines along the y direction
+ let dline = dy * cell_count.y as f32;
+ for ix in 0..line_count.x {
+ let ix = ix as f32;
+ for iz in 0..line_count.z {
+ let iz = iz as f32;
+ let line_start = y_start + ix * dx + iz * dz;
+ let line_end = line_start + dline;
+
+ gizmos.line(rotation * line_start, rotation * line_end, color);
+ }
+ }
+ // Lines along the z direction
+ let dline = dz * cell_count.z as f32;
+ for ix in 0..line_count.x {
+ let ix = ix as f32;
+ for iy in 0..line_count.y {
+ let iy = iy as f32;
+ let line_start = z_start + ix * dx + iy * dy;
+ let line_end = line_start + dline;
+
+ gizmos.line(rotation * line_start, rotation * line_end, color);
+ }
+ }
+}
diff --git a/examples/gizmos/2d_gizmos.rs b/examples/gizmos/2d_gizmos.rs
index ccd4b1171f699..c4aa2c03838ff 100644
--- a/examples/gizmos/2d_gizmos.rs
+++ b/examples/gizmos/2d_gizmos.rs
@@ -51,7 +51,7 @@ fn draw_example_collection(
// Light gray
LinearRgba::gray(0.65),
)
- .outer_edges(true);
+ .outer_edges();
// Triangle
gizmos.linestrip_gradient_2d([