수학/이득우의 게임 수학 - 실습 예제

행렬을 사용하여 하트의 크기 변환 & 회전 변환 구현

monstro 2025. 9. 10. 14:42
728x90
반응형

- 개요

크기 변환 행렬

 

두 개의 표준기저벡터 (1, 0)(0, 1)를 열벡터로 설정하여

각각 a배b배만큼 크기를 변환하면 위와 같은 행렬을 얻을 수 있다

 

회전 변환 행렬

 

θ에 대한 회전행렬은 위와 같이 설계할 수 있다 

 

- 예제

1) 2X2 정방행렬을 표현하는 Matrix2x2 구조체

struct Matrix2x2
{
public:
	// 생성자 
	FORCEINLINE constexpr Matrix2x2() = default;
	FORCEINLINE explicit constexpr Matrix2x2(const Vector2& InCol0, const Vector2& InCol1) { Cols = { InCol0, InCol1 }; }

	// 연산자 오버로딩
	// 1) 배열 인덱스로 접근을 허용
	// 2) 행렬과 스칼라 곱
	// 3) 행렬과 행렬 곱
	// 4) 행렬과 벡터 곱
	// 5) 행렬과 벡터 곱 대입 연산자
	FORCEINLINE const Vector2& operator[](BYTE InIndex) const;
	FORCEINLINE Vector2& operator[](BYTE InIndex);
	FORCEINLINE Matrix2x2 operator*(float InScalar) const;
	FORCEINLINE Matrix2x2 operator*(const Matrix2x2& InMatrix) const;
	FORCEINLINE Vector2 operator*(const Vector2& InVector) const;
	FORCEINLINE friend Vector2 operator*=(Vector2& InVector, const Matrix2x2& InMatrix)
	{
		InVector = InMatrix * InVector;
		return InVector;
	}

	// 멤버함수 - 행렬에 대한 전치연산
	...
	FORCEINLINE Matrix2x2 Transpose() const;

	...
    
	// 정적멤버변수 - 행렬의 크기를 지정
	...
	static constexpr BYTE Rank = 2;

	// 멤버변수 - 2차원 벡터를 저장하여 열 배열로 사용, 초기값으로 2개의 표준벡터 사용
	std::array<Vector2, Rank> Cols = { Vector2::UnitX, Vector2::UnitY };
};

...

// 열 행렬을 전치하여 행 행렬로 전환
FORCEINLINE Matrix2x2 Matrix2x2::Transpose() const
{
	return Matrix2x2(
		Vector2(Cols[0].X, Cols[1].X),
		Vector2(Cols[0].Y, Cols[1].Y)
	);
}

...

 

2차원 정방행렬을 표현하는 Matrix2x2 구조체를 위와 같이 정의하였다

이전까지는 삼각함수를 사용하여 크기 변환회전 변환을 각각 구현하였지만,

이번에는 행렬을 사용하여 크기 변환회전 변환을 한번에 구현한다

 

2) 행렬을 사용한 크기 변환과 회전 변환 구현

...

// 화면에 렌더링하는 함수
void SoftRenderer::Render2D()
{
	// 화면에 렌더하기 위해 가져올 레퍼런스
	auto& r = GetRenderer();
	const auto& g = Get2DGameEngine();

	// 배경에 격자 그리기
	DrawGizmo2D();

	// 로컬 변수
        // 현재 각도 / 각도를 서서히 증가시킬 값 / 하트를 구성하는 점을 저장할 컨테이너 / 현재 각도에 대응하는 색
	float rad = 0.f;
	static float increment = 0.001f;
	static std::vector<Vector2> hearts;
	HSVColor hsv(0.f, 1.f, 0.85f);

	// 컨테이너에 하트를 구성하는 점들을 저장하는 로직
	if (hearts.empty())
	{
    	        // sin 함수와 cos 함수의 정의역인 0과 2π의 범위만을 순회
		for (rad = 0.f; rad < Math::TwoPI; rad += increment)
		{
			float sin = sinf(rad);
			float cos = cosf(rad);
			float cos2 = cosf(2 * rad);
			float cos3 = cosf(3 * rad);
			float cos4 = cosf(4 * rad);
            
                        // 하트 방정식 사용
			float x = 16.f * sin * sin * sin;
			float y = 13 * cos - 5 * cos2 - 2 * cos3 - cos4;
			hearts.push_back(Vector2(x, y));
		}
	}

	// 최종 각도에 대응하는 사인과 코사인 값 얻기
	float sin = 0.f, cos = 0.f;
	Math::GetSinCos(sin, cos, currentDegree);

	// 회전 변환 행렬 생성
        // 각도에 대응하는 사인과 코사인값을 통해 회전 변환 행렬의 기저 벡터를 생성 
        // 생성한 기저 벡터를 통해 회전 변환 행렬 생성
	Vector2 rBasis1(cos, sin);
	Vector2 rBasis2(-sin, cos);
	Matrix2x2 rMatrix(rBasis1, rBasis2);

	// 크기 변환 행렬 생성
        // 하트의 현재 크기를 표준기저벡터 (1, 0)과 (0, 1)에 곱하여 크기 변환 행렬의 기저 벡터를 생성
        // 생성한 기저 벡터를 통해 크기 변환 행렬 생성
	Vector2 sBasis1 = Vector2::UnitX * currentScale;
	Vector2 sBasis2 = Vector2::UnitY * currentScale;
	Matrix2x2 sMatrix(sBasis1, sBasis2);

	// 크기 변환 -> 회전 변환 순서로 진행하는 합성 행렬변환 수행
	Matrix2x2 finalMatrix = rMatrix * sMatrix;

	// 현재 각도를 초기화한 후 색상을 증가시키면서 점에 대응
	rad = 0.f;
	for (auto const& v : hearts)
	{
		// 1) 점(위치벡터)에 행렬을 적용
		Vector2 transformedV = finalMatrix * v;
		// 2) 변환된 점을 이동
		Vector2 translatedV = transformedV + currentPosition;

		hsv.H = rad / Math::TwoPI;
		r.DrawPoint(translatedV, hsv.ToLinearColor());
		rad += increment;
	}

	// 현재 위치, 크기, 각도를 화면에 출력
	r.PushStatisticText(std::string("Position : ") + currentPosition.ToString());
	r.PushStatisticText(std::string("Scale : ") + std::to_string(currentScale));
	r.PushStatisticText(std::string("Degree : ") + std::to_string(currentDegree));
}

...

 

Render2D 함수에서 렌더링을 진행한다

이전과 다르게 행렬을 사용하여 크기 변환회전 변환을 한번에 수행한다

  • 회전 변환 행렬 rMatrix 로컬 변수 : 하트의 최종 각도로부터 sincos을 가져와서 회전 변환 행렬 생성
  • 크기 변환 행렬 sMatrix 로컬 변수 : 하트의 현재 크기 2개의 표준기저벡터에 곱하여 크기 변환 행렬 생성
  • 최종 합성 변환 행렬 finalMatrix 로컬 변수 : 크기 변환 -> 회전 변환 순서로 수행하여 합성 변환 행렬 생성
  • 하트를 구성하는 점합성 변환 행렬을 적용하여 크기 변환회전 변환을 동시에 적용

 

- 최종 실행 결과

 

728x90
반응형