[DX] Framework - Transform

vector·2025년 11월 10일

Transform

  • 로컬(Local) 정보를 가지고 월드(World) 변환을 계산
  • 게임 엔진에서 Transform은 각 객체의 위치(Position), 회전(Rotation), 크기(Scale)을 표현하는 기본 단위
  • 부모 자식 관계를 고려한 WorldMatrix(세계 변환 행렬)을 계산
#pragma once
#include "Component.h"

class Transform : public Component
{
public:
	Transform();
	~Transform();

	void Init() override;
	void Update() override;

	void UpdateTransform();

	// Local
	Vec3 GetLocalScale() { return _localScale; }
	void SetLocalScale(const Vec3& localScale) { _localScale = localScale; UpdateTransform(); }
	Vec3 GetLocalRotation() { return _localRotation; }
	void SetLocalRotation(const Vec3& localRotation) { _localRotation = localRotation; UpdateTransform(); }
	Vec3 GetLocalPosition() { return _localPosition; }
	void SetLocalPosition(const Vec3& localPosition) { _localPosition = localPosition; UpdateTransform(); }

	// World
	Vec3 GetScale() { return _scale; }
	void SetScale(const Vec3& worldScale);
	Vec3 GetRotation() { return _rotation; }
	void SetRotation(const Vec3& worldRotation);
	Vec3 GetPosition() { return _position; }
	void SetPosition(const Vec3& worldPosition);

	Matrix GetWorldMatrix() { return _matWorld; }


	// 계층관계 관련
	bool HasParent() { return _parent != nullptr; }
	shared_ptr<Transform> GetParent() { return _parent; }
	void SetParent(shared_ptr<Transform> parent) { _parent = parent; }

	const vector<shared_ptr<Transform>> GetChildren() { return _children; }
	void AddChild(shared_ptr<Transform> child) { _children.push_back(child); }

private:
	Vec3 _localScale = Vec3(1.f, 1.f, 1.f);
	Vec3 _localRotation = Vec3(0.f, 0.f, 0.f);
	Vec3 _localPosition = Vec3(0.f, 0.f, 0.f);


	// Cache
	Matrix _matLocal = Matrix::Identity;		// 현재 객체의 스케일 회전 이동 결과
	Matrix _matWorld = Matrix::Identity;		// 부모까지 포함한 최종 결과 (렌더링에 사용)

	Vec3 _scale;
	// x y z w
	Vec3 _rotation;
	Vec3 _position;

	Vec3 _right;
	Vec3 _up;
	Vec3 _look;


private:
	shared_ptr<Transform> _parent;
	vector<shared_ptr<Transform>> _children;
};
#include "pch.h"
#include "Transform.h"

Transform::Transform()
{
}

Transform::~Transform()
{
}

void Transform::Init()
{
}

void Transform::Update()
{
}

Vec3 ToEulerAngles(Quaternion q)
{
	Vec3 angles;

	// roll (x-axis rotation)
	double sinr_cosp = 2 * (q.w * q.x + q.y * q.z);
	double cosr_cosp = 1 - 2 * (q.x * q.x + q.y * q.y);
	angles.x = std::atan2(sinr_cosp, cosr_cosp);


	// pitch (y-axis rotation)
	double sinp = std::sqrt(1 + 2 * (q.w * q.y - q.x * q.z));
	double cosp = std::sqrt(1 - 2 * (q.w * q.y - q.x * q.z));
	angles.y = 2 * std::atan2(sinp, cosp) - 3.14159f / 2;


	// yaw (z-axis rotation)
	double siny_cosp = 2 * (q.w * q.z + q.x * q.y);
	double cosy_cosp = 1 - 2 * (q.y * q.y + q.z * q.z);
	angles.z = std::atan2(siny_cosp, cosy_cosp);

	return angles;
}

void Transform::UpdateTransform()
{
	// 로컬 SRT 행렬 생성 (스케일 -> 회전 -> 이동 순서 중요)
	Matrix matScale = Matrix::CreateScale(_localScale);
	Matrix matRotation = Matrix::CreateRotationX(_localRotation.x);
	matRotation *= Matrix::CreateRotationY(_localRotation.y);
	matRotation *= Matrix::CreateRotationZ(_localRotation.z);
	Matrix matTranslation = Matrix::CreateTranslation(_localPosition);

	_matLocal = matScale * matRotation * matTranslation;

	// 부보가 있으면 부모의 월드 행렬과 곱
	if (HasParent())
	{
		_matWorld = _matLocal * _parent->GetWorldMatrix();
	}
	else
	{
		_matWorld = _matLocal;
	}

	//Decompose로 다시 스케일, 회전(쿼터니언), 위치 추출
	Quaternion quat;
	_matWorld.Decompose(_scale, quat, _position);

	_rotation = ToEulerAngles(quat);

	// TransformNormal로 방향벡터 계산
	_right = Vec3::TransformNormal(Vec3::Right, _matWorld);
	_up = Vec3::TransformNormal(Vec3::Up, _matWorld);
	_look = Vec3::TransformNormal(Vec3::Backward, _matWorld);

	// Children 자식까지 재귀적으로 업데이트
	for (const shared_ptr<Transform>& child : _children)
		child->UpdateTransform();
}

void Transform::SetScale(const Vec3& worldScale)
{
	if (HasParent())
	{
		Vec3 parentScale = _parent->GetScale();
		Vec3 scale = worldScale;
		scale.x /= parentScale.x;
		scale.y /= parentScale.y;
		scale.z /= parentScale.z;
		SetLocalScale(scale);
	}
	else
	{
		SetLocalScale(worldScale);
	}
}

void Transform::SetRotation(const Vec3& worldRotation)
{
	if (HasParent())
	{
		Matrix worldToParentLocalMatirx = _parent->GetWorldMatrix().Invert();

		Vec3 rotation;
		rotation.TransformNormal(worldRotation, worldToParentLocalMatirx);

		SetLocalRotation(rotation);
	}
	else
	{
		SetLocalRotation(worldRotation);
	}
}

void Transform::SetPosition(const Vec3& worldPosition)
{
	if (HasParent())
	{
		Matrix worldToParentLocalMatirx = _parent->GetWorldMatrix().Invert();

		Vec3 position;
		position.Transform(worldPosition, worldToParentLocalMatirx);

		SetLocalPosition(position);
	}
	else
	{
		SetLocalPosition(worldPosition);
	}
}

공부해두면 좋은 부분

좌표계 & 행렬 기초

  • SRT (Scale -> Rotation -> Translate) 순서의 중요성
  • Row-Major vs Column-Major(DirectX 와 OpenGL 차이)
  • Transform() vs TransformNormal 차이
  • 역행렬(Inverse Matrix)의 의미와 사용법
  • Left-Handed / Right-Handed 좌표계 차이

회전 관련

  • 쿼터니언(Quaternion) 사용 이유 -> Gimbal Lock 방지
  • Euler(오일러) 각도 -> 사용자는 직관적, 계산은 불안정
  • ToEulerAngles / FromQuaternion 변환 연습
  • 회전 순서 (X->Y->Z / Z->Y->X)에 따른 결과 차이

계층 구조

  • 부보의 이동에 자식이 따라가는 구조 (재귀 업데이트)
  • "World = Local x ParentWorld" 관례
  • Matrix::Invert()로 부모 좌표계 기준으로 변환
    • 예시) 캐릭터 손에 무기 붙이기
    • 예시) 카메라를 플레이어에 부모로 연걸
    • 예시) UI 트리 구조 (Canvas -> Panal -> Button)
profile
게임 클라이언트 프로그래머 준비중 (공부 및 기록용)

0개의 댓글