If you're seeing this message, it means we're having trouble loading external resources on our website.

웹 필터가 올바르게 작동하지 않으면 도메인 *. kastatic.org*.kasandbox.org이 차단되어 있는지 확인하세요.

주요 내용

뉴턴의 운동 법칙

지난 단원 마지막 예제에서는 원에서부터 화면 상의 마우스까지를 가리키는 벡터를 기반으로 동적인 가속도를 계산하는 방법을 알아보았습니다. 그 결과로 움직임은 원과 마우스가 자석에 이끌리는 듯한, 마우스로 원을 끌어당기는 힘이 있는 듯한 모습을 보였습니다.
이 단원에서는 힘과 가속도의 관계에 대한 개념을 공식화해 보겠습니다. 이 단원의 목표는 물체 여러 개를 화면상에서 움직이게 하고 여러 종류의 자연적인 힘에 반응하도록 하는 것입니다.
힘을 실제로 코드에서 구현방법을 살펴보기 전에 현실에서 힘이 무엇을 의미하는지, 힘의 개념이 무엇인지 한 번 살펴봅시다. 벡터(vector)처럼, 힘(force)은 다양한 의미를 가지고 있습니다. 힘은 어떤 강력한 물리력, 이를테면, 바위를 큰 힘으로 미는 것을 의미하는 개념이 될 수 있습니다. 여기서 다룰  의 정의는 물리학에서 배우는 힘이고, 아이작 뉴턴(Isaac Newton)의 운동의 법칙에서 비롯된 개념입니다.
힘은 질량을 지닌 물체를 가속하는 벡터입니다.
힘은 벡터 라는 정의의 첫 번째 부분은 이미 배웠습니다. 고맙게도 이미 한 단원 동안 벡터의 정의와 PVector를 이용하여 프로그램하는 방법을 배웠습니다!
힘의 개념과 관련된 뉴턴의 운동의 법칙 3개를 살펴보겠습니다.

뉴턴의 제1법칙

뉴턴의 제1법칙은 일반적으로 다음과 같이 정의합니다.
정지해 있던 물체는 계속 정지 상태로 있고 움직이던 물체는 계속 움직인다
그러나 여기에는 힘과 관련된 중요한 요소가 빠져 있습니다. 제1 법칙의 정의를 다음과 같이 확장시켜 보겠습니다.
움직이지 않는 물체는 계속 움직이지 않으려 하고 움직이는 물체는 불균형력이 가해지지 않는 한 일정한 속도와 방향으로 계속 움직이려 한다.
뉴턴이 등장했을 당시, 아리스토텔레스(Aristotle)에 의해 공식화된 운동 이론은 거의 2천년이나 된 것이었습니다. 이것은 물체가 움직이고 있는 경우 계속 움직이려면 어떤 종류의 힘이 필요하다는 것입니다. 움직이는 물체를 밀지 않거나 당기지 않으면 물체는 느려지거나 멈출 것입니다. 그렇죠?
물론 이것은 사실이 아닙니다. 애초에 힘이 아예 없으면 물체가 계속 움직이게 하는 데 어떠한 힘도 필요하지 않습니다. 지구 대기권에 던져진 (공과 같은) 물체는 공기 저항(힘) 때문에 느려집니다. 힘이 없거나 물체에 가해지는 힘이 서로 상쇄되면 즉, 알짜 힘이 결국 0이 되면 물체의 속도는 일정하게 유지됩니다. 이것을 평형(equilibrium)이라 합니다. 일단 공기 저항력이 중력과 같아지면 떨어지는 공은 (일정하게 유지되는) 종단 속도에 도달하게 될 것입니다.
두 사람이 입으로 바람을 불어서 추를 밀고 있는 그림
모든 힘이 서로 상쇄(알짜 힘이 0이 됩니다.)되기 때문에 추는 움직이지 않습니다.
ProcessingJS 세계에서 뉴턴의 제1법칙을 다음과 같이 다시 기술할 수 있습니다.
객체의PVector 속도는 평형의 상태에 있는 경우 일정하게 유지된다.
잠시 (본 과정에서 가장 중요한 법칙인) 뉴턴의 제2법칙을 건너뛰고 제3법칙을 살펴보도록 하겠습니다.

뉴턴의 제3법칙

이 법칙은 종종 다음과 같이 기술됩니다.
모든 작용은 반대방향으로 똑같은 힘을 가지는 반작용이 있습니다.
이 법칙은 설명하는 방식이 약간 혼란스러울 수 있습니다. 우선 한 힘이 다른 힘을 일으키는 것 같습니다. 맞습니다. 여러분이 누군가를 밀면 그 사람도 마찬가지로 여러분을 반대로 밀 겁니다. 그러나 이런 작용과 반작용은 뉴턴의 제3 법칙의 정의에서 말하는 작용과 반작용과는 다릅니다.
여러분이 벽을 민다고 가정해 봅시다. 벽은 적극적으로 여러분을 반대로 밀지는 않습니다. 벽은 여러분을 밀만 한 “근원”이 되는 힘을 갖고 있지 않습니다. 그러나 여러분의 미는 힘에는 “작용/반작용이 쌍”이 되는 두 개의 힘이 포함됩니다.
이 규칙을 더 나은 방법으로 설명해 보겠습니다.
힘은 항상 쌍으로 발생합니다. 두 힘은 세기가 같지만 방향은 반대입니다.
이 설명을 듣고 나니, 힘이 항상 서로를 상쇄시키는 것 같아서 여전히 혼란스러울 수 있습니다. 하지만 항상 그렇지는 않습니다. 힘은 서로 다른 물체들에 작용한다는 것을 기억하세요. 그리고 두 힘이 같다고 움직임도 항상 같지는 않습니다(물체가 운동을 중단하는 현상과 같은).
정지 상태인 트럭을 밀어본다고 가정해 봅시다. 트럭이 여러분보다 훨씬 더 힘이 세지만, 움직이는 트럭과 달리 정지된 트럭은 절대로 여러분을 반대로 밀어서 뒤로 나가떨어지게 하지는 못합니다. 여러분이 트럭에 가하는 힘은 여러분의 손에 가해진 힘과 같은 크기를 갖고 반대 방향으로 작용합니다. 결과는 다양한 요소에 따라 달라집니다. 트럭이 얼음 낀 비탈길 위에 있는 작은 트럭이라면 아마도 트럭을 움직일 수 있을 겁니다. 그러나 트럭이 비포장도로 위에 있는 매우 큰 트럭을 세게 밀면 손을 다칠 수도 있습니다.
롤러 스케이트를 신고 트럭을 밀면 어떻게 될까요?
롤러 스케이트를 타면서 트럭을 미는 남자
뉴턴의 제3 법칙을 ProcessingJS에 맞게 다시 기술해 보겠습니다.
물체 B에 대한 물체 A의 힘인 PVector f 를 계산한다면 반드시 물체 B가 물체 A에 가하는 힘인 PVector.mult(f,-1);도 적용해야 한다.
나중에 ProcessingJS 프로그래밍 세계에서 항상 위 법칙을 따를 필요는 없다는 사실을 알게 될 것입니다. 때로 물체 사이의 중력을 구현하고 싶을 때 서로 크기는 같으나 방향은 반대인 힘을 모델링 합니다. 단순히 바람이 좀 부는 것 같은 동작을 만들 때 물체가 공기에 가하는 힘을 모델링 하지는 않습니다. 사실 공기는 아예 모델링 하지 않습니다! 정밀하게 모든 것을 완벽히 시뮬레이션을 하는 것이 아니라 단지 자연계의 물리 현상에서 영감을 얻는다는 사실을 기억하기 바랍니다.

뉴턴의 제2법칙

그리고 이제 ProcessingJS 프로그래머에게 가장 중요한 법칙을 살펴보겠습니다.
전통적으로 이 규칙은 다음과 같이 정의합니다.
힘은 질량 곱하기 가속도이다.
또는
F=MA
왜 이 법칙이 가장 중요할까요? 식을 다른 방식으로 써 보겠습니다.
A=F/M
가속도는 힘과 비례하고 질량에 반비례합니다. 즉, 더 세게 밀면 밀수록 더 빨리 움직입니다(가속합니다). 여러분이 크면 클수록 더 느리게 움직이게 됩니다.
무게(weight) 와 질량(mass)
물체의 질량은 물체에 포함되어 있는 물질의 양입니다. (킬로그램 단위로 측정합니다.)
질량과 혼동되는 무게는 사실 물체에 작용하는 중력의 힘을 말합니다. 뉴턴의 제 2 법칙에 따라 질량 곱하기 중력 가속도 (w = m * g)를 통해 무게를 계산할 수 있습니다. 무게는 뉴턴으로 측정합니다.
밀도는 질량을 단위 부피로 나눈 값으로 정의합니다. (예: 그램 당 세제곱 센티미터.)
지구에서 질량이 1 kg 인 물체는 달에서도 질량이 1 kg 입니다. 그러나 무게는 1/6로 줄어듭니다.
이제 ProcessingJS의 세계에서 질량은 무엇일까요? 픽셀을 이용하여 처리하면 되지 않을까요? 간단히 하기 위해 상상의 픽셀 세계에서 모든 물체들의 질량은 1이라고 하겠습니다. F/1 = F입니다. 따라서, 다음이 성립합니다.
A=F
물체의 가속도는 힘과 같습니다. 매우 좋은 소식이에요. 벡터 단원에서 가속도는 화면상 물체의 움직임을 제어하는 핵심적인 요소라는 것을 배웠습니다. 위치는 속도로 조정하고 속도는 가속도로 조정합니다. 모든 움직임은 작은 가속도에서 시작됩니다. 이제 이 진정으로 모든 것을 움직인다고 알 수 있습니다.
Mover 객체를 만들 때 배웠던 것처럼 현재 위치, 속도, 가속도를 갖는 객체를 만들어 봅시다. 지금 목표는 이 객체에 힘을 부여하는 것입니다:
mover.applyForce(wind);
또는
mover.applyForce(gravity);
이때, 바람과 중력은 PVector입니다. 뉴턴의 제2법칙에 따라 이 함수를 다음과 같이 구현할 수 있습니다.
Mover.prototype.applyForce = function(force) {
        this.acceleration = force;
};

힘의 축적

지금까지 만든 코드는 꽤 괜찮아 보입니다. 결국 가속도 = 힘 이라는 것은 (질량을 고려하지 않은) 뉴턴의 제2 법칙을 정의 그대로 해석한 것입니다. 그런데 여기에는 꽤 큰 문제가 있습니다. 지금 이루고자 하는 목표는 화면상에서 바람과 중력에 의해 움직이는 객체를 생성하는 것입니다.
mover.applyForce(wind);
mover.applyForce(gravity);
mover.update();
mover.display();
이제 잠깐 컴퓨터 입장에서 생각해 봅시다. 먼저 바람의 applyForce()를 호출합니다. 그러면 Mover 객체의 가속도는 바람 PVector로 설정됩니다. 두 번째로 중력의 applyForce()를 호출합니다. 이제 Mover 객체의 가속도가 중력 PVector에 설정됩니다. 세 번째로 update()를 호출합니다. update()에서 무슨 일이 생길까요? 속도에 가속도를 더하게 됩니다.
velocity.add(acceleration);
프로그램에 어떠한 에러도 없을 것 같지만, 좀 이상합니다! 큰 문제가 생겼어요. 가속도가 속도에 더해지면 어떤 값이 나오나요? 바로 중력의 힘과 같습니다. 바람은 미처 고려하지 않았습니다. 한 번 이상 applyForce()을 호출하면 이전 호출을 무시하고 덮어쓰게 됩니다. 하나 이상의 힘을 어떻게 처리해야 할까요?
사실 지금까지는 뉴턴의 제2 법칙을 단순하게 해석했습니다. 뉴턴의 제2 법칙을 좀 더 정확하게 정의하면 다음과 같습니다.
알짜 힘은 질량 곱하기 가속도와 같다.
이는 가속도가 힘의 총량 을 질량으로 나눈 값과 같다는 말과 똑같습니다. 상당히 합리적인 정의 같군요. 결국 뉴턴의 제1법칙과 같이, 모든 힘의 합이 0이 되면 물체는 평형 상태(가속도가 존재하지 않는 상태)가 됩니다. 프로그래밍에서는 힘의 축적이라고 알려진 프로세스를 통해 이를 구현할 수 있습니다. 이는 실제로 매우 간단해서, 단지 모든 힘을 더하면 됩니다. 주어진 어떤 순간에 1, 2, 6, 12, 303 가지 힘이 존재할 수도 있습니다. 그러나 객체가 그 힘을 축적하는 방법을 알게 된다면, 제아무리 많은 힘이 객체에 작용하더라도 크게 문제 될 것이 없습니다.
이제 applyForce() 메소드를 변경하여 힘을 축적하면서 가속도에 새로운 힘을 더해봅시다.
Mover.prototype.applyForce = function(force) {
    this.acceleration.add(force);
};
아직 끝나지 않았습니다. 힘의 축적을 구현하기 위해 한 가지를 더 해야 합니다. 계속해서 모든 힘을 더해야 하므로, 매번 update()가 호출되기 전에 확실하게 가속도를 없애야 (가속도를 0으로 설정)합니다. 잠시 바람에 대해 생각해 봅시다. 바람은 매우 강력하기도 약하기도 하고 아예 없기도 합니다. 어떤 순간, 예를 들면 사용자가 마우스를 누르는 순간에는 엄청난 돌풍이 불 수도 있습니다:
if (mouseIsPressed) {
  var wind = new PVector(0.5, 0);
  mover.applyForce(wind);
}
사용자가 마우스를 떼면 바람은 멈추지만 뉴턴의 제1법칙에 의하면 물체는 일정한 속도로 계속 움직입니다. 그런데 가속도를 0으로 놓지 않는다면, 바람이 계속 작용하게 됩니다. 더 안 좋은 것은 힘이 축적되기 때문에 힘이 전 프레임의 힘과 계속해서 합쳐진다는 것입니다.
이 시뮬레이션에서 가속도는 기억이 없습니다. 가속도는 단지 특정 시간에 존재하는 자연의 힘을 계산할 뿐입니다. 이는 위치의 경우처럼 다음 위치로 옮길 때 꼭 전 위치를 알고 있어야 하는 것과 다릅니다.
각 프레임에 대해 가속도를 없애는 가장 쉬운 방법은 update()의 마지막 PVector에 0을 곱하는 것입니다.
Mover.prototype.update = function() {
    this.velocity.add(this.acceleration);
    this.position.add(this.velocity);
    this.acceleration.mult(0);
};

질량

Mover 클래스에 힘을 통합하고 예제를 살펴보기 전에 한 가지 작은 사항을 추가해야 합니다. 뉴턴의 제2 법칙은 실제로 A=F가 아닌 F=MA입니다. 질량을 부여하는 것은 객체에 프로퍼티를 추가하는 것만큼 쉽습니다. 그러나 약간 복잡한 문제가 생기기 때문에 여기서 해결하고 넘어갈 필요가 있습니다.
측정 단위
질량에 대해 생각하기 시작했으니 측정 단위를 짚고 넘어가는 것이 중요합니다. 실제 세상에서 측정을 하면 특정 단위를 이용합니다. 물체가 3 m 떨어져 있다거나, 야구공이 90 mph로 움직이고 있다거나, 볼링공의 질량이 6 kg라고 말이죠. 나중에는 실제 단위를 이용하는 경우도 알아보겠지만, 이 단원에서는 대부분 실제 단위는 무시할 것입니다.
여기서는 단위로 픽셀(예:두 원이 100픽셀 떨어져 있다)과 프레임(예:원이 프레임당 2픽셀로 움직이고 있다)을 사용합니다. 질량의 경우에는 사용할만한 단위가 없어서 임의의 수를 사용합니다. 다음의 예제에서는 임의로 10.을 골랐고 단위는 없습니다.
편의를 위해 물체의 질량과 크기를 묶도록 하겠습니다. 물체의 질량이 10이면 반지름이 10인 원을 그리는 식입니다. 이렇게 하면 물체의 질량을 시각화하여 프로그램에서 질량이 야기하는 효과를 관찰할 수 있습니다. 실제 세계에서는 크기가 질량을 나타내지 않습니다. 높은 밀도 때문에 작은 쇠 공은 큰 풍선보다 더 큰 질량을 갖습니다.
질량은 벡터가 아니라 스칼라(부동 소수점)입니다. 질량은 어떤 물체를 구성하는 물질의 양을 나타내는 수입니다. 물체의 면적을 질량으로 계산할 수도 있지만 물체의 질량을 10이라고 정하면 프로그램이 간단해집니다.
var Mover = function() {
    this.mass = 10;
    this.position = new PVector(random(width), random(height));
    this.velocity = new PVector(0, 0);
    this.acceleration = new PVector(0, 0);
};
객체들이 서로 다양한 질량을 가지고 있어야만 흥미로운 일이 생기긴 하지만, 일단 이렇게 간단하게 시작해 봅시다. 질량은 어디에 사용될까요? 바로 뉴턴의 제2법칙을 객체에 적용할 때 질량을 이용합니다.
Mover.prototype.applyForce = function(force) {
  force.div(this.mass);
  this.acceleration.add(force);
};
이번에도 코드가 꽤 합리적으로 보이더라도 큰 문제가 있습니다. 다음과 같이 두 개의 Mover 객체가 있는 시나리오를 살펴봅시다. 두 객체 모두 바람의 힘으로 멀리 날아가고 있습니다.
var m1 = new Mover();
var m2 = new Mover();

var wind = new PVector(1, 0);

m1.applyForce(wind);
m2.applyForce(wind);
다시 컴퓨터 입장에서 생각해 봅시다. m1.applyForce()는 바람의 힘 (1,0)을 받아 질량 (10)으로 나누어 가속도에 이를 더합니다.
코드wind
var wind = new PVector(1, 0);(1, 0)
m1.applyForce(wind)(0.1, 0)
이제 객체 m2를 봅시다. 이 객체도 바람의 힘 (1,0)을 받습니다. 잠깐, 바람의 힘이 얼마이죠? 잘 보면 바람의 힘이 이제 (0.1,0)입니다!
객체를 사용할 때 주의할 점 기억하시나요? JavaScript에서 PVector 같은 객체에 할당된 변수는 사실 메모리에 있는 객체의 포인터를 가지고 있습니다. 함수에 객체를 전달하면, 복사본을 전달하는 것이 아니라 기존의 포인터를 전달하게 됩니다. 따라서 함수가 객체를 수정하면(질량으로 나누는 것처럼) 그 객체는 영구적으로 바뀝니다.
그래서 오류가 발생한 것입니다. m2에는 m1의 질량으로 나눈 힘이 아니라 원래의 힘 (1,0)을 적용해야 합니다. 따라서 질량으로 나누기 전에 PVector f를 복사해 놓아야 합니다.
다행히 PVector 객체는 복사본을 만들어주는 메소드 get()을 가지고 있습니다. get()은 같은 데이터를 가진 새 PVector 객체를 반환합니다. 그러면 applyForce()를 다음과 같이 수정해 볼 수 있습니다:
Mover.prototype.applyForce = function(force) {
    var f = force.get();
    f.div(this.mass);
    this.acceleration.add(f);
};
또 다른 방법으로는, 이전 단원에서 배운 정적 함수를 이용하여 정적 div() 함수를 사용해 메소드를 다시 쓸 수 있습니다.
Mover.prototype.applyForce = function(force) {
  var f = PVector.div(force, this.mass);
  this.acceleration.add(f);
};
여기에서 중요한 것은, 여러 개의Mover 객체들에 적용할 수 있도록 기존 힘 벡터에 영향을 주지 않는 방법을 찾아야 한다는 점입니다

힘의 생성

이제 힘이 어떤 것인지는 잘 압니다(벡터입니다). 힘을 객체에 적용하는 방법도 살펴보았습니다(힘을 질량으로 나눈 후 객체의 가속도 벡터에 추가합니다). 빠뜨린 것이 있나요? 힘을 처음에 어떻게 얻는지를 알아내야 하네요. 힘은 어디에서 오는 걸까요?
이 단원을 통해 ProcessingJS 세계에서 힘을 생성하는 두 가지 방법을 살펴볼 것입니다.
  • 그냥 힘을 만들어내세요. 여러분은 프로그래머이자 창조주입니다. 새로운 힘을 만들어 적용하지 못할 이유가 없습니다.
  • 힘을 모델링하세요. 맞아요. 힘은 실존하는 양입니다. 물리 교과서에서도 이런 힘을 수식으로 표현한 공식이 실려있습니다. 이 공식을 이용하여 소스 코드로 변환하고 실세계의 힘을 ProcessingJS에서 모델링합니다.
힘을 만드는 가장 쉬운 방법은 그냥 숫자를 선택하는 것입니다. 바람을 시뮬레이션하는 것부터 시작하겠습니다. 오른쪽으로 부는 약한 바람은 어떨까요? Mover 객체 m을 가정하면 코드는 다음과 같습니다.
var wind = new PVector(0.01, 0);
m.applyForce(wind);
결과는 별로 흥미롭지는 않지만 시작하기에는 괜찮습니다. PVector 객체를 생성하고 초기화한 후 다른 객체로 전달합니다(차례로 그 객체 자체가 지닌 가속도에도 적용될 것입니다.). 바람과 중력(좀 더 강하고 아래를 향합니다.) 두 개의 힘이 필요하므로 다음과 같이 나타내 봅시다.
var wind = new PVector(0.01, 0);
var gravity = new PVector(0, 0.1);
m.applyForce(wind);
m.applyForce(gravity);
이제 서로 다른 크기와 방향을 갖는 두 개의 힘이 있습니다. 두 객체 모두 m에 적용합니다. 이제야 좀 윤곽이 보이네요. 이제 ProcessingJS로 객체가 돌아다닐 환경을 만들었습니다. 이 환경에서는 객체가 움직임에 반응합니다.
모든 코드를 다 결합한 프로그램은 다음과 같습니다:
우와! 정말 많은 것을 살펴보았네요. 그래도 이제는 더 많은 것을 할 수 있게 되었습니다. 계속해서 힘을 이용하는 방법을 배우세요!

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