mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
Implement Arc3D for Gizmos (#11540)
# Objective - Implement an arc3d API for gizmos - Solves #11536 ## Solution ### `arc_3d` - The current `arc3d` method on gizmos only takes an angle - It draws an "standard arc" by default, this is an arc starting at `Vec3::X`, in the XZ plane, in counter clockwise direction with a normal that is facing up - The "standard arc" can be customized with the usual gizmo builder pattern. This way you'll be able to draw arbitrary arcs ### `short/long_arc_3d_between` - Given `center`, `from`, `to` draws an arc between `from` and `to` --- ## Changelog > This section is optional. If this was a trivial fix, or has no externally-visible impact, you can delete this section. - Added: `Gizmos::arc3d(&mut self, angle)` method - Added: `Gizmos::long_arc_3d_between(&mut self, center, from, to)` method - Added: `Gizmos::short_arc_3d_between(&mut self, center, from, to)` method --- This PR factors out an orthogonal part of another PR as mentioned in [this comment](https://github.com/bevyengine/bevy/pull/11072#issuecomment-1883859573)
This commit is contained in:
parent
9fcf862114
commit
bcae8e9a8b
2 changed files with 300 additions and 11 deletions
|
@ -5,10 +5,12 @@
|
|||
|
||||
use crate::circles::DEFAULT_CIRCLE_SEGMENTS;
|
||||
use crate::prelude::{GizmoConfigGroup, Gizmos};
|
||||
use bevy_math::Vec2;
|
||||
use bevy_math::{Quat, Vec2, Vec3};
|
||||
use bevy_render::color::Color;
|
||||
use std::f32::consts::TAU;
|
||||
|
||||
// === 2D ===
|
||||
|
||||
impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> {
|
||||
/// Draw an arc, which is a part of the circumference of a circle, in 2D.
|
||||
///
|
||||
|
@ -73,7 +75,7 @@ pub struct Arc2dBuilder<'a, 'w, 's, T: GizmoConfigGroup> {
|
|||
impl<T: GizmoConfigGroup> Arc2dBuilder<'_, '_, '_, T> {
|
||||
/// Set the number of line-segments for this arc.
|
||||
pub fn segments(mut self, segments: usize) -> Self {
|
||||
self.segments = Some(segments);
|
||||
self.segments.replace(segments);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
@ -83,20 +85,18 @@ impl<T: GizmoConfigGroup> Drop for Arc2dBuilder<'_, '_, '_, T> {
|
|||
if !self.gizmos.enabled {
|
||||
return;
|
||||
}
|
||||
let segments = match self.segments {
|
||||
Some(segments) => segments,
|
||||
// Do a linear interpolation between 1 and `DEFAULT_CIRCLE_SEGMENTS`
|
||||
// using the arc angle as scalar.
|
||||
None => ((self.arc_angle.abs() / TAU) * DEFAULT_CIRCLE_SEGMENTS as f32).ceil() as usize,
|
||||
};
|
||||
|
||||
let positions = arc_inner(self.direction_angle, self.arc_angle, self.radius, segments)
|
||||
.map(|vec2| vec2 + self.position);
|
||||
let segments = self
|
||||
.segments
|
||||
.unwrap_or_else(|| segments_from_angle(self.arc_angle));
|
||||
|
||||
let positions = arc_2d_inner(self.direction_angle, self.arc_angle, self.radius, segments)
|
||||
.map(|vec2| (vec2 + self.position));
|
||||
self.gizmos.linestrip_2d(positions, self.color);
|
||||
}
|
||||
}
|
||||
|
||||
fn arc_inner(
|
||||
fn arc_2d_inner(
|
||||
direction_angle: f32,
|
||||
arc_angle: f32,
|
||||
radius: f32,
|
||||
|
@ -109,3 +109,282 @@ fn arc_inner(
|
|||
Vec2::from(angle.sin_cos()) * radius
|
||||
})
|
||||
}
|
||||
|
||||
// === 3D ===
|
||||
|
||||
impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> {
|
||||
/// Draw an arc, which is a part of the circumference of a circle, in 3D. For default values
|
||||
/// this is drawing a standard arc. A standard arc is defined as
|
||||
///
|
||||
/// - an arc with a center at `Vec3::ZERO`
|
||||
/// - starting at `Vec3::X`
|
||||
/// - embedded in the XZ plane
|
||||
/// - rotates counterclockwise
|
||||
///
|
||||
/// This should be called for each frame the arc needs to be rendered.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `angle`: sets how much of a circle circumference is passed, e.g. PI is half a circle. This
|
||||
/// value should be in the range (-2 * PI..=2 * PI)
|
||||
/// - `radius`: distance between the arc and it's center point
|
||||
/// - `position`: position of the arcs center point
|
||||
/// - `rotation`: defines orientation of the arc, by default we assume the arc is contained in a
|
||||
/// plane parallel to the XZ plane and the default starting point is (`position + Vec3::X`)
|
||||
/// - `color`: color of the arc
|
||||
///
|
||||
/// # Builder methods
|
||||
/// The number of segments of the arc (i.e. the level of detail) can be adjusted with the
|
||||
/// `.segements(...)` method.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use bevy_gizmos::prelude::*;
|
||||
/// # use bevy_render::prelude::*;
|
||||
/// # use bevy_math::prelude::*;
|
||||
/// # use std::f32::consts::PI;
|
||||
/// fn system(mut gizmos: Gizmos) {
|
||||
/// // rotation rotates normal to point in the direction of `Vec3::NEG_ONE`
|
||||
/// let rotation = Quat::from_rotation_arc(Vec3::Y, Vec3::NEG_ONE.normalize());
|
||||
///
|
||||
/// gizmos
|
||||
/// .arc_3d(
|
||||
/// 270.0_f32.to_radians(),
|
||||
/// 0.25,
|
||||
/// Vec3::ONE,
|
||||
/// rotation,
|
||||
/// Color::ORANGE
|
||||
/// )
|
||||
/// .segments(100);
|
||||
/// }
|
||||
/// # bevy_ecs::system::assert_is_system(system);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn arc_3d(
|
||||
&mut self,
|
||||
angle: f32,
|
||||
radius: f32,
|
||||
position: Vec3,
|
||||
rotation: Quat,
|
||||
color: Color,
|
||||
) -> Arc3dBuilder<'_, 'w, 's, T> {
|
||||
Arc3dBuilder {
|
||||
gizmos: self,
|
||||
start_vertex: Vec3::X,
|
||||
center: position,
|
||||
rotation,
|
||||
angle,
|
||||
radius,
|
||||
color,
|
||||
segments: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws the shortest arc between two points (`from` and `to`) relative to a specified `center` point.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `center`: The center point around which the arc is drawn.
|
||||
/// - `from`: The starting point of the arc.
|
||||
/// - `to`: The ending point of the arc.
|
||||
/// - `color`: color of the arc
|
||||
///
|
||||
/// # Builder methods
|
||||
/// The number of segments of the arc (i.e. the level of detail) can be adjusted with the
|
||||
/// `.segements(...)` method.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use bevy_gizmos::prelude::*;
|
||||
/// # use bevy_render::prelude::*;
|
||||
/// # use bevy_math::prelude::*;
|
||||
/// fn system(mut gizmos: Gizmos) {
|
||||
/// gizmos.short_arc_3d_between(
|
||||
/// Vec3::ONE,
|
||||
/// Vec3::ONE + Vec3::NEG_ONE,
|
||||
/// Vec3::ZERO,
|
||||
/// Color::ORANGE
|
||||
/// )
|
||||
/// .segments(100);
|
||||
/// }
|
||||
/// # bevy_ecs::system::assert_is_system(system);
|
||||
/// ```
|
||||
///
|
||||
/// # Notes
|
||||
/// - This method assumes that the points `from` and `to` are distinct from `center`. If one of
|
||||
/// the points is coincident with `center`, nothing is rendered.
|
||||
/// - The arc is drawn as a portion of a circle with a radius equal to the distance from the
|
||||
/// `center` to `from`. If the distance from `center` to `to` is not equal to the radius, then
|
||||
/// the results will behave as if this were the case
|
||||
#[inline]
|
||||
pub fn short_arc_3d_between(
|
||||
&mut self,
|
||||
center: Vec3,
|
||||
from: Vec3,
|
||||
to: Vec3,
|
||||
color: Color,
|
||||
) -> Arc3dBuilder<'_, 'w, 's, T> {
|
||||
self.arc_from_to(center, from, to, color, |x| x)
|
||||
}
|
||||
|
||||
/// Draws the longest arc between two points (`from` and `to`) relative to a specified `center` point.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `center`: The center point around which the arc is drawn.
|
||||
/// - `from`: The starting point of the arc.
|
||||
/// - `to`: The ending point of the arc.
|
||||
/// - `color`: color of the arc
|
||||
///
|
||||
/// # Builder methods
|
||||
/// The number of segments of the arc (i.e. the level of detail) can be adjusted with the
|
||||
/// `.segements(...)` method.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use bevy_gizmos::prelude::*;
|
||||
/// # use bevy_render::prelude::*;
|
||||
/// # use bevy_math::prelude::*;
|
||||
/// fn system(mut gizmos: Gizmos) {
|
||||
/// gizmos.long_arc_3d_between(
|
||||
/// Vec3::ONE,
|
||||
/// Vec3::ONE + Vec3::NEG_ONE,
|
||||
/// Vec3::ZERO,
|
||||
/// Color::ORANGE
|
||||
/// )
|
||||
/// .segments(100);
|
||||
/// }
|
||||
/// # bevy_ecs::system::assert_is_system(system);
|
||||
/// ```
|
||||
///
|
||||
/// # Notes
|
||||
/// - This method assumes that the points `from` and `to` are distinct from `center`. If one of
|
||||
/// the points is coincident with `center`, nothing is rendered.
|
||||
/// - The arc is drawn as a portion of a circle with a radius equal to the distance from the
|
||||
/// `center` to `from`. If the distance from `center` to `to` is not equal to the radius, then
|
||||
/// the results will behave as if this were the case.
|
||||
#[inline]
|
||||
pub fn long_arc_3d_between(
|
||||
&mut self,
|
||||
center: Vec3,
|
||||
from: Vec3,
|
||||
to: Vec3,
|
||||
color: Color,
|
||||
) -> Arc3dBuilder<'_, 'w, 's, T> {
|
||||
self.arc_from_to(center, from, to, color, |angle| {
|
||||
if angle > 0.0 {
|
||||
TAU - angle
|
||||
} else if angle < 0.0 {
|
||||
-TAU - angle
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn arc_from_to(
|
||||
&mut self,
|
||||
center: Vec3,
|
||||
from: Vec3,
|
||||
to: Vec3,
|
||||
color: Color,
|
||||
angle_fn: impl Fn(f32) -> f32,
|
||||
) -> Arc3dBuilder<'_, 'w, 's, T> {
|
||||
// `from` and `to` can be the same here since in either case nothing gets rendered and the
|
||||
// orientation ambiguity of `up` doesn't matter
|
||||
let from_axis = (from - center).normalize_or_zero();
|
||||
let to_axis = (to - center).normalize_or_zero();
|
||||
let (up, angle) = Quat::from_rotation_arc(from_axis, to_axis).to_axis_angle();
|
||||
|
||||
let angle = angle_fn(angle);
|
||||
let radius = center.distance(from);
|
||||
let rotation = Quat::from_rotation_arc(Vec3::Y, up);
|
||||
|
||||
let start_vertex = rotation.inverse() * from_axis;
|
||||
|
||||
Arc3dBuilder {
|
||||
gizmos: self,
|
||||
start_vertex,
|
||||
center,
|
||||
rotation,
|
||||
angle,
|
||||
radius,
|
||||
color,
|
||||
segments: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A builder returned by [`Gizmos::arc_2d`].
|
||||
pub struct Arc3dBuilder<'a, 'w, 's, T: GizmoConfigGroup> {
|
||||
gizmos: &'a mut Gizmos<'w, 's, T>,
|
||||
// this is the vertex the arc starts on in the XZ plane. For the normal arc_3d method this is
|
||||
// always starting at Vec3::X. For the short/long arc methods we actually need a way to start
|
||||
// at the from position and this is where this internal field comes into play. Some implicit
|
||||
// assumptions:
|
||||
//
|
||||
// 1. This is always in the XZ plane
|
||||
// 2. This is always normalized
|
||||
//
|
||||
// DO NOT expose this field to users as it is easy to mess this up
|
||||
start_vertex: Vec3,
|
||||
center: Vec3,
|
||||
rotation: Quat,
|
||||
angle: f32,
|
||||
radius: f32,
|
||||
color: Color,
|
||||
segments: Option<usize>,
|
||||
}
|
||||
|
||||
impl<T: GizmoConfigGroup> Arc3dBuilder<'_, '_, '_, T> {
|
||||
/// Set the number of line-segments for this arc.
|
||||
pub fn segments(mut self, segments: usize) -> Self {
|
||||
self.segments.replace(segments);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: GizmoConfigGroup> Drop for Arc3dBuilder<'_, '_, '_, T> {
|
||||
fn drop(&mut self) {
|
||||
if !self.gizmos.enabled {
|
||||
return;
|
||||
}
|
||||
|
||||
let segments = self
|
||||
.segments
|
||||
.unwrap_or_else(|| segments_from_angle(self.angle));
|
||||
|
||||
let positions = arc_3d_inner(
|
||||
self.start_vertex,
|
||||
self.center,
|
||||
self.rotation,
|
||||
self.angle,
|
||||
self.radius,
|
||||
segments,
|
||||
);
|
||||
self.gizmos.linestrip(positions, self.color);
|
||||
}
|
||||
}
|
||||
|
||||
fn arc_3d_inner(
|
||||
start_vertex: Vec3,
|
||||
center: Vec3,
|
||||
rotation: Quat,
|
||||
angle: f32,
|
||||
radius: f32,
|
||||
segments: usize,
|
||||
) -> impl Iterator<Item = Vec3> {
|
||||
// drawing arcs bigger than TAU degrees or smaller than -TAU degrees makes no sense since
|
||||
// we won't see the overlap and we would just decrease the level of details since the segments
|
||||
// would be larger
|
||||
let angle = angle.clamp(-TAU, TAU);
|
||||
(0..=segments)
|
||||
.map(move |frac| frac as f32 / segments as f32)
|
||||
.map(move |percentage| angle * percentage)
|
||||
.map(move |frac_angle| Quat::from_axis_angle(Vec3::Y, frac_angle) * start_vertex)
|
||||
.map(move |p| rotation * (p * radius) + center)
|
||||
}
|
||||
|
||||
// helper function for getting a default value for the segments parameter
|
||||
fn segments_from_angle(angle: f32) -> usize {
|
||||
((angle.abs() / TAU) * DEFAULT_CIRCLE_SEGMENTS as f32).ceil() as usize
|
||||
}
|
||||
|
|
|
@ -96,6 +96,16 @@ fn system(mut gizmos: Gizmos, mut my_gizmos: Gizmos<MyRoundGizmos>, time: Res<Ti
|
|||
);
|
||||
}
|
||||
|
||||
my_gizmos
|
||||
.arc_3d(
|
||||
180.0_f32.to_radians(),
|
||||
0.2,
|
||||
Vec3::ONE,
|
||||
Quat::from_rotation_arc(Vec3::Y, Vec3::ONE.normalize()),
|
||||
Color::ORANGE,
|
||||
)
|
||||
.segments(10);
|
||||
|
||||
// Circles have 32 line-segments by default.
|
||||
my_gizmos.circle(Vec3::ZERO, Direction3d::Y, 3., Color::BLACK);
|
||||
// You may want to increase this for larger circles or spheres.
|
||||
|
|
Loading…
Reference in a new issue