有一种更简单、更有效的方法来实现它,它只需要你使用不同的公式计算你的切线,而不需要实现 Barry 和 Goldman 的递归评估算法。
如果您对节点 (t0,t1,t2,t3) 和控制点 (P0,P1,P2,P3) 采用 Barry-Goldman 参数化(在 Ted 的回答中引用)C(t),则其封闭形式非常复杂,但最终,当您将其约束在区间 (t1,t2) 时,它仍然是 t 中的三次多项式。因此,我们需要完整描述它的是两个端点 t1 和 t2 的值和切线。如果我们计算出这些值(我在 Mathematica 中做过),我们会发现
C(t1) = P1
C(t2) = P2
C'(t1) = (P1 - P0) / (t1 - t0) - (P2 - P0) / (t2 - t0) + (P2 - P1) / (t2 - t1)
C'(t2) = (P2 - P1) / (t2 - t1) - (P3 - P1) / (t3 - t1) + (P3 - P2) / (t3 - t2)
我们可以简单地将其插入标准公式,以计算在端点处具有给定值和切线的三次样条,我们就有了非均匀 Catmull-Rom 样条。需要注意的是,上述切线是针对区间 (t1,t2) 计算的,因此,如果您想评估标准区间 (0,1) 中的曲线,只需将切线乘以因子 (t2-t1) 即可重新缩放切线)。
我在 Ideone 上放了一个可用的 C++ 示例:http: //ideone.com/NoEbVM
我还将粘贴下面的代码。
#include <iostream>
#include <cmath>
using namespace std;
struct CubicPoly
{
float c0, c1, c2, c3;
float eval(float t)
{
float t2 = t*t;
float t3 = t2 * t;
return c0 + c1*t + c2*t2 + c3*t3;
}
};
/*
* Compute coefficients for a cubic polynomial
* p(s) = c0 + c1*s + c2*s^2 + c3*s^3
* such that
* p(0) = x0, p(1) = x1
* and
* p'(0) = t0, p'(1) = t1.
*/
void InitCubicPoly(float x0, float x1, float t0, float t1, CubicPoly &p)
{
p.c0 = x0;
p.c1 = t0;
p.c2 = -3*x0 + 3*x1 - 2*t0 - t1;
p.c3 = 2*x0 - 2*x1 + t0 + t1;
}
// standard Catmull-Rom spline: interpolate between x1 and x2 with previous/following points x0/x3
// (we don't need this here, but it's for illustration)
void InitCatmullRom(float x0, float x1, float x2, float x3, CubicPoly &p)
{
// Catmull-Rom with tension 0.5
InitCubicPoly(x1, x2, 0.5f*(x2-x0), 0.5f*(x3-x1), p);
}
// compute coefficients for a nonuniform Catmull-Rom spline
void InitNonuniformCatmullRom(float x0, float x1, float x2, float x3, float dt0, float dt1, float dt2, CubicPoly &p)
{
// compute tangents when parameterized in [t1,t2]
float t1 = (x1 - x0) / dt0 - (x2 - x0) / (dt0 + dt1) + (x2 - x1) / dt1;
float t2 = (x2 - x1) / dt1 - (x3 - x1) / (dt1 + dt2) + (x3 - x2) / dt2;
// rescale tangents for parametrization in [0,1]
t1 *= dt1;
t2 *= dt1;
InitCubicPoly(x1, x2, t1, t2, p);
}
struct Vec2D
{
Vec2D(float _x, float _y) : x(_x), y(_y) {}
float x, y;
};
float VecDistSquared(const Vec2D& p, const Vec2D& q)
{
float dx = q.x - p.x;
float dy = q.y - p.y;
return dx*dx + dy*dy;
}
void InitCentripetalCR(const Vec2D& p0, const Vec2D& p1, const Vec2D& p2, const Vec2D& p3,
CubicPoly &px, CubicPoly &py)
{
float dt0 = powf(VecDistSquared(p0, p1), 0.25f);
float dt1 = powf(VecDistSquared(p1, p2), 0.25f);
float dt2 = powf(VecDistSquared(p2, p3), 0.25f);
// safety check for repeated points
if (dt1 < 1e-4f) dt1 = 1.0f;
if (dt0 < 1e-4f) dt0 = dt1;
if (dt2 < 1e-4f) dt2 = dt1;
InitNonuniformCatmullRom(p0.x, p1.x, p2.x, p3.x, dt0, dt1, dt2, px);
InitNonuniformCatmullRom(p0.y, p1.y, p2.y, p3.y, dt0, dt1, dt2, py);
}
int main()
{
Vec2D p0(0,0), p1(1,1), p2(1.1,1), p3(2,0);
CubicPoly px, py;
InitCentripetalCR(p0, p1, p2, p3, px, py);
for (int i = 0; i <= 10; ++i)
cout << px.eval(0.1f*i) << " " << py.eval(0.1f*i) << endl;
}