Rotating btRigidBody depending on mouse movement

Post Reply
MrOnlineCoder
Posts: 7
Joined: Sun Jan 06, 2019 10:40 pm

Rotating btRigidBody depending on mouse movement

Post by MrOnlineCoder » Mon Jan 07, 2019 11:14 pm

Hello, I am new to Bullet and need some clarification.

I am using btRigidBody for my player object, so that player would be affected by physics. It's movement is changed with setLinearVelocity() and works fine. But I can't get it's rotation working.

What I need: given yaw and pitch angles (in degrees), set rotation of the rigid body based on these values. Rotation should be set around it's local axes i.e. do not move object at all, just change it's "the direction in which it is looking".

Current code: I've tried tons of different variants, but none of them worked so far. My current code:

Code: Select all

btQuaternion q = btQuaternion(yaw, pitch, 0.0f);
btTransform trans = m_rigidBody->getWorldTransform();;

trans.setRotation(q);

m_rigidBody->setWorldTransform(trans);
Current behaviour: it works very oddly/doesn't work at all, seems that the values can't go outside of range (-1, 1).

Expected behaviour: get some sort of working FPS camera (by rotating the rigid body in the right angle)

Details: bullet 2.88 Debug, btDiscreteDynamicsWorld with gravity going down (so Y is UP)

Any help would be appreciated.
Thanks in advance,
MrOnlineCoder

User avatar
drleviathan
Posts: 487
Joined: Tue Sep 30, 2014 6:03 pm
Location: San Francisco

Re: Rotating btRigidBody depending on mouse movement

Post by drleviathan » Tue Jan 08, 2019 6:11 am

If your angles are in degrees, then you must convert to radians:

Code: Select all

const btScalar RADIANS_PER_DEGREE = PI / btScalar(180.0);
btQuaternion q = btQuaternion(yaw * RADIANS_PER_DEGREE, pitch * RADIANS_PER_DEGREE, 0.0f);
btTransform trans = m_rigidBody->getWorldTransform();;
trans.setRotation(q);
m_rigidBody->setWorldTransform(trans);
Why do nearly ALL programming languages use radians instead of degrees? Because radians are the natural unit to use for angles. With radians the trigonometric functions are simpler. Their Fourier expansions are simple and for small angle delta the cos() and sin() functions can be approximated like so:

Code: Select all

sin(alpha) = alpha
cos(alpha) = 1.0 - alpha * alpha / 2.0
If you use degrees then you must add the RADIANS_PER_DEGREE coefficient to the series expansions which would look silly.

MrOnlineCoder
Posts: 7
Joined: Sun Jan 06, 2019 10:40 pm

Re: Rotating btRigidBody depending on mouse movement

Post by MrOnlineCoder » Tue Jan 08, 2019 11:15 am

drleviathan, thanks for fast response! I thought about it, but didn't use in my code. The problem that I didn't find the requirement units of angles in documentation or wiki (despite the fact that radians are considered de-facto standard for sin/cos).

I've applied your code, but it still does not work as expected. I am logging out the rotation values and they are getting reset to 0 or -1 after they become larger that 1.0f. Also, I've tried setting constant values, but it didn't work too.

Code: Select all

btQuaternion q = btQuaternion(180 * RADIANS_PER_DEGREES, 90 * RADIANS_PER_DEGREES, roll * RADIANS_PER_DEGREES);

User avatar
drleviathan
Posts: 487
Joined: Tue Sep 30, 2014 6:03 pm
Location: San Francisco

Re: Rotating btRigidBody depending on mouse movement

Post by drleviathan » Tue Jan 08, 2019 4:28 pm

The method you use to slam the transform of the RigidBody looks correct to me, so I would guess something else is going wrong. You will have to supply more code context. Meanwhile, here are some ideas that occur to me:

(1) You slam the rotation but what is the angular velocity? Are you slamming that to zero? If you don't want the body to receive angular velocity from collisions perhaps you want to use btRigidBody::setAngularFactor(btVector3(0.0, 0.0, 0.0)).

(2) Do you have a constraint or MotionState that is also trying to manipulate the body elsewhere?

(3) You say
seems that the values can't go outside of range (-1, 1).
And also:
I am logging out the rotation values and they are getting reset to 0 or -1 after they become larger that 1.0f.
What do you mean? Are you talking about the values in the transform, the quaternion, or something else?

The the rotational 3x3 part of the 4x4 transform matrix will have normalized rows and columns, so none of those values can leave the [-1, 1] neighborhood. Meanwhile, a "quaternion rotation" is a point on the unitary hypersphere in quaternion 4D space, so yeah its components cannot leave the [-1, 1] neighborhood either.

MrOnlineCoder
Posts: 7
Joined: Sun Jan 06, 2019 10:40 pm

Re: Rotating btRigidBody depending on mouse movement

Post by MrOnlineCoder » Tue Jan 08, 2019 6:23 pm

drleviathan,

1) I've set the angular factors to 0 - it didn't help, but anyway that is a helpful hint, I've forget about it.

2) I don't use any constraints. MotionState is the default one (btDefaultMotionState)

3) Maybe I am reading rotation values incorrectly. I have a camera which needs these yaw and pitch angles to correctly render the world.

I'll try my best at supplying more context, maybe this will help:

The yaw and pitch values are provided by mouse controller, which is working fine, because I tested it before.

My Player is basically a btRigidBody in a discrete-dynamics world. I use Bullet to handle collisions and gravity. So I would like to use the btRigidBody position and rotation as data for my Camera.

So the workflow is basically:

Code: Select all

read yaw and pitch from mouse --> set them as rotation angles for btRigidBody --> on render phase, read these values from btRigidBody and pass them to camera to generate the view matrix.
You say that rotation btQuaternion's values are in the range [-1, 1]. How can I transfer this values to the radians angles?

User avatar
drleviathan
Posts: 487
Joined: Tue Sep 30, 2014 6:03 pm
Location: San Francisco

Re: Rotating btRigidBody depending on mouse movement

Post by drleviathan » Tue Jan 08, 2019 11:18 pm

A quaternion is a 4D beast with three imaginary axes and one real one. The set of all quaternions on the unitary hypersphere represents a 2-to-1 mapping to 3D rotations. I mention all that to disabuse you of the notion the quaternion has Euler angles as components. What you really wanted to know was:

You obtain the Euler angles from btQuaternion as follows:

Code: Select all

btQuaternion rotation;
...
btScalar yaw, pitch, roll;
rotation.getEulerZYX(yaw, pitch, roll);
Those angles will have units of radians.

MrOnlineCoder
Posts: 7
Joined: Sun Jan 06, 2019 10:40 pm

Re: Rotating btRigidBody depending on mouse movement

Post by MrOnlineCoder » Wed Jan 09, 2019 2:05 pm

drleviathan, thanks, that worked after some additions. Anyway:

1) Why there is no alternative getEuler() method, where the angles order are not reversed. There is setEuler(), but no getter for it. What are advantages of using ZYX form?
2) My yaw (rotation around Y axis) is controlled by mouse, so the it can be in range from -180 to 180 degrees. But btRigidBody rotation values seems to be in range from -90 to 90 (-1.57 to 1.57 radians). I found it by logging the rotation values. When it goes out of this range, it goes crazy and controls invert.
3) Also, I always pass 0.0f as my roll (Z-axes rotation) value, but the real value in the rigid body is sometimes equals PI or zero. Why it changes the value when I don't want do it?
4) Sorry for asking that many questions, (new to physics engines): how can I apply velocity to my rigid body, but take account of it's rotation (i.e. if I press forward, it will move forward in the direction it is facing). I've tried applying different forces like impulse and torque but it didn't work (that's was just a guess, they are not documented in anyway so).

Thanks for your help.

User avatar
drleviathan
Posts: 487
Joined: Tue Sep 30, 2014 6:03 pm
Location: San Francisco

Re: Rotating btRigidBody depending on mouse movement

Post by drleviathan » Wed Jan 09, 2019 5:17 pm

(1) When it comes to Euler angles my advice is: stay away from them when possible. My reasons are:

(a) There is no true "standard" for Euler angles. There are multiple ways to define them and the way they are used in game development doesn't even match with how Euler himself defined them. Therefore, if you compare how they are used in physics text books, engineering, game development, and whatever else you'll find discrepancies. Does the Bullet API use Euler angles the same way you do? If yes, then great. If not, headaches will ensue until you fully understand.

(b) Euler angles suffer from "gimbal lock" which is a zone of orientations where solving for Euler angles (however they are defined) from the rotation (either in Quaternion form or 3x3 Matrix form) becomes indeterminate because of a singularity.

(c) As a consequence of (b) there are discontinuities in Euler angle values for objects that are experiencing smooth simple rotational motion. Derivatives in Euler space are meaningless.

In some cases (such as a Character whose axis is always aligned with "up") it becomes useful to talk about yaw + pitch + roll in that order. But I suggest you call these "yaw pitch roll" instead of "Euler angles". I don't have time right now to explain how I would proceed here but perhaps I'll find time later today to write it down.

(2) Your problem here is related to (1)(a) through (1)(c).

(3) Your problem here is related to (1)(a) through (1)(c).

(4) To apply "forward" motion in the local-frame of the object you first need to transform the local velocity to world frame. btRigidBody::setLinearVelocity(v) expects v to be in the world-frame. If "forward" is along the local z-axis then you would do something like:

Code: Select all

const btVector3 forward(0.0, 0.0, 1.0);
btVector3 localVelocity = speed * forward;
btTransform trans = body->getWorldTransform();
trans.setOrigin(btVector3(0.0, 0.0, 0.0)); // remove translation part so trans is just rotation
btVector3 worldVelocity = trans * localVelcocity;
body->setLinearVelocity(worldVelocity);
Note: I did not test that code so I don't know if it compiles or is even correct according to Bullet API.

MrOnlineCoder
Posts: 7
Joined: Sun Jan 06, 2019 10:40 pm

Re: Rotating btRigidBody depending on mouse movement

Post by MrOnlineCoder » Wed Jan 09, 2019 5:23 pm

(1) Thanks for detailed explanation.
(4) Thanks for that one. Is it possible to create direction (which I will use to convert velocity to world-frame) vector directly from rotation data in btTransform? (surely I can use the one that is used by my camera class, but just asking to avoid passing additional data from one part to other)

It would be really nice and helpful I you could write some ideas or example on the better way to combine Bullet + Camera + Player movement.

Regards.

User avatar
drleviathan
Posts: 487
Joined: Tue Sep 30, 2014 6:03 pm
Location: San Francisco

Re: Rotating btRigidBody depending on mouse movement

Post by drleviathan » Thu Jan 10, 2019 5:04 pm

When it comes to orienting an upright character it really just comes down to figuring out the desired yaw and pitch. Once they are known you would compute the finalOrientation like so:

Code: Select all

// assuming the character looks down the negative z-axis
// (which is usually how they are modeled in Blender, Maya, or 3DStudio)
const btVector UP(0.0, 1.0, 0.0);
const btVector RIGHT(1.0, 0.0, 0.0);
const btVector FORWARD(0.0, 0.0, -1.0);

// if a right-handed local coordinate system is correctly defined above
// the code below should Just Work

// compute yaw and pitch here...
...

// compute finalOrientation
btQuaternion yawRotation(yaw, UP);
btQuaternion pitchRotation(pitch, RIGHT);
btQuaternion finalOrientation = yawRotation * pitchRotation; 
Rotations don't commute so how do we know the order is correct when computing finalOrientation?

By convention in the code: the quaternions operate from the left on a hypothetical vector to the right. So pretend we're operating on the FORWARD vector and we want to first pitch it up/down so the result is now off the horizontal plane. The yaw rotation then operates on the result and spins it around the vertical. If you can picture the rotations successively operating from the left on this hypothetical vector then you can be confident the order is correct.

How to compute yaw and pitch?

Suppose our character is at pointA in the world-frame and we want it to be looking at pointB, also in world-frame. We can then compute forward in world-frame: forward = pointB - pointA. From there we would compute yaw and pitch like so:

Code: Select all

const btScalar MIN_USEABLE_LENGTH_SQUARED = 1.0e-8;
btScalar forward2 = forward.length2();
if (forward2 > MIN_USEABLE_LENGTH_SQUARED) { 
    forward /= sqrt(forwar2);  // forward is now normalized
    btScalar upComponent = forward.dot(UP);
    btScalar pitch = asin(upComponent);
    btVector horizontal = forward - upComponent * UP;
    btScalar yaw = atan2(horizontal.dot(RIGHT), horizontal.dot(FORWARD));
    
    // yaw and pitch are only valid in this context, not in the else case below
    ...
} else {
    // bad data
}
Again, I wrote that from scratch and have not tested it. There may very well be an error, but it should be close.

MrOnlineCoder
Posts: 7
Joined: Sun Jan 06, 2019 10:40 pm

Re: Rotating btRigidBody depending on mouse movement

Post by MrOnlineCoder » Thu Jan 10, 2019 7:06 pm

UPDATE: I've figured out that my collision shape is a box, and rotating it by pitch value causes the box to rotate too, and it causes some problems (look at the picture attached, the lower vertex of the rectangle "traps" into the ground). You can now optionally ignore the stuff that I've written below, my question transforms: I want Bullet to handle collisions and dynamics (gravitation, movement, jumping) and raycasting for me (in a multiplayer game), that's all. What I should give Bullet to control specifically and what should I take care of by myself? I think that I will change btRigidBody velocity, but use pitch, yaw angles just for visual rotation, without passing them to bullet.

bullet rotation.png
bullet rotation.png (9.7 KiB) Viewed 424 times

Post Reply