//! 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/// This should be called for each frame the arc needs to be rendered.20///21/// # Arguments22/// - `isometry` defines the translation and rotation of the arc.23/// - the translation specifies the center of the arc24/// - the rotation is counter-clockwise starting from `Vec2::Y`25/// - `arc_angle` sets the length of this arc, in radians.26/// - `radius` controls the distance from `position` to this arc, and thus its curvature.27/// - `color` sets the color to draw the arc.28///29/// # Example30/// ```31/// # use bevy_gizmos::prelude::*;32/// # use bevy_math::prelude::*;33/// # use std::f32::consts::FRAC_PI_4;34/// # use bevy_color::palettes::basic::{GREEN, RED};35/// fn system(mut gizmos: Gizmos) {36/// gizmos.arc_2d(Isometry2d::IDENTITY, FRAC_PI_4, 1., GREEN);37///38/// // Arcs have 32 line-segments by default.39/// // You may want to increase this for larger arcs.40/// gizmos41/// .arc_2d(Isometry2d::IDENTITY, FRAC_PI_4, 5., RED)42/// .resolution(64);43/// }44/// # bevy_ecs::system::assert_is_system(system);45/// ```46#[inline]47pub fn arc_2d(48&mut self,49isometry: impl Into<Isometry2d>,50arc_angle: f32,51radius: f32,52color: impl Into<Color>,53) -> Arc2dBuilder<'_, Config, Clear> {54Arc2dBuilder {55gizmos: self,56isometry: isometry.into(),57arc_angle,58radius,59color: color.into(),60resolution: None,61}62}63}6465/// A builder returned by [`GizmoBuffer::arc_2d`].66pub struct Arc2dBuilder<'a, Config, Clear>67where68Config: GizmoConfigGroup,69Clear: 'static + Send + Sync,70{71gizmos: &'a mut GizmoBuffer<Config, Clear>,72isometry: Isometry2d,73arc_angle: f32,74radius: f32,75color: Color,76resolution: Option<u32>,77}7879impl<Config, Clear> Arc2dBuilder<'_, Config, Clear>80where81Config: GizmoConfigGroup,82Clear: 'static + Send + Sync,83{84/// Set the number of lines used to approximate the geometry of this arc.85pub fn resolution(mut self, resolution: u32) -> Self {86self.resolution.replace(resolution);87self88}89}9091impl<Config, Clear> Drop for Arc2dBuilder<'_, Config, Clear>92where93Config: GizmoConfigGroup,94Clear: 'static + Send + Sync,95{96fn drop(&mut self) {97if !self.gizmos.enabled {98return;99}100101let resolution = self102.resolution103.unwrap_or_else(|| resolution_from_angle(self.arc_angle));104105let positions =106arc_2d_inner(self.arc_angle, self.radius, resolution).map(|vec2| self.isometry * vec2);107self.gizmos.linestrip_2d(positions, self.color);108}109}110111fn arc_2d_inner(arc_angle: f32, radius: f32, resolution: u32) -> impl Iterator<Item = Vec2> {112(0..=resolution)113.map(move |n| arc_angle * n as f32 / resolution as f32)114.map(|angle| angle + FRAC_PI_2)115.map(Vec2::from_angle)116.map(move |vec2| vec2 * radius)117}118119// === 3D ===120121impl<Config, Clear> GizmoBuffer<Config, Clear>122where123Config: GizmoConfigGroup,124Clear: 'static + Send + Sync,125{126/// Draw an arc, which is a part of the circumference of a circle, in 3D. For default values127/// this is drawing a standard arc. A standard arc is defined as128///129/// - an arc with a center at `Vec3::ZERO`130/// - starting at `Vec3::X`131/// - embedded in the XZ plane132/// - rotates counterclockwise133///134/// This should be called for each frame the arc needs to be rendered.135///136/// # Arguments137/// - `angle`: sets how much of a circle circumference is passed, e.g. PI is half a circle. This138/// value should be in the range (-2 * PI..=2 * PI)139/// - `radius`: distance between the arc and its center point140/// - `isometry` defines the translation and rotation of the arc.141/// - the translation specifies the center of the arc142/// - the rotation is counter-clockwise starting from `Vec3::Y`143/// - `color`: color of the arc144///145/// # Builder methods146/// The resolution of the arc (i.e. the level of detail) can be adjusted with the147/// `.resolution(...)` method.148///149/// # Example150/// ```151/// # use bevy_gizmos::prelude::*;152/// # use bevy_math::prelude::*;153/// # use std::f32::consts::PI;154/// # use bevy_color::palettes::css::ORANGE;155/// fn system(mut gizmos: Gizmos) {156/// // rotation rotates normal to point in the direction of `Vec3::NEG_ONE`157/// let rotation = Quat::from_rotation_arc(Vec3::Y, Vec3::NEG_ONE.normalize());158///159/// gizmos160/// .arc_3d(161/// 270.0_f32.to_radians(),162/// 0.25,163/// Isometry3d::new(Vec3::ONE, rotation),164/// ORANGE165/// )166/// .resolution(100);167/// }168/// # bevy_ecs::system::assert_is_system(system);169/// ```170#[inline]171pub fn arc_3d(172&mut self,173angle: f32,174radius: f32,175isometry: impl Into<Isometry3d>,176color: impl Into<Color>,177) -> Arc3dBuilder<'_, Config, Clear> {178Arc3dBuilder {179gizmos: self,180start_vertex: Vec3::X,181isometry: isometry.into(),182angle,183radius,184color: color.into(),185resolution: None,186}187}188189/// Draws the shortest arc between two points (`from` and `to`) relative to a specified `center` point.190///191/// # Arguments192///193/// - `center`: The center point around which the arc is drawn.194/// - `from`: The starting point of the arc.195/// - `to`: The ending point of the arc.196/// - `color`: color of the arc197///198/// # Builder methods199/// The resolution of the arc (i.e. the level of detail) can be adjusted with the200/// `.resolution(...)` method.201///202/// # Examples203/// ```204/// # use bevy_gizmos::prelude::*;205/// # use bevy_math::prelude::*;206/// # use bevy_color::palettes::css::ORANGE;207/// fn system(mut gizmos: Gizmos) {208/// gizmos.short_arc_3d_between(209/// Vec3::ONE,210/// Vec3::ONE + Vec3::NEG_ONE,211/// Vec3::ZERO,212/// ORANGE213/// )214/// .resolution(100);215/// }216/// # bevy_ecs::system::assert_is_system(system);217/// ```218///219/// # Notes220/// - This method assumes that the points `from` and `to` are distinct from `center`. If one of221/// the points is coincident with `center`, nothing is rendered.222/// - The arc is drawn as a portion of a circle with a radius equal to the distance from the223/// `center` to `from`. If the distance from `center` to `to` is not equal to the radius, then224/// the results will behave as if this were the case225#[inline]226pub fn short_arc_3d_between(227&mut self,228center: Vec3,229from: Vec3,230to: Vec3,231color: impl Into<Color>,232) -> Arc3dBuilder<'_, Config, Clear> {233self.arc_from_to(center, from, to, color, |x| x)234}235236/// Draws the longest arc between two points (`from` and `to`) relative to a specified `center` point.237///238/// # Arguments239/// - `center`: The center point around which the arc is drawn.240/// - `from`: The starting point of the arc.241/// - `to`: The ending point of the arc.242/// - `color`: color of the arc243///244/// # Builder methods245/// The resolution of the arc (i.e. the level of detail) can be adjusted with the246/// `.resolution(...)` method.247///248/// # Examples249/// ```250/// # use bevy_gizmos::prelude::*;251/// # use bevy_math::prelude::*;252/// # use bevy_color::palettes::css::ORANGE;253/// fn system(mut gizmos: Gizmos) {254/// gizmos.long_arc_3d_between(255/// Vec3::ONE,256/// Vec3::ONE + Vec3::NEG_ONE,257/// Vec3::ZERO,258/// ORANGE259/// )260/// .resolution(100);261/// }262/// # bevy_ecs::system::assert_is_system(system);263/// ```264///265/// # Notes266/// - This method assumes that the points `from` and `to` are distinct from `center`. If one of267/// the points is coincident with `center`, nothing is rendered.268/// - The arc is drawn as a portion of a circle with a radius equal to the distance from the269/// `center` to `from`. If the distance from `center` to `to` is not equal to the radius, then270/// the results will behave as if this were the case.271#[inline]272pub fn long_arc_3d_between(273&mut self,274center: Vec3,275from: Vec3,276to: Vec3,277color: impl Into<Color>,278) -> Arc3dBuilder<'_, Config, Clear> {279self.arc_from_to(center, from, to, color, |angle| {280if angle > 0.0 {281TAU - angle282} else if angle < 0.0 {283-TAU - angle284} else {2850.0286}287})288}289290#[inline]291fn arc_from_to(292&mut self,293center: Vec3,294from: Vec3,295to: Vec3,296color: impl Into<Color>,297angle_fn: impl Fn(f32) -> f32,298) -> Arc3dBuilder<'_, Config, Clear> {299// `from` and `to` can be the same here since in either case nothing gets rendered and the300// orientation ambiguity of `up` doesn't matter301let from_axis = (from - center).normalize_or_zero();302let to_axis = (to - center).normalize_or_zero();303let (up, angle) = Quat::from_rotation_arc(from_axis, to_axis).to_axis_angle();304305let angle = angle_fn(angle);306let radius = center.distance(from);307let rotation = Quat::from_rotation_arc(Vec3::Y, up);308309let start_vertex = rotation.inverse() * from_axis;310311Arc3dBuilder {312gizmos: self,313start_vertex,314isometry: Isometry3d::new(center, rotation),315angle,316radius,317color: color.into(),318resolution: None,319}320}321322/// Draws the shortest arc between two points (`from` and `to`) relative to a specified `center` point.323///324/// # Arguments325///326/// - `center`: The center point around which the arc is drawn.327/// - `from`: The starting point of the arc.328/// - `to`: The ending point of the arc.329/// - `color`: color of the arc330///331/// # Builder methods332/// The resolution of the arc (i.e. the level of detail) can be adjusted with the333/// `.resolution(...)` method.334///335/// # Examples336/// ```337/// # use bevy_gizmos::prelude::*;338/// # use bevy_math::prelude::*;339/// # use bevy_color::palettes::css::ORANGE;340/// fn system(mut gizmos: Gizmos) {341/// gizmos.short_arc_2d_between(342/// Vec2::ZERO,343/// Vec2::X,344/// Vec2::Y,345/// ORANGE346/// )347/// .resolution(100);348/// }349/// # bevy_ecs::system::assert_is_system(system);350/// ```351///352/// # Notes353/// - This method assumes that the points `from` and `to` are distinct from `center`. If one of354/// the points is coincident with `center`, nothing is rendered.355/// - The arc is drawn as a portion of a circle with a radius equal to the distance from the356/// `center` to `from`. If the distance from `center` to `to` is not equal to the radius, then357/// the results will behave as if this were the case358#[inline]359pub fn short_arc_2d_between(360&mut self,361center: Vec2,362from: Vec2,363to: Vec2,364color: impl Into<Color>,365) -> Arc2dBuilder<'_, Config, Clear> {366self.arc_2d_from_to(center, from, to, color, core::convert::identity)367}368369/// Draws the longest arc between two points (`from` and `to`) relative to a specified `center` point.370///371/// # Arguments372/// - `center`: The center point around which the arc is drawn.373/// - `from`: The starting point of the arc.374/// - `to`: The ending point of the arc.375/// - `color`: color of the arc376///377/// # Builder methods378/// The resolution of the arc (i.e. the level of detail) can be adjusted with the379/// `.resolution(...)` method.380///381/// # Examples382/// ```383/// # use bevy_gizmos::prelude::*;384/// # use bevy_math::prelude::*;385/// # use bevy_color::palettes::css::ORANGE;386/// fn system(mut gizmos: Gizmos) {387/// gizmos.long_arc_2d_between(388/// Vec2::ZERO,389/// Vec2::X,390/// Vec2::Y,391/// ORANGE392/// )393/// .resolution(100);394/// }395/// # bevy_ecs::system::assert_is_system(system);396/// ```397///398/// # Notes399/// - This method assumes that the points `from` and `to` are distinct from `center`. If one of400/// the points is coincident with `center`, nothing is rendered.401/// - The arc is drawn as a portion of a circle with a radius equal to the distance from the402/// `center` to `from`. If the distance from `center` to `to` is not equal to the radius, then403/// the results will behave as if this were the case.404#[inline]405pub fn long_arc_2d_between(406&mut self,407center: Vec2,408from: Vec2,409to: Vec2,410color: impl Into<Color>,411) -> Arc2dBuilder<'_, Config, Clear> {412self.arc_2d_from_to(center, from, to, color, |angle| angle - TAU)413}414415#[inline]416fn arc_2d_from_to(417&mut self,418center: Vec2,419from: Vec2,420to: Vec2,421color: impl Into<Color>,422angle_fn: impl Fn(f32) -> f32,423) -> Arc2dBuilder<'_, Config, Clear> {424// `from` and `to` can be the same here since in either case nothing gets rendered and the425// orientation ambiguity of `up` doesn't matter426let from_axis = (from - center).normalize_or_zero();427let to_axis = (to - center).normalize_or_zero();428let rotation = Vec2::Y.angle_to(from_axis);429let arc_angle_raw = from_axis.angle_to(to_axis);430431let arc_angle = angle_fn(arc_angle_raw);432let radius = center.distance(from);433434Arc2dBuilder {435gizmos: self,436isometry: Isometry2d::new(center, Rot2::radians(rotation)),437arc_angle,438radius,439color: color.into(),440resolution: None,441}442}443}444445/// A builder returned by [`GizmoBuffer::arc_2d`].446pub struct Arc3dBuilder<'a, Config, Clear>447where448Config: GizmoConfigGroup,449Clear: 'static + Send + Sync,450{451gizmos: &'a mut GizmoBuffer<Config, Clear>,452// this is the vertex the arc starts on in the XZ plane. For the normal arc_3d method this is453// always starting at Vec3::X. For the short/long arc methods we actually need a way to start454// at the from position and this is where this internal field comes into play. Some implicit455// assumptions:456//457// 1. This is always in the XZ plane458// 2. This is always normalized459//460// DO NOT expose this field to users as it is easy to mess this up461start_vertex: Vec3,462isometry: Isometry3d,463angle: f32,464radius: f32,465color: Color,466resolution: Option<u32>,467}468469impl<Config, Clear> Arc3dBuilder<'_, Config, Clear>470where471Config: GizmoConfigGroup,472Clear: 'static + Send + Sync,473{474/// Set the number of lines for this arc.475pub fn resolution(mut self, resolution: u32) -> Self {476self.resolution.replace(resolution);477self478}479}480481impl<Config, Clear> Drop for Arc3dBuilder<'_, Config, Clear>482where483Config: GizmoConfigGroup,484Clear: 'static + Send + Sync,485{486fn drop(&mut self) {487if !self.gizmos.enabled {488return;489}490491let resolution = self492.resolution493.unwrap_or_else(|| resolution_from_angle(self.angle));494495let positions = arc_3d_inner(496self.start_vertex,497self.isometry,498self.angle,499self.radius,500resolution,501);502self.gizmos.linestrip(positions, self.color);503}504}505506fn arc_3d_inner(507start_vertex: Vec3,508isometry: Isometry3d,509angle: f32,510radius: f32,511resolution: u32,512) -> impl Iterator<Item = Vec3> {513// drawing arcs bigger than TAU degrees or smaller than -TAU degrees makes no sense since514// we won't see the overlap and we would just decrease the level of details since the resolution515// would be larger516let angle = angle.clamp(-TAU, TAU);517(0..=resolution)518.map(move |frac| frac as f32 / resolution as f32)519.map(move |percentage| angle * percentage)520.map(move |frac_angle| Quat::from_axis_angle(Vec3::Y, frac_angle) * start_vertex)521.map(move |vec3| vec3 * radius)522.map(move |vec3| isometry * vec3)523}524525// helper function for getting a default value for the resolution parameter526fn resolution_from_angle(angle: f32) -> u32 {527((angle.abs() / TAU) * DEFAULT_CIRCLE_RESOLUTION as f32).ceil() as u32528}529530531