Skip to content

Commit

Permalink
Debug gizmos and time controls (#4)
Browse files Browse the repository at this point in the history
* ui: add gizmos, flatten app module

* debug: cleanup and gizmos

* physics: time control

* ci: skip deploys
  • Loading branch information
philiplinden authored Nov 29, 2024
1 parent 119dade commit ae590a3
Show file tree
Hide file tree
Showing 17 changed files with 438 additions and 66 deletions.
11 changes: 11 additions & 0 deletions docs/devlog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# development log

## 2024-11-28

I tinkered with camera controls and the new `require` attribute. Now the camera
follows the balloon. I also fixed the toggles for debug gizmos!

I added controls for changing the physics time multiplier (and a debug ui) and
it is correctly changing the physics clock's relative speed, but the physics
breaks when the physics clock's relative speed goes above 1.5. Maybe it has
something to do with schedules, but more likely the timestep is simply too
large.

## 2024-11-27

Some of my dependencies may now have Bevy 0.15 support.
Expand Down
102 changes: 98 additions & 4 deletions src/app3d/camera.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,114 @@
use bevy::prelude::*;
use bevy::{
input::mouse::{MouseScrollUnit, MouseWheel},
prelude::*,
};

// use crate::controls::CameraControls;
use super::controls::KeyBindingsConfig;
use crate::simulator::Balloon;

const INVERT_ZOOM: bool = true;

pub struct CameraPlugin;

impl Plugin for CameraPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<CameraSelection>();
app.add_systems(Startup, setup);
app.add_systems(Update, zoom_camera);
app.add_plugins(CameraFollowPlugin);
}
}

#[derive(Component, Default)]
#[require(Camera3d, PerspectiveProjection)]
struct MainCamera;

/// A resource that stores the currently selected camera target.
#[derive(Resource)]
struct CameraSelection {
entity: Entity,
offset: Vec3,
}

impl Default for CameraSelection {
fn default() -> Self {
Self {
entity: Entity::PLACEHOLDER,
offset: Vec3::new(0., 0., 10.),
}
}
}

/// A marker component for entities that can be selected as a camera target.
#[derive(Component, Default, Reflect)]
pub struct CameraTarget;

fn setup(mut commands: Commands) {
commands.spawn((
// Note we're setting the initial position below with yaw, pitch, and radius, hence
// we don't set transform on the camera.
Name::new("Main Camera"),
MainCamera,
Camera3d::default(),
Transform::from_xyz(0.0, 20., 50.0).looking_at(Vec3::new(0., 20., 0.), Vec3::Y),
));
}

fn zoom_camera(
mut camera: Query<&mut PerspectiveProjection, (With<Camera3d>, With<MainCamera>)>,
mut evr_scroll: EventReader<MouseWheel>,
key_bindings: Res<KeyBindingsConfig>,
) {
let mut projection = camera.single_mut();
let ctrl = &key_bindings.camera_controls;
let direction = if INVERT_ZOOM { -1.0 } else { 1.0 };
for ev in evr_scroll.read() {
match ev.unit {
MouseScrollUnit::Line => {
projection.fov = projection.fov.clamp(ctrl.min_fov, ctrl.max_fov)
+ ev.y * ctrl.zoom_step * direction;
}
MouseScrollUnit::Pixel => {
projection.fov = projection.fov.clamp(ctrl.min_fov, ctrl.max_fov)
+ ev.y * ctrl.zoom_step * direction;
}
}
}
}

struct CameraFollowPlugin;

impl Plugin for CameraFollowPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, (mark_new_targets, follow_selected_target));
}
}

fn mark_new_targets(
mut commands: Commands,
balloons: Query<Entity, Added<Balloon>>,
mut selection: ResMut<CameraSelection>,
) {
for entity in &balloons {
commands.entity(entity).insert(CameraTarget);
// Focus on the newest balloon
selection.entity = entity;
}
}

fn follow_selected_target(
selection: Res<CameraSelection>,
targets: Query<&Transform, (With<CameraTarget>, Without<MainCamera>)>,
mut camera: Query<&mut Transform, With<MainCamera>>,
) {
let mut cam = camera.single_mut();
match targets.get(selection.entity) {
Ok(t) => {
// If the target exists, move the camera next to it
cam.translation = t.translation + selection.offset;
// Look at the target position
cam.look_at(t.translation, Vec3::Y);
}
Err(_) => {
// If there is no selected entity, stay where you are
}
}
}
22 changes: 18 additions & 4 deletions src/app3d/controls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@ pub struct KeyBindingsConfig {

#[derive(Reflect)]
pub struct CameraControls {
pub modifier_pan: Option<KeyCode>,
pub cycle_target: KeyCode,
pub modifier_pan: KeyCode,
pub button_pan: MouseButton,
pub button_orbit: MouseButton,
pub toggle_zoom_direction: KeyCode,
pub zoom_step: f32,
pub max_fov: f32,
pub min_fov: f32,
}

#[derive(Reflect)]
Expand All @@ -36,6 +39,10 @@ pub struct DebugControls {
#[derive(Reflect)]
pub struct TimeControls {
pub toggle_pause: KeyCode,
pub faster: KeyCode,
pub slower: KeyCode,
pub reset_speed: KeyCode,
pub scale_step: f32,
}

// ============================ DEFAULT KEYBINDINGS ============================
Expand All @@ -44,10 +51,13 @@ pub struct TimeControls {
impl Default for CameraControls {
fn default() -> Self {
Self {
modifier_pan: Some(KeyCode::ShiftLeft),
cycle_target: KeyCode::Tab,
modifier_pan: KeyCode::ShiftLeft,
button_pan: MouseButton::Middle,
button_orbit: MouseButton::Middle,
toggle_zoom_direction: KeyCode::KeyZ,
zoom_step: 0.01,
max_fov: 1.0,
min_fov: 0.01,
}
}
}
Expand All @@ -68,6 +78,10 @@ impl Default for TimeControls {
fn default() -> Self {
Self {
toggle_pause: KeyCode::Space,
faster: KeyCode::ArrowUp,
slower: KeyCode::ArrowDown,
reset_speed: KeyCode::Backspace,
scale_step: 0.1,
}
}
}
27 changes: 23 additions & 4 deletions src/app3d/dev_tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ use bevy::{
prelude::*,
};

use crate::{app3d::controls::KeyBindingsConfig, simulator::SimState};
use crate::simulator::SimState;

use super::{controls::KeyBindingsConfig, gizmos::ForceGizmos};

pub struct DevToolsPlugin;

Expand All @@ -34,8 +36,11 @@ impl Plugin for DevToolsPlugin {

app.init_resource::<DebugState>();

app.add_systems(Update, log_transitions::<SimState>);
app.add_systems(Update, show_physics_gizmos);
app.add_systems(Update, (
log_transitions::<SimState>,
show_force_gizmos,
show_physics_gizmos,
));

// Wireframe doesn't work on WASM
#[cfg(not(target_arch = "wasm32"))]
Expand Down Expand Up @@ -103,11 +108,25 @@ fn toggle_debug_ui(
}
}

fn show_force_gizmos(
debug_state: Res<DebugState>,
mut gizmo_store: ResMut<GizmoConfigStore>
) {
if debug_state.is_changed() {
let (_, force_config) = gizmo_store.config_mut::<ForceGizmos>();
if debug_state.forces {
*force_config = ForceGizmos::all();
} else {
*force_config = ForceGizmos::none();
}
}
}

fn show_physics_gizmos(
debug_state: Res<DebugState>,
mut gizmo_store: ResMut<GizmoConfigStore>
) {
if gizmo_store.is_changed() {
if debug_state.is_changed() {
let (_, physics_config) = gizmo_store.config_mut::<PhysicsGizmos>();
if debug_state.physics {
*physics_config = PhysicsGizmos::all();
Expand Down
69 changes: 64 additions & 5 deletions src/app3d/gizmos.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,83 @@
use bevy::{color::palettes::basic::*, prelude::*};

use crate::simulator::{forces::Force, SimState};
use crate::simulator::forces::Force;

const ARROW_SCALE: f32 = 0.1;

pub struct ForceArrowsPlugin;

impl Plugin for ForceArrowsPlugin {
fn build(&self, app: &mut App) {
app.add_systems(PostUpdate, force_arrows);
app.init_gizmo_group::<ForceGizmos>();
app.register_type::<ForceGizmos>();
app.add_systems(
PostUpdate,
force_arrows.run_if(
|store: Res<GizmoConfigStore>| {
store.config::<ForceGizmos>().0.enabled
}),
);
}
}

fn force_arrows(query: Query<&dyn Force>, mut gizmos: Gizmos) {
fn force_arrows(
query: Query<&dyn Force>,
mut gizmos: Gizmos,
) {
for forces in query.iter() {
for force in forces.iter() {
let start = force.point_of_application();
let end = start + force.force() * ARROW_SCALE;
let color = force.color().unwrap_or(RED.into());
gizmos.arrow(start, end, color).with_tip_length(0.1);
let color = match force.color() {
Some(c) => c,
None => RED.into(),
};
gizmos.arrow(start, end, color).with_tip_length(0.3);
}
}
}

#[derive(Reflect, GizmoConfigGroup)]
pub struct ForceGizmos {
/// The scale of the force arrows.
pub arrow_scale: Option<f32>,
/// The color of the force arrows. If `None`, the arrows will not be rendered.
pub arrow_color: Option<Color>,
/// The length of the arrow tips.
pub tip_length: Option<f32>,
/// Determines if the forces should be hidden when not active.
pub enabled: bool,
}

impl Default for ForceGizmos {
fn default() -> Self {
Self {
arrow_scale: Some(0.1),
arrow_color: Some(RED.into()),
tip_length: Some(0.3),
enabled: false,
}
}
}

impl ForceGizmos {
/// Creates a [`ForceGizmos`] configuration with all rendering options enabled.
pub fn all() -> Self {
Self {
arrow_scale: Some(0.1),
arrow_color: Some(RED.into()),
tip_length: Some(0.3),
enabled: true,
}
}

/// Creates a [`ForceGizmos`] configuration with debug rendering enabled but all options turned off.
pub fn none() -> Self {
Self {
arrow_scale: None,
arrow_color: None,
tip_length: None,
enabled: false,
}
}
}
29 changes: 27 additions & 2 deletions src/app3d/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use monitors::MonitorsPlugin;

use bevy::{app::{PluginGroup, PluginGroupBuilder}, prelude::*};

use crate::simulator::SimState;
use crate::simulator::{SimState, time::TimeScaleOptions};

pub struct App3dPlugins;

Expand All @@ -35,6 +35,7 @@ impl Plugin for InterfacePlugin {
fn build(&self, app: &mut App) {
app.add_plugins((
PausePlayPlugin,
ChangeTimeScalePlugin,
ForceArrowsPlugin,
MonitorsPlugin,
#[cfg(feature = "dev")]
Expand All @@ -43,7 +44,7 @@ impl Plugin for InterfacePlugin {
}
}

pub struct PausePlayPlugin;
struct PausePlayPlugin;

impl Plugin for PausePlayPlugin {
fn build(&self, app: &mut App) {
Expand All @@ -65,3 +66,27 @@ fn toggle_pause(
}
}
}

struct ChangeTimeScalePlugin;

impl Plugin for ChangeTimeScalePlugin {
fn build(&self, app: &mut App) {
app.add_systems(PreUpdate, modify_time_scale);
}
}

fn modify_time_scale(
mut time_options: ResMut<TimeScaleOptions>,
key_input: Res<ButtonInput<KeyCode>>,
key_bindings: Res<KeyBindingsConfig>,
) {
if key_input.just_pressed(key_bindings.time_controls.faster) {
time_options.multiplier += key_bindings.time_controls.scale_step;
}
if key_input.just_pressed(key_bindings.time_controls.slower) {
time_options.multiplier -= key_bindings.time_controls.scale_step;
}
if key_input.just_pressed(key_bindings.time_controls.reset_speed) {
time_options.multiplier = 1.0;
}
}
Loading

0 comments on commit ae590a3

Please sign in to comment.