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;