Koło wewnątrz kolizji koła

9

W jednym z moich projektów mam obszar gry w kształcie koła. Wewnątrz tego koła porusza się kolejny mały okrąg. Chcę, aby małe kółko nie poruszało się poza większym. Poniżej widać, że w ramce 2 małe kółko jest częściowo na zewnątrz, potrzebuję sposobu, aby przenieść je z powrotem tuż przed tym, jak ma zamiar wyjść na zewnątrz. Jak można to zrobić?

Podstawowy przykład

Potrzebuję też punktu kolizji wzdłuż łuku dużego koła, aby móc zaktualizować prędkość małego koła. Jak przejść do obliczenia tego punktu?

Przed przesunięciem małego koła przewiduję jego następną pozycję, a jeśli jest na zewnątrz, znajduję czas kolizji między t = 0 it = 1 (t = 1 pełny krok czasu). Jeśli mam czas kolizji t, to po prostu poruszam małym kółkiem podczas t zamiast kroku pełnego czasu. Ale znowu problem polega na tym, że nie wiem, jak wykryć, że w tym momencie dochodzi do kolizji dwóch kół i jednego z nich.

EDYTOWAĆ:

Przykład punktu kolizji (zielony), który chcę znaleźć. Być może zdjęcie jest trochę nie w porządku, ale masz pomysł.

wprowadź opis zdjęcia tutaj

dbostream
źródło

Odpowiedzi:

10

Załóżmy, że duży okrąg ma środek Ai promień, Ra mały okrąg ma środek Bi promień rporuszający się w kierunku lokalizacji C.

Istnieje elegancki sposób rozwiązania tego problemu przy użyciu sum Minkovskiego (w rzeczywistości odejmowanie): zastąp dysk o promieniu Rdyskiem o promieniu R-r, a dysk o promieniu rdyskiem o promieniu 0, tj. prosty punkt zlokalizowany przy ul B. Problem staje się problemem przecięcia linii z okręgiem.

Następnie wystarczy sprawdzić, czy odległość AC jest mniejsza niż R-r. Jeśli tak, kręgi się nie kolidują. Jeśli jest większy, wystarczy znaleźć punkt Dna BCw odległości R-rod Ai jest to nowa lokalizacja w centrum swojego małego okręgu. Jest to równoważne znalezieniu ktakiego, że:

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

Zastąpienie vec(AD)przez vec(AB) + vec(BD)daje:

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

Pod warunkiem, że początkowa pozycja znajdowała się wewnątrz dużego koła, to równanie kwadratowe kma jeden dodatni pierwiastek. Oto jak rozwiązać równanie w pseudokodzie:

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

Przy tej wartości knowy środek małego koła jest Dtaki, że BD = kBC.

Edytować : dodaj rozwiązanie równania kwadratowego

sam hocevar
źródło
Dzięki, wygląda elegancko, ale nie jestem pewien, czy rozumiem. Na przykład: „po prostu znajdź punkt D na BC w odległości Rr od A”. Narysowałem zdjęcie, aby lepiej zrozumieć. Więc jeśli zaczniemy od B (AX, AY- (Rr)), a C będzie tam, gdzie skończy się z bieżącą prędkością. Sposób, w jaki rozumiem cytowany tekst: Znajdź punkt D na odcinku linii BC, który jest w odległości Rr od A. Ale widzę to na narysowanym obrazie, że tylko dokładnie B jest Rr od A. Wszystkie inne punkty będą> Rr od A. Czego mi brakuje?
dbostream
@dbostream Niczego nie brakuje. Jeśli dwa kręgi są już w kontakcie, nie ma prawdziwej kolizji do wykrycia : kolizja ma miejsce w B, i k=0. Teraz, jeśli chcesz rozwiązania kolizji , nie udzieliłem tego w mojej odpowiedzi, ponieważ wymagałoby to wiedzy o fizycznych właściwościach obiektów. Co się stanie? Czy wewnętrzny okrąg powinien odbijać się w środku? A może rzucić? Zamiatać?
sam hocevar
Chcę, aby małe kółko zaczęło ślizgać się wzdłuż łuku dużego koła. Więc jeśli się nie mylę, chcę punktu kolizji na łuku dużego koła, aby móc użyć jego normalnej wartości do aktualizacji prędkości.
dbostream
@dbostream, jeśli ruch powinien być ograniczony w taki sposób, sugeruję jak najszybsze zastosowanie się do tego ograniczenia: jeśli prędkość jest V, spraw, aby wewnętrzny okrąg przesunął się V*twzdłuż obwodu R-rkoła. Oznacza to obrót V*t/(R-r)radianów kątowych wokół punktu A. Wektor prędkości można obracać w ten sam sposób. Nie trzeba znać normalnej (która i tak zawsze jest zorientowana w stronę środka koła), ani aktualizować prędkości w jakikolwiek inny sposób.
sam hocevar
Nadal muszę obrócić małe kółko do punktu kolizji przed obracaniem. A kiedy próbowałem obrócić pozycję za pomocą macierzy obrotu, nowa pozycja nie była dokładnie (ale prawie) Rr od środka dużego koła, ale ta niewielka różnica wystarczyła, aby mój test kolizji nie powiódł się przy następnym uruchomieniu. Czy muszę obrócić pozycję, aby znaleźć nową, czy nie można użyć operacji wektorowych tak, jak można, jeśli coś zderzy się z prostą ścianą?
dbostream
4

Powiedz, że duży okrąg to okrąg A, a mały okrąg to okrąg B.

Sprawdź, czy B znajduje się w środku A:

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

Jeśli w ramce n-1B znajdował się wewnątrz A, a w ramce nB znajduje się na zewnątrz A, a czas między ramkami nie był zbyt duży (inaczej B nie poruszał się zbyt szybko), możemy przybliżyć punkt zderzenia, po prostu znajdując współrzędne kartezjańskie względnego B do A:

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

Następnie możemy przekonwertować te punkty na kąt:

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

Jeśli chcesz dowiedzieć się dokładniej, co tB znajduje się poza A, po raz pierwszy możesz wykonać skrzyżowanie promień-okrąg w każdej klatce, a następnie porównać, czy odległość od B do punktu zderzenia jest większa niż odległość B, którą może przebyć, biorąc pod uwagę, że obecna prędkość. Jeśli tak, możesz obliczyć dokładny czas kolizji.

Roy T.
źródło
Dzięki, ale czy naprawdę poprawne jest strzelanie promieniem ze środka małego koła podczas wykonywania testu przecięcia? Czy nie skończymy ze scenariuszem na środku tego zdjęcia ? Mam na myśli, że pierwszy punkt na łuku małego koła, który zderza się z dużym kołem, niekoniecznie musi być tym na łuku w kierunku prędkości. Myślę, że potrzebuję czegoś w dolnym scenariuszu zdjęcia, z którym się łączyłem. Dodałem nowe zdjęcie w pierwszym poście pokazujące przykład tego, co moim zdaniem potrzebuję.
dbostream
Hmm, przypuszczam, że ten scenariusz jest możliwy. Może przetestuj z nowym okręgiem C, który ma B.Radius + maksymalny ruch B tej ramki, sprawdź, czy koliduje on z A, a następnie przećwicz punkt na C, który jest dalej od A. (Nie próbowałem tego btw)
Roy T.
3
Używając mniej słów: jeśli (odległość (A, B))> (Ra-Rb) nastąpi zderzenie i wystarczy przesunąć mały okrąg, aby uzyskać odległość równą Ra-Rb. W przeciwnym razie poruszasz małym kółkiem normalnie. Przy okazji @dbostream używasz czegoś podobnego do uproszczonej formy kontaktów spekulacyjnych, spróbuj wyszukać to.
Darkwings
@Darkwings +1 masz absolutną rację, a to sprawia, że ​​brzmi o wiele prostiej!
Roy T.
Brzmi prosto, ponieważ usunąłem całą niezbędną geometrię podstawową. Zamiast nazwać go „kolizją”, mógłbyś nazwać go boundAB, ponieważ tak właśnie jest: wektor swobodny AB związany z (0,0). Po normalizacji otrzymujesz zarówno równanie wiązki równoległych do AB linii prostych, jak i użyteczny wektor jednostek. Następnie możesz pomnożyć ten wektor jednostek dla dowolnej odległości D i dodać nowo znalezione parametry do A znajdując potrzebny punkt kolizji: C (Ax + Dx, Ay + Dy). Teraz wydaje się to bardziej skomplikowane, ale to samo: P
Darkwings
0

Niech (Xa, Ya) pozycję dużego koła i jego promień R, a (Xb, Yb) pozycję mniejszego koła i jego promień r.

Możesz sprawdzić, czy te dwa kręgi kolidują, jeśli

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

Aby znaleźć pozycję kolizji, znajdź dokładny moment, w którym zderzają się koła, używając wyszukiwania binarnego, ale ze stałą liczbą kroków. W zależności od tego, jak tworzona jest gra, możesz zoptymalizować tę część kodu (podałem to rozwiązanie, aby być niezależnym od zachowania małej kulki. Jeśli ma stałe przyspieszenie lub stałą prędkość, tę część kodu można zoptymalizować i zastąpiony prostą formułą).

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

Kiedy znasz czas kolizji, oblicz pozycje dwóch kół w ostatnim czasie, a końcowym punktem kolizji jest

CollisionX = (Xb - Xa)*R/(R-r) + Xa
CollisionY = (Yb - Ya)*R/(R-r) + Ya
Vlad
źródło
0

Zaimplementowałem demo piłki odbijającej się w kręgu na jsfiddle za pomocą algorytmu opisanego przez Sama Hocevara :

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

Oto javascript, który identyfikuje punkt kontaktowy:

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;
}
klenwell
źródło