What's happening
First of all, note that this behavior is not specific to oblique collisions. Below is an example of two balls colliding head on with a wall. All fixtures have m_restitution = 1.0
, and m_friction = 0.0
. The ball on the left is traveling with v = 0,1
and the ball on the right has v = 0,1.01
.
I found three similar questions asked on the box2d forums a while back, which suggest lowering a value in b2Settings.h, b2_velocityThreshold
.
It didn't take me long to find the code block where b2_velocityThreshold
is referenced. It turned out to be line 211 of the file b2ContactSolver.cpp, and inside the function b2ContactSolver::initializeVelocityConstraints
:1
It is here that the velocity bias factor is set based on whether or not the relative velocity exceeds b2_velocityThreshold
. That velocity bias factor is then referenced in the iterated function b2ContactSolver::SolveVelocityConstraints
. When the bias velocity is not set (defaults to zero), the initial accumulated impulse is becomes just enough to counteract the initial momentum of the ball, and it sticks to the wall.
I have tried to understand why the b2_velocityThreshold
exits in the first place (many web searches) and haven't come up with a definitive answer. The best I can do is quote Erin Catto (author of box2d) who posted in one of the above threads:
If you set the [b2_velocityThreshold
] to zero, non-zero restitution will cause your bodies to bounce forever.
So I'm extrapolating that this behaviour is implemented for the sake of games with bouncing balls, where balls with restitution 0<e<1
would otherwise never stop bouncing (even though the bounce heights would eventually become very small). This would waste tons of cpu time creating/deleting the contact between the two bodies, and in addition the bouncing body would never sleep.
Recommended Solutions
Solution 1
The recommendation from the above forum threads is to reduce the parameter in b2Settings.h:
#define b2_velocityThreshold 1.0f
A velocity threshold for elastic collisions. Any collision with a relative linear velocity below this threshold will be treated as inelastic.
Unfortunately, this parameter is a #define
constant and is therefore not editable unless box2d is re-compiled. I'm assuming that you don't wan to re-compile libgdx to do this.
Solution 2
Zee was on the right track with his answer, but it needs a little more detail. A good approach to doing this is using collision callbacks as you suggest. You sound like you know what you're doing with this one, so I won't go into detail here.
Solution 3
This one's a bit of a trick, and I'm only recommending this because you say you're working on a breakout clone, which only has a couple of moving parts.
You should leave almost everything as is, but increase your ball speed by a factor of s
where s>1
. If s
is high enough, then very oblique collisions will still have a significant relative velocity and will therefore trigger the velocity bias-setting else branch in the referenced code, and your ball will almost always bounce. The problem is that now your game is impossible because the ball is so fast and the player cannot keep up. To compensate for this, just multiply your time-step by a factor of 1/s
before calling b2World::Step
. That way box2d will technically be operating in slow motion, but since your ball has been sped up by the same amount, everything will look normal.
Here are 2 more gifs. The left one shows a ball with v=1,1
and time step size ts = dt
. The second is with v=2,2
(magnitude doubled), and time step size ts = 0.5*dt
. Voila:
I hope this helps!
1 This is the only place I could find where that threshold parameter is referenced. (i.e. it has the same effect for all contact types, regardless of the shapes involved in the collision)