前回の曲線に引き続き、今度は平面を作成していきたいと思います。
前提
平面を決定するための要素は
などのいずれかの条件が必要です。 さらに、有限平面を幾何的に決定するには、
などの条件が必要となります。
一方無限平面は、画面に表示したり、重心位置や面積を計算したり、交線を発生させたり、さまざまな幾何的処理を行う際に不都合が多いのは確かです。また、コンピュータのリソースも有限です。
OCCでは、「無限平面」をジオメトリックに、式や値の集合として保持することは可能ですが、トポロジカルなシェイプ(形状)として保持することはありません。これは次のような見方ができるかもしれません。
- 直線 y = ax + b の式 ← ジオメトリーレベル、範囲は無限
- 1 の条件 かつ 0 <= x <= 10 ← トポロジーレベル、範囲は有限
トポロジーレベルでの条件(?)は、範囲を設定することだけではなく、行列を用いたトランスフォーメーションなどもそれに当たります。式以外でその形状を決定づける全ての要因がトポロジーであると言えます。
この部分の解釈は、私自身、幾何数学があまり得意ではないので上手く説明ができませんし、何か勘違いをしている部分もあるかもしれませんが、ジオペトリーとトポロジーの概念を詳しく説明したサイトはインターネット上にたくさんありますし、OCCでのコーディングを深めていく上で、雰囲気を感じとっていただければ…ということでお茶を濁します。すいません。
以後、このブログで直線、曲線、平面、曲面といった単語が出てきたら、すべて「有限~」であると思ってください。あんまり出てこないとは思いますが、有限か無限かが重要なポイントでは、明示的に述べたいと思います。
矩形平面を作成する
さて、実際にモノを作ります。ここで矩形平面と呼んでいる平面は、任意の三次元座標を基準にし、uv方向に大きさを持つ矩形上の平面のことです。下図のようなものです。
ここで平面を決定づける要素は
- 基準点 pos[x, y, z]
- 法線ベクトル norm[x, y, z]
- v ベクトル vdir[x, y, z]
- u 方向の範囲 umin, umax
- v 方向の範囲 vmin, vmax
です。u ベクトルはありませんが、法線ベクトルと v 方向を示すベクトルの二つがあれば、この二つの外積から u 方向は決定されます。
コード
Standard_Real umin = -10, umax = 10;
Standard_Real vmin = -10, vmax = 10;
gp_Pnt pos(0, 0, 0);
gp_Dir norm(0, 0, 1);
gp_Dir vdir(0, 1, 0);
gp_Ax3 ax(pos, norm, vdir);
gp_Pln pln(ax);
TopoDS_Face f = BRepBuilderAPI_MakeFace(pln, umin, umax, vmin, vmax);
上で示した要素を定義して、BRepBuilderAPI_MakeFace クラスで平面を作成しているだけですので、あまり説明をすることはないと思います。
今回、初めて登場した gp_Dir は、名前の通り方向(Direction)を示す単位ベクトルです。この型を用いて API を呼び出したりする際、その方向だけが用いられ、大きさは無視されます。なので、
gp_Dir dir(1, 1, 0); // XY方向にナナメ
という指定の仕方をしても、用いられる際には正規化されていますので大丈夫です。
gp_Ax3 は、軸クラスです。gp_Ax3 のほか、gp_Ax1 と gp_Ax2 も存在します。
- gp_Ax1 … 位置(gp_Pnt)と方向(gp_Dir)を持つ
- gp_Ax2 … 位置(gp_Pnt)と方向(gp_Dir)とv方向(gp_Dir)を持つ
- gp_Ax3 … Ax2 の要素に加え、右手系・左手系の設定を持つ
軸をカメラに例えると、Ax1 でカメラの位置と視線方向が決定でき、Ax2 では視線方向を軸とした視野の回転を制限することができるわけです。(決定要因が視線方向だけだと、視野における上がどちらの方向か定まりませんもんね)
さらに gp_Pln は、Ax3 の情報を持った「無限」平面です。持っている情報は Ax3 のそれと変わりませんので、意味論としての入れ物として解釈していいと思います。
あとは、BRepBuilderAPI を使ってトポロジカルな面 TopoDS_Face を作成しています。
1点だけ補足を述べておきますと、上記の例の pos は、あくまでもベースとなる無限平面を定義するものですので、uv 方向の範囲内(有限平面内)に存在している必要はありません。つまり、例えば原点(0, 0, 0)に位置定義をしておき、u が 100~200 の範囲、v が 50~80 の範囲、という定義も問題なく成立するわけです。
任意の外形を持つ平面を作成する
さて、次は任意の外形を持つ平面を作成します。図で示すと、次のような形状を作成します。
前項では、uv 方向の範囲を指定して平面を作成しましたが、この項からは「合わせ技」が登場します。具体的に述べますと、外形を一旦、ワイヤーというシェイプとして生成しておき、そのワイヤーを用いて平面を作成するわけです。
コード
百聞は一見にしかず。実際にコードを追っていきましょう。
BRepBuilderAPI_MakePolygon mp;
mp.Add(gp_Pnt(0, 0, 0));
mp.Add(gp_Pnt(10, 0, 0));
mp.Add(gp_Pnt(10, 5, 0));
mp.Add(gp_Pnt(20, 5, 0));
mp.Add(gp_Pnt(20, 15, 0));
mp.Add(gp_Pnt(15, 12, 0));
mp.Add(gp_Pnt(5, 12, 0));
mp.Add(gp_Pnt(3, 10, 0));
mp.Add(gp_Pnt(0, 0, 0));
mp.Build();
if (mp.IsDone()) {
TopoDS_Wire w = mp.Wire();
BRepBuilderAPI_MakeFace mf(w, Standard_True);
mf.Build();
if (mf.IsDone()) {
TopoDS_Face f = mf.Face();
return mrb_fixnum_value(::set(f, self));
}
}
以前のポリラインを作成する記事で紹介したように、BRepBuilderAPI_MakePolygon クラスを使って、ポリラインを作成し、TopoDS_Wire として受け取っています。このワイヤーを用い BRepBuilderAPI_MakeFace クラスで平面を作成しています。
この MakeFace クラスは、前項と同じものですが、コンストラクタがオーバーロードされており、いくつもの方法で面を作ることが可能となっています。これは MakeFace に限らず、MakeEdge などのクラスも同様です。
MakePolygon クラスは、Wire() だけでなく Edge() や Shape() でも作成した曲線を返すことができますが、ここでは明示的に Wire として受け取っています。Wire を端的に表現すると「1個以上の Edge からなる連続した曲線群」で、連続性が保証されていることが重要なポイントです。単に「複数の Edge」で良ければ、Edge の配列かなにかで十分ですが、複数の Edge をひとつの Wire として表現するということは、同時にそれらの Edge が連続してつながっている状態であるということを表します。
閉じた領域である有限平面の外形となるには、外形を構成している Edge がバラバラに存在しているのではなく、ひと筆書きで連続していることが条件となるため、MakeFace の引数には Wire が用いられるわけです。
さらに条件をつけ加えると、その Wire は始終点がくっついて輪状になっている必要があります。外形線なのに、どこかが途切れていたりしたら、その平面の範囲を決定できませんよね。
BRepBuilderAPI_MakeFace クラスのコンストラクタの第2引数が Standard_True になっていますが、これは平面であることを強制するためのフラグです。 前項では gp_Pln クラスを用いて、作りたい面が乗っているベースとなる無限平面を明示的に指定していました。今回の Wire から平面を生成する方法では、そのような明示的な指定はありません。そのため、平面であることを強制するフラグが存在するのです。(このフラグを False にすると、曲面として面を張ります)
また当たり前ですが、平面を作成する場合、ポリラインの各通過点は同一平面上になければなりません。
オマケ: OCCにおける数値型について
OCCでは移植性向上のため、整数値、浮動小数点数、真偽値などの値を次のように定義しています。
typedef int Standard_Integer;
typedef double Standard_Real;
typedef bool Standard_Boolean;
typedef float Standard_ShortReal;
typedef char Standard_Character;
typedef short Standard_ExtCharacter;
typedef unsigned char Standard_Byte;
typedef void* Standard_Address;
typedef size_t Standard_Size;
typedef std::time_t Standard_Time;
typedef const char* Standard_CString;
typedef const short* Standard_ExtString;
詳しい内容は、Standard_TypeDef.hxx に記載されています。このブログでも、これらの定義に則って表記しています。 複数のプラットフォームへの移植を考えてなくても、OCCを使ったコードではできるだけ上記の型名を用いておいた方がいいでしょう。