Collisions in 3.5

Topics: Developer Forum
Sep 12, 2013 at 6:24 PM
Edited Sep 12, 2013 at 9:53 PM
I'm curious how collisions (and possibly restitution) have changed in Farseer Physics 3.5. I tried switching to 3.5 in my XNA project (upgrading from an unmodified 3.3.1), and the collision physics feel subtly but significantly different to me.

Here's my basic scenario: I have a ball that bounces around a room. There is no gravity in the room. The ball is created with BodyFactory.CreateCircle, its restitution is 0, and its density is 0. The room also has a bunch of walls and rectangular blocks. They're created with BodyFactory.CreateRectangle, their restitution is 1f, and their densities are 0.

With Farseer 3.3.1, if I give the ball a little push (set its velocity to, say, Vector2(5,5)), it will bounce around the room pretty much perpetually. Every time it hits a wall, it ricochets off. If it happens to hit a wall at a really low angle (pretty much parallel to the wall), it just "sticks" and slides along the wall instead of bouncing off. This is good, and the way I would expect things to work.

With Farseer 3.5, however, the ball sticks to the walls much more frequently, even when it collides at an angle that should clearly result in a "bounce". I'm not sure what causes this exactly, because the ball often bounces around appropriately for 5-10 collisions and works exactly as I would expect it to, but then suddenly it will just stop bouncing and stick to the wall.

My collision code and restitution settings are identical in both projects (3.3.1 vs 3.5), and they're really quite basic. Nothing crazy or fancy going on.

Has the collision code changed in some significant way in Farseer 3.5?

Thanks,

theremin
Sep 12, 2013 at 10:06 PM
Okay, I've investigated this a little more, and I'm fairly confident that this is a bug in Farseer 3.5. I created a Testbed project which reliably reproduces the bug 100% of the time (at least on my computer). For the first 30 seconds of the Testbed simulation, the ball merrily bounces around between the walls, and then it suddenly hits a wall, stops bouncing, and just slides along the wall.

You can see the bug in action in this video:

https://vimeo.com/74416243

When I run the same Testbed simulation with Farseer 3.3.1, the ball never stops bouncing. Perhaps even more interesting: The way the ball bounces (i.e., the angles of reflection) is different between Farseer 3.3.1 and 3.5. So it definitely seems like there's something amiss here. Either this is a bug, or the way the collisions work in 3.5 is fairly different.

Is this a known issue? A bug? Or is this new behavior by design?

Here's a Testbed project to reproduce the behavior:
using System.Text;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Factories;
using FarseerPhysics.Samples.Demos.Prefabs;
using FarseerPhysics.Samples.DrawingSystem;
using FarseerPhysics.Samples.ScreenSystem;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace FarseerPhysics.Samples.Demos
{
    internal class SimpleDemo1 : PhysicsGameScreen, IDemoScreen
    {
        private Body _ball;
        private Body[] _walls;

        private Sprite _ballSprite;
        private Sprite[] _wallSprites;

        public string GetTitle() { return "Farseer 3.3 vs 3.5 collisions"; }
        public string GetDetails() { return GetTitle(); }

        public override void LoadContent()
        {
            base.LoadContent();

            World.Gravity = Vector2.Zero;

            //
            // Ball
            //

            _ball = BodyFactory.CreateCircle(World, 1f, 0f);
            _ball.BodyType = BodyType.Dynamic;
            _ball.Restitution = 0f;
            _ball.Friction = 0f;
            _ball.FixedRotation = true;
            _ball.LinearVelocity = new Vector2(14, 12);

            _ballSprite = new Sprite(ScreenManager.Assets.TextureFromShape(_ball.FixtureList[0].Shape, MaterialType.Squares, Color.Orange, 1f));

            //
            // Walls
            //

            var screenRect = new Rectangle(-(ScreenManager.Game.Window.ClientBounds.Width / 2), -(ScreenManager.Game.Window.ClientBounds.Height / 2), ScreenManager.Game.Window.ClientBounds.Width, ScreenManager.Game.Window.ClientBounds.Height);

            _walls = new Body[6];
            _walls[0] = BodyFactory.CreateRectangle(World, ConvertUnits.ToSimUnits(20), ConvertUnits.ToSimUnits(screenRect.Height), 0f, ConvertUnits.ToSimUnits(new Vector2(screenRect.Left + 10, screenRect.Center.Y)));
            _walls[1] = BodyFactory.CreateRectangle(World, ConvertUnits.ToSimUnits(screenRect.Width), ConvertUnits.ToSimUnits(20), 0f, ConvertUnits.ToSimUnits(new Vector2(screenRect.Center.X, screenRect.Top + 10)));
            _walls[2] = BodyFactory.CreateRectangle(World, ConvertUnits.ToSimUnits(20), ConvertUnits.ToSimUnits(screenRect.Height), 0f, ConvertUnits.ToSimUnits(new Vector2(screenRect.Right - 10, screenRect.Center.Y)));
            _walls[3] = BodyFactory.CreateRectangle(World, ConvertUnits.ToSimUnits(screenRect.Width), ConvertUnits.ToSimUnits(20), 0f, ConvertUnits.ToSimUnits(new Vector2(screenRect.Center.X, screenRect.Bottom - 10)));
            _walls[4] = BodyFactory.CreateRectangle(World, ConvertUnits.ToSimUnits(120), ConvertUnits.ToSimUnits(screenRect.Height / 2), 0f, ConvertUnits.ToSimUnits(new Vector2(screenRect.Center.X + screenRect.Width / 4, screenRect.Center.Y)));
            _walls[5] = BodyFactory.CreateRectangle(World, ConvertUnits.ToSimUnits(120), ConvertUnits.ToSimUnits(screenRect.Height / 2), 0f, ConvertUnits.ToSimUnits(new Vector2(screenRect.Center.X - screenRect.Width / 4, screenRect.Center.Y)));

            _wallSprites = new Sprite[6];

            for (int i = 0; i < +_walls.Length; i++)
            {
                _walls[i].BodyType = BodyType.Static;
                _walls[i].Restitution = 1f;
                _walls[i].Friction = 0f;

                _wallSprites[i] = new Sprite(ScreenManager.Assets.TextureFromShape(_walls[i].FixtureList[0].Shape, MaterialType.Squares, Color.Orange, 1f));
            }
        }

        public override void Draw(GameTime gameTime)
        {
            ScreenManager.SpriteBatch.Begin(0, null, null, null, null, null, Camera.View);

            ScreenManager.SpriteBatch.Draw(_ballSprite.Texture, ConvertUnits.ToDisplayUnits(_ball.Position), null, Color.White, _ball.Rotation, _ballSprite.Origin, 1f, SpriteEffects.None, 0f);

            for (int i = 0; i < _wallSprites.Length; i++)
                ScreenManager.SpriteBatch.Draw(_wallSprites[i].Texture, ConvertUnits.ToDisplayUnits(_walls[i].Position), null, Color.White, _walls[0].Rotation, _wallSprites[i].Origin, 1f, SpriteEffects.None, 0f);
            
            ScreenManager.SpriteBatch.End();

            base.Draw(gameTime);
        }
    }
}
Sep 17, 2013 at 5:13 AM
I have also issues with 3.5 FPE, behavior is totally different. I don't know if it's about collisions or joints yet...
Oct 7, 2013 at 7:35 PM
Bump. Nothing on this? Seems like a pretty serious bug/change.
Oct 8, 2013 at 3:27 AM
Edited Oct 8, 2013 at 8:22 AM
I am getting something similar though not with collisions at such sharp angles. I don't understand the farseer code very well but I was tinkering with Settings.VelocityThreshold and noticed something about these lines (about 264 in ContactSolver.cs):
// Setup a velocity bias for restitution.
vcp.velocityBias = 0.0f;
float vRel = Vector2.Dot(vc.normal, vB + MathUtils.Cross(wB, vcp.rB) - vA - MathUtils.Cross(wA, vcp.rA));
if (vRel < -Settings.VelocityThreshold)
{
    vcp.velocityBias = -vc.restitution * vRel;
}
My ball bounces happily several times and the stops suddenly even though velocity is well above the threshold. If I breakpoint I notice a difference.
  • vA is the world body velocity and is always {0,0}.
  • vB is the ball velocity and slowly decreasing each bounce (restitution 0.9).
On a normal bounce:
  • vB is something like {0,5}, heading straight down.
  • vRel comes to -5, the bias applies and we bounce.
On a broken bounce:
  • vB will be reversed like {0,-5}, heading straight up.
  • vRel comes to 5 which fails the check, and we do not bounce.
I have no idea how it gets in this state. Perhaps it is one of those odd times where the contact lasts more than one frame?
Oct 8, 2013 at 3:43 AM
Some other notes that might be relevant for debugging:
  • Using 3.5.
  • Ball is radius .75, restitution 0.9, dynamic body.
  • World being collided with is a large polygon, static body.
  • Disabling ContinuousPhysics seems to resolve this issue (but boo).
Oct 10, 2013 at 10:01 AM
Finally getting around to upgrading to 3.5 this weekend... might hold off until this issue is reviewed.
Coordinator
Oct 10, 2013 at 7:29 PM
I don't think the collision code has changed. It has mainly been bug fixes and work on joints. As always, I check the testbed against the Box2D testbed to see if there are any differences in simulation outputs, and I don't see any differences between the two. Farseer does ignore collisions when there is a small angle between two objects, this is to prevent crazy collisions and jitter, but there should not be any noticeable difference between 3.3.1 and 3.5 on that account.

I will take a look at this in the weekend and verify if there is a bug or not.
Coordinator
Oct 10, 2013 at 7:31 PM
Oh, and if turning off CCD fixes this completely, then it is expected behavior. However, as said before, this should not change between 3.3.1 and 3.5.
Oct 10, 2013 at 8:11 PM
My impression was that the velocityBias code above could probably be improved. In a contact lasting two frames, the first frame sets it correctly and later the the solver flips the velocity around for the bounce. In the second frame the bodies are now separating so vRel comes out positive, the bias is lost and the solver just ends up killing velocity. This happens even though the objects are moving well above VelocityThreshold (relative).

Assuming contacts are meant to be counted when objects are moving apart, does tweaking the bias check like this make sense?
if (vRel < -Settings.VelocityThreshold || vRel > 0)
{
    vcp.velocityBias = -vc.restitution * vRel;
}
I've actually moved back to 3.1 for now, but I'm not really sure why its different as the relevant checks seem to be the same. I assume something else is causing this to happen more often in 3.5.

Would it be useful for me to make a test case for you and if so in what sort of format?
Oct 11, 2013 at 7:45 AM
@genbox
"It has mainly been bug fixes and work on joints"

Could you be more specific on that? I have issues with the behavior of the joints in 3.5.
Oct 11, 2013 at 5:16 PM
@genbox:

Hi, original poster here. This thread has taken on a bit of a life of its own, so I think it might be good to refocus on something a bit simpler to start with: My test bed sample code above is a great starting point for looking into collision behavior differences between 3.3.1 and 3.5. Joints and CCD and VelocityThreshold settings have nothing to do with the original issue I described. (Although, granted, they may be subject to their own [possibly related] problems.)

The core problem here exists in the simplest scenario, regardless of whether CCD is enabled. If you run my sample code in 3.3.1, and then run it again in 3.5, you'll see that the bouncing ball behaves fairly differently between the two Farseer versions. The reflection angles (how the ball bounces off the walls) are pretty different, and as my video above demonstrates, in 3.5 the ball will incorrectly stop bouncing and just slide along the wall after 10-20 collisions.

I'm happy to provide more examples/information/whatever is needed. Thanks for all your hard work, and thanks for looking into this.
Coordinator
Oct 11, 2013 at 9:27 PM
The Box2D engine was never built to simulate realistic elastic conditions, and that is why your ball hits the wall and then slide along the edge. This is not a bug, it is just how the engine is designed. On a similar note, this also makes restitution unrealistic, as a restitution of 1 can actually make an object increase in speed, if it hits two objects at the same time (and creates 2 contacts).

I will take a look at your testcase tomorrow, but for now assume that this behavior is intentional.
Coordinator
Oct 11, 2013 at 9:31 PM
@beady: You might want to ask Erin Catto that question over at box2d.org/forum/
I don't like to change the behavior from the one Box2D have, as I'm trying to keep the physics simulation inline with Box2D. If he change the math, I change the math.
Coordinator
Oct 12, 2013 at 4:09 PM
This discussion has been copied to a work item. Click here to go to the work item and continue the discussion.
Oct 16, 2013 at 3:57 PM
genbox wrote:
The Box2D engine was never built to simulate realistic elastic conditions, and that is why your ball hits the wall and then slide along the edge. This is not a bug, it is just how the engine is designed. On a similar note, this also makes restitution unrealistic, as a restitution of 1 can actually make an object increase in speed, if it hits two objects at the same time (and creates 2 contacts).

I will take a look at your testcase tomorrow, but for now assume that this behavior is intentional.
Thanks for taking a look. I'm not really worried about realism. The problem is that the collision angles are different between 3.3.1 and 3.5 using identical basic settings. It seems like that should not be the case. A quick test of my sample code in 3.3.1 vs. 3.5 demonstrates the problem.
Jan 23, 2014 at 5:30 PM
Has any more investigation been done on this issue? I noticed on the issues page that this is by far the most upvoted bug ("Collisions in 3.5"), so clearly other people have noticed the problem too.

I feel like I may not have explained the issue clearly enough, and maybe the real problem got muddled in the process. As I noted above, the problem has nothing to do with physics realism. The problem is that the collisions behave differently between 3.3.1 and 3.5 using basic settings in a very simple project.

I posted a very simple TestBed project above that illustrates the problem. Simply run that test code in 3.3.1, and then run it again in 3.5. You'll see there are very clear differences between the two, and the inconsistent behaviors can be reproduced with 100% reliability.