N阶Bezier曲线

时间:2017-01-15 16:22:33

标签: c# math bezier

我正在尝试在我的程序中实现N阶的贝塞尔曲线的公式。 在我看来,我已经做了一切正确但视觉效果不正确。

这是:Curve problem

红色立方体是P0,蓝色是P8。 白色立方体是构成曲线的实际点集。 橙色立方体是控制点。

我看到的是曲线末端之前有一个循环,曲线连接到最后一个(蓝色立方体)点。看起来有一个看不见的点。 另一件事是在P0和P1之间也是奇怪的事情......

有人可以帮我解决吗?

以下是我使用的代码:

    private void Update()
    {
        controlPointsCoords = ControlPoints.Select(p => p.transform.position).ToArray();

        for (int p = 0; p < PointsSet.Count; p++)
        {
            PointsSet[p].transform.position = CurveDegreeN
            (
                controlPointsCoords,
                Rt(p, PointsSet.Count)
            );
        }
    }

    private Vector3 CurveDegreeN(Vector3[] pointsCoords, float u)
    {
        float X = 0, Y = 0, Z = 0;
        float n = pointsCoords.Length - 1;

        for (int i = 0; i < pointsCoords.Length; i++)
        {
            var coef = (Factorial(n) / (Factorial((float)i) * Factorial(n - i))) * Mathf.Pow(u, i) * Mathf.Pow(1 - u, n - i);
            X += coef * pointsCoords[i].x;
            Y += coef * pointsCoords[i].y;
            Z += coef * pointsCoords[i].z;
        }

        return new Vector3(X, Y, Z);
    }

    private float Factorial(float n)
    {
        if (n == 0) return 1;

        float res = 0.0f;
        for (int i = 1; i < n; i++) res += (float)Math.Log(i);
        return (float)Math.Exp(res);
    }


    private float Rt(int current, int count)
    {
        return ((float)current - 0) / ((float)count - 0) * (1 - 0) + 0;
    }

我希望有人能清楚这一点! 提前谢谢!

更新 我将点数减少到3.结果如下:3 Points curve。 在这里可以清楚地看到计算出现问题...还有更多建议吗?

2 个答案:

答案 0 :(得分:1)

首先简化该代码,因为这对调试来说是不可靠的。第一步:不要使用微积分,除非这样做有实际好处。使用完整二项式计算和t次方通常与插值一样快(或慢)(贝塞尔曲线简单地表示为列表缩减),但插值很容易通过简单的加法和乘法实现,而二项式计算和权力是更多的工作。所以让我们几何学评估,而不是使用微积分:

function drawCurve(coords[]):
  points = []
  // the higher you make "steps", the more curve points you generate:
  for (s=0, steps=10; s<=steps; s++):
    t = s/steps
    nt = 1 - t
    list[] = coords.shallowCopy()

    // We now run our list reduction to get our on-curve
    // point at t, using de Casteljau's algorithm:
    while(list.length > 1)
      for(i = 0, e = list.length; i < e; i++):
        list[i] = nt * list[i] + t * list[i+1]
      list.pop()

    // And what's left is our on-curve point at t.
    // Beauty; push and move on to the next point.
    points.push(list[0])
  return points

完成。通过排除二项式和幂,并且纯粹基于迭代插值(即使用de Casteljau's algorithm)实现曲线评估,在这段代码中几乎没有什么可以“做错”:代码具有很高的质量! / p>

通过使用数组[3]代替3d向量类,您可以通过明确您的坐标使代码更加高效,这样您就不必在插值步骤中依赖运算符重载或函数调用slowldowns ,所以你会得到类似的东西:

function drawCurve(coords[]):
  coords = flatten(coords) // one-time convert Vector3 to flat [x,y,z] arrays
    ...
    while(list.length > 1)
      for(i = 0, e = list.length; i < e; i++):
        v1 = list[i]
        v2 = list[i+1]
        list[i][0] = nt * v1[0] + t * v2[0] // x
        list[i][1] = nt * v1[1] + t * v2[1] // y
        list[i][2] = nt * v1[2] + t * v2[2] // z
      list.pop()
    points.push(new Vector3(list[0]))
  return points

(最后的优化虽然通常不值得,但也要展开while,根据初始for和计数器{{来实现单个L=list.length循环1}},其中i递减1,Li时重置为0,并在i==L时终止

如果你绝对需要微积分(事实上并非如此),至少可以“有效地”生成二项式系数:基于Pascal's triangle生成它们非常简单,所以对于数学的热爱协处理器使用阶乘来评估它们,它们可以通过只添加一些整数来生成:

L==1

(如果你这样做,要么确保你记住你正在编程并且数组偏移从0开始,或者在行/列位置[0]添加虚拟值,这样你就可以“直观地”调用{{1}得到4选择2而不是5选择3)

答案 1 :(得分:0)

除了这是二进制的最低效计算之外,你应该预先计算二项式序列,而不是为你绘制的每个点重新计算它们,并且你可以避免大多数对幂函数的调用通过采用类似霍纳的方法,......

您的代码似乎是正确的,并且视觉与控制点一致,迫使中间部分成直线。高阶多项式插值可以在样本或控制点具有(相对)大的值变化的段处具有一些复杂的摆动。

更新:我一开始并没有仔细查看辅助函数,因为我不会使用因子的单独计算来计算二项式,但您的阶乘函数不包含因子n对于带参数n的调用,即它计算(n-1)!