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::shape::{Capsule, Cuboid, Cylinder};
use rustc_hash::FxHashMap;

use crate::collision_tasks::BatchesReductionContext;
use crate::{
    CollisionCallBack, ComplexShapeId, ContactContext, ConvexHullId, ReductionId, Shape,
    ShapeBatches, ShapeContainer, SphereIncludingPair, StandardPair, Triangle,
};

#[derive(Default, Clone)]
pub struct PluginContainer {
    plugins: FxHashMap<u64, Box<dyn CollisionShapePlugin>>,
}

impl PluginContainer {
    pub fn add(&mut self, plugin: impl CollisionShapePlugin) {
        let plugin_id = plugin.plugin_id();
        self.plugins.insert(plugin_id, Box::new(plugin));
    }

    #[must_use]
    pub fn get_plugin(&self, id: u64) -> Option<&(dyn CollisionShapePlugin)> {
        if let Some(value) = self.plugins.get(&id) {
            Some(value.as_ref())
        } else {
            None
        }
    }

    pub fn get_plugin_mut(&mut self, id: u64) -> Option<&mut Box<dyn CollisionShapePlugin>> {
        self.plugins.get_mut(&id)
    }

    pub fn execute(
        &self,
        container: Option<&ShapeContainer>,
        reduction_contex: &mut BatchesReductionContext,
        callback: &mut dyn CollisionCallBack,
    ) {
        for plugin in self.plugins.values() {
            plugin.solve_collision(container, reduction_contex, callback);
        }
    }

    pub fn clear(&mut self) {
        for plugin in &mut self.plugins.values_mut() {
            plugin.clear_pairs();
        }
    }
}

pub struct PairGenerateContext<'a> {
    pub pair_id: usize,
    pub shape_batches: &'a mut ShapeBatches,
    pub reduction_context: &'a mut BatchesReductionContext,
    pub shape: &'a Shape,
    pub complex_shape_id: &'a ComplexShapeId,
    pub speculative_margin: f32,
    pub flip_mask: bool,
}

impl PairGenerateContext<'_> {
    pub fn create_reduction_pair(&mut self) -> ReductionId {
        self.reduction_context.create_reduction_pair(self.pair_id)
    }

    pub fn set_to_concave_reduction(&mut self, reduction_id: ReductionId) {
        self.reduction_context
            .set_to_concave_reduction(reduction_id);
    }

    pub fn record_reduction(&mut self, reduction_id: ReductionId, count: usize) {
        self.reduction_context.record_reduction(reduction_id, count);
    }

    pub fn push_sphere_triangle(&mut self, pair: SphereIncludingPair<Triangle>) {
        self.shape_batches.push_sphere_triangle(pair);
    }

    pub fn push_capsule_triangle(&mut self, pair: StandardPair<Capsule, Triangle>) {
        self.shape_batches.push_capsule_triangle(pair);
    }

    pub fn push_cuboid_triangle(&mut self, pair: StandardPair<Cuboid, Triangle>) {
        self.shape_batches.push_cuboid_triangle(pair);
    }

    pub fn push_triangle_cylinder(&mut self, pair: StandardPair<Triangle, Cylinder>) {
        self.shape_batches.push_triangle_cylinder(pair);
    }

    pub fn push_triangle_convex_hull(&mut self, pair: StandardPair<Triangle, ConvexHullId>) {
        self.shape_batches.push_triangle_convex_hull(pair);
    }
}

pub trait CollisionShapePlugin: 'static + Send + Sync + dyn_clone::DynClone {
    fn plugin_id(&self) -> u64;

    /// Clear the solved pair every frame.
    fn clear_pairs(&mut self);

    /// Solve the recorded pairs' collision.
    fn solve_collision(
        &self,
        container: Option<&ShapeContainer>,
        reduction_contex: &mut BatchesReductionContext,
        callback: &mut dyn CollisionCallBack,
    );

    /// Generate the collision pairs to be solved.
    fn on_pair_generate(
        &mut self,
        ctx: &mut PairGenerateContext,
        contact_context: &ContactContext,
        shapes: &ShapeContainer,
    );
}

dyn_clone::clone_trait_object!(CollisionShapePlugin);

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

    use super::{CollisionShapePlugin, PluginContainer};

    #[derive(Clone)]
    struct TestPlugin {
        pub value: u64,
    }

    impl CollisionShapePlugin for TestPlugin {
        fn clear_pairs(&mut self) {
            self.value = 0;
        }

        fn on_pair_generate(
            &mut self,
            _ctx: &mut super::PairGenerateContext,
            _contact_context: &crate::ContactContext,
            _shapes: &crate::ShapeContainer,
        ) {
        }

        fn plugin_id(&self) -> u64 {
            0x123456
        }

        fn solve_collision(
            &self,
            _container: Option<&crate::ShapeContainer>,
            _reduction_contex: &mut crate::BatchesReductionContext,
            _callback: &mut dyn crate::CollisionCallBack,
        ) {
        }
    }

    #[test]
    #[wasm_bindgen_test]
    fn collision_plugin_test() {
        let mut plugins = PluginContainer::default();
        let my_plugin = TestPlugin { value: 12 };
        let id = my_plugin.plugin_id();
        let id_2 = 0x55454;
        plugins.add(my_plugin);

        {
            let result = plugins.get_plugin(id);
            assert!(result.is_some());

            let result_2 = plugins.get_plugin(id_2);
            assert!(result_2.is_none());
        }

        {
            let result_3 = plugins.get_plugin_mut(id).unwrap();
            result_3.clear_pairs();
        }
    }
}