Aligning RevoluteJoints

Topics: Developer Forum, User Forum
Apr 25, 2012 at 8:26 PM
Edited Apr 26, 2012 at 8:38 PM

Hello everyone,

I am new to these forums and have recently started using Farseer. So far it's been awesome and makes my life easier. But I have come across a problem that hopefully someone might be able to point me in the right direction. Recently I have been trying to give my character the ability to grapple onto objects. While I have the grappling down the problem is the rope itself. The player attaches and detaches and can swing the way I have planned. But I'm having trouble aligning the rope's RevoluteJoints with the rope's bodies. The RevoluteJoints are appearing in the middle of the bodies rather than in between them making it look very stiff. This is difficult for me because I am trying to determine the direction the rope should be created in by the direction from the player's body to the grappled object. Also note, that I am referencing code from 3.1 Farseer TestBed tutorial. The following code is what I currently have and the two lines that I'm really focusing on are..

 

body.Position = new Vector2(x + (direction.X) * i, y + (direction.Y));

 

and

 

Vector2 anchor = new Vector2(x + (direction.X) * i, y + (direction.Y));

 

and the rest

 

public void SetupRope(Vector2 position, int Count)
        {
            float y = position.Y;
            float x = position.X;

            Body prevBody = new Body(GameSetup.world);
            prevBody.Position = Start.body.Position;
            Bodies.Add(prevBody);
            for (int i = 0; i < Count; ++i)
            {
                Body body = BodyFactory.CreateBody(GameSetup.world);
                body.CollidesWith = Category.All & ~Category.Cat3 & ~Category.Cat2;
                body.CollisionCategories = Category.Cat2;
                body.BodyType = BodyType.Dynamic;
                Bodies.Add(body);

//Move Later
  Vector2 direction = ConvertUnits.ToDisplayUnits(End.body.Position) - ConvertUnits.ToDisplayUnits(Start.body.Position); direction.Normalize(); direction = ConvertUnits.ToSimUnits(direction); body.Position = new Vector2(x + (direction.X) * i, y + (direction.Y)); if (i == Count - 1) { body = End.body; body.AngularDamping = 0.4f; } else { Vertices box = PolygonTools.CreateRectangle(0.5f, 0.125f); PolygonShape shape = new PolygonShape(box, 1); shape.Density = 10; Fixture fixture = body.CreateFixture(shape); fixture.Friction = 0.2f; fixture.CollisionCategories = Category.Cat2; fixture.CollidesWith = Category.All & ~Category.Cat3 & ~Category.Cat2; } prevStartPosition = Start.Position; Vector2 anchor = new Vector2(x + (direction.X) * i, y + (direction.Y));//Works Kinda RevoluteJoint jd = new RevoluteJoint(prevBody, body, prevBody.GetLocalPoint(ref anchor), body.GetLocalPoint(ref anchor)); jd.CollideConnected = false; RevJoints.Add(jd); GameSetup.world.AddJoint(jd); prevBody = body; } prevBodyRef = prevBody; const float extraLength = 0.01f; }

Any help would be very much appreciated. Thank you for your time. :)
Apr 25, 2012 at 9:01 PM

The problem is that you are basing your anchor around the body's position, which is from the center of the body. You need to add an offset when creating the anchor to account for the length of the body.

Apr 26, 2012 at 1:36 AM

Yea I understand I need to establish an offset but I'm having trouble finding how to accomplish that. When I attempt to make an offset the bodies will fly across the screen.

//I create an offset that is half of the collision box created.
Vector2 offset = ConvertUnits.ToDisplayUnits(0.25f, 0.0625f);

//Multiply that offset by my direction
offset *= direction;

//... Code

//Set the Anchor with the offset
Vector2 anchor = new Vector2(offset.X *i, offset.Y *i);

Apr 26, 2012 at 8:36 PM

There is something fishy with your code. For example what's direction2 in the first snippet? And why you constantly convert from display to sim units. When doing physics you should work with physics units - meters, kilograms, seconds. Convert to display units only in your draw method. To say it more formally I prefer a general design pattern that does not push the physics to the scenegraph but pulls the physics whenever necessary from inside the graphics routines. Basically run your physics independently and let the draw method pull the coordinates from the physics objects and convert them (with ConvertUnits or even automagically through a carefully set up projection matrices). You are more or less doing this but it's somewhat confusing. For example why do your physics need to know about the diplay units?

Sorry for digressing, I'll try to be more helpful. A revolute joint (aka pin joint) pins two bodies together at a common anchor so they can rotate but not translate relative to each other. It's perhaps easier to visualize it like this. Note that the first thing the engine does is bring the bodies together so they share the anchor so the constraint is satisfied (no relative translation at the anchor). That means if the anchor is not set properly the simulation will explode and that's especially true with many constraints that suddenly can't be satisfied. This is probably what you are seeing. 

Basically you can do this (vertical rope):

rectangle1=PolygonTools.CreateRectangle(half-width, half-height). (note that PolygonTools uses half lenghts which is perhaps somewhat counter-intuitive)

rectangle2=PolygonTools.CreateRectangle(half-width, half-height).

Vector2 anchor1=new Vector2(x1,y1) where  x,y is a point in the *local* coordinates of the rectangle1. For example the center of the *lower* half like in your second snippet. E.g. if the rectangle is 1 mx1m  (PolygonTools.CreateRectangle(0.5,0.5)) it's center in local coordinates is 0,0 and vertices are (-0.5,-0.5) (0.5,-0.5) (0.5,0.5) (0.5,0.5)

Vector2 anchor2=new Vector2(x2,y2) where  x,y is a point in the *local* coordinates of the rectangle2. For example the center of the *upper* half.

RevoluteJoint(rectangleBody1,rectangleBody2,anchor1,anchor2).

Note the use of a local coordinate system for anchors.

This will make a joint where the upper body's bottom slighly overlaps the lower body's top. By adjusting the overlap you can make the thing more or less stiff. You can even try negative overlap. I also like making two seperate revolute joints on the two upper/lower corners. I.e. use quadrants instead of halves. Makes the "chain" pretty cool and with different properties than just using one joint (more elastic-like with joint limits and motors).

P.S: there is also a rope joint (in the spirit of a chain of distance joints) that might be of use. It's more like real rope whereas a chain of revolute joints is more like a metal chain or rather a long thin rod.

Apr 26, 2012 at 11:08 PM
Edited Apr 27, 2012 at 7:29 PM

You're right about the direction variable. It was a debug variable to help me find out what was going on. I just forgot to edit it within the post. I did go back and fix it so it should just show as direction. About the conversion, I completely agree with you. Originally I did that because it helped me visualize where the direction was going in screen space. However I did get a different result when I was converting than compared to when I wasn't. The code is confusing and I'm trying to clean it up the best I can while at the same time figuring out what is going on.

I did take your advice and removed the ConvertUnits function calls. I just created a video of my project showing what exactly I'm seeing. Hopefully this will help visualize the problem I'm poorly describing. Also note, that after removing the ConvertUnit functions it has become a lot more flexible and less stiff. But the end body position is off. Which was the problem I was describing in the previous paragraph.

http://www.youtube.com/watch?v=tuYuLxo6sWg&feature=youtu.be

For my purposes having this rope act more like a metal chain is closer to what I want. But I didn't think of that and will definitely try it out. Thanks :)

And thank you very much for your help so far.

 

EDIT: So I just made a change with the code regarding the problem with how I'm setting the anchor for the last body. The last body is the player avatar itself. Instead of giving that last body the same anchor as the others, I sent in the variables x and y created at the top of the function. The position passed in is the position of the grappling hook or the Start body. Right now I am cleaning the code, but when I'm done I'll post what I've come up with.

Apr 27, 2012 at 8:39 PM
Edited Apr 27, 2012 at 8:57 PM

This is what I have now.

public void SetupRope(Vector2 position, Vector2 direction, int Count)
        {
            float y = position.Y;
            float x = position.X;

            //Begin attaching from the Start body
            Body prevBody = new Body(GameSetup.world);
            prevBody.Position = Start.body.Position;
            Bodies.Add(prevBody);

            //Loop through the set count and create a chain or link for each iteration, making up the rope.
            for (int i = 0; i < Count; ++i)
            {
                Body body = BodyFactory.CreateBody(GameSetup.world);
                RevoluteJoint jd;

                //Set Bodies collision
                body.CollidesWith = Category.All & ~Category.Cat3 & ~Category.Cat2;
                body.CollisionCategories = Category.Cat2;

                //Give body a position based on direction and position of the Start body
                body.BodyType = BodyType.Dynamic;
                body.Position = new Vector2(x + (direction.X) * i, y + (direction.Y));
                Bodies.Add(body);

                //Create anchors based on local coordinate system
                Vector2 anchor = new Vector2((x + (direction.X) * i), (y + (direction.Y)));
                Vector2 endBodyAnchor = End.body.Position;

                if (i == Count - 1)
                {
                    body = End.body;
                    body.AngularDamping = 0.4f;

                    //Setup/Attach Revolute joint
                    jd = new RevoluteJoint(prevBody, body, prevBody.GetLocalPoint(ref anchor), 
body.GetLocalPoint(ref endBodyAnchor)); } else { //Create/Setup each chain of the rope. Vertices box = PolygonTools.CreateRectangle(0.5f, 0.125f); PolygonShape shape = new PolygonShape(box, 1); shape.Density = 10; Fixture fixture = body.CreateFixture(shape); fixture.Friction = 0.2f; //Set fixture collisions fixture.CollisionCategories = Category.Cat2; fixture.CollidesWith = Category.All & ~Category.Cat3 & ~Category.Cat2; //Setup/Attach Revolute joint jd = new RevoluteJoint(prevBody, body, prevBody.GetLocalPoint(ref anchor), body.GetLocalPoint(ref anchor)); } jd.CollideConnected = false; //Add joint to the world for calculations GameSetup.world.AddJoint(jd); prevBody = body; } prevBodyRef = prevBody; }
Apr 28, 2012 at 12:39 PM

Hi, I've watched the video again. Please correct me if I'm not reading the new code properly but here are a few comments: 

- You are not rotating the rope bodies at creation but letting the engine do it (0:07 in the video). This is a problem because it's not very easy for the solver and it may take quite a few iterations. Also it might not reach the best solution. From the solver's point of view the bodies suddenly materialize out of thin air with all their constraints unsatisfied (it's a mess). You should help it as much as you can.

- Your anchors seem off. Since your rope is horizontal anchorA should be some offset to the right of the center of BodyA and anchorB to the left of the center of bodyB. Try making one anchor (0.5,0) and the other (-0.5,0). Right now it seems your anchor is at the center of BodyA. I don't know if that's intentional. For further examples there is good code in LinkFactory and TestBed.ChainTest. Path.SubdivideEvenly for some trig magic to find the rotation. PathManager (and LinkFactory) will make your chain automatically even.

- Finally you want your character to hang on the chain. That's also a problem. You should make the links have similar masses to the character because light bodies supporting heavy bodies is difficult for a physics engine. In your video the joints stretch and for a revolute joint it means it's very unstable and under a lot of stress risking to explode. Might take some tuning or even additional joints (like a distance joint from end to character, rope joint etc.)

Apr 29, 2012 at 12:58 AM
Edited Apr 29, 2012 at 1:06 AM

There has been some significant changes since the last video. The following link below is a video of the updated version.

http://www.youtube.com/watch?v=9xvM-S6Bzdc&feature=youtu.be

So far the rope is behaving just as I hoped for. You're right about the bodies' rotation, but when I attempted to add a rotation to the bodies I got a bad result. I believe it was because I didn't apply the rotation correctly. But I am now currently working on the rotation problem.

For the anchor I am now using (0.5f, 0.0f) and (-0.5f, 0.0f) like you suggested, and they look perfectly aligned now. Also if by BodyA you are referring to the Start body, then the anchor is suppose to be in the middle and the same for the End body's anchor. For finding the rotation of the bodies, I was going to write a function that would do it for me but it sounds like Path.SubdivideEvenly could be extremely useful. I'll look more into that. :)

The rope actually supports the player by using a distance joint created in another function. That distance joint is created once the grappling hook collides with a grapple platform. In the older video you can see the line from the distance joint (the grappling hook) to the End body (the player). However, in that version the end joint that was meant to be in the center of End's body had a wrong offset. So when I moved the player it would stretch the joints more than it should and caused them to become unstable like you said.

Also to note, in the older video I shoot the grappling hook from a greater distance than I intended for the purpose of demonstration.

Here is the new code.

 

public void SetupRope(Vector2 position, Vector2 direction, float rotation, int Count)
        {
            float y = position.Y;
            float x = position.X;

            //Begin attaching from the Start body
            Body prevBody = new Body(GameSetup.world);
            prevBody.Position = Start.body.Position;
            Bodies.Add(prevBody);

            //Loop through the set count and create a chain or link for each iteration, making up the rope.
            for (int i = 0; i < Count; ++i)
            {
                Body body = BodyFactory.CreateBody(GameSetup.world);
                RevoluteJoint jd;

                //Set Bodies collision
                body.CollidesWith = Category.All & ~Category.Cat3 & ~Category.Cat2;
                body.CollisionCategories = Category.Cat2;

                //Give body a position based on direction and position of the Start body
                body.BodyType = BodyType.Dynamic;
                body.Position = new Vector2(x + (direction.X) * i, y + (direction.Y));
                Bodies.Add(body);

                //Create anchors based on local coordinate system
                Vector2 offset = new Vector2(0.50f, 0.0f);
                if (direction.X > 0)
                    offset.X *= -1;
                Vector2 anchor = new Vector2((x + (direction.X) * i), (y + (direction.Y))) + offset;
                Vector2 endBodyAnchor = End.body.Position;
                Vector2 startBodyAnchor = Start.body.Position;

                if (i == Count - 1)
                {
                    body = End.body;
                    body.AngularDamping = 0.4f;

                    //Setup/Attach Revolute joint
                    jd = new RevoluteJoint(prevBody, body, prevBody.GetLocalPoint(ref anchor), body.GetLocalPoint(ref endBodyAnchor));
                }
                else
                {
                    //Create/Setup each chain of the rope.
                    Vertices box = PolygonTools.CreateRectangle(0.5f, 0.125f);
                    PolygonShape shape = new PolygonShape(box, 1);
                    shape.Density = 10;
                    Fixture fixture = body.CreateFixture(shape);
                    fixture.Friction = 0.2f;

                    //Set fixture collisions
                    fixture.CollisionCategories = Category.Cat2;
                    fixture.CollidesWith = Category.All & ~Category.Cat3 & ~Category.Cat2;

                    //Setup/Attach Revolute joint
                    if (i == 0)
                        jd = new RevoluteJoint(prevBody, body, prevBody.GetLocalPoint(ref startBodyAnchor), body.GetLocalPoint(ref anchor));
                    else
                        jd = new RevoluteJoint(prevBody, body, prevBody.GetLocalPoint(ref anchor), body.GetLocalPoint(ref anchor));

                }
                jd.CollideConnected = false;

                //Add joint to the world for calculations
                GameSetup.world.AddJoint(jd);

                prevBody = body;
            }
            prevBodyRef = prevBody;
        }