Calculating AABB position after movement.

Topics: Developer Forum, User Forum
Jul 15, 2013 at 7:09 AM
Edited Jul 15, 2013 at 9:54 AM
After discussing my problem here, I was able to get a basic bounding box for a shape.

However i now have trouble re-calculating the position of the bounding box after moving the body.

Please see video and screenshots below:
Here is a bounding box for my object
Image

Here is how I calculate it (Thanks HAL_9000
PS: I am aware of the spelling mistake :)

Note: My "terrrain" consists of kinematic bodies

Here is my issue
Youtube Video
Body selectedBody = null;
        Fixture selectedFicture = null;
        Vector2[] boundingBox;
public void AttemptSelect(Vector2 Point)
        {

            List<Vector2[]> selectionBoxes = new List<Vector2[]>();

            Vector2[] ret = null;

            Fixture f = World.TestPoint(Point);
            if (f != null)
            {
                ObjectSelected = true;
                selectedFicture = f;
                Body b = f.Body;

                for (int x = 0; x < b.FixtureList.Count; x++)
                {
                    AABB aabb;
                    f = b.FixtureList[x];
                    f.GetAABB(out aabb, 0);

                    if (ret == null)
                        ret = aabb.Vertices.ToArray();

                    selectionBoxes.Add(aabb.Vertices.ToArray());
                }


                for (int x = 0; x < selectionBoxes.Count; x++)
                {

                    Vector2[] v = selectionBoxes[x];

                    for (int y = 0; y < v.Length; y++)
                    {

                        //Top Left
                        if (v[y].X < ret[0].X)
                            ret[0].X = v[y].X;
                        if (v[y].Y < ret[0].Y)
                            ret[0].Y = v[y].Y;

                        //Top Right
                        if (v[y].X > ret[1].X)
                            ret[1].X = v[y].X;
                        if (v[y].Y < ret[1].Y)
                            ret[1].Y = v[y].Y;

                        //Bottom Right
                        if (v[y].X > ret[2].X)
                            ret[2].X = v[y].X;
                        if (v[y].Y > ret[2].Y)
                            ret[2].Y = v[y].Y;

                        //Bottom Left
                        if (v[y].X < ret[3].X)
                            ret[3].X = v[y].X;
                        if (v[y].Y > ret[3].Y)
                            ret[3].Y = v[y].Y;

                    }
                }

                boundingBox = ret;
            }else
            {
                ObjectSelected = false;
                selectedFicture = null;
                boundingBox = null;
            }
            
        }
I then attempt to move it the following way
// Manipulate teh selected object
            if (level.ObjectSelected)
            {
                Vector2 newPos = new Vector2();
                if (input.KeyboardState.IsKeyDown(Keys.Left))
                {
                    newPos.X -= 0.002f;
                }
                if (input.KeyboardState.IsKeyDown(Keys.Right))
                {
                    newPos.X += 0.002f;
                }
                if (input.KeyboardState.IsKeyDown(Keys.Up))
                {
                    newPos.Y -= 0.002f;
                }
                if (input.KeyboardState.IsKeyDown(Keys.Down))
                {
                    newPos.Y += 0.002f;
                }
                //newPos = ConvertUnits.ToDisplayUnits(newPos);
                level.MoveSelectedObject(newPos);
            }

public void MoveSelectedObject(Vector2 moveAmount)
        {
            selectedFicture.Body.Position += moveAmount;
        }
And here is how I attempt to recalculate the boundingbox's position
void UpdateSelectionBox()
        { 
            if(selectedFicture != null)
            {
                List<Vector2[]> selectionBoxes = new List<Vector2[]>();
                Vector2[] ret = null;
                Fixture f = selectedFicture;

                Body b = f.Body;
                for (int x = 0; x < b.FixtureList.Count; x++)
                {
                    AABB aabb;
                    f = b.FixtureList[x];
                    f.GetAABB(out aabb, 0);

                    if (ret == null)
                        ret = aabb.Vertices.ToArray();

                    selectionBoxes.Add(aabb.Vertices.ToArray());
                }


                for (int x = 0; x < selectionBoxes.Count; x++)
                {

                    Vector2[] v = selectionBoxes[x];

                    for (int y = 0; y < v.Length; y++)
                    {

                        //Top Left
                        if (v[y].X < ret[0].X)
                            ret[0].X = v[y].X;
                        if (v[y].Y < ret[0].Y)
                            ret[0].Y = v[y].Y;

                        //Top Right
                        if (v[y].X > ret[1].X)
                            ret[1].X = v[y].X;
                        if (v[y].Y < ret[1].Y)
                            ret[1].Y = v[y].Y;

                        //Bottom Right
                        if (v[y].X > ret[2].X)
                            ret[2].X = v[y].X;
                        if (v[y].Y > ret[2].Y)
                            ret[2].Y = v[y].Y;

                        //Bottom Left
                        if (v[y].X < ret[3].X)
                            ret[3].X = v[y].X;
                        if (v[y].Y > ret[3].Y)
                            ret[3].Y = v[y].Y;

                    }
                }
                boundingBox = ret;
            }
        }
Also, here is my draw logic
if(ObjectSelected)
                if(boundingBox != null)
                {
                    // draw teh bounding box
                    Boolean lastIteration = false;
                    int count = boundingBox.Count();
                    for(int b = 1; b <= count; b++)
                    {
                        if(b == count)
                        {
                            lastIteration = true;
                            spriteBatch.DrawLine(lineTexture, ConvertUnits.ToDisplayUnits(boundingBox[b - 1]), ConvertUnits.ToDisplayUnits(boundingBox[0]));
                        }else
                        {
                            spriteBatch.DrawLine(lineTexture, ConvertUnits.ToDisplayUnits(boundingBox[b - 1]), ConvertUnits.ToDisplayUnits(boundingBox[b]));
                        }

                    }
                }
Jul 15, 2013 at 10:57 PM
Strange because my demo tracks the body around the screen.

Any chance you can upload your project as a zip somewhere? Or a testbed screen with the logic you're using?
Coordinator
Jul 16, 2013 at 2:39 AM
I've not followed your progress, as I'm busy with other things, so I'll just post a note:

A body is basically just a point in space with some properties. That point in space also determines the position of the fixtures, as they are constantly translated (moved) by the coordinates of the body. Quick example:

A rectangle shape has the following properties: width = 1, height = 2, position = (0,0)

The shape is attached with a fixture, to a body with the following properties: position = (0,0).

Then you move the body to (5,5), where is the shape now? at (5,5)? - No, it is still at (0,0). But why is that?
It is because the shape uses its own coordinate system, and when a body moves, it does not affect the shape.

To get the real position of the shape, you have to get the transform (translation/movement & rotation/angle) of the body, and apply it to the shape.
In your case, you need to get the vertices of the fixtures and transform them by the body transform - then you can calculate an enclosing AABB.
Jul 16, 2013 at 5:25 PM
Thanks guys, for some reason I omitted the transform bit out.
Works perfect now!

Just a quick note, to get the position at which to draw my texture I have to apply the transform also?
Jul 16, 2013 at 5:39 PM
No you don't need the transform for that. ConvertUnits.ToDisplayUnits(body.Position) is all you should need.
Jul 16, 2013 at 5:46 PM
Edited Jul 16, 2013 at 6:09 PM
Well, that's what I also thought but for some reason when I create the body, its position is at 0,0. Here is my code
public EditorPhysicsObject(Level level, Vertices verts)
        {
            levelRef = level;
            this.Verticies = verts;
            Name = String.Format("terrain_{0}_{1}", DateTime.Now.Second, DateTime.Now.Millisecond);

            // Create it!!!
            List<Vertices> list = BayazitDecomposer.ConvexPartition(Verticies);
            body = BodyFactory.CreateCompoundPolygon(levelRef.World, list, 0.1f);
            body.BodyType = BodyType.Kinematic;
            body.UserData = this;
            fixtures = body.FixtureList;
            texture = levelRef.Services.AssetCreator.TextureFromVertices(verts, MaterialType.Waves, Color.White, 1.0f);
        }
and here is a breakpoint after the body creation, as you can see, position is 0,0
Image

here is what it looks like
Image

and here is my draw code
(public void Draw(SpriteBatch spriteBatch, Texture2D lineTexture)
        {
            Transform trans;
            Body.GetTransform(out trans);
            if(Selected)
            {
                int count = BoundingBox.Count();
                for (int b = 1; b <= count; b++)
                {
                    if (b == count)
                        spriteBatch.DrawLine(lineTexture, ConvertUnits.ToDisplayUnits(BoundingBox[b - 1]), ConvertUnits.ToDisplayUnits(BoundingBox[0]));
                    else
                        spriteBatch.DrawLine(lineTexture, ConvertUnits.ToDisplayUnits(BoundingBox[b - 1]), ConvertUnits.ToDisplayUnits(BoundingBox[b]));
                }
            }
            spriteBatch.Draw(texture, ConvertUnits.ToDisplayUnits(body.Position), Color.White);
        })
Jul 16, 2013 at 6:10 PM
Edited Jul 16, 2013 at 6:11 PM
However when I move the body, the textures moves also.

i think this might have something to do when i create the polygon from vertices....
Do i need to manually specify polygon position or does it take into the account the position of vertices in the world space?
Jul 16, 2013 at 7:19 PM
Farseer uses the center of the body when positioning objects but XNA uses the top-left of the texture. You can specify an origin in the draw method to compensate.

You can work out the origin for an irregular shape like this:

                Vector2 centroid = -this.Verticies.GetCentroid();
                this.Verticies.Translate(ref centroid);
                Vector2 Origin = -centroid;
And the draw:
                sb.Draw(theTexture, ConvertUnits.ToDisplayUnits(sprite.Body.Position),
                                null, Color.White, sprite.Body.Rotation, Origin, 1f, SpriteEffects.None,
                               1f);
Jul 19, 2013 at 10:12 AM
Edited Jul 19, 2013 at 10:18 AM
for some reason I am unable to get it to work.

My body is created at 0,0 when i create it from the vertices.

Here is a video describing my issue.

Here is my creation code:
Vector2 Centroid;
 Body body;

        public EditorPhysicsObject(Level level, Vertices verts)
        {
            levelRef = level;
            this.Verticies = verts;
            Name = String.Format("terrain_{0}_{1}", DateTime.Now.Second, DateTime.Now.Millisecond);

            // Create it!!!
            List<Vertices> list = BayazitDecomposer.ConvexPartition(Verticies);
            body = BodyFactory.CreateCompoundPolygon(levelRef.World, list, 0.1f);
            body.BodyType = BodyType.Kinematic;
            body.UserData = this;
            fixtures = body.FixtureList;
            texture = levelRef.Services.AssetCreator.TextureFromVertices(verts, MaterialType.Test, Color.White, 5.0f);
            Centroid = -verts.GetCentroid();
            this.Verticies.Translate(ref Centroid);
            Centroid = ConvertUnits.ToDisplayUnits(-Centroid);
        }
Here is my draw code
        public void Draw(SpriteBatch spriteBatch, Texture2D lineTexture)
        {
            Color c = Color.White;
            if(Selected)
            {
                c = Color.GreenYellow;
                int count = BoundingBox.Count();
                for (int b = 1; b <= count; b++)
                {
                    if (b == count)
                        spriteBatch.DrawLine(lineTexture, ConvertUnits.ToDisplayUnits(BoundingBox[b - 1]), ConvertUnits.ToDisplayUnits(BoundingBox[0]));
                    else
                        spriteBatch.DrawLine(lineTexture, ConvertUnits.ToDisplayUnits(BoundingBox[b - 1]), ConvertUnits.ToDisplayUnits(BoundingBox[b]));
                }
            }
            Vector2 pos = ConvertUnits.ToDisplayUnits(body.Position);
            spriteBatch.Draw(texture, new Rectangle((int)pos.X, (int)pos.Y, texture.Width, texture.Height), null, c, 0f, Centroid, SpriteEffects.None, 0);
        }
Unfortunately I cant upload teh project somewhere as its a part of a large solution
Jul 21, 2013 at 6:09 PM
From the looks of it verts is the variable holding the vertices for the polygon you're trying to make.

So I think you need to change the line:

this.Verticies.Translate(ref Centroid);

to

verts.Translate(ref Centroid);

Also, if memory serves you shouldn't need to be using ConvertUnits on the line

Centroid = ConvertUnits.ToDisplayUnits(-Centroid);

As the vertices are the actual size of the polygon, not the sim units.
Jul 22, 2013 at 2:16 AM
Thanks for taking your time to have a look but I have tried the said solutions and its not working. It still seems that my body.position is always at 0,0 upon creation.

I have uploaded my entire solution, the issue I am having is in the CS2Editor.EditorPhysicsObject

Counterstrike2D.rar (10.4 MB)
https://mega.co.nz/#!jwgXRQRB!byYcJ31we5OvhC0QpLo8vitiP95tqCrV5uKzPZzV7aE


I am totaly at a loss why my shape is at the right place but body always get created at 0,0
Jul 22, 2013 at 4:49 AM
Bodies are always created at 0,0. You specify where you want them to go after they're created. The problem is the centroid can't be calculated so the body position / texture position + origin never match.

It looks like the verts aren't right. Something like not clockwise / concave / translated or something. Hopefully someone far smarter than I will read this and explain why.

I couldn't figure it out so I just cheated and used the awesome tools in the engine ^^

Basically it generates the texture from the verts got by the mouse and generates the body from the texture using the tried and tested PolygonTools.
        public EditorPhysicsObject(Level level, Vertices verts)
        {

            levelRef = level;
            Name = String.Format("terrain_{0}_{1}", DateTime.Now.Second, DateTime.Now.Millisecond);
            texture = levelRef.Services.AssetCreator.TextureFromVertices(verts, MaterialType.Test, Color.White, 5.0f);

            uint[] data = new uint[texture.Width * texture.Height];
            texture.GetData(data);

            Vertices textureVertice = PolygonTools.CreatePolygon(data, texture.Width, true);

            Centroid = -textureVertice.GetCentroid();
            textureVertice.Translate(ref Centroid);
            Centroid= -Centroid;

            //Here we simplify the vertices
            //This is totally optional but can save loads of vertices being created as most aren't needed to make the shape.
            //You may need to tweak the value passed to the procedure
            //Pass 1 if you want maximum detail 
            //Pass higher numbers to get progressively less detail (currently 600)
            //Or just comment this line out and swap the comments on the lines below it
            Vertices simplifiedVerts = FarseerPhysics.Common.PolygonManipulation.SimplifyTools.ReduceByDistance(textureVertice, 600f);

            //            List<Vertices> ListOfVerts = BayazitDecomposer.ConvexPartition(textureVertice);
            List<Vertices> ListOfVerts = BayazitDecomposer.ConvexPartition(simplifiedVerts);

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

            body = BodyFactory.CreateCompoundPolygon(levelRef.World, ListOfVerts, 0.1f);

            body.BodyType = BodyType.Kinematic;
            body.UserData = this;
            fixtures = body.FixtureList;

        }

Jul 22, 2013 at 6:25 AM
Thanks HAL for your workaround, it will have to do for now i guess,
I would however prefer the bodies to be drawn where the vertices are and not at 0,0

Thanks all the same, maybe genbox or other devs might be able to help me out
Jul 22, 2013 at 7:05 PM
The engine will never do that for you. You have to specify where you want the bodies to go.

Here's a quick function. It's not perfect but should get you in the right direction.

        public EditorPhysicsObject(Level level, Vertices verts)
        {
            levelRef = level;
            Name = String.Format("terrain_{0}_{1}", DateTime.Now.Second, DateTime.Now.Millisecond);
            texture = levelRef.Services.AssetCreator.TextureFromVertices(verts, MaterialType.Test, Color.White, 5.0f);

            uint[] data = new uint[texture.Width * texture.Height];
            texture.GetData(data);

            Vertices textureVertice = PolygonTools.CreatePolygon(data, texture.Width, true);

            //Here we simplify the vertices
            //This is totally optional but can save loads of vertices being created as most aren't needed to make the shape.
            //You may need to tweak the value passed to the procedure
            //Pass 1 if you want maximum detail 
            //Pass higher numbers to get progresively less detail
            //Or just comment this line out and swap the comments on the lines below it
          //  Vertices simplifiedVerts = FarseerPhysics.Common.PolygonManipulation.SimplifyTools.ReduceByDistance(textureVertice, 600f);

            Centroid = -textureVertice.GetCentroid();
            textureVertice.Translate(ref Centroid);
            Centroid = -Centroid;

            List<Vertices> ListOfVerts = BayazitDecomposer.ConvexPartition(textureVertice);
           // List<Vertices> ListOfVerts = BayazitDecomposer.ConvexPartition(simplifiedVerts);

            //scale the vertices from graphics space to sim space
            Vector2 vertScale = new Vector2(ConvertUnits.ToSimUnits(1)) * 1f;
            foreach (Vertices vertices in ListOfVerts)
            {
                vertices.Scale(ref vertScale);
            }

            body = BodyFactory.CreateCompoundPolygon(levelRef.World, ListOfVerts, 0.1f);

            body.Position = GetPositionForVerts(verts);
            body.BodyType = BodyType.Kinematic;
            body.UserData = this;
            fixtures = body.FixtureList;

        }

        public Vector2 GetPositionForVerts(Vertices v)
        {

            Vector2[] verts = v.ToArray();
            for (int i = 0; i < v.Count; i++)
            {


                //Top Left
                if (v[i].X < verts[0].X)
                    verts[0].X = v[i].X;
                if (v[i].Y < verts[0].Y)
                    verts[0].Y = v[i].Y;

                //Top Right
                if (v[i].X > verts[1].X)
                    verts[1].X = v[i].X;
                if (v[i].Y < verts[1].Y)
                    verts[1].Y = v[i].Y;

                //Bottom Right
                if (v[i].X > verts[2].X)
                    verts[2].X = v[i].X;
                if (v[i].Y > verts[2].Y)
                    verts[2].Y = v[i].Y;

                //Bottom Left
                if (v[i].X < verts[3].X)
                    verts[3].X = v[i].X;
                if (v[i].Y > verts[3].Y)
                    verts[3].Y = v[i].Y;

            }


            Vector2 ret = new Vector2();

            float x = (verts[1].X - verts[0].X) / 2f;
            float y = (verts[2].Y - verts[1].Y) / 2f;

            ret.X = x;
            ret.Y = y;
            ret.X += verts[0].X;
            ret.Y += verts[0].Y;

            return ret; 
        }