簡略化した3次スプライン曲線の生成方法

スプライン曲線とは、簡単に言えばn+1個の点を結ぶ滑らかな曲線の一種です。3次スプライン曲線は、各点と点の間を3次関数で表現し、点での接続が滑らかになるようにしたものです。

基本的なスプライン曲線

以下に単純なy=f(x)形のスプライン曲線の生成方法を示します。

説明図

x[0]<x[1]<...<x[n]に対して y[0],y[1],...,y[n]が与えられているとします。 j=0,1,...,n-1に対して、 S[j](x)=a[j]+b[j]*(x-x[j])+c[j]*(x-x[j])^2+d[j]*(x-x[j])^3は 区間[x[j],x[j+1]]で定義される関数とします。 S[j](x)が 点(x[j],y[j])と 点(x[j+1],y[j+1])を通り(以下の前提条件の1と2)、 各函数が滑らかに接続する(以下の前提条件の3と4)ように 係数a[j],b[j],c[j],d[j]を決定します。 なお、S[j](x)関数を一意に決定するために、 以下の前提条件5を追加します。 このS[j](x)を連結したものが 3次スプライン曲線になります。

前提条件:j=0,1,...,n-2に対して、

上記前提条件を元に係数a[j],b[j],c[j],d[j]を計算すると 以下の通りになります。 但し、h[j]=x[j+1]-x[j]です。

a[j]=y[j]

b[j]=(a[j+1]-a[j])/h[j]-h[j]*(c[j+1]+2*c[j])/3

d[j]=(c[j+1]-c[j])/(3*h[j])

c[j]は以下の連立1次方程式の解になります。

連立方程式

上記の連立1次方程式は、一般的な解法でも解くことが可能ですが、ほとんどの部分がゼロなので、 処理を最適化すると処理がもっと単純化します(下記プログラム参照)。

n次元空間上の基本的なスプライン曲線

空間上の点に対して、点を結ぶスプライン曲線を作れます。 例えば3次元空間上の点の列(x[0],y[0],z[0]),(x[1],y[1],z[1]),...(x[n],y[n],z[n])に対して スプライン曲線を作る場合、 tを媒介変数として、 各次元毎に分解してx=fx(t),y=fy(t),z=fz(t)となる 3つのスプライン曲線を作ります。 媒介変数tをどうするかは、 いろいろあると思いますが、 安直にx[0]=fx(0),x[1]=fx(n),...x[n]=fx(n)とするのも一例です。 この場合、h[j]=x[j+1]-x[j]=1という特殊な条件になるので、 係数a[j],b[j],c[j],d[j]の計算がより楽になります。

a[j]=y[j]

b[j]=a[j+1]-a[j]-(c[j+1]+2*c[j])/3

d[j]=(c[j+1]-c[j])/3

c[j]を計算する連立1次方程式:

連立方程式

プログラム例を示します。

#define MaxSplineSize 100

class Spline {
    int num;
    double a[MaxSplineSize+1], b[MaxSplineSize+1], c[MaxSplineSize+1], d[MaxSplineSize+1];
public:
    Spline() { num=0; }
    void init(double *sp, int num);
    double culc(double t);
};

// Bスプライン描画
// x[num], y[num], z[num] は座標の配列
void drowSpline(double *x, double *y, double *z, int num)
{
    Spline xs, ys, zs;
    double t, m;
    xs.init(x, num);
    ys.init(y, num);
    zs.init(z, num);
    m = (double)(num-1);

    moveTo(x[0], y[0], z[0])
    for(t=0.0; t<=m; t += 0.01) {
        lineTo(xs.culc(t), ys.culc(t), zs.culc(t));
    }
}

//スプラインデータ初期化
void Spline::init(double *sp, int spnum)
{
    double tmp, w[MaxSplineSize+1];
    int i;

    num = spnum-1;

    // 3次多項式の0次係数(a)を設定
    for(i=0; i<=num; i++) {
        a[i] = sp[i];
    }

    // 3次多項式の2次係数(c)を計算
    // 連立方程式を解く。
    // 但し、一般解法でなくスプライン計算にチューニングした方法
    c[0] = c[num] = 0.0;
    for(i=1; i<num; i++) {
        c[i] = 3.0 * (a[i-1] - 2.0 * a[i] + a[i+1]);
    }
    // 左下を消す
    w[0]=0.0;
    for(i=1; i<num; i++) {
        tmp = 4.0 - w[i-1];
        c[i] = (c[i] - c[i-1])/tmp;
        w[i] = 1.0 / tmp;
    }
    // 右上を消す
    for(i=num-1; i>0; i--) {
        c[i] = c[i] - c[i+1] * w[i];
    }

    // 3次多項式の1次係数(b)と3次係数(b)を計算
    b[num] = d[num] =0.0;
    for(i=0; i<num; i++) {
        d[i] = ( c[i+1] - c[i]) / 3.0;
        b[i] = a[i+1] - a[i] - c[i] - d[i];
    }
}

//媒介変数(0〜num-1の実数)に対する値を計算
double Spline::culc(double t)
{
    int j;
    double dt;
    j = (int)floor(t); // 小数点以下切捨て
    if(j < 0) j=0; else if (j >= num) j=num-1; // 丸め誤差を考慮

    dt = t - (double)j;
    return a[j] + ( b[j] + (c[j] + d[j] * dt) * dt ) * dt;
}

参考リンク


サイトトップ 迷路トップ
記述内容について一切保障しません。リンクは自由に行ってかまいません。
Since 2005/8/4, Final update 2004/8/15, Presented by Ishida So