@@ -187,6 +187,34 @@ impl Default for PhysicalCameraParameters {
187
187
}
188
188
}
189
189
190
+ /// Error returned when a conversion between world-space and viewport-space coordinates fails.
191
+ ///
192
+ /// See [`world_to_viewport`][Camera::world_to_viewport] and [`viewport_to_world`][Camera::viewport_to_world].
193
+ #[ derive( Debug , Eq , PartialEq , Copy , Clone ) ]
194
+ pub enum ViewportConversionError {
195
+ /// The pre-computed size of the viewport was not available.
196
+ ///
197
+ /// This may be because the `Camera` was just created and [`camera_system`] has not been executed
198
+ /// yet, or because the [`RenderTarget`] is misconfigured in one of the following ways:
199
+ /// - it references the [`PrimaryWindow`](RenderTarget::Window) when there is none,
200
+ /// - it references a [`Window`](RenderTarget::Window) entity that doesn't exist or doesn't actually have a `Window` component,
201
+ /// - it references an [`Image`](RenderTarget::Image) that doesn't exist (invalid handle),
202
+ /// - it references a [`TextureView`](RenderTarget::TextureView) that doesn't exist (invalid handle).
203
+ NoViewportSize ,
204
+ /// The computed coordinate was beyond the `Camera`'s near plane.
205
+ ///
206
+ /// Only applicable when converting from world-space to viewport-space.
207
+ PastNearPlane ,
208
+ /// The computed coordinate was beyond the `Camera`'s far plane.
209
+ ///
210
+ /// Only applicable when converting from world-space to viewport-space.
211
+ PastFarPlane ,
212
+ /// The Normalized Device Coordinates could not be computed because the `camera_transform`, the
213
+ /// `world_position`, or the projection matrix defined by [`CameraProjection`] contained `NAN`
214
+ /// (see [`world_to_ndc`][Camera::world_to_ndc] and [`ndc_to_world`][Camera::ndc_to_world]).
215
+ InvalidData ,
216
+ }
217
+
190
218
/// The defining [`Component`] for camera entities,
191
219
/// storing information about how and what to render through this camera.
192
220
///
@@ -348,52 +376,64 @@ impl Camera {
348
376
/// To get the coordinates in Normalized Device Coordinates, you should use
349
377
/// [`world_to_ndc`](Self::world_to_ndc).
350
378
///
351
- /// Returns `None` if any of these conditions occur:
352
- /// - The computed coordinates are beyond the near or far plane
353
- /// - The logical viewport size cannot be computed. See [`logical_viewport_size`](Camera::logical_viewport_size)
354
- /// - The world coordinates cannot be mapped to the Normalized Device Coordinates. See [`world_to_ndc`](Camera::world_to_ndc)
355
- /// May also panic if `glam_assert` is enabled. See [`world_to_ndc`](Camera::world_to_ndc).
379
+ /// # Panics
380
+ ///
381
+ /// Will panic if `glam_assert` is enabled and the `camera_transform` contains `NAN`
382
+ /// (see [`world_to_ndc`][Self::world_to_ndc]).
356
383
#[ doc( alias = "world_to_screen" ) ]
357
384
pub fn world_to_viewport (
358
385
& self ,
359
386
camera_transform : & GlobalTransform ,
360
387
world_position : Vec3 ,
361
- ) -> Option < Vec2 > {
362
- let target_size = self . logical_viewport_size ( ) ?;
363
- let ndc_space_coords = self . world_to_ndc ( camera_transform, world_position) ?;
388
+ ) -> Result < Vec2 , ViewportConversionError > {
389
+ let target_size = self
390
+ . logical_viewport_size ( )
391
+ . ok_or ( ViewportConversionError :: NoViewportSize ) ?;
392
+ let ndc_space_coords = self
393
+ . world_to_ndc ( camera_transform, world_position)
394
+ . ok_or ( ViewportConversionError :: InvalidData ) ?;
364
395
// NDC z-values outside of 0 < z < 1 are outside the (implicit) camera frustum and are thus not in viewport-space
365
- if ndc_space_coords. z < 0.0 || ndc_space_coords. z > 1.0 {
366
- return None ;
396
+ if ndc_space_coords. z < 0.0 {
397
+ return Err ( ViewportConversionError :: PastNearPlane ) ;
398
+ }
399
+ if ndc_space_coords. z > 1.0 {
400
+ return Err ( ViewportConversionError :: PastFarPlane ) ;
367
401
}
368
402
369
403
// Once in NDC space, we can discard the z element and rescale x/y to fit the screen
370
404
let mut viewport_position = ( ndc_space_coords. truncate ( ) + Vec2 :: ONE ) / 2.0 * target_size;
371
405
// Flip the Y co-ordinate origin from the bottom to the top.
372
406
viewport_position. y = target_size. y - viewport_position. y ;
373
- Some ( viewport_position)
407
+ Ok ( viewport_position)
374
408
}
375
409
376
410
/// Given a position in world space, use the camera to compute the viewport-space coordinates and depth.
377
411
///
378
412
/// To get the coordinates in Normalized Device Coordinates, you should use
379
413
/// [`world_to_ndc`](Self::world_to_ndc).
380
414
///
381
- /// Returns `None` if any of these conditions occur:
382
- /// - The computed coordinates are beyond the near or far plane
383
- /// - The logical viewport size cannot be computed. See [`logical_viewport_size`](Camera::logical_viewport_size)
384
- /// - The world coordinates cannot be mapped to the Normalized Device Coordinates. See [`world_to_ndc`](Camera::world_to_ndc)
385
- /// May also panic if `glam_assert` is enabled. See [`world_to_ndc`](Camera::world_to_ndc).
415
+ /// # Panics
416
+ ///
417
+ /// Will panic if `glam_assert` is enabled and the `camera_transform` contains `NAN`
418
+ /// (see [`world_to_ndc`][Self::world_to_ndc]).
386
419
#[ doc( alias = "world_to_screen_with_depth" ) ]
387
420
pub fn world_to_viewport_with_depth (
388
421
& self ,
389
422
camera_transform : & GlobalTransform ,
390
423
world_position : Vec3 ,
391
- ) -> Option < Vec3 > {
392
- let target_size = self . logical_viewport_size ( ) ?;
393
- let ndc_space_coords = self . world_to_ndc ( camera_transform, world_position) ?;
424
+ ) -> Result < Vec3 , ViewportConversionError > {
425
+ let target_size = self
426
+ . logical_viewport_size ( )
427
+ . ok_or ( ViewportConversionError :: NoViewportSize ) ?;
428
+ let ndc_space_coords = self
429
+ . world_to_ndc ( camera_transform, world_position)
430
+ . ok_or ( ViewportConversionError :: InvalidData ) ?;
394
431
// NDC z-values outside of 0 < z < 1 are outside the (implicit) camera frustum and are thus not in viewport-space
395
- if ndc_space_coords. z < 0.0 || ndc_space_coords. z > 1.0 {
396
- return None ;
432
+ if ndc_space_coords. z < 0.0 {
433
+ return Err ( ViewportConversionError :: PastNearPlane ) ;
434
+ }
435
+ if ndc_space_coords. z > 1.0 {
436
+ return Err ( ViewportConversionError :: PastFarPlane ) ;
397
437
}
398
438
399
439
// Stretching ndc depth to value via near plane and negating result to be in positive room again.
@@ -403,7 +443,7 @@ impl Camera {
403
443
let mut viewport_position = ( ndc_space_coords. truncate ( ) + Vec2 :: ONE ) / 2.0 * target_size;
404
444
// Flip the Y co-ordinate origin from the bottom to the top.
405
445
viewport_position. y = target_size. y - viewport_position. y ;
406
- Some ( viewport_position. extend ( depth) )
446
+ Ok ( viewport_position. extend ( depth) )
407
447
}
408
448
409
449
/// Returns a ray originating from the camera, that passes through everything beyond `viewport_position`.
@@ -415,16 +455,18 @@ impl Camera {
415
455
/// To get the world space coordinates with Normalized Device Coordinates, you should use
416
456
/// [`ndc_to_world`](Self::ndc_to_world).
417
457
///
418
- /// Returns `None` if any of these conditions occur:
419
- /// - The logical viewport size cannot be computed. See [`logical_viewport_size`](Camera::logical_viewport_size)
420
- /// - 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`.
421
- /// Panics if the projection matrix is null and `glam_assert` is enabled.
458
+ /// # Panics
459
+ ///
460
+ /// Will panic if the camera's projection matrix is invalid (has a determinant of 0) and
461
+ /// `glam_assert` is enabled (see [`ndc_to_world`](Self::ndc_to_world) .
422
462
pub fn viewport_to_world (
423
463
& self ,
424
464
camera_transform : & GlobalTransform ,
425
465
mut viewport_position : Vec2 ,
426
- ) -> Option < Ray3d > {
427
- let target_size = self . logical_viewport_size ( ) ?;
466
+ ) -> Result < Ray3d , ViewportConversionError > {
467
+ let target_size = self
468
+ . logical_viewport_size ( )
469
+ . ok_or ( ViewportConversionError :: NoViewportSize ) ?;
428
470
// Flip the Y co-ordinate origin from the top to the bottom.
429
471
viewport_position. y = target_size. y - viewport_position. y ;
430
472
let ndc = viewport_position * 2. / target_size - Vec2 :: ONE ;
@@ -436,12 +478,12 @@ impl Camera {
436
478
let world_far_plane = ndc_to_world. project_point3 ( ndc. extend ( f32:: EPSILON ) ) ;
437
479
438
480
// The fallible direction constructor ensures that world_near_plane and world_far_plane aren't NaN.
439
- Dir3 :: new ( world_far_plane - world_near_plane) . map_or ( None , |direction| {
440
- Some ( Ray3d {
481
+ Dir3 :: new ( world_far_plane - world_near_plane)
482
+ . map_err ( |_| ViewportConversionError :: InvalidData )
483
+ . map ( |direction| Ray3d {
441
484
origin : world_near_plane,
442
485
direction,
443
486
} )
444
- } )
445
487
}
446
488
447
489
/// Returns a 2D world position computed from a position on this [`Camera`]'s viewport.
@@ -451,23 +493,27 @@ impl Camera {
451
493
/// To get the world space coordinates with Normalized Device Coordinates, you should use
452
494
/// [`ndc_to_world`](Self::ndc_to_world).
453
495
///
454
- /// Returns `None` if any of these conditions occur:
455
- /// - The logical viewport size cannot be computed. See [`logical_viewport_size`](Camera::logical_viewport_size)
456
- /// - The viewport position cannot be mapped to the world. See [`ndc_to_world`](Camera::ndc_to_world)
457
- /// May panic. See [`ndc_to_world`](Camera ::ndc_to_world).
496
+ /// # Panics
497
+ ///
498
+ /// Will panic if the camera's projection matrix is invalid (has a determinant of 0) and
499
+ /// `glam_assert` is enabled (see [`ndc_to_world`](Self ::ndc_to_world).
458
500
pub fn viewport_to_world_2d (
459
501
& self ,
460
502
camera_transform : & GlobalTransform ,
461
503
mut viewport_position : Vec2 ,
462
- ) -> Option < Vec2 > {
463
- let target_size = self . logical_viewport_size ( ) ?;
504
+ ) -> Result < Vec2 , ViewportConversionError > {
505
+ let target_size = self
506
+ . logical_viewport_size ( )
507
+ . ok_or ( ViewportConversionError :: NoViewportSize ) ?;
464
508
// Flip the Y co-ordinate origin from the top to the bottom.
465
509
viewport_position. y = target_size. y - viewport_position. y ;
466
510
let ndc = viewport_position * 2. / target_size - Vec2 :: ONE ;
467
511
468
- let world_near_plane = self . ndc_to_world ( camera_transform, ndc. extend ( 1. ) ) ?;
512
+ let world_near_plane = self
513
+ . ndc_to_world ( camera_transform, ndc. extend ( 1. ) )
514
+ . ok_or ( ViewportConversionError :: InvalidData ) ?;
469
515
470
- Some ( world_near_plane. truncate ( ) )
516
+ Ok ( world_near_plane. truncate ( ) )
471
517
}
472
518
473
519
/// Given a position in world space, use the camera's viewport to compute the Normalized Device Coordinates.
@@ -478,7 +524,10 @@ impl Camera {
478
524
/// [`world_to_viewport`](Self::world_to_viewport).
479
525
///
480
526
/// Returns `None` if the `camera_transform`, the `world_position`, or the projection matrix defined by [`CameraProjection`] contain `NAN`.
481
- /// Panics if the `camera_transform` contains `NAN` and the `glam_assert` feature is enabled.
527
+ ///
528
+ /// # Panics
529
+ ///
530
+ /// Will panic if the `camera_transform` contains `NAN` and the `glam_assert` feature is enabled.
482
531
pub fn world_to_ndc (
483
532
& self ,
484
533
camera_transform : & GlobalTransform ,
@@ -501,7 +550,10 @@ impl Camera {
501
550
/// [`world_to_viewport`](Self::world_to_viewport).
502
551
///
503
552
/// Returns `None` if the `camera_transform`, the `world_position`, or the projection matrix defined by [`CameraProjection`] contain `NAN`.
504
- /// Panics if the projection matrix is null and `glam_assert` is enabled.
553
+ ///
554
+ /// # Panics
555
+ ///
556
+ /// Will panic if the projection matrix is invalid (has a determinant of 0) and `glam_assert` is enabled.
505
557
pub fn ndc_to_world ( & self , camera_transform : & GlobalTransform , ndc : Vec3 ) -> Option < Vec3 > {
506
558
// Build a transformation matrix to convert from NDC to world space using camera data
507
559
let ndc_to_world =
0 commit comments