Unprecise Collision

Topics: Developer Forum, Project Management Forum, User Forum
Sep 24, 2013 at 5:01 PM
Edited Sep 24, 2013 at 5:03 PM
I've modified the simpleDemo1 code, and I've added another rectangle:
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 Border _border;
        private Body _rectangle;
        private Sprite _rectangleSprite;
        private Body _rectangle2;
        private Sprite _rectangleSprite2;

        #region IDemoScreen Members

        public string GetTitle()
        {
            return "Body with a single fixture";
        }

        public string GetDetails()
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine("This demo shows a single body with one attached fixture and shape.");
            sb.AppendLine("A fixture binds a shape to a body and adds material");
            sb.AppendLine("properties such as density, friction, and restitution.");
            sb.AppendLine(string.Empty);
            sb.AppendLine("GamePad:");
            sb.AppendLine("  - Rotate object: left and right triggers");
            sb.AppendLine("  - Move object: right thumbstick");
            sb.AppendLine("  - Move cursor: left thumbstick");
            sb.AppendLine("  - Grab object (beneath cursor): A button");
            sb.AppendLine("  - Drag grabbed object: left thumbstick");
            sb.AppendLine("  - Exit to menu: Back button");
            sb.AppendLine(string.Empty);
            sb.AppendLine("Keyboard:");
            sb.AppendLine("  - Rotate Object: left and right arrows");
            sb.AppendLine("  - Move Object: A,S,D,W");
            sb.AppendLine("  - Exit to menu: Escape");
            sb.AppendLine(string.Empty);
            sb.AppendLine("Mouse / Touchscreen");
            sb.AppendLine("  - Grab object (beneath cursor): Left click");
            sb.AppendLine("  - Drag grabbed object: move mouse / finger");
            return sb.ToString();
        }

        #endregion

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

            World.Gravity = new Vector2(0f, 9.82f);

            _border = new Border(World, ScreenManager, Camera);
            _rectangleSprite.Texture = FarseerPhysicsGame.c.Load<Texture2D>("Blu");

            _rectangleSprite.Origin = new Vector2(_rectangleSprite.Texture.Width / 2, _rectangleSprite.Texture.Height / 2);

            _rectangle = BodyFactory.CreateRectangle(World, ConvertUnits.ToSimUnits(_rectangleSprite.Texture.Width), ConvertUnits.ToSimUnits(_rectangleSprite.Texture.Height), 1f);
            _rectangle.BodyType = BodyType.Dynamic;
            _rectangle.FixedRotation = true;

            //SetUserAgent(_rectangle, 100f, 100f);

            // create sprite based on body

            _rectangleSprite2.Texture = FarseerPhysicsGame.c.Load<Texture2D>("Verde");

            _rectangleSprite2.Origin = new Vector2(_rectangleSprite2.Texture.Width / 2, _rectangleSprite2.Texture.Height / 2);

            _rectangle2 = BodyFactory.CreateRectangle(World, ConvertUnits.ToSimUnits( _rectangleSprite2.Texture.Width), ConvertUnits.ToSimUnits(_rectangleSprite2.Texture.Height), 1f);
            _rectangle2.BodyType = BodyType.Dynamic;

            //SetUserAgent(_rectangle, 100f, 100f);

            // create sprite based on body
        }

        public override void Draw(GameTime gameTime)
        {
            ScreenManager.SpriteBatch.Begin(0, null, null, null, null, null, Camera.View);
            ScreenManager.SpriteBatch.Draw(_rectangleSprite.Texture, new Rectangle((int)ConvertUnits.ToDisplayUnits(_rectangle.Position.X), (int)ConvertUnits.ToDisplayUnits(_rectangle.Position.Y), (int)_rectangleSprite.Texture.Width, (int)_rectangleSprite.Texture.Height), null, Color.White, _rectangle.Rotation, _rectangleSprite.Origin, SpriteEffects.None, 0f);
            ScreenManager.SpriteBatch.Draw(_rectangleSprite2.Texture, new Rectangle((int)ConvertUnits.ToDisplayUnits(_rectangle2.Position.X), (int)ConvertUnits.ToDisplayUnits(_rectangle2.Position.Y), (int)_rectangleSprite2.Texture.Width, (int)_rectangleSprite2.Texture.Height), null, Color.White, _rectangle2.Rotation, _rectangleSprite2.Origin, SpriteEffects.None, 0f);
            
            ScreenManager.SpriteBatch.End();
            _border.Draw();
            base.Draw(gameTime);
        }
    }
}
In the main game method I've changed the representation from 24 pixel per meter to 100 pixel per meter (to have a simple conversion).

ISSUE

My two bodies don't collide themselves and with the border in a precise way. There's always at least 1 empty pixel beetween them.

Image

Link of the enlarged image

I've tried everything and I don't know why it happens.
Thanks in advance.
Oct 11, 2013 at 3:18 PM
Have you tried rendering the scene via DebugView? My guess would be that your textures are not lining up with the physics bodies and the quickest way to check that is to render just the simulation.
Oct 11, 2013 at 4:54 PM
Are you sure it's not just bouncing slightly away after contact?

What happens when you drop the green square on top of the blue square? Is there still a space?
Oct 11, 2013 at 5:59 PM
Thanks for the replies.

No, it's not about bouncing because through DebugView I saw that almost everything is drawn one pixel up and to the left of his shape.
So there is always at least one pixel between the drawing of the bodies.

I still don't understand what is causing this.
Oct 11, 2013 at 6:49 PM
So when the green box is on top of the blue box in the debug view they're touching but when drawing the textures they're apart?

What's the width and height of your textures?

I wonder if its related to the stuttering texture issue you also have? Did you get that resolved using the point clamp suggestion?

Have you tried changing the scale the test scenarios use? If I remember right farseer uses a scale of 24 by default. Have you tried changing it to 100?

Also, what resolution have you got set on your monitor?
Oct 12, 2013 at 4:55 PM
So when the green box is on top of the blue box in the debug view they're touching but when drawing the textures they're apart? YES
What's the width and height of your textures? 100x100 each
I wonder if its related to the stuttering texture issue you also have? Did you get that resolved using the point clamp suggestion?
NO, I didn't. Unfortunately does not work in my case
Have you tried changing the scale the test scenarios use? If I remember right farseer uses a scale of 24 by default. Have you tried changing it to 100?
I changed from 24 to 100 in this project. Although I tried 24 and 64. The spacing remains, somewhere instead of 2px spacing there is only 1, but the problem remains
I've noticed a weird thing. In DebugView the draw of the real texture is right, only when I draw without DebugView there is spacing.

WITH DebugView
Image

WITHOUT DebugView
Image

Overlaying these image in a photo processor shows that the texture are drawing differently (basically some difference pixel).

For anyone who wants to see the full project:
DOWNLOAD FROM MEDIAFIRE

Thank you very much, it is nice to know that Farseer, as well as being a great physics engine, also has a good community.
Oct 13, 2013 at 2:25 PM
Yeah I see what you mean now.

If you set the scale to 200 and then place the 100x100 boxes on top of each other and look at the positions in the draw method it thinks they're 103 pixels apart! That's why it's showing the gap around the boxes.

It seems to be the old fashioned float point precision problem and unfortunately there's not much that can be done about it unless you change the engine to use doubles instead (and that would probably have a massive impact on performance as they're almost always more CPU intensive as well as a bunch of conversion problems).

I did a few tests and anything with a scale above 4 would produce a big enough difference when converting the position to display for the game to render at the wrong position. By rounding the position values in the draw method after converting them I got the scale up to 19 before the gap was shown.

You don't see this in the samples project because the demos use the asset creator and that actually creates a texture that's slightly larger than the body, giving it "padding"

So it looks like the answer is use a lower scale (1-4), round the values before using them in your draw method or create your bodies slightly smaller than the textures (e.g. -2 on width and height for this example).

Of course I could be completely wrong about it all. I usually am :D
Oct 13, 2013 at 4:32 PM
Well thank you very much for all the support. Now I am a bit confused cause I'm looking for a solution for about two weeks. I'll continue to try.
Oct 13, 2013 at 7:08 PM
If it was me I'd just use a scale of 2. That way you don't get the position errors and it should just effect that amount of force you'd need to apply to fixtures to get them to move. As you're at the beginning of the game that shouldn't be much of an issue.

Have fun and good luck
Developer
Oct 14, 2013 at 9:20 AM
In short: Farseer/Box2D is simply not made for pixel perfect collision. Even with doubles you can not eliminate this issue. At the core is an iterative solver, which will never be 100% mathematically correct. That said you should rarely need it. If you don't go for some blocky pixels retro style, just compensate with slightly bigger sprites like the samples do.

To some extent the problem is related to the spritebatch (which the debug view does not use, hence the problem is less visible there). So all the stuff HAL_9000 said about texture filtering can be an issue. Spritebatch.begin() however sets some default shaders, blending states and all other kind of stuff. So no matter what you change after Spritebatch.begin() a lot of those renderstate related settings are gone again. That is the reason why it does nothing for you.

The main problem however is that the default spritebatch shader tries to do some texel to pixel mapping adjustment, which causes sprites to be rendered with a half-pixel offset. This is done to get a 1on1 pixel mapping of sprites and your screen resolution. So your best chance would be to write a custom shader and pass it to Spritebatch.begin() or to avoid spritebatch at all.

You can get the default shader source code here: http://blogs.msdn.com/b/shawnhar/archive/2008/08/16/basiceffect-and-spritebatch-shader-source-code.aspx
So even if you don't know anything about shaders, you can copy paste and remove the half-pixel offset.

In any way you should go for resolution independence even for 2D nowadays. That is a way to big topic to cover here. But in short you want to use some orthographic projection with arbitrary (i.e. makes sense in your gameworld) units which are independent from the actual resolution. So that you get crisper graphics with higher resolutions but for lower ones all is scaled automatically to show the exact same scene from your gameworld. You can find a ton of articles about how to achieve that.

What you shouldn't do is use a scale like 2 for your physics world. Farseer is tuned to be stable and work best within a certain value range which has meters, kilograms, and seconds as base units (See 1.7 Units here http://www.box2d.org/manual.html). Of course if you want to simulate big spaceships for example you can define 1 unit to be 100km instead of just 1m. But you should try to find a scale where your smallest and biggest game object is within the specified range.
Oct 14, 2013 at 3:56 PM
Thank you very much. Day by day I'm more and more surprised by the community effort.