Path: blob/main/crates/bevy_gizmos/src/primitives/dim2.rs
6596 views
//! A module for rendering each of the 2D [`bevy_math::primitives`] with [`GizmoBuffer`].12use core::f32::consts::{FRAC_PI_2, PI};34use super::helpers::*;56use bevy_color::Color;7use bevy_math::{8primitives::{9Annulus, Arc2d, Capsule2d, Circle, CircularSector, CircularSegment, Ellipse, Line2d,10Plane2d, Polygon, Polyline2d, Primitive2d, Rectangle, RegularPolygon, Rhombus, Segment2d,11Triangle2d,12},13Dir2, Isometry2d, Rot2, Vec2,14};1516use crate::{gizmos::GizmoBuffer, prelude::GizmoConfigGroup};1718// some magic number since using directions as offsets will result in lines of length 1 pixel19const MIN_LINE_LEN: f32 = 50.0;20const HALF_MIN_LINE_LEN: f32 = 25.0;21// length used to simulate infinite lines22const INFINITE_LEN: f32 = 100_000.0;2324/// A trait for rendering 2D geometric primitives (`P`) with [`GizmoBuffer`].25pub trait GizmoPrimitive2d<P: Primitive2d> {26/// The output of `primitive_2d`. This is a builder to set non-default values.27type Output<'a>28where29Self: 'a;3031/// Renders a 2D primitive with its associated details.32fn primitive_2d(33&mut self,34primitive: &P,35isometry: impl Into<Isometry2d>,36color: impl Into<Color>,37) -> Self::Output<'_>;38}3940// direction 2d4142impl<Config, Clear> GizmoPrimitive2d<Dir2> for GizmoBuffer<Config, Clear>43where44Config: GizmoConfigGroup,45Clear: 'static + Send + Sync,46{47type Output<'a>48= ()49where50Self: 'a;5152fn primitive_2d(53&mut self,54primitive: &Dir2,55isometry: impl Into<Isometry2d>,56color: impl Into<Color>,57) -> Self::Output<'_> {58if !self.enabled {59return;60}61let isometry = isometry.into();62let start = Vec2::ZERO;63let end = *primitive * MIN_LINE_LEN;64self.arrow_2d(isometry * start, isometry * end, color);65}66}6768// arc 2d6970impl<Config, Clear> GizmoPrimitive2d<Arc2d> for GizmoBuffer<Config, Clear>71where72Config: GizmoConfigGroup,73Clear: 'static + Send + Sync,74{75type Output<'a>76= ()77where78Self: 'a;7980fn primitive_2d(81&mut self,82primitive: &Arc2d,83isometry: impl Into<Isometry2d>,84color: impl Into<Color>,85) -> Self::Output<'_> {86if !self.enabled {87return;88}8990let isometry = isometry.into();91let start_iso = isometry * Isometry2d::from_rotation(Rot2::radians(-primitive.half_angle));9293self.arc_2d(94start_iso,95primitive.half_angle * 2.0,96primitive.radius,97color,98);99}100}101102// circle 2d103104impl<Config, Clear> GizmoPrimitive2d<Circle> for GizmoBuffer<Config, Clear>105where106Config: GizmoConfigGroup,107Clear: 'static + Send + Sync,108{109type Output<'a>110= crate::circles::Ellipse2dBuilder<'a, Config, Clear>111where112Self: 'a;113114fn primitive_2d(115&mut self,116primitive: &Circle,117isometry: impl Into<Isometry2d>,118color: impl Into<Color>,119) -> Self::Output<'_> {120self.circle_2d(isometry, primitive.radius, color)121}122}123124// circular sector 2d125126impl<Config, Clear> GizmoPrimitive2d<CircularSector> for GizmoBuffer<Config, Clear>127where128Config: GizmoConfigGroup,129Clear: 'static + Send + Sync,130{131type Output<'a>132= ()133where134Self: 'a;135136fn primitive_2d(137&mut self,138primitive: &CircularSector,139isometry: impl Into<Isometry2d>,140color: impl Into<Color>,141) -> Self::Output<'_> {142if !self.enabled {143return;144}145146let isometry = isometry.into();147let color = color.into();148149let start_iso =150isometry * Isometry2d::from_rotation(Rot2::radians(-primitive.arc.half_angle));151let end_iso = isometry * Isometry2d::from_rotation(Rot2::radians(primitive.arc.half_angle));152153// we need to draw the arc part of the sector, and the two lines connecting the arc and the center154self.arc_2d(155start_iso,156primitive.arc.half_angle * 2.0,157primitive.arc.radius,158color,159);160161let end_position = primitive.arc.radius * Vec2::Y;162self.line_2d(isometry * Vec2::ZERO, start_iso * end_position, color);163self.line_2d(isometry * Vec2::ZERO, end_iso * end_position, color);164}165}166167// circular segment 2d168169impl<Config, Clear> GizmoPrimitive2d<CircularSegment> for GizmoBuffer<Config, Clear>170where171Config: GizmoConfigGroup,172Clear: 'static + Send + Sync,173{174type Output<'a>175= ()176where177Self: 'a;178179fn primitive_2d(180&mut self,181primitive: &CircularSegment,182isometry: impl Into<Isometry2d>,183color: impl Into<Color>,184) -> Self::Output<'_> {185if !self.enabled {186return;187}188189let isometry = isometry.into();190let color = color.into();191192let start_iso =193isometry * Isometry2d::from_rotation(Rot2::radians(-primitive.arc.half_angle));194let end_iso = isometry * Isometry2d::from_rotation(Rot2::radians(primitive.arc.half_angle));195196// we need to draw the arc part of the segment, and the line connecting the two ends197self.arc_2d(198start_iso,199primitive.arc.half_angle * 2.0,200primitive.arc.radius,201color,202);203204let position = primitive.arc.radius * Vec2::Y;205self.line_2d(start_iso * position, end_iso * position, color);206}207}208209// ellipse 2d210211impl<Config, Clear> GizmoPrimitive2d<Ellipse> for GizmoBuffer<Config, Clear>212where213Config: GizmoConfigGroup,214Clear: 'static + Send + Sync,215{216type Output<'a>217= crate::circles::Ellipse2dBuilder<'a, Config, Clear>218where219Self: 'a;220221fn primitive_2d<'a>(222&mut self,223primitive: &Ellipse,224isometry: impl Into<Isometry2d>,225color: impl Into<Color>,226) -> Self::Output<'_> {227self.ellipse_2d(isometry, primitive.half_size, color)228}229}230231// annulus 2d232233/// Builder for configuring the drawing options of [`Annulus`].234pub struct Annulus2dBuilder<'a, Config, Clear>235where236Config: GizmoConfigGroup,237Clear: 'static + Send + Sync,238{239gizmos: &'a mut GizmoBuffer<Config, Clear>,240isometry: Isometry2d,241inner_radius: f32,242outer_radius: f32,243color: Color,244inner_resolution: u32,245outer_resolution: u32,246}247248impl<Config, Clear> Annulus2dBuilder<'_, Config, Clear>249where250Config: GizmoConfigGroup,251Clear: 'static + Send + Sync,252{253/// Set the number of line-segments for each circle of the annulus.254pub fn resolution(mut self, resolution: u32) -> Self {255self.outer_resolution = resolution;256self.inner_resolution = resolution;257self258}259260/// Set the number of line-segments for the outer circle of the annulus.261pub fn outer_resolution(mut self, resolution: u32) -> Self {262self.outer_resolution = resolution;263self264}265266/// Set the number of line-segments for the inner circle of the annulus.267pub fn inner_resolution(mut self, resolution: u32) -> Self {268self.inner_resolution = resolution;269self270}271}272273impl<Config, Clear> GizmoPrimitive2d<Annulus> for GizmoBuffer<Config, Clear>274where275Config: GizmoConfigGroup,276Clear: 'static + Send + Sync,277{278type Output<'a>279= Annulus2dBuilder<'a, Config, Clear>280where281Self: 'a;282283fn primitive_2d(284&mut self,285primitive: &Annulus,286isometry: impl Into<Isometry2d>,287color: impl Into<Color>,288) -> Self::Output<'_> {289Annulus2dBuilder {290gizmos: self,291isometry: isometry.into(),292inner_radius: primitive.inner_circle.radius,293outer_radius: primitive.outer_circle.radius,294color: color.into(),295inner_resolution: crate::circles::DEFAULT_CIRCLE_RESOLUTION,296outer_resolution: crate::circles::DEFAULT_CIRCLE_RESOLUTION,297}298}299}300301impl<Config, Clear> Drop for Annulus2dBuilder<'_, Config, Clear>302where303Config: GizmoConfigGroup,304Clear: 'static + Send + Sync,305{306fn drop(&mut self) {307if !self.gizmos.enabled {308return;309}310311let Annulus2dBuilder {312gizmos,313isometry,314inner_radius,315outer_radius,316inner_resolution,317outer_resolution,318color,319..320} = self;321322gizmos323.circle_2d(*isometry, *outer_radius, *color)324.resolution(*outer_resolution);325gizmos326.circle_2d(*isometry, *inner_radius, *color)327.resolution(*inner_resolution);328}329}330331// rhombus 2d332333impl<Config, Clear> GizmoPrimitive2d<Rhombus> for GizmoBuffer<Config, Clear>334where335Config: GizmoConfigGroup,336Clear: 'static + Send + Sync,337{338type Output<'a>339= ()340where341Self: 'a;342343fn primitive_2d(344&mut self,345primitive: &Rhombus,346isometry: impl Into<Isometry2d>,347color: impl Into<Color>,348) -> Self::Output<'_> {349if !self.enabled {350return;351};352let isometry = isometry.into();353let [a, b, c, d] =354[(1.0, 0.0), (0.0, 1.0), (-1.0, 0.0), (0.0, -1.0)].map(|(sign_x, sign_y)| {355Vec2::new(356primitive.half_diagonals.x * sign_x,357primitive.half_diagonals.y * sign_y,358)359});360let positions = [a, b, c, d, a].map(|vec2| isometry * vec2);361self.linestrip_2d(positions, color);362}363}364365// capsule 2d366367impl<Config, Clear> GizmoPrimitive2d<Capsule2d> for GizmoBuffer<Config, Clear>368where369Config: GizmoConfigGroup,370Clear: 'static + Send + Sync,371{372type Output<'a>373= ()374where375Self: 'a;376377fn primitive_2d(378&mut self,379primitive: &Capsule2d,380isometry: impl Into<Isometry2d>,381color: impl Into<Color>,382) -> Self::Output<'_> {383let isometry = isometry.into();384let polymorphic_color: Color = color.into();385386if !self.enabled {387return;388}389390// transform points from the reference unit square to capsule "rectangle"391let [top_left, top_right, bottom_left, bottom_right, top_center, bottom_center] = [392[-1.0, 1.0],393[1.0, 1.0],394[-1.0, -1.0],395[1.0, -1.0],396// just reuse the pipeline for these points as well397[0.0, 1.0],398[0.0, -1.0],399]400.map(|[sign_x, sign_y]| Vec2::X * sign_x + Vec2::Y * sign_y)401.map(|reference_point| {402let scaling = Vec2::X * primitive.radius + Vec2::Y * primitive.half_length;403reference_point * scaling404})405.map(|vec2| isometry * vec2);406407// draw left and right side of capsule "rectangle"408self.line_2d(bottom_left, top_left, polymorphic_color);409self.line_2d(bottom_right, top_right, polymorphic_color);410411let start_angle_top = isometry.rotation.as_radians() - FRAC_PI_2;412let start_angle_bottom = isometry.rotation.as_radians() + FRAC_PI_2;413414// draw arcs415self.arc_2d(416Isometry2d::new(top_center, Rot2::radians(start_angle_top)),417PI,418primitive.radius,419polymorphic_color,420);421self.arc_2d(422Isometry2d::new(bottom_center, Rot2::radians(start_angle_bottom)),423PI,424primitive.radius,425polymorphic_color,426);427}428}429430// line 2d431//432/// Builder for configuring the drawing options of [`Line2d`].433pub struct Line2dBuilder<'a, Config, Clear>434where435Config: GizmoConfigGroup,436Clear: 'static + Send + Sync,437{438gizmos: &'a mut GizmoBuffer<Config, Clear>,439440direction: Dir2, // Direction of the line441442isometry: Isometry2d,443color: Color, // color of the line444445draw_arrow: bool, // decides whether to indicate the direction of the line with an arrow446}447448impl<Config, Clear> Line2dBuilder<'_, Config, Clear>449where450Config: GizmoConfigGroup,451Clear: 'static + Send + Sync,452{453/// Set the drawing mode of the line (arrow vs. plain line)454pub fn draw_arrow(mut self, is_enabled: bool) -> Self {455self.draw_arrow = is_enabled;456self457}458}459460impl<Config, Clear> GizmoPrimitive2d<Line2d> for GizmoBuffer<Config, Clear>461where462Config: GizmoConfigGroup,463Clear: 'static + Send + Sync,464{465type Output<'a>466= Line2dBuilder<'a, Config, Clear>467where468Self: 'a;469470fn primitive_2d(471&mut self,472primitive: &Line2d,473isometry: impl Into<Isometry2d>,474color: impl Into<Color>,475) -> Self::Output<'_> {476Line2dBuilder {477gizmos: self,478direction: primitive.direction,479isometry: isometry.into(),480color: color.into(),481draw_arrow: false,482}483}484}485486impl<Config, Clear> Drop for Line2dBuilder<'_, Config, Clear>487where488Config: GizmoConfigGroup,489Clear: 'static + Send + Sync,490{491fn drop(&mut self) {492if !self.gizmos.enabled {493return;494}495496let [start, end] = [1.0, -1.0]497.map(|sign| sign * INFINITE_LEN)498// offset the line from the origin infinitely into the given direction499.map(|length| self.direction * length)500// transform the line with the given isometry501.map(|offset| self.isometry * offset);502503self.gizmos.line_2d(start, end, self.color);504505// optionally draw an arrow head at the center of the line506if self.draw_arrow {507self.gizmos.arrow_2d(508self.isometry * (-self.direction * MIN_LINE_LEN),509self.isometry * Vec2::ZERO,510self.color,511);512}513}514}515516// plane 2d517518impl<Config, Clear> GizmoPrimitive2d<Plane2d> for GizmoBuffer<Config, Clear>519where520Config: GizmoConfigGroup,521Clear: 'static + Send + Sync,522{523type Output<'a>524= ()525where526Self: 'a;527528fn primitive_2d(529&mut self,530primitive: &Plane2d,531isometry: impl Into<Isometry2d>,532color: impl Into<Color>,533) -> Self::Output<'_> {534let isometry = isometry.into();535let polymorphic_color: Color = color.into();536537if !self.enabled {538return;539}540// draw normal of the plane (orthogonal to the plane itself)541let normal = primitive.normal;542let normal_segment = Segment2d::from_direction_and_length(normal, HALF_MIN_LINE_LEN * 2.);543self.primitive_2d(544&normal_segment,545// offset the normal so it starts on the plane line546Isometry2d::new(isometry * (HALF_MIN_LINE_LEN * normal), isometry.rotation),547polymorphic_color,548)549.draw_arrow(true);550551// draw the plane line552let direction = Dir2::new_unchecked(-normal.perp());553self.primitive_2d(&Line2d { direction }, isometry, polymorphic_color)554.draw_arrow(false);555556// draw an arrow such that the normal is always left side of the plane with respect to the557// planes direction. This is to follow the "counter-clockwise" convention558self.arrow_2d(559isometry * Vec2::ZERO,560isometry * (MIN_LINE_LEN * direction),561polymorphic_color,562);563}564}565566// segment 2d567568/// Builder for configuring the drawing options of [`Segment2d`].569pub struct Segment2dBuilder<'a, Config, Clear>570where571Config: GizmoConfigGroup,572Clear: 'static + Send + Sync,573{574gizmos: &'a mut GizmoBuffer<Config, Clear>,575576point1: Vec2, // First point of the segment577point2: Vec2, // Second point of the segment578579isometry: Isometry2d, // isometric transformation of the line segment580color: Color, // color of the line segment581582draw_arrow: bool, // decides whether to draw just a line or an arrow583}584585impl<Config, Clear> Segment2dBuilder<'_, Config, Clear>586where587Config: GizmoConfigGroup,588Clear: 'static + Send + Sync,589{590/// Set the drawing mode of the line (arrow vs. plain line)591pub fn draw_arrow(mut self, is_enabled: bool) -> Self {592self.draw_arrow = is_enabled;593self594}595}596597impl<Config, Clear> GizmoPrimitive2d<Segment2d> for GizmoBuffer<Config, Clear>598where599Config: GizmoConfigGroup,600Clear: 'static + Send + Sync,601{602type Output<'a>603= Segment2dBuilder<'a, Config, Clear>604where605Self: 'a;606607fn primitive_2d(608&mut self,609primitive: &Segment2d,610isometry: impl Into<Isometry2d>,611color: impl Into<Color>,612) -> Self::Output<'_> {613Segment2dBuilder {614gizmos: self,615point1: primitive.point1(),616point2: primitive.point2(),617618isometry: isometry.into(),619color: color.into(),620621draw_arrow: Default::default(),622}623}624}625626impl<Config, Clear> Drop for Segment2dBuilder<'_, Config, Clear>627where628Config: GizmoConfigGroup,629Clear: 'static + Send + Sync,630{631fn drop(&mut self) {632if !self.gizmos.enabled {633return;634}635636let segment = Segment2d::new(self.point1, self.point2).transformed(self.isometry);637638if self.draw_arrow {639self.gizmos640.arrow_2d(segment.point1(), segment.point2(), self.color);641} else {642self.gizmos643.line_2d(segment.point1(), segment.point2(), self.color);644}645}646}647648// polyline 2d649650impl<Config, Clear> GizmoPrimitive2d<Polyline2d> for GizmoBuffer<Config, Clear>651where652Config: GizmoConfigGroup,653Clear: 'static + Send + Sync,654{655type Output<'a>656= ()657where658Self: 'a;659660fn primitive_2d(661&mut self,662primitive: &Polyline2d,663isometry: impl Into<Isometry2d>,664color: impl Into<Color>,665) -> Self::Output<'_> {666if !self.enabled {667return;668}669670let isometry = isometry.into();671672self.linestrip_2d(673primitive674.vertices675.iter()676.copied()677.map(|vec2| isometry * vec2),678color,679);680}681}682683// triangle 2d684685impl<Config, Clear> GizmoPrimitive2d<Triangle2d> for GizmoBuffer<Config, Clear>686where687Config: GizmoConfigGroup,688Clear: 'static + Send + Sync,689{690type Output<'a>691= ()692where693Self: 'a;694695fn primitive_2d(696&mut self,697primitive: &Triangle2d,698isometry: impl Into<Isometry2d>,699color: impl Into<Color>,700) -> Self::Output<'_> {701if !self.enabled {702return;703}704705let isometry = isometry.into();706707let [a, b, c] = primitive.vertices;708let positions = [a, b, c, a].map(|vec2| isometry * vec2);709self.linestrip_2d(positions, color);710}711}712713// rectangle 2d714715impl<Config, Clear> GizmoPrimitive2d<Rectangle> for GizmoBuffer<Config, Clear>716where717Config: GizmoConfigGroup,718Clear: 'static + Send + Sync,719{720type Output<'a>721= ()722where723Self: 'a;724725fn primitive_2d(726&mut self,727primitive: &Rectangle,728isometry: impl Into<Isometry2d>,729color: impl Into<Color>,730) -> Self::Output<'_> {731if !self.enabled {732return;733}734735let isometry = isometry.into();736737let [a, b, c, d] =738[(1.0, 1.0), (1.0, -1.0), (-1.0, -1.0), (-1.0, 1.0)].map(|(sign_x, sign_y)| {739Vec2::new(740primitive.half_size.x * sign_x,741primitive.half_size.y * sign_y,742)743});744let positions = [a, b, c, d, a].map(|vec2| isometry * vec2);745self.linestrip_2d(positions, color);746}747}748749// polygon 2d750751impl<Config, Clear> GizmoPrimitive2d<Polygon> for GizmoBuffer<Config, Clear>752where753Config: GizmoConfigGroup,754Clear: 'static + Send + Sync,755{756type Output<'a>757= ()758where759Self: 'a;760761fn primitive_2d(762&mut self,763primitive: &Polygon,764isometry: impl Into<Isometry2d>,765color: impl Into<Color>,766) -> Self::Output<'_> {767if !self.enabled {768return;769}770771let isometry = isometry.into();772773// Check if the polygon needs a closing point774let closing_point = {775let first = primitive.vertices.first();776(primitive.vertices.last() != first)777.then_some(first)778.flatten()779.cloned()780};781782self.linestrip_2d(783primitive784.vertices785.iter()786.copied()787.chain(closing_point)788.map(|vec2| isometry * vec2),789color,790);791}792}793794// regular polygon 2d795796impl<Config, Clear> GizmoPrimitive2d<RegularPolygon> for GizmoBuffer<Config, Clear>797where798Config: GizmoConfigGroup,799Clear: 'static + Send + Sync,800{801type Output<'a>802= ()803where804Self: 'a;805806fn primitive_2d(807&mut self,808primitive: &RegularPolygon,809isometry: impl Into<Isometry2d>,810color: impl Into<Color>,811) -> Self::Output<'_> {812if !self.enabled {813return;814}815816let isometry = isometry.into();817818let points = (0..=primitive.sides)819.map(|n| single_circle_coordinate(primitive.circumcircle.radius, primitive.sides, n))820.map(|vec2| isometry * vec2);821self.linestrip_2d(points, color);822}823}824825826