5
5
6
6
use crate :: circles:: DEFAULT_CIRCLE_SEGMENTS ;
7
7
use crate :: prelude:: { GizmoConfigGroup , Gizmos } ;
8
- use bevy_math:: Vec2 ;
8
+ use bevy_math:: { Quat , Vec2 , Vec3 } ;
9
9
use bevy_render:: color:: Color ;
10
10
use std:: f32:: consts:: TAU ;
11
11
12
+ // === 2D ===
13
+
12
14
impl < ' w , ' s , T : GizmoConfigGroup > Gizmos < ' w , ' s , T > {
13
15
/// Draw an arc, which is a part of the circumference of a circle, in 2D.
14
16
///
@@ -73,7 +75,7 @@ pub struct Arc2dBuilder<'a, 'w, 's, T: GizmoConfigGroup> {
73
75
impl < T : GizmoConfigGroup > Arc2dBuilder < ' _ , ' _ , ' _ , T > {
74
76
/// Set the number of line-segments for this arc.
75
77
pub fn segments ( mut self , segments : usize ) -> Self {
76
- self . segments = Some ( segments) ;
78
+ self . segments . replace ( segments) ;
77
79
self
78
80
}
79
81
}
@@ -83,20 +85,18 @@ impl<T: GizmoConfigGroup> Drop for Arc2dBuilder<'_, '_, '_, T> {
83
85
if !self . gizmos . enabled {
84
86
return ;
85
87
}
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 ) ) ;
95
95
self . gizmos . linestrip_2d ( positions, self . color ) ;
96
96
}
97
97
}
98
98
99
- fn arc_inner (
99
+ fn arc_2d_inner (
100
100
direction_angle : f32 ,
101
101
arc_angle : f32 ,
102
102
radius : f32 ,
@@ -109,3 +109,282 @@ fn arc_inner(
109
109
Vec2 :: from ( angle. sin_cos ( ) ) * radius
110
110
} )
111
111
}
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