How to Rotate a Quaternion with 2Nd Quaternion on Its Local or World Axes Without Using Transform.Rotate

How do I rotate a Quaternion with 2nd Quaternion on its local or world axes without using transform.Rotate?

Use the Quaternion operator *.

In Unity, when you multiply two quaternions, it applies the second quaternion (expressed in global axes) to the first quaternion along the local axes produced after the first Quaternion).

So, if you want to do the equivalent of Rotate(q2, Space.Self), where q1 is the transform's original rotation, and q3 is the transform's new rotation, you want Quaternion q3 = q1 * q2.


What do you do if you want to apply q2 to q1 along the axes before q1 applies (i.e., along the global axes)? Well, it turns out that is equivalent to applying q1 along the local axes of q2.

As an example, applying q2 along the global axes of q1 rotates its back to face down and its top to face right. Consider that that applying q1 along the local axes of p2 also rotates its back to face down and its top to face right. They result in the same orientation!

So what does that mean? If you want to apply q2 to q1 along the axes before q1 applies, then do Quaternion q3 = q2 * q1.

This is the equivalent of doing Rotate(q2, Space.World) where q1 is the transform's original rotation, and q3 is the new rotation.


Unlike Rotate, you can use this operator to rotate an object relative to its parent's axes! If you do transform.localRotation = q2 * transform.localRotation, you will be rotating the object relative to the axis of the parent.

Problem to rotate Quaternion on two axes with reference to a plane in Unity

In general you will want to separate the Y and X rotation the following way:

  • rotate around Y in global space
  • rotate around X in local space.

Whether you do this purely by logic or using a parent-child hierarchy is up to you ... second is easier to understand in my opinion.



Using the Hierarchy

There you would simply have a hierarchy like e.g.

CameraAnchor
|--Camera

and have a script on the CameraAnchor like e.g.

public class CameraAnchorController : MonoBehaviour
{
public float cameraSmoothness = 5f;

private Quaternion targetGlobalRotation;
private Quaternion targetLocalRotation;

private Transform child;

private void Start()
{
child = transform.GetChild(0);

targetGlobalRotation = transform.rotation;
targetLocalRotation = transform.GetChild(0).localRotation;
}

private void Update()
{
targetGlobalRotation *= Quaternion.Euler(Vector3.up * Input.GetAxis("Mouse X"));
targetLocalRotation *= Quaternion.Euler(Vector3.right * -Input.GetAxis("Mouse Y"));

var lerpFactor = cameraSmoothness * Time.deltaTime;

transform.rotation = Quaternion.Lerp(transform.rotation, targetGlobalRotation, lerpFactor);
child.localRotation = Quaternion.Lerp(child.localRotation, targetLocalRotation, lerpFactor);
}
}


Using Quaternion math

As said you could do the same thing in one single object but I would still keep both rotations separated:

public class CameraController : MonoBehaviour
{
public float cameraSmoothness = 5f;

private Quaternion targetGlobalRotation;
private Quaternion targetLocalRotation = Quaternion.identity;

private void Start()
{
targetGlobalRotation = transform.rotation;
}

private void Update()
{
targetGlobalRotation *= Quaternion.Euler(Vector3.up * Input.GetAxis("Mouse X"));
targetLocalRotation *= Quaternion.Euler(Vector3.right * -Input.GetAxis("Mouse Y"));

transform.rotation = Quaternion.Lerp(transform.rotation, targetGlobalRotation * targetLocalRotation, cameraSmoothness * Time.deltaTime);
}
}

Now why does this work when we are still using Vector3.right now?

By doing targetGlobalRotation * targetLocalRotation we first rotate around the global Y axis and then apply the rotation on the X axis based on this already applied rotation -> it is now an additional local rotation!

Rotating one quaternion via the rotation of another

At the start of each frame, cache the rotation and the left hand -> right hand direction then use Quat B * Quat A to rotate A by B in world space, or by using transform.Rotate with Space.World.

IEnumerator DoRotate()
{
Vector3 previousLeftHand = new Vector3(Left.x, Left.y, Left.z);
Vector3 previousRightHand = new Vector3(Right.x, Right.y, Right.z);

Vector3 previousFromLeftToRight = previousRightHand - previousLeftHand;

while (!doneFlag)
{
yield return null;

Vector3 newLeftHand = new Vector3(Left.x, Left.y, Left.z);
Vector3 newRightHand = new Vector3(Right.x, Right.y, Right.z);

Vector3 newFromLeftToRight = newRightHand - newLeftHand;

Quaternion deltaRotationWorld = Quaternion.FromToRotation(
previousFromLeftToRight, newFromLeftToRight);

_myObject.transform.rotation = deltaRotationWorld * _myObject.transform.rotation;
// or _myObject.transform.Rotate(deltaRotationWorld, Space.World);

previousFromLeftToRight = newFromLeftToRight;

}
}

You could use this technique except only caching a start rotation & the start hand positions, but it would often result in unexpected rotation along the local axis going between the hands. Using per-frame deltas minimizes that.

Quaternion rotations, trying to rotate an object around his axis

My first suggestion would be to not define your Quaternion rotation using Euler angles:

Here are some links that explain it better than I can:

http://bitsquid.blogspot.ca/2013/03/what-is-gimbal-lock-and-why-do-we-still.html

https://mathoverflow.net/questions/95902/the-gimbal-lock-shows-up-in-my-quaternions

http://answers.unity3d.com/questions/573035/avoiding-gimbal-lock.html

I recommend defining your rotations as a single axis and angle and writing your class interface to follow that, here are some suggestions:

Vector3 operator* ( const Vector3& vec ) const;        // rotate a vector
Quaternion operator* ( const Quaternion& quat ) const; // concatenate
void set( const Vector3& axis, float angle ); // set quaternion
void getAxisAngle( Vector3* axis, float* angle ); // extract axis and angle

As for applying a local axis rotation using quaternions you could simply transform the local axis into world space and apply the transformation that way.

I'm assuming you store the characters transform as a rotation quaternion, translation vector, and scale vector/scalar. In this case you simply apply the characters rotation quaternion to the local axis to bring it into world space, then use that world space axis to build and apply the rotation as you normally would.

You have your characters current rotation stored as a quaternion:

Quaternion characterQuaternion;

Then you have the local rotation you want to apply:

Vector3 localAxis;
float angle;

You need to transform the local axis, into world space, then build a quaternion from that and apply it to your characters quaternion:

Vector3 worldAxis = characterQuaternion * localAxis;       // transform local axis to world coordinate system   
Quaternion worldRotation( worldAxis, angle ); // build quaternion
characterQuaternion = worldRotation * characterQuaternion; // apply the rotation

For example, if you wanted to apply a 'roll' rotation of with an angle of 45 units (degree or radian depending on your rotation methods ) to your character:

Sample Image

localAxis = Vector3(1,0,0);
angle = 45;

What is the correct way to rotate a quaternion from its current rotation a certain angle?

I believe I have figured it out.

  1. You need to create a quaternion and rotate it to your delta values, do not manipulate quaternion values directly (e.g. use rotationX function instead).

  2. To add quaternions together you multiply them.

  3. Finally you need to use the equation:

    delta quaternion * quaternion to rotate * inverse of delta quaternion

Code:

    public void rotate(float x, float y, float z) {
//Use modulus to fix values to below 360 then convert values to radians
float newX = (float) Math.toRadians(x % 360);
float newY = (float) Math.toRadians(y % 360);
float newZ = (float) Math.toRadians(z % 360);

//Create a quaternion with the delta rotation values
Quaternionf rotationDelta = new Quaternionf();
rotationDelta.rotationXYZ(newX, newY, newZ);

//Calculate the inverse of the delta quaternion
Quaternionf conjugate = rotationDelta.conjugate();

//Multiply this transform by the rotation delta quaternion and its inverse
rotation.mul(rotationDelta).mul(conjugate);
}

Unity, Quaternion.Euler, rotate only around Y axis

Rather use transform.Rotate

private IEnumerator SmoothlyRotateCamera()
{
var duration = 0.3f;

// in angles per second
var rotationSpeed = 180f / duration;

float rotated = 0;

do
{
var angle = Mathf.Min(180.0f - rotated, rotationSpeed * Time.deltaTime);
var rotation = Vector3.up * angle;

rotated += angle;

// depends which Y axis you want to rotate around here
// pass Space.World to rotate around the global Y axis instead of the local one
// if left out it rotates around its transform.up vector instead
transform.Rotate(rotation, Space.World);

yield return null;
} while (rotated < 180f);

isRotating = false;

// your last yield return is redundant
}

I added the Mathf.Min to make sure there is no overshooting. As you can see it lands exactly on 180° and is smooth in time (you don't need necessarily Lerp for that only use Time.deltaTime).

In order to show the effect I only changed the duration to 3 instead of 0.3 (which is to fast to notice it).

Rotating around global Y using transform.Rotate(rotation, Space.World);

Sample Image

Rotating around local Y using transform.Rotate(rotation);

Sample Image

how to chagne transform.eulerAngles to quaternion

If you wanted to use quaternions to do the same thing as your transform.eulerAngles += new Vector3(-yRotation, xRotation, 0); method, you would have to apply the Y axis rotation in global axes, and then the X axis rotation in local axis:

if (Input.GetMouseButton(0))
{
xMovement = Input.GetAxis("Mouse X");
yMovement = Input.GetAxis("Mouse Y");

Quaternion yRot = Quaternion.Euler(0f, xMovement, 0f);
Quaternion xRot = Quaternion.Euler(-yMovement, 0f, 0f);

transform.rotation = yRot * transform.rotation * xRot;
// ...

See this question for more information on how to use quaternion multiplication.



Related Topics



Leave a reply



Submit