以前「数学曲面のモデリング」にて、任意の物体をイメージし、任意座標(x, y, z)がその物体の内部なのか/外部なのか?を判定できる手続きを定義できるなら、その物体をポリゴンでモデリング可能となる、Xi-TRACEの機能をご紹介しました。
今回は、この機能を応用して、色々な立体をモデリングしてみたいと思います。
手続きによる立体のモデリングは、処理速度が大きな課題でしたので、合わせて「組み込み手続き」による高速化についてもご紹介します。
いずれの例も、Wavefront社OBJフォーマットでポリゴンデータを書き出してからXi-TRACEで取り込んでレイトレーシングしています。レイトレーシングだと、設定した視野でしか鑑賞できませんが、OBJファイルを読み込み可能なモデリングソフトウェアをお持ちなら、OBJファイルを読み込んで、自在に色々な角度から見ることができます。
作者は「mini3DViewer」というソフトウェアを使わせてもらいました。閲覧だけでなく、編集もしたい時には、「六角大王Super 5.5」を使っています。レイトレーシングする前に、完成したポリゴンデータを確認するのに重宝しています。
平面図で指定した画像に厚みを持たせて立体化(ポリゴン変換)します。
まず、平面図を問いあわせるウインドウを開きます。指定された画像データを読み込み、Xi-TRACEで表示しておきます。
// 平面図を読み込む
var rtn;
macro fname;
gui openfile, rtn, "入力画像ファイル","",fname;
if (rtn!=guiOK)
{
// キャンセルされた
ifnacc $(dir)$/$(file)"_out.obj"
{
// OBJファイルもないので、終了する。
leave stop_error;
}
}
else
{
load $(fname);
if (status!=0)
{
// ロードエラー
leave stop_error;
}
// 以前出力したOBJファイルを削除する
fdel $(dir)$/$(file)"_out.obj";
}
読み込んだ画像を使い、polygon命令でポリゴンに変換するので、polygon命令に渡す手続きを記述します。
procedure thick x,y,z
{
// 定義域内の相対座標に変換する
x=(x - polygon.X1)/polygon.XSZ;
y=(y - polygon.Y1)/polygon.YSZ;
z=(z - polygon.Z1)/polygon.ZSZ;
// グラフィック座標を求める
var ix,iy;
ix=x*fwid;
iy=(1 - y)*fhei;
// 輝度値を求める
var pc;
var lu;
getpix ix,iy, pc;
pix2lum pc, lu;
// 判定
if (lu<0.5)
{
thick=1; // 外部
}
else
{
thick=-1; // 内部
}
}
一番手前に表示中のグラフィック画面からピクセル値を取り込み(getpix命令)、ピクセルの輝度値を計算(pix2lum命令)、輝度値が1/2以上なら物体の内部、1/2未満なら外部と判定する手続きです。一番手前に表示されているグラフィック画面の大きさ(fwid×fhei)をpolygon命令に指定した定義域のx軸・y軸方向の幅に対応させています。厚み方向がz軸方向となります。
polygon thick, 0,0,0, obj_x,obj_y,obj_z, obj_x/acr, $(dir)$/$(file)"_out.obj";
polygon命令を実行すると、以下の変数が宣言され、値がセットされます。手続きの中で参照して汎用化できるようにすることが目的です。
システム変数名 | 説明 |
polygon.X1 | polygon命令に指定した定義域のX1座標 |
polygon.X2 | 同X2座標(X2>X1) |
polygon.Y1 | 同Y1座標 |
polygon.Y2 | 同Y2座標(Y2>Y1) |
polygon.Z1 | 同Z1座標 |
polygon.Z2 | 同Z2座標(Z2>Z1) |
polygon.XSZ | =polygon.X2-polygon.X1 |
polygon.YSZ | =polygon.Y2-polygon.Y1 |
polygon.ZSZ | =polygon.Z2-polygon.Z1 |
polygon.MSZ | polygon命令に指定したメッシュサイズ |
定義域の座標は、メッシュサイズによって量子化されているので、polymesh命令に渡した数値その物とは異なっている場合があります。
相当、メッシュを細かくしないと辛いですが、漢字などをグラフィックデータにしておいて、立体化することも可能です。
少し応用してみましょう。先ほど示した手続きでは、輝度値の1/2を閾値にして、内部/外部判定をしていましたが、輝度値を物体の厚みに読み替えると、以下の様になります(判定部分だけを示します)。
// 判定
if (z<=polygon.ILOW)
{
z=0.;
}
if (lu>z)
{
thick=-1; // 内部
}
else
{
thick=1; // 外部
}
輝度値が高い→明るい(白い)時には、z軸方向の厚みが最大値(z軸方向の定義域いっぱい)になります。逆に、輝度値が低い→暗い(黒い)時には、厚みが最小値となり、完全な黒だと、物体の外部と見なします。
この時注意しなければならないのは、画像フォーマットとピクセル値の関係です。自然画像を保存する時、画像フォーマットとしてJPEGを選択することが多いと思いますが、JPEGは「非可逆」の圧縮アルゴリズムなので、一般に、画像を作成してセーブした時と全く同じ画像には戻りません。人間の目では判別できないような細かなところが微妙に異なっています。画像を作成した時は、「完全な黒」で作図していても、JPEGでセーブして読み込んだ画像では、「完全な黒」に戻らず、少しだけ明るさを持った灰色になっています(特に白い部分との境界線付近)。
このデータを使うと、物体の外側にしたかった黒い部分に、バリのようにポリゴンが生成されてしまうことがあります。手続き中で、polygon.thick.ILOWという変数値を参照し、輝度値がこの値以下だった場合には、「黒と見なす」処理をしています。もしどうしても、polygon.thick.ILOW=0 の状態で使用したい場合には、JPEGなどの非可逆圧縮フォーマットではなく、BMPなどの可逆圧縮フォーマットを使い、データ作成時に「完全な黒」を使ってください。
手続きの中身が分かったところで、この手続きは、"thick"と言う名前で組み込み手続きとして定義されており、polygon命令から参照することができます。組み込み手続きは、Cでコーディングされ、Xi-TRACEに組み込んでいますので、ユーザ定義手続きに比べて高速です。
組み込み手続きより、ユーザが定義した手続きの方が優先されるので、組み込み手続きを使いたい時には、組み込み手続きと同じ名前の手続きを定義しないようにしてください。データでは、use_embeddedと言う変数の値(trueかfalse)によって、組み込み手続きを使う(true)か、ユーザ定義手続きを使う(false)か、切り替えています。
var use_embedded=true; // true:組み込み手続きを使う
if (!use_embedded)
{
// 組み込み手続きと同名の手続きが定義されたら、組み込み手続きは無効。
static : procedure thick x,y,z
{
……
}
}
Xi-TRACEデータ:pgon_thick.xitr
輝度値が厚みに対応するので、前方後円墳みたいな形はこんな感じ↓です。
読み込んだ画像を断面図とし、縦軸を中心にぐるっと一周回した時にできる物体(回転体)を生成してみます。
この場合も、輝度の1/2を閾値として明るい(白い)部分が内部、暗い(黒い)部分が外部とします。
上図に示した通り、回転体の円を描く面がx-z平面ですので、x座標とz座標を使って極座標(動径と偏角)を求め、グラフィック座標を計算する、と言う手順ですが、偏角の方は使いませんので、動径だけを求めます。動径=sqrt(sq(x)+sq(z))です。後は、前節「平面図の立体化」と同様、画素値を取り込み、輝度を計算して、内部/外部判定を実行します。
procedure revol x,y,z
{
// 定義域内の相対座標に変換する
x=(x - polygon.X1)/polygon.XSZ*2 - 1;
y=(y - polygon.Y1)/polygon.YSZ;
z=(z - polygon.Z1)/polygon.ZSZ*2 - 1;
// グラフィック座標を求める
var ix,iy;
ix=sqrt (sq (x) + sq (z))*fwid;
iy=(1 - y)*fhei;
// 輝度値を求める
var pc;
var lu;
getpix ix,iy, pc;
pix2lum pc, lu;
// 判定
if (lu<0.5)
{
revol=1; // 外部
}
else
{
revol=-1; // 内部
}
}
この手続きも、"revol"と言う名前で組み込み手続きとして定義してあります。組み込み手続きは、Cでコーディングされ、Xi-TRACEに組み込んでいますので、ユーザ定義手続きに比べて高速です。
Xi-TRACEデータ:pgon_revol.xitr
以下の断面図をくるっと一周回転させると、茶碗の様な形になります。
平面図、正面図、右側面図の三面図を満たす物体を生成してみます。
三面図をグラフィックデータとしてセーブしておいてもらって、グラフィックデータからポリゴンを生成する手続きを作ります。グラフィックデータ(三面図)の仕様を以下とします。
このようなグラフィックデータが読み込まれた状態で、以下の様な手続きがpolygon命令に渡されることとします。なお、SQH=SQ/2(平面図、正面図、右側面図の一辺の長さ)、SQ=<三面図のグラフィックデータサイズ>です。
procedure three x,y,z
{
// 定義域内の相対座標に変換する
x=(x - polygon.X1)/polygon.XSZ;
y=(y - polygon.Y1)/polygon.YSZ;
z=(z - polygon.Z1)/polygon.ZSZ;
// グラフィック座標を求める
var ix,iy,iz;
ix=x*SQH;
iy=y*SQH;
iz=z*SQH;
clip ix=0,SQH - 1;
clip iy=0,SQH - 1;
clip iz=0,SQH - 1;
// 判定
var pc;
var lu;
// 平面図(xy)
getpix ix,SQH - 1 - iy, pc;
pix2lum pc, lu;
if (lu<0.5)
{
three=1; // 外部
}
else
{
// 正面図(xz)
getpix ix,SQ - 1 - iz, pc;
pix2lum pc, lu;
if (lu<0.5)
{
three=1; // 外部
}
else
{
// 右側面図(yz)
getpix SQH + iy,SQ - 1 - iz, pc;
pix2lum pc, lu;
if (lu<0.5)
{
three=1; // 外部
}
else
{
three=-1; // 内部
}
}
}
}
この手続きも、"three"と言う名前で組み込み手続きとして定義してあります。組み込み手続きは、Cでコーディングされ、Xi-TRACEに組み込んでいますので、ユーザ定義手続きに比べて高速です。
作ろうとする物体によっては、三面図で、ある程度モデリング可能かと思います。ただ、今回示した仕様の三面図では表現しきれない物体の方が多いので、注意が必要です。例えば、以下の様な物体。
一瞬、「ボール、球体!」と、思ってしまうかもしれませんが、実際にポリゴンを生成してみると、以下の様になります。3方向の影に注目してください。三面図の3つの図面に対応しています。
確かに、正面、横、上から見ると円(○)になっているのですが、(1,1,1)方向から見てみると、こんな感じ。ボール(球体)とはかけ離れてますね。
もう一つ、今度は、ありえない(矛盾した)三面図だった場合。
上から見ると○、正面から見ると凵A横から見ると◇、という事です。作ってみると、
凾ニ◇はOKなのですが、○の影がおかしいですね(○くない)。最初に与えた三面図が間違っていて、存在し得ない物体を定義しようとしたのが原因です。与えた三面図を満たす物体は存在せず、三面図の指定する形状の最大限共通部分(AND?)を取った結果、○になるべきところが削れてしまった、という事です。
ちょっとだけ変えて、凾△に変えてみます。
今度は、○、△、◇共、ちゃんと生成できたようです。この例でも分かる通り、ちょっと形が変わっただけで、NGケースとOKケースが変わります。組み込み手続きによって実行速度も向上しましたので、試行錯誤で「やって見る」のが一番手っ取り早いかと……。
Xi-TRACEデータ:pgon_three.xitr
polygon命令のメッシュサイズを十分小さくしないと辛いのですが、漢字のポリゴン生成をやってみました。
完成したデータと動画です。
Xi-TRACEデータ:three_views.xitr
OBJファイル:three_views.obj.zip
ポリゴン生成に使った三面図が以下です。"pgon_three.xitr"に読み込ませてOBJファイルを作成後、"three_views.obj"にリネームして使用しました。
動画はちょっとずるして、回転が治まった最後のフレームだけ、メッシュサイズを細かくした画像とすり替えています。メッシュサイズを細かくしたポリゴンは、ポリゴン数が増大しており、計算時間が余計にかかるので……。