johanhelsing / bevy_pancam Goto Github PK
View Code? Open in Web Editor NEWA bevy plugin for panning orthographic cameras
License: Apache License 2.0
A bevy plugin for panning orthographic cameras
License: Apache License 2.0
code is binding to mouse buttons but not touch interactions so it doesn't work on WASM mobile
I'll probably put up a PR for this later today, I'm fixing it right now on my local fork
Bevy had released v0.11, I am not sure whether it will influence the usage of pan_cam or not.
But I think maybe it should be updated to fit the new bevy.
Maybe add:
Perhaps not all of them... I want to keep the scope and complexity of the crate down...
I would like to create a PR to add keyboard movement if that is desired?
The Cargo.toml
specifies license = "MIT OR Apache-2.0"
but there are no LICENSE files. It would be nice to have them so that you can easily view the license at the place where you'd expect them to be.
If we rotate the camera, I noticed the movement doesn't follow the cursor, as if camera wasn't rotated.
It may be possible to fix by transforming the screenpos to worldpos and infer the camera movement from that
when moving the mouse from outside the window into it while at the same time scrolling, bevy_pancam will crash with:
thread 'Compute Task Pool (1)' panicked at 'called `Option::unwrap()` on a `None` value', C:\Users\laundmo\.cargo\registry\src\github.com-1ecc6299db9ec823\bevy_pancam-0.5.0\src\lib.rs:38:10
This is because a scroll event has been picked up before the mouse is inside the window, causing the unwrap to panik on the mouse position.
a fix could be a simple if let Some(cursor) = window.cursor_position()
instead of unwrap.
Consider an example similar to the one shown in examples/clamped_borders.rs
but modified to account for the concern highlighted in #42
So, we have code like this:
commands.spawn(SpriteBundle {
transform: Transform::from_xyz(0.0, 0.0, 0.0),
sprite: Sprite {
color: Color::BLUE,
custom_size: Some(Vec2::splat(spacing * n as f32)),
..default()
},
..default()
});
which renders a 1000x1000 square centered at 0,0 center anchor.
Now, consider initializing bevy_pancam
with the following:
commands.spawn((
Camera2dBundle::default(),
PanCam {
min_x: Some(-(spacing * n) as f32 / 2.0),
max_x: Some((spacing * n) as f32 / 2.0),
min_y: Some(-(spacing * n) as f32 / 2.0),
max_y: Some((spacing * n) as f32 / 2.0),
..default()
},
));
These settings attempt to ensure that the blue square always covers the entire viewport. If the viewport is square then the two will scale to fit perfectly. Otherwise, the blue square will overflow in one dimension and be bounded by the viewport in the other. If I apply only one of these four properties at a time then I am able to see the square snap to the left/right/top/bottom side.
On load, the application looks like this:
if I click the screen it then updates and looks like this:
if, instead of clicking, I scroll my mousewheel forward once as if I were zooming in, then the UI looks like this:
if, instead of clicking, I scroll my mousewheel forward twice as if I were zooming in repeatedly, then the UI looks like this:
This last perspective is correct / desired. It shows projection.scale
adjusted such that the blue square fully covers the viewport. I am able to zoom inward and pan around, but I am not able to zoom out past the bounds of the blue square. I am able to pan up/down the page because the scale is such that the height of blue square exceeds that of the viewport.
1. As a user, I do not expect variations in projection.scale
or clamping behavior when I click, scroll once, or scroll multiple times. If I take an action, and the current scale and clamped bounds are invalid, then the action's response should be to apply the clamped bounds in such a way that all three actions result in consistent UIs.
2. As a user, I do not expect projection.scale
to be invalid on first load when applying clamped boundaries. I want the initial projection.scale
to respect the provided clamp boundaries.
I think if these two issues are address that, as a side-effect, the concern I highlight in #38 will be addressed.
Smooth zoom and pan real
you know how on mobile if you scroll, it doesnt immediately stop scrolling when you release, but instead slowly stops, almost like it has velocity? that
As requested in #4, it would be nice if it was possible to limit the area the camera is able to view.
The point of this is to limit so you can't see something outside of the map for instance.
This needs to be handled both when zooming out and when panning.
Also, like max_scale
, it should be an opt-in feature.
I noticed that the drag is captured even when it is done on egui windows
Adding
if egui_ctx.ctx_mut().wants_pointer_input() || egui_ctx.ctx_mut().wants_keyboard_input() {
return;
}
in systems should fix this.
It can be added behind a feature flag. What do you think about this? I can contribute this change.
I am using bevy_pancam 0.11 with bevy 0.13 on a project that I have recently been updating from bevy 0.12 and bevy_pancam 0.10.
After the update, I start to randomly see this error upon exiting, it seems to occur about half of the time when I exit the app:
thread 'Compute Task Pool (17)' panicked at /home/m/.cargo/registry/src/index.crates.io-6f17d22bba15001f/bevy_pancam-0.11.0/src/lib.rs:187:33:
called `Result::unwrap()` on an `Err` value: NoEntities("bevy_ecs::query::state::QueryState<&bevy_window::window::Window, bevy_ecs::query::filter::With<bevy_window::window::PrimaryWindow>>")
stack backtrace:
0: rust_begin_unwind
at /rustc/3c85e56249b0b1942339a6a989a971bf6f1c9e0f/library/std/src/panicking.rs:645:5
1: core::panicking::panic_fmt
at /rustc/3c85e56249b0b1942339a6a989a971bf6f1c9e0f/library/core/src/panicking.rs:72:14
2: core::result::unwrap_failed
at /rustc/3c85e56249b0b1942339a6a989a971bf6f1c9e0f/library/core/src/result.rs:1654:5
3: core::result::Result<T,E>::unwrap
at /rustc/3c85e56249b0b1942339a6a989a971bf6f1c9e0f/library/core/src/result.rs:1077:23
4: bevy_ecs::system::query::Query<D,F>::single
at /home/m/.cargo/registry/src/index.crates.io-6f17d22bba15001f/bevy_ecs-0.13.1/src/system/query.rs:1307:9
5: bevy_pancam::camera_movement
at /home/m/.cargo/registry/src/index.crates.io-6f17d22bba15001f/bevy_pancam-0.11.0/src/lib.rs:187:18
6: core::ops::function::FnMut::call_mut
at /rustc/3c85e56249b0b1942339a6a989a971bf6f1c9e0f/library/core/src/ops/function.rs:166:5
7: core::ops::function::impls::<impl core::ops::function::FnMut<A> for &mut F>::call_mut
at /rustc/3c85e56249b0b1942339a6a989a971bf6f1c9e0f/library/core/src/ops/function.rs:294:13
8: <Func as bevy_ecs::system::function_system::SystemParamFunction<fn(F0,F1,F2,F3) .> Out>>::run::call_inner
at /home/m/.cargo/registry/src/index.crates.io-6f17d22bba15001f/bevy_ecs-0.13.1/src/system/function_system.rs:656:21
9: <Func as bevy_ecs::system::function_system::SystemParamFunction<fn(F0,F1,F2,F3) .> Out>>::run
at /home/m/.cargo/registry/src/index.crates.io-6f17d22bba15001f/bevy_ecs-0.13.1/src/system/function_system.rs:659:17
10: <bevy_ecs::system::function_system::FunctionSystem<Marker,F> as bevy_ecs::system::system::System>::run_unsafe
at /home/m/.cargo/registry/src/index.crates.io-6f17d22bba15001f/bevy_ecs-0.13.1/src/system/function_system.rs:499:19
11: bevy_ecs::schedule::executor::multi_threaded::MultiThreadedExecutor::spawn_system_task::{{closure}}::{{closure}}
at /home/m/.cargo/registry/src/index.crates.io-6f17d22bba15001f/bevy_ecs-0.13.1/src/schedule/executor/multi_threaded.rs:534:26
12: core::ops::function::FnOnce::call_once
at /rustc/3c85e56249b0b1942339a6a989a971bf6f1c9e0f/library/core/src/ops/function.rs:250:5
13: <core::panic::unwind_safe::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once
at /rustc/3c85e56249b0b1942339a6a989a971bf6f1c9e0f/library/core/src/panic/unwind_safe.rs:272:9
14: std::panicking::try::do_call
at /rustc/3c85e56249b0b1942339a6a989a971bf6f1c9e0f/library/std/src/panicking.rs:552:40
15: std::panicking::try
at /rustc/3c85e56249b0b1942339a6a989a971bf6f1c9e0f/library/std/src/panicking.rs:516:19
16: std::panic::catch_unwind
at /rustc/3c85e56249b0b1942339a6a989a971bf6f1c9e0f/library/std/src/panic.rs:146:14
17: bevy_ecs::schedule::executor::multi_threaded::MultiThreadedExecutor::spawn_system_task::{{closure}}
at /home/m/.cargo/registry/src/index.crates.io-6f17d22bba15001f/bevy_ecs-0.13.1/src/schedule/executor/multi_threaded.rs:529:23
18: <core::panic::unwind_safe::AssertUnwindSafe<F> as core::future::future::Future>::poll
at /rustc/3c85e56249b0b1942339a6a989a971bf6f1c9e0f/library/core/src/panic/unwind_safe.rs:297:9
19: <futures_lite::future::CatchUnwind<F> as core::future::future::Future>::poll::{{closure}}
at /home/m/.cargo/registry/src/index.crates.io-6f17d22bba15001f/futures-lite-2.3.0/src/future.rs:588:42
20: <core::panic::unwind_safe::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once
at /rustc/3c85e56249b0b1942339a6a989a971bf6f1c9e0f/library/core/src/panic/unwind_safe.rs:272:9
21: std::panicking::try::do_call
at /rustc/3c85e56249b0b1942339a6a989a971bf6f1c9e0f/library/std/src/panicking.rs:552:40
22: std::panicking::try
at /rustc/3c85e56249b0b1942339a6a989a971bf6f1c9e0f/library/std/src/panicking.rs:516:19
23: std::panic::catch_unwind
at /rustc/3c85e56249b0b1942339a6a989a971bf6f1c9e0f/library/std/src/panic.rs:146:14
24: <futures_lite::future::CatchUnwind<F> as core::future::future::Future>::poll
at /home/m/.cargo/registry/src/index.crates.io-6f17d22bba15001f/futures-lite-2.3.0/src/future.rs:588:9
25: async_executor::Executor::spawn::{{closure}}
at /home/m/.cargo/registry/src/index.crates.io-6f17d22bba15001f/async-executor-1.8.0/src/lib.rs:158:20
26: async_task::raw::RawTask<F,T,S,M>::run::{{closure}}
at /home/m/.cargo/registry/src/index.crates.io-6f17d22bba15001f/async-task-4.7.0/src/raw.rs:550:21
27: core::ops::function::FnOnce::call_once
at /rustc/3c85e56249b0b1942339a6a989a971bf6f1c9e0f/library/core/src/ops/function.rs:250:5
28: <core::panic::unwind_safe::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once
at /rustc/3c85e56249b0b1942339a6a989a971bf6f1c9e0f/library/core/src/panic/unwind_safe.rs:272:9
29: std::panicking::try::do_call
at /rustc/3c85e56249b0b1942339a6a989a971bf6f1c9e0f/library/std/src/panicking.rs:552:40
30: std::panicking::try
at /rustc/3c85e56249b0b1942339a6a989a971bf6f1c9e0f/library/std/src/panicking.rs:516:19
31: std::panic::catch_unwind
at /rustc/3c85e56249b0b1942339a6a989a971bf6f1c9e0f/library/std/src/panic.rs:146:14
32: async_task::raw::RawTask<F,T,S,M>::run
at /home/m/.cargo/registry/src/index.crates.io-6f17d22bba15001f/async-task-4.7.0/src/raw.rs:549:23
33: async_task::runnable::Runnable<M>::run
at /home/m/.cargo/registry/src/index.crates.io-6f17d22bba15001f/async-task-4.7.0/src/runnable.rs:781:18
34: async_executor::Executor::run::{{closure}}::{{closure}}
at /home/m/.cargo/registry/src/index.crates.io-6f17d22bba15001f/async-executor-1.8.0/src/lib.rs:254:21
35: <futures_lite::future::Or<F1,F2> as core::future::future::Future>::poll
at /home/m/.cargo/registry/src/index.crates.io-6f17d22bba15001f/futures-lite-2.3.0/src/future.rs:449:33
36: async_executor::Executor::run::{{closure}}
at /home/m/.cargo/registry/src/index.crates.io-6f17d22bba15001f/async-executor-1.8.0/src/lib.rs:261:32
37: futures_lite::future::block_on::{{closure}}
at /home/m/.cargo/registry/src/index.crates.io-6f17d22bba15001f/futures-lite-2.3.0/src/future.rs:99:19
38: std::thread::local::LocalKey<T>::try_with
at /rustc/3c85e56249b0b1942339a6a989a971bf6f1c9e0f/library/std/src/thread/local.rs:284:16
39: std::thread::local::LocalKey<T>::with
at /rustc/3c85e56249b0b1942339a6a989a971bf6f1c9e0f/library/std/src/thread/local.rs:260:9
40: futures_lite::future::block_on
at /home/m/.cargo/registry/src/index.crates.io-6f17d22bba15001f/futures-lite-2.3.0/src/future.rs:78:11
41: bevy_tasks::task_pool::TaskPool::new_internal::{{closure}}::{{closure}}::{{closure}}::{{closure}}
at /home/m/.cargo/registry/src/index.crates.io-6f17d22bba15001f/bevy_tasks-0.13.1/src/task_pool.rs:180:37
42: std::panicking::try::do_call
at /rustc/3c85e56249b0b1942339a6a989a971bf6f1c9e0f/library/std/src/panicking.rs:552:40
43: std::panicking::try
at /rustc/3c85e56249b0b1942339a6a989a971bf6f1c9e0f/library/std/src/panicking.rs:516:19
44: std::panic::catch_unwind
at /rustc/3c85e56249b0b1942339a6a989a971bf6f1c9e0f/library/std/src/panic.rs:146:14
45: bevy_tasks::task_pool::TaskPool::new_internal::{{closure}}::{{closure}}::{{closure}}
at /home/m/.cargo/registry/src/index.crates.io-6f17d22bba15001f/bevy_tasks-0.13.1/src/task_pool.rs:174:43
46: std::thread::local::LocalKey<T>::try_with
at /rustc/3c85e56249b0b1942339a6a989a971bf6f1c9e0f/library/std/src/thread/local.rs:284:16
47: std::thread::local::LocalKey<T>::with
at /rustc/3c85e56249b0b1942339a6a989a971bf6f1c9e0f/library/std/src/thread/local.rs:260:9
48: bevy_tasks::task_pool::TaskPool::new_internal::{{closure}}::{{closure}}
at /home/m/.cargo/registry/src/index.crates.io-6f17d22bba15001f/bevy_tasks-0.13.1/src/task_pool.rs:167:50
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
This is how I am exiting my bevy app:
fn menu_keys(
kbd: Res<ButtonInput<KeyCode>>,
mut app_exit_events: EventWriter<AppExit>,
) {
if kbd.just_pressed(KeyCode::Escape) {
app_exit_events.send(AppExit);
}
}
I did not notice this error triggering with bevy 0.12 / bevy_pancam 0.10
We currently just zoom and pan using the primary window. Perhaps we should try to figure out which window received input events and somehow map input to the right camera?
Hello! :D
I am working on creating a follow-up response to you regarding the PR I proposed a few months ago.
In attempting to recreate my issue I realized that I was experiencing confusion due to an unexpected example.
This is the code that is currently used to generate a visually pleasing square of squares:
let n = 20;
let spacing = 50.;
let offset = spacing * n as f32 / 2.;
let custom_size = Some(Vec2::new(spacing, spacing));
for x in 0..n {
for y in 0..n {
let x = x as f32 * spacing - offset;
let y = y as f32 * spacing - offset;
let color = Color::hsl(240., random::<f32>() * 0.3, random::<f32>() * 0.3);
commands.spawn(SpriteBundle {
sprite: Sprite {
color,
custom_size,
..default()
},
transform: Transform::from_xyz(x, y, 0.),
..default()
});
}
}
This square appears approximately in the center of the screen, but it's not true center. This is because we're spawning multiple sprites, rather than one large sprite, and each sprite is being added with the default center anchoring.
In contrast, consider this code:
commands.spawn(SpriteBundle {
transform: Transform::from_xyz(0.0, 0.0, 0.0),
sprite: Sprite {
color: Color::BLUE,
custom_size: Some(Vec2::splat(spacing * n as f32)),
..default()
},
..default()
});
Here we generate a square which is the same dimensions and spawn it at 0,0 with a center anchor.
Visually, here are the outputs:
and we see that these two squares, while taking similar dimensions, have varying alignment. I find this to be extremely confusing given the context of attempting to verify the efficacy of boundary clamping.
I would expect that if I set projection.scale = 1.0 / (viewport.width / (spacing * n as f32))
that the square would be centered, but it is slightly off center, which gives the impression that potentially something is going awry with bounding logic.
A new version is out that is compatible with latest bevy-egui
Now that bevy_egui
integration works, it would probably also be a good idea to play nice with bevy-inpsector-egui
.
This could also be implemented behind a feature flag.
Could take the same approach as done here: johanhelsing/bevy_smud#6
When trying to compile the egui example there is an issue with the EguiContext
which is retrieved as a resource but is now a normal component.
Error:
the trait bound
EguiContext: Resource is not satisfied
This would be great for any scientific graphs/plots etc, where you may often zoom in only on x or only on y.
A typical way would be by allowing modifiers โ e.g. wheel zooms by x, shift-wheel zooms by y, cmd-wheel zooms by both.
Not sure if it's feasible or not but figured I'd chime in anyways.
(Related: #45)
Whenever I click on the egui window, there is a translation of the camera. This did not happen with bevy 0.9.1.
git clone https://github.com/johanhelsing/bevy_pancam.git
cd bevy_pancam
cargo run --example egui --features bevy_egui
(running on iGPU)
2023-05-02T20:36:48.842895Z INFO bevy_winit::system: Creating new window "Bevy App" (0v0)
2023-05-02T20:36:48.843434Z INFO winit::platform_impl::platform::x11::window: Guessed window scale factor: 1.6666666666666667
2023-05-02T20:36:48.892126Z INFO bevy_render::renderer: AdapterInfo { name: "Intel(R) UHD Graphics (CML GT2)", device_type: IntegratedGpu, driver: "Intel open-source Mesa driver", driver_info: "Mesa 23.0.2", backend: Vulkan }
2023-05-02T20:36:48.989418Z INFO bevy_diagnostic::system_information_diagnostics_plugin::internal: SystemInfo { os: "Linux rolling ArcoLinux", kernel: "6.2.12-arch1-1", cpu: "Intel(R) Core(TM) i5-10210U CPU @ 1.60GHz", core_count: "4", memory: "15.4 GiB" }
When runing with:
.insert_resource(bevy::ecs::schedule::ReportExecutionOrderAmbiguities)
The plugin triggers warnings like:
2022-12-11T11:59:00.137995Z INFO bevy_ecs::schedule::ambiguity_detection: Execution order ambiguities detected, you might want to add an explicit dependency relation between some of these systems:
* Parallel systems:
-- "bevy_pancam::camera_movement" and "bevy_pancam::camera_zoom"
conflicts: ["bevy_egui::EguiContext", "bevy_render::camera::projection::OrthographicProjection", "bevy_transform::components::transform::Transform"]
We should look into whether this matters, and either add explicit ordering or add .ambiguous_with
calls to silence the warning.
So that scrolling increases to 1, 2, 3, 4, 5 instead of in-between.
bevy_egui has bumped breaking semver, so for the feature to still work with the latest version we need to bump the dependency
It would be useful to be able to toggle whether or not received inputs should be accepted so that you can disable the camera.
This could be e.g. a enabled
bool on the PanCam
component (https://docs.rs/bevy_fly_camera/latest/bevy_fly_camera/struct.FlyCamera.html#structfield.enabled).
If you add a different scale to x
and y
in the camera transform, it seems to completely mess things up. Wonder if that might be an easy fix?
there should be an option to allow zooming out past the bounds, in the same vein as pixi-viewport's "underflow" option.
tl;dr
max_scale
if presentThe current code assumes the camera is axis aligned with y up and x to the right.
We could perhaps try to support modes where the camera has been rotated around the z axis.
This would probably make the limits API (max_x
etc.) a bit awkward to use (what does it even mean if the camera is rotated 45 degrees?)
So if we implement this, it's probably best to just turn off panning limits.
Maybe it's better to just define it as out of scope. It really boils down to how much it would complicate the code and API usage
Hi! Thanks for this useful plugin.
Is there a way to limit how much the camera can zoom in or out? Or how far it can pan away?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.