Documentation
use crate::Mesh;
use crate::MeshData;
use crate::Vec3;
use macaw::ColorRgba8;

/// Create a box mesh centered on origin with the specified extents.
///
/// Instead of coloring the box here, especially if you're using boxes with
/// multiple colors, consider making it white (`ColorRgba8([255, 255, 255, 255)`)
/// and using `MeshStyle` Tint to color the box. This applies both to the scene
/// and world APIs.
pub fn create_box_mesh(extents: Vec3, color: Option<ColorRgba8>) -> Mesh {
    let s = extents / 2.0;

    //
    //      a +--------------+ b
    //       /|             /|
    //      / |            / |
    //   c *--+-----------*  | d
    //     |  |           |  |
    //     |  |           |  |
    //     |  |           |  |
    //   e |  +-----------+--+ f
    //     | /            | /
    //     |/             |/
    //   g *--------------* h
    let mut positions = vec![
        Vec3::new(s.x, -s.y, s.z),   // g
        Vec3::new(s.x, -s.y, -s.z),  // e
        Vec3::new(s.x, s.y, -s.z),   // a
        Vec3::new(s.x, s.y, s.z),    // c
        Vec3::new(-s.x, -s.y, s.z),  // h
        Vec3::new(-s.x, -s.y, -s.z), // f
        Vec3::new(-s.x, s.y, -s.z),  // b
        Vec3::new(-s.x, s.y, s.z),   // d
    ];

    let mut indices: Vec<u32> = vec![
        4, 0, 3, 4, 3, 7, 0, 1, 2, 0, 2, 3, 1, 5, 6, 1, 6, 2, 5, 4, 7, 5, 7, 6, 7, 3, 2, 7, 2, 6,
        0, 5, 1, 0, 4, 5,
    ];

    let use_normals = true;

    let normals = use_normals.then(|| {
        let face_normals = indices
            .chunks(3)
            .map(|i| {
                let p = positions[i[0] as usize];
                let a = positions[i[1] as usize] - p;
                let b = positions[i[2] as usize] - p;
                a.cross(b).normalize()
            })
            .collect::<Vec<_>>();

        positions = indices
            .iter()
            .map(|i| positions[*i as usize])
            .collect::<Vec<_>>();

        indices = (0..36_u32).collect::<Vec<_>>();

        indices
            .iter()
            .map(|i| *face_normals[(i / 3) as usize].as_ref())
            .collect::<Vec<[f32; 3]>>()
    });

    let color = color.unwrap_or(ColorRgba8([255, 255, 255, 255]));
    let colors = vec![color; positions.len()];
    let half_size = s;

    Mesh {
        bounding_box_min: -half_size,
        bounding_box_max: half_size,
        bounding_sphere_radius: half_size.length(),
        primitive_count: (indices.len() / 3) as u64,
        data: Some(MeshData {
            name: "box".into(),
            indices,
            positions: positions
                .iter()
                .map(|p| *p.as_ref())
                .collect::<Vec<[f32; 3]>>(),
            normals,
            colors: Some(colors),
        }),
    }
}

/// Create a latitude-longitude sphere mesh centered on origin with the specified extents
///
/// Instead of coloring the sphere here, especially if you're using boxes with
/// multiple colors, consider making it white (`ColorRgba8([255, 255, 255, 255)`)
/// and using `MeshStyle` Tint to color the box. This applies both to the scene
/// and world APIs.
///
/// If `subdivision_x` or `subdivision_y` are less than 3 they will be overridden
/// to 3. This is done as a lower subdivision will result in a mesh that is not
/// visible when rendered.
pub fn create_sphere_mesh(
    radius: f32,
    subdivision_x: usize,
    subdivision_y: usize,
    color: Option<ColorRgba8>,
) -> Mesh {
    let subdivision_x = 3.max(subdivision_x as u32);
    let subdivision_y = 3.max(subdivision_y as u32);

    let delta_x = 2.0 * std::f32::consts::PI / subdivision_x as f32;
    let delta_y = std::f32::consts::PI / subdivision_y as f32;

    let mut positions = vec![];
    let mut normals = vec![];
    // North pole
    positions.push(Vec3::new(0.0, radius, 0.0));
    // Stripes
    for y in 1..subdivision_y {
        let angle_y = delta_y * y as f32;
        for x in 0..subdivision_x {
            let angle_x = delta_x * x as f32;

            let pos = Vec3::new(
                angle_x.cos() * angle_y.sin(),
                angle_y.cos(),
                angle_x.sin() * angle_y.sin(),
            ) * radius;

            positions.push(pos);
        }
    }
    // South pole.
    positions.push(Vec3::new(0.0, -radius, 0.0));

    for p in &positions {
        normals.push(p.normalize());
    }

    let mut indices: Vec<u32> = vec![];
    // North cap
    for i in 0..subdivision_x {
        indices.push(0);
        indices.push(1 + (i + 1) % subdivision_x);
        indices.push(1 + i);
    }

    // Stripes
    for y in 0..subdivision_y - 2 {
        for x in 0..subdivision_x {
            let b = 1 + y * subdivision_x + x;
            let b_next = 1 + y * subdivision_x + (x + 1) % subdivision_x;
            indices.push(b);
            indices.push(b_next);
            indices.push(b + subdivision_x);
            indices.push(b_next);
            indices.push(b_next + subdivision_x);
            indices.push(b + subdivision_x);
        }
    }

    // South cap
    let b = 1 + (subdivision_y - 2) * subdivision_x;
    for i in 0..subdivision_x {
        indices.push(b + (i + 1) % subdivision_x);
        indices.push(b + subdivision_x);
        indices.push(b + i);
    }

    let color = color.unwrap_or(ColorRgba8([255, 255, 255, 255]));
    let colors = vec![color; positions.len()];

    Mesh {
        bounding_box_min: -Vec3::splat(radius),
        bounding_box_max: Vec3::splat(radius),
        bounding_sphere_radius: radius,
        primitive_count: (indices.len() / 3) as u64,
        data: Some(MeshData {
            name: "sphere".into(),
            indices,
            positions: positions
                .iter()
                .map(|p| *p.as_ref())
                .collect::<Vec<[f32; 3]>>(),
            normals: Some(
                normals
                    .iter()
                    .map(|p| *p.as_ref())
                    .collect::<Vec<[f32; 3]>>(),
            ),
            colors: Some(colors),
        }),
    }
}

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

    #[test]
    fn box_mesh() {
        let color = Some(ColorRgba8([255, 255, 255, 255]));
        create_box_mesh(Vec3::new(2.0, 1.0, 0.5), None)
            .validate()
            .unwrap();
        create_box_mesh(Vec3::new(2.0, 1.0, 0.5), color)
            .validate()
            .unwrap();
        // weird to use negative size, but let's test and create an inverted box
        create_box_mesh(Vec3::new(-2.0, -1.0, -0.5), None)
            .validate()
            .unwrap();
        create_box_mesh(Vec3::new(-2.0, -1.0, -0.5), color)
            .validate()
            .unwrap();
    }

    #[test]
    fn sphere_mesh() {
        // Subdivisions of < 3 are overridden
        assert_eq!(
            create_sphere_mesh(5.0, 0, 0, None),
            create_sphere_mesh(5.0, 3, 3, None),
        );

        assert_eq!(
            create_sphere_mesh(5.0, 1, 0, None),
            create_sphere_mesh(5.0, 3, 3, None),
        );

        assert_eq!(
            create_sphere_mesh(5.0, 2, 0, None),
            create_sphere_mesh(5.0, 3, 3, None),
        );

        assert_eq!(
            create_sphere_mesh(5.0, 4, 0, None),
            create_sphere_mesh(5.0, 4, 3, None),
        );

        assert_eq!(
            create_sphere_mesh(5.0, 4, 1, None),
            create_sphere_mesh(5.0, 4, 3, None),
        );

        assert_eq!(
            create_sphere_mesh(5.0, 4, 2, None),
            create_sphere_mesh(5.0, 4, 3, None),
        );

        assert_eq!(
            create_sphere_mesh(5.0, 4, 3, None),
            create_sphere_mesh(5.0, 4, 3, None),
        );

        assert_eq!(
            create_sphere_mesh(5.0, 4, 4, None),
            create_sphere_mesh(5.0, 4, 4, None),
        );
    }
}