【问题标题】:How to LERP between 2 angles going the longest route or path in Godot如何在Godot中最长的路线或路径的2个角度之间进行LERP
【发布时间】:2021-10-22 19:30:59
【问题描述】:

这是 Godot 中两个角度之间的 LERP 代码:

 func angle_dist(from, to):
    var max_angle = PI * 2
    var difference = fmod(to - from, max_angle)
    return (fmod(2 * difference, max_angle) - difference)

 func lerp_angle(from, to, weight):
    return from + angle_dist(from, to) * weight

我想要做的不是走最短的路径,而是希望它 LERP 最长的路径,而不是用最宽的角度旋转

如上图所示,头部直接旋转到最近的角度,我希望头部旋转回另一侧,还有一个角度限制,防止头部不切实际地转动 360

【问题讨论】:

    标签: math godot


    【解决方案1】:

    最短路径

    首先,lerp_angle 是一个 GDScript 内置函数。而且它会走得更短。

    例如:

    extends Sprite
    
    var from := 0.0
    var to := 0.0
    var weight := 0.0
    
    func _process(delta: float) -> void:
        if Input.is_action_pressed("Click"):
            var mouse_position = get_viewport().get_mouse_position()
            from = rotation
            to = Vector2.UP.angle_to(mouse_position - global_position)
            weight = 0.0
    
        weight = min(weight + delta * 10, 1.0)
        rotation = lerp_angle(from, to, weight)
    

    如果你定义你自己的,你需要给它起别的名字,否则,Godot 不会使用它。例如:

    func my_lerp_angle(from, to, weight):
        var difference = fmod(to - from, TAU)
        var distance = fmod(2.0 * difference, TAU) - difference
        return from + distance * weight
    

    此实现基于 Godot 源。说服自己这等同于您的代码。请记住,TAU 是一个回合(助记词:它以 t 开头),即您的 max_angle (PI * 2)。

    您可能还对fposmodwrapf 函数感兴趣。事实上,我们可以像这样更短的实现:

    func my_lerp_angle(from, to, weight):
        var distance = wrapf(to - from, -PI, PI)
        return from + distance * weight
    

    这里wrapf 会给我们一个介于-PIPI 之间的值。或者,如果您愿意:

    func short_angle_dist(from, to):
        return wrapf(to - from, -PI, PI)
    
    func my_lerp_angle(from, to, weight):
        return from + short_angle_dist(from, to) * weight
    

    最长的路

    最长的方式就是采取最短的方式并在相反方向上添加一个旋转。也就是加上一个反号的TAU,即:

    func my_lerp_angle(from, to, weight):
        var distance = wrapf(to - from, -PI, PI)
        distance -= TAU * sign(distance)
        return from + distance * weight
    

    或者,如果您愿意:

    func complement_angle(angle):
        return angle - TAU * sign(angle)
    
    func short_angle_dist(from, to):
        return wrapf(to - from, -PI, PI)
    
    func long_angle_dist(from, to):
        return complement_angle(short_angle_dist(from, to))
    
    func my_lerp_angle(from, to, weight):
        return from + long_angle_dist(from, to) * weight
    

    自然方式

    你认为你想要最长的路。你没有。你想要的是避免角色的脖子在动画中被勒死。

    回到基本的 lerp:

    func my_lerp_angle(from, to, weight):
        var distance = to - from
        return from + distance * weight
    

    这将为您提供一个永远不会超过半圈的旋转。

    好吧,除非你的角度没有标准化,但我们可以解决这个问题:

    func my_lerp_angle(from, to, weight):
        var distance = wrapf(to, -PI, PI) - wrapf(from, -PI, PI)
        return from + distance * weight
    

    如果你想移动它从不相交的角度,那就是抵消这些角度。结果是这样的:

    func my_lerp_angle(from, to, zero, weight):
        to = wrapf(to - zero, -PI, PI)
        from = wrapf(from - zero, -PI, PI)
        var distance = to - from
        return from + distance * weight + zero
    

    其中zero 是中间位置(偏离角度半转以避免)。

    或者像这样:

    func my_lerp_angle(from, to, avoid, weight):
        to = wrapf(to - avoid + PI, -PI, PI)
        from = wrapf(from - avoid + PI, -PI, PI)
        var distance = to - from
        return from + distance * weight + avoid - PI
    

    其中avoid 是旋转不应交叉的角度。


    此外,头部的范围有限。如果目标角度 (to) 不在它的范围内,请不要放纵。像这样的:

    extends Sprite
    
    var from := 0.0
    var to := 0.0
    var weight := 0.0
    var zero := deg2rad(90)
    
    func _process(delta: float) -> void:
        if Input.is_action_pressed("Click"):
            var mouse_position = get_viewport().get_mouse_position()
            from = rotation
            to = Vector2.UP.angle_to(mouse_position - global_position)
            weight = 0.0
    
        if abs(wrapf(to - zero, -PI, PI)) > deg2rad(170):
            return
    
        weight = min(weight + delta * 10, 1.0)
        rotation = my_lerp_angle(from, to, zero, weight)
    
    func my_lerp_angle(from, to, zero, weight):
        to = wrapf(to - zero, -PI, PI)
        from = wrapf(from - zero, -PI, PI)
        var distance = to - from
        return from + distance * weight + zero
    

    总是顺时针,总是逆时针

    为了完整起见,这里是一个简单的总是顺时针的 lerp:

    func my_lerp_angle(from, to, weight):
        var distance = fposmod(to - from, TAU)
        return from + distance * weight
    

    逆时针:

    func my_lerp_angle(from, to, weight):
        var distance = fposmod(to - from, -TAU)
        return from + distance * weight
    

    附录:角度范围

    虽然上面我建议在角度超出其范围时不要放纵。如果那不是您想要的……我想您仍然想 lerp,但将其限制在一个范围内。

    要到达那里,我们需要从夹紧角度开始。但是,我们需要确保对其进行归一化(例如wrapf(angle, -PI, PI)),然后进行钳制:

    func clamp_angle(angle, min_angle, max_angle):
        return clamp(wrapf(angle, -PI, PI), min_angle, max_angle)
    

    或者只是从零开始的deviation 角度:

    func clamp_angle(angle, deviation):
        return clamp(wrapf(angle, -PI, PI), -deviation, deviation)
    

    说到zero,我们需要考虑如何处理它。一种选择是钳制输出,一旦与 lerp 代码合并,如下所示:

    func my_lerp_angle(from, to, zero, deviation, weight):
        to = wrapf(to - zero, PI, -PI)
        from = wrapf(from - zero, PI, -PI)
        var distance = to - from
        var angle = wrapf(from + distance * weight, PI, -PI)
        return clamp(angle, -deviation, deviation) + zero
    

    但是,请记住,这会截断 lerp。因此,动画将比停止在那里更快地到达极限。

    为避免这种情况,不要钳制输出,而是钳制目标角度 (to):

    func my_lerp_angle(from, to, zero, deviation, weight):
        to = clamp(wrapf(to - zero, PI, -PI), -deviation, deviation)
        from = wrapf(from - zero, PI, -PI)
        var distance = to - from
        return from + distance * weight + zero
    

    【讨论】:

    • 具有零变量的自然方式脚本不起作用,头部在抽搐并且不对鼠标进行正确旋转,我希望头部被限制在某个 FOV 角度,例如从 -170 到 170,只是一个小的 FOV 角度,头部后面的区域,就像一个间隙,这可能吗?
    • @YourDearestFan 我在方法中内联变量时犯了一个错误。查看更新的答案。
    • @YourDearestFan 我已经扩展了答案。
    • 我最终使用了不同角度的条件,感谢您提供好的和扩展的答案:) 如果出现任何问题,我会在这里评论它,顺便说一下禁用指定角度的 lerp 功能或fov 头部停在当前位置并且看起来不真实这就是为什么我实现了几个条件来使头部在鼠标快速移动到极限时达到极限
    猜你喜欢
    • 2011-03-08
    • 1970-01-01
    • 2010-11-18
    • 1970-01-01
    • 1970-01-01
    • 2019-08-07
    • 1970-01-01
    • 2011-04-26
    • 1970-01-01
    相关资源
    最近更新 更多