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}