Godot Version
4.5-dev3
Question
I created a StaticBody3D (we’ll call it ground) and a RigidBody3D (we’ll call it ball). Did all the things like applying collision shapes, sphere for the ball, material, etc. I’ve run multiple tests with various numbers of bounce applied to both ball and ground, an no matter what I choose, those two numbers are always added together to give the coefficient of restitution. When ground was 0.3, ball 0.3, CoR was 0.6. Ground 0.1, ball 0.1, CoR was 0.2. I tried ball 0.75, ground 0.2, and got CoR of 0.95. It seems as if it’s clamped at 1.0, however, as testing ball at 0.75 and ground at 0.8 (= 1.55) produced CoR of 1.0.
I went searching, and apparently this has been reported on Github as well, but the issue/proposal is still open.
“For bounciness/restitution, Godot has an absorbent boolean. If true, it subtracts the bounciness from the colliding object’s bounciness. If false, they are added together.”
(See Change physics material friction/bounce combine modes to match other engines · Issue #11715 · godotengine/godot-proposals · GitHub)
So in an attempt to find a more realistic approach, I tried installing Rapier 3D. It was my understanding that Rapier used the more conventional means of calculating bounciness/CoR. However, that doesn’t appear to be the case. Bounce numbers seem to still be added together. Just to make sure I wasn’t losing my mind and incorrectly doing the math, I had AI take a second look. I performed three tests:
Analysis of THIS Log (Rapier, GroundBounce=0.1, BallBounce=0.1, Bias=0.01):
First Bounce (Frames 89-90):
The log shows a large incoming velocity at Frame 88, and then a much smaller one at Frame 89, just before the collision signal.
Frame: 88, YPos: 0.1522, YVel: -13.2176 (V_true_in1 - true incoming velocity before any resolution)
The log for Frame 89 (YPos: 0.0340, YVel: -13.3587) is the state at the point of deep impact or just as resolution begins. The actual rebound will be seen in the next frame.
Ball collided with: StaticBody3D … (This occurs between frame 89 and 90)
Frame: 90, YPos: 0.0704, YVel: 2.5450 (V_out1)
Effective CoR1 (using V_true_in1 from frame 88 as the impact velocity): abs(2.5450 / -13.2176) = ~0.1925
Effective CoR1 (using V_in1 from frame 89 as the impact velocity): abs(2.5450 / -13.3587) = ~0.1905
Both calculations give a CoR of approximately 0.19.
Second Bounce (Frames 122-123):
Frame: 122, YPos: 0.0131, YVel: -2.6727 (V_in2)
Ball collided with: StaticBody3D …
Frame: 123, YPos: 0.0246, YVel: 0.5336 (V_out2)
Effective CoR2 = abs(0.5336 / -2.6727) = ~0.1996 (approximately 0.20)
Third Bounce (Frames 128-129):
Frame: 128, YPos: 0.0333, YVel: -0.2834 (V_in3)
Ball collided with: StaticBody3D …
Frame: 129, YPos: 0.0340, YVel: 0.0566 (V_out3)
Effective CoR3 = abs(0.0566 / -0.2834) = ~0.1997 (approximately 0.20)
Settling: The ball then settles very quickly to YPos ~0.033x, with YVel eventually becoming 0.0000. Multiple collision messages are logged during settling. This is correct.
Observations and Conclusions (with RAPIER, Ground=0.1, Ball=0.1, Bias=0.01):
Effective CoR is Consistently ~0.19 - 0.20:
With both material bounces set to 0.1, the effective CoR for the significant bounces is consistently around 0.19-0.20.
Your hypothesis that the CoR would be ~0.2 (0.1 + 0.1) if the “additive” behavior persisted is confirmed by this log data, even with Rapier!
This is Highly Unexpected for Rapier (or Jolt):
Standard physics engines like Rapier or Jolt are expected to use more conventional combine modes (Average, Multiply, Min, Max) for material properties.
For bounce values of 0.1 and 0.1:
Average would give (0.1 + 0.1) / 2 = 0.1.
Multiply would give 0.1 * 0.1 = 0.01.
Min or Max would give 0.1.
The observed ~0.20 (which looks like 0.1 + 0.1) is therefore still exhibiting the same “additive” pattern you first identified with Godot Physics, even after switching to Rapier (and with Default Contact Bias set low at 0.01).
Analysis of THIS New Log (Rapier, BallBounce=0.5, GroundBounce=0.3, Bias=0.01):
First Bounce (Frames 89-90):
Major deceleration before the “Ball collided” message is still present:
Frame: 88, YPos: 0.1522, YVel: -13.2176 (V_true_in1)
Frame: 89, YPos: 0.0340, YVel: -13.3587 (V_impact_at_signal_resolution_start)
Ball collided with: StaticBody3D …
Frame: 90, YPos: 0.2004, YVel: 10.5469 (V_out1)
Effective CoR1 (using V_from_frame_89 as impact): abs(10.5469 / -13.3587) = ~0.7895
Effective CoR1 (using V_true_in1 from frame 88): abs(10.5469 / -13.2176) = ~0.7980
Both are very close: ~0.79-0.80.
Second Bounce (Frames 216-217):
Frame: 216, YPos: 0.0340, YVel: -9.9983 (V_in2)
Ball collided with: StaticBody3D …
Frame: 217, YPos: 0.1583, YVel: 7.8610 (V_out2)
Effective CoR2 = abs(7.8610 / -9.9983) = ~0.7862
Third Bounce (Frames 312-313):
Frame: 312, YPos: 0.0340, YVel: -7.6288 (V_in3)
Ball collided with: StaticBody3D …
Frame: 313, YPos: 0.1286, YVel: 5.9706 (V_out3)
Effective CoR3 = abs(5.9706 / -7.6288) = ~0.7826
Fourth Bounce (Frames 386-387):
Frame: 386, YPos: 0.0340, YVel: -5.9306 (V_in4)
Ball collided with: StaticBody3D …
Frame: 387, YPos: 0.1073, YVel: 4.6143 (V_out4)
Effective CoR4 = abs(4.6143 / -5.9306) = ~0.7780
Subsequent Bounces: The CoR stays fairly consistently in the high 0.7s for a good number of bounces, gradually decreasing as the impact velocities get lower. For example:
Bounce around Frame 444: V_in ~ -4.6780, V_out ~ 3.6954 => CoR ~0.79
Bounce around Frame 490: V_in ~ -3.6469, V_out ~ 2.8311 => CoR ~0.776
Settling Phase: Eventually, the bounces become very small, and the ball comes to rest (YPos: 0.033x, YVel: 0.0000).
Observations and Conclusions (with RAPIER, BallBounce=0.5, GroundBounce=0.3, Bias=0.01):
Additive Hypothesis CONFIRMED (for Rapier in this setup)!
Ball Bounce = 0.5
Ground Bounce = 0.3
Sum = 0.8
The observed effective Coefficient of Restitution for all significant bounces is consistently ~0.78 - 0.80. This is remarkably close to the sum of 0.8!
Rapier Behaves Similarly to Godot Physics in this Additive Manner:
This is the most surprising and crucial finding. Even after switching to Rapier (and confirming it’s active via your screenshot of Project Settings showing “Rapier3D” selected as the Physics Engine), the bounce restitution is still behaving as if the individual material bounce values are being added together (and clamped at 1.0 if the sum exceeds it).
This means the non-standard additive bounce combination is not unique to Godot’s built-in physics engine but is also how the Godot Rapier GDExtension is currently making Rapier behave with Godot’s PhysicsMaterial properties.
I ran one final test, where both ground and ball bounce were set to 0.3.
Analysis of THIS Log (Rapier, BallBounce=0.3, GroundBounce=0.3, Bias=0.01):
First Bounce (Frames 89-90):
Major deceleration before “Ball collided”:
Frame: 88, YPos: 0.1522, YVel: -13.2176 (V_true_in1)
Frame: 89, YPos: 0.0340, YVel: -13.3587 (V_impact_at_signal_resolution_start)
Ball collided with: StaticBody3D …
Frame: 90, YPos: 0.1571, YVel: 7.8796 (V_out1)
Effective CoR1 (using V_from_frame_89 as impact): abs(7.8796 / -13.3587) = ~0.5899
Effective CoR1 (using V_true_in1 from frame 88): abs(7.8796 / -13.2176) = ~0.5962
Both are very close: ~0.59 - 0.60.
Second Bounce (Frames 185-186):
Frame: 185, YPos: 0.0340, YVel: -7.6130 (V_in2)
Ball collided with: StaticBody3D …
Frame: 186, YPos: 0.1037, YVel: 4.4379 (V_out2)
Effective CoR2 = abs(4.4379 / -7.6130) = ~0.5829
Third Bounce (Frames 241-242):
Frame: 241, YPos: -0.0189, YVel: -4.5276 (V_in3 - note YPos is already < 0.0335)
Ball collided with: StaticBody3D …
Frame: 242, YPos: 0.0317, YVel: 2.7120 (V_out3)
Effective CoR3 = abs(2.7120 / -4.5276) = ~0.5989
Fourth Bounce (Frames 275-276):
Frame: 275, YPos: 0.0176, YVel: -2.6732 (V_in4)
Ball collided with: StaticBody3D …
Frame: 276, YPos: 0.0440, YVel: 1.5605 (V_out4)
Effective CoR4 = abs(1.5605 / -2.6732) = ~0.5837
Subsequent Bounces & Settling: The pattern of CoR ~0.58-0.60 continues for a few more distinct bounces. The ball then settles to YPos 0.033x and YVel 0.0000.
If your TestBall material bounce is 0.3 and TestGround material bounce is 0.3:
If Godot/Rapier used Average (a common default): The effective e would be 0.3.
Incoming YVel (just before impact, e.g., V_resolved_in from your logs): -X m/s
Outgoing YVel: Should be approximately +0.3 * X m/s
If Godot/Rapier used Multiply: The effective e would be 0.09.
Outgoing YVel: Should be approximately +0.09 * X m/s
If Godot/Rapier used Min or Max: The effective e would be 0.3.
Outgoing YVel: Should be approximately +0.3 * X m/s
The Discrepancy:
Your logs (for both Godot Physics with low bias and Rapier with low bias) consistently show an effective CoR of approximately 0.6 when both material bounces are set to 0.3 (calculated from the V_resolved_in). This is double what Average, Min, or Max would give, and far from Multiply.
This strongly reinforces the idea that the Godot PhysicsMaterial.bounce property, when Absorbent is false, is being interpreted by the physics integration layer (for both Godot Physics in your version and the Rapier GDExtension) in an additive manner to determine the final CoR applied to the collision after initial penetration resolution.
At first I thought this was perhaps a bug I was experiencing because of the dev3 version, but this appears to be standard behavior in Godot physics, at least according to what I linked above on Github. But is this a known thing no matter what engine I use, then, whether it’s Rapier, or Jolt, or some other choice? And if so, are there any plans to fix this?