Limit local rotation

Hi,

i have an issue i am not able to resolve myself. I have looked around topics here, but i couldnt find exactly what i am looking for.

I have an object, which is rotating around its axis, i.e. its orbiting on place. But i need to limit the rotation when it stops. So it means the object can have local y rotation anything between 0 and 360. Lets say its current y rotation is 100. I need to limit in +/- 40 interval. I.e. 60 - 140. Thats not problem, i am clamping it using eulerAngle.y. But when the current rotation is for example 20, then the issue comes up as it the interval is -20 to 60. Which causes issues as the rotation is only 0 - 360. So when the current rotation gets to this break point, whole limit code brakes and it does some funky stuff.
Is there anyway how to prevent it from going in 0-360 interval and have negative values for rotation ? Or any other solution ?

Thanks a lot for any reply.

Luke

This is really a big problem: the limits below 0 and above 360 can’t become negative or higher than 360; to further complicate matters, if you rotate the object past 359, it returns to 0; if you rotate the object below 0, it returns to 359. But I created a function similar to Mathf.Clamp which I think may solve your problem:

function ClampAngle(angle: float, min: float, max: float): float {

    if (angle<90 || angle>270){       // if angle in the critic region...
        if (angle>180) angle -= 360;  // convert all angles to -180..+180
        if (max>180) max -= 360;
        if (min>180) min -= 360;
    }	
	angle = Mathf.Clamp(angle, min, max);
	if (angle<0) angle += 360;  // if angle negative, convert to 0..360
	return angle;
}

This function has some limitations: it uses Mathf.Clamp if angle is in the range 90 to 270, and convert everything to the -180…+180 when angle is outside this range. In any case, the result is converted to 0…360 (the range eulerAngles accepts).

You can use it this way:

  transform.eulerAngles = Vector3(0, ClampAngle(angle, ang0-40, ang0+40), 0);

its a little late, but here my approach:

  m_startRotation = transform.rotation.eulerAngles.y;
  m_minRot = m_startRotation - 90.0f;
  m_maxRot = m_startRotation + 90.0f;
  m_isTranslationNessecary = false;

  // check if rotation happens between 0/360
  if(m_minRot <= 0.0f)
   m_minRot += 360.0f;

  if(m_maxRot >= 360.0f)
   m_maxRot -= 360.0f;

  if(m_minRot > m_maxRot)
   m_isTranslationNessecary = true;

and this function checks if the obj can be rotated:

	private float ClampAngle(float angle, float min, float max, bool isTranslationNecessary)
	{
		if(!isTranslationNecessary)
		{
			if(angle < min )
				return min;

			if(angle > max)
				return max;
		}
		else
		{
			if(angle > max && angle < min)
			{
				if(min - angle > angle - max)
					return max;
				else
					return min;
			}
		}

		return angle;
	}

Here is my solution. I have a camera thar rotates around a target in X and Y axis. I’m only limiting the angle in X-Axis

public void OnDrag (PointerEventData eventData){
		float deltaX = Mathf.Clamp(eventData.delta.x,-maxPointerDelta,maxPointerDelta)*Time.smoothDeltaTime*speed;
		float deltaY = Mathf.Clamp(eventData.delta.y,-maxPointerDelta,maxPointerDelta)*Time.smoothDeltaTime*speed;
		Vector3 relativePos = cameraTransform.position - handler.position;
		Vector3 v1 = new Vector3(relativePos.x,0,relativePos.z);
		float angleX = Vector3.Angle(v1,relativePos);
		float futureangle = angleX + -deltaY;
		if(futureangle<maxAngle && futureangle>minAngle){
			cameraTransform.RotateAround(target.position,cameraTransform.right,-deltaY);
		}
		cameraTransform.RotateAround(targer.position,Vector3.up,deltaX);
	}

The following code is stable for all angles. Both methods can be static. NormalizeAngle method fix the angles above 360 and under -360 degree and the ClampAngle method normalize the angles from -180 to 180 to make the angles clamp-able. You can call this method with e.g. ClampAngle(angle, 3450, -70), ClampAngle(angle, -70, 70), ClampAngle(angle, -470, 5604). Its stable! I used this code to limit the angles of my hover vehicle, to avoid turning around it.

   /** Normalize angles to a range from -180 to 180 an then clamp the angle
     * with min and max.
     */
    protected float ClampAngle(float angle, float min, float max) {

        angle = NormalizeAngle(angle);
        if (angle > 180) {
            angle -= 360;
        } else if (angle < -180) {
            angle += 360;
        }

        min = NormalizeAngle(min);
        if (min > 180) {
            min -= 360;
        } else if (min < -180) {
            min += 360;
        }

        max = NormalizeAngle(max);
        if (max > 180) {
            max -= 360;
        } else if (max < -180) {
            max += 360;
        }

        // Aim is, convert angles to -180 until 180.
        return Mathf.Clamp(angle, min, max);
    }

    /** If angles over 360 or under 360 degree, then normalize them.
     */
    protected float NormalizeAngle(float angle) {
        while (angle > 360)
            angle -= 360;
        while (angle < 0)
            angle += 360;
        return angle;
    }

Use it in your code to limit the angles:

transform.eulerAngles = new Vector3(0, ClampAngle(angle, -40, 40), 0);

Or like this for fake tilt to terrain normal:

// Raycat down to terrain, in my case the GameManager.TERRAIN_MASK is
// the terrain mask.
Vector3 linecastTo = new Vector3(transform.position.x, transform.position.y - 100, transform.position.z);
        Vector3 linecastFrom = new Vector3(transform.position.x, transform.position.y + 20, transform.position.z);
        bool hitSomewhat = Physics.Linecast(linecastFrom, linecastTo, out hitInfo, GameManager.TERRAIN_MASK);

 float SwingSpeed = 1;
 // Difference between our up vector and the terrain normal vector.
 Quaternion tilt = Quaternion.FromToRotation(transform.up, hitInfo.normal);
 Vector3 rot = transform.eulerAngles;
// Rotation for the next frame until this value ClampAngle(tilt.eulerAngles.x, -30, 30) 
// has been achieved. Max tilt is 30, even on a 45 degree terrain mesh normal!
Quaternion qRot = Quaternion.Euler(
            Mathf.LerpAngle(rot.x,  ClampAngle(tilt.eulerAngles.x, -30, 30), 
                                          Time.deltaTime * SwingSpeed), 
            rot.y,
            Mathf.LerpAngle(rot.z, ClampAngle(tilt.eulerAngles.z, -30, 30), 
                                          Time.deltaTime * SwingSpeed));
        
    // RB is the rigidbody, smooth physical change because of the LerpAngle before.
    RB.MoveRotation(qRot);

Happy coding!

public void SetRotation(float amount) {
float clampedAngle = Mathf.Clamp(CheckAngle(transform.eulerAngles.x - amount), -15, 15);
transform.eulerAngles = new Vector3(clampedAngle, transform.eulerAngles.y, transform.eulerAngles.z);
}

 public float CheckAngle(float value)
 {
        float angle = value - 180;

        if (angle > 0)
            return angle - 180;

        return angle + 180;
    }

Why do you complicate it so much?

public static float ClampAngle(float angle, float min, float max)
{
    return Mathf.Clamp(angle % 360, min % 360, max % 360);
}

you can if <> 360 if you want to avoid the mod operation when not necessary but that’s just if you want to be picky.

Also remember this can limit to 180 degrees. For instance if you want to be able to rotate from the front to the back clockwise and then stop it and be forced to rotate anti-clockwise, it’s easy with:
ClampAngle(angle, -180, 180);
So this will allow you to rotate 360 but lock it at the back (all other methods I saw here didn’t do this).
Of course, if you don’t want to lock 360… that means you don’t want to clamp the angle so don’t call the method!