Skip to main content

linear_sim/collision/contact/
mod.rs

1use std;
2use stash::Stash;
3use vec_map::VecMap;
4#[cfg(feature = "derive_serdes")]
5use serde::{Deserialize, Serialize};
6
7use crate::{constraint, event, math, object};
8use super::{CONTACT_DISTANCE, InternalId, ObjectPair, Proximity};
9
10pub (crate) mod group;
11pub (crate) use self::group::Group;
12
13#[cfg_attr(feature = "derive_serdes", derive(Deserialize, Serialize))]
14#[derive(Clone, Debug, PartialEq)]
15pub struct Contact {
16  pub constraint : constraint::Planar
17}
18
19/// A "TOI contact".
20///
21/// A pair of objects together with a contact plane and non-negative restitution
22/// value.
23///
24/// A resting contact will have a restitution value of 0.0 and a colliding
25/// contact will usually have a positive non-zero restitution value.
26///
27/// ⚠ Note that the `PartialOrd` and `Ord` implementations are only so
28/// that contacts can be sorted in the narrow TOI list. Attempting to compare
29/// contacts will always panic.
30#[cfg_attr(feature = "derive_serdes", derive(Deserialize, Serialize))]
31#[derive(Clone, Debug, PartialEq)]
32pub struct Colliding {
33  pub contact     : Contact,
34  pub restitution : math::Normalized <f64>
35}
36
37/// Persistent contact group manager
38#[cfg_attr(feature = "derive_serdes", derive(Deserialize, Serialize))]
39#[derive(Clone, Debug)]
40pub (crate) struct Manager {
41  // NOTE: we put this field in an option so we can take and iterate over groups
42  // without borrowing the manager
43  pub contact_groups            : Option <Stash <Group>>,
44  /// Number of contacts for static objects.
45  ///
46  /// Entries are only occupied when the number of contacts is greater than
47  /// zero.
48  object_contacts_static        : VecMap <u32>,
49  /// Group key and number of contacts for dynamic objects
50  object_group_contacts_dynamic : VecMap <(group::KeyType, u32)>
51}
52
53impl Manager {
54  pub(super) fn get_group (&self, key : group::KeyType) -> Option <&Group> {
55    self.contact_groups.as_ref().unwrap().get (key as usize)
56  }
57  pub(super) fn get_group_mut (&mut self, key : group::KeyType) -> Option <&mut Group> {
58    self.contact_groups.as_mut().unwrap().get_mut (key as usize)
59  }
60  pub(super) fn remove_group (&mut self, key : group::KeyType) -> Option <Group> {
61    self.contact_groups.as_mut().unwrap().take (key as usize)
62  }
63  pub(super) fn add_contact (&mut self, object_pair : ObjectPair, contact : Contact) {
64    let (object_id_a, object_id_b) = object_pair.into();
65    let group = match (
66      self.get_group_key (object_id_a), self.get_group_key (object_id_b)
67    ) {
68      (Some (group_a), None) => {
69        // dynamic object b joins existing group a
70        self.increment_contact (object_id_a);
71        self.assign_dynamic_group_key (object_id_b, group_a);
72        self.get_group_mut (group_a).unwrap()
73      }
74      (None, Some (group_b)) => {
75        // object a joins existing group b
76        if object_id_a.kind() == object::Kind::Dynamic {
77          self.assign_dynamic_group_key (object_id_a, group_b);
78        } else {
79          self.increment_contact (object_id_a);
80        }
81        self.increment_contact (object_id_b);
82        self.get_group_mut (group_b).unwrap()
83      }
84      (Some (group_a), Some (group_b)) => {
85        self.increment_contact (object_id_a);
86        self.increment_contact (object_id_b);
87        if group_a == group_b {
88          self.get_group_mut (group_a).unwrap()
89        } else {
90          // merge groups
91          let group_b = self.remove_group (group_b).unwrap();
92          for (pair, _) in group_b.contacts.iter() {
93            let (id_a, id_b) = (*pair).into();
94            if id_a.kind() == object::Kind::Dynamic {
95              self.change_dynamic_group_key (id_a, group_a);
96            }
97            self.change_dynamic_group_key (id_b, group_a);
98          }
99          let group_a = self.get_group_mut (group_a).unwrap();
100          group_a.contacts.extend (group_b.contacts);
101          group_a
102        }
103      }
104      (None, None) => {
105        // new group
106        let new_group_index = self.contact_groups.as_mut().unwrap()
107          .put (Group::default());
108        debug_assert!(new_group_index < group::KeyType::MAX as usize);
109        let new_group_key = new_group_index as group::KeyType;
110        if object_id_a.kind() == object::Kind::Dynamic {
111          self.assign_dynamic_group_key (object_id_a, new_group_key);
112        } else {
113          self.increment_contact (object_id_a);
114        }
115        self.assign_dynamic_group_key (object_id_b, new_group_key);
116        self.get_group_mut (new_group_key).unwrap()
117      }
118    };
119    group.contacts.push ((object_pair, contact));
120  }
121
122  /// Remove the object if it belongs to a persistent contact group.
123  ///
124  /// Returns true if the object was found and removed, otherwise returns false if the
125  /// object was not in a contact group.
126  #[must_use]
127  pub(super) fn remove_object (&mut self, object_id : InternalId) -> bool {
128    if let Some (group_key) = self.get_group_key (object_id) {
129      debug_assert_eq!(object_id.kind(), object::Kind::Dynamic);
130      let group   = self.get_group_mut (group_key).unwrap();
131      let removed = group.remove_object (object_id);
132      let empty   = group.contacts.is_empty();
133      if empty {
134        let _ = self.remove_group (group_key).unwrap();
135      }
136      for id in removed {
137        self.decrement_contact (id);
138      }
139      self.object_group_contacts_dynamic.remove (object_id.key().index()).unwrap();
140      true
141    } else if object_id.kind() == object::Kind::Static {
142      let index = object_id.key().index();
143      if let Some (mut contact_count) = self.object_contacts_static.remove (index) {
144        debug_assert!(contact_count > 0);
145        let mut empty = vec![];
146        let mut contact_groups = self.contact_groups.take().unwrap();
147        for (i, group) in contact_groups.iter_mut() {
148          let removed = group.remove_object (object_id);
149          if group.contacts.is_empty() {
150            empty.push (i);
151          }
152          contact_count -= removed.len() as u32;
153          for id in removed {
154            self.decrement_contact (id);
155          }
156          if contact_count == 0 {
157            break
158          }
159        }
160        for i in empty {
161          contact_groups.take (i);
162        }
163        self.contact_groups = Some (contact_groups);
164        true
165      } else {
166        false
167      }
168    } else {
169      false
170    }
171  }
172
173  pub(super) fn output_contacts (&self, output : &mut Vec <event::Output>) {
174    for group in self.contact_groups.as_ref().unwrap().values() {
175      for (object_pair, contact) in group.contacts.iter().cloned() {
176        let (id_a, id_b) = object_pair.into();
177        output.push (event::Contact {
178          object_id_a: id_a.into(),
179          object_id_b: id_b.into(),
180          contact
181        }.into())
182      }
183    }
184  }
185
186  /// Takes list of contact indices to remove.
187  ///
188  /// List should be non-empty. Note that the modified group may no longer be
189  /// connected and `group.partition()` should be called to get connected
190  /// components.
191  pub(super) fn remove_contacts (&mut self, group : &mut Group, remove_list : &[u32]) {
192    debug_assert!(!remove_list.is_empty());
193    group.contacts = group.contacts.drain (..).enumerate().filter_map (
194      |(i, contact@(object_pair, _))|
195      if remove_list.contains (&(i as u32)) {
196        let (object_id_a, object_id_b) = object_pair.into();
197        self.decrement_contact (object_id_a);
198        self.decrement_contact (object_id_b);
199        None
200      } else {
201        Some (contact)
202      }
203    ).collect();
204  }
205
206  /// Get the contact count for an object
207  pub(super) fn get_contact_count (&self, object_id : InternalId) -> Option <u32> {
208    let index = object_id.key().index();
209    match object_id.kind() {
210      object::Kind::Static  => self.object_contacts_static.get (index).copied(),
211      object::Kind::Dynamic => self.object_group_contacts_dynamic.get (index)
212        .map (|g| g.1),
213      object::Kind::Nodetect => unreachable!()
214    }
215  }
216
217  /// Get the group key for dynamic object, if it exists, otherwise returns None
218  /// if the object is static
219  pub(super) fn get_group_key (&self, object_id : InternalId)
220    -> Option <group::KeyType>
221  {
222    let index = object_id.key().index();
223    match object_id.kind() {
224      // static objects can be members of more than one group
225      object::Kind::Static  => None,
226      object::Kind::Dynamic => self.object_group_contacts_dynamic.get (index)
227        .map (|g| g.0),
228      object::Kind::Nodetect => unreachable!()
229    }
230  }
231
232  /// Change existing group key
233  pub(super) fn change_dynamic_group_key (&mut self,
234    object_id : InternalId, group_key : group::KeyType
235  ) {
236    match object_id.kind() {
237      object::Kind::Static  => unreachable!(),
238      object::Kind::Dynamic => {
239        let index = object_id.key().index();
240        self.object_group_contacts_dynamic[index].0 = group_key;
241      }
242      object::Kind::Nodetect => unimplemented!()
243    }
244  }
245
246  /// Assign a new group key to a dynamic object
247  fn assign_dynamic_group_key (&mut self,
248    object_id : InternalId, group_key : group::KeyType
249  ) {
250    let index = object_id.key().index();
251    match object_id.kind() {
252      object::Kind::Static  => unreachable!("call increment_contact instead"),
253      object::Kind::Dynamic =>
254        assert!(self.object_group_contacts_dynamic.insert (index, (group_key, 1))
255          .is_none()),
256      object::Kind::Nodetect => unimplemented!()
257    }
258  }
259
260  /// Increment the contact counter.
261  ///
262  /// Adds the contact counter if this is the first contact of a static object.
263  fn increment_contact (&mut self, object_id : InternalId) {
264    let index = object_id.key().index();
265    match object_id.kind() {
266      object::Kind::Static  =>
267        *self.object_contacts_static.entry (index).or_insert (0) += 1,
268      object::Kind::Dynamic => self.object_group_contacts_dynamic[index].1 += 1,
269      object::Kind::Nodetect => unimplemented!()
270    }
271  }
272
273  /// Reduce the contact count for the given object ID
274  fn decrement_contact (&mut self, object_id : InternalId) {
275    let index = object_id.key().index();
276    match object_id.kind() {
277      object::Kind::Dynamic => {
278        let count = &mut self.object_group_contacts_dynamic.get_mut (index).unwrap().1;
279        *count -= 1;
280        if *count == 0 {
281          self.object_group_contacts_dynamic.remove (index);
282        }
283      }
284      object::Kind::Static  => {
285        let count = self.object_contacts_static.get_mut (index).unwrap();
286        *count -= 1;
287        if *count == 0 {
288          self.object_contacts_static.remove (index);
289        }
290      }
291      object::Kind::Nodetect => unreachable!()
292    }
293  }
294}
295
296impl Default for Manager {
297  fn default() -> Self {
298    Manager {
299      contact_groups:                Some (Stash::default()),
300      object_contacts_static:        VecMap::default(),
301      object_group_contacts_dynamic: VecMap::default()
302    }
303  }
304}
305
306impl TryFrom <Proximity> for Contact {
307  type Error = ();
308  fn try_from (proximity : Proximity) -> Result <Self, Self::Error> {
309    if proximity.distance >= 0.0 && proximity.distance < CONTACT_DISTANCE {
310      Ok (Contact { constraint: proximity.into() })
311    } else {
312      Err (())
313    }
314  }
315}
316
317impl std::ops::Deref for Colliding {
318  type Target = Contact;
319  fn deref (&self) -> &Contact {
320    &self.contact
321  }
322}
323impl Eq for Colliding { }
324#[expect(clippy::non_canonical_partial_ord_impl)]
325impl PartialOrd for Colliding {
326  /// Panics: should not be called; this trait is implemented so that narrow TOI
327  /// results can be automatically be sorted
328  fn partial_cmp (&self, _rhs : &Self) -> Option <std::cmp::Ordering> {
329    // TODO: compiler hint ?
330    unreachable!()
331  }
332}
333impl Ord for Colliding {
334  fn cmp (&self, _other : &Self) -> std::cmp::Ordering {
335    // TODO: compiler hint ?
336    unreachable!()
337  }
338}