─━ IT ━─

재귀 프로그램에 의한 프랙탈 도형 그리기

DKel 2021. 8. 16. 01:09
반응형
처음에 ′프랙탈 도형′이란 그 도형을 확대해가면 다시 처음의 도형과 같은 것이 나타나는 특수한 도형입니다.자연계에 많이 보이며, 해안선과 구름의 모양이 프랙탈이라고 알려져 있습니다.프로그래밍 기술로 보면 프랙탈을 그리기 위해서는 ′재귀 프로그램′이라는, 처음에는 좀 이해하기 어려운 특수한 기술을 사용합니다.프로그래밍학습에서는이재귀기술을습득하기위해서프랙탈이미지를그리는것이많이사용됩니다.재귀 프로그램은 특수한 경우밖에 유효하지 않지만, 스마트하기 때문에 테크닉을 과시하고 싶은 사람은 사용하고 싶어합니다.대상 독자 「재귀 프로그램」이란 무엇인가, 어떻게 코드를 쓰면 좋은가를 배우고 싶은 사람.정보처리 기술자 시험에 나오기도 합니다.코드는 자바 로 써있습니다만, 생각은 다른 언어에도 통용되므로 참고해 주세요.필요한 환경 J2SE 5.0을 사용하고 있습니다만, 그 이전의 버전이라도 문제 없습니다.프랙탈 도형이란 프랙탈(Fractal) 도형이란 만델브로(Benoit Mandelbrot)가 명명한 말로 간단히 말해서 자기 상사성을 갖고 도형의 일부를 확대하면 또 같은 도형이 나타나는 것을 말합니다.만델브로 도형은 확실히 프랙탈의 조건을 갖추고 있지만, 재귀 프로그램으로는 만들지 않습니다.재귀 프로그램으로 만드는 프랙탈 도형의 대표적인 것으로 코흐(Helge von Koch) 곡선과 수목 곡선, 드래곤(용) 곡선 등이 있습니다.재귀 프로그램이란 재귀 프로그램이 복잡한 사항들을 쉽게 표현할 수 있고 함수 호출의 경우 외부 변수와 내부 변수의 관계를 잘 이해할 수 있기 때문에 교육적인 효과가 있습니다.재귀 프로그램이란 호출된 함수(method) 중 다시 ′나 자신′의 함수를 호출하는 것입니다.따라서 초보자에게는 머리가 혼란스럽고 이해하기 어려운 단점이 있습니다.글로벌 변수(오브젝트)만 사용할 수 있는 초기 프로그램 언어에서는 재귀 프로그램을 짤 수 없었습니다.재귀 프로그램의 함수는 자기 자신을 무한히 호출하는 것이 아닙니다.어떤 조건이 되면 다른 처리를 해서 호출원으로 돌아갑니다.재귀 프로그램의 특징은, 함수 안에, 자기 자신을 호출하는 명령이 포함되어 있다.
일정한 조건(호출 회수, 취급하는 수치의 크기등)에서는 자기 자신을 호출하지 않고 다른 명령을 실행하는 「스토퍼」가 준비되어 있다.
함수를 호출할 때마다 호출 횟수 및 수치가 변경된다.라고 하는 것입니다.재귀 프로그램의 예 재귀 프로그램의 가장 간단한 예로 n의 계승을 구하는 프로그램이 있습니다.이것은 n!=n x (n-1)!의 관계를 사용합니다. n-1의 계승을 알면 n의 계승을 계산할 수 있으므로 계승을 구하는 함수 중에서 계승을 구하는 함수를 다시 호출합니다.그러나 이것은 너무 간단해서 실행 후 감동을 느낄 수 없습니다.따라서 재귀 프로그램의 예로 잘 알려진 코흐 곡선, 수목 곡선, 드래곤(용) 곡선 등의 프랙탈 도형 묘사 방법을 소개합니다.모두 자바 애플릿으로 작성되었고 사이즈는 500x500을 가정하고 있습니다.코흐 곡선 그림 1의 맨 위에 나타내는 직선에 대해서 코흐 곡선 묘화 메서드를 호출하면 그림의 중앙에 나타난 것과 같이 꺾인 선으로 변환됩니다.그림의 경우, 코흐 곡선 묘화 메서드는 한 번만 실행하고 원래대로 돌아오기 때문에 아직 재귀적인 동작은 하지 않습니다.맨 아래 그림은 코흐 곡선 묘화 메서드가 자신을 다시 불러와 만든 것으로 재귀 횟수가 1번이 됩니다.이 조작을 일정 횟수만큼 반복하면 코흐 곡선을 얻을 수 있습니다.코흐 곡선 그리기 프로그램에 대한 설명 메인 프로그램을 paint 메서드로 작성합니다.여기에서는 코흐 곡선을 그리는 기준이 되는 역정삼각형을 형성하는 3점(P, Q, R)을 지정하고 별도로 작성하는 draw Koch 메서드를 각각 한 번씩 호출합니다.
예에서는 화면상의 점 P(100, 160), 점 Q(400, 160), 점(250, 420)을 이용하여 재귀 횟수는 4회로 하고 있습니다.
이 프로그램에서 가장 중요한 drawKoch 메서드를 만듭니다.이 메서드는 아래에 설명하는 처리로 이루어져 있습니다.그림 2는 외부로부터 부여받은 원래 직선의 좌표(점 A와 점 B)로부터 점 C, 점 D, 점 E를 구해 코흐 곡선을 그리는 방법의 원리를 나타내고 있습니다. 점 C 및 점 D는 점 A와 점 B 사이를 3등분함으로써 용이하게 구할 수 있습니다.점 E에 대해서는 점 A-점 C와 점 A-점 E가 만드는 각도가 30°(Math. PI/6)인 점에 주목하고 점 A-점 B의 경사각 Math. atan (double) yy/xx)에 Math. PI/6을 가산해 점 A-점 E의 각도를 구합니다.점 A-점 E의 길이 distance는 점 A-점 B의 길이의 1/√ 3입니다.여기에서 yy는 정수(int)이기 때문에 나눗셈을 정밀하게 하기 위해서 double에 캐스트하고 있습니다.실제로는 E점의 좌표 계산은 약간 귀찮아서 도형을 왼쪽 아래에서 오른쪽 위를 향해 그리는 경우와 그 반대의 경우, 왼쪽 위에서 오른쪽 아래를 향해 그리는 경우와 그 반대의 경우 등에 대해 약간의 궁리를 해야 합니다.또, PC 화면상에서는, 아래로 갈수록 y 의 값이 커지기 때문에, 극성에 주의할 필요가 있습니다.재귀를 반복하는 동안(n> 0)에는 점 A-점 C, 점 E-점 E, 점 E-점 D, 점 D-점 B간을 그리기 위해 다시 draw Koch 메서드를 호출하고 마지막(n= 0)에는 점 A-점 C, 점 C-점 E, 점 E-점 D, 점 D-점 B를 draw Line 함수로 그려줍니다.코흐 곡선 그리기 프로그램
import java . applet . Applet ;
import java . awt . * ;

public class Koch extends Applet {

public void init ( ) {
set Background (Color.yellow); // 배경을 노란색으로 그려요
}

public void paint ( Graphics g ) {

// 출발점이 될 3점을 지정합니다
Point P = new Point ( 100 , 160 ) ;
Point Q = new Point ( 400 , 160 ) ;
Point R = new Point ( 250 , 420 ) ;

// 선색을 파란색으로 설정 후 위 3점 사이에 코흐곡선을 그립니다
g . setColor ( Color . blue ) ;
drawKoch ( g , P , Q , 3 ) ;
drawKoch ( g , Q , R , 3 ) ;
drawKoch ( g , R , P , 3 ) ;
}

//코흐곡선을 그리는 메서드
public void drawKoch ( Graphics g , Point a , Point b , int n ) {

// 메서드 내부에서 사용할 3점을 생성합니다
Point c = new Point ( ) ;
Point d = new Point ( ) ;
Point e = new Point ( ) ;

int xx , yy ;
double angle 1 , angle 2 , distance ;

c . x = ( 2 * a . x + b . x ) / 3 ;
c . y = ( 2 * a . y + b . y ) / 3 ;
d . x = ( a . x + 2 * b . x ) / 3 ;
d . y = ( a . y + 2 * b . y ) / 3 ;
xx = b . x - a . x ;
yy = - ( b . y - a . y ) ;

distance = Math . sqrt ( xx * xx + yy * yy ) / Math . sqrt ( 3 ) ;

if(xx>=0){ //원래 되는 직선이 우측 상단일 경우
angle 1 = Math . atan (( double ) yy / xx ) + Math . PI / 6 ;
e . x = a . x + ( int ) ( distance * Math . cos ( angle 1 )) ;
e . y = a . y - ( int ) ( distance * Math . sin ( angle 1 )) ;
}
else{ //원래 직선이 오른쪽 내려가는 경우
angle 2 = Math . atan (( double ) yy / xx ) - Math . PI / 6 ;
e . x = b . x + ( int ) ( distance * Math . cos ( angle 2 )) ;
e . y = b . y - ( int ) ( distance * Math . sin ( angle 2 )) ;
}

//마지막이라 실제로 선을 그습니다
if ( n < = 0 ) {
g.draw Line(a.x,a.y,c.x,c.y); //점 A에서 점 C로
g.draw Line(c.x,c.y,e.x,e.y); //점C에서점E로
g.draw Line(e.x,e.y,d.x,d.y); //점 E에서 점 D로
g.draw Line(d.x,d.y,b.x,b.y); // 점D에서 점B로
}
//마지막이 아니므로 다시 메서드를 호출합니다(재귀 처리)
else {
drawKoch(g,a,c,n-1);//점A에서 점C로
drawKoch(g,c,e,n-1);//점C에서점E로
drawKoch(g,e,d,n-1);//점E에서 점D로
drawKoch(g,d,b,n-1); // 점D에서 점B로
}
}
} 코흐 곡선 그리기 프로그램의 실행 결과를 그림 3에 나타냅니다.이러한 도형을 ′코흐 섬′이라고 부르기도 합니다자바 애플릿은 이쪽을 보시면 됩니다.수목곡선 수목곡선을 그리려면 여러 가지 방법이 있는데, 여기에서는 예시로 그림 4와 같이 그림 왼쪽에 나타난 원래의 직선을 오른쪽 그림과 같이 변환하기로 합니다.오른쪽 그림은 아래에서 일정한 비율의 부분을 줄기로 상정하고 거기에서 좌우로 가지를 뻗은 모습을 보여줍니다.세로로 뻗은 직선의 나머지 부분(즉, 원래의 직선에서 아래 줄기의 부분을 제외한 것)의 길이에 대해, 가지의 길이를 일정한 비율로 합니다.줄기를 제외한 세로로 뻗은 중심 부분과 좌우 가지 부분에 다시 이와 같은 변경을 일정 횟수만큼 반복하면 수목곡선이 생겨요.하나의 예로서 가지가 나오는 각도를 「30°」로 했습니다.수목곡선 그리기 프로그램에 대한 설명 메인 프로그램을 paint 메서드로 만듭니다.여기서는 수목곡선을 그리기 위해서 컴퓨터 화면 상의 점을 3쌍만 지정을 하고(P와 Q, R과 S, T와 U), 따로 작성하는 draw Tree 메서드를 각각 한 번씩 호출을 하겠습니다.예에서는 화면상의 점 P(200, 400), 점 Q(200, 100) 등을 이용하여 재귀 횟수는 각각 3, 4, 5회로 하였습니다.
이 프로그램에서 가장 중요한 draw Tree 메서드를 만듭니다.STEM_RATIO와 BRANCHI_RATIO는 정수로 부여합니다.그림 5는 외부로부터 부여받은 원래 직선의 좌표(점 A와 점 B)로부터 점 C, 점 D, 점 E를 구하는 방법을 나타냅니다.그림과 같이 점 C는 STEM_RATIO에서 쉽게 구합니다. 점 D에 대해서는 점 C-점 B와 점 C-점 D가 만드는 각도가 30°(Math.PI/6)인 점에 주목하고 점 A-점 B의 경사각 Math.atan (y/xx)에 Math.PI/6을 가산하여 angle1을 구합니다.마찬가지로 점E의 각도 angle2를 구합니다.angle1과 angle2를 알면 점 D와 점 E의 좌표는 삼각함수에 의해 쉽게 구할 수 있습니다.만약을 위해 각 변수, 정수에 대해 설명을 하겠습니다.수목 곡선 그리기 프로그램
import java . applet . Applet ;
import java . awt . * ;

public class Tree extends Applet {

public void init ( ) {
setBackground ( Color . yellow ) ;
}

public void paint ( Graphics g ) {

// 3쌍의 점을 지정합니다
Point P = new Point ( 100 , 400 ) ;
Point Q = new Point ( 100 , 100 ) ;
Point R = new Point ( 250 , 400 ) ;
Point S = new Point ( 250 , 100 ) ;
Point T = new Point ( 400 , 400 ) ;
Point U = new Point ( 400 , 100 ) ;

// 각 쌍을 이루는 두 점 사이에 수목곡선을 그립니다
g . setColor ( Color . blue ) ;
drawTree ( g , P , Q , 3 ) ;
drawTree ( g , R , S , 4 ) ;
drawTree ( g , T , U , 5 ) ;
}

//수목곡선을 그리는 메서드
public void drawTree ( Graphics g , Point a , Point b , int n ) {

double STEM _ RATIO = 0 . 25 , BRANCH _ RATIO = 0 . 6 ;

Point c = new Point ( ) ;
Point d = new Point ( ) ;
Point e = new Point ( ) ;

int sign ;
int xx , yy ;
double angle 1 , angle 2 , center _ length , branch _ length ;

xx = b . x - a . x ;
yy = - ( b . y - a . y ) ;

angle 1 = Math . atan (( double ) yy / xx ) + Math . PI / 6 ;
angle 2 = Math . atan (( double ) yy / xx ) - Math . PI / 6 ;

center _ length = Math . sqrt ( xx * xx + yy * yy ) * ( 1 - STEM _ RATIO ) ;
branch _ length = BRANCH _ RATIO * center _ length ;

//원래의 직선이 오른쪽으로 내려가면 부호를 마이너스로 합니다
sign = ( xx > = 0 ) ? 1 : - 1 ;

c . x = ( int ) ( a . x + STEM _ RATIO * xx ) ;
c . y = ( int ) ( a . y - STEM _ RATIO * yy ) ;
d . x = c . x + sign * ( int ) ( branch _ length * Math . cos ( angle 1 )) ;
d . y = c . y - sign * ( int ) ( branch _ length * Math . sin ( angle 1 )) ;
e . x = c . x + sign * ( int ) ( branch _ length * Math . cos ( angle 2 )) ;
e . y = c . y - sign * ( int ) ( branch _ length * Math . sin ( angle 2 )) ;

// 줄기부분은 재귀를 하지 않으므로, 점A에서 점C로 실제로 선을 그습니다.
g . drawLine ( a . x , a . y , c . x , c . y ) ;

//마지막이라 실제로 선을 그습니다
if ( n < = 0 ) {
g.draw Line(c.x,c.y,b.x,b.y);//중앙부(점C에서 점B로)


g.draw Line(c.x,c.y,d.x,d.y);//왼쪽 가지(점C에서 점D로)
g.draw Line(c.x,c.y,e.x,e.y);//오른쪽 가지(점C에서 점E로)
}
//마지막이 아니므로 다시 메서드를 호출합니다(재귀 처리)
else {
draw Tree(g,c,b,n-1);//중앙부(점C에서 점B로)
draw Tree(g,c,d,n-1);//왼쪽 가지(점C에서 점D로)
draw Tree(g,c,e,n-1);//오른쪽 가지(점C에서 점E로)
}
}
}수목곡선 그리기 프로그램의 실행 결과를 그림 6에 나타냅니다.재귀 반복 횟수(왼쪽부터 3번, 4번, 5번)에 따라 점차 나무다워지는 모습을 볼 수 있습니다.이밖에도조건을바꾸어나무를그리면재미있는결과를얻을수있을것같습니다.자바 애플릿은 이쪽을 보시면 됩니다.드래곤(용)곡선 생긴 결과가 중국풍의 드래곤(용)과 비슷하다는 점에서 이 명칭이 있습니다.그림7의 왼쪽 최상부에 표시된 한 줄의 직선을 아래(재귀횟수 0회라고 적혀 있음)에 있는 것처럼 두 줄의 직선으로 바꾸고 이를 재귀적으로 반복하면 점차 드래곤으로 바뀝니다.드래곤 곡선 그리기 프로그램에 대한 설명 메인 프로그램을 paint 메서드로 작성합니다.여기에서는 드래곤 곡선을 그리기 위한 컴퓨터 화면 상의 좌표를 두 점만 지정하고(P와 Q) 두 점 사이에 드래곤 곡선을 그리기 위해서 draw Dragon 메서드를 한 번 호출하기만 하면 됩니다.
예에서는 화면상의 점 P(179, 140), 점 Q(400, 350)를 이용하여 재귀 횟수는 10회로 하고 있습니다.
이 프로그램에서 가장 중요한 drawDragon 메서드를 만듭니다.이 메서드는 주어진 원래 직선의 위치를 나타내는 점 A와 점 B의 좌표로부터 접기에 필요한 점 C의 좌표를 구한 후 다시 자기 자신을 호출합니다. 점 C의 좌표는 그림 8의 오른쪽에 나타내는 방법으로 얻을 수 있습니다.그림에서 칠한 세 개의 삼각형은 같은 모양과 크기 때문에 점 C의 좌표를 쉽게 구할 수 있습니다.묘화는 점 A에서 점 C로 향하는 직선과 점 B에서 점 C로 향하는 직선(점 C에서 점 B를 향해 거꾸로 그리면 안 된다)의 두 직선을 그립니다.드래곤 곡선 그리기 프로그램
import java . applet . Applet ;
import java . awt . * ;

public class Dragon extends Applet {

public void init ( ) {
setBackground ( Color . yellow ) ;
}

public void paint ( Graphics g ) {

// 출발점이 될 한쌍의 점을 지정합니다
Point P = new Point ( 170 , 140 ) ;
Point Q = new Point ( 400 , 350 ) ;

// 쌍을 이루는 두 점 사이에 드래곤 곡선을 그립니다
g . setColor ( Color . blue ) ;
drawDragon ( g , P , Q , 10 ) ;

}

//드래곤 곡선을 그리는 메서드
public void drawDragon ( Graphics g , Point a , Point b , int n ) {

Point c = new Point ( ) ;

int xx , yy ;
xx = b . x - a . x ;
yy = - ( b . y - a . y ) ;

c . x = a . x + ( xx + yy ) / 2 ;
c . y = b . y + ( xx + yy ) / 2 ;

//마지막이라 실제로 선을 그습니다
if ( n < = 0 ) {
g.draw Line(a.x,a.y,c.x,c.y); //점 A에서 점 C로
g.draw Line(b.x,b.y,c.x,c.y); // 점 B에서 점 C로
}
//마지막이 아니므로 메서드를 더 호출합니다(재귀 처리)
else {
draw Dragon(g,a,c,n-1);//점 A에서 점 C로
draw Dragon(g, b, c, n-1); // 점 B에서 점 C로
}
}
} 드래곤 곡선 그리기 프로그램의 실행 결과 실제로 얻은 결과를 그림 9에 나타냅니다.드래곤(용)으로 보일까요?재귀는 10번 반복했습니다.자바 애플릿은 이쪽을 보시면 됩니다.정리 재귀 프로그램이 결코 어렵지 않다는 것을 아셨을 겁니다.draw Koch 등의 재귀 메서드를 작성하기 위해서는 도형 그리기 위한 약간의 기하학적 발상이 필요한데, 이것이 지혜의 발휘이자 재미있는 점입니다.여러분도 새로운 프랙탈 도형을 그려보세요.다른 문헌이나 자료에서는 프랙탈 도형 그리기에 ′터틀 그래픽스′라고 하는 특수한 그리기법을 사용하는 것이 많은데, 이 기사에서는 그것을 사용하지 않고도 직접 그릴 수 있다는 것을 보여줬을 겁니다.터틀그래픽은그림을거북이의이동으로보아화면상의절대좌표나각도에의하지말고그전까지의이동방향에서의변화각과상대적인이동거리를주어직선을그리는방법입니다.이것을 사용하기 위해서는 전용 클래스 라이브러리를 입수하거나 작성할 필요가 있습니다.참고자료 이 기사는 필자의 홈페이지 「Visual C++의 쉬운 사용법(14)-재귀 프로그램의 예제로서의 프랙탈 도형 그리기-(다만 현재는 Visual C++ 2005 Express Edition판으로 개정)」를 Java 언어로 고쳐 써 알기 쉽게 가필한 것입니다.덧붙여서, 터틀·그래픽을 사용하고 있는 것에는, 이하의 자료가 있습니다.『자바 알고리즘사전』 오쿠무라 하루히코 외 저, 기술평론사, 2003년 5월
자바의 첫 알고리즘 입문 하서 아사오 저, 기술평론사, 2001년 6월
자바그래픽스 완전제패 세리자와 히로시 저, 기술평론사, 2001년 12월
반응형