오늘은 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(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으로~^^* 멋진 터닝포인트를 준비하는 밤~^^* 편안히 보내셔요~^^*
네~^^* 꿈은 이루어 집니다~^^*
댓글 남기기