C++
アルゴリズム
DXライブラリ
ゲーム制作
探索

概要

こんにちは、がっちょ( ¨̮ )です。

こちらはC++ (DXライブラリ)を使って、水彩画のようなボロノイ図を作成する記事です。
ここではそのようなボロノイ図を便宜上、"水彩ボロノイ図"と呼ぶことにします。

DXライブラリはこちらからダウンロードできます。
何か不備がありましたら指摘していただければ幸いです。

また、疑問点や質問等がございましたら、この投稿のコメント欄または「がっちょ( ¨̮ ) Twitter」にてお申し付けください。
内容に関する質問、いつでもウェルカムです。

コード

0.5秒おきに水彩ボロノイ図が自動生成されるプログラムです。

Main.cpp
/*水彩ボロノイ図を生成*/

#include "DxLib.h"

//横画面サイズ
#define MAP_X 512

//縦画面サイズ
#define MAP_Y 512

//原点サイズ
#define PX_SIZE 16

//各位置のID
#define LU VoronoiMap[im][jm]
#define U VoronoiMap[i][jm]
#define RU VoronoiMap[ip][jm]
#define L VoronoiMap[im][j]
#define C VoronoiMap[i][j]
#define R VoronoiMap[ip][j]
#define LD VoronoiMap[im][jp]
#define D VoronoiMap[i][jp]
#define RD VoronoiMap[ip][jp]

//各位置のX座標
#define LU_X WorldX[im][jm]
#define U_X WorldX[i][jm]
#define RU_X WorldX[ip][jm]
#define L_X WorldX[im][j]
#define C_X WorldX[i][j]
#define R_X WorldX[ip][j]
#define LD_X WorldX[im][jp]
#define D_X WorldX[i][jp]
#define RD_X WorldX[ip][jp]

//各位置のY座標
#define LU_Y WorldY[im][jm]
#define U_Y WorldY[i][jm]
#define RU_Y WorldY[ip][jm]
#define L_Y WorldY[im][j]
#define C_Y WorldY[i][j]
#define R_Y WorldY[ip][j]
#define LD_Y WorldY[im][jp]
#define D_Y WorldY[i][jp]
#define RD_Y WorldY[ip][jp]


/*原点を生成する*/
void DrawPX(int x, int y, int l, int id, unsigned char VoronoiMap[][MAP_Y], unsigned short WorldX[][MAP_Y], unsigned short WorldY[][MAP_Y]) {
    for (int i = x; i < x + l; i++) {
        for (int j = y; j < y + l; j++) {
            VoronoiMap[i][j] = id;
            WorldX[i][j] = i;
            WorldY[i][j] = j;
        }
    }
    return;
}


/*ボロノイ図を生成する*/
void DrawVoronoi(unsigned char VoronoiMap[][MAP_Y]) {

    //マップを初期化
    for (unsigned int i = 0; i < MAP_X; i++) {
        for (unsigned int j = 0; j < MAP_Y; j++) {
            C = 0;
        }
    }

    //マップの座標を格納する
    unsigned short WorldX[MAP_X][MAP_Y] = { 0 };
    unsigned short WorldY[MAP_X][MAP_Y] = { 0 };

    //生成する原点の個数を決める
    int mapCount = 1 + GetRand(12);

    //原点を生成
    for (int i = 1; i <= mapCount; i++) {
        DrawPX(GetRand(MAP_X - PX_SIZE), GetRand(MAP_Y - PX_SIZE), PX_SIZE, i, VoronoiMap, WorldX, WorldY);
    }

    //上下左右の座標
    unsigned int im;
    unsigned int ip;
    unsigned int jm;
    unsigned int jp;

    //原点からの距離の2乗
    unsigned int sizeXY;


    for (unsigned int k = MAP_X; k > 0; k >>= 1) {

        for (unsigned int i = 0; i < MAP_X; i++) {

            im = (MAP_X + i - k) & (MAP_X - 1);
            ip = (i + k) & (MAP_X - 1);

            for (unsigned int j = 0; j < MAP_Y; j++) {

                jm = (MAP_Y + j - k) & (MAP_Y - 1);
                jp = (j + k) & (MAP_Y - 1);

                //原点と中心点の距離の2乗を代入
                sizeXY = (C_X - i)*(C_X - i) + (C_Y - j)*(C_Y - j);

                //上と下を比較
                if (U && U == D && (!C || sizeXY >(D_X - i)*(D_X - i) + (D_Y - j)*(D_Y - j))) {
                    C = D;
                    C_X = D_X;
                    C_Y = D_Y;
                }

                //左と右を比較
                if (L && L == R && (!C || sizeXY > (R_X - i)*(R_X - i) + (R_Y - j)*(R_Y - j))) {
                    C = R;
                    C_X = R_X;
                    C_Y = R_Y;
                }

                //左上と右下を比較
                if (LU && LU == RD && (!C || sizeXY > (RD_X - i)*(RD_X - i) + (RD_Y - j)*(RD_Y - j))) {
                    C = RD;
                    C_X = RD_X;
                    C_Y = RD_Y;
                }

                //右上と左下を比較
                if (RU && RU == LD && (!C || sizeXY > (LD_X - i)*(LD_X - i) + (LD_Y - j)*(LD_Y - j))) {
                    C = LD;
                    C_X = LD_X;
                    C_Y = LD_Y;
                }

                //色を分けて線画
                switch (VoronoiMap[i][j])
                {
                case 0:DrawPixel(i, j, GetColor(255, 255, 255)); break;
                case 1:DrawPixel(i, j, GetColor(255, 0, 0)); break;
                case 2:DrawPixel(i, j, GetColor(0, 255, 0)); break;
                case 3:DrawPixel(i, j, GetColor(0, 0, 255)); break;
                case 4:DrawPixel(i, j, GetColor(255, 255, 0)); break;
                case 5:DrawPixel(i, j, GetColor(255, 0, 255)); break;
                case 6:DrawPixel(i, j, GetColor(0, 255, 255)); break;
                case 7:DrawPixel(i, j, GetColor(127, 127, 127)); break;
                case 8:DrawPixel(i, j, GetColor(127, 0, 0)); break;
                case 9:DrawPixel(i, j, GetColor(0, 127, 0)); break;
                case 10:DrawPixel(i, j, GetColor(0, 0, 127)); break;
                case 11:DrawPixel(i, j, GetColor(127, 127, 0)); break;
                case 12:DrawPixel(i, j, GetColor(127, 0, 127)); break;
                case 13:DrawPixel(i, j, GetColor(0, 127, 127)); break;
                }

            }

        }
    }

}

/*メイン関数*/
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {

    //log出力停止・ウィンドウモード変更・初期化・裏画面設定
    SetOutApplicationLogValidFlag(FALSE), ChangeWindowMode(TRUE), DxLib_Init(), SetDrawScreen(DX_SCREEN_BACK);

    //画面サイズの決定
    SetGraphMode(MAP_X, MAP_Y, 32);

    //画面サイズが偶数ではない場合
    if (MAP_X & 1 || MAP_Y & 1) {
        //DXライブラリの終了処理
        DxLib_End();
        return -1;
    }

    //マップ
    unsigned char VoronoiMap[MAP_X + 1][MAP_Y] = { 0 };

    //時間
    int time = 0;

    //ボロノイ図を生成
    DrawVoronoi(VoronoiMap);

    while (ScreenFlip() == 0 && ProcessMessage() == 0) {
        //0.5秒おきにボロノイ図を生成
        if (time == 30) {
            DrawVoronoi(VoronoiMap);
            time = 0;
        }

        //ESCキーで終了
        if (CheckHitKey(KEY_INPUT_ESCAPE)) break;

        //時間を増やす
        time++;
    }

    //DXライブラリの終了処理
    DxLib_End();
    return 0;
}

結果

実行してみたらこんな感じ

suivoro.png

アニメーションしてみたらこんな感じ

circleanimationmuvie

ボロノイ図と違う点は、境界線が凸凹している点ですね。
この性質を利用して例えば、自然地形を線画すると自然な出来になります。

参考

DXライブラリ 関数リファレンスページ
新・C言語 ~ゲームプログラミングの館
ボロノイ図
ボロノイ図を作る
ボロノイ図とその3つの性質
ボロノイ分割とは?
GPUでボロノイ図を描画する
ボロノイ図の描画アルゴリズムについて