开发者

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 ifs and whiles 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;
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜