Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_gizmos/src/arcs.rs
6595 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
/// 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`].
67
pub struct Arc2dBuilder<'a, Config, Clear>
68
where
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
80
impl<Config, Clear> Arc2dBuilder<'_, Config, Clear>
81
where
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
92
impl<Config, Clear> Drop for Arc2dBuilder<'_, Config, Clear>
93
where
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
112
fn 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
122
impl<Config, Clear> GizmoBuffer<Config, Clear>
123
where
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`].
447
pub struct Arc3dBuilder<'a, Config, Clear>
448
where
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
470
impl<Config, Clear> Arc3dBuilder<'_, Config, Clear>
471
where
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
482
impl<Config, Clear> Drop for Arc3dBuilder<'_, Config, Clear>
483
where
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
507
fn 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
527
fn resolution_from_angle(angle: f32) -> u32 {
528
((angle.abs() / TAU) * DEFAULT_CIRCLE_RESOLUTION as f32).ceil() as u32
529
}
530
531