굉장하지 않나요? 각운동 단원에서(벡터의 각을 찾기 위한) 탄젠트, (극좌표를 데카르트 좌표로 변환하기 위한) 사인과 코사인를 이용해 굉장한 것을 만들었습니다. 이걸로 만족할 수도 있습니다. 하지만 아직 더 남았어요. 지금까지 한 것은 단지 시작일뿐입니다. 사인과 코사인은 수학 공식과 직각삼각형에만 쓰이지 않고 무궁무진합니다.
사인 함수의 그래프를 살펴보도록 하겠습니다. y = sine(x) 입니다.
사인 함수는 -1과 1 사이의 값을 교차하며 부드러운 곡선을 그리는 함수입니다. 이런 움직임을 진동(oscillation)한다고 말합니다. 진동은 두 점 사이를 반복적으로 왔다 갔다 하면서 움직이는 주기적인 운동입니다. 기타 줄을 퉁기기, 추의 흔들림, 콩콩이로 뛰기 같은 모든 것들이 진동 운동의 예입니다.
위 사실을 알고 나면, 기쁘게도 사인 함수의 결과를 객체 위치에 지정하여 ProcessingJS 프로그램에서 진동을 시뮬레이션할 수 있다는 것을 금방 눈치챘을 겁니다. 노이즈 단원에서 펄린 노이즈에 적용했던 것과 똑같은 방법을 쓸 겁니다.
아주 기본적인 시나리오로 시작해 보겠습니다. 원이 배경의 왼쪽과 오른쪽 사이를 진동운동 하도록 만들어봅시다.
이것을 단진동(다른 말로는 “물체의 주기적인 사인형 진동(periodic sinusoidal oscillation)”) 이라고 합니다. 만들기에 단순한 프로그램이지만, 코딩을 시작하기 전에 진동(그리고 파동)과 관련된 몇 가지 용어에 익숙해지도록 하겠습니다.
단진동은 다음과 같이 두 개의 요소를 가지는 시간 함수로, 임의의 위치(여기에서는 x 위치)로 표현할 수 있습니다.
  • 진폭(amplitude): 진동의 중심으로부터 극점까지 움직인 거리
  • 주기(period): 왕복운동을 한 번 할 때까지 걸리는 시간
위의 사인 그래프를 살펴보면 진폭은 1이고 주기는 TWO_PI라는 것을 알 수 있습니다. 사인 함수의 결과는 절대로 1보다 크거나 -1보다 작지 않습니다. 그리고 TWO_PI 라디안(또는 360도)마다 파동이 반복됩니다.
이제 ProcessingJS 세계에서 진폭은 무엇이고 주기는 무엇일까요? 진폭은 픽셀 단위로 비교적 쉽게 나타낼 수 있습니다. 200 픽셀 너비의 화면의 경우 중심에서 100 픽셀 오른쪽으로 100 픽셀 왼쪽으로 진동합니다. 그러므로:
// 진폭은 픽셀로 나타냅니다.
var amplitude = 100;
주기는 한 번 왕복할 때 걸리는 시간인데 ProcessingJS 세계에서는 시간은 어떻게 나타낼까요? 원이 매 3초당 진동하도록 만들고 싶으면 프로그램에서 시간을 밀리초 단위로 기록해서 (millis()을 이용하여) 객체를 진동시키는 정교한 알고리즘을 만들어 낼 수도 있습니다.
그러나 다른 방법도 있습니다. ProcessingJS 프로그램이 "프레임" 개념을 쓰고 있으므로, 기본값으로 초당 30 프레임으로 프로그램을 실행한다는 사실을 이용할 수 있습니다. ProcessingJS는 현재 프레임을 알려주는 frameCount 변수와 초당 프레임을 원하는 수치로 바꿔주는 frameRate()함수를 제공합니다. 30 FPS(frames per second)는 기본 프레임 속도입니다. 이정도면 인간에게 착시를 일으켜서 움직임이 아주 부드럽게 보이지만, 가끔 디버깅할 때 같은 경우에는 프레임 속도를 느리게 하는 것이 좋을 때도 있습니다.
프레임은 현실에서 시간이 가는 원리와 비슷하게 작동합니다. 따라서, 주기를 프레임 수로 나타내는 것이 적절할 것 같습니다. 이제 진동 운동이 30 프레임마다, 50 프레임마다, 아니면 1000 프레임마다 반복된다는 식으로 프로그램을 짜면 됩니다.
// 주기는 프레임(애니메이션에 대한 시간 단위)으로 나타냅니다. 
var period = 120;
일단 진폭과 주기가 있으므로, x를 시간의 함수로 나타낼 공식을 만들어야 합니다. 이 식으로 현재의 프레임 수를 대체할 것입니다.
var x = 진폭 * sin(TWO_PI * 프레임 총 수 / 주기);
공식을 좀 더 세분화해서 각 요소를 이해해 봅시다. 첫 번째 요소가 아마도 가장 이해하기 쉬울 겁니다. 사인 함수의 결과와 상관없이 진폭을 곱합니다. 사인 함수는 -1과 1 사이에서 진동합니다. 이 값을 진폭과 곱하면 원하는 결과를 얻을 수 있습니다. 결국, 값은 진폭과 진폭 사이를 진동합니다. (알아두기: 사인 함수의 결과를 사용자 지정 범위로 대응시키는 ProcessingJS의 map() 함수를 이용할 수도 있습니다.)
이제 사인 함수의 안에 어떤 것이 있는지 살펴 보겠습니다.
TWO_PI * frameCount / period
무엇이 일어나고 있나요? 먼저 알고 있는 것부터 시작해 봅시다. 사인 함수는 2PI 마다 반복됩니다. 즉, 0에서 시작하여 2PI, 4PI, 6PI 등 주기를 가지고 반복합니다. 주기가 120 프레임이라면 frameCount가 120 프레임, 240 프레임, 360 프레임일 때마다 진동 운동이 반복되도록 합니다. frameCount은 실제로 유일한 변수이며, 0부터 시작하여 증가합니다. 이 값을 넣으면 식이 어떤 결과를 내놓을지 함께 봅시다.
프레임 총수프레임 총수 / 주기TWO_PI * frameCount / period
000
600.5PI
1201TWO_PI
24022 * TWO_PI (or 4* PI)
기타  
frameCount / 주기 항목을 보면 완료한 왕복운동의 수를 알 수 있습니다. 그러면 물체가 반 번 왕복하는지 두 번 왕복운동을 하는지 알 수 있겠네요. TWO_PI는 한 번 왕복운동을 하는데 사인함수가 필요한 라디언이기 때문에 TWO_PI에 이 수를 곱하면 원하는 결과가 나옵니다.
위에서 배운 모든 것을 종합해서, 진폭이 100 픽셀이고 주기가 120 프레임인 원을 진동시키는 프로그램을 아래에 만들었습니다.
또한, 단위 시간당 회전수인 진동수(frequency)라는 용어를 살펴볼 필요가 있습니다. 진동수는 1을 주기로 나눈 값과 같습니다. 주기가 120 프레임이라면 한 프레임에서 왕복 회수의 1/120 만 완료된 것입니다. 그러므로 진동수는 1/120 왕복 회수/프레임입니다. 위 예제에서는 단순히 주기라는 용어를 이용하여 진동의 빠르기를 정의했으므로 진동수에 대한 변수를 만들 필요는 없었습니다.
여태까지는 오직 사인 함수(ProcessingJS의 sin())로 코딩을 했지만 똑같이 코사인 함수를 이용해도 된다는 사실을 알아야 합니다. 주기는 두 함수 모두 똑같고 큰 차이는 진폭이 1 또는 0 에서 시작한다는 점 밖에 없습니다.

본 "내추럴 시뮬레이션" 과정은 다니엘 쉬프만(Daniel Shiffman)이 저술한 "The Nature of Code"의 내용을 차용한 것이며, 본 내용물의 저작권은 Creative Commons Attribution-NonCommercial 3.0 Unported License를 적용합니다.
로딩 중