Architectural question

Topics: Developer Forum, User Forum
Jun 4, 2013 at 6:56 PM
Edited Jun 4, 2013 at 6:58 PM
So I have a higher-level sort of question and a general usage question related to fixture / body issues.

I am working on a sort of proof-of-concept.
I want to work with different sized "ships" (from 10s of meters to about 1000) - which should be fine in terms of size according to the faq.

The trick is that the ships are user designed to an extent, changing the physical properties.

So a really dumb visual of this could be:
Image

In this case, the person has made a single body consisting of a large number of simple polygons (circles and squares only)

In this case I have 2000 shapes, using 2000 fixtures, to attach to one body.

This body moves in a simple fashion (I apply force so it moves towards the mouse)

The problem is that there are no other bodies, no joints, no gravity, ect. Yet it lags immensely once it starts moving.

For stats - it is about 88 meters wide and ~24 meters tall. I'm using a scale of 64.

When looking at performance of it this is what I find:

Image

I notice that we are checking a LOT for contacts when there could be none - it is one body, how could it collide with itself?

Am I missing something really dumb? I tried using collision categories (both at the body and fixture level - set to various values) but it didn't seem to have a visible effect on lag once it starts moving.

So I was thinking - is there a way I can pre-compute and set the mass, center of gravity, inertia, ect then set those for the body. Then use one of the body decomp utilities to generate a set of convex hulls for collision, but have those be purely for collision detection (mass = 0), normal of collision, restitution, ect in relation to the body? Is that the best approach?

I have other questions, but I think the answer to this changes what I ask next.

Thanks to anyone in advance :)

EDIT:
I have an insane CPU Dual Xeon 2.3 GHz (24 virtual cores, 6 physical) - that why it shows low CPU. If farseer was multi-threaded then I would get a lot more out of it, but in this case I get bad FPS because I'm bound by clock speed.
Jun 13, 2013 at 1:39 AM
Nobody? :(
Jun 13, 2013 at 2:53 AM
I'm here, but I can't help. Sorry. :/
Jun 14, 2013 at 2:06 PM
Fixtures in a body can still collide. Your head and your knee are different parts of your body but if you fall you can still knee yourself in the face (unfortunately) :)

I'm no expert but it just looks like you're creating too many collisions per step for high performance. Even if they aren't set to collide in their categories the engine has to work out what could be colliding first and then check if the categories are right.

Can you post your code? It's easier to just copy/paste what you have and check rather than creating another testbed screen. But I'd go with the decomposer to create your body from whatever image you're using and tweak it from there. It's a great tool.
Jun 14, 2013 at 3:46 PM
I thought body was short for RigidBody?

Here's the working parts of the code:
public class DerpShip
    {
        private readonly World world;
        private readonly Texture2D rectangle;
        private readonly Texture2D circle;
        private readonly List<ShipComponent> components = new List<ShipComponent>();
        private readonly Body body;
        private Matrix renderMatrix;

        public IList<ShipComponent> Components
        {
            get { return components.AsReadOnly(); }
        }

        public Body Body
        {
            get { return body; }
        }

        public DerpShip(World world, Texture2D rectangle, Texture2D circle, Vector2 position = new Vector2())
        {
            this.world = world;
            this.rectangle = rectangle;
            this.circle = circle;
            body = BodyFactory.CreateBody(world, ConvertUnits.ToSimUnits(position));
            body.BodyType = BodyType.Dynamic;
            AddParts();
        }

        public void UpdateMatrix(Matrix camera)
        {
            var worldPlace = Matrix.CreateTranslation(new Vector3(ConvertUnits.ToDisplayUnits(body.Position), 0));
            renderMatrix = Matrix.CreateRotationZ(body.Rotation) * worldPlace * camera;
        }

        public void Draw(SpriteBatch batch)
        {
            batch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, null, null, null, null, renderMatrix);
           
            foreach (var shipComponent in components)
            {
                shipComponent.Draw(batch);
            } 
            batch.End();
        }

        private void AddParts()
        {
            int grid = 4;

            for (int i = -grid; i < grid; i++)
            {
                for (int j = -grid; j < grid; j++)
                {
                    var offset = new Vector2(i*512, j*140);
                    components.Add(new ShipComponent(rectangle, new Vector2(0, 0) + offset, 0, body, false));
                }
            }
            for (int i = -grid; i < grid; i++)
            {
                for (int j = -grid; j < grid; j++)
                {
                    var offset = new Vector2(i*512, j*140);
                    components.Add(new ShipComponent(circle, new Vector2(256, 32) + offset, 0, body, true));
                    components.Add(new ShipComponent(circle, new Vector2(-256, 32) + offset, 0, body, true));
                    components.Add(new ShipComponent(circle, new Vector2(256, -32) + offset, 0, body, true));
                    components.Add(new ShipComponent(circle, new Vector2(-256, -32) + offset, 0, body, true));

                }
            }
        }
    }
 public class ShipComponent
    {
        private readonly Texture2D texture;
        private readonly Vector2 vector2;
        private readonly float rotation;
        private readonly Body body;
        private readonly bool isCircle;

        public ShipComponent(Texture2D texture, Vector2 vector2, float rotation, Body body, bool isCircle)
        {
            this.texture = texture;
            this.vector2 = vector2;
            this.rotation = rotation;
            this.body = body;
            this.isCircle = isCircle;

            AddComponent();
        }

        private void AddComponent()
        {
            if (isCircle)
            {
                var circleShape = new CircleShape(ConvertUnits.ToSimUnits(texture.Width)/2, 1)
                {
                    Position = ConvertUnits.ToSimUnits(vector2)
                };
                body.CreateFixture(circleShape);
            }
            else
            {
                var rectangleVertices = PolygonTools.CreateRectangle(ConvertUnits.ToSimUnits(texture.Width)/2, ConvertUnits.ToSimUnits(texture.Height)/2);
                rectangleVertices.Rotate(rotation);
                rectangleVertices.Translate(ConvertUnits.ToSimUnits(vector2));
                body.CreateFixture(new PolygonShape(rectangleVertices, 1));
            }
        }

        public void Draw(SpriteBatch batch)
        {
            batch.Draw(texture,vector2, null, Color.White, rotation, new Vector2(texture.Width / 2.0f, texture.Height / 2.0f), 1f, SpriteEffects.None, 0f);
        }
    }
Jun 18, 2013 at 1:00 AM
Ok, thanks for the code.

I ran some tests and I couldn't find any lag from the actual engine, everything seems good there. I think the problem is actually rendering the changes to the screen.

If you drop the FPS from 60 to 20 the problem seems to go away. No idea why though!

So the solution seems to be drop the frame updates or limit the forces being applied to your body.

Here's what I looked at

In your Game1 files initialise method add this

TargetElapsedTime = TimeSpan.FromTicks(533333);


And here's my TestBed class (you might have to modify the TestBed download a bit to make this work, i think I just made GameInstance available statically)
using FarseerPhysics.Collision;
using FarseerPhysics.Collision.Shapes;
using FarseerPhysics.Common;
using FarseerPhysics.TestBed.Framework;
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.Decomposition;
using FarseerPhysics.Common.PolygonManipulation;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Factories;
using FarseerPhysics.Dynamics.Contacts;

namespace FarseerPhysics.TestBed.Tests
{
    public class DerpShipTest : Test
    {
        DerpShip ship;
        SpriteBatch sb;

        int frameRate = 0;
        int frameCounter = 0;
        public int frameHigh = 0;
        public int frameLow = 999;
        TimeSpan elapsedTime = TimeSpan.Zero;

        private DerpShipTest()
        {

            World = new World(Vector2.Zero);

            ship = new DerpShip(World, Test.GameInstance.Content.Load<Texture2D>("DerpShip"), Test.GameInstance.Content.Load<Texture2D>("DerpCircle"), new Vector2(0, 0));

            sb = new SpriteBatch(Test.GameInstance.GraphicsDevice);

            FarseerPhysics.Settings.EnableDiagnostics = false;

        }

        internal static Test Create()
        {
            return new DerpShipTest();
        }

        public override void Update(GameSettings settings, GameTime gameTime)
        {
            base.Update(settings, gameTime);

//            DebugView.RenderDebugData(ref GameInstance.Projection, ref GameInstance.View);

            ship.Draw(sb);


            //FPS
            elapsedTime += gameTime.ElapsedGameTime;

            if (elapsedTime > TimeSpan.FromSeconds(1))
            {
                elapsedTime -= TimeSpan.FromSeconds(1);
                frameRate = frameCounter;

                if (frameRate > frameHigh)
                    frameHigh = frameRate;

                if (frameRate < frameLow)
                    frameLow = frameRate;

                frameCounter = 0;
            }
            frameCounter++;

            DebugView.DrawString(100, 50, frameRate.ToString());
            DebugView.DrawString(100, 100, ship.Body.AngularVelocity.ToString());
        }

        public override void Keyboard(KeyboardManager keyboardManager)
        {

            if (keyboardManager.IsKeyDown(Keys.D1))
            {
                ship.Body.ApplyAngularImpulse(-100f);
            }
            if (keyboardManager.IsKeyDown(Keys.D2))
            {
                ship.Body.ApplyAngularImpulse(100f);
            }
            if (keyboardManager.IsKeyDown(Keys.D3))
            {
                ship.Body.ApplyLinearImpulse(new Vector2(1, 1));
            }
            if (keyboardManager.IsKeyDown(Keys.D4))
            {
                ship.Body.ApplyLinearImpulse(new Vector2(-1, -1));
            }

        }
    }
}

namespace FarseerPhysics.TestBed.Tests
{
    public class DerpShip
    {
        private readonly World world;
        private readonly Texture2D rectangle;
        private readonly Texture2D circle;
        private readonly List<ShipComponent> components = new List<ShipComponent>();
        private readonly Body body;
        private Matrix renderMatrix;

        public IList<ShipComponent> Components
        {
            get { return components.AsReadOnly(); }
        }

        public Body Body
        {
            get { return body; }
        }

        public DerpShip(World world, Texture2D rectangle, Texture2D circle, Vector2 position = new Vector2())
        {
            this.world = world;
            this.rectangle = rectangle;
            this.circle = circle;
            body = BodyFactory.CreateBody(world, ConvertUnits.ToSimUnits(position));
            body.BodyType = BodyType.Dynamic;
            AddParts();
        }

        public void UpdateMatrix(Matrix camera)
        {
            var worldPlace = Matrix.CreateTranslation(new Vector3(ConvertUnits.ToDisplayUnits(body.Position), 0));
            renderMatrix = Matrix.CreateRotationZ(body.Rotation) * worldPlace * camera;
        }

        public void Draw(SpriteBatch batch)
        {
            //batch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, null, null, null, null, renderMatrix);

            //foreach (var shipComponent in components)
            //{
            //    shipComponent.Draw(batch);
            //}
            //batch.End();
        }

        private void AddParts()
        {
            int grid = 4;

            int space = 300;

            for (int i = -grid; i < grid; i++)
            {
                for (int j = -grid; j < grid; j++)
                {
//                    var offset = new Vector2(i * 512, j * 140);
                    var offset = new Vector2((i * 512) + (i * space), (j * 140) + (j * space));
                    components.Add(new ShipComponent(rectangle, new Vector2(0, 0) + offset, 0, body, false));
                }
            }
            for (int i = -grid; i < grid; i++)
            {
                for (int j = -grid; j < grid; j++)
                {
                    var offset = new Vector2((i * 512) + (i * space), (j * 140) + (j * space));
                    components.Add(new ShipComponent(circle, new Vector2(400, 70) + offset, 0, body, true));
                 //   components.Add(new ShipComponent(circle, new Vector2(-400, 70) + offset, 0, body, true));
                    components.Add(new ShipComponent(circle, new Vector2(400, -70) + offset, 0, body, true));
                 //   components.Add(new ShipComponent(circle, new Vector2(-400, -70) + offset, 0, body, true));

                }
            }
        }
    }
    public class ShipComponent
    {
        private readonly Texture2D texture;
        private readonly Vector2 vector2;
        private readonly float rotation;
        private readonly Body body;
        private readonly bool isCircle;

        public ShipComponent(Texture2D texture, Vector2 vector2, float rotation, Body body, bool isCircle)
        {
            this.texture = texture;
            this.vector2 = vector2;
            this.rotation = rotation;
            this.body = body;
            this.isCircle = isCircle;

            AddComponent();
        }

        private void AddComponent()
        {
            if (isCircle)
            {
                var circleShape = new CircleShape(ConvertUnits.ToSimUnits(texture.Width) / 2, 1)
                {
                    Position = ConvertUnits.ToSimUnits(vector2)
                };
                body.CreateFixture(circleShape);

            }
            else
            {
                var rectangleVertices = PolygonTools.CreateRectangle(ConvertUnits.ToSimUnits(texture.Width) / 2, ConvertUnits.ToSimUnits(texture.Height) / 2);
                rectangleVertices.Rotate(rotation);
                rectangleVertices.Translate(ConvertUnits.ToSimUnits(vector2));
                body.CreateFixture(new PolygonShape(rectangleVertices, 1));
            }


            //body.CollisionCategories = Category.None;
            //body.CollidesWith = Category.None;
        }

        public void Draw(SpriteBatch batch)
        {
            batch.Draw(texture, vector2, null, Color.White, rotation, new Vector2(texture.Width / 2.0f, texture.Height / 2.0f), 1f, SpriteEffects.None, 0f);
        }
    }
}
Jun 18, 2013 at 1:55 AM
Thanks so much - I will try this out when I get back (Sadly I'm on vacation with only my wimpy netbook)

I didn't have any frame issues when moving it without physics, i.e. just moving the position of the centroid of the derp ship directly. I was even able to spawn about 8 to 9 of them before I noticed artifacts of rendering >16000 or so sprites, even then my FPS was 60.

It was only when I started enabling physics updates that things lagged. However, no use on conjecture until I actually try out your test bed once I get back home Wednesday evening. (Provided I have the willpower to stay up after a few flights)
Jun 18, 2013 at 5:06 AM
I was sat by a pool when first reading your question - just couldn't do any work. Now I'm back where it's raining I can think again :)

Just noticed if you set IsFixedTimeStep in Game1 to false then it drops the framerate properly and you get smooth motion. So you're right, it's the world.step method that's taking longer than the specified frame refresh (1 / 60 of 1 second) The problem with dynamic time step is it would make your physics jittery when the FPS changed. You'd have to write in something that would calculate the needed delta and pass it to the world.step method if you wanted to make the most of your FPS. Or if 20 is enough use FixedTimeStep and a higher TargetElapsedTime.