Ich fange an, das Erlernen der DIY-Physik zu erlernen, und ich habe eine Frage zur Implementierung der Integration auf der grundlegendsten Ebene (dh dies ist keine Frage von Euler vs. RK4).
Fast jedes Beispiel, auf das ich integrate()
stoße, hat eine Funktion, die den Zeitschritt seit dem letzten Update und die Beschleunigung (und / oder Geschwindigkeit und / oder Position) seit dem letzten Update aktualisiert.
In der einfachsten Form: position += velocity * deltaTime
Ich verstehe jedoch nicht, warum es sich so ansammelt, wenn es genauso leicht durch Ändern einer Funktion erhalten werden kann . Zum Beispiel: getPosition = makeNewFunction()
Was etwas zurückgeben könnte, das die Signatur von hat Time -> Position
, und das Innenleben dieser Funktion werden über die entsprechende mathematische Formel generiert.
Auf diese Weise gibt es keine Akkumulation ... wann immer die Position abgerufen werden muss, ruft es diese Funktion mit der aktuellen Zeit auf.
Mein Neuling Verständnis ist, dass dies auch die Fehler, die von Akkumulation kommen, vermeiden würde ... also warum funktioniert das nicht, was fehle ich?
(Fwiw Ich habe einen grundlegenden Proof of Concept für diese Idee zusammengestellt - obwohl gleichzeitig einige andere Dinge getestet werden, ist dies nicht das sauberste Beispiel: https://github.com/dakom/ball-bounce-frp )
EDIT 1: Wie in den Kommentaren erwähnt, ist es wahrscheinlich wichtig darauf hinzuweisen, dass ich noch nichts über das Ändern der Beschleunigung oder den Umgang mit Ruck und anderen Dingen gelernt habe, die eine Integration höherer Ordnung erfordern als eine konstante Beschleunigung.
EDIT 2: hier einige grundlegende Beispielcode von der Idee, und Pseudo-JavaScript - Syntax - Note , die getKinematicPosition
sich teilweise angewandt , so wird eine neue Funktion von nur Zeit Rückkehr -> Position:
Ich halte an meiner Position fest, aber es könnte auch etwas anderes sein, getVelocity
denke ich ...
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
}
}
quelle
Antworten:
Dies funktioniert für bestimmte Problemklassen, und der Schlüsselbegriff, nach dem gesucht werden muss, ist eine geschlossene Lösung. Beispielsweise wird im Kerbal Space Program die Bewegung eines Raumfahrzeugs im Orbit auf diese Weise berechnet. Leider haben die meisten nicht-trivialen Probleme (z. B. der Wiedereintritt des Raumfahrzeugs in die Atmosphäre) keine bekannte Lösung in geschlossener Form. Daher sind mathematisch einfachere numerische Näherungen (dh
integrate()
über die Zeit) erforderlich .quelle
Das Problem bei Ihrer Vorgehensweise ist, dass Sie keine Historie Ihres Objekts haben. Sie können die Position berechnen, wenn Sie sich in eine Richtung bewegen, aber was passiert, wenn Sie etwas treffen und zurückprallen?
Wenn Sie sich von Ihrer letzten bekannten Position angesammelt haben, können Sie den Aufprall bewältigen und von dort aus fortfahren. Wenn Sie versuchen, es von Anfang an zu berechnen, müssen Sie den Aufprall jedes Mal neu berechnen oder als neue Startposition festlegen.
Ihr Beispiel erinnerte mich an ein Rennspiel. (Ich weiß nicht, ob die Position von der Physik-Engine gesteuert wird, aber ich denke, es funktioniert gut zu erklären)
Wenn Sie mit Ihrem Auto fahren, können Sie beschleunigen und verlangsamen. Sie können Ihre Position nicht berechnen, ohne zu wissen, wie das Geschwindigkeitsprofil Ihres Autos von Anfang an ausgesehen hat. Die Akkumulation der Distanz ist viel einfacher als das Speichern der Geschwindigkeit, die Sie von Anfang an in jedem Frame hatten.
Haftungsausschluss: Ich habe bis jetzt noch keine Spielphysik geschrieben. Genau so sehe ich das Problem.
Bearbeiten:
In diesem Diagramm können Sie sehen, wie sich die Werte im Laufe der Zeit ändern.
rot = Beschleunigung (vom Anfahren bis zum Abbremsen)
grün = Geschwindigkeit (vom Anfahren bis zum Anhalten)
blau = Ihr Weg.
Die Gesamtgeschwindigkeit ist das Integral der Beschleunigung von Ihrem Startpunkt zu Ihrer tatsächlichen Protokollierung. (Der Bereich zwischen Linie und Achse)
Der Weg ist das Integral Ihrer Geschwindigkeit.
Wenn Sie die Werte für Ihre Beschleunigung kennen, können Sie die anderen Werte berechnen. Aber wenn ich mich nicht irre, werden Integrale auch durch Akkumulation auf PCs berechnet. Und es ist viel mehr Aufwand, alle Beschleunigungswerte zu speichern.
Außerdem ist es wahrscheinlich zu viel, um in jedem Frame berechnet zu werden.
Ich weiß, meine Malfähigkeiten sind großartig. ;)
Edit 2:
Dieses Beispiel ist für lineare Bewegungen. Richtungswechsel erschweren dies zusätzlich.
quelle
Du kannst!
Es wird mit einer analytischen oder geschlossenen Lösung aufgerufen . Dies hat den Vorteil, dass es genauer ist, da es keine Rundungsfehler gibt, die sich im Laufe der Zeit ansammeln.
Dies funktioniert jedoch nur, wenn Sie ein solches geschlossenes Formular vorher kennen. Bei Spielen ist dies oft einfach nicht der Fall.
Spielerbewegung ist unberechenbar und kann einfach nicht in eine vorberechnete Funktion umgewandelt werden. Der Spieler kann und wird seine Geschwindigkeit und Orientierung ziemlich oft ändern.
NPCs könnten möglicherweise Lösungen in geschlossener Form verwenden, und tatsächlich tun sie dies manchmal. Dies hat jedoch einige andere Nachteile. Denken Sie an ein einfaches Rennspiel. Jedes Mal, wenn Ihr Fahrzeug mit einem anderen Fahrzeug kollidiert, müssen Sie Ihre Funktion ändern. Vielleicht bewegt sich das Auto je nach Untergrund schneller. Dann wird es ziemlich schwierig sein, eine solche geschlossene Lösung zu finden. In der Tat gibt es wahrscheinlich mehr Fälle, in denen das Auffinden einer solchen geschlossenen Form entweder unmöglich oder so kompliziert ist, dass es einfach nicht durchführbar ist.
Ein gutes Beispiel für die Verwendung einer geschlossenen Lösung ist das Kerbal Space Program. Sobald sich Ihre Rakete in der Umlaufbahn befindet und nicht mehr unter Schub steht, kann KSP sie "auf Schienen" setzen. Umlaufbahnen sind im Zweikörpersystem vorbestimmt und periodisch. Solange die Rakete keinen Schub mehr ausübt, wissen Sie bereits, wo die Rakete sein wird, und können einfach anrufen
getPositionAtTime(t)
(es ist nicht genau so benannt, aber Sie haben die Idee).In der Praxis ist die schrittweise Integration jedoch oft viel praktischer. Aber wenn Sie eine Situation sehen, in der es eine geschlossene Lösung gibt und die einfach zu berechnen ist, entscheiden Sie sich dafür! Es gibt keinen Grund, es nicht zu benutzen.
Wenn Ihr Charakter beispielsweise auf eine Kanone zielt, können Sie den vorhergesagten Aufprallpunkt der Kanonenkugel mit einer geschlossenen Lösung leicht anzeigen. Und wenn Ihr Spiel nicht zulässt, dass der Kurs der Kanonenkugel geändert wird (z. B. kein Wind), können Sie sie sogar zum Bewegen der Kanonenkugel verwenden. Beachten Sie, dass Sie besonders auf Hindernisse achten müssen, die sich in den Weg Ihrer Kanonenkugel bewegen.
Es gibt viele ähnliche Situationen. Wenn Sie ein rundbasiertes Spiel erstellen, gibt es wahrscheinlich weitaus geschlossenere Lösungen als beim Erstellen eines RTS-Spiels, da Sie alle Parameter im Voraus kennen und mit Sicherheit sagen können, dass sie sich nicht ändern (nichts bewegt sich plötzlich) in diesen Weg, zum Beispiel).
Beachten Sie, dass es Techniken gibt, um dann numerische Ungenauigkeiten der schrittweisen Integration zu bekämpfen. Beispielsweise können Sie den akkumulierten Fehler verfolgen und einen Korrekturterm anwenden, um den Fehler in Schach zu halten, z. B. Kahan Summation
quelle
Im Falle eines einfachen springenden Balls ist es einfach, Lösungen mit geschlossener Form zu finden. Bei komplexeren Systemen ist jedoch in der Regel die Lösung einer gewöhnlichen Differentialgleichung (ODE) erforderlich. Numerische Löser sind erforderlich, um alle Fälle außer den einfachsten zu behandeln.
Es gibt in der Tat zwei Klassen numerischer ODE-Löser: explizit und implizit. Explizite Löser bieten eine geschlossene Näherung für Ihren nächsten Zustand, während implizite Löser die Lösung einer Gleichung erfordern. Was Sie für Ihren springenden Ball beschreiben, ist eigentlich ein impliziter ODE-Löser, ob Sie es wussten oder nicht!
Implizite Löser haben den Vorteil, dass sie viel größere Zeitschritte verwenden können. Für Ihren Bouncing Ball-Algorithmus kann Ihr Zeitschritt mindestens so lang sein wie die Dauer bis zur nächsten Kollision (was Ihre Funktion ändern würde). Dadurch kann Ihr Programm viel schneller ausgeführt werden. Im Allgemeinen können wir jedoch nicht immer gute implizite Lösungen für die ODEs finden, an denen wir interessiert sind. Wenn dies nicht möglich ist, greifen wir auf die explizite Integration zurück.
Der große Vorteil, den ich bei der expliziten Integration sehe, ist, dass die Fallstricke bekannt sind. Sie können jedes Lehrbuch aus den 60er Jahren öffnen und alles lesen, was Sie über die kleinen Macken wissen müssen, die bei bestimmten Integrationstechniken auftreten. Ein Entwickler lernt diese Fähigkeiten also einmal und muss sie nie wieder erlernen. Wenn Sie implizite Integration durchführen, ist jeder Anwendungsfall leicht unterschiedlich, mit leicht unterschiedlichen Fallstricken. Es ist etwas schwieriger, das, was Sie von einer Aufgabe gelernt haben, auf die nächste anzuwenden.
quelle
pos (t) = v (t) * t
funktioniert nur, wenn pos (0) = 0 und v (t) = k
Sie können die Position nicht mit der Zeit in Beziehung setzen, ohne die Anfangsbedingung und die gesamte Geschwindigkeitsfunktion zu kennen. Die Gleichung ist also eine Annäherung an das Integral
pos (t) = Integral von v (t) dt von 0 bis t
EDIT _________
Hier ist ein kleiner Beweis für die Kommentare (unter der Annahme, dass pos (0) = 0)
sei v (t) = 4
Gleichung 1: pos (t) = 4 * t (richtig)
Gleichung 2: pos (t) = c + 4 * t von 0 bis t = 4 * t (richtig)
sei v (t) = 2 * t
Gleichung 1: pos (t) = 2 * t ^ 2 (falsch)
Gleichung 2: pos (t) = c + t ^ 2 von 0 bis t = t ^ 2 (richtig)
Ich sollte hinzufügen, dass Ihre Gleichung bereits eine konstante Beschleunigung berücksichtigt (dh Ihre Gleichung ist Gleichung 2, wobei v (t) = v0 + a * t und die Integrationsgrenzen t0 und t sind), sodass Ihre Gleichung so lange funktionieren sollte, wie Sie sie aktualisieren Ausgangsposition, Anfangsgeschwindigkeit und Beschleunigung bleiben konstant.
EDIT2 ________
Ich sollte auch hinzufügen, dass Sie auch die Position mit der Anfangsposition, der Anfangsgeschwindigkeit, der Anfangsbeschleunigung und dem konstanten Ruck berechnen können. Mit anderen Worten, Sie können eine Funktion auf der Grundlage von Gleichung 2 erstellen, die die Position gegen die Zeit darstellt, indem Sie sie in ihre Ableitungen, dh Geschwindigkeit, Ruck, was auch immer als nächstes kommt, usw. usw., aufteilen. Sie werden jedoch in Ihrer Gleichung nur dann genau sein, wenn v (t) kann auf diese Weise modelliert werden. Wenn v (t) nicht nur mit Geschwindigkeit, Beschleunigung, konstantem Ruck usw. modelliert werden kann, müssen Sie zu einer Näherung von Gleichung 2 zurückkehren, die häufig auftritt, wenn Dinge springen, Luftwiderstand, Wind usw .
quelle