bevy/crates/bevy_gizmos/src/circles.rs
Arthur Brussee ffb6faafc2
Use Direction3d for gizmos.circle normal (#11422)
# Objective

Fix weird visuals when drawing a gizmo with a non-normed normal.

Fixes #11401

## Solution
Just normalize right before we draw. Could do it when constructing the
builder but that seems less consistent.

## Changelog
- gizmos.circle normal is now a Direction3d instead of a Vec3.

## Migration Guide
- Pass a Direction3d for gizmos.circle normal, eg.
`Direction3d::new(vec).unwrap_or(default)` or potentially
`Direction3d::new_unchecked(vec)` if you know your vec is definitely
normalized.
2024-01-21 18:03:26 +00:00

152 lines
4.4 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::{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> {
(0..segments + 1).map(move |i| {
let angle = i as f32 * TAU / segments as f32;
Vec2::from(angle.sin_cos()) * radius
})
}
impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> {
/// 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,
) -> CircleBuilder<'_, 'w, 's, T> {
CircleBuilder {
gizmos: self,
position,
normal,
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,
) -> Circle2dBuilder<'_, 'w, 's, T> {
Circle2dBuilder {
gizmos: self,
position,
radius,
color,
segments: DEFAULT_CIRCLE_SEGMENTS,
}
}
}
/// A builder returned by [`Gizmos::circle`].
pub struct CircleBuilder<'a, 'w, 's, T: GizmoConfigGroup> {
gizmos: &'a mut Gizmos<'w, 's, T>,
position: Vec3,
normal: Direction3d,
radius: f32,
color: Color,
segments: usize,
}
impl<T: GizmoConfigGroup> CircleBuilder<'_, '_, '_, T> {
/// Set the number of line-segments for this circle.
pub fn segments(mut self, segments: usize) -> Self {
self.segments = segments;
self
}
}
impl<T: GizmoConfigGroup> Drop for CircleBuilder<'_, '_, '_, 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.));
self.gizmos.linestrip(positions, self.color);
}
}
/// A builder returned by [`Gizmos::circle_2d`].
pub struct Circle2dBuilder<'a, 'w, 's, T: GizmoConfigGroup> {
gizmos: &'a mut Gizmos<'w, 's, T>,
position: Vec2,
radius: f32,
color: Color,
segments: usize,
}
impl<T: GizmoConfigGroup> Circle2dBuilder<'_, '_, '_, T> {
/// Set the number of line-segments for this circle.
pub fn segments(mut self, segments: usize) -> Self {
self.segments = segments;
self
}
}
impl<T: GizmoConfigGroup> Drop for Circle2dBuilder<'_, '_, '_, T> {
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
}
let positions = circle_inner(self.radius, self.segments).map(|vec2| vec2 + self.position);
self.gizmos.linestrip_2d(positions, self.color);
}
}