cube_rotations/
lib.rs

1#![cfg_attr(not(test), no_std)]
2#![cfg_attr(debug_assertions, forbid(unsafe_code))]
3#![allow(clippy::zero_prefixed_literal)]
4#![allow(clippy::unusual_byte_groupings)]
5#![deny(clippy::missing_const_for_fn)]
6#![deny(missing_debug_implementations)]
7#![deny(missing_docs)]
8//! # Introduction
9//! This crate serves to model and solve the following couple of problems:
10//!
11//! 1. How can we model all the possible rotations that can happen to a cube,
12//!     that leave all its edges parallel to the same axes as before?
13//!
14//! 2. How many and which points must we place on this cube's surface such that
15//!     a) they are absolutely symmetric according to the previous rotations, and
16//!     b) they have absolutely no mirror symmetry?
17//!     In other words, how can we make sure that the cube will look the same from
18//!     every possible orientation, while being different than its mirror image?
19//!
20//! This is known as
21//! [_octahedral symmetry_](https://en.wikipedia.org/wiki/Octahedral_symmetry)
22//! in the available literature. The linked article does contain an explanation,
23//! but this documentation aims to be a smidge more accessible.
24//!
25//! ## Initial approach and restrictions
26//! Let there be a cube centred around the origin, with an edge-length of 6; the
27//! criterion for the length will shortly become apparent. The vertices of this
28//! cube are the eight points `[±3, ±3, ±3]`.
29//!
30//! Our first order of business, obviously, is to place one point on some face
31//! of this cube. Its faces are the set of points whose maximum coördinate by
32//! absolute value is equal to `3`. Thus, it immediately follows that all
33//! coördinates must be between `-3` and `3` inclusive.
34//!
35//! The next thing to do is to ensure that, if the cube is reflected along one
36//! of its planes of symmetry, no point will coincide with itself. Thus, the
37//! question is raised: What is the effect that a reflection has on the
38//! coördinates of a point?
39//!
40//! The answer: If the plane if symmetry is horizontal or vertical, it negates
41//! one of the coördinates. If it is diagonal, it swaps the values of
42//! two coördinates.
43//!
44//! With that in mind, we immediately arrive at two fundamental restrictions: no
45//! coördinate can be zero, or else negating it would not change it; and no
46//! coördinate can be equal to another, or else swapping them would change
47//! nothing.
48//!
49//! ## Encoding the transforms
50//! Next question: What do rotations do to a point's coördinates? And the answer
51//! is that _each even amount of reflections corresponds to a rotation_[^¹]. In
52//! other words, each rotation can be decomposed to an even amount of negations
53//! and swaps.
54//!
55//! This immediately answers both our questions. 3 coördinates can be ordered in
56//! 6 different ways in total, and their possible combinations of signs are 8.
57//! Thus, the possible transformations are 6×8 = 48 in total: 24 of them are
58//! rotations, and the other 24 are so-called
59//! _improper rotations_ or _rotoreflections_.
60//!
61//! [^¹]: This is a general property of geometry, irrespective of amount of
62//! dimensions or possibly even Euclideanness.
63//!
64//! Apart from that, for each complete transformation, each elementary
65//! reflection that comprises it needs only occur once, and they can always be
66//! examined in the same order. This permits us to encode, in just 1 bit, the
67//! application-or-not of each one of them, and thus fit all possible
68//! transformations within 6 bits. Of those, the rotations can be discerned by
69//! the fact that their binary representation will have an even amount of ones.
70//!
71//! ## Encoding coördinates
72//! With regards to the encoding of the coördinates themselves, the choice of 6
73//! for the size of the cube permits us to use integers for all 3 coördinates:
74//! `±1`, `±2`, `±3`. By choosing a specific order and signs, each of the 48
75//! possible combinations can be found.
76//!
77//! It is noteworthy that, if we assume one point to be a reference,
78//! (`[1, 2, 3]` in our case,) every other point can be found by applying one
79//! of those transformations –a different one each time– on it. This has the
80//! extremely useful property that it permits us to encode transformations and
81//! points in the exact same way. It also immediately separates those 48
82//! possible points into two groups of 24: `[1, 2, 3]` and its possible
83//! rotations, and `[3, 2, 1]` and its possible rotations. Each point belonging
84//! to one of those groups can, with some suitable rotation, be made to coincide
85//! with any other point of the same group; however, it is impossible for a
86//! rotation to make it coincide with a point from the other group. Thus, each
87//! of those two groups can comprise the solution to our original question.
88//!
89//! Of note: since each transformation retains the absolute values of the
90//! coördinates of each point, their exact values are not important. The point
91//! `[-400, 0.5, 14]` can be considered to be in `[-3, 1, 2]` and all operations
92//! remain correct.
93//!
94//! # Nomenclature
95//! In the rest of the documentation, each point is called a
96//! “[`CubeSurfacePoint`]”, and each set of operations on it (swaps and/or sign
97//! flips) is called a “Transformation” or a “[`Rotation`]”. The point
98//! `[1, 2, 3]` as also commonly called a Reference Point, because we judge all
99//! transformations relative to it.
100//!
101//! In the documentation, the two possible groups of 24 points are called
102//! _Geometric Groups_. They are sometimes distinguished into the _Reference
103//! Geometric Group_, ie the Point of Reference and the points in the same group
104//! as it, and the _Opposite Geometric Group_, ie the mirror image of the
105//! Reference Geometric Group.
106//!
107//! The afore-mentioned reflections to which each of the 6 bits corresponds (ie,
108//! swapping the value of two coördinates or negating one of them) are
109//! collectively referred to as _Elementary Reflections_.
110//!
111//! # Using the crate
112//! Let there be three points on a cube, `x`, `y`, and `z`.
113//!
114//! ```
115//! # use cube_rotations::CubeSurfacePoint::*;
116//! let x = NegThreePosOnePosTwo;
117//! let y = PosTwoPosThreeNegOne;
118//! let z = PosThreePosOneNegTwo;
119//! ```
120//!
121//! We eventually realise that `x`'s actual orientation is different:
122//!
123//! ```
124//! # use cube_rotations::CubeSurfacePoint::*;
125//! let x_actual = PosThreePosTwoPosOne;
126//! ```
127//! We want our cube to be rotated in such a way, that `x` ends up coïnciding
128//! with `x_actual`. That means that each of those points has to be rotated in
129//! the exact same way.
130//!
131//! To calculate the rotation, it suffices to perform one division:
132//! `rotation = x_actual / x`.
133//! Afterwards, the operations `r * x`, `r * y`, and `r * z` give us the results
134//! we want. Please note that the operation `(x_actual / x) * x` gives just
135//! `x_actual`, exactly as one would suppose from the notation.
136//!
137//! ```
138//! # use cube_rotations::CubeSurfacePoint::*;
139//! # let x = NegThreePosOnePosTwo;
140//! # let y = PosTwoPosThreeNegOne;
141//! # let z = PosThreePosOneNegTwo;
142//! #
143//! # let x_actual = PosThreePosTwoPosOne;
144//! let rotation = x_actual / x;
145//! ```
146//! Here is the complete code, including validation of results:
147//! ```
148//! # use cube_rotations::CubeSurfacePoint::*;
149//! let x = NegThreePosOnePosTwo;
150//! let y = PosTwoPosThreeNegOne;
151//! let z = PosThreePosOneNegTwo;
152//!
153//! let x_actual = PosThreePosTwoPosOne;
154//!
155//! let rotation = x_actual / x;
156//!
157//! assert_eq!(rotation * x, PosThreePosTwoPosOne);
158//! assert_eq!(rotation * y, NegTwoNegOnePosThree);
159//! assert_eq!(rotation * z, NegThreeNegTwoPosOne);
160//! ```
161//!
162//! # Implementation details
163//! This crate was implemented with the following criteria, in descending order
164//! of importance:
165//! 1. Correct structuring of API
166//! 2. Serving as a proof-of-concept
167//! 3. Needing as little memory as possible (RAM + program memory)
168//! 4. Performance
169//!
170//! Performance is last as, for the use-case for which this crate was
171//! implemented, those calculations are far outside of the critical path.
172//!
173//! # Safety and panics
174//! Despite this code using `unsafe` internally, running `cargo build`
175//! successfully is enough to guarantee the complete absence of panics or
176//! undefined behaviour –henceforth “UB”– in this code. This is thanks to the
177//! following properties:
178//! * All functions are pure, ie stateless: this permits them to all be `const`.
179//!     Additionally, no function has more than 2304 possible inputs. This means
180//!     that all of them can be checked exhaustively in a relatively short amount of
181//!     time, even under `miri`.
182//! * All panics/unsafety in this crate have been corralled into one function,
183//!     `unreachable_semichecked`. In debug mode, it panics; in release mode, it's
184//!     UB; and it is the only function in the entire code-base that is permitted to
185//!     do either of those things. As a result, as long as it remains indeed
186//!     unreachable, the entire code-base is free from both panics and UB.
187//! * Each function has a corresponding one that operates using a Look-Up Table—
188//!     henceforth “LUT”. The LUT is populated at compile-time by calling the
189//!     corresponding `const` function iteratively, and exhaustively. This means
190//!     that every function ends up being called with every possible input during
191//!     compilation: in other words, every possible code-path gets activated at
192//!     compile-time. (A funny result of this is that the code builds
193//!     faster with `--release` than without it.)
194//! * As a combination of the above, any possible code-path leading to the
195//!     `unreachable_semichecked` function will indeed call it during compile-time,
196//!     resulting in a compilation error as long as debug assertions are enabled.
197//!     Thus, the absence of compilation errors suffices to prove the complete
198//!     absence of panics or UB in the entire code-base.
199//! * Lastly, as a bonus: The test suite is so comprehensive that, as long as it
200//!     is intact, even a purposeful sabotage of the crate would struggle to cause
201//!     serious damage without test-breakage.
202//!
203//! That said, this crate has also been annotated with the following attribute:
204//! ```rust
205//! #![cfg_attr(debug_assertions, forbid(unsafe_code))]
206//! ```
207//! Thus, any down-stream user that prefers to forbid `unsafe` code entirely can
208//! do so by including the following snippet in their `Cargo.toml` file:
209//!
210//! ```toml
211//! [profile.dev.package.cube-rotations]
212//! debug-assertions = true
213//! ```
214//! This, of course, opens the door for some missed optimisations. If there is
215//! any way to achieve both of those things, either through regular compiler
216//! optimisations or through PGO, that has not been investigated as of yet.
217//!
218//! # Look-up tables and proper rotations
219//! The most basic and most broadly applicable data-types contained herein are
220//! [`CubeSurfacePoint`] and [`Rotation`]. They can be used to model all
221//! relevant operations, but offer no particular guarantees.
222//!
223//! Each of those two data-types is further split into two equally-sized,
224//! compementary sub-sets, each offering a particular geometric guarantee.
225//! Further, all of those 3 point-types also have wrapper data-types that
226//! operate using look-up tables. In turn, all 7 of the point data-types are
227//! generalised in the [`OctahedrallySymmetricPoint`] trait.
228//!
229//! The full list is as follows:
230//! * [`ProperRotation`]: A subset of [`Rotation`]. Models a proper rotation, ie
231//!     one that can happen in the real world.
232//! * [`ImproperRotation`]: A subset of [`Rotation`] that models a
233//!     rotoreflection. Complementary to [`ProperRotation`].
234//! * [`ReferenceGroupPoint`]: A subset of [`CubeSurfacePoint`].
235//!     Models a point in space that can be made to
236//!     coincide with the Reference Point using just one rotation.
237//! * [`OppositeGroupPoint`]: A subset of [`CubeSurfacePoint`]. Models a point
238//!     in space that can be made to coincide with the Reference Point using just
239//!     one rotoreflection. Complementary to [`ReferenceGroupPoint`].
240//! * [`luts::CubeSurfacePoint::<false>`]: As per the basic
241//!     [`CubeSurfacePoint`], but uses LUTs. Each LUT is at most 48 bytes in length,
242//!     but some operations might need multiple look-ups.
243//! * [`luts::CubeSurfacePoint::<true>`]: As per the basic
244//!     [`CubeSurfacePoint`], but uses LUTs. Each LUT is up to 2304 bytes in length,
245//!     and each operation is guaranteed to consist of a single look-up.
246//! * [`luts::ReferenceGroupPoint`]: As per the basic
247//!     [`ReferenceGroupPoint`], but uses LUTs. Each LUT is up to 576 bytes in
248//!     length, and each operation is guaranteed to consist of a single look-up.
249//! * [`luts::OppositeGroupPoint`]: As per the basic
250//!     [`OppositeGroupPoint`], but uses LUTs. Each LUT is up to 576 bytes in
251//!     length, and each operation is guaranteed to consist of a single look-up.
252//! * [`OctahedrallySymmetricPoint`]: A trait that generalises the operation of
253//!     all 7 afore-mentioned point data-types.
254//!
255//! The Geometric-Group-specific data-types of the [`luts`] module only operate
256//! using big LUTs, for reasons further analysed in their documentation.
257
258pub mod luts;
259use core::ops::{Div, Mul, MulAssign};
260
261/// Encodes the 48 possible points in space whose coördinates, in
262/// ascending order of absolute value, are equal to `[1, 2, 3]`.
263///
264/// Uses 6 bits, and is therefore represented as a `u8`.
265///
266/// # Usage
267/// ```
268/// # use cube_rotations::*;
269/// # use cube_rotations::CubeSurfacePoint::*;
270/// let test = PosOnePosTwoPosThree;
271/// assert_eq!(test as u8, 0);
272/// ```
273///
274/// # Representation
275/// The bits of each encoding correspond one-by-one to the
276/// Elementary Reflections that have to happen to the Reference Point in order
277/// to produce the result we want. The documentation for the [`Rotation`]
278/// data-type contains more details.
279///
280/// # Geometric Groups
281/// A very important property is that those points can be divided into
282/// two geometric categories, depending on whether the amount of ones in their
283/// binary representation is odd or even. In each group, each `CubeSurfacePoint`
284/// can be made to coincide with any other point using just one rotation. For it
285/// to coincide with a point from the other group, however, it'd also require a
286/// reflection, or what's called an “improper rotation” or “rotoreflection”.
287/// The [`ReferenceGroupPoint`] and [`OppositeGroupPoint`] data-types separate
288/// the two, and respectively correspond to the [`ProperRotation`] and
289/// [`ImproperRotation`] transformation data-types.
290///
291/// Below please find each possible point, along with the transformation to
292/// which it corresponds. The signs for the rotations have been chosen in
293/// accordance with the `nalgebra` crate.
294#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)]
295#[repr(u8)]
296pub enum CubeSurfacePoint {
297    /// The point `[1, 2, 3]`. Also the Point of Reference. Divided by itself,
298    /// it yields –unsurprisingly enough– the identity operation.
299    PosOnePosTwoPosThree = 00,
300    /// The point `[1, 2, -3]`. Divided by the Reference Point, it yields a
301    /// reflection through the `z = 0` plane. Its arithmetic representation is
302    /// equal to 2<sup>0</sup>, thus it is the zeroth Elementary Reflection.
303    PosOnePosTwoNegThree = 01,
304    /// The point `[1, -2, 3]`. Divided by the Reference Point, it yields a
305    /// reflection through the `y = 0` plane. Its arithmetic representation is
306    /// equal to 2<sup>1</sup>, thus it is the first Elementary Reflection.
307    PosOneNegTwoPosThree = 02,
308    /// The point `[1, -2, -3]`. Divided by the Reference Point, it yields a
309    /// rotation of 180° around the `x` axis.
310    PosOneNegTwoNegThree = 03,
311    /// The point `[-1, 2, 3]`. Divided by the Reference Point, it yields a
312    /// reflection through the `x = 0` plane. Its arithmetic representation is
313    /// equal to 2<sup>2</sup>, thus it is the second Elementary Reflection.
314    NegOnePosTwoPosThree = 04,
315    /// The point `[-1, 2, -3]`. Divided by the Reference Point, it yields a
316    /// rotation of 180° around the `y` axis.
317    NegOnePosTwoNegThree = 05,
318    /// The point `[-1, -2, 3]`. Divided by the Reference Point, it yields a
319    /// rotation of 180° around the `z` axis.
320    NegOneNegTwoPosThree = 06,
321    /// The point `[-1, -2, -3]`. Divided by the Reference Point, it yields a
322    /// complete central inversion, ie a negation of all coördinates.
323    NegOneNegTwoNegThree = 07,
324    /// The point `[3, 2, 1]`. Divided by the Reference Point, it yields a
325    /// reflection through the `x = z` plane. Its arithmetic representation is
326    /// equal to 2<sup>3</sup>, thus it is the third Elementary Reflection.
327    PosThreePosTwoPosOne = 08,
328    /// The point `[3, 2, -1]`. Divided by the Reference Point, it yields a
329    /// rotation of 90° around the `y` axis.
330    PosThreePosTwoNegOne = 09,
331    /// The point `[3, -2, 1]`. Divided by the Reference Point, it yields a
332    /// rotation of 180° around the `y = 0, z = x` axis.
333    PosThreeNegTwoPosOne = 10,
334    /// The point `[3, -2, -1]`. Divided by the Reference Point, it yields a
335    /// rotoreflection of 90° with respect to the `y` axis.
336    PosThreeNegTwoNegOne = 11,
337    /// The point `[-3, 2, 1]`. Divided by the Reference Point, it yields a
338    /// rotation of -90° around the `y` axis.
339    NegThreePosTwoPosOne = 12,
340    /// The point `[-3, 2, -1]`. Divided by the Reference Point, it yields a
341    /// reflection through the `z = -x` plane.
342    NegThreePosTwoNegOne = 13,
343    /// The point `[-3, -2, 1]`. Divided by the Reference Point, it yields a
344    /// rotoreflection of -90° with respect to the `y` axis.
345    NegThreeNegTwoPosOne = 14,
346    /// The point `[-3, -2, -1]`. Divided by the Reference Point, it yields a
347    /// rotation of 180° around the `y = 0, z = -x` axis.
348    NegThreeNegTwoNegOne = 15,
349    /// The point `[1, 3, 2]`. Divided by the Reference Point, it yields a
350    /// reflection through the `z = y` plane. Its arithmetic representation is
351    /// equal to 2<sup>4</sup>, thus it is the fourth Elementary Reflection.
352    PosOnePosThreePosTwo = 16,
353    /// The point `[1, 3, -2]`. Divided by the Reference Point, it yields a
354    /// rotation of -90° around the `x` axis.
355    PosOnePosThreeNegTwo = 17,
356    /// The point `[1, -3, 2]`. Divided by the Reference Point, it yields a
357    /// rotation of 90° around the `x` axis.
358    PosOneNegThreePosTwo = 18,
359    /// The point `[1, -3, -2]`. Divided by the Reference Point, it yields a
360    /// reflection through the `y = -z` plane.
361    PosOneNegThreeNegTwo = 19,
362    /// The point `[-1, 3, 2]`. Divided by the Reference Point, it yields a
363    /// rotation of 180° around the `x = 0, y = z` axis.
364    NegOnePosThreePosTwo = 20,
365    /// The point `[-1, 3, -2]`. Divided by the Reference Point, it yields a
366    /// rotoreflection of -90° with respect to the `x = y = z` axis.
367    NegOnePosThreeNegTwo = 21,
368    /// The point `[-1, -3, 2]`. Divided by the Reference Point, it yields a
369    /// rotoreflection of 90° with respect to the `x` axis.
370    NegOneNegThreePosTwo = 22,
371    /// The point `[-1, -3, -2]`. Divided by the Reference Point, it yields a
372    /// rotation of 180° around the `x = 0, y = -z` axis.
373    NegOneNegThreeNegTwo = 23,
374    /// The point `[3, 1, 2]`. Divided by the Reference Point, it yields a
375    /// rotation of 120° around the `x = y = z` axis.
376    PosThreePosOnePosTwo = 24,
377    /// The point `[3, 1, -2]`. Divided by the Reference Point, it yields a
378    /// rotoreflection of -60° with respect to the `x = -y = -z` axis.
379    PosThreePosOneNegTwo = 25,
380    /// The point `[3, -1, 2]`. Divided by the Reference Point, it yields a
381    /// rotoreflection of 60° with respect to the `x = y = -z` axis.
382    PosThreeNegOnePosTwo = 26,
383    /// The point `[3, -1, -2]`. Divided by the Reference Point, it yields a
384    /// rotation of -120° around the `x = -y = z` axis.
385    PosThreeNegOneNegTwo = 27,
386    /// The point `[-3, 1, 2]`. Divided by the Reference Point, it yields a
387    /// rotoreflection of 60° with respect to the `x = -y = z` axis.
388    NegThreePosOnePosTwo = 28,
389    /// The point `[-3, 1, -2]`. Divided by the Reference Point, it yields a
390    /// rotation of -120° around the `x = y = -z` axis.
391    NegThreePosOneNegTwo = 29,
392    /// The point `[-3, -1, 2]`. Divided by the Reference Point, it yields a
393    /// rotation of 120° around the `x = -y = -z` axis.
394    NegThreeNegOnePosTwo = 30,
395    /// The point `[-3, -1, -2]`. Divided by the Reference Point, it yields a
396    /// rotoreflection of -60° with respect to the `x = y = z` axis.
397    NegThreeNegOneNegTwo = 31,
398    /// The point `[2, 1, 3]`. Divided by the Reference Point, it yields a
399    /// reflection through the `y = x` plane. Its arithmetic representation is
400    /// equal to 2<sup>5</sup>, thus it is the fifth and final
401    /// Elementary Reflection.
402    PosTwoPosOnePosThree = 32,
403    /// The point `[2, 1, -3]`. Divided by the Reference Point, it yields a
404    /// rotation of 180° around the `z = 0, x = y` axis.
405    PosTwoPosOneNegThree = 33,
406    /// The point `[2, -1, 3]`. Divided by the Reference Point, it yields a
407    /// rotation of -90° around the `z` axis.
408    PosTwoNegOnePosThree = 34,
409    /// The point `[2, -1, -3]`. Divided by the Reference Point, it yields a
410    /// reflection through the `x = -y` plane.
411    PosTwoNegOneNegThree = 35,
412    /// The point `[-2, 1, 3]`. Divided by the Reference Point, it yields a
413    /// rotation of 90° around the `z` axis.
414    NegTwoPosOnePosThree = 36,
415    /// The point `[-2, 1, -3]`. Divided by the Reference Point, it yields a
416    /// rotoreflection of 90° with respect to the `z` axis.
417    NegTwoPosOneNegThree = 37,
418    /// The point `[-2, -1, 3]`. Divided by the Reference Point, it yields a
419    /// rotoreflection of -90° with respect to the `z` axis.
420    NegTwoNegOnePosThree = 38,
421    /// The point `[-2, -1, -3]`. Divided by the Reference Point, it yields a
422    /// rotation of 180° around the `z = 0, x = -y` axis.
423    NegTwoNegOneNegThree = 39,
424    /// The point `[2, 3, 1]`. Divided by the Reference Point, it yields a
425    /// rotation of -120° around the `x = y = z` axis.
426    PosTwoPosThreePosOne = 40,
427    /// The point `[2, 3, -1]`. Divided by the Reference Point, it yields a
428    /// rotoreflection of -60° with respect to the `x = -y = z` axis.
429    PosTwoPosThreeNegOne = 41,
430    /// The point `[2, -3, 1]`. Divided by the Reference Point, it yields a
431    /// rotoreflection of 60° with respect to the `x = -y = -z` axis.
432    PosTwoNegThreePosOne = 42,
433    /// The point `[2, -3, -1]`. Divided by the Reference Point, it yields a
434    /// rotation of 120° around the `x = y = -z` axis.
435    PosTwoNegThreeNegOne = 43,
436    /// The point `[-2, 3, 1]`. Divided by the Reference Point, it yields a
437    /// rotoreflection of -60° with respect to the `x = y = -z` axis.
438    NegTwoPosThreePosOne = 44,
439    /// The point `[-2, 3, -1]`. Divided by the Reference Point, it yields a
440    /// rotation of -120° around the `x = -y = -z` axis.
441    NegTwoPosThreeNegOne = 45,
442    /// The point `[-2, -3, 1]`. Divided by the Reference Point, it yields a
443    /// rotation of 120° around the `x = -y = z` axis.
444    NegTwoNegThreePosOne = 46,
445    /// The point `[-2, -3, -1]`. Divided by the Reference Point, it yields a
446    /// rotoreflection of 60° with respect to the `x = y = z` axis.
447    NegTwoNegThreeNegOne = 47,
448}
449
450/// A data-type that describes, within 6 bits, the transformation from one
451/// [`CubeSurfacePoint`] to another.
452///
453/// # Usage
454/// ```
455/// #  {
456/// # use cube_rotations::*;
457/// # fn random_point() -> CubeSurfacePoint {
458/// #   let x = rand::random::<u8>() % 48;
459/// #   x.try_into().unwrap()
460/// # }
461/// # let beginning_point = random_point();
462/// # let ending_point = random_point();
463/// let rotation = ending_point / beginning_point;
464/// assert_eq!(beginning_point * rotation, ending_point);
465/// # }
466/// ```
467///
468/// # Representation
469/// For a complete correspondence between _arithmetic values_ and geometric
470/// transformations, please refer to the documentation for
471/// [`CubeSurfacePoint`]. In here, we will describe the correspondence of each
472/// transformation with its _binary representation_.
473///
474/// The gist is that the three first bits describe the order in which the three
475/// coördinates must be positioned, while the last three bits describe which of
476/// their signs have to be flipped. It must be noted that the bits cannot be
477/// examined in arbitrary order: Bit 3 must always be examined first, and the
478/// three last bits must be examined last. Their order and meaning is as
479/// follows:
480///  * **3**: Swaps coöordinates 1 and 3.
481///  * **4**: Swaps coöordinates 2 and 3.
482///  * **5**: Swaps coöordinates 1 and 2.
483///  * **2**: Flips the sign of coördinate 1.
484///  * **1**: Flips the sign of coördinate 2.
485///  * **0**: Flips the sign of coördinate 3.
486///
487/// Also note that this data-type encodes both proper and improper rotations.
488/// The [`ProperRotation`] and [`ImproperRotation`] data-types separate the two.
489///
490/// # Multiplying/dividing rotations together
491/// The various rotation data-types can be multiplied, and therefore
492/// have had `Mul` and `Div` implemented between
493/// them:
494/// * The ordinary `Rotation` can only be multiplied with itself.
495/// * In contrast, the `ProperRotation` and the `ImproperRotation` can be
496///     multiplied both with themselves and with each other.
497#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)]
498pub struct Rotation {
499    corresponding_point: CubeSurfacePoint,
500}
501
502/// Extracts the rotation that must occur so that the `divisor` point ends up
503/// coinciding with `self`, ie the dividend.
504impl Div for CubeSurfacePoint {
505    /// A rotation, either proper or improper.
506    type Output = Rotation;
507
508    /// The operation occurs by finding the reciprocal of the `divisor`, and
509    /// rotating it as described by `self`.
510    fn div(self, divisor: Self) -> Self::Output {
511        self.div(divisor)
512    }
513}
514
515/// Rotates a copy of `self` per some `Rotation`.
516impl Mul<Rotation> for CubeSurfacePoint {
517    /// Output same as input.
518    type Output = Self;
519
520    /// The operation is performed by examining the bits of the `Rotation`
521    /// one-by-one, then performing the corresponding elementary reflections on
522    /// `self`.
523    fn mul(self, rot: Rotation) -> Self::Output {
524        self.mul(rot)
525    }
526}
527
528/// Rotates `self` per some `Rotation`.
529impl MulAssign<Rotation> for CubeSurfacePoint {
530    /// The data-type doesn't change, so the result can be directly
531    /// assigned.
532    fn mul_assign(&mut self, rot: Rotation) {
533        *self = *self * rot;
534    }
535}
536
537/// Converts an `u8` to a `CubeSurfacePoint`, if it is within limits.
538///
539/// If it is not, the `u8` is returned as-is.
540impl core::convert::TryFrom<u8> for CubeSurfacePoint {
541    type Error = u8;
542
543    fn try_from(input: u8) -> Result<Self, Self::Error> {
544        Self::try_from_u8(input).ok_or(input)
545    }
546}
547
548impl Rotation {
549    /// Because [`Mul`] is not `const`.
550    pub const fn mul(self, x: CubeSurfacePoint) -> CubeSurfacePoint {
551        x.mul(self)
552    }
553}
554
555/// Rotates a given `CubeSurfacePoint` according to `self`.
556impl Mul<CubeSurfacePoint> for Rotation {
557    /// Output same as input.
558    type Output = CubeSurfacePoint;
559
560    /// The operation is performed by examining `self`'s bits one by one, and
561    /// performing on the other `surface_point` the corresponding elementary
562    /// reflections.
563    fn mul(self, cub_sur_pt: CubeSurfacePoint) -> Self::Output {
564        cub_sur_pt * self
565    }
566}
567
568/// Helper function for marking code that's dead, and therefore unreachable.
569/// In other words, if this function gets actually called, something has
570/// gone very wrong.
571///
572/// If debug assertions had been disabled, this function would be immediate
573/// Undefined Behaviour. With the current compilation flags, however, it
574/// merely panics unconditionally.
575#[cfg(debug_assertions)]
576#[cold]
577const fn unreachable_semichecked<T>() -> T {
578    debug_assert!(false, "Dead code was called!");
579    panic!()
580}
581
582/// Helper function for marking code that's dead, and therefore unreachable.
583/// In other words, if this function gets actually called, something has
584/// gone very wrong.
585///
586/// If debug assertions had been enabled, this function would panic
587/// unconditionally. With the current compilation flags, however, it
588/// produces immediate Undefined Behaviour.
589#[cfg(not(debug_assertions))]
590#[cold]
591const fn unreachable_semichecked<T>() -> T {
592    debug_assert!(false, "Dead code was called!");
593    unsafe { core::hint::unreachable_unchecked() }
594}
595
596impl CubeSurfacePoint {
597    /// The point chosen as a Reference Point, according to which all rotations
598    /// are judged.
599    ///
600    /// Its binary representation is 0 by definition.
601    ///
602    /// The point `[1, 2, 3]` has been chosen as the Reference Point for this
603    /// crate, corresponding to `CubeSurfacePoint::PosOnePosTwoPosThree`.
604    pub const REFERENCE_POINT: Self = Self::probs_from_u8(0);
605
606    /// Counts how many ones there are in the binary representation of a certain
607    /// [`CubeSurfacePoint`], so it can be judged in which geometric group it
608    /// belongs.
609    ///
610    /// ```
611    /// #  {
612    /// # use cube_rotations::CubeSurfacePoint::*;
613    /// assert_eq!(PosTwoNegThreeNegOne.odd_ones(), false);
614    /// # }
615    /// ```
616    pub const fn odd_ones(self) -> bool {
617        (self as u8).count_ones() & 1 != 0
618    }
619
620    /// Equal to the negation of [`CubeSurfacePoint::odd_ones`] by definition.
621    pub const fn even_ones(self) -> bool {
622        !self.odd_ones()
623    }
624
625    const fn xor(self, bit_mask: u8) -> Self {
626        Self::probs_from_u8(self as u8 ^ bit_mask)
627    }
628
629    /// Discriminates a given [`CubeSurfacePoint`] depending on the geometric
630    /// group to which it belongs. If it belongs to the Reference Rotation
631    /// Group, it returns an `Ok<ReferenceGroupPoint>`, else it returns an
632    /// `Err<OppositeGroupPoint>`.
633    #[inline(always)]
634    pub const fn determine_group(
635        self: CubeSurfacePoint,
636    ) -> Result<ReferenceGroupPoint, OppositeGroupPoint> {
637        use CubeSurfacePoint as Csp;
638
639        macro_rules! test {
640            ($type:ty; $($names:ident),+ $(,)?) => {
641                match self {
642                    $( // They must have both the same binary representation,
643                        _ if (self as u8 == <$type>::$names as u8) &&
644                                (self as u8 == Csp::$names as u8) => {
645                            <$type>::$names
646                        }, // and the same name.
647                    )+
648                    _ => unreachable_semichecked(),
649                }
650            };
651        }
652        if self.even_ones() {
653            Ok(test!(ReferenceGroupPoint;
654                PosOnePosTwoPosThree, PosOneNegTwoNegThree,
655                NegOnePosTwoNegThree, NegOneNegTwoPosThree,
656                PosThreePosTwoNegOne, PosThreeNegTwoPosOne,
657                NegThreePosTwoPosOne, NegThreeNegTwoNegOne,
658                PosOnePosThreeNegTwo, PosOneNegThreePosTwo,
659                NegOnePosThreePosTwo, NegOneNegThreeNegTwo,
660                PosThreePosOnePosTwo, PosThreeNegOneNegTwo,
661                NegThreePosOneNegTwo, NegThreeNegOnePosTwo,
662                PosTwoPosOneNegThree, PosTwoNegOnePosThree,
663                NegTwoPosOnePosThree, NegTwoNegOneNegThree,
664                PosTwoPosThreePosOne, PosTwoNegThreeNegOne,
665                NegTwoPosThreeNegOne, NegTwoNegThreePosOne,
666            ))
667        } else {
668            Err(test!(OppositeGroupPoint;
669                PosOnePosTwoNegThree, PosOneNegTwoPosThree,
670                NegOnePosTwoPosThree, NegOneNegTwoNegThree,
671                PosThreePosTwoPosOne, PosThreeNegTwoNegOne,
672                NegThreePosTwoNegOne, NegThreeNegTwoPosOne,
673                PosOnePosThreePosTwo, PosOneNegThreeNegTwo,
674                NegOnePosThreeNegTwo, NegOneNegThreePosTwo,
675                PosThreePosOneNegTwo, PosThreeNegOnePosTwo,
676                NegThreePosOnePosTwo, NegThreeNegOneNegTwo,
677                PosTwoPosOnePosThree, PosTwoNegOneNegThree,
678                NegTwoPosOneNegThree, NegTwoNegOnePosThree,
679                PosTwoPosThreeNegOne, PosTwoNegThreePosOne,
680                NegTwoPosThreePosOne, NegTwoNegThreeNegOne,
681            ))
682        }
683    }
684
685    /// The same as [`CubeSurfacePoint::determine_group()`], but returns an `Ok`
686    /// value for `OppositeGroupPoint`s and an `Err` value otherwise.
687    #[inline(always)]
688    pub const fn determine_antigroup(
689        self: CubeSurfacePoint,
690    ) -> Result<OppositeGroupPoint, ReferenceGroupPoint> {
691        match self.determine_group() {
692            Ok(a) => Err(a),
693            Err(a) => Ok(a),
694        }
695    }
696
697    /// Used in implementing the division. Shouldn't be needed for end users;
698    /// even if it is, it can be computed by
699    /// `(REFERENCE_POINT / self) * REFERENCE_POINT`.
700    const fn reciprocal(self) -> Self {
701        let mut result = Self::probs_from_u8(self as u8 & 7);
702
703        if self as u8 & 0b100_000 != 0 {
704            result = result.swap_x_y();
705        }
706
707        if self as u8 & 0b010_000 != 0 {
708            result = result.swap_y_z();
709        }
710
711        if self as u8 & 0b001_000 != 0 {
712            result = result.swap_z_x();
713        }
714
715        result
716    }
717
718    /// Implements the fifth Elementary Reflection.
719    const fn swap_x_y(&self) -> Self {
720        let mut x = *self as u8;
721        if x & 0b010_000 != 0 {
722            x ^= 0b001_000;
723        } else {
724            x ^= 0b100_000;
725        }
726        if (x + 2) & 7 > 3 {
727            x ^= 0b000_110;
728        }
729        Self::probs_from_u8(x)
730    }
731
732    /// Implements the fourth Elementary Reflection.
733    const fn swap_y_z(&self) -> Self {
734        let mut x = *self as u8;
735        if x >= 0b100_000 {
736            x ^= 0b001_000;
737        } else {
738            x ^= 0b010_000;
739        }
740        if (x + 1) & 3 > 1 {
741            x ^= 0b000_011;
742        }
743        Self::probs_from_u8(x)
744    }
745
746    /// Implements the third Elementary Reflection.
747    const fn swap_z_x(&self) -> Self {
748        let mut x = *self as u8;
749        if x < 0b010_000 {
750            x ^= 0b001_000;
751        } else {
752            x ^= 0b111_000;
753        }
754        let x_ = (x & 5).wrapping_sub(1);
755        if x_ & 7 < 4 {
756            x ^= 0b000_101;
757        }
758        Self::probs_from_u8(x)
759    }
760
761    // For the curious: The second, first, and zeroth Elementary Reflections
762    // are jointly implemented as simple bitwise XOR.
763
764    /// Returns the point in the corresponding position of the opposite side of
765    /// the cube.
766    ///
767    /// Basically, finds which coördinate has an absolute value of 3 and
768    /// multiplies it by -1.
769    ///
770    /// ```
771    /// #  {
772    /// # use cube_rotations::CubeSurfacePoint::*;
773    /// assert_eq!(PosOnePosTwoPosThree.opposite(), PosOnePosTwoNegThree);
774    /// # }
775    /// ```
776    pub const fn opposite(self) -> Self {
777        if (self as u8) & 0b011_000 == 0 {
778            self.xor(0b001)
779        } else if (self as u8) & 0b101_000 == 0b001_000 {
780            self.xor(0b100)
781        } else {
782            self.xor(0b010)
783        }
784    }
785
786    /// Returns the other point found in the same edge of the same face of the
787    /// cube.
788    ///
789    /// Basically, finds which coördinate has an absolute value of 1 and
790    /// multiplies it by -1.
791    ///
792    /// ```
793    /// #  {
794    /// # use cube_rotations::CubeSurfacePoint::*;
795    /// assert_eq!(PosOnePosTwoPosThree.beside(), NegOnePosTwoPosThree);
796    /// # }
797    /// ```
798    pub const fn beside(self) -> Self {
799        if (self as u8) >= 24 && (self as u8) < 40 {
800            self.xor(0b010)
801        } else if (self as u8) & 0b001_000 != 0 {
802            self.xor(0b001)
803        } else {
804            self.xor(0b100)
805        }
806    }
807
808    /// Functionally identical to `self.opposite().beside()`, or
809    /// (equivalently) `self.beside().opposite()`. Implemented separately
810    /// for optimisation purposes, as
811    /// 1. It was useful for our purposes,
812    /// 2. It maintains the geometric group of its input, and
813    /// 3. It is _way_ simpler to implement than either function separately.
814    ///
815    /// Corresponds to a rotation of 180° as seen from the face to which the
816    /// point is _closest_ without being _on_.
817    ///
818    /// ```
819    /// #  {
820    /// # use cube_rotations::CubeSurfacePoint::*;
821    /// assert_eq!(PosOnePosTwoPosThree.opposite_then_beside(), NegOnePosTwoNegThree);
822    /// # }
823    /// ```
824    pub const fn opposite_then_beside(self) -> Self {
825        if (self as u8) >= 32 {
826            self.xor(0b011)
827        } else if (self as u8) < 16 {
828            self.xor(0b101)
829        } else {
830            self.xor(0b110)
831        }
832    }
833
834    /// Different name for [`CubeSurfacePoint::opposite_then_beside`].
835    pub const fn beside_then_opposite(self) -> Self {
836        self.opposite_then_beside()
837    }
838
839    /// Different name for [`CubeSurfacePoint::beside`].
840    pub const fn flip_sign_of_1(self) -> Self {
841        self.beside()
842    }
843
844    /// No idea if this if going to be useful, but it's so easy to implement
845    /// that we might as well do so for completeness.
846    pub const fn flip_sign_of_2(self) -> Self {
847        if (self as u8) >= 32 {
848            self.xor(0b100)
849        } else if (self as u8) < 16 {
850            self.xor(0b010)
851        } else {
852            self.xor(0b001)
853        }
854    }
855
856    /// Different name for the [`CubeSurfacePoint::opposite`] function.
857    pub const fn flip_sign_of_3(self) -> Self {
858        self.opposite()
859    }
860
861    /// Different name for [`CubeSurfacePoint::opposite_then_beside`].
862    ///
863    /// Corresponds to a rotation of 180° as seen from the face to which the
864    /// point is _closest_ without being _on_.
865    pub const fn flip_1_and_3(self) -> Self {
866        self.opposite_then_beside()
867    }
868
869    /// A rotation of 180° as seen from a face which the
870    /// point is neither _on_ nor _close to_.
871    pub const fn flip_2_and_3(self) -> Self {
872        if (self as u8) >= 24 && (self as u8) < 40 {
873            self.xor(0b101)
874        } else if (self as u8) & 0b001_000 != 0 {
875            self.xor(0b110)
876        } else {
877            self.xor(0b011)
878        }
879    }
880
881    /// A rotation of 180° as seen from the face _on_ which the
882    /// point is located.
883    pub const fn flip_1_and_2(self) -> Self {
884        if (self as u8) & 0b011_000 == 0 {
885            self.xor(0b110)
886        } else if (self as u8) & 0b101_000 == 0b001_000 {
887            self.xor(0b011)
888        } else {
889            self.xor(0b101)
890        }
891    }
892
893    /// Helper function for unwrapping an [`Option`](core::option::Option) if it
894    /// actually exists. If it does not, it calls [`unreachable_semichecked`],
895    /// either panicking or producing Undefined Behaviour depending on compiler
896    /// optimisation flags.
897    const fn const_unwrap_semichecked(x: Option<Self>) -> Self {
898        debug_assert!(
899            x.is_some(),
900            "This value does not correspond to any cube surface point!"
901        );
902        if let Some(y) = x {
903            y
904        } else {
905            unreachable_semichecked()
906        }
907    }
908
909    /// Transforms its argument into a [`CubeSurfacePoint`], if it is within
910    /// bounds. If it is not, it either panics or UBs depending on compiler
911    /// optimisation flags. (See also [`unreachable_semichecked`].)
912    const fn probs_from_u8(x: u8) -> Self {
913        Self::const_unwrap_semichecked(Self::try_from_u8(x))
914    }
915
916    /// Checks if its argument is within the limit of legal values for
917    /// [`CubeSurfacePoint`]s, and if so returns the `CubeSurfacePoint` to which
918    /// it corresponds.
919    ///
920    /// Much more legible than (its alternative)[`try_from_u8_unreadable`], but
921    /// uses `unsafe`. The two versions are nonetheless functionally identical:
922    /// they both compile to the same assembly, ie a comparison and a no-op.
923    #[cfg(not(debug_assertions))]
924    const fn _try_from_u8_readable(x: u8) -> Option<Self> {
925        if x < 48 {
926            unsafe { core::mem::transmute(x) }
927        } else {
928            None
929        }
930    }
931
932    /// Checks if its argument is within the limit of legal values for
933    /// [`CubeSurfacePoint`]s, and if so returns the `CubeSurfacePoint` to which
934    /// it corresponds.
935    ///
936    /// Fully safe, utterly unreadable, and functionally identical to
937    /// `(x < 48).then (|| unsafe { core::mem::transmute(x) } )`—both versions
938    /// compile to the same assembly, ie a comparison and a no-op.
939    #[rustfmt::skip]
940    #[inline(always)]
941    const fn try_from_u8_unreadable(x: u8) -> Option<Self> {
942        macro_rules! test {
943            ($($names:ident $(= $_whatever:literal)?),+ $(,)?) => {
944                match x {
945                    $(
946                        _ if x == CubeSurfacePoint::$names as u8 => {
947                            Some(CubeSurfacePoint::$names)
948                        },
949                    )*
950                    _ => None,
951                }
952            };
953        }
954
955        test!(
956            PosOnePosTwoPosThree, PosOnePosTwoNegThree, PosOneNegTwoPosThree,
957            PosOneNegTwoNegThree, NegOnePosTwoPosThree, NegOnePosTwoNegThree,
958            NegOneNegTwoPosThree, NegOneNegTwoNegThree, PosThreePosTwoPosOne,
959            PosThreePosTwoNegOne, PosThreeNegTwoPosOne, PosThreeNegTwoNegOne,
960            NegThreePosTwoPosOne, NegThreePosTwoNegOne, NegThreeNegTwoPosOne,
961            NegThreeNegTwoNegOne, PosOnePosThreePosTwo, PosOnePosThreeNegTwo,
962            PosOneNegThreePosTwo, PosOneNegThreeNegTwo, NegOnePosThreePosTwo,
963            NegOnePosThreeNegTwo, NegOneNegThreePosTwo, NegOneNegThreeNegTwo,
964            PosThreePosOnePosTwo, PosThreePosOneNegTwo, PosThreeNegOnePosTwo,
965            PosThreeNegOneNegTwo, NegThreePosOnePosTwo, NegThreePosOneNegTwo,
966            NegThreeNegOnePosTwo, NegThreeNegOneNegTwo, PosTwoPosOnePosThree,
967            PosTwoPosOneNegThree, PosTwoNegOnePosThree, PosTwoNegOneNegThree,
968            NegTwoPosOnePosThree, NegTwoPosOneNegThree, NegTwoNegOnePosThree,
969            NegTwoNegOneNegThree, PosTwoPosThreePosOne, PosTwoPosThreeNegOne,
970            PosTwoNegThreePosOne, PosTwoNegThreeNegOne, NegTwoPosThreePosOne,
971            NegTwoPosThreeNegOne, NegTwoNegThreePosOne, NegTwoNegThreeNegOne,
972        )
973    }
974
975    /// A helper `const` function for turning `u8`s into `CubeSurfacePoint`s.
976    #[inline(always)]
977    pub const fn try_from_u8(x: u8) -> Option<Self> {
978        Self::try_from_u8_unreadable(x)
979    }
980
981    /// Because [`Mul`] is not `const`.
982    pub const fn mul(self, rot: Rotation) -> Self {
983        let rot = rot.corresponding_point as u8;
984        let mut result = self;
985
986        if rot & 0b001_000 != 0 {
987            result = result.swap_z_x();
988        }
989
990        if rot & 0b010_000 != 0 {
991            result = result.swap_y_z();
992        }
993
994        if rot & 0b100_000 != 0 {
995            result = result.swap_x_y();
996        }
997
998        // As previously mentioned, this implements the second, first, and
999        // zeroth Elementary Reflections, conditionally, using one bit-wise XOR.
1000        let rot = rot & 7;
1001
1002        Self::probs_from_u8(result as u8 ^ rot)
1003    }
1004
1005    /// Because [`Div`] is not `const`.
1006    pub const fn div(self, divisor: Self) -> Rotation {
1007        // No idea if this can be made faster. Was not important for our
1008        // use-case.
1009        let reciprocal = divisor.reciprocal();
1010        let corresponding_point = Rotation {
1011            corresponding_point: self,
1012        }
1013        .mul(reciprocal);
1014        Rotation {
1015            corresponding_point,
1016        }
1017    }
1018
1019    /// Returns the `CubeSurfacePoint` which is positioned 90°
1020    /// from `self` –clockwise or anti-clockwise depending on the `const`
1021    /// parametre of the same name– as viewed from the edge on which both are
1022    /// located.
1023    pub const fn one_right_angle<const CLOCKWISE: bool>(self) -> Self {
1024        let mut result = self;
1025        let x = self as u8;
1026        let mask = if x & 0b011_000 == 0 {
1027            result = result.swap_x_y();
1028            if (x & 0b000_001 == 0) == CLOCKWISE {
1029                0b000_010
1030            } else {
1031                0b000_100
1032            }
1033        } else if x & 0b101_000 == 0b001_000 {
1034            result = result.swap_y_z();
1035            if (x & 0b000_100 == 0) == CLOCKWISE {
1036                0b000_001
1037            } else {
1038                0b000_010
1039            }
1040        } else {
1041            result = result.swap_z_x();
1042            if (x & 0b000_010 == 0) == CLOCKWISE {
1043                0b000_100
1044            } else {
1045                0b000_001
1046            }
1047        };
1048        result.xor(mask)
1049    }
1050
1051    /// Returns the `CubeSurfacePoint` which is positioned 90° clockwise
1052    /// from `self`, as viewed from the edge on which both are located.
1053    /// ```
1054    /// #  {
1055    /// # use cube_rotations::*;
1056    /// # use cube_rotations::CubeSurfacePoint::*;
1057    /// # fn random_point() -> CubeSurfacePoint {
1058    /// #   let x = rand::random::<u8>() % 48;
1059    /// #   x.try_into().unwrap()
1060    /// # }
1061    /// # let random_point = random_point();
1062    /// assert_eq!(random_point.direction(),
1063    ///         random_point.one_right_angle_cw().direction())
1064    /// # }
1065    /// ```
1066    pub const fn one_right_angle_cw(self) -> Self {
1067        self.one_right_angle::<true>()
1068    }
1069
1070    /// Returns the `CubeSurfacePoint` which is positioned 90° anti-clockwise
1071    /// from `self`, as viewed from the edge on which both are located.
1072    /// ```
1073    /// #  {
1074    /// # use cube_rotations::*;
1075    /// # use cube_rotations::CubeSurfacePoint::*;
1076    /// # fn random_point() -> CubeSurfacePoint {
1077    /// #   let x = rand::random::<u8>() % 48;
1078    /// #   x.try_into().unwrap()
1079    /// # }
1080    /// # let random_point = random_point();
1081    /// assert_eq!(random_point.direction(),
1082    ///         random_point.one_right_angle_acw().direction())
1083    /// # }
1084    /// ```
1085    pub const fn one_right_angle_acw(self) -> Self {
1086        self.one_right_angle::<false>()
1087    }
1088
1089    /// Returns the `CubeSurfacePoint` which is positioned (90 * `n`)°
1090    /// from `self` –clockwise or anti-clockwise depending on the `const`
1091    /// parametre of the same name–, as viewed from the edge on which both are
1092    /// located.
1093    pub const fn n_right_angles<const CLOCKWISE: bool>(self, angle: u8) -> Self {
1094        let angle = angle & 0b11;
1095
1096        let one_or_three = if CLOCKWISE { 1 } else { 3 };
1097        let three_or_one = if CLOCKWISE { 3 } else { 1 };
1098
1099        match angle {
1100            0 => self,
1101            _ if angle == one_or_three => self.one_right_angle_cw(),
1102            _ if angle == three_or_one => self.one_right_angle_acw(),
1103            2 => self.flip_1_and_2(),
1104            _ => {
1105                debug_assert!(false, "A 2-bit number cannot be larger than 3!");
1106                unreachable_semichecked()
1107            }
1108        }
1109    }
1110
1111    /// Returns the `CubeSurfacePoint` which is positioned (90 * `n`)°
1112    /// clockwise
1113    /// from `self`, as viewed from the edge on which both are located.
1114    ///
1115    /// ```
1116    /// #  {
1117    /// # use cube_rotations::*;
1118    /// # use cube_rotations::CubeSurfacePoint::*;
1119    /// let surface_point = PosOnePosTwoPosThree;
1120    /// let rotated_1 = surface_point
1121    ///         .one_right_angle_cw()
1122    ///         .one_right_angle_cw();
1123    /// let rotated_2 = surface_point
1124    ///         .n_right_angles_cw(2);
1125    /// assert_eq!(rotated_1, rotated_2);
1126    /// # }
1127    /// ```
1128    pub const fn n_right_angles_cw(self, angle: u8) -> CubeSurfacePoint {
1129        self.n_right_angles::<true>(angle)
1130    }
1131
1132    /// Returns the `CubeSurfacePoint` which is positioned (90 * `n`)°
1133    /// anti-clockwise
1134    /// from `self`, as viewed from the edge on which both are located.
1135    ///
1136    /// ```
1137    /// #  {
1138    /// # use cube_rotations::*;
1139    /// # use cube_rotations::CubeSurfacePoint::*;
1140    /// let surface_point = PosOnePosTwoPosThree;
1141    /// let rotated_1 = surface_point
1142    ///         .one_right_angle_cw()
1143    ///         .one_right_angle_cw()
1144    ///         .one_right_angle_cw();
1145    /// let rotated_2 = surface_point
1146    ///         .n_right_angles_acw(1);
1147    /// assert_eq!(rotated_1, rotated_2);
1148    /// # }
1149    /// ```
1150    pub const fn n_right_angles_acw(self, angle: u8) -> CubeSurfacePoint {
1151        self.n_right_angles::<false>(angle)
1152    }
1153
1154    /// Returns the spatial direction towards which `self` is oriented.
1155    ///
1156    /// Essentially finds which coördinate has an absolute value of 3, and
1157    /// judges by that.
1158    ///
1159    /// ```
1160    /// #  {
1161    /// # use cube_rotations::CubeSurfacePoint::*;
1162    /// # use cube_rotations::Direction::*;
1163    /// assert_eq!(PosOnePosTwoPosThree.direction(), Up);
1164    /// assert_eq!(PosOnePosTwoNegThree.direction(), Down);
1165    /// assert_eq!(PosOnePosThreePosTwo.direction(), Front);
1166    /// assert_eq!(PosOneNegThreePosTwo.direction(), Back);
1167    /// assert_eq!(PosThreePosTwoPosOne.direction(), Right);
1168    /// assert_eq!(NegThreePosTwoPosOne.direction(), Left);
1169    /// # }
1170    /// ```
1171    pub const fn direction(self) -> Direction {
1172        use Direction::*;
1173        let x = self as u8;
1174        if x & 0b011_000 == 0 {
1175            [Down, Up][(x & 0b000_001 == 0) as usize]
1176        } else if x & 0b101_000 == 0b001_000 {
1177            [Left, Right][(x & 0b000_100 == 0) as usize]
1178        } else {
1179            [Back, Front][(x & 0b000_010 == 0) as usize]
1180        }
1181    }
1182}
1183
1184/// Describes towards which direction the face of a cube is oriented.
1185///
1186/// ```
1187/// #  {
1188/// # use cube_rotations::CubeSurfacePoint::*;
1189/// # use cube_rotations::Direction::*;
1190/// assert_eq!(PosOnePosTwoPosThree.direction(), Up);
1191/// # }
1192/// ```
1193#[derive(PartialEq, Eq, Debug, Clone, Copy, PartialOrd, Ord)]
1194pub enum Direction {
1195    /// Towards the negative `x` half-axis.
1196    Left,
1197    /// Towards the positive `x` half-axis.
1198    Right,
1199    /// Towards the negative `y` half-axis.
1200    Back,
1201    /// Towards the positive `y` half-axis.
1202    Front,
1203    /// Towards the negative `z` half-axis.
1204    Down,
1205    /// Towards the positive `z` half-axis.
1206    Up,
1207}
1208
1209/// A trait that groups the common behaviour of all possible data-types that
1210/// represent points on the surface of a cube.
1211///
1212/// Those can be [`CubeSurfacePoint`]s, [`ReferenceGroupPoint`]s,
1213///  [`OppositeGroupPoint`]s, or their LUT equivalents.
1214pub trait OctahedrallySymmetricPoint:
1215    Into<CubeSurfacePoint>
1216    + TryFrom<CubeSurfacePoint>
1217    + Div<Self>
1218    + Mul<ProperRotation, Output = Self>
1219    + Mul<ImproperRotation, Output = Self::OtherGroup>
1220    + MulAssign<ProperRotation>
1221{
1222    /// The geometric group mirror to the one to which `self` belongs.
1223    type OtherGroup;
1224
1225    /// Returns the other point found in the same edge of the same face of the
1226    /// cube.
1227    fn beside(self) -> Self::OtherGroup;
1228
1229    /// Returns the point in the corresponding position of the opposite face of
1230    /// the cube.
1231    fn opposite(self) -> Self::OtherGroup;
1232
1233    /// Functionally identical to `self.opposite().beside()`. Also available
1234    /// with the words swapped, as the order doesn't matter.
1235    ///
1236    /// Corresponds to a rotation of 180° as seen from the face to which the
1237    /// point is _closest_ without being _on_.
1238    fn opposite_then_beside(self) -> Self;
1239
1240    /// Returns the point which is positioned 90° clockwise
1241    /// from `self`, as viewed from the face on which both are located.
1242    fn one_right_angle_cw(self) -> Self;
1243
1244    /// Returns the point which is positioned 90° anti-clockwise
1245    /// from `self`, as viewed from the face on which both are located.
1246    fn one_right_angle_acw(self) -> Self;
1247
1248    /// Returns the point which is positioned (n * 90)° clockwise
1249    /// from `self`, as viewed from the face on which both are located.
1250    fn n_right_angles_cw(self, angle: u8) -> Self;
1251
1252    /// Returns the point which is positioned (n * 90)° anti-clockwise
1253    /// from `self`, as viewed from the face on which both are located.
1254    fn n_right_angles_acw(self, angle: u8) -> Self;
1255
1256    /// Flips the sign of the coördinate whose absolute value is 2.
1257    fn flip_sign_of_2(self) -> Self::OtherGroup;
1258
1259    /// Flips the sign of the coördinates whose absolute values are 2 and 3.
1260    ///
1261    /// Corresponds to a rotation of 180° as seen from a face which the
1262    /// point is neither _on_ nor _close to_.
1263    fn flip_2_and_3(self) -> Self;
1264
1265    //-------------------------Provided methods---------------------------------
1266
1267    /// Returns the spatial direction towards which `self` is oriented.
1268    fn direction(self) -> Direction {
1269        Into::<CubeSurfacePoint>::into(self).direction()
1270    }
1271
1272    /// Counts how many ones there are in the binary representation of a certain
1273    /// point, so it can be judged in which geometric group it belongs.
1274    fn odd_ones(self) -> bool {
1275        Into::<CubeSurfacePoint>::into(self).odd_ones()
1276    }
1277
1278    /// Equal to the negation of [`Self::odd_ones`] by definition.
1279    fn even_ones(self) -> bool {
1280        !self.odd_ones()
1281    }
1282
1283    /// Returns the point which is positioned (n * 90)°
1284    /// from `self`, as viewed from the face on which both are located.
1285    fn n_right_angles<const CLOCKWISE: bool>(self, angle: u8) -> Self {
1286        if CLOCKWISE {
1287            self.n_right_angles_cw(angle)
1288        } else {
1289            self.n_right_angles_acw(angle)
1290        }
1291    }
1292
1293    /// Different name for [`CubeSurfacePoint::opposite_then_beside`].
1294    ///
1295    /// Corresponds to a rotation of 180° as seen from the face to which the
1296    /// point is _closest_ without being _on_.
1297    fn beside_then_opposite(self) -> Self {
1298        self.opposite_then_beside()
1299    }
1300
1301    /// Different name for [`CubeSurfacePoint::opposite_then_beside`].
1302    ///
1303    /// Corresponds to a rotation of 180° as seen from the face to which the
1304    /// point is _closest_ without being _on_.
1305    fn flip_1_and_3(self) -> Self {
1306        self.opposite_then_beside()
1307    }
1308
1309    /// Different name for [`CubeSurfacePoint::beside`]
1310    fn flip_sign_of_1(self) -> Self::OtherGroup {
1311        self.beside()
1312    }
1313
1314    /// Different name for [`CubeSurfacePoint::opposite`]
1315    fn flip_sign_of_3(self) -> Self::OtherGroup {
1316        self.opposite()
1317    }
1318
1319    /// Flips the sign of the coördinates whose absolute values are 1 and 2.
1320    ///
1321    /// Corresponds to a rotation of 180° as seen from the face on which the
1322    /// point is located.
1323    fn flip_1_and_2(self) -> Self {
1324        self.n_right_angles_cw(2)
1325    }
1326}
1327
1328/// Rotates a copy of `self` in a way that maintains its Geometric Group.
1329impl Mul<ProperRotation> for CubeSurfacePoint {
1330    /// The Geometric Group doesn't change. Even if it did, this data-type is
1331    /// group-agnostic.
1332    type Output = CubeSurfacePoint;
1333    fn mul(self, rotation: ProperRotation) -> Self {
1334        self * Into::<Rotation>::into(rotation)
1335    }
1336}
1337
1338/// Rotates `self` in a way that maintains its Geometric Group.
1339impl MulAssign<ProperRotation> for CubeSurfacePoint {
1340    /// Neither the Geometric Group nor the data-type change, so the result can
1341    /// be directly assigned.
1342    fn mul_assign(&mut self, x: ProperRotation) {
1343        *self = *self * x;
1344    }
1345}
1346
1347/// Rotates a copy of `self` in a way that switches its Geometric Group.
1348impl Mul<ImproperRotation> for CubeSurfacePoint {
1349    /// Although the Geometric Group does change, `CubeSurfacePoint` does not
1350    /// change its data-type depending on Geometric Group. Thus the data-type
1351    /// remains the same.
1352    type Output = CubeSurfacePoint;
1353    fn mul(self, rotation: ImproperRotation) -> Self {
1354        self * Into::<Rotation>::into(rotation)
1355    }
1356}
1357
1358/// Rotates `self` in a way that switches its Geometric Group.
1359impl MulAssign<ImproperRotation> for CubeSurfacePoint {
1360    /// The data-type remains the same, despite the Geometric Group changing.
1361    /// Thus, the result can be directly assigned.
1362    fn mul_assign(&mut self, x: ImproperRotation) {
1363        *self = *self * x;
1364    }
1365}
1366
1367macro_rules! implement_trivial {
1368    ($fn: ident, $out: ty $(, $angle: ident)?) => {
1369        fn $fn(self $(, $angle:u8)?) -> $out {
1370            self.$fn($($angle)?)
1371        }
1372    };
1373
1374    (everything; $us: ty, $others: ty) => {
1375
1376        impl OctahedrallySymmetricPoint for $us {
1377            type OtherGroup = $others;
1378
1379            implement_trivial!(beside, Self::OtherGroup);
1380            implement_trivial!(opposite, Self::OtherGroup);
1381            implement_trivial!(opposite_then_beside, Self);
1382            implement_trivial!(one_right_angle_cw, Self);
1383            implement_trivial!(one_right_angle_acw, Self);
1384            implement_trivial!(flip_sign_of_2, Self::OtherGroup);
1385            implement_trivial!(flip_2_and_3, Self);
1386            implement_trivial!(direction, Direction);
1387            implement_trivial!(n_right_angles_cw, Self, angle);
1388            implement_trivial!(n_right_angles_acw, Self, angle);
1389        }
1390    }
1391}
1392
1393implement_trivial!(everything; CubeSurfacePoint, CubeSurfacePoint);
1394implement_trivial!(
1395    everything;
1396    luts::CubeSurfacePoint<true>,
1397    luts::CubeSurfacePoint<true>
1398);
1399implement_trivial!(
1400    everything;
1401    luts::CubeSurfacePoint<false>,
1402    luts::CubeSurfacePoint<false>
1403);
1404implement_trivial!(
1405    everything;
1406    luts::ReferenceGroupPoint,
1407    luts::OppositeGroupPoint
1408);
1409implement_trivial!(
1410    everything;
1411    luts::OppositeGroupPoint,
1412    luts::ReferenceGroupPoint
1413);
1414
1415/// The set of `CubeSurfacePoint`s that can be made to coincide with the
1416/// Reference Point using just one *proper* rotation.
1417///
1418/// Not coincidentally, their
1419/// binary representations all have an even number of ones.
1420#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)]
1421#[repr(u8)]
1422pub enum ReferenceGroupPoint {
1423    /// The point `[1, 2, 3]`. Also the Point of Reference. Divided by itself,
1424    /// it yields –unsurprisingly enough– the identity operation.
1425    PosOnePosTwoPosThree = 00,
1426    /// The point `[1, -2, -3]`. Divided by the Reference Point, it yields a
1427    /// rotation of 180° around the `x` axis.
1428    PosOneNegTwoNegThree = 03,
1429    /// The point `[-1, 2, -3]`. Divided by the Reference Point, it yields a
1430    /// rotation of 180° around the `y` axis.
1431    NegOnePosTwoNegThree = 05,
1432    /// The point `[-1, -2, 3]`. Divided by the Reference Point, it yields a
1433    /// rotation of 180° around the `z` axis.
1434    NegOneNegTwoPosThree = 06,
1435    /// The point `[3, 2, -1]`. Divided by the Reference Point, it yields a
1436    /// rotation of 90° around the `y` axis.
1437    PosThreePosTwoNegOne = 09,
1438    /// The point `[3, -2, 1]`. Divided by the Reference Point, it yields a
1439    /// rotation of 180° around the `y = 0, z = x` axis.
1440    PosThreeNegTwoPosOne = 10,
1441    /// The point `[-3, 2, 1]`. Divided by the Reference Point, it yields a
1442    /// rotation of -90° around the `y` axis.
1443    NegThreePosTwoPosOne = 12,
1444    /// The point `[-3, -2, -1]`. Divided by the Reference Point, it yields a
1445    /// rotation of 180° around the `y = 0, z = -x` axis.
1446    NegThreeNegTwoNegOne = 15,
1447    /// The point `[1, 3, -2]`. Divided by the Reference Point, it yields a
1448    /// rotation of -90° around the `x` axis.
1449    PosOnePosThreeNegTwo = 17,
1450    /// The point `[1, -3, 2]`. Divided by the Reference Point, it yields a
1451    /// rotation of 90° around the `x` axis.
1452    PosOneNegThreePosTwo = 18,
1453    /// The point `[-1, 3, 2]`. Divided by the Reference Point, it yields a
1454    /// rotation of 180° around the `x = 0, y = z` axis.
1455    NegOnePosThreePosTwo = 20,
1456    /// The point `[-1, -3, -2]`. Divided by the Reference Point, it yields a
1457    /// rotation of 180° around the `x = 0, y = -z` axis.
1458    NegOneNegThreeNegTwo = 23,
1459    /// The point `[3, 1, 2]`. Divided by the Reference Point, it yields a
1460    /// rotation of 120° around the `x = y = z` axis.
1461    PosThreePosOnePosTwo = 24,
1462    /// The point `[3, -1, -2]`. Divided by the Reference Point, it yields a
1463    /// rotation of -120° around the `x = -y = z` axis.
1464    PosThreeNegOneNegTwo = 27,
1465    /// The point `[-3, 1, -2]`. Divided by the Reference Point, it yields a
1466    /// rotation of -120° around the `x = y = -z` axis.
1467    NegThreePosOneNegTwo = 29,
1468    /// The point `[-3, -1, 2]`. Divided by the Reference Point, it yields a
1469    /// rotation of 120° around the `x = -y = -z` axis.
1470    NegThreeNegOnePosTwo = 30,
1471    /// The point `[2, 1, -3]`. Divided by the Reference Point, it yields a
1472    /// rotation of 180° around the `z = 0, x = y` axis.
1473    PosTwoPosOneNegThree = 33,
1474    /// The point `[2, -1, 3]`. Divided by the Reference Point, it yields a
1475    /// rotation of -90° around the `z` axis.
1476    PosTwoNegOnePosThree = 34,
1477    /// The point `[-2, 1, 3]`. Divided by the Reference Point, it yields a
1478    /// rotation of 90° around the `z` axis.
1479    NegTwoPosOnePosThree = 36,
1480    /// The point `[-2, -1, -3]`. Divided by the Reference Point, it yields a
1481    /// rotation of 180° around the `z = 0, x = -y` axis.
1482    NegTwoNegOneNegThree = 39,
1483    /// The point `[2, 3, 1]`. Divided by the Reference Point, it yields a
1484    /// rotation of -120° around the `x = y = z` axis.
1485    PosTwoPosThreePosOne = 40,
1486    /// The point `[2, -3, -1]`. Divided by the Reference Point, it yields a
1487    /// rotation of 120° around the `x = y = -z` axis.
1488    PosTwoNegThreeNegOne = 43,
1489    /// The point `[-2, 3, -1]`. Divided by the Reference Point, it yields a
1490    /// rotation of -120° around the `x = -y = -z` axis.
1491    NegTwoPosThreeNegOne = 45,
1492    /// The point `[-2, -3, 1]`. Divided by the Reference Point, it yields a
1493    /// rotation of 120° around the `x = -y = z` axis.
1494    NegTwoNegThreePosOne = 46,
1495}
1496
1497/// The set of `CubeSurfacePoint`s that can be made to coincide with the
1498/// Reference Point using just one *improper* rotation.
1499///
1500/// Not coincidentally, their
1501/// binary representations all have an odd number of ones.
1502#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)]
1503#[repr(u8)]
1504pub enum OppositeGroupPoint {
1505    /// The point `[1, 2, -3]`. Divided by the Reference Point, it yields a
1506    /// reflection through the `z = 0` plane. Its arithmetic representation is
1507    /// equal to 2<sup>0</sup>, thus it is the zeroth Elementary Reflection.
1508    PosOnePosTwoNegThree = 01,
1509    /// The point `[1, -2, 3]`. Divided by the Reference Point, it yields a
1510    /// reflection through the `y = 0` plane. Its arithmetic representation is
1511    /// equal to 2<sup>1</sup>, thus it is the first Elementary Reflection.
1512    PosOneNegTwoPosThree = 02,
1513    /// The point `[-1, 2, 3]`. Divided by the Reference Point, it yields a
1514    /// reflection through the `x = 0` plane. Its arithmetic representation is
1515    /// equal to 2<sup>2</sup>, thus it is the second Elementary Reflection.
1516    NegOnePosTwoPosThree = 04,
1517    /// The point `[-1, -2, -3]`. Divided by the Reference Point, it yields a
1518    /// complete central inversion, ie a negation of all coördinates.
1519    NegOneNegTwoNegThree = 07,
1520    /// The point `[3, 2, 1]`. Divided by the Reference Point, it yields a
1521    /// reflection through the `x = z` plane. Its arithmetic representation is
1522    /// equal to 2<sup>3</sup>, thus it is the third Elementary Reflection.
1523    PosThreePosTwoPosOne = 08,
1524    /// The point `[3, -2, -1]`. Divided by the Reference Point, it yields a
1525    /// rotoreflection of 90° with respect to the `y` axis.
1526    PosThreeNegTwoNegOne = 11,
1527    /// The point `[-3, 2, -1]`. Divided by the Reference Point, it yields a
1528    /// reflection through the `z = -x` plane.
1529    NegThreePosTwoNegOne = 13,
1530    /// The point `[-3, -2, 1]`. Divided by the Reference Point, it yields a
1531    /// rotoreflection of -90° with respect to the `y` axis.
1532    NegThreeNegTwoPosOne = 14,
1533    /// The point `[1, 3, 2]`. Divided by the Reference Point, it yields a
1534    /// reflection through the `z = y` plane. Its arithmetic representation is
1535    /// equal to 2<sup>4</sup>, thus it is the fourth Elementary Reflection.
1536    PosOnePosThreePosTwo = 16,
1537    /// The point `[1, -3, -2]`. Divided by the Reference Point, it yields a
1538    /// reflection through the `y = -z` plane.
1539    PosOneNegThreeNegTwo = 19,
1540    /// The point `[-1, 3, -2]`. Divided by the Reference Point, it yields a
1541    /// rotoreflection of -90° with respect to the `x = y = z` axis.
1542    NegOnePosThreeNegTwo = 21,
1543    /// The point `[-1, -3, 2]`. Divided by the Reference Point, it yields a
1544    /// rotoreflection of 90° with respect to the `x` axis.
1545    NegOneNegThreePosTwo = 22,
1546    /// The point `[3, 1, -2]`. Divided by the Reference Point, it yields a
1547    /// rotoreflection of -60° with respect to the `x = -y = -z` axis.
1548    PosThreePosOneNegTwo = 25,
1549    /// The point `[3, -1, 2]`. Divided by the Reference Point, it yields a
1550    /// rotoreflection of 60° with respect to the `x = y = -z` axis.
1551    PosThreeNegOnePosTwo = 26,
1552    /// The point `[-3, 1, 2]`. Divided by the Reference Point, it yields a
1553    /// rotoreflection of 60° with respect to the `x = -y = z` axis.
1554    NegThreePosOnePosTwo = 28,
1555    /// The point `[-3, -1, -2]`. Divided by the Reference Point, it yields a
1556    /// rotoreflection of -60° with respect to the `x = y = z` axis.
1557    NegThreeNegOneNegTwo = 31,
1558    /// The point `[2, 1, 3]`. Divided by the Reference Point, it yields a
1559    /// reflection through the `y = x` plane. Its arithmetic representation is
1560    /// equal to 2<sup>5</sup>, thus it is the fifth and final
1561    /// Elementary Reflection.
1562    PosTwoPosOnePosThree = 32,
1563    /// The point `[2, -1, -3]`. Divided by the Reference Point, it yields a
1564    /// reflection through the `x = -y` plane.
1565    PosTwoNegOneNegThree = 35,
1566    /// The point `[-2, 1, -3]`. Divided by the Reference Point, it yields a
1567    /// rotoreflection of 90° with respect to the `z` axis.
1568    NegTwoPosOneNegThree = 37,
1569    /// The point `[-2, -1, 3]`. Divided by the Reference Point, it yields a
1570    /// rotoreflection of -90° with respect to the `z` axis.
1571    NegTwoNegOnePosThree = 38,
1572    /// The point `[2, 3, -1]`. Divided by the Reference Point, it yields a
1573    /// rotoreflection of -60° with respect to the `x = -y = z` axis.
1574    PosTwoPosThreeNegOne = 41,
1575    /// The point `[2, -3, 1]`. Divided by the Reference Point, it yields a
1576    /// rotoreflection of 60° with respect to the `x = -y = -z` axis.
1577    PosTwoNegThreePosOne = 42,
1578    /// The point `[-2, 3, 1]`. Divided by the Reference Point, it yields a
1579    /// rotoreflection of -60° with respect to the `x = y = -z` axis.
1580    NegTwoPosThreePosOne = 44,
1581    /// The point `[-2, -3, -1]`. Divided by the Reference Point, it yields a
1582    /// rotoreflection of 60° with respect to the `x = y = z` axis.
1583    NegTwoNegThreeNegOne = 47,
1584}
1585
1586impl ReferenceGroupPoint {
1587    const fn to_u8(self) -> u8 {
1588        self as u8
1589    }
1590}
1591
1592impl OppositeGroupPoint {
1593    const fn to_u8(self) -> u8 {
1594        self as u8
1595    }
1596}
1597
1598macro_rules! implement {
1599    ($fnam: ident, $out: ty, $check: ident $(, $angle: ident)? $(; $const:tt)?) => {
1600        #[doc = concat!(
1601            "Please refer to the [function of the same name
1602            ](CubeSurfacePoint::",
1603            stringify!($fnam),
1604            ") in [`CubeSurfacePoint`]."
1605        )]
1606        $(pub $const)? fn $fnam(self $(, $angle:u8)?) -> $out {
1607            let Ok(result) = self.downcast().$fnam($($angle)?).$check() else {
1608                unreachable_semichecked()
1609            };
1610            result
1611        }
1612    };
1613
1614    (mul; $rot: ty, $us: ty, $out: ty, $check: ident, $fnam:ident $(; $const:tt)?) => {
1615
1616
1617        #[doc = concat!(
1618            "Multiplies one [`",
1619                        stringify!($us),
1620                        "`] with one [`",
1621                        stringify!($rot),
1622                        "`], producing one [`",
1623                        stringify!($out),
1624                        "`] as a result. "
1625        )]
1626        /// Useful for static confirmation of Geometric Groups.
1627        $(pub $const)? fn $fnam(self, other: $rot) -> $out {
1628            let a: CubeSurfacePoint = self.downcast();
1629            let b: Rotation = Rotation {
1630                corresponding_point: other.corresponding_point.downcast()
1631            };
1632            let Ok(result) = a.mul(b).$check() else {
1633                unreachable_semichecked()
1634            };
1635            result
1636        }
1637    };
1638
1639    (div; $us: ty, $other: ty, $out: ty, $check: ident, $fnam: ident $(; $const:tt)?) =>
1640    {
1641
1642        #[doc = concat!(
1643            "Divides one [`",
1644            stringify!($us),
1645                        "`] by one [`",
1646                        stringify!($other),
1647                        "`], producing one [`",
1648                        stringify!($out),
1649                        "`] as a result. "
1650        )]
1651        /// Useful for static confirmation of Geometric Group.
1652        $(pub $const)? fn $fnam(self, other: $other) -> $out {
1653            let a: CubeSurfacePoint = self.downcast();
1654            let b: CubeSurfacePoint = other.downcast();
1655            let Ok(result) = a.div(b).$check() else {
1656                unreachable_semichecked()
1657            };
1658            result
1659        }
1660
1661    };
1662
1663    (everything; $us: ty, $others: ty, $check: ident, $anticheck: ident) => {
1664        impl $us {
1665            #[doc = concat!("Down-casts one [`", stringify!($us), "`] to a [`CubeSurfacePoint`].")]
1666            pub const fn downcast(self) -> CubeSurfacePoint {
1667                CubeSurfacePoint::probs_from_u8(self.to_u8())
1668            }
1669
1670            implement!(beside, $others, $anticheck; const);
1671            implement!(opposite, $others, $anticheck; const);
1672            implement!(opposite_then_beside, Self, $check; const);
1673            implement!(one_right_angle_cw, Self, $check; const);
1674            implement!(one_right_angle_acw, Self, $check; const);
1675            implement!(flip_sign_of_2, $others, $anticheck; const);
1676            implement!(flip_2_and_3, Self, $check; const);
1677            implement!(n_right_angles_cw, Self, $check, angle; const);
1678            implement!(n_right_angles_acw, Self, $check, angle; const);
1679            implement!(mul; ProperRotation, $us, $us, $check, mul_prop; const);
1680            implement!(mul; ImproperRotation, $us, $others, $anticheck, mul_improp; const);
1681            implement!(div; $us, $us, ProperRotation, determine_group, div_prop; const);
1682            implement!(div; $us, $others, ImproperRotation, determine_antigroup, div_improp; const);
1683
1684            /// Please refer to
1685            /// [the function of the same name](CubeSurfacePoint::direction)
1686            /// in [`CubeSurfacePoint`].
1687            pub const fn direction(self) -> Direction {
1688                self.downcast().direction()
1689            }
1690        }
1691
1692        /// Rotates a copy of `self` in a way that maintains its Geometric Group.
1693        impl Mul<ProperRotation> for $us {
1694            /// Same Geometric Group, so same data-type.
1695            type Output = Self;
1696            implement!(mul; ProperRotation, $us, $us, $check, mul);
1697        }
1698
1699        /// Rotates `self` in a way that maintains its Geometric Group.
1700        impl MulAssign<ProperRotation> for $us {
1701            /// The data-type doesn't change, so the result can be directly
1702            /// assigned.
1703            fn mul_assign (&mut self, x: ProperRotation) {
1704                *self = *self * x;
1705            }
1706        }
1707
1708        /// Rotates a copy the argument in a way that maintains its Geometric
1709        /// Group.
1710        impl Mul<$us> for ProperRotation {
1711            /// Same Geometric Group, so same data-type.
1712            type Output = $us;
1713            fn mul(self, x: $us) -> Self::Output {
1714                x * self
1715            }
1716        }
1717
1718        /// Rotates a copy of `self` in a way that switches its Geometric Group.
1719        impl Mul<ImproperRotation> for $us {
1720            /// Output belongs to the other Geometric Group.
1721            type Output = $others;
1722            implement!(mul; ImproperRotation, $us, $others, $anticheck, mul);
1723        }
1724
1725        /// Rotates a copy the argument in a way that switches its Geometric
1726        /// Group.
1727        impl Mul<$us> for ImproperRotation {
1728            /// Output belongs to the other Geometric Group.
1729            type Output = $others;
1730            fn mul(self, x: $us) -> Self::Output {
1731                x * self
1732            }
1733        }
1734
1735        /// Extracts the proper rotation that must occur so that the `divisor`
1736        /// point ends up coinciding with `self`, ie the dividend.
1737        impl Div for $us {
1738            /// A proper rotation is enough for this operation.
1739            type Output = ProperRotation;
1740            implement!(div; Self, Self, ProperRotation, determine_group, div);
1741        }
1742
1743        /// Extracts the improper rotation that must occur so that the `divisor`
1744        /// point ends up coinciding with `self`, ie the dividend.
1745        impl Div<$others> for $us {
1746            /// This operation needs an improper rotation.
1747            type Output = ImproperRotation;
1748
1749            implement!(div; Self, $others, ImproperRotation, determine_antigroup, div);
1750        }
1751
1752        /// Discards any knowledge of Geometric Group, producing a general
1753        /// `CubeSurfacePoint`.
1754        impl From<$us> for CubeSurfacePoint {
1755            fn from(x: $us) -> Self {
1756                x.downcast()
1757            }
1758        }
1759
1760        /// Please refer to [`CubeSurfacePoint::determine_group`].
1761        impl TryFrom<CubeSurfacePoint> for $us {
1762            #[doc = concat!(
1763            "If a certain [`CubeSurfacePoint`] does not belong to the [`",
1764                            stringify!($us),
1765                            "`]s, it must by necessity belong to the [`",
1766                            stringify!($others),
1767                            "`]s."
1768            )]
1769            type Error = $others;
1770
1771            fn try_from(x: CubeSurfacePoint) -> Result<Self, Self::Error> {
1772                x.$check()
1773            }
1774        }
1775
1776        impl OctahedrallySymmetricPoint for $us {
1777            type OtherGroup = $others;
1778
1779            implement!(beside, Self::OtherGroup, $anticheck);
1780            implement!(opposite, Self::OtherGroup, $anticheck);
1781            implement!(flip_sign_of_2, Self::OtherGroup, $anticheck);
1782            implement!(opposite_then_beside, Self, $check);
1783            implement!(flip_2_and_3, Self, $check);
1784            implement!(one_right_angle_cw, Self, $check);
1785            implement!(one_right_angle_acw, Self, $check);
1786            implement!(n_right_angles_cw, Self, $check, angle);
1787            implement!(n_right_angles_acw, Self, $check, angle);
1788        }
1789
1790
1791    };
1792}
1793
1794implement!(everything; ReferenceGroupPoint, OppositeGroupPoint, determine_group, determine_antigroup);
1795implement!(everything; OppositeGroupPoint, ReferenceGroupPoint, determine_antigroup, determine_group);
1796
1797/// As per a [`Rotation`], but one that specifically needs no reflections.
1798#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)]
1799pub struct ProperRotation {
1800    corresponding_point: ReferenceGroupPoint,
1801}
1802
1803/// As per a [`Rotation`], but one that specifically needs at least one
1804/// reflection.
1805#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)]
1806pub struct ImproperRotation {
1807    corresponding_point: OppositeGroupPoint,
1808}
1809
1810impl Rotation {
1811    const fn determine_group(self) -> Result<ProperRotation, ImproperRotation> {
1812        let argh = self.corresponding_point.determine_group();
1813        match argh {
1814            Ok(corresponding_point) => Ok(ProperRotation {
1815                corresponding_point,
1816            }),
1817            Err(corresponding_point) => Err(ImproperRotation {
1818                corresponding_point,
1819            }),
1820        }
1821    }
1822
1823    const fn determine_antigroup(self) -> Result<ImproperRotation, ProperRotation> {
1824        match self.determine_group() {
1825            Ok(a) => Err(a),
1826            Err(a) => Ok(a),
1827        }
1828    }
1829}
1830
1831macro_rules! mul_div_rots {
1832    ($us: ty, $other: ty, $output: path) => {
1833        /// Implements multiplication between rotations, such that
1834        /// `rot_a * rot_b * point_x` can be computed as either
1835        /// `rot_a * (rot_b * point_x)` or `(rot_a * rot_b) * point_x`,
1836        /// with no change to the result computed.
1837        ///
1838        /// Important note: `Rotation`s are essentially highly simplified
1839        /// matrices. This means that, in the general case, commutativity _does
1840        /// not hold_, and therefore `rot_a * rot_b` and `rot_b * rot_a` are not
1841        /// necessarily equal.
1842        impl Mul<$other> for $us {
1843            /// The output type tells us all we know about the propriety of the
1844            /// result.
1845            type Output = $output;
1846
1847            /// Implemented by multiplying the second rotation's corresponding
1848            /// point with the first one.
1849            fn mul(self, other: $other) -> Self::Output {
1850                let corresponding_point = self * other.corresponding_point;
1851                $output {
1852                    corresponding_point,
1853                }
1854            }
1855        }
1856
1857        /// Implements division between rotations, such that
1858        /// `(rot_a * rot_b) / rot_b == rot_a`.
1859        ///
1860        /// Important note: `rot_a / rot_b` is not necessarily equal to
1861        /// `(1 / rot_b) * rot_a`. See also the relevant comment on `Mul`.
1862        impl Div<$other> for $us {
1863            /// The output type tells us all we know about the propriety of the
1864            /// result.
1865            type Output = $output;
1866
1867            /// Implemented by dividing the two corresponding points
1868            /// with one another.
1869            fn div(self, other: $other) -> Self::Output {
1870                self.corresponding_point / other.corresponding_point
1871            }
1872        }
1873    };
1874}
1875
1876mul_div_rots!(Rotation, Rotation, Rotation);
1877mul_div_rots!(ProperRotation, ProperRotation, ProperRotation);
1878mul_div_rots!(ImproperRotation, ImproperRotation, ProperRotation);
1879mul_div_rots!(ProperRotation, ImproperRotation, ImproperRotation);
1880mul_div_rots!(ImproperRotation, ProperRotation, ImproperRotation);
1881
1882/// Discards any notion of propriety, producing a general `Rotation`.
1883impl From<ProperRotation> for Rotation {
1884    fn from(x: ProperRotation) -> Self {
1885        let c_p = x.corresponding_point as u8;
1886        Self {
1887            corresponding_point: CubeSurfacePoint::probs_from_u8(c_p),
1888        }
1889    }
1890}
1891
1892/// Discards any notion of propriety, producing a general `Rotation`.
1893impl From<ImproperRotation> for Rotation {
1894    fn from(x: ImproperRotation) -> Self {
1895        let c_p = x.corresponding_point as u8;
1896        Self {
1897            corresponding_point: CubeSurfacePoint::probs_from_u8(c_p),
1898        }
1899    }
1900}
1901
1902/// Discriminates a `Rotation` based on impropriety.
1903impl TryFrom<Rotation> for ImproperRotation {
1904    /// If a [`Rotation`] is not improper, it must by necessity be proper.
1905    type Error = ProperRotation;
1906    fn try_from(x: Rotation) -> Result<Self, Self::Error> {
1907        x.determine_antigroup()
1908    }
1909}
1910
1911/// Discriminates a `Rotation` based on propriety.
1912impl TryFrom<Rotation> for ProperRotation {
1913    /// If a [`Rotation`] is not proper, it must by necessity be improper.
1914    type Error = ImproperRotation;
1915    fn try_from(x: Rotation) -> Result<Self, Self::Error> {
1916        x.determine_group()
1917    }
1918}
1919
1920impl From<Rotation> for CubeSurfacePoint {
1921    fn from(x: Rotation) -> Self {
1922        x.corresponding_point
1923    }
1924}
1925
1926impl From<ProperRotation> for ReferenceGroupPoint {
1927    fn from(x: ProperRotation) -> Self {
1928        x.corresponding_point
1929    }
1930}
1931
1932impl From<ImproperRotation> for OppositeGroupPoint {
1933    fn from(x: ImproperRotation) -> Self {
1934        x.corresponding_point
1935    }
1936}
1937
1938impl From<CubeSurfacePoint> for Rotation {
1939    fn from(corresponding_point: CubeSurfacePoint) -> Self {
1940        Self {
1941            corresponding_point,
1942        }
1943    }
1944}
1945
1946impl From<ReferenceGroupPoint> for ProperRotation {
1947    fn from(corresponding_point: ReferenceGroupPoint) -> Self {
1948        Self {
1949            corresponding_point,
1950        }
1951    }
1952}
1953
1954impl From<OppositeGroupPoint> for ImproperRotation {
1955    fn from(corresponding_point: OppositeGroupPoint) -> Self {
1956        Self {
1957            corresponding_point,
1958        }
1959    }
1960}
1961
1962#[cfg(test)]
1963mod tests;