C# Hermite曲线的子曲线不适合原始曲线
我试图在一个项目中使用Hermite曲线,对数学的实际工作原理的理解是有限的,我遇到了一些我不理解的行为。我已经用下面的最小代码示例演示了我的混淆,但基本上我希望沿着hermite曲线的次曲线(即使用原始曲线上的点和切线定义的次曲线)的点能够拟合原始曲线,但这似乎是错误的 下面的c#代码定义了一个Hermite曲线类,该类提供了用于计算沿曲线以一定比例的点的位置和切线的函数。我从互联网上的其他地方复制/粘贴了这两个函数的数学 然后,一个小测试工具执行我期望成功但没有成功的测试。我不清楚我的代码中是否有错误,我的数学中是否有错误,或者我是否误解了Hermite曲线的工作原理,并且这个测试实际上不应该通过 任何见解都值得赞赏C# Hermite曲线的子曲线不适合原始曲线,c#,spline,hermite,C#,Spline,Hermite,我试图在一个项目中使用Hermite曲线,对数学的实际工作原理的理解是有限的,我遇到了一些我不理解的行为。我已经用下面的最小代码示例演示了我的混淆,但基本上我希望沿着hermite曲线的次曲线(即使用原始曲线上的点和切线定义的次曲线)的点能够拟合原始曲线,但这似乎是错误的 下面的c#代码定义了一个Hermite曲线类,该类提供了用于计算沿曲线以一定比例的点的位置和切线的函数。我从互联网上的其他地方复制/粘贴了这两个函数的数学 然后,一个小测试工具执行我期望成功但没有成功的测试。我不清楚我的代码中
using System;
using System.Numerics;
class Program
{
class HermiteCurve
{
Vector2 start;
Vector2 startTangent;
Vector2 end;
Vector2 endTangent;
public HermiteCurve(Vector2 start, Vector2 startTangent, Vector2 end, Vector2 endTangent)
{
this.start = start;
this.startTangent = startTangent;
this.end = end;
this.endTangent = endTangent;
}
public Vector2 GetPoint(float t)
{
var t2 = t * t;
var t3 = t2 * t;
return
( 2f*t3 - 3f*t2 + 1f) * start +
(-2f*t3 + 3f*t2) * end +
(t3 - 2f*t2 + t) * startTangent +
(t3 - t2) * endTangent;
}
public Vector2 GetTangent(float t)
{
var t2 = t * t;
return
(6f*t2 - 6*t) * start +
(-6f*t2 + 6*t) * end +
(3f*t2 - 4f*t + 1) * startTangent +
(3f*t2 - 2f*t) * endTangent;
}
}
static void Main(string[] args)
{
Vector2 p0 = new Vector2(0, 0);
Vector2 m0 = new Vector2(1, 0);
Vector2 p1 = new Vector2(1, 1);
Vector2 m1 = new Vector2(0, 1);
HermiteCurve curve = new HermiteCurve(p0, m0, p1, m1);
Vector2 p0prime = curve.GetPoint(0.5f);
Vector2 m0prime = curve.GetTangent(0.5f);
HermiteCurve curvePrime = new HermiteCurve(p0prime, m0prime, p1, m1);
Vector2 curvePoint = curve.GetPoint(0.75f);
Vector2 curveTangent = curve.GetTangent(0.75f);
Vector2 curvePrimePoint = curvePrime.GetPoint(0.5f);
Vector2 curvePrimeTangent = curvePrime.GetTangent(0.5f);
// Why does this check fail?
if (curvePoint != curvePrimePoint || curveTangent != curvePrimeTangent)
{
Console.WriteLine("fail");
Console.WriteLine("curvePosition - x: " + curvePoint.X + " y: " + curvePoint.Y);
Console.WriteLine("curvePrimePosition - x: " + curvePrimePoint.X + " y: " + curvePrimePoint.Y);
Console.WriteLine("curveTangent - x: " + curveTangent.X + " y: " + curveTangent.Y);
Console.WriteLine("curvePrimeTangent - x: " + curvePrimeTangent.X + " y: " + curvePrimeTangent.Y);
}
}
}
程序输出:
fail
curvePosition - x: 0.890625 y: 0.703125
curvePrimePosition - x: 0.96875 y: 0.71875
curveTangent - x: 0.8125 y: 1.3125
curvePrimeTangent - x: 0.25 y: 0.375
您使用的是指针相等而不是对象相等?简单的回答是,数学并没有按照您希望的方式工作 我已经很久没有玩弄多项式曲线了,所以为了好玩,我拼凑了一个Python程序来计算“分割”hermite曲线的控制点,以及你的“错误”曲线。实际上,您最好使用de Casteljau算法 这个实现可能在很多方面都很可怕,但至少它似乎产生了正确的结果。:-)
代码中的问题是从原始Hermite曲线中获取一阶导数向量,并直接使用它们创建子曲线。以这种方式创建的子曲线实际上与原始Hermite曲线的[0.5,1]部分不同。您可以尝试绘制曲线,您将看到原始曲线和子曲线不匹配 要使子曲线与原始曲线的[0.5,1.0]部分匹配,必须使用“m0prime/2”和“m1/2”创建“CurveTime”。一旦你这样做了,你会发现计算出的“curvePoint”和“curvePrimePoint”将是相同的,“CurvePrimeTranscent”将是“curveTangent”的一半
简而言之,三次Hermite曲线和三次Bezier曲线本质上是相同的东西(三次多项式曲线),但具有不同的表示形式。三次Hermite曲线的定义包含点和向量,而三次Bezier曲线的定义仅包含点。所以,确实三次厄米曲线有时会引起混乱。但是三次贝塞尔曲线有其缺点,而且中间的两个控制点不在曲线上,这使得使用三次贝塞尔曲线时“点插值”问题更难解决。Vector2是一种值类型。您的答案看起来像是一条要求澄清的注释。是一个吗?啊,即使如此,你也不应该对不精确的类型进行相等处理,你应该计算距离并断言它小于某个小值,比如10e-5或诸如此类。谢谢@Joey为添加程序输出所做的澄清编辑。我应该补充一点,一个相当明显的问题是GetTangent规范化了切向量。输入中切线的绝对值是重要的,而不仅仅是它们的方向!然而,我的直觉是,你对这些东西的参数空间是如何工作的过于乐观了。这个问题涉及到同样的问题,我想:感谢@TurePålsson,我并没有真正关注切线向量的大小这一事实,尽管回想起来这似乎是显而易见的。我已使用此更改更新了问题。但是,检查仍然失败,现在切线值之间的距离更大。这对我来说很不直观。仍然希望能对理解我的误解有所帮助。非常感谢您的回答,并花时间编写此代码。我会把它弄得一团糟,希望能对正在发生的事情有一些直觉。我还将研究de Casteljau算法。
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import numpy.polynomial.polynomial as npp
# Hermite basis matrix
B = np.array([[1, 0, -3, 2],
[0, 1, -2, 1],
[0, 0, 3, -2],
[0, 0, -1, 1]])
# Create the parameter space, for plotting. T has one column for each point
# that we plot, with the first row t^0, second row t^1 etc.
step = 0.01
Tt = np.arange(0.0, 1.01, step)
T = np.vstack([np.ones(Tt.shape[0]), Tt, Tt ** 2, Tt ** 3])
# Control points for first curve. One column for each point/tangent.
C1 = np.array([[0, 1, 1, 0],
[0, 0, 1, 1]])
# Coefficients for first curve. @ is matrix multiplication. A1 will be a
# matrix with two rows, one for the "x" polynomial and one for the "y"
# polynomial, and four columns, one for each power in the polynomial.
# So, x(t) = Σ A[0,k] t^k and y(t) = Σ A[1,k] t^k.
A1 = C1 @ B
# Points for first curve.
X1 = A1 @ T
# Parameter value where we will split the curve.
t0 = 0.5
# Evaluate the first curve and its tangent at t=t0. The 'polyval' function
# evaluates a polynomial; 'polyder' computes the derivative of a polynomial.
# Reshape and transpose business because I want my COordinates to be COlumns,
# but polyval/polyder wants coefficients to be columns...
p = npp.polyval(t0, A1.T).reshape(2, 1)
d = npp.polyval(t0, npp.polyder(A1.T, 1)).reshape(2, 1)
# Control points for second, "wrong", curve (last two points are same as C1)
C2 = np.hstack([p, d, C1[:,2:]])
# Coefficients for second, "wrong", curve
A2 = C2 @ B
# Points for second, "wrong", curve
X2 = A2 @ T
# We want to create a new curve, such that that its parameter
# u ∈ [t0, 1] maps to t ∈ [0,1], so we let t = k * u + m.
# To get the curve on [0, t0] instead, set k=t0, m=0.
k = 1 - t0
m = t0
k2 = k * k; k3 = k2 * k; m2 = m * m; m3 = m2 * m
# This matrix maps a polynomial from "t" space to "u" space.
KM = np.array([[1, 0, 0, 0],
[m, k, 0, 0],
[m2, 2 * k * m, k2, 0],
[m3, 3 * k * m2, 3 * k2 * m, k3]])
# A3 are the coefficients for a polynomial which gives the same values
# on the range [0, 1] as the A1 coefficients give on the range [t0, 1].
A3 = A1 @ KM
X3 = A3 @ T
# Compute the control points corresponding to the A3 coefficients, by
# multiplying by the inverse of the B matrix.
C3 = A3 @ np.linalg.inv(B)
# C3 = (np.linalg.solve(B.T, A3.T)).T # Possibly better version!
print(C3)
# Plot curves
fig, ax = plt.subplots()
ax.plot(X1[0,:], X1[1,:], linewidth=3)
ax.plot(X2[0,:], X2[1,:])
ax.plot(X3[0,:], X3[1,:])
ax.set_aspect('equal')
ax.grid()
plt.show()