Wie speichere und stelle ich den Bullet Physics-Status wieder her?

7

Ich suche nach Informationen darüber, wie der Laufzeitzustand starrer Körper mit Bullet Physics gespeichert werden kann. Der größte Teil meiner Welt besteht aus statischen Objekten, aber ich habe auch einige dynamische und kinematische Objekte. Ich muss einen Schnappschuss des Spielstatus machen und später genau denselben Status wiederherstellen, damit dieselbe Simulation erzeugt wird, als ob es keine Pause gäbe. Ich kann leicht (und lieber) die ganze Welt neu erschaffen, einschließlich der starren Körper, ihrer Konfiguration, Kollisionsformen und Einschränkungen.

Außerdem muss ich die Objekte in ihren Ausgangszustand zurücksetzen, aber ich denke, dass das Lösen des Problems beim Speichern / Wiederherstellen auch das Zurücksetzen löst.

Ich verwende JBullet , das auf Bullet Version 2.72 basiert. Dies bedeutet, dass die integrierte Serialisierung für mich nicht verfügbar ist, da sie in Version 2.76 hinzugefügt wurde. Antworten, die die Serialisierung verwenden, sind jedoch ebenfalls willkommen, da Java über eine integrierte Serialisierung verfügt und andere dies möglicherweise nützlich finden. Die Beschreibung auf der Wiki-Seite klingt jedoch so, als ob sie zum Speichern ganzer Welten und nicht nur des Laufzeitstatus gedacht ist.

Wenn Sie den Quellcode von RigidBody und CollisionObject überprüfen, werden viele interne nicht endgültige Felder angezeigt, von denen viele Zeiger statt Grundelemente sind. Ich weiß nicht, welches der Felder ich speichern soll und wie ich mit den nicht primitiven Feldern umgehen soll.

Ich bin überrascht, dass ich im Internet keine Informationen dazu gefunden habe, da ich denke, dass dies für jedes Spiel mit Bullet-Physik erforderlich ist und genaue Funktionen zum Speichern und Wiederherstellen erfordert. Ich würde eine Lösung bevorzugen, die nur die öffentliche API erfordert, aber ich bin auch offen für andere Lösungen, einschließlich der Änderung des Quellcodes als letztes Mittel. Ich bin mir auch der Unterschiede bei Gleitkommaoperationen zwischen Plattformen bewusst, aber das ist für mich kein Problem.

Bearbeiten:

Ich habe die von Byte56 vorgeschlagene Lösung implementiert . Ich speichere jetzt WorldTransform, LinearVelocity und AngularVelocity und stelle sie wieder her. Ich habe auch motionState ausprobiert, aber das hatte keine Auswirkungen. Nach dem Wiederherstellen des Zustands scheint mein Objekt die korrekte Flugbahn bis zur ersten Kollision mit einer (statischen) Wand fortzusetzen. Nach der Kollision folgt das Objekt nicht mehr demselben Pfad. Interessanterweise scheint es nur 5-7 leicht unterschiedliche Pfade zu geben, die das Objekt zufällig auswählt.

Ohne das Speichern und Wiederherstellen ist meine Simulation vollständig deterministisch und liefert immer genau die gleichen Ergebnisse, wenn sie mehrmals mit denselben Startwerten ausgeführt wird.

Ich dachte, dass die Zufälligkeit auf eine ungerade Anzahl von Simulationsschritten zurückzuführen sein könnte. Ich verwende das Überspringen von Frames und das Übergeben von 1.0 / 60 als Deltazeit für stepSimulation : dynamicsWorld.stepSimulation(deltaTime, 100, 1.0f / 200);. Ich habe versucht, 200 auf 180 zu ändern, aber das hat nicht geholfen. Ich frage mich, ob es in der DiscreteDynamicsWorld etwas gibt, das ich auch speichern sollte. Ich habe auch versucht, DiscreteDynamicsWorld.localTimedurch Nachdenken zu sparen , aber kein Glück.

Edit2:

Bei näherer Betrachtung ist sogar die anfängliche Flugbahn vor Kollisionen leicht verschoben. Es scheint jedoch nur einen konstanten Versatz in den Koordinaten zu geben, und nach der ersten Kollision ändert der Pfad die Richtung im Vergleich zum Original.

Ich habe einen benutzerdefinierten Serializer mithilfe der Java Reflection-API implementiert, in dem alle primitiven Felder von RigidBodyund rekursiv gespeichert werden DiscreteDynamicsWorld. Das hat nicht geholfen. Ich habe auch versucht, die Physik-Aktualisierung auf einfach zu ändern dynamicsWorld.stepSimulation(1.0f / 60.0f);, aber auch dort kein Glück. Jetzt habe ich keine Ideen mehr, wie ich vorgehen soll.

msell
quelle
Hey, mir ist gerade aufgefallen, dass der Bullet Physics-Solver eine setRandSeedFunktion hat, die longals Argument dient. Vielleicht hilft das Speichern und Zurücksetzen eines Samens? Ich habe es noch nicht ausprobiert, dachte nur, ich würde dich wissen lassen, um etwas zu untersuchen. Ich bin nicht sicher, wofür der Löser zufällige Werte verwendet ...
MichaelHouse
Der Kommentar besagt, dass "btSeed2 zum Neuanordnen der Einschränkungszeilen verwendet wird. Verbessert die Konvergenz / Reibungsqualität". Noch kein Problem, sieht aber später wichtig aus, wenn ich Einschränkungen serialisieren muss.
Msell
Tatsächlich werden Kollisionen mithilfe von Einschränkungen gelöst, sodass die Zufälligkeit möglicherweise darauf zurückzuführen ist.
Zouch

Antworten:

8

Ich schreibe einfach die Körper- und Formwerte in eine Datei. Wenn ich sie dann lese, erstelle ich einen neuen Körper und eine neue Form und setze die Werte auf die aus der Datei gelesenen. Nach allem, was ich gesehen habe, funktioniert das großartig. Ich habe keine seltsamen Verhaltensweisen oder ähnliches gesehen. Körper, die durch die Luft geflogen sind, tun dies auch weiterhin, wenn das Spiel neu geladen wird.

Die Werte, die ich lese / schreibe, sind: Masse, Reibung, lokale Trägheit, lineare Geschwindigkeit, Winkelgeschwindigkeit.

Beschleunigungswerte werden von anderen Komponenten gespeichert, da ich sowieso nur Impulse anlege. Das Speichern erfolgt nach einem Rahmenschritt, sodass außer der Schwerkraft keine anderen Kräfte vorhanden sind.

Ich nehme einfach diese Werte und erstelle ein neues Physikobjekt, als würde ich der DyanmicsWorld ein neues hinzufügen.

Funktioniert gut genug für mich. Ihre Ergebnisse können variieren, wenn Sie den Motor fortgeschrittener als ich verwenden.


Ich habe das ausprobiert. Ich habe mein Spiel gespeichert, als ein Physikobjekt auf eine Rampe am Boden fiel. Dann lud ich das Spiel und beobachtete, wo das Physikobjekt landete, nachdem ich die Rampe getroffen und zur Ruhe gerollt hatte. Jedes Mal, wenn ich das Objekt lud und betrachtete, landete es genau an der gleichen Stelle, Pixel für Pixel (ich machte Screenshots und verglich sie). Ich habe den Test fünf Mal mit dem gleichen Ergebnis durchgeführt.

Also dachte ich, ich würde es wieder retten, etwas weiter entlang seines Sturzes, bevor es die Rampe erreicht, und dann wieder laden. Das nächste Mal, wenn ich geladen habe, endete es an einem anderen Ort! Jedes Mal, wenn ich es lade, landet es jedoch an dieser zweiten Stelle.

Jedes Mal, wenn ich das Spiel lade, endet es an derselben Stelle, aber diese Stelle kann sich abhängig von den Startbedingungen ändern.


Bei näherer Betrachtung habe ich festgestellt, dass dies ein häufiges Problem ist. Siehe diesen gegen Bullet eingereichten Fehlerbericht . Und dieser Beitrag über Serialisierung und Determinismus . Der Benutzer behauptet, einen guten Determinismus zu haben, wenn er speichert / wiederherstellt, wenn die Szene größtenteils erledigt ist (was mir nicht sehr nützlich erscheint). Dieser Beitrag behauptet, Informationen zu enthalten, die behaupten, dass 3.x Bullet diesbezüglich viel besser sein wird.

Es scheint also ein Misserfolg mit Bullet Physics zu sein . Es besteht die Hoffnung, dass dies bald mit 3.x gelöst wird. Leider wissen wir nicht genau, wie lange es dauern wird, bis 3.x auf JBullet portiert ist.

Wenn Sie jetzt Determinismus benötigen , müssen Sie ein von Bullet Physics unabhängiges System implementieren. Andernfalls können Sie abwarten, was 3.x auf den Tisch bringt.

MichaelHouse
quelle
Ich habe versucht, worldTransform, linearVelocity und angleVelocity zu speichern, aber die Wiederherstellung führt nicht zu identischen Ergebnissen :(. Meine Masse und lokale Trägheit sind Konstanten.
msell
Was ist anders an den Ergebnissen?
MichaelHouse
Es sieht zunächst korrekt aus, aber die wiederhergestellte Simulation weicht vor dem Speichern von der Simulation ab. Ich implementiere eine Wiederholung, daher muss die Physiksimulation Stück für Stück übereinstimmen.
Msell
Interessant. Ich werde heute Abend einige weitere Tests durchführen und prüfen, ob meine Simulationen korrekt sind und ob ich hier etwas verpasst habe. Ich werde Sie wissen lassen, was ich mir ausgedacht habe.
MichaelHouse
Ich habe meine Frage mit den Ergebnissen dessen aktualisiert, was ich jetzt versucht habe.
Msell
5

Aus Ihren letzten Kommentaren geht hervor, dass Sie versuchen, den gesamten internen Simulationsstatus in Bullet zu speichern / wiederherzustellen (überlappende Paare, Kontaktpunkte usw.). Das klingt ... entmutigend!

Eine andere Idee: Entfernen Sie in jedem Frame alle Ihre dynamischen Objekte und fügen Sie sie erneut hinzu. Dies ist natürlich sehr schlecht für die Leistung, aber Sie haben angegeben, dass Sie nicht viele dynamische Objekte haben. Vielleicht ist es also in Ordnung. Das Ziel hier ist es, Bullet dazu zu bringen, im Wesentlichen seinen gesamten internen Zustand zu klären, der Determinismusprobleme verursacht.

Um meinen Denkprozess zu veranschaulichen, lassen Sie uns diese Idee auf das Äußerste bringen: Sie könnten Ihre Bullet-Welt und alle Ihre Bullet-Objekte in jedem Frame zerstören und neu erstellen. Natürlich würden Sie dynamische Objekte mit den gespeicherten Positionen und Geschwindigkeiten aus dem vorherigen Frame neu erstellen, sodass die Simulation weiterhin normal und kontinuierlich erscheint. Dies wäre mit ziemlicher Sicherheit deterministisch, da Sie verhindern, dass Bullet von einem Frame zum nächsten einen eigenen internen Status beibehält.

Eric Undersander
quelle
1
Danke, das ist eine interessante Idee. Ich wollte als nächstes versuchen, den JBullet-Quellcode zu ändern, um die Caches in jedem Frame zu löschen, aber Ihr Vorschlag könnte mit der öffentlichen API gemacht werden.
Msell
Ich habe versucht, alle meine dynamischen und sogar statischen Objekte zu entfernen und wieder hinzuzufügen, aber auch dies hat nicht geholfen. Wenn ich in jedem Frame die gesamte Physikwelt neu erschaffe, bekomme ich endlich das richtige Verhalten. Auch dies wirkt sich nicht zu stark auf die Leistung aus, sodass ich jetzt eine funktionierende Lösung habe. Vielen Dank. Ich kann jetzt auch besser untersuchen, welche Teile der Welt zwischen Frames gehalten werden können.
Msell
3

Wenn ich Ihr Problem verstanden habe, haben Sie die Möglichkeit, einen Anfangszustand und eine konsistente Funktion zum Vorrücken des Zustands um einen Frame festzulegen. Sie haben auch die Möglichkeit, einen aktuellen Status abzurufen, dieser ist jedoch nicht zuverlässig, da die Wiederherstellung zu anderen Ergebnissen führt, als wenn Sie ihn überhaupt nicht wiederhergestellt hätten.

In diesem Fall haben Sie drei Möglichkeiten:

  1. Wenn Sie es schaffen, die genaue Simulation zu reproduzieren, indem Sie dieselben Anfangsparameter festlegen, können Sie einen Status speichern, indem Sie diese Anfangsparameter und die Bildnummer speichern, an der Sie anhalten möchten. Beim Laden des Status setzen Sie Ihre Anfangsparameter zurück und führen die Simulation so schnell wie möglich aus, ohne anzuhalten.

    Dies ist meiner Meinung nach eine pragmatische Lösung, die wahrscheinlich die geringste Anzahl von Änderungen in Ihrem Code erfordert. Wenn der Zweck Ihres Systems darin besteht, Wiederholungen zu implementieren, funktioniert dies ziemlich gut, da es die native Wiedergabe und Vorwärtssuche unterstützt. Rückwärtswiedergabe, Rückwärts- und Zufallssuche sind jedoch sehr teuer. Was uns führt zu:

  2. Wenn Sie nach dem Zufallsprinzip suchen möchten, aber hauptsächlich für die Wiedergabesimulation, und Ihr einziges Problem beim Speichern und Wiederherstellen auf die von Byte56 vorgeschlagene Weise darin besteht, dass es unterschiedliche, aber nicht falsche Ergebnisse liefert , können Sie Option 1 implementieren. mit Keyframes. Dies würde bedeuten, dass Sie während der ersten Simulation (der von Ihnen aufgezeichneten) alle n Frames den Status speichern, wiederherstellen und dann fortfahren.

    Bei der Suche stellen Sie zuerst den nächstgelegenen gespeicherten Status vor dem Frame wieder her, nach dem Sie suchen möchten, und verwenden dann Option 1 oben, um nach Ihrem spezifischen Frame zu suchen. Dies funktioniert wiederum bei zufälliger Suche, ist jedoch beim Rückwärtsspielen nur unwesentlich besser als Option 1. So viele Video-Codecs funktionieren übrigens.

    Je näher Ihre Keyframes sind, desto länger dauert es natürlich, sie zu speichern. Das Suchen wird jedoch schneller sein. Ich bin mir ziemlich sicher, dass Sie in Ihrem Fall einen guten Kompromiss erzielen können.

  3. Verwenden Sie Fortsetzungen . Eine Fortsetzung ist ein Objekt, das den Status eines Programms (oder eines Teils davon) darstellt. Viele Sprachen, einschließlich C #, Lua und Python, bieten eine ausgereifte Unterstützung für Fortsetzungen. Auch Sprachen, in denen Sie direkten Zugriff auf Ihren Speicher haben, wie C, können die Implementierung von Fortsetzungen erleichtern.

    Im Java-Fall scheint Javaflow leider die nächstgelegene Implementierung zu sein , die anscheinend noch nicht abgeschlossen ist. Kann aber einen Blick wert sein. Dies kann einige wesentliche Änderungen in Ihrem Programm erfordern, und Sie werden wahrscheinlich viel mehr als nur die relevanten Parameter speichern.

    Um Fortsetzungen zu verwenden, erstellen Sie einfach ein Fortsetzungsobjekt, wann immer Sie den Programmstatus speichern möchten, und setzen es dann fort, wann immer Sie danach suchen möchten.

Panda Pyjama
quelle
Danke für die hervorragende Antwort. Option 1 ist eine gute Idee. Ich muss überprüfen, wie schnell ich nur die Physik wiedergeben kann, aber ich befürchte, dass es eine Weile dauern wird, bis das Spiel eine halbe Stunde dauert. Außerdem weiß ich nicht, wie ich die Anfangsparameter einstellen soll, ohne die Physikwelt neu zu erstellen. Es ist also etwas kompliziert, aber immer noch machbar. Option 2 wäre wahrscheinlich der beste Weg, obwohl ich denke, dass ich ausgelöste Aktionen separat in exakten Frames speichern muss, da die physikalische Umgebung sehr empfindlich ist.
Msell
1
Über Option 3 Ich sehe nicht, wie es helfen würde, wenn es nur den Stapel und die lokalen Variablen speichert. Java kann den vollständigen Status der Objekthierarchie durch Serialisierung speichern, aber ich habe bereits versucht, dies ohne Erfolg zu verwenden. Ich möchte nicht alles (Geometrie usw.) speichern , sondern nur den Laufzeitstatus. Ich hoffe immer noch, dass ich eine Lösung finden kann, wie der Status von JBullet tatsächlich gespeichert werden kann, selbst wenn Änderungen am Quellcode erforderlich sind. Diese Antwort ist eine gute Sicherungslösung, wenn dies fehlschlägt.
Msell
@msell: Wenn die Wiedergabe der Physik zu teuer ist, können Sie Option 2 wählen. In Bezug auf Option 3 sollen Fortsetzungen den gesamten (oder teilweise, je nach Implementierung) Status der VM auf einem viel niedrigeren Niveau als dem von Ihnen speichern Holen Sie sich mit einfacher Serialisierung. Es ist nicht nur ein Stapel, sondern auch Dinge wie Heap, CPU-Status, Flags und sogar Datei-Handles. Dies wird auf VM-Ebene erledigt, sodass die Wahrscheinlichkeit, dass identische Ergebnisse erzielt werden, viel besser ist als durch die bloße Serialisierung Ihrer Objekte. Viele Spiele implementieren den Spielstatus in einer anderen VM (z. B. in Lua), um das Speichern des Status zu vereinfachen.
Panda Pyjama
2

Ich habe die Bullet-Physik deterministisch gemacht, indem ich den gesamten Bullet-Speicher gespeichert und wiederhergestellt habe: Rufen Sie einfach an btAlignedAllocSetCustomund fordern Sie btAlignedAllocSetCustomAlignedeinen benutzerdefinierten Allokator an. Das funktioniert immer.

Betrüger
quelle
1
Das ist ein interessanter Ansatz. Leider hilft es mir nicht, da ich den Java-Port verwende, an dem der Speicher von der JVM verwaltet wird. Für andere klingt das hilfreich. Wie verbindet man den wiederhergestellten Bullet-Speicher beim Laden mit dem Rest der Spielwelt?
Msell
0

Das Problem hat nichts mit dem Laden oder Speichern des Physikzustands zu tun. Der Vorschlag, den Byte56 gemacht hat, ist vollkommen gültig; Das eigentliche Problem hier und der Grund, warum Sie abweichendes Verhalten feststellen, ist jedoch, dass Ihre Physik einen variablen Zeitschritt verwendet. Lesen Sie diese Frage und Antwort und ändern Sie Ihren Zeitschritt für die Korrektur der Physik, wenn Sie kein anderes Verhalten bei separaten Läufen wünschen.

Vaughan Hilts
quelle
Mein Zeitschritt ist festgelegt und das Verhalten ist bei separaten Läufen dasselbe.
Msell
Warum vergehen Sie dann in Delta-Zeit?
Vaughan Hilts
Es ist eine Konstante. Hätte es wahrscheinlich als timeStep bezeichnen sollen.
Msell