2025年1月20日(月) 05:08 JST

2016年度第8回GDL講習会

  • 2016年6月17日(金) 21:04 JST
  • 投稿者:
    ゲストユーザ

第8回ではGDLプログラミングマニュアルの8章で紹介している「他言語からGDLを使う」方法を学習しました。GDLもプログラミング言語ですので、人の手では描きにくい曲面、あるいはそれに円柱を適応させた立体トラスなどをプログラミングできますが、C言語やPythonなどの汎用プログラミング言語により複雑な処理を任せ、計算結果をGDLスクリプトとして出力させるといった分業が効果的なシーンもあります。ここでは、こういった分業の例として、パーリンノイズを用いた地形と、再帰によるシェルピンスキーのギャスケットを紹介します。

image image

パーリンノイズによる地形

下図左の地形はMESHに単純な乱数を高さに与えることで生成したものです。これはC言語からの書き出しで行いましたが、GDLには乱数を発生させるRNDコマンドが用意されていますので、パラメータバッファを用いてMESHコマンドに渡してあげれば同様のモデルを作成することができます。しかし、ここに単純な乱数ではなく、パーリンノイズのようなアルゴリズムを適用することを考えます。パーリンノイズとはCGの世界で地形や雲、炎などを自然に表現するために用いられるアルゴリズムです。パーリンノイズでgoogle検索などして頂けるとC#などでの実装を紹介するウェブサイトがありますが、ちょっとGDLへの移植はしたくありませんね…。かなり大変そうです。このように、GDLでは実装しにくいアルゴリズムや、他言語ですでに実装したものがある場合、その言語によるプログラミングからGDLの書式に合わせたテキストを書き出すという方法が思いつきます。下図右はパーリンノイズをC言語で実装したプログラミングからGDLスクリプト(のテキスト)を出力し、それをGDLの3Dスクリプト欄にコピペしています。


シェルピンスキーのギャスケット

次はシェルピンスキーのギャスケット(の三次元版)です。①の「DivideTetra」関数で四面体を四つの四面体に分割し、それぞれが「DivideTetra」関数を呼び出すことで再帰しています。今回の講習会的にポイントなのは②の「WriteGDL」関数ですね。ここで計算結果としてもとめた頂点情報をもとにGDL形式のテキスト形式で書き出しています。再帰図形をGDLで実装できなくはないですが、関数やクラスなど便利な機能が用意されている言語を用いて分業したほうが合理的な場合もあるかと思います。他言語で得意なものをお持ちの場合もテクニックやアルゴリズムが使いまわせて効果的でしょうか。一方、今回紹介した分業には、デメリットとしてGDLのパラメトリックな性質が活かしにくいという点があります。このシェルピンスキーのギャスケットでは、計算処理をC言語に依っているため、GDLとしては再帰数を変えるなどのパラメトリックな操作ができない点には注意が必要です。

#include 〈stdio.h〉
#include 〈fstream〉
#define _USE_MATH_DEFINES
#include 〈math.h〉

using namespace std;

//頂点のクラス
class Vec{
public:
	double x, y, z;
	Vec() {};
	Vec(double _x, double _y, double _z) { //コンストラクタ
		x = _x;
		y = _y;
		z = _z;
	};
	~Vec() {};
};

///正四面体のクラス
class Tetra {
public:
	Vec v1, v2, v3, v4; //四面体の各頂点
	unsigned int generation; //世代数
	Tetra() {};
	Tetra(const Vec &_v1, const Vec &_v2, const Vec &_v3, const Vec &_v4) {
		v1 = _v1, v2 = _v2, v3 = _v3, v4 = _v4;///四頂点を格納
	}
	~Tetra() {};///デストラクタ
};

static const unsigned int recurrenceMax = 4; //再帰数の上限
ofstream out("GDL.txt", ios::out | ios::trunc);

//②GDL出力用の関数
void WriteGDL(Tetra &tetra){
	out << "PLANE 3, " <<
		tetra.v1.x << ", " << tetra.v1.y << ", " << tetra.v1.z << ", " <<
		tetra.v2.x << ", " << tetra.v2.y << ", " << tetra.v2.z << ", " <<
		tetra.v3.x << ", " << tetra.v3.y << ", " << tetra.v3.z << endl;

	out << "PLANE 3, " <<
		tetra.v4.x << ", " << tetra.v4.y << ", " << tetra.v4.z << ", " <<
		tetra.v2.x << ", " << tetra.v2.y << ", " << tetra.v2.z << ", " <<
		tetra.v3.x << ", " << tetra.v3.y << ", " << tetra.v3.z << endl;

	out << "PLANE 3, " <<
		tetra.v1.x << ", " << tetra.v1.y << ", " << tetra.v1.z << ", " <<
		tetra.v4.x << ", " << tetra.v4.y << ", " << tetra.v4.z << ", " <<
		tetra.v3.x << ", " << tetra.v3.y << ", " << tetra.v3.z << endl;

	out << "PLANE 3, " <<
		tetra.v1.x << ", " << tetra.v1.y << ", " << tetra.v1.z << ", " <<
		tetra.v2.x << ", " << tetra.v2.y << ", " << tetra.v2.z << ", " <<
		tetra.v4.x << ", " << tetra.v4.y << ", " << tetra.v4.z << endl;
}

//二点の中心点を得るための関数
Vec InnerDividing2(const Vec &v1, const Vec &v2){
	return Vec((v2.x + v1.x) / 2.0, (v2.y + v1.y) / 2.0, (v2.z + v1.z) / 2.0);
}

//①正四面体を分割して再帰する関数
void DivideTetra(Tetra &tetra){
	if (tetra.generation > recurrenceMax) { //再帰数の上限に達したら終了
		WriteGDL(tetra);
		return;
	}

	Tetra t1;
	t1.generation = tetra.generation + 1;
	t1.v1 = tetra.v1;
	t1.v2 = InnerDividing2(tetra.v1, tetra.v2);
	t1.v3 = InnerDividing2(tetra.v1, tetra.v3);
	t1.v4 = InnerDividing2(tetra.v1, tetra.v4);
	DivideTetra(t1);

	Tetra t2;
	t2.generation = tetra.generation + 1;
	t2.v1 = tetra.v2;
	t2.v2 = InnerDividing2(tetra.v2, tetra.v1);
	t2.v3 = InnerDividing2(tetra.v2, tetra.v3);
	t2.v4 = InnerDividing2(tetra.v2, tetra.v4);
	DivideTetra(t2);

	Tetra t3;
	t3.generation = tetra.generation + 1;
	t3.v1 = tetra.v3;
	t3.v2 = InnerDividing2(tetra.v3, tetra.v1);
	t3.v3 = InnerDividing2(tetra.v3, tetra.v2);
	t3.v4 = InnerDividing2(tetra.v3, tetra.v4);
	DivideTetra(t3);

	Tetra t4;
	t4.generation = tetra.generation + 1;
	t4.v1 = tetra.v4;
	t4.v2 = InnerDividing2(tetra.v4, tetra.v1);
	t4.v3 = InnerDividing2(tetra.v4, tetra.v2);
	t4.v4 = InnerDividing2(tetra.v4, tetra.v3);
	DivideTetra(t4);

	return;
}

int main(int argc, char* argv[]){
	///元の正四面体の形状の定義
	double r = 10.0;
	double alpha = 360.0 / 3.0 * ( M_PI / 180.0 );
	Vec a(r*cos(alpha * 1.0), r*sin(alpha * 1.0), 0.0);
	Vec b(r*cos(alpha * 2.0), r*sin(alpha * 2.0), 0.0);
	Vec c(r*cos(alpha * 3.0), r*sin(alpha * 3.0), 0.0);
	Vec d(0.0, 0.0, r * sqrt(2));

	///最初の正四面体を定義
	Tetra fst(a, b, c, d);
	fst.generation = 0;

	///分割
	DivideTetra( fst );
	return 0;
}