• 文章
  • 謝爾賓斯基三角形分形 - 最簡單的
釋出者:
2015年10月13日(最後更新:2015年10月13日)

謝爾賓斯基三角形分形 - 產生隨機性的最簡單方法

評分:4.3/5 (928票)
*****

什麼是謝爾賓斯基三角形?


謝爾賓斯基三角形是由多個(或無限個)三角形組成的圖形。看看下面的謝爾賓斯基三角形,就能知道它看起來是多麼的無限


其背後的概念是,填充的三角形在中心被一個空的等邊三角形填充,使得這個三角形空間與圍繞它的三個三角形全等。


如果您看了我之前的文章,連結在此,您會看到我之前有一個三角形,我只是粗暴地將其分解成更多的三角形,但沒有基底。這次將以一種技術性的方式進行!換句話說,我們不建立三角形然後將其分解成三個,而是做一些其他的事情來產生隨機性。

我們將使用的概念很簡單。它將包含瓦片!如果有一個或多個瓦片,並且在瓦片空間上方沒有三個瓦片,那麼我們將在該空間放置一個瓦片,否則就不放置瓦片!

實現


首先,您應該瞭解 C++ 的基礎知識,以及一些 SDL2 和基礎三角學的知識才能理解它。
我們將使用 SDL2 來實現圖形。我們只會使用它的一些基本原始繪圖方法來繪製線條。所以,我們只會包含 SDL2 標頭檔案。這次,它將是一個多檔案專案。

檔案: main.cpp, SierpinskiTile.h, SierpinskiTile.cpp

在 SierpinskiTile.h 中
我們需要包含 SDL.h 來繪圖,並使用 list 來儲存SDL_Rect* 或瓦片的列表。

1
2
#include <SDL.h>
#include <list> 


現在,這是帶有預處理指令的類

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#ifndef _SIERPINSKI_TILE_
#define _SIERPINSKI_TILE_

class SierpinskiTile
{
public:
	SierpinskiTile(int scrW, int scrH, int w, int h)
		: scrW(scrW), scrH(scrH), tileW(w), tileH(h) {};
	~SierpinskiTile();
	void setTile(int x_index, int y_index);
	bool isThereTile(int x_index, int y_index);
	void calculate(int y_index = -1);
	void draw(SDL_Renderer*& renderer, int r, int g, int b, int y_index);

private:
	int scrW, scrH;
	int tileW, tileH;
	std::list<SDL_Rect*> rects;
};

#endif 


在 SierpinskiTile.cpp 中
這是實現,其中包含一些註釋以幫助理解

解構函式
1
2
3
4
5
6
7
#include "SierpinskiTile.h"

SierpinskiTile::~SierpinskiTile() //Deleting all resources in destructor
{
	for (auto itr : rects)
		delete itr;
}


setTile() 方法用於在瓦片索引位置設定瓦片
1
2
3
4
5
6
7
8
9
10
void SierpinskiTile::setTile(int x_index, int y_index) //Setting tile on the tile index position
{
	SDL_Rect* rectToAdd = new SDL_Rect;
	rectToAdd->x = x_index * tileW;
	rectToAdd->y = y_index * tileH;
	rectToAdd->w = tileW;
	rectToAdd->h = tileH;

	rects.push_back(rectToAdd);
}


isThereTile() 方法用於找出給定瓦片索引位置是否有瓦片
1
2
3
4
5
6
7
8
9
bool SierpinskiTile::isThereTile(int x_index, int y_index) //Finding out whether a tile is here or not
{
	for (auto itr : rects)
		if (itr->x == tileW * x_index
			&& itr->y == tileH * y_index)
			return true;

	return false;
}


最重要的 -> calculate() 方法用於找出下一行的瓦片排列,預設引數為 -1,這會導致 calculate() 方法計算所有行的瓦片排列
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
void SierpinskiTile::calculate(int y_index) //Calculating where to put tiles in the next row
//by the tile arrangement present in the previous row
{
	/////////////////////////////////////////////////
	//Conditions for putting a tile below the upper tile (or tile space):
	// 1- Tile is at that spot, 0- Tile is not at that spot, X- Unknown (can be 0 or 1)

	/////////////////////////////////////////////////
	// Case 1: 0 1 0, Case 2: 1 0 0, Case 3: 0 0 1,
	// Case 4: 1 1 0, Case 5: 1 0 1, Case 6: 0 1 1

	// Output for Cases 1-6: X 1 X

	/////////////////////////////////////////////////
	// Case 7: 0 0 0, Case 8: 1 1 1

	// Output for Cases 7-8: X 0 X

	int y = 0;
	if (y_index > -1)
	{
		y = y_index;

		for (int x = 0; x < scrW / tileW; x++)
		{
			if ((isThereTile(x, y) || isThereTile(x + 1, y) || isThereTile(x - 1, y))
				&& !(isThereTile(x, y) && isThereTile(x + 1, y) && isThereTile(x - 1, y))
				)
				setTile(x, y + 1);
		}
	}
	else
	{
		for (; y < scrH / tileH; y++)
			for (int x = 0; x < scrW / tileW; x++)
			{
				if ((isThereTile(x, y) || isThereTile(x + 1, y) || isThereTile(x - 1, y))
					&& !(isThereTile(x, y) && isThereTile(x + 1, y) && isThereTile(x - 1, y))
					)
					setTile(x, y + 1);
			}
	}
}


哎呀… 第二重要的 -> draw() 方法,它實際上只繪製一行並刪除之前所有行的所有瓦片
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void SierpinskiTile::draw(SDL_Renderer*& renderer, int r, int g, int b, int y_index)
{
	SDL_SetRenderDrawColor(renderer, r, g, b, 255); //Setting renderer's color

	std::list<SDL_Rect*> deleteRects; //For getting a list of rectangles/tiles to be deleted
	for (auto itr : rects)
	{
		SDL_RenderFillRect(renderer, itr); //Draw all tiles present in the rects which
		//will be just all tiles in the particular row

		if (itr->y <= tileH * y_index) //Put all tiles of rows before the given row
			//to deleteRects for deleting
			deleteRects.push_back(itr);
	}

	for (auto itr : deleteRects) //Delete all collected tiles and clear them
	{
		rects.remove(itr);
		delete itr;
	}
	deleteRects.clear();

	SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); //Resetting renderer's color
}


在 main.cpp 中
首先,我們需要一些必要的常量,以及 SDL_Window*, SDL_Renderer*SDL_Event。我們還需要一個布林變數。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <SDL.h>
#include "SierpinskiTile.h"

#undef main //Solution to the problem: No entry point defined.

const int SCR_W = 640;
const int SCR_H = 480;
const int TILE_W = 5; //Each tile's width
const int TILE_H = 5; //Each tile's height

SDL_Window* window = NULL;
SDL_Renderer* renderer = NULL;
SDL_Event event;

bool quit = false;

SierpinskiTile* generator = NULL;


是時候行動了,一切都很順利,main() 方法現在很容易實現了!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
int main(int argc, char** args)
{
	SDL_Init(SDL_INIT_VIDEO); //Initializing SDL2

	window = SDL_CreateWindow("Koch Fractal", SDL_WINDOWPOS_UNDEFINED,
		SDL_WINDOWPOS_UNDEFINED, SCR_W, SCR_H, SDL_WINDOW_SHOWN); //Creating window
	renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); //Creating renderer

	SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); //Setting default screen color

	generator = new SierpinskiTile(SCR_W, SCR_H, TILE_W, TILE_H); //Creating fractal generator
	generator->setTile((SCR_W / TILE_W) / 2, 0); //Setting a tile at the top middle of the screen

	int row = 0;
	while (!quit)
	{
		while (SDL_PollEvent(&event) > 0) //Minimal event polling for proper quitting
			if (event.type == SDL_QUIT)
				quit = true;

		//***NOTE: Screen must not be cleaned as the draw() method draws a row only
		//and deletes all tiles of the previous rows***
		//SDL_RenderClear(renderer);

		if (row < SCR_H / TILE_H) //Draw and calculate until the last row
		{
			generator->draw(renderer, 0, 255, 0, row-1); //Drawing the row in green color

			SDL_RenderPresent(renderer); //Updating screen

			generator->calculate(row++); //Calculating the next row
		}
	}

	delete generator; //Deallocating fractal generator

	SDL_DestroyRenderer(renderer);
	SDL_DestroyWindow(window);
	SDL_Quit(); //Clearing all SDL resources

	return 0;
}


這非常直接,我希望您能理解……

結果

以下圖片顯示了結果。它是在移動裝置上使用 C4Droid(一款用於在 Android 上執行 C++ 程式的程式)執行的。

我希望這能激發您對更多程式設計、演算法和分形的興趣。欲瞭解更多此類資訊,請訪問我的部落格 bacprogramming.wordpress.com

細節程度取決於常量 TILE_W 和 TILE_H。值越小,分形顯示的細節越多。

細節較少:-


細節一般:-


細節較多:-


細節最多:-


我也在 PC 上運行了它。這是另一個極其精細的分形:-


隨機性!

透過在頂角設定一個瓦片,我們可以看到分形產生了混亂且難以理解的三角形圖案,這些圖案看起來具有隨機的性質。

具有不同起始 setTile() 的相同程式碼可以產生這個效果