付録 A: NURBS ジオメトリ

スプライン ジオメトリと NURBS ジオメトリに関しては、大変すぐれた本がたくさん出版されています。この付録では、NURBS のすべてについて説明するのではなく、Maya に特有な部分の概要を提供します。

図の 6 つのカーブのうち最初のカーブは、4 つのスパンを持つ円プリミティブです。円とハルが接触していないことに注目してください。その他の 5 つのカーブは、Maya のカーブ作成ツールを使用して円を複製しようとする過程を表します。

円にはカーブ ツール(作成 > CV カーブ ツール(Create > CV Curve Tool))の既定オプション設定を使用した 4 つの CV があるように見えます。オプション設定は次のとおりです。

多重エンド ノット

オン

カーブの次数

3 次

4 つの CV を配置すると、「カーブ 1」のようなものが作成されます。これが開いたカーブです。つまり、カーブの起点 CV と終点 CV の間に切れ目があります。また、円には 4 つのスパンがあるのに対して、カーブ 1 のスパンは 1 つだけです (スパンは 2 つのエディット ポイントを接続します)。

最初の CV の上にもう 1 つの CV を配置してカーブを閉じると、カーブ 2 のようなものが作成されます。カーブ 2 はプリミティブ円に近いものです。これは、まず第一に閉じているから、つまりカーブ上の最初と最後の CV 間にギャップがないからです。第二に、このカーブには 2 つのスパンがあります。それでもまだ円には見えません。起点 CV でカーブとハルが交差しており、このポイントのカーブの接線に連続性がないからです。2 つ目の CV 上に 6 つ目の CV を配置すると、カーブは開いたカーブになり、ますます円に見えなくなります。7 つ目、8 つ目の CV を配置しても、起点 CV と終点 CV が常にハル上にあるので、円にはなりません。

カーブ ツールのオプション ボックスで多重エンドノット(Multiple End Knots)をオフにして 4 つの CV を配置すると、カーブ 3 のようなカーブが作成されます。このカーブはハルと交差していないので、カーブ 1 よりは円になり得そうです。ここで 5 番目の CV を最初の CV の上に配置すると、カーブ 4 が作成されます。このカーブのほうがさらに円になる可能性が高そうです。さらに進めて 6 番目と 7 番目の CV をそれぞれ 2 番目と 3 番目の CV の上に追加すると、カーブ 5 が作成されます。これはまさに円のように見えます。円が完成しました。

しかしまだ完全ではありません。追加した 5 ~ 7 つ目の CV のいずれかを引っ張ってその下の CV から離すと、カーブがばらばらに引っ張られ、カーブは再び開いてしまいます。それに対して図の最初の円では、CV をどのように引っ張っても、カーブはばらばらにならず、カーブは閉じたままです。つまり、最初の円とカーブ 5 の間にはまだ少し違いがあります。その違いは、カーブのシェイプです。カーブ 1、3、4 はすべて開いているので開いたシェイプです。カーブ 2 とカーブ 5 は閉じているので閉じたシェイプです。最初の円は、開いても閉じてもいない第 3 のタイプのシェイプで、周期的なシェイプと呼ばれます。周期的とは、カーブの次数によって表される数だけ、カーブの最後の CV がカーブの最初の CV にオーバーラップすることを意味します。CV 上でどんな操作を行っても、オーバーラップしている CV は重なったままで、ばらばらになりません。

周期的カーブでは、通常、カーブ全体に接線の連続性があります。閉じたカーブでは接線の連続性はありません。

次のプラグインでカーブ 1 を作成します。CV が確実にカーブのエンドポイントを補間するように、カーブのノットをそれぞれのエンド ポイントで複製します(積み重ねます)。

#include <maya/MSimple.h>
#include <maya/MIOStream.h>
#include <maya/MPointArray.h>
#include <maya/MDoubleArray.h>
#include <maya/MFnNurbsCurve.h>
MStatus curveTest( const MArgList& )
{
     MFnNurbsCurve curveFn;
     const double cvs[][4] = {
                                 { -1, 0, -1, 1 },
                                 { -1, 0,  1, 1 },
                                 {  1, 0,  1, 1 },
                                 {  1, 0, -1, 1 }
                             };
     const double knots[] = { 0, 0, 0, 1, 1, 1 };
     MPointArray cvArray( cvs, unsigned( sizeof(cvs) / (4*sizeof(double)) ) );
     MDoubleArray knotArray( knots, unsigned(sizeof( knots )/sizeof( double )) );
     MStatus status;
     MObject curve = curveFn.create( cvArray, knotArray, 3, MFnNurbsCurve::kOpen,
     false, false, MObject::kNullObj, &status );
     if ( MS::kSuccess != status )
     {
         cout << "Failed to create curve\n";
         return status;
     }
     return MS::kSuccess;
}
     DeclareSingle( curveTest );

カーブ 1 からカーブ 3 を生成するには、カーブノットが最後の CV に重ならないようにノット ベクトルを変更するだけです。

const double knots[] = { 0, 1, 2, 3, 4, 5 };

カーブ 1 をカーブ 2 に変形するのはやや複雑です。CV 配列の最後に CV (最初の CV の複製)を追加し、ノット配列に新しいノットを挿入する必要があります。

const double cvs[][4] = {
                            { -1, 0, -1, 1 },
                            { -1, 0,  1, 1 },
                            { 1,  0,  1, 1 },
                            { 1,  0, -1, 1 },
                            { -1, 0, -1, 1 }
                        };
const double knots[] = { 0, 0, 0, 1, 2, 2, 2 };

カーブ 3 からカーブ 4 への変形は、カーブ 1 からカーブ 2 に変形する場合と同じです。つまり、CV (最初の CV の複製)とノットを追加するだけです。

const double cvs[][4] = {
                            { -1, 0, -1, 1 },
                            { -1, 0,  1, 1 },
                            {  1, 0,  1, 1 },
                            {  1, 0, -1, 1 },
                            { -1, 0, -1, 1 }
                        };
const double knots[] = { 0, 1, 2, 3, 4, 5, 6 };

カーブ 5 は、カーブ 4 にさらに 2 つの CV (2つ目の CV と 3 つ目の CV の複製)と 2 つのノットを追加します。

const double cvs[][4] = {
                             { -1, 0, -1, 1 },
                             { -1, 0,  1, 1 },
                             {  1, 0,  1, 1 },
                             {  1, 0, -1, 1 },
                             { -1, 0, -1, 1 },
                             { -1, 0,  1, 1 },
                             {  1, 0,  1, 1 }
};
const double knots[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8 };

これらのプラグインによって、円プリミティブと同じシェイプのカーブが生成されます。ただし、このカーブの CV を引っ張ると、カーブがばらばらに引っ張られてしまいます。カーブが分離しないようにするには、もう 1 つ変更を加える必要があります。カーブを作成するときに、開いたカーブになるように指定する(MFnNurbsCurve::kOpen)のではなく、周期的カーブになるように指定します(MFnNurbsCurve::kPeriodic)。

MObject curve = curveFn.create( cvArray, knotArray, 3,
 MFnNurbsCurve::kPeriodic, false, false, MObject::kNullObj, &status );

オーバーラップする CV が適切な数だけあれば(カーブの各次数について 1 つ。つまり上記のカーブは次数 3 なので 3 つのオーバーラップする CV が必要です)、周期的なカーブを作成することができます。オーバーラップする CV の数が足りないと、create() メソッドは失敗します。