Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_math/src/primitives/dim2.rs
6596 views
1
use core::f32::consts::{FRAC_1_SQRT_2, FRAC_PI_2, FRAC_PI_3, PI};
2
use derive_more::derive::From;
3
#[cfg(feature = "alloc")]
4
use thiserror::Error;
5
6
use super::{Measured2d, Primitive2d, WindingOrder};
7
use crate::{
8
ops::{self, FloatPow},
9
Dir2, InvalidDirectionError, Isometry2d, Ray2d, Rot2, Vec2,
10
};
11
12
#[cfg(feature = "alloc")]
13
use super::polygon::is_polygon_simple;
14
15
#[cfg(feature = "bevy_reflect")]
16
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
17
#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
18
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
19
20
#[cfg(feature = "alloc")]
21
use alloc::vec::Vec;
22
23
/// A circle primitive, representing the set of points some distance from the origin
24
#[derive(Clone, Copy, Debug, PartialEq)]
25
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
26
#[cfg_attr(
27
feature = "bevy_reflect",
28
derive(Reflect),
29
reflect(Debug, PartialEq, Default, Clone)
30
)]
31
#[cfg_attr(
32
all(feature = "serialize", feature = "bevy_reflect"),
33
reflect(Serialize, Deserialize)
34
)]
35
pub struct Circle {
36
/// The radius of the circle
37
pub radius: f32,
38
}
39
40
impl Primitive2d for Circle {}
41
42
impl Default for Circle {
43
/// Returns the default [`Circle`] with a radius of `0.5`.
44
fn default() -> Self {
45
Self { radius: 0.5 }
46
}
47
}
48
49
impl Circle {
50
/// Create a new [`Circle`] from a `radius`
51
#[inline(always)]
52
pub const fn new(radius: f32) -> Self {
53
Self { radius }
54
}
55
56
/// Get the diameter of the circle
57
#[inline(always)]
58
pub const fn diameter(&self) -> f32 {
59
2.0 * self.radius
60
}
61
62
/// Finds the point on the circle that is closest to the given `point`.
63
///
64
/// If the point is outside the circle, the returned point will be on the perimeter of the circle.
65
/// Otherwise, it will be inside the circle and returned as is.
66
#[inline(always)]
67
pub fn closest_point(&self, point: Vec2) -> Vec2 {
68
let distance_squared = point.length_squared();
69
70
if distance_squared <= self.radius.squared() {
71
// The point is inside the circle.
72
point
73
} else {
74
// The point is outside the circle.
75
// Find the closest point on the perimeter of the circle.
76
let dir_to_point = point / ops::sqrt(distance_squared);
77
self.radius * dir_to_point
78
}
79
}
80
}
81
82
impl Measured2d for Circle {
83
/// Get the area of the circle
84
#[inline(always)]
85
fn area(&self) -> f32 {
86
PI * self.radius.squared()
87
}
88
89
/// Get the perimeter or circumference of the circle
90
#[inline(always)]
91
#[doc(alias = "circumference")]
92
fn perimeter(&self) -> f32 {
93
2.0 * PI * self.radius
94
}
95
}
96
97
/// A primitive representing an arc between two points on a circle.
98
///
99
/// An arc has no area.
100
/// If you want to include the portion of a circle's area swept out by the arc,
101
/// use the pie-shaped [`CircularSector`].
102
/// If you want to include only the space inside the convex hull of the arc,
103
/// use the bowl-shaped [`CircularSegment`].
104
///
105
/// The arc is drawn starting from [`Vec2::Y`], extending by `half_angle` radians on
106
/// either side. The center of the circle is the origin [`Vec2::ZERO`]. Note that this
107
/// means that the origin may not be within the `Arc2d`'s convex hull.
108
///
109
/// **Warning:** Arcs with negative angle or radius, or with angle greater than an entire circle, are not officially supported.
110
/// It is recommended to normalize arcs to have an angle in [0, 2Ï€].
111
#[derive(Clone, Copy, Debug, PartialEq)]
112
#[doc(alias("CircularArc", "CircleArc"))]
113
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
114
#[cfg_attr(
115
feature = "bevy_reflect",
116
derive(Reflect),
117
reflect(Debug, PartialEq, Default, Clone)
118
)]
119
#[cfg_attr(
120
all(feature = "serialize", feature = "bevy_reflect"),
121
reflect(Serialize, Deserialize)
122
)]
123
pub struct Arc2d {
124
/// The radius of the circle
125
pub radius: f32,
126
/// Half the angle defining the arc
127
pub half_angle: f32,
128
}
129
130
impl Primitive2d for Arc2d {}
131
132
impl Default for Arc2d {
133
/// Returns the default [`Arc2d`] with radius `0.5`, covering one third of a circle
134
fn default() -> Self {
135
Self {
136
radius: 0.5,
137
half_angle: 2.0 * FRAC_PI_3,
138
}
139
}
140
}
141
142
impl Arc2d {
143
/// Create a new [`Arc2d`] from a `radius` and a `half_angle`
144
#[inline(always)]
145
pub const fn new(radius: f32, half_angle: f32) -> Self {
146
Self { radius, half_angle }
147
}
148
149
/// Create a new [`Arc2d`] from a `radius` and an `angle` in radians
150
#[inline(always)]
151
pub const fn from_radians(radius: f32, angle: f32) -> Self {
152
Self {
153
radius,
154
half_angle: angle / 2.0,
155
}
156
}
157
158
/// Create a new [`Arc2d`] from a `radius` and an `angle` in degrees.
159
#[inline(always)]
160
pub const fn from_degrees(radius: f32, angle: f32) -> Self {
161
Self {
162
radius,
163
half_angle: angle.to_radians() / 2.0,
164
}
165
}
166
167
/// Create a new [`Arc2d`] from a `radius` and a `fraction` of a single turn.
168
///
169
/// For instance, `0.5` turns is a semicircle.
170
#[inline(always)]
171
pub const fn from_turns(radius: f32, fraction: f32) -> Self {
172
Self {
173
radius,
174
half_angle: fraction * PI,
175
}
176
}
177
178
/// Get the angle of the arc
179
#[inline(always)]
180
pub const fn angle(&self) -> f32 {
181
self.half_angle * 2.0
182
}
183
184
/// Get the length of the arc
185
#[inline(always)]
186
pub const fn length(&self) -> f32 {
187
self.angle() * self.radius
188
}
189
190
/// Get the right-hand end point of the arc
191
#[inline(always)]
192
pub fn right_endpoint(&self) -> Vec2 {
193
self.radius * Vec2::from_angle(FRAC_PI_2 - self.half_angle)
194
}
195
196
/// Get the left-hand end point of the arc
197
#[inline(always)]
198
pub fn left_endpoint(&self) -> Vec2 {
199
self.radius * Vec2::from_angle(FRAC_PI_2 + self.half_angle)
200
}
201
202
/// Get the endpoints of the arc
203
#[inline(always)]
204
pub fn endpoints(&self) -> [Vec2; 2] {
205
[self.left_endpoint(), self.right_endpoint()]
206
}
207
208
/// Get the midpoint of the arc
209
#[inline]
210
pub fn midpoint(&self) -> Vec2 {
211
self.radius * Vec2::Y
212
}
213
214
/// Get half the distance between the endpoints (half the length of the chord)
215
#[inline(always)]
216
pub fn half_chord_length(&self) -> f32 {
217
self.radius * ops::sin(self.half_angle)
218
}
219
220
/// Get the distance between the endpoints (the length of the chord)
221
#[inline(always)]
222
pub fn chord_length(&self) -> f32 {
223
2.0 * self.half_chord_length()
224
}
225
226
/// Get the midpoint of the two endpoints (the midpoint of the chord)
227
#[inline(always)]
228
pub fn chord_midpoint(&self) -> Vec2 {
229
self.apothem() * Vec2::Y
230
}
231
232
/// Get the length of the apothem of this arc, that is,
233
/// the distance from the center of the circle to the midpoint of the chord, in the direction of the midpoint of the arc.
234
/// Equivalently, the [`radius`](Self::radius) minus the [`sagitta`](Self::sagitta).
235
///
236
/// Note that for a [`major`](Self::is_major) arc, the apothem will be negative.
237
#[inline(always)]
238
// Naming note: Various sources are inconsistent as to whether the apothem is the segment between the center and the
239
// midpoint of a chord, or the length of that segment. Given this confusion, we've opted for the definition
240
// used by Wolfram MathWorld, which is the distance rather than the segment.
241
pub fn apothem(&self) -> f32 {
242
let sign = if self.is_minor() { 1.0 } else { -1.0 };
243
sign * ops::sqrt(self.radius.squared() - self.half_chord_length().squared())
244
}
245
246
/// Get the length of the sagitta of this arc, that is,
247
/// the length of the line between the midpoints of the arc and its chord.
248
/// Equivalently, the height of the triangle whose base is the chord and whose apex is the midpoint of the arc.
249
///
250
/// The sagitta is also the sum of the [`radius`](Self::radius) and the [`apothem`](Self::apothem).
251
pub fn sagitta(&self) -> f32 {
252
self.radius - self.apothem()
253
}
254
255
/// Produces true if the arc is at most half a circle.
256
///
257
/// **Note:** This is not the negation of [`is_major`](Self::is_major): an exact semicircle is both major and minor.
258
#[inline(always)]
259
pub const fn is_minor(&self) -> bool {
260
self.half_angle <= FRAC_PI_2
261
}
262
263
/// Produces true if the arc is at least half a circle.
264
///
265
/// **Note:** This is not the negation of [`is_minor`](Self::is_minor): an exact semicircle is both major and minor.
266
#[inline(always)]
267
pub const fn is_major(&self) -> bool {
268
self.half_angle >= FRAC_PI_2
269
}
270
}
271
272
/// A primitive representing a circular sector: a pie slice of a circle.
273
///
274
/// The segment is positioned so that it always includes [`Vec2::Y`] and is vertically symmetrical.
275
/// To orient the sector differently, apply a rotation.
276
/// The sector is drawn with the center of its circle at the origin [`Vec2::ZERO`].
277
///
278
/// **Warning:** Circular sectors with negative angle or radius, or with angle greater than an entire circle, are not officially supported.
279
/// We recommend normalizing circular sectors to have an angle in [0, 2Ï€].
280
#[derive(Clone, Copy, Debug, PartialEq, From)]
281
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
282
#[cfg_attr(
283
feature = "bevy_reflect",
284
derive(Reflect),
285
reflect(Debug, PartialEq, Default, Clone)
286
)]
287
#[cfg_attr(
288
all(feature = "serialize", feature = "bevy_reflect"),
289
reflect(Serialize, Deserialize)
290
)]
291
pub struct CircularSector {
292
/// The arc defining the sector
293
#[cfg_attr(all(feature = "serialize", feature = "alloc"), serde(flatten))]
294
pub arc: Arc2d,
295
}
296
297
impl Primitive2d for CircularSector {}
298
299
impl Default for CircularSector {
300
/// Returns the default [`CircularSector`] with radius `0.5` and covering a third of a circle
301
fn default() -> Self {
302
Self::from(Arc2d::default())
303
}
304
}
305
306
impl Measured2d for CircularSector {
307
#[inline(always)]
308
fn area(&self) -> f32 {
309
self.arc.radius.squared() * self.arc.half_angle
310
}
311
312
#[inline(always)]
313
fn perimeter(&self) -> f32 {
314
if self.half_angle() >= PI {
315
self.arc.radius * 2.0 * PI
316
} else {
317
2.0 * self.radius() + self.arc_length()
318
}
319
}
320
}
321
322
impl CircularSector {
323
/// Create a new [`CircularSector`] from a `radius` and an `angle`
324
#[inline(always)]
325
pub const fn new(radius: f32, angle: f32) -> Self {
326
Self {
327
arc: Arc2d::new(radius, angle),
328
}
329
}
330
331
/// Create a new [`CircularSector`] from a `radius` and an `angle` in radians.
332
#[inline(always)]
333
pub const fn from_radians(radius: f32, angle: f32) -> Self {
334
Self {
335
arc: Arc2d::from_radians(radius, angle),
336
}
337
}
338
339
/// Create a new [`CircularSector`] from a `radius` and an `angle` in degrees.
340
#[inline(always)]
341
pub const fn from_degrees(radius: f32, angle: f32) -> Self {
342
Self {
343
arc: Arc2d::from_degrees(radius, angle),
344
}
345
}
346
347
/// Create a new [`CircularSector`] from a `radius` and a number of `turns` of a circle.
348
///
349
/// For instance, `0.5` turns is a semicircle.
350
#[inline(always)]
351
pub const fn from_turns(radius: f32, fraction: f32) -> Self {
352
Self {
353
arc: Arc2d::from_turns(radius, fraction),
354
}
355
}
356
357
/// Get half the angle of the sector
358
#[inline(always)]
359
pub const fn half_angle(&self) -> f32 {
360
self.arc.half_angle
361
}
362
363
/// Get the angle of the sector
364
#[inline(always)]
365
pub const fn angle(&self) -> f32 {
366
self.arc.angle()
367
}
368
369
/// Get the radius of the sector
370
#[inline(always)]
371
pub const fn radius(&self) -> f32 {
372
self.arc.radius
373
}
374
375
/// Get the length of the arc defining the sector
376
#[inline(always)]
377
pub const fn arc_length(&self) -> f32 {
378
self.arc.length()
379
}
380
381
/// Get half the length of the chord defined by the sector
382
///
383
/// See [`Arc2d::half_chord_length`]
384
#[inline(always)]
385
pub fn half_chord_length(&self) -> f32 {
386
self.arc.half_chord_length()
387
}
388
389
/// Get the length of the chord defined by the sector
390
///
391
/// See [`Arc2d::chord_length`]
392
#[inline(always)]
393
pub fn chord_length(&self) -> f32 {
394
self.arc.chord_length()
395
}
396
397
/// Get the midpoint of the chord defined by the sector
398
///
399
/// See [`Arc2d::chord_midpoint`]
400
#[inline(always)]
401
pub fn chord_midpoint(&self) -> Vec2 {
402
self.arc.chord_midpoint()
403
}
404
405
/// Get the length of the apothem of this sector
406
///
407
/// See [`Arc2d::apothem`]
408
#[inline(always)]
409
pub fn apothem(&self) -> f32 {
410
self.arc.apothem()
411
}
412
413
/// Get the length of the sagitta of this sector
414
///
415
/// See [`Arc2d::sagitta`]
416
#[inline(always)]
417
pub fn sagitta(&self) -> f32 {
418
self.arc.sagitta()
419
}
420
}
421
422
/// A primitive representing a circular segment:
423
/// the area enclosed by the arc of a circle and its chord (the line between its endpoints).
424
///
425
/// The segment is drawn starting from [`Vec2::Y`], extending equally on either side.
426
/// To orient the segment differently, apply a rotation.
427
/// The segment is drawn with the center of its circle at the origin [`Vec2::ZERO`].
428
/// When positioning a segment, the [`apothem`](Self::apothem) function may be particularly useful.
429
///
430
/// **Warning:** Circular segments with negative angle or radius, or with angle greater than an entire circle, are not officially supported.
431
/// We recommend normalizing circular segments to have an angle in [0, 2Ï€].
432
#[derive(Clone, Copy, Debug, PartialEq, From)]
433
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
434
#[cfg_attr(
435
feature = "bevy_reflect",
436
derive(Reflect),
437
reflect(Debug, PartialEq, Default, Clone)
438
)]
439
#[cfg_attr(
440
all(feature = "serialize", feature = "bevy_reflect"),
441
reflect(Serialize, Deserialize)
442
)]
443
pub struct CircularSegment {
444
/// The arc defining the segment
445
#[cfg_attr(all(feature = "serialize", feature = "alloc"), serde(flatten))]
446
pub arc: Arc2d,
447
}
448
449
impl Primitive2d for CircularSegment {}
450
451
impl Default for CircularSegment {
452
/// Returns the default [`CircularSegment`] with radius `0.5` and covering a third of a circle
453
fn default() -> Self {
454
Self::from(Arc2d::default())
455
}
456
}
457
458
impl Measured2d for CircularSegment {
459
#[inline(always)]
460
fn area(&self) -> f32 {
461
0.5 * self.arc.radius.squared() * (self.arc.angle() - ops::sin(self.arc.angle()))
462
}
463
464
#[inline(always)]
465
fn perimeter(&self) -> f32 {
466
self.chord_length() + self.arc_length()
467
}
468
}
469
470
impl CircularSegment {
471
/// Create a new [`CircularSegment`] from a `radius`, and an `angle`
472
#[inline(always)]
473
pub const fn new(radius: f32, angle: f32) -> Self {
474
Self {
475
arc: Arc2d::new(radius, angle),
476
}
477
}
478
479
/// Create a new [`CircularSegment`] from a `radius` and an `angle` in radians.
480
#[inline(always)]
481
pub const fn from_radians(radius: f32, angle: f32) -> Self {
482
Self {
483
arc: Arc2d::from_radians(radius, angle),
484
}
485
}
486
487
/// Create a new [`CircularSegment`] from a `radius` and an `angle` in degrees.
488
#[inline(always)]
489
pub const fn from_degrees(radius: f32, angle: f32) -> Self {
490
Self {
491
arc: Arc2d::from_degrees(radius, angle),
492
}
493
}
494
495
/// Create a new [`CircularSegment`] from a `radius` and a number of `turns` of a circle.
496
///
497
/// For instance, `0.5` turns is a semicircle.
498
#[inline(always)]
499
pub const fn from_turns(radius: f32, fraction: f32) -> Self {
500
Self {
501
arc: Arc2d::from_turns(radius, fraction),
502
}
503
}
504
505
/// Get the half-angle of the segment
506
#[inline(always)]
507
pub const fn half_angle(&self) -> f32 {
508
self.arc.half_angle
509
}
510
511
/// Get the angle of the segment
512
#[inline(always)]
513
pub const fn angle(&self) -> f32 {
514
self.arc.angle()
515
}
516
517
/// Get the radius of the segment
518
#[inline(always)]
519
pub const fn radius(&self) -> f32 {
520
self.arc.radius
521
}
522
523
/// Get the length of the arc defining the segment
524
#[inline(always)]
525
pub const fn arc_length(&self) -> f32 {
526
self.arc.length()
527
}
528
529
/// Get half the length of the segment's base, also known as its chord
530
#[inline(always)]
531
#[doc(alias = "half_base_length")]
532
pub fn half_chord_length(&self) -> f32 {
533
self.arc.half_chord_length()
534
}
535
536
/// Get the length of the segment's base, also known as its chord
537
#[inline(always)]
538
#[doc(alias = "base_length")]
539
#[doc(alias = "base")]
540
pub fn chord_length(&self) -> f32 {
541
self.arc.chord_length()
542
}
543
544
/// Get the midpoint of the segment's base, also known as its chord
545
#[inline(always)]
546
#[doc(alias = "base_midpoint")]
547
pub fn chord_midpoint(&self) -> Vec2 {
548
self.arc.chord_midpoint()
549
}
550
551
/// Get the length of the apothem of this segment,
552
/// which is the signed distance between the segment and the center of its circle
553
///
554
/// See [`Arc2d::apothem`]
555
#[inline(always)]
556
pub fn apothem(&self) -> f32 {
557
self.arc.apothem()
558
}
559
560
/// Get the length of the sagitta of this segment, also known as its height
561
///
562
/// See [`Arc2d::sagitta`]
563
#[inline(always)]
564
#[doc(alias = "height")]
565
pub fn sagitta(&self) -> f32 {
566
self.arc.sagitta()
567
}
568
}
569
570
#[cfg(test)]
571
mod arc_tests {
572
use core::f32::consts::FRAC_PI_4;
573
use core::f32::consts::SQRT_2;
574
575
use approx::assert_abs_diff_eq;
576
577
use super::*;
578
579
struct ArcTestCase {
580
radius: f32,
581
half_angle: f32,
582
angle: f32,
583
length: f32,
584
right_endpoint: Vec2,
585
left_endpoint: Vec2,
586
endpoints: [Vec2; 2],
587
midpoint: Vec2,
588
half_chord_length: f32,
589
chord_length: f32,
590
chord_midpoint: Vec2,
591
apothem: f32,
592
sagitta: f32,
593
is_minor: bool,
594
is_major: bool,
595
sector_area: f32,
596
sector_perimeter: f32,
597
segment_area: f32,
598
segment_perimeter: f32,
599
}
600
601
impl ArcTestCase {
602
fn check_arc(&self, arc: Arc2d) {
603
assert_abs_diff_eq!(self.radius, arc.radius);
604
assert_abs_diff_eq!(self.half_angle, arc.half_angle);
605
assert_abs_diff_eq!(self.angle, arc.angle());
606
assert_abs_diff_eq!(self.length, arc.length());
607
assert_abs_diff_eq!(self.right_endpoint, arc.right_endpoint());
608
assert_abs_diff_eq!(self.left_endpoint, arc.left_endpoint());
609
assert_abs_diff_eq!(self.endpoints[0], arc.endpoints()[0]);
610
assert_abs_diff_eq!(self.endpoints[1], arc.endpoints()[1]);
611
assert_abs_diff_eq!(self.midpoint, arc.midpoint());
612
assert_abs_diff_eq!(self.half_chord_length, arc.half_chord_length());
613
assert_abs_diff_eq!(self.chord_length, arc.chord_length(), epsilon = 0.00001);
614
assert_abs_diff_eq!(self.chord_midpoint, arc.chord_midpoint());
615
assert_abs_diff_eq!(self.apothem, arc.apothem());
616
assert_abs_diff_eq!(self.sagitta, arc.sagitta());
617
assert_eq!(self.is_minor, arc.is_minor());
618
assert_eq!(self.is_major, arc.is_major());
619
}
620
621
fn check_sector(&self, sector: CircularSector) {
622
assert_abs_diff_eq!(self.radius, sector.radius());
623
assert_abs_diff_eq!(self.half_angle, sector.half_angle());
624
assert_abs_diff_eq!(self.angle, sector.angle());
625
assert_abs_diff_eq!(self.half_chord_length, sector.half_chord_length());
626
assert_abs_diff_eq!(self.chord_length, sector.chord_length(), epsilon = 0.00001);
627
assert_abs_diff_eq!(self.chord_midpoint, sector.chord_midpoint());
628
assert_abs_diff_eq!(self.apothem, sector.apothem());
629
assert_abs_diff_eq!(self.sagitta, sector.sagitta());
630
assert_abs_diff_eq!(self.sector_area, sector.area());
631
assert_abs_diff_eq!(self.sector_perimeter, sector.perimeter());
632
}
633
634
fn check_segment(&self, segment: CircularSegment) {
635
assert_abs_diff_eq!(self.radius, segment.radius());
636
assert_abs_diff_eq!(self.half_angle, segment.half_angle());
637
assert_abs_diff_eq!(self.angle, segment.angle());
638
assert_abs_diff_eq!(self.half_chord_length, segment.half_chord_length());
639
assert_abs_diff_eq!(self.chord_length, segment.chord_length(), epsilon = 0.00001);
640
assert_abs_diff_eq!(self.chord_midpoint, segment.chord_midpoint());
641
assert_abs_diff_eq!(self.apothem, segment.apothem());
642
assert_abs_diff_eq!(self.sagitta, segment.sagitta());
643
assert_abs_diff_eq!(self.segment_area, segment.area());
644
assert_abs_diff_eq!(self.segment_perimeter, segment.perimeter());
645
}
646
}
647
648
#[test]
649
fn zero_angle() {
650
let tests = ArcTestCase {
651
radius: 1.0,
652
half_angle: 0.0,
653
angle: 0.0,
654
length: 0.0,
655
left_endpoint: Vec2::Y,
656
right_endpoint: Vec2::Y,
657
endpoints: [Vec2::Y, Vec2::Y],
658
midpoint: Vec2::Y,
659
half_chord_length: 0.0,
660
chord_length: 0.0,
661
chord_midpoint: Vec2::Y,
662
apothem: 1.0,
663
sagitta: 0.0,
664
is_minor: true,
665
is_major: false,
666
sector_area: 0.0,
667
sector_perimeter: 2.0,
668
segment_area: 0.0,
669
segment_perimeter: 0.0,
670
};
671
672
tests.check_arc(Arc2d::new(1.0, 0.0));
673
tests.check_sector(CircularSector::new(1.0, 0.0));
674
tests.check_segment(CircularSegment::new(1.0, 0.0));
675
}
676
677
#[test]
678
fn zero_radius() {
679
let tests = ArcTestCase {
680
radius: 0.0,
681
half_angle: FRAC_PI_4,
682
angle: FRAC_PI_2,
683
length: 0.0,
684
left_endpoint: Vec2::ZERO,
685
right_endpoint: Vec2::ZERO,
686
endpoints: [Vec2::ZERO, Vec2::ZERO],
687
midpoint: Vec2::ZERO,
688
half_chord_length: 0.0,
689
chord_length: 0.0,
690
chord_midpoint: Vec2::ZERO,
691
apothem: 0.0,
692
sagitta: 0.0,
693
is_minor: true,
694
is_major: false,
695
sector_area: 0.0,
696
sector_perimeter: 0.0,
697
segment_area: 0.0,
698
segment_perimeter: 0.0,
699
};
700
701
tests.check_arc(Arc2d::new(0.0, FRAC_PI_4));
702
tests.check_sector(CircularSector::new(0.0, FRAC_PI_4));
703
tests.check_segment(CircularSegment::new(0.0, FRAC_PI_4));
704
}
705
706
#[test]
707
fn quarter_circle() {
708
let sqrt_half: f32 = ops::sqrt(0.5);
709
let tests = ArcTestCase {
710
radius: 1.0,
711
half_angle: FRAC_PI_4,
712
angle: FRAC_PI_2,
713
length: FRAC_PI_2,
714
left_endpoint: Vec2::new(-sqrt_half, sqrt_half),
715
right_endpoint: Vec2::splat(sqrt_half),
716
endpoints: [Vec2::new(-sqrt_half, sqrt_half), Vec2::splat(sqrt_half)],
717
midpoint: Vec2::Y,
718
half_chord_length: sqrt_half,
719
chord_length: ops::sqrt(2.0),
720
chord_midpoint: Vec2::new(0.0, sqrt_half),
721
apothem: sqrt_half,
722
sagitta: 1.0 - sqrt_half,
723
is_minor: true,
724
is_major: false,
725
sector_area: FRAC_PI_4,
726
sector_perimeter: FRAC_PI_2 + 2.0,
727
segment_area: FRAC_PI_4 - 0.5,
728
segment_perimeter: FRAC_PI_2 + SQRT_2,
729
};
730
731
tests.check_arc(Arc2d::from_turns(1.0, 0.25));
732
tests.check_sector(CircularSector::from_turns(1.0, 0.25));
733
tests.check_segment(CircularSegment::from_turns(1.0, 0.25));
734
}
735
736
#[test]
737
fn half_circle() {
738
let tests = ArcTestCase {
739
radius: 1.0,
740
half_angle: FRAC_PI_2,
741
angle: PI,
742
length: PI,
743
left_endpoint: Vec2::NEG_X,
744
right_endpoint: Vec2::X,
745
endpoints: [Vec2::NEG_X, Vec2::X],
746
midpoint: Vec2::Y,
747
half_chord_length: 1.0,
748
chord_length: 2.0,
749
chord_midpoint: Vec2::ZERO,
750
apothem: 0.0,
751
sagitta: 1.0,
752
is_minor: true,
753
is_major: true,
754
sector_area: FRAC_PI_2,
755
sector_perimeter: PI + 2.0,
756
segment_area: FRAC_PI_2,
757
segment_perimeter: PI + 2.0,
758
};
759
760
tests.check_arc(Arc2d::from_radians(1.0, PI));
761
tests.check_sector(CircularSector::from_radians(1.0, PI));
762
tests.check_segment(CircularSegment::from_radians(1.0, PI));
763
}
764
765
#[test]
766
fn full_circle() {
767
let tests = ArcTestCase {
768
radius: 1.0,
769
half_angle: PI,
770
angle: 2.0 * PI,
771
length: 2.0 * PI,
772
left_endpoint: Vec2::NEG_Y,
773
right_endpoint: Vec2::NEG_Y,
774
endpoints: [Vec2::NEG_Y, Vec2::NEG_Y],
775
midpoint: Vec2::Y,
776
half_chord_length: 0.0,
777
chord_length: 0.0,
778
chord_midpoint: Vec2::NEG_Y,
779
apothem: -1.0,
780
sagitta: 2.0,
781
is_minor: false,
782
is_major: true,
783
sector_area: PI,
784
sector_perimeter: 2.0 * PI,
785
segment_area: PI,
786
segment_perimeter: 2.0 * PI,
787
};
788
789
tests.check_arc(Arc2d::from_degrees(1.0, 360.0));
790
tests.check_sector(CircularSector::from_degrees(1.0, 360.0));
791
tests.check_segment(CircularSegment::from_degrees(1.0, 360.0));
792
}
793
}
794
795
/// An ellipse primitive, which is like a circle, but the width and height can be different
796
#[derive(Clone, Copy, Debug, PartialEq)]
797
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
798
#[cfg_attr(
799
feature = "bevy_reflect",
800
derive(Reflect),
801
reflect(Debug, PartialEq, Default, Clone)
802
)]
803
#[cfg_attr(
804
all(feature = "serialize", feature = "bevy_reflect"),
805
reflect(Serialize, Deserialize)
806
)]
807
pub struct Ellipse {
808
/// Half of the width and height of the ellipse.
809
///
810
/// This corresponds to the two perpendicular radii defining the ellipse.
811
pub half_size: Vec2,
812
}
813
814
impl Primitive2d for Ellipse {}
815
816
impl Default for Ellipse {
817
/// Returns the default [`Ellipse`] with a half-width of `1.0` and a half-height of `0.5`.
818
fn default() -> Self {
819
Self {
820
half_size: Vec2::new(1.0, 0.5),
821
}
822
}
823
}
824
825
impl Ellipse {
826
/// Create a new `Ellipse` from half of its width and height.
827
///
828
/// This corresponds to the two perpendicular radii defining the ellipse.
829
#[inline(always)]
830
pub const fn new(half_width: f32, half_height: f32) -> Self {
831
Self {
832
half_size: Vec2::new(half_width, half_height),
833
}
834
}
835
836
/// Create a new `Ellipse` from a given full size.
837
///
838
/// `size.x` is the diameter along the X axis, and `size.y` is the diameter along the Y axis.
839
#[inline(always)]
840
pub const fn from_size(size: Vec2) -> Self {
841
Self {
842
half_size: Vec2::new(size.x / 2.0, size.y / 2.0),
843
}
844
}
845
846
#[inline(always)]
847
/// Returns the [eccentricity](https://en.wikipedia.org/wiki/Eccentricity_(mathematics)) of the ellipse.
848
/// It can be thought of as a measure of how "stretched" or elongated the ellipse is.
849
///
850
/// The value should be in the range [0, 1), where 0 represents a circle, and 1 represents a parabola.
851
pub fn eccentricity(&self) -> f32 {
852
let a = self.semi_major();
853
let b = self.semi_minor();
854
855
ops::sqrt(a * a - b * b) / a
856
}
857
858
#[inline(always)]
859
/// Get the focal length of the ellipse. This corresponds to the distance between one of the foci and the center of the ellipse.
860
///
861
/// The focal length of an ellipse is related to its eccentricity by `eccentricity = focal_length / semi_major`
862
pub fn focal_length(&self) -> f32 {
863
let a = self.semi_major();
864
let b = self.semi_minor();
865
866
ops::sqrt(a * a - b * b)
867
}
868
869
/// Returns the length of the semi-major axis. This corresponds to the longest radius of the ellipse.
870
#[inline(always)]
871
pub fn semi_major(&self) -> f32 {
872
self.half_size.max_element()
873
}
874
875
/// Returns the length of the semi-minor axis. This corresponds to the shortest radius of the ellipse.
876
#[inline(always)]
877
pub fn semi_minor(&self) -> f32 {
878
self.half_size.min_element()
879
}
880
}
881
882
impl Measured2d for Ellipse {
883
/// Get the area of the ellipse
884
#[inline(always)]
885
fn area(&self) -> f32 {
886
PI * self.half_size.x * self.half_size.y
887
}
888
889
#[inline(always)]
890
/// Get an approximation for the perimeter or circumference of the ellipse.
891
///
892
/// The approximation is reasonably precise with a relative error less than 0.007%, getting more precise as the eccentricity of the ellipse decreases.
893
fn perimeter(&self) -> f32 {
894
let a = self.semi_major();
895
let b = self.semi_minor();
896
897
// In the case that `a == b`, the ellipse is a circle
898
if a / b - 1. < 1e-5 {
899
return PI * (a + b);
900
};
901
902
// In the case that `a` is much larger than `b`, the ellipse is a line
903
if a / b > 1e4 {
904
return 4. * a;
905
};
906
907
// These values are the result of (0.5 choose n)^2 where n is the index in the array
908
// They could be calculated on the fly but hardcoding them yields more accurate and faster results
909
// because the actual calculation for these values involves factorials and numbers > 10^23
910
const BINOMIAL_COEFFICIENTS: [f32; 21] = [
911
1.,
912
0.25,
913
0.015625,
914
0.00390625,
915
0.0015258789,
916
0.00074768066,
917
0.00042057037,
918
0.00025963783,
919
0.00017140154,
920
0.000119028846,
921
0.00008599834,
922
0.00006414339,
923
0.000049109784,
924
0.000038430585,
925
0.000030636627,
926
0.000024815668,
927
0.000020380836,
928
0.000016942893,
929
0.000014236736,
930
0.000012077564,
931
0.000010333865,
932
];
933
934
// The algorithm used here is the Gauss-Kummer infinite series expansion of the elliptic integral expression for the perimeter of ellipses
935
// For more information see https://www.wolframalpha.com/input/?i=gauss-kummer+series
936
// We only use the terms up to `i == 20` for this approximation
937
let h = ((a - b) / (a + b)).squared();
938
939
PI * (a + b)
940
* (0..=20)
941
.map(|i| BINOMIAL_COEFFICIENTS[i] * ops::powf(h, i as f32))
942
.sum::<f32>()
943
}
944
}
945
946
/// A primitive shape formed by the region between two circles, also known as a ring.
947
#[derive(Clone, Copy, Debug, PartialEq)]
948
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
949
#[cfg_attr(
950
feature = "bevy_reflect",
951
derive(Reflect),
952
reflect(Debug, PartialEq, Default, Clone)
953
)]
954
#[cfg_attr(
955
all(feature = "serialize", feature = "bevy_reflect"),
956
reflect(Serialize, Deserialize)
957
)]
958
#[doc(alias = "Ring")]
959
pub struct Annulus {
960
/// The inner circle of the annulus
961
pub inner_circle: Circle,
962
/// The outer circle of the annulus
963
pub outer_circle: Circle,
964
}
965
966
impl Primitive2d for Annulus {}
967
968
impl Default for Annulus {
969
/// Returns the default [`Annulus`] with radii of `0.5` and `1.0`.
970
fn default() -> Self {
971
Self {
972
inner_circle: Circle::new(0.5),
973
outer_circle: Circle::new(1.0),
974
}
975
}
976
}
977
978
impl Annulus {
979
/// Create a new [`Annulus`] from the radii of the inner and outer circle
980
#[inline(always)]
981
pub const fn new(inner_radius: f32, outer_radius: f32) -> Self {
982
Self {
983
inner_circle: Circle::new(inner_radius),
984
outer_circle: Circle::new(outer_radius),
985
}
986
}
987
988
/// Get the diameter of the annulus
989
#[inline(always)]
990
pub const fn diameter(&self) -> f32 {
991
self.outer_circle.diameter()
992
}
993
994
/// Get the thickness of the annulus
995
#[inline(always)]
996
pub const fn thickness(&self) -> f32 {
997
self.outer_circle.radius - self.inner_circle.radius
998
}
999
1000
/// Finds the point on the annulus that is closest to the given `point`:
1001
///
1002
/// - If the point is outside of the annulus completely, the returned point will be on the outer perimeter.
1003
/// - If the point is inside of the inner circle (hole) of the annulus, the returned point will be on the inner perimeter.
1004
/// - Otherwise, the returned point is overlapping the annulus and returned as is.
1005
#[inline(always)]
1006
pub fn closest_point(&self, point: Vec2) -> Vec2 {
1007
let distance_squared = point.length_squared();
1008
1009
if self.inner_circle.radius.squared() <= distance_squared {
1010
if distance_squared <= self.outer_circle.radius.squared() {
1011
// The point is inside the annulus.
1012
point
1013
} else {
1014
// The point is outside the annulus and closer to the outer perimeter.
1015
// Find the closest point on the perimeter of the annulus.
1016
let dir_to_point = point / ops::sqrt(distance_squared);
1017
self.outer_circle.radius * dir_to_point
1018
}
1019
} else {
1020
// The point is outside the annulus and closer to the inner perimeter.
1021
// Find the closest point on the perimeter of the annulus.
1022
let dir_to_point = point / ops::sqrt(distance_squared);
1023
self.inner_circle.radius * dir_to_point
1024
}
1025
}
1026
}
1027
1028
impl Measured2d for Annulus {
1029
/// Get the area of the annulus
1030
#[inline(always)]
1031
fn area(&self) -> f32 {
1032
PI * (self.outer_circle.radius.squared() - self.inner_circle.radius.squared())
1033
}
1034
1035
/// Get the perimeter or circumference of the annulus,
1036
/// which is the sum of the perimeters of the inner and outer circles.
1037
#[inline(always)]
1038
#[doc(alias = "circumference")]
1039
fn perimeter(&self) -> f32 {
1040
2.0 * PI * (self.outer_circle.radius + self.inner_circle.radius)
1041
}
1042
}
1043
1044
/// A rhombus primitive, also known as a diamond shape.
1045
/// A four sided polygon, centered on the origin, where opposite sides are parallel but without
1046
/// requiring right angles.
1047
#[derive(Clone, Copy, Debug, PartialEq)]
1048
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1049
#[cfg_attr(
1050
feature = "bevy_reflect",
1051
derive(Reflect),
1052
reflect(Debug, PartialEq, Default, Clone)
1053
)]
1054
#[cfg_attr(
1055
all(feature = "serialize", feature = "bevy_reflect"),
1056
reflect(Serialize, Deserialize)
1057
)]
1058
#[doc(alias = "Diamond")]
1059
pub struct Rhombus {
1060
/// Size of the horizontal and vertical diagonals of the rhombus
1061
pub half_diagonals: Vec2,
1062
}
1063
1064
impl Primitive2d for Rhombus {}
1065
1066
impl Default for Rhombus {
1067
/// Returns the default [`Rhombus`] with a half-horizontal and half-vertical diagonal of `0.5`.
1068
fn default() -> Self {
1069
Self {
1070
half_diagonals: Vec2::splat(0.5),
1071
}
1072
}
1073
}
1074
1075
impl Rhombus {
1076
/// Create a new `Rhombus` from a vertical and horizontal diagonal sizes.
1077
#[inline(always)]
1078
pub const fn new(horizontal_diagonal: f32, vertical_diagonal: f32) -> Self {
1079
Self {
1080
half_diagonals: Vec2::new(horizontal_diagonal / 2.0, vertical_diagonal / 2.0),
1081
}
1082
}
1083
1084
/// Create a new `Rhombus` from a side length with all inner angles equal.
1085
#[inline(always)]
1086
pub const fn from_side(side: f32) -> Self {
1087
Self {
1088
half_diagonals: Vec2::splat(side * FRAC_1_SQRT_2),
1089
}
1090
}
1091
1092
/// Create a new `Rhombus` from a given inradius with all inner angles equal.
1093
#[inline(always)]
1094
pub const fn from_inradius(inradius: f32) -> Self {
1095
let half_diagonal = inradius * 2.0 / core::f32::consts::SQRT_2;
1096
Self {
1097
half_diagonals: Vec2::new(half_diagonal, half_diagonal),
1098
}
1099
}
1100
1101
/// Get the length of each side of the rhombus
1102
#[inline(always)]
1103
pub fn side(&self) -> f32 {
1104
self.half_diagonals.length()
1105
}
1106
1107
/// Get the radius of the circumcircle on which all vertices
1108
/// of the rhombus lie
1109
#[inline(always)]
1110
pub const fn circumradius(&self) -> f32 {
1111
self.half_diagonals.x.max(self.half_diagonals.y)
1112
}
1113
1114
/// Get the radius of the largest circle that can
1115
/// be drawn within the rhombus
1116
#[inline(always)]
1117
#[doc(alias = "apothem")]
1118
pub fn inradius(&self) -> f32 {
1119
let side = self.side();
1120
if side == 0.0 {
1121
0.0
1122
} else {
1123
(self.half_diagonals.x * self.half_diagonals.y) / side
1124
}
1125
}
1126
1127
/// Finds the point on the rhombus that is closest to the given `point`.
1128
///
1129
/// If the point is outside the rhombus, the returned point will be on the perimeter of the rhombus.
1130
/// Otherwise, it will be inside the rhombus and returned as is.
1131
#[inline(always)]
1132
pub fn closest_point(&self, point: Vec2) -> Vec2 {
1133
// Fold the problem into the positive quadrant
1134
let point_abs = point.abs();
1135
let half_diagonals = self.half_diagonals.abs(); // to ensure correct sign
1136
1137
// The unnormalised normal vector perpendicular to the side of the rhombus
1138
let normal = Vec2::new(half_diagonals.y, half_diagonals.x);
1139
let normal_magnitude_squared = normal.length_squared();
1140
if normal_magnitude_squared == 0.0 {
1141
return Vec2::ZERO; // A null Rhombus has only one point anyway.
1142
}
1143
1144
// The last term corresponds to normal.dot(rhombus_vertex)
1145
let distance_unnormalised = normal.dot(point_abs) - half_diagonals.x * half_diagonals.y;
1146
1147
// The point is already inside so we simply return it.
1148
if distance_unnormalised <= 0.0 {
1149
return point;
1150
}
1151
1152
// Clamp the point to the edge
1153
let mut result = point_abs - normal * distance_unnormalised / normal_magnitude_squared;
1154
1155
// Clamp the point back to the positive quadrant
1156
// if it's outside, it needs to be clamped to either vertex
1157
if result.x <= 0.0 {
1158
result = Vec2::new(0.0, half_diagonals.y);
1159
} else if result.y <= 0.0 {
1160
result = Vec2::new(half_diagonals.x, 0.0);
1161
}
1162
1163
// Finally, we restore the signs of the original vector
1164
result.copysign(point)
1165
}
1166
}
1167
1168
impl Measured2d for Rhombus {
1169
/// Get the area of the rhombus
1170
#[inline(always)]
1171
fn area(&self) -> f32 {
1172
2.0 * self.half_diagonals.x * self.half_diagonals.y
1173
}
1174
1175
/// Get the perimeter of the rhombus
1176
#[inline(always)]
1177
fn perimeter(&self) -> f32 {
1178
4.0 * self.side()
1179
}
1180
}
1181
1182
/// An unbounded plane in 2D space. It forms a separating surface through the origin,
1183
/// stretching infinitely far
1184
#[derive(Clone, Copy, Debug, PartialEq)]
1185
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1186
#[cfg_attr(
1187
feature = "bevy_reflect",
1188
derive(Reflect),
1189
reflect(Debug, PartialEq, Default, Clone)
1190
)]
1191
#[cfg_attr(
1192
all(feature = "serialize", feature = "bevy_reflect"),
1193
reflect(Serialize, Deserialize)
1194
)]
1195
pub struct Plane2d {
1196
/// The normal of the plane. The plane will be placed perpendicular to this direction
1197
pub normal: Dir2,
1198
}
1199
1200
impl Primitive2d for Plane2d {}
1201
1202
impl Default for Plane2d {
1203
/// Returns the default [`Plane2d`] with a normal pointing in the `+Y` direction.
1204
fn default() -> Self {
1205
Self { normal: Dir2::Y }
1206
}
1207
}
1208
1209
impl Plane2d {
1210
/// Create a new `Plane2d` from a normal
1211
///
1212
/// # Panics
1213
///
1214
/// Panics if the given `normal` is zero (or very close to zero), or non-finite.
1215
#[inline(always)]
1216
pub fn new(normal: Vec2) -> Self {
1217
Self {
1218
normal: Dir2::new(normal).expect("normal must be nonzero and finite"),
1219
}
1220
}
1221
}
1222
1223
/// An infinite line going through the origin along a direction in 2D space.
1224
///
1225
/// For a finite line: [`Segment2d`]
1226
#[derive(Clone, Copy, Debug, PartialEq)]
1227
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1228
#[cfg_attr(
1229
feature = "bevy_reflect",
1230
derive(Reflect),
1231
reflect(Debug, PartialEq, Clone)
1232
)]
1233
#[cfg_attr(
1234
all(feature = "serialize", feature = "bevy_reflect"),
1235
reflect(Serialize, Deserialize)
1236
)]
1237
pub struct Line2d {
1238
/// The direction of the line. The line extends infinitely in both the given direction
1239
/// and its opposite direction
1240
pub direction: Dir2,
1241
}
1242
1243
impl Primitive2d for Line2d {}
1244
1245
/// A line segment defined by two endpoints in 2D space.
1246
#[derive(Clone, Copy, Debug, PartialEq)]
1247
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1248
#[cfg_attr(
1249
feature = "bevy_reflect",
1250
derive(Reflect),
1251
reflect(Debug, PartialEq, Clone)
1252
)]
1253
#[cfg_attr(
1254
all(feature = "serialize", feature = "bevy_reflect"),
1255
reflect(Serialize, Deserialize)
1256
)]
1257
#[doc(alias = "LineSegment2d")]
1258
pub struct Segment2d {
1259
/// The endpoints of the line segment.
1260
pub vertices: [Vec2; 2],
1261
}
1262
1263
impl Primitive2d for Segment2d {}
1264
1265
impl Default for Segment2d {
1266
fn default() -> Self {
1267
Self {
1268
vertices: [Vec2::new(-0.5, 0.0), Vec2::new(0.5, 0.0)],
1269
}
1270
}
1271
}
1272
1273
impl Segment2d {
1274
/// Create a new `Segment2d` from its endpoints.
1275
#[inline(always)]
1276
pub const fn new(point1: Vec2, point2: Vec2) -> Self {
1277
Self {
1278
vertices: [point1, point2],
1279
}
1280
}
1281
1282
/// Create a new `Segment2d` centered at the origin with the given direction and length.
1283
///
1284
/// The endpoints will be at `-direction * length / 2.0` and `direction * length / 2.0`.
1285
#[inline(always)]
1286
pub fn from_direction_and_length(direction: Dir2, length: f32) -> Self {
1287
let endpoint = 0.5 * length * direction;
1288
Self {
1289
vertices: [-endpoint, endpoint],
1290
}
1291
}
1292
1293
/// Create a new `Segment2d` centered at the origin from a vector representing
1294
/// the direction and length of the line segment.
1295
///
1296
/// The endpoints will be at `-scaled_direction / 2.0` and `scaled_direction / 2.0`.
1297
#[inline(always)]
1298
pub fn from_scaled_direction(scaled_direction: Vec2) -> Self {
1299
let endpoint = 0.5 * scaled_direction;
1300
Self {
1301
vertices: [-endpoint, endpoint],
1302
}
1303
}
1304
1305
/// Create a new `Segment2d` starting from the origin of the given `ray`,
1306
/// going in the direction of the ray for the given `length`.
1307
///
1308
/// The endpoints will be at `ray.origin` and `ray.origin + length * ray.direction`.
1309
#[inline(always)]
1310
pub fn from_ray_and_length(ray: Ray2d, length: f32) -> Self {
1311
Self {
1312
vertices: [ray.origin, ray.get_point(length)],
1313
}
1314
}
1315
1316
/// Get the position of the first endpoint of the line segment.
1317
#[inline(always)]
1318
pub const fn point1(&self) -> Vec2 {
1319
self.vertices[0]
1320
}
1321
1322
/// Get the position of the second endpoint of the line segment.
1323
#[inline(always)]
1324
pub const fn point2(&self) -> Vec2 {
1325
self.vertices[1]
1326
}
1327
1328
/// Compute the midpoint between the two endpoints of the line segment.
1329
#[inline(always)]
1330
#[doc(alias = "midpoint")]
1331
pub fn center(&self) -> Vec2 {
1332
self.point1().midpoint(self.point2())
1333
}
1334
1335
/// Compute the length of the line segment.
1336
#[inline(always)]
1337
pub fn length(&self) -> f32 {
1338
self.point1().distance(self.point2())
1339
}
1340
1341
/// Compute the squared length of the line segment.
1342
#[inline(always)]
1343
pub fn length_squared(&self) -> f32 {
1344
self.point1().distance_squared(self.point2())
1345
}
1346
1347
/// Compute the normalized direction pointing from the first endpoint to the second endpoint.
1348
///
1349
/// For the non-panicking version, see [`Segment2d::try_direction`].
1350
///
1351
/// # Panics
1352
///
1353
/// Panics if a valid direction could not be computed, for example when the endpoints are coincident, NaN, or infinite.
1354
#[inline(always)]
1355
pub fn direction(&self) -> Dir2 {
1356
self.try_direction().unwrap_or_else(|err| {
1357
panic!("Failed to compute the direction of a line segment: {err}")
1358
})
1359
}
1360
1361
/// Try to compute the normalized direction pointing from the first endpoint to the second endpoint.
1362
///
1363
/// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if a valid direction could not be computed,
1364
/// for example when the endpoints are coincident, NaN, or infinite.
1365
#[inline(always)]
1366
pub fn try_direction(&self) -> Result<Dir2, InvalidDirectionError> {
1367
Dir2::new(self.scaled_direction())
1368
}
1369
1370
/// Compute the vector from the first endpoint to the second endpoint.
1371
#[inline(always)]
1372
pub fn scaled_direction(&self) -> Vec2 {
1373
self.point2() - self.point1()
1374
}
1375
1376
/// Compute the normalized counterclockwise normal on the left-hand side of the line segment.
1377
///
1378
/// For the non-panicking version, see [`Segment2d::try_left_normal`].
1379
///
1380
/// # Panics
1381
///
1382
/// Panics if a valid normal could not be computed, for example when the endpoints are coincident, NaN, or infinite.
1383
#[inline(always)]
1384
pub fn left_normal(&self) -> Dir2 {
1385
self.try_left_normal().unwrap_or_else(|err| {
1386
panic!("Failed to compute the left-hand side normal of a line segment: {err}")
1387
})
1388
}
1389
1390
/// Try to compute the normalized counterclockwise normal on the left-hand side of the line segment.
1391
///
1392
/// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if a valid normal could not be computed,
1393
/// for example when the endpoints are coincident, NaN, or infinite.
1394
#[inline(always)]
1395
pub fn try_left_normal(&self) -> Result<Dir2, InvalidDirectionError> {
1396
Dir2::new(self.scaled_left_normal())
1397
}
1398
1399
/// Compute the non-normalized counterclockwise normal on the left-hand side of the line segment.
1400
///
1401
/// The length of the normal is the distance between the endpoints.
1402
#[inline(always)]
1403
pub fn scaled_left_normal(&self) -> Vec2 {
1404
let scaled_direction = self.scaled_direction();
1405
Vec2::new(-scaled_direction.y, scaled_direction.x)
1406
}
1407
1408
/// Compute the normalized clockwise normal on the right-hand side of the line segment.
1409
///
1410
/// For the non-panicking version, see [`Segment2d::try_right_normal`].
1411
///
1412
/// # Panics
1413
///
1414
/// Panics if a valid normal could not be computed, for example when the endpoints are coincident, NaN, or infinite.
1415
#[inline(always)]
1416
pub fn right_normal(&self) -> Dir2 {
1417
self.try_right_normal().unwrap_or_else(|err| {
1418
panic!("Failed to compute the right-hand side normal of a line segment: {err}")
1419
})
1420
}
1421
1422
/// Try to compute the normalized clockwise normal on the right-hand side of the line segment.
1423
///
1424
/// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if a valid normal could not be computed,
1425
/// for example when the endpoints are coincident, NaN, or infinite.
1426
#[inline(always)]
1427
pub fn try_right_normal(&self) -> Result<Dir2, InvalidDirectionError> {
1428
Dir2::new(self.scaled_right_normal())
1429
}
1430
1431
/// Compute the non-normalized clockwise normal on the right-hand side of the line segment.
1432
///
1433
/// The length of the normal is the distance between the endpoints.
1434
#[inline(always)]
1435
pub fn scaled_right_normal(&self) -> Vec2 {
1436
let scaled_direction = self.scaled_direction();
1437
Vec2::new(scaled_direction.y, -scaled_direction.x)
1438
}
1439
1440
/// Compute the segment transformed by the given [`Isometry2d`].
1441
#[inline(always)]
1442
pub fn transformed(&self, isometry: impl Into<Isometry2d>) -> Self {
1443
let isometry: Isometry2d = isometry.into();
1444
Self::new(
1445
isometry.transform_point(self.point1()),
1446
isometry.transform_point(self.point2()),
1447
)
1448
}
1449
1450
/// Compute the segment translated by the given vector.
1451
#[inline(always)]
1452
pub fn translated(&self, translation: Vec2) -> Segment2d {
1453
Self::new(self.point1() + translation, self.point2() + translation)
1454
}
1455
1456
/// Compute the segment rotated around the origin by the given rotation.
1457
#[inline(always)]
1458
pub fn rotated(&self, rotation: Rot2) -> Segment2d {
1459
Segment2d::new(rotation * self.point1(), rotation * self.point2())
1460
}
1461
1462
/// Compute the segment rotated around the given point by the given rotation.
1463
#[inline(always)]
1464
pub fn rotated_around(&self, rotation: Rot2, point: Vec2) -> Segment2d {
1465
// We offset our segment so that our segment is rotated as if from the origin, then we can apply the offset back
1466
let offset = self.translated(-point);
1467
let rotated = offset.rotated(rotation);
1468
rotated.translated(point)
1469
}
1470
1471
/// Compute the segment rotated around its own center.
1472
#[inline(always)]
1473
pub fn rotated_around_center(&self, rotation: Rot2) -> Segment2d {
1474
self.rotated_around(rotation, self.center())
1475
}
1476
1477
/// Compute the segment with its center at the origin, keeping the same direction and length.
1478
#[inline(always)]
1479
pub fn centered(&self) -> Segment2d {
1480
let center = self.center();
1481
self.translated(-center)
1482
}
1483
1484
/// Compute the segment with a new length, keeping the same direction and center.
1485
#[inline(always)]
1486
pub fn resized(&self, length: f32) -> Segment2d {
1487
let offset_from_origin = self.center();
1488
let centered = self.translated(-offset_from_origin);
1489
let ratio = length / self.length();
1490
let segment = Segment2d::new(centered.point1() * ratio, centered.point2() * ratio);
1491
segment.translated(offset_from_origin)
1492
}
1493
1494
/// Reverses the direction of the line segment by swapping the endpoints.
1495
#[inline(always)]
1496
pub fn reverse(&mut self) {
1497
let [point1, point2] = &mut self.vertices;
1498
core::mem::swap(point1, point2);
1499
}
1500
1501
/// Returns the line segment with its direction reversed by swapping the endpoints.
1502
#[inline(always)]
1503
#[must_use]
1504
pub fn reversed(mut self) -> Self {
1505
self.reverse();
1506
self
1507
}
1508
1509
/// Returns the point on the [`Segment2d`] that is closest to the specified `point`.
1510
#[inline(always)]
1511
pub fn closest_point(&self, point: Vec2) -> Vec2 {
1512
// `point`
1513
// x
1514
// ^|
1515
// / |
1516
//`offset`/ |
1517
// / | `segment_vector`
1518
// x----.-------------->x
1519
// 0 t 1
1520
let segment_vector = self.vertices[1] - self.vertices[0];
1521
let offset = point - self.vertices[0];
1522
// The signed projection of `offset` onto `segment_vector`, scaled by the length of the segment.
1523
let projection_scaled = segment_vector.dot(offset);
1524
1525
// `point` is too far "left" in the picture
1526
if projection_scaled <= 0.0 {
1527
return self.vertices[0];
1528
}
1529
1530
let length_squared = segment_vector.length_squared();
1531
// `point` is too far "right" in the picture
1532
if projection_scaled >= length_squared {
1533
return self.vertices[1];
1534
}
1535
1536
// Point lies somewhere in the middle, we compute the closest point by finding the parameter along the line.
1537
let t = projection_scaled / length_squared;
1538
self.vertices[0] + t * segment_vector
1539
}
1540
}
1541
1542
impl From<[Vec2; 2]> for Segment2d {
1543
#[inline(always)]
1544
fn from(vertices: [Vec2; 2]) -> Self {
1545
Self { vertices }
1546
}
1547
}
1548
1549
impl From<(Vec2, Vec2)> for Segment2d {
1550
#[inline(always)]
1551
fn from((point1, point2): (Vec2, Vec2)) -> Self {
1552
Self::new(point1, point2)
1553
}
1554
}
1555
1556
/// A series of connected line segments in 2D space.
1557
#[cfg(feature = "alloc")]
1558
#[derive(Clone, Debug, PartialEq)]
1559
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1560
#[cfg_attr(
1561
feature = "bevy_reflect",
1562
derive(Reflect),
1563
reflect(Debug, PartialEq, Clone)
1564
)]
1565
#[cfg_attr(
1566
all(feature = "serialize", feature = "bevy_reflect"),
1567
reflect(Serialize, Deserialize)
1568
)]
1569
pub struct Polyline2d {
1570
/// The vertices of the polyline
1571
pub vertices: Vec<Vec2>,
1572
}
1573
1574
#[cfg(feature = "alloc")]
1575
impl Primitive2d for Polyline2d {}
1576
1577
#[cfg(feature = "alloc")]
1578
impl FromIterator<Vec2> for Polyline2d {
1579
fn from_iter<I: IntoIterator<Item = Vec2>>(iter: I) -> Self {
1580
Self {
1581
vertices: iter.into_iter().collect(),
1582
}
1583
}
1584
}
1585
1586
#[cfg(feature = "alloc")]
1587
impl Default for Polyline2d {
1588
fn default() -> Self {
1589
Self {
1590
vertices: Vec::from([Vec2::new(-0.5, 0.0), Vec2::new(0.5, 0.0)]),
1591
}
1592
}
1593
}
1594
1595
#[cfg(feature = "alloc")]
1596
impl Polyline2d {
1597
/// Create a new `Polyline2d` from its vertices
1598
pub fn new(vertices: impl IntoIterator<Item = Vec2>) -> Self {
1599
Self::from_iter(vertices)
1600
}
1601
1602
/// Create a new `Polyline2d` from two endpoints with subdivision points.
1603
/// `subdivisions = 0` creates a simple line with just start and end points.
1604
/// `subdivisions = 1` adds one point in the middle, creating 2 segments, etc.
1605
pub fn with_subdivisions(start: Vec2, end: Vec2, subdivisions: usize) -> Self {
1606
let total_vertices = subdivisions + 2;
1607
let mut vertices = Vec::with_capacity(total_vertices);
1608
1609
let step = (end - start) / (subdivisions + 1) as f32;
1610
for i in 0..total_vertices {
1611
vertices.push(start + step * i as f32);
1612
}
1613
1614
Self { vertices }
1615
}
1616
}
1617
1618
/// A triangle in 2D space
1619
#[derive(Clone, Copy, Debug, PartialEq)]
1620
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1621
#[cfg_attr(
1622
feature = "bevy_reflect",
1623
derive(Reflect),
1624
reflect(Debug, PartialEq, Default, Clone)
1625
)]
1626
#[cfg_attr(
1627
all(feature = "serialize", feature = "bevy_reflect"),
1628
reflect(Serialize, Deserialize)
1629
)]
1630
pub struct Triangle2d {
1631
/// The vertices of the triangle
1632
pub vertices: [Vec2; 3],
1633
}
1634
1635
impl Primitive2d for Triangle2d {}
1636
1637
impl Default for Triangle2d {
1638
/// Returns the default [`Triangle2d`] with the vertices `[0.0, 0.5]`, `[-0.5, -0.5]`, and `[0.5, -0.5]`.
1639
fn default() -> Self {
1640
Self {
1641
vertices: [Vec2::Y * 0.5, Vec2::new(-0.5, -0.5), Vec2::new(0.5, -0.5)],
1642
}
1643
}
1644
}
1645
1646
impl Triangle2d {
1647
/// Create a new `Triangle2d` from points `a`, `b`, and `c`
1648
#[inline(always)]
1649
pub const fn new(a: Vec2, b: Vec2, c: Vec2) -> Self {
1650
Self {
1651
vertices: [a, b, c],
1652
}
1653
}
1654
1655
/// Get the [`WindingOrder`] of the triangle
1656
#[inline(always)]
1657
#[doc(alias = "orientation")]
1658
pub fn winding_order(&self) -> WindingOrder {
1659
let [a, b, c] = self.vertices;
1660
let area = (b - a).perp_dot(c - a);
1661
if area > f32::EPSILON {
1662
WindingOrder::CounterClockwise
1663
} else if area < -f32::EPSILON {
1664
WindingOrder::Clockwise
1665
} else {
1666
WindingOrder::Invalid
1667
}
1668
}
1669
1670
/// Compute the circle passing through all three vertices of the triangle.
1671
/// The vector in the returned tuple is the circumcenter.
1672
pub fn circumcircle(&self) -> (Circle, Vec2) {
1673
// We treat the triangle as translated so that vertex A is at the origin. This simplifies calculations.
1674
//
1675
// A = (0, 0)
1676
// *
1677
// / \
1678
// / \
1679
// / \
1680
// / \
1681
// / U \
1682
// / \
1683
// *-------------*
1684
// B C
1685
1686
let a = self.vertices[0];
1687
let (b, c) = (self.vertices[1] - a, self.vertices[2] - a);
1688
let b_length_sq = b.length_squared();
1689
let c_length_sq = c.length_squared();
1690
1691
// Reference: https://en.wikipedia.org/wiki/Circumcircle#Cartesian_coordinates_2
1692
let inv_d = (2.0 * (b.x * c.y - b.y * c.x)).recip();
1693
let ux = inv_d * (c.y * b_length_sq - b.y * c_length_sq);
1694
let uy = inv_d * (b.x * c_length_sq - c.x * b_length_sq);
1695
let u = Vec2::new(ux, uy);
1696
1697
// Compute true circumcenter and circumradius, adding the tip coordinate so that
1698
// A is translated back to its actual coordinate.
1699
let center = u + a;
1700
let radius = u.length();
1701
1702
(Circle { radius }, center)
1703
}
1704
1705
/// Checks if the triangle is degenerate, meaning it has zero area.
1706
///
1707
/// A triangle is degenerate if the cross product of the vectors `ab` and `ac` has a length less than `10e-7`.
1708
/// This indicates that the three vertices are collinear or nearly collinear.
1709
#[inline(always)]
1710
pub fn is_degenerate(&self) -> bool {
1711
let [a, b, c] = self.vertices;
1712
let ab = (b - a).extend(0.);
1713
let ac = (c - a).extend(0.);
1714
ab.cross(ac).length() < 10e-7
1715
}
1716
1717
/// Checks if the triangle is acute, meaning all angles are less than 90 degrees
1718
#[inline(always)]
1719
pub fn is_acute(&self) -> bool {
1720
let [a, b, c] = self.vertices;
1721
let ab = b - a;
1722
let bc = c - b;
1723
let ca = a - c;
1724
1725
// a^2 + b^2 < c^2 for an acute triangle
1726
let mut side_lengths = [
1727
ab.length_squared(),
1728
bc.length_squared(),
1729
ca.length_squared(),
1730
];
1731
side_lengths.sort_by(|a, b| a.partial_cmp(b).unwrap());
1732
side_lengths[0] + side_lengths[1] > side_lengths[2]
1733
}
1734
1735
/// Checks if the triangle is obtuse, meaning one angle is greater than 90 degrees
1736
#[inline(always)]
1737
pub fn is_obtuse(&self) -> bool {
1738
let [a, b, c] = self.vertices;
1739
let ab = b - a;
1740
let bc = c - b;
1741
let ca = a - c;
1742
1743
// a^2 + b^2 > c^2 for an obtuse triangle
1744
let mut side_lengths = [
1745
ab.length_squared(),
1746
bc.length_squared(),
1747
ca.length_squared(),
1748
];
1749
side_lengths.sort_by(|a, b| a.partial_cmp(b).unwrap());
1750
side_lengths[0] + side_lengths[1] < side_lengths[2]
1751
}
1752
1753
/// Reverse the [`WindingOrder`] of the triangle
1754
/// by swapping the first and last vertices.
1755
#[inline(always)]
1756
pub fn reverse(&mut self) {
1757
self.vertices.swap(0, 2);
1758
}
1759
1760
/// This triangle but reversed.
1761
#[inline(always)]
1762
#[must_use]
1763
pub fn reversed(mut self) -> Self {
1764
self.reverse();
1765
self
1766
}
1767
}
1768
1769
impl Measured2d for Triangle2d {
1770
/// Get the area of the triangle
1771
#[inline(always)]
1772
fn area(&self) -> f32 {
1773
let [a, b, c] = self.vertices;
1774
ops::abs(a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y)) / 2.0
1775
}
1776
1777
/// Get the perimeter of the triangle
1778
#[inline(always)]
1779
fn perimeter(&self) -> f32 {
1780
let [a, b, c] = self.vertices;
1781
1782
let ab = a.distance(b);
1783
let bc = b.distance(c);
1784
let ca = c.distance(a);
1785
1786
ab + bc + ca
1787
}
1788
}
1789
1790
/// A rectangle primitive, which is like a square, except that the width and height can be different
1791
#[derive(Clone, Copy, Debug, PartialEq)]
1792
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1793
#[cfg_attr(
1794
feature = "bevy_reflect",
1795
derive(Reflect),
1796
reflect(Debug, PartialEq, Default, Clone)
1797
)]
1798
#[cfg_attr(
1799
all(feature = "serialize", feature = "bevy_reflect"),
1800
reflect(Serialize, Deserialize)
1801
)]
1802
#[doc(alias = "Quad")]
1803
pub struct Rectangle {
1804
/// Half of the width and height of the rectangle
1805
pub half_size: Vec2,
1806
}
1807
1808
impl Primitive2d for Rectangle {}
1809
1810
impl Default for Rectangle {
1811
/// Returns the default [`Rectangle`] with a half-width and half-height of `0.5`.
1812
fn default() -> Self {
1813
Self {
1814
half_size: Vec2::splat(0.5),
1815
}
1816
}
1817
}
1818
1819
impl Rectangle {
1820
/// Create a new `Rectangle` from a full width and height
1821
#[inline(always)]
1822
pub const fn new(width: f32, height: f32) -> Self {
1823
Self::from_size(Vec2::new(width, height))
1824
}
1825
1826
/// Create a new `Rectangle` from a given full size
1827
#[inline(always)]
1828
pub const fn from_size(size: Vec2) -> Self {
1829
Self {
1830
half_size: Vec2::new(size.x / 2.0, size.y / 2.0),
1831
}
1832
}
1833
1834
/// Create a new `Rectangle` from two corner points
1835
#[inline(always)]
1836
pub fn from_corners(point1: Vec2, point2: Vec2) -> Self {
1837
Self {
1838
half_size: (point2 - point1).abs() / 2.0,
1839
}
1840
}
1841
1842
/// Create a `Rectangle` from a single length.
1843
/// The resulting `Rectangle` will be the same size in every direction.
1844
#[inline(always)]
1845
pub const fn from_length(length: f32) -> Self {
1846
Self {
1847
half_size: Vec2::splat(length / 2.0),
1848
}
1849
}
1850
1851
/// Get the size of the rectangle
1852
#[inline(always)]
1853
pub fn size(&self) -> Vec2 {
1854
2.0 * self.half_size
1855
}
1856
1857
/// Finds the point on the rectangle that is closest to the given `point`.
1858
///
1859
/// If the point is outside the rectangle, the returned point will be on the perimeter of the rectangle.
1860
/// Otherwise, it will be inside the rectangle and returned as is.
1861
#[inline(always)]
1862
pub fn closest_point(&self, point: Vec2) -> Vec2 {
1863
// Clamp point coordinates to the rectangle
1864
point.clamp(-self.half_size, self.half_size)
1865
}
1866
}
1867
1868
impl Measured2d for Rectangle {
1869
/// Get the area of the rectangle
1870
#[inline(always)]
1871
fn area(&self) -> f32 {
1872
4.0 * self.half_size.x * self.half_size.y
1873
}
1874
1875
/// Get the perimeter of the rectangle
1876
#[inline(always)]
1877
fn perimeter(&self) -> f32 {
1878
4.0 * (self.half_size.x + self.half_size.y)
1879
}
1880
}
1881
1882
/// A polygon with N vertices.
1883
#[cfg(feature = "alloc")]
1884
#[derive(Clone, Debug, PartialEq)]
1885
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1886
#[cfg_attr(
1887
feature = "bevy_reflect",
1888
derive(Reflect),
1889
reflect(Debug, PartialEq, Clone)
1890
)]
1891
#[cfg_attr(
1892
all(feature = "serialize", feature = "bevy_reflect"),
1893
reflect(Serialize, Deserialize)
1894
)]
1895
pub struct Polygon {
1896
/// The vertices of the `Polygon`
1897
pub vertices: Vec<Vec2>,
1898
}
1899
1900
#[cfg(feature = "alloc")]
1901
impl Primitive2d for Polygon {}
1902
1903
#[cfg(feature = "alloc")]
1904
impl FromIterator<Vec2> for Polygon {
1905
fn from_iter<I: IntoIterator<Item = Vec2>>(iter: I) -> Self {
1906
Self {
1907
vertices: iter.into_iter().collect(),
1908
}
1909
}
1910
}
1911
1912
#[cfg(feature = "alloc")]
1913
impl Polygon {
1914
/// Create a new `Polygon` from its vertices
1915
pub fn new(vertices: impl IntoIterator<Item = Vec2>) -> Self {
1916
Self::from_iter(vertices)
1917
}
1918
1919
/// Tests if the polygon is simple.
1920
///
1921
/// A polygon is simple if it is not self intersecting and not self tangent.
1922
/// As such, no two edges of the polygon may cross each other and each vertex must not lie on another edge.
1923
#[cfg(feature = "alloc")]
1924
pub fn is_simple(&self) -> bool {
1925
is_polygon_simple(&self.vertices)
1926
}
1927
}
1928
1929
#[cfg(feature = "alloc")]
1930
impl From<ConvexPolygon> for Polygon {
1931
fn from(val: ConvexPolygon) -> Self {
1932
Polygon {
1933
vertices: val.vertices,
1934
}
1935
}
1936
}
1937
1938
/// A convex polygon with `N` vertices.
1939
#[cfg(feature = "alloc")]
1940
#[derive(Clone, Debug, PartialEq)]
1941
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1942
#[cfg_attr(
1943
feature = "bevy_reflect",
1944
derive(Reflect),
1945
reflect(Debug, PartialEq, Clone)
1946
)]
1947
#[cfg_attr(
1948
all(feature = "serialize", feature = "bevy_reflect"),
1949
reflect(Serialize, Deserialize)
1950
)]
1951
pub struct ConvexPolygon {
1952
/// The vertices of the [`ConvexPolygon`].
1953
vertices: Vec<Vec2>,
1954
}
1955
1956
#[cfg(feature = "alloc")]
1957
impl Primitive2d for ConvexPolygon {}
1958
1959
/// An error that happens when creating a [`ConvexPolygon`].
1960
#[cfg(feature = "alloc")]
1961
#[derive(Error, Debug, Clone)]
1962
pub enum ConvexPolygonError {
1963
/// The created polygon is not convex.
1964
#[error("The created polygon is not convex")]
1965
Concave,
1966
}
1967
1968
#[cfg(feature = "alloc")]
1969
impl ConvexPolygon {
1970
fn triangle_winding_order(
1971
&self,
1972
a_index: usize,
1973
b_index: usize,
1974
c_index: usize,
1975
) -> WindingOrder {
1976
let a = self.vertices[a_index];
1977
let b = self.vertices[b_index];
1978
let c = self.vertices[c_index];
1979
Triangle2d::new(a, b, c).winding_order()
1980
}
1981
1982
/// Create a [`ConvexPolygon`] from its `vertices`.
1983
///
1984
/// # Errors
1985
///
1986
/// Returns [`ConvexPolygonError::Concave`] if the `vertices` do not form a convex polygon.
1987
pub fn new(vertices: impl IntoIterator<Item = Vec2>) -> Result<Self, ConvexPolygonError> {
1988
let polygon = Self::new_unchecked(vertices);
1989
let len = polygon.vertices.len();
1990
let ref_winding_order = polygon.triangle_winding_order(len - 1, 0, 1);
1991
for i in 1..len {
1992
let winding_order = polygon.triangle_winding_order(i - 1, i, (i + 1) % len);
1993
if winding_order != ref_winding_order {
1994
return Err(ConvexPolygonError::Concave);
1995
}
1996
}
1997
Ok(polygon)
1998
}
1999
2000
/// Create a [`ConvexPolygon`] from its `vertices`, without checks.
2001
/// Use this version only if you know that the `vertices` make up a convex polygon.
2002
#[inline(always)]
2003
pub fn new_unchecked(vertices: impl IntoIterator<Item = Vec2>) -> Self {
2004
Self {
2005
vertices: vertices.into_iter().collect(),
2006
}
2007
}
2008
2009
/// Get the vertices of this polygon
2010
#[inline(always)]
2011
pub fn vertices(&self) -> &[Vec2] {
2012
&self.vertices
2013
}
2014
}
2015
2016
#[cfg(feature = "alloc")]
2017
impl TryFrom<Polygon> for ConvexPolygon {
2018
type Error = ConvexPolygonError;
2019
2020
fn try_from(val: Polygon) -> Result<Self, Self::Error> {
2021
ConvexPolygon::new(val.vertices)
2022
}
2023
}
2024
2025
/// A polygon centered on the origin where all vertices lie on a circle, equally far apart.
2026
#[derive(Clone, Copy, Debug, PartialEq)]
2027
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
2028
#[cfg_attr(
2029
feature = "bevy_reflect",
2030
derive(Reflect),
2031
reflect(Debug, PartialEq, Default, Clone)
2032
)]
2033
#[cfg_attr(
2034
all(feature = "serialize", feature = "bevy_reflect"),
2035
reflect(Serialize, Deserialize)
2036
)]
2037
pub struct RegularPolygon {
2038
/// The circumcircle on which all vertices lie
2039
pub circumcircle: Circle,
2040
/// The number of sides
2041
pub sides: u32,
2042
}
2043
2044
impl Primitive2d for RegularPolygon {}
2045
2046
impl Default for RegularPolygon {
2047
/// Returns the default [`RegularPolygon`] with six sides (a hexagon) and a circumradius of `0.5`.
2048
fn default() -> Self {
2049
Self {
2050
circumcircle: Circle { radius: 0.5 },
2051
sides: 6,
2052
}
2053
}
2054
}
2055
2056
impl RegularPolygon {
2057
/// Create a new `RegularPolygon`
2058
/// from the radius of the circumcircle and a number of sides
2059
///
2060
/// # Panics
2061
///
2062
/// Panics if `circumradius` is negative
2063
#[inline(always)]
2064
pub const fn new(circumradius: f32, sides: u32) -> Self {
2065
assert!(
2066
circumradius.is_sign_positive(),
2067
"polygon has a negative radius"
2068
);
2069
assert!(sides > 2, "polygon has less than 3 sides");
2070
2071
Self {
2072
circumcircle: Circle {
2073
radius: circumradius,
2074
},
2075
sides,
2076
}
2077
}
2078
2079
/// Get the radius of the circumcircle on which all vertices
2080
/// of the regular polygon lie
2081
#[inline(always)]
2082
pub const fn circumradius(&self) -> f32 {
2083
self.circumcircle.radius
2084
}
2085
2086
/// Get the inradius or apothem of the regular polygon.
2087
/// This is the radius of the largest circle that can
2088
/// be drawn within the polygon
2089
#[inline(always)]
2090
#[doc(alias = "apothem")]
2091
pub fn inradius(&self) -> f32 {
2092
self.circumradius() * ops::cos(PI / self.sides as f32)
2093
}
2094
2095
/// Get the length of one side of the regular polygon
2096
#[inline(always)]
2097
pub fn side_length(&self) -> f32 {
2098
2.0 * self.circumradius() * ops::sin(PI / self.sides as f32)
2099
}
2100
2101
/// Get the internal angle of the regular polygon in degrees.
2102
///
2103
/// This is the angle formed by two adjacent sides with points
2104
/// within the angle being in the interior of the polygon
2105
#[inline(always)]
2106
pub const fn internal_angle_degrees(&self) -> f32 {
2107
(self.sides - 2) as f32 / self.sides as f32 * 180.0
2108
}
2109
2110
/// Get the internal angle of the regular polygon in radians.
2111
///
2112
/// This is the angle formed by two adjacent sides with points
2113
/// within the angle being in the interior of the polygon
2114
#[inline(always)]
2115
pub const fn internal_angle_radians(&self) -> f32 {
2116
(self.sides - 2) as f32 * PI / self.sides as f32
2117
}
2118
2119
/// Get the external angle of the regular polygon in degrees.
2120
///
2121
/// This is the angle formed by two adjacent sides with points
2122
/// within the angle being in the exterior of the polygon
2123
#[inline(always)]
2124
pub const fn external_angle_degrees(&self) -> f32 {
2125
360.0 / self.sides as f32
2126
}
2127
2128
/// Get the external angle of the regular polygon in radians.
2129
///
2130
/// This is the angle formed by two adjacent sides with points
2131
/// within the angle being in the exterior of the polygon
2132
#[inline(always)]
2133
pub const fn external_angle_radians(&self) -> f32 {
2134
2.0 * PI / self.sides as f32
2135
}
2136
2137
/// Returns an iterator over the vertices of the regular polygon,
2138
/// rotated counterclockwise by the given angle in radians.
2139
///
2140
/// With a rotation of 0, a vertex will be placed at the top `(0.0, circumradius)`.
2141
pub fn vertices(self, rotation: f32) -> impl IntoIterator<Item = Vec2> {
2142
// Add pi/2 so that the polygon has a vertex at the top (sin is 1.0 and cos is 0.0)
2143
let start_angle = rotation + FRAC_PI_2;
2144
let step = core::f32::consts::TAU / self.sides as f32;
2145
2146
(0..self.sides).map(move |i| {
2147
let theta = start_angle + i as f32 * step;
2148
let (sin, cos) = ops::sin_cos(theta);
2149
Vec2::new(cos, sin) * self.circumcircle.radius
2150
})
2151
}
2152
}
2153
2154
impl Measured2d for RegularPolygon {
2155
/// Get the area of the regular polygon
2156
#[inline(always)]
2157
fn area(&self) -> f32 {
2158
let angle: f32 = 2.0 * PI / (self.sides as f32);
2159
(self.sides as f32) * self.circumradius().squared() * ops::sin(angle) / 2.0
2160
}
2161
2162
/// Get the perimeter of the regular polygon.
2163
/// This is the sum of its sides
2164
#[inline(always)]
2165
fn perimeter(&self) -> f32 {
2166
self.sides as f32 * self.side_length()
2167
}
2168
}
2169
2170
/// A 2D capsule primitive, also known as a stadium or pill shape.
2171
///
2172
/// A two-dimensional capsule is defined as a neighborhood of points at a distance (radius) from a line
2173
#[derive(Clone, Copy, Debug, PartialEq)]
2174
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
2175
#[cfg_attr(
2176
feature = "bevy_reflect",
2177
derive(Reflect),
2178
reflect(Debug, PartialEq, Default, Clone)
2179
)]
2180
#[cfg_attr(
2181
all(feature = "serialize", feature = "bevy_reflect"),
2182
reflect(Serialize, Deserialize)
2183
)]
2184
#[doc(alias = "stadium", alias = "pill")]
2185
pub struct Capsule2d {
2186
/// The radius of the capsule
2187
pub radius: f32,
2188
/// Half the height of the capsule, excluding the semicircles
2189
pub half_length: f32,
2190
}
2191
2192
impl Primitive2d for Capsule2d {}
2193
2194
impl Default for Capsule2d {
2195
/// Returns the default [`Capsule2d`] with a radius of `0.5` and a half-height of `0.5`,
2196
/// excluding the semicircles.
2197
fn default() -> Self {
2198
Self {
2199
radius: 0.5,
2200
half_length: 0.5,
2201
}
2202
}
2203
}
2204
2205
impl Capsule2d {
2206
/// Create a new `Capsule2d` from a radius and length
2207
pub const fn new(radius: f32, length: f32) -> Self {
2208
Self {
2209
radius,
2210
half_length: length / 2.0,
2211
}
2212
}
2213
2214
/// Get the part connecting the semicircular ends of the capsule as a [`Rectangle`]
2215
#[inline]
2216
pub const fn to_inner_rectangle(&self) -> Rectangle {
2217
Rectangle::new(self.radius * 2.0, self.half_length * 2.0)
2218
}
2219
}
2220
2221
impl Measured2d for Capsule2d {
2222
/// Get the area of the capsule
2223
#[inline]
2224
fn area(&self) -> f32 {
2225
// pi*r^2 + (2r)*l
2226
PI * self.radius.squared() + self.to_inner_rectangle().area()
2227
}
2228
2229
/// Get the perimeter of the capsule
2230
#[inline]
2231
fn perimeter(&self) -> f32 {
2232
// 2pi*r + 2l
2233
2.0 * PI * self.radius + 4.0 * self.half_length
2234
}
2235
}
2236
2237
#[cfg(test)]
2238
mod tests {
2239
// Reference values were computed by hand and/or with external tools
2240
2241
use super::*;
2242
use approx::{assert_abs_diff_eq, assert_relative_eq};
2243
2244
#[test]
2245
fn rectangle_closest_point() {
2246
let rectangle = Rectangle::new(2.0, 2.0);
2247
assert_eq!(rectangle.closest_point(Vec2::X * 10.0), Vec2::X);
2248
assert_eq!(rectangle.closest_point(Vec2::NEG_ONE * 10.0), Vec2::NEG_ONE);
2249
assert_eq!(
2250
rectangle.closest_point(Vec2::new(0.25, 0.1)),
2251
Vec2::new(0.25, 0.1)
2252
);
2253
}
2254
2255
#[test]
2256
fn circle_closest_point() {
2257
let circle = Circle { radius: 1.0 };
2258
assert_eq!(circle.closest_point(Vec2::X * 10.0), Vec2::X);
2259
assert_eq!(
2260
circle.closest_point(Vec2::NEG_ONE * 10.0),
2261
Vec2::NEG_ONE.normalize()
2262
);
2263
assert_eq!(
2264
circle.closest_point(Vec2::new(0.25, 0.1)),
2265
Vec2::new(0.25, 0.1)
2266
);
2267
}
2268
2269
#[test]
2270
fn annulus_closest_point() {
2271
let annulus = Annulus::new(1.5, 2.0);
2272
assert_eq!(annulus.closest_point(Vec2::X * 10.0), Vec2::X * 2.0);
2273
assert_eq!(
2274
annulus.closest_point(Vec2::NEG_ONE),
2275
Vec2::NEG_ONE.normalize() * 1.5
2276
);
2277
assert_eq!(
2278
annulus.closest_point(Vec2::new(1.55, 0.85)),
2279
Vec2::new(1.55, 0.85)
2280
);
2281
}
2282
2283
#[test]
2284
fn rhombus_closest_point() {
2285
let rhombus = Rhombus::new(2.0, 1.0);
2286
assert_eq!(rhombus.closest_point(Vec2::X * 10.0), Vec2::X);
2287
assert_eq!(
2288
rhombus.closest_point(Vec2::NEG_ONE * 0.2),
2289
Vec2::NEG_ONE * 0.2
2290
);
2291
assert_eq!(
2292
rhombus.closest_point(Vec2::new(-0.55, 0.35)),
2293
Vec2::new(-0.5, 0.25)
2294
);
2295
2296
let rhombus = Rhombus::new(0.0, 0.0);
2297
assert_eq!(rhombus.closest_point(Vec2::X * 10.0), Vec2::ZERO);
2298
assert_eq!(rhombus.closest_point(Vec2::NEG_ONE * 0.2), Vec2::ZERO);
2299
assert_eq!(rhombus.closest_point(Vec2::new(-0.55, 0.35)), Vec2::ZERO);
2300
}
2301
2302
#[test]
2303
fn segment_closest_point() {
2304
assert_eq!(
2305
Segment2d::new(Vec2::new(0.0, 0.0), Vec2::new(3.0, 0.0))
2306
.closest_point(Vec2::new(1.0, 6.0)),
2307
Vec2::new(1.0, 0.0)
2308
);
2309
2310
let segments = [
2311
Segment2d::new(Vec2::new(0.0, 0.0), Vec2::new(0.0, 0.0)),
2312
Segment2d::new(Vec2::new(0.0, 0.0), Vec2::new(1.0, 0.0)),
2313
Segment2d::new(Vec2::new(1.0, 0.0), Vec2::new(0.0, 1.0)),
2314
Segment2d::new(Vec2::new(1.0, 0.0), Vec2::new(1.0, 5.0 * f32::EPSILON)),
2315
];
2316
let points = [
2317
Vec2::new(0.0, 0.0),
2318
Vec2::new(1.0, 0.0),
2319
Vec2::new(-1.0, 1.0),
2320
Vec2::new(1.0, 1.0),
2321
Vec2::new(-1.0, 0.0),
2322
Vec2::new(5.0, -1.0),
2323
Vec2::new(1.0, f32::EPSILON),
2324
];
2325
2326
for point in points.iter() {
2327
for segment in segments.iter() {
2328
let closest = segment.closest_point(*point);
2329
assert!(
2330
point.distance_squared(closest) <= point.distance_squared(segment.point1()),
2331
"Closest point must always be at least as close as either vertex."
2332
);
2333
assert!(
2334
point.distance_squared(closest) <= point.distance_squared(segment.point2()),
2335
"Closest point must always be at least as close as either vertex."
2336
);
2337
assert!(
2338
point.distance_squared(closest) <= point.distance_squared(segment.center()),
2339
"Closest point must always be at least as close as the center."
2340
);
2341
let closest_to_closest = segment.closest_point(closest);
2342
// Closest point must already be on the segment
2343
assert_relative_eq!(closest_to_closest, closest);
2344
}
2345
}
2346
}
2347
2348
#[test]
2349
fn circle_math() {
2350
let circle = Circle { radius: 3.0 };
2351
assert_eq!(circle.diameter(), 6.0, "incorrect diameter");
2352
assert_eq!(circle.area(), 28.274334, "incorrect area");
2353
assert_eq!(circle.perimeter(), 18.849556, "incorrect perimeter");
2354
}
2355
2356
#[test]
2357
fn capsule_math() {
2358
let capsule = Capsule2d::new(2.0, 9.0);
2359
assert_eq!(
2360
capsule.to_inner_rectangle(),
2361
Rectangle::new(4.0, 9.0),
2362
"rectangle wasn't created correctly from a capsule"
2363
);
2364
assert_eq!(capsule.area(), 48.566371, "incorrect area");
2365
assert_eq!(capsule.perimeter(), 30.566371, "incorrect perimeter");
2366
}
2367
2368
#[test]
2369
fn annulus_math() {
2370
let annulus = Annulus::new(2.5, 3.5);
2371
assert_eq!(annulus.diameter(), 7.0, "incorrect diameter");
2372
assert_eq!(annulus.thickness(), 1.0, "incorrect thickness");
2373
assert_eq!(annulus.area(), 18.849556, "incorrect area");
2374
assert_eq!(annulus.perimeter(), 37.699112, "incorrect perimeter");
2375
}
2376
2377
#[test]
2378
fn rhombus_math() {
2379
let rhombus = Rhombus::new(3.0, 4.0);
2380
assert_eq!(rhombus.area(), 6.0, "incorrect area");
2381
assert_eq!(rhombus.perimeter(), 10.0, "incorrect perimeter");
2382
assert_eq!(rhombus.side(), 2.5, "incorrect side");
2383
assert_eq!(rhombus.inradius(), 1.2, "incorrect inradius");
2384
assert_eq!(rhombus.circumradius(), 2.0, "incorrect circumradius");
2385
let rhombus = Rhombus::new(0.0, 0.0);
2386
assert_eq!(rhombus.area(), 0.0, "incorrect area");
2387
assert_eq!(rhombus.perimeter(), 0.0, "incorrect perimeter");
2388
assert_eq!(rhombus.side(), 0.0, "incorrect side");
2389
assert_eq!(rhombus.inradius(), 0.0, "incorrect inradius");
2390
assert_eq!(rhombus.circumradius(), 0.0, "incorrect circumradius");
2391
let rhombus = Rhombus::from_side(core::f32::consts::SQRT_2);
2392
assert_abs_diff_eq!(rhombus.half_diagonals, Vec2::new(1.0, 1.0));
2393
assert_abs_diff_eq!(
2394
rhombus.half_diagonals,
2395
Rhombus::from_inradius(FRAC_1_SQRT_2).half_diagonals
2396
);
2397
}
2398
2399
#[test]
2400
fn ellipse_math() {
2401
let ellipse = Ellipse::new(3.0, 1.0);
2402
assert_eq!(ellipse.area(), 9.424778, "incorrect area");
2403
2404
assert_eq!(ellipse.eccentricity(), 0.94280905, "incorrect eccentricity");
2405
2406
let line = Ellipse::new(1., 0.);
2407
assert_eq!(line.eccentricity(), 1., "incorrect line eccentricity");
2408
2409
let circle = Ellipse::new(2., 2.);
2410
assert_eq!(circle.eccentricity(), 0., "incorrect circle eccentricity");
2411
}
2412
2413
#[test]
2414
fn ellipse_perimeter() {
2415
let circle = Ellipse::new(1., 1.);
2416
assert_relative_eq!(circle.perimeter(), 6.2831855);
2417
2418
let line = Ellipse::new(75_000., 0.5);
2419
assert_relative_eq!(line.perimeter(), 300_000.);
2420
2421
let ellipse = Ellipse::new(0.5, 2.);
2422
assert_relative_eq!(ellipse.perimeter(), 8.578423);
2423
2424
let ellipse = Ellipse::new(5., 3.);
2425
assert_relative_eq!(ellipse.perimeter(), 25.526999);
2426
}
2427
2428
#[test]
2429
fn triangle_math() {
2430
let triangle = Triangle2d::new(
2431
Vec2::new(-2.0, -1.0),
2432
Vec2::new(1.0, 4.0),
2433
Vec2::new(7.0, 0.0),
2434
);
2435
assert_eq!(triangle.area(), 21.0, "incorrect area");
2436
assert_eq!(triangle.perimeter(), 22.097439, "incorrect perimeter");
2437
2438
let degenerate_triangle =
2439
Triangle2d::new(Vec2::new(-1., 0.), Vec2::new(0., 0.), Vec2::new(1., 0.));
2440
assert!(degenerate_triangle.is_degenerate());
2441
2442
let acute_triangle =
2443
Triangle2d::new(Vec2::new(-1., 0.), Vec2::new(1., 0.), Vec2::new(0., 5.));
2444
let obtuse_triangle =
2445
Triangle2d::new(Vec2::new(-1., 0.), Vec2::new(1., 0.), Vec2::new(0., 0.5));
2446
2447
assert!(acute_triangle.is_acute());
2448
assert!(!acute_triangle.is_obtuse());
2449
assert!(!obtuse_triangle.is_acute());
2450
assert!(obtuse_triangle.is_obtuse());
2451
}
2452
2453
#[test]
2454
fn triangle_winding_order() {
2455
let mut cw_triangle = Triangle2d::new(
2456
Vec2::new(0.0, 2.0),
2457
Vec2::new(-0.5, -1.2),
2458
Vec2::new(-1.0, -1.0),
2459
);
2460
assert_eq!(cw_triangle.winding_order(), WindingOrder::Clockwise);
2461
2462
let ccw_triangle = Triangle2d::new(
2463
Vec2::new(-1.0, -1.0),
2464
Vec2::new(-0.5, -1.2),
2465
Vec2::new(0.0, 2.0),
2466
);
2467
assert_eq!(ccw_triangle.winding_order(), WindingOrder::CounterClockwise);
2468
2469
// The clockwise triangle should be the same as the counterclockwise
2470
// triangle when reversed
2471
cw_triangle.reverse();
2472
assert_eq!(cw_triangle, ccw_triangle);
2473
2474
let invalid_triangle = Triangle2d::new(
2475
Vec2::new(0.0, 2.0),
2476
Vec2::new(0.0, -1.0),
2477
Vec2::new(0.0, -1.2),
2478
);
2479
assert_eq!(invalid_triangle.winding_order(), WindingOrder::Invalid);
2480
}
2481
2482
#[test]
2483
fn rectangle_math() {
2484
let rectangle = Rectangle::new(3.0, 7.0);
2485
assert_eq!(
2486
rectangle,
2487
Rectangle::from_corners(Vec2::new(-1.5, -3.5), Vec2::new(1.5, 3.5))
2488
);
2489
assert_eq!(rectangle.area(), 21.0, "incorrect area");
2490
assert_eq!(rectangle.perimeter(), 20.0, "incorrect perimeter");
2491
}
2492
2493
#[test]
2494
fn regular_polygon_math() {
2495
let polygon = RegularPolygon::new(3.0, 6);
2496
assert_eq!(polygon.inradius(), 2.598076, "incorrect inradius");
2497
assert_eq!(polygon.side_length(), 3.0, "incorrect side length");
2498
assert_relative_eq!(polygon.area(), 23.38268, epsilon = 0.00001);
2499
assert_eq!(polygon.perimeter(), 18.0, "incorrect perimeter");
2500
assert_eq!(
2501
polygon.internal_angle_degrees(),
2502
120.0,
2503
"incorrect internal angle"
2504
);
2505
assert_eq!(
2506
polygon.internal_angle_radians(),
2507
120_f32.to_radians(),
2508
"incorrect internal angle"
2509
);
2510
assert_eq!(
2511
polygon.external_angle_degrees(),
2512
60.0,
2513
"incorrect external angle"
2514
);
2515
assert_eq!(
2516
polygon.external_angle_radians(),
2517
60_f32.to_radians(),
2518
"incorrect external angle"
2519
);
2520
}
2521
2522
#[test]
2523
fn triangle_circumcenter() {
2524
let triangle = Triangle2d::new(
2525
Vec2::new(10.0, 2.0),
2526
Vec2::new(-5.0, -3.0),
2527
Vec2::new(2.0, -1.0),
2528
);
2529
let (Circle { radius }, circumcenter) = triangle.circumcircle();
2530
2531
// Calculated with external calculator
2532
assert_eq!(radius, 98.34887);
2533
assert_eq!(circumcenter, Vec2::new(-28.5, 92.5));
2534
}
2535
2536
#[test]
2537
fn regular_polygon_vertices() {
2538
let polygon = RegularPolygon::new(1.0, 4);
2539
2540
// Regular polygons have a vertex at the top by default
2541
let mut vertices = polygon.vertices(0.0).into_iter();
2542
assert!((vertices.next().unwrap() - Vec2::Y).length() < 1e-7);
2543
2544
// Rotate by 45 degrees, forming an axis-aligned square
2545
let mut rotated_vertices = polygon.vertices(core::f32::consts::FRAC_PI_4).into_iter();
2546
2547
// Distance from the origin to the middle of a side, derived using Pythagorean theorem
2548
let side_distance = FRAC_1_SQRT_2;
2549
assert!(
2550
(rotated_vertices.next().unwrap() - Vec2::new(-side_distance, side_distance)).length()
2551
< 1e-7,
2552
);
2553
}
2554
}
2555
2556