aevyrie / big_space

Floating origin plugin for spaces larger than the universe
Apache License 2.0
168 stars 22 forks source link

2+ cameras and render passes / minimap / split screen #17

Closed johnny-smitherson closed 5 months ago

johnny-smitherson commented 6 months ago

Has this been attempted to be used with 2 cameras with different render passes?

Here are some use cases: bevy split screen example and trackball + minimap.

Adding 2 objects with the FloatingOrigin component triggers panic.

I have not looked too closely at what this would all entail, but I do see

https://github.com/aevyrie/big_space/blob/2eb87f948d362b8d18ab77ac77bb7b7441482ee4/src/reference_frame/local_origin.rs#L443

This "local floating origin" value that needs to be updated on all the reference frames would need to be computed for each of the cameras/floating origins we want to render from.

And I guess I'd go down the rabbit hole from there, using the correct instance of the data when the correct camera is rendering, right?

Also, bevy seems to only give us 32 RenderLayers for control of what actually shows up on the cameras - would we need to put the different "local floating origin" recomputations on different layers, or could this be done by changing the data on the fly when doing each camera's render pass?

Finally, is such a feature desired in the lib?

aevyrie commented 6 months ago

There is nothing stopping you from using multiple cameras with a single floating origin. The only thing that matters is the distance of each camera from the floating origin. If you have 2 player split screen, you might place the floating origin at the center point between the two players, and prevent them from moving more than a kilometer apart. The floating origin has no coupling with cameras at all - it only defines what to use as the origin for computing the GlobalTransform of all entities. It just happens to be that when you have a single camera, making that entity be your floating origin is what you want.

The only way to support multiple floating origins would be if each floating origin was operating on an entirely unique set of objects - they could not overlap. So yes, you could use renderlayers for this, but only in cases where an object is on a single layer - it's possible for objects to be on many layers. This would require you to effectively have two copies of everything in your world.

For the case of the minimap or similar, you can simply add the IgnoreFloatingOrigin component, and work with those objects as if they were in a normal bevy transform system.

I'm not opposed to this feature on principle, but I wouldn't want to merge it if it greatly increased complexity or impacted performance.

johnny-smitherson commented 6 months ago

Thanks for the clarification, I understand things better now.

you might place the floating origin at the center point between the two players, and prevent them from moving more than a kilometer apart

Yep, tried it and it works great as long as all the cameras are around the same grid cells as you said. Still, I would like to try and work around this limitation. My use case would also be a "spectator mode" where you watch 4-8 players that are spread out further than just a few cells. The ability to spectate any set of players/things in the universe in small pop-up windows is also interesting.

This would require you to effectively have two copies of everything in your world.

Actually, since the cameras would be so far apart, it makes sense to have a different distance culling and level of detail for each object vs. each camera - so object duplication and one-hot render layers absolutely make sense here.

The only way to support multiple floating origins would be if each floating origin was operating on an entirely unique set of objects - they could not overlap.

Ah, makes sense, GlobalTransform cannot have two values for the same object, you have to pick one or the other. I wonder if I can override the GlobalTransform in the camera's render pipeline extract stage, to get around the "no overlap" limitation - will play with that later, through I think it's low priority considering the "different level of detail" issue.


I wouldn't want to merge it if it greatly increased complexity

I went ahead and added a const L: u8=0 generic to everything, to see how that would look like. The plugins, the grid cells, the reference frames, the floating origin, the ignore marker, everything.

Because of the default value of the generic, most of the examples/tests/demo code does not change. Still, there are some structs that were not generic, and now are; and Rust doesn't want to infer the default value of L if there's no P there too (or I don't know how to do it).

Here are the only structs that require adding explicit ::<0> to keep current functionality:

I think we could add the P generic parameter to the floating point origin and ignore marker. This would also guard better against accidentally using different precisions - through this would be a breaking change. The alternative is to rename FloatingOrigin into FloatingOriginEx and then have a typedef FloatingOrigin = FloatingOriginEx::<0> - this would mean no breaking change, but more confusion?

Here is the draft PR to see what it'd look like. #18

What do you think?

aevyrie commented 6 months ago

Another idea - you could have aWorld for each player, but have all worlds extracting to the same render world? Instead of adding complexity to the floating origin plugin, you could let each world be independent and handle the world -> camera connection as part of a custom render plugin. This would be easier in the big picture, potentially, because you don't have to worry about each view of the world stomping on each other. This would likely also be easier for working with other 3rd party plugins, as they wouldn't need to know anything about this multi-world setup.

As a plugin author, this is what I would prefer. It's impossible for me to have a plugin work for everyone's use case if I need to start adding those implementation details inside the plugin.

I'm also not super keen on adding more generics, they tend to make usage and errors pretty confusing, especially if they end up in the public API.

In fact, now that I think about it, you could actually have independent layers today without adding another generic, if you use a new generic type for the grid precision. You could newtype the integer you want to use and implement the trait. e.g. FloatingOriginPlugin<MyI64<1>>, FloatingOriginPlugin<MyI64<2>>

johnny-smitherson commented 6 months ago

you could have a World for each player

I can have more than 1 World?? Yeah that sounds much better

https://github.com/bevyengine/bevy/discussions/2237

https://github.com/bevyengine/bevy/discussions/7893

https://github.com/bevyengine/rfcs/pull/16

https://github.com/bevyengine/rfcs/pull/43

not yet, but maybe soon.


you could actually have independent layers today without adding another generic

Great idea - but I think I'd still need a way to differentiate between the FloatingOrigin instances, and a way to map all the ignore markers.


Writing an example file for the horrowshow above, I can see it's not very friendly to work with, lots of IgnoreFloatingOrigin::<X> to add everywhere.

johnny-smitherson commented 6 months ago

Actually, since you were talking about many-World, I guess I will try and spawn a bunch of threads and run separate "App" instances on each.

If that doesn't work out, I can always execute many instances of the game exe with different parameters. It'll be a bunch of OS windows instead of bevy UI TargetCamera, but it'll do until they add some kind of support for multi-World

Thanks for the conversation, very useful, much appreciated

aevyrie commented 6 months ago

You can definitely have multiple worlds right now - bevy's pipelined renderer runs in another world, and data is extracted from the simulation world into the render world at the sync point. It's as simple as sticking a World inside a resource. The idea is that you would have an independent simulation in each world, run the schedule one frame, then extract the renderable stuff out into the single render world.

In your case, each world would be running the simulation from some entity's point of view, and you would be extracting out the GlobalTransforms into the render world, handling renderlayers and whatnot. The main world would be nothing more than this glue, as well as a place to store all the independent worlds.

johnny-smitherson commented 6 months ago

ok i will try it and submit example file with a demo and a bunch of cameras