Check for NaN in Camera::world_to_screen() (#3268)

# Objective

- Checks for NaN in computed NDC space coordinates, fixing unexpected NaN in a fallible (`Option<T>`) function.

## Solution

- Adds a NaN check, in addition to the existing NDC bounds checks.
- This is a helper function, and should have no performance impact to the engine itself.
- This will help prevent hard-to-trace NaN propagation in user code, by returning `None` instead of `Some(NaN)`.


Depends on https://github.com/bevyengine/bevy/pull/3269 for CI error fix.
This commit is contained in:
Aevyrie 2021-12-08 01:31:31 +00:00
parent bf96f266d7
commit 38c7d5eb9e
2 changed files with 12 additions and 4 deletions

View file

@ -55,13 +55,17 @@ impl Camera {
let world_to_ndc: Mat4 = let world_to_ndc: Mat4 =
self.projection_matrix * camera_transform.compute_matrix().inverse(); self.projection_matrix * camera_transform.compute_matrix().inverse();
let ndc_space_coords: Vec3 = world_to_ndc.project_point3(world_position); let ndc_space_coords: Vec3 = world_to_ndc.project_point3(world_position);
// NDC z-values outside of 0 < z < 1 are behind the camera and are thus not in screen space // NDC z-values outside of 0 < z < 1 are outside the camera frustum and are thus not in screen space
if ndc_space_coords.z < 0.0 || ndc_space_coords.z > 1.0 { if ndc_space_coords.z < 0.0 || ndc_space_coords.z > 1.0 {
return None; return None;
} }
// Once in NDC space, we can discard the z element and rescale x/y to fit the screen // Once in NDC space, we can discard the z element and rescale x/y to fit the screen
let screen_space_coords = (ndc_space_coords.truncate() + Vec2::ONE) / 2.0 * window_size; let screen_space_coords = (ndc_space_coords.truncate() + Vec2::ONE) / 2.0 * window_size;
Some(screen_space_coords) if !screen_space_coords.is_nan() {
Some(screen_space_coords)
} else {
None
}
} }
} }

View file

@ -54,13 +54,17 @@ impl Camera {
let world_to_ndc: Mat4 = let world_to_ndc: Mat4 =
self.projection_matrix * camera_transform.compute_matrix().inverse(); self.projection_matrix * camera_transform.compute_matrix().inverse();
let ndc_space_coords: Vec3 = world_to_ndc.project_point3(world_position); let ndc_space_coords: Vec3 = world_to_ndc.project_point3(world_position);
// NDC z-values outside of 0 < z < 1 are behind the camera and are thus not in screen space // NDC z-values outside of 0 < z < 1 are outside the camera frustum and are thus not in screen space
if ndc_space_coords.z < 0.0 || ndc_space_coords.z > 1.0 { if ndc_space_coords.z < 0.0 || ndc_space_coords.z > 1.0 {
return None; return None;
} }
// Once in NDC space, we can discard the z element and rescale x/y to fit the screen // Once in NDC space, we can discard the z element and rescale x/y to fit the screen
let screen_space_coords = (ndc_space_coords.truncate() + Vec2::ONE) / 2.0 * window_size; let screen_space_coords = (ndc_space_coords.truncate() + Vec2::ONE) / 2.0 * window_size;
Some(screen_space_coords) if !screen_space_coords.is_nan() {
Some(screen_space_coords)
} else {
None
}
} }
} }