From dd7223c0992bdaa5683fb4dcd1b7f18bc3961738 Mon Sep 17 00:00:00 2001 From: Oliver Dew Date: Fri, 7 Jun 2024 23:25:51 +0100 Subject: [PATCH] Remove dependencies that depend on Bevy --- Cargo.toml | 3 +- examples/emissive-model.rs | 2 +- examples/modify-scene.rs | 2 +- examples/modify-voxels.rs | 2 +- examples/scene-slice.rs | 2 +- examples/ssao-model.rs | 2 +- examples/transmission-scene.rs | 2 +- examples/voxel-collisions.rs | 2 +- examples/voxel-generation.rs | 2 +- utilities/Cargo.toml | 9 +++ utilities/src/lib.rs | 132 +++++++++++++++++++++++++++++++++ 11 files changed, 151 insertions(+), 9 deletions(-) create mode 100644 utilities/Cargo.toml create mode 100644 utilities/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 19bc016..88da737 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,4 @@ +workspace = { members = ["utilities"] } [package] name = "bevy_vox_scene" description = "A Bevy engine plugin for loading Magica Voxel world files and render materials directly in Bevy as a scene graph." @@ -47,6 +48,6 @@ serde = "1.0.193" [dev-dependencies] bevy = "0.13.0" -bevy_panorbit_camera = "0.14.0" +utilities = { path = "utilities" } rand = "0.8.5" async-std = { version = "1.12.0", features = ["attributes"] } diff --git a/examples/emissive-model.rs b/examples/emissive-model.rs index 2e03601..e72affb 100644 --- a/examples/emissive-model.rs +++ b/examples/emissive-model.rs @@ -1,5 +1,5 @@ use bevy::{core_pipeline::bloom::BloomSettings, prelude::*}; -use bevy_panorbit_camera::{PanOrbitCamera, PanOrbitCameraPlugin}; +use utilities::{PanOrbitCamera, PanOrbitCameraPlugin}; use bevy_vox_scene::{VoxScenePlugin, VoxelSceneBundle}; fn main() { diff --git a/examples/modify-scene.rs b/examples/modify-scene.rs index 8d523b2..28e574d 100644 --- a/examples/modify-scene.rs +++ b/examples/modify-scene.rs @@ -8,7 +8,7 @@ use bevy::{ input::keyboard::KeyboardInput, prelude::*, }; -use bevy_panorbit_camera::{PanOrbitCamera, PanOrbitCameraPlugin}; +use utilities::{PanOrbitCamera, PanOrbitCameraPlugin}; use bevy_vox_scene::{VoxScenePlugin, VoxelSceneHook, VoxelSceneHookBundle}; use rand::Rng; use std::f32::consts::PI; diff --git a/examples/modify-voxels.rs b/examples/modify-voxels.rs index 6b72366..aab1ccb 100644 --- a/examples/modify-voxels.rs +++ b/examples/modify-voxels.rs @@ -3,7 +3,7 @@ use bevy::{ prelude::*, time::common_conditions::on_timer, }; -use bevy_panorbit_camera::{PanOrbitCamera, PanOrbitCameraPlugin}; +use utilities::{PanOrbitCamera, PanOrbitCameraPlugin}; use bevy_vox_scene::{ ModifyVoxelCommandsExt, VoxScenePlugin, Voxel, VoxelModelInstance, VoxelRegion, VoxelRegionMode, VoxelSceneHook, VoxelSceneHookBundle, diff --git a/examples/scene-slice.rs b/examples/scene-slice.rs index bba8d5c..851c99b 100644 --- a/examples/scene-slice.rs +++ b/examples/scene-slice.rs @@ -7,7 +7,7 @@ use bevy::{ }, prelude::*, }; -use bevy_panorbit_camera::{PanOrbitCamera, PanOrbitCameraPlugin}; +use utilities::{PanOrbitCamera, PanOrbitCameraPlugin}; use bevy_vox_scene::{VoxScenePlugin, VoxelSceneBundle}; /// Asset labels aren't just for loading individual models within a scene, they can load any named group within a scene, a "slice" of the scene diff --git a/examples/ssao-model.rs b/examples/ssao-model.rs index 6315509..294ab2d 100644 --- a/examples/ssao-model.rs +++ b/examples/ssao-model.rs @@ -7,7 +7,7 @@ use bevy::{ pbr::ScreenSpaceAmbientOcclusionBundle, prelude::*, }; -use bevy_panorbit_camera::{PanOrbitCamera, PanOrbitCameraPlugin}; +use utilities::{PanOrbitCamera, PanOrbitCameraPlugin}; use bevy_vox_scene::{VoxScenePlugin, VoxelSceneBundle}; /// Press any key to toggle Screen Space Ambient Occlusion diff --git a/examples/transmission-scene.rs b/examples/transmission-scene.rs index 9ee3ad8..ad0036d 100644 --- a/examples/transmission-scene.rs +++ b/examples/transmission-scene.rs @@ -7,7 +7,7 @@ use bevy::{ }, prelude::*, }; -use bevy_panorbit_camera::{PanOrbitCamera, PanOrbitCameraPlugin}; +use utilities::{PanOrbitCamera, PanOrbitCameraPlugin}; use bevy_vox_scene::{VoxScenePlugin, VoxelSceneBundle}; fn main() { diff --git a/examples/voxel-collisions.rs b/examples/voxel-collisions.rs index 974491e..e0b8bff 100644 --- a/examples/voxel-collisions.rs +++ b/examples/voxel-collisions.rs @@ -5,7 +5,7 @@ use bevy::{ prelude::*, time::common_conditions::on_timer, }; -use bevy_panorbit_camera::{PanOrbitCamera, PanOrbitCameraPlugin}; +use utilities::{PanOrbitCamera, PanOrbitCameraPlugin}; use bevy_vox_scene::{ ModifyVoxelCommandsExt, VoxScenePlugin, Voxel, VoxelModelCollection, VoxelModelInstance, VoxelQueryable, VoxelRegion, VoxelRegionMode, VoxelScene, VoxelSceneBundle, VoxelSceneHook, diff --git a/examples/voxel-generation.rs b/examples/voxel-generation.rs index f59e70b..1981f26 100644 --- a/examples/voxel-generation.rs +++ b/examples/voxel-generation.rs @@ -1,5 +1,5 @@ use bevy::{core_pipeline::bloom::BloomSettings, prelude::*}; -use bevy_panorbit_camera::{PanOrbitCamera, PanOrbitCameraPlugin}; +use utilities::{PanOrbitCamera, PanOrbitCameraPlugin}; use bevy_vox_scene::{ VoxScenePlugin, Voxel, VoxelModelCollection, VoxelModelInstance, VoxelPalette, SDF, }; diff --git a/utilities/Cargo.toml b/utilities/Cargo.toml new file mode 100644 index 0000000..7f6823c --- /dev/null +++ b/utilities/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "utilities" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bevy = "0.13.0" \ No newline at end of file diff --git a/utilities/src/lib.rs b/utilities/src/lib.rs new file mode 100644 index 0000000..1c4630c --- /dev/null +++ b/utilities/src/lib.rs @@ -0,0 +1,132 @@ +use bevy::{input::mouse::{MouseMotion, MouseWheel}, prelude::*, window::PrimaryWindow}; + +/// Tags an entity as capable of panning and orbiting. +#[derive(Component)] +pub struct PanOrbitCamera { + /// The "focus point" to orbit around. It is automatically updated when panning the camera + pub focus: Vec3, + pub radius: f32, + pub upside_down: bool, +} + +impl Default for PanOrbitCamera { + fn default() -> Self { + PanOrbitCamera { + focus: Vec3::ZERO, + radius: 5.0, + upside_down: false, + } + } +} + +pub struct PanOrbitCameraPlugin; + +impl Plugin for PanOrbitCameraPlugin { + fn build(&self, app: &mut App) { + app + .add_systems(Update, (on_spawn_camera, pan_orbit_camera)); + } +} + +fn on_spawn_camera( + mut query: Query<(&Transform, &mut PanOrbitCamera), Added> +) { + for (transform, mut pan_orbit_camera) in query.iter_mut() { + pan_orbit_camera.radius = transform.translation.length(); + } +} + +/// Pan the camera with middle mouse click, zoom with scroll wheel, orbit with right mouse click. +fn pan_orbit_camera( + window_query: Query<&Window, With>, + mut ev_motion: EventReader, + mut ev_scroll: EventReader, + input_mouse: Res>, + mut query: Query<(&mut PanOrbitCamera, &mut Transform, &Projection)>, +) { + // change input mapping for orbit and panning here + let orbit_button = MouseButton::Left; + let pan_button = MouseButton::Right; + + let mut pan = Vec2::ZERO; + let mut rotation_move = Vec2::ZERO; + let mut scroll = 0.0; + let mut orbit_button_changed = false; + + if input_mouse.pressed(orbit_button) { + for ev in ev_motion.read() { + rotation_move += ev.delta; + } + } else if input_mouse.pressed(pan_button) { + // Pan only if we're not rotating at the moment + for ev in ev_motion.read() { + pan += ev.delta; + } + } + for ev in ev_scroll.read() { + scroll += ev.y * 0.005; + } + if input_mouse.just_released(orbit_button) || input_mouse.just_pressed(orbit_button) { + orbit_button_changed = true; + } + + for (mut pan_orbit, mut transform, projection) in query.iter_mut() { + if orbit_button_changed { + // only check for upside down when orbiting started or ended this frame + // if the camera is "upside" down, panning horizontally would be inverted, so invert the input to make it correct + let up = transform.rotation * Vec3::Y; + pan_orbit.upside_down = up.y <= 0.0; + } + + let mut any = false; + if rotation_move.length_squared() > 0.0 { + any = true; + let window = get_primary_window_size(&window_query); + let delta_x = { + let delta = rotation_move.x / window.x * std::f32::consts::PI * 2.0; + if pan_orbit.upside_down { -delta } else { delta } + }; + let delta_y = rotation_move.y / window.y * std::f32::consts::PI; + let yaw = Quat::from_rotation_y(-delta_x); + let pitch = Quat::from_rotation_x(-delta_y); + transform.rotation = yaw * transform.rotation; // rotate around global y axis + transform.rotation = transform.rotation * pitch; // rotate around local x axis + } else if pan.length_squared() > 0.0 { + any = true; + // make panning distance independent of resolution and FOV, + let window = get_primary_window_size(&window_query); + if let Projection::Perspective(projection) = projection { + pan *= Vec2::new(projection.fov * projection.aspect_ratio, projection.fov) / window; + } + // translate by local axes + let right = transform.rotation * Vec3::X * -pan.x; + let up = transform.rotation * Vec3::Y * pan.y; + // make panning proportional to distance away from focus point + let translation = (right + up) * pan_orbit.radius; + pan_orbit.focus += translation; + } else if scroll.abs() > 0.0 { + any = true; + pan_orbit.radius -= scroll * pan_orbit.radius * 0.2; + // dont allow zoom to reach zero or you get stuck + pan_orbit.radius = f32::max(pan_orbit.radius, 0.05); + } + + if any { + // emulating parent/child to make the yaw/y-axis rotation behave like a turntable + // parent = x and y rotation + // child = z-offset + let rot_matrix = Mat3::from_quat(transform.rotation); + transform.translation = pan_orbit.focus + rot_matrix.mul_vec3(Vec3::new(0.0, 0.0, pan_orbit.radius)); + } + } + + // consume any remaining events, so they don't pile up if we don't need them + // (and also to avoid Bevy warning us about not checking events every frame update) + ev_motion.clear(); +} + +fn get_primary_window_size(window_query: &Query<&Window, With>) -> Vec2 { + let window = window_query.get_single().expect("no window found"); + let window = Vec2::new(window.width() as f32, window.height() as f32); + window +}