什么是谢尔宾斯基三角形?
谢尔宾斯基三角形是由多个(或
无限个)三角形组成的图形。看看下面的谢尔宾斯基三角形,就能知道它看起来是多么的
无限。
其背后的概念是,填充的三角形在中心被一个空的等边三角形填充,使得这个三角形空间与围绕它的三个三角形全等。
如果您看了我之前的文章,
链接在此,您会看到我之前有一个三角形,我只是粗暴地将其分解成更多的三角形,但没有基底。这次将以一种技术性的方式进行!换句话说,我们不创建三角形然后将其分解成三个,而是做一些其他的事情来产生随机性。
我们将使用的概念很简单。它将包含瓦片!如果有一个或多个瓦片,并且在瓦片空间上方没有三个瓦片,那么我们将在该空间放置一个瓦片,否则就不放置瓦片!
实现
首先,您应该了解 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() 的相同代码可以产生这个效果