//! Additional [`GizmoBuffer`] Functions -- Arcs1//!2//! Includes the implementation of [`GizmoBuffer::arc_2d`],3//! and assorted support items.45use crate::{circles::DEFAULT_CIRCLE_RESOLUTION, gizmos::GizmoBuffer, prelude::GizmoConfigGroup};6use bevy_color::Color;7use bevy_math::{Isometry2d, Isometry3d, Quat, Rot2, Vec2, Vec3};8use core::f32::consts::{FRAC_PI_2, TAU};910// === 2D ===1112impl<Config, Clear> GizmoBuffer<Config, Clear>13where14Config: GizmoConfigGroup,15Clear: 'static + Send + Sync,16{17/// Draw an arc, which is a part of the circumference of a circle, in 2D.18///19/// # Arguments20/// - `isometry` defines the translation and rotation of the arc.21/// - the translation specifies the center of the arc22/// - the rotation is counter-clockwise starting from `Vec2::Y`23/// - `arc_angle` sets the length of this arc, in radians.24/// - `radius` controls the distance from `position` to this arc, and thus its curvature.25/// - `color` sets the color to draw the arc.26///27/// # Example28/// ```29/// # use bevy_gizmos::prelude::*;30/// # use bevy_math::prelude::*;31/// # use std::f32::consts::FRAC_PI_4;32/// # use bevy_color::palettes::basic::{GREEN, RED};33/// fn system(mut gizmos: Gizmos) {34/// gizmos.arc_2d(Isometry2d::IDENTITY, FRAC_PI_4, 1., GREEN);35///36/// // Arcs have 32 line-segments by default.37/// // You may want to increase this for larger arcs.38/// gizmos39/// .arc_2d(Isometry2d::IDENTITY, FRAC_PI_4, 5., RED)40/// .resolution(64);41/// }42/// # bevy_ecs::system::assert_is_system(system);43/// ```44#[inline]45pub fn arc_2d(46&mut self,47isometry: impl Into<Isometry2d>,48arc_angle: f32,49radius: f32,50color: impl Into<Color>,51) -> Arc2dBuilder<'_, Config, Clear> {52Arc2dBuilder {53gizmos: self,54isometry: isometry.into(),55arc_angle,56radius,57color: color.into(),58resolution: None,59}60}61}6263/// A builder returned by [`GizmoBuffer::arc_2d`].64pub struct Arc2dBuilder<'a, Config, Clear>65where66Config: GizmoConfigGroup,67Clear: 'static + Send + Sync,68{69gizmos: &'a mut GizmoBuffer<Config, Clear>,70isometry: Isometry2d,71arc_angle: f32,72radius: f32,73color: Color,74resolution: Option<u32>,75}7677impl<Config, Clear> Arc2dBuilder<'_, Config, Clear>78where79Config: GizmoConfigGroup,80Clear: 'static + Send + Sync,81{82/// Set the number of lines used to approximate the geometry of this arc.83pub fn resolution(mut self, resolution: u32) -> Self {84self.resolution.replace(resolution);85self86}87}8889impl<Config, Clear> Drop for Arc2dBuilder<'_, Config, Clear>90where91Config: GizmoConfigGroup,92Clear: 'static + Send + Sync,93{94fn drop(&mut self) {95if !self.gizmos.enabled {96return;97}9899let resolution = self100.resolution101.unwrap_or_else(|| resolution_from_angle(self.arc_angle));102103let positions =104arc_2d_inner(self.arc_angle, self.radius, resolution).map(|vec2| self.isometry * vec2);105self.gizmos.linestrip_2d(positions, self.color);106}107}108109fn arc_2d_inner(arc_angle: f32, radius: f32, resolution: u32) -> impl Iterator<Item = Vec2> {110(0..=resolution)111.map(move |n| arc_angle * n as f32 / resolution as f32)112.map(|angle| angle + FRAC_PI_2)113.map(Vec2::from_angle)114.map(move |vec2| vec2 * radius)115}116117// === 3D ===118119impl<Config, Clear> GizmoBuffer<Config, Clear>120where121Config: GizmoConfigGroup,122Clear: 'static + Send + Sync,123{124/// Draw an arc, which is a part of the circumference of a circle, in 3D. For default values125/// this is drawing a standard arc. A standard arc is defined as126///127/// - an arc with a center at `Vec3::ZERO`128/// - starting at `Vec3::X`129/// - embedded in the XZ plane130/// - rotates counterclockwise131///132/// # Arguments133/// - `angle`: sets how much of a circle circumference is passed, e.g. PI is half a circle. This134/// value should be in the range (-2 * PI..=2 * PI)135/// - `radius`: distance between the arc and its center point136/// - `isometry` defines the translation and rotation of the arc.137/// - the translation specifies the center of the arc138/// - the rotation is counter-clockwise starting from `Vec3::Y`139/// - `color`: color of the arc140///141/// # Builder methods142/// The resolution of the arc (i.e. the level of detail) can be adjusted with the143/// `.resolution(...)` method.144///145/// # Example146/// ```147/// # use bevy_gizmos::prelude::*;148/// # use bevy_math::prelude::*;149/// # use std::f32::consts::PI;150/// # use bevy_color::palettes::css::ORANGE;151/// fn system(mut gizmos: Gizmos) {152/// // rotation rotates normal to point in the direction of `Vec3::NEG_ONE`153/// let rotation = Quat::from_rotation_arc(Vec3::Y, Vec3::NEG_ONE.normalize());154///155/// gizmos156/// .arc_3d(157/// 270.0_f32.to_radians(),158/// 0.25,159/// Isometry3d::new(Vec3::ONE, rotation),160/// ORANGE161/// )162/// .resolution(100);163/// }164/// # bevy_ecs::system::assert_is_system(system);165/// ```166#[inline]167pub fn arc_3d(168&mut self,169angle: f32,170radius: f32,171isometry: impl Into<Isometry3d>,172color: impl Into<Color>,173) -> Arc3dBuilder<'_, Config, Clear> {174Arc3dBuilder {175gizmos: self,176start_vertex: Vec3::X,177isometry: isometry.into(),178angle,179radius,180color: color.into(),181resolution: None,182}183}184185/// Draws the shortest arc between two points (`from` and `to`) relative to a specified `center` point.186///187/// # Arguments188///189/// - `center`: The center point around which the arc is drawn.190/// - `from`: The starting point of the arc.191/// - `to`: The ending point of the arc.192/// - `color`: color of the arc193///194/// # Builder methods195/// The resolution of the arc (i.e. the level of detail) can be adjusted with the196/// `.resolution(...)` method.197///198/// # Examples199/// ```200/// # use bevy_gizmos::prelude::*;201/// # use bevy_math::prelude::*;202/// # use bevy_color::palettes::css::ORANGE;203/// fn system(mut gizmos: Gizmos) {204/// gizmos.short_arc_3d_between(205/// Vec3::ONE,206/// Vec3::ONE + Vec3::NEG_ONE,207/// Vec3::ZERO,208/// ORANGE209/// )210/// .resolution(100);211/// }212/// # bevy_ecs::system::assert_is_system(system);213/// ```214///215/// # Notes216/// - This method assumes that the points `from` and `to` are distinct from `center`. If one of217/// the points is coincident with `center`, nothing is rendered.218/// - The arc is drawn as a portion of a circle with a radius equal to the distance from the219/// `center` to `from`. If the distance from `center` to `to` is not equal to the radius, then220/// the results will behave as if this were the case221#[inline]222pub fn short_arc_3d_between(223&mut self,224center: Vec3,225from: Vec3,226to: Vec3,227color: impl Into<Color>,228) -> Arc3dBuilder<'_, Config, Clear> {229self.arc_from_to(center, from, to, color, |x| x)230}231232/// Draws the longest arc between two points (`from` and `to`) relative to a specified `center` point.233///234/// # Arguments235/// - `center`: The center point around which the arc is drawn.236/// - `from`: The starting point of the arc.237/// - `to`: The ending point of the arc.238/// - `color`: color of the arc239///240/// # Builder methods241/// The resolution of the arc (i.e. the level of detail) can be adjusted with the242/// `.resolution(...)` method.243///244/// # Examples245/// ```246/// # use bevy_gizmos::prelude::*;247/// # use bevy_math::prelude::*;248/// # use bevy_color::palettes::css::ORANGE;249/// fn system(mut gizmos: Gizmos) {250/// gizmos.long_arc_3d_between(251/// Vec3::ONE,252/// Vec3::ONE + Vec3::NEG_ONE,253/// Vec3::ZERO,254/// ORANGE255/// )256/// .resolution(100);257/// }258/// # bevy_ecs::system::assert_is_system(system);259/// ```260///261/// # Notes262/// - This method assumes that the points `from` and `to` are distinct from `center`. If one of263/// the points is coincident with `center`, nothing is rendered.264/// - The arc is drawn as a portion of a circle with a radius equal to the distance from the265/// `center` to `from`. If the distance from `center` to `to` is not equal to the radius, then266/// the results will behave as if this were the case.267#[inline]268pub fn long_arc_3d_between(269&mut self,270center: Vec3,271from: Vec3,272to: Vec3,273color: impl Into<Color>,274) -> Arc3dBuilder<'_, Config, Clear> {275self.arc_from_to(center, from, to, color, |angle| {276if angle > 0.0 {277TAU - angle278} else if angle < 0.0 {279-TAU - angle280} else {2810.0282}283})284}285286#[inline]287fn arc_from_to(288&mut self,289center: Vec3,290from: Vec3,291to: Vec3,292color: impl Into<Color>,293angle_fn: impl Fn(f32) -> f32,294) -> Arc3dBuilder<'_, Config, Clear> {295// `from` and `to` can be the same here since in either case nothing gets rendered and the296// orientation ambiguity of `up` doesn't matter297let from_axis = (from - center).normalize_or_zero();298let to_axis = (to - center).normalize_or_zero();299let (up, angle) = Quat::from_rotation_arc(from_axis, to_axis).to_axis_angle();300301let angle = angle_fn(angle);302let radius = center.distance(from);303let rotation = Quat::from_rotation_arc(Vec3::Y, up);304305let start_vertex = rotation.inverse() * from_axis;306307Arc3dBuilder {308gizmos: self,309start_vertex,310isometry: Isometry3d::new(center, rotation),311angle,312radius,313color: color.into(),314resolution: None,315}316}317318/// Draws the shortest arc between two points (`from` and `to`) relative to a specified `center` point.319///320/// # Arguments321///322/// - `center`: The center point around which the arc is drawn.323/// - `from`: The starting point of the arc.324/// - `to`: The ending point of the arc.325/// - `color`: color of the arc326///327/// # Builder methods328/// The resolution of the arc (i.e. the level of detail) can be adjusted with the329/// `.resolution(...)` method.330///331/// # Examples332/// ```333/// # use bevy_gizmos::prelude::*;334/// # use bevy_math::prelude::*;335/// # use bevy_color::palettes::css::ORANGE;336/// fn system(mut gizmos: Gizmos) {337/// gizmos.short_arc_2d_between(338/// Vec2::ZERO,339/// Vec2::X,340/// Vec2::Y,341/// ORANGE342/// )343/// .resolution(100);344/// }345/// # bevy_ecs::system::assert_is_system(system);346/// ```347///348/// # Notes349/// - This method assumes that the points `from` and `to` are distinct from `center`. If one of350/// the points is coincident with `center`, nothing is rendered.351/// - The arc is drawn as a portion of a circle with a radius equal to the distance from the352/// `center` to `from`. If the distance from `center` to `to` is not equal to the radius, then353/// the results will behave as if this were the case354#[inline]355pub fn short_arc_2d_between(356&mut self,357center: Vec2,358from: Vec2,359to: Vec2,360color: impl Into<Color>,361) -> Arc2dBuilder<'_, Config, Clear> {362self.arc_2d_from_to(center, from, to, color, core::convert::identity)363}364365/// Draws the longest arc between two points (`from` and `to`) relative to a specified `center` point.366///367/// # Arguments368/// - `center`: The center point around which the arc is drawn.369/// - `from`: The starting point of the arc.370/// - `to`: The ending point of the arc.371/// - `color`: color of the arc372///373/// # Builder methods374/// The resolution of the arc (i.e. the level of detail) can be adjusted with the375/// `.resolution(...)` method.376///377/// # Examples378/// ```379/// # use bevy_gizmos::prelude::*;380/// # use bevy_math::prelude::*;381/// # use bevy_color::palettes::css::ORANGE;382/// fn system(mut gizmos: Gizmos) {383/// gizmos.long_arc_2d_between(384/// Vec2::ZERO,385/// Vec2::X,386/// Vec2::Y,387/// ORANGE388/// )389/// .resolution(100);390/// }391/// # bevy_ecs::system::assert_is_system(system);392/// ```393///394/// # Notes395/// - This method assumes that the points `from` and `to` are distinct from `center`. If one of396/// the points is coincident with `center`, nothing is rendered.397/// - The arc is drawn as a portion of a circle with a radius equal to the distance from the398/// `center` to `from`. If the distance from `center` to `to` is not equal to the radius, then399/// the results will behave as if this were the case.400#[inline]401pub fn long_arc_2d_between(402&mut self,403center: Vec2,404from: Vec2,405to: Vec2,406color: impl Into<Color>,407) -> Arc2dBuilder<'_, Config, Clear> {408self.arc_2d_from_to(center, from, to, color, |angle| angle - TAU)409}410411#[inline]412fn arc_2d_from_to(413&mut self,414center: Vec2,415from: Vec2,416to: Vec2,417color: impl Into<Color>,418angle_fn: impl Fn(f32) -> f32,419) -> Arc2dBuilder<'_, Config, Clear> {420// `from` and `to` can be the same here since in either case nothing gets rendered and the421// orientation ambiguity of `up` doesn't matter422let from_axis = (from - center).normalize_or_zero();423let to_axis = (to - center).normalize_or_zero();424let rotation = Vec2::Y.angle_to(from_axis);425let arc_angle_raw = from_axis.angle_to(to_axis);426427let arc_angle = angle_fn(arc_angle_raw);428let radius = center.distance(from);429430Arc2dBuilder {431gizmos: self,432isometry: Isometry2d::new(center, Rot2::radians(rotation)),433arc_angle,434radius,435color: color.into(),436resolution: None,437}438}439}440441/// A builder returned by [`GizmoBuffer::arc_2d`].442pub struct Arc3dBuilder<'a, Config, Clear>443where444Config: GizmoConfigGroup,445Clear: 'static + Send + Sync,446{447gizmos: &'a mut GizmoBuffer<Config, Clear>,448// this is the vertex the arc starts on in the XZ plane. For the normal arc_3d method this is449// always starting at Vec3::X. For the short/long arc methods we actually need a way to start450// at the from position and this is where this internal field comes into play. Some implicit451// assumptions:452//453// 1. This is always in the XZ plane454// 2. This is always normalized455//456// DO NOT expose this field to users as it is easy to mess this up457start_vertex: Vec3,458isometry: Isometry3d,459angle: f32,460radius: f32,461color: Color,462resolution: Option<u32>,463}464465impl<Config, Clear> Arc3dBuilder<'_, Config, Clear>466where467Config: GizmoConfigGroup,468Clear: 'static + Send + Sync,469{470/// Set the number of lines for this arc.471pub fn resolution(mut self, resolution: u32) -> Self {472self.resolution.replace(resolution);473self474}475}476477impl<Config, Clear> Drop for Arc3dBuilder<'_, Config, Clear>478where479Config: GizmoConfigGroup,480Clear: 'static + Send + Sync,481{482fn drop(&mut self) {483if !self.gizmos.enabled {484return;485}486487let resolution = self488.resolution489.unwrap_or_else(|| resolution_from_angle(self.angle));490491let positions = arc_3d_inner(492self.start_vertex,493self.isometry,494self.angle,495self.radius,496resolution,497);498self.gizmos.linestrip(positions, self.color);499}500}501502fn arc_3d_inner(503start_vertex: Vec3,504isometry: Isometry3d,505angle: f32,506radius: f32,507resolution: u32,508) -> impl Iterator<Item = Vec3> {509// drawing arcs bigger than TAU degrees or smaller than -TAU degrees makes no sense since510// we won't see the overlap and we would just decrease the level of details since the resolution511// would be larger512let angle = angle.clamp(-TAU, TAU);513(0..=resolution)514.map(move |frac| frac as f32 / resolution as f32)515.map(move |percentage| angle * percentage)516.map(move |frac_angle| Quat::from_axis_angle(Vec3::Y, frac_angle) * start_vertex)517.map(move |vec3| vec3 * radius)518.map(move |vec3| isometry * vec3)519}520521// helper function for getting a default value for the resolution parameter522fn resolution_from_angle(angle: f32) -> u32 {523((angle.abs() / TAU) * DEFAULT_CIRCLE_RESOLUTION as f32).ceil() as u32524}525526527