use crate::coordinate_systems::{IJ, KJ};
use crate::lattice::basis::kj_to_ij;
use crate::lattice::quaternary::{ij_to_quaternary, quaternary_to_flips, quaternary_to_kj, ZERO};
use crate::lattice::shift_digits::{
shift_digits, PATTERN, PATTERN_FLIPPED, PATTERN_FLIPPED_REVERSED, PATTERN_REVERSED,
};
use crate::lattice::types::{Anchor, Flip, Orientation, Quaternary, NO, YES};
const FLIP_SHIFT: IJ = IJ(crate::coordinate_systems::vec2::Vec2 { x: -1.0, y: 1.0 });
pub fn s_to_anchor(s: u64, resolution: usize, orientation: Orientation) -> Anchor {
let input = s;
let reverse = matches!(
orientation,
Orientation::VU | Orientation::WU | Orientation::VW
);
let invert_j = matches!(orientation, Orientation::WV | Orientation::VW);
let flip_ij = matches!(orientation, Orientation::WU | Orientation::UW);
let adjusted_input = if reverse {
(1u64 << (2 * resolution)) - input - 1
} else {
input
};
let mut anchor = s_to_anchor_internal(adjusted_input, resolution, invert_j, flip_ij);
if flip_ij {
let i = anchor.offset.x();
let j = anchor.offset.y();
anchor.offset = IJ::new(j, i);
if anchor.flips[0] == YES {
anchor.offset = IJ::new(
anchor.offset.x() + FLIP_SHIFT.x(),
anchor.offset.y() + FLIP_SHIFT.y(),
);
}
if anchor.flips[1] == YES {
anchor.offset = IJ::new(
anchor.offset.x() - FLIP_SHIFT.x(),
anchor.offset.y() - FLIP_SHIFT.y(),
);
}
}
if invert_j {
let i = anchor.offset.x();
let j = anchor.offset.y();
let new_j = (1 << resolution) as f64 - (i + j);
anchor.flips[0] = -anchor.flips[0];
anchor.offset = IJ::new(i, new_j);
}
anchor
}
pub fn s_to_anchor_internal(s: u64, resolution: usize, invert_j: bool, flip_ij: bool) -> Anchor {
let mut offset = ZERO;
let mut flips = [NO, NO];
let mut input = s;
let mut digits = Vec::new();
while input > 0 || digits.len() < resolution {
digits.push((input % 4) as Quaternary);
input >>= 2;
}
let pattern = if flip_ij { &PATTERN_FLIPPED } else { &PATTERN };
for i in (0..digits.len()).rev() {
shift_digits(&mut digits, i, flips, invert_j, pattern);
let next_flips = quaternary_to_flips(digits[i]);
flips[0] *= next_flips[0];
flips[1] *= next_flips[1];
}
flips = [NO, NO]; for i in (0..digits.len()).rev() {
offset = KJ::new(offset.x() * 2.0, offset.y() * 2.0);
let child_offset = quaternary_to_kj(digits[i], flips);
offset = KJ::new(offset.x() + child_offset.x(), offset.y() + child_offset.y());
let next_flips = quaternary_to_flips(digits[i]);
flips[0] *= next_flips[0];
flips[1] *= next_flips[1];
}
let q = digits.first().copied().unwrap_or(0);
Anchor {
flips,
q,
offset: kj_to_ij(offset),
}
}
pub fn get_required_digits(offset: IJ) -> usize {
let index_sum = offset.x().ceil() + offset.y().ceil();
if index_sum == 0.0 {
return 1;
}
1 + (index_sum.log2().floor() as usize)
}
pub fn ij_to_s(input: IJ, resolution: usize, orientation: Orientation) -> u64 {
let reverse = matches!(
orientation,
Orientation::VU | Orientation::WU | Orientation::VW
);
let invert_j = matches!(orientation, Orientation::WV | Orientation::VW);
let flip_ij = matches!(orientation, Orientation::WU | Orientation::UW);
let mut ij = input;
if flip_ij {
ij = IJ::new(input.y(), input.x());
}
if invert_j {
let i = ij.x();
let j = ij.y();
ij = IJ::new(i, (1 << resolution) as f64 - (i + j));
}
let s = ij_to_s_internal(ij, invert_j, flip_ij, resolution);
if reverse {
(1u64 << (2 * resolution)) - s - 1
} else {
s
}
}
pub fn ij_to_s_internal(input: IJ, invert_j: bool, flip_ij: bool, resolution: usize) -> u64 {
let num_digits = resolution;
let mut digits = vec![0u8; num_digits];
let mut flips = [NO, NO];
let mut pivot = IJ::new(0.0, 0.0);
for i in (0..num_digits).rev() {
let relative_offset = IJ::new(input.x() - pivot.x(), input.y() - pivot.y());
let scale = 1.0 / (1u64 << i) as f64;
let scaled_offset = IJ::new(relative_offset.x() * scale, relative_offset.y() * scale);
let digit = ij_to_quaternary(scaled_offset, flips);
digits[i] = digit;
let child_offset = kj_to_ij(quaternary_to_kj(digit, flips));
let upscaled_child_offset = IJ::new(
child_offset.x() * (1u64 << i) as f64,
child_offset.y() * (1u64 << i) as f64,
);
pivot = IJ::new(
pivot.x() + upscaled_child_offset.x(),
pivot.y() + upscaled_child_offset.y(),
);
let next_flips = quaternary_to_flips(digit);
flips[0] *= next_flips[0];
flips[1] *= next_flips[1];
}
let pattern: &[usize] = if flip_ij {
&PATTERN_FLIPPED_REVERSED
} else {
&PATTERN_REVERSED
};
for i in 0..digits.len() {
let next_flips = quaternary_to_flips(digits[i]);
flips[0] *= next_flips[0];
flips[1] *= next_flips[1];
shift_digits(&mut digits, i, flips, invert_j, pattern);
}
let mut output = 0u64;
for (i, &digit) in digits.iter().enumerate().rev() {
let scale = 1u64 << (2 * i);
output += (digit as u64) * scale;
}
output
}
pub fn ij_to_flips(input: IJ, resolution: usize) -> [Flip; 2] {
let num_digits = resolution;
let mut flips = [NO, NO];
let mut pivot = IJ::new(0.0, 0.0);
for i in (0..num_digits).rev() {
let relative_offset = IJ::new(input.x() - pivot.x(), input.y() - pivot.y());
let scale = 1.0 / (1u64 << i) as f64;
let scaled_offset = IJ::new(relative_offset.x() * scale, relative_offset.y() * scale);
let digit = ij_to_quaternary(scaled_offset, flips);
let child_offset = kj_to_ij(quaternary_to_kj(digit, flips));
let upscaled_child_offset = IJ::new(
child_offset.x() * (1u64 << i) as f64,
child_offset.y() * (1u64 << i) as f64,
);
pivot = IJ::new(
pivot.x() + upscaled_child_offset.x(),
pivot.y() + upscaled_child_offset.y(),
);
let next_flips = quaternary_to_flips(digit);
flips[0] *= next_flips[0];
flips[1] *= next_flips[1];
}
flips
}
const PROBE_R: f64 = 0.1;
lazy_static::lazy_static! {
static ref PROBE_OFFSETS: [(f64, f64); 4] = {
let angles = [45.0_f64, 113.0, 293.0, 225.0];
let mut offsets = [(0.0, 0.0); 4];
for (i, &angle_deg) in angles.iter().enumerate() {
let angle_rad = angle_deg * std::f64::consts::PI / 180.0;
offsets[i] = (PROBE_R * angle_rad.cos(), PROBE_R * angle_rad.sin());
}
offsets
};
}
pub fn anchor_to_s(anchor: &Anchor, resolution: usize, orientation: Orientation) -> u64 {
let i = anchor.offset.x();
let j = anchor.offset.y();
let probe_idx = ((1 - anchor.flips[0]) + (1 - anchor.flips[1]) / 2) as usize;
let probe_offset = PROBE_OFFSETS[probe_idx];
ij_to_s(
IJ::new(i + probe_offset.0, j + probe_offset.1),
resolution,
orientation,
)
}