代码之家  ›  专栏  ›  技术社区  ›  Rob Pranay Rana

旋转插值

  •  25
  • Rob Pranay Rana  · 技术社区  · 15 年前

    注意:为了简单起见,我将用度数来表示这个问题,弧度,度数,不同的零方向角,问题本质上是相同的。

    有人对旋转插值背后的代码有什么想法吗?给定一个线性插值函数:LERP(From,To,Amount),其中Amount是0…1,它返回一个介于From和To之间的值(按Amount)。我如何将这个相同的函数应用于0到360度之间的旋转插值?考虑到0和360度之外不应返回度数。

    给定此单位圆的度数:

    其中,从=45到=315,算法应采用角度的最短路径,即,它应通过零,到360,然后到315-而不是一路旋转90,180,270到315。

    有没有一个很好的方法来实现这个目标?或者只是一堆糟糕的if()块?我是否遗漏了一些理解良好的标准方法? 如有任何帮助,我们将不胜感激。

    有人对旋转插值背后的代码有什么想法吗?给定一个线性插值函数:LERP(From,To,Amount),其中Amount是0…1,它返回一个介于From和To之间的值(按Amount)。我如何将这个相同的函数应用于0到360度之间的旋转插值?考虑到度数不应返回到0和360之外。

    给定此单位圆的度数:

    Unit Circle

    当从=45到=315时,算法应以最短路径到达角度,即,它应经过零、360、315,而不是90、180、270到315。

    有没有一个很好的方法来实现这个目标?或者只是一堆糟糕的if()块?我是否遗漏了一些理解良好的标准方法? 任何帮助都将不胜感激。

    7 回复  |  直到 6 年前
        1
  •  31
  •   frodo2975    6 年前

    我知道这是2年前的事,但我最近一直在寻找同样的问题,如果没有在这里发布IFS,我看不到一个优雅的解决方案,所以这里是:

        shortest_angle=((((end - start) % 360) + 540) % 360) - 180;
        return start + (shortest_angle * amount) % 360;
    

    就是这样

    PS:当然,%是模,最短角是保持整个内插角的变量。

        2
  •  13
  •   waterproof    6 年前

    抱歉,这有点复杂,这里有一个更简洁的版本:

        public static float LerpDegrees(float start, float end, float amount)
        {
            float difference = Math.Abs(end - start);
            if (difference > 180)
            {
                // We need to add on to one of the values.
                if (end > start)
                {
                    // We'll add it on to start...
                    start += 360;
                }
                else
                {
                    // Add it on to end.
                    end += 360;
                }
            }
    
            // Interpolate it.
            float value = (start + ((end - start) * amount));
    
            // Wrap it..
            float rangeZero = 360;
    
            if (value >= 0 && value <= 360)
                return value;
    
            return (value % rangeZero);
        }
    

    有人有更优化的版本吗?

        3
  •  7
  •   Paul Colby    10 年前

    我认为更好的方法是插入sin和cos,因为它们不受形式被乘法定义的影响。设w=“amount”使w=0为角度a,w=1为角度b。然后

    CS = (1-w)*cos(A) + w*cos(B);
    SN = (1-w)*sin(A) + w*sin(B);
    C = atan2(SN,CS);
    

    一个人必须根据需要转换成弧度和度数。还必须调整分支。对于atan2,c返回到范围-pi到pi。如果你想要0到2pi,那就把pi加到c上。

        4
  •  3
  •   waterproof    6 年前

    注意:使用C代码

    在我脑子里翻找了一番之后,我想到的就是这个。 基本上,前提是在最后一分钟进行0-360包裹。在内部处理0-360以外的值,然后在函数请求值时将其包装在0-360内。

    在选择起点和终点的点上,执行以下操作:

    float difference = Math.Abs(end - start);
    if (difference > 180)
    {
        // We need to add on to one of the values.
        if (end > start)
        {
            // We'll add it on to start...
            start += 360;
        }
        else
        {
            // Add it on to end.
            end += 360;
        }
    }
    

    这将为您提供实际的开始和结束值,可能在0-360之外…

    我们有一个wrap函数来确保值在0到360之间…

    public static float Wrap(float value, float lower, float upper)
    {
        float rangeZero = upper - lower;
    
        if (value >= lower && value <= upper)
            return value;
    
        return (value % rangeZero) + lower;
    }
    

    然后,此时您从函数请求当前值:

    return Wrap(Lerp(start, end, amount), 0, 360);
    

    这几乎肯定不是解决这个问题的最佳方案,但是它看起来确实能始终如一地工作。如果有人有更好的方法来做这件事,那就太好了。

        5
  •  1
  •   phkahler    15 年前

    我处理角度的首选方法是使用每转2次的单位。例如,它使用16位有符号整数来表示-180到+180度,您可以简单地(从到)/num_步骤来进行插值。当二进制值从360到0溢出时,加和减角度总是有效的。

    在这种情况下,您可能想做的是数学模块360。所以角度差计算为(从到)%360。在其他问题中,仍然存在一些符号问题。

        6
  •  1
  •   Adam Adam    10 年前

    我想重写我的答案,以便更好地解释回答问题。我用Excel计算公式,用学位计算单位。

    为简单起见, B 是两个值中较大的一个,并且 A 是两个值中的较小值。你可以使用 MAX() MIN() 稍后分别在您的解决方案中。

    第一部分-去哪条路?

    我们首先要做的是计算出我们要在哪个方向进行计算,顺时针或逆时针。我们使用 IF() 声明:

    IF( (B-A)<=180, (Clockwise_Formula), (AntiClockwise_Formula) )
    

    上面的公式检查是否从 (与顺时针方向相同 )小于或等于180度。如果不是的话,往另一个方向走会短一些。

    为了检查这项工作:90-45=45(小于或等于180)使if语句为真,因此顺时针方向较短,而315-45=270(大于180)使if语句为假,因此逆时针公式较短。

    第2部分-顺时针公式

    现在你要插入 N 时代之间 顺时针或逆时针。顺时针公式比较简单。

    Clockwise_Formula: ((B-A)/N*S)+A
    

    在哪里? S 是从1开始到n-1结束的插入次数的计数(如果 S = N ,你的答案是 )

    例子: =90, =270, n = 4

    S=1:     ((270-90)/4*1)+90 = 135
    S=2:     ((270-90)/4*2)+90 = 180
    S=3:     ((270-90)/4*3)+90 = 225
    

    第3部分-逆时针公式

    逆时针公式会更复杂一点,因为我们需要逆时针穿过360度角。我能想到的最简单的方法是在 ,然后使用 MOD(FORMULA,VALUE) 功能。

    你还得换衣服 因为 现在是最小的数字。(听起来可能有点困惑,但它确实有效!)

    (Unmodulated) AntiClockwise_Formula: (((A+360)-B)/N*S)+B
    

    例子: =60, =300, n = 4

    S=1:     (((60+360)-300)/4*1)+300 = 330
    S=2:     (((60+360)-300)/4*2)+300 = 360
    S=3:     (((60+360)-300)/4*3)+300 = 390
    

    第4部分-将答案限制在0到360之间

    看看有时候(但并非总是)答案会超过360吗?这就是把逆时针的公式包装在 MOD() 功能进入:

    AntiClockwise_Formula: MOD((((A+360)-B)/N*S)+B,360)
    

    调制第3部分中使用的示例将为您提供:

    S=1:     330
    S=2:     0
    S=3:     30
    

    第5部分-整合

    将第1-4部分的所有元素组合在一起,答案是:

    IF((B-A)<=180,((B-A)/N*S)+A,MOD((((A+360)-B)/N*S)+B,360))
    

    哪里:

    =两个值中的较小值(可以用min()替换a)

    =两个值中的较大值(可以用max()替换b)

    n =要执行的插值数目(例如,2是一半,3是三分之一等)

    S =最大n-1的增量计数(解释见第2部分)

        7
  •  0
  •   Richard Keene    6 年前

    我对学位问题的解决方案。在我的vartracker类中

        @classmethod
    def shortest_angle(cls, start: float, end: float, amount: float):
        """ Find shortest angle change around circle from start to end, the return
            fractional part by amount.
        VarTracker.shortest_angle(10, 30, 0.1) --> 2.0
        VarTracker.shortest_angle(30, 10, 0.1) --> -2.0
        VarTracker.shortest_angle(350, 30, 0.1) --> 4.0
        VarTracker.shortest_angle(350, 30, 0.8) --> 32.0
        VarTracker.shortest_angle(30, 350, 0.5) --> -20.0
        VarTracker.shortest_angle(170, 190, 0.1) --> 2.0
        VarTracker.shortest_angle(10, 310, 0.5) --> -30.0
        """
        sa = ((((end - start) % 360) + 540) % 360) - 180;
        return sa * amount;
    
    @classmethod
    def slerp(cls, current: float, target: float, amount: float):
        """ Return the new value if spherical linear interpolation from current toward target, by amount, all in degrees.
        This method uses abs(amount) so sign of amount is ignored.
        current and target determine the direction of the lerp.
        Wraps around 360 to 0 correctly.
    
        Lerp from 10 degrees toward 30 degrees by 3 degrees
        VarTracker.slerp(10, 30, 3.0) --> 13.0
        Ignores sign of amount
        VarTracker.slerp(10, 30, -3.0) --> 13.0
        VarTracker.slerp(30, 10, 3.0) --> 27.0
        Wraps around 360 correctly
        VarTracker.slerp(350, 30, 6) --> 356.0
        VarTracker.slerp(350, 30, 12) --> 2.0
        VarTracker.slerp(30, 350, -35) --> 355.0
        a = VarTracker.slerp(30, 3140, -35) --> 355.0
        VarTracker.slerp(170, 190, 2) --> 172.0
        VarTracker.slerp(10, 310, 12) --> 358.0
        Wraps over 0 degrees correctly
        VarTracker.slerp(-10, 10, 3) --> 353.0
        VarTracker.slerp(10, -10, 12) --> 358
        """
        a = VarTracker.shortest_angle(current, target, 1.0)
        diff = target - current
        if np.abs(amount) > np.abs(diff):
            amount = diff
        if a < 0:
            amount = -np.abs(amount)
        else:
            amount = np.abs(amount)
        ret = current + amount
        while ret < 0:
            ret = ret + 360
        ret = ret % 360
        return ret