배경
42과제인 ft_transendence를 진행하는 중, game 부분에서 object가 paddle과 충돌할 경우 멈춰버리거나, Stack overflow가 발생하는 일이 발생했다. float 오차 범위가 문제라는건 짐작했지만 이게 어떤식으로 문제가 되는지는 뒤늦게 알게 되어서 이를 정리해보려 한다.
오차 범위에 따른 오류
충돌 로직은 2D 직선 충돌 logic에서 언급한 것과 같이 구현했다. 이때, 문제가 되었던 부분이 충돌까지의 거리를 정확하게 예측하는 것이 아니라, 컴퓨터의 소수점 표기의 한계에 따라, 미세하게 오차가 발생하여, paddle, canvas 내부로 object가 미세하게 통과하는 문제가 있었고, 이 때문에 충돌감지 로직에서 미세한 충돌을 무한에 가깝게 재귀로 감지하면서, Stack overflow 및 object가 정지하는 문제가 발생했었다.
구체적인 예시를 살펴보면서 이야기해본다.
const collisionResult = process.checkCollision();
if (collisionResult !== undefined) {
object.move(collisionResult.p);
if (process.handleCollision(collisionResult))
return;
if (process.CheckObjectInside()) {
process.clamp();
}
const restAfterCollision = delta - collisionResult.p;
this.GuaranteeConflict(object, restAfterCollision, depth + 1);
return;
}해당 코드에서 checkCollision 단계에서 2D 직선 충돌 logic에서 사용한 방법 대로, 계산을 하게되는데 하나씩 보면 float에 대한 곱셈, 덧셈으로 이루어진 것을 알 수 있다. 이러한 계산은 정확한 결과값이 아닌, 근사치를 반환하게 되고 충돌과 같은 매우 정확한 위치를 필요로 하는 연산에서 이는 문제가 된다.
해결 방안
CheckObjectInside함수를 통해서 오차로 인해서 공이 object 내부에 있는 경우, process.clamp를 호출하여 이를 막아주었다. 구현 방법은 가장 간단하다고 생각되는 2가지 방식으로 이를 구현했다.
-
미세한 움직임 주기
this.move(0.0001);와 같은 미세하게 공에 움직임을 줘서 object의 밖으로 빠져나올 수 있도록 하였다. 이는 완벽한 clamp 방식이라고 보기는 어렵지만, 간단하고 효과적으로 동작한 방식이기도 하다. -
자체 clamp 사용
paddle에서는 정확한 clamp를 구현하기에는 부자연스러운 부분이 있어서 적용하지 못했지만, canvas에서는 크기에 맞게 clamp를 사용했다.
public clampWithWall() { const min = -1.0 + this.radius * 1.1; const max = 1.0 - this.radius * 1.1; const x = this.clamp(this.position[0], min, max); const y = this.clamp(this.position[1], min, max); this.position = vec2.fromValues(x, y); }