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([