Creating and Drawing Polygons

Topics: Developer Forum, Project Management Forum, User Forum
Mar 5, 2011 at 7:43 AM
Edited Mar 5, 2011 at 7:49 AM

 

Hi , I'm new to Farseer and XNA but I really need them for a project of mine. Been going around the web for a week now and I am making decent progress.

 

I was aiming to create something like the Crayon Physics game. Right now I've managed to store mouse coordinates in a List<Vector2> on every click and using 3 of them to create squares and rectangles in the game using FixtureFactory.CreateRectangle and then drawing them using spriteBatch and new Rectangles.  

 

Now I want to create a random irregular shape like we could in Crayon Physics but I am failing miserably. I know about convex, concave polygons and triangulation but have no idea how to use them.

I just want to retrieve coordinates from my mouse click and create a random polygons, regardless of their type and shape.

 

Any sort of help would be much appreciated !

Any sort of code snippet for creating and drawing the polygons would be even more appreciated because I'm new and I've read a lot of discussions but have still not found a solution, only more confusion.

 

Thanks a million in advance !

Coordinator
Mar 10, 2011 at 6:54 PM

When you have the vertices from the mouse position, you simply use a decomposer like EarclipDecomposer.ConvexPartition() on the vertices. This will convex partition the polygon. Then you give the list of vertices to FixtureFactory.CreateCompoundPolygon() and that should be it.

Take a look at the Texture-To-Vertices test from the testbed. It imports a texture (could be a random drawn polygon) and convert it to an object usable in FPE using the above mentioned tools.

Developer
Mar 10, 2011 at 8:34 PM

I'll try and write up a simple demo over the weekend.

Mar 10, 2011 at 9:21 PM
Maybe its time for us to get some documentation and revised tutorials together. I'll be into doing this.

Sent from my Windows Phone
Mar 10, 2011 at 10:10 PM

A demo for this would be amazing and helpful. Can you also include how to create shapes such as concave, convex and so on as I have been trying to remove the initial layout (borders) in the SimpleSamples XNA and add something that looks more like a pinball environment (a square but at the top it's round), but for some reason neither can I get the right size of the window and when adding the convex/concave part it gives me a error (even when using EarClipDecomposer.ConvexPartition()) or if there is a way to create a image of what the layout can be and the import it as a background which I can guess would be loads of vertices or something?. Which I have not seen much about this topic.

The few changes I have seen in the documentation are going well and have been of great help, thank you guys!

Coordinator
Mar 10, 2011 at 10:13 PM

I've added arc shapes to FPE 3.3 (next version of FPE - currently in feature freeze) and I'm working on a Pinball sample that shows how to use the arc shapes. If you absolutely need it now, you are welcome to download the latest changeset from the source control and test it now.

Mar 10, 2011 at 11:46 PM

Hi Genbox,

Thank you for your quick reply, I have been looking at the new code and the order in which the files are now is sooo much better to read plus I have seen some of the changes in the code and all I can say is that I can't wait until FPE 3.3 comes out. :)

I think I understand some of the code now. One thing which is missing and which is confusing me is if I require the Draw function to be called at all times?. For example right now I needed to add the draw function (missing in the files right now) as follows: 

        public override void Draw(GameTime gameTime)
        {
            ScreenManager.LineBatch.Begin(Camera.SimProjection, Camera.SimView);
            // draw ground
            for (int i = 0; i < body.FixtureList.Count; ++i)
            {
                ScreenManager.LineBatch.DrawLineShape(body.FixtureList[i].Shape, Color.Black);
            }
            ScreenManager.LineBatch.End();

            base.Draw(gameTime);
        }

And that will draw the body of the borders. But will I need to do this all the time if I want to create a circle and other objects?... just wonder if it is part of the new update?

Also as it is new code, how do I set the screen to fullscreen to start with as I will need it in fullscreen at all times and the code I had is not working anymore:

            //fullscreen
            _graphics.PreferredBackBufferWidth = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
            _graphics.PreferredBackBufferHeight = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
            _graphics.IsFullScreen = true;

I need to get a Pinball game sorted by this weekend. I can post final version if needed as soon as I get the initial setup sorted.

Thank you for the help!

Mar 11, 2011 at 4:37 AM

Thanks a lot Genbox, mattbettcher, ElijahNomad and lordregor666

 

Will try the suggestions posted here.

Anxiously waiting for the code and new tutorials though!

 

Thanks again !!

Mar 15, 2011 at 4:57 PM

Hey people, I have made some progress, I have created a class to draw polygons using the AdvancedDemo1.cs sample in the latest change set. I have encountered a problem though.

(The Class)

public class PolygonPhysicsObject
    {
                
        Vector2 origin;
        Body compound;
        Texture2D texture;
                      
        
        public PolygonPhysicsObject(World world,Vertices vertices,Texture2D texture)
        {
            this.texture = texture;
            Vector2 centroid = -vertices.GetCentroid();
            vertices.Translate(ref origin);
            origin = -centroid;

            List<Vertices> list = BayazitDecomposer.ConvexPartition(vertices);

            Vector2 vertScale = new Vector2(ConvertUnits.ToSimUnits(1)) * 1f;
            foreach (Vertices verti in list)
            {
                verti.Scale(ref vertScale);
            }

            compound = BodyFactory.CreateCompoundPolygon(world, list, 1f, BodyType.Dynamic);
            compound.BodyType = BodyType.Dynamic;        
            
        }
        


        public void Draw(SpriteBatch spriteBatch)
        {            
            spriteBatch.Draw(texture , ConvertUnits.ToDisplayUnits(compound.Position),
                                           null, Color.Tomato, compound.Rotation, origin, 1f, SpriteEffects.None, 0f);
        }

    }

 

The problem is that I am forced to use a "Texture" in the spritebatch.Draw method and I just have vertices from the mouse clicks that I have stored. I can "feel" the object in the game space because of the collisions with other objects and stuff but it is "invisible" so to speak as it has no color.

Is there a way I can color it or draw it ? l tried using a random texture , but it just pasted the texture on one end of the polygon and it looked really weird.

 

Thanks again

Mar 15, 2011 at 5:22 PM

You'll need to use DebugViewXNA class object.  It's a good idea  to  draw things in debug mode when your testing out your simulations the basic syntax is like the following 

DebugDraw.DrawSolidCircle(center, radius, Vector2.Zero, color); There's lots of examples of this in the advanced and simple demos.

Mar 15, 2011 at 5:31 PM

Sorry, not sure I am of much help as I am having problems as well understanding the Draw function but in any case I think what you want is to draw a line and not a whole shape so I'd do something like:

        public override void Draw(GameTime gameTime) 
// draw ground if (bodyWorld.FixtureList != null)
{
for (int i = 0; i < bodyWorld.FixtureList.Count; ++i)
{
ScreenManager.LineBatch.DrawLineShape(bodyWorld.FixtureList[i].Shape, Color.Black);
}
}

I am also trying to do something like Crayon Physics as well. So far I can get drawings done with the Mouse in the HandleInput... here is my code:

        private Vector2[] pointArray = new Vector2[999];
        private int posCounter = 0;
        bool leftButtonDown = false;
        Vertices drawShape = new Vertices();

        public override void HandleInput(InputHelper input, GameTime gameTime)
        {
            if (input.KeyboardState.IsKeyDown(Keys.Space))
                _jumper.ApplyForce(new Vector2(0, 300));

            if (input.IsCursorMoved)
            {
                if (leftButtonDown == true)
                {
                    Vector2 position = Camera.ConvertScreenToWorld(input.Cursor);
                    posCounter++;
                    pointArray[posCounter] = position;
                }
            }

            if (HasCursor)
            {
                if (input.IsNewMouseButtonPress(MouseButtons.RightButton))
                {                    
                    World.RemoveBody(lineBody);
                }
                if (input.IsNewMouseButtonPress(MouseButtons.LeftButton))
                {
                    leftButtonDown = true;
                }
                if (input.IsNewMouseButtonRelease(MouseButtons.LeftButton))
                {
                    leftButtonDown = false;

                    if (lineBody.FixtureList == null)
                    {
                        lineBody = BodyFactory.CreateBody(World);

                        for (int i = 0; i < posCounter + 1; i++)
                        {
                            drawShape.Add(pointArray[i]);
                            pointArray[i] = Vector2.Zero;
                            if (i > 2)
                            {
                                FixtureFactory.AttachEdge(drawShape[i - 1], drawShape[i], lineBody);
                            }
                        }
                    }
                    else
                    {
                        for (int i = 0; i < posCounter + 1; i++)
                        {
                            drawShape.Add(pointArray[i]);
                            pointArray[i] = Vector2.Zero;
                            if (i > 2)
                            {
                                FixtureFactory.AttachEdge(drawShape[i - 1], drawShape[i], lineBody);
                            }
                        }
                    }
                    posCounter = 0;
                    drawShape = new Vertices();
                }
        
            }
            base.HandleInput(input, gameTime);
        }

So far it does the drawing after the mouse left button has been released. So you can't see what you are drawing while you are drawing it until it has finished. If anyone knows how to get it do draw while mouse button is pressed, please let me know!

 

As for recognizing shapes, does anyone know how it is done?. Like for example how does it recognize if it is a square, circle or concave shape?. I would like to do something similar to this: http://privateasteroid.com/games/ceres/

 

Mar 21, 2011 at 4:07 PM

Hey guys,
I'v managed to draw the polygons based on vertices, using a few references and the Texture to Polygon article.

The problem now is that the polygon and its texture are slightly misaligned. Pressing F1 clearly shows that. The texture is mostly to the left of the body.
Any idea as to why that may be possible? The bigger the shape the further apart the two are.
If I draw squares using vertices then the texture and the body match up perfectly.
Maybe there is a problem with the centroid or something.

Please help

Code Snippet

(In Load Content() )

vertices.Add(ConvertUnits.ToSimUnits(new Vector2(100f, 100f))); vertices.Add(ConvertUnits.ToSimUnits(new Vector2(150f, 150f))); vertices.Add(ConvertUnits.ToSimUnits(new Vector2(100f, 200f))); vertices.Add(ConvertUnits.ToSimUnits(new Vector2(50f, 100f))); Vector2 centroid = -vertices.GetCentroid(); vertices.Translate(ref centroid); origin = -centroid; List<Vertices> list = BayazitDecomposer.ConvexPartition(vertices); polygonSprite = new Sprite(ScreenManager.Assets.TextureFromVertices(vertices, MaterialType.Dots, Color.Red, 1f)); polygon = BodyFactory.CreateCompoundPolygon(World, list, 1f); polygon.BodyType = BodyType.Dynamic; (In Draw() )
ScreenManager.SpriteBatch.Draw(polygonSprite.texture, ConvertUnits.ToDisplayUnits(polygon.Position),null, Color.Tomato, polygon.Rotation, polygonSprite.origin, 1f, SpriteEffects.None, 0f);

Thanks

Coordinator
Mar 22, 2011 at 4:38 PM

When you say that symmetric shapes like boxes work correctly, then I'm also inclined to think that it is the centroid.

is origin (origin = -centroid) the same as polygonSprite.origin? Check the values you get using the debugger.

Mar 23, 2011 at 6:34 AM

Hey Genbox,

 

I went to the debugger as you said , the values of origin and polygonSprite.origin are quite different.

Origin has a very small value like (5.47,4.39) etc and polygonSprite.origin has values which are 50 plus.

 

What do you think the problem is ?

Mar 25, 2011 at 4:27 PM
Edited Mar 25, 2011 at 4:28 PM

Hey there farhanif.  I'm not sure if you're building your project from the original tech demo engine, but what I'm using is the Camera2D.ConvertScreenToWorld function...or something like that.  The Camera2D class has functions that easily converts from screen coordinates to Farseer world coordinates(and vice versa) pretty easily.  I also had problems using the ConvertUnits.ToSimsUnits functions but after I switched to using the Camera2D class, that seemed to solve it.  I hope that helps!

Mar 25, 2011 at 4:37 PM
Edited Mar 25, 2011 at 4:39 PM
farhanarif wrote:

Hey Genbox,

 

I went to the debugger as you said , the values of origin and polygonSprite.origin are quite different.

Origin has a very small value like (5.47,4.39) etc and polygonSprite.origin has values which are 50 plus.

 

What do you think the problem is ?


Oh...the origin problems look like one is in World coordinates(converted) vs the sprite origin which is still in screen coordinates.  Also, I don't think you posted code on how you define the sprite's origin vector2 value?

Mar 26, 2011 at 5:24 AM

Hey Kahuna

Thanks for the reply. Im posting the entire code here, in case you can help me out using that.

Like I said the error is only slight. About half a cm or so in most cases. Which means that it cant be due to the conversion issue between the two system otherwise the error would have been huge. Hope to hear from you soon.

I am using the built in demos of the physics engine fro all of this.

And interestingly if I make squares and rectangles the textures map up perfectly but they don't on anything irregular.

 


namespace FarseerPhysics.SamplesFramework { internal class PhysicsPolygonDemo: PhysicsGameScreen, IDemoScreen { #region Variables public Body bodyWorld, lineBody; private Body polygon; private Fixture squareBody; private Sprite squareSprite, polygonSprite; private Vertices vertices = new Vertices(); private Vector2 origin; public float _halfWidth, _halfHeight; #endregion variables #region IDemoScreen Members public string GetTitle() { return "Physics Polygon Test"; } public string GetDetails() { StringBuilder sb = new StringBuilder(); sb.AppendLine("Use the mouse to form polygons!"); sb.AppendLine(string.Empty); sb.AppendLine("GamePad:"); sb.AppendLine(" - Exit to menu: Back button"); sb.AppendLine(string.Empty); sb.AppendLine("Keyboard:"); sb.AppendLine(" - Exit to menu: Escape"); return sb.ToString(); } #endregion public override void LoadContent() { base.LoadContent(); World.Gravity = new Vector2(0.0f, 0.0f); //Set Gravity _halfHeight = ConvertUnits.ToSimUnits(ScreenManager.GraphicsDevice.Viewport.Height) / 2f - 0.75f; //Screen Height _halfWidth = ConvertUnits.ToSimUnits(ScreenManager.GraphicsDevice.Viewport.Width) / 2f - 0.75f; //Screen Width #region Creating World Bounds Vertices bounds = new Vertices(4); //Create screen bounds bounds.Add(new Vector2(-_halfWidth, _halfHeight)); bounds.Add(new Vector2(_halfWidth, _halfHeight)); bounds.Add(new Vector2(_halfWidth, -_halfHeight)); bounds.Add(new Vector2(-_halfWidth, -_halfHeight)); bodyWorld = BodyFactory.CreateLoopShape(World, bounds); //Set the bounds to the world lineBody = BodyFactory.CreateBody(World); //Create the world for the mouse drawing line #endregion #region Creating World Objects and Sprites squareBody = FixtureFactory.CreateRectangle(World,ConvertUnits.ToSimUnits(50f),ConvertUnits.ToSimUnits(50f),ConvertUnits.ToSimUnits(10f),ConvertUnits.ToSimUnits(new Vector2(200f,200f))); squareBody.Body.BodyType = BodyType.Dynamic; squareSprite = new Sprite(ScreenManager.Assets.TextureFromShape(squareBody.Shape,MaterialType.Dots,Color.Red,1f)); vertices.Add(ConvertUnits.ToSimUnits(new Vector2(250f, 250f))); vertices.Add(ConvertUnits.ToSimUnits(new Vector2(300f, 250f))); vertices.Add(ConvertUnits.ToSimUnits(new Vector2(300f, 300f))); vertices.Add(ConvertUnits.ToSimUnits(new Vector2(220f, 300f))); Vector2 centroid = -vertices.GetCentroid(); vertices.Translate(ref centroid); origin = -centroid; List<Vertices> list = BayazitDecomposer.ConvexPartition(vertices); polygonSprite = new Sprite(ScreenManager.Assets.TextureFromVertices(vertices, MaterialType.Dots, Color.Red, 1f)); polygon = BodyFactory.CreateCompoundPolygon(World, list, 1f, origin); polygon.BodyType = BodyType.Dynamic; #endregion } public override void HandleInput(InputHelper input, GameTime gameTime) { if (input.KeyboardState.IsKeyDown(Keys.Escape)) ScreenManager.Game.Exit(); base.HandleInput(input, gameTime); } public override void Update(GameTime gameTime, bool otherScreenHasFocus, bool coveredByOtherScreen) { World.Step((float)(gameTime.ElapsedGameTime.TotalMilliseconds * 0.001)); base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen); } public override void Draw(GameTime gameTime) { ScreenManager.SpriteBatch.Begin(0, null, null, null, null, null, Camera.View); ScreenManager.SpriteBatch.Draw(squareSprite.texture, ConvertUnits.ToDisplayUnits(squareBody.Body.Position), null, Color.White, squareBody.Body.Rotation, squareSprite.origin, 1f, SpriteEffects.None, 0f); ScreenManager.SpriteBatch.Draw(polygonSprite.texture, ConvertUnits.ToDisplayUnits(polygon.Position), null, Color.White, polygon.Rotation, polygonSprite.origin, 1f, SpriteEffects.None, 0f); ScreenManager.SpriteBatch.End(); #region Drawing Bounds // for background purposes only ScreenManager.LineBatch.Begin(Camera.SimProjection, Camera.SimView); // draw ground for (int i = 0; i < bodyWorld.FixtureList.Count; ++i) { ScreenManager.LineBatch.DrawLineShape(bodyWorld.FixtureList[i].Shape, Color.Black); } ScreenManager.LineBatch.End(); #endregion base.Draw(gameTime); } } }

 



Thanks again

 

Mar 27, 2011 at 5:07 AM

Hrm...thanks for posting the code.  But it's still a little difficult to tell because there's still a lot of unknowns that aren't posted.  For instance, I'm not sure how your Sprite class looks like.  Also, did you add the "Assets" member in your ScreenManager class?  I'm not quite sure what that class does as that's not defined in my ScreenManager class.  I can guess but like I said, it's hard to be sure what's causing the problem unless we see everything that your code depends on.  Otherwise, looking at it on the surface, everything looks fine.  I say try to dig a little deeper and see if there's any bugs within the other classes you're using.

Mar 28, 2011 at 3:05 PM

So I took a look at farhan's code and things look okay.  I did manage to fix his texture alignment problem.  There was a minor tweak I had to do after he calculated the origin.  For the simple polygon he was trying to build manually, I had to add X and Y offsets in order to align them properly.  I tried other solutions as well.  Initially, I thought maybe there were problems with the transformations but calculating the transform matrix had the same results.  My only guess is maybe there's a difference with how Farseer generates the textures and bodies even if they're using the same vertices?  In any case, right now the temporary solution is to hard code those origin offsets but I can see how that might not be the best solution for this problem.

Sep 14, 2011 at 5:56 AM
farhanarif wrote:
 
Thanks again

That one last line of code gives me an error message... You know what may be the problem?

Just kidding. ;-) I too am interested in creating texture from dynamically defined polygon shapes. I noticed you were using the classes from the sample code, more specifically the AssetManager with the TextureFromShape-method. 

Apart from seemingly doing the job, I am a bit hesitant using the entire Farseer sample project into my own code. I started just picking and choosing stuff and putting that into a library called "FarseerUtil", but so many things appear interconnected that I still feel like ending up with a lot of ballast that I may not even be using.

Also, the "TextureFromShape"-method relies on getting some pre-defined texture from content (i.e. image files). I haven't figured out yet if this is strictly necessary, but -again- I would prefer to be without, if possible. 

Did you or anyone else figure out a simple way of getting textures from dynamically generated polygons?

 

 

 

Sep 15, 2011 at 6:08 AM

Hey Pertander,

Well you can try the TextureFromVertices method. Like so,

 

polygonSprite = new Sprite(ScreenManager.Assets.TextureFromVertices(vertices, MaterialType.Dots, Color.Red, 1f))

Just store the vertices in a variable and use them to create new sprites on runtime.

 


Sep 15, 2011 at 8:04 AM

Hi Farhanarif,

Well, yeah, the TextureFromVertices is the private method called by the TextureFromShape method I mentioned. And for some reason it uses an image file ("dots.png") for generating the texture, as I hinted at. 

I was just wondering if it is possible to do it in a simpler way and without relying on an image files.

Anyways, I have just given in and am using the AssetCreator class and its content resources in my own project for the moment anyway. 

BTW I assume you agree that it would be really cool if future versions of FPE could have these tools integrated somehow or in a separate library, so you wouldn't feel you had to "steal" code from the samples project.

 

 

Sep 15, 2011 at 2:05 PM

Hey Petrander

Well yes, I used the entire sample project and built my project around it. There were absolutely no issues with anything. Everything integrated quite easily, so I am grateful to the Farseer team for that.

You can't really call this "stealing". This is open source, is it not ? I actually preferred to do it this way.

If you do manage to find another way round it, do post it in the forums. A lot of other people could benefit ! 

Sep 27, 2011 at 6:31 AM

Hi Farhanarif, 

Well, of course, I didn't mean "stealing" in the literal sense, which is why I used the quotations marks. What I am talking about is being forced to take a project that was meant for something else (a comprehensive demo) and either integrate it into your own project including all the unnecessary ballast, or picking and choosing things and trying to get them to work in your own project. It would have been nicer if there was an all-purpose utility library where all these smart functions would reside. 

Anyways, I will just continue trying to create my own utility library for my own project derived from the code of the demo project. I can get it to work so that is the most important thing. :-) 

I am still wondering whether resource base textures are really necessary, but if I analyze the code, maybe I will find that out.