a5 0.7.3

High-performance Rust implementation of the A5 hierarchical spatial indexing system
Documentation
// A5
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) A5 contributors

use crate::coordinate_systems::{Face, Polar};
use crate::core::constants::TWO_PI_OVER_5;
use crate::core::hilbert::{Anchor, NO, YES};
use crate::core::pentagon::{basis, pentagon, triangle, v, w, Mat2};
use crate::geometry::PentagonShape;

const TRIANGLE_MODE: bool = false;

/// Shift right vector (clone of w)
fn shift_right() -> Face {
    w()
}

/// Shift left vector (negative w)
fn shift_left() -> Face {
    let w_vec = w();
    Face::new(-w_vec.x(), -w_vec.y())
}

/// Generate quintant rotation matrices
fn quintant_rotations() -> [Mat2; 5] {
    let mut rotations = [Mat2::new(1.0, 0.0, 0.0, 1.0); 5];

    for (quintant, rotation) in rotations.iter_mut().enumerate() {
        let angle = (TWO_PI_OVER_5).0 * quintant as f64;
        let cos_angle = angle.cos();
        let sin_angle = angle.sin();
        *rotation = Mat2::new(cos_angle, -sin_angle, sin_angle, cos_angle);
    }

    rotations
}

/// Transform a pentagon shape using a 2x2 matrix
fn transform_pentagon(pentagon: &mut PentagonShape, matrix: &Mat2) {
    let vertices = pentagon.get_vertices_vec();
    let mut transformed_vertices = Vec::new();

    for vertex in vertices {
        let transformed_x = matrix.m00 * vertex.x() + matrix.m01 * vertex.y();
        let transformed_y = matrix.m10 * vertex.x() + matrix.m11 * vertex.y();
        transformed_vertices.push(Face::new(transformed_x, transformed_y));
    }

    // Create new pentagon with transformed vertices - need 5 for Pentagon type
    if transformed_vertices.len() == 5 {
        let pentagon_vertices: [Face; 5] = [
            transformed_vertices[0],
            transformed_vertices[1],
            transformed_vertices[2],
            transformed_vertices[3],
            transformed_vertices[4],
        ];
        *pentagon = PentagonShape::new(pentagon_vertices);
    } else if transformed_vertices.len() == 3 {
        let pentagon_vertices: [Face; 3] = [
            transformed_vertices[0],
            transformed_vertices[1],
            transformed_vertices[2],
        ];
        *pentagon = PentagonShape::new_triangle(pentagon_vertices);
    }
}

/// Get pentagon vertices with transformations applied
///
/// # Arguments
///
/// * `resolution` - The resolution level
/// * `quintant` - The quintant index (0-4)  
/// * `anchor` - The anchor information containing offset and flip data
///
/// # Returns
///
/// A pentagon shape with transformed vertices
pub fn get_pentagon_vertices(resolution: i32, quintant: usize, anchor: &Anchor) -> PentagonShape {
    let mut pentagon_shape = if TRIANGLE_MODE {
        triangle().clone()
    } else {
        pentagon().clone()
    };

    // Transform anchor offset using basis matrix
    let basis_mat = basis();
    let translation_x = basis_mat.m00 * anchor.offset.x() + basis_mat.m01 * anchor.offset.y();
    let translation_y = basis_mat.m10 * anchor.offset.x() + basis_mat.m11 * anchor.offset.y();
    let translation = Face::new(translation_x, translation_y);

    // Apply transformations based on anchor properties
    if anchor.flips[0] == NO && anchor.flips[1] == YES {
        pentagon_shape.rotate180();
    }

    let k = anchor.q;
    let f = anchor.flips[0] + anchor.flips[1];

    if
    // Orient last two pentagons when both or neither flips are YES
    ((f == -2 || f == 2) && k > 1) ||
        // Orient first & last pentagons when only one of flips is YES  
        (f == 0 && (k == 0 || k == 3))
    {
        pentagon_shape.reflect_y();
    }

    if anchor.flips[0] == YES && anchor.flips[1] == YES {
        pentagon_shape.rotate180();
    } else if anchor.flips[0] == YES {
        pentagon_shape.translate(shift_left());
    } else if anchor.flips[1] == YES {
        pentagon_shape.translate(shift_right());
    }

    // Position within quintant
    pentagon_shape.translate(translation);
    pentagon_shape.scale(1.0 / (2.0_f64.powi(resolution)));

    let rotations = quintant_rotations();
    transform_pentagon(&mut pentagon_shape, &rotations[quintant]);

    pentagon_shape
}

/// Get quintant vertices
///
/// # Arguments
///
/// * `quintant` - The quintant index (0-4)
///
/// # Returns
///
/// Triangle vertices for the specified quintant as PentagonShape
pub fn get_quintant_vertices(quintant: usize) -> crate::geometry::pentagon::PentagonShape {
    // Create proper 3-vertex triangle from the triangle vertices
    let triangle_verts = triangle().get_vertices();
    let triangle_3_verts = [triangle_verts[0], triangle_verts[1], triangle_verts[2]];

    let mut pentagon_shape =
        crate::geometry::pentagon::PentagonShape::new_triangle(triangle_3_verts);
    let rotations = quintant_rotations();
    transform_pentagon(&mut pentagon_shape, &rotations[quintant]);
    pentagon_shape
}

/// Get face vertices with correct winding order
///
/// # Returns
///
/// Pentagon shape representing the face vertices
pub fn get_face_vertices() -> PentagonShape {
    let mut vertices = Vec::new();
    let v_vertex = v();
    let rotations = quintant_rotations();

    for rotation in &rotations {
        // Transform v vertex by rotation matrix
        let transformed_x = rotation.m00 * v_vertex.x() + rotation.m01 * v_vertex.y();
        let transformed_y = rotation.m10 * v_vertex.x() + rotation.m11 * v_vertex.y();
        vertices.push(Face::new(transformed_x, transformed_y));
    }

    // Need to reverse to obtain correct winding order
    vertices.reverse();

    // Convert Vec to array for PentagonShape::new
    let pentagon_vertices: [Face; 5] = [
        vertices[0],
        vertices[1],
        vertices[2],
        vertices[3],
        vertices[4],
    ];
    PentagonShape::new(pentagon_vertices)
}

/// Get quintant from polar coordinates
///
/// # Arguments
///
/// * `polar` - Polar coordinates [rho, gamma]
///
/// # Returns  
///
/// The quintant index (0-4)
pub fn get_quintant_polar(polar: Polar) -> usize {
    let gamma = polar.gamma().0; // Extract f64 from Radians
    ((gamma / (TWO_PI_OVER_5).0).round() as i32 + 5) as usize % 5
}

/// Pentagon flavor type (0-7)
pub type PentagonFlavor = u8;

/// Get the flavor (0-7) of a pentagon from its anchor.
pub fn get_pentagon_flavor(anchor: &Anchor) -> PentagonFlavor {
    let mut f: u8 = 0;
    if anchor.flips[1] == YES {
        f += 2;
    }

    let q = anchor.q;
    let sum = anchor.flips[0] + anchor.flips[1];
    if
    // Orient last two pentagons when both or neither flips are YES
    ((sum == -2 || sum == 2) && q > 1) ||
        // Orient first & last pentagons when only one of flips is YES
        (sum == 0 && (q == 0 || q == 3))
    {
        f += 1;
    }

    if sum == -2 || sum == 2 {
        f += 4;
    }

    f
}