use crate::prelude::*;
use bevy_app::prelude::*;
use bevy_camera::{
primitives::Aabb,
visibility::{InheritedVisibility, RenderLayers},
};
use bevy_ecs::entity::EntityHashSet;
use bevy_ecs::prelude::*;
use bevy_input::{mouse::MouseMotion, prelude::*};
use bevy_math::{prelude::*, DQuat, DVec3};
use bevy_platform::prelude::*;
use bevy_reflect::prelude::*;
use bevy_time::prelude::*;
use bevy_transform::{prelude::*, TransformSystems};
pub struct BigSpaceCameraControllerPlugin;
impl Plugin for BigSpaceCameraControllerPlugin {
fn build(&self, app: &mut App) {
app.register_type::<BigSpaceCameraController>()
.register_type::<BigSpaceCameraInput>()
.init_resource::<BigSpaceCameraInput>()
.add_systems(
PostUpdate,
(
default_camera_inputs
.before(camera_controller)
.run_if(|input: Res<BigSpaceCameraInput>| !input.defaults_disabled),
nearest_objects_in_grid.before(camera_controller),
camera_controller.before(TransformSystems::Propagate),
),
);
}
}
#[derive(Clone, Debug, Reflect, Component)]
#[reflect(Component)]
pub struct BigSpaceCameraController {
pub smoothness: f64,
pub rotational_smoothness: f64,
pub speed: f64,
pub speed_yaw: f64,
pub speed_pitch: f64,
pub speed_roll: f64,
pub speed_bounds: [f64; 2],
pub slow_near_objects: bool,
nearest_object: Option<(Entity, f64)>,
vel_translation: DVec3,
vel_rotation: DQuat,
}
impl BigSpaceCameraController {
pub fn with_smoothness(mut self, translation: f64, rotation: f64) -> Self {
self.smoothness = translation;
self.rotational_smoothness = rotation;
self
}
pub fn with_slowing(mut self, slow_near_objects: bool) -> Self {
self.slow_near_objects = slow_near_objects;
self
}
pub fn with_speed(mut self, speed: f64) -> Self {
self.speed = speed;
self
}
pub fn with_speed_yaw(mut self, speed: f64) -> Self {
self.speed_yaw = speed;
self
}
pub fn with_speed_pitch(mut self, speed: f64) -> Self {
self.speed_pitch = speed;
self
}
pub fn with_speed_roll(mut self, speed: f64) -> Self {
self.speed_roll = speed;
self
}
pub fn with_speed_bounds(mut self, speed_limits: [f64; 2]) -> Self {
self.speed_bounds = speed_limits;
self
}
pub fn velocity(&self) -> (DVec3, DQuat) {
(self.vel_translation, self.vel_rotation)
}
pub fn nearest_object(&self) -> Option<(Entity, f64)> {
self.nearest_object
}
}
impl Default for BigSpaceCameraController {
fn default() -> Self {
Self {
smoothness: 0.85,
rotational_smoothness: 0.8,
speed: 1.0,
speed_pitch: 2.0,
speed_yaw: 2.0,
speed_roll: 1.0,
speed_bounds: [1e-17, 1e30],
slow_near_objects: true,
nearest_object: None,
vel_translation: DVec3::ZERO,
vel_rotation: DQuat::IDENTITY,
}
}
}
#[derive(Clone, Debug, Default, Reflect, Resource)]
#[reflect(Resource)]
pub struct BigSpaceCameraInput {
pub defaults_disabled: bool,
pub forward: f64,
pub up: f64,
pub right: f64,
pub roll: f64,
pub pitch: f64,
pub yaw: f64,
pub boost: bool,
}
impl BigSpaceCameraInput {
pub fn reset(&mut self) {
*self = BigSpaceCameraInput {
defaults_disabled: self.defaults_disabled,
..Default::default()
};
}
pub fn target_velocity(
&self,
controller: &BigSpaceCameraController,
speed: f64,
dt: f64,
) -> (DVec3, DQuat) {
let rotation = DQuat::from_euler(
EulerRot::XYZ,
self.pitch * dt * controller.speed_pitch,
self.yaw * dt * controller.speed_yaw,
self.roll * dt * controller.speed_roll,
);
let translation = DVec3::new(self.right, self.up, self.forward) * speed * dt;
(translation, rotation)
}
}
pub fn default_camera_inputs(
keyboard: Res<ButtonInput<KeyCode>>,
mut mouse_move: MessageReader<MouseMotion>,
mut cam: ResMut<BigSpaceCameraInput>,
) {
keyboard.pressed(KeyCode::KeyW).then(|| cam.forward -= 1.0);
keyboard.pressed(KeyCode::KeyS).then(|| cam.forward += 1.0);
keyboard.pressed(KeyCode::KeyA).then(|| cam.right -= 1.0);
keyboard.pressed(KeyCode::KeyD).then(|| cam.right += 1.0);
keyboard.pressed(KeyCode::Space).then(|| cam.up += 1.0);
keyboard
.pressed(KeyCode::ControlLeft)
.then(|| cam.up -= 1.0);
keyboard.pressed(KeyCode::KeyQ).then(|| cam.roll += 2.0);
keyboard.pressed(KeyCode::KeyE).then(|| cam.roll -= 2.0);
keyboard
.pressed(KeyCode::ShiftLeft)
.then(|| cam.boost = true);
if let Some(total_mouse_motion) = mouse_move.read().map(|e| e.delta).reduce(|sum, i| sum + i) {
cam.pitch += total_mouse_motion.y as f64 * -0.1;
cam.yaw += total_mouse_motion.x as f64 * -0.1;
}
}
pub fn nearest_objects_in_grid(
objects: Query<(
Entity,
&Transform,
&GlobalTransform,
&Aabb,
Option<&RenderLayers>,
&InheritedVisibility,
)>,
mut camera: Query<(
Entity,
&mut BigSpaceCameraController,
&GlobalTransform,
Option<&RenderLayers>,
)>,
children: Query<&Children>,
) {
let Ok((cam_entity, mut camera, cam_pos, cam_layer)) = camera.single_mut() else {
return;
};
if !camera.slow_near_objects {
return;
}
let cam_layer = cam_layer.to_owned().unwrap_or_default();
let cam_children: EntityHashSet = children.iter_descendants(cam_entity).collect();
let nearest_object = objects
.iter()
.filter(|(entity, ..)| !cam_children.contains(entity))
.filter(|(.., obj_layer, _)| {
let obj_layer = obj_layer.unwrap_or_default();
cam_layer.intersects(obj_layer)
})
.filter(|(.., visibility)| visibility.get())
.map(|(entity, object_local, obj_pos, aabb, ..)| {
let center_distance =
obj_pos.translation().as_dvec3() - cam_pos.translation().as_dvec3();
let nearest_distance = center_distance.length()
- (aabb.half_extents.as_dvec3() * object_local.scale.as_dvec3())
.abs()
.min_element();
(entity, nearest_distance)
})
.filter(|v| v.1.is_finite())
.reduce(|nearest, this| if this.1 < nearest.1 { this } else { nearest });
camera.nearest_object = nearest_object;
}
pub fn camera_controller(
time: Res<Time>,
grids: Grids,
mut input: ResMut<BigSpaceCameraInput>,
mut camera: Query<(
Entity,
&mut CellCoord,
&mut Transform,
&mut BigSpaceCameraController,
)>,
) {
for (camera, mut cell, mut transform, mut controller) in camera.iter_mut() {
let Some(grid) = grids.parent_grid(camera) else {
continue;
};
let speed = match (controller.nearest_object, controller.slow_near_objects) {
(Some(nearest), true) => nearest.1.abs(),
_ => controller.speed,
} * (controller.speed + input.boost as usize as f64);
let [min, max] = controller.speed_bounds;
let speed = speed.clamp(min, max);
let lerp_translation = 1.0 - controller.smoothness.clamp(0.0, 0.999);
let lerp_rotation = 1.0 - controller.rotational_smoothness.clamp(0.0, 0.999);
let (vel_t_current, vel_r_current) = (controller.vel_translation, controller.vel_rotation);
let (vel_t_target, vel_r_target) =
input.target_velocity(&controller, speed, time.delta_secs_f64());
let cam_rot = transform.rotation.as_dquat();
let vel_t_next = cam_rot * vel_t_target; let vel_t_next = vel_t_current.lerp(vel_t_next, lerp_translation);
let (cell_offset, new_translation) = grid.translation_to_grid(vel_t_next);
let new = *cell.bypass_change_detection() + cell_offset;
cell.set_if_neq(new);
transform.translation += new_translation;
let new_rotation = vel_r_current.slerp(vel_r_target, lerp_rotation);
transform.rotation *= new_rotation.as_quat();
controller.vel_translation = vel_t_next;
controller.vel_rotation = new_rotation;
input.reset();
}
}