[C++] 3.2 두 공을 충돌시켜보자.
카테고리: C++ games
인프런에 있는 홍정모 교수님의 홍정모의 게임 만들기 연습 문제 패키지 강의를 듣고 정리한 필기입니다.😀
🌜 공부에 사용된 홍정모 교수님의 코드들 보러가기
🌜 [홍정모의 게임 만들기 연습 문제 패키지] 강의 들으러 가기!
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
- 중력에 의한 영향
- 각각의 벽에 부딪칠 때
- 바닥으로부터 마찰력을 받을 때
- 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 스칼라 값 리턴
- 벡터의 크기를 계산하고 리턴한다.
- getDotProduct(const Vector2
📜 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 🔴🟡 의 위치와 속도를 업뎃함.
- 각 🔴🟡의 시간에 따른 위치,속도 업뎃
- 각 🔴🟡의 벽 충돌시 처리
- 각 🔴🟡의 바닥 충돌시 마찰력
- 각 RigidCircle 🔴🟡 의 위치와 속도를 업뎃함.
- const float distance = (rb0.pos - rb1.pos).getMagnitude();
- 두 공의 중심간의 거리 p0과 p1의 거리
- \(\vert{p_0-p_1}\vert\)
- 📜Vector2 객체의 getMagnitude() 함수를 사용하여 구한다.
- 두 공의 중심간의 거리 p0과 p1의 거리
두 공의 충돌 조건
- 두 공이 닿았다면
- & 두 공이 가까워 지고 있는 상태였다면
- 충격량을 구하여
- 속도 변화량을 구하고
- 각각 두 공의 속도를 속도변화량 만큼 더 해 업뎃해준다.
- & 두 공이 가까워 지고 있는 상태였다면
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}\))만을 나타내는 벡터
- 두 공의 중점을 이은 선과 평행
- 노말 벡터 \(\vec{n_1}\)
- ✨✨if (vel_rel.getDotProduct(normal) < 0.0f)✨✨
- 두 공이 닿아있다는 사실만으로는 두 공이 충돌 효과를 일으키는지 알 수 없다.
- 두 공이 닿았더라도 충돌 효과를 일으키지 않는 경우
vel_rel
(\(\vec{v_{0\vert{1}}}\))와normal
(\(\vec{n_1}\))의 내적값이 0 혹은 양수인 경우-
- 🟡공이 느끼는 🔴공의 상대 속도와 🟡공에서 🔴공으로 향하는 방향(\(\vec{n_1}\))이 이루는 각이 예각, 혹은 직각이라면 두 공이 우연히 닿았다 하더라도 충돌 효과는 일어나지 않는다.
- 원래 🟡공과 🔴공이 안그래도 멀어지고 있던 상태였기 때문.
- 즉 🟡공이 느끼는 🔴공의 상대 속도가 🔴공에서 🟡공으로 향하는 방향(노멀 벡터)의 내적값이 0이상이라면 충돌로 인한 속도 업데이트는 하지 않는다.
- 충격량을 얻을 필요가 없다.
- 두 공이 닿아 있고 동시에 충돌 효과를 일으키는 경우
vel_rel
(\(\vec{v_{0\vert{1}}}\))와normal
(\(\vec{n_1}\))의 내적값이 음수인 경우-
- 🟡공이 느끼는 🔴공의 상대 속도와 🟡공에서 🔴공으로 향하는 방향(\(\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}\)
- 왼쪽 밑줄이 \(\vec{n_1}\)이고 오른쪽 밑줄이 충격량(\(j\))이다.
- 가속도로서 공의 속도에
- 속도 변화량을 구하기 위해 충격량 (\(j\)) 를 계산해야함.
- 두 공의 충돌로 인한 🔴공,🟡공 속도 업데이트 (가속도 더해주기)
- rb0.vel += impulse / rb0.mass;
- rb1.vel += impulse / rb1.mass;
- 가속도 = \(△\vec{v}={\vec{J}\over{m}}\)
- 두 공이 닿아있다는 사실만으로는 두 공이 충돌 효과를 일으키는지 알 수 없다.
🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄
댓글 남기기