Delta Angles

I’m trying to find the difference in two angles and apply that difference to another angle but Eulers and Quaternions are confusing me and I’m not getting any results that are satisfactory, I have searched so many forums and googled so many variations without success.

My goal is basically to get the difference in angle between Vector3.up and the normal of a platform that is tilted, for example, 45 degrees:

3217167--246449--unityhelp.png
So I start by determining Vector3.up (Red) and raycasting down from my character and finding the normal (Blue) of the surface hit (Black), then I get the direction that my player is facing (Green) and I want the angle (Yellow) that is the same distance away from green, that blue is away from red.

The picture makes it look easy but platforms will vary in angles and it’ll change in real time, so no angles are foreknown.

Out of all the research I’ve done with angles in Unity, quaternions seem like the best idea, but I’m getting inconsistent results using functions like Quaternion.Inverse to get the delta angle of two quaternions (up and normal).

I’ve tried using Euler and got close to what I wanted, however it never yielded the correct angle off-set to my facing direction. It returned the difference in angle between up and normal, but wouldn’t correctly apply the same off-set to any other direction.

I’ve been using Debug.DrawRay to test all these angles, which raised different problems. The command uses a Vector3 as a direction so I need to convert a quaternion direction into a Vector3 for debug purposes, which seems to yield weird results most of the time. So instead I tried putting in an empty object and applying the quaternion rotation to that and then using that object’s transform.forward vector3 as the drawray direction. This worked but the whole process has left me confused as to how exactly to manipulate angles in the way in which I want without having to using a transform as a medium.

Finally, what has also confused me, is that sometimes Vector3 angles are of magnitude 0-360 and sometimes -1 to 1. I don’t really understand why this is so or how to convert between them.

Any help or advice is greatly appreciated.
Dan

Here some simple Quaternion math rules for you.

  • default Angle = Quaternion.identity
  • AngleA + AngleB = QuaternionA * QuaternionB.
  • -AngleB = Quaternion.Inverse(QuaternionB)
  • AngleB - AngleA = Quaternion.FromToRotation(QuaternionA, QuaternionB)
  • QuaternionA * Vector3.forward = the vector3 direction the quaternion is facing (for your Debug Rays)
  • Quarternion.LookRotation(lookDirection) = Quaternion rotated to face the direction of the lookDirection

Quaternion.identity is the default rotation, the zero angle, which all quaternion rotations base their rotation from (which is positive z forward positive y up).

With quaternions, multiplying is actually adding them together. But unlike simple addition, with Quaternion multiplication the order in which you multiply matters (for example turning left 90 then down 90 is a different result from turning down 90 then left 90).
Minor Factoid The reason why its using the multiplication operator instead of the addition operator is because even though it appears the Quaternions are added together, the complex numbers inside the quaternion (the x,y,z, and w components) are actually multiplied like imaginary number multiplication (i.e. (1+i) * i = i-1). its not important if you don’t understand the underlying math since the Quaternion class is written in a way so you won’t have to mess with it, but thats why.

What Inverse does is basically returns the quaternion needed to get from the current rotation back to identity, in other words it returns the negative|mirrored rotation. to help visualize how inverse works, you can have a quaternion constantly rotate in random angles and then debug ray both its current rotation and its inverse

And to find that direction a quaternion is facing you can multiply the quaternion against a vector3 direction and it will return a direction relative to the rotation. for example if you want to know which way an aircraft perceives is up simply take its rotation * Vector3.up and the Vector3 it returns is the direction it sees as up. Remember, Vector3.forward is positive blue z-axis and Vector3.right is positive red x-axis, which is important to remember when working on 2d games.

Thus to get the player facing in the desired diraction:

Quaternion upRot    = Quaternion.LookRotation(Vector3.Up);
Quaternion normRot  = Quaternion.LookRotation(surfaceNormal);
Quaternion deltaRot = Quaternion.FromToRotation(upRot,normRot);

Quaternion desiredRot = playerTransform.rotation * deltaRot;

 Debug.DrawRay(playerTransform.position,desiredRot * Vector3.forward);
//playerTransform.rotation = desiredRot;
3 Likes

I’m guessing that when you’re seeing the 1 to -1, its because you have the inspector in debug mode and you are seeing the true complex numbers stored in the quaternion… or you’ve made an Editor class that draws a Quaternion directly. For transforms, Unity has written a specialized editor class for transforms that converts the quaternion to vector3 angles because its more intuitive to us. The 0-360 you see in the Inspector is actually cached in the inspector’s editor class for transforms, not in the gameobject’s transform itself. The transform simply holds the quaternion which the inspector then converts to Vector3 angles for our viewing pleasure.

the cached values really aren’t that easily accessible and the closest you can get to convert between them is Quaternion.Euler and quaternion.eulerAngles which will return the same rotation, but can be different angles (since multiple Euler angles can match the same quaternion rotation)

/*
 * Add this script to an empty game object
 */

using UnityEngine;

public class Test : MonoBehaviour
{
    GameObject plane;
    GameObject player;

    private void Start()
    {
        player = GameObject.CreatePrimitive(PrimitiveType.Capsule);
        player.name = "Player";
        player.transform.position += Vector3.forward * 4f;
        player.transform.Rotate(new Vector3(0, 180, 0));

        plane = GameObject.CreatePrimitive(PrimitiveType.Plane);
        plane.GetComponent<Renderer>().material.color = Color.black;
        plane.name = "Plane";        
        plane.transform.Rotate(new Vector3(45, 0, 0));
    }
    void Update()
    {
        Vector3 up = Vector3.up;
        Vector3 playerFacing = player.transform.forward;
        Vector3 playerPosition = player.transform.position;

        // cast a ray from player to plane
        RaycastHit hitInfo = new RaycastHit();
        Physics.Raycast(playerPosition, playerFacing, out hitInfo);

        // i believe this is the desired angle you are seeking
        Vector3 desiredAngle = Quaternion.FromToRotation(up * -1f, hitInfo.normal) * playerFacing;

        // draw for clarity
        float lineLength = 2f;

        // draw up
        Debug.DrawRay(hitInfo.point, Vector3.up * lineLength, Color.red);

        // draw surface normal
        Debug.DrawRay(hitInfo.point, hitInfo.normal * lineLength, Color.blue);
       
        // draw player facing
        Debug.DrawLine(playerPosition, hitInfo.point, Color.green);      

        // draw desired angle
        Debug.DrawRay(hitInfo.point, desiredAngle * lineLength, Color.yellow);        
    }
}

Thank you for the replies.

I feel the answers above gave me the correct information, however it didn’t yield the results that I wanted, leading me to believe I don’t really know how to explain the problem with math or coding, so I’ll explain the concept I’m after.

Basically I’m making a small FPS, most things are from scratch. Basic movement is pretty simple, however the player isn’t ‘attached’ to an object, so if you face up or diagonally then move, you will try walk into the air and you just hop in the direction you’re looking at.

To combat this, I just got the movement force from Input and made the y component = 0, so only x/z are applied and no vertical movement. This works on horizontal surfaces but it’s not smooth if you’re on a slope. My theory on how to get around this was to get the delta angle between Vector3.up/slope.normal and then apply the y = 0 like usual, before applying the delta tilt to the entire rotation.

So in theory, if you were facing the horizon but were standing on a 45 degree slope downwards and moved forwards, you would walk down that slope as if you were facing 45 degrees downwards, if that makes sense?

I did look into other methods that may already exist that would do what I wanted, but didn’t find any. Pointing me to something that may already have the solution to my problem, would be greatly appreciated. I assume it’s a common issue with FPS thus it could be raised frequently? (I couldn’t find much information on this problem personally.)

Any and all help is greatly appreciated.
Thank you.

Personally I would usually just raycast down all the time - if I’m within a threshold of the ground and not in a ‘jumping’ mode I would just snap the Y position of the character to that of the ground.

Alternatively this might be what you’re looking for (Getting perpendicular direction vector from surface normal - Questions & Answers - Unity Discussions) - it finds the perpendicular normal which should give you the correct direction to move in.

/*
 * Add this script to an empty game object
 */

using UnityEngine;

public class Test : MonoBehaviour
{
    GameObject plane;
    GameObject player;

    private void Start()
    {
        plane = GameObject.CreatePrimitive(PrimitiveType.Plane);
        plane.name = "Plane";
        plane.GetComponent<Renderer>().material.color = Color.black;
        plane.transform.Rotate(new Vector3(45, 0, 0));
       
        player = GameObject.CreatePrimitive(PrimitiveType.Capsule);
        player.name = "Player";
        player.transform.position += Vector3.up * player.GetComponent<Renderer>().bounds.extents.y;
        player.transform.Rotate(new Vector3(0, 0, 0));
        player.AddComponent<Rigidbody>().useGravity = true;
        player.GetComponent<Rigidbody>().constraints = (RigidbodyConstraints)122;

        GameObject visor = GameObject.CreatePrimitive(PrimitiveType.Cube);
        visor.name = "Visor";
        visor.transform.parent = player.transform;
        visor.transform.localPosition = new Vector3(0f, 0.5f, 0.24f);
        visor.transform.localScale = new Vector3(0.95f, 0.25f, 0.5f);
        visor.GetComponent<Renderer>().material.color = Color.black;
    }

    void Update()
    {
        // turn player
        player.transform.Rotate(Vector3.up * Input.GetAxis("Horizontal"), Space.World);

        // move player
        player.transform.position += player.transform.forward * Input.GetAxis("Vertical") * Time.deltaTime;

        // align player with plane
        player.transform.Rotate(Quaternion.FromToRotation(player.transform.up, plane.transform.up).eulerAngles, Space.World);
    }
}

Just move the character normally using the CharacterController component. Its built to handle moving a character without the character moving through objects it’s capsule collides with. just don’t forget to also apply gravity to the move calls (since the CharacterController component could also be used for flying objects, it doesn’t account for gravity, just constrains movement via collisions).

that’ll handle slopes, stairs, walls, low ceilings, obstacles, etc. Refer to the API for documentation on its fields and methods.