aevyrie / bevy_mod_raycast

A little mesh raycasting plugin for Bevy
https://crates.io/crates/bevy_mod_raycast
MIT License
325 stars 93 forks source link

intersects_aabb can return NaN which fails the filter predicate when checking for intersections #118

Open StrikeForceZero opened 4 months ago

StrikeForceZero commented 4 months ago

From investigating https://github.com/aevyrie/bevy_mod_picking/issues/341, I've discovered that:

https://github.com/aevyrie/bevy_mod_raycast/blob/dbc5ef32fe48997a1a7eeec7434d9dd8b829e52e/src/primitives.rs#L165-L204

intersects_aabb returns [NaN, NaN] because of a divide by 0 when the intersection check is right on the edge of a mesh, thus failing the filter predicate far > 0.0 here:

https://github.com/aevyrie/bevy_mod_raycast/blob/dbc5ef32fe48997a1a7eeec7434d9dd8b829e52e/src/immediate.rs#L260

It was not apparent to me what the right approach to prevent intersects_aabb from returning NaN. It's possible I'm being naive, and there's a more obvious fix, however, this diff seems to solve the symptom of that issue. (I have no idea if this introduces other side effects, but all the tests seem to pass?)

Index: src/immediate.rs
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/immediate.rs b/src/immediate.rs
--- a/src/immediate.rs  (revision Staged)
+++ b/src/immediate.rs  (date 1720831909681)
@@ -257,7 +257,7 @@
                 };
                 if should_raycast {
                     if let Some([near, _]) = intersects_aabb(ray, aabb, &transform.compute_matrix())
-                        .filter(|[_, far]| *far >= 0.0)
+                        .filter(|[_, far]| *far >= 0.0 || far.is_nan())
                     {
                         aabb_hits_tx.send((FloatOrd(near), entity)).ok();
                     }
StrikeForceZero commented 4 months ago

snippet to reproduce it in headless

#[cfg(test)]
mod tests {
    use super::*;
    use bevy_math::{Dir3, Mat4, Ray3d, Vec3, Vec4};
    use bevy_render::primitives::Aabb;

    #[test]
    fn test_intersects_aabb_nan() {
        let ray = Ray3d {
            origin: Vec3::new(275.0, 247.0, 1000.0),
            direction: Dir3::new_unchecked(Vec3::new(0.0, 0.0, -1.0)),
        };
        let aabb = Aabb {
            center: Vec3A::new(0.0, 0.0, 0.0),
            half_extents: Vec3A::new(25.0, 25.0, 0.0),
        };
        let model_to_world = Mat4 {
            x_axis: Vec4::new(1.0, 0.0, 0.0, 0.0),
            y_axis: Vec4::new(0.0, 1.0, 0.0, 0.0),
            z_axis: Vec4::new(0.0, 0.0, 1.0, 0.0),
            w_axis: Vec4::new(250.0, 350.0, 1.0, 1.0),
        };

        let result = intersects_aabb(ray, &aabb, &model_to_world);

        assert_eq!(result.map(|[near, far]| [near.is_nan(), far.is_nan()]), Some([true, true]));
    }
}