Cardinal 曲线
可以参考一下我另一篇文章 Cardinal 与三阶 Bezier 相互转换。
根据定义,给定点集
{
P
k
−
1
,
P
k
,
P
k
+
1
,
P
k
+
2
}
\{ \mathbf {P}_{k-1}, \mathbf {P}_k, \mathbf {P}_{k+1}, \mathbf {P}_{k+2} \}
{Pk−1,Pk,Pk+1,Pk+2} , 则
P
k
\mathbf {P}_k
Pk 到
P
k
+
1
\mathbf {P}_{k+1}
Pk+1 之间的曲线可以由如下方程生成:
P
(
u
)
=
a
u
3
+
b
u
2
+
c
u
+
d
d
=
P
k
c
=
s
(
P
k
+
1
−
P
k
−
1
)
b
=
3
(
P
k
+
1
−
P
k
)
−
s
(
P
k
+
2
−
P
k
)
−
2
c
a
=
(
P
k
+
1
−
P
k
)
−
c
−
b
\begin {aligned} \mathbf{P}(u) & = \mathbf{a}u^3 + \mathbf{b}u^2 + \mathbf{c}u + \mathbf{d}\\ \mathbf{d} & =\mathbf {P}_k\\ \mathbf{c} & =s(\mathbf {P}_{k+1}-\mathbf {P}_{k-1})\\ \mathbf{b}& =3(\mathbf {P}_{k+1}-\mathbf {P}_k)-s(\mathbf {P}_{k+2}-\mathbf {P}_k)-2\mathbf{c}\\ \mathbf{a}& =(\mathbf {P}_{k+1}-\mathbf {P}_k) - \mathbf{c} - \mathbf{b} \end {aligned}
P(u)dcba=au3+bu2+cu+d=Pk=s(Pk+1−Pk−1)=3(Pk+1−Pk)−s(Pk+2−Pk)−2c=(Pk+1−Pk)−c−b
其中,
s
s
s 用于控制曲线的松紧,取值范围为 [0, 1]。 0 表示最紧绷 (无平滑转角);1 表示最松弛。
绘制思路
既然方程已经给出了,则只需将 u u u 从 0 到 1 采样并依据公式计算坐标即可。不过,由于不好把握 u u u 的采样间隔,这里并不打算采用这种方案。
我的思路是:
- 先计算两点之间 x, y 方向的差值
- 取差值绝对值较大的那个值,按预设精度进行细分,计算出 u u u 的步长
- 根据 u u u 的步长,计算方程的增量 Δ P \Delta \mathbf{P} ΔP
- 采用前向差分法,依次计算中间点坐标
a. 如果中间点到上一线段的距离小于预设精度,抛弃该点
前向差分
设有一个三次样条曲线,其表达式如下:
P
(
u
)
=
a
u
3
+
b
u
2
+
c
u
+
d
\mathbf {P}(u) = \mathbf{a}u^3 + \mathbf{b}u^2 + \mathbf{c}u + \mathbf {d}
P(u)=au3+bu2+cu+d
如果将
u
u
u 的取值范围细分为具有固定大小
h
h
h 的子区间,则相邻两点
x
x
x 坐标为:
x
k
=
x
(
u
k
)
x
k
+
1
=
x
(
u
k
+
h
)
\begin{aligned} x_k &= x(u_k) \\ x_{k+1} &= x(u_k + h) \end{aligned}
xkxk+1=x(uk)=x(uk+h)
可计算出前向差分为:
Δ
1
x
k
=
x
k
+
1
−
x
k
=
3
a
h
u
k
2
+
(
3
a
h
2
+
2
b
h
)
u
k
+
(
a
h
3
+
b
h
2
+
c
h
)
\begin{aligned} \Delta_1 x_k &= x_{k+1} - x_k \\ & = 3\mathbf {a} hu^2_k + (3\mathbf{a}h^2 + 2\mathbf{b}h)u_k + (\mathbf{a}h^3 + \mathbf{b}h^2 + \mathbf{c}h) \end{aligned}
Δ1xk=xk+1−xk=3ahuk2+(3ah2+2bh)uk+(ah3+bh2+ch)
由于
Δ
1
x
k
\Delta_1 x_k
Δ1xk 是关于
u
u
u 的多项式,计算复杂,所以也可以计算出
Δ
1
x
k
\Delta_1 x_k
Δ1xk 的增量
Δ
2
x
k
\Delta_2 x_k
Δ2xk,这样即可用加法计算出
Δ
1
x
k
\Delta_1 x_k
Δ1xk 的值。不断重复计算增量的过程,直到增量为常数。最终可以得到:
Δ
1
x
k
=
3
a
h
u
k
2
+
(
3
a
h
2
+
2
b
h
)
u
k
+
(
a
h
3
+
b
h
2
+
c
h
)
Δ
2
x
k
=
6
a
h
2
u
k
+
6
a
h
3
+
2
b
h
2
Δ
3
x
k
=
6
a
h
3
\begin{aligned} \Delta_1 x_k &= 3\mathbf {a} hu^2_k + (3\mathbf{a}h^2 + 2\mathbf{b}h)u_k + (\mathbf{a}h^3 + \mathbf{b}h^2 + \mathbf{c}h) \\ \Delta_2 x_k &= 6\mathbf {a} h^2u_k +6\mathbf{a}h^3 + 2\mathbf{b}h^2\\ \Delta_3 x_k &= 6\mathbf{a}h^3 \end{aligned}
Δ1xkΔ2xkΔ3xk=3ahuk2+(3ah2+2bh)uk+(ah3+bh2+ch)=6ah2uk+6ah3+2bh2=6ah3
根据以上公式,从 u 0 = 0 u_0=0 u0=0 开始,步长为 h h h, x x x 坐标的迭代过程为:
k k k | Δ 3 x k \Delta_3 x_k Δ3xk | Δ 2 x k \Delta_2 x_k Δ2xk | Δ 1 x k \Delta_1 x_k Δ1xk | x k x_k xk |
---|---|---|---|---|
0 | 6 a h 3 6\mathbf{a}h^3 6ah3 | 6 a h 3 + 2 b h 2 6\mathbf{a}h^3 + 2\mathbf{b}h^2 6ah3+2bh2 | a h 3 + b h 2 + c h \mathbf {a}h^3 + \mathbf {b}h^2 + \mathbf {c}h ah3+bh2+ch | d \mathbf {d} d |
1 | 6 a h 3 6\mathbf{a}h^3 6ah3 | Δ 2 x 0 + Δ 3 x 0 \Delta_2 x_0 + \Delta_3 x_0 Δ2x0+Δ3x0 | Δ 1 x 0 + Δ 2 x 0 \Delta_1 x_0 + \Delta_2 x_0 Δ1x0+Δ2x0 | x 0 + Δ 1 x 0 x_0+\Delta_1 x_0 x0+Δ1x0 |
⋮ \vdots ⋮ | ||||
k k k | 6 a h 3 6\mathbf{a}h^3 6ah3 | Δ 2 x k − 1 + Δ 3 x k − 1 \Delta_2 x_{k-1} + \Delta_3 x_{k-1} Δ2xk−1+Δ3xk−1 | Δ 1 x k − 1 + Δ 2 x k − 1 \Delta_1 x_{k-1} + \Delta_2 x_{k-1} Δ1xk−1+Δ2xk−1 | x k − 1 + Δ x k − 1 x_{k-1} + \Delta x_{k-1} xk−1+Δxk−1 |
可以看到,每次迭代只需计算 3 次加法。
成果
最终效果如下图,也可以点击此处查看演示效果及源代码。
看起来效果还是不错的。不过,根据算法描述可以发现,如果两个点相距很近,就会因为中间点不够多而走样。
其它生成方法
当然还其它方法也可以绘制。比如,二分迭代法。每次计算出曲线的中点,将曲线按中点分为两部分,然后迭代这个过程。还可以先把曲线转换为 Bezier 曲线,然后进行绘制。