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