1pub fn twice_area_fp2(ring: &[[i64; 2]]) -> u128 {
20 let n = ring.len();
21 if n < 3 {
22 return 0;
23 }
24
25 let mut signed: i128 = 0;
26 for i in 0..n {
27 let j = if i + 1 == n { 0 } else { i + 1 };
28 let xi = ring[i][0] as i128;
29 let yi = ring[i][1] as i128;
30 let xj = ring[j][0] as i128;
31 let yj = ring[j][1] as i128;
32 signed += xi * yj - xj * yi;
33 }
34
35 signed.unsigned_abs()
36}
37
38pub fn area_display(twice_area: u128, area_divisor: u128) -> u64 {
40 if area_divisor == 0 {
41 return twice_area as u64;
42 }
43 (twice_area / area_divisor) as u64
44}
45
46pub fn areas_conserved(original: u128, parts: &[u128]) -> bool {
48 let sum: u128 = parts.iter().sum();
49 sum == original
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55
56 const M: i64 = 1_000_000;
57
58 #[test]
59 fn square_10m_twice_area_matches_move() {
60 let ring = vec![[0, 0], [10 * M, 0], [10 * M, 10 * M], [0, 10 * M]];
61 let area = twice_area_fp2(&ring);
62 assert_eq!(
63 area, 200_000_000_000_000_u128,
64 "square area mismatch: got {area}"
65 );
66 }
67
68 #[test]
69 fn triangle_twice_area_correct() {
70 let ring = vec![[0, 0], [10 * M, 0], [0, 10 * M]];
71 let area = twice_area_fp2(&ring);
72 assert_eq!(area, 100_000_000_000_000_u128);
73 }
74
75 #[test]
76 fn area_display_converts_correctly() {
77 let twice_area = 200_000_000_000_000_u128;
78 assert_eq!(
79 area_display(twice_area, crate::constants::AREA_DIVISOR),
80 100
81 );
82 }
83
84 #[test]
85 fn area_display_1m_x_1m_is_1_m2() {
86 let ring = vec![[0, 0], [M, 0], [M, M], [0, M]];
87 let twice_area = twice_area_fp2(&ring);
88 assert_eq!(twice_area, 2_000_000_000_000_u128);
89 assert_eq!(area_display(twice_area, crate::constants::AREA_DIVISOR), 1);
90 }
91
92 #[test]
93 fn twice_area_fp2_triangle_with_diagonal_edge() {
94 let ring = vec![[0, 0], [3, 0], [0, 4]];
95 assert_eq!(twice_area_fp2(&ring), 12);
96 }
97
98 #[test]
99 fn twice_area_fp2_l_shape_matches_expected() {
100 let ring = vec![[0, 0], [4, 0], [4, 2], [2, 2], [2, 4], [0, 4]];
101 assert_eq!(twice_area_fp2(&ring), 24);
102 }
103
104 #[test]
105 fn twice_area_fp2_degenerate_two_vertices_is_zero() {
106 let ring = vec![[0, 0], [10, 0]];
107 assert_eq!(twice_area_fp2(&ring), 0);
108 }
109
110 #[test]
111 fn area_display_scales_large_values() {
112 assert_eq!(
113 area_display(2_000_000_000_000_000_000, 2_000_000_000_000),
114 1_000_000
115 );
116 }
117
118 #[test]
119 fn area_display_zero_area_is_zero() {
120 assert_eq!(area_display(0, 1), 0);
121 }
122
123 #[test]
124 fn areas_conserved_accepts_exact_sum() {
125 assert!(areas_conserved(30, &[10, 20]));
126 }
127
128 #[test]
129 fn areas_conserved_rejects_overage() {
130 assert!(!areas_conserved(30, &[10, 21]));
131 }
132
133 #[test]
134 fn area_conservation_check() {
135 let square = vec![[0, 0], [10 * M, 0], [10 * M, 10 * M], [0, 10 * M]];
136 let original = twice_area_fp2(&square);
137
138 let t1 = vec![[0, 0], [10 * M, 0], [10 * M, 10 * M]];
139 let t2 = vec![[0, 0], [10 * M, 10 * M], [0, 10 * M]];
140
141 let part_areas = vec![twice_area_fp2(&t1), twice_area_fp2(&t2)];
142
143 assert!(
144 areas_conserved(original, &part_areas),
145 "area not conserved: original={original}, sum={}",
146 part_areas.iter().sum::<u128>()
147 );
148 }
149
150 #[test]
151 fn twice_area_fp2_square_ring_matches() {
152 let ring = vec![[0i64, 0], [10 * M, 0], [10 * M, 10 * M], [0, 10 * M]];
153 let area = twice_area_fp2(&ring);
154 assert_eq!(area, 200_000_000_000_000_u128);
155 }
156
157 #[test]
158 fn area_is_order_independent_for_ccw_and_cw() {
159 let ccw = vec![[0, 0], [10 * M, 0], [10 * M, 10 * M], [0, 10 * M]];
160 let cw = vec![[0, 0], [0, 10 * M], [10 * M, 10 * M], [10 * M, 0]];
161 assert_eq!(twice_area_fp2(&ccw), twice_area_fp2(&cw));
162 }
163
164 #[test]
165 fn degenerate_ring_returns_zero() {
166 let ring = vec![[0, 0], [M, 0]];
167 assert_eq!(twice_area_fp2(&ring), 0);
168 }
169
170 #[test]
171 fn negative_coords_are_handled_correctly() {
172 let ring = vec![[-M, 0], [M, 0], [0, M]];
176 let translated = vec![[0, 0], [2 * M, 0], [M, M]];
178 let expected = twice_area_fp2(&translated);
179 assert_eq!(twice_area_fp2(&ring), expected);
180 assert_eq!(expected, 2 * (M as u128) * (M as u128));
182 }
183
184 #[test]
185 fn cw_and_ccw_negative_polygon_match() {
186 let cw = vec![
189 [-91 * M, -70 * M],
190 [-120 * M, 177 * M],
191 [M, 56 * M],
192 [110 * M, 210 * M],
193 [138 * M, -70 * M],
194 ];
195 let ccw: Vec<[i64; 2]> = cw.iter().rev().copied().collect();
196 let area_cw = twice_area_fp2(&cw);
197 let area_ccw = twice_area_fp2(&ccw);
198 assert_eq!(area_cw, area_ccw, "winding should not affect |2·area|");
199 assert_eq!(area_cw, 90_064_u128 * (M as u128) * (M as u128));
202 }
203
204 #[test]
205 fn matches_move_on_u64_domain() {
206 let ring = vec![
210 [0, 0],
211 [10 * M, 15 * M],
212 [30 * M, 5 * M],
213 [20 * M, 25 * M],
214 [5 * M, 20 * M],
215 ];
216 assert_eq!(twice_area_fp2(&ring), 525_u128 * (M as u128) * (M as u128));
217 }
218}