bevy/crates/bevy_gizmos/src/circles.rs
Robert Walter 041731b7e0
Drawing Primitives with Gizmos (#11072)
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 #10571

## Solution

- This implements the first design idea I had that covered everything
that was mentioned in the Issue
https://github.com/bevyengine/bevy/issues/10571#issuecomment-1863646197

--- 

## 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>
2024-02-02 21:13:03 +00:00

235 lines
7 KiB
Rust

//! Additional [`Gizmos`] Functions -- Circles
//!
//! Includes the implementation of [`Gizmos::circle`] and [`Gizmos::circle_2d`],
//! 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 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;
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.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_render::prelude::*;
/// # use bevy_math::prelude::*;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.circle(Vec3::ZERO, Direction3d::Z, 1., Color::GREEN);
///
/// // Circles have 32 line-segments by default.
/// // You may want to increase this for larger circles.
/// gizmos
/// .circle(Vec3::ZERO, Direction3d::Z, 5., Color::RED)
/// .segments(64);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn circle(
&mut self,
position: Vec3,
normal: Direction3d,
radius: f32,
color: Color,
) -> EllipseBuilder<'_, 'w, 's, T> {
EllipseBuilder {
gizmos: self,
position,
rotation: Quat::from_rotation_arc(Vec3::Z, *normal),
half_size: Vec2::splat(radius),
color,
segments: DEFAULT_CIRCLE_SEGMENTS,
}
}
/// Draw a circle in 2D.
///
/// This should be called for each frame the circle needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_render::prelude::*;
/// # use bevy_math::prelude::*;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.circle_2d(Vec2::ZERO, 1., Color::GREEN);
///
/// // Circles have 32 line-segments by default.
/// // You may want to increase this for larger circles.
/// gizmos
/// .circle_2d(Vec2::ZERO, 5., Color::RED)
/// .segments(64);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn circle_2d(
&mut self,
position: Vec2,
radius: f32,
color: Color,
) -> Ellipse2dBuilder<'_, 'w, 's, T> {
Ellipse2dBuilder {
gizmos: self,
position,
rotation: Mat2::IDENTITY,
half_size: Vec2::splat(radius),
color,
segments: DEFAULT_CIRCLE_SEGMENTS,
}
}
}
/// A builder returned by [`Gizmos::ellipse`].
pub struct EllipseBuilder<'a, 'w, 's, T: GizmoConfigGroup> {
gizmos: &'a mut Gizmos<'w, 's, T>,
position: Vec3,
rotation: Quat,
half_size: Vec2,
color: Color,
segments: usize,
}
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 EllipseBuilder<'_, '_, '_, T> {
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
}
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::ellipse_2d`].
pub struct Ellipse2dBuilder<'a, 'w, 's, T: GizmoConfigGroup> {
gizmos: &'a mut Gizmos<'w, 's, T>,
position: Vec2,
rotation: Mat2,
half_size: Vec2,
color: Color,
segments: usize,
}
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 Ellipse2dBuilder<'_, '_, '_, T> {
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
};
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);
}
}