Clamping a wrapping rotation.

I am trying to clamp the rotation of turret’s barrel in an interesting way. The negative elevation of the barrel is 10 degrees, the positive elevation of the barrel is 350 degrees. 0 degrees is straight ahead. All rotations are being done through localEulerAngles as the turret rides on a body which pitches and rolls to follow terrain.

A visual example: Envision pacman with his mouth open. The 0 degree line is drawn out from the center of his mouth. The top of his mouth is 350 degrees. The minimum is 10 degrees. I want to allow a transform to only rotate through the 20 degree range which denotes the open area.

I have attempted to manipulate Mathf.Clamp and various if statements without success, the problem lies with the fact that my minimum rotation is numerically higher than the maximum as a result of the wrapping past zero.

Is there a way to write an if statement of the form “int > var > int” or anything I can do to not allow a range of 11 to 349.

If any more information is needed, or if I’ve omitted anything critical, please let me know.

Map the higher values down to negative rotations and clamp them, then map back.

Edit: now with source code.

using UnityEngine;
using System.Collections;

public class ClampAngleTest : MonoBehaviour {

public float angleIn;
public float angleOut;
public float minAngle;
public float maxAngle;

void Update () {
  angleOut = ClampAngle(angleIn, minAngle, maxAngle);
}

float ClampAngle(float angle, float from, float to) {
  if(angle > 180) angle = 360 - angle;
  angle = Mathf.Clamp(angle, from, to);
  if(angle < 0) angle = 360 + angle;

  return angle;
}

You can feed that angle directly back to eulerAngles. Try it out by changing angleIn in the Inspector while in play mode.

As you noted, EulerAngles.x is unreliable (and the docs caution against using it.) So, just use your own angle, that you say will be -10 to 10. Unity is just fine setting rotations using negative angles:

float gunDegs = 0; // will be -10 to 10 degrees, of x rotation

// user changes it, and just in case... :
gunDegs = Mathf.Clamp(gunDegs, -10, 10);

gun.localRotation = Quaternion.Euler(gunDegs, 0, 0);

Technically, to handle any angle thrown at it, you’d want to use this:

public static float ClampAngle(this float angle) {
	if(angle < 0f)
		return angle + (360f * (int) ((angle / 360f) + 1));
	else if(angle > 360f)
		return angle - (360f * (int) (angle / 360f));
	else
		return angle;
}

This returns the following:

Input:		Output:
754			34
-1000		80
50			50

Hi @Exarch ,

I wrote the following code to rotate a catapult via mouse (left and right) - so I have 2 positive values and it’s a wrapping rotation like yours.

// Get x axis from mouse
float MouseTurn = Input.GetAxis("Mouse X");
// rotate your object in the y axis (left and right)
transform.Rotate(0, MouseTurn * MouseTurnSpeed * Time.deltaTime, 0);

// Set a tracker to check if the angle is in between the values you want
bool InBetween = false;
Vector3 currentRotation = transform.localRotation.eulerAngles; Debug.Log("CURRENT ROTATION: " + currentRotation.ToString());
                        
// Because this is wrapping rotation, I have 2 positive values
if(currentRotation.y > 320.0f &&currentRotation.y <= 360.0f || currentRotation.y >= 0 && currentRotation.y <= 40.0f)
{
      InBetween = true;
}
else
{
     InBetween = false;
}
 // if its outside the range you want, force it back to the closest value within the range and set the rotation via transform.localRotation
if(!InBetween)
{
     // Closer to 40
     if(Mathf.Abs(currentRotation.y - 320.0f) > 
             Mathf.Abs(currentRotation.y - 40.0f)) 
     {
             currentRotation.y = 40.0f;
             transform.localRotation = Quaternion.Euler(currentRotation);
             InBetween = true;
     }
     // Closer to 320
     else if(Mathf.Abs(currentRotation.y - 40.0f) >      Mathf.Abs(currentRotation.y - 320.0f))
    {
             currentRotation.y = 320.0f;
             transform.localRotation = Quaternion.Euler(currentRotation);
             InBetween = true;
    }
}

I hope this helps someone!

Try this extension method (C#). It properly handles negative inputs as well as the boundaries on multiples of 360:

	public static float ClampedAngle (this float angleInDegrees)
	{
		if (angleInDegrees >= 360f) {
			return angleInDegrees - (360f * (int)(angleInDegrees / 360f));
		} else if (angleInDegrees >= 0f) {
			return angleInDegrees;
		} else {
			float f = angleInDegrees / -360f;
			int i = (int)f;
			if (f != i)
				++i;
			return angleInDegrees + (360f * i);
		}
	}

Same solution but in simplier form (without any “if”)

float clampedAngle = Mathf.Clamp((quat.eulerAngles.x+180)%360,150, 210)-180;

(my actual line of code looks like this:)

quat = 
Quaternion.Euler(Mathf.Clamp((quat.eulerAngles.x+180)%360, 150, 210)-180, 
quat.eulerAngles.y, quat.eulerAngles.z);

(i had a problem where Mathf.Clamp didn’t work with negative numbers)

So basically the constraining will be -30 and 30 degrees, but since we can’t use Mathf.Clamp(quat.eulerAngles.x,-30,30), we add 180 to all these values, and get Mathf.Clamp((quat.eulerAngles.x+180), 150, 210). But, since over 360 is bad too, we use %360 (it divides the value by 360 and returns the remainder, so 420%360 will return 60), and now it looks like ((quat.eulerAngles.x+180)%360, 150, 210). But in the end we should substract 180 that was previously added, and will get the actual angle that we need. So we get the line of code from the start! Mathf.Clamp((quat.eulerAngles.x+180)%360,150, 210)-180

That will always work with cases when you get “350” instead “-10”.
P.S.: For negative input angles just use +360