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 phys_geom::volume::ComputeVolume;
use glam_det::{Point3, Vec3};

use super::traits::{ComplexShapeTrait, ShapePlugin};
use crate::bvh::bvh::Bvh4;
use crate::traits::Expansion;
use crate::{
    Aabb3, ComputeAabb3, ContainsPoint, Ray, Raycast, RaycastHitResult, SignedDistanceToPoint,
};

#[derive(Default, Clone, Debug)]
pub struct Mesh {
    tree: Bvh4<u32>,
    vertices: Vec<Point3>,
    triangles: Vec<u32>,
    scale: Vec3,
    inverse_scale: Vec3,
}

impl Mesh {
    pub const PLUGIN_ID: u64 = 0x5fd1243d;
}
impl ComputeAabb3 for Mesh {
    fn compute_aabb(&self) -> Aabb3 {
        phys_geom::volume::compute_mesh_bound(self.vertices.iter().map(Point3::to_array))
    }
}

impl Raycast for Mesh {
    fn raycast(
        &self,
        _local_ray: Ray,
        _max_distance: f32,
        _discard_inside_hit: bool,
    ) -> Option<RaycastHitResult> {
        todo!("issue #1201");
    }
}

impl Expansion for Mesh {
    #[inline]
    fn max_radius_and_max_angular_expansion(&self) -> (f32, f32) {
        todo!()
    }
}

impl ComputeVolume for Mesh {
    #[inline]
    fn compute_volume(&self) -> phys_geom::math::Real {
        todo!()
    }
}

impl SignedDistanceToPoint for Mesh {
    fn signed_distance_to_point(&self, _local_point: Point3) -> f32 {
        todo!()
    }
}

impl ContainsPoint for Mesh {
    fn contains_point_with_threshold(
        &self,
        _local_point: Point3,
        _threshold: f32,
    ) -> crate::ContainsResult {
        todo!()
    }
}

impl ShapePlugin for Mesh {
    const PLUGIN_ID: u64 = Mesh::PLUGIN_ID;
}
impl ComplexShapeTrait for Mesh {}
#[cfg(test)]
mod tests {
    use approx_det::assert_relative_eq;
    use chull_adapt::ConvexHullConstructState;
    use phys_geom::math::Point3;
    use phys_geom::volume::ComputeVolume;
    use glam_det::Vec3;
    use wasm_bindgen_test::*;

    use crate::bvh::bvh::Bvh4;
    use crate::shapes::CuboidExt;
    use crate::traits::Expansion;
    use crate::{ComputeAabb3, ContainsPoint, ConvexMesh, Cuboid, Mesh, SignedDistanceToPoint};
    wasm_bindgen_test_configure!(run_in_browser);

    #[test]
    #[wasm_bindgen_test]
    fn mesh_compute_bound() {
        let _ = env_logger::builder().is_test(true).try_init();

        let cuboid = Cuboid::UNIT;
        let points = vec![
            cuboid.get_vertex(0),
            cuboid.get_vertex(1),
            cuboid.get_vertex(2),
            cuboid.get_vertex(3),
            cuboid.get_vertex(4),
            cuboid.get_vertex(5),
            cuboid.get_vertex(6),
            cuboid.get_vertex(7),
        ];

        let mesh = Mesh {
            tree: Bvh4::default(),
            vertices: points,
            triangles: vec![],
            scale: Vec3::default(),
            inverse_scale: Vec3::default(),
        };
        let bound0 = mesh.compute_aabb();
        let bound1 = cuboid.compute_aabb();
        assert_eq!(bound0.min(), bound1.min());
        assert_eq!(bound0.max(), bound1.max());
    }

    #[test]
    #[wasm_bindgen_test]
    #[should_panic]
    fn test_compute_expand() {
        let _ = env_logger::builder().is_test(true).try_init();

        let shape = Mesh {
            tree: Bvh4::default(),
            vertices: vec![],
            triangles: vec![],
            scale: Vec3::default(),
            inverse_scale: Vec3::default(),
        };
        let (max_radius, max_angular_expansion) = shape.max_radius_and_max_angular_expansion();
        assert_relative_eq!(max_radius, 0f32);
        assert_relative_eq!(max_angular_expansion, 0f32);
    }

    #[test]
    #[wasm_bindgen_test]
    fn test_convex_mesh() {
        let _ = env_logger::builder().is_test(true).try_init();

        let cuboid = Cuboid::UNIT;
        let points = vec![
            cuboid.get_vertex(0),
            cuboid.get_vertex(1),
            cuboid.get_vertex(2),
            cuboid.get_vertex(3),
            cuboid.get_vertex(4),
            cuboid.get_vertex(4),
            cuboid.get_vertex(5),
            cuboid.get_vertex(6),
            cuboid.get_vertex(7),
            cuboid.get_vertex(2),
            cuboid.get_vertex(3),
            cuboid.get_vertex(4),
            cuboid.get_vertex(4),
            cuboid.get_vertex(5),
            cuboid.get_vertex(6),
            cuboid.get_vertex(7),
            glam_det::Point3::new(0.0, 0.0, 0.0),
        ];

        let (mesh, state) = ConvexMesh::new(&points).unwrap();
        if state == ConvexHullConstructState::Success {
            let num_triangles = mesh.num_triangles();
            assert_eq!(12, num_triangles);
            let num_vertices = mesh.num_vertices();
            assert_eq!(8, num_vertices);

            let vertices = mesh.vertices();
            assert_eq!(8, vertices.len());
            let triangles = mesh.triangles();
            assert_eq!(36, triangles.len());
        } else {
            panic!("Build convex mesh from cuboid failed!")
        }
    }

    #[test]
    #[wasm_bindgen_test]
    #[should_panic]
    fn test_mesh_volume() {
        let _ = env_logger::builder().is_test(true).try_init();

        let cuboid = Cuboid::UNIT;
        let points = vec![
            cuboid.get_vertex(0),
            cuboid.get_vertex(1),
            cuboid.get_vertex(2),
            cuboid.get_vertex(3),
            cuboid.get_vertex(4),
            cuboid.get_vertex(5),
            cuboid.get_vertex(6),
            cuboid.get_vertex(7),
        ];

        let mesh = Mesh {
            tree: Bvh4::default(),
            vertices: points,
            triangles: vec![],
            scale: Vec3::default(),
            inverse_scale: Vec3::default(),
        };
        mesh.compute_volume();
    }

    #[test]
    #[wasm_bindgen_test]
    #[should_panic]
    fn test_mesh_signed_distance() {
        let _ = env_logger::builder().is_test(true).try_init();

        let cuboid = Cuboid::UNIT;
        let points = vec![
            cuboid.get_vertex(0),
            cuboid.get_vertex(1),
            cuboid.get_vertex(2),
            cuboid.get_vertex(3),
            cuboid.get_vertex(4),
            cuboid.get_vertex(5),
            cuboid.get_vertex(6),
            cuboid.get_vertex(7),
        ];

        let mesh = Mesh {
            tree: Bvh4::default(),
            vertices: points,
            triangles: vec![],
            scale: Vec3::default(),
            inverse_scale: Vec3::default(),
        };
        mesh.signed_distance_to_point(Point3::new(0.0, 0.0, 0.0));
    }

    #[test]
    #[wasm_bindgen_test]
    #[should_panic]
    fn test_mesh_contains_point() {
        let _ = env_logger::builder().is_test(true).try_init();

        let cuboid = Cuboid::UNIT;
        let points = vec![
            cuboid.get_vertex(0),
            cuboid.get_vertex(1),
            cuboid.get_vertex(2),
            cuboid.get_vertex(3),
            cuboid.get_vertex(4),
            cuboid.get_vertex(5),
            cuboid.get_vertex(6),
            cuboid.get_vertex(7),
        ];

        let mesh = Mesh {
            tree: Bvh4::default(),
            vertices: points,
            triangles: vec![],
            scale: Vec3::default(),
            inverse_scale: Vec3::default(),
        };
        mesh.contains_point_with_threshold(Point3::new(0.0, 0.0, 0.0), 0.01f32);
    }
}