1use std::{
29 fmt::{Debug, Display},
30 ops::{Add, AddAssign, Neg},
31};
32
33use fxhash::FxHashMap;
34
35use crate::core::{
36 algebra::{Matrix2, Scalar, SimdPartialOrd, Vector2},
37 math::{Number, Rect},
38 reflect::prelude::*,
39 visitor::prelude::*,
40};
41
42use super::OptionTileRect;
43
44#[derive(Copy, Clone, Hash, Eq, PartialEq, Visit, Reflect)]
53pub struct OrthoTransformation(i8);
54
55#[derive(Clone, Debug, Visit)]
57pub struct OrthoTransformMap<V: Visit + Default> {
58 transform: OrthoTransformation,
59 map: FxHashMap<Vector2<i32>, V>,
60}
61
62impl Default for OrthoTransformation {
63 fn default() -> Self {
64 Self::identity()
65 }
66}
67
68const ROTATION_MATRICES: [Matrix2<f32>; 4] = [
69 Matrix2::new(1.0, 0.0, 0.0, 1.0),
70 Matrix2::new(0.0, -1.0, 1.0, 0.0),
71 Matrix2::new(-1.0, 0.0, 0.0, -1.0),
72 Matrix2::new(0.0, 1.0, -1.0, 0.0),
73];
74
75const X_FLIP_MATRIX: Matrix2<f32> = Matrix2::new(-1.0, 0.0, 0.0, 1.0);
76
77impl OrthoTransformation {
78 #[inline]
80 pub const fn identity() -> Self {
81 Self(1)
82 }
83 #[inline]
88 pub const fn new(flipped: bool, rotation: i8) -> Self {
89 let rotation = rotation.rem_euclid(4);
90 Self(if flipped { -rotation - 1 } else { rotation + 1 })
91 }
92 pub fn all() -> impl Iterator<Item = OrthoTransformation> {
94 [-4i8, -3, -2, -1, 1, 2, 3, 4]
95 .into_iter()
96 .map(OrthoTransformation)
97 }
98 #[inline]
100 pub const fn is_identity(&self) -> bool {
101 self.0 == 1
102 }
103 #[inline]
107 pub const fn inverted(self) -> Self {
108 Self(match self.0 {
109 1 => 1,
110 2 => 4,
111 3 => 3,
112 4 => 2,
113 -1 => -1,
114 -2 => -2,
115 -3 => -3,
116 -4 => -4,
117 _ => unreachable!(),
118 })
119 }
120 #[inline]
122 pub const fn is_flipped(&self) -> bool {
123 self.0 < 0
124 }
125 #[inline]
129 pub const fn rotation(&self) -> i8 {
130 self.0.abs() - 1
131 }
132 pub fn matrix(&self) -> Matrix2<f32> {
134 let matrix = if self.is_flipped() {
135 X_FLIP_MATRIX
136 } else {
137 Matrix2::identity()
138 };
139 ROTATION_MATRICES[self.rotation() as usize] * matrix
140 }
141}
142
143impl Debug for OrthoTransformation {
144 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145 f.debug_tuple("OrthoTransformation")
146 .field(&self.0)
147 .field(&self.is_flipped())
148 .field(&self.rotation())
149 .finish()
150 }
151}
152
153impl Display for OrthoTransformation {
154 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155 let rotation = match self.rotation() {
156 0 => 0,
157 1 => 90,
158 2 => 180,
159 3 => 270,
160 _ => unreachable!(),
161 };
162 if self.is_flipped() {
163 write!(f, "rotate({rotation})(flipped)")
164 } else {
165 write!(f, "rotate({rotation})")
166 }
167 }
168}
169
170pub trait OrthoTransform: Sized {
174 fn x_flipped(self) -> Self;
176 fn y_flipped(self) -> Self {
178 self.x_flipped().rotated(2)
179 }
180 fn rotated(self, amount: i8) -> Self;
182 fn transformed(self, amount: OrthoTransformation) -> Self {
184 (if amount.is_flipped() {
185 self.x_flipped()
186 } else {
187 self
188 })
189 .rotated(amount.rotation())
190 }
191}
192
193impl OrthoTransform for OrthoTransformation {
194 fn x_flipped(self) -> Self {
195 Self(match self.0 {
196 1 => -1,
197 2 => -4,
198 3 => -3,
199 4 => -2,
200 -1 => 1,
201 -2 => 4,
202 -3 => 3,
203 -4 => 2,
204 _ => unreachable!(),
205 })
206 }
207 fn rotated(self, amount: i8) -> Self {
208 let amount = amount.rem_euclid(4);
209 if self.0 > 0 {
210 Self((self.0 + amount - 1).rem_euclid(4) + 1)
211 } else {
212 Self(-(self.0.abs() + amount - 1).rem_euclid(4) - 1)
213 }
214 }
215}
216
217impl<V: Neg<Output = V> + Scalar + Clone> OrthoTransform for Vector2<V> {
218 fn x_flipped(self) -> Self {
219 Self::new(-self.x.clone(), self.y.clone())
220 }
221
222 fn rotated(self, amount: i8) -> Self {
223 let amount = amount.rem_euclid(4);
224 match amount {
225 0 => self,
226 1 => Self::new(-self.y.clone(), self.x.clone()),
227 2 => Self::new(-self.x.clone(), -self.y.clone()),
228 3 => Self::new(self.y.clone(), -self.x.clone()),
229 _ => unreachable!(),
230 }
231 }
232}
233
234impl<V: Number + SimdPartialOrd + Add + AddAssign + Neg<Output = V> + Scalar> OrthoTransform
235 for Rect<V>
236{
237 fn x_flipped(self) -> Self {
238 Rect::from_points(
239 self.position.x_flipped(),
240 (self.position + self.size).x_flipped(),
241 )
242 }
243
244 fn rotated(self, amount: i8) -> Self {
245 Rect::from_points(
246 self.position.rotated(amount),
247 (self.position + self.size).rotated(amount),
248 )
249 }
250}
251
252impl<V: Visit + Default> OrthoTransformMap<V> {
253 pub fn bounding_rect(&self) -> OptionTileRect {
255 let mut result = OptionTileRect::default();
256 for position in self.keys() {
257 result.push(position);
258 }
259 result
260 }
261
262 #[inline]
264 pub fn clear(&mut self) {
265 self.map.clear()
266 }
267 #[inline]
269 pub fn is_empty(&self) -> bool {
270 self.map.is_empty()
271 }
272 #[inline]
274 pub fn contains_key(&self, position: Vector2<i32>) -> bool {
275 self.map
276 .contains_key(&position.transformed(self.transform.inverted()))
277 }
278 #[inline]
280 pub fn remove(&mut self, position: Vector2<i32>) -> Option<V> {
281 self.map
282 .remove(&position.transformed(self.transform.inverted()))
283 }
284 #[inline]
286 pub fn get(&self, position: Vector2<i32>) -> Option<&V> {
287 self.map
288 .get(&position.transformed(self.transform.inverted()))
289 }
290 #[inline]
292 pub fn get_mut(&mut self, position: Vector2<i32>) -> Option<&mut V> {
293 self.map
294 .get_mut(&position.transformed(self.transform.inverted()))
295 }
296 #[inline]
298 pub fn insert(&mut self, position: Vector2<i32>, value: V) -> Option<V> {
299 self.map
300 .insert(position.transformed(self.transform.inverted()), value)
301 }
302 #[inline]
304 pub fn iter(&self) -> Iter<V> {
305 Iter(self.transform, self.map.iter())
306 }
307 #[inline]
309 pub fn keys(&self) -> Keys<V> {
310 Keys(self.transform, self.map.keys())
311 }
312 #[inline]
314 pub fn values(&self) -> std::collections::hash_map::Values<Vector2<i32>, V> {
315 self.map.values()
316 }
317}
318
319impl<V: Visit + Default> Default for OrthoTransformMap<V> {
320 fn default() -> Self {
321 Self {
322 transform: Default::default(),
323 map: Default::default(),
324 }
325 }
326}
327
328impl<V: Visit + Default> OrthoTransform for OrthoTransformMap<V> {
329 fn x_flipped(self) -> Self {
330 Self {
331 transform: self.transform.x_flipped(),
332 map: self.map,
333 }
334 }
335
336 fn rotated(self, amount: i8) -> Self {
337 Self {
338 transform: self.transform.rotated(amount),
339 map: self.map,
340 }
341 }
342}
343pub struct Iter<'a, V>(
345 OrthoTransformation,
346 std::collections::hash_map::Iter<'a, Vector2<i32>, V>,
347);
348
349pub struct Keys<'a, V>(
351 OrthoTransformation,
352 std::collections::hash_map::Keys<'a, Vector2<i32>, V>,
353);
354
355impl<'a, V> Iterator for Iter<'a, V> {
356 type Item = (Vector2<i32>, &'a V);
357
358 fn next(&mut self) -> Option<Self::Item> {
359 let (k, v) = self.1.next()?;
360 Some((k.transformed(self.0), v))
361 }
362}
363impl<V> Iterator for Keys<'_, V> {
364 type Item = Vector2<i32>;
365
366 fn next(&mut self) -> Option<Self::Item> {
367 let k = self.1.next()?;
368 Some(k.transformed(self.0))
369 }
370}
371
372#[derive(Clone, Copy, Debug, PartialEq, Eq)]
376pub struct TransformSetCell(Vector2<i32>, OrthoTransformation);
377
378fn transform_to_cell_position(rotation: i8) -> Vector2<i32> {
379 match rotation {
380 -1 => Vector2::new(3, 0),
381 -2 => Vector2::new(3, 1),
382 -3 => Vector2::new(2, 1),
383 -4 => Vector2::new(2, 0),
384 1 => Vector2::new(0, 0),
385 2 => Vector2::new(1, 0),
386 3 => Vector2::new(1, 1),
387 4 => Vector2::new(0, 1),
388 _ => panic!(),
389 }
390}
391
392fn cell_position_to_transform(position: Vector2<i32>) -> i8 {
393 match (position.x, position.y) {
394 (3, 0) => -1,
395 (3, 1) => -2,
396 (2, 1) => -3,
397 (2, 0) => -4,
398 (0, 0) => 1,
399 (1, 0) => 2,
400 (1, 1) => 3,
401 (0, 1) => 4,
402 _ => panic!(),
403 }
404}
405
406impl TransformSetCell {
407 pub fn into_position(self) -> Vector2<i32> {
409 self.0 + transform_to_cell_position(self.1 .0)
410 }
411 pub fn from_position(position: Vector2<i32>) -> Self {
413 let rem = Vector2::new(position.x.rem_euclid(4), position.y.rem_euclid(2));
414 let pos = Vector2::new(position.x - rem.x, position.y - rem.y);
415 TransformSetCell(pos, OrthoTransformation(cell_position_to_transform(rem)))
416 }
417 pub fn with_transformation(self, trans: OrthoTransformation) -> Self {
425 TransformSetCell(self.0, trans)
426 }
427}
428
429impl OrthoTransform for TransformSetCell {
430 fn x_flipped(self) -> Self {
431 Self(self.0, self.1.x_flipped())
432 }
433
434 fn rotated(self, amount: i8) -> Self {
435 Self(self.0, self.1.rotated(amount))
436 }
437}
438
439#[cfg(test)]
440mod tests {
441 use super::*;
442 use crate::core::algebra::Point2;
443 use OrthoTransformation as Trans;
444 use TransformSetCell as Cell;
445
446 #[test]
447 fn identity() {
448 assert_eq!(Trans::identity(), Trans::new(false, 0));
449 for v in [
450 Vector2::new(2, 1),
451 Vector2::new(1, 2),
452 Vector2::new(-1, 5),
453 Vector2::new(-2, -2),
454 ] {
455 assert_eq!(v.transformed(Trans::identity()), v);
456 }
457 }
458 fn matrix_check(trans: OrthoTransformation) {
459 let v = Vector2::new(1.0, 0.5);
460 let m = trans.matrix().to_homogeneous();
461 let p = m.transform_point(&Point2::from(v)).coords;
462 assert_eq!(p, v.transformed(trans), "{trans}");
463 }
464 #[test]
465 fn matrix() {
466 for i in 0..4 {
467 matrix_check(Trans::new(false, i))
468 }
469 for i in 0..4 {
470 matrix_check(Trans::new(true, i))
471 }
472 }
473 #[test]
474 fn rotate_4() {
475 assert_eq!(Trans::identity(), Trans::new(false, 4))
476 }
477 #[test]
478 fn invert() {
479 for i in [-4, -3, -2, -1, 1, 2, 3, 4] {
480 assert_eq!(
481 Trans(i).transformed(Trans(i).inverted()),
482 Trans::identity(),
483 "{:?}: {:?} {:?}",
484 i,
485 Trans(i),
486 Trans(i).inverted()
487 );
488 }
489 }
490 #[test]
491 fn inverse_undoes_transform() {
492 for i in [-4, -3, -2, -1, 1, 2, 3, 4] {
493 assert_eq!(
494 Vector2::new(2, 3)
495 .transformed(Trans(i))
496 .transformed(Trans(i).inverted()),
497 Vector2::new(2, 3),
498 );
499 }
500 }
501 #[test]
502 fn rotate_trans() {
503 assert_eq!(Trans::new(false, 0).rotated(0), Trans::new(false, 0));
504 assert_eq!(Trans::new(true, 0).rotated(0), Trans::new(true, 0));
505 assert_eq!(Trans::new(true, 2).rotated(0), Trans::new(true, 2));
506 assert_eq!(Trans::new(false, 0).rotated(1), Trans::new(false, 1));
507 assert_eq!(Trans::new(true, 0).rotated(1), Trans::new(true, 1));
508 assert_eq!(Trans::new(true, 2).rotated(1), Trans::new(true, 3));
509 assert_eq!(Trans::new(false, 0).rotated(2), Trans::new(false, 2));
510 assert_eq!(Trans::new(true, 0).rotated(2), Trans::new(true, 2));
511 assert_eq!(Trans::new(true, 2).rotated(2), Trans::new(true, 0));
512 }
513 #[test]
514 fn flipped_trans() {
515 assert_eq!(Trans::new(false, 0).x_flipped(), Trans::new(true, 0));
516 assert_eq!(Trans::new(true, 0).x_flipped(), Trans::new(false, 0));
517 assert_eq!(Trans::new(true, 2).x_flipped(), Trans::new(false, 2));
518 assert_eq!(Trans::new(true, 1).x_flipped(), Trans::new(false, 3));
519 assert_eq!(Trans::new(false, 0).y_flipped(), Trans::new(true, 2));
520 assert_eq!(Trans::new(true, 0).y_flipped(), Trans::new(false, 2));
521 assert_eq!(Trans::new(true, 2).y_flipped(), Trans::new(false, 0));
522 assert_eq!(Trans::new(true, 1).y_flipped(), Trans::new(false, 1));
523 }
524 #[test]
525 fn rotate_vector() {
526 assert_eq!(Vector2::new(1, 0).rotated(0), Vector2::new(1, 0));
527 assert_eq!(Vector2::new(0, 1).rotated(0), Vector2::new(0, 1));
528 assert_eq!(Vector2::new(2, 3).rotated(0), Vector2::new(2, 3));
529 assert_eq!(Vector2::new(1, 0).rotated(1), Vector2::new(0, 1));
530 assert_eq!(Vector2::new(0, 1).rotated(1), Vector2::new(-1, 0));
531 assert_eq!(Vector2::new(2, 3).rotated(1), Vector2::new(-3, 2));
532 assert_eq!(Vector2::new(1, 0).rotated(2), Vector2::new(-1, 0));
533 assert_eq!(Vector2::new(0, 1).rotated(2), Vector2::new(0, -1));
534 assert_eq!(Vector2::new(2, 3).rotated(2), Vector2::new(-2, -3));
535 assert_eq!(Vector2::new(1, 0).rotated(3), Vector2::new(0, -1));
536 assert_eq!(Vector2::new(0, 1).rotated(3), Vector2::new(1, 0));
537 assert_eq!(Vector2::new(2, 3).rotated(3), Vector2::new(3, -2));
538 assert_eq!(Vector2::new(1, 0).rotated(4), Vector2::new(1, 0));
539 assert_eq!(Vector2::new(0, 1).rotated(4), Vector2::new(0, 1));
540 assert_eq!(Vector2::new(2, 3).rotated(4), Vector2::new(2, 3));
541 }
542 #[test]
543 fn flipped_vector() {
544 assert_eq!(Vector2::new(1, 0).x_flipped(), Vector2::new(-1, 0));
545 assert_eq!(Vector2::new(0, 1).x_flipped(), Vector2::new(0, 1));
546 assert_eq!(Vector2::new(2, 3).x_flipped(), Vector2::new(-2, 3));
547 assert_eq!(Vector2::new(1, 0).y_flipped(), Vector2::new(1, 0));
548 assert_eq!(Vector2::new(0, 1).y_flipped(), Vector2::new(0, -1));
549 assert_eq!(Vector2::new(2, 3).y_flipped(), Vector2::new(2, -3));
550 }
551 #[test]
552 fn flipped() {
553 assert!(!Trans::new(false, -3).is_flipped());
554 assert!(!Trans::new(false, -2).is_flipped());
555 assert!(!Trans::new(false, -1).is_flipped());
556 assert!(!Trans::new(false, 0).is_flipped());
557 assert!(!Trans::new(false, 1).is_flipped());
558 assert!(!Trans::new(false, 2).is_flipped());
559 assert!(!Trans::new(false, 3).is_flipped());
560 assert!(!Trans::new(false, 4).is_flipped());
561 assert!(!Trans::new(false, 5).is_flipped());
562 assert!(Trans::new(true, -3).is_flipped());
563 assert!(Trans::new(true, -2).is_flipped());
564 assert!(Trans::new(true, -1).is_flipped());
565 assert!(Trans::new(true, 0).is_flipped());
566 assert!(Trans::new(true, 1).is_flipped());
567 assert!(Trans::new(true, 2).is_flipped());
568 assert!(Trans::new(true, 3).is_flipped());
569 assert!(Trans::new(true, 4).is_flipped());
570 assert!(Trans::new(true, 5).is_flipped());
571 }
572 #[test]
573 fn rotate_amount() {
574 assert_eq!(Trans::new(false, -3).rotation(), 1);
575 assert_eq!(Trans::new(false, -2).rotation(), 2);
576 assert_eq!(Trans::new(false, -1).rotation(), 3);
577 assert_eq!(Trans::new(false, 0).rotation(), 0);
578 assert_eq!(Trans::new(false, 1).rotation(), 1);
579 assert_eq!(Trans::new(false, 2).rotation(), 2);
580 assert_eq!(Trans::new(false, 3).rotation(), 3);
581 assert_eq!(Trans::new(false, 4).rotation(), 0);
582 assert_eq!(Trans::new(false, 5).rotation(), 1);
583 }
584 #[test]
585 fn flipped_rotate_amount() {
586 assert_eq!(Trans::new(true, -3).rotation(), 1);
587 assert_eq!(Trans::new(true, -2).rotation(), 2);
588 assert_eq!(Trans::new(true, -1).rotation(), 3);
589 assert_eq!(Trans::new(true, 0).rotation(), 0);
590 assert_eq!(Trans::new(true, 1).rotation(), 1);
591 assert_eq!(Trans::new(true, 2).rotation(), 2);
592 assert_eq!(Trans::new(true, 3).rotation(), 3);
593 assert_eq!(Trans::new(true, 4).rotation(), 0);
594 assert_eq!(Trans::new(true, 5).rotation(), 1);
595 }
596 #[test]
597 fn double_x_flip() {
598 assert_eq!(Trans::identity(), Trans::identity().x_flipped().x_flipped())
599 }
600 #[test]
601 fn double_y_flip() {
602 assert_eq!(Trans::identity(), Trans::identity().y_flipped().y_flipped())
603 }
604 #[test]
605 fn cell_from_position_0() {
606 assert_eq!(
607 Cell::from_position(Vector2::new(0, 0)),
608 Cell(Vector2::new(0, 0), Trans::identity())
609 );
610 }
611 #[test]
612 fn cell_from_position_1() {
613 assert_eq!(
614 Cell::from_position(Vector2::new(1, 0)),
615 Cell(Vector2::new(0, 0), Trans::identity().rotated(1))
616 );
617 }
618 #[test]
619 fn cell_from_position_2() {
620 assert_eq!(
621 Cell::from_position(Vector2::new(2, 0)),
622 Cell(Vector2::new(0, 0), Trans::identity().x_flipped().rotated(3))
623 );
624 }
625 #[test]
626 fn cell_from_position_negative_0() {
627 assert_eq!(
628 Cell::from_position(Vector2::new(0, -2)),
629 Cell(Vector2::new(0, -2), Trans::identity())
630 );
631 }
632 #[test]
633 fn cell_into_position_0() {
634 assert_eq!(
635 Cell(Vector2::new(0, 0), Trans::identity().rotated(0)).into_position(),
636 Vector2::new(0, 0),
637 );
638 assert_eq!(
639 Cell(Vector2::new(0, 0), Trans::identity().rotated(1)).into_position(),
640 Vector2::new(1, 0),
641 );
642 assert_eq!(
643 Cell(Vector2::new(0, 0), Trans::identity().rotated(2)).into_position(),
644 Vector2::new(1, 1),
645 );
646 assert_eq!(
647 Cell(Vector2::new(0, 0), Trans::identity().rotated(3)).into_position(),
648 Vector2::new(0, 1),
649 );
650 }
651 #[test]
652 fn cell_into_position_1() {
653 assert_eq!(
654 Cell(Vector2::new(0, 0), Trans::identity().x_flipped().rotated(0)).into_position(),
655 Vector2::new(3, 0),
656 );
657 assert_eq!(
658 Cell(Vector2::new(0, 0), Trans::identity().x_flipped().rotated(1)).into_position(),
659 Vector2::new(3, 1),
660 );
661 assert_eq!(
662 Cell(Vector2::new(0, 0), Trans::identity().x_flipped().rotated(2)).into_position(),
663 Vector2::new(2, 1),
664 );
665 assert_eq!(
666 Cell(Vector2::new(0, 0), Trans::identity().x_flipped().rotated(3)).into_position(),
667 Vector2::new(2, 0),
668 );
669 }
670 #[test]
671 fn cell_into_position_negative_0() {
672 assert_eq!(
673 Cell(Vector2::new(0, -2), Trans::identity().rotated(0)).into_position(),
674 Vector2::new(0, -2),
675 );
676 assert_eq!(
677 Cell(Vector2::new(0, -2), Trans::identity().rotated(1)).into_position(),
678 Vector2::new(1, -2),
679 );
680 assert_eq!(
681 Cell(Vector2::new(0, -2), Trans::identity().rotated(2)).into_position(),
682 Vector2::new(1, -1),
683 );
684 assert_eq!(
685 Cell(Vector2::new(0, -2), Trans::identity().rotated(3)).into_position(),
686 Vector2::new(0, -1),
687 );
688 }
689 #[test]
690 fn cell_uniqueness() {
691 let mut set = fxhash::FxHashSet::<Vector2<i32>>::default();
692 for t in Trans::all() {
693 set.insert(Cell(Vector2::new(0, 0), t).into_position());
694 }
695 assert_eq!(set.len(), 8, "{set:?}");
696 }
697 #[test]
698 fn cell_correctness() {
699 for x in 0..4 {
700 for y in 0..2 {
701 assert_eq!(
702 Cell::from_position(Vector2::new(x, y)).0,
703 Vector2::new(0, 0),
704 "{x}, {y}"
705 );
706 }
707 }
708 for x in 4..8 {
709 for y in -2..0 {
710 assert_eq!(
711 Cell::from_position(Vector2::new(x, y)).0,
712 Vector2::new(4, -2),
713 "{x}, {y}"
714 );
715 }
716 }
717 }
718 #[test]
719 fn cell_from_position_and_back() {
720 for x in -3..4 {
721 for y in -2..4 {
722 let p = Vector2::new(x, y);
723 assert_eq!(
724 Cell::from_position(p).into_position(),
725 p,
726 "Cell: {:?}",
727 Cell::from_position(p)
728 );
729 }
730 }
731 }
732}