bevy_gizmos/grid.rs
1//! Additional [`Gizmos`] Functions -- Grids
2//!
3//! Includes the implementation of [`Gizmos::grid`] and [`Gizmos::grid_2d`].
4//! and assorted support items.
5
6use crate::prelude::{GizmoConfigGroup, Gizmos};
7use bevy_color::Color;
8use bevy_math::{ops, Isometry2d, Isometry3d, Quat, UVec2, UVec3, Vec2, Vec3, Vec3Swizzles};
9
10/// A builder returned by [`Gizmos::grid_3d`]
11pub struct GridBuilder3d<'a, 'w, 's, Config, Clear>
12where
13 Config: GizmoConfigGroup,
14 Clear: 'static + Send + Sync,
15{
16 gizmos: &'a mut Gizmos<'w, 's, Config, Clear>,
17 isometry: Isometry3d,
18 spacing: Vec3,
19 cell_count: UVec3,
20 skew: Vec3,
21 outer_edges: [bool; 3],
22 color: Color,
23}
24/// A builder returned by [`Gizmos::grid`] and [`Gizmos::grid_2d`]
25pub struct GridBuilder2d<'a, 'w, 's, Config, Clear>
26where
27 Config: GizmoConfigGroup,
28 Clear: 'static + Send + Sync,
29{
30 gizmos: &'a mut Gizmos<'w, 's, Config, Clear>,
31 isometry: Isometry3d,
32 spacing: Vec2,
33 cell_count: UVec2,
34 skew: Vec2,
35 outer_edges: [bool; 2],
36 color: Color,
37}
38
39impl<Config, Clear> GridBuilder3d<'_, '_, '_, Config, Clear>
40where
41 Config: GizmoConfigGroup,
42 Clear: 'static + Send + Sync,
43{
44 /// Skews the grid by `tan(skew)` in the x direction.
45 /// `skew` is in radians
46 pub fn skew_x(mut self, skew: f32) -> Self {
47 self.skew.x = skew;
48 self
49 }
50 /// Skews the grid by `tan(skew)` in the y direction.
51 /// `skew` is in radians
52 pub fn skew_y(mut self, skew: f32) -> Self {
53 self.skew.y = skew;
54 self
55 }
56 /// Skews the grid by `tan(skew)` in the z direction.
57 /// `skew` is in radians
58 pub fn skew_z(mut self, skew: f32) -> Self {
59 self.skew.z = skew;
60 self
61 }
62 /// Skews the grid by `tan(skew)` in the x, y and z directions.
63 /// `skew` is in radians
64 pub fn skew(mut self, skew: Vec3) -> Self {
65 self.skew = skew;
66 self
67 }
68
69 /// Declare that the outer edges of the grid along the x axis should be drawn.
70 /// By default, the outer edges will not be drawn.
71 pub fn outer_edges_x(mut self) -> Self {
72 self.outer_edges[0] = true;
73 self
74 }
75 /// Declare that the outer edges of the grid along the y axis should be drawn.
76 /// By default, the outer edges will not be drawn.
77 pub fn outer_edges_y(mut self) -> Self {
78 self.outer_edges[1] = true;
79 self
80 }
81 /// Declare that the outer edges of the grid along the z axis should be drawn.
82 /// By default, the outer edges will not be drawn.
83 pub fn outer_edges_z(mut self) -> Self {
84 self.outer_edges[2] = true;
85 self
86 }
87 /// Declare that all outer edges of the grid should be drawn.
88 /// By default, the outer edges will not be drawn.
89 pub fn outer_edges(mut self) -> Self {
90 self.outer_edges.fill(true);
91 self
92 }
93}
94
95impl<Config, Clear> GridBuilder2d<'_, '_, '_, Config, Clear>
96where
97 Config: GizmoConfigGroup,
98 Clear: 'static + Send + Sync,
99{
100 /// Skews the grid by `tan(skew)` in the x direction.
101 /// `skew` is in radians
102 pub fn skew_x(mut self, skew: f32) -> Self {
103 self.skew.x = skew;
104 self
105 }
106 /// Skews the grid by `tan(skew)` in the y direction.
107 /// `skew` is in radians
108 pub fn skew_y(mut self, skew: f32) -> Self {
109 self.skew.y = skew;
110 self
111 }
112 /// Skews the grid by `tan(skew)` in the x and y directions.
113 /// `skew` is in radians
114 pub fn skew(mut self, skew: Vec2) -> Self {
115 self.skew = skew;
116 self
117 }
118
119 /// Declare that the outer edges of the grid along the x axis should be drawn.
120 /// By default, the outer edges will not be drawn.
121 pub fn outer_edges_x(mut self) -> Self {
122 self.outer_edges[0] = true;
123 self
124 }
125 /// Declare that the outer edges of the grid along the y axis should be drawn.
126 /// By default, the outer edges will not be drawn.
127 pub fn outer_edges_y(mut self) -> Self {
128 self.outer_edges[1] = true;
129 self
130 }
131 /// Declare that all outer edges of the grid should be drawn.
132 /// By default, the outer edges will not be drawn.
133 pub fn outer_edges(mut self) -> Self {
134 self.outer_edges.fill(true);
135 self
136 }
137}
138
139impl<Config, Clear> Drop for GridBuilder3d<'_, '_, '_, Config, Clear>
140where
141 Config: GizmoConfigGroup,
142 Clear: 'static + Send + Sync,
143{
144 /// Draws a grid, by drawing lines with the stored [`Gizmos`]
145 fn drop(&mut self) {
146 draw_grid(
147 self.gizmos,
148 self.isometry,
149 self.spacing,
150 self.cell_count,
151 self.skew,
152 self.outer_edges,
153 self.color,
154 );
155 }
156}
157
158impl<Config, Clear> Drop for GridBuilder2d<'_, '_, '_, Config, Clear>
159where
160 Config: GizmoConfigGroup,
161 Clear: 'static + Send + Sync,
162{
163 fn drop(&mut self) {
164 draw_grid(
165 self.gizmos,
166 self.isometry,
167 self.spacing.extend(0.),
168 self.cell_count.extend(0),
169 self.skew.extend(0.),
170 [self.outer_edges[0], self.outer_edges[1], true],
171 self.color,
172 );
173 }
174}
175impl<'w, 's, Config, Clear> Gizmos<'w, 's, Config, Clear>
176where
177 Config: GizmoConfigGroup,
178 Clear: 'static + Send + Sync,
179{
180 /// Draw a 2D grid in 3D.
181 ///
182 /// This should be called for each frame the grid needs to be rendered.
183 ///
184 /// The grid's default orientation aligns with the XY-plane.
185 ///
186 /// # Arguments
187 ///
188 /// - `isometry` defines the translation and rotation of the grid.
189 /// - the translation specifies the center of the grid
190 /// - defines the orientation of the grid, by default
191 /// we assume the grid is contained in a plane parallel
192 /// to the XY plane
193 /// - `cell_count`: defines the amount of cells in the x and y axes
194 /// - `spacing`: defines the distance between cells along the x and y axes
195 /// - `color`: color of the grid
196 ///
197 /// # Builder methods
198 ///
199 /// - 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.
200 /// - 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.
201 ///
202 /// # Example
203 /// ```
204 /// # use bevy_gizmos::prelude::*;
205 /// # use bevy_math::prelude::*;
206 /// # use bevy_color::palettes::basic::GREEN;
207 /// fn system(mut gizmos: Gizmos) {
208 /// gizmos.grid(
209 /// Isometry3d::IDENTITY,
210 /// UVec2::new(10, 10),
211 /// Vec2::splat(2.),
212 /// GREEN
213 /// )
214 /// .skew_x(0.25)
215 /// .outer_edges();
216 /// }
217 /// # bevy_ecs::system::assert_is_system(system);
218 /// ```
219 pub fn grid(
220 &mut self,
221 isometry: impl Into<Isometry3d>,
222 cell_count: UVec2,
223 spacing: Vec2,
224 color: impl Into<Color>,
225 ) -> GridBuilder2d<'_, 'w, 's, Config, Clear> {
226 GridBuilder2d {
227 gizmos: self,
228 isometry: isometry.into(),
229 spacing,
230 cell_count,
231 skew: Vec2::ZERO,
232 outer_edges: [false, false],
233 color: color.into(),
234 }
235 }
236
237 /// Draw a 3D grid of voxel-like cells.
238 ///
239 /// This should be called for each frame the grid needs to be rendered.
240 ///
241 /// # Arguments
242 ///
243 /// - `isometry` defines the translation and rotation of the grid.
244 /// - the translation specifies the center of the grid
245 /// - defines the orientation of the grid, by default
246 /// we assume the grid is aligned with all axes
247 /// - `cell_count`: defines the amount of cells in the x, y and z axes
248 /// - `spacing`: defines the distance between cells along the x, y and z axes
249 /// - `color`: color of the grid
250 ///
251 /// # Builder methods
252 ///
253 /// - 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.
254 /// - 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.
255 ///
256 /// # Example
257 /// ```
258 /// # use bevy_gizmos::prelude::*;
259 /// # use bevy_math::prelude::*;
260 /// # use bevy_color::palettes::basic::GREEN;
261 /// fn system(mut gizmos: Gizmos) {
262 /// gizmos.grid_3d(
263 /// Isometry3d::IDENTITY,
264 /// UVec3::new(10, 2, 10),
265 /// Vec3::splat(2.),
266 /// GREEN
267 /// )
268 /// .skew_x(0.25)
269 /// .outer_edges();
270 /// }
271 /// # bevy_ecs::system::assert_is_system(system);
272 /// ```
273 pub fn grid_3d(
274 &mut self,
275 isometry: impl Into<Isometry3d>,
276 cell_count: UVec3,
277 spacing: Vec3,
278 color: impl Into<Color>,
279 ) -> GridBuilder3d<'_, 'w, 's, Config, Clear> {
280 GridBuilder3d {
281 gizmos: self,
282 isometry: isometry.into(),
283 spacing,
284 cell_count,
285 skew: Vec3::ZERO,
286 outer_edges: [false, false, false],
287 color: color.into(),
288 }
289 }
290
291 /// Draw a grid in 2D.
292 ///
293 /// This should be called for each frame the grid needs to be rendered.
294 ///
295 /// # Arguments
296 ///
297 /// - `isometry` defines the translation and rotation of the grid.
298 /// - the translation specifies the center of the grid
299 /// - defines the orientation of the grid, by default
300 /// we assume the grid is aligned with all axes
301 /// - `cell_count`: defines the amount of cells in the x and y axes
302 /// - `spacing`: defines the distance between cells along the x and y axes
303 /// - `color`: color of the grid
304 ///
305 /// # Builder methods
306 ///
307 /// - 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.
308 /// - 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.
309 ///
310 /// # Example
311 /// ```
312 /// # use bevy_gizmos::prelude::*;
313 /// # use bevy_math::prelude::*;
314 /// # use bevy_color::palettes::basic::GREEN;
315 /// fn system(mut gizmos: Gizmos) {
316 /// gizmos.grid_2d(
317 /// Isometry2d::IDENTITY,
318 /// UVec2::new(10, 10),
319 /// Vec2::splat(1.),
320 /// GREEN
321 /// )
322 /// .skew_x(0.25)
323 /// .outer_edges();
324 /// }
325 /// # bevy_ecs::system::assert_is_system(system);
326 /// ```
327 pub fn grid_2d(
328 &mut self,
329 isometry: impl Into<Isometry2d>,
330 cell_count: UVec2,
331 spacing: Vec2,
332 color: impl Into<Color>,
333 ) -> GridBuilder2d<'_, 'w, 's, Config, Clear> {
334 let isometry = isometry.into();
335 GridBuilder2d {
336 gizmos: self,
337 isometry: Isometry3d::new(
338 isometry.translation.extend(0.0),
339 Quat::from_rotation_z(isometry.rotation.as_radians()),
340 ),
341 spacing,
342 cell_count,
343 skew: Vec2::ZERO,
344 outer_edges: [false, false],
345 color: color.into(),
346 }
347 }
348}
349
350#[allow(clippy::too_many_arguments)]
351fn draw_grid<Config, Clear>(
352 gizmos: &mut Gizmos<'_, '_, Config, Clear>,
353 isometry: Isometry3d,
354 spacing: Vec3,
355 cell_count: UVec3,
356 skew: Vec3,
357 outer_edges: [bool; 3],
358 color: Color,
359) where
360 Config: GizmoConfigGroup,
361 Clear: 'static + Send + Sync,
362{
363 if !gizmos.enabled {
364 return;
365 }
366
367 #[inline]
368 fn or_zero(cond: bool, val: Vec3) -> Vec3 {
369 if cond {
370 val
371 } else {
372 Vec3::ZERO
373 }
374 }
375
376 // Offset between two adjacent grid cells along the x/y-axis and accounting for skew.
377 let skew_tan = Vec3::from(skew.to_array().map(ops::tan));
378 let dx = or_zero(
379 cell_count.x != 0,
380 spacing.x * Vec3::new(1., skew_tan.y, skew_tan.z),
381 );
382 let dy = or_zero(
383 cell_count.y != 0,
384 spacing.y * Vec3::new(skew_tan.x, 1., skew_tan.z),
385 );
386 let dz = or_zero(
387 cell_count.z != 0,
388 spacing.z * Vec3::new(skew_tan.x, skew_tan.y, 1.),
389 );
390
391 // Bottom-left-front corner of the grid
392 let cell_count_half = cell_count.as_vec3() * 0.5;
393 let grid_start = -cell_count_half.x * dx - cell_count_half.y * dy - cell_count_half.z * dz;
394
395 let outer_edges_u32 = UVec3::from(outer_edges.map(|v| v as u32));
396 let line_count = outer_edges_u32 * cell_count.saturating_add(UVec3::ONE)
397 + (UVec3::ONE - outer_edges_u32) * cell_count.saturating_sub(UVec3::ONE);
398
399 let x_start = grid_start + or_zero(!outer_edges[0], dy + dz);
400 let y_start = grid_start + or_zero(!outer_edges[1], dx + dz);
401 let z_start = grid_start + or_zero(!outer_edges[2], dx + dy);
402
403 fn iter_lines(
404 delta_a: Vec3,
405 delta_b: Vec3,
406 delta_c: Vec3,
407 line_count: UVec2,
408 cell_count: u32,
409 start: Vec3,
410 ) -> impl Iterator<Item = [Vec3; 2]> {
411 let dline = delta_a * cell_count as f32;
412 (0..line_count.x).map(|v| v as f32).flat_map(move |b| {
413 (0..line_count.y).map(|v| v as f32).map(move |c| {
414 let line_start = start + b * delta_b + c * delta_c;
415 let line_end = line_start + dline;
416 [line_start, line_end]
417 })
418 })
419 }
420
421 // Lines along the x direction
422 let x_lines = iter_lines(dx, dy, dz, line_count.yz(), cell_count.x, x_start);
423 // Lines along the y direction
424 let y_lines = iter_lines(dy, dz, dx, line_count.zx(), cell_count.y, y_start);
425 // Lines along the z direction
426 let z_lines = iter_lines(dz, dx, dy, line_count.xy(), cell_count.z, z_start);
427 x_lines
428 .chain(y_lines)
429 .chain(z_lines)
430 .map(|vec3s| vec3s.map(|vec3| isometry * vec3))
431 .for_each(|[start, end]| {
432 gizmos.line(start, end, color);
433 });
434}