Skip to main content

engvis_core/
mesh.rs

1use crate::aabb::Aabb;
2
3/// GPU-ready vertex for PBR rendering
4#[repr(C)]
5#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
6pub struct MeshVertex {
7    pub position: [f32; 3],
8    pub normal: [f32; 3],
9    pub uv: [f32; 2],
10    pub tangent: [f32; 4],
11}
12
13impl MeshVertex {
14    pub const SIZE: usize = std::mem::size_of::<Self>();
15    pub const ATTRIB_COUNT: usize = 4;
16}
17
18/// A sub-mesh references a material index
19#[derive(Debug, Clone)]
20pub struct SubMesh {
21    pub material_index: usize,
22    pub index_offset: u32,
23    pub index_count: u32,
24}
25
26/// A complete mesh with vertices and indices
27#[derive(Debug, Clone)]
28pub struct Mesh {
29    pub name: String,
30    pub vertices: Vec<MeshVertex>,
31    pub indices: Vec<u32>,
32    pub sub_meshes: Vec<SubMesh>,
33    pub aabb: Aabb,
34}
35
36impl Mesh {
37    /// Extract edge indices from triangle indices for edge (line) rendering.
38    /// For each triangle (i0, i1, i2), generates 3 edges: (i0, i1), (i1, i2), (i2, i0).
39    pub fn extract_edge_indices(&self) -> Vec<u32> {
40        let mut edge_indices = Vec::with_capacity(self.indices.len() * 2);
41        for tri in self.indices.chunks(3) {
42            if tri.len() == 3 {
43                edge_indices.extend_from_slice(&[tri[0], tri[1], tri[1], tri[2], tri[2], tri[0]]);
44            }
45        }
46        edge_indices
47    }
48
49    /// Create a smooth-shaded mesh from flat position and index arrays.
50    ///
51    /// Computes smooth vertex normals (area-weighted), zero UVs and a default tangent.
52    pub fn from_triangles(
53        name: impl Into<String>,
54        positions: &[[f32; 3]],
55        indices: &[u32],
56    ) -> Self {
57        let n_verts = positions.len();
58        let mut smooth_normals = vec![[0.0_f32; 3]; n_verts];
59
60        for tri in indices.chunks_exact(3) {
61            let i0 = tri[0] as usize;
62            let i1 = tri[1] as usize;
63            let i2 = tri[2] as usize;
64            let p0 = glam::Vec3::from(positions[i0]);
65            let p1 = glam::Vec3::from(positions[i1]);
66            let p2 = glam::Vec3::from(positions[i2]);
67            let n = (p1 - p0).cross(p2 - p0);
68            for &i in &[i0, i1, i2] {
69                smooth_normals[i][0] += n.x;
70                smooth_normals[i][1] += n.y;
71                smooth_normals[i][2] += n.z;
72            }
73        }
74        for n in &mut smooth_normals {
75            let len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
76            if len > 1e-10 {
77                let inv = 1.0 / len;
78                n[0] *= inv; n[1] *= inv; n[2] *= inv;
79            } else { *n = [0.0, 1.0, 0.0]; }
80        }
81
82        let vertices: Vec<MeshVertex> = positions.iter().zip(smooth_normals.iter())
83            .map(|(pos, norm)| MeshVertex {
84                position: *pos, normal: *norm,
85                uv: [0.0, 0.0], tangent: [1.0, 0.0, 0.0, 1.0],
86            }).collect();
87
88        let mut aabb = crate::Aabb::empty();
89        for p in positions { aabb.expand(glam::Vec3::from(*p)); }
90
91        let index_count = indices.len() as u32;
92        Mesh {
93            name: name.into(),
94            vertices,
95            indices: indices.to_vec(),
96            sub_meshes: vec![SubMesh { material_index: 0, index_offset: 0, index_count }],
97            aabb,
98        }
99    }
100}
101
102/// Create a unit cube mesh with normals, UVs, and tangents
103pub fn create_cube_mesh() -> Mesh {
104    let mut vertices = Vec::new();
105    let mut indices = Vec::new();
106    let mut aabb = Aabb::empty();
107
108    // Each face: 4 vertices with position, normal, uv, tangent
109    let faces: [([f32; 3], [f32; 3], [f32; 4]); 6] = [
110        // +Y (top)
111        ([0.0, 1.0, 0.0], [0.0, 1.0, 0.0], [1.0, 0.0, 0.0, 1.0]),
112        // -Y (bottom)
113        ([0.0, -1.0, 0.0], [0.0, -1.0, 0.0], [-1.0, 0.0, 0.0, 1.0]),
114        // +X (right)
115        ([1.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, -1.0, 1.0]),
116        // -X (left)
117        ([-1.0, 0.0, 0.0], [-1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 1.0]),
118        // +Z (front)
119        ([0.0, 0.0, 1.0], [0.0, 0.0, 1.0], [1.0, 0.0, 0.0, 1.0]),
120        // -Z (back)
121        ([0.0, 0.0, -1.0], [0.0, 0.0, -1.0], [-1.0, 0.0, 0.0, 1.0]),
122    ];
123
124    let face_vertices: [[([f32; 3], [f32; 2]); 4]; 6] = [
125        // +Y top
126        [
127            ([-0.5, 0.5, -0.5], [0.0, 0.0]),
128            ([0.5, 0.5, -0.5], [1.0, 0.0]),
129            ([0.5, 0.5, 0.5], [1.0, 1.0]),
130            ([-0.5, 0.5, 0.5], [0.0, 1.0]),
131        ],
132        // -Y bottom
133        [
134            ([-0.5, -0.5, 0.5], [0.0, 0.0]),
135            ([0.5, -0.5, 0.5], [1.0, 0.0]),
136            ([0.5, -0.5, -0.5], [1.0, 1.0]),
137            ([-0.5, -0.5, -0.5], [0.0, 1.0]),
138        ],
139        // +X right
140        [
141            ([0.5, -0.5, -0.5], [0.0, 0.0]),
142            ([0.5, -0.5, 0.5], [1.0, 0.0]),
143            ([0.5, 0.5, 0.5], [1.0, 1.0]),
144            ([0.5, 0.5, -0.5], [0.0, 1.0]),
145        ],
146        // -X left
147        [
148            ([-0.5, -0.5, 0.5], [0.0, 0.0]),
149            ([-0.5, -0.5, -0.5], [1.0, 0.0]),
150            ([-0.5, 0.5, -0.5], [1.0, 1.0]),
151            ([-0.5, 0.5, 0.5], [0.0, 1.0]),
152        ],
153        // +Z front
154        [
155            ([0.5, -0.5, 0.5], [0.0, 0.0]),
156            ([-0.5, -0.5, 0.5], [1.0, 0.0]),
157            ([-0.5, 0.5, 0.5], [1.0, 1.0]),
158            ([0.5, 0.5, 0.5], [0.0, 1.0]),
159        ],
160        // -Z back
161        [
162            ([-0.5, -0.5, -0.5], [0.0, 0.0]),
163            ([0.5, -0.5, -0.5], [1.0, 0.0]),
164            ([0.5, 0.5, -0.5], [1.0, 1.0]),
165            ([-0.5, 0.5, -0.5], [0.0, 1.0]),
166        ],
167    ];
168
169    for (face_idx, (normal, _, tangent)) in faces.iter().enumerate() {
170        let base = vertices.len() as u32;
171        for (pos, uv) in &face_vertices[face_idx] {
172            vertices.push(MeshVertex {
173                position: *pos,
174                normal: *normal,
175                uv: *uv,
176                tangent: *tangent,
177            });
178            aabb.expand(glam::Vec3::from(*pos));
179        }
180        indices.extend_from_slice(&[
181            base, base + 2, base + 1,
182            base, base + 3, base + 2,
183        ]);
184    }
185
186    let index_count = indices.len() as u32;
187    Mesh {
188        name: "Cube".to_string(),
189        vertices,
190        indices,
191        sub_meshes: vec![SubMesh {
192            material_index: 0,
193            index_offset: 0,
194            index_count,
195        }],
196        aabb,
197    }
198}