Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    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:
    158
    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:
    589
    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,068
    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:
    988
    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.