Skip to main content

agpu/scene3d/
mesh.rs

1//! Mesh — triangle-mesh geometry with 3D vertices.
2
3/// A 3D vertex with position, normal, and texture coordinates.
4#[derive(Debug, Clone, Copy)]
5pub struct Vertex3D {
6    pub position: [f32; 3],
7    pub normal: [f32; 3],
8    pub uv: [f32; 2],
9}
10
11impl Default for Vertex3D {
12    fn default() -> Self {
13        Self {
14            position: [0.0, 0.0, 0.0],
15            normal: [0.0, 0.0, 1.0],
16            uv: [0.0, 0.0],
17        }
18    }
19}
20
21impl Vertex3D {
22    pub fn new(position: [f32; 3], normal: [f32; 3], uv: [f32; 2]) -> Self {
23        Self {
24            position,
25            normal,
26            uv,
27        }
28    }
29}
30
31/// A triangle mesh with indexed geometry.
32pub struct Mesh {
33    vertices: Vec<Vertex3D>,
34    indices: Vec<u32>,
35}
36
37impl Mesh {
38    pub fn new(vertices: Vec<Vertex3D>, indices: Vec<u32>) -> Self {
39        Self { vertices, indices }
40    }
41
42    pub fn vertices(&self) -> &[Vertex3D] {
43        &self.vertices
44    }
45
46    pub fn indices(&self) -> &[u32] {
47        &self.indices
48    }
49
50    /// Generate a unit cube centred at the origin.
51    pub fn cube(size: f32) -> Self {
52        let h = size / 2.0;
53        let mut vertices = Vec::with_capacity(24);
54        let mut indices = Vec::with_capacity(36);
55
56        // Six faces, each with 4 vertices and 2 triangles
57        type CubeFace = ([f32; 3], [[f32; 3]; 4], [[f32; 2]; 4]);
58        let faces: [CubeFace; 6] = [
59            // Front (+Z)
60            (
61                [0.0, 0.0, 1.0],
62                [[-h, -h, h], [h, -h, h], [h, h, h], [-h, h, h]],
63                [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]],
64            ),
65            // Back (-Z)
66            (
67                [0.0, 0.0, -1.0],
68                [[h, -h, -h], [-h, -h, -h], [-h, h, -h], [h, h, -h]],
69                [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]],
70            ),
71            // Right (+X)
72            (
73                [1.0, 0.0, 0.0],
74                [[h, -h, h], [h, -h, -h], [h, h, -h], [h, h, h]],
75                [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]],
76            ),
77            // Left (-X)
78            (
79                [-1.0, 0.0, 0.0],
80                [[-h, -h, -h], [-h, -h, h], [-h, h, h], [-h, h, -h]],
81                [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]],
82            ),
83            // Top (+Y)
84            (
85                [0.0, 1.0, 0.0],
86                [[-h, h, h], [h, h, h], [h, h, -h], [-h, h, -h]],
87                [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]],
88            ),
89            // Bottom (-Y)
90            (
91                [0.0, -1.0, 0.0],
92                [[-h, -h, -h], [h, -h, -h], [h, -h, h], [-h, -h, h]],
93                [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]],
94            ),
95        ];
96
97        for (normal, positions, uvs) in &faces {
98            let base = vertices.len() as u32;
99            for i in 0..4 {
100                vertices.push(Vertex3D::new(positions[i], *normal, uvs[i]));
101            }
102            indices.extend_from_slice(&[base, base + 1, base + 2, base, base + 2, base + 3]);
103        }
104
105        Self { vertices, indices }
106    }
107
108    /// Generate a flat plane on the XZ plane, centred at the origin.
109    pub fn plane(width: f32, depth: f32) -> Self {
110        let hw = width / 2.0;
111        let hd = depth / 2.0;
112        let vertices = vec![
113            Vertex3D::new([-hw, 0.0, -hd], [0.0, 1.0, 0.0], [0.0, 0.0]),
114            Vertex3D::new([hw, 0.0, -hd], [0.0, 1.0, 0.0], [1.0, 0.0]),
115            Vertex3D::new([hw, 0.0, hd], [0.0, 1.0, 0.0], [1.0, 1.0]),
116            Vertex3D::new([-hw, 0.0, hd], [0.0, 1.0, 0.0], [0.0, 1.0]),
117        ];
118        let indices = vec![0, 1, 2, 0, 2, 3];
119        Self { vertices, indices }
120    }
121}