Rotating a body around a specific point (another body)

Topics: Developer Forum
Oct 14, 2012 at 10:56 PM

Hi all,

I've been trying to generate a couple of bodies that rotate around a specific point by maintaining the distance. I tried all available joints, like the revolute joint (which I found via google to be good for this problem), but none did work. For the revolute joint I created another body in the middle of the others and connected them. Here is my code: Could you please have a short look what's wrong? Thanks´

        public override void Initialize()
        {
            // basic initialization
            _bricks = new Body[level.GetLength(0), level.GetLength(1)];

            // load graphics
            _tileSheet = new Sprite(this._world.Game.Content.Load<Texture2D>("TileSheet"));

            // create the center where the cube is rotating around
            _center = new Body(this._world);
            _center.BodyType = FarseerPhysics.Dynamics.BodyType.Static;
            _center.Position = ConvertUnits.ToSimUnits(new Vector2(12 * 10, 12 * 10));
            _center.Mass = 1f;
            _center.CreateFixture(new CircleShape(ConvertUnits.ToSimUnits(5f), 5f));

            // create bodies from the level data
            for (int x = 0; x < level.GetLength(0); x++)
            {
                for (int y = 0; y < level.GetLength(1); y++)
                {
                    if (level[x, y] > 0)
                    {
                        Body body = new Body(this._world);
                        body.BodyType = FarseerPhysics.Dynamics.BodyType.Static;
                        body.Position = ConvertUnits.ToSimUnits(new Vector2(x * 10, y * 10));
                        body.Mass = 1f;
                        
                        body.CreateFixture(new PolygonShape(PolygonTools.CreateRectangle(ConvertUnits.ToSimUnits(5), ConvertUnits.ToSimUnits(5)), 5f));

                        //JointFactory.CreateWeldJoint(_world, body, _center, body.GetWorldVector(Vector2.Zero));
                        //JointFactory.CreateAngleJoint(_world, _center, body);
                        //JointFactory.CreateFixedRevoluteJoint(_world, body, Vector2.Zero, _center.Position);
                        JointFactory.CreateRevoluteJoint(_world, _center, body, _center.GetWorldVector(Vector2.Zero));

                        _bricks[x, y] = body;
                    }
                }
            }
        }

        public override void Update(Microsoft.Xna.Framework.GameTime gameTime)
        {
            if (Keyboard.GetState().IsKeyDown(Keys.Right))
            {
                _center.Rotation += 0.1f;
                //foreach (var body in _bricks)
                //{
                //    if (body != null)
                //    {
                //        body.Rotation += 0.1f;
                //    }
                //}
            }
            if (Keyboard.GetState().IsKeyDown(Keys.Left))
            {
                _center.Rotation -= 0.1f;
            }
        }
Oct 15, 2012 at 6:25 PM
Edited Oct 15, 2012 at 6:28 PM

Probably what you want to do is JointFactory.CreateRevoluteJoint(_world, body, _center, Vector2.Zero).

This is valid when the body's center of mass is at it's origin (circle, rectangle, any regular polygon etc.). Then LocalCenter=Vector2.Zero. Otherwise you need to use LocalCenter as the pivot because torques are produced about the center of mass.

Oct 16, 2012 at 5:31 AM

I tried what you suggested. I came one step closer but it seems that when the bodies get inactive once, it doesn't work anymore. I have debug view enabled and see the red and blue dots (from the revolute joint) connected, after they are inactivated. I thought that the revolute joint is between the center body and the outer bodies. Here is a video that shows this: (first it works, then the bodies get inactive, then it doesn't work) http://youtu.be/WQ59fDKbNDs

Btw.: Besides the revolute joint you suggested, I changed the type of the bodies (not the center) to dynamic. This seems to be required, too as your change alone didn't work also.

Oct 16, 2012 at 3:05 PM

Yes joints make sense only if at least one of the bodies for the joint is dynamic. Only dynamic bodies get simulated by the solver. Static and kinematic must be manually moved. Also body.AllowSleep=false will prevent a body from sleeping.

Oct 16, 2012 at 10:00 PM

Thanks a lot. I've set the bodies to allow sleep = false and it now works.

However, I now experience the issue that my player body may fall through the other bodies that make up the level. I tried to play around with the mass and density as this is what I found via Google but no luck. Here is a video that shows this strange behaviour. How can I avoid it?

http://www.youtube.com/watch?v=6yneHUkF8zc

Oct 16, 2012 at 10:56 PM
Edited Oct 16, 2012 at 11:04 PM

This issue is due to the fact that you are manually setting the rotation of the center body. What happens is that the physics engine suddenly finds itself a body that has gotten a transform through unphysical terms (teleporting aka magic). So it does it's best to simulate the world and honor constraints from this moment on which might lead to unrealistic collision response like falling through geometry. 

In order to let the solver participate in the motion you should avoid manually setting Position/Rotation but instead use forces/torques. The exact amout of torque needed, though, might be hard to estimate given an articulated system with joints and different objects.

You can use trial and error to apply some torque that works.

You can add an additional revolute joint between a static object (pretending to be the background), make your center dynamic and try using a joint motor and/or limits on the revolute joint to let the solver rotate the center in a few steps. The peripheral bodies would then be linked with no rotation DOFs (prismatic joint) to the center so they rotate rigidly with it. I use this approach myself a lot but have not yet come to an ultimate recipe. Changing joint limits on the fly sometimes makes a complex simulation unstable and jittery however it is a great universal way of modifying a system according to custom logic and not bothering with too many calculations.

You can also manipulate the AngularVelocity of the center object directly each time step and try not to overshoot the target angle using some form of PID controller.

Ultimately following Occam's Razor the simplest thing will probably work best but depends on your situation and testing. Here is a tutorial that might be of help:

http://www.iforce2d.net/b2dtut/rotate-to-angle

Oct 18, 2012 at 10:10 PM

Thanks for pointing me to that article. However, I'm still stuck with the rotation as whatever I try to apply to my center (torque, impulse, force) nothing happens. I assume this issue relates to the types of bodies that I have. While the outer bodies (rectangles) are dynamic, the center is not (it's static). So applying some force to a static doesn't make sense I guess. But I tried several variations on how to set the types of the bodies but either nothing happens or the whole scene is completely messed up (in case center and outer bodies are dynamic).

Here is my current code. Please note that I just wanted to apply some torque for now without implementing your suggestions in the blog article. When the whole scene is rotating (more or less physically correct), then I will try this.

        public override void Initialize()
        {
            // basic initialization
            _bricks = new Body[level.GetLength(0), level.GetLength(1)];

            // load graphics
            _tileSheet = new Sprite(this._world.Game.Content.Load<Texture2D>("TileSheet"));

            // create the center where the cube is rotating around
            _center = new Body(this._world);
            _center.BodyType = FarseerPhysics.Dynamics.BodyType.Static;
            _center.Position = ConvertUnits.ToSimUnits(new Vector2(12 * 10, 12 * 10));
            _center.Mass = 1f;
            _center.IgnoreGravity = true;
            _center.CreateFixture(new CircleShape(ConvertUnits.ToSimUnits(5f), 5f));

            // create bodies from the level data
            for (int x = 0; x < level.GetLength(0); x++)
            {
                for (int y = 0; y < level.GetLength(1); y++)
                {
                    if (level[x, y] > 0)
                    {
                        Body body = new Body(this._world);
                        body.BodyType = FarseerPhysics.Dynamics.BodyType.Dynamic;
                        body.Position = ConvertUnits.ToSimUnits(new Vector2(x * 10, y * 10));
                        body.IgnoreGravity = true;
                        body.SleepingAllowed = false;
                        body.Friction = 1f;
                        body.Restitution = 1f;
                        body.Mass = 10f;
                        
                        body.CreateFixture(new PolygonShape(PolygonTools.CreateRectangle(ConvertUnits.ToSimUnits(5), ConvertUnits.ToSimUnits(5)), 50f));

                        JointFactory.CreateRevoluteJoint(_world, _center, body, Vector2.Zero);

                        _bricks[x, y] = body;
                    }
                }
            }
        }

        public override void Update(Microsoft.Xna.Framework.GameTime gameTime)
        {
            float angleSpeed = 0.045f;
            if (Keyboard.GetState().IsKeyDown(Keys.Right))
            {
                //_center.Rotation += angleSpeed;
                //_center.SetTransform(_center.Position, _center.Rotation + angleSpeed);
                //float totalRotation = 10.0f;
                //while (totalRotation < -180 * DEGTORAD) totalRotation += 360 * DEGTORAD;
                //while (totalRotation > 180 * DEGTORAD) totalRotation -= 360 * DEGTORAD;
                _center.ApplyTorque(10.0f);
            }
            if (Keyboard.GetState().IsKeyDown(Keys.Left))
            {
                //_center.Rotation -= angleSpeed;
            }
        }

 

 

Oct 18, 2012 at 10:51 PM

In order to respond to forces the bodies must be dynamic. Why is your scene messed up with dynamic bodies? You might be creating the joints incorrectly so the constraints fight each other. They should be already in equilibrium upon creation, if they are not something is wrong.

Oct 20, 2012 at 10:16 PM

Please have a look at http://www.youtube.com/watch?v=sguY4NcadSY

As soon as I start the simulation this happens. All bodies are now dynamic and ignore gravity (except the player ball). I don't have to apply any force on the objects (as you know, my next step is to rotate the bodies).

Here is what I have so far to create the bodies (except the player ball which is basically danymic and does not ignore gravity.

// basic initialization
_bricks = new Body[level.GetLength(0), level.GetLength(1)];

// load graphics
_tileSheet = new Sprite(this._world.Game.Content.Load<Texture2D>("TileSheet"));

// create the center where the cube is rotating around
_center = new Body(this._world);
_center.BodyType = FarseerPhysics.Dynamics.BodyType.Dynamic;
_center.Position = ConvertUnits.ToSimUnits(new Vector2(12 * 10, 12 * 10));
_center.Mass = 1f;
_center.IgnoreGravity = true;
_center.CreateFixture(new CircleShape(ConvertUnits.ToSimUnits(5f), 5f));

// create bodies from the level data
for (int x = 0; x < level.GetLength(0); x++)
{
	for (int y = 0; y < level.GetLength(1); y++)
	{
		if (level[x, y] > 0)
		{
			Body body = new Body(this._world);
			body.BodyType = FarseerPhysics.Dynamics.BodyType.Dynamic;
			body.Position = ConvertUnits.ToSimUnits(new Vector2(x * 10, y * 10));
			body.IgnoreGravity = true;
			body.SleepingAllowed = false;
			body.Friction = 1f;
			body.Restitution = 1f;
			body.Mass = 10f;
			
			body.CreateFixture(new PolygonShape(PolygonTools.CreateRectangle(ConvertUnits.ToSimUnits(5), ConvertUnits.ToSimUnits(5)), 50f));

			JointFactory.CreateRevoluteJoint(_world, _center, body, Vector2.Zero);

			_bricks[x, y] = body;
		}
	}
}

Oct 20, 2012 at 10:40 PM

It's definitely a problem with joints. Please try my suggestion from before to invert the _center and body arguments:

JointFactory.CreateRevoluteJoint(_world, body, _center, Vector2.Zero)

What happens is that the anchor parameter you give it is the local anchor for BodyB (it's local center which is supposedly Vector2.Zero). In your case BodyB are all the perimeter bodies. In my sugggestion it's the center body.

The revolute joint is also called a pin joint as in the bodies are pinned at a shared anchor point but can rotate around it. In your case the center body is pinned at local centers of each and every perimeter body. This leads to the mess - the center body has difficulty rotating when constrained at multiple pivot points. If you invert them it's going to be the other way round - each perimeter body is going to be pinned at the center. Now the center has only one pivot point (it's local center=Vector2.Zero) and can rotate around a single axis of rotation. This is what you really want to do.

Oct 20, 2012 at 10:52 PM

I tried switching body and _center in the revolute joint but still no luck. As soon as the player hits one of the bodies the mess, allthough looking different, starts again.