내적의 성질
벡터의 내적은 같은 차원의 두 벡터가 주어졌을 때, 벡터를 구성하는 각 성분을 곱한 후 더하여 스칼라를 더하내는 연산이다. 내적은 곱셈 기호와 동일한 가웃뎃점(.)을 사용한다.
1. 벡터의 내적은 교환법칙이 성립하며, 결합 법칙은 성립하지 않는다.
2. 같은 벡터를 내적하면 벡터의 크기를 제곱한 결과가 나온다.
내적과 삼각함수와의 관계
벡터 공간에서 직교하는 두 표준기저벡터(1,0) 과 (0,1)을 내적한 결과는 0이 나온다.
두 기저벡터 (cos θ, sin θ), (-sin θ, cos θ)는 항상 직교하기 때문에 두 벡터의 내적은 언제나 0이 나온다.
Vector2 vec1(2.f, 3.f);
Vector2 vec2(4.f, 5.f);
// 두 벡터의 내적을 구하고 dot에 내적 결과를 구한다.
float dot = vec1.Dot(vec2);
벡터의 시야 판별
두 벡터가 이루는 사잇각의 범위에 따라 내적의 부호가 결정된다.
첫 번쨰 벡터를 캐릭터의 시선 방향으로 생각한다면 두 번째 벡터는 시선 벡터에서 멀어질 수록 사잇각이 커진다.
하지만 cos 함수는 ( - 90 °, 90 ° ) 영역에 대해 양의 부호를 가지므로 두 번째 벡터가 [-90 ° , 90 ° ]의 영역 내에 존재하게 된다면 언제나 양의 부호를 가지게 된다.
특징 정리
- 벡터 내적의 결과가 양수 : 두 벡터는 같은 방향을 보고 있음
- 벡터 내적의 결과 음수 : 두 벡터는 다른 방향을 향하고 있음.
- 벡터 내적의 결과 0 : 두 벡터는 서로 직교한다.
즉, 이것을 캐릭터의 시야각으로 다시 한번 정리하자면 (f, v는 벡터라고 가정하자),
- f * v > 0 : 캐릭터가 앞에 목표물이 존재
- f * v < 0 : 캐릭터 뒤에 목표물이 존재
- f * v = 0 : 캐릭터 바로 앞에 목표물이 존재
내적을 활용한 목표물 감지
// 게임 로직을 담당하는 함수
void SoftRenderer::Update2D(float InDeltaSeconds)
{
// 게임 로직에서 사용하는 모듈 내 주요 레퍼런스
auto& g = Get2DGameEngine();
const InputManager& input = g.GetInputManager();
// 게임 로직의 로컬 변수
static float moveSpeed = 100.f;
// rd, mt는 균등 분포에 사용되는 난수를 사용하기 위해 선언된 변수
static std::random_device rd;
static std::mt19937 mt(rd());
static std::uniform_real_distribution<float> randomPosX(-300.f, 300.f);
static std::uniform_real_distribution<float> randomPosY(-200.f, 200.f);
static float duration = 3.f;
static float elapsedTime = 0.f;
static Vector2 targetStart = targetPosition;
static Vector2 targetDestination = Vector2(randomPosX(mt), randomPosY(mt));
// 시야각의 cos 값은 최초 1회만 계산해 보관.
// 플레이어의 시야각의 절반값에 대한 cos 함수 값을 구한다.
static float halfFovCos = cosf(Math::Deg2Rad(fovAngle * 0.5f));
// 프레임 시간을 더해, 경과 시간이 duration을 넘지 않도록, Clamp 함수로 조절.
elapsedTime = Math::Clamp(elapsedTime + InDeltaSeconds, 0.f, duration);
// 지정한 시간이 경과하면 새로운 이동 지점을 랜덤하게 설정
if (elapsedTime == duration)
{
targetStart = targetDestination;
targetPosition = targetDestination;
targetDestination = Vector2(randomPosX(mt), randomPosY(mt));
elapsedTime = 0.f;
}
else // 비율에 따라 목표지점까지 선형보간하면서 이동
{
float ratio = elapsedTime / duration;
targetPosition = Vector2(
Math::Lerp(targetStart.X, targetDestination.X, ratio),
Math::Lerp(targetStart.Y, targetDestination.Y, ratio)
);
}
Vector2 inputVector = Vector2(input.GetAxis(InputAxis::XAxis), input.GetAxis(InputAxis::YAxis)).GetNormalize();
Vector2 deltaPosition = inputVector * moveSpeed * InDeltaSeconds;
// 플레이어의 시야각 확보. Y축을 향하게 함
Vector2 f = Vector2::UnitY;
Vector2 v = (targetPosition - playerPosition).GetNormalize();
// 물체의 최종 상태 설정
if (v.Dot(f) >= halfFovCos)
{
playerColor = LinearColor::Red;
targetColor = LinearColor::Red;
}
else
{
playerColor = LinearColor::Gray;
targetColor = LinearColor::Blue;
}
playerPosition += deltaPosition;
}
// 렌더링 로직을 담당하는 함수
void SoftRenderer::Render2D()
{
// 렌더링 로직에서 사용하는 모듈 내 주요 레퍼런스
auto& r = GetRenderer();
const auto& g = Get2DGameEngine();
// 렌더링 로직의 로컬 변수
static float radius = 5.f;
static std::vector<Vector2> sphere;
// 시야각을 그리는 보조선의 길이
static float sightLength = 300.f;
if (sphere.empty())
{
for (float x = -radius; x <= radius; ++x)
{
for (float y = -radius; y <= radius; ++y)
{
Vector2 target(x, y);
float sizeSquared = target.SizeSquared();
float rr = radius * radius;
if (sizeSquared < rr)
{
sphere.push_back(target);
}
}
}
}
// 플레이어 렌더링.
// 플레이어 렌더링.
float halfFovSin = 0.f, halfFovCos = 0.f;
// 시야각을 나타내는 보조선을 그리기 위해 sin, cos 함수 계산
Math::GetSinCos(halfFovSin, halfFovCos, fovAngle * 0.5f);
// 시야각을 나타내는 두 보조선과 프레이어의 시선 벡터를 그려준다.
r.DrawLine(playerPosition, playerPosition + Vector2(sightLength * halfFovSin, sightLength * halfFovCos), playerColor);
r.DrawLine(playerPosition, playerPosition + Vector2(-sightLength * halfFovSin, sightLength * halfFovCos), playerColor);
r.DrawLine(playerPosition, playerPosition + Vector2::UnitY * sightLength * 0.2f, playerColor);
for (auto const& v : sphere)
{
r.DrawPoint(v + playerPosition, playerColor);
}
// 타겟 렌더링
for (auto const& v : sphere)
{
r.DrawPoint(v + targetPosition, targetColor);
}
// 주요 정보 출력
r.PushStatisticText(std::string("Player Position : ") + playerPosition.ToString());
r.PushStatisticText(std::string("Target Position : ") + targetPosition.ToString());
}