use glam::{Mat4, Vec3, Vec3A, Vec4Swizzles};
#[derive(Debug, Clone, Copy)]
#[repr(C, align(16))]
pub struct BoundingSphere {
pub center: Vec3,
pub radius: f32,
}
impl BoundingSphere {
pub fn from_mesh(mesh: &[Vec3]) -> Self {
let center = find_mesh_center(mesh);
let radius = find_mesh_bounding_sphere_radius(center, mesh);
Self {
center: Vec3::from(center),
radius,
}
}
pub fn apply_transform(self, model_view: Mat4) -> Self {
let max_scale = model_view
.x_axis
.xyz()
.length_squared()
.max(
model_view
.y_axis
.xyz()
.length_squared()
.max(model_view.z_axis.xyz().length_squared()),
)
.sqrt();
let center = model_view * self.center.extend(1.0);
Self {
center: center.truncate(),
radius: max_scale * self.radius,
}
}
}
fn find_mesh_center(mesh: &[Vec3]) -> Vec3A {
let first = if let Some(first) = mesh.first() {
*first
} else {
return Vec3A::ZERO;
};
let mut max = Vec3A::from(first);
let mut min = max;
for pos in mesh.iter().skip(1) {
let pos = Vec3A::from(*pos);
max = max.max(pos);
min = min.min(pos);
}
(max + min) / 2.0
}
fn find_mesh_bounding_sphere_radius(mesh_center: Vec3A, mesh: &[Vec3]) -> f32 {
mesh.iter().fold(0.0, |distance, pos| {
distance.max((Vec3A::from(*pos) - mesh_center).length())
})
}
#[derive(Debug, Copy, Clone)]
#[repr(C, align(16))]
pub struct ShaderPlane {
pub abc: Vec3,
pub d: f32,
}
impl ShaderPlane {
pub fn new(a: f32, b: f32, c: f32, d: f32) -> Self {
Self {
abc: Vec3::new(a, b, c),
d,
}
}
pub fn normalize(mut self) -> Self {
let mag = self.abc.length();
self.abc /= mag;
self.d /= mag;
self
}
pub fn distance(self, point: Vec3) -> f32 {
self.abc.dot(point) + self.d
}
}
#[derive(Debug, Copy, Clone)]
#[repr(C, align(16))]
pub struct ShaderFrustum {
left: ShaderPlane,
right: ShaderPlane,
top: ShaderPlane,
bottom: ShaderPlane,
near: ShaderPlane,
}
impl ShaderFrustum {
pub fn from_matrix(matrix: Mat4) -> Self {
let mat_arr = matrix.to_cols_array_2d();
let left = ShaderPlane::new(
mat_arr[0][3] + mat_arr[0][0],
mat_arr[1][3] + mat_arr[1][0],
mat_arr[2][3] + mat_arr[2][0],
mat_arr[3][3] + mat_arr[3][0],
);
let right = ShaderPlane::new(
mat_arr[0][3] - mat_arr[0][0],
mat_arr[1][3] - mat_arr[1][0],
mat_arr[2][3] - mat_arr[2][0],
mat_arr[3][3] - mat_arr[3][0],
);
let top = ShaderPlane::new(
mat_arr[0][3] - mat_arr[0][1],
mat_arr[1][3] - mat_arr[1][1],
mat_arr[2][3] - mat_arr[2][1],
mat_arr[3][3] - mat_arr[3][1],
);
let bottom = ShaderPlane::new(
mat_arr[0][3] + mat_arr[0][1],
mat_arr[1][3] + mat_arr[1][1],
mat_arr[2][3] + mat_arr[2][1],
mat_arr[3][3] + mat_arr[3][1],
);
let near = ShaderPlane::new(
mat_arr[0][3] - mat_arr[0][2],
mat_arr[1][3] - mat_arr[1][2],
mat_arr[2][3] - mat_arr[2][2],
mat_arr[3][3] - mat_arr[3][2],
);
Self {
left: left.normalize(),
right: right.normalize(),
top: top.normalize(),
bottom: bottom.normalize(),
near: near.normalize(),
}
}
pub fn contains_sphere(&self, sphere: BoundingSphere) -> bool {
let neg_radius = -sphere.radius;
let array = [self.left, self.right, self.top, self.bottom, self.near];
for plane in &array {
let inside = plane.distance(sphere.center) >= neg_radius;
if !inside {
return false;
}
}
true
}
}