- Home /
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.
This is the problem with relying on eulerAngles. It's one of the reasons Quaternions are so often used in game development. Is there any real reason why you can't reimplement it using Quaternions?
(oh, and why is angle an int?)
Otherwise, you can use something like this:
if(angle < 180)
{
angle = $$anonymous$$athf.Clamp(angle, 0, 10);
}
if(angle > 180)
{
angle = $$anonymous$$athf.Clamp(angle, 350, 360);
}
Then, when you increment the angle make sure you are using things like $$anonymous$$athf.LerpAngle (which manages things looping at 360).
I tried Quaternions initially, however I did not have success when I attempted to clamp rotation to one axis only. Nor did I have success attempting to make the turret look at a target point while the platform it was on was being driven all over the terrain. Further, I had a lot of trouble working out a way to set rotation speed to maximum degrees/second. Finally I had a great deal of difficulty making Quaternions work in local space. Ultimately I probably could have gotten it to work, but managed to come up with a localEulerAngles solution much quicker.
The angle is an int because I mistyped "float" :) $$anonymous$$athf.$$anonymous$$oveTowardsAngle uses floats and returns the rotation difference as a float.
// $$anonymous$$ake a rotation from the difference between current and target, then move at aimspeedvert degrees/second elerot = $$anonymous$$athf.$$anonymous$$oveTowardsAngle(currentele.x,vertaim.x,aimspeedvert * Time.deltaTime);
Your example works well but I would have to insert it where the script initially takes the aim angles from the camera, which would be a mess in the scripts current form and take a great deal of re-implimentation. I'll keep it in $$anonymous$$d should I rewrite for any reason though. (It won't work on the barrel directly, because once the first if statement is true, the second could never be)
It would be somewhat interesting to get working,
In the end, I managed to "pull my head out", draw a diagram and work from there, resulting in this:
// Negative Clamp if(elerot > 10){ if(elerot < 90){ elerot = 10;} } // Positive Clamp if(elerot < 350){ if(elerot > 240){ elerot = 350;} }
Which clamps the barrel's elevation effectively.
UnityAnswers seems to work just like real life, work on something for a few evenings, ask the question and figure it out yourself 10 $$anonymous$$utes later...
Answer by fherbst · Mar 15, 2012 at 10:05 AM
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.
Right after I clicked submit, I managed to get something working, however I like the idea of mapping angles, I'll have to see if I can find it in the scripting reference.
I can't seem to find the reference page on remapping values. If you would, can you steer me in the right direction?
using UnityEngine;
using System.Collections;
public class ClampAngleTest : $$anonymous$$onoBehaviour {
public float angleIn;
public float angleOut;
public float $$anonymous$$Angle;
public float maxAngle;
void Update () {
angleOut = ClampAngle(angleIn, $$anonymous$$Angle, maxAngle);
}
float ClampAngle(float angle, float from, float to) {
if(angle > 180) angle = 360 - angle;
angle = $$anonymous$$athf.Clamp(angle, from, to);
if(angle < 0) angle = 360 + angle;
return angle;
}
Answer by Owen-Reynolds · Mar 15, 2012 at 05:24 PM
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);
Answer by TheBeardPhantom · Dec 11, 2013 at 05:29 PM
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
Answer by ryvianstyron · Oct 22, 2015 at 08:13 AM
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 &¤tRotation.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!
Answer by Tanky · Sep 13, 2017 at 08:54 PM
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);
}
}
A method like this basically already exists. Just use $$anonymous$$athf.Repeat with a length of "360".
Also your method should not be called "clamp" as your method does not clamp anything but just "wrap" the angle. Clamping is pretty much the opposite from wrapping.
Also you don't really read the question. He want to restrict the angle to be in a "20°" range from "-10" to "+10". However since "-10" wraps to "350" that was the actual problem. So he want to allow the value in the range 350 - 360 and 0 - 10
As others have already said the best approach is to remap the value to the range -180 to 180, clamp it (-10 to 10) and map it back.
// expects angle in the range 0 to 360
// expects $$anonymous$$ and max in the range -180 to 180
// returns the clamped angle in the range 0 to 360
float Clamp(float angle, float $$anonymous$$, float max)
{
if (angle > 180f) // remap 0 - 360 --> -180 - 180
angle -= 360f;
angle = $$anonymous$$athf.Clamp(angle, $$anonymous$$, max);
if (angle < 0f) // map back to 0 - 360
angle += 360f;
return angle;
}
ps: $$anonymous$$athf.Repeat just does this:
public static float Repeat(float t, float length)
{
return t - $$anonymous$$athf.Floor(t / length) * length;
}
@Bunny83 I have a similar problem, but my object can also rotate backwards, so essentially the $$anonymous$$ and max stay the same, but after an object has been kind of mirrored to point in the opposite direction. Any advice?
Your answer
Follow this Question
Related Questions
Help clamping a Rotation 1 Answer
How to clamp Lookat? 2 Answers
Bizzare problem with limiting camera veritcal rotation 2 Answers
Limit gameobject rotation to -480 and 480 degrees? 2 Answers
Turret rotation clamp. 2 Answers