Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Quaternion.Slerp on X and Z axis without Y axis

Tags:

c#

unity3d

Quaternion RotationI am trying to rotate the Player about X, Y, and Z axis. The Y axis should not move from last angle. Example, if I rotate 45 degree's to the left, the player should not rotate back to 0. The players X and Z axis rotate a maximum of 30 degrees, then when Input is no longer in use, settle to 0.

Through trial and error, I have finally gotten my Y angle to not Slerp back to 0. However, X and Z, still consider Y to be 0 degree's. The player is rotated (assume 45 degree's to the left), but movement along X and Z is as if Y is 0 degree's.

I've been reading articles and threads, and watching video's across multiple domains, including but not limited StackOverflow, Unity forums, Unity API, and YouTube video's.

Video of Current Game - notice the engine exhaust - X and Z never change to the new normal of the Camera view / Player Y direction.

        void Update()
    {
        if(!controller.isGrounded)
        {

            //Three degree's
            moveDirection = new Vector3(Input.GetAxis("Horizontal"), Input.GetAxis("Thrust"), Input.GetAxis("Vertical"));
            moveDirection *= speed;

            //rotate around Y-Axis
            transform.Rotate(0, Input.GetAxis("Yaw") * rotationSpeed, 0);
            float currentY = transform.eulerAngles.y; //save Y for later

            //rotation around X and Z
            float tiltAroundX = Input.GetAxis("Vertical") * tiltAngle;
            float tiltAroundZ = -1 * (Input.GetAxis("Horizontal") * tiltAngle);

            Quaternion targetRotation = Quaternion.Euler(tiltAroundX, currentY, tiltAroundZ);

            Vector3 finalRotation = Quaternion.Slerp(transform.rotation, targetRotation, smooth).eulerAngles;
            finalRotation.y = currentY; //reintroduce Y
            transform.rotation = Quaternion.Euler(finalRotation);

        controller.Move(moveDirection * Time.deltaTime);
    }
like image 403
Lee Paulison Avatar asked Jul 03 '19 21:07

Lee Paulison


People also ask

How does quaternion Slerp work?

Quaternion Slerp The effect is a rotation with uniform angular velocity around a fixed rotation axis. When the initial end point is the identity quaternion, Slerp gives a segment of a one-parameter subgroup of both the Lie group of 3D rotations, SO(3), and its universal covering group of unit quaternions, S3.

What does quaternion Lookrotation do?

Creates a rotation with the specified forward and upwards directions.

What is the W parameter in a quaternion?

A quaternion can represent a 3D rotation and is defined by 4 real numbers. x, y and z represent a vector. w is a scalar that stores the rotation around the vector.

What is quaternion Angleaxis?

Creates a rotation which rotates angle degrees around axis .


2 Answers

After further research that lead me along different avenues, I discovered that there were two issues. Both issue's revolved around the fact that the Z-axis was never being normalized to the new Y-axis degree after rotation. @Ruzihm, solved the issue of Rotation. I solved the then visible issue of movement. Which became readily visible once rotation was working properly.

In essence, the Z-axis (transform.forward) must be recalculated after any change in the Y-axis rotation (Vector3.up). Once you have the new normal (transform.forward), the movement vector needed to flattened to the plane to keep the player from diving into the surface of the world. Thank you @Ruzihm for all your assistance.

Here is the new code:

//Three degree's
moveDirection = new Vector3(Input.GetAxis("Horizontal"),
                            Input.GetAxis("Thrust"),
                            Input.GetAxis("Vertical"));

//Normalize the movement direction and flatten the Plane
moveDirection = transform.TransformDirection(moveDirection);
moveDirection = Vector3.ProjectOnPlane(moveDirection, Vector3.up);

moveDirection *= speed;

// collect inputs
float yaw = Input.GetAxis("Yaw") * rotationSpeed;
float pitch = Input.GetAxis("Vertical") * tiltAngle;
float roll = -1 * (Input.GetAxis("Horizontal") * tiltAngle);

// Get current forward direction projected to plane normal to up (horizontal plane)
Vector3 forwardCurrent = transform.forward
                        - Vector3.Dot(transform.forward, Vector3.up) * Vector3.up;
// Debug to view forwardCurrent
Debug.DrawRay(transform.position, forwardCurrent * 2, Color.white);

// create rotation based on forward
Quaternion targetRotation = Quaternion.LookRotation(forwardCurrent);

// rotate based on yaw, then pitch, then roll. 
// This order prevents changes to the projected forward direction

targetRotation = targetRotation * Quaternion.AngleAxis(yaw, Vector3.up);


// Debug to see forward after applying yaw
Debug.DrawRay(transform.position, targetRotation * Vector3.forward, Color.red);

targetRotation = targetRotation * Quaternion.AngleAxis(pitch, Vector3.right);
targetRotation = targetRotation * Quaternion.AngleAxis(roll, Vector3.forward);

transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, smooth);


controller.Move(moveDirection * Time.deltaTime);
like image 160
Lee Paulison Avatar answered Sep 29 '22 10:09

Lee Paulison


There seem to be some incorrect assumptions about the order of rotations that apply when working with Euler angles. Roll is applied, then pitch, then finally yaw. This means that keeping the same yaw then setting the roll and pitch to zero (or even just changing roll) can completely change the flattened direction you're facing.

It may help to rotate by yaw, flatten the forward direction (aka project it to a completely horizontal plane) Then create a rotation based off that (using Quaternion.LookRotation) which you can then rotate by each axis manually.

if(!controller.isGrounded)
{

    //Three degree's
    moveDirection = new Vector3(Input.GetAxis("Horizontal"), 
                                Input.GetAxis("Thrust"), 
                                Input.GetAxis("Vertical"));
    moveDirection *= speed;

    // collect inputs
    float yaw = Input.GetAxis("Yaw") * rotationSpeed;
    float pitch = Input.GetAxis("Vertical") * tiltAngle;
    float roll = -1 * (Input.GetAxis("Horizontal") * tiltAngle);

    // Get current forward direction projected to plane normal to up (horizontal plane)
    Vector3 forwardCurrent = transform.forward 
                            - Vector3.Dot(transform.forward,Vector3.up) * Vector3.up;

    // Debug to view forwardCurrent
    Debug.DrawRay(transform.location, forwardCurrent, Color.white, 0f, false);

    // create rotation based on forward
    Quaternion targetRotation = Quaternion.LookRotation(forwardCurrent);

    // rotate based on yaw, then pitch, then roll. 
    // This order prevents changes to the projected forward direction

    targetRotation = targetRotation * Quaternion.AngleAxis(yaw, Vector3.up);


    // Debug to see forward after applying yaw
    Debug.DrawRay(transform.location, targetRotation * Vector3.forward, Color.red, 0f, false);

    targetRotation = targetRotation * Quaternion.AngleAxis(pitch, Vector3.right);
    targetRotation = targetRotation  * Quaternion.AngleAxis(roll, Vector3.forward);

    transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, smooth);

    //debug new forward/up
    Debug.DrawRay(transform.location, Transform.forward, Color.blue, 0f, false);
    Debug.DrawRay(transform.location, Transform.up, Color.green, 0f, false);

    controller.Move(moveDirection * Time.deltaTime);
}

This may be considered a partial answer because being able to determine a "flattened forward" direction and reorder the process of applying component rotations is useful to answering your question but may not be enough to get the full effect you want depending on the details.

As a sidenote, you may want to consider using Quaternion.RotateTowards instead of Quaternion.Slerp if you want to ensure that it will actually reach the target rotation instead of infinitely approach it.

like image 39
Ruzihm Avatar answered Sep 29 '22 09:09

Ruzihm