bevy_mesh/
mikktspace.rs

1use crate::MeshAccessError;
2
3use super::{Indices, Mesh, VertexAttributeValues};
4use thiserror::Error;
5use wgpu_types::{PrimitiveTopology, VertexFormat};
6
7struct MikktspaceGeometryHelper<'a> {
8    indices: Option<&'a Indices>,
9    positions: &'a Vec<[f32; 3]>,
10    normals: &'a Vec<[f32; 3]>,
11    uvs: &'a Vec<[f32; 2]>,
12    tangents: Vec<[f32; 4]>,
13}
14
15impl MikktspaceGeometryHelper<'_> {
16    fn index(&self, face: usize, vert: usize) -> usize {
17        let index_index = face * 3 + vert;
18
19        match self.indices {
20            Some(Indices::U16(indices)) => indices[index_index] as usize,
21            Some(Indices::U32(indices)) => indices[index_index] as usize,
22            None => index_index,
23        }
24    }
25}
26
27impl bevy_mikktspace::Geometry for MikktspaceGeometryHelper<'_> {
28    fn num_faces(&self) -> usize {
29        self.indices
30            .map(Indices::len)
31            .unwrap_or_else(|| self.positions.len())
32            / 3
33    }
34
35    fn num_vertices_of_face(&self, _: usize) -> usize {
36        3
37    }
38
39    fn position(&self, face: usize, vert: usize) -> [f32; 3] {
40        self.positions[self.index(face, vert)]
41    }
42
43    fn normal(&self, face: usize, vert: usize) -> [f32; 3] {
44        self.normals[self.index(face, vert)]
45    }
46
47    fn tex_coord(&self, face: usize, vert: usize) -> [f32; 2] {
48        self.uvs[self.index(face, vert)]
49    }
50
51    fn set_tangent(
52        &mut self,
53        tangent_space: Option<bevy_mikktspace::TangentSpace>,
54        face: usize,
55        vert: usize,
56    ) {
57        let idx = self.index(face, vert);
58        self.tangents[idx] = tangent_space.unwrap_or_default().tangent_encoded();
59    }
60}
61
62#[derive(Error, Debug)]
63/// Failed to generate tangents for the mesh.
64pub enum GenerateTangentsError {
65    #[error("cannot generate tangents for {0:?}")]
66    UnsupportedTopology(PrimitiveTopology),
67    #[error("missing indices")]
68    MissingIndices,
69    #[error("missing vertex attributes '{0}'")]
70    MissingVertexAttribute(&'static str),
71    #[error("the '{0}' vertex attribute should have {1:?} format")]
72    InvalidVertexAttributeFormat(&'static str, VertexFormat),
73    #[error("mesh not suitable for tangent generation")]
74    MikktspaceError(#[from] bevy_mikktspace::GenerateTangentSpaceError),
75    #[error("Mesh access error: {0}")]
76    MeshAccessError(#[from] MeshAccessError),
77}
78
79pub(crate) fn generate_tangents_for_mesh(
80    mesh: &Mesh,
81) -> Result<Vec<[f32; 4]>, GenerateTangentsError> {
82    match mesh.primitive_topology() {
83        PrimitiveTopology::TriangleList => {}
84        other => return Err(GenerateTangentsError::UnsupportedTopology(other)),
85    };
86
87    let positions = mesh.try_attribute_option(Mesh::ATTRIBUTE_POSITION)?.ok_or(
88        GenerateTangentsError::MissingVertexAttribute(Mesh::ATTRIBUTE_POSITION.name),
89    )?;
90    let VertexAttributeValues::Float32x3(positions) = positions else {
91        return Err(GenerateTangentsError::InvalidVertexAttributeFormat(
92            Mesh::ATTRIBUTE_POSITION.name,
93            VertexFormat::Float32x3,
94        ));
95    };
96    let normals = mesh.try_attribute_option(Mesh::ATTRIBUTE_NORMAL)?.ok_or(
97        GenerateTangentsError::MissingVertexAttribute(Mesh::ATTRIBUTE_NORMAL.name),
98    )?;
99    let VertexAttributeValues::Float32x3(normals) = normals else {
100        return Err(GenerateTangentsError::InvalidVertexAttributeFormat(
101            Mesh::ATTRIBUTE_NORMAL.name,
102            VertexFormat::Float32x3,
103        ));
104    };
105    let uvs = mesh.try_attribute_option(Mesh::ATTRIBUTE_UV_0)?.ok_or(
106        GenerateTangentsError::MissingVertexAttribute(Mesh::ATTRIBUTE_UV_0.name),
107    )?;
108    let VertexAttributeValues::Float32x2(uvs) = uvs else {
109        return Err(GenerateTangentsError::InvalidVertexAttributeFormat(
110            Mesh::ATTRIBUTE_UV_0.name,
111            VertexFormat::Float32x2,
112        ));
113    };
114
115    let len = positions.len();
116    let tangents = vec![[0., 0., 0., 0.]; len];
117    let mut mikktspace_mesh = MikktspaceGeometryHelper {
118        indices: mesh.try_indices_option()?,
119        positions,
120        normals,
121        uvs,
122        tangents,
123    };
124    bevy_mikktspace::generate_tangents(&mut mikktspace_mesh)?;
125
126    // mikktspace seems to assume left-handedness so we can flip the sign to correct for this
127    for tangent in &mut mikktspace_mesh.tangents {
128        tangent[3] = -tangent[3];
129    }
130
131    Ok(mikktspace_mesh.tangents)
132}