Skip to main content

dynamis_world/world/
body.rs

1use super::World;
2use super::commands::BodyCommand;
3use super::ids::IdSpace;
4use crate::world::static_aabb;
5use bytemuck::Zeroable;
6use dynamis_layout::{
7    AabbRecord, BODY_CCD, BODY_KINEMATIC, BodyDescriptorRecord, BodyStateRecord, ColliderRecord,
8    OVERRIDE_SLEEP_ANGULAR, OVERRIDE_SLEEP_LINEAR, PATCH_ANGULAR_VELOCITY, PATCH_ORIENTATION,
9    PATCH_POSITION, PATCH_VELOCITY,
10};
11use dynamis_model::{
12    BodyDesc, BodyHandle, BodyState, ColliderDesc, ContactEventMode, MAX_COLLIDERS_PER_BODY,
13    MassProperties, Shape,
14};
15
16pub(crate) struct Bodies {
17    pub(crate) alive: Vec<BodyHandle>,
18    pub(crate) ids: IdSpace,
19    pub(crate) index_of: Vec<u32>,
20    pub(crate) collider_descs: Vec<Vec<ColliderDesc>>,
21    pub(crate) masses: Vec<f32>,
22    pub(crate) com_overrides: Vec<Option<[f32; 3]>>,
23    pub(crate) inertia_overrides: Vec<Option<[f32; 6]>>,
24    pub(crate) descriptors: Vec<BodyDescriptorRecord>,
25    pub(crate) dynamic_count: usize,
26    pub(crate) kinematic: Vec<bool>,
27    pub(crate) states: Vec<Option<BodyState>>,
28    pub(crate) states_ready: bool,
29    pub(crate) device_count: u32,
30    pub(crate) commands: Vec<BodyCommand>,
31    pub(crate) dirty: Vec<u32>,
32    pub(crate) last_moves: u32,
33    pub(crate) last_edits: u32,
34}
35
36impl Bodies {
37    pub(crate) const fn new() -> Self {
38        Self {
39            alive: Vec::new(),
40            ids: IdSpace::new(),
41            index_of: Vec::new(),
42            collider_descs: Vec::new(),
43            masses: Vec::new(),
44            com_overrides: Vec::new(),
45            inertia_overrides: Vec::new(),
46            descriptors: Vec::new(),
47            dynamic_count: 0,
48            kinematic: Vec::new(),
49            states: Vec::new(),
50            states_ready: true,
51            device_count: 0,
52            commands: Vec::new(),
53            dirty: Vec::new(),
54            last_moves: 0,
55            last_edits: 0,
56        }
57    }
58
59    fn grow_to(&mut self, id: u32) {
60        let rows = id as usize + 1;
61        if rows <= self.index_of.len() {
62            return;
63        }
64        self.index_of.resize(rows, u32::MAX);
65        self.collider_descs.resize(rows, Vec::new());
66        self.masses.resize(rows, 1.0);
67        self.com_overrides.resize(rows, None);
68        self.inertia_overrides.resize(rows, None);
69        self.descriptors
70            .resize(rows, BodyDescriptorRecord::zeroed());
71        self.kinematic.resize(rows, false);
72        self.states.resize(rows, None);
73    }
74}
75
76impl World {
77    pub fn spawn(&mut self, desc: BodyDesc) -> BodyHandle {
78        let (id, generation) = self.bodies.ids.acquire();
79        self.bodies.grow_to(id);
80        let handle = BodyHandle { id, generation };
81        let id = id as usize;
82        self.validate_world_geometry(&desc);
83        let mass = desc.effective_mass(|shape| self.shape_bounds(shape));
84        let mut spawn_desc = desc.clone();
85        spawn_desc.mass = mass;
86        self.bodies.masses[id] = mass;
87        self.bodies.com_overrides[id] = desc.com;
88        self.bodies.inertia_overrides[id] = desc.inertia;
89        self.bodies.kinematic[id] = desc.kinematic;
90        self.bodies.collider_descs[id] = desc.colliders.clone();
91        for collider in &desc.colliders {
92            self.retain_shape_ref(&collider.shape);
93        }
94        let mass_properties = self.mass_properties_of(id);
95        self.bodies.descriptors[id] =
96            BodyDescriptorRecord::build(&spawn_desc, mass_properties, &self.config);
97        self.record_state(id, &spawn_desc);
98        let state = BodyStateRecord::initial(&spawn_desc, id as u32, handle.generation);
99        let slot = self.bodies.alive.len() as u32;
100        self.bodies.index_of[id] = slot;
101        self.bodies.alive.push(handle);
102        self.bodies.dirty.push(slot);
103        self.bodies
104            .commands
105            .push(BodyCommand::Add { row: slot, state });
106        if mass > 0.0 || desc.kinematic {
107            if (self.bodies.dynamic_count as u32) < slot {
108                self.swap_slots(self.bodies.dynamic_count as u32, slot);
109            }
110            self.bodies.dynamic_count += 1;
111        }
112        handle
113    }
114
115    pub fn remove(&mut self, handle: BodyHandle) {
116        self.validate(handle);
117        self.assert_no_constraints(handle);
118        let id = handle.id as usize;
119        let slot = self.bodies.index_of[id];
120        if (slot as usize) < self.bodies.dynamic_count {
121            let tail_dynamic = (self.bodies.dynamic_count - 1) as u32;
122            if slot != tail_dynamic {
123                self.swap_slots(slot, tail_dynamic);
124            }
125            self.bodies.dynamic_count -= 1;
126            let last = (self.bodies.alive.len() - 1) as u32;
127            if tail_dynamic != last {
128                self.swap_slots(tail_dynamic, last);
129            }
130        } else {
131            let last = (self.bodies.alive.len() - 1) as u32;
132            if slot != last {
133                self.swap_slots(slot, last);
134            }
135        }
136        self.discard_tail();
137    }
138
139    fn discard_tail(&mut self) {
140        let last = (self.bodies.alive.len() - 1) as u32;
141        let handle = self.bodies.alive[last as usize];
142        let id = handle.id as usize;
143        self.bodies.index_of[id] = u32::MAX;
144        self.bodies.ids.release(handle.id);
145        self.bodies.alive.pop();
146        self.bodies.dirty.retain(|dirty| *dirty != last);
147        let removed = std::mem::take(&mut self.bodies.collider_descs[id]);
148        for collider in &removed {
149            self.release_shape_ref(&collider.shape);
150        }
151        self.bodies.states[id] = None;
152        self.bodies.kinematic[id] = false;
153        self.bodies.descriptors[id] = BodyDescriptorRecord::zeroed();
154        self.bodies.commands.push(BodyCommand::Remove {
155            hole: last,
156            tail: last,
157        });
158    }
159
160    fn swap_slots(&mut self, first: u32, second: u32) {
161        self.bodies.alive.swap(first as usize, second as usize);
162        self.bodies.index_of[self.bodies.alive[first as usize].id as usize] = first;
163        self.bodies.index_of[self.bodies.alive[second as usize].id as usize] = second;
164        self.remap_constraint_slots(first, second);
165        self.bodies
166            .commands
167            .push(BodyCommand::Swap { first, second });
168        self.bodies.dirty.push(first);
169        self.bodies.dirty.push(second);
170    }
171
172    fn migrate_partition(&mut self, handle: BodyHandle) {
173        let id = handle.id as usize;
174        let static_now = self.is_static_id(id);
175        let slot = self.bodies.index_of[id];
176        let in_dynamic = (slot as usize) < self.bodies.dynamic_count;
177        if static_now == !in_dynamic {
178            return;
179        }
180        if static_now {
181            let tail = (self.bodies.dynamic_count - 1) as u32;
182            if slot != tail {
183                self.swap_slots(slot, tail);
184            }
185            self.bodies.dynamic_count -= 1;
186            self.bodies.commands.push(BodyCommand::Patch {
187                row: tail,
188                mask: PATCH_VELOCITY,
189                state: BodyStateRecord::zeroed(),
190            });
191            if let Some(state) = self.bodies.states[id].as_mut() {
192                state.velocity = [0.0; 3];
193                state.angular_velocity = [0.0; 3];
194            }
195            self.bodies.dirty.push(tail);
196        } else {
197            let boundary = self.bodies.dynamic_count as u32;
198            if slot != boundary {
199                self.swap_slots(slot, boundary);
200            }
201            self.bodies.dynamic_count += 1;
202        }
203        self.bodies.dirty.push(slot);
204    }
205
206    pub fn read_state(&self, handle: BodyHandle) -> BodyState {
207        self.validate(handle);
208        assert!(
209            self.bodies.states_ready,
210            "body states require synchronize_states() after stepping"
211        );
212        self.bodies.states[handle.id as usize].expect("body state is unavailable")
213    }
214
215    fn record_state(&mut self, id: usize, desc: &BodyDesc) {
216        self.bodies.states[id] = Some(BodyState {
217            position: desc.position,
218            prev_position: desc.position,
219            orientation: desc.orientation,
220            velocity: desc.velocity,
221            angular_velocity: desc.angular_velocity,
222            inverse_mass: self.bodies.descriptors[id].inverse_mass,
223            com: self.bodies.descriptors[id].com,
224            sleeping: false,
225            step: self.clock.step,
226        });
227    }
228
229    fn validate_world_geometry(&self, desc: &BodyDesc) {
230        for collider in &desc.colliders {
231            if collider.shape.is_world_geometry() && (desc.mass > 0.0 && !desc.kinematic) {
232                panic!("world geometry colliders must be static or kinematic");
233            }
234        }
235    }
236
237    fn is_static_id(&self, id: usize) -> bool {
238        self.bodies.masses[id] <= 0.0 && !self.bodies.kinematic[id]
239    }
240
241    fn observed_pose(&self, id: usize) -> ([f32; 3], [f32; 4]) {
242        match self.bodies.states[id] {
243            Some(state) => (state.position, state.orientation),
244            None => panic!("static body has no observed pose"),
245        }
246    }
247
248    pub(super) fn aabb_block_of(&self, id: usize) -> [AabbRecord; MAX_COLLIDERS_PER_BODY] {
249        if self.is_static_id(id) {
250            let (position, orientation) = self.observed_pose(id);
251            static_aabb::static_aabbs(
252                position,
253                orientation,
254                &self.collider_block_of(id),
255                &self.shapes.pool,
256            )
257        } else {
258            [AabbRecord::empty(); MAX_COLLIDERS_PER_BODY]
259        }
260    }
261
262    fn patch_descriptor(
263        &mut self,
264        handle: BodyHandle,
265        edit: impl FnOnce(&mut BodyDescriptorRecord),
266    ) {
267        self.validate(handle);
268        let id = handle.id as usize;
269        edit(&mut self.bodies.descriptors[id]);
270        self.bodies.dirty.push(self.bodies.index_of[id]);
271    }
272
273    fn refresh_descriptor(&mut self, handle: BodyHandle) {
274        let id = handle.id as usize;
275        let mass = self.mass_properties_of(id);
276        let kinematic = self.bodies.kinematic[id];
277        let inverse_mass = if kinematic || self.bodies.masses[id] <= 0.0 {
278            0.0
279        } else {
280            1.0 / self.bodies.masses[id]
281        };
282        let descriptor = &mut self.bodies.descriptors[id];
283        descriptor.com = mass.com;
284        descriptor.inverse_inertia = mass.inverse_inertia;
285        descriptor.inverse_mass = inverse_mass;
286        descriptor.flags =
287            (descriptor.flags & !BODY_KINEMATIC) | if kinematic { BODY_KINEMATIC } else { 0 };
288        let row = *descriptor;
289        self.bodies.dirty.push(self.bodies.index_of[id]);
290        if let Some(state) = self.bodies.states[id].as_mut() {
291            state.inverse_mass = row.inverse_mass;
292            state.com = row.com;
293        }
294    }
295
296    fn apply_mass(&mut self, handle: BodyHandle, mass: f32) {
297        self.bodies.masses[handle.id as usize] = mass;
298        self.refresh_descriptor(handle);
299    }
300
301    fn command_slot(&self, handle: BodyHandle) -> u32 {
302        self.validate(handle);
303        self.bodies.index_of[handle.id as usize]
304    }
305
306    fn schedule_patch(&mut self, handle: BodyHandle, mask: u32, state: BodyStateRecord) {
307        let slot = self.command_slot(handle);
308        self.bodies.commands.push(BodyCommand::Patch {
309            row: slot,
310            mask,
311            state,
312        });
313    }
314
315    pub fn set_position(&mut self, handle: BodyHandle, position: [f32; 3]) {
316        self.validate(handle);
317        if let Some(state) = self.bodies.states[handle.id as usize].as_mut() {
318            state.position = position;
319            state.prev_position = position;
320        }
321        let mut payload = BodyStateRecord::zeroed();
322        payload.position = position;
323        self.schedule_patch(handle, PATCH_POSITION, payload);
324        self.bodies
325            .dirty
326            .push(self.bodies.index_of[handle.id as usize]);
327    }
328
329    pub fn set_orientation(&mut self, handle: BodyHandle, orientation: [f32; 4]) {
330        self.assert_unit(orientation);
331        self.validate(handle);
332        if let Some(state) = self.bodies.states[handle.id as usize].as_mut() {
333            state.orientation = orientation;
334        }
335        let mut payload = BodyStateRecord::zeroed();
336        payload.orientation = orientation;
337        self.schedule_patch(handle, PATCH_ORIENTATION, payload);
338        self.bodies
339            .dirty
340            .push(self.bodies.index_of[handle.id as usize]);
341    }
342
343    pub fn set_velocity(&mut self, handle: BodyHandle, velocity: [f32; 3]) {
344        self.validate(handle);
345        if let Some(state) = self.bodies.states[handle.id as usize].as_mut() {
346            state.velocity = velocity;
347        }
348        let mut payload = BodyStateRecord::zeroed();
349        payload.velocity = velocity;
350        self.schedule_patch(handle, PATCH_VELOCITY, payload);
351    }
352
353    pub fn set_angular_velocity(&mut self, handle: BodyHandle, angular_velocity: [f32; 3]) {
354        self.validate(handle);
355        if let Some(state) = self.bodies.states[handle.id as usize].as_mut() {
356            state.angular_velocity = angular_velocity;
357        }
358        let mut payload = BodyStateRecord::zeroed();
359        payload.angular_velocity = angular_velocity;
360        self.schedule_patch(handle, PATCH_ANGULAR_VELOCITY, payload);
361    }
362
363    pub fn set_mass(&mut self, handle: BodyHandle, mass: f32) {
364        assert!(mass >= 0.0, "mass must be non-negative");
365        self.validate(handle);
366        self.apply_mass(handle, mass);
367        self.migrate_partition(handle);
368    }
369
370    pub fn set_density(&mut self, handle: BodyHandle, density: f32) {
371        assert!(density >= 0.0, "density must be non-negative");
372        self.validate(handle);
373        let volume = dynamis_model::solid_volume_of(
374            &self.bodies.collider_descs[handle.id as usize],
375            &|shape| self.shape_bounds(shape),
376        );
377        self.apply_mass(handle, density * volume);
378        self.migrate_partition(handle);
379    }
380
381    pub fn set_com(&mut self, handle: BodyHandle, com: [f32; 3]) {
382        self.validate(handle);
383        self.bodies.com_overrides[handle.id as usize] = Some(com);
384        self.refresh_descriptor(handle);
385    }
386
387    pub fn set_inertia(&mut self, handle: BodyHandle, inertia: [f32; 6]) {
388        assert!(
389            inertia.iter().all(|value| value.is_finite()),
390            "inertia tensor must be finite"
391        );
392        self.validate(handle);
393        self.bodies.inertia_overrides[handle.id as usize] = Some(inertia);
394        self.refresh_descriptor(handle);
395    }
396
397    pub fn set_linear_damping(&mut self, handle: BodyHandle, damping: f32) {
398        assert!(damping >= 0.0, "damping must be non-negative");
399        self.patch_descriptor(handle, |row| row.linear_damping = damping);
400    }
401
402    pub fn set_angular_damping(&mut self, handle: BodyHandle, damping: f32) {
403        assert!(damping >= 0.0, "angular damping must be non-negative");
404        self.patch_descriptor(handle, |row| row.angular_damping = damping);
405    }
406
407    pub fn set_gravity_scale(&mut self, handle: BodyHandle, gravity_scale: f32) {
408        self.patch_descriptor(handle, |row| row.gravity_scale = gravity_scale);
409    }
410
411    pub fn set_sleep_thresholds(
412        &mut self,
413        handle: BodyHandle,
414        velocity: f32,
415        angular_velocity: f32,
416    ) {
417        assert!(velocity >= 0.0, "sleep velocity must be non-negative");
418        assert!(
419            angular_velocity >= 0.0,
420            "sleep angular velocity must be non-negative"
421        );
422        self.patch_descriptor(handle, |row| {
423            row.sleep_velocity = velocity;
424            row.sleep_angular_velocity = angular_velocity;
425            row.flags |= OVERRIDE_SLEEP_LINEAR | OVERRIDE_SLEEP_ANGULAR;
426        });
427    }
428
429    pub fn set_collision_group(&mut self, handle: BodyHandle, group: u32) {
430        self.patch_descriptor(handle, |row| row.collision_group = group);
431    }
432
433    pub fn set_collision_mask(&mut self, handle: BodyHandle, mask: u32) {
434        self.patch_descriptor(handle, |row| row.collision_mask = mask);
435    }
436
437    pub fn set_ccd(&mut self, handle: BodyHandle, ccd: bool) {
438        self.patch_descriptor(handle, |row| {
439            row.flags = (row.flags & !BODY_CCD) | if ccd { BODY_CCD } else { 0 };
440        });
441    }
442
443    pub fn set_kinematic(&mut self, handle: BodyHandle, kinematic: bool) {
444        self.validate(handle);
445        self.bodies.kinematic[handle.id as usize] = kinematic;
446        self.refresh_descriptor(handle);
447        self.migrate_partition(handle);
448    }
449
450    pub fn set_collider(&mut self, handle: BodyHandle, index: usize, collider: ColliderDesc) {
451        self.validate(handle);
452        let id = handle.id as usize;
453        assert!(
454            index < self.bodies.collider_descs[id].len(),
455            "collider index out of range"
456        );
457        let existing = std::mem::replace(&mut self.bodies.collider_descs[id][index], collider);
458        self.release_shape_ref(&existing.shape);
459        self.retain_shape_ref(&collider.shape);
460        self.refresh_descriptor(handle);
461    }
462
463    pub fn add_collider(&mut self, handle: BodyHandle, collider: ColliderDesc) {
464        self.validate(handle);
465        let id = handle.id as usize;
466        let count = self.bodies.collider_descs[id].len();
467        assert!(
468            count < MAX_COLLIDERS_PER_BODY,
469            "collider capacity per body reached"
470        );
471        self.retain_shape_ref(&collider.shape);
472        self.bodies.collider_descs[id].push(collider);
473        self.refresh_descriptor(handle);
474    }
475
476    pub fn remove_collider(&mut self, handle: BodyHandle, index: usize) {
477        self.validate(handle);
478        let id = handle.id as usize;
479        assert!(
480            index < self.bodies.collider_descs[id].len(),
481            "collider index out of range"
482        );
483        assert!(
484            self.bodies.collider_descs[id].len() > 1,
485            "a body requires at least one collider"
486        );
487        let removed = self.bodies.collider_descs[id].swap_remove(index);
488        self.release_shape_ref(&removed.shape);
489        self.refresh_descriptor(handle);
490    }
491
492    pub fn set_shape(&mut self, handle: BodyHandle, shape: Shape) {
493        self.validate(handle);
494        let id = handle.id as usize;
495        let replaced = std::mem::replace(&mut self.bodies.collider_descs[id][0].shape, shape);
496        self.release_shape_ref(&replaced);
497        self.retain_shape_ref(&shape);
498        self.refresh_descriptor(handle);
499    }
500
501    pub fn set_restitution(&mut self, handle: BodyHandle, restitution: f32) {
502        self.validate(handle);
503        self.bodies.collider_descs[handle.id as usize][0].restitution = restitution;
504        self.bodies
505            .dirty
506            .push(self.bodies.index_of[handle.id as usize]);
507    }
508
509    pub fn set_friction(&mut self, handle: BodyHandle, friction: f32) {
510        assert!(friction >= 0.0, "friction must be non-negative");
511        self.validate(handle);
512        self.bodies.collider_descs[handle.id as usize][0].friction = friction;
513        self.bodies
514            .dirty
515            .push(self.bodies.index_of[handle.id as usize]);
516    }
517
518    pub fn set_collider_events(
519        &mut self,
520        handle: BodyHandle,
521        index: usize,
522        events: ContactEventMode,
523    ) {
524        self.validate(handle);
525        let id = handle.id as usize;
526        assert!(
527            index < self.bodies.collider_descs[id].len(),
528            "collider index out of range"
529        );
530        self.bodies.collider_descs[id][index].events = events;
531        self.bodies.dirty.push(self.bodies.index_of[id]);
532    }
533
534    pub fn apply_force(&mut self, handle: BodyHandle, force: [f32; 3]) {
535        let slot = self.command_slot(handle);
536        self.bodies
537            .commands
538            .push(BodyCommand::Force { row: slot, force });
539    }
540
541    pub fn apply_force_at_point(&mut self, handle: BodyHandle, force: [f32; 3], point: [f32; 3]) {
542        let slot = self.command_slot(handle);
543        self.bodies.commands.push(BodyCommand::ForceAtPoint {
544            row: slot,
545            force,
546            point,
547        });
548    }
549
550    pub fn apply_torque(&mut self, handle: BodyHandle, torque: [f32; 3]) {
551        let slot = self.command_slot(handle);
552        self.bodies
553            .commands
554            .push(BodyCommand::Torque { row: slot, torque });
555    }
556
557    pub fn apply_impulse(&mut self, handle: BodyHandle, impulse: [f32; 3]) {
558        let slot = self.command_slot(handle);
559        self.bodies
560            .commands
561            .push(BodyCommand::Impulse { row: slot, impulse });
562    }
563
564    pub fn apply_impulse_at_point(
565        &mut self,
566        handle: BodyHandle,
567        impulse: [f32; 3],
568        point: [f32; 3],
569    ) {
570        let slot = self.command_slot(handle);
571        self.bodies.commands.push(BodyCommand::ImpulseAtPoint {
572            row: slot,
573            impulse,
574            point,
575        });
576    }
577
578    pub fn apply_angular_impulse(&mut self, handle: BodyHandle, impulse: [f32; 3]) {
579        let slot = self.command_slot(handle);
580        self.bodies
581            .commands
582            .push(BodyCommand::AngularImpulse { row: slot, impulse });
583    }
584
585    pub fn wake(&mut self, handle: BodyHandle) {
586        let slot = self.command_slot(handle);
587        self.bodies.commands.push(BodyCommand::Wake { row: slot });
588    }
589
590    pub fn sleep(&mut self, handle: BodyHandle) {
591        let slot = self.command_slot(handle);
592        self.bodies.commands.push(BodyCommand::Sleep { row: slot });
593    }
594
595    pub(crate) fn collider_block_of(&self, id: usize) -> [ColliderRecord; MAX_COLLIDERS_PER_BODY] {
596        std::array::from_fn(|index| {
597            self.bodies.collider_descs[id]
598                .get(index)
599                .map_or(ColliderRecord::zeroed(), |collider| {
600                    self.collider_record(collider)
601                })
602        })
603    }
604
605    pub(super) fn validate(&self, handle: BodyHandle) {
606        let id = handle.id as usize;
607        if id >= self.bodies.ids.len() {
608            panic!("body handle {handle:?} is out of range");
609        }
610        if self.bodies.ids.generation(handle.id) != handle.generation {
611            panic!("body handle {handle:?} is stale");
612        }
613        if self.bodies.index_of[id] == u32::MAX {
614            panic!("body handle {handle:?} is not alive");
615        }
616    }
617
618    pub(super) fn assert_unit(&self, orientation: [f32; 4]) {
619        assert!(
620            (orientation[0] * orientation[0]
621                + orientation[1] * orientation[1]
622                + orientation[2] * orientation[2]
623                + orientation[3] * orientation[3]
624                - 1.0)
625                .abs()
626                < 1e-4,
627            "orientation must be a unit quaternion"
628        );
629    }
630
631    fn retain_shape_ref(&mut self, shape: &Shape) {
632        if let Shape::Hull(handle) | Shape::Mesh(handle) | Shape::HeightField(handle) = shape {
633            self.shapes.pool.retain(*handle);
634        }
635    }
636
637    fn release_shape_ref(&mut self, shape: &Shape) {
638        if let Shape::Hull(handle) | Shape::Mesh(handle) | Shape::HeightField(handle) = shape {
639            self.shapes.pool.release(*handle);
640        }
641    }
642}
643
644impl World {
645    pub(crate) fn state_snapshot(&self, id: usize) -> Option<BodyState> {
646        assert!(
647            self.bodies.states_ready,
648            "body states require synchronize_states() after stepping"
649        );
650        self.bodies.states[id]
651    }
652
653    pub(crate) fn mass_properties_of(&self, id: usize) -> MassProperties {
654        dynamis_model::mass_properties_of_intent(
655            &self.bodies.collider_descs[id],
656            self.bodies.masses[id],
657            self.bodies.com_overrides[id],
658            self.bodies.inertia_overrides[id],
659            |shape| self.shape_bounds(shape),
660        )
661    }
662
663    pub(crate) fn collider_record(&self, desc: &ColliderDesc) -> ColliderRecord {
664        let source = match desc.shape {
665            Shape::Hull(handle) | Shape::Mesh(handle) | Shape::HeightField(handle) => handle.id,
666            _ => 0,
667        };
668        ColliderRecord::build(desc, source)
669    }
670}