bevy_gizmos/grid.rs
1//! Additional [`GizmoBuffer`] Functions -- Grids
2//!
3//! Includes the implementation of [`GizmoBuffer::grid`] and [`GizmoBuffer::grid_2d`].
4//! and assorted support items.
5
6use crate::{gizmos::GizmoBuffer, prelude::GizmoConfigGroup};
7use bevy_color::Color;
8use bevy_math::{ops, Isometry2d, Isometry3d, Quat, UVec2, UVec3, Vec2, Vec3};
9
10/// A builder returned by [`GizmoBuffer::grid_3d`]
11pub struct GridBuilder3d<'a, Config, Clear>
12where
13 Config: GizmoConfigGroup,
14 Clear: 'static + Send + Sync,
15{
16 gizmos: &'a mut GizmoBuffer<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 [`GizmoBuffer::grid`] and [`GizmoBuffer::grid_2d`]
25pub struct GridBuilder2d<'a, Config, Clear>
26where
27 Config: GizmoConfigGroup,
28 Clear: 'static + Send + Sync,
29{
30 gizmos: &'a mut GizmoBuffer<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 parallel to 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 parallel to 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 parallel to 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 parallel to 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 parallel to 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 [`GizmoBuffer`]
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}
175
176impl<Config, Clear> GizmoBuffer<Config, Clear>
177where
178 Config: GizmoConfigGroup,
179 Clear: 'static + Send + Sync,
180{
181 /// Draw a 2D grid in 3D.
182 ///
183 /// The grid's default orientation aligns with the XY-plane.
184 ///
185 /// # Arguments
186 ///
187 /// - `isometry` defines the translation and rotation of the grid.
188 /// - the translation specifies the center of the grid
189 /// - defines the orientation of the grid, by default we assume the grid is contained in a
190 /// plane parallel to the XY plane
191 /// - `cell_count`: defines the amount of cells in the x and y axes
192 /// - `spacing`: defines the distance between cells along the x and y axes
193 /// - `color`: color of the grid
194 ///
195 /// # Builder methods
196 ///
197 /// - 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.
198 /// - 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.
199 ///
200 /// # Example
201 /// ```
202 /// # use bevy_gizmos::prelude::*;
203 /// # use bevy_math::prelude::*;
204 /// # use bevy_color::palettes::basic::GREEN;
205 /// fn system(mut gizmos: Gizmos) {
206 /// gizmos.grid(
207 /// Isometry3d::IDENTITY,
208 /// UVec2::new(10, 10),
209 /// Vec2::splat(2.),
210 /// GREEN
211 /// )
212 /// .skew_x(0.25)
213 /// .outer_edges();
214 /// }
215 /// # bevy_ecs::system::assert_is_system(system);
216 /// ```
217 pub fn grid(
218 &mut self,
219 isometry: impl Into<Isometry3d>,
220 cell_count: UVec2,
221 spacing: Vec2,
222 color: impl Into<Color>,
223 ) -> GridBuilder2d<'_, Config, Clear> {
224 GridBuilder2d {
225 gizmos: self,
226 isometry: isometry.into(),
227 spacing,
228 cell_count,
229 skew: Vec2::ZERO,
230 outer_edges: [false, false],
231 color: color.into(),
232 }
233 }
234
235 /// Draw a 3D grid of voxel-like cells.
236 ///
237 /// # Arguments
238 ///
239 /// - `isometry` defines the translation and rotation of the grid.
240 /// - the translation specifies the center of the grid
241 /// - defines the orientation of the grid, by default we assume the grid is aligned with all axes
242 /// - `cell_count`: defines the amount of cells in the x, y and z axes
243 /// - `spacing`: defines the distance between cells along the x, y and z axes
244 /// - `color`: color of the grid
245 ///
246 /// # Builder methods
247 ///
248 /// - 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.
249 /// - 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.
250 ///
251 /// # Example
252 /// ```
253 /// # use bevy_gizmos::prelude::*;
254 /// # use bevy_math::prelude::*;
255 /// # use bevy_color::palettes::basic::GREEN;
256 /// fn system(mut gizmos: Gizmos) {
257 /// gizmos.grid_3d(
258 /// Isometry3d::IDENTITY,
259 /// UVec3::new(10, 2, 10),
260 /// Vec3::splat(2.),
261 /// GREEN
262 /// )
263 /// .skew_x(0.25)
264 /// .outer_edges();
265 /// }
266 /// # bevy_ecs::system::assert_is_system(system);
267 /// ```
268 pub fn grid_3d(
269 &mut self,
270 isometry: impl Into<Isometry3d>,
271 cell_count: UVec3,
272 spacing: Vec3,
273 color: impl Into<Color>,
274 ) -> GridBuilder3d<'_, Config, Clear> {
275 GridBuilder3d {
276 gizmos: self,
277 isometry: isometry.into(),
278 spacing,
279 cell_count,
280 skew: Vec3::ZERO,
281 outer_edges: [false, false, false],
282 color: color.into(),
283 }
284 }
285
286 /// Draw a grid in 2D.
287 ///
288 /// # Arguments
289 ///
290 /// - `isometry` defines the translation and rotation of the grid.
291 /// - the translation specifies the center of the grid
292 /// - defines the orientation of the grid, by default we assume the grid is aligned with all axes
293 /// - `cell_count`: defines the amount of cells in the x and y axes
294 /// - `spacing`: defines the distance between cells along the x and y axes
295 /// - `color`: color of the grid
296 ///
297 /// # Builder methods
298 ///
299 /// - 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.
300 /// - 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.
301 ///
302 /// # Example
303 /// ```
304 /// # use bevy_gizmos::prelude::*;
305 /// # use bevy_math::prelude::*;
306 /// # use bevy_color::palettes::basic::GREEN;
307 /// fn system(mut gizmos: Gizmos) {
308 /// gizmos.grid_2d(
309 /// Isometry2d::IDENTITY,
310 /// UVec2::new(10, 10),
311 /// Vec2::splat(1.),
312 /// GREEN
313 /// )
314 /// .skew_x(0.25)
315 /// .outer_edges();
316 /// }
317 /// # bevy_ecs::system::assert_is_system(system);
318 /// ```
319 pub fn grid_2d(
320 &mut self,
321 isometry: impl Into<Isometry2d>,
322 cell_count: UVec2,
323 spacing: Vec2,
324 color: impl Into<Color>,
325 ) -> GridBuilder2d<'_, Config, Clear> {
326 let isometry = isometry.into();
327 GridBuilder2d {
328 gizmos: self,
329 isometry: Isometry3d::new(
330 isometry.translation.extend(0.0),
331 Quat::from_rotation_z(isometry.rotation.as_radians()),
332 ),
333 spacing,
334 cell_count,
335 skew: Vec2::ZERO,
336 outer_edges: [false, false],
337 color: color.into(),
338 }
339 }
340}
341
342fn draw_grid<Config, Clear>(
343 gizmos: &mut GizmoBuffer<Config, Clear>,
344 isometry: Isometry3d,
345 spacing: Vec3,
346 cell_count: UVec3,
347 skew: Vec3,
348 outer_edges: [bool; 3],
349 color: Color,
350) where
351 Config: GizmoConfigGroup,
352 Clear: 'static + Send + Sync,
353{
354 if !gizmos.enabled {
355 return;
356 }
357
358 #[inline]
359 fn or_zero(cond: bool, val: Vec3) -> Vec3 {
360 if cond {
361 val
362 } else {
363 Vec3::ZERO
364 }
365 }
366
367 // Offset between two adjacent grid cells along the x/y-axis and accounting for skew.
368 let skew_tan = Vec3::from(skew.to_array().map(ops::tan));
369 let dx = or_zero(
370 cell_count.x != 0,
371 spacing.x * Vec3::new(1., skew_tan.y, skew_tan.z),
372 );
373 let dy = or_zero(
374 cell_count.y != 0,
375 spacing.y * Vec3::new(skew_tan.x, 1., skew_tan.z),
376 );
377 let dz = or_zero(
378 cell_count.z != 0,
379 spacing.z * Vec3::new(skew_tan.x, skew_tan.y, 1.),
380 );
381
382 // Bottom-left-front corner of the grid
383 let cell_count_half = cell_count.as_vec3() * 0.5;
384 let grid_start = -cell_count_half.x * dx - cell_count_half.y * dy - cell_count_half.z * dz;
385
386 #[inline]
387 fn cell_count_to_line_count(include_outer: bool, cell_count: u32) -> u32 {
388 if include_outer {
389 cell_count.saturating_add(1)
390 } else {
391 cell_count.saturating_sub(1).max(1)
392 }
393 }
394
395 let x_line_count = UVec2::new(
396 cell_count_to_line_count(outer_edges[0], cell_count.y),
397 cell_count_to_line_count(outer_edges[0], cell_count.z),
398 );
399 let y_line_count = UVec2::new(
400 cell_count_to_line_count(outer_edges[1], cell_count.z),
401 cell_count_to_line_count(outer_edges[1], cell_count.x),
402 );
403 let z_line_count = UVec2::new(
404 cell_count_to_line_count(outer_edges[2], cell_count.x),
405 cell_count_to_line_count(outer_edges[2], cell_count.y),
406 );
407
408 let x_start = grid_start + or_zero(!outer_edges[0], dy + dz);
409 let y_start = grid_start + or_zero(!outer_edges[1], dx + dz);
410 let z_start = grid_start + or_zero(!outer_edges[2], dx + dy);
411
412 fn iter_lines(
413 delta_a: Vec3,
414 delta_b: Vec3,
415 delta_c: Vec3,
416 line_count: UVec2,
417 cell_count: u32,
418 start: Vec3,
419 ) -> impl Iterator<Item = [Vec3; 2]> {
420 let dline = delta_a * cell_count as f32;
421 (0..line_count.x).map(|v| v as f32).flat_map(move |b| {
422 (0..line_count.y).map(|v| v as f32).map(move |c| {
423 let line_start = start + b * delta_b + c * delta_c;
424 let line_end = line_start + dline;
425 [line_start, line_end]
426 })
427 })
428 }
429
430 // Lines along the x direction
431 let x_lines = iter_lines(dx, dy, dz, x_line_count, cell_count.x, x_start);
432 // Lines along the y direction
433 let y_lines = iter_lines(dy, dz, dx, y_line_count, cell_count.y, y_start);
434 // Lines along the z direction
435 let z_lines = iter_lines(dz, dx, dy, z_line_count, cell_count.z, z_start);
436
437 x_lines
438 .chain(y_lines)
439 .chain(z_lines)
440 .map(|vec3s| vec3s.map(|vec3| isometry * vec3))
441 .for_each(|[start, end]| {
442 gizmos.line(start, end, color);
443 });
444}