N阶贝塞尔曲线

Bezier Curve of N order

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

这里是:

红色方块是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。 在这里可以清楚地看到计算出了问题...还有更多建议吗?

除了这是对二项式的最低效计算之外,您应该 pre-compute 二项式序列而不是为您绘制的每个点重新计算它们,并且您可以避免大多数调用通过使用类似 Horner 的方法来计算幂函数,....

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


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

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

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)实现曲线评估,此代码中几乎没有任何东西可以 "done wrong":代码质量很高!

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

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,以实现基于初始 L=list.length 和计数器的单个 for 循环i,其中 L 减一,ii==L 时重置为 0,并在 L==1)

时终止

如果你绝对需要微积分(老实说这里不是这种情况)至少生成你的二项式系数 "efficiently":它们非常简单,可以基于 Pascal's triangle 生成,所以对于喜欢你的数学协处理器不要使用阶乘来计算它们,它们实际上可以通过将一些整数相加来生成:

lut = [      [1],           // n=0
            [1,1],          // n=1
           [1,2,1],         // n=2
          [1,3,3,1],        // n=3
         [1,4,6,4,1],       // n=4
        [1,5,10,10,5,1],    // n=5
       [1,6,15,20,15,6,1]]  // n=6

binomial(n,k):
  while(n >= lut.length):
    s = lut.length
    nextRow = []
    nextRow[0] = 1
    for(i=1, prev=s-1; i<prev; i++):
      nextRow[i] = lut[prev][i-1] + lut[prev][i]
    nextRow[s] = 1
    lut.push(nextRow)
  return lut[n][k]

(如果你这样做,请确保你记得你正在编程并且数组偏移量从 0 开始,或者在 row/column 位置 [0] 处添加虚拟值,以便你可以 "intuitively" 调用 binomial(4,2) 得到 4 选 2 而不是 5 选 3)