diff --git a/Cargo.lock b/Cargo.lock index 2b70f1e..8ada8a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -294,7 +294,6 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18e71a9143ac21bed247c30129399af8be170309e7ff5983a1bd37e87d3da520" dependencies = [ - "bevy_dylib", "bevy_internal", ] @@ -456,15 +455,6 @@ dependencies = [ "sysinfo", ] -[[package]] -name = "bevy_dylib" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2107d1e8f3d5320ae02b3bf4b2b98c078f225a78a93b01fa29d619bc170f2d9b" -dependencies = [ - "bevy_internal", -] - [[package]] name = "bevy_ecs" version = "0.11.2" @@ -2612,6 +2602,14 @@ dependencies = [ "redox_syscall", ] +[[package]] +name = "orbital" +version = "0.1.0" +source = "git+https://github.com/oli-obk/solar_sailors.git?rev=2fabdd044f0f362595494dcad5102c50ae9572f7#2fabdd044f0f362595494dcad5102c50ae9572f7" +dependencies = [ + "tracing", +] + [[package]] name = "overload" version = "0.1.1" @@ -3069,8 +3067,12 @@ dependencies = [ name = "spaceflight" version = "0.1.0" dependencies = [ + "ahash 0.8.3", "bevy", "bevy_rapier3d", + "glam", + "indexmap 2.0.0", + "orbital", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index a00fab0..323b1e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,8 +12,12 @@ opt-level = 1 opt-level = 3 [dependencies] -bevy = { version = "0.11.2", features = ["dynamic_linking"] } +ahash = "0.8.3" +bevy = { version = "0.11.2", features = [] } bevy_rapier3d = { version = "0.22.0", features = [ "simd-stable", "debug-render-3d", ] } +glam = { version = "0.24.1", features = ["debug-glam-assert"] } +indexmap = "2.0.0" +orbital = { git = "https://github.com/oli-obk/solar_sailors.git", rev = "2fabdd044f0f362595494dcad5102c50ae9572f7" } diff --git a/src/forces.rs b/src/forces.rs new file mode 100644 index 0000000..e52b8ee --- /dev/null +++ b/src/forces.rs @@ -0,0 +1,35 @@ +use std::any::TypeId; + +use bevy::prelude::*; +use bevy_rapier3d::prelude::ExternalForce; +use indexmap::IndexMap; + +#[derive(Component, Default)] +pub struct ExternalForceSet { + forces: IndexMap, +} + +impl ExternalForceSet { + pub fn get(&self) -> ExternalForce { + self.forces + .get(&TypeId::of::()) + .copied() + .unwrap_or_default() + } + + pub fn set(&mut self, force: ExternalForce) { + self.forces.insert(TypeId::of::(), force); + } + + fn combine(&self) -> ExternalForce { + self.forces + .values() + .fold(ExternalForce::default(), |f1, &f2| f1 + f2) + } +} + +pub fn update_external_forces(mut query: Query<(&mut ExternalForce, &ExternalForceSet)>) { + for (mut force, forces) in &mut query { + *force = forces.combine(); + } +} diff --git a/src/main.rs b/src/main.rs index 30b3722..967c037 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,20 @@ -//! A simple 3D scene with light shining over a cube sitting on a plane. +mod forces; use bevy::{ audio::PlaybackMode, input::mouse::{MouseMotion, MouseWheel}, prelude::*, - window::PrimaryWindow, + window::{CursorGrabMode, PrimaryWindow}, }; use bevy_rapier3d::prelude::*; +use forces::ExternalForceSet; + +use crate::forces::update_external_forces; fn main() { + dbg!(orbital::Orbit::from_pos_dir(42000.0, 0.0, 0.0, 3074.0)); + return; + App::new() .add_plugins(DefaultPlugins) .add_plugins(RapierPhysicsPlugin::::default()) @@ -17,9 +23,11 @@ fn main() { .add_systems( Update, ( + update_external_forces, fire_thrusters, orbit_camera, - apply_gravity, + apply_gravity.before(update_external_forces), + debug_spaceship_orbit, bevy::window::close_on_esc, ), ) @@ -34,18 +42,14 @@ struct SpaceshipBundle { body: RigidBody, collider: Collider, restitution: Restitution, - mass: Mass, thrusters: Thrusters, thruster_force: ExternalForce, + forces: ExternalForceSet, } #[derive(Component)] struct Spaceship; -/// Mass in kg. -#[derive(Component)] -struct Mass(f32); - #[derive(Component)] struct Thrusters { /// Strength in some units @@ -53,7 +57,7 @@ struct Thrusters { } #[derive(Component)] -struct HasGravity { +struct GravityAttractor { mass: f32, } @@ -68,11 +72,13 @@ struct ThrusterSound; fn fire_thrusters( mut commands: Commands, keyboard_input: Res>, - mut query: Query<(&mut ExternalForce, &Thrusters)>, + mut query: Query<(&mut ExternalForceSet, &Transform, &Thrusters)>, sound_query: Query<&AudioSink, With>, asset_server: Res, ) { - let (mut force, thrusters) = query.single_mut(); + struct ThrusterForce; + + let (mut force_set, transform, thrusters) = query.single_mut(); if keyboard_input.just_pressed(KeyCode::Space) { if let Ok(sound) = sound_query.get_single() { @@ -89,23 +95,127 @@ fn fire_thrusters( ThrusterSound, )); } - } - - if keyboard_input.just_pressed(KeyCode::Space) { - force.force = Vec3 { - x: 0.0, - y: thrusters.strength, - z: 0.0, - }; } else if keyboard_input.just_released(KeyCode::Space) { if let Ok(sound) = sound_query.get_single() { sound.pause(); } + } + + let rotation = Mat3::from_quat(transform.rotation); + + let mut force = force_set.get::(); + + if keyboard_input.pressed(KeyCode::Space) { + force.force = rotation.mul_vec3(Vec3::new(0.0, thrusters.strength, 0.0)); + } else { force.force = Vec3::ZERO; } + + let torque = 0.2; + let keybinds = [ + (KeyCode::W, Vec3::new(torque, 0.0, 0.0)), + (KeyCode::S, Vec3::new(-torque, -0.0, 0.0)), + (KeyCode::Q, Vec3::new(0.0, torque, 0.0)), + (KeyCode::E, Vec3::new(0.0, -torque, 0.0)), + (KeyCode::A, Vec3::new(0.0, 0.0, torque)), + (KeyCode::D, Vec3::new(0.0, -0.0, -torque)), + ]; + + let mut any_pressed = false; + for (bind, vec) in keybinds { + if keyboard_input.pressed(bind) { + any_pressed = true; + force.torque = rotation.mul_vec3(vec); + } + } + if !any_pressed { + force.torque = Vec3::ZERO; + } + + force_set.set::(force); +} + +fn apply_gravity( + mut query: Query<(&mut ExternalForceSet, &Transform), With>, + body_query: Query<(&GravityAttractor, &Transform), Without>, +) { + struct GravityForce; + + const G: f64 = 1.0; + + let (mut ship_forces, ship_transform) = query.single_mut(); + + for (gravity, body_transform) in &body_query { + let distance = ship_transform + .translation + .distance(body_transform.translation) as f64; + + let fg = (G * (gravity.mass as f64)) / (distance * distance); + let direction = (body_transform.translation - ship_transform.translation).normalize(); + + let fg = ExternalForce { + force: direction * (fg as f32), + torque: Vec3::ZERO, + }; + + ship_forces.set::(fg); + } } -fn apply_gravity(query: Query<&mut ExternalForce, With>) {} +fn debug_spaceship_orbit( + query: Query<(&Transform, &Velocity), With>, + body_query: Query<&Transform, (With, Without)>, + mut gizmos: Gizmos, +) { + let (ship_transform, &v) = query.single(); + + let ship_pos = ship_transform.translation; + let body_transform = body_query.single(); + let body_pos = body_transform.translation; + + let body_rotation = body_transform.rotation; + let body_axis = body_rotation * Vec3::Y; + + gizmos.ray(body_pos, body_axis * 150.0, Color::GOLD); + gizmos.ray(body_pos, -body_axis * 150.0, Color::GOLD); + + let velocity = v.linvel; + let translation = ship_pos - body_pos; + + let orbital_plane_normal = velocity.cross(translation).normalize_or_zero() * 10.0; + gizmos.ray(ship_pos, orbital_plane_normal, Color::PINK); + + let orbital_plane_rot = Quat::from_rotation_arc( + orbital_plane_normal.try_normalize().unwrap_or(Vec3::X), + Vec3::Y, + ); + + let rotated_vel = orbital_plane_rot * velocity; + let rotated_pos = orbital_plane_rot * translation; + dbg!((rotated_pos, rotated_vel)); + + gizmos.ray(body_pos, rotated_pos, Color::FUCHSIA); + gizmos.ray( + body_pos, + rotated_vel.normalize_or_zero() * 120.0, + Color::OLIVE, + ); + + if true && (rotated_pos.length() > 0.0 && rotated_vel.length() > 0.1 && rotated_vel.z > 0.1) { + let orbit = orbital::Orbit::from_pos_dir( + rotated_pos.x.into(), + rotated_pos.z.into(), + rotated_vel.x.into(), + rotated_vel.z.into(), + ); + dbg!(orbit); + } + + gizmos.ray_gradient(ship_pos, velocity, Color::RED, Color::GREEN); + gizmos.ray_gradient(ship_pos, translation, Color::BLUE, Color::GREEN); + + gizmos.line(body_transform.translation, ship_pos, Color::WHITE); +} // adapted from https://bevy-cheatbook.github.io/cookbook/pan-orbit-camera.html fn orbit_camera( @@ -143,13 +253,14 @@ fn orbit_camera( /// set up a simple 3D scene fn setup( + mut windows: Query<&mut Window>, mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, ) { let mut rapier = RapierConfiguration::default(); // We ain't a normal game, we do our own gravity. - rapier.gravity = Vec3::new(0.0, -0.5, 0.0); + rapier.gravity = Vec3::new(0.0, 0.0, 0.0); commands.insert_resource(rapier); commands.spawn(PlanetBundle::new( @@ -157,7 +268,7 @@ fn setup( &mut materials, Transform::from_xyz(0.0, -100.0, 0.0), 100.0, - 0.0, + 5000.0, Color::rgb(0.2, 0.8, 0.5), )); @@ -185,6 +296,10 @@ fn setup( radius: camera_translation.length(), }, )); + + let mut window = windows.single_mut(); + // window.cursor.visible = false; + window.cursor.grab_mode = CursorGrabMode::Locked; } impl SpaceshipBundle { @@ -192,7 +307,7 @@ impl SpaceshipBundle { let height = 4.0; let width = 0.5; - Self { + SpaceshipBundle { ship_marker: Spaceship, model: PbrBundle { mesh: meshes.add(Mesh::from(shape::Box::new(width, height, width))), @@ -205,10 +320,13 @@ impl SpaceshipBundle { ..default() }, vel: Velocity { - linvel: Vec3::ZERO, + linvel: Vec3 { + x: 100.0, + y: 100.0, + z: 100.0, + }, angvel: Vec3::ZERO, }, - mass: Mass(1000.0), body: RigidBody::Dynamic, collider: Collider::cuboid(width / 2.0, height / 2.0, width / 2.0), restitution: Restitution::coefficient(0.1), @@ -217,6 +335,7 @@ impl SpaceshipBundle { force: Vec3::new(0.0, -0.5, 0.0), // gravity torque: Vec3::ZERO, }, + forces: ExternalForceSet::default(), } } } @@ -225,7 +344,7 @@ impl SpaceshipBundle { struct PlanetBundle { mesh: PbrBundle, coll: Collider, - gravity: HasGravity, + gravity: GravityAttractor, } impl PlanetBundle { @@ -252,7 +371,7 @@ impl PlanetBundle { ..default() }, coll: Collider::ball(radius), - gravity: HasGravity { mass }, + gravity: GravityAttractor { mass }, } } }