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

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

주요 내용

중력 인력

세상에서 가장 유명한 힘은 아마도 중력일 것입니다. 사람들은 중력을 생각할 때 아이작 뉴턴(Isaac Newton) 의 머리에 떨어진 사과를 함께 떠올립니다. 중력은 물체가 아래로 떨어지게 만듭니다. 그러나 이는 우리가 직접 경험해 본 것일 뿐입니다. 사실은 지구가 중력으로 사과를 끌어당기는 것처럼 사과도 지구를 끌어당깁니다. 문제는 지구가 너무 거대해서 지구상의 모든 다른 물체가 가진 중력을 압도하는 것입니다. 질량을 가진 모든 물체는 모든 다른 물체에 중력을 가합니다. 아래의 그림과 같이 중력의 세기를 계산하는 식이 있습니다.
두 구체 사이에 작용하는 중력
이 식을 좀 더 자세히 살펴보겠습니다.
  • F는 중력을 나타내는 값으로, 최종적으로 계산하여 applyForce() 함수에 전달하려는 벡터입니다.
  • G만유 인력의 상수 로 6.67428 x 10^-11 초(s) 제곱 분의 킬로그램(kg) 분의 미터(m) 세제곱 입니다. 여러분이 아이작 뉴턴(Isaac Newton) 이나 알버트 아인슈타인(Albert Einstein) 이라면 이 값이 꽤 중요합니다. 그러나 여러분이 ProcessingJS 프로그래머라면 별로 중요하지 않습니다. 다시 말하지만 이것은 힘을 보다 약하게 만들거나 보다 강하게 만드는 상수입니다. 이 값을 1로 두고 무시해도 큰일나지 않습니다.
  • m1m2는 객체 12 의 질량입니다. 뉴턴의 제2 법칙(F=MA)에서 배운것과 같이 질량을 고려하지 않아도 됩니다. 어차피 화면상 물체의 물리적 질량은 실제로 존재하지 않습니다. 그러나 이 값을 유지하는 경우 “보다 큰” 물체가 작은 물체보다 훨씬 더 강한 중력을 발휘하는 재미있는 시뮬레이션을 만들 수 있습니다.
  • r^은 객체 1 에서 객체 2 를 가리키는 단위 벡터입니다. 금방 살펴보겠지만, 한 객체에서 다른 객체의 위치를 빼서 이 방향 벡터를 계산할 수 있습니다.
  • r2는 두 물체 사이의 거리를 제곱한 것입니다. 좀 더 생각해 봅시다. 식에서 위에 있는 모든 변수—G, m_1, m2—의 값이 클 수록 힘은 더 강해집니다. 질량이 크면 힘이 셉니다. G가 크면 힘이 셉니다. 무언가를 나누면 그 영향이 반대로 됩니다. 힘의 강도는 거리 제곱에 반비례합니다. 물체가 멀리 있을수록 힘은 더 약해지고 , 가까울수록 , 더 강해집니다.
이제 식이 약간은 이해할 수 있을 것 같군요. 그림을 살펴보면서 식의 개별 요소를 분석했습니다. 이제 수학을 ProcessingJS 코드로 해석할 방법을 알아내야 합니다. 다음을 가정해 보겠습니다.
다음과 같은 조건을 만족하는 두 개의 객체가 있습니다.
  1. 각 객체의 PVector 위치는 location1location2입니다.
  2. 각 객체의 질량은 mass1mass2입니다.
  3. 만유 인력 상수는 숫자 변수 G입니다.
위와 같은 가정하에서 중력의 힘 PVector force를 계산하고자 합니다. 두 부분으로 나누어서 계산해 봅시다. 먼저 위의 식에서 힘의 방향 r^을 계산합니다. 두 번째로 질량과 거리에 따른 힘의 세기를 계산합니다.
마우스 쪽으로 가속하는 객체를 만들었던 때를 기억하시나요? 똑같은 논리를 여기에서 이용해 봅시다.
벡터는 두 점 사이의 거리입니다. 한 원에서 마우스쪽으로 가리키는 벡터를 만들기 위해서는 단순히 다른 점에서 한 점을 빼면 됩니다.
var dir = PVector.sub(mouse, location);
이 경우 객체 1이 객체 2에 가하는 인력의 방향은 다음과 같습니다.
var dir = PVector.sub(location1, location2);
방향만을 알려 주는 단위 벡터가 필요하기 때문에 위치를 뺀 후에 벡터를 정규화해야 한다는 것을 잊지 않도록 합니다.
dir.normalize();
힘의 방향을 알았습니다. 이제 벡터의 크기를 계산하고 그에 맞추어 조정하면 됩니다.
var m = (G * mass1 * mass2) / (distance * distance);
dir.mult(m);
유일한 문제는 거리를 모른다는 것입니다. G, mass1 및 mass2는 모두 주어졌으나 위의 코드가 동작하기 전에 거리를 실제로 계산해야 합니다. 그런데 방금 한 위치에서 또 다른 위치 끝까지 가리키는 벡터를 만들지 않았나요? 해당 벡터의 길이가 두 객체 사이의 거리가 되지 않을까요?
코드 한 줄을 추가해서 벡터를 정규화하기 전에 벡터의 크기를 알 수 있다면 거리도 알 수 있습니다.
// 한 객체에서 다른 객체를 가리키는 벡터
var force = PVector.sub(location1, location2);

// 해당 벡터의 길이(크기)는 두 객체들 사이의 거리입니다.
var distance = force.magnitude();

// 중력에 대한 공식을 이용하여 힘의 크기를 계산합니다.
var strength = (G * mass1 * mass2) / (distance * distance);

// 힘 벡터를 정규화하고 적절한 크기로 조정합니다.
force.normalize();
force.mult(strength);
PVector “dir”도 마찬가지로 “force”로 이름을 바꾼 점을 잘 살펴보세요. 결과적으로, 계산을 마치면 PVector는 전부터 구하고자 했던 힘의 벡터가 됩니다.
이제 (중력을 모방하는) 인력을 계산하기 위한 수학과 코드를 살펴보았습니다. 실제 ProcessingJS 프로그램의 문맥에 이 기술을 적용하는 데 집중해 보겠습니다. 본 절의 초기에 단순한 Mover 객체— PVector의 위치, 속도, 가속도 및 applyForce()를 갖는 객체를 만들었습니다. 해당 클래스를 가지고 다음과 함께 프로그램에 넣어보겠습니다.
  • 단일의 Mover object
  • 단일의 Attractor 객체(새로운 객체 형태로 고정된 위치를 갖습니다.)
그림에서와 같이 Mover object는 Attractor object쪽으로 끌리는 중력을 경험합니다.
새로운 Attractor 객체를 매우 간단한 구조로 만드는 것으로 시작할 수 있습니다. 위치와 질량을 부여하는 것과 동시에 해당 객체를 자체적으로 표시할 수 있는 메소드가 주어집니다. (질량은 곧 크기로 나타냅니다.)
var Attractor = function() {
    this.position = new PVector(width/2, height/2);
    this.mass = 20;
    this.G = 1;
    this.dragOffset = new PVector(0, 0);
    this.dragging = false;
    this.rollover = false;
};

// Method to display
Attractor.prototype.display = function() {
    ellipseMode(CENTER);
    strokeWeight(4);
    stroke(0);
    fill(175, 175, 175, 200);
    ellipse(this.position.x, this.position.y, this.mass*2, this.mass*2);
};
정의한 후에 Attractor 객체 형태의 인스턴스를 만들 수 있습니다.
var mover = new Mover();
var attractor = new Attractor();

draw = function() {
    background(50, 50, 50);

    attractor.display();
    mover.update();
    mover.display();
};
이 프로그램은 MoverAttractor 객체가 주를 이루는 프로그램으로, 좋은 구조의 예입니다. 퍼즐의 마지막 조각은 한 물체가 다른 물체를 끌어당기는 동작을 구현하는 것입니다. 두 물체를 어떻게 해야 서로 교감할 수 있을까요?
이렇게 동작할 수 있도록 프로그램을 설계하는 방법은 많이 있습니다. 다음은 그중 일부를 나타낸 것입니다.
과제함수
1. AttractorMover를 수신하는 함수attraction(a, m);
2. Mover를 전달받는 Attractor 객체 내 메소드a.attract(m);
3. Attractor를 전달받는 Mover 객체 내 메소드mover.attractedTo(a);
4. Mover를 전달받은 후 인력인 PVector를 반환하는 Attractor 객체 내의 메소드. 이후 인력은 MoverapplyForce() 메소드로 전달됩니다.`var f = a.calculateAttraction(m);
mover.applyForce(f);` |
물체가 서로 상호 작용하게 만드는 코드를 여러 가지 생각해 놓는것이 좋습니다. 그러면 위에 나열한 항목 각각에 대한 인수를 만들 수 있을 겁니다. 우선 첫 번째 항목을 보면, Mover 또는 Attractor 객체를 연결하지 않은 임의의 함수보다 객체 지향적인 방식이 실제로 훨씬 더 나은 선택이므로 버립니다. 2번과 3번의 차이는 “attactor가 mover를 끌어당기는” 부분과 “mover가 attractor를 끌어당기는” 부분에서 드러납니다. 최소한 현재까지의 과정으로는 네 번째 선택이 가장 적절한 것으로 보입니다. applyForce() 메소드를 알아내는 데 시간을 많이 보냈습니다. 따라서, 똑같은 방법을 사용하면 예제를 더 명확하게 풀 수 있을 것 같군요.
다시 말하자면, 이전에 만든 코드가 다음과 같다면,
var f = new PVector(0.1, 0); // 힘을 형성합니다.
mover.applyForce(f);
지금 코드는 다음과 같이 바꿉니다.
var f = a.calculateAttraction(m); // 두 object들 사이의 인력
mover.applyForce(f);
그리고 이제 draw() 함수를 다음과 같이 작성할 수 있습니다.
draw = function() {
    background(50, 50, 50);

    // 인력을 계산해서 적용합니다.
    var f = a.calculateAttraction(m);
    mover.applyForce(f);

    attractor.display();
    mover.update();
    mover.display();
};
거의 다 했습니다. calculateAttraction() 메소드를 Attractor 객체형 내부에 넣기 때문에 실제로 함수를 만들어야 합니다. 함수는 Mover object를 수신한 후 PVector를 반환합니다. 그리고 함수 안에 무엇이 들어갈까요? 바로 중력을 계산하기 위해 여태까지 계산한 식입니다!
Attractor.prototype.calculateAttraction = function(mover) {

    // 힘의 방향은 무엇인가요?
    var force = PVector.sub(this.position, mover.position);    
    var distance = force.mag();
    force.normalize();

    // 힘의 크기는 무엇인가요?
    var strength = (this.G * this.mass * mover.mass) / (distance * distance);
    force.mult(strength);

    // 힘을 반환해서 적용합니다!
    return force; 
};
이제 다 했습니다. 완전히 다는 아니지만 거의 다 했습니다. 아직 해결해야 할 작은 문제가 있습니다. 위의 코드를 다시 살펴봅시다. 나누기 기호, 사선(/)을 보세요. 나누기 기호를 볼 때마다 거리가 정말로 정말로 작은 값이거나 최악의 경우 0이면 어떤 일이 발생할지 잘 생각해 보세요! 어떤 수를 0으로 나눌 수는 없습니다. 그리고 어떤 수를 0.0001으로 나누면 10,000을 곱한 값과 같습니다! 그렇습니다. 우리가 쓴 공식은 현실에서 쓰이는 중력의 힘에 대한 수식이지만 지금 만드는 환경은 현실이 아닙니다. 여러분은 지금 ProcessingJS 세계 에서 프로그램을 만들고 있습니다. 그리고 ProcessingJS 세계에서 mover는 결국 attractor에 매우 근접하게 되면 힘이 너무 강해서 mover가 화면 밖으로 튕겨 나갈 수도 있습니다. 그러므로 이 수식을 이용한다면 현실적으로 접근하여 거리의 범위를 제한하는 것이 좋을 겁니다. 어쩌면 Mover 가 실제로 있는 위치에 상관없이 attractor 로부터 5 픽셀보다 작거나 25 픽셀 이상 거리를 두지 않도록 만들어야 할 수도 있습니다.
distance = constrain(distance, 5, 25);
최소 거리를 제한하는 이유와 마찬가지로, 최대 거리를 제한하는 것도 좋을 것입니다. 즉, mover가 attractor로부터 500 픽셀에 있다면(지나치게 멀지는 않은 거리입니다) 힘을 250,000으로 나누는 것입니다. 이렇게 하면 힘은 전혀 적용하지 않을 정도로 너무 약해질 수 있습니다.
이제 여러분의 프로그램 작동 방식을 스스로 결정해야 할 때입니다. 그러나 “지나치게 약하거나 강하지 않은 인력” 을 구현하고 싶다면 거리를 제한하는 것이 좋습니다.
이제 모든 것을 프로그램에 통합해봅시다. Mover 의 객체형은 전혀 바뀌지 않았지만, 이제 프로그램에 Attractor 객체와 그 둘을 함께 묶는 코드가 들어갔습니다. 또 attractor를 마우스로 제어하기 위해 코드를 프로그램에 추가해서 효과를 관찰하기 더 쉽게 되었습니다.
그리고 전에 마찰과 항력을 이용했던 것처럼, 많은 Mover 객체를 포함하기 위해 배열을 써서 이 예제를 확장할 수도 있습니다. 프로그램의 주요 변화는 Mover 객체를 조정하여 (전에 했던 것처럼) 질량값, x값, y값을 입력받고 무작위로 위치한 Mover의 배열을 초기화하고 해당 배열에 대해 반복문을 수행하여 매번 각각에 대한 인력을 계산한 것입니다.
var movers = [];
var attractor = new Attractor();

for (var i = 0; i < 10; i++) {
    movers[i] = new Mover(random(0.1, 2), random(width), random(height));
}

draw = function() {
    background(50, 50, 50);

    attractor.display();
    for (var i = 0; i < movers.length; i++) {
        var force = attractor.calculateAttraction(movers[i]);
        movers[i].applyForce(force);

        movers[i].update();
        movers[i].display();
    }
};

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