Wykrywanie kolizji

0

Tak się trochę zastanawiałem, czy dać tu, czy do inne, no ale w gruncie rzeczy kod jest w c#, więc niech na razie jest tu. Jak się komuś nie spodoba, to przeniesie ;)
Ale idę do sedna. Otóż, w ramach zakończenia sesji i zyskania odrobiny wolnego czasu, postanowiłem sobie coś tam skodzić, padło na odbijanie się kulek :P znaczy oczywista projekt potem się rozrośnie, a może i się nie rozrośnie, ale na razie jest w fazie początkowej. Otóż latają sobie dwie kulki i chcę, żeby po zderzeniu się odbiły, ale żeby robiły to w miarę realistycznie, zgodnie z zasadami fizyki. Jako że przyjmuję kulki za idealne, padło na zderzenie niesprężyste (inelastic collision). Metoda jest prosta: obracamy wektor prędkości tak, by zderzenie następowało na lini jednego wektora, na przykład x. W tym momencie wektor y nie bierze udziału w obliczaniu wektora po kolizji. Wzór na wartości składowych po obrocie wektora jest dosyć prosty:
x' = x * cos(alfa) - y * sin(alfa)
y' = x * sin(alfa) + y * cos(alfa)
Po przekształceniu wektorów prędkości następuje obliczenie nowych prędkości x, zgodnie ze wzorem znalezionym na wikipedii, a który potwierdza się też na paru innych stronach:
http://pl.wikipedia.org/wiki/Zderzenie_sprężyste#Analiza_zderzenia_spr.C4.99.C5.BCystego
Potem oczywiście powrót do wektorów podstawowych, czyli składowe obracam o kąt 360 - alfa, zgodnie z powyższym wzorem.
Wydaje mi się, ze wszystko robię dobrze, cóż, kiedy jednak nie. Błąd na pewno występuje, co można rozpoznać po nieoczekiwanym zachowaniu się piłek ;P Gdy założymy, że piłki lecą wzdłuż osi x lub y, odbicie jest prawidłowe. Jednak gdy kulki lecą na prykład po skosie, odbicie, zamiast zmienić ich kierunki na przeciwne (zderzenie centralne), sprawia, że obie zaczynają lecieć w zupełnie nieoczekiwanym kierunku. JAko że obraz wart tysiąca słów:
przed odbiciem
user image
po odbiciu
user image
Są tam zaznaczone boundingboxy, a także wektory prędkości (z rozbiciem na wektory skłądowe).
Jak więc widać, zupełnie nie tak, jak logika i doświadczenie nakazuje.
Wierzę, że błąd jakiś jest, jednak mimo analizy kodu kilka razy, nie znalazłem. Wydaje się też być wszystko zgodne z równaniami z wiki, obrót wektorów też raczej ok. Gdzie więc jest błąd? Coś nie tak robię? Czy może ogólnie zastosowanie tych wzorów jest złe?
Kod wykrywania kolizji i zmiany prędkości ciał kolidujących:

static public void CheckCollision(List<ISimulationObject> simulationObjects)
{
ISimulationObject collisionObject, simulationObject;
for (int i = 0; i < simulationObjects.Count; ++i)
{
simulationObject = simulationObjects[i];
for (int j = i + 1; j < simulationObjects.Count; ++j)
{
collisionObject = simulationObjects[j];

                double left1 = simulationObject.boundingBox.X;
                double left2 = collisionObject.boundingBox.X;
                double right1 = simulationObject.boundingBox.X + simulationObject.boundingBox.Width;
                double right2 = collisionObject.boundingBox.X + collisionObject.boundingBox.Width;
                double top1 = simulationObject.boundingBox.Y;
                double top2 = collisionObject.boundingBox.Y;
                double bottom1 = simulationObject.boundingBox.Y + simulationObject.boundingBox.Height;
                double bottom2 = collisionObject.boundingBox.Y + collisionObject.boundingBox.Height;

                //checking boundbox collision
                if (bottom1 < top2) continue;
                if (top1 > bottom2) continue;
                if (right1 < left2) continue;
                if (left1 > right2) continue;

                //if there is probability of collision, check carefully
                Region simulationRegion = simulationObject.region;
                Region collisionRegion = collisionObject.region;
                simulationRegion.Intersect(collisionRegion);
                RectangleF[] intersection = simulationRegion.GetRegionScans(new Matrix());
                simulationRegion.Dispose();
                collisionRegion.Dispose();

                if (intersection.Length == 0)
                    continue;

                //take action after collision detection
                double angle, dx, dy;
                dx = collisionObject.position.x - simulationObject.position.x;
                dy = collisionObject.position.y - simulationObject.position.y;
                //angle of collision
                angle = Math.Atan(dy / dx);

                //calculation of velocity after frame correction
                Vector2D v1, v2;
                v1 = new Vector2D();
                v2 = new Vector2D();
                v1.x = simulationObject.velocity.x * Math.Cos(angle) - simulationObject.velocity.y * Math.Sin(angle);
                v1.y = simulationObject.velocity.x * Math.Sin(angle) + simulationObject.velocity.y * Math.Cos(angle);
                v2.x = collisionObject.velocity.x * Math.Cos(angle) - collisionObject.velocity.y * Math.Sin(angle);
                v2.y = collisionObject.velocity.x * Math.Sin(angle) + collisionObject.velocity.y * Math.Cos(angle);

                //calculating velocity after collision
                Vector2D newV1, newV2;
                double massSum = simulationObject.mass + collisionObject.mass;
                newV1 = new Vector2D();
                newV2 = new Vector2D();

                newV1.x = v1.x * (simulationObject.mass - collisionObject.mass) + 2 * collisionObject.mass * v2.x;
                newV1.x /= massSum;
                newV1.y = v1.y;

                newV2.x = v2.x * (collisionObject.mass - simulationObject.mass) + 2 * simulationObject.mass * v1.x;
                newV2.x /= massSum;
                newV2.y = v2.y;

                //correcting frame to previous state
                simulationObject.velocity.x = newV1.x * Math.Cos(2 * Math.PI - angle) + newV1.y * Math.Sin(360 - angle);
                simulationObject.velocity.y = newV1.x * Math.Sin(2 * Math.PI - angle) + newV1.y * Math.Cos(360 - angle);
                collisionObject.velocity.x = newV2.x * Math.Cos(2 * Math.PI - angle) + newV2.y * Math.Sin(360 - angle);
                collisionObject.velocity.y = newV2.x * Math.Sin(2 * Math.PI - angle) + newV2.y * Math.Cos(360 - angle);
            }
Fakt, nakłądania się kul po odbiciu myślę, że jest póki co do pominięcia, to trzeba jeszcze oczywiście poprawić w wykrywaniu kolizji, ale na razie chciałbym uzyskac zadowalający efekt odbicia ;)
Wiem, rozwlekły post i kodu też trochę, ale może ktoś chcętny jednak poczyta :) za co z góry dzięki :)
Pozdr
Pako
0

Znaczy ogólnie bład znalazłem, strasznie głupi. Zamiast obracać vetory, z wuzględnieniem, zę ich suma (czyli prędkość) ma być taka sama po obrocie co do wartości kierunku i zwrotu, po prostu je obracałem, co skutkowało bzdurnymi wynikami. Parę poprawek trochę poprawiło stan ale tylko trochę. Ale walczę dalej.
Za linki dzięki, przejrzę :)

0

//take action after collision detection
double angle, dx, dy;
dx = collisionObject.position.x - simulationObject.position.x;
dy = collisionObject.position.y - simulationObject.position.y;
//angle of collision
angle = Math.Atan(dy / dx);
Hmm, raczej powinieneś wyznaczyć wcześniej miejsce zderzenia. Dajmy na to mamy taką sytuację:
user image
Trzeba wyznaczyć tx. Jak ? Na podstawie wektorów prędkości zbudować funkcję d(t) odległości środków kul i znaleźć takie tx dla którego d(tx)=r1+r2 (gdzie r1, r2 to promienie kul).

Po przemieszczeniu kul w miejsce zderzenia i obliczeniu nowych wektorów prędkości należy dodatkowo przemieścić obydwie kule (już w nowo obliczonym kierunku) o czas pozostały do pełnego tyknięcia dt - (tx-t1), gdzie dt to czas między tyknięciami.

To tak na początek, stary błąd nie jest rozwiązany, bo kule po zderzeniu i tak powinny lecieć w przeciwne strony.
Przy małym dt może jest to zbędne, ale jak będziesz chciał więcej kul to komp nie uciągnie.
Dodatkowo zderzenie wykrywać trzeba, gdy kule zbliża się na odpowiednią odległość, nie koniecznie muszą się pokryć w czasie t2, gdyż może się zdarzyć tak, że pokrywanie nastąpi wyłącznie między t1 a t2 (muśnięcie).

//if there is probability of collision, check carefully
Region simulationRegion = simulationObject.region;
Region collisionRegion = collisionObject.region;
simulationRegion.Intersect(collisionRegion);
RectangleF[] intersection = simulationRegion.GetRegionScans(new Matrix());
simulationRegion.Dispose();
collisionRegion.Dispose();
po co tak topornie ? Oblicz odległość między środkami, to przecież kule.

Kiedyś na potrzeby nauki synchronizacji robiłem coś podobnego równolegle w C++ (każda kula miała swój wątek). Poszukam później, bo na razie zmykam.

//EDIT
Niestety, dawno już to pisałem i gdzieś wywiało :(

Co do aktualnego błędu - debugger, kartka papieru oraz długopis w łapę i sprawdzasz co idzie nie tak.
Wrzuć gdzieś cały projekt, to może przysiądę chwile.

0

Co do obliczania kolizji - teoretycznie miało być bardziej uniwersalnie, czyli sprawdzać dla dowolnych obiektów. Dalszy fragment, ten z reakcją na kolizję, to już w ogóle taka wolna amerykanka która w ogóle tam nie powinna się znaleźć, ale na potrzeby chwili ją tam umieśicłem, żeby zrobić i zobaczyć te kolizje ;) Dlatego pominąłem na razie takie rzeczy jak przeskakiwanie przez siebie obiektów pomiędzy klatkami (że najpierw jest tuż przed kolizją, a potem już za tym obiektem, jeśli funkcja nie wywołąłą się dość szybko) i takie sprawy.
Błędów okazało się być mnogo. Używałem nie tych zmiennych, obracałem wektory skłądowe, ale nie dbałem o to, by prędkość ostateczna była taka sama, sypało się przy dzieleniach przez zero, których nie sprawdzałem w odpowiednich miejscach i w ogóle.
Ogólnie jednak udało mi się to jakoś okiełznać i doprowadzić do stanu, gdzie kule się od siebie odbijają realistycznie.
Zresztą, brak mi trochę pomysłu na realizację całości. Znaczy jak zrealizować reakcje na kolizję obiektów, przy jednoczesnym braku znajomości wszystkich klas.
Czyli: kulka z kulką odbijają się inaczej (wg innych zasad) niż kulka ze ścianą. I nie mam za bardoz pomysłu jak to rozdzielić, żeby każda klasa nie musiała implementować reakcji z wszystkimi innymi klasami obiektów.

Projektem trochę wstydzę się dzielić - lama jestem ciagle, projekt może i najgorszy nie jest, ale jego jakos mimo wszystko pozostawia pewnie wiele do życzenia. Ciągle brak mi praktyki. Ale jak znajdę wolną chwilę i uporządkuję parę rzeczy, to moze gdzieś wrzucę zipa.

0
pako1337 napisał(a)

Zresztą, brak mi trochę pomysłu na realizację całości. Znaczy jak zrealizować reakcje na kolizję obiektów, przy jednoczesnym braku znajomości wszystkich klas.
Ja widzę przede wszystkim 2 klasy - obiekt styczny i dynamiczny. Obiekty statyczne można traktować jak dynamiczne o nieskończonej masie oraz mogą mieć dowolny kształt (liczy się jedynie punkt i kąt uderzenia).

Co do kształtu obiektów dynamicznych widzę jedynie 2 wyjścia: tylko kule lub dowolne bryły. Przy nie-kulach sytuacja wielokrotnie się komplikuje. Dochodzi ruch obrotowy (dodatkowy wymiar ruchy !), środki masy, moment bezwładności... tak więc zdecyduj się od razu, czy to będą wyłącznie kulki czy bryły.

Co do liczenia odbicia - trzeba wyjść z zasady zachowania energii:

user image

0

No tak też przez weekend miałem trochę czasu i sobie to przemyślałem, ograniczę się na razie jedynie do kul i ograniczenia objętości ścianami, może dowolnymi ścianami, znaczy pod dowolnymi kątami. Bo faktycznie, z realistycznym odwzorowaniem brył mógłby być problem.

1 użytkowników online, w tym zalogowanych: 0, gości: 1