use crate::math::{Isometry, Point, Real, Vector};
use crate::shape::FeatureId;
#[cfg(feature = "rkyv")]
use rkyv::{bytecheck, CheckBytes};
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(
    feature = "rkyv",
    derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, CheckBytes),
    archive(as = "Self")
)]
#[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))]
#[repr(C)]
pub struct Ray {
    pub origin: Point<Real>,
    pub dir: Vector<Real>,
}
impl Ray {
    pub fn new(origin: Point<Real>, dir: Vector<Real>) -> Ray {
        Ray { origin, dir }
    }
    #[inline]
    pub fn transform_by(&self, m: &Isometry<Real>) -> Self {
        Self::new(m * self.origin, m * self.dir)
    }
    #[inline]
    pub fn inverse_transform_by(&self, m: &Isometry<Real>) -> Self {
        Self::new(
            m.inverse_transform_point(&self.origin),
            m.inverse_transform_vector(&self.dir),
        )
    }
    #[inline]
    pub fn translate_by(&self, v: Vector<Real>) -> Self {
        Self::new(self.origin + v, self.dir)
    }
    #[inline]
    pub fn point_at(&self, t: Real) -> Point<Real> {
        self.origin + self.dir * t
    }
}
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(
    feature = "rkyv",
    derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, CheckBytes),
    archive(as = "Self")
)]
pub struct RayIntersection {
    pub toi: Real,
    pub normal: Vector<Real>,
    pub feature: FeatureId,
}
impl RayIntersection {
    #[inline]
    #[cfg(feature = "dim3")]
    pub fn new(toi: Real, normal: Vector<Real>, feature: FeatureId) -> RayIntersection {
        RayIntersection {
            toi,
            normal,
            feature,
        }
    }
    #[inline]
    #[cfg(feature = "dim2")]
    pub fn new(toi: Real, normal: Vector<Real>, feature: FeatureId) -> RayIntersection {
        RayIntersection {
            toi,
            normal,
            feature,
        }
    }
    #[inline]
    pub fn transform_by(&self, transform: &Isometry<Real>) -> Self {
        RayIntersection {
            toi: self.toi,
            normal: transform * self.normal,
            feature: self.feature,
        }
    }
}
pub trait RayCast {
    fn cast_local_ray(&self, ray: &Ray, max_toi: Real, solid: bool) -> Option<Real> {
        self.cast_local_ray_and_get_normal(ray, max_toi, solid)
            .map(|inter| inter.toi)
    }
    fn cast_local_ray_and_get_normal(
        &self,
        ray: &Ray,
        max_toi: Real,
        solid: bool,
    ) -> Option<RayIntersection>;
    #[inline]
    fn intersects_local_ray(&self, ray: &Ray, max_toi: Real) -> bool {
        self.cast_local_ray(ray, max_toi, true).is_some()
    }
    fn cast_ray(&self, m: &Isometry<Real>, ray: &Ray, max_toi: Real, solid: bool) -> Option<Real> {
        let ls_ray = ray.inverse_transform_by(m);
        self.cast_local_ray(&ls_ray, max_toi, solid)
    }
    fn cast_ray_and_get_normal(
        &self,
        m: &Isometry<Real>,
        ray: &Ray,
        max_toi: Real,
        solid: bool,
    ) -> Option<RayIntersection> {
        let ls_ray = ray.inverse_transform_by(m);
        self.cast_local_ray_and_get_normal(&ls_ray, max_toi, solid)
            .map(|inter| inter.transform_by(m))
    }
    #[inline]
    fn intersects_ray(&self, m: &Isometry<Real>, ray: &Ray, max_toi: Real) -> bool {
        let ls_ray = ray.inverse_transform_by(m);
        self.intersects_local_ray(&ls_ray, max_toi)
    }
}