use crate::core::engine::rendering::raytracing::{Camera, Scene};
use super::frustum::{CullResult, Frustum};
use super::helpers::{is_backfacing, is_contribution_negligible, triangle_normal};
#[derive(Debug, Clone, Copy)]
pub struct CullStats {
pub total_spheres: usize,
pub total_triangles: usize,
pub culled_spheres: usize,
pub culled_triangles: usize,
}
impl CullStats {
pub fn sphere_ratio(&self) -> f64 {
if self.total_spheres == 0 {
0.0
} else {
self.culled_spheres as f64 / self.total_spheres as f64
}
}
pub fn triangle_ratio(&self) -> f64 {
if self.total_triangles == 0 {
0.0
} else {
self.culled_triangles as f64 / self.total_triangles as f64
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct SceneCuller {
max_distance: f64,
distance_margin: f64,
min_screen_pixels: f64,
fov_rad: f64,
screen_height: f64,
backface_cull: bool,
}
impl SceneCuller {
pub fn new(max_distance: f64) -> Self {
Self {
max_distance,
distance_margin: 6.0,
min_screen_pixels: 0.5,
fov_rad: 60.0_f64.to_radians(),
screen_height: 720.0,
backface_cull: true,
}
}
pub fn with_screen_params(mut self, fov_rad: f64, screen_height: f64) -> Self {
self.fov_rad = fov_rad;
self.screen_height = screen_height;
self
}
pub fn with_contribution_threshold(mut self, min_pixels: f64) -> Self {
self.min_screen_pixels = min_pixels;
self
}
pub fn with_backface_culling(mut self, enabled: bool) -> Self {
self.backface_cull = enabled;
self
}
pub fn cull_scene(&self, scene: &Scene, camera: &Camera) -> Scene {
let cull_limit = self.max_distance + self.distance_margin;
let objects = scene
.objects
.iter()
.copied()
.filter(|object| {
let distance = (object.center - camera.origin).length() - object.radius;
if distance > cull_limit {
return false;
}
if distance > 0.0
&& is_contribution_negligible(
object.radius,
distance,
self.fov_rad,
self.screen_height,
self.min_screen_pixels,
)
{
return false;
}
true
})
.collect();
let view_dir = camera.direction.normalize();
let triangles = scene
.triangles
.iter()
.copied()
.filter(|tri| {
let centroid = (tri.a + tri.b + tri.c) / 3.0;
let dist = (centroid - camera.origin).length();
if dist > cull_limit {
return false;
}
let to_tri = (centroid - camera.origin).normalize();
let view_dot = view_dir.dot(to_tri);
if view_dot < -0.15 && dist > self.max_distance * 0.5 {
return false;
}
if self.backface_cull {
let normal = triangle_normal(tri.a, tri.b, tri.c);
if is_backfacing(normal, to_tri) {
return false;
}
}
true
})
.collect();
Scene {
objects,
triangles,
sun: scene.sun,
area_lights: scene.area_lights.clone(),
sky_top: scene.sky_top,
sky_bottom: scene.sky_bottom,
exposure: scene.exposure,
volume: scene.volume,
hdri: scene.hdri.clone(),
solar_elevation: scene.solar_elevation,
}
}
pub fn cull_scene_with_stats(&self, scene: &Scene, camera: &Camera) -> (Scene, CullStats) {
let total_spheres = scene.objects.len();
let total_triangles = scene.triangles.len();
let culled = self.cull_scene(scene, camera);
let stats = CullStats {
total_spheres,
total_triangles,
culled_spheres: total_spheres - culled.objects.len(),
culled_triangles: total_triangles - culled.triangles.len(),
};
(culled, stats)
}
pub fn cull_with_frustum(&self, scene: &Scene, frustum: &Frustum) -> Scene {
let objects = scene
.objects
.iter()
.copied()
.filter(|obj| frustum.contains_sphere(obj.center, obj.radius) != CullResult::Outside)
.collect();
let triangles = scene
.triangles
.iter()
.copied()
.filter(|tri| {
let centroid = (tri.a + tri.b + tri.c) / 3.0;
let half = ((tri.a - centroid).length())
.max((tri.b - centroid).length())
.max((tri.c - centroid).length());
frustum.contains_sphere(centroid, half) != CullResult::Outside
})
.collect();
Scene {
objects,
triangles,
sun: scene.sun,
area_lights: scene.area_lights.clone(),
sky_top: scene.sky_top,
sky_bottom: scene.sky_bottom,
exposure: scene.exposure,
volume: scene.volume,
hdri: scene.hdri.clone(),
solar_elevation: scene.solar_elevation,
}
}
}