Skip to content

Commit

Permalink
physics: forces are reliable yay
Browse files Browse the repository at this point in the history
  • Loading branch information
philiplinden committed Nov 18, 2024
1 parent fb5c6e7 commit b087019
Show file tree
Hide file tree
Showing 22 changed files with 731 additions and 488 deletions.
6 changes: 2 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,17 @@ dev_native = [
"bevy/sysinfo_plugin",
]
config-files = ["ron", "bevy_common_assets", "serde"]
inspect = ["bevy-inspector-egui"]
inspect = ["bevy-inspector-egui", "bevy_panorbit_camera/bevy_egui"]

[dependencies]
# core dependencies
bevy = "0.14.2"
bevy-trait-query = "0.6.0"
rand = "0.8.5"
# physics dependencies
avian3d = { version = "0.1.2", features = ["debug-plugin"] }
rayon = "1.10.0"
# ui dependencies
bevy_panorbit_camera = { version = "0.20.0" }
bevy-inspector-egui = { version = "0.27.0", features = ["highlight_changes"], optional = true }
bevy_prototype_debug_lines = { version = "0.12.0", features = ["3d"] }
iyes_perf_ui = { version = "0.3.0" }
# file io dependencies
bevy_common_assets = { version = "0.11.0", features = ["ron"], optional = true }
Expand Down
22 changes: 22 additions & 0 deletions docs/devlog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,19 @@ along the velocity vector and compute the drag as a function of the angle of
attack. Each raycast hits a point on the surface of the collider and the normal
and differential area is used to compute the drag force for each one.

Turns out air viscosity also changes with altitude for the standard atmosphere.
I learned today that there is also _dynamic_ viscosity and _kinematic_
viscosity. The latter is the former divided by the density. So as density
changes, so too does viscosity. These two types are really just different ways
to express the same thing.

In any case, I will wire up drag to be calculated from the bounding-sphere of
the collider. That way I can get a simple drag calculation working before
wiring up the more complex stuff and ignore things like shear and asymmetry for
now.

I reorganized the module hierarchy again shut up I'm not the problem you are.

- Split the `forces` module into `body` and `aero`. The base `forces` module
contains the common code for all forces.
- Added `AeroPlugin` for computing drag on solid bodies.
Expand All @@ -16,6 +29,15 @@ and differential area is used to compute the drag force for each one.
scene.
- Moved serde features behind the `config-files` feature flag. I'm hoping to
pare down the default dependencies for the project.
- Added `intro_to_drag.md` as I learn how to compute drag on arbitrary shapes.
- Added `camera.rs` as a simple pan-orbit camera controller.
- Added `controls.rs` for managing keybindings, and `KeyBindingsConfig` resource
for storing the keybindings. I use a resource instead of constants because
it makes the keybindings easier to change at runtime and feels more natural
alongside how Bevy handles inputs anyway.
- Moved `scene` and `ui` modules to a `graphics` module. Hopefully this will
make it easier to separate the concerns of physics simulation and graphics,
like if I ever want to add a TUI or CLI instead of a 3D graphics UI.

## 2024-11-17

Expand Down
File renamed without changes.
63 changes: 63 additions & 0 deletions src/app3d/controls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use bevy::prelude::*;

pub struct ControlsPlugin;

impl Plugin for ControlsPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<KeyBindingsConfig>();
}
}

#[derive(Resource)]
pub struct KeyBindingsConfig {
pub camera_controls: CameraControls,
pub debug_controls: DebugControls,
}

impl Default for KeyBindingsConfig {
fn default() -> Self {
Self {
camera_controls: CameraControls::default(),
debug_controls: DebugControls::default(),
}
}
}

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

#[derive(Reflect)]
pub struct DebugControls {
pub toggle_wireframe: KeyCode,
pub toggle_inspector: KeyCode,
pub toggle_perf_ui: KeyCode,
}

// ============================ DEFAULT KEYBINDINGS ============================

/// Defaults follow Blender conventions
impl Default for CameraControls {
fn default() -> Self {
Self {
modifier_pan: Some(KeyCode::ShiftLeft),
button_pan: MouseButton::Middle,
button_orbit: MouseButton::Middle,
toggle_zoom_direction: KeyCode::KeyZ,
}
}
}

impl Default for DebugControls {
fn default() -> Self {
Self {
toggle_wireframe: KeyCode::KeyW,
toggle_inspector: KeyCode::KeyI,
toggle_perf_ui: KeyCode::KeyP,
}
}
}
8 changes: 8 additions & 0 deletions src/app3d/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
mod scene;
mod ui;
mod controls;

// Re-export the plugins so they can be added to the app with `app.add_plugins`.
pub use scene::ScenePlugin;
pub use ui::InterfacePlugins;
pub use controls::{ControlsPlugin, KeyBindingsConfig};
65 changes: 65 additions & 0 deletions src/app3d/scene/camera.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use bevy::prelude::*;
use bevy_panorbit_camera::{PanOrbitCamera, PanOrbitCameraPlugin};
use avian3d::math::TAU;

use crate::app3d::KeyBindingsConfig;

pub struct CameraPlugin;

impl Plugin for CameraPlugin {
fn build(&self, app: &mut App) {
app.add_plugins(PanOrbitCameraPlugin);
app.add_systems(Startup, setup);
app.add_systems(Update, toggle_camera_controls_system);
}
}

fn setup(mut commands: Commands, key_bindings: Res<KeyBindingsConfig>) {
commands.spawn((
// Note we're setting the initial position below with yaw, pitch, and radius, hence
// we don't set transform on the camera.
Camera3dBundle::default(),
PanOrbitCamera {
// Set focal point (what the camera should look at)
focus: Vec3::new(0.0, 1.0, 0.0),
// Set the starting position, relative to focus (overrides camera's transform).
yaw: Some(TAU / 8.0),
pitch: Some(TAU / 8.0),
radius: Some(5.0),
// Set limits on rotation and zoom
yaw_upper_limit: Some(TAU / 4.0),
yaw_lower_limit: Some(-TAU / 4.0),
pitch_upper_limit: Some(TAU / 3.0),
pitch_lower_limit: Some(-TAU / 3.0),
zoom_upper_limit: Some(100.0),
zoom_lower_limit: 1.0,
// Adjust sensitivity of controls
orbit_sensitivity: 1.5,
pan_sensitivity: 0.5,
zoom_sensitivity: 0.5,
// Allow the camera to go upside down
allow_upside_down: true,
// Change the controls (these match Blender)
button_orbit: key_bindings.camera_controls.button_orbit,
button_pan: key_bindings.camera_controls.button_pan,
modifier_pan: key_bindings.camera_controls.modifier_pan,
// Reverse the zoom direction
reversed_zoom: false,
..default()
},
));
}

// This is how you can change config at runtime.
// Press 'T' to toggle the camera zoom direction.
fn toggle_camera_controls_system(
key_input: Res<ButtonInput<KeyCode>>,
key_bindings: Res<KeyBindingsConfig>,
mut pan_orbit_query: Query<&mut PanOrbitCamera>,
) {
if key_input.just_pressed(key_bindings.camera_controls.toggle_zoom_direction) {
for mut pan_orbit in pan_orbit_query.iter_mut() {
pan_orbit.reversed_zoom = !pan_orbit.reversed_zoom;
}
}
}
11 changes: 3 additions & 8 deletions src/scene.rs → src/app3d/scene/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
mod camera;

use avian3d::prelude::*;
use bevy::prelude::*;

pub struct ScenePlugin;

impl Plugin for ScenePlugin {
fn build(&self, app: &mut App) {
app.add_plugins(camera::CameraPlugin);
app.add_systems(Startup, simple_scene);
}
}
Expand All @@ -27,14 +30,6 @@ fn simple_scene(
..default()
},
));
// camera
commands.spawn((
Name::new("Camera"),
Camera3dBundle {
transform: Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),
..default()
},
));
// ground
let ground_size = Vec3::new(4.0, 0.1, 4.0);
commands.spawn((
Expand Down
35 changes: 35 additions & 0 deletions src/app3d/scene/vectors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use bevy::{prelude::*, render::primitives::Aabb};
use avian3d::math::PI;
use crate::simulator::forces::Force;

pub struct ForceVectorPlugin;

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

/// System to visualize all force vectors
// fn visualize_forces(
// gizmos: Res<Gizmos>,
// query: Query<(&Transform, &dyn Force)>,
// ) {
// for (transform, all_forces) in query.iter() {
// let origin = transform.translation;
// let segments: Vec<(Vec3, Vec3)> = all_forces.iter().map(|force| {
// let force_vector = force.force();
// (origin, origin + force_vector)
// }).collect();

// }
// }

// This system draws the axes based on the cube's transform, with length based on the size of
// the entity's axis-aligned bounding box (AABB).
fn draw_axes(mut gizmos: Gizmos, query: Query<(&Transform, &Aabb)>) {
for (&transform, &aabb) in &query {
let length = aabb.half_extents.length() * 0.1;
gizmos.axes(transform, length);
}
}
108 changes: 108 additions & 0 deletions src/app3d/ui/dev_tools.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
//! Development tools for the game. This plugin is only enabled in dev builds.
#[allow(unused_imports)]
use bevy::{
// dev_tools::states::log_transitions,
diagnostic::{
FrameTimeDiagnosticsPlugin, EntityCountDiagnosticsPlugin,
SystemInformationDiagnosticsPlugin,
},
input::common_conditions::input_just_pressed,
prelude::*,
};
use iyes_perf_ui::prelude::*;

#[cfg(not(target_arch = "wasm32"))]
use bevy::pbr::wireframe::{WireframeConfig, WireframePlugin};

use avian3d::debug_render::PhysicsDebugPlugin;

use crate::app3d::KeyBindingsConfig;

pub(super) fn plugin(app: &mut App) {
// Toggle the debug overlay for UI.
app.add_plugins((
// physics
PhysicsDebugPlugin::default(),
// performance
FrameTimeDiagnosticsPlugin,
EntityCountDiagnosticsPlugin,
SystemInformationDiagnosticsPlugin,
// rendering
#[cfg(not(target_arch = "wasm32"))]
WireframePlugin,
));
app.add_systems(
Update,
toggle_debug_ui
.before(iyes_perf_ui::PerfUiSet::Setup),
);

// Wireframe doesn't work on WASM
#[cfg(not(target_arch = "wasm32"))]
app.add_systems(
Update,
toggle_wireframe,
);

#[cfg(feature = "inspect")]
{
use bevy_inspector_egui::quick::WorldInspectorPlugin;
app.add_plugins(WorldInspectorPlugin::new());
}
}

/// Toggle the debug overlay
fn toggle_debug_ui(
mut commands: Commands,
q_root: Query<Entity, With<PerfUiRoot>>,
key_input: Res<ButtonInput<KeyCode>>,
key_bindings: Res<KeyBindingsConfig>,
) {
if key_input.just_pressed(key_bindings.debug_controls.toggle_perf_ui) {
if let Ok(e) = q_root.get_single() {
// despawn the existing Perf UI
commands.entity(e).despawn_recursive();
} else {
// create a simple Perf UI with default settings
// and all entries provided by the crate:
commands.spawn((
PerfUiRoot {
// set a fixed width to make all the bars line up
values_col_width: Some(160.0),
..Default::default()
},
// when we have lots of entries, we have to group them
// into tuples, because of Bevy Rust syntax limitations
(
PerfUiWidgetBar::new(PerfUiEntryFPS::default()),
PerfUiWidgetBar::new(PerfUiEntryFPSWorst::default()),
PerfUiWidgetBar::new(PerfUiEntryFrameTime::default()),
PerfUiWidgetBar::new(PerfUiEntryFrameTimeWorst::default()),
PerfUiWidgetBar::new(PerfUiEntryEntityCount::default()),
),
(
PerfUiEntryRunningTime::default(),
PerfUiEntryClock::default(),
),
(
PerfUiEntryCursorPosition::default(),
// PerfUiEntryWindowResolution::default(),
// PerfUiEntryWindowScaleFactor::default(),
// PerfUiEntryWindowMode::default(),
// PerfUiEntryWindowPresentMode::default(),
),
));
}
}
}

#[cfg(not(target_arch = "wasm32"))]
fn toggle_wireframe(
mut wireframe_config: ResMut<WireframeConfig>,
key_input: Res<ButtonInput<KeyCode>>,
key_bindings: Res<KeyBindingsConfig>,
) {
if key_input.just_pressed(key_bindings.debug_controls.toggle_wireframe) {
wireframe_config.global = !wireframe_config.global;
}
}
3 changes: 1 addition & 2 deletions src/ui/mod.rs → src/app3d/ui/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// mod vectors;
// mod monitors;

#[cfg(feature = "dev")]
Expand All @@ -14,7 +13,7 @@ impl PluginGroup for InterfacePlugins {
fn build(self) -> PluginGroupBuilder {
PluginGroupBuilder::start::<Self>()
.add(CoreUiPlugin)
// .add(vectors::ForceVectorPlugin)
.add(monitors::ForceMonitorPlugin)
}
}

Expand Down
Loading

0 comments on commit b087019

Please sign in to comment.