Skip to main content

linear_sim/
system.rs

1//! Simulation model
2
3use std;
4
5use log;
6use vec_map::VecMap;
7#[cfg(feature = "derive_serdes")]
8use serde::{Deserialize, Serialize};
9
10use crate::{
11  constraint, event, force, geometry, integrator, math, object, Collision, Integrator
12};
13#[cfg(feature = "debug_dump")]
14use crate::collision;
15use crate::object::InternalId;
16
17/// A system representing the computational model for the simulation.
18///
19/// There are three categories of entities that are found in the system (model):
20///
21/// - Objects -- `Static`, `Dynamic`, `Nodetect`
22/// - Forces -- `Gravity`
23/// - Constraints -- 'Planar', 'Penetration' (TODO)
24#[cfg_attr(feature = "derive_serdes", derive(Deserialize, Serialize))]
25#[derive(Clone, Debug, Default)]
26pub struct System <INTG : Integrator> {
27  /// Step counter
28  step               : u64,
29  /// No integration
30  objects_static     : VecMap <object::Static>,
31  /// Integration and collision
32  objects_dynamic    : VecMap <object::Dynamic>,
33  /// Integration only; no collision detection or response
34  objects_nodetect   : VecMap <object::Nodetect>,
35  /// Global gravity force
36  gravity            : Option <force::Gravity>,
37  /// Collision subsystem
38  collision          : Collision,
39  /// Constraint islands
40  constraint_manager : constraint::Manager,
41  #[cfg_attr(feature = "derive_serdes", serde(skip))]
42  _phantom_data      : std::marker::PhantomData <INTG>
43}
44
45impl <INTG : Integrator> System <INTG> {
46  #[inline]
47  pub fn new() -> Self where INTG : Default {
48    Default::default()
49  }
50  #[inline]
51  pub const fn step (&self) -> u64 {
52    self.step
53  }
54  #[inline]
55  pub const fn objects_dynamic (&self) -> &VecMap <object::Dynamic> {
56    &self.objects_dynamic
57  }
58  #[inline]
59  pub const fn objects_nodetect (&self) -> &VecMap <object::Nodetect> {
60    &self.objects_nodetect
61  }
62  #[inline]
63  pub const fn objects_static (&self) -> &VecMap <object::Static> {
64    &self.objects_static
65  }
66
67  #[expect(mismatched_lifetime_syntaxes)]
68  pub fn get_object (&self, id : object::Id) -> object::VariantRef {
69    match id.kind {
70      object::Kind::Static   => (&self.objects_static[id.key.index()]).into(),
71      object::Kind::Dynamic  => (&self.objects_dynamic[id.key.index()]).into(),
72      object::Kind::Nodetect => (&self.objects_nodetect[id.key.index()]).into()
73    }
74  }
75
76  pub fn segment_query (&self,
77    segment     : geometry::Segment3 <f64>,
78    ignore_list : &[object::Id]
79  ) -> Vec <(math::Normalized <f64>, math::Point3 <f64>, object::Id)> {
80    use object::Bounded;
81    let mut out = Vec::<(math::Normalized <f64>, math::Point3 <f64>, object::Id)>::new();
82    let aabb = segment.aabb3();
83    let mut overlaps : Vec <object::Id> = self.collision.broad()
84      .overlaps_discrete (aabb).into_iter().map (object::Id::from).collect();
85    overlaps.retain (|id| !ignore_list.contains (id));
86    for object_id in overlaps {
87      if let Some ((frac, point)) = match object_id.kind {
88        object::Kind::Static  =>
89          self.objects_static[object_id.key.index()].hit_test (segment),
90        object::Kind::Dynamic =>
91          self.objects_dynamic[object_id.key.index()].hit_test (segment),
92        object::Kind::Nodetect => unreachable!()
93      } {
94        out.insert (
95          out.binary_search_by (|(f, _, _)| f.partial_cmp (&frac).unwrap())
96            .unwrap_or_else (|i| i),
97          (frac, point, object_id)
98        );
99      }
100    }
101    out
102  }
103
104  #[expect(clippy::doc_overindented_list_items)]
105  /// Input event handling.
106  ///
107  /// - `CreateObject` -- if an object is found to be in *non-colliding* contact
108  ///    with any other objects, those contacts will be registered with the
109  ///    collision system as *persistent contacts* for the upcoming step
110  /// - `DestroyObject`
111  /// - `SetGravity`
112  /// - `ClearGravity`
113  /// - `Step` -- advance the simulation by a single *timestep*:
114  ///     1. Derivative evaluation: forces are accumulated and acceleration computed
115  ///     2. Velocities are integrated
116  ///     3. Velocity constraints are solved
117  ///     4. Positions are integrated using the new velocity (semi-implicit Euler)
118  ///     5. Position constraints are solved
119  ///     6. Collision detects and resolves collisions in the normalized sub-timestep
120  ///        [0.0-1.0).
121  ///
122  ///        Note that colliding contacts detected at t==1.0 will *not* produce
123  ///        a collision response event (the next step will detect and resolve this
124  ///        collision), however resting or separating contacts detected
125  ///        at t==1.0 will be registered as a *persistent contact* for the next
126  ///        simulation step.
127  pub fn handle_event (&mut self, input : event::Input) -> Vec <event::Output> {
128    let input_step = self.step;
129    log::debug!(step=input_step, input=input.to_string().as_str();
130      "input event");
131    let mut output = Vec::new();
132    match input {
133
134      //
135      // event::Input::CreateObject
136      //
137      event::Input::CreateObject (object, key) => {
138        let key = key.unwrap_or_else (|| self.new_object_key (object.kind()).unwrap());
139        match object {
140          object::Variant::Static (object) => {
141            self.create_object_static (object, key, &mut output);
142          }
143          object::Variant::Dynamic (object) => {
144            self.create_object_dynamic (object, key, &mut output);
145          }
146          object::Variant::Nodetect (object) => {
147            self.create_object_nodetect (object, key);
148          }
149        }
150      }
151
152      //
153      // event::Input::ModifyObject
154      //
155      event::Input::ModifyObject (object_modify_event) => {
156        match object_modify_event {
157          event::ObjectModify::Dynamic (key, event) => {
158            self.modify_object_dynamic (key, event);
159          }
160          event::ObjectModify::Static  (key, event) => {
161            self.modify_object_static (key, event);
162          }
163        }
164      }
165
166      //
167      // event::Input::DestroyObject
168      //
169      event::Input::DestroyObject (object::Id { kind, key }) => {
170        match kind {
171          object::Kind::Static => {
172            self.collision.remove_object_static (key);
173            self.constraint_manager.remove_object (InternalId::new_static (key));
174            assert!(self.objects_static.remove (key.index()).is_some());
175          }
176          object::Kind::Dynamic => {
177            self.collision.remove_object_dynamic (key);
178            self.constraint_manager.remove_object (InternalId::new_dynamic (key));
179            assert!(self.objects_dynamic.remove (key.index()).is_some());
180          }
181          object::Kind::Nodetect =>
182            assert!(self.objects_nodetect.remove (key.index()).is_some())
183        }
184      }
185
186      //
187      // event::Input::SetGravity
188      //
189      event::Input::SetGravity (gravity) => {
190        assert!(self.gravity.is_none());
191        self.gravity = Some (gravity);
192      }
193
194      //
195      // event::Input::ClearGravity
196      //
197      event::Input::ClearGravity => {
198        assert!(self.gravity.is_some());
199        self.gravity = None;
200      }
201
202      //
203      // event::Input::Step
204      //
205      event::Input::Step => {
206        #[cfg(feature = "debug_dump")]
207        let s = self.clone();
208
209        // 1. derivative evaluation (i.e., accumulate forces and compute
210        //    accelerations)
211        self.derivative_evaluation();
212        // 2. integrate velocity
213        self.integrate_velocity();
214        // 3. constrain velocities
215        self.constrain_velocity();
216        // 4. integrate position
217        self.integrate_position();
218        // 5. constrain position
219        self.constrain_position();
220        // 6. detect and solve collisions in a loop
221        self.collision (&mut output);
222
223        #[cfg(feature = "debug_dump")]
224        if collision::DEBUG_DUMP.swap (false, std::sync::atomic::Ordering::SeqCst)
225          && std::env::var ("LINEAR_SIM_DEBUG_DUMP").is_ok()
226        {
227          let p = format!("linear-sim-{}.dump", s.step);
228          log::info!(filepath=p.as_str(); "dumping system");
229          let mut f = std::fs::File::create (&p).unwrap();
230          bincode::serde::encode_into_std_write (&s, &mut f, bincode::config::standard())
231            .unwrap();
232        }
233
234        self.step += 1;
235      }
236
237    }
238    log::debug!(
239      step=input_step,
240      output:?=output.iter().map (ToString::to_string).collect::<Vec<_>>();
241      "output events");
242    output
243  }
244
245  /// Returns `None` if object container is full
246  pub fn new_object_key (&self, kind : object::Kind) -> Option <object::Key> {
247    fn next_available_key <T> (map : &VecMap <T>) -> Option <object::Key> {
248      for i in 0..object::KEY_MAX as usize {
249        if !map.contains_key (i) {
250          return Some (i.into())
251        }
252      }
253      None
254    }
255    match kind {
256      object::Kind::Static   => next_available_key (&self.objects_static),
257      object::Kind::Dynamic  => next_available_key (&self.objects_dynamic),
258      object::Kind::Nodetect => next_available_key (&self.objects_nodetect)
259    }
260  }
261
262  /// Returns constraints partitioned by constraint islands
263  pub fn constraints (&self)
264    -> Vec <Vec <(object::Id, object::Id, constraint::Variant)>>
265  {
266    let mut islands = vec![];
267    for (_, island) in self.constraint_manager.islands.as_ref().unwrap().iter() {
268      let mut v = vec![];
269      for (object_pair, constraint) in island.constraints.iter().cloned() {
270        let (object_id_a, object_id_b) = object_pair.into();
271        v.push ((object_id_a.into(), object_id_b.into(), constraint));
272      }
273      islands.push (v);
274    }
275    islands
276  }
277
278  pub fn add_constraint (&mut self,
279    object_id_a : object::Id, object_id_b : object::Id, constraint : constraint::Variant
280  ) {
281    self.constraint_manager
282      .add_constraint ((object_id_a, object_id_b).try_into().unwrap(), constraint)
283  }
284
285  //
286  //  private
287  //
288
289  fn create_object_static (&mut self,
290    object : object::Static,
291    key    : object::Key,
292    output : &mut Vec <event::Output>
293  ) {
294    let object_id = object::Id { kind: object::Kind::Static, key };
295    // try to add to collision system: detects intersections in case of failure
296    match self.collision.try_add_object_static (
297      &self.objects_dynamic, &object, key
298    ) {
299      Ok  (()) => {
300        // object successfully added
301        assert!(self.objects_static.insert (key.index(), object).is_none());
302        output.push (event::CreateObjectResult::Created (object_id).into());
303      }
304      Err (intersections) => {
305        debug_assert!(!intersections.is_empty());
306        output.push (
307          event::CreateObjectResult::Intersection (intersections).into());
308      }
309    }
310  }
311
312  fn create_object_dynamic (&mut self,
313    object : object::Dynamic,
314    key    : object::Key,
315    output : &mut Vec <event::Output>
316  ) {
317    let object_id = object::Id { kind: object::Kind::Dynamic, key };
318    // try to add to collision system: detects intersections in case of failure
319    match self.collision.try_add_object_dynamic (
320      &mut self.constraint_manager.pseudo_velocities,
321      &self.objects_static, &self.objects_dynamic, &object, key
322    ) {
323      Ok  (()) => {
324        // object successfully added
325        assert!(self.objects_dynamic.insert (key.index(), object).is_none());
326        output.push (event::CreateObjectResult::Created (object_id).into());
327      }
328      Err (intersections) => {
329        debug_assert!(!intersections.is_empty());
330        output.push (
331          event::CreateObjectResult::Intersection (intersections).into());
332      }
333    }
334  }
335
336  fn create_object_nodetect (&mut self,
337    object : object::Nodetect,
338    key    : object::Key,
339  ) {
340    assert!(self.objects_nodetect.insert (key.index(), object).is_none());
341  }
342
343  /// Panics if object with the given key is not present
344  fn modify_object_dynamic (&mut self,
345    key   : object::Key,
346    event : event::ObjectModifyDynamic
347  ) {
348    let object = &mut self.objects_dynamic[key.index()];
349    match event {
350      event::ObjectModifyDynamic::ApplyImpulse (impulse) =>
351        object.derivatives.velocity += impulse,
352      event::ObjectModifyDynamic::SetForceFlags (force_flags) =>
353        object.derivatives.force_flags = force_flags,
354      event::ObjectModifyDynamic::SetDrag (drag) => object.drag.0 = drag,
355      event::ObjectModifyDynamic::SetPosition (position) => {
356        object.position = position;
357        self.collision.update_object_dynamic (object, key);
358        log::warn!("TODO: dynamic object set position results");
359      }
360    }
361  }
362
363  /// Panics if object with the given key is not present
364  fn modify_object_static (&mut self,
365    key   : object::Key,
366    event : event::ObjectModifyStatic
367  ) {
368    let object = &mut self.objects_static[key.index()];
369    match event {
370      // TODO: has this been tested?
371      event::ObjectModifyStatic::Move (move_vector) => {
372        object.position.0 += move_vector;
373        self.collision.update_object_static (object, key);
374        unimplemented!("TODO: move static object results");
375      }
376    }
377  }
378
379  /// Computes accelerations from accumulated forces
380  fn derivative_evaluation (&mut self) {
381    use crate::Force;
382    use object::Inertial;
383    // clear forces
384    for object in self.objects_dynamic.values_mut() {
385      object.derivatives.force = [0.0, 0.0, 0.0].into();
386    }
387    for object in self.objects_nodetect.values_mut() {
388      object.derivatives.force = [0.0, 0.0, 0.0].into();
389    }
390
391    // accumulate forces
392    if let Some (gravity) = &self.gravity {
393      macro accumulate_forces ($object:ident) {
394        if $object.derivatives.force_flags.contains (force::Flag::Gravity) {
395          let impulse = gravity.impulse ($object, self.step, 1.0);
396          $object.derivatives.force += impulse;
397        }
398      }
399      for object in self.objects_dynamic.values_mut() {
400        accumulate_forces!(object);
401      }
402      for object in self.objects_nodetect.values_mut() {
403        accumulate_forces!(object);
404      }
405    }
406
407    // compute accelerations from accumulated forces
408    for object in self.objects_dynamic.values_mut() {
409      object.compute_acceleration_inplace();
410    }
411    for object in self.objects_nodetect.values_mut() {
412      object.compute_acceleration_inplace();
413    }
414  }
415
416  /// Integrates velocity from acceleration
417  fn integrate_velocity (&mut self) {
418    macro integrate_velocity ($integrator:ident, $object:ident) {
419      $integrator::integrate_velocity ($object);
420      // TODO: we are applying drag here; this is not a physically accurate
421      // drag force, but simply scales the velocity by a fixed factor; is this
422      // the correct place to apply drag?
423      // apply drag
424      $object.derivatives.velocity *= 1.0 - $object.drag.0;
425    }
426    for object in self.objects_dynamic.values_mut() {
427      integrate_velocity!(INTG, object);
428    }
429    for object in self.objects_nodetect.values_mut() {
430      integrate_velocity!(INTG, object);
431    }
432  }
433
434  /// Solve velocity constraints
435  #[inline]
436  fn constrain_velocity (&mut self) {
437    self.constraint_manager
438      .constrain_velocities (&self.objects_static, &mut self.objects_dynamic);
439  }
440
441  /// Integrates position from velocity
442  fn integrate_position (&mut self) {
443    for object in self.objects_dynamic.values_mut() {
444      INTG::integrate_position (object);
445    }
446    for object in self.objects_nodetect.values_mut() {
447      INTG::integrate_position (object);
448    }
449  }
450
451  /// Solve position constraints
452  #[inline]
453  fn constrain_position (&mut self) {
454    self.constraint_manager
455      .constrain_positions (&self.objects_static, &mut self.objects_dynamic);
456  }
457
458  /// Collision detection and response
459  fn collision (&mut self, output : &mut Vec <event::Output>) {
460    self.collision.detect_resolve_loop (
461      &mut self.constraint_manager, &self.objects_static, &mut self.objects_dynamic,
462      self.step, output);
463  }
464
465}
466
467/// Print system size information
468pub fn report_sizes() {
469  use std::mem::size_of;
470  println!("system report sizes...");
471
472  println!("  size of System <integrator::SemiImplicitEuler>: {}",
473    size_of::<System <integrator::SemiImplicitEuler>>());
474
475  println!("...system report sizes");
476}
477
478#[cfg(test)]
479mod tests {
480  use super::*;
481  use crate::component;
482  use crate::geometry::{self, shape};
483  #[test]
484  fn segment_query() {
485    let segment = geometry::Segment3::noisy (
486      [-1.0, 0.0, 0.5].into(),
487      [ 2.0, 0.0, 0.5].into());
488    let mut system = System::<integrator::SemiImplicitEuler>::new();
489    let sphere1 = {
490      let position    = component::Position ([0.5, 0.0, 0.5].into());
491      let mass        = component::Mass::new (20.0);
492      let derivatives = component::Derivatives::zero();
493      let drag        = component::Drag::zero();
494      let bound       = component::Bound::from (shape::Sphere::noisy (0.5));
495      let material    = component::MATERIAL_STONE;
496      let collidable  = true;
497      object::Dynamic {
498        position, mass, derivatives, drag, bound, material, collidable
499      }.into()
500    };
501    let mut result = system.handle_event (event::Input::CreateObject (sphere1, None));
502    let event::Output::CreateObjectResult (
503      event::CreateObjectResult::Created (sphere1_id)
504    ) = result.pop().unwrap() else { unreachable!() };
505    assert!(result.pop().is_none());
506    let block1 = {
507      let position   = component::Position ([1.5, 0.0, 0.5].into());
508      let bound      = component::Bound    (
509        shape::Bounded::from (shape::Cuboid::noisy ([0.5, 0.5, 0.5])).into());
510      let material   = component::MATERIAL_STONE;
511      let collidable = true;
512      object::Static { position, bound, material, collidable }.into()
513    };
514    let mut result = system.handle_event (event::Input::CreateObject (block1, None));
515    let event::Output::CreateObjectResult (
516      event::CreateObjectResult::Created (block1_id)
517    ) = result.pop().unwrap() else { unreachable!() };
518    assert!(result.pop().is_none());
519    let result = system.segment_query (segment, &[]);
520    let (frac, point, id) = result.first().unwrap();
521    assert_eq!(**frac, 1.0/3.0);
522    assert_eq!(*point, [0.0, 0.0, 0.5].into());
523    assert_eq!(*id, sphere1_id);
524    let (frac, point, id) = result.get (1).unwrap();
525    assert_eq!(**frac, 2.0/3.0);
526    assert_eq!(*point, [1.0, 0.0, 0.5].into());
527    assert_eq!(*id, block1_id);
528  }
529}