Skip to main content

ply_engine/
math.rs

1#[derive(Debug, Clone, Copy, PartialEq, Default)]
2#[repr(C)]
3pub struct Vector2 {
4    pub x: f32,
5    pub y: f32,
6}
7
8impl Vector2 {
9    pub fn new(x: f32, y: f32) -> Self {
10        Self { x, y }
11    }
12}
13
14impl From<(f32, f32)> for Vector2 {
15    fn from(value: (f32, f32)) -> Self {
16        Self::new(value.0, value.1)
17    }
18}
19
20impl From<macroquad::prelude::Vec2> for Vector2 {
21    fn from(value: macroquad::prelude::Vec2) -> Self {
22        Self::new(value.x, value.y)
23    }
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Default)]
27#[repr(C)]
28pub struct Dimensions {
29    pub width: f32,
30    pub height: f32,
31}
32
33impl Dimensions {
34    pub fn new(width: f32, height: f32) -> Self {
35        Self { width, height }
36    }
37}
38
39impl From<(f32, f32)> for Dimensions {
40    fn from(value: (f32, f32)) -> Self {
41        Self::new(value.0, value.1)
42    }
43}
44
45/// An axis-aligned rectangle defined by its top-left position and dimensions.
46#[derive(Debug, Clone, Copy, PartialEq, Default)]
47#[repr(C)]
48pub struct BoundingBox {
49    pub x: f32,
50    pub y: f32,
51    pub width: f32,
52    pub height: f32,
53}
54
55impl BoundingBox {
56    pub fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
57        Self {
58            x,
59            y,
60            width,
61            height,
62        }
63    }
64}
65
66/// Classifies a rotation angle into common fast-path categories.
67#[derive(Debug, Clone, Copy, PartialEq)]
68pub enum AngleType {
69    /// 0° (or 360°) — no rotation needed.
70    Zero,
71    /// 90° clockwise.
72    Right90,
73    /// 180°.
74    Straight180,
75    /// 270° clockwise (= 90° counter-clockwise).
76    Right270,
77    /// An angle that doesn't match any fast-path.
78    Arbitrary(f32),
79}
80
81/// Classifies a rotation in radians into an [`AngleType`].
82/// Normalises to `[0, 2π)` first, then checks within `EPS` of each cardinal.
83pub fn classify_angle(radians: f32) -> AngleType {
84    let normalized = radians.rem_euclid(std::f32::consts::TAU);
85    const EPS: f32 = 0.001;
86    if normalized < EPS || (std::f32::consts::TAU - normalized) < EPS {
87        AngleType::Zero
88    } else if (normalized - std::f32::consts::FRAC_PI_2).abs() < EPS {
89        AngleType::Right90
90    } else if (normalized - std::f32::consts::PI).abs() < EPS {
91        AngleType::Straight180
92    } else if (normalized - 3.0 * std::f32::consts::FRAC_PI_2).abs() < EPS {
93        AngleType::Right270
94    } else {
95        AngleType::Arbitrary(normalized)
96    }
97}
98
99use crate::layout::CornerRadius;
100
101/// Computes the axis-aligned bounding box of a rounded rectangle after rotation.
102///
103/// Uses the Minkowski-sum approach for equal corner radii:
104///   `AABB_w = |(w-2r)·cosθ| + |(h-2r)·sinθ| + 2r`
105///   `AABB_h = |(w-2r)·sinθ| + |(h-2r)·cosθ| + 2r`
106///
107/// For non-uniform radii, uses the maximum radius as a conservative approximation.
108/// Returns `(effective_width, effective_height)`.
109pub fn compute_rotated_aabb(
110    width: f32,
111    height: f32,
112    corner_radius: &CornerRadius,
113    rotation_radians: f32,
114) -> (f32, f32) {
115    let angle = classify_angle(rotation_radians);
116    match angle {
117        AngleType::Zero => (width, height),
118        AngleType::Straight180 => (width, height),
119        AngleType::Right90 | AngleType::Right270 => (height, width),
120        AngleType::Arbitrary(theta) => {
121            let r = corner_radius
122                .top_left
123                .max(corner_radius.top_right)
124                .max(corner_radius.bottom_left)
125                .max(corner_radius.bottom_right)
126                .min(width / 2.0)
127                .min(height / 2.0);
128
129            let cos_t = theta.cos().abs();
130            let sin_t = theta.sin().abs();
131            let inner_w = (width - 2.0 * r).max(0.0);
132            let inner_h = (height - 2.0 * r).max(0.0);
133
134            let eff_w = inner_w * cos_t + inner_h * sin_t + 2.0 * r;
135            let eff_h = inner_w * sin_t + inner_h * cos_t + 2.0 * r;
136            (eff_w, eff_h)
137        }
138    }
139}