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