Po co integrować ponad akumulację?

14

Zaczynam się uczyć fizyki majsterkowania i mam pytanie dotyczące implementacji integracji na najbardziej podstawowym poziomie (tj. Nie jest to pytanie Eulera vs. RK4).

Niemal każdy przykład, na jaki natrafiam, ma integrate()funkcję, która pobiera czas od ostatniej aktualizacji i aktualizuje przyspieszenie (i / lub prędkość i / lub pozycję) od ostatniej aktualizacji.

W najprostszej formie: position += velocity * deltaTime

Nie rozumiem jednak, dlaczego gromadzi się w ten sposób, skoro równie łatwo można go uzyskać, zmieniając funkcję . Na przykład: getPosition = makeNewFunction()który może zwrócić coś, co ma sygnaturę Time -> Position, a wewnętrzne funkcje tej funkcji są generowane za pomocą odpowiedniego wzoru matematycznego.

W ten sposób nie dochodzi do kumulacji ... ilekroć trzeba uzyskać pozycję, wywołuje tę funkcję z bieżącym czasem.

Rozumiem dla początkujących, że pozwoli to uniknąć błędów wynikających z akumulacji ... więc dlaczego to nie działa, czego mi brakuje?

(FWIW ja nie ułożyła podstawowy dowód koncepcji tego idea- choć to także testowanie kilka innych rzeczy w tym samym czasie, więc nie jest to najczystszy przykład: https://github.com/dakom/ball-bounce-frp )

EDYCJA 1: jak wspomniano w komentarzach, prawdopodobnie ważne jest, aby zauważyć, że jeszcze nie nauczyłem się o zmianie przyspieszenia lub radzeniu sobie z szarpnięciem i innymi rzeczami, które wymagają integracji wyższego rzędu niż stałe przyspieszenie.

EDYCJA 2: oto podstawowy przykładowy kod pomysłu i składnia pseudo javascript - zauważ, że getKinematicPositionjest częściowo zastosowana, więc zwraca nową funkcję tylko Czas -> Pozycja:

Trzymam się pozycji, ale może to być coś innego, jak getVelocitysądzę ...

getKinematicPosition = initialVelocity => acceleration => time => 
  ((.5 *acceleration) * (time * time)) + (initialVelocity * time);

getPosition = getKinematicPosition ([0,0,0]) (GRAVITY);

onTick = totalTime => {
   position = getPosition (totalTime);
   onCollision = () => {
     getPosition = changeTheFunction(totalTime);
     //changeTheFunction uses totalTime to base updates from 0
     //it could use getKinematicPosition or something else entirely
   }
}
davidkomer
źródło
1
Jaka byłaby twoja funkcja, gdybyś nie miał stałej prędkości / przyspieszenia?
Linaith,
Nie mam pojęcia! : D Jeśli to jest powód - np. Nie zmieniłem jeszcze przyspieszenia, naprawdę doceniłbym pełniejsze wyjaśnienie, w jaki sposób to się zepsuje jako odpowiedź (w przeciwnym razie pójdę tą funkcjonalną drogą i wpadnę w ślepy zaułek !)
davidkomer
6
Cóż, jeśli twój obiekt krąży po prostu w kółko, to na pewno ... a co z sytuacją, w której gracz pcha? Kiedy zadzwonisz do getPosition (teraz + 100), czy przewiduje przyszłość, kiedy gracz przestanie go naciskać? Kiedy wywołasz getPosition (teraz-1000), czy musi pamiętać przeszłość?
user253751

Odpowiedzi:

34

... wewnętrzne działanie tej funkcji jest generowane za pomocą odpowiedniego wzoru matematycznego ...

Będzie to działać w przypadku niektórych klas problemów, a kluczową frazą do wyszukiwania jest rozwiązanie o zamkniętej formie . Na przykład w programie kosmicznym Kerbal ruch statku kosmicznego na orbicie jest obliczany w ten sposób. Niestety, większość nietrywialnych problemów (np. Ponowne wejście w atmosferę tego statku kosmicznego) nie ma znanego rozwiązania w formie zamkniętej. Zatem potrzeba matematycznie prostszych aproksymacji liczbowych (tj. W integrate()czasie).

MooseBoys
źródło
Ahh ... super! Jeśli masz chwilę - co sądzisz o dążeniu do tego funkcjonalnego podejścia, a potem wracasz do gromadzenia, jeśli nie mogę wymyślić, jak to zrobić (np. Jeśli mam do czynienia z problemem nie zamkniętej formy lub Nie mogę wymyślić, jak to zrobić)? Podoba mi się pomysł generowania funkcji, ponieważ pasuje do matematyki 1: 1 - ale jeśli zawsze nieuchronnie
trafię
8
@davidkomer Dlaczego w ogóle chcesz generować funkcje? Jeśli możesz to zrobić, możesz po prostu wstępnie obliczyć i zapisać całą trajektorię! Oczywiście ludzie już to robią: nazywa się to animacją i ma swój udział w subtelnościach.
Joker_vD
Funkcje zmieniają się w zależności od dynamiki środowiska uruchomieniowego ... patrz na przykład odbicie piłki FRP
davidkomer
Właściwie muszę zaktualizować ten przykład, aby był bardziej podobny do ponga, z ruchomymi obiektami kontrolowanymi losowo / przez użytkownika ...
davidkomer
10

Problem z twoim podejściem polega na tym, że nie masz historii swojego obiektu. Możesz obliczyć pozycję, jeśli poruszasz się w określonym kierunku, ale co się stanie, jeśli trafisz coś i odskoczysz?
Jeśli zgromadzisz na swojej ostatniej znanej pozycji, możesz poradzić sobie z uderzeniem i kontynuować od tego momentu. Jeśli spróbujesz obliczyć go od początku, za każdym razem musisz ponownie obliczyć wpływ lub ustawić go jako nową pozycję początkową.

Twój przykład przypominał mi grę wyścigową. (Nie wiem, czy pozycja byłaby kontrolowana przez silnik fizyki, ale myślę, że świetnie to wyjaśnia)
Jeśli jeździsz samochodem, możesz przyspieszyć i zwolnić. Nie możesz obliczyć swojej pozycji, nie wiedząc od początku, jak wyglądał profil prędkości twojego samochodu. Akumulacja odległości jest o wiele łatwiejsza niż zapisywanie prędkości, którą miałeś w każdej klatce od początku do teraz.

Oświadczenie: Do tej pory nie pisałem fizyki gry, tak właśnie widzę problem.

Edycja: na
tym diagramie możesz zobaczyć, jak wartości zmieniają się w czasie.
czerwony = przyspieszenie (od rozpoczęcia przyspieszania do zwolnienia)
zielony = prędkość (od rozpoczęcia do zatrzymania)
niebieski = droga, którą poszedłeś.

Całkowita prędkość jest integralną częścią przyspieszenia od punktu początkowego do faktycznej rejestracji. (Obszar między linią a osią)
. Droga jest całką twojej prędkości.
Jeśli znasz wartości swojego przyspieszenia, możesz obliczyć inne wartości. Ale jeśli się nie mylę, całki są również obliczane przez akumulację na komputerach PC. I to o wiele większe obciążenie, aby przechowywać wszystkie wartości przyspieszenia.
Ponadto prawdopodobnie jest to zbyt wiele, aby obliczyć każdą klatkę.

wykres przyspieszenia / prędkości / czas-droga

Wiem, moje umiejętności malowania są świetne. ;)

Edycja 2:
Ten przykład dotyczy ruchu liniowego. Zmiana kierunku czyni to jeszcze trudniejszym.

Linaith
źródło
„lub ustaw go jako nową pozycję początkową”. - tak, ale nie widzę z tym problemu :) Re: przykład samochodu ... Mam silne wrażenie, że naprawdę muszę zacząć grać z czymś bardziej złożonym, aby intuicyjnie zrozumieć, gdzie to się nie udaje ... ,
davidkomer
ustawienie nowej pozycji prawdopodobnie nie stanowi problemu. zredagowałem część samochodową
Linaith,
1
Wydaje mi się, że w grze samochodowej przyspieszenie byłoby jeszcze bardziej skomplikowane. Prawdopodobnie byłyby skoki i kolce i może to zależeć od prędkości. (Np. Przyspieszenie zmniejsza się do 0, gdy samochód zbliża się do maksymalnej prędkości.)
jpmc26
3
@davidkomer nawet nie zawraca sobie głowy samochodem (chyba że chcesz), wystarczy podstawowa platformówka. Jak działa mario.getPosition (czas) w Super Mario Bros?
user253751
8

Nie rozumiem jednak, dlaczego gromadzi się w ten sposób, skoro równie łatwo można go uzyskać, zmieniając funkcję. Na przykład: getPosition = makeNewFunction (), który może zwrócić coś, co ma sygnaturę Time -> Position, a wewnętrzne działanie tej funkcji jest generowane za pomocą odpowiedniego wzoru matematycznego.

Możesz!

Nazywa się to za pomocą analitycznego lub zamkniętego roztworu. Ma tę zaletę, że jest dokładniejszy, ponieważ błędy zaokrąglania, które kumulują się w czasie, nie istnieją.

Działa to jednak tylko wtedy , gdy znasz wcześniej taki zamknięty formularz. W przypadku gier często tak się nie dzieje.

Ruch gracza jest zmienny i po prostu nie można go włączyć w jakąś wstępnie obliczoną funkcję. Gracz może i często zmienia swoją prędkość i orientację.

NPC mogą potencjalnie wykorzystywać rozwiązania w formie zamkniętej, a czasem tak robią. Ma to jednak inne wady. Pomyśl o prostej grze wyścigowej. Za każdym razem, gdy Twój pojazd zderzy się z innym pojazdem, musisz zmienić swoją funkcję. Może samochód porusza się szybciej w zależności od metra. Zatem znalezienie takiego zamkniętego rozwiązania będzie dość trudne. W rzeczywistości istnieje prawdopodobnie więcej przypadków, w których znalezienie takiej zamkniętej formy jest albo niemożliwe, albo tak skomplikowane, że po prostu niemożliwe.

Doskonałym przykładem zastosowania rozwiązania w formie zamkniętej jest program kosmiczny Kerbal. Gdy tylko rakieta znajdzie się na orbicie i nie będzie pod naciskiem, KSP może umieścić ją „na szynach”. Orbity są z góry ustalone w układzie dwóch ciał i są okresowe. Dopóki rakieta nie zastosuje więcej ciągu, wiesz już, gdzie będzie rakieta, i możesz po prostu zadzwonićgetPositionAtTime(t) (nie ma dokładnie takiej nazwy, ale masz pomysł).

W praktyce jednak stosowanie integracji krok po kroku jest często znacznie bardziej praktyczne. Ale kiedy zobaczysz sytuację, w której istnieje rozwiązanie w formie zamkniętej i jest łatwe do obliczenia, idź do niego! Nie ma powodu, żeby nie aby go używać.

Na przykład, jeśli twoja postać celuje w armatę, możesz łatwo pokazać przewidywany punkt uderzenia kuli armaty, używając rozwiązania o zamkniętej formie. A jeśli twoja gra nie pozwala na zmianę przebiegu kuli armatniej (na przykład bez wiatru), możesz nawet użyć jej do przesunięcia kuli armatniej. Pamiętaj, że musisz szczególnie uważać na przeszkody poruszające się na ścieżkę twojej kuli armatniej.

Istnieje wiele podobnych sytuacji. Jeśli budujesz grę opartą na rundach, wówczas prawdopodobnie będzie dostępnych znacznie więcej zamkniętych rozwiązań niż podczas tworzenia gry RTS, ponieważ znasz wszystkie parametry z wyprzedzeniem i możesz z całą pewnością powiedzieć, że się nie zmieniają (nic nie rusza się nagle na przykład w tę ścieżkę).

Zauważ, że istnieją techniki walki z liczbowymi nieścisłościami stopniowej integracji. Na przykład możesz śledzić nagromadzony błąd i zastosować termin korygujący, aby utrzymać błąd w ryzach, np. Podsumowanie Kahana

Polygnome
źródło
8

W przypadku zwykłej odbijającej się piłki znalezienie rozwiązania w formie zamkniętej jest łatwe. Jednak bardziej złożone układy zwykle wymagają rozwiązania zwykłego równania różniczkowego (ODE). Rozwiązania numeryczne są wymagane do obsługi wszystkich oprócz najprostszych przypadków.

Rzeczywiście istnieją dwie klasy numerycznych solverów ODE: jawne i niejawne. Jawne solwery zapewniają przybliżone przybliżenie do następnego stanu, podczas gdy niejawne solwery wymagają rozwiązania równania, aby to zrobić. To, co opisujesz dla swojej odbijającej się piłki, jest w rzeczywistości ukrytym rozwiązaniem ODE, niezależnie od tego, czy o tym wiedziałeś, czy nie!

Rozwiązania niejawne mają tę zaletę, że mogą wykorzystywać znacznie większe przedziały czasowe. W przypadku algorytmu odbijającej się piłki twój czas może być co najmniej tak długi, jak czas do następnej kolizji (co zmieniłoby twoją funkcję). Może to spowodować, że Twój program będzie działał znacznie szybciej. Jednak generalnie nie zawsze możemy znaleźć dobre ukryte rozwiązania ODE, którymi jesteśmy zainteresowani. Gdy nie możemy, polegamy na wyraźnej integracji.

Dużą zaletą, którą widzę przy wyraźnej integracji, jest to, że gotcha jest dobrze znana. Możesz otworzyć dowolny podręcznik z lat 60. i przeczytać wszystko, co musisz wiedzieć o małych dziwactwach, które pojawiają się przy poszczególnych technikach integracji. W ten sposób programista uczy się tych umiejętności raz i nigdy więcej nie musi się ich uczyć. Jeśli wykonujesz integrację niejawną, każdy przypadek użycia jest nieco inny, z nieco innymi problemami. Nieco trudniej jest zastosować to, czego nauczyłeś się z jednego zadania do następnego.

Cort Ammon
źródło
1

pos (t) = v (t) * t

działa tylko wtedy, gdy pos (0) = 0 oraz v (t) = k

nie można powiązać pozycji z czasem bez znajomości stanu początkowego i całej funkcji prędkości, więc równanie jest przybliżeniem całki

pos (t) = całka v (t) dt od 0 do t

EDYTOWAĆ _________

Oto mały dowód na komentarze (przy założeniu, że pos (0) = 0)

niech v (t) = 4

eqn 1: pos (t) = 4 * t (poprawnie)

eqn 2: pos (t) = c + 4 * t od 0 do t = 4 * t (poprawnie)

niech v (t) = 2 * t

eqn 1: pos (t) = 2 * t ^ 2 (źle)

eqn 2: pos (t) = c + t ^ 2 od 0 do t = t ^ 2 (poprawnie)

Powinienem dodać, że twoje równanie już uwzględnia stałe przyspieszenie (tzn. Twoje równanie to eqn 2, gdzie v (t) = v0 + a * t, a granice integracji wynoszą t0 it), więc twoje równanie powinno działać tak długo, jak aktualizujesz położenie początkowe, prędkość początkowa i przyspieszenie pozostają stałe.

EDIT2 ________

Powinienem również dodać, że można również obliczyć pozycję z początkowym pos, początkową prędkością, początkowym przyspieszeniem i stałym szarpnięciem. Innymi słowy, możesz stworzyć funkcję opartą na równaniu 2, która reprezentuje pozycję w funkcji czasu, dzieląc ją na jej pochodne, tj. Prędkość, szarpnięcie, cokolwiek będzie dalej, itp. Itd., Ale będziesz dokładny w równaniu, jeśli v (t) można modelować w ten sposób. Jeśli v (t) nie może być modelowane tylko z prędkością, przyspieszeniem, stałym szarpnięciem itp., Musisz wrócić do przybliżenia eqn 2, co zwykle zdarza się, gdy coś się podskakuje, opór powietrza, wiatr itp. .

Kyy13
źródło
stała. oznacza to po prostu, że v (t) nie może zmieniać się w czasie
Kyy13,
Muszę z tym usiąść i dowiedzieć się, dlaczego to prawda ... na razie głosowanie :) W odpowiedzi na pytanie zamieściłem próbkę kodu na wypadek, gdyby coś się
zmieniło
nie ma problemu, zaktualizowano ponownie lepszymi słowami :)
Kyy13,