55
66use crate :: circles:: DEFAULT_CIRCLE_SEGMENTS ;
77use crate :: prelude:: { GizmoConfigGroup , Gizmos } ;
8- use bevy_math:: Vec2 ;
8+ use bevy_math:: { Quat , Vec2 , Vec3 } ;
99use bevy_render:: color:: Color ;
1010use std:: f32:: consts:: TAU ;
1111
12+ // === 2D ===
13+
1214impl < ' w , ' s , T : GizmoConfigGroup > Gizmos < ' w , ' s , T > {
1315 /// Draw an arc, which is a part of the circumference of a circle, in 2D.
1416 ///
@@ -73,7 +75,7 @@ pub struct Arc2dBuilder<'a, 'w, 's, T: GizmoConfigGroup> {
7375impl < T : GizmoConfigGroup > Arc2dBuilder < ' _ , ' _ , ' _ , T > {
7476 /// Set the number of line-segments for this arc.
7577 pub fn segments ( mut self , segments : usize ) -> Self {
76- self . segments = Some ( segments) ;
78+ self . segments . replace ( segments) ;
7779 self
7880 }
7981}
@@ -83,20 +85,18 @@ impl<T: GizmoConfigGroup> Drop for Arc2dBuilder<'_, '_, '_, T> {
8385 if !self . gizmos . enabled {
8486 return ;
8587 }
86- let segments = match self . segments {
87- Some ( segments) => segments,
88- // Do a linear interpolation between 1 and `DEFAULT_CIRCLE_SEGMENTS`
89- // using the arc angle as scalar.
90- None => ( ( self . arc_angle . abs ( ) / TAU ) * DEFAULT_CIRCLE_SEGMENTS as f32 ) . ceil ( ) as usize ,
91- } ;
92-
93- let positions = arc_inner ( self . direction_angle , self . arc_angle , self . radius , segments)
94- . map ( |vec2| vec2 + self . position ) ;
88+
89+ let segments = self
90+ . segments
91+ . unwrap_or_else ( || segments_from_angle ( self . arc_angle ) ) ;
92+
93+ let positions = arc_2d_inner ( self . direction_angle , self . arc_angle , self . radius , segments)
94+ . map ( |vec2| ( vec2 + self . position ) ) ;
9595 self . gizmos . linestrip_2d ( positions, self . color ) ;
9696 }
9797}
9898
99- fn arc_inner (
99+ fn arc_2d_inner (
100100 direction_angle : f32 ,
101101 arc_angle : f32 ,
102102 radius : f32 ,
@@ -109,3 +109,282 @@ fn arc_inner(
109109 Vec2 :: from ( angle. sin_cos ( ) ) * radius
110110 } )
111111}
112+
113+ // === 3D ===
114+
115+ impl < ' w , ' s , T : GizmoConfigGroup > Gizmos < ' w , ' s , T > {
116+ /// Draw an arc, which is a part of the circumference of a circle, in 3D. For default values
117+ /// this is drawing a standard arc. A standard arc is defined as
118+ ///
119+ /// - an arc with a center at `Vec3::ZERO`
120+ /// - starting at `Vec3::X`
121+ /// - embedded in the XZ plane
122+ /// - rotates counterclockwise
123+ ///
124+ /// This should be called for each frame the arc needs to be rendered.
125+ ///
126+ /// # Arguments
127+ /// - `angle`: sets how much of a circle circumference is passed, e.g. PI is half a circle. This
128+ /// value should be in the range (-2 * PI..=2 * PI)
129+ /// - `radius`: distance between the arc and it's center point
130+ /// - `position`: position of the arcs center point
131+ /// - `rotation`: defines orientation of the arc, by default we assume the arc is contained in a
132+ /// plane parallel to the XZ plane and the default starting point is (`position + Vec3::X`)
133+ /// - `color`: color of the arc
134+ ///
135+ /// # Builder methods
136+ /// The number of segments of the arc (i.e. the level of detail) can be adjusted with the
137+ /// `.segements(...)` method.
138+ ///
139+ /// # Example
140+ /// ```
141+ /// # use bevy_gizmos::prelude::*;
142+ /// # use bevy_render::prelude::*;
143+ /// # use bevy_math::prelude::*;
144+ /// # use std::f32::consts::PI;
145+ /// fn system(mut gizmos: Gizmos) {
146+ /// // rotation rotates normal to point in the direction of `Vec3::NEG_ONE`
147+ /// let rotation = Quat::from_rotation_arc(Vec3::Y, Vec3::NEG_ONE.normalize());
148+ ///
149+ /// gizmos
150+ /// .arc_3d(
151+ /// 270.0_f32.to_radians(),
152+ /// 0.25,
153+ /// Vec3::ONE,
154+ /// rotation,
155+ /// Color::ORANGE
156+ /// )
157+ /// .segments(100);
158+ /// }
159+ /// # bevy_ecs::system::assert_is_system(system);
160+ /// ```
161+ #[ inline]
162+ pub fn arc_3d (
163+ & mut self ,
164+ angle : f32 ,
165+ radius : f32 ,
166+ position : Vec3 ,
167+ rotation : Quat ,
168+ color : Color ,
169+ ) -> Arc3dBuilder < ' _ , ' w , ' s , T > {
170+ Arc3dBuilder {
171+ gizmos : self ,
172+ start_vertex : Vec3 :: X ,
173+ center : position,
174+ rotation,
175+ angle,
176+ radius,
177+ color,
178+ segments : None ,
179+ }
180+ }
181+
182+ /// Draws the shortest arc between two points (`from` and `to`) relative to a specified `center` point.
183+ ///
184+ /// # Arguments
185+ ///
186+ /// - `center`: The center point around which the arc is drawn.
187+ /// - `from`: The starting point of the arc.
188+ /// - `to`: The ending point of the arc.
189+ /// - `color`: color of the arc
190+ ///
191+ /// # Builder methods
192+ /// The number of segments of the arc (i.e. the level of detail) can be adjusted with the
193+ /// `.segements(...)` method.
194+ ///
195+ /// # Examples
196+ /// ```
197+ /// # use bevy_gizmos::prelude::*;
198+ /// # use bevy_render::prelude::*;
199+ /// # use bevy_math::prelude::*;
200+ /// fn system(mut gizmos: Gizmos) {
201+ /// gizmos.short_arc_3d_between(
202+ /// Vec3::ONE,
203+ /// Vec3::ONE + Vec3::NEG_ONE,
204+ /// Vec3::ZERO,
205+ /// Color::ORANGE
206+ /// )
207+ /// .segments(100);
208+ /// }
209+ /// # bevy_ecs::system::assert_is_system(system);
210+ /// ```
211+ ///
212+ /// # Notes
213+ /// - This method assumes that the points `from` and `to` are distinct from `center`. If one of
214+ /// the points is coincident with `center`, nothing is rendered.
215+ /// - The arc is drawn as a portion of a circle with a radius equal to the distance from the
216+ /// `center` to `from`. If the distance from `center` to `to` is not equal to the radius, then
217+ /// the results will behave as if this were the case
218+ #[ inline]
219+ pub fn short_arc_3d_between (
220+ & mut self ,
221+ center : Vec3 ,
222+ from : Vec3 ,
223+ to : Vec3 ,
224+ color : Color ,
225+ ) -> Arc3dBuilder < ' _ , ' w , ' s , T > {
226+ self . arc_from_to ( center, from, to, color, |x| x)
227+ }
228+
229+ /// Draws the longest arc between two points (`from` and `to`) relative to a specified `center` point.
230+ ///
231+ /// # Arguments
232+ /// - `center`: The center point around which the arc is drawn.
233+ /// - `from`: The starting point of the arc.
234+ /// - `to`: The ending point of the arc.
235+ /// - `color`: color of the arc
236+ ///
237+ /// # Builder methods
238+ /// The number of segments of the arc (i.e. the level of detail) can be adjusted with the
239+ /// `.segements(...)` method.
240+ ///
241+ /// # Examples
242+ /// ```
243+ /// # use bevy_gizmos::prelude::*;
244+ /// # use bevy_render::prelude::*;
245+ /// # use bevy_math::prelude::*;
246+ /// fn system(mut gizmos: Gizmos) {
247+ /// gizmos.long_arc_3d_between(
248+ /// Vec3::ONE,
249+ /// Vec3::ONE + Vec3::NEG_ONE,
250+ /// Vec3::ZERO,
251+ /// Color::ORANGE
252+ /// )
253+ /// .segments(100);
254+ /// }
255+ /// # bevy_ecs::system::assert_is_system(system);
256+ /// ```
257+ ///
258+ /// # Notes
259+ /// - This method assumes that the points `from` and `to` are distinct from `center`. If one of
260+ /// the points is coincident with `center`, nothing is rendered.
261+ /// - The arc is drawn as a portion of a circle with a radius equal to the distance from the
262+ /// `center` to `from`. If the distance from `center` to `to` is not equal to the radius, then
263+ /// the results will behave as if this were the case.
264+ #[ inline]
265+ pub fn long_arc_3d_between (
266+ & mut self ,
267+ center : Vec3 ,
268+ from : Vec3 ,
269+ to : Vec3 ,
270+ color : Color ,
271+ ) -> Arc3dBuilder < ' _ , ' w , ' s , T > {
272+ self . arc_from_to ( center, from, to, color, |angle| {
273+ if angle > 0.0 {
274+ TAU - angle
275+ } else if angle < 0.0 {
276+ -TAU - angle
277+ } else {
278+ 0.0
279+ }
280+ } )
281+ }
282+
283+ #[ inline]
284+ fn arc_from_to (
285+ & mut self ,
286+ center : Vec3 ,
287+ from : Vec3 ,
288+ to : Vec3 ,
289+ color : Color ,
290+ angle_fn : impl Fn ( f32 ) -> f32 ,
291+ ) -> Arc3dBuilder < ' _ , ' w , ' s , T > {
292+ // `from` and `to` can be the same here since in either case nothing gets rendered and the
293+ // orientation ambiguity of `up` doesn't matter
294+ let from_axis = ( from - center) . normalize_or_zero ( ) ;
295+ let to_axis = ( to - center) . normalize_or_zero ( ) ;
296+ let ( up, angle) = Quat :: from_rotation_arc ( from_axis, to_axis) . to_axis_angle ( ) ;
297+
298+ let angle = angle_fn ( angle) ;
299+ let radius = center. distance ( from) ;
300+ let rotation = Quat :: from_rotation_arc ( Vec3 :: Y , up) ;
301+
302+ let start_vertex = rotation. inverse ( ) * from_axis;
303+
304+ Arc3dBuilder {
305+ gizmos : self ,
306+ start_vertex,
307+ center,
308+ rotation,
309+ angle,
310+ radius,
311+ color,
312+ segments : None ,
313+ }
314+ }
315+ }
316+
317+ /// A builder returned by [`Gizmos::arc_2d`].
318+ pub struct Arc3dBuilder < ' a , ' w , ' s , T : GizmoConfigGroup > {
319+ gizmos : & ' a mut Gizmos < ' w , ' s , T > ,
320+ // this is the vertex the arc starts on in the XZ plane. For the normal arc_3d method this is
321+ // always starting at Vec3::X. For the short/long arc methods we actually need a way to start
322+ // at the from position and this is where this internal field comes into play. Some implicit
323+ // assumptions:
324+ //
325+ // 1. This is always in the XZ plane
326+ // 2. This is always normalized
327+ //
328+ // DO NOT expose this field to users as it is easy to mess this up
329+ start_vertex : Vec3 ,
330+ center : Vec3 ,
331+ rotation : Quat ,
332+ angle : f32 ,
333+ radius : f32 ,
334+ color : Color ,
335+ segments : Option < usize > ,
336+ }
337+
338+ impl < T : GizmoConfigGroup > Arc3dBuilder < ' _ , ' _ , ' _ , T > {
339+ /// Set the number of line-segments for this arc.
340+ pub fn segments ( mut self , segments : usize ) -> Self {
341+ self . segments . replace ( segments) ;
342+ self
343+ }
344+ }
345+
346+ impl < T : GizmoConfigGroup > Drop for Arc3dBuilder < ' _ , ' _ , ' _ , T > {
347+ fn drop ( & mut self ) {
348+ if !self . gizmos . enabled {
349+ return ;
350+ }
351+
352+ let segments = self
353+ . segments
354+ . unwrap_or_else ( || segments_from_angle ( self . angle ) ) ;
355+
356+ let positions = arc_3d_inner (
357+ self . start_vertex ,
358+ self . center ,
359+ self . rotation ,
360+ self . angle ,
361+ self . radius ,
362+ segments,
363+ ) ;
364+ self . gizmos . linestrip ( positions, self . color ) ;
365+ }
366+ }
367+
368+ fn arc_3d_inner (
369+ start_vertex : Vec3 ,
370+ center : Vec3 ,
371+ rotation : Quat ,
372+ angle : f32 ,
373+ radius : f32 ,
374+ segments : usize ,
375+ ) -> impl Iterator < Item = Vec3 > {
376+ // drawing arcs bigger than TAU degrees or smaller than -TAU degrees makes no sense since
377+ // we won't see the overlap and we would just decrease the level of details since the segments
378+ // would be larger
379+ let angle = angle. clamp ( -TAU , TAU ) ;
380+ ( 0 ..=segments)
381+ . map ( move |frac| frac as f32 / segments as f32 )
382+ . map ( move |percentage| angle * percentage)
383+ . map ( move |frac_angle| Quat :: from_axis_angle ( Vec3 :: Y , frac_angle) * start_vertex)
384+ . map ( move |p| rotation * ( p * radius) + center)
385+ }
386+
387+ // helper function for getting a default value for the segments parameter
388+ fn segments_from_angle ( angle : f32 ) -> usize {
389+ ( ( angle. abs ( ) / TAU ) * DEFAULT_CIRCLE_SEGMENTS as f32 ) . ceil ( ) as usize
390+ }
0 commit comments