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.localTime
durch 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 RigidBody
und 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.
quelle
setRandSeed
Funktion hat, dielong
als 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 ...Antworten:
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.
quelle
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.
quelle
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:
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:
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.
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.
quelle
Ich habe die Bullet-Physik deterministisch gemacht, indem ich den gesamten Bullet-Speicher gespeichert und wiederhergestellt habe: Rufen Sie einfach an
btAlignedAllocSetCustom
und fordern SiebtAlignedAllocSetCustomAligned
einen benutzerdefinierten Allokator an. Das funktioniert immer.quelle
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.
quelle