1#![allow(
5 clippy::module_name_repetitions,
6 reason = "false positive; TODO: remove after Rust 1.84 is released"
7)]
8
9use core::fmt;
10use core::ops;
11
12use euclid::Vector3D;
13
14use manyfmt::formats::Unquote;
15use manyfmt::Refmt as _;
16#[cfg(not(feature = "std"))]
18#[allow(unused_imports)]
19use num_traits::float::FloatCore as _;
20
21use crate::math::{
22 Axis, ConciseDebug, Cube, FreeCoordinate, FreeVector, GridCoordinate, GridPoint, GridRotation,
23 GridVector, Gridgid, LineVertex, Zero,
24};
25
26#[doc = include_str!("../serde-warning.md")]
32#[expect(clippy::exhaustive_enums)]
33#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, exhaust::Exhaust)]
34#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
35#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
36#[repr(u8)]
37pub enum Face6 {
38 NX = 1,
40 NY = 2,
42 NZ = 3,
44 PX = 4,
46 PY = 5,
48 PZ = 6,
50}
51
52#[doc = include_str!("../serde-warning.md")]
59#[expect(clippy::exhaustive_enums)]
60#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, exhaust::Exhaust)]
61#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
62#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
63#[repr(u8)]
64pub enum Face7 {
65 Within = 0,
67 NX,
69 NY,
71 NZ,
73 PX,
75 PY,
77 PZ,
79}
80
81impl Face6 {
82 pub const ALL: [Face6; 6] = [
84 Face6::NX,
85 Face6::NY,
86 Face6::NZ,
87 Face6::PX,
88 Face6::PY,
89 Face6::PZ,
90 ];
91
92 #[inline]
94 pub const fn from_discriminant(d: u8) -> Option<Self> {
95 match d {
96 1 => Some(Self::NX),
97 2 => Some(Self::NY),
98 3 => Some(Self::NZ),
99 4 => Some(Self::PX),
100 5 => Some(Self::PY),
101 6 => Some(Self::PZ),
102 _ => None,
103 }
104 }
105
106 #[allow(clippy::missing_inline_in_public_items, reason = "unsure")]
115 pub fn from_snapped_vector(vector: FreeVector) -> Option<Self> {
116 let Vector3D { x, y, z, _unit } = vector;
117
118 if x.is_nan() || y.is_nan() || z.is_nan() {
121 return None;
122 }
123
124 let (neg_face, sign) = if x.abs() > y.abs() && x.abs() > z.abs() {
129 (Face6::NX, x.signum())
130 } else if y.abs() > z.abs() {
131 (Face6::NY, y.signum())
132 } else {
133 (Face6::NZ, z.signum())
134 };
135 Some(if sign < 0. {
136 neg_face
137 } else {
138 neg_face.opposite()
139 })
140 }
141
142 #[inline]
144 #[must_use]
145 pub const fn axis(self) -> Axis {
146 match self {
147 Self::NX | Self::PX => Axis::X,
148 Self::NY | Self::PY => Axis::Y,
149 Self::NZ | Self::PZ => Axis::Z,
150 }
151 }
152
153 #[inline]
164 pub const fn is_positive(self) -> bool {
165 matches!(self, Self::PX | Self::PY | Self::PZ)
166 }
167
168 #[inline]
179 pub fn is_negative(self) -> bool {
180 matches!(self, Self::NX | Self::NY | Self::NZ)
181 }
182
183 #[inline]
184 pub(crate) fn signum(self) -> GridCoordinate {
185 match self {
186 Self::NX | Self::NY | Self::NZ => -1,
187 Self::PX | Self::PY | Self::PZ => 1,
188 }
189 }
190
191 #[inline]
193 #[must_use]
194 pub const fn opposite(self) -> Face6 {
195 match self {
196 Face6::NX => Face6::PX,
197 Face6::NY => Face6::PY,
198 Face6::NZ => Face6::PZ,
199 Face6::PX => Face6::NX,
200 Face6::PY => Face6::NY,
201 Face6::PZ => Face6::NZ,
202 }
203 }
204
205 #[inline]
208 #[must_use]
209 pub const fn cross(self, other: Self) -> Face7 {
210 self.into7().cross(other.into7())
211 }
212 #[inline]
214 #[must_use]
215 pub fn normal_vector<S, U>(self) -> Vector3D<S, U>
216 where
217 S: Zero + num_traits::One + ops::Neg<Output = S>,
218 {
219 self.into7().normal_vector()
220 }
221
222 #[inline]
235 #[must_use]
236 pub fn dot<S, U>(self, vector: Vector3D<S, U>) -> S
237 where
238 S: Zero + ops::Neg<Output = S>,
239 {
240 self.into7().dot(vector)
241 }
242
243 #[inline]
249 pub const fn rotation_from_nz(self) -> GridRotation {
250 match self {
251 Face6::NX => GridRotation::RYZX,
252 Face6::NY => GridRotation::RZXY,
253 Face6::NZ => GridRotation::RXYZ,
254 Face6::PX => GridRotation::RyZx, Face6::PY => GridRotation::RZxy, Face6::PZ => GridRotation::RXyz, }
259 }
260
261 #[must_use]
276 #[rustfmt::skip]
277 #[allow(clippy::missing_inline_in_public_items)]
278 pub const fn face_transform(self, scale: GridCoordinate) -> Gridgid {
279 self.rotation_from_nz().to_positive_octant_transform(scale)
280 }
281
282 #[inline]
284 pub(crate) const fn into7(self) -> Face7 {
285 match self {
286 Face6::NX => Face7::NX,
287 Face6::NY => Face7::NY,
288 Face6::NZ => Face7::NZ,
289 Face6::PX => Face7::PX,
290 Face6::PY => Face7::PY,
291 Face6::PZ => Face7::PZ,
292 }
293 }
294}
295
296impl Face7 {
297 pub const ALL: [Face7; 7] = [
299 Face7::Within,
300 Face7::NX,
301 Face7::NY,
302 Face7::NZ,
303 Face7::PX,
304 Face7::PY,
305 Face7::PZ,
306 ];
307
308 #[inline]
310 pub const fn from_discriminant(d: u8) -> Option<Self> {
311 match d {
312 0 => Some(Self::Within),
313 1 => Some(Self::NX),
314 2 => Some(Self::NY),
315 3 => Some(Self::NZ),
316 4 => Some(Self::PX),
317 5 => Some(Self::PY),
318 6 => Some(Self::PZ),
319 _ => None,
320 }
321 }
322
323 #[inline]
326 #[must_use]
327 pub const fn axis(self) -> Option<Axis> {
328 match self {
329 Face7::Within => None,
330 Face7::NX | Face7::PX => Some(Axis::X),
331 Face7::NY | Face7::PY => Some(Axis::Y),
332 Face7::NZ | Face7::PZ => Some(Axis::Z),
333 }
334 }
335
336 #[inline]
348 pub fn is_positive(self) -> bool {
349 matches!(self, Face7::PX | Face7::PY | Face7::PZ)
350 }
351
352 #[inline]
364 pub fn is_negative(self) -> bool {
365 matches!(self, Face7::NX | Face7::NY | Face7::NZ)
366 }
367
368 #[inline]
370 #[must_use]
371 pub const fn opposite(self) -> Face7 {
372 match self {
373 Face7::Within => Face7::Within,
374 Face7::NX => Face7::PX,
375 Face7::NY => Face7::PY,
376 Face7::NZ => Face7::PZ,
377 Face7::PX => Face7::NX,
378 Face7::PY => Face7::NY,
379 Face7::PZ => Face7::NZ,
380 }
381 }
382
383 #[inline]
385 #[must_use]
386 pub const fn cross(self, other: Self) -> Self {
387 use Face7::*;
388 match (self, other) {
389 (Within, _) => Within,
391 (_, Within) => Within,
392
393 (NX, NX) => Within,
395 (NY, NY) => Within,
396 (NZ, NZ) => Within,
397 (PX, PX) => Within,
398 (PY, PY) => Within,
399 (PZ, PZ) => Within,
400
401 (NX, PX) => Within,
403 (NY, PY) => Within,
404 (NZ, PZ) => Within,
405 (PX, NX) => Within,
406 (PY, NY) => Within,
407 (PZ, NZ) => Within,
408
409 (NX, NY) => PZ,
410 (NX, NZ) => NY,
411 (NX, PY) => NZ,
412 (NX, PZ) => PY,
413
414 (NY, NX) => NZ,
415 (NY, NZ) => PX,
416 (NY, PX) => PZ,
417 (NY, PZ) => NX,
418
419 (NZ, NX) => PY,
420 (NZ, NY) => NX,
421 (NZ, PX) => NY,
422 (NZ, PY) => PX,
423
424 (PX, NY) => NZ,
425 (PX, NZ) => PY,
426 (PX, PY) => PZ,
427 (PX, PZ) => NY,
428
429 (PY, NX) => PZ,
430 (PY, NZ) => NX,
431 (PY, PX) => NZ,
432 (PY, PZ) => PX,
433
434 (PZ, NX) => NY,
435 (PZ, NY) => PX,
436 (PZ, PX) => PY,
437 (PZ, PY) => NX,
438 }
439 }
440
441 #[inline]
444 #[must_use]
445 pub fn normal_vector<S, U>(self) -> Vector3D<S, U>
446 where
447 S: Zero + num_traits::One + ops::Neg<Output = S>,
448 {
449 match self {
450 Face7::Within => Vector3D::new(S::zero(), S::zero(), S::zero()),
451 Face7::NX => Vector3D::new(-S::one(), S::zero(), S::zero()),
452 Face7::NY => Vector3D::new(S::zero(), -S::one(), S::zero()),
453 Face7::NZ => Vector3D::new(S::zero(), S::zero(), -S::one()),
454 Face7::PX => Vector3D::new(S::one(), S::zero(), S::zero()),
455 Face7::PY => Vector3D::new(S::zero(), S::one(), S::zero()),
456 Face7::PZ => Vector3D::new(S::zero(), S::zero(), S::one()),
457 }
458 }
459
460 #[inline]
465 #[must_use]
466 pub(crate) const fn normal_vector_const(self) -> GridVector {
467 match self {
468 Face7::Within => Vector3D::new(0, 0, 0),
469 Face7::NX => Vector3D::new(-1, 0, 0),
470 Face7::NY => Vector3D::new(0, -1, 0),
471 Face7::NZ => Vector3D::new(0, 0, -1),
472 Face7::PX => Vector3D::new(1, 0, 0),
473 Face7::PY => Vector3D::new(0, 1, 0),
474 Face7::PZ => Vector3D::new(0, 0, 1),
475 }
476 }
477
478 #[inline]
491 #[must_use]
492 pub fn dot<S, U>(self, vector: Vector3D<S, U>) -> S
493 where
494 S: Zero + ops::Neg<Output = S>,
495 {
496 match self {
497 Face7::Within => S::zero(),
498 Face7::NX => -vector.x,
499 Face7::NY => -vector.y,
500 Face7::NZ => -vector.z,
501 Face7::PX => vector.x,
502 Face7::PY => vector.y,
503 Face7::PZ => vector.z,
504 }
505 }
506}
507
508impl ops::Neg for Face6 {
509 type Output = Self;
510 #[inline]
511 fn neg(self) -> Self::Output {
512 self.opposite()
513 }
514}
515impl ops::Neg for Face7 {
516 type Output = Self;
517 #[inline]
518 fn neg(self) -> Self::Output {
519 self.opposite()
520 }
521}
522
523impl From<Face6> for Face7 {
524 #[inline]
525 fn from(value: Face6) -> Self {
526 value.into7()
527 }
528}
529impl TryFrom<Face7> for Face6 {
530 type Error = Faceless;
531 #[inline]
532 fn try_from(value: Face7) -> Result<Face6, Self::Error> {
533 match value {
534 Face7::Within => Err(Faceless),
535 Face7::NX => Ok(Face6::NX),
536 Face7::NY => Ok(Face6::NY),
537 Face7::NZ => Ok(Face6::NZ),
538 Face7::PX => Ok(Face6::PX),
539 Face7::PY => Ok(Face6::PY),
540 Face7::PZ => Ok(Face6::PZ),
541 }
542 }
543}
544
545impl TryFrom<GridVector> for Face6 {
546 type Error = GridVector;
549
550 #[inline]
567 fn try_from(value: GridVector) -> Result<Self, Self::Error> {
568 let f7 = Face7::try_from(value)?;
569 Face6::try_from(f7).map_err(|_| value)
570 }
571}
572impl TryFrom<GridVector> for Face7 {
573 type Error = GridVector;
576
577 #[rustfmt::skip]
594 #[allow(clippy::missing_inline_in_public_items)] fn try_from(value: GridVector) -> Result<Self, Self::Error> {
596 use Face7::*;
597 match value {
598 GridVector { _unit: _, x: 0, y: 0, z: 0 } => Ok(Within),
599 GridVector { _unit: _, x: 1, y: 0, z: 0 } => Ok(PX),
600 GridVector { _unit: _, x: 0, y: 1, z: 0 } => Ok(PY),
601 GridVector { _unit: _, x: 0, y: 0, z: 1 } => Ok(PZ),
602 GridVector { _unit: _, x: -1, y: 0, z: 0 } => Ok(NX),
603 GridVector { _unit: _, x: 0, y: -1, z: 0 } => Ok(NY),
604 GridVector { _unit: _, x: 0, y: 0, z: -1 } => Ok(NZ),
605 not_unit_vector => Err(not_unit_vector),
606 }
607 }
608}
609
610#[derive(Copy, Clone, Debug, Eq, PartialEq, displaydoc::Display)]
613#[displaydoc("Face7::Within does not have a direction or axis")]
614#[expect(clippy::exhaustive_structs)]
615pub struct Faceless;
616
617#[cfg(feature = "rerun")]
618impl From<Face6> for re_types::view_coordinates::SignedAxis3 {
619 #[inline]
620 fn from(face: Face6) -> Self {
621 use re_types::view_coordinates::{Axis3, Sign, SignedAxis3};
622 match face {
623 Face6::NX => SignedAxis3 {
624 sign: Sign::Negative,
625 axis: Axis3::X,
626 },
627 Face6::NY => SignedAxis3 {
628 sign: Sign::Negative,
629 axis: Axis3::Y,
630 },
631 Face6::NZ => SignedAxis3 {
632 sign: Sign::Negative,
633 axis: Axis3::Z,
634 },
635 Face6::PX => SignedAxis3 {
636 sign: Sign::Positive,
637 axis: Axis3::X,
638 },
639 Face6::PY => SignedAxis3 {
640 sign: Sign::Positive,
641 axis: Axis3::Y,
642 },
643 Face6::PZ => SignedAxis3 {
644 sign: Sign::Positive,
645 axis: Axis3::Z,
646 },
647 }
648 }
649}
650
651#[expect(clippy::exhaustive_structs)]
653#[derive(Clone, Copy, Default, Hash, PartialEq, Eq, exhaust::Exhaust)]
654#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
655pub struct FaceMap<V> {
656 pub nx: V,
658 pub ny: V,
660 pub nz: V,
662 pub px: V,
664 pub py: V,
666 pub pz: V,
668}
669
670#[allow(
671 clippy::missing_inline_in_public_items,
672 reason = "all methods are generic code"
673)]
674impl<V> FaceMap<V> {
675 #[inline]
678 pub fn from_fn(mut f: impl FnMut(Face6) -> V) -> Self {
679 Self {
680 nx: f(Face6::NX),
681 ny: f(Face6::NY),
682 nz: f(Face6::NZ),
683 px: f(Face6::PX),
684 py: f(Face6::PY),
685 pz: f(Face6::PZ),
686 }
687 }
688
689 #[inline]
692 #[doc(hidden)] pub fn symmetric([x, y, z]: [V; 3]) -> Self
694 where
695 V: Default + Clone,
696 {
697 Self {
698 nx: x.clone(),
699 px: x,
700 ny: y.clone(),
701 py: y,
702 nz: z.clone(),
703 pz: z,
704 }
705 }
706
707 pub fn negatives<U>(self) -> Vector3D<V, U>
709 where
710 V: Copy,
711 {
712 Vector3D::new(self.nx, self.ny, self.nz)
713 }
714
715 pub fn positives<U>(self) -> Vector3D<V, U>
717 where
718 V: Copy,
719 {
720 Vector3D::new(self.px, self.py, self.pz)
721 }
722
723 pub fn iter(&self) -> impl Iterator<Item = (Face6, &V)> {
725 Face6::ALL.iter().copied().map(move |f| (f, &self[f]))
726 }
727
728 pub fn iter_mut(&mut self) -> impl Iterator<Item = (Face6, &mut V)> {
730 [
731 (Face6::NX, &mut self.nx),
732 (Face6::NY, &mut self.ny),
733 (Face6::NZ, &mut self.nz),
734 (Face6::PX, &mut self.px),
735 (Face6::PY, &mut self.py),
736 (Face6::PZ, &mut self.pz),
737 ]
738 .into_iter()
739 }
740
741 pub fn values(&self) -> impl Iterator<Item = &V> {
743 Face6::ALL.iter().copied().map(move |f| &self[f])
744 }
745
746 pub fn into_values(self) -> [V; 6] {
748 [self.nx, self.ny, self.nz, self.px, self.py, self.pz]
749 }
750
751 pub fn into_values_iter(self) -> impl Iterator<Item = V> {
753 self.into_values().into_iter()
755 }
756
757 pub fn map<U>(self, mut f: impl FnMut(Face6, V) -> U) -> FaceMap<U> {
759 FaceMap {
760 nx: f(Face6::NX, self.nx),
761 ny: f(Face6::NY, self.ny),
762 nz: f(Face6::NZ, self.nz),
763 px: f(Face6::PX, self.px),
764 py: f(Face6::PY, self.py),
765 pz: f(Face6::PZ, self.pz),
766 }
767 }
768
769 pub fn zip<U, R>(self, other: FaceMap<U>, mut f: impl FnMut(Face6, V, U) -> R) -> FaceMap<R> {
771 FaceMap {
772 nx: f(Face6::NX, self.nx, other.nx),
773 ny: f(Face6::NY, self.ny, other.ny),
774 nz: f(Face6::NZ, self.nz, other.nz),
775 px: f(Face6::PX, self.px, other.px),
776 py: f(Face6::PY, self.py, other.py),
777 pz: f(Face6::PZ, self.pz, other.pz),
778 }
779 }
780
781 #[inline]
799 #[must_use]
800 pub fn with(mut self, face: Face6, value: V) -> Self {
801 self[face] = value;
802 self
803 }
804
805 #[must_use]
807 pub fn rotate(self, rotation: GridRotation) -> Self {
808 let to_source = rotation.inverse();
811 let mut source = self.map(|_, value| Some(value));
812 Self::from_fn(|face| source[to_source.transform(face)].take().unwrap())
813 }
814}
815
816impl<V: Clone> FaceMap<V> {
817 #[inline]
819 pub fn splat(value: V) -> Self {
820 Self {
821 nx: value.clone(),
822 ny: value.clone(),
823 nz: value.clone(),
824 px: value.clone(),
825 py: value.clone(),
826 pz: value,
827 }
828 }
829}
830
831impl<V: Copy> FaceMap<V> {
832 #[inline]
838 pub const fn splat_copy(value: V) -> Self {
839 Self {
840 nx: value,
841 ny: value,
842 nz: value,
843 px: value,
844 py: value,
845 pz: value,
846 }
847 }
848}
849
850impl<V> ops::Index<Face6> for FaceMap<V> {
851 type Output = V;
852 #[inline]
853 fn index(&self, face: Face6) -> &V {
854 match face {
855 Face6::NX => &self.nx,
856 Face6::NY => &self.ny,
857 Face6::NZ => &self.nz,
858 Face6::PX => &self.px,
859 Face6::PY => &self.py,
860 Face6::PZ => &self.pz,
861 }
862 }
863}
864
865impl<V> ops::IndexMut<Face6> for FaceMap<V> {
866 #[inline]
867 fn index_mut(&mut self, face: Face6) -> &mut V {
868 match face {
869 Face6::NX => &mut self.nx,
870 Face6::NY => &mut self.ny,
871 Face6::NZ => &mut self.nz,
872 Face6::PX => &mut self.px,
873 Face6::PY => &mut self.py,
874 Face6::PZ => &mut self.pz,
875 }
876 }
877}
878
879impl<V> fmt::Debug for FaceMap<V>
880where
881 V: fmt::Debug + PartialEq,
882{
883 #[allow(clippy::missing_inline_in_public_items)]
886 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
887 let FaceMap {
888 nx,
889 ny,
890 nz,
891 px,
892 py,
893 pz,
894 } = self;
895
896 let mut dm = f.debug_map();
897
898 if nx == ny && nx == nz && nx == px && nx == py && nx == pz {
899 dm.entry(&"all".refmt(&Unquote), nx);
900 } else if nx == ny && nx == nz && px == py && px == pz {
901 dm.entry(&"−all".refmt(&Unquote), nx);
902 dm.entry(&"+all".refmt(&Unquote), px);
903 } else if nx == px && ny == py && nz == pz {
904 dm.entry(&"x".refmt(&Unquote), nx);
905 dm.entry(&"y".refmt(&Unquote), ny);
906 dm.entry(&"z".refmt(&Unquote), nz);
907 } else {
908 dm.entry(&"−x".refmt(&Unquote), nx);
909 dm.entry(&"−y".refmt(&Unquote), ny);
910 dm.entry(&"−z".refmt(&Unquote), nz);
911 dm.entry(&"+x".refmt(&Unquote), px);
912 dm.entry(&"+y".refmt(&Unquote), py);
913 dm.entry(&"+z".refmt(&Unquote), pz);
914 };
915
916 dm.finish()
917 }
918}
919
920macro_rules! impl_binary_operator_for_facemap {
921 ($trait:ident :: $method:ident) => {
922 impl<V: ops::$trait> ops::$trait for FaceMap<V> {
923 type Output = FaceMap<V::Output>;
924 #[inline]
926 fn $method(self, other: FaceMap<V>) -> FaceMap<V::Output> {
927 self.zip(other, |_, a, b| <V as ops::$trait>::$method(a, b))
928 }
929 }
930 };
931}
932impl_binary_operator_for_facemap!(BitAnd::bitand);
933impl_binary_operator_for_facemap!(BitOr::bitor);
934impl_binary_operator_for_facemap!(BitXor::bitxor);
935impl_binary_operator_for_facemap!(Add::add);
936impl_binary_operator_for_facemap!(Mul::mul);
937impl_binary_operator_for_facemap!(Sub::sub);
938impl_binary_operator_for_facemap!(Div::div);
939impl_binary_operator_for_facemap!(Rem::rem);
940
941#[derive(Clone, Copy, Hash, Eq, PartialEq)]
944#[expect(clippy::exhaustive_structs)]
945#[allow(missing_docs)]
946pub struct CubeFace {
947 pub cube: Cube,
948 pub face: Face7,
949}
950
951impl CubeFace {
952 #[allow(missing_docs)]
953 #[inline]
954 pub fn new(cube: impl Into<Cube>, face: Face7) -> Self {
955 Self {
956 cube: cube.into(),
957 face,
958 }
959 }
960
961 #[inline]
966 pub fn adjacent(self) -> Cube {
967 self.cube + self.face.normal_vector()
968 }
969
970 #[inline]
972 #[must_use]
973 pub fn translate(mut self, offset: GridVector) -> Self {
974 self.cube += offset;
975 self
976 }
977}
978
979impl fmt::Debug for CubeFace {
980 #[allow(clippy::missing_inline_in_public_items)]
981 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
982 write!(
983 fmt,
984 "CubeFace({:?}, {:?})",
985 self.cube.refmt(&ConciseDebug),
986 self.face,
987 )
988 }
989}
990
991impl crate::math::Wireframe for CubeFace {
992 #[allow(clippy::missing_inline_in_public_items)]
993 fn wireframe_points<E>(&self, output: &mut E)
994 where
995 E: Extend<LineVertex>,
996 {
997 let expansion = 0.005;
999 let aab = self.cube.aab().expand(expansion);
1000 aab.wireframe_points(output);
1001
1002 if let Ok(face) = Face6::try_from(self.face) {
1004 let face_transform = face.face_transform(1);
1005 const X_POINTS: [GridPoint; 4] = [
1006 GridPoint::new(0, 0, 0),
1007 GridPoint::new(1, 1, 0),
1008 GridPoint::new(1, 0, 0),
1009 GridPoint::new(0, 1, 0),
1010 ];
1011 output.extend(X_POINTS.into_iter().map(|p| {
1014 LineVertex::from(
1015 (face_transform.transform_point(p))
1016 .map(|c| (FreeCoordinate::from(c) - 0.5) * (1. + expansion * 2.) + 0.5)
1017 + self.cube.aab().lower_bounds_v(),
1018 )
1019 }));
1020 }
1021 }
1022}
1023
1024#[cfg(test)]
1025mod tests {
1026 use crate::util::MultiFailure;
1027
1028 use super::*;
1029 use alloc::string::String;
1030 use alloc::vec::Vec;
1031 use exhaust::Exhaust;
1032 use pretty_assertions::assert_eq;
1033
1034 #[test]
1035 fn from_snapped_vector_roundtrip() {
1036 for face in Face6::ALL {
1037 let normal = face.normal_vector();
1038 let snapped = Face6::from_snapped_vector(normal);
1039 assert_eq!(Some(face), snapped, "from {normal:?}");
1040 }
1041 }
1042
1043 #[test]
1044 #[rustfmt::skip]
1045 fn from_snapped_vector_cases() {
1046 let mut f = MultiFailure::new();
1047 for (face, vector, comment) in [
1048 (Some(Face6::PZ), [0., 0., 0.], "zero tie, positive Z, positive other"),
1049 (Some(Face6::PZ), [-0., -0., 0.], "zero tie, positive Z, negative other"),
1050 (Some(Face6::NZ), [0., 0., -0.], "zero tie, negative Z, positive other"),
1051 (Some(Face6::NZ), [-0., -0., -0.], "zero tie, negative Z, negative other"),
1052
1053 (Some(Face6::NZ), [-2., -3., -3.], "2-axis tie YZ, negative"),
1054 (Some(Face6::NY), [-3., -3., -2.], "2-axis tie XY, negative"),
1055 (Some(Face6::PZ), [2., 3., 3.], "2-axis tie YZ, positive"),
1056 (Some(Face6::PY), [3., 3., 2.], "2-axis tie XY, positive"),
1057
1058 (None, [f64::NAN, 1.0, 1.0], "NaN X"),
1059 (None, [1.0, f64::NAN, 1.0], "NaN Y"),
1060 (None, [1.0, 1.0, f64::NAN], "NaN Z"),
1061 ] {
1062 f.catch(|| {
1063 let vector = FreeVector::from(vector);
1064 assert_eq!(face, Face6::from_snapped_vector(vector), "{comment}, {vector:?}");
1065 });
1066 }
1067 }
1068
1069 #[test]
1070 fn cross_6() {
1071 let mut f = MultiFailure::new();
1072 for face1 in Face6::ALL {
1073 for face2 in Face6::ALL {
1074 f.catch(|| {
1075 assert_eq!(
1077 face1.cross(face2).normal_vector::<f64, ()>(),
1078 face1.normal_vector().cross(face2.normal_vector()),
1079 "{face1:?} cross {face2:?}",
1080 );
1081 });
1082 }
1083 }
1084 }
1085
1086 #[test]
1087 fn cross_7() {
1088 let mut f = MultiFailure::new();
1089 for face1 in Face7::ALL {
1090 for face2 in Face7::ALL {
1091 f.catch(|| {
1092 assert_eq!(
1094 face1.cross(face2).normal_vector::<f64, ()>(),
1095 face1.normal_vector().cross(face2.normal_vector()),
1096 "{face1:?} cross {face2:?}",
1097 );
1098 });
1099 }
1100 }
1101 }
1102
1103 #[test]
1104 fn rotation_from_nz() {
1105 for face in Face6::ALL {
1106 let rot = face.rotation_from_nz();
1107 assert_eq!(
1108 rot.transform(Face6::NZ),
1109 face,
1110 "{face:?}: {rot:?} should rotate from NZ"
1111 );
1112 assert!(!rot.is_reflection(), "{face:?}: {rot:?} should not reflect");
1113 }
1114 }
1115
1116 #[test]
1117 fn face_transform_does_not_reflect() {
1118 for face in Face6::ALL {
1119 assert!(!face.face_transform(7).rotation.is_reflection());
1120 }
1121 }
1122
1123 #[test]
1126 fn face_map_debug_cmp() {
1127 let strings = FaceMap::<bool>::exhaust()
1128 .map(|fm| format!("{fm:?}"))
1129 .collect::<Vec<String>>();
1130 assert_eq!(
1131 strings.iter().map(String::as_str).collect::<Vec<_>>(),
1132 vec![
1133 "{all: false}",
1134 "{−x: false, −y: false, −z: false, +x: false, +y: false, +z: true}",
1135 "{−x: false, −y: false, −z: false, +x: false, +y: true, +z: false}",
1136 "{−x: false, −y: false, −z: false, +x: false, +y: true, +z: true}",
1137 "{−x: false, −y: false, −z: false, +x: true, +y: false, +z: false}",
1138 "{−x: false, −y: false, −z: false, +x: true, +y: false, +z: true}",
1139 "{−x: false, −y: false, −z: false, +x: true, +y: true, +z: false}",
1140 "{−all: false, +all: true}",
1141 "{−x: false, −y: false, −z: true, +x: false, +y: false, +z: false}",
1142 "{x: false, y: false, z: true}",
1143 "{−x: false, −y: false, −z: true, +x: false, +y: true, +z: false}",
1144 "{−x: false, −y: false, −z: true, +x: false, +y: true, +z: true}",
1145 "{−x: false, −y: false, −z: true, +x: true, +y: false, +z: false}",
1146 "{−x: false, −y: false, −z: true, +x: true, +y: false, +z: true}",
1147 "{−x: false, −y: false, −z: true, +x: true, +y: true, +z: false}",
1148 "{−x: false, −y: false, −z: true, +x: true, +y: true, +z: true}",
1149 "{−x: false, −y: true, −z: false, +x: false, +y: false, +z: false}",
1150 "{−x: false, −y: true, −z: false, +x: false, +y: false, +z: true}",
1151 "{x: false, y: true, z: false}",
1152 "{−x: false, −y: true, −z: false, +x: false, +y: true, +z: true}",
1153 "{−x: false, −y: true, −z: false, +x: true, +y: false, +z: false}",
1154 "{−x: false, −y: true, −z: false, +x: true, +y: false, +z: true}",
1155 "{−x: false, −y: true, −z: false, +x: true, +y: true, +z: false}",
1156 "{−x: false, −y: true, −z: false, +x: true, +y: true, +z: true}",
1157 "{−x: false, −y: true, −z: true, +x: false, +y: false, +z: false}",
1158 "{−x: false, −y: true, −z: true, +x: false, +y: false, +z: true}",
1159 "{−x: false, −y: true, −z: true, +x: false, +y: true, +z: false}",
1160 "{x: false, y: true, z: true}",
1161 "{−x: false, −y: true, −z: true, +x: true, +y: false, +z: false}",
1162 "{−x: false, −y: true, −z: true, +x: true, +y: false, +z: true}",
1163 "{−x: false, −y: true, −z: true, +x: true, +y: true, +z: false}",
1164 "{−x: false, −y: true, −z: true, +x: true, +y: true, +z: true}",
1165 "{−x: true, −y: false, −z: false, +x: false, +y: false, +z: false}",
1166 "{−x: true, −y: false, −z: false, +x: false, +y: false, +z: true}",
1167 "{−x: true, −y: false, −z: false, +x: false, +y: true, +z: false}",
1168 "{−x: true, −y: false, −z: false, +x: false, +y: true, +z: true}",
1169 "{x: true, y: false, z: false}",
1170 "{−x: true, −y: false, −z: false, +x: true, +y: false, +z: true}",
1171 "{−x: true, −y: false, −z: false, +x: true, +y: true, +z: false}",
1172 "{−x: true, −y: false, −z: false, +x: true, +y: true, +z: true}",
1173 "{−x: true, −y: false, −z: true, +x: false, +y: false, +z: false}",
1174 "{−x: true, −y: false, −z: true, +x: false, +y: false, +z: true}",
1175 "{−x: true, −y: false, −z: true, +x: false, +y: true, +z: false}",
1176 "{−x: true, −y: false, −z: true, +x: false, +y: true, +z: true}",
1177 "{−x: true, −y: false, −z: true, +x: true, +y: false, +z: false}",
1178 "{x: true, y: false, z: true}",
1179 "{−x: true, −y: false, −z: true, +x: true, +y: true, +z: false}",
1180 "{−x: true, −y: false, −z: true, +x: true, +y: true, +z: true}",
1181 "{−x: true, −y: true, −z: false, +x: false, +y: false, +z: false}",
1182 "{−x: true, −y: true, −z: false, +x: false, +y: false, +z: true}",
1183 "{−x: true, −y: true, −z: false, +x: false, +y: true, +z: false}",
1184 "{−x: true, −y: true, −z: false, +x: false, +y: true, +z: true}",
1185 "{−x: true, −y: true, −z: false, +x: true, +y: false, +z: false}",
1186 "{−x: true, −y: true, −z: false, +x: true, +y: false, +z: true}",
1187 "{x: true, y: true, z: false}",
1188 "{−x: true, −y: true, −z: false, +x: true, +y: true, +z: true}",
1189 "{−all: true, +all: false}",
1190 "{−x: true, −y: true, −z: true, +x: false, +y: false, +z: true}",
1191 "{−x: true, −y: true, −z: true, +x: false, +y: true, +z: false}",
1192 "{−x: true, −y: true, −z: true, +x: false, +y: true, +z: true}",
1193 "{−x: true, −y: true, −z: true, +x: true, +y: false, +z: false}",
1194 "{−x: true, −y: true, −z: true, +x: true, +y: false, +z: true}",
1195 "{−x: true, −y: true, −z: true, +x: true, +y: true, +z: false}",
1196 "{all: true}",
1197 ],
1198 );
1199 }
1200
1201 #[test]
1203 fn face_map_iter_in_enum_order() {
1204 let mut map = FaceMap::from_fn(|f| f);
1205 let expected_both: Vec<(Face6, Face6)> = Face6::ALL.into_iter().zip(Face6::ALL).collect();
1206
1207 assert_eq!(
1209 expected_both,
1210 map.iter().map(|(k, &v)| (k, v)).collect::<Vec<_>>(),
1211 );
1212
1213 assert_eq!(
1215 expected_both,
1216 map.iter_mut().map(|(k, &mut v)| (k, v)).collect::<Vec<_>>(),
1217 );
1218
1219 assert_eq!(
1221 Face6::ALL.to_vec(),
1222 map.values().copied().collect::<Vec<_>>(),
1223 );
1224
1225 assert_eq!(Face6::ALL, map.into_values());
1227 }
1228
1229 #[test]
1230 fn face_map_rotate() {
1231 assert_eq!(
1232 FaceMap {
1233 nx: 10,
1234 px: 20,
1235 ny: 11,
1236 py: 21,
1237 nz: 12,
1238 pz: 22,
1239 }
1240 .rotate(GridRotation::RyXZ),
1241 FaceMap {
1242 nx: 11,
1243 px: 21,
1244 ny: 20,
1245 py: 10,
1246 nz: 12,
1247 pz: 22,
1248 }
1249 )
1250 }
1251
1252 #[test]
1255 fn cubeface_format() {
1256 let cube_face = CubeFace {
1257 cube: Cube::new(1, 2, 3),
1258 face: Face7::NY,
1259 };
1260 assert_eq!(&format!("{cube_face:#?}"), "CubeFace((+1, +2, +3), NY)");
1261 }
1262}