Silverback9

#야생으로

Creative Coding 독학 제240일 2024년11월22일(금)

오늘은 우리 어린이들의 슈퍼 스타 공Dragon의 깃털이 우아하게 펼쳐지며 전체 모습이 화면에 다 담기는 장관을 표현해 보겠습니다~^^*

먼저, 깃털이 펼쳐지는 속력을 조절해 보겠습니다. 깃털이 커질 수록 좀 천천히 펼쳐지면 우아한 느낌을 들 것 같아서요~^^*

회전속도조절을 위한 글로벌 변수를 하나 준비하겠습니다~

let rotationSpeed = 0.1;

클래스 Segment 내장함수 update()에서 회전각에 회전속도조절 변수 rotationSpeed를 더하겠습니다.

update() {
    
    this.angle += rotationSpeed;
    
    if (this.angle > PI/2) {
      this.angle = PI/2;
      this.completed = true;
    }

마우스가 클릭되어 깃털의 회전이 일어날 때마다 회전속도조절 변수 rotationSpeed를 90% 줄이도록 하겠습니다.

function mousePressed() {
        .
        .
        .
  rotationSpeed *= 0.9;
  //회전 속도를 점차 줄여 나가겠습니다~
}

깃털이 커질 수록 천천히 회전하는 우리들의 우아한 Dragon Curve를 보실까요~^^*

이번에는, 깃털이 회전을 할 때마다 깃털의 길이가 줄어들어 전체 크기가 작아지도록 만들어 보겠습니다~^^*

전체 크기를 조절하는 글로벌 변수를 하나 장만해 보겠습니다~

let zoom = 1;

화면 중앙을 중심으로 상대좌표체계를 만들고~ 전체 크기 scale을 zoom의 크기에 따라 변화시키겠습니다~

깃털 보여주기가 한 번 끝나면, zoom를 살짝 줄여나가겠습니다.

그런데, 함수 draw() 안에서 변수 zoom의 크기가 줄어들기 때문에, 마우스 클릭과는 상관없이 줄어들게 될 것 입니다~^^*

function draw(){

  background(220);
  
  translate(width/2, height/2);
  scale(zoom);

  for (let s of segments) {
    
    if(!s.completed) {
      s.update();
    }
    s.show();
  } 
  zoom -= 0.001;
}  

scale 전체 크기가 줄어들게 되어서, 선의 굵기도 가늘어 지게 되는데요. 이를 예방하기 위해서, 선의 굵기를 zoom으로 나누어, 점점 굵어지도록 만들겠습니다~^^*

정수를 분수로 나누면, 값이 커지겠지요? 2/0.001 = 2 * 1000 = 2000 이 되니까요~^^*

클래스 Segment의 내장 함수 show()의 내용을 개선해 보겠습니다~^^*

 show(){
    stroke(0);
    strokeWeight(2/zoom);
    line(this.a.x, this.a.y, this.b.x, this.b.y);
  }

자 그럼 이제 전체 코드를 우리 함께 살펴 볼까요~~^^* 가볍게 살짝쿵만 훑어 보셔도 좋을 것 같습니다~^^*

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 += rotationSpeed;
    
    if (this.angle > PI/2) {
      this.angle = PI/2;
      this.completed = true;
    }
    
    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;

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

function mousePressed() {
  
  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);
  //화면 중앙을 원점으로 삼겠습니다~^^*
  scale(zoom);
  //전체 크기를 변수 zoom에 맞추어 조절하겠습니다~^^*

  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();
  } 
  zoom -= 0.001;
  //크기를 점차 줄여나가겠습니다~^^*
}  

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

전체 크기가 줄어들면서 펼쳐지는 Dragon Curve를 우리 함께 보러 갈까요~~^^*

전체크기가 마우스 클릭과 상관없이, 무한 반복 함수인 draw()에서 줄어들게 되어, 어느 순간 너무 빨리 줄어 들다가~~ 잉크 굵은 한 방울이 떨어지는 느낌의 장면이 펼쳐지게 되네요…

우리 이것을 내일 개선해 볼까요~~^^*

마우스 클릭과 상관없이 Dragon의 깃털이 펼쳐지면 좀더 자연스러울 것 같아요~~^^*

좀 막연하고 어렵게 느껴지지만…우리 한 번 도전해 볼까요!!!

오늘 저와 함께 우아하게 펼쳐지면서 점점 크기가 작아져서 화면에 전체 모습이 다 담기는 zoom-out 애니메이션 version 1을 완성해 주셔서 감사합니다~^^*

네~~^^* 우리 내일은 version 2를 도전해 보면 좋겠어요~~^^*

오늘도

멋진 아침 잘 마무리 하시고요!

점심도 맛있게 드시고요!

멋진 하루 보내시고요!

내일 우리 또 만나요~~^^*

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

댓글 남기기