1use 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#[cfg_attr(feature = "derive_serdes", derive(Deserialize, Serialize))]
25#[derive(Clone, Debug, Default)]
26pub struct System <INTG : Integrator> {
27 step : u64,
29 objects_static : VecMap <object::Static>,
31 objects_dynamic : VecMap <object::Dynamic>,
33 objects_nodetect : VecMap <object::Nodetect>,
35 gravity : Option <force::Gravity>,
37 collision : Collision,
39 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 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 event::Input::CreateObject (object, key) => {
138 let key = key.unwrap_or_else (
139 || self.new_object_key (object.kind()).unwrap());
140 match object {
141 object::Variant::Static (object) => {
142 self.create_object_static (object, key, &mut output);
143 }
144 object::Variant::Dynamic (object) => {
145 self.create_object_dynamic (object, key, &mut output);
146 }
147 object::Variant::Nodetect (object) => {
148 self.create_object_nodetect (object, key);
149 }
150 }
151 }
152
153 event::Input::ModifyObject (object_modify_event) => {
157 match object_modify_event {
158 event::ObjectModify::Dynamic (key, event) => {
159 self.modify_object_dynamic (key, event);
160 }
161 event::ObjectModify::Static (key, event) => {
162 self.modify_object_static (key, event);
163 }
164 }
165 }
166
167 event::Input::DestroyObject (object::Id { kind, key }) => {
171 match kind {
172 object::Kind::Static => {
173 self.collision.remove_object_static (key);
174 let id = InternalId::new_static (key);
175 assert!(self.constraint_manager.remove_object (id));
176 assert!(self.objects_static.remove (key.index()).is_some());
177 }
178 object::Kind::Dynamic => {
179 self.collision.remove_object_dynamic (key);
180 let id = InternalId::new_dynamic (key);
181 assert!(self.constraint_manager.remove_object (id));
182 assert!(self.objects_dynamic.remove (key.index()).is_some());
183 }
184 object::Kind::Nodetect =>
185 assert!(self.objects_nodetect.remove (key.index()).is_some())
186 }
187 }
188
189 event::Input::SetGravity (gravity) => {
193 assert!(self.gravity.is_none());
194 self.gravity = Some (gravity);
195 }
196
197 event::Input::ClearGravity => {
201 assert!(self.gravity.is_some());
202 self.gravity = None;
203 }
204
205 event::Input::Step => {
209 #[cfg(feature = "debug_dump")]
210 let s = self.clone();
211
212 self.derivative_evaluation();
215 self.integrate_velocity();
217 self.constrain_velocity();
219 self.integrate_position();
221 self.constrain_position();
223 self.collision (&mut output);
225
226 #[cfg(feature = "debug_dump")]
227 if collision::DEBUG_DUMP.swap (false, std::sync::atomic::Ordering::SeqCst)
228 && std::env::var ("LINEAR_SIM_DEBUG_DUMP").is_ok()
229 {
230 let p = format!("linear-sim-{}.dump", s.step);
231 log::info!(filepath=p.as_str(); "dumping system");
232 let mut f = std::fs::File::create (&p).unwrap();
233 bincode::serde::encode_into_std_write (&s, &mut f, bincode::config::standard())
234 .unwrap();
235 }
236
237 self.step += 1;
238 }
239
240 }
241 log::debug!(
242 step=input_step,
243 output:?=output.iter().map (ToString::to_string).collect::<Vec<_>>();
244 "output events");
245 output
246 }
247
248 pub fn new_object_key (&self, kind : object::Kind) -> Option <object::Key> {
250 fn next_available_key <T> (map : &VecMap <T>) -> Option <object::Key> {
251 for i in 0..object::KEY_MAX as usize {
252 if !map.contains_key (i) {
253 return Some (i.into())
254 }
255 }
256 None
257 }
258 match kind {
259 object::Kind::Static => next_available_key (&self.objects_static),
260 object::Kind::Dynamic => next_available_key (&self.objects_dynamic),
261 object::Kind::Nodetect => next_available_key (&self.objects_nodetect)
262 }
263 }
264
265 pub fn constraints (&self)
267 -> Vec <Vec <(object::Id, object::Id, constraint::Variant)>>
268 {
269 let mut islands = vec![];
270 for (_, island) in self.constraint_manager.islands.as_ref().unwrap().iter() {
271 let mut v = vec![];
272 for (object_pair, constraint) in island.constraints.iter().cloned() {
273 let (object_id_a, object_id_b) = object_pair.into();
274 v.push ((object_id_a.into(), object_id_b.into(), constraint));
275 }
276 islands.push (v);
277 }
278 islands
279 }
280
281 pub fn add_constraint (&mut self,
282 object_id_a : object::Id, object_id_b : object::Id, constraint : constraint::Variant
283 ) {
284 self.constraint_manager
285 .add_constraint ((object_id_a, object_id_b).try_into().unwrap(), constraint)
286 }
287
288 fn create_object_static (&mut self,
293 object : object::Static,
294 key : object::Key,
295 output : &mut Vec <event::Output>
296 ) {
297 let object_id = object::Id { kind: object::Kind::Static, key };
298 match self.collision.try_add_object_static (
300 &self.objects_dynamic, &object, key
301 ) {
302 Ok (()) => {
303 assert!(self.objects_static.insert (key.index(), object).is_none());
305 output.push (event::CreateObjectResult::Created (object_id).into());
306 }
307 Err (intersections) => {
308 debug_assert!(!intersections.is_empty());
309 output.push (
310 event::CreateObjectResult::Intersection (intersections).into());
311 }
312 }
313 }
314
315 fn create_object_dynamic (&mut self,
316 object : object::Dynamic,
317 key : object::Key,
318 output : &mut Vec <event::Output>
319 ) {
320 let object_id = object::Id { kind: object::Kind::Dynamic, key };
321 match self.collision.try_add_object_dynamic (
323 &mut self.constraint_manager.pseudo_velocities,
324 &self.objects_static, &self.objects_dynamic, &object, key
325 ) {
326 Ok (()) => {
327 assert!(self.objects_dynamic.insert (key.index(), object).is_none());
329 output.push (event::CreateObjectResult::Created (object_id).into());
330 }
331 Err (intersections) => {
332 debug_assert!(!intersections.is_empty());
333 output.push (
334 event::CreateObjectResult::Intersection (intersections).into());
335 }
336 }
337 }
338
339 fn create_object_nodetect (&mut self,
340 object : object::Nodetect,
341 key : object::Key,
342 ) {
343 assert!(self.objects_nodetect.insert (key.index(), object).is_none());
344 }
345
346 fn modify_object_dynamic (&mut self,
348 key : object::Key,
349 event : event::ObjectModifyDynamic
350 ) {
351 let object = &mut self.objects_dynamic[key.index()];
352 match event {
353 event::ObjectModifyDynamic::ApplyImpulse (impulse) =>
354 object.derivatives.velocity += impulse,
355 event::ObjectModifyDynamic::SetForceFlags (force_flags) =>
356 object.derivatives.force_flags = force_flags,
357 event::ObjectModifyDynamic::SetDrag (drag) => object.drag.0 = drag,
358 event::ObjectModifyDynamic::SetPosition (position) => {
359 object.position = position;
360 self.collision.update_object_dynamic (object, key);
361 log::warn!("TODO: dynamic object set position results");
362 }
363 }
364 }
365
366 fn modify_object_static (&mut self,
368 key : object::Key,
369 event : event::ObjectModifyStatic
370 ) {
371 let object = &mut self.objects_static[key.index()];
372 match event {
373 event::ObjectModifyStatic::Move (move_vector) => {
375 object.position.0 += move_vector;
376 self.collision.update_object_static (object, key);
377 unimplemented!("TODO: move static object results");
378 }
379 }
380 }
381
382 fn derivative_evaluation (&mut self) {
384 use crate::Force;
385 use object::Inertial;
386 for object in self.objects_dynamic.values_mut() {
388 object.derivatives.force = [0.0, 0.0, 0.0].into();
389 }
390 for object in self.objects_nodetect.values_mut() {
391 object.derivatives.force = [0.0, 0.0, 0.0].into();
392 }
393
394 if let Some (gravity) = &self.gravity {
396 macro accumulate_forces ($object:ident) {
397 if $object.derivatives.force_flags.contains (force::Flag::Gravity) {
398 let impulse = gravity.impulse ($object, self.step, 1.0);
399 $object.derivatives.force += impulse;
400 }
401 }
402 for object in self.objects_dynamic.values_mut() {
403 accumulate_forces!(object);
404 }
405 for object in self.objects_nodetect.values_mut() {
406 accumulate_forces!(object);
407 }
408 }
409
410 for object in self.objects_dynamic.values_mut() {
412 object.compute_acceleration_inplace();
413 }
414 for object in self.objects_nodetect.values_mut() {
415 object.compute_acceleration_inplace();
416 }
417 }
418
419 fn integrate_velocity (&mut self) {
421 macro integrate_velocity ($integrator:ident, $object:ident) {
422 $integrator::integrate_velocity ($object);
423 $object.derivatives.velocity *= 1.0 - $object.drag.0;
428 }
429 for object in self.objects_dynamic.values_mut() {
430 integrate_velocity!(INTG, object);
431 }
432 for object in self.objects_nodetect.values_mut() {
433 integrate_velocity!(INTG, object);
434 }
435 }
436
437 #[inline]
439 fn constrain_velocity (&mut self) {
440 self.constraint_manager
441 .constrain_velocities (&self.objects_static, &mut self.objects_dynamic);
442 }
443
444 fn integrate_position (&mut self) {
446 for object in self.objects_dynamic.values_mut() {
447 INTG::integrate_position (object);
448 }
449 for object in self.objects_nodetect.values_mut() {
450 INTG::integrate_position (object);
451 }
452 }
453
454 #[inline]
456 fn constrain_position (&mut self) {
457 self.constraint_manager
458 .constrain_positions (&self.objects_static, &mut self.objects_dynamic);
459 }
460
461 fn collision (&mut self, output : &mut Vec <event::Output>) {
463 self.collision.detect_resolve_loop (
464 &mut self.constraint_manager, &self.objects_static, &mut self.objects_dynamic,
465 self.step, output);
466 }
467
468}
469
470pub fn report_sizes() {
472 use std::mem::size_of;
473 println!("system report sizes...");
474
475 println!(" size of System <integrator::SemiImplicitEuler>: {}",
476 size_of::<System <integrator::SemiImplicitEuler>>());
477
478 println!("...system report sizes");
479}
480
481#[cfg(test)]
482mod tests {
483 use super::*;
484 use crate::component;
485 use crate::geometry::{self, shape};
486 #[test]
487 fn segment_query() {
488 let segment = geometry::Segment3::noisy (
489 [-1.0, 0.0, 0.5].into(),
490 [ 2.0, 0.0, 0.5].into());
491 let mut system = System::<integrator::SemiImplicitEuler>::new();
492 let sphere1 = {
493 let position = component::Position ([0.5, 0.0, 0.5].into());
494 let mass = component::Mass::new (20.0);
495 let derivatives = component::Derivatives::zero();
496 let drag = component::Drag::zero();
497 let bound = component::Bound::from (shape::Sphere::noisy (0.5));
498 let material = component::MATERIAL_STONE;
499 let collidable = true;
500 object::Dynamic {
501 position, mass, derivatives, drag, bound, material, collidable
502 }.into()
503 };
504 let mut result = system.handle_event (event::Input::CreateObject (sphere1, None));
505 let event::Output::CreateObjectResult (
506 event::CreateObjectResult::Created (sphere1_id)
507 ) = result.pop().unwrap() else { unreachable!() };
508 assert!(result.pop().is_none());
509 let block1 = {
510 let position = component::Position ([1.5, 0.0, 0.5].into());
511 let bound = component::Bound (
512 shape::Bounded::from (shape::Cuboid::noisy ([0.5, 0.5, 0.5])).into());
513 let material = component::MATERIAL_STONE;
514 let collidable = true;
515 object::Static { position, bound, material, collidable }.into()
516 };
517 let mut result = system.handle_event (event::Input::CreateObject (block1, None));
518 let event::Output::CreateObjectResult (
519 event::CreateObjectResult::Created (block1_id)
520 ) = result.pop().unwrap() else { unreachable!() };
521 assert!(result.pop().is_none());
522 let result = system.segment_query (segment, &[]);
523 let (frac, point, id) = result.first().unwrap();
524 assert_eq!(**frac, 1.0/3.0);
525 assert_eq!(*point, [0.0, 0.0, 0.5].into());
526 assert_eq!(*id, sphere1_id);
527 let (frac, point, id) = result.get (1).unwrap();
528 assert_eq!(**frac, 2.0/3.0);
529 assert_eq!(*point, [1.0, 0.0, 0.5].into());
530 assert_eq!(*id, block1_id);
531 }
532}