Farseer Engine - Texture To Static Polygon = Center of mass and Position bug

Topics: Developer Forum, Project Management Forum, User Forum
Feb 1 at 8:09 PM
Hi ! I am new to Farseer Engine used with MonoGame so I decided to do some test with the API to see the results and learn.

If I understand, in Farseer the Position property of a Body object is located at it's center. Wich is unusual.

Ok, now that read all the tutorial on Farseer I wanted to test the Texture 2 Polygon algorithm. So I have my Heart shaped JPG transformed in a real Heart shape in my game ! It worked just well ! Collision were all fine. I am using Farseer Debug Draw to render physics object.

I also tested Joints on my Heart polygons and I discovered something Strange and so annoying. First I tried to connect 2 DYNAMIC Heart Polygon with a simple Distance Joint. It worked well because I used both Hearts.WorldCenter as Anchors

The problem is that when I try to change the type of one of my Heart to Static, the centering and positionning is all messed up ! The actual position of a static heart is top corner and its WorldCenter property too ! What happen ? And how to get back to normal centers ?

Here is a picture rendering all Body.WorldCenter Vectors with a blue square. Static objects are green and Dynamic are red.
Image
Here is also the code for my entire game:
using FarseerPhysics.DebugView;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Joints;
using FarseerPhysics.Factories;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace FarseerTest
{
    /// <summary>
    /// This is the main type for your game.
    /// </summary>
    public class Game1 : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        private Body ball;
        private Body ball2;
        private Body poly;
        private Body ground;
        private Body heart;
        private Body heart2;

        World w = new World(new Vector2(0, 9f));

        KeyboardState kbs = new KeyboardState();

        DebugViewXNA debug;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            graphics.PreferredBackBufferHeight = 700;
            graphics.PreferredBackBufferWidth = 1500;
            graphics.ApplyChanges();
            ConvertUnits.SetDisplayUnitToSimUnitRatio(32);

        }


        protected override void Initialize()
        {
            ball = BodyFactory.CreateCircle(w, 1, 1, new Vector2(3, 1));
            ball.BodyType = BodyType.Dynamic;
            ball.Restitution = 0.1f;
            ball.Friction = 1f;

            ball2 = BodyFactory.CreateCircle(w, 3, 1, new Vector2(8, 3));
            ball2.BodyType = BodyType.Dynamic;
            ball2.Restitution = 0.1f;

            poly = BodyFactory.CreatePolygon(w, new Vertices(new List<Vector2>(new[] { new Vector2(-4, 0), new Vector2(0, 4), new Vector2(4, 0) })), 100);
            poly.Position = new Vector2(19, 5);
            poly.BodyType = BodyType.Dynamic;
            poly.Friction = 0.3f;

            ground = BodyFactory.CreateRectangle(w, 500, 2, 9, new Vector2(0, 20));
            ground.BodyType = BodyType.Static;

            BodyFactory.CreateRectangle(w, 2, 500, 9, new Vector2(1, 0)).BodyType = BodyType.Static;
            BodyFactory.CreateRectangle(w, 2, 500, 9, new Vector2(ConvertUnits.ToSimUnits(graphics.PreferredBackBufferWidth) - 1, 0)).BodyType = BodyType.Static;
            BodyFactory.CreateRectangle(w, 500, 2, 9, new Vector2(0, -1)).BodyType = BodyType.Static;


            for (int i = 0; i < 35; i++)
            {
                Body b = BodyFactory.CreateCapsule(w, 0.5f, 0.4f, 0.02f);
                b.Position = new Vector2(i, 0);
                b.BodyType = BodyType.Dynamic;
                b.GravityScale = 0.1f;
                b.Restitution = 0.6f;
            }


            //Debug view
            debug = new DebugViewXNA(w);
            debug.RemoveFlags(DebugViewFlags.Controllers);
            //debug.AppendFlags(DebugViewFlags.AABB);
            debug.DefaultShapeColor = Color.Red;
            debug.ActiveAlpha = 1;

            base.Initialize();
        }

        private Body circle;
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            debug.LoadContent(GraphicsDevice, Content);

            Texture2D heartTex = Content.Load<Texture2D>("heart");

            heart = CreatePolygonFromTexture(heartTex, w, 0.1f, new Vector2(27, 5), 0.5f);
            heart.BodyType = BodyType.Static;


            heart2 = CreatePolygonFromTexture(heartTex, w, 0.1f, new Vector2(10, 5), 0.5f);
            heart2.BodyType = BodyType.Dynamic;

            DistanceJoint j = JointFactory.CreateDistanceJoint(w, heart, heart2, heart.WorldCenter, heart2.WorldCenter, true);
            j.Frequency = 0.8f;
            j.DampingRatio = 0.05f;
            j.CollideConnected = true;
            //j.Breakpoint = 67f;

            Body box = BodyFactory.CreateRectangle(w, 3, 3, 3, new Vector2(20, 5));
            box.BodyType = BodyType.Static;

            Body box2 = BodyFactory.CreateRectangle(w, 3, 3, 3, new Vector2(15, 5));
            box2.BodyType = BodyType.Dynamic;

            circle = BodyFactory.CreateCircle(w, 1, 1, Vector2.Zero);
            circle.IsSensor = true;

            DistanceJoint j2 = JointFactory.CreateDistanceJoint(w, box, box2, box.WorldCenter, box2.WorldCenter, true);
            j2.Frequency = 0.5f;
            j.DampingRatio = 0.1f;

            for (int i = 0; i < 10; i++)
            {
                Body b = CreatePolygonFromTexture(heartTex, w, 0.1f, new Vector2(5, 2), 0.3f);
                b.Position = new Vector2(i * 5, 2);
                b.BodyType = BodyType.Dynamic;
                b.GravityScale = 0.1f;
                b.Restitution = 0.6f;
            }

        }


        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }


        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
                Exit();
            kbs = Keyboard.GetState();

            if(kbs.IsKeyDown(Keys.Right))
                ball.ApplyForce(new Vector2(14, 0));
            else if (kbs.IsKeyDown(Keys.Left))
                ball.ApplyForce(new Vector2(-14, 0));

            if (kbs.IsKeyDown(Keys.Up))
            {
                ball.ApplyLinearImpulse(new Vector2(0, -6));
                //heart2.ApplyLinearImpulse(new Vector2(0, -6));
            }
            else if (kbs.IsKeyDown(Keys.Down))
                ball.ApplyLinearImpulse(new Vector2(0, 6));

            circle.Position = poly.WorldCenter;
            w.Step(1f/30f);

            base.Update(gameTime);
        }


        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            spriteBatch.Begin();

            //spriteBatch.Draw(CreateRectangle(100, 100, Color.Red), body.Position, null, Color.White, body.Rotation, Vector2.Zero, 1f, SpriteEffects.None, 0f);

            var projection = Matrix.CreateOrthographicOffCenter(0f, ConvertUnits.ToSimUnits(graphics.GraphicsDevice.Viewport.Width), ConvertUnits.ToSimUnits(graphics.GraphicsDevice.Viewport.Height), 0f, 0f, 1f);
            debug.RenderDebugData(ref projection);
            foreach (Body body in w.BodyList)
            {
                spriteBatch.Draw(CreateRectangle(10, 10, Color.Blue), ConvertUnits.ToDisplayUnits(body.WorldCenter), Color.White);
            }

            spriteBatch.End();

            base.Draw(gameTime);
        }

        public Body CreatePolygonFromTexture(Texture2D tex, World world, float density, Vector2 position, float scale, TriangulationAlgorithm algorithm = TriangulationAlgorithm.Bayazit)
        {
            uint[] texData = new uint[tex.Width * tex.Height];
            tex.GetData<uint>(texData);

            Vertices vertices = TextureConverter.DetectVertices(texData, tex.Width);
            List<Vertices> vertexList = Triangulate.ConvexPartition(vertices, algorithm);

            Vector2 vertScale = new Vector2(ConvertUnits.ToSimUnits(scale));
            foreach (Vertices vert in vertexList)
                vert.Scale(ref vertScale);

            Vector2 centroid = -vertices.GetCentroid(); 
            vertices.Translate(ref centroid); 
            //basketOrigin = -centroid;

            return BodyFactory.CreateCompoundPolygon(w, vertexList, density, position);
        }
        public Texture2D CreateRectangle(int width, int height, Color c)
        {
            Texture2D rectangleTexture = new Texture2D(this.GraphicsDevice, width, height);
            Color[] color = new Color[width * height];

            for (int i = 0; i < color.Length; i++)
            {
                color[i] = c;
            }
            rectangleTexture.SetData(color);
            return rectangleTexture;
        }
    }
}
Also, you can see that my 2 Bodies box and box2 show that the center of mass of a static body Rectangle is properly calculated as shown on the picture. Thanks !