こんにちは、きうちです。
速いものでもう年末ですね!
お仕事をなさっている方の中にはまもなく仕事納め、という方も多いのではないかと思います。
その前にクリスマスですね!?
なんだかんだと世間もクリスマス一色。クリスマスソングなんかもそこかしこでかかり、
クリスマス気分を嫌が上にも盛り上げますね!
今回は、そんな盛り上げにいっちょかんでみようかと思います。
結論を言うと、「クリスマスツリーを描くプログラム」をご紹介します!
・・・ですが、ただ描いても面白くないので・・・
私が以前から構想していた「シェルピンスキーのギャスケットを使ってクリスマスツリーを描く」をやります^^
★ ★ ★
シェルピンスキーのギャスケットというのはフラクタル図形(自己相似図形)の一種で、
三角の中に一定のルールで三角を書く、というのを繰り返していって描画する図形で、
ポーランドの数学者”ヴァツワフ・シェルピンスキ”にちなんで名づけられています。
雑ですいませんが、図にするとこんな感じ。

ツリーを作る前に、まずは単純に1つのシェルピンスキーのギャスケットを描かせてみましょう。
言語は例によってC#にします。Windowsフォームアプリとして作成します。
画面サイズは1024×768にします。
背景は黒にします。
また、フォームのPaintイベントメソッドを作っておきます。

再帰レベルとして maxLevel というフィールドを用意して5に。
また、3頂点のデータを作っておきます。なんとなく正三角形っぽくなるように適当に。
なお、コンストラクタはデフォルトのまま弄る必要はないので、ここに書くのは略します。
private int maxLevel = 5; // 再帰レベル
private Point[] pp = { // 3頂点のデータ
new Point(512, 30),
new Point(768, 430),
new Point(256, 430),
};
続いて画面描画処理。先ほど作って置いたPaintイベントメソッドに追加をします。
private void Form1_Paint(object sender, PaintEventArgs e) // 画面描画処理
{
var g = e.Graphics;
DrawGasket(pp, g, new Pen(Color.Lime, 2), 0); // 一番外の三角形を描く
DrawSGasket(pp, e.Graphics, new Pen(Color.Lime, 2), 0); // 中の三角形を描く
}
ここで DrawGasketは三角形を描くためのメソッドであり、
DrawSGasket は中の三角形を描くためのメソッドです。以下のような感じにします。
private void DrawGasket(Point[] pp, Graphics g, Pen pen, int level) // 三角形を描く
{
g.DrawPolygon(pen, pp);
}
private void DrawSGasket(Point[] pp, Graphics g, Pen pen, int level) // シェルピンスキーのギャスケットを描く
{
if (level == maxLevel) return; // 指定した再帰レベルに到達したら何もせずreturn
// 3辺のそれぞれの中点を求める
Point[] mp = new Point[3];
for (int i = 1; i <= pp.Length; i++)
{
int idx1 = i - 1;
int idx2 = i == pp.Length ? 0 : i;
mp[i - 1] = GetMiddlePoint(pp[idx1], pp[idx2]);
}
DrawGasket(mp, g, pen, level);
// 3頂点と中点を基に新たな三角形を3つ定義する
Point[] pp1 = { pp[0], mp[0], mp[2] };
Point[] pp2 = { mp[0], pp[1], mp[1] };
Point[] pp3 = { mp[2], mp[1], pp[2] };
// 定義した新たな三角形でシェルピンスキーのギャスケットを描く
DrawSGasket(pp1, g, pen, level + 1);
DrawSGasket(pp2, g, pen, level + 1);
DrawSGasket(pp3, g, pen, level + 1);
}
DrawGasket は単純に DrawPolygon を Graphics のオブジェクトで呼び出しているだけです。
そして、DrawSGasket は先ほど図解した内部の三角形の座標を計算し、
まずは中点の3点で三角形を描いた後、3つの新たな三角形について自身を再帰呼出しします。
この中で中点を求めるメソッド GetMiddlePoint が出てきますが、以下のようにします。
private Point GetMiddlePoint(Point p1, Point p2) // 中点を求める
{
return new Point(CalcAverage(p1.X, p2.X), CalcAverage(p1.Y, p2.Y));
}
private int CalcAverage(int v1, int v2) // 2値の平均を求める
{
return (int)((v1 + v2) / 2.0);
}
ここまで書いたら実行させてみましょう。

描かれましたね!
★ ★ ★
次はこれを、こんな感じに並べます。相変わらず図が雑で申し訳ない。

プログラムは以下の通りです。今回は一気に載せますね。説明は次回やります。
class Part // 絵のパーツを定義するためのデータクラス
{
public Point[] PP { get; set; } // 3頂点
public Pen Pen { get; set; } // ベースの色
public bool WithSnow { get; set; } = true; // 雪をまとうか
public Part(Point[] pp, Pen pen, bool withSnow) // コンストラクタ
{
PP = pp;
Pen = pen;
WithSnow = withSnow;
}
}
public partial class Form1 : Form // フォームクラス
{
private List parts = new List(); // パーツのリスト
private int maxLevel = 5; // 再帰レベル
private Random random = new Random(); // 乱数発生用
private System.Drawing.Font font = new System.Drawing.Font("Comic Sans MS", 64); // 文字描画フォント
public Form1() // コンストラクタ
{
InitializeComponent();
MakeData(); // データ作成処理呼出し
}
private void MakeData() // データ作成
{
for (int i = 0; i <= 450; i += 150) // ▲を縦に4つ作る
{
parts.Add(new Part(MakeData(i), new Pen(Brushes.Green, 3), true));
}
// 最後の1つはちょっと幅を縮めて、色を茶色にする。雪は無しにする。
Part last = parts[parts.Count - 1];
last.PP[1].X = 612;
last.PP[2].X = 412;
last.Pen = new Pen(Color.FromArgb(100, 50, 0), 3); // 色を焦げ茶色に変更…余談ですが、これを Pens.DarkGoldenrod にすると何か松っぽくなります^^
last.WithSnow = false; // 幹には雪はまとわせないでおく
// 雪原
for (int y = Height - 32; y < Height; y += 16)
{
for (int x = 0; x < Width; x += 32)
{
parts.Add(new Part(MakeData2(x, y), new Pen(Color.White, 3), false));
}
}
}
private Point[] MakeData(int i) // データ作成(木)
{
return [
new Point(512, 30 + i),
new Point(768, 230 + i),
new Point(256, 230 + i),
];
}
private Point[] MakeData2(int x, int y) // データ作成(雪原)
{
return [
new Point(x, y - 64),
new Point(x + 32, y - 1),
new Point(x - 32, y - 1),
];
}
private void Form1_Paint(object sender, PaintEventArgs e) // 画面描画処理
{
var g = e.Graphics;
// パーツを描く
foreach (Part p in parts)
{
DrawGasket(g, p.PP, p.Pen!, 0);
DrawSGasket(g, p.PP, p.Pen!, p.WithSnow, 0);
}
// 文字を描く
g.DrawString("Merry Christmas!", font, Brushes.Red, 148, Height / 2 - 112);
g.DrawString("Merry Christmas!", font, Brushes.Yellow, 140, Height / 2 - 120);
}
private void DrawSGasket(Graphics g, Point[] pp, Pen pen, bool withSnow, int level) // シェルピンスキーのギャスケットを描く
{
if (level == maxLevel) return; // 指定した再帰レベルに到達したら何もせずreturn
// 3辺のそれぞれの中点を求める
Point[] mp = new Point[3];
for (int i = 1; i <= pp.Length; i++)
{
int idx1 = i - 1;
int idx2 = i == pp.Length ? 0 : i;
mp[i - 1] = GetMiddlePoint(pp[idx1], pp[idx2]);
}
DrawGasket(g, mp, pen, level);
// 3頂点と中点を基に新たな三角形を3つ定義する
Point[] pp1 = { pp[0], mp[0], mp[2] };
Point[] pp2 = { mp[0], pp[1], mp[1] };
Point[] pp3 = { mp[2], mp[1], pp[2] };
// 定義した新たな三角形でシェルピンスキーのギャスケットを描く
DrawSGasket(g, pp1, random.Next(0, 100) < 30 && withSnow ? Pens.White : pen, withSnow, level + 1);
DrawSGasket(g, pp2, random.Next(0, 100) < 30 && withSnow ? Pens.White : pen, withSnow, level + 1);
DrawSGasket(g, pp3, random.Next(0, 100) < 30 && withSnow ? Pens.White : pen, withSnow, level + 1);
}
private void DrawGasket(Graphics g, Point[] pp, Pen pen, int level) // 三角形を描く
{
g.DrawPolygon(pen, pp);
}
private Point GetMiddlePoint(Point p1, Point p2) // 中点を求める
{
return new Point(CalcAverage(p1.X, p2.X), CalcAverage(p1.Y, p2.Y));
}
private int CalcAverage(int v1, int v2) // 2値の平均を求める
{
return (int)((v1 + v2) / 2.0);
}
}
実行するとこんな感じ。

メリークリスマス!
★ ★ ★
というわけで、今回はここまでです。
次回は、このプログラムの説明をお伝えします^^
それでは!