use crate::block_state::BlockState;
use crate::transforms::{transform_block_state_flip, transform_block_state_rotate, Axis};
pub type Pos = (i32, i32, i32);
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum Step {
Rotate(Axis, i32), Flip(Axis),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum Symmetry {
None,
Yaw,
YawMirror,
Octahedral,
OctahedralFull,
}
impl Symmetry {
pub fn from_name(name: &str) -> Option<Symmetry> {
Some(match name {
"none" => Symmetry::None,
"yaw" => Symmetry::Yaw,
"yaw_mirror" => Symmetry::YawMirror,
"octahedral" => Symmetry::Octahedral,
"octahedral_full" => Symmetry::OctahedralFull,
_ => return None,
})
}
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct RigidOp {
steps: Vec<Step>,
}
fn rot_pos(p: Pos, axis: Axis, deg: i32) -> Pos {
let (x, y, z) = p;
let d = deg.rem_euclid(360);
match (axis, d) {
(Axis::Y, 90) => (z, y, -x),
(Axis::Y, 180) => (-x, y, -z),
(Axis::Y, 270) => (-z, y, x),
(Axis::X, 90) => (x, -z, y),
(Axis::X, 180) => (x, -y, -z),
(Axis::X, 270) => (x, z, -y),
(Axis::Z, 90) => (-y, x, z),
(Axis::Z, 180) => (-x, -y, z),
(Axis::Z, 270) => (y, -x, z),
_ => (x, y, z),
}
}
fn flip_pos(p: Pos, axis: Axis) -> Pos {
let (x, y, z) = p;
match axis {
Axis::X => (-x, y, z),
Axis::Y => (x, -y, z),
Axis::Z => (x, y, -z),
}
}
impl RigidOp {
pub fn identity() -> RigidOp {
RigidOp { steps: vec![] }
}
pub fn apply_pos(&self, p: Pos) -> Pos {
self.steps.iter().fold(p, |acc, s| match *s {
Step::Rotate(a, d) => rot_pos(acc, a, d),
Step::Flip(a) => flip_pos(acc, a),
})
}
pub fn apply_block(&self, b: &BlockState) -> BlockState {
self.steps.iter().fold(b.clone(), |acc, s| match *s {
Step::Rotate(a, d) => transform_block_state_rotate(&acc, a, d),
Step::Flip(a) => transform_block_state_flip(&acc, a),
})
}
}
impl Symmetry {
pub fn elements(&self) -> Vec<RigidOp> {
match self {
Symmetry::None => vec![RigidOp { steps: vec![] }],
Symmetry::Yaw => yaw_ops(),
Symmetry::YawMirror => {
let mut v = yaw_ops();
let mirrored: Vec<RigidOp> = yaw_ops()
.into_iter()
.map(|mut op| {
op.steps.push(Step::Flip(Axis::X));
op
})
.collect();
v.extend(mirrored);
v
}
Symmetry::Octahedral => octahedral_ops(false),
Symmetry::OctahedralFull => octahedral_ops(true),
}
}
}
fn yaw_ops() -> Vec<RigidOp> {
[0, 90, 180, 270]
.into_iter()
.map(|d| RigidOp {
steps: if d == 0 {
vec![]
} else {
vec![Step::Rotate(Axis::Y, d)]
},
})
.collect()
}
fn octahedral_ops(with_reflections: bool) -> Vec<RigidOp> {
let basis = [(1, 0, 0), (0, 1, 0), (0, 0, 1)];
let key = |op: &RigidOp| -> [Pos; 3] {
[
op.apply_pos(basis[0]),
op.apply_pos(basis[1]),
op.apply_pos(basis[2]),
]
};
let mut generators = vec![
RigidOp {
steps: vec![Step::Rotate(Axis::X, 90)],
},
RigidOp {
steps: vec![Step::Rotate(Axis::Y, 90)],
},
];
if with_reflections {
generators.push(RigidOp {
steps: vec![Step::Flip(Axis::X)],
});
}
let identity = RigidOp { steps: vec![] };
let mut seen = std::collections::BTreeMap::new();
seen.insert(key(&identity), identity.clone());
let mut frontier = vec![identity];
while let Some(cur) = frontier.pop() {
for g in &generators {
let mut steps = cur.steps.clone();
steps.extend(g.steps.iter().copied());
let next = RigidOp { steps };
let k = key(&next);
if let std::collections::btree_map::Entry::Vacant(e) = seen.entry(k) {
e.insert(next.clone());
frontier.push(next);
}
}
}
seen.into_values().collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn yaw_rotations_of_a_point() {
let ops = Symmetry::Yaw.elements();
assert_eq!(ops.len(), 4);
let p = (1, 0, 0);
let mut seen: Vec<Pos> = ops.iter().map(|g| g.apply_pos(p)).collect();
assert!(seen.iter().all(|q| q.1 == 0), "Y is fixed under yaw");
seen.sort();
seen.dedup();
assert_eq!(seen.len(), 4, "four distinct images");
}
#[test]
fn group_sizes() {
assert_eq!(Symmetry::None.elements().len(), 1);
assert_eq!(Symmetry::Yaw.elements().len(), 4);
assert_eq!(Symmetry::YawMirror.elements().len(), 8);
assert_eq!(Symmetry::Octahedral.elements().len(), 24);
assert_eq!(Symmetry::OctahedralFull.elements().len(), 48);
}
#[test]
fn rotating_a_directional_block_rotates_its_facing() {
let north = BlockState::new("minecraft:repeater")
.with_properties(vec![("facing".into(), "north".into())]);
let yaw90 = &Symmetry::Yaw.elements()[1];
let once = yaw90.apply_block(&north);
assert_ne!(once.get_property("facing"), north.get_property("facing"));
let four = (0..4).fold(north.clone(), |b, _| yaw90.apply_block(&b));
assert_eq!(four.get_property("facing"), north.get_property("facing"));
}
#[test]
fn rigidop_round_trips_via_serde() {
let op = Symmetry::YawMirror.elements().into_iter().nth(2).unwrap();
let j = serde_json::to_string(&op).unwrap();
let back: RigidOp = serde_json::from_str(&j).unwrap();
assert_eq!(format!("{:?}", op), format!("{:?}", back));
}
#[test]
fn symmetry_from_name() {
assert_eq!(Symmetry::from_name("none"), Some(Symmetry::None));
assert_eq!(Symmetry::from_name("yaw_mirror"), Some(Symmetry::YawMirror));
assert_eq!(
Symmetry::from_name("octahedral"),
Some(Symmetry::Octahedral)
);
assert_eq!(Symmetry::from_name("bogus"), None);
}
}