Flash CS4 + ActionScript 3.0로 재귀를 이용한 프렉탈 나무 그리기 테스트.

우상단 단계수 옆 입력창에 1~11 사이의 숫자를 입력한 뒤 엔터키를 누르면 화면을 지우고 해당 단계의 프렉탈 나무를 새로 그린다.

재귀를 이용, 피보나치 수열 만들어 배열에 저장하기.
재귀를 이용, 프렉탈 나무 그리기.

최근 읽은 유키 히로시의 책 '프로그래머, 수학으로 생각하라' 6장 재귀 관련 내용 중 재귀함수를 이용, 프렉탈 나무를 그리는 의사 코드가 나왔기에 플래시로 구현해 봤다.
처음엔 x,y좌표를 바꾸는 방식으로 시도해 봤다가 실패. 이후 cos, sin이용해 각도 변화 뒤 이동시키는 방법 사용해 일단 성공.
한데 가지 수를 늘릴 수록 오차가 커지는 문제에 봉착. 나무가 좌우 동일 형태가 나와야 하는데 왼쪽으로 기울어진 형태가 되고 11단계 쯤 가면 아예 괴상한 결과가 나온다.
카오스의 원인이라고 하는 컴퓨터의 소수점 이하 계산 생략이 축적되어 오차가 생기는 듯 하지만 그렇다고 치기엔 너무 오차가 크다. 역시 뭔가 코딩 논리 상의 문제일 듯한 가능성도 커보인다.
원주율 3.14를 Math.PI로 변경했지만 그리 오차가 많이 수정되진 않는다.
만들고 난 뒤 보니 전반적으로 코드가 좀 지저분.
이후 단계수를 키보드로 입력받아 해당 단계에 맞는 프렉탈 나무를 새로 그리는 기능 추가.

책에 나온 의사코드는 아래와 같은 것.

function drawTree(n){ //재귀를 이용 나무 그리기.
     if(n==0){
          //아무것도 안함
     }else{
          left(); //왼쪽으로 각도 변화
          forward(); //앞으로 그리기
          drawTree(n-1); //숫자 줄이고 재귀 호출
          back(n); //뒤로 돌아가기
          right(); //오른쪽으로 각도변화(원래 중앙 각도로 복귀)
         
          right(); //오른쪽으로 각도 변화
          forward(); //앞으로 그리기
          drawTree(n-1); //숫자 줄이고 재귀 호출
          back(n); //뒤로 돌아가기
          left(); //왼쪽으로 각도변화(원래 중앙 각도로 복귀)
     }
}

15/5/25 월

화면에 'pointM'란 이름의 삼각형 무비클립과 'pNumTxt'란 이름의 입력 텍스트 창, 'explainTxt'란 이름의 다이나믹 텍스트 창을 미리 만들어 두다.

var ix=250, iy=300; //시작 x,y좌표
var tx=250, ty=270; //목표 x,y좌표
var fiboArr:Array=new Array(); //피보나치 수 담기용 배열
var fb=0; //피보나치 수 계산용 변수
var pnt:MovieClip=pointM; //삼각형 무비클립
var lr=0.40; //가지 벌어지는 각도(라디안)
var bl = 20; //가지 길이
var processNum=0; //단계수 저장용 변수

function init(){
     processNumSet();
     
     lineDraw01(ix,iy,tx,ty);
     ix = tx, iy = ty;
     
     pnt.x = tx, pnt.y = ty;
     pnt.tx = 0, pnt.ty = 0;
     pnt.theta = 4.7124; //각도(라디안) 4.71 라디안 = 270도
     drawTree(processNum);

     for(var i=0;i<12;i++){ //피보나치 수 만들어 배열에 담기
          fiboArr.push(fibonacci(i));
     }
     trace(fiboArr);
     
     stage.addEventListener(KeyboardEvent.KEY_UP,keyUps);
}
function processNumSet(){ //단계수 결정하기
     processNum = Number(pNumTxt.text);
     if(processNum > 12){
          explainTxt.text="11이하의 숫자를 입력해 주세요. 11단계의 프렉탈 나무입니다.";
          processNum = 11;
     }else if(processNum < 1){
          explainTxt.text="1이상의 숫자를 입력해 주세요. 1단계의 프렉탈 나무입니다.";
          processNum = 1;
     }else{
          explainTxt.text=processNum+"단계의 프렉탈 나무입니다";
     }
}
function keyUps(e:KeyboardEvent){ //키보드 눌렀다 뗄 경우
     switch(e.keyCode){
          case(13):{
               graphics.clear();
               ix=250, iy=300;
               tx=250, ty=270;
               init(); 
               break;               
          }
     }
}
function loop(e:Event){  } //기본 루프
function fibonacci(n){ //재귀 이용 피보나치 수 만들어 리턴
     if(n==0){
          return 0;
     }else if(n==1){
          return 1;
     }else{
          return fibonacci(n-1)+fibonacci(n-2);
     }
}
function drawTree(n){ //재귀를 이용 나무 그리기.
     if(n==0){
          //아무것도 안함
     }else{
          bl -= 2; //가지 길이 약간 줄이기
          rotateMove(-lr, bl); //왼쪽으로 30도 각도 변화, 이동
          lineDraw01(ix,iy,pnt.tx,pnt.ty,1,Math.random()*0xffffff);
          pnt.x = pnt.tx, pnt.y = pnt.ty;
          ix = pnt.tx, iy = pnt.ty;
          drawTree(n-1); //숫자 줄이고 재귀 호출
          rotateMove(Math.PI, bl); //원래 위치로 각도 변화, 이동
          pnt.theta += Math.PI+lr;
          pnt.x = pnt.tx, pnt.y = pnt.ty;
          ix = pnt.tx, iy = pnt.ty;     
          bl += 2; //가지 길이 복원
          
          bl -= 2; //가지 길이 약간 줄이기
          rotateMove(lr, bl); //오른쪽으로 각도 변화, 이동
          lineDraw01(ix,iy,pnt.tx,pnt.ty,1,Math.random()*0xffffff);
          pnt.x = pnt.tx, pnt.y = pnt.ty;
          ix = pnt.tx, iy = pnt.ty;
          drawTree(n-1); //숫자 줄이고 재귀 호출
          rotateMove(Math.PI, bl); //원래 위치로 각도 변화, 이동
          pnt.theta += Math.PI-lr;
          pnt.x = pnt.tx, pnt.y = pnt.ty;
          ix = pnt.tx, iy = pnt.ty;
          bl += 2; //가지 길이 복원
     }
}
function rotateMove(omega, length){//각도 변화시키기
     pnt.theta += omega;
     //위치계산
     pnt.tx = pnt.x + length * Math.cos(pnt.theta);
     pnt.ty = pnt.y + length * Math.sin(pnt.theta);
     pnt.rotation = pnt.theta*(180/Math.PI); //라디안 > 도 변화하여 회전
}
function lineDraw01(mX,mY,tX,tY,style1=1,style2=0){ //선그리기 함수
     //(mX,mY)위치로 이동, (tX,tY)까지 style(굵기,색)대로 선그리기
     graphics.lineStyle(style1, style2);
     graphics.moveTo(mX, mY);
     graphics.lineTo(tX, tY);
}
init();