mirage-engine 0.2.0

Mirage, an immediate-mode 3D engine for simple games on desktop and the browser
Documentation
use crate::math::{Vec2, Vec3};
use crate::mesh::{Mesh, MeshData, Vertex};
use crate::{Assets, Catalog};

const HALF: f32 = 0.5;

const QUAD_INDICES: [u32; 6] = [0, 1, 2, 0, 2, 3];

/// A one-meter cube centered on the origin.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Cube;

impl Catalog for Cube {
    fn catalog() -> Vec<Self> {
        vec![Self]
    }
}

impl Mesh for Cube {
    fn build(&self, _assets: &Assets) -> MeshData {
        let faces = [
            (Vec3::X, Vec3::NEG_Z, Vec3::Y),
            (Vec3::NEG_X, Vec3::Z, Vec3::Y),
            (Vec3::Y, Vec3::X, Vec3::NEG_Z),
            (Vec3::NEG_Y, Vec3::X, Vec3::Z),
            (Vec3::Z, Vec3::X, Vec3::Y),
            (Vec3::NEG_Z, Vec3::NEG_X, Vec3::Y),
        ];

        let vertices = faces
            .iter()
            .flat_map(|&(normal, right, up)| square(normal * HALF, normal, right, up))
            .collect();
        let indices = (0..faces.len() as u32)
            .flat_map(|face| QUAD_INDICES.map(|index| face * 4 + index))
            .collect();

        MeshData::new(vertices, indices)
    }
}

/// A one-meter square in the XZ plane, facing up — a surface under a scene.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Plane;

impl Catalog for Plane {
    fn catalog() -> Vec<Self> {
        vec![Self]
    }
}

impl Mesh for Plane {
    fn build(&self, _assets: &Assets) -> MeshData {
        square_mesh(Vec3::Y, Vec3::X, Vec3::NEG_Z)
    }
}

/// A one-meter square in the XY plane, facing the default camera.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Quad;

impl Catalog for Quad {
    fn catalog() -> Vec<Self> {
        vec![Self]
    }
}

impl Mesh for Quad {
    fn build(&self, _assets: &Assets) -> MeshData {
        square_mesh(Vec3::Z, Vec3::X, Vec3::Y)
    }
}

/// A one-meter sphere centered on the origin.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Sphere {
    /// The sphere mesh's density: `4 * (subdivisions + 1)` parts go around
    /// it, and `2 * (subdivisions + 1)` go pole to pole.
    pub subdivisions: u32,
}

impl Catalog for Sphere {
    /// The values `0..=3`, which are built at startup; a sphere of any other
    /// [`subdivisions`](Sphere::subdivisions) is built on its first draw and
    /// cached like any other mesh.
    fn catalog() -> Vec<Self> {
        (0..=3).map(|subdivisions| Self { subdivisions }).collect()
    }
}

impl Mesh for Sphere {
    fn build(&self, _assets: &Assets) -> MeshData {
        let segments = 4 * (self.subdivisions + 1);
        let rings = 2 * (self.subdivisions + 1);

        let mut vertices = Vec::with_capacity(((rings + 1) * (segments + 1)) as usize);
        for ring in 0..=rings {
            let latitude = core::f32::consts::PI * ring as f32 / rings as f32;
            let (radius, height) = latitude.sin_cos();
            for segment in 0..=segments {
                let longitude = core::f32::consts::TAU * segment as f32 / segments as f32;
                let (ahead, right) = longitude.sin_cos();
                let normal = Vec3::new(radius * right, height, radius * ahead);
                vertices.push(Vertex::new(
                    normal * HALF,
                    normal,
                    Vec2::new(segment as f32 / segments as f32, ring as f32 / rings as f32),
                ));
            }
        }

        let corner = |ring: u32, segment: u32| ring * (segments + 1) + segment;
        let mut indices = Vec::with_capacity((rings * segments * 6) as usize);
        for ring in 0..rings {
            for segment in 0..segments {
                let (here, next) = (corner(ring, segment), corner(ring, segment + 1));
                let (under, under_next) =
                    (corner(ring + 1, segment), corner(ring + 1, segment + 1));

                let touches_north_pole = ring == 0;
                let touches_south_pole = ring + 1 == rings;
                if !touches_north_pole {
                    indices.extend([here, next, under_next]);
                }
                if !touches_south_pole {
                    indices.extend([here, under_next, under]);
                }
            }
        }

        MeshData::new(vertices, indices)
    }
}

/// A square centered at `center`, facing `normal`; `right` and `up` set its
/// plane.
fn square(center: Vec3, normal: Vec3, right: Vec3, up: Vec3) -> [Vertex; 4] {
    let (right, up) = (right * HALF, up * HALF);
    [
        Vertex::new(center - right - up, normal, Vec2::new(0.0, 1.0)),
        Vertex::new(center + right - up, normal, Vec2::new(1.0, 1.0)),
        Vertex::new(center + right + up, normal, Vec2::new(1.0, 0.0)),
        Vertex::new(center - right + up, normal, Vec2::new(0.0, 0.0)),
    ]
}

fn square_mesh(normal: Vec3, right: Vec3, up: Vec3) -> MeshData {
    MeshData::new(
        square(Vec3::ZERO, normal, right, up).to_vec(),
        QUAD_INDICES.to_vec(),
    )
}