nightshade 0.8.2

A cross-platform data-oriented game engine.
Documentation
use crate::ecs::world::{Mat4, Quat, Vec3, Vec4};
use nalgebra_glm::Mat3;

#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
#[serde(default)]
pub struct OrientedBoundingBox {
    pub center: Vec3,
    pub half_extents: Vec3,
    pub orientation: Quat,
}

impl Default for OrientedBoundingBox {
    fn default() -> Self {
        Self {
            center: Vec3::zeros(),
            half_extents: Vec3::new(0.5, 0.5, 0.5),
            orientation: Quat::identity(),
        }
    }
}

impl OrientedBoundingBox {
    pub fn new(center: Vec3, half_extents: Vec3, orientation: Quat) -> Self {
        Self {
            center,
            half_extents,
            orientation,
        }
    }

    pub fn from_aabb(min: Vec3, max: Vec3) -> Self {
        let center = (min + max) * 0.5;
        let half_extents = (max - min) * 0.5;
        Self {
            center,
            half_extents,
            orientation: Quat::identity(),
        }
    }

    pub fn transform(&self, matrix: &Mat4) -> Self {
        let transformed_center =
            (matrix * Vec4::new(self.center.x, self.center.y, self.center.z, 1.0)).xyz();

        let right = Vec3::new(matrix[(0, 0)], matrix[(1, 0)], matrix[(2, 0)]);
        let up = Vec3::new(matrix[(0, 1)], matrix[(1, 1)], matrix[(2, 1)]);
        let forward = Vec3::new(matrix[(0, 2)], matrix[(1, 2)], matrix[(2, 2)]);

        let scale_x = nalgebra_glm::length(&right);
        let scale_y = nalgebra_glm::length(&up);
        let scale_z = nalgebra_glm::length(&forward);

        let rotation_matrix = Mat3::new(
            right.x / scale_x,
            up.x / scale_y,
            forward.x / scale_z,
            right.y / scale_x,
            up.y / scale_y,
            forward.y / scale_z,
            right.z / scale_x,
            up.z / scale_y,
            forward.z / scale_z,
        );

        let orientation = nalgebra_glm::mat3_to_quat(&rotation_matrix) * self.orientation;

        let scaled_half_extents = Vec3::new(
            self.half_extents.x * scale_x,
            self.half_extents.y * scale_y,
            self.half_extents.z * scale_z,
        );

        Self {
            center: transformed_center,
            half_extents: scaled_half_extents,
            orientation,
        }
    }

    pub fn get_corners(&self) -> [Vec3; 8] {
        let rotation_matrix = nalgebra_glm::quat_to_mat3(&self.orientation);

        let x_axis = rotation_matrix.column(0).into_owned() * self.half_extents.x;
        let y_axis = rotation_matrix.column(1).into_owned() * self.half_extents.y;
        let z_axis = rotation_matrix.column(2).into_owned() * self.half_extents.z;

        [
            self.center - x_axis - y_axis - z_axis,
            self.center + x_axis - y_axis - z_axis,
            self.center - x_axis + y_axis - z_axis,
            self.center + x_axis + y_axis - z_axis,
            self.center - x_axis - y_axis + z_axis,
            self.center + x_axis - y_axis + z_axis,
            self.center - x_axis + y_axis + z_axis,
            self.center + x_axis + y_axis + z_axis,
        ]
    }

    pub fn intersect_ray(&self, ray_origin: Vec3, ray_direction: Vec3) -> Option<f32> {
        let inv_orientation = self.orientation.conjugate();
        let local_ray_origin =
            nalgebra_glm::quat_rotate_vec3(&inv_orientation, &(ray_origin - self.center));
        let local_ray_direction = nalgebra_glm::quat_rotate_vec3(&inv_orientation, &ray_direction);

        let inv_dir = Vec3::new(
            if local_ray_direction.x.abs() > 1e-10 {
                1.0 / local_ray_direction.x
            } else {
                f32::INFINITY
            },
            if local_ray_direction.y.abs() > 1e-10 {
                1.0 / local_ray_direction.y
            } else {
                f32::INFINITY
            },
            if local_ray_direction.z.abs() > 1e-10 {
                1.0 / local_ray_direction.z
            } else {
                f32::INFINITY
            },
        );

        let t_min = (-self.half_extents - local_ray_origin).component_mul(&inv_dir);
        let t_max = (self.half_extents - local_ray_origin).component_mul(&inv_dir);

        let t1 = Vec3::new(
            t_min.x.min(t_max.x),
            t_min.y.min(t_max.y),
            t_min.z.min(t_max.z),
        );

        let t2 = Vec3::new(
            t_min.x.max(t_max.x),
            t_min.y.max(t_max.y),
            t_min.z.max(t_max.z),
        );

        let t_near = t1.x.max(t1.y).max(t1.z);
        let t_far = t2.x.min(t2.y).min(t2.z);

        if t_near <= t_far && t_far >= 0.0 {
            if t_near >= 0.0 {
                Some(t_near)
            } else {
                Some(t_far)
            }
        } else {
            None
        }
    }
}

#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
#[serde(default)]
pub struct BoundingVolume {
    pub obb: OrientedBoundingBox,
    pub sphere_radius: f32,
}

impl Default for BoundingVolume {
    fn default() -> Self {
        Self {
            obb: OrientedBoundingBox::default(),
            sphere_radius: 1.0,
        }
    }
}

impl BoundingVolume {
    pub fn new(obb: OrientedBoundingBox, sphere_radius: f32) -> Self {
        Self { obb, sphere_radius }
    }

    pub fn from_mesh_type(mesh_type: &str) -> Self {
        let (half_extents, sphere_radius) = match mesh_type {
            "Cube" => {
                let half = 0.5;
                (Vec3::new(half, half, half), half * 1.732_050_8)
            }
            "Sphere" => {
                let radius = 1.0;
                (Vec3::new(radius, radius, radius), radius)
            }
            "Cylinder" => {
                let radius = 0.5f32;
                let half_height = 0.5f32;
                (
                    Vec3::new(radius, half_height, radius),
                    (radius * radius + half_height * half_height).sqrt(),
                )
            }
            "Torus" => {
                let major = 1.0;
                let minor = 0.3;
                let outer = major + minor;
                (Vec3::new(outer, minor, outer), outer)
            }
            "Cone" => {
                let radius = 0.5f32;
                let half_height = 0.5f32;
                (
                    Vec3::new(radius, half_height, radius),
                    (radius * radius + half_height * half_height).sqrt(),
                )
            }
            "Plane" | "SubdividedPlane" => (Vec3::new(1.0, 1.0, 1.0), 1.732),
            _ => (Vec3::new(0.5, 0.5, 0.5), 0.866),
        };

        Self {
            obb: OrientedBoundingBox::new(Vec3::zeros(), half_extents, Quat::identity()),
            sphere_radius,
        }
    }

    pub fn transform(&self, matrix: &Mat4) -> Self {
        let transformed_obb = self.obb.transform(matrix);

        let scale_x =
            nalgebra_glm::length(&Vec3::new(matrix[(0, 0)], matrix[(1, 0)], matrix[(2, 0)]));
        let scale_y =
            nalgebra_glm::length(&Vec3::new(matrix[(0, 1)], matrix[(1, 1)], matrix[(2, 1)]));
        let scale_z =
            nalgebra_glm::length(&Vec3::new(matrix[(0, 2)], matrix[(1, 2)], matrix[(2, 2)]));
        let max_scale = scale_x.max(scale_y).max(scale_z);

        Self {
            obb: transformed_obb,
            sphere_radius: self.sphere_radius * max_scale,
        }
    }
}