Skip to content

Commit

Permalink
Drawing Primitives with Gizmos (bevyengine#11072)
Browse files Browse the repository at this point in the history
The PR is in a reviewable state now in the sense that the basic
implementations are there. There are still some ToDos that I'm aware of:

- [x] docs for all the new structs and traits
- [x] implement `Default` and derive other useful traits for the new
structs
- [x] Take a look at the notes again (Do this after a first round of
reviews)
- [x] Take care of the repetition in the circle drawing functions

---

# Objective

- TLDR: This PR enables us to quickly draw all the newly added
primitives from `bevy_math` in immediate mode with gizmos
- Addresses bevyengine#10571

## Solution

- This implements the first design idea I had that covered everything
that was mentioned in the Issue
bevyengine#10571 (comment)

--- 

## Caveats

- I added the `Primitive(2/3)d` impls for `Direction(2/3)d` to make them
work with the current solution. We could impose less strict requirements
for the gizmoable objects and remove the impls afterwards if the
community doesn't like the current approach.

---

## Changelog

- implement capabilities to draw ellipses on the gizmo in general (this
was required to have some code which is able to draw the ellipse
primitive)
- refactored circle drawing code to use the more general ellipse drawing
code to keep code duplication low
- implement `Primitive2d` for `Direction2d` and impl `Primitive3d` for
`Direction3d`
- implement trait to draw primitives with specialized details with
gizmos
  - `GizmoPrimitive2d` for all the 2D primitives
  - `GizmoPrimitive3d` for all the 3D primitives
- (question while writing this: Does it actually matter if we split this
in 2D and 3D? I guess it could be useful in the future if we do
something based on the main rendering mode even though atm it's kinda
useless)

---

---------

Co-authored-by: nothendev <borodinov.ilya@gmail.com>
  • Loading branch information
2 people authored and tjamaan committed Feb 6, 2024
1 parent cd51482 commit f3caf07
Show file tree
Hide file tree
Showing 12 changed files with 2,104 additions and 43 deletions.
6 changes: 3 additions & 3 deletions crates/bevy_gizmos/src/arcs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> {
///
/// # Builder methods
/// The number of segments of the arc (i.e. the level of detail) can be adjusted with the
/// `.segements(...)` method.
/// `.segments(...)` method.
///
/// # Example
/// ```
Expand Down Expand Up @@ -190,7 +190,7 @@ impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> {
///
/// # Builder methods
/// The number of segments of the arc (i.e. the level of detail) can be adjusted with the
/// `.segements(...)` method.
/// `.segments(...)` method.
///
/// # Examples
/// ```
Expand Down Expand Up @@ -236,7 +236,7 @@ impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> {
///
/// # Builder methods
/// The number of segments of the arc (i.e. the level of detail) can be adjusted with the
/// `.segements(...)` method.
/// `.segments(...)` method.
///
/// # Examples
/// ```
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_gizmos/src/arrows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ impl<T: GizmoConfigGroup> Drop for ArrowBuilder<'_, '_, '_, T> {
}

impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> {
/// Draw an arrow in 3D, from `start` to `end`. Has four tips for convienent viewing from any direction.
/// Draw an arrow in 3D, from `start` to `end`. Has four tips for convenient viewing from any direction.
///
/// This should be called for each frame the arrow needs to be rendered.
///
Expand Down
138 changes: 111 additions & 27 deletions crates/bevy_gizmos/src/circles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,98 @@
//! and assorted support items.

use crate::prelude::{GizmoConfigGroup, Gizmos};
use bevy_math::Mat2;
use bevy_math::{primitives::Direction3d, Quat, Vec2, Vec3};
use bevy_render::color::Color;
use std::f32::consts::TAU;

pub(crate) const DEFAULT_CIRCLE_SEGMENTS: usize = 32;

fn circle_inner(radius: f32, segments: usize) -> impl Iterator<Item = Vec2> {
fn ellipse_inner(half_size: Vec2, segments: usize) -> impl Iterator<Item = Vec2> {
(0..segments + 1).map(move |i| {
let angle = i as f32 * TAU / segments as f32;
Vec2::from(angle.sin_cos()) * radius
let (x, y) = angle.sin_cos();
Vec2::new(x, y) * half_size
})
}

impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> {
/// Draw an ellipse in 3D at `position` with the flat side facing `normal`.
///
/// This should be called for each frame the ellipse needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_render::prelude::*;
/// # use bevy_math::prelude::*;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.ellipse(Vec3::ZERO, Quat::IDENTITY, Vec2::new(1., 2.), Color::GREEN);
///
/// // Ellipses have 32 line-segments by default.
/// // You may want to increase this for larger ellipses.
/// gizmos
/// .ellipse(Vec3::ZERO, Quat::IDENTITY, Vec2::new(5., 1.), Color::RED)
/// .segments(64);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn ellipse(
&mut self,
position: Vec3,
rotation: Quat,
half_size: Vec2,
color: Color,
) -> EllipseBuilder<'_, 'w, 's, T> {
EllipseBuilder {
gizmos: self,
position,
rotation,
half_size,
color,
segments: DEFAULT_CIRCLE_SEGMENTS,
}
}

/// Draw an ellipse in 2D.
///
/// This should be called for each frame the ellipse needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_render::prelude::*;
/// # use bevy_math::prelude::*;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.ellipse_2d(Vec2::ZERO, 180.0_f32.to_radians(), Vec2::new(2., 1.), Color::GREEN);
///
/// // Ellipses have 32 line-segments by default.
/// // You may want to increase this for larger ellipses.
/// gizmos
/// .ellipse_2d(Vec2::ZERO, 180.0_f32.to_radians(), Vec2::new(5., 1.), Color::RED)
/// .segments(64);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn ellipse_2d(
&mut self,
position: Vec2,
angle: f32,
half_size: Vec2,
color: Color,
) -> Ellipse2dBuilder<'_, 'w, 's, T> {
Ellipse2dBuilder {
gizmos: self,
position,
rotation: Mat2::from_angle(angle),
half_size,
color,
segments: DEFAULT_CIRCLE_SEGMENTS,
}
}

/// Draw a circle in 3D at `position` with the flat side facing `normal`.
///
/// This should be called for each frame the circle needs to be rendered.
Expand Down Expand Up @@ -45,12 +123,12 @@ impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> {
normal: Direction3d,
radius: f32,
color: Color,
) -> CircleBuilder<'_, 'w, 's, T> {
CircleBuilder {
) -> EllipseBuilder<'_, 'w, 's, T> {
EllipseBuilder {
gizmos: self,
position,
normal,
radius,
rotation: Quat::from_rotation_arc(Vec3::Z, *normal),
half_size: Vec2::splat(radius),
color,
segments: DEFAULT_CIRCLE_SEGMENTS,
}
Expand Down Expand Up @@ -82,70 +160,76 @@ impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> {
position: Vec2,
radius: f32,
color: Color,
) -> Circle2dBuilder<'_, 'w, 's, T> {
Circle2dBuilder {
) -> Ellipse2dBuilder<'_, 'w, 's, T> {
Ellipse2dBuilder {
gizmos: self,
position,
radius,
rotation: Mat2::IDENTITY,
half_size: Vec2::splat(radius),
color,
segments: DEFAULT_CIRCLE_SEGMENTS,
}
}
}

/// A builder returned by [`Gizmos::circle`].
pub struct CircleBuilder<'a, 'w, 's, T: GizmoConfigGroup> {
/// A builder returned by [`Gizmos::ellipse`].
pub struct EllipseBuilder<'a, 'w, 's, T: GizmoConfigGroup> {
gizmos: &'a mut Gizmos<'w, 's, T>,
position: Vec3,
normal: Direction3d,
radius: f32,
rotation: Quat,
half_size: Vec2,
color: Color,
segments: usize,
}

impl<T: GizmoConfigGroup> CircleBuilder<'_, '_, '_, T> {
/// Set the number of line-segments for this circle.
impl<T: GizmoConfigGroup> EllipseBuilder<'_, '_, '_, T> {
/// Set the number of line-segments for this ellipse.
pub fn segments(mut self, segments: usize) -> Self {
self.segments = segments;
self
}
}

impl<T: GizmoConfigGroup> Drop for CircleBuilder<'_, '_, '_, T> {
impl<T: GizmoConfigGroup> Drop for EllipseBuilder<'_, '_, '_, T> {
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
}
let rotation = Quat::from_rotation_arc(Vec3::Z, *self.normal);
let positions = circle_inner(self.radius, self.segments)
.map(|vec2| self.position + rotation * vec2.extend(0.));

let positions = ellipse_inner(self.half_size, self.segments)
.map(|vec2| self.rotation * vec2.extend(0.))
.map(|vec3| vec3 + self.position);
self.gizmos.linestrip(positions, self.color);
}
}

/// A builder returned by [`Gizmos::circle_2d`].
pub struct Circle2dBuilder<'a, 'w, 's, T: GizmoConfigGroup> {
/// A builder returned by [`Gizmos::ellipse_2d`].
pub struct Ellipse2dBuilder<'a, 'w, 's, T: GizmoConfigGroup> {
gizmos: &'a mut Gizmos<'w, 's, T>,
position: Vec2,
radius: f32,
rotation: Mat2,
half_size: Vec2,
color: Color,
segments: usize,
}

impl<T: GizmoConfigGroup> Circle2dBuilder<'_, '_, '_, T> {
/// Set the number of line-segments for this circle.
impl<T: GizmoConfigGroup> Ellipse2dBuilder<'_, '_, '_, T> {
/// Set the number of line-segments for this ellipse.
pub fn segments(mut self, segments: usize) -> Self {
self.segments = segments;
self
}
}

impl<T: GizmoConfigGroup> Drop for Circle2dBuilder<'_, '_, '_, T> {
impl<T: GizmoConfigGroup> Drop for Ellipse2dBuilder<'_, '_, '_, T> {
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
}
let positions = circle_inner(self.radius, self.segments).map(|vec2| vec2 + self.position);
};

let positions = ellipse_inner(self.half_size, self.segments)
.map(|vec2| self.rotation * vec2)
.map(|vec2| vec2 + self.position);
self.gizmos.linestrip_2d(positions, self.color);
}
}
2 changes: 2 additions & 0 deletions crates/bevy_gizmos/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub mod arrows;
pub mod circles;
pub mod config;
pub mod gizmos;
pub mod primitives;

#[cfg(feature = "bevy_sprite")]
mod pipeline_2d;
Expand All @@ -45,6 +46,7 @@ pub mod prelude {
aabb::{AabbGizmoConfigGroup, ShowAabbGizmo},
config::{DefaultGizmoConfigGroup, GizmoConfig, GizmoConfigGroup, GizmoConfigStore},
gizmos::Gizmos,
primitives::{dim2::GizmoPrimitive2d, dim3::GizmoPrimitive3d},
AppGizmoBuilder,
};
}
Expand Down
Loading

0 comments on commit f3caf07

Please sign in to comment.