Normalise orientation between 0 and 360
I'm working on a simple rotate routine which normalizes an objects rotation between 0 and 360 degrees. My C# code seems to be working but I'm not entirely happy with it. Can anyone improve on the code below making it a bit more robust?
public void Rotate(int degrees)
{
this.orientation += degrees;
开发者_StackOverflow中文版if (this.orientation < 0)
{
while (this.orientation < 0)
{
this.orientation += 360;
}
}
else if (this.orientation >= 360)
{
while (this.orientation >= 360)
{
this.orientation -= 360;
}
}
}
Use modulo arithmetic:
this.orientation += degrees;
this.orientation = this.orientation % 360;
if (this.orientation < 0)
{
this.orientation += 360;
}
This is one that normalizes to any range. Useful for normalizing between [-180,180], [0,180] or [0,360].
( it's in C++ though )
// Normalizes any number to an arbitrary range
// by assuming the range wraps around when going below min or above max
double normalize( const double value, const double start, const double end )
{
const double width = end - start ; //
const double offsetValue = value - start ; // value relative to 0
return ( offsetValue - ( floor( offsetValue / width ) * width ) ) + start ;
// + start to reset back to start of original range
}
For ints
// Normalizes any number to an arbitrary range
// by assuming the range wraps around when going below min or above max
int normalize( const int value, const int start, const int end )
{
const int width = end - start ; //
const int offsetValue = value - start ; // value relative to 0
return ( offsetValue - ( ( offsetValue / width ) * width ) ) + start ;
// + start to reset back to start of original range
}
So basically the same but without the floor. The version I personally use is a generic one that works for all numeric types and it also uses a redefined floor that does nothing in case of integral types.
This can be simplified to the following.
public void Rotate (int degrees) {
this.orientation = (this.orientation + degrees) % 360;
if (this.orientation < 0) this.orientation += 360;
}
C# follows the same rules as C and C++ and angle % 360
will give you a value between -359
and 359
for any integer. Then the second line is to ensure it's in the range 0
through 359
inclusive.
If you wanted to be "clever", you could get it down to one line:
this.orientation = (this.orientation + (degrees % 360) + 360) % 360;
This would keep it positive under all conditions but that's a nasty hack for saving one line of code, so I wouldn't do it, but I will explain it.
From degrees % 360
you will get a number between -359
and 359
. Adding 360
will modify the range to between 1
and 719
. If orientation
is already positive, adding this will guarantee it still is, and the final % 360
will bring it back to the range 0
through 359
.
At a bare minimum, you could simplify your code since the if
s and while
s can be combined. For example, the result of the conditions in these two lines is always the same, hence you don't need the surrounding if
:
if (this.orientation < 0) {
while (this.orientation < 0) {
So, to that end, you could do:
public void Rotate (int degrees) {
this.orientation += degrees;
while (this.orientation < 0) this.orientation += 360;
while (this.orientation > 359) this.orientation -= 360;
}
but I'd still go for the modulus version since it avoids potentially expensive loops.
This will be important when a user enters 360,000,000,000 for the rotation (and they will do this, believe me) and then find they have to take an early lunch while your code grinds away :-)
I prefer to avoid loops, conditionals, arbitrary offsets (3600), and Math.____()
calls:
var degrees = -123;
degrees = (degrees % 360 + 360) % 360;
// degrees: 237
formula for re-orienting circular values i.e to keep angle between 0 and 359 is:
angle + Math.ceil( -angle / 360 ) * 360
generalized formula for shifting angle orientation can be:
angle + Math.ceil( (-angle+shift) / 360 ) * 360
in which value of shift represent circular shift for e.g I want values in -179 to 180 then it can be represented as: angle + Math.ceil( (-angle-179) / 360 ) * 360
I sort of quickly mocked this up in AS3, but should work (you may need +=
on the angle)
private Number clampAngle(Number angle)
{
return (angle % 360) + (angle < 0 ? 360 : 0);
}
A bit late to the party, but it would seem many of the preferred solutions make an assumption that a negative angle will not be lower than -360°. This solution makes no such assumption, works for virtually any angle that an int can represent, and does not do excessive multiplication or division:
public void Rotate(int degrees)
{
this.orientation += degrees;
if (this.orientation >= 0)
this.orientation %= 360;
else
this.orientation = 360 - (360 - this.orientation) % 360;
}
I'd recommend making separate function for normalizing angle - it's a cleaner solution.
public static float NormalizeEulerAngle(float angle){
var normalized = angle % 360;
if(normalized < 0)
normalized += 360;
return normalized;
}
Fiddle proving that such function works as intended: https://dotnetfiddle.net/Vh4CUa
And then you can use it like here:
public void Rotate(int degrees){
orientation = NormalizeEulerAngle(orientation + degrees);
}
Add any multiple of 360 degrees between which your possible input values could be (to take it above zero), and just take the remaining with %, like this
angle = 382;
normalized_angle = (angle+3600) %360;
//result = 22
The case above can take input angles down to -3600. You can add any number (multiple of 360) crazily high that would make the input value positive first.
Usually during an animation, your previous frame/step value would probably be already normalized by the previous step, so you'll be good to go by just adding 360:
normalized_angle = (angle+360) %360;
Function that comes handy when normalizing angles (degrees) into interval [0, 360> :
float normalize_angle(float angle)
{
float k = angle;
while(k < 0.0)
k += 360.0;
while(k >= 360.0)
k -= 360.0;
return k;
}
精彩评论