[C++] 3.2 두 공을 충돌시켜보자.

Date:     Updated:

카테고리:

태그:

인프런에 있는 홍정모 교수님의 홍정모의 게임 만들기 연습 문제 패키지 강의를 듣고 정리한 필기입니다.😀
🌜 공부에 사용된 홍정모 교수님의 코드들 보러가기
🌜 [홍정모의 게임 만들기 연습 문제 패키지] 강의 들으러 가기!


Chapter 3. 게임 물리 맛보기 : 공 두 개를 충돌시켜보자

벡터, 상대 속도 등등, 해당 강의에서 다루었던 물리학적 개념들이 문과생인 나에겐 생소하고 잘 와닿지 않았던 개념이라 강의를 필기하기에 앞서 개인적으로 따로 공부하여 정리해보았다.


📜 RigidCircle.h

#pragma once

#include "Game2D.h"

namespace jm
{
	class RigidCircle
	{
	public:
		vec2 pos;
		vec2 vel;
		RGB color = Colors::hotpink;
		float radius = 0.1f;
		float mass = 1.0f;

	public:
		void draw()
		{
			beginTransformation();
			{
				translate(pos);
				drawFilledCircle(color, radius - 1e-3f);
				setLineWidth(2.0f);
				drawWiredCircle(Colors::black, radius);
			}
			endTransformation();
		}

		void update(const float & dt)
		{
			static const vec2 gravity = vec2(0.0f, -9.8f);
			static const float coef_res = 0.7f; // coefficient of restitution
			static const float coef_friction = 0.99f; // friction (not physical)

			// numerical integration
			vel += gravity * dt;
			pos += vel * dt;

			// wall collision, friction
			if (1.0f - pos.x <= radius) // right wall
			{
				pos.x = 1.0f - radius;

				if (vel.x >= 0.0f)
					vel.x *= -1.0f * coef_res;
			}

			if (pos.x <= -1.0f + radius) // left wall
			{
				pos.x = -1.0f + radius;

				if (vel.x <= 0.0f)
					vel.x *= -1.0f * coef_res;
			}

			if (pos.y <= -1.0f + radius) // ground
			{
				pos.y = -1.0f + radius;

				if (vel.y <= 0.0f)
					vel.y *= -1.0f * coef_res;

				vel.x *= coef_friction;
			}
		}
	};
}
  • 자세한 설명은 이전 포스트 참고
  • 객체를 찍어낼 클래스
    • 변수
      • pos : 공의 위치
      • vel : 공의 속도
      • color : 공의 색깔
      • radius : 공의 반지름
      • mass : 공의 질량
    • 함수
      • draw() : 공을 그림
      • update() : 공의 위치와 속도를 업데이트함
        • case
          • 중력에 의한 영향
          • 각각의 벽에 부딪칠 때
          • 바닥으로부터 마찰력을 받을 때


📜 Vector2.h

<Vector.h>

T getDotProduct(const Vector2<T>& v) const  // 매개변수로 받은 벡터와의 내적
{
		return x * v.x + y * v.y;
}

T getMagnitude()  // 벡터의 크기 
{
		return std::sqrt(x * x + y * y);
}
  • 📜 Vector2.h 中 알아야할 함수
    • getDotProduct(const Vector2& v)
      • 나 자신(벡터)와 매개변수로 받은 벡터와의 내적 값을 계산하고 리턴한다.
        • Vector는 float을 담으니 T가 float이 되어 내적 값이 되는 float 스칼라 값 리턴
    • getMagnitude()
      • 벡터의 크기를 계산하고 리턴한다.
        • Vector는 float을 담으니 T가 float이 되어 크기 값이 되는 float 스칼라 값 리턴


📜 main : 두 공의 충돌 처리

#include "Game2D.h"
#include "Examples/PrimitivesGallery.h"
#include "RandomNumberGenerator.h"
#include "RigidCircle.h"
#include <vector>
#include <memory>

namespace jm
{
	class Example : public Game2D
	{
	public:
		RigidCircle rb0, rb1;

		Example()
			: Game2D()
		{
			reset();
		}

		void reset() 
		{
			// 두 공의 멤버 값들 초기화 
			rb0.pos = vec2(-0.8f, 0.3f);
			rb0.vel = vec2(5.0f, 0.0f); 
			rb0.color = Colors::hotpink;
			rb0.radius = 0.1f;
			rb0.mass = 1.0f; // 

			rb1.pos = vec2(0.8f, 0.3f);
			rb1.vel = vec2(-5.0f, 0.0f); 
			rb1.color = Colors::yellow;
			rb1.radius = 0.15f;
			rb1.mass = rb0.mass * std::pow(rb1.radius / rb0.radius, 2.0f);
		}

		void drawWall()  // 벽을 그린다.
		{
			setLineWidth(5.0f);
			drawLine(Colors::blue, { -1.0f, -1.0f }, Colors::blue, { 1.0f, -1.0f });
			drawLine(Colors::blue, { 1.0f, -1.0f }, Colors::blue, { 1.0f, 1.0f });
			drawLine(Colors::blue, { -1.0f, -1.0f }, Colors::blue, { -1.0f, 1.0f });
		}

		void update() override
		{
			const float dt = getTimeStep() * 0.4f;
			const float epsilon = 0.5f; // 반발계수

			// physics update
			rb0.update(dt);  
			rb1.update(dt);

			// check collision between two rigid bodies
			const float distance = (rb0.pos - rb1.pos).getMagnitude(); 

			if (distance <= rb0.radius + rb1.radius)   // 충돌했을 때 
			{
				// compute impulse
				const auto vel_rel = rb0.vel - rb1.vel;    // 상대 속도 구하기
				const auto normal = (rb0.pos - rb1.pos) / (rb0.pos - rb1.pos).getMagnitude(); // 노말 벡터 구하기 n1

				if (vel_rel.getDotProduct(normal) < 0.0f) // approaching, 두 개의 공이 가까워지고 있을 때만 충격량 계산
				{      
					const auto impulse = normal * -(1.0f + epsilon) * vel_rel.getDotProduct(normal) /
						((1.0f / rb0.mass) + (1.0f / rb1.mass));

					// update velocities of two bodies
					rb0.vel += impulse / rb0.mass; // 🔴원의 새로운 속도 
					rb1.vel -= impulse / rb1.mass; // 🟡원의 새로운 속도
				}
			}

			// draw
			drawWall();
			rb0.draw();
			rb1.draw();

			// reset button
			if (isKeyPressedAndReleased(GLFW_KEY_R)) reset();
		}

	};
}

int main(void)
{
	jm::Example().run();

	return 0;
}
  • RigidCircle rb0 : 🔴공 객체
  • RigidCircle rb1 : 🟡공 객체
  • void drawWall() : 벽 그리는 함수
  • void reset() : 공 객체들의 멤버변수들 초기화하는 함수
    • 두 공 🔴🟡이 서로 좌우 대칭으로 마주보는 것으로 시작하게끔 위치를 초기화
    • 속도는 반대 방향
    • rb0.mass = 1.0f;
      • 🔴질량을 1.0f이라고 가정한다면
    • rb1.mass = rb0.mass * std::pow(rb1.radius / rb0.radius, 2.0f);
      • 🟡질량은 🔴보다 반지름 크면 🔴질량보다 더 크도록 조정.
      • 3차원이라면 2.0f 이 아닌 3.0f으로 세제곱해야 함.
      • 🔴🟡 둘이 밀도가 같다고 가정.
      • 가벼운 공(질량이 더 작은 공)이 충돌시 더 멀리 튕긴다.

⭐void update() override⭐

매 프레임마다 실행되며 두 공의 충돌 처리를 해주고 그로 인한 두 공의 위치와 속도를 업데이트 해줄 것이다.

  • dt : 시간
  • const float epsilon = 0.5f;
    • 반발 계수
    • 반발 계수를 0.0으로 한다면 충돌시 둘이 딱 달라 붙을 것임
      • 즉 서로 안 튕김
    • 반발 계수가 클 수록 두 공이 충돌시 멀리 튕긴다.
  • rb0.update(dt); rb1.update(dt);
    • 각 RigidCircle 🔴🟡 의 위치와 속도를 업뎃함.
      • 각 🔴🟡의 시간에 따른 위치,속도 업뎃
      • 각 🔴🟡의 벽 충돌시 처리
      • 각 🔴🟡의 바닥 충돌시 마찰력
  • const float distance = (rb0.pos - rb1.pos).getMagnitude();
    • 두 공의 중심간의 거리 p0과 p1의 거리
      • \(\vert{p_0-p_1}\vert\)
    • 📜Vector2 객체의 getMagnitude() 함수를 사용하여 구한다.

두 공의 충돌 조건

  • 두 공이 닿았다면
    • & 두 공이 가까워 지고 있는 상태였다면
      • 충격량을 구하여
      • 속도 변화량을 구하고
      • 각각 두 공의 속도를 속도변화량 만큼 더 해 업뎃해준다.

distance <= rb0.radius + rb1.radius 두 공이 닿아있는 것과 동시에 vel_rel.getDotProduct(normal) < 0.0f : 🟡👉🏻🔴상대 속도🟡👉🏻🔴노말 벡터의 내적값이 음수여야 한다.

🟡공이 작용, 🔴공이 반작용 하는 입장이라고 가정

  • ✨✨if(distance <= rb0.radius + rb1.radius)✨✨
    • 두 공 사이의 거리가 두 공의 반지름을 합한 것 보다 같거나 작다면 두 공이 닿아 있다는 것을 의미한다.
    • const auto vel_rel = rb0.vel - rb1.vel;
      • 🟡공이 느끼는 🔴공의 상대 속도
    • const auto normal = -(rb1.pos - rb0.pos) / (rb1.pos - rb0.pos).getMagnitude();
      • 노말 벡터 \(\vec{n_1}\)
        • 🟡공에서 🔴공으로 향하는 방향(\(\vec{n_1}\))만을 나타내는 벡터
      • 두 공의 중점을 이은 선과 평행
    • ✨✨if (vel_rel.getDotProduct(normal) < 0.0f)✨✨
      • 두 공이 닿아있다는 사실만으로는 두 공이 충돌 효과를 일으키는지 알 수 없다.
        • 두 공이 닿았더라도 충돌 효과를 일으키지 않는 경우
          • vel_rel(\(\vec{v_{0\vert{1}}}\))와 normal(\(\vec{n_1}\))의 내적값이 0 혹은 양수인 경우
          • image
            • 🟡공이 느끼는 🔴공의 상대 속도와 🟡공에서 🔴공으로 향하는 방향(\(\vec{n_1}\))이 이루는 각이 예각, 혹은 직각이라면 두 공이 우연히 닿았다 하더라도 충돌 효과는 일어나지 않는다.
            • 원래 🟡공과 🔴공이 안그래도 멀어지고 있던 상태였기 때문.
            • 즉 🟡공이 느끼는 🔴공의 상대 속도가 🔴공에서 🟡공으로 향하는 방향(노멀 벡터)의 내적값이 0이상이라면 충돌로 인한 속도 업데이트는 하지 않는다.
            • 충격량을 얻을 필요가 없다.
        • 두 공이 닿아 있고 동시에 충돌 효과를 일으키는 경우
          • vel_rel(\(\vec{v_{0\vert{1}}}\))와 normal(\(\vec{n_1}\))의 내적값이 음수인 경우
          • image
            • 🟡공이 느끼는 🔴공의 상대 속도와 🟡공에서 🔴공으로 향하는 방향(\(\vec{n_1}\))이 이루는 각이 둔각
            • 원래 🟡공과 🔴공이 안그래도 가까워지고 있던 상태였기 때문.
            • 즉 🟡공이 느끼는 🔴공의 상대 속도가 🔴공에서 🟡공으로 향하는 방향(노멀 벡터)의 내적값이 음수라면 충돌 효과가 있는 것이므로 그로 인한 속도 업데이트를 해야 한다.
            • 충격량이 얻어지는 상태가 된다.
      • const auto impulse = normal * -(1.0f + epsilon) * vel_rel.getDotProduct(normal) /((1.0f / rb0.mass) + (1.0f / rb1.mass));
        • 속도 변화량을 구하기 위해 충격량 (\(j\)) 를 계산해야함.
          • \[j={-(1+\epsilon)\vec{v_{0\vert{1}}}\cdot\vec{n_1}\over{1\over{m_0}}+{1\over{m_1}}}\]
        • const auto impulse는 충격량(\(j\))이 아니라 충격 벡터 (\(\vec{J}=j×\vec{n_1}\))다.
          • 가속도로서 공의 속도에 벡터를 더해주어야 하니까 충격량(\(j\))에 노말 벡터(\(\vec{n_1}\))를 곱해 충격 벡터(\(\vec{J}\))로 만들었다.
          • \(\vec{J}=\vec{n_1}×j\) = normal * -(1.0f + epsilon) * vel_rel.getDotProduct(normal)/((1.0f / rb0.mass) + (1.0f / rb1.mass))
            • 왼쪽 밑줄이 \(\vec{n_1}\)이고 오른쪽 밑줄이 충격량(\(j\))이다.
              • vel_rel.getDotProduct(normal) 👉🏻 \(\vec{v_{0\vert{1}}}\cdot\vec{n_1}\)
      • 두 공의 충돌로 인한 🔴공,🟡공 속도 업데이트 (가속도 더해주기)
        • rb0.vel += impulse / rb0.mass;
        • rb1.vel += impulse / rb1.mass;
        • 가속도 = \(△\vec{v}={\vec{J}\over{m}}\)


🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우 
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄

맨 위로 이동하기

댓글남기기