1#![allow(dead_code)]
2
3use na::{Isometry3, Matrix4, Point3, Quaternion, Translation3, Unit, UnitQuaternion, Vector3};
4use physx::cooking::{
5 ConvexMeshCookingResult, PxConvexMeshDesc, PxCookingParams, PxHeightFieldDesc,
6 PxTriangleMeshDesc, TriangleMeshCookingResult,
7};
8use physx::foundation::DefaultAllocator;
9use physx::prelude::*;
10use physx::scene::{FrictionType, SceneFlags};
11use physx::traits::Class;
12use physx_sys::PxFilterFlags;
13use physx_sys::{
14 FilterShaderCallbackInfo, PxBitAndByte, PxConvexFlags, PxConvexMeshGeometryFlags,
15 PxHeightFieldSample, PxMeshGeometryFlags, PxMeshScale_new, PxRigidActor,
16};
17use rapier::counters::Counters;
18use rapier::dynamics::{
19 ImpulseJointSet, IntegrationParameters, MultibodyJointSet, RigidBodyHandle, RigidBodySet,
20};
21use rapier::geometry::{Collider, ColliderSet};
22use rapier::prelude::JointAxesMask;
23use std::collections::HashMap;
24
25trait IntoNa {
26 type Output;
27 fn into_na(self) -> Self::Output;
28}
29
30impl IntoNa for glam::Mat4 {
31 type Output = Matrix4<f32>;
32 fn into_na(self) -> Self::Output {
33 self.to_cols_array_2d().into()
34 }
35}
36
37impl IntoNa for PxVec3 {
38 type Output = Vector3<f32>;
39 fn into_na(self) -> Self::Output {
40 Vector3::new(self.x(), self.y(), self.z())
41 }
42}
43
44impl IntoNa for PxQuat {
45 type Output = Quaternion<f32>;
46 fn into_na(self) -> Self::Output {
47 Quaternion::new(self.w(), self.x(), self.y(), self.z())
48 }
49}
50
51impl IntoNa for PxTransform {
52 type Output = Isometry3<f32>;
53 fn into_na(self) -> Self::Output {
54 let tra = self.translation().into_na();
55 let quat = self.rotation().into_na();
56 let unit_quat = Unit::new_unchecked(quat);
57 Isometry3::from_parts(tra.into(), unit_quat)
58 }
59}
60
61trait IntoPhysx {
62 type Output;
63 fn into_physx(self) -> Self::Output;
64}
65
66impl IntoPhysx for Vector3<f32> {
67 type Output = PxVec3;
68 fn into_physx(self) -> Self::Output {
69 PxVec3::new(self.x, self.y, self.z)
70 }
71}
72
73impl IntoPhysx for Point3<f32> {
74 type Output = PxVec3;
75 fn into_physx(self) -> Self::Output {
76 PxVec3::new(self.x, self.y, self.z)
77 }
78}
79
80impl IntoPhysx for UnitQuaternion<f32> {
81 type Output = PxQuat;
82 fn into_physx(self) -> Self::Output {
83 PxQuat::new(self.i, self.j, self.k, self.w)
84 }
85}
86
87impl IntoPhysx for Isometry3<f32> {
88 type Output = PxTransform;
89 fn into_physx(self) -> Self::Output {
90 PxTransform::from_translation_rotation(
91 &self.translation.vector.into_physx(),
92 &self.rotation.into_physx(),
93 )
94 }
95}
96
97trait IntoGlam {
98 type Output;
99 fn into_glam(self) -> Self::Output;
100}
101
102impl IntoGlam for Vector3<f32> {
103 type Output = glam::Vec3;
104 fn into_glam(self) -> Self::Output {
105 glam::vec3(self.x, self.y, self.z)
106 }
107}
108
109impl IntoGlam for Point3<f32> {
110 type Output = glam::Vec3;
111 fn into_glam(self) -> Self::Output {
112 glam::vec3(self.x, self.y, self.z)
113 }
114}
115
116impl IntoGlam for Matrix4<f32> {
117 type Output = glam::Mat4;
118 fn into_glam(self) -> Self::Output {
119 glam::Mat4::from_cols_array_2d(&self.into())
120 }
121}
122
123impl IntoGlam for Isometry3<f32> {
124 type Output = glam::Mat4;
125 fn into_glam(self) -> Self::Output {
126 glam::Mat4::from_cols_array_2d(&self.to_homogeneous().into())
127 }
128}
129
130thread_local! {
131pub static FOUNDATION: std::cell::RefCell<PxPhysicsFoundation> = std::cell::RefCell::new(PhysicsFoundation::default());
132}
133
134pub struct PhysxWorld {
135 materials: Vec<Owner<PxMaterial>>,
137 shapes: Vec<Owner<PxShape>>,
138 scene: Option<Owner<PxScene>>,
139}
140
141impl Drop for PhysxWorld {
142 fn drop(&mut self) {
143 let scene = self.scene.take();
144 std::mem::forget(scene);
146 }
147}
148
149impl PhysxWorld {
150 #[profiling::function]
151 pub fn from_rapier(
152 gravity: Vector3<f32>,
153 integration_parameters: &IntegrationParameters,
154 bodies: &RigidBodySet,
155 colliders: &ColliderSet,
156 impulse_joints: &ImpulseJointSet,
157 multibody_joints: &MultibodyJointSet,
158 use_two_friction_directions: bool,
159 num_threads: usize,
160 ) -> Self {
161 FOUNDATION.with(|physics| {
162 let mut physics = physics.borrow_mut();
163 let mut shapes = Vec::new();
164 let mut materials = Vec::new();
165
166 let friction_type = if use_two_friction_directions {
167 FrictionType::TwoDirectional
168 } else {
169 FrictionType::Patch
170 };
171
172 let mut scene_desc = SceneDescriptor {
173 gravity: gravity.into_physx(),
174 thread_count: num_threads as u32,
175 broad_phase_type: BroadPhaseType::Abp,
176 solver_type: SolverType::Tgs,
177 friction_type,
178 ccd_max_passes: integration_parameters.max_ccd_substeps as u32,
179 ..SceneDescriptor::new(())
180 };
181
182 let ccd_enabled = bodies.iter().any(|(_, rb)| rb.is_ccd_enabled());
183
184 if ccd_enabled {
185 scene_desc.simulation_filter_shader =
186 FilterShaderDescriptor::CallDefaultFirst(ccd_filter_shader);
187 scene_desc.flags.insert(SceneFlags::EnableCcd);
188 }
189
190 let mut scene: Owner<PxScene> = physics.create(scene_desc).unwrap();
191 let mut rapier2dynamic = HashMap::new();
192 let mut rapier2static = HashMap::new();
193 let mut rapier2link = HashMap::new();
194
195 for (rapier_handle, rb) in bodies.iter() {
201 if multibody_joints.rigid_body_link(rapier_handle).is_some() {
202 continue;
203 };
204
205 let pos = rb.position().into_physx();
206 if rb.is_dynamic() {
207 let mut actor = physics.create_dynamic(&pos, rapier_handle).unwrap();
208 let linvel = rb.linvel().into_physx();
209 let angvel = rb.angvel().into_physx();
210 actor.set_linear_velocity(&linvel, true);
211 actor.set_angular_velocity(&angvel, true);
212 actor.set_solver_iteration_counts(
213 integration_parameters.num_solver_iterations as u32,
215 1,
216 );
217
218 rapier2dynamic.insert(rapier_handle, actor);
219 } else {
220 let actor = physics.create_static(pos, ()).unwrap();
221 rapier2static.insert(rapier_handle, actor);
222 }
223 }
224
225 for (_, collider) in colliders.iter() {
304 if let Some((mut px_shape, px_material, collider_pos)) =
305 physx_collider_from_rapier_collider(&mut *physics, &collider)
306 {
307 if let Some(parent_handle) = collider.parent() {
308 let parent_body = &bodies[parent_handle];
309
310 if let Some(link) = rapier2link.get_mut(&parent_handle) {
311 unsafe {
312 physx_sys::PxRigidActor_attachShape_mut(
313 *link as *mut PxRigidActor,
314 px_shape.as_mut_ptr(),
315 );
316 }
317 } else if !parent_body.is_dynamic() {
318 let actor = rapier2static.get_mut(&parent_handle).unwrap();
319 actor.attach_shape(&mut px_shape);
320 } else {
321 let actor = rapier2dynamic.get_mut(&parent_handle).unwrap();
322 actor.attach_shape(&mut px_shape);
323 }
324
325 unsafe {
326 let pose = collider_pos.into_physx();
327 physx_sys::PxShape_setLocalPose_mut(
328 px_shape.as_mut_ptr(),
329 &pose.into(),
330 );
331 }
332
333 shapes.push(px_shape);
334 materials.push(px_material);
335 }
336 }
337 }
338
339 for (rapier_handle, _rb) in bodies.iter() {
341 let rb = &bodies[rapier_handle];
342 let densities: Vec<_> = rb
343 .colliders()
344 .iter()
345 .map(|h| colliders[*h].density())
346 .collect();
347
348 unsafe {
349 let actor = if let Some(actor) = rapier2dynamic.get_mut(&rapier_handle) {
350 std::mem::transmute(actor.as_mut())
351 } else if let Some(actor) = rapier2link.get_mut(&rapier_handle) {
352 *actor as *mut _
353 } else {
354 continue;
355 };
356
357 physx_sys::PxRigidBodyExt_updateMassAndInertia(
358 actor,
359 densities.as_ptr(),
360 densities.len() as u32,
361 std::ptr::null(),
362 false,
363 );
364
365 if rb.is_ccd_enabled() {
366 physx_sys::PxRigidBody_setRigidBodyFlag_mut(
367 actor,
368 RigidBodyFlag::EnableCcd,
369 true,
370 );
371 }
372 }
373 }
374
375 Self::setup_joints(
381 &mut physics,
382 impulse_joints,
383 &mut rapier2static,
384 &mut rapier2dynamic,
385 &mut rapier2link,
386 );
387
388 for (_, actor) in rapier2static {
389 scene.add_static_actor(actor);
390 }
391
392 for (_, actor) in rapier2dynamic {
393 scene.add_dynamic_actor(actor);
394 }
395
396 Self {
397 scene: Some(scene),
398 shapes,
399 materials,
400 }
401 })
402 }
403
404 fn setup_joints(
405 physics: &mut PxPhysicsFoundation,
406 impulse_joints: &ImpulseJointSet,
407 rapier2static: &mut HashMap<RigidBodyHandle, Owner<PxRigidStatic>>,
408 rapier2dynamic: &mut HashMap<RigidBodyHandle, Owner<PxRigidDynamic>>,
409 rapier2link: &mut HashMap<RigidBodyHandle, *mut PxArticulationLink>,
410 ) {
411 unsafe {
412 for joint in impulse_joints.iter() {
413 let actor1 = rapier2static
414 .get_mut(&joint.1.body1)
415 .map(|act| &mut **act as *mut PxRigidStatic as *mut PxRigidActor)
416 .or(rapier2dynamic
417 .get_mut(&joint.1.body1)
418 .map(|act| &mut **act as *mut PxRigidDynamic as *mut PxRigidActor))
419 .or(rapier2link
420 .get_mut(&joint.1.body1)
421 .map(|lnk| *lnk as *mut PxRigidActor))
422 .unwrap();
423 let actor2 = rapier2static
424 .get_mut(&joint.1.body2)
425 .map(|act| &mut **act as *mut PxRigidStatic as *mut PxRigidActor)
426 .or(rapier2dynamic
427 .get_mut(&joint.1.body2)
428 .map(|act| &mut **act as *mut PxRigidDynamic as *mut PxRigidActor))
429 .or(rapier2link
430 .get_mut(&joint.1.body2)
431 .map(|lnk| *lnk as *mut PxRigidActor))
432 .unwrap();
433
434 let px_frame1 = joint.1.data.local_frame1.into_physx();
435 let px_frame2 = joint.1.data.local_frame2.into_physx();
436
437 let px_joint = physx_sys::phys_PxD6JointCreate(
438 physics.as_mut_ptr(),
439 actor1,
440 px_frame1.as_ptr(),
441 actor2,
442 px_frame2.as_ptr(),
443 );
444
445 let motion_x = if joint.1.data.limit_axes.contains(JointAxesMask::LIN_X) {
446 physx_sys::PxD6Motion::Limited
447 } else if !joint.1.data.locked_axes.contains(JointAxesMask::LIN_X) {
448 physx_sys::PxD6Motion::Free
449 } else {
450 physx_sys::PxD6Motion::Locked
451 };
452 let motion_y = if joint.1.data.limit_axes.contains(JointAxesMask::LIN_Y) {
453 physx_sys::PxD6Motion::Limited
454 } else if !joint.1.data.locked_axes.contains(JointAxesMask::LIN_Y) {
455 physx_sys::PxD6Motion::Free
456 } else {
457 physx_sys::PxD6Motion::Locked
458 };
459 let motion_z = if joint.1.data.limit_axes.contains(JointAxesMask::LIN_Z) {
460 physx_sys::PxD6Motion::Limited
461 } else if !joint.1.data.locked_axes.contains(JointAxesMask::LIN_Z) {
462 physx_sys::PxD6Motion::Free
463 } else {
464 physx_sys::PxD6Motion::Locked
465 };
466 let motion_ax = if joint.1.data.limit_axes.contains(JointAxesMask::ANG_X) {
467 physx_sys::PxD6Motion::Limited
468 } else if !joint.1.data.locked_axes.contains(JointAxesMask::ANG_X) {
469 physx_sys::PxD6Motion::Free
470 } else {
471 physx_sys::PxD6Motion::Locked
472 };
473 let motion_ay = if joint.1.data.limit_axes.contains(JointAxesMask::ANG_Y) {
474 physx_sys::PxD6Motion::Limited
475 } else if !joint.1.data.locked_axes.contains(JointAxesMask::ANG_Y) {
476 physx_sys::PxD6Motion::Free
477 } else {
478 physx_sys::PxD6Motion::Locked
479 };
480 let motion_az = if joint.1.data.limit_axes.contains(JointAxesMask::ANG_Z) {
481 physx_sys::PxD6Motion::Limited
482 } else if !joint.1.data.locked_axes.contains(JointAxesMask::ANG_Z) {
483 physx_sys::PxD6Motion::Free
484 } else {
485 physx_sys::PxD6Motion::Locked
486 };
487
488 physx_sys::PxD6Joint_setMotion_mut(px_joint, physx_sys::PxD6Axis::X, motion_x);
489 physx_sys::PxD6Joint_setMotion_mut(px_joint, physx_sys::PxD6Axis::Y, motion_y);
490 physx_sys::PxD6Joint_setMotion_mut(px_joint, physx_sys::PxD6Axis::Z, motion_z);
491 physx_sys::PxD6Joint_setMotion_mut(px_joint, physx_sys::PxD6Axis::Twist, motion_ax);
492 physx_sys::PxD6Joint_setMotion_mut(
493 px_joint,
494 physx_sys::PxD6Axis::Swing1,
495 motion_ay,
496 );
497 physx_sys::PxD6Joint_setMotion_mut(
498 px_joint,
499 physx_sys::PxD6Axis::Swing2,
500 motion_az,
501 );
502 }
503 }
504 }
505
506 pub fn step(&mut self, counters: &mut Counters, params: &IntegrationParameters) {
507 let mut scratch = unsafe { ScratchBuffer::new(4) };
508
509 counters.step_started();
510 self.scene
511 .as_mut()
512 .unwrap()
513 .step(
514 params.dt,
515 None::<&mut physx_sys::PxBaseTask>,
516 Some(&mut scratch),
517 true,
518 )
519 .expect("error occurred during PhysX simulation");
520 counters.step_completed();
521 }
522
523 pub fn sync(&mut self, bodies: &mut RigidBodySet, colliders: &mut ColliderSet) {
524 let mut sync_pos = |handle: &RigidBodyHandle, pos: Isometry3<f32>| {
525 let rb = &mut bodies[*handle];
526 rb.set_position(pos, false);
527
528 for coll_handle in rb.colliders() {
529 let collider = &mut colliders[*coll_handle];
530 collider.set_position(
531 pos * collider.position_wrt_parent().copied().unwrap_or(na::one()),
532 );
533 }
534 };
535
536 for actor in self.scene.as_mut().unwrap().get_dynamic_actors() {
537 let handle = actor.get_user_data();
538 let pos = actor.get_global_pose().into_na();
539 sync_pos(handle, pos);
540 }
541
542 }
555}
556
557fn physx_collider_from_rapier_collider(
558 physics: &mut PxPhysicsFoundation,
559 collider: &Collider,
560) -> Option<(Owner<PxShape>, Owner<PxMaterial>, Isometry3<f32>)> {
561 let mut local_pose = collider.position_wrt_parent().copied().unwrap_or(na::one());
562 let cooking_params = PxCookingParams::new(physics).unwrap();
563 let shape = collider.shape();
564 let shape_flags = if collider.is_sensor() {
565 ShapeFlags::TriggerShape
566 } else {
567 ShapeFlags::SimulationShape
568 };
569 let mut material = physics
570 .create_material(
571 collider.material().friction,
572 collider.material().friction,
573 collider.material().restitution,
574 (),
575 )
576 .unwrap();
577 let materials = &mut [material.as_mut()][..];
578
579 let shape = if let Some(cuboid) = shape.as_cuboid() {
580 let geometry = PxBoxGeometry::new(
581 cuboid.half_extents.x,
582 cuboid.half_extents.y,
583 cuboid.half_extents.z,
584 );
585 physics.create_shape(&geometry, materials, true, shape_flags, ())
586 } else if let Some(ball) = shape.as_ball() {
587 let geometry = PxSphereGeometry::new(ball.radius);
588 physics.create_shape(&geometry, materials, true, shape_flags, ())
589 } else if let Some(capsule) = shape.as_capsule() {
590 let center = capsule.center();
591 let mut dir = capsule.segment.b - capsule.segment.a;
592
593 if dir.x < 0.0 {
594 dir = -dir;
595 }
596
597 let rot = UnitQuaternion::rotation_between(&Vector3::x(), &dir);
598 local_pose = local_pose
599 * Translation3::from(center.coords)
600 * rot.unwrap_or(UnitQuaternion::identity());
601 let geometry = PxCapsuleGeometry::new(capsule.radius, capsule.half_height());
602 physics.create_shape(&geometry, materials, true, shape_flags, ())
603 } else if let Some(heightfield) = shape.as_heightfield() {
604 let heights = heightfield.heights();
605 let scale = heightfield.scale();
606 local_pose = local_pose * Translation3::new(-scale.x / 2.0, 0.0, -scale.z / 2.0);
607 const Y_FACTOR: f32 = 1_000f32;
608 let mut heightfield_desc;
609 unsafe {
610 let samples: Vec<_> = heights
611 .iter()
612 .map(|h| PxHeightFieldSample {
613 height: (*h * Y_FACTOR) as i16,
614 materialIndex0: PxBitAndByte {
615 structgen_pad0: [0; 1],
616 },
617 materialIndex1: PxBitAndByte {
618 structgen_pad0: [0; 1],
619 },
620 })
621 .collect();
622 heightfield_desc = physx_sys::PxHeightFieldDesc_new();
623 heightfield_desc.nbRows = heights.nrows() as u32;
624 heightfield_desc.nbColumns = heights.ncols() as u32;
625 heightfield_desc.samples.stride = std::mem::size_of::<PxHeightFieldSample>() as u32;
626 heightfield_desc.samples.data = samples.as_ptr() as *const std::ffi::c_void;
627 }
628
629 let heightfield_desc = PxHeightFieldDesc {
630 obj: heightfield_desc,
631 };
632 let heightfield = physx::cooking::create_height_field(physics, &heightfield_desc);
633
634 if let Some(mut heightfield) = heightfield {
635 let flags = PxMeshGeometryFlags::DoubleSided;
636 let geometry = PxHeightFieldGeometry::new(
637 &mut *heightfield,
638 flags,
639 scale.y / Y_FACTOR,
640 scale.x / (heights.nrows() as f32 - 1.0),
641 scale.z / (heights.ncols() as f32 - 1.0),
642 );
643 physics.create_shape(&geometry, materials, true, shape_flags, ())
644 } else {
645 eprintln!("PhysX heightfield construction failed.");
646 return None;
647 }
648 } else if let Some(convex) = shape
649 .as_convex_polyhedron()
650 .or(shape.as_round_convex_polyhedron().map(|c| &c.inner_shape))
651 {
652 let vertices = convex.points();
653 let mut convex_desc;
654 unsafe {
655 convex_desc = physx_sys::PxConvexMeshDesc_new();
656 convex_desc.points.count = vertices.len() as u32;
657 convex_desc.points.stride = (3 * std::mem::size_of::<f32>()) as u32;
658 convex_desc.points.data = vertices.as_ptr() as *const std::ffi::c_void;
659 convex_desc.flags = PxConvexFlags::ComputeConvex;
660 }
661
662 let convex_desc = PxConvexMeshDesc { obj: convex_desc };
663 let convex = physx::cooking::create_convex_mesh(physics, &cooking_params, &convex_desc);
664
665 if let ConvexMeshCookingResult::Success(mut convex) = convex {
666 let flags = PxConvexMeshGeometryFlags::empty();
667 let scaling = unsafe { PxMeshScale_new() };
668 let geometry = PxConvexMeshGeometry::new(&mut convex, &scaling, flags);
669 physics.create_shape(&geometry, materials, true, shape_flags, ())
670 } else {
671 eprintln!("PhysX convex mesh construction failed.");
672 return None;
673 }
674 } else if let Some(trimesh) = shape.as_trimesh() {
675 let vertices = trimesh.vertices();
676 let indices = trimesh.flat_indices();
677
678 let mut mesh_desc;
679 unsafe {
680 mesh_desc = physx_sys::PxTriangleMeshDesc_new();
681
682 mesh_desc.points.count = trimesh.vertices().len() as u32;
683 mesh_desc.points.stride = (3 * std::mem::size_of::<f32>()) as u32;
684 mesh_desc.points.data = vertices.as_ptr() as *const std::ffi::c_void;
685
686 mesh_desc.triangles.count = (indices.len() as u32) / 3;
687 mesh_desc.triangles.stride = (3 * std::mem::size_of::<u32>()) as u32;
688 mesh_desc.triangles.data = indices.as_ptr() as *const std::ffi::c_void;
689 }
690
691 let mesh_desc = PxTriangleMeshDesc { obj: mesh_desc };
692 let trimesh = physx::cooking::create_triangle_mesh(physics, &cooking_params, &mesh_desc);
693
694 if let TriangleMeshCookingResult::Success(mut trimesh) = trimesh {
695 let flags = PxMeshGeometryFlags::DoubleSided;
696 let scaling = unsafe { PxMeshScale_new() };
697 let geometry = PxTriangleMeshGeometry::new(&mut trimesh, &scaling, flags);
698 physics.create_shape(&geometry, materials, true, shape_flags, ())
699 } else {
700 eprintln!("PhysX triangle mesh construction failed.");
701 return None;
702 }
703 } else {
704 eprintln!("Creating a shape unknown to the PhysX backend.");
705 return None;
706 };
707
708 shape.map(|s| (s, material, local_pose))
709}
710
711type PxPhysicsFoundation = PhysicsFoundation<DefaultAllocator, PxShape>;
712type PxMaterial = physx::material::PxMaterial<()>;
713type PxShape = physx::shape::PxShape<(), PxMaterial>;
714type PxArticulationLink = physx::articulation_link::PxArticulationLink<RigidBodyHandle, PxShape>;
715type PxRigidStatic = physx::rigid_static::PxRigidStatic<(), PxShape>;
716type PxRigidDynamic = physx::rigid_dynamic::PxRigidDynamic<RigidBodyHandle, PxShape>;
717type PxArticulationReducedCoordinate =
718 physx::articulation_reduced_coordinate::PxArticulationReducedCoordinate<(), PxArticulationLink>;
719type PxScene = physx::scene::PxScene<
720 (),
721 PxArticulationLink,
722 PxRigidStatic,
723 PxRigidDynamic,
724 PxArticulationReducedCoordinate,
725 OnCollision,
726 OnTrigger,
727 OnConstraintBreak,
728 OnWakeSleep,
729 OnAdvance,
730>;
731
732struct OnCollision;
735impl CollisionCallback for OnCollision {
736 fn on_collision(
737 &mut self,
738 _header: &physx_sys::PxContactPairHeader,
739 _pairs: &[physx_sys::PxContactPair],
740 ) {
741 }
742}
743struct OnTrigger;
744impl TriggerCallback for OnTrigger {
745 fn on_trigger(&mut self, _pairs: &[physx_sys::PxTriggerPair]) {}
746}
747
748struct OnConstraintBreak;
749impl ConstraintBreakCallback for OnConstraintBreak {
750 fn on_constraint_break(&mut self, _constraints: &[physx_sys::PxConstraintInfo]) {}
751}
752struct OnWakeSleep;
753impl WakeSleepCallback<PxArticulationLink, PxRigidStatic, PxRigidDynamic> for OnWakeSleep {
754 fn on_wake_sleep(
755 &mut self,
756 _actors: &[&physx::actor::ActorMap<PxArticulationLink, PxRigidStatic, PxRigidDynamic>],
757 _is_waking: bool,
758 ) {
759 }
760}
761
762struct OnAdvance;
763impl AdvanceCallback<PxArticulationLink, PxRigidDynamic> for OnAdvance {
764 fn on_advance(
765 &self,
766 _actors: &[&physx::rigid_body::RigidBodyMap<PxArticulationLink, PxRigidDynamic>],
767 _transforms: &[PxTransform],
768 ) {
769 }
770}
771
772unsafe extern "C" fn ccd_filter_shader(data: *mut FilterShaderCallbackInfo) -> PxFilterFlags {
773 unsafe {
774 (*(*data).pairFlags) |= physx_sys::PxPairFlags::DetectCcdContact;
775 }
776 PxFilterFlags::empty()
777}