로그인 시도 실패 먹어서 유동으로 들어옵니다...
전에 로프 액션에 관한 얘길 했는데 아무래도 코딩만으론 한계가 있을 것 같아서 그림도 첨부해서 올립니다
코드 얘기는 좀 더 빼고, 분야도 진자 운동으로 한정시켜서요
저는 물리엔진 제작에도 관심이 있어서 이런쪽도 찾아보게 되었습니다
여기 등속으로 운동하는 물체가 있습니다.
얘는 그 누구의 방해도 받지 않고 한 방향으로 일정한 속도로 움직입니다.
xy가 있는 2차원 벡터 VEC2 클래스를 만들고, VEC2 pos, speed를 선언했다고 하겠습니다.
이 물체는 매 프레임마다 다음 영향을 받습니다.
pos += speed * DT;
(DT: DeltaTime, 이전 프레임 후 경과된 시간. 100 * DT일 경우 초당 100 이동)
이 물체의 이동을 바꾸는 수단은 무엇일까요?
그것은 바로 힘입니다.
힘이 계속 가해지는 동안 물체는 점차 이동 방향을 바꾸게 됩니다.
힘과 가속에 관한 공식은 다음과 같습니다.
F = m * a
m은 질량이고(mass), a는 가속도입니다(acceleration).
물체의 질량이 1이라고 가정하면, 힘이 곧 가속도가 됩니다.
즉 { 200.0f, 0.0f }로 이동하던 물체에게
1초에 걸쳐 { 0.0f, 200.0f }의 가속(acc)이 가해지면
1초 후 이동 속도는 { 200.0f, 200.0f }가 됩니다. 코드로는 다음과 같습니다.
acc = { 0.0f, 200.0f };
speed += acc * DT;
pos += speed * DT;
가속은 매 순간의 힘이기 때문에 더하지 않고 매번 세팅합니다.
가속은 속도를 변화시키는 요인이고, 속도는 위치를 변화시키는 요인입니다.
단위로도 생각해 보면 가속은 m/s^2, 속도는 m/s인데
여기서 시간을 곱하면 가속은 m/s, 속도는 m가 되죠.
코드에서 시간을 곱하는 행위는 DT를 곱하는 행위가 됩니다.
그럼 원운동으로 넘어가 보겠습니다.
실의 길이가 고정된 원 운동에서 물체는
실과 수직인 방향으로 나아가려고 하고 실은 물체가 더 멀이지지 못하게 하는 구심력이 작용합니다.
힘이 가해지면 속력이 바뀌는 것 아니냐고 의아해할 수 있는데
위에 힘의 사례에서 보시다시피 수직의 힘이 가해졌을 때
기존의 속도 x가 200인 것에는 전혀 변함이 없었습니다.
따라서 여기서는 연속적으로 수직인 힘이 가해지면서 방향만 바뀌고 속력은 바뀌지 않는 상황이라고 보면 됩니다.
구심력의 공식은 다음과 같습니다.
F = m * v^2 / r
(m은 질량, v는 속력, r은 실의 길이)
요컨대 속력이 셀수록 강해지고 실의 길이가 길어질수록 약해진단 소리입니다.
코드로는 다음과 같습니다.
VEC2 gap = center - pos;
acc = gap.dir() * speed.scalar() * speed.scalar() / gap.scalar();
speed += acc * DT;
pos += speed * DT;
(center는 원의 중심입니다)
(dir() 함수 내용: return { cosf(theta()), sinf(theta()) }; // 코사인과 사인으로 길이가 1인 단위 벡터를 반환)
(theta() 함수 내용: return atan2f( y, x ); // 탄젠트의 역함수 아크탄젠트로 각을 반환)
(scalar() 함수 내용: return sqrtf(x * x + y * y); // 피타고라스의 정리를 사용해 벡터의 길이를 반환)
구심력의 존재를 알아도 구체적으로 얼마나 적용시킬지가 애매했는데 이게 진짜로 작동해서 신기하더군요.
이렇게 되면 등속 원운동이 되긴 하지만, 점점 더 멀어진다는 오차가 있습니다.
왜냐하면 현실에서는 조금만 움직여도 연속적으로 힘의 방향과 이동 방향이 바뀌지만
여기서는 짧은 시간 동안 일정한 힘의 방향을 받고 또한 직선으로 이동하기 때문입니다.
따라서 곡선으로 이동하는 것이 아니라 직선으로 이동해서, 조금씩 멀어지게 됩니다.
이를 보정하려면 최초의 길이까지 다시 줄여야 합니다.
정확한 시뮬레이팅을 위해선 훨씬 더 어려워질 텐데, 그건 아마 미분이나 적분까지 들어가게 될 겁니다.
제가 그 정도까지 할 기량은 못 됩니다...
VEC2 regap = center - pos;
pos += center - regap.dir() * gap.scalar();
(dir은 길이가 1인 단위 벡터로 바꿔 반환하는 함수입니다.)
이렇게 보정까지 하게 되면 등속 원운동이 완성됩니다.
이제 여기서 중력이 들어가면 진자 운동이 됩니다.
acc에 더할 요소는 중력과, 실이 주는 장력입니다.
G는 중력, T는 장력, F는 최종 힘입니다. (gravity, tension, force)
물체에는 일단 중력이 가해지지만, 실의 길이 때문에
시계 방향이나 반시계 방향 둘 중 하나로 힘이 정해져야 합니다. (어느쪽이든 수직인 쪽으로)
그리고 힘을 그렇게 바꾸는것이 장력입니다.
여기서 장력을 구체적으로 구해보겠습니다.
내부의 각 b를 구하려면 직각에서 a를 빼면 됩니다. 직각은 PI / 2 고, a는 gap.theta()와 동일합니다.
b를 구했으면 내부를 직각삼각형으로 보고 cosf(b)에 중력의 세기를 곱하면 장력의 세기가 나옵니다.
장력을 벡터로 바꾸려면 gap.dir() 단위 벡터에 위의 값을 곱하면 됩니다.
코드로 쓰자면 다음과 같습니다.
VEC2 gravity = { 0.0f, -600.0f };
float theta = PI / 2 - gap.theta();
float tensionpower = gravity.scalar() * cosf(theta);
VEC2 tension = gap.dir() * tensionpower;
중력과 장력이 세팅됐습니다.
이제 이걸 구심력과 보정까지 더해서 최종적으로 쓰면 다음과 같습니다.
// center, pos, speed는 이미 선언됐다고 가정.
VEC2 gap = center - pos; // 실의 길이
VEC2 centripetal = gap.dir() * speed.scalar() * speed.scalar() / gap.scalar(); // 구심력
acc = centripetal; // 구심력, 세 힘 중 첫번째 힘
VEC2 gravity = { 0.0f, -600.0f }; // 중력
float theta = PI / 2 - gap.theta(); // 내부 삼각형의 각
float tensionpower = gravity.scalar() * cosf(theta); // 장력의 세기
VEC2 tension = gap.dir() * tensionpower; // 장력
acc += gravity; // 중력, 세 힘 중 두번째 힘
acc += tension; // 장력, 세 힘 중 세번째 힘
speed += acc * DT; // 가속에 따른 속도 변화
pos += speed * DT; // 속도에 따른 위치 변화
VEC2 regap = center - pos; // 이동 후 다시 잰 실의 길이 (약간 늘어남)
pos = center - regap().dir() * gap.scalar(); // 위치 보정 (거리를 약간 줄임)
이러면 구심력, 중력, 장력을 기반으로 한 진자 운동이 만들어집니다.
다만 연속적인 값은 고려하지 않았기 때문에 약간의 오차는 있습니다.
않..않이...이걸 다
이걸 보니 진자운동을 이용해서 뭔가 하고싶어집니다... 좋은 자료 감사합니다
한때 이거에 완전 꽂힌 적이 있어서 나름 이런 결론이 나오게 됐습니다
원래는 저것이 줄줄이 이어진 부드러운 줄을 하려고 했는데 줄은 차원이 다르더군요