@@ -17,7 +17,7 @@ use bevy_ecs::{
17
17
reflect:: ReflectComponent ,
18
18
system:: { Commands , ParamSet , Query , Res } ,
19
19
} ;
20
- use bevy_math:: { Mat4 , UVec2 , UVec4 , Vec2 , Vec3 } ;
20
+ use bevy_math:: { Mat4 , Ray , UVec2 , UVec4 , Vec2 , Vec3 } ;
21
21
use bevy_reflect:: prelude:: * ;
22
22
use bevy_reflect:: FromReflect ;
23
23
use bevy_transform:: components:: GlobalTransform ;
@@ -212,26 +212,66 @@ impl Camera {
212
212
Some ( ( ndc_space_coords. truncate ( ) + Vec2 :: ONE ) / 2.0 * target_size)
213
213
}
214
214
215
+ /// Returns a ray originating from the camera, that passes through everything beyond `viewport_position`.
216
+ ///
217
+ /// The resulting ray starts on the near plane of the camera.
218
+ ///
219
+ /// If the camera's projection is orthographic the direction of the ray is always equal to `camera_transform.forward()`.
220
+ ///
221
+ /// To get the world space coordinates with Normalized Device Coordinates, you should use
222
+ /// [`ndc_to_world`](Self::ndc_to_world).
223
+ pub fn viewport_to_world (
224
+ & self ,
225
+ camera_transform : & GlobalTransform ,
226
+ viewport_position : Vec2 ,
227
+ ) -> Option < Ray > {
228
+ let target_size = self . logical_viewport_size ( ) ?;
229
+ let ndc = viewport_position * 2. / target_size - Vec2 :: ONE ;
230
+
231
+ let world_near_plane = self . ndc_to_world ( camera_transform, ndc. extend ( 1. ) ) ?;
232
+ // Using EPSILON because passing an ndc with Z = 0 returns NaNs.
233
+ let world_far_plane = self . ndc_to_world ( camera_transform, ndc. extend ( f32:: EPSILON ) ) ?;
234
+
235
+ Some ( Ray {
236
+ origin : world_near_plane,
237
+ direction : ( world_far_plane - world_near_plane) . normalize ( ) ,
238
+ } )
239
+ }
240
+
215
241
/// Given a position in world space, use the camera's viewport to compute the Normalized Device Coordinates.
216
242
///
217
- /// Values returned will be between -1.0 and 1.0 when the position is within the viewport.
243
+ /// When the position is within the viewport the values returned will be between -1.0 and 1.0 on the X and Y axes,
244
+ /// and between 0.0 and 1.0 on the Z axis.
218
245
/// To get the coordinates in the render target's viewport dimensions, you should use
219
246
/// [`world_to_viewport`](Self::world_to_viewport).
220
247
pub fn world_to_ndc (
221
248
& self ,
222
249
camera_transform : & GlobalTransform ,
223
250
world_position : Vec3 ,
224
251
) -> Option < Vec3 > {
225
- // Build a transform to convert from world to NDC using camera data
252
+ // Build a transformation matrix to convert from world space to NDC using camera data
226
253
let world_to_ndc: Mat4 =
227
254
self . computed . projection_matrix * camera_transform. compute_matrix ( ) . inverse ( ) ;
228
255
let ndc_space_coords: Vec3 = world_to_ndc. project_point3 ( world_position) ;
229
256
230
- if !ndc_space_coords. is_nan ( ) {
231
- Some ( ndc_space_coords)
232
- } else {
233
- None
234
- }
257
+ ( !ndc_space_coords. is_nan ( ) ) . then_some ( ndc_space_coords)
258
+ }
259
+
260
+ /// Given a position in Normalized Device Coordinates,
261
+ /// use the camera's viewport to compute the world space position.
262
+ ///
263
+ /// When the position is within the viewport the values returned will be between -1.0 and 1.0 on the X and Y axes,
264
+ /// and between 0.0 and 1.0 on the Z axis.
265
+ /// To get the world space coordinates with the viewport position, you should use
266
+ /// [`world_to_viewport`](Self::world_to_viewport).
267
+ pub fn ndc_to_world ( & self , camera_transform : & GlobalTransform , ndc : Vec3 ) -> Option < Vec3 > {
268
+ // Build a transformation matrix to convert from NDC to world space using camera data
269
+ let ndc_to_world =
270
+ camera_transform. compute_matrix ( ) * self . computed . projection_matrix . inverse ( ) ;
271
+
272
+ let world_space_coords = ndc_to_world. project_point3 ( ndc) ;
273
+
274
+ ( !world_space_coords. is_nan ( ) ) . then_some ( world_space_coords)
235
275
}
236
276
}
237
277
0 commit comments