bevy_mesh/
mikktspace.rs

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