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
减一,i
在 i==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)
我正在尝试在我的程序中实现 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
减一,i
在 i==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)