bevy_gizmos/arcs.rs
1//! Additional [`GizmoBuffer`] Functions -- Arcs
2//!
3//! Includes the implementation of [`GizmoBuffer::arc_2d`],
4//! and assorted support items.
5
6use crate::{circles::DEFAULT_CIRCLE_RESOLUTION, gizmos::GizmoBuffer, prelude::GizmoConfigGroup};
7use bevy_color::Color;
8use bevy_math::{Isometry2d, Isometry3d, Quat, Rot2, Vec2, Vec3};
9use core::f32::consts::{FRAC_PI_2, TAU};
10
11// === 2D ===
12
13impl<Config, Clear> GizmoBuffer<Config, Clear>
14where
15 Config: GizmoConfigGroup,
16 Clear: 'static + Send + Sync,
17{
18 /// Draw an arc, which is a part of the circumference of a circle, in 2D.
19 ///
20 /// This should be called for each frame the arc needs to be rendered.
21 ///
22 /// # Arguments
23 /// - `isometry` defines the translation and rotation of the arc.
24 /// - the translation specifies the center of the arc
25 /// - the rotation is counter-clockwise starting from `Vec2::Y`
26 /// - `arc_angle` sets the length of this arc, in radians.
27 /// - `radius` controls the distance from `position` to this arc, and thus its curvature.
28 /// - `color` sets the color to draw the arc.
29 ///
30 /// # Example
31 /// ```
32 /// # use bevy_gizmos::prelude::*;
33 /// # use bevy_math::prelude::*;
34 /// # use std::f32::consts::FRAC_PI_4;
35 /// # use bevy_color::palettes::basic::{GREEN, RED};
36 /// fn system(mut gizmos: Gizmos) {
37 /// gizmos.arc_2d(Isometry2d::IDENTITY, FRAC_PI_4, 1., GREEN);
38 ///
39 /// // Arcs have 32 line-segments by default.
40 /// // You may want to increase this for larger arcs.
41 /// gizmos
42 /// .arc_2d(Isometry2d::IDENTITY, FRAC_PI_4, 5., RED)
43 /// .resolution(64);
44 /// }
45 /// # bevy_ecs::system::assert_is_system(system);
46 /// ```
47 #[inline]
48 pub fn arc_2d(
49 &mut self,
50 isometry: impl Into<Isometry2d>,
51 arc_angle: f32,
52 radius: f32,
53 color: impl Into<Color>,
54 ) -> Arc2dBuilder<'_, Config, Clear> {
55 Arc2dBuilder {
56 gizmos: self,
57 isometry: isometry.into(),
58 arc_angle,
59 radius,
60 color: color.into(),
61 resolution: None,
62 }
63 }
64}
65
66/// A builder returned by [`GizmoBuffer::arc_2d`].
67pub struct Arc2dBuilder<'a, Config, Clear>
68where
69 Config: GizmoConfigGroup,
70 Clear: 'static + Send + Sync,
71{
72 gizmos: &'a mut GizmoBuffer<Config, Clear>,
73 isometry: Isometry2d,
74 arc_angle: f32,
75 radius: f32,
76 color: Color,
77 resolution: Option<u32>,
78}
79
80impl<Config, Clear> Arc2dBuilder<'_, Config, Clear>
81where
82 Config: GizmoConfigGroup,
83 Clear: 'static + Send + Sync,
84{
85 /// Set the number of lines used to approximate the geometry of this arc.
86 pub fn resolution(mut self, resolution: u32) -> Self {
87 self.resolution.replace(resolution);
88 self
89 }
90}
91
92impl<Config, Clear> Drop for Arc2dBuilder<'_, Config, Clear>
93where
94 Config: GizmoConfigGroup,
95 Clear: 'static + Send + Sync,
96{
97 fn drop(&mut self) {
98 if !self.gizmos.enabled {
99 return;
100 }
101
102 let resolution = self
103 .resolution
104 .unwrap_or_else(|| resolution_from_angle(self.arc_angle));
105
106 let positions =
107 arc_2d_inner(self.arc_angle, self.radius, resolution).map(|vec2| self.isometry * vec2);
108 self.gizmos.linestrip_2d(positions, self.color);
109 }
110}
111
112fn arc_2d_inner(arc_angle: f32, radius: f32, resolution: u32) -> impl Iterator<Item = Vec2> {
113 (0..=resolution)
114 .map(move |n| arc_angle * n as f32 / resolution as f32)
115 .map(|angle| angle + FRAC_PI_2)
116 .map(Vec2::from_angle)
117 .map(move |vec2| vec2 * radius)
118}
119
120// === 3D ===
121
122impl<Config, Clear> GizmoBuffer<Config, Clear>
123where
124 Config: GizmoConfigGroup,
125 Clear: 'static + Send + Sync,
126{
127 /// Draw an arc, which is a part of the circumference of a circle, in 3D. For default values
128 /// this is drawing a standard arc. A standard arc is defined as
129 ///
130 /// - an arc with a center at `Vec3::ZERO`
131 /// - starting at `Vec3::X`
132 /// - embedded in the XZ plane
133 /// - rotates counterclockwise
134 ///
135 /// This should be called for each frame the arc needs to be rendered.
136 ///
137 /// # Arguments
138 /// - `angle`: sets how much of a circle circumference is passed, e.g. PI is half a circle. This
139 /// value should be in the range (-2 * PI..=2 * PI)
140 /// - `radius`: distance between the arc and its center point
141 /// - `isometry` defines the translation and rotation of the arc.
142 /// - the translation specifies the center of the arc
143 /// - the rotation is counter-clockwise starting from `Vec3::Y`
144 /// - `color`: color of the arc
145 ///
146 /// # Builder methods
147 /// The resolution of the arc (i.e. the level of detail) can be adjusted with the
148 /// `.resolution(...)` method.
149 ///
150 /// # Example
151 /// ```
152 /// # use bevy_gizmos::prelude::*;
153 /// # use bevy_math::prelude::*;
154 /// # use std::f32::consts::PI;
155 /// # use bevy_color::palettes::css::ORANGE;
156 /// fn system(mut gizmos: Gizmos) {
157 /// // rotation rotates normal to point in the direction of `Vec3::NEG_ONE`
158 /// let rotation = Quat::from_rotation_arc(Vec3::Y, Vec3::NEG_ONE.normalize());
159 ///
160 /// gizmos
161 /// .arc_3d(
162 /// 270.0_f32.to_radians(),
163 /// 0.25,
164 /// Isometry3d::new(Vec3::ONE, rotation),
165 /// ORANGE
166 /// )
167 /// .resolution(100);
168 /// }
169 /// # bevy_ecs::system::assert_is_system(system);
170 /// ```
171 #[inline]
172 pub fn arc_3d(
173 &mut self,
174 angle: f32,
175 radius: f32,
176 isometry: impl Into<Isometry3d>,
177 color: impl Into<Color>,
178 ) -> Arc3dBuilder<'_, Config, Clear> {
179 Arc3dBuilder {
180 gizmos: self,
181 start_vertex: Vec3::X,
182 isometry: isometry.into(),
183 angle,
184 radius,
185 color: color.into(),
186 resolution: None,
187 }
188 }
189
190 /// Draws the shortest arc between two points (`from` and `to`) relative to a specified `center` point.
191 ///
192 /// # Arguments
193 ///
194 /// - `center`: The center point around which the arc is drawn.
195 /// - `from`: The starting point of the arc.
196 /// - `to`: The ending point of the arc.
197 /// - `color`: color of the arc
198 ///
199 /// # Builder methods
200 /// The resolution of the arc (i.e. the level of detail) can be adjusted with the
201 /// `.resolution(...)` method.
202 ///
203 /// # Examples
204 /// ```
205 /// # use bevy_gizmos::prelude::*;
206 /// # use bevy_math::prelude::*;
207 /// # use bevy_color::palettes::css::ORANGE;
208 /// fn system(mut gizmos: Gizmos) {
209 /// gizmos.short_arc_3d_between(
210 /// Vec3::ONE,
211 /// Vec3::ONE + Vec3::NEG_ONE,
212 /// Vec3::ZERO,
213 /// ORANGE
214 /// )
215 /// .resolution(100);
216 /// }
217 /// # bevy_ecs::system::assert_is_system(system);
218 /// ```
219 ///
220 /// # Notes
221 /// - This method assumes that the points `from` and `to` are distinct from `center`. If one of
222 /// the points is coincident with `center`, nothing is rendered.
223 /// - The arc is drawn as a portion of a circle with a radius equal to the distance from the
224 /// `center` to `from`. If the distance from `center` to `to` is not equal to the radius, then
225 /// the results will behave as if this were the case
226 #[inline]
227 pub fn short_arc_3d_between(
228 &mut self,
229 center: Vec3,
230 from: Vec3,
231 to: Vec3,
232 color: impl Into<Color>,
233 ) -> Arc3dBuilder<'_, Config, Clear> {
234 self.arc_from_to(center, from, to, color, |x| x)
235 }
236
237 /// Draws the longest arc between two points (`from` and `to`) relative to a specified `center` point.
238 ///
239 /// # Arguments
240 /// - `center`: The center point around which the arc is drawn.
241 /// - `from`: The starting point of the arc.
242 /// - `to`: The ending point of the arc.
243 /// - `color`: color of the arc
244 ///
245 /// # Builder methods
246 /// The resolution of the arc (i.e. the level of detail) can be adjusted with the
247 /// `.resolution(...)` method.
248 ///
249 /// # Examples
250 /// ```
251 /// # use bevy_gizmos::prelude::*;
252 /// # use bevy_math::prelude::*;
253 /// # use bevy_color::palettes::css::ORANGE;
254 /// fn system(mut gizmos: Gizmos) {
255 /// gizmos.long_arc_3d_between(
256 /// Vec3::ONE,
257 /// Vec3::ONE + Vec3::NEG_ONE,
258 /// Vec3::ZERO,
259 /// ORANGE
260 /// )
261 /// .resolution(100);
262 /// }
263 /// # bevy_ecs::system::assert_is_system(system);
264 /// ```
265 ///
266 /// # Notes
267 /// - This method assumes that the points `from` and `to` are distinct from `center`. If one of
268 /// the points is coincident with `center`, nothing is rendered.
269 /// - The arc is drawn as a portion of a circle with a radius equal to the distance from the
270 /// `center` to `from`. If the distance from `center` to `to` is not equal to the radius, then
271 /// the results will behave as if this were the case.
272 #[inline]
273 pub fn long_arc_3d_between(
274 &mut self,
275 center: Vec3,
276 from: Vec3,
277 to: Vec3,
278 color: impl Into<Color>,
279 ) -> Arc3dBuilder<'_, Config, Clear> {
280 self.arc_from_to(center, from, to, color, |angle| {
281 if angle > 0.0 {
282 TAU - angle
283 } else if angle < 0.0 {
284 -TAU - angle
285 } else {
286 0.0
287 }
288 })
289 }
290
291 #[inline]
292 fn arc_from_to(
293 &mut self,
294 center: Vec3,
295 from: Vec3,
296 to: Vec3,
297 color: impl Into<Color>,
298 angle_fn: impl Fn(f32) -> f32,
299 ) -> Arc3dBuilder<'_, Config, Clear> {
300 // `from` and `to` can be the same here since in either case nothing gets rendered and the
301 // orientation ambiguity of `up` doesn't matter
302 let from_axis = (from - center).normalize_or_zero();
303 let to_axis = (to - center).normalize_or_zero();
304 let (up, angle) = Quat::from_rotation_arc(from_axis, to_axis).to_axis_angle();
305
306 let angle = angle_fn(angle);
307 let radius = center.distance(from);
308 let rotation = Quat::from_rotation_arc(Vec3::Y, up);
309
310 let start_vertex = rotation.inverse() * from_axis;
311
312 Arc3dBuilder {
313 gizmos: self,
314 start_vertex,
315 isometry: Isometry3d::new(center, rotation),
316 angle,
317 radius,
318 color: color.into(),
319 resolution: None,
320 }
321 }
322
323 /// Draws the shortest arc between two points (`from` and `to`) relative to a specified `center` point.
324 ///
325 /// # Arguments
326 ///
327 /// - `center`: The center point around which the arc is drawn.
328 /// - `from`: The starting point of the arc.
329 /// - `to`: The ending point of the arc.
330 /// - `color`: color of the arc
331 ///
332 /// # Builder methods
333 /// The resolution of the arc (i.e. the level of detail) can be adjusted with the
334 /// `.resolution(...)` method.
335 ///
336 /// # Examples
337 /// ```
338 /// # use bevy_gizmos::prelude::*;
339 /// # use bevy_math::prelude::*;
340 /// # use bevy_color::palettes::css::ORANGE;
341 /// fn system(mut gizmos: Gizmos) {
342 /// gizmos.short_arc_2d_between(
343 /// Vec2::ZERO,
344 /// Vec2::X,
345 /// Vec2::Y,
346 /// ORANGE
347 /// )
348 /// .resolution(100);
349 /// }
350 /// # bevy_ecs::system::assert_is_system(system);
351 /// ```
352 ///
353 /// # Notes
354 /// - This method assumes that the points `from` and `to` are distinct from `center`. If one of
355 /// the points is coincident with `center`, nothing is rendered.
356 /// - The arc is drawn as a portion of a circle with a radius equal to the distance from the
357 /// `center` to `from`. If the distance from `center` to `to` is not equal to the radius, then
358 /// the results will behave as if this were the case
359 #[inline]
360 pub fn short_arc_2d_between(
361 &mut self,
362 center: Vec2,
363 from: Vec2,
364 to: Vec2,
365 color: impl Into<Color>,
366 ) -> Arc2dBuilder<'_, Config, Clear> {
367 self.arc_2d_from_to(center, from, to, color, core::convert::identity)
368 }
369
370 /// Draws the longest arc between two points (`from` and `to`) relative to a specified `center` point.
371 ///
372 /// # Arguments
373 /// - `center`: The center point around which the arc is drawn.
374 /// - `from`: The starting point of the arc.
375 /// - `to`: The ending point of the arc.
376 /// - `color`: color of the arc
377 ///
378 /// # Builder methods
379 /// The resolution of the arc (i.e. the level of detail) can be adjusted with the
380 /// `.resolution(...)` method.
381 ///
382 /// # Examples
383 /// ```
384 /// # use bevy_gizmos::prelude::*;
385 /// # use bevy_math::prelude::*;
386 /// # use bevy_color::palettes::css::ORANGE;
387 /// fn system(mut gizmos: Gizmos) {
388 /// gizmos.long_arc_2d_between(
389 /// Vec2::ZERO,
390 /// Vec2::X,
391 /// Vec2::Y,
392 /// ORANGE
393 /// )
394 /// .resolution(100);
395 /// }
396 /// # bevy_ecs::system::assert_is_system(system);
397 /// ```
398 ///
399 /// # Notes
400 /// - This method assumes that the points `from` and `to` are distinct from `center`. If one of
401 /// the points is coincident with `center`, nothing is rendered.
402 /// - The arc is drawn as a portion of a circle with a radius equal to the distance from the
403 /// `center` to `from`. If the distance from `center` to `to` is not equal to the radius, then
404 /// the results will behave as if this were the case.
405 #[inline]
406 pub fn long_arc_2d_between(
407 &mut self,
408 center: Vec2,
409 from: Vec2,
410 to: Vec2,
411 color: impl Into<Color>,
412 ) -> Arc2dBuilder<'_, Config, Clear> {
413 self.arc_2d_from_to(center, from, to, color, |angle| angle - TAU)
414 }
415
416 #[inline]
417 fn arc_2d_from_to(
418 &mut self,
419 center: Vec2,
420 from: Vec2,
421 to: Vec2,
422 color: impl Into<Color>,
423 angle_fn: impl Fn(f32) -> f32,
424 ) -> Arc2dBuilder<'_, Config, Clear> {
425 // `from` and `to` can be the same here since in either case nothing gets rendered and the
426 // orientation ambiguity of `up` doesn't matter
427 let from_axis = (from - center).normalize_or_zero();
428 let to_axis = (to - center).normalize_or_zero();
429 let rotation = Vec2::Y.angle_to(from_axis);
430 let arc_angle_raw = from_axis.angle_to(to_axis);
431
432 let arc_angle = angle_fn(arc_angle_raw);
433 let radius = center.distance(from);
434
435 Arc2dBuilder {
436 gizmos: self,
437 isometry: Isometry2d::new(center, Rot2::radians(rotation)),
438 arc_angle,
439 radius,
440 color: color.into(),
441 resolution: None,
442 }
443 }
444}
445
446/// A builder returned by [`GizmoBuffer::arc_2d`].
447pub struct Arc3dBuilder<'a, Config, Clear>
448where
449 Config: GizmoConfigGroup,
450 Clear: 'static + Send + Sync,
451{
452 gizmos: &'a mut GizmoBuffer<Config, Clear>,
453 // this is the vertex the arc starts on in the XZ plane. For the normal arc_3d method this is
454 // always starting at Vec3::X. For the short/long arc methods we actually need a way to start
455 // at the from position and this is where this internal field comes into play. Some implicit
456 // assumptions:
457 //
458 // 1. This is always in the XZ plane
459 // 2. This is always normalized
460 //
461 // DO NOT expose this field to users as it is easy to mess this up
462 start_vertex: Vec3,
463 isometry: Isometry3d,
464 angle: f32,
465 radius: f32,
466 color: Color,
467 resolution: Option<u32>,
468}
469
470impl<Config, Clear> Arc3dBuilder<'_, Config, Clear>
471where
472 Config: GizmoConfigGroup,
473 Clear: 'static + Send + Sync,
474{
475 /// Set the number of lines for this arc.
476 pub fn resolution(mut self, resolution: u32) -> Self {
477 self.resolution.replace(resolution);
478 self
479 }
480}
481
482impl<Config, Clear> Drop for Arc3dBuilder<'_, Config, Clear>
483where
484 Config: GizmoConfigGroup,
485 Clear: 'static + Send + Sync,
486{
487 fn drop(&mut self) {
488 if !self.gizmos.enabled {
489 return;
490 }
491
492 let resolution = self
493 .resolution
494 .unwrap_or_else(|| resolution_from_angle(self.angle));
495
496 let positions = arc_3d_inner(
497 self.start_vertex,
498 self.isometry,
499 self.angle,
500 self.radius,
501 resolution,
502 );
503 self.gizmos.linestrip(positions, self.color);
504 }
505}
506
507fn arc_3d_inner(
508 start_vertex: Vec3,
509 isometry: Isometry3d,
510 angle: f32,
511 radius: f32,
512 resolution: u32,
513) -> impl Iterator<Item = Vec3> {
514 // drawing arcs bigger than TAU degrees or smaller than -TAU degrees makes no sense since
515 // we won't see the overlap and we would just decrease the level of details since the resolution
516 // would be larger
517 let angle = angle.clamp(-TAU, TAU);
518 (0..=resolution)
519 .map(move |frac| frac as f32 / resolution as f32)
520 .map(move |percentage| angle * percentage)
521 .map(move |frac_angle| Quat::from_axis_angle(Vec3::Y, frac_angle) * start_vertex)
522 .map(move |vec3| vec3 * radius)
523 .map(move |vec3| isometry * vec3)
524}
525
526// helper function for getting a default value for the resolution parameter
527fn resolution_from_angle(angle: f32) -> u32 {
528 ((angle.abs() / TAU) * DEFAULT_CIRCLE_RESOLUTION as f32).ceil() as u32
529}