0426

2026년 4월 26일

0426.gif

심심해서 토크 공식 시뮬레이션을 만들었다.

개인적으로 토크 공식을 처음 봤을 때

정말 멋진 공식이라 생각했다.

뭔가 선형대수학이랑 물리학이랑

적절히 결합된 공식 같았다.

생각해보면 오일러 공식같이 $$ e^{ix} = \cos x + i\sin x $$ 자연로그밑+복소수+삼각함수

이렇게 전혀 관련없어 보이는 게 모이는 공식도

아름답다고 평가받는다.

막상 선형대수 배울 때는 외적을 대체 어디다가 쓰냐

하는 생각이 들었지만

이렇게 두 세계관이 충돌할 때면

배우길 잘했단 생각이 든다.


공식은 이렇다.

Torque = Cross ( r, F )

여기서 r은 '작용점'이 물체의 회전축에서 얼마나 떨어져 있는지를

벡터로 나타낸 거고

F는 작용점에서 주는 힘이다.

이 공식을 좋아하는 또 하나의 이유는,

실생활에서 바로 연상해볼 수 있기 때문이다.

예를 들면 그네 타는 게 있겠다.

그네를 앉아서 타면

(=작용점을 회전축으로터 멀리 잡으면)

같은 힘을 주더라도

서서 타는 경우

(=작용점이 회전축과 가까운 경우)

보다 더 크게 흔들린다.

여기서 작용점은

앉아서 탈 때는 하체 부분이지만

서서 탈 때는 그네 중간쯤 될 것이다.

또 회전축은 그네의 위 끝부분이다.


뭐 여기서 더 나아가면

각운동량 보존 법칙이랑 관성 모먼트가 있다. $$ L = Iw $$ 외력이 없을 때,

각운동량 L은 보존된다.

여기서 I는 관성 모먼트고

w는 각속도이다.

이걸 제대로 알 수 있는 실험이

돌아가는 원판 위에서

팔을 뻗고 돌다가

갑자기 팔을 움츠리면

도는 속도가 빨라지는 건데,

이는 원판에 대한 관성 모먼트가

줄어들었기 때문이다.

(대충 세상에는 여러 모양의 물체들이 있는데,

일반적인 도형은 관성모먼트 구하는 공식이 정의되어 있다. 찾아보길 바란다.)

원판 관성 모먼트 공식을 보면 $$ I = 0.5 * M * R^2 $$ 인데, 여기서 M은 원판 질량, R은 원판 반지름이다.

즉 관성모먼트가 반지름에 비례한다.

따라서??

L = Iw 는 보존되기 때문에

팔을 움츠려 I가 줄어들면

w, 즉 각속도가 빨라지는 것이다.

멋지지 않는가?


대충 코드는 이렇다.

update() 에서:

{
    if (get_owner().get_window()->get_is_mouse_button_pressed(dp::bcknd::MouseButton::Left)) {
        if (dp::length(get_world_space_mouse_position() - (center + r)) < 10.0f) {
            gizmo_focused = 1;
        }
        else if (dp::length(get_world_space_mouse_position() - (center + r + F)) < 10.0f) {
            gizmo_focused = 2;
        }
    }
    else if (get_owner().get_window()->get_is_mouse_button_released(dp::bcknd::MouseButton::Left)) {
        gizmo_focused = 0;
    }

    if (gizmo_focused == 1) {
        r = (get_world_space_mouse_position() - center);
    }
    else if (gizmo_focused == 2) {
        F = (get_world_space_mouse_position() - (center + r));

        // 진짜로 힘 주기 ㅇㅇ
        //float T = dp::cross(r, F);
        //float T = dp::cross(dp::normalize(r), dp::normalize(F)) * 0.0001f;
        //float T = dp::cross(dp::normalize(r), F) * 0.0001f;
        float T = dp::cross(r, F) * 0.0001f;

        angular_velocity += dp::RangedAngleTwoPI(dp::Degree(T));
        angle += angular_velocity;

        angular_velocity = dp::RangedAngleTwoPI(dp::Degree(angular_velocity.as_degree() * 0.65f));

        //r = dp::rotate2d(r, dp::Angle(-dp::Degree(angle.as_degree())));
        r = dp::rotate2d(r, dp::Angle(-dp::Degree(angular_velocity.as_degree())));
    }
}
{
    label->set_text(dp::s2ws(dp::Format("r = {}\nF = {}\nTorque : {}\nNormalizedTorque : {}", r, F, dp::cross(r, F), dp::cross(dp::normalize(r), dp::normalize(F)))));
} 

(처음에 r에 대해 회전을 누적시켜가지고 고생 좀 했다.

아니 누적시켜서 이동 / 회전 시키는 버그 나는게

게임 개발하면서 도대체 몇 번을 겪었는데도

아직도 난 저걸 한눈에 알아보질 못한다.

위에 주석처리된게 누적회전 에러 버전이고

밑에게 정상버전이다. 각을 계속 누적하는 게 아니라

각속도를 누적하면 정상적인 각이 되는 거다.)

참고로 밑에거엔 cross() 로 계산한 좌표계랑 내 엔진 (비주얼) 좌표계가 달라서 반전한 거다.

전역변수는 여기 있다.

// 토크 공식 테스트
// T = r x F
//    r : '작용점'이 회전축으로부터 어디만큼 떨어져 있는가
//    F : '작용점'을 기준으로 가하는 힘.
//dp::Vector2f r = { 1.0f, 0.0f };
//dp::Vector2f F = { 0.0f, 1.0f };
//float T = dp::cross(r, F);
//std::cout << "T = " << T << "\n";

const dp::Vector2f center = { 640, 360 };
const float radius = 100.0f;

// r이 곧 작용점임ㅇㅇ(point_of_application) (모델공간!!)
dp::Vector2f r = { 100.0f, 0.0f };
// 모델공간 힘.
dp::Vector2f F = { 0.0f, -100.0f };

int gizmo_focused = 0;
dp::RangedAngleTwoPI angular_velocity = dp::RangedAngleTwoPI(0.0_deg);
dp::RangedAngleTwoPI angle = dp::RangedAngleTwoPI(0.0_deg);

렌더링은 걍 심플하게.

        {
            DrawCircle(center, radius, dp::White);

            DrawLine(center, center + r, 2.0f, dp::Red);
            DrawLine(center + r, center + r + F, 2.0f, dp::Green);

            DrawCircle(center + r, 10.0f, dp::Red);
            DrawCircle(center + r + F, 10.0f, dp::Green);
        }

막상 만들어보니까 굉장히 멋지게 나와서

gif를 꼭 넣어야겠다.