원 충돌 내부의 원


9

내 프로젝트 중 하나에서 나는 원 모양의 게임 영역이 있습니다. 이 원 안에서 또 다른 작은 원이 움직입니다. 내가하고 싶은 것은 작은 원이 더 큰 원 밖으로 이동하지 못하게하는 것입니다. 아래에서 프레임 2에서 작은 원이 부분적으로 바깥에 있음을 알 수 있습니다. 바깥으로 이동하기 직전에 원을 다시 이동할 수있는 방법이 필요합니다. 이것을 어떻게 할 수 있습니까?

기본 예

또한 작은 원의 속도를 업데이트하려면 큰 원의 호를 따라 충돌 지점이 필요합니다. 이 점을 어떻게 계산할 수 있습니까?

내가하고 싶은 것은 작은 원을 이동하기 전에 다음 위치를 예측하고 바깥에 있으면 t = 0과 t = 1 사이의 충돌 시간을 발견합니다 (t = 풀 타임 스텝). 충돌 시간 t가 있으면 풀 타임 단계 대신 t 동안 작은 원을 이동합니다. 그러나 다시 말하지만, 문제는 두 개의 원과 하나가 다른 원 안에있을 때 충돌이 발생하는 시점을 감지하는 방법을 모른다는 것입니다.

편집하다:

충돌 지점 (녹색)의 예 찾고 싶습니다. 어쩌면 사진이 약간 떨어져 있지만 아이디어를 얻을 수 있습니다.

여기에 이미지 설명을 입력하십시오

답변:


10

큰 원의 중심 A과 반경이 R있고 작은 원의 중심 B과 반경 r이 location으로 이동 한다고 가정 해 봅시다 C.

우아한 사용하여이 문제를 해결하는 방법이있다 Minkovski 합계 반경의 디스크를 교체합니다 (실제로는 뺄셈을) R반경의 디스크로 R-r, 반경의 디스크 r반경의 디스크로 0, 즉. 에 위치한 간단한 지점 B. 이 문제는 직선 교차 문제가됩니다.

그런 다음 거리 AC가보다 작은 지 확인하면됩니다 R-r. 그렇다면 원이 충돌하지 않습니다. 더 큰 경우, 거리 가 먼 지점 D을 찾으면 작은 원 중심의 새로운 위치입니다. 이것은 다음 과 같은 것을 찾는 것과 같습니다.BCR-rAk

  vec(BD) = k*vec(BC)
and
  norm(vec(AD)) = R-r

로 대체 하면 다음 vec(AD)vec(AB) + vec(BD)제공합니다.

AB² + k² BC² + 2k vec(AB).vec(BC) = (R-r

초기 위치가 큰 원 안에있는 경우이 2 차 방정식 k은 하나의 양의 근 을 갖습니다. 의사 코드에서 방정식을 해결하는 방법은 다음과 같습니다.

b = - vec(AB).vec(BC) / BC²    // dot product
c = (AB² - (R-r)²) / BC²
d = b*b - c
k = b - sqrt(d)
if (k < 0)
    k = b + sqrt(d)
if (k < 0)
    // no solution! we must be slightly out of the large circle

이 값으로 k작은 원의 새 중심은 다음 D과 같습니다 BD = kBC.

편집 : 이차 방정식 솔루션 추가


고마워, 그것은 우아해 보이지만 나는 확실히 확신하지 못한다. 예를 들면 : "A의 거리 Rr에서 BC의 점 D를 찾으십시오". 더 잘 이해하려고 그림 을 그렸습니다 . 따라서 B (AX, AY- (Rr))에서 시작하고 C는 현재 속도로 끝나는 곳입니다. 인용 된 텍스트를 이해하는 방법 : 선 세그먼트 BC에서 A와 Rr의 거리 인 점 D를 찾으십시오. 그러나 내가 그린 그림에서 볼 수있는 방법은 정확히 B 만 A와 Rr입니다. 다른 점은 A에서 Rr 떨어져 있습니다. 내가 무엇을 놓치고 있습니까?
dbostream

@dbostream 아무것도 빠지지 않았습니다. 두 원이 이미 접촉 한 경우 감지 할 실제 충돌이 없습니다 : B및 에서 충돌이 발생합니다 k=0. 이제 충돌 해결 이 필요한 경우 객체의 물리적 특성에 대한 지식이 필요하기 때문에 답변에서 다루지 않았습니다. 무슨 일이야? 안쪽 원이 안쪽으로 튀어야합니까? 아니면 롤? 스위프?
sam hocevar

작은 원이 큰 원의 호를 따라 미끄러지기 시작하기를 원합니다. 따라서 내가 실수하지 않으면 큰 원호의 충돌 지점을 원하므로 속도를 업데이트하기 위해 법선을 사용할 수 있습니다.
dbostream

@ dbostream 운동이 그런 식으로 제한되어야한다면 가능한 한 빨리 그 제약 조건을 따르는 것이 좋습니다. 속도가 V인 경우 원 V*t의 원주를 따라 내부 원을 앞으로 이동시킵니다 R-r. 이는 V*t/(R-r)점 주위 의 각도 라디안 회전을 의미합니다 A. 속도 벡터도 같은 방식으로 회전 할 수 있습니다. 법선 (항상 원의 중심을 향함)을 알 필요가 없으며 다른 방법으로 속도를 업데이트 할 필요가 없습니다.
sam hocevar

회전하기 전에 작은 원을 충돌 지점으로 이동해야합니다. 그리고 회전 행렬을 사용하여 위치를 회전하려고 할 때 새로운 위치는 큰 원의 중심에서 정확히 (거의) Rr 거리는 아니지만 작은 차이로 인해 다음 실행에서 충돌 테스트가 실패했습니다. 새 위치를 찾으려면 위치를 회전시켜야합니까, 직선 벽과 충돌하는 경우처럼 벡터 작업을 사용할 수 없습니까?
dbostream 16:27에

4

큰 원이 원 A이고 작은 원이 원 B라고 가정하십시오.

B가 A 안에 있는지 확인하십시오.

distance = sqrt((B.x - A.x)^2 + (B.y - A.y)^2))
if(distance > A.Radius + B.Radius) { // B is outside A }

프레임 n-1B에 A가 있고 프레임 nB에 A가 있고 프레임 사이의 시간이 너무 크지 않은 경우 (일명 B는 너무 빠르게 움직이지 않음) B의 직교 좌표를 찾아 충돌 지점을 근사 할 수 있습니다. A에게 :

collision.X = B.X - A.X;
collision.Y = B.Y - A.Y;

그런 다음이 점을 각도로 변환 할 수 있습니다.

collision.Normalize(); //not 100% certain if this step is necessary     
radians = atan2(collision.Y, collision.X)

tB가 A 외부에있는 것을 처음으로 더 정확하게 알고 싶다면 매 프레임마다 광선 원 교차점을 수행 한 다음 B에서 충돌 지점까지의 거리가 더 큰 경우 비교할 수 있습니다. 현재 속도. 그렇다면 정확한 충돌 시간을 계산할 수 있습니다.


고맙지 만 교차 테스트를 할 때 작은 원의 중심에서 광선을 쏘는 것이 실제로 맞습니까? 이 그림 중간에 시나리오가 나오지 않습니까? 작은 원호의 첫 번째 점이 큰 원과 충돌하는 것이 반드시 속도 방향의 원호에있는 것은 아닙니다. 내가 연결 한 그림의 맨 아래 시나리오와 같은 것이 필요하다고 생각합니다. 첫 게시물에 내가 필요하다고 생각하는 예를 보여주는 새로운 그림을 추가했습니다.
dbostream

흠 시나리오가 가능하다고 생각합니다. 이 프레임에 B.Radius + B의 최대 움직임이있는 새 서클 C로 테스트하고 A와 충돌하는지 확인한 다음 A에서 멀어진 C 지점을 운동하십시오 (이 btw는 시도하지 않았습니다)
Roy T.

3
적은 단어를 사용하면 : if (distance (A, B))> (Ra-Rb) 충돌이 발생하고 작은 원을 이동하여 Ra-Rb와 같은 거리를 얻습니다. 그렇지 않으면 작은 원을 정상적으로 움직입니다. @dbostream은 간단한 형태의 추측 연락처와 비슷한 것을 사용하고 있습니다.
Darkwings

@Darkwings +1 당신은 절대적으로 맞습니다. 그리고 그것은 훨씬 간단하게 들립니다!
Roy T.

필요한 모든 기본 지오메트리를 제거했기 때문에 간단하게 들립니다. 'collision'이라는 이름을 지정하는 대신 실제 AB이므로 이름을 boundAB로 지정할 수 있습니다. 정규화하면 직선에 평행 한 AB 번들에 대한 방정식과 유용한 단위 벡터를 얻을 수 있습니다. 그런 다음 거리 D에 해당 단위 벡터를 곱하고 새로 찾은 매개 변수를 A에 추가하여 필요한 충돌 지점을 찾습니다 (C (Ax + Dx, Ay + Dy)). 이제는 더 복잡하게 들리지만 같은 것입니다 : P
Darkwings

0

큰 원의 위치와 반경 R을 (Xa, Ya), 작은 원의 위치와 반경 r을 (Xb, Yb)라고합니다.

이 두 서클이 충돌하는지 확인할 수 있습니다.

DistanceTest = sqrt(((Xa - Xb) ^ 2) + ((Ya - Yb) ^ 2)) >= (R - r)

충돌 위치를 찾으려면 이진 검색을 사용하지만 고정 된 단계 수로 원이 충돌하는 정확한 시간 모멘트를 찾으십시오. 게임 제작 방법에 따라이 코드 부분을 최적화 할 수 있습니다 (작은 공의 동작 방식과 무관하게이 솔루션을 제공했습니다. 일정한 가속도 또는 일정한 속도가있는 경우 코드의이 부분을 최적화하고 간단한 공식으로 대체).

left = 0 //the frame with no collision
right = 1 //the frame after collision
steps = 8 //or some other value, depending on how accurate you want it to be
while (steps > 0)
    checktime = left + (right - left) / 2
    if DistanceTest(checktime) is inside circle //if at the moment in the middle of the interval [left;right] the small circle is still inside the bigger one
        then left = checktime //the collision is after this moment of time
        else right = checktime //the collision is before
    steps -= 1
finaltime = left + (right - left) / 2 // the moment of time will be somewhere in between, so we take the moment in the middle of interval to have a small overall error

충돌 시간을 알고 나면 마지막 시간에 두 원의 위치를 ​​계산하면 최종 충돌 지점은

CollisionX = (Xb - Xa)*R/(R-r) + Xa
CollisionY = (Yb - Ya)*R/(R-r) + Ya

0

Sam Hocevar가 설명 한 알고리즘을 사용하여 jsfiddle의 원에서 튀는 공 데모를 구현했습니다 .

http://jsfiddle.net/klenwell/3ZdXf/

연락 지점을 식별하는 자바 스크립트는 다음과 같습니다.

find_contact_point: function(world, ball) {
    // see https://gamedev.stackexchange.com/a/29658
    var A = world.point();
    var B = ball.point().subtract(ball.velocity());
    var C = ball.point();
    var R = world.r;
    var r = ball.r;

    var AB = B.subtract(A);
    var BC = C.subtract(B);
    var AB_len = AB.get_length();
    var BC_len = BC.get_length();

    var b = AB.dot(BC) / Math.pow(BC_len, 2) * -1;
    var c = (Math.pow(AB_len, 2) - Math.pow(R - r, 2)) / Math.pow(BC_len, 2);
    var d = b * b - c;
    var k = b - Math.sqrt(d);

    if ( k < 0 ) {
        k = b + Math.sqrt(d);
    }

    var BD = C.subtract(B);
    var BD_len = BC_len * k;
    BD.set_length(BD_len);

    var D = B.add(BD);
    return D;
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.