use std::sync::OnceLock;
use super::constants::FINEANGLES;
static FINE_SINE: OnceLock<Box<[f32; FINEANGLES]>> = OnceLock::new();
static FINE_COSINE: OnceLock<Box<[f32; FINEANGLES]>> = OnceLock::new();
static FINE_TANGENT: OnceLock<Box<[f32; FINEANGLES]>> = OnceLock::new();
fn init_sine() -> Box<[f32; FINEANGLES]> {
let mut table = Box::new([0.0f32; FINEANGLES]);
for i in 0..FINEANGLES {
let angle = (i as f32) * std::f32::consts::TAU / (FINEANGLES as f32);
table[i] = angle.sin();
}
table
}
fn init_cosine() -> Box<[f32; FINEANGLES]> {
let mut table = Box::new([0.0f32; FINEANGLES]);
for i in 0..FINEANGLES {
let angle = (i as f32) * std::f32::consts::TAU / (FINEANGLES as f32);
table[i] = angle.cos();
}
table
}
fn init_tangent() -> Box<[f32; FINEANGLES]> {
let mut table = Box::new([0.0f32; FINEANGLES]);
for i in 0..FINEANGLES {
let angle = (i as f32) * std::f32::consts::TAU / (FINEANGLES as f32);
let c = angle.cos();
table[i] = if c.abs() < 1e-10 {
if angle.sin() >= 0.0 {
f32::MAX
} else {
f32::MIN
}
} else {
angle.sin() / c
};
}
table
}
#[inline]
pub fn fine_sine(angle: usize) -> f32 {
let table = FINE_SINE.get_or_init(init_sine);
table[angle & (FINEANGLES - 1)]
}
#[inline]
pub fn fine_cosine(angle: usize) -> f32 {
let table = FINE_COSINE.get_or_init(init_cosine);
table[angle & (FINEANGLES - 1)]
}
#[inline]
pub fn fine_tangent(angle: usize) -> f32 {
let table = FINE_TANGENT.get_or_init(init_tangent);
table[angle & (FINEANGLES - 1)]
}
#[inline]
pub fn radians_to_fine(rad: f32) -> usize {
let normalized = rad.rem_euclid(std::f32::consts::TAU);
((normalized / std::f32::consts::TAU) * FINEANGLES as f32) as usize & (FINEANGLES - 1)
}
#[inline]
pub fn fine_to_radians(fine: usize) -> f32 {
(fine as f32) * std::f32::consts::TAU / (FINEANGLES as f32)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sine_at_zero_is_zero() {
assert!(fine_sine(0).abs() < 1e-5);
}
#[test]
fn cosine_at_zero_is_one() {
assert!((fine_cosine(0) - 1.0).abs() < 1e-5);
}
#[test]
fn sine_at_quarter_is_one() {
assert!((fine_sine(FINEANGLES / 4) - 1.0).abs() < 1e-4);
}
#[test]
fn radians_roundtrip() {
let angle = 1.234f32;
let fine = radians_to_fine(angle);
let back = fine_to_radians(fine);
assert!((angle - back).abs() < 0.001);
}
#[test]
fn tangent_at_zero_is_zero() {
assert!(fine_tangent(0).abs() < 1e-5);
}
#[test]
fn sine_at_half_is_zero() {
assert!(fine_sine(FINEANGLES / 2).abs() < 1e-4);
}
#[test]
fn cosine_at_quarter_is_zero() {
assert!(fine_cosine(FINEANGLES / 4).abs() < 1e-4);
}
#[test]
fn cosine_at_half_is_negative_one() {
assert!((fine_cosine(FINEANGLES / 2) + 1.0).abs() < 1e-4);
}
#[test]
fn sine_at_three_quarters_is_negative_one() {
assert!((fine_sine(3 * FINEANGLES / 4) + 1.0).abs() < 1e-4);
}
#[test]
fn angle_wraps_around() {
assert!((fine_sine(FINEANGLES) - fine_sine(0)).abs() < 1e-10);
assert!((fine_cosine(FINEANGLES + 100) - fine_cosine(100)).abs() < 1e-10);
assert!((fine_tangent(FINEANGLES + 200) - fine_tangent(200)).abs() < 1e-10);
}
#[test]
fn radians_to_fine_zero() {
assert_eq!(radians_to_fine(0.0), 0);
}
#[test]
fn radians_to_fine_negative_wraps() {
let idx = radians_to_fine(-1.0);
assert!(idx < FINEANGLES);
}
#[test]
fn fine_to_radians_zero_is_zero() {
assert!((fine_to_radians(0) - 0.0).abs() < 1e-10);
}
#[test]
fn pythagorean_identity() {
for angle in [0, 100, FINEANGLES / 8, FINEANGLES / 4, FINEANGLES / 3, 5000] {
let s = fine_sine(angle);
let c = fine_cosine(angle);
let sum = s * s + c * c;
assert!(
(sum - 1.0).abs() < 1e-5,
"sin^2+cos^2 != 1 at angle {angle}: {sum}"
);
}
}
#[test]
fn tangent_equals_sine_over_cosine() {
for angle in [100, 500, 1000, 2000, 3000, 6000, 7000] {
let s = fine_sine(angle);
let c = fine_cosine(angle);
if c.abs() > 0.01 {
let expected = s / c;
let actual = fine_tangent(angle);
assert!(
(actual - expected).abs() < 1e-4,
"tan != sin/cos at angle {angle}: {actual} vs {expected}"
);
}
}
}
#[test]
fn radians_to_fine_tau_wraps_to_zero() {
assert_eq!(radians_to_fine(std::f32::consts::TAU), 0);
assert_eq!(radians_to_fine(2.0 * std::f32::consts::TAU), 0);
}
#[test]
fn fine_to_radians_last_entry_is_below_tau() {
let last = fine_to_radians(FINEANGLES - 1);
assert!(last >= 0.0);
assert!(last < std::f32::consts::TAU);
}
#[test]
fn tangent_near_quarter_turn_has_large_magnitude() {
let tan_q1 = fine_tangent(FINEANGLES / 4);
let tan_q3 = fine_tangent(3 * FINEANGLES / 4);
assert!(
tan_q1.abs() > 1_000_000.0,
"expected very large tangent near PI/2, got {tan_q1}"
);
assert!(
tan_q3.abs() > 1_000_000.0,
"expected very large tangent near 3PI/2, got {tan_q3}"
);
}
#[test]
fn all_sine_values_in_unit_range() {
for i in 0..FINEANGLES {
let s = fine_sine(i);
assert!((-1.0..=1.0).contains(&s), "sine[{i}]={s} outside [-1, 1]");
}
}
#[test]
fn all_cosine_values_in_unit_range() {
for i in 0..FINEANGLES {
let c = fine_cosine(i);
assert!((-1.0..=1.0).contains(&c), "cosine[{i}]={c} outside [-1, 1]");
}
}
#[test]
fn sine_odd_symmetry() {
for i in 1..FINEANGLES {
let a = fine_sine(i);
let b = fine_sine(FINEANGLES - i);
assert!(
(a + b).abs() < 1e-4,
"sine odd symmetry failed at {i}: sin({i})={a}, sin({})={b}",
FINEANGLES - i
);
}
}
#[test]
fn cosine_even_symmetry() {
for i in 1..FINEANGLES {
let a = fine_cosine(i);
let b = fine_cosine(FINEANGLES - i);
assert!(
(a - b).abs() < 1e-4,
"cosine even symmetry failed at {i}: cos({i})={a}, cos({})={b}",
FINEANGLES - i
);
}
}
#[test]
fn sine_cosine_phase_shift() {
let quarter = FINEANGLES / 4;
for i in [0, 100, 500, 1000, 2000, 4000, 6000, 7500] {
let sin_shifted = fine_sine((i + quarter) & (FINEANGLES - 1));
let cos_val = fine_cosine(i);
assert!(
(sin_shifted - cos_val).abs() < 1e-4,
"sin({i}+N/4)={sin_shifted} != cos({i})={cos_val}"
);
}
}
#[test]
fn fine_to_radians_quarter_is_half_pi() {
let rad = fine_to_radians(FINEANGLES / 4);
assert!(
(rad - std::f32::consts::FRAC_PI_2).abs() < 1e-4,
"expected PI/2, got {rad}"
);
}
#[test]
fn radians_to_fine_pi_is_half_fineangles() {
let idx = radians_to_fine(std::f32::consts::PI);
assert_eq!(idx, FINEANGLES / 2);
}
#[test]
fn large_index_wrap_around() {
for offset in [0, 1, 100, FINEANGLES / 4, FINEANGLES - 1] {
let direct = fine_sine(offset);
let wrapped = fine_sine(10 * FINEANGLES + offset);
assert!(
(direct - wrapped).abs() < 1e-10,
"wrap failed at offset {offset}"
);
}
}
#[test]
fn repeated_access_returns_same_value() {
let a = fine_sine(1234);
let b = fine_sine(1234);
assert_eq!(a.to_bits(), b.to_bits());
let c = fine_cosine(5678);
let d = fine_cosine(5678);
assert_eq!(c.to_bits(), d.to_bits());
let e = fine_tangent(2000);
let f = fine_tangent(2000);
assert_eq!(e.to_bits(), f.to_bits());
}
#[test]
fn tangent_sign_per_quadrant() {
let t1 = fine_tangent(FINEANGLES / 8);
assert!(t1 > 0.0, "Q1 tangent should be positive, got {t1}");
let t2 = fine_tangent(3 * FINEANGLES / 8);
assert!(t2 < 0.0, "Q2 tangent should be negative, got {t2}");
let t3 = fine_tangent(5 * FINEANGLES / 8);
assert!(t3 > 0.0, "Q3 tangent should be positive, got {t3}");
let t4 = fine_tangent(7 * FINEANGLES / 8);
assert!(t4 < 0.0, "Q4 tangent should be negative, got {t4}");
}
}