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

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

주요 내용

용수철의 힘

이 단원의 시작 부분에서는 사인 파동을 픽셀에 매핑해서 단진동을 모델링했고, 사인 파동을 이용하여 용수철에 달린 추를 모델링 했습니다. sin() 함수를 이용하면 코드 한 줄로 빠르고 간편하게 실행할 수는 있습니다. 하지만 실질적으로 용수철에 추를 매달아 여러 배경 힘(바람, 중력 등)에 반응하는 2차원 공간을 만들고자 한다면 이대로는 안 됩니다. 이런 동작을 (진자와 같지만 줄이 용수철인 경우) 시뮬레이션하기 위해서는 PVector를 이용하여 용수철의 힘을 모델링해야 합니다.
용수철의 힘은 1660년에 공식을 만든 영국 물리학자 로버트 훅(Robert Hooke)의 이름을 딴 훅의 법칙을 사용해 계산합니다. 로버트 훅은 자신의 법칙을 라틴어로 기술하였습니다: "Ut tensio, sic vis," 는 “길어질수록 힘도 증가한다” 라는 뜻입니다. 이렇게 생각해 봅시다:
용수철의 힘은 용수철의 늘어난 길이와 정비례한다.
다시 말하면 추를 많이 잡아 당길수록 힘이 세진다는 말입니다. 추를 조금 잡아 당기면 힘은 약해집니다. 이를 공식으로 쓰면 다음과 같습니다.
Fspring=k×x
  • k는 상수이고 궁극적으로 이 값에 따라 힘의 크기가 달라집니다. 이 값은 용수철이 탄력적인지 뻣뻣한지 나타냅니다
  • x는 용수철의 늘어난 길이로, 늘어난 길이와 원래 길이의 차이로 나타냅니다. 원래 길이는 평형 상태인 용수철의 길이로 정의합니다.
힘은 벡터이므로 크기와 방향을 계산해야 한다는 것을 기억하세요. 용수철 그림을 한 번 더 보고 프로그램에 집어넣어야 할 요소를 살펴봅시다.
위 그림에서 보이는 것처럼, 먼저 다음과 같은 초기 변수 세 개를 만들고 적절한 값을 넣어봅시다.
var anchor = new PVector(100, 10);
var bob = new PVector(110, 100);
var restLength = 20;
첫 번째로 훅의 법칙으로 힘의 크기를 계산해봅시다. 우선 kx값을 알아야 합니다. k는 상수이기 때문에 아무 숫자를 대입해도 됩니다.
var k = 0.1;
x는 “현재의 길이와 원래 길이의 차이”로 정의하므로 좀 더 복잡합니다. 정지 상태의 길이는 변수 restLength로 정의합니다. 그럼 용수철의 현재 길이는 얼마일까요? 바로 축(anchor) 과 추(bob) 사이의 거리입니다. 그러면 이 거리를 어떻게 계산할까요? 축에서 추를 가리키는 벡터의 크기는 얼마나 클까요? (중력 단원에서 거리를 계산하는 과정과 똑같습니다.)
var dir = PVector.sub(bob, anchor);
var currentLength = dir.mag();
var x = currentLength - restLength;
힘의 크기(-1 * k * x) 계산에 필요한 요소들을 정리해 보았습니다. 이제 힘의 방향을 가리키는 단위벡터가 필요합니다. 좋은 소식은 이미 이 벡터를 알고 있다는 것입니다. 조금 전에 거리를 어떻게 계산할지, 축에서 추를 가리키는 벡터의 크기는 얼마나 될지 고민했습니다. 여기서 나온 벡터가 바로 힘의 방향입니다!
위 그림을 보면, 용수철을 정지 상태에서 길이를 늘이면 축 방향으로 다시 끌어당기는 힘이 있다는 것을 알 수 있습니다. 그리고 정지 상태에서 길이를 줄이면 축 반대 방향으로 밀어내는 힘이 발생합니다. 반대 방향으로 생기는 힘은 공식에 있는 - 부호와 상관이 있습니다. 이제 거리를 계산할 때 사용한 PVector를 정규화하기만 하면 됩니다! 코드를 보면서 해당 PVector 변수를 “force”로 이름을 바꿔봅시다.
var k = 0.01;
var force = PVector.sub(bob, anchor);
var currentLength = force.mag();
var x = currentLength - restLength;
// 용수철 힘의 방향, 단위벡터
force.normalize();
// 방향과 크기를 합칩니다
force.mult(-1 * k * x);
이제 용수철 힘 벡터를 계산하는 알고리즘을 만들었습니다. 문제는 어떤 객체 지향 프로그래밍 구조를 사용해야 할지 정하는 것입니다. 다시 말하지만, 프로그래밍에 “정답”은 없습니다. 여러 가지 가능성을 살펴보세요. 이럴 때는 프로그램의 목적과 자기만의 코딩 스타일에 따라 선택하면 됩니다. 그래도 여태까지 Mover 객체를 다뤘으니 같은 프레임워크를 사용해 봅시다. Mover 객체를 용수철의 추라고 생각해 보겠습니다. 추는 화면에서 돌아다닐 위치, 속도 및 가속도 벡터가 필요합니다. 그런데 이 값은 이미 다 알고 있습니다. 추는 applyForce() 메소드를 통해 중력의 영향을 받습니다. 여기에 한 단계 더 나아가서 용수철 힘을 적용해야 합니다:
var bob = new Bob();

draw = function()  {
  // 임의로 정한 중력
  var gravity = new PVector(0, 1);
  bob.applyForce(gravity);
  // 용수철의 힘도 계산하고 더해야 합니다
  var spring = ????
  bob.applyForce(spring);

  // 항상 사용하던 update()와 display() 메소드
  bob.update();
  bob.display();
};
한 가지 방법은 모든 용수철 힘 코드를 주 draw() 반복문에 쓰는 것입니다. 그러나 수많은 추와 용수철이 연결되어있는 상황을 생각해보면 추가적으로 Spring 객체를 쓰는 것이 바람직합니다. 위 그림과 같이 Bob 객체는 추의 움직임을 제어하고 Spring 객체는 용수철의 축과 정지 상태의 길이를 저장하고 추에 가해지는 용추철의 힘을 계산합니다.
이를 이용하면 다음과 같이 여러 코드를 깔끔하게 묶을 수 있습니다:
var bob = new Bob();
var spring = new Spring();

draw = function()  {
  // 임의로 정한 중력
  var gravity = new PVector(0, 1);
  bob.applyForce(gravity);
  // Spring.connect()는 
  // 용수철 힘의 계산과 적용을 책임집니다
  spring.connect(bob);

  // 항상 사용하던 ()와 display() 메소드
  bob.update();
  bob.display();
};
이것은 중력 단원에서 했던 것과 꽤 비슷합니다. 그때는 다음과 같이 썼습니다:
var force = attractor.calculateAttraction(mover);
mover.applyForce(force);
지금처럼 용수철을 이용할 때도 비슷합니다.
var force = spring.calculateForce(bob);
bob.applyForce(force);
그럼에도 불구하고 이 예제에서는 다음과 같이 썼습니다:
spring.connect(bob);
왜 그럴까요? 추 부분에 applyForce()를 호출할 필요는 없을까요? 당연히 추에 대해 applyForce()를 호출할 필요가 있습니다. 이 기능을 구현할 때 추에 applyForce()draw()에서 호출하는 방법을 굳이 쓰지 않아도 된다는 사실을 보여주고 싶었습니다. connect() 메소드에 요청하여 내부적으로 추에 'applyForce()'를 호출하도록 하는 것도 매우 합리적이고 때로는 오히려 더 나은 방안이 될 수 있습니다.
Spring.prototype.connect(bob) {
  var force = /* 이런저런 계산*/;
  bob.applyForce(force);
};
Attractor 객체를 사용할 때와 Spring 객체를 사용할 때의 방법이 다를까요? 처음에 힘을 배울 때는 더 명확하게 전달하기 위해 draw() 반복문에 적용되는 모든 힘을 보여주었고 이는 힘 축적을 배우는 데 도움이 되었을 겁니다. 지금은 좀 더 익숙해졌으니 아마 객체 자체에 좀 더 구체적인 내용을 심는 편이 단순하게 느껴질 겁니다.
모든 코드를 합쳐서 아래 프로그램에 적용했습니다. 이 수업에서 여러 가지를 추가해보았습니다. (1) 먼저 Bob 객체에 마우스와 상호작용할 수 있는 함수를 넣어 추를 창에서 드래그할 수 있도록 하고, (2) Spring 객체에 최솟값과 최댓값을 두고 용수철 길이를 이 사이에 제한하는 함수를 넣었습니다.

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