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)]
57pub 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 for tangent in &mut mikktspace_mesh.tangents {
123 tangent[3] = -tangent[3];
124 }
125
126 Ok(mikktspace_mesh.tangents)
127}
128
129pub(crate) fn scale_normal(normal: Vec3, scale_recip: Vec3) -> Vec3 {
131 let n = Vec3::select(normal.cmpeq(Vec3::ZERO), Vec3::ZERO, normal * scale_recip);
134
135 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}