1use core::fmt;
5use core::ops;
6
7use euclid::Vector3D;
8
9use manyfmt::Refmt as _;
10use manyfmt::formats::Unquote;
11
12use crate::math::{
13 Axis, ConciseDebug, Cube, FreeCoordinate, FreeVector, GridCoordinate, GridPoint, GridRotation,
14 GridVector, Gridgid, Zero, lines,
15};
16
17#[doc = include_str!("../serde-warning.md")]
23#[expect(clippy::exhaustive_enums)]
24#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, exhaust::Exhaust)]
25#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
26#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
27#[repr(u8)]
28pub enum Face6 {
29 NX = 1,
31 NY = 2,
33 NZ = 3,
35 PX = 4,
37 PY = 5,
39 PZ = 6,
41}
42
43#[doc = include_str!("../serde-warning.md")]
50#[expect(clippy::exhaustive_enums)]
51#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, exhaust::Exhaust)]
52#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
53#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
54#[repr(u8)]
55pub enum Face7 {
56 Within = 0,
58 NX,
60 NY,
62 NZ,
64 PX,
66 PY,
68 PZ,
70}
71
72impl Face6 {
73 pub const ALL: [Face6; 6] = [
75 Face6::NX,
76 Face6::NY,
77 Face6::NZ,
78 Face6::PX,
79 Face6::PY,
80 Face6::PZ,
81 ];
82
83 #[inline]
85 pub const fn from_discriminant(d: u8) -> Option<Self> {
86 match d {
87 1 => Some(Self::NX),
88 2 => Some(Self::NY),
89 3 => Some(Self::NZ),
90 4 => Some(Self::PX),
91 5 => Some(Self::PY),
92 6 => Some(Self::PZ),
93 _ => None,
94 }
95 }
96
97 #[allow(clippy::missing_inline_in_public_items, reason = "unsure")]
106 pub fn from_snapped_vector(vector: FreeVector) -> Option<Self> {
107 let Vector3D { x, y, z, _unit } = vector;
108
109 if x.is_nan() || y.is_nan() || z.is_nan() {
112 return None;
113 }
114
115 let (neg_face, sign) = if x.abs() > y.abs() && x.abs() > z.abs() {
120 (Face6::NX, x.signum())
121 } else if y.abs() > z.abs() {
122 (Face6::NY, y.signum())
123 } else {
124 (Face6::NZ, z.signum())
125 };
126 Some(if sign < 0. {
127 neg_face
128 } else {
129 neg_face.opposite()
130 })
131 }
132
133 #[inline]
135 #[must_use]
136 pub const fn axis(self) -> Axis {
137 match self {
138 Self::NX | Self::PX => Axis::X,
139 Self::NY | Self::PY => Axis::Y,
140 Self::NZ | Self::PZ => Axis::Z,
141 }
142 }
143
144 #[inline]
155 pub const fn is_positive(self) -> bool {
156 matches!(self, Self::PX | Self::PY | Self::PZ)
157 }
158
159 #[inline]
170 pub fn is_negative(self) -> bool {
171 matches!(self, Self::NX | Self::NY | Self::NZ)
172 }
173
174 #[inline]
175 pub(crate) fn signum(self) -> GridCoordinate {
176 match self {
177 Self::NX | Self::NY | Self::NZ => -1,
178 Self::PX | Self::PY | Self::PZ => 1,
179 }
180 }
181
182 #[inline]
184 #[must_use]
185 pub const fn opposite(self) -> Face6 {
186 match self {
187 Face6::NX => Face6::PX,
188 Face6::NY => Face6::PY,
189 Face6::NZ => Face6::PZ,
190 Face6::PX => Face6::NX,
191 Face6::PY => Face6::NY,
192 Face6::PZ => Face6::NZ,
193 }
194 }
195
196 #[inline]
199 #[must_use]
200 pub const fn cross(self, other: Self) -> Face7 {
201 self.into7().cross(other.into7())
202 }
203
204 #[inline]
209 #[must_use]
210 pub fn normal_vector<S, U>(self) -> Vector3D<S, U>
211 where
212 S: Zero + num_traits::One + ops::Neg<Output = S>,
213 {
214 self.into7().normal_vector()
215 }
216
217 #[inline]
234 #[must_use]
235 pub fn vector<S, U>(self, magnitude: S) -> Vector3D<S, U>
236 where
237 S: Zero + ops::Neg<Output = S>,
238 {
239 let zero1 = S::zero();
240 let zero2 = S::zero();
241 match self {
242 Face6::NX => Vector3D::new(-magnitude, zero1, zero2),
243 Face6::NY => Vector3D::new(zero1, -magnitude, zero2),
244 Face6::NZ => Vector3D::new(zero1, zero2, -magnitude),
245 Face6::PX => Vector3D::new(magnitude, zero1, zero2),
246 Face6::PY => Vector3D::new(zero1, magnitude, zero2),
247 Face6::PZ => Vector3D::new(zero1, zero2, magnitude),
248 }
249 }
250
251 #[inline]
264 #[must_use]
265 pub fn dot<S, U>(self, vector: Vector3D<S, U>) -> S
266 where
267 S: Zero + ops::Neg<Output = S>,
268 {
269 self.into7().dot(vector)
270 }
271
272 #[inline]
278 pub const fn rotation_from_nz(self) -> GridRotation {
279 match self {
280 Face6::NX => GridRotation::RYZX,
281 Face6::NY => GridRotation::RZXY,
282 Face6::NZ => GridRotation::RXYZ,
283 Face6::PX => GridRotation::RyZx, Face6::PY => GridRotation::RZxy, Face6::PZ => GridRotation::RXyz, }
288 }
289
290 #[must_use]
305 #[rustfmt::skip]
306 #[allow(clippy::missing_inline_in_public_items)]
307 pub const fn face_transform(self, scale: GridCoordinate) -> Gridgid {
308 self.rotation_from_nz().to_positive_octant_transform(scale)
309 }
310
311 #[inline]
323 pub const fn clockwise(self) -> GridRotation {
324 match self {
325 Face6::NX => GridRotation::RXzY,
326 Face6::NY => GridRotation::RzYX,
327 Face6::NZ => GridRotation::RYxZ,
328 Face6::PX => GridRotation::RXZy,
329 Face6::PY => GridRotation::RZYx,
330 Face6::PZ => GridRotation::RyXZ,
331 }
332 }
333
334 #[inline]
346 pub const fn counterclockwise(self) -> GridRotation {
347 self.clockwise().inverse()
348 }
349
350 #[inline]
368 pub const fn r180(self) -> GridRotation {
369 match self {
370 Face6::NX | Face6::PX => GridRotation::RXyz,
371 Face6::NY | Face6::PY => GridRotation::RxYz,
372 Face6::NZ | Face6::PZ => GridRotation::RxyZ,
373 }
374 }
375
376 #[inline]
378 pub(crate) const fn into7(self) -> Face7 {
379 match self {
380 Face6::NX => Face7::NX,
381 Face6::NY => Face7::NY,
382 Face6::NZ => Face7::NZ,
383 Face6::PX => Face7::PX,
384 Face6::PY => Face7::PY,
385 Face6::PZ => Face7::PZ,
386 }
387 }
388}
389
390impl Face7 {
391 pub const ALL: [Face7; 7] = [
393 Face7::Within,
394 Face7::NX,
395 Face7::NY,
396 Face7::NZ,
397 Face7::PX,
398 Face7::PY,
399 Face7::PZ,
400 ];
401
402 #[inline]
404 pub const fn from_discriminant(d: u8) -> Option<Self> {
405 match d {
406 0 => Some(Self::Within),
407 1 => Some(Self::NX),
408 2 => Some(Self::NY),
409 3 => Some(Self::NZ),
410 4 => Some(Self::PX),
411 5 => Some(Self::PY),
412 6 => Some(Self::PZ),
413 _ => None,
414 }
415 }
416
417 #[inline]
420 #[must_use]
421 pub const fn axis(self) -> Option<Axis> {
422 match self {
423 Face7::Within => None,
424 Face7::NX | Face7::PX => Some(Axis::X),
425 Face7::NY | Face7::PY => Some(Axis::Y),
426 Face7::NZ | Face7::PZ => Some(Axis::Z),
427 }
428 }
429
430 #[inline]
442 pub fn is_positive(self) -> bool {
443 matches!(self, Face7::PX | Face7::PY | Face7::PZ)
444 }
445
446 #[inline]
458 pub fn is_negative(self) -> bool {
459 matches!(self, Face7::NX | Face7::NY | Face7::NZ)
460 }
461
462 #[inline]
464 #[must_use]
465 pub const fn opposite(self) -> Face7 {
466 match self {
467 Face7::Within => Face7::Within,
468 Face7::NX => Face7::PX,
469 Face7::NY => Face7::PY,
470 Face7::NZ => Face7::PZ,
471 Face7::PX => Face7::NX,
472 Face7::PY => Face7::NY,
473 Face7::PZ => Face7::NZ,
474 }
475 }
476
477 #[inline]
479 #[must_use]
480 pub const fn cross(self, other: Self) -> Self {
481 use Face7::*;
482 match (self, other) {
483 (Within, _) => Within,
485 (_, Within) => Within,
486
487 (NX, NX) => Within,
489 (NY, NY) => Within,
490 (NZ, NZ) => Within,
491 (PX, PX) => Within,
492 (PY, PY) => Within,
493 (PZ, PZ) => Within,
494
495 (NX, PX) => Within,
497 (NY, PY) => Within,
498 (NZ, PZ) => Within,
499 (PX, NX) => Within,
500 (PY, NY) => Within,
501 (PZ, NZ) => Within,
502
503 (NX, NY) => PZ,
504 (NX, NZ) => NY,
505 (NX, PY) => NZ,
506 (NX, PZ) => PY,
507
508 (NY, NX) => NZ,
509 (NY, NZ) => PX,
510 (NY, PX) => PZ,
511 (NY, PZ) => NX,
512
513 (NZ, NX) => PY,
514 (NZ, NY) => NX,
515 (NZ, PX) => NY,
516 (NZ, PY) => PX,
517
518 (PX, NY) => NZ,
519 (PX, NZ) => PY,
520 (PX, PY) => PZ,
521 (PX, PZ) => NY,
522
523 (PY, NX) => PZ,
524 (PY, NZ) => NX,
525 (PY, PX) => NZ,
526 (PY, PZ) => PX,
527
528 (PZ, NX) => NY,
529 (PZ, NY) => PX,
530 (PZ, PX) => PY,
531 (PZ, PY) => NX,
532 }
533 }
534
535 #[inline]
541 #[must_use]
542 pub fn normal_vector<S, U>(self) -> Vector3D<S, U>
543 where
544 S: Zero + num_traits::One + ops::Neg<Output = S>,
545 {
546 self.vector(S::one())
547 }
548
549 #[inline]
554 #[must_use]
555 pub(crate) const fn normal_vector_const(self) -> GridVector {
556 match self {
557 Face7::Within => Vector3D::new(0, 0, 0),
558 Face7::NX => Vector3D::new(-1, 0, 0),
559 Face7::NY => Vector3D::new(0, -1, 0),
560 Face7::NZ => Vector3D::new(0, 0, -1),
561 Face7::PX => Vector3D::new(1, 0, 0),
562 Face7::PY => Vector3D::new(0, 1, 0),
563 Face7::PZ => Vector3D::new(0, 0, 1),
564 }
565 }
566
567 #[inline]
584 #[must_use]
585 pub fn vector<S, U>(self, magnitude: S) -> Vector3D<S, U>
586 where
587 S: Zero + ops::Neg<Output = S>,
588 {
589 let zero1 = S::zero();
590 let zero2 = S::zero();
591 match self {
592 Face7::Within => Vector3D::new(zero1, zero2, S::zero()),
593 Face7::NX => Vector3D::new(-magnitude, zero1, zero2),
594 Face7::NY => Vector3D::new(zero1, -magnitude, zero2),
595 Face7::NZ => Vector3D::new(zero1, zero2, -magnitude),
596 Face7::PX => Vector3D::new(magnitude, zero1, zero2),
597 Face7::PY => Vector3D::new(zero1, magnitude, zero2),
598 Face7::PZ => Vector3D::new(zero1, zero2, magnitude),
599 }
600 }
601
602 #[inline]
615 #[must_use]
616 pub fn dot<S, U>(self, vector: Vector3D<S, U>) -> S
617 where
618 S: Zero + ops::Neg<Output = S>,
619 {
620 match self {
621 Face7::Within => S::zero(),
622 Face7::NX => -vector.x,
623 Face7::NY => -vector.y,
624 Face7::NZ => -vector.z,
625 Face7::PX => vector.x,
626 Face7::PY => vector.y,
627 Face7::PZ => vector.z,
628 }
629 }
630}
631
632impl ops::Neg for Face6 {
633 type Output = Self;
634 #[inline]
635 fn neg(self) -> Self::Output {
636 self.opposite()
637 }
638}
639impl ops::Neg for Face7 {
640 type Output = Self;
641 #[inline]
642 fn neg(self) -> Self::Output {
643 self.opposite()
644 }
645}
646
647impl From<Face6> for Face7 {
648 #[inline]
649 fn from(value: Face6) -> Self {
650 value.into7()
651 }
652}
653impl TryFrom<Face7> for Face6 {
654 type Error = Faceless;
655 #[inline]
656 fn try_from(value: Face7) -> Result<Face6, Self::Error> {
657 match value {
658 Face7::Within => Err(Faceless),
659 Face7::NX => Ok(Face6::NX),
660 Face7::NY => Ok(Face6::NY),
661 Face7::NZ => Ok(Face6::NZ),
662 Face7::PX => Ok(Face6::PX),
663 Face7::PY => Ok(Face6::PY),
664 Face7::PZ => Ok(Face6::PZ),
665 }
666 }
667}
668
669impl TryFrom<GridVector> for Face6 {
670 type Error = GridVector;
673
674 #[inline]
691 fn try_from(value: GridVector) -> Result<Self, Self::Error> {
692 let f7 = Face7::try_from(value)?;
693 Face6::try_from(f7).map_err(|_| value)
694 }
695}
696impl TryFrom<GridVector> for Face7 {
697 type Error = GridVector;
700
701 #[rustfmt::skip]
718 #[allow(clippy::missing_inline_in_public_items)] fn try_from(value: GridVector) -> Result<Self, Self::Error> {
720 use Face7::*;
721 match value {
722 GridVector { _unit: _, x: 0, y: 0, z: 0 } => Ok(Within),
723 GridVector { _unit: _, x: 1, y: 0, z: 0 } => Ok(PX),
724 GridVector { _unit: _, x: 0, y: 1, z: 0 } => Ok(PY),
725 GridVector { _unit: _, x: 0, y: 0, z: 1 } => Ok(PZ),
726 GridVector { _unit: _, x: -1, y: 0, z: 0 } => Ok(NX),
727 GridVector { _unit: _, x: 0, y: -1, z: 0 } => Ok(NY),
728 GridVector { _unit: _, x: 0, y: 0, z: -1 } => Ok(NZ),
729 not_unit_vector => Err(not_unit_vector),
730 }
731 }
732}
733
734#[derive(Copy, Clone, Debug, Eq, PartialEq, displaydoc::Display)]
737#[displaydoc("Face7::Within does not have a direction or axis")]
738#[expect(clippy::exhaustive_structs)]
739pub struct Faceless;
740
741#[cfg(feature = "rerun")]
742impl From<Face6> for re_types::view_coordinates::SignedAxis3 {
743 #[inline]
744 fn from(face: Face6) -> Self {
745 use re_types::view_coordinates::{Axis3, Sign, SignedAxis3};
746 match face {
747 Face6::NX => SignedAxis3 {
748 sign: Sign::Negative,
749 axis: Axis3::X,
750 },
751 Face6::NY => SignedAxis3 {
752 sign: Sign::Negative,
753 axis: Axis3::Y,
754 },
755 Face6::NZ => SignedAxis3 {
756 sign: Sign::Negative,
757 axis: Axis3::Z,
758 },
759 Face6::PX => SignedAxis3 {
760 sign: Sign::Positive,
761 axis: Axis3::X,
762 },
763 Face6::PY => SignedAxis3 {
764 sign: Sign::Positive,
765 axis: Axis3::Y,
766 },
767 Face6::PZ => SignedAxis3 {
768 sign: Sign::Positive,
769 axis: Axis3::Z,
770 },
771 }
772 }
773}
774
775#[expect(clippy::exhaustive_structs)]
777#[derive(Clone, Copy, Default, Hash, PartialEq, Eq, exhaust::Exhaust)]
778#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
779pub struct FaceMap<V> {
780 pub nx: V,
782 pub ny: V,
784 pub nz: V,
786 pub px: V,
788 pub py: V,
790 pub pz: V,
792}
793
794#[allow(
795 clippy::missing_inline_in_public_items,
796 reason = "all methods are generic code"
797)]
798impl<V> FaceMap<V> {
799 #[inline]
802 pub fn from_fn(mut f: impl FnMut(Face6) -> V) -> Self {
803 Self {
804 nx: f(Face6::NX),
805 ny: f(Face6::NY),
806 nz: f(Face6::NZ),
807 px: f(Face6::PX),
808 py: f(Face6::PY),
809 pz: f(Face6::PZ),
810 }
811 }
812
813 #[inline]
816 #[doc(hidden)] pub fn symmetric([x, y, z]: [V; 3]) -> Self
818 where
819 V: Default + Clone,
820 {
821 Self {
822 nx: x.clone(),
823 px: x,
824 ny: y.clone(),
825 py: y,
826 nz: z.clone(),
827 pz: z,
828 }
829 }
830
831 pub fn negatives<U>(self) -> Vector3D<V, U>
833 where
834 V: Copy,
835 {
836 Vector3D::new(self.nx, self.ny, self.nz)
837 }
838
839 pub fn positives<U>(self) -> Vector3D<V, U>
841 where
842 V: Copy,
843 {
844 Vector3D::new(self.px, self.py, self.pz)
845 }
846
847 pub fn iter(&self) -> impl Iterator<Item = (Face6, &V)> {
849 Face6::ALL.iter().copied().map(move |f| (f, &self[f]))
850 }
851
852 pub fn iter_mut(&mut self) -> impl Iterator<Item = (Face6, &mut V)> {
854 [
855 (Face6::NX, &mut self.nx),
856 (Face6::NY, &mut self.ny),
857 (Face6::NZ, &mut self.nz),
858 (Face6::PX, &mut self.px),
859 (Face6::PY, &mut self.py),
860 (Face6::PZ, &mut self.pz),
861 ]
862 .into_iter()
863 }
864
865 pub fn values(&self) -> impl Iterator<Item = &V> + Clone {
867 Face6::ALL.iter().copied().map(move |f| &self[f])
868 }
869
870 pub fn values_mut(&mut self) -> impl Iterator<Item = &mut V> {
872 [
873 &mut self.nx,
874 &mut self.ny,
875 &mut self.nz,
876 &mut self.px,
877 &mut self.py,
878 &mut self.pz,
879 ]
880 .into_iter()
881 }
882
883 pub fn into_values(self) -> [V; 6] {
885 [self.nx, self.ny, self.nz, self.px, self.py, self.pz]
886 }
887
888 pub fn into_values_iter(self) -> impl Iterator<Item = V> {
890 self.into_values().into_iter()
892 }
893
894 #[inline]
899 pub fn sum(self) -> V
900 where
901 V: ops::Add<Output = V>,
902 {
903 (self.nx + self.px) + (self.ny + self.py) + (self.nz + self.pz)
907 }
908
909 pub fn map<U>(self, mut f: impl FnMut(Face6, V) -> U) -> FaceMap<U> {
911 FaceMap {
912 nx: f(Face6::NX, self.nx),
913 ny: f(Face6::NY, self.ny),
914 nz: f(Face6::NZ, self.nz),
915 px: f(Face6::PX, self.px),
916 py: f(Face6::PY, self.py),
917 pz: f(Face6::PZ, self.pz),
918 }
919 }
920
921 pub fn map_ref<'map, U>(&'map self, mut f: impl FnMut(Face6, &'map V) -> U) -> FaceMap<U> {
923 FaceMap {
924 nx: f(Face6::NX, &self.nx),
925 ny: f(Face6::NY, &self.ny),
926 nz: f(Face6::NZ, &self.nz),
927 px: f(Face6::PX, &self.px),
928 py: f(Face6::PY, &self.py),
929 pz: f(Face6::PZ, &self.pz),
930 }
931 }
932
933 pub fn zip<U, R>(self, other: FaceMap<U>, mut f: impl FnMut(Face6, V, U) -> R) -> FaceMap<R> {
935 FaceMap {
936 nx: f(Face6::NX, self.nx, other.nx),
937 ny: f(Face6::NY, self.ny, other.ny),
938 nz: f(Face6::NZ, self.nz, other.nz),
939 px: f(Face6::PX, self.px, other.px),
940 py: f(Face6::PY, self.py, other.py),
941 pz: f(Face6::PZ, self.pz, other.pz),
942 }
943 }
944
945 #[inline]
963 #[must_use]
964 pub fn with(mut self, face: Face6, value: V) -> Self {
965 self[face] = value;
966 self
967 }
968
969 #[must_use]
971 pub fn rotate(self, rotation: GridRotation) -> Self {
972 let to_source = rotation.inverse();
975 let mut source = self.map(|_, value| Some(value));
976 Self::from_fn(|face| source[to_source.transform(face)].take().unwrap())
977 }
978}
979
980impl<V: Clone> FaceMap<V> {
981 #[inline]
983 pub fn splat(value: V) -> Self {
984 Self {
985 nx: value.clone(),
986 ny: value.clone(),
987 nz: value.clone(),
988 px: value.clone(),
989 py: value.clone(),
990 pz: value,
991 }
992 }
993}
994
995impl<V: Copy> FaceMap<V> {
996 #[inline]
1002 pub const fn splat_copy(value: V) -> Self {
1003 Self {
1004 nx: value,
1005 ny: value,
1006 nz: value,
1007 px: value,
1008 py: value,
1009 pz: value,
1010 }
1011 }
1012}
1013
1014impl<V> ops::Index<Face6> for FaceMap<V> {
1015 type Output = V;
1016 #[inline]
1017 fn index(&self, face: Face6) -> &V {
1018 match face {
1019 Face6::NX => &self.nx,
1020 Face6::NY => &self.ny,
1021 Face6::NZ => &self.nz,
1022 Face6::PX => &self.px,
1023 Face6::PY => &self.py,
1024 Face6::PZ => &self.pz,
1025 }
1026 }
1027}
1028
1029impl<V> ops::IndexMut<Face6> for FaceMap<V> {
1030 #[inline]
1031 fn index_mut(&mut self, face: Face6) -> &mut V {
1032 match face {
1033 Face6::NX => &mut self.nx,
1034 Face6::NY => &mut self.ny,
1035 Face6::NZ => &mut self.nz,
1036 Face6::PX => &mut self.px,
1037 Face6::PY => &mut self.py,
1038 Face6::PZ => &mut self.pz,
1039 }
1040 }
1041}
1042
1043impl<V> fmt::Debug for FaceMap<V>
1044where
1045 V: fmt::Debug + PartialEq,
1046{
1047 #[allow(clippy::missing_inline_in_public_items)]
1050 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1051 let FaceMap {
1052 nx,
1053 ny,
1054 nz,
1055 px,
1056 py,
1057 pz,
1058 } = self;
1059
1060 let mut dm = f.debug_map();
1061
1062 if nx == ny && nx == nz && nx == px && nx == py && nx == pz {
1063 dm.entry(&"all".refmt(&Unquote), nx);
1064 } else if nx == ny && nx == nz && px == py && px == pz {
1065 dm.entry(&"−all".refmt(&Unquote), nx);
1066 dm.entry(&"+all".refmt(&Unquote), px);
1067 } else if nx == px && ny == py && nz == pz {
1068 dm.entry(&"x".refmt(&Unquote), nx);
1069 dm.entry(&"y".refmt(&Unquote), ny);
1070 dm.entry(&"z".refmt(&Unquote), nz);
1071 } else {
1072 dm.entry(&"−x".refmt(&Unquote), nx);
1073 dm.entry(&"−y".refmt(&Unquote), ny);
1074 dm.entry(&"−z".refmt(&Unquote), nz);
1075 dm.entry(&"+x".refmt(&Unquote), px);
1076 dm.entry(&"+y".refmt(&Unquote), py);
1077 dm.entry(&"+z".refmt(&Unquote), pz);
1078 }
1079
1080 dm.finish()
1081 }
1082}
1083
1084macro_rules! impl_binary_operator_for_facemap {
1085 ($trait:ident :: $method:ident, $assign_trait:ident :: $assign_method:ident) => {
1086 impl<V: ops::$trait> ops::$trait for FaceMap<V> {
1087 type Output = FaceMap<V::Output>;
1088 #[inline]
1090 fn $method(self, other: FaceMap<V>) -> FaceMap<V::Output> {
1091 self.zip(other, |_, a, b| <V as ops::$trait>::$method(a, b))
1092 }
1093 }
1094
1095 impl<V: ops::$assign_trait> ops::$assign_trait for FaceMap<V> {
1096 #[inline]
1098 fn $assign_method(&mut self, rhs: Self) {
1099 self.nx.$assign_method(rhs.nx);
1100 self.ny.$assign_method(rhs.ny);
1101 self.nz.$assign_method(rhs.nz);
1102 self.px.$assign_method(rhs.px);
1103 self.py.$assign_method(rhs.py);
1104 self.pz.$assign_method(rhs.pz);
1105 }
1106 }
1107 };
1108}
1109impl_binary_operator_for_facemap!(BitAnd::bitand, BitAndAssign::bitand_assign);
1110impl_binary_operator_for_facemap!(BitOr::bitor, BitOrAssign::bitor_assign);
1111impl_binary_operator_for_facemap!(BitXor::bitxor, BitXorAssign::bitxor_assign);
1112impl_binary_operator_for_facemap!(Add::add, AddAssign::add_assign);
1113impl_binary_operator_for_facemap!(Mul::mul, MulAssign::mul_assign);
1114impl_binary_operator_for_facemap!(Sub::sub, SubAssign::sub_assign);
1115impl_binary_operator_for_facemap!(Div::div, DivAssign::div_assign);
1116impl_binary_operator_for_facemap!(Rem::rem, RemAssign::rem_assign);
1117
1118impl<V> IntoIterator for FaceMap<V> {
1119 type Item = (Face6, V);
1120
1121 type IntoIter = <[(Face6, V); 6] as IntoIterator>::IntoIter;
1123
1124 #[inline]
1125 fn into_iter(self) -> Self::IntoIter {
1126 [
1127 (Face6::NX, self.nx),
1128 (Face6::NY, self.ny),
1129 (Face6::NZ, self.nz),
1130 (Face6::PX, self.px),
1131 (Face6::PY, self.py),
1132 (Face6::PZ, self.pz),
1133 ]
1134 .into_iter()
1135 }
1136}
1137
1138#[derive(Clone, Copy, Hash, Eq, PartialEq)]
1141#[expect(clippy::exhaustive_structs)]
1142#[allow(missing_docs)]
1143pub struct CubeFace {
1144 pub cube: Cube,
1145 pub face: Face7,
1146}
1147
1148impl CubeFace {
1149 #[allow(missing_docs)]
1150 #[inline]
1151 pub fn new(cube: impl Into<Cube>, face: Face7) -> Self {
1152 Self {
1153 cube: cube.into(),
1154 face,
1155 }
1156 }
1157
1158 #[inline]
1163 pub fn adjacent(self) -> Cube {
1164 self.cube + self.face.normal_vector()
1165 }
1166
1167 #[inline]
1169 #[must_use]
1170 pub fn translate(mut self, offset: GridVector) -> Self {
1171 self.cube += offset;
1172 self
1173 }
1174}
1175
1176impl fmt::Debug for CubeFace {
1177 #[allow(clippy::missing_inline_in_public_items)]
1178 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
1179 write!(
1180 fmt,
1181 "CubeFace({:?}, {:?})",
1182 self.cube.refmt(&ConciseDebug),
1183 self.face,
1184 )
1185 }
1186}
1187
1188impl lines::Wireframe for CubeFace {
1189 #[allow(clippy::missing_inline_in_public_items)]
1190 fn wireframe_points<E: Extend<[lines::Vertex; 2]>>(&self, output: &mut E) {
1191 let expansion = 0.005;
1193 let aab = self.cube.aab().expand(expansion);
1194 aab.wireframe_points(output);
1195
1196 if let Ok(face) = Face6::try_from(self.face) {
1198 let face_transform = face.face_transform(1);
1199 const X_POINTS: [[GridPoint; 2]; 2] = [
1200 [GridPoint::new(0, 0, 0), GridPoint::new(1, 1, 0)],
1201 [GridPoint::new(1, 0, 0), GridPoint::new(0, 1, 0)],
1202 ];
1203 output.extend(X_POINTS.into_iter().map(|line| {
1206 line.map(|point| {
1207 lines::Vertex::from(
1208 (face_transform.transform_point(point))
1209 .map(|c| (FreeCoordinate::from(c) - 0.5) * (1. + expansion * 2.) + 0.5)
1210 + self.cube.aab().lower_bounds_v(),
1211 )
1212 })
1213 }));
1214 }
1215 }
1216}
1217
1218#[cfg(test)]
1219mod tests {
1220 use crate::util::MultiFailure;
1221
1222 use super::*;
1223 use alloc::string::String;
1224 use alloc::vec::Vec;
1225 use exhaust::Exhaust;
1226 use pretty_assertions::assert_eq;
1227
1228 #[test]
1229 fn from_snapped_vector_roundtrip() {
1230 for face in Face6::ALL {
1231 let normal = face.normal_vector();
1232 let snapped = Face6::from_snapped_vector(normal);
1233 assert_eq!(Some(face), snapped, "from {normal:?}");
1234 }
1235 }
1236
1237 #[test]
1238 #[rustfmt::skip]
1239 fn from_snapped_vector_cases() {
1240 let mut f = MultiFailure::new();
1241 for (face, vector, comment) in [
1242 (Some(Face6::PZ), [0., 0., 0.], "zero tie, positive Z, positive other"),
1243 (Some(Face6::PZ), [-0., -0., 0.], "zero tie, positive Z, negative other"),
1244 (Some(Face6::NZ), [0., 0., -0.], "zero tie, negative Z, positive other"),
1245 (Some(Face6::NZ), [-0., -0., -0.], "zero tie, negative Z, negative other"),
1246
1247 (Some(Face6::NZ), [-2., -3., -3.], "2-axis tie YZ, negative"),
1248 (Some(Face6::NY), [-3., -3., -2.], "2-axis tie XY, negative"),
1249 (Some(Face6::PZ), [2., 3., 3.], "2-axis tie YZ, positive"),
1250 (Some(Face6::PY), [3., 3., 2.], "2-axis tie XY, positive"),
1251
1252 (None, [f64::NAN, 1.0, 1.0], "NaN X"),
1253 (None, [1.0, f64::NAN, 1.0], "NaN Y"),
1254 (None, [1.0, 1.0, f64::NAN], "NaN Z"),
1255 ] {
1256 f.catch(|| {
1257 let vector = FreeVector::from(vector);
1258 assert_eq!(face, Face6::from_snapped_vector(vector), "{comment}, {vector:?}");
1259 });
1260 }
1261 }
1262
1263 #[test]
1264 fn cross_6() {
1265 let mut f = MultiFailure::new();
1266 for face1 in Face6::ALL {
1267 for face2 in Face6::ALL {
1268 f.catch(|| {
1269 assert_eq!(
1271 face1.cross(face2).normal_vector::<f64, ()>(),
1272 face1.normal_vector().cross(face2.normal_vector()),
1273 "{face1:?} cross {face2:?}",
1274 );
1275 });
1276 }
1277 }
1278 }
1279
1280 #[test]
1281 fn cross_7() {
1282 let mut f = MultiFailure::new();
1283 for face1 in Face7::ALL {
1284 for face2 in Face7::ALL {
1285 f.catch(|| {
1286 assert_eq!(
1288 face1.cross(face2).normal_vector::<f64, ()>(),
1289 face1.normal_vector().cross(face2.normal_vector()),
1290 "{face1:?} cross {face2:?}",
1291 );
1292 });
1293 }
1294 }
1295 }
1296
1297 #[test]
1298 fn rotation_from_nz() {
1299 for face in Face6::ALL {
1300 let rot = face.rotation_from_nz();
1301 assert_eq!(
1302 rot.transform(Face6::NZ),
1303 face,
1304 "{face:?}: {rot:?} should rotate from NZ"
1305 );
1306 assert!(!rot.is_reflection(), "{face:?}: {rot:?} should not reflect");
1307 }
1308 }
1309
1310 #[test]
1311 fn face_transform_does_not_reflect() {
1312 for face in Face6::ALL {
1313 assert!(!face.face_transform(7).rotation.is_reflection());
1314 }
1315 }
1316
1317 #[test]
1320 fn clockwise_properties() {
1321 let mut f = MultiFailure::new();
1322 for face in Face6::ALL {
1323 f.catch(|| {
1324 assert_eq!(
1325 face.counterclockwise(),
1326 face.clockwise().inverse(),
1327 "{face:?} ccw is inverse of cw"
1328 );
1329 assert!(
1330 !face.clockwise().is_reflection(),
1331 "{face:?}.clockwise() is not a reflection"
1332 );
1333 assert_eq!(
1334 face.clockwise().transform(face),
1335 face,
1336 "{face:?}.clockwise() leaves itself unchanged"
1337 );
1338 assert_eq!(
1339 face.clockwise().iterate().count(),
1340 4,
1341 "{face:?}.clockwise() is a 90° rotation"
1342 );
1343 assert_eq!(
1344 face.clockwise() * face.clockwise(),
1345 face.r180(),
1346 "{face:?}.clockwise() twice equals 180°"
1347 )
1348 });
1349 }
1350 }
1351
1352 #[test]
1353 fn face_map_debug_cmp() {
1354 let strings =
1355 FaceMap::<bool>::exhaust().map(|fm| format!("{fm:?}")).collect::<Vec<String>>();
1356 assert_eq!(
1357 strings.iter().map(String::as_str).collect::<Vec<_>>(),
1358 vec![
1359 "{all: false}",
1360 "{−x: false, −y: false, −z: false, +x: false, +y: false, +z: true}",
1361 "{−x: false, −y: false, −z: false, +x: false, +y: true, +z: false}",
1362 "{−x: false, −y: false, −z: false, +x: false, +y: true, +z: true}",
1363 "{−x: false, −y: false, −z: false, +x: true, +y: false, +z: false}",
1364 "{−x: false, −y: false, −z: false, +x: true, +y: false, +z: true}",
1365 "{−x: false, −y: false, −z: false, +x: true, +y: true, +z: false}",
1366 "{−all: false, +all: true}",
1367 "{−x: false, −y: false, −z: true, +x: false, +y: false, +z: false}",
1368 "{x: false, y: false, z: true}",
1369 "{−x: false, −y: false, −z: true, +x: false, +y: true, +z: false}",
1370 "{−x: false, −y: false, −z: true, +x: false, +y: true, +z: true}",
1371 "{−x: false, −y: false, −z: true, +x: true, +y: false, +z: false}",
1372 "{−x: false, −y: false, −z: true, +x: true, +y: false, +z: true}",
1373 "{−x: false, −y: false, −z: true, +x: true, +y: true, +z: false}",
1374 "{−x: false, −y: false, −z: true, +x: true, +y: true, +z: true}",
1375 "{−x: false, −y: true, −z: false, +x: false, +y: false, +z: false}",
1376 "{−x: false, −y: true, −z: false, +x: false, +y: false, +z: true}",
1377 "{x: false, y: true, z: false}",
1378 "{−x: false, −y: true, −z: false, +x: false, +y: true, +z: true}",
1379 "{−x: false, −y: true, −z: false, +x: true, +y: false, +z: false}",
1380 "{−x: false, −y: true, −z: false, +x: true, +y: false, +z: true}",
1381 "{−x: false, −y: true, −z: false, +x: true, +y: true, +z: false}",
1382 "{−x: false, −y: true, −z: false, +x: true, +y: true, +z: true}",
1383 "{−x: false, −y: true, −z: true, +x: false, +y: false, +z: false}",
1384 "{−x: false, −y: true, −z: true, +x: false, +y: false, +z: true}",
1385 "{−x: false, −y: true, −z: true, +x: false, +y: true, +z: false}",
1386 "{x: false, y: true, z: true}",
1387 "{−x: false, −y: true, −z: true, +x: true, +y: false, +z: false}",
1388 "{−x: false, −y: true, −z: true, +x: true, +y: false, +z: true}",
1389 "{−x: false, −y: true, −z: true, +x: true, +y: true, +z: false}",
1390 "{−x: false, −y: true, −z: true, +x: true, +y: true, +z: true}",
1391 "{−x: true, −y: false, −z: false, +x: false, +y: false, +z: false}",
1392 "{−x: true, −y: false, −z: false, +x: false, +y: false, +z: true}",
1393 "{−x: true, −y: false, −z: false, +x: false, +y: true, +z: false}",
1394 "{−x: true, −y: false, −z: false, +x: false, +y: true, +z: true}",
1395 "{x: true, y: false, z: false}",
1396 "{−x: true, −y: false, −z: false, +x: true, +y: false, +z: true}",
1397 "{−x: true, −y: false, −z: false, +x: true, +y: true, +z: false}",
1398 "{−x: true, −y: false, −z: false, +x: true, +y: true, +z: true}",
1399 "{−x: true, −y: false, −z: true, +x: false, +y: false, +z: false}",
1400 "{−x: true, −y: false, −z: true, +x: false, +y: false, +z: true}",
1401 "{−x: true, −y: false, −z: true, +x: false, +y: true, +z: false}",
1402 "{−x: true, −y: false, −z: true, +x: false, +y: true, +z: true}",
1403 "{−x: true, −y: false, −z: true, +x: true, +y: false, +z: false}",
1404 "{x: true, y: false, z: true}",
1405 "{−x: true, −y: false, −z: true, +x: true, +y: true, +z: false}",
1406 "{−x: true, −y: false, −z: true, +x: true, +y: true, +z: true}",
1407 "{−x: true, −y: true, −z: false, +x: false, +y: false, +z: false}",
1408 "{−x: true, −y: true, −z: false, +x: false, +y: false, +z: true}",
1409 "{−x: true, −y: true, −z: false, +x: false, +y: true, +z: false}",
1410 "{−x: true, −y: true, −z: false, +x: false, +y: true, +z: true}",
1411 "{−x: true, −y: true, −z: false, +x: true, +y: false, +z: false}",
1412 "{−x: true, −y: true, −z: false, +x: true, +y: false, +z: true}",
1413 "{x: true, y: true, z: false}",
1414 "{−x: true, −y: true, −z: false, +x: true, +y: true, +z: true}",
1415 "{−all: true, +all: false}",
1416 "{−x: true, −y: true, −z: true, +x: false, +y: false, +z: true}",
1417 "{−x: true, −y: true, −z: true, +x: false, +y: true, +z: false}",
1418 "{−x: true, −y: true, −z: true, +x: false, +y: true, +z: true}",
1419 "{−x: true, −y: true, −z: true, +x: true, +y: false, +z: false}",
1420 "{−x: true, −y: true, −z: true, +x: true, +y: false, +z: true}",
1421 "{−x: true, −y: true, −z: true, +x: true, +y: true, +z: false}",
1422 "{all: true}",
1423 ],
1424 );
1425 }
1426
1427 #[test]
1429 fn face_map_iter_in_enum_order() {
1430 let mut map = FaceMap::from_fn(core::convert::identity);
1431 let expected_both: Vec<(Face6, Face6)> = Face6::ALL.into_iter().zip(Face6::ALL).collect();
1432
1433 assert_eq!(
1435 expected_both,
1436 map.iter().map(|(k, &v)| (k, v)).collect::<Vec<_>>(),
1437 );
1438
1439 assert_eq!(
1441 expected_both,
1442 map.iter_mut().map(|(k, &mut v)| (k, v)).collect::<Vec<_>>(),
1443 );
1444
1445 assert_eq!(
1447 Face6::ALL.to_vec(),
1448 map.values().copied().collect::<Vec<_>>(),
1449 );
1450
1451 assert_eq!(Face6::ALL, map.into_values());
1453 }
1454
1455 #[test]
1456 fn face_map_rotate() {
1457 assert_eq!(
1458 FaceMap {
1459 nx: 10,
1460 px: 20,
1461 ny: 11,
1462 py: 21,
1463 nz: 12,
1464 pz: 22,
1465 }
1466 .rotate(GridRotation::RyXZ),
1467 FaceMap {
1468 nx: 11,
1469 px: 21,
1470 ny: 20,
1471 py: 10,
1472 nz: 12,
1473 pz: 22,
1474 }
1475 )
1476 }
1477
1478 #[test]
1481 fn cubeface_format() {
1482 let cube_face = CubeFace {
1483 cube: Cube::new(1, 2, 3),
1484 face: Face7::NY,
1485 };
1486 assert_eq!(&format!("{cube_face:#?}"), "CubeFace((+1, +2, +3), NY)");
1487 }
1488}