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(
51        &mut self,
52        tangent_space: Option<bevy_mikktspace::TangentSpace>,
53        face: usize,
54        vert: usize,
55    ) {
56        let idx = self.index(face, vert);
57        self.tangents[idx] = tangent_space.unwrap_or_default().tangent_encoded();
58    }
59}
60
61#[derive(Error, Debug)]
62/// Failed to generate tangents for the mesh.
63pub enum GenerateTangentsError {
64    #[error("cannot generate tangents for {0:?}")]
65    UnsupportedTopology(PrimitiveTopology),
66    #[error("missing indices")]
67    MissingIndices,
68    #[error("missing vertex attributes '{0}'")]
69    MissingVertexAttribute(&'static str),
70    #[error("the '{0}' vertex attribute should have {1:?} format")]
71    InvalidVertexAttributeFormat(&'static str, VertexFormat),
72    #[error("mesh not suitable for tangent generation")]
73    MikktspaceError(#[from] bevy_mikktspace::GenerateTangentSpaceError),
74}
75
76pub(crate) fn generate_tangents_for_mesh(
77    mesh: &Mesh,
78) -> Result<Vec<[f32; 4]>, GenerateTangentsError> {
79    match mesh.primitive_topology() {
80        PrimitiveTopology::TriangleList => {}
81        other => return Err(GenerateTangentsError::UnsupportedTopology(other)),
82    };
83
84    let positions = mesh.attribute(Mesh::ATTRIBUTE_POSITION).ok_or(
85        GenerateTangentsError::MissingVertexAttribute(Mesh::ATTRIBUTE_POSITION.name),
86    )?;
87    let VertexAttributeValues::Float32x3(positions) = positions else {
88        return Err(GenerateTangentsError::InvalidVertexAttributeFormat(
89            Mesh::ATTRIBUTE_POSITION.name,
90            VertexFormat::Float32x3,
91        ));
92    };
93    let normals = mesh.attribute(Mesh::ATTRIBUTE_NORMAL).ok_or(
94        GenerateTangentsError::MissingVertexAttribute(Mesh::ATTRIBUTE_NORMAL.name),
95    )?;
96    let VertexAttributeValues::Float32x3(normals) = normals else {
97        return Err(GenerateTangentsError::InvalidVertexAttributeFormat(
98            Mesh::ATTRIBUTE_NORMAL.name,
99            VertexFormat::Float32x3,
100        ));
101    };
102    let uvs = mesh.attribute(Mesh::ATTRIBUTE_UV_0).ok_or(
103        GenerateTangentsError::MissingVertexAttribute(Mesh::ATTRIBUTE_UV_0.name),
104    )?;
105    let VertexAttributeValues::Float32x2(uvs) = uvs else {
106        return Err(GenerateTangentsError::InvalidVertexAttributeFormat(
107            Mesh::ATTRIBUTE_UV_0.name,
108            VertexFormat::Float32x2,
109        ));
110    };
111
112    let len = positions.len();
113    let tangents = vec![[0., 0., 0., 0.]; len];
114    let mut mikktspace_mesh = MikktspaceGeometryHelper {
115        indices: mesh.indices(),
116        positions,
117        normals,
118        uvs,
119        tangents,
120    };
121    bevy_mikktspace::generate_tangents(&mut mikktspace_mesh)?;
122
123    // mikktspace seems to assume left-handedness so we can flip the sign to correct for this
124    for tangent in &mut mikktspace_mesh.tangents {
125        tangent[3] = -tangent[3];
126    }
127
128    Ok(mikktspace_mesh.tangents)
129}
130
131/// Correctly scales and renormalizes an already normalized `normal` by the scale determined by its reciprocal `scale_recip`
132pub(crate) fn scale_normal(normal: Vec3, scale_recip: Vec3) -> Vec3 {
133    // This is basically just `normal * scale_recip` but with the added rule that `0. * anything == 0.`
134    // This is necessary because components of `scale_recip` may be infinities, which do not multiply to zero
135    let n = Vec3::select(normal.cmpeq(Vec3::ZERO), Vec3::ZERO, normal * scale_recip);
136
137    // If n is finite, no component of `scale_recip` was infinite or the normal was perpendicular to the scale
138    // else the scale had at least one zero-component and the normal needs to point along the direction of that component
139    if n.is_finite() {
140        n.normalize_or_zero()
141    } else {
142        Vec3::select(n.abs().cmpeq(Vec3::INFINITY), n.signum(), Vec3::ZERO).normalize()
143    }
144}