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