Skip to main content

bevy_shape/
inset.rs

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