我正在尝试在我的程序中实现 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;
}
Run Code Online (Sandbox Code Playgroud)
我希望这对某人来说是清楚的!先感谢您!
更新: 我将点数减少到 3。结果如下:3 点曲线。这里清楚地可见计算有问题......还有更多建议吗?
首先简化该代码,因为这对于调试来说是不可靠的。第一步:我们不要使用微积分,除非这样做确实有好处。使用完整的二项式计算和 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
Run Code Online (Sandbox Code Playgroud)
完毕。通过排除二项式和幂,并纯粹基于迭代插值(即使用de Casteljau 算法)来实现曲线评估,这段代码中实际上没有什么可以“做错”:代码具有很高的品质!
您可以通过明确坐标、使用 array[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
Run Code Online (Sandbox Code Playgroud)
(最后的优化,虽然通常不值得,但也展开,以基于初始值和计数器while实现单个循环,其中减一并在 时重置为 0 ,并在 时终止)forL=list.lengthiLii==LL==1
如果你绝对需要微积分(老实说这里不是这种情况),至少“有效”地生成你的二项式系数:它们基于帕斯卡三角形生成起来非常简单,所以为了你的数学协处理器的热爱,不要使用阶乘来评估它们,它们实际上可以通过添加一些整数来生成:
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]
Run Code Online (Sandbox Code Playgroud)
(如果这样做,请确保您记住正在编程并且数组偏移量从 0 开始,或者在行/列位置 [0] 添加虚拟值,以便您可以“直观地”调用binomial(4,2)get 4 选择 2 而不是5 选择 3)