1use std::ops::Sub;
3
4use enum_ordinalize::Ordinalize;
5use glam::{IVec2, Vec2};
6
7use crate::GridPoint;
8
9use super::GridSize;
10
11#[derive(Eq, PartialEq, Clone, Copy, Debug, Ordinalize)]
15pub enum Pivot {
16 TopLeft,
18 TopCenter,
20 TopRight,
22 LeftCenter,
24 RightCenter,
26 BottomLeft,
28 BottomCenter,
30 BottomRight,
32 Center,
34}
35
36impl Pivot {
37 #[inline]
40 pub fn axis(&self) -> IVec2 {
41 match self {
42 Pivot::TopLeft => IVec2::new(1, -1),
43 Pivot::TopRight => IVec2::new(-1, -1),
44 Pivot::Center => IVec2::new(1, 1),
45 Pivot::BottomLeft => IVec2::new(1, 1),
46 Pivot::BottomRight => IVec2::new(-1, 1),
47 Pivot::TopCenter => IVec2::new(1, -1),
48 Pivot::LeftCenter => IVec2::new(1, 1),
49 Pivot::RightCenter => IVec2::new(-1, 1),
50 Pivot::BottomCenter => IVec2::new(1, 1),
51 }
52 }
53
54 #[inline]
57 pub fn normalized(&self) -> Vec2 {
58 match self {
59 Pivot::TopLeft => Vec2::new(0.0, 1.0),
60 Pivot::TopRight => Vec2::new(1.0, 1.0),
61 Pivot::Center => Vec2::new(0.5, 0.5),
62 Pivot::BottomLeft => Vec2::new(0.0, 0.0),
63 Pivot::BottomRight => Vec2::new(1.0, 0.0),
64 Pivot::TopCenter => Vec2::new(0.5, 1.0),
65 Pivot::LeftCenter => Vec2::new(0.0, 0.5),
66 Pivot::RightCenter => Vec2::new(1.0, 0.5),
67 Pivot::BottomCenter => Vec2::new(0.5, 0.0),
68 }
69 }
70
71 #[inline]
73 pub fn transform_point(&self, grid_point: impl GridPoint) -> IVec2 {
74 grid_point.to_ivec2() * self.axis()
75 }
76
77 #[inline]
79 pub fn pivot_position(&self, grid_size: impl GridSize) -> IVec2 {
80 (grid_size.to_vec2().sub(1.0) * self.normalized())
81 .round()
82 .as_ivec2()
83 }
84}
85
86#[derive(Debug, Clone, Copy, Eq, PartialEq)]
88pub struct PivotedPoint {
89 pub point: IVec2,
90 pub pivot: Option<Pivot>,
91}
92
93impl PivotedPoint {
94 pub fn new(xy: impl GridPoint, pivot: Pivot) -> Self {
95 Self {
96 point: xy.to_ivec2(),
97 pivot: Some(pivot),
98 }
99 }
100
101 pub fn calculate(&self, grid_size: impl GridSize) -> IVec2 {
106 if let Some(pivot) = self.pivot {
107 pivot.pivot_position(grid_size) + pivot.transform_point(self.point)
108 } else {
109 self.point
110 }
111 }
112
113 pub fn with_default_pivot(&self, default_pivot: Pivot) -> PivotedPoint {
116 Self {
117 point: self.point,
118 pivot: Some(self.pivot.unwrap_or(default_pivot)),
119 }
120 }
121}
122
123impl<T: GridPoint> From<T> for PivotedPoint {
124 fn from(value: T) -> Self {
125 Self {
126 point: value.to_ivec2(),
127 pivot: None,
128 }
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135
136 #[test]
137 fn grid_pivot_size_offset() {
138 assert_eq!([4, 4], Pivot::TopRight.pivot_position([5, 5]).to_array());
139 assert_eq!([2, 2], Pivot::Center.pivot_position([5, 5]).to_array());
140 assert_eq!([3, 3], Pivot::TopRight.pivot_position([4, 4]).to_array());
141 assert_eq!([2, 2], Pivot::Center.pivot_position([4, 4]).to_array());
142 }
143
144 #[test]
145 fn pivoted_point() {
146 let pp = [1, 1].pivot(Pivot::TopLeft);
147 assert_eq!([1, 3], pp.calculate([5, 5]).to_array());
148
149 let pp = [1, 1].pivot(Pivot::TopRight);
150 assert_eq!([3, 3], pp.calculate([5, 5]).to_array());
151
152 let pp = [1, 1].pivot(Pivot::TopRight);
153 assert_eq!([4, 4], pp.calculate([6, 6]).to_array());
154
155 let pp = [1, 1].pivot(Pivot::Center);
156 assert_eq!([4, 4], pp.calculate([6, 6]).to_array());
157
158 let pp = [1, 1].pivot(Pivot::Center);
159 assert_eq!([3, 3], pp.calculate([5, 5]).to_array());
160
161 let pp = [0, 0].pivot(Pivot::BottomRight);
162 assert_eq!([8, 0], pp.calculate([9, 9]).to_array());
163 }
164
165 #[test]
166 fn center_negative() {
167 let p = [-5, -5].pivot(Pivot::Center);
168 assert_eq!([0, 0], p.calculate([10, 10]).to_array());
169 }
170}