【问题标题】:Normalise orientation between 0 and 360在 0 到 360 之间标准化方向
【发布时间】:2021-07-03 10:35:54
【问题描述】:

我正在研究一个简单的旋转例程,它将对象旋转标准化为 0 到 360 度。我的 C# 代码似乎可以正常工作,但我对它并不完全满意。任何人都可以改进下面的代码使其更加健壮吗?

public void Rotate(int degrees)
    {
        this.orientation += degrees;

        if (this.orientation < 0)
        {
            while (this.orientation < 0)
            {
                this.orientation += 360;
            }
        }
        else if (this.orientation >= 360)
        {
            while (this.orientation >= 360)
            {
                this.orientation -= 360;
            }
        }
    }

【问题讨论】:

  • 仅适用于偶然发现这些神奇解决方案的任何人。我已经测试了其中的几个,以获得真正的 0-360 值。使用可以在 +/- 数千范围内给出值的陀螺仪 enzuguri 的解决方案可以实现这一点! Saad Ahmed 和 QBziZ 的解决方案在输入 -10 时产生 -10;不是我期望的350。希望这对 JC 有帮助

标签: c# rotation angle


【解决方案1】:

使用模运算:

this.orientation += degrees;

this.orientation = this.orientation % 360;

if (this.orientation < 0)
{
    this.orientation += 360;
}

【讨论】:

  • 你能从 modulous 中得到否定的结果吗?浮点问题?
  • C 给你否定,我假设 C# 做同样的事情。而且,由于度数是一个整数,因此您仅限于整数旋转。没有明确说明方向是一个 int,但如果不是,那就太奇怪了。
  • 哦。这样你就可以。 -1 % 360 是负数 1。我预计是 359。我真傻。你每天都会学到一些东西。
  • (angle+3600) %360(它允许输入 10 个完整的负转)
【解决方案2】:

这是一个标准化到任何范围的方法。 用于在 [-180,180]、[0,180] 或 [0,360] 之间进行归一化。

(虽然它在 C++ 中)

// 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
}

对于整数

// 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
}

所以基本相同,但没有地板。 我个人使用的版本是适用于所有数字类型的通用版本,它还使用了重新定义的地板,在整数类型的情况下不做任何事情。

【讨论】:

  • 这个功能很神奇,一劳永逸地解决了“归一化角度问题”! :) 导致,如果你只想在 [0; 2*PI[一个角度:angle -= Math.Floor(angle/(2*Math.PI))*2*Math.PI;
  • 双打函数不能很好地处理非常小的数字。例如输入 normalise(-1.0E-15, 0.0, 360.0),结果将是 360.0。结果应该小于 360 imo。
  • 你说的是真的,但是很琐碎。您不应将浮点运算与实际的分析数学混淆。最基本的浮点数是:这是一组有限的数字,使用它们,您必须尝试以最小的误差获得解析解。更详细地说,浮点数是具有有限分子和分母集合的有理数。这一切都取决于您的应用程序域,因为没有任何功能对现实世界是完美的。就我而言,如果范围那么大,那么可以考虑这样小的数字。
【解决方案3】:

这可以简化为以下内容。

public void Rotate (int degrees) {
    this.orientation = (this.orientation + degrees) % 360;
    if (this.orientation < 0) this.orientation += 360;
}

C# 遵循与 C 和 C++ 相同的规则,angle % 360 将为您提供介于 -359359 之间的任何整数值。然后第二行是确保它在0359 的范围内。

如果你想变得“聪明”,你可以把它写成一行:

this.orientation = (this.orientation + (degrees % 360) + 360) % 360;

这将在所有情况下保持积极,但对于保存一行代码来说这是一种令人讨厌的黑客行为,所以我不会这样做,但我会解释一下。

degrees % 360 你会得到一个介于-359359 之间的数字。添加360 会将范围修改为1719 之间。如果orientation 已经是正数,添加它可以保证它仍然是正数,最后的% 360 会将它带回到0359 的范围内。

最少,您可以简化代码,因为ifs 和whiles 可以组合使用。例如,这两行条件的结果总是相同,因此你不需要周围的if

if (this.orientation < 0) {
   while (this.orientation < 0) {

因此,为此,您可以这样做:

public void Rotate (int degrees) {
    this.orientation += degrees;
    while (this.orientation <   0) this.orientation += 360;
    while (this.orientation > 359) this.orientation -= 360;
}

但我仍然会选择模数版本,因为它避免了潜在的昂贵循环。

当用户输入 360,000,000,000 进行轮换时(相信我,他们这样做)然后发现他们必须在你的代码逐渐消失时吃早午餐时,这一点很重要 :-)

【讨论】:

  • 我想说this.orientation = (this.orientation + degrees + 360) % 360; 更容易阅读。
  • @jensgram,问题在于它可以仍然为您提供-359到-1范围内的数字,例如,如果方向是0和度数是-719,给出-359。您必须先将度数标准化,然后将其强制为正数。
  • @paxdiablo,你是对的!我没想到degrees 会超出 +/-359。
【解决方案4】:

我更喜欢避免循环、条件、任意偏移 (3600) 和 Math.____() 调用:

var degrees = -123;
degrees = (degrees % 360 + 360) % 360;
// degrees: 237

【讨论】:

  • 糟糕,刚刚看到@paxdiablo 的回答提到了这个单线。
  • 这对于条件非常昂贵的着色器也非常有用。
【解决方案5】:

重新定位圆形值的公式,即保持角度在 0 到 359 之间是:

angle + Math.ceil( -angle / 360 ) * 360

平移角度方向的通用公式可以是:

angle + Math.ceil( (-angle+shift) / 360 ) * 360

其中 shift 的值表示循环移位,例如我想要 -179 到 180 之间的值,那么它可以表示为: 角度 + Math.ceil( (-angle-179) / 360 ) * 360

【讨论】:

  • 这是适用于分数和负数的最简单的答案。也适用于无循环的任意大数。
【解决方案6】:

我在 AS3 中快速模拟了这个,但应该可以工作(你可能需要 += 角度)

private Number clampAngle(Number angle)
{
    return (angle % 360) + (angle < 0 ? 360 : 0);
}

【讨论】:

    【解决方案7】:

    我建议使用单独的函数来标准化角度 - 这是一种更清洁的解决方案。

    public static float NormalizeEulerAngle(float angle){
        var normalized = angle % 360;
        if(normalized < 0)
            normalized += 360;
        return normalized;
    }
    

    小提琴证明此类功能按预期工作:https://dotnetfiddle.net/Vh4CUa

    然后你可以像这里一样使用它:

    public void Rotate(int degrees){
        orientation = NormalizeEulerAngle(orientation + degrees);
    }
    

    【讨论】:

      【解决方案8】:

      添加 360 度的任意倍数,您可能的输入值可能在其之间(使其高于零),然后将剩余的部分与 % 一起取,如下所示

      angle = 382;
      normalized_angle = (angle+3600) %360;
      //result = 22
      

      上述情况可以将输入角度降至 -3600。您可以添加任意高的数字(360 的倍数),以使输入值首先变为正数。

      通常在动画期间,您之前的帧/步值可能已经被上一步标准化,因此您只需添加 360 度即可:

      normalized_angle = (angle+360) %360;
      

      【讨论】:

        【解决方案9】:

        将角度(度)标准化为区间 [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;
        }
        

        【讨论】:

        • 不,没那么有用。如果您(不小心)在此函数中输入了一个非常大的值,它将挂在循环中。
        • 这个和问题里的方法基本一样。
        猜你喜欢
        • 1970-01-01
        • 2013-10-18
        • 1970-01-01
        • 2016-11-07
        • 1970-01-01
        • 1970-01-01
        • 2017-04-10
        • 2020-02-11
        • 2017-11-06
        相关资源
        最近更新 更多