Open thombruce opened 1 year ago
Just happened across the big_space crate when looking for something else: https://docs.rs/big_space/latest/big_space/
Whether or not we implement big_space itself, the approach taken there is worth learning from and is loosely similar to what I've been thinking about implementing:
While using the FloatingOriginPlugin, entities are placed into a GridCell in a large fixed precision grid. Inside a GridCell, an entity’s Transform is relative to the center of that grid cell. If an entity moves into a neighboring cell, its transform will be recomputed relative to the center of that new cell. This prevents Transforms from ever becoming larger than a single grid cell, and thus prevents floating point precision artifacts.
My proposed solution was to separate the universe into grids of different scales and work within only a small grid at a time, essentially teleporting into the next subgrid along on the supergrid as we reach the edges... which is sort of equivalent to the above?
Worth a look at the internals.
There are some clear descriptions of the problem here and suggested ways around it: https://www.reddit.com/r/bevy/comments/pys81b/trying_to_decide_what_game_engine_to_build_a_2d/
My thinking is to use... separate coordinate systems, as planned, though it seems f32 isn't sufficient for even a single planetary system.
However, given that I intend to seamlessly travel between these coordinate grids, is there anything to stop us doing this starting on the scale of a planetary system?
Take Pluto and the Sun as examples. The f32 coordinate grid is insufficient to traverse the distance between them accurately, but I believe we could calculate Pluto's solar orbit accurately despite this. Even in the absence of the Sun itself, Pluto's traversal through a coordinate grid would still follow a predictable arc for simple circular or elliptical orbits with Kepler modelling.
It's also... not strictly necessary, given how slowly the planet travels in its orbit. We really just need to know where it is on a larger coordinate system at any given time when we visit it.
Landing on a planet - that's going to use a separate coordinate system.
Exploring your own ship or a space station - that's (probably) going to use a separate coordinate system. I say "probably" because exiting an airlock should drop you into space relative to that airlock and nearby bodies, which could include ships and stations - this does mess with my planned orientation system (which always locks the ship view to a cardinal grid). What would happen if one coordinate grid were rotated relative to another?
Could ship velocity shift coordinate systems? Once you cross a velocity threshold, you swap to a coordinate system where a pixel is more like a kilometre instead of a metre? This would need to be seamless, your velocity would need to be IMMEDIATELY adjusted. Possible this is an active process, where you switch to a stronger engine system, so any perceptible differences aren't... unexpected. Kinda like jumping into warp.
My thinking is that a coordinate system would be a single quanta of a larger coordinate system, so for instance...
[123.0, 123.0] @ [456.0, 456.0] where the first set represents a 1 m x 1 m scale and the second a 1 km x 1 km scale, so the player is 123 m along and up in a grid tile 456 km along and up on a larger grid. When they reach [1000.0, 1000.0], their position would reset to 0 on a new grid tile living at [457.0, 457.0].
Any problems with doing that, or something quite similar? For their current position, the presence of world entities would be calculated by determining their relative positions first on the larger grid, then if relevant the smaller grid...
Important to know the scale of each grid, and probably to use a predictable factor when increasing. See SI metric prefixes: https://en.wikipedia.org/wiki/Metric_prefix
I've circled back around and am looking at big_space again: https://github.com/aevyrie/big_space
My planned solution was essentially equivalent, but there are some interesting details worth talking about...
This will require some changes to my orbit system, I believe, which does set the positions explicitly.
The docs also have this note about handling orbits:
However, if you have something that must not accumulate error, like the orbit of a planet, you can instead do the orbital calculation (position as a function of time) to compute the absolute position of the planet with high precision, then directly compute the GridCell and [Transform] of that entity using FloatingOriginSettings::translation_to_grid. If the star this planet is orbiting around is also moving through space, note that you can add/subtract grid cells. This means you can do each calculation in the reference frame of the moving body, and sum up the computed translations and grid cell offsets to get a more precise result.
This answers some questions I was beginning to ask about how a planet, in orbit around a star, might transfer between cells on a grid. We first do the regular orbital calculation... then we compute the GridCell (presumably from the origin of the star or parent Orbitable).
There are some other notes about moving the camera around and objects that have a new position exceeding the bounds of the current Grid Cell...
If you are updating the position of an entity with absolute positions, and the position exceeds the bounds of the entity’s grid cell, the floating origin plugin will recenter that entity into its new cell. Every time you update that entity, you will be fighting with the plugin as it constantly recenters your entity. This can especially cause problems with camera controllers which may not expect the large discontinuity in position as an entity moves between cells.
...but this seems to concern absolute position transforms (meaning those not applied as a delta - see above), so... it's a side effect to be on the lookout for, but not one that should be too concerning if we do things right.
Where it might be a problem is in the case of planets, whose positions are calculated absolutely... but the solution implemented by big_space is the same as I had intended to apply myself.
Okay, so we're probably going to use big_space. The whole library is about a thousand lines of code and clearly documented and, at a glance I think I can reasonably understand most of it (probably couldn't have said this a week ago).
Note about big_space: The camera.rs module is just a camera controller provided as a compatible default... I think.
It has input controls... but I just need my camera to exactly match the position of the player. I'm also using the pixel perfect camera plugin... so I don't know how necessary that part of big_space is for my use case. Might have to try playing with it on and off at some point, to see if it yields any kind of noticeable difference. E.g. Do the native Bevy cameras work at all? Do they have bugs not present in this custom camera?
My guess is we don't need it, but we'll see. Something worth noting.
Note that when thinking about scaling, raw numbers aren't the most important thing. While a game like Elite: Dangerous does have around 400 billion stars, and No Man's Sky has 18 quintillion planets, these are sort of nonsense numbers.
Stellaris may be a very different game, but worth noting that the max galaxy size is only around 1000 stars.
I'd much rather a smaller, more focused experience than an expansive repetitive one. And... that's totally at odds with a desire for scale and a notion that it should take a very long time to travel from one side of the galaxy to the other.
But I think we can do... both... -ish. It's just a matter of finding numbers that feel right. And it will be tightly related to other numbers like... how many stories, how many alien races, how many etc. etc. etc.
Big numbers to balance, but numbers that need balancing nonetheless. 🤔
I do think 1000 might be too small. But I do think 100 billion is probably too many. I feel like something in the range of 100 thousand to 1 million feels more correct for this particular game. It's still an unfathomable scale, but... assuming a hundred thousand, say, if we had 100 focal points in the galaxy (stories, races, discoveries), there would still be 1000 stars padding around each of them. That's about 32 stars in each direction (sqrt 1024).
So yeah, that seems kinda right to me. Somewhere in the range of 100,000 to 1,000,000.
Enough space for the world to feel expansive, while little enough that I can conceivably seed it all with handcrafted stories that are never too far away.
When mapping the stars (particularly known stars)...
The positions of stars in a galactic coordinate system used today are given by their Right Ascension and Declination as seen from the Earth. The Sun (Sol), for example, has no Right Ascension or Declination because it is both too close to be markedly different from Earth and it is variable (the Earth orbits the Sun); it's Declination is 0 and its Right Ascension changes across the course of the year (tracing a path known as the ecliptic).
Think of the Right Ascension as the degrees around a circle, and Declination as the angle extruding that circle into a sphere.
To illustrate, a "Pole Star" (assuming it were a perfect pole star) would have a Declination of 90 degrees. At this declination, it would have no meaningful Right Ascension (it would be directly over the circle or right ascension's centre-point).
The current North Star, Polaris, in actuality has these values:
Note that a different star, Gamma Cephei, will become the new North Star sometime around the year 3100 CE.
Pole Stars are a bit of a problem for a 2 dimensional version of our galaxy.
For a lot of stars, we can roughly take the Right Ascension, Declination and distance, and calculate some placement based on these. This works reasonably for any star with low declinations. The higher the declination, the more we have to mess with distances a little (possibly also right ascension - it may make sense to do so in order to ensure that its distance from some other star is not skewed disproportionately compared to its distance to Earth).
It makes a great deal of sense to ignore most stars above a certain declination. It's just infeasible to correct their distances relative to all of their neighbours in a 2-dimensional plane. We may make some exceptions if it makes sense to do so so that we aren't omitting any particularly notable stars. In these cases, we probably exercise a bias favouring distance from Earth. _This might, for example, place Polaris the correct distance from Earth along the Right Ascension angle but will vastly decrease its distance from stars sharing that Right Ascension and vastly increase its distance from stars with an opposed one.
Still, right ascension is given as a time-like value (hhʰ mmᵐ ss.sˢ). We would likely prefer degrees or radians.
Better still, we would prefer actual coordinate values relative to [0, 0] on an xy plane. My initial thinking was that the galactic centre, Sagittarius A*, would be our [0, 0], but I become less sure. Perhaps Sol is the centre of our coordinate system instead, given that this reduces calculation steps...
In either case, the positions of known stars should be calculated at compile time (or earlier) rather than set to be determined at runtime.
It is also possible that Verse will expand to feature more than one galaxy. This isn't presently planned, but if it were the case, it would no longer make sense for Sagittarius A* to necessarily be [0, 0] in our coordinate system. Of course, it makes little sense for Sol to be in either case (this may simply ease calculations, given that our observations are all centred on the Earth - for obvious reasons).
Note that setting Sol as the centre of our simulated galaxy/universe would also enable us to grow the galaxy/universe as we advance in development.
To go from The Solar System to The Milky Way Galaxy is an enormous leap in scale. We might take steps that are much smaller as we work on increasing the available gameplay and story. Start with The Solar System, add a local neighbourhood of stars (see things like "Local Interstellar Cloud", "G-Cloud" and "Very Local Interstellar Medium") and continue to expand outwards from there eventually wrapping our expansion around the entire Milky Way Galaxy (and maybe eventually adding Andromeda and other galaxies in the local group).
I had been wondering how I might go from the arcadey space shooter we currently have to a suddenly much more expansive game. Perhaps these graded steps would allow me to more slowly expand and spread out the gameplay content.
Fun fact: The Local Interstellar Cloud through which our Solar System is moving might be thought of as a nebula.
Nebulae are mostly empty space, of course, so you can't really see them from within or from very close by.
This might mean something for how we represent nebulae in-game, or for how we depict the stars/medium in the background.
For now, just know that the Earth is in a particularly dense interstellar cloud which you might think of as a nebula. One way we might emphasise this eventually is by adjusting the hue of the backdrop and the density of cloud-like patterns depicted moving relative to it (likely in parallax).
Initial implementation will be ludicrously small and will fundamentally exist to test gravity. But we do want star systems, we do want a galaxy and we do want (eventually) planet surfaces. With these, we have to at some point ask questions about scale...
Take our own star system, The Solar System. Earth is about 149.89 million km from the Sun. A lightspeed trip takes about seven minutes to get from one to the other, and the scale just grows greater as we get to the outer planets.
Do we want trips of about 8 minutes between planets in a single system? No. So we do need either faster than light travel or some kind of in-game time acceleration if we're going for realism (we're probably not).
We can also... shrink the distances. This certainly makes sense on planet surfaces. But it won't be unnoticeable.
If for example we model the Martian surface and give it its proper circumference but then also call a single tile "1 meter" and have the surface be, say, only 1 quarter of the tiles around it ought to be... someone can measure that.
Reducing the scale (lying) like this is in the interests of making the game a bit less sparse and a bit more fun to play.
So what about distances between planets?
At the moment, the physics engine believes that 1 pixel = 1 meter. Our ship is ~100 meters across and can reach a speed (with dampeners active) of around 600 m/s... which is not a lot. Even at 1000 m/s (which is still not a lot), a journey between Sun and Earth would take 1735 days. But even if we say the ship can go a million meters a second, that's still a 1.7 day journey. And as I've said, if it could go the speed of light... it's still around 8 minutes.
It's a long time to be in transit between two bodies as a player. The solution to which is to either scale down the star system or allow faster than light travel...
Even so... the Solar System is 143.73 billion km across:
It's well within the limits of what f32 can represent, but how likely are we to start seeing oddities at this scale or at speeds when traversing it?
In particular, realise that dealing with relative positions at points far, far from the origin could result in weird behaviour. It might make sense to always root the world position at [0.0, 0.0] and fix the player to this point while the system being traversed moves instead...
Other ideas include entering a different state when at high speeds - some space systems that are localised could be unloaded - and the high-speed state would have a different coordinate system (e.g. 1 pixel = 1 km instead of 1m), after which as we slow down we can calculate the slow-state coordinate value from the higher speed coordinates... I have a feeling this is what Elite: Dangerous does with its Frame Shift Drive.
An ideal implementation would allow the player to seamlessly accelerate up to a ludicrous speed and then right back down again without any visual trickery making it feel as though the universe isn't continuous (warp effects). Certain objects would probably not be loaded in the high speed state to avoid collisions or the lack thereof (tunnelling), like asteroids. But I do think certain large and known comets should have permanence, like planets, moons and stars.
I haven't even talked about system-to-system scales. We're going to try to implement star systems first, before the capability to jump/warp/wormhole between them.
At present, I believe I will continue course with the player's position unbound, but be aware of the potential for this to become unfeasible either at the scale of a star system or beyond.
All of this said... I don't actually know how Bevy implements its coordinate system under the hood. Maybe it already does some of this magic and camera position is used?
f64 is also useable, if needed, and/or we can replace some critical values with integers.
Point is this:
If
[0.0, 0.0]
is at the other side of the galaxy, we're going to have problems. So we do need some way in which we shift the coordinate system.My first instinct is to have each star system be a separate space and have the gravitational centre of the system be the source coordinates.
If we do that, we should find out relatively early whether or not we have problems on a star system scale. And it might be that we simulate the star system on one larger scale, while letting you explore regions of it on a smaller one, populating those regions with artefacts known to be there in the larger scale simulation...
THAT makes a lot of sense. But we do have to decide how the system is broken up into smaller regions, what happens if a cosmic body (planet, moon, sun, etc.) is presently in position to be drawn in two or more at once? I don't think that should be a problem; just draw it at position regardless of which region you're in - the scale should translate so that there's no noticeable difference when it is undrawn in one and redrawn in another... probably.