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.
The problem is that 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.
using System.Collections.Generic;
using FarseerPhysics;
using FarseerPhysics.Common;
using FarseerPhysics.Common.Decomposition;
using FarseerPhysics.Common.TextureTools;
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 !