Pęd i kolejność problemów aktualizacji w moim silniku fizyki

22

wprowadź opis zdjęcia tutaj

To pytanie jest kolejnym pytaniem z mojego poprzedniego, dotyczącym wykrywania i rozwiązywania kolizji, które można znaleźć tutaj .


Jeśli nie chcesz czytać poprzedniego pytania, oto krótki opis działania mojego silnika fizyki:

Każda jednostka fizyczna jest przechowywana w klasie o nazwie SSSPBody.

Obsługiwane są tylko AABB.

Każde ciało SSSPBody jest przechowywane w klasie o nazwie SSSPWorld, która aktualizuje każde ciało i obsługuje grawitację.

Każda ramka, SSSPWorld aktualizuje każde ciało.

Każde zaktualizowane ciało szuka pobliskich obiektów w haszu przestrzennym, sprawdza, czy muszą wykryć kolizje z nimi. Jeśli tak, wywołują zdarzenie „kolizji” i sprawdzają, czy muszą rozwiązać z nimi kolizje. Jeśli tak, obliczają wektor penetracji i kierunkowe nakładanie się, a następnie zmieniają pozycję w celu rozwiązania penetracji.

Kiedy ciało zderza się z innym, przenosi prędkość na drugie, po prostu ustawiając prędkość ciała na własną.

Prędkość ciała jest ustawiona na 0, jeśli nie zmieniła pozycji od ostatniej klatki. Jeśli zderzy się również z ruchomym ciałem (takim jak winda lub ruchome platformy), oblicza różnicę ruchu windy, aby sprawdzić, czy ciało nie poruszyło się z ostatniej pozycji.

Ponadto ciało wywołuje zdarzenie „zmiażdżenia”, gdy wszystkie jego narożniki AABB nachodzą na coś w ramce.

To jest PEŁNY kod źródłowy mojej gry. Jest podzielony na trzy projekty. SFMLStart to prosta biblioteka obsługująca wprowadzanie danych, rysowanie i aktualizację encji. SFMLStartPhysics jest najważniejszy, w którym znajdują się klasy SSSPBody i SSSPWorld. PlatformerPhysicsTest to projekt gry, zawierający całą logikę gry.

I to jest „aktualizacja” metoda w klasie SSSPBody komentuje i uproszczonej. Możesz na to spojrzeć tylko wtedy, gdy nie masz ochoty patrzeć na cały projekt SFMLStartSimplePhysics. (A nawet jeśli to zrobisz, powinieneś jeszcze rzucić na to okiem, ponieważ jest to komentarz).


.Gif pokazuje dwa problemy.

  1. Jeśli ciała zostaną umieszczone w innej kolejności, wystąpią różne wyniki. Skrzynie po lewej są identyczne jak skrzynki po prawej stronie, umieszczone tylko w odwrotnej kolejności (w edytorze).
  2. Obie skrzynki powinny być napędzane w kierunku górnej części ekranu. W sytuacji po lewej skrzynie nie są napędzane. Po prawej stronie jest tylko jeden z nich. Obie sytuacje są niezamierzone.

Pierwszy problem: kolejność aktualizacji

Jest to dość proste do zrozumienia. W sytuacji po lewej, najwyższa skrzynia jest aktualizowana przed drugą. Nawet jeśli skrzynia na dole „przenosi” prędkość na drugą, musi poczekać na przejście następnej klatki. Ponieważ się nie poruszał, prędkość dolnej skrzyni jest ustawiona na 0.

Nie mam pojęcia, jak to naprawić. Wolałbym, żeby to rozwiązanie nie zależało od „sortowania” listy aktualizacji, ponieważ czuję, że robię coś złego w całym projekcie silnika fizyki.

Jak główne silniki fizyki (Box2D, Bullet, Chipmunk) radzą sobie z kolejnością aktualizacji?


Drugi problem: tylko jedna skrzynia jest napędzana w kierunku sufitu

Jeszcze nie rozumiem, dlaczego tak się dzieje. To, co robi „sprężyna”, ustawia prędkość ciała na -4000 i ponownie ustawia ją nad samą sprężyną. Nawet jeśli wyłączę kod zmiany położenia, problem nadal występuje.

Mój pomysł jest taki, że kiedy dolna skrzynia zderza się z górną skrzynią, jej prędkość jest ustawiona na 0. Nie jestem pewien, dlaczego tak się dzieje.


Pomimo szansy na wyglądanie jak ktoś, kto zrezygnuje z pierwszego problemu, zamieściłem cały kod źródłowy projektu powyżej. Nie mam niczego, co by to udowodniło, ale uwierzcie mi, starałem się to naprawić, ale po prostu nie mogłem znaleźć rozwiązania i nie mam wcześniejszego doświadczenia z fizyką i kolizjami. Próbowałem rozwiązać te dwa problemy od ponad tygodnia, a teraz jestem zdesperowany.

Nie sądzę, że mogę znaleźć rozwiązanie samodzielnie, bez usuwania wielu funkcji z gry (na przykład przenoszenia prędkości i sprężyn).

Bardzo dziękuję za czas poświęcony na przeczytanie tego pytania, a jeszcze bardziej, jeśli nawet spróbujesz znaleźć rozwiązanie lub sugestię.

Vittorio Romeo
źródło
Ilekroć układasz pudełka w stosy, czy możesz połączyć ich fizykę, aby traktować je jak pojedynczy obiekt?
CiscoIPPhone

Odpowiedzi:

12

W rzeczywistości problemy z kolejnością aktualizacji są dość powszechne w normalnych silnikach fizyki impulsowej, nie można po prostu opóźnić przyłożenia siły, jak sugeruje Vigil, skończyłoby to przerywaniem zachowania energii, gdy obiekt zderza się jednocześnie z 2 innymi. Zwykle jednak udaje im się stworzyć coś, co wydaje się całkiem realne, nawet jeśli inna kolejność aktualizacji przyniosłaby znacznie inny wynik.

W każdym razie, w twoim celu jest wystarczająca czkawka w systemie impulsowym, że sugerowałbym zamiast tego zbudować model masowo-sprężynowy.

Podstawową ideą jest to, że zamiast próby rozwiązania kolizji w jednym kroku przykładasz siłę do zderzających się obiektów, siła ta powinna być równoważna z nakładaniem się obiektów, jest to porównywalne z tym, jak rzeczywiste obiekty podczas kolizji przekształcają swoje wielką energią w tym systemie jest to, że pozwala on na przemieszczanie się energii przez obiekt bez konieczności odbijania się tam i z powrotem, i można to zrobić całkowicie niezależnie od kolejności aktualizacji.

Aby obiekty zatrzymywały się, a nie odbijały w nieskończoność, będziesz musiał zastosować jakąś formę tłumienia, możesz znacznie wpłynąć na styl i styl gry w zależności od tego, jak to zrobisz, ale bardzo podstawowym podejściem byłoby przyłóż siłę do dwóch dotykających obiektów, równoważną ich wewnętrznemu ruchowi, możesz zastosować ją tylko wtedy, gdy poruszają się ku sobie, lub też gdy odsuwają się od siebie, ten ostatni może być użyty, aby całkowicie zapobiec odbijaniu się obiektów kiedy uderzą o ziemię, ale sprawią, że będą trochę lepkie.

Można również uzyskać efekt tarcia, hamując obiekt w kierunku prostopadłym do zderzenia, czas hamowania powinien być równoważny z nakładaniem się.

Możesz łatwo ominąć pojęcie masy, sprawiając, że wszystkie obiekty mają tę samą masę, a obiekty nieruchome będą działać tak, jakby miały nieskończoną masę, jeśli po prostu zaniedbujesz je.

Trochę pseudokodu, na wypadek gdyby powyższe nie było wystarczająco jasne:

//Presuming that you have done collision checks between two objects and now have  
//numbers for how much they overlap in each direction.
overlapX
overlapY
if(overlapX<overlapY){ //Do collision in direction X
    if(obj1.X>obj2.X){
        swap(obj1,obj2)
    }
    //Spring effect:
    obj1.addXvelocity-=overlapX*0.1 //Constant, the lower this is set the softer the  
                                    //collision will be.
    obj2.addXvelocity+=overlapX*0.1
    //Dampener effect:
    velocityDifference=obj2.Xvelocity-obj1.Xvelocity
    //velocityDifference=min(velocityDifference,0) //Uncomment to only dampen when  
                                                   //objects move towards each other.
    obj1.addXvelocity+=velocityDifference*0.1 //Constant, higher for more dampening.
    obj2.addXvelocity-=velocityDifference*0.1
    //Friction effect:
    if(obj1.Yvelocity>obj2.Yvelocity){
        swap(obj1,obj2)
    }
    friction=overlapX*0.01
    if(2*friction>obj2.Yvelocity-obj1.Yvelocity){
        obj1.addYvelocity+=(obj2.Yvelocity-obj1.Yvelocity)/2
        obj2.addYvelocity-=(obj2.Yvelocity-obj1.Yvelocity)/2
    }
    else{
        obj1.addYvelocity+=friction
        obj2.addYvelocity-=friction
    }
}
else{ //Do collision in direction Y

}

Istotą właściwości addXvelocity i addYvelocity jest to, że są one dodawane do prędkości obiektu po zakończeniu obsługi kolizji.

Edycja:
Możesz robić rzeczy w następującej kolejności, w której każdy pocisk musi zostać wykonany na wszystkich elementach przed wykonaniem następnego:

  • Wykryj kolizje, mogą zostać rozwiązane natychmiast po ich wykryciu.
  • Dodaj wartości addVelocity do wartości prędkości, dodaj grawitację Yvelocity, zresetuj wartości addVelocity na 0, przesuwaj obiekty zgodnie z ich prędkością.
  • Renderuj scenę.

Zdaję sobie również sprawę, że poniższe informacje mogą nie być całkowicie jasne w moim początkowym poście, pod wpływem grawitacji obiekty nakładają się na siebie, co sugeruje, że ich pole kolizji powinno być nieco wyższe niż ich reprezentacja graficzna, aby uniknąć nakładania się naocznie. Ten problem będzie mniejszy, jeśli fizyka działa z wyższą częstotliwością aktualizacji. Sugeruję, abyś spróbował pracować z częstotliwością 120 Hz, aby uzyskać rozsądny kompromis między czasem procesora a dokładnością fizyki.

Edycja2:
Bardzo podstawowy przepływ silnika fizyki:

  • Zderzenia i grawitacja wytwarzają siłę / przyspieszenie. acceleration = [Complicated formulas]
  • Siła / przyspieszenie są dodawane do prędkości. velocity += acceleration
  • Prędkość jest dodawana do pozycji. position += velocity
aaaaaaaaaaaa
źródło
Wygląda dobrze, nigdy nie myślałem o masowej wiosennej platformówce. Kciuki w górę za coś pouczającego :)
EnoughTea
Spróbuję to zaimplementować za kilka godzin, kiedy wrócę do domu. Czy powinienem przesuwać ciała (Pozycja + = Prędkość) jednocześnie, a następnie sprawdzać kolizje, czy też przemieszczać się i sprawdzać kolizje jeden po drugim? [Czy muszę w ogóle ręcznie modyfikować pozycję, aby rozwiązać kolizje? A może zajmie się tym zmiana prędkości?]
Vittorio Romeo,
Nie jestem do końca pewien, jak interpretować twoje pierwsze pytanie. Rozdzielczość kolizji zmieni prędkość, a zatem tylko pośrednio wpłynie na pozycję.
aaaaaaaaaaaa
Faktem jest, że poruszam bytami ręcznie ustawiając ich prędkość na określoną wartość. Aby rozwiązać nakładanie się, usuwam odległość nakładania się z ich położenia. Jeśli użyję twojej metody, czy będę musiał poruszać bytami za pomocą sił lub czegoś innego? Nigdy wcześniej tego nie robiłem.
Vittorio Romeo,
Technicznie tak, będziesz musiał użyć sił, w moim fragmencie kodu jest to jednak nieco uproszczone, ponieważ wszystkie obiekty mają wagę 1, a zatem siła jest równa przyspieszeniu.
aaaaaaaaaaaa
14

Cóż, oczywiście nie jesteś kimś, kto łatwo się poddaje, jesteś prawdziwym człowiekiem z żelaza, rzuciłbym ręce w powietrze znacznie wcześniej, ponieważ projekt ten bardzo przypomina las wodorostów :)

Po pierwsze, pozycje i prędkości ustawia się wszędzie, z punktu widzenia podsystemu fizyki jest to przepis na katastrofę. Ponadto, zmieniając integralne rzeczy za pomocą różnych podsystemów, utwórz prywatne metody, takie jak „ChangeVelocityByPhysicsEngine”, „ChangeVelocityBySpring”, „LimitVelocity”, „TransferVelocity” lub coś w tym rodzaju. Doda to możliwość sprawdzania zmian dokonanych przez określoną część logiki i zapewni dodatkowe znaczenie dla tych zmian prędkości. W ten sposób debugowanie byłoby łatwiejsze.

Pierwszy problem

Na samo pytanie. Teraz stosujesz tylko poprawki pozycji i prędkości „na bieżąco” w kolejności wyglądu i logiki gry. To nie zadziała w przypadku złożonych interakcji bez dokładnego zakodowania fizyki każdej złożonej rzeczy. Oddzielny silnik fizyki nie jest wtedy potrzebny.

Aby wykonywać złożone interakcje bez hacków, musisz dodać dodatkowy krok między wykrywaniem kolizji na podstawie pozycji, które zostały zmienione przez prędkości początkowe, a końcowymi zmianami pozycji na podstawie „prędkości pospiesznej”. Wyobrażam sobie, że wyglądałoby to tak:

  • zintegruj prędkość za pomocą wszystkich sił działających na ciała (teraz stosujesz poprawki prędkości, pozostaw obliczenia prędkości w silniku fizyki i użyj sił, aby poruszać rzeczami zamiast tego) , a następnie użyj nowej prędkości do zintegrowania pozycji.
  • wykrywaj kolizje, a następnie przywracaj prędkość i pozycje,
  • następnie przetwarzaj kolizje (używając impulsów bez natychmiastowej aktualizacji pozycji, ofc, zmienia się tylko prędkość do ostatniego kroku)
  • zintegruj ponownie nową prędkość i ponownie przetwarzaj wszystkie kolizje za pomocą impulsów, tyle że teraz kolizje są nieelastyczne.
  • dokonać ostatecznej integracji pozycji przy użyciu wynikowej prędkości.

Mogą pojawić się dodatkowe rzeczy, takie jak szarpanie się, odmowa kumulowania się, gdy FPS jest mały, lub inne rzeczy, bądź przygotowany :)

Drugi problem

Prędkość pionowa obu „martwych” skrzyń nigdy nie zmienia się od zera. O dziwo, w pętli aktualizacji PhysSpring przypisujesz prędkość, ale w pętli aktualizacji PhysCrate jest już zero. Można znaleźć linię, w której prędkość nie działa, ale przestałem tutaj debugować, ponieważ jest to sytuacja „Reap What You Sew”. Czas przestać kodować i zacząć wszystko od nowa, kiedy debugowanie staje się trudne. Ale jeśli dojdzie do punktu, w którym nawet autor kodu nie jest w stanie zrozumieć, co się dzieje w kodzie, twoja baza kodu jest już martwa, nie zdając sobie z tego sprawy :)

Trzeci problem

Myślę, że coś jest nie tak, gdy trzeba odtworzyć część Farseer, aby zrobić prostą platformówkę opartą na kafelkach. Osobiście pomyślałbym o twoim obecnym silniku jako o ogromnym doświadczeniu, a następnie porzucił go całkowicie, aby uzyskać prostszą i prostszą fizykę opartą na płytkach. Mając to na uwadze, rozsądnie byłoby zająć się takimi rzeczami jak debugowanie. Zapewnij, a może nawet, o zgrozo, testy jednostkowe, ponieważ wcześniej można byłoby złapać nieoczekiwane rzeczy.

Dosyć
źródło
Podobało mi się to porównanie „lasu wodorostów”.
Den
Właściwie trochę się wstydzę używać takich słów, ale czułem, że jeśli spowoduje to refaktoryzację lub dwa, to byłoby uzasadnione.
EnoughTea
Przy tylko jednym teście w t nie zawsze będzie taka możliwość? Wyobrażam sobie, że musisz zintegrować prędkości w t, a następnie sprawdzić kolizje w t + 1 przed ustawieniem jakichkolwiek prędkości na 0?
Jonathan Connell,
Tak, wykrywamy kolizje z wyprzedzeniem po zintegrowaniu stanu początkowego z t do t + dt za pomocą Runge-Kutta lub czegoś takiego.
EnoughTea
„zintegruj prędkość za pomocą wszystkich sił działających na ciała” „pozostaw obliczenia prędkości silnikowi fizyki” - Rozumiem, co próbujesz powiedzieć, ale nie mam pojęcia, jak to zrobić. Czy jest jakiś przykład / artykuł, który możesz mi pokazać?
Vittorio Romeo,
7

Kiedy ciało zderza się z innym, przenosi prędkość na drugie, po prostu ustawiając prędkość ciała na własną.

Twoim problemem jest to, że są to zasadniczo błędne założenia dotyczące ruchu, więc to, co dostajesz, nie przypomina ruchu, ponieważ go znasz.

Kiedy ciało zderza się z innym, pęd zostaje zachowany. Myślenie o tym jako o „A uderza B” w porównaniu z „B uderza A” oznacza zastosowanie czasownika przechodniego w sytuacji nieprzechodniej. Zderzają się A i B; wynikowy pęd musi być równy momentowi początkowemu. Oznacza to, że jeśli A i B mają równą masę, oba teraz podróżują ze średnią ich pierwotnych prędkości.

Prawdopodobnie będziesz również potrzebować trochę kolizji i iteracyjnego rozwiązania, w przeciwnym razie napotkasz problemy ze stabilnością. Prawdopodobnie powinieneś przeczytać niektóre prezentacje Erin Catto GDC.


źródło
2
Uzyskują średnią prędkość początkową tylko wtedy, gdy zderzenie jest całkowicie nieelastyczne, np. A i B to kawałki ciasta.
Mikael Öhman
„po prostu ustawiając prędkość ciała na własną”. Takie stwierdzenia wyjaśniają, dlaczego nie działa. Ogólnie rzecz biorąc, zawsze stwierdziłem, że niedoświadczeni ludzie piszą systemy fizyki, nie rozumiejąc związanych z tym zasad. Nigdy nie „ustawiasz prędkości” ani „po prostu ...”. Każda modyfikacja właściwości ciała powinna być bezpośrednim zastosowaniem praw dynamiki; w tym zachowanie pędu, energii itp. Tak, zawsze będą istnieć czynniki kruchości, które kompensują niestabilność, ale w żadnym momencie nie możesz po prostu magicznie zmienić prędkości ciała.
MrCranky,
Najłatwiej jest założyć nieelastyczne ciała, starając się uruchomić silnik, im mniej skomplikowane, tym lepiej dla rozwiązywania problemów.
Patrick Hughes,
4

Myślę, że podjąłeś naprawdę szlachetny wysiłek, ale wydaje się, że istnieją podstawowe problemy ze strukturą kodu. Jak zasugerowali inni, pomocne może być podzielenie operacji na dyskretne części, np .:

  1. Faza szeroka : Pętla przez wszystkie obiekty - wykonaj szybki test (np. AABB), aby ustalić, które obiekty mogą kolidować - odrzuć te, które nie są.
  2. Wąska faza : Zapętlić wszystkie kolidujące obiekty - obliczyć wektor penetracji dla kolizji (np. Za pomocą SAT).
  3. Reakcja na zderzenie : Zapętlić listę wektorów zderzeniowych - obliczyć wektor siły na podstawie masy, a następnie użyć go do obliczenia wektora przyspieszenia.
  4. Integracja : Zapętlić wszystkie wektory przyspieszenia i zintegrować pozycję (i obrócić w razie potrzeby).
  5. Renderowanie : Zapętlaj wszystkie obliczone pozycje i renderuj każdy obiekt.

Dzięki rozdzieleniu faz wszystkie obiekty są aktualizowane synchronicznie i nie będziesz mieć zależności od kolejności, z którymi się obecnie zmagasz. Kod okazuje się również ogólnie prostszy i łatwiejszy do zmiany. Każda z tych faz jest dość ogólna i często możliwe jest zastąpienie lepszych algorytmów po posiadaniu działającego systemu.

To powiedziawszy, każda z tych części jest nauką samą w sobie i może zająć dużo czasu, próbując znaleźć optymalne rozwiązanie. Lepiej zacząć od niektórych najczęściej używanych algorytmów:

  • Wykrywanie kolizji w fazie szerokiej : mieszanie przestrzenne .
  • Wykrywanie kolizji w wąskiej fazie : w przypadku prostej fizyki płytek można po prostu zastosować testy przecięcia osi AABB. W przypadku bardziej skomplikowanych kształtów można użyć twierdzenia o osi oddzielającej . Niezależnie od używanego algorytmu, powinien zwrócić kierunek i głębokość przecięcia dwóch obiektów (zwany wektorem penetracji).
  • Reakcja na zderzenie : Użyj Projekcji, aby rozwiązać penetrację.
  • Integracja : Integrator jest największym wyznacznikiem stabilności i prędkości silnika. Dwie popularne opcje to integracja Verlet (szybka, ale prosta) lub RK4 (dokładna, ale wolna). Korzystanie z integracji Verleta może prowadzić do niezwykle prostego projektu, ponieważ większość zachowań fizycznych (odbicie, obrót) po prostu działa bez większego wysiłku. Jednym z najlepszych odniesień, jakie widziałem podczas nauki integracji RK4, jest seria Glena Fiedlera na temat fizyki gier .

Dobrym (i oczywistym) miejscem na początek są reguły ruchu Newtona .

lukevanin
źródło
Dziękuję za odpowiedź. Jak jednak przenosić prędkość między ciałami? Czy dzieje się to podczas fazy integracji?
Vittorio Romeo,
W pewnym sensie tak. Transfer prędkości rozpoczyna się od fazy reakcji na zderzenie. Wtedy obliczasz siły działające na ciała. Siła przekłada się na przyspieszenie przy użyciu wzoru przyspieszenie = siła / masa. Przyspieszenie jest używane w fazie integracji do obliczania prędkości, a następnie do obliczania pozycji. Dokładność fazy całkowania określa, jak dokładnie prędkość (a następnie pozycja) zmienia się w czasie.
Luke Van W