mirror of
https://github.com/eliasstepanik/big_space_with_trim.git
synced 2026-01-10 23:48:27 +00:00
demo and camera improvements
This commit is contained in:
parent
fb71b2baa4
commit
95d4483afb
@ -17,6 +17,7 @@ bevy = { version = "0.9", default_features = false, features = [
|
||||
"bevy_winit",
|
||||
"x11",
|
||||
] }
|
||||
bevy_framepace = "0.11"
|
||||
|
||||
[features]
|
||||
default = ["debug"]
|
||||
|
||||
Binary file not shown.
BIN
assets/fonts/FiraMono-Regular.ttf
Normal file
BIN
assets/fonts/FiraMono-Regular.ttf
Normal file
Binary file not shown.
@ -3,17 +3,23 @@ use bevy::{
|
||||
prelude::*,
|
||||
render::primitives::{Aabb, Sphere},
|
||||
};
|
||||
use big_space::{camera::CameraController, FloatingOrigin, GridCell};
|
||||
use big_space::{
|
||||
camera::{CameraController, CameraInput, CameraVelocity},
|
||||
FloatingOrigin, GridCell,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins.build().disable::<TransformPlugin>())
|
||||
.add_plugin(big_space::FloatingOriginPlugin::<i128>::default())
|
||||
.add_plugin(big_space::debug::FloatingOriginDebugPlugin::<i128>::default())
|
||||
.add_plugin(big_space::camera::CameraControllerPlugin)
|
||||
.add_plugin(big_space::camera::CameraControllerPlugin::<i128>::default())
|
||||
.add_plugin(bevy_framepace::FramepacePlugin)
|
||||
.insert_resource(ClearColor(Color::BLACK))
|
||||
.add_startup_system(setup)
|
||||
.add_system(cursor_grab_system)
|
||||
.add_system(ui_text_system)
|
||||
.add_startup_system(ui_setup)
|
||||
.run()
|
||||
}
|
||||
|
||||
@ -32,8 +38,8 @@ fn setup(
|
||||
GridCell::<i128>::default(), // All spatial entities need this component
|
||||
FloatingOrigin, // Important: marks this as the entity to use as the floating origin
|
||||
CameraController {
|
||||
max_speed: 10e12,
|
||||
smoothness: 0.9,
|
||||
max_speed: 10e35,
|
||||
smoothness: 0.8,
|
||||
..default()
|
||||
}, // Built-in camera controller
|
||||
));
|
||||
@ -41,20 +47,21 @@ fn setup(
|
||||
let mesh_handle = meshes.add(
|
||||
shape::Icosphere {
|
||||
radius: 0.5,
|
||||
subdivisions: 16,
|
||||
subdivisions: 32,
|
||||
}
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
let matl_handle = materials.add(StandardMaterial {
|
||||
base_color: Color::YELLOW,
|
||||
unlit: false,
|
||||
base_color: Color::MIDNIGHT_BLUE,
|
||||
perceptual_roughness: 0.8,
|
||||
reflectance: 1.0,
|
||||
..default()
|
||||
});
|
||||
|
||||
let mut translation = Vec3::ZERO;
|
||||
for i in 1..100i128 {
|
||||
let j = i.pow(10) as f32 + 1.0;
|
||||
for i in 1..=100i128 {
|
||||
let j = i.pow(14) as f32;
|
||||
translation.x += j;
|
||||
commands.spawn((
|
||||
PbrBundle {
|
||||
@ -81,21 +88,79 @@ fn setup(
|
||||
},));
|
||||
}
|
||||
|
||||
#[derive(Component, Reflect)]
|
||||
pub struct BigSpaceDebugText;
|
||||
|
||||
fn ui_setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
commands.spawn((
|
||||
TextBundle::from_section(
|
||||
"",
|
||||
TextStyle {
|
||||
font: asset_server.load("fonts/FiraMono-Regular.ttf"),
|
||||
font_size: 18.0,
|
||||
color: Color::WHITE,
|
||||
},
|
||||
)
|
||||
.with_text_alignment(TextAlignment::TOP_LEFT)
|
||||
.with_style(Style {
|
||||
position_type: PositionType::Absolute,
|
||||
position: UiRect {
|
||||
top: Val::Px(10.0),
|
||||
left: Val::Px(10.0),
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
}),
|
||||
BigSpaceDebugText,
|
||||
));
|
||||
}
|
||||
|
||||
fn ui_text_system(
|
||||
mut text: Query<&mut Text, With<BigSpaceDebugText>>,
|
||||
time: Res<Time>,
|
||||
origin: Query<(&GridCell<i128>, &Transform), With<FloatingOrigin>>,
|
||||
velocity: Res<CameraVelocity>,
|
||||
) {
|
||||
let (cell, transform) = origin.single();
|
||||
let translation = transform.translation;
|
||||
|
||||
let grid_text = format!("Origin GridCell: {}x, {}y, {}z", cell.x, cell.y, cell.z);
|
||||
|
||||
let translation_text = format!(
|
||||
"Origin Transform: {:>8.2}x, {:>8.2}y, {:>8.2}z",
|
||||
translation.x, translation.y, translation.z
|
||||
);
|
||||
|
||||
let speed = velocity.translation().length() / time.delta_seconds_f64();
|
||||
let camera_text = if speed > 3.0e8 {
|
||||
format!("Camera Speed: {:.0e} x speed of light", speed / 3.0e8)
|
||||
} else {
|
||||
format!("Camera Speed: {:.2e}", speed)
|
||||
};
|
||||
|
||||
text.single_mut().sections[0].value = format!("{grid_text}\n{translation_text}\n{camera_text}");
|
||||
}
|
||||
|
||||
fn cursor_grab_system(
|
||||
mut windows: ResMut<Windows>,
|
||||
mut cam: ResMut<CameraInput>,
|
||||
btn: Res<Input<MouseButton>>,
|
||||
key: Res<Input<KeyCode>>,
|
||||
) {
|
||||
use bevy::window::CursorGrabMode;
|
||||
let window = windows.get_primary_mut().unwrap();
|
||||
|
||||
use bevy::window::CursorGrabMode;
|
||||
if btn.just_pressed(MouseButton::Left) {
|
||||
window.set_cursor_grab_mode(CursorGrabMode::Locked);
|
||||
window.set_cursor_visibility(false);
|
||||
window.set_mode(WindowMode::BorderlessFullscreen);
|
||||
cam.defaults_disabled = false;
|
||||
}
|
||||
|
||||
if key.just_pressed(KeyCode::Escape) {
|
||||
window.set_cursor_grab_mode(CursorGrabMode::None);
|
||||
window.set_cursor_visibility(true);
|
||||
window.set_mode(WindowMode::Windowed);
|
||||
cam.defaults_disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,7 +81,7 @@ fn rotator_system(time: Res<Time>, mut query: Query<&mut Transform, With<Rotator
|
||||
}
|
||||
|
||||
fn setup_ui(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
let font = asset_server.load("fonts/FiraMono-Medium.ttf");
|
||||
let font = asset_server.load("fonts/FiraMono-Regular.ttf");
|
||||
commands.spawn(TextBundle {
|
||||
style: Style {
|
||||
align_self: AlignSelf::FlexStart,
|
||||
|
||||
171
src/camera.rs
171
src/camera.rs
@ -1,20 +1,43 @@
|
||||
//! Provides a camera controller compatible with the floating origin plugin.
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use bevy::{
|
||||
input::mouse::MouseMotion, prelude::*, render::primitives::Aabb, transform::TransformSystem,
|
||||
utils::HashMap,
|
||||
ecs::schedule::ShouldRun,
|
||||
input::mouse::MouseMotion,
|
||||
math::{DQuat, DVec3},
|
||||
prelude::*,
|
||||
render::primitives::Aabb,
|
||||
transform::TransformSystem,
|
||||
};
|
||||
|
||||
use crate::{precision::GridPrecision, FloatingOriginSettings, GridCell};
|
||||
|
||||
/// Adds the `big_space` camera controller
|
||||
pub struct CameraControllerPlugin;
|
||||
impl Plugin for CameraControllerPlugin {
|
||||
#[derive(Default)]
|
||||
pub struct CameraControllerPlugin<P: GridPrecision>(PhantomData<P>);
|
||||
impl<P: GridPrecision> Plugin for CameraControllerPlugin<P> {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.init_resource::<CameraInput>().add_system_set_to_stage(
|
||||
CoreStage::PostUpdate,
|
||||
SystemSet::new()
|
||||
.with_system(default_camera_inputs.before(camera_controller))
|
||||
.with_system(camera_controller.before(TransformSystem::TransformPropagate)),
|
||||
);
|
||||
app.init_resource::<CameraInput>()
|
||||
.init_resource::<CameraVelocity>()
|
||||
.add_system_set_to_stage(
|
||||
CoreStage::PostUpdate,
|
||||
SystemSet::new()
|
||||
.with_system(
|
||||
default_camera_inputs
|
||||
.before(camera_controller::<P>)
|
||||
.with_run_criteria(|input: Res<CameraInput>| {
|
||||
if input.defaults_disabled {
|
||||
ShouldRun::No
|
||||
} else {
|
||||
ShouldRun::Yes
|
||||
}
|
||||
}),
|
||||
)
|
||||
.with_system(
|
||||
camera_controller::<P>.before(TransformSystem::TransformPropagate),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,9 +45,9 @@ impl Plugin for CameraControllerPlugin {
|
||||
#[derive(Clone, Debug, Reflect, Component)]
|
||||
pub struct CameraController {
|
||||
/// Smoothness of motion, from `0.0` to `1.0`.
|
||||
pub smoothness: f32,
|
||||
pub smoothness: f64,
|
||||
/// Maximum possible speed.
|
||||
pub max_speed: f32,
|
||||
pub max_speed: f64,
|
||||
/// Whether the camera should slow down when approaching an entity's [`Aabb`].
|
||||
pub slow_near_objects: bool,
|
||||
}
|
||||
@ -42,39 +65,46 @@ impl Default for CameraController {
|
||||
/// camera. Allows you to map any input to camera motions. Uses aircraft principle axes conventions.
|
||||
#[derive(Clone, Debug, Default, Reflect, Resource)]
|
||||
pub struct CameraInput {
|
||||
/// When disabled, the camera input system is not run.
|
||||
pub defaults_disabled: bool,
|
||||
/// Z-negative
|
||||
pub forward: f32,
|
||||
pub forward: f64,
|
||||
/// Y-positive
|
||||
pub up: f32,
|
||||
pub up: f64,
|
||||
/// X-positive
|
||||
pub right: f32,
|
||||
pub right: f64,
|
||||
/// Positive = right wing down
|
||||
pub roll: f32,
|
||||
pub roll: f64,
|
||||
/// Positive = nose up
|
||||
pub pitch: f32,
|
||||
pub pitch: f64,
|
||||
/// Positive = nose right
|
||||
pub yaw: f32,
|
||||
pub yaw: f64,
|
||||
/// Modifier to increase speed, e.g. "sprint"
|
||||
pub boost: bool,
|
||||
}
|
||||
|
||||
impl CameraInput {
|
||||
/// Reset the controller back to zero to ready fro the next frame.
|
||||
pub fn reset(&mut self) {
|
||||
*self = CameraInput::default();
|
||||
*self = CameraInput {
|
||||
defaults_disabled: self.defaults_disabled,
|
||||
..default()
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns the desired velocity transform.
|
||||
pub fn target_velocity(&self, speed: f32, dt: f32) -> Transform {
|
||||
let mut new_transform = Transform::from_rotation(Quat::from_euler(
|
||||
pub fn target_velocity(&self, speed: f64, dt: f64) -> (DVec3, DQuat) {
|
||||
let rotation = DQuat::from_euler(
|
||||
EulerRot::XYZ,
|
||||
self.pitch * dt,
|
||||
self.yaw * dt,
|
||||
self.roll * dt,
|
||||
));
|
||||
);
|
||||
|
||||
let delta = Vec3::new(self.right, self.up, self.forward) * speed * dt;
|
||||
let translation =
|
||||
DVec3::new(self.right as f64, self.up as f64, self.forward as f64) * speed * dt as f64;
|
||||
|
||||
new_transform.translation = delta;
|
||||
new_transform
|
||||
(translation, rotation)
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,55 +122,98 @@ pub fn default_camera_inputs(
|
||||
keyboard.pressed(KeyCode::LControl).then(|| cam.up -= 1.0);
|
||||
keyboard.pressed(KeyCode::Q).then(|| cam.roll += 1.0);
|
||||
keyboard.pressed(KeyCode::E).then(|| cam.roll -= 1.0);
|
||||
keyboard.pressed(KeyCode::LShift).then(|| cam.boost = true);
|
||||
if let Some(total_mouse_motion) = mouse_move.iter().map(|e| e.delta).reduce(|sum, i| sum + i) {
|
||||
cam.pitch += total_mouse_motion.y * -0.1;
|
||||
cam.yaw += total_mouse_motion.x * -0.1;
|
||||
cam.pitch += total_mouse_motion.y as f64 * -0.1;
|
||||
cam.yaw += total_mouse_motion.x as f64 * -0.1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Tracks the camera's velocity
|
||||
#[derive(Debug, Default, Resource, Reflect)]
|
||||
pub struct CameraVelocity {
|
||||
entity: Option<Entity>,
|
||||
translation: DVec3,
|
||||
rotation: DQuat,
|
||||
}
|
||||
|
||||
impl CameraVelocity {
|
||||
/// Get the translation component of the camera velocity.
|
||||
pub fn translation(&self) -> DVec3 {
|
||||
self.translation
|
||||
}
|
||||
|
||||
/// Get the rotation component of the camera velocity.
|
||||
pub fn rotation(&self) -> DQuat {
|
||||
self.rotation
|
||||
}
|
||||
}
|
||||
|
||||
/// Uses [`CameraInput`] state to update the camera position.
|
||||
pub fn camera_controller(
|
||||
pub fn camera_controller<P: GridPrecision>(
|
||||
time: Res<Time>,
|
||||
settings: Res<FloatingOriginSettings>,
|
||||
mut input: ResMut<CameraInput>,
|
||||
mut camera: Query<(
|
||||
Entity,
|
||||
&mut Transform,
|
||||
&GlobalTransform,
|
||||
&mut CameraController,
|
||||
&CameraController,
|
||||
&mut GridCell<P>,
|
||||
)>,
|
||||
objects: Query<(&GlobalTransform, &Aabb)>,
|
||||
mut velocities: Local<HashMap<Entity, Transform>>,
|
||||
mut velocity: ResMut<CameraVelocity>,
|
||||
) {
|
||||
let (entity, mut cam_transform, cam_global_transform, controller) = camera.single_mut();
|
||||
let (entity, mut cam_transform, cam_global_transform, controller, mut cell) =
|
||||
camera.single_mut();
|
||||
|
||||
let speed = if controller.slow_near_objects {
|
||||
let mut nearest_object = f32::MAX;
|
||||
let mut nearest_object = f64::MAX;
|
||||
for (transform, aabb) in &objects {
|
||||
let distance = (transform.translation() + Vec3::from(aabb.center)
|
||||
- cam_global_transform.translation())
|
||||
let distance = (transform.translation().as_dvec3() + aabb.center.as_dvec3()
|
||||
- cam_global_transform.translation().as_dvec3())
|
||||
.length()
|
||||
- aabb.half_extents.max_element();
|
||||
- aabb.half_extents.as_dvec3().max_element();
|
||||
nearest_object = nearest_object.min(distance);
|
||||
}
|
||||
nearest_object.abs().clamp(1.0, controller.max_speed)
|
||||
} else {
|
||||
controller.max_speed
|
||||
} * (1.0 + input.boost as usize as f64);
|
||||
|
||||
let lerp_val = 1.0 - controller.smoothness.clamp(0.0, 0.999); // The lerp factor
|
||||
|
||||
if velocity.entity != Some(entity) {
|
||||
velocity.entity = Some(entity);
|
||||
velocity.translation = DVec3::ZERO;
|
||||
velocity.rotation = DQuat::IDENTITY;
|
||||
}
|
||||
|
||||
let (vel_t_current, vel_r_current) = (velocity.translation, velocity.rotation);
|
||||
let (vel_t_target, vel_r_target) = input.target_velocity(speed, time.delta_seconds_f64());
|
||||
|
||||
let cam_rot = cam_transform.rotation.as_f64();
|
||||
let vel_t_next = cam_rot * vel_t_target; // Orients the translation to match the camera
|
||||
let vel_t_next = vel_t_current.lerp(vel_t_next, lerp_val);
|
||||
// Convert the high precision translation to a grid cell and low precision translation
|
||||
let (cell_offset, new_translation) = settings.translation_to_grid(vel_t_next);
|
||||
*cell += cell_offset;
|
||||
cam_transform.translation += new_translation;
|
||||
|
||||
let new_rotation = vel_r_current.slerp(vel_r_target, lerp_val);
|
||||
cam_transform.rotation *= new_rotation.as_f32();
|
||||
|
||||
// Store the new velocity to be used in the next frame
|
||||
velocity.translation = if vel_t_next.length().abs() < 0.001 {
|
||||
DVec3::ZERO
|
||||
} else {
|
||||
vel_t_next
|
||||
};
|
||||
|
||||
let lerp_val = 1.0 - controller.smoothness.clamp(0.0, 0.99999999); // The lerp factor
|
||||
|
||||
let v_current = velocities.entry(entity).or_default();
|
||||
let v_target = input.target_velocity(speed, time.delta_seconds());
|
||||
|
||||
let v_next = Transform {
|
||||
translation: v_current.translation.lerp(v_target.translation, lerp_val),
|
||||
rotation: v_current.rotation.slerp(v_target.rotation, lerp_val),
|
||||
..default()
|
||||
velocity.rotation = if new_rotation.to_axis_angle().1.abs() < 0.001 {
|
||||
DQuat::IDENTITY
|
||||
} else {
|
||||
new_rotation
|
||||
};
|
||||
let cam_rot = cam_transform.rotation;
|
||||
cam_transform.translation += cam_rot * v_next.translation;
|
||||
cam_transform.rotation *= v_next.rotation;
|
||||
*v_current = v_next;
|
||||
|
||||
input.reset();
|
||||
}
|
||||
|
||||
@ -362,6 +362,13 @@ impl<P: GridPrecision> std::ops::Sub for &GridCell<P> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> std::ops::AddAssign for GridCell<P> {
|
||||
fn add_assign(&mut self, rhs: Self) {
|
||||
use std::ops::Add;
|
||||
*self = self.add(rhs);
|
||||
}
|
||||
}
|
||||
|
||||
/// Marks the entity to use as the floating origin. All other entities will be positioned relative
|
||||
/// to this entity's [`GridCell`].
|
||||
#[derive(Component, Reflect)]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user