bevy_mesh/
mikktspace.rs

1use super::{Indices, Mesh, VertexAttributeValues};
2use bevy_math::Vec3;
3use thiserror::Error;
4use wgpu_types::{PrimitiveTopology, VertexFormat};
5
6struct MikktspaceGeometryHelper<'a> {
7    indices: Option<&'a Indices>,
8    positions: &'a Vec<[f32; 3]>,
9    normals: &'a Vec<[f32; 3]>,
10    uvs: &'a Vec<[f32; 2]>,
11    tangents: Vec<[f32; 4]>,
12}
13
14impl MikktspaceGeometryHelper<'_> {
15    fn index(&self, face: usize, vert: usize) -> usize {
16        let index_index = face * 3 + vert;
17
18        match self.indices {
19            Some(Indices::U16(indices)) => indices[index_index] as usize,
20            Some(Indices::U32(indices)) => indices[index_index] as usize,
21            None => index_index,
22        }
23    }
24}
25
26impl bevy_mikktspace::Geometry for MikktspaceGeometryHelper<'_> {
27    fn num_faces(&self) -> usize {
28        self.indices
29            .map(Indices::len)
30            .unwrap_or_else(|| self.positions.len())
31            / 3
32    }
33
34    fn num_vertices_of_face(&self, _: usize) -> usize {
35        3
36    }
37
38    fn position(&self, face: usize, vert: usize) -> [f32; 3] {
39        self.positions[self.index(face, vert)]
40    }
41
42    fn normal(&self, face: usize, vert: usize) -> [f32; 3] {
43        self.normals[self.index(face, vert)]
44    }
45
46    fn tex_coord(&self, face: usize, vert: usize) -> [f32; 2] {
47        self.uvs[self.index(face, vert)]
48    }
49
50    fn set_tangent_encoded(&mut self, tangent: [f32; 4], face: usize, vert: usize) {
51        let idx = self.index(face, vert);
52        self.tangents[idx] = tangent;
53    }
54}
55
56#[derive(Error, Debug)]
57/// Failed to generate tangents for the mesh.
58pub enum GenerateTangentsError {
59    #[error("cannot generate tangents for {0:?}")]
60    UnsupportedTopology(PrimitiveTopology),
61    #[error("missing indices")]
62    MissingIndices,
63    #[error("missing vertex attributes '{0}'")]
64    MissingVertexAttribute(&'static str),
65    #[error("the '{0}' vertex attribute should have {1:?} format")]
66    InvalidVertexAttributeFormat(&'static str, VertexFormat),
67    #[error("mesh not suitable for tangent generation")]
68    MikktspaceError,
69}
70
71pub(crate) fn generate_tangents_for_mesh(
72    mesh: &Mesh,
73) -> Result<Vec<[f32; 4]>, GenerateTangentsError> {
74    match mesh.primitive_topology() {
75        PrimitiveTopology::TriangleList => {}
76        other => return Err(GenerateTangentsError::UnsupportedTopology(other)),
77    };
78
79    let positions = mesh.attribute(Mesh::ATTRIBUTE_POSITION).ok_or(
80        GenerateTangentsError::MissingVertexAttribute(Mesh::ATTRIBUTE_POSITION.name),
81    )?;
82    let VertexAttributeValues::Float32x3(positions) = positions else {
83        return Err(GenerateTangentsError::InvalidVertexAttributeFormat(
84            Mesh::ATTRIBUTE_POSITION.name,
85            VertexFormat::Float32x3,
86        ));
87    };
88    let normals = mesh.attribute(Mesh::ATTRIBUTE_NORMAL).ok_or(
89        GenerateTangentsError::MissingVertexAttribute(Mesh::ATTRIBUTE_NORMAL.name),
90    )?;
91    let VertexAttributeValues::Float32x3(normals) = normals else {
92        return Err(GenerateTangentsError::InvalidVertexAttributeFormat(
93            Mesh::ATTRIBUTE_NORMAL.name,
94            VertexFormat::Float32x3,
95        ));
96    };
97    let uvs = mesh.attribute(Mesh::ATTRIBUTE_UV_0).ok_or(
98        GenerateTangentsError::MissingVertexAttribute(Mesh::ATTRIBUTE_UV_0.name),
99    )?;
100    let VertexAttributeValues::Float32x2(uvs) = uvs else {
101        return Err(GenerateTangentsError::InvalidVertexAttributeFormat(
102            Mesh::ATTRIBUTE_UV_0.name,
103            VertexFormat::Float32x2,
104        ));
105    };
106
107    let len = positions.len();
108    let tangents = vec![[0., 0., 0., 0.]; len];
109    let mut mikktspace_mesh = MikktspaceGeometryHelper {
110        indices: mesh.indices(),
111        positions,
112        normals,
113        uvs,
114        tangents,
115    };
116    let success = bevy_mikktspace::generate_tangents(&mut mikktspace_mesh);
117    if !success {
118        return Err(GenerateTangentsError::MikktspaceError);
119    }
120
121    // mikktspace seems to assume left-handedness so we can flip the sign to correct for this
122    for tangent in &mut mikktspace_mesh.tangents {
123        tangent[3] = -tangent[3];
124    }
125
126    Ok(mikktspace_mesh.tangents)
127}
128
129/// Correctly scales and renormalizes an already normalized `normal` by the scale determined by its reciprocal `scale_recip`
130pub(crate) fn scale_normal(normal: Vec3, scale_recip: Vec3) -> Vec3 {
131    // This is basically just `normal * scale_recip` but with the added rule that `0. * anything == 0.`
132    // This is necessary because components of `scale_recip` may be infinities, which do not multiply to zero
133    let n = Vec3::select(normal.cmpeq(Vec3::ZERO), Vec3::ZERO, normal * scale_recip);
134
135    // If n is finite, no component of `scale_recip` was infinite or the normal was perpendicular to the scale
136    // else the scale had at least one zero-component and the normal needs to point along the direction of that component
137    if n.is_finite() {
138        n.normalize_or_zero()
139    } else {
140        Vec3::select(n.abs().cmpeq(Vec3::INFINITY), n.signum(), Vec3::ZERO).normalize()
141    }
142}