bevy_math/primitives/
inset.rs

1use crate::{
2    ops,
3    primitives::{
4        Capsule2d, Circle, CircularSegment, Primitive2d, Rectangle, RegularPolygon, Rhombus,
5        Triangle2d,
6    },
7    Vec2,
8};
9
10/// A primitive that can be resized uniformly.
11///
12/// See documentation on [`Inset::inset`].
13///
14/// See also [`ToRing`](crate::primitives::ToRing).
15pub trait Inset: Primitive2d {
16    /// Create a new version of this primitive that is resized uniformly.
17    /// That is, it resizes the shape inwards such that for the lines between vertices,
18    /// it creates new parallel lines that are `distance` inwards from the original lines.
19    ///
20    /// This is useful for creating smaller shapes or making outlines of `distance` thickness with [`Ring`](crate::primitives::Ring).
21    ///
22    /// See also [`ToRing::to_ring`](crate::primitives::ToRing::to_ring)
23    fn inset(self, distance: f32) -> Self;
24}
25
26impl Inset for Circle {
27    fn inset(mut self, distance: f32) -> Self {
28        self.radius -= distance;
29        self
30    }
31}
32
33impl Inset for Triangle2d {
34    fn inset(self, distance: f32) -> Self {
35        fn find_inset_point(a: Vec2, b: Vec2, c: Vec2, distance: f32) -> Vec2 {
36            let unit_vector_ab = (b - a).normalize();
37            let unit_vector_ac = (c - a).normalize();
38            let half_angle_bac = unit_vector_ab.angle_to(unit_vector_ac) / 2.0;
39            let mean = (unit_vector_ab + unit_vector_ac) / 2.0;
40            let direction = mean.normalize();
41            let magnitude = distance / ops::sin(half_angle_bac);
42            a + direction * magnitude
43        }
44
45        let [a, b, c] = self.vertices;
46
47        let new_a = find_inset_point(a, b, c, distance);
48        let new_b = find_inset_point(b, c, a, distance);
49        let new_c = find_inset_point(c, a, b, distance);
50
51        Self::new(new_a, new_b, new_c)
52    }
53}
54
55impl Inset for Rhombus {
56    fn inset(mut self, distance: f32) -> Self {
57        let [half_width, half_height] = self.half_diagonals.into();
58        let angle = ops::atan(half_height / half_width);
59        let x_offset = distance / ops::sin(angle);
60        let y_offset = distance / ops::cos(angle);
61        self.half_diagonals -= Vec2::new(x_offset, y_offset);
62        self
63    }
64}
65
66impl Inset for Capsule2d {
67    fn inset(mut self, distance: f32) -> Self {
68        self.radius -= distance;
69        self
70    }
71}
72
73impl Inset for Rectangle {
74    fn inset(mut self, distance: f32) -> Self {
75        self.half_size -= Vec2::splat(distance);
76        self
77    }
78}
79
80impl Inset for CircularSegment {
81    fn inset(self, distance: f32) -> Self {
82        let old_arc = self.arc;
83        let radius = old_arc.radius - distance;
84        let apothem = old_arc.apothem() + distance;
85        // https://en.wikipedia.org/wiki/Circular_segment
86        let half_angle = ops::acos(apothem / radius);
87        Self::new(radius, half_angle)
88    }
89}
90
91impl Inset for RegularPolygon {
92    fn inset(mut self, distance: f32) -> Self {
93        let half_angle = self.internal_angle_radians() / 2.0;
94        let offset = distance / ops::sin(half_angle);
95        self.circumcircle.radius -= offset;
96        self
97    }
98}