Unproject screen coordinates to a world-space ray. screen_x, screen_y: pixel coordinates (0,0 = top-left) width, height: viewport dimensions inv_vp: inverse view-projection matrix camera_pos: camera world position
(
screen_x: f32, screen_y: f32,
width: f32, height: f32,
inv_vp: &[[f32; 4]; 4],
_camera_pos: &[f32; 3],
)
| 33 | /// inv_vp: inverse view-projection matrix |
| 34 | /// camera_pos: camera world position |
| 35 | pub fn screen_to_ray( |
| 36 | screen_x: f32, screen_y: f32, |
| 37 | width: f32, height: f32, |
| 38 | inv_vp: &[[f32; 4]; 4], |
| 39 | _camera_pos: &[f32; 3], |
| 40 | ) -> ([f32; 3], [f32; 3]) { |
| 41 | // Convert screen coords to NDC (-1 to 1) |
| 42 | let ndc_x = (screen_x / width) * 2.0 - 1.0; |
| 43 | let ndc_y = 1.0 - (screen_y / height) * 2.0; // flip Y |
| 44 | |
| 45 | // Unproject near point (z = -1 in NDC) |
| 46 | let near_ndc = [ndc_x, ndc_y, -1.0, 1.0]; |
| 47 | let near_world = mat4_mul_vec4(inv_vp, &near_ndc); |
| 48 | |
| 49 | // Unproject far point (z = 1 in NDC) |
| 50 | let far_ndc = [ndc_x, ndc_y, 1.0, 1.0]; |
| 51 | let far_world = mat4_mul_vec4(inv_vp, &far_ndc); |
| 52 | |
| 53 | // Perspective divide |
| 54 | let near = [ |
| 55 | near_world[0] / near_world[3], |
| 56 | near_world[1] / near_world[3], |
| 57 | near_world[2] / near_world[3], |
| 58 | ]; |
| 59 | let far = [ |
| 60 | far_world[0] / far_world[3], |
| 61 | far_world[1] / far_world[3], |
| 62 | far_world[2] / far_world[3], |
| 63 | ]; |
| 64 | |
| 65 | // Ray direction |
| 66 | let dx = far[0] - near[0]; |
| 67 | let dy = far[1] - near[1]; |
| 68 | let dz = far[2] - near[2]; |
| 69 | let len = (dx * dx + dy * dy + dz * dz).sqrt(); |
| 70 | let dir = if len > 1e-8 { |
| 71 | [dx / len, dy / len, dz / len] |
| 72 | } else { |
| 73 | [0.0, 0.0, -1.0] |
| 74 | }; |
| 75 | |
| 76 | (near, dir) |
| 77 | } |
| 78 | |
| 79 | /// Raycast against all visible scene nodes. Returns the closest hit. |
| 80 | pub fn raycast_scene( |
no test coverage detected