Silverback9

#야생으로

Creative Coding 독학 제241일 2024년11월23일(토)

오늘은 Zoom-Out Animation Version 2를 만들어 보겠습니다~^^* YEAH~^^*

Dragon Curve의 펼쳐짐이 한 번 완성될 때마다 Zoom-Out을 해 보는 것이 어떨까요~^^*

그런데, 깃털 펼쳐짐의 한 단계 나아감에 있어서 전체 크기에 관련하여 2의 제곱근이 관계가 있는 것 같아요.

이것을 사용하여, 한 번 회전할 때마다 전체 크기를 줄이는 비율을 찾아내어 볼 수 있을 것 같습니다~^^*

그런데요….

오늘 제가 오전부터 움직여야 해서요….저녁에 공부해서 다시 정리해 보도록 할게요…

오늘 멋진 하루 보내시고요^^*

저는 밤에 공부정리 올리러 다시 돌아올게요~^^*

금방 돌아올게요~~^^*

네~^^* 꿈은 이루어 집니다~^^*

네~^^* 꿈은 이루어 집니다~^^* 꿈을 계속 추구하면요~^^* 그래서! 오늘 공부를 다시 시작해 보겠습니다~^^* 저녁이네요~^^*

mouse를 클릭하여 회전시키는 것이 아니라, 하나의 깃털이 다 만들어지면 자연스럽게 다음 깃털을 만드는 것으로 바꾸어 보도록 하겠습니다~^^*

그래서 다음 깃털 생성을 위한 함수를 만들어 보겠습니다. 함수 mousePressed()의 내용을 새로운 함수 nextGeneration()에 담게 될 것 같아요~^^* 마우스 클릭과 연동되지 않고, 다른 함수가 호출을 할 때만 작동되게 되겠네요~^^*

function nextGeneration() {
  
  let newSegments = [];
  
  for (let s of segments) {
    
    let newS = s.duplicate(endSegment.a);
    if(firstTime) {
      newS.origin = endSegment.b.copy();
      firstTime = false;
    }
    //첫 번째 회전만 endSegment.b를 origin으로 삼겠습니다.
    //두 번째 회전부터는 모두 endSegment.a를 origin을 삼을게요~^^*
   
    newSegments.push(newS);
  }  
  
  endSegment= newSegments[0];
  //태초의 깃털이, 앞으로의 모든 회전의 중심이 되는 끝자락 깃털이 됩니다~^^*
  
  segments = segments.concat(newSegments); 
  //마우스클릭으로 생성 회전 변화된 newSegments[]를 전체 segments[]에 덧붙입니다~^^*
  
  rotationSpeed *= 0.9;
  //회전 속도를 점차 줄여 나가겠습니다~
  
}

함수 draw()에서 깃털 하나의 애니메이션이 다 보여지면 다음 깃털을 만들기 위해서 nextGeneration()을 호출하겠습니다~^^*

function draw(){

  background(220);
  
  translate(width/2, height/2);
  //화면 중앙을 원점으로 삼겠습니다~^^*
  scale(zoom);
  //전체 크기를 변수 zoom에 맞추어 조절하겠습니다~^^*
  
  let allCompleted = true; 
  //하나의 깃털의 애니메이션이 완성되었는지를 나타내는 변수 allCompleted입니다~^^*
  
  for (let s of segments) {
    
    if(!s.completed) {
      
      allCompleted = false;
      //깃털 하나의 애니메이션이 완성되지 않으면, 상태 변수 allCompleted의 진리값은 false가 됩니다. 

      s.update();
       //태초의 깃털 endSegment는 이미 completed가 true이기 떄문에, animation을 위한 update 과정을 거치지 않습니다~^^*
       //회전을 해서 생성되는 두 번째 깃털부터는 completed가 false상태로 시작하기 때문에 update()를 호출하여 animation 효과를 주겠습니다.
       //회전각이 PI/2에 다다르면, completed가 true가 되기 때문에 update()를 호출하지 않게 되어, animation이 멈추게 되겠네요~^^*
    }
    s.show();
  } 
  
  if (allCompleted) {
    nextGeneration();
    //깃털 하나의 애니메이션이 완성되면, 다음 깃털 생성을 위해 nextGeneration()을 호출합니다~^^*
  }
  
}  

이제 우리는 자연스럽게 Zoom-Out 할 수 있는 장치들을 마련해 봐야 할 것 같아요~^^*

이때 요긴하게 쓸 수 있는 함수가 하나 있다고 하는데요~~^^*

함수 lerp()입니다~^^*

lerp(a, b, amt);
구성을 하고 있는데요~^^* 
수 a와 수 b 사이의 거리를 1로 볼 때, 0과 1 사이의 크기를 가진 amt가 그 사잇값을 나타낼 수 있다고 합니다. 
예를 들어, 
lerp(0, 10, 0.5);라면 5 가 되는 것이고~
lerp(0, 10, 0.7);라면 7 이 되는 것 같아요~^^* 
amt가 0보다 작거나 1보다 크면, 0보다 작은 수 10보다 큰 수를 나타낼 수도 있어요.
lerp(0,10, - 0.5);라면 - 5 가 되는 것이고~
lerp(0, 10, 1.5);라면 15 가 되는 것 같아요~^^*

회전각을 0에서 PI/2까지 키우는 작업에 함수 lerp()를 쓸 수 있을 것 같아요!

let amt = 0;
//글로벌 변수로 선언하여, amt의 값의 변화가 회전각 크기 변화 및 zoom 크기 변화를 조절하는데 함께 사용해 보겠습니다. 

amt += 0.01;

this.angle = lerp(0, PI/2, amt);
//회전각은 0에서 PI/2 까지 변화하게 될 것 같습니다.

그럼 이제 Zoom-Out 조절에 관한 변수들을 장만해 보겠습니다~^^* 함수 lerp()도 사용해 보겠습니다~^^*

let zoom = 1;
//깃털 만들기 단계가 시작 되기 전의 zoom 크기입니다.

let targetZoom = 1;
//깃털 만들기 단계가 완료 되었을 때의 목표 targetZoom입니다. 

let amt = 0;

let newZoom = lerp(zoom, targetZoom, amt);
// 깃털 만들기 전 zoom 크기에서 깃털 만들기 완료 후 목표 zoom크기로 변해가는 새로운 newZoom입니다.

scale(newZoom);
//newZoom에 맞추어 전체크기가 조절될 것입니다. 

amt = 0;

zoom = newZoom;
//깃털 완성 후의 newZoom이 다음 깃털 만들기 단계 전의 zoom이 됩니다. 

targetZoom = zoom / sqrt(2);
//다음 깃털 완성 후의 목표 targetZoom은 전단계 zoom / 2의 제곱근 입니다. 

이제 전테 코드를 우리 함께 살펴 볼까요~^^*

allCompleted 가 true 값을 갖는 것은, amt의 값이 1일 때를 의미할 것 같네요. 회전각이 PI/2가 될때이니가 회전이 완료된 떄가 될 것 같아요. 그래서 회전 완료 시점을 파악하는 기준을 allCompleted에서 amt으로 바꾸어 볼게요.

if (allCompleted)를 if (amt >= 1)로 바꾸겠습니다~^^*

class Segment {
  constructor(a, b, origin) {
    this.startA = a;
    this.startB = b;
    
    this.a = a.copy();
    this.b = b.copy();
    
    this.origin = origin.copy();
    
    this.completed = false;
    this.angle = 0;
  }
  
  show(){
    stroke(0);
    strokeWeight(2/zoom);
    //전체 크기가 줄어들어도 선이 가늘어지지 않도록 예방하겠습니다~^^*
    line(this.a.x, this.a.y, this.b.x, this.b.y);
  }
  
  duplicate(origin) {
    return new Segment(this.a.copy(), this.b.copy(), origin);
  }
  
  update() {
    
    this.angle = lerp(0, PI/2, amt);
    //회전각을 0에서 PI/2까지 조절해 보겠습니다. 
  
    let va = p5.Vector.sub(this.startA, this.origin);
    //this.origin에서 this.startA로 향하는 벡터 va입니다.
    
    let vb = p5.Vector.sub(this.startB, this.origin);
    //this.origin에서 this.startB로 향하는 벡터 vb입니다. 
    //이 프로그램에서 벡터this.origin는 벡터this.startB와 동일할 것입니다.
    //그래서 동일 벡터 뺄셈의 결과인 벡터 vb의 크기는 0가 될 것입니다.
    
    va.rotate(-this.angle);
    vb.rotate(-this.angle);
    //반시계방향 angle 크기만큼 회전하겠습니다.
    //두 벡터의 방향은 일치합니다.
    
    this.a = p5.Vector.add(this.origin, va);
    //크기(0 + va = va)인 새로운 벡터 this.a입니다.
    this.b = p5.Vector.add(this.origin, vb);
    //크기(0 + 0 = 0)인 새로운 벡터 this.b입니다. 
    //원래 this.startB의 위치 그대로입니다.   
    
  }
}

let segments = [ ];

let endSegment;

let firstTime = true;

let rotationSpeed = 0.1;

let zoom = 1;

let targetZoom = 1;

let amt = 0;

//segment[]는 전체 깃털들을 담아내는 배열입니다~^^*.
//태초의 깃털은 회전해서 생성되는 새로운 깃털의 끝자락이 되어 또다시 회전의 중심이 되므로, 태초의 깃털을 endSegment라고 이름지어 보겠습니다~^^*.
//맨 처음의 회전의 origin과 두번째부터의 회전의 origin은 달라지지요? 그래서,상태를 나타내는 진리값 변수 firstTime로 맨 처음 회전을 특정해 보겠습니다~^^*
//회전속도를 점차 줄여나가기 위해 회전속도조절 변수 rotationSpeed를 준비해 보겠습니다~^^*
//zoom은 깃털 만들기 시작 전의 zoom 크기입니다. 
//targetZoom은 깃털 만들기 완료 후의 targetZoom 크기입니다. 
//amt는 함수 lerp()안에서 사용되어, 회전각 크기와 zoom크기 조절 역할을 할 것입니다. 
//이 일곱 가지 변수는 글로벌 변수로 지정하여, 모든 함수에서 그 값을 변화시킬 수 있도록 해보겠습니다~^^*

function nextGeneration() {
  
  let newSegments = [];
  
  for (let s of segments) {
    
    let newS = s.duplicate(endSegment.a);
    if(firstTime) {
      newS.origin = endSegment.b.copy();
      firstTime = false;
    }
    //첫 번째 회전만 endSegment.b를 origin으로 삼겠습니다.
    //두 번째 회전부터는 모두 endSegment.a를 origin을 삼을게요~^^*
   
    newSegments.push(newS);
  }  
  
  endSegment= newSegments[0];
  //태초의 깃털이, 앞으로의 모든 회전의 중심이 되는 끝자락 깃털이 됩니다~^^*
  
  segments = segments.concat(newSegments); 
  //마우스클릭으로 생성 회전 변화된 newSegments[]를 전체 segments[]에 덧붙입니다~^^*
  
  rotationSpeed *= 0.9;
  //회전 속도를 점차 줄여 나가겠습니다~
  
}

function setup(){

  createCanvas(400,400);
  
  let a = createVector(0, 200);
  let b = createVector(0, 0);
  
  endSegment = new Segment(a,b,b);
  endSegment.completed = true;
  //b를 origin으로 가지고 a와 b사이를 잇는 태초의 깃털을 글로벌 변수 endSegment에 저장합니다~^^*
  //태초의 깃털 endSegment는 animation이 완결되었다고 미리 설정하여, 움직임 update 없이 바로 고정해서 보여주겠습니다~^^*. 

  segments.push(endSegment);
  //태초의 깃털 endSegment를 전체 깃털 모음 배열인 글로변 변수 segment에 담겠습니다~^^*
}


function draw(){

  background(220);
  
  translate(width/2, height/2);
  //화면 중앙을 원점으로 삼겠습니다~^^*
  
  let newZoom = lerp(zoom, targetZoom, amt);
  scale(newZoom);
  //전체 크기를 변수 newZoom에 맞추어 조절하겠습니다~^^*
  
  amt += 0.01;

  for (let s of segments) {
    
    if(!s.completed) {
      s.update();
       //태초의 깃털 endSegment는 이미 completed가 true이기 떄문에, animation을 위한 update 과정을 거치지 않습니다~^^*
       //회전을 해서 생성되는 두 번째 깃털부터는 completed가 false상태로 시작하기 때문에 update()를 호출하여 animation 효과를 주겠습니다.
       //회전각이 PI/2에 다다르면, completed가 true가 되기 때문에 update()를 호출하지 않게 되어, animation이 멈추게 되겠네요~^^*
    }
    s.show();
  } 
  
  if(amt>=1){
    //amt가 1이 되면, 회전각이 PI/2에 다다른 것이니,
    for( let s of segments) {
      s.completed = true;
    }
    nextGeneration();
    //다음 깃털 만들기 단계를 시작하겠습니다.
    amt = 0;
    //회전각 및 zoom 조절 변수 amt도 0로 복귀시키고~
    zoom = newZoom;
    //새로운 newZoom이 다음 깃털 만들기 시작 전 zoom이 되고~
    targetZoom = zoom / sqrt(2);
    //다음 깃털 만들기 완료 후 목표 target zoom은 zoom 나누기 2의 제곱근이 되고~~
  }
}  

//클래스 Segment를 저장한 배열 segments의 구성요소인 모든 s들을 보여줍니다. 

자 그럼 우리 Dragon Curve가 차르르 자연스럽게 펼쳐지며 전체 크기가 단계 별로 2의 제곱근으로 줄어드는 Zoom-Out 화면을 함께 감상할까요~^^*

와~~^^* 우리가 해내었네요~~^^*

저와 함께 자동적으로 우아하게 Zoom-Out 되며 차르르 펼쳐지는 Dragon Curve를 완성해 주셔서 감사합니다~~^^*

이제 우리는 Fractal Part를 다 마쳤습니다~~^^* 또 하나의 긴 여정을 마무리 하였네요~~^^*

내일부터 우리는 유전 알고리즘 Genetic Algorithms 공부를 시작할 수 있게 되었어요~^^* 와우!! 생물학까지 우리가 공부하게 되네요~~^^*

생경한 도전이지만!!!

우리는 함께 해 낼 수 있을 거예요!!!

떨리고…설레고…쪼끔 부담스럽고…

내일의 도전은 내일부터 대면하기로 하고~^^*

지금은, 우리가 방금 마친 Fractal Part 마무리를 축하하면 좋겠어요~~^^*

Congratulations~ and Celebrations~^^*

그 동안 수고했다~^^* 내일의 새 도전도 잘 시작할 것이다~^^* 내면의 속삭임에 귀기울이며~^^*

편안하고도 튼튼한 심장으로 깊은 밤 토요일 밤, 한 주의 피로를 푸는 밤 보내시기 바래요~^^*

Fractal 에서 Genetics으로~^^* 멋진 터닝포인트를 준비하는 밤~^^* 편안히 보내셔요~^^*

네~^^* 꿈은 이루어 집니다~^^*

댓글 남기기