//! Encoding/decoding for a 32-bit representation of unit 3d vectors.
//!
//! Follows the Oct32 encoding specified in the paper "A Survey
//! of Efficient Representations for Independent Unit Vectors" by
//! Cigolle et al.

/// Encodes a vector of three floats to the oct32 format.
///
/// The input vector does not need to be normalized--only the direction
/// matters to the encoding process, not the length.
#[inline]
pub fn encode(vec: (f32, f32, f32)) -> u32 {
    let inv_l1_norm = 1.0f32 / (vec.0.abs() + vec.1.abs() + vec.2.abs());

    let (u, v) = if vec.2 < 0.0 {
        (
            u32::from(to_snorm_16(
                (1.0 - (vec.1 * inv_l1_norm).abs()) * sign(vec.0),
            )),
            u32::from(to_snorm_16(
                (1.0 - (vec.0 * inv_l1_norm).abs()) * sign(vec.1),
            )),
        )
    } else {
        (
            u32::from(to_snorm_16(vec.0 * inv_l1_norm)),
            u32::from(to_snorm_16(vec.1 * inv_l1_norm)),
        )
    };

    (u << 16) | v
}

/// Decodes from an oct32 to a vector of three floats.
///
/// The returned vector will not generally be normalized.  Code that
/// needs a normalized vector should normalize the returned vector.
#[inline]
pub fn decode(n: u32) -> (f32, f32, f32) {
    let mut vec0 = from_snorm_16((n >> 16) as u16);
    let mut vec1 = from_snorm_16(n as u16);
    let vec2 = 1.0 - (vec0.abs() + vec1.abs());

    if vec2 < 0.0 {
        let old_x = vec0;
        vec0 = (1.0 - vec1.abs()) * sign(old_x);
        vec1 = (1.0 - old_x.abs()) * sign(vec1);
    }

    (vec0, vec1, vec2)
}

#[inline(always)]
fn to_snorm_16(n: f32) -> u16 {
    (n.max(-1.0).min(1.0) * ((1u32 << (16 - 1)) - 1) as f32).round() as i16 as u16
}

#[inline(always)]
fn from_snorm_16(n: u16) -> f32 {
    f32::from(n as i16)
        * (1.0f32 / ((1u32 << (16 - 1)) - 1) as f32)
            .max(-1.0)
            .min(1.0)
}

#[inline(always)]
fn sign(n: f32) -> f32 {
    if n < 0.0 {
        -1.0
    } else {
        1.0
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn axis_directions() {
        let px = (1.0, 0.0, 0.0);
        let px_oct = encode(px);

        let nx = (-1.0, 0.0, 0.0);
        let nx_oct = encode(nx);

        let py = (0.0, 1.0, 0.0);
        let py_oct = encode(py);

        let ny = (0.0, -1.0, 0.0);
        let ny_oct = encode(ny);

        let pz = (0.0, 0.0, 1.0);
        let pz_oct = encode(pz);

        let nz = (0.0, 0.0, -1.0);
        let nz_oct = encode(nz);

        assert_eq!(px, decode(px_oct));
        assert_eq!(nx, decode(nx_oct));
        assert_eq!(py, decode(py_oct));
        assert_eq!(ny, decode(ny_oct));
        assert_eq!(pz, decode(pz_oct));
        assert_eq!(nz, decode(nz_oct));
    }
}
