Source code for simvx.core.helpers.raycast

"""Raycasting helpers: screen-to-ray and ray-sphere intersection."""

import math

import numpy as np

from ..math.types import Vec3


[docs] def screen_to_ray(screen_pos, screen_size, view, proj): """Convert a screen pixel coordinate to a world-space ray (origin, direction). Returns (origin, direction) as Vec3. """ sp_x = float(screen_pos[0]) if isinstance(screen_pos, tuple | list) else float(screen_pos.x) sp_y = float(screen_pos[1]) if isinstance(screen_pos, tuple | list) else float(screen_pos.y) ss_x = float(screen_size[0]) if isinstance(screen_size, tuple | list) else float(screen_size.x) ss_y = float(screen_size[1]) if isinstance(screen_size, tuple | list) else float(screen_size.y) ndc_x = (2.0 * sp_x / ss_x) - 1.0 ndc_y = 1.0 - (2.0 * sp_y / ss_y) if not isinstance(view, np.ndarray): view = np.array(view, dtype=np.float32).reshape(4, 4) if not isinstance(proj, np.ndarray): proj = np.array(proj, dtype=np.float32).reshape(4, 4) # Detect Vulkan Y-flip if proj[1, 1] < 0: ndc_y = -ndc_y inv_vp = np.linalg.inv(proj @ view) near = inv_vp @ np.array([ndc_x, ndc_y, -1.0, 1.0], dtype=np.float32) far = inv_vp @ np.array([ndc_x, ndc_y, 1.0, 1.0], dtype=np.float32) near_pos = near[:3] / near[3] far_pos = far[:3] / far[3] direction = far_pos - near_pos direction = direction / np.linalg.norm(direction) return Vec3(near_pos), Vec3(direction)
[docs] def ray_intersect_sphere(origin, direction, center, radius: float): """Ray-sphere intersection. Returns distance t or None if no hit.""" if not isinstance(origin, Vec3): origin = Vec3(origin) if not isinstance(direction, Vec3): direction = Vec3(direction) if not isinstance(center, Vec3): center = Vec3(center) oc = origin - center b = oc.dot(direction) c = oc.dot(oc) - radius * radius discriminant = b * b - c if discriminant < 0: return None sqrt_d = math.sqrt(discriminant) t = -b - sqrt_d if t < 0: t = -b + sqrt_d return t if t >= 0 else None