Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

How do I clamp a quaternion?

Discussion in 'Scripting' started by Palimon, Nov 25, 2015.

Thread Status:
Not open for further replies.
  1. Palimon

    Palimon

    Joined:
    Apr 18, 2013
    Posts:
    225
    I had to switch from using a cumulative float input for my y-axis mouse input and am now just using an additive approach so I don't break other functionality:
    Code (CSharp):
    1.             float rotationYInput = Input.GetAxis("Mouse Y") * sensitivityY * Time.deltaTime;
    2.             Quaternion yQuaternion = Quaternion.AngleAxis(rotationYInput, -Vector3.right);
    3.             camera.transform.localRotation = camera.transform.localRotation * yQuaternion;
    I'm not sure how to clamp this though, to make sure my player can't look upside-down. It was easy with cumulative input where I could just check if I'm going over 360° or under 0°...but quaternions still confuse me frequently :p.
     
    DragonCoder likes this.
  2. CaoMengde777

    CaoMengde777

    Joined:
    Nov 5, 2013
    Posts:
    813
    yeah i dont really know quaternions either hehe...

    well, rotationYInput is angle in degrees right?, so clamp rotationYinput
    right? .. i think so?
     
  3. Palimon

    Palimon

    Joined:
    Apr 18, 2013
    Posts:
    225
    rotationYinput is just the mouse-input per frame. That means it'll usually be a very small number - can't exactly clamp the input :). I need to clamp the result, which is only stored in the transform quaternion.
     
  4. CaoMengde777

    CaoMengde777

    Joined:
    Nov 5, 2013
    Posts:
    813
    ?? oooh yeah okay, i probably used that method differently sometime, like a one shot rotation, i see

    hmm seems most people convert it to eulerAngles , lol thats what i do.. seems math nooby but yeah idk about quaternions lol

    hmm check out how hes limiting the angle here
    http://geheris.com/2013/04/25/unity3dfps-mouselook/
     
  5. IsGreen

    IsGreen

    Joined:
    Jan 17, 2014
    Posts:
    206
    Using Quaternion.Angle:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class ClampAngle : MonoBehaviour {
    5.  
    6.     public float maxAngle;
    7.  
    8.     public float sensitivityY;
    9.     public Transform camera;
    10.  
    11.     Quaternion center;
    12.  
    13.     void Start(){
    14.  
    15.         this.center = camera.rotation;
    16.  
    17.     }
    18.  
    19.     void Update () {
    20.  
    21.         float rotationYInput = Input.GetAxis("Mouse Y") * sensitivityY * Time.deltaTime;
    22.         Quaternion yQuaternion = Quaternion.AngleAxis(rotationYInput, -Vector3.right);
    23.         Quaternion temp = this.camera.localRotation * yQuaternion;
    24.         if(Quaternion.Angle(center, temp)<this.maxAngle) camera.localRotation = temp;
    25.  
    26.     }
    27.  
    28.  
    29. }
    Also you can use Vector3:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class ClampAngle : MonoBehaviour {
    5.  
    6.     public float maxAngle;
    7.  
    8.     public float sensitivityY;
    9.     public Transform camera;
    10.  
    11.     Vector3 center;
    12.  
    13.     void Start(){
    14.  
    15.         this.center = camera.forward;
    16.  
    17.     }
    18.  
    19.     void Update () {
    20.  
    21.         float rotationYInput = Input.GetAxis("Mouse Y") * sensitivityY * Time.deltaTime;
    22.         Quaternion yQuaternion = Quaternion.AngleAxis(rotationYInput, -Vector3.right);
    23.         this.camera.forward = ClampVector(yQuaternion * this.camera.forward, this.center, this.maxAngle);
    24.    
    25.     }
    26.  
    27.     Vector3 ClampVector(Vector3 direction, Vector3 center, float maxAngle){
    28.  
    29.  
    30.         float angle = Vector3.Angle(center, direction);
    31.         if(angle > maxAngle){
    32.  
    33.             direction.Normalize();
    34.             center.Normalize();
    35.             Vector3 rotation = (direction - center) / angle;
    36.             return (rotation * maxAngle) + center;
    37.  
    38.         }
    39.  
    40.         return direction;
    41.  
    42.     }
    43.  
    44.  
    45. }
    46.  
     
    Last edited: Nov 25, 2015
  6. r8149970788

    r8149970788

    Joined:
    Dec 12, 2016
    Posts:
    1
    This helped me alot thanks
     
  7. neoRiley

    neoRiley

    Joined:
    Dec 12, 2008
    Posts:
    148
    I found this on another post that pointed to one of the Unity sample scripts. I extended it to include all 3 axis and it's based on passing in a Vector3 that represent the values x/y/z of what you're willing to allow:

    Code (CSharp):
    1.  
    2. public static Quaternion ClampRotation(Quaternion q, Vector3 bounds)
    3. {
    4.     q.x /= q.w;
    5.     q.y /= q.w;
    6.     q.z /= q.w;
    7.     q.w = 1.0f;
    8.  
    9.     float angleX = 2.0f * Mathf.Rad2Deg * Mathf.Atan(q.x);
    10.     angleX = Mathf.Clamp(angleX, -bounds.x, bounds.x);
    11.     q.x = Mathf.Tan(0.5f * Mathf.Deg2Rad * angleX);
    12.  
    13.     float angleY = 2.0f * Mathf.Rad2Deg * Mathf.Atan(q.y);
    14.     angleY = Mathf.Clamp(angleY, -bounds.y, bounds.y);
    15.     q.y = Mathf.Tan(0.5f * Mathf.Deg2Rad * angleY);
    16.  
    17.     float angleZ = 2.0f * Mathf.Rad2Deg * Mathf.Atan(q.z);
    18.     angleZ = Mathf.Clamp(angleZ, -bounds.z, bounds.z);
    19.     q.z = Mathf.Tan(0.5f * Mathf.Deg2Rad * angleZ);
    20.  
    21.     return q;
    22. }
    23.  
    24. // ... usage
    25. void Update()
    26. {
    27.     Vector3 bounds = new Vector3(20, 30, 5); // ie: x axis has a range of -20 to 20 degrees
    28.     var dif = transform.position - target.position;
    29.     targetRot = Quaternion.LookRotation(dif);
    30.     targetRot = ClampRotation(targetRot, bounds);
    31.     transform.rotation = Quaternion.Lerp(transform.rotation, targetRot, Time.deltaTime * 0.75f);
    32. }
    33.  
     
  8. De-Panther

    De-Panther

    Joined:
    Dec 27, 2009
    Posts:
    552
    That's great, but can throw errors if q.w is zero. (e.g. Vector3 180, 0, 0 )
     
  9. koirat

    koirat

    Joined:
    Jul 7, 2012
    Posts:
    2,008
    Should we not recalculate q.w in the end since right now quaternion that is returned is not normalized ?
     
    SINePrime likes this.
  10. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    939
    For my camera, I don't clamp my x rotation, I compute the resulting x rotation (current rotation + offset rotation around X) if it exceed the 'clamped' value I expect, I don't apply the offset rotation.

    Code (CSharp):
    1.  quaternion qy = quaternion.RotateY(time * rotate.rotateAroundY * speed);
    2.  
    3.                 quaternion qx = quaternion.RotateX(time * rotate.rotateAroundX * speed);
    4.  
    5.                 float angleX = rotation.Value.ComputeXAngle();
    6.                 float angleXOffset = qx.ComputeXAngle();
    7.  
    8.                 // if we will overshoot the clamp axis roation, replace the roation offset with the identity wuaternion to avoid applying additional rotation.
    9.                 qx = math.select(qx.value, quaternion.identity.value, (angleX + angleXOffset >= xClamp && rotate.rotateAroundX > 0) || (angleX + angleXOffset <= -xClamp && rotate.rotateAroundX < 0));
    10.  
    11.                 // Old but Gold : https://forum.unity.com/threads/understanding-rotations-in-local-and-world-space-quaternions.153330/#post-1051063
    12.                 rotation.Value = math.normalize(math.mul(math.mul(qy, rotation.Value), qx)).value;
    Code (CSharp):
    1. using Unity.Mathematics;
    2.  
    3. public static class QuaternionExtensions
    4. {
    5.     public static float ComputeXAngle(this quaternion q)
    6.     {
    7.         float sinr_cosp = 2 * (q.value.w * q.value.x + q.value.y * q.value.z);
    8.         float cosr_cosp = 1 - 2 * (q.value.x * q.value.x + q.value.y * q.value.y);
    9.         return math.atan2(sinr_cosp, cosr_cosp);
    10.     }
    11. }
     
  11. Deleted User

    Deleted User

    Guest

    Whatever you do with your rotations (as Quaternions), when you change values you need to put a copy of the modified values back into the original component rotation.

    transform.rotation = Quaternion.LookAt(<someVector>);
    transform.rotation = ClampRotation(transform.rotation, minAngle, maxAngle, Axis.x);




    public enum Axis
    {
    x,
    y,
    z
    }



    public static Quaternion ClampRotation(Quaternion rotation, float min, float max, Axis axis)
    {
    Quaternion minRotation = Quaternion.Euler(min, min, min);
    Quaternion maxRotation = Quaternion.Euler(max, max, max);
    Quaternion finalRotation = rotation;
    float rot;
    float minAngle;
    float maxAngle;
    switch (axis)
    {
    case Axis.x:
    rot = Mathf.Abs(finalRotation.x);
    minAngle = Mathf.Abs(minRotation.x);
    maxAngle = Mathf.Abs(maxRotation.x);
    if (rot <= minAngle)
    {
    finalRotation.x = minRotation.x;
    }
    if (rot >= maxAngle)
    {
    finalRotation.x = maxRotation.x;
    }
    break;
    case Axis.y:
    rot = Mathf.Abs(finalRotation.y);
    minAngle = Mathf.Abs(minRotation.y);
    maxAngle = Mathf.Abs(maxRotation.y);
    if (rot <= minAngle)
    {
    finalRotation.y = minRotation.y;
    }
    if (rot >= maxAngle)
    {
    finalRotation.y = maxRotation.y;
    }
    break;
    case Axis.z:
    rot = Mathf.Abs(finalRotation.z);
    minAngle = Mathf.Abs(minRotation.z);
    maxAngle = Mathf.Abs(maxRotation.z);
    if (rot <= minAngle)
    {
    finalRotation.z = minRotation.z;
    }
    if (rot >= maxAngle)
    {
    finalRotation.z = maxRotation.z;
    }
    break;
    }
    return finalRotation;
    }
     
    Last edited by a moderator: Oct 13, 2020
  12. SINePrime

    SINePrime

    Joined:
    Jan 24, 2019
    Posts:
    54
    Huge thanks to both you guys; I managed to get this working for my needs.

    Code (CSharp):
    1.  
    2. public static Quaternion ClampRotation(Quaternion q, Vector3 bounds)
    3. {
    4.     q.x /= q.w;
    5.     q.y /= q.w;
    6.     q.z /= q.w;
    7.     q.w = 1.0f;
    8.  
    9.     float angleX = 2.0f * Mathf.Rad2Deg * Mathf.Atan(q.x);
    10.     angleX = Mathf.Clamp(angleX, -bounds.x, bounds.x);
    11.     q.x = Mathf.Tan(0.5f * Mathf.Deg2Rad * angleX);
    12.  
    13.     float angleY = 2.0f * Mathf.Rad2Deg * Mathf.Atan(q.y);
    14.     angleY = Mathf.Clamp(angleY, -bounds.y, bounds.y);
    15.     q.y = Mathf.Tan(0.5f * Mathf.Deg2Rad * angleY);
    16.  
    17.     float angleZ = 2.0f * Mathf.Rad2Deg * Mathf.Atan(q.z);
    18.     angleZ = Mathf.Clamp(angleZ, -bounds.z, bounds.z);
    19.     q.z = Mathf.Tan(0.5f * Mathf.Deg2Rad * angleZ);
    20.  
    21.     return q.normalized;
    22. }
    23.  
     
Thread Status:
Not open for further replies.