@@ -169,41 +169,6 @@ struct ColliderAttachmentMassPlan2D {
169169 should_remove_fallback_mass : bool ,
170170}
171171
172- /// Applies Lambda collision-material combination rules to solver contacts.
173- ///
174- /// This hook implements backend-agnostic combination semantics without
175- /// exposing vendor types to `lambda-rs`:
176- /// - `combined_friction = sqrt(friction_a * friction_b)`
177- /// - `combined_restitution = max(restitution_a, restitution_b)`
178- #[ derive( Debug , Clone , Copy ) ]
179- struct ColliderMaterialCombineHooks2D ;
180-
181- impl PhysicsHooks for ColliderMaterialCombineHooks2D {
182- fn modify_solver_contacts ( & self , context : & mut ContactModificationContext ) {
183- let Some ( collider_1) = context. colliders . get ( context. collider1 ) else {
184- return ;
185- } ;
186- let Some ( collider_2) = context. colliders . get ( context. collider2 ) else {
187- return ;
188- } ;
189-
190- let friction_1 = collider_1. friction ( ) ;
191- let friction_2 = collider_2. friction ( ) ;
192- let restitution_1 = collider_1. restitution ( ) ;
193- let restitution_2 = collider_2. restitution ( ) ;
194-
195- let combined_friction = ( friction_1 * friction_2) . sqrt ( ) ;
196- let combined_restitution = restitution_1. max ( restitution_2) ;
197-
198- for solver_contact in context. solver_contacts . iter_mut ( ) {
199- solver_contact. friction = combined_friction;
200- solver_contact. restitution = combined_restitution;
201- }
202-
203- return ;
204- }
205- }
206-
207172/// A 2D physics backend powered by `rapier2d`.
208173///
209174/// This type is an internal implementation detail used by `lambda-rs`.
@@ -219,7 +184,6 @@ pub struct PhysicsBackend2D {
219184 multibody_joints : MultibodyJointSet ,
220185 ccd_solver : CCDSolver ,
221186 pipeline : PhysicsPipeline ,
222- material_combine_hooks_2d : ColliderMaterialCombineHooks2D ,
223187 rigid_body_slots_2d : Vec < RigidBodySlot2D > ,
224188 collider_slots_2d : Vec < ColliderSlot2D > ,
225189}
@@ -253,7 +217,6 @@ impl PhysicsBackend2D {
253217 multibody_joints : MultibodyJointSet :: new ( ) ,
254218 ccd_solver : CCDSolver :: new ( ) ,
255219 pipeline : PhysicsPipeline :: new ( ) ,
256- material_combine_hooks_2d : ColliderMaterialCombineHooks2D ,
257220 rigid_body_slots_2d : Vec :: new ( ) ,
258221 collider_slots_2d : Vec :: new ( ) ,
259222 } ;
@@ -864,7 +827,7 @@ impl PhysicsBackend2D {
864827 & mut self . impulse_joints ,
865828 & mut self . multibody_joints ,
866829 & mut self . ccd_solver ,
867- & self . material_combine_hooks_2d ,
830+ & ( ) ,
868831 & ( ) ,
869832 ) ;
870833
@@ -1035,6 +998,11 @@ impl PhysicsBackend2D {
1035998 /// inserts the built collider into Rapier, recomputes parent mass
1036999 /// properties, and allocates the public collider slot.
10371000 ///
1001+ /// Lambda material semantics are encoded using Rapier's built-in combine
1002+ /// rules instead of a custom contact hook:
1003+ /// - friction stores `sqrt(requested_friction)` and uses `Multiply`
1004+ /// - restitution stores the requested value and uses `Max`
1005+ ///
10381006 /// # Arguments
10391007 /// - `parent_slot_index`: The parent rigid body slot index.
10401008 /// - `parent_slot_generation`: The parent slot generation counter.
@@ -1073,9 +1041,10 @@ impl PhysicsBackend2D {
10731041 . translation ( Vector :: new ( local_offset[ 0 ] , local_offset[ 1 ] ) )
10741042 . rotation ( local_rotation)
10751043 . density ( rapier_density)
1076- . friction ( friction)
1044+ . friction ( encode_rapier_friction_coefficient ( friction) )
1045+ . friction_combine_rule ( CoefficientCombineRule :: Multiply )
10771046 . restitution ( restitution)
1078- . active_hooks ( ActiveHooks :: MODIFY_SOLVER_CONTACTS )
1047+ . restitution_combine_rule ( CoefficientCombineRule :: Max )
10791048 . build ( ) ;
10801049
10811050 let rapier_handle = self . colliders . insert_with_parent (
@@ -1433,6 +1402,22 @@ fn resolve_additional_mass_kg(
14331402 return Ok ( fallback_mass_kg) ;
14341403}
14351404
1405+ /// Encodes a public friction coefficient for Rapier's `Multiply` rule.
1406+ ///
1407+ /// Lambda specifies `sqrt(friction_a * friction_b)` as the effective contact
1408+ /// friction. Rapier cannot express that rule directly, so the backend stores
1409+ /// `sqrt(requested_friction)` on each collider and relies on
1410+ /// `CoefficientCombineRule::Multiply` to recover the public result.
1411+ ///
1412+ /// # Arguments
1413+ /// - `requested_friction`: The public friction coefficient.
1414+ ///
1415+ /// # Returns
1416+ /// Returns the Rapier friction coefficient to store on the collider.
1417+ fn encode_rapier_friction_coefficient ( requested_friction : f32 ) -> f32 {
1418+ return requested_friction. sqrt ( ) ;
1419+ }
1420+
14361421/// Resolves how attaching a collider affects a body's backend mass state.
14371422///
14381423/// This helper encodes the public density semantics without directly mutating
@@ -1735,6 +1720,19 @@ mod tests {
17351720 return ;
17361721 }
17371722
1723+ /// Encodes friction so Rapier `Multiply` matches the public rule.
1724+ #[ test]
1725+ fn rapier_friction_encoding_preserves_public_combination_semantics ( ) {
1726+ let encoded_friction_1 = encode_rapier_friction_coefficient ( 4.0 ) ;
1727+ let encoded_friction_2 = encode_rapier_friction_coefficient ( 9.0 ) ;
1728+
1729+ assert_eq ! ( encoded_friction_1, 2.0 ) ;
1730+ assert_eq ! ( encoded_friction_2, 3.0 ) ;
1731+ assert_eq ! ( encoded_friction_1 * encoded_friction_2, 6.0 ) ;
1732+
1733+ return ;
1734+ }
1735+
17381736 /// Verifies that applying an impulse updates velocity immediately.
17391737 #[ test]
17401738 fn impulse_updates_velocity_immediately ( ) {
0 commit comments