Simple example getting out of sync

Topics: User Forum
Apr 10, 2012 at 10:28 AM

I've been playing around with Farseer 3.3 and made a simple example where I have two rectangles in a zero gravity environment. I set the WASD keys to apply force on one box, and the arrow keys for the other. At first the collisions seem natural, but after manipulating the boxes for less than a minute and causing collisions, they begin to act strange (intersecting, spinning, hitting the other box where it wasn't drawn). The issue could be from me implementing Farseer poorly, how I'm handling keyboard input, or how I'm drawing the result. I would appreciate any help from you veterans!

I wrote this sample all in one file so it would be more easily digestible (code is below). Here is also a download of the project if someone wishes to run it (you should just need to add Farseer 3.3 as a reference):

http://dl.dropbox.com/u/196458/WindowsGame3.zip

 

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using FarseerPhysics.Common;
using FarseerPhysics.Common.Decomposition;
using FarseerPhysics.Common.PolygonManipulation;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Factories;

namespace WindowsGame3
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        Body mBody;
        Body mBody2;
        Fixture mFix;
        Fixture mFix2;
        World mWorld;
        Texture2D PlainTexture;

        float height;
        float width;
        float density;
        int posx;
        int posy;

        float height2;
        float width2;
        float density2;
        int posx2;
        int posy2;

        float gravity;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            height = 100;
            width = 100;
            density = 1;
            posx = 5;
            posy = 5;

            height2 = 50;
            width2 = 100;
            density2 = .1f;
            posx2 = 200;
            posy2 = 5;

            gravity = .00f;

            // TODO: Add your initialization logic here
            mWorld = new World(new Vector2(0, gravity));

            mBody = BodyFactory.CreateBody(mWorld, new Vector2(posx, posy));
            mFix = FixtureFactory.AttachRectangle(width, height, density, Vector2.Zero, mBody);
            mBody.BodyType = BodyType.Dynamic;

            mBody2 = BodyFactory.CreateBody(mWorld, new Vector2(posx2, posy2));
            mFix2 = FixtureFactory.AttachRectangle(width2, height2, density2, Vector2.Zero, mBody2);
            mBody2.BodyType = BodyType.Dynamic;

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            // TODO: use this.Content to load your game content here
            PlainTexture = new Texture2D(GraphicsDevice, 1, 1);
            PlainTexture.SetData(new[] { Color.White });
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {

            Vector2 force1 = Vector2.Zero;
            Vector2 force2 = Vector2.Zero;
            float forcePower = 500000;

            if (Keyboard.GetState(PlayerIndex.One).IsKeyDown(Keys.W))
                force1 += new Vector2(0, -forcePower);
            if (Keyboard.GetState(PlayerIndex.One).IsKeyDown(Keys.A))
                force1 += new Vector2(-forcePower, 0);
            if (Keyboard.GetState(PlayerIndex.One).IsKeyDown(Keys.S))
                force1 += new Vector2(0, forcePower);
            if (Keyboard.GetState(PlayerIndex.One).IsKeyDown(Keys.D))
                force1 += new Vector2(forcePower, 0);

            if (Keyboard.GetState(PlayerIndex.One).IsKeyDown(Keys.Up))
                force2 += new Vector2(0, -forcePower);
            if (Keyboard.GetState(PlayerIndex.One).IsKeyDown(Keys.Left))
                force2 += new Vector2(-forcePower, 0);
            if (Keyboard.GetState(PlayerIndex.One).IsKeyDown(Keys.Down))
                force2 += new Vector2(0, forcePower);
            if (Keyboard.GetState(PlayerIndex.One).IsKeyDown(Keys.Right))
                force2 += new Vector2(forcePower, 0);


            mBody.ApplyForce(force1);
            mBody2.ApplyForce(force2);

            mWorld.Step((float)gameTime.ElapsedGameTime.TotalMilliseconds * .001f);

            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.Black);

            spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend);

            // TODO: Add your drawing code here
            spriteBatch.Draw(PlainTexture, mBody.Position, new Rectangle((int)mBody.Position.X, (int)mBody.Position.Y, (int)width, (int)height), Color.Red, mBody.Rotation, Vector2.Zero, 1, SpriteEffects.None, .6f);
            spriteBatch.Draw(PlainTexture, mBody2.Position, new Rectangle((int)mBody2.Position.X, (int)mBody2.Position.Y, (int)width2, (int)height2), Color.White, mBody2.Rotation, new Vector2(0, -25), 1, SpriteEffects.None, .6f);

            spriteBatch.End();

            base.Draw(gameTime);
        }
    }
}

Apr 11, 2012 at 6:44 PM
Edited Apr 11, 2012 at 8:04 PM

If I'm not mistaken XNA's Rectangle uses top left vertex in the constructor together with width and height. Farseer's AttachRectangle uses the body's center (which would be the equivalent of XNA's Rectangle.Center). So your drawing code doesn't seem correct.

Also don't use pixels in physics. Farseer uses meters and works best in the 0.1-10 scale. Basically your're making a 100mx100m block of solid rock collide with a 100mx50m block. That's like two cruise ships colliding at arbitrary speed. That can't be good...

Apr 13, 2012 at 10:41 PM

Thank you, that helps a lot. I didn't know about the scale thing, which explains why I had to apply such a tremendous force to move them around.