1use 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#[cfg_attr(feature = "derive_serdes", derive(Deserialize, Serialize))]
21#[derive(Clone, Debug, Default)]
22pub struct System <INTG : Integrator> {
23 step : u64,
25 objects_static : VecMap <object::Static>,
27 objects_dynamic : VecMap <object::Dynamic>,
29 objects_nodetect : VecMap <object::Nodetect>,
31 gravity : Option <force::Gravity>,
33 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 Default::default()
43 }
44 #[inline]
45 pub const fn step (&self) -> u64 {
46 self.step
47 }
48 #[inline]
49 pub const fn objects_dynamic (&self) -> &VecMap <object::Dynamic> {
50 &self.objects_dynamic
51 }
52 #[inline]
53 pub const fn objects_nodetect (&self) -> &VecMap <object::Nodetect> {
54 &self.objects_nodetect
55 }
56 #[inline]
57 pub const fn objects_static (&self) -> &VecMap <object::Static> {
58 &self.objects_static
59 }
60
61 #[expect(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 <(math::Normalized <f64>, math::Point3 <f64>, object::Id)> {
74 use object::Bounded;
75 let mut out = Vec::<(math::Normalized <f64>, math::Point3 <f64>, object::Id)>::new();
76 let aabb = segment.aabb3();
77 let mut overlaps : Vec <object::Id> = self.collision.broad()
78 .overlaps_discrete (aabb).into_iter().map (object::Id::from).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 object::Kind::Nodetect => 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 #[expect(clippy::doc_overindented_list_items)]
99 pub fn handle_event (&mut self, input : event::Input) -> Vec <event::Output> {
122 let input_step = self.step;
123 log::debug!(step=input_step, input=input.to_string().as_str();
124 "input event");
125 let mut output = Vec::new();
126 match input {
127
128 event::Input::CreateObject (object, key) => {
132 let key = key.unwrap_or_else (
133 || self.new_object_key (object.kind()).unwrap());
134 match object {
135 object::Variant::Static (object) => {
136 self.create_object_static (object, key, &mut output);
137 }
138 object::Variant::Dynamic (object) => {
139 self.create_object_dynamic (object, key, &mut output);
140 }
141 object::Variant::Nodetect (object) => {
142 self.create_object_nodetect (object, key);
143 }
144 }
145 }
146
147 event::Input::ModifyObject (object_modify_event) => {
151 match object_modify_event {
152 event::ObjectModify::Dynamic (key, event) => {
153 self.modify_object_dynamic (key, event);
154 }
155 event::ObjectModify::Static (key, event) => {
156 self.modify_object_static (key, event);
157 }
158 }
159 }
160
161 event::Input::DestroyObject (object::Id { kind, key }) => {
165 match kind {
166 object::Kind::Static => {
167 self.collision.remove_object_static (key);
168 assert!(self.objects_static.remove (key.index()).is_some());
169 }
170 object::Kind::Dynamic => {
171 self.collision.remove_object_dynamic (key);
172 assert!(self.objects_dynamic.remove (key.index()).is_some());
173 }
174 object::Kind::Nodetect =>
175 assert!(self.objects_nodetect.remove (key.index()).is_some())
176 }
177 }
178
179 event::Input::SetGravity (gravity) => {
183 assert!(self.gravity.is_none());
184 self.gravity = Some (gravity);
185 }
186
187 event::Input::ClearGravity => {
191 assert!(self.gravity.is_some());
192 self.gravity = None;
193 }
194
195 event::Input::Step => {
199 #[cfg(feature = "debug_dump")]
200 let s = self.clone();
201
202 self.derivative_evaluation();
205 self.integrate_velocity();
207 self.constrain_velocity();
209 self.integrate_position();
211 self.constrain_position();
213 self.collision (&mut output);
215
216 #[cfg(feature = "debug_dump")]
217 if collision::DEBUG_DUMP.swap (false, std::sync::atomic::Ordering::SeqCst)
218 && std::env::var ("LINEAR_SIM_DEBUG_DUMP").is_ok()
219 {
220 let p = format!("linear-sim-{}.dump", s.step);
221 log::info!(filepath=p.as_str(); "dumping system");
222 let mut f = std::fs::File::create (&p).unwrap();
223 bincode::serde::encode_into_std_write (&s, &mut f, bincode::config::standard())
224 .unwrap();
225 }
226
227 self.step += 1;
228 }
229
230 }
231 log::debug!(
232 step=input_step,
233 output:?=output.iter().map (ToString::to_string).collect::<Vec<_>>();
234 "output events");
235 output
236 }
237
238 pub fn new_object_key (&self, kind : object::Kind) -> Option <object::Key> {
240 fn next_available_key <T> (map : &VecMap <T>) -> Option <object::Key> {
241 for i in 0..object::KEY_MAX as usize {
242 if !map.contains_key (i) {
243 return Some (i.into())
244 }
245 }
246 None
247 }
248 match kind {
249 object::Kind::Static => next_available_key (&self.objects_static),
250 object::Kind::Dynamic => next_available_key (&self.objects_dynamic),
251 object::Kind::Nodetect => next_available_key (&self.objects_nodetect)
252 }
253 }
254
255 pub fn contacts (&self) -> Vec <Vec <(object::Id, object::Id, collision::Contact)>> {
256 let mut groups = vec![];
257 for (_, group) in self.collision.contact_groups().iter() {
258 let mut v = vec![];
259 for (object_pair, contact) in group.contacts.iter().cloned() {
260 let (object_id_a, object_id_b) = object_pair.into();
261 v.push ((object_id_a.into(), object_id_b.into(), contact));
262 }
263 groups.push (v);
264 }
265 groups
266 }
267
268 fn create_object_static (&mut self,
273 object : object::Static,
274 key : object::Key,
275 output : &mut Vec <event::Output>
276 ) {
277 let object_id = object::Id { kind: object::Kind::Static, key };
278 match self.collision.try_add_object_static (
280 &self.objects_dynamic, &object, key
281 ) {
282 Ok (()) => {
283 assert!(self.objects_static.insert (key.index(), object).is_none());
285 output.push (event::CreateObjectResult::Created (object_id).into());
286 }
287 Err (intersections) => {
288 debug_assert!(!intersections.is_empty());
289 output.push (
290 event::CreateObjectResult::Intersection (intersections).into());
291 }
292 }
293 }
294
295 fn create_object_dynamic (&mut self,
296 object : object::Dynamic,
297 key : object::Key,
298 output : &mut Vec <event::Output>
299 ) {
300 let object_id = object::Id { kind: object::Kind::Dynamic, key };
301 match self.collision.try_add_object_dynamic (
303 &self.objects_static, &self.objects_dynamic, &object, key
304 ) {
305 Ok (()) => {
306 assert!(self.objects_dynamic.insert (key.index(), object).is_none());
308 output.push (event::CreateObjectResult::Created (object_id).into());
309 }
310 Err (intersections) => {
311 debug_assert!(!intersections.is_empty());
312 output.push (
313 event::CreateObjectResult::Intersection (intersections).into());
314 }
315 }
316 }
317
318 fn create_object_nodetect (&mut self,
319 object : object::Nodetect,
320 key : object::Key,
321 ) {
322 assert!(self.objects_nodetect.insert (key.index(), object).is_none());
323 }
324
325 fn modify_object_dynamic (&mut self,
327 key : object::Key,
328 event : event::ObjectModifyDynamic
329 ) {
330 let object = &mut self.objects_dynamic[key.index()];
331 match event {
332 event::ObjectModifyDynamic::ApplyImpulse (impulse) =>
333 object.derivatives.velocity += impulse,
334 event::ObjectModifyDynamic::SetForceFlags (force_flags) =>
335 object.derivatives.force_flags = force_flags,
336 event::ObjectModifyDynamic::SetDrag (drag) => object.drag.0 = drag,
337 event::ObjectModifyDynamic::SetPosition (position) => {
338 object.position = position;
339 self.collision.update_object_dynamic (object, key);
340 log::warn!("TODO: dynamic object set position results");
341 }
342 }
343 }
344
345 fn modify_object_static (&mut self,
347 key : object::Key,
348 event : event::ObjectModifyStatic
349 ) {
350 let object = &mut self.objects_static[key.index()];
351 match event {
352 event::ObjectModifyStatic::Move (move_vector) => {
354 object.position.0 += move_vector;
355 self.collision.update_object_static (object, key);
356 unimplemented!("TODO: move static object results");
357 }
358 }
359 }
360
361 fn derivative_evaluation (&mut self) {
363 for_sequence!{
365 (_, object) in (
366 self.objects_dynamic.iter_mut(), self.objects_nodetect.iter_mut()
367 ) {
368 object.derivatives.force = [0.0, 0.0, 0.0].into();
369 }
370 }
371
372 if let Some (gravity) = &self.gravity {
374 for_sequence!{
375 (_, object) in (
376 self.objects_dynamic.iter_mut(), self.objects_nodetect.iter_mut()
377 ) {
378 if object.derivatives.force_flags.contains (force::Flag::Gravity) {
379 use crate::Force;
380 let impulse = gravity.impulse (object, self.step, 1.0);
381 object.derivatives.force += impulse;
382 }
383 }
384 }
385 }
386
387 for_sequence!{
389 (_, object) in (
390 self.objects_dynamic.iter_mut(), self.objects_nodetect.iter_mut()
391 ) {
392 use object::Inertial;
393 object.compute_acceleration_inplace();
394 }
395 }
396 }
397
398 fn integrate_velocity (&mut self) {
400 for_sequence!{
401 (_, object) in (
402 self.objects_dynamic.iter_mut(), self.objects_nodetect.iter_mut()
403 ) {
404 INTG::integrate_velocity (object);
405 object.derivatives.velocity *= 1.0 - object.drag.0;
410 }
411 }
412 }
413
414 #[inline]
416 fn constrain_velocity (&mut self) {
417 self.collision
418 .constrain_contact_velocities (&self.objects_static, &mut self.objects_dynamic);
419 }
420
421 fn integrate_position (&mut self) {
423 for_sequence!{
424 (_, object) in (
425 self.objects_dynamic.iter_mut(), self.objects_nodetect.iter_mut()
426 ) {
427 INTG::integrate_position (object);
428 }
429 }
430 }
431
432 #[inline]
434 fn constrain_position (&mut self) {
435 self.collision
436 .constrain_contact_positions (&self.objects_static, &mut self.objects_dynamic);
437 }
438
439 fn collision (&mut self, output : &mut Vec <event::Output>) {
441 self.collision.detect_resolve_loop (
442 &mut self.objects_static, &mut self.objects_dynamic, self.step, output);
443 }
444
445}
446
447pub fn report_sizes() {
449 use std::mem::size_of;
450 println!("system report sizes...");
451
452 println!(" size of System <integrator::SemiImplicitEuler>: {}",
453 size_of::<System <integrator::SemiImplicitEuler>>());
454
455 println!("...system report sizes");
456}
457
458#[cfg(test)]
459mod tests {
460 use super::*;
461 use crate::component;
462 use crate::geometry::{self, shape};
463 #[test]
464 fn segment_query() {
465 let segment = geometry::Segment3::noisy (
466 [-1.0, 0.0, 0.5].into(),
467 [ 2.0, 0.0, 0.5].into());
468 let mut system = System::<integrator::SemiImplicitEuler>::new();
469 let sphere1 = {
470 let position = component::Position ([0.5, 0.0, 0.5].into());
471 let mass = component::Mass::new (20.0);
472 let derivatives = component::Derivatives::zero();
473 let drag = component::Drag::zero();
474 let bound = component::Bound::from (shape::Sphere::noisy (0.5));
475 let material = component::MATERIAL_STONE;
476 let collidable = true;
477 object::Dynamic {
478 position, mass, derivatives, drag, bound, material, collidable
479 }.into()
480 };
481 let mut result = system.handle_event (event::Input::CreateObject (sphere1, None));
482 let event::Output::CreateObjectResult (
483 event::CreateObjectResult::Created (sphere1_id)
484 ) = result.pop().unwrap() else { unreachable!() };
485 assert!(result.pop().is_none());
486 let block1 = {
487 let position = component::Position ([1.5, 0.0, 0.5].into());
488 let bound = component::Bound (
489 shape::Bounded::from (shape::Cuboid::noisy ([0.5, 0.5, 0.5])).into());
490 let material = component::MATERIAL_STONE;
491 let collidable = true;
492 object::Static { position, bound, material, collidable }.into()
493 };
494 let mut result = system.handle_event (event::Input::CreateObject (block1, None));
495 let event::Output::CreateObjectResult (
496 event::CreateObjectResult::Created (block1_id)
497 ) = result.pop().unwrap() else { unreachable!() };
498 assert!(result.pop().is_none());
499 let result = system.segment_query (segment, &[]);
500 let (frac, point, id) = result.first().unwrap();
501 assert_eq!(**frac, 1.0/3.0);
502 assert_eq!(*point, [0.0, 0.0, 0.5].into());
503 assert_eq!(*id, sphere1_id);
504 let (frac, point, id) = result.get (1).unwrap();
505 assert_eq!(**frac, 2.0/3.0);
506 assert_eq!(*point, [1.0, 0.0, 0.5].into());
507 assert_eq!(*id, block1_id);
508 }
509}