use std::fmt::{Debug, Display};
use num_complex::Complex;
use num_traits::ToPrimitive;
use num_traits::Zero;
use crate::angles::{normalize_angle, upscale_angles};
use crate::grid::UnitSquareGrid;
use crate::zz::{HasZZ12, HasZZ4, HasZZ6};
use crate::zzbase::{ZZBase, ZZNum};
use crate::zzgeom::{intersect, wedge};
pub struct Turtle<T: ZZNum> {
pub pos: T,
pub dir: i8,
}
impl<T: ZZNum> Turtle<T> {
pub fn new(pos: T, dir: i8) -> Self {
Self { pos, dir }
}
}
impl<T: ZZNum> Default for Turtle<T> {
fn default() -> Self {
Turtle {
pos: T::zero(),
dir: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct Snake<T: ZZNum> {
angles: Vec<i8>,
ang_sum: i64,
points: Vec<T>,
grid: UnitSquareGrid,
allow_intersections: bool,
}
impl<I: ToPrimitive, T: ZZNum> TryFrom<&[I]> for Snake<T> {
type Error = &'static str;
fn try_from(angles: &[I]) -> Result<Self, Self::Error> {
let mut result = Self::new();
match result.extend_from_slice(angles) {
Ok(()) => Ok(result),
Err(_) => Err("Self-intersecting angle sequence!"),
}
}
}
impl<const N: usize, I: ToPrimitive, T: ZZNum> TryFrom<&[I; N]> for Snake<T> {
type Error = &'static str;
fn try_from(angles: &[I; N]) -> Result<Self, Self::Error> {
Self::try_from(angles.as_slice())
}
}
impl<T: ZZNum> Snake<T> {
pub fn new() -> Self {
let mut grid = UnitSquareGrid::new();
grid.add((0, 0), 0);
Self {
points: vec![T::zero(); 1],
angles: Vec::new(),
ang_sum: 0,
grid: grid,
allow_intersections: false,
}
}
pub fn new_unchecked() -> Self {
Self {
allow_intersections: true,
..Self::new()
}
}
pub fn from_slice_unchecked<I: ToPrimitive>(angles: &[I]) -> Self {
let mut result = Self::new_unchecked();
result.extend_from_slice(angles).unwrap(); result
}
pub fn is_unchecked(&self) -> bool {
self.allow_intersections
}
pub fn len(&self) -> usize {
self.angles.len()
}
pub fn is_empty(&self) -> bool {
self.angles.is_empty()
}
pub fn angles(&self) -> &[i8] {
self.angles.as_slice()
}
pub fn angle_sum(&self) -> i64 {
self.ang_sum
}
pub fn representative(&self) -> &[T] {
self.points.as_slice()
}
pub fn is_closed(&self) -> bool {
!self.is_empty() && *self.points.last().unwrap() == T::zero()
}
pub fn offset(&self) -> T {
*self.points.last().unwrap()
}
pub fn direction(&self) -> i8 {
(self.ang_sum % (T::turn() as i64)) as i8
}
pub fn head(&self) -> Turtle<T> {
Turtle {
pos: self.offset(),
dir: self.direction(),
}
}
fn next_seg(&self, angle: i8) -> (T, T) {
let old_direction = self.direction();
let last = *self.points.last().unwrap();
let new_pt = last + (T::unit(old_direction) * T::unit(angle));
(last, new_pt)
}
fn add_unsafe(&mut self, angle: i8) {
let (_, new_pt) = self.next_seg(angle);
self.angles.push(angle);
self.ang_sum += angle as i64;
self.grid
.add(UnitSquareGrid::cell_of(new_pt), self.points.len());
self.points.push(new_pt);
}
pub fn to_polyline(&self, turtle: Turtle<T>) -> Vec<T> {
let mut result = Self::new();
*result.points.last_mut().unwrap() = turtle.pos;
result.ang_sum = turtle.dir as i64;
for angle in self.angles.iter() {
result.add_unsafe(*angle);
}
result.points
}
pub fn to_polyline_f64(&self, turtle: Turtle<T>) -> Vec<(f64, f64)> {
self.to_polyline(turtle)
.iter()
.map(|p| {
let Complex { re: x, im: y } = p.complex64();
(x, y)
})
.collect()
}
fn cell_segs(&self, cells: &[(i64, i64)]) -> Vec<(T, T)> {
let mut seg_pt_indices: Vec<(usize, usize)> = Vec::new();
for pt_idx in self.grid.get_cells(cells) {
if pt_idx != 0 {
seg_pt_indices.push((pt_idx - 1, pt_idx));
}
if pt_idx != self.len() {
seg_pt_indices.push((pt_idx, pt_idx + 1));
}
}
seg_pt_indices.sort();
seg_pt_indices.dedup();
seg_pt_indices
.iter()
.map(|(ix1, ix2)| (self.points[*ix1], self.points[*ix2]))
.collect()
}
fn can_add(&self, angle: i8) -> bool {
if self.allow_intersections {
return true; }
if self.is_closed() {
return false; }
let new_seg @ (prev_pt, new_pt) = self.next_seg(angle);
let neighborhood = UnitSquareGrid::seg_neighborhood_of(prev_pt, new_pt);
let neighbor_segs = self.cell_segs(&neighborhood);
let new_pt_nz = !new_pt.is_zero();
for s @ (x, y) in neighbor_segs {
if new_pt_nz && (new_pt == x || new_pt == y) {
return false;
}
if intersect(&new_seg, &s) {
return false;
}
}
return true;
}
pub fn add(&mut self, angle: i8) -> bool {
let a = normalize_angle::<T>(angle);
assert!(a.abs() != T::hturn());
if !self.can_add(a) {
return false;
}
self.add_unsafe(a);
if self.is_closed() {
let target = (T::turn() as i64) * self.ang_sum.signum();
let missing = target - (self.ang_sum - self.angles[0] as i64);
self.angles[0] = missing as i8;
self.ang_sum = target;
}
return true;
}
pub fn extend_from_slice<I: ToPrimitive>(&mut self, angles: &[I]) -> Result<(), &str> {
for angle in angles {
if !self.add(angle.to_i8().unwrap()) {
return Err("Cannot extend, self-intersecting sequence!");
}
}
Ok(())
}
pub fn extend(&mut self, other: &Snake<T>) -> Result<(), &str> {
self.extend_from_slice(other.angles.as_slice())
}
pub fn concat(&self, other: &Snake<T>) -> Result<Self, &str> {
let errmsg = "Cannot concatenate, self-intersecting sequence!";
let mut result = Self::new();
match result.extend(self) {
Ok(()) => {}
Err(_) => return Err(errmsg),
}
match result.extend(other) {
Ok(()) => {}
Err(_) => return Err(errmsg),
}
return Ok(result);
}
pub fn double_area(&self) -> T::Real {
let mut result = <<T as ZZBase>::Real as Zero>::zero();
for i in 1..self.len() {
result = result + wedge(&self.points[i - 1], &self.points[i]);
}
if !self.is_closed() {
result = result + wedge(&self.points[self.len() - 1], &self.points[0]);
}
return result;
}
pub fn is_point_inside(&self) -> bool {
if !self.is_closed() {
return false;
}
return true;
}
}
impl<T: ZZNum> PartialEq for Snake<T> {
fn eq(&self, other: &Self) -> bool {
self.angles == other.angles
}
}
impl<T: ZZNum> Eq for Snake<T> {}
impl<T: ZZNum> PartialOrd for Snake<T> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.angles.partial_cmp(&other.angles)
}
}
impl<T: ZZNum> Ord for Snake<T> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.angles.cmp(&other.angles)
}
}
impl<T: ZZNum> Display for Snake<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.angles.fmt(f)
}
}
pub mod constants {
use super::*;
pub fn square<T: ZZNum + HasZZ4>() -> Snake<T> {
Snake::try_from(upscale_angles::<T>(4, &[1, 1, 1, 1]).as_slice()).unwrap()
}
pub fn triangle<T: ZZNum + HasZZ6>() -> Snake<T> {
Snake::try_from(upscale_angles::<T>(6, &[2, 2, 2]).as_slice()).unwrap()
}
pub fn hexagon<T: ZZNum + HasZZ6>() -> Snake<T> {
Snake::try_from(upscale_angles::<T>(6, &[1, 1, 1, 1, 1, 1]).as_slice()).unwrap()
}
pub fn spectre<T: ZZNum + HasZZ12>() -> Snake<T> {
Snake::try_from(
upscale_angles::<T>(12, &[3, 2, 0, 2, -3, 2, 3, 2, -3, 2, 3, -2, 3, -2]).as_slice(),
)
.unwrap()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::traits::Ccw;
use crate::zz::{Z12, ZZ12, ZZ24};
use crate::zzbase::ZZBase;
use constants::{hexagon, spectre, square, triangle};
use num_rational::Ratio;
use num_traits::{One, Zero};
#[test]
fn test_spectre() {
let seq_zz24: Vec<i8> = vec![6, 4, 0, 4, -6, 4, 6, 4, -6, 4, 6, -4, 6, -4];
let s: Snake<ZZ24> = spectre();
assert_eq!(s.angles(), seq_zz24);
}
#[test]
#[should_panic]
fn test_add_invalid_angle() {
let mut s: Snake<ZZ12> = Snake::new();
s.add(6);
}
#[test]
#[should_panic]
fn test_add_invalid_angle_neg() {
let mut s: Snake<ZZ12> = Snake::new();
s.add(-6);
}
#[test]
#[should_panic]
fn test_add_invalid_angle_mod() {
let mut s: Snake<ZZ12> = Snake::new();
s.add(18);
}
#[test]
fn test_basic() {
let mut s: Snake<ZZ12> = Snake::new();
assert!(s.is_empty());
assert!(!s.is_closed());
assert_eq!(s.len(), 0);
assert_eq!(s.angle_sum(), 0);
assert_eq!(s.direction(), 0);
assert_eq!(s.offset(), ZZ12::zero());
s.add(-9); assert!(!s.is_empty());
assert!(!s.is_closed());
assert_eq!(*s.angles().last().unwrap(), 3);
assert_eq!(s.direction(), 3);
assert_eq!(s.offset(), ZZ12::unit(3));
s.add(2);
s.add(2);
s.add(5);
assert_eq!(s.len(), 4);
let mut s2: Snake<ZZ12> = Snake::new();
s2.add(-2);
s2.add(0);
s2.add(5);
assert_eq!(s2.len(), 3);
let s3 = s.concat(&s2).unwrap();
s.extend(&s2).unwrap();
assert_eq!(s, s3);
assert_eq!(s.len(), 7);
assert_eq!(s.angle_sum(), 15);
assert_eq!(s.direction(), 3);
assert_eq!(s.head().dir, s.direction());
assert_eq!(s.head().pos, s.offset());
let l_exp: Vec<ZZ12> = s.representative().to_vec();
let l = s.to_polyline(Turtle::default());
assert_eq!(l, l_exp);
let t = Turtle {
pos: ZZ12::ccw().scale(7),
dir: -2,
};
let l2_exp: Vec<ZZ12> = l_exp
.iter()
.map(|pt| (*pt * ZZ12::unit(t.dir)) + t.pos)
.collect();
let l2 = s.to_polyline(t);
assert_eq!(l2, l2_exp);
}
#[test]
fn test_closed_snake() {
let sq1: Snake<ZZ12> = Snake::try_from(&[0, 3, 3, 3]).unwrap();
assert!(sq1.is_closed());
assert_eq!(sq1.angle_sum(), ZZ12::turn() as i64);
let sq2: Snake<ZZ12> = Snake::try_from(&[0, -3, -3, -3]).unwrap();
assert!(sq2.is_closed());
assert_eq!(sq2.angle_sum(), -ZZ12::turn() as i64);
let mut square: Snake<ZZ12> = Snake::new();
assert!(!square.is_closed());
for _ in 0..3 {
square.add(3);
}
assert!(!square.is_closed());
square.add(3);
assert!(square.is_closed()); }
#[test]
fn test_cell_segs() {
let mut s: Snake<ZZ12> = Snake::new();
assert!(s.cell_segs(&[(0, 0)]).is_empty());
s.add(0);
assert_eq!(s.cell_segs(&[(0, 0)]), &[(ZZ12::zero(), ZZ12::one())]);
s.add(0);
assert_eq!(s.cell_segs(&[(0, 0)]), &[(ZZ12::zero(), ZZ12::one())]);
assert_eq!(
s.cell_segs(&[(1, 0)]),
&[
(ZZ12::zero(), ZZ12::one()),
(ZZ12::one(), ZZ12::one().scale(2))
]
);
assert_eq!(
s.cell_segs(&[(2, 0)]),
&[(ZZ12::one(), ZZ12::one().scale(2))]
);
}
#[test]
fn test_can_add() {
let mut s: Snake<ZZ12> = Snake::try_from(&[3, 3, 3]).unwrap();
assert!(s.add(3)); assert!(!s.add(0));
let mut s2: Snake<ZZ12> = Snake::try_from(&[0, 3, 3, 3]).unwrap();
assert!(!s2.add(3));
let mut s3: Snake<ZZ12> = Snake::try_from(&[0, 4]).unwrap();
assert!(!s3.add(5));
let mut s4: Snake<ZZ12> = Snake::new();
assert!(s4.add(0));
assert!(s4.add(5));
assert!(!s4.add(4));
assert!(s4.add(3));
assert!(!s4.add(5));
assert!(s4.add(4));
assert!(!s4.add(2));
assert!(s4.add(1));
assert!(!s4.add(5));
assert!(s4.add(4));
s4.extend(&Snake::try_from(&[1, 1, 3, 5]).unwrap()).unwrap();
assert_eq!(s4.len(), 10);
for i in (-ZZ12::hturn() + 1)..(ZZ12::hturn() - 1) {
assert!(!s4.add(i));
}
}
#[test]
fn test_area() {
let tri_area_sq3 = Ratio::<i64>::new_raw(1, 2);
assert_eq!(
triangle::<ZZ12>().double_area(),
Z12::new(&[0.into(), tri_area_sq3])
);
assert_eq!(
square::<ZZ12>().double_area(),
Z12::new(&[2.into(), 0.into()])
);
assert_eq!(
hexagon::<ZZ12>().double_area(),
Z12::new(&[0.into(), 3.into()])
);
}
#[test]
fn test_eq_ord() {
let s1: Snake<ZZ12> = Snake::try_from(&[1, 2, 3]).unwrap();
let mut s2: Snake<ZZ12> = Snake::try_from(s1.angles.as_slice()).unwrap();
assert_eq!(s1, s2);
s2.add(4);
assert!(s1 != s2);
assert!(s1 < s2);
let s3: Snake<ZZ12> = Snake::try_from(&[-1, 4, 3]).unwrap();
assert!(s3 < s1);
assert!(s3 < s2);
let s4: Snake<ZZ12> = Snake::new();
assert!(s4 < s1);
assert!(s4 < s2);
assert!(s4 < s3);
}
#[test]
fn test_display() {
let mut s: Snake<ZZ12> = Snake::new();
for i in -2..2 {
s.add(i);
}
s.add(7);
assert_eq!(format!("{s}"), "[-2, -1, 0, 1, -5]");
}
}