- 로컬(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();
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(); }
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);
Matrix _matLocal = Matrix::Identity;
Matrix _matWorld = Matrix::Identity;
Vec3 _scale;
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;
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);
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;
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()
{
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;
}
Quaternion quat;
_matWorld.Decompose(_scale, quat, _position);
_rotation = ToEulerAngles(quat);
_right = Vec3::TransformNormal(Vec3::Right, _matWorld);
_up = Vec3::TransformNormal(Vec3::Up, _matWorld);
_look = Vec3::TransformNormal(Vec3::Backward, _matWorld);
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)