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