use crate::camera3d::Camera3D;
use crate::mesh3d::Vec3;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Frustum {
pub planes: [Plane; 6],
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct Plane {
pub normal: Vec3,
pub distance: f32,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct BoundingBox {
pub min: Vec3,
pub max: Vec3,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct BoundingSphere {
pub center: Vec3,
pub radius: f32,
}
impl Frustum {
pub fn from_camera(camera: &Camera3D, aspect_ratio: f32) -> Self {
let fov_rad = camera.fov.to_radians();
let near = camera.near;
let far = camera.far;
let near_height = 2.0 * (fov_rad / 2.0).tan() * near;
let near_width = near_height * aspect_ratio;
let far_height = 2.0 * (fov_rad / 2.0).tan() * far;
let _far_width = far_height * aspect_ratio;
let forward = camera.get_forward();
let right = camera.get_right();
let up = camera.get_up();
let near_center = camera.position + forward * near;
let far_center = camera.position + forward * far;
let planes = [
Plane {
normal: forward,
distance: -forward.dot(&near_center),
},
Plane {
normal: -forward,
distance: forward.dot(&far_center),
},
Plane {
normal: (forward * near + right * (near_width / 2.0))
.cross(&up)
.normalize(),
distance: 0.0,
},
Plane {
normal: up
.cross(&(forward * near - right * (near_width / 2.0)))
.normalize(),
distance: 0.0,
},
Plane {
normal: (forward * near - up * (near_height / 2.0))
.cross(&right)
.normalize(),
distance: 0.0,
},
Plane {
normal: right
.cross(&(forward * near + up * (near_height / 2.0)))
.normalize(),
distance: 0.0,
},
];
Self { planes }
}
pub fn contains_point(&self, point: Vec3) -> bool {
for plane in &self.planes {
if plane.distance_to_point(point) < 0.0 {
return false;
}
}
true
}
pub fn contains_sphere(&self, sphere: &BoundingSphere) -> bool {
for plane in &self.planes {
if plane.distance_to_point(sphere.center) < -sphere.radius {
return false;
}
}
true
}
pub fn contains_box(&self, bbox: &BoundingBox) -> bool {
for plane in &self.planes {
let mut inside = false;
for x in &[bbox.min.x, bbox.max.x] {
for y in &[bbox.min.y, bbox.max.y] {
for z in &[bbox.min.z, bbox.max.z] {
let point = Vec3::new(*x, *y, *z);
if plane.distance_to_point(point) >= 0.0 {
inside = true;
break;
}
}
if inside {
break;
}
}
if inside {
break;
}
}
if !inside {
return false;
}
}
true
}
pub fn intersects_sphere(&self, sphere: &BoundingSphere) -> CullingResult {
let mut intersecting = false;
for plane in &self.planes {
let distance = plane.distance_to_point(sphere.center);
if distance < -sphere.radius {
return CullingResult::Outside;
}
if distance.abs() < sphere.radius {
intersecting = true;
}
}
if intersecting {
CullingResult::Intersecting
} else {
CullingResult::Inside
}
}
}
impl Plane {
pub fn new(normal: Vec3, distance: f32) -> Self {
Self { normal, distance }
}
pub fn distance_to_point(&self, point: Vec3) -> f32 {
self.normal.dot(&point) + self.distance
}
}
impl BoundingBox {
pub fn new(min: Vec3, max: Vec3) -> Self {
Self { min, max }
}
pub fn from_points(points: &[Vec3]) -> Self {
if points.is_empty() {
return Self {
min: Vec3::zero(),
max: Vec3::zero(),
};
}
let mut min = points[0];
let mut max = points[0];
for point in points.iter().skip(1) {
min.x = min.x.min(point.x);
min.y = min.y.min(point.y);
min.z = min.z.min(point.z);
max.x = max.x.max(point.x);
max.y = max.y.max(point.y);
max.z = max.z.max(point.z);
}
Self { min, max }
}
pub fn center(&self) -> Vec3 {
Vec3::new(
(self.min.x + self.max.x) / 2.0,
(self.min.y + self.max.y) / 2.0,
(self.min.z + self.max.z) / 2.0,
)
}
pub fn size(&self) -> Vec3 {
Vec3::new(
self.max.x - self.min.x,
self.max.y - self.min.y,
self.max.z - self.min.z,
)
}
pub fn to_sphere(&self) -> BoundingSphere {
let center = self.center();
let radius = center.distance(&self.max);
BoundingSphere { center, radius }
}
}
impl BoundingSphere {
pub fn new(center: Vec3, radius: f32) -> Self {
Self { center, radius }
}
pub fn from_points(points: &[Vec3]) -> Self {
if points.is_empty() {
return Self {
center: Vec3::zero(),
radius: 0.0,
};
}
let mut center = Vec3::zero();
for point in points {
center.x += point.x;
center.y += point.y;
center.z += point.z;
}
center.x /= points.len() as f32;
center.y /= points.len() as f32;
center.z /= points.len() as f32;
let mut radius: f32 = 0.0;
for point in points {
let distance = center.distance(point);
radius = radius.max(distance);
}
Self { center, radius }
}
pub fn contains_point(&self, point: Vec3) -> bool {
self.center.distance(&point) <= self.radius
}
pub fn intersects(&self, other: &BoundingSphere) -> bool {
let distance = self.center.distance(&other.center);
distance <= self.radius + other.radius
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CullingResult {
Inside,
Outside,
Intersecting,
}
pub struct CullingStats {
pub total_objects: usize,
pub visible_objects: usize,
pub culled_objects: usize,
pub culling_percentage: f32,
}
impl CullingStats {
pub fn new(total: usize, visible: usize) -> Self {
let culled = total.saturating_sub(visible);
let percentage = if total > 0 {
(culled as f32 / total as f32) * 100.0
} else {
0.0
};
Self {
total_objects: total,
visible_objects: visible,
culled_objects: culled,
culling_percentage: percentage,
}
}
}