phys-collision 2.0.1-beta.0

Provides collision detection ability
// Copyright (C) 2020-2025 phys-collision authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use glam_det::{Isometry3, Point3, Vec3, Vec3x4};
pub use phys_geom::shape::Cuboid;

use crate::ray::{Raycast, RaycastHitResult};
use crate::traits::{
    BaseShapeWide, ContainsPoint, ContainsResult, CreateShapeWide, Expansion, MinkowskiSupport,
    MinkowskiSupportResult, MinkowskiSupportResultWide, MinkowskiSupportWide,
    SignedDistanceToPoint,
};
use crate::{CuboidWide, Ray, ShapeContainer};

#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[allow(dead_code)]
enum PlaneNormalAxis {
    X,
    Y,
    Z,
}
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PlanerCuboid {
    value: Cuboid,
    plane_normal_axis: PlaneNormalAxis,
}

impl Default for PlanerCuboid {
    fn default() -> Self {
        Self {
            value: Cuboid::new([1.0, 0.01, 1.0]),
            plane_normal_axis: PlaneNormalAxis::Y,
        }
    }
}

impl MinkowskiSupport for PlanerCuboid {
    fn support_point(&self, direction: Vec3, transform: &Isometry3) -> MinkowskiSupportResult {
        self.value.support_point(direction, transform)
    }
}

impl MinkowskiSupportWide for PlanerCuboidWide {
    fn support_point_local(
        &self,
        local_direction: Vec3x4,
        _: Option<&ShapeContainer>,
    ) -> MinkowskiSupportResultWide {
        self.value.support_point_local(local_direction, None)
    }
}

impl Expansion for PlanerCuboid {
    #[inline]
    fn max_radius_and_max_angular_expansion(&self) -> (f32, f32) {
        self.value.max_radius_and_max_angular_expansion()
    }
}

impl Raycast for PlanerCuboid {
    fn raycast(
        &self,
        local_ray: Ray,
        max_distance: f32,
        discard_inside_hit: bool,
    ) -> Option<RaycastHitResult> {
        self.value
            .raycast(local_ray, max_distance, discard_inside_hit)
    }
}

impl ContainsPoint for PlanerCuboid {
    #[inline]
    fn contains_point_with_threshold(&self, local_point: Point3, threshold: f32) -> ContainsResult {
        self.value
            .contains_point_with_threshold(local_point, threshold)
    }
}

impl SignedDistanceToPoint for PlanerCuboid {
    fn signed_distance_to_point(&self, local_point: Point3) -> f32 {
        self.value.signed_distance_to_point(local_point)
    }
}

#[derive(Debug)]
pub struct PlanerCuboidWide {
    pub value: CuboidWide,
}

impl Default for PlanerCuboidWide {
    #[inline]
    fn default() -> Self {
        Self {
            value: CuboidWide::default(),
        }
    }
}

impl BaseShapeWide for PlanerCuboidWide {
    type TShape = PlanerCuboid;
}

macro_rules! impl_planer_cuboid_wide {
    ($($num:tt),*) => {
            $(
                impl CreateShapeWide<$num> for PlanerCuboidWide {
                    fn create<'a>(iter: impl Iterator<Item=&'a Self::TShape> + Clone) -> Self where Self::TShape: 'a {
                        Self {
                            value: <CuboidWide as CreateShapeWide<$num>>::create(iter.map(|shape| &shape.value)),
                        }
                    }
                }
            )*
    };
}

impl_planer_cuboid_wide!(1, 2, 3, 4);

#[cfg(test)]
mod tests {
    use approx_det::assert_relative_eq;

    use super::*;
    use crate::glam::{Isometry3, Point3, Vec3};

    #[test]
    fn default_values_and_expansion() {
        let pc = PlanerCuboid::default();
        // Default inner cuboid half-extents and axis
        // half extents should match defaults; Cuboid exposes getters via phys-geom API
        let he = pc.value.half_length();
        assert_relative_eq!(he[0], 0.5);
        assert_relative_eq!(he[1], 0.005);
        assert_relative_eq!(he[2], 0.5);
        assert_eq!(pc.plane_normal_axis, PlaneNormalAxis::Y);

        // Delegation to Expansion
        let (max_r, max_ang) = pc.max_radius_and_max_angular_expansion();
        assert!(max_r >= 0.0);
        assert!(max_ang >= 0.0);
    }

    #[test]
    fn minkowski_support_and_distance_and_contains() {
        let pc = PlanerCuboid::default();
        let iso = Isometry3::default();

        // MinkowskiSupport delegates to inner cuboid
        let dir = Vec3::new(1.0, 0.0, 0.0);
        let sp = pc.support_point(dir, &iso);
        // Should be on +X face, so x >= 0
        assert!(sp.point.x >= 0.0);

        // Signed distance and contains
        let inside = Point3::new(0.0, 0.0, 0.0);
        let sd_inside = pc.signed_distance_to_point(inside);
        assert!(sd_inside <= 0.0);

        let outside = Point3::new(10.0, 0.0, 0.0);
        let sd_outside = pc.signed_distance_to_point(outside);
        assert!(sd_outside >= 0.0);

        let contains = pc.contains_point_with_threshold(inside, 1.0e-5);
        assert!(matches!(contains, ContainsResult::Inside));
    }

    #[test]
    fn wide_support_and_create_shape_wide() {
        // Prepare shapes
        let shapes = [
            PlanerCuboid::default(),
            PlanerCuboid::default(),
            PlanerCuboid::default(),
            PlanerCuboid::default(),
        ];

        // Create wide for different widths via the macro-generated impls
        let _w1: PlanerCuboidWide = <PlanerCuboidWide as CreateShapeWide<1>>::create(shapes.iter());
        let _w2: PlanerCuboidWide = <PlanerCuboidWide as CreateShapeWide<2>>::create(shapes.iter());
        let _w3: PlanerCuboidWide = <PlanerCuboidWide as CreateShapeWide<3>>::create(shapes.iter());
        let _w4: PlanerCuboidWide = <PlanerCuboidWide as CreateShapeWide<4>>::create(shapes.iter());

        // Wide support delegates to inner wide cuboid
        let wide = PlanerCuboidWide::default();
        let dir4 = crate::traits::ArrayGetter::<4>::get_vec3x4_from_iter(
            [
                Vec3::new(1.0, 0.0, 0.0),
                Vec3::new(1.0, 0.0, 0.0),
                Vec3::new(1.0, 0.0, 0.0),
                Vec3::new(1.0, 0.0, 0.0),
            ]
            .into_iter(),
        );
        let sp4 = wide.support_point_local(dir4, None);
        // Ensure the call path executes and returns a sane point (finite)
        // We can't easily extract SIMD lanes here without extra helpers, so sanity check sums
        let px = sp4.point.x;
        let py = sp4.point.y;
        let pz = sp4.point.z;
        let _ = (px, py, pz); // touch all fields to cover struct paths
    }
}