经过三次贝塞尔曲线的匀速运动

3
我试图解决的问题是我似乎无法以恒定速度沿着三次贝塞尔曲线移动二维点。
我最初按照这个教程实施了此曲线:http://catlikecoding.com/unity/tutorials/curves-and-splines/,并且它效果非常好。但是,当尝试以恒定速度近似点时,它偏差非常大。
从我到目前为止所读的内容来看,您应该遍历曲线,在每个时间步长计算弧长和间隔距离。然后,将这些距离与目标距离(弧长*时间)进行比较,以找到最接近的距离。具有足够高分辨率时,这应该具有很小的误差,并足以满足我的需求。
以下是我目前拥有的代码:
点计算:
public static Vector3 GetPoint (Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t) 
{
    t = Mathf.Clamp01(t);
    float oneMinusT = 1f - t;
    return
        oneMinusT * oneMinusT * oneMinusT * p0 +
            3f * oneMinusT * oneMinusT * t * p1 +
            3f * oneMinusT * t * t * p2 +
            t * t * t * p3;
}

试图在恒定时间内计算点的失败尝试:

private float GetApproximatedTime(float u)
{
    int resolution = 100;
    float ratio = 1.0f / resolution;
    float arcLength = 0.0f;
    Vector3 p0 = SelectedSpline.Evaluate(0.0f);
    List<MultiCurveUtility.ArcTimeLength> arcTimeLengthMap = new List<MultiCurveUtility.ArcTimeLength>();
    arcTimeLengthMap.Add(new MultiCurveUtility.ArcTimeLength(0.0f, 0.0f));

    for (int i = 1; i <= resolution; i++)
    {
        float t = ((float)i) * ratio;
        Vector3 p1 = SelectedSpline.Evaluate(t);
        arcLength += Vector3.Distance(p0, p1);
        arcTimeLengthMap.Add(new MultiCurveUtility.ArcTimeLength(t, arcLength));
        p0 = p1;
    }

    float target = u * arcLength;
    int low = 0;
    int high = 1;
    float min = 0.0f;
    float max = 0.0f;

    for (int i = 1; i < arcTimeLengthMap.Count; i++)
    {
        max = arcTimeLengthMap[i].ArcLength;
        if (target > min && target < max) 
        {
            high = i;
            low = i - 1;
            break; 
        }

        min = max;
    }

    float p = (target - min) / (max - min);
    float lowTime = arcTimeLengthMap[low].ArcTime;
    float highTime = arcTimeLengthMap[high].ArcTime;
    float lowHighDelta = highTime - lowTime;
    return arcTimeLengthMap[low].ArcTime + (lowHighDelta * p);
}

在上述代码中,我传入了介于0和1之间的时间(u),以返回一个可用于评估表示x轴运动的立方贝塞尔曲线上的点的时间。
结果如下图所示:Cubic Bezier Image 红点表示仅通过使用贝塞尔公式评估原始时间返回的正常点。黄点表示传递近似的时间后的“恒定”速度位置。它看起来相当准确,直到我开始更改切线为相当夸张的情况。我还尝试增加时间间隔,但这并没有帮助什么。
总之,任何帮助都将是美妙的。我还不是非常擅长阅读公式(我确定问题来自何处),因此请使用代码示例帮助一下。 :>
谢谢!
2个回答

1

我认为这种方法没有明显的错误,因此将分辨率增加到1000或10000可能会有所帮助。

虽然这不是您要求的内容,但是如果这是一款具有大量图形和高性能要求的游戏的一部分,为了使其更加高效,您可以:

a)一步存储表中的值,然后在单独的步骤中访问它们,以便于该曲线,您不必每次重新计算100(0(0))个点。

b)不要逐个遍历值,而是使用二分查找或线性估计正确区间的下一个猜测。

此外,您需要用C语言编写它,而不是Python,但显然这是关于Python的。


谢谢。我会在优化时考虑这些事情,并研究二分查找和线性估计。另外,我不是用Python写的,而是用C#。 - doomcake

-4

好的,我似乎已经自己找到了答案。

简而言之,对于二维曲线,不要使用弧长来计算目标距离。只使用水平(x轴)长度。

快速提示:如果您的曲线可以沿着x轴向后移动,则此解决方案可能不适用于您。我的曲线不会这样。

详细说明一下,目标距离是用时间和弧长的乘积来近似确定我应该查看曲线上的哪个位置。弧长不准确,因为它考虑了y轴距离。我只关心水平移动,所以y距离是不必要的。

以下是我的更新代码:

private float GetApproximatedTime(float u)
{
int resolution = 25 * SelectedSpline.CurveCount; // Factor in additional curves.
float ratio = 1.0f / resolution;
float arcLength = 0.0f;
Vector3 p0 = SelectedSpline.Evaluate(0.0f);
List<MultiCurveUtility.ArcTimeLength> arcTimeLengthMap = new List<MultiCurveUtility.ArcTimeLength>();
arcTimeLengthMap.Add(new MultiCurveUtility.ArcTimeLength(0.0f, 0.0f));

for (int i = 1; i <= resolution; i++)
{
    float t = ((float)i) * ratio;
    Vector3 p1 = SelectedSpline.Evaluate(t);
    arcLength += Mathf.Abs(p1.x - p0.x); // Only use the x-axis delta.
    arcTimeLengthMap.Add(new MultiCurveUtility.ArcTimeLength(t, arcLength));
    p0 = p1;
}

float target = u * arcLength;
int low = 0;
int high = 1;
float min = 0.0f;
float max = 0.0f;

for (int i = 1; i < arcTimeLengthMap.Count; i++)
{
    max = arcTimeLengthMap[i].ArcLength;
    if (target > min && target < max) 
    {
        high = i;
        low = i - 1;
        break; 
    }

    min = max;
}

float p = (target - min) / (max - min);
float lowTime = arcTimeLengthMap[low].ArcTime;
float highTime = arcTimeLengthMap[high].ArcTime;
float lowHighDelta = highTime - lowTime;
return arcTimeLengthMap[low].ArcTime + (lowHighDelta * p);
}

请注意这里有两个重要的更改:

arcLength += Mathf.Abs(p1.x - p0.x);

并且

int resolution = 25 * SelectedSpline.CurveCount;

第二个更改是确保在添加曲线时不会降低分辨率。否则,您可能会注意到返回时间的准确性出现错误。我发现每条曲线的间隔为25非常准确且非常快速。话虽如此,这段代码中还有一些明显的优化可以进行,但如果您也无法解决此问题,它应该适用于您。

这是结果的屏幕截图。黄点是我使用新时间评估的点。绿点表示我的高点和低点。

IMAGE - Resulting Graph - Constant Time Over Cubic Bezier Curve


这...没有任何意义吗?如果你有一个四分之一圆曲线,严格正的x轴移动,那么使用“水平长度”就和与弧长无关的任何其他东西一样无用。你会在曲线的不同部分以完全不同的速度行驶。对于“看起来足够正确”,只需将曲线展平,在每个顶点处使用t绑定(基本上是构建LUT),然后进行下一个旅行点的二进制搜索,完成... http://pomax.github.io/bezierinfo/#tracing - Mike 'Pomax' Kamermans
这实际上就是我所做的。LUT由水平长度和t组成。但是,是的,我的本地变量被命名为弧长确实很令人困惑。因为它现在只包含x轴的长度,所以它不再是弧长了。感谢您的反馈! :) - doomcake
不仅如此,你声称水平长度没有问题,这显然是不正确的事实。像http://cubic-bezier.com/#.91,.05,.96,.27这样的单调曲线就足以说明问题:沿着曲线向右走,相同t间隔值给出的x间隔会越来越小,而相同x间隔值给出的t间隔会越来越大。你声称仅使用水平长度已经足够,这是可以*明显地*证明错误的。 - Mike 'Pomax' Kamermans
嗯,既然这对我有效,我只能假设你不理解我的意思。当然,这是我的错。将来,我会尽量在解释时更清晰明了。如果您真的有兴趣指出我做错了什么,请随意使用我提供的代码进行操作,然后也许可以根据该代码为我提供详细的解释。如果没有,无论如何感谢您的时间。 - doomcake

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接