역사로 따지면 칸토어 먼지(1872년) 다음은 코호 곡선(1904년) 순이나 코호 곡선에 대해서는 다음 편에서 알아보기로 하고, 코호 곡선 다음 순에 해당하는 시어핀스키 양탄자(1910년대 말)에 대해 먼저 설명한다. 다음은 시어핀스키 양탄자(Sierpinski Carpet)를 표시하였다.
정사각형을 준비하여 각 변을 3등분해서 9개의 정사각형으로 분할한 후 가운데 사각형을 제거한다. 이어서는 제거된 사각형 둘레의 8개의 분할된 사각형에 대해 동일한 방법으로 가운데 정사각형을 제거한다. 이와 같은 작업을 다시 제거된 둘레에 남겨진 8개의 사각형에 대해 무한히 반복한다.
이 결과 사각형은 어떤 모양일 될까? 칸토어의 먼지처럼 무엇이 존재하기는 하나 그것들의 총면적은 0 이 되어 버린다. 양탄자는 존재하되 양탄자로 덮을 수 있는 면적은 없는 것이다. 아리달쏭하다. 아래의 그림은 이 과정을 나타낸 것이다.
(A)원형 그림이 시어핀스크 양탄자의 원형인데 재귀의 경우의 수가 8개(↑↓←→↖↗↙↘ 방향)나 되어 좌표 따지기가 번거롭다. (B)변형 그림과 같이 변형된 시어핀스키 양탄자를 가지고 설명을 진행해 나가기로 한다. 이 양탄자는 재귀의 경우의 수가 4개(↖↗↙↘ 방향)이다. 따라서 (A)원형 양탄자에 비해 반만 따지면 된다. 이 경우 시어핀스킨 양탄자 원형과 같은 방식으로 중앙의 사각형을 제거하면 단순한 모양이 되어 버린다. 따라서 이곳에서는 다음과 같은 방식으로 양탄자를 만들도록 한다. 이 방식으로 양탄자를 만들어 보면 (A)원형 시어핀스키 양탄자는 손쉽게 만들어 낼 수 있다.
단, 이 방법으로 만들어진 양탄자는 무면적이 되지는 않는다. 이상의 내용을 코드로 표현해 보면 아래와 같다.
Copy // 사각형을 그린다.
void DrawRectangle ( GLfloat x1 , GLfloat y1 , GLfloat x2 , GLfloat y2)
{
glRectf (x1 , y1 , x2 , y2);
}
//변형의 시어핀스키 양탄자를 그린다.
void SCarpet_b ( GLfloat x , GLfloat y , GLfloat r)
{
if (r >= 1.0 )
{
DrawRectangle (x - r / 3 , y - r / 3 , x + r / 3 , y + r / 3 ); //한변의 길이가 2r/3 인 사각형을 그린다.
SCarpet_b (x - r , y + r , r / 2.0 f ); //좌상 ↖
SCarpet_b (x + r , y + r , r / 2.0 f ); //우상 ↗
SCarpet_b (x - r , y - r , r / 2.0 f ); //좌하 ↙
SCarpet_b (x + r , y - r , r / 2.0 f ); //우하 ↘
}
}
그렇다면 시어핀스키 양탄자의 원형을 그리는 방법은 어떻게 될까? 다음과 같이 하면 된다.
변형과 달리 4가지의 방향에 대해서 더 그려주기만 하면 된다. 이를 코드로 표현해 보면 아래와 같다.
Copy //원형의 시어핀스키 양탄자를 그린다.
void SCarpet_a ( GLfloat x , GLfloat y , GLfloat r)
{
if (r >= 1.0 f )
{
DrawRectangle (x - r / 6 , y - r / 6 , x + r / 6 , y + r / 6 ); //한변의 길이가 2r/6 인 사각형을 그린다.
SCarpet_a (x - r / 3 , y - r / 3 , r / 3 ); //좌하 ↙
SCarpet_a (x , y - r / 3 , r / 3 ); //하 ↓
SCarpet_a (x + r / 3 , y - r / 3 , r / 3 ); //우하 ↘
SCarpet_a (x - r / 3 , y , r / 3 ); //좌 ←
SCarpet_a (x + r / 3 , y , r / 3 ); //우 →
SCarpet_a (x - r / 3 , y + r / 3 , r / 3 ); //좌상 ↖
SCarpet_a (x , y + r / 3 , r / 3 ); //상 ↑
SCarpet_a (x + r / 3 , y + r / 3 , r / 3 ); //우상 ↗
}
}
위의 그림은 시어핀스키 양탄자의 원형과 변형을 출력한 결과다. 사각형을 다른 모양의 기하로 바꿔보면 더욱더 재미있을 것이다. 아래는 이 프로그램의 전체 코드다.
Copy #include "lib\egl.h"
using namespace egl;
// 사각형을 그린다.
void DrawRectangle ( GLfloat x1 , GLfloat y1 , GLfloat x2 , GLfloat y2)
{
glRectf (x1 , y1 , x2 , y2);
}
//변형의 시어핀스키 양탄자를 그린다.
void SCarpet_b ( GLfloat x , GLfloat y , GLfloat r)
{
if (r >= 1.0 )
{
DrawRectangle (x - r / 3 , y - r / 3 , x + r / 3 , y + r / 3 ); //한변의 길이가 2r/3 인 사각형을 그린다.
SCarpet_b (x - r , y + r , r / 2.0 f ); //좌상 ↖
SCarpet_b (x + r , y + r , r / 2.0 f ); //우상 ↗
SCarpet_b (x - r , y - r , r / 2.0 f ); //좌하 ↙
SCarpet_b (x + r , y - r , r / 2.0 f ); //우하 ↘
}
}
//원형의 시어핀스키 양탄자를 그린다.
void SCarpet_a ( GLfloat x , GLfloat y , GLfloat r)
{
if (r >= 1.0 f )
{
DrawRectangle (x - r / 6 , y - r / 6 , x + r / 6 , y + r / 6 ); //한변의 길이가 2r/6 인 사각형을 그린다.
SCarpet_a (x - r / 3 , y - r / 3 , r / 3 ); //좌하 ↙
SCarpet_a (x , y - r / 3 , r / 3 ); //하 ↓
SCarpet_a (x + r / 3 , y - r / 3 , r / 3 ); //우하 ↘
SCarpet_a (x - r / 3 , y , r / 3 ); //좌 ←
SCarpet_a (x + r / 3 , y , r / 3 ); //우 →
SCarpet_a (x - r / 3 , y + r / 3 , r / 3 ); //좌상 ↖
SCarpet_a (x , y + r / 3 , r / 3 ); //상 ↑
SCarpet_a (x + r / 3 , y + r / 3 , r / 3 ); //우상 ↗
}
}
class RenderWindow : public Window
{
private :
bool mode;
public :
virtual void RenderGLScene ( void );
virtual void OnCreate ( WPARAM wParam , LPARAM lParam);
virtual void OnSize ( WPARAM wParam , LPARAM lParam);
virtual void OnKeyDown ( WPARAM wParam , LPARAM lParam);
};
void RenderWindow :: OnCreate ( WPARAM wParam , LPARAM lParam)
{
mode = true ;
}
void RenderWindow :: OnKeyDown ( WPARAM wParam , LPARAM lParam)
{
if (wParam == VK_F1)
{
mode = ! mode;
}
Window :: OnKeyDown (wParam , lParam);
}
void RenderWindow :: OnSize ( WPARAM wParam , LPARAM lParam)
{
GLfloat width , height;
width = LOWORD (lParam);
height = HIWORD (lParam);
if (height == 0 )
height = 1 ;
glViewport ( 0 , 0 , width , height );
glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
gluPerspective ( 54.0 f , GLfloat (width) / GLfloat (height) , 1.0 f , 1000.0 f );
glMatrixMode (GL_MODELVIEW);
glLoadIdentity ();
}
void RenderWindow :: RenderGLScene ( void )
{
Window :: RenderGLScene ();
if (mode)
{
//원형을 그린다.
glTranslatef ( 0.0 f , 0.0 f , - 70.0 f );
SCarpet_a ( 0.0 f , 0.0 f , 50.0 f );
}
else
{
//변형을 그린다.
glTranslatef ( 0.0 f , 0.0 f , - 400.0 f );
SCarpet_b ( 0.0 f , 0.0 f , 80.0 f );
}
}
int APIENTRY WinMain (HINSTANCE hInstance , HINSTANCE hPrevInstance , LPSTR lpCmdLine , int nShowCmd)
{
RenderWindow app;
if ( ! app . Create (FALSE , "Fractal - Sierpinski Carpet" ))
return EXIT_FAILURE;
return app . Run ();
}