linear-sim 0.7.0

Minimal linear 3D simulation library
Documentation
//! Contact groups

use sorted_vec::SortedSet;

use crate::object;
use crate::collision::{InternalId, ObjectPair};
use super::Contact;
#[cfg(feature = "derive_serdes")]
use serde::{Deserialize, Serialize};

/// Type used for group keys.
///
/// Being defined as equal to the `object::KeyType` should keep it roughly large enough
/// to handle any number of objects since the number of groups is always less than the
/// number of individual objects.
pub(crate) type KeyType = object::KeyType;

/// A group of contacts that are directly or indirectly connected by sharing common
/// objects
#[cfg_attr(feature = "derive_serdes", derive(Deserialize, Serialize))]
#[derive(Clone, Debug, Default, PartialEq)]
pub(crate) struct Group {
  // TODO: use sorted container?
  pub contacts : Vec <(ObjectPair, Contact)>
}

impl Group {
  /// Returns two or more partitions if group is not connected
  pub(crate) fn partition (&self) -> Vec <Group> {
    let mut components : Vec <Vec <InternalId>> = vec![];
    let find_component = |components : &Vec <Vec <InternalId>>, object_id| {
      let mut found_component = None;
      for (i, component) in components.iter().enumerate() {
        if component.contains (&object_id) {
          found_component = Some (i);
          break
        }
      }
      found_component
    };
    for (ObjectPair (object_id_a, object_id_b), _) in self.contacts.iter() {
      let component_index_a = if object_id_a.kind() == object::Kind::Dynamic {
        find_component (&components, *object_id_a)
      } else {
        None
      };
      let component_index_b = find_component (&components, *object_id_b);
      match (component_index_a, component_index_b) {
        (None, None) => {
          // new component
          let mut component = vec![*object_id_b];
          if object_id_a.kind() == object::Kind::Dynamic {
            component.push (*object_id_a);
          }
          components.push (component);
        }
        (None, Some (component_index_b)) =>
          if object_id_a.kind() == object::Kind::Dynamic {
            // add object a to component b
            components[component_index_b].push (*object_id_a);
          }
        (Some (component_index_a), None) =>
          // add object b to component a
          components[component_index_a].push (*object_id_b),
        (Some (mut component_index_a), Some (component_index_b)) =>
          if component_index_a != component_index_b {
            // merge components
            let component_b = components.remove (component_index_b);
            if component_index_a > component_index_b {
              component_index_a -= 1;
            }
            components[component_index_a].extend (component_b);
          }
      }
    }
    debug_assert!(!components.is_empty());
    if components.len() == 1 {
      debug_assert_eq!(components[0].len(), self.dynamic_ids().len());
      vec![]
    } else {
      let mut out = vec![Group::default(); components.len()];
      for contact@(ObjectPair (_, object_id_b), _) in self.contacts.iter() {
        for (i, component) in components.iter().enumerate() {
          if component.contains (object_id_b) {
            out[i].contacts.push (contact.clone());
            break
          }
        }
      }
      debug_assert!(out.len() >= 2);
      out
    }
  }

  pub(crate) fn dynamic_ids (&self) -> SortedSet <InternalId> {
    let mut out = SortedSet::new();
    for (ObjectPair (object_id_a, object_id_b), _) in self.contacts.iter() {
      if object_id_a.kind() == object::Kind::Dynamic {
        out.push (*object_id_a);
      }
      out.push (*object_id_b);
    }
    out
  }

  #[expect(dead_code)]
  pub(super) fn contains (&self, object_pair : ObjectPair) -> bool {
    let (object_id_a, object_id_b) = object_pair.into();
    // NOTE: object IDs in the pipeline should always be ordered so we don't
    // need to check id_a == object_id_b and vice versa
    for (pair, _) in self.contacts.iter() {
      let (id_a, id_b) = (*pair).into();
      if id_a == object_id_a && id_b == object_id_b {
        return true
      }
    }
    false
  }

  /// Returns list of objects that were in contact
  pub(super) fn remove_object (&mut self, object_id : InternalId) -> Vec <InternalId> {
    let mut out = vec![];
    self.contacts.retain (|(object_pair, _)|{
      let (id_a, id_b) = (*object_pair).into();
      if id_a == object_id {
        out.push (id_b);
        false
      } else if id_b == object_id {
        out.push (id_a);
        false
      } else {
        true
      }
    });
    out
  }
}