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 approx_det::assert_relative_eq;
use wasm_bindgen_test::*;

use crate::collision_tasks::tests::common::{
    get_input_wide, Convex4ContactManifold, ConvexHullInput, Mvec3, TestInput,
};
wasm_bindgen_test_configure!(run_in_browser);

use crate::collision_tasks::tests::common::convex4manifold_wide2convex4contact_manifold;
use crate::collision_tasks::ShapeWideTester;
use crate::shapes::ConvexHullWide;
use crate::{ConvexHull, ConvexHullId, ShapeContainer};

type ConvexHullPairInput = TestInput<ConvexHullInput, ConvexHullInput>;
type InputType = TestInput<ConvexHullId, ConvexHullId>;
use crate::traits::PairWideTest;

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

    let file_input = include_bytes!("resource/convex_hull_convex_hull/random.json");
    let input0: ConvexHullPairInput =
        serde_json::from_slice(file_input).expect("file should be proper JSON");
    let convex_hull_a = ConvexHull::new_unchecked(&input0.a.vertices[..]);
    let convex_hull_b = ConvexHull::new_unchecked(&input0.b.vertices[..]);
    let mut container = ShapeContainer::default();
    let id_a = container.add(convex_hull_a);
    let id_b = container.add(convex_hull_b);
    let input0 = InputType {
        a: id_a,
        b: id_b,
        speculative_margin: input0.speculative_margin,
        offset_b: input0.offset_b,
        orientation_a: input0.orientation_a,
        orientation_b: input0.orientation_b,
    };
    let array = [input0];
    let output0 = Convex4ContactManifold {
        OffsetA0: Mvec3 {
            x: 0.5,
            y: 0.5,
            z: 0.5,
        },
        OffsetA1: Mvec3 {
            x: -0.5,
            y: 0.5,
            z: -0.5,
        },
        OffsetA2: Mvec3 {
            x: 0.5,
            y: 0.5,
            z: -0.5,
        },
        OffsetA3: Mvec3 {
            x: -0.5,
            y: 0.5,
            z: 0.5,
        },
        Normal: Mvec3 {
            x: 0.0,
            y: -1.0,
            z: 0.0,
        },
        Depth0: 0.5,
        Depth1: 0.5,
        Depth2: 0.5,
        Depth3: 0.5,
        FeatureId0: 0,
        FeatureId1: 2,
        FeatureId2: 259,
        FeatureId3: 262,
        Contact0Exists: true,
        Contact1Exists: true,
        Contact2Exists: true,
        Contact3Exists: true,
    };
    let outputs = [output0];
    let pair_count = array.len();
    TestWide!(
        ConvexHullWide,
        ConvexHullWide,
        array,
        pair_count,
        outputs,
        convex4manifold_wide2convex4contact_manifold,
        Some(&container)
    );
}

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

    let file_input = include_bytes!("resource/convex_hull_convex_hull/no_collide.json");
    let input0: ConvexHullPairInput =
        serde_json::from_slice(file_input).expect("file should be proper JSON");
    let convex_hull_a = ConvexHull::new_unchecked(&input0.a.vertices[..]);
    let convex_hull_b = ConvexHull::new_unchecked(&input0.b.vertices[..]);
    let mut container = ShapeContainer::default();
    let id_a = container.add(convex_hull_a);
    let id_b = container.add(convex_hull_b);
    let input0 = InputType {
        a: id_a,
        b: id_b,
        speculative_margin: input0.speculative_margin,
        offset_b: input0.offset_b,
        orientation_a: input0.orientation_a,
        orientation_b: input0.orientation_b,
    };
    let array = [input0];
    let output0 = Convex4ContactManifold::default();
    let outputs = [output0];
    let pair_count = array.len();
    TestWide!(
        ConvexHullWide,
        ConvexHullWide,
        array,
        pair_count,
        outputs,
        convex4manifold_wide2convex4contact_manifold,
        Some(&container)
    );
}

//qhull has no "shift to center" param
#[cfg(not(feature = "qhull"))]
mod qhull_test {
    use glam::{UnitQuat, Vec3};
    use wasm_bindgen_test::wasm_bindgen_test;

    use crate::collision_tasks::tests::common::compare_and_print_convex_contact_manifold;
    use crate::{ConvexContactManifold, ConvexHull, PairTest, ShapeContainer, ShapeTester};

    fn build_cuboid_convexhull(
        length: Vec3,
        displaced_vec: Vec3,
        shift_align_center: bool,
    ) -> ConvexHull {
        let points = ConvexHull::generate_displacement_cuboid_vertex(length, displaced_vec);
        if shift_align_center {
            ConvexHull::new_unchecked_and_shift(&points)
        } else {
            ConvexHull::new_unchecked(&points)
        }
    }
    fn execute_displaced_convexhull_pair_test(
        convex_hull_a: ConvexHull,
        convex_hull_b: ConvexHull,
        offset_b: Vec3,
        orientation_a: UnitQuat,
        orientation_b: UnitQuat,
    ) -> ConvexContactManifold {
        let mut container = ShapeContainer::default();
        let hull_id_a = container.add(convex_hull_a);
        let hull_id_b = container.add(convex_hull_b);

        ShapeTester::test(
            hull_id_a,
            hull_id_b,
            1e-5,
            offset_b,
            orientation_a,
            orientation_b,
            Some(&container),
        )
    }

    fn build_displaced_convexhull_pair_test(
        shape_a_displacement_vec: Vec3,
        shape_b_displacement_vec: Vec3,
        rotation_a: UnitQuat,
        rotation_b: UnitQuat,
    ) {
        let _ = env_logger::builder().is_test(true).try_init();

        println!(
            "shape_a_displacement_vec: {shape_a_displacement_vec}; shape_b_displacement_vec: {shape_b_displacement_vec}; rotation_a: {rotation_a}; rotation_b: {rotation_b}"
        );

        let desired_offset_b = Vec3::new(0., 3.0, 0.);

        let shape_a = build_cuboid_convexhull(Vec3::new(1., 2., 3.), Vec3::ZERO, false);
        let shape_b = build_cuboid_convexhull(Vec3::new(4., 5., 6.), Vec3::ZERO, false);
        let mut manifold_native_centering = execute_displaced_convexhull_pair_test(
            shape_a,
            shape_b,
            desired_offset_b,
            rotation_a,
            rotation_b,
        );

        let rotated_shape_a_displacement_vec = rotation_a.mul_vec3(shape_a_displacement_vec);
        let rotated_shape_b_displacement_vec = rotation_b.mul_vec3(shape_b_displacement_vec);
        let adjusted_offset_b =
            desired_offset_b + rotated_shape_a_displacement_vec - rotated_shape_b_displacement_vec;

        //  Matching offset_a to displacement manifold by adding rotated_shape_a_displacement_vec.
        for i in 0..manifold_native_centering.count {
            manifold_native_centering.contacts[i].offset_a += rotated_shape_a_displacement_vec;
        }

        let shape_a =
            build_cuboid_convexhull(Vec3::new(1., 2., 3.), shape_a_displacement_vec, false);
        let shape_b =
            build_cuboid_convexhull(Vec3::new(4., 5., 6.), shape_b_displacement_vec, false);
        let manifold_with_displacement = execute_displaced_convexhull_pair_test(
            shape_a,
            shape_b,
            adjusted_offset_b,
            rotation_a,
            rotation_b,
        );

        let shape_a =
            build_cuboid_convexhull(Vec3::new(1., 2., 3.), shape_a_displacement_vec, true);
        let shape_b =
            build_cuboid_convexhull(Vec3::new(4., 5., 6.), shape_b_displacement_vec, true);
        let manifold_with_displacement_and_shift_align_center =
            execute_displaced_convexhull_pair_test(
                shape_a,
                shape_b,
                adjusted_offset_b,
                rotation_a,
                rotation_b,
            );

        println!("No align:");
        let err_no_align = compare_and_print_convex_contact_manifold(
            &manifold_native_centering,
            &manifold_with_displacement,
        );
        println!("With align:");
        let err_with_align = compare_and_print_convex_contact_manifold(
            &manifold_native_centering,
            &manifold_with_displacement_and_shift_align_center,
        );

        println!("err_no_align: {err_no_align:.2} ,err_with_align: {err_with_align:.2}",);
        assert!(err_no_align >= err_with_align);
        assert_eq!(
            manifold_native_centering.count,
            manifold_with_displacement_and_shift_align_center.count
        );
    }

    #[test]
    #[wasm_bindgen_test]
    fn test_convex_pair_no_shift_no_rotation() {
        build_displaced_convexhull_pair_test(
            Vec3::new(0., 0., 0.),
            Vec3::new(0., 0., 0.),
            UnitQuat::from_euler_default(0., 0., 0.),
            UnitQuat::from_euler_default(0., 0., 0.),
        );
    }

    #[test]
    #[wasm_bindgen_test]
    fn test_convex_pair_shift_no_rotation() {
        build_displaced_convexhull_pair_test(
            Vec3::new(10., 10., 10.),
            Vec3::new(10., 10., 10.),
            UnitQuat::from_euler_default(0., 0., 0.),
            UnitQuat::from_euler_default(0., 0., 0.),
        );
    }

    #[test]
    #[wasm_bindgen_test]
    fn test_convex_pair_shift_only_rotation() {
        build_displaced_convexhull_pair_test(
            Vec3::new(0., 0., 0.),
            Vec3::new(0., 0., 0.),
            UnitQuat::from_euler_default(1., 2., 3.),
            UnitQuat::from_euler_default(1., 2., 3.),
        );
    }

    // This case will return incorrect contact points when the `shift_align_to_center` item is
    // false; Those contact points will be located on the surface of `shape_b`.
    #[test]
    #[wasm_bindgen_test]
    fn test_convex_pair_shift_with_rotation() {
        build_displaced_convexhull_pair_test(
            Vec3::new(10., 10., 10.),
            Vec3::new(20., 20., 20.),
            UnitQuat::from_euler_default(1., 2., 3.),
            UnitQuat::from_euler_default(1., 2., 3.),
        );
    }

    #[test]
    #[wasm_bindgen_test]
    fn test_convex_pair_shift_cross_with_no_rotation() {
        build_displaced_convexhull_pair_test(
            Vec3::new(10., 10., 10.),
            Vec3::new(-20., -20., -20.),
            UnitQuat::from_euler_default(0., 0., 0.),
            UnitQuat::from_euler_default(0., 0., 0.),
        );
    }

    #[test]
    #[wasm_bindgen_test]
    // shift to center will be removed in the future
    #[ignore]
    fn test_convex_pair_shift_cross_with_rotation() {
        build_displaced_convexhull_pair_test(
            Vec3::new(10., 20., 30.),
            Vec3::new(-10., -20., -30.),
            UnitQuat::from_euler_default(1., 2., 3.),
            UnitQuat::from_euler_default(-1., -2., -3.),
        );
    }
}