Source code for simvx.graphics.scene.frustum
"""View frustum culling utilities."""
from __future__ import annotations
import logging
import numpy as np
log = logging.getLogger(__name__)
__all__ = ["Frustum"]
[docs]
class Frustum:
"""View frustum defined by 6 planes for culling."""
def __init__(self):
self.planes = np.zeros((6, 4), dtype=np.float32)
[docs]
def test_sphere(self, center: np.ndarray, radius: float) -> bool:
"""Test if sphere intersects or is inside the frustum."""
dists = self.planes[:, :3] @ center + self.planes[:, 3]
return bool(np.all(dists >= -radius))
[docs]
def test_aabb(self, min_bounds: np.ndarray, max_bounds: np.ndarray) -> bool:
"""Test if AABB intersects the frustum."""
for i in range(6):
normal = self.planes[i, :3]
# Positive vertex: furthest in plane's positive direction
p = np.where(normal >= 0, max_bounds, min_bounds)
if np.dot(normal, p) + self.planes[i, 3] < 0:
return False
return True
[docs]
def cull_spheres(
self,
centers: np.ndarray,
radii: np.ndarray,
) -> np.ndarray:
"""Vectorized frustum cull for many bounding spheres.
Args:
centers: (N, 3) array of sphere centers.
radii: (N,) array of radii.
Returns: boolean mask (N,) — True = visible.
"""
# (6, N) = (6, 3) @ (3, N) + (6, 1)
dists = self.planes[:, :3] @ centers.T + self.planes[:, 3:4]
# Visible if all planes pass: dist >= -radius
return np.all(dists >= -radii[np.newaxis, :], axis=0)