Skip to content

Commit

Permalink
Implement errors for the rest.
Browse files Browse the repository at this point in the history
  • Loading branch information
tormeh committed Sep 18, 2023
1 parent ae6de8e commit 0ce2fe2
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 41 deletions.
158 changes: 121 additions & 37 deletions crates/bevy_render/src/camera/camera.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,19 +170,32 @@ impl Camera {
/// For logic that requires the full logical size of the
/// [`RenderTarget`], prefer [`Camera::logical_target_size`].
///
/// Returns `None` if either:
/// Returns an error if either:
/// - the function is called just after the `Camera` is created, before `camera_system` is executed,
/// - the [`RenderTarget`] isn't correctly set:
/// - it references the [`PrimaryWindow`](RenderTarget::Window) when there is none,
/// - it references a [`Window`](RenderTarget::Window) entity that doesn't exist or doesn't actually have a `Window` component,
/// - it references an [`Image`](RenderTarget::Image) that doesn't exist (invalid handle),
/// - it references a [`TextureView`](RenderTarget::TextureView) that doesn't exist (invalid handle).
#[inline]
pub fn logical_viewport_size(&self) -> Option<Vec2> {
self.viewport
.as_ref()
.and_then(|v| self.to_logical(v.physical_size))
.or_else(|| self.logical_target_size())
pub fn logical_viewport_size(&self) -> Result<Vec2, LogicalViewportSizeError> {
let viewport = self.viewport.as_ref();
let logical_viewport_size = viewport.and_then(|v| self.to_logical(v.physical_size));
// TODO: This code sucks
if let Some(size) = logical_viewport_size {
Ok(size)
} else {
let logical_target_size = self.logical_target_size();
if let Some(size) = logical_target_size {
Ok(size)
} else {
Err(LogicalViewportSizeError {
window_is_set: viewport.is_some(),
logical_viewport_size,
logical_target_size,
})
}
}
}

/// The physical size of this camera's viewport. If the `viewport` field is set to [`Some`], this
Expand Down Expand Up @@ -227,29 +240,31 @@ impl Camera {
/// To get the coordinates in Normalized Device Coordinates, you should use
/// [`world_to_ndc`](Self::world_to_ndc).
///
/// Returns `None` if any of these conditions occur:
/// - The computed coordinates are beyond the near or far plane
/// - The logical viewport size cannot be computed. See [`logical_viewport_size`](Camera::logical_viewport_size)
/// - The world coordinates cannot be mapped to the Normalized Device Coordinates. See [`world_to_ndc`](Camera::world_to_ndc)
/// May also panic if `glam_assert` is enabled. See [`world_to_ndc`](Camera::world_to_ndc).
/// May panic if `glam_assert` is enabled. See [`world_to_ndc`](Camera::world_to_ndc).
#[doc(alias = "world_to_screen")]
pub fn world_to_viewport(
&self,
camera_transform: &GlobalTransform,
world_position: Vec3,
) -> Option<Vec2> {
let target_size = self.logical_viewport_size()?;
let ndc_space_coords = self.world_to_ndc(camera_transform, world_position)?;
) -> Result<Vec2, WorldToViewportError> {
let target_size = self
.logical_viewport_size()
.map_err(WorldToViewportError::LogicalViewportSizeError)?;
let ndc_space_coords = self
.world_to_ndc(camera_transform, world_position)
.map_err(WorldToViewportError::WorldToNdcError)?;
// NDC z-values outside of 0 < z < 1 are outside the (implicit) camera frustum and are thus not in viewport-space
if ndc_space_coords.z < 0.0 || ndc_space_coords.z > 1.0 {
return None;
return Err(WorldToViewportError::NdcCoordsOutsideFrustumError(
ndc_space_coords,
));
}

// Once in NDC space, we can discard the z element and rescale x/y to fit the screen
let mut viewport_position = (ndc_space_coords.truncate() + Vec2::ONE) / 2.0 * target_size;
// Flip the Y co-ordinate origin from the bottom to the top.
viewport_position.y = target_size.y - viewport_position.y;
Some(viewport_position)
Ok(viewport_position)
}

/// Returns a ray originating from the camera, that passes through everything beyond `viewport_position`.
Expand All @@ -261,27 +276,49 @@ impl Camera {
/// To get the world space coordinates with Normalized Device Coordinates, you should use
/// [`ndc_to_world`](Self::ndc_to_world).
///
/// Returns `None` if any of these conditions occur:
/// - The logical viewport size cannot be computed. See [`logical_viewport_size`](Camera::logical_viewport_size)
/// - The near or far plane cannot be computed. This can happen if the `camera_transform`, the `world_position`, or the projection matrix defined by [`CameraProjection`] contain `NAN`.
/// Panics if the projection matrix is null and `glam_assert` is enabled.
pub fn viewport_to_world(
&self,
camera_transform: &GlobalTransform,
mut viewport_position: Vec2,
) -> Option<Ray> {
let target_size = self.logical_viewport_size()?;
) -> Result<Ray, ViewportToWorldError> {
let target_size = self
.logical_viewport_size()
.map_err(ViewportToWorldError::LogicalViewportSizeError)?;
// Flip the Y co-ordinate origin from the top to the bottom.
viewport_position.y = target_size.y - viewport_position.y;
let ndc = viewport_position * 2. / target_size - Vec2::ONE;

let ndc_to_world =
camera_transform.compute_matrix() * self.computed.projection_matrix.inverse();
let camera_transform_matrix = camera_transform.compute_matrix();
if !camera_transform_matrix.is_finite() {
return Err(ViewportToWorldError::CameraTransformNotFiniteError(
camera_transform_matrix,
));
}
// TODO: Do some code deduplication of these two snippets..
let projection_matrix = self.computed.projection_matrix;
if !projection_matrix.is_finite() {
return Err(ViewportToWorldError::ProjectionMatrixNotFiniteError(
projection_matrix,
));
}

let ndc_to_world = camera_transform_matrix * projection_matrix.inverse();
let world_near_plane = ndc_to_world.project_point3(ndc.extend(1.));
// Using EPSILON because an ndc with Z = 0 returns NaNs.
let world_far_plane = ndc_to_world.project_point3(ndc.extend(f32::EPSILON));

(!world_near_plane.is_nan() && !world_far_plane.is_nan()).then_some(Ray {
if !world_near_plane.is_finite() {
return Err(ViewportToWorldError::WorldNearPlaneNotFiniteError(
world_near_plane,
));
}

if world_far_plane.is_nan() {
return Err(ViewportToWorldError::WorldFarPlaneNanError(world_far_plane));
}

Ok(Ray {
origin: world_near_plane,
direction: (world_far_plane - world_near_plane).normalize(),
})
Expand All @@ -294,25 +331,22 @@ impl Camera {
/// To get the world space coordinates with Normalized Device Coordinates, you should use
/// [`ndc_to_world`](Self::ndc_to_world).
///
/// Returns `None` if any of these conditions occur:
/// - The logical viewport size cannot be computed. See [`logical_viewport_size`](Camera::logical_viewport_size)
/// - The viewport position cannot be mapped to the world. See [`ndc_to_world`](Camera::ndc_to_world)
/// May panic. See [`ndc_to_world`](Camera::ndc_to_world).
pub fn viewport_to_world_2d(
&self,
camera_transform: &GlobalTransform,
mut viewport_position: Vec2,
) -> Result<Vec2, ViewportToWorldError> {
) -> Result<Vec2, ViewportToWorld2DError> {
let target_size = self
.logical_viewport_size()
.ok_or(ViewportToWorldError::LogicalViewportSizeError)?;
.map_err(ViewportToWorld2DError::LogicalViewportSizeError)?;
// Flip the Y co-ordinate origin from the top to the bottom.
viewport_position.y = target_size.y - viewport_position.y;
let ndc = viewport_position * 2. / target_size - Vec2::ONE;

let world_near_plane = self
.ndc_to_world(camera_transform, ndc.extend(1.))
.map_err(ViewportToWorldError::NdcToWorldError)?;
.map_err(ViewportToWorld2DError::NdcToWorldError)?;

Ok(world_near_plane.truncate())
}
Expand All @@ -324,19 +358,37 @@ impl Camera {
/// To get the coordinates in the render target's viewport dimensions, you should use
/// [`world_to_viewport`](Self::world_to_viewport).
///
/// Returns `None` if the `camera_transform`, the `world_position`, or the projection matrix defined by [`CameraProjection`] contain `NAN`.
/// Panics if the `camera_transform` contains `NAN` and the `glam_assert` feature is enabled.
pub fn world_to_ndc(
&self,
camera_transform: &GlobalTransform,
world_position: Vec3,
) -> Option<Vec3> {
) -> Result<Vec3, WorldToNdcError> {
let camera_transform_matrix = camera_transform.compute_matrix();
if !camera_transform_matrix.is_finite() {
return Err(WorldToNdcError::CameraTransformNotFiniteError(
camera_transform_matrix,
));
}

let projection_matrix = self.computed.projection_matrix;
if !projection_matrix.is_finite() {
return Err(WorldToNdcError::ProjectionMatrixNotFiniteError(
projection_matrix,
));
}

// Build a transformation matrix to convert from world space to NDC using camera data
let world_to_ndc: Mat4 =
self.computed.projection_matrix * camera_transform.compute_matrix().inverse();
let world_to_ndc: Mat4 = projection_matrix * camera_transform_matrix.inverse();
let ndc_space_coords: Vec3 = world_to_ndc.project_point3(world_position);

(!ndc_space_coords.is_nan()).then_some(ndc_space_coords)
if !ndc_space_coords.is_finite() {
return Err(WorldToNdcError::NdcSpaceCoordsNotFiniteError(
ndc_space_coords,
));
}

Ok(ndc_space_coords)
}

/// Given a position in Normalized Device Coordinates,
Expand Down Expand Up @@ -381,15 +433,47 @@ impl Camera {
}
}

#[derive(Debug)]
pub enum NdcToWorldError {
WorldSpaceCoordsNotFiniteError(Vec3),
CameraTransformNotFiniteError(Mat4),
ProjectionMatrixNotFiniteError(Mat4),
}

#[derive(Debug)]
pub enum WorldToNdcError {
NdcSpaceCoordsNotFiniteError(Vec3),
CameraTransformNotFiniteError(Mat4),
ProjectionMatrixNotFiniteError(Mat4),
}

#[derive(Debug)]
pub struct LogicalViewportSizeError {
pub window_is_set: bool,
pub logical_viewport_size: Option<Vec2>,
pub logical_target_size: Option<Vec2>,
}

#[derive(Debug)]
pub enum ViewportToWorldError {
LogicalViewportSizeError(LogicalViewportSizeError),
CameraTransformNotFiniteError(Mat4),
ProjectionMatrixNotFiniteError(Mat4),
WorldNearPlaneNotFiniteError(Vec3),
WorldFarPlaneNanError(Vec3), //Presumably this being infinite is sometimes ok?
}

#[derive(Debug)]
pub enum ViewportToWorld2DError {
NdcToWorldError(NdcToWorldError),
LogicalViewportSizeError,
LogicalViewportSizeError(LogicalViewportSizeError),
}

#[derive(Debug)]
pub enum WorldToViewportError {
WorldToNdcError(WorldToNdcError),
LogicalViewportSizeError(LogicalViewportSizeError),
NdcCoordsOutsideFrustumError(Vec3),
}

/// Control how this camera outputs once rendering is completed.
Expand Down Expand Up @@ -642,7 +726,7 @@ pub fn camera_system<T: CameraProjection + Component>(
&images,
&manual_texture_views,
);
if let Some(size) = camera.logical_viewport_size() {
if let Ok(size) = camera.logical_viewport_size() {
camera_projection.update(size.x, size.y);
camera.computed.projection_matrix = camera_projection.get_projection_matrix();
}
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_ui/src/accessibility.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ fn calc_bounds(
if let Ok((camera, camera_transform)) = camera.get_single() {
for (mut accessible, node, transform) in &mut nodes {
if node.is_changed() || transform.is_changed() {
if let Some(translation) =
if let Ok(translation) =
camera.world_to_viewport(camera_transform, transform.translation())
{
let bounds = Rect::new(
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_ui/src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,7 @@ pub fn extract_default_ui_camera_view<T: Component>(
continue;
}
if let (
Some(logical_size),
Ok(logical_size),
Some(URect {
min: physical_origin,
..
Expand Down
2 changes: 1 addition & 1 deletion examples/2d/2d_viewport_to_world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ fn draw_cursor(
};

// Calculate a world position based on the cursor's position.
let Some(point) = camera.viewport_to_world_2d(camera_transform, cursor_position) else {
let Ok(point) = camera.viewport_to_world_2d(camera_transform, cursor_position) else {
return;
};

Expand Down
2 changes: 1 addition & 1 deletion examples/3d/3d_viewport_to_world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ fn draw_cursor(
};

// Calculate a ray pointing from the camera into the world based on the cursor's position.
let Some(ray) = camera.viewport_to_world(camera_transform, cursor_position) else {
let Ok(ray) = camera.viewport_to_world(camera_transform, cursor_position) else {
return;
};

Expand Down

0 comments on commit 0ce2fe2

Please sign in to comment.