5
\$\begingroup\$

I have two long Box2D bodies, that can collide while moving one of them around with MouseJoint. I want them to try to hold their position and rotation.

box 2d illustration

  1. Blue body is moved using MouseJoint (yellow) towards the Red body. Red body has another MouseJoint - Blue can push Red, but Red will try to return to the start point thanks to the MouseJoint - this works just fine.
  2. Both bodies correctly rotate along the middle. This is still as I want. I change the MouseJoint to move the Blue away.
  3. What I need is both bodies return to their initial rotation (green arrows)
  4. Desired positions and rotations

Is there anything in Box2D that could do this automatically? The MouseJoint does that nicely for position. I need it in AndEngine (Java, Android) port, but any Box2D solution is fine.

EDIT: By automatically I mean having something I can add to the object "Paddle" without the need to change game loop. I want to encapsulate this functionality to the object itself. I already have an object Paddle that has its own UpdateHandler which is being called from the game loop. What would be much nicer is to attach some kind of "spring" joint to both left and right sides of the paddle that would automatically level the paddle. I will be exploring this option soon.

\$\endgroup\$
6
  • \$\begingroup\$ You could probably use some kind of spring, but that might affect the way the objects interact too, is that OK? \$\endgroup\$
    – House
    Oct 3, 2013 at 17:25
  • \$\begingroup\$ @Byte56 Not sure what you mean by spring. Right now I am applying opposite torque to the angle in the update thread. That works, but it's a bit hard to fine tune. \$\endgroup\$ Oct 3, 2013 at 18:01
  • \$\begingroup\$ Well, I did some research and it appears that Box2D doesn't have anything built in for what I was thinking. Basically it's a joint that acts like a spring, where its neutral position is centered. And rotating left or right are like pushing/pulling the spring. \$\endgroup\$
    – House
    Oct 3, 2013 at 18:30
  • 2
    \$\begingroup\$ Should be able to do this with a revolute joint acting as a motor with a target angle. \$\endgroup\$
    – RandyGaul
    Oct 4, 2013 at 7:01
  • 1
    \$\begingroup\$ @Byte56 I am not sure if I can have more mousejoints attached to the same body and now I don't have time to test it, but your idea of a spring actually made me think of attaching a (mouse) joint to left (and right) end of the paddle and then to an invisible body in the resting position of the left (and right) end of paddle. That might work as your spring. And it would be fully automatic. \$\endgroup\$ Oct 15, 2013 at 9:45

3 Answers 3

4
\$\begingroup\$

One way would be to apply a correctional torque that is proportional to the offset angle and set a high angular damping on the body. Let me elaborate:

First of all, you need to store the desired angle (targetAngle) that you want to rotate to. Somewhere in your game update loop you should cycle through all the bodies that tend to snap back to their original rotations and run some code like this:

double relativeAngle = body->GetAngle()-targetAngle;
if(abs(relativeAngle) >= thresholdAngle)
{
    body->ApplyTorque(diffAngle*C);  //C is a multiplier that you can tune
}

The thresholdAngle is some small angle that you specify. If the magnitude of the relativeAngle is smaller than thresholdAngle then no torque is applied. This accounts for situations where the body angle is very close to the relative angle. If that if statement were not there, miniscule torques would be applied to the body for a very long time after it has visibly settled at the target angle, performing computations that don't really affect the final result.

You will also need to SetAngularDamping on the body to slow it down as it gets closer to targetAngle. Once this is set up I recommend just playing around with C and the angularDamping. If C is too large or if the angularDamping is too small you will reach your target angle very quickly but it will rotate past it (overshoot the angle). Depending on what you want, some overshoot may be acceptable. The other extreme is when the angularDamping is too large and C is too small. This will cause the body to slow down too much and it will take a very long time to reach the target angle. If you really want some control you can also play with the body's rotational intertia (which is proportional to the density). Changing the density will also change the linear response of your blocks, so be wary when doing that. There are ways of objectively determining the ideal combination of these three parameters, but unless you know your control systems pretty well and know exactly what response you want, it's probably just easier, faster, and more fun to play around until you get something that 'feels right'.

I hope this helps!

\$\endgroup\$
2
  • \$\begingroup\$ This is what I am already doing. But it is not automatic. \$\endgroup\$ Oct 15, 2013 at 9:41
  • \$\begingroup\$ I don't think there is a way to simplify this any further. Maybe in the future there will be a box2d member function for joints that will encapsulate this behaviour, and it will run that function inside b2World::Step(). But for now I think you will just have to live with something like this in your update loop. \$\endgroup\$ Oct 15, 2013 at 13:38
2
\$\begingroup\$

As mentioned by Randy Gaul in the comments, another way to accomplish this would be to employ a b2RevoluteJoint and activate the joint motor. This is a more box2d-ish solution than the one outlined in my other answer.

If you are using a joint then you may wonder, "What body do I join to?" Unfortunately, you will need to create a new body. I would recommend a small box, that is always completely enveloped by your main body. Take a look at my crappy drawing:

enter image description here

Here, the red dot is the joint location, the green line shows the angle of the main body, and the orange line is the angle of the dummy body (in this case a small box). The left image shows the body at the target angle, and the right image shows the body tipped away from the target angle, along with the restoring torque (yellow arrow) that will be applied.

Notice that, in order for this to work, the small box must not be allowed to rotate. Also, its mass should be very small, so as to minimize its effect on the main body.

The box2d User Manual addresses your issue specifically:

You can also use joint motors to track a desired joint angle. For example:

... Game Loop Begin ...

float32 angleError = myJoint->GetJointAngle() - angleTarget;

float32 gain = 0.1f;

myJoint->SetMotorSpeed(-gain * angleError);

... Game Loop End ...

Generally your gain parameter should not be too large. Otherwise your joint may become unstable.

You can also set the maxMotorTorque at every step if you so desire. Now you still have some parameters to play with: gain,maxMotorTorque, and the mass of your body.

The advantage of this solution over my other answer is that it leaves you free to choose the angularDamping of the body. If you set a non-zero angularDamping it will still affect how it snaps back, but if you want to disable this behavior at any point during the game, you can just destroy the dummy body (which will destroy the joint by extension), and your main body won't be affected.

On the flip side, box2d needs to store and update an extra dummy body and joint for every 'snappable' body you create.

\$\endgroup\$
6
  • \$\begingroup\$ It's debatable whether this actually warrants a separate answer. I thought it did, because the implementation is different enough and the consequences are somewhat different. Feel free to disagree. \$\endgroup\$ Oct 5, 2013 at 16:44
  • \$\begingroup\$ It's a different answer, but doesn't help neither. I still have to take care of the joint and make sure to switch it left/right based on the position of the paddle. \$\endgroup\$ Oct 15, 2013 at 9:42
  • \$\begingroup\$ You have to take care of the joint for sure, but you shouldn't have to worry about which side of the target angle the paddle is currently on. If it's on one side the sign of angleError will be negative, and the motor speed will be set to a positive value. If it's on the other side angleError will be positive, and the motor speed will be set to a negative value (i.e. rotating in the opposite direction). \$\endgroup\$ Oct 15, 2013 at 13:33
  • \$\begingroup\$ Take care = add code to my game loop that will calculate something. As I said, I am already doing that. \$\endgroup\$ Oct 16, 2013 at 16:29
  • \$\begingroup\$ I apologize, I didn't really get that until now. Please see my comment to the question. \$\endgroup\$ Oct 16, 2013 at 18:25
1
\$\begingroup\$

The complete answer to make this fully automatic FOR ONE BODY is a combination of efforts of Byte56, NauticalMile and me. You will need:

  1. Your main body (paddle)
  2. 5 static bodies with no fixture.
  3. 4 "soft" distance joints with frequency > 0 and damping ratio > 0
  4. 1 "hard" distance joint

This is how to position the items:

enter image description here

The "hard" distance joint holds the paddle in place, it is like it's axis. The paddle can rotate along it, but can't move from it.

The four "soft" distance joints are springs (or bungy ropes...) that make the paddle go back to initial position. If you need to move the paddle, you have to move all six bodies. It works exactly as I wanted for one body. But it doesn't work when two paddles collide, because the paddles are fixed in place. I am still working on it, but maybe somebody will be inspired.

Simulation created in RUBE.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .