Skip to content

Commit af5dd35

Browse files
committed
Fix merge conflict
1 parent 16dacb0 commit af5dd35

File tree

15 files changed

+347
-45
lines changed

15 files changed

+347
-45
lines changed

crates/bevy_gltf/src/lib.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,10 @@ use bevy_platform_support::collections::HashMap;
102102
use bevy_app::prelude::*;
103103
use bevy_asset::AssetApp;
104104
use bevy_image::CompressedImageFormats;
105-
use bevy_render::{mesh::MeshVertexAttribute, renderer::RenderDevice};
105+
use bevy_render::{
106+
mesh::{MeshVertexAttribute, TangentStrategy},
107+
renderer::RenderDevice,
108+
};
106109

107110
/// The glTF prelude.
108111
///
@@ -118,6 +121,8 @@ pub use {assets::*, label::GltfAssetLabel, loader::*};
118121
#[derive(Default)]
119122
pub struct GltfPlugin {
120123
custom_vertex_attributes: HashMap<Box<str>, MeshVertexAttribute>,
124+
/// The strategy to use when computing mesh tangents.
125+
pub computed_tangent_strategy: TangentStrategy,
121126
}
122127

123128
impl GltfPlugin {
@@ -134,6 +139,12 @@ impl GltfPlugin {
134139
self.custom_vertex_attributes.insert(name.into(), attribute);
135140
self
136141
}
142+
143+
/// The strategy to use when computing mesh tangents.
144+
pub fn with_computed_tangent_strategy(mut self, tangent_strategy: TangentStrategy) -> Self {
145+
self.computed_tangent_strategy = tangent_strategy;
146+
self
147+
}
137148
}
138149

139150
impl Plugin for GltfPlugin {
@@ -159,6 +170,7 @@ impl Plugin for GltfPlugin {
159170
app.register_asset_loader(GltfLoader {
160171
supported_compressed_formats,
161172
custom_vertex_attributes: self.custom_vertex_attributes.clone(),
173+
computed_tangent_strategy: self.computed_tangent_strategy,
162174
});
163175
}
164176
}

crates/bevy_gltf/src/loader/mod.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ use bevy_render::{
3535
mesh::{
3636
morph::{MeshMorphWeights, MorphAttributes, MorphTargetImage, MorphWeights},
3737
skinning::{SkinnedMesh, SkinnedMeshInverseBindposes},
38-
Indices, Mesh, Mesh3d, MeshVertexAttribute, VertexAttributeValues,
38+
Indices, Mesh, Mesh3d, MeshVertexAttribute, TangentStrategy, VertexAttributeValues,
3939
},
4040
primitives::Aabb,
4141
render_asset::RenderAssetUsages,
@@ -145,6 +145,8 @@ pub struct GltfLoader {
145145
/// See [this section of the glTF specification](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes-overview)
146146
/// for additional details on custom attributes.
147147
pub custom_vertex_attributes: HashMap<Box<str>, MeshVertexAttribute>,
148+
/// The strategy to use when computing mesh tangents.
149+
pub computed_tangent_strategy: TangentStrategy,
148150
}
149151

150152
/// Specifies optional settings for processing gltfs at load time. By default, all recognized contents of
@@ -684,16 +686,17 @@ async fn load_gltf<'a, 'b, 'c>(
684686
&& needs_tangents(&primitive.material())
685687
{
686688
tracing::debug!(
687-
"Missing vertex tangents for {}, computing them using the mikktspace algorithm. Consider using a tool such as Blender to pre-compute the tangents.", file_name
689+
"Missing vertex tangents for {}, computing them using the {:?} strategy. Consider using a tool such as Blender to pre-compute the tangents.",
690+
file_name, loader.computed_tangent_strategy
688691
);
689692

690-
let generate_tangents_span = info_span!("generate_tangents", name = file_name);
693+
let generate_tangents_span = info_span!("compute_tangents", name = file_name);
691694

692695
generate_tangents_span.in_scope(|| {
693-
if let Err(err) = mesh.generate_tangents() {
696+
if let Err(err) = mesh.compute_tangents(loader.computed_tangent_strategy) {
694697
warn!(
695-
"Failed to generate vertex tangents using the mikktspace algorithm: {}",
696-
err
698+
"Failed to generate vertex tangents using {:?}: {}",
699+
loader.computed_tangent_strategy, err
697700
);
698701
}
699702
});
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
use super::{GenerateTangentsError, Mesh};
2+
use bevy_math::{Vec2, Vec3A, Vec4};
3+
use wgpu_types::{PrimitiveTopology, VertexFormat};
4+
5+
struct TriangleIndexIter<'a, I>(&'a mut I);
6+
7+
impl<'a, I> Iterator for TriangleIndexIter<'a, I>
8+
where
9+
I: Iterator<Item = usize>,
10+
{
11+
type Item = [usize; 3];
12+
fn next(&mut self) -> Option<[usize; 3]> {
13+
let i = &mut self.0;
14+
match (i.next(), i.next(), i.next()) {
15+
(Some(i1), Some(i2), Some(i3)) => Some([i1, i2, i3]),
16+
_ => None,
17+
}
18+
}
19+
}
20+
21+
pub(crate) fn generate_tangents_for_mesh(
22+
mesh: &Mesh,
23+
) -> Result<Vec<[f32; 4]>, GenerateTangentsError> {
24+
let positions = mesh.attribute(Mesh::ATTRIBUTE_POSITION);
25+
let normals = mesh.attribute(Mesh::ATTRIBUTE_NORMAL);
26+
let uvs = mesh.attribute(Mesh::ATTRIBUTE_UV_0);
27+
let indices = mesh.indices();
28+
let primitive_topology = mesh.primitive_topology();
29+
30+
if primitive_topology != PrimitiveTopology::TriangleList {
31+
return Err(GenerateTangentsError::UnsupportedTopology(
32+
primitive_topology,
33+
));
34+
}
35+
36+
match (positions, normals, uvs, indices) {
37+
(None, _, _, _) => Err(GenerateTangentsError::MissingVertexAttribute(
38+
Mesh::ATTRIBUTE_POSITION.name,
39+
)),
40+
(_, None, _, _) => Err(GenerateTangentsError::MissingVertexAttribute(
41+
Mesh::ATTRIBUTE_NORMAL.name,
42+
)),
43+
(_, _, None, _) => Err(GenerateTangentsError::MissingVertexAttribute(
44+
Mesh::ATTRIBUTE_UV_0.name,
45+
)),
46+
(_, _, _, None) => Err(GenerateTangentsError::MissingIndices),
47+
(Some(positions), Some(normals), Some(uvs), Some(indices)) => {
48+
let positions = positions.as_float3().ok_or(
49+
GenerateTangentsError::InvalidVertexAttributeFormat(
50+
Mesh::ATTRIBUTE_POSITION.name,
51+
VertexFormat::Float32x3,
52+
),
53+
)?;
54+
let normals =
55+
normals
56+
.as_float3()
57+
.ok_or(GenerateTangentsError::InvalidVertexAttributeFormat(
58+
Mesh::ATTRIBUTE_NORMAL.name,
59+
VertexFormat::Float32x3,
60+
))?;
61+
let uvs =
62+
uvs.as_float2()
63+
.ok_or(GenerateTangentsError::InvalidVertexAttributeFormat(
64+
Mesh::ATTRIBUTE_UV_0.name,
65+
VertexFormat::Float32x2,
66+
))?;
67+
let vertex_count = positions.len();
68+
let mut tangents = vec![Vec3A::ZERO; vertex_count];
69+
let mut bi_tangents = vec![Vec3A::ZERO; vertex_count];
70+
71+
for [i1, i2, i3] in TriangleIndexIter(&mut indices.iter()) {
72+
let v1 = Vec3A::from_array(positions[i1]);
73+
let v2 = Vec3A::from_array(positions[i2]);
74+
let v3 = Vec3A::from_array(positions[i3]);
75+
76+
let w1 = Vec2::from(uvs[i1]);
77+
let w2 = Vec2::from(uvs[i2]);
78+
let w3 = Vec2::from(uvs[i3]);
79+
80+
let delta_pos1 = v2 - v1;
81+
let delta_pos2 = v3 - v1;
82+
83+
let delta_uv1 = w2 - w1;
84+
let delta_uv2 = w3 - w1;
85+
86+
let determinant = delta_uv1.x * delta_uv2.y - delta_uv1.y * delta_uv2.x;
87+
88+
// check for degenerate triangles
89+
if determinant.abs() > 1e-6 {
90+
let r = 1.0 / determinant;
91+
let tangent = (delta_pos1 * delta_uv2.y - delta_pos2 * delta_uv1.y) * r;
92+
let bi_tangent = (delta_pos2 * delta_uv1.x - delta_pos1 * delta_uv2.x) * r;
93+
94+
tangents[i1] += tangent;
95+
tangents[i2] += tangent;
96+
tangents[i3] += tangent;
97+
98+
bi_tangents[i1] += bi_tangent;
99+
bi_tangents[i2] += bi_tangent;
100+
bi_tangents[i3] += bi_tangent;
101+
}
102+
}
103+
104+
let mut result_tangents = Vec::with_capacity(vertex_count);
105+
106+
for i in 0..vertex_count {
107+
let normal = Vec3A::from_array(normals[i]);
108+
let tangent = tangents[i].normalize();
109+
// Gram-Schmidt orthogonalization
110+
let tangent = (tangent - normal * normal.dot(tangent)).normalize();
111+
let bi_tangent = bi_tangents[i];
112+
let handedness = if normal.cross(tangent).dot(bi_tangent) > 0.0 {
113+
1.0
114+
} else {
115+
-1.0
116+
};
117+
// Both the gram-schmidt and mikktspace algorithms seem to assume left-handedness,
118+
// so we flip the sign to correct for this. The extra multiplication here is
119+
// negligible and it's done as a separate step to better document that it's
120+
// a deviation from the general algorithm. The generated mikktspace tangents are
121+
// also post processed to flip the sign.
122+
let handedness = handedness * -1.0;
123+
result_tangents
124+
.push(Vec4::new(tangent.x, tangent.y, tangent.z, handedness).to_array());
125+
}
126+
127+
Ok(result_tangents)
128+
}
129+
}
130+
}
131+
132+
#[cfg(test)]
133+
mod tests {
134+
use bevy_math::{primitives::*, Vec2, Vec3, Vec3A};
135+
136+
use crate::{Mesh, TangentStrategy};
137+
138+
// The tangents should be very close for simple shapes
139+
fn compare_tangents(mut mesh: Mesh) {
140+
let hq_tangents: Vec<[f32; 4]> = {
141+
mesh.remove_attribute(Mesh::ATTRIBUTE_TANGENT);
142+
mesh.compute_tangents(TangentStrategy::HighQuality)
143+
.expect("compute_tangents(HighQuality)");
144+
mesh.attribute(Mesh::ATTRIBUTE_TANGENT)
145+
.expect("hq_tangents.attribute(tangent)")
146+
.as_float4()
147+
.expect("hq_tangents.as_float4")
148+
.to_vec()
149+
};
150+
151+
let fa_tangents: Vec<[f32; 4]> = {
152+
mesh.remove_attribute(Mesh::ATTRIBUTE_TANGENT);
153+
mesh.compute_tangents(TangentStrategy::FastApproximation)
154+
.expect("compute_tangents(FastApproximation)");
155+
mesh.attribute(Mesh::ATTRIBUTE_TANGENT)
156+
.expect("fa_tangents.attribute(tangent)")
157+
.as_float4()
158+
.expect("fa_tangents.as_float4")
159+
.to_vec()
160+
};
161+
162+
for (hq, fa) in hq_tangents.iter().zip(fa_tangents.iter()) {
163+
assert_eq!(hq[3], fa[3], "handedness");
164+
let hq = Vec3A::from_slice(hq);
165+
let fa = Vec3A::from_slice(fa);
166+
let angle = hq.angle_between(fa);
167+
let threshold = 15.0f32.to_radians();
168+
assert!(
169+
angle < threshold,
170+
"tangents differ significantly: hq = {:?}, fa = {:?}, angle.to_degrees() = {}",
171+
hq,
172+
fa,
173+
angle.to_degrees()
174+
);
175+
}
176+
}
177+
178+
#[test]
179+
fn cuboid() {
180+
compare_tangents(Mesh::from(Cuboid::new(10.0, 10.0, 10.0)));
181+
}
182+
183+
#[test]
184+
fn capsule3d() {
185+
compare_tangents(Mesh::from(Capsule3d::new(10.0, 10.0)));
186+
}
187+
188+
#[test]
189+
fn plane3d() {
190+
compare_tangents(Mesh::from(Plane3d::new(Vec3::Y, Vec2::splat(10.0))));
191+
}
192+
193+
#[test]
194+
fn cylinder() {
195+
compare_tangents(Mesh::from(Cylinder::new(10.0, 10.0)));
196+
}
197+
198+
#[test]
199+
fn torus() {
200+
compare_tangents(Mesh::from(Torus::new(10.0, 100.0)));
201+
}
202+
203+
#[test]
204+
fn rhombus() {
205+
compare_tangents(Mesh::from(Rhombus::new(10.0, 100.0)));
206+
}
207+
208+
#[test]
209+
fn tetrahedron() {
210+
compare_tangents(Mesh::from(Tetrahedron::default()));
211+
}
212+
213+
#[test]
214+
fn cone() {
215+
compare_tangents(Mesh::from(Cone::new(10.0, 100.0)));
216+
}
217+
}

crates/bevy_mesh/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ extern crate alloc;
44
extern crate core;
55

66
mod conversions;
7+
mod gramschmidt;
78
mod index;
89
mod mesh;
910
mod mikktspace;
@@ -14,7 +15,7 @@ mod vertex;
1415
use bitflags::bitflags;
1516
pub use index::*;
1617
pub use mesh::*;
17-
pub use mikktspace::*;
18+
use mikktspace::*;
1819
pub use primitives::*;
1920
pub use vertex::*;
2021

0 commit comments

Comments
 (0)