diff --git a/Cargo.toml b/Cargo.toml index 96d5fe3..95225e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 } diff --git a/docs/devlog.md b/docs/devlog.md index b282a41..3a074fc 100644 --- a/docs/devlog.md +++ b/docs/devlog.md @@ -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. @@ -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 diff --git a/docs/intro_to_drag.md b/docs/newtonian_aero.md similarity index 100% rename from docs/intro_to_drag.md rename to docs/newtonian_aero.md diff --git a/src/app3d/controls.rs b/src/app3d/controls.rs new file mode 100644 index 0000000..f275207 --- /dev/null +++ b/src/app3d/controls.rs @@ -0,0 +1,63 @@ +use bevy::prelude::*; + +pub struct ControlsPlugin; + +impl Plugin for ControlsPlugin { + fn build(&self, app: &mut App) { + app.init_resource::(); + } +} + +#[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, + 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, + } + } +} diff --git a/src/app3d/mod.rs b/src/app3d/mod.rs new file mode 100644 index 0000000..9995a43 --- /dev/null +++ b/src/app3d/mod.rs @@ -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}; diff --git a/src/app3d/scene/camera.rs b/src/app3d/scene/camera.rs new file mode 100644 index 0000000..114884c --- /dev/null +++ b/src/app3d/scene/camera.rs @@ -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) { + 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>, + key_bindings: Res, + 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; + } + } +} diff --git a/src/scene.rs b/src/app3d/scene/mod.rs similarity index 88% rename from src/scene.rs rename to src/app3d/scene/mod.rs index 4045bc2..cb7c6da 100644 --- a/src/scene.rs +++ b/src/app3d/scene/mod.rs @@ -1,3 +1,5 @@ +mod camera; + use avian3d::prelude::*; use bevy::prelude::*; @@ -5,6 +7,7 @@ pub struct ScenePlugin; impl Plugin for ScenePlugin { fn build(&self, app: &mut App) { + app.add_plugins(camera::CameraPlugin); app.add_systems(Startup, simple_scene); } } @@ -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(( diff --git a/src/app3d/scene/vectors.rs b/src/app3d/scene/vectors.rs new file mode 100644 index 0000000..8f21a06 --- /dev/null +++ b/src/app3d/scene/vectors.rs @@ -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, +// 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); + } +} diff --git a/src/app3d/ui/dev_tools.rs b/src/app3d/ui/dev_tools.rs new file mode 100644 index 0000000..c8484af --- /dev/null +++ b/src/app3d/ui/dev_tools.rs @@ -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>, + key_input: Res>, + key_bindings: Res, +) { + 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, + key_input: Res>, + key_bindings: Res, +) { + if key_input.just_pressed(key_bindings.debug_controls.toggle_wireframe) { + wireframe_config.global = !wireframe_config.global; + } +} diff --git a/src/ui/mod.rs b/src/app3d/ui/mod.rs similarity index 92% rename from src/ui/mod.rs rename to src/app3d/ui/mod.rs index 3280ae4..66d3000 100644 --- a/src/ui/mod.rs +++ b/src/app3d/ui/mod.rs @@ -1,4 +1,3 @@ -// mod vectors; // mod monitors; #[cfg(feature = "dev")] @@ -14,7 +13,7 @@ impl PluginGroup for InterfacePlugins { fn build(self) -> PluginGroupBuilder { PluginGroupBuilder::start::() .add(CoreUiPlugin) - // .add(vectors::ForceVectorPlugin) + .add(monitors::ForceMonitorPlugin) } } diff --git a/src/app3d/ui/monitors.rs b/src/app3d/ui/monitors.rs new file mode 100644 index 0000000..81b3382 --- /dev/null +++ b/src/app3d/ui/monitors.rs @@ -0,0 +1,95 @@ +//! UI for monitoring the simulation. + +use bevy::prelude::*; +use iyes_perf_ui::prelude::*; + +pub struct ForceMonitorPlugin ; + +impl Plugin for ForceMonitorPlugin { + fn build(&self, app: &mut App) { + app.add_perf_ui_simple_entry::(); + app.add_perf_ui_simple_entry::(); + app.add_perf_ui_simple_entry::(); + app.add_perf_ui_simple_entry::(); + } +} + +// TODO: make a generic force monitor that takes a force trait and displays the +// force vector so we don't have to make a new monitor for each force type and +// redo all that boilerplate code. + +#[derive(Component)] +struct WeightForceMonitor { + /// The label text to display, to allow customization + pub label: String, + /// Should we display units? + pub display_units: bool, + /// Highlight the value if it goes above this threshold + pub threshold_highlight: Option, + /// Support color gradients! + pub color_gradient: ColorGradient, + /// Width for formatting the string + pub digits: u8, + /// Precision for formatting the string + pub precision: u8, + + /// Required to ensure the entry appears in the correct place in the Perf UI + pub sort_key: i32, +} + +#[derive(Component)] +struct BuoyancyForceMonitor { + /// The label text to display, to allow customization + pub label: String, + /// Should we display units? + pub display_units: bool, + /// Highlight the value if it goes above this threshold + pub threshold_highlight: Option, + /// Support color gradients! + pub color_gradient: ColorGradient, + /// Width for formatting the string + pub digits: u8, + /// Precision for formatting the string + pub precision: u8, + + /// Required to ensure the entry appears in the correct place in the Perf UI + pub sort_key: i32, +} + +#[derive(Component)] +struct DragForceMonitor { + /// The label text to display, to allow customization + pub label: String, + /// Should we display units? + pub display_units: bool, + /// Highlight the value if it goes above this threshold + pub threshold_highlight: Option, + /// Support color gradients! + pub color_gradient: ColorGradient, + /// Width for formatting the string + pub digits: u8, + /// Precision for formatting the string + pub precision: u8, + + /// Required to ensure the entry appears in the correct place in the Perf UI + pub sort_key: i32, +} + +#[derive(Component)] +struct NetForceMonitor { + /// The label text to display, to allow customization + pub label: String, + /// Should we display units? + pub display_units: bool, + /// Highlight the value if it goes above this threshold + pub threshold_highlight: Option, + /// Support color gradients! + pub color_gradient: ColorGradient, + /// Width for formatting the string + pub digits: u8, + /// Precision for formatting the string + pub precision: u8, + + /// Required to ensure the entry appears in the correct place in the Perf UI + pub sort_key: i32, +} diff --git a/src/lib.rs b/src/lib.rs index a8bc4af..b9a6fac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ mod simulator; -mod scene; -mod ui; +mod app3d; + #[cfg(feature = "config-files")] mod assets; @@ -34,11 +34,14 @@ impl Plugin for AppCorePlugin { }), ); - // Add other plugins. + // Add the simulator plugins (that don't deal with graphics). + app.add_plugins(simulator::SimulatorPlugins); + + // Add the 3D application plugins. app.add_plugins(( - simulator::SimulatorPlugins, - ui::InterfacePlugins, - scene::ScenePlugin, + app3d::InterfacePlugins, + app3d::ScenePlugin, + app3d::ControlsPlugin, )); } } diff --git a/src/simulator/balloon.rs b/src/simulator/balloon.rs index 42bd6fa..e32177d 100644 --- a/src/simulator/balloon.rs +++ b/src/simulator/balloon.rs @@ -6,6 +6,7 @@ use bevy::prelude::*; use serde::{Deserialize, Serialize}; use super::{ + SimulatedBody, ideal_gas::{GasSpecies, IdealGasBundle}, properties::*, }; @@ -85,7 +86,6 @@ impl Default for Balloon { } } } - fn spawn_balloon( mut commands: Commands, mut meshes: ResMut>, @@ -94,6 +94,7 @@ fn spawn_balloon( let radius = 0.3; commands.spawn(( Name::new("BalloonBundle"), + SimulatedBody, BalloonBundle { balloon: Balloon::default(), gas: IdealGasBundle::new( diff --git a/src/simulator/forces/aero.rs b/src/simulator/forces/aero.rs index 6a6db9c..f2b77c5 100644 --- a/src/simulator/forces/aero.rs +++ b/src/simulator/forces/aero.rs @@ -1,12 +1,11 @@ //! Forces applied to rigid bodies due to aerodynamic drag. -use avian3d::{math::{PI, FRAC_PI_2, TAU}, prelude::*}; -use parry3d::{math::Point, shape::{FeatureId, Shape}}; +use avian3d::{math::PI, prelude::*}; +use parry3d::shape::{ShapeType, Shape, Ball}; use bevy::prelude::*; use bevy_trait_query::{self, RegisterExt}; - -use super::{Atmosphere, Density, ForceUpdateOrder, Force}; +use super::{Atmosphere, Density, ForceUpdateOrder, Force, SimulatedBody}; pub struct AeroForcesPlugin; @@ -14,7 +13,6 @@ impl Plugin for AeroForcesPlugin { fn build(&self, app: &mut App) { app.register_type::(); app.register_component_as::(); - app.init_resource::(); app.add_systems(Update, update_drag_parameters.in_set(ForceUpdateOrder::Prepare)); } } @@ -22,8 +20,7 @@ impl Plugin for AeroForcesPlugin { /// Force (N) due to drag as a solid body moves through a fluid. #[derive(Component, Reflect)] pub struct Drag { - drag_force: Vec3, - drag_moment: Vec3, + flow_velocity: Vec3, ambient_density: Density, drag_area: f32, drag_coeff: f32, @@ -31,36 +28,35 @@ pub struct Drag { impl Default for Drag { fn default() -> Self { Self { - drag_force: Vec3::ZERO, - drag_moment: Vec3::ZERO, + flow_velocity: Vec3::ZERO, ambient_density: Density::ZERO, drag_area: 0.0, - drag_coeff: 0.0, + drag_coeff: 1.0, } } } impl Drag { pub fn update( &mut self, + flow_velocity: Vec3, ambient_density: Density, - drag_force: Vec3, - drag_moment: Vec3, drag_area: f32, drag_coeff: f32, ) { + self.flow_velocity = flow_velocity; self.ambient_density = ambient_density; - self.drag_force = drag_force; - self.drag_moment = drag_moment; self.drag_area = drag_area; self.drag_coeff = drag_coeff; } } impl Force for Drag { + fn name(&self) -> String { + String::from("Drag") + } fn force(&self) -> Vec3 { drag( + self.flow_velocity, self.ambient_density.kg_per_m3(), - self.drag_force, - self.drag_moment, self.drag_area, self.drag_coeff, ) @@ -72,277 +68,39 @@ impl Force for Drag { fn update_drag_parameters( atmosphere: Res, - mut bodies: Query<(&mut Drag, &Position, &LinearVelocity, &Collider), With>, - sampling_config: Res, + mut bodies: Query<(&mut Drag, &Position, &LinearVelocity, &Collider), With>, ) { - let num_samples = sampling_config.num_samples; - - let wind_velocity = Vec3::ZERO; // TODO: update with actual wind - for (mut drag, position, velocity, collider) in bodies.iter_mut() { - let ambient_density = atmosphere.density(position.0); - - // Calculate relative flow velocity - let relative_flow_velocity = velocity.0 - wind_velocity; - - // Update drag component with normalized force direction - let drag_force = aero_drag_from_collider(collider, relative_flow_velocity, ambient_density, num_samples); - + let bounding_sphere = collider.shape().compute_bounding_sphere(&position.0.into()); drag.update( - ambient_density, - drag_normal, - 1.0, // Area handled in aero_drag_from_collider - 1.0, // Coefficient handled in aero_drag_from_collider + velocity.0, + atmosphere.density(position.0), + projected_spherical_area(bounding_sphere.radius()), + drag_coefficient(&Ball::new(bounding_sphere.radius()), &atmosphere), ); } } -/// Number of samples for surface sampling. -#[derive(Resource, Reflect)] -pub struct SurfaceSamplingConfig { - num_samples: usize, -} -impl Default for SurfaceSamplingConfig { - fn default() -> Self { - SurfaceSamplingConfig { num_samples: 100 } - } -} - -/// Represents a sampled point on the collider's surface along with its normal and differential area. -struct SurfaceSample { - point: Vec3, - normal: Vec3, - differential_area: f32, -} - -use std::f64::consts::PI; -use nalgebra as na; - -// Constants -const AIR_DENSITY: f64 = 1.225; // kg/m³ at sea level, 15°C -const AIR_VISCOSITY: f64 = 1.81e-5; // kg/(m·s) at 15°C - -#[derive(Debug, Clone)] -struct Vertex(na::Point3); - -#[derive(Debug, Clone)] -struct Face { - vertices: Vec, // Indices into vertex array - normal: na::Vector3, - area: f64, -} - -#[derive(Debug)] -struct IrregularPolyhedron { - vertices: Vec, - faces: Vec, - center_of_mass: na::Point3, - orientation: na::Rotation3, -} - -impl IrregularPolyhedron { - fn new(vertices: Vec<[f64; 3]>, faces: Vec>) -> Self { - let vertices: Vec = vertices - .into_iter() - .map(|v| Vertex(na::Point3::new(v[0], v[1], v[2]))) - .collect(); - - let mut polyhedron_faces = Vec::new(); - for face_vertices in faces { - let v0 = vertices[face_vertices[0]].0; - let v1 = vertices[face_vertices[1]].0; - let v2 = vertices[face_vertices[2]].0; - - // Calculate face normal and area - let edge1 = v1 - v0; - let edge2 = v2 - v0; - let normal = edge1.cross(&edge2).normalize(); - - // Calculate area using Newell's method for arbitrary polygons - let mut area = 0.0; - for i in 0..face_vertices.len() { - let j = (i + 1) % face_vertices.len(); - let vi = vertices[face_vertices[i]].0; - let vj = vertices[face_vertices[j]].0; - area += (vj - vi).norm() * (vi + vj).norm() / 2.0; - } - - polyhedron_faces.push(Face { - vertices: face_vertices, - normal: normal, - area, - }); - } - - // Calculate center of mass (assuming uniform density) - let com = vertices.iter().fold(na::Point3::origin(), |acc, v| acc + v.0.coords) - / vertices.len() as f64; - - IrregularPolyhedron { - vertices, - faces: polyhedron_faces, - center_of_mass: com, - orientation: na::Rotation3::identity(), - } - } - - fn rotate(&mut self, axis: &na::Vector3, angle: f64) { - self.orientation *= na::Rotation3::from_axis_angle(&na::Unit::new_normalize(*axis), angle); - } - - fn get_projected_area(&self, flow_direction: &na::Vector3) -> f64 { - let flow_dir = self.orientation * flow_direction; - - // Calculate projected area by summing contributions from each face - self.faces.iter().map(|face| { - let cos_angle = face.normal.dot(&flow_dir).abs(); - face.area * cos_angle - }).sum() - } - - fn get_characteristic_length(&self) -> f64 { - // Use maximum distance between any two vertices as characteristic length - let mut max_distance = 0.0; - for (i, v1) in self.vertices.iter().enumerate() { - for v2 in self.vertices.iter().skip(i + 1) { - let distance = (v2.0 - v1.0).norm(); - max_distance = max_distance.max(distance); - } - } - max_distance - } - - fn get_surface_area(&self) -> f64 { - self.faces.iter().map(|face| face.area).sum() - } -} - -struct FlowConditions { - velocity: na::Vector3, - density: f64, - viscosity: f64, -} - -impl Default for FlowConditions { - fn default() -> Self { - FlowConditions { - velocity: na::Vector3::new(10.0, 0.0, 0.0), // 10 m/s in x direction - density: AIR_DENSITY, - viscosity: AIR_VISCOSITY, - } - } -} - -fn calculate_reynolds_number(length: f64, velocity: f64, conditions: &FlowConditions) -> f64 { - (conditions.density * velocity * length) / conditions.viscosity -} - -fn calculate_drag_coefficients(reynolds: f64, shape_complexity: f64) -> (f64, f64) { - // Pressure drag coefficient - let cd_pressure = match reynolds { - re if re < 1.0 => 24.0 / re, - re if re < 1000.0 => 24.0 / re + 6.0 / (1.0 + re.sqrt()), - re if re < 2e5 => 1.1 * shape_complexity, - re if re < 7e5 => 0.8 * shape_complexity, - _ => 0.6 * shape_complexity, - }; - - // Friction drag coefficient - let cd_friction = match reynolds { - re if re < 1e5 => 1.328 / re.sqrt(), - _ => 0.074 / reynolds.powf(0.2), - }; - - (cd_pressure, cd_friction) -} - -struct DragResult { - pressure_drag: na::Vector3, - friction_drag: na::Vector3, - reynolds_number: f64, - cd_pressure: f64, - cd_friction: f64, -} - -fn calculate_drag(polyhedron: &IrregularPolyhedron, conditions: &FlowConditions) -> DragResult { - let velocity_magnitude = conditions.velocity.norm(); - let flow_direction = conditions.velocity.normalize(); - - // Get characteristic length and Reynolds number - let char_length = polyhedron.get_characteristic_length(); - let reynolds = calculate_reynolds_number(char_length, velocity_magnitude, conditions); - - // Calculate shape complexity factor (ratio of actual surface area to minimum possible surface area) - let actual_surface_area = polyhedron.get_surface_area(); - let min_surface_area = 4.0 * PI * (char_length / 2.0).powi(2); // sphere surface area - let shape_complexity = actual_surface_area / min_surface_area; - - // Get drag coefficients - let (cd_pressure, cd_friction) = calculate_drag_coefficients(reynolds, shape_complexity); - - // Calculate projected area - let projected_area = polyhedron.get_projected_area(&flow_direction); - - // Calculate drag forces - let dynamic_pressure = 0.5 * conditions.density * velocity_magnitude.powi(2); - - let pressure_drag = flow_direction * (cd_pressure * dynamic_pressure * projected_area); - let friction_drag = flow_direction * (cd_friction * dynamic_pressure * actual_surface_area); - - DragResult { - pressure_drag, - friction_drag, - reynolds_number: reynolds, - cd_pressure, - cd_friction, +/// Force (N) due to drag as a solid body moves through a fluid. +pub fn drag(flow_velocity: Vec3, ambient_density: f32, drag_area: f32, drag_coeff: f32) -> Vec3 { + let drag_direction = flow_velocity.normalize_or_zero(); // parallel to flow + let drag_magnitude = drag_coeff / 2.0 + * ambient_density + * flow_velocity.length_squared() + * drag_area; + drag_direction * drag_magnitude +} + +/// Get the projected area (m^2) of a sphere with a given radius (m) +fn projected_spherical_area(radius: f32) -> f32 { + f32::powf(radius, 2.0) * PI +} + +/// Get the drag coefficient for a given shape and ambient conditions. +fn drag_coefficient(shape: &dyn Shape, _atmosphere: &Atmosphere) -> f32 { + match shape.shape_type() { + ShapeType::Ball => 1.17, + ShapeType::Cuboid => 2.05, + _ => 1.0, } } - -fn main() { - // Example usage with an irregular tetrahedron - let vertices = vec![ - [0.0, 0.0, 0.0], - [1.0, 0.0, 0.0], - [0.3, 1.2, 0.0], - [0.5, 0.4, 1.5], - ]; - - let faces = vec![ - vec![0, 1, 2], - vec![0, 1, 3], - vec![1, 2, 3], - vec![0, 2, 3], - ]; - - let mut polyhedron = IrregularPolyhedron::new(vertices, faces); - - // Rotate the polyhedron 30 degrees around the y-axis - polyhedron.rotate(&na::Vector3::new(0.0, 1.0, 0.0), PI / 6.0); - - let conditions = FlowConditions::default(); - - let result = calculate_drag(&polyhedron, &conditions); - - println!("Irregular Polyhedron Analysis:"); - println!("Characteristic Length: {:.3} m", polyhedron.get_characteristic_length()); - println!("Surface Area: {:.3} m²", polyhedron.get_surface_area()); - println!("Projected Area: {:.3} m²", - polyhedron.get_projected_area(&conditions.velocity.normalize())); - - println!("\nFlow Conditions:"); - println!("Velocity: [{:.1}, {:.1}, {:.1}] m/s", - conditions.velocity.x, conditions.velocity.y, conditions.velocity.z); - - println!("\nResults:"); - println!("Reynolds Number: {:.1e}", result.reynolds_number); - println!("Pressure Drag Coefficient: {:.3}", result.cd_pressure); - println!("Friction Drag Coefficient: {:.3}", result.cd_friction); - println!("Pressure Drag: [{:.3}, {:.3}, {:.3}] N", - result.pressure_drag.x, result.pressure_drag.y, result.pressure_drag.z); - println!("Friction Drag: [{:.3}, {:.3}, {:.3}] N", - result.friction_drag.x, result.friction_drag.y, result.friction_drag.z); - println!("Total Drag: [{:.3}, {:.3}, {:.3}] N", - result.pressure_drag.x + result.friction_drag.x, - result.pressure_drag.y + result.friction_drag.y, - result.pressure_drag.z + result.friction_drag.z); -} diff --git a/src/simulator/forces/body.rs b/src/simulator/forces/body.rs index b9cc653..7cb0a0d 100644 --- a/src/simulator/forces/body.rs +++ b/src/simulator/forces/body.rs @@ -4,7 +4,7 @@ use avian3d::prelude::*; use bevy::prelude::*; use bevy_trait_query::{self, RegisterExt}; -use super::{Atmosphere, Density, Force, ForceUpdateOrder, Mass, Volume}; +use super::{Atmosphere, Density, Force, ForceUpdateOrder, Mass, SimulatedBody, Volume}; use crate::simulator::properties::{EARTH_RADIUS_M, STANDARD_G}; pub struct BodyForcesPlugin; @@ -46,6 +46,9 @@ impl Weight { } } impl Force for Weight { + fn name(&self) -> String { + String::from("Weight") + } fn force(&self) -> Vec3 { weight(self.position, self.mass) } @@ -66,7 +69,9 @@ pub fn weight(position: Vec3, mass: f32) -> Vec3 { Vec3::NEG_Y * g(position) * mass // [N] } -fn update_weight_parameters(mut bodies: Query<(&mut Weight, &Position, &Mass), With>) { +fn update_weight_parameters( + mut bodies: Query<(&mut Weight, &Position, &Mass), With>, +) { for (mut weight, position, mass) in bodies.iter_mut() { weight.update(position.0, mass.kg()); } @@ -96,6 +101,9 @@ impl Buoyancy { } } impl Force for Buoyancy { + fn name(&self) -> String { + String::from("Buoyancy") + } fn force(&self) -> Vec3 { buoyancy(self.position, self.displaced_volume, self.ambient_density) } @@ -112,7 +120,7 @@ pub fn buoyancy(position: Vec3, displaced_volume: Volume, ambient_density: Densi fn update_buoyant_parameters( atmosphere: Res, - mut bodies: Query<(&mut Buoyancy, &Position, &Volume), With>, + mut bodies: Query<(&mut Buoyancy, &Position, &Volume), With>, ) { for (mut buoyancy, position, volume) in bodies.iter_mut() { let density = atmosphere.density(position.0); diff --git a/src/simulator/forces/mod.rs b/src/simulator/forces/mod.rs index b4b0e08..63a2e38 100644 --- a/src/simulator/forces/mod.rs +++ b/src/simulator/forces/mod.rs @@ -6,7 +6,7 @@ use avian3d::prelude::*; use bevy::prelude::*; use bevy_trait_query; -use super::{Atmosphere, Density, Mass, Volume}; +use super::{Atmosphere, Density, Mass, Volume, SimulatedBody}; pub struct ForcesPlugin; @@ -20,12 +20,16 @@ impl Plugin for ForcesPlugin { Update, ( ForceUpdateOrder::First, - ForceUpdateOrder::Prepare, - ForceUpdateOrder::Apply, - ) - .before(PhysicsStepSet::First), + ForceUpdateOrder::Prepare.after(ForceUpdateOrder::First), + ForceUpdateOrder::Apply + .after(ForceUpdateOrder::Prepare) + .before(PhysicsStepSet::First), + ), + ); + app.add_systems( + Update, + on_simulated_body_added.in_set(ForceUpdateOrder::First), ); - app.add_systems(Update, on_rigid_body_added.in_set(ForceUpdateOrder::First)); app.add_systems( Update, update_total_external_force.in_set(ForceUpdateOrder::Apply), @@ -42,21 +46,19 @@ enum ForceUpdateOrder { Apply, } -#[derive(Bundle)] +/// A bundle of force components to be added to entities with a `RigidBody`. The +/// body can only have one of each type of force component. +#[derive(Bundle, Default)] pub struct ForceBundle { weight: body::Weight, buoyancy: body::Buoyancy, drag: aero::Drag, } -/// Add a `ForceCollection` to entities with a `RigidBody` when they are added. -fn on_rigid_body_added(mut commands: Commands, query: Query>) { +/// Add a `ForceBundle` to entities with a `RigidBody` when they are added. +fn on_simulated_body_added(mut commands: Commands, query: Query>) { for entity in &query { - commands.entity(entity).insert(ForceBundle { - weight: body::Weight::default(), - buoyancy: body::Buoyancy::default(), - drag: aero::Drag::default(), - }); + commands.entity(entity).insert(ForceBundle::default()); } } @@ -66,6 +68,9 @@ fn on_rigid_body_added(mut commands: Commands, query: Query String { + String::from("Force") + } fn force(&self) -> Vec3; fn direction(&self) -> Vec3 { self.force().normalize() @@ -79,19 +84,23 @@ pub trait Force { /// Set the `ExternalForce` to the sum of all forces in the `Forces` collection. /// This effectively applies all the calculated force vectors to the physics /// rigid body without regard to where the forces came from. -/// +/// /// TODO: preserve the position of the total force vector and apply it at that /// point instead of the center of mass. fn update_total_external_force( - mut body_forces: Query<(&mut ExternalForce, &dyn Force), With>, + mut body_forces: Query<(&mut ExternalForce, &dyn Force, &RigidBody), With>, ) { // Iterate over each entity that has force vector components. - for (mut physics_force_component, acting_forces) in body_forces.iter_mut() { - let mut net_force = Vec3::ZERO; // reset the net force to zero - // Iterate over each force vector component and compute its value. - for force in acting_forces { - net_force += force.force(); + for (mut physics_force_component, acting_forces, rigid_body) in body_forces.iter_mut() { + if rigid_body.is_dynamic() { + let mut net_force = Vec3::ZERO; // reset the net force to zero + + // Iterate over each force vector component and compute its value. + for force in acting_forces.iter() { + net_force += force.force(); + info!("{}: {:?}", force.name(), force.force()); + } + physics_force_component.set_force(net_force); } - physics_force_component.set_force(net_force); } } diff --git a/src/simulator/forces/scratch.rs b/src/simulator/forces/scratch.rs new file mode 100644 index 0000000..f9fdbae --- /dev/null +++ b/src/simulator/forces/scratch.rs @@ -0,0 +1,232 @@ + +use std::f64::consts::PI; +use nalgebra as na; + +// Constants +const AIR_DENSITY: f64 = 1.225; // kg/m³ at sea level, 15°C +const AIR_VISCOSITY: f64 = 1.81e-5; // kg/(m·s) at 15°C + +#[derive(Debug, Clone)] +struct Vertex(na::Point3); + +#[derive(Debug, Clone)] +struct Face { + vertices: Vec, // Indices into vertex array + normal: na::Vector3, + area: f64, +} + +#[derive(Debug)] +struct IrregularPolyhedron { + vertices: Vec, + faces: Vec, + center_of_mass: na::Point3, + orientation: na::Rotation3, +} + +impl IrregularPolyhedron { + fn new(vertices: Vec<[f64; 3]>, faces: Vec>) -> Self { + let vertices: Vec = vertices + .into_iter() + .map(|v| Vertex(na::Point3::new(v[0], v[1], v[2]))) + .collect(); + + let mut polyhedron_faces = Vec::new(); + for face_vertices in faces { + let v0 = vertices[face_vertices[0]].0; + let v1 = vertices[face_vertices[1]].0; + let v2 = vertices[face_vertices[2]].0; + + // Calculate face normal and area + let edge1 = v1 - v0; + let edge2 = v2 - v0; + let normal = edge1.cross(&edge2).normalize(); + + // Calculate area using Newell's method for arbitrary polygons + let mut area = 0.0; + for i in 0..face_vertices.len() { + let j = (i + 1) % face_vertices.len(); + let vi = vertices[face_vertices[i]].0; + let vj = vertices[face_vertices[j]].0; + area += (vj - vi).norm() * (vi + vj).norm() / 2.0; + } + + polyhedron_faces.push(Face { + vertices: face_vertices, + normal: normal, + area, + }); + } + + // Calculate center of mass (assuming uniform density) + let com = vertices.iter().fold(na::Point3::origin(), |acc, v| acc + v.0.coords) + / vertices.len() as f64; + + IrregularPolyhedron { + vertices, + faces: polyhedron_faces, + center_of_mass: com, + orientation: na::Rotation3::identity(), + } + } + + fn rotate(&mut self, axis: &na::Vector3, angle: f64) { + self.orientation *= na::Rotation3::from_axis_angle(&na::Unit::new_normalize(*axis), angle); + } + + fn get_projected_area(&self, flow_direction: &na::Vector3) -> f64 { + let flow_dir = self.orientation * flow_direction; + + // Calculate projected area by summing contributions from each face + self.faces.iter().map(|face| { + let cos_angle = face.normal.dot(&flow_dir).abs(); + face.area * cos_angle + }).sum() + } + + fn get_characteristic_length(&self) -> f64 { + // Use maximum distance between any two vertices as characteristic length + let mut max_distance = 0.0; + for (i, v1) in self.vertices.iter().enumerate() { + for v2 in self.vertices.iter().skip(i + 1) { + let distance = (v2.0 - v1.0).norm(); + max_distance = max_distance.max(distance); + } + } + max_distance + } + + fn get_surface_area(&self) -> f64 { + self.faces.iter().map(|face| face.area).sum() + } +} + +struct FlowConditions { + velocity: na::Vector3, + density: f64, + viscosity: f64, +} + +impl Default for FlowConditions { + fn default() -> Self { + FlowConditions { + velocity: na::Vector3::new(10.0, 0.0, 0.0), // 10 m/s in x direction + density: AIR_DENSITY, + viscosity: AIR_VISCOSITY, + } + } +} + +fn calculate_reynolds_number(length: f64, velocity: f64, conditions: &FlowConditions) -> f64 { + (conditions.density * velocity * length) / conditions.viscosity +} + +fn calculate_drag_coefficients(reynolds: f64, shape_complexity: f64) -> (f64, f64) { + // Pressure drag coefficient + let cd_pressure = match reynolds { + re if re < 1.0 => 24.0 / re, + re if re < 1000.0 => 24.0 / re + 6.0 / (1.0 + re.sqrt()), + re if re < 2e5 => 1.1 * shape_complexity, + re if re < 7e5 => 0.8 * shape_complexity, + _ => 0.6 * shape_complexity, + }; + + // Friction drag coefficient + let cd_friction = match reynolds { + re if re < 1e5 => 1.328 / re.sqrt(), + _ => 0.074 / reynolds.powf(0.2), + }; + + (cd_pressure, cd_friction) +} + +struct DragResult { + pressure_drag: na::Vector3, + friction_drag: na::Vector3, + reynolds_number: f64, + cd_pressure: f64, + cd_friction: f64, +} + +fn calculate_drag(polyhedron: &IrregularPolyhedron, conditions: &FlowConditions) -> DragResult { + let velocity_magnitude = conditions.velocity.norm(); + let flow_direction = conditions.velocity.normalize(); + + // Get characteristic length and Reynolds number + let char_length = polyhedron.get_characteristic_length(); + let reynolds = calculate_reynolds_number(char_length, velocity_magnitude, conditions); + + // Calculate shape complexity factor (ratio of actual surface area to minimum possible surface area) + let actual_surface_area = polyhedron.get_surface_area(); + let min_surface_area = 4.0 * PI * (char_length / 2.0).powi(2); // sphere surface area + let shape_complexity = actual_surface_area / min_surface_area; + + // Get drag coefficients + let (cd_pressure, cd_friction) = calculate_drag_coefficients(reynolds, shape_complexity); + + // Calculate projected area + let projected_area = polyhedron.get_projected_area(&flow_direction); + + // Calculate drag forces + let dynamic_pressure = 0.5 * conditions.density * velocity_magnitude.powi(2); + + let pressure_drag = flow_direction * (cd_pressure * dynamic_pressure * projected_area); + let friction_drag = flow_direction * (cd_friction * dynamic_pressure * actual_surface_area); + + DragResult { + pressure_drag, + friction_drag, + reynolds_number: reynolds, + cd_pressure, + cd_friction, + } +} + +fn main() { + // Example usage with an irregular tetrahedron + let vertices = vec![ + [0.0, 0.0, 0.0], + [1.0, 0.0, 0.0], + [0.3, 1.2, 0.0], + [0.5, 0.4, 1.5], + ]; + + let faces = vec![ + vec![0, 1, 2], + vec![0, 1, 3], + vec![1, 2, 3], + vec![0, 2, 3], + ]; + + let mut polyhedron = IrregularPolyhedron::new(vertices, faces); + + // Rotate the polyhedron 30 degrees around the y-axis + polyhedron.rotate(&na::Vector3::new(0.0, 1.0, 0.0), PI / 6.0); + + let conditions = FlowConditions::default(); + + let result = calculate_drag(&polyhedron, &conditions); + + println!("Irregular Polyhedron Analysis:"); + println!("Characteristic Length: {:.3} m", polyhedron.get_characteristic_length()); + println!("Surface Area: {:.3} m²", polyhedron.get_surface_area()); + println!("Projected Area: {:.3} m²", + polyhedron.get_projected_area(&conditions.velocity.normalize())); + + println!("\nFlow Conditions:"); + println!("Velocity: [{:.1}, {:.1}, {:.1}] m/s", + conditions.velocity.x, conditions.velocity.y, conditions.velocity.z); + + println!("\nResults:"); + println!("Reynolds Number: {:.1e}", result.reynolds_number); + println!("Pressure Drag Coefficient: {:.3}", result.cd_pressure); + println!("Friction Drag Coefficient: {:.3}", result.cd_friction); + println!("Pressure Drag: [{:.3}, {:.3}, {:.3}] N", + result.pressure_drag.x, result.pressure_drag.y, result.pressure_drag.z); + println!("Friction Drag: [{:.3}, {:.3}, {:.3}] N", + result.friction_drag.x, result.friction_drag.y, result.friction_drag.z); + println!("Total Drag: [{:.3}, {:.3}, {:.3}] N", + result.pressure_drag.x + result.friction_drag.x, + result.pressure_drag.y + result.friction_drag.y, + result.pressure_drag.z + result.friction_drag.z); +} diff --git a/src/simulator/mod.rs b/src/simulator/mod.rs index e6be410..858fc03 100644 --- a/src/simulator/mod.rs +++ b/src/simulator/mod.rs @@ -13,6 +13,11 @@ use avian3d::prelude::*; pub use properties::{Temperature, Pressure, Volume, Density, Mass}; pub use atmosphere::Atmosphere; +/// A marker component for entities that are simulated. +#[derive(Component, Default)] +pub struct SimulatedBody; + + pub struct SimulatorPlugins; impl PluginGroup for SimulatorPlugins { diff --git a/src/simulator/properties.rs b/src/simulator/properties.rs index 947e0b8..7fc57c2 100644 --- a/src/simulator/properties.rs +++ b/src/simulator/properties.rs @@ -34,11 +34,6 @@ fn sphere_surface_area(radius: f32) -> f32 { 4.0 * PI * f32::powf(radius, 2.0) } -pub fn projected_spherical_area(volume: f32) -> f32 { - // Get the projected area (m^2) of a sphere with a given volume (m³) - f32::powf(sphere_radius_from_volume(volume), 2.0) * PI -} - pub struct CorePropertiesPlugin; impl Plugin for CorePropertiesPlugin { diff --git a/src/ui/dev_tools.rs b/src/ui/dev_tools.rs deleted file mode 100644 index 4b7964a..0000000 --- a/src/ui/dev_tools.rs +++ /dev/null @@ -1,103 +0,0 @@ -//! 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; - -const TOGGLE_DEBUG_UI_KEY: KeyCode = KeyCode::F3; -const TOGGLE_WIREFRAME_KEY: KeyCode = KeyCode::F4; - -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) - .run_if(input_just_pressed(TOGGLE_DEBUG_UI_KEY)), - ); - #[cfg(not(target_arch = "wasm32"))] - app.add_systems( - Update, - toggle_wireframe.run_if(input_just_pressed(TOGGLE_WIREFRAME_KEY)), - ); - - #[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>, -) { - 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, - keyboard: Res>, -) { - if keyboard.just_pressed(TOGGLE_WIREFRAME_KEY) { - wireframe_config.global = !wireframe_config.global; - } -} diff --git a/src/ui/monitors.rs b/src/ui/monitors.rs deleted file mode 100644 index 70eedb2..0000000 --- a/src/ui/monitors.rs +++ /dev/null @@ -1,16 +0,0 @@ -//! UI for monitoring the simulation. - -use bevy::prelude::*; -use iyes_perf_ui::prelude::*; - -/// Plugin for the monitor UI. -pub struct MonitorPlugin; - -impl Plugin for MonitorPlugin { - fn build(&self, app: &mut App) { - app - // we must register our custom entry type - .add_perf_ui_simple_entry::() - .init_resource::(); - } -} diff --git a/src/ui/vectors.rs b/src/ui/vectors.rs deleted file mode 100644 index a262d21..0000000 --- a/src/ui/vectors.rs +++ /dev/null @@ -1,37 +0,0 @@ -use bevy::prelude::*; -use avian3d::prelude::*; -use bevy_prototype_debug_lines::*; - -use crate::simulator::forces::Force; - -pub struct ForceVectorPlugin; - -impl Plugin for ForceVectorPlugin { - fn build(&self, app: &mut App) { - app.add_plugins(DebugLines::default()); - app.add_systems(Update, visualize_forces); - } -} - -/// System to visualize all force vectors using DebugLines -fn visualize_forces( - query: Query<(&Transform, &dyn Force), With>, - mut debug_lines: ResMut, -) { - for (transform, force) in query.iter() { - let origin = force.point_of_application().unwrap_or(transform.translation); - let force_vector = force.force(); - - // Define the color based on force type or magnitude if needed - let color = match force.downcast_ref::() { - Some(_) => Color::RED, - None => match force.downcast_ref::() { - Some(_) => Color::GREEN, - None => Color::BLUE, - }, - }; - - // Draw the force vector - debug_lines.line(origin, origin + force_vector, color, 0.0); - } -}