Conversation
| if let Ok(mesh) = skin_mesh_entity( | ||
| mesh3d, | ||
| skinned_mesh, | ||
| transform, | ||
| &self.meshes, | ||
| &self.skinned_mesh_inverse_bindposes, | ||
| &self.joint_transforms, | ||
| ) { |
There was a problem hiding this comment.
Picking will silently skip over a skinned mesh if there's any errors. This feels like a bad idea, but I'm also cautious of adding a very spammy log. But maybe logging is the least worst option until Bevy gets a better way to handle these kinds of errors.
There was a problem hiding this comment.
That might be the best compromise, yeah. I'll wait a bit to see if there's any second opinions.
There was a problem hiding this comment.
There was similar concerns when we added mesh picking in the first place, what if the mesh is malformed etc. Pretty sure think we went with just skipping malformed meshes, I think partially because you likely have bigger concerns than picking is your skinned mesh is broken, you'll likely see it render wrong etc.
Ideally there is a "validated" mesh we could test against but it's a different issue
| let mut positions = match input_mesh | ||
| .try_attribute_option(Mesh::ATTRIBUTE_POSITION.id)? | ||
| .cloned() | ||
| { | ||
| Some(VertexAttributeValues::Float32x3(values)) => Some(values), | ||
| Some(_) => Err(MeshAttributeError::UnexpectedFormat( | ||
| Mesh::ATTRIBUTE_POSITION.name, | ||
| Mesh::ATTRIBUTE_POSITION.format, | ||
| ))?, | ||
| None => None, | ||
| }; |
There was a problem hiding this comment.
This boilerplate is repeated for normals and tangents. I did try adding a macro, similar to impl_expect_attribute, but it's kinda special case and also adds a fair amount of code. Maybe boilerplate is the least worst option for now.
| /// Returns a copy of the mesh without attributes, indices, morph targets, | ||
| /// and the data derived from them (like the AABB). Settings like | ||
| /// `primitive_topology` and `asset_usage` are preserved. | ||
| pub fn as_empty(&self) -> Mesh { |
There was a problem hiding this comment.
I added this so that skin_mesh can replace a mesh's attributes while keeping the settings. It's maybe a bit niche for the main mesh API, but other solutions would break easily if Mesh acquires new members.
|
There is a lot of allocation to build up the skinned mesh for each test which is immediately thrown away. I think it's perfectly fine as a first solution, just wondering what we could do to alleviate it in future.
|
Objective
Fix #23378. As a bonus, add some useful skinning functions, and add tangents to
RayMeshHit.Solution
Add new functions that take the various aspects of a skinned mesh, and return a new
Meshwith skinning baked into its positions, normals and tangents.When
bevy_pickingencounters a skinned mesh, it uses these functions to make a temporary mesh before ray-casting.The PR also adds tangents to
RayMeshHit:pub struct RayMeshHit { /// The point of intersection in world space. pub point: Vec3, /// The normal vector of the triangle at the point of intersection. Not guaranteed to be normalized for scaled meshes. pub normal: Vec3, + /// The tangent vector of the triangle at the point of intersection, if the mesh has tangent attributes. + /// Not guaranteed to be normalized for scaled meshes. + pub tangent: Option<Vec3>,I wanted the tangents for testing, but they could be useful elsewhere. There will be a small per-mesh performance cost if the tangent is unwanted. That could be reduced by making the tangent (and other attributes) opt-in or opt-out via
MeshRayCastSettings.Performance
Tested on
Fox.glb(25 joints, 576 vertices) with desktop Zen 4:This seems like a reasonable cost for simple cases - twenty foxes would be 0.64ms.
Picking could be optimized. Currently the whole mesh is copied and skinned, but picking only needs the position attribute for intersection tests - the normals/tangents could be calculated for just the hit triangle. Other attributes don't need to be copied or skinned at all. Fixing this is possible, at the cost of a whole custom implementation of
skin_meshfor picking.Also, the cost of the
skin_verticesfunction is higher than what my gut feeling says it should be. Currently it's roughly 130 cycles per-vertex - I was expecting about half that. Maybe I'm wrong, or maybe there's some space for low-level optimization. One likely culprit is redundant bound checks on arrays that we know are the same size.Testing
The PR adds a new example:
cargo run --example test_skinned_mesh_picking --features "free_camera"And some unit tests:
cargo test -p bevy_mesh -- skin_meshConcerns
I've left a few comments on debatable parts. Also
bevy_mesh/skinning.rsis getting a bit chonky (~1200 lines). Maybe should be refactored into a module with a few parts moved to separate files.