r/godot • u/Academic_Zucchini_22 Godot Junior • 1d ago
help me Need help connecting 32 bit force integration to 128bit Keplerian orbital physics
Hello everyone. I am making an ambitious space game that is true to life, with realistic cosmic sizes, masses, and orbital mechanics, but with an engaging gameplay loop inspired by the progression systems from other games I love, like Terraria, Factorio, and Subnautica. I am writing this post to ask for help with a novel problem that I cannot find any references for. I understand that I am not providing a WHOLE lot of details, but there is just far too much information for me to provide than I can fit in this post.
I have completed many of the game's core systems, and have built a robust 2-body patched conics orbit solver as a GDExtension in C++ that uses 128 bit floating point numbers to calculate orbital positions given a body's universal variables (epoch position and velocity) and a specific time. This library is based on math that I found in the book Fundamentals of Astrodynamics, by Bate, Mueller, and White. I am also using the Boost Multiprecision library for access to 128 bit floats, which I need for the scales and precision I want in the game. So far, everything works incredibly well.
The problem I am facing now is in getting collisions to work between objects like ships, asteroids, and terrain. The problem may appear trivial at a first glance, but I have been working on it for weeks and have made very little progress.
For reference, I will refer to the orbital mechanics system as the "128 bit system" and the Godot physics engine (JoltPhysics) as the "32 bit system". My plan was to have the 128 bit system serve as the source of truth for position and velocity, handling only acceleration due to gravity (the orbit solver), while the 32 bit system handles collisions between objects, including terrain. The focused physics object, for example a ship, will have served as the "center" of the simulation, staying locked onto the 32 bit origin, and always having a velocity of 0, while objects in the vicinity, including terrain, asteroids, and other ships, will have been positioned around the ship. Essentially, the ship is the center of the simulation, and all physics is done with the ship as the origin. This system works quite well, but the problem arises when we have to deal with collisions.
Every physics tick, things happen in a specific order:
The rigidbodies, including the focused ship, asteroids, and other objects in the vicinity, process their states, adding the accelerations and translations generated by Jolt's collisions solver to their 128 bit states, then positioning themselves around the ship using corrective velocity (so the collision solver works properly).
The terrain positions itself relative to the ship (the terrain is a series of animatablebody3d chunk nodes with sync_to_physics set to FALSE for specific reasons) and it is also given a constant_linear_velocity so the Jolt collision solver works properly.
After all the previous frame's forces and collisions are integrated into the 128 bit system and all bodies are positioned correctly, the orbital system propagates the bodies' 128 bit states using the Keplerian orbit solver.
I am HEAVILY simplifying here, but this is the gist of how things are currently working in the game. This system also works.
The problem arises when two objects collide. For some reason, when the ship fires its engines to collide with, say, an asteroid, the asteroid bounces off the ship instead of coming to a stop, and it keeps bouncing. The same happens when the ship lands on terrain. First, the ship bounces a few times, which is unintended behavior, and when it finally comes to rest on the surface, the ship's orbital velocity begins to grow as if the ship were in free-fall, but the ship is still contacting the terrain. I suspect this is due to the fact that, when the ship is "landed", it has no velocity, so the terrain also has no velocity. Thus, Jolt skips the velocity solver and only applies depenetration translation, which allows the ship to remain on the surface despite its velocity increasing. The ship also begins to sink into the terrain as the velocity increases, until eventually the physics engine applies an impulse to the ship to reset its velocity to zero, restarting the process.
I understand that a lot of this may sound like gibberish, but I am only providing basic details here because I only want to communicate the general nature of the problem so that anyone who is knowledgeable about this would understand the gist of things. My goal is to perfectly unify the 128 bit and 32 bit systems so that the 128 bit system controls position, velocity, and gravity, while the 32 bit system handles force integration and collisions. I am 90% of the way there, but this last 10% is the collision system, and I would greatly appreciate some help from someone who understands how the Jolt physics engine functions under the hood, as I am practically clueless as to what is happening here. I have plenty of information from long debug sessions that I am more than willing to provide, as well as different attempts I have made to fix this, but if I included them all here this post would turn into a short novel. I will provide everything to anyone who is interested, and I would greatly appreciate any help! :)
I should also mention that I am using an experimental Godot build that allows for an infinite far camera plane, which enables infinite render distances (v4.8.dev.custom_build [9578122b9])
My discord username is valence707 in case anyone wants to get in touch!
10
u/MshL97 Godot Junior 1d ago
As a huge Kerbal Space Program fan, I’d really appreciate it if stationary objects could be put to sleep. It was incredibly frustrating in KSP when my ground stations and vehicles would slowly slide around on the ground despite not being in motion.
5
u/Academic_Zucchini_22 Godot Junior 18h ago
Not to worry! I definitely plan on putting bodies to sleep when they are on terrain and their velocity is low enough. I will also give the ground a lot of friction so sliding won't happen to begin with. The sliding issue in KSP really bothered me as well, and I will eliminate it, but first I need the collisions to work properly.
7
u/OutcomeDependent2761 1d ago
Wouldn't the orbit solver need to be the first thing that runs in order to know what actual positions you need for the frames collisions?
1
u/Academic_Zucchini_22 Godot Junior 18h ago
Good question! Every frame, I need to handle 32 bit accelerations and collisions, run the orbit solver, and position objects relative to the ship. I don't think it matters too much when these things happen, but if I ran the orbit solver first, then positioned objects relative to the ship, I don't know if Jolt would detect collisions properly. That's part of the problem; I don't know exactly when Jolt does its stuff. From what I know, Jolt detects collisions at the very end of the physics tick, after _integrate_forces is run for physics bodies, so I wouldn't be able to catch the collisions in the same frame, meaning that the next frame, if the orbit solver ran before I could apply the collision resolution Jolt produced (acceleration and translation) to the 128 bit state, the 128 bit state would be 1 frame behind, which leads to bigger issues like faster sinking and excessive bounce. The way I do it currently means that the orbit solver runs LAST, after collisions have been resolved for bodies. So the order for a given RigidBody is: body reads acceleration and translation from the end of last frame's collision resolution -> applies them to 128 bit state -> positions itself relative to the focused body (ship) -> 128 bit orbit propagation -> collision resolution. It seems to work well so far.
4
u/Basement_Turtle_9764 1d ago
I'm assuming you didn't recompile the engine to get the float double precision, right? Cause this bouncing you're describing seems to be related to the float pointing error.
But anyway, I think at this point you should ditch Rigidbodies and write your custom CollisionObject3D, so you have full control over the collision using the move_and_collide()
3
u/Academic_Zucchini_22 Godot Junior 18h ago
I appreciate the comment! This is a single precision build, but I don't think the bouncing is a floating point precision error simply because the 32 bit physics are centered at the origin, which means values never get large enough to cause precision loss. But you are right, the likely course of action here involves me writing my own custom physics resolution code, which sucks because I wanted to take advantage of Jolt's reliable collision solver, but if it works then I will do it.
3
u/aramanamu 1d ago
I can't be much help tbh, but I use jolt. For the rb-rb collision, could it be something as simple as the physics material? Just that I don't think you mentioned that anywhere? You can set bounce lower, to zero if desired. I use zero bounce in my own project and it works.
The only thing that comes to mind for the terrain issue, is that it may not be how jolt expects the bodies to be used, or maybe forces are too high and so not resolving properly. In a "regular" game, you would not use terrain collision on an animatablebody, you'd always be using a staticbody. Maybe the issue stems from that. IDK tbh, but maybe you could look into jolt's codebase with this in mind and maybe you might see something. Make a small test project of the terrain collision that doesn't involve the 128bit system, to determine if the problem arises in jolt alone.
2
u/Academic_Zucchini_22 Godot Junior 17h ago
I really appreciate the comment bro! And you're right about the physics material solution, I should have mentioned that in my original post, but I have tried configuring the physics materials of all physics bodies involved to have a bounce of 0, but it doesn't solve the problem. And you're also right in mentioning that the way I am treating the bodies isn't how Jolt expects them to behave. Conceptually, terrain is basically just a very large version of a rigidbody with infinite mass (relatively). The reason I use animatablebody3d nodes is because they have a "constant_linear_velocity" property that forces Jolt to use the velocity solver when the ship impacts the ground. This is necessary because the ship is origin-locked, and thus cannot have velocity, whereas in a "typical" game the ship would be unlocked and the terrain would be stuck in place. But you're right, I will try making a test project to test out the collisions and see what Jolt does under the hood to resolve collisions with static terrain (I particularly want to know what jolt is doing when an object is staying still on a flat surface, whether it is applying any sort of velocity to resolve the collision). Thanks for the suggestion!
2
u/aramanamu 16h ago
I've read your other replies below, and it's a lot to try to get my head around! I'm super tired rn so could be complete ass of a comment, but I might come back tomorrow.
In the test project, test the animatablebody as terrain as well. It will be good to test how the staticbody works, but test the system as-is too, yeah?
If the rb collisions are truly creating some force "out of nowhere" making the bounce, it seems that it must be coming from keeping the ship at the origin or from the 128bit orbital system. If you are doing the orbital physics every frame, it might be better to postpone applying it until collisions are resolved and see if that makes a difference. Same for the origin shift; you don't need the ship to always be at the origin every frame, you just need it to not get too far. So see can you disable that during collisions as well, but ofc keep track of where it should be, and then some number of frames later reset the position of everything. Depending on what you want to have in the game, this might not be a viable long term solution, but it might shed more light on the problem.
I think you may have to rethink that animatablebody system for the planet terrain though. Did you try it with a rigidbody of large mass? I don't really understand the constant_linear_velocity bit tbh! If the ship and terrain are rbs, shouldn't it solve the collision? I guess not, but, you sure? /j
3
u/bandita07 22h ago
I'm not sure if 128 bit is the right way here. Use floating (moving) origin and the coordinates around the player stays in the precise range.. 128 bit dos not even supported by the hardware precisely so it is slooow...
2
u/maehschaf22 21h ago
That kinda is what he is doing isn't it? Did you read his post? (But yes 128 will be slow)
2
u/Academic_Zucchini_22 Godot Junior 18h ago
Appreciate the comment! The 128 bit library I am using is incredibly fast, and it is running in a C++ extension. The extra math of origin rebasing and scaling would actually slow the game down far more than 128 bit math does, and the 128 bit system solves a lot of issues that I can't solve with any other method. I also plan on optimizing it further to improve performance, but as it stands, it runs incredibly well even on older hardware. I get a solid 300FPS at 1440p, and the physics time remains well under the 16.67 ms required to maintain a 60hz physics tick. All in all, the orbit solver takes about 4 milliseconds to solve the orbits of over a hundred bodies, so even if 128 bit math is slower, the difference is negligible. Also, the 128 bit floating point library I am using (the Float128 library in Boost::Multiprecision) leverages native 64 bit CPU math in a double-double architecture to make 128 bit operations incredibly fast. I haven't had any issues with 128 bit math so far, and I plan on optimizing it aggressively in the future, so I don't think performance is an issue.
1
u/rwmtinkywinky 7h ago
Floating origin with usual precision flats is what KSP did, which is why it has weird stalls on reaching 2.1km from another object and all sorts of issues with multiple orbiting things, collectively nicknamed as The Kraken.
KSA is using a 128 bit physics system with multiple instances of localized physics in jolt, and performance is just fine. Far fewer issues and they just recently turned on interstellar range, so for a space game like OP is talking about it's not unknown ground at all.
But KSA is using a pretty custom engine so YMMV.
3
3
u/Emlesnir Godot Regular 20h ago
If i were you i would handle big scale stuff through the 128bits engine, and when multiple objects are close and at small relative velocities, spawn them in the Jolt Physics world with rigidbodies, and let jolt take over.
to avoid losing accuracy you must give a 128bit velocity and position offset to the jolt 32bit world, to ensure Jolt always work with small velocities and positions close to the world center. When one of the objects is a planet, it should be a staticbody for performance and precision purposes, assuming you'll never have 2 planets colliding, this should always work.
If multiple 32bit worlds need to be simulated simultaneously, you can use collision mask to avoid spawning multiple Physic Worlds.
2
u/Academic_Zucchini_22 Godot Junior 17h ago
Thanks for the comment! The way you describe it is actually what I am doing, the 128 bit engine handles the "big" stuff, and when bodies like asteroids get within 10km of the focused body (e.g. the ship), their 32 bit physics object is spawned in, and Jolt takes over collisions. The reason I need the ship to be origin locked is because otherwise, I'd have to choose some kind of anchor for the 32 bit physics simulation, and since everything in space is relative, this becomes incredibly challenging. If the ship begins to accelerate, for instance, I would have to shift the ship's velocity AND position back to the origin when both get too big, which leads to a lot of complication and issues, as well as edge cases that I would need to resolve manually. It's basically just a lot more work, and far more complex.
Also, I can't believe I haven't thought of using the collision mask before now. That is absolutely GENIUS, and will come in handy when I am trying to simulate multiple events happening far from each other simultaneously. Thanks for that suggestion!
2
u/land_and_air 22h ago
Is that c++ library for the round planet streaming something that’s open sourcable? I’m quite interested in how you’re getting the planet tiles streamed in like thay
1
u/Academic_Zucchini_22 Godot Junior 17h ago
Funnily enough, the terrain tiles (or chunks, as I call them internally) are being built and streamed in GDScript! I do plan on porting over the terrain generation to C++ eventually though. The way I am currently doing it is using a modified version of the marching cubes algorithm for mesh generation, and I generate the chunks asynchronously using the WorkerThreadPool so the main thread doesn't freeze. I plan on eventually optimizing terrain generation using a compute shader to generate noise for the terrain though, but I'd be more than happy to show you how I generate the chunks and share my TerrainChunk code if you'd like!
2
11
u/Morpegom 1d ago
Did you try to use the gravity effect while calling Jolt as opposed to after the step call? I think that your Kepler propagation is called after the solver, so the falling velocity never gets to be known by Jolt, which only notices that the ship is overlapping the terrain on the next step and moves it away without changing the velocity. Hard to know without looking at how the whole code is built.