Skip to main content

mirage_engine/mesh/
primitives.rs

1use crate::math::{Vec2, Vec3};
2use crate::mesh::{Mesh, MeshData, Vertex};
3use crate::{Assets, Catalog};
4
5const HALF: f32 = 0.5;
6
7const QUAD_INDICES: [u32; 6] = [0, 1, 2, 0, 2, 3];
8
9/// A one-meter cube centered on the origin.
10#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
11pub struct Cube;
12
13impl Catalog for Cube {
14    fn catalog() -> Vec<Self> {
15        vec![Self]
16    }
17}
18
19impl Mesh for Cube {
20    fn build(&self, _assets: &Assets) -> MeshData {
21        let faces = [
22            (Vec3::X, Vec3::NEG_Z, Vec3::Y),
23            (Vec3::NEG_X, Vec3::Z, Vec3::Y),
24            (Vec3::Y, Vec3::X, Vec3::NEG_Z),
25            (Vec3::NEG_Y, Vec3::X, Vec3::Z),
26            (Vec3::Z, Vec3::X, Vec3::Y),
27            (Vec3::NEG_Z, Vec3::NEG_X, Vec3::Y),
28        ];
29
30        let vertices = faces
31            .iter()
32            .flat_map(|&(normal, right, up)| square(normal * HALF, normal, right, up))
33            .collect();
34        let indices = (0..faces.len() as u32)
35            .flat_map(|face| QUAD_INDICES.map(|index| face * 4 + index))
36            .collect();
37
38        MeshData::new(vertices, indices)
39    }
40}
41
42/// A one-meter square in the XZ plane, facing up — a surface under a scene.
43#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
44pub struct Plane;
45
46impl Catalog for Plane {
47    fn catalog() -> Vec<Self> {
48        vec![Self]
49    }
50}
51
52impl Mesh for Plane {
53    fn build(&self, _assets: &Assets) -> MeshData {
54        square_mesh(Vec3::Y, Vec3::X, Vec3::NEG_Z)
55    }
56}
57
58/// A one-meter square in the XY plane, facing the default camera.
59#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
60pub struct Quad;
61
62impl Catalog for Quad {
63    fn catalog() -> Vec<Self> {
64        vec![Self]
65    }
66}
67
68impl Mesh for Quad {
69    fn build(&self, _assets: &Assets) -> MeshData {
70        square_mesh(Vec3::Z, Vec3::X, Vec3::Y)
71    }
72}
73
74/// A one-meter sphere centered on the origin.
75#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
76pub struct Sphere {
77    /// The sphere mesh's density: `4 * (subdivisions + 1)` parts go around
78    /// it, and `2 * (subdivisions + 1)` go pole to pole.
79    pub subdivisions: u32,
80}
81
82impl Catalog for Sphere {
83    /// The values `0..=3`, which are built at startup; a sphere of any other
84    /// [`subdivisions`](Sphere::subdivisions) is built on its first draw and
85    /// cached like any other mesh.
86    fn catalog() -> Vec<Self> {
87        (0..=3).map(|subdivisions| Self { subdivisions }).collect()
88    }
89}
90
91impl Mesh for Sphere {
92    fn build(&self, _assets: &Assets) -> MeshData {
93        let segments = 4 * (self.subdivisions + 1);
94        let rings = 2 * (self.subdivisions + 1);
95
96        let mut vertices = Vec::with_capacity(((rings + 1) * (segments + 1)) as usize);
97        for ring in 0..=rings {
98            let latitude = core::f32::consts::PI * ring as f32 / rings as f32;
99            let (radius, height) = latitude.sin_cos();
100            for segment in 0..=segments {
101                let longitude = core::f32::consts::TAU * segment as f32 / segments as f32;
102                let (ahead, right) = longitude.sin_cos();
103                let normal = Vec3::new(radius * right, height, radius * ahead);
104                vertices.push(Vertex::new(
105                    normal * HALF,
106                    normal,
107                    Vec2::new(segment as f32 / segments as f32, ring as f32 / rings as f32),
108                ));
109            }
110        }
111
112        let corner = |ring: u32, segment: u32| ring * (segments + 1) + segment;
113        let mut indices = Vec::with_capacity((rings * segments * 6) as usize);
114        for ring in 0..rings {
115            for segment in 0..segments {
116                let (here, next) = (corner(ring, segment), corner(ring, segment + 1));
117                let (under, under_next) =
118                    (corner(ring + 1, segment), corner(ring + 1, segment + 1));
119
120                let touches_north_pole = ring == 0;
121                let touches_south_pole = ring + 1 == rings;
122                if !touches_north_pole {
123                    indices.extend([here, next, under_next]);
124                }
125                if !touches_south_pole {
126                    indices.extend([here, under_next, under]);
127                }
128            }
129        }
130
131        MeshData::new(vertices, indices)
132    }
133}
134
135/// A square centered at `center`, facing `normal`; `right` and `up` set its
136/// plane.
137fn square(center: Vec3, normal: Vec3, right: Vec3, up: Vec3) -> [Vertex; 4] {
138    let (right, up) = (right * HALF, up * HALF);
139    [
140        Vertex::new(center - right - up, normal, Vec2::new(0.0, 1.0)),
141        Vertex::new(center + right - up, normal, Vec2::new(1.0, 1.0)),
142        Vertex::new(center + right + up, normal, Vec2::new(1.0, 0.0)),
143        Vertex::new(center - right + up, normal, Vec2::new(0.0, 0.0)),
144    ]
145}
146
147fn square_mesh(normal: Vec3, right: Vec3, up: Vec3) -> MeshData {
148    MeshData::new(
149        square(Vec3::ZERO, normal, right, up).to_vec(),
150        QUAD_INDICES.to_vec(),
151    )
152}