linear_sim/
system.rs

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